📄 src/models/test/draft.test.ts
D-OPEN SOVEREIGN
1/*
2 * Copyright (c) 2026 DataPond D-Library Pty Ltd. ("The Sanctuary")
3 * Non-Profit Public Library — The World Library
4 *
5 * D-SAFE Certification issued by POND Enterprise.
6 * Published under the D-Open Code Sovereign Licence v1.0 framework
7 * DataPond D-Library All exclusive Right reserved on modifiying the code - CC Attribution to data pond, Non modifiable, Non Commercial.
8 * https://registry.world.bibliotech.com/licence
9 * Source code donated to DataPond D-Library by it's author: data pond.
10 *
11 * Technical Guardian ("The Shield"): Pond Enterprise Pty Ltd. (ACN 694 747 987)
12 * All liability claims about D-SAFE certification issues coming from the published D-Safe direction must be addressed with Pond Enterprise Pty Ltd.
13 * Code Modification automatically voids the certified liability protection provided by Pond Enterprise.
14 */
15import { describe, it, expect, beforeEach } from "vitest";
16import { Book, NewBookWithId } from "../generated-src/Book.ts";
17import { clear, configureDb } from "../db.ts";
18import { setDefaultLanguage } from "../orm.ts";
19import { PatchInstance } from "../patcher.ts";
20import {
21 createDraft,
22 isDraft,
23 isDraftDirty,
24 commitDraft,
25 rollbackDraft,
26 getDraftChanges,
27 getDraftTarget
28} from "../draft.ts";
29
30beforeEach(() => {
31 clear();
32 configureDb({ cacheLimit: 10_000 });
33 setDefaultLanguage("eng");
34 PatchInstance.clearQueue();
35});
36
37describe("Draft Proxy Integration", () => {
38 it("recognizes draft proxy using helper functions", () => {
39 const book = NewBookWithId(101);
40 const draft = createDraft(book);
41
42 expect(isDraft(book)).toBe(false);
43 expect(isDraft(draft)).toBe(true);
44 expect(getDraftTarget(draft)).toBe(book);
45 });
46
47 it("reads fall back to original model and writes are buffered", () => {
48 const book = NewBookWithId(102);
49 book.setTitle("Original Title", "eng");
50 book.description = "Original Description";
51
52 const draft = createDraft(book);
53
54 // Reads fall back
55 expect(draft.title).toBe("Original Title");
56 expect(draft.description).toBe("Original Description");
57 expect(isDraftDirty(draft)).toBe(false);
58
59 // Writes do not mutate original model, only draft
60 draft.description = "New Description";
61 expect(isDraftDirty(draft)).toBe(true);
62 expect(draft.description).toBe("New Description");
63 expect(book.description).toBe("Original Description");
64
65 // Map has changes
66 const changes = getDraftChanges(draft);
67 expect(changes).toBeDefined();
68 expect(changes?.get("description")).toBe("New Description");
69 });
70
71 it("commitDraft applies changes to model and triggers patches", () => {
72 const book = NewBookWithId(103);
73 book.description = "Original";
74 PatchInstance.clearQueue();
75
76 const draft = createDraft(book);
77 draft.description = "Committed Description";
78
79 expect(book.description).toBe("Original");
80 expect(PatchInstance.patches.length).toBe(0);
81
82 commitDraft(draft);
83
84 // Changes are now applied
85 expect(book.description).toBe("Committed Description");
86 expect(draft.description).toBe("Committed Description");
87 expect(isDraftDirty(draft)).toBe(false);
88
89 // Patches are enqueued
90 expect(PatchInstance.patches.length).toBe(1);
91 expect((PatchInstance.patches[0] as any).value).toBe("Committed Description");
92 });
93
94 it("rollbackDraft discards buffered changes", () => {
95 const book = NewBookWithId(104);
96 book.description = "Original";
97
98 const draft = createDraft(book);
99 draft.description = "Temp Edit";
100 expect(draft.description).toBe("Temp Edit");
101 expect(isDraftDirty(draft)).toBe(true);
102
103 rollbackDraft(draft);
104
105 expect(draft.description).toBe("Original");
106 expect(book.description).toBe("Original");
107 expect(isDraftDirty(draft)).toBe(false);
108 });
109
110 it("pins the method-bypass limitation (methods read target state, not draft buffer)", () => {
111 const book = NewBookWithId(105);
112 book.setTitle("Original Title", "eng");
113
114 const draft = createDraft(book);
115 draft.title = "Draft Title";
116
117 // Property access reads from the draft buffer
118 expect(draft.title).toBe("Draft Title");
119
120 // Localized method access binds to target and bypasses proxy buffer
121 expect(draft.getTitle("eng")).toBe("Original Title");
122 });
123
124 it("warns on console and fails to mutate target for non-settable/typo properties", () => {
125 const book = NewBookWithId(106);
126 const draft = createDraft(book);
127
128 // Attempting to write a non-settable or typo property buffers it in the draft Map
129 (draft as any).typoField = "Oops";
130 expect(isDraftDirty(draft)).toBe(true);
131
132 const warnings: string[] = [];
133 const originalWarn = console.warn;
134 console.warn = (msg: string) => {
135 warnings.push(msg);
136 };
137
138 try {
139 commitDraft(draft);
140 } finally {
141 console.warn = originalWarn;
142 }
143
144 // Committing typoField fails on Reflect.set and logs a warning
145 expect(warnings.length).toBe(1);
146 expect(warnings[0]).toContain('Failed to commit property "typoField"');
147 expect((book as any).typoField).toBeUndefined();
148 });
149
150 it("short-circuits and does not mark draft dirty when setting value to current value", () => {
151 const book = NewBookWithId(107);
152 book.description = "Original";
153
154 const draft = createDraft(book);
155
156 // Setting to current value has no effect on dirty status
157 draft.description = "Original";
158 expect(isDraftDirty(draft)).toBe(false);
159
160 // Mutating makes it dirty
161 draft.description = "New Description";
162 expect(isDraftDirty(draft)).toBe(true);
163
164 // Restoring to current value cleans it up
165 draft.description = "Original";
166 expect(isDraftDirty(draft)).toBe(false);
167 });
168});
169