@yuneta/gobj-ui 7.25.15 → 7.25.17

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.
@@ -613,9 +613,30 @@ function build_dialog(shell, message, buttons, opts)
613
613
  }
614
614
 
615
615
 
616
+ /***************************************************************
617
+ * The labels a confirmation shows when the caller names none.
618
+ *
619
+ * They are i18n KEYS, lower-case like every key: a button
620
+ * translates its label with the app's t() -- its text, its title
621
+ * and its aria-label. Until 7.25.16 they were "OK", "Yes", "No",
622
+ * "Delete" and "Cancel", keys that no locale carries, so a dialog
623
+ * that took the defaults read in English in every language.
624
+ *
625
+ * Written as `i18n:` entries so the validate-locales script of a
626
+ * consumer finds them when it scans this module: an app that
627
+ * mounts these dialogs must define the five keys.
628
+ ***************************************************************/
629
+ const DEFAULT_LABEL = Object.freeze({
630
+ ok: Object.freeze({i18n: "ok"}),
631
+ yes: Object.freeze({i18n: "yes"}),
632
+ no: Object.freeze({i18n: "no"}),
633
+ delete: Object.freeze({i18n: "delete"}),
634
+ cancel: Object.freeze({i18n: "cancel"})
635
+ });
636
+
616
637
  export function yui_shell_confirm_ok(shell, message, opts)
617
638
  {
618
- let label = (opts && opts.ok_label) || "OK";
639
+ let label = (opts && opts.ok_label) || DEFAULT_LABEL.ok.i18n;
619
640
  if(!opts || !opts.type) {
620
641
  opts = Object.assign({}, opts, {type: "success"});
621
642
  }
@@ -626,8 +647,8 @@ export function yui_shell_confirm_ok(shell, message, opts)
626
647
 
627
648
  export function yui_shell_confirm_yesno(shell, message, opts)
628
649
  {
629
- let yes_label = (opts && opts.yes_label) || "Yes";
630
- let no_label = (opts && opts.no_label) || "No";
650
+ let yes_label = (opts && opts.yes_label) || DEFAULT_LABEL.yes.i18n;
651
+ let no_label = (opts && opts.no_label) || DEFAULT_LABEL.no.i18n;
631
652
  return build_dialog(shell, message, [
632
653
  {label: yes_label, value: "yes", kind: "primary"},
633
654
  {label: no_label, value: "no"}
@@ -650,8 +671,8 @@ export function yui_shell_confirm_yesno(shell, message, opts)
650
671
  ***************************************************************/
651
672
  export function yui_shell_confirm_danger(shell, message, opts)
652
673
  {
653
- let confirm_label = (opts && opts.confirm_label) || "Delete";
654
- let cancel_label = (opts && opts.cancel_label) || "Cancel";
674
+ let confirm_label = (opts && opts.confirm_label) || DEFAULT_LABEL.delete.i18n;
675
+ let cancel_label = (opts && opts.cancel_label) || DEFAULT_LABEL.cancel.i18n;
655
676
  if(!opts || !opts.type) {
656
677
  opts = Object.assign({}, opts, {type: "danger"});
657
678
  }
@@ -663,9 +684,9 @@ export function yui_shell_confirm_danger(shell, message, opts)
663
684
 
664
685
  export function yui_shell_confirm_yesnocancel(shell, message, opts)
665
686
  {
666
- let yes_label = (opts && opts.yes_label) || "Yes";
667
- let no_label = (opts && opts.no_label) || "No";
668
- let cancel_label = (opts && opts.cancel_label) || "Cancel";
687
+ let yes_label = (opts && opts.yes_label) || DEFAULT_LABEL.yes.i18n;
688
+ let no_label = (opts && opts.no_label) || DEFAULT_LABEL.no.i18n;
689
+ let cancel_label = (opts && opts.cancel_label) || DEFAULT_LABEL.cancel.i18n;
669
690
  return build_dialog(shell, message, [
670
691
  {label: yes_label, value: "yes", kind: "primary"},
671
692
  {label: no_label, value: "no"},
@@ -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;
@@ -200,3 +203,63 @@ describe("every other button of a dialog is named", () => {
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
+ });
@@ -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
- /* M31: Tabulator puts a string through innerHTML */
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");
@@ -149,13 +149,19 @@ function save_arrangement(engine, topic)
149
149
  });
150
150
  }
151
151
 
152
+ /* The answer as the real transports build it: the command frame
153
+ * carries ONLY the request's `__md_command__` (C_IEVENT_CLI's
154
+ * mt_command, gui_agent's C_AGENT_TREEDB_LINK), never the request's
155
+ * own kw. Echoing the whole kw here once hid that the refusal read
156
+ * its topic from a `record` no real answer carries. */
152
157
  function answer(host, remote, request, result, comment)
153
158
  {
159
+ const md_command = request.kw.__md_command__ || {};
154
160
  gobj_send_event(host, "EV_MT_COMMAND_ANSWER", {
155
161
  result: result,
156
162
  comment: comment || "",
157
163
  data: null,
158
- __md_iev__: {command_stack: [{command: request.command, kw: request.kw}]}
164
+ __md_iev__: {command_stack: [{command: request.command, kw: md_command}]}
159
165
  }, remote);
160
166
  }
161
167
 
@@ -172,6 +178,7 @@ describe("a __graphs__ write that does not land is said to the engine", () => {
172
178
  expect(commands.length).toBe(1);
173
179
  answer(host, remote, commands[0], -1, "not authorized");
174
180
  expect(refused()).toEqual(["yunos"]);
181
+ expect(logged.filter((l) => /names no topic/.test(l.msg))).toEqual([]);
175
182
  });
176
183
 
177
184
  test("landed: nothing is said", () => {
@@ -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 (M8)", () => {
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");
@@ -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 || "Install this app?", {
227
+ yui_shell_confirm_yesno(shell, opts.message || DEFAULT_ASK.message.i18n, {
217
228
  t: opts.t,
218
- yes_label: opts.yes_label || "install",
219
- no_label: opts.no_label || "not now"
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) {
@@ -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
+ });