@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
|
@@ -5,17 +5,17 @@
|
|
|
5
5
|
* stacked under it: one close of the transport settles every
|
|
6
6
|
* request it cut, each view that asked shows its failure, and
|
|
7
7
|
* the operator got a column of identical "the connection
|
|
8
|
-
* dropped"
|
|
8
|
+
* dropped".
|
|
9
9
|
*
|
|
10
|
-
* And a repeat is a caller of its own
|
|
11
|
-
*
|
|
12
|
-
*
|
|
10
|
+
* And a repeat is a caller of its own: its handle and its time
|
|
11
|
+
* are its own, and the toast stays while any caller still holds
|
|
12
|
+
* it.
|
|
13
13
|
*
|
|
14
14
|
* And every ✕ this module draws is a control with a NAME: a
|
|
15
|
-
* `title` and an `aria-label`, both translatable (
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
15
|
+
* `title` and an `aria-label`, both translatable (TOAST_CLOSE had
|
|
16
|
+
* the label only). So is every other button of a dialog (the
|
|
17
|
+
* dialog's back arrow had no title, and the Yes/No of every
|
|
18
|
+
* confirmation had neither).
|
|
19
19
|
*
|
|
20
20
|
* Copyright (c) 2026, ArtGins.
|
|
21
21
|
* All Rights Reserved.
|
|
@@ -30,6 +30,9 @@ const {
|
|
|
30
30
|
yui_shell_show_info,
|
|
31
31
|
yui_shell_show_modal,
|
|
32
32
|
yui_shell_confirm_danger,
|
|
33
|
+
yui_shell_confirm_ok,
|
|
34
|
+
yui_shell_confirm_yesno,
|
|
35
|
+
yui_shell_confirm_yesnocancel,
|
|
33
36
|
} = await import("./shell_modals.js");
|
|
34
37
|
|
|
35
38
|
let shell = null;
|
|
@@ -78,7 +81,7 @@ describe("one toast per message on screen", () => {
|
|
|
78
81
|
});
|
|
79
82
|
|
|
80
83
|
/*
|
|
81
|
-
*
|
|
84
|
+
* A repeat was handed the FIRST toast's
|
|
82
85
|
* handle and timer. A repeat asking `timeout: 0` was dismissed on the
|
|
83
86
|
* first one's timer, and closing one caller's handle closed the toast
|
|
84
87
|
* another caller was still showing.
|
|
@@ -168,7 +171,7 @@ describe("every close control is named", () => {
|
|
|
168
171
|
});
|
|
169
172
|
});
|
|
170
173
|
|
|
171
|
-
describe("every other button of a dialog is named
|
|
174
|
+
describe("every other button of a dialog is named", () => {
|
|
172
175
|
|
|
173
176
|
function named($b)
|
|
174
177
|
{
|
|
@@ -200,3 +203,63 @@ describe("every other button of a dialog is named (fifth review)", () => {
|
|
|
200
203
|
]);
|
|
201
204
|
});
|
|
202
205
|
});
|
|
206
|
+
|
|
207
|
+
/*
|
|
208
|
+
* A confirmation that names no label shows the DEFAULT ones, and those
|
|
209
|
+
* are i18n keys like every other label: lower-case, translated by the
|
|
210
|
+
* app's t(), and carried as the key of the text, the title and the
|
|
211
|
+
* aria-label. They were "OK", "Yes", "No", "Delete" and "Cancel" --
|
|
212
|
+
* keys no locale has, so the button read in English in every language.
|
|
213
|
+
*/
|
|
214
|
+
describe("the default labels of a confirmation are i18n keys", () => {
|
|
215
|
+
|
|
216
|
+
function keys_of($layer)
|
|
217
|
+
{
|
|
218
|
+
return [...$layer.querySelectorAll(".CONFIRM_BTN")].map(($b) => [
|
|
219
|
+
$b.getAttribute("data-i18n"),
|
|
220
|
+
$b.getAttribute("data-i18n-title"),
|
|
221
|
+
$b.getAttribute("data-i18n-aria-label"),
|
|
222
|
+
]);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/* What the reader hears: the aria-label, translated by t() when
|
|
226
|
+
* the button is built. */
|
|
227
|
+
function texts_of($layer)
|
|
228
|
+
{
|
|
229
|
+
return [...$layer.querySelectorAll(".CONFIRM_BTN")].map(($b) => $b.getAttribute("aria-label"));
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const k = (key) => [key, key, key];
|
|
233
|
+
|
|
234
|
+
/* A translator that tells a key it knows from one it does not. */
|
|
235
|
+
const ES = {ok: "Aceptar", yes: "Sí", no: "No", delete: "Borrar", cancel: "Cancelar"};
|
|
236
|
+
const t = (key) => ES[key] || `?${key}?`;
|
|
237
|
+
|
|
238
|
+
test("ok", () => {
|
|
239
|
+
shell.priv.layers.modal = document.createElement("div");
|
|
240
|
+
yui_shell_confirm_ok(shell, "saved", {t: t});
|
|
241
|
+
expect(keys_of(shell.priv.layers.modal)).toEqual([k("ok")]);
|
|
242
|
+
expect(texts_of(shell.priv.layers.modal)).toEqual(["Aceptar"]);
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
test("yes / no", () => {
|
|
246
|
+
shell.priv.layers.modal = document.createElement("div");
|
|
247
|
+
yui_shell_confirm_yesno(shell, "go on?", {t: t});
|
|
248
|
+
expect(keys_of(shell.priv.layers.modal)).toEqual([k("yes"), k("no")]);
|
|
249
|
+
expect(texts_of(shell.priv.layers.modal)).toEqual(["Sí", "No"]);
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
test("yes / no / cancel", () => {
|
|
253
|
+
shell.priv.layers.modal = document.createElement("div");
|
|
254
|
+
yui_shell_confirm_yesnocancel(shell, "keep it?", {t: t});
|
|
255
|
+
expect(keys_of(shell.priv.layers.modal)).toEqual([k("yes"), k("no"), k("cancel")]);
|
|
256
|
+
expect(texts_of(shell.priv.layers.modal)).toEqual(["Sí", "No", "Cancelar"]);
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
test("delete / cancel", () => {
|
|
260
|
+
shell.priv.layers.modal = document.createElement("div");
|
|
261
|
+
yui_shell_confirm_danger(shell, "delete this column?", {t: t});
|
|
262
|
+
expect(keys_of(shell.priv.layers.modal)).toEqual([k("delete"), k("cancel")]);
|
|
263
|
+
expect(texts_of(shell.priv.layers.modal)).toEqual(["Borrar", "Cancelar"]);
|
|
264
|
+
});
|
|
265
|
+
});
|
package/src/table_cell_text.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* (only its own `plaintext` formatter escapes), so a record's text
|
|
8
8
|
* returned as a string is parsed as markup: `a<b and c>d` showed
|
|
9
9
|
* as `ad`, and a field holding `<img src=x onerror=...>` ran it in
|
|
10
|
-
* the operator's browser
|
|
10
|
+
* the operator's browser. Record
|
|
11
11
|
* data goes in as a TEXT NODE, and a cell built around it as DOM,
|
|
12
12
|
* never as an HTML string with the data interpolated.
|
|
13
13
|
*
|
|
@@ -9,7 +9,7 @@ const doc = {
|
|
|
9
9
|
describe("table_cell_text", () => {
|
|
10
10
|
|
|
11
11
|
it("record text is a text node, markup included", () => {
|
|
12
|
-
/*
|
|
12
|
+
/* Tabulator puts a string through innerHTML */
|
|
13
13
|
const node = cell_text("limit a<b and c>d", doc);
|
|
14
14
|
expect(node.nodeType).toBe(3);
|
|
15
15
|
expect(node.textContent).toBe("limit a<b and c>d");
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
/***********************************************************************
|
|
2
|
+
* treedb_graph_writes.wiring.test.js
|
|
3
|
+
*
|
|
4
|
+
* C_YUI_TREEDB_GRAPH and the writes of `__graphs__`, the topic
|
|
5
|
+
* where the graph keeps its arrangement. The engine
|
|
6
|
+
* (C_G6_NODES_TREE) takes such a write for granted before it
|
|
7
|
+
* leaves: nothing answers it when it lands. So the host must say
|
|
8
|
+
* when one did NOT land -- refused by the backend, refused on the
|
|
9
|
+
* way out, or never sent for want of a session. Without that, the
|
|
10
|
+
* engine believed the backend held the arrangement, and the next
|
|
11
|
+
* Save had nothing to write.
|
|
12
|
+
*
|
|
13
|
+
* Driven through the real host on a document double, with a fake
|
|
14
|
+
* engine that records what it is told and a fake transport whose
|
|
15
|
+
* state is the one the host reads.
|
|
16
|
+
*
|
|
17
|
+
* Copyright (c) 2026, ArtGins.
|
|
18
|
+
* All Rights Reserved.
|
|
19
|
+
***********************************************************************/
|
|
20
|
+
import {describe, test, expect, beforeAll, beforeEach} from "vitest";
|
|
21
|
+
import {install_dom_double} from "../test/dom_double.js";
|
|
22
|
+
|
|
23
|
+
install_dom_double();
|
|
24
|
+
|
|
25
|
+
/* The host watches its container's size; the document double has no
|
|
26
|
+
* layout to watch. */
|
|
27
|
+
if(!globalThis.ResizeObserver) {
|
|
28
|
+
globalThis.ResizeObserver = class {
|
|
29
|
+
observe() {}
|
|
30
|
+
unobserve() {}
|
|
31
|
+
disconnect() {}
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const {
|
|
36
|
+
SDATA, SDATA_END, data_type_t,
|
|
37
|
+
gclass_create,
|
|
38
|
+
gobj_start_up, gobj_create_yuno, gobj_create, gobj_create_service,
|
|
39
|
+
gobj_start, gobj_send_event, gobj_publish_event,
|
|
40
|
+
gobj_read_pointer_attr, gobj_subscribe_event,
|
|
41
|
+
gobj_change_state, gobj_current_state,
|
|
42
|
+
set_log_callback,
|
|
43
|
+
} = await import("@yuneta/gobj-js");
|
|
44
|
+
|
|
45
|
+
const logged = [];
|
|
46
|
+
const commands = []; /* what the fake transport was asked */
|
|
47
|
+
const told = []; /* what the fake engine was told */
|
|
48
|
+
let refuse_on_the_way = false;
|
|
49
|
+
|
|
50
|
+
function remote_command_parser(gobj, command, kw, src)
|
|
51
|
+
{
|
|
52
|
+
if(refuse_on_the_way) {
|
|
53
|
+
return `cannot route '${command}'`;
|
|
54
|
+
}
|
|
55
|
+
commands.push({command, kw, src});
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
let yuno = null;
|
|
60
|
+
|
|
61
|
+
beforeAll(async () => {
|
|
62
|
+
gobj_start_up(null, null, null, null, null, null, null);
|
|
63
|
+
set_log_callback((level, msg) => {
|
|
64
|
+
logged.push({level: String(level), msg: String(msg)});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
gclass_create("C_TEST_HOST", [], [["ST_IDLE", []]], {}, 0, [SDATA_END()], {}, 0, 0, 0, 0);
|
|
68
|
+
gclass_create(
|
|
69
|
+
"C_TEST_REMOTE",
|
|
70
|
+
[],
|
|
71
|
+
[["ST_SESSION", []], ["ST_DISCONNECTED", []]],
|
|
72
|
+
{mt_command_parser: remote_command_parser},
|
|
73
|
+
0, [SDATA_END()], {}, 0, 0, 0, 0
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
/* The engine, registered BEFORE the host so the host uses it. It
|
|
77
|
+
* publishes what the real one publishes on a Save, and records every
|
|
78
|
+
* event it is sent. */
|
|
79
|
+
const record = (gobj, event, kw) => {
|
|
80
|
+
told.push({event, kw});
|
|
81
|
+
return 0;
|
|
82
|
+
};
|
|
83
|
+
const ENGINE_EVENTS = [
|
|
84
|
+
"EV_DESCS", "EV_CLEAR_DATA", "EV_LOAD_DATA", "EV_NODE_CREATED",
|
|
85
|
+
"EV_NODE_UPDATED", "EV_NODE_DELETED", "EV_GRAPHS_WRITE_REFUSED",
|
|
86
|
+
"EV_SHOW", "EV_HIDE", "EV_RESIZE", "EV_THEME", "EV_LANGUAGE_CHANGED",
|
|
87
|
+
"EV_SET_OPERATION_MODE", "EV_SET_LAYOUT"
|
|
88
|
+
];
|
|
89
|
+
gclass_create(
|
|
90
|
+
"C_G6_NODES_TREE",
|
|
91
|
+
ENGINE_EVENTS.map((e) => [e, 0]).concat([["EV_UPDATE_NODE", 2]]),
|
|
92
|
+
[["ST_IDLE", ENGINE_EVENTS.map((e) => [e, record, null])]],
|
|
93
|
+
{
|
|
94
|
+
mt_create: (gobj) => {
|
|
95
|
+
const subscriber = gobj_read_pointer_attr(gobj, "subscriber");
|
|
96
|
+
if(subscriber) {
|
|
97
|
+
gobj_subscribe_event(gobj, null, {}, subscriber);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
0,
|
|
102
|
+
[
|
|
103
|
+
SDATA(data_type_t.DTP_POINTER, "subscriber", 0, null, ""),
|
|
104
|
+
SDATA(data_type_t.DTP_POINTER, "$container", 0, null, ""),
|
|
105
|
+
SDATA(data_type_t.DTP_POINTER, "gobj_remote_yuno", 0, null, ""),
|
|
106
|
+
SDATA(data_type_t.DTP_STRING, "treedb_name", 0, "", ""),
|
|
107
|
+
SDATA(data_type_t.DTP_JSON, "layout_names", 0, "[]", ""),
|
|
108
|
+
SDATA(data_type_t.DTP_STRING, "layout", 0, "", ""),
|
|
109
|
+
SDATA_END()
|
|
110
|
+
],
|
|
111
|
+
{}, 0, 0, 0, 0
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
const {register_c_yui_treedb_graph} = await import("./c_yui_treedb_graph.js");
|
|
115
|
+
register_c_yui_treedb_graph();
|
|
116
|
+
|
|
117
|
+
yuno = gobj_create_yuno("graph_writes_yuno", "C_TEST_HOST", {});
|
|
118
|
+
gobj_start(yuno);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
beforeEach(() => {
|
|
122
|
+
logged.length = 0;
|
|
123
|
+
commands.length = 0;
|
|
124
|
+
told.length = 0;
|
|
125
|
+
refuse_on_the_way = false;
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
function build(name)
|
|
129
|
+
{
|
|
130
|
+
const remote = gobj_create_service(`${name}_remote`, "C_TEST_REMOTE", {}, yuno);
|
|
131
|
+
gobj_change_state(remote, "ST_SESSION");
|
|
132
|
+
const host = gobj_create_service(`${name}_graph`, "C_YUI_TREEDB_GRAPH", {
|
|
133
|
+
gobj_remote_yuno: remote,
|
|
134
|
+
treedb_name: "treedb_test"
|
|
135
|
+
}, yuno);
|
|
136
|
+
const engine = host.priv.gobj_nodes_tree;
|
|
137
|
+
expect(engine).toBeTruthy();
|
|
138
|
+
return {remote, host, engine};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/* What the engine publishes on a Save of one topic's arrangement. */
|
|
142
|
+
function save_arrangement(engine, topic)
|
|
143
|
+
{
|
|
144
|
+
gobj_publish_event(engine, "EV_UPDATE_NODE", {
|
|
145
|
+
treedb_name: "treedb_test",
|
|
146
|
+
topic_name: "__graphs__",
|
|
147
|
+
record: {id: topic, topic: topic, active: true, properties: {nodes: {}}},
|
|
148
|
+
options: {list_dict: true, autolink: false, create: true}
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function answer(host, remote, request, result, comment)
|
|
153
|
+
{
|
|
154
|
+
gobj_send_event(host, "EV_MT_COMMAND_ANSWER", {
|
|
155
|
+
result: result,
|
|
156
|
+
comment: comment || "",
|
|
157
|
+
data: null,
|
|
158
|
+
__md_iev__: {command_stack: [{command: request.command, kw: request.kw}]}
|
|
159
|
+
}, remote);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function refused()
|
|
163
|
+
{
|
|
164
|
+
return told.filter((t) => t.event === "EV_GRAPHS_WRITE_REFUSED").map((t) => t.kw.topic);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
describe("a __graphs__ write that does not land is said to the engine", () => {
|
|
168
|
+
|
|
169
|
+
test("refused by the backend", () => {
|
|
170
|
+
const {remote, host, engine} = build("g1");
|
|
171
|
+
save_arrangement(engine, "yunos");
|
|
172
|
+
expect(commands.length).toBe(1);
|
|
173
|
+
answer(host, remote, commands[0], -1, "not authorized");
|
|
174
|
+
expect(refused()).toEqual(["yunos"]);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
test("landed: nothing is said", () => {
|
|
178
|
+
const {remote, host, engine} = build("g2");
|
|
179
|
+
save_arrangement(engine, "yunos");
|
|
180
|
+
answer(host, remote, commands[0], 0);
|
|
181
|
+
expect(refused()).toEqual([]);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
test("with no session: not sent, and said", () => {
|
|
185
|
+
const {remote, host, engine} = build("g3");
|
|
186
|
+
gobj_change_state(remote, "ST_DISCONNECTED");
|
|
187
|
+
save_arrangement(engine, "realms");
|
|
188
|
+
expect(commands).toEqual([]);
|
|
189
|
+
expect(refused()).toEqual(["realms"]);
|
|
190
|
+
expect(logged.some((l) => /not sent/.test(l.msg))).toBe(true);
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
test("refused on the way out by the transport", () => {
|
|
194
|
+
const {remote, host, engine} = build("g4");
|
|
195
|
+
refuse_on_the_way = true;
|
|
196
|
+
save_arrangement(engine, "binaries");
|
|
197
|
+
expect(refused()).toEqual(["binaries"]);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
test("a refused write of another topic says nothing to the engine", () => {
|
|
201
|
+
const {remote, host, engine} = build("g5");
|
|
202
|
+
gobj_send_event(host, "EV_UPDATE_NODE", {
|
|
203
|
+
topic_name: "yunos", record: {id: "1", x: 1}
|
|
204
|
+
}, engine);
|
|
205
|
+
answer(host, remote, commands[0], -1, "refused");
|
|
206
|
+
expect(refused()).toEqual([]);
|
|
207
|
+
});
|
|
208
|
+
});
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* through the real gclasses (C_YUI_SHELL, C_YUI_TREEDB_TOPICS) on a
|
|
6
6
|
* document double. The helper (form_writes_in_flight.js) was tested
|
|
7
7
|
* and right; what failed was that two hosts never called the action
|
|
8
|
-
* that uses it
|
|
8
|
+
* that uses it, which no helper test
|
|
9
9
|
* can see.
|
|
10
10
|
*
|
|
11
11
|
* Copyright (c) 2026, ArtGins.
|
|
@@ -169,7 +169,7 @@ function errors()
|
|
|
169
169
|
return logged.filter((l) => l.level === "error").map((l) => l.msg);
|
|
170
170
|
}
|
|
171
171
|
|
|
172
|
-
describe("a Save in flight when the backend drops
|
|
172
|
+
describe("a Save in flight when the backend drops", () => {
|
|
173
173
|
|
|
174
174
|
test("the shell's connection edge answers every form, with no host forwarding", () => {
|
|
175
175
|
const {shell, topics, forms} = build("t1");
|
package/src/yui_install.js
CHANGED
|
@@ -194,6 +194,17 @@ function yui_install_start_watch()
|
|
|
194
194
|
* app can call yui_install_prompt() from a button of its
|
|
195
195
|
* own whenever it likes.
|
|
196
196
|
***************************************************************/
|
|
197
|
+
/* The question and its answers when the app names none. They are i18n
|
|
198
|
+
* KEYS, lower-case, and the ones every consumer already defines; the
|
|
199
|
+
* question was "Install this app?" until 7.25.16, a key no locale
|
|
200
|
+
* carries. Written as `i18n:` entries so the validate-locales script of
|
|
201
|
+
* a consumer finds them when it scans this module. */
|
|
202
|
+
const DEFAULT_ASK = Object.freeze({
|
|
203
|
+
message: Object.freeze({i18n: "install this app"}),
|
|
204
|
+
yes: Object.freeze({i18n: "install"}),
|
|
205
|
+
no: Object.freeze({i18n: "not now"})
|
|
206
|
+
});
|
|
207
|
+
|
|
197
208
|
function yui_install_ask_once(shell, opts)
|
|
198
209
|
{
|
|
199
210
|
opts = opts || {};
|
|
@@ -213,10 +224,10 @@ function yui_install_ask_once(shell, opts)
|
|
|
213
224
|
S.asking = true;
|
|
214
225
|
kw_set_local_storage_value(ASKED_KEY, true);
|
|
215
226
|
|
|
216
|
-
yui_shell_confirm_yesno(shell, opts.message ||
|
|
227
|
+
yui_shell_confirm_yesno(shell, opts.message || DEFAULT_ASK.message.i18n, {
|
|
217
228
|
t: opts.t,
|
|
218
|
-
yes_label: opts.yes_label ||
|
|
219
|
-
no_label: opts.no_label ||
|
|
229
|
+
yes_label: opts.yes_label || DEFAULT_ASK.yes.i18n,
|
|
230
|
+
no_label: opts.no_label || DEFAULT_ASK.no.i18n
|
|
220
231
|
}).then(function(yes) {
|
|
221
232
|
S.asking = false;
|
|
222
233
|
if(yes) {
|
package/src/yui_install.test.js
CHANGED
|
@@ -140,3 +140,26 @@ test("subscribe(): a throwing listener does not break the others", async () => {
|
|
|
140
140
|
off1();
|
|
141
141
|
off2();
|
|
142
142
|
});
|
|
143
|
+
|
|
144
|
+
/* The question asked when the app names none is an i18n KEY, the one
|
|
145
|
+
* every consumer already defines ("install this app"). It was
|
|
146
|
+
* "Install this app?", a key no locale carries, so an app that took
|
|
147
|
+
* the default asked in English in every language. */
|
|
148
|
+
test("ask_once(): the default question and answers are i18n keys", async () => {
|
|
149
|
+
const asked = [];
|
|
150
|
+
vi.resetModules();
|
|
151
|
+
vi.doMock("./shell_modals.js", () => ({
|
|
152
|
+
yui_shell_confirm_yesno: (shell, message, opts) => {
|
|
153
|
+
asked.push({message, yes: opts.yes_label, no: opts.no_label});
|
|
154
|
+
return new Promise(() => {});
|
|
155
|
+
}
|
|
156
|
+
}));
|
|
157
|
+
try {
|
|
158
|
+
const {yui_install_ask_once} = await import("./yui_install.js");
|
|
159
|
+
window[STASH] = fake_event("accepted");
|
|
160
|
+
yui_install_ask_once({fake_shell: true}, {});
|
|
161
|
+
expect(asked).toEqual([{message: "install this app", yes: "install", no: "not now"}]);
|
|
162
|
+
} finally {
|
|
163
|
+
vi.doUnmock("./shell_modals.js");
|
|
164
|
+
}
|
|
165
|
+
});
|
|
@@ -115,7 +115,7 @@ describe("is_hook_size", () => {
|
|
|
115
115
|
* The children of a hook as the NODE EVENTS deliver them: `{id,
|
|
116
116
|
* topic_name}`, with no hook_name. Not taken for a reference, their
|
|
117
117
|
* topic_name -- the same word on every row -- was a wildcard for every
|
|
118
|
-
* row that had received an UPDATED
|
|
118
|
+
* row that had received an UPDATED.
|
|
119
119
|
*/
|
|
120
120
|
describe("a hook's children as the node events deliver them", () => {
|
|
121
121
|
const row = {id: "w1", devices: [{id: "d7", topic_name: "devices"}]};
|
|
@@ -59,7 +59,7 @@ describe("reading and dropping the selection", () => {
|
|
|
59
59
|
* A table as the delete of the treedb topic table meets it AFTER a
|
|
60
60
|
* confirmation: rows came and went while the dialog was open. A position
|
|
61
61
|
* names whatever row sits there now; an id names the row the question named
|
|
62
|
-
* (the regression of d60ec78
|
|
62
|
+
* (the regression of d60ec78).
|
|
63
63
|
*/
|
|
64
64
|
function fake_table(rows, index)
|
|
65
65
|
{
|