@yuneta/gobj-ui 7.26.34 → 7.26.36
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 +223 -53
- package/dist/gobj-ui.cjs.js +1109 -143
- package/dist/gobj-ui.es.js +1109 -143
- package/package.json +2 -2
- package/scripts/validate-locales.mjs +672 -96
- package/src/c_yui_form.js +186 -9
- package/src/c_yui_gclass.js +2 -0
- package/src/c_yui_gobj_tree_js.js +2 -0
- package/src/c_yui_json.js +2 -0
- package/src/c_yui_json_graph.js +2 -0
- package/src/c_yui_nav.js +23 -5
- package/src/c_yui_shell.css +16 -0
- package/src/c_yui_shell.js +282 -26
- package/src/c_yui_treedb_graph.js +26 -8
- package/src/c_yui_treedb_topic_with_form.js +40 -18
- package/src/c_yui_treedb_topics.js +34 -11
- package/src/c_yui_window.js +132 -32
- package/src/c_yui_window_manager.js +250 -46
- package/src/form_json_editor_names.js +448 -0
- package/src/form_json_editor_names.wiring.test.js +595 -0
- package/src/form_label_i18n.wiring.test.js +169 -0
- package/src/i18n_key_functions.js +14 -6
- package/src/i18n_key_functions.test.js +124 -2
- package/src/lib_maplibre.js +26 -2
- package/src/lib_maplibre_relocalize.test.js +78 -0
- package/src/named_controls.wiring.test.js +468 -0
- package/src/nav_cards_helpers.js +9 -1
- package/src/nav_item_badge.wiring.test.js +182 -1
- package/src/shell_route_map.js +25 -8
- package/src/table_editor_name.wiring.test.js +201 -0
- package/src/treedb_column_title.wiring.test.js +151 -0
- package/src/treedb_json_drill.wiring.test.js +41 -1
- package/src/window_layer.wiring.test.js +348 -43
- package/src/window_show.wiring.test.js +6 -2
- package/src/window_stacking.wiring.test.js +334 -0
- package/src/yui_badge.js +58 -13
- package/src/yui_badge.test.js +40 -3
- package/src/yui_dev.js +2 -1
- package/src/yui_file_field.js +2 -0
- package/src/yui_frontend_view.js +2 -1
- package/src/yui_gclass_view.js +9 -6
- package/src/yui_tabulator_i18n.js +42 -0
- package/src/yui_tabulator_i18n.test.js +48 -1
|
@@ -53,11 +53,13 @@ const {
|
|
|
53
53
|
const logged = [];
|
|
54
54
|
const commands = []; /* what the fake transport was asked */
|
|
55
55
|
let refuse_in_session = false;
|
|
56
|
+
let refuse_silently = false;
|
|
56
57
|
|
|
57
58
|
/*
|
|
58
59
|
* The transport. Out of session it refuses, as gui_agent's routing
|
|
59
60
|
* adapter (C_AGENT_TREEDB_LINK) does; in session it refuses only when
|
|
60
|
-
* the test says so (a transport that cannot route the command)
|
|
61
|
+
* the test says so (a transport that cannot route the command) -- with
|
|
62
|
+
* its words, or with a bare -1 that says nothing.
|
|
61
63
|
*/
|
|
62
64
|
function remote_command_parser(gobj, command, kw, src)
|
|
63
65
|
{
|
|
@@ -65,6 +67,9 @@ function remote_command_parser(gobj, command, kw, src)
|
|
|
65
67
|
if(gobj_current_state(gobj) !== "ST_SESSION") {
|
|
66
68
|
return `cannot route '${command}' -- not in session`;
|
|
67
69
|
}
|
|
70
|
+
if(refuse_silently) {
|
|
71
|
+
return -1;
|
|
72
|
+
}
|
|
68
73
|
if(refuse_in_session) {
|
|
69
74
|
return `cannot route '${command}'`;
|
|
70
75
|
}
|
|
@@ -148,6 +153,7 @@ beforeEach(() => {
|
|
|
148
153
|
logged.length = 0;
|
|
149
154
|
commands.length = 0;
|
|
150
155
|
refuse_in_session = false;
|
|
156
|
+
refuse_silently = false;
|
|
151
157
|
});
|
|
152
158
|
|
|
153
159
|
/*
|
|
@@ -305,6 +311,40 @@ for(const gclass of ["C_YUI_TREEDB_TOPICS", "C_YUI_TREEDB_GRAPH"]) {
|
|
|
305
311
|
expect(logged.filter((l) => l.level === "error")).toEqual([]);
|
|
306
312
|
});
|
|
307
313
|
|
|
314
|
+
test("a failure with no word from the backend draws the fallback KEY, translated", async () => {
|
|
315
|
+
/* "print-tranger failed" travelled as `error`, which the
|
|
316
|
+
* viewer draws as it came: the raw key, in English, in every
|
|
317
|
+
* language (REV-CLOUD V23.5.4). As `i18n` it is drawn with
|
|
318
|
+
* t() and carries data-i18n, so it changes language too. */
|
|
319
|
+
const i18next = (await import("i18next")).default;
|
|
320
|
+
i18next.addResourceBundle("en", "translation",
|
|
321
|
+
{"print-tranger failed": "the tranger could not be read"}, true, true);
|
|
322
|
+
const {remote, host, viewer} = build(`${gclass}_i`, gclass);
|
|
323
|
+
click_stub(viewer);
|
|
324
|
+
const request = commands.find((c) => c.command === "print-tranger");
|
|
325
|
+
gobj_send_event(host, "EV_MT_COMMAND_ANSWER", {
|
|
326
|
+
result: -1, comment: "", data: null,
|
|
327
|
+
__md_iev__: {
|
|
328
|
+
command_stack: [{command: "print-tranger", kw: request.kw.__md_command__}]
|
|
329
|
+
}
|
|
330
|
+
}, remote);
|
|
331
|
+
expect(viewer.priv.errors.get("topics")).toEqual(
|
|
332
|
+
{error: "print-tranger failed", is_key: true});
|
|
333
|
+
const $err = viewer.priv.$tree.querySelector(".JSON_STUB_ERR");
|
|
334
|
+
expect($err.getAttribute("data-i18n")).toBe("print-tranger failed");
|
|
335
|
+
expect($err.textContent).toBe("the tranger could not be read");
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
test("a refusal of the transport that says nothing draws the fallback KEY too", () => {
|
|
339
|
+
const {viewer} = build(`${gclass}_j`, gclass);
|
|
340
|
+
refuse_silently = true;
|
|
341
|
+
click_stub(viewer);
|
|
342
|
+
expect(drills()).toEqual(["topics"]);
|
|
343
|
+
expect(viewer.priv.pending.has("topics")).toBe(false);
|
|
344
|
+
expect(viewer.priv.errors.get("topics")).toEqual(
|
|
345
|
+
{error: "print-tranger failed", is_key: true});
|
|
346
|
+
});
|
|
347
|
+
|
|
308
348
|
test("a drill that failed is not answered again by a drop", () => {
|
|
309
349
|
const {remote, host, viewer} = build(`${gclass}_f`, gclass);
|
|
310
350
|
click_stub(viewer);
|
|
@@ -2,15 +2,26 @@
|
|
|
2
2
|
* window_layer.wiring.test.js
|
|
3
3
|
*
|
|
4
4
|
* Where a C_YUI_WINDOW lives when its host names no `$parent`:
|
|
5
|
-
* the
|
|
6
|
-
*
|
|
7
|
-
* where it covered the whole
|
|
8
|
-
* own) and the dialog of its own
|
|
5
|
+
* the WINDOW layer of the shell, a stacking context inside the
|
|
6
|
+
* overlay layer, below the drawer, the dropdowns, the dialogs and
|
|
7
|
+
* the toasts -- not document.body, where it covered the whole
|
|
8
|
+
* shell (a stacking context of its own) and the dialog of its own
|
|
9
|
+
* close opened under it.
|
|
10
|
+
*
|
|
11
|
+
* And that the place FOLLOWS the shell (REV-CLOUD V23.3): a window
|
|
12
|
+
* parented to the app outlives a logout that destroys the shell.
|
|
13
|
+
* From 7.26.31 to 7.26.34 it stayed in the destroyed shell's layer,
|
|
14
|
+
* alive and detached, and the next shell never placed it again: the
|
|
15
|
+
* developer window of gui_agent, gui_treedb and wattyzer vanished
|
|
16
|
+
* after a logout and a login. The dock, the same.
|
|
9
17
|
*
|
|
10
18
|
* Copyright (c) 2026, ArtGins.
|
|
11
19
|
* All Rights Reserved.
|
|
12
20
|
***********************************************************************/
|
|
13
|
-
import {describe, test, expect, beforeAll} from "vitest";
|
|
21
|
+
import {describe, test, expect, beforeAll, beforeEach} from "vitest";
|
|
22
|
+
import {readFileSync} from "node:fs";
|
|
23
|
+
import {dirname, join} from "node:path";
|
|
24
|
+
import {fileURLToPath} from "node:url";
|
|
14
25
|
import {install_dom_double} from "../test/dom_double.js";
|
|
15
26
|
|
|
16
27
|
install_dom_double();
|
|
@@ -22,18 +33,27 @@ if(typeof globalThis.matchMedia === "undefined") {
|
|
|
22
33
|
const {
|
|
23
34
|
SDATA, SDATA_END, data_type_t, gclass_create, gobj_write_attr,
|
|
24
35
|
gobj_start_up, gobj_create_yuno, gobj_create, gobj_create_service,
|
|
25
|
-
gobj_start, gobj_stop, gobj_destroy, gobj_read_attr,
|
|
36
|
+
gobj_start, gobj_stop, gobj_destroy, gobj_read_attr, gobj_is_destroying,
|
|
37
|
+
set_log_callback,
|
|
26
38
|
} = await import("@yuneta/gobj-js");
|
|
27
|
-
const {register_c_yui_shell} = await import("./c_yui_shell.js");
|
|
39
|
+
const {register_c_yui_shell, yui_shell_window_layer, yui_shell_language_changed} = await import("./c_yui_shell.js");
|
|
28
40
|
const {register_c_yui_nav} = await import("./c_yui_nav.js");
|
|
29
41
|
const {register_c_yui_window} = await import("./c_yui_window.js");
|
|
42
|
+
const {register_c_yui_window_manager} = await import("./c_yui_window_manager.js");
|
|
43
|
+
|
|
44
|
+
const SRC = dirname(fileURLToPath(import.meta.url));
|
|
30
45
|
|
|
46
|
+
const logged = [];
|
|
31
47
|
let yuno = null;
|
|
48
|
+
let seq = 0;
|
|
32
49
|
|
|
33
50
|
beforeAll(async () => {
|
|
34
51
|
const i18next = (await import("i18next")).default;
|
|
35
52
|
await i18next.init({lng: "en", resources: {}});
|
|
36
53
|
gobj_start_up(null, null, null, null, null, null, null);
|
|
54
|
+
set_log_callback((level, msg) => {
|
|
55
|
+
logged.push({level: String(level), msg: String(msg)});
|
|
56
|
+
});
|
|
37
57
|
gclass_create("C_TEST_LAYER_HOST", [], [["ST_IDLE", []]], {}, 0, [SDATA_END()], {}, 0, 0, 0, 0);
|
|
38
58
|
gclass_create(
|
|
39
59
|
"C_TEST_LAYER_VIEW", [], [["ST_IDLE", []]], {
|
|
@@ -48,59 +68,344 @@ beforeAll(async () => {
|
|
|
48
68
|
register_c_yui_shell();
|
|
49
69
|
register_c_yui_nav();
|
|
50
70
|
register_c_yui_window();
|
|
71
|
+
register_c_yui_window_manager();
|
|
51
72
|
yuno = gobj_create_yuno("layer_yuno", "C_TEST_LAYER_HOST", {});
|
|
52
73
|
gobj_start(yuno);
|
|
53
74
|
});
|
|
54
75
|
|
|
76
|
+
beforeEach(() => {
|
|
77
|
+
logged.length = 0;
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
function errors()
|
|
81
|
+
{
|
|
82
|
+
return logged.filter((l) => l.level === "error").map((l) => l.msg);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/* A shell with one routed view, as an app builds it at login. */
|
|
86
|
+
function build_shell(opts)
|
|
87
|
+
{
|
|
88
|
+
const shell = gobj_create(`layer_shell_${++seq}`, "C_YUI_SHELL", Object.assign({
|
|
89
|
+
config: {
|
|
90
|
+
shell: {
|
|
91
|
+
zones: {center: {host: "stage.main"}},
|
|
92
|
+
stages: {main: {zone: "center", default_route: "/a"}}
|
|
93
|
+
},
|
|
94
|
+
menu: {primary: {items: [
|
|
95
|
+
{id: "a", name: "a", route: "/a",
|
|
96
|
+
target: {stage: "main", gclass: "C_TEST_LAYER_VIEW", kw: {}}}
|
|
97
|
+
]}}
|
|
98
|
+
},
|
|
99
|
+
use_hash: false
|
|
100
|
+
}, opts || {}), yuno);
|
|
101
|
+
gobj_start(shell);
|
|
102
|
+
return shell;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/* What an app does at logout (gui_agent's destroy_shell()). */
|
|
106
|
+
function logout(shell)
|
|
107
|
+
{
|
|
108
|
+
gobj_stop(shell);
|
|
109
|
+
gobj_destroy(shell);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function open_window(name, kw, parent)
|
|
113
|
+
{
|
|
114
|
+
const w = gobj_create_service(name, "C_YUI_WINDOW",
|
|
115
|
+
Object.assign({header: name, body: ["div", {}, name]}, kw || {}), parent || yuno);
|
|
116
|
+
gobj_start(w);
|
|
117
|
+
return w;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function close(w)
|
|
121
|
+
{
|
|
122
|
+
gobj_stop(w);
|
|
123
|
+
gobj_destroy(w);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/* Identity, asked as a boolean: a failing toBe() over a DOM double
|
|
127
|
+
* makes vitest pretty-print it, and the double has no attribute list. */
|
|
128
|
+
function lives_in(w, $place)
|
|
129
|
+
{
|
|
130
|
+
return gobj_read_attr(w, "$container").parentNode === $place;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function on_page(w)
|
|
134
|
+
{
|
|
135
|
+
return document.documentElement.contains(gobj_read_attr(w, "$container"));
|
|
136
|
+
}
|
|
137
|
+
|
|
55
138
|
describe("the default parent of a window", () => {
|
|
56
139
|
test("with no shell: document.body", () => {
|
|
57
|
-
const w =
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
gobj_stop(w);
|
|
61
|
-
gobj_destroy(w);
|
|
140
|
+
const w = open_window("layer_w0");
|
|
141
|
+
expect(lives_in(w, document.body)).toBe(true);
|
|
142
|
+
close(w);
|
|
62
143
|
});
|
|
63
144
|
|
|
64
|
-
test("with a shell: its overlay layer, also for a window that is not its child", () => {
|
|
65
|
-
const shell =
|
|
66
|
-
config: {
|
|
67
|
-
shell: {
|
|
68
|
-
zones: {center: {host: "stage.main"}},
|
|
69
|
-
stages: {main: {zone: "center", default_route: "/a"}}
|
|
70
|
-
},
|
|
71
|
-
menu: {primary: {items: [
|
|
72
|
-
{id: "a", name: "a", route: "/a",
|
|
73
|
-
target: {stage: "main", gclass: "C_TEST_LAYER_VIEW", kw: {}}}
|
|
74
|
-
]}}
|
|
75
|
-
},
|
|
76
|
-
use_hash: false
|
|
77
|
-
}, yuno);
|
|
78
|
-
gobj_start(shell);
|
|
145
|
+
test("with a shell: its window layer, inside the overlay layer, also for a window that is not its child", () => {
|
|
146
|
+
const shell = build_shell();
|
|
79
147
|
|
|
80
148
|
/* A child of the shell, and one parented to the app (the dev
|
|
81
|
-
* window, a device window of a view): both in the
|
|
82
|
-
const inside =
|
|
83
|
-
|
|
84
|
-
const outside = gobj_create_service("layer_w2", "C_YUI_WINDOW", {header: "b", body: ["div", {}, "b"]}, yuno);
|
|
85
|
-
gobj_start(outside);
|
|
149
|
+
* window, a device window of a view): both in the window layer. */
|
|
150
|
+
const inside = open_window("layer_w1", {}, shell);
|
|
151
|
+
const outside = open_window("layer_w2");
|
|
86
152
|
|
|
87
153
|
const overlay = shell.priv.layers.overlay;
|
|
88
|
-
|
|
89
|
-
expect(
|
|
90
|
-
expect(
|
|
154
|
+
const windows = yui_shell_window_layer(shell);
|
|
155
|
+
expect(!!overlay).toBe(true);
|
|
156
|
+
expect(windows.parentNode === overlay).toBe(true);
|
|
157
|
+
expect(windows.classList.contains("SHELL_WINDOW_LAYER")).toBe(true);
|
|
158
|
+
expect(lives_in(inside, windows)).toBe(true);
|
|
159
|
+
expect(lives_in(outside, windows)).toBe(true);
|
|
91
160
|
|
|
92
161
|
/* And a `$parent` named by the host still wins. */
|
|
93
162
|
const $mine = document.createElement("div");
|
|
94
|
-
const named =
|
|
95
|
-
|
|
96
|
-
gobj_start(named);
|
|
97
|
-
expect(gobj_read_attr(named, "$container").parentNode).toBe($mine);
|
|
163
|
+
const named = open_window("layer_w3", {$parent: $mine});
|
|
164
|
+
expect(lives_in(named, $mine)).toBe(true);
|
|
98
165
|
|
|
99
166
|
for(const w of [inside, outside, named]) {
|
|
100
|
-
|
|
101
|
-
gobj_destroy(w);
|
|
167
|
+
close(w);
|
|
102
168
|
}
|
|
103
|
-
|
|
104
|
-
|
|
169
|
+
logout(shell);
|
|
170
|
+
expect(errors()).toEqual([]);
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
describe("a window outlives its shell (logout, then login)", () => {
|
|
175
|
+
test("it goes to the body with the shell, and the next shell takes it in", () => {
|
|
176
|
+
const shell1 = build_shell();
|
|
177
|
+
const dev = open_window("layer_dev"); /* parented to the app */
|
|
178
|
+
expect(lives_in(dev, yui_shell_window_layer(shell1))).toBe(true);
|
|
179
|
+
|
|
180
|
+
/* Logout: the shell and its layers go, the window does not. */
|
|
181
|
+
logout(shell1);
|
|
182
|
+
expect(gobj_is_destroying(dev)).toBe(false);
|
|
183
|
+
expect(lives_in(dev, document.body)).toBe(true);
|
|
184
|
+
expect(on_page(dev)).toBe(true);
|
|
185
|
+
|
|
186
|
+
/* Login: a new shell, and the window in it, on the page. */
|
|
187
|
+
const shell2 = build_shell();
|
|
188
|
+
expect(lives_in(dev, yui_shell_window_layer(shell2))).toBe(true);
|
|
189
|
+
expect(on_page(dev)).toBe(true);
|
|
190
|
+
|
|
191
|
+
close(dev);
|
|
192
|
+
logout(shell2);
|
|
193
|
+
expect(errors()).toEqual([]);
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
test("a window created before any shell is taken in by the first one", () => {
|
|
197
|
+
const early = open_window("layer_early");
|
|
198
|
+
expect(lives_in(early, document.body)).toBe(true);
|
|
199
|
+
|
|
200
|
+
const shell = build_shell();
|
|
201
|
+
expect(lives_in(early, yui_shell_window_layer(shell))).toBe(true);
|
|
202
|
+
|
|
203
|
+
close(early);
|
|
204
|
+
logout(shell);
|
|
205
|
+
expect(errors()).toEqual([]);
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
test("a window its host placed stays there; one whose place went with the shell takes the window place", () => {
|
|
209
|
+
const shell1 = build_shell();
|
|
210
|
+
|
|
211
|
+
/* Placed by its host in an element of the page: never moved. */
|
|
212
|
+
const $mine = document.createElement("div");
|
|
213
|
+
document.body.appendChild($mine);
|
|
214
|
+
const placed = open_window("layer_placed", {$parent: $mine});
|
|
215
|
+
|
|
216
|
+
/* Placed by its host INSIDE the shell (a layer of it), but not
|
|
217
|
+
* its child: the place goes with the shell, the window does not. */
|
|
218
|
+
const $popup = shell1.priv.layers.popup;
|
|
219
|
+
const stranded = open_window("layer_stranded", {$parent: $popup});
|
|
220
|
+
|
|
221
|
+
logout(shell1);
|
|
222
|
+
expect(lives_in(placed, $mine)).toBe(true);
|
|
223
|
+
expect(lives_in(stranded, document.body)).toBe(true);
|
|
224
|
+
|
|
225
|
+
const shell2 = build_shell();
|
|
226
|
+
expect(lives_in(placed, $mine)).toBe(true);
|
|
227
|
+
expect(lives_in(stranded, yui_shell_window_layer(shell2))).toBe(true);
|
|
228
|
+
|
|
229
|
+
close(placed);
|
|
230
|
+
close(stranded);
|
|
231
|
+
$mine.remove();
|
|
232
|
+
logout(shell2);
|
|
233
|
+
expect(errors()).toEqual([]);
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
test("a child of the shell goes with it, and nobody is told about it later", () => {
|
|
237
|
+
const shell1 = build_shell();
|
|
238
|
+
const child = open_window("layer_child", {}, shell1);
|
|
239
|
+
logout(shell1);
|
|
240
|
+
expect(gobj_is_destroying(child)).toBe(true);
|
|
241
|
+
|
|
242
|
+
/* The next shell tells only the live ones: no event to a dead
|
|
243
|
+
* window, no "destroyed gobj still watches". */
|
|
244
|
+
const shell2 = build_shell();
|
|
245
|
+
logout(shell2);
|
|
246
|
+
expect(errors()).toEqual([]);
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
test("its Back entry goes with the shell that held it", () => {
|
|
250
|
+
const shell1 = build_shell({use_hash: true});
|
|
251
|
+
const floating = open_window("layer_back");
|
|
252
|
+
expect(!!gobj_read_attr(floating, "back_overlay")).toBe(true);
|
|
253
|
+
|
|
254
|
+
logout(shell1);
|
|
255
|
+
expect(gobj_read_attr(floating, "back_overlay")).toBe(null);
|
|
256
|
+
|
|
257
|
+
/* The next shell holds no entry of it, and closing it retires
|
|
258
|
+
* nothing there. */
|
|
259
|
+
const shell2 = build_shell({use_hash: true});
|
|
260
|
+
expect(shell2.priv.overlay_stack.length).toBe(0);
|
|
261
|
+
close(floating);
|
|
262
|
+
expect(shell2.priv.overlay_stack.length).toBe(0);
|
|
263
|
+
logout(shell2);
|
|
264
|
+
expect(errors()).toEqual([]);
|
|
265
|
+
});
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
describe("the dock follows the shell", () => {
|
|
269
|
+
test("a floating dock lives in the shell's popup layer, under the dialogs; in the body while there is none", () => {
|
|
270
|
+
const dock = gobj_create_service("layer_dock1", "C_YUI_WINDOW_MANAGER", {}, yuno);
|
|
271
|
+
gobj_start(dock);
|
|
272
|
+
const $dock = gobj_read_attr(dock, "$container");
|
|
273
|
+
expect($dock.parentNode === document.body).toBe(true);
|
|
274
|
+
|
|
275
|
+
const shell1 = build_shell();
|
|
276
|
+
expect($dock.parentNode === shell1.priv.layers.popup).toBe(true);
|
|
277
|
+
|
|
278
|
+
logout(shell1);
|
|
279
|
+
expect($dock.parentNode === document.body).toBe(true);
|
|
280
|
+
|
|
281
|
+
const shell2 = build_shell();
|
|
282
|
+
expect($dock.parentNode === shell2.priv.layers.popup).toBe(true);
|
|
283
|
+
|
|
284
|
+
gobj_stop(dock);
|
|
285
|
+
gobj_destroy(dock);
|
|
286
|
+
logout(shell2);
|
|
287
|
+
expect(errors()).toEqual([]);
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
test("a docked window survives a logout, and its chip still brings it back", () => {
|
|
291
|
+
const dock = gobj_create_service("layer_dock2", "C_YUI_WINDOW_MANAGER", {}, yuno);
|
|
292
|
+
gobj_start(dock);
|
|
293
|
+
const shell1 = build_shell();
|
|
294
|
+
const dev = open_window("layer_docked", {manager: dock});
|
|
295
|
+
const $win = gobj_read_attr(dev, "$container");
|
|
296
|
+
expect(lives_in(dev, yui_shell_window_layer(shell1))).toBe(true);
|
|
297
|
+
|
|
298
|
+
logout(shell1);
|
|
299
|
+
const shell2 = build_shell();
|
|
300
|
+
expect(lives_in(dev, yui_shell_window_layer(shell2))).toBe(true);
|
|
301
|
+
expect(on_page(dev)).toBe(true);
|
|
302
|
+
|
|
303
|
+
/* Its chip is still in the dock, and the dock on the page: a
|
|
304
|
+
* press of its toggle minimises it, another one brings it back
|
|
305
|
+
* on top. */
|
|
306
|
+
const $toggle = gobj_read_attr(dock, "$container").querySelector(".DOCK_CHIP_TOGGLE");
|
|
307
|
+
expect(!!$toggle).toBe(true);
|
|
308
|
+
expect(document.documentElement.contains($toggle)).toBe(true);
|
|
309
|
+
$toggle.click();
|
|
310
|
+
expect($win.style.display).toBe("none");
|
|
311
|
+
$toggle.click();
|
|
312
|
+
expect($win.style.display).toBe(undefined);
|
|
313
|
+
expect(Number($win.style.zIndex) > 10).toBe(true);
|
|
314
|
+
|
|
315
|
+
close(dev);
|
|
316
|
+
gobj_stop(dock);
|
|
317
|
+
gobj_destroy(dock);
|
|
318
|
+
logout(shell2);
|
|
319
|
+
expect(errors()).toEqual([]);
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
test("an inline dock moves to the next shell's zone, and floats in the body between them", () => {
|
|
323
|
+
const dock = gobj_create_service("layer_dock3", "C_YUI_WINDOW_MANAGER", {
|
|
324
|
+
dock_mode: "inline",
|
|
325
|
+
inline_selector: '[data-zone="bottom-sub"]'
|
|
326
|
+
}, yuno);
|
|
327
|
+
gobj_start(dock);
|
|
328
|
+
const $dock = gobj_read_attr(dock, "$container");
|
|
329
|
+
|
|
330
|
+
const shell1 = build_shell();
|
|
331
|
+
expect($dock.parentNode === shell1.priv.zones["bottom-sub"]).toBe(true);
|
|
332
|
+
|
|
333
|
+
logout(shell1);
|
|
334
|
+
expect($dock.parentNode === document.body).toBe(true);
|
|
335
|
+
expect($dock.classList.contains("yui-dock--floating")).toBe(true);
|
|
336
|
+
|
|
337
|
+
const shell2 = build_shell();
|
|
338
|
+
expect($dock.parentNode === shell2.priv.zones["bottom-sub"]).toBe(true);
|
|
339
|
+
expect($dock.classList.contains("yui-dock--inline")).toBe(true);
|
|
340
|
+
|
|
341
|
+
gobj_stop(dock);
|
|
342
|
+
gobj_destroy(dock);
|
|
343
|
+
logout(shell2);
|
|
344
|
+
expect(errors()).toEqual([]);
|
|
345
|
+
});
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
describe("the stacking of the windows", () => {
|
|
349
|
+
/* The z-index a window takes from the dock grows on every focus.
|
|
350
|
+
* In the overlay layer itself it competed with the drawer (18):
|
|
351
|
+
* after a few focus changes a new drawer opened UNDER the windows.
|
|
352
|
+
* The window layer is a stacking context below the drawer, so no
|
|
353
|
+
* number a window takes reaches it. CSS, read as text: the double
|
|
354
|
+
* has no layout. */
|
|
355
|
+
test("the window layer is a stacking context under the drawer", () => {
|
|
356
|
+
const css = readFileSync(join(SRC, "c_yui_shell.css"), "utf8");
|
|
357
|
+
const rule = (selector) => {
|
|
358
|
+
const m = css.match(new RegExp(selector + "\\s*\\{([^}]*)\\}"));
|
|
359
|
+
return m? m[1] : "";
|
|
360
|
+
};
|
|
361
|
+
const z_of = (body) => {
|
|
362
|
+
const m = body.match(/z-index:\s*(\d+)/);
|
|
363
|
+
return m? Number(m[1]) : NaN;
|
|
364
|
+
};
|
|
365
|
+
const windows = rule("\\.yui-layer > \\.yui-window-layer");
|
|
366
|
+
const drawer = rule("\\.yui-drawer");
|
|
367
|
+
expect(windows).toMatch(/position:\s*absolute/);
|
|
368
|
+
expect(windows).toMatch(/pointer-events:\s*none/);
|
|
369
|
+
expect(z_of(windows) < z_of(drawer)).toBe(true);
|
|
370
|
+
});
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
describe("the title of a dock chip changes language (REV-CLOUD V23, QA of estadodelaire)", () => {
|
|
374
|
+
/* A window with a DATA half (a device id) and a KIND half (a key):
|
|
375
|
+
* the toggle's title and aria-label are COMPOSED, so no key carries
|
|
376
|
+
* them. Up to 7.26.35 they stayed in the language the window was
|
|
377
|
+
* opened in; the dock composes them again on the shell's
|
|
378
|
+
* EV_LANGUAGE_CHANGED -- of the shell that holds it now, across a
|
|
379
|
+
* logout. */
|
|
380
|
+
test("composed again in the new language, and after a logout by the next shell", async () => {
|
|
381
|
+
const i18next = (await import("i18next")).default;
|
|
382
|
+
i18next.addResourceBundle("en", "translation", {"device alarms": "Device alarms"}, true, true);
|
|
383
|
+
i18next.addResourceBundle("es", "translation", {"device alarms": "Alarmas del dispositivo"}, true, true);
|
|
384
|
+
await i18next.changeLanguage("en");
|
|
385
|
+
|
|
386
|
+
const shell = build_shell();
|
|
387
|
+
const dock = gobj_create_service(`lang_dock_${++seq}`, "C_YUI_WINDOW_MANAGER", {}, yuno);
|
|
388
|
+
gobj_start(dock);
|
|
389
|
+
const w = open_window(`lang_w_${seq}`,
|
|
390
|
+
{title: "device alarms", title_prefix: "DEV-1", manager: dock});
|
|
391
|
+
const $toggle = gobj_read_attr(dock, "$container").querySelector(".DOCK_CHIP_TOGGLE");
|
|
392
|
+
expect($toggle.getAttribute("title")).toBe("DEV-1 · Device alarms");
|
|
393
|
+
|
|
394
|
+
await i18next.changeLanguage("es");
|
|
395
|
+
yui_shell_language_changed(shell);
|
|
396
|
+
expect($toggle.getAttribute("title")).toBe("DEV-1 · Alarmas del dispositivo");
|
|
397
|
+
expect($toggle.getAttribute("aria-label")).toBe("DEV-1 · Alarmas del dispositivo");
|
|
398
|
+
|
|
399
|
+
logout(shell);
|
|
400
|
+
const shell2 = build_shell();
|
|
401
|
+
await i18next.changeLanguage("en");
|
|
402
|
+
yui_shell_language_changed(shell2);
|
|
403
|
+
expect($toggle.getAttribute("title")).toBe("DEV-1 · Device alarms");
|
|
404
|
+
expect($toggle.getAttribute("aria-label")).toBe("DEV-1 · Device alarms");
|
|
405
|
+
expect(errors()).toEqual([]);
|
|
406
|
+
|
|
407
|
+
close(w);
|
|
408
|
+
gobj_destroy(dock);
|
|
409
|
+
logout(shell2);
|
|
105
410
|
});
|
|
106
411
|
});
|
|
@@ -51,17 +51,21 @@ function errors()
|
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
describe("EV_SHOW", () => {
|
|
54
|
-
test("without a dock: a hidden window is shown, and goes on top of
|
|
54
|
+
test("without a dock: a hidden window is shown, and goes on top of the others", () => {
|
|
55
55
|
const a = gobj_create_service("show_a", "C_YUI_WINDOW", {header: "a", body: ["div", {}, "a"]}, yuno);
|
|
56
56
|
gobj_start(a);
|
|
57
57
|
const b = gobj_create_service("show_b", "C_YUI_WINDOW", {header: "b", body: ["div", {}, "b"]}, yuno);
|
|
58
58
|
gobj_start(b);
|
|
59
59
|
const $a = gobj_read_attr(a, "$container");
|
|
60
|
+
const $b = gobj_read_attr(b, "$container");
|
|
60
61
|
$a.style.setProperty("display", "none", "important");
|
|
61
62
|
|
|
62
63
|
gobj_send_event(a, "EV_SHOW", {}, yuno);
|
|
63
64
|
expect($a.style.getPropertyValue("display")).toBe("");
|
|
64
|
-
|
|
65
|
+
/* On top by the windows' one z-order (yui_shell_raise_window),
|
|
66
|
+
* not by being moved in the DOM: a move takes its focus and the
|
|
67
|
+
* press that started on it. */
|
|
68
|
+
expect(Number($a.style.zIndex) > Number($b.style.zIndex)).toBe(true);
|
|
65
69
|
expect(errors()).toEqual([]);
|
|
66
70
|
});
|
|
67
71
|
|