@nan0web/ui 1.0.0 → 1.0.2

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 (42) hide show
  1. package/README.md +7 -1
  2. package/package.json +6 -1
  3. package/src/README.md.js +23 -0
  4. package/src/core/Error/CancelError.js +6 -0
  5. package/src/core/InputAdapter.js +21 -3
  6. package/types/core/Error/CancelError.d.ts +3 -0
  7. package/types/core/InputAdapter.d.ts +18 -2
  8. package/.datasets/README.dataset.jsonl +0 -12
  9. package/.editorconfig +0 -20
  10. package/CONTRIBUTING.md +0 -42
  11. package/docs/uk/README.md +0 -240
  12. package/playground/User.js +0 -52
  13. package/playground/currency.exchange.js +0 -48
  14. package/playground/i18n/index.js +0 -21
  15. package/playground/i18n/uk.js +0 -53
  16. package/playground/language.form.js +0 -25
  17. package/playground/main.js +0 -72
  18. package/playground/registration.form.js +0 -58
  19. package/playground/topup.telephone.js +0 -62
  20. package/src/App/User/UserApp.test.js +0 -56
  21. package/src/App/User/UserUI.test.js +0 -51
  22. package/src/Frame/Frame.test.js +0 -429
  23. package/src/View/View.test.js +0 -77
  24. package/src/core/Form/Form.test.js +0 -116
  25. package/src/core/Form/Input.test.js +0 -58
  26. package/src/core/Form/Message.test.js +0 -54
  27. package/src/core/InputAdapter.test.js +0 -35
  28. package/src/core/Message/InputMessage.test.js +0 -45
  29. package/src/core/Message/Message.test.js +0 -58
  30. package/src/core/Message/OutputMessage.test.js +0 -61
  31. package/src/core/OutputAdapter.test.js +0 -35
  32. package/src/core/Stream.test.js +0 -78
  33. package/src/index.test.js +0 -14
  34. package/stories/App/AppView.js +0 -15
  35. package/stories/App/AppView.test.js +0 -22
  36. package/stories/App/RenderOptions.js +0 -14
  37. package/stories/nodejs/interface.test.js +0 -27
  38. package/system.md +0 -187
  39. package/system1.md +0 -137
  40. package/task.md +0 -181
  41. package/tsconfig.json +0 -23
  42. package/vitest.config.js +0 -26
