@yuneta/gobj-ui 7.25.10 → 7.25.12

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 CHANGED
@@ -2134,6 +2134,11 @@ And what the toolbar offers, each answering a question the storage could not:
2134
2134
  answer. Escaping crosses two layers and the second is not JSON's:
2135
2135
  `helper_quote2doublequote()` rewrites *every* single quote before the parse,
2136
2136
  so a quote inside a value can only survive as `\u0027`.
2137
+ Its C / JSON switch is two named buttons, and a click on one is an event,
2138
+ `EV_EXPORT_VIEW` (`{pane: "c" | "json"}`), answered in every state: the
2139
+ texts were composed when the dialog opened, and the dialog outlives the
2140
+ screen and the loads under it (7.25.12; before, the switch ran in the DOM
2141
+ handler and the `machine` trace did not show it).
2137
2142
  - **import** — the writes that make the stored schema equal a pasted one, shown
2138
2143
  as a **plan** before it runs. Import is the one operation here that can delete
2139
2144
  a column, so what is confirmed is what runs.
@@ -2201,8 +2206,10 @@ else.
2201
2206
  column form sends every field it shows, so its Save after a reload wrote
2202
2207
  the OLD record over the newer one -- 7.25.8 told the operator to "try
2203
2208
  again", which did exactly that. The import plan is forgotten when a load
2204
- LEAVES (7.25.10: one that could not be sent keeps it), and a Yes that finds
2205
- it gone is said (`the import plan is gone: preview it again`). A load that
2209
+ LANDS, with the model it was computed on (7.25.12; from 7.25.9 to 7.25.11
2210
+ it went when the load LEFT, so a load that then failed left the dialog up
2211
+ with no plan and Import disabled). A load that fails or never leaves keeps
2212
+ it, and Import stays enabled. A Yes that finds it gone is said (`the import plan is gone: preview it again`). A load that
2206
2213
  failed or never left replaced nothing, and the dialog stays with what was
2207
2214
  typed. A confirmation is a shell modal and outlives a load: its Yes in
2208
2215
  `ST_EMPTY` or `ST_IDLE` is refused with the same words, not *"NOT
@@ -2225,7 +2232,12 @@ else.
2225
2232
  orphans -- is refused with
2226
2233
  `the schemas shown may be out of date: they are read again, try again when they are in`
2227
2234
  and the model is read again (keeping the draft chips); a move goes where it
2228
- was going and reads there. Not at once and not on a timer: a load refused
2235
+ was going and reads there -- the editor's own moves, and since 7.25.12 the
2236
+ host's too (`EV_SHOW` from a url or the browser's Back). **Any load that
2237
+ lands pays it** (7.25.12): the operator's Refresh, a late write's reload,
2238
+ the reconnect. Before, only the reload the editor asked for itself cleared
2239
+ it, so after a Refresh that landed the next edit was refused and read the
2240
+ store again. Not at once and not on a timer: a load refused
2229
2241
  on a deadline asked again at once is refused the same way, and one asked
2230
2242
  on a clock is polling. A Refresh asked while a load is in flight keeps the
2231
2243
  records of the model shown (7.25.10), not the half the first load got.
@@ -2235,7 +2247,9 @@ else.
2235
2247
  // the load failed in session: model kept, reload owed
2236
2248
  gobj_send_event(editor, "EV_EDIT_COLUMN", {col: "id"}, host); // refused, said; ST_LOADING
2237
2249
  // ... the load lands: the same click now opens the form
2238
- ``` A write answered with no record reloads the model and
2250
+ ```
2251
+
2252
+ A write answered with no record reloads the model and
2239
2253
  says what it did not send after it
2240
2254
  (`the treedb did not describe a write back: the writes after it were not sent`).
2241
2255
  - The toolbar offers Diagram, Check, Export, Import, New topic and New column
@@ -72220,7 +72220,8 @@ var PRIVATE_DATA$1 = {
72220
72220
  pending_seg: null,
72221
72221
  import_plan: null,
72222
72222
  import_pane: null,
72223
- import_run: null
72223
+ import_run: null,
72224
+ export_view: null
72224
72225
  };
72225
72226
  var __gclass__$1 = null;
