@yuneta/gobj-ui 7.25.4 → 7.25.6
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 +86 -1
- package/dist/gobj-ui.cjs.js +287 -56
- package/dist/gobj-ui.es.js +288 -57
- package/package.json +1 -1
- package/src/c_yui_schema_editor.js +165 -32
- package/src/c_yui_shell.js +61 -8
- package/src/c_yui_treedb_topic_with_form.js +12 -1
- package/src/c_yui_treedb_topics.js +41 -10
- package/src/form_time_value.js +23 -0
- package/src/form_writes_in_flight.js +32 -13
- package/src/form_writes_in_flight.test.js +18 -3
- package/src/host_drafts.js +28 -2
- package/src/host_drafts.test.js +24 -1
- package/src/schema_editor_drafts.wiring.test.js +176 -0
- package/src/schema_editor_transport.wiring.test.js +284 -0
- package/src/schema_version_bump.test.js +10 -5
- package/src/treedb_topics_transport.wiring.test.js +228 -0
- package/src/treedb_write_plan.js +32 -1
- package/src/treedb_write_plan.test.js +22 -1
package/src/form_time_value.js
CHANGED
|
@@ -6,6 +6,29 @@
|
|
|
6
6
|
* them, and they must be each other's inverse: a writable time
|
|
7
7
|
* column travels back on EVERY save of the record.
|
|
8
8
|
*
|
|
9
|
+
* KNOWN LIMIT -- the change of hour. A `datetime-local` holds a
|
|
10
|
+
* LOCAL WALL time and no offset, and a wall time is not always
|
|
11
|
+
* one instant:
|
|
12
|
+
*
|
|
13
|
+
* - the night the clocks go back (autumn), one hour happens
|
|
14
|
+
* twice. "02:30" names two instants an hour apart, and
|
|
15
|
+
* `new Date("YYYY-MM-DDT02:30:00")` answers the EARLIER one
|
|
16
|
+
* (ECMAScript's "compatible" disambiguation). An epoch in the
|
|
17
|
+
* SECOND pass of that hour is shown as 02:30 and comes back
|
|
18
|
+
* one hour earlier: saving the record -- any field of it --
|
|
19
|
+
* moves a writable time column that holds it by -3600 s.
|
|
20
|
+
* - the night the clocks go forward (spring), one hour does not
|
|
21
|
+
* exist. No epoch is shown in it, so nothing read comes back
|
|
22
|
+
* wrong; a wall time TYPED in it is moved forward by the
|
|
23
|
+
* browser.
|
|
24
|
+
*
|
|
25
|
+
* Not fixable here without the offset, which the input cannot
|
|
26
|
+
* carry. What is safe: a read-only time column never goes back
|
|
27
|
+
* (treedb_write_plan.js, col_goes_back_to_treedb), and a time
|
|
28
|
+
* pkey2 goes back as the record had it, never through this
|
|
29
|
+
* widget (instance_keys_of). A writable time column edited or
|
|
30
|
+
* merely saved inside the repeated hour is the exposed case.
|
|
31
|
+
*
|
|
9
32
|
* Copyright (c) 2026, ArtGins.
|
|
10
33
|
* All Rights Reserved.
|
|
11
34
|
****************************************************************************/
|
|
@@ -2,18 +2,33 @@
|
|
|
2
2
|
* form_writes_in_flight.js
|
|
3
3
|
*
|
|
4
4
|
* The writes of topic forms a C_YUI_TREEDB_TOPICS view has sent
|
|
5
|
-
* and not yet answered
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
5
|
+
* and not yet answered. A form that waits for its answer
|
|
6
|
+
* (form_waits_for_answer) stays open and busy until the host
|
|
7
|
+
* answers, so the host has to answer EVERY way a write ends --
|
|
8
|
+
* and the transport closing under a write in flight, or a
|
|
9
|
+
* command that never left because there was no session, are two
|
|
10
|
+
* of them that nobody answered: the form stayed busy for ever
|
|
11
|
+
* (N8 of the 2026-09-22 review).
|
|
12
|
+
*
|
|
13
|
+
* A write is known by its TOPIC and its `form_write` serial,
|
|
14
|
+
* never by the serial alone: each topic has its own form, and
|
|
15
|
+
* each form counts its serials from 1. Keyed by the serial, two
|
|
16
|
+
* forms with a write in flight at once shared the key "1", the
|
|
17
|
+
* first answer settled both, and the transport closing then
|
|
18
|
+
* answered only one of them.
|
|
12
19
|
*
|
|
13
20
|
* Copyright (c) 2026, ArtGins.
|
|
14
21
|
* All Rights Reserved.
|
|
15
22
|
***********************************************************************/
|
|
16
23
|
|
|
24
|
+
/************************************************************
|
|
25
|
+
* The key of one write: `<topic>^<serial>`.
|
|
26
|
+
************************************************************/
|
|
27
|
+
function form_write_key(topic_name, form_write)
|
|
28
|
+
{
|
|
29
|
+
return `${topic_name || ""}^${form_write}`;
|
|
30
|
+
}
|
|
31
|
+
|
|
17
32
|
/************************************************************
|
|
18
33
|
* A write left: remember whose it is
|
|
19
34
|
************************************************************/
|
|
@@ -22,18 +37,21 @@ function track_form_write(in_flight, form_write, topic_name)
|
|
|
22
37
|
if(!in_flight || !form_write) {
|
|
23
38
|
return;
|
|
24
39
|
}
|
|
25
|
-
in_flight[form_write] =
|
|
40
|
+
in_flight[form_write_key(topic_name, form_write)] = {
|
|
41
|
+
form_write: form_write,
|
|
42
|
+
topic_name: topic_name || ""
|
|
43
|
+
};
|
|
26
44
|
}
|
|
27
45
|
|
|
28
46
|
/************************************************************
|
|
29
47
|
* A write was answered, one way or the other
|
|
30
48
|
************************************************************/
|
|
31
|
-
function settle_form_write(in_flight, form_write)
|
|
49
|
+
function settle_form_write(in_flight, form_write, topic_name)
|
|
32
50
|
{
|
|
33
51
|
if(!in_flight || !form_write) {
|
|
34
52
|
return;
|
|
35
53
|
}
|
|
36
|
-
delete in_flight[form_write];
|
|
54
|
+
delete in_flight[form_write_key(topic_name, form_write)];
|
|
37
55
|
}
|
|
38
56
|
|
|
39
57
|
/************************************************************
|
|
@@ -46,9 +64,10 @@ function abandon_form_writes(in_flight)
|
|
|
46
64
|
if(!in_flight) {
|
|
47
65
|
return abandoned;
|
|
48
66
|
}
|
|
49
|
-
Object.keys(in_flight).forEach((
|
|
50
|
-
|
|
51
|
-
|
|
67
|
+
Object.keys(in_flight).forEach((key) => {
|
|
68
|
+
const w = in_flight[key];
|
|
69
|
+
abandoned.push({form_write: Number(w.form_write), topic_name: w.topic_name});
|
|
70
|
+
delete in_flight[key];
|
|
52
71
|
});
|
|
53
72
|
return abandoned;
|
|
54
73
|
}
|
|
@@ -11,8 +11,23 @@ describe("form writes in flight", () => {
|
|
|
11
11
|
const in_flight = {};
|
|
12
12
|
track_form_write(in_flight, 1, "users");
|
|
13
13
|
track_form_write(in_flight, 2, "roles");
|
|
14
|
-
settle_form_write(in_flight, 1);
|
|
15
|
-
expect(
|
|
14
|
+
settle_form_write(in_flight, 1, "users");
|
|
15
|
+
expect(abandon_form_writes(in_flight)).toEqual([
|
|
16
|
+
{form_write: 2, topic_name: "roles"},
|
|
17
|
+
]);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("two forms, each at its serial 1: two writes, not one", () => {
|
|
21
|
+
/* Each topic's form counts its serials from 1. Keyed by the serial
|
|
22
|
+
* alone, the answer of one form settled the other's write, and a
|
|
23
|
+
* transport closing then left that one busy for ever. */
|
|
24
|
+
const in_flight = {};
|
|
25
|
+
track_form_write(in_flight, 1, "users");
|
|
26
|
+
track_form_write(in_flight, 1, "roles");
|
|
27
|
+
settle_form_write(in_flight, 1, "users");
|
|
28
|
+
expect(abandon_form_writes(in_flight)).toEqual([
|
|
29
|
+
{form_write: 1, topic_name: "roles"},
|
|
30
|
+
]);
|
|
16
31
|
});
|
|
17
32
|
|
|
18
33
|
it("the transport closes: every write in flight is abandoned, once (N8)", () => {
|
|
@@ -39,7 +54,7 @@ describe("form writes in flight", () => {
|
|
|
39
54
|
|
|
40
55
|
it("no map: nothing to do", () => {
|
|
41
56
|
expect(() => track_form_write(null, 1, "x")).not.toThrow();
|
|
42
|
-
expect(() => settle_form_write(null, 1)).not.toThrow();
|
|
57
|
+
expect(() => settle_form_write(null, 1, "x")).not.toThrow();
|
|
43
58
|
expect(abandon_form_writes(null)).toEqual([]);
|
|
44
59
|
});
|
|
45
60
|
});
|
package/src/host_drafts.js
CHANGED
|
@@ -10,17 +10,42 @@
|
|
|
10
10
|
* __system__ still differed from the file in use (N13 of the
|
|
11
11
|
* 2026-09-22 review). The host reads that difference from
|
|
12
12
|
* C_TREEDB's `saved-schema` (`draft_changed`, by treedb) and
|
|
13
|
-
* hands it here; the editor applies it to its model
|
|
13
|
+
* hands it here; the editor applies it to its model, in place
|
|
14
|
+
* of what the host said before.
|
|
15
|
+
*
|
|
16
|
+
* What `draft_changed` measures depends on the node: the draft
|
|
17
|
+
* against the SAVED schema on a node newer than yunetas 7.25.3,
|
|
18
|
+
* and against the file IN USE on a 7.25.3 node -- where a topic saved and
|
|
19
|
+
* not yet applied is still named, and the editor, which cannot
|
|
20
|
+
* tell the two apart, shows it as a draft until the Apply.
|
|
14
21
|
*
|
|
15
22
|
* Copyright (c) 2026, ArtGins.
|
|
16
23
|
* All Rights Reserved.
|
|
17
24
|
***********************************************************************/
|
|
18
25
|
|
|
26
|
+
/************************************************************
|
|
27
|
+
* The topics of `model` the host names as drafts, as
|
|
28
|
+
* {topic id: true}: `drafts` is {treedb_name: [topic names]}. A
|
|
29
|
+
* treedb or a topic the model does not hold is ignored.
|
|
30
|
+
*
|
|
31
|
+
* What the editor keeps is THIS, apart from what its own session
|
|
32
|
+
* wrote, and replaced whole on every EV_DRAFTS: the host is the
|
|
33
|
+
* truth after a reload. Folded into the session's marks, a topic the
|
|
34
|
+
* host once named stayed a draft after the save that published it.
|
|
35
|
+
************************************************************/
|
|
36
|
+
function host_draft_ids(model, drafts)
|
|
37
|
+
{
|
|
38
|
+
const ids = {};
|
|
39
|
+
mark_host_drafts(ids, model, drafts);
|
|
40
|
+
return ids;
|
|
41
|
+
}
|
|
42
|
+
|
|
19
43
|
/************************************************************
|
|
20
44
|
* Mark in `written` the topics of `model` the host names as
|
|
21
45
|
* drafts: `drafts` is {treedb_name: [topic names]}. A treedb or a
|
|
22
46
|
* topic the model does not hold is ignored. Returns how many were
|
|
23
|
-
* marked.
|
|
47
|
+
* marked. It only ADDS: a caller that must forget what the host
|
|
48
|
+
* no longer names starts from an empty map (host_draft_ids()).
|
|
24
49
|
************************************************************/
|
|
25
50
|
function mark_host_drafts(written, model, drafts)
|
|
26
51
|
{
|
|
@@ -63,6 +88,7 @@ function drafts_of_saved_answer(rows, into)
|
|
|
63
88
|
}
|
|
64
89
|
|
|
65
90
|
export {
|
|
91
|
+
host_draft_ids,
|
|
66
92
|
mark_host_drafts,
|
|
67
93
|
drafts_of_saved_answer,
|
|
68
94
|
};
|
package/src/host_drafts.test.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import {describe, it, expect} from "vitest";
|
|
2
|
-
import {mark_host_drafts, drafts_of_saved_answer} from "./host_drafts.js";
|
|
2
|
+
import {host_draft_ids, mark_host_drafts, drafts_of_saved_answer} from "./host_drafts.js";
|
|
3
3
|
|
|
4
4
|
const model = {
|
|
5
5
|
treedbs: [
|
|
@@ -60,3 +60,26 @@ describe("drafts_of_saved_answer", () => {
|
|
|
60
60
|
expect(drafts_of_saved_answer(null)).toEqual({});
|
|
61
61
|
});
|
|
62
62
|
});
|
|
63
|
+
|
|
64
|
+
describe("host_draft_ids", () => {
|
|
65
|
+
|
|
66
|
+
it("resolves the host's names on the model, as topic ids", () => {
|
|
67
|
+
expect(host_draft_ids(model, {treedb_a: ["users"], treedb_b: ["users"]}))
|
|
68
|
+
.toEqual({"treedb_a^users": true, "treedb_b^users": true});
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("is a fresh map every time: what the host stopped naming is gone (M1)", () => {
|
|
72
|
+
/* mark_host_drafts() only ADDS, and the editor fed it the same map
|
|
73
|
+
* every time: a topic the host named before the Save stayed a
|
|
74
|
+
* draft after it. */
|
|
75
|
+
const before = host_draft_ids(model, {treedb_a: ["users", "roles"]});
|
|
76
|
+
const after = host_draft_ids(model, {treedb_a: []});
|
|
77
|
+
expect(before).toEqual({"treedb_a^users": true, "treedb_a^roles": true});
|
|
78
|
+
expect(after).toEqual({});
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("no model, no drafts: empty", () => {
|
|
82
|
+
expect(host_draft_ids(null, {treedb_a: ["users"]})).toEqual({});
|
|
83
|
+
expect(host_draft_ids(model, null)).toEqual({});
|
|
84
|
+
});
|
|
85
|
+
});
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/***********************************************************************
|
|
2
|
+
* schema_editor_drafts.wiring.test.js
|
|
3
|
+
*
|
|
4
|
+
* EV_DRAFTS driven through a real C_YUI_SCHEMA_EDITOR on a document
|
|
5
|
+
* double. The helper was right and tested; the ACTION folded the
|
|
6
|
+
* host's marks into the session's, so a topic the host named before
|
|
7
|
+
* a Save stayed a draft after it (M1 of the 2026-09-23 review).
|
|
8
|
+
*
|
|
9
|
+
* Copyright (c) 2026, ArtGins.
|
|
10
|
+
* All Rights Reserved.
|
|
11
|
+
***********************************************************************/
|
|
12
|
+
import {describe, test, expect, beforeAll, beforeEach} from "vitest";
|
|
13
|
+
import {install_dom_double} from "../test/dom_double.js";
|
|
14
|
+
|
|
15
|
+
install_dom_double();
|
|
16
|
+
|
|
17
|
+
const {
|
|
18
|
+
SDATA_END,
|
|
19
|
+
gclass_create,
|
|
20
|
+
gobj_start_up, gobj_create_yuno, gobj_create, gobj_create_service,
|
|
21
|
+
gobj_start, gobj_send_event, gobj_read_attr,
|
|
22
|
+
gobj_change_state,
|
|
23
|
+
set_log_callback,
|
|
24
|
+
} = await import("@yuneta/gobj-js");
|
|
25
|
+
const {register_c_yui_schema_editor, topic_is_draft} = await import("./c_yui_schema_editor.js");
|
|
26
|
+
|
|
27
|
+
const logged = [];
|
|
28
|
+
const commands = [];
|
|
29
|
+
|
|
30
|
+
/* The yuno's system schema: one treedb, two topics. */
|
|
31
|
+
const RECORDS = {
|
|
32
|
+
treedbs: [{id: "db", schema_version: 4}],
|
|
33
|
+
topics: [
|
|
34
|
+
{id: "db.users", value: "users", order: 1, pkey: "id", topic_version: 2,
|
|
35
|
+
treedbs: ["treedbs^db^topics"]},
|
|
36
|
+
{id: "db.roles", value: "roles", order: 2, pkey: "id", topic_version: 1,
|
|
37
|
+
treedbs: ["treedbs^db^topics"]},
|
|
38
|
+
],
|
|
39
|
+
cols: [
|
|
40
|
+
{id: "db.users.id", value: "id", order: 1, type: "string",
|
|
41
|
+
flag: ["persistent", "required"], topics: ["topics^db.users^cols"]},
|
|
42
|
+
{id: "db.roles.id", value: "id", order: 1, type: "string",
|
|
43
|
+
flag: ["persistent", "required"], topics: ["topics^db.roles^cols"]},
|
|
44
|
+
],
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
function remote_command_parser(gobj, command, kw, src)
|
|
48
|
+
{
|
|
49
|
+
commands.push({command, kw, src});
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
let yuno = null;
|
|
54
|
+
|
|
55
|
+
beforeAll(() => {
|
|
56
|
+
gobj_start_up(null, null, null, null, null, null, null);
|
|
57
|
+
set_log_callback((level, msg) => {
|
|
58
|
+
logged.push({level: String(level), msg: String(msg)});
|
|
59
|
+
});
|
|
60
|
+
gclass_create("C_TEST_HOST", [], [["ST_IDLE", []]], {}, 0, [SDATA_END()], {}, 0, 0, 0, 0);
|
|
61
|
+
gclass_create(
|
|
62
|
+
"C_TEST_EDITOR_HOST",
|
|
63
|
+
[["EV_POSITION_CHANGED", 0], ["EV_RECORD_WRITTEN", 0], ["EV_SCHEMA_CHECKED", 0]],
|
|
64
|
+
[["ST_IDLE", [
|
|
65
|
+
["EV_POSITION_CHANGED", () => 0, null],
|
|
66
|
+
["EV_RECORD_WRITTEN", () => 0, null],
|
|
67
|
+
["EV_SCHEMA_CHECKED", () => 0, null]
|
|
68
|
+
]]],
|
|
69
|
+
{}, 0, [SDATA_END()], {}, 0, 0, 0, 0
|
|
70
|
+
);
|
|
71
|
+
gclass_create(
|
|
72
|
+
"C_TEST_REMOTE",
|
|
73
|
+
[],
|
|
74
|
+
[["ST_SESSION", []]],
|
|
75
|
+
{mt_command_parser: remote_command_parser},
|
|
76
|
+
0, [SDATA_END()], {}, 0, 0, 0, 0
|
|
77
|
+
);
|
|
78
|
+
register_c_yui_schema_editor();
|
|
79
|
+
yuno = gobj_create_yuno("drafts_yuno", "C_TEST_HOST", {});
|
|
80
|
+
gobj_start(yuno);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
beforeEach(() => {
|
|
84
|
+
logged.length = 0;
|
|
85
|
+
commands.length = 0;
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
/* Answer every `nodes` the editor asked, the way a transport does: the
|
|
89
|
+
* request's frame on top of the command_stack. */
|
|
90
|
+
function answer_the_load(editor, remote)
|
|
91
|
+
{
|
|
92
|
+
const asked = commands.filter((c) => c.command === "nodes");
|
|
93
|
+
commands.length = 0;
|
|
94
|
+
for(const c of asked) {
|
|
95
|
+
gobj_send_event(editor, "EV_MT_COMMAND_ANSWER", {
|
|
96
|
+
result: 0,
|
|
97
|
+
data: RECORDS[c.kw.topic_name],
|
|
98
|
+
__md_iev__: {command_stack: [{command: "nodes", kw: c.kw}]}
|
|
99
|
+
}, remote);
|
|
100
|
+
}
|
|
101
|
+
return asked.length;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function build(name)
|
|
105
|
+
{
|
|
106
|
+
const remote = gobj_create_service(`${name}_remote`, "C_TEST_REMOTE", {}, yuno);
|
|
107
|
+
gobj_change_state(remote, "ST_SESSION");
|
|
108
|
+
const host = gobj_create(`${name}_host`, "C_TEST_EDITOR_HOST", {}, yuno);
|
|
109
|
+
const editor = gobj_create(`${name}_editor`, "C_YUI_SCHEMA_EDITOR", {
|
|
110
|
+
gobj_remote_yuno: remote,
|
|
111
|
+
treedb_name: "treedb_system_schema",
|
|
112
|
+
base_route: "/schemas"
|
|
113
|
+
}, host);
|
|
114
|
+
gobj_start(editor);
|
|
115
|
+
expect(answer_the_load(editor, remote)).toBe(3);
|
|
116
|
+
gobj_send_event(editor, "EV_SHOW", {subpath: "db"}, host);
|
|
117
|
+
return {editor, remote, host};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function drafts_on_screen(editor)
|
|
121
|
+
{
|
|
122
|
+
const $c = gobj_read_attr(editor, "$container");
|
|
123
|
+
return $c.querySelectorAll(".SCHEMA_TOPIC_VERSION .is-warning").length;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function is_draft(editor, topic_id)
|
|
127
|
+
{
|
|
128
|
+
return topic_is_draft(editor.priv, {id: topic_id});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
describe("EV_DRAFTS: the host is the truth", () => {
|
|
132
|
+
|
|
133
|
+
test("the host names a topic: it is a draft, on screen too", () => {
|
|
134
|
+
const {editor, host} = build("d1");
|
|
135
|
+
gobj_send_event(editor, "EV_DRAFTS", {drafts: {db: ["users"]}}, host);
|
|
136
|
+
expect(is_draft(editor, "db.users")).toBe(true);
|
|
137
|
+
expect(is_draft(editor, "db.roles")).toBe(false);
|
|
138
|
+
expect(drafts_on_screen(editor)).toBe(1);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test("the host names none any more (a Save): the mark goes (M1)", () => {
|
|
142
|
+
/* It only ever ADDED: after the Save the editor still said
|
|
143
|
+
* "unsaved schema changes". */
|
|
144
|
+
const {editor, host} = build("d2");
|
|
145
|
+
gobj_send_event(editor, "EV_DRAFTS", {drafts: {db: ["users", "roles"]}}, host);
|
|
146
|
+
expect(drafts_on_screen(editor)).toBe(2);
|
|
147
|
+
|
|
148
|
+
gobj_send_event(editor, "EV_DRAFTS", {drafts: {db: []}}, host);
|
|
149
|
+
expect(is_draft(editor, "db.users")).toBe(false);
|
|
150
|
+
expect(is_draft(editor, "db.roles")).toBe(false);
|
|
151
|
+
expect(drafts_on_screen(editor)).toBe(0);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
test("a reload keeps what the host said last, and only that", () => {
|
|
155
|
+
const {editor, remote, host} = build("d3");
|
|
156
|
+
gobj_send_event(editor, "EV_DRAFTS", {drafts: {db: ["roles"]}}, host);
|
|
157
|
+
gobj_send_event(editor, "EV_REFRESH", {}, host);
|
|
158
|
+
expect(answer_the_load(editor, remote)).toBe(3);
|
|
159
|
+
expect(is_draft(editor, "db.roles")).toBe(true);
|
|
160
|
+
expect(is_draft(editor, "db.users")).toBe(false);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
test("what this session wrote is not cleared by the host's answer", () => {
|
|
164
|
+
const {editor, host} = build("d4");
|
|
165
|
+
editor.priv.written["db.users"] = true; /* a write of this session */
|
|
166
|
+
gobj_send_event(editor, "EV_DRAFTS", {drafts: {}}, host);
|
|
167
|
+
expect(is_draft(editor, "db.users")).toBe(true);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
test("no error logged on the way", () => {
|
|
171
|
+
const {editor, host} = build("d5");
|
|
172
|
+
gobj_send_event(editor, "EV_DRAFTS", {drafts: {db: ["users"]}}, host);
|
|
173
|
+
gobj_send_event(editor, "EV_DRAFTS", {drafts: {}}, host);
|
|
174
|
+
expect(logged.filter((l) => l.level === "error").map((l) => l.msg)).toEqual([]);
|
|
175
|
+
});
|
|
176
|
+
});
|