@@ -1,429 +0,0 @@
1
- import { describe, it } from "node:test"
2
- import { strict as assert } from "node:assert"
3
- import { empty, notEmpty } from "@nan0web/types"
4
- import Frame from "./Frame.js"
5
- import stringWidth from "string-width"
6
-
7
- describe("Frame", () => {
8
- it("should create empty Frame", () => {
9
- const frame = new Frame()
10
- assert.ok(empty(frame))
11
- })
12
- it("should create non-empty Frame", () => {
13
- const frame = Frame.from(["Non", "empty"])
14
- assert.ok(notEmpty(frame))
15
- })
16
- it("should print empty zero and false", () => {
17
- const input = [
18
- [0, "0", false, "false"],
19
- [undefined, null, "", {}]
20
- ]
21
- const frame = new Frame({ value: input, width: 144, height: 33 })
22
- assert.ok(notEmpty(frame))
23
- const rows = input.map(row => row.map(String))
24
- assert.deepStrictEqual(frame.value, rows)
25
- frame.render()
26
- assert.equal(
27
- frame.imprint,
28
- rows.map(
29
- row => row.join("")
30
- ).map(
31
- row => row + " ".repeat(144 - stringWidth(row))
32
- ).join("\n")
33
- )
34
- })
35
- it("should print welcome", () => {
36
- const input = [
37
- ["Welcome", " ", "World", "!"],
38
- ["What can we do today great?"],
39
- ]
40
- const rows = input.map(
41
- row => row.join("")
42
- ).map(
43
- row => row + " ".repeat(144 - stringWidth(row))
44
- )
45
- const frame = new Frame({ value: input, width: 144, height: 33 })
46
- frame.render()
47
- assert.equal(frame.imprint, rows.join("\n"))
48
- })
49
- it("should transform value", () => {
50
- const frame = Frame.from(["Welcome"])
51
- const t = v => "Вітання"
52
- const transformed = frame.transform(t)
53
- assert.deepStrictEqual(transformed.value, [["Вітання"]])
54
- })
55
- it("should print special utf characters and fit the width", () => {
56
- const input = [
57
- ["你好", "世界"],
58
- ["Привіт", "Світ"],
59
- ["こんにちは", "世界"],
60
- ]
61
- const frame = Frame.from(input)
62
- frame.render()
63
- const imprintLines = frame.imprint.split("\n")
64
- imprintLines.forEach(line => {
65
- assert.ok(stringWidth(line) <= 144)
66
- })
67
- assert.deepStrictEqual(frame.value, input)
68
- })
69
- it("should render table with padding", () => {
70
- const rows = [
71
- ["gpt-4.1", 1_047_576],
72
- ["gpt-4o", 128_000],
73
- ]
74
- const table = Frame.table({ padding: 3 })(rows)
75
- assert.deepStrictEqual(table, [
76
- ["gpt-4.1 ", "1047576"],
77
- ["gpt-4o ", "128000"],
78
- ])
79
- })
80
- it("should render with replace method and fill spaces", () => {
81
- const input = [["Test"]]
82
- const frame = new Frame({ value: input, width: 10, height: 3 })
83
- const output = frame.render({ method: Frame.RenderMethod.REPLACE }).split("\n")
84
- assert.equal(output.length, 3)
85
- assert.equal(output[0].length - `\x1b[0;0H`.length, 10)
86
- assert.equal(output[1].length, 10)
87
- assert.equal(output[2].length, 10)
88
- })
89
- it("should render with append method over previous frame", () => {
90
- const input = [["Append"]]
91
- const frame = new Frame({ value: input, width: 10, height: 3 })
92
- const output = frame.render({ method: "append" }).split("\n")
93
- assert.ok(output.length <= 3)
94
- assert.ok(output[0].length <= 10)
95
- })
96
- it("should render visible method", () => {
97
- const input = [["Visible"]]
98
- const frame = new Frame({ value: input, width: 10, height: 3 })
99
- const output = frame.render({ method: "visible" }).split("\n")
100
- assert.ok(output.length <= 3)
101
- })
102
- it("should handle cell options with style objects", () => {
103
- const input = [
104
- ["<b>Hello</b>", "<i>World</i>"],
105
- ["<fg=red>Red</>", "<bg=#00ff00>Green background</>"]
106
- ]
107
- const frame = new Frame({ value: input, width: 20, height: 4 })
108
- const output = frame.render()
109
- assert.ok(output.split("\n").length <= 4)
110
- })
111
- it("should handle row options with style objects", () => {
112
- const input = [
113
- ["Name", "Age"],
114
- ["John", 30],
115
- ["<u>Underlined</u>", "<s>Strikethrough</s>", { color: "blue" }]
116
- ]
117
- const frame = new Frame({ value: input, width: 30, height: 4 })
118
- const output = frame.render()
119
- assert.ok(output.split("\n").length <= 4)
120
- })
121
- it("should handle frame options set by method with XML tags", () => {
122
- const input = [
123
- ["<b>Bold</b>", "<i>Italic</i>"],
124
- ["<fg=red>Red</fg>", "<bg=#0000ff>Blue bg</bg>"]
125
- ]
126
- const frame = new Frame({ value: input, width: 20, height: 4 })
127
- const output = frame.render()
128
- assert.ok(output.split("\n").length <= 4)
129
- })
130
-
131
- it("should render correctly with different window sizes", () => {
132
- const input = [
133
- ["Line 1: Hello World!"],
134
- ["Line 2: Another line"],
135
- ["Line 3: Yet another line"],
136
- ["Line 4: More text here"],
137
- ["Line 5: Last line"]
138
- ]
139
-
140
- const sizes = [
141
- { width: 10, height: 2 },
142
- { width: 20, height: 3 },
143
- { width: 50, height: 5 },
144
- { width: 144, height: 33 },
145
- ]
146
-
147
- sizes.forEach(({ width, height }) => {
148
- const frame = new Frame({ value: input, width, height })
149
- const output = frame.render({ method: Frame.RenderMethod.APPEND })
150
- const lines = output.split("\n")
151
- assert.ok(lines.length <= height)
152
- lines.forEach(line => {
153
- assert.ok(stringWidth(line) <= width)
154
- })
155
- })
156
- })
157
-
158
- // Additional tests for BOF, BOL in different positions with REPLACE, VISIBLE, APPEND
159
-
160
- it("should handle BOF at start with REPLACE method", () => {
161
- const input = [
162
- Frame.BOF,
163
- ["Line 1"],
164
- ["Line 2"]
165
- ]
166
- const frame = new Frame({ value: input, width: 10, height: 4 })
167
- const output = frame.render({ method: Frame.RenderMethod.REPLACE })
168
- assert.ok(output.startsWith(Frame.BOF))
169
- const lines = output.split("\n")
170
- assert.equal(lines.length, 4)
171
- assert.match(lines[1], /^\s*$/) // empty row
172
- assert.deepStrictEqual(lines.slice(-2), ["Line 1 ", "Line 2 "])
173
- })
174
-
175
- it("should handle BOF at end with REPLACE method", () => {
176
- const input = [
177
- ["Line 1"],
178
- ["Line 2"],
179
- Frame.BOF
180
- ]
181
- const frame = new Frame({ value: input, width: 10, height: 4 })
182
- const output = frame.render({ method: Frame.RenderMethod.REPLACE })
183
- assert.ok(output.endsWith(Frame.BOF))
184
- const lines = output.split("\n")
185
- assert.equal(lines.length, 4)
186
- assert.deepStrictEqual(lines.slice(0, 2), [
187
- "Line 1 ",
188
- "Line 2 "
189
- ])
190
- assert.match(lines[2], /^\s*$/) // empty row
191
- })
192
-
193
- it("should handle BOF at start with APPEND method", () => {
194
- const input = [
195
- Frame.BOF,
196
- ["Line 1"],
197
- ["Line 2"]
198
- ]
199
- const frame = new Frame({ value: input, width: 10, height: 4 })
200
- const output = frame.render({ method: Frame.RenderMethod.APPEND })
201
- assert.ok(output.startsWith(Frame.BOF))
202
- const lines = output.split("\n")
203
- assert.ok(lines.length <= 4)
204
- assert.deepStrictEqual(lines.slice(-2), ["Line 1 ", "Line 2 "])
205
- })
206
-
207
- it("should handle BOF at end with APPEND method", () => {
208
- const input = [
209
- ["Line 1"],
210
- ["Line 2"],
211
- Frame.BOF
212
- ]
213
- const frame = new Frame({ value: input, width: 10, height: 4 })
214
- const output = frame.render({ method: Frame.RenderMethod.APPEND })
215
- assert.ok(output.endsWith(Frame.BOF))
216
- const lines = output.split("\n")
217
- assert.equal(lines.length, 4)
218
- assert.deepStrictEqual(lines.slice(1, 3), ["Line 2 ", ""])
219
- })
220
-
221
- it("should handle BOF at start with VISIBLE method", () => {
222
- const input = [
223
- Frame.BOF,
224
- ["Line 1"],
225
- ["Line 2"]
226
- ]
227
- const frame = new Frame({ value: input, width: 10, height: 4 })
228
- const output = frame.render({ method: Frame.RenderMethod.VISIBLE })
229
- assert.ok(output.startsWith(`\x1b[1A`))
230
- const lines = output.split("\n")
231
- assert.ok(lines.length <= 2)
232
- assert.deepStrictEqual(lines, [
233
- Frame.cursorUp() + Frame.CLEAR_LINE + "\r" + "Line 1",
234
- Frame.CLEAR_LINE + "\r" + "Line 2"
235
- ])
236
- })
237
-
238
- it("should handle BOF at end with VISIBLE method", () => {
239
- const input = [
240
- ["Line 1"],
241
- ["Line 2"],
242
- Frame.BOF
243
- ]
244
- const frame = new Frame({ value: input, width: 10, height: 4 })
245
- const output = frame.render({ method: Frame.RenderMethod.VISIBLE })
246
- assert.ok(output.startsWith(Frame.CLEAR_LINE + "\r"))
247
- const lines = output.split("\n")
248
- assert.ok(lines.length <= 2)
249
- assert.deepStrictEqual(lines, [
250
- Frame.CLEAR_LINE + "\r" + "Line 1",
251
- Frame.CLEAR_LINE + "\r" + "Line 2" + Frame.cursorUp(1)
252
- ])
253
- })
254
-
255
- it("should handle BOL in lines with REPLACE method", () => {
256
- const input = [
257
- ["Line 1" + Frame.BOL],
258
- ["Line 2"]
259
- ]
260
- const frame = new Frame({ value: input, width: 10, height: 3 })
261
- const output = frame.render({ method: Frame.RenderMethod.REPLACE })
262
- const lines = output.split("\n")
263
- assert.ok(lines.some(line => line.includes(Frame.BOL)))
264
- assert.equal(lines.length, 3)
265
- })
266
-
267
- it("should handle BOL in lines with APPEND method", () => {
268
- const input = [
269
- ["Line 1" + Frame.BOL],
270
- ["Line 2"]
271
- ]
272
- const frame = new Frame({ value: input, width: 10, height: 3 })
273
- const output = frame.render({ method: Frame.RenderMethod.APPEND })
274
- const lines = output.split("\n")
275
- assert.ok(lines.some(line => line.includes(Frame.BOL)))
276
- assert.ok(lines.length <= 3)
277
- })
278
-
279
- it("should handle BOL in lines with VISIBLE method", () => {
280
- const input = [
281
- ["Line 1" + Frame.BOL],
282
- ["Line 2"]
283
- ]
284
- const frame = new Frame({ value: input, width: 10, height: 3 })
285
- const output = frame.render({ method: Frame.RenderMethod.VISIBLE })
286
- const lines = output.split("\n")
287
- assert.ok(lines.some(line => line.includes(Frame.BOL)))
288
- assert.ok(lines.length <= 3)
289
- })
290
- })
291
-
292
- describe("Frame.RenderMethod", () => {
293
- const table = Frame.table({
294
- aligns: ["r"],
295
- padding: 2,
296
- })([
297
- ["Usage: node memory-usage.js", "", ""],
298
- ["[process_name]", "", "Name of the process to check memory usage for"],
299
- ["-n number_of_processes", "", "Number of top memory consumers to show (default: 9)"],
300
- ["--help", "", "Show this help message"],
301
- ])
302
- const frame = new Frame({
303
- value: [...table, ""],
304
- renderMethod: Frame.RenderMethod.REPLACE,
305
- })
306
-
307
- it("should render with REPLACE method", () => {
308
- const output = frame.render({
309
- method: Frame.RenderMethod.REPLACE,
310
- })
311
- assert.equal(typeof output, "string")
312
- const lines = output.split("\n")
313
- assert.ok(lines.length > 0)
314
- lines.forEach(line => {
315
- assert.ok(line.length <= (frame.width < 0 ? 144 : frame.width))
316
- })
317
- })
318
-
319
- it("should render with APPEND method", () => {
320
- const output = frame.render({ method: Frame.RenderMethod.APPEND })
321
- assert.equal(typeof output, "string")
322
- const lines = output.split("\n")
323
- const height = frame.height < 0 ? 10 : frame.height
324
- const width = frame.width < 0 ? 144 : frame.width
325
- assert.ok(lines.length <= height)
326
- lines.forEach(line => {
327
- assert.ok(line.length <= width)
328
- })
329
- })
330
-
331
- it("should render with VISIBLE method", () => {
332
- const output = frame.render({ method: Frame.RenderMethod.VISIBLE })
333
- assert.equal(typeof output, "string")
334
- const lines = output.split("\n")
335
- const height = frame.height < 0 ? 10 : frame.height
336
- const width = frame.width < 0 ? 144 : frame.width
337
- assert.ok(lines.length <= height)
338
- lines.forEach(line => {
339
- assert.ok(line.length <= width)
340
- })
341
- })
342
- })
343
-
344
- describe("Frame (3rd party deprecations)", () => {
345
- it("should detect extra lines above BOF and allow for offset rendering", () => {
346
- // Simulate a terminal with 2 extra lines above (e.g., from warnings)
347
- const extraLines = 2
348
- const selectBox = [
349
- Frame.BOF,
350
- ["Select a command to run"],
351
- ["[+]draw"],
352
- [" - send"],
353
- [" - stats"],
354
- [" - analyze"],
355
- [" - extract"]
356
- ]
357
- const frame = new Frame({ value: selectBox, width: 30, height: 8 })
358
- const output = frame.render({ method: Frame.RenderMethod.REPLACE })
359
- // The output should start with BOF and have the select box content
360
- assert.ok(output.startsWith(Frame.BOF))
361
- const lines = output.split("\n")
362
- // Simulate that the select box is rendered at the top, but if there are extra lines above,
363
- // the select box will be shifted down by `extraLines`
364
- // To check for this, we can simulate a "screen" with extra lines and see where the select box appears
365
- const screen = [
366
- "DeprecationWarning: ...", // extra line 1
367
- "Some other warning...", // extra line 2
368
- ...lines
369
- ]
370
- // The select box should appear at index `extraLines` (after the warnings)
371
- assert.ok(screen[extraLines + 2].includes("Select a command to run"))
372
- assert.ok(screen[extraLines + 3].includes("[+]draw"))
373
- assert.ok(screen[extraLines + 4].includes("- send"))
374
- })
375
-
376
- it("should allow for offset rendering by prepending empty lines before BOF", () => {
377
- // If you want to compensate for extra lines, you can prepend empty lines before BOF
378
- const extraLines = 2
379
- const selectBox = [
380
- "",
381
- "",
382
- Frame.BOF,
383
- ["Select a command to run"],
384
- ["[+]draw"],
385
- [" - send"],
386
- [" - stats"],
387
- [" - analyze"],
388
- [" - extract"]
389
- ]
390
- const frame = new Frame({ value: selectBox, width: 30, height: 10 })
391
- const output = frame.render({ method: Frame.RenderMethod.REPLACE })
392
- const lines = output.split("\n")
393
- // The select box should now appear after the empty lines
394
- assert.equal(lines[4], "Select a command to run".padEnd(30))
395
- assert.equal(lines[5], "[+]draw".padEnd(30))
396
- })
397
-
398
- it("should render select box correctly even with extra lines above (simulate terminal shift)", () => {
399
- // Simulate a terminal with 2 extra lines above (e.g., from warnings)
400
- const extraLines = 2
401
- const selectBox = [
402
- Frame.BOF,
403
- ["Select a command to run"],
404
- ["[+]draw"],
405
- [" - send"],
406
- [" - stats"],
407
- [" - analyze"],
408
- [" - extract"]
409
- ]
410
- const frame = new Frame({ value: selectBox, width: 30, height: 8 })
411
- const output = frame.render({ method: Frame.RenderMethod.REPLACE })
412
- const lines = output.split("\n")
413
- // Simulate a "screen" with extra lines above
414
- const screen = [
415
- "DeprecationWarning: ...",
416
- "Some other warning...",
417
- ...lines
418
- ]
419
- // The select box should still be visible at the correct offset
420
- assert.equal(screen[extraLines].trim(), `\x1b[0;0H`)
421
- assert.equal(screen[extraLines + 1].trim(), "")
422
- assert.equal(screen[extraLines + 2].trim(), "Select a command to run")
423
- assert.equal(screen[extraLines + 3].trim(), "[+]draw")
424
- assert.equal(screen[extraLines + 4].trim(), "- send")
425
- assert.equal(screen[extraLines + 5].trim(), "- stats")
426
- assert.equal(screen[extraLines + 6].trim(), "- analyze")
427
- assert.equal(screen[extraLines + 7].trim(), "- extract")
428
- })
429
- })
@@ -1,77 +0,0 @@
1
- import { describe, it } from "node:test"
2
- import { strict as assert } from "node:assert"
3
- import stringWidth from "string-width"
4
- import View from "./View.js"
5
- import Frame from "../Frame/Frame.js"
6
- import FrameProps from "../Frame/Props.js"
7
- import Welcome from "../Component/Welcome/index.js"
8
-
9
- describe("View", () => {
10
- it("should create empty instance", () => {
11
- const view = new View()
12
- assert.ok(view)
13
- })
14
- it("should print frame", () => {
15
- const view = new View()
16
- view.render(1)(["Hello"])
17
- assert.equal(String(view.frame), Frame.CLEAR_LINE + "\r" + "Hello")
18
- const value = view.render(0)(["No output"])
19
- assert.deepStrictEqual({ ...value }, {
20
- imprint: Frame.CLEAR_LINE + "\r" + "No output",
21
- value: [["No output"]],
22
- width: 144,
23
- height: 33,
24
- renderMethod: "visible",
25
- defaultProps: new FrameProps(),
26
- })
27
- view.render(() => (["fn result"]))(["Even with a function"])
28
- assert.equal(String(view.frame), Frame.CLEAR_LINE + "\r" + "fn result")
29
- assert.deepStrictEqual(view.stdout.stream, [
30
- Frame.RESET + Frame.CLEAR_LINE + "\r" + "Hello",
31
- Frame.CLEAR_LINE + "\r" + "fn result"
32
- ])
33
- })
34
- it("should print frame with render method set to append", () => {
35
- const view = new View({ renderMethod: View.RenderMethod.APPEND })
36
- view.render(1)(["Hello"])
37
- assert.equal(String(view.frame), "Hello" + " ".repeat(139))
38
- view.render(1)(["world"])
39
- assert.equal(String(view.frame), "world" + " ".repeat(139))
40
- const value = view.render(0)(["No output"])
41
- assert.deepStrictEqual({ ...value }, {
42
- imprint: "No output" + " ".repeat(144 - "No output".length),
43
- value: [["No output"]],
44
- width: 144,
45
- height: 33,
46
- renderMethod: View.RenderMethod.APPEND,
47
- defaultProps: new FrameProps(),
48
- })
49
- view.render(() => (["fn result"]))(["Even with a function"])
50
- assert.equal(String(view.frame), "fn result" + " ".repeat(144 - 9))
51
- assert.deepStrictEqual(view.stdout.stream, [
52
- Frame.RESET + "Hello",
53
- Frame.RESET + "world",
54
- "fn result"
55
- ].map(
56
- row => row + " ".repeat(144 - stringWidth(row))
57
- ))
58
- })
59
- it("should render Welcome component", () => {
60
- const view = new View({
61
- renderMethod: View.RenderMethod.APPEND,
62
- width: 144,
63
- height: 33,
64
- })
65
- view.register(Welcome)
66
- const frame = view.render("Welcome")({ user: { name: "View" } })
67
- assert.ok(frame instanceof Frame)
68
- const expected = [
69
- "Welcome View!",
70
- "What can we do today great?",
71
- "",
72
- ].map(
73
- row => (row + " ".repeat(144 - row.length))
74
- ).join("\n")
75
- assert.deepStrictEqual(view.stdout.stream, [expected])
76
- })
77
- })
@@ -1,116 +0,0 @@
1
- import { describe, it } from "node:test"
2
- import { strict as assert } from "node:assert"
3
- import UIForm from "./Form.js"
4
- import FormInput from "./Input.js"
5
-
6
- describe("UIForm", () => {
7
- it("should create instance with default values", () => {
8
- const form = new UIForm()
9
- assert.ok(form instanceof UIForm)
10
- assert.equal(form.title, "")
11
- assert.deepEqual(form.fields, [])
12
- assert.deepEqual(form.state, {})
13
- })
14
-
15
- it("should create instance with custom values", () => {
16
- const fields = [
17
- new FormInput({ name: "name", label: "Name", required: true }),
18
- new FormInput({ name: "email", label: "Email", type: "email" })
19
- ]
20
- const props = {
21
- title: "Test Form",
22
- fields,
23
- state: { name: "John" }
24
- }
25
- const form = new UIForm(props)
26
- assert.equal(form.title, "Test Form")
27
- assert.equal(form.fields.length, 2)
28
- assert.deepEqual(form.state, { name: "John" })
29
- })
30
-
31
- it("should set data", () => {
32
- const form = new UIForm({ state: { name: "John" } })
33
- const newForm = form.setData({ email: "john@example.com" })
34
- assert.deepEqual(newForm.state, { name: "John", email: "john@example.com" })
35
- })
36
-
37
- it("should get field by name", () => {
38
- const fields = [new FormInput({ name: "test", label: "Test Field" })]
39
- const form = new UIForm({ fields })
40
- const field = form.getField("test")
41
- assert.ok(field)
42
- assert.equal(field.name, "test")
43
- })
44
-
45
- it("should get values", () => {
46
- const form = new UIForm({ state: { name: "John", age: 30 } })
47
- const values = form.getValues()
48
- assert.deepEqual(values, { name: "John", age: 30 })
49
- })
50
-
51
- it("should validate form", () => {
52
- const fields = [new FormInput({ name: "requiredField", required: true })]
53
- const form = new UIForm({ fields, state: {} })
54
- const result = form.validate()
55
- assert.ok(!result.isValid)
56
- assert.ok(result.errors.requiredField)
57
- })
58
-
59
- it("should validate individual field", () => {
60
- const fields = [new FormInput({ name: "email", type: "email" })]
61
- const form = new UIForm({ fields })
62
- const result = form.validateField("email", "invalid-email")
63
- assert.ok(!result.isValid)
64
- assert.ok(result.errors.email)
65
- })
66
-
67
- it("should convert to JSON", () => {
68
- const form = new UIForm({ title: "Test", state: { name: "John" } })
69
- const json = form.toJSON()
70
- assert.ok(json.id)
71
- assert.equal(json.title, "Test")
72
- assert.ok(Array.isArray(json.fields))
73
- assert.ok(json.state)
74
- assert.ok(json.meta)
75
- })
76
-
77
- it("parses default text fields", () => {
78
- const data = { name: "", email: "" }
79
- const { fields } = UIForm.parse(data)
80
- assert.equal(fields.length, 2)
81
- assert.ok(fields[0] instanceof FormInput)
82
- assert.equal(fields[0].name, "name")
83
- assert.equal(fields[0].label, "Name")
84
- assert.equal(fields[0].type, FormInput.TYPES.TEXT)
85
- assert.equal(fields[0].required, false)
86
- })
87
-
88
- it("applies overrides correctly", () => {
89
- const data = { age: "" }
90
- const { fields } = UIForm.parse(data, {
91
- age: { type: FormInput.TYPES.NUMBER, required: true, label: "User Age" },
92
- })
93
- const ageField = fields[0]
94
- assert.equal(ageField.type, FormInput.TYPES.NUMBER)
95
- assert.ok(ageField.required)
96
- assert.equal(ageField.label, "User Age")
97
- })
98
-
99
- it("supports static custom validators", () => {
100
- // register a validator that ensures a string contains only digits
101
- UIForm.addValidator("digitsOnly", value => {
102
- return /^\d+$/.test(value) ? true : "Must contain only digits"
103
- })
104
-
105
- const fields = [
106
- new FormInput({ name: "phone", label: "Phone", required: true })
107
- ]
108
- const schema = {
109
- phone: { validator: "digitsOnly" }
110
- }
111
- const form = new UIForm({ fields, schema, state: { phone: "12a34" } })
112
- const result = form.validate()
113
- assert.ok(!result.isValid)
114
- assert.equal(result.errors.phone, "Must contain only digits")
115
- })
116
- })
@@ -1,58 +0,0 @@
1
- import { describe, it } from "node:test"
2
- import { strict as assert } from "node:assert"
3
- import FormInput from "./Input.js"
4
-
5
- describe("FormInput", () => {
6
- it("should create instance with default values", () => {
7
- const input = new FormInput({ name: "name" })
8
- assert.ok(input instanceof FormInput)
9
- assert.equal(input.type, FormInput.TYPES.TEXT)
10
- assert.equal(input.required, false)
11
- assert.equal(input.name, "name")
12
- assert.equal(input.placeholder, "")
13
- assert.deepEqual(input.options, [])
14
- })
15
-
16
- it("should create instance with custom values", () => {
17
- const props = {
18
- type: "email",
19
- name: "email",
20
- label: "Email Address",
21
- required: true,
22
- placeholder: "Enter email",
23
- options: ["option1", "option2"],
24
- defaultValue: "test@example.com"
25
- }
26
- const input = new FormInput(props)
27
- assert.equal(input.type, "email")
28
- assert.equal(input.name, "email")
29
- assert.equal(input.label, "Email Address")
30
- assert.equal(input.required, true)
31
- assert.equal(input.placeholder, "Enter email")
32
- assert.deepEqual(input.options, ["option1", "option2"])
33
- assert.equal(input.defaultValue, "test@example.com")
34
- })
35
-
36
- it("should create from string", () => {
37
- const input = FormInput.from("testField")
38
- assert.equal(input.name, "testField")
39
- assert.equal(input.label, "testField")
40
- })
41
-
42
- it("should create from object", () => {
43
- const objInput = { name: "test", type: "text" }
44
- const input = FormInput.from(objInput)
45
- assert.ok(input instanceof FormInput)
46
- assert.equal(input.name, "test")
47
- })
48
-
49
- it("should validate type", async () => {
50
- const validInput = new FormInput({ name: "email", type: "email" })
51
- assert.ok(validInput)
52
- const fn = async () => new FormInput({ name: "email", type: "invalid" })
53
- await assert.rejects(fn, {
54
- name: "TypeError",
55
- message: /FormInput\.type is invalid!/
56
- })
57
- })
58
- })