@hamak/navigation-utils 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +82 -0
- package/dist/es2015/filesystem/fs-node-abstract.types.js +1 -0
- package/dist/es2015/index.js +9 -0
- package/dist/es2015/itinerary/hyper-layer-node.js +21 -0
- package/dist/es2015/itinerary/itinerary.js +276 -0
- package/dist/es2015/itinerary/itinerary.spec.js +296 -0
- package/dist/es2015/itinerary/stack.js +47 -0
- package/dist/es2015/itinerary/stack.spec.js +35 -0
- package/dist/es2015/path/pathway-resolver.js +31 -0
- package/dist/es2015/path/pathway.js +206 -0
- package/dist/filesystem/fs-node-abstract.types.d.ts +34 -0
- package/dist/filesystem/fs-node-abstract.types.d.ts.map +1 -0
- package/dist/filesystem/fs-node-abstract.types.js +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/itinerary/hyper-layer-node.d.ts +12 -0
- package/dist/itinerary/hyper-layer-node.d.ts.map +1 -0
- package/dist/itinerary/hyper-layer-node.js +21 -0
- package/dist/itinerary/itinerary.d.ts +86 -0
- package/dist/itinerary/itinerary.d.ts.map +1 -0
- package/dist/itinerary/itinerary.js +275 -0
- package/dist/itinerary/itinerary.spec.d.ts +2 -0
- package/dist/itinerary/itinerary.spec.d.ts.map +1 -0
- package/dist/itinerary/itinerary.spec.js +294 -0
- package/dist/itinerary/stack.d.ts +20 -0
- package/dist/itinerary/stack.d.ts.map +1 -0
- package/dist/itinerary/stack.js +47 -0
- package/dist/itinerary/stack.spec.d.ts +2 -0
- package/dist/itinerary/stack.spec.d.ts.map +1 -0
- package/dist/itinerary/stack.spec.js +34 -0
- package/dist/path/pathway-resolver.d.ts +20 -0
- package/dist/path/pathway-resolver.d.ts.map +1 -0
- package/dist/path/pathway-resolver.js +31 -0
- package/dist/path/pathway.d.ts +84 -0
- package/dist/path/pathway.d.ts.map +1 -0
- package/dist/path/pathway.js +206 -0
- package/package.json +41 -0
- package/src/filesystem/fs-node-abstract.types.ts +34 -0
- package/src/index.ts +11 -0
- package/src/itinerary/hyper-layer-node.ts +25 -0
- package/src/itinerary/itinerary.spec.ts +388 -0
- package/src/itinerary/itinerary.ts +363 -0
- package/src/itinerary/stack.spec.ts +46 -0
- package/src/itinerary/stack.ts +62 -0
- package/src/path/pathway-resolver.ts +36 -0
- package/src/path/pathway.ts +232 -0
- package/tsconfig.es2015.json +23 -0
- package/tsconfig.json +19 -0
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
areSameItineraryStep,
|
|
4
|
+
constructiveItinerary,
|
|
5
|
+
Itinerary,
|
|
6
|
+
itineraryOf,
|
|
7
|
+
itineraryOverlay,
|
|
8
|
+
itineraryToStepArray,
|
|
9
|
+
navigate,
|
|
10
|
+
navigationDepth,
|
|
11
|
+
overlayNavigator,
|
|
12
|
+
positionStep,
|
|
13
|
+
propertyStep,
|
|
14
|
+
xpathFromStack
|
|
15
|
+
} from './itinerary';
|
|
16
|
+
import stack from './stack';
|
|
17
|
+
import { hyperReflect } from './hyper-layer-node';
|
|
18
|
+
|
|
19
|
+
interface Address {
|
|
20
|
+
street: string
|
|
21
|
+
lines: string[]
|
|
22
|
+
country: string
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface Person {
|
|
26
|
+
name: string
|
|
27
|
+
age?: number
|
|
28
|
+
address?: Address
|
|
29
|
+
children?: Person[]
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
describe('Navigate itinerary', () => {
|
|
33
|
+
it('should work', () => {
|
|
34
|
+
const itinerary: Itinerary = {
|
|
35
|
+
"value": {
|
|
36
|
+
"type": "property",
|
|
37
|
+
"propertyName": "schema"
|
|
38
|
+
},
|
|
39
|
+
"parent": {
|
|
40
|
+
"value": {
|
|
41
|
+
"type": "position",
|
|
42
|
+
"position": 0
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const actual = navigate(sampleData(), itinerary);
|
|
48
|
+
const expected = schemaEntry1.schema
|
|
49
|
+
|
|
50
|
+
expect(actual).toBe(expected);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('Conversion to array should work', () => {
|
|
54
|
+
const path = [0, 'schema']
|
|
55
|
+
const itinerary = itineraryOf(...path)
|
|
56
|
+
const actual = itineraryToStepArray(itinerary!);
|
|
57
|
+
const expected = path.map(e => typeof e === 'string' ? propertyStep(e) : positionStep(e))
|
|
58
|
+
|
|
59
|
+
expect(actual).toStrictEqual(expected);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('Ensure path should work', () => {
|
|
63
|
+
const person: Person[] = [{
|
|
64
|
+
name: "Elon MUSK",
|
|
65
|
+
}]
|
|
66
|
+
const path = [0, 'address']
|
|
67
|
+
const itinerary = itineraryOf(...path)
|
|
68
|
+
const proto = { street: "Wall Street" } as Address;
|
|
69
|
+
const result = navigate(person, itinerary, stack.fromArray([proto]));
|
|
70
|
+
|
|
71
|
+
expect(result).toBe(proto);
|
|
72
|
+
expect(person[0].address).toBe(proto);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('Deep navigation with constructiveItinerary should work', () => {
|
|
76
|
+
const person: Person[] = [{
|
|
77
|
+
name: "Guezo",
|
|
78
|
+
children: []
|
|
79
|
+
}]
|
|
80
|
+
|
|
81
|
+
const { itinerary, prototypes } = constructiveItinerary(["children", [], 0, { name: "Glélé" }, "children", [], 0, { name: "Béhanzin" }])
|
|
82
|
+
const result = navigate(person, stack.concat(itineraryOf(0), itinerary), prototypes) as Person;
|
|
83
|
+
|
|
84
|
+
expect(person[0]?.name).toBe("Guezo");
|
|
85
|
+
expect(result.name).toBe("Béhanzin");
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it("Overlay navigation should work", () => {
|
|
89
|
+
const person: Person = {
|
|
90
|
+
name: "John Doe",
|
|
91
|
+
age: 40,
|
|
92
|
+
address: {
|
|
93
|
+
street: "123 Main Street",
|
|
94
|
+
lines: ["Apt 4B", "2nd Floor"],
|
|
95
|
+
country: "USA"
|
|
96
|
+
},
|
|
97
|
+
children: [
|
|
98
|
+
{
|
|
99
|
+
name: "Jane Doe",
|
|
100
|
+
age: 10,
|
|
101
|
+
address: {
|
|
102
|
+
street: "456 Maple Avenue",
|
|
103
|
+
lines: ["House 12"],
|
|
104
|
+
country: "USA"
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
name: "Jack Doe",
|
|
109
|
+
age: 8
|
|
110
|
+
}
|
|
111
|
+
]
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const personOverlay = hyperReflect(person)
|
|
115
|
+
|
|
116
|
+
const itinerary = itineraryOf("children", 0, "address", "lines", 0)
|
|
117
|
+
|
|
118
|
+
expect(navigate(person, itinerary)).toBe("House 12")
|
|
119
|
+
expect(navigate(personOverlay, itineraryOverlay(itinerary))).toBe("House 12")
|
|
120
|
+
expect(overlayNavigator.navigate(personOverlay, itinerary)?.data).toBe("House 12")
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
it("Overlay itinerary rewrite should work", () => {
|
|
124
|
+
const toOverlay = (...path: (string | number)[]) => {
|
|
125
|
+
return xpathFromStack(itineraryOverlay(itineraryOf(...path)))
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
expect(toOverlay()).toBe("/data")
|
|
129
|
+
expect(toOverlay("name")).toBe("/children/name/data")
|
|
130
|
+
expect(toOverlay("persons", 0)).toBe("/children/persons/children/*[1]/data")
|
|
131
|
+
})
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
const schemaEntry1 = {
|
|
135
|
+
"id": "schema:User",
|
|
136
|
+
"schema": {
|
|
137
|
+
"schemaNodeType": "object",
|
|
138
|
+
"operator": false,
|
|
139
|
+
"facets": {},
|
|
140
|
+
"properties": [
|
|
141
|
+
{
|
|
142
|
+
"uuid": "172302fe-29de-462a-9e27-053463b6d118",
|
|
143
|
+
"schemaNodeType": "property",
|
|
144
|
+
"facets": {},
|
|
145
|
+
"name": "uuid",
|
|
146
|
+
"valueSchema": {
|
|
147
|
+
"schemaNodeType": "scalar",
|
|
148
|
+
"operator": false,
|
|
149
|
+
"facets": {},
|
|
150
|
+
"type": "string"
|
|
151
|
+
}
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
"uuid": "733675e6-543e-4013-a071-a9a45145321b",
|
|
155
|
+
"schemaNodeType": "property",
|
|
156
|
+
"facets": {},
|
|
157
|
+
"name": "name",
|
|
158
|
+
"valueSchema": {
|
|
159
|
+
"schemaNodeType": "scalar",
|
|
160
|
+
"operator": false,
|
|
161
|
+
"facets": {},
|
|
162
|
+
"type": "string"
|
|
163
|
+
}
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
"uuid": "8376b829-459a-4b40-b2f9-71a6aea506e5",
|
|
167
|
+
"schemaNodeType": "property",
|
|
168
|
+
"facets": {},
|
|
169
|
+
"name": "firstName",
|
|
170
|
+
"valueSchema": {
|
|
171
|
+
"schemaNodeType": "scalar",
|
|
172
|
+
"operator": false,
|
|
173
|
+
"facets": {},
|
|
174
|
+
"type": "string"
|
|
175
|
+
}
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
"uuid": "3c39bfc4-90ae-4525-9366-47c7866265f3",
|
|
179
|
+
"schemaNodeType": "property",
|
|
180
|
+
"facets": {},
|
|
181
|
+
"name": "lastName",
|
|
182
|
+
"valueSchema": {
|
|
183
|
+
"schemaNodeType": "scalar",
|
|
184
|
+
"operator": false,
|
|
185
|
+
"facets": {},
|
|
186
|
+
"type": "string"
|
|
187
|
+
}
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
"uuid": "a5f05908-8a9f-4879-8dc9-f5214a6aa693",
|
|
191
|
+
"schemaNodeType": "property",
|
|
192
|
+
"facets": {},
|
|
193
|
+
"name": "age",
|
|
194
|
+
"valueSchema": {
|
|
195
|
+
"schemaNodeType": "scalar",
|
|
196
|
+
"operator": false,
|
|
197
|
+
"facets": {},
|
|
198
|
+
"type": "integer"
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
]
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
function sampleData(): any {
|
|
206
|
+
return [
|
|
207
|
+
schemaEntry1,
|
|
208
|
+
{
|
|
209
|
+
"id": "schema:Car",
|
|
210
|
+
"schema": {
|
|
211
|
+
"schemaNodeType": "object",
|
|
212
|
+
"operator": false,
|
|
213
|
+
"facets": {},
|
|
214
|
+
"properties": [
|
|
215
|
+
{
|
|
216
|
+
"uuid": "50fa33fe-03b4-45f2-b5df-da00e742bb89",
|
|
217
|
+
"schemaNodeType": "property",
|
|
218
|
+
"facets": {},
|
|
219
|
+
"name": "uuid",
|
|
220
|
+
"valueSchema": {
|
|
221
|
+
"schemaNodeType": "scalar",
|
|
222
|
+
"operator": false,
|
|
223
|
+
"facets": {},
|
|
224
|
+
"type": "string"
|
|
225
|
+
}
|
|
226
|
+
},
|
|
227
|
+
{
|
|
228
|
+
"uuid": "6ff05784-dde5-4a70-a57b-d93cba8afb00",
|
|
229
|
+
"schemaNodeType": "property",
|
|
230
|
+
"facets": {},
|
|
231
|
+
"name": "name",
|
|
232
|
+
"valueSchema": {
|
|
233
|
+
"schemaNodeType": "scalar",
|
|
234
|
+
"operator": false,
|
|
235
|
+
"facets": {},
|
|
236
|
+
"type": "string"
|
|
237
|
+
}
|
|
238
|
+
},
|
|
239
|
+
{
|
|
240
|
+
"uuid": "a8dae1ce-a82f-46f4-a2a0-4fc177988e3c",
|
|
241
|
+
"schemaNodeType": "property",
|
|
242
|
+
"facets": {},
|
|
243
|
+
"name": "brand",
|
|
244
|
+
"valueSchema": {
|
|
245
|
+
"schemaNodeType": "scalar",
|
|
246
|
+
"operator": false,
|
|
247
|
+
"facets": {},
|
|
248
|
+
"type": "string"
|
|
249
|
+
}
|
|
250
|
+
},
|
|
251
|
+
{
|
|
252
|
+
"uuid": "85342429-673f-420e-b0d0-7e019e7bea2a",
|
|
253
|
+
"schemaNodeType": "property",
|
|
254
|
+
"facets": {},
|
|
255
|
+
"name": "color",
|
|
256
|
+
"valueSchema": {
|
|
257
|
+
"schemaNodeType": "scalar",
|
|
258
|
+
"operator": false,
|
|
259
|
+
"facets": {},
|
|
260
|
+
"type": "string"
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
]
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
]
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Unit tests for areSameItineraryStep
|
|
270
|
+
describe('areSameItineraryStep', () => {
|
|
271
|
+
it('returns true for two position steps with the same position', () => {
|
|
272
|
+
expect(
|
|
273
|
+
areSameItineraryStep(positionStep(3), positionStep(3))
|
|
274
|
+
).toBe(true);
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
it('returns false for two position steps with different positions', () => {
|
|
278
|
+
expect(
|
|
279
|
+
areSameItineraryStep(
|
|
280
|
+
positionStep(2),
|
|
281
|
+
positionStep(5)
|
|
282
|
+
)
|
|
283
|
+
).toBe(false);
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
it('returns true for two property steps with the same propertyName', () => {
|
|
287
|
+
expect(
|
|
288
|
+
areSameItineraryStep(propertyStep('foo'), propertyStep('foo'))
|
|
289
|
+
).toBe(true);
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
it('returns false for two property steps with different propertyNames', () => {
|
|
293
|
+
expect(
|
|
294
|
+
areSameItineraryStep(
|
|
295
|
+
propertyStep('foo'),
|
|
296
|
+
propertyStep('bar')
|
|
297
|
+
)
|
|
298
|
+
).toBe(false);
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
it('returns false for steps of different types', () => {
|
|
302
|
+
expect(
|
|
303
|
+
areSameItineraryStep(
|
|
304
|
+
positionStep(1),
|
|
305
|
+
propertyStep('1')
|
|
306
|
+
)
|
|
307
|
+
).toBe(false);
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
it('returns false if one or both steps are null or undefined', () => {
|
|
311
|
+
expect(
|
|
312
|
+
areSameItineraryStep(
|
|
313
|
+
positionStep(1),
|
|
314
|
+
null as any
|
|
315
|
+
)
|
|
316
|
+
).toBe(false);
|
|
317
|
+
expect(
|
|
318
|
+
areSameItineraryStep(
|
|
319
|
+
undefined as any,
|
|
320
|
+
propertyStep('foo')
|
|
321
|
+
)
|
|
322
|
+
).toBe(false);
|
|
323
|
+
expect(
|
|
324
|
+
areSameItineraryStep(
|
|
325
|
+
undefined as any,
|
|
326
|
+
undefined as any
|
|
327
|
+
)
|
|
328
|
+
).toBe(false);
|
|
329
|
+
});
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
describe('navigationDepth with itineraryOf', () => {
|
|
333
|
+
it('should return root when itinerary is undefined', () => {
|
|
334
|
+
const obj = { a: 1 };
|
|
335
|
+
const result = navigationDepth(obj, undefined);
|
|
336
|
+
expect(result.value).toBe(obj);
|
|
337
|
+
expect(result.path).toBe('');
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
it('should navigate fully when path exists', () => {
|
|
341
|
+
const obj = { a: { b: { c: 42 } } };
|
|
342
|
+
const itinerary = itineraryOf('a', 'b', 'c');
|
|
343
|
+
|
|
344
|
+
const result = navigationDepth(obj, itinerary);
|
|
345
|
+
expect(result.value).toBe(42);
|
|
346
|
+
expect(result.path).toBe('/a/b/c');
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
it('should stop at last defined property (null)', () => {
|
|
350
|
+
const obj = { a: { b: null } };
|
|
351
|
+
const itinerary = itineraryOf('a', 'b', 'c');
|
|
352
|
+
|
|
353
|
+
const result = navigationDepth(obj, itinerary);
|
|
354
|
+
expect(result.value).toEqual({ b: null });
|
|
355
|
+
expect(result.path).toBe('/a/b');
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
it('should stop when root is undefined', () => {
|
|
359
|
+
const result = navigationDepth(undefined, itineraryOf('a', 'b'));
|
|
360
|
+
expect(result.value).toBeUndefined();
|
|
361
|
+
expect(result.path).toBe('');
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
it('should navigate inside array with position step', () => {
|
|
365
|
+
const obj = { list: [{ id: 1 }, { id: 2 }] };
|
|
366
|
+
const itinerary = itineraryOf('list', 1);
|
|
367
|
+
|
|
368
|
+
const result = navigationDepth(obj, itinerary);
|
|
369
|
+
expect(result.value).toEqual({ id: 2 });
|
|
370
|
+
expect(result.path).toBe('/list/*[2]');
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
it('should stop when lookup fails', () => {
|
|
374
|
+
const obj = { list: [{ id: 1 }, { id: 2 }] };
|
|
375
|
+
const lookupStep = {
|
|
376
|
+
type: 'lookup' as const,
|
|
377
|
+
keys: [{ propertyName: 'id', propertyValue: 3 }]
|
|
378
|
+
};
|
|
379
|
+
const itinerary = {
|
|
380
|
+
value: lookupStep,
|
|
381
|
+
parent: itineraryOf('list')
|
|
382
|
+
};
|
|
383
|
+
|
|
384
|
+
const result = navigationDepth(obj, itinerary);
|
|
385
|
+
expect(result.value).toEqual(obj.list);
|
|
386
|
+
expect(result.path).toBe('/list');
|
|
387
|
+
});
|
|
388
|
+
});
|