@yuneta/gobj-ui 6.1.1 → 6.2.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 +26 -6
- package/dist/gobj-ui.cjs.js +16 -7
- package/dist/gobj-ui.es.js +16 -7
- package/package.json +1 -1
- package/src/c_yui_node.js +32 -2
- package/src/c_yui_schema_editor.js +5 -3
- package/src/c_yui_treedb_schema.js +17 -7
- package/src/node_tree_model.js +26 -0
- package/src/node_tree_model.test.js +31 -1
package/README.md
CHANGED
|
@@ -201,6 +201,23 @@ seen*:
|
|
|
201
201
|
`projection.path`) while the rest of the tree keeps its tabs. Note the
|
|
202
202
|
asymmetry that makes it a third mode and not a layout: `index` and `chrome`
|
|
203
203
|
project a node's CHILDREN; `path` projects the way in.
|
|
204
|
+
- **`remember_position` — an item points at where you LEFT that child.**
|
|
205
|
+
Without it a nav item points at the canonical route of its child, so a strip
|
|
206
|
+
of children behaves like a row of tabs that forgets: open a topic inside one,
|
|
207
|
+
move to a sibling, come back, and the tab is at its landing — browser Back
|
|
208
|
+
the only way to what was open. With it, the item carries the tail that was
|
|
209
|
+
last active under that child.
|
|
210
|
+
|
|
211
|
+
It stays a **real position**, which is the reason it is done here and not by
|
|
212
|
+
the viewer restoring itself: clicking is a navigation like any other, nothing
|
|
213
|
+
redirects and nothing argues with the url. A bare navigation to a child
|
|
214
|
+
records "its home", so choosing the landing sticks too.
|
|
215
|
+
|
|
216
|
+
Off by default: a tree whose children are pages wants the item to BE the
|
|
217
|
+
destination. On for a tree whose children are workspaces with a position
|
|
218
|
+
inside them — the agent console's strip of treedbs, each with its open topic.
|
|
219
|
+
Since 6.2.0.
|
|
220
|
+
|
|
204
221
|
- **`nav_mode` — the three shapes as one runtime knob.** The two bullets above
|
|
205
222
|
describe what a tree *declares*; `nav_mode` is how a user *chooses* between
|
|
206
223
|
the shapes without the app rewriting anything:
|
|
@@ -539,12 +556,15 @@ schema, so the drawing can be held against the ASCII one in its `.c`.
|
|
|
539
556
|
- Events: `EV_SHOW`, `EV_REBUILD`, `EV_THEME` (restyle — it repaints the G6
|
|
540
557
|
graph in place, preserving the user's zoom/pan), plus `EV_NODE_CLICK`, which
|
|
541
558
|
a node click sends into the FSM. **With a `node_route` the click IS a
|
|
542
|
-
navigation** and this view makes it
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
559
|
+
navigation** and this view makes it. **Without one the click is dropped,
|
|
560
|
+
unless the host asked for it with `with_node_click`** — then it is published
|
|
561
|
+
as `{topic}`, for a host that draws the same picture inside its own screens
|
|
562
|
+
and opens the topic in place, with no hash involved. That host must declare
|
|
563
|
+
`EV_NODE_CLICK` in its own FSM, as with every event a child publishes, which
|
|
564
|
+
is exactly why it is opt-in: the CHILD subscription model subscribes a host
|
|
565
|
+
to ALL of this view's events, so publishing one unasked turns a click into
|
|
566
|
+
"Event NOT DEFINED in state" underneath a host that never wanted it. Since
|
|
567
|
+
6.1.2 (6.1.0 and 6.1.1 published it unconditionally).
|
|
548
568
|
|
|
549
569
|
Barrel-exported and public from 4.0.0. Renders with `@antv/g6`; the cards are
|
|
550
570
|
HTML nodes carrying their own inline colours, so a theme switch repaints them in
|
package/dist/gobj-ui.cjs.js
CHANGED
|
@@ -19831,6 +19831,7 @@ var attrs_table$3 = [
|
|
|
19831
19831
|
(0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_POINTER, "subscriber", 0, null, "Subscriber of output events"),
|
|
19832
19832
|
(0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_JSON, "descs", 0, null, "Treedb schema: {topic_name: desc}"),
|
|
19833
19833
|
(0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_STRING, "node_route", 0, "", "Hash-route template with a {topic} placeholder: a node click opens that topic (e.g. '#/topics/db/<sel>/{topic}')"),
|
|
19834
|
+
(0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_BOOLEAN, "with_node_click", 0, false, "With no `node_route`, PUBLISH a node click as EV_NODE_CLICK instead of dropping it. Opt-in, because a subscriber has to DECLARE the event: the hosts that route by hash, and the ones that want nothing, must not have it appear underneath them"),
|
|
19834
19835
|
(0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_BOOLEAN, "system", 0, false, "Include system topics (__*__) too"),
|
|
19835
19836
|
(0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_POINTER, "$container", 0, null, "Root HTML element"),
|
|
19836
19837
|
(0, _yuneta_gobj_js.SDATA_END)()
|
|
@@ -20235,19 +20236,26 @@ function destroy_graph(gobj) {
|
|
|
20235
20236
|
* With a `node_route` the click IS a navigation and this view
|
|
20236
20237
|
* makes it: that is what the route was handed down for.
|
|
20237
20238
|
*
|
|
20238
|
-
* Without one it is still an action, and it
|
|
20239
|
-
* mounted this view — the schema editor draws the same
|
|
20240
|
-
* inside its own screens, where a topic opens in place
|
|
20241
|
-
* hash is involved.
|
|
20242
|
-
*
|
|
20243
|
-
*
|
|
20239
|
+
* Without one it is still an action, and it can belong to
|
|
20240
|
+
* whoever mounted this view — the schema editor draws the same
|
|
20241
|
+
* picture inside its own screens, where a topic opens in place
|
|
20242
|
+
* and no hash is involved. That host asks for it with
|
|
20243
|
+
* `with_node_click` and DECLARES EV_NODE_CLICK in its own FSM,
|
|
20244
|
+
* as with every event a child publishes.
|
|
20245
|
+
*
|
|
20246
|
+
* It is opt-in and not the default for exactly that reason: the
|
|
20247
|
+
* CHILD subscription model subscribes the host to ALL of this
|
|
20248
|
+
* view's events, so publishing one unasked turns a click into
|
|
20249
|
+
* "Event NOT DEFINED in state" in every host that never wanted
|
|
20250
|
+
* it — the topics view that mounts this as its schema landing,
|
|
20251
|
+
* and the offline demo that mounts it against a json file.
|
|
20244
20252
|
************************************************************/
|
|
20245
20253
|
function ac_node_click$2(gobj, event, kw, src) {
|
|
20246
20254
|
let topic = kw && kw.node_id;
|
|
20247
20255
|
let route = (0, _yuneta_gobj_js.gobj_read_str_attr)(gobj, "node_route");
|
|
20248
20256
|
if (!topic) return 0;
|
|
20249
20257
|
if (!route) {
|
|
20250
|
-
(0, _yuneta_gobj_js.gobj_publish_event)(gobj, "EV_NODE_CLICK", { topic });
|
|
20258
|
+
if ((0, _yuneta_gobj_js.gobj_read_bool_attr)(gobj, "with_node_click")) (0, _yuneta_gobj_js.gobj_publish_event)(gobj, "EV_NODE_CLICK", { topic });
|
|
20251
20259
|
return 0;
|
|
20252
20260
|
}
|
|
20253
20261
|
let href = route.replace("{topic}", topic);
|
|
@@ -23415,6 +23423,7 @@ function render_diagram(gobj) {
|
|
|
23415
23423
|
subscriber: gobj,
|
|
23416
23424
|
descs,
|
|
23417
23425
|
node_route: "",
|
|
23426
|
+
with_node_click: true,
|
|
23418
23427
|
system: true
|
|
23419
23428
|
}, gobj);
|
|
23420
23429
|
if (!diagram) {
|
package/dist/gobj-ui.es.js
CHANGED
|
@@ -19818,6 +19818,7 @@ var attrs_table$3 = [
|
|
|
19818
19818
|
SDATA(data_type_t.DTP_POINTER, "subscriber", 0, null, "Subscriber of output events"),
|
|
19819
19819
|
SDATA(data_type_t.DTP_JSON, "descs", 0, null, "Treedb schema: {topic_name: desc}"),
|
|
19820
19820
|
SDATA(data_type_t.DTP_STRING, "node_route", 0, "", "Hash-route template with a {topic} placeholder: a node click opens that topic (e.g. '#/topics/db/<sel>/{topic}')"),
|
|
19821
|
+
SDATA(data_type_t.DTP_BOOLEAN, "with_node_click", 0, false, "With no `node_route`, PUBLISH a node click as EV_NODE_CLICK instead of dropping it. Opt-in, because a subscriber has to DECLARE the event: the hosts that route by hash, and the ones that want nothing, must not have it appear underneath them"),
|
|
19821
19822
|
SDATA(data_type_t.DTP_BOOLEAN, "system", 0, false, "Include system topics (__*__) too"),
|
|
19822
19823
|
SDATA(data_type_t.DTP_POINTER, "$container", 0, null, "Root HTML element"),
|
|
19823
19824
|
SDATA_END()
|
|
@@ -20222,19 +20223,26 @@ function destroy_graph(gobj) {
|
|
|
20222
20223
|
* With a `node_route` the click IS a navigation and this view
|
|
20223
20224
|
* makes it: that is what the route was handed down for.
|
|
20224
20225
|
*
|
|
20225
|
-
* Without one it is still an action, and it
|
|
20226
|
-
* mounted this view — the schema editor draws the same
|
|
20227
|
-
* inside its own screens, where a topic opens in place
|
|
20228
|
-
* hash is involved.
|
|
20229
|
-
*
|
|
20230
|
-
*
|
|
20226
|
+
* Without one it is still an action, and it can belong to
|
|
20227
|
+
* whoever mounted this view — the schema editor draws the same
|
|
20228
|
+
* picture inside its own screens, where a topic opens in place
|
|
20229
|
+
* and no hash is involved. That host asks for it with
|
|
20230
|
+
* `with_node_click` and DECLARES EV_NODE_CLICK in its own FSM,
|
|
20231
|
+
* as with every event a child publishes.
|
|
20232
|
+
*
|
|
20233
|
+
* It is opt-in and not the default for exactly that reason: the
|
|
20234
|
+
* CHILD subscription model subscribes the host to ALL of this
|
|
20235
|
+
* view's events, so publishing one unasked turns a click into
|
|
20236
|
+
* "Event NOT DEFINED in state" in every host that never wanted
|
|
20237
|
+
* it — the topics view that mounts this as its schema landing,
|
|
20238
|
+
* and the offline demo that mounts it against a json file.
|
|
20231
20239
|
************************************************************/
|
|
20232
20240
|
function ac_node_click$2(gobj, event, kw, src) {
|
|
20233
20241
|
let topic = kw && kw.node_id;
|
|
20234
20242
|
let route = gobj_read_str_attr(gobj, "node_route");
|
|
20235
20243
|
if (!topic) return 0;
|
|
20236
20244
|
if (!route) {
|
|
20237
|
-
gobj_publish_event(gobj, "EV_NODE_CLICK", { topic });
|
|
20245
|
+
if (gobj_read_bool_attr(gobj, "with_node_click")) gobj_publish_event(gobj, "EV_NODE_CLICK", { topic });
|
|
20238
20246
|
return 0;
|
|
20239
20247
|
}
|
|
20240
20248
|
let href = route.replace("{topic}", topic);
|
|
@@ -23402,6 +23410,7 @@ function render_diagram(gobj) {
|
|
|
23402
23410
|
subscriber: gobj,
|
|
23403
23411
|
descs,
|
|
23404
23412
|
node_route: "",
|
|
23413
|
+
with_node_click: true,
|
|
23405
23414
|
system: true
|
|
23406
23415
|
}, gobj);
|
|
23407
23416
|
if (!diagram) {
|
package/package.json
CHANGED
package/src/c_yui_node.js
CHANGED
|
@@ -106,6 +106,7 @@ import {
|
|
|
106
106
|
gobj_parent,
|
|
107
107
|
gobj_gclass_name,
|
|
108
108
|
gobj_read_attr,
|
|
109
|
+
gobj_read_bool_attr,
|
|
109
110
|
gobj_read_pointer_attr,
|
|
110
111
|
gobj_write_attr,
|
|
111
112
|
gobj_subscribe_event,
|
|
@@ -124,6 +125,7 @@ import {
|
|
|
124
125
|
join_route,
|
|
125
126
|
projection_renders,
|
|
126
127
|
child_nav_items,
|
|
128
|
+
nav_route_with_tail,
|
|
127
129
|
chrome_visible,
|
|
128
130
|
normalize_spec,
|
|
129
131
|
is_nav_mode,
|
|
@@ -175,6 +177,7 @@ SDATA(data_type_t.DTP_JSON, "children", 0, null, "Declared child spec
|
|
|
175
177
|
SDATA(data_type_t.DTP_STRING, "base_route", 0, "", "ROOT only: the declared route the whole tree hangs from"),
|
|
176
178
|
SDATA(data_type_t.DTP_STRING, "tree_version", 0, "", "ROOT only: version of the tree CONTRACT (its paths are public urls)"),
|
|
177
179
|
SDATA(data_type_t.DTP_STRING, "nav_mode", 0, "stack","ROOT only: how the way in is shown — stack|back|path"),
|
|
180
|
+
SDATA(data_type_t.DTP_BOOLEAN, "remember_position", 0, false, "A nav item points at WHERE THE OPERATOR LEFT that child, not at its bare route: the tail last active under each child is remembered and appended. For a tree whose children are workspaces with a position inside them (a treedb and its open topic); off for one whose children are pages, where the item IS the destination"),
|
|
178
181
|
|
|
179
182
|
SDATA(data_type_t.DTP_POINTER, "$container", 0, null, "Root HTMLElement (shell view contract)"),
|
|
180
183
|
SDATA_END()
|
|
@@ -187,6 +190,7 @@ let PRIVATE_DATA = {
|
|
|
187
190
|
zone_sig: null, /* their last item signature */
|
|
188
191
|
content_gobj: null,
|
|
189
192
|
active_child: null, /* child NODE gobj currently on the path */
|
|
193
|
+
remembered: null, /* child id -> tail last active under it */
|
|
190
194
|
chrome_depth: null, /* effective for the active path (null = all) */
|
|
191
195
|
distance: 0, /* segments from me down to the tip */
|
|
192
196
|
chain: null, /* nodes from my child down to the tip */
|
|
@@ -224,6 +228,7 @@ function mt_create(gobj)
|
|
|
224
228
|
gobj_subscribe_event(gobj, null, {}, subscriber);
|
|
225
229
|
|
|
226
230
|
priv.children = [];
|
|
231
|
+
priv.remembered = {};
|
|
227
232
|
priv.navs = [];
|
|
228
233
|
priv.zone_navs = {};
|
|
229
234
|
priv.zone_sig = {};
|
|
@@ -652,7 +657,16 @@ function render_projection(gobj, mode, $where, active_route, active_id, zones_on
|
|
|
652
657
|
disabled: gobj_read_attr(child, "disabled")
|
|
653
658
|
});
|
|
654
659
|
}
|
|
655
|
-
|
|
660
|
+
/* A nav item points at the child's canonical route, or — when this
|
|
661
|
+
* tree remembers — at where the operator left that child. Same
|
|
662
|
+
* contract either way: what the item carries is a real position,
|
|
663
|
+
* so clicking it is a navigation and not a restore that would then
|
|
664
|
+
* have to argue with the url. */
|
|
665
|
+
let remember = gobj_read_bool_attr(gobj, "remember_position");
|
|
666
|
+
let items = child_nav_items(specs, (id) => {
|
|
667
|
+
let route = `${base}/${id}`;
|
|
668
|
+
return remember ? nav_route_with_tail(route, priv.remembered[id]) : route;
|
|
669
|
+
});
|
|
656
670
|
|
|
657
671
|
let i = 0;
|
|
658
672
|
for(let render of renders) {
|
|
@@ -714,7 +728,11 @@ function project_into_zone(gobj, render, items, active_route, active_id)
|
|
|
714
728
|
{
|
|
715
729
|
let priv = gobj.priv;
|
|
716
730
|
let key = `${render.zone}|${render.layout}|${render.show_on || ""}`;
|
|
717
|
-
|
|
731
|
+
/* The route is part of it: with `remember_position` an item's id
|
|
732
|
+
* never changes while its destination does, so a signature of ids
|
|
733
|
+
* alone would leave a persisted zone nav pointing at where the
|
|
734
|
+
* operator was three navigations ago. */
|
|
735
|
+
let sig = items.map((it) => `${it.id}=${it.route}`).join(",");
|
|
718
736
|
let nav = priv.zone_navs[key];
|
|
719
737
|
|
|
720
738
|
if(!nav) {
|
|
@@ -989,6 +1007,18 @@ function activate(gobj, subpath, inherited_depth)
|
|
|
989
1007
|
priv.chrome_depth = info.chrome_depth;
|
|
990
1008
|
priv.chain = info.chain;
|
|
991
1009
|
|
|
1010
|
+
/* WHERE THE OPERATOR IS INSIDE THIS CHILD, recorded before anything
|
|
1011
|
+
* is drawn, because the strip drawn on this very activation is the
|
|
1012
|
+
* one that has to point back here.
|
|
1013
|
+
*
|
|
1014
|
+
* An empty tail is recorded too, and that is not the same as not
|
|
1015
|
+
* recording: navigating to a child's bare route IS the operator
|
|
1016
|
+
* choosing its home, and the item must stop pointing at the topic
|
|
1017
|
+
* they left three navigations ago. */
|
|
1018
|
+
if(head) {
|
|
1019
|
+
priv.remembered[head] = tail || "";
|
|
1020
|
+
}
|
|
1021
|
+
|
|
992
1022
|
/* Structure ends here: whatever is left of the url is data, and it
|
|
993
1023
|
* belongs to the viewer. Re-render only the first time — paging
|
|
994
1024
|
* through data must not rebuild the DOM under the viewer. */
|
|
@@ -1389,9 +1389,11 @@ function render_diagram(gobj)
|
|
|
1389
1389
|
subscriber: gobj,
|
|
1390
1390
|
descs: descs,
|
|
1391
1391
|
/* No `node_route`: this diagram lives inside the editor and
|
|
1392
|
-
* a node click is answered
|
|
1393
|
-
* would have to own
|
|
1394
|
-
|
|
1392
|
+
* a node click is answered HERE, not by a hash the shell
|
|
1393
|
+
* would have to own — which is what `with_node_click` asks
|
|
1394
|
+
* for, and why EV_NODE_CLICK is declared in this FSM. */
|
|
1395
|
+
node_route: "",
|
|
1396
|
+
with_node_click: true,
|
|
1395
1397
|
system: true
|
|
1396
1398
|
},
|
|
1397
1399
|
gobj
|
|
@@ -94,6 +94,7 @@ const attrs_table = [
|
|
|
94
94
|
SDATA(data_type_t.DTP_POINTER, "subscriber", 0, null, "Subscriber of output events"),
|
|
95
95
|
SDATA(data_type_t.DTP_JSON, "descs", 0, null, "Treedb schema: {topic_name: desc}"),
|
|
96
96
|
SDATA(data_type_t.DTP_STRING, "node_route", 0, "", "Hash-route template with a {topic} placeholder: a node click opens that topic (e.g. '#/topics/db/<sel>/{topic}')"),
|
|
97
|
+
SDATA(data_type_t.DTP_BOOLEAN, "with_node_click", 0, false, "With no `node_route`, PUBLISH a node click as EV_NODE_CLICK instead of dropping it. Opt-in, because a subscriber has to DECLARE the event: the hosts that route by hash, and the ones that want nothing, must not have it appear underneath them"),
|
|
97
98
|
SDATA(data_type_t.DTP_BOOLEAN, "system", 0, false, "Include system topics (__*__) too"),
|
|
98
99
|
SDATA(data_type_t.DTP_POINTER, "$container", 0, null, "Root HTML element"),
|
|
99
100
|
SDATA_END()
|
|
@@ -622,12 +623,19 @@ function destroy_graph(gobj)
|
|
|
622
623
|
* With a `node_route` the click IS a navigation and this view
|
|
623
624
|
* makes it: that is what the route was handed down for.
|
|
624
625
|
*
|
|
625
|
-
* Without one it is still an action, and it
|
|
626
|
-
* mounted this view — the schema editor draws the same
|
|
627
|
-
* inside its own screens, where a topic opens in place
|
|
628
|
-
* hash is involved.
|
|
629
|
-
*
|
|
630
|
-
*
|
|
626
|
+
* Without one it is still an action, and it can belong to
|
|
627
|
+
* whoever mounted this view — the schema editor draws the same
|
|
628
|
+
* picture inside its own screens, where a topic opens in place
|
|
629
|
+
* and no hash is involved. That host asks for it with
|
|
630
|
+
* `with_node_click` and DECLARES EV_NODE_CLICK in its own FSM,
|
|
631
|
+
* as with every event a child publishes.
|
|
632
|
+
*
|
|
633
|
+
* It is opt-in and not the default for exactly that reason: the
|
|
634
|
+
* CHILD subscription model subscribes the host to ALL of this
|
|
635
|
+
* view's events, so publishing one unasked turns a click into
|
|
636
|
+
* "Event NOT DEFINED in state" in every host that never wanted
|
|
637
|
+
* it — the topics view that mounts this as its schema landing,
|
|
638
|
+
* and the offline demo that mounts it against a json file.
|
|
631
639
|
************************************************************/
|
|
632
640
|
function ac_node_click(gobj, event, kw, src)
|
|
633
641
|
{
|
|
@@ -638,7 +646,9 @@ function ac_node_click(gobj, event, kw, src)
|
|
|
638
646
|
return 0;
|
|
639
647
|
}
|
|
640
648
|
if(!route) {
|
|
641
|
-
|
|
649
|
+
if(gobj_read_bool_attr(gobj, "with_node_click")) {
|
|
650
|
+
gobj_publish_event(gobj, "EV_NODE_CLICK", {topic: topic});
|
|
651
|
+
}
|
|
642
652
|
return 0;
|
|
643
653
|
}
|
|
644
654
|
let href = route.replace("{topic}", topic);
|
package/src/node_tree_model.js
CHANGED
|
@@ -169,6 +169,32 @@ export function projection_renders(projection, mode)
|
|
|
169
169
|
* `route_of(id)` returns the canonical route of a child — the
|
|
170
170
|
* node supplies it, so this file stays route-agnostic.
|
|
171
171
|
************************************************************/
|
|
172
|
+
/***************************************************************
|
|
173
|
+
* nav_route_with_tail(route, tail) -> where that item goes
|
|
174
|
+
*
|
|
175
|
+
* A nav item normally points at the canonical route of a
|
|
176
|
+
* child. When the tree remembers positions it points at
|
|
177
|
+
* WHERE THE OPERATOR LEFT that child instead: the child's
|
|
178
|
+
* route plus the tail last active under it.
|
|
179
|
+
*
|
|
180
|
+
* It is the difference between a strip of treedbs that
|
|
181
|
+
* behaves like a row of tabs and one that drops what each
|
|
182
|
+
* tab had open every time you come back to it.
|
|
183
|
+
*
|
|
184
|
+
* An empty tail is not a position: it means the operator was
|
|
185
|
+
* at the child's own home, which is the canonical route.
|
|
186
|
+
***************************************************************/
|
|
187
|
+
export function nav_route_with_tail(route, tail)
|
|
188
|
+
{
|
|
189
|
+
let base = String(route || "");
|
|
190
|
+
let rest = String(tail || "").replace(/^\/+/, "").replace(/\/+$/, "");
|
|
191
|
+
|
|
192
|
+
if(!rest) {
|
|
193
|
+
return base;
|
|
194
|
+
}
|
|
195
|
+
return base.replace(/\/+$/, "") + "/" + rest;
|
|
196
|
+
}
|
|
197
|
+
|
|
172
198
|
export function child_nav_items(children, route_of)
|
|
173
199
|
{
|
|
174
200
|
let items = [];
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Unit tests for the pure node-tree helpers.
|
|
5
5
|
***********************************************************************/
|
|
6
|
-
import { test, expect } from "vitest";
|
|
6
|
+
import { describe, test, expect } from "vitest";
|
|
7
7
|
import {
|
|
8
8
|
chrome_visible,
|
|
9
9
|
split_subpath,
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
join_route,
|
|
12
12
|
projection_renders,
|
|
13
13
|
child_nav_items,
|
|
14
|
+
nav_route_with_tail,
|
|
14
15
|
normalize_spec,
|
|
15
16
|
is_nav_mode,
|
|
16
17
|
nav_mode_renders,
|
|
@@ -350,3 +351,32 @@ test("back is depth 1 and path is depth 0, whatever the tree declared", () => {
|
|
|
350
351
|
expect(nav_mode_depth(null, "stack")).toBe(null);
|
|
351
352
|
expect(nav_mode_depth(2, "stack")).toBe(2);
|
|
352
353
|
});
|
|
354
|
+
|
|
355
|
+
describe("nav_route_with_tail — a nav item that points where you left", () => {
|
|
356
|
+
test("no tail is the canonical route: the child's own home", () => {
|
|
357
|
+
expect(nav_route_with_tail("/schemas/db", "")).toBe("/schemas/db");
|
|
358
|
+
expect(nav_route_with_tail("/schemas/db", null)).toBe("/schemas/db");
|
|
359
|
+
expect(nav_route_with_tail("/schemas/db", undefined)).toBe("/schemas/db");
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
test("a tail is appended, which is the whole point", () => {
|
|
363
|
+
expect(nav_route_with_tail("/schemas/db", "users")).toBe("/schemas/db/users");
|
|
364
|
+
expect(nav_route_with_tail("/schemas/db", "users/info"))
|
|
365
|
+
.toBe("/schemas/db/users/info");
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
test("it never doubles a slash, whichever side carries one", () => {
|
|
369
|
+
expect(nav_route_with_tail("/schemas/db/", "users")).toBe("/schemas/db/users");
|
|
370
|
+
expect(nav_route_with_tail("/schemas/db", "/users")).toBe("/schemas/db/users");
|
|
371
|
+
expect(nav_route_with_tail("/schemas/db/", "/users/")).toBe("/schemas/db/users");
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
test("a tail of only slashes is no tail", () => {
|
|
375
|
+
expect(nav_route_with_tail("/schemas/db", "/")).toBe("/schemas/db");
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
test("no route at all is not a crash", () => {
|
|
379
|
+
expect(nav_route_with_tail("", "users")).toBe("/users");
|
|
380
|
+
expect(nav_route_with_tail(null, null)).toBe("");
|
|
381
|
+
});
|
|
382
|
+
});
|