@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
|
@@ -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
|
-
})
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import RenderOptions from "../../src/View/RenderOptions.js"
|
|
2
|
-
|
|
3
|
-
class AppRenderOptions extends RenderOptions {
|
|
4
|
-
static DEFAULTS = {
|
|
5
|
-
...RenderOptions.DEFAULTS,
|
|
6
|
-
resizeToView: true,
|
|
7
|
-
}
|
|
8
|
-
static from(props = {}) {
|
|
9
|
-
if (props instanceof AppRenderOptions) return props
|
|
10
|
-
return new this(props)
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export default AppRenderOptions
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @todo write tests for the interfaces StdIn, StdOut, Locale, Frame, FrameProps and View in NodeJS with vitest.
|
|
3
|
-
* Follow the actor / scenario model.
|
|
4
|
-
* Basic interfaces must include:
|
|
5
|
-
* - read text from stdin
|
|
6
|
-
* - write text to stdout
|
|
7
|
-
* - read an option from a selectbox with the bordered frame
|
|
8
|
-
* - write result to stdout
|
|
9
|
-
*
|
|
10
|
-
* Frames and Views must follow the blessed interface as for flexibility.
|
|
11
|
-
*/
|
|
12
|
-
import { describe, it, expect } from "vitest"
|
|
13
|
-
|
|
14
|
-
describe("Interface", () => {
|
|
15
|
-
it("should read text from stdin", () => {
|
|
16
|
-
expect("").toBe("")
|
|
17
|
-
})
|
|
18
|
-
it("should write text to stdout", () => {
|
|
19
|
-
expect("").toBe("")
|
|
20
|
-
})
|
|
21
|
-
it("should read an option from a selectbox with the bordered frame", () => {
|
|
22
|
-
expect("").toBe("")
|
|
23
|
-
})
|
|
24
|
-
it("should write result to stdout with the bordered frame", () => {
|
|
25
|
-
expect("").toBe("")
|
|
26
|
-
})
|
|
27
|
-
})
|
package/system.md
DELETED
|
@@ -1,187 +0,0 @@
|
|
|
1
|
-
# System intructions `system.md` / NaN•Web UI
|
|
2
|
-
|
|
3
|
-
Vanilla javascript UI interfaces with no platform dependencies.
|
|
4
|
-
Must work for nodejs, browser and other compilers that supports ESM.
|
|
5
|
-
|
|
6
|
-
## Requirements
|
|
7
|
-
|
|
8
|
-
### 1. Javascript
|
|
9
|
-
|
|
10
|
-
1.1. Pure vanilla js only.
|
|
11
|
-
1.2. Platform abstract.
|
|
12
|
-
1.3. Minimum dependency as possible.
|
|
13
|
-
1.4. Classes
|
|
14
|
-
1.4.1. Order of the class elements:
|
|
15
|
-
- static properties
|
|
16
|
-
- properties
|
|
17
|
-
- constructor
|
|
18
|
-
- getters
|
|
19
|
-
- setters
|
|
20
|
-
- base functions
|
|
21
|
-
- async functions
|
|
22
|
-
- static base functions
|
|
23
|
-
- static async functions
|
|
24
|
-
|
|
25
|
-
1.5. Views, every must:
|
|
26
|
-
1.5.1. Have a separate file and test file `View/AppView.js` and `View/AppView.test.js`.
|
|
27
|
-
1.6. Components, every must:
|
|
28
|
-
1.6.1. Have a separate directory in `View/Component/*` or registered from any other directory with files:
|
|
29
|
-
- `Component.js`
|
|
30
|
-
- `Component.test.js`
|
|
31
|
-
- `ComponentInput.js`
|
|
32
|
-
- `index.js`
|
|
33
|
-
```js
|
|
34
|
-
export default Component
|
|
35
|
-
export { ComponentInput }
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
### 2. Tests
|
|
39
|
-
|
|
40
|
-
Every model, component, class must have their own tests file to cover their functionality in isolated environment.
|
|
41
|
-
|
|
42
|
-
#### node:test
|
|
43
|
-
|
|
44
|
-
For the core (base), cgi and cli interfaces, and web interfaces non `.jsx` components `node:test` with `node:assert/strict` must be used for tests.
|
|
45
|
-
|
|
46
|
-
#### vitest
|
|
47
|
-
|
|
48
|
-
For `React` apps and any that uses `.jsx` components `vitest` must be used for tests.
|
|
49
|
-
|
|
50
|
-
## Interfaces
|
|
51
|
-
|
|
52
|
-
1. `base` - Самий базовий інтерфейс, який має описувати всі доступні для інтерфейсів функції + тести `node:test`.
|
|
53
|
-
1. `cli` - Console line application на основі команд через `argv[]` і вивід на екран за допомогою `@nan0web/log`.
|
|
54
|
-
1. `api` - API який працює постійно для надання даних для `web`, `mobile` та інших інтерфейсів.
|
|
55
|
-
1. `cgi` - Console graphic interface з віконцями і кнопками за допомогою `blessed`.
|
|
56
|
-
1. `web` - Веб інтерфейс з `Web.customElements`, `Lit`, або `React`.
|
|
57
|
-
1. `mobile` - Мобільний інтерфейс з `Ionic` або `Swift` і `Java`.
|
|
58
|
-
1. `desktop` - Веб інтерфейс у обкладинці `Electron` або `Tauri`.
|
|
59
|
-
1. `chat` - Чат інтерфейс `@nan0web/chat-ui`.
|
|
60
|
-
1. `audio` - Мовний (голосовий) інтерфейс `@nan0web/audio-ui`.
|
|
61
|
-
|
|
62
|
-
Базовий інтерфейс описує всі взаємодії і можливі функції.
|
|
63
|
-
Всі інші інтерфейси мають розширювати базовий.
|
|
64
|
-
|
|
65
|
-
Інтерфейси клієнти, у випадку коли є окремий `api` сервер, можуть розширювати свій `ClientUI`.
|
|
66
|
-
|
|
67
|
-
Приклад успадкування:
|
|
68
|
-
```js
|
|
69
|
-
import UI from "@nan0web/ui"
|
|
70
|
-
|
|
71
|
-
class BaseUI extends UI {}
|
|
72
|
-
|
|
73
|
-
class CliUI extends BaseUI {}
|
|
74
|
-
|
|
75
|
-
class APIUI extends CliUI {}
|
|
76
|
-
|
|
77
|
-
class CgiUI extends CliUI {}
|
|
78
|
-
|
|
79
|
-
class WebUI extends BaseUI {}
|
|
80
|
-
|
|
81
|
-
class MobileUI extends BaseUI {}
|
|
82
|
-
|
|
83
|
-
class DesktopUI extends WebUI {}
|
|
84
|
-
|
|
85
|
-
class ChatUI extends BaseUI {}
|
|
86
|
-
|
|
87
|
-
class AudioUI extends ChatUI {}
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
### Views
|
|
91
|
-
|
|
92
|
-
#### Widgets
|
|
93
|
-
|
|
94
|
-
Widget is a control alement including a view with and ask() ability to input data.
|
|
95
|
-
|
|
96
|
-
#### Output
|
|
97
|
-
|
|
98
|
-
Output data is passed to the view but also in specific format:
|
|
99
|
-
```js
|
|
100
|
-
function UserDocumentsView(input = {}) {
|
|
101
|
-
input = UserDocumentsInput.from(input)
|
|
102
|
-
if (empty(input)) {
|
|
103
|
-
throw new Error("Input data required in a format off UserDocumentsInput")
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
return Frame.from([
|
|
107
|
-
['Documents'],
|
|
108
|
-
...input.documents.map(d => d.name),
|
|
109
|
-
])
|
|
110
|
-
}
|
|
111
|
-
```
|
|
112
|
-
|
|
113
|
-
In this scenario I can easily define Input classes for the every View and Widget to be sure all the input pass, and easily require the proper input from the app before running a job.
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
#### Input
|
|
117
|
-
|
|
118
|
-
When application needs any data it requires.
|
|
119
|
-
|
|
120
|
-
The input might be a command line `node start.js -user.name root` or, if not provided, user input through the provided infterface (any of CLI, API, voice, mobile, table, desktop, web, etc.)
|
|
121
|
-
|
|
122
|
-
```js
|
|
123
|
-
class App {
|
|
124
|
-
async requireUser() {
|
|
125
|
-
const user = await this.view.ask("User")()
|
|
126
|
-
if (user instanceof User) return user
|
|
127
|
-
throw new TypeError("User is required to continue")
|
|
128
|
-
}
|
|
129
|
-
async run() {
|
|
130
|
-
const user = await this.requireUser()
|
|
131
|
-
this.view.render("Welcome")({ user })
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
```
|
|
135
|
-
|
|
136
|
-
## Cross platform
|
|
137
|
-
|
|
138
|
-
Very important that I can extend basic UI with different apps, for instance:
|
|
139
|
-
```jsx
|
|
140
|
-
// apps/web/src/components/User/DocumentsView.jsx
|
|
141
|
-
import React from "react"
|
|
142
|
-
/** the very basic component **/
|
|
143
|
-
import UserDocumentsInput from "src/components/User/DocumentsInput.js"
|
|
144
|
-
|
|
145
|
-
function UserDocumentsView(input = {}) {
|
|
146
|
-
input = UserDocumentsInput.from(input)
|
|
147
|
-
if (empty(input)) {
|
|
148
|
-
throw new Error("Input data required in a format off UserDocumentsInput")
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
return <div>
|
|
152
|
-
<h3>Documents</h3>
|
|
153
|
-
<ul>
|
|
154
|
-
{input.documents.map((d, i) => <li key={i}>{d.name}</li>)}
|
|
155
|
-
</ul>
|
|
156
|
-
</div>
|
|
157
|
-
}
|
|
158
|
-
```
|
|
159
|
-
|
|
160
|
-
### Apps locations
|
|
161
|
-
|
|
162
|
-
#### Core application
|
|
163
|
-
|
|
164
|
-
Core application s with the minimum stdin, stdout
|
|
165
|
-
- `src/*`
|
|
166
|
-
- `src/App.js`
|
|
167
|
-
- `bin/start.js`
|
|
168
|
-
- `package.json`
|
|
169
|
-
|
|
170
|
-
Core application might have own resository, for instance `@nan0web/app`.
|
|
171
|
-
|
|
172
|
-
#### CLI application
|
|
173
|
-
|
|
174
|
-
CLI application can utilize `blessed` or other CLI library for rich UI experience, compared to Core.
|
|
175
|
-
|
|
176
|
-
- `apps/cli/src/*`
|
|
177
|
-
- `apps/cli/bin/start.js`
|
|
178
|
-
- `apps/cli/package.json`
|
|
179
|
-
|
|
180
|
-
#### Web application
|
|
181
|
-
|
|
182
|
-
Web applications can utilize `react` or `lit` or other Web UI library for rich UI experience.
|
|
183
|
-
Most desired is `lit` to avoid using typescript or jsx.
|
|
184
|
-
|
|
185
|
-
- `apps/web/src/*`
|
|
186
|
-
- `apps/web/bin/start.js`
|
|
187
|
-
- `apps/web/package.json`
|