@yuneta/gobj-ui 7.25.13 → 7.25.14

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
@@ -819,24 +819,29 @@ A write is known by its topic AND its serial (`<topic>^<form_write>`): each
819
819
  topic's form counts its serials from 1, and keyed by the serial alone two
820
820
  forms saving at once shared one entry.
821
821
 
822
- **A write cut by the drop is not a refusal** (7.25.13). A refused write
823
- reads its topic again at once, so a cell edited in place goes back to what the
824
- store has. A write that fails while the transport is NOT in session is the
825
- drop: a routing adapter (gui_agent's `C_AGENT_TREEDB_LINK`) settles what it
826
- had in flight when its session closes. The topic cannot be read then — the
827
- transport refuses the read (*"cannot route 'nodes' -- not in session"*) — so
828
- the view logs a warning and owes the read. Writes in flight on the
829
- disconnect edge are owed the same way. The first edge that finds the
830
- transport in session again pays it: each owed topic whose table is open is
831
- read once. An `EV_CONNECTION_STATE` "up" that comes before the view's own
832
- transport is in session leaves the read owed. A form is answered once, also
833
- when the edge answered it before the failure arrived.
822
+ **A drop is followed by a read of every open table** (7.25.13, 7.25.14). A
823
+ refused write reads its topic again at once, so a cell edited in place goes
824
+ back to what the store has. A write that fails while the transport is NOT in
825
+ session is the drop, not a refusal: a routing adapter (gui_agent's
826
+ `C_AGENT_TREEDB_LINK`) settles what it had in flight when its session closes.
827
+ The topic cannot be read then — the transport refuses the read (*"cannot route
828
+ 'nodes' -- not in session"*) — so the view logs a warning and waits.
829
+
830
+ A drop also hides the node events published while it lasts: another writer's
831
+ create, update or delete reaches no table. So the disconnect edge marks the
832
+ view, and the first edge that finds the transport in session again reads
833
+ EVERY open table once — as the schema editor reloads its model on the
834
+ reconnect. An `EV_CONNECTION_STATE` "up" that comes before the view's own
835
+ transport is in session does not read; the read waits for an edge that finds
836
+ the transport in session. An "up" with no drop before it reads nothing. A form
837
+ is answered once, also when the edge answered it before the failure arrived.
834
838
 
835
839
  ```js
836
- // Session up: a cell edit goes out as update-node.
837
- // Session drops; the adapter answers it {result: -1} -> no read, a warning
840
+ // Session up, tables `users` and `roles` open.
841
+ // Session drops -> writes in flight answered refused
842
+ // (another writer creates a user; its EV_TREEDB_NODE_CREATED is lost)
838
843
  // EV_TRANSPORT_STATE {connected: true}, transport in session
839
- // -> `nodes` of that topic, once
844
+ // -> `nodes` of users and of roles, once each
840
845
  ```
841
846
 
842
847
  **The pkey2 of an update goes back as the record had it** (7.25.5). It names
@@ -23757,7 +23757,7 @@ var __gclass__$6 = null;
23757
23757
  ***************************************************************/
23758
23758
  function mt_create$6(gobj) {
23759
23759
  gobj.priv.form_writes = {};
23760
- gobj.priv.reload_on_session = {};
23760
+ gobj.priv.reread_on_session = false;
23761
23761
  gobj.priv.conn_shell = null;
23762
23762
  build_ui$6(gobj);
23763
23763
  let subscriber = (0, _yuneta_gobj_js.gobj_read_pointer_attr)(gobj, "subscriber");
@@ -25193,7 +25193,7 @@ function ac_mt_command_answer$2(gobj, event, kw, src) {
25193
25193
  if (is_connected$1(gobj)) (0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_REFRESH_TOPIC", { topic_name: failed_topic }, gobj);
25194
25194
  else {
25195
25195
  (0, _yuneta_gobj_js.log_warning)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: the write of '${failed_topic}' was cut by the drop: the topic is read again when the session is back`);
25196
- gobj.priv.reload_on_session[failed_topic] = true;
25196
+ gobj.priv.reread_on_session = true;
25197
25197
  }
25198
25198
  }
25199
25199
  if (settle_form_write(gobj.priv.form_writes, form_write, failed_topic)) answer_form_write(gobj, get_gobj_formtable(gobj, failed_topic), form_write, false);
@@ -25394,30 +25394,34 @@ function ac_back_to_topics(gobj, event, kw, src) {
25394
25394
  function ac_transport_state$2(gobj, event, kw, src) {
25395
25395
  let connected = !!(kw && kw.connected);
25396
25396
  refresh_toolbar_buttons(gobj, connected);
25397
- if (!connected) abandon_form_writes(gobj.priv.form_writes).forEach((w) => {
25398
- (0, _yuneta_gobj_js.log_warning)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: transport closed, the write ${w.form_write} of '${w.topic_name}' is lost`);
25399
- if (w.topic_name) gobj.priv.reload_on_session[w.topic_name] = true;
25400
- answer_form_write(gobj, get_gobj_formtable(gobj, w.topic_name), w.form_write, false);
25401
- });
25402
- else reload_owed_topics(gobj);
25397
+ if (!connected) {
25398
+ gobj.priv.reread_on_session = true;
25399
+ abandon_form_writes(gobj.priv.form_writes).forEach((w) => {
25400
+ (0, _yuneta_gobj_js.log_warning)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: transport closed, the write ${w.form_write} of '${w.topic_name}' is lost`);
25401
+ answer_form_write(gobj, get_gobj_formtable(gobj, w.topic_name), w.form_write, false);
25402
+ });
25403
+ } else reread_open_topics(gobj);
25403
25404
  return 0;
25404
25405
  }
25405
25406
  /************************************************************
25406
- * The session is back: read again the topics a drop left
25407
- * unknown. Only when the TRANSPORT says so too -- the app's
25408
- * connection (EV_CONNECTION_STATE) can report "up" before the
25409
- * transport of this view is in session, and a read asked then is
25410
- * refused. What is owed stays owed until an edge finds the
25411
- * transport in session; each topic is read once.
25407
+ * The session is back after a drop: read every open table again,
25408
+ * as the schema editor reloads its model. A drop cuts the writes
25409
+ * in flight AND the node events published meanwhile, so any
25410
+ * table may be stale, not only the one a cut write left.
25411
+ *
25412
+ * Only when the TRANSPORT says so too -- the app's connection
25413
+ * (EV_CONNECTION_STATE) can report "up" before the transport of
25414
+ * this view is in session, and a read asked then is refused. The
25415
+ * read waits for an edge that finds the transport in session, and
25416
+ * is done once, though both edges report the same "up".
25412
25417
  ************************************************************/
25413
- function reload_owed_topics(gobj) {
25418
+ function reread_open_topics(gobj) {
25414
25419
  let priv = gobj.priv;
25415
- if (!is_connected$1(gobj)) return;
25416
- let topics = Object.keys(priv.reload_on_session);
25417
- priv.reload_on_session = {};
25418
- topics.forEach((topic_name) => {
25419
- if (!get_gobj_formtable(gobj, topic_name)) return;
25420
- (0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_REFRESH_TOPIC", { topic_name }, gobj);
25420
+ if (!priv.reread_on_session || !is_connected$1(gobj)) return;
25421
+ priv.reread_on_session = false;
25422
+ (0, _yuneta_gobj_js.gobj_match_children)(gobj, { __gclass_name__: "C_YUI_TREEDB_TOPIC_WITH_FORM" }).forEach((form) => {
25423
+ let topic_name = (0, _yuneta_gobj_js.gobj_read_attr)(form, "topic_name");
25424
+ if (topic_name) (0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_REFRESH_TOPIC", { topic_name }, gobj);
25421
25425
  });
25422
25426
  }
25423
25427
  /************************************************************
@@ -23752,7 +23752,7 @@ var __gclass__$6 = null;
23752
23752
  ***************************************************************/
23753
23753
  function mt_create$6(gobj) {
23754
23754
  gobj.priv.form_writes = {};
23755
- gobj.priv.reload_on_session = {};
23755
+ gobj.priv.reread_on_session = false;
23756
23756
  gobj.priv.conn_shell = null;
23757
23757
  build_ui$6(gobj);
23758
23758
  let subscriber = gobj_read_pointer_attr(gobj, "subscriber");
@@ -25188,7 +25188,7 @@ function ac_mt_command_answer$2(gobj, event, kw, src) {
25188
25188
  if (is_connected$1(gobj)) gobj_send_event(gobj, "EV_REFRESH_TOPIC", { topic_name: failed_topic }, gobj);
25189
25189
  else {
25190
25190
  log_warning(`${gobj_short_name(gobj)}: the write of '${failed_topic}' was cut by the drop: the topic is read again when the session is back`);
25191
- gobj.priv.reload_on_session[failed_topic] = true;
25191
+ gobj.priv.reread_on_session = true;
25192
25192
  }
25193
25193
  }
25194
25194
  if (settle_form_write(gobj.priv.form_writes, form_write, failed_topic)) answer_form_write(gobj, get_gobj_formtable(gobj, failed_topic), form_write, false);
@@ -25389,30 +25389,34 @@ function ac_back_to_topics(gobj, event, kw, src) {
25389
25389
  function ac_transport_state$2(gobj, event, kw, src) {
25390
25390
  let connected = !!(kw && kw.connected);
25391
25391
  refresh_toolbar_buttons(gobj, connected);
25392
- if (!connected) abandon_form_writes(gobj.priv.form_writes).forEach((w) => {
25393
- log_warning(`${gobj_short_name(gobj)}: transport closed, the write ${w.form_write} of '${w.topic_name}' is lost`);
25394
- if (w.topic_name) gobj.priv.reload_on_session[w.topic_name] = true;
25395
- answer_form_write(gobj, get_gobj_formtable(gobj, w.topic_name), w.form_write, false);
25396
- });
25397
- else reload_owed_topics(gobj);
25392
+ if (!connected) {
25393
+ gobj.priv.reread_on_session = true;
25394
+ abandon_form_writes(gobj.priv.form_writes).forEach((w) => {
25395
+ log_warning(`${gobj_short_name(gobj)}: transport closed, the write ${w.form_write} of '${w.topic_name}' is lost`);
25396
+ answer_form_write(gobj, get_gobj_formtable(gobj, w.topic_name), w.form_write, false);
25397
+ });
25398
+ } else reread_open_topics(gobj);
25398
25399
  return 0;
25399
25400
  }
25400
25401
  /************************************************************
25401
- * The session is back: read again the topics a drop left
25402
- * unknown. Only when the TRANSPORT says so too -- the app's
25403
- * connection (EV_CONNECTION_STATE) can report "up" before the
25404
- * transport of this view is in session, and a read asked then is
25405
- * refused. What is owed stays owed until an edge finds the
25406
- * transport in session; each topic is read once.
25402
+ * The session is back after a drop: read every open table again,
25403
+ * as the schema editor reloads its model. A drop cuts the writes
25404
+ * in flight AND the node events published meanwhile, so any
25405
+ * table may be stale, not only the one a cut write left.
25406
+ *
25407
+ * Only when the TRANSPORT says so too -- the app's connection
25408
+ * (EV_CONNECTION_STATE) can report "up" before the transport of
25409
+ * this view is in session, and a read asked then is refused. The
25410
+ * read waits for an edge that finds the transport in session, and
25411
+ * is done once, though both edges report the same "up".
25407
25412
  ************************************************************/
25408
- function reload_owed_topics(gobj) {
25413
+ function reread_open_topics(gobj) {
25409
25414
  let priv = gobj.priv;
25410
- if (!is_connected$1(gobj)) return;
25411
- let topics = Object.keys(priv.reload_on_session);
25412
- priv.reload_on_session = {};
25413
- topics.forEach((topic_name) => {
25414
- if (!get_gobj_formtable(gobj, topic_name)) return;
25415
- gobj_send_event(gobj, "EV_REFRESH_TOPIC", { topic_name }, gobj);
25415
+ if (!priv.reread_on_session || !is_connected$1(gobj)) return;
25416
+ priv.reread_on_session = false;
25417
+ gobj_match_children(gobj, { __gclass_name__: "C_YUI_TREEDB_TOPIC_WITH_FORM" }).forEach((form) => {
25418
+ let topic_name = gobj_read_attr(form, "topic_name");
25419
+ if (topic_name) gobj_send_event(gobj, "EV_REFRESH_TOPIC", { topic_name }, gobj);
25416
25420
  });
25417
25421
  }
25418
25422
  /************************************************************
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yuneta/gobj-ui",
3
- "version": "7.25.13",
3
+ "version": "7.25.14",
4
4
  "type": "module",
5
5
  "main": "dist/gobj-ui.cjs.js",
6
6
  "module": "dist/gobj-ui.es.js",
@@ -160,10 +160,11 @@ function mt_create(gobj)
160
160
  * and serial (form_writes_in_flight): answered refused when the
161
161
  * transport closes under them. */
162
162
  gobj.priv.form_writes = {};
163
- /* The topics a write cut by a DROP left unknown: the table may show
164
- * what was typed and the store what it had. Read again when the
165
- * session is back (reload_owed_topics), never out of session. */
166
- gobj.priv.reload_on_session = {};
163
+ /* The session dropped since the tables were read: every open table
164
+ * may be stale (a write the drop cut, node events published while
165
+ * nobody heard them). Read again when the session is back
166
+ * (reread_open_topics), never out of session. */
167
+ gobj.priv.reread_on_session = false;
167
168
  gobj.priv.conn_shell = null; /* the shell whose EV_CONNECTION_STATE we hear */
168
169
 
169
170
  build_ui(gobj);
@@ -2111,7 +2112,7 @@ function ac_mt_command_answer(gobj, event, kw, src)
2111
2112
  log_warning(`${gobj_short_name(gobj)}: the write of ` +
2112
2113
  `'${failed_topic}' was cut by the drop: the topic is ` +
2113
2114
  `read again when the session is back`);
2114
- gobj.priv.reload_on_session[failed_topic] = true;
2115
+ gobj.priv.reread_on_session = true;
2115
2116
  }
2116
2117
  }
2117
2118
  /* Answered once: the transport closing may have answered
@@ -2480,13 +2481,12 @@ function ac_transport_state(gobj, event, kw, src)
2480
2481
  * was typed, and the person saves again when the session is back.
2481
2482
  */
2482
2483
  if(!connected) {
2484
+ /* What happens in the store while the session is down reaches
2485
+ * no table: its node events are published to nobody. Every open
2486
+ * table is read again when the session is back. */
2487
+ gobj.priv.reread_on_session = true;
2483
2488
  abandon_form_writes(gobj.priv.form_writes).forEach((w) => {
2484
2489
  log_warning(`${gobj_short_name(gobj)}: transport closed, the write ${w.form_write} of '${w.topic_name}' is lost`);
2485
- /* Whether it reached the store is unknown: read it again
2486
- * when the session is back. */
2487
- if(w.topic_name) {
2488
- gobj.priv.reload_on_session[w.topic_name] = true;
2489
- }
2490
2490
  answer_form_write(
2491
2491
  gobj,
2492
2492
  get_gobj_formtable(gobj, w.topic_name),
@@ -2495,32 +2495,37 @@ function ac_transport_state(gobj, event, kw, src)
2495
2495
  );
2496
2496
  });
2497
2497
  } else {
2498
- reload_owed_topics(gobj);
2498
+ reread_open_topics(gobj);
2499
2499
  }
2500
2500
  return 0;
2501
2501
  }
2502
2502
 
2503
2503
  /************************************************************
2504
- * The session is back: read again the topics a drop left
2505
- * unknown. Only when the TRANSPORT says so too -- the app's
2506
- * connection (EV_CONNECTION_STATE) can report "up" before the
2507
- * transport of this view is in session, and a read asked then is
2508
- * refused. What is owed stays owed until an edge finds the
2509
- * transport in session; each topic is read once.
2504
+ * The session is back after a drop: read every open table again,
2505
+ * as the schema editor reloads its model. A drop cuts the writes
2506
+ * in flight AND the node events published meanwhile, so any
2507
+ * table may be stale, not only the one a cut write left.
2508
+ *
2509
+ * Only when the TRANSPORT says so too -- the app's connection
2510
+ * (EV_CONNECTION_STATE) can report "up" before the transport of
2511
+ * this view is in session, and a read asked then is refused. The
2512
+ * read waits for an edge that finds the transport in session, and
2513
+ * is done once, though both edges report the same "up".
2510
2514
  ************************************************************/
2511
- function reload_owed_topics(gobj)
2515
+ function reread_open_topics(gobj)
2512
2516
  {
2513
2517
  let priv = gobj.priv;
2514
- if(!is_connected(gobj)) {
2518
+ if(!priv.reread_on_session || !is_connected(gobj)) {
2515
2519
  return;
2516
2520
  }
2517
- let topics = Object.keys(priv.reload_on_session);
2518
- priv.reload_on_session = {};
2519
- topics.forEach((topic_name) => {
2520
- if(!get_gobj_formtable(gobj, topic_name)) {
2521
- return; /* its table is closed: opening it reads it */
2521
+ priv.reread_on_session = false;
2522
+ gobj_match_children(gobj, {
2523
+ __gclass_name__: "C_YUI_TREEDB_TOPIC_WITH_FORM"
2524
+ }).forEach((form) => {
2525
+ let topic_name = gobj_read_attr(form, "topic_name");
2526
+ if(topic_name) {
2527
+ gobj_send_event(gobj, "EV_REFRESH_TOPIC", {topic_name: topic_name}, gobj);
2522
2528
  }
2523
- gobj_send_event(gobj, "EV_REFRESH_TOPIC", {topic_name: topic_name}, gobj);
2524
2529
  });
2525
2530
  }
2526
2531
 
@@ -30,6 +30,7 @@ const {register_c_yui_treedb_topics} = await import("./c_yui_treedb_topics.js");
30
30
 
31
31
  const logged = [];
32
32
  const answers = []; /* what the fake forms were told */
33
+ const loaded = []; /* the rows each fake form was loaded with */
33
34
  const commands = []; /* what the fake transport was asked */
34
35
 
35
36
  /*
@@ -57,6 +58,12 @@ function ac_form_answer(gobj, event, kw, src)
57
58
  return 0;
58
59
  }
59
60
 
61
+ function ac_form_load(gobj, event, kw, src)
62
+ {
63
+ loaded.push({topic: gobj.priv_topic, rows: kw});
64
+ return 0;
65
+ }
66
+
60
67
  let yuno = null;
61
68
  let remote = null;
62
69
 
@@ -76,10 +83,11 @@ beforeAll(() => {
76
83
  );
77
84
  gclass_create(
78
85
  "C_YUI_TREEDB_TOPIC_WITH_FORM",
79
- [["EV_WRITE_DONE", 0], ["EV_WRITE_REFUSED", 0]],
86
+ [["EV_WRITE_DONE", 0], ["EV_WRITE_REFUSED", 0], ["EV_LOAD_NODES", 0]],
80
87
  [["ST_IDLE", [
81
88
  ["EV_WRITE_DONE", ac_form_answer, null],
82
- ["EV_WRITE_REFUSED", ac_form_answer, null]
89
+ ["EV_WRITE_REFUSED", ac_form_answer, null],
90
+ ["EV_LOAD_NODES", ac_form_load, null]
83
91
  ]]],
84
92
  {},
85
93
  0,
@@ -115,6 +123,7 @@ beforeEach(() => {
115
123
  logged.length = 0;
116
124
  answers.length = 0;
117
125
  commands.length = 0;
126
+ loaded.length = 0;
118
127
  });
119
128
 
120
129
  function build(name)
@@ -136,7 +145,9 @@ function build(name)
136
145
 
137
146
  const forms = {};
138
147
  for(const topic of ["users", "roles"]) {
139
- forms[topic] = gobj_create(`${name}_form_${topic}`, "C_YUI_TREEDB_TOPIC_WITH_FORM",
148
+ /* Named as the view names its forms: the answer of a `nodes`
149
+ * finds its table by that name. */
150
+ forms[topic] = gobj_create(`${name}_topics?${topic}`, "C_YUI_TREEDB_TOPIC_WITH_FORM",
140
151
  {topic_name: topic}, topics);
141
152
  forms[topic].priv_topic = topic;
142
153
  }
@@ -277,13 +288,12 @@ describe("a write cut by the drop does not reload out of session", () => {
277
288
  gobj_send_event(topics, "EV_TRANSPORT_STATE", {connected: false}, topics);
278
289
  expect(nodes_asked()).toEqual([]);
279
290
 
280
- /* The reconnect reloads what the drop left unknown -- once, though
281
- * two edges report it. */
291
+ /* The reconnect reads every open table -- once, though two edges
292
+ * report it. */
282
293
  gobj_change_state(remote, "ST_SESSION");
283
294
  gobj_send_event(topics, "EV_TRANSPORT_STATE", {connected: true}, topics);
284
- yui_shell_set_connection_state(shell, false);
285
295
  yui_shell_set_connection_state(shell, true);
286
- expect(nodes_asked()).toEqual(["users"]);
296
+ expect(nodes_asked().sort()).toEqual(["roles", "users"]);
287
297
  expect(errors()).toEqual([]);
288
298
  });
289
299
 
@@ -303,7 +313,7 @@ describe("a write cut by the drop does not reload out of session", () => {
303
313
 
304
314
  gobj_change_state(remote, "ST_SESSION");
305
315
  yui_shell_set_connection_state(shell, true);
306
- expect(nodes_asked()).toEqual(["users"]);
316
+ expect(nodes_asked().sort()).toEqual(["roles", "users"]);
307
317
  expect(errors()).toEqual([]);
308
318
  });
309
319
 
@@ -322,20 +332,78 @@ describe("a write cut by the drop does not reload out of session", () => {
322
332
 
323
333
  gobj_change_state(remote, "ST_SESSION");
324
334
  gobj_send_event(topics, "EV_TRANSPORT_STATE", {connected: true}, topics);
325
- expect(nodes_asked()).toEqual(["roles"]);
335
+ expect(nodes_asked().sort()).toEqual(["roles", "users"]);
326
336
  expect(errors().filter((m) => m.includes("not in session"))).toEqual([]);
327
337
  });
328
338
 
329
- test("a write refused IN session reloads its topic at once, and only then", () => {
339
+ test("a write refused IN session reloads its topic at once", () => {
330
340
  const {shell, topics} = build("t9");
331
341
  yui_shell_set_connection_state(shell, true);
332
342
  gobj_send_event(topics, "EV_UPDATE_FIELD",
333
343
  {topic_name: "users", id: "x", field: "name", value: "typed"}, topics);
334
344
  gobj_send_event(topics, "EV_MT_COMMAND_ANSWER", failed_write("users", 0), remote);
335
345
  expect(nodes_asked()).toEqual(["users"]);
346
+ });
347
+ });
336
348
 
349
+ /*
350
+ * The answer of a `nodes`, the way C_IEVENT_CLI delivers it.
351
+ */
352
+ function nodes_answer_of(topic_name, rows)
353
+ {
354
+ return {result: 0, comment: "", data: rows, __md_iev__: {
355
+ command_stack: [{command: "nodes", kw: {topic_name: topic_name}}]
356
+ }};
357
+ }
358
+
359
+ describe("what the drop hid is read again on the reconnect", () => {
360
+
361
+ test("a node created during the drop is in its table after the reconnect", () => {
362
+ const {shell, topics} = build("t10");
363
+ yui_shell_set_connection_state(shell, true);
364
+
365
+ /* No write in flight: the session just drops. While it is down,
366
+ * another writer creates a user; its EV_TREEDB_NODE_CREATED is
367
+ * published to nobody. */
368
+ gobj_change_state(remote, "ST_DISCONNECTED");
337
369
  gobj_send_event(topics, "EV_TRANSPORT_STATE", {connected: false}, topics);
370
+ yui_shell_set_connection_state(shell, false);
371
+ expect(nodes_asked()).toEqual([]);
372
+
373
+ gobj_change_state(remote, "ST_SESSION");
338
374
  gobj_send_event(topics, "EV_TRANSPORT_STATE", {connected: true}, topics);
339
- expect(nodes_asked()).toEqual(["users"]);
375
+ yui_shell_set_connection_state(shell, true);
376
+
377
+ /* Every open table is read once, though two edges said "up". */
378
+ expect(nodes_asked().sort()).toEqual(["roles", "users"]);
379
+
380
+ gobj_send_event(topics, "EV_MT_COMMAND_ANSWER",
381
+ nodes_answer_of("users", [{id: "x"}, {id: "created_during_the_drop"}]), remote);
382
+ expect(loaded).toEqual([
383
+ {topic: "users", rows: [{id: "x"}, {id: "created_during_the_drop"}]}
384
+ ]);
385
+ expect(errors()).toEqual([]);
386
+ });
387
+
388
+ test("an 'up' with no drop before it reads nothing", () => {
389
+ const {shell, topics} = build("t11");
390
+ yui_shell_set_connection_state(shell, true);
391
+ gobj_send_event(topics, "EV_TRANSPORT_STATE", {connected: true}, topics);
392
+ expect(nodes_asked()).toEqual([]);
393
+ });
394
+
395
+ test("an edge that says 'up' before the transport is in session waits", () => {
396
+ const {shell, topics} = build("t12");
397
+ yui_shell_set_connection_state(shell, true);
398
+ gobj_change_state(remote, "ST_DISCONNECTED");
399
+ yui_shell_set_connection_state(shell, false);
400
+
401
+ yui_shell_set_connection_state(shell, true);
402
+ expect(nodes_asked()).toEqual([]);
403
+ expect(errors().filter((m) => m.includes("not in session"))).toEqual([]);
404
+
405
+ gobj_change_state(remote, "ST_SESSION");
406
+ gobj_send_event(topics, "EV_TRANSPORT_STATE", {connected: true}, topics);
407
+ expect(nodes_asked().sort()).toEqual(["roles", "users"]);
340
408
  });
341
409
  });