@nan0web/ui 1.0.0 → 1.0.1

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.
@@ -1,58 +0,0 @@
1
- /**
2
- * Simple registration form using stdin.
3
- *
4
- * Fields: username, password, confirm, emailOrTel
5
- *
6
- * Controls:
7
- * - Input "0" -> cancel (onCancel)
8
- * - Empty input on final prompt -> submit (onSubmit)
9
- */
10
- import { CancelError } from "@nan0web/ui-cli"
11
-
12
- /**
13
- * Main registration flow
14
- * @todo add proper jsdoc to make autocomplete work for input as result of ask, console, t.
15
- */
16
- export async function runRegistration(t, ask, console) {
17
- console.info("\n=== " + t("Registration Form") + " ===")
18
- const data = {}
19
-
20
- // username
21
- {
22
- const input = await ask(t("Username") + ": ", true)
23
- if (input.cancelled) throw new CancelError()
24
- data.username = input.value
25
- }
26
- // password
27
- {
28
- const input = await ask(
29
- t("Password (min 4 chars)") + ": ",
30
- (input) => input.value.length < 4,
31
- t("! Password must be at least 4 characters") + ": "
32
- )
33
- if (input.cancelled) throw new CancelError()
34
- data.password = input.value
35
- }
36
- // confirm password
37
- {
38
- await ask(t("Confirm Password") + ": ", (input) => {
39
- if (input.value !== data.password) {
40
- console.error(t("Passwords do not match. Try again."))
41
- return true
42
- }
43
- return false
44
- })
45
- }
46
- // email or telephone
47
- {
48
- const input = await ask(t("Email or Telephone") + ": ", true)
49
- if (input.cancelled) throw new CancelError()
50
- data.emailOrTel = input.value
51
- }
52
- // final confirmation – empty line submits, "0" cancels
53
- {
54
- await ask(t("Press ENTER to submit, type 0 to cancel") + ": ", i => "" != i.value)
55
- }
56
- console.success("\n" + t("Form submitted successfully!"))
57
- console.info(JSON.stringify({ ...data, password: "****" }, null, 2))
58
- }
@@ -1,62 +0,0 @@
1
- /**
2
- * Simple telephone top‑up form.
3
- *
4
- * Fields: number, amount, currency
5
- *
6
- * Controls:
7
- * - Input "0" on any prompt to cancel.
8
- * - Empty input on final confirmation submits.
9
- *
10
- * Validation:
11
- * - Phone number must contain only digits and be 7‑15 characters long.
12
- */
13
- import { CancelError } from "@nan0web/ui-cli"
14
-
15
- const rates = {
16
- USD: 1,
17
- EUR: 0.9,
18
- UAH: 27,
19
- }
20
-
21
- /** Main top‑up flow */
22
- export async function runTopup(t, ask, console) {
23
- /** Choose currency */
24
- async function chooseCurrency(t) {
25
- const list = Object.keys(rates)
26
- console.info(t("Currency options:"))
27
- list.forEach((c, i) => console.info(` ${i + 1}) ${c}`))
28
- const input = await ask(
29
- `${t("Select currency")} (1-${list.length}): `,
30
- input => {
31
- input.idx = Number(input.value) - 1
32
- return ! (input.idx >= 0 && input.idx < list.length)
33
- },
34
- [t("Invalid choice."), t("Try again") + ":", ""].join(" ")
35
- )
36
- return list[input.idx] ?? null
37
- }
38
-
39
- /** Validate phone number */
40
- function isValidPhone(number) {
41
- return /^[0-9]{7,15}$/.test(number)
42
- }
43
-
44
- console.info("\n=== " + t("Top‑up Telephone") + " ===")
45
- const numberInp = await ask(
46
- t("Phone Number") + ": ",
47
- input => !isValidPhone(input.value),
48
- t("Invalid phone number. Use digits only, 7‑15 characters.") + ": "
49
- )
50
- if (numberInp.cancelled) throw new CancelError()
51
- const number = numberInp.value
52
- const amountInp = await ask(
53
- t("Top‑up Amount") + ": ",
54
- input => isNaN(input.value) || Number(input.value) <= 0 || Number(input.value) > 1_000_000,
55
- t("Amount must be a positive number below 1 million.") + ": "
56
- )
57
- if (amountInp.cancelled) throw new CancelError()
58
- const amount = Number(amountInp.value)
59
- const currency = await chooseCurrency(t)
60
- if (!currency) throw new CancelError()
61
- console.success(`\n${t("Top‑up of")} ${amount} ${currency} ${t("to")} ${number} ${t("scheduled.")}\n`)
62
- }
@@ -1,56 +0,0 @@
1
- import { describe, it } from "node:test"
2
- import { strict as assert } from "node:assert"
3
- import UserApp from "./UserApp.js"
4
- import View from "../../View/View.js"
5
- import UserUI from "./UserUI.js"
6
- import Command from "./Command/index.js"
7
- import Frame from "../../Frame/Frame.js"
8
-
9
- describe("UserApp", () => {
10
- /** @type {UserApp} */
11
- let app
12
- /** @type {View} */
13
- let view
14
- /** @type {UserUI} */
15
- let ui
16
-
17
- it("should set user and welcome", async () => {
18
- app = new UserApp()
19
- view = new View()
20
- view.register("Welcome", (input) => {
21
- return ["Welcome " + input.user.name]
22
- })
23
- ui = new UserUI(app, view)
24
- const cmd = Command.Message.parse("setUser --user Alice")
25
- const result = await app.processCommand(cmd, ui)
26
- assert.equal(String(result.message), Frame.CLEAR_LINE + "\r" + "Welcome Alice")
27
- })
28
-
29
- it("should welcome with user", async () => {
30
- app = new UserApp()
31
- view = new View()
32
- view.register("Welcome", (input) => {
33
- return ["Welcome " + input.user.name]
34
- })
35
- ui = new UserUI(app, view)
36
- const cmd = Command.Message.parse("welcome --user YaRa")
37
- const result = await app.processCommand(cmd, ui)
38
- assert.equal(String(result), Frame.CLEAR_LINE + "\r" + "Welcome YaRa")
39
- })
40
-
41
- it("should ask for a user name and welcome with user", async () => {
42
- app = new UserApp()
43
- view = new View()
44
- view.register("Welcome", (input) => {
45
- return ["Welcome " + input.user.name]
46
- })
47
- ui = new UserUI(app, view)
48
- const cmd = Command.Message.parse("welcome")
49
-
50
- // Mock the stdin to provide input immediately
51
- view.stdin.write("Alice\n")
52
-
53
- const result = await app.processCommand(cmd, ui)
54
- assert.equal(String(result), Frame.CLEAR_LINE + "\r" + "Welcome Alice\n")
55
- })
56
- })
@@ -1,51 +0,0 @@
1
- import { describe, it } from "node:test"
2
- import { strict as assert } from "node:assert"
3
- import UserApp from "./UserApp.js"
4
- import UserUI from "./UserUI.js"
5
- import View from "../../View/View.js"
6
- import InputMessage from "../../core/Message/InputMessage.js"
7
- import Welcome from "../../Component/Welcome/index.js"
8
-
9
- describe("UserUI", () => {
10
- it("should convert input with user.name to commands", () => {
11
- const app = new UserApp()
12
- const view = new View()
13
- const ui = new UserUI(app, view)
14
-
15
- const commands = ui.convertInput("setUser --user Bob welcome")
16
- assert.equal(commands.length, 1)
17
- assert.equal(commands[0].args[0], "setUser")
18
- assert.equal(commands[0].args[1], "welcome")
19
- assert.equal(commands[0].opts.user, "Bob")
20
- })
21
-
22
- it("should convert input without user.name to askUserName command", () => {
23
- const app = new UserApp()
24
- const view = new View()
25
- const ui = new UserUI(app, view)
26
-
27
- const commands = ui.convertInput("")
28
- assert.equal(commands.length, 1)
29
- assert.equal(commands[0].args.length, 0)
30
- assert.equal(commands[0].opts.user, "")
31
- })
32
-
33
- it("should process askUserName command interactively", async () => {
34
- const app = new UserApp()
35
- const view = new View()
36
- const ui = new UserUI(app, view)
37
- view.register("Welcome", Welcome)
38
-
39
- // Mock view.ask to return a name
40
- view.ask = (input) => Promise.resolve(new InputMessage("Charlie"))
41
- // Mock output to collect outputs
42
- const outputs = []
43
- ui.output = (results) => outputs.push(...results)
44
-
45
- const results = await ui.process(["welcome"])
46
- assert.ok(view.ask)
47
- assert.equal(results.length, 1)
48
- assert.ok(results[0].value[0][0].includes("Welcome"))
49
- assert.ok(view.stdout.stream[0].includes("Welcome Charlie!"))
50
- })
51
- })
@@ -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
- })