@schukai/monster 4.142.4 → 4.143.1
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/package.json +1 -1
- package/source/components/datatable/datatable.mjs +24 -9
- package/source/components/datatable/filter/util.mjs +7 -3
- package/source/components/datatable/filter.mjs +21 -5
- package/source/components/datatable/pagination.mjs +75 -1
- package/source/components/form/select.mjs +2 -1
- package/source/components/host/config-manager.mjs +222 -32
- package/source/components/host/host.mjs +26 -0
- package/source/components/host/util.mjs +52 -2
- package/test/cases/components/datatable/config-keys.mjs +47 -0
- package/test/cases/components/form/select.mjs +32 -0
- package/test/cases/components/host/config-manager.mjs +86 -0
- package/test/cases/components/host/util.mjs +98 -72
- package/test/web/import.js +2 -0
package/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"author":"Volker Schukai","dependencies":{"@floating-ui/dom":"^1.7.6"},"description":"Monster is a simple library for creating fast, robust and lightweight websites.","homepage":"https://monsterjs.org/","keywords":["framework","web","dom","css","sass","mobile-first","app","front-end","templates","schukai","core","shopcloud","alvine","monster","buildmap","stack","observer","observable","uuid","node","nodelist","css-in-js","logger","log","theme"],"license":"AGPL 3.0","main":"source/monster.mjs","module":"source/monster.mjs","name":"@schukai/monster","repository":{"type":"git","url":"https://gitlab.schukai.com/oss/libraries/javascript/monster.git"},"type":"module","version":"4.
|
|
1
|
+
{"author":"Volker Schukai","dependencies":{"@floating-ui/dom":"^1.7.6"},"description":"Monster is a simple library for creating fast, robust and lightweight websites.","homepage":"https://monsterjs.org/","keywords":["framework","web","dom","css","sass","mobile-first","app","front-end","templates","schukai","core","shopcloud","alvine","monster","buildmap","stack","observer","observable","uuid","node","nodelist","css-in-js","logger","log","theme"],"license":"AGPL 3.0","main":"source/monster.mjs","module":"source/monster.mjs","name":"@schukai/monster","repository":{"type":"git","url":"https://gitlab.schukai.com/oss/libraries/javascript/monster.git"},"type":"module","version":"4.143.1"}
|
|
@@ -76,7 +76,7 @@ import {
|
|
|
76
76
|
import { getDocumentTranslations } from "../../i18n/translations.mjs";
|
|
77
77
|
import "../state/state.mjs";
|
|
78
78
|
import "../host/collapse.mjs";
|
|
79
|
-
import {
|
|
79
|
+
import { generateComponentConfigKey } from "../host/util.mjs";
|
|
80
80
|
|
|
81
81
|
import "./datasource/dom.mjs";
|
|
82
82
|
import "./datasource/rest.mjs";
|
|
@@ -279,6 +279,10 @@ class DataTable extends CustomElement {
|
|
|
279
279
|
"row-key": null,
|
|
280
280
|
"filter-id": null,
|
|
281
281
|
},
|
|
282
|
+
|
|
283
|
+
config: {
|
|
284
|
+
instanceKey: null,
|
|
285
|
+
},
|
|
282
286
|
},
|
|
283
287
|
initOptionsFromArguments.call(this),
|
|
284
288
|
);
|
|
@@ -659,15 +663,21 @@ class DataTable extends CustomElement {
|
|
|
659
663
|
* @return {string}
|
|
660
664
|
*/
|
|
661
665
|
function getColumnVisibilityConfigKey() {
|
|
662
|
-
return
|
|
666
|
+
return generateComponentConfigKey("datatable", this?.id, "columns-visibility", {
|
|
667
|
+
instanceKey: this.getOption("config.instanceKey"),
|
|
668
|
+
});
|
|
663
669
|
}
|
|
664
670
|
|
|
665
671
|
/**
|
|
666
672
|
* @private
|
|
667
673
|
* @return {string}
|
|
668
674
|
*/
|
|
669
|
-
function
|
|
670
|
-
return
|
|
675
|
+
function hasConfigIdentity() {
|
|
676
|
+
return (
|
|
677
|
+
(isString(this.id) && this.id !== "") ||
|
|
678
|
+
(isString(this.getOption("config.instanceKey")) &&
|
|
679
|
+
this.getOption("config.instanceKey").trim() !== "")
|
|
680
|
+
);
|
|
671
681
|
}
|
|
672
682
|
|
|
673
683
|
/**
|
|
@@ -682,8 +692,11 @@ function getHostConfig(callback) {
|
|
|
682
692
|
return Promise.resolve({});
|
|
683
693
|
}
|
|
684
694
|
|
|
685
|
-
if (!this
|
|
686
|
-
addErrorAttribute(
|
|
695
|
+
if (!hasConfigIdentity.call(this)) {
|
|
696
|
+
addErrorAttribute(
|
|
697
|
+
this,
|
|
698
|
+
"no id or config.instanceKey found; one is required for config",
|
|
699
|
+
);
|
|
687
700
|
return Promise.resolve({});
|
|
688
701
|
}
|
|
689
702
|
|
|
@@ -782,7 +795,7 @@ function updateConfigColumnBar() {
|
|
|
782
795
|
}
|
|
783
796
|
|
|
784
797
|
const host = findElementWithSelectorUpwards(this, "monster-host");
|
|
785
|
-
if (!(host && this
|
|
798
|
+
if (!(host && hasConfigIdentity.call(this))) {
|
|
786
799
|
return;
|
|
787
800
|
}
|
|
788
801
|
const configKey = getColumnVisibilityConfigKey.call(this);
|
|
@@ -1616,7 +1629,9 @@ function getTranslations() {
|
|
|
1616
1629
|
* @return {string}
|
|
1617
1630
|
*/
|
|
1618
1631
|
export function getStoredOrderConfigKey() {
|
|
1619
|
-
return
|
|
1632
|
+
return generateComponentConfigKey("datatable", this?.id, "stored-order", {
|
|
1633
|
+
instanceKey: this.getOption("config.instanceKey"),
|
|
1634
|
+
});
|
|
1620
1635
|
}
|
|
1621
1636
|
|
|
1622
1637
|
/**
|
|
@@ -1629,7 +1644,7 @@ function storeOrderStatement(doFetch) {
|
|
|
1629
1644
|
setDataSource.call(this, { order: statement }, doFetch);
|
|
1630
1645
|
|
|
1631
1646
|
const host = findElementWithSelectorUpwards(this, "monster-host");
|
|
1632
|
-
if (!(host && this
|
|
1647
|
+
if (!(host && hasConfigIdentity.call(this))) {
|
|
1633
1648
|
return;
|
|
1634
1649
|
}
|
|
1635
1650
|
|
|
@@ -12,14 +12,16 @@
|
|
|
12
12
|
* SPDX-License-Identifier: AGPL-3.0
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
-
import {
|
|
15
|
+
import { generateComponentConfigKey } from "../../host/util.mjs";
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
18
|
* @private
|
|
19
19
|
* @return {string}
|
|
20
20
|
*/
|
|
21
21
|
export function getFilterConfigKey() {
|
|
22
|
-
return
|
|
22
|
+
return generateComponentConfigKey("datatable", this?.id, "filter", {
|
|
23
|
+
instanceKey: this.getOption("config.instanceKey"),
|
|
24
|
+
});
|
|
23
25
|
}
|
|
24
26
|
|
|
25
27
|
/**
|
|
@@ -27,7 +29,9 @@ export function getFilterConfigKey() {
|
|
|
27
29
|
* @return {string}
|
|
28
30
|
*/
|
|
29
31
|
export function getStoredFilterConfigKey() {
|
|
30
|
-
return
|
|
32
|
+
return generateComponentConfigKey("datatable", this?.id, "stored-filter", {
|
|
33
|
+
instanceKey: this.getOption("config.instanceKey"),
|
|
34
|
+
});
|
|
31
35
|
}
|
|
32
36
|
|
|
33
37
|
/**
|
|
@@ -293,6 +293,10 @@ class Filter extends CustomElement {
|
|
|
293
293
|
selector: "",
|
|
294
294
|
},
|
|
295
295
|
|
|
296
|
+
config: {
|
|
297
|
+
instanceKey: null,
|
|
298
|
+
},
|
|
299
|
+
|
|
296
300
|
timeouts: {
|
|
297
301
|
message: 4000,
|
|
298
302
|
},
|
|
@@ -915,7 +919,7 @@ function initEventHandler() {
|
|
|
915
919
|
.then(() => {
|
|
916
920
|
const configKey = getStoredFilterConfigKey.call(self);
|
|
917
921
|
const host = getDocument().querySelector("monster-host");
|
|
918
|
-
if (!host) {
|
|
922
|
+
if (!(host && hasConfigIdentity.call(self))) {
|
|
919
923
|
return;
|
|
920
924
|
}
|
|
921
925
|
|
|
@@ -1070,7 +1074,7 @@ function initTabEvents() {
|
|
|
1070
1074
|
}
|
|
1071
1075
|
|
|
1072
1076
|
const host = findElementWithSelectorUpwards(this, "monster-host");
|
|
1073
|
-
if (!(host && this
|
|
1077
|
+
if (!(host && hasConfigIdentity.call(this))) {
|
|
1074
1078
|
return;
|
|
1075
1079
|
}
|
|
1076
1080
|
|
|
@@ -1111,7 +1115,7 @@ function updateFilterTabs() {
|
|
|
1111
1115
|
}
|
|
1112
1116
|
|
|
1113
1117
|
const host = findElementWithSelectorUpwards(this, "monster-host");
|
|
1114
|
-
if (!(host && this
|
|
1118
|
+
if (!(host && hasConfigIdentity.call(this))) {
|
|
1115
1119
|
return;
|
|
1116
1120
|
}
|
|
1117
1121
|
|
|
@@ -1580,7 +1584,7 @@ function getControlValuesFromLabel(label) {
|
|
|
1580
1584
|
function initFromConfig() {
|
|
1581
1585
|
const host = findElementWithSelectorUpwards(this, "monster-host");
|
|
1582
1586
|
|
|
1583
|
-
if (!(isInstance(host, Host) && this
|
|
1587
|
+
if (!(isInstance(host, Host) && hasConfigIdentity.call(this))) {
|
|
1584
1588
|
return Promise.resolve();
|
|
1585
1589
|
}
|
|
1586
1590
|
|
|
@@ -1622,7 +1626,7 @@ function initFromConfig() {
|
|
|
1622
1626
|
*/
|
|
1623
1627
|
function updateConfig() {
|
|
1624
1628
|
const host = findElementWithSelectorUpwards(this, "monster-host");
|
|
1625
|
-
if (!(host && this
|
|
1629
|
+
if (!(host && hasConfigIdentity.call(this))) {
|
|
1626
1630
|
return;
|
|
1627
1631
|
}
|
|
1628
1632
|
const configKey = getFilterConfigKey.call(this);
|
|
@@ -1634,6 +1638,18 @@ function updateConfig() {
|
|
|
1634
1638
|
}
|
|
1635
1639
|
}
|
|
1636
1640
|
|
|
1641
|
+
/**
|
|
1642
|
+
* @private
|
|
1643
|
+
* @return {boolean}
|
|
1644
|
+
*/
|
|
1645
|
+
function hasConfigIdentity() {
|
|
1646
|
+
return (
|
|
1647
|
+
(isString(this.id) && this.id !== "") ||
|
|
1648
|
+
(isString(this.getOption("config.instanceKey")) &&
|
|
1649
|
+
this.getOption("config.instanceKey").trim() !== "")
|
|
1650
|
+
);
|
|
1651
|
+
}
|
|
1652
|
+
|
|
1637
1653
|
/**
|
|
1638
1654
|
* @private
|
|
1639
1655
|
* @return {string}
|
|
@@ -145,6 +145,8 @@ class Pagination extends CustomElement {
|
|
|
145
145
|
* @property {number} currentPage Current page
|
|
146
146
|
* @property {number} pages Pages
|
|
147
147
|
* @property {number} objectsPerPage Objects per page
|
|
148
|
+
* @property {Object} layout Layout configuration
|
|
149
|
+
* @property {"auto"|"compact"} layout.mode Layout mode. Use `"compact"` to always show the small arrow pagination.
|
|
148
150
|
* @property {Object} mapping Mapping
|
|
149
151
|
* @property {string} mapping.pages Pages mapping
|
|
150
152
|
* @property {string} mapping.objectsPerPage Objects per page mapping
|
|
@@ -175,6 +177,10 @@ class Pagination extends CustomElement {
|
|
|
175
177
|
objectsPerPage: 20,
|
|
176
178
|
currentPage: null,
|
|
177
179
|
|
|
180
|
+
layout: {
|
|
181
|
+
mode: "auto",
|
|
182
|
+
},
|
|
183
|
+
|
|
178
184
|
mapping: {
|
|
179
185
|
pages: "sys.pagination.pages",
|
|
180
186
|
objectsPerPage: "sys.pagination.objectsPerPage",
|
|
@@ -250,6 +256,11 @@ class Pagination extends CustomElement {
|
|
|
250
256
|
}
|
|
251
257
|
|
|
252
258
|
refreshLayout({ schedule = false } = {}) {
|
|
259
|
+
if (isForcedCompactPagination.call(this)) {
|
|
260
|
+
applyForcedCompactPaginationLayout.call(this);
|
|
261
|
+
return this;
|
|
262
|
+
}
|
|
263
|
+
|
|
253
264
|
if (schedule === true) {
|
|
254
265
|
if (isAdaptivePagination.call(this)) {
|
|
255
266
|
schedulePaginationLayoutUpdate.call(this);
|
|
@@ -363,7 +374,15 @@ class Pagination extends CustomElement {
|
|
|
363
374
|
});
|
|
364
375
|
});
|
|
365
376
|
|
|
366
|
-
if (
|
|
377
|
+
if (isForcedCompactPagination.call(this)) {
|
|
378
|
+
this[sizeDataSymbol] = {
|
|
379
|
+
last: {
|
|
380
|
+
parentWidth: 0,
|
|
381
|
+
},
|
|
382
|
+
showNumbers: false,
|
|
383
|
+
};
|
|
384
|
+
applyForcedCompactPaginationLayout.call(this);
|
|
385
|
+
} else if (isAdaptivePagination.call(this)) {
|
|
367
386
|
this[sizeDataSymbol] = {
|
|
368
387
|
last: {
|
|
369
388
|
parentWidth: 0,
|
|
@@ -877,6 +896,11 @@ function syncPaginationStateToDom(pagination) {
|
|
|
877
896
|
* @private
|
|
878
897
|
*/
|
|
879
898
|
function schedulePaginationLayoutUpdate() {
|
|
899
|
+
if (isForcedCompactPagination.call(this)) {
|
|
900
|
+
applyForcedCompactPaginationLayout.call(this);
|
|
901
|
+
return;
|
|
902
|
+
}
|
|
903
|
+
|
|
880
904
|
if (!isAdaptivePagination.call(this)) {
|
|
881
905
|
return;
|
|
882
906
|
}
|
|
@@ -895,6 +919,11 @@ function schedulePaginationLayoutUpdate() {
|
|
|
895
919
|
* @private
|
|
896
920
|
*/
|
|
897
921
|
function applyPaginationLayout() {
|
|
922
|
+
if (isForcedCompactPagination.call(this)) {
|
|
923
|
+
applyForcedCompactPaginationLayout.call(this);
|
|
924
|
+
return;
|
|
925
|
+
}
|
|
926
|
+
|
|
898
927
|
if (!this.isConnected || !this.shadowRoot) {
|
|
899
928
|
return;
|
|
900
929
|
}
|
|
@@ -974,6 +1003,51 @@ function applyPaginationLayout() {
|
|
|
974
1003
|
}
|
|
975
1004
|
}
|
|
976
1005
|
|
|
1006
|
+
/**
|
|
1007
|
+
* @private
|
|
1008
|
+
*/
|
|
1009
|
+
function applyForcedCompactPaginationLayout() {
|
|
1010
|
+
if (!this.isConnected || !this.shadowRoot) {
|
|
1011
|
+
return;
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
if (this[layoutApplySymbol]) {
|
|
1015
|
+
return;
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
this[layoutApplySymbol] = true;
|
|
1019
|
+
try {
|
|
1020
|
+
ensureLabelState.call(this);
|
|
1021
|
+
|
|
1022
|
+
const list = this.shadowRoot.querySelector(".pagination-list");
|
|
1023
|
+
if (!list) {
|
|
1024
|
+
return;
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
const prevLink = this.shadowRoot.querySelector(
|
|
1028
|
+
"a[data-monster-role=pagination-prev]",
|
|
1029
|
+
);
|
|
1030
|
+
const nextLink = this.shadowRoot.querySelector(
|
|
1031
|
+
"a[data-monster-role=pagination-next]",
|
|
1032
|
+
);
|
|
1033
|
+
|
|
1034
|
+
setClassEnabled(list, "pagination-no-wrap", true);
|
|
1035
|
+
applyPaginationMode.call(this, list, prevLink, nextLink, "compact");
|
|
1036
|
+
setAttributeValue(list, "data-monster-adaptive-ready", "true");
|
|
1037
|
+
this[layoutModeSymbol] = "compact";
|
|
1038
|
+
} finally {
|
|
1039
|
+
this[layoutApplySymbol] = false;
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
/**
|
|
1044
|
+
* @private
|
|
1045
|
+
* @return {boolean}
|
|
1046
|
+
*/
|
|
1047
|
+
function isForcedCompactPagination() {
|
|
1048
|
+
return this.getOption("layout.mode") === "compact";
|
|
1049
|
+
}
|
|
1050
|
+
|
|
977
1051
|
/**
|
|
978
1052
|
* @private
|
|
979
1053
|
* @param {number} parentWidth
|
|
@@ -5769,7 +5769,8 @@ function getTemplate() {
|
|
|
5769
5769
|
<div part="options" data-monster-role="options" data-monster-insert="options path:options"
|
|
5770
5770
|
tabindex="-1"></div>
|
|
5771
5771
|
</div>
|
|
5772
|
-
<monster-pagination data-monster-role="pagination" part="pagination"
|
|
5772
|
+
<monster-pagination data-monster-role="pagination" part="pagination"
|
|
5773
|
+
data-monster-option-layout-mode="compact"></monster-pagination>
|
|
5773
5774
|
<div part="remote-info"
|
|
5774
5775
|
data-monster-role="remote-info"
|
|
5775
5776
|
data-monster-attributes="class path:classes.remoteInfo"
|
|
@@ -21,6 +21,7 @@ import { ConfigManagerStyleSheet } from "./stylesheet/config-manager.mjs";
|
|
|
21
21
|
import { getWindow } from "../../dom/util.mjs";
|
|
22
22
|
import { instanceSymbol } from "../../constants.mjs";
|
|
23
23
|
import { diff } from "../../data/diff.mjs";
|
|
24
|
+
import { isFunction, isObject } from "../../types/is.mjs";
|
|
24
25
|
|
|
25
26
|
export { ConfigManager };
|
|
26
27
|
|
|
@@ -108,17 +109,42 @@ class ConfigManager extends CustomElement {
|
|
|
108
109
|
keyPath: "key",
|
|
109
110
|
},
|
|
110
111
|
},
|
|
112
|
+
storage: {
|
|
113
|
+
adapter: null,
|
|
114
|
+
serverAuthoritative: false,
|
|
115
|
+
},
|
|
111
116
|
});
|
|
112
117
|
}
|
|
113
118
|
|
|
119
|
+
/**
|
|
120
|
+
* Register an external host configuration storage adapter.
|
|
121
|
+
*
|
|
122
|
+
* Supported adapter methods are getConfig/hasConfig/setConfig/deleteConfig
|
|
123
|
+
* or their short aliases get/has/set/delete. If managesConfigKey, managesKey
|
|
124
|
+
* or manages is present, it is used to decide whether a key is handled by
|
|
125
|
+
* the adapter. Without such a method, an adapter handles all keys.
|
|
126
|
+
*
|
|
127
|
+
* @param {Object|null} adapter
|
|
128
|
+
* @return {ConfigManager}
|
|
129
|
+
*/
|
|
130
|
+
setStorageAdapter(adapter) {
|
|
131
|
+
this.setOption("storage.adapter", adapter);
|
|
132
|
+
return this;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* @return {Object|null}
|
|
137
|
+
*/
|
|
138
|
+
getStorageAdapter() {
|
|
139
|
+
return getStorageAdapter.call(this);
|
|
140
|
+
}
|
|
141
|
+
|
|
114
142
|
/**
|
|
115
143
|
* @param {string} key
|
|
116
144
|
* @return {Promise<unknown>}
|
|
117
145
|
*/
|
|
118
146
|
getConfig(key) {
|
|
119
|
-
return this.ready().then(() =>
|
|
120
|
-
return getBlob.call(this, key);
|
|
121
|
-
});
|
|
147
|
+
return this.ready().then(() => readConfig.call(this, key));
|
|
122
148
|
}
|
|
123
149
|
|
|
124
150
|
/**
|
|
@@ -126,16 +152,7 @@ class ConfigManager extends CustomElement {
|
|
|
126
152
|
* @return {Promise<boolean>}
|
|
127
153
|
*/
|
|
128
154
|
hasConfig(key) {
|
|
129
|
-
return this.ready()
|
|
130
|
-
.then(() => {
|
|
131
|
-
return getBlob.call(this, key);
|
|
132
|
-
})
|
|
133
|
-
.then(() => {
|
|
134
|
-
return true;
|
|
135
|
-
})
|
|
136
|
-
.catch(() => {
|
|
137
|
-
return false;
|
|
138
|
-
});
|
|
155
|
+
return this.ready().then(() => hasConfig.call(this, key));
|
|
139
156
|
}
|
|
140
157
|
|
|
141
158
|
/**
|
|
@@ -144,28 +161,11 @@ class ConfigManager extends CustomElement {
|
|
|
144
161
|
* @return {Promise<unknown>}
|
|
145
162
|
*/
|
|
146
163
|
setConfig(key, value) {
|
|
147
|
-
return this.ready().then(() =>
|
|
148
|
-
return getBlob
|
|
149
|
-
.call(this, key)
|
|
150
|
-
.then((storedValue) => {
|
|
151
|
-
if (diff(storedValue, value).length === 0) {
|
|
152
|
-
return;
|
|
153
|
-
}
|
|
154
|
-
return setBlob.call(this, key, value);
|
|
155
|
-
})
|
|
156
|
-
.catch((error) => {
|
|
157
|
-
if (error?.message?.match(/is not defined/)) {
|
|
158
|
-
return setBlob.call(this, key, value);
|
|
159
|
-
}
|
|
160
|
-
throw error;
|
|
161
|
-
});
|
|
162
|
-
});
|
|
164
|
+
return this.ready().then(() => writeConfig.call(this, key, value));
|
|
163
165
|
}
|
|
164
166
|
|
|
165
167
|
deleteConfig(key) {
|
|
166
|
-
return this.ready().then(() =>
|
|
167
|
-
return deleteBlob.call(this, key);
|
|
168
|
-
});
|
|
168
|
+
return this.ready().then(() => removeConfig.call(this, key));
|
|
169
169
|
}
|
|
170
170
|
|
|
171
171
|
/**
|
|
@@ -190,6 +190,192 @@ class ConfigManager extends CustomElement {
|
|
|
190
190
|
}
|
|
191
191
|
}
|
|
192
192
|
|
|
193
|
+
/**
|
|
194
|
+
* @private
|
|
195
|
+
* @return {Object|null}
|
|
196
|
+
*/
|
|
197
|
+
function getStorageAdapter() {
|
|
198
|
+
const adapter = this.getOption("storage.adapter");
|
|
199
|
+
return isObject(adapter) ? adapter : null;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* @private
|
|
204
|
+
* @param {Object} adapter
|
|
205
|
+
* @param {string} key
|
|
206
|
+
* @return {boolean}
|
|
207
|
+
*/
|
|
208
|
+
function adapterManagesKey(adapter, key) {
|
|
209
|
+
for (const methodName of ["managesConfigKey", "managesKey", "manages"]) {
|
|
210
|
+
if (isFunction(adapter?.[methodName])) {
|
|
211
|
+
return adapter[methodName](key) === true;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return true;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* @private
|
|
220
|
+
* @param {Object} adapter
|
|
221
|
+
* @param {string[]} methodNames
|
|
222
|
+
* @param {Array} args
|
|
223
|
+
* @return {Promise<unknown>}
|
|
224
|
+
*/
|
|
225
|
+
function callAdapter(adapter, methodNames, args) {
|
|
226
|
+
for (const methodName of methodNames) {
|
|
227
|
+
if (isFunction(adapter?.[methodName])) {
|
|
228
|
+
return Promise.resolve(adapter[methodName](...args));
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return Promise.reject(new Error("The storage adapter method is not defined."));
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* @private
|
|
237
|
+
* @return {boolean}
|
|
238
|
+
*/
|
|
239
|
+
function isServerAuthoritative() {
|
|
240
|
+
return this.getOption("storage.serverAuthoritative") === true;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* @private
|
|
245
|
+
* @param {string} key
|
|
246
|
+
* @return {Promise<unknown>}
|
|
247
|
+
*/
|
|
248
|
+
function readConfig(key) {
|
|
249
|
+
const adapter = getStorageAdapter.call(this);
|
|
250
|
+
const managed = adapter && adapterManagesKey(adapter, key);
|
|
251
|
+
|
|
252
|
+
if (managed) {
|
|
253
|
+
return callAdapter(adapter, ["getConfig", "get"], [key]).catch((error) => {
|
|
254
|
+
if (isServerAuthoritative.call(this)) {
|
|
255
|
+
throw error;
|
|
256
|
+
}
|
|
257
|
+
return getBlob.call(this, key);
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
return getBlob.call(this, key);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* @private
|
|
266
|
+
* @param {string} key
|
|
267
|
+
* @return {Promise<boolean>}
|
|
268
|
+
*/
|
|
269
|
+
function hasConfig(key) {
|
|
270
|
+
const adapter = getStorageAdapter.call(this);
|
|
271
|
+
const managed = adapter && adapterManagesKey(adapter, key);
|
|
272
|
+
|
|
273
|
+
if (managed) {
|
|
274
|
+
return callAdapter(adapter, ["hasConfig", "has"], [key])
|
|
275
|
+
.catch(() => {
|
|
276
|
+
return callAdapter(adapter, ["getConfig", "get"], [key]).then(
|
|
277
|
+
() => true,
|
|
278
|
+
);
|
|
279
|
+
})
|
|
280
|
+
.then((result) => {
|
|
281
|
+
if (result === true || isServerAuthoritative.call(this)) {
|
|
282
|
+
return result === true;
|
|
283
|
+
}
|
|
284
|
+
return hasLocalConfig.call(this, key);
|
|
285
|
+
})
|
|
286
|
+
.catch(() => {
|
|
287
|
+
if (isServerAuthoritative.call(this)) {
|
|
288
|
+
return false;
|
|
289
|
+
}
|
|
290
|
+
return hasLocalConfig.call(this, key);
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
return hasLocalConfig.call(this, key);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* @private
|
|
299
|
+
* @param {string} key
|
|
300
|
+
* @return {Promise<boolean>}
|
|
301
|
+
*/
|
|
302
|
+
function hasLocalConfig(key) {
|
|
303
|
+
return getBlob
|
|
304
|
+
.call(this, key)
|
|
305
|
+
.then(() => true)
|
|
306
|
+
.catch(() => false);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* @private
|
|
311
|
+
* @param {string} key
|
|
312
|
+
* @param {*} value
|
|
313
|
+
* @return {Promise<unknown>}
|
|
314
|
+
*/
|
|
315
|
+
function writeConfig(key, value) {
|
|
316
|
+
const adapter = getStorageAdapter.call(this);
|
|
317
|
+
const managed = adapter && adapterManagesKey(adapter, key);
|
|
318
|
+
|
|
319
|
+
if (managed) {
|
|
320
|
+
return callAdapter(adapter, ["setConfig", "set"], [key, value]).catch(
|
|
321
|
+
(error) => {
|
|
322
|
+
if (isServerAuthoritative.call(this)) {
|
|
323
|
+
throw error;
|
|
324
|
+
}
|
|
325
|
+
return writeLocalConfig.call(this, key, value);
|
|
326
|
+
},
|
|
327
|
+
);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
return writeLocalConfig.call(this, key, value);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* @private
|
|
335
|
+
* @param {string} key
|
|
336
|
+
* @param {*} value
|
|
337
|
+
* @return {Promise<unknown>}
|
|
338
|
+
*/
|
|
339
|
+
function writeLocalConfig(key, value) {
|
|
340
|
+
return getBlob
|
|
341
|
+
.call(this, key)
|
|
342
|
+
.then((storedValue) => {
|
|
343
|
+
if (diff(storedValue, value).length === 0) {
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
return setBlob.call(this, key, value);
|
|
347
|
+
})
|
|
348
|
+
.catch((error) => {
|
|
349
|
+
if (error?.message?.match(/is not defined/)) {
|
|
350
|
+
return setBlob.call(this, key, value);
|
|
351
|
+
}
|
|
352
|
+
throw error;
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* @private
|
|
358
|
+
* @param {string} key
|
|
359
|
+
* @return {Promise<unknown>}
|
|
360
|
+
*/
|
|
361
|
+
function removeConfig(key) {
|
|
362
|
+
const adapter = getStorageAdapter.call(this);
|
|
363
|
+
const managed = adapter && adapterManagesKey(adapter, key);
|
|
364
|
+
|
|
365
|
+
if (managed) {
|
|
366
|
+
return callAdapter(adapter, ["deleteConfig", "delete", "remove"], [
|
|
367
|
+
key,
|
|
368
|
+
]).catch((error) => {
|
|
369
|
+
if (isServerAuthoritative.call(this)) {
|
|
370
|
+
throw error;
|
|
371
|
+
}
|
|
372
|
+
return deleteBlob.call(this, key);
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
return deleteBlob.call(this, key);
|
|
377
|
+
}
|
|
378
|
+
|
|
193
379
|
/**
|
|
194
380
|
* @private
|
|
195
381
|
* @returns {Promise<unknown>}
|
|
@@ -206,6 +392,10 @@ function openDatabase() {
|
|
|
206
392
|
throw new Error("The database name and version must be set.");
|
|
207
393
|
}
|
|
208
394
|
|
|
395
|
+
if (!window.indexedDB) {
|
|
396
|
+
return Promise.resolve(null);
|
|
397
|
+
}
|
|
398
|
+
|
|
209
399
|
const request = window.indexedDB.open(name, version);
|
|
210
400
|
|
|
211
401
|
return new Promise((resolve, reject) => {
|
|
@@ -194,6 +194,32 @@ class Host extends CustomElement {
|
|
|
194
194
|
return this[configManagerElementSymbol].setConfig(key, value);
|
|
195
195
|
}
|
|
196
196
|
|
|
197
|
+
/**
|
|
198
|
+
* Register an external storage adapter for host configuration.
|
|
199
|
+
*
|
|
200
|
+
* @param {Object|null} adapter
|
|
201
|
+
* @return {Host}
|
|
202
|
+
*/
|
|
203
|
+
setConfigStorageAdapter(adapter) {
|
|
204
|
+
if (this[configManagerElementSymbol] instanceof HTMLElement === false) {
|
|
205
|
+
throw new Error("There is no config manager element");
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
this[configManagerElementSymbol].setStorageAdapter(adapter);
|
|
209
|
+
return this;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* @return {Object|null}
|
|
214
|
+
*/
|
|
215
|
+
getConfigStorageAdapter() {
|
|
216
|
+
if (this[configManagerElementSymbol] instanceof HTMLElement === false) {
|
|
217
|
+
throw new Error("There is no config manager element");
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return this[configManagerElementSymbol].getStorageAdapter();
|
|
221
|
+
}
|
|
222
|
+
|
|
197
223
|
/**
|
|
198
224
|
* @private
|
|
199
225
|
* @fires Host#monster-host-connected
|
|
@@ -13,8 +13,14 @@
|
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
15
|
import { getWindow } from "../../dom/util.mjs";
|
|
16
|
+
import { isString } from "../../types/is.mjs";
|
|
16
17
|
|
|
17
|
-
export {
|
|
18
|
+
export {
|
|
19
|
+
generateConfigKey,
|
|
20
|
+
generateComponentConfigKey,
|
|
21
|
+
generateUniqueConfigKey,
|
|
22
|
+
sanitizeConfigKey,
|
|
23
|
+
};
|
|
18
24
|
|
|
19
25
|
/**
|
|
20
26
|
* Generate a unique configuration key based on the current browser location,
|
|
@@ -38,5 +44,49 @@ function generateUniqueConfigKey(componentName, id, prefix) {
|
|
|
38
44
|
const uniqueKey = `${prefix}_${urlWithoutParamsAndHash}_${componentName}_${id}`;
|
|
39
45
|
|
|
40
46
|
// Replace any special characters and spaces with underscores
|
|
41
|
-
return uniqueKey
|
|
47
|
+
return sanitizeConfigKey(uniqueKey);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Generate a stable host configuration key from an explicit technical instance key.
|
|
52
|
+
*
|
|
53
|
+
* @param {string} componentName - The name of the component.
|
|
54
|
+
* @param {string} instanceKey - Technical, host-defined instance key.
|
|
55
|
+
* @param {string} scope - Configuration scope, for example "filter".
|
|
56
|
+
* @return {string}
|
|
57
|
+
*/
|
|
58
|
+
function generateConfigKey(componentName, instanceKey, scope) {
|
|
59
|
+
return sanitizeConfigKey(`${scope}_${componentName}_${instanceKey}`);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Generate a component host configuration key.
|
|
64
|
+
*
|
|
65
|
+
* If an explicit instance key is provided, the key is independent from the
|
|
66
|
+
* current URL and DOM id. Without an instance key, this keeps the historical
|
|
67
|
+
* URL-derived key for backwards compatibility.
|
|
68
|
+
*
|
|
69
|
+
* @param {string} componentName
|
|
70
|
+
* @param {string} id
|
|
71
|
+
* @param {string} scope
|
|
72
|
+
* @param {object} [options]
|
|
73
|
+
* @param {string} [options.instanceKey]
|
|
74
|
+
* @return {string}
|
|
75
|
+
*/
|
|
76
|
+
function generateComponentConfigKey(componentName, id, scope, options = {}) {
|
|
77
|
+
if (isString(options.instanceKey) && options.instanceKey.trim() !== "") {
|
|
78
|
+
return generateConfigKey(componentName, options.instanceKey.trim(), scope);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return generateUniqueConfigKey(componentName, id, scope);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* @param {string} key
|
|
86
|
+
* @return {string}
|
|
87
|
+
*/
|
|
88
|
+
function sanitizeConfigKey(key) {
|
|
89
|
+
return String(key)
|
|
90
|
+
.replace(/[^\w\s]/gi, "_")
|
|
91
|
+
.replace(/\s+/g, "_");
|
|
42
92
|
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { expect } from "chai";
|
|
2
|
+
import { initJSDOM } from "../../../util/jsdom.mjs";
|
|
3
|
+
|
|
4
|
+
let getStoredOrderConfigKey;
|
|
5
|
+
let getFilterConfigKey;
|
|
6
|
+
let getStoredFilterConfigKey;
|
|
7
|
+
|
|
8
|
+
describe("Datatable config keys", function () {
|
|
9
|
+
before(function (done) {
|
|
10
|
+
initJSDOM().then(() => {
|
|
11
|
+
import("../../../../source/components/datatable/datatable.mjs")
|
|
12
|
+
.then((m) => {
|
|
13
|
+
getStoredOrderConfigKey = m.getStoredOrderConfigKey;
|
|
14
|
+
return import(
|
|
15
|
+
"../../../../source/components/datatable/filter/util.mjs"
|
|
16
|
+
);
|
|
17
|
+
})
|
|
18
|
+
.then((m) => {
|
|
19
|
+
getFilterConfigKey = m.getFilterConfigKey;
|
|
20
|
+
getStoredFilterConfigKey = m.getStoredFilterConfigKey;
|
|
21
|
+
done();
|
|
22
|
+
})
|
|
23
|
+
.catch(done);
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("uses the same explicit instance-key contract for filter, stored filters and sorting", function () {
|
|
28
|
+
const context = {
|
|
29
|
+
id: "dom-id",
|
|
30
|
+
getOption(path) {
|
|
31
|
+
if (path === "config.instanceKey") {
|
|
32
|
+
return "orders.default";
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
expect(getFilterConfigKey.call(context)).to.equal(
|
|
38
|
+
"filter_datatable_orders_default",
|
|
39
|
+
);
|
|
40
|
+
expect(getStoredFilterConfigKey.call(context)).to.equal(
|
|
41
|
+
"stored_filter_datatable_orders_default",
|
|
42
|
+
);
|
|
43
|
+
expect(getStoredOrderConfigKey.call(context)).to.equal(
|
|
44
|
+
"stored_order_datatable_orders_default",
|
|
45
|
+
);
|
|
46
|
+
});
|
|
47
|
+
});
|
|
@@ -156,6 +156,38 @@ describe('Select', function () {
|
|
|
156
156
|
expect(cssText.replace(/\s+/g, '')).to.contain('z-index:var(--monster-z-index-popover)');
|
|
157
157
|
});
|
|
158
158
|
|
|
159
|
+
it('should render select pagination in the compact arrow mode immediately', async function () {
|
|
160
|
+
const mocks = document.getElementById('mocks');
|
|
161
|
+
const select = document.createElement('monster-select');
|
|
162
|
+
|
|
163
|
+
mocks.appendChild(select);
|
|
164
|
+
|
|
165
|
+
await waitForCondition(() => {
|
|
166
|
+
return select.shadowRoot?.querySelector('[data-monster-role="pagination"]');
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
const pagination = select.shadowRoot.querySelector('[data-monster-role="pagination"]');
|
|
170
|
+
expect(pagination.getOption('layout.mode')).to.equal('compact');
|
|
171
|
+
|
|
172
|
+
pagination.style.display = 'block';
|
|
173
|
+
pagination.setPaginationState({currentPage: 2, totalPages: 5});
|
|
174
|
+
|
|
175
|
+
await waitForCondition(() => {
|
|
176
|
+
const list = pagination.shadowRoot?.querySelector('.pagination-list');
|
|
177
|
+
const previous = pagination.shadowRoot?.querySelector(
|
|
178
|
+
'a[data-monster-role="pagination-prev"]'
|
|
179
|
+
);
|
|
180
|
+
const next = pagination.shadowRoot?.querySelector(
|
|
181
|
+
'a[data-monster-role="pagination-next"]'
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
return list?.classList.contains('pagination-compact') &&
|
|
185
|
+
list.getAttribute('data-monster-adaptive-ready') === 'true' &&
|
|
186
|
+
previous?.querySelector('svg') &&
|
|
187
|
+
next?.querySelector('svg');
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
|
|
159
191
|
it('should deduplicate floating layout queue jobs by popper element', async function () {
|
|
160
192
|
const popper = document.createElement('div');
|
|
161
193
|
const reasons = [];
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import * as chai from "chai";
|
|
2
|
+
import { initJSDOM } from "../../../util/jsdom.mjs";
|
|
3
|
+
|
|
4
|
+
const expect = chai.expect;
|
|
5
|
+
|
|
6
|
+
describe("ConfigManager storage adapter", function () {
|
|
7
|
+
before(function (done) {
|
|
8
|
+
initJSDOM().then(() => {
|
|
9
|
+
import("../../../../source/components/host/config-manager.mjs")
|
|
10
|
+
.then(() => {
|
|
11
|
+
done();
|
|
12
|
+
})
|
|
13
|
+
.catch((e) => done(e));
|
|
14
|
+
});
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
afterEach(() => {
|
|
18
|
+
let mocks = document.getElementById("mocks");
|
|
19
|
+
mocks.innerHTML = "";
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("uses an external storage adapter for managed keys", function (done) {
|
|
23
|
+
const config = document.createElement("monster-config-manager");
|
|
24
|
+
const values = new Map();
|
|
25
|
+
const calls = [];
|
|
26
|
+
|
|
27
|
+
config.setStorageAdapter({
|
|
28
|
+
managesConfigKey: (key) => key.startsWith("server_"),
|
|
29
|
+
hasConfig: (key) => values.has(key),
|
|
30
|
+
getConfig: (key) => values.get(key),
|
|
31
|
+
setConfig: (key, value) => {
|
|
32
|
+
calls.push(["set", key, value]);
|
|
33
|
+
values.set(key, value);
|
|
34
|
+
},
|
|
35
|
+
deleteConfig: (key) => {
|
|
36
|
+
calls.push(["delete", key]);
|
|
37
|
+
values.delete(key);
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
config.setOption("storage.serverAuthoritative", true);
|
|
42
|
+
|
|
43
|
+
config
|
|
44
|
+
.setConfig("server_table_filter", { visible: true })
|
|
45
|
+
.then(() => config.hasConfig("server_table_filter"))
|
|
46
|
+
.then((hasConfig) => {
|
|
47
|
+
expect(hasConfig).to.equal(true);
|
|
48
|
+
return config.getConfig("server_table_filter");
|
|
49
|
+
})
|
|
50
|
+
.then((value) => {
|
|
51
|
+
expect(value).to.deep.equal({ visible: true });
|
|
52
|
+
expect(calls).to.deep.equal([
|
|
53
|
+
["set", "server_table_filter", { visible: true }],
|
|
54
|
+
]);
|
|
55
|
+
return config.deleteConfig("server_table_filter");
|
|
56
|
+
})
|
|
57
|
+
.then(() => {
|
|
58
|
+
expect(values.has("server_table_filter")).to.equal(false);
|
|
59
|
+
done();
|
|
60
|
+
})
|
|
61
|
+
.catch(done);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("does not fall back to IndexedDB for server-authoritative adapter keys", function (done) {
|
|
65
|
+
const config = document.createElement("monster-config-manager");
|
|
66
|
+
|
|
67
|
+
config.setStorageAdapter({
|
|
68
|
+
managesConfigKey: (key) => key === "server_key",
|
|
69
|
+
setConfig: () => Promise.reject(new Error("server unavailable")),
|
|
70
|
+
});
|
|
71
|
+
config.setOption("storage.serverAuthoritative", true);
|
|
72
|
+
config.setOption("indexDB.objectStore.name", "missing-store");
|
|
73
|
+
|
|
74
|
+
config
|
|
75
|
+
.setConfig("server_key", { value: 1 })
|
|
76
|
+
.then(() => done(new Error("setConfig should reject")))
|
|
77
|
+
.catch((error) => {
|
|
78
|
+
try {
|
|
79
|
+
expect(error.message).to.equal("server unavailable");
|
|
80
|
+
done();
|
|
81
|
+
} catch (e) {
|
|
82
|
+
done(e);
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
});
|
|
@@ -1,79 +1,105 @@
|
|
|
1
1
|
// Import the required libraries
|
|
2
|
-
import { expect } from
|
|
2
|
+
import { expect } from "chai";
|
|
3
3
|
//import { JSDOM } from 'jsdom';
|
|
4
|
-
import {
|
|
5
|
-
|
|
4
|
+
import {
|
|
5
|
+
generateComponentConfigKey,
|
|
6
|
+
generateConfigKey,
|
|
7
|
+
generateUniqueConfigKey,
|
|
8
|
+
} from "../../../../source/components/host/util.mjs";
|
|
9
|
+
import { initJSDOM } from "../../../util/jsdom.mjs";
|
|
6
10
|
|
|
7
11
|
// Create a JSDOM instance to simulate the browser environment
|
|
8
12
|
//const dom = new JSDOM();
|
|
9
13
|
|
|
10
|
-
|
|
11
14
|
// Test suite for the generateUniqueConfigKey function
|
|
12
|
-
describe(
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
15
|
+
describe("generateUniqueConfigKey", () => {
|
|
16
|
+
//let originalWindow;
|
|
17
|
+
|
|
18
|
+
// before(() => {
|
|
19
|
+
// // Store the original window object
|
|
20
|
+
// originalWindow = globalThis.window;
|
|
21
|
+
//
|
|
22
|
+
// // Create a JSDOM instance to simulate the browser environment
|
|
23
|
+
// const dom = new JSDOM();
|
|
24
|
+
// globalThis.window = dom.window;
|
|
25
|
+
// });
|
|
26
|
+
|
|
27
|
+
before(function (done) {
|
|
28
|
+
initJSDOM().then(() => {
|
|
29
|
+
done();
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
// ... (same test cases as before)
|
|
34
|
+
|
|
35
|
+
after(() => {
|
|
36
|
+
// Restore the original window object
|
|
37
|
+
// globalThis.window = originalWindow;
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("should generate a unique key with the given parameters", () => {
|
|
41
|
+
const componentName = "MyComponent";
|
|
42
|
+
const id = "123";
|
|
43
|
+
const prefix = "myPrefix";
|
|
44
|
+
|
|
45
|
+
const uniqueKey = generateUniqueConfigKey(componentName, id, prefix);
|
|
46
|
+
|
|
47
|
+
// Ensure the unique key contains the given parameters and follows the expected format
|
|
48
|
+
expect(uniqueKey).to.include(prefix);
|
|
49
|
+
expect(uniqueKey).to.include(componentName);
|
|
50
|
+
expect(uniqueKey).to.include(id);
|
|
51
|
+
expect(uniqueKey).to.match(/^[a-zA-Z0-9_]+$/);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("should replace special characters and spaces with underscores", () => {
|
|
55
|
+
const componentName = "My$Component";
|
|
56
|
+
const id = "12#3";
|
|
57
|
+
const prefix = "my Prefix";
|
|
58
|
+
|
|
59
|
+
const uniqueKey = generateUniqueConfigKey(componentName, id, prefix);
|
|
60
|
+
|
|
61
|
+
// Ensure the unique key does not contain any special characters or spaces
|
|
62
|
+
expect(uniqueKey).to.match(/^[a-zA-Z0-9_]+$/);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("should include the browser location without parameters", () => {
|
|
66
|
+
const componentName = "MyComponent";
|
|
67
|
+
const id = "123";
|
|
68
|
+
const prefix = "myPrefix";
|
|
69
|
+
|
|
70
|
+
const uniqueKey = generateUniqueConfigKey(componentName, id, prefix);
|
|
71
|
+
|
|
72
|
+
// Ensure the unique key contains the browser location without parameters
|
|
73
|
+
const urlWithoutParams = window.location.href.split("?")[0];
|
|
74
|
+
const sanitizedUrl = urlWithoutParams
|
|
75
|
+
.replace(/[^\w\s]/gi, "_")
|
|
76
|
+
.replace(/\s+/g, "_");
|
|
77
|
+
expect(uniqueKey).to.include(sanitizedUrl);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("should generate explicit instance keys without the browser location", () => {
|
|
81
|
+
const uniqueKey = generateConfigKey(
|
|
82
|
+
"datatable",
|
|
83
|
+
"orders.default",
|
|
84
|
+
"filter",
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
expect(uniqueKey).to.equal("filter_datatable_orders_default");
|
|
88
|
+
expect(uniqueKey).to.not.include("example_test");
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("should prefer explicit instance keys over legacy URL keys", () => {
|
|
92
|
+
const uniqueKey = generateComponentConfigKey(
|
|
93
|
+
"datatable",
|
|
94
|
+
"dom-id",
|
|
95
|
+
"stored-order",
|
|
96
|
+
{
|
|
97
|
+
instanceKey: "orders.default",
|
|
98
|
+
},
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
expect(uniqueKey).to.equal("stored_order_datatable_orders_default");
|
|
102
|
+
expect(uniqueKey).to.not.include("dom-id");
|
|
103
|
+
expect(uniqueKey).to.not.include("example_test");
|
|
104
|
+
});
|
|
105
|
+
});
|
package/test/web/import.js
CHANGED
|
@@ -25,8 +25,10 @@ import "../cases/components/notify/message.mjs";
|
|
|
25
25
|
import "../cases/components/notify/notify.mjs";
|
|
26
26
|
import "../cases/components/host/host.mjs";
|
|
27
27
|
import "../cases/components/host/overlay.mjs";
|
|
28
|
+
import "../cases/components/host/config-manager.mjs";
|
|
28
29
|
import "../cases/components/host/util.mjs";
|
|
29
30
|
import "../cases/components/host/details.mjs";
|
|
31
|
+
import "../cases/components/datatable/config-keys.mjs";
|
|
30
32
|
import "../cases/components/datatable/writeback-sanitizer.mjs";
|
|
31
33
|
import "../cases/components/datatable/pagination.mjs";
|
|
32
34
|
import "../cases/components/navigation/site-navigation.mjs";
|