@derivation/rpc 0.3.0 → 0.3.5
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/dist/iso.d.ts +1 -1
- package/dist/iso.js +1 -1
- package/dist/reactive-map-adapter.d.ts +7 -7
- package/dist/reactive-map-adapter.js +4 -3
- package/dist/reactive-set-adapter.d.ts +7 -7
- package/dist/reactive-set-adapter.js +4 -3
- package/dist/tests/context.test.d.ts +1 -0
- package/dist/tests/context.test.js +252 -0
- package/dist/tests/iso.test.d.ts +1 -0
- package/dist/tests/iso.test.js +186 -0
- package/dist/tests/messages.test.d.ts +1 -0
- package/dist/tests/messages.test.js +152 -0
- package/dist/tests/mutations.test.d.ts +1 -0
- package/dist/tests/mutations.test.js +122 -0
- package/dist/tests/queue.test.d.ts +1 -0
- package/dist/tests/queue.test.js +84 -0
- package/dist/tests/reactive-map-adapter.test.d.ts +1 -0
- package/dist/tests/reactive-map-adapter.test.js +190 -0
- package/dist/tests/reactive-set-adapter.test.d.ts +1 -0
- package/dist/tests/reactive-set-adapter.test.js +157 -0
- package/dist/tests/stream-adapter.test.d.ts +1 -0
- package/dist/tests/stream-adapter.test.js +119 -0
- package/dist/tests/weak-list.test.d.ts +1 -0
- package/dist/tests/weak-list.test.js +100 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +10 -5
- package/dist/websocket-server.d.ts +0 -11
- package/dist/websocket-server.js +0 -59
- package/dist/websocket-transport.d.ts +0 -28
- package/dist/websocket-transport.js +0 -58
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { Graph } from 'derivation';
|
|
3
|
+
import { ZSet, Reactive, ZSetOperations, ZSetChangeInput } from '@derivation/composable';
|
|
4
|
+
import { ReactiveSetSourceAdapter, ReactiveSetSinkAdapter, sink } from '../reactive-set-adapter.js';
|
|
5
|
+
import * as iso from '../iso.js';
|
|
6
|
+
describe('ReactiveSetSourceAdapter', () => {
|
|
7
|
+
it('should provide snapshot as array of [item, weight] tuples', () => {
|
|
8
|
+
const graph = new Graph();
|
|
9
|
+
let zset = new ZSet();
|
|
10
|
+
zset = zset.add('a', 1).add('b', 2);
|
|
11
|
+
const reactiveSet = Reactive.create(graph, new ZSetOperations(), new ZSetChangeInput(graph), zset);
|
|
12
|
+
const adapter = new ReactiveSetSourceAdapter(reactiveSet, iso.id());
|
|
13
|
+
const snapshot = adapter.Snapshot;
|
|
14
|
+
expect(snapshot).toEqual([['a', 1], ['b', 2]]);
|
|
15
|
+
});
|
|
16
|
+
it('should transform elements using isomorphism', () => {
|
|
17
|
+
const graph = new Graph();
|
|
18
|
+
let zset = new ZSet();
|
|
19
|
+
zset = zset.add(1, 2).add(3, 4);
|
|
20
|
+
const reactiveSet = Reactive.create(graph, new ZSetOperations(), new ZSetChangeInput(graph), zset);
|
|
21
|
+
const numToString = {
|
|
22
|
+
to: (n) => n.toString(),
|
|
23
|
+
from: (s) => parseInt(s, 10),
|
|
24
|
+
};
|
|
25
|
+
const adapter = new ReactiveSetSourceAdapter(reactiveSet, numToString);
|
|
26
|
+
const snapshot = adapter.Snapshot;
|
|
27
|
+
expect(snapshot).toEqual([['1', 2], ['3', 4]]);
|
|
28
|
+
});
|
|
29
|
+
it('should provide last change after modifications', () => {
|
|
30
|
+
const graph = new Graph();
|
|
31
|
+
const input = new ZSetChangeInput(graph);
|
|
32
|
+
const reactiveSet = Reactive.create(graph, new ZSetOperations(), input, new ZSet());
|
|
33
|
+
const adapter = new ReactiveSetSourceAdapter(reactiveSet, iso.id());
|
|
34
|
+
// Push a change
|
|
35
|
+
let zset = new ZSet();
|
|
36
|
+
zset = zset.add('x', 1);
|
|
37
|
+
input.push(zset);
|
|
38
|
+
graph.step();
|
|
39
|
+
const lastChange = adapter.LastChange;
|
|
40
|
+
expect(lastChange).toEqual([['x', 1]]);
|
|
41
|
+
});
|
|
42
|
+
it('should return the underlying reactive set', () => {
|
|
43
|
+
const graph = new Graph();
|
|
44
|
+
const reactiveSet = Reactive.create(graph, new ZSetOperations(), new ZSetChangeInput(graph), new ZSet());
|
|
45
|
+
const adapter = new ReactiveSetSourceAdapter(reactiveSet, iso.id());
|
|
46
|
+
expect(adapter.Stream).toBe(reactiveSet);
|
|
47
|
+
});
|
|
48
|
+
it('should handle empty sets', () => {
|
|
49
|
+
const graph = new Graph();
|
|
50
|
+
const reactiveSet = Reactive.create(graph, new ZSetOperations(), new ZSetChangeInput(graph), new ZSet());
|
|
51
|
+
const adapter = new ReactiveSetSourceAdapter(reactiveSet, iso.id());
|
|
52
|
+
expect(adapter.Snapshot).toEqual([]);
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
describe('ReactiveSetSinkAdapter', () => {
|
|
56
|
+
it('should build a reactive set source', () => {
|
|
57
|
+
const graph = new Graph();
|
|
58
|
+
const adapter = new ReactiveSetSinkAdapter(graph, iso.compose(iso.zset(iso.id()), iso.zsetToArray()), []);
|
|
59
|
+
const { stream: source } = adapter.build();
|
|
60
|
+
expect(source).toBeDefined();
|
|
61
|
+
expect([...source.snapshot.getEntries()]).toEqual([]);
|
|
62
|
+
});
|
|
63
|
+
it('should apply changes to a reactive set', () => {
|
|
64
|
+
const graph = new Graph();
|
|
65
|
+
const isoComposed = iso.compose(iso.zset(iso.id()), iso.zsetToArray());
|
|
66
|
+
const adapter = new ReactiveSetSinkAdapter(graph, isoComposed, []);
|
|
67
|
+
const { stream: source, input } = adapter.build();
|
|
68
|
+
// Apply a change to add items
|
|
69
|
+
const change = [['a', 1], ['b', 2]];
|
|
70
|
+
adapter.apply(change, input);
|
|
71
|
+
graph.step();
|
|
72
|
+
// Check snapshot after change
|
|
73
|
+
const entries = [...source.snapshot.getEntries()];
|
|
74
|
+
expect(entries).toContainEqual(['a', 1]);
|
|
75
|
+
expect(entries).toContainEqual(['b', 2]);
|
|
76
|
+
});
|
|
77
|
+
it('should transform elements using isomorphism', () => {
|
|
78
|
+
const graph = new Graph();
|
|
79
|
+
const numToString = {
|
|
80
|
+
to: (n) => n.toString(),
|
|
81
|
+
from: (s) => parseInt(s, 10),
|
|
82
|
+
};
|
|
83
|
+
const isoComposed = iso.compose(iso.zset(numToString), iso.zsetToArray());
|
|
84
|
+
const adapter = new ReactiveSetSinkAdapter(graph, isoComposed, []);
|
|
85
|
+
const { stream: source, input } = adapter.build();
|
|
86
|
+
// Apply change with string values
|
|
87
|
+
const change = [['5', 1], ['10', 2]];
|
|
88
|
+
adapter.apply(change, input);
|
|
89
|
+
graph.step();
|
|
90
|
+
// Should be converted to numbers
|
|
91
|
+
const entries = [...source.snapshot.getEntries()];
|
|
92
|
+
expect(entries).toContainEqual([5, 1]);
|
|
93
|
+
expect(entries).toContainEqual([10, 2]);
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
describe('sink function', () => {
|
|
97
|
+
it('should create a sink initialized with snapshot', () => {
|
|
98
|
+
const graph = new Graph();
|
|
99
|
+
const sinkFn = sink(graph, iso.id());
|
|
100
|
+
const snapshot = [['item1', 1], ['item2', 2]];
|
|
101
|
+
const sinkAdapter = sinkFn(snapshot);
|
|
102
|
+
const { stream: source } = sinkAdapter.build();
|
|
103
|
+
const entries = [...source.snapshot.getEntries()];
|
|
104
|
+
expect(entries).toContainEqual(['item1', 1]);
|
|
105
|
+
expect(entries).toContainEqual(['item2', 2]);
|
|
106
|
+
});
|
|
107
|
+
it('should allow applying changes after initialization', () => {
|
|
108
|
+
const graph = new Graph();
|
|
109
|
+
const sinkFn = sink(graph, iso.id());
|
|
110
|
+
const snapshot = [['a', 1]];
|
|
111
|
+
const sinkAdapter = sinkFn(snapshot);
|
|
112
|
+
const { stream: source, input } = sinkAdapter.build();
|
|
113
|
+
// Apply a change
|
|
114
|
+
const change = [['b', 2], ['c', 3]];
|
|
115
|
+
sinkAdapter.apply(change, input);
|
|
116
|
+
graph.step();
|
|
117
|
+
const entries = [...source.snapshot.getEntries()];
|
|
118
|
+
expect(entries.length).toBeGreaterThan(1);
|
|
119
|
+
expect(entries).toContainEqual(['b', 2]);
|
|
120
|
+
expect(entries).toContainEqual(['c', 3]);
|
|
121
|
+
});
|
|
122
|
+
it('should transform snapshot using isomorphism', () => {
|
|
123
|
+
const graph = new Graph();
|
|
124
|
+
const stringToNum = {
|
|
125
|
+
to: (n) => n.toString(),
|
|
126
|
+
from: (s) => parseInt(s, 10),
|
|
127
|
+
};
|
|
128
|
+
const sinkFn = sink(graph, stringToNum);
|
|
129
|
+
// Snapshot with string values
|
|
130
|
+
const snapshot = [['42', 1], ['99', 2]];
|
|
131
|
+
const sinkAdapter = sinkFn(snapshot);
|
|
132
|
+
const { stream: source } = sinkAdapter.build();
|
|
133
|
+
// Should be converted to numbers
|
|
134
|
+
const entries = [...source.snapshot.getEntries()];
|
|
135
|
+
expect(entries).toContainEqual([42, 1]);
|
|
136
|
+
expect(entries).toContainEqual([99, 2]);
|
|
137
|
+
});
|
|
138
|
+
it('should handle empty snapshot', () => {
|
|
139
|
+
const graph = new Graph();
|
|
140
|
+
const sinkFn = sink(graph, iso.id());
|
|
141
|
+
const sinkAdapter = sinkFn([]);
|
|
142
|
+
const { stream: source } = sinkAdapter.build();
|
|
143
|
+
expect([...source.snapshot.getEntries()]).toEqual([]);
|
|
144
|
+
});
|
|
145
|
+
it('should create new source instances on each build call', () => {
|
|
146
|
+
const graph = new Graph();
|
|
147
|
+
const sinkFn = sink(graph, iso.id());
|
|
148
|
+
const sinkAdapter = sinkFn([['test', 1]]);
|
|
149
|
+
const { stream: source1 } = sinkAdapter.build();
|
|
150
|
+
const { stream: source2 } = sinkAdapter.build();
|
|
151
|
+
// Each build creates a new instance - use strict equality to avoid vitest's deep comparison
|
|
152
|
+
expect(source1 === source2).toBe(false);
|
|
153
|
+
// But both have the same initial snapshot
|
|
154
|
+
expect([...source1.snapshot.getEntries()]).toEqual([['test', 1]]);
|
|
155
|
+
expect([...source2.snapshot.getEntries()]).toEqual([['test', 1]]);
|
|
156
|
+
});
|
|
157
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { Graph, inputValue } from 'derivation';
|
|
3
|
+
import { StreamSourceAdapter, StreamSinkAdapter, sink } from '../stream-adapter.js';
|
|
4
|
+
import * as iso from '../iso.js';
|
|
5
|
+
describe('StreamSourceAdapter', () => {
|
|
6
|
+
it('should provide snapshot of the stream value', () => {
|
|
7
|
+
const graph = new Graph();
|
|
8
|
+
const stream = inputValue(graph, { x: 10, y: 20 });
|
|
9
|
+
const adapter = new StreamSourceAdapter(stream, iso.id());
|
|
10
|
+
expect(adapter.Snapshot).toEqual({ x: 10, y: 20 });
|
|
11
|
+
});
|
|
12
|
+
it('should transform snapshot using isomorphism', () => {
|
|
13
|
+
const graph = new Graph();
|
|
14
|
+
const stream = inputValue(graph, { count: 42 });
|
|
15
|
+
const countToString = {
|
|
16
|
+
to: (obj) => ({ count: obj.count.toString() }),
|
|
17
|
+
from: (obj) => ({ count: parseInt(obj.count, 10) }),
|
|
18
|
+
};
|
|
19
|
+
const adapter = new StreamSourceAdapter(stream, countToString);
|
|
20
|
+
expect(adapter.Snapshot).toEqual({ count: '42' });
|
|
21
|
+
});
|
|
22
|
+
it('should provide last change as the current value', () => {
|
|
23
|
+
const graph = new Graph();
|
|
24
|
+
const stream = inputValue(graph, { a: 1 });
|
|
25
|
+
const adapter = new StreamSourceAdapter(stream, iso.id());
|
|
26
|
+
expect(adapter.LastChange).toEqual({ a: 1 });
|
|
27
|
+
stream.push({ a: 2 });
|
|
28
|
+
graph.step();
|
|
29
|
+
expect(adapter.LastChange).toEqual({ a: 2 });
|
|
30
|
+
});
|
|
31
|
+
it('should return the underlying stream', () => {
|
|
32
|
+
const graph = new Graph();
|
|
33
|
+
const stream = inputValue(graph, { test: true });
|
|
34
|
+
const adapter = new StreamSourceAdapter(stream, iso.id());
|
|
35
|
+
expect(adapter.Stream).toBe(stream);
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
describe('StreamSinkAdapter', () => {
|
|
39
|
+
it('should apply changes to a stream', () => {
|
|
40
|
+
const graph = new Graph();
|
|
41
|
+
const adapter = new StreamSinkAdapter(graph, iso.id(), { x: 0 });
|
|
42
|
+
const { stream, input } = adapter.build();
|
|
43
|
+
adapter.apply({ x: 5 }, input);
|
|
44
|
+
graph.step();
|
|
45
|
+
expect(stream.value).toEqual({ x: 5 });
|
|
46
|
+
adapter.apply({ x: 10 }, input);
|
|
47
|
+
graph.step();
|
|
48
|
+
expect(stream.value).toEqual({ x: 10 });
|
|
49
|
+
});
|
|
50
|
+
it('should transform changes using isomorphism', () => {
|
|
51
|
+
const graph = new Graph();
|
|
52
|
+
const stringToNum = {
|
|
53
|
+
to: (obj) => ({ val: obj.val.toString() }),
|
|
54
|
+
from: (obj) => ({ val: parseInt(obj.val, 10) }),
|
|
55
|
+
};
|
|
56
|
+
const adapter = new StreamSinkAdapter(graph, stringToNum, { val: '0' });
|
|
57
|
+
const { stream, input } = adapter.build();
|
|
58
|
+
// Apply change with string, should be converted to number
|
|
59
|
+
adapter.apply({ val: '42' }, input);
|
|
60
|
+
graph.step();
|
|
61
|
+
expect(stream.value).toEqual({ val: 42 });
|
|
62
|
+
});
|
|
63
|
+
it('should build a new input stream each time', () => {
|
|
64
|
+
const graph = new Graph();
|
|
65
|
+
const adapter = new StreamSinkAdapter(graph, iso.id(), { id: 'test' });
|
|
66
|
+
const { stream: stream1 } = adapter.build();
|
|
67
|
+
const { stream: stream2 } = adapter.build();
|
|
68
|
+
expect(stream1).toBeDefined();
|
|
69
|
+
expect(stream2).toBeDefined();
|
|
70
|
+
expect(stream1).not.toBe(stream2);
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
describe('sink function', () => {
|
|
74
|
+
it('should create a sink that initializes with snapshot', () => {
|
|
75
|
+
const graph = new Graph();
|
|
76
|
+
const sinkFn = sink(graph, iso.id());
|
|
77
|
+
const snapshot = { name: 'initial' };
|
|
78
|
+
const sinkAdapter = sinkFn(snapshot);
|
|
79
|
+
const { stream } = sinkAdapter.build();
|
|
80
|
+
expect(stream.value).toEqual({ name: 'initial' });
|
|
81
|
+
});
|
|
82
|
+
it('should allow applying changes after initialization', () => {
|
|
83
|
+
const graph = new Graph();
|
|
84
|
+
const sinkFn = sink(graph, iso.id());
|
|
85
|
+
const sinkAdapter = sinkFn({ count: 0 });
|
|
86
|
+
const { stream, input } = sinkAdapter.build();
|
|
87
|
+
expect(stream.value).toEqual({ count: 0 });
|
|
88
|
+
sinkAdapter.apply({ count: 5 }, input);
|
|
89
|
+
graph.step();
|
|
90
|
+
expect(stream.value).toEqual({ count: 5 });
|
|
91
|
+
sinkAdapter.apply({ count: 10 }, input);
|
|
92
|
+
graph.step();
|
|
93
|
+
expect(stream.value).toEqual({ count: 10 });
|
|
94
|
+
});
|
|
95
|
+
it('should transform snapshot using isomorphism', () => {
|
|
96
|
+
const graph = new Graph();
|
|
97
|
+
const strToNum = {
|
|
98
|
+
to: (obj) => ({ value: obj.value.toString() }),
|
|
99
|
+
from: (obj) => ({ value: parseInt(obj.value, 10) }),
|
|
100
|
+
};
|
|
101
|
+
const sinkFn = sink(graph, strToNum);
|
|
102
|
+
const sinkAdapter = sinkFn({ value: '99' });
|
|
103
|
+
const { stream } = sinkAdapter.build();
|
|
104
|
+
// Should be converted from string to number
|
|
105
|
+
expect(stream.value).toEqual({ value: 99 });
|
|
106
|
+
});
|
|
107
|
+
it('should create new stream instances on each build call', () => {
|
|
108
|
+
const graph = new Graph();
|
|
109
|
+
const sinkFn = sink(graph, iso.id());
|
|
110
|
+
const sinkAdapter = sinkFn({ data: 'test' });
|
|
111
|
+
const { stream: stream1 } = sinkAdapter.build();
|
|
112
|
+
const { stream: stream2 } = sinkAdapter.build();
|
|
113
|
+
// Each build creates a new instance
|
|
114
|
+
expect(stream1).not.toBe(stream2);
|
|
115
|
+
// But both have the same initial value
|
|
116
|
+
expect(stream1.value).toEqual({ data: 'test' });
|
|
117
|
+
expect(stream2.value).toEqual({ data: 'test' });
|
|
118
|
+
});
|
|
119
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import WeakList from '../weak-list.js';
|
|
3
|
+
describe('WeakList', () => {
|
|
4
|
+
it('should store and iterate over objects', () => {
|
|
5
|
+
const list = new WeakList();
|
|
6
|
+
const obj1 = { id: 1 };
|
|
7
|
+
const obj2 = { id: 2 };
|
|
8
|
+
const obj3 = { id: 3 };
|
|
9
|
+
list.add(obj1);
|
|
10
|
+
list.add(obj2);
|
|
11
|
+
list.add(obj3);
|
|
12
|
+
const items = [...list];
|
|
13
|
+
expect(items).toHaveLength(3);
|
|
14
|
+
expect(items).toContain(obj1);
|
|
15
|
+
expect(items).toContain(obj2);
|
|
16
|
+
expect(items).toContain(obj3);
|
|
17
|
+
});
|
|
18
|
+
it('should automatically clean up collected references', () => {
|
|
19
|
+
const list = new WeakList();
|
|
20
|
+
// Add objects in a scope
|
|
21
|
+
{
|
|
22
|
+
const obj1 = { id: 1 };
|
|
23
|
+
const obj2 = { id: 2 };
|
|
24
|
+
list.add(obj1);
|
|
25
|
+
list.add(obj2);
|
|
26
|
+
}
|
|
27
|
+
// Keep one object alive
|
|
28
|
+
const obj3 = { id: 3 };
|
|
29
|
+
list.add(obj3);
|
|
30
|
+
// Force garbage collection if available
|
|
31
|
+
if (global.gc) {
|
|
32
|
+
global.gc();
|
|
33
|
+
}
|
|
34
|
+
// Iterate - this should clean up dead references
|
|
35
|
+
const items = [...list];
|
|
36
|
+
// obj3 should still be present
|
|
37
|
+
expect(items).toContain(obj3);
|
|
38
|
+
// Note: We can't reliably test that obj1 and obj2 are gone
|
|
39
|
+
// because GC timing is non-deterministic
|
|
40
|
+
});
|
|
41
|
+
it('should handle empty list', () => {
|
|
42
|
+
const list = new WeakList();
|
|
43
|
+
const items = [...list];
|
|
44
|
+
expect(items).toHaveLength(0);
|
|
45
|
+
});
|
|
46
|
+
it('should allow multiple iterations', () => {
|
|
47
|
+
const list = new WeakList();
|
|
48
|
+
const obj1 = { id: 1 };
|
|
49
|
+
const obj2 = { id: 2 };
|
|
50
|
+
list.add(obj1);
|
|
51
|
+
list.add(obj2);
|
|
52
|
+
const items1 = [...list];
|
|
53
|
+
expect(items1).toHaveLength(2);
|
|
54
|
+
const items2 = [...list];
|
|
55
|
+
expect(items2).toHaveLength(2);
|
|
56
|
+
expect(items2).toEqual(items1);
|
|
57
|
+
});
|
|
58
|
+
it('should work with different object types', () => {
|
|
59
|
+
class MyClass {
|
|
60
|
+
constructor(value) {
|
|
61
|
+
this.value = value;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
const list = new WeakList();
|
|
65
|
+
const obj1 = new MyClass('test1');
|
|
66
|
+
const obj2 = new MyClass('test2');
|
|
67
|
+
list.add(obj1);
|
|
68
|
+
list.add(obj2);
|
|
69
|
+
const items = [...list];
|
|
70
|
+
expect(items).toHaveLength(2);
|
|
71
|
+
expect(items[0]).toBeInstanceOf(MyClass);
|
|
72
|
+
expect(items[1]).toBeInstanceOf(MyClass);
|
|
73
|
+
});
|
|
74
|
+
it('should maintain insertion order for live objects', () => {
|
|
75
|
+
const list = new WeakList();
|
|
76
|
+
const obj1 = { id: 1 };
|
|
77
|
+
const obj2 = { id: 2 };
|
|
78
|
+
const obj3 = { id: 3 };
|
|
79
|
+
list.add(obj1);
|
|
80
|
+
list.add(obj2);
|
|
81
|
+
list.add(obj3);
|
|
82
|
+
const items = [...list];
|
|
83
|
+
expect(items[0]).toBe(obj1);
|
|
84
|
+
expect(items[1]).toBe(obj2);
|
|
85
|
+
expect(items[2]).toBe(obj3);
|
|
86
|
+
});
|
|
87
|
+
it('should handle adding the same object multiple times', () => {
|
|
88
|
+
const list = new WeakList();
|
|
89
|
+
const obj1 = { id: 1 };
|
|
90
|
+
list.add(obj1);
|
|
91
|
+
list.add(obj1);
|
|
92
|
+
list.add(obj1);
|
|
93
|
+
const items = [...list];
|
|
94
|
+
// Should have 3 references to the same object
|
|
95
|
+
expect(items).toHaveLength(3);
|
|
96
|
+
expect(items[0]).toBe(obj1);
|
|
97
|
+
expect(items[1]).toBe(obj1);
|
|
98
|
+
expect(items[2]).toBe(obj1);
|
|
99
|
+
});
|
|
100
|
+
});
|