@yuneta/gobj-ui 7.26.32 → 7.26.34
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/dist/gobj-ui.cjs.js +22 -5
- package/dist/gobj-ui.es.js +22 -5
- package/package.json +1 -1
- package/src/c_yui_form.js +53 -7
- package/src/form_table_validator.wiring.test.js +192 -0
- package/src/form_template_field.wiring.test.js +181 -0
package/dist/gobj-ui.cjs.js
CHANGED
|
@@ -10415,6 +10415,7 @@ function template2columns(gobj, columns, template, sub_elements) {
|
|
|
10415
10415
|
else return value;
|
|
10416
10416
|
};
|
|
10417
10417
|
if (field_desc.is_required) validator.push("required");
|
|
10418
|
+
if ((0, _yuneta_gobj_js.is_object)(value) && value.validator) validator.push(...Array.isArray(value.validator) ? value.validator : [value.validator]);
|
|
10418
10419
|
switch (field_desc.type) {
|
|
10419
10420
|
case "object":
|
|
10420
10421
|
case "dict": break;
|
|
@@ -10631,12 +10632,20 @@ function validate_form(gobj, $form) {
|
|
|
10631
10632
|
mark_field_validity($editor);
|
|
10632
10633
|
}
|
|
10633
10634
|
});
|
|
10635
|
+
let $first_bad = null;
|
|
10636
|
+
function table_valid(tabulator) {
|
|
10637
|
+
let r = tabulator.validate();
|
|
10638
|
+
if (r === true) return true;
|
|
10639
|
+
if (!$first_bad && Array.isArray(r) && r.length > 0) $first_bad = r[0].getElement();
|
|
10640
|
+
return false;
|
|
10641
|
+
}
|
|
10634
10642
|
$form.querySelectorAll(".yui-tabulator-table").forEach(($table) => {
|
|
10635
|
-
if (
|
|
10643
|
+
if (!table_valid($table.tabulator)) validated = false;
|
|
10636
10644
|
$table.querySelectorAll(".yui-tabulator-table-nested").forEach(($table_nested) => {
|
|
10637
|
-
if (
|
|
10645
|
+
if (!table_valid($table_nested.tabulator)) validated = false;
|
|
10638
10646
|
});
|
|
10639
10647
|
});
|
|
10648
|
+
if ($first_bad) $first_bad.scrollIntoView({ block: "nearest" });
|
|
10640
10649
|
return validated;
|
|
10641
10650
|
}
|
|
10642
10651
|
/************************************************************
|
|
@@ -10711,7 +10720,9 @@ function get_form_values(gobj, $form) {
|
|
|
10711
10720
|
else value = value_.text;
|
|
10712
10721
|
}
|
|
10713
10722
|
break;
|
|
10714
|
-
case "fieldset":
|
|
10723
|
+
case "fieldset":
|
|
10724
|
+
value = {};
|
|
10725
|
+
break;
|
|
10715
10726
|
case "table":
|
|
10716
10727
|
let template = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "template");
|
|
10717
10728
|
if (!(0, _yuneta_gobj_js.empty_string)(name) && (0, _yuneta_gobj_js.kw_has_key)(template, name)) {
|
|
@@ -10733,7 +10744,7 @@ function get_form_values(gobj, $form) {
|
|
|
10733
10744
|
break;
|
|
10734
10745
|
default: (0, _yuneta_gobj_js.log_error)((0, _yuneta_gobj_js.sprintf)("%s: $form type unknown: '%s'", (0, _yuneta_gobj_js.gobj_short_name)(gobj), tag));
|
|
10735
10746
|
}
|
|
10736
|
-
value = form_value_2_treedb_value(gobj, field_desc, value);
|
|
10747
|
+
if (tag !== "fieldset") value = form_value_2_treedb_value(gobj, field_desc, value);
|
|
10737
10748
|
let path;
|
|
10738
10749
|
if ((0, _yuneta_gobj_js.empty_string)(prefix)) path = name;
|
|
10739
10750
|
else path = prefix + "`" + name;
|
|
@@ -10754,7 +10765,13 @@ function clear_data(gobj, $form) {
|
|
|
10754
10765
|
} else if ($input.type === "checkbox") $input.checked = false;
|
|
10755
10766
|
else if ($input.type === "radio") $input.checked = false;
|
|
10756
10767
|
else if ($input.type === "datetime-local") $input.value = null;
|
|
10757
|
-
else if ($input.type === "
|
|
10768
|
+
else if ($input.type === "color") {
|
|
10769
|
+
$input.value = "#000000";
|
|
10770
|
+
if ($input.yui_color_value) {
|
|
10771
|
+
$input.yui_color_value.textContent = $input.value;
|
|
10772
|
+
$input.yui_color_value.title = $input.value;
|
|
10773
|
+
}
|
|
10774
|
+
} else if ($input.type === "select-multiple") $input.tom_select.clear();
|
|
10758
10775
|
else {
|
|
10759
10776
|
$input.value = null;
|
|
10760
10777
|
if ($input.yui_svg_preview) $input.yui_svg_preview();
|
package/dist/gobj-ui.es.js
CHANGED
|
@@ -10410,6 +10410,7 @@ function template2columns(gobj, columns, template, sub_elements) {
|
|
|
10410
10410
|
else return value;
|
|
10411
10411
|
};
|
|
10412
10412
|
if (field_desc.is_required) validator.push("required");
|
|
10413
|
+
if (is_object(value) && value.validator) validator.push(...Array.isArray(value.validator) ? value.validator : [value.validator]);
|
|
10413
10414
|
switch (field_desc.type) {
|
|
10414
10415
|
case "object":
|
|
10415
10416
|
case "dict": break;
|
|
@@ -10626,12 +10627,20 @@ function validate_form(gobj, $form) {
|
|
|
10626
10627
|
mark_field_validity($editor);
|
|
10627
10628
|
}
|
|
10628
10629
|
});
|
|
10630
|
+
let $first_bad = null;
|
|
10631
|
+
function table_valid(tabulator) {
|
|
10632
|
+
let r = tabulator.validate();
|
|
10633
|
+
if (r === true) return true;
|
|
10634
|
+
if (!$first_bad && Array.isArray(r) && r.length > 0) $first_bad = r[0].getElement();
|
|
10635
|
+
return false;
|
|
10636
|
+
}
|
|
10629
10637
|
$form.querySelectorAll(".yui-tabulator-table").forEach(($table) => {
|
|
10630
|
-
if (
|
|
10638
|
+
if (!table_valid($table.tabulator)) validated = false;
|
|
10631
10639
|
$table.querySelectorAll(".yui-tabulator-table-nested").forEach(($table_nested) => {
|
|
10632
|
-
if (
|
|
10640
|
+
if (!table_valid($table_nested.tabulator)) validated = false;
|
|
10633
10641
|
});
|
|
10634
10642
|
});
|
|
10643
|
+
if ($first_bad) $first_bad.scrollIntoView({ block: "nearest" });
|
|
10635
10644
|
return validated;
|
|
10636
10645
|
}
|
|
10637
10646
|
/************************************************************
|
|
@@ -10706,7 +10715,9 @@ function get_form_values(gobj, $form) {
|
|
|
10706
10715
|
else value = value_.text;
|
|
10707
10716
|
}
|
|
10708
10717
|
break;
|
|
10709
|
-
case "fieldset":
|
|
10718
|
+
case "fieldset":
|
|
10719
|
+
value = {};
|
|
10720
|
+
break;
|
|
10710
10721
|
case "table":
|
|
10711
10722
|
let template = gobj_read_attr(gobj, "template");
|
|
10712
10723
|
if (!empty_string(name) && kw_has_key(template, name)) {
|
|
@@ -10728,7 +10739,7 @@ function get_form_values(gobj, $form) {
|
|
|
10728
10739
|
break;
|
|
10729
10740
|
default: log_error(sprintf("%s: $form type unknown: '%s'", gobj_short_name(gobj), tag));
|
|
10730
10741
|
}
|
|
10731
|
-
value = form_value_2_treedb_value(gobj, field_desc, value);
|
|
10742
|
+
if (tag !== "fieldset") value = form_value_2_treedb_value(gobj, field_desc, value);
|
|
10732
10743
|
let path;
|
|
10733
10744
|
if (empty_string(prefix)) path = name;
|
|
10734
10745
|
else path = prefix + "`" + name;
|
|
@@ -10749,7 +10760,13 @@ function clear_data(gobj, $form) {
|
|
|
10749
10760
|
} else if ($input.type === "checkbox") $input.checked = false;
|
|
10750
10761
|
else if ($input.type === "radio") $input.checked = false;
|
|
10751
10762
|
else if ($input.type === "datetime-local") $input.value = null;
|
|
10752
|
-
else if ($input.type === "
|
|
10763
|
+
else if ($input.type === "color") {
|
|
10764
|
+
$input.value = "#000000";
|
|
10765
|
+
if ($input.yui_color_value) {
|
|
10766
|
+
$input.yui_color_value.textContent = $input.value;
|
|
10767
|
+
$input.yui_color_value.title = $input.value;
|
|
10768
|
+
}
|
|
10769
|
+
} else if ($input.type === "select-multiple") $input.tom_select.clear();
|
|
10753
10770
|
else {
|
|
10754
10771
|
$input.value = null;
|
|
10755
10772
|
if ($input.yui_svg_preview) $input.yui_svg_preview();
|
package/package.json
CHANGED
package/src/c_yui_form.js
CHANGED
|
@@ -2273,6 +2273,15 @@ function template2columns(gobj, columns, template, sub_elements)
|
|
|
2273
2273
|
validator.push("required");
|
|
2274
2274
|
}
|
|
2275
2275
|
|
|
2276
|
+
/* What a host knows and a schema cannot say: a template item may
|
|
2277
|
+
* carry Tabulator validators of its own -- a validator's name, or
|
|
2278
|
+
* a function of (cell, value, parameters) -- that its column runs
|
|
2279
|
+
* besides `required`. A template that comes as json carries none;
|
|
2280
|
+
* the host adds them to its copy before handing it on. */
|
|
2281
|
+
if(is_object(value) && value.validator) {
|
|
2282
|
+
validator.push(...(Array.isArray(value.validator)? value.validator : [value.validator]));
|
|
2283
|
+
}
|
|
2284
|
+
|
|
2276
2285
|
// TODO add the validators needed, like validator.push("integer");
|
|
2277
2286
|
|
|
2278
2287
|
switch(field_desc.type) {
|
|
@@ -2658,18 +2667,38 @@ function validate_form(gobj, $form)
|
|
|
2658
2667
|
}
|
|
2659
2668
|
});
|
|
2660
2669
|
|
|
2670
|
+
/* Tabulator's validate() answers `true`, or the LIST of the cells that
|
|
2671
|
+
* failed -- and a list is truthy, so `!validate()` never refused a
|
|
2672
|
+
* table: a cell left empty in a `required` column was saved. Tabulator
|
|
2673
|
+
* marks every bad cell; the first one is brought into view. */
|
|
2674
|
+
let $first_bad = null;
|
|
2675
|
+
function table_valid(tabulator) {
|
|
2676
|
+
let r = tabulator.validate();
|
|
2677
|
+
if(r === true) {
|
|
2678
|
+
return true;
|
|
2679
|
+
}
|
|
2680
|
+
if(!$first_bad && Array.isArray(r) && r.length > 0) {
|
|
2681
|
+
$first_bad = r[0].getElement();
|
|
2682
|
+
}
|
|
2683
|
+
return false;
|
|
2684
|
+
}
|
|
2685
|
+
|
|
2661
2686
|
$form.querySelectorAll('.yui-tabulator-table').forEach($table => {
|
|
2662
|
-
if(
|
|
2687
|
+
if(!table_valid($table.tabulator)) {
|
|
2663
2688
|
validated = false;
|
|
2664
2689
|
}
|
|
2665
2690
|
|
|
2666
2691
|
$table.querySelectorAll('.yui-tabulator-table-nested').forEach($table_nested => {
|
|
2667
|
-
if(
|
|
2692
|
+
if(!table_valid($table_nested.tabulator)) {
|
|
2668
2693
|
validated = false;
|
|
2669
2694
|
}
|
|
2670
2695
|
});
|
|
2671
2696
|
});
|
|
2672
2697
|
|
|
2698
|
+
if($first_bad) {
|
|
2699
|
+
$first_bad.scrollIntoView({block: "nearest"});
|
|
2700
|
+
}
|
|
2701
|
+
|
|
2673
2702
|
return validated;
|
|
2674
2703
|
}
|
|
2675
2704
|
|
|
@@ -2767,10 +2796,13 @@ function get_form_values(gobj, $form)
|
|
|
2767
2796
|
break;
|
|
2768
2797
|
|
|
2769
2798
|
case "fieldset":
|
|
2770
|
-
/*
|
|
2771
|
-
*
|
|
2772
|
-
*
|
|
2773
|
-
|
|
2799
|
+
/* A NODE of the template, not a leaf: its value is the
|
|
2800
|
+
* dict its own fields fill, and they come after it in the
|
|
2801
|
+
* document. No json anybody typed, so it skips the
|
|
2802
|
+
* conversion below, which handed its null on (7.26.8 to
|
|
2803
|
+
* 7.26.32): the fields found no dict to write into and the
|
|
2804
|
+
* save stored the node as null. */
|
|
2805
|
+
value = {};
|
|
2774
2806
|
break;
|
|
2775
2807
|
|
|
2776
2808
|
case "table":
|
|
@@ -2811,7 +2843,9 @@ function get_form_values(gobj, $form)
|
|
|
2811
2843
|
break;
|
|
2812
2844
|
}
|
|
2813
2845
|
|
|
2814
|
-
|
|
2846
|
+
if(tag !== "fieldset") {
|
|
2847
|
+
value = form_value_2_treedb_value(gobj, field_desc, value);
|
|
2848
|
+
}
|
|
2815
2849
|
let path;
|
|
2816
2850
|
if(empty_string(prefix)) {
|
|
2817
2851
|
path = name;
|
|
@@ -2846,6 +2880,18 @@ function clear_data(gobj, $form)
|
|
|
2846
2880
|
} else if($input.type === 'datetime-local') { /* time */
|
|
2847
2881
|
$input.value = null;
|
|
2848
2882
|
|
|
2883
|
+
} else if($input.type === 'color') {
|
|
2884
|
+
/* A colour input holds a colour: the "" a null is written
|
|
2885
|
+
* as is refused, the browser keeps its black, and Chrome
|
|
2886
|
+
* says so on the console, one line per field each time
|
|
2887
|
+
* the form is filled. Black, then, and the readout beside
|
|
2888
|
+
* the swatch told (writing `.value` fires nothing). */
|
|
2889
|
+
$input.value = "#000000";
|
|
2890
|
+
if($input.yui_color_value) {
|
|
2891
|
+
$input.yui_color_value.textContent = $input.value;
|
|
2892
|
+
$input.yui_color_value.title = $input.value;
|
|
2893
|
+
}
|
|
2894
|
+
|
|
2849
2895
|
} else if($input.type === 'select-multiple') {
|
|
2850
2896
|
$input.tom_select.clear();
|
|
2851
2897
|
} else {
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
/***********************************************************************
|
|
2
|
+
* form_table_validator.wiring.test.js
|
|
3
|
+
*
|
|
4
|
+
* A table field of a C_YUI_FORM ('exec' mode) and its validators.
|
|
5
|
+
*
|
|
6
|
+
* - A template item may carry Tabulator validators of its own
|
|
7
|
+
* (`validator`: a name or a function), which its column runs
|
|
8
|
+
* besides `required`: what a host knows and a schema cannot say
|
|
9
|
+
* (estadodelaire refuses a trigger value that its measure can
|
|
10
|
+
* never match).
|
|
11
|
+
* - A table that does not validate refuses the save. Tabulator's
|
|
12
|
+
* validate() answers `true` or the LIST of the bad cells, and
|
|
13
|
+
* validate_form() asked `!validate()`, which a list never is: a
|
|
14
|
+
* `required` cell left empty was saved.
|
|
15
|
+
*
|
|
16
|
+
* Driven through the real gclass on a document double, with a
|
|
17
|
+
* Tabulator double that keeps its settings and answers validate()
|
|
18
|
+
* as told.
|
|
19
|
+
*
|
|
20
|
+
* Copyright (c) 2026, ArtGins.
|
|
21
|
+
* All Rights Reserved.
|
|
22
|
+
***********************************************************************/
|
|
23
|
+
import {describe, test, expect, beforeAll, beforeEach, vi} from "vitest";
|
|
24
|
+
import {install_dom_double} from "../test/dom_double.js";
|
|
25
|
+
|
|
26
|
+
install_dom_double();
|
|
27
|
+
|
|
28
|
+
if(typeof globalThis.ResizeObserver === "undefined") {
|
|
29
|
+
globalThis.ResizeObserver = class {
|
|
30
|
+
observe() {}
|
|
31
|
+
unobserve() {}
|
|
32
|
+
disconnect() {}
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const tables = vi.hoisted(() => []);
|
|
37
|
+
vi.mock("tabulator-tables", () => {
|
|
38
|
+
class FakeTabulator {
|
|
39
|
+
constructor($el, settings) {
|
|
40
|
+
this.$el = $el;
|
|
41
|
+
this.settings = settings;
|
|
42
|
+
this.externalEvents = {events: {}};
|
|
43
|
+
this.initialized = true;
|
|
44
|
+
this.answer = true; // what validate() says
|
|
45
|
+
this.validated = 0;
|
|
46
|
+
tables.push(this);
|
|
47
|
+
return new Proxy(this, {
|
|
48
|
+
get(target, prop) {
|
|
49
|
+
if(prop in target) {
|
|
50
|
+
return target[prop];
|
|
51
|
+
}
|
|
52
|
+
return () => [];
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
on(ev, fn) {
|
|
57
|
+
(this.externalEvents.events[ev] = this.externalEvents.events[ev] || []).push(fn);
|
|
58
|
+
}
|
|
59
|
+
off(ev) {
|
|
60
|
+
delete this.externalEvents.events[ev];
|
|
61
|
+
}
|
|
62
|
+
validate() {
|
|
63
|
+
this.validated++;
|
|
64
|
+
return this.answer;
|
|
65
|
+
}
|
|
66
|
+
getRows() {
|
|
67
|
+
return [];
|
|
68
|
+
}
|
|
69
|
+
destroy() {
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return {TabulatorFull: FakeTabulator, Tabulator: FakeTabulator};
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
const {
|
|
76
|
+
SDATA_END, gclass_create,
|
|
77
|
+
gobj_start_up, gobj_create_yuno, gobj_create_pure_child,
|
|
78
|
+
gobj_start, gobj_send_event,
|
|
79
|
+
set_log_callback,
|
|
80
|
+
} = await import("@yuneta/gobj-js");
|
|
81
|
+
const {register_c_yui_form} = await import("./c_yui_form.js");
|
|
82
|
+
|
|
83
|
+
const saved = [];
|
|
84
|
+
let yuno = null;
|
|
85
|
+
|
|
86
|
+
beforeAll(async () => {
|
|
87
|
+
const i18next = (await import("i18next")).default;
|
|
88
|
+
await i18next.init({lng: "en", resources: {}});
|
|
89
|
+
gobj_start_up(null, null, null, null, null, null, null);
|
|
90
|
+
set_log_callback(() => {});
|
|
91
|
+
gclass_create("C_TEST_FORM_HOST", [], [["ST_IDLE", []]], {}, 0, [SDATA_END()], {}, 0, 0, 0, 0);
|
|
92
|
+
gclass_create(
|
|
93
|
+
"C_TEST_FORM_APP",
|
|
94
|
+
[["EV_SAVE_RECORD", 0], ["EV_RECORD_CHANGED", 0]],
|
|
95
|
+
[["ST_IDLE", [
|
|
96
|
+
["EV_SAVE_RECORD", (gobj, event, kw) => {
|
|
97
|
+
saved.push(kw);
|
|
98
|
+
return 0;
|
|
99
|
+
}, null],
|
|
100
|
+
["EV_RECORD_CHANGED", () => 0, null],
|
|
101
|
+
]]],
|
|
102
|
+
{}, 0, [SDATA_END()], {}, 0, 0, 0, 0
|
|
103
|
+
);
|
|
104
|
+
register_c_yui_form();
|
|
105
|
+
yuno = gobj_create_yuno("form_validator_yuno", "C_TEST_FORM_HOST", {});
|
|
106
|
+
gobj_start(yuno);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
beforeEach(() => {
|
|
110
|
+
saved.length = 0;
|
|
111
|
+
tables.length = 0;
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
let seq = 0;
|
|
115
|
+
|
|
116
|
+
/* The alarms of an estadodelaire device, cut down: a table whose
|
|
117
|
+
* `value` column carries a validator the host added. */
|
|
118
|
+
function build(validator)
|
|
119
|
+
{
|
|
120
|
+
const value = {
|
|
121
|
+
type: "string",
|
|
122
|
+
flag: ["writable", "persistent"]
|
|
123
|
+
};
|
|
124
|
+
if(validator) {
|
|
125
|
+
value.validator = validator;
|
|
126
|
+
}
|
|
127
|
+
const app = gobj_create_pure_child(`vapp${++seq}`, "C_TEST_FORM_APP", {}, yuno);
|
|
128
|
+
gobj_start(app);
|
|
129
|
+
const form = gobj_create_pure_child(`vform${seq}`, "C_YUI_FORM", {
|
|
130
|
+
template: {
|
|
131
|
+
triggers: {
|
|
132
|
+
type: "array",
|
|
133
|
+
flag: ["persistent", "writable", "table"],
|
|
134
|
+
table: {
|
|
135
|
+
id: {
|
|
136
|
+
type: "string",
|
|
137
|
+
flag: ["writable", "persistent", "required"]
|
|
138
|
+
},
|
|
139
|
+
value: value
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
record: {triggers: []},
|
|
144
|
+
editable: true,
|
|
145
|
+
}, app);
|
|
146
|
+
gobj_start(form);
|
|
147
|
+
expect(tables.length).toBe(1);
|
|
148
|
+
return {form, table: tables[0]};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function column(table, field)
|
|
152
|
+
{
|
|
153
|
+
return table.settings.columns.find((c) => c.field === field);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
describe("a table field's validators", () => {
|
|
157
|
+
test("a template item's validator runs in its column, besides required", () => {
|
|
158
|
+
const fits = () => true;
|
|
159
|
+
const {table} = build(fits);
|
|
160
|
+
expect(column(table, "id").validator).toEqual(["required"]);
|
|
161
|
+
expect(column(table, "value").validator).toEqual([fits]);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
test("a list of them, names and functions, is taken whole", () => {
|
|
165
|
+
const fits = () => true;
|
|
166
|
+
const {table} = build(["numeric", fits]);
|
|
167
|
+
expect(column(table, "value").validator).toEqual(["numeric", fits]);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
test("an item without one keeps what the schema says", () => {
|
|
171
|
+
const {table} = build(null);
|
|
172
|
+
expect(column(table, "value").validator).toEqual([]);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
test("a table with a bad cell refuses the save, and shows the cell", () => {
|
|
176
|
+
const {form, table} = build(null);
|
|
177
|
+
const $cell = document.createElement("div");
|
|
178
|
+
let shown = 0;
|
|
179
|
+
$cell.scrollIntoView = () => {
|
|
180
|
+
shown++;
|
|
181
|
+
};
|
|
182
|
+
table.answer = [{getElement: () => $cell}]; // what Tabulator answers
|
|
183
|
+
gobj_send_event(form, "EV_SAVE_RECORD", {}, form);
|
|
184
|
+
expect(table.validated).toBe(1);
|
|
185
|
+
expect(saved).toEqual([]);
|
|
186
|
+
expect(shown).toBe(1);
|
|
187
|
+
|
|
188
|
+
table.answer = true;
|
|
189
|
+
gobj_send_event(form, "EV_SAVE_RECORD", {}, form);
|
|
190
|
+
expect(saved.length).toBe(1);
|
|
191
|
+
});
|
|
192
|
+
});
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/***********************************************************************
|
|
2
|
+
* form_template_field.wiring.test.js
|
|
3
|
+
*
|
|
4
|
+
* A `template` field of a C_YUI_FORM in 'exec' mode is a sub-form
|
|
5
|
+
* (a <fieldset>): a NODE of the record, whose value is the dict its
|
|
6
|
+
* own fields fill. From 7.26.8 to 7.26.32 the node went through the
|
|
7
|
+
* json conversion, which handed its null on: the fields below found
|
|
8
|
+
* no dict to write into (kw_set_dict_value() refuses a null segment)
|
|
9
|
+
* and the save stored the node as null -- the alarm colours of a
|
|
10
|
+
* device in estadodelaire, wiped by every save of its settings.
|
|
11
|
+
* Driven through the real gclass on a document double, with the
|
|
12
|
+
* template of those settings.
|
|
13
|
+
*
|
|
14
|
+
* Copyright (c) 2026, ArtGins.
|
|
15
|
+
* All Rights Reserved.
|
|
16
|
+
***********************************************************************/
|
|
17
|
+
import {describe, test, expect, beforeAll, beforeEach} from "vitest";
|
|
18
|
+
import {install_dom_double} from "../test/dom_double.js";
|
|
19
|
+
|
|
20
|
+
install_dom_double();
|
|
21
|
+
|
|
22
|
+
if(typeof globalThis.ResizeObserver === "undefined") {
|
|
23
|
+
globalThis.ResizeObserver = class {
|
|
24
|
+
observe() {}
|
|
25
|
+
unobserve() {}
|
|
26
|
+
disconnect() {}
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const {
|
|
31
|
+
SDATA_END, gclass_create,
|
|
32
|
+
gobj_start_up, gobj_create_yuno, gobj_create_pure_child,
|
|
33
|
+
gobj_start, gobj_send_event, gobj_read_attr,
|
|
34
|
+
set_log_callback,
|
|
35
|
+
} = await import("@yuneta/gobj-js");
|
|
36
|
+
const {register_c_yui_form} = await import("./c_yui_form.js");
|
|
37
|
+
|
|
38
|
+
const saved = [];
|
|
39
|
+
const logged = [];
|
|
40
|
+
let yuno = null;
|
|
41
|
+
|
|
42
|
+
/* The settings template of an estadodelaire device type, cut down to
|
|
43
|
+
* the sub-form and one field beside it. */
|
|
44
|
+
const TEMPLATE = {
|
|
45
|
+
template_version: {
|
|
46
|
+
type: "integer",
|
|
47
|
+
default: 7,
|
|
48
|
+
flag: ["hidden", "persistent"]
|
|
49
|
+
},
|
|
50
|
+
alarm_colors: {
|
|
51
|
+
type: "object",
|
|
52
|
+
flag: ["writable", "persistent", "template"],
|
|
53
|
+
template: {
|
|
54
|
+
active_not_validated: {
|
|
55
|
+
type: "string",
|
|
56
|
+
default: "red",
|
|
57
|
+
flag: ["writable", "persistent", "color"]
|
|
58
|
+
},
|
|
59
|
+
active_validated: {
|
|
60
|
+
type: "string",
|
|
61
|
+
default: "#e5a50a",
|
|
62
|
+
flag: ["writable", "persistent", "color"]
|
|
63
|
+
},
|
|
64
|
+
not_active_not_validated: {
|
|
65
|
+
type: "string",
|
|
66
|
+
default: "Fuchsia",
|
|
67
|
+
flag: ["writable", "persistent", "color"]
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
beforeAll(async () => {
|
|
74
|
+
const i18next = (await import("i18next")).default;
|
|
75
|
+
await i18next.init({lng: "en", resources: {}});
|
|
76
|
+
gobj_start_up(null, null, null, null, null, null, null);
|
|
77
|
+
set_log_callback((level, msg) => {
|
|
78
|
+
logged.push({level: String(level), msg: String(msg)});
|
|
79
|
+
});
|
|
80
|
+
gclass_create("C_TEST_FORM_HOST", [], [["ST_IDLE", []]], {}, 0, [SDATA_END()], {}, 0, 0, 0, 0);
|
|
81
|
+
gclass_create(
|
|
82
|
+
"C_TEST_FORM_APP",
|
|
83
|
+
[["EV_SAVE_RECORD", 0], ["EV_RECORD_CHANGED", 0]],
|
|
84
|
+
[["ST_IDLE", [
|
|
85
|
+
["EV_SAVE_RECORD", (gobj, event, kw) => {
|
|
86
|
+
saved.push(kw);
|
|
87
|
+
return 0;
|
|
88
|
+
}, null],
|
|
89
|
+
["EV_RECORD_CHANGED", () => 0, null],
|
|
90
|
+
]]],
|
|
91
|
+
{}, 0, [SDATA_END()], {}, 0, 0, 0, 0
|
|
92
|
+
);
|
|
93
|
+
register_c_yui_form();
|
|
94
|
+
yuno = gobj_create_yuno("form_template_yuno", "C_TEST_FORM_HOST", {});
|
|
95
|
+
gobj_start(yuno);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
beforeEach(() => {
|
|
99
|
+
saved.length = 0;
|
|
100
|
+
logged.length = 0;
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
let seq = 0;
|
|
104
|
+
|
|
105
|
+
function build(record)
|
|
106
|
+
{
|
|
107
|
+
const app = gobj_create_pure_child(`tapp${++seq}`, "C_TEST_FORM_APP", {}, yuno);
|
|
108
|
+
gobj_start(app);
|
|
109
|
+
const form = gobj_create_pure_child(`tform${seq}`, "C_YUI_FORM", {
|
|
110
|
+
template: TEMPLATE,
|
|
111
|
+
record: record,
|
|
112
|
+
editable: true,
|
|
113
|
+
}, app);
|
|
114
|
+
gobj_start(form);
|
|
115
|
+
const $c = gobj_read_attr(form, "$container");
|
|
116
|
+
expect($c.querySelector("fieldset")).toBeTruthy(); // exec mode: a sub-form
|
|
117
|
+
return form;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function errors()
|
|
121
|
+
{
|
|
122
|
+
return logged.filter(l => /error/i.test(l.level) || /ERROR/.test(l.msg));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
describe("a template field in exec mode (a sub-form)", () => {
|
|
126
|
+
test("is saved as the dict its fields fill", () => {
|
|
127
|
+
const form = build({
|
|
128
|
+
template_version: 7,
|
|
129
|
+
alarm_colors: {
|
|
130
|
+
active_not_validated: "#ff0000",
|
|
131
|
+
active_validated: "#e5a50a",
|
|
132
|
+
not_active_not_validated: "#ff00ff"
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
gobj_send_event(form, "EV_SAVE_RECORD", {}, form);
|
|
136
|
+
expect(saved.length).toBe(1);
|
|
137
|
+
expect(saved[0].alarm_colors).toEqual({
|
|
138
|
+
active_not_validated: "#ff0000",
|
|
139
|
+
active_validated: "#e5a50a",
|
|
140
|
+
not_active_not_validated: "#ff00ff"
|
|
141
|
+
});
|
|
142
|
+
expect(saved[0].template_version).toBe(7);
|
|
143
|
+
expect(errors()).toEqual([]);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
test("a colour cleared is black, and its readout says so", () => {
|
|
147
|
+
const form = build({
|
|
148
|
+
template_version: 7,
|
|
149
|
+
alarm_colors: {
|
|
150
|
+
active_not_validated: "#ff0000",
|
|
151
|
+
active_validated: "#e5a50a",
|
|
152
|
+
not_active_not_validated: "#ff00ff"
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
const $c = gobj_read_attr(form, "$container");
|
|
156
|
+
const $colors = $c.querySelectorAll("fieldset input");
|
|
157
|
+
expect($colors.length).toBe(3);
|
|
158
|
+
gobj_send_event(form, "EV_CLEAR_RECORD", {}, form);
|
|
159
|
+
for(const $input of $colors) {
|
|
160
|
+
/* Never "": a colour input refuses it (Chrome warns). */
|
|
161
|
+
expect($input.value).toBe("#000000");
|
|
162
|
+
expect($input.yui_color_value.textContent).toBe("#000000");
|
|
163
|
+
}
|
|
164
|
+
expect(errors()).toEqual([]);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
test("left null by a save before the fix, comes back with its defaults", () => {
|
|
168
|
+
const form = build({
|
|
169
|
+
template_version: 7,
|
|
170
|
+
alarm_colors: null
|
|
171
|
+
});
|
|
172
|
+
gobj_send_event(form, "EV_SAVE_RECORD", {}, form);
|
|
173
|
+
expect(saved.length).toBe(1);
|
|
174
|
+
expect(saved[0].alarm_colors).toEqual({
|
|
175
|
+
active_not_validated: "red",
|
|
176
|
+
active_validated: "#e5a50a",
|
|
177
|
+
not_active_not_validated: "Fuchsia"
|
|
178
|
+
});
|
|
179
|
+
expect(errors()).toEqual([]);
|
|
180
|
+
});
|
|
181
|
+
});
|