@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.
- package/package.json +6 -1
- 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/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
|
-
})
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
import { describe, it } from "node:test"
|
|
2
|
-
import { strict as assert } from "node:assert"
|
|
3
|
-
import FormMessage from "./Message.js"
|
|
4
|
-
|
|
5
|
-
describe("FormMessage", () => {
|
|
6
|
-
it("should create instance with default values", () => {
|
|
7
|
-
const msg = new FormMessage()
|
|
8
|
-
assert.ok(msg instanceof FormMessage)
|
|
9
|
-
assert.deepEqual(msg.data, {})
|
|
10
|
-
assert.deepEqual(msg.schema, {})
|
|
11
|
-
assert.equal(msg.type, "form")
|
|
12
|
-
})
|
|
13
|
-
|
|
14
|
-
it("should create instance with custom values", () => {
|
|
15
|
-
const props = {
|
|
16
|
-
data: { name: "John" },
|
|
17
|
-
schema: { name: { required: true } },
|
|
18
|
-
body: ["Form content"]
|
|
19
|
-
}
|
|
20
|
-
const msg = new FormMessage(props)
|
|
21
|
-
assert.deepEqual(msg.data, { name: "John" })
|
|
22
|
-
assert.deepEqual(msg.schema, { name: { required: true } })
|
|
23
|
-
assert.deepEqual(msg.body, ["Form content"])
|
|
24
|
-
})
|
|
25
|
-
|
|
26
|
-
it("should add data", () => {
|
|
27
|
-
const msg = new FormMessage({ data: { name: "John" } })
|
|
28
|
-
const newMsg = msg.addData({ age: 30 })
|
|
29
|
-
assert.deepEqual(newMsg.data, { name: "John", age: 30 })
|
|
30
|
-
})
|
|
31
|
-
|
|
32
|
-
it("should validate data correctly", () => {
|
|
33
|
-
const schema = {
|
|
34
|
-
name: { required: true },
|
|
35
|
-
email: { type: "email" },
|
|
36
|
-
age: { type: "number" }
|
|
37
|
-
}
|
|
38
|
-
const msg = new FormMessage({ schema })
|
|
39
|
-
|
|
40
|
-
// Valid data
|
|
41
|
-
const validData = { name: "John", email: "john@example.com", age: "30" }
|
|
42
|
-
const validResult = msg.validateData(validData)
|
|
43
|
-
assert.ok(validResult.isValid)
|
|
44
|
-
assert.deepEqual(validResult.errors, {})
|
|
45
|
-
|
|
46
|
-
// Invalid data
|
|
47
|
-
const invalidData = { email: "invalid-email", age: "not-a-number" }
|
|
48
|
-
const invalidResult = msg.validateData(invalidData)
|
|
49
|
-
assert.ok(!invalidResult.isValid)
|
|
50
|
-
assert.ok(invalidResult.errors.name)
|
|
51
|
-
assert.ok(invalidResult.errors.email)
|
|
52
|
-
assert.ok(invalidResult.errors.age)
|
|
53
|
-
})
|
|
54
|
-
})
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import { describe, it } from "node:test"
|
|
2
|
-
import { strict as assert } from "node:assert"
|
|
3
|
-
import InputAdapter from "./InputAdapter.js"
|
|
4
|
-
|
|
5
|
-
describe("InputAdapter", () => {
|
|
6
|
-
it("should create instance", () => {
|
|
7
|
-
const adapter = new InputAdapter()
|
|
8
|
-
assert.ok(adapter instanceof InputAdapter)
|
|
9
|
-
})
|
|
10
|
-
|
|
11
|
-
it("should start and emit input message", () => {
|
|
12
|
-
const adapter = new InputAdapter()
|
|
13
|
-
let emitted = false
|
|
14
|
-
let message = null
|
|
15
|
-
|
|
16
|
-
adapter.on('input', (msg) => {
|
|
17
|
-
emitted = true
|
|
18
|
-
message = msg
|
|
19
|
-
})
|
|
20
|
-
|
|
21
|
-
adapter.start()
|
|
22
|
-
assert.ok(emitted)
|
|
23
|
-
assert.ok(message)
|
|
24
|
-
})
|
|
25
|
-
|
|
26
|
-
it("should stop without error", () => {
|
|
27
|
-
const adapter = new InputAdapter()
|
|
28
|
-
assert.doesNotThrow(() => adapter.stop())
|
|
29
|
-
})
|
|
30
|
-
|
|
31
|
-
it("should be ready by default", () => {
|
|
32
|
-
const adapter = new InputAdapter()
|
|
33
|
-
assert.ok(adapter.isReady())
|
|
34
|
-
})
|
|
35
|
-
})
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import { describe, it } from "node:test"
|
|
2
|
-
import { strict as assert } from "node:assert"
|
|
3
|
-
import InputMessage from "./InputMessage.js"
|
|
4
|
-
import { notEmpty, empty } from "@nan0web/types"
|
|
5
|
-
|
|
6
|
-
describe("InputMessage", () => {
|
|
7
|
-
it("should create instance with default values", () => {
|
|
8
|
-
const msg = new InputMessage()
|
|
9
|
-
assert.ok(msg instanceof InputMessage)
|
|
10
|
-
assert.equal(msg.value, "")
|
|
11
|
-
assert.equal(msg.waiting, false)
|
|
12
|
-
assert.deepEqual(msg.options, [])
|
|
13
|
-
})
|
|
14
|
-
|
|
15
|
-
it("should create instance with custom values", () => {
|
|
16
|
-
const props = {
|
|
17
|
-
value: "user input",
|
|
18
|
-
waiting: true,
|
|
19
|
-
options: ["option1", "option2"]
|
|
20
|
-
}
|
|
21
|
-
const msg = new InputMessage(props)
|
|
22
|
-
assert.equal(msg.value, "user input")
|
|
23
|
-
assert.equal(msg.waiting, true)
|
|
24
|
-
assert.deepEqual(msg.options, ["option1", "option2"])
|
|
25
|
-
})
|
|
26
|
-
|
|
27
|
-
it("should handle string options correctly", () => {
|
|
28
|
-
const msg = new InputMessage({ options: "single-option" })
|
|
29
|
-
assert.deepEqual(msg.options, ["single-option"])
|
|
30
|
-
})
|
|
31
|
-
|
|
32
|
-
it("should detect empty value", () => {
|
|
33
|
-
const emptyMsg = new InputMessage({ value: "" })
|
|
34
|
-
const nonEmptyMsg = new InputMessage({ value: "test" })
|
|
35
|
-
assert.ok(emptyMsg.empty)
|
|
36
|
-
assert.ok(!nonEmptyMsg.empty)
|
|
37
|
-
})
|
|
38
|
-
|
|
39
|
-
it("should validate message", () => {
|
|
40
|
-
const validMsg = new InputMessage({ value: "test" })
|
|
41
|
-
const invalidMsg = new InputMessage({ value: "" })
|
|
42
|
-
assert.ok(validMsg.isValid)
|
|
43
|
-
assert.ok(!invalidMsg.isValid)
|
|
44
|
-
})
|
|
45
|
-
})
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import { describe, it } from "node:test"
|
|
2
|
-
import { strict as assert } from "node:assert"
|
|
3
|
-
import UIMessage from "./Message.js"
|
|
4
|
-
|
|
5
|
-
describe("UIMessage", () => {
|
|
6
|
-
it("should create instance with default values", () => {
|
|
7
|
-
const msg = new UIMessage()
|
|
8
|
-
assert.ok(msg instanceof UIMessage)
|
|
9
|
-
assert.equal(typeof msg.type, "string")
|
|
10
|
-
assert.equal(typeof msg.id, "string")
|
|
11
|
-
assert.ok(msg.time instanceof Date)
|
|
12
|
-
})
|
|
13
|
-
|
|
14
|
-
it("should create instance with custom values", () => {
|
|
15
|
-
const props = {
|
|
16
|
-
type: "test",
|
|
17
|
-
id: "custom-id",
|
|
18
|
-
body: ["test content"]
|
|
19
|
-
}
|
|
20
|
-
const msg = new UIMessage(props)
|
|
21
|
-
assert.equal(msg.type, "test")
|
|
22
|
-
assert.equal(msg.id, "custom-id")
|
|
23
|
-
assert.deepEqual(msg.body, ["test content"])
|
|
24
|
-
})
|
|
25
|
-
|
|
26
|
-
it("should generate unique id when not provided", () => {
|
|
27
|
-
const msg1 = new UIMessage()
|
|
28
|
-
const msg2 = new UIMessage()
|
|
29
|
-
assert.notEqual(msg1.id, msg2.id)
|
|
30
|
-
})
|
|
31
|
-
|
|
32
|
-
it("should use content as body when body not provided", () => {
|
|
33
|
-
const msg = new UIMessage({ content: ["hello"] })
|
|
34
|
-
assert.deepEqual(msg.body, ["hello"])
|
|
35
|
-
})
|
|
36
|
-
|
|
37
|
-
it("should create from data using from() method", () => {
|
|
38
|
-
const data = { type: "info", body: ["test"] }
|
|
39
|
-
const msg = UIMessage.from(data)
|
|
40
|
-
assert.ok(msg instanceof UIMessage)
|
|
41
|
-
assert.equal(msg.type, "info")
|
|
42
|
-
assert.deepEqual(msg.body, ["test"])
|
|
43
|
-
})
|
|
44
|
-
|
|
45
|
-
it("should validate type correctly", () => {
|
|
46
|
-
const validMsg = new UIMessage({ type: UIMessage.TYPES.TEXT })
|
|
47
|
-
const invalidMsg = new UIMessage({ type: "invalid-type" })
|
|
48
|
-
assert.ok(validMsg.isValidType())
|
|
49
|
-
assert.ok(!invalidMsg.isValidType())
|
|
50
|
-
})
|
|
51
|
-
|
|
52
|
-
it("should check if message is empty", () => {
|
|
53
|
-
const emptyMsg = new UIMessage()
|
|
54
|
-
const nonEmptyMsg = new UIMessage({ body: ["content"] })
|
|
55
|
-
assert.ok(emptyMsg.isEmpty())
|
|
56
|
-
assert.ok(!nonEmptyMsg.isEmpty())
|
|
57
|
-
})
|
|
58
|
-
})
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
import { describe, it } from "node:test"
|
|
2
|
-
import { strict as assert } from "node:assert"
|
|
3
|
-
import OutputMessage from "./OutputMessage.js"
|
|
4
|
-
|
|
5
|
-
describe("OutputMessage", () => {
|
|
6
|
-
it("should create instance with default values", () => {
|
|
7
|
-
const msg = new OutputMessage()
|
|
8
|
-
assert.ok(msg instanceof OutputMessage)
|
|
9
|
-
assert.deepEqual(msg.body, [])
|
|
10
|
-
assert.deepEqual(msg.meta, {})
|
|
11
|
-
assert.equal(msg.error, null)
|
|
12
|
-
assert.equal(msg.priority, OutputMessage.PRIORITY.NORMAL)
|
|
13
|
-
})
|
|
14
|
-
|
|
15
|
-
it("should create instance with custom values", () => {
|
|
16
|
-
const props = {
|
|
17
|
-
content: ["test content"],
|
|
18
|
-
meta: { key: "value" },
|
|
19
|
-
error: new Error("test error"),
|
|
20
|
-
priority: OutputMessage.PRIORITY.HIGH
|
|
21
|
-
}
|
|
22
|
-
const msg = new OutputMessage(props)
|
|
23
|
-
assert.deepEqual(msg.content, ["test content"])
|
|
24
|
-
assert.deepEqual(msg.meta, { key: "value" })
|
|
25
|
-
assert.ok(msg.error instanceof Error)
|
|
26
|
-
assert.equal(msg.priority, OutputMessage.PRIORITY.HIGH)
|
|
27
|
-
})
|
|
28
|
-
|
|
29
|
-
it("should set type based on error presence", () => {
|
|
30
|
-
const errorMsg = new OutputMessage({ error: "error occurred" })
|
|
31
|
-
const infoMsg = new OutputMessage({ content: ["info"] })
|
|
32
|
-
assert.equal(errorMsg.type, "error")
|
|
33
|
-
assert.equal(infoMsg.type, "info")
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
it("should handle string content", () => {
|
|
37
|
-
const msg = new OutputMessage({ content: "single string" })
|
|
38
|
-
assert.deepEqual(msg.content, ["single string"])
|
|
39
|
-
})
|
|
40
|
-
|
|
41
|
-
it("should combine messages", () => {
|
|
42
|
-
const msg1 = new OutputMessage({ content: ["part1"], priority: 1 })
|
|
43
|
-
const msg2 = new OutputMessage({ content: ["part2"], priority: 2 })
|
|
44
|
-
const combined = msg1.combine(msg2)
|
|
45
|
-
assert.deepEqual(combined.content, ["part1", "part2"])
|
|
46
|
-
assert.equal(combined.priority, 2)
|
|
47
|
-
})
|
|
48
|
-
|
|
49
|
-
it("should convert to JSON", () => {
|
|
50
|
-
const msg = new OutputMessage({
|
|
51
|
-
content: ["test"],
|
|
52
|
-
meta: { test: true },
|
|
53
|
-
error: new Error("test error")
|
|
54
|
-
})
|
|
55
|
-
const json = msg.toJSON()
|
|
56
|
-
assert.ok(json.body)
|
|
57
|
-
assert.ok(json.meta)
|
|
58
|
-
assert.ok(json.error)
|
|
59
|
-
assert.ok(json.time)
|
|
60
|
-
})
|
|
61
|
-
})
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import { describe, it } from "node:test"
|
|
2
|
-
import { strict as assert } from "node:assert"
|
|
3
|
-
import OutputAdapter from "./OutputAdapter.js"
|
|
4
|
-
|
|
5
|
-
describe("OutputAdapter", () => {
|
|
6
|
-
it("should create instance", () => {
|
|
7
|
-
const adapter = new OutputAdapter()
|
|
8
|
-
assert.ok(adapter instanceof OutputAdapter)
|
|
9
|
-
})
|
|
10
|
-
|
|
11
|
-
it("should throw error on render method", () => {
|
|
12
|
-
const adapter = new OutputAdapter()
|
|
13
|
-
assert.throws(() => adapter.render(), {
|
|
14
|
-
message: "render() must be implemented by subclass"
|
|
15
|
-
})
|
|
16
|
-
})
|
|
17
|
-
|
|
18
|
-
it("should call render on progress", () => {
|
|
19
|
-
const adapter = new OutputAdapter()
|
|
20
|
-
let rendered = false
|
|
21
|
-
|
|
22
|
-
// Override render for test
|
|
23
|
-
adapter.render = () => {
|
|
24
|
-
rendered = true
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
adapter.progress(0.5)
|
|
28
|
-
assert.ok(rendered)
|
|
29
|
-
})
|
|
30
|
-
|
|
31
|
-
it("should stop without error", () => {
|
|
32
|
-
const adapter = new OutputAdapter()
|
|
33
|
-
assert.doesNotThrow(() => adapter.stop())
|
|
34
|
-
})
|
|
35
|
-
})
|
package/src/core/Stream.test.js
DELETED
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
import { describe, it } from "node:test"
|
|
2
|
-
import { strict as assert } from "node:assert"
|
|
3
|
-
import UIStream from "./Stream.js"
|
|
4
|
-
import StreamEntry from "./StreamEntry.js"
|
|
5
|
-
|
|
6
|
-
describe("UIStream", () => {
|
|
7
|
-
it("should create processor generator", async () => {
|
|
8
|
-
const controller = new AbortController()
|
|
9
|
-
const processorFn = () => Promise.resolve("test result")
|
|
10
|
-
const generator = UIStream.createProcessor(controller.signal, processorFn)
|
|
11
|
-
|
|
12
|
-
assert.equal(typeof generator, "function")
|
|
13
|
-
|
|
14
|
-
for await (const item of generator()) {
|
|
15
|
-
assert.equal(item, "test result")
|
|
16
|
-
break
|
|
17
|
-
}
|
|
18
|
-
})
|
|
19
|
-
|
|
20
|
-
it("should handle aborted signal", async () => {
|
|
21
|
-
const controller = new AbortController()
|
|
22
|
-
controller.abort()
|
|
23
|
-
|
|
24
|
-
const processorFn = () => Promise.resolve(StreamEntry.from({ value: "Hello", done: true }))
|
|
25
|
-
const generator = UIStream.createProcessor(controller.signal, processorFn)
|
|
26
|
-
|
|
27
|
-
for await (const item of generator()) {
|
|
28
|
-
assert.deepEqual(item, StreamEntry.from({ done: true, value: "Hello" }))
|
|
29
|
-
break
|
|
30
|
-
}
|
|
31
|
-
})
|
|
32
|
-
|
|
33
|
-
it("should process stream with callbacks", async () => {
|
|
34
|
-
const controller = new AbortController()
|
|
35
|
-
let progressCalled = false
|
|
36
|
-
let completeCalled = false
|
|
37
|
-
|
|
38
|
-
const generatorFn = async function* () {
|
|
39
|
-
yield StreamEntry.from({ progress: 0.5 })
|
|
40
|
-
yield StreamEntry.from({ done: true })
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
const onProgress = () => { progressCalled = true }
|
|
44
|
-
const onComplete = () => { completeCalled = true }
|
|
45
|
-
|
|
46
|
-
await UIStream.process(
|
|
47
|
-
controller.signal,
|
|
48
|
-
generatorFn,
|
|
49
|
-
onProgress,
|
|
50
|
-
null,
|
|
51
|
-
onComplete
|
|
52
|
-
)
|
|
53
|
-
|
|
54
|
-
assert.ok(progressCalled)
|
|
55
|
-
assert.ok(completeCalled)
|
|
56
|
-
})
|
|
57
|
-
|
|
58
|
-
it("should handle errors in stream processing", async () => {
|
|
59
|
-
const controller = new AbortController()
|
|
60
|
-
let errorCalled = false
|
|
61
|
-
|
|
62
|
-
const generatorFn = async function* () {
|
|
63
|
-
yield { error: "test error" }
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
const onError = () => { errorCalled = true }
|
|
67
|
-
|
|
68
|
-
await UIStream.process(
|
|
69
|
-
controller.signal,
|
|
70
|
-
generatorFn,
|
|
71
|
-
null,
|
|
72
|
-
onError,
|
|
73
|
-
null
|
|
74
|
-
)
|
|
75
|
-
|
|
76
|
-
assert.ok(errorCalled)
|
|
77
|
-
})
|
|
78
|
-
})
|
package/src/index.test.js
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { describe, it } from "node:test"
|
|
2
|
-
import { strict as assert } from "node:assert"
|
|
3
|
-
import { App } from "@nan0web/ui"
|
|
4
|
-
|
|
5
|
-
describe("App", () => {
|
|
6
|
-
it("should be defined", () => {
|
|
7
|
-
assert.ok(App)
|
|
8
|
-
assert.ok(App.Core)
|
|
9
|
-
assert.ok(App.User)
|
|
10
|
-
assert.ok(App.Command)
|
|
11
|
-
assert.ok(App.Scenario)
|
|
12
|
-
assert.ok(App.UI)
|
|
13
|
-
})
|
|
14
|
-
})
|
package/stories/App/AppView.js
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import View from "../../src/View/View.js"
|
|
2
|
-
import Welcome from "../../src/Component/Welcome/index.js"
|
|
3
|
-
import Process from "../../src/Component/Process/index.js"
|
|
4
|
-
import AppRenderOptions from "./RenderOptions.js"
|
|
5
|
-
|
|
6
|
-
class AppView extends View {
|
|
7
|
-
static RenderOptions = AppRenderOptions
|
|
8
|
-
constructor(props = {}) {
|
|
9
|
-
super(props)
|
|
10
|
-
this.register(Welcome)
|
|
11
|
-
this.register(Process)
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export default AppView
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest"
|
|
2
|
-
import AppView from "./AppView.js"
|
|
3
|
-
import Frame from "../../src/Frame/Frame.js"
|
|
4
|
-
|
|
5
|
-
describe("AppView", () => {
|
|
6
|
-
it("should create an instance", () => {
|
|
7
|
-
const view = new AppView()
|
|
8
|
-
expect(view).toBeInstanceOf(AppView)
|
|
9
|
-
})
|
|
10
|
-
it("should render a welcome component", () => {
|
|
11
|
-
const view = new AppView()
|
|
12
|
-
const welcome = view.render("Welcome")({ user: { name: "NaN•" } })
|
|
13
|
-
expect(welcome).toBeInstanceOf(Frame)
|
|
14
|
-
expect(view.stdout.stream).toEqual([
|
|
15
|
-
[
|
|
16
|
-
"Welcome NaN•!",
|
|
17
|
-
"What can we do today great?",
|
|
18
|
-
"",
|
|
19
|
-
].join("\n")
|
|
20
|
-
])
|
|
21
|
-
})
|
|
22
|
-
})
|