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.
Files changed (89) hide show
  1. package/.gitattributes +8 -0
  2. package/.vscode/settings.json +3 -3
  3. package/.vscode/tasks.json +23 -23
  4. package/ASSESSMENT.md +249 -0
  5. package/README.md +131 -1
  6. package/__tests__/DBC/AE.test.ts +62 -62
  7. package/__tests__/DBC/ARRAY.test.ts +91 -91
  8. package/__tests__/DBC/DEFINED.test.ts +53 -53
  9. package/__tests__/DBC/DOM.test.ts +481 -0
  10. package/__tests__/DBC/Decorators.test.ts +367 -367
  11. package/__tests__/DBC/EQ.test.ts +13 -13
  12. package/__tests__/DBC/GREATER.test.ts +31 -31
  13. package/__tests__/DBC/HasAttribute.test.ts +60 -60
  14. package/__tests__/DBC/IF.test.ts +62 -62
  15. package/__tests__/DBC/INSTANCE.test.ts +13 -13
  16. package/__tests__/DBC/JSON.OP.test.ts +47 -47
  17. package/__tests__/DBC/JSON.Parse.test.ts +17 -17
  18. package/__tests__/DBC/OR.test.ts +14 -14
  19. package/__tests__/DBC/PLAIN_OBJECT.test.ts +109 -109
  20. package/__tests__/DBC/REGEX.test.ts +17 -17
  21. package/__tests__/DBC/TYPE.test.ts +13 -13
  22. package/__tests__/DBC/UNDEFINED.test.ts +45 -45
  23. package/__tests__/DBC/ZOD.test.ts +54 -54
  24. package/__tests__/DBC/onInfringement.test.ts +262 -0
  25. package/biome.json +40 -40
  26. package/dist/DBC/AE.js +172 -0
  27. package/dist/DBC/ARR/PLAIN_OBJECT.d.ts +0 -3
  28. package/dist/DBC/ARR/PLAIN_OBJECT.js +95 -0
  29. package/dist/DBC/ARRAY.d.ts +0 -3
  30. package/dist/DBC/ARRAY.js +90 -0
  31. package/dist/DBC/COMPARISON/GREATER.js +21 -0
  32. package/dist/DBC/COMPARISON/GREATER_OR_EQUAL.js +21 -0
  33. package/dist/DBC/COMPARISON/LESS.js +21 -0
  34. package/dist/DBC/COMPARISON/LESS_OR_EQUAL.js +21 -0
  35. package/dist/DBC/COMPARISON.js +98 -0
  36. package/dist/DBC/DEFINED.js +87 -0
  37. package/dist/DBC/DOM.d.ts +87 -0
  38. package/dist/DBC/DOM.js +223 -0
  39. package/dist/DBC/EQ/DIFFERENT.js +34 -0
  40. package/dist/DBC/EQ.js +101 -0
  41. package/dist/DBC/HasAttribute.js +101 -0
  42. package/dist/DBC/IF.js +96 -0
  43. package/dist/DBC/INSTANCE.js +122 -0
  44. package/dist/DBC/JSON.OP.js +120 -0
  45. package/dist/DBC/JSON.Parse.js +104 -0
  46. package/dist/DBC/OR.js +125 -0
  47. package/dist/DBC/REGEX.js +136 -0
  48. package/dist/DBC/TYPE.js +112 -0
  49. package/dist/DBC/UNDEFINED.js +87 -0
  50. package/dist/DBC/ZOD.js +99 -0
  51. package/dist/DBC.d.ts +18 -4
  52. package/dist/DBC.js +645 -0
  53. package/dist/Demo.d.ts +10 -0
  54. package/dist/Demo.js +713 -0
  55. package/dist/bundle.js +6140 -405
  56. package/dist/index.d.ts +22 -0
  57. package/dist/index.js +22 -0
  58. package/jest.config.js +32 -32
  59. package/package.json +71 -55
  60. package/src/DBC/AE.ts +269 -288
  61. package/src/DBC/ARR/PLAIN_OBJECT.ts +122 -133
  62. package/src/DBC/ARRAY.ts +117 -127
  63. package/src/DBC/COMPARISON/GREATER.ts +41 -46
  64. package/src/DBC/COMPARISON/GREATER_OR_EQUAL.ts +41 -45
  65. package/src/DBC/COMPARISON/LESS.ts +41 -45
  66. package/src/DBC/COMPARISON/LESS_OR_EQUAL.ts +41 -45
  67. package/src/DBC/COMPARISON.ts +149 -159
  68. package/src/DBC/DEFINED.ts +117 -122
  69. package/src/DBC/DOM.ts +291 -0
  70. package/src/DBC/EQ/DIFFERENT.ts +51 -57
  71. package/src/DBC/EQ.ts +154 -163
  72. package/src/DBC/HasAttribute.ts +149 -154
  73. package/src/DBC/IF.ts +173 -179
  74. package/src/DBC/INSTANCE.ts +168 -171
  75. package/src/DBC/JSON.OP.ts +178 -186
  76. package/src/DBC/JSON.Parse.ts +150 -157
  77. package/src/DBC/OR.ts +183 -187
  78. package/src/DBC/REGEX.ts +195 -196
  79. package/src/DBC/TYPE.ts +142 -149
  80. package/src/DBC/UNDEFINED.ts +115 -117
  81. package/src/DBC/ZOD.ts +130 -135
  82. package/src/DBC.ts +902 -904
  83. package/src/Demo.ts +537 -404
  84. package/src/index.ts +22 -0
  85. package/tsconfig.json +18 -18
  86. package/tsconfig.test.json +7 -7
  87. package/typedoc.json +16 -16
  88. package/webpack.config.js +27 -27
  89. 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
+ });