@voidhash/mimic 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.
- package/dist/EffectSchema.cjs +3 -3
- package/dist/EffectSchema.d.cts +5 -5
- package/dist/EffectSchema.d.cts.map +1 -1
- package/dist/EffectSchema.d.mts +5 -5
- package/dist/EffectSchema.d.mts.map +1 -1
- package/dist/EffectSchema.mjs +3 -3
- package/dist/EffectSchema.mjs.map +1 -1
- package/dist/FractionalIndex.mjs.map +1 -1
- package/dist/Operation.d.cts +4 -4
- package/dist/Operation.d.cts.map +1 -1
- package/dist/Operation.d.mts +4 -4
- package/dist/Operation.d.mts.map +1 -1
- package/dist/Operation.mjs.map +1 -1
- package/dist/OperationDefinition.d.cts +2 -2
- package/dist/OperationDefinition.d.cts.map +1 -1
- package/dist/OperationDefinition.d.mts +2 -2
- package/dist/OperationDefinition.d.mts.map +1 -1
- package/dist/OperationDefinition.mjs.map +1 -1
- package/dist/Presence.mjs.map +1 -1
- package/dist/SchemaJSON.cjs +305 -0
- package/dist/SchemaJSON.d.cts +11 -0
- package/dist/SchemaJSON.d.cts.map +1 -0
- package/dist/SchemaJSON.d.mts +11 -0
- package/dist/SchemaJSON.d.mts.map +1 -0
- package/dist/SchemaJSON.mjs +301 -0
- package/dist/SchemaJSON.mjs.map +1 -0
- package/dist/index.cjs +7 -0
- package/dist/index.d.cts +2 -1
- package/dist/index.d.mts +2 -1
- package/dist/index.mjs +2 -1
- package/dist/primitives/Array.cjs +12 -2
- package/dist/primitives/Array.d.cts.map +1 -1
- package/dist/primitives/Array.d.mts.map +1 -1
- package/dist/primitives/Array.mjs +12 -2
- package/dist/primitives/Array.mjs.map +1 -1
- package/dist/primitives/Boolean.mjs.map +1 -1
- package/dist/primitives/Either.mjs.map +1 -1
- package/dist/primitives/Literal.mjs.map +1 -1
- package/dist/primitives/Number.cjs +27 -5
- package/dist/primitives/Number.d.cts.map +1 -1
- package/dist/primitives/Number.d.mts.map +1 -1
- package/dist/primitives/Number.mjs +27 -5
- package/dist/primitives/Number.mjs.map +1 -1
- package/dist/primitives/String.cjs +44 -13
- package/dist/primitives/String.d.cts.map +1 -1
- package/dist/primitives/String.d.mts.map +1 -1
- package/dist/primitives/String.mjs +44 -13
- package/dist/primitives/String.mjs.map +1 -1
- package/dist/primitives/Union.mjs.map +1 -1
- package/dist/primitives/shared.d.cts +2 -0
- package/dist/primitives/shared.d.cts.map +1 -1
- package/dist/primitives/shared.d.mts +2 -0
- package/dist/primitives/shared.d.mts.map +1 -1
- package/dist/primitives/shared.mjs.map +1 -1
- package/package.json +15 -8
- package/src/EffectSchema.ts +3 -3
- package/src/FractionalIndex.ts +18 -18
- package/src/Operation.ts +5 -5
- package/src/OperationDefinition.ts +2 -2
- package/src/Presence.ts +3 -3
- package/src/SchemaJSON.ts +396 -0
- package/src/index.ts +1 -0
- package/src/primitives/Array.ts +18 -8
- package/src/primitives/Boolean.ts +2 -2
- package/src/primitives/Either.ts +2 -2
- package/src/primitives/Literal.ts +2 -2
- package/src/primitives/Number.ts +44 -22
- package/src/primitives/String.ts +61 -34
- package/src/primitives/Union.ts +1 -1
- package/src/primitives/shared.ts +2 -0
- package/.turbo/turbo-build.log +0 -270
- package/tests/Document.test.ts +0 -557
- package/tests/EffectSchema.test.ts +0 -546
- package/tests/FractionalIndex.test.ts +0 -377
- package/tests/OperationPath.test.ts +0 -151
- package/tests/Presence.test.ts +0 -321
- package/tests/Primitive.test.ts +0 -381
- package/tests/client/ClientDocument.test.ts +0 -1981
- package/tests/client/WebSocketTransport.test.ts +0 -1217
- package/tests/primitives/Array.test.ts +0 -526
- package/tests/primitives/Boolean.test.ts +0 -126
- package/tests/primitives/Either.test.ts +0 -707
- package/tests/primitives/Lazy.test.ts +0 -143
- package/tests/primitives/Literal.test.ts +0 -122
- package/tests/primitives/Number.test.ts +0 -133
- package/tests/primitives/String.test.ts +0 -128
- package/tests/primitives/Struct.test.ts +0 -1154
- package/tests/primitives/Tree.test.ts +0 -1139
- package/tests/primitives/TreeNode.test.ts +0 -50
- package/tests/primitives/Union.test.ts +0 -554
- package/tests/server/ServerDocument.test.ts +0 -903
- package/tsconfig.build.json +0 -24
- package/tsconfig.json +0 -8
- package/tsdown.config.ts +0 -18
- package/vitest.mts +0 -11
|
@@ -1,526 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "@effect/vitest";
|
|
2
|
-
import * as Primitive from "../../src/Primitive";
|
|
3
|
-
import * as ProxyEnvironment from "../../src/ProxyEnvironment";
|
|
4
|
-
import * as OperationPath from "../../src/OperationPath";
|
|
5
|
-
import * as Operation from "../../src/Operation";
|
|
6
|
-
|
|
7
|
-
describe("ArrayPrimitive", () => {
|
|
8
|
-
// Helper to create a mock environment with state access
|
|
9
|
-
const createEnvWithState = (
|
|
10
|
-
state: Primitive.ArrayEntry<any>[] = []
|
|
11
|
-
): { env: ReturnType<typeof ProxyEnvironment.make>; operations: Operation.Operation<any, any, any>[] } => {
|
|
12
|
-
const operations: Operation.Operation<any, any, any>[] = [];
|
|
13
|
-
let currentState = [...state];
|
|
14
|
-
|
|
15
|
-
const env = ProxyEnvironment.make({
|
|
16
|
-
onOperation: (op) => {
|
|
17
|
-
operations.push(op);
|
|
18
|
-
// Apply operation to keep state in sync for subsequent operations
|
|
19
|
-
if (op.kind === "array.insert") {
|
|
20
|
-
currentState.push(op.payload);
|
|
21
|
-
} else if (op.kind === "array.remove") {
|
|
22
|
-
currentState = currentState.filter(e => e.id !== op.payload.id);
|
|
23
|
-
} else if (op.kind === "array.move") {
|
|
24
|
-
currentState = currentState.map(e =>
|
|
25
|
-
e.id === op.payload.id ? { ...e, pos: op.payload.pos } : e
|
|
26
|
-
);
|
|
27
|
-
} else if (op.kind === "array.set") {
|
|
28
|
-
currentState = op.payload;
|
|
29
|
-
}
|
|
30
|
-
},
|
|
31
|
-
getState: () => currentState,
|
|
32
|
-
generateId: () => crypto.randomUUID(),
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
return { env, operations };
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
describe("proxy", () => {
|
|
39
|
-
it("set() generates array.set operation with entries", () => {
|
|
40
|
-
const { env, operations } = createEnvWithState();
|
|
41
|
-
const arrayPrimitive = Primitive.Array(Primitive.String());
|
|
42
|
-
const proxy = arrayPrimitive._internal.createProxy(env, OperationPath.make(""));
|
|
43
|
-
|
|
44
|
-
proxy.set(["a", "b", "c"]);
|
|
45
|
-
|
|
46
|
-
expect(operations).toHaveLength(1);
|
|
47
|
-
expect(operations[0]!.kind).toBe("array.set");
|
|
48
|
-
|
|
49
|
-
const entries = operations[0]!.payload as Primitive.ArrayEntry<string>[];
|
|
50
|
-
expect(entries).toHaveLength(3);
|
|
51
|
-
expect(entries[0]!.value).toBe("a");
|
|
52
|
-
expect(entries[1]!.value).toBe("b");
|
|
53
|
-
expect(entries[2]!.value).toBe("c");
|
|
54
|
-
|
|
55
|
-
// Each entry should have an ID and position
|
|
56
|
-
entries.forEach((entry: Primitive.ArrayEntry<string>) => {
|
|
57
|
-
expect(typeof entry.id).toBe("string");
|
|
58
|
-
expect(typeof entry.pos).toBe("string");
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
// Positions should be in order
|
|
62
|
-
expect(entries[0]!.pos < entries[1]!.pos).toBe(true);
|
|
63
|
-
expect(entries[1]!.pos < entries[2]!.pos).toBe(true);
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
it("push() generates array.insert operation with ID and position", () => {
|
|
67
|
-
const { env, operations } = createEnvWithState();
|
|
68
|
-
const arrayPrimitive = Primitive.Array(Primitive.String());
|
|
69
|
-
const proxy = arrayPrimitive._internal.createProxy(env, OperationPath.make(""));
|
|
70
|
-
|
|
71
|
-
proxy.push("newItem");
|
|
72
|
-
|
|
73
|
-
expect(operations).toHaveLength(1);
|
|
74
|
-
expect(operations[0]!.kind).toBe("array.insert");
|
|
75
|
-
|
|
76
|
-
const payload = operations[0]!.payload as { id: string; pos: string; value: string };
|
|
77
|
-
expect(payload.value).toBe("newItem");
|
|
78
|
-
expect(typeof payload.id).toBe("string");
|
|
79
|
-
expect(typeof payload.pos).toBe("string");
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
it("push() generates position after last element", () => {
|
|
83
|
-
const existingEntry = { id: "existing-id", pos: "a0", value: "existing" };
|
|
84
|
-
const { env, operations } = createEnvWithState([existingEntry]);
|
|
85
|
-
const arrayPrimitive = Primitive.Array(Primitive.String());
|
|
86
|
-
const proxy = arrayPrimitive._internal.createProxy(env, OperationPath.make(""));
|
|
87
|
-
|
|
88
|
-
proxy.push("newItem");
|
|
89
|
-
|
|
90
|
-
const payload = operations[0]!.payload as { id: string; pos: string; value: string };
|
|
91
|
-
expect(payload.pos > existingEntry.pos).toBe(true);
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
it("insertAt() generates array.insert with position between neighbors", () => {
|
|
95
|
-
const entries = [
|
|
96
|
-
{ id: "id1", pos: "a0", value: "first" },
|
|
97
|
-
{ id: "id2", pos: "a2", value: "third" },
|
|
98
|
-
];
|
|
99
|
-
const { env, operations } = createEnvWithState(entries);
|
|
100
|
-
const arrayPrimitive = Primitive.Array(Primitive.String());
|
|
101
|
-
const proxy = arrayPrimitive._internal.createProxy(env, OperationPath.make(""));
|
|
102
|
-
|
|
103
|
-
proxy.insertAt(1, "second");
|
|
104
|
-
|
|
105
|
-
expect(operations).toHaveLength(1);
|
|
106
|
-
expect(operations[0]!.kind).toBe("array.insert");
|
|
107
|
-
|
|
108
|
-
const payload = operations[0]!.payload as { id: string; pos: string; value: string };
|
|
109
|
-
expect(payload.value).toBe("second");
|
|
110
|
-
// Position should be between a0 and a2
|
|
111
|
-
expect(payload.pos > "a0").toBe(true);
|
|
112
|
-
expect(payload.pos < "a2").toBe(true);
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
it("insertAt(0) generates position before first element", () => {
|
|
116
|
-
const entries = [{ id: "id1", pos: "a0", value: "existing" }];
|
|
117
|
-
const { env, operations } = createEnvWithState(entries);
|
|
118
|
-
const arrayPrimitive = Primitive.Array(Primitive.String());
|
|
119
|
-
const proxy = arrayPrimitive._internal.createProxy(env, OperationPath.make(""));
|
|
120
|
-
|
|
121
|
-
proxy.insertAt(0, "first");
|
|
122
|
-
|
|
123
|
-
const payload = operations[0]!.payload as { id: string; pos: string; value: string };
|
|
124
|
-
expect(payload.pos < "a0").toBe(true);
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
it("remove() generates array.remove operation with ID", () => {
|
|
128
|
-
const { env, operations } = createEnvWithState();
|
|
129
|
-
const arrayPrimitive = Primitive.Array(Primitive.String());
|
|
130
|
-
const proxy = arrayPrimitive._internal.createProxy(env, OperationPath.make(""));
|
|
131
|
-
|
|
132
|
-
proxy.remove("some-id");
|
|
133
|
-
|
|
134
|
-
expect(operations).toHaveLength(1);
|
|
135
|
-
expect(operations[0]!.kind).toBe("array.remove");
|
|
136
|
-
expect(operations[0]!.payload).toEqual({ id: "some-id" });
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
it("move() generates array.move operation with new position", () => {
|
|
140
|
-
const entries = [
|
|
141
|
-
{ id: "id1", pos: "a0", value: "first" },
|
|
142
|
-
{ id: "id2", pos: "a1", value: "second" },
|
|
143
|
-
{ id: "id3", pos: "a2", value: "third" },
|
|
144
|
-
];
|
|
145
|
-
const { env, operations } = createEnvWithState(entries);
|
|
146
|
-
const arrayPrimitive = Primitive.Array(Primitive.String());
|
|
147
|
-
const proxy = arrayPrimitive._internal.createProxy(env, OperationPath.make(""));
|
|
148
|
-
|
|
149
|
-
// Move first element to end
|
|
150
|
-
proxy.move("id1", 3);
|
|
151
|
-
|
|
152
|
-
expect(operations).toHaveLength(1);
|
|
153
|
-
expect(operations[0]!.kind).toBe("array.move");
|
|
154
|
-
|
|
155
|
-
const payload = operations[0]!.payload as { id: string; pos: string };
|
|
156
|
-
expect(payload.id).toBe("id1");
|
|
157
|
-
// New position should be after a2
|
|
158
|
-
expect(payload.pos > "a2").toBe(true);
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
it("at() returns element proxy with ID in path", () => {
|
|
162
|
-
const { env, operations } = createEnvWithState();
|
|
163
|
-
const arrayPrimitive = Primitive.Array(Primitive.String());
|
|
164
|
-
const proxy = arrayPrimitive._internal.createProxy(env, OperationPath.make("items"));
|
|
165
|
-
|
|
166
|
-
const elementProxy = proxy.at("some-uuid");
|
|
167
|
-
elementProxy.set("element value");
|
|
168
|
-
|
|
169
|
-
expect(operations).toHaveLength(1);
|
|
170
|
-
expect(operations[0]!.kind).toBe("string.set");
|
|
171
|
-
expect(operations[0]!.path.toTokens()).toEqual(["items", "some-uuid"]);
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
it("at() with nested struct returns nested proxy", () => {
|
|
175
|
-
const { env, operations } = createEnvWithState();
|
|
176
|
-
const arrayPrimitive = Primitive.Array(
|
|
177
|
-
Primitive.Struct({
|
|
178
|
-
name: Primitive.String(),
|
|
179
|
-
age: Primitive.Number(),
|
|
180
|
-
})
|
|
181
|
-
);
|
|
182
|
-
const proxy = arrayPrimitive._internal.createProxy(env, OperationPath.make("users"));
|
|
183
|
-
|
|
184
|
-
proxy.at("user-id-123").name.set("John");
|
|
185
|
-
|
|
186
|
-
expect(operations).toHaveLength(1);
|
|
187
|
-
expect(operations[0]!.kind).toBe("string.set");
|
|
188
|
-
expect(operations[0]!.path.toTokens()).toEqual(["users", "user-id-123", "name"]);
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
it("find() returns proxy for matching element", () => {
|
|
192
|
-
const entries = [
|
|
193
|
-
{ id: "id1", pos: "a0", value: "alice" },
|
|
194
|
-
{ id: "id2", pos: "a1", value: "bob" },
|
|
195
|
-
];
|
|
196
|
-
const { env, operations } = createEnvWithState(entries);
|
|
197
|
-
const arrayPrimitive = Primitive.Array(Primitive.String());
|
|
198
|
-
const proxy = arrayPrimitive._internal.createProxy(env, OperationPath.make(""));
|
|
199
|
-
|
|
200
|
-
const found = proxy.find((value) => value === "bob");
|
|
201
|
-
expect(found).toBeDefined();
|
|
202
|
-
|
|
203
|
-
found!.set("robert");
|
|
204
|
-
|
|
205
|
-
expect(operations[0]!.path.toTokens()).toEqual(["id2"]);
|
|
206
|
-
});
|
|
207
|
-
});
|
|
208
|
-
|
|
209
|
-
describe("applyOperation", () => {
|
|
210
|
-
it("array.set replaces entire array with entries", () => {
|
|
211
|
-
const arrayPrimitive = Primitive.Array(Primitive.String());
|
|
212
|
-
const newEntries: Primitive.ArrayEntry<string>[] = [
|
|
213
|
-
{ id: "id1", pos: "a0", value: "x" },
|
|
214
|
-
{ id: "id2", pos: "a1", value: "y" },
|
|
215
|
-
];
|
|
216
|
-
const operation: Operation.Operation<any, any, any> = {
|
|
217
|
-
kind: "array.set",
|
|
218
|
-
path: OperationPath.make(""),
|
|
219
|
-
payload: newEntries,
|
|
220
|
-
};
|
|
221
|
-
|
|
222
|
-
const result = arrayPrimitive._internal.applyOperation([], operation);
|
|
223
|
-
expect(result).toEqual(newEntries);
|
|
224
|
-
});
|
|
225
|
-
|
|
226
|
-
it("array.insert adds new entry to array", () => {
|
|
227
|
-
const arrayPrimitive = Primitive.Array(Primitive.String());
|
|
228
|
-
const existingEntries: Primitive.ArrayEntry<string>[] = [
|
|
229
|
-
{ id: "id1", pos: "a0", value: "existing" },
|
|
230
|
-
];
|
|
231
|
-
const operation: Operation.Operation<any, any, any> = {
|
|
232
|
-
kind: "array.insert",
|
|
233
|
-
path: OperationPath.make(""),
|
|
234
|
-
payload: { id: "id2", pos: "a1", value: "new" },
|
|
235
|
-
};
|
|
236
|
-
|
|
237
|
-
const result = arrayPrimitive._internal.applyOperation(existingEntries, operation);
|
|
238
|
-
expect(result).toHaveLength(2);
|
|
239
|
-
expect(result[1]).toEqual({ id: "id2", pos: "a1", value: "new" });
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
it("array.insert works with undefined state", () => {
|
|
243
|
-
const arrayPrimitive = Primitive.Array(Primitive.String());
|
|
244
|
-
const operation: Operation.Operation<any, any, any> = {
|
|
245
|
-
kind: "array.insert",
|
|
246
|
-
path: OperationPath.make(""),
|
|
247
|
-
payload: { id: "id1", pos: "a0", value: "first" },
|
|
248
|
-
};
|
|
249
|
-
|
|
250
|
-
const result = arrayPrimitive._internal.applyOperation(undefined, operation);
|
|
251
|
-
expect(result).toHaveLength(1);
|
|
252
|
-
expect(result[0]).toEqual({ id: "id1", pos: "a0", value: "first" });
|
|
253
|
-
});
|
|
254
|
-
|
|
255
|
-
it("array.remove removes entry by ID", () => {
|
|
256
|
-
const arrayPrimitive = Primitive.Array(Primitive.String());
|
|
257
|
-
const entries: Primitive.ArrayEntry<string>[] = [
|
|
258
|
-
{ id: "id1", pos: "a0", value: "a" },
|
|
259
|
-
{ id: "id2", pos: "a1", value: "b" },
|
|
260
|
-
{ id: "id3", pos: "a2", value: "c" },
|
|
261
|
-
];
|
|
262
|
-
const operation: Operation.Operation<any, any, any> = {
|
|
263
|
-
kind: "array.remove",
|
|
264
|
-
path: OperationPath.make(""),
|
|
265
|
-
payload: { id: "id2" },
|
|
266
|
-
};
|
|
267
|
-
|
|
268
|
-
const result = arrayPrimitive._internal.applyOperation(entries, operation);
|
|
269
|
-
expect(result).toHaveLength(2);
|
|
270
|
-
expect(result.map(e => e.id)).toEqual(["id1", "id3"]);
|
|
271
|
-
});
|
|
272
|
-
|
|
273
|
-
it("array.move updates entry position", () => {
|
|
274
|
-
const arrayPrimitive = Primitive.Array(Primitive.String());
|
|
275
|
-
const entries: Primitive.ArrayEntry<string>[] = [
|
|
276
|
-
{ id: "id1", pos: "a0", value: "first" },
|
|
277
|
-
{ id: "id2", pos: "a1", value: "second" },
|
|
278
|
-
];
|
|
279
|
-
const operation: Operation.Operation<any, any, any> = {
|
|
280
|
-
kind: "array.move",
|
|
281
|
-
path: OperationPath.make(""),
|
|
282
|
-
payload: { id: "id1", pos: "a2" },
|
|
283
|
-
};
|
|
284
|
-
|
|
285
|
-
const result = arrayPrimitive._internal.applyOperation(entries, operation);
|
|
286
|
-
const movedEntry = result.find(e => e.id === "id1");
|
|
287
|
-
expect(movedEntry!.pos).toBe("a2");
|
|
288
|
-
expect(movedEntry!.value).toBe("first");
|
|
289
|
-
});
|
|
290
|
-
|
|
291
|
-
it("delegates element operations by ID", () => {
|
|
292
|
-
const arrayPrimitive = Primitive.Array(Primitive.String());
|
|
293
|
-
const entries: Primitive.ArrayEntry<string>[] = [
|
|
294
|
-
{ id: "id1", pos: "a0", value: "a" },
|
|
295
|
-
{ id: "id2", pos: "a1", value: "b" },
|
|
296
|
-
];
|
|
297
|
-
const operation: Operation.Operation<any, any, any> = {
|
|
298
|
-
kind: "string.set",
|
|
299
|
-
path: OperationPath.make("id2"),
|
|
300
|
-
payload: "updated",
|
|
301
|
-
};
|
|
302
|
-
|
|
303
|
-
const result = arrayPrimitive._internal.applyOperation(entries, operation);
|
|
304
|
-
expect(result[1]!.value).toBe("updated");
|
|
305
|
-
expect(result[1]!.id).toBe("id2");
|
|
306
|
-
expect(result[1]!.pos).toBe("a1");
|
|
307
|
-
});
|
|
308
|
-
|
|
309
|
-
it("delegates nested struct operations by ID", () => {
|
|
310
|
-
const arrayPrimitive = Primitive.Array(
|
|
311
|
-
Primitive.Struct({
|
|
312
|
-
name: Primitive.String(),
|
|
313
|
-
})
|
|
314
|
-
);
|
|
315
|
-
const entries: Primitive.ArrayEntry<{ name: string }>[] = [
|
|
316
|
-
{ id: "user-1", pos: "a0", value: { name: "Original" } },
|
|
317
|
-
];
|
|
318
|
-
const operation: Operation.Operation<any, any, any> = {
|
|
319
|
-
kind: "string.set",
|
|
320
|
-
path: OperationPath.make("user-1/name"),
|
|
321
|
-
payload: "Updated Name",
|
|
322
|
-
};
|
|
323
|
-
|
|
324
|
-
const result = arrayPrimitive._internal.applyOperation(entries, operation);
|
|
325
|
-
expect(result[0]!.value.name).toBe("Updated Name");
|
|
326
|
-
});
|
|
327
|
-
|
|
328
|
-
it("throws ValidationError for non-array payload on set", () => {
|
|
329
|
-
const arrayPrimitive = Primitive.Array(Primitive.String());
|
|
330
|
-
const operation: Operation.Operation<any, any, any> = {
|
|
331
|
-
kind: "array.set",
|
|
332
|
-
path: OperationPath.make(""),
|
|
333
|
-
payload: "not an array",
|
|
334
|
-
};
|
|
335
|
-
|
|
336
|
-
expect(() => arrayPrimitive._internal.applyOperation(undefined, operation)).toThrow(
|
|
337
|
-
Primitive.ValidationError
|
|
338
|
-
);
|
|
339
|
-
});
|
|
340
|
-
|
|
341
|
-
it("throws ValidationError for unknown element ID", () => {
|
|
342
|
-
const arrayPrimitive = Primitive.Array(Primitive.String());
|
|
343
|
-
const entries: Primitive.ArrayEntry<string>[] = [
|
|
344
|
-
{ id: "id1", pos: "a0", value: "a" },
|
|
345
|
-
];
|
|
346
|
-
const operation: Operation.Operation<any, any, any> = {
|
|
347
|
-
kind: "string.set",
|
|
348
|
-
path: OperationPath.make("unknown-id"),
|
|
349
|
-
payload: "value",
|
|
350
|
-
};
|
|
351
|
-
|
|
352
|
-
expect(() => arrayPrimitive._internal.applyOperation(entries, operation)).toThrow(
|
|
353
|
-
Primitive.ValidationError
|
|
354
|
-
);
|
|
355
|
-
});
|
|
356
|
-
});
|
|
357
|
-
|
|
358
|
-
describe("getInitialState", () => {
|
|
359
|
-
it("returns undefined when no default is set", () => {
|
|
360
|
-
const arrayPrimitive = Primitive.Array(Primitive.String());
|
|
361
|
-
expect(arrayPrimitive._internal.getInitialState()).toBeUndefined();
|
|
362
|
-
});
|
|
363
|
-
|
|
364
|
-
it("returns the default value when set", () => {
|
|
365
|
-
const defaultEntries: Primitive.ArrayEntry<string>[] = [
|
|
366
|
-
{ id: "id1", pos: "a0", value: "a" },
|
|
367
|
-
{ id: "id2", pos: "a1", value: "b" },
|
|
368
|
-
];
|
|
369
|
-
const arrayPrimitive = Primitive.Array(Primitive.String()).default(defaultEntries);
|
|
370
|
-
expect(arrayPrimitive._internal.getInitialState()).toEqual(defaultEntries);
|
|
371
|
-
});
|
|
372
|
-
});
|
|
373
|
-
|
|
374
|
-
describe("fractional index ordering", () => {
|
|
375
|
-
it("entries are sorted by position when accessed", () => {
|
|
376
|
-
const arrayPrimitive = Primitive.Array(Primitive.String());
|
|
377
|
-
// Entries stored out of order
|
|
378
|
-
const entries: Primitive.ArrayEntry<string>[] = [
|
|
379
|
-
{ id: "id3", pos: "a2", value: "third" },
|
|
380
|
-
{ id: "id1", pos: "a0", value: "first" },
|
|
381
|
-
{ id: "id2", pos: "a1", value: "second" },
|
|
382
|
-
];
|
|
383
|
-
|
|
384
|
-
// Apply a move operation - it should work regardless of storage order
|
|
385
|
-
const operation: Operation.Operation<any, any, any> = {
|
|
386
|
-
kind: "array.move",
|
|
387
|
-
path: OperationPath.make(""),
|
|
388
|
-
payload: { id: "id1", pos: "a3" },
|
|
389
|
-
};
|
|
390
|
-
|
|
391
|
-
const result = arrayPrimitive._internal.applyOperation(entries, operation);
|
|
392
|
-
const movedEntry = result.find(e => e.id === "id1");
|
|
393
|
-
expect(movedEntry!.pos).toBe("a3");
|
|
394
|
-
});
|
|
395
|
-
|
|
396
|
-
it("multiple inserts generate valid ordering", () => {
|
|
397
|
-
const { env, operations } = createEnvWithState();
|
|
398
|
-
const arrayPrimitive = Primitive.Array(Primitive.String());
|
|
399
|
-
const proxy = arrayPrimitive._internal.createProxy(env, OperationPath.make(""));
|
|
400
|
-
|
|
401
|
-
// Push multiple items
|
|
402
|
-
proxy.push("first");
|
|
403
|
-
proxy.push("second");
|
|
404
|
-
proxy.push("third");
|
|
405
|
-
|
|
406
|
-
expect(operations).toHaveLength(3);
|
|
407
|
-
|
|
408
|
-
const positions = operations.map(op => (op.payload as { pos: string }).pos);
|
|
409
|
-
// All positions should be different and in order
|
|
410
|
-
expect(positions[0]! < positions[1]!).toBe(true);
|
|
411
|
-
expect(positions[1]! < positions[2]!).toBe(true);
|
|
412
|
-
});
|
|
413
|
-
});
|
|
414
|
-
describe("struct elements with defaults", () => {
|
|
415
|
-
// Define a struct element with required and optional fields
|
|
416
|
-
const TaskStruct = Primitive.Struct({
|
|
417
|
-
title: Primitive.String().required(), // Must provide
|
|
418
|
-
priority: Primitive.Number().default(0), // Has default, optional
|
|
419
|
-
completed: Primitive.Boolean().default(false), // Has default, optional
|
|
420
|
-
});
|
|
421
|
-
|
|
422
|
-
const taskArray = Primitive.Array(TaskStruct);
|
|
423
|
-
|
|
424
|
-
// Helper to create environment for task array
|
|
425
|
-
const createTaskEnv = (
|
|
426
|
-
state: Primitive.ArrayEntry<{ title: string; priority: number; completed: boolean }>[] = []
|
|
427
|
-
): { env: ReturnType<typeof ProxyEnvironment.make>; operations: Operation.Operation<any, any, any>[] } => {
|
|
428
|
-
const operations: Operation.Operation<any, any, any>[] = [];
|
|
429
|
-
let currentState = [...state];
|
|
430
|
-
let idCounter = 0;
|
|
431
|
-
|
|
432
|
-
const env = ProxyEnvironment.make({
|
|
433
|
-
onOperation: (op) => {
|
|
434
|
-
operations.push(op);
|
|
435
|
-
if (op.kind === "array.insert") {
|
|
436
|
-
currentState.push(op.payload);
|
|
437
|
-
} else if (op.kind === "array.set") {
|
|
438
|
-
currentState = op.payload;
|
|
439
|
-
}
|
|
440
|
-
},
|
|
441
|
-
getState: () => currentState,
|
|
442
|
-
generateId: () => `task-${++idCounter}`,
|
|
443
|
-
});
|
|
444
|
-
|
|
445
|
-
return { env, operations };
|
|
446
|
-
};
|
|
447
|
-
|
|
448
|
-
it("push() only requires fields without defaults", () => {
|
|
449
|
-
const { env, operations } = createTaskEnv();
|
|
450
|
-
const proxy = taskArray._internal.createProxy(env, OperationPath.make(""));
|
|
451
|
-
|
|
452
|
-
// Only provide required 'title', priority and completed should use defaults
|
|
453
|
-
proxy.push({ title: "New Task" });
|
|
454
|
-
|
|
455
|
-
expect(operations).toHaveLength(1);
|
|
456
|
-
expect(operations[0]!.kind).toBe("array.insert");
|
|
457
|
-
|
|
458
|
-
const payload = operations[0]!.payload as { value: { title: string; priority: number; completed: boolean } };
|
|
459
|
-
expect(payload.value.title).toBe("New Task");
|
|
460
|
-
expect(payload.value.priority).toBe(0); // Default value
|
|
461
|
-
expect(payload.value.completed).toBe(false); // Default value
|
|
462
|
-
});
|
|
463
|
-
|
|
464
|
-
it("push() allows overriding defaults", () => {
|
|
465
|
-
const { env, operations } = createTaskEnv();
|
|
466
|
-
const proxy = taskArray._internal.createProxy(env, OperationPath.make(""));
|
|
467
|
-
|
|
468
|
-
// Provide title and override priority, let completed use default
|
|
469
|
-
proxy.push({ title: "Important Task", priority: 10 });
|
|
470
|
-
|
|
471
|
-
const payload = operations[0]!.payload as { value: { title: string; priority: number; completed: boolean } };
|
|
472
|
-
expect(payload.value.title).toBe("Important Task");
|
|
473
|
-
expect(payload.value.priority).toBe(10); // Overridden
|
|
474
|
-
expect(payload.value.completed).toBe(false); // Default value
|
|
475
|
-
});
|
|
476
|
-
|
|
477
|
-
it("insertAt() applies defaults for omitted fields", () => {
|
|
478
|
-
const existingEntry = {
|
|
479
|
-
id: "existing",
|
|
480
|
-
pos: "a0",
|
|
481
|
-
value: { title: "Existing", priority: 5, completed: true },
|
|
482
|
-
};
|
|
483
|
-
const { env, operations } = createTaskEnv([existingEntry]);
|
|
484
|
-
const proxy = taskArray._internal.createProxy(env, OperationPath.make(""));
|
|
485
|
-
|
|
486
|
-
// Insert with only required field
|
|
487
|
-
proxy.insertAt(0, { title: "First Task" });
|
|
488
|
-
|
|
489
|
-
const payload = operations[0]!.payload as { value: { title: string; priority: number; completed: boolean } };
|
|
490
|
-
expect(payload.value.title).toBe("First Task");
|
|
491
|
-
expect(payload.value.priority).toBe(0); // Default
|
|
492
|
-
expect(payload.value.completed).toBe(false); // Default
|
|
493
|
-
});
|
|
494
|
-
|
|
495
|
-
it("set() applies defaults to each element", () => {
|
|
496
|
-
const { env, operations } = createTaskEnv();
|
|
497
|
-
const proxy = taskArray._internal.createProxy(env, OperationPath.make(""));
|
|
498
|
-
|
|
499
|
-
// Set array with items that only have required fields
|
|
500
|
-
proxy.set([
|
|
501
|
-
{ title: "Task 1" },
|
|
502
|
-
{ title: "Task 2", priority: 5 },
|
|
503
|
-
{ title: "Task 3", completed: true },
|
|
504
|
-
]);
|
|
505
|
-
|
|
506
|
-
expect(operations).toHaveLength(1);
|
|
507
|
-
expect(operations[0]!.kind).toBe("array.set");
|
|
508
|
-
|
|
509
|
-
const entries = operations[0]!.payload as { value: { title: string; priority: number; completed: boolean } }[];
|
|
510
|
-
expect(entries).toHaveLength(3);
|
|
511
|
-
|
|
512
|
-
// First item: only title, defaults for others
|
|
513
|
-
expect(entries[0]!.value).toEqual({ title: "Task 1", priority: 0, completed: false });
|
|
514
|
-
|
|
515
|
-
// Second item: title and priority, default for completed
|
|
516
|
-
expect(entries[1]!.value).toEqual({ title: "Task 2", priority: 5, completed: false });
|
|
517
|
-
|
|
518
|
-
// Third item: title and completed, default for priority
|
|
519
|
-
expect(entries[2]!.value).toEqual({ title: "Task 3", priority: 0, completed: true });
|
|
520
|
-
});
|
|
521
|
-
});
|
|
522
|
-
});
|
|
523
|
-
|
|
524
|
-
// =============================================================================
|
|
525
|
-
// Lazy Primitive Tests
|
|
526
|
-
// =============================================================================
|
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "@effect/vitest";
|
|
2
|
-
import * as Primitive from "../../src/Primitive";
|
|
3
|
-
import * as ProxyEnvironment from "../../src/ProxyEnvironment";
|
|
4
|
-
import * as OperationPath from "../../src/OperationPath";
|
|
5
|
-
import * as Operation from "../../src/Operation";
|
|
6
|
-
|
|
7
|
-
describe("BooleanPrimitive", () => {
|
|
8
|
-
describe("proxy", () => {
|
|
9
|
-
it("set() generates correct operation with path and payload", () => {
|
|
10
|
-
const operations: Operation.Operation<any, any, any>[] = [];
|
|
11
|
-
const env = ProxyEnvironment.make((op) => operations.push(op));
|
|
12
|
-
|
|
13
|
-
const booleanPrimitive = Primitive.Boolean().required();
|
|
14
|
-
const proxy = booleanPrimitive._internal.createProxy(env, OperationPath.make(""));
|
|
15
|
-
|
|
16
|
-
proxy.set(true);
|
|
17
|
-
|
|
18
|
-
expect(operations).toHaveLength(1);
|
|
19
|
-
expect(operations[0]!.kind).toBe("boolean.set");
|
|
20
|
-
expect(operations[0]!.payload).toBe(true);
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
it("set() includes the correct path in operation", () => {
|
|
24
|
-
const operations: Operation.Operation<any, any, any>[] = [];
|
|
25
|
-
const env = ProxyEnvironment.make((op) => operations.push(op));
|
|
26
|
-
|
|
27
|
-
const booleanPrimitive = Primitive.Boolean();
|
|
28
|
-
const proxy = booleanPrimitive._internal.createProxy(env, OperationPath.make("visible"));
|
|
29
|
-
|
|
30
|
-
proxy.set(false);
|
|
31
|
-
|
|
32
|
-
expect(operations[0]!.path.toTokens()).toEqual(["visible"]);
|
|
33
|
-
});
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
describe("applyOperation", () => {
|
|
37
|
-
it("returns the new boolean value from boolean.set operation", () => {
|
|
38
|
-
const booleanPrimitive = Primitive.Boolean();
|
|
39
|
-
const operation: Operation.Operation<any, any, any> = {
|
|
40
|
-
kind: "boolean.set",
|
|
41
|
-
path: OperationPath.make(""),
|
|
42
|
-
payload: true,
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
const result = booleanPrimitive._internal.applyOperation(undefined, operation);
|
|
46
|
-
expect(result).toBe(true);
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
it("replaces existing state with new value", () => {
|
|
50
|
-
const booleanPrimitive = Primitive.Boolean();
|
|
51
|
-
const operation: Operation.Operation<any, any, any> = {
|
|
52
|
-
kind: "boolean.set",
|
|
53
|
-
path: OperationPath.make(""),
|
|
54
|
-
payload: false,
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
const result = booleanPrimitive._internal.applyOperation(true, operation);
|
|
58
|
-
expect(result).toBe(false);
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
it("throws ValidationError for non-boolean payload", () => {
|
|
62
|
-
const booleanPrimitive = Primitive.Boolean();
|
|
63
|
-
const operation: Operation.Operation<any, any, any> = {
|
|
64
|
-
kind: "boolean.set",
|
|
65
|
-
path: OperationPath.make(""),
|
|
66
|
-
payload: "true",
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
expect(() => booleanPrimitive._internal.applyOperation(undefined, operation)).toThrow(
|
|
70
|
-
Primitive.ValidationError
|
|
71
|
-
);
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
it("throws ValidationError for wrong operation kind", () => {
|
|
75
|
-
const booleanPrimitive = Primitive.Boolean();
|
|
76
|
-
const operation: Operation.Operation<any, any, any> = {
|
|
77
|
-
kind: "string.set",
|
|
78
|
-
path: OperationPath.make(""),
|
|
79
|
-
payload: true,
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
expect(() => booleanPrimitive._internal.applyOperation(undefined, operation)).toThrow(
|
|
83
|
-
Primitive.ValidationError
|
|
84
|
-
);
|
|
85
|
-
});
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
describe("getInitialState", () => {
|
|
89
|
-
it("returns undefined when no default is set", () => {
|
|
90
|
-
const booleanPrimitive = Primitive.Boolean();
|
|
91
|
-
expect(booleanPrimitive._internal.getInitialState()).toBeUndefined();
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
it("returns the default value when set", () => {
|
|
95
|
-
const booleanPrimitive = Primitive.Boolean().default(true);
|
|
96
|
-
expect(booleanPrimitive._internal.getInitialState()).toBe(true);
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
it("returns false as default when explicitly set", () => {
|
|
100
|
-
const booleanPrimitive = Primitive.Boolean().default(false);
|
|
101
|
-
expect(booleanPrimitive._internal.getInitialState()).toBe(false);
|
|
102
|
-
});
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
describe("schema modifiers", () => {
|
|
106
|
-
it("required() returns a new BooleanPrimitive", () => {
|
|
107
|
-
const original = Primitive.Boolean();
|
|
108
|
-
const required = original.required();
|
|
109
|
-
|
|
110
|
-
expect(required).toBeInstanceOf(Primitive.BooleanPrimitive);
|
|
111
|
-
expect(required).not.toBe(original);
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
it("default() returns a new BooleanPrimitive with default value", () => {
|
|
115
|
-
const original = Primitive.Boolean();
|
|
116
|
-
const withDefault = original.default(true);
|
|
117
|
-
|
|
118
|
-
expect(withDefault).toBeInstanceOf(Primitive.BooleanPrimitive);
|
|
119
|
-
expect(withDefault._internal.getInitialState()).toBe(true);
|
|
120
|
-
});
|
|
121
|
-
});
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
// =============================================================================
|
|
125
|
-
// Number Primitive Tests
|
|
126
|
-
// =============================================================================
|