1 |
|
2 |
|
3 | const expect = require('expect');
|
4 | const { ObservedRemoveSet } = require('../src');
|
5 | const { generateValue } = require('./lib/values');
|
6 |
|
7 |
|
8 | describe('Set', () => {
|
9 | test('Add and delete values', () => {
|
10 | const A = generateValue();
|
11 | const B = generateValue();
|
12 | const set = new ObservedRemoveSet();
|
13 | expect(set.size).toEqual(0);
|
14 | set.add(A);
|
15 | expect(set.has(A)).toEqual(true);
|
16 | expect(set.has(B)).toEqual(false);
|
17 | expect(set.size).toEqual(1);
|
18 | set.add(B);
|
19 | expect(set.has(A)).toEqual(true);
|
20 | expect(set.has(B)).toEqual(true);
|
21 | expect(set.size).toEqual(2);
|
22 | set.delete(B);
|
23 | expect(set.has(A)).toEqual(true);
|
24 | expect(set.has(B)).toEqual(false);
|
25 | expect(set.size).toEqual(1);
|
26 | set.delete(A);
|
27 | expect(set.has(A)).toEqual(false);
|
28 | expect(set.has(B)).toEqual(false);
|
29 | expect(set.size).toEqual(0);
|
30 | set.add(A);
|
31 | expect(set.has(A)).toEqual(true);
|
32 | expect(set.has(B)).toEqual(false);
|
33 | expect(set.size).toEqual(1);
|
34 | set.add(B);
|
35 | expect(set.has(A)).toEqual(true);
|
36 | expect(set.has(B)).toEqual(true);
|
37 | expect(set.size).toEqual(2);
|
38 | expect([...set.values()]).toEqual([A, B]);
|
39 | expect([...set]).toEqual([A, B]);
|
40 | expect([...set.entries()]).toEqual([[A, A], [B, B]]);
|
41 | });
|
42 |
|
43 | test('Emit add and delete events', async () => {
|
44 | const A = generateValue();
|
45 | const B = generateValue();
|
46 | const set = new ObservedRemoveSet();
|
47 | const addAPromise = new Promise((resolve) => {
|
48 | set.once('add', (x) => {
|
49 | expect(x).toEqual(A);
|
50 | resolve();
|
51 | });
|
52 | set.add(A);
|
53 | });
|
54 | const addBPromise = new Promise((resolve) => {
|
55 | set.once('add', (x) => {
|
56 | expect(x).toEqual(B);
|
57 | resolve();
|
58 | });
|
59 | set.add(B);
|
60 | });
|
61 | await addAPromise;
|
62 | await addBPromise;
|
63 | const deleteAPromise = new Promise((resolve) => {
|
64 | set.once('delete', (x) => {
|
65 | expect(x).toEqual(A);
|
66 | resolve();
|
67 | });
|
68 | set.delete(A);
|
69 | });
|
70 | const deleteBPromise = new Promise((resolve) => {
|
71 | set.once('delete', (x) => {
|
72 | expect(x).toEqual(B);
|
73 | resolve();
|
74 | });
|
75 | set.delete(B);
|
76 | });
|
77 | await deleteAPromise;
|
78 | await deleteBPromise;
|
79 | });
|
80 |
|
81 | test('Iterate through values', () => {
|
82 | const A = generateValue();
|
83 | const B = generateValue();
|
84 | const C = generateValue();
|
85 | const set = new ObservedRemoveSet([A, B, C]);
|
86 | let i = 0;
|
87 | for (const x of set) {
|
88 | if (i === 0) {
|
89 | expect(x).toEqual(A);
|
90 | } else if (i === 1) {
|
91 | expect(x).toEqual(B);
|
92 | } else if (i === 2) {
|
93 | expect(x).toEqual(C);
|
94 | }
|
95 | i += 1;
|
96 | }
|
97 | set.forEach((x, index) => {
|
98 | if (index === 0) {
|
99 | expect(x).toEqual(A);
|
100 | } else if (index === 1) {
|
101 | expect(x).toEqual(B);
|
102 | } else if (index === 2) {
|
103 | expect(x).toEqual(C);
|
104 | }
|
105 | });
|
106 | });
|
107 |
|
108 | test('Hashes objects', () => {
|
109 | const A = [{ example: 1 }];
|
110 | const B = [{ example: 1 }];
|
111 | const set = new ObservedRemoveSet([A, B]);
|
112 | expect(set.size).toEqual(1);
|
113 | expect(set.has(A)).toEqual(true);
|
114 | expect(set.has(B)).toEqual(true);
|
115 | });
|
116 |
|
117 | test('Clear values', () => {
|
118 | const A = generateValue();
|
119 | const B = generateValue();
|
120 | const C = generateValue();
|
121 | const set = new ObservedRemoveSet([A, B, C], { maxAge: -1, bufferPublishing: 0 });
|
122 | expect(set.size).toEqual(3);
|
123 | set.clear();
|
124 | expect(set.size).toEqual(0);
|
125 | expect(set.insertQueue.length).toEqual(0);
|
126 | expect(set.deleteQueue.length).toEqual(0);
|
127 | expect(set.deletions.size).toEqual(3);
|
128 | set.flush();
|
129 | expect(set.size).toEqual(0);
|
130 | expect(set.insertQueue.length).toEqual(0);
|
131 | expect(set.deleteQueue.length).toEqual(0);
|
132 | expect(set.deletions.size).toEqual(0);
|
133 | });
|
134 |
|
135 | test('Synchronize sets', async () => {
|
136 | const X = generateValue();
|
137 | const Y = generateValue();
|
138 | const Z = generateValue();
|
139 | const alice = new ObservedRemoveSet();
|
140 | const bob = new ObservedRemoveSet();
|
141 | let aliceAddCount = 0;
|
142 | let bobAddCount = 0;
|
143 | let aliceDeleteCount = 0;
|
144 | let bobDeleteCount = 0;
|
145 | alice.on('add', () => (aliceAddCount += 1));
|
146 | bob.on('add', () => (bobAddCount += 1));
|
147 | alice.on('delete', () => (aliceDeleteCount += 1));
|
148 | bob.on('delete', () => (bobDeleteCount += 1));
|
149 | alice.on('publish', (message) => {
|
150 | bob.process(message);
|
151 | });
|
152 | bob.on('publish', (message) => {
|
153 | alice.process(message);
|
154 | });
|
155 | alice.add(X);
|
156 | alice.add(Y);
|
157 | alice.add(Z);
|
158 | while (aliceAddCount !== 3 || bobAddCount !== 3) {
|
159 | await new Promise((resolve) => setTimeout(resolve, 20));
|
160 | }
|
161 | expect([...alice]).toEqual([X, Y, Z]);
|
162 | expect([...bob]).toEqual([X, Y, Z]);
|
163 | bob.delete(X);
|
164 | bob.delete(Y);
|
165 | bob.delete(Z);
|
166 | while (aliceDeleteCount !== 3 || bobDeleteCount !== 3) {
|
167 | await new Promise((resolve) => setTimeout(resolve, 100));
|
168 | }
|
169 | expect([...alice]).toEqual([]);
|
170 | expect([...bob]).toEqual([]);
|
171 | });
|
172 |
|
173 | test('Flush values', async () => {
|
174 | const X = generateValue();
|
175 | const Y = generateValue();
|
176 | const Z = generateValue();
|
177 | const set = new ObservedRemoveSet([X, Y, Z], { maxAge: 100 });
|
178 | set.delete(X);
|
179 | set.delete(Y);
|
180 | set.delete(Z);
|
181 | expect(set.deletions.size).toEqual(3);
|
182 | set.flush();
|
183 | expect(set.deletions.size).toEqual(3);
|
184 | await new Promise((resolve) => setTimeout(resolve, 200));
|
185 | set.flush();
|
186 | expect(set.deletions.size).toEqual(0);
|
187 | });
|
188 |
|
189 | test('Flush deletions from add events', async () => {
|
190 | const X = generateValue();
|
191 | const Y = generateValue();
|
192 | const Z = generateValue();
|
193 | const set = new ObservedRemoveSet([X, Y, Z], { maxAge: 100 });
|
194 | set.flush();
|
195 | expect(set.deletions.size).toEqual(0);
|
196 | set.add(X);
|
197 | set.add(Y);
|
198 | set.add(Z);
|
199 | expect(set.deletions.size).toEqual(3);
|
200 | await new Promise((resolve) => setTimeout(resolve, 200));
|
201 | set.flush();
|
202 | expect(set.deletions.size).toEqual(0);
|
203 | });
|
204 |
|
205 | test('Only send out delete events when values have changed', async () => {
|
206 | const X = generateValue();
|
207 | const set = new ObservedRemoveSet([X], { maxAge: 100 });
|
208 | const deletePromise = new Promise((resolve, reject) => {
|
209 | const handleDelete = () => {
|
210 | clearTimeout(timeout);
|
211 | reject(new Error('Delete event should not be trigged on add'));
|
212 | };
|
213 | const timeout = setTimeout(() => {
|
214 | set.removeListener('delete', handleDelete);
|
215 | resolve();
|
216 | }, 200);
|
217 | set.on('delete', handleDelete);
|
218 | });
|
219 | set.add(X);
|
220 | await deletePromise;
|
221 | });
|
222 |
|
223 | test('Synchronize add and delete events', async () => {
|
224 | const X = generateValue();
|
225 | const Y = generateValue();
|
226 | const alice = new ObservedRemoveSet();
|
227 | const bob = new ObservedRemoveSet();
|
228 | alice.on('publish', (message) => {
|
229 | bob.process(message);
|
230 | });
|
231 | bob.on('publish', (message) => {
|
232 | alice.process(message);
|
233 | });
|
234 | const aliceAddXPromise = new Promise((resolve) => {
|
235 | alice.once('add', (value) => {
|
236 | expect(value).toEqual(X);
|
237 | resolve();
|
238 | });
|
239 | });
|
240 | const aliceDeleteXPromise = new Promise((resolve) => {
|
241 | alice.once('delete', (value) => {
|
242 | expect(value).toEqual(X);
|
243 | resolve();
|
244 | });
|
245 | });
|
246 | bob.add(X);
|
247 | await aliceAddXPromise;
|
248 | bob.delete(X);
|
249 | await aliceDeleteXPromise;
|
250 | const bobAddYPromise = new Promise((resolve) => {
|
251 | bob.once('add', (value) => {
|
252 | expect(value).toEqual(Y);
|
253 | resolve();
|
254 | });
|
255 | });
|
256 | const bobDeleteYPromise = new Promise((resolve) => {
|
257 | bob.once('delete', (value) => {
|
258 | expect(value).toEqual(Y);
|
259 | resolve();
|
260 | });
|
261 | });
|
262 | alice.add(Y);
|
263 | await bobAddYPromise;
|
264 | alice.delete(Y);
|
265 | await bobDeleteYPromise;
|
266 | });
|
267 |
|
268 | test('Should not emit events for remote set/delete combos on sync', async () => {
|
269 | const X = generateValue();
|
270 | const Y = generateValue();
|
271 | const alice = new ObservedRemoveSet();
|
272 | const bob = new ObservedRemoveSet();
|
273 | alice.add(X);
|
274 | alice.delete(X);
|
275 | bob.add(Y);
|
276 | bob.delete(Y);
|
277 | await new Promise((resolve) => setTimeout(resolve, 250));
|
278 | const bobPromise = new Promise((resolve, reject) => {
|
279 | bob.once('add', () => {
|
280 | reject(new Error('Bob should not receive add event'));
|
281 | });
|
282 | bob.once('delete', () => {
|
283 | reject(new Error('Bob should not receive delete event'));
|
284 | });
|
285 | setTimeout(resolve, 500);
|
286 | });
|
287 | const alicePromise = new Promise((resolve, reject) => {
|
288 | alice.once('add', () => {
|
289 | reject(new Error('Alice should not receive add event'));
|
290 | });
|
291 | alice.once('delete', () => {
|
292 | reject(new Error('Alice should not receive delete event'));
|
293 | });
|
294 | setTimeout(resolve, 500);
|
295 | });
|
296 | alice.on('publish', (message) => {
|
297 | bob.process(message);
|
298 | });
|
299 | bob.on('publish', (message) => {
|
300 | alice.process(message);
|
301 | });
|
302 | alice.sync();
|
303 | bob.sync();
|
304 | await bobPromise;
|
305 | await alicePromise;
|
306 | expect(alice.has(X)).toEqual(false);
|
307 | expect(alice.has(Y)).toEqual(false);
|
308 | expect(bob.has(X)).toEqual(false);
|
309 | expect(bob.has(Y)).toEqual(false);
|
310 | });
|
311 |
|
312 | test('Synchronize mixed sets using sync', async () => {
|
313 | const A = generateValue();
|
314 | const B = generateValue();
|
315 | const C = generateValue();
|
316 | const X = generateValue();
|
317 | const Y = generateValue();
|
318 | const Z = generateValue();
|
319 | const alice = new ObservedRemoveSet();
|
320 | const bob = new ObservedRemoveSet();
|
321 | alice.add(A);
|
322 | bob.add(X);
|
323 | alice.add(B);
|
324 | bob.add(Y);
|
325 | alice.add(C);
|
326 | bob.add(Z);
|
327 | let aliceAddCount = 0;
|
328 | let bobAddCount = 0;
|
329 | let aliceDeleteCount = 0;
|
330 | let bobDeleteCount = 0;
|
331 | await new Promise((resolve) => setTimeout(resolve, 100));
|
332 | expect(new Set([...alice])).toEqual(new Set([A, B, C]));
|
333 | expect(new Set([...bob])).toEqual(new Set([X, Y, Z]));
|
334 | alice.on('add', () => (aliceAddCount += 1));
|
335 | bob.on('add', () => (bobAddCount += 1));
|
336 | alice.on('delete', () => (aliceDeleteCount += 1));
|
337 | bob.on('delete', () => (bobDeleteCount += 1));
|
338 | alice.on('publish', (message) => {
|
339 | bob.process(message);
|
340 | });
|
341 | bob.on('publish', (message) => {
|
342 | alice.process(message);
|
343 | });
|
344 | alice.sync();
|
345 | bob.sync();
|
346 | while (aliceAddCount !== 3 || bobAddCount !== 3) {
|
347 | await new Promise((resolve) => setTimeout(resolve, 20));
|
348 | }
|
349 | expect(new Set([...alice])).toEqual(new Set([A, X, B, Y, C, Z]));
|
350 | expect(new Set([...bob])).toEqual(new Set([A, X, B, Y, C, Z]));
|
351 | });
|
352 |
|
353 | test('Values should not repeat', async () => {
|
354 | const value = generateValue();
|
355 | const alice = new ObservedRemoveSet();
|
356 | alice.add(value);
|
357 | alice.add(value);
|
358 | expect([...alice].length).toEqual(1);
|
359 | expect([...alice.values()].length).toEqual(1);
|
360 | expect([...alice.entries()].length).toEqual(1);
|
361 | expect([...alice]).toEqual([value]);
|
362 | expect([...alice.values()]).toEqual([value]);
|
363 | expect([...alice.entries()]).toEqual([[value, value]]);
|
364 | });
|
365 |
|
366 | test('Synchronizes 100 asynchrous sets', async () => {
|
367 | const A = generateValue();
|
368 | const B = generateValue();
|
369 | const C = generateValue();
|
370 | const sets = [];
|
371 | const callbacks = [];
|
372 | const publish = (sourceId:number, message:Buffer) => {
|
373 | for (let i = 0; i < callbacks.length; i += 1) {
|
374 | const [targetId, callback] = callbacks[i];
|
375 | if (targetId === sourceId) {
|
376 | continue;
|
377 | }
|
378 | setTimeout(() => callback(message), Math.round(1000 * Math.random()));
|
379 | }
|
380 | };
|
381 | const subscribe = (targetId: number, callback:Function) => {
|
382 | callbacks.push([targetId, callback]);
|
383 | };
|
384 | const getPair = () => {
|
385 | const setA = sets[Math.floor(Math.random() * sets.length)];
|
386 | let setB = setA;
|
387 | while (setB === setA) {
|
388 | setB = sets[Math.floor(Math.random() * sets.length)];
|
389 | }
|
390 | return [setA, setB];
|
391 | };
|
392 | for (let i = 0; i < 100; i += 1) {
|
393 | const set = new ObservedRemoveSet();
|
394 | set.on('publish', (message) => publish(i, message));
|
395 | subscribe(i, (message) => set.process(message));
|
396 | sets.push(set);
|
397 | }
|
398 | const [alice, bob] = getPair();
|
399 | let aliceAddCount = 0;
|
400 | let bobAddCount = 0;
|
401 | let aliceDeleteCount = 0;
|
402 | let bobDeleteCount = 0;
|
403 | alice.on('add', () => (aliceAddCount += 1));
|
404 | bob.on('add', () => (bobAddCount += 1));
|
405 | alice.on('delete', () => (aliceDeleteCount += 1));
|
406 | bob.on('delete', () => (bobDeleteCount += 1));
|
407 | alice.add(A);
|
408 | alice.add(B);
|
409 | alice.add(C);
|
410 | while (aliceAddCount !== 3 || bobAddCount !== 3) {
|
411 | await new Promise((resolve) => setTimeout(resolve, 100));
|
412 | }
|
413 | bob.delete(C);
|
414 | bob.delete(B);
|
415 | bob.delete(A);
|
416 | while (aliceDeleteCount !== 3 || bobDeleteCount !== 3) {
|
417 | await new Promise((resolve) => setTimeout(resolve, 100));
|
418 | }
|
419 | expect([...alice]).toEqual([]);
|
420 | expect([...bob]).toEqual([]);
|
421 | });
|
422 | });
|
423 |
|