@voidhash/mimic-react 1.0.0-beta.16 → 1.0.0-beta.17

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.
@@ -1,588 +0,0 @@
1
- import { describe, it, expect, beforeEach, vi } from "vitest";
2
- import { createStore } from "zustand";
3
- import { mimic } from "../../src/zustand/middleware";
4
- import type { ClientDocument } from "@voidhash/mimic/client";
5
- import type { Primitive, Presence } from "@voidhash/mimic";
6
-
7
- // =============================================================================
8
- // Mock ClientDocument
9
- // =============================================================================
10
-
11
- interface MockClientDocumentState {
12
- snapshot: { title: string; count: number };
13
- isConnected: boolean;
14
- isReady: boolean;
15
- pendingCount: number;
16
- hasPendingChanges: boolean;
17
- // Presence
18
- presenceEnabled: boolean;
19
- presenceSelfId?: string;
20
- presenceSelf?: unknown;
21
- presenceOthers: Map<string, Presence.PresenceEntry<unknown>>;
22
- }
23
-
24
- interface MockClientDocument {
25
- root: { toSnapshot: () => { title: string; count: number } };
26
- isConnected: () => boolean;
27
- isReady: () => boolean;
28
- getPendingCount: () => number;
29
- hasPendingChanges: () => boolean;
30
- connect: () => void;
31
- subscribe: (listener: {
32
- onStateChange?: () => void;
33
- onConnectionChange?: () => void;
34
- onReady?: () => void;
35
- }) => () => void;
36
- presence?: {
37
- selfId: () => string | undefined;
38
- self: () => unknown | undefined;
39
- others: () => ReadonlyMap<string, Presence.PresenceEntry<unknown>>;
40
- all: () => ReadonlyMap<string, Presence.PresenceEntry<unknown>>;
41
- subscribe: (listener: { onPresenceChange?: () => void }) => () => void;
42
- };
43
- // Helpers to update state and trigger events
44
- _setState: (updates: Partial<MockClientDocumentState>) => void;
45
- _triggerStateChange: () => void;
46
- _triggerConnectionChange: () => void;
47
- _triggerReady: () => void;
48
- _triggerPresenceChange: () => void;
49
- _getSubscriberCount: () => number;
50
- }
51
-
52
- const createMockClientDocument = (
53
- initial?: Partial<MockClientDocumentState>
54
- ): MockClientDocument => {
55
- let state: MockClientDocumentState = {
56
- snapshot: { title: "Test", count: 0 },
57
- isConnected: false,
58
- isReady: false,
59
- pendingCount: 0,
60
- hasPendingChanges: false,
61
- presenceEnabled: false,
62
- presenceOthers: new Map(),
63
- ...initial,
64
- };
65
-
66
- const listeners = new Set<{
67
- onStateChange?: () => void;
68
- onConnectionChange?: () => void;
69
- onReady?: () => void;
70
- }>();
71
-
72
- const presenceListeners = new Set<{ onPresenceChange?: () => void }>();
73
-
74
- return {
75
- root: {
76
- toSnapshot: () => state.snapshot,
77
- },
78
- isConnected: () => state.isConnected,
79
- isReady: () => state.isReady,
80
- getPendingCount: () => state.pendingCount,
81
- hasPendingChanges: () => state.hasPendingChanges,
82
- getActiveDraftIds: () => new Set<string>(),
83
- createDraft: () => {
84
- throw new Error("Not implemented in mock");
85
- },
86
- connect: () => {
87
- // Mock connect - does nothing in tests
88
- },
89
- subscribe: (listener) => {
90
- listeners.add(listener);
91
- return () => {
92
- listeners.delete(listener);
93
- };
94
- },
95
- presence: state.presenceEnabled
96
- ? {
97
- selfId: () => state.presenceSelfId,
98
- self: () => state.presenceSelf,
99
- others: () => state.presenceOthers,
100
- all: () => {
101
- const all = new Map<string, Presence.PresenceEntry<unknown>>();
102
- for (const [id, entry] of state.presenceOthers) {
103
- all.set(id, entry);
104
- }
105
- if (state.presenceSelfId !== undefined && state.presenceSelf !== undefined) {
106
- all.set(state.presenceSelfId, { data: state.presenceSelf });
107
- }
108
- return all;
109
- },
110
- subscribe: (listener) => {
111
- presenceListeners.add(listener);
112
- return () => {
113
- presenceListeners.delete(listener);
114
- };
115
- },
116
- }
117
- : undefined,
118
- _setState: (updates) => {
119
- state = { ...state, ...updates };
120
- },
121
- _triggerStateChange: () => {
122
- for (const listener of listeners) {
123
- listener.onStateChange?.();
124
- }
125
- },
126
- _triggerConnectionChange: () => {
127
- for (const listener of listeners) {
128
- listener.onConnectionChange?.();
129
- }
130
- },
131
- _triggerReady: () => {
132
- for (const listener of listeners) {
133
- listener.onReady?.();
134
- }
135
- },
136
- _triggerPresenceChange: () => {
137
- for (const listener of presenceListeners) {
138
- listener.onPresenceChange?.();
139
- }
140
- },
141
- _getSubscriberCount: () => listeners.size,
142
- };
143
- };
144
-
145
- // =============================================================================
146
- // Test Schema Type (for type inference)
147
- // =============================================================================
148
-
149
- type TestSchema = Primitive.Primitive<
150
- { title: string; count: number },
151
- { toSnapshot: () => { title: string; count: number } }
152
- >;
153
-
154
- // =============================================================================
155
- // Tests
156
- // =============================================================================
157
-
158
- describe("mimic middleware", () => {
159
- let mockDocument: MockClientDocument;
160
-
161
- beforeEach(() => {
162
- mockDocument = createMockClientDocument();
163
- });
164
-
165
- describe("Basic Integration", () => {
166
- it("should create store with initial mimic slice", () => {
167
- mockDocument._setState({
168
- snapshot: { title: "Initial", count: 42 },
169
- isConnected: true,
170
- isReady: true,
171
- pendingCount: 0,
172
- hasPendingChanges: false,
173
- });
174
-
175
- const store = createStore(
176
- mimic(mockDocument as unknown as ClientDocument.ClientDocument<TestSchema>, () => ({}))
177
- );
178
-
179
- const state = store.getState();
180
-
181
- expect(state.mimic).toBeDefined();
182
- expect(state.mimic.snapshot).toEqual({ title: "Initial", count: 42 });
183
- expect(state.mimic.isConnected).toBe(true);
184
- expect(state.mimic.isReady).toBe(true);
185
- expect(state.mimic.pendingCount).toBe(0);
186
- expect(state.mimic.hasPendingChanges).toBe(false);
187
- });
188
-
189
- it("should preserve user state alongside mimic slice", () => {
190
- const store = createStore(
191
- mimic(mockDocument as unknown as ClientDocument.ClientDocument<TestSchema>, () => ({
192
- customField: "hello",
193
- customNumber: 123,
194
- }))
195
- );
196
-
197
- const state = store.getState();
198
-
199
- expect(state.customField).toBe("hello");
200
- expect(state.customNumber).toBe(123);
201
- expect(state.mimic).toBeDefined();
202
- });
203
-
204
- it("should provide document reference via state.mimic.document", () => {
205
- const store = createStore(
206
- mimic(mockDocument as unknown as ClientDocument.ClientDocument<TestSchema>, () => ({}))
207
- );
208
-
209
- const state = store.getState();
210
-
211
- expect(state.mimic.document).toBe(mockDocument);
212
- });
213
-
214
- it("should reflect initial document state correctly", () => {
215
- mockDocument._setState({
216
- snapshot: { title: "Doc Title", count: 99 },
217
- isConnected: false,
218
- isReady: false,
219
- pendingCount: 3,
220
- hasPendingChanges: true,
221
- });
222
-
223
- const store = createStore(
224
- mimic(mockDocument as unknown as ClientDocument.ClientDocument<TestSchema>, () => ({}))
225
- );
226
-
227
- const state = store.getState();
228
-
229
- expect(state.mimic.snapshot).toEqual({ title: "Doc Title", count: 99 });
230
- expect(state.mimic.isConnected).toBe(false);
231
- expect(state.mimic.isReady).toBe(false);
232
- expect(state.mimic.pendingCount).toBe(3);
233
- expect(state.mimic.hasPendingChanges).toBe(true);
234
- });
235
- });
236
-
237
- describe("Reactive Updates", () => {
238
- it("should update store when onStateChange fires", () => {
239
- mockDocument._setState({
240
- snapshot: { title: "Before", count: 1 },
241
- });
242
-
243
- const store = createStore(
244
- mimic(mockDocument as unknown as ClientDocument.ClientDocument<TestSchema>, () => ({}))
245
- );
246
-
247
- expect(store.getState().mimic.snapshot).toEqual({ title: "Before", count: 1 });
248
-
249
- // Update document state and trigger change
250
- mockDocument._setState({
251
- snapshot: { title: "After", count: 2 },
252
- });
253
- mockDocument._triggerStateChange();
254
-
255
- expect(store.getState().mimic.snapshot).toEqual({ title: "After", count: 2 });
256
- });
257
-
258
- it("should update store when onConnectionChange fires", () => {
259
- mockDocument._setState({
260
- isConnected: false,
261
- });
262
-
263
- const store = createStore(
264
- mimic(mockDocument as unknown as ClientDocument.ClientDocument<TestSchema>, () => ({}))
265
- );
266
-
267
- expect(store.getState().mimic.isConnected).toBe(false);
268
-
269
- // Update connection status and trigger change
270
- mockDocument._setState({
271
- isConnected: true,
272
- });
273
- mockDocument._triggerConnectionChange();
274
-
275
- expect(store.getState().mimic.isConnected).toBe(true);
276
- });
277
-
278
- it("should update store when onReady fires", () => {
279
- mockDocument._setState({
280
- isReady: false,
281
- });
282
-
283
- const store = createStore(
284
- mimic(mockDocument as unknown as ClientDocument.ClientDocument<TestSchema>, () => ({}))
285
- );
286
-
287
- expect(store.getState().mimic.isReady).toBe(false);
288
-
289
- // Update ready status and trigger change
290
- mockDocument._setState({
291
- isReady: true,
292
- });
293
- mockDocument._triggerReady();
294
-
295
- expect(store.getState().mimic.isReady).toBe(true);
296
- });
297
-
298
- it("should update pendingCount and hasPendingChanges on state change", () => {
299
- mockDocument._setState({
300
- pendingCount: 0,
301
- hasPendingChanges: false,
302
- });
303
-
304
- const store = createStore(
305
- mimic(mockDocument as unknown as ClientDocument.ClientDocument<TestSchema>, () => ({}))
306
- );
307
-
308
- expect(store.getState().mimic.pendingCount).toBe(0);
309
- expect(store.getState().mimic.hasPendingChanges).toBe(false);
310
-
311
- // Simulate pending transactions
312
- mockDocument._setState({
313
- pendingCount: 5,
314
- hasPendingChanges: true,
315
- });
316
- mockDocument._triggerStateChange();
317
-
318
- expect(store.getState().mimic.pendingCount).toBe(5);
319
- expect(store.getState().mimic.hasPendingChanges).toBe(true);
320
- });
321
-
322
- it("should update all mimic properties on any event", () => {
323
- mockDocument._setState({
324
- snapshot: { title: "V1", count: 1 },
325
- isConnected: false,
326
- isReady: false,
327
- pendingCount: 0,
328
- hasPendingChanges: false,
329
- });
330
-
331
- const store = createStore(
332
- mimic(mockDocument as unknown as ClientDocument.ClientDocument<TestSchema>, () => ({}))
333
- );
334
-
335
- // Update all state at once
336
- mockDocument._setState({
337
- snapshot: { title: "V2", count: 2 },
338
- isConnected: true,
339
- isReady: true,
340
- pendingCount: 3,
341
- hasPendingChanges: true,
342
- });
343
-
344
- // Triggering any event should refresh all mimic state
345
- mockDocument._triggerConnectionChange();
346
-
347
- const state = store.getState().mimic;
348
- expect(state.snapshot).toEqual({ title: "V2", count: 2 });
349
- expect(state.isConnected).toBe(true);
350
- expect(state.isReady).toBe(true);
351
- expect(state.pendingCount).toBe(3);
352
- expect(state.hasPendingChanges).toBe(true);
353
- });
354
- });
355
-
356
- describe("Presence", () => {
357
- it("should expose reactive self + others presence when enabled", () => {
358
- mockDocument = createMockClientDocument({
359
- presenceEnabled: true,
360
- presenceSelfId: "self",
361
- presenceSelf: { name: "me" },
362
- presenceOthers: new Map([
363
- ["other-1", { data: { name: "alice" }, userId: "u1" }],
364
- ]),
365
- });
366
-
367
- const store = createStore(
368
- mimic(mockDocument as unknown as ClientDocument.ClientDocument<TestSchema>, () => ({}))
369
- );
370
-
371
- const presence = (store.getState().mimic as any).presence;
372
- expect(presence).toBeDefined();
373
- expect(presence.selfId).toBe("self");
374
- expect(presence.self).toEqual({ name: "me" });
375
- expect(presence.others).toBeInstanceOf(Map);
376
- expect(presence.all).toBeInstanceOf(Map);
377
- expect(presence.others.get("other-1")).toEqual({ data: { name: "alice" }, userId: "u1" });
378
- expect(presence.all.get("self")).toEqual({ data: { name: "me" } });
379
- });
380
-
381
- it("should update store when onPresenceChange fires (and clone Map references)", () => {
382
- mockDocument = createMockClientDocument({
383
- presenceEnabled: true,
384
- presenceSelfId: "self",
385
- presenceSelf: { name: "me" },
386
- presenceOthers: new Map(),
387
- });
388
-
389
- const store = createStore(
390
- mimic(mockDocument as unknown as ClientDocument.ClientDocument<TestSchema>, () => ({}))
391
- );
392
-
393
- const beforePresence = (store.getState().mimic as any).presence;
394
- const beforeOthersRef = beforePresence.others;
395
- expect(beforePresence.self).toEqual({ name: "me" });
396
- expect(beforePresence.others.size).toBe(0);
397
-
398
- // Update others + self and trigger presence change
399
- mockDocument._setState({
400
- presenceSelf: { name: "me-2" },
401
- presenceOthers: new Map([
402
- ["other-1", { data: { name: "alice" }, userId: "u1" }],
403
- ]),
404
- });
405
- mockDocument._triggerPresenceChange();
406
-
407
- const afterPresence = (store.getState().mimic as any).presence;
408
- expect(afterPresence.self).toEqual({ name: "me-2" });
409
- expect(afterPresence.others.get("other-1")).toEqual({ data: { name: "alice" }, userId: "u1" });
410
-
411
- // Critical: maps should be new references so selectors re-render
412
- expect(afterPresence.others).not.toBe(beforeOthersRef);
413
- });
414
- });
415
-
416
- describe("Options", () => {
417
- it("should subscribe to document events by default (autoSubscribe: true)", () => {
418
- const store = createStore(
419
- mimic(mockDocument as unknown as ClientDocument.ClientDocument<TestSchema>, () => ({}))
420
- );
421
-
422
- expect(mockDocument._getSubscriberCount()).toBe(1);
423
-
424
- // Verify subscription is active by updating and triggering
425
- mockDocument._setState({ snapshot: { title: "Updated", count: 10 } });
426
- mockDocument._triggerStateChange();
427
-
428
- expect(store.getState().mimic.snapshot).toEqual({ title: "Updated", count: 10 });
429
- });
430
-
431
- it("should subscribe to document events when autoSubscribe: true explicitly", () => {
432
- const store = createStore(
433
- mimic(
434
- mockDocument as unknown as ClientDocument.ClientDocument<TestSchema>,
435
- () => ({}),
436
- { autoSubscribe: true }
437
- )
438
- );
439
-
440
- expect(mockDocument._getSubscriberCount()).toBe(1);
441
- });
442
-
443
- it("should not subscribe to document events when autoSubscribe: false", () => {
444
- const store = createStore(
445
- mimic(
446
- mockDocument as unknown as ClientDocument.ClientDocument<TestSchema>,
447
- () => ({}),
448
- { autoSubscribe: false }
449
- )
450
- );
451
-
452
- expect(mockDocument._getSubscriberCount()).toBe(0);
453
-
454
- // Verify store doesn't update when events fire
455
- const initialSnapshot = store.getState().mimic.snapshot;
456
- mockDocument._setState({ snapshot: { title: "Should Not Update", count: 999 } });
457
- mockDocument._triggerStateChange();
458
-
459
- expect(store.getState().mimic.snapshot).toEqual(initialSnapshot);
460
- });
461
- });
462
-
463
- describe("User State Integration", () => {
464
- it("should allow user state to use set function", () => {
465
- interface UserState {
466
- localCount: number;
467
- increment: () => void;
468
- }
469
-
470
- const store = createStore(
471
- mimic<TestSchema, undefined, UserState>(
472
- mockDocument as unknown as ClientDocument.ClientDocument<TestSchema>,
473
- (set) => ({
474
- localCount: 0,
475
- increment: () => set((state) => ({ ...state, localCount: state.localCount + 1 })),
476
- })
477
- )
478
- );
479
-
480
- expect(store.getState().localCount).toBe(0);
481
-
482
- store.getState().increment();
483
-
484
- expect(store.getState().localCount).toBe(1);
485
- // Mimic state should still be present
486
- expect(store.getState().mimic).toBeDefined();
487
- });
488
-
489
- it("should allow user state to use get function", () => {
490
- interface UserState {
491
- localCount: number;
492
- doubleCount: () => number;
493
- }
494
-
495
- const store = createStore(
496
- mimic<TestSchema, undefined, UserState>(
497
- mockDocument as unknown as ClientDocument.ClientDocument<TestSchema>,
498
- (set, get) => ({
499
- localCount: 5,
500
- doubleCount: () => get().localCount * 2,
501
- })
502
- )
503
- );
504
-
505
- expect(store.getState().doubleCount()).toBe(10);
506
- });
507
-
508
- it("should preserve mimic state when user state changes", () => {
509
- mockDocument._setState({
510
- snapshot: { title: "Preserved", count: 42 },
511
- });
512
-
513
- interface UserState {
514
- value: string;
515
- setValue: (v: string) => void;
516
- }
517
-
518
- const store = createStore(
519
- mimic<TestSchema, undefined, UserState>(
520
- mockDocument as unknown as ClientDocument.ClientDocument<TestSchema>,
521
- (set) => ({
522
- value: "initial",
523
- setValue: (v: string) => set((state) => ({ ...state, value: v })),
524
- })
525
- )
526
- );
527
-
528
- expect(store.getState().mimic.snapshot).toEqual({ title: "Preserved", count: 42 });
529
-
530
- store.getState().setValue("updated");
531
-
532
- expect(store.getState().value).toBe("updated");
533
- expect(store.getState().mimic.snapshot).toEqual({ title: "Preserved", count: 42 });
534
- });
535
- });
536
-
537
- describe("Multiple Events", () => {
538
- it("should handle rapid successive events correctly", () => {
539
- const store = createStore(
540
- mimic(mockDocument as unknown as ClientDocument.ClientDocument<TestSchema>, () => ({}))
541
- );
542
-
543
- // Fire multiple events in quick succession
544
- for (let i = 0; i < 10; i++) {
545
- mockDocument._setState({ snapshot: { title: `Update ${i}`, count: i } });
546
- mockDocument._triggerStateChange();
547
- }
548
-
549
- expect(store.getState().mimic.snapshot).toEqual({ title: "Update 9", count: 9 });
550
- });
551
-
552
- it("should handle interleaved event types", () => {
553
- const store = createStore(
554
- mimic(mockDocument as unknown as ClientDocument.ClientDocument<TestSchema>, () => ({}))
555
- );
556
-
557
- mockDocument._setState({ isConnected: true });
558
- mockDocument._triggerConnectionChange();
559
-
560
- mockDocument._setState({ snapshot: { title: "New", count: 1 } });
561
- mockDocument._triggerStateChange();
562
-
563
- mockDocument._setState({ isReady: true });
564
- mockDocument._triggerReady();
565
-
566
- const state = store.getState().mimic;
567
- expect(state.isConnected).toBe(true);
568
- expect(state.snapshot).toEqual({ title: "New", count: 1 });
569
- expect(state.isReady).toBe(true);
570
- });
571
- });
572
- });
573
-
574
- // =============================================================================
575
- // Type Export Tests (compile-time verification)
576
- // =============================================================================
577
-
578
- describe("Type Exports", () => {
579
- it("should export all expected types from index", async () => {
580
- // This test verifies that the types are properly exported
581
- // by importing them - if they're missing, TypeScript will error
582
- const exports = await import("../../src/zustand/index");
583
-
584
- expect(exports.mimic).toBeDefined();
585
- // Type exports are verified at compile time - if this file compiles,
586
- // the types are exported correctly
587
- });
588
- });