@yuneta/gobj-ui 7.25.14 → 7.25.16
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 +100 -4
- package/dist/gobj-ui.cjs.js +406 -94
- package/dist/gobj-ui.es.js +406 -94
- package/package.json +2 -2
- package/src/c_g6_nodes_tree.js +79 -15
- package/src/c_yui_schema_editor.js +343 -66
- package/src/c_yui_treedb_graph.js +53 -6
- package/src/c_yui_treedb_topic_with_form.js +10 -10
- package/src/c_yui_treedb_topics.js +7 -7
- package/src/form_busy.js +2 -3
- package/src/form_busy.test.js +1 -1
- package/src/form_time_value.js +1 -1
- package/src/form_time_value.test.js +1 -1
- package/src/form_writes_in_flight.js +1 -2
- package/src/form_writes_in_flight.test.js +1 -1
- package/src/g6_graphs_write_owed.wiring.test.js +200 -0
- package/src/g6_nodes_tree_fsm.test.js +33 -0
- package/src/graph_save_plan.js +28 -0
- package/src/graph_save_plan.test.js +36 -2
- package/src/host_drafts.js +2 -3
- package/src/host_drafts.test.js +2 -2
- package/src/schema_editor_drafts.wiring.test.js +2 -2
- package/src/schema_editor_reload.wiring.test.js +329 -49
- package/src/schema_editor_transport.wiring.test.js +2 -2
- package/src/schema_version_bump.test.js +1 -1
- package/src/shell_modals.js +31 -11
- package/src/shell_toast_once.test.js +73 -10
- package/src/table_cell_text.js +1 -1
- package/src/table_cell_text.test.js +1 -1
- package/src/treedb_graph_writes.wiring.test.js +208 -0
- package/src/treedb_topics_transport.wiring.test.js +2 -2
- package/src/yui_install.js +14 -3
- package/src/yui_install.test.js +23 -0
- package/src/yui_row_search.test.js +1 -1
- package/src/yui_table_select.test.js +1 -1
|
@@ -1,37 +1,35 @@
|
|
|
1
1
|
/***********************************************************************
|
|
2
2
|
* schema_editor_reload.wiring.test.js
|
|
3
3
|
*
|
|
4
|
-
* C_YUI_SCHEMA_EDITOR while it RELOADS
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* open -- answered "Event NOT DEFINED in state ST_LOADING" and
|
|
4
|
+
* C_YUI_SCHEMA_EDITOR while it RELOADS. A reload entered
|
|
5
|
+
* ST_LOADING and drew nothing: the screen it replaced stayed up and
|
|
6
|
+
* clickable, and ST_LOADING declares none of its actions, so a click
|
|
7
|
+
* on a card, on Back, on the drawing -- or the Save of a column form
|
|
8
|
+
* left open -- answered "Event NOT DEFINED in state ST_LOADING" and
|
|
10
9
|
* the click, or the edit, was lost.
|
|
11
10
|
*
|
|
12
|
-
* And
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
11
|
+
* And what a reload left behind: a position left over from a load
|
|
12
|
+
* that could not leave, a drop during a WRITE that dropped the
|
|
13
|
+
* host's position (a drop during a load applies it), the drawing of
|
|
14
|
+
* a treedb that is gone drawn over the old screen, the reload of a
|
|
15
|
+
* late write that forgot this session's marks and never told the
|
|
16
|
+
* host a write had landed, a dialog opened on the model it replaced
|
|
17
|
+
* (its Save wrote the old record over the newer one), a load refused
|
|
18
|
+
* IN session that blanked the model, a late write whose list_dict
|
|
19
|
+
* fkey marked nothing, a write answered with no record that owed a
|
|
20
|
+
* second reload and dropped the rest of its queue in silence, and a
|
|
21
|
+
* toolbar gated on the state instead of on the treedb being there.
|
|
18
22
|
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* silence, and a toolbar gated on the state instead of on the
|
|
26
|
-
* treedb being there.
|
|
23
|
+
* And where the owed reload lingered: a load that LANDED by another
|
|
24
|
+
* road (the operator's Refresh, a late write) left it owed, so the
|
|
25
|
+
* next edit was refused and read the store again; a move sent by the
|
|
26
|
+
* host did not run it; a load that failed forgot the import plan of
|
|
27
|
+
* a dialog it left up; and the export's two views switched in a DOM
|
|
28
|
+
* handler, out of the machine.
|
|
27
29
|
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
* edit was refused and read the store again; a move sent by the
|
|
32
|
-
* host did not run it; a load that failed forgot the import plan
|
|
33
|
-
* of a dialog it left up; and the export's two views switched in
|
|
34
|
-
* a DOM handler, out of the machine.
|
|
30
|
+
* And a dialog that outlives its screen through a move sent by the
|
|
31
|
+
* host: its Save answered "Event NOT DEFINED", and a confirmation
|
|
32
|
+
* answered after a move to another treedb deleted there.
|
|
35
33
|
*
|
|
36
34
|
* Driven through the FSM on a document double, with a fake
|
|
37
35
|
* transport whose state is the one the library reads.
|
|
@@ -63,8 +61,10 @@ vi.mock("./shell_modals.js", () => ({
|
|
|
63
61
|
}};
|
|
64
62
|
},
|
|
65
63
|
yui_shell_confirm_danger: (shell, message, opts) => {
|
|
66
|
-
|
|
67
|
-
return new Promise(() => {
|
|
64
|
+
/* Answered by the test that needs it (`resolve`), or never. */
|
|
65
|
+
return new Promise((resolve) => {
|
|
66
|
+
confirms.push({message, opts, resolve});
|
|
67
|
+
});
|
|
68
68
|
},
|
|
69
69
|
}));
|
|
70
70
|
|
|
@@ -316,6 +316,8 @@ const WAIT = "the schemas are loading: wait for them";
|
|
|
316
316
|
const STALE = "the schemas were read again: open the dialog again";
|
|
317
317
|
const KEPT = "cannot read the schemas again: the ones shown may be out of date, your next change reads them first";
|
|
318
318
|
const OWED = "the schemas shown may be out of date: they are read again, try again when they are in";
|
|
319
|
+
const OWED_FORM = "the schemas shown may be out of date: they are read again, and the form opens again on them with your changes";
|
|
320
|
+
const GONE = "the schemas were read again and what the form was editing is not there any more";
|
|
319
321
|
const NOT_SENT = "the treedb did not describe a write back: the writes after it were not sent";
|
|
320
322
|
|
|
321
323
|
describe("the screen of a reload", () => {
|
|
@@ -530,7 +532,7 @@ describe("a write given up that was DONE", () => {
|
|
|
530
532
|
});
|
|
531
533
|
});
|
|
532
534
|
|
|
533
|
-
describe("a dialog built on the model a reload replaced
|
|
535
|
+
describe("a dialog built on the model a reload replaced", () => {
|
|
534
536
|
|
|
535
537
|
test("an edit form: the load that lands closes it, and its Save writes nothing", () => {
|
|
536
538
|
const {editor, remote, host} = build("s1", "db/users");
|
|
@@ -588,7 +590,7 @@ describe("a dialog built on the model a reload replaced (fourth review)", () =>
|
|
|
588
590
|
r.cols.push({id: "db.users.email", value: "email", order: 2, type: "integer",
|
|
589
591
|
topics: ["topics^db.users^cols"]});
|
|
590
592
|
}));
|
|
591
|
-
/* Forgotten when the load LANDS, not when it leaves
|
|
593
|
+
/* Forgotten when the load LANDS, not when it leaves. */
|
|
592
594
|
expect(editor.priv.import_plan).toBe(null);
|
|
593
595
|
expect(dialog.closed).toBe(true);
|
|
594
596
|
|
|
@@ -612,12 +614,13 @@ describe("a dialog built on the model a reload replaced (fourth review)", () =>
|
|
|
612
614
|
});
|
|
613
615
|
});
|
|
614
616
|
|
|
615
|
-
describe("a load that fails IN session
|
|
617
|
+
describe("a load that fails IN session", () => {
|
|
616
618
|
|
|
617
619
|
test("keeps the model, the screen and the open form, says so, and owes the reload", () => {
|
|
618
620
|
const {editor, remote, host} = build("b1", "db/users");
|
|
619
621
|
gobj_send_event(editor, "EV_EDIT_COLUMN", {col: "id"}, host);
|
|
620
622
|
const form = modals[modals.length - 1];
|
|
623
|
+
form.$content.querySelector('[data-name="header"]').value = "Identifier";
|
|
621
624
|
gobj_send_event(editor, "EV_REFRESH", {}, host);
|
|
622
625
|
const asked = take("nodes");
|
|
623
626
|
answer(editor, remote, asked[0], -1, null, "deadline");
|
|
@@ -634,16 +637,28 @@ describe("a load that fails IN session (fourth review)", () => {
|
|
|
634
637
|
|
|
635
638
|
/* The form was built on a model that may be older than the
|
|
636
639
|
* store: its Save would write every field of it. It runs the
|
|
637
|
-
* reload owed instead
|
|
638
|
-
*
|
|
640
|
+
* reload owed instead. The load that lands closes the form,
|
|
641
|
+
* and opens it again ON THE SCHEMAS JUST READ with what the
|
|
642
|
+
* operator changed: closed alone, what was typed was lost. */
|
|
639
643
|
form.$content.querySelector(".SCHEMA_COL_FORM_SAVE").click();
|
|
640
644
|
expect(not_defined()).toEqual([]);
|
|
641
645
|
expect(take("update-node")).toEqual([]);
|
|
642
|
-
expect(shown).toEqual([KEPT,
|
|
646
|
+
expect(shown).toEqual([KEPT, OWED_FORM]);
|
|
647
|
+
const before = modals.length;
|
|
643
648
|
expect(answer_the_load(editor, remote)).toBe(3);
|
|
644
649
|
expect(form.closed).toBe(true);
|
|
645
|
-
expect(
|
|
650
|
+
expect(modals.length).toBe(before + 1);
|
|
651
|
+
const again = modals[modals.length - 1];
|
|
652
|
+
expect(again.closed).toBe(false);
|
|
653
|
+
expect(again.$content.querySelector('[data-name="header"]').value).toBe("Identifier");
|
|
654
|
+
expect(shown).toEqual([KEPT, OWED_FORM]);
|
|
646
655
|
expect(editor.priv.reload_on_open).toBe(false);
|
|
656
|
+
|
|
657
|
+
/* Its Save writes, on the record just read. */
|
|
658
|
+
again.$content.querySelector(".SCHEMA_COL_FORM_SAVE").click();
|
|
659
|
+
const [write] = take("update-node");
|
|
660
|
+
expect(write.kw.record.id).toBe("db.users.id");
|
|
661
|
+
expect(write.kw.record.header).toBe("Identifier");
|
|
647
662
|
expect(errors()).toEqual([]);
|
|
648
663
|
});
|
|
649
664
|
|
|
@@ -675,7 +690,7 @@ describe("a load that fails IN session (fourth review)", () => {
|
|
|
675
690
|
});
|
|
676
691
|
});
|
|
677
692
|
|
|
678
|
-
describe("a late write, in the shape the store answers
|
|
693
|
+
describe("a late write, in the shape the store answers", () => {
|
|
679
694
|
|
|
680
695
|
test("a list_dict fkey marks the topic the column belongs to", () => {
|
|
681
696
|
const {editor, remote, host} = build("c1", "db/users");
|
|
@@ -692,7 +707,7 @@ describe("a late write, in the shape the store answers (fourth review)", () => {
|
|
|
692
707
|
});
|
|
693
708
|
});
|
|
694
709
|
|
|
695
|
-
describe("a write answered with no record
|
|
710
|
+
describe("a write answered with no record", () => {
|
|
696
711
|
|
|
697
712
|
test("the reload it asks is the one owed: the next write does not load again", () => {
|
|
698
713
|
const {editor, remote, host} = build("n1", "db/users");
|
|
@@ -736,7 +751,7 @@ describe("a write answered with no record (fourth review)", () => {
|
|
|
736
751
|
});
|
|
737
752
|
});
|
|
738
753
|
|
|
739
|
-
describe("the toolbar of a treedb that is not there
|
|
754
|
+
describe("the toolbar of a treedb that is not there", () => {
|
|
740
755
|
|
|
741
756
|
test("offers Back and Refresh, and nothing that needs the treedb", () => {
|
|
742
757
|
const {editor, remote, host} = build("t1", "db");
|
|
@@ -773,7 +788,7 @@ describe("the toolbar of a treedb that is not there (fourth review)", () => {
|
|
|
773
788
|
});
|
|
774
789
|
});
|
|
775
790
|
|
|
776
|
-
describe("every control of the editor's dialogs is named
|
|
791
|
+
describe("every control of the editor's dialogs is named", () => {
|
|
777
792
|
|
|
778
793
|
/* A control a wrapping <label> names is the one shape that needs no
|
|
779
794
|
* attribute (CLAUDE.md, "title + aria-label"). */
|
|
@@ -810,7 +825,7 @@ describe("every control of the editor's dialogs is named (fourth review)", () =>
|
|
|
810
825
|
});
|
|
811
826
|
});
|
|
812
827
|
|
|
813
|
-
describe("the export's two views are controls
|
|
828
|
+
describe("the export's two views are controls", () => {
|
|
814
829
|
|
|
815
830
|
test("each is a named button that says which one is shown, and switches the text", () => {
|
|
816
831
|
const {editor, remote, host} = build("x1", "db");
|
|
@@ -840,7 +855,7 @@ describe("the export's two views are controls (fifth review)", () => {
|
|
|
840
855
|
});
|
|
841
856
|
});
|
|
842
857
|
|
|
843
|
-
describe("a Refresh asked while a load is in flight
|
|
858
|
+
describe("a Refresh asked while a load is in flight", () => {
|
|
844
859
|
|
|
845
860
|
test("keeps the records of the model shown, not the half the first load got", () => {
|
|
846
861
|
const {editor, remote, host} = build("h1", "db/users");
|
|
@@ -876,7 +891,7 @@ describe("a Refresh asked while a load is in flight (fifth review)", () => {
|
|
|
876
891
|
});
|
|
877
892
|
});
|
|
878
893
|
|
|
879
|
-
describe("the reload a load refused in session owes
|
|
894
|
+
describe("the reload a load refused in session owes", () => {
|
|
880
895
|
|
|
881
896
|
function refused_load(name, subpath)
|
|
882
897
|
{
|
|
@@ -956,7 +971,7 @@ describe("the reload a load refused in session owes (fifth review)", () => {
|
|
|
956
971
|
});
|
|
957
972
|
});
|
|
958
973
|
|
|
959
|
-
describe("the import plan
|
|
974
|
+
describe("the import plan", () => {
|
|
960
975
|
|
|
961
976
|
const IMPORT = JSON.stringify({id: "db", topics: [{id: "users", pkey: "id", cols: [
|
|
962
977
|
{id: "id", type: "string", flag: ["persistent", "required"]},
|
|
@@ -990,7 +1005,7 @@ describe("the import plan (fifth review)", () => {
|
|
|
990
1005
|
});
|
|
991
1006
|
});
|
|
992
1007
|
|
|
993
|
-
describe("a confirmation answered where no treedb is open
|
|
1008
|
+
describe("a confirmation answered where no treedb is open", () => {
|
|
994
1009
|
|
|
995
1010
|
test("after a reload that landed on zero treedbs: refused as stale, not NOT DEFINED", () => {
|
|
996
1011
|
const {editor, remote, host} = build("e1", "db/users");
|
|
@@ -1031,7 +1046,7 @@ describe("a confirmation answered where no treedb is open (fifth review)", () =>
|
|
|
1031
1046
|
});
|
|
1032
1047
|
});
|
|
1033
1048
|
|
|
1034
|
-
describe("the answers of the editor's confirmations are i18n keys
|
|
1049
|
+
describe("the answers of the editor's confirmations are i18n keys", () => {
|
|
1035
1050
|
|
|
1036
1051
|
/* shell_modals names each answer by its label; its defaults ("Delete",
|
|
1037
1052
|
* "Cancel") are not lower-case, so no validated locale holds them and
|
|
@@ -1046,7 +1061,7 @@ describe("the answers of the editor's confirmations are i18n keys (fifth review,
|
|
|
1046
1061
|
});
|
|
1047
1062
|
});
|
|
1048
1063
|
|
|
1049
|
-
describe("the owed reload, settled wherever a load lands
|
|
1064
|
+
describe("the owed reload, settled wherever a load lands", () => {
|
|
1050
1065
|
|
|
1051
1066
|
function refused_load(name, subpath)
|
|
1052
1067
|
{
|
|
@@ -1125,7 +1140,7 @@ describe("the owed reload, settled wherever a load lands (sixth review)", () =>
|
|
|
1125
1140
|
});
|
|
1126
1141
|
});
|
|
1127
1142
|
|
|
1128
|
-
describe("the import plan outlives a load that fails
|
|
1143
|
+
describe("the import plan outlives a load that fails", () => {
|
|
1129
1144
|
|
|
1130
1145
|
const IMPORT = JSON.stringify({id: "db", topics: [{id: "users", pkey: "id", cols: [
|
|
1131
1146
|
{id: "id", type: "string", flag: ["persistent", "required"]},
|
|
@@ -1173,7 +1188,7 @@ describe("the import plan outlives a load that fails (sixth review)", () => {
|
|
|
1173
1188
|
});
|
|
1174
1189
|
});
|
|
1175
1190
|
|
|
1176
|
-
describe("the export's two views go through the machine
|
|
1191
|
+
describe("the export's two views go through the machine", () => {
|
|
1177
1192
|
|
|
1178
1193
|
test("a click on a view is an event, and the action switches the text", () => {
|
|
1179
1194
|
const {editor, remote, host} = build("y1", "db");
|
|
@@ -1221,3 +1236,268 @@ describe("the export's two views go through the machine (sixth review)", () => {
|
|
|
1221
1236
|
expect(errors().length).toBe(2);
|
|
1222
1237
|
});
|
|
1223
1238
|
});
|
|
1239
|
+
|
|
1240
|
+
describe("a move sent by the host while a dialog is up", () => {
|
|
1241
|
+
|
|
1242
|
+
/* C_YUI_SHELL keeps the overlays open on a move that changes only
|
|
1243
|
+
* the subpath (a url typed in, a link). The dialog of the screen the
|
|
1244
|
+
* editor left sends what only that screen declares. */
|
|
1245
|
+
const MOVED = "the view moved: open the dialog again";
|
|
1246
|
+
|
|
1247
|
+
const IMPORT = JSON.stringify({id: "db", topics: [{id: "users", pkey: "id", cols: [
|
|
1248
|
+
{id: "id", type: "string", flag: ["persistent", "required"]},
|
|
1249
|
+
{id: "email", type: "string"}
|
|
1250
|
+
]}]});
|
|
1251
|
+
|
|
1252
|
+
function settle()
|
|
1253
|
+
{
|
|
1254
|
+
return new Promise((resolve) => {
|
|
1255
|
+
setTimeout(resolve, 0);
|
|
1256
|
+
});
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
test("a column form: closed and said, not left to answer NOT DEFINED", () => {
|
|
1260
|
+
const {editor, remote, host} = build("mv1", "db/users");
|
|
1261
|
+
gobj_send_event(editor, "EV_EDIT_COLUMN", {col: "id"}, host);
|
|
1262
|
+
const form = modals[modals.length - 1];
|
|
1263
|
+
expect(form.closed).toBe(false);
|
|
1264
|
+
|
|
1265
|
+
gobj_send_event(editor, "EV_SHOW", {subpath: "db"}, host);
|
|
1266
|
+
expect(gobj_current_state(editor)).toBe("ST_TOPICS");
|
|
1267
|
+
expect(form.closed).toBe(true);
|
|
1268
|
+
expect(editor.priv.dialog).toBe(null);
|
|
1269
|
+
expect(shown).toEqual([MOVED]);
|
|
1270
|
+
expect(not_defined()).toEqual([]);
|
|
1271
|
+
expect(errors()).toEqual([]);
|
|
1272
|
+
expect(commands).toEqual([]);
|
|
1273
|
+
});
|
|
1274
|
+
|
|
1275
|
+
test("the import dialog: closed, and its plan forgotten", () => {
|
|
1276
|
+
const {editor, remote, host} = build("mv2", "db");
|
|
1277
|
+
gobj_send_event(editor, "EV_IMPORT", {}, host);
|
|
1278
|
+
const dialog = modals[modals.length - 1];
|
|
1279
|
+
gobj_send_event(editor, "EV_PREVIEW_IMPORT", {prune: false, text: IMPORT,
|
|
1280
|
+
model_gen: editor.priv.model_gen}, editor);
|
|
1281
|
+
expect(editor.priv.import_plan).not.toBe(null);
|
|
1282
|
+
|
|
1283
|
+
gobj_send_event(editor, "EV_SHOW", {subpath: ""}, host);
|
|
1284
|
+
expect(gobj_current_state(editor)).toBe("ST_TREEDBS");
|
|
1285
|
+
expect(dialog.closed).toBe(true);
|
|
1286
|
+
expect(editor.priv.import_plan).toBe(null);
|
|
1287
|
+
expect(shown).toEqual([MOVED]);
|
|
1288
|
+
expect(not_defined()).toEqual([]);
|
|
1289
|
+
expect(errors()).toEqual([]);
|
|
1290
|
+
});
|
|
1291
|
+
|
|
1292
|
+
test("a move that arrives during a load that fails: the same, when it is applied", () => {
|
|
1293
|
+
const {editor, remote, host} = build("mv3", "db/users");
|
|
1294
|
+
gobj_send_event(editor, "EV_EDIT_COLUMN", {col: "id"}, host);
|
|
1295
|
+
const form = modals[modals.length - 1];
|
|
1296
|
+
gobj_send_event(editor, "EV_REFRESH", {}, host);
|
|
1297
|
+
gobj_send_event(editor, "EV_SHOW", {subpath: "db"}, host);
|
|
1298
|
+
expect(form.closed).toBe(false);
|
|
1299
|
+
for(const c of take("nodes")) {
|
|
1300
|
+
answer(editor, remote, c, -1, null, "deadline");
|
|
1301
|
+
}
|
|
1302
|
+
expect(gobj_current_state(editor)).toBe("ST_TOPICS");
|
|
1303
|
+
expect(form.closed).toBe(true);
|
|
1304
|
+
expect(shown).toEqual([KEPT, MOVED]);
|
|
1305
|
+
expect(not_defined()).toEqual([]);
|
|
1306
|
+
});
|
|
1307
|
+
|
|
1308
|
+
test("the same position sent again leaves the dialog alone", () => {
|
|
1309
|
+
const {editor, remote, host} = build("mv4", "db/users");
|
|
1310
|
+
gobj_send_event(editor, "EV_EDIT_COLUMN", {col: "id"}, host);
|
|
1311
|
+
const form = modals[modals.length - 1];
|
|
1312
|
+
gobj_send_event(editor, "EV_SHOW", {subpath: "db/users"}, host);
|
|
1313
|
+
expect(form.closed).toBe(false);
|
|
1314
|
+
expect(shown).toEqual([]);
|
|
1315
|
+
});
|
|
1316
|
+
|
|
1317
|
+
test("the export and the check read nothing of the screen: they stay up", () => {
|
|
1318
|
+
const {editor, remote, host} = build("mv5", "db");
|
|
1319
|
+
gobj_send_event(editor, "EV_EXPORT", {}, host);
|
|
1320
|
+
const exported = modals[modals.length - 1];
|
|
1321
|
+
gobj_send_event(editor, "EV_SHOW", {subpath: ""}, host);
|
|
1322
|
+
expect(exported.closed).toBe(false);
|
|
1323
|
+
exported.$content.querySelectorAll(".SCHEMA_EXPORT_TAB")[1].click();
|
|
1324
|
+
expect(exported.$content.querySelector(".SCHEMA_EXPORT_TEXT").value.trim()
|
|
1325
|
+
.startsWith("{")).toBe(true);
|
|
1326
|
+
|
|
1327
|
+
gobj_send_event(editor, "EV_SHOW", {subpath: "db"}, host);
|
|
1328
|
+
gobj_send_event(editor, "EV_VALIDATE", {}, host);
|
|
1329
|
+
const report = modals[modals.length - 1];
|
|
1330
|
+
gobj_send_event(editor, "EV_SHOW", {subpath: "db/users"}, host);
|
|
1331
|
+
expect(report.closed).toBe(false);
|
|
1332
|
+
expect(shown).toEqual([]);
|
|
1333
|
+
expect(not_defined()).toEqual([]);
|
|
1334
|
+
expect(errors()).toEqual([]);
|
|
1335
|
+
});
|
|
1336
|
+
|
|
1337
|
+
test("a confirmation answered after a move to another treedb deletes nothing", async () => {
|
|
1338
|
+
const {editor, remote, host} = build("mv6", "db/users");
|
|
1339
|
+
gobj_send_event(editor, "EV_REFRESH", {}, host);
|
|
1340
|
+
answer_the_load_with(editor, remote, records_with((r) => {
|
|
1341
|
+
r.treedbs.push({id: "db2", schema_version: 1});
|
|
1342
|
+
r.topics.push({id: "db2.users", value: "users", order: 1, pkey: "id",
|
|
1343
|
+
topic_version: 1, treedbs: ["treedbs^db2^topics"]});
|
|
1344
|
+
r.cols.push({id: "db2.users.id", value: "id", order: 1, type: "string",
|
|
1345
|
+
flag: ["persistent", "required"], topics: ["topics^db2.users^cols"]});
|
|
1346
|
+
}));
|
|
1347
|
+
expect(gobj_current_state(editor)).toBe("ST_COLUMNS");
|
|
1348
|
+
|
|
1349
|
+
gobj_send_event(editor, "EV_DELETE_COLUMN", {col: "id"}, host);
|
|
1350
|
+
expect(confirms.length).toBe(1);
|
|
1351
|
+
gobj_send_event(editor, "EV_SHOW", {subpath: "db2/users"}, host);
|
|
1352
|
+
expect(gobj_current_state(editor)).toBe("ST_COLUMNS");
|
|
1353
|
+
confirms[0].resolve(true);
|
|
1354
|
+
await settle();
|
|
1355
|
+
|
|
1356
|
+
expect(take("delete-node")).toEqual([]);
|
|
1357
|
+
expect(commands).toEqual([]);
|
|
1358
|
+
expect(shown).toEqual([MOVED]);
|
|
1359
|
+
expect(errors()).toEqual([]);
|
|
1360
|
+
});
|
|
1361
|
+
|
|
1362
|
+
test("a confirmation answered on the screen it was asked from still runs", async () => {
|
|
1363
|
+
const {editor, remote, host} = build("mv7", "db/users");
|
|
1364
|
+
gobj_send_event(editor, "EV_DELETE_COLUMN", {col: "id"}, host);
|
|
1365
|
+
confirms[0].resolve(true);
|
|
1366
|
+
await settle();
|
|
1367
|
+
expect(take("delete-node").length).toBe(1);
|
|
1368
|
+
expect(shown).toEqual([]);
|
|
1369
|
+
});
|
|
1370
|
+
});
|
|
1371
|
+
|
|
1372
|
+
describe("the form opened again after the owed reload", () => {
|
|
1373
|
+
|
|
1374
|
+
function owing(name, subpath)
|
|
1375
|
+
{
|
|
1376
|
+
const built = build(name, subpath);
|
|
1377
|
+
built.editor.priv.reload_on_open = true; /* as a load refused in session leaves it */
|
|
1378
|
+
return built;
|
|
1379
|
+
}
|
|
1380
|
+
|
|
1381
|
+
test("only what was CHANGED is carried: a field the store changed meanwhile shows the store's", () => {
|
|
1382
|
+
const {editor, remote, host} = build("ro6", "db/users");
|
|
1383
|
+
gobj_send_event(editor, "EV_EDIT_COLUMN", {col: "id"}, host);
|
|
1384
|
+
editor.priv.reload_on_open = true;
|
|
1385
|
+
const form = modals[modals.length - 1];
|
|
1386
|
+
form.$content.querySelector('[data-name="header"]').value = "Identifier";
|
|
1387
|
+
form.$content.querySelector(".SCHEMA_COL_FORM_SAVE").click();
|
|
1388
|
+
answer_the_load_with(editor, remote, records_with((r) => {
|
|
1389
|
+
r.cols[0].description = "written by somebody else";
|
|
1390
|
+
}));
|
|
1391
|
+
const again = modals[modals.length - 1];
|
|
1392
|
+
expect(again.$content.querySelector('[data-name="header"]').value).toBe("Identifier");
|
|
1393
|
+
expect(again.$content.querySelector('[data-name="description"]').value)
|
|
1394
|
+
.toBe("written by somebody else");
|
|
1395
|
+
again.$content.querySelector(".SCHEMA_COL_FORM_SAVE").click();
|
|
1396
|
+
const [write] = take("update-node");
|
|
1397
|
+
expect(write.kw.record.description).toBe("written by somebody else");
|
|
1398
|
+
expect(write.kw.record.header).toBe("Identifier");
|
|
1399
|
+
expect(errors()).toEqual([]);
|
|
1400
|
+
});
|
|
1401
|
+
|
|
1402
|
+
test("a NEW column comes back whole: its name, its type and its flags", () => {
|
|
1403
|
+
const {editor, remote, host} = owing("ro2", "db/users");
|
|
1404
|
+
/* The owed reload runs on the form's opening too: open it on a
|
|
1405
|
+
* model that is current, then owe the reload. */
|
|
1406
|
+
editor.priv.reload_on_open = false;
|
|
1407
|
+
gobj_send_event(editor, "EV_ADD_COLUMN", {}, host);
|
|
1408
|
+
editor.priv.reload_on_open = true;
|
|
1409
|
+
const form = modals[modals.length - 1];
|
|
1410
|
+
form.$content.querySelector('[data-name="value"]').value = "email";
|
|
1411
|
+
form.$content.querySelector('[data-name="header"]').value = "Email";
|
|
1412
|
+
for(const $box of form.$content.querySelectorAll(".SCHEMA_FLAG_BOX")) {
|
|
1413
|
+
if($box.dataset.flag === "required") {
|
|
1414
|
+
$box.checked = true;
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
form.$content.querySelector(".SCHEMA_COL_FORM_SAVE").click();
|
|
1418
|
+
expect(shown).toEqual([OWED_FORM]);
|
|
1419
|
+
answer_the_load(editor, remote);
|
|
1420
|
+
const again = modals[modals.length - 1];
|
|
1421
|
+
expect(again).not.toBe(form);
|
|
1422
|
+
expect(again.$content.querySelector('[data-name="value"]').value).toBe("email");
|
|
1423
|
+
expect(again.$content.querySelector('[data-name="header"]').value).toBe("Email");
|
|
1424
|
+
const on = [...again.$content.querySelectorAll(".SCHEMA_FLAG_BOX")]
|
|
1425
|
+
.filter(($b) => $b.checked).map(($b) => $b.dataset.flag);
|
|
1426
|
+
expect(on).toContain("required");
|
|
1427
|
+
expect(errors()).toEqual([]);
|
|
1428
|
+
});
|
|
1429
|
+
|
|
1430
|
+
test("a topic form comes back the same way", () => {
|
|
1431
|
+
const {editor, remote, host} = build("ro3", "db");
|
|
1432
|
+
gobj_send_event(editor, "EV_EDIT_TOPIC", {topic: "users"}, host);
|
|
1433
|
+
editor.priv.reload_on_open = true;
|
|
1434
|
+
const form = modals[modals.length - 1];
|
|
1435
|
+
form.$content.querySelector('[data-name="tkey"]').value = "tm";
|
|
1436
|
+
form.$content.querySelector(".SCHEMA_TOPIC_FORM_SAVE").click();
|
|
1437
|
+
expect(shown).toEqual([OWED_FORM]);
|
|
1438
|
+
answer_the_load(editor, remote);
|
|
1439
|
+
const again = modals[modals.length - 1];
|
|
1440
|
+
expect(again).not.toBe(form);
|
|
1441
|
+
expect(again.$content.querySelector('[data-name="tkey"]').value).toBe("tm");
|
|
1442
|
+
expect(errors()).toEqual([]);
|
|
1443
|
+
});
|
|
1444
|
+
|
|
1445
|
+
test("what the form edited is gone after the reload: not opened again, and SAID", () => {
|
|
1446
|
+
const {editor, remote, host} = build("ro4", "db/users");
|
|
1447
|
+
gobj_send_event(editor, "EV_EDIT_COLUMN", {col: "id"}, host);
|
|
1448
|
+
editor.priv.reload_on_open = true;
|
|
1449
|
+
const form = modals[modals.length - 1];
|
|
1450
|
+
form.$content.querySelector(".SCHEMA_COL_FORM_SAVE").click();
|
|
1451
|
+
const before = modals.length;
|
|
1452
|
+
answer_the_load_with(editor, remote, records_with((r) => {
|
|
1453
|
+
r.cols = [];
|
|
1454
|
+
}));
|
|
1455
|
+
expect(form.closed).toBe(true);
|
|
1456
|
+
expect(modals.length).toBe(before);
|
|
1457
|
+
expect(shown).toEqual([OWED_FORM, GONE]);
|
|
1458
|
+
expect(errors()).toEqual([]);
|
|
1459
|
+
});
|
|
1460
|
+
|
|
1461
|
+
test("a form closed by the operator while the reload ran is not opened again", () => {
|
|
1462
|
+
const {editor, remote, host} = build("ro5", "db/users");
|
|
1463
|
+
gobj_send_event(editor, "EV_EDIT_COLUMN", {col: "id"}, host);
|
|
1464
|
+
editor.priv.reload_on_open = true;
|
|
1465
|
+
const form = modals[modals.length - 1];
|
|
1466
|
+
form.$content.querySelector(".SCHEMA_COL_FORM_SAVE").click();
|
|
1467
|
+
form.$content.querySelector(".SCHEMA_COL_FORM_CANCEL").click();
|
|
1468
|
+
const before = modals.length;
|
|
1469
|
+
answer_the_load(editor, remote);
|
|
1470
|
+
expect(modals.length).toBe(before);
|
|
1471
|
+
expect(shown).toEqual([OWED_FORM]);
|
|
1472
|
+
expect(errors()).toEqual([]);
|
|
1473
|
+
});
|
|
1474
|
+
});
|
|
1475
|
+
|
|
1476
|
+
describe("EV_REFRESH while a write is in flight", () => {
|
|
1477
|
+
|
|
1478
|
+
test("waits for the end of the writes: the queue is written whole, then the schemas are read", () => {
|
|
1479
|
+
const {editor, remote, host} = build("rq1", "db");
|
|
1480
|
+
gobj_send_event(editor, "EV_CONFIRMED", {what: "topic", topic: "users",
|
|
1481
|
+
model_gen: editor.priv.model_gen}, editor);
|
|
1482
|
+
expect(gobj_current_state(editor)).toBe("ST_SAVING");
|
|
1483
|
+
const [first] = take("delete-node");
|
|
1484
|
+
expect(first.kw.topic_name).toBe("cols");
|
|
1485
|
+
|
|
1486
|
+
gobj_send_event(editor, "EV_REFRESH", {}, host);
|
|
1487
|
+
expect(gobj_current_state(editor)).toBe("ST_SAVING");
|
|
1488
|
+
expect(take("nodes")).toEqual([]);
|
|
1489
|
+
expect(logged.some((l) => l.level === "warning" &&
|
|
1490
|
+
/EV_REFRESH.*when the writes end/.test(l.msg))).toBe(true);
|
|
1491
|
+
|
|
1492
|
+
answer(editor, remote, first, 0, {});
|
|
1493
|
+
const [second] = take("delete-node");
|
|
1494
|
+
expect(second.kw.topic_name).toBe("topics");
|
|
1495
|
+
answer(editor, remote, second, 0, {});
|
|
1496
|
+
|
|
1497
|
+
expect(gobj_current_state(editor)).toBe("ST_LOADING");
|
|
1498
|
+
expect(answer_the_load(editor, remote)).toBe(3);
|
|
1499
|
+
expect(editor.priv.written).toEqual({});
|
|
1500
|
+
expect(errors()).toEqual([]);
|
|
1501
|
+
expect(not_defined()).toEqual([]);
|
|
1502
|
+
});
|
|
1503
|
+
});
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/***********************************************************************
|
|
2
2
|
* schema_editor_transport.wiring.test.js
|
|
3
3
|
*
|
|
4
|
-
* C_YUI_SCHEMA_EDITOR when the transport drops
|
|
5
|
-
*
|
|
4
|
+
* C_YUI_SCHEMA_EDITOR when the transport drops. A drop used to
|
|
5
|
+
* leave the editor
|
|
6
6
|
* where it was: in ST_SAVING with its body busy, or in ST_LOADING
|
|
7
7
|
* -- and the reconnect skipped the reload BECAUSE the state was
|
|
8
8
|
* ST_LOADING. Only a reload of the page got it out.
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* "This topic was written in this session and is not saved."
|
|
5
5
|
*
|
|
6
|
-
* An edit is a DRAFT
|
|
6
|
+
* An edit is a DRAFT: it moves
|
|
7
7
|
* no version, and the host's save-schema raises the versions of what
|
|
8
8
|
* changed. So "written" alone is the whole question -- the version is
|
|
9
9
|
* no longer measured against a baseline, which is also why the old
|
package/src/shell_modals.js
CHANGED
|
@@ -124,9 +124,8 @@ function maybe_apply_translator($node, opts)
|
|
|
124
124
|
* it -- until every handle is closed or has timed out. Handed the
|
|
125
125
|
* first caller's handle and timer, a repeat asking `timeout: 0`
|
|
126
126
|
* was dismissed on the first one's timer, and closing one
|
|
127
|
-
* caller's handle closed a toast another was still showing
|
|
128
|
-
*
|
|
129
|
-
* everybody.
|
|
127
|
+
* caller's handle closed a toast another was still showing.
|
|
128
|
+
* The operator's ✕ closes it for everybody.
|
|
130
129
|
***************************************************************/
|
|
131
130
|
function show_notification(shell, kind, message, opts)
|
|
132
131
|
{
|
|
@@ -614,9 +613,30 @@ function build_dialog(shell, message, buttons, opts)
|
|
|
614
613
|
}
|
|
615
614
|
|
|
616
615
|
|
|
616
|
+
/***************************************************************
|
|
617
|
+
* The labels a confirmation shows when the caller names none.
|
|
618
|
+
*
|
|
619
|
+
* They are i18n KEYS, lower-case like every key: a button
|
|
620
|
+
* translates its label with the app's t() -- its text, its title
|
|
621
|
+
* and its aria-label. Until 7.25.16 they were "OK", "Yes", "No",
|
|
622
|
+
* "Delete" and "Cancel", keys that no locale carries, so a dialog
|
|
623
|
+
* that took the defaults read in English in every language.
|
|
624
|
+
*
|
|
625
|
+
* Written as `i18n:` entries so the validate-locales script of a
|
|
626
|
+
* consumer finds them when it scans this module: an app that
|
|
627
|
+
* mounts these dialogs must define the five keys.
|
|
628
|
+
***************************************************************/
|
|
629
|
+
const DEFAULT_LABEL = Object.freeze({
|
|
630
|
+
ok: Object.freeze({i18n: "ok"}),
|
|
631
|
+
yes: Object.freeze({i18n: "yes"}),
|
|
632
|
+
no: Object.freeze({i18n: "no"}),
|
|
633
|
+
delete: Object.freeze({i18n: "delete"}),
|
|
634
|
+
cancel: Object.freeze({i18n: "cancel"})
|
|
635
|
+
});
|
|
636
|
+
|
|
617
637
|
export function yui_shell_confirm_ok(shell, message, opts)
|
|
618
638
|
{
|
|
619
|
-
let label = (opts && opts.ok_label) ||
|
|
639
|
+
let label = (opts && opts.ok_label) || DEFAULT_LABEL.ok.i18n;
|
|
620
640
|
if(!opts || !opts.type) {
|
|
621
641
|
opts = Object.assign({}, opts, {type: "success"});
|
|
622
642
|
}
|
|
@@ -627,8 +647,8 @@ export function yui_shell_confirm_ok(shell, message, opts)
|
|
|
627
647
|
|
|
628
648
|
export function yui_shell_confirm_yesno(shell, message, opts)
|
|
629
649
|
{
|
|
630
|
-
let yes_label = (opts && opts.yes_label) ||
|
|
631
|
-
let no_label = (opts && opts.no_label) ||
|
|
650
|
+
let yes_label = (opts && opts.yes_label) || DEFAULT_LABEL.yes.i18n;
|
|
651
|
+
let no_label = (opts && opts.no_label) || DEFAULT_LABEL.no.i18n;
|
|
632
652
|
return build_dialog(shell, message, [
|
|
633
653
|
{label: yes_label, value: "yes", kind: "primary"},
|
|
634
654
|
{label: no_label, value: "no"}
|
|
@@ -651,8 +671,8 @@ export function yui_shell_confirm_yesno(shell, message, opts)
|
|
|
651
671
|
***************************************************************/
|
|
652
672
|
export function yui_shell_confirm_danger(shell, message, opts)
|
|
653
673
|
{
|
|
654
|
-
let confirm_label = (opts && opts.confirm_label) ||
|
|
655
|
-
let cancel_label = (opts && opts.cancel_label) ||
|
|
674
|
+
let confirm_label = (opts && opts.confirm_label) || DEFAULT_LABEL.delete.i18n;
|
|
675
|
+
let cancel_label = (opts && opts.cancel_label) || DEFAULT_LABEL.cancel.i18n;
|
|
656
676
|
if(!opts || !opts.type) {
|
|
657
677
|
opts = Object.assign({}, opts, {type: "danger"});
|
|
658
678
|
}
|
|
@@ -664,9 +684,9 @@ export function yui_shell_confirm_danger(shell, message, opts)
|
|
|
664
684
|
|
|
665
685
|
export function yui_shell_confirm_yesnocancel(shell, message, opts)
|
|
666
686
|
{
|
|
667
|
-
let yes_label = (opts && opts.yes_label) ||
|
|
668
|
-
let no_label = (opts && opts.no_label) ||
|
|
669
|
-
let cancel_label = (opts && opts.cancel_label) ||
|
|
687
|
+
let yes_label = (opts && opts.yes_label) || DEFAULT_LABEL.yes.i18n;
|
|
688
|
+
let no_label = (opts && opts.no_label) || DEFAULT_LABEL.no.i18n;
|
|
689
|
+
let cancel_label = (opts && opts.cancel_label) || DEFAULT_LABEL.cancel.i18n;
|
|
670
690
|
return build_dialog(shell, message, [
|
|
671
691
|
{label: yes_label, value: "yes", kind: "primary"},
|
|
672
692
|
{label: no_label, value: "no"},
|