72226
72227
  /******************************
@@ -72405,7 +72406,12 @@ function built_on_a_replaced_model(gobj, event, kw) {
72405
72406
  * export) -- is refused and said: it would be computed on what
72406
72407
  * the store may no longer hold, and the load that lands closes a
72407
72408
  * dialog built on the old model anyway (end_load()). A move is
72408
- * not refused (reload_after_move()): it goes, and reads there.
72409
+ * not refused (reload_after_move()): it goes, and reads there --
72410
+ * the editor's own moves and the host's alike (ac_show(): a url,
72411
+ * the browser's Back).
72412
+ *
72413
+ * Paid by ANY load that lands, whoever asked it
72414
+ * (ac_mt_command_answer()): the store has just been read whole.
72409
72415
  *
72410
72416
  * Not on a timer, and not at once: a load refused on a deadline
72411
72417
  * asked again at once is refused the same way, and one asked on a
@@ -72447,9 +72453,10 @@ function reload_after_move(gobj) {
72447
72453
  * left open is answered in ST_LOADING instead (ac_wait_for_the_load),
72448
72454
  * and a load that LANDS closes it (end_load()): it was built on the
72449
72455
  * model the load replaced, and its Save sends every field it shows.
72450
- * An import plan is forgotten here for the same reason: it is a list
72451
- * of writes computed against the model going away -- once the load
72452
- * has LEFT (one that could not be sent replaces nothing).
72456
+ * An import plan is forgotten for the same reason, and at the same
72457
+ * moment: when the load LANDS and the model it was computed on goes
72458
+ * (ac_mt_command_answer()). A load that cannot leave, or that fails,
72459
+ * replaces nothing, and the plan stays with the dialog showing it.
72453
72460
  *
72454
72461
  * `keep_written`: a load nobody ASKED for -- the reconnect, a write
72455
72462
  * that turned out done, the reload a refused load owed -- keeps what
@@ -72503,7 +72510,6 @@ function request_model(gobj, keep_written) {
72503
72510
  end_load(gobj);
72504
72511
  return -1;
72505
72512
  }
72506
- forget_import_plan(gobj);
72507
72513
  priv.pending = left;
72508
72514
  return 0;
72509
72515
  }
@@ -73663,6 +73669,7 @@ function close_dialog(gobj) {
73663
73669
  if (!priv.dialog) return;
73664
73670
  let dialog = priv.dialog;
73665
73671
  priv.dialog = null;
73672
+ priv.export_view = null;
73666
73673
  try {
73667
73674
  dialog.close();
73668
73675
  } catch (e) {
@@ -73681,6 +73688,7 @@ function open_dialog(gobj, $content, title, logical, title_prefix) {
73681
73688
  t: i18next.t,
73682
73689
  on_close: () => {
73683
73690
  priv.dialog = null;
73691
+ priv.export_view = null;
73684
73692
  }
73685
73693
  });
73686
73694
  (0, _yuneta_gobj_js.refresh_language)($content, i18next.t);
@@ -74409,17 +74417,43 @@ function open_export(gobj, treedb) {
74409
74417
  let $text = $content.querySelector(".SCHEMA_EXPORT_TEXT");
74410
74418
  for (let $tab of $content.querySelectorAll(".SCHEMA_EXPORT_TAB")) $tab.addEventListener("click", (evt) => {
74411
74419
  evt.stopPropagation();
74412
- for (let $other of $content.querySelectorAll(".SCHEMA_EXPORT_TAB")) {
74413
- $other.classList.toggle("selected_state", $other === $tab);
74414
- $other.setAttribute("aria-pressed", $other === $tab ? "true" : "false");
74415
- }
74416
- $text.value = $tab.dataset.pane === "c" ? c_text : json_text;
74420
+ (0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_EXPORT_VIEW", { pane: $tab.dataset.pane }, gobj);
74417
74421
  });
74418
74422
  $content.querySelector(".SCHEMA_EXPORT_COPY").addEventListener("click", (evt) => {
74419
74423
  evt.stopPropagation();
74420
74424
  copy_text(gobj, $text);
74421
74425
  });
74422
74426
  open_dialog(gobj, $content, "export", "SCHEMA_EXPORT_DIALOG", treedb.id);
74427
+ priv.export_view = {
74428
+ $content,
74429
+ texts: {
74430
+ c: c_text,
74431
+ json: json_text
74432
+ }
74433
+ };
74434
+ }
74435
+ /***************************************************************
74436
+ * Show one of the export's two texts, and say which one is shown
74437
+ * (`selected_state` + aria-pressed). Reached only through
74438
+ * EV_EXPORT_VIEW: a click is an action like any other.
74439
+ ***************************************************************/
74440
+ function show_export_view(gobj, pane) {
74441
+ let view = gobj.priv.export_view;
74442
+ if (!view) {
74443
+ (0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: export view '${pane}' asked with no export open`);
74444
+ return -1;
74445
+ }
74446
+ if (!Object.prototype.hasOwnProperty.call(view.texts, pane)) {
74447
+ (0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: the export has no view '${pane}'`);
74448
+ return -1;
74449
+ }
74450
+ for (let $tab of view.$content.querySelectorAll(".SCHEMA_EXPORT_TAB")) {
74451
+ let shown = $tab.dataset.pane === pane;
74452
+ $tab.classList.toggle("selected_state", shown);
74453
+ $tab.setAttribute("aria-pressed", shown ? "true" : "false");
74454
+ }
74455
+ view.$content.querySelector(".SCHEMA_EXPORT_TEXT").value = view.texts[pane];
74456
+ return 0;
74423
74457
  }
74424
74458
  /***************************************************************
74425
74459
  * Copy through the clipboard when the page is allowed one, and
@@ -74574,9 +74608,9 @@ function open_import(gobj, treedb) {
74574
74608
  }
74575
74609
  /***************************************************************
74576
74610
  * The plan is a list of writes computed against the model shown:
74577
- * a load replaces the model, so the plan goes with it and the
74578
- * dialog, if it stays up (a load that failed keeps the model),
74579
- * asks for a Preview again.
74611
+ * a load that LANDS replaces the model, so the plan goes with it
74612
+ * (and end_load() closes the dialog built on that model). A load
74613
+ * that fails or never leaves keeps the model, and the plan.
74580
74614
  ***************************************************************/
