@yuneta/gobj-ui 7.25.14 → 7.25.15
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 +41 -0
- package/dist/gobj-ui.cjs.js +184 -53
- package/dist/gobj-ui.es.js +184 -53
- package/package.json +1 -1
- package/src/c_g6_nodes_tree.js +42 -11
- package/src/c_yui_schema_editor.js +104 -37
- package/src/c_yui_treedb_graph.js +53 -6
- package/src/c_yui_treedb_topic_with_form.js +10 -10
- package/src/c_yui_treedb_topics.js +7 -7
- package/src/form_busy.js +2 -3
- package/src/form_time_value.js +1 -1
- package/src/form_time_value.test.js +1 -1
- package/src/form_writes_in_flight.js +1 -2
- package/src/g6_nodes_tree_fsm.test.js +33 -0
- package/src/graph_save_plan.js +28 -0
- package/src/graph_save_plan.test.js +36 -2
- package/src/host_drafts.js +2 -3
- package/src/schema_editor_drafts.wiring.test.js +1 -1
- package/src/schema_editor_reload.wiring.test.js +178 -46
- package/src/schema_editor_transport.wiring.test.js +2 -2
- package/src/schema_version_bump.test.js +1 -1
- package/src/shell_modals.js +2 -3
- package/src/shell_toast_once.test.js +10 -10
- package/src/table_cell_text.js +1 -1
- package/src/treedb_graph_writes.wiring.test.js +208 -0
- package/src/treedb_topics_transport.wiring.test.js +1 -1
- package/src/yui_row_search.test.js +1 -1
- package/src/yui_table_select.test.js +1 -1
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
/***********************************************************************
|
|
2
|
+
* treedb_graph_writes.wiring.test.js
|
|
3
|
+
*
|
|
4
|
+
* C_YUI_TREEDB_GRAPH and the writes of `__graphs__`, the topic
|
|
5
|
+
* where the graph keeps its arrangement. The engine
|
|
6
|
+
* (C_G6_NODES_TREE) takes such a write for granted before it
|
|
7
|
+
* leaves: nothing answers it when it lands. So the host must say
|
|
8
|
+
* when one did NOT land -- refused by the backend, refused on the
|
|
9
|
+
* way out, or never sent for want of a session. Without that, the
|
|
10
|
+
* engine believed the backend held the arrangement, and the next
|
|
11
|
+
* Save had nothing to write.
|
|
12
|
+
*
|
|
13
|
+
* Driven through the real host on a document double, with a fake
|
|
14
|
+
* engine that records what it is told and a fake transport whose
|
|
15
|
+
* state is the one the host reads.
|
|
16
|
+
*
|
|
17
|
+
* Copyright (c) 2026, ArtGins.
|
|
18
|
+
* All Rights Reserved.
|
|
19
|
+
***********************************************************************/
|
|
20
|
+
import {describe, test, expect, beforeAll, beforeEach} from "vitest";
|
|
21
|
+
import {install_dom_double} from "../test/dom_double.js";
|
|
22
|
+
|
|
23
|
+
install_dom_double();
|
|
24
|
+
|
|
25
|
+
/* The host watches its container's size; the document double has no
|
|
26
|
+
* layout to watch. */
|
|
27
|
+
if(!globalThis.ResizeObserver) {
|
|
28
|
+
globalThis.ResizeObserver = class {
|
|
29
|
+
observe() {}
|
|
30
|
+
unobserve() {}
|
|
31
|
+
disconnect() {}
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const {
|
|
36
|
+
SDATA, SDATA_END, data_type_t,
|
|
37
|
+
gclass_create,
|
|
38
|
+
gobj_start_up, gobj_create_yuno, gobj_create, gobj_create_service,
|
|
39
|
+
gobj_start, gobj_send_event, gobj_publish_event,
|
|
40
|
+
gobj_read_pointer_attr, gobj_subscribe_event,
|
|
41
|
+
gobj_change_state, gobj_current_state,
|
|
42
|
+
set_log_callback,
|
|
43
|
+
} = await import("@yuneta/gobj-js");
|
|
44
|
+
|
|
45
|
+
const logged = [];
|
|
46
|
+
const commands = []; /* what the fake transport was asked */
|
|
47
|
+
const told = []; /* what the fake engine was told */
|
|
48
|
+
let refuse_on_the_way = false;
|
|
49
|
+
|
|
50
|
+
function remote_command_parser(gobj, command, kw, src)
|
|
51
|
+
{
|
|
52
|
+
if(refuse_on_the_way) {
|
|
53
|
+
return `cannot route '${command}'`;
|
|
54
|
+
}
|
|
55
|
+
commands.push({command, kw, src});
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
let yuno = null;
|
|
60
|
+
|
|
61
|
+
beforeAll(async () => {
|
|
62
|
+
gobj_start_up(null, null, null, null, null, null, null);
|
|
63
|
+
set_log_callback((level, msg) => {
|
|
64
|
+
logged.push({level: String(level), msg: String(msg)});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
gclass_create("C_TEST_HOST", [], [["ST_IDLE", []]], {}, 0, [SDATA_END()], {}, 0, 0, 0, 0);
|
|
68
|
+
gclass_create(
|
|
69
|
+
"C_TEST_REMOTE",
|
|
70
|
+
[],
|
|
71
|
+
[["ST_SESSION", []], ["ST_DISCONNECTED", []]],
|
|
72
|
+
{mt_command_parser: remote_command_parser},
|
|
73
|
+
0, [SDATA_END()], {}, 0, 0, 0, 0
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
/* The engine, registered BEFORE the host so the host uses it. It
|
|
77
|
+
* publishes what the real one publishes on a Save, and records every
|
|
78
|
+
* event it is sent. */
|
|
79
|
+
const record = (gobj, event, kw) => {
|
|
80
|
+
told.push({event, kw});
|
|
81
|
+
return 0;
|
|
82
|
+
};
|
|
83
|
+
const ENGINE_EVENTS = [
|
|
84
|
+
"EV_DESCS", "EV_CLEAR_DATA", "EV_LOAD_DATA", "EV_NODE_CREATED",
|
|
85
|
+
"EV_NODE_UPDATED", "EV_NODE_DELETED", "EV_GRAPHS_WRITE_REFUSED",
|
|
86
|
+
"EV_SHOW", "EV_HIDE", "EV_RESIZE", "EV_THEME", "EV_LANGUAGE_CHANGED",
|
|
87
|
+
"EV_SET_OPERATION_MODE", "EV_SET_LAYOUT"
|
|
88
|
+
];
|
|
89
|
+
gclass_create(
|
|
90
|
+
"C_G6_NODES_TREE",
|
|
91
|
+
ENGINE_EVENTS.map((e) => [e, 0]).concat([["EV_UPDATE_NODE", 2]]),
|
|
92
|
+
[["ST_IDLE", ENGINE_EVENTS.map((e) => [e, record, null])]],
|
|
93
|
+
{
|
|
94
|
+
mt_create: (gobj) => {
|
|
95
|
+
const subscriber = gobj_read_pointer_attr(gobj, "subscriber");
|
|
96
|
+
if(subscriber) {
|
|
97
|
+
gobj_subscribe_event(gobj, null, {}, subscriber);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
0,
|
|
102
|
+
[
|
|
103
|
+
SDATA(data_type_t.DTP_POINTER, "subscriber", 0, null, ""),
|
|
104
|
+
SDATA(data_type_t.DTP_POINTER, "$container", 0, null, ""),
|
|
105
|
+
SDATA(data_type_t.DTP_POINTER, "gobj_remote_yuno", 0, null, ""),
|
|
106
|
+
SDATA(data_type_t.DTP_STRING, "treedb_name", 0, "", ""),
|
|
107
|
+
SDATA(data_type_t.DTP_JSON, "layout_names", 0, "[]", ""),
|
|
108
|
+
SDATA(data_type_t.DTP_STRING, "layout", 0, "", ""),
|
|
109
|
+
SDATA_END()
|
|
110
|
+
],
|
|
111
|
+
{}, 0, 0, 0, 0
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
const {register_c_yui_treedb_graph} = await import("./c_yui_treedb_graph.js");
|
|
115
|
+
register_c_yui_treedb_graph();
|
|
116
|
+
|
|
117
|
+
yuno = gobj_create_yuno("graph_writes_yuno", "C_TEST_HOST", {});
|
|
118
|
+
gobj_start(yuno);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
beforeEach(() => {
|
|
122
|
+
logged.length = 0;
|
|
123
|
+
commands.length = 0;
|
|
124
|
+
told.length = 0;
|
|
125
|
+
refuse_on_the_way = false;
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
function build(name)
|
|
129
|
+
{
|
|
130
|
+
const remote = gobj_create_service(`${name}_remote`, "C_TEST_REMOTE", {}, yuno);
|
|
131
|
+
gobj_change_state(remote, "ST_SESSION");
|
|
132
|
+
const host = gobj_create_service(`${name}_graph`, "C_YUI_TREEDB_GRAPH", {
|
|
133
|
+
gobj_remote_yuno: remote,
|
|
134
|
+
treedb_name: "treedb_test"
|
|
135
|
+
}, yuno);
|
|
136
|
+
const engine = host.priv.gobj_nodes_tree;
|
|
137
|
+
expect(engine).toBeTruthy();
|
|
138
|
+
return {remote, host, engine};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/* What the engine publishes on a Save of one topic's arrangement. */
|
|
142
|
+
function save_arrangement(engine, topic)
|
|
143
|
+
{
|
|
144
|
+
gobj_publish_event(engine, "EV_UPDATE_NODE", {
|
|
145
|
+
treedb_name: "treedb_test",
|
|
146
|
+
topic_name: "__graphs__",
|
|
147
|
+
record: {id: topic, topic: topic, active: true, properties: {nodes: {}}},
|
|
148
|
+
options: {list_dict: true, autolink: false, create: true}
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function answer(host, remote, request, result, comment)
|
|
153
|
+
{
|
|
154
|
+
gobj_send_event(host, "EV_MT_COMMAND_ANSWER", {
|
|
155
|
+
result: result,
|
|
156
|
+
comment: comment || "",
|
|
157
|
+
data: null,
|
|
158
|
+
__md_iev__: {command_stack: [{command: request.command, kw: request.kw}]}
|
|
159
|
+
}, remote);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function refused()
|
|
163
|
+
{
|
|
164
|
+
return told.filter((t) => t.event === "EV_GRAPHS_WRITE_REFUSED").map((t) => t.kw.topic);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
describe("a __graphs__ write that does not land is said to the engine", () => {
|
|
168
|
+
|
|
169
|
+
test("refused by the backend", () => {
|
|
170
|
+
const {remote, host, engine} = build("g1");
|
|
171
|
+
save_arrangement(engine, "yunos");
|
|
172
|
+
expect(commands.length).toBe(1);
|
|
173
|
+
answer(host, remote, commands[0], -1, "not authorized");
|
|
174
|
+
expect(refused()).toEqual(["yunos"]);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
test("landed: nothing is said", () => {
|
|
178
|
+
const {remote, host, engine} = build("g2");
|
|
179
|
+
save_arrangement(engine, "yunos");
|
|
180
|
+
answer(host, remote, commands[0], 0);
|
|
181
|
+
expect(refused()).toEqual([]);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
test("with no session: not sent, and said", () => {
|
|
185
|
+
const {remote, host, engine} = build("g3");
|
|
186
|
+
gobj_change_state(remote, "ST_DISCONNECTED");
|
|
187
|
+
save_arrangement(engine, "realms");
|
|
188
|
+
expect(commands).toEqual([]);
|
|
189
|
+
expect(refused()).toEqual(["realms"]);
|
|
190
|
+
expect(logged.some((l) => /not sent/.test(l.msg))).toBe(true);
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
test("refused on the way out by the transport", () => {
|
|
194
|
+
const {remote, host, engine} = build("g4");
|
|
195
|
+
refuse_on_the_way = true;
|
|
196
|
+
save_arrangement(engine, "binaries");
|
|
197
|
+
expect(refused()).toEqual(["binaries"]);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
test("a refused write of another topic says nothing to the engine", () => {
|
|
201
|
+
const {remote, host, engine} = build("g5");
|
|
202
|
+
gobj_send_event(host, "EV_UPDATE_NODE", {
|
|
203
|
+
topic_name: "yunos", record: {id: "1", x: 1}
|
|
204
|
+
}, engine);
|
|
205
|
+
answer(host, remote, commands[0], -1, "refused");
|
|
206
|
+
expect(refused()).toEqual([]);
|
|
207
|
+
});
|
|
208
|
+
});
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* through the real gclasses (C_YUI_SHELL, C_YUI_TREEDB_TOPICS) on a
|
|
6
6
|
* document double. The helper (form_writes_in_flight.js) was tested
|
|
7
7
|
* and right; what failed was that two hosts never called the action
|
|
8
|
-
* that uses it
|
|
8
|
+
* that uses it, which no helper test
|
|
9
9
|
* can see.
|
|
10
10
|
*
|
|
11
11
|
* Copyright (c) 2026, ArtGins.
|
|
@@ -115,7 +115,7 @@ describe("is_hook_size", () => {
|
|
|
115
115
|
* The children of a hook as the NODE EVENTS deliver them: `{id,
|
|
116
116
|
* topic_name}`, with no hook_name. Not taken for a reference, their
|
|
117
117
|
* topic_name -- the same word on every row -- was a wildcard for every
|
|
118
|
-
* row that had received an UPDATED
|
|
118
|
+
* row that had received an UPDATED.
|
|
119
119
|
*/
|
|
120
120
|
describe("a hook's children as the node events deliver them", () => {
|
|
121
121
|
const row = {id: "w1", devices: [{id: "d7", topic_name: "devices"}]};
|
|
@@ -59,7 +59,7 @@ describe("reading and dropping the selection", () => {
|
|
|
59
59
|
* A table as the delete of the treedb topic table meets it AFTER a
|
|
60
60
|
* confirmation: rows came and went while the dialog was open. A position
|
|
61
61
|
* names whatever row sits there now; an id names the row the question named
|
|
62
|
-
* (the regression of d60ec78
|
|
62
|
+
* (the regression of d60ec78).
|
|
63
63
|
*/
|
|
64
64
|
function fake_table(rows, index)
|
|
65
65
|
{
|