@yuneta/gobj-ui 7.23.195 → 7.23.196
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 +11 -10
- package/dist/gobj-ui.cjs.js +81 -155
- package/dist/gobj-ui.es.js +81 -155
- package/package.json +1 -1
- package/src/c_yui_schema_editor.js +57 -122
- package/src/schema_validate.js +5 -17
- package/src/schema_validate.test.js +4 -24
- package/src/schema_version_bump.test.js +14 -71
|
@@ -19,13 +19,15 @@
|
|
|
19
19
|
* fkey, the order of a new sibling, the version that publishes
|
|
20
20
|
* the change.
|
|
21
21
|
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* the
|
|
27
|
-
*
|
|
28
|
-
*
|
|
22
|
+
* AN EDIT IS A DRAFT. A write here moves no version and reaches no
|
|
23
|
+
* treedb: the host SAVES it (C_TREEDB's save-schema raises the
|
|
24
|
+
* topic_version of what changed and the schema_version, once) and
|
|
25
|
+
* APPLIES it (apply-schema + a restart of the owning yuno). Until
|
|
26
|
+
* the owner's design of M36 (2026-09-21 review) every write here
|
|
27
|
+
* raised both versions, so an edit half made was already the
|
|
28
|
+
* schema of the next start. The topic list marks what this session
|
|
29
|
+
* wrote and has not saved; the host's EV_REFRESH after a save is
|
|
30
|
+
* what forgets it.
|
|
29
31
|
*
|
|
30
32
|
* WHAT ELSE IS HERE, and why each is here and not somewhere else:
|
|
31
33
|
*
|
|
@@ -572,21 +574,23 @@ function start_measuring(gobj)
|
|
|
572
574
|
}
|
|
573
575
|
|
|
574
576
|
/***************************************************************
|
|
575
|
-
* "This topic was written
|
|
577
|
+
* "This topic was written in this session and is not saved."
|
|
576
578
|
*
|
|
577
|
-
*
|
|
578
|
-
*
|
|
579
|
-
*
|
|
579
|
+
* An edit is a DRAFT: it moves no version and reaches no treedb
|
|
580
|
+
* until the host saves it (C_TREEDB's save-schema, which raises the
|
|
581
|
+
* versions of what changed) -- and the save makes the host send
|
|
582
|
+
* EV_REFRESH, whose reload is what forgets the drafts.
|
|
583
|
+
*
|
|
584
|
+
* ONE predicate, because it is asked in three places -- the topic
|
|
585
|
+
* list draws a chip with it, the column view a banner, the export
|
|
586
|
+
* a warning -- and copies of a rule are how they drift.
|
|
580
587
|
***************************************************************/
|
|
581
|
-
function
|
|
588
|
+
function topic_is_draft(written, topic)
|
|
582
589
|
{
|
|
583
|
-
if(!topic || !written
|
|
584
|
-
return false;
|
|
585
|
-
}
|
|
586
|
-
if(!written[topic.id]) {
|
|
590
|
+
if(!topic || !written) {
|
|
587
591
|
return false;
|
|
588
592
|
}
|
|
589
|
-
return
|
|
593
|
+
return written[topic.id]? true: false;
|
|
590
594
|
}
|
|
591
595
|
|
|
592
596
|
/***************************************************************
|
|
@@ -1041,7 +1045,7 @@ function render_topics(gobj, $body)
|
|
|
1041
1045
|
let twice = repeated_names(treedb.topics);
|
|
1042
1046
|
let $rows = [];
|
|
1043
1047
|
for(let topic of treedb.topics) {
|
|
1044
|
-
let pending =
|
|
1048
|
+
let pending = topic_is_draft(priv.written, topic);
|
|
1045
1049
|
/* Two topics of one name: the id is what tells them apart, and
|
|
1046
1050
|
* it is what the url has to carry — addressing by name would
|
|
1047
1051
|
* open the first one whichever row was clicked. */
|
|
@@ -1192,7 +1196,7 @@ function render_columns(gobj, $body)
|
|
|
1192
1196
|
* The version banner: the one thing that makes
|
|
1193
1197
|
* an edit succeed and change nothing.
|
|
1194
1198
|
*----------------------------------------------*/
|
|
1195
|
-
let $banner =
|
|
1199
|
+
let $banner = draft_banner(gobj, topic);
|
|
1196
1200
|
if($banner) {
|
|
1197
1201
|
$wrap.appendChild($banner);
|
|
1198
1202
|
}
|
|
@@ -1235,48 +1239,34 @@ function render_columns(gobj, $body)
|
|
|
1235
1239
|
}
|
|
1236
1240
|
|
|
1237
1241
|
/***************************************************************
|
|
1238
|
-
* "This topic
|
|
1242
|
+
* "This topic has changes that are not saved."
|
|
1239
1243
|
*
|
|
1240
|
-
*
|
|
1241
|
-
*
|
|
1242
|
-
*
|
|
1243
|
-
*
|
|
1244
|
-
*
|
|
1244
|
+
* A draft, not an error: the edit is in __system__ and moves no
|
|
1245
|
+
* version, so the treedb does not see it until the host saves it
|
|
1246
|
+
* (and applies it). The banner says so and offers nothing -- the
|
|
1247
|
+
* save is the host's, one level up, because it publishes the whole
|
|
1248
|
+
* treedb and not one topic.
|
|
1245
1249
|
***************************************************************/
|
|
1246
|
-
function
|
|
1250
|
+
function draft_banner(gobj, topic)
|
|
1247
1251
|
{
|
|
1248
1252
|
let priv = gobj.priv;
|
|
1249
1253
|
|
|
1250
|
-
if(!
|
|
1254
|
+
if(!topic_is_draft(priv.written, topic)) {
|
|
1251
1255
|
return null;
|
|
1252
1256
|
}
|
|
1253
1257
|
if(is_readonly(gobj)) {
|
|
1254
1258
|
return null;
|
|
1255
1259
|
}
|
|
1256
1260
|
|
|
1257
|
-
|
|
1258
|
-
["div", {class: "
|
|
1261
|
+
return createElement2(
|
|
1262
|
+
["div", {class: "SCHEMA_DRAFT_BANNER notification is-warning is-light p-3 mb-3 " +
|
|
1259
1263
|
"is-flex is-align-items-center", style: "gap:.5rem;"}, [
|
|
1260
1264
|
["span", {class: "icon"}, [["i", {class: "yi-triangle-exclamation"}]]],
|
|
1261
|
-
["span", {class: "
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
type: "button",
|
|
1265
|
-
style: "margin-left:auto;",
|
|
1266
|
-
title: t("raise the version"), "aria-label": t("raise the version"),
|
|
1267
|
-
"data-i18n-title": "raise the version",
|
|
1268
|
-
"data-i18n-aria-label": "raise the version"}, [
|
|
1269
|
-
["span", {class: "icon"}, [["i", {class: "yi-circle-up"}]]],
|
|
1270
|
-
["span", {i18n: "raise"}, t("raise")]
|
|
1271
|
-
]]
|
|
1265
|
+
["span", {class: "SCHEMA_DRAFT_TEXT is-size-7",
|
|
1266
|
+
i18n: "unsaved schema changes: save to publish them"},
|
|
1267
|
+
t("unsaved schema changes: save to publish them")]
|
|
1272
1268
|
]]
|
|
1273
1269
|
);
|
|
1274
|
-
let $bump = $banner.querySelector(".SCHEMA_VERSION_BUMP");
|
|
1275
|
-
$bump.addEventListener("click", (evt) => {
|
|
1276
|
-
evt.stopPropagation();
|
|
1277
|
-
gobj_send_event(gobj, "EV_BUMP_VERSION", {topic: topic.name}, gobj);
|
|
1278
|
-
});
|
|
1279
|
-
return $banner;
|
|
1280
1270
|
}
|
|
1281
1271
|
|
|
1282
1272
|
/***************************************************************
|
|
@@ -2001,6 +1991,8 @@ function open_report(gobj, findings, treedb_id)
|
|
|
2001
1991
|
***************************************************************/
|
|
2002
1992
|
function open_export(gobj, treedb)
|
|
2003
1993
|
{
|
|
1994
|
+
let priv = gobj.priv;
|
|
1995
|
+
let drafts = treedb.topics.some((topic) => topic_is_draft(priv.written, topic));
|
|
2004
1996
|
let c_text = schema_to_c(treedb);
|
|
2005
1997
|
let json_text = JSON.stringify(schema_to_json(treedb), null, 4);
|
|
2006
1998
|
|
|
@@ -2017,11 +2009,20 @@ function open_export(gobj, treedb)
|
|
|
2017
2009
|
["p", {class: "SCHEMA_EXPORT_HELP help mb-2",
|
|
2018
2010
|
i18n: "an edit made here works and lives in no source: this is what to paste back"},
|
|
2019
2011
|
t("an edit made here works and lives in no source: this is what to paste back")],
|
|
2012
|
+
drafts
|
|
2013
|
+
? ["p", {class: "SCHEMA_EXPORT_DRAFTS notification is-warning is-light p-2 mb-2 is-size-7",
|
|
2014
|
+
i18n: "unsaved changes: save first, so the literal carries the versions that publish it"},
|
|
2015
|
+
t("unsaved changes: save first, so the literal carries the versions that publish it")]
|
|
2016
|
+
: ["span", {}, ""],
|
|
2020
2017
|
["textarea", {class: "SCHEMA_EXPORT_TEXT textarea is-small is-family-monospace",
|
|
2021
|
-
rows: "18", readonly: "readonly", spellcheck: "false"
|
|
2018
|
+
rows: "18", readonly: "readonly", spellcheck: "false",
|
|
2019
|
+
title: t("schema"), "data-i18n-title": "schema",
|
|
2020
|
+
"aria-label": t("schema"), "data-i18n-aria-label": "schema"}, c_text],
|
|
2022
2021
|
["div", {class: "SCHEMA_EXPORT_ACTIONS is-flex mt-3",
|
|
2023
2022
|
style: "gap:.5rem; justify-content:flex-end;"}, [
|
|
2024
|
-
["button", {class: "SCHEMA_EXPORT_COPY button", type: "button"
|
|
2023
|
+
["button", {class: "SCHEMA_EXPORT_COPY button", type: "button",
|
|
2024
|
+
title: t("copy"), "data-i18n-title": "copy",
|
|
2025
|
+
"aria-label": t("copy"), "data-i18n-aria-label": "copy"}, [
|
|
2025
2026
|
["span", {class: "icon"}, [["i", {class: "yi-copy"}]]],
|
|
2026
2027
|
["span", {i18n: "copy"}, t("copy")]
|
|
2027
2028
|
]]
|
|
@@ -2240,51 +2241,6 @@ function open_orphans(gobj)
|
|
|
2240
2241
|
|
|
2241
2242
|
|
|
2242
2243
|
|
|
2243
|
-
/***************************************************************
|
|
2244
|
-
* THE TWO VERSIONS A SCHEMA EDIT HAS TO RAISE, and why both.
|
|
2245
|
-
*
|
|
2246
|
-
* `topic_version` is what publishes a change of a topic's
|
|
2247
|
-
* columns: leave it and the persisted `topic_cols.json` masks
|
|
2248
|
-
* the edit, the restart succeeds and nothing moved.
|
|
2249
|
-
*
|
|
2250
|
-
* `schema_version` is what publishes the schema as a whole —
|
|
2251
|
-
* "the stored one wins on ties, and the incoming one has to be
|
|
2252
|
-
* strictly newer to take over" (c_treedb.c). It is safe to
|
|
2253
|
-
* raise: re-projection from C compares `c_schema_version`, the
|
|
2254
|
-
* version of the LITERAL, precisely so that an edit made here
|
|
2255
|
-
* survives every start until a newer literal arrives.
|
|
2256
|
-
*
|
|
2257
|
-
* So every write that changes a schema carries both, and the
|
|
2258
|
-
* operator is never asked to remember either.
|
|
2259
|
-
***************************************************************/
|
|
2260
|
-
function version_writes(gobj, topic)
|
|
2261
|
-
{
|
|
2262
|
-
let treedb = current_treedb(gobj);
|
|
2263
|
-
let writes = [];
|
|
2264
|
-
|
|
2265
|
-
if(topic) {
|
|
2266
|
-
let version = Number(topic.topic_version);
|
|
2267
|
-
if(!isFinite(version)) {
|
|
2268
|
-
version = 0;
|
|
2269
|
-
}
|
|
2270
|
-
writes.push({
|
|
2271
|
-
op: "update", topic_name: T_TOPICS,
|
|
2272
|
-
record: {id: topic.id, topic_version: version + 1}
|
|
2273
|
-
});
|
|
2274
|
-
}
|
|
2275
|
-
if(treedb) {
|
|
2276
|
-
let version = Number(treedb.schema_version);
|
|
2277
|
-
if(!isFinite(version)) {
|
|
2278
|
-
version = 0;
|
|
2279
|
-
}
|
|
2280
|
-
writes.push({
|
|
2281
|
-
op: "update", topic_name: T_TREEDBS,
|
|
2282
|
-
record: {id: treedb.id, schema_version: version + 1}
|
|
2283
|
-
});
|
|
2284
|
-
}
|
|
2285
|
-
return writes;
|
|
2286
|
-
}
|
|
2287
|
-
|
|
2288
2244
|
/***************************************************************
|
|
2289
2245
|
* WHERE THE COLUMNS WERE before this session started moving them.
|
|
2290
2246
|
*
|
|
@@ -2968,7 +2924,7 @@ function ac_save_column(gobj, event, kw, src)
|
|
|
2968
2924
|
op: kw.creating ? "create" : "update",
|
|
2969
2925
|
topic_name: T_COLS,
|
|
2970
2926
|
record: column_record(gobj, topic, col, values)
|
|
2971
|
-
}]
|
|
2927
|
+
}]);
|
|
2972
2928
|
}
|
|
2973
2929
|
|
|
2974
2930
|
function ac_delete_column(gobj, event, kw, src)
|
|
@@ -3026,7 +2982,7 @@ function ac_move_column(gobj, event, kw, src)
|
|
|
3026
2982
|
if(writes.length === 0) {
|
|
3027
2983
|
return 0; /* dropped where it already was */
|
|
3028
2984
|
}
|
|
3029
|
-
return queue_writes(gobj, writes
|
|
2985
|
+
return queue_writes(gobj, writes);
|
|
3030
2986
|
}
|
|
3031
2987
|
|
|
3032
2988
|
/***************************************************************
|
|
@@ -3055,7 +3011,7 @@ function ac_undo_order(gobj, event, kw, src)
|
|
|
3055
3011
|
}
|
|
3056
3012
|
/* Spent: what it named is where the columns are about to be. */
|
|
3057
3013
|
delete priv.order_undo[topic.id];
|
|
3058
|
-
return queue_writes(gobj, writes
|
|
3014
|
+
return queue_writes(gobj, writes);
|
|
3059
3015
|
}
|
|
3060
3016
|
|
|
3061
3017
|
/***************************************************************
|
|
@@ -3127,7 +3083,7 @@ function ac_save_topic(gobj, event, kw, src)
|
|
|
3127
3083
|
op: kw.creating ? "create" : "update",
|
|
3128
3084
|
topic_name: T_TOPICS,
|
|
3129
3085
|
record: topic_record(gobj, treedb, topic, values)
|
|
3130
|
-
}]
|
|
3086
|
+
}]);
|
|
3131
3087
|
}
|
|
3132
3088
|
|
|
3133
3089
|
function ac_delete_topic(gobj, event, kw, src)
|
|
@@ -3148,25 +3104,6 @@ function ac_delete_topic(gobj, event, kw, src)
|
|
|
3148
3104
|
return 0;
|
|
3149
3105
|
}
|
|
3150
3106
|
|
|
3151
|
-
/***************************************************************
|
|
3152
|
-
* Raising a version by hand: what the banner offers when
|
|
3153
|
-
* something else wrote the topic and left its version alone.
|
|
3154
|
-
***************************************************************/
|
|
3155
|
-
function ac_bump_version(gobj, event, kw, src)
|
|
3156
|
-
{
|
|
3157
|
-
let priv = gobj.priv;
|
|
3158
|
-
let topic = find_topic(priv.model, priv.treedb_id, kw.topic);
|
|
3159
|
-
|
|
3160
|
-
if(refuse_if_readonly(gobj, event)) {
|
|
3161
|
-
return -1;
|
|
3162
|
-
}
|
|
3163
|
-
if(!topic) {
|
|
3164
|
-
log_error(`${gobj_short_name(gobj)}: ${event} names no topic: ${kw.topic}`);
|
|
3165
|
-
return -1;
|
|
3166
|
-
}
|
|
3167
|
-
return queue_writes(gobj, version_writes(gobj, topic));
|
|
3168
|
-
}
|
|
3169
|
-
|
|
3170
3107
|
/***************************************************************
|
|
3171
3108
|
* Deleting a leftover.
|
|
3172
3109
|
***************************************************************/
|
|
@@ -3339,7 +3276,7 @@ function ac_confirmed(gobj, event, kw, src)
|
|
|
3339
3276
|
return {op: "delete", topic_name: T_COLS, record: {id: col.id}};
|
|
3340
3277
|
});
|
|
3341
3278
|
writes.push({op: "delete", topic_name: T_TOPICS, record: {id: topic.id}});
|
|
3342
|
-
return queue_writes(gobj, writes
|
|
3279
|
+
return queue_writes(gobj, writes);
|
|
3343
3280
|
}
|
|
3344
3281
|
if(kw.what === "column") {
|
|
3345
3282
|
let topic = find_topic(priv.model, priv.treedb_id, kw.topic);
|
|
@@ -3350,7 +3287,7 @@ function ac_confirmed(gobj, event, kw, src)
|
|
|
3350
3287
|
}
|
|
3351
3288
|
return queue_writes(gobj, [{
|
|
3352
3289
|
op: "delete", topic_name: T_COLS, record: {id: col.id}
|
|
3353
|
-
}]
|
|
3290
|
+
}]);
|
|
3354
3291
|
}
|
|
3355
3292
|
|
|
3356
3293
|
log_error(`${gobj_short_name(gobj)}: ${event} of nothing this view asked about: ${kw.what}`);
|
|
@@ -3450,8 +3387,7 @@ function create_gclass(gclass_name)
|
|
|
3450
3387
|
["EV_DELETE_COLUMN", ac_delete_column, null],
|
|
3451
3388
|
["EV_SAVE_COLUMN", ac_save_column, null],
|
|
3452
3389
|
["EV_MOVE_COLUMN", ac_move_column, null],
|
|
3453
|
-
["EV_UNDO_ORDER", ac_undo_order, null]
|
|
3454
|
-
["EV_BUMP_VERSION", ac_bump_version, null]
|
|
3390
|
+
["EV_UNDO_ORDER", ac_undo_order, null]
|
|
3455
3391
|
])],
|
|
3456
3392
|
|
|
3457
3393
|
/* A write, or a plan of them, in flight. Only the answers and
|
|
@@ -3494,7 +3430,6 @@ function create_gclass(gclass_name)
|
|
|
3494
3430
|
["EV_SAVE_COLUMN", 0],
|
|
3495
3431
|
["EV_MOVE_COLUMN", 0],
|
|
3496
3432
|
["EV_UNDO_ORDER", 0],
|
|
3497
|
-
["EV_BUMP_VERSION", 0],
|
|
3498
3433
|
["EV_VALIDATE", 0],
|
|
3499
3434
|
["EV_EXPORT", 0],
|
|
3500
3435
|
["EV_IMPORT", 0],
|
|
@@ -3549,5 +3484,5 @@ export {
|
|
|
3549
3484
|
register_c_yui_schema_editor,
|
|
3550
3485
|
/* Exported for its test: the rule that decides whether a topic still
|
|
3551
3486
|
* owes a version bump, which is asked in two places. */
|
|
3552
|
-
|
|
3487
|
+
topic_is_draft
|
|
3553
3488
|
};
|
package/src/schema_validate.js
CHANGED
|
@@ -16,10 +16,11 @@
|
|
|
16
16
|
* operator drew does nothing at all. Restarting on
|
|
17
17
|
* this is an outage with no gain.
|
|
18
18
|
* `warning` the schema opens and something is not what it looks
|
|
19
|
-
* like
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
19
|
+
* like.
|
|
20
|
+
*
|
|
21
|
+
* A version is not checked here: an edit is a DRAFT and moves no
|
|
22
|
+
* version, and C_TREEDB's save-schema raises the versions of what
|
|
23
|
+
* changed when it publishes it.
|
|
23
24
|
*
|
|
24
25
|
* Every `code` is an i18n key: the caller renders, this decides.
|
|
25
26
|
*
|
|
@@ -69,8 +70,6 @@ function validate_schema(treedb, options)
|
|
|
69
70
|
{
|
|
70
71
|
let findings = [];
|
|
71
72
|
let opts = options || {};
|
|
72
|
-
let written = Array.isArray(opts.written_topics) ? opts.written_topics : [];
|
|
73
|
-
let baseline = opts.baseline || {};
|
|
74
73
|
|
|
75
74
|
if(!treedb || !Array.isArray(treedb.topics)) {
|
|
76
75
|
return findings;
|
|
@@ -117,17 +116,6 @@ function validate_schema(treedb, options)
|
|
|
117
116
|
}
|
|
118
117
|
}
|
|
119
118
|
|
|
120
|
-
/* A version that did not move republishes nothing: the stored
|
|
121
|
-
* topic_cols.json masks the whole edit and the restart looks
|
|
122
|
-
* like it worked. */
|
|
123
|
-
if(written.indexOf(topic.id) >= 0) {
|
|
124
|
-
let before = baseline[topic.id];
|
|
125
|
-
if(before !== undefined && String(before) === String(topic.topic_version)) {
|
|
126
|
-
add("warning", "topic version not bumped", topic.name, "",
|
|
127
|
-
String(topic.topic_version));
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
119
|
for(let col of topic.cols) {
|
|
132
120
|
let flags = col_flags(col.record);
|
|
133
121
|
let type = col.record ? col.record.type : "";
|
|
@@ -221,34 +221,14 @@ describe("the column itself", () => {
|
|
|
221
221
|
});
|
|
222
222
|
});
|
|
223
223
|
|
|
224
|
-
describe("
|
|
225
|
-
test("a written topic whose topic_version
|
|
224
|
+
describe("a version is not the validator's", () => {
|
|
225
|
+
test("a written topic whose topic_version did not move is NOT reported", () => {
|
|
226
|
+
/* An edit is a draft; save-schema raises the version. */
|
|
226
227
|
const f = validate_schema(schema(), {
|
|
227
228
|
written_topics: ["db.departments"],
|
|
228
229
|
baseline: {"db.departments": 1}
|
|
229
230
|
});
|
|
230
|
-
|
|
231
|
-
expect(v.length).toBe(1);
|
|
232
|
-
expect(v[0].topic).toBe("departments");
|
|
233
|
-
expect(v[0].severity).toBe("warning");
|
|
234
|
-
});
|
|
235
|
-
|
|
236
|
-
test("a bumped version says nothing", () => {
|
|
237
|
-
expect(validate_schema(schema({dep_version: 2}), {
|
|
238
|
-
written_topics: ["db.departments"],
|
|
239
|
-
baseline: {"db.departments": 1}
|
|
240
|
-
})).toEqual([]);
|
|
241
|
-
});
|
|
242
|
-
|
|
243
|
-
test("a topic nobody wrote is not asked about its version", () => {
|
|
244
|
-
expect(validate_schema(schema(), {baseline: {"db.departments": 1}})).toEqual([]);
|
|
245
|
-
});
|
|
246
|
-
|
|
247
|
-
test("a version compared across types still matches (2 vs '2')", () => {
|
|
248
|
-
expect(validate_schema(schema({dep_version: "1"}), {
|
|
249
|
-
written_topics: ["db.departments"],
|
|
250
|
-
baseline: {"db.departments": 1}
|
|
251
|
-
}).length).toBe(1);
|
|
231
|
+
expect(f.filter(x => x.code === "topic version not bumped")).toEqual([]);
|
|
252
232
|
});
|
|
253
233
|
});
|
|
254
234
|
|
|
@@ -1,92 +1,35 @@
|
|
|
1
1
|
/***********************************************************************
|
|
2
2
|
* schema_version_bump.test.js
|
|
3
3
|
*
|
|
4
|
-
* "This topic was written
|
|
4
|
+
* "This topic was written in this session and is not saved."
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* offered a Raise button that wrote another version and was
|
|
12
|
-
* measured against itself again.
|
|
13
|
-
*
|
|
14
|
-
* The banner could not be dismissed. One operator pressed it
|
|
15
|
-
* thirty-three times on a single column delete, and that is why
|
|
16
|
-
* schema versions climbed by tens with the schema unchanged.
|
|
6
|
+
* An edit is a DRAFT (M36 of yunetas' 2026-09-21 review): it moves
|
|
7
|
+
* no version, and the host's save-schema raises the versions of what
|
|
8
|
+
* changed. So "written" alone is the whole question -- the version is
|
|
9
|
+
* no longer measured against a baseline, which is also why the old
|
|
10
|
+
* "Raise" button and the loop it fed are gone.
|
|
17
11
|
*
|
|
18
12
|
* Copyright (c) 2026, ArtGins.
|
|
19
13
|
* All Rights Reserved.
|
|
20
14
|
***********************************************************************/
|
|
21
15
|
import {describe, it, expect} from "vitest";
|
|
22
|
-
import {
|
|
16
|
+
import {topic_is_draft} from "./c_yui_schema_editor.js";
|
|
23
17
|
|
|
24
18
|
const TOPIC = {id: "device_groups", topic_version: 5};
|
|
25
19
|
|
|
26
|
-
describe("
|
|
20
|
+
describe("topic_is_draft", () => {
|
|
27
21
|
it("says nothing about a topic this session never wrote", () => {
|
|
28
|
-
expect(
|
|
22
|
+
expect(topic_is_draft({}, TOPIC)).toBe(false);
|
|
29
23
|
});
|
|
30
24
|
|
|
31
|
-
it("
|
|
32
|
-
expect(
|
|
25
|
+
it("a written topic is a draft, whatever its version says", () => {
|
|
26
|
+
expect(topic_is_draft({device_groups: true}, TOPIC)).toBe(true);
|
|
27
|
+
expect(topic_is_draft({device_groups: true}, {id: "device_groups", topic_version: 6}))
|
|
33
28
|
.toBe(true);
|
|
34
29
|
});
|
|
35
30
|
|
|
36
|
-
it("STOPS asking once the version has moved past the baseline", () => {
|
|
37
|
-
/* The case the loop got wrong: the write raised it to 6 and the
|
|
38
|
-
* baseline still says 5, so there is nothing left to raise. */
|
|
39
|
-
expect(needs_version_bump(
|
|
40
|
-
{device_groups: true}, {device_groups: 5}, {id: "device_groups", topic_version: 6}
|
|
41
|
-
)).toBe(false);
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
it("compares as text, so 5 and '5' are the same version", () => {
|
|
45
|
-
expect(needs_version_bump(
|
|
46
|
-
{device_groups: true}, {device_groups: "5"}, TOPIC
|
|
47
|
-
)).toBe(true);
|
|
48
|
-
expect(needs_version_bump(
|
|
49
|
-
{device_groups: true}, {device_groups: 5}, {id: "device_groups", topic_version: "5"}
|
|
50
|
-
)).toBe(true);
|
|
51
|
-
});
|
|
52
|
-
|
|
53
31
|
it("answers false rather than throwing on a missing half", () => {
|
|
54
|
-
expect(
|
|
55
|
-
expect(
|
|
56
|
-
expect(needs_version_bump({device_groups: true}, {device_groups: 5}, null)).toBe(false);
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
it("a topic with no baseline entry is not owed a bump", () => {
|
|
60
|
-
expect(needs_version_bump({device_groups: true}, {}, TOPIC)).toBe(false);
|
|
61
|
-
});
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
describe("the loop it came from", () => {
|
|
65
|
-
/*
|
|
66
|
-
* What the editor does per bump: raise the version by one. With the
|
|
67
|
-
* baseline left alone, ONE press settles it; with the baseline
|
|
68
|
-
* re-taken each time -- the old bug -- it never settles.
|
|
69
|
-
*/
|
|
70
|
-
function press_raise(state) {
|
|
71
|
-
return {written: state.written, baseline: state.baseline,
|
|
72
|
-
topic: {id: "t", topic_version: Number(state.topic.topic_version) + 1}};
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
it("settles after a single press", () => {
|
|
76
|
-
let st = {written: {t: true}, baseline: {t: 5}, topic: {id: "t", topic_version: 5}};
|
|
77
|
-
expect(needs_version_bump(st.written, st.baseline, st.topic)).toBe(true);
|
|
78
|
-
st = press_raise(st);
|
|
79
|
-
expect(needs_version_bump(st.written, st.baseline, st.topic)).toBe(false);
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
it("never settles if the baseline is re-taken with the version", () => {
|
|
83
|
-
let st = {written: {t: true}, baseline: {t: 5}, topic: {id: "t", topic_version: 5}};
|
|
84
|
-
let presses = 0;
|
|
85
|
-
while(needs_version_bump(st.written, st.baseline, st.topic) && presses < 40) {
|
|
86
|
-
st = press_raise(st);
|
|
87
|
-
st.baseline = {t: st.topic.topic_version}; /* the bug */
|
|
88
|
-
presses++;
|
|
89
|
-
}
|
|
90
|
-
expect(presses).toBe(40); /* it ran to the guard: no way out */
|
|
32
|
+
expect(topic_is_draft(null, TOPIC)).toBe(false);
|
|
33
|
+
expect(topic_is_draft({device_groups: true}, null)).toBe(false);
|
|
91
34
|
});
|
|
92
35
|
});
|