74581
74615
  function forget_import_plan(gobj) {
74582
74616
  let priv = gobj.priv;
@@ -75143,6 +75177,8 @@ function ac_mt_command_answer(gobj, event, kw, src) {
75143
75177
  priv.order_undo = {};
75144
75178
  priv.records_before = null;
75145
75179
  priv.model_gen++;
75180
+ priv.reload_on_open = false;
75181
+ forget_import_plan(gobj);
75146
75182
  build_model(gobj);
75147
75183
  start_measuring(gobj);
75148
75184
  return end_load(gobj);
@@ -75273,7 +75309,8 @@ function ac_show(gobj, event, kw, src) {
75273
75309
  priv.pending_seg = seg;
75274
75310
  return 0;
75275
75311
  }
75276
- return apply_seg(gobj, seg);
75312
+ apply_seg(gobj, seg);
75313
+ return reload_after_move(gobj);
75277
75314
  }
75278
75315
  /***************************************************************
75279
75316
  * Hidden by the host: drop the overlays. A dialog outlives the
@@ -75433,7 +75470,9 @@ function confirm_then(gobj, message, detail, kw) {
75433
75470
  kw = Object.assign({ model_gen: gobj.priv.model_gen }, kw);
75434
75471
  yui_shell_confirm_danger(yui_shell_of(gobj), message, {
75435
75472
  t: i18next.t,
75436
- detail
75473
+ detail,
75474
+ confirm_label: "delete",
75475
+ cancel_label: "cancel"
75437
75476
  }).then((yes) => {
75438
75477
  if (!yes || (0, _yuneta_gobj_js.gobj_is_destroying)(gobj)) return;
75439
75478
  (0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_CONFIRMED", kw, gobj);
@@ -75710,6 +75749,15 @@ function ac_export(gobj, event, kw, src) {
75710
75749
  open_export(gobj, treedb);
75711
75750
  return 0;
75712
75751
  }
75752
+ /***************************************************************
75753
+ * The export dialog's C / JSON switch. Its texts were composed
75754
+ * when it opened, so it reads nothing of the model and is
75755
+ * answered in every state: the dialog stays up through a load
75756
+ * that fails and through a move sent by the host.
75757
+ ***************************************************************/
75758
+ function ac_export_view(gobj, event, kw, src) {
75759
+ return show_export_view(gobj, kw && kw.pane);
75760
+ }
75713
75761
  function ac_import(gobj, event, kw, src) {
75714
75762
  let treedb = current_treedb(gobj);
75715
75763
  if (refuse_if_readonly(gobj, event)) return -1;
@@ -75893,6 +75941,11 @@ function create_gclass$1(gclass_name) {
75893
75941
  "EV_DRAFTS",
75894
75942
  ac_drafts,
75895
75943
  null
75944
+ ],
75945
+ [
75946
+ "EV_EXPORT_VIEW",
75947
+ ac_export_view,
75948
+ null
75896
75949
  ]
75897
75950
  ];
75898
75951
  const TREEDB_WIDE = [
@@ -76100,6 +76153,7 @@ function create_gclass$1(gclass_name) {
76100
76153
  ["EV_UNDO_ORDER", 0],
76101
76154
  ["EV_VALIDATE", 0],
76102
76155
  ["EV_EXPORT", 0],
76156
+ ["EV_EXPORT_VIEW", 0],
76103
76157
  ["EV_IMPORT", 0],
76104
76158
  ["EV_PREVIEW_IMPORT", 0],
76105
76159
  ["EV_APPLY_IMPORT", 0],
@@ -72215,7 +72215,8 @@ var PRIVATE_DATA$1 = {
72215
72215
  pending_seg: null,
72216
72216
  import_plan: null,
72217
72217
  import_pane: null,
72218
- import_run: null
72218
+ import_run: null,
72219
+ export_view: null
72219
72220
  };
72220
72221
  var __gclass__$1 = null;
72221
72222
  /******************************
@@ -72400,7 +72401,12 @@ function built_on_a_replaced_model(gobj, event, kw) {
72400
72401
  * export) -- is refused and said: it would be computed on what
72401
72402
  * the store may no longer hold, and the load that lands closes a
72402
72403
  * dialog built on the old model anyway (end_load()). A move is
72403
- * not refused (reload_after_move()): it goes, and reads there.
72404
+ * not refused (reload_after_move()): it goes, and reads there --
72405
+ * the editor's own moves and the host's alike (ac_show(): a url,
72406
+ * the browser's Back).
72407
+ *
72408
+ * Paid by ANY load that lands, whoever asked it
72409
+ * (ac_mt_command_answer()): the store has just been read whole.
72404
72410
  *
72405
72411
  * Not on a timer, and not at once: a load refused on a deadline
72406
72412
  * asked again at once is refused the same way, and one asked on a
@@ -72442,9 +72448,10 @@ function reload_after_move(gobj) {
72442
72448
  * left open is answered in ST_LOADING instead (ac_wait_for_the_load),
72443
72449
  * and a load that LANDS closes it (end_load()): it was built on the
72444
72450
  * model the load replaced, and its Save sends every field it shows.
72445
- * An import plan is forgotten here for the same reason: it is a list
72446
- * of writes computed against the model going away -- once the load
72447
- * has LEFT (one that could not be sent replaces nothing).
72451
+ * An import plan is forgotten for the same reason, and at the same
72452
+ * moment: when the load LANDS and the model it was computed on goes
72453
+ * (ac_mt_command_answer()). A load that cannot leave, or that fails,
72454
+ * replaces nothing, and the plan stays with the dialog showing it.
72448
72455
  *
72449
72456
  * `keep_written`: a load nobody ASKED for -- the reconnect, a write
72450
72457
  * that turned out done, the reload a refused load owed -- keeps what
@@ -72498,7 +72505,6 @@ function request_model(gobj, keep_written) {
72498
72505
  end_load(gobj);
72499
72506
  return -1;
72500
72507
  }
72501
- forget_import_plan(gobj);
72502
72508
  priv.pending = left;
72503
72509
  return 0;
72504
72510
  }
@@ -73658,6 +73664,7 @@ function close_dialog(gobj) {
73658
73664
  if (!priv.dialog) return;
73659
73665
  let dialog = priv.dialog;
73660
73666
  priv.dialog = null;
73667
+ priv.export_view = null;
73661
73668
  try {
73662
73669
  dialog.close();
73663
73670
  } catch (e) {
@@ -73676,6 +73683,7 @@ function open_dialog(gobj, $content, title, logical, title_prefix) {
73676
73683
  t,
73677
73684
  on_close: () => {
73678
73685
  priv.dialog = null;
73686
+ priv.export_view = null;
73679
73687
  }
73680
73688
  });
73681
73689
  refresh_language($content, t);
@@ -74404,17 +74412,43 @@ function open_export(gobj, treedb) {
74404
74412
  let $text = $content.querySelector(".SCHEMA_EXPORT_TEXT");
74405
74413
  for (let $tab of $content.querySelectorAll(".SCHEMA_EXPORT_TAB")) $tab.addEventListener("click", (evt) => {
74406
74414
  evt.stopPropagation();
74407
- for (let $other of $content.querySelectorAll(".SCHEMA_EXPORT_TAB")) {
74408
- $other.classList.toggle("selected_state", $other === $tab);
74409
- $other.setAttribute("aria-pressed", $other === $tab ? "true" : "false");
74410
- }
74411
- $text.value = $tab.dataset.pane === "c" ? c_text : json_text;
74415
+ gobj_send_event(gobj, "EV_EXPORT_VIEW", { pane: $tab.dataset.pane }, gobj);
74412
74416
  });
74413
74417
  $content.querySelector(".SCHEMA_EXPORT_COPY").addEventListener("click", (evt) => {
74414
74418
  evt.stopPropagation();
74415
74419
  copy_text(gobj, $text);
74416
74420
  });
74417
74421
  open_dialog(gobj, $content, "export", "SCHEMA_EXPORT_DIALOG", treedb.id);
74422
+ priv.export_view = {
74423
+ $content,
74424
+ texts: {
74425
+ c: c_text,
74426
+ json: json_text
74427
+ }
74428
+ };
74429
+ }
74430
+ /***************************************************************
74431
+ * Show one of the export's two texts, and say which one is shown
74432
+ * (`selected_state` + aria-pressed). Reached only through
74433
+ * EV_EXPORT_VIEW: a click is an action like any other.
74434
+ ***************************************************************/
74435
+ function show_export_view(gobj, pane) {
74436
+ let view = gobj.priv.export_view;
74437
+ if (!view) {
74438
+ log_error(`${gobj_short_name(gobj)}: export view '${pane}' asked with no export open`);
74439
+ return -1;
74440
+ }
74441
+ if (!Object.prototype.hasOwnProperty.call(view.texts, pane)) {
74442
+ log_error(`${gobj_short_name(gobj)}: the export has no view '${pane}'`);
74443
+ return -1;
74444
+ }
74445
+ for (let $tab of view.$content.querySelectorAll(".SCHEMA_EXPORT_TAB")) {
74446
+ let shown = $tab.dataset.pane === pane;
74447
+ $tab.classList.toggle("selected_state", shown);
74448
+ $tab.setAttribute("aria-pressed", shown ? "true" : "false");
74449
+ }
74450
+ view.$content.querySelector(".SCHEMA_EXPORT_TEXT").value = view.texts[pane];
74451
+ return 0;
74418
74452
  }
74419
74453
  /***************************************************************
74420
74454
  * Copy through the clipboard when the page is allowed one, and
@@ -74569,9 +74603,9 @@ function open_import(gobj, treedb) {
74569
74603
  }
74570
74604
  /***************************************************************
74571
74605
  * The plan is a list of writes computed against the model shown:
74572
- * a load replaces the model, so the plan goes with it and the
74573
- * dialog, if it stays up (a load that failed keeps the model),
74574
- * asks for a Preview again.
74606
+ * a load that LANDS replaces the model, so the plan goes with it
74607
+ * (and end_load() closes the dialog built on that model). A load
74608
+ * that fails or never leaves keeps the model, and the plan.
74575
74609
  ***************************************************************/
74576
74610
  function forget_import_plan(gobj) {
74577
74611
  let priv = gobj.priv;
@@ -75138,6 +75172,8 @@ function ac_mt_command_answer(gobj, event, kw, src) {
75138
75172
  priv.order_undo = {};
75139
75173
  priv.records_before = null;
75140
75174
  priv.model_gen++;
75175
+ priv.reload_on_open = false;
75176
+ forget_import_plan(gobj);
75141
75177
  build_model(gobj);
75142
75178
  start_measuring(gobj);
75143
75179
  return end_load(gobj);
@@ -75268,7 +75304,8 @@ function ac_show(gobj, event, kw, src) {
75268
75304
  priv.pending_seg = seg;
75269
75305
  return 0;
75270
75306
  }
75271
- return apply_seg(gobj, seg);
75307
+ apply_seg(gobj, seg);
75308
+ return reload_after_move(gobj);
75272
75309
  }
75273
75310
  /***************************************************************
75274
75311
  * Hidden by the host: drop the overlays. A dialog outlives the
@@ -75428,7 +75465,9 @@ function confirm_then(gobj, message, detail, kw) {
75428
75465
  kw = Object.assign({ model_gen: gobj.priv.model_gen }, kw);
75429
75466
  yui_shell_confirm_danger(yui_shell_of(gobj), message, {
75430
75467
  t,
75431
- detail
75468
+ detail,
75469
+ confirm_label: "delete",
75470
+ cancel_label: "cancel"
75432
75471
  }).then((yes) => {
75433
75472
  if (!yes || gobj_is_destroying(gobj)) return;
75434
75473
  gobj_send_event(gobj, "EV_CONFIRMED", kw, gobj);
@@ -75705,6 +75744,15 @@ function ac_export(gobj, event, kw, src) {
75705
75744
  open_export(gobj, treedb);
75706
75745
  return 0;
75707
75746
  }
75747
+ /***************************************************************
75748
+ * The export dialog's C / JSON switch. Its texts were composed
75749
+ * when it opened, so it reads nothing of the model and is
75750
+ * answered in every state: the dialog stays up through a load
75751
+ * that fails and through a move sent by the host.
75752
+ ***************************************************************/
75753
+ function ac_export_view(gobj, event, kw, src) {
75754
+ return show_export_view(gobj, kw && kw.pane);
75755
+ }
75708
75756
  function ac_import(gobj, event, kw, src) {
75709
75757
  let treedb = current_treedb(gobj);
75710
75758
  if (refuse_if_readonly(gobj, event)) return -1;
@@ -75888,6 +75936,11 @@ function create_gclass$1(gclass_name) {
75888
75936
  "EV_DRAFTS",
75889
75937
  ac_drafts,
75890
75938
  null
75939
+ ],
75940
+ [
75941
+ "EV_EXPORT_VIEW",
75942
+ ac_export_view,
75943
+ null
75891
75944
  ]
75892
75945
  ];
75893
75946
  const TREEDB_WIDE = [
@@ -76095,6 +76148,7 @@ function create_gclass$1(gclass_name) {
76095
76148
  ["EV_UNDO_ORDER", 0],
76096
76149
  ["EV_VALIDATE", 0],
76097
76150
  ["EV_EXPORT", 0],
76151
+ ["EV_EXPORT_VIEW", 0],
76098
76152
  ["EV_IMPORT", 0],
76099
76153
  ["EV_PREVIEW_IMPORT", 0],
76100
76154
  ["EV_APPLY_IMPORT", 0],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yuneta/gobj-ui",
3
- "version": "7.25.10",
3
+ "version": "7.25.12",
4
4
  "type": "module",
5
5
  "main": "dist/gobj-ui.cjs.js",
6
6
  "module": "dist/gobj-ui.es.js",
@@ -249,6 +249,9 @@ let PRIVATE_DATA = {
249
249
  import_plan: null,
250
250
  import_pane: null,
251
251
  import_run: null,
252
+
253
+ /* The export dialog while it is up: its content and its two texts */
254
+ export_view: null,
252
255
  };
253
256
 
254
257
  let __gclass__ = null;
@@ -494,7 +497,12 @@ function built_on_a_replaced_model(gobj, event, kw)
494
497
  * export) -- is refused and said: it would be computed on what
495
498
  * the store may no longer hold, and the load that lands closes a
496
499
  * dialog built on the old model anyway (end_load()). A move is
497
- * not refused (reload_after_move()): it goes, and reads there.
500
+ * not refused (reload_after_move()): it goes, and reads there --
501
+ * the editor's own moves and the host's alike (ac_show(): a url,
502
+ * the browser's Back).
503
+ *
504
+ * Paid by ANY load that lands, whoever asked it
505
+ * (ac_mt_command_answer()): the store has just been read whole.
498
506
  *
499
507
  * Not on a timer, and not at once: a load refused on a deadline
500
508
  * asked again at once is refused the same way, and one asked on a
@@ -558,9 +566,10 @@ function reload_after_move(gobj)
558
566
  * left open is answered in ST_LOADING instead (ac_wait_for_the_load),
559
567
  * and a load that LANDS closes it (end_load()): it was built on the
560
568
  * model the load replaced, and its Save sends every field it shows.
561
- * An import plan is forgotten here for the same reason: it is a list
562
- * of writes computed against the model going away -- once the load
563
- * has LEFT (one that could not be sent replaces nothing).
569
+ * An import plan is forgotten for the same reason, and at the same
570
+ * moment: when the load LANDS and the model it was computed on goes
571
+ * (ac_mt_command_answer()). A load that cannot leave, or that fails,
572
+ * replaces nothing, and the plan stays with the dialog showing it.
564
573
  *
565
574
  * `keep_written`: a load nobody ASKED for -- the reconnect, a write
566
575
  * that turned out done, the reload a refused load owed -- keeps what
@@ -626,9 +635,6 @@ function request_model(gobj, keep_written)
626
635
  end_load(gobj);
627
636
  return -1;
628
637
  }
629
- /* Only a load that LEFT replaces the model the plan was computed
630
- * on; one that could not be sent keeps it, and the plan with it. */
631
- forget_import_plan(gobj);
632
638
  priv.pending = left;
633
639
  return 0;
634
640
  }
@@ -1739,6 +1745,7 @@ function close_dialog(gobj)
1739
1745
  }
1740
1746
  let dialog = priv.dialog;
1741
1747
  priv.dialog = null;
1748
+ priv.export_view = null;
1742
1749
  try {
1743
1750
  dialog.close();
1744
1751
  } catch(e) {
@@ -1760,6 +1767,7 @@ function open_dialog(gobj, $content, title, logical, title_prefix)
1760
1767
  t: t,
1761
1768
  on_close: () => {
1762
1769
  priv.dialog = null;
1770
+ priv.export_view = null;
1763
1771
  }
1764
1772
  });
1765
1773
  refresh_language($content, t);
@@ -2279,11 +2287,7 @@ function open_export(gobj, treedb)
2279
2287
  for(let $tab of $content.querySelectorAll(".SCHEMA_EXPORT_TAB")) {
2280
2288
  $tab.addEventListener("click", (evt) => {
2281
2289
  evt.stopPropagation();
2282
- for(let $other of $content.querySelectorAll(".SCHEMA_EXPORT_TAB")) {
2283
- $other.classList.toggle("selected_state", $other === $tab);
2284
- $other.setAttribute("aria-pressed", ($other === $tab)? "true": "false");
2285
- }
2286
- $text.value = ($tab.dataset.pane === "c") ? c_text : json_text;
2290
+ gobj_send_event(gobj, "EV_EXPORT_VIEW", {pane: $tab.dataset.pane}, gobj);
2287
2291
  });
2288
2292
  }
2289
2293
  $content.querySelector(".SCHEMA_EXPORT_COPY").addEventListener("click", (evt) => {
@@ -2292,6 +2296,35 @@ function open_export(gobj, treedb)
2292
2296
  });
2293
2297
 
2294
2298
  open_dialog(gobj, $content, "export", "SCHEMA_EXPORT_DIALOG", treedb.id);
2299
+ /* After open_dialog(): it closes the dialog before, and that forgets
2300
+ * the view of the one it closes. */
2301
+ priv.export_view = {$content: $content, texts: {c: c_text, json: json_text}};
2302
+ }
2303
+
2304
+ /***************************************************************
2305
+ * Show one of the export's two texts, and say which one is shown
2306
+ * (`selected_state` + aria-pressed). Reached only through
2307
+ * EV_EXPORT_VIEW: a click is an action like any other.
2308
+ ***************************************************************/
2309
+ function show_export_view(gobj, pane)
2310
+ {
2311
+ let view = gobj.priv.export_view;
2312
+
2313
+ if(!view) {
2314
+ log_error(`${gobj_short_name(gobj)}: export view '${pane}' asked with no export open`);
2315
+ return -1;
2316
+ }
2317
+ if(!Object.prototype.hasOwnProperty.call(view.texts, pane)) {
2318
+ log_error(`${gobj_short_name(gobj)}: the export has no view '${pane}'`);
2319
+ return -1;
2320
+ }
2321
+ for(let $tab of view.$content.querySelectorAll(".SCHEMA_EXPORT_TAB")) {
2322
+ let shown = ($tab.dataset.pane === pane);
2323
+ $tab.classList.toggle("selected_state", shown);
2324
+ $tab.setAttribute("aria-pressed", shown ? "true" : "false");
2325
+ }
2326
+ view.$content.querySelector(".SCHEMA_EXPORT_TEXT").value = view.texts[pane];
2327
+ return 0;
2295
2328
  }
2296
2329
 
2297
2330
  /***************************************************************
@@ -2384,9 +2417,9 @@ function open_import(gobj, treedb)
2384
2417
 
2385
2418
  /***************************************************************
2386
2419
  * The plan is a list of writes computed against the model shown:
2387
- * a load replaces the model, so the plan goes with it and the
2388
- * dialog, if it stays up (a load that failed keeps the model),
2389
- * asks for a Preview again.
2420
+ * a load that LANDS replaces the model, so the plan goes with it
2421
+ * (and end_load() closes the dialog built on that model). A load
2422
+ * that fails or never leaves keeps the model, and the plan.
2390
2423
  ***************************************************************/
2391
2424
  function forget_import_plan(gobj)
2392
2425
  {
@@ -3103,6 +3136,17 @@ function ac_mt_command_answer(gobj, event, kw, src)
3103
3136
  priv.order_undo = {};
3104
3137
  priv.records_before = null;
3105
3138
  priv.model_gen++;
3139
+ /* The store was read whole: whatever reload was owed is paid,
3140
+ * whoever asked for this one -- the operator's Refresh, a late
3141
+ * write, the reconnect. Left owed, the next edit was refused
3142
+ * and read the store AGAIN (sixth independent review). */
3143
+ priv.reload_on_open = false;
3144
+ /* The plan was a list of writes computed on the model this load
3145
+ * replaces. Forgotten HERE, where the model goes, and not when
3146
+ * the requests leave: a load that fails replaces nothing, and
3147
+ * forgetting early left its dialog up with no plan and Import
3148
+ * disabled (sixth independent review). */
3149
+ forget_import_plan(gobj);
3106
3150
  build_model(gobj);
3107
3151
  /* Same reason as order_undo above: this is the LOAD, and the
3108
3152
  * baseline is what a later "the version did not move" is measured
@@ -3297,7 +3341,12 @@ function ac_show(gobj, event, kw, src)
3297
3341
  priv.pending_seg = seg;
3298
3342
  return 0; /* applied when the write lands (end_writes) or the load (end_load) */
3299
3343
  }
3300
- return apply_seg(gobj, seg);
3344
+ /* A move like the editor's own (ac_back() and the rest): it goes,
3345
+ * and pays the reload a refused load owes there. Without it a url
3346
+ * or the browser's Back left it owed on a screen that asked for
3347
+ * nothing (sixth independent review). */
3348
+ apply_seg(gobj, seg);
3349
+ return reload_after_move(gobj);
3301
3350
  }
