@dxos/app-graph 0.6.3-main.9e4e207 → 0.6.3-next.2f65b78

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/src/graph.test.ts CHANGED
@@ -5,11 +5,10 @@
5
5
  import { effect } from '@preact/signals-core';
6
6
  import { expect } from 'chai';
7
7
 
8
- import { updateCounter } from '@dxos/echo-schema/testing';
9
8
  import { registerSignalRuntime } from '@dxos/echo-signals';
10
9
  import { describe, test } from '@dxos/test';
11
10
 
12
- import { Graph, ROOT_ID, ROOT_TYPE, getGraph } from './graph';
11
+ import { Graph } from './graph';
13
12
  import { type Node, type NodeFilter } from './node';
14
13
 
15
14
  const longestPaths = new Map<string, string[]>();
@@ -27,398 +26,253 @@ const filterLongestPath: NodeFilter = (node, connectedNode): node is Node => {
27
26
  return true;
28
27
  };
29
28
 
29
+ // TODO(wittjosiah): Add tests for granularity of reactivity.
30
30
  describe('Graph', () => {
31
- test('getGraph', () => {
32
- const graph = new Graph();
33
- expect(getGraph(graph.root)).to.equal(graph);
34
- });
35
-
36
31
  test('add nodes', () => {
37
32
  const graph = new Graph();
38
33
 
39
- const [root] = graph._addNodes([
40
- {
41
- id: ROOT_ID,
42
- type: ROOT_TYPE,
43
- nodes: [
44
- { id: 'test1', type: 'test' },
45
- { id: 'test2', type: 'test' },
46
- ],
47
- },
48
- ]);
34
+ const [root] = graph.addNodes({
35
+ id: 'root',
36
+ nodes: [{ id: 'test1' }, { id: 'test2' }],
37
+ });
49
38
 
50
39
  expect(root.id).to.equal('root');
51
- expect(graph.nodes(root)).to.have.length(2);
40
+ expect(root.nodes()).to.have.length(2);
52
41
  expect(graph.findNode('test1')?.id).to.equal('test1');
53
42
  expect(graph.findNode('test2')?.id).to.equal('test2');
54
- expect(graph.nodes(graph.findNode('test1')!)).to.be.empty;
55
- expect(graph.nodes(graph.findNode('test2')!)).to.be.empty;
56
- expect(graph.nodes(graph.findNode('test1')!, { relation: 'inbound' })).to.have.length(1);
57
- expect(graph.nodes(graph.findNode('test2')!, { relation: 'inbound' })).to.have.length(1);
43
+ expect(graph.findNode('test1')?.nodes()).to.be.empty;
44
+ expect(graph.findNode('test2')?.nodes()).to.be.empty;
45
+ expect(graph.findNode('test1')?.nodes({ direction: 'inbound' })).to.have.length(1);
46
+ expect(graph.findNode('test2')?.nodes({ direction: 'inbound' })).to.have.length(1);
58
47
  });
59
48
 
60
49
  test('add nodes updates existing nodes', () => {
61
50
  const graph = new Graph();
62
51
 
63
- graph._addNodes([
64
- {
65
- id: ROOT_ID,
66
- type: ROOT_TYPE,
67
- nodes: [
68
- { id: 'test1', type: 'test' },
69
- { id: 'test2', type: 'test' },
70
- ],
71
- },
72
- ]);
73
- graph._addNodes([
74
- {
75
- id: ROOT_ID,
76
- type: ROOT_TYPE,
77
- nodes: [
78
- { id: 'test1', type: 'test' },
79
- { id: 'test2', type: 'test' },
80
- ],
81
- },
82
- ]);
52
+ graph.addNodes({
53
+ id: 'root',
54
+ nodes: [{ id: 'test1' }, { id: 'test2' }],
55
+ });
56
+ graph.addNodes({
57
+ id: 'root',
58
+ nodes: [{ id: 'test1' }, { id: 'test2' }],
59
+ });
83
60
 
84
61
  expect(Object.keys(graph._nodes)).to.have.length(3);
85
62
  expect(Object.keys(graph._edges)).to.have.length(3);
86
- expect(graph.nodes(graph.root)).to.have.length(2);
63
+ expect(graph.root.nodes()).to.have.length(2);
87
64
  });
88
65
 
89
66
  test('remove node', () => {
90
67
  const graph = new Graph();
91
68
 
92
- const [root] = graph._addNodes([
93
- {
94
- id: ROOT_ID,
95
- type: ROOT_TYPE,
96
- nodes: [
97
- { id: 'test1', type: 'test' },
98
- { id: 'test2', type: 'test' },
99
- ],
100
- },
101
- ]);
69
+ const [root] = graph.addNodes({
70
+ id: 'root',
71
+ nodes: [{ id: 'test1' }, { id: 'test2' }],
72
+ });
102
73
 
103
74
  expect(root.id).to.equal('root');
104
- expect(graph.nodes(root)).to.have.length(2);
75
+ expect(root.nodes()).to.have.length(2);
105
76
  expect(graph.findNode('test1')?.id).to.equal('test1');
106
77
  expect(graph.findNode('test2')?.id).to.equal('test2');
107
78
 
108
- graph._removeNodes(['test1']);
79
+ graph.removeNode('test1');
109
80
  expect(graph.findNode('test1')).to.be.undefined;
110
- expect(graph.nodes(root)).to.have.length(1);
81
+ expect(root.nodes()).to.have.length(1);
111
82
  });
112
83
 
113
84
  test('add edge', () => {
114
85
  const graph = new Graph();
115
86
 
116
- graph._addNodes([
117
- {
118
- id: ROOT_ID,
119
- type: ROOT_TYPE,
120
- nodes: [
121
- { id: 'test1', type: 'test' },
122
- { id: 'test2', type: 'test' },
123
- ],
124
- },
125
- ]);
126
- graph._addEdges([{ source: 'test1', target: 'test2' }]);
87
+ graph.addNodes({
88
+ id: 'root',
89
+ nodes: [{ id: 'test1' }, { id: 'test2' }],
90
+ });
91
+ graph.addEdge({ source: 'test1', target: 'test2' });
127
92
 
128
- expect(graph.nodes(graph.findNode('test1')!)).to.have.length(1);
129
- expect(graph.nodes(graph.findNode('test2')!, { relation: 'inbound' })).to.have.length(2);
93
+ expect(graph.findNode('test1')?.nodes()).to.have.length(1);
94
+ expect(graph.findNode('test2')?.nodes({ direction: 'inbound' })).to.have.length(2);
130
95
  });
131
96
 
132
97
  test('add edges is idempontent', () => {
133
98
  const graph = new Graph();
134
99
 
135
- graph._addNodes([
136
- {
137
- id: ROOT_ID,
138
- type: ROOT_TYPE,
139
- nodes: [
140
- { id: 'test1', type: 'test' },
141
- { id: 'test2', type: 'test' },
142
- ],
143
- },
144
- ]);
145
- graph._addEdges([{ source: 'test1', target: 'test2' }]);
146
- graph._addEdges([{ source: 'test1', target: 'test2' }]);
100
+ graph.addNodes({
101
+ id: 'root',
102
+ nodes: [{ id: 'test1' }, { id: 'test2' }],
103
+ });
104
+ graph.addEdge({ source: 'test1', target: 'test2' });
105
+ graph.addEdge({ source: 'test1', target: 'test2' });
147
106
 
148
- expect(graph.nodes(graph.findNode('test1')!)).to.have.length(1);
149
- expect(graph.nodes(graph.findNode('test2')!, { relation: 'inbound' })).to.have.length(2);
107
+ expect(graph.findNode('test1')?.nodes()).to.have.length(1);
108
+ expect(graph.findNode('test2')?.nodes({ direction: 'inbound' })).to.have.length(2);
150
109
  });
151
110
 
152
111
  test('sort edges', () => {
153
112
  const graph = new Graph();
154
113
 
155
- const [root] = graph._addNodes([
156
- {
157
- id: ROOT_ID,
158
- type: ROOT_TYPE,
159
- nodes: [
160
- { id: 'test1', type: 'test' },
161
- { id: 'test3', type: 'test' },
162
- { id: 'test2', type: 'test' },
163
- { id: 'test4', type: 'test' },
164
- ],
165
- },
166
- ]);
114
+ const [root] = graph.addNodes({
115
+ id: 'root',
116
+ nodes: [{ id: 'test1' }, { id: 'test3' }, { id: 'test2' }, { id: 'test4' }],
117
+ });
167
118
 
168
- expect(graph.nodes(root).map((node) => node.id)).to.deep.equal(['test1', 'test3', 'test2', 'test4']);
119
+ expect(root.nodes().map((node) => node.id)).to.deep.equal(['test1', 'test3', 'test2', 'test4']);
169
120
 
170
- graph._sortEdges('root', 'outbound', ['test4', 'test3']);
121
+ graph.sortEdges('root', 'outbound', ['test4', 'test3']);
171
122
 
172
- expect(graph.nodes(root).map((node) => node.id)).to.deep.equal(['test4', 'test3', 'test1', 'test2']);
123
+ expect(root.nodes().map((node) => node.id)).to.deep.equal(['test4', 'test3', 'test1', 'test2']);
173
124
  });
174
125
 
175
126
  test('remove edge', () => {
176
127
  const graph = new Graph();
177
128
 
178
- graph._addNodes([
179
- {
180
- id: ROOT_ID,
181
- type: ROOT_TYPE,
182
- nodes: [
183
- { id: 'test1', type: 'test' },
184
- { id: 'test2', type: 'test' },
185
- ],
186
- },
187
- ]);
188
- graph._removeEdges([{ source: 'root', target: 'test1' }]);
129
+ graph.addNodes({
130
+ id: 'root',
131
+ nodes: [{ id: 'test1' }, { id: 'test2' }],
132
+ });
133
+ graph.removeEdge({ source: 'root', target: 'test1' });
189
134
 
190
- expect(graph.nodes(graph.root)).to.have.length(1);
191
- expect(graph.nodes(graph.findNode('test1')!, { relation: 'inbound' })).to.be.empty;
135
+ expect(graph.root.nodes()).to.have.length(1);
136
+ expect(graph.findNode('test1')?.nodes({ direction: 'inbound' })).to.be.empty;
192
137
  });
193
138
 
194
139
  test('toJSON', () => {
195
140
  const graph = new Graph();
196
141
 
197
- graph._addNodes([
198
- {
199
- id: ROOT_ID,
200
- type: ROOT_TYPE,
201
- nodes: [
202
- { id: 'test1', type: 'test' },
203
- { id: 'test2', type: 'test' },
204
- ],
205
- },
206
- ]);
207
- graph._addEdges([{ source: 'test1', target: 'test2' }]);
142
+ graph.addNodes({
143
+ id: 'root',
144
+ nodes: [{ id: 'test1' }, { id: 'test2' }],
145
+ });
146
+ graph.addEdge({ source: 'test1', target: 'test2' });
208
147
 
209
148
  const json = graph.toJSON();
210
149
  expect(json).to.deep.equal({
211
- id: ROOT_ID,
212
- type: ROOT_TYPE,
213
- nodes: [
214
- { id: 'test1', type: 'test', nodes: [{ id: 'test2', type: 'test' }] },
215
- { id: 'test2', type: 'test' },
216
- ],
150
+ id: 'root',
151
+ nodes: [{ id: 'test1', nodes: [{ id: 'test2' }] }, { id: 'test2' }],
217
152
  });
218
153
  });
219
154
 
220
- test('waitForNode', async () => {
221
- registerSignalRuntime();
155
+ test('can be traversed', () => {
222
156
  const graph = new Graph();
223
- const promise = graph.waitForNode('test1');
224
- graph._addNodes([{ id: 'test1', type: 'test', data: 1 }]);
225
- const node = await promise;
226
- expect(node.id).to.equal('test1');
227
- expect(node.data).to.equal(1);
157
+
158
+ const [root] = graph.addNodes({
159
+ id: 'root',
160
+ nodes: [{ id: 'test1' }, { id: 'test2' }],
161
+ });
162
+
163
+ const nodes: string[] = [];
164
+ graph.traverse({ node: root, visitor: (node) => nodes.push(node.id) });
165
+ expect(nodes).to.deep.equal(['root', 'test1', 'test2']);
228
166
  });
229
167
 
230
- test('updates are constrained on data', () => {
231
- registerSignalRuntime();
168
+ test('traversal breaks cycles', () => {
232
169
  const graph = new Graph();
233
- const [node1] = graph._addNodes([{ id: 'test1', type: 'test', data: 1 }]);
234
- using updates = updateCounter(() => {
235
- node1.data;
170
+
171
+ const [root] = graph.addNodes({
172
+ id: 'root',
173
+ nodes: [{ id: 'test1' }, { id: 'test2' }],
236
174
  });
237
- graph._addNodes([{ id: 'test2', type: 'test', data: 2 }]);
238
- graph._addEdges([{ source: 'test1', target: 'test2' }]);
239
- expect(updates.count, 'update count').to.eq(0);
240
- graph._addNodes([{ id: 'test1', type: 'test', data: -1 }]);
241
- expect(updates.count, 'update count').to.eq(1);
242
- graph._addNodes([{ id: 'test1', type: 'test', data: -1, properties: { label: 'test' } }]);
243
- expect(updates.count, 'update count').to.eq(1);
175
+ graph.addEdge({ source: 'test1', target: 'root' });
176
+
177
+ const nodes: string[] = [];
178
+ graph.traverse({ node: root, visitor: (node) => nodes.push(node.id) });
179
+ expect(nodes).to.deep.equal(['root', 'test1', 'test2']);
244
180
  });
245
181
 
246
- test('updates are constrained on properties', () => {
247
- registerSignalRuntime();
182
+ test('traversal can be limited by predicate', () => {
248
183
  const graph = new Graph();
249
- const [node1] = graph._addNodes([{ id: 'test1', type: 'test', properties: { value: 1 } }]);
250
- using updates = updateCounter(() => {
251
- node1.properties.value;
184
+
185
+ const [root] = graph.addNodes({
186
+ id: 'root',
187
+ nodes: [{ id: 'test1' }, { id: 'test2' }, { id: 'test3' }, { id: 'test4' }],
188
+ });
189
+
190
+ const nodes: string[] = [];
191
+ graph.traverse({
192
+ node: root,
193
+ visitor: (node) => nodes.push(node.id),
194
+ filter: (node) => {
195
+ try {
196
+ const id = parseInt(node.id.replace('test', ''), 10);
197
+ return id % 2 === 0;
198
+ } catch (e) {
199
+ return false;
200
+ }
201
+ },
252
202
  });
253
- graph._addNodes([{ id: 'test2', type: 'test', properties: { value: 2 } }]);
254
- graph._addEdges([{ source: 'test1', target: 'test2' }]);
255
- expect(updates.count, 'update count').to.eq(0);
256
- graph._addNodes([{ id: 'test1', type: 'test', properties: { value: -1 } }]);
257
- expect(updates.count, 'update count').to.eq(1);
203
+ expect(nodes).to.deep.equal(['test2', 'test4']);
258
204
  });
259
205
 
260
- test('updates are constrained on connected nodes', () => {
261
- registerSignalRuntime();
206
+ test('traversal can be started from any node', () => {
262
207
  const graph = new Graph();
263
- const [node1] = graph._addNodes([{ id: 'test1', type: 'test', properties: { value: 1 } }]);
264
- using updates = updateCounter(() => {
265
- graph.nodes(node1);
208
+
209
+ graph.addNodes({
210
+ id: 'root',
211
+ nodes: [{ id: 'test1', nodes: [{ id: 'test2', nodes: [{ id: 'test3' }] }] }],
266
212
  });
267
- expect(updates.count, 'update count').to.eq(0);
268
- graph._addNodes([{ id: 'test2', type: 'test', properties: { value: 2 } }]);
269
- expect(updates.count, 'update count').to.eq(0);
270
- graph._addEdges([{ source: 'test1', target: 'test2' }]);
271
- expect(updates.count, 'update count').to.eq(1);
272
- graph._addNodes([{ id: 'test2', type: 'test', properties: { value: -2 } }]);
273
- expect(updates.count, 'update count').to.eq(1);
274
- graph._addNodes([{ id: 'test3', type: 'test', properties: { value: 3 } }]);
275
- expect(updates.count, 'update count').to.eq(1);
276
- graph._addEdges([{ source: 'test2', target: 'test3' }]);
277
- expect(updates.count, 'update count').to.eq(1);
278
- graph._addEdges([{ source: 'test1', target: 'test3' }]);
279
- expect(updates.count, 'update count').to.eq(2);
213
+
214
+ const nodes: string[] = [];
215
+ graph.traverse({
216
+ node: graph.findNode('test2')!,
217
+ visitor: (node) => nodes.push(node.id),
218
+ });
219
+ expect(nodes).to.deep.equal(['test2', 'test3']);
280
220
  });
281
221
 
282
- test('get path', () => {
222
+ test('traversal can follow inbound edges', () => {
283
223
  const graph = new Graph();
284
224
 
285
- graph._addNodes([
286
- {
287
- id: ROOT_ID,
288
- type: ROOT_TYPE,
289
- nodes: [
290
- { id: 'test1', type: 'test' },
291
- { id: 'test2', type: 'test' },
292
- ],
293
- },
294
- ]);
295
- graph._addEdges([{ source: 'test1', target: 'test2' }]);
225
+ graph.addNodes({
226
+ id: 'root',
227
+ nodes: [{ id: 'test1', nodes: [{ id: 'test2', nodes: [{ id: 'test3' }] }] }],
228
+ });
296
229
 
297
- expect(graph.getPath({ target: 'test2' })).to.deep.equal(['root', 'test1', 'test2']);
298
- expect(graph.getPath({ source: 'test1', target: 'test2' })).to.deep.equal(['test1', 'test2']);
299
- expect(graph.getPath({ source: 'test2', target: 'test1' })).to.be.undefined;
230
+ const nodes: string[] = [];
231
+ graph.traverse({
232
+ node: graph.findNode('test2')!,
233
+ direction: 'inbound',
234
+ visitor: (node) => nodes.push(node.id),
235
+ });
236
+ expect(nodes).to.deep.equal(['test2', 'test1', 'root']);
300
237
  });
301
238
 
302
- describe('traverse', () => {
303
- test('can be traversed', () => {
304
- const graph = new Graph();
305
-
306
- const [root] = graph._addNodes([
307
- {
308
- id: ROOT_ID,
309
- type: ROOT_TYPE,
310
- nodes: [
311
- { id: 'test1', type: 'test' },
312
- { id: 'test2', type: 'test' },
313
- ],
314
- },
315
- ]);
239
+ test('can filter to longest pathes', () => {
240
+ const graph = new Graph();
316
241
 
317
- const nodes: string[] = [];
318
- graph.traverse({
319
- node: root,
320
- visitor: (node) => {
321
- nodes.push(node.id);
322
- },
323
- });
324
- expect(nodes).to.deep.equal(['root', 'test1', 'test2']);
242
+ graph.addNodes({
243
+ id: 'root',
244
+ nodes: [{ id: 'test1' }, { id: 'test2' }],
325
245
  });
246
+ graph.addEdge({ source: 'test1', target: 'test2' });
326
247
 
327
- test('traversal breaks cycles', () => {
328
- const graph = new Graph();
329
-
330
- const [root] = graph._addNodes([
331
- {
332
- id: ROOT_ID,
333
- type: ROOT_TYPE,
334
- nodes: [
335
- { id: 'test1', type: 'test' },
336
- { id: 'test2', type: 'test' },
337
- ],
338
- },
339
- ]);
340
- graph._addEdges([{ source: 'test1', target: 'root' }]);
341
-
342
- const nodes: string[] = [];
343
- graph.traverse({
344
- node: root,
345
- visitor: (node) => {
346
- nodes.push(node.id);
347
- },
348
- });
349
- expect(nodes).to.deep.equal(['root', 'test1', 'test2']);
248
+ graph.traverse({
249
+ visitor: (node, path) => {
250
+ if (!longestPaths.has(node.id) || longestPaths.get(node.id)!.length < path.length) {
251
+ longestPaths.set(node.id, path);
252
+ }
253
+ },
350
254
  });
351
255
 
352
- test('traversal can be started from any node', () => {
353
- const graph = new Graph();
354
-
355
- graph._addNodes([
356
- {
357
- id: ROOT_ID,
358
- type: ROOT_TYPE,
359
- nodes: [
360
- {
361
- id: 'test1',
362
- type: 'test',
363
- nodes: [{ id: 'test2', type: 'test', nodes: [{ id: 'test3', type: 'test' }] }],
364
- },
365
- ],
366
- },
367
- ]);
256
+ expect(longestPaths.get('root')).to.deep.equal(['root']);
257
+ expect(longestPaths.get('test1')).to.deep.equal(['root', 'test1']);
258
+ expect(longestPaths.get('test2')).to.deep.equal(['root', 'test1', 'test2']);
259
+ expect(graph.root.nodes({ filter: filterLongestPath })).to.have.length(1);
260
+ expect(graph.findNode('test1')?.nodes({ filter: filterLongestPath })).to.have.length(1);
261
+ expect(graph.findNode('test2')?.nodes({ filter: filterLongestPath })).to.be.empty;
368
262
 
369
- const nodes: string[] = [];
370
- graph.traverse({
371
- node: graph.findNode('test2')!,
372
- visitor: (node) => {
373
- nodes.push(node.id);
374
- },
375
- });
376
- expect(nodes).to.deep.equal(['test2', 'test3']);
377
- });
263
+ longestPaths.clear();
264
+ });
378
265
 
379
- test('traversal can follow inbound edges', () => {
380
- const graph = new Graph();
381
-
382
- graph._addNodes([
383
- {
384
- id: ROOT_ID,
385
- type: ROOT_TYPE,
386
- nodes: [
387
- {
388
- id: 'test1',
389
- type: 'test',
390
- nodes: [{ id: 'test2', type: 'test', nodes: [{ id: 'test3', type: 'test' }] }],
391
- },
392
- ],
393
- },
394
- ]);
266
+ test('traversing the graph subscribes to changes', () => {
267
+ registerSignalRuntime();
268
+ const graph = new Graph();
395
269
 
396
- const nodes: string[] = [];
397
- graph.traverse({
398
- node: graph.findNode('test2')!,
399
- relation: 'inbound',
400
- visitor: (node) => {
401
- nodes.push(node.id);
402
- },
403
- });
404
- expect(nodes).to.deep.equal(['test2', 'test1', 'root']);
270
+ graph.addNodes({
271
+ id: 'root',
272
+ nodes: [{ id: 'test1' }, { id: 'test2' }],
405
273
  });
406
274
 
407
- test('can filter to longest paths', () => {
408
- const graph = new Graph();
409
-
410
- graph._addNodes([
411
- {
412
- id: ROOT_ID,
413
- type: ROOT_TYPE,
414
- nodes: [
415
- { id: 'test1', type: 'test' },
416
- { id: 'test2', type: 'test' },
417
- ],
418
- },
419
- ]);
420
- graph._addEdges([{ source: 'test1', target: 'test2' }]);
421
-
275
+ const dispose = effect(() => {
422
276
  graph.traverse({
423
277
  visitor: (node, path) => {
424
278
  if (!longestPaths.has(node.id) || longestPaths.get(node.id)!.length < path.length) {
@@ -426,145 +280,39 @@ describe('Graph', () => {
426
280
  }
427
281
  },
428
282
  });
429
-
430
- expect(longestPaths.get('root')).to.deep.equal(['root']);
431
- expect(longestPaths.get('test1')).to.deep.equal(['root', 'test1']);
432
- expect(longestPaths.get('test2')).to.deep.equal(['root', 'test1', 'test2']);
433
- expect(graph.nodes(graph.root, { filter: filterLongestPath })).to.have.length(1);
434
- expect(graph.nodes(graph.findNode('test1')!, { filter: filterLongestPath })).to.have.length(1);
435
- expect(graph.nodes(graph.findNode('test2')!, { filter: filterLongestPath })).to.be.empty;
436
-
437
- longestPaths.clear();
438
283
  });
439
284
 
440
- test('traversing the graph subscribes to changes', () => {
441
- registerSignalRuntime();
442
- const graph = new Graph();
443
-
444
- graph._addNodes([
445
- {
446
- id: ROOT_ID,
447
- type: ROOT_TYPE,
448
- nodes: [
449
- { id: 'test1', type: 'test' },
450
- { id: 'test2', type: 'test' },
451
- ],
452
- },
453
- ]);
454
-
455
- const dispose = effect(() => {
456
- graph.traverse({
457
- visitor: (node, path) => {
458
- if (!longestPaths.has(node.id) || longestPaths.get(node.id)!.length < path.length) {
459
- longestPaths.set(node.id, path);
460
- }
461
- },
462
- });
463
- });
464
-
465
- expect(longestPaths.get('root')).to.deep.equal(['root']);
466
- expect(longestPaths.get('test1')).to.deep.equal(['root', 'test1']);
467
- expect(longestPaths.get('test2')).to.deep.equal(['root', 'test2']);
468
- expect(graph.nodes(graph.root, { filter: filterLongestPath })).to.have.length(2);
469
- expect(graph.nodes(graph.findNode('test1')!, { filter: filterLongestPath })).to.be.empty;
470
- expect(graph.nodes(graph.findNode('test2')!, { filter: filterLongestPath })).to.be.empty;
471
-
472
- graph._addEdges([{ source: 'test1', target: 'test2' }]);
285
+ expect(longestPaths.get('root')).to.deep.equal(['root']);
286
+ expect(longestPaths.get('test1')).to.deep.equal(['root', 'test1']);
287
+ expect(longestPaths.get('test2')).to.deep.equal(['root', 'test2']);
288
+ expect(graph.root.nodes({ filter: filterLongestPath })).to.have.length(2);
289
+ expect(graph.findNode('test1')?.nodes({ filter: filterLongestPath })).to.be.empty;
290
+ expect(graph.findNode('test2')?.nodes({ filter: filterLongestPath })).to.be.empty;
473
291
 
474
- expect(longestPaths.get('root')).to.deep.equal(['root']);
475
- expect(longestPaths.get('test1')).to.deep.equal(['root', 'test1']);
476
- expect(longestPaths.get('test2')).to.deep.equal(['root', 'test1', 'test2']);
477
- expect(graph.nodes(graph.root, { filter: filterLongestPath })).to.have.length(1);
478
- expect(graph.nodes(graph.findNode('test1')!, { filter: filterLongestPath })).to.have.length(1);
479
- expect(graph.nodes(graph.findNode('test2')!, { filter: filterLongestPath })).to.be.empty;
292
+ graph.addEdge({ source: 'test1', target: 'test2' });
480
293
 
481
- dispose();
482
- longestPaths.clear();
483
- });
294
+ expect(longestPaths.get('root')).to.deep.equal(['root']);
295
+ expect(longestPaths.get('test1')).to.deep.equal(['root', 'test1']);
296
+ expect(longestPaths.get('test2')).to.deep.equal(['root', 'test1', 'test2']);
297
+ expect(graph.root.nodes({ filter: filterLongestPath })).to.have.length(1);
298
+ expect(graph.findNode('test1')?.nodes({ filter: filterLongestPath })).to.have.length(1);
299
+ expect(graph.findNode('test2')?.nodes({ filter: filterLongestPath })).to.be.empty;
484
300
 
485
- test('traversal can be terminated early', () => {
486
- const graph = new Graph();
487
-
488
- const [root] = graph._addNodes([
489
- {
490
- id: ROOT_ID,
491
- type: ROOT_TYPE,
492
- nodes: [
493
- { id: 'test1', type: 'test' },
494
- { id: 'test2', type: 'test' },
495
- ],
496
- },
497
- ]);
301
+ dispose();
302
+ longestPaths.clear();
303
+ });
498
304
 
499
- const nodes: string[] = [];
500
- graph.traverse({
501
- node: root,
502
- visitor: (node) => {
503
- if (nodes.length === 2) {
504
- return false;
505
- }
305
+ test('get path', () => {
306
+ const graph = new Graph();
506
307
 
507
- nodes.push(node.id);
508
- },
509
- });
510
- expect(nodes).to.deep.equal(['root', 'test1']);
308
+ graph.addNodes({
309
+ id: 'root',
310
+ nodes: [{ id: 'test1' }, { id: 'test2' }],
511
311
  });
312
+ graph.addEdge({ source: 'test1', target: 'test2' });
512
313
 
513
- test('traversal can be reactive', async () => {
514
- registerSignalRuntime();
515
- const graph = new Graph();
516
- const latest: Record<string, any> = {};
517
- const updates: Record<string, number> = {};
518
- graph.subscribeTraverse({
519
- node: graph.root,
520
- visitor: (node) => {
521
- latest[node.id] = node.data;
522
- updates[node.id] = (updates[node.id] ?? 0) + 1;
523
- },
524
- });
525
-
526
- expect(latest.root).to.equal(null);
527
- expect(updates.root).to.equal(1);
528
-
529
- graph._addNodes([
530
- {
531
- id: ROOT_ID,
532
- type: ROOT_TYPE,
533
- nodes: [
534
- {
535
- id: 'test1',
536
- type: 'test',
537
- data: 1,
538
- nodes: [{ id: 'test2', type: 'test', data: 2 }],
539
- },
540
- ],
541
- },
542
- ]);
543
-
544
- expect(latest.root).to.equal(null);
545
- expect(latest.test1).to.equal(1);
546
- expect(latest.test2).to.equal(2);
547
- expect(updates.root).to.equal(2);
548
- expect(updates.test1).to.equal(1);
549
- expect(updates.test2).to.equal(1);
550
-
551
- graph._addNodes([{ id: 'test2', type: 'test', data: -2 }]);
552
-
553
- expect(latest.root).to.equal(null);
554
- expect(latest.test1).to.equal(1);
555
- expect(latest.test2).to.equal(-2);
556
- expect(updates.root).to.equal(2);
557
- expect(updates.test1).to.equal(1);
558
- expect(updates.test2).to.equal(2);
559
-
560
- graph._addNodes([{ id: 'test1', type: 'test', data: -1 }]);
561
-
562
- expect(latest.root).to.equal(null);
563
- expect(latest.test1).to.equal(-1);
564
- expect(latest.test2).to.equal(-2);
565
- expect(updates.root).to.equal(2);
566
- expect(updates.test1).to.equal(2);
567
- expect(updates.test2).to.equal(3);
568
- });
314
+ expect(graph.getPath({ target: 'test2' })).to.deep.equal(['root', 'test1', 'test2']);
315
+ expect(graph.getPath({ source: 'test1', target: 'test2' })).to.deep.equal(['test1', 'test2']);
316
+ expect(graph.getPath({ source: 'test2', target: 'test1' })).to.be.undefined;
569
317
  });
570
318
  });