@yuneta/gobj-ui 7.26.22 → 7.26.24

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.
@@ -73618,6 +73618,11 @@ function plan_import(treedb, incoming, options) {
73618
73618
  conflicts
73619
73619
  };
73620
73620
  }
73621
+ if (topic_name_breaks_the_rule(treedb_id)) conflicts.push({
73622
+ code: "treedb name breaks the name rule",
73623
+ topic: "",
73624
+ col: ""
73625
+ });
73621
73626
  for (let in_topic of incoming.topics) {
73622
73627
  let name = (in_topic || {}).id;
73623
73628
  if (name && topic_name_breaks_the_rule(name)) conflicts.push({
@@ -73625,11 +73630,40 @@ function plan_import(treedb, incoming, options) {
73625
73630
  topic: name,
73626
73631
  col: ""
73627
73632
  });
73628
- for (let in_col of incoming_cols(in_topic || {})) if (col_name_breaks_the_rule(in_col.name)) conflicts.push({
73629
- code: "column name breaks the name rule",
73630
- topic: name || "",
73631
- col: in_col.name
73632
- });
73633
+ let cols = (in_topic || {}).cols;
73634
+ if (Array.isArray(cols)) {
73635
+ for (let c of cols) if (!c || !c.id) conflicts.push({
73636
+ code: "column has no name",
73637
+ topic: name || "",
73638
+ col: ""
73639
+ });
73640
+ }
73641
+ for (let in_col of incoming_cols(in_topic || {})) {
73642
+ if (col_name_breaks_the_rule(in_col.name)) conflicts.push({
73643
+ code: "column name breaks the name rule",
73644
+ topic: name || "",
73645
+ col: in_col.name
73646
+ });
73647
+ let inner_id = (in_col.col || {}).id;
73648
+ if (typeof inner_id === "string" && inner_id !== in_col.name && col_name_breaks_the_rule(inner_id)) conflicts.push({
73649
+ code: "column name breaks the name rule",
73650
+ topic: name || "",
73651
+ col: inner_id
73652
+ });
73653
+ let hook = (in_col.col || {}).hook;
73654
+ if (hook && typeof hook === "object" && !Array.isArray(hook)) for (let [child_topic, fkey_col] of Object.entries(hook)) {
73655
+ if (topic_name_breaks_the_rule(child_topic)) conflicts.push({
73656
+ code: "topic name breaks the name rule",
73657
+ topic: child_topic,
73658
+ col: ""
73659
+ });
73660
+ if (col_name_breaks_the_rule(fkey_col)) conflicts.push({
73661
+ code: "column name breaks the name rule",
73662
+ topic: child_topic,
73663
+ col: String(fkey_col)
73664
+ });
73665
+ }
73666
+ }
73633
73667
  }
73634
73668
  if (conflicts.length > 0) return {
73635
73669
  writes,
@@ -74436,12 +74470,22 @@ function mt_destroy$1(gobj) {
74436
74470
  * name field is marked aria-invalid for a reader.
74437
74471
  ************************************************************/
74438
74472
  function refuse_bad_name(gobj, $message) {
74439
- let priv = gobj.priv;
74440
- let $value = priv.dialog_content ? priv.dialog_content.querySelector("[data-name=\"value\"]") : null;
74441
- if ($value) $value.setAttribute("aria-invalid", "true");
74473
+ mark_name_field(gobj, true);
74442
74474
  yui_shell_show_error(yui_shell_of(gobj), $message, { t: i18next.t });
74443
74475
  return -1;
74444
74476
  }
74477
+ /************************************************************
74478
+ * The name field says whether the LAST save refused its name:
74479
+ * set by every refusal of the name (empty, a rule, a duplicate),
74480
+ * cleared by a save that gets past them.
74481
+ ************************************************************/
74482
+ function mark_name_field(gobj, invalid) {
74483
+ let priv = gobj.priv;
74484
+ let $value = priv.dialog_content ? priv.dialog_content.querySelector("[data-name=\"value\"]") : null;
74485
+ if (!$value) return;
74486
+ if (invalid) $value.setAttribute("aria-invalid", "true");
74487
+ else $value.removeAttribute("aria-invalid");
74488
+ }
74445
74489
  /***************************************************************
74446
74490
  * The frame: a toolbar that never changes place, a notice line
74447
74491
  * for what the whole view has to say, and one body the screens
@@ -76925,13 +76969,19 @@ function render_import_plan(gobj, plan) {
76925
76969
  "div",
76926
76970
  { class: "SCHEMA_IMPORT_CONFLICTS notification is-danger is-light p-3" },
76927
76971
  plan.conflicts.map((conflict) => {
76972
+ let where = conflict.col ? `${conflict.topic}.${conflict.col}` : conflict.topic;
76928
76973
  return [
76929
76974
  "p",
76930
- {
76931
- class: "SCHEMA_IMPORT_CONFLICT is-size-7",
76932
- i18n: conflict.code
76933
- },
76934
- (0, i18next.t)(conflict.code)
76975
+ { class: "SCHEMA_IMPORT_CONFLICT is-size-7" },
76976
+ [[
76977
+ "span",
76978
+ { i18n: conflict.code },
76979
+ (0, i18next.t)(conflict.code)
76980
+ ], where ? [
76981
+ "code",
76982
+ { class: "SCHEMA_IMPORT_CONFLICT_NAME ml-2" },
76983
+ text_node(where)
76984
+ ] : null].filter(Boolean)
76935
76985
  ];
76936
76986
  })
76937
76987
  ]));
@@ -77954,6 +78004,7 @@ function ac_save_column(gobj, event, kw, src) {
77954
78004
  }
77955
78005
  let values = kw.values || {};
77956
78006
  if (kw.creating && (0, _yuneta_gobj_js.empty_string)(String(values.value || "").trim())) {
78007
+ mark_name_field(gobj, true);
77957
78008
  yui_shell_show_error(yui_shell_of(gobj), "a column needs a name", { t: i18next.t });
77958
78009
  return -1;
77959
78010
  }
@@ -77963,9 +78014,11 @@ function ac_save_column(gobj, event, kw, src) {
77963
78014
  (0, i18next.t)("invalid column name")
77964
78015
  ]));
77965
78016
  if (kw.creating && find_col(priv.model, priv.treedb_id, kw.topic, String(values.value).trim())) {
78017
+ mark_name_field(gobj, true);
77966
78018
  yui_shell_show_error(yui_shell_of(gobj), "that column is already there", { t: i18next.t });
77967
78019
  return -1;
77968
78020
  }
78021
+ mark_name_field(gobj, false);
77969
78022
  close_dialog(gobj);
77970
78023
  return queue_writes(gobj, [{
77971
78024
  op: kw.creating ? "create" : "update",
@@ -78213,6 +78266,7 @@ function ac_save_topic(gobj, event, kw, src) {
78213
78266
  if (kw.creating) {
78214
78267
  let name = String(values.value || "").trim();
78215
78268
  if ((0, _yuneta_gobj_js.empty_string)(name)) {
78269
+ mark_name_field(gobj, true);
78216
78270
  yui_shell_show_error(yui_shell_of(gobj), "a topic needs a name", { t: i18next.t });
78217
78271
  return -1;
78218
78272
  }
@@ -78222,9 +78276,11 @@ function ac_save_topic(gobj, event, kw, src) {
78222
78276
  (0, i18next.t)("invalid topic name")
78223
78277
  ]));
78224
78278
  if (find_topic(priv.model, priv.treedb_id, name)) {
78279
+ mark_name_field(gobj, true);
78225
78280
  yui_shell_show_error(yui_shell_of(gobj), "that topic is already there", { t: i18next.t });
78226
78281
  return -1;
78227
78282
  }
78283
+ mark_name_field(gobj, false);
78228
78284
  }
78229
78285
  close_dialog(gobj);
78230
78286
  return queue_writes(gobj, [{
@@ -73613,6 +73613,11 @@ function plan_import(treedb, incoming, options) {
73613
73613
  conflicts
73614
73614
  };
73615
73615
  }
73616
+ if (topic_name_breaks_the_rule(treedb_id)) conflicts.push({
73617
+ code: "treedb name breaks the name rule",
73618
+ topic: "",
73619
+ col: ""
73620
+ });
73616
73621
  for (let in_topic of incoming.topics) {
73617
73622
  let name = (in_topic || {}).id;
73618
73623
  if (name && topic_name_breaks_the_rule(name)) conflicts.push({
@@ -73620,11 +73625,40 @@ function plan_import(treedb, incoming, options) {
73620
73625
  topic: name,
73621
73626
  col: ""
73622
73627
  });
73623
- for (let in_col of incoming_cols(in_topic || {})) if (col_name_breaks_the_rule(in_col.name)) conflicts.push({
73624
- code: "column name breaks the name rule",
73625
- topic: name || "",
73626
- col: in_col.name
73627
- });
73628
+ let cols = (in_topic || {}).cols;
73629
+ if (Array.isArray(cols)) {
73630
+ for (let c of cols) if (!c || !c.id) conflicts.push({
73631
+ code: "column has no name",
73632
+ topic: name || "",
73633
+ col: ""
73634
+ });
73635
+ }
73636
+ for (let in_col of incoming_cols(in_topic || {})) {
73637
+ if (col_name_breaks_the_rule(in_col.name)) conflicts.push({
73638
+ code: "column name breaks the name rule",
73639
+ topic: name || "",
73640
+ col: in_col.name
73641
+ });
73642
+ let inner_id = (in_col.col || {}).id;
73643
+ if (typeof inner_id === "string" && inner_id !== in_col.name && col_name_breaks_the_rule(inner_id)) conflicts.push({
73644
+ code: "column name breaks the name rule",
73645
+ topic: name || "",
73646
+ col: inner_id
73647
+ });
73648
+ let hook = (in_col.col || {}).hook;
73649
+ if (hook && typeof hook === "object" && !Array.isArray(hook)) for (let [child_topic, fkey_col] of Object.entries(hook)) {
73650
+ if (topic_name_breaks_the_rule(child_topic)) conflicts.push({
73651
+ code: "topic name breaks the name rule",
73652
+ topic: child_topic,
73653
+ col: ""
73654
+ });
73655
+ if (col_name_breaks_the_rule(fkey_col)) conflicts.push({
73656
+ code: "column name breaks the name rule",
73657
+ topic: child_topic,
73658
+ col: String(fkey_col)
73659
+ });
73660
+ }
73661
+ }
73628
73662
  }
73629
73663
  if (conflicts.length > 0) return {
73630
73664
  writes,
@@ -74431,12 +74465,22 @@ function mt_destroy$1(gobj) {
74431
74465
  * name field is marked aria-invalid for a reader.
74432
74466
  ************************************************************/
74433
74467
  function refuse_bad_name(gobj, $message) {
74434
- let priv = gobj.priv;
74435
- let $value = priv.dialog_content ? priv.dialog_content.querySelector("[data-name=\"value\"]") : null;
74436
- if ($value) $value.setAttribute("aria-invalid", "true");
74468
+ mark_name_field(gobj, true);
74437
74469
  yui_shell_show_error(yui_shell_of(gobj), $message, { t });
74438
74470
  return -1;
74439
74471
  }
74472
+ /************************************************************
74473
+ * The name field says whether the LAST save refused its name:
74474
+ * set by every refusal of the name (empty, a rule, a duplicate),
74475
+ * cleared by a save that gets past them.
74476
+ ************************************************************/
74477
+ function mark_name_field(gobj, invalid) {
74478
+ let priv = gobj.priv;
74479
+ let $value = priv.dialog_content ? priv.dialog_content.querySelector("[data-name=\"value\"]") : null;
74480
+ if (!$value) return;
74481
+ if (invalid) $value.setAttribute("aria-invalid", "true");
74482
+ else $value.removeAttribute("aria-invalid");
74483
+ }
74440
74484
  /***************************************************************
74441
74485
  * The frame: a toolbar that never changes place, a notice line
74442
74486
  * for what the whole view has to say, and one body the screens
@@ -76920,13 +76964,19 @@ function render_import_plan(gobj, plan) {
76920
76964
  "div",
76921
76965
  { class: "SCHEMA_IMPORT_CONFLICTS notification is-danger is-light p-3" },
76922
76966
  plan.conflicts.map((conflict) => {
76967
+ let where = conflict.col ? `${conflict.topic}.${conflict.col}` : conflict.topic;
76923
76968
  return [
76924
76969
  "p",
76925
- {
76926
- class: "SCHEMA_IMPORT_CONFLICT is-size-7",
76927
- i18n: conflict.code
76928
- },
76929
- t(conflict.code)
76970
+ { class: "SCHEMA_IMPORT_CONFLICT is-size-7" },
76971
+ [[
76972
+ "span",
76973
+ { i18n: conflict.code },
76974
+ t(conflict.code)
76975
+ ], where ? [
76976
+ "code",
76977
+ { class: "SCHEMA_IMPORT_CONFLICT_NAME ml-2" },
76978
+ text_node(where)
76979
+ ] : null].filter(Boolean)
76930
76980
  ];
76931
76981
  })
76932
76982
  ]));
@@ -77949,6 +77999,7 @@ function ac_save_column(gobj, event, kw, src) {
77949
77999
  }
77950
78000
  let values = kw.values || {};
77951
78001
  if (kw.creating && empty_string(String(values.value || "").trim())) {
78002
+ mark_name_field(gobj, true);
77952
78003
  yui_shell_show_error(yui_shell_of(gobj), "a column needs a name", { t });
77953
78004
  return -1;
77954
78005
  }
@@ -77958,9 +78009,11 @@ function ac_save_column(gobj, event, kw, src) {
77958
78009
  t("invalid column name")
77959
78010
  ]));
77960
78011
  if (kw.creating && find_col(priv.model, priv.treedb_id, kw.topic, String(values.value).trim())) {
78012
+ mark_name_field(gobj, true);
77961
78013
  yui_shell_show_error(yui_shell_of(gobj), "that column is already there", { t });
77962
78014
  return -1;
77963
78015
  }
78016
+ mark_name_field(gobj, false);
77964
78017
  close_dialog(gobj);
77965
78018
  return queue_writes(gobj, [{
77966
78019
  op: kw.creating ? "create" : "update",
@@ -78208,6 +78261,7 @@ function ac_save_topic(gobj, event, kw, src) {
78208
78261
  if (kw.creating) {
78209
78262
  let name = String(values.value || "").trim();
78210
78263
  if (empty_string(name)) {
78264
+ mark_name_field(gobj, true);
78211
78265
  yui_shell_show_error(yui_shell_of(gobj), "a topic needs a name", { t });
78212
78266
  return -1;
78213
78267
  }
@@ -78217,9 +78271,11 @@ function ac_save_topic(gobj, event, kw, src) {
78217
78271
  t("invalid topic name")
78218
78272
  ]));
78219
78273
  if (find_topic(priv.model, priv.treedb_id, name)) {
78274
+ mark_name_field(gobj, true);
78220
78275
  yui_shell_show_error(yui_shell_of(gobj), "that topic is already there", { t });
78221
78276
  return -1;
78222
78277
  }
78278
+ mark_name_field(gobj, false);
78223
78279
  }
78224
78280
  close_dialog(gobj);
78225
78281
  return queue_writes(gobj, [{
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yuneta/gobj-ui",
3
- "version": "7.26.22",
3
+ "version": "7.26.24",
4
4
  "type": "module",
5
5
  "main": "dist/gobj-ui.cjs.js",
6
6
  "module": "dist/gobj-ui.es.js",
@@ -371,15 +371,30 @@ function mt_destroy(gobj)
371
371
  * name field is marked aria-invalid for a reader.
372
372
  ************************************************************/
373
373
  function refuse_bad_name(gobj, $message)
374
+ {
375
+ mark_name_field(gobj, true);
376
+ yui_shell_show_error(yui_shell_of(gobj), $message, {t: t});
377
+ return -1;
378
+ }
379
+
380
+ /************************************************************
381
+ * The name field says whether the LAST save refused its name:
382
+ * set by every refusal of the name (empty, a rule, a duplicate),
383
+ * cleared by a save that gets past them.
384
+ ************************************************************/
385
+ function mark_name_field(gobj, invalid)
374
386
  {
375
387
  let priv = gobj.priv;
376
388
  let $value = priv.dialog_content?
377
389
  priv.dialog_content.querySelector('[data-name="value"]') : null;
378
- if($value) {
390
+ if(!$value) {
391
+ return;
392
+ }
393
+ if(invalid) {
379
394
  $value.setAttribute("aria-invalid", "true");
395
+ } else {
396
+ $value.removeAttribute("aria-invalid");
380
397
  }
381
- yui_shell_show_error(yui_shell_of(gobj), $message, {t: t});
382
- return -1;
383
398
  }
384
399
 
385
400
  /***************************************************************
@@ -2669,8 +2684,15 @@ function render_import_plan(gobj, plan)
2669
2684
  $pane.appendChild(createElement2(
2670
2685
  ["div", {class: "SCHEMA_IMPORT_CONFLICTS notification is-danger is-light p-3"},
2671
2686
  plan.conflicts.map((conflict) => {
2672
- return ["p", {class: "SCHEMA_IMPORT_CONFLICT is-size-7", i18n: conflict.code},
2673
- t(conflict.code)];
2687
+ /* What the conflict is about, said: two bad entries of
2688
+ * one hook map were two identical lines. */
2689
+ let where = conflict.col
2690
+ ? `${conflict.topic}.${conflict.col}`
2691
+ : conflict.topic;
2692
+ return ["p", {class: "SCHEMA_IMPORT_CONFLICT is-size-7"}, [
2693
+ ["span", {i18n: conflict.code}, t(conflict.code)],
2694
+ where ? ["code", {class: "SCHEMA_IMPORT_CONFLICT_NAME ml-2"}, text_node(where)] : null
2695
+ ].filter(Boolean)];
2674
2696
  })]
2675
2697
  ));
