@yuneta/gobj-ui 7.26.28 → 7.26.30

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.
@@ -0,0 +1,564 @@
1
+ /***********************************************************************
2
+ * nav_item_badge.wiring.test.js
3
+ *
4
+ * The badge of a MENU item: `badge` / `badge_tone` on an item of
5
+ * app_config.json or of yui_shell_set_submenu(), and
6
+ * yui_shell_set_nav_item_badge(), the twin of the toolbar's
7
+ * yui_shell_set_toolbar_item_badge() -- which takes a tone now too.
8
+ *
9
+ * Driven through the real C_YUI_SHELL and C_YUI_NAV on a document
10
+ * double: one entry drawn by a rail, an icon bar and a drawer; a
11
+ * tab strip rebuilt by yui_shell_set_submenu(); a section-index
12
+ * landing mounted after the badge was set; and each layout's place
13
+ * for the badge, on a nav created on its own.
14
+ *
15
+ * Copyright (c) 2026, ArtGins.
16
+ * All Rights Reserved.
17
+ ***********************************************************************/
18
+ import {describe, test, expect, beforeAll, beforeEach, afterEach} from "vitest";
19
+ import {install_dom_double} from "../test/dom_double.js";
20
+
21
+ install_dom_double();
22
+
23
+ const {
24
+ SDATA, SDATA_END, data_type_t, gclass_create, gobj_write_attr,
25
+ gobj_start_up, gobj_create_yuno, gobj_create, gobj_start, gobj_stop,
26
+ gobj_destroy, gobj_is_running, gobj_read_attr, gobj_send_event,
27
+ set_log_callback,
28
+ } = await import("@yuneta/gobj-js");
29
+ const {
30
+ register_c_yui_shell,
31
+ yui_shell_navigate,
32
+ yui_shell_set_nav_item_badge,
33
+ yui_shell_set_toolbar_item_badge,
34
+ yui_shell_set_submenu,
35
+ yui_shell_set_translator,
36
+ yui_shell_language_changed,
37
+ } = await import("./c_yui_shell.js");
38
+ const {register_c_yui_nav} = await import("./c_yui_nav.js");
39
+ const {BADGE_TONES} = await import("./yui_badge.js");
40
+
41
+ const logged = [];
42
+ const alive = [];
43
+ let yuno = null;
44
+ let seq = 0;
45
+
46
+ beforeAll(async () => {
47
+ const i18next = (await import("i18next")).default;
48
+ await i18next.init({lng: "en", resources: {}});
49
+ gobj_start_up(null, null, null, null, null, null, null);
50
+ set_log_callback((level, msg) => {
51
+ logged.push({level: String(level), msg: String(msg)});
52
+ });
53
+ gclass_create("C_TEST_BADGE_HOST", [], [["ST_IDLE", []]], {}, 0, [SDATA_END()], {}, 0, 0, 0, 0);
54
+ gclass_create(
55
+ "C_TEST_BADGE_VIEW", [], [["ST_IDLE", []]], {
56
+ mt_create: (gobj) => {
57
+ gobj_write_attr(gobj, "$container", document.createElement("div"));
58
+ }
59
+ }, 0, [
60
+ SDATA(data_type_t.DTP_POINTER, "$container", 0, null, "view root"),
61
+ SDATA_END()
62
+ ], {}, 0, 0, 0, 0
63
+ );
64
+ register_c_yui_shell();
65
+ register_c_yui_nav();
66
+ yuno = gobj_create_yuno("nav_badge_yuno", "C_TEST_BADGE_HOST", {});
67
+ gobj_start(yuno);
68
+ });
69
+
70
+ beforeEach(() => {
71
+ logged.length = 0;
72
+ });
73
+
74
+ afterEach(() => {
75
+ for(const gobj of alive.splice(0)) {
76
+ if(gobj_is_running(gobj)) {
77
+ gobj_stop(gobj);
78
+ }
79
+ gobj_destroy(gobj);
80
+ }
81
+ });
82
+
83
+
84
+ /***************************************************************
85
+ * The app: one alarms entry in the primary menu, which a rail
86
+ * (desktop) and an icon bar (phone) both draw, and which a drawer
87
+ * menu repeats; a section with a tab strip; a section-index
88
+ * landing; and a toolbar bell.
89
+ ***************************************************************/
90
+ function view()
91
+ {
92
+ return {stage: "main", gclass: "C_TEST_BADGE_VIEW", kw: {}};
93
+ }
94
+
95
+ function devices_items(extra)
96
+ {
97
+ return [
98
+ {id: "list", name: "list", route: "/devices/list", target: view()},
99
+ {id: "map", name: "map", route: "/devices/map", target: view()},
100
+ ...(extra || [])
101
+ ];
102
+ }
103
+
104
+ function config()
105
+ {
106
+ return {
107
+ shell: {
108
+ zones: {
109
+ top: {host: "toolbar"},
110
+ left: {host: "menu.primary"},
111
+ bottom: {host: "menu.primary"},
112
+ "top-sub": {host: "menu.secondary"},
113
+ center: {host: "stage.main"}
114
+ },
115
+ stages: {main: {zone: "center", default_route: "/home"}}
116
+ },
117
+ toolbar: {
118
+ zone: "top",
119
+ items: [
120
+ {id: "bell", icon: "yi-triangle-exclamation", aria_label: "alarms",
121
+ badge: 4, badge_tone: "info",
122
+ action: {type: "navigate", route: "/alarms"}}
123
+ ]
124
+ },
125
+ menu: {
126
+ primary: {
127
+ render: {
128
+ left: {layout: "vertical", icon_pos: "left"},
129
+ bottom: {layout: "icon-bar", icon_pos: "top"}
130
+ },
131
+ items: [
132
+ {id: "home", name: "home", icon: "yi-house", route: "/home", target: view()},
133
+ {id: "alarms", name: "alarms", icon: "yi-triangle-exclamation", route: "/alarms",
134
+ badge: 2, badge_tone: "warning", target: view()},
135
+ {id: "devices", name: "devices", icon: "yi-cog", route: "/devices",
136
+ submenu: {render: {"top-sub": "tabs"}, items: devices_items()}},
137
+ {id: "reports", name: "reports", icon: "yi-table", route: "/reports",
138
+ submenu: {index: true, items: [
139
+ {id: "daily", name: "daily", icon: "yi-eye",
140
+ route: "/reports/daily", target: view()},
141
+ {id: "weekly", name: "weekly", icon: "yi-cog",
142
+ route: "/reports/weekly", target: view()}
143
+ ]}}
144
+ ]
145
+ },
146
+ quick: {
147
+ render: {overlay: "drawer"},
148
+ items: [
149
+ {id: "alarms", name: "alarms", icon: "yi-triangle-exclamation", route: "/alarms"}
150
+ ]
151
+ }
152
+ }
153
+ };
154
+ }
155
+
156
+ function new_shell(cfg)
157
+ {
158
+ const shell = gobj_create(`badge_shell_${++seq}`, "C_YUI_SHELL",
159
+ {config: cfg || config(), use_hash: false}, yuno);
160
+ gobj_start(shell);
161
+ alive.push(shell);
162
+ return shell;
163
+ }
164
+
165
+ function new_nav(kw)
166
+ {
167
+ const nav = gobj_create(`badge_nav_${++seq}`, "C_YUI_NAV", kw, yuno);
168
+ gobj_start(nav);
169
+ alive.push(nav);
170
+ return nav;
171
+ }
172
+
173
+ function root(gobj)
174
+ {
175
+ return gobj_read_attr(gobj, "$container");
176
+ }
177
+
178
+ function zone(shell, id)
179
+ {
180
+ return root(shell).querySelector(`[data-zone="${id}"]`);
181
+ }
182
+
183
+ function drawer(shell)
184
+ {
185
+ return root(shell).querySelector(".yui-layer-overlay");
186
+ }
187
+
188
+ function badge_in($where, item_id)
189
+ {
190
+ return $where.querySelector(`[data-item-id="${item_id}"] .NAV_ITEM_BADGE`);
191
+ }
192
+
193
+ function badges_in($where, item_id)
194
+ {
195
+ return $where.querySelectorAll(`[data-item-id="${item_id}"] .NAV_ITEM_BADGE`);
196
+ }
197
+
198
+ /* What a badge shows: its text, or null while hidden, and its tones. */
199
+ function shows($badge)
200
+ {
201
+ return {
202
+ text: $badge.hasAttribute("hidden") ? null : $badge.textContent,
203
+ tones: BADGE_TONES.filter((tone) => $badge.classList.contains(`is-${tone}`))
204
+ };
205
+ }
206
+
207
+ function errors()
208
+ {
209
+ return logged.filter((l) => l.level === "error" || l.level === "warning").map((l) => l.msg);
210
+ }
211
+
212
+
213
+ describe("a badge declared in app_config.json", () => {
214
+ test("renders: after the label in the rail, on the icon in the icon bar", () => {
215
+ const shell = new_shell();
216
+
217
+ const $rail = badge_in(zone(shell, "left"), "alarms");
218
+ expect(shows($rail)).toEqual({text: "2", tones: ["warning"]});
219
+ expect($rail.getAttribute("role")).toBe("status");
220
+ expect($rail.classList.contains("yui-badge")).toBe(true);
221
+ expect($rail.classList.contains("yui-nav-badge-pinned")).toBe(false);
222
+ expect($rail.parentNode.tagName).toBe("A");
223
+ expect($rail.previousElementSibling.classList.contains("yui-nav-label")).toBe(true);
224
+
225
+ const $bar = badge_in(zone(shell, "bottom"), "alarms");
226
+ expect(shows($bar)).toEqual({text: "2", tones: ["warning"]});
227
+ expect($bar.classList.contains("yui-nav-badge-pinned")).toBe(true);
228
+ expect($bar.parentNode.classList.contains("yui-nav-icon")).toBe(true);
229
+ expect(errors()).toEqual([]);
230
+ });
231
+
232
+ test("each declaration seeds its own paint: the drawer's alarms declares none", () => {
233
+ const shell = new_shell();
234
+ expect(shows(badge_in(drawer(shell), "alarms"))).toEqual({text: null, tones: []});
235
+ });
236
+
237
+ test("an item without a badge carries an empty one, hidden", () => {
238
+ const shell = new_shell();
239
+ const $b = badge_in(zone(shell, "left"), "home");
240
+ expect(shows($b)).toEqual({text: null, tones: []});
241
+ expect($b.textContent).toBe("");
242
+ });
243
+ });
244
+
245
+ describe("yui_shell_set_nav_item_badge()", () => {
246
+ test("reaches every instance of the item: rail, icon bar and drawer", () => {
247
+ const shell = new_shell();
248
+ yui_shell_set_nav_item_badge(shell, "alarms", 7);
249
+
250
+ const all = [...badges_in(root(shell), "alarms")];
251
+ expect(all.length).toBe(3);
252
+ for(const $b of all) {
253
+ /* No tone given: the default look, not the seed's warning */
254
+ expect(shows($b)).toEqual({text: "7", tones: []});
255
+ }
256
+ expect(errors()).toEqual([]);
257
+ });
258
+
259
+ test("0, \"\" and null clear it", () => {
260
+ const shell = new_shell();
261
+ for(const value of [0, "", null]) {
262
+ yui_shell_set_nav_item_badge(shell, "alarms", 5, "danger");
263
+ yui_shell_set_nav_item_badge(shell, "alarms", value);
264
+ for(const $b of badges_in(root(shell), "alarms")) {
265
+ expect(shows($b), String(value)).toEqual({text: null, tones: []});
266
+ expect($b.textContent).toBe("");
267
+ }
268
+ }
269
+ });
270
+
271
+ test("over 99 reads 99+, and a text passes through", () => {
272
+ const shell = new_shell();
273
+ yui_shell_set_nav_item_badge(shell, "alarms", 150);
274
+ expect(shows(badge_in(zone(shell, "left"), "alarms")).text).toBe("99+");
275
+ yui_shell_set_nav_item_badge(shell, "alarms", "!");
276
+ expect(shows(badge_in(zone(shell, "left"), "alarms")).text).toBe("!");
277
+ });
278
+
279
+ test("the same value again touches no DOM: the badge is a live region", () => {
280
+ const shell = new_shell();
281
+ yui_shell_set_nav_item_badge(shell, "alarms", 3);
282
+ const $b = badge_in(zone(shell, "left"), "alarms");
283
+ const text = $b.firstChild;
284
+ yui_shell_set_nav_item_badge(shell, "alarms", 3);
285
+ expect($b.firstChild).toBe(text);
286
+ });
287
+ });
288
+
289
+ describe("the tone", () => {
290
+ test("is set, changed and cleared, without rewriting the count", () => {
291
+ const shell = new_shell();
292
+ yui_shell_set_nav_item_badge(shell, "alarms", 3, "danger");
293
+ const $b = badge_in(zone(shell, "bottom"), "alarms");
294
+ const text = $b.firstChild;
295
+ expect(shows($b)).toEqual({text: "3", tones: ["danger"]});
296
+
297
+ yui_shell_set_nav_item_badge(shell, "alarms", 3, "info");
298
+ expect(shows($b)).toEqual({text: "3", tones: ["info"]});
299
+
300
+ yui_shell_set_nav_item_badge(shell, "alarms", 3);
301
+ expect(shows($b)).toEqual({text: "3", tones: []});
302
+ expect($b.firstChild).toBe(text);
303
+ expect(errors()).toEqual([]);
304
+ });
305
+
306
+ test("an unknown one is logged and drawn with the default look", () => {
307
+ const shell = new_shell();
308
+ yui_shell_set_nav_item_badge(shell, "alarms", 3, "red");
309
+ for(const $b of badges_in(root(shell), "alarms")) {
310
+ expect(shows($b)).toEqual({text: "3", tones: []});
311
+ }
312
+ const e = errors();
313
+ expect(e.length).toBe(1);
314
+ expect(e[0]).toContain("yui_shell_set_nav_item_badge('alarms')");
315
+ expect(e[0]).toContain("'red'");
316
+ });
317
+
318
+ test("an unknown one is logged even where the id is not", () => {
319
+ const shell = new_shell();
320
+ yui_shell_set_nav_item_badge(shell, "nope", 3, "red");
321
+ expect(errors().length).toBe(1);
322
+ });
323
+
324
+ test("an unknown declared one is logged when it is drawn", () => {
325
+ new_nav({layout: "vertical", menu_id: "lab",
326
+ menu_items: [{id: "x", name: "x", route: "/x", badge: 1, badge_tone: "red"}]});
327
+ const e = errors();
328
+ expect(e.length).toBe(1);
329
+ expect(e[0]).toContain("C_YUI_NAV 'lab': item 'x'");
330
+ });
331
+ });
332
+
333
+ describe("the badge survives the nav being rebuilt", () => {
334
+ test("yui_shell_set_submenu() rebuilds the tab strip with it", () => {
335
+ const shell = new_shell();
336
+ yui_shell_set_nav_item_badge(shell, "list", 4, "danger");
337
+ const $old = badge_in(zone(shell, "top-sub"), "list");
338
+ expect(shows($old)).toEqual({text: "4", tones: ["danger"]});
339
+
340
+ yui_shell_set_submenu(shell, "devices", devices_items([
341
+ {id: "grid", name: "grid", route: "/devices/grid", target: view(), badge: 9}
342
+ ]));
343
+ const $new = badge_in(zone(shell, "top-sub"), "list");
344
+ expect($new).not.toBe($old);
345
+ expect(shows($new)).toEqual({text: "4", tones: ["danger"]});
346
+ /* An item given at runtime seeds its own */
347
+ expect(shows(badge_in(zone(shell, "top-sub"), "grid"))).toEqual({text: "9", tones: []});
348
+ expect(errors()).toEqual([]);
349
+ });
350
+
351
+ test("once set, the value of the id wins over a descriptor's own badge", () => {
352
+ const shell = new_shell();
353
+ yui_shell_set_nav_item_badge(shell, "list", 4);
354
+ yui_shell_set_submenu(shell, "devices", [
355
+ {id: "list", name: "list", route: "/devices/list", target: view(),
356
+ badge: 1, badge_tone: "success"}
357
+ ]);
358
+ expect(shows(badge_in(zone(shell, "top-sub"), "list"))).toEqual({text: "4", tones: []});
359
+ });
360
+
361
+ test("an item given by yui_shell_set_submenu() is a menu item too", () => {
362
+ const shell = new_shell();
363
+ yui_shell_set_submenu(shell, "devices", devices_items([
364
+ {id: "grid", name: "grid", route: "/devices/grid", target: view()}
365
+ ]));
366
+ yui_shell_set_nav_item_badge(shell, "grid", 2, "warning");
367
+ expect(shows(badge_in(zone(shell, "top-sub"), "grid"))).toEqual({text: "2", tones: ["warning"]});
368
+ });
369
+
370
+ test("a submenu of a sibling and a language change leave it as it was", () => {
371
+ const shell = new_shell();
372
+ yui_shell_set_nav_item_badge(shell, "alarms", 6, "info");
373
+ yui_shell_set_submenu(shell, "devices", devices_items());
374
+ yui_shell_set_translator(shell, (key) => String(key).toUpperCase());
375
+ yui_shell_language_changed(shell);
376
+
377
+ const $a = zone(shell, "left").querySelector('a[data-item-id="alarms"]');
378
+ expect($a.getAttribute("aria-label")).toBe("ALARMS"); // it did change language
379
+ for(const $b of badges_in(root(shell), "alarms")) {
380
+ expect(shows($b)).toEqual({text: "6", tones: ["info"]});
381
+ }
382
+ });
383
+
384
+ test("a nav created after the call draws it: the section-index landing", () => {
385
+ const shell = new_shell();
386
+ yui_shell_set_nav_item_badge(shell, "daily", 5, "success");
387
+ yui_shell_navigate(shell, "/reports");
388
+
389
+ const $stage = zone(shell, "center");
390
+ const $b = badge_in($stage, "daily");
391
+ expect($b).toBeTruthy();
392
+ expect(shows($b)).toEqual({text: "5", tones: ["success"]});
393
+ expect($b.parentNode.classList.contains("yui-nav-icon")).toBe(true);
394
+ expect(shows(badge_in($stage, "weekly"))).toEqual({text: null, tones: []});
395
+
396
+ /* ...and, mounted, it is one of the navs the call reaches */
397
+ yui_shell_set_nav_item_badge(shell, "daily", 8);
398
+ expect(shows(badge_in($stage, "daily"))).toEqual({text: "8", tones: []});
399
+ expect(errors()).toEqual([]);
400
+ });
401
+
402
+ test("on its own, a nav keeps what EV_SET_ITEM_BADGE set across EV_SET_ITEMS", () => {
403
+ const nav = new_nav({layout: "vertical", menu_id: "lab",
404
+ menu_items: [{id: "a", name: "a", route: "/a"}]});
405
+ gobj_send_event(nav, "EV_SET_ITEM_BADGE", {item_id: "a", badge: 3, badge_tone: "warning"}, yuno);
406
+ /* An id it does not draw yet is kept all the same */
407
+ gobj_send_event(nav, "EV_SET_ITEM_BADGE", {item_id: "c", badge: 1}, yuno);
408
+ expect(shows(badge_in(root(nav), "a"))).toEqual({text: "3", tones: ["warning"]});
409
+
410
+ gobj_send_event(nav, "EV_SET_ITEMS", {items: [
411
+ {id: "a", name: "a", route: "/a"},
412
+ {id: "c", name: "c", route: "/c"}
413
+ ]}, yuno);
414
+ expect(shows(badge_in(root(nav), "a"))).toEqual({text: "3", tones: ["warning"]});
415
+ expect(shows(badge_in(root(nav), "c"))).toEqual({text: "1", tones: []});
416
+ expect(errors()).toEqual([]);
417
+ });
418
+
419
+ test("EV_SET_ITEM_BADGE without an item id is refused, loudly", () => {
420
+ const nav = new_nav({layout: "vertical", menu_id: "lab", menu_items: []});
421
+ gobj_send_event(nav, "EV_SET_ITEM_BADGE", {badge: 3}, yuno);
422
+ expect(errors().length).toBe(1);
423
+ expect(errors()[0]).toContain("EV_SET_ITEM_BADGE without item_id");
424
+ });
425
+ });
426
+
427
+ describe("an unknown id behaves as the toolbar twin does", () => {
428
+ test("a silent no-op, for the nav and for the toolbar", () => {
429
+ const shell = new_shell();
430
+ const before = badges_in(root(shell), "alarms").map(shows);
431
+ expect(() => {
432
+ yui_shell_set_nav_item_badge(shell, "nope", 3);
433
+ yui_shell_set_toolbar_item_badge(shell, "nope", 3);
434
+ yui_shell_set_nav_item_badge(null, "alarms", 3);
435
+ yui_shell_set_toolbar_item_badge(null, "bell", 3);
436
+ yui_shell_set_nav_item_badge(shell, "", 3);
437
+ yui_shell_set_toolbar_item_badge(shell, "", 3);
438
+ }).not.toThrow();
439
+ expect(errors()).toEqual([]);
440
+ expect(badges_in(root(shell), "alarms").map(shows)).toEqual(before);
441
+ });
442
+
443
+ test("and nothing is kept for it: an item that comes later starts with its own", () => {
444
+ const shell = new_shell();
445
+ yui_shell_set_nav_item_badge(shell, "nope", 3);
446
+ yui_shell_set_submenu(shell, "devices", devices_items([
447
+ {id: "nope", name: "nope", route: "/devices/nope", target: view()}
448
+ ]));
449
+ expect(shows(badge_in(zone(shell, "top-sub"), "nope"))).toEqual({text: null, tones: []});
450
+ });
451
+ });
452
+
453
+ describe("where each layout puts it", () => {
454
+ function nav_with(kw, items)
455
+ {
456
+ return root(new_nav(Object.assign({menu_id: "lab", menu_items: items}, kw)));
457
+ }
458
+
459
+ test("tabs: after the label, before the close; on the icon of an icon-only tab", () => {
460
+ let $c = nav_with({layout: "tabs"}, [
461
+ {id: "t", name: "t", icon: "yi-eye", route: "/t", closable: true, badge: 2}
462
+ ]);
463
+ let $b = badge_in($c, "t");
464
+ expect($b.parentNode.tagName).toBe("A");
465
+ expect($b.previousElementSibling.getAttribute("data-i18n")).toBe("t");
466
+ expect($b.nextElementSibling.classList.contains("yui-nav-close")).toBe(true);
467
+
468
+ $c = nav_with({layout: "tabs", show_label: false}, [
469
+ {id: "t", name: "t", icon: "yi-eye", route: "/t", badge: 2}
470
+ ]);
471
+ expect(badge_in($c, "t").parentNode.classList.contains("yui-nav-icon")).toBe(true);
472
+ });
473
+
474
+ test("vertical: on the icon of a stacked item; between label and icon at icon_pos right", () => {
475
+ let $c = nav_with({layout: "vertical", icon_pos: "top"}, [
476
+ {id: "v", name: "v", icon: "yi-eye", route: "/v", badge: 2}
477
+ ]);
478
+ expect(badge_in($c, "v").classList.contains("yui-nav-badge-pinned")).toBe(true);
479
+
480
+ $c = nav_with({layout: "vertical", icon_pos: "right"}, [
481
+ {id: "v", name: "v", icon: "yi-eye", route: "/v", badge: 2}
482
+ ]);
483
+ let $b = badge_in($c, "v");
484
+ expect($b.previousElementSibling.classList.contains("yui-nav-label")).toBe(true);
485
+ expect($b.nextElementSibling.classList.contains("icon")).toBe(true);
486
+ });
487
+
488
+ test("drawer and submenu: after the label", () => {
489
+ for(const layout of ["drawer", "submenu"]) {
490
+ let $c = nav_with({layout: layout}, [
491
+ {id: "d", name: "d", icon: "yi-eye", route: "/d", badge: 2}
492
+ ]);
493
+ let $b = badge_in($c, "d");
494
+ expect(shows($b), layout).toEqual({text: "2", tones: []});
495
+ expect($b.previousElementSibling.classList.contains("yui-nav-label"), layout).toBe(true);
496
+ }
497
+ });
498
+
499
+ test("accordion: after the head's label, and after a leaf's", () => {
500
+ let $c = nav_with({layout: "accordion"}, [
501
+ {id: "s", name: "section", route: "/s", badge: 4, submenu: {items: [
502
+ {id: "l", name: "leaf", route: "/s/l", badge: 1, badge_tone: "info"}
503
+ ]}}
504
+ ]);
505
+ let $head = $c.querySelector(".yui-accordion-head");
506
+ let $hb = $head.querySelector(".NAV_ITEM_BADGE");
507
+ expect(shows($hb)).toEqual({text: "4", tones: []});
508
+ expect($head.firstChild.nodeType).toBe(3); // the label, which a language change rewrites
509
+ expect(shows(badge_in($c, "l"))).toEqual({text: "1", tones: ["info"]});
510
+ });
511
+
512
+ test("cards: on the icon, or after the label of a card without one", () => {
513
+ let $c = nav_with({layout: "cards"}, [
514
+ {id: "c1", name: "c1", icon: "yi-eye", route: "/c1", badge: 2},
515
+ {id: "c2", name: "c2", route: "/c2", badge: 3}
516
+ ]);
517
+ expect(badge_in($c, "c1").parentNode.classList.contains("yui-nav-icon")).toBe(true);
518
+ let $b = badge_in($c, "c2");
519
+ expect($b.parentNode.classList.contains("yui-nav-card")).toBe(true);
520
+ expect($b.previousElementSibling.classList.contains("yui-nav-label")).toBe(true);
521
+ });
522
+
523
+ test("breadcrumb: none, deliberately", () => {
524
+ let $c = nav_with({layout: "breadcrumb"}, [
525
+ {id: "b", name: "b", icon: "yi-eye", route: "/b", badge: 2}
526
+ ]);
527
+ expect($c.querySelectorAll(".NAV_ITEM_BADGE").length).toBe(0);
528
+ });
529
+ });
530
+
531
+ describe("the toolbar badge takes a tone too", () => {
532
+ function bell(shell)
533
+ {
534
+ return root(shell).querySelector('[data-toolbar-item-id="bell"] .yui-toolbar-badge');
535
+ }
536
+
537
+ test("declared: badge_tone next to badge", () => {
538
+ const shell = new_shell();
539
+ const $b = bell(shell);
540
+ expect(shows($b)).toEqual({text: "4", tones: ["info"]});
541
+ expect($b.classList.contains("TOOLBAR_ITEM_BADGE")).toBe(true);
542
+ expect($b.classList.contains("yui-badge")).toBe(true);
543
+ });
544
+
545
+ test("set, changed and cleared; a call with three arguments works as it did", () => {
546
+ const shell = new_shell();
547
+ yui_shell_set_toolbar_item_badge(shell, "bell", 3, "warning");
548
+ expect(shows(bell(shell))).toEqual({text: "3", tones: ["warning"]});
549
+ yui_shell_set_toolbar_item_badge(shell, "bell", 12);
550
+ expect(shows(bell(shell))).toEqual({text: "12", tones: []});
551
+ yui_shell_set_toolbar_item_badge(shell, "bell", 0);
552
+ expect(shows(bell(shell))).toEqual({text: null, tones: []});
553
+ expect(errors()).toEqual([]);
554
+ });
555
+
556
+ test("an unknown tone is logged and drawn with the default look", () => {
557
+ const shell = new_shell();
558
+ yui_shell_set_toolbar_item_badge(shell, "bell", 3, "red");
559
+ expect(shows(bell(shell))).toEqual({text: "3", tones: []});
560
+ const e = errors();
561
+ expect(e.length).toBe(1);
562
+ expect(e[0]).toContain("yui_shell_set_toolbar_item_badge('bell')");
563
+ });
564
+ });
@@ -88,7 +88,7 @@ function render_node(node)
88
88
  row.push(["span", {class: "icon ROUTEMAP_ICON"},
89
89
  [["i", {class: node.icon}]]]);
90
90
  }
91
- row.push(["span", {class: "ROUTEMAP_NAME", i18n: /* i18n: SHELL_ROUTE_MAP_I18N_KEYS */ node.label},
91
+ row.push(["span", {class: "ROUTEMAP_NAME", i18n: node.label},
92
92
  text_node(node.label)]);
93
93
  /* Documentation columns: route + implementing gclass + action event. */
94
94
  if(node.route) {
@@ -250,7 +250,7 @@ function build_body(shell, t)
250
250
  let $tree = createElement2(
251
251
  ["div", {class: "ROUTEMAP_TREE"}, [
252
252
  createElement2(["ul", {class: "ROUTEMAP_UL ROUTEMAP_ROOT"},
253
- [render_node(root)]])
253
+ [render_node(/* i18n: SHELL_ROUTE_MAP_I18N_KEYS */ root)]])
254
254
  ]]
255
255
  );
256
256