3302
3351
 
3303
3352
  /***************************************************************
@@ -3504,9 +3553,15 @@ function ac_show_orphans(gobj, event, kw, src)
3504
3553
  function confirm_then(gobj, message, detail, kw)
3505
3554
  {
3506
3555
  kw = Object.assign({model_gen: gobj.priv.model_gen}, kw);
3556
+ /* The answers are i18n KEYS: shell_modals names each button by its
3557
+ * label, and its defaults ("Delete", "Cancel") are not lower-case, so
3558
+ * no validated locale holds them -- they read English in every
3559
+ * language, text, title and aria-label alike. */
3507
3560
  yui_shell_confirm_danger(yui_shell_of(gobj), message, {
3508
- t: t,
3509
- detail: detail
3561
+ t: t,
3562
+ detail: detail,
3563
+ confirm_label: "delete",
3564
+ cancel_label: "cancel"
3510
3565
  }).then((yes) => {
3511
3566
  if(!yes || gobj_is_destroying(gobj)) {
3512
3567
  return;
@@ -3897,6 +3952,17 @@ function ac_export(gobj, event, kw, src)
3897
3952
  return 0;
3898
3953
  }
3899
3954
 
3955
+ /***************************************************************
3956
+ * The export dialog's C / JSON switch. Its texts were composed
3957
+ * when it opened, so it reads nothing of the model and is
3958
+ * answered in every state: the dialog stays up through a load
3959
+ * that fails and through a move sent by the host.
3960
+ ***************************************************************/
3961
+ function ac_export_view(gobj, event, kw, src)
3962
+ {
3963
+ return show_export_view(gobj, kw && kw.pane);
3964
+ }
3965
+
3900
3966
  function ac_import(gobj, event, kw, src)
3901
3967
  {
3902
3968
  let treedb = current_treedb(gobj);
@@ -3988,8 +4054,9 @@ function run_import(gobj)
3988
4054
  let priv = gobj.priv;
3989
4055
  let plan = priv.import_plan;
3990
4056
 
3991
- /* Not an ERROR: a load that left forgets the plan (request_model()),
3992
- * and the confirmation of it may still be up. The operator's Yes
4057
+ /* Not an ERROR: a load that lands forgets the plan
4058
+ * (ac_mt_command_answer()), and the confirmation of it may still be
4059
+ * up. The operator's Yes
3993
4060
  * is answered, not lost in the log. */
3994
4061
  if(!plan) {
3995
4062
  log_warning(`${gobj_short_name(gobj)}: the import plan is gone`);
@@ -4093,7 +4160,9 @@ function create_gclass(gclass_name)
4093
4160
  ["EV_TRANSPORT_STATE", ac_transport_state, null],
4094
4161
  ["EV_LANGUAGE_CHANGED", ac_language_changed, null],
4095
4162
  ["EV_REFRESH", ac_refresh, null],
4096
- ["EV_DRAFTS", ac_drafts, null]
4163
+ ["EV_DRAFTS", ac_drafts, null],
4164
+ /* The export dialog outlives the screen it was opened from. */
4165
+ ["EV_EXPORT_VIEW", ac_export_view, null]
4097
4166
  ];
4098
4167
 
4099
4168
  /* What a treedb is open for: reading it whole, and writing it
@@ -4209,6 +4278,7 @@ function create_gclass(gclass_name)
4209
4278
  ["EV_UNDO_ORDER", 0],
4210
4279
  ["EV_VALIDATE", 0],
4211
4280
  ["EV_EXPORT", 0],
4281
+ ["EV_EXPORT_VIEW", 0],
4212
4282
  ["EV_IMPORT", 0],
4213
4283
  ["EV_PREVIEW_IMPORT", 0],
4214
4284
  ["EV_APPLY_IMPORT", 0],
@@ -25,6 +25,14 @@
25
25
  * silence, and a toolbar gated on the state instead of on the
26
26
  * treedb being there.
27
27
  *
28
+ * The sixth independent review (same day) found where the owed
29
+ * reload still lingered: a load that LANDED by another road (the
30
+ * operator's Refresh, a late write) left it owed, so the next
31
+ * edit was refused and read the store again; a move sent by the
32
+ * host did not run it; a load that failed forgot the import plan
33
+ * of a dialog it left up; and the export's two views switched in
34
+ * a DOM handler, out of the machine.
35
+ *
28
36
  * Driven through the FSM on a document double, with a fake
29
37
  * transport whose state is the one the library reads.
30
38
  *
@@ -40,6 +48,7 @@ install_dom_double();
40
48
  * clicked like the operator would). */
41
49
  const shown = [];
42
50
  const modals = [];
51
+ const confirms = [];
43
52
  vi.mock("./shell_modals.js", () => ({
44
53
  yui_shell_show_error: (shell, message) => {
45
54
  shown.push(message);
@@ -53,7 +62,10 @@ vi.mock("./shell_modals.js", () => ({
53
62
  modal.closed = true;
54
63
  }};
55
64
  },
56
- yui_shell_confirm_danger: () => new Promise(() => {}),
65
+ yui_shell_confirm_danger: (shell, message, opts) => {
66
+ confirms.push({message, opts});
67
+ return new Promise(() => {});
68
+ },
57
69
  }));
58
70
 
59
71
  const {
@@ -64,6 +76,7 @@ const {
64
76
  gobj_start_up, gobj_create_yuno, gobj_create, gobj_create_service,
65
77
  gobj_start, gobj_send_event, gobj_read_attr,
66
78
  gobj_change_state, gobj_current_state,
79
+ gobj_set_gobj_trace,
67
80
  set_log_callback,
68
81
  } = await import("@yuneta/gobj-js");
69
82
  const {register_c_yui_schema_editor} = await import("./c_yui_schema_editor.js");
@@ -167,6 +180,7 @@ beforeEach(() => {
167
180
  commands.length = 0;
168
181
  shown.length = 0;
169
182
  modals.length = 0;
183
+ confirms.length = 0;
170
184
  published.length = 0;
171
185
  });
172
186
 
@@ -570,11 +584,12 @@ describe("a dialog built on the model a reload replaced (fourth review)", () =>
570
584
  expect(editor.priv.import_plan.writes.length).toBeGreaterThan(0);
571
585
 
572
586
  gobj_send_event(editor, "EV_REFRESH", {}, host);
573
- expect(editor.priv.import_plan).toBe(null);
574
587
  answer_the_load_with(editor, remote, records_with((r) => {
575
588
  r.cols.push({id: "db.users.email", value: "email", order: 2, type: "integer",
576
589
  topics: ["topics^db.users^cols"]});
577
590
  }));
591
+ /* Forgotten when the load LANDS, not when it leaves (sixth review). */
592
+ expect(editor.priv.import_plan).toBe(null);
578
593
  expect(dialog.closed).toBe(true);
579
594
 
580
595
  dialog.$content.querySelector(".SCHEMA_IMPORT_RUN").disabled = false;
@@ -1015,3 +1030,194 @@ describe("a confirmation answered where no treedb is open (fifth review)", () =>
1015
1030
  expect(shown).toEqual([STALE, STALE]);
1016
1031
  });
1017
1032
  });
1033
+
1034
+ describe("the answers of the editor's confirmations are i18n keys (fifth review, live)", () => {
1035
+
1036
+ /* shell_modals names each answer by its label; its defaults ("Delete",
1037
+ * "Cancel") are not lower-case, so no validated locale holds them and
1038
+ * the buttons -- and now their title and aria-label -- read English in
1039
+ * every language (seen on the deployed agent console, in Spanish). */
1040
+ test("a column delete asks with `delete` / `cancel`", () => {
1041
+ const {editor, remote, host} = build("k1", "db/users");
1042
+ gobj_send_event(editor, "EV_DELETE_COLUMN", {col: "id"}, host);
1043
+ expect(confirms.length).toBe(1);
1044
+ expect([confirms[0].opts.confirm_label, confirms[0].opts.cancel_label])
1045
+ .toEqual(["delete", "cancel"]);
1046
+ });
1047
+ });
1048
+
1049
+ describe("the owed reload, settled wherever a load lands (sixth review)", () => {
1050
+
1051
+ function refused_load(name, subpath)
1052
+ {
1053
+ const built = build(name, subpath);
1054
+ gobj_send_event(built.editor, "EV_REFRESH", {}, built.host);
1055
+ const asked = take("nodes");
1056
+ answer(built.editor, built.remote, asked[0], -1, null, "deadline");
1057
+ answer(built.editor, built.remote, asked[1], 0, RECORDS[asked[1].kw.topic_name]);
1058
+ answer(built.editor, built.remote, asked[2], 0, RECORDS[asked[2].kw.topic_name]);
1059
+ expect(built.editor.priv.reload_on_open).toBe(true);
1060
+ expect(shown).toEqual([KEPT]);
1061
+ return built;
1062
+ }
1063
+
1064
+ test("the operator's Refresh that lands pays it: the next edit opens its form", () => {
1065
+ const {editor, remote, host} = refused_load("q1", "db/users");
1066
+ gobj_send_event(editor, "EV_REFRESH", {}, host);
1067
+ expect(answer_the_load(editor, remote)).toBe(3);
1068
+ expect(gobj_current_state(editor)).toBe("ST_COLUMNS");
1069
+ expect(editor.priv.reload_on_open).toBe(false);
1070
+
1071
+ const before = modals.length;
1072
+ gobj_send_event(editor, "EV_EDIT_COLUMN", {col: "id"}, host);
1073
+ expect(modals.length).toBe(before + 1);
1074
+ expect(take("nodes")).toEqual([]);
1075
+ expect(shown).toEqual([KEPT]);
1076
+ expect(errors()).toEqual([]);
1077
+ expect(not_defined()).toEqual([]);
1078
+ });
1079
+
1080
+ test("the reload of a late write that lands pays it too", () => {
1081
+ const {editor, remote, host} = build("q2", "db/users");
1082
+ const w = start_a_write(editor, host, "name");
1083
+ drop(editor, remote, host);
1084
+ reconnect(editor, remote, host);
1085
+ expect(answer_the_load(editor, remote)).toBe(3);
1086
+
1087
+ gobj_send_event(editor, "EV_REFRESH", {}, host);
1088
+ const asked = take("nodes");
1089
+ answer(editor, remote, asked[0], -1, null, "deadline");
1090
+ answer(editor, remote, asked[1], 0, RECORDS[asked[1].kw.topic_name]);
1091
+ answer(editor, remote, asked[2], 0, RECORDS[asked[2].kw.topic_name]);
1092
+ expect(editor.priv.reload_on_open).toBe(true);
1093
+
1094
+ /* The write given up answers DONE: the model is read again. */
1095
+ answer(editor, remote, w, 0, {id: "db.users.name", value: "name", type: "string",
1096
+ order: 2, topics: [{id: "db.users", topic_name: "topics", hook_name: "cols"}]});
1097
+ expect(gobj_current_state(editor)).toBe("ST_LOADING");
1098
+ expect(answer_the_load(editor, remote)).toBe(3);
1099
+ expect(editor.priv.reload_on_open).toBe(false);
1100
+
1101
+ const before = modals.length;
1102
+ gobj_send_event(editor, "EV_EDIT_COLUMN", {col: "id"}, host);
1103
+ expect(modals.length).toBe(before + 1);
1104
+ expect(take("nodes")).toEqual([]);
1105
+ expect(errors()).toEqual([]);
1106
+ });
1107
+
1108
+ test("a move sent by the HOST (a url, the browser's Back) runs it, as the editor's own moves do", () => {
1109
+ const {editor, remote, host} = refused_load("q3", "db/users");
1110
+ gobj_send_event(editor, "EV_SHOW", {subpath: "db"}, host);
1111
+ expect(gobj_current_state(editor)).toBe("ST_LOADING");
1112
+ expect(editor.priv.reload_on_open).toBe(false);
1113
+ expect(shown).toEqual([KEPT]); /* a move is not refused */
1114
+ expect(answer_the_load(editor, remote)).toBe(3);
1115
+ expect(gobj_current_state(editor)).toBe("ST_TOPICS");
1116
+ expect(errors()).toEqual([]);
1117
+ expect(not_defined()).toEqual([]);
1118
+ });
1119
+
1120
+ test("a host EV_SHOW with nothing owed asks for nothing", () => {
1121
+ const {editor, remote, host} = build("q4", "db/users");
1122
+ gobj_send_event(editor, "EV_SHOW", {subpath: "db"}, host);
1123
+ expect(gobj_current_state(editor)).toBe("ST_TOPICS");
1124
+ expect(take("nodes")).toEqual([]);
1125
+ });
1126
+ });
1127
+
1128
+ describe("the import plan outlives a load that fails (sixth review)", () => {
1129
+
1130
+ const IMPORT = JSON.stringify({id: "db", topics: [{id: "users", pkey: "id", cols: [
1131
+ {id: "id", type: "string", flag: ["persistent", "required"]},
1132
+ {id: "email", type: "string"}
1133
+ ]}]});
1134
+
1135
+ test("a load that LEFT and failed replaced nothing: the plan and its Import stay", () => {
1136
+ const {editor, remote, host} = build("m1", "db");
1137
+ gobj_send_event(editor, "EV_IMPORT", {}, host);
1138
+ const dialog = modals[modals.length - 1];
1139
+ gobj_send_event(editor, "EV_PREVIEW_IMPORT", {prune: false, text: IMPORT,
1140
+ model_gen: editor.priv.model_gen}, editor);
1141
+ const plan = editor.priv.import_plan;
1142
+ expect(plan.writes.length).toBeGreaterThan(0);
1143
+
1144
+ gobj_send_event(editor, "EV_REFRESH", {}, host);
1145
+ const asked = take("nodes");
1146
+ answer(editor, remote, asked[0], -1, null, "deadline");
1147
+ answer(editor, remote, asked[1], 0, RECORDS[asked[1].kw.topic_name]);
1148
+ answer(editor, remote, asked[2], 0, RECORDS[asked[2].kw.topic_name]);
1149
+
1150
+ expect(dialog.closed).toBe(false);
1151
+ expect(editor.priv.import_plan).toBe(plan);
1152
+ expect(dialog.$content.querySelector(".SCHEMA_IMPORT_RUN").disabled).toBe(false);
1153
+ expect(dialog.$content.querySelector(".SCHEMA_IMPORT_PLAN").childNodes.length)
1154
+ .toBeGreaterThan(0);
1155
+ expect(errors()).toEqual([]);
1156
+ });
1157
+
1158
+ test("a load that LANDS forgets it, and closes the dialog it was shown in", () => {
1159
+ const {editor, remote, host} = build("m2", "db");
1160
+ gobj_send_event(editor, "EV_IMPORT", {}, host);
1161
+ const dialog = modals[modals.length - 1];
1162
+ gobj_send_event(editor, "EV_PREVIEW_IMPORT", {prune: false, text: IMPORT,
1163
+ model_gen: editor.priv.model_gen}, editor);
1164
+ expect(editor.priv.import_plan).toBeTruthy();
1165
+
1166
+ gobj_send_event(editor, "EV_REFRESH", {}, host);
1167
+ expect(editor.priv.import_plan).toBeTruthy(); /* in the air: nothing replaced yet */
1168
+ expect(answer_the_load(editor, remote)).toBe(3);
1169
+ expect(editor.priv.import_plan).toBe(null);
1170
+ expect(dialog.closed).toBe(true);
1171
+ expect(shown).toEqual([STALE]);
1172
+ expect(errors()).toEqual([]);
1173
+ });
1174
+ });
1175
+
1176
+ describe("the export's two views go through the machine (sixth review)", () => {
1177
+
1178
+ test("a click on a view is an event, and the action switches the text", () => {
1179
+ const {editor, remote, host} = build("y1", "db");
1180
+ gobj_send_event(editor, "EV_EXPORT", {}, host);
1181
+ const $content = modals[modals.length - 1].$content;
1182
+ const $tabs = [...$content.querySelectorAll(".SCHEMA_EXPORT_TAB")];
1183
+ const $text = $content.querySelector(".SCHEMA_EXPORT_TEXT");
1184
+
1185
+ gobj_set_gobj_trace(editor, "machine", true);
1186
+ try {
1187
+ $tabs[1].click();
1188
+ } finally {
1189
+ gobj_set_gobj_trace(editor, "machine", false);
1190
+ }
1191
+ expect(logged.some((l) => /EV_EXPORT_VIEW/.test(l.msg))).toBe(true);
1192
+ expect($text.value.trim().startsWith("{")).toBe(true);
1193
+ expect($tabs.map(($b) => $b.getAttribute("aria-pressed"))).toEqual(["false", "true"]);
1194
+ expect($tabs.map(($b) => $b.classList.contains("selected_state"))).toEqual([false, true]);
1195
+
1196
+ gobj_send_event(editor, "EV_EXPORT_VIEW", {pane: "c"}, editor);
1197
+ expect($text.value.trim().startsWith("{")).toBe(false);
1198
+ expect($tabs.map(($b) => $b.getAttribute("aria-pressed"))).toEqual(["true", "false"]);
1199
+ expect(errors()).toEqual([]);
1200
+ expect(not_defined()).toEqual([]);
1201
+ });
1202
+
1203
+ test("answered while a load is in the air, where the dialog is still up", () => {
1204
+ const {editor, remote, host} = build("y2", "db");
1205
+ gobj_send_event(editor, "EV_EXPORT", {}, host);
1206
+ const $content = modals[modals.length - 1].$content;
1207
+ gobj_send_event(editor, "EV_REFRESH", {}, host);
1208
+ expect(gobj_current_state(editor)).toBe("ST_LOADING");
1209
+ $content.querySelectorAll(".SCHEMA_EXPORT_TAB")[1].click();
1210
+ expect($content.querySelector(".SCHEMA_EXPORT_TEXT").value.trim().startsWith("{")).toBe(true);
1211
+ expect(not_defined()).toEqual([]);
1212
+ expect(answer_the_load(editor, remote)).toBe(3);
1213
+ expect(errors()).toEqual([]);
1214
+ });
1215
+
1216
+ test("a view that is not one, or no export open, is an ERROR, not a guess", () => {
1217
+ const {editor, remote, host} = build("y3", "db");
1218
+ gobj_send_event(editor, "EV_EXPORT_VIEW", {pane: "c"}, editor);
1219
+ gobj_send_event(editor, "EV_EXPORT", {}, host);
1220
+ gobj_send_event(editor, "EV_EXPORT_VIEW", {pane: "yaml"}, editor);
1221
+ expect(errors().length).toBe(2);
1222
+ });
1223
+ });