@yuneta/gobj-ui 7.26.33 → 7.26.35
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 +1053 -144
- package/dist/gobj-ui.es.js +1053 -144
- package/package.json +2 -2
- package/scripts/validate-locales.mjs +672 -96
- package/src/c_yui_form.js +229 -11
- 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 +156 -44
- 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/form_table_validator.wiring.test.js +192 -0
- package/src/form_template_field.wiring.test.js +21 -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 +308 -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
|
@@ -0,0 +1,595 @@
|
|
|
1
|
+
/***********************************************************************
|
|
2
|
+
* form_json_editor_names.wiring.test.js
|
|
3
|
+
*
|
|
4
|
+
* The menu vanilla-jsoneditor draws in a json field of C_YUI_FORM
|
|
5
|
+
* was English, named by a `title` alone (REV-CLOUD V23.5.6). Now:
|
|
6
|
+
*
|
|
7
|
+
* - its items go through json_editor_menu_items(), the editor's
|
|
8
|
+
* onRenderMenu: titles translated (the shortcut kept), the mode
|
|
9
|
+
* buttons' text translated;
|
|
10
|
+
* - after each render the form's FSM names the drawn buttons:
|
|
11
|
+
* aria-label with its key, aria-pressed on the mode buttons;
|
|
12
|
+
* - a language change makes each editor draw its menu again.
|
|
13
|
+
*
|
|
14
|
+
* The pure halves are tested on their own; the wiring through the
|
|
15
|
+
* real C_YUI_FORM and C_YUI_SHELL on a document double, with an
|
|
16
|
+
* editor double that draws its menu from onRenderMenu, as the real
|
|
17
|
+
* one does, and again when handed a new one (updateProps).
|
|
18
|
+
*
|
|
19
|
+
* Copyright (c) 2026, ArtGins.
|
|
20
|
+
* All Rights Reserved.
|
|
21
|
+
***********************************************************************/
|
|
22
|
+
import {describe, test, expect, beforeAll, afterEach, vi} from "vitest";
|
|
23
|
+
import {install_dom_double} from "../test/dom_double.js";
|
|
24
|
+
|
|
25
|
+
install_dom_double();
|
|
26
|
+
|
|
27
|
+
if(typeof globalThis.ResizeObserver === "undefined") {
|
|
28
|
+
globalThis.ResizeObserver = class {
|
|
29
|
+
observe() {}
|
|
30
|
+
unobserve() {}
|
|
31
|
+
disconnect() {}
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/* The items the editor hands onRenderMenu in tree mode, as 3.13 builds
|
|
36
|
+
* them: the class of each button is what this library knows it by. */
|
|
37
|
+
const TREE_MENU = [
|
|
38
|
+
{type: "button", title: "Expand all", className: "jse-expand-all"},
|
|
39
|
+
{type: "button", title: "Collapse all", className: "jse-collapse-all"},
|
|
40
|
+
{type: "separator"},
|
|
41
|
+
{type: "button", title: "Sort", className: "jse-sort"},
|
|
42
|
+
{type: "button", title: "Transform contents (filter, sort, project)", className: "jse-transform"},
|
|
43
|
+
{type: "button", title: "Search (Ctrl+F)", className: "jse-search"},
|
|
44
|
+
{type: "button", title: "Open context menu (Click here, right click on the selection, or use the context menu button or Ctrl+Q)",
|
|
45
|
+
className: "jse-contextmenu"},
|
|
46
|
+
{type: "separator"},
|
|
47
|
+
{type: "button", title: "Undo (Ctrl+Z)", className: "jse-undo"},
|
|
48
|
+
{type: "button", title: "Redo (Ctrl+Shift+Z)", className: "jse-redo"},
|
|
49
|
+
{type: "space"},
|
|
50
|
+
{type: "button", text: "text", title: "Switch to text mode (current mode: tree)",
|
|
51
|
+
className: "jse-group-button jse-first"},
|
|
52
|
+
{type: "button", text: "tree", title: "Switch to tree mode (current mode: tree)",
|
|
53
|
+
className: "jse-group-button jse-selected"},
|
|
54
|
+
{type: "button", text: "table", title: "Switch to table mode (current mode: tree)",
|
|
55
|
+
className: "jse-group-button jse-last"},
|
|
56
|
+
{type: "button", title: "Something new", className: "jse-something-new"},
|
|
57
|
+
];
|
|
58
|
+
|
|
59
|
+
/* The CONTEXT menus of 3.13, as it builds them: no class on any item;
|
|
60
|
+
* an icon (FontAwesome), a place. `convert` picks the tree mode's
|
|
61
|
+
* second column: converting a selected value (arrows) or inserting
|
|
62
|
+
* (a plus), with the label to match. */
|
|
63
|
+
const fa = (name) => ({prefix: "fas", iconName: name, icon: [16, 16, [], "", ""]});
|
|
64
|
+
|
|
65
|
+
function tree_context_menu(convert)
|
|
66
|
+
{
|
|
67
|
+
const I = convert ? "Convert to:" : "Insert:";
|
|
68
|
+
const target = convert ? fa("arrow-right-arrow-left") : fa("plus");
|
|
69
|
+
return [
|
|
70
|
+
{type: "row", items: [
|
|
71
|
+
{type: "button", icon: fa("pen"), text: "Edit key", title: "Edit the key (Double-click on the key)"},
|
|
72
|
+
{type: "dropdown-button", width: "11em",
|
|
73
|
+
main: {type: "button", icon: fa("pen"), text: "Edit object",
|
|
74
|
+
title: "Edit the value (Double-click on the value)"},
|
|
75
|
+
items: [
|
|
76
|
+
{type: "button", icon: fa("pen"), text: "Edit object",
|
|
77
|
+
title: "Edit the value (Double-click on the value)"},
|
|
78
|
+
{type: "button", icon: fa("square"), text: "Enforce string",
|
|
79
|
+
title: "Enforce keeping the value as string when it contains a numeric value"}
|
|
80
|
+
]}
|
|
81
|
+
]},
|
|
82
|
+
{type: "separator"},
|
|
83
|
+
{type: "row", items: [
|
|
84
|
+
{type: "dropdown-button", width: "10em",
|
|
85
|
+
main: {type: "button", icon: fa("scissors"), text: "Cut",
|
|
86
|
+
title: "Cut selected contents, formatted with indentation (Ctrl+X)"},
|
|
87
|
+
items: [
|
|
88
|
+
{type: "button", icon: fa("scissors"), text: "Cut formatted",
|
|
89
|
+
title: "Cut selected contents, formatted with indentation (Ctrl+X)"},
|
|
90
|
+
{type: "button", icon: fa("scissors"), text: "Cut compacted",
|
|
91
|
+
title: "Cut selected contents, without indentation (Ctrl+Shift+X)"}
|
|
92
|
+
]},
|
|
93
|
+
{type: "dropdown-button", width: "12em",
|
|
94
|
+
main: {type: "button", icon: fa("copy"), text: "Copy",
|
|
95
|
+
title: "Copy selected contents, formatted with indentation (Ctrl+C)"},
|
|
96
|
+
items: [
|
|
97
|
+
{type: "button", icon: fa("copy"), text: "Copy formatted",
|
|
98
|
+
title: "Copy selected contents, formatted with indentation (Ctrl+C)"},
|
|
99
|
+
{type: "button", icon: fa("copy"), text: "Copy compacted",
|
|
100
|
+
title: "Copy selected contents, without indentation (Ctrl+Shift+C)"}
|
|
101
|
+
]},
|
|
102
|
+
{type: "button", icon: fa("paste"), text: "Paste", title: "Paste clipboard contents (Ctrl+V)"}
|
|
103
|
+
]},
|
|
104
|
+
{type: "separator"},
|
|
105
|
+
{type: "row", items: [
|
|
106
|
+
{type: "column", items: [
|
|
107
|
+
{type: "button", icon: fa("clone"), text: "Duplicate", title: "Duplicate selected contents (Ctrl+D)"},
|
|
108
|
+
{type: "button", icon: fa("crop-simple"), text: "Extract", title: "Extract selected contents"},
|
|
109
|
+
{type: "button", icon: fa("arrow-down-short-wide"), text: "Sort", title: "Sort array or object contents"},
|
|
110
|
+
{type: "button", icon: fa("filter"), text: "Transform",
|
|
111
|
+
title: "Transform array or object contents (filter, sort, project)"},
|
|
112
|
+
{type: "button", icon: fa("trash-can"), text: "Remove", title: "Remove selected contents (Delete)"}
|
|
113
|
+
]},
|
|
114
|
+
{type: "column", items: [
|
|
115
|
+
{type: "label", text: I},
|
|
116
|
+
{type: "button", icon: target, text: "Structure", title: I + " structure like the first item in the array"},
|
|
117
|
+
{type: "button", icon: target, text: "Object", title: I + " object"},
|
|
118
|
+
{type: "button", icon: target, text: "Array", title: I + " array"},
|
|
119
|
+
{type: "button", icon: target, text: "Value", title: I + " value"}
|
|
120
|
+
]}
|
|
121
|
+
]},
|
|
122
|
+
{type: "separator"},
|
|
123
|
+
{type: "row", items: [
|
|
124
|
+
{type: "button", icon: fa("square-caret-up"), text: "Insert before",
|
|
125
|
+
title: "Select area before current entry to insert or paste contents"},
|
|
126
|
+
{type: "button", icon: fa("square-caret-down"), text: "Insert after",
|
|
127
|
+
title: "Select area after current entry to insert or paste contents"}
|
|
128
|
+
]}
|
|
129
|
+
];
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function table_context_menu()
|
|
133
|
+
{
|
|
134
|
+
return [
|
|
135
|
+
{type: "separator"},
|
|
136
|
+
{type: "row", items: [
|
|
137
|
+
{type: "column", items: [
|
|
138
|
+
{type: "label", text: "Table cell:"},
|
|
139
|
+
{type: "dropdown-button", width: "11em",
|
|
140
|
+
main: {type: "button", icon: fa("pen"), text: "Edit",
|
|
141
|
+
title: "Edit the value (Double-click on the value)"},
|
|
142
|
+
items: [
|
|
143
|
+
{type: "button", icon: fa("pen"), text: "Edit", title: "Edit the value (Double-click on the value)"},
|
|
144
|
+
{type: "button", icon: fa("square-check"), text: "Enforce string",
|
|
145
|
+
title: "Enforce keeping the value as string when it contains a numeric value"}
|
|
146
|
+
]},
|
|
147
|
+
{type: "dropdown-button", width: "10em",
|
|
148
|
+
main: {type: "button", icon: fa("scissors"), text: "Cut",
|
|
149
|
+
title: "Cut selected contents, formatted with indentation (Ctrl+X)"},
|
|
150
|
+
items: [
|
|
151
|
+
{type: "button", icon: fa("scissors"), text: "Cut formatted", title: "(Ctrl+X)"},
|
|
152
|
+
{type: "button", icon: fa("scissors"), text: "Cut compacted", title: "(Ctrl+Shift+X)"}
|
|
153
|
+
]},
|
|
154
|
+
{type: "dropdown-button", width: "12em",
|
|
155
|
+
main: {type: "button", icon: fa("copy"), text: "Copy",
|
|
156
|
+
title: "Copy selected contents, formatted with indentation (Ctrl+C)"},
|
|
157
|
+
items: [
|
|
158
|
+
{type: "button", icon: fa("copy"), text: "Copy formatted", title: "(Ctrl+C)"},
|
|
159
|
+
{type: "button", icon: fa("copy"), text: "Copy compacted", title: "(Ctrl+Shift+C)"}
|
|
160
|
+
]},
|
|
161
|
+
{type: "button", icon: fa("paste"), text: "Paste", title: "Paste clipboard contents (Ctrl+V)"},
|
|
162
|
+
{type: "button", icon: fa("trash-can"), text: "Remove", title: "Remove selected contents (Delete)"}
|
|
163
|
+
]},
|
|
164
|
+
{type: "column", items: [
|
|
165
|
+
{type: "label", text: "Table row:"},
|
|
166
|
+
{type: "button", icon: fa("pen"), text: "Edit row", title: "Edit the current row"},
|
|
167
|
+
{type: "button", icon: fa("clone"), text: "Duplicate row", title: "Duplicate the current row (Ctrl+D)"},
|
|
168
|
+
{type: "button", icon: fa("plus"), text: "Insert before", title: "Insert a row before the current row"},
|
|
169
|
+
{type: "button", icon: fa("plus"), text: "Insert after", title: "Insert a row after the current row"},
|
|
170
|
+
{type: "button", icon: fa("trash-can"), text: "Remove row", title: "Remove current row"}
|
|
171
|
+
]}
|
|
172
|
+
]}
|
|
173
|
+
];
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/* Every button of a context menu, in the order it draws them, as
|
|
177
|
+
* {text, title} -- a dropdown's main, then its list. */
|
|
178
|
+
function context_buttons(items)
|
|
179
|
+
{
|
|
180
|
+
const out = [];
|
|
181
|
+
const walk = (it) => {
|
|
182
|
+
if(!it) {
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
if(it.type === "button") {
|
|
186
|
+
out.push({text: it.text, title: it.title});
|
|
187
|
+
} else if(it.type === "label") {
|
|
188
|
+
out.push({label: it.text});
|
|
189
|
+
} else if(it.type === "dropdown-button") {
|
|
190
|
+
walk(it.main);
|
|
191
|
+
(it.items || []).forEach(walk);
|
|
192
|
+
} else if(it.type === "row" || it.type === "column") {
|
|
193
|
+
(it.items || []).forEach(walk);
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
items.forEach(walk);
|
|
197
|
+
return out;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/* The editor: draws its menu from what onRenderMenu answers, into a
|
|
201
|
+
* `.jse-menu` of its target, and again when handed a new one. */
|
|
202
|
+
const editors = vi.hoisted(() => []);
|
|
203
|
+
vi.mock("vanilla-jsoneditor", () => ({
|
|
204
|
+
createJSONEditor: (opts) => {
|
|
205
|
+
const ed = {
|
|
206
|
+
target: opts.target,
|
|
207
|
+
props: Object.assign({}, opts.props),
|
|
208
|
+
content: {text: ""},
|
|
209
|
+
draws: 0,
|
|
210
|
+
draw() {
|
|
211
|
+
const items = this.props.onRenderMenu
|
|
212
|
+
? this.props.onRenderMenu(TREE_MENU.map((i) => Object.assign({}, i)), {mode: "tree"})
|
|
213
|
+
: TREE_MENU;
|
|
214
|
+
const $menu = document.createElement("div");
|
|
215
|
+
$menu.className = "jse-menu";
|
|
216
|
+
for(const item of items) {
|
|
217
|
+
if(item.type !== "button") {
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
const $b = document.createElement("button");
|
|
221
|
+
$b.className = `jse-button ${item.className}`;
|
|
222
|
+
$b.setAttribute("title", item.title);
|
|
223
|
+
$b.textContent = item.text || "";
|
|
224
|
+
$menu.appendChild($b);
|
|
225
|
+
}
|
|
226
|
+
const $old = this.target.querySelector(".jse-menu");
|
|
227
|
+
if($old) {
|
|
228
|
+
$old.remove();
|
|
229
|
+
}
|
|
230
|
+
this.target.appendChild($menu);
|
|
231
|
+
this.draws++;
|
|
232
|
+
},
|
|
233
|
+
updateProps(props) {
|
|
234
|
+
Object.assign(this.props, props);
|
|
235
|
+
this.draw();
|
|
236
|
+
},
|
|
237
|
+
/* A right click: the popup, inside its own element, from what
|
|
238
|
+
* onRenderContextMenu answers -- a dropdown as the editor
|
|
239
|
+
* draws it, its main button and the arrow that opens it. */
|
|
240
|
+
open_context_menu(mode) {
|
|
241
|
+
const make = mode === "table" ? table_context_menu() : tree_context_menu(true);
|
|
242
|
+
const items = this.props.onRenderContextMenu
|
|
243
|
+
? this.props.onRenderContextMenu(make, {mode: mode}) : make;
|
|
244
|
+
const $menu = document.createElement("div");
|
|
245
|
+
$menu.className = "jse-contextmenu";
|
|
246
|
+
const button = (item) => {
|
|
247
|
+
const $b = document.createElement("button");
|
|
248
|
+
$b.className = "jse-context-menu-button";
|
|
249
|
+
$b.setAttribute("title", item.title || "");
|
|
250
|
+
$b.textContent = item.text || "";
|
|
251
|
+
return $b;
|
|
252
|
+
};
|
|
253
|
+
const walk = (it) => {
|
|
254
|
+
if(!it) {
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
if(it.type === "button") {
|
|
258
|
+
$menu.appendChild(button(it));
|
|
259
|
+
} else if(it.type === "dropdown-button") {
|
|
260
|
+
const $d = document.createElement("div");
|
|
261
|
+
$d.className = "jse-dropdown-button";
|
|
262
|
+
$d.appendChild(button(it.main));
|
|
263
|
+
const $open = document.createElement("button");
|
|
264
|
+
$open.setAttribute("data-type", "jse-open-dropdown");
|
|
265
|
+
$d.appendChild($open);
|
|
266
|
+
$menu.appendChild($d);
|
|
267
|
+
(it.items || []).forEach(walk);
|
|
268
|
+
} else if(it.type === "row" || it.type === "column") {
|
|
269
|
+
(it.items || []).forEach(walk);
|
|
270
|
+
}
|
|
271
|
+
};
|
|
272
|
+
items.forEach(walk);
|
|
273
|
+
this.target.appendChild($menu);
|
|
274
|
+
return $menu;
|
|
275
|
+
},
|
|
276
|
+
get() {
|
|
277
|
+
return this.content;
|
|
278
|
+
},
|
|
279
|
+
set(content) {
|
|
280
|
+
this.content = content;
|
|
281
|
+
},
|
|
282
|
+
destroy() {},
|
|
283
|
+
};
|
|
284
|
+
ed.draw();
|
|
285
|
+
editors.push(ed);
|
|
286
|
+
return ed;
|
|
287
|
+
},
|
|
288
|
+
}));
|
|
289
|
+
|
|
290
|
+
const {
|
|
291
|
+
SDATA_END, gclass_create,
|
|
292
|
+
gobj_start_up, gobj_create_yuno, gobj_create, gobj_create_pure_child,
|
|
293
|
+
gobj_start, gobj_read_attr,
|
|
294
|
+
set_log_callback,
|
|
295
|
+
} = await import("@yuneta/gobj-js");
|
|
296
|
+
const {register_c_yui_shell, yui_shell_set_translator, yui_shell_language_changed} =
|
|
297
|
+
await import("./c_yui_shell.js");
|
|
298
|
+
const {register_c_yui_form} = await import("./c_yui_form.js");
|
|
299
|
+
const {json_editor_menu_items, json_editor_context_menu_items, name_json_editor_menu} =
|
|
300
|
+
await import("./form_json_editor_names.js");
|
|
301
|
+
|
|
302
|
+
const ES = {
|
|
303
|
+
"expand all": "desplegar todo",
|
|
304
|
+
"collapse all": "plegar todo",
|
|
305
|
+
"sort": "Ordenar",
|
|
306
|
+
"transform": "Transformar",
|
|
307
|
+
"search": "buscar",
|
|
308
|
+
"open context menu": "Abrir el menú contextual",
|
|
309
|
+
"undo": "Deshacer",
|
|
310
|
+
"redo": "Rehacer",
|
|
311
|
+
"text view": "Vista de texto",
|
|
312
|
+
"tree view": "Vista de árbol",
|
|
313
|
+
"table view": "Vista de tabla",
|
|
314
|
+
"text mode": "texto",
|
|
315
|
+
"tree mode": "árbol",
|
|
316
|
+
"table mode": "tabla",
|
|
317
|
+
"edit key": "Editar clave",
|
|
318
|
+
"edit value": "Editar valor",
|
|
319
|
+
"enforce string": "Forzar texto",
|
|
320
|
+
"cut": "Cortar",
|
|
321
|
+
"cut formatted": "Cortar con formato",
|
|
322
|
+
"cut compacted": "Cortar compactado",
|
|
323
|
+
"copy": "Copiar",
|
|
324
|
+
"copy formatted": "Copiar con formato",
|
|
325
|
+
"copy compacted": "Copiar compactado",
|
|
326
|
+
"paste": "Pegar",
|
|
327
|
+
"duplicate": "Duplicar",
|
|
328
|
+
"extract": "Extraer",
|
|
329
|
+
"remove": "Quitar",
|
|
330
|
+
"convert to": "Convertir en:",
|
|
331
|
+
"insert": "Insertar:",
|
|
332
|
+
"structure": "Estructura",
|
|
333
|
+
"object": "Objeto",
|
|
334
|
+
"array": "Lista",
|
|
335
|
+
"value": "Valor",
|
|
336
|
+
"insert before": "Insertar antes",
|
|
337
|
+
"insert after": "Insertar después",
|
|
338
|
+
"table cell": "Celda:",
|
|
339
|
+
"table row": "Fila:",
|
|
340
|
+
"edit row": "Editar fila",
|
|
341
|
+
"duplicate row": "Duplicar fila",
|
|
342
|
+
"remove row": "Quitar fila",
|
|
343
|
+
"more options": "Más opciones",
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
const logged = [];
|
|
347
|
+
let i18next = null;
|
|
348
|
+
let yuno = null;
|
|
349
|
+
let seq = 0;
|
|
350
|
+
|
|
351
|
+
beforeAll(async () => {
|
|
352
|
+
i18next = (await import("i18next")).default;
|
|
353
|
+
await i18next.init({lng: "en", resources: {es: {translation: ES}}});
|
|
354
|
+
gobj_start_up(null, null, null, null, null, null, null);
|
|
355
|
+
set_log_callback((level, msg) => {
|
|
356
|
+
logged.push({level: String(level), msg: String(msg)});
|
|
357
|
+
});
|
|
358
|
+
gclass_create("C_TEST_JSE_HOST", [], [["ST_IDLE", []]], {}, 0, [SDATA_END()], {}, 0, 0, 0, 0);
|
|
359
|
+
gclass_create(
|
|
360
|
+
"C_TEST_JSE_APP",
|
|
361
|
+
[["EV_RECORD_CHANGED", 0], ["EV_SAVE_RECORD", 0]],
|
|
362
|
+
[["ST_IDLE", [
|
|
363
|
+
["EV_RECORD_CHANGED", () => 0, null],
|
|
364
|
+
["EV_SAVE_RECORD", () => 0, null],
|
|
365
|
+
]]],
|
|
366
|
+
{}, 0, [SDATA_END()], {}, 0, 0, 0, 0
|
|
367
|
+
);
|
|
368
|
+
register_c_yui_shell();
|
|
369
|
+
register_c_yui_form();
|
|
370
|
+
yuno = gobj_create_yuno("jse_names_yuno", "C_TEST_JSE_HOST", {});
|
|
371
|
+
gobj_start(yuno);
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
afterEach(async () => {
|
|
375
|
+
editors.length = 0;
|
|
376
|
+
logged.length = 0;
|
|
377
|
+
await i18next.changeLanguage("en");
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
function errors()
|
|
381
|
+
{
|
|
382
|
+
return logged.filter((l) => l.level === "error").map((l) => l.msg);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/* A posted event is delivered on the next turn of the loop. */
|
|
386
|
+
function next_turn()
|
|
387
|
+
{
|
|
388
|
+
return new Promise((resolve) => setTimeout(resolve, 0));
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
function button($menu, cls)
|
|
392
|
+
{
|
|
393
|
+
return $menu.querySelector(`.${cls}`);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
describe("json_editor_menu_items(): what the editor draws", () => {
|
|
397
|
+
test("titles translated, the shortcut kept; the mode buttons' name and text", async () => {
|
|
398
|
+
await i18next.changeLanguage("es");
|
|
399
|
+
const items = json_editor_menu_items(TREE_MENU);
|
|
400
|
+
const by = (cls) => items.find((i) => String(i.className || "").includes(cls));
|
|
401
|
+
|
|
402
|
+
expect(by("jse-undo").title).toBe("Deshacer (Ctrl+Z)");
|
|
403
|
+
expect(by("jse-redo").title).toBe("Rehacer (Ctrl+Shift+Z)");
|
|
404
|
+
expect(by("jse-search").title).toBe("buscar (Ctrl+F)");
|
|
405
|
+
expect(by("jse-sort").title).toBe("Ordenar");
|
|
406
|
+
expect(by("jse-contextmenu").title).toBe("Abrir el menú contextual");
|
|
407
|
+
|
|
408
|
+
const tree = items.find((i) => i.text === "árbol");
|
|
409
|
+
expect(tree.title).toBe("Vista de árbol");
|
|
410
|
+
expect(tree.className).toContain("yui-jse-mode-tree");
|
|
411
|
+
expect(tree.className).toContain("jse-selected"); // the editor's state is kept
|
|
412
|
+
|
|
413
|
+
/* What it does not know, and what is no button, as it came. */
|
|
414
|
+
expect(by("jse-something-new")).toBe(TREE_MENU[TREE_MENU.length - 1]);
|
|
415
|
+
expect(items[2]).toBe(TREE_MENU[2]);
|
|
416
|
+
});
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
describe("name_json_editor_menu(): the drawn buttons", () => {
|
|
420
|
+
test("an aria-label with its key, and aria-pressed on the mode buttons", async () => {
|
|
421
|
+
await i18next.changeLanguage("es");
|
|
422
|
+
const $editor = document.createElement("div");
|
|
423
|
+
const $menu = document.createElement("div");
|
|
424
|
+
$menu.className = "jse-menu";
|
|
425
|
+
for(const cls of ["jse-undo", "jse-group-button yui-jse-mode-tree jse-selected",
|
|
426
|
+
"jse-group-button yui-jse-mode-table", "jse-something-new"]) {
|
|
427
|
+
const $b = document.createElement("button");
|
|
428
|
+
$b.className = `jse-button ${cls}`;
|
|
429
|
+
$menu.appendChild($b);
|
|
430
|
+
}
|
|
431
|
+
$editor.appendChild($menu);
|
|
432
|
+
|
|
433
|
+
expect(name_json_editor_menu($editor)).toBe(3);
|
|
434
|
+
const $undo = button($menu, "jse-undo");
|
|
435
|
+
expect($undo.getAttribute("aria-label")).toBe("Deshacer");
|
|
436
|
+
expect($undo.getAttribute("data-i18n-aria-label")).toBe("undo");
|
|
437
|
+
const $tree = button($menu, "yui-jse-mode-tree");
|
|
438
|
+
expect($tree.getAttribute("aria-label")).toBe("Vista de árbol");
|
|
439
|
+
expect($tree.getAttribute("data-i18n-aria-label")).toBe("tree view");
|
|
440
|
+
expect($tree.getAttribute("aria-pressed")).toBe("true");
|
|
441
|
+
expect(button($menu, "yui-jse-mode-table").getAttribute("aria-pressed")).toBe("false");
|
|
442
|
+
expect(button($menu, "jse-something-new").getAttribute("aria-label")).toBe(null);
|
|
443
|
+
});
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
describe("json_editor_context_menu_items(): the context menu, by icon and place", () => {
|
|
447
|
+
test("the tree mode's menu, converting a value", async () => {
|
|
448
|
+
await i18next.changeLanguage("es");
|
|
449
|
+
const items = json_editor_context_menu_items(tree_context_menu(true), {mode: "tree"});
|
|
450
|
+
expect(context_buttons(items)).toEqual([
|
|
451
|
+
{text: "Editar clave", title: "Editar clave"},
|
|
452
|
+
{text: "Editar valor", title: "Editar valor"}, // "Edit object" too
|
|
453
|
+
{text: "Editar valor", title: "Editar valor"},
|
|
454
|
+
{text: "Forzar texto", title: "Forzar texto"},
|
|
455
|
+
{text: "Cortar", title: "Cortar (Ctrl+X)"},
|
|
456
|
+
{text: "Cortar con formato", title: "Cortar con formato (Ctrl+X)"},
|
|
457
|
+
{text: "Cortar compactado", title: "Cortar compactado (Ctrl+Shift+X)"},
|
|
458
|
+
{text: "Copiar", title: "Copiar (Ctrl+C)"},
|
|
459
|
+
{text: "Copiar con formato", title: "Copiar con formato (Ctrl+C)"},
|
|
460
|
+
{text: "Copiar compactado", title: "Copiar compactado (Ctrl+Shift+C)"},
|
|
461
|
+
{text: "Pegar", title: "Pegar (Ctrl+V)"},
|
|
462
|
+
{text: "Duplicar", title: "Duplicar (Ctrl+D)"},
|
|
463
|
+
{text: "Extraer", title: "Extraer"},
|
|
464
|
+
{text: "Ordenar", title: "Ordenar"},
|
|
465
|
+
{text: "Transformar", title: "Transformar"},
|
|
466
|
+
{text: "Quitar", title: "Quitar"},
|
|
467
|
+
{label: "Convertir en:"},
|
|
468
|
+
{text: "Estructura", title: "Estructura"},
|
|
469
|
+
{text: "Objeto", title: "Objeto"},
|
|
470
|
+
{text: "Lista", title: "Lista"},
|
|
471
|
+
{text: "Valor", title: "Valor"},
|
|
472
|
+
{text: "Insertar antes", title: "Insertar antes"},
|
|
473
|
+
{text: "Insertar después", title: "Insertar después"},
|
|
474
|
+
]);
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
test("the tree mode's menu, inserting: the label says so, by the icon", async () => {
|
|
478
|
+
await i18next.changeLanguage("es");
|
|
479
|
+
const items = json_editor_context_menu_items(tree_context_menu(false), {mode: "tree"});
|
|
480
|
+
const labels = context_buttons(items).filter((b) => b.label);
|
|
481
|
+
expect(labels).toEqual([{label: "Insertar:"}]);
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
test("the table mode's menu: the cell and the row", async () => {
|
|
485
|
+
await i18next.changeLanguage("es");
|
|
486
|
+
const items = json_editor_context_menu_items(table_context_menu(), {mode: "table"});
|
|
487
|
+
expect(context_buttons(items)).toEqual([
|
|
488
|
+
{label: "Celda:"},
|
|
489
|
+
{text: "Editar valor", title: "Editar valor"},
|
|
490
|
+
{text: "Editar valor", title: "Editar valor"},
|
|
491
|
+
{text: "Forzar texto", title: "Forzar texto"},
|
|
492
|
+
{text: "Cortar", title: "Cortar (Ctrl+X)"},
|
|
493
|
+
{text: "Cortar con formato", title: "Cortar con formato (Ctrl+X)"},
|
|
494
|
+
{text: "Cortar compactado", title: "Cortar compactado (Ctrl+Shift+X)"},
|
|
495
|
+
{text: "Copiar", title: "Copiar (Ctrl+C)"},
|
|
496
|
+
{text: "Copiar con formato", title: "Copiar con formato (Ctrl+C)"},
|
|
497
|
+
{text: "Copiar compactado", title: "Copiar compactado (Ctrl+Shift+C)"},
|
|
498
|
+
{text: "Pegar", title: "Pegar (Ctrl+V)"},
|
|
499
|
+
{text: "Quitar", title: "Quitar"},
|
|
500
|
+
{label: "Fila:"},
|
|
501
|
+
{text: "Editar fila", title: "Editar fila"},
|
|
502
|
+
{text: "Duplicar fila", title: "Duplicar fila (Ctrl+D)"},
|
|
503
|
+
{text: "Insertar antes", title: "Insertar antes"},
|
|
504
|
+
{text: "Insertar después", title: "Insertar después"},
|
|
505
|
+
{text: "Quitar fila", title: "Quitar fila"},
|
|
506
|
+
]);
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
test("what it does not know comes back as it came, never mislabelled", () => {
|
|
510
|
+
const odd = {type: "button", icon: fa("rocket"), text: "Launch", title: "Launch it"};
|
|
511
|
+
const plain = {type: "button", text: "No icon", title: "No icon at all"};
|
|
512
|
+
const items = json_editor_context_menu_items([odd, plain, {type: "separator"}], {mode: "tree"});
|
|
513
|
+
expect(items[0]).toBe(odd);
|
|
514
|
+
expect(items[1]).toBe(plain);
|
|
515
|
+
expect(json_editor_context_menu_items(undefined, {})).toBe(undefined);
|
|
516
|
+
});
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
describe("a json field of C_YUI_FORM", () => {
|
|
520
|
+
function build(parent)
|
|
521
|
+
{
|
|
522
|
+
const app = gobj_create_pure_child(`jse_app${++seq}`, "C_TEST_JSE_APP", {}, parent || yuno);
|
|
523
|
+
gobj_start(app);
|
|
524
|
+
const form = gobj_create_pure_child(`jse_form${seq}`, "C_YUI_FORM", {
|
|
525
|
+
template: [
|
|
526
|
+
{id: "id", header: "id", type: "string", flag: ["persistent", "required"]},
|
|
527
|
+
{id: "settings", header: "settings", type: "dict", flag: ["persistent", "writable"]},
|
|
528
|
+
],
|
|
529
|
+
record: {id: "a", settings: {}},
|
|
530
|
+
render_mode: "edit",
|
|
531
|
+
editable: true,
|
|
532
|
+
}, app);
|
|
533
|
+
gobj_start(form);
|
|
534
|
+
expect(editors.length).toBe(1);
|
|
535
|
+
return {form, editor: editors[0]};
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
test("names its menu after the render, through the FSM", async () => {
|
|
539
|
+
const {editor} = build();
|
|
540
|
+
const $menu = editor.target.querySelector(".jse-menu");
|
|
541
|
+
/* Drawn translated at once; named on the next turn, when the
|
|
542
|
+
* event the render posted is handled. */
|
|
543
|
+
expect(button($menu, "jse-undo").getAttribute("title")).toBe("undo (Ctrl+Z)");
|
|
544
|
+
expect(button($menu, "jse-undo").getAttribute("aria-label")).toBe(null);
|
|
545
|
+
await next_turn();
|
|
546
|
+
expect(button($menu, "jse-undo").getAttribute("aria-label")).toBe("undo");
|
|
547
|
+
expect(button($menu, "jse-undo").getAttribute("data-i18n-aria-label")).toBe("undo");
|
|
548
|
+
expect(button($menu, "yui-jse-mode-tree").getAttribute("aria-pressed")).toBe("true");
|
|
549
|
+
expect(errors()).toEqual([]);
|
|
550
|
+
});
|
|
551
|
+
|
|
552
|
+
test("names its context menu when it opens: its buttons, and the arrow of a dropdown", async () => {
|
|
553
|
+
await i18next.changeLanguage("es");
|
|
554
|
+
const {editor} = build();
|
|
555
|
+
const $menu = editor.open_context_menu("tree");
|
|
556
|
+
const $cut = [...$menu.querySelectorAll(".jse-context-menu-button")]
|
|
557
|
+
.find(($b) => $b.textContent === "Cortar");
|
|
558
|
+
expect(!!$cut).toBe(true); // drawn translated at once
|
|
559
|
+
expect($cut.getAttribute("title")).toBe("Cortar (Ctrl+X)");
|
|
560
|
+
await next_turn();
|
|
561
|
+
expect($cut.getAttribute("aria-label")).toBe("Cortar");
|
|
562
|
+
const $arrows = $menu.querySelectorAll('button[data-type="jse-open-dropdown"]');
|
|
563
|
+
expect($arrows.length).toBe(3);
|
|
564
|
+
for(const $a of $arrows) {
|
|
565
|
+
expect($a.getAttribute("title")).toBe("Más opciones");
|
|
566
|
+
expect($a.getAttribute("aria-label")).toBe("Más opciones");
|
|
567
|
+
expect($a.getAttribute("data-i18n-aria-label")).toBe("more options");
|
|
568
|
+
}
|
|
569
|
+
expect(errors()).toEqual([]);
|
|
570
|
+
});
|
|
571
|
+
|
|
572
|
+
test("draws its menu again in the new language when the shell says it changed", async () => {
|
|
573
|
+
const shell = gobj_create(`jse_shell${++seq}`, "C_YUI_SHELL",
|
|
574
|
+
{config: {shell: {}}, use_hash: false}, yuno);
|
|
575
|
+
gobj_start(shell);
|
|
576
|
+
logged.length = 0; /* the shell's own start: no route table here */
|
|
577
|
+
const {editor} = build(shell);
|
|
578
|
+
await next_turn();
|
|
579
|
+
const draws = editor.draws;
|
|
580
|
+
|
|
581
|
+
await i18next.changeLanguage("es");
|
|
582
|
+
yui_shell_set_translator(shell, i18next.t.bind(i18next));
|
|
583
|
+
yui_shell_language_changed(shell);
|
|
584
|
+
expect(editor.draws).toBe(draws + 1);
|
|
585
|
+
|
|
586
|
+
await next_turn();
|
|
587
|
+
const $menu = editor.target.querySelector(".jse-menu");
|
|
588
|
+
expect(button($menu, "jse-undo").getAttribute("title")).toBe("Deshacer (Ctrl+Z)");
|
|
589
|
+
expect(button($menu, "jse-undo").getAttribute("aria-label")).toBe("Deshacer");
|
|
590
|
+
const $tree = button($menu, "yui-jse-mode-tree");
|
|
591
|
+
expect($tree.textContent).toBe("árbol");
|
|
592
|
+
expect($tree.getAttribute("aria-label")).toBe("Vista de árbol");
|
|
593
|
+
expect(errors()).toEqual([]);
|
|
594
|
+
});
|
|
595
|
+
});
|