xdbc 1.0.217 → 1.0.218
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/.gitattributes +8 -0
- package/.vscode/settings.json +3 -3
- package/.vscode/tasks.json +23 -23
- package/ASSESSMENT.md +249 -0
- package/README.md +131 -1
- package/__tests__/DBC/AE.test.ts +62 -62
- package/__tests__/DBC/ARRAY.test.ts +91 -91
- package/__tests__/DBC/DEFINED.test.ts +53 -53
- package/__tests__/DBC/DOM.test.ts +481 -0
- package/__tests__/DBC/Decorators.test.ts +367 -367
- package/__tests__/DBC/EQ.test.ts +13 -13
- package/__tests__/DBC/GREATER.test.ts +31 -31
- package/__tests__/DBC/HasAttribute.test.ts +60 -60
- package/__tests__/DBC/IF.test.ts +62 -62
- package/__tests__/DBC/INSTANCE.test.ts +13 -13
- package/__tests__/DBC/JSON.OP.test.ts +47 -47
- package/__tests__/DBC/JSON.Parse.test.ts +17 -17
- package/__tests__/DBC/OR.test.ts +14 -14
- package/__tests__/DBC/PLAIN_OBJECT.test.ts +109 -109
- package/__tests__/DBC/REGEX.test.ts +17 -17
- package/__tests__/DBC/TYPE.test.ts +13 -13
- package/__tests__/DBC/UNDEFINED.test.ts +45 -45
- package/__tests__/DBC/ZOD.test.ts +54 -54
- package/__tests__/DBC/onInfringement.test.ts +262 -0
- package/biome.json +40 -40
- package/dist/DBC/AE.js +172 -0
- package/dist/DBC/ARR/PLAIN_OBJECT.d.ts +0 -3
- package/dist/DBC/ARR/PLAIN_OBJECT.js +95 -0
- package/dist/DBC/ARRAY.d.ts +0 -3
- package/dist/DBC/ARRAY.js +90 -0
- package/dist/DBC/COMPARISON/GREATER.js +21 -0
- package/dist/DBC/COMPARISON/GREATER_OR_EQUAL.js +21 -0
- package/dist/DBC/COMPARISON/LESS.js +21 -0
- package/dist/DBC/COMPARISON/LESS_OR_EQUAL.js +21 -0
- package/dist/DBC/COMPARISON.js +98 -0
- package/dist/DBC/DEFINED.js +87 -0
- package/dist/DBC/DOM.d.ts +87 -0
- package/dist/DBC/DOM.js +223 -0
- package/dist/DBC/EQ/DIFFERENT.js +34 -0
- package/dist/DBC/EQ.js +101 -0
- package/dist/DBC/HasAttribute.js +101 -0
- package/dist/DBC/IF.js +96 -0
- package/dist/DBC/INSTANCE.js +122 -0
- package/dist/DBC/JSON.OP.js +120 -0
- package/dist/DBC/JSON.Parse.js +104 -0
- package/dist/DBC/OR.js +125 -0
- package/dist/DBC/REGEX.js +136 -0
- package/dist/DBC/TYPE.js +112 -0
- package/dist/DBC/UNDEFINED.js +87 -0
- package/dist/DBC/ZOD.js +99 -0
- package/dist/DBC.d.ts +18 -4
- package/dist/DBC.js +645 -0
- package/dist/Demo.d.ts +10 -0
- package/dist/Demo.js +713 -0
- package/dist/bundle.js +6140 -405
- package/dist/index.d.ts +22 -0
- package/dist/index.js +22 -0
- package/jest.config.js +32 -32
- package/package.json +71 -55
- package/src/DBC/AE.ts +269 -288
- package/src/DBC/ARR/PLAIN_OBJECT.ts +122 -133
- package/src/DBC/ARRAY.ts +117 -127
- package/src/DBC/COMPARISON/GREATER.ts +41 -46
- package/src/DBC/COMPARISON/GREATER_OR_EQUAL.ts +41 -45
- package/src/DBC/COMPARISON/LESS.ts +41 -45
- package/src/DBC/COMPARISON/LESS_OR_EQUAL.ts +41 -45
- package/src/DBC/COMPARISON.ts +149 -159
- package/src/DBC/DEFINED.ts +117 -122
- package/src/DBC/DOM.ts +291 -0
- package/src/DBC/EQ/DIFFERENT.ts +51 -57
- package/src/DBC/EQ.ts +154 -163
- package/src/DBC/HasAttribute.ts +149 -154
- package/src/DBC/IF.ts +173 -179
- package/src/DBC/INSTANCE.ts +168 -171
- package/src/DBC/JSON.OP.ts +178 -186
- package/src/DBC/JSON.Parse.ts +150 -157
- package/src/DBC/OR.ts +183 -187
- package/src/DBC/REGEX.ts +195 -196
- package/src/DBC/TYPE.ts +142 -149
- package/src/DBC/UNDEFINED.ts +115 -117
- package/src/DBC/ZOD.ts +130 -135
- package/src/DBC.ts +902 -904
- package/src/Demo.ts +537 -404
- package/src/index.ts +22 -0
- package/tsconfig.json +18 -18
- package/tsconfig.test.json +7 -7
- package/typedoc.json +16 -16
- package/webpack.config.js +27 -27
- package/Assessment.md +0 -507
|
@@ -0,0 +1,481 @@
|
|
|
1
|
+
import { DBC } from "../../src/DBC";
|
|
2
|
+
import { registerDOMContract, scanDOM } from "../../src/DBC/DOM";
|
|
3
|
+
|
|
4
|
+
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
5
|
+
|
|
6
|
+
/** Creates an <input> with the given attributes, appends it to document.body, and returns it. */
|
|
7
|
+
function makeInput(attrs: Record<string, string>): HTMLInputElement {
|
|
8
|
+
const el = document.createElement("input");
|
|
9
|
+
el.type = "text";
|
|
10
|
+
for (const [k, v] of Object.entries(attrs)) {
|
|
11
|
+
el.setAttribute(k, v);
|
|
12
|
+
}
|
|
13
|
+
document.body.appendChild(el);
|
|
14
|
+
return el;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Fires an "input" event on the element (simulates any user input change). */
|
|
18
|
+
function fireInput(
|
|
19
|
+
el: HTMLInputElement | HTMLTextAreaElement,
|
|
20
|
+
newValue: string,
|
|
21
|
+
): void {
|
|
22
|
+
el.value = newValue;
|
|
23
|
+
el.dispatchEvent(new Event("input"));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Fires compositionstart → sets value → compositionend → input (simulates IME). */
|
|
27
|
+
function fireIME(el: HTMLInputElement, composed: string): void {
|
|
28
|
+
el.dispatchEvent(new Event("compositionstart"));
|
|
29
|
+
el.value = composed;
|
|
30
|
+
el.dispatchEvent(new Event("compositionend"));
|
|
31
|
+
el.dispatchEvent(new Event("input"));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
beforeEach(() => {
|
|
35
|
+
// Clean up any elements added during a test.
|
|
36
|
+
document.body.innerHTML = "";
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// ─── scanDOM — basic binding ───────────────────────────────────────────────────
|
|
40
|
+
|
|
41
|
+
describe("scanDOM — basic binding", () => {
|
|
42
|
+
test("returns a cleanup function", () => {
|
|
43
|
+
const cleanup = scanDOM();
|
|
44
|
+
expect(typeof cleanup).toBe("function");
|
|
45
|
+
cleanup();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test("ignores elements without data-xdbc", () => {
|
|
49
|
+
const el = makeInput({ "data-xdbc-regex": "^\\d*$" }); // no data-xdbc marker
|
|
50
|
+
const cleanup = scanDOM();
|
|
51
|
+
fireInput(el, "abc");
|
|
52
|
+
expect(el.value).toBe("abc"); // not blocked
|
|
53
|
+
cleanup();
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test("ignores data-xdbc elements that have no recognised contract attribute", () => {
|
|
57
|
+
const el = makeInput({ "data-xdbc": "" }); // marker only
|
|
58
|
+
const cleanup = scanDOM();
|
|
59
|
+
fireInput(el, "anything");
|
|
60
|
+
expect(el.value).toBe("anything");
|
|
61
|
+
cleanup();
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test("cleanup removes all listeners — further input is no longer validated", () => {
|
|
65
|
+
const el = makeInput({ "data-xdbc": "", "data-xdbc-regex": "^\\d*$" });
|
|
66
|
+
const cleanup = scanDOM();
|
|
67
|
+
cleanup();
|
|
68
|
+
fireInput(el, "abc"); // would be blocked before cleanup
|
|
69
|
+
expect(el.value).toBe("abc");
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test("can scan a sub-element instead of the whole document", () => {
|
|
73
|
+
const container = document.createElement("div");
|
|
74
|
+
document.body.appendChild(container);
|
|
75
|
+
const inside = document.createElement("input");
|
|
76
|
+
inside.setAttribute("data-xdbc", "");
|
|
77
|
+
inside.setAttribute("data-xdbc-regex", "^\\d*$");
|
|
78
|
+
container.appendChild(inside);
|
|
79
|
+
const outside = makeInput({ "data-xdbc": "", "data-xdbc-regex": "^\\d*$" });
|
|
80
|
+
|
|
81
|
+
const cleanup = scanDOM(container);
|
|
82
|
+
fireInput(inside, "abc");
|
|
83
|
+
expect(inside.value).toBe(""); // blocked
|
|
84
|
+
fireInput(outside, "abc");
|
|
85
|
+
expect(outside.value).toBe("abc"); // NOT scanned
|
|
86
|
+
cleanup();
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// ─── data-xdbc-regex ──────────────────────────────────────────────────────────
|
|
91
|
+
|
|
92
|
+
describe("data-xdbc-regex", () => {
|
|
93
|
+
test("accepts a valid value", () => {
|
|
94
|
+
const el = makeInput({ "data-xdbc": "", "data-xdbc-regex": "^\\d*$" });
|
|
95
|
+
const cleanup = scanDOM();
|
|
96
|
+
fireInput(el, "123");
|
|
97
|
+
expect(el.value).toBe("123");
|
|
98
|
+
cleanup();
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test("reverts an invalid value to the last valid state", () => {
|
|
102
|
+
const el = makeInput({ "data-xdbc": "", "data-xdbc-regex": "^\\d*$" });
|
|
103
|
+
// el.value is "" when scanDOM() runs, so lastValid starts as ""
|
|
104
|
+
const cleanup = scanDOM();
|
|
105
|
+
fireInput(el, "1a");
|
|
106
|
+
expect(el.value).toBe(""); // reverted to lastValid = ""
|
|
107
|
+
cleanup();
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test("preserves the last valid value on revert", () => {
|
|
111
|
+
const el = makeInput({ "data-xdbc": "", "data-xdbc-regex": "^\\d*$" });
|
|
112
|
+
const cleanup = scanDOM();
|
|
113
|
+
fireInput(el, "42");
|
|
114
|
+
fireInput(el, "42x");
|
|
115
|
+
expect(el.value).toBe("42");
|
|
116
|
+
cleanup();
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
test("warns and skips element with invalid RegExp pattern", () => {
|
|
120
|
+
const warn = jest.spyOn(console, "warn").mockImplementation(() => {});
|
|
121
|
+
const el = makeInput({ "data-xdbc": "", "data-xdbc-regex": "[invalid" });
|
|
122
|
+
const cleanup = scanDOM();
|
|
123
|
+
// Element still bound; invalid check returns error string, so value is reverted
|
|
124
|
+
fireInput(el, "anything");
|
|
125
|
+
expect(el.value).toBe(""); // reverted
|
|
126
|
+
warn.mockRestore();
|
|
127
|
+
cleanup();
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
// ─── data-xdbc-type ───────────────────────────────────────────────────────────
|
|
132
|
+
|
|
133
|
+
describe("data-xdbc-type", () => {
|
|
134
|
+
test("accepts empty string (TYPE skips null/undefined/empty)", () => {
|
|
135
|
+
const el = makeInput({ "data-xdbc": "", "data-xdbc-type": "number" });
|
|
136
|
+
const cleanup = scanDOM();
|
|
137
|
+
fireInput(el, "");
|
|
138
|
+
expect(el.value).toBe("");
|
|
139
|
+
cleanup();
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
test("accepts a value matching the type", () => {
|
|
143
|
+
// All input values are strings — TYPE 'string' always passes for non-empty input
|
|
144
|
+
const el = makeInput({ "data-xdbc": "", "data-xdbc-type": "string" });
|
|
145
|
+
const cleanup = scanDOM();
|
|
146
|
+
fireInput(el, "hello");
|
|
147
|
+
expect(el.value).toBe("hello");
|
|
148
|
+
cleanup();
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// ─── data-xdbc-eq ─────────────────────────────────────────────────────────────
|
|
153
|
+
|
|
154
|
+
describe("data-xdbc-eq", () => {
|
|
155
|
+
test("accepts the exact configured value", () => {
|
|
156
|
+
const el = makeInput({ "data-xdbc": "", "data-xdbc-eq": "secret" });
|
|
157
|
+
const cleanup = scanDOM();
|
|
158
|
+
fireInput(el, "secret");
|
|
159
|
+
expect(el.value).toBe("secret");
|
|
160
|
+
cleanup();
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
test("reverts when value does not match", () => {
|
|
164
|
+
const el = makeInput({ "data-xdbc": "", "data-xdbc-eq": "secret" });
|
|
165
|
+
const cleanup = scanDOM();
|
|
166
|
+
fireInput(el, "other");
|
|
167
|
+
expect(el.value).toBe("");
|
|
168
|
+
cleanup();
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
// ─── data-xdbc-different ──────────────────────────────────────────────────────
|
|
173
|
+
|
|
174
|
+
describe("data-xdbc-different", () => {
|
|
175
|
+
test("accepts a value that differs from the configured value", () => {
|
|
176
|
+
const el = makeInput({
|
|
177
|
+
"data-xdbc": "",
|
|
178
|
+
"data-xdbc-different": "forbidden",
|
|
179
|
+
});
|
|
180
|
+
const cleanup = scanDOM();
|
|
181
|
+
fireInput(el, "allowed");
|
|
182
|
+
expect(el.value).toBe("allowed");
|
|
183
|
+
cleanup();
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
test("reverts the forbidden value", () => {
|
|
187
|
+
const el = makeInput({
|
|
188
|
+
"data-xdbc": "",
|
|
189
|
+
"data-xdbc-different": "forbidden",
|
|
190
|
+
});
|
|
191
|
+
const cleanup = scanDOM();
|
|
192
|
+
fireInput(el, "forbidden");
|
|
193
|
+
expect(el.value).toBe("");
|
|
194
|
+
cleanup();
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
// ─── data-xdbc-defined ────────────────────────────────────────────────────────
|
|
199
|
+
|
|
200
|
+
describe("data-xdbc-defined", () => {
|
|
201
|
+
test("accepts a non-empty string", () => {
|
|
202
|
+
const el = makeInput({ "data-xdbc": "", "data-xdbc-defined": "" });
|
|
203
|
+
const cleanup = scanDOM();
|
|
204
|
+
fireInput(el, "hello");
|
|
205
|
+
expect(el.value).toBe("hello");
|
|
206
|
+
cleanup();
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
// ─── data-xdbc-greater / less / or-equal variants ────────────────────────────
|
|
211
|
+
|
|
212
|
+
describe("data-xdbc-greater", () => {
|
|
213
|
+
test("accepts a value greater than the threshold", () => {
|
|
214
|
+
const el = makeInput({ "data-xdbc": "", "data-xdbc-greater": "5" });
|
|
215
|
+
const cleanup = scanDOM();
|
|
216
|
+
fireInput(el, "10");
|
|
217
|
+
expect(el.value).toBe("10");
|
|
218
|
+
cleanup();
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
test("reverts a value equal to the threshold (strict greater)", () => {
|
|
222
|
+
const el = makeInput({ "data-xdbc": "", "data-xdbc-greater": "5" });
|
|
223
|
+
const cleanup = scanDOM();
|
|
224
|
+
fireInput(el, "5");
|
|
225
|
+
expect(el.value).toBe("");
|
|
226
|
+
cleanup();
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
test("reverts a value less than the threshold", () => {
|
|
230
|
+
const el = makeInput({ "data-xdbc": "", "data-xdbc-greater": "5" });
|
|
231
|
+
const cleanup = scanDOM();
|
|
232
|
+
fireInput(el, "3");
|
|
233
|
+
expect(el.value).toBe("");
|
|
234
|
+
cleanup();
|
|
235
|
+
});
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
describe("data-xdbc-greater-or-equal", () => {
|
|
239
|
+
test("accepts a value equal to the threshold", () => {
|
|
240
|
+
const el = makeInput({
|
|
241
|
+
"data-xdbc": "",
|
|
242
|
+
"data-xdbc-greater-or-equal": "5",
|
|
243
|
+
});
|
|
244
|
+
const cleanup = scanDOM();
|
|
245
|
+
fireInput(el, "5");
|
|
246
|
+
expect(el.value).toBe("5");
|
|
247
|
+
cleanup();
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
test("reverts a value below the threshold", () => {
|
|
251
|
+
const el = makeInput({
|
|
252
|
+
"data-xdbc": "",
|
|
253
|
+
"data-xdbc-greater-or-equal": "5",
|
|
254
|
+
});
|
|
255
|
+
const cleanup = scanDOM();
|
|
256
|
+
fireInput(el, "4");
|
|
257
|
+
expect(el.value).toBe("");
|
|
258
|
+
cleanup();
|
|
259
|
+
});
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
describe("data-xdbc-less", () => {
|
|
263
|
+
test("accepts a value less than the threshold", () => {
|
|
264
|
+
const el = makeInput({ "data-xdbc": "", "data-xdbc-less": "10" });
|
|
265
|
+
const cleanup = scanDOM();
|
|
266
|
+
fireInput(el, "3");
|
|
267
|
+
expect(el.value).toBe("3");
|
|
268
|
+
cleanup();
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
test("reverts a value equal to the threshold (strict less)", () => {
|
|
272
|
+
const el = makeInput({ "data-xdbc": "", "data-xdbc-less": "10" });
|
|
273
|
+
const cleanup = scanDOM();
|
|
274
|
+
fireInput(el, "10");
|
|
275
|
+
expect(el.value).toBe("");
|
|
276
|
+
cleanup();
|
|
277
|
+
});
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
describe("data-xdbc-less-or-equal", () => {
|
|
281
|
+
test("accepts a value equal to the threshold", () => {
|
|
282
|
+
const el = makeInput({ "data-xdbc": "", "data-xdbc-less-or-equal": "10" });
|
|
283
|
+
const cleanup = scanDOM();
|
|
284
|
+
fireInput(el, "10");
|
|
285
|
+
expect(el.value).toBe("10");
|
|
286
|
+
cleanup();
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
test("reverts a value above the threshold", () => {
|
|
290
|
+
const el = makeInput({ "data-xdbc": "", "data-xdbc-less-or-equal": "10" });
|
|
291
|
+
const cleanup = scanDOM();
|
|
292
|
+
fireInput(el, "11");
|
|
293
|
+
expect(el.value).toBe("");
|
|
294
|
+
cleanup();
|
|
295
|
+
});
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
// ─── data-xdbc-or ─────────────────────────────────────────────────────────────
|
|
299
|
+
|
|
300
|
+
describe("data-xdbc-or", () => {
|
|
301
|
+
test("passes when the first fragment matches", () => {
|
|
302
|
+
const el = makeInput({
|
|
303
|
+
"data-xdbc": "",
|
|
304
|
+
"data-xdbc-or": "regex:^\\d+$;;eq:N/A",
|
|
305
|
+
});
|
|
306
|
+
const cleanup = scanDOM();
|
|
307
|
+
fireInput(el, "123");
|
|
308
|
+
expect(el.value).toBe("123");
|
|
309
|
+
cleanup();
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
test("passes when the second fragment matches", () => {
|
|
313
|
+
const el = makeInput({
|
|
314
|
+
"data-xdbc": "",
|
|
315
|
+
"data-xdbc-or": "regex:^\\d+$;;eq:N/A",
|
|
316
|
+
});
|
|
317
|
+
const cleanup = scanDOM();
|
|
318
|
+
fireInput(el, "N/A");
|
|
319
|
+
expect(el.value).toBe("N/A");
|
|
320
|
+
cleanup();
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
test("reverts when no fragment matches", () => {
|
|
324
|
+
const el = makeInput({
|
|
325
|
+
"data-xdbc": "",
|
|
326
|
+
"data-xdbc-or": "regex:^\\d+$;;eq:N/A",
|
|
327
|
+
});
|
|
328
|
+
const cleanup = scanDOM();
|
|
329
|
+
fireInput(el, "abc");
|
|
330
|
+
expect(el.value).toBe("");
|
|
331
|
+
cleanup();
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
test("colons inside a regex value are handled correctly", () => {
|
|
335
|
+
// "regex:^https?://" — colon after first split position belongs to the pattern
|
|
336
|
+
const el = makeInput({
|
|
337
|
+
"data-xdbc": "",
|
|
338
|
+
"data-xdbc-or": "regex:^https?://;;eq:N/A",
|
|
339
|
+
});
|
|
340
|
+
const cleanup = scanDOM();
|
|
341
|
+
fireInput(el, "https://example.com");
|
|
342
|
+
expect(el.value).toBe("https://example.com");
|
|
343
|
+
// lastValid is now "https://example.com"; ftp:// fails both fragments
|
|
344
|
+
fireInput(el, "ftp://nope");
|
|
345
|
+
expect(el.value).toBe("https://example.com"); // reverted to last valid
|
|
346
|
+
cleanup();
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
test("warns and skips an unknown contract key in or fragment", () => {
|
|
350
|
+
const warn = jest.spyOn(console, "warn").mockImplementation(() => {});
|
|
351
|
+
const el = makeInput({
|
|
352
|
+
"data-xdbc": "",
|
|
353
|
+
"data-xdbc-or": "unknown:foo;;eq:ok",
|
|
354
|
+
});
|
|
355
|
+
const cleanup = scanDOM();
|
|
356
|
+
fireInput(el, "ok");
|
|
357
|
+
expect(el.value).toBe("ok"); // eq:ok passes
|
|
358
|
+
warn.mockRestore();
|
|
359
|
+
cleanup();
|
|
360
|
+
});
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
// ─── Multiple contracts on one element ────────────────────────────────────────
|
|
364
|
+
|
|
365
|
+
describe("multiple contracts on one element", () => {
|
|
366
|
+
test("all must pass — first failure blocks and reverts", () => {
|
|
367
|
+
const el = makeInput({
|
|
368
|
+
"data-xdbc": "",
|
|
369
|
+
"data-xdbc-regex": "^\\d+$",
|
|
370
|
+
"data-xdbc-greater": "0",
|
|
371
|
+
"data-xdbc-less-or-equal": "100",
|
|
372
|
+
});
|
|
373
|
+
const cleanup = scanDOM();
|
|
374
|
+
fireInput(el, "50");
|
|
375
|
+
expect(el.value).toBe("50");
|
|
376
|
+
fireInput(el, "150"); // fails less-or-equal
|
|
377
|
+
expect(el.value).toBe("50");
|
|
378
|
+
fireInput(el, "abc"); // fails regex
|
|
379
|
+
expect(el.value).toBe("50");
|
|
380
|
+
cleanup();
|
|
381
|
+
});
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
// ─── onInfringement integration ───────────────────────────────────────────────
|
|
385
|
+
|
|
386
|
+
describe("scanDOM — onInfringement integration", () => {
|
|
387
|
+
test("fires onInfringement callback with precondition context on invalid input", () => {
|
|
388
|
+
const spy = jest.fn();
|
|
389
|
+
DBC.isolated((dbc) => {
|
|
390
|
+
dbc.infringementSettings.throwException = false;
|
|
391
|
+
dbc.infringementSettings.onInfringement = spy;
|
|
392
|
+
const el = makeInput({ "data-xdbc": "", "data-xdbc-regex": "^\\d*$" });
|
|
393
|
+
const cleanup = scanDOM();
|
|
394
|
+
fireInput(el, "abc");
|
|
395
|
+
cleanup();
|
|
396
|
+
});
|
|
397
|
+
expect(spy).toHaveBeenCalledTimes(1);
|
|
398
|
+
expect(spy.mock.calls[0][1].type).toBe("precondition");
|
|
399
|
+
expect(spy.mock.calls[0][1].value).toBe("abc");
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
test("does not fire onInfringement on valid input", () => {
|
|
403
|
+
const spy = jest.fn();
|
|
404
|
+
DBC.isolated((dbc) => {
|
|
405
|
+
dbc.infringementSettings.throwException = false;
|
|
406
|
+
dbc.infringementSettings.onInfringement = spy;
|
|
407
|
+
const el = makeInput({ "data-xdbc": "", "data-xdbc-regex": "^\\d*$" });
|
|
408
|
+
const cleanup = scanDOM();
|
|
409
|
+
fireInput(el, "123");
|
|
410
|
+
cleanup();
|
|
411
|
+
});
|
|
412
|
+
expect(spy).not.toHaveBeenCalled();
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
test("throwException:true does not propagate out of the event handler", () => {
|
|
416
|
+
DBC.isolated((dbc) => {
|
|
417
|
+
dbc.infringementSettings.throwException = true;
|
|
418
|
+
const el = makeInput({ "data-xdbc": "", "data-xdbc-regex": "^\\d*$" });
|
|
419
|
+
const cleanup = scanDOM();
|
|
420
|
+
expect(() => fireInput(el, "abc")).not.toThrow();
|
|
421
|
+
cleanup();
|
|
422
|
+
});
|
|
423
|
+
});
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
// ─── IME / composition ────────────────────────────────────────────────────────
|
|
427
|
+
|
|
428
|
+
describe("scanDOM — IME / composition", () => {
|
|
429
|
+
test("does not validate mid-composition, validates on compositionend", () => {
|
|
430
|
+
const el = makeInput({ "data-xdbc": "", "data-xdbc-regex": "^[a-z]*$" });
|
|
431
|
+
const cleanup = scanDOM();
|
|
432
|
+
// Simulate typing a valid composed value
|
|
433
|
+
fireIME(el, "hello");
|
|
434
|
+
expect(el.value).toBe("hello");
|
|
435
|
+
cleanup();
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
test("reverts invalid value after compositionend", () => {
|
|
439
|
+
const el = makeInput({ "data-xdbc": "", "data-xdbc-regex": "^[a-z]*$" });
|
|
440
|
+
const cleanup = scanDOM();
|
|
441
|
+
fireIME(el, "123"); // invalid after composition
|
|
442
|
+
expect(el.value).toBe("");
|
|
443
|
+
cleanup();
|
|
444
|
+
});
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
// ─── registerDOMContract ──────────────────────────────────────────────────────
|
|
448
|
+
|
|
449
|
+
describe("registerDOMContract", () => {
|
|
450
|
+
test("a custom registered contract is picked up by scanDOM", () => {
|
|
451
|
+
registerDOMContract("test-max-length", (value, attr) =>
|
|
452
|
+
value.length <= Number(attr) ? true : `Max length is ${attr}`,
|
|
453
|
+
);
|
|
454
|
+
const el = makeInput({ "data-xdbc": "", "data-xdbc-test-max-length": "5" });
|
|
455
|
+
const cleanup = scanDOM();
|
|
456
|
+
fireInput(el, "hello"); // length 5 — passes
|
|
457
|
+
expect(el.value).toBe("hello");
|
|
458
|
+
fireInput(el, "toolong"); // length 7 — fails
|
|
459
|
+
expect(el.value).toBe("hello");
|
|
460
|
+
cleanup();
|
|
461
|
+
});
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
// ─── <textarea> support ───────────────────────────────────────────────────────
|
|
465
|
+
|
|
466
|
+
describe("textarea support", () => {
|
|
467
|
+
test("works with <textarea> exactly like <input>", () => {
|
|
468
|
+
const ta = document.createElement("textarea");
|
|
469
|
+
ta.setAttribute("data-xdbc", "");
|
|
470
|
+
ta.setAttribute("data-xdbc-regex", "^[a-z ]*$");
|
|
471
|
+
document.body.appendChild(ta);
|
|
472
|
+
const cleanup = scanDOM();
|
|
473
|
+
ta.value = "hello";
|
|
474
|
+
ta.dispatchEvent(new Event("input"));
|
|
475
|
+
expect(ta.value).toBe("hello");
|
|
476
|
+
ta.value = "HELLO";
|
|
477
|
+
ta.dispatchEvent(new Event("input"));
|
|
478
|
+
expect(ta.value).toBe("hello");
|
|
479
|
+
cleanup();
|
|
480
|
+
});
|
|
481
|
+
});
|