diff --git a/src/jsonData.js b/src/jsonData.js index 2094f00..3e9d517 100644 --- a/src/jsonData.js +++ b/src/jsonData.js @@ -461,7 +461,7 @@ export function expand (data, callback, expanded) { // console.log('expand', callback, expand) if (typeof callback === 'function') { - return transform (data, function (value, path, root) { + return transform (data, function (value, path) { if (value.type === 'Array' || value.type === 'Object') { if (callback(path)) { return setIn(value, ['expanded'], expanded) @@ -503,18 +503,51 @@ export function addErrors (data, errors) { } /** - * Merge one or multiple errors (for example JSON schema errors) - * into the data + * Search some text in all properties and values * * @param {JSONData} data - * @param {string} search + * @param {string} text + * @return {JSONData} Returns an updated `data` object containing the search results */ -export function setSearch (data, search) { - let updatedData = data +export function search (data, text) { + return transform(data, function (value) { + // search in values + if (value.type === 'value') { + if (containsCaseInsensitive(value.value, text)) { + return setIn(value, ['search'], true) + } + else { + return deleteIn(value, ['search']) + } + } - // TODO: traverse over the data, add a field search: 'field'|'value' to all matching fields/values + // search object property names + if (value.type === 'Object') { + let updatedProps = value.props + updatedProps.forEach((prop, index) => { + if (containsCaseInsensitive(prop.name, text)) { + updatedProps = setIn(updatedProps, [index, 'search'], true) + } + else { + updatedProps = deleteIn(updatedProps, [index, 'search']) + } + }) - return updatedData + return setIn(value, ['props'], updatedProps) + } + + return value + }) +} + +/** + * Do a case insensitive search for a search text in a text + * @param {String} text + * @param {String} search + * @return {boolean} Returns true if `search` is found in `text` + */ +export function containsCaseInsensitive (text, search) { + return String(text).toLowerCase().indexOf(search.toLowerCase()) !== -1 } /** @@ -543,8 +576,8 @@ function recurseTransform (value, path, root, callback) { let updatedItems = updatedValue.items updatedValue.items.forEach((item, index) => { - updatedItems = setIn(updatedItems, [index], - recurseTransform(item, path.concat(String(index)), root, callback)) + const updatedItem = recurseTransform(item, path.concat(String(index)), root, callback) + updatedItems = setIn(updatedItems, [index], updatedItem) }) updatedValue = setIn(updatedValue, ['items'], updatedItems) @@ -556,8 +589,8 @@ function recurseTransform (value, path, root, callback) { let updatedProps = updatedValue.props updatedValue.props.forEach((prop, index) => { - updatedProps = setIn(updatedProps, [index, 'value'], - recurseTransform(prop.value, path.concat(prop.name), root, callback)) + const updatedItem = recurseTransform(prop.value, path.concat(prop.name), root, callback) + updatedProps = setIn(updatedProps, [index, 'value'], updatedItem) }) updatedValue = setIn(updatedValue, ['props'], updatedProps) diff --git a/test/jsonData.test.js b/test/jsonData.test.js index c85694b..95b68f1 100644 --- a/test/jsonData.test.js +++ b/test/jsonData.test.js @@ -1,6 +1,6 @@ import test from 'ava'; import { - jsonToData, dataToJson, patchData, pathExists, transform, + jsonToData, dataToJson, patchData, pathExists, transform, search, parseJSONPointer, compileJSONPointer, expand, addErrors } from '../src/jsonData' @@ -237,6 +237,194 @@ const JSON_DATA_EXAMPLE_COLLAPSED_2 = { ] } +// after search for 'O' (case insensitive) +const JSON_DATA_EXAMPLE_SEARCH_1 = { + type: 'Object', + expanded: true, + props: [ + { + name: 'obj', + search: true, + value: { + type: 'Object', + expanded: true, + props: [ + { + name: 'arr', + value: { + type: 'Array', + expanded: true, + items: [ + { + type: 'value', + value: 1 + }, + { + type: 'value', + value: 2 + }, + { + type: 'Object', + expanded: true, + props: [ + { + name: 'a', + value: { + type: 'value', + value: 3 + } + }, + { + name: 'b', + value: { + type: 'value', + value: 4 + } + } + ] + }, + ] + } + } + ] + } + }, + { + name: 'str', + value: { + type: 'value', + value: 'hello world', + search: true + } + }, + { + name: 'nill', + value: { + type: 'value', + value: null + } + }, + { + name: 'bool', + search: true, + value: { + type: 'value', + value: false + } + } + ] +} + +// after search for '2' +const JSON_DATA_EXAMPLE_SEARCH_2 = { + type: 'Object', + expanded: true, + props: [ + { + name: 'obj', + value: { + type: 'Object', + expanded: true, + props: [ + { + name: 'arr', + value: { + type: 'Array', + expanded: true, + items: [ + { + type: 'value', + value: 1 + }, + { + type: 'value', + value: 2, + search: true + }, + { + type: 'Object', + expanded: true, + props: [ + { + name: 'a', + value: { + type: 'value', + value: 3 + } + }, + { + name: 'b', + value: { + type: 'value', + value: 4 + } + } + ] + }, + ] + } + } + ] + } + }, + { + name: 'str', + value: { + type: 'value', + value: 'hello world' + } + }, + { + name: 'nill', + value: { + type: 'value', + value: null + } + }, + { + name: 'bool', + value: { + type: 'value', + value: false + } + } + ] +} + +const JSON_DATA_SMALL = { + type: 'Object', + props: [ + { + name: 'obj', + value: { + type: 'Object', + props: [ + { + name: 'a', + value: { + type: 'value', + value: 2 + } + } + ] + } + }, + { + name: 'arr', + value: { + type: 'Array', + items: [ + { + type: 'value', + value: 3 + } + ] + } + } + ] +} + + const JSON_SCHEMA_ERRORS = [ {dataPath: '/obj/arr/2/b', message: 'String expected'}, {dataPath: '/nill', message: 'Null expected'} @@ -740,43 +928,10 @@ test('add and remove errors', t => { test('transform', t => { // {obj: {a: 2}, arr: [3]} - const data = { - type: 'Object', - props: [ - { - name: 'obj', - value: { - type: 'Object', - props: [ - { - name: 'a', - value: { - type: 'value', - value: 2 - } - } - ] - } - }, - { - name: 'arr', - value: { - type: 'Array', - items: [ - { - type: 'value', - value: 3 - } - ] - } - } - ] - } - - + let log = [] - const transformed = transform(data, function (value, path, root) { - t.truthy(root === data) + const transformed = transform(JSON_DATA_SMALL, function (value, path, root) { + t.truthy(root === JSON_DATA_SMALL) log.push([value, path, root]) @@ -792,21 +947,31 @@ test('transform', t => { // console.log('transformed', JSON.stringify(transformed, null, 2)) const EXPECTED_LOG = [ - [data, [], data], - [data.props[0].value, ['obj'], data], - [data.props[0].value.props[0].value, ['obj', 'a'], data], - [data.props[1].value, ['arr'], data], - [data.props[1].value.items[0], ['arr', '0'], data], + [JSON_DATA_SMALL, [], JSON_DATA_SMALL], + [JSON_DATA_SMALL.props[0].value, ['obj'], JSON_DATA_SMALL], + [JSON_DATA_SMALL.props[0].value.props[0].value, ['obj', 'a'], JSON_DATA_SMALL], + [JSON_DATA_SMALL.props[1].value, ['arr'], JSON_DATA_SMALL], + [JSON_DATA_SMALL.props[1].value.items[0], ['arr', '0'], JSON_DATA_SMALL], ] // log.forEach((row, index) => { // t.deepEqual(log[index], EXPECTED_LOG[index], 'should have equal log at index ' + index ) // }) t.deepEqual(log, EXPECTED_LOG) - t.truthy(transformed !== data) - t.truthy(transformed.props[0].value !== data.props[0].value) - t.truthy(transformed.props[0].value.props[0].value !== data.props[0].value.props[0].value) - t.truthy(data.props[1].value === data.props[1].value) - t.truthy(data.props[1].value.items[0] === data.props[1].value.items[0]) + t.truthy(transformed !== JSON_DATA_SMALL) + t.truthy(transformed.props[0].value !== JSON_DATA_SMALL.props[0].value) + t.truthy(transformed.props[0].value.props[0].value !== JSON_DATA_SMALL.props[0].value.props[0].value) + t.truthy(JSON_DATA_SMALL.props[1].value === JSON_DATA_SMALL.props[1].value) + t.truthy(JSON_DATA_SMALL.props[1].value.items[0] === JSON_DATA_SMALL.props[1].value.items[0]) }) + + +test('search', t => { + const result1 = search(JSON_DATA_EXAMPLE, 'O') + t.deepEqual(result1, JSON_DATA_EXAMPLE_SEARCH_1) + + // search for something else. Should clean up earlier search results + const result2 = search(result1, '2') + t.deepEqual(result2, JSON_DATA_EXAMPLE_SEARCH_2) +})