@schukai/monster 4.103.1 → 4.105.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/CHANGELOG.md +21 -0
- package/package.json +1 -1
- package/source/components/datatable/dataset.mjs +63 -4
- package/source/components/datatable/datatable.mjs +6 -0
- package/source/components/form/form.mjs +2 -0
- package/source/components/layout/tabs.mjs +10 -0
- package/source/dom/customelement.mjs +8 -0
- package/source/dom/updater.mjs +52 -5
- package/source/types/version.mjs +1 -1
- package/test/cases/components/datatable/writeback-sanitizer.mjs +104 -0
- package/test/cases/components/layout/tabs.mjs +24 -1
- package/test/cases/monster.mjs +1 -1
- package/test/web/test.html +2 -2
- package/test/web/tests.js +338 -55
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,27 @@
|
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
|
|
5
|
+
## [4.105.0] - 2026-01-22
|
|
6
|
+
|
|
7
|
+
### Add Features
|
|
8
|
+
|
|
9
|
+
- Shorten tab labels for better readability
|
|
10
|
+
- Add write-back sanitizer for cleaner dataset diffs
|
|
11
|
+
### Bug Fixes
|
|
12
|
+
|
|
13
|
+
- tabs labels
|
|
14
|
+
- update
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
## [4.104.0] - 2026-01-19
|
|
19
|
+
|
|
20
|
+
### Add Features
|
|
21
|
+
|
|
22
|
+
- Add batching feature for updates in the DataTable and CustomElement
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|
|
5
26
|
## [4.103.1] - 2026-01-19
|
|
6
27
|
|
|
7
28
|
### Bug Fixes
|
package/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"author":"Volker Schukai","dependencies":{"@floating-ui/dom":"^1.7.4","@popperjs/core":"^2.11.8"},"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.4","@popperjs/core":"^2.11.8"},"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.105.0"}
|
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
getDocument,
|
|
27
27
|
getWindow,
|
|
28
28
|
} from "../../dom/util.mjs";
|
|
29
|
-
import { isString } from "../../types/is.mjs";
|
|
29
|
+
import { isArray, isObject, isString } from "../../types/is.mjs";
|
|
30
30
|
import { Observer } from "../../types/observer.mjs";
|
|
31
31
|
import {
|
|
32
32
|
ATTRIBUTE_DATASOURCE_SELECTOR,
|
|
@@ -40,6 +40,8 @@ import {
|
|
|
40
40
|
} from "./util.mjs";
|
|
41
41
|
import { FormStyleSheet } from "../stylesheet/form.mjs";
|
|
42
42
|
import { addErrorAttribute } from "../../dom/error.mjs";
|
|
43
|
+
import { Pipe } from "../../data/pipe.mjs";
|
|
44
|
+
import { clone } from "../../util/clone.mjs";
|
|
43
45
|
|
|
44
46
|
export { DataSet };
|
|
45
47
|
|
|
@@ -135,6 +137,11 @@ class DataSet extends CustomElement {
|
|
|
135
137
|
"input, select, textarea, monster-select, monster-toggle-switch",
|
|
136
138
|
},
|
|
137
139
|
|
|
140
|
+
writeBack: {
|
|
141
|
+
transformer: null,
|
|
142
|
+
callbacks: null,
|
|
143
|
+
},
|
|
144
|
+
|
|
138
145
|
data: {},
|
|
139
146
|
});
|
|
140
147
|
|
|
@@ -204,6 +211,17 @@ class DataSet extends CustomElement {
|
|
|
204
211
|
pathWithIndex = String(index);
|
|
205
212
|
}
|
|
206
213
|
|
|
214
|
+
let sanitizedData;
|
|
215
|
+
try {
|
|
216
|
+
sanitizedData = applyWriteBackTransform.call(
|
|
217
|
+
this,
|
|
218
|
+
clone(internalData),
|
|
219
|
+
);
|
|
220
|
+
} catch (e) {
|
|
221
|
+
reject(e);
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
|
|
207
225
|
if (this.getOption("features.skipNoopWrite") === true) {
|
|
208
226
|
const currentData = this[datasourceLinkedElementSymbol]?.data;
|
|
209
227
|
let currentValue;
|
|
@@ -214,7 +232,7 @@ class DataSet extends CustomElement {
|
|
|
214
232
|
currentValue = undefined;
|
|
215
233
|
}
|
|
216
234
|
}
|
|
217
|
-
if (diff(currentValue,
|
|
235
|
+
if (diff(currentValue, sanitizedData).length === 0) {
|
|
218
236
|
resolve();
|
|
219
237
|
return;
|
|
220
238
|
}
|
|
@@ -223,7 +241,7 @@ class DataSet extends CustomElement {
|
|
|
223
241
|
if (this.getOption("logLevel") === "debug") {
|
|
224
242
|
console.log("monster-dataset: write", {
|
|
225
243
|
path: pathWithIndex,
|
|
226
|
-
data:
|
|
244
|
+
data: sanitizedData,
|
|
227
245
|
});
|
|
228
246
|
}
|
|
229
247
|
|
|
@@ -236,7 +254,7 @@ class DataSet extends CustomElement {
|
|
|
236
254
|
const unref = JSON.stringify(data);
|
|
237
255
|
const ref = JSON.parse(unref);
|
|
238
256
|
|
|
239
|
-
new Pathfinder(ref).setVia(pathWithIndex,
|
|
257
|
+
new Pathfinder(ref).setVia(pathWithIndex, sanitizedData);
|
|
240
258
|
|
|
241
259
|
this[datasourceLinkedElementSymbol].data = ref;
|
|
242
260
|
|
|
@@ -447,4 +465,45 @@ function getTemplate() {
|
|
|
447
465
|
`;
|
|
448
466
|
}
|
|
449
467
|
|
|
468
|
+
/**
|
|
469
|
+
* @private
|
|
470
|
+
* @param {Object|Array} data
|
|
471
|
+
* @return {Object|Array}
|
|
472
|
+
*/
|
|
473
|
+
function applyWriteBackTransform(data) {
|
|
474
|
+
const transformer = this.getOption("writeBack.transformer");
|
|
475
|
+
if (transformer === undefined || transformer === null || transformer === "") {
|
|
476
|
+
return clone(data);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
const pipe = new Pipe(transformer);
|
|
480
|
+
const callbacks = this.getOption("writeBack.callbacks");
|
|
481
|
+
|
|
482
|
+
if (isArray(callbacks)) {
|
|
483
|
+
for (const callback of callbacks) {
|
|
484
|
+
if (typeof callback === "function") {
|
|
485
|
+
pipe.setCallback(callback);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
if (isObject(callbacks)) {
|
|
491
|
+
for (const key in callbacks) {
|
|
492
|
+
if (
|
|
493
|
+
Object.prototype.hasOwnProperty.call(callbacks, key) &&
|
|
494
|
+
typeof callbacks[key] === "function"
|
|
495
|
+
) {
|
|
496
|
+
pipe.setCallback(key, callbacks[key]);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
const result = pipe.run(data);
|
|
502
|
+
if (typeof result === "undefined") {
|
|
503
|
+
return clone(data);
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
return clone(result);
|
|
507
|
+
}
|
|
508
|
+
|
|
450
509
|
registerCustomElement(DataSet);
|
|
@@ -202,6 +202,8 @@ class DataTable extends CustomElement {
|
|
|
202
202
|
* @property {string} copy.quoteOpen Quote open character
|
|
203
203
|
* @property {string} copy.quoteClose Quote close character
|
|
204
204
|
* @property {string} copy.rowBreak Row break character
|
|
205
|
+
* @property {Object} updater Updater configuration
|
|
206
|
+
* @property {boolean} updater.batchUpdates Enables batched updater content/attribute updates
|
|
205
207
|
* @property {Object} templateMapping Template mapping
|
|
206
208
|
* @property {string} templateMapping.row-key Row key
|
|
207
209
|
* @property {string} templateMapping.filter-id Filter id
|
|
@@ -257,6 +259,10 @@ class DataTable extends CustomElement {
|
|
|
257
259
|
rowBreak: "\n",
|
|
258
260
|
},
|
|
259
261
|
|
|
262
|
+
updater: {
|
|
263
|
+
batchUpdates: true,
|
|
264
|
+
},
|
|
265
|
+
|
|
260
266
|
templateMapping: {
|
|
261
267
|
"row-key": null,
|
|
262
268
|
"filter-id": null,
|
|
@@ -1326,6 +1326,16 @@ function getButtonLabel(node) {
|
|
|
1326
1326
|
label = "";
|
|
1327
1327
|
}
|
|
1328
1328
|
|
|
1329
|
+
if (setLabel === true) {
|
|
1330
|
+
if (label.trim() === "") {
|
|
1331
|
+
label = node.textContent || "";
|
|
1332
|
+
}
|
|
1333
|
+
label = label.replace(/\s+/g, " ").trim();
|
|
1334
|
+
const words = label.split(" ").filter((word) => word !== "");
|
|
1335
|
+
if (words.length > 3) {
|
|
1336
|
+
label = `${words.slice(0, 3).join(" ")}…`;
|
|
1337
|
+
}
|
|
1338
|
+
}
|
|
1329
1339
|
label = label.trim();
|
|
1330
1340
|
|
|
1331
1341
|
if (label === "") {
|
|
@@ -314,6 +314,8 @@ class CustomElement extends HTMLElement {
|
|
|
314
314
|
* @property {Function} templateFormatter.marker.close=null Specifies the closing marker for the templates.
|
|
315
315
|
* @property {Boolean} templateFormatter.i18n=false Specifies whether the templates should be formatted with i18n.
|
|
316
316
|
* @property {Boolean} eventProcessing=false Specifies whether the control processes events.
|
|
317
|
+
* @property {Object} updater Specifies updater options.
|
|
318
|
+
* @property {Boolean} updater.batchUpdates=false Batches updater content/attribute updates per diff.
|
|
317
319
|
* @since 1.8.0
|
|
318
320
|
*/
|
|
319
321
|
get defaults() {
|
|
@@ -334,6 +336,9 @@ class CustomElement extends HTMLElement {
|
|
|
334
336
|
},
|
|
335
337
|
|
|
336
338
|
eventProcessing: false,
|
|
339
|
+
updater: {
|
|
340
|
+
batchUpdates: false,
|
|
341
|
+
},
|
|
337
342
|
};
|
|
338
343
|
}
|
|
339
344
|
|
|
@@ -651,6 +656,9 @@ class CustomElement extends HTMLElement {
|
|
|
651
656
|
if (this.getOption("eventProcessing") === true) {
|
|
652
657
|
cfg.eventProcessing = true;
|
|
653
658
|
}
|
|
659
|
+
if (this.getOption("updater.batchUpdates") === true) {
|
|
660
|
+
cfg.batchUpdates = true;
|
|
661
|
+
}
|
|
654
662
|
addObjectWithUpdaterToElement.call(
|
|
655
663
|
this,
|
|
656
664
|
nodeList,
|
package/source/dom/updater.mjs
CHANGED
|
@@ -71,6 +71,8 @@ const pendingDiffsSymbol = Symbol("pendingDiffs");
|
|
|
71
71
|
* @type {symbol}
|
|
72
72
|
*/
|
|
73
73
|
const processingSymbol = Symbol("processing");
|
|
74
|
+
const processQueueSymbol = Symbol("processQueue");
|
|
75
|
+
const applyChangeSymbol = Symbol("applyChange");
|
|
74
76
|
const updaterRootSymbol = Symbol.for("@schukai/monster/dom/@@updater-root");
|
|
75
77
|
|
|
76
78
|
/**
|
|
@@ -125,6 +127,9 @@ class Updater extends Base {
|
|
|
125
127
|
callbacks: new Map(),
|
|
126
128
|
eventTypes: ["keyup", "click", "change", "drop", "touchend", "input"],
|
|
127
129
|
subject: subject,
|
|
130
|
+
features: {
|
|
131
|
+
batchUpdates: false,
|
|
132
|
+
},
|
|
128
133
|
};
|
|
129
134
|
|
|
130
135
|
this[internalSymbol].callbacks.set(
|
|
@@ -144,7 +149,7 @@ class Updater extends Base {
|
|
|
144
149
|
return Promise.resolve();
|
|
145
150
|
}
|
|
146
151
|
this[pendingDiffsSymbol].push(diffResult);
|
|
147
|
-
return this
|
|
152
|
+
return this[processQueueSymbol]();
|
|
148
153
|
}),
|
|
149
154
|
);
|
|
150
155
|
}
|
|
@@ -153,7 +158,7 @@ class Updater extends Base {
|
|
|
153
158
|
* @private
|
|
154
159
|
* @return {Promise}
|
|
155
160
|
*/
|
|
156
|
-
async
|
|
161
|
+
async [processQueueSymbol]() {
|
|
157
162
|
if (this[processingSymbol]) {
|
|
158
163
|
return Promise.resolve();
|
|
159
164
|
}
|
|
@@ -162,8 +167,34 @@ class Updater extends Base {
|
|
|
162
167
|
try {
|
|
163
168
|
while (this[pendingDiffsSymbol].length) {
|
|
164
169
|
const diffResult = this[pendingDiffsSymbol].shift();
|
|
165
|
-
|
|
166
|
-
|
|
170
|
+
if (this[internalSymbol].features.batchUpdates === true) {
|
|
171
|
+
const updatePaths = new Map();
|
|
172
|
+
for (const change of Object.values(diffResult)) {
|
|
173
|
+
removeElement.call(this, change);
|
|
174
|
+
insertElement.call(this, change);
|
|
175
|
+
|
|
176
|
+
const path = isArray(change?.["path"]) ? change["path"] : null;
|
|
177
|
+
if (!path) {
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
if (path.length === 0) {
|
|
181
|
+
updatePaths.set("", path);
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
const root = path[0];
|
|
185
|
+
if (!updatePaths.has(root)) {
|
|
186
|
+
updatePaths.set(root, [root]);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
for (const path of updatePaths.values()) {
|
|
191
|
+
updateContent.call(this, { path });
|
|
192
|
+
updateAttributes.call(this, { path });
|
|
193
|
+
}
|
|
194
|
+
} else {
|
|
195
|
+
for (const change of Object.values(diffResult)) {
|
|
196
|
+
await this[applyChangeSymbol](change);
|
|
197
|
+
}
|
|
167
198
|
}
|
|
168
199
|
}
|
|
169
200
|
} catch (err) {
|
|
@@ -174,7 +205,7 @@ class Updater extends Base {
|
|
|
174
205
|
}
|
|
175
206
|
|
|
176
207
|
/** @private **/
|
|
177
|
-
async
|
|
208
|
+
async [applyChangeSymbol](change) {
|
|
178
209
|
removeElement.call(this, change);
|
|
179
210
|
insertElement.call(this, change);
|
|
180
211
|
updateContent.call(this, change);
|
|
@@ -195,6 +226,18 @@ class Updater extends Base {
|
|
|
195
226
|
return this;
|
|
196
227
|
}
|
|
197
228
|
|
|
229
|
+
/**
|
|
230
|
+
* Enable or disable batched update processing.
|
|
231
|
+
*
|
|
232
|
+
* @since 4.104.0
|
|
233
|
+
* @param {boolean} enabled
|
|
234
|
+
* @return {Updater}
|
|
235
|
+
*/
|
|
236
|
+
setBatchUpdates(enabled) {
|
|
237
|
+
this[internalSymbol].features.batchUpdates = enabled === true;
|
|
238
|
+
return this;
|
|
239
|
+
}
|
|
240
|
+
|
|
198
241
|
/**
|
|
199
242
|
* With this method, the eventlisteners are hooked in and the magic begins.
|
|
200
243
|
*
|
|
@@ -1097,6 +1140,7 @@ function handleInputControlAttributeUpdate(element, name, value) {
|
|
|
1097
1140
|
* @param {object} config
|
|
1098
1141
|
*
|
|
1099
1142
|
* Config: enableEventProcessing {boolean} - default: false - enables the event processing
|
|
1143
|
+
* Config: batchUpdates {boolean} - default: false - batches updateContent/updateAttributes per diff
|
|
1100
1144
|
*
|
|
1101
1145
|
* @return {Promise[]}
|
|
1102
1146
|
* @license AGPLv3
|
|
@@ -1169,6 +1213,9 @@ function addObjectWithUpdaterToElement(elements, symbol, object, config = {}) {
|
|
|
1169
1213
|
|
|
1170
1214
|
result.push(
|
|
1171
1215
|
u.run().then(() => {
|
|
1216
|
+
if (config.batchUpdates === true) {
|
|
1217
|
+
u.setBatchUpdates(true);
|
|
1218
|
+
}
|
|
1172
1219
|
if (config.eventProcessing === true) {
|
|
1173
1220
|
u.enableEventProcessing();
|
|
1174
1221
|
}
|
package/source/types/version.mjs
CHANGED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import * as chai from 'chai';
|
|
2
|
+
import {chaiDom} from "../../../util/chai-dom.mjs";
|
|
3
|
+
import {initJSDOM} from "../../../util/jsdom.mjs";
|
|
4
|
+
|
|
5
|
+
let expect = chai.expect;
|
|
6
|
+
chai.use(chaiDom);
|
|
7
|
+
|
|
8
|
+
let Dataset;
|
|
9
|
+
|
|
10
|
+
function buildDatasource(id, payload) {
|
|
11
|
+
const ds = document.createElement('monster-datasource-dom');
|
|
12
|
+
ds.id = id;
|
|
13
|
+
ds.innerHTML = `<script type="application/json">${JSON.stringify(payload)}</script>`;
|
|
14
|
+
return ds;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
describe('Dataset writeBack sanitizer', function () {
|
|
18
|
+
|
|
19
|
+
before(async function () {
|
|
20
|
+
await initJSDOM();
|
|
21
|
+
await import("element-internals-polyfill").catch(() => {});
|
|
22
|
+
await import("../../../../source/components/datatable/datasource/dom.mjs");
|
|
23
|
+
const mod = await import("../../../../source/components/datatable/dataset.mjs");
|
|
24
|
+
Dataset = mod['DataSet'];
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
beforeEach(() => {
|
|
28
|
+
const mocks = document.getElementById('mocks');
|
|
29
|
+
mocks.innerHTML = '';
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
afterEach(() => {
|
|
33
|
+
const mocks = document.getElementById('mocks');
|
|
34
|
+
mocks.innerHTML = '';
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('sanitizes data before write and avoids side effects', async function () {
|
|
38
|
+
const mocks = document.getElementById('mocks');
|
|
39
|
+
const ds = buildDatasource('ds-sanitize', [{}]);
|
|
40
|
+
mocks.appendChild(ds);
|
|
41
|
+
|
|
42
|
+
const dataset = document.createElement('monster-dataset');
|
|
43
|
+
expect(dataset).is.instanceof(Dataset);
|
|
44
|
+
|
|
45
|
+
dataset.setOption('datasource.selector', '#ds-sanitize');
|
|
46
|
+
dataset.setOption('mapping.data', '');
|
|
47
|
+
|
|
48
|
+
const initial = {name: '', email: 'a@b', tags: []};
|
|
49
|
+
dataset.setOption('data', initial);
|
|
50
|
+
|
|
51
|
+
dataset.setOption('writeBack.transformer', 'call:sanitize');
|
|
52
|
+
dataset.setOption('writeBack.callbacks', {
|
|
53
|
+
sanitize: (obj) => {
|
|
54
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
55
|
+
if (value === '' || value === null || value === undefined) {
|
|
56
|
+
delete obj[key];
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
if (Array.isArray(value) && value.length === 0) {
|
|
60
|
+
delete obj[key];
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return obj;
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
mocks.appendChild(dataset);
|
|
68
|
+
|
|
69
|
+
await new Promise((resolve) => setTimeout(resolve, 30));
|
|
70
|
+
await dataset.write();
|
|
71
|
+
|
|
72
|
+
expect(ds.data[0]).deep.equal({email: 'a@b'});
|
|
73
|
+
|
|
74
|
+
const internal = dataset.getInternalUpdateCloneData();
|
|
75
|
+
expect(internal.data).deep.equal({name: '', email: 'a@b', tags: []});
|
|
76
|
+
expect(initial).deep.equal({name: '', email: 'a@b', tags: []});
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('uses original data when sanitizer returns undefined', async function () {
|
|
80
|
+
const mocks = document.getElementById('mocks');
|
|
81
|
+
const ds = buildDatasource('ds-undefined', [{}]);
|
|
82
|
+
mocks.appendChild(ds);
|
|
83
|
+
|
|
84
|
+
const dataset = document.createElement('monster-dataset');
|
|
85
|
+
dataset.setOption('datasource.selector', '#ds-undefined');
|
|
86
|
+
dataset.setOption('mapping.data', '');
|
|
87
|
+
|
|
88
|
+
const initial = {name: '', email: 'a@b'};
|
|
89
|
+
dataset.setOption('data', initial);
|
|
90
|
+
|
|
91
|
+
dataset.setOption('writeBack.transformer', 'call:noop');
|
|
92
|
+
dataset.setOption('writeBack.callbacks', {
|
|
93
|
+
noop: () => undefined,
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
mocks.appendChild(dataset);
|
|
97
|
+
|
|
98
|
+
await new Promise((resolve) => setTimeout(resolve, 30));
|
|
99
|
+
await dataset.write();
|
|
100
|
+
|
|
101
|
+
expect(ds.data[0]).deep.equal({name: '', email: 'a@b'});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
});
|
|
@@ -92,7 +92,30 @@ describe('Tabs', function () {
|
|
|
92
92
|
|
|
93
93
|
});
|
|
94
94
|
|
|
95
|
+
it('should shorten label from content when no explicit label is provided', function (done) {
|
|
96
|
+
|
|
97
|
+
let mocks = document.getElementById('mocks');
|
|
98
|
+
mocks.innerHTML = html1;
|
|
99
|
+
|
|
100
|
+
setTimeout(() => {
|
|
101
|
+
try {
|
|
102
|
+
const tabs = document.getElementById('mytabs');
|
|
103
|
+
expect(tabs).is.instanceof(Tabs);
|
|
104
|
+
|
|
105
|
+
setTimeout(() => {
|
|
106
|
+
const buttons = tabs.shadowRoot.querySelectorAll('button[part=button]');
|
|
107
|
+
const labelSpan = buttons[2].querySelector('span[data-monster-replace]');
|
|
108
|
+
expect(labelSpan).to.not.equal(null);
|
|
109
|
+
expect(labelSpan.textContent.trim()).to.equal('Das ist tab…');
|
|
110
|
+
done();
|
|
111
|
+
}, 100);
|
|
112
|
+
} catch (e) {
|
|
113
|
+
return done(e);
|
|
114
|
+
}
|
|
115
|
+
}, 0);
|
|
116
|
+
});
|
|
117
|
+
|
|
95
118
|
});
|
|
96
119
|
|
|
97
120
|
|
|
98
|
-
});
|
|
121
|
+
});
|
package/test/cases/monster.mjs
CHANGED
package/test/web/test.html
CHANGED
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
</head>
|
|
10
10
|
<body>
|
|
11
11
|
<div id="headline" style="display: flex;align-items: center;justify-content: center;flex-direction: column;">
|
|
12
|
-
<h1 style='margin-bottom: 0.1em;'>Monster 4.
|
|
13
|
-
<div id="lastupdate" style='font-size:0.7em'>last update
|
|
12
|
+
<h1 style='margin-bottom: 0.1em;'>Monster 4.104.0</h1>
|
|
13
|
+
<div id="lastupdate" style='font-size:0.7em'>last update Mi 21. Jan 21:19:58 CET 2026</div>
|
|
14
14
|
</div>
|
|
15
15
|
<div id="mocha-errors"
|
|
16
16
|
style="color: red;font-weight: bold;display: flex;align-items: center;justify-content: center;flex-direction: column;margin:20px;"></div>
|