@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.
- package/README.md +7 -1
- package/package.json +6 -1
- package/src/README.md.js +23 -0
- package/src/core/Error/CancelError.js +6 -0
- package/src/core/InputAdapter.js +21 -3
- package/types/core/Error/CancelError.d.ts +3 -0
- package/types/core/InputAdapter.d.ts +18 -2
- package/.datasets/README.dataset.jsonl +0 -12
- package/.editorconfig +0 -20
- package/CONTRIBUTING.md +0 -42
- package/docs/uk/README.md +0 -240
- package/playground/User.js +0 -52
- package/playground/currency.exchange.js +0 -48
- package/playground/i18n/index.js +0 -21
- package/playground/i18n/uk.js +0 -53
- package/playground/language.form.js +0 -25
- package/playground/main.js +0 -72
- package/playground/registration.form.js +0 -58
- package/playground/topup.telephone.js +0 -62
- package/src/App/User/UserApp.test.js +0 -56
- package/src/App/User/UserUI.test.js +0 -51
- package/src/Frame/Frame.test.js +0 -429
- package/src/View/View.test.js +0 -77
- package/src/core/Form/Form.test.js +0 -116
- package/src/core/Form/Input.test.js +0 -58
- package/src/core/Form/Message.test.js +0 -54
- package/src/core/InputAdapter.test.js +0 -35
- package/src/core/Message/InputMessage.test.js +0 -45
- package/src/core/Message/Message.test.js +0 -58
- package/src/core/Message/OutputMessage.test.js +0 -61
- package/src/core/OutputAdapter.test.js +0 -35
- package/src/core/Stream.test.js +0 -78
- package/src/index.test.js +0 -14
- package/stories/App/AppView.js +0 -15
- package/stories/App/AppView.test.js +0 -22
- package/stories/App/RenderOptions.js +0 -14
- package/stories/nodejs/interface.test.js +0 -27
- package/system.md +0 -187
- package/system1.md +0 -137
- package/task.md +0 -181
- package/tsconfig.json +0 -23
- package/vitest.config.js +0 -26
package/src/Frame/Frame.test.js
DELETED
|
@@ -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
|
-
})
|
package/src/View/View.test.js
DELETED
|
@@ -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
|
-
})
|