@yuneta/gobj-ui 5.14.2 → 5.16.0
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 +39 -0
- package/dist/gobj-ui.cjs.js +179 -17
- package/dist/gobj-ui.es.js +179 -17
- package/package.json +1 -1
- package/src/c_yui_treedb_graph.js +99 -1
- package/src/c_yui_treedb_topic_with_form.js +87 -7
- package/src/c_yui_treedb_topics.js +39 -5
- package/src/treedb_write_plan.js +73 -0
- package/src/treedb_write_plan.test.js +83 -0
|
@@ -76,6 +76,8 @@ import {yui_tabulator_lang, yui_tabulator_relocalize} from "./yui_tabulator_i18n
|
|
|
76
76
|
|
|
77
77
|
import {t} from "i18next";
|
|
78
78
|
|
|
79
|
+
import {plan_treedb_writes} from "./treedb_write_plan.js";
|
|
80
|
+
|
|
79
81
|
import "./c_yui_treedb_topic_with_form.css";
|
|
80
82
|
import "./tabulator.css";
|
|
81
83
|
|
|
@@ -97,6 +99,7 @@ SDATA(data_type_t.DTP_STRING, "topic_name", 0, null, "Topic name"
|
|
|
97
99
|
SDATA(data_type_t.DTP_POINTER, "desc", 0, null, "Description of topics"),
|
|
98
100
|
|
|
99
101
|
/*---------------- Edition Mode ----------------*/
|
|
102
|
+
SDATA(data_type_t.DTP_BOOLEAN, "readonly", 0, false, "The topic cannot be written: no edition mode, no new/delete/paste, no in-row icons, and the record form opens without its save toolbar. Set it when the treedb is not the master of its tranger (only the master can write; the yuno refuses otherwise), or when the user lacks the authz"),
|
|
100
103
|
SDATA(data_type_t.DTP_BOOLEAN, "with_edition_mode", 0, true, "Enable EDITION mode showing EDIT Button toolbar"),
|
|
101
104
|
SDATA(data_type_t.DTP_BOOLEAN, "with_new_button", 0, true, "Button toolbar NEW"),
|
|
102
105
|
SDATA(data_type_t.DTP_BOOLEAN, "with_delete_button", 0, true, "Button toolbar DELETE"),
|
|
@@ -343,11 +346,16 @@ function build_ui(gobj)
|
|
|
343
346
|
// TODO pon autorización, solo si está autorizado a modificar los datos!!!
|
|
344
347
|
// TODO deja que estos botones se queden en el top cuando se hace scroll (clip ?)
|
|
345
348
|
let $table_toolbar = [];
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
+
/* One plan decides every write affordance (treedb_write_plan.js):
|
|
350
|
+
* `readonly` is the STATE of the topic and beats each with_* flag,
|
|
351
|
+
* and stating that in five `!readonly &&` expressions is five places
|
|
352
|
+
* to forget the sixth. */
|
|
353
|
+
let plan = write_plan(gobj);
|
|
354
|
+
let with_edition_mode = plan.edition_mode;
|
|
355
|
+
let with_new_button = plan.new_button;
|
|
356
|
+
let with_delete_button = plan.delete_button;
|
|
349
357
|
let with_copy_button = gobj_read_bool_attr(gobj, "with_copy_button");
|
|
350
|
-
let with_paste_button =
|
|
358
|
+
let with_paste_button = plan.paste_button;
|
|
351
359
|
|
|
352
360
|
let toolbar_id = gobj_read_str_attr(gobj, "toolbar_id");
|
|
353
361
|
|
|
@@ -884,7 +892,7 @@ function create_tabulator(gobj)
|
|
|
884
892
|
].join('');
|
|
885
893
|
}
|
|
886
894
|
|
|
887
|
-
let with_in_row_edit_icons =
|
|
895
|
+
let with_in_row_edit_icons = write_plan(gobj).in_row_icons;
|
|
888
896
|
if(with_in_row_edit_icons) {
|
|
889
897
|
columns.push({
|
|
890
898
|
field: '_operation',
|
|
@@ -1306,6 +1314,7 @@ function open_form_dialog(gobj, mode, record)
|
|
|
1306
1314
|
style: 'display:flex; flex-direction:column; height:100%;'}, []]
|
|
1307
1315
|
);
|
|
1308
1316
|
|
|
1317
|
+
let form_plan = write_plan(gobj);
|
|
1309
1318
|
let form = gobj_create_pure_child(
|
|
1310
1319
|
"form_" + clean_name(gobj_name(gobj)),
|
|
1311
1320
|
"C_YUI_FORM",
|
|
@@ -1314,13 +1323,18 @@ function open_form_dialog(gobj, mode, record)
|
|
|
1314
1323
|
record: record,
|
|
1315
1324
|
fkey_options: build_fkey_options(gobj),
|
|
1316
1325
|
form_mode: mode,
|
|
1326
|
+
/* A read-only topic still OPENS its form -- looking at a record
|
|
1327
|
+
* is the point of a replica -- with the cells not editable and
|
|
1328
|
+
* the write half of the toolbar gone. A null plan leaves the
|
|
1329
|
+
* form's own default alone. */
|
|
1330
|
+
...(form_plan.form_toolbar ? {toolbar: form_plan.form_toolbar} : {}),
|
|
1317
1331
|
/* Editing the raw topic record: structured cols
|
|
1318
1332
|
* (template / table / coordinates) are raw JSON editors,
|
|
1319
1333
|
* not interpreted into sub-widgets (the pre-merge behaviour). */
|
|
1320
1334
|
render_mode: "edit",
|
|
1321
1335
|
pkey: desc.pkey || "id",
|
|
1322
1336
|
topic_name: topic_name,
|
|
1323
|
-
editable:
|
|
1337
|
+
editable: !form_plan.readonly,
|
|
1324
1338
|
$parent: $body
|
|
1325
1339
|
},
|
|
1326
1340
|
gobj
|
|
@@ -2066,6 +2080,42 @@ function show_dropdown_popup_menu(gobj, x, y, items, callback)
|
|
|
2066
2080
|
|
|
2067
2081
|
|
|
2068
2082
|
|
|
2083
|
+
/***************************************************************
|
|
2084
|
+
* A read-only topic refuses the write EVENTS too, not only their
|
|
2085
|
+
* buttons. The buttons are gone, but an event can still arrive: a
|
|
2086
|
+
* keyboard path, a hosted form that outlived the flag, a caller that
|
|
2087
|
+
* sends EV_SAVE_RECORD itself. Refusing here is what makes `readonly`
|
|
2088
|
+
* a state of the gclass rather than a way of drawing it -- and it
|
|
2089
|
+
* fails LOUDLY, because an ignored write is how the backend used to
|
|
2090
|
+
* behave and what this whole change exists to stop.
|
|
2091
|
+
***************************************************************/
|
|
2092
|
+
function write_plan(gobj)
|
|
2093
|
+
{
|
|
2094
|
+
return plan_treedb_writes({
|
|
2095
|
+
readonly: gobj_read_bool_attr(gobj, "readonly"),
|
|
2096
|
+
with_edition_mode: gobj_read_bool_attr(gobj, "with_edition_mode"),
|
|
2097
|
+
with_new_button: gobj_read_bool_attr(gobj, "with_new_button"),
|
|
2098
|
+
with_delete_button: gobj_read_bool_attr(gobj, "with_delete_button"),
|
|
2099
|
+
with_paste_button: gobj_read_bool_attr(gobj, "with_paste_button"),
|
|
2100
|
+
with_in_row_edit_icons: gobj_read_bool_attr(gobj, "with_in_row_edit_icons")
|
|
2101
|
+
});
|
|
2102
|
+
}
|
|
2103
|
+
|
|
2104
|
+
/***************************************************************
|
|
2105
|
+
* A read-only topic refuses the write EVENTS too, not only their
|
|
2106
|
+
* buttons.
|
|
2107
|
+
***************************************************************/
|
|
2108
|
+
function refuse_if_readonly(gobj, event)
|
|
2109
|
+
{
|
|
2110
|
+
if(!gobj_read_bool_attr(gobj, "readonly")) {
|
|
2111
|
+
return false;
|
|
2112
|
+
}
|
|
2113
|
+
log_error(`${gobj_short_name(gobj)}: ${event} refused, topic ` +
|
|
2114
|
+
`'${gobj_read_str_attr(gobj, "topic_name")}' is READ-ONLY`);
|
|
2115
|
+
return true;
|
|
2116
|
+
}
|
|
2117
|
+
|
|
2118
|
+
|
|
2069
2119
|
/***************************
|
|
2070
2120
|
* Actions
|
|
2071
2121
|
***************************/
|
|
@@ -2192,7 +2242,22 @@ function ac_load_node_updated(gobj, event, kw, src)
|
|
|
2192
2242
|
let tabulator = gobj_read_attr(gobj, "tabulator");
|
|
2193
2243
|
if(tabulator) {
|
|
2194
2244
|
for(let record of data) {
|
|
2195
|
-
|
|
2245
|
+
/*
|
|
2246
|
+
* updateData() REJECTS the promise on a row it cannot find and
|
|
2247
|
+
* nobody awaits it, so the rejection surfaces as an unhandled
|
|
2248
|
+
* "Update Error - Unable to find row" that names neither the
|
|
2249
|
+
* gclass nor the topic. Ask first, like ac_node_deleted does.
|
|
2250
|
+
*
|
|
2251
|
+
* A table nobody has opened holds NO rows, and a node event for
|
|
2252
|
+
* it is not news: its rows are read when the topic is shown. A
|
|
2253
|
+
* row missing from a table that IS loaded is.
|
|
2254
|
+
*/
|
|
2255
|
+
if(tabulator.getRow(record.id)) {
|
|
2256
|
+
tabulator.updateData([record]);
|
|
2257
|
+
} else if(tabulator.getDataCount() > 0) {
|
|
2258
|
+
log_error(`${gobj_short_name(gobj)}: updated node is not in ` +
|
|
2259
|
+
`the loaded table: ${record.id}`);
|
|
2260
|
+
}
|
|
2196
2261
|
}
|
|
2197
2262
|
}
|
|
2198
2263
|
|
|
@@ -2242,6 +2307,9 @@ function ac_node_deleted(gobj, event, kw, src)
|
|
|
2242
2307
|
************************************************************/
|
|
2243
2308
|
function ac_edition_mode(gobj, event, kw, src)
|
|
2244
2309
|
{
|
|
2310
|
+
if(refuse_if_readonly(gobj, event)) {
|
|
2311
|
+
return -1; /* Error already logged */
|
|
2312
|
+
}
|
|
2245
2313
|
let tabulator = gobj_read_attr(gobj, "tabulator");
|
|
2246
2314
|
|
|
2247
2315
|
/*
|
|
@@ -2307,6 +2375,9 @@ function ac_edition_mode(gobj, event, kw, src)
|
|
|
2307
2375
|
************************************************************/
|
|
2308
2376
|
function ac_new_row(gobj, event, kw, src)
|
|
2309
2377
|
{
|
|
2378
|
+
if(refuse_if_readonly(gobj, event)) {
|
|
2379
|
+
return -1; /* Error already logged */
|
|
2380
|
+
}
|
|
2310
2381
|
/*
|
|
2311
2382
|
* Build default values. TODO no debería estar en desc configuration?
|
|
2312
2383
|
*/
|
|
@@ -2352,6 +2423,9 @@ function ac_new_row(gobj, event, kw, src)
|
|
|
2352
2423
|
************************************************************/
|
|
2353
2424
|
function ac_delete_rows(gobj, event, kw, src)
|
|
2354
2425
|
{
|
|
2426
|
+
if(refuse_if_readonly(gobj, event)) {
|
|
2427
|
+
return -1; /* Error already logged */
|
|
2428
|
+
}
|
|
2355
2429
|
let tabulator = gobj_read_attr(gobj, "tabulator");
|
|
2356
2430
|
|
|
2357
2431
|
if(json_size(kw) === 0) {
|
|
@@ -2451,6 +2525,9 @@ function ac_copy_rows(gobj, event, kw, src)
|
|
|
2451
2525
|
************************************************************/
|
|
2452
2526
|
function ac_paste_rows(gobj, event, kw, src)
|
|
2453
2527
|
{
|
|
2528
|
+
if(refuse_if_readonly(gobj, event)) {
|
|
2529
|
+
return -1; /* Error already logged */
|
|
2530
|
+
}
|
|
2454
2531
|
/*----------------------------*
|
|
2455
2532
|
* Paste rows
|
|
2456
2533
|
*----------------------------*/
|
|
@@ -2478,6 +2555,9 @@ function ac_paste_rows(gobj, event, kw, src)
|
|
|
2478
2555
|
************************************************************/
|
|
2479
2556
|
function ac_form_save_record(gobj, event, kw, src)
|
|
2480
2557
|
{
|
|
2558
|
+
if(refuse_if_readonly(gobj, event)) {
|
|
2559
|
+
return -1; /* Error already logged */
|
|
2560
|
+
}
|
|
2481
2561
|
let mode = gobj_read_str_attr(src, "form_mode");
|
|
2482
2562
|
gobj_publish_event(
|
|
2483
2563
|
gobj,
|
|
@@ -77,6 +77,7 @@ SDATA(data_type_t.DTP_JSON, "descs", 0, null, "Description of
|
|
|
77
77
|
SDATA(data_type_t.DTP_BOOLEAN, "system", 0, false, "Manage system topics (true) or user topics (false)"),
|
|
78
78
|
SDATA(data_type_t.DTP_STRING, "tabs_style", 0, "is-toggle is-fullwidth", "Bulma tab styling"),
|
|
79
79
|
SDATA(data_type_t.DTP_BOOLEAN, "with_cards_landing",0, false, "Land on a grid of topic cards (list->detail): a card opens its table, with the tabs bar + a back-to-grid button. Off = tabs only (legacy)."),
|
|
80
|
+
SDATA(data_type_t.DTP_BOOLEAN, "readonly", 0, false, "The whole treedb is read-only: every topic opens without its write affordances and the write events are refused. Set it when this yuno is not the MASTER of the treedb's tranger -- only the master can write, and the yuno answers 'READ-ONLY' to a write since SDK 7.13.0 (ask `treedb-info`)"),
|
|
80
81
|
SDATA(data_type_t.DTP_JSON, "card_action_routes",0, null, "Per-card hash-route templates {info, table, graph} with a {topic} placeholder (host-supplied, route-agnostic). Present ⇒ cards show 3 icon actions; absent ⇒ a single card that opens the table."),
|
|
81
82
|
SDATA(data_type_t.DTP_JSON, "landing_routes", 0, null, "Host-supplied hashes for the two landing sub-views {cards, schema}; the toggle navigates to them so the landing is URL-addressable (ROUTING.md). Absent ⇒ toggle flips in-view only (legacy)."),
|
|
82
83
|
SDATA(data_type_t.DTP_STRING, "base_route", 0, "", "This view's base route (host-supplied); used to declare its sub-routes (topics / info / schema) to the site map (ROUTING.md contributor)."),
|
|
@@ -997,6 +998,7 @@ function process_treedb_descs(gobj)
|
|
|
997
998
|
let descs = gobj_read_attr(gobj, "descs");
|
|
998
999
|
let system = gobj_read_bool_attr(gobj, "system");
|
|
999
1000
|
let treedb_name = gobj_read_str_attr(gobj, "treedb_name");
|
|
1001
|
+
let readonly = gobj_read_bool_attr(gobj, "readonly");
|
|
1000
1002
|
for(const [key, desc] of Object.entries(descs)) {
|
|
1001
1003
|
if(system) {
|
|
1002
1004
|
} else {
|
|
@@ -1008,10 +1010,13 @@ function process_treedb_descs(gobj)
|
|
|
1008
1010
|
let kw_topic_form = {
|
|
1009
1011
|
subscriber: gobj, // HACK get all output events
|
|
1010
1012
|
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1013
|
+
/* One flag, not three: a read-only treedb has no writable
|
|
1014
|
+
* topic. (The old TODO here asked for the authz to decide the
|
|
1015
|
+
* buttons; the authz is a per-user question the backend answers
|
|
1016
|
+
* per command, while this is a property of the STORE.) */
|
|
1017
|
+
readonly: readonly,
|
|
1018
|
+
with_new_button: !readonly,
|
|
1019
|
+
with_delete_button: !readonly,
|
|
1015
1020
|
|
|
1016
1021
|
treedb_name: treedb_name,
|
|
1017
1022
|
topic_name: key,
|
|
@@ -1619,6 +1624,25 @@ function request_print_tranger(gobj, path)
|
|
|
1619
1624
|
|
|
1620
1625
|
|
|
1621
1626
|
|
|
1627
|
+
/***************************************************************
|
|
1628
|
+
* The last gate before a write leaves for the yuno. The per-topic
|
|
1629
|
+
* children already hide their write buttons when the treedb is
|
|
1630
|
+
* read-only, but the record events converge HERE, and this is the
|
|
1631
|
+
* gclass that owns the treedb: a write that got this far on a replica
|
|
1632
|
+
* would travel, be refused by the yuno, and come back as a toast --
|
|
1633
|
+
* after the table had already drawn the row.
|
|
1634
|
+
***************************************************************/
|
|
1635
|
+
function refuse_if_readonly(gobj, event)
|
|
1636
|
+
{
|
|
1637
|
+
if(!gobj_read_bool_attr(gobj, "readonly")) {
|
|
1638
|
+
return false;
|
|
1639
|
+
}
|
|
1640
|
+
log_error(`${gobj_short_name(gobj)}: ${event} refused, treedb ` +
|
|
1641
|
+
`'${gobj_read_str_attr(gobj, "treedb_name")}' is READ-ONLY`);
|
|
1642
|
+
return true;
|
|
1643
|
+
}
|
|
1644
|
+
|
|
1645
|
+
|
|
1622
1646
|
/***************************
|
|
1623
1647
|
* Actions
|
|
1624
1648
|
***************************/
|
|
@@ -1731,7 +1755,8 @@ function ac_mt_command_answer(gobj, event, kw, src)
|
|
|
1731
1755
|
treedb_name: kw_get_str(gobj, kw_command, "treedb_name", "", 0),
|
|
1732
1756
|
topic_name: kw_get_str(gobj, kw_command, "topic_name", "", 0),
|
|
1733
1757
|
record: kw_get_dict(gobj, kw_command, "record", {}, 0),
|
|
1734
|
-
created: (command === "create-node")
|
|
1758
|
+
created: (command === "create-node"),
|
|
1759
|
+
command: command
|
|
1735
1760
|
});
|
|
1736
1761
|
break;
|
|
1737
1762
|
|
|
@@ -2051,6 +2076,9 @@ function ac_treedb_node_deleted(gobj, event, kw, src)
|
|
|
2051
2076
|
********************************************/
|
|
2052
2077
|
function ac_create_record(gobj, event, kw, src)
|
|
2053
2078
|
{
|
|
2079
|
+
if(refuse_if_readonly(gobj, event)) {
|
|
2080
|
+
return -1; /* Error already logged */
|
|
2081
|
+
}
|
|
2054
2082
|
let treedb_name = gobj_read_str_attr(gobj, "treedb_name");
|
|
2055
2083
|
let topic_name = gobj_read_attr(src, "topic_name");
|
|
2056
2084
|
let record = kw.record;
|
|
@@ -2079,6 +2107,9 @@ function ac_create_record(gobj, event, kw, src)
|
|
|
2079
2107
|
********************************************/
|
|
2080
2108
|
function ac_update_record(gobj, event, kw, src)
|
|
2081
2109
|
{
|
|
2110
|
+
if(refuse_if_readonly(gobj, event)) {
|
|
2111
|
+
return -1; /* Error already logged */
|
|
2112
|
+
}
|
|
2082
2113
|
let treedb_name = gobj_read_str_attr(gobj, "treedb_name");
|
|
2083
2114
|
let topic_name = gobj_read_attr(src, "topic_name");
|
|
2084
2115
|
let record = kw.record;
|
|
@@ -2106,6 +2137,9 @@ function ac_update_record(gobj, event, kw, src)
|
|
|
2106
2137
|
********************************************/
|
|
2107
2138
|
function ac_delete_record(gobj, event, kw, src)
|
|
2108
2139
|
{
|
|
2140
|
+
if(refuse_if_readonly(gobj, event)) {
|
|
2141
|
+
return -1; /* Error already logged */
|
|
2142
|
+
}
|
|
2109
2143
|
let treedb_name = gobj_read_str_attr(gobj, "treedb_name");
|
|
2110
2144
|
let topic_name = gobj_read_attr(src, "topic_name");
|
|
2111
2145
|
let record = kw.record;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/***********************************************************************
|
|
2
|
+
* treedb_write_plan.js
|
|
3
|
+
*
|
|
4
|
+
* Which write affordances a treedb topic offers.
|
|
5
|
+
*
|
|
6
|
+
* Pure, so it can be tested without a DOM: the gclass turns the
|
|
7
|
+
* plan into buttons and columns, this decides the plan. Same split
|
|
8
|
+
* as form_toolbar_plan.js.
|
|
9
|
+
*
|
|
10
|
+
* The rule it exists to state ONCE: `readonly` is not one more
|
|
11
|
+
* button flag, it is the STATE of the topic and it wins over all of
|
|
12
|
+
* them. A treedb whose tranger this yuno does not master answers
|
|
13
|
+
* every write with "READ-ONLY" (the yuno refuses since SDK 7.13.0),
|
|
14
|
+
* so offering the buttons anyway only turns a fact into an error
|
|
15
|
+
* message per click. Written as `!readonly && with_x` in five
|
|
16
|
+
* places it is five places to forget the sixth.
|
|
17
|
+
*
|
|
18
|
+
* Copyright (c) 2026, ArtGins.
|
|
19
|
+
* All Rights Reserved.
|
|
20
|
+
***********************************************************************/
|
|
21
|
+
|
|
22
|
+
/* What a read-only form keeps: reading a record includes taking it with
|
|
23
|
+
* you, so `copy` stays and the write half goes. */
|
|
24
|
+
const READONLY_FORM_TOOLBAR = ["copy"];
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
/***************************************************************
|
|
28
|
+
* plan_treedb_writes(flags) -> plan
|
|
29
|
+
*
|
|
30
|
+
* flags {readonly, with_edition_mode, with_new_button,
|
|
31
|
+
* with_delete_button, with_paste_button,
|
|
32
|
+
* with_in_row_edit_icons}
|
|
33
|
+
* Every with_* defaults to TRUE when absent, which is
|
|
34
|
+
* what the gclass attrs default to: a caller that sets
|
|
35
|
+
* nothing gets the toolbar this view always had.
|
|
36
|
+
*
|
|
37
|
+
* plan the same names without the `with_` prefix, plus
|
|
38
|
+
* `form_toolbar`: the list to hand C_YUI_FORM, or null
|
|
39
|
+
* to leave the form's own default alone.
|
|
40
|
+
***************************************************************/
|
|
41
|
+
function plan_treedb_writes(flags)
|
|
42
|
+
{
|
|
43
|
+
let f = flags || {};
|
|
44
|
+
let readonly = !!f.readonly;
|
|
45
|
+
let want = (name) => {
|
|
46
|
+
return (f[name] === undefined) ? true : !!f[name];
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
if(readonly) {
|
|
50
|
+
return {
|
|
51
|
+
readonly: true,
|
|
52
|
+
edition_mode: false,
|
|
53
|
+
new_button: false,
|
|
54
|
+
delete_button: false,
|
|
55
|
+
paste_button: false,
|
|
56
|
+
in_row_icons: false,
|
|
57
|
+
form_toolbar: READONLY_FORM_TOOLBAR.slice()
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
readonly: false,
|
|
63
|
+
edition_mode: want("with_edition_mode"),
|
|
64
|
+
new_button: want("with_new_button"),
|
|
65
|
+
delete_button: want("with_delete_button"),
|
|
66
|
+
paste_button: want("with_paste_button"),
|
|
67
|
+
in_row_icons: want("with_in_row_edit_icons"),
|
|
68
|
+
form_toolbar: null
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
export {plan_treedb_writes, READONLY_FORM_TOOLBAR};
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/***********************************************************************
|
|
2
|
+
* treedb_write_plan.test.js
|
|
3
|
+
*
|
|
4
|
+
* What a read-only treedb topic must NOT offer, pinned.
|
|
5
|
+
*
|
|
6
|
+
* The point of the file under test is that one flag beats five, so
|
|
7
|
+
* these tests are mostly about the ways someone could get a write
|
|
8
|
+
* affordance back by accident.
|
|
9
|
+
***********************************************************************/
|
|
10
|
+
import { describe, test, expect } from "vitest";
|
|
11
|
+
import { plan_treedb_writes, READONLY_FORM_TOOLBAR } from "./treedb_write_plan.js";
|
|
12
|
+
|
|
13
|
+
const WRITES = ["edition_mode", "new_button", "delete_button", "paste_button", "in_row_icons"];
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
describe("writable (the default)", () => {
|
|
17
|
+
test("nothing set is everything on — the toolbar this view always had", () => {
|
|
18
|
+
const plan = plan_treedb_writes({});
|
|
19
|
+
for(const k of WRITES) {
|
|
20
|
+
expect(plan[k]).toBe(true);
|
|
21
|
+
}
|
|
22
|
+
expect(plan.readonly).toBe(false);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test("an absent flags object behaves the same", () => {
|
|
26
|
+
expect(plan_treedb_writes(undefined).new_button).toBe(true);
|
|
27
|
+
expect(plan_treedb_writes(null).delete_button).toBe(true);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test("the form keeps its OWN default toolbar (null, not a copy of it)", () => {
|
|
31
|
+
/* Repeating the five names here is how the default drifts from
|
|
32
|
+
* C_YUI_FORM's; null says "do not touch it". */
|
|
33
|
+
expect(plan_treedb_writes({}).form_toolbar).toBe(null);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test("a single with_* off turns off only that one", () => {
|
|
37
|
+
const plan = plan_treedb_writes({with_delete_button: false});
|
|
38
|
+
expect(plan.delete_button).toBe(false);
|
|
39
|
+
expect(plan.new_button).toBe(true);
|
|
40
|
+
expect(plan.edition_mode).toBe(true);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
describe("readonly beats every with_*", () => {
|
|
46
|
+
test("all write affordances off, even when each is explicitly asked for", () => {
|
|
47
|
+
const plan = plan_treedb_writes({
|
|
48
|
+
readonly: true,
|
|
49
|
+
with_edition_mode: true,
|
|
50
|
+
with_new_button: true,
|
|
51
|
+
with_delete_button: true,
|
|
52
|
+
with_paste_button: true,
|
|
53
|
+
with_in_row_edit_icons: true
|
|
54
|
+
});
|
|
55
|
+
for(const k of WRITES) {
|
|
56
|
+
expect(plan[k]).toBe(false);
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test("the form opens with copy only — reading includes taking the record", () => {
|
|
61
|
+
expect(plan_treedb_writes({readonly: true}).form_toolbar).toEqual(["copy"]);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test("the plan hands out a COPY of the toolbar, so a caller cannot mutate the constant", () => {
|
|
65
|
+
const plan = plan_treedb_writes({readonly: true});
|
|
66
|
+
plan.form_toolbar.push("save");
|
|
67
|
+
expect(READONLY_FORM_TOOLBAR).toEqual(["copy"]);
|
|
68
|
+
expect(plan_treedb_writes({readonly: true}).form_toolbar).toEqual(["copy"]);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test("readonly is reported, so the gclass can refuse the EVENTS too", () => {
|
|
72
|
+
/* Hiding the buttons is not the same as refusing the write: an
|
|
73
|
+
* event can still arrive from a keyboard path or a stale form. */
|
|
74
|
+
expect(plan_treedb_writes({readonly: true}).readonly).toBe(true);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test("only a truthy readonly counts — a missing flag is writable", () => {
|
|
78
|
+
expect(plan_treedb_writes({readonly: false}).new_button).toBe(true);
|
|
79
|
+
expect(plan_treedb_writes({readonly: 0}).new_button).toBe(true);
|
|
80
|
+
expect(plan_treedb_writes({readonly: undefined}).new_button).toBe(true);
|
|
81
|
+
expect(plan_treedb_writes({readonly: "yes"}).new_button).toBe(false);
|
|
82
|
+
});
|
|
83
|
+
});
|