2676
2698
  if(priv.import_run) {
@@ -4047,6 +4069,7 @@ function ac_save_column(gobj, event, kw, src)
4047
4069
 
4048
4070
  let values = kw.values || {};
4049
4071
  if(kw.creating && empty_string(String(values.value || "").trim())) {
4072
+ mark_name_field(gobj, true);
4050
4073
  yui_shell_show_error(yui_shell_of(gobj), "a column needs a name", {t: t});
4051
4074
  return -1;
4052
4075
  }
@@ -4055,9 +4078,11 @@ function ac_save_column(gobj, event, kw, src)
4055
4078
  ["span", {i18n: "invalid column name"}, t("invalid column name")]));
4056
4079
  }
4057
4080
  if(kw.creating && find_col(priv.model, priv.treedb_id, kw.topic, String(values.value).trim())) {
4081
+ mark_name_field(gobj, true);
4058
4082
  yui_shell_show_error(yui_shell_of(gobj), "that column is already there", {t: t});
4059
4083
  return -1;
4060
4084
  }
4085
+ mark_name_field(gobj, false);
4061
4086
 
4062
4087
  close_dialog(gobj);
4063
4088
  return queue_writes(gobj, [{
@@ -4358,6 +4383,7 @@ function ac_save_topic(gobj, event, kw, src)
4358
4383
  if(kw.creating) {
4359
4384
  let name = String(values.value || "").trim();
4360
4385
  if(empty_string(name)) {
4386
+ mark_name_field(gobj, true);
4361
4387
  yui_shell_show_error(yui_shell_of(gobj), "a topic needs a name", {t: t});
4362
4388
  return -1;
4363
4389
  }
@@ -4366,9 +4392,11 @@ function ac_save_topic(gobj, event, kw, src)
4366
4392
  ["span", {i18n: "invalid topic name"}, t("invalid topic name")]));
4367
4393
  }
4368
4394
  if(find_topic(priv.model, priv.treedb_id, name)) {
4395
+ mark_name_field(gobj, true);
4369
4396
  yui_shell_show_error(yui_shell_of(gobj), "that topic is already there", {t: t});
4370
4397
  return -1;
4371
4398
  }
4399
+ mark_name_field(gobj, false);
4372
4400
  }
