@yuneta/gobj-ui 7.25.7 → 7.25.9
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 +60 -1
- package/dist/gobj-ui.cjs.js +355 -101
- package/dist/gobj-ui.es.js +355 -101
- package/package.json +1 -1
- package/src/c_yui_schema_editor.js +384 -77
- package/src/schema_editor_reload.wiring.test.js +787 -0
- package/src/shell_modals.js +58 -33
- package/src/shell_toast_once.test.js +106 -3
|
@@ -0,0 +1,787 @@
|
|
|
1
|
+
/***********************************************************************
|
|
2
|
+
* schema_editor_reload.wiring.test.js
|
|
3
|
+
*
|
|
4
|
+
* C_YUI_SCHEMA_EDITOR while it RELOADS (the third independent
|
|
5
|
+
* review, 2026-09-23). A reload entered ST_LOADING and drew
|
|
6
|
+
* nothing: the screen it replaced stayed up and clickable, and
|
|
7
|
+
* ST_LOADING declares none of its actions, so a click on a card,
|
|
8
|
+
* on Back, on the drawing -- or the Save of a column form left
|
|
9
|
+
* open -- answered "Event NOT DEFINED in state ST_LOADING" and
|
|
10
|
+
* the click, or the edit, was lost.
|
|
11
|
+
*
|
|
12
|
+
* And the lows of the same review: a position left over from a
|
|
13
|
+
* load that could not leave, a drop during a WRITE that dropped
|
|
14
|
+
* the host's position (a drop during a load applies it), the
|
|
15
|
+
* drawing of a treedb that is gone drawn over the old screen, and
|
|
16
|
+
* the reload of a late write that forgot this session's marks and
|
|
17
|
+
* never told the host a write had landed.
|
|
18
|
+
*
|
|
19
|
+
* The fourth independent review (same day) found what a reload
|
|
20
|
+
* still left behind: a dialog opened on the model it replaced
|
|
21
|
+
* (its Save wrote the old record over the newer one), a load
|
|
22
|
+
* refused IN session that blanked the model, a late write whose
|
|
23
|
+
* list_dict fkey marked nothing, a write answered with no record
|
|
24
|
+
* that owed a second reload and dropped the rest of its queue in
|
|
25
|
+
* silence, and a toolbar gated on the state instead of on the
|
|
26
|
+
* treedb being there.
|
|
27
|
+
*
|
|
28
|
+
* Driven through the FSM on a document double, with a fake
|
|
29
|
+
* transport whose state is the one the library reads.
|
|
30
|
+
*
|
|
31
|
+
* Copyright (c) 2026, ArtGins.
|
|
32
|
+
* All Rights Reserved.
|
|
33
|
+
***********************************************************************/
|
|
34
|
+
import {describe, test, expect, beforeAll, beforeEach, vi} from "vitest";
|
|
35
|
+
import {install_dom_double} from "../test/dom_double.js";
|
|
36
|
+
|
|
37
|
+
install_dom_double();
|
|
38
|
+
|
|
39
|
+
/* What the editor SAYS, and the dialogs it opens (their content is
|
|
40
|
+
* clicked like the operator would). */
|
|
41
|
+
const shown = [];
|
|
42
|
+
const modals = [];
|
|
43
|
+
vi.mock("./shell_modals.js", () => ({
|
|
44
|
+
yui_shell_show_error: (shell, message) => {
|
|
45
|
+
shown.push(message);
|
|
46
|
+
return {close() {}};
|
|
47
|
+
},
|
|
48
|
+
yui_shell_show_info: () => ({close() {}}),
|
|
49
|
+
yui_shell_show_modal: (shell, $content, opts) => {
|
|
50
|
+
const modal = {$content: $content, opts: opts, closed: false};
|
|
51
|
+
modals.push(modal);
|
|
52
|
+
return {close() {
|
|
53
|
+
modal.closed = true;
|
|
54
|
+
}};
|
|
55
|
+
},
|
|
56
|
+
yui_shell_confirm_danger: () => new Promise(() => {}),
|
|
57
|
+
}));
|
|
58
|
+
|
|
59
|
+
const {
|
|
60
|
+
SDATA, SDATA_END, data_type_t, event_flag_t,
|
|
61
|
+
gclass_create,
|
|
62
|
+
gobj_read_pointer_attr, gobj_write_attr, gobj_parent,
|
|
63
|
+
gobj_subscribe_event, gobj_publish_event,
|
|
64
|
+
gobj_start_up, gobj_create_yuno, gobj_create, gobj_create_service,
|
|
65
|
+
gobj_start, gobj_send_event, gobj_read_attr,
|
|
66
|
+
gobj_change_state, gobj_current_state,
|
|
67
|
+
set_log_callback,
|
|
68
|
+
} = await import("@yuneta/gobj-js");
|
|
69
|
+
const {register_c_yui_schema_editor} = await import("./c_yui_schema_editor.js");
|
|
70
|
+
|
|
71
|
+
const logged = [];
|
|
72
|
+
const commands = [];
|
|
73
|
+
const published = [];
|
|
74
|
+
|
|
75
|
+
const RECORDS = {
|
|
76
|
+
treedbs: [{id: "db", schema_version: 4}],
|
|
77
|
+
topics: [
|
|
78
|
+
{id: "db.users", value: "users", order: 1, pkey: "id", topic_version: 2,
|
|
79
|
+
treedbs: ["treedbs^db^topics"]},
|
|
80
|
+
],
|
|
81
|
+
cols: [
|
|
82
|
+
{id: "db.users.id", value: "id", order: 1, type: "string",
|
|
83
|
+
flag: ["persistent", "required"], topics: ["topics^db.users^cols"]},
|
|
84
|
+
],
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
function remote_command_parser(gobj, command, kw, src)
|
|
88
|
+
{
|
|
89
|
+
if(gobj_current_state(gobj) !== "ST_SESSION") {
|
|
90
|
+
return `${command}: not in session`;
|
|
91
|
+
}
|
|
92
|
+
commands.push({command, kw, src});
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
let yuno = null;
|
|
97
|
+
|
|
98
|
+
beforeAll(() => {
|
|
99
|
+
gobj_start_up(null, null, null, null, null, null, null);
|
|
100
|
+
set_log_callback((level, msg) => {
|
|
101
|
+
logged.push({level: String(level), msg: String(msg)});
|
|
102
|
+
});
|
|
103
|
+
gclass_create("C_TEST_HOST", [], [["ST_IDLE", []]], {}, 0, [SDATA_END()], {}, 0, 0, 0, 0);
|
|
104
|
+
const heard = (gobj, event) => {
|
|
105
|
+
published.push(event);
|
|
106
|
+
return 0;
|
|
107
|
+
};
|
|
108
|
+
gclass_create(
|
|
109
|
+
"C_TEST_EDITOR_HOST",
|
|
110
|
+
[["EV_POSITION_CHANGED", 0], ["EV_RECORD_WRITTEN", 0], ["EV_SCHEMA_CHECKED", 0]],
|
|
111
|
+
[["ST_IDLE", [
|
|
112
|
+
["EV_POSITION_CHANGED", heard, null],
|
|
113
|
+
["EV_RECORD_WRITTEN", heard, null],
|
|
114
|
+
["EV_SCHEMA_CHECKED", heard, null]
|
|
115
|
+
]]],
|
|
116
|
+
{}, 0, [SDATA_END()], {}, 0, 0, 0, 0
|
|
117
|
+
);
|
|
118
|
+
gclass_create(
|
|
119
|
+
"C_TEST_REMOTE",
|
|
120
|
+
[],
|
|
121
|
+
[["ST_DISCONNECTED", []], ["ST_SESSION", []]],
|
|
122
|
+
{mt_command_parser: remote_command_parser},
|
|
123
|
+
0, [SDATA_END()], {}, 0, 0, 0, 0
|
|
124
|
+
);
|
|
125
|
+
/* The drawing, as the editor hosts it: a child that publishes a
|
|
126
|
+
* click on one of its nodes. The real one draws with G6. */
|
|
127
|
+
gclass_create(
|
|
128
|
+
"C_YUI_TREEDB_SCHEMA",
|
|
129
|
+
[["EV_SHOW", 0], ["EV_NODE_CLICK", event_flag_t.EVF_OUTPUT_EVENT]],
|
|
130
|
+
[["ST_IDLE", [["EV_SHOW", () => 0, null]]]],
|
|
131
|
+
{
|
|
132
|
+
mt_create: (gobj) => {
|
|
133
|
+
const $c = document.createElement("div");
|
|
134
|
+
const $node = document.createElement("button");
|
|
135
|
+
$node.className = "FAKE_DIAGRAM_NODE";
|
|
136
|
+
$node.addEventListener("click", () => {
|
|
137
|
+
gobj_publish_event(gobj, "EV_NODE_CLICK", {topic: "users"});
|
|
138
|
+
});
|
|
139
|
+
$c.appendChild($node);
|
|
140
|
+
gobj_write_attr(gobj, "$container", $c);
|
|
141
|
+
let subscriber = gobj_read_pointer_attr(gobj, "subscriber");
|
|
142
|
+
if(!subscriber) {
|
|
143
|
+
subscriber = gobj_parent(gobj);
|
|
144
|
+
}
|
|
145
|
+
gobj_subscribe_event(gobj, null, {}, subscriber);
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
0,
|
|
149
|
+
[
|
|
150
|
+
SDATA(data_type_t.DTP_POINTER, "subscriber", 0, null, ""),
|
|
151
|
+
SDATA(data_type_t.DTP_JSON, "descs", 0, null, ""),
|
|
152
|
+
SDATA(data_type_t.DTP_STRING, "node_route", 0, "", ""),
|
|
153
|
+
SDATA(data_type_t.DTP_BOOLEAN, "with_node_click", 0, false, ""),
|
|
154
|
+
SDATA(data_type_t.DTP_BOOLEAN, "system", 0, false, ""),
|
|
155
|
+
SDATA(data_type_t.DTP_POINTER, "$container", 0, null, ""),
|
|
156
|
+
SDATA_END()
|
|
157
|
+
],
|
|
158
|
+
{}, 0, 0, 0, 0
|
|
159
|
+
);
|
|
160
|
+
register_c_yui_schema_editor();
|
|
161
|
+
yuno = gobj_create_yuno("reload_yuno", "C_TEST_HOST", {});
|
|
162
|
+
gobj_start(yuno);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
beforeEach(() => {
|
|
166
|
+
logged.length = 0;
|
|
167
|
+
commands.length = 0;
|
|
168
|
+
shown.length = 0;
|
|
169
|
+
modals.length = 0;
|
|
170
|
+
published.length = 0;
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
function take(command)
|
|
174
|
+
{
|
|
175
|
+
const out = commands.filter((c) => c.command === command);
|
|
176
|
+
for(const c of out) {
|
|
177
|
+
commands.splice(commands.indexOf(c), 1);
|
|
178
|
+
}
|
|
179
|
+
return out;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function answer(editor, remote, request, result, data, comment)
|
|
183
|
+
{
|
|
184
|
+
gobj_send_event(editor, "EV_MT_COMMAND_ANSWER", {
|
|
185
|
+
result: result,
|
|
186
|
+
comment: comment || "",
|
|
187
|
+
data: data,
|
|
188
|
+
__md_iev__: {command_stack: [{command: request.command, kw: request.kw}]}
|
|
189
|
+
}, remote);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function answer_the_load(editor, remote)
|
|
193
|
+
{
|
|
194
|
+
const asked = take("nodes");
|
|
195
|
+
for(const c of asked) {
|
|
196
|
+
answer(editor, remote, c, 0, JSON.parse(JSON.stringify(RECORDS[c.kw.topic_name])));
|
|
197
|
+
}
|
|
198
|
+
return asked.length;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function drop(editor, remote, host)
|
|
202
|
+
{
|
|
203
|
+
gobj_change_state(remote, "ST_DISCONNECTED");
|
|
204
|
+
gobj_send_event(editor, "EV_TRANSPORT_STATE", {connected: false}, host);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function reconnect(editor, remote, host)
|
|
208
|
+
{
|
|
209
|
+
gobj_change_state(remote, "ST_SESSION");
|
|
210
|
+
gobj_send_event(editor, "EV_TRANSPORT_STATE", {connected: true}, host);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function build(name, subpath)
|
|
214
|
+
{
|
|
215
|
+
const remote = gobj_create_service(`${name}_remote`, "C_TEST_REMOTE", {}, yuno);
|
|
216
|
+
gobj_change_state(remote, "ST_SESSION");
|
|
217
|
+
const host = gobj_create(`${name}_host`, "C_TEST_EDITOR_HOST", {}, yuno);
|
|
218
|
+
const editor = gobj_create(`${name}_editor`, "C_YUI_SCHEMA_EDITOR", {
|
|
219
|
+
gobj_remote_yuno: remote,
|
|
220
|
+
treedb_name: "treedb_system_schema",
|
|
221
|
+
base_route: "/schemas"
|
|
222
|
+
}, host);
|
|
223
|
+
gobj_start(editor);
|
|
224
|
+
gobj_send_event(editor, "EV_TRANSPORT_STATE", {connected: true}, host);
|
|
225
|
+
expect(answer_the_load(editor, remote)).toBe(3);
|
|
226
|
+
gobj_send_event(editor, "EV_SHOW", {subpath: subpath}, host);
|
|
227
|
+
return {editor, remote, host};
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function start_a_write(editor, host, name)
|
|
231
|
+
{
|
|
232
|
+
gobj_send_event(editor, "EV_SAVE_COLUMN", {
|
|
233
|
+
creating: true, topic: "users", values: {value: name || "name", type: "string"}
|
|
234
|
+
}, host);
|
|
235
|
+
expect(gobj_current_state(editor)).toBe("ST_SAVING");
|
|
236
|
+
const [write] = take("update-node");
|
|
237
|
+
expect(write).toBeTruthy();
|
|
238
|
+
return write;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function $in(editor, selector)
|
|
242
|
+
{
|
|
243
|
+
return gobj_read_attr(editor, "$container").querySelector(selector);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function $all(editor, selector)
|
|
247
|
+
{
|
|
248
|
+
return gobj_read_attr(editor, "$container").querySelectorAll(selector);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function is_disabled($b)
|
|
252
|
+
{
|
|
253
|
+
return $b.disabled === true || $b.getAttribute("disabled") != null;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/* What an operator can reach: every clickable thing of the view, pressed
|
|
257
|
+
* the way a browser presses it -- a disabled button does not click. */
|
|
258
|
+
function press_everything(editor)
|
|
259
|
+
{
|
|
260
|
+
let pressed = 0;
|
|
261
|
+
for(const $el of $all(editor, "button, [role=button]")) {
|
|
262
|
+
if(is_disabled($el)) {
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
265
|
+
$el.click();
|
|
266
|
+
pressed++;
|
|
267
|
+
}
|
|
268
|
+
return pressed;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function errors()
|
|
272
|
+
{
|
|
273
|
+
return logged.filter((l) => l.level === "error").map((l) => l.msg);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function not_defined()
|
|
277
|
+
{
|
|
278
|
+
return logged.filter((l) => /NOT DEFINED/i.test(l.msg)).map((l) => l.msg);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/* The load, answered with records other than the ones it started on:
|
|
282
|
+
* somebody else wrote the store meanwhile. */
|
|
283
|
+
function answer_the_load_with(editor, remote, records)
|
|
284
|
+
{
|
|
285
|
+
const asked = take("nodes");
|
|
286
|
+
for(const c of asked) {
|
|
287
|
+
answer(editor, remote, c, 0, JSON.parse(JSON.stringify(records[c.kw.topic_name])));
|
|
288
|
+
}
|
|
289
|
+
return asked.length;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function records_with(change)
|
|
293
|
+
{
|
|
294
|
+
const records = JSON.parse(JSON.stringify(RECORDS));
|
|
295
|
+
change(records);
|
|
296
|
+
return records;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/* What the editor says while a load is in the air, and when one lands
|
|
300
|
+
* under an open dialog. */
|
|
301
|
+
const WAIT = "the schemas are loading: wait for them";
|
|
302
|
+
const STALE = "the schemas were read again: open the dialog again";
|
|
303
|
+
const KEPT = "cannot read the schemas again: the previous ones stay";
|
|
304
|
+
const NOT_SENT = "the treedb did not describe a write back: the writes after it were not sent";
|
|
305
|
+
|
|
306
|
+
describe("the screen of a reload", () => {
|
|
307
|
+
|
|
308
|
+
test("Refresh on the columns: the old screen goes, the toolbar stops answering", () => {
|
|
309
|
+
const {editor, remote, host} = build("r1", "db/users");
|
|
310
|
+
expect($in(editor, ".SCHEMA_BODY .SCHEMA_COLUMNS")).toBeTruthy();
|
|
311
|
+
|
|
312
|
+
gobj_send_event(editor, "EV_REFRESH", {}, host);
|
|
313
|
+
expect(gobj_current_state(editor)).toBe("ST_LOADING");
|
|
314
|
+
expect($in(editor, ".SCHEMA_BODY").childNodes.length).toBe(0);
|
|
315
|
+
const $buttons = $all(editor, ".SCHEMA_TOOLBAR button");
|
|
316
|
+
expect($buttons.length).toBeGreaterThan(0);
|
|
317
|
+
for(const $b of $buttons) {
|
|
318
|
+
expect(is_disabled($b)).toBe(true);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
answer_the_load(editor, remote);
|
|
322
|
+
expect(gobj_current_state(editor)).toBe("ST_COLUMNS");
|
|
323
|
+
expect($in(editor, ".SCHEMA_BODY .SCHEMA_COLUMNS")).toBeTruthy();
|
|
324
|
+
expect(errors()).toEqual([]);
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
test.each([
|
|
328
|
+
["the treedbs", ""],
|
|
329
|
+
["the topics", "db"],
|
|
330
|
+
["the columns", "db/users"],
|
|
331
|
+
["the drawing", "db/diagram"],
|
|
332
|
+
])("everything clickable on %s during a reload: nothing NOT DEFINED", (label, subpath) => {
|
|
333
|
+
const {editor, remote, host} = build(`r2_${subpath.replace(/\W/g, "_")}`, subpath);
|
|
334
|
+
gobj_send_event(editor, "EV_REFRESH", {}, host);
|
|
335
|
+
press_everything(editor);
|
|
336
|
+
expect(gobj_current_state(editor)).toBe("ST_LOADING");
|
|
337
|
+
expect(errors()).toEqual([]);
|
|
338
|
+
|
|
339
|
+
answer_the_load(editor, remote);
|
|
340
|
+
expect(errors()).toEqual([]);
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
test("the reload of a reconnect draws the same screen", () => {
|
|
344
|
+
const {editor, remote, host} = build("r3", "db/users");
|
|
345
|
+
start_a_write(editor, host);
|
|
346
|
+
drop(editor, remote, host);
|
|
347
|
+
reconnect(editor, remote, host);
|
|
348
|
+
expect(gobj_current_state(editor)).toBe("ST_LOADING");
|
|
349
|
+
expect($in(editor, ".SCHEMA_BODY").childNodes.length).toBe(0);
|
|
350
|
+
press_everything(editor);
|
|
351
|
+
gobj_send_event(editor, "EV_SHOW", {subpath: "db"}, host);
|
|
352
|
+
answer_the_load(editor, remote);
|
|
353
|
+
expect(gobj_current_state(editor)).toBe("ST_TOPICS");
|
|
354
|
+
expect(errors()).toEqual([]);
|
|
355
|
+
});
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
describe("a form open when a reload starts", () => {
|
|
359
|
+
|
|
360
|
+
test("its Save is refused and said, and the load that lands closes it: nothing written", () => {
|
|
361
|
+
const {editor, remote, host} = build("f1", "db/users");
|
|
362
|
+
gobj_send_event(editor, "EV_ADD_COLUMN", {}, editor);
|
|
363
|
+
expect(modals.length).toBe(1);
|
|
364
|
+
const form = modals[0];
|
|
365
|
+
form.$content.querySelector('[data-name="value"]').value = "email";
|
|
366
|
+
|
|
367
|
+
gobj_send_event(editor, "EV_REFRESH", {}, host);
|
|
368
|
+
form.$content.querySelector(".SCHEMA_COL_FORM_SAVE").click();
|
|
369
|
+
|
|
370
|
+
expect(errors()).toEqual([]);
|
|
371
|
+
/* Not "try again": the same Save, once the load is in, wrote a
|
|
372
|
+
* form built on the model the load replaced. */
|
|
373
|
+
expect(shown).toEqual([WAIT]);
|
|
374
|
+
expect(form.closed).toBe(false);
|
|
375
|
+
expect(take("update-node")).toEqual([]);
|
|
376
|
+
|
|
377
|
+
answer_the_load(editor, remote);
|
|
378
|
+
expect(form.closed).toBe(true);
|
|
379
|
+
expect(shown).toEqual([WAIT, STALE]);
|
|
380
|
+
form.$content.querySelector(".SCHEMA_COL_FORM_SAVE").click();
|
|
381
|
+
expect(take("update-node")).toEqual([]);
|
|
382
|
+
expect(errors()).toEqual([]);
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
test("a confirmation given during a reload is refused and said", () => {
|
|
386
|
+
const {editor, remote, host} = build("f2", "db/users");
|
|
387
|
+
gobj_send_event(editor, "EV_REFRESH", {}, host);
|
|
388
|
+
gobj_send_event(editor, "EV_CONFIRMED", {what: "column", topic: "users", col: "id"}, editor);
|
|
389
|
+
expect(errors()).toEqual([]);
|
|
390
|
+
expect(shown).toEqual([WAIT]);
|
|
391
|
+
expect(take("update-node")).toEqual([]);
|
|
392
|
+
answer_the_load(editor, remote);
|
|
393
|
+
});
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
describe("a position that waited", () => {
|
|
397
|
+
|
|
398
|
+
test("a load that could not leave does not keep it for a later load", () => {
|
|
399
|
+
const {editor, remote, host} = build("p1", "db/users");
|
|
400
|
+
gobj_send_event(editor, "EV_REFRESH", {}, host);
|
|
401
|
+
take("nodes"); /* never answered */
|
|
402
|
+
gobj_send_event(editor, "EV_SHOW", {subpath: "db"}, host);
|
|
403
|
+
gobj_change_state(remote, "ST_DISCONNECTED");
|
|
404
|
+
gobj_send_event(editor, "EV_REFRESH", {}, host); /* cannot leave */
|
|
405
|
+
|
|
406
|
+
gobj_send_event(editor, "EV_SHOW", {subpath: "db/users"}, host);
|
|
407
|
+
expect(gobj_current_state(editor)).toBe("ST_COLUMNS");
|
|
408
|
+
|
|
409
|
+
reconnect(editor, remote, host);
|
|
410
|
+
answer_the_load(editor, remote);
|
|
411
|
+
gobj_send_event(editor, "EV_REFRESH", {}, host);
|
|
412
|
+
answer_the_load(editor, remote);
|
|
413
|
+
expect(gobj_current_state(editor)).toBe("ST_COLUMNS");
|
|
414
|
+
expect(editor.priv.topic_name).toBe("users");
|
|
415
|
+
expect(editor.priv.model.treedbs.length).toBe(1);
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
test("a load that could not leave keeps the records the model was built on", () => {
|
|
419
|
+
const {editor, remote, host} = build("p2", "db/users");
|
|
420
|
+
gobj_change_state(remote, "ST_DISCONNECTED");
|
|
421
|
+
gobj_send_event(editor, "EV_REFRESH", {}, host);
|
|
422
|
+
expect(editor.priv.records.cols.length).toBe(1);
|
|
423
|
+
|
|
424
|
+
/* ...and the session back reads the model again. */
|
|
425
|
+
reconnect(editor, remote, host);
|
|
426
|
+
expect(gobj_current_state(editor)).toBe("ST_LOADING");
|
|
427
|
+
answer_the_load(editor, remote);
|
|
428
|
+
expect(gobj_current_state(editor)).toBe("ST_COLUMNS");
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
test("a Refresh out of session asks nothing and logs no error: the session back loads", () => {
|
|
432
|
+
const {editor, remote, host} = build("p4", "db/users");
|
|
433
|
+
gobj_change_state(remote, "ST_DISCONNECTED");
|
|
434
|
+
gobj_send_event(editor, "EV_REFRESH", {}, host);
|
|
435
|
+
expect(errors()).toEqual([]);
|
|
436
|
+
expect(shown).toEqual(["cannot reach the treedb"]);
|
|
437
|
+
expect(gobj_current_state(editor)).toBe("ST_COLUMNS");
|
|
438
|
+
reconnect(editor, remote, host);
|
|
439
|
+
expect(answer_the_load(editor, remote)).toBe(3);
|
|
440
|
+
expect(errors()).toEqual([]);
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
test("a drop during a WRITE applies it, as a drop during a load does", () => {
|
|
444
|
+
const {editor, remote, host} = build("p3", "db/users");
|
|
445
|
+
start_a_write(editor, host);
|
|
446
|
+
gobj_send_event(editor, "EV_SHOW", {subpath: "db"}, host);
|
|
447
|
+
drop(editor, remote, host);
|
|
448
|
+
expect(shown).toEqual(["the connection dropped during the write"]);
|
|
449
|
+
expect(gobj_current_state(editor)).toBe("ST_TOPICS");
|
|
450
|
+
expect(editor.priv.topic_name).toBe("");
|
|
451
|
+
expect(editor.priv.pending_seg).toBe(null);
|
|
452
|
+
});
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
describe("the drawing of a treedb that is not there", () => {
|
|
456
|
+
|
|
457
|
+
test("does not leave the old screen up", () => {
|
|
458
|
+
const {editor, remote, host} = build("d1", "db/users");
|
|
459
|
+
gobj_send_event(editor, "EV_SHOW", {subpath: "gone/diagram"}, host);
|
|
460
|
+
expect(gobj_current_state(editor)).toBe("ST_DIAGRAM");
|
|
461
|
+
expect($in(editor, ".SCHEMA_BODY .SCHEMA_COLUMNS")).toBe(null);
|
|
462
|
+
expect($in(editor, ".SCHEMA_NOTICE_TEXT").getAttribute("data-i18n")).toBe("that treedb is not here any more");
|
|
463
|
+
expect(errors()).toEqual([]);
|
|
464
|
+
});
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
describe("a write given up that was DONE", () => {
|
|
468
|
+
|
|
469
|
+
test("the host is told, and the reload keeps what this session wrote", () => {
|
|
470
|
+
const {editor, remote, host} = build("l1", "db/users");
|
|
471
|
+
const w1 = start_a_write(editor, host, "name");
|
|
472
|
+
answer(editor, remote, w1, 0, {id: "db.users.name", value: "name", order: 2,
|
|
473
|
+
type: "string", topics: ["topics^db.users^cols"]});
|
|
474
|
+
expect(editor.priv.written).toEqual({"db.users": true});
|
|
475
|
+
|
|
476
|
+
const w2 = start_a_write(editor, host, "email");
|
|
477
|
+
drop(editor, remote, host);
|
|
478
|
+
reconnect(editor, remote, host);
|
|
479
|
+
answer_the_load(editor, remote);
|
|
480
|
+
expect(editor.priv.written).toEqual({"db.users": true});
|
|
481
|
+
published.length = 0;
|
|
482
|
+
|
|
483
|
+
/* The shape the store answers with: `nodes`/`update-node` ask
|
|
484
|
+
* for list_dict, so a fkey comes back as a list of refs. */
|
|
485
|
+
answer(editor, remote, w2, 0, {id: "db.users.email", value: "email",
|
|
486
|
+
type: "string", topics: [{id: "db.users", topic_name: "topics", hook_name: "cols"}]});
|
|
487
|
+
expect(published).toContain("EV_RECORD_WRITTEN");
|
|
488
|
+
expect(gobj_current_state(editor)).toBe("ST_LOADING");
|
|
489
|
+
answer_the_load(editor, remote);
|
|
490
|
+
expect(editor.priv.written).toEqual({"db.users": true});
|
|
491
|
+
expect(errors()).toEqual([]);
|
|
492
|
+
});
|
|
493
|
+
|
|
494
|
+
test("a write answered with no record: told, marked, and the body answers again", () => {
|
|
495
|
+
const {editor, remote, host} = build("l3", "db/users");
|
|
496
|
+
const w = start_a_write(editor, host, "name");
|
|
497
|
+
answer(editor, remote, w, 0, null);
|
|
498
|
+
expect(published).toContain("EV_RECORD_WRITTEN");
|
|
499
|
+
expect(gobj_current_state(editor)).toBe("ST_LOADING");
|
|
500
|
+
answer_the_load(editor, remote);
|
|
501
|
+
expect(gobj_current_state(editor)).toBe("ST_COLUMNS");
|
|
502
|
+
expect($in(editor, ".SCHEMA_BODY").classList.contains("SCHEMA_BUSY")).toBe(false);
|
|
503
|
+
expect(editor.priv.written).toEqual({"db.users": true});
|
|
504
|
+
expect(errors()).toEqual([]);
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
test("the host's Refresh still forgets them (a Save is what sends it)", () => {
|
|
508
|
+
const {editor, remote, host} = build("l2", "db/users");
|
|
509
|
+
const w1 = start_a_write(editor, host, "name");
|
|
510
|
+
answer(editor, remote, w1, 0, {id: "db.users.name", value: "name", order: 2,
|
|
511
|
+
type: "string", topics: ["topics^db.users^cols"]});
|
|
512
|
+
gobj_send_event(editor, "EV_REFRESH", {}, host);
|
|
513
|
+
answer_the_load(editor, remote);
|
|
514
|
+
expect(editor.priv.written).toEqual({});
|
|
515
|
+
});
|
|
516
|
+
});
|
|
517
|
+
|
|
518
|
+
describe("a dialog built on the model a reload replaced (fourth review)", () => {
|
|
519
|
+
|
|
520
|
+
test("an edit form: the load that lands closes it, and its Save writes nothing", () => {
|
|
521
|
+
const {editor, remote, host} = build("s1", "db/users");
|
|
522
|
+
gobj_send_event(editor, "EV_EDIT_COLUMN", {col: "id"}, host);
|
|
523
|
+
const form = modals[modals.length - 1];
|
|
524
|
+
form.$content.querySelector('[data-name="description"]').value = "mine";
|
|
525
|
+
|
|
526
|
+
gobj_send_event(editor, "EV_REFRESH", {}, host);
|
|
527
|
+
answer_the_load_with(editor, remote, records_with((r) => {
|
|
528
|
+
r.cols[0].description = "NEWER";
|
|
529
|
+
r.cols[0].header = "Newer header";
|
|
530
|
+
}));
|
|
531
|
+
expect(form.closed).toBe(true);
|
|
532
|
+
expect(shown).toEqual([STALE]);
|
|
533
|
+
expect(editor.priv.dialog).toBe(null);
|
|
534
|
+
|
|
535
|
+
/* The form's button, pressed anyway (a stale screen, a keyboard):
|
|
536
|
+
* the record it would write is the OLD one with one field changed,
|
|
537
|
+
* over the newer one. */
|
|
538
|
+
form.$content.querySelector(".SCHEMA_COL_FORM_SAVE").click();
|
|
539
|
+
expect(take("update-node")).toEqual([]);
|
|
540
|
+
expect(errors()).toEqual([]);
|
|
541
|
+
});
|
|
542
|
+
|
|
543
|
+
test("the column of the open form is gone after the reload: closed, no ERROR", () => {
|
|
544
|
+
const {editor, remote, host} = build("s2", "db/users");
|
|
545
|
+
gobj_send_event(editor, "EV_EDIT_COLUMN", {col: "id"}, host);
|
|
546
|
+
const form = modals[modals.length - 1];
|
|
547
|
+
gobj_send_event(editor, "EV_REFRESH", {}, host);
|
|
548
|
+
answer_the_load_with(editor, remote, records_with((r) => {
|
|
549
|
+
r.cols = [];
|
|
550
|
+
}));
|
|
551
|
+
expect(form.closed).toBe(true);
|
|
552
|
+
form.$content.querySelector(".SCHEMA_COL_FORM_SAVE").click();
|
|
553
|
+
expect(take("update-node")).toEqual([]);
|
|
554
|
+
expect(errors()).toEqual([]);
|
|
555
|
+
expect(not_defined()).toEqual([]);
|
|
556
|
+
});
|
|
557
|
+
|
|
558
|
+
test("an import plan is not run on the model that replaced the one it was planned on", () => {
|
|
559
|
+
const {editor, remote, host} = build("s3", "db");
|
|
560
|
+
gobj_send_event(editor, "EV_IMPORT", {}, host);
|
|
561
|
+
const dialog = modals[modals.length - 1];
|
|
562
|
+
gobj_send_event(editor, "EV_PREVIEW_IMPORT", {
|
|
563
|
+
prune: false,
|
|
564
|
+
text: JSON.stringify({id: "db", topics: [{id: "users", pkey: "id", cols: [
|
|
565
|
+
{id: "id", type: "string", flag: ["persistent", "required"]},
|
|
566
|
+
{id: "email", type: "string"}
|
|
567
|
+
]}]})
|
|
568
|
+
}, editor);
|
|
569
|
+
expect(editor.priv.import_plan.writes.length).toBeGreaterThan(0);
|
|
570
|
+
|
|
571
|
+
gobj_send_event(editor, "EV_REFRESH", {}, host);
|
|
572
|
+
expect(editor.priv.import_plan).toBe(null);
|
|
573
|
+
answer_the_load_with(editor, remote, records_with((r) => {
|
|
574
|
+
r.cols.push({id: "db.users.email", value: "email", order: 2, type: "integer",
|
|
575
|
+
topics: ["topics^db.users^cols"]});
|
|
576
|
+
}));
|
|
577
|
+
expect(dialog.closed).toBe(true);
|
|
578
|
+
|
|
579
|
+
dialog.$content.querySelector(".SCHEMA_IMPORT_RUN").disabled = false;
|
|
580
|
+
dialog.$content.querySelector(".SCHEMA_IMPORT_RUN").click();
|
|
581
|
+
expect(take("update-node")).toEqual([]);
|
|
582
|
+
expect(take("delete-node")).toEqual([]);
|
|
583
|
+
expect(errors()).toEqual([]);
|
|
584
|
+
});
|
|
585
|
+
|
|
586
|
+
test("a confirmation decided on the model a reload replaced is not applied", () => {
|
|
587
|
+
const {editor, remote, host} = build("s4", "db/users");
|
|
588
|
+
const kw = {what: "column", topic: "users", col: "id",
|
|
589
|
+
model_gen: editor.priv.model_gen};
|
|
590
|
+
gobj_send_event(editor, "EV_REFRESH", {}, host);
|
|
591
|
+
answer_the_load(editor, remote);
|
|
592
|
+
gobj_send_event(editor, "EV_CONFIRMED", kw, editor);
|
|
593
|
+
expect(take("update-node")).toEqual([]);
|
|
594
|
+
expect(shown).toEqual([STALE]);
|
|
595
|
+
expect(errors()).toEqual([]);
|
|
596
|
+
});
|
|
597
|
+
});
|
|
598
|
+
|
|
599
|
+
describe("a load that fails IN session (fourth review)", () => {
|
|
600
|
+
|
|
601
|
+
test("keeps the model, the screen and the open form, says so, and owes the reload", () => {
|
|
602
|
+
const {editor, remote, host} = build("b1", "db/users");
|
|
603
|
+
gobj_send_event(editor, "EV_EDIT_COLUMN", {col: "id"}, host);
|
|
604
|
+
const form = modals[modals.length - 1];
|
|
605
|
+
gobj_send_event(editor, "EV_REFRESH", {}, host);
|
|
606
|
+
const asked = take("nodes");
|
|
607
|
+
answer(editor, remote, asked[0], -1, null, "deadline");
|
|
608
|
+
answer(editor, remote, asked[1], 0, RECORDS[asked[1].kw.topic_name]);
|
|
609
|
+
answer(editor, remote, asked[2], 0, RECORDS[asked[2].kw.topic_name]);
|
|
610
|
+
|
|
611
|
+
expect(gobj_current_state(editor)).toBe("ST_COLUMNS");
|
|
612
|
+
expect(editor.priv.model.treedbs.length).toBe(1);
|
|
613
|
+
expect(editor.priv.records.treedbs.length).toBe(1);
|
|
614
|
+
expect(editor.priv.reload_on_open).toBe(true);
|
|
615
|
+
expect(shown).toEqual([KEPT]);
|
|
616
|
+
expect(form.closed).toBe(false);
|
|
617
|
+
expect($in(editor, ".SCHEMA_BODY .SCHEMA_COLUMNS")).toBeTruthy();
|
|
618
|
+
|
|
619
|
+
/* The form was built on the model still shown: it works. */
|
|
620
|
+
form.$content.querySelector(".SCHEMA_COL_FORM_SAVE").click();
|
|
621
|
+
expect(not_defined()).toEqual([]);
|
|
622
|
+
expect(take("update-node").length).toBe(1);
|
|
623
|
+
expect(errors()).toEqual([]);
|
|
624
|
+
});
|
|
625
|
+
|
|
626
|
+
test("with no model before it, it is the empty screen it always was", () => {
|
|
627
|
+
const remote = gobj_create_service("b2_remote", "C_TEST_REMOTE", {}, yuno);
|
|
628
|
+
gobj_change_state(remote, "ST_SESSION");
|
|
629
|
+
const host = gobj_create("b2_host", "C_TEST_EDITOR_HOST", {}, yuno);
|
|
630
|
+
const editor = gobj_create("b2_editor", "C_YUI_SCHEMA_EDITOR", {
|
|
631
|
+
gobj_remote_yuno: remote, treedb_name: "treedb_system_schema"
|
|
632
|
+
}, host);
|
|
633
|
+
gobj_start(editor);
|
|
634
|
+
for(const c of take("nodes")) {
|
|
635
|
+
answer(editor, remote, c, -1, null, "deadline");
|
|
636
|
+
}
|
|
637
|
+
expect(gobj_current_state(editor)).toBe("ST_IDLE");
|
|
638
|
+
expect(editor.priv.model).toBe(null);
|
|
639
|
+
expect(errors()).toEqual([]);
|
|
640
|
+
});
|
|
641
|
+
|
|
642
|
+
test("a drop that cuts a reload keeps the records the model was built on", () => {
|
|
643
|
+
const {editor, remote, host} = build("b3", "db/users");
|
|
644
|
+
gobj_send_event(editor, "EV_REFRESH", {}, host);
|
|
645
|
+
const asked = take("nodes");
|
|
646
|
+
answer(editor, remote, asked[0], 0, []); /* half a load */
|
|
647
|
+
drop(editor, remote, host);
|
|
648
|
+
expect(editor.priv.records.treedbs.length).toBe(1);
|
|
649
|
+
expect(editor.priv.records.cols.length).toBe(1);
|
|
650
|
+
expect(gobj_current_state(editor)).toBe("ST_COLUMNS");
|
|
651
|
+
});
|
|
652
|
+
});
|
|
653
|
+
|
|
654
|
+
describe("a late write, in the shape the store answers (fourth review)", () => {
|
|
655
|
+
|
|
656
|
+
test("a list_dict fkey marks the topic the column belongs to", () => {
|
|
657
|
+
const {editor, remote, host} = build("c1", "db/users");
|
|
658
|
+
const w = start_a_write(editor, host, "name");
|
|
659
|
+
drop(editor, remote, host);
|
|
660
|
+
reconnect(editor, remote, host);
|
|
661
|
+
answer_the_load(editor, remote);
|
|
662
|
+
expect(editor.priv.written).toEqual({});
|
|
663
|
+
answer(editor, remote, w, 0, {id: "db.users.name", value: "name", type: "string",
|
|
664
|
+
topics: [{id: "db.users", topic_name: "topics", hook_name: "cols"}]});
|
|
665
|
+
expect(editor.priv.written).toEqual({"db.users": true});
|
|
666
|
+
answer_the_load(editor, remote);
|
|
667
|
+
expect(errors()).toEqual([]);
|
|
668
|
+
});
|
|
669
|
+
});
|
|
670
|
+
|
|
671
|
+
describe("a write answered with no record (fourth review)", () => {
|
|
672
|
+
|
|
673
|
+
test("the reload it asks is the one owed: the next write does not load again", () => {
|
|
674
|
+
const {editor, remote, host} = build("n1", "db/users");
|
|
675
|
+
const w1 = start_a_write(editor, host, "a1");
|
|
676
|
+
drop(editor, remote, host);
|
|
677
|
+
reconnect(editor, remote, host);
|
|
678
|
+
answer_the_load(editor, remote);
|
|
679
|
+
const w2 = start_a_write(editor, host, "a2");
|
|
680
|
+
answer(editor, remote, w1, 0, {id: "db.users.a1", value: "a1",
|
|
681
|
+
topics: [{id: "db.users", topic_name: "topics", hook_name: "cols"}]});
|
|
682
|
+
expect(editor.priv.reload_after_write).toBe(true);
|
|
683
|
+
answer(editor, remote, w2, 0, null);
|
|
684
|
+
expect(editor.priv.reload_after_write).toBe(false);
|
|
685
|
+
answer_the_load(editor, remote);
|
|
686
|
+
|
|
687
|
+
const w3 = start_a_write(editor, host, "a3");
|
|
688
|
+
answer(editor, remote, w3, 0, {id: "db.users.a3", value: "a3",
|
|
689
|
+
topics: [{id: "db.users", topic_name: "topics", hook_name: "cols"}]});
|
|
690
|
+
expect(gobj_current_state(editor)).toBe("ST_COLUMNS");
|
|
691
|
+
expect(take("nodes")).toEqual([]);
|
|
692
|
+
expect(errors()).toEqual([]);
|
|
693
|
+
});
|
|
694
|
+
|
|
695
|
+
test("the writes queued after it are said, not dropped in silence", () => {
|
|
696
|
+
const {editor, remote, host} = build("n2", "db/users");
|
|
697
|
+
for(const name of ["a", "b"]) {
|
|
698
|
+
const w = start_a_write(editor, host, name);
|
|
699
|
+
answer(editor, remote, w, 0, {id: `db.users.${name}`, value: name, type: "string",
|
|
700
|
+
order: name === "a" ? 2 : 3, topics: ["topics^db.users^cols"]});
|
|
701
|
+
}
|
|
702
|
+
gobj_send_event(editor, "EV_MOVE_COLUMN", {from: 0, to: 3}, host);
|
|
703
|
+
expect(gobj_current_state(editor)).toBe("ST_SAVING");
|
|
704
|
+
expect(editor.priv.save_queue.length).toBeGreaterThan(1);
|
|
705
|
+
const [first] = take("update-node");
|
|
706
|
+
answer(editor, remote, first, 0, null);
|
|
707
|
+
expect(take("update-node")).toEqual([]);
|
|
708
|
+
expect(shown).toEqual([NOT_SENT]);
|
|
709
|
+
expect(logged.some((l) => l.level === "warning" && /not sent/.test(l.msg))).toBe(true);
|
|
710
|
+
answer_the_load(editor, remote);
|
|
711
|
+
expect(errors()).toEqual([]);
|
|
712
|
+
});
|
|
713
|
+
});
|
|
714
|
+
|
|
715
|
+
describe("the toolbar of a treedb that is not there (fourth review)", () => {
|
|
716
|
+
|
|
717
|
+
test("offers Back and Refresh, and nothing that needs the treedb", () => {
|
|
718
|
+
const {editor, remote, host} = build("t1", "db");
|
|
719
|
+
gobj_send_event(editor, "EV_REFRESH", {}, host);
|
|
720
|
+
answer_the_load_with(editor, remote, records_with((r) => {
|
|
721
|
+
r.treedbs = [{id: "other", schema_version: 1}];
|
|
722
|
+
r.topics = [];
|
|
723
|
+
r.cols = [];
|
|
724
|
+
}));
|
|
725
|
+
expect($in(editor, ".SCHEMA_BACK")).toBeTruthy();
|
|
726
|
+
expect($in(editor, ".SCHEMA_REFRESH_BTN")).toBeTruthy();
|
|
727
|
+
for(const cls of ["SCHEMA_DIAGRAM_BTN", "SCHEMA_VALIDATE_BTN", "SCHEMA_EXPORT_BTN",
|
|
728
|
+
"SCHEMA_IMPORT_BTN", "SCHEMA_ADD_TOPIC_BTN"]) {
|
|
729
|
+
expect([cls, !!$in(editor, `.${cls}`)]).toEqual([cls, false]);
|
|
730
|
+
}
|
|
731
|
+
for(const $b of $all(editor, ".SCHEMA_TOOLBAR button")) {
|
|
732
|
+
if($b.classList.contains("SCHEMA_REFRESH_BTN") || $b.classList.contains("SCHEMA_BACK")) {
|
|
733
|
+
continue;
|
|
734
|
+
}
|
|
735
|
+
$b.click();
|
|
736
|
+
}
|
|
737
|
+
expect(errors()).toEqual([]);
|
|
738
|
+
});
|
|
739
|
+
|
|
740
|
+
test("a topic that is not there offers no New column", () => {
|
|
741
|
+
const {editor, remote, host} = build("t2", "db/users");
|
|
742
|
+
gobj_send_event(editor, "EV_REFRESH", {}, host);
|
|
743
|
+
answer_the_load_with(editor, remote, records_with((r) => {
|
|
744
|
+
r.topics = [];
|
|
745
|
+
r.cols = [];
|
|
746
|
+
}));
|
|
747
|
+
expect(!!$in(editor, ".SCHEMA_ADD_COL_BTN")).toBe(false);
|
|
748
|
+
expect($in(editor, ".SCHEMA_BACK")).toBeTruthy();
|
|
749
|
+
});
|
|
750
|
+
});
|
|
751
|
+
|
|
752
|
+
describe("every control of the editor's dialogs is named (fourth review)", () => {
|
|
753
|
+
|
|
754
|
+
/* A control a wrapping <label> names is the one shape that needs no
|
|
755
|
+
* attribute (CLAUDE.md, "title + aria-label"). */
|
|
756
|
+
function unnamed($root)
|
|
757
|
+
{
|
|
758
|
+
const out = [];
|
|
759
|
+
for(const $c of $root.querySelectorAll("button, input, select, textarea")) {
|
|
760
|
+
if($c.getAttribute("type") === "checkbox" && $c.closest("label")) {
|
|
761
|
+
continue;
|
|
762
|
+
}
|
|
763
|
+
const ok = $c.getAttribute("title") !== null &&
|
|
764
|
+
$c.getAttribute("aria-label") !== null &&
|
|
765
|
+
!!$c.getAttribute("data-i18n-title") &&
|
|
766
|
+
!!$c.getAttribute("data-i18n-aria-label");
|
|
767
|
+
if(!ok) {
|
|
768
|
+
out.push(`${$c.tagName} ${$c.className || $c.getAttribute("data-name") || ""}`);
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
return out;
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
test("the column form, the topic form and the import", () => {
|
|
775
|
+
const {editor, remote, host} = build("a1", "db/users");
|
|
776
|
+
gobj_send_event(editor, "EV_EDIT_COLUMN", {col: "id"}, host);
|
|
777
|
+
expect(unnamed(modals[modals.length - 1].$content)).toEqual([]);
|
|
778
|
+
|
|
779
|
+
gobj_send_event(editor, "EV_SHOW", {subpath: "db"}, host);
|
|
780
|
+
gobj_send_event(editor, "EV_EDIT_TOPIC", {topic: "users"}, host);
|
|
781
|
+
expect(unnamed(modals[modals.length - 1].$content)).toEqual([]);
|
|
782
|
+
|
|
783
|
+
gobj_send_event(editor, "EV_IMPORT", {}, host);
|
|
784
|
+
expect(unnamed(modals[modals.length - 1].$content)).toEqual([]);
|
|
785
|
+
expect(errors()).toEqual([]);
|
|
786
|
+
});
|
|
787
|
+
});
|