From 0edbc4290e2dcd49128acaf382b268c98e976a8a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 6 Apr 2026 23:37:52 +0000 Subject: [PATCH 1/5] feat: add toJson/fromJson methods to LookupByPath for serialization/deserialization Agent-Logs-Url: https://github.com/microsoft/rushstack/sessions/3c398d38-9717-42ba-a657-ca2385a2a8f3 Co-authored-by: dmichon-msft <26827560+dmichon-msft@users.noreply.github.com> --- common/reviews/api/lookup-by-path.api.md | 16 ++ libraries/lookup-by-path/src/LookupByPath.ts | 153 +++++++++++++ libraries/lookup-by-path/src/index.ts | 8 +- .../src/test/LookupByPath.test.ts | 215 ++++++++++++++++++ 4 files changed, 391 insertions(+), 1 deletion(-) diff --git a/common/reviews/api/lookup-by-path.api.md b/common/reviews/api/lookup-by-path.api.md index 66336af9e63..dec5eefc235 100644 --- a/common/reviews/api/lookup-by-path.api.md +++ b/common/reviews/api/lookup-by-path.api.md @@ -16,6 +16,13 @@ export interface IGetFirstDifferenceInCommonNodesOptions { second: IReadonlyPathTrieNode; } +// @beta +export interface ILookupByPathJson { + delimiter: string; + tree: ISerializedPathTrieNode; + values: TSerialized[]; +} + // @beta export interface IPrefixMatch { index: number; @@ -35,6 +42,7 @@ export interface IReadonlyLookupByPath extends Iterable<[strin groupByChild(infoByPath: Map, delimiter?: string): Map>; has(query: string, delimiter?: string): boolean; get size(): number; + toJson(serializeValue: (value: TItem) => TSerialized): ILookupByPathJson; // (undocumented) get tree(): IReadonlyPathTrieNode; } @@ -45,6 +53,12 @@ export interface IReadonlyPathTrieNode { readonly value: TItem | undefined; } +// @beta +export interface ISerializedPathTrieNode { + children?: Record; + valueIndex?: number; +} + // @beta export class LookupByPath implements IReadonlyLookupByPath { [Symbol.iterator](query?: string, delimiter?: string): IterableIterator<[string, TItem]>; @@ -57,6 +71,7 @@ export class LookupByPath implements IReadonlyLookupByPath): TItem | undefined; findLongestPrefixMatch(query: string, delimiter?: string): IPrefixMatch | undefined; + static fromJson(json: ILookupByPathJson, deserializeValue: (serialized: TSerialized) => TItem): LookupByPath; get(key: string, delimiter?: string): TItem | undefined; getNodeAtPrefix(query: string, delimiter?: string): IReadonlyPathTrieNode | undefined; groupByChild(infoByPath: Map, delimiter?: string): Map>; @@ -65,6 +80,7 @@ export class LookupByPath implements IReadonlyLookupByPath, value: TItem): this; get size(): number; + toJson(serializeValue: (value: TItem) => TSerialized): ILookupByPathJson; // (undocumented) get tree(): IReadonlyPathTrieNode; } diff --git a/libraries/lookup-by-path/src/LookupByPath.ts b/libraries/lookup-by-path/src/LookupByPath.ts index a6b3353371e..d60406abd21 100644 --- a/libraries/lookup-by-path/src/LookupByPath.ts +++ b/libraries/lookup-by-path/src/LookupByPath.ts @@ -36,6 +36,53 @@ export interface IReadonlyPathTrieNode { readonly children: ReadonlyMap> | undefined; } +/** + * JSON-serializable representation of a node in a {@link LookupByPath} trie. + * + * @beta + */ +export interface ISerializedPathTrieNode { + /** + * Index into the `values` array of the containing {@link ILookupByPathJson}. + * If `undefined`, this node has no associated value. + */ + valueIndex?: number; + + /** + * Child nodes keyed by path segment. + */ + children?: Record; +} + +/** + * JSON-serializable representation of a {@link LookupByPath} instance. + * + * @remarks + * The `values` array stores each unique value exactly once (by reference identity). + * Nodes in the tree reference values by their index in this array, which ensures that + * reference equality is preserved across serialization and deserialization. + * + * @beta + */ +export interface ILookupByPathJson { + /** + * The path delimiter used by the serialized trie. + */ + delimiter: string; + + /** + * Array of serialized values. Nodes in the tree reference values by their index in this array. + * Using an array with index-based references preserves reference equality: if multiple nodes + * share the same value (by reference), they will reference the same index. + */ + values: TSerialized[]; + + /** + * The serialized tree structure. + */ + tree: ISerializedPathTrieNode; +} + interface IPrefixEntry { /** * The prefix that was matched @@ -195,6 +242,18 @@ export interface IReadonlyLookupByPath extends Iterable<[strin * @returns The trie node at the specified prefix, or `undefined` if no node was found */ getNodeAtPrefix(query: string, delimiter?: string): IReadonlyPathTrieNode | undefined; + + /** + * Serializes this `LookupByPath` instance to a JSON-compatible representation. + * + * @param serializeValue - A function that converts a value of type `TItem` to a JSON-serializable form. + * @returns A JSON-serializable representation of this trie. + * + * @remarks + * Values that are reference-equal will be serialized once and referenced by index, ensuring + * that reference equality is preserved when deserialized via {@link LookupByPath.fromJson}. + */ + toJson(serializeValue: (value: TItem) => TSerialized): ILookupByPathJson; } /** @@ -545,6 +604,100 @@ export class LookupByPath implements IReadonlyLookupByPath( + serializeValue: (value: TItem) => TSerialized + ): ILookupByPathJson { + const valueToIndex: Map = new Map(); + const values: TSerialized[] = []; + + const getOrAddValueIndex: (value: TItem) => number = (value: TItem) => { + let index: number | undefined = valueToIndex.get(value); + if (index === undefined) { + index = values.length; + valueToIndex.set(value, index); + values.push(serializeValue(value)); + } + return index; + }; + + const serializeNode: (node: IPathTrieNode) => ISerializedPathTrieNode = ( + node: IPathTrieNode + ) => { + const result: ISerializedPathTrieNode = {}; + + if (node.value !== undefined) { + result.valueIndex = getOrAddValueIndex(node.value); + } + + if (node.children && node.children.size > 0) { + const children: Record = {}; + for (const [segment, child] of node.children) { + children[segment] = serializeNode(child); + } + result.children = children; + } + + return result; + }; + + return { + delimiter: this.delimiter, + values, + tree: serializeNode(this._root) + }; + } + + /** + * Deserializes a `LookupByPath` instance from a JSON representation previously + * created by {@link LookupByPath.toJson}. + * + * @param json - The JSON representation to deserialize. + * @param deserializeValue - A function that converts a serialized value back to its original type. + * @returns A new `LookupByPath` instance. + * + * @remarks + * Reference equality is preserved: if multiple nodes in the serialized trie pointed at the same + * value (i.e., the same index in the `values` array), the deserialized nodes will share the same + * object reference. + */ + public static fromJson( + json: ILookupByPathJson, + deserializeValue: (serialized: TSerialized) => TItem + ): LookupByPath { + const deserializedValues: TItem[] = json.values.map(deserializeValue); + + const result: LookupByPath = new LookupByPath(undefined, json.delimiter); + + const deserializeNode: ( + jsonNode: ISerializedPathTrieNode, + targetNode: IPathTrieNode + ) => void = (jsonNode: ISerializedPathTrieNode, targetNode: IPathTrieNode) => { + if (jsonNode.valueIndex !== undefined) { + targetNode.value = deserializedValues[jsonNode.valueIndex]; + result._size++; + } + + if (jsonNode.children) { + targetNode.children = new Map(); + for (const [segment, childJson] of Object.entries(jsonNode.children)) { + const childNode: IPathTrieNode = { + value: undefined, + children: undefined + }; + targetNode.children.set(segment, childNode); + deserializeNode(childJson, childNode); + } + } + }; + + deserializeNode(json.tree, result._root); + + return result; + } + /** * Iterates through progressively longer prefixes of a given string and returns as soon * as the number of candidate items that match the prefix are 1 or 0. diff --git a/libraries/lookup-by-path/src/index.ts b/libraries/lookup-by-path/src/index.ts index c91d2692ef9..25fbc6b065f 100644 --- a/libraries/lookup-by-path/src/index.ts +++ b/libraries/lookup-by-path/src/index.ts @@ -7,7 +7,13 @@ * @packageDocumentation */ -export type { IPrefixMatch, IReadonlyLookupByPath, IReadonlyPathTrieNode } from './LookupByPath'; +export type { + ILookupByPathJson, + IPrefixMatch, + IReadonlyLookupByPath, + IReadonlyPathTrieNode, + ISerializedPathTrieNode +} from './LookupByPath'; export { LookupByPath } from './LookupByPath'; export type { IGetFirstDifferenceInCommonNodesOptions } from './getFirstDifferenceInCommonNodes'; export { getFirstDifferenceInCommonNodes } from './getFirstDifferenceInCommonNodes'; diff --git a/libraries/lookup-by-path/src/test/LookupByPath.test.ts b/libraries/lookup-by-path/src/test/LookupByPath.test.ts index d785252dccb..b616efa494b 100644 --- a/libraries/lookup-by-path/src/test/LookupByPath.test.ts +++ b/libraries/lookup-by-path/src/test/LookupByPath.test.ts @@ -2,6 +2,7 @@ // See LICENSE in the project root for license information. import { LookupByPath } from '../LookupByPath'; +import type { ILookupByPathJson } from '../LookupByPath'; describe(LookupByPath.iteratePathSegments.name, () => { it('returns empty for an empty string', () => { @@ -673,3 +674,217 @@ describe(LookupByPath.prototype.groupByChild.name, () => { expect(falsyLookup.groupByChild(infoByPath)).toEqual(expected); }); }); + +describe('toJson and fromJson', () => { + it('round-trips an empty trie', () => { + const original = new LookupByPath(); + const json: ILookupByPathJson = original.toJson((v) => v); + const restored: LookupByPath = LookupByPath.fromJson(json, (v) => v); + + expect(restored.size).toEqual(0); + expect([...restored]).toEqual([]); + }); + + it('round-trips with number values', () => { + const original = new LookupByPath([ + ['foo', 1], + ['foo/bar', 2], + ['baz', 3] + ]); + + const json: ILookupByPathJson = original.toJson((v) => v); + const restored: LookupByPath = LookupByPath.fromJson(json, (v) => v); + + expect(restored.size).toEqual(3); + expect(restored.get('foo')).toEqual(1); + expect(restored.get('foo/bar')).toEqual(2); + expect(restored.get('baz')).toEqual(3); + expect(restored.get('missing')).toEqual(undefined); + }); + + it('round-trips with string values', () => { + const original = new LookupByPath([ + ['a', 'alpha'], + ['a/b', 'bravo'], + ['c', 'charlie'] + ]); + + const json: ILookupByPathJson = original.toJson((v) => v); + const restored: LookupByPath = LookupByPath.fromJson(json, (v) => v); + + expect(restored.size).toEqual(3); + expect(restored.get('a')).toEqual('alpha'); + expect(restored.get('a/b')).toEqual('bravo'); + expect(restored.get('c')).toEqual('charlie'); + }); + + it('preserves reference equality for shared values', () => { + const sharedObj = { name: 'shared' }; + const original = new LookupByPath<{ name: string }>([ + ['foo', sharedObj], + ['bar', sharedObj], + ['baz/qux', sharedObj] + ]); + + const json: ILookupByPathJson<{ name: string }> = original.toJson((v) => ({ ...v })); + // All three entries should point at the same index + expect(json.values.length).toEqual(1); + expect(json.values[0]).toEqual({ name: 'shared' }); + + const restored: LookupByPath<{ name: string }> = LookupByPath.fromJson(json, (v) => ({ ...v })); + + expect(restored.size).toEqual(3); + const fooVal = restored.get('foo'); + const barVal = restored.get('bar'); + const bazQuxVal = restored.get('baz/qux'); + + // All deserialized values should be the same reference + expect(fooVal).toBe(barVal); + expect(barVal).toBe(bazQuxVal); + expect(fooVal).toEqual({ name: 'shared' }); + }); + + it('keeps non-reference-equal objects with same JSON as separate entries', () => { + const obj1 = { name: 'same' }; + const obj2 = { name: 'same' }; + // Verify they are not reference-equal + expect(obj1).not.toBe(obj2); + + const original = new LookupByPath<{ name: string }>([ + ['foo', obj1], + ['bar', obj2] + ]); + + const json: ILookupByPathJson<{ name: string }> = original.toJson((v) => ({ ...v })); + // Should have two separate entries even though the JSON is the same + expect(json.values.length).toEqual(2); + + const restored: LookupByPath<{ name: string }> = LookupByPath.fromJson(json, (v) => ({ ...v })); + + expect(restored.size).toEqual(2); + const fooVal = restored.get('foo'); + const barVal = restored.get('bar'); + + // Values should be structurally equal + expect(fooVal).toEqual({ name: 'same' }); + expect(barVal).toEqual({ name: 'same' }); + + // But NOT reference-equal + expect(fooVal).not.toBe(barVal); + }); + + it('round-trips a complex multi-level tree', () => { + const original = new LookupByPath([ + ['foo', 1], + ['foo/bar', 2], + ['foo/bar/baz', 3], + ['foo/bar/baz/qux', 4], + ['bar', 5], + ['bar/baz', 6] + ]); + + const json: ILookupByPathJson = original.toJson((v) => v); + const restored: LookupByPath = LookupByPath.fromJson(json, (v) => v); + + expect(restored.size).toEqual(original.size); + for (const [path, value] of original) { + expect(restored.get(path)).toEqual(value); + } + }); + + it('round-trips with a custom delimiter', () => { + const original = new LookupByPath( + [ + ['foo,bar', 1], + ['foo,bar,baz', 2], + ['qux', 3] + ], + ',' + ); + + const json: ILookupByPathJson = original.toJson((v) => v); + expect(json.delimiter).toEqual(','); + + const restored: LookupByPath = LookupByPath.fromJson(json, (v) => v); + + expect(restored.delimiter).toEqual(','); + expect(restored.size).toEqual(3); + expect(restored.get('foo,bar')).toEqual(1); + expect(restored.get('foo,bar,baz')).toEqual(2); + expect(restored.get('qux')).toEqual(3); + }); + + it('uses custom serializer and deserializer', () => { + const original = new LookupByPath<{ id: number; label: string }>([ + ['a', { id: 1, label: 'one' }], + ['b', { id: 2, label: 'two' }] + ]); + + const json: ILookupByPathJson = original.toJson((v) => JSON.stringify(v)); + expect(json.values).toEqual(['{"id":1,"label":"one"}', '{"id":2,"label":"two"}']); + + const restored: LookupByPath<{ id: number; label: string }> = LookupByPath.fromJson( + json, + (v) => JSON.parse(v) as { id: number; label: string } + ); + + expect(restored.size).toEqual(2); + expect(restored.get('a')).toEqual({ id: 1, label: 'one' }); + expect(restored.get('b')).toEqual({ id: 2, label: 'two' }); + }); + + it('produces valid JSON for the serialized form', () => { + const original = new LookupByPath([ + ['foo', 1], + ['foo/bar', 2] + ]); + + const json: ILookupByPathJson = original.toJson((v) => v); + const jsonString: string = JSON.stringify(json); + const parsed: ILookupByPathJson = JSON.parse(jsonString) as ILookupByPathJson; + + const restored: LookupByPath = LookupByPath.fromJson(parsed, (v) => v); + expect(restored.size).toEqual(2); + expect(restored.get('foo')).toEqual(1); + expect(restored.get('foo/bar')).toEqual(2); + }); + + it('preserves findChildPath behavior after round-trip', () => { + const original = new LookupByPath([ + ['foo', 1], + ['foo/bar', 2], + ['baz', 3] + ]); + + const json: ILookupByPathJson = original.toJson((v) => v); + const restored: LookupByPath = LookupByPath.fromJson(json, (v) => v); + + expect(restored.findChildPath('foo/baz')).toEqual(1); + expect(restored.findChildPath('foo/bar/baz')).toEqual(2); + expect(restored.findChildPath('baz/anything')).toEqual(3); + expect(restored.findChildPath('missing')).toEqual(undefined); + }); + + it('handles nodes with children but no value', () => { + const original = new LookupByPath([ + ['foo/bar/baz', 1], + ['foo/bar/qux', 2] + ]); + + const json: ILookupByPathJson = original.toJson((v) => v); + // The intermediate nodes 'foo' and 'foo/bar' should exist in the tree but have no valueIndex + const fooNode = json.tree.children!.foo; + const barNode = fooNode.children!.bar; + expect(fooNode.valueIndex).toBeUndefined(); + expect(barNode.valueIndex).toBeUndefined(); + expect(barNode.children!.baz.valueIndex).toEqual(0); + expect(barNode.children!.qux.valueIndex).toEqual(1); + + const restored: LookupByPath = LookupByPath.fromJson(json, (v) => v); + expect(restored.size).toEqual(2); + expect(restored.get('foo')).toEqual(undefined); + expect(restored.get('foo/bar')).toEqual(undefined); + expect(restored.get('foo/bar/baz')).toEqual(1); + expect(restored.get('foo/bar/qux')).toEqual(2); + }); +}); From 7134c9b23ff0a4f1ea535426e1509a1fe46dd157 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 7 Apr 2026 21:17:31 +0000 Subject: [PATCH 2/5] fix: use Object.create(null) for dictionary object in toJson serialization Agent-Logs-Url: https://github.com/microsoft/rushstack/sessions/bf4dc31b-0da1-4979-b465-0bdff2d00028 Co-authored-by: dmichon-msft <26827560+dmichon-msft@users.noreply.github.com> --- libraries/lookup-by-path/src/LookupByPath.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/lookup-by-path/src/LookupByPath.ts b/libraries/lookup-by-path/src/LookupByPath.ts index d60406abd21..0a524a6d7fb 100644 --- a/libraries/lookup-by-path/src/LookupByPath.ts +++ b/libraries/lookup-by-path/src/LookupByPath.ts @@ -633,7 +633,7 @@ export class LookupByPath implements IReadonlyLookupByPath 0) { - const children: Record = {}; + const children: Record = Object.create(null); for (const [segment, child] of node.children) { children[segment] = serializeNode(child); } From 72c9eaf4e039301c621fff4f9e63ec4f69c0b00c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 7 Apr 2026 22:32:32 +0000 Subject: [PATCH 3/5] chore: add change file for @rushstack/lookup-by-path minor bump Agent-Logs-Url: https://github.com/microsoft/rushstack/sessions/a422a6e3-515f-4967-9ae3-eec7adf9edef Co-authored-by: dmichon-msft <26827560+dmichon-msft@users.noreply.github.com> --- ...-lookup-by-path-json-methods_2026-04-07-22-32.json | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 common/changes/@rushstack/lookup-by-path/enhance-lookup-by-path-json-methods_2026-04-07-22-32.json diff --git a/common/changes/@rushstack/lookup-by-path/enhance-lookup-by-path-json-methods_2026-04-07-22-32.json b/common/changes/@rushstack/lookup-by-path/enhance-lookup-by-path-json-methods_2026-04-07-22-32.json new file mode 100644 index 00000000000..4d2e08fe69f --- /dev/null +++ b/common/changes/@rushstack/lookup-by-path/enhance-lookup-by-path-json-methods_2026-04-07-22-32.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "comment": "Add toJson/fromJson methods for serialization/deserialization; use Object.create(null) for dictionary initialization", + "type": "minor", + "packageName": "@rushstack/lookup-by-path" + } + ], + "packageName": "@rushstack/lookup-by-path", + "email": "198982749+Copilot@users.noreply.github.com" +} From c3ccf78a63622c4c3da52008de8fe9ad62e2d015 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 7 Apr 2026 23:00:30 +0000 Subject: [PATCH 4/5] chore: update change file comment for toJson/fromJson Agent-Logs-Url: https://github.com/microsoft/rushstack/sessions/5d9f37a7-dbe9-4977-b391-4a4d359e0856 Co-authored-by: dmichon-msft <26827560+dmichon-msft@users.noreply.github.com> --- .../enhance-lookup-by-path-json-methods_2026-04-07-22-32.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/changes/@rushstack/lookup-by-path/enhance-lookup-by-path-json-methods_2026-04-07-22-32.json b/common/changes/@rushstack/lookup-by-path/enhance-lookup-by-path-json-methods_2026-04-07-22-32.json index 4d2e08fe69f..af900b75572 100644 --- a/common/changes/@rushstack/lookup-by-path/enhance-lookup-by-path-json-methods_2026-04-07-22-32.json +++ b/common/changes/@rushstack/lookup-by-path/enhance-lookup-by-path-json-methods_2026-04-07-22-32.json @@ -1,7 +1,7 @@ { "changes": [ { - "comment": "Add toJson/fromJson methods for serialization/deserialization; use Object.create(null) for dictionary initialization", + "comment": "Add toJson/fromJson methods to LookupByPath for serialization/deserialization", "type": "minor", "packageName": "@rushstack/lookup-by-path" } From dc1a4eea5c834d3f569e7e75a41f1e8e6cd41da2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 8 Apr 2026 21:08:59 +0000 Subject: [PATCH 5/5] Address PR feedback: backtick formatting in changelog, dynamic describe name, add snapshot tests Agent-Logs-Url: https://github.com/microsoft/rushstack/sessions/f33fe415-7fc0-4905-8330-774013ebbe50 Co-authored-by: dmichon-msft <26827560+dmichon-msft@users.noreply.github.com> --- ...by-path-json-methods_2026-04-07-22-32.json | 2 +- .../src/test/LookupByPath.test.ts | 27 ++++++++- .../__snapshots__/LookupByPath.test.ts.snap | 57 +++++++++++++++++++ 3 files changed, 84 insertions(+), 2 deletions(-) create mode 100644 libraries/lookup-by-path/src/test/__snapshots__/LookupByPath.test.ts.snap diff --git a/common/changes/@rushstack/lookup-by-path/enhance-lookup-by-path-json-methods_2026-04-07-22-32.json b/common/changes/@rushstack/lookup-by-path/enhance-lookup-by-path-json-methods_2026-04-07-22-32.json index af900b75572..b3e2d220c5f 100644 --- a/common/changes/@rushstack/lookup-by-path/enhance-lookup-by-path-json-methods_2026-04-07-22-32.json +++ b/common/changes/@rushstack/lookup-by-path/enhance-lookup-by-path-json-methods_2026-04-07-22-32.json @@ -1,7 +1,7 @@ { "changes": [ { - "comment": "Add toJson/fromJson methods to LookupByPath for serialization/deserialization", + "comment": "Add `toJson`/`fromJson` methods to `LookupByPath` for serialization/deserialization", "type": "minor", "packageName": "@rushstack/lookup-by-path" } diff --git a/libraries/lookup-by-path/src/test/LookupByPath.test.ts b/libraries/lookup-by-path/src/test/LookupByPath.test.ts index b616efa494b..2b8e8beeb65 100644 --- a/libraries/lookup-by-path/src/test/LookupByPath.test.ts +++ b/libraries/lookup-by-path/src/test/LookupByPath.test.ts @@ -675,7 +675,7 @@ describe(LookupByPath.prototype.groupByChild.name, () => { }); }); -describe('toJson and fromJson', () => { +describe(`${LookupByPath.prototype.toJson.name} and ${LookupByPath.fromJson.name}`, () => { it('round-trips an empty trie', () => { const original = new LookupByPath(); const json: ILookupByPathJson = original.toJson((v) => v); @@ -702,6 +702,31 @@ describe('toJson and fromJson', () => { expect(restored.get('missing')).toEqual(undefined); }); + it('snapshot: serialized JSON for a simple tree', () => { + const original = new LookupByPath([ + ['foo', 1], + ['foo/bar', 2], + ['baz', 3] + ]); + + const json: ILookupByPathJson = original.toJson((v) => v); + expect(json).toMatchSnapshot(); + }); + + it('snapshot: serialized JSON with intermediate nodes and custom delimiter', () => { + const original = new LookupByPath( + [ + ['a,b,c', 'deep'], + ['a,b', 'mid'], + ['x', 'top'] + ], + ',' + ); + + const json: ILookupByPathJson = original.toJson((v) => v); + expect(json).toMatchSnapshot(); + }); + it('round-trips with string values', () => { const original = new LookupByPath([ ['a', 'alpha'], diff --git a/libraries/lookup-by-path/src/test/__snapshots__/LookupByPath.test.ts.snap b/libraries/lookup-by-path/src/test/__snapshots__/LookupByPath.test.ts.snap new file mode 100644 index 00000000000..bf63c46b8d6 --- /dev/null +++ b/libraries/lookup-by-path/src/test/__snapshots__/LookupByPath.test.ts.snap @@ -0,0 +1,57 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`toJson and fromJson snapshot: serialized JSON for a simple tree 1`] = ` +Object { + "delimiter": "/", + "tree": Object { + "children": Object { + "baz": Object { + "valueIndex": 2, + }, + "foo": Object { + "children": Object { + "bar": Object { + "valueIndex": 1, + }, + }, + "valueIndex": 0, + }, + }, + }, + "values": Array [ + 1, + 2, + 3, + ], +} +`; + +exports[`toJson and fromJson snapshot: serialized JSON with intermediate nodes and custom delimiter 1`] = ` +Object { + "delimiter": ",", + "tree": Object { + "children": Object { + "a": Object { + "children": Object { + "b": Object { + "children": Object { + "c": Object { + "valueIndex": 1, + }, + }, + "valueIndex": 0, + }, + }, + }, + "x": Object { + "valueIndex": 2, + }, + }, + }, + "values": Array [ + "mid", + "deep", + "top", + ], +} +`;