4373
4401
 
4374
4402
  close_dialog(gobj);
@@ -1748,3 +1748,30 @@ describe("names the treedb refuses", () => {
1748
1748
  }
1749
1749
  });
1750
1750
  });
1751
+
1752
+
1753
+ /*
1754
+ * The name field says whether the LAST save refused its name (REV-CLOUD
1755
+ * V15.4): set by every refusal of the name, cleared by a save that gets
1756
+ * past them. It was set by the rule only, and never cleared.
1757
+ */
1758
+ describe("aria-invalid of the name field", () => {
1759
+ test("set by a bad name and by a duplicate, cleared by a good one", () => {
1760
+ const {editor, host} = build("aria_topic", "db");
1761
+ gobj_send_event(editor, "EV_ADD_TOPIC", {}, host);
1762
+ const form = modals[modals.length - 1];
1763
+ const $value = form.$content.querySelector('[data-name="value"]');
1764
+ const save = (name) => {
1765
+ gobj_send_event(editor, "EV_SAVE_TOPIC", {
1766
+ creating: true, topic: "", values: {value: name}
1767
+ }, host);
1768
+ };
1769
+ save("a/b");
1770
+ expect($value.getAttribute("aria-invalid")).toBe("true");
1771
+ save("users"); // a duplicate: the topic is there
1772
+ expect($value.getAttribute("aria-invalid")).toBe("true");
1773
+ save("ok_name");
1774
+ expect($value.getAttribute("aria-invalid")).toBe(null);
1775
+ take("update-node");
1776
+ });
1777
+ });
@@ -211,15 +211,53 @@ function plan_import(treedb, incoming, options)
211
211
  * stop the plan BEFORE any write: run through, the backend refused
