@yuneta/gobj-ui 7.25.19 → 7.25.20
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 +49 -7
- package/dist/gobj-ui.cjs.js +78 -23
- package/dist/gobj-ui.es.js +78 -23
- package/package.json +2 -2
- package/src/c_g6_nodes_tree.js +29 -4
- package/src/c_yui_json.js +28 -5
- package/src/c_yui_json_pad.js +3 -2
- package/src/c_yui_schema_editor.js +14 -1
- package/src/c_yui_treedb_graph.js +24 -10
- package/src/c_yui_treedb_topic_with_form.js +3 -2
- package/src/c_yui_treedb_topics.js +1 -1
- package/src/form_json_expand.wiring.test.js +86 -3
- package/src/g6_graphs_write_owed.wiring.test.js +77 -0
- package/src/schema_editor_transport.wiring.test.js +89 -5
- package/src/treedb_graph_writes.wiring.test.js +38 -4
package/src/c_g6_nodes_tree.js
CHANGED
|
@@ -494,6 +494,7 @@ let PRIVATE_DATA = {
|
|
|
494
494
|
_graph_properties: {}, // topic_name → {nodes: {node_id: {x,y,size,...}}}
|
|
495
495
|
_saved_graph_properties: {}, // the same, as the BACKEND has it: what a Save compares against
|
|
496
496
|
_graphs_writes_owed: {}, // topic_name → true: a __graphs__ write the host said did not land
|
|
497
|
+
_graphs_load: 0, // the LOAD a __graphs__ write belongs to: EV_CLEAR_DATA starts another
|
|
497
498
|
yet_showed: false,
|
|
498
499
|
edit_mode: false,
|
|
499
500
|
operation_mode: null,
|
|
@@ -3301,7 +3302,10 @@ function save_topic_graph_properties(gobj, topic_name)
|
|
|
3301
3302
|
list_dict: true,
|
|
3302
3303
|
autolink: false,
|
|
3303
3304
|
create: true
|
|
3304
|
-
}
|
|
3305
|
+
},
|
|
3306
|
+
/* Echoed back with a refusal (EV_GRAPHS_WRITE_REFUSED), so
|
|
3307
|
+
* one answered after a reload is told from one of THIS load. */
|
|
3308
|
+
graphs_load: priv._graphs_load
|
|
3305
3309
|
};
|
|
3306
3310
|
gobj_publish_event(gobj, "EV_UPDATE_NODE", kw_update);
|
|
3307
3311
|
}
|
|
@@ -10372,6 +10376,9 @@ function ac_clear_data(gobj, event, kw, src)
|
|
|
10372
10376
|
* reload brings what the backend holds, and that is what the view
|
|
10373
10377
|
* will hold. */
|
|
10374
10378
|
priv._graphs_writes_owed = {};
|
|
10379
|
+
/* And a write still on its way belongs to it: its refusal, if
|
|
10380
|
+
* one comes, is about an arrangement this view no longer holds. */
|
|
10381
|
+
priv._graphs_load++;
|
|
10375
10382
|
|
|
10376
10383
|
/* The node counters belong to the data that is going away: a refresh
|
|
10377
10384
|
* that kept them would decide the layout on the previous treedb. */
|
|
@@ -10537,12 +10544,19 @@ function ac_save_graph(gobj, event, kw, src)
|
|
|
10537
10544
|
/************************************************************
|
|
10538
10545
|
* The host says a write of `__graphs__` did not land: the
|
|
10539
10546
|
* backend refused it, the transport refused it on the way out,
|
|
10540
|
-
* or there was no session to send it on.
|
|
10547
|
+
* or there was no session to send it on.
|
|
10548
|
+
* kw: {topic, graphs_load}
|
|
10541
10549
|
*
|
|
10542
10550
|
* save_topic_graph_properties() recorded it as saved before it
|
|
10543
10551
|
* left. Forgotten now, and the Save is lit again: the next Save
|
|
10544
10552
|
* writes the topic again. Before, the view believed the backend
|
|
10545
10553
|
* held the arrangement, and the next Save had nothing to write.
|
|
10554
|
+
*
|
|
10555
|
+
* `graphs_load` is the load the write was sent from, echoed by the
|
|
10556
|
+
* host. A refusal of an EARLIER load is about an arrangement the
|
|
10557
|
+
* reload replaced with what the backend holds: applied, it lit the
|
|
10558
|
+
* Save over nothing changed (before gobj-ui 7.25.20). A host that
|
|
10559
|
+
* echoes no load is taken at its word, as before.
|
|
10546
10560
|
************************************************************/
|
|
10547
10561
|
function ac_graphs_write_refused(gobj, event, kw, src)
|
|
10548
10562
|
{
|
|
@@ -10553,6 +10567,11 @@ function ac_graphs_write_refused(gobj, event, kw, src)
|
|
|
10553
10567
|
log_error(`${gobj_short_name(gobj)}: ${event} names no topic`);
|
|
10554
10568
|
return -1;
|
|
10555
10569
|
}
|
|
10570
|
+
if(kw.graphs_load !== undefined && kw.graphs_load !== priv._graphs_load) {
|
|
10571
|
+
log_warning(`${gobj_short_name(gobj)}: a refused write of the arrangement ` +
|
|
10572
|
+
`of '${topic_name}' belongs to an earlier load: ignored`);
|
|
10573
|
+
return 0;
|
|
10574
|
+
}
|
|
10556
10575
|
log_warning(`${gobj_short_name(gobj)}: the arrangement of '${topic_name}' ` +
|
|
10557
10576
|
`was not saved: the next Save writes it again`);
|
|
10558
10577
|
forget_refused_graphs_write(priv._saved_graph_properties, topic_name);
|
|
@@ -10600,10 +10619,16 @@ function ac_node_created(gobj, event, kw, src)
|
|
|
10600
10619
|
return 0;
|
|
10601
10620
|
}
|
|
10602
10621
|
|
|
10603
|
-
|
|
10622
|
+
/* The first record of a topic's arrangement. Only THAT topic
|
|
10623
|
+
* moves, as for an update echo: build_graph_properties() took
|
|
10624
|
+
* every topic's saved copy from the live objects the view edits
|
|
10625
|
+
* in place, so an unsaved change of another topic counted as
|
|
10626
|
+
* saved and its Save was lost (before gobj-ui 7.25.20). */
|
|
10604
10627
|
if(topic_name === '__graphs__') {
|
|
10605
10628
|
priv.__graphs__.push(node);
|
|
10606
|
-
|
|
10629
|
+
apply_graphs_echo(
|
|
10630
|
+
priv._graph_properties, priv._saved_graph_properties, priv.__graphs__, node
|
|
10631
|
+
);
|
|
10607
10632
|
settle_owed_graphs_write(gobj, node);
|
|
10608
10633
|
return 0;
|
|
10609
10634
|
}
|
package/src/c_yui_json.js
CHANGED
|
@@ -64,6 +64,7 @@ import {
|
|
|
64
64
|
gclass_create,
|
|
65
65
|
gclass_find_by_name,
|
|
66
66
|
log_error,
|
|
67
|
+
log_warning,
|
|
67
68
|
gobj_read_pointer_attr,
|
|
68
69
|
gobj_parent,
|
|
69
70
|
gobj_subscribe_event,
|
|
@@ -898,8 +899,12 @@ function push_collapsed_row(ctx, value, segments, depth, key, rows)
|
|
|
898
899
|
children.push(['span', {class: 'JSON_STUB_HINT is-size-7 ml-2',
|
|
899
900
|
'data-i18n': is_pending ? 'loading' : 'click to load'},
|
|
900
901
|
is_pending ? 'loading' : 'click to load']);
|
|
901
|
-
if(err) {
|
|
902
|
-
children.push(['span', {class: 'JSON_STUB_ERR has-text-danger is-size-7 ml-2'
|
|
902
|
+
if(err && err.is_key) {
|
|
903
|
+
children.push(['span', {class: 'JSON_STUB_ERR has-text-danger is-size-7 ml-2',
|
|
904
|
+
'data-i18n': err.error}, t(err.error)]);
|
|
905
|
+
} else if(err) {
|
|
906
|
+
children.push(['span', {class: 'JSON_STUB_ERR has-text-danger is-size-7 ml-2'},
|
|
907
|
+
String(err.error)]);
|
|
903
908
|
}
|
|
904
909
|
|
|
905
910
|
let attrs = {
|
|
@@ -1117,17 +1122,35 @@ function ac_subtree_loaded(gobj, event, kw, src)
|
|
|
1117
1122
|
}
|
|
1118
1123
|
|
|
1119
1124
|
/************************************************************
|
|
1120
|
-
* EV_SUBTREE_ERROR { path, error
|
|
1125
|
+
* EV_SUBTREE_ERROR { path, error | i18n, by_design }
|
|
1126
|
+
* — mark the branch that was not loaded.
|
|
1127
|
+
*
|
|
1128
|
+
* `error` is text, drawn as it came (a backend's comment).
|
|
1129
|
+
* `i18n` is a KEY, drawn as t(key) with data-i18n so the stub
|
|
1130
|
+
* changes language with the app (a text translated by the host
|
|
1131
|
+
* stays in the language it was sent in). The name is the one a
|
|
1132
|
+
* locale check scans for (`i18n: "<key>"`), so the key of every
|
|
1133
|
+
* host stays visible to it.
|
|
1134
|
+
* `by_design`: the host answers that it CANNOT read a subtree
|
|
1135
|
+
* (a form holds records, a pasted pad has no backend) -- that is
|
|
1136
|
+
* not a failure, and it is logged as a warning. Anything else is
|
|
1137
|
+
* a load that failed, logged as an error.
|
|
1121
1138
|
************************************************************/
|
|
1122
1139
|
function ac_subtree_error(gobj, event, kw, src)
|
|
1123
1140
|
{
|
|
1124
1141
|
let priv = gobj.priv;
|
|
1125
1142
|
|
|
1126
1143
|
let path = kw.path || "";
|
|
1144
|
+
let is_key = !!kw.i18n;
|
|
1145
|
+
let error = kw.i18n || kw.error || "error";
|
|
1127
1146
|
priv.pending.delete(path);
|
|
1128
|
-
priv.errors.set(path,
|
|
1147
|
+
priv.errors.set(path, {error: error, is_key: is_key});
|
|
1129
1148
|
|
|
1130
|
-
|
|
1149
|
+
if(kw.by_design) {
|
|
1150
|
+
log_warning(`${GCLASS_NAME}: subtree not loaded at '${path}': ${error}`);
|
|
1151
|
+
} else {
|
|
1152
|
+
log_error(`${GCLASS_NAME}: subtree load failed at '${path}': ${error}`);
|
|
1153
|
+
}
|
|
1131
1154
|
|
|
1132
1155
|
render_view(gobj);
|
|
1133
1156
|
return 0;
|
package/src/c_yui_json_pad.js
CHANGED
|
@@ -798,8 +798,9 @@ function ac_hide_diff(gobj, event, kw, src)
|
|
|
798
798
|
function ac_expand_path(gobj, event, kw, src)
|
|
799
799
|
{
|
|
800
800
|
gobj_send_event(src, "EV_SUBTREE_ERROR", {
|
|
801
|
-
path:
|
|
802
|
-
|
|
801
|
+
path: (kw && kw.path) || "",
|
|
802
|
+
i18n: "collapsed in the source",
|
|
803
|
+
by_design: true
|
|
803
804
|
}, gobj);
|
|
804
805
|
return 0;
|
|
805
806
|
}
|
|
@@ -2820,6 +2820,19 @@ function run_next_write(gobj)
|
|
|
2820
2820
|
let command = write_command(write.op);
|
|
2821
2821
|
let options = write_options(write.op);
|
|
2822
2822
|
|
|
2823
|
+
/* Out of session a write is not sent, and ends here. A direct
|
|
2824
|
+
* C_IEVENT_CLI answers a command out of session with null -- the
|
|
2825
|
+
* answer of a request that LEFT -- so without this the editor
|
|
2826
|
+
* waited in ST_SAVING, inert, for an answer that never comes
|
|
2827
|
+
* (before gobj-ui 7.25.20). What the queue wrote before is not
|
|
2828
|
+
* known to the next session: it reads the model again. */
|
|
2829
|
+
if(!transport_in_session(gobj)) {
|
|
2830
|
+
log_warning(`${gobj_short_name(gobj)}: no session, '${command}' ` +
|
|
2831
|
+
`of '${write.topic_name}' was not sent`);
|
|
2832
|
+
priv.reload_on_open = true;
|
|
2833
|
+
return end_writes(gobj, "cannot reach the treedb");
|
|
2834
|
+
}
|
|
2835
|
+
|
|
2823
2836
|
priv.write_tag++;
|
|
2824
2837
|
if(remote_command(gobj, command, {
|
|
2825
2838
|
topic_name: write.topic_name,
|
|
@@ -2827,7 +2840,7 @@ function run_next_write(gobj)
|
|
|
2827
2840
|
options: options,
|
|
2828
2841
|
record_id: write.record.id || ""
|
|
2829
2842
|
}, {write: priv.write_tag}) < 0) {
|
|
2830
|
-
return end_writes(gobj,
|
|
2843
|
+
return end_writes(gobj, "cannot reach the treedb"); /* a key: the toast translates it */
|
|
2831
2844
|
}
|
|
2832
2845
|
return 0;
|
|
2833
2846
|
}
|
|
@@ -1325,7 +1325,7 @@ function request_print_tranger(gobj, path)
|
|
|
1325
1325
|
let jv = priv.json_gobj;
|
|
1326
1326
|
if(path && jv && is_gobj(jv) && !gobj_is_destroying(jv)) {
|
|
1327
1327
|
gobj_send_event(jv, "EV_SUBTREE_ERROR",
|
|
1328
|
-
{path: path,
|
|
1328
|
+
{path: path, i18n: "no session"}, gobj);
|
|
1329
1329
|
}
|
|
1330
1330
|
return;
|
|
1331
1331
|
}
|
|
@@ -1498,7 +1498,7 @@ function treedb_create_node(gobj, treedb_name, topic_name, record, options)
|
|
|
1498
1498
|
/************************************************************
|
|
1499
1499
|
* Command to remote service
|
|
1500
1500
|
************************************************************/
|
|
1501
|
-
function treedb_update_node(gobj, treedb_name, topic_name, record, options)
|
|
1501
|
+
function treedb_update_node(gobj, treedb_name, topic_name, record, options, graphs_load)
|
|
1502
1502
|
{
|
|
1503
1503
|
let priv = gobj.priv;
|
|
1504
1504
|
|
|
@@ -1525,6 +1525,11 @@ function treedb_update_node(gobj, treedb_name, topic_name, record, options)
|
|
|
1525
1525
|
* arranges, so that topic has to travel here. */
|
|
1526
1526
|
if(topic_name === "__graphs__") {
|
|
1527
1527
|
kw.__md_command__.graph_topic = graph_topic_of(record);
|
|
1528
|
+
/* And the engine's LOAD it was sent from: a refusal answered
|
|
1529
|
+
* after a reload is not about the arrangement shown now. */
|
|
1530
|
+
if(graphs_load !== undefined) {
|
|
1531
|
+
kw.__md_command__.graphs_load = graphs_load;
|
|
1532
|
+
}
|
|
1528
1533
|
}
|
|
1529
1534
|
|
|
1530
1535
|
let ret = gobj_command(priv.gobj_remote_yuno,
|
|
@@ -2162,7 +2167,8 @@ function ac_mt_command_answer(gobj, event, kw, src)
|
|
|
2162
2167
|
if(result < 0 && command === "update-node") {
|
|
2163
2168
|
graphs_write_refused(gobj,
|
|
2164
2169
|
kw_get_str(gobj, kw_command, "topic_name", "", 0),
|
|
2165
|
-
kw_get_str(gobj, kw_command, "graph_topic", "", 0)
|
|
2170
|
+
kw_get_str(gobj, kw_command, "graph_topic", "", 0),
|
|
2171
|
+
kw_command ? kw_command.graphs_load : undefined);
|
|
2166
2172
|
}
|
|
2167
2173
|
if(result >= 0 &&
|
|
2168
2174
|
kw_get_str(gobj, kw_command, "topic_name", "", 0) !== "__graphs__") {
|
|
@@ -2533,9 +2539,10 @@ function ac_update_node(gobj, event, kw, src)
|
|
|
2533
2539
|
let topic_name = kw.topic_name;
|
|
2534
2540
|
let record = kw.record;
|
|
2535
2541
|
let options = kw.options || {};
|
|
2542
|
+
let graphs_load = kw.graphs_load;
|
|
2536
2543
|
|
|
2537
2544
|
if(refuse_if_readonly(gobj, event)) {
|
|
2538
|
-
graphs_write_refused(gobj, topic_name, graph_topic_of(record));
|
|
2545
|
+
graphs_write_refused(gobj, topic_name, graph_topic_of(record), graphs_load);
|
|
2539
2546
|
return -1;
|
|
2540
2547
|
}
|
|
2541
2548
|
|
|
@@ -2545,7 +2552,7 @@ function ac_update_node(gobj, event, kw, src)
|
|
|
2545
2552
|
if(topic_name === "__graphs__" && !is_connected(gobj)) {
|
|
2546
2553
|
log_warning(`${gobj_short_name(gobj)}: no session, the arrangement ` +
|
|
2547
2554
|
`was not sent`);
|
|
2548
|
-
graphs_write_refused(gobj, topic_name, graph_topic_of(record));
|
|
2555
|
+
graphs_write_refused(gobj, topic_name, graph_topic_of(record), graphs_load);
|
|
2549
2556
|
return -1;
|
|
2550
2557
|
}
|
|
2551
2558
|
|
|
@@ -2554,10 +2561,11 @@ function ac_update_node(gobj, event, kw, src)
|
|
|
2554
2561
|
treedb_name,
|
|
2555
2562
|
topic_name,
|
|
2556
2563
|
record,
|
|
2557
|
-
options
|
|
2564
|
+
options,
|
|
2565
|
+
graphs_load
|
|
2558
2566
|
);
|
|
2559
2567
|
if(ret < 0) {
|
|
2560
|
-
graphs_write_refused(gobj, topic_name, graph_topic_of(record));
|
|
2568
|
+
graphs_write_refused(gobj, topic_name, graph_topic_of(record), graphs_load);
|
|
2561
2569
|
}
|
|
2562
2570
|
return ret;
|
|
2563
2571
|
}
|
|
@@ -2578,9 +2586,11 @@ function graph_topic_of(record)
|
|
|
2578
2586
|
* (C_G6_NODES_TREE) took it for granted when it left -- nothing
|
|
2579
2587
|
* answers it when it lands -- so it is told, and its next Save
|
|
2580
2588
|
* writes the topic again. Other topics are the operator's data:
|
|
2581
|
-
* the answer's error says what happened to them.
|
|
2589
|
+
* the answer's error says what happened to them. `graphs_load` is
|
|
2590
|
+
* the engine's load the write was sent from, handed back so it can
|
|
2591
|
+
* tell a refusal of an earlier load from one of the current.
|
|
2582
2592
|
************************************************************/
|
|
2583
|
-
function graphs_write_refused(gobj, topic_name, topic)
|
|
2593
|
+
function graphs_write_refused(gobj, topic_name, topic, graphs_load)
|
|
2584
2594
|
{
|
|
2585
2595
|
let priv = gobj.priv;
|
|
2586
2596
|
|
|
@@ -2594,7 +2604,11 @@ function graphs_write_refused(gobj, topic_name, topic)
|
|
|
2594
2604
|
if(!priv.gobj_nodes_tree) {
|
|
2595
2605
|
return; /* no engine: nothing believes it was saved */
|
|
2596
2606
|
}
|
|
2597
|
-
|
|
2607
|
+
let kw = {topic: topic};
|
|
2608
|
+
if(graphs_load !== undefined) {
|
|
2609
|
+
kw.graphs_load = graphs_load;
|
|
2610
|
+
}
|
|
2611
|
+
gobj_send_event(priv.gobj_nodes_tree, "EV_GRAPHS_WRITE_REFUSED", kw, gobj);
|
|
2598
2612
|
}
|
|
2599
2613
|
|
|
2600
2614
|
/********************************************
|
|
@@ -3711,8 +3711,9 @@ function ac_expand_path(gobj, event, kw, src)
|
|
|
3711
3711
|
let path = (kw && kw.path) || "";
|
|
3712
3712
|
|
|
3713
3713
|
gobj_send_event(src, "EV_SUBTREE_ERROR", {
|
|
3714
|
-
path:
|
|
3715
|
-
|
|
3714
|
+
path: path,
|
|
3715
|
+
i18n: "this part cannot be loaded here",
|
|
3716
|
+
by_design: true
|
|
3716
3717
|
}, gobj);
|
|
3717
3718
|
return 0;
|
|
3718
3719
|
}
|
|
@@ -1955,7 +1955,7 @@ function request_print_tranger(gobj, path)
|
|
|
1955
1955
|
let jv = priv.json_gobj;
|
|
1956
1956
|
if(path && jv && is_gobj(jv) && !gobj_is_destroying(jv)) {
|
|
1957
1957
|
gobj_send_event(jv, "EV_SUBTREE_ERROR",
|
|
1958
|
-
{path: path,
|
|
1958
|
+
{path: path, i18n: "no session"}, gobj);
|
|
1959
1959
|
}
|
|
1960
1960
|
return;
|
|
1961
1961
|
}
|
|
@@ -43,10 +43,12 @@ const {
|
|
|
43
43
|
SDATA_END, gclass_create,
|
|
44
44
|
gobj_start_up, gobj_create_yuno, gobj_create, gobj_create_service,
|
|
45
45
|
gobj_start, gobj_send_event, gobj_current_state,
|
|
46
|
+
gobj_create_pure_child, gobj_read_attr,
|
|
46
47
|
set_log_callback,
|
|
47
48
|
} = await import("@yuneta/gobj-js");
|
|
48
49
|
const {register_c_yui_shell} = await import("./c_yui_shell.js");
|
|
49
50
|
const {register_c_yui_json} = await import("./c_yui_json.js");
|
|
51
|
+
const {t} = await import("i18next");
|
|
50
52
|
const {register_c_yui_treedb_topic_with_form} = await import("./c_yui_treedb_topic_with_form.js");
|
|
51
53
|
|
|
52
54
|
const logged = [];
|
|
@@ -86,6 +88,11 @@ function errors()
|
|
|
86
88
|
return logged.filter((l) => l.level === "error").map((l) => l.msg);
|
|
87
89
|
}
|
|
88
90
|
|
|
91
|
+
function warnings()
|
|
92
|
+
{
|
|
93
|
+
return logged.filter((l) => l.level === "warning").map((l) => l.msg);
|
|
94
|
+
}
|
|
95
|
+
|
|
89
96
|
describe("a viewer of the form asks to load a truncated part", () => {
|
|
90
97
|
|
|
91
98
|
test("the form answers that it cannot, and the stub stops loading", () => {
|
|
@@ -112,10 +119,86 @@ describe("a viewer of the form asks to load a truncated part", () => {
|
|
|
112
119
|
gobj_send_event(viewer, "EV_EXPAND_COLLAPSED", {path: "cols`name", size: 40}, viewer);
|
|
113
120
|
|
|
114
121
|
expect(viewer.priv.pending.has("cols`name")).toBe(false);
|
|
115
|
-
|
|
116
|
-
|
|
122
|
+
/* The KEY travels, not its translation: the stub draws t(key)
|
|
123
|
+
* with data-i18n, so it changes language with the app. */
|
|
124
|
+
expect(viewer.priv.errors.get("cols`name")).toEqual(
|
|
125
|
+
{error: "this part cannot be loaded here", is_key: true});
|
|
126
|
+
/* A host saying it cannot, by design, is a WARNING: an ERROR
|
|
127
|
+
* on every click broke the clean-console check of a deploy and
|
|
128
|
+
* reached the backend through the remote log. */
|
|
129
|
+
expect(errors()).toEqual([]);
|
|
130
|
+
expect(warnings()).toEqual([
|
|
131
|
+
"C_YUI_JSON: subtree not loaded at 'cols`name': this part cannot be loaded here"
|
|
132
|
+
]);
|
|
133
|
+
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
describe("the stub of a subtree that was not loaded", () => {
|
|
138
|
+
|
|
139
|
+
/* A host answering with a KEY that it cannot, by design. */
|
|
140
|
+
function make_viewer(name, answer)
|
|
141
|
+
{
|
|
142
|
+
gclass_create(`C_TEST_JSON_HOST_${name}`, [["EV_EXPAND_PATH", 0]], [
|
|
143
|
+
["ST_IDLE", [["EV_EXPAND_PATH", function(gobj, event, kw, src) {
|
|
144
|
+
gobj_send_event(src, "EV_SUBTREE_ERROR",
|
|
145
|
+
Object.assign({path: kw.path}, answer), gobj);
|
|
146
|
+
return 0;
|
|
147
|
+
}, null]]]
|
|
148
|
+
], {}, 0, [SDATA_END()], {}, 0, 0, 0, 0);
|
|
149
|
+
const host = gobj_create(`${name}_host`, `C_TEST_JSON_HOST_${name}`, {}, yuno);
|
|
150
|
+
gobj_start(host);
|
|
151
|
+
const viewer = gobj_create_pure_child(`${name}_jv`, "C_YUI_JSON", {
|
|
152
|
+
json_data: {a: {__collapsed__: {size: 3, path: "a"}}}
|
|
153
|
+
}, host);
|
|
154
|
+
gobj_start(viewer);
|
|
155
|
+
return viewer;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function stub_error(viewer)
|
|
159
|
+
{
|
|
160
|
+
return gobj_read_attr(viewer, "$container").querySelector(".JSON_STUB_ERR");
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
test("a key is drawn translated, carries data-i18n, and follows the language", async () => {
|
|
164
|
+
const i18next = (await import("i18next")).default;
|
|
165
|
+
i18next.addResource("es", "translation",
|
|
166
|
+
"this part cannot be loaded here", "esta parte no se puede cargar aqui");
|
|
167
|
+
|
|
168
|
+
const viewer = make_viewer("s1",
|
|
169
|
+
{i18n: "this part cannot be loaded here", by_design: true});
|
|
170
|
+
logged.length = 0;
|
|
171
|
+
gobj_send_event(viewer, "EV_EXPAND_COLLAPSED", {path: "a", size: 3}, viewer);
|
|
172
|
+
|
|
173
|
+
let el = stub_error(viewer);
|
|
174
|
+
expect(el).toBeTruthy();
|
|
175
|
+
expect(el.getAttribute("data-i18n")).toBe("this part cannot be loaded here");
|
|
176
|
+
expect(el.textContent).toBe("this part cannot be loaded here");
|
|
177
|
+
expect(errors()).toEqual([]);
|
|
178
|
+
expect(warnings()).toEqual([
|
|
179
|
+
"C_YUI_JSON: subtree not loaded at 'a': this part cannot be loaded here"
|
|
180
|
+
]);
|
|
181
|
+
|
|
182
|
+
await i18next.changeLanguage("es");
|
|
183
|
+
try {
|
|
184
|
+
gobj_send_event(viewer, "EV_LANGUAGE_CHANGED", {}, viewer);
|
|
185
|
+
expect(stub_error(viewer).textContent).toBe("esta parte no se puede cargar aqui");
|
|
186
|
+
expect(t("this part cannot be loaded here")).toBe("esta parte no se puede cargar aqui");
|
|
187
|
+
} finally {
|
|
188
|
+
await i18next.changeLanguage("en");
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
test("a failure in free text is drawn as it came, and is an ERROR", () => {
|
|
193
|
+
const viewer = make_viewer("s2", {error: "print-tranger failed"});
|
|
194
|
+
logged.length = 0;
|
|
195
|
+
gobj_send_event(viewer, "EV_EXPAND_COLLAPSED", {path: "a", size: 3}, viewer);
|
|
196
|
+
|
|
197
|
+
let el = stub_error(viewer);
|
|
198
|
+
expect(el.getAttribute("data-i18n")).toBe(null);
|
|
199
|
+
expect(el.textContent).toBe("print-tranger failed");
|
|
117
200
|
expect(errors()).toEqual([
|
|
118
|
-
"C_YUI_JSON: subtree load failed at '
|
|
201
|
+
"C_YUI_JSON: subtree load failed at 'a': print-tranger failed"
|
|
119
202
|
]);
|
|
120
203
|
});
|
|
121
204
|
});
|
|
@@ -235,3 +235,80 @@ describe("a refused __graphs__ write keeps the Save lit", () => {
|
|
|
235
235
|
expect(errors()).toEqual([]);
|
|
236
236
|
});
|
|
237
237
|
});
|
|
238
|
+
|
|
239
|
+
/* A refusal is about the LOAD its write was sent from. Before gobj-ui
|
|
240
|
+
* 7.25.20 it carried only the topic: Save, then Refresh, then the
|
|
241
|
+
* refusal landed and was applied to the FRESH load -- the Save lit
|
|
242
|
+
* over nothing changed, and pressing it wrote a record identical to
|
|
243
|
+
* what the backend holds. */
|
|
244
|
+
describe("a refusal of an earlier load is not applied to the current one", () => {
|
|
245
|
+
|
|
246
|
+
test("the write carries its load, and a refusal of it counts", () => {
|
|
247
|
+
const g = build("edition");
|
|
248
|
+
g.tree.priv._graph_properties.users.nodes.u1.x = 99;
|
|
249
|
+
gobj_send_event(g.tree, "EV_SAVE_GRAPH", {}, g.tree);
|
|
250
|
+
const [write] = published.filter((p) => p.event === "EV_UPDATE_NODE");
|
|
251
|
+
expect(typeof write.kw.graphs_load).toBe("number");
|
|
252
|
+
|
|
253
|
+
gobj_send_event(g.tree, "EV_GRAPHS_WRITE_REFUSED",
|
|
254
|
+
{topic: "users", graphs_load: write.kw.graphs_load}, g.tree);
|
|
255
|
+
expect(g.lit()).toBe(true);
|
|
256
|
+
expect(g.tree.priv._saved_graph_properties.users).toBe(undefined);
|
|
257
|
+
expect(errors()).toEqual([]);
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
test("Save, reload, then the refusal: ignored, and said", () => {
|
|
261
|
+
const g = build("edition");
|
|
262
|
+
g.tree.priv._graph_properties.users.nodes.u1.x = 99;
|
|
263
|
+
gobj_send_event(g.tree, "EV_SAVE_GRAPH", {}, g.tree);
|
|
264
|
+
const [write] = published.filter((p) => p.event === "EV_UPDATE_NODE");
|
|
265
|
+
|
|
266
|
+
gobj_send_event(g.tree, "EV_CLEAR_DATA", {}, g.tree);
|
|
267
|
+
/* The reload brings what the backend holds. */
|
|
268
|
+
g.tree.priv._graph_properties = {users: {nodes: {u1: {x: 10, y: 20}}}};
|
|
269
|
+
g.tree.priv._saved_graph_properties = {users: {nodes: {u1: {x: 10, y: 20}}}};
|
|
270
|
+
gobj_send_event(g.tree, "EV_SET_OPERATION_MODE", {operation_mode: "edition"}, g.tree);
|
|
271
|
+
logged.length = 0;
|
|
272
|
+
|
|
273
|
+
gobj_send_event(g.tree, "EV_GRAPHS_WRITE_REFUSED",
|
|
274
|
+
{topic: "users", graphs_load: write.kw.graphs_load}, g.tree);
|
|
275
|
+
expect(g.lit()).toBe(false);
|
|
276
|
+
expect(g.tree.priv._saved_graph_properties.users).toEqual({nodes: {u1: {x: 10, y: 20}}});
|
|
277
|
+
expect(logged.map((l) => [l.level, l.msg.replace(/^\S+: /, "")])).toEqual([
|
|
278
|
+
["warning", "a refused write of the arrangement of 'users' belongs to an earlier load: ignored"]
|
|
279
|
+
]);
|
|
280
|
+
});
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
/* The FIRST record of a topic's arrangement arrives as a CREATE echo
|
|
284
|
+
* (from this browser or another). Before gobj-ui 7.25.20 it rebuilt
|
|
285
|
+
* every topic's saved copy from the live objects the view edits in
|
|
286
|
+
* place, so an unsaved change of ANOTHER topic counted as saved and
|
|
287
|
+
* the next Save did not write it. */
|
|
288
|
+
describe("a __graphs__ create echo moves only its own topic", () => {
|
|
289
|
+
|
|
290
|
+
test("an unsaved change of another topic stays unsaved", () => {
|
|
291
|
+
const g = build("edition");
|
|
292
|
+
g.tree.priv.descs = {};
|
|
293
|
+
g.tree.priv.__graphs__ = [
|
|
294
|
+
{id: "users", topic: "users", active: true,
|
|
295
|
+
properties: g.tree.priv._graph_properties.users}
|
|
296
|
+
];
|
|
297
|
+
/* Edited in place, not saved. */
|
|
298
|
+
g.tree.priv._graph_properties.users.nodes.u1.x = 77;
|
|
299
|
+
|
|
300
|
+
gobj_send_event(g.tree, "EV_NODE_CREATED", {
|
|
301
|
+
topic_name: "__graphs__",
|
|
302
|
+
node: {id: "roles", topic: "roles", active: true,
|
|
303
|
+
properties: {nodes: {r1: {x: 1, y: 2}}}}
|
|
304
|
+
}, g.tree);
|
|
305
|
+
|
|
306
|
+
expect(g.tree.priv._saved_graph_properties.users).toEqual({nodes: {u1: {x: 10, y: 20}}});
|
|
307
|
+
expect(g.tree.priv._saved_graph_properties.roles).toEqual({nodes: {r1: {x: 1, y: 2}}});
|
|
308
|
+
|
|
309
|
+
gobj_send_event(g.tree, "EV_SAVE_GRAPH", {}, g.tree);
|
|
310
|
+
const writes = published.filter((p) => p.event === "EV_UPDATE_NODE");
|
|
311
|
+
expect(writes.map((w) => w.kw.record.id)).toEqual(["users"]);
|
|
312
|
+
expect(errors()).toEqual([]);
|
|
313
|
+
});
|
|
314
|
+
});
|
|
@@ -37,7 +37,7 @@ const {
|
|
|
37
37
|
gobj_start_up, gobj_create_yuno, gobj_create, gobj_create_service,
|
|
38
38
|
gobj_start, gobj_send_event, gobj_read_attr,
|
|
39
39
|
gobj_change_state, gobj_current_state,
|
|
40
|
-
set_log_callback,
|
|
40
|
+
set_log_callback, log_error,
|
|
41
41
|
} = await import("@yuneta/gobj-js");
|
|
42
42
|
const {register_c_yui_schema_editor} = await import("./c_yui_schema_editor.js");
|
|
43
43
|
|
|
@@ -56,13 +56,33 @@ const RECORDS = {
|
|
|
56
56
|
],
|
|
57
57
|
};
|
|
58
58
|
|
|
59
|
-
/* A transport out of session refuses in the RETURN, as
|
|
60
|
-
*
|
|
59
|
+
/* A transport out of session refuses in the RETURN, as the routing
|
|
60
|
+
* adapters do (gui_agent's C_AGENT_TREEDB_LINK): a request that did not
|
|
61
|
+
* leave is not waited for. `refuse_on_the_way` makes it refuse IN
|
|
62
|
+
* session too, as a transport that cannot send. */
|
|
63
|
+
let refuse_on_the_way = false;
|
|
64
|
+
|
|
61
65
|
function remote_command_parser(gobj, command, kw, src)
|
|
62
66
|
{
|
|
63
67
|
if(gobj_current_state(gobj) !== "ST_SESSION") {
|
|
64
68
|
return `${command}: not in session`;
|
|
65
69
|
}
|
|
70
|
+
if(refuse_on_the_way) {
|
|
71
|
+
return `${command}: cannot send`;
|
|
72
|
+
}
|
|
73
|
+
commands.push({command, kw, src});
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/* A DIRECT C_IEVENT_CLI, as its mt_command does it: out of session it
|
|
78
|
+
* logs "Not in session" and answers NULL -- the same answer as a request
|
|
79
|
+
* that left. The README allows `gobj_remote_yuno` to be one. */
|
|
80
|
+
function ievent_cli_command_parser(gobj, command, kw, src)
|
|
81
|
+
{
|
|
82
|
+
if(gobj_current_state(gobj) !== "ST_SESSION") {
|
|
83
|
+
log_error(`Not in session`);
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
66
86
|
commands.push({command, kw, src});
|
|
67
87
|
return null;
|
|
68
88
|
}
|
|
@@ -93,6 +113,13 @@ beforeAll(() => {
|
|
|
93
113
|
{mt_command_parser: remote_command_parser},
|
|
94
114
|
0, [SDATA_END()], {}, 0, 0, 0, 0
|
|
95
115
|
);
|
|
116
|
+
gclass_create(
|
|
117
|
+
"C_TEST_IEVENT_CLI",
|
|
118
|
+
[],
|
|
119
|
+
[["ST_DISCONNECTED", []], ["ST_SESSION", []]],
|
|
120
|
+
{mt_command_parser: ievent_cli_command_parser},
|
|
121
|
+
0, [SDATA_END()], {}, 0, 0, 0, 0
|
|
122
|
+
);
|
|
96
123
|
register_c_yui_schema_editor();
|
|
97
124
|
yuno = gobj_create_yuno("transport_yuno", "C_TEST_HOST", {});
|
|
98
125
|
gobj_start(yuno);
|
|
@@ -102,6 +129,7 @@ beforeEach(() => {
|
|
|
102
129
|
logged.length = 0;
|
|
103
130
|
commands.length = 0;
|
|
104
131
|
shown.length = 0;
|
|
132
|
+
refuse_on_the_way = false;
|
|
105
133
|
});
|
|
106
134
|
|
|
107
135
|
function take(command)
|
|
@@ -148,9 +176,10 @@ function reconnect(editor, remote, host)
|
|
|
148
176
|
gobj_send_event(editor, "EV_TRANSPORT_STATE", {connected: true}, host);
|
|
149
177
|
}
|
|
150
178
|
|
|
151
|
-
function build(name, loaded)
|
|
179
|
+
function build(name, loaded, remote_gclass)
|
|
152
180
|
{
|
|
153
|
-
const remote = gobj_create_service(`${name}_remote`,
|
|
181
|
+
const remote = gobj_create_service(`${name}_remote`,
|
|
182
|
+
remote_gclass || "C_TEST_REMOTE", {}, yuno);
|
|
154
183
|
gobj_change_state(remote, "ST_SESSION");
|
|
155
184
|
const host = gobj_create(`${name}_host`, "C_TEST_EDITOR_HOST", {}, yuno);
|
|
156
185
|
const editor = gobj_create(`${name}_editor`, "C_YUI_SCHEMA_EDITOR", {
|
|
@@ -455,3 +484,58 @@ describe("a write answered after it was given up", () => {
|
|
|
455
484
|
expect(errors()).toEqual([]);
|
|
456
485
|
});
|
|
457
486
|
});
|
|
487
|
+
|
|
488
|
+
/* Before gobj-ui 7.25.20 a write never asked whether the transport was
|
|
489
|
+
* in session. A direct C_IEVENT_CLI answers null out of session, which
|
|
490
|
+
* remote_command() took for "sent": the editor sat in ST_SAVING, busy
|
|
491
|
+
* and inert, until another drop. */
|
|
492
|
+
describe("a write with the transport out of session", () => {
|
|
493
|
+
|
|
494
|
+
test("a direct C_IEVENT_CLI: the write ends, said, and the next session reads again", () => {
|
|
495
|
+
const {editor, remote, host} = build("x1", true, "C_TEST_IEVENT_CLI");
|
|
496
|
+
/* Gone, and the host's edge has not arrived yet. */
|
|
497
|
+
gobj_change_state(remote, "ST_DISCONNECTED");
|
|
498
|
+
|
|
499
|
+
gobj_send_event(editor, "EV_SAVE_COLUMN", {
|
|
500
|
+
creating: true, topic: "users", values: {value: "name", type: "string"}
|
|
501
|
+
}, host);
|
|
502
|
+
expect(gobj_current_state(editor)).toBe("ST_COLUMNS");
|
|
503
|
+
expect(is_busy(editor)).toBe(false);
|
|
504
|
+
expect(take("update-node")).toEqual([]);
|
|
505
|
+
expect(shown).toEqual(["cannot reach the treedb"]);
|
|
506
|
+
expect(errors()).toEqual([]);
|
|
507
|
+
expect(warnings()).toEqual([
|
|
508
|
+
"C_YUI_SCHEMA_EDITOR^x1_editor: no session, 'update-node' of 'cols' was not sent"
|
|
509
|
+
]);
|
|
510
|
+
|
|
511
|
+
gobj_send_event(editor, "EV_TRANSPORT_STATE", {connected: false}, host);
|
|
512
|
+
reconnect(editor, remote, host);
|
|
513
|
+
expect(answer_the_load(editor, remote)).toBe(3);
|
|
514
|
+
expect(gobj_current_state(editor)).toBe("ST_COLUMNS");
|
|
515
|
+
expect(errors()).toEqual([]);
|
|
516
|
+
});
|
|
517
|
+
|
|
518
|
+
/* The toast is handed the KEY (yui_shell_show_error translates it
|
|
519
|
+
* and keeps it for a change of language). This path passed
|
|
520
|
+
* t("cannot reach the treedb"), a text that cannot re-translate. */
|
|
521
|
+
test("a transport that refuses in session: the toast gets the key", async () => {
|
|
522
|
+
const i18next = (await import("i18next")).default;
|
|
523
|
+
if(!i18next.isInitialized) {
|
|
524
|
+
await i18next.init({lng: "en", resources: {}});
|
|
525
|
+
}
|
|
526
|
+
i18next.addResource(i18next.language || "en", "translation",
|
|
527
|
+
"cannot reach the treedb", "TRANSLATED: cannot reach the treedb");
|
|
528
|
+
try {
|
|
529
|
+
const {editor, remote, host} = build("x2", true);
|
|
530
|
+
refuse_on_the_way = true;
|
|
531
|
+
gobj_send_event(editor, "EV_SAVE_COLUMN", {
|
|
532
|
+
creating: true, topic: "users", values: {value: "name", type: "string"}
|
|
533
|
+
}, host);
|
|
534
|
+
expect(gobj_current_state(editor)).toBe("ST_COLUMNS");
|
|
535
|
+
expect(shown).toEqual(["cannot reach the treedb"]);
|
|
536
|
+
expect(errors()).toEqual(["update-node: cannot send"]);
|
|
537
|
+
} finally {
|
|
538
|
+
i18next.removeResourceBundle(i18next.language || "en", "translation");
|
|
539
|
+
}
|
|
540
|
+
});
|
|
541
|
+
});
|