@yuneta/gobj-ui 7.25.16 → 7.25.18
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 +76 -0
- package/dist/gobj-ui.cjs.js +104 -34
- package/dist/gobj-ui.es.js +104 -34
- package/package.json +2 -2
- package/src/c_yui_schema_editor.js +41 -18
- package/src/c_yui_treedb_graph.js +71 -12
- package/src/c_yui_treedb_topics.js +23 -2
- package/src/schema_editor_reload.wiring.test.js +161 -0
- package/src/treedb_graph_writes.wiring.test.js +79 -4
- package/src/treedb_topics_transport.wiring.test.js +22 -7
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yuneta/gobj-ui",
|
|
3
|
-
"version": "7.25.
|
|
3
|
+
"version": "7.25.18",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/gobj-ui.cjs.js",
|
|
6
6
|
"module": "dist/gobj-ui.es.js",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"devDependencies": {
|
|
31
31
|
"@rollup/plugin-terser": "^1.0.0",
|
|
32
32
|
"@vitest/coverage-v8": "^4.1.10",
|
|
33
|
-
"@yuneta/gobj-js": "^7.25.
|
|
33
|
+
"@yuneta/gobj-js": "^7.25.3",
|
|
34
34
|
"vite": "^8.2.1",
|
|
35
35
|
"vitest": "^4.1.10"
|
|
36
36
|
},
|
|
@@ -403,7 +403,11 @@ function el_attrs(attrs)
|
|
|
403
403
|
}
|
|
404
404
|
|
|
405
405
|
/***************************************************************
|
|
406
|
-
* The body stops answering while a write is in flight.
|
|
406
|
+
* The body stops answering while a write is in flight. For the
|
|
407
|
+
* keyboard too: `pointer-events` stops the mouse alone, and the
|
|
408
|
+
* shell's focus trap puts the focus back on the row control that
|
|
409
|
+
* had it, where Enter or Delete sent an action ST_SAVING does
|
|
410
|
+
* not declare. `inert` takes the whole body out of reach.
|
|
407
411
|
***************************************************************/
|
|
408
412
|
function set_busy(gobj, busy)
|
|
409
413
|
{
|
|
@@ -411,6 +415,7 @@ function set_busy(gobj, busy)
|
|
|
411
415
|
|
|
412
416
|
if($body) {
|
|
413
417
|
$body.classList.toggle("SCHEMA_BUSY", !!busy);
|
|
418
|
+
$body.toggleAttribute("inert", !!busy);
|
|
414
419
|
}
|
|
415
420
|
}
|
|
416
421
|
|
|
@@ -707,7 +712,7 @@ function request_model(gobj, keep_written)
|
|
|
707
712
|
priv.records_before = null;
|
|
708
713
|
priv.load_round++;
|
|
709
714
|
priv.reload_on_open = true;
|
|
710
|
-
priv.load_error =
|
|
715
|
+
priv.load_error = "cannot reach the treedb"; /* a key: the notice translates it */
|
|
711
716
|
if(priv.model) {
|
|
712
717
|
yui_shell_show_error(yui_shell_of(gobj), "cannot reach the treedb", {t: t});
|
|
713
718
|
}
|
|
@@ -993,9 +998,14 @@ function show_notice(gobj, key, detail)
|
|
|
993
998
|
$notice.appendChild(createElement2(
|
|
994
999
|
["span", {class: "SCHEMA_NOTICE_TEXT", i18n: key}, t(key)]
|
|
995
1000
|
));
|
|
996
|
-
|
|
1001
|
+
/* The detail is a KEY too (ours, or the backend's words, which
|
|
1002
|
+
* translate to themselves when no locale has them), so a change
|
|
1003
|
+
* of language changes it with the rest. One that repeats the
|
|
1004
|
+
* title says nothing. */
|
|
1005
|
+
if(detail && detail !== key) {
|
|
997
1006
|
$notice.appendChild(createElement2(
|
|
998
|
-
["span", {class: "SCHEMA_NOTICE_DETAIL yui-text-quiet ml-2"
|
|
1007
|
+
["span", {class: "SCHEMA_NOTICE_DETAIL yui-text-quiet ml-2", i18n: `${detail}`},
|
|
1008
|
+
t(`${detail}`)]
|
|
999
1009
|
));
|
|
1000
1010
|
}
|
|
1001
1011
|
}
|
|
@@ -3350,7 +3360,7 @@ function ac_mt_command_answer(gobj, event, kw, src)
|
|
|
3350
3360
|
}
|
|
3351
3361
|
priv.pending--;
|
|
3352
3362
|
if(result < 0) {
|
|
3353
|
-
priv.load_error = comment ||
|
|
3363
|
+
priv.load_error = comment || "cannot load the schemas"; /* a key */
|
|
3354
3364
|
} else if(priv.records && Array.isArray(priv.records[topic_name])) {
|
|
3355
3365
|
priv.records[topic_name] = Array.isArray(data) ? data : [];
|
|
3356
3366
|
}
|
|
@@ -3427,10 +3437,11 @@ function ac_mt_command_answer(gobj, event, kw, src)
|
|
|
3427
3437
|
return end_writes(gobj, comment || t("the treedb refused the write"));
|
|
3428
3438
|
}
|
|
3429
3439
|
priv.save_wrote = true;
|
|
3440
|
+
let record = null;
|
|
3430
3441
|
if(command === "delete-node") {
|
|
3431
3442
|
forget_record(gobj, topic_name, kw_get_str(gobj, md, "record_id", "", 0));
|
|
3432
3443
|
} else {
|
|
3433
|
-
|
|
3444
|
+
record = is_object(data) ? data
|
|
3434
3445
|
: (Array.isArray(data) && is_object(data[0]) ? data[0] : null);
|
|
3435
3446
|
if(record) {
|
|
3436
3447
|
patch_record(gobj, topic_name, record);
|
|
@@ -3453,7 +3464,7 @@ function ac_mt_command_answer(gobj, event, kw, src)
|
|
|
3453
3464
|
}
|
|
3454
3465
|
if(topic_name === T_TOPICS || topic_name === T_COLS) {
|
|
3455
3466
|
let write = priv.save_queue[0];
|
|
3456
|
-
mark_written(gobj, topic_name, write ? write.record : null);
|
|
3467
|
+
mark_written(gobj, topic_name, null, write ? write.record : null);
|
|
3457
3468
|
}
|
|
3458
3469
|
priv.reload_after_write = false;
|
|
3459
3470
|
priv.save_queue = [];
|
|
@@ -3471,11 +3482,12 @@ function ac_mt_command_answer(gobj, event, kw, src)
|
|
|
3471
3482
|
}
|
|
3472
3483
|
}
|
|
3473
3484
|
if(topic_name === T_TOPICS || topic_name === T_COLS) {
|
|
3474
|
-
/* Which record it was
|
|
3475
|
-
* adapter echoes only what it was
|
|
3476
|
-
* record is not small enough to be
|
|
3485
|
+
/* Which record it was: the one the store answered, else the
|
|
3486
|
+
* one in the QUEUE -- an adapter echoes only what it was
|
|
3487
|
+
* asked to echo, and the record is not small enough to be
|
|
3488
|
+
* worth echoing. */
|
|
3477
3489
|
let write = priv.save_queue[0];
|
|
3478
|
-
mark_written(gobj, topic_name, write ? write.record : null);
|
|
3490
|
+
mark_written(gobj, topic_name, record, write ? write.record : null);
|
|
3479
3491
|
}
|
|
3480
3492
|
priv.save_queue.shift();
|
|
3481
3493
|
return run_next_write(gobj);
|
|
@@ -3544,35 +3556,46 @@ function late_write_done(gobj, command, topic_name, data)
|
|
|
3544
3556
|
function mark_written_record(gobj, topic_name, record)
|
|
3545
3557
|
{
|
|
3546
3558
|
let priv = gobj.priv;
|
|
3559
|
+
let marked = 0;
|
|
3547
3560
|
|
|
3548
3561
|
if(!record) {
|
|
3549
|
-
return;
|
|
3562
|
+
return 0;
|
|
3550
3563
|
}
|
|
3551
3564
|
if(topic_name === T_TOPICS && typeof record.id === "string" && record.id) {
|
|
3552
3565
|
priv.written[record.id] = true;
|
|
3553
|
-
return;
|
|
3566
|
+
return 1;
|
|
3554
3567
|
}
|
|
3555
3568
|
if(topic_name !== T_COLS) {
|
|
3556
|
-
return;
|
|
3569
|
+
return 0;
|
|
3557
3570
|
}
|
|
3558
3571
|
for(let ref of parse_fkey_ref(record.topics)) {
|
|
3559
3572
|
priv.written[ref.id] = true;
|
|
3573
|
+
marked++;
|
|
3560
3574
|
}
|
|
3575
|
+
return marked;
|
|
3561
3576
|
}
|
|
3562
3577
|
|
|
3563
3578
|
/***************************************************************
|
|
3564
3579
|
* Remember which topic this write belongs to, which is what
|
|
3565
3580
|
* "its version still has to move" is asked about.
|
|
3581
|
+
*
|
|
3582
|
+
* Asked of the RECORD first: the one the store answered (a new
|
|
3583
|
+
* topic has its id only there), then the one queued (a column
|
|
3584
|
+
* names its topic in its fkey). The screen is the last resort,
|
|
3585
|
+
* and a poor one: an import writes topics the operator is not
|
|
3586
|
+
* on, and from the topics screen there is no topic on it at all.
|
|
3566
3587
|
***************************************************************/
|
|
3567
|
-
function mark_written(gobj, topic_name,
|
|
3588
|
+
function mark_written(gobj, topic_name, answered, queued)
|
|
3568
3589
|
{
|
|
3569
3590
|
let priv = gobj.priv;
|
|
3570
|
-
let topic = current_topic(gobj);
|
|
3571
3591
|
|
|
3572
|
-
if(topic_name
|
|
3573
|
-
priv.written[record.id] = true;
|
|
3592
|
+
if(mark_written_record(gobj, topic_name, answered) > 0) {
|
|
3574
3593
|
return;
|
|
3575
3594
|
}
|
|
3595
|
+
if(mark_written_record(gobj, topic_name, queued) > 0) {
|
|
3596
|
+
return;
|
|
3597
|
+
}
|
|
3598
|
+
let topic = current_topic(gobj);
|
|
3576
3599
|
if(topic) {
|
|
3577
3600
|
priv.written[topic.id] = true;
|
|
3578
3601
|
}
|
|
@@ -1520,6 +1520,12 @@ function treedb_update_node(gobj, treedb_name, topic_name, record, options)
|
|
|
1520
1520
|
kw.__md_command__ = { // Data to be returned
|
|
1521
1521
|
topic_name: topic_name,
|
|
1522
1522
|
};
|
|
1523
|
+
/* The answer carries this echo and nothing else of the request: a
|
|
1524
|
+
* refused `__graphs__` write is told to the engine by the topic it
|
|
1525
|
+
* arranges, so that topic has to travel here. */
|
|
1526
|
+
if(topic_name === "__graphs__") {
|
|
1527
|
+
kw.__md_command__.graph_topic = graph_topic_of(record);
|
|
1528
|
+
}
|
|
1523
1529
|
|
|
1524
1530
|
let ret = gobj_command(priv.gobj_remote_yuno,
|
|
1525
1531
|
command,
|
|
@@ -1569,6 +1575,38 @@ function treedb_delete_node(gobj, treedb_name, topic_name, record, options)
|
|
|
1569
1575
|
}
|
|
1570
1576
|
}
|
|
1571
1577
|
|
|
1578
|
+
/************************************************************
|
|
1579
|
+
* What a link or an unlink asks back with its answer: the
|
|
1580
|
+
* topic of the CHILD (the node a link saves) and both refs.
|
|
1581
|
+
* `child_ref` is "topic^id", `parent_ref` "topic^id^hook".
|
|
1582
|
+
************************************************************/
|
|
1583
|
+
function link_echo(parent_ref, child_ref)
|
|
1584
|
+
{
|
|
1585
|
+
let child_topic = (typeof child_ref === "string") ? child_ref.split("^")[0] : "";
|
|
1586
|
+
return {
|
|
1587
|
+
topic_name: child_topic,
|
|
1588
|
+
parent_ref: parent_ref,
|
|
1589
|
+
child_ref: child_ref
|
|
1590
|
+
};
|
|
1591
|
+
}
|
|
1592
|
+
|
|
1593
|
+
/************************************************************
|
|
1594
|
+
* The record a write answered: the node the store wrote (an
|
|
1595
|
+
* update or a create), deleted, or the child of a link. The
|
|
1596
|
+
* answer's frame carries back only `__md_command__`, never the
|
|
1597
|
+
* request's own record.
|
|
1598
|
+
************************************************************/
|
|
1599
|
+
function answered_record(data)
|
|
1600
|
+
{
|
|
1601
|
+
if(data && typeof data === "object" && !Array.isArray(data)) {
|
|
1602
|
+
return data;
|
|
1603
|
+
}
|
|
1604
|
+
if(Array.isArray(data) && data[0] && typeof data[0] === "object") {
|
|
1605
|
+
return data[0];
|
|
1606
|
+
}
|
|
1607
|
+
return {};
|
|
1608
|
+
}
|
|
1609
|
+
|
|
1572
1610
|
/************************************************************
|
|
1573
1611
|
* Command to remote service
|
|
1574
1612
|
************************************************************/
|
|
@@ -1590,6 +1628,7 @@ function treedb_link_nodes(gobj, treedb_name, parent_ref, child_ref, options)
|
|
|
1590
1628
|
child_ref: child_ref,
|
|
1591
1629
|
options: options || {}
|
|
1592
1630
|
};
|
|
1631
|
+
kw.__md_command__ = link_echo(parent_ref, child_ref);
|
|
1593
1632
|
|
|
1594
1633
|
let ret = gobj_command(priv.gobj_remote_yuno,
|
|
1595
1634
|
command,
|
|
@@ -1622,6 +1661,7 @@ function treedb_unlink_nodes(gobj, treedb_name, parent_ref, child_ref, options)
|
|
|
1622
1661
|
child_ref: child_ref,
|
|
1623
1662
|
options: options || {}
|
|
1624
1663
|
};
|
|
1664
|
+
kw.__md_command__ = link_echo(parent_ref, child_ref);
|
|
1625
1665
|
|
|
1626
1666
|
let ret = gobj_command(priv.gobj_remote_yuno,
|
|
1627
1667
|
command,
|
|
@@ -2105,8 +2145,12 @@ function ac_mt_command_answer(gobj, event, kw, src)
|
|
|
2105
2145
|
* answering its own writes in a loop.
|
|
2106
2146
|
*
|
|
2107
2147
|
* Same event and same kw as C_YUI_TREEDB_TOPICS, plus `command`:
|
|
2108
|
-
* in a graph a LINK is as much a write as a record is, and
|
|
2109
|
-
*
|
|
2148
|
+
* in a graph a LINK is as much a write as a record is, and a
|
|
2149
|
+
* link adds its two refs. Read from what THIS view knows and
|
|
2150
|
+
* from the answer, never from the request: the frame carries
|
|
2151
|
+
* back only `__md_command__`. `treedb_name` is the view's,
|
|
2152
|
+
* `record` is the node the store answered (the child, for a
|
|
2153
|
+
* link).
|
|
2110
2154
|
*
|
|
2111
2155
|
* `__graphs__` is EXCLUDED, and that exclusion is the point of
|
|
2112
2156
|
* reading this comment: the view writes that topic ITSELF, one
|
|
@@ -2118,17 +2162,22 @@ function ac_mt_command_answer(gobj, event, kw, src)
|
|
|
2118
2162
|
if(result < 0 && command === "update-node") {
|
|
2119
2163
|
graphs_write_refused(gobj,
|
|
2120
2164
|
kw_get_str(gobj, kw_command, "topic_name", "", 0),
|
|
2121
|
-
|
|
2165
|
+
kw_get_str(gobj, kw_command, "graph_topic", "", 0));
|
|
2122
2166
|
}
|
|
2123
2167
|
if(result >= 0 &&
|
|
2124
2168
|
kw_get_str(gobj, kw_command, "topic_name", "", 0) !== "__graphs__") {
|
|
2125
|
-
|
|
2126
|
-
treedb_name:
|
|
2169
|
+
let written = {
|
|
2170
|
+
treedb_name: priv.treedb_name,
|
|
2127
2171
|
topic_name: kw_get_str(gobj, kw_command, "topic_name", "", 0),
|
|
2128
|
-
record:
|
|
2172
|
+
record: answered_record(data),
|
|
2129
2173
|
created: (command === "create-node"),
|
|
2130
2174
|
command: command
|
|
2131
|
-
}
|
|
2175
|
+
};
|
|
2176
|
+
if(command === "link-nodes" || command === "unlink-nodes") {
|
|
2177
|
+
written.parent_ref = kw_get_str(gobj, kw_command, "parent_ref", "", 0);
|
|
2178
|
+
written.child_ref = kw_get_str(gobj, kw_command, "child_ref", "", 0);
|
|
2179
|
+
}
|
|
2180
|
+
gobj_publish_event(gobj, "EV_RECORD_WRITTEN", written);
|
|
2132
2181
|
}
|
|
2133
2182
|
break;
|
|
2134
2183
|
|
|
@@ -2486,7 +2535,7 @@ function ac_update_node(gobj, event, kw, src)
|
|
|
2486
2535
|
let options = kw.options || {};
|
|
2487
2536
|
|
|
2488
2537
|
if(refuse_if_readonly(gobj, event)) {
|
|
2489
|
-
graphs_write_refused(gobj, topic_name, record);
|
|
2538
|
+
graphs_write_refused(gobj, topic_name, graph_topic_of(record));
|
|
2490
2539
|
return -1;
|
|
2491
2540
|
}
|
|
2492
2541
|
|
|
@@ -2496,7 +2545,7 @@ function ac_update_node(gobj, event, kw, src)
|
|
|
2496
2545
|
if(topic_name === "__graphs__" && !is_connected(gobj)) {
|
|
2497
2546
|
log_warning(`${gobj_short_name(gobj)}: no session, the arrangement ` +
|
|
2498
2547
|
`was not sent`);
|
|
2499
|
-
graphs_write_refused(gobj, topic_name, record);
|
|
2548
|
+
graphs_write_refused(gobj, topic_name, graph_topic_of(record));
|
|
2500
2549
|
return -1;
|
|
2501
2550
|
}
|
|
2502
2551
|
|
|
@@ -2508,11 +2557,22 @@ function ac_update_node(gobj, event, kw, src)
|
|
|
2508
2557
|
options
|
|
2509
2558
|
);
|
|
2510
2559
|
if(ret < 0) {
|
|
2511
|
-
graphs_write_refused(gobj, topic_name, record);
|
|
2560
|
+
graphs_write_refused(gobj, topic_name, graph_topic_of(record));
|
|
2512
2561
|
}
|
|
2513
2562
|
return ret;
|
|
2514
2563
|
}
|
|
2515
2564
|
|
|
2565
|
+
/************************************************************
|
|
2566
|
+
* The topic a `__graphs__` record arranges.
|
|
2567
|
+
************************************************************/
|
|
2568
|
+
function graph_topic_of(record)
|
|
2569
|
+
{
|
|
2570
|
+
if(!record || typeof record !== "object") {
|
|
2571
|
+
return "";
|
|
2572
|
+
}
|
|
2573
|
+
return record.topic || record.id || "";
|
|
2574
|
+
}
|
|
2575
|
+
|
|
2516
2576
|
/************************************************************
|
|
2517
2577
|
* A write of `__graphs__` did not land. The engine
|
|
2518
2578
|
* (C_G6_NODES_TREE) took it for granted when it left -- nothing
|
|
@@ -2520,14 +2580,13 @@ function ac_update_node(gobj, event, kw, src)
|
|
|
2520
2580
|
* writes the topic again. Other topics are the operator's data:
|
|
2521
2581
|
* the answer's error says what happened to them.
|
|
2522
2582
|
************************************************************/
|
|
2523
|
-
function graphs_write_refused(gobj, topic_name,
|
|
2583
|
+
function graphs_write_refused(gobj, topic_name, topic)
|
|
2524
2584
|
{
|
|
2525
2585
|
let priv = gobj.priv;
|
|
2526
2586
|
|
|
2527
2587
|
if(topic_name !== "__graphs__") {
|
|
2528
2588
|
return;
|
|
2529
2589
|
}
|
|
2530
|
-
let topic = (record && typeof record === "object") ? (record.topic || record.id) : "";
|
|
2531
2590
|
if(!topic) {
|
|
2532
2591
|
log_error(`${gobj_short_name(gobj)}: a refused __graphs__ write names no topic`);
|
|
2533
2592
|
return;
|
|
@@ -1624,6 +1624,23 @@ function treedb_nodes(gobj, treedb_name, topic_name, options, page, purpose)
|
|
|
1624
1624
|
return 0;
|
|
1625
1625
|
}
|
|
1626
1626
|
|
|
1627
|
+
/************************************************************
|
|
1628
|
+
* The record a write answered: the node the store wrote (an
|
|
1629
|
+
* update or a create), deleted, or the child of a link. The
|
|
1630
|
+
* answer's frame carries back only `__md_command__`, never the
|
|
1631
|
+
* request's own record.
|
|
1632
|
+
************************************************************/
|
|
1633
|
+
function answered_record(data)
|
|
1634
|
+
{
|
|
1635
|
+
if(data && typeof data === "object" && !Array.isArray(data)) {
|
|
1636
|
+
return data;
|
|
1637
|
+
}
|
|
1638
|
+
if(Array.isArray(data) && data[0] && typeof data[0] === "object") {
|
|
1639
|
+
return data[0];
|
|
1640
|
+
}
|
|
1641
|
+
return {};
|
|
1642
|
+
}
|
|
1643
|
+
|
|
1627
1644
|
/************************************************************
|
|
1628
1645
|
* Command to remote service
|
|
1629
1646
|
************************************************************/
|
|
@@ -2210,10 +2227,14 @@ function ac_mt_command_answer(gobj, event, kw, src)
|
|
|
2210
2227
|
* and cannot use the node events without answering its own writes
|
|
2211
2228
|
* in a loop.
|
|
2212
2229
|
*/
|
|
2230
|
+
/* From what THIS view knows and from the answer, never from
|
|
2231
|
+
* the request: the frame carries back only `__md_command__`,
|
|
2232
|
+
* so `treedb_name` and `record` read there arrived empty.
|
|
2233
|
+
* `record` is the node the store answered, as it was written. */
|
|
2213
2234
|
gobj_publish_event(gobj, "EV_RECORD_WRITTEN", {
|
|
2214
|
-
treedb_name:
|
|
2235
|
+
treedb_name: gobj_read_str_attr(gobj, "treedb_name"),
|
|
2215
2236
|
topic_name: kw_get_str(gobj, kw_command, "topic_name", "", 0),
|
|
2216
|
-
record:
|
|
2237
|
+
record: answered_record(data),
|
|
2217
2238
|
created: (command === "create-node"),
|
|
2218
2239
|
command: command
|
|
2219
2240
|
});
|
|
@@ -1501,3 +1501,164 @@ describe("EV_REFRESH while a write is in flight", () => {
|
|
|
1501
1501
|
expect(not_defined()).toEqual([]);
|
|
1502
1502
|
});
|
|
1503
1503
|
});
|
|
1504
|
+
|
|
1505
|
+
describe("the body while a write is in flight", () => {
|
|
1506
|
+
|
|
1507
|
+
/* pointer-events stops the mouse and nothing else: the shell's
|
|
1508
|
+
* focus trap puts the focus back on the row control that had it,
|
|
1509
|
+
* and Delete or Enter there sent an action ST_SAVING does not
|
|
1510
|
+
* declare. `inert` takes the body out of the keyboard's reach too. */
|
|
1511
|
+
test("is inert, and answers again when the write ends", () => {
|
|
1512
|
+
const {editor, remote, host} = build("r16k1", "db/users");
|
|
1513
|
+
const $body = $in(editor, ".SCHEMA_BODY");
|
|
1514
|
+
expect($body.hasAttribute("inert")).toBe(false);
|
|
1515
|
+
|
|
1516
|
+
const write = start_a_write(editor, host, "name");
|
|
1517
|
+
expect($body.hasAttribute("inert")).toBe(true);
|
|
1518
|
+
|
|
1519
|
+
answer(editor, remote, write, 0, write.kw.record);
|
|
1520
|
+
expect(gobj_current_state(editor)).toBe("ST_COLUMNS");
|
|
1521
|
+
expect($in(editor, ".SCHEMA_BODY").hasAttribute("inert")).toBe(false);
|
|
1522
|
+
expect(errors()).toEqual([]);
|
|
1523
|
+
});
|
|
1524
|
+
|
|
1525
|
+
test("a drop that ends the write gives the body back too", () => {
|
|
1526
|
+
const {editor, remote, host} = build("r16k2", "db/users");
|
|
1527
|
+
start_a_write(editor, host, "name");
|
|
1528
|
+
drop(editor, remote, host);
|
|
1529
|
+
expect($in(editor, ".SCHEMA_BODY").hasAttribute("inert")).toBe(false);
|
|
1530
|
+
});
|
|
1531
|
+
});
|
|
1532
|
+
|
|
1533
|
+
describe("the writes of an import mark the topic they write", () => {
|
|
1534
|
+
|
|
1535
|
+
/* An import that adds a topic, `roles`. */
|
|
1536
|
+
const IMPORT = JSON.stringify({id: "db", topics: [
|
|
1537
|
+
{id: "users", pkey: "id", cols: [
|
|
1538
|
+
{id: "id", type: "string", flag: ["persistent", "required"]}
|
|
1539
|
+
]},
|
|
1540
|
+
{id: "roles", pkey: "id", cols: [
|
|
1541
|
+
{id: "id", type: "string", flag: ["persistent", "required"]}
|
|
1542
|
+
]}
|
|
1543
|
+
]});
|
|
1544
|
+
|
|
1545
|
+
/* The record as the store answers it: with the id it gave a new
|
|
1546
|
+
* one, and its fkeys in list_dict. */
|
|
1547
|
+
function as_the_store_answers(write)
|
|
1548
|
+
{
|
|
1549
|
+
const record = JSON.parse(JSON.stringify(write.kw.record));
|
|
1550
|
+
const to_dict = (refs) => refs.map((ref) => {
|
|
1551
|
+
const [topic_name, id, hook_name] = ref.split("^");
|
|
1552
|
+
return {topic_name, id, hook_name};
|
|
1553
|
+
});
|
|
1554
|
+
if(write.kw.topic_name === "topics") {
|
|
1555
|
+
record.id = record.id || `db.${record.value}`;
|
|
1556
|
+
record.treedbs = to_dict(record.treedbs || []);
|
|
1557
|
+
}
|
|
1558
|
+
if(write.kw.topic_name === "cols") {
|
|
1559
|
+
record.id = record.id || `${record.topics[0].split("^")[1]}.${record.value}`;
|
|
1560
|
+
record.topics = to_dict(record.topics || []);
|
|
1561
|
+
}
|
|
1562
|
+
return record;
|
|
1563
|
+
}
|
|
1564
|
+
|
|
1565
|
+
function run_the_import(editor, host)
|
|
1566
|
+
{
|
|
1567
|
+
gobj_send_event(editor, "EV_IMPORT", {}, host);
|
|
1568
|
+
gobj_send_event(editor, "EV_PREVIEW_IMPORT", {prune: false, text: IMPORT,
|
|
1569
|
+
model_gen: editor.priv.model_gen}, editor);
|
|
1570
|
+
expect(editor.priv.import_plan.writes.length).toBeGreaterThan(0);
|
|
1571
|
+
gobj_send_event(editor, "EV_APPLY_IMPORT", {model_gen: editor.priv.model_gen}, editor);
|
|
1572
|
+
expect(gobj_current_state(editor)).toBe("ST_SAVING");
|
|
1573
|
+
}
|
|
1574
|
+
|
|
1575
|
+
function answer_every_write(editor, remote)
|
|
1576
|
+
{
|
|
1577
|
+
for(let i = 0; i < 10; i++) {
|
|
1578
|
+
const [write] = take("update-node");
|
|
1579
|
+
if(!write) {
|
|
1580
|
+
break;
|
|
1581
|
+
}
|
|
1582
|
+
answer(editor, remote, write, 0, as_the_store_answers(write));
|
|
1583
|
+
}
|
|
1584
|
+
}
|
|
1585
|
+
|
|
1586
|
+
test("from another topic's screen: the topic it wrote, not the one on screen", () => {
|
|
1587
|
+
const {editor, remote, host} = build("r16m1", "db/users");
|
|
1588
|
+
run_the_import(editor, host);
|
|
1589
|
+
answer_every_write(editor, remote);
|
|
1590
|
+
expect(gobj_current_state(editor)).toBe("ST_COLUMNS");
|
|
1591
|
+
expect(editor.priv.written).toEqual({"db.roles": true});
|
|
1592
|
+
expect(errors()).toEqual([]);
|
|
1593
|
+
});
|
|
1594
|
+
|
|
1595
|
+
test("from the topics screen, where no topic is on screen", () => {
|
|
1596
|
+
const {editor, remote, host} = build("r16m2", "db");
|
|
1597
|
+
run_the_import(editor, host);
|
|
1598
|
+
answer_every_write(editor, remote);
|
|
1599
|
+
expect(gobj_current_state(editor)).toBe("ST_TOPICS");
|
|
1600
|
+
expect(editor.priv.written).toEqual({"db.roles": true});
|
|
1601
|
+
expect(errors()).toEqual([]);
|
|
1602
|
+
});
|
|
1603
|
+
|
|
1604
|
+
test("a column answered with no record: the record it queued says the topic", () => {
|
|
1605
|
+
const {editor, remote, host} = build("r16m3", "db/users");
|
|
1606
|
+
run_the_import(editor, host);
|
|
1607
|
+
const [topic] = take("update-node");
|
|
1608
|
+
expect(topic.kw.topic_name).toBe("topics");
|
|
1609
|
+
answer(editor, remote, topic, 0, as_the_store_answers(topic));
|
|
1610
|
+
const [col] = take("update-node");
|
|
1611
|
+
expect(col.kw.topic_name).toBe("cols");
|
|
1612
|
+
answer(editor, remote, col, 0, null);
|
|
1613
|
+
expect(editor.priv.written).toEqual({"db.roles": true});
|
|
1614
|
+
});
|
|
1615
|
+
});
|
|
1616
|
+
|
|
1617
|
+
describe("the reason a load failed", () => {
|
|
1618
|
+
|
|
1619
|
+
test("is a key the notice translates, not a string frozen in one language", () => {
|
|
1620
|
+
const remote = gobj_create_service("r16e1_remote", "C_TEST_REMOTE", {}, yuno);
|
|
1621
|
+
gobj_change_state(remote, "ST_SESSION");
|
|
1622
|
+
const host = gobj_create("r16e1_host", "C_TEST_EDITOR_HOST", {}, yuno);
|
|
1623
|
+
const editor = gobj_create("r16e1_editor", "C_YUI_SCHEMA_EDITOR", {
|
|
1624
|
+
gobj_remote_yuno: remote, treedb_name: "treedb_system_schema"
|
|
1625
|
+
}, host);
|
|
1626
|
+
gobj_start(editor);
|
|
1627
|
+
for(const c of take("nodes")) {
|
|
1628
|
+
answer(editor, remote, c, -1, null, "");
|
|
1629
|
+
}
|
|
1630
|
+
expect(gobj_current_state(editor)).toBe("ST_IDLE");
|
|
1631
|
+
expect(editor.priv.load_error).toBe("cannot load the schemas");
|
|
1632
|
+
/* The notice already says it: no detail repeating the title. */
|
|
1633
|
+
expect($in(editor, ".SCHEMA_NOTICE_DETAIL")).toBe(null);
|
|
1634
|
+
});
|
|
1635
|
+
|
|
1636
|
+
test("the backend's own words travel as its key", () => {
|
|
1637
|
+
const remote = gobj_create_service("r16e2_remote", "C_TEST_REMOTE", {}, yuno);
|
|
1638
|
+
gobj_change_state(remote, "ST_SESSION");
|
|
1639
|
+
const host = gobj_create("r16e2_host", "C_TEST_EDITOR_HOST", {}, yuno);
|
|
1640
|
+
const editor = gobj_create("r16e2_editor", "C_YUI_SCHEMA_EDITOR", {
|
|
1641
|
+
gobj_remote_yuno: remote, treedb_name: "treedb_system_schema"
|
|
1642
|
+
}, host);
|
|
1643
|
+
gobj_start(editor);
|
|
1644
|
+
for(const c of take("nodes")) {
|
|
1645
|
+
answer(editor, remote, c, -1, null, "treedb not found");
|
|
1646
|
+
}
|
|
1647
|
+
const $detail = $in(editor, ".SCHEMA_NOTICE_DETAIL");
|
|
1648
|
+
expect($detail.getAttribute("data-i18n")).toBe("treedb not found");
|
|
1649
|
+
});
|
|
1650
|
+
|
|
1651
|
+
test("a load that cannot leave keeps its key", () => {
|
|
1652
|
+
const remote = gobj_create_service("r16e3_remote", "C_TEST_REMOTE", {}, yuno);
|
|
1653
|
+
const host = gobj_create("r16e3_host", "C_TEST_EDITOR_HOST", {}, yuno);
|
|
1654
|
+
const editor = gobj_create("r16e3_editor", "C_YUI_SCHEMA_EDITOR", {
|
|
1655
|
+
gobj_remote_yuno: remote, treedb_name: "treedb_system_schema"
|
|
1656
|
+
}, host);
|
|
1657
|
+
gobj_start(editor);
|
|
1658
|
+
gobj_change_state(remote, "ST_DISCONNECTED");
|
|
1659
|
+
gobj_send_event(editor, "EV_REFRESH", {}, host);
|
|
1660
|
+
expect(editor.priv.load_error).toBe("cannot reach the treedb");
|
|
1661
|
+
const $detail = $in(editor, ".SCHEMA_NOTICE_DETAIL");
|
|
1662
|
+
expect($detail.getAttribute("data-i18n")).toBe("cannot reach the treedb");
|
|
1663
|
+
});
|
|
1664
|
+
});
|
|
@@ -43,6 +43,7 @@ const {
|
|
|
43
43
|
} = await import("@yuneta/gobj-js");
|
|
44
44
|
|
|
45
45
|
const logged = [];
|
|
46
|
+
const written = []; /* the EV_RECORD_WRITTEN the host heard */
|
|
46
47
|
const commands = []; /* what the fake transport was asked */
|
|
47
48
|
const told = []; /* what the fake engine was told */
|
|
48
49
|
let refuse_on_the_way = false;
|
|
@@ -65,6 +66,20 @@ beforeAll(async () => {
|
|
|
65
66
|
});
|
|
66
67
|
|
|
67
68
|
gclass_create("C_TEST_HOST", [], [["ST_IDLE", []]], {}, 0, [SDATA_END()], {}, 0, 0, 0, 0);
|
|
69
|
+
/* The host of the view: it hears what the view publishes. */
|
|
70
|
+
gclass_create(
|
|
71
|
+
"C_TEST_GRAPH_HOST",
|
|
72
|
+
[["EV_RECORD_WRITTEN", 0], ["EV_TOPIC_SELECTED", 0], ["EV_OPERATION_MODE_CHANGED", 0]],
|
|
73
|
+
[["ST_IDLE", [
|
|
74
|
+
["EV_RECORD_WRITTEN", (gobj, event, kw) => {
|
|
75
|
+
written.push(kw);
|
|
76
|
+
return 0;
|
|
77
|
+
}, null],
|
|
78
|
+
["EV_TOPIC_SELECTED", () => 0, null],
|
|
79
|
+
["EV_OPERATION_MODE_CHANGED", () => 0, null]
|
|
80
|
+
]]],
|
|
81
|
+
{}, 0, [SDATA_END()], {}, 0, 0, 0, 0
|
|
82
|
+
);
|
|
68
83
|
gclass_create(
|
|
69
84
|
"C_TEST_REMOTE",
|
|
70
85
|
[],
|
|
@@ -120,6 +135,7 @@ beforeAll(async () => {
|
|
|
120
135
|
|
|
121
136
|
beforeEach(() => {
|
|
122
137
|
logged.length = 0;
|
|
138
|
+
written.length = 0;
|
|
123
139
|
commands.length = 0;
|
|
124
140
|
told.length = 0;
|
|
125
141
|
refuse_on_the_way = false;
|
|
@@ -129,9 +145,11 @@ function build(name)
|
|
|
129
145
|
{
|
|
130
146
|
const remote = gobj_create_service(`${name}_remote`, "C_TEST_REMOTE", {}, yuno);
|
|
131
147
|
gobj_change_state(remote, "ST_SESSION");
|
|
148
|
+
const view_host = gobj_create(`${name}_view_host`, "C_TEST_GRAPH_HOST", {}, yuno);
|
|
132
149
|
const host = gobj_create_service(`${name}_graph`, "C_YUI_TREEDB_GRAPH", {
|
|
133
150
|
gobj_remote_yuno: remote,
|
|
134
|
-
treedb_name: "treedb_test"
|
|
151
|
+
treedb_name: "treedb_test",
|
|
152
|
+
subscriber: view_host
|
|
135
153
|
}, yuno);
|
|
136
154
|
const engine = host.priv.gobj_nodes_tree;
|
|
137
155
|
expect(engine).toBeTruthy();
|
|
@@ -149,13 +167,19 @@ function save_arrangement(engine, topic)
|
|
|
149
167
|
});
|
|
150
168
|
}
|
|
151
169
|
|
|
152
|
-
|
|
170
|
+
/* The answer as the real transports build it: the command frame
|
|
171
|
+
* carries ONLY the request's `__md_command__` (C_IEVENT_CLI's
|
|
172
|
+
* mt_command, gui_agent's C_AGENT_TREEDB_LINK), never the request's
|
|
173
|
+
* own kw. Echoing the whole kw here once hid that the refusal read
|
|
174
|
+
* its topic from a `record` no real answer carries. */
|
|
175
|
+
function answer(host, remote, request, result, comment, data)
|
|
153
176
|
{
|
|
177
|
+
const md_command = request.kw.__md_command__ || {};
|
|
154
178
|
gobj_send_event(host, "EV_MT_COMMAND_ANSWER", {
|
|
155
179
|
result: result,
|
|
156
180
|
comment: comment || "",
|
|
157
|
-
data: null,
|
|
158
|
-
__md_iev__: {command_stack: [{command: request.command, kw:
|
|
181
|
+
data: (data === undefined) ? null : data,
|
|
182
|
+
__md_iev__: {command_stack: [{command: request.command, kw: md_command}]}
|
|
159
183
|
}, remote);
|
|
160
184
|
}
|
|
161
185
|
|
|
@@ -172,6 +196,7 @@ describe("a __graphs__ write that does not land is said to the engine", () => {
|
|
|
172
196
|
expect(commands.length).toBe(1);
|
|
173
197
|
answer(host, remote, commands[0], -1, "not authorized");
|
|
174
198
|
expect(refused()).toEqual(["yunos"]);
|
|
199
|
+
expect(logged.filter((l) => /names no topic/.test(l.msg))).toEqual([]);
|
|
175
200
|
});
|
|
176
201
|
|
|
177
202
|
test("landed: nothing is said", () => {
|
|
@@ -206,3 +231,53 @@ describe("a __graphs__ write that does not land is said to the engine", () => {
|
|
|
206
231
|
expect(refused()).toEqual([]);
|
|
207
232
|
});
|
|
208
233
|
});
|
|
234
|
+
|
|
235
|
+
/* What THIS view wrote, told to its host. The answer carries back only
|
|
236
|
+
* `__md_command__`: `treedb_name` and `record` read from that frame
|
|
237
|
+
* arrived empty with every real transport. */
|
|
238
|
+
describe("EV_RECORD_WRITTEN carries what it says", () => {
|
|
239
|
+
|
|
240
|
+
test("an update: the treedb, the topic and the record the store answered", () => {
|
|
241
|
+
const {remote, host, engine} = build("w1");
|
|
242
|
+
logged.length = 0; /* the fake engine's fixture, not the check */
|
|
243
|
+
gobj_send_event(host, "EV_UPDATE_NODE", {
|
|
244
|
+
topic_name: "yunos", record: {id: "1", x: 1}
|
|
245
|
+
}, engine);
|
|
246
|
+
const [write] = commands;
|
|
247
|
+
answer(host, remote, write, 0, "", {id: "1", x: 1, y: 2});
|
|
248
|
+
expect(written).toEqual([{
|
|
249
|
+
treedb_name: "treedb_test",
|
|
250
|
+
topic_name: "yunos",
|
|
251
|
+
record: {id: "1", x: 1, y: 2},
|
|
252
|
+
created: false,
|
|
253
|
+
command: "update-node"
|
|
254
|
+
}]);
|
|
255
|
+
expect(logged.filter((l) => l.level === "error")).toEqual([]);
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
test("a link: the child's topic, both refs, and the child the store answered", () => {
|
|
259
|
+
const {remote, host, engine} = build("w2");
|
|
260
|
+
gobj_send_event(host, "EV_LINK_NODES", {
|
|
261
|
+
parent_ref: "realms^r1^yunos", child_ref: "yunos^1"
|
|
262
|
+
}, engine);
|
|
263
|
+
const [link] = commands;
|
|
264
|
+
expect(link.command).toBe("link-nodes");
|
|
265
|
+
answer(host, remote, link, 0, "", {id: "1", realms: ["realms^r1^yunos"]});
|
|
266
|
+
expect(written).toEqual([{
|
|
267
|
+
treedb_name: "treedb_test",
|
|
268
|
+
topic_name: "yunos",
|
|
269
|
+
record: {id: "1", realms: ["realms^r1^yunos"]},
|
|
270
|
+
parent_ref: "realms^r1^yunos",
|
|
271
|
+
child_ref: "yunos^1",
|
|
272
|
+
created: false,
|
|
273
|
+
command: "link-nodes"
|
|
274
|
+
}]);
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
test("the arrangement (__graphs__) is not reported", () => {
|
|
278
|
+
const {remote, host, engine} = build("w3");
|
|
279
|
+
save_arrangement(engine, "yunos");
|
|
280
|
+
answer(host, remote, commands[0], 0, "", {id: "yunos"});
|
|
281
|
+
expect(written).toEqual([]);
|
|
282
|
+
});
|
|
283
|
+
});
|