212
212
  * them half way, with the writes before them already done.
213
213
  */
214
+ if(topic_name_breaks_the_rule(treedb_id)) {
215
+ /* The treedb is a directory too (treedb_open_db() refuses it) */
216
+ conflicts.push({code: "treedb name breaks the name rule", topic: "", col: ""});
217
+ }
214
218
  for(let in_topic of incoming.topics) {
215
219
  let name = (in_topic || {}).id;
216
220
  if(name && topic_name_breaks_the_rule(name)) {
217
221
  conflicts.push({code: "topic name breaks the name rule", topic: name, col: ""});
218
222
  }
223
+ let cols = (in_topic || {}).cols;
224
+ if(Array.isArray(cols)) {
225
+ /* incoming_cols() drops a column with no id: said, not dropped */
226
+ for(let c of cols) {
227
+ if(!c || !c.id) {
228
+ conflicts.push({code: "column has no name", topic: name || "", col: ""});
229
+ }
230
+ }
231
+ }
219
232
  for(let in_col of incoming_cols(in_topic || {})) {
220
233
  if(col_name_breaks_the_rule(in_col.name)) {
221
234
  conflicts.push({code: "column name breaks the name rule", topic: name || "", col: in_col.name});
222
235
  }
236
+ /* A dict column carries its id twice: the key (what is
237
+ * imported) and its own `id`. One that breaks the rule is said
238
+ * too, not imported under the key in silence. */
239
+ let inner_id = (in_col.col || {}).id;
240
+ if(typeof inner_id === "string" && inner_id !== in_col.name &&
241
+ col_name_breaks_the_rule(inner_id)) {
242
+ conflicts.push({code: "column name breaks the name rule", topic: name || "", col: inner_id});
243
+ }
244
+ /* The hook map names a child topic and its fkey column: names
245
+ * the treedb refuses too, even when no column declares them. */
246
+ let hook = (in_col.col || {}).hook;
247
+ if(hook && typeof hook === "object" && !Array.isArray(hook)) {
248
+ for(let [child_topic, fkey_col] of Object.entries(hook)) {
249
+ /* The conflict names the BAD name (the child topic, its
250
+ * fkey), not the hook column that maps to it. */
251
+ if(topic_name_breaks_the_rule(child_topic)) {
252
+ conflicts.push({code: "topic name breaks the name rule",
253
+ topic: child_topic, col: ""});
254
+ }
255
+ if(col_name_breaks_the_rule(fkey_col)) {
256
+ conflicts.push({code: "column name breaks the name rule",
257
+ topic: child_topic, col: String(fkey_col)});
258
+ }
259
+ }
260
+ }
223
261
  }
224
262
  }
225
263
  if(conflicts.length > 0) {
@@ -282,3 +282,50 @@ describe("names the treedb refuses stop the plan before any write (REV-CLOUD V14
282
282
  expect(plan.writes.length).toBeGreaterThan(0);
283
283
  });
284
284
  });
285
+
286
+
287
+ describe("the import reads the hook map and the treedb id (REV-CLOUD V15.4)", () => {
288
+ test("a hook to an fkey x`y or to a topic a/b that no column declares: conflicts", () => {
289
+ let plan = plan_import(null, {
290
+ id: "db", schema_version: "1",
291
+ topics: [{id: "p", pkey: "id", cols: {
292
+ "id": {id: "id", type: "string"},
293
+ "kids": {id: "kids", type: "dict", flag: ["hook"], hook: {"k": "x`y", "a/b": "pid"}},
294
+ }}]
295
+ });
296
+ expect(plan.writes).toEqual([]);
297
+ let codes = plan.conflicts.map((c) => c.code);
298
+ expect(codes).toContain("column name breaks the name rule");
299
+ expect(codes).toContain("topic name breaks the name rule");
300
+ });
301
+
302
+ test("a treedb a/b, and a column with no id in an array: conflicts, not dropped", () => {
303
+ let plan = plan_import(null, {
304
+ id: "a/b", schema_version: "1",
305
+ topics: [{id: "t", pkey: "id", cols: [{id: "id", type: "string"}, {id: "", type: "string"}]}]
306
+ });
307
+ expect(plan.writes).toEqual([]);
308
+ let codes = plan.conflicts.map((c) => c.code);
309
+ expect(codes).toContain("treedb name breaks the name rule");
310
+ expect(codes).toContain("column has no name");
311
+ });
312
+ });
313
+
314
+
315
+ describe("a conflict names the bad name (REV-CLOUD V16.5)", () => {
316
+ test("the child topic and the fkey of a hook map, and a dict column's inner id", () => {
317
+ let plan = plan_import(null, {
318
+ id: "db", schema_version: "1",
319
+ topics: [{id: "p", pkey: "id", cols: {
320
+ "id": {id: "id", type: "string"},
321
+ "kids": {id: "kids", type: "dict", flag: ["hook"], hook: {"a/b": "x`y"}},
322
+ "good": {id: "go`od", type: "string"},
323
+ }}]
324
+ });
325
+ expect(plan.writes).toEqual([]);
326
+ let named = plan.conflicts.map((c) => `${c.code}|${c.topic}|${c.col}`);
327
+ expect(named).toContain("topic name breaks the name rule|a/b|");
328
+ expect(named).toContain("column name breaks the name rule|a/b|x`y");
329
+ expect(named).toContain("column name breaks the name rule|p|go`od");
330
+ });
331
+ });