diff --git a/src/childPaths.js b/src/childPaths.js index f3562da..f3d9d44 100644 --- a/src/childPaths.js +++ b/src/childPaths.js @@ -1,4 +1,5 @@ import { getContext } from "./context.js"; +import { enumerateChildPaths, getByPath } from "./util.js"; /** * Get a node's children and the corresponding properties and indices @@ -17,41 +18,21 @@ export default function childPaths (node) { return []; } - const childProperties = context.getChildProperties(node); + const paths = enumerateChildPaths(node, context.getChildProperties); let children = []; - if (childProperties) { - for (const property of childProperties) { - const child = node[property]; - // When the node is an array, we want to include the index in the result - if (Array.isArray(child)) { - let childPaths = child.map((c, index) => ({node: c, path: [property, index]})); - children.push(...childPaths); - } - else { - children.push({node: child, path: [property]}); - } + for (const path of paths) { + const child = getByPath(node, path); + if (Array.isArray(child)) { + const childPaths = child.map((c, index) => context.isNode(c) ? ({node: c, path: [...path, index]}) : null).filter(Boolean); + children.push(...childPaths); } - } - else { - for (let property in node) { - const child = node[property]; - - if (Array.isArray(child)) { - // Why not filter first? That would affect the index. - let childPaths = child.map((c, index) => (context.isNode(c) ? {node: c, path: [property, index]} : null)).filter(Boolean); - children.push(...childPaths); - } - else if (context.isNode(child)) { - children.push({node: child, path: [property]}); - } + else if (context.isNode(child)) { + children.push({node: child, path}); } + } return children; } - - - - diff --git a/src/children.js b/src/children.js index 1f3c77d..73f4683 100644 --- a/src/children.js +++ b/src/children.js @@ -1,4 +1,4 @@ -import { getContext } from "./context.js"; +import childPaths from "./childPaths.js"; /** * Get a node’s children as an array @@ -6,23 +6,5 @@ import { getContext } from "./context.js"; * @returns {(object | string | number | boolean | null)[]} */ export default function children (node) { - if (Array.isArray(node)) { - // when node is an array, flatten to avoid nested arrays of children - return node.flatMap(node => children(node)); - } - - let context = getContext(this); - - if (!context.isNode(node)) { - return []; - } - - const childProperties = context.getChildProperties(node); - - if (childProperties) { - return childProperties.flatMap(property => node[property] ?? []); - } - else { - return Object.values(node).flat(); - } + return childPaths.call(this, node).map(({node}) => node); } diff --git a/src/util.js b/src/util.js index 27f6198..6ad6f19 100644 --- a/src/util.js +++ b/src/util.js @@ -56,4 +56,43 @@ export function setByPath (obj, path, value) { const parent = getByPath(obj, parentPath); return parent && (parent[lastKey] = value); +} + +const wildcard = "*"; + +// handles all the ugly details of getting the child paths, including wildcards +export function enumerateChildPaths (node, getChildProperties) { + const propertyPaths = Array.isArray(getChildProperties) ? getChildProperties : getChildProperties(node); + // if no properties are specified, return the keys of the object by default + if (!propertyPaths) { + return Object.keys(node).map(key => [key]); + } + const paths = []; + console.log(propertyPaths); + for (let propertyPath of propertyPaths) { + // if it's a flat single property, wrap it in an array + propertyPath = Array.isArray(propertyPath) ? propertyPath : [propertyPath]; + // check for wildcard + if (propertyPath[propertyPath.length - 1] === wildcard) { + console.log("in here!", propertyPath) + // remove the wildcard + propertyPath.pop(); + // get the value of the property path + const value = getByPath(node, propertyPath); + // if the value is an array, enumerate its items + if (Array.isArray(value)) { + value.forEach((_, index) => { + paths.push([...propertyPath, index]); + }); + } + else { + // if the value is an object, enumerate its keys + paths.push(...Object.keys(value).map(key => [...propertyPath, key])); + } + } + else { + paths.push(propertyPath); + } + } + return paths; } \ No newline at end of file diff --git a/test/children.js b/test/children.js index 448dc84..935bfff 100644 --- a/test/children.js +++ b/test/children.js @@ -1,28 +1,67 @@ import childPaths from "../src/childPaths.js"; import trees from "./utils/trees.js"; +import Treecle from "../src/Treecle.js"; -const tree = trees[0]; +const basicTree = trees[0]; +const complexTree = trees[2]; + +const treecle = new Treecle({ + isNode: node => node && node.type, + getChildProperties: node => { + if (node.type === "single") { + return ["child"]; + } + else if (node.type === "multi") { + return [["children", "*"]]; + } + return []; + } +}) export default { name: "children", - run (node) { + run (node, context) { + if (context) { + return childPaths.call(context, node); + } return childPaths(node); }, tests: [ { name: "Root node", - args: [tree], - expect: [{node: tree.left, path: ["left"]}, {node: tree.right, path: ["right"]}] + args: [basicTree], + expect: [{node: basicTree.left, path: ["left"]}, {node: basicTree.right, path: ["right"]}] }, { name: "Non-root node with children", - args: [tree.right], - expect: [{node: tree.right.left, path: ["left"]}, {node: tree.right.right, path: ["right"]}], + args: [basicTree.right], + expect: [{node: basicTree.right.left, path: ["left"]}, {node: basicTree.right.right, path: ["right"]}], }, { name: "Leaf node", - args: [tree.left.left], + args: [basicTree.left.left], expect: [], + }, + { + name: "Custom settings root node", + args: [complexTree, treecle], + expect: [ + {node: complexTree.children.one, path: ["children", "one"]}, + {node: complexTree.children.two, path: ["children", "two"]}, + {node: complexTree.children.three, path: ["children", "three"]} + ] + }, + { + name: "Custom settings non-root node", + args: [complexTree.children.two, treecle], + expect: [ + {node: complexTree.children.two.child, path: ["child"]} + ] + }, + { + name: "Custom settings leaf node", + args: [complexTree.children.three.one, treecle], + expect: [] } ] }; diff --git a/test/utils/trees.js b/test/utils/trees.js index beaaedf..75e85f3 100644 --- a/test/utils/trees.js +++ b/test/utils/trees.js @@ -16,6 +16,18 @@ const trees = [ {type: "single", child: {type: "leaf", name: "4"}} ]} ] + }, + // tree with object children + { + type: "multi", + children: { + one: {type: "single", child: {type: "leaf", name: "1"}}, + two: {type: "single", child: {type: "leaf", name: "2"}}, + three: {type: "multi", children: { + one: {type: "single", child: {type: "leaf", name: "3"}}, + two: {type: "single", child: {type: "leaf", name: "4"}} + }} + } } ];