@schukai/monster 4.129.6 → 4.129.8
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/form/confirm-button.mjs +1 -0
- package/source/components/form/util/floating-ui.mjs +172 -22
- package/test/cases/components/form/confirm-button.mjs +30 -14
- package/test/cases/components/form/context-help.mjs +71 -0
- package/test/cases/components/form/popper-button.mjs +21 -0
- package/test/cases/components/form/select.mjs +32 -78
- package/test/cases/dom/updater.mjs +15 -4
- package/test/web/tests.js +5800 -6811
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.129.
|
|
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.129.8"}
|
|
@@ -98,6 +98,7 @@ class ConfirmButton extends PopperButton {
|
|
|
98
98
|
},
|
|
99
99
|
});
|
|
100
100
|
|
|
101
|
+
obj["popper"]["strategy"] = "fixed";
|
|
101
102
|
obj["classes"]["confirmButton"] = "monster-button-primary";
|
|
102
103
|
obj["classes"]["cancelButton"] = "monster-button-secondary";
|
|
103
104
|
obj["actions"]["cancel"] = (e) => {
|
|
@@ -28,6 +28,7 @@ import { Processing } from "../../../util/processing.mjs";
|
|
|
28
28
|
|
|
29
29
|
export {
|
|
30
30
|
closePositionedPopper,
|
|
31
|
+
resolveClippingBoundaryElement,
|
|
31
32
|
isPositionedPopperOpen,
|
|
32
33
|
openPositionedPopper,
|
|
33
34
|
positionPopper,
|
|
@@ -43,7 +44,7 @@ const autoUpdateCleanupMap = new WeakMap();
|
|
|
43
44
|
* @return {Promise|*}
|
|
44
45
|
*/
|
|
45
46
|
function positionPopper(controlElement, popperElement, options) {
|
|
46
|
-
const config = normalizePopperConfig(options);
|
|
47
|
+
const config = normalizePopperConfig(options, controlElement, popperElement);
|
|
47
48
|
|
|
48
49
|
return new Processing(() => {
|
|
49
50
|
enableFloatingPositioning(controlElement, popperElement, config);
|
|
@@ -51,15 +52,15 @@ function positionPopper(controlElement, popperElement, options) {
|
|
|
51
52
|
}
|
|
52
53
|
|
|
53
54
|
function openPositionedPopper(controlElement, popperElement, options) {
|
|
54
|
-
const config = normalizePopperConfig(options);
|
|
55
|
+
const config = normalizePopperConfig(options, controlElement, popperElement);
|
|
55
56
|
|
|
56
57
|
stopAutoUpdate(popperElement);
|
|
57
58
|
popperElement.style.display = "block";
|
|
59
|
+
popperElement.style.position = config.strategy;
|
|
58
60
|
popperElement.style.removeProperty("transform");
|
|
59
61
|
|
|
60
62
|
// Keep the call signature stable even though only Floating UI is used now.
|
|
61
63
|
void controlElement;
|
|
62
|
-
void config;
|
|
63
64
|
}
|
|
64
65
|
|
|
65
66
|
function enableFloatingPositioning(controlElement, popperElement, config) {
|
|
@@ -93,6 +94,7 @@ function syncFloatingPopover(controlElement, popperElement, config) {
|
|
|
93
94
|
Object.assign(popperElement.style, {
|
|
94
95
|
top: "0",
|
|
95
96
|
left: "0",
|
|
97
|
+
position: config.strategy,
|
|
96
98
|
transform: `translate(${roundByDPR(x)}px,${roundByDPR(y)}px)`,
|
|
97
99
|
});
|
|
98
100
|
|
|
@@ -106,6 +108,7 @@ function closePositionedPopper(popperElement) {
|
|
|
106
108
|
stopAutoUpdate(popperElement);
|
|
107
109
|
popperElement.style.display = "none";
|
|
108
110
|
popperElement.style.removeProperty("visibility");
|
|
111
|
+
popperElement.style.removeProperty("position");
|
|
109
112
|
popperElement.style.removeProperty("transform");
|
|
110
113
|
}
|
|
111
114
|
|
|
@@ -113,23 +116,39 @@ function isPositionedPopperOpen(popperElement) {
|
|
|
113
116
|
return popperElement.style.display === "block";
|
|
114
117
|
}
|
|
115
118
|
|
|
116
|
-
function normalizePopperConfig(options) {
|
|
119
|
+
function normalizePopperConfig(options, controlElement, popperElement) {
|
|
117
120
|
const config = Object.assign(
|
|
118
121
|
{},
|
|
119
122
|
{
|
|
120
123
|
placement: "top",
|
|
121
124
|
engine: "floating",
|
|
125
|
+
strategy: "absolute",
|
|
122
126
|
},
|
|
123
127
|
options,
|
|
124
128
|
);
|
|
125
129
|
|
|
130
|
+
config.boundaryElement = resolveClippingBoundaryElement(
|
|
131
|
+
controlElement,
|
|
132
|
+
popperElement,
|
|
133
|
+
);
|
|
134
|
+
config.detectOverflowOptions = buildDetectOverflowOptions(
|
|
135
|
+
config.boundaryElement,
|
|
136
|
+
);
|
|
126
137
|
config.middleware = normalizeMiddleware(config);
|
|
127
138
|
config.middlewareTokens = config.middleware.filter((line) => isString(line));
|
|
128
139
|
config.floatingMiddleware = buildFloatingMiddleware(
|
|
129
140
|
config.middleware,
|
|
130
141
|
config.placement,
|
|
142
|
+
config.detectOverflowOptions,
|
|
143
|
+
popperElement,
|
|
131
144
|
);
|
|
132
145
|
|
|
146
|
+
if (!config.middlewareTokens.includes("size")) {
|
|
147
|
+
config.floatingMiddleware.push(
|
|
148
|
+
createAdaptiveSizeMiddleware(config.detectOverflowOptions, popperElement),
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
|
|
133
152
|
return config;
|
|
134
153
|
}
|
|
135
154
|
|
|
@@ -146,7 +165,12 @@ function normalizeMiddleware(config) {
|
|
|
146
165
|
return [];
|
|
147
166
|
}
|
|
148
167
|
|
|
149
|
-
function buildFloatingMiddleware(
|
|
168
|
+
function buildFloatingMiddleware(
|
|
169
|
+
middleware,
|
|
170
|
+
placement,
|
|
171
|
+
detectOverflowOptions,
|
|
172
|
+
popperElement,
|
|
173
|
+
) {
|
|
150
174
|
const result = [...middleware];
|
|
151
175
|
|
|
152
176
|
for (const key in result) {
|
|
@@ -166,10 +190,10 @@ function buildFloatingMiddleware(middleware, placement) {
|
|
|
166
190
|
|
|
167
191
|
switch (fn) {
|
|
168
192
|
case "flip":
|
|
169
|
-
result[key] = flip();
|
|
193
|
+
result[key] = flip(detectOverflowOptions);
|
|
170
194
|
break;
|
|
171
195
|
case "shift":
|
|
172
|
-
result[key] = shift();
|
|
196
|
+
result[key] = shift(detectOverflowOptions);
|
|
173
197
|
break;
|
|
174
198
|
case "autoPlacement":
|
|
175
199
|
let defaultAllowedPlacements = ["top", "bottom", "left", "right"];
|
|
@@ -189,31 +213,28 @@ function buildFloatingMiddleware(middleware, placement) {
|
|
|
189
213
|
}
|
|
190
214
|
defaultAllowedPlacements.unshift(placement);
|
|
191
215
|
|
|
192
|
-
result[key] = autoPlacement(
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
216
|
+
result[key] = autoPlacement(
|
|
217
|
+
Object.assign({}, detectOverflowOptions, {
|
|
218
|
+
crossAxis: true,
|
|
219
|
+
autoAlignment: true,
|
|
220
|
+
allowedPlacements: defaultAllowedPlacements,
|
|
221
|
+
}),
|
|
222
|
+
);
|
|
197
223
|
break;
|
|
198
224
|
case "arrow":
|
|
199
225
|
result[key] = null;
|
|
200
226
|
break;
|
|
201
227
|
case "size":
|
|
202
|
-
result[key] =
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
maxWidth: `${availableWidth}px`,
|
|
207
|
-
maxHeight: `${availableHeight}px`,
|
|
208
|
-
});
|
|
209
|
-
},
|
|
210
|
-
});
|
|
228
|
+
result[key] = createAdaptiveSizeMiddleware(
|
|
229
|
+
detectOverflowOptions,
|
|
230
|
+
popperElement,
|
|
231
|
+
);
|
|
211
232
|
break;
|
|
212
233
|
case "offset":
|
|
213
234
|
result[key] = offset(parseInt(kv?.shift()) || 10);
|
|
214
235
|
break;
|
|
215
236
|
case "hide":
|
|
216
|
-
result[key] = hide();
|
|
237
|
+
result[key] = hide(detectOverflowOptions);
|
|
217
238
|
break;
|
|
218
239
|
default:
|
|
219
240
|
throw new Error(`Unknown function: ${fn}`);
|
|
@@ -223,6 +244,135 @@ function buildFloatingMiddleware(middleware, placement) {
|
|
|
223
244
|
return result.filter(Boolean);
|
|
224
245
|
}
|
|
225
246
|
|
|
247
|
+
function createAdaptiveSizeMiddleware(detectOverflowOptions, popperElement) {
|
|
248
|
+
return size(
|
|
249
|
+
Object.assign({}, detectOverflowOptions, {
|
|
250
|
+
apply({ availableWidth, availableHeight, elements }) {
|
|
251
|
+
const floatingElement = elements?.floating || popperElement;
|
|
252
|
+
if (!(floatingElement instanceof HTMLElement)) {
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const maxWidth = clampAvailableDimension(
|
|
257
|
+
availableWidth,
|
|
258
|
+
readMaxDimension(floatingElement, "maxWidth"),
|
|
259
|
+
);
|
|
260
|
+
const maxHeight = clampAvailableDimension(
|
|
261
|
+
availableHeight,
|
|
262
|
+
readMaxDimension(floatingElement, "maxHeight"),
|
|
263
|
+
);
|
|
264
|
+
const nextStyle = {
|
|
265
|
+
boxSizing: "border-box",
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
if (Number.isFinite(maxWidth) && maxWidth > 0) {
|
|
269
|
+
nextStyle.maxWidth = `${maxWidth}px`;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (Number.isFinite(maxHeight) && maxHeight > 0) {
|
|
273
|
+
nextStyle.maxHeight = `${maxHeight}px`;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
Object.assign(floatingElement.style, nextStyle);
|
|
277
|
+
},
|
|
278
|
+
}),
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function buildDetectOverflowOptions(boundaryElement) {
|
|
283
|
+
const result = {
|
|
284
|
+
rootBoundary: "viewport",
|
|
285
|
+
padding: 0,
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
if (boundaryElement instanceof HTMLElement) {
|
|
289
|
+
result.boundary = boundaryElement;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
return result;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function resolveClippingBoundaryElement(...elements) {
|
|
296
|
+
for (const element of elements) {
|
|
297
|
+
const clippingBoundary = findNearestClippingContainer(element);
|
|
298
|
+
if (clippingBoundary instanceof HTMLElement) {
|
|
299
|
+
return clippingBoundary;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
return null;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function findNearestClippingContainer(element) {
|
|
307
|
+
let current = getComposedParent(element);
|
|
308
|
+
|
|
309
|
+
while (current) {
|
|
310
|
+
if (
|
|
311
|
+
current instanceof HTMLElement &&
|
|
312
|
+
isClippingContainer(getComputedStyle(current))
|
|
313
|
+
) {
|
|
314
|
+
return current;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
current = getComposedParent(current);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
return null;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function getComposedParent(node) {
|
|
324
|
+
if (!node) {
|
|
325
|
+
return null;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
if (node instanceof ShadowRoot) {
|
|
329
|
+
return node.host || null;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
if (node.parentElement instanceof HTMLElement) {
|
|
333
|
+
return node.parentElement;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
const rootNode = node.getRootNode?.();
|
|
337
|
+
if (rootNode instanceof ShadowRoot) {
|
|
338
|
+
return rootNode.host || null;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
return node.parentNode || null;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function isClippingContainer(style) {
|
|
345
|
+
if (!style) {
|
|
346
|
+
return false;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
return [style.overflow, style.overflowX, style.overflowY].some((value) => {
|
|
350
|
+
return ["hidden", "auto", "scroll", "clip"].includes(value);
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
function readMaxDimension(element, property) {
|
|
355
|
+
const rawValue = getComputedStyle(element)?.[property];
|
|
356
|
+
if (!rawValue || rawValue === "none") {
|
|
357
|
+
return Infinity;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
const value = Number.parseFloat(rawValue);
|
|
361
|
+
return Number.isFinite(value) ? value : Infinity;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function clampAvailableDimension(available, configuredMax) {
|
|
365
|
+
if (!Number.isFinite(available) || available <= 0) {
|
|
366
|
+
return null;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
if (!Number.isFinite(configuredMax)) {
|
|
370
|
+
return available;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
return Math.min(available, configuredMax);
|
|
374
|
+
}
|
|
375
|
+
|
|
226
376
|
function startAutoUpdate(controlElement, popperElement, callback) {
|
|
227
377
|
stopAutoUpdate(popperElement);
|
|
228
378
|
autoUpdateCleanupMap.set(
|
|
@@ -15,10 +15,11 @@ let html1, options, html2, ConfirmButton;
|
|
|
15
15
|
|
|
16
16
|
describe('ConfirmButton', function () {
|
|
17
17
|
|
|
18
|
-
before(function (
|
|
18
|
+
before(async function () {
|
|
19
|
+
|
|
20
|
+
await initJSDOM();
|
|
21
|
+
await import("element-internals-polyfill");
|
|
19
22
|
|
|
20
|
-
import("element-internals-polyfill").catch(e => done(e));
|
|
21
|
-
|
|
22
23
|
if(!global.ResizeObserver) {
|
|
23
24
|
global.ResizeObserver = ResizeObserverMock;
|
|
24
25
|
}
|
|
@@ -43,16 +44,8 @@ describe('ConfirmButton', function () {
|
|
|
43
44
|
</div>
|
|
44
45
|
`
|
|
45
46
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
import("../../../../source/components/form/confirm-button.mjs").then((m) => {
|
|
50
|
-
ConfirmButton = m['ConfirmButton'];
|
|
51
|
-
done()
|
|
52
|
-
}).catch(e => done(e))
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
});
|
|
47
|
+
const m = await import("../../../../source/components/form/confirm-button.mjs");
|
|
48
|
+
ConfirmButton = m['ConfirmButton'];
|
|
56
49
|
})
|
|
57
50
|
|
|
58
51
|
describe('new ConfirmButton', function () {
|
|
@@ -120,8 +113,31 @@ describe('ConfirmButton', function () {
|
|
|
120
113
|
}, 0)
|
|
121
114
|
|
|
122
115
|
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('should use fixed positioning by default', function (done) {
|
|
119
|
+
|
|
120
|
+
let mocks = document.getElementById('mocks');
|
|
121
|
+
const button = document.createElement('monster-confirm-button');
|
|
122
|
+
mocks.appendChild(button);
|
|
123
|
+
|
|
124
|
+
setTimeout(() => {
|
|
125
|
+
try {
|
|
126
|
+
button.showDialog();
|
|
127
|
+
|
|
128
|
+
const popper = button.shadowRoot.querySelector('[data-monster-role="popper"]');
|
|
129
|
+
expect(popper).to.exist;
|
|
130
|
+
expect(popper.style.position).to.equal('fixed');
|
|
131
|
+
} catch (e) {
|
|
132
|
+
return done(e);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
done();
|
|
136
|
+
}, 0)
|
|
137
|
+
|
|
138
|
+
|
|
123
139
|
});
|
|
124
140
|
});
|
|
125
141
|
|
|
126
142
|
|
|
127
|
-
});
|
|
143
|
+
});
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { getGlobal } from "../../../../source/types/global.mjs";
|
|
2
|
+
import * as chai from "chai";
|
|
3
|
+
import { chaiDom } from "../../../util/chai-dom.mjs";
|
|
4
|
+
import { initJSDOM } from "../../../util/jsdom.mjs";
|
|
5
|
+
import { ResizeObserverMock } from "../../../util/resize-observer.mjs";
|
|
6
|
+
|
|
7
|
+
let expect = chai.expect;
|
|
8
|
+
chai.use(chaiDom);
|
|
9
|
+
|
|
10
|
+
const global = getGlobal();
|
|
11
|
+
|
|
12
|
+
let ContextHelp;
|
|
13
|
+
let resolveClippingBoundaryElement;
|
|
14
|
+
|
|
15
|
+
describe("ContextHelp", function () {
|
|
16
|
+
before(function (done) {
|
|
17
|
+
initJSDOM().then(() => {
|
|
18
|
+
import("element-internals-polyfill").catch((e) => done(e));
|
|
19
|
+
|
|
20
|
+
if (!global.ResizeObserver) {
|
|
21
|
+
global.ResizeObserver = ResizeObserverMock;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
Promise.all([
|
|
25
|
+
import("../../../../source/components/form/context-help.mjs"),
|
|
26
|
+
import("../../../../source/components/form/util/floating-ui.mjs"),
|
|
27
|
+
])
|
|
28
|
+
.then(([contextHelpModule, floatingUiModule]) => {
|
|
29
|
+
ContextHelp = contextHelpModule.ContextHelp;
|
|
30
|
+
resolveClippingBoundaryElement =
|
|
31
|
+
floatingUiModule.resolveClippingBoundaryElement;
|
|
32
|
+
done();
|
|
33
|
+
})
|
|
34
|
+
.catch((e) => done(e));
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
afterEach(() => {
|
|
39
|
+
let mocks = document.getElementById("mocks");
|
|
40
|
+
mocks.innerHTML = "";
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("should resolve the nearest clipping parent across the shadow boundary", function (done) {
|
|
44
|
+
let mocks = document.getElementById("mocks");
|
|
45
|
+
const outer = document.createElement("div");
|
|
46
|
+
const wrapper = document.createElement("div");
|
|
47
|
+
const help = document.createElement("monster-context-help");
|
|
48
|
+
|
|
49
|
+
outer.style.overflow = "visible";
|
|
50
|
+
wrapper.style.overflow = "hidden";
|
|
51
|
+
wrapper.appendChild(help);
|
|
52
|
+
outer.appendChild(wrapper);
|
|
53
|
+
mocks.appendChild(outer);
|
|
54
|
+
help.innerHTML = "<p>Inline help</p>";
|
|
55
|
+
|
|
56
|
+
setTimeout(() => {
|
|
57
|
+
try {
|
|
58
|
+
expect(help).to.be.instanceof(ContextHelp);
|
|
59
|
+
|
|
60
|
+
const popper = help.shadowRoot.querySelector(
|
|
61
|
+
'[data-monster-role="popper"]',
|
|
62
|
+
);
|
|
63
|
+
expect(popper).to.exist;
|
|
64
|
+
expect(resolveClippingBoundaryElement(help, popper)).to.equal(wrapper);
|
|
65
|
+
done();
|
|
66
|
+
} catch (e) {
|
|
67
|
+
done(e);
|
|
68
|
+
}
|
|
69
|
+
}, 0);
|
|
70
|
+
});
|
|
71
|
+
});
|
|
@@ -86,4 +86,25 @@ describe("PopperButton", function () {
|
|
|
86
86
|
}
|
|
87
87
|
}, 0);
|
|
88
88
|
});
|
|
89
|
+
|
|
90
|
+
it("should use absolute positioning by default", function (done) {
|
|
91
|
+
let mocks = document.getElementById("mocks");
|
|
92
|
+
const button = document.createElement("monster-popper-button");
|
|
93
|
+
mocks.appendChild(button);
|
|
94
|
+
|
|
95
|
+
setTimeout(() => {
|
|
96
|
+
try {
|
|
97
|
+
button.showDialog();
|
|
98
|
+
|
|
99
|
+
const popper = button.shadowRoot.querySelector(
|
|
100
|
+
'[data-monster-role="popper"]',
|
|
101
|
+
);
|
|
102
|
+
expect(popper).to.exist;
|
|
103
|
+
expect(popper.style.position).to.equal("absolute");
|
|
104
|
+
done();
|
|
105
|
+
} catch (e) {
|
|
106
|
+
done(e);
|
|
107
|
+
}
|
|
108
|
+
}, 0);
|
|
109
|
+
});
|
|
89
110
|
});
|
|
@@ -254,87 +254,41 @@ describe('Select', function () {
|
|
|
254
254
|
|
|
255
255
|
const pagination = () => select.shadowRoot.querySelector('[data-monster-role=pagination]');
|
|
256
256
|
|
|
257
|
-
const
|
|
258
|
-
|
|
259
|
-
filterInput.value = value;
|
|
260
|
-
filterInput.dispatchEvent(new InputEvent('input', {
|
|
261
|
-
bubbles: true,
|
|
262
|
-
composed: true,
|
|
263
|
-
data: value
|
|
264
|
-
}));
|
|
265
|
-
filterInput.dispatchEvent(new KeyboardEvent('keydown', {
|
|
266
|
-
code: 'KeyA',
|
|
267
|
-
key: 'a',
|
|
268
|
-
bubbles: true,
|
|
269
|
-
composed: true
|
|
270
|
-
}));
|
|
271
|
-
};
|
|
272
|
-
|
|
273
|
-
const startedAt = Date.now();
|
|
274
|
-
const pollLoadedState = () => {
|
|
275
|
-
try {
|
|
276
|
-
const options = select.getOption('options');
|
|
277
|
-
const pager = pagination();
|
|
278
|
-
|
|
279
|
-
if (
|
|
280
|
-
options?.length !== 1 ||
|
|
281
|
-
!pager ||
|
|
282
|
-
pager.getOption('currentPage') !== 1 ||
|
|
283
|
-
pager.getOption('pages') !== 2 ||
|
|
284
|
-
pager.getOption('objectsPerPage') !== 1
|
|
285
|
-
) {
|
|
286
|
-
if (Date.now() - startedAt < 3000) {
|
|
287
|
-
return setTimeout(pollLoadedState, 50);
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
expect(options.length).to.equal(1);
|
|
292
|
-
expect(pager.getOption('currentPage')).to.equal(1);
|
|
293
|
-
expect(pager.getOption('pages')).to.equal(2);
|
|
294
|
-
expect(pager.getOption('objectsPerPage')).to.equal(1);
|
|
295
|
-
|
|
296
|
-
triggerFilter('b');
|
|
297
|
-
setTimeout(pollErrorState, 50);
|
|
298
|
-
} catch (e) {
|
|
299
|
-
done(e);
|
|
300
|
-
}
|
|
301
|
-
};
|
|
302
|
-
|
|
303
|
-
const pollErrorState = () => {
|
|
304
|
-
try {
|
|
305
|
-
const optionsAfterError = select.getOption('options');
|
|
306
|
-
const pager = pagination();
|
|
307
|
-
|
|
308
|
-
if (
|
|
309
|
-
optionsAfterError?.length !== 0 ||
|
|
310
|
-
!pager ||
|
|
311
|
-
pager.getOption('currentPage') !== null ||
|
|
312
|
-
pager.getOption('pages') !== null ||
|
|
313
|
-
pager.getOption('objectsPerPage') !== null ||
|
|
314
|
-
select.getOption('total') !== null ||
|
|
315
|
-
select.getOption('messages.total') !== ""
|
|
316
|
-
) {
|
|
317
|
-
if (Date.now() - startedAt < 4500) {
|
|
318
|
-
return setTimeout(pollErrorState, 50);
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
expect(optionsAfterError.length).to.equal(0);
|
|
323
|
-
expect(pager.getOption('currentPage')).to.equal(null);
|
|
324
|
-
expect(pager.getOption('pages')).to.equal(null);
|
|
325
|
-
expect(pager.getOption('objectsPerPage')).to.equal(null);
|
|
326
|
-
expect(select.getOption('total')).to.equal(null);
|
|
327
|
-
expect(select.getOption('messages.total')).to.equal("");
|
|
328
|
-
} catch (e) {
|
|
329
|
-
return done(e);
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
done();
|
|
257
|
+
const fetchRemotePage = (value) => {
|
|
258
|
+
return select.fetch(`https://example.com/items?filter=${encodeURIComponent(value)}&page=1`);
|
|
333
259
|
};
|
|
334
260
|
|
|
335
261
|
setTimeout(() => {
|
|
336
|
-
|
|
337
|
-
|
|
262
|
+
fetchRemotePage('a')
|
|
263
|
+
.then(() => {
|
|
264
|
+
const options = select.getOption('options');
|
|
265
|
+
const pager = pagination();
|
|
266
|
+
|
|
267
|
+
expect(options.length).to.equal(1);
|
|
268
|
+
expect(pager.getOption('currentPage')).to.equal(1);
|
|
269
|
+
expect(pager.getOption('pages')).to.equal(2);
|
|
270
|
+
expect(pager.getOption('objectsPerPage')).to.equal(1);
|
|
271
|
+
|
|
272
|
+
return fetchRemotePage('b')
|
|
273
|
+
.then(() => Promise.reject(new Error('Expected remote fetch to fail')))
|
|
274
|
+
.catch((e) => {
|
|
275
|
+
if (e.message === 'Expected remote fetch to fail') {
|
|
276
|
+
throw e;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const optionsAfterError = select.getOption('options');
|
|
280
|
+
const pager = pagination();
|
|
281
|
+
|
|
282
|
+
expect(optionsAfterError.length).to.equal(0);
|
|
283
|
+
expect(pager.getOption('currentPage')).to.equal(null);
|
|
284
|
+
expect(pager.getOption('pages')).to.equal(null);
|
|
285
|
+
expect(pager.getOption('objectsPerPage')).to.equal(null);
|
|
286
|
+
expect(select.getOption('total')).to.equal(null);
|
|
287
|
+
expect(select.getOption('messages.total')).to.equal("");
|
|
288
|
+
});
|
|
289
|
+
})
|
|
290
|
+
.then(() => done())
|
|
291
|
+
.catch((e) => done(e));
|
|
338
292
|
}, 50);
|
|
339
293
|
});
|
|
340
294
|
});
|
|
@@ -289,18 +289,19 @@ describe("DOM", function () {
|
|
|
289
289
|
firedDisconnected: 0,
|
|
290
290
|
},
|
|
291
291
|
};
|
|
292
|
+
static pendingCallbacks = [];
|
|
292
293
|
|
|
293
294
|
connectedCallback() {
|
|
294
295
|
const mode = this.getAttribute("data-mode") || "replace";
|
|
295
296
|
MonsterTestRaceItem.stats[mode].connected++;
|
|
296
297
|
|
|
297
|
-
|
|
298
|
+
MonsterTestRaceItem.pendingCallbacks.push(() => {
|
|
298
299
|
if (this.isConnected) {
|
|
299
300
|
MonsterTestRaceItem.stats[mode].firedConnected++;
|
|
300
301
|
} else {
|
|
301
302
|
MonsterTestRaceItem.stats[mode].firedDisconnected++;
|
|
302
303
|
}
|
|
303
|
-
}
|
|
304
|
+
});
|
|
304
305
|
}
|
|
305
306
|
|
|
306
307
|
disconnectedCallback() {
|
|
@@ -308,6 +309,14 @@ describe("DOM", function () {
|
|
|
308
309
|
MonsterTestRaceItem.stats[mode].disconnected++;
|
|
309
310
|
}
|
|
310
311
|
|
|
312
|
+
static flushPendingCallbacks() {
|
|
313
|
+
const pending = [...MonsterTestRaceItem.pendingCallbacks];
|
|
314
|
+
MonsterTestRaceItem.pendingCallbacks = [];
|
|
315
|
+
for (const callback of pending) {
|
|
316
|
+
callback();
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
311
320
|
static resetStats() {
|
|
312
321
|
MonsterTestRaceItem.stats = {
|
|
313
322
|
replace: {
|
|
@@ -323,6 +332,7 @@ describe("DOM", function () {
|
|
|
323
332
|
firedDisconnected: 0,
|
|
324
333
|
},
|
|
325
334
|
};
|
|
335
|
+
MonsterTestRaceItem.pendingCallbacks = [];
|
|
326
336
|
}
|
|
327
337
|
}
|
|
328
338
|
|
|
@@ -671,8 +681,9 @@ describe("DOM", function () {
|
|
|
671
681
|
|
|
672
682
|
setTimeout(() => {
|
|
673
683
|
try {
|
|
674
|
-
const
|
|
675
|
-
|
|
684
|
+
const raceItem = customElements.get("monster-test-race-item");
|
|
685
|
+
raceItem.flushPendingCallbacks();
|
|
686
|
+
const raceStats = raceItem.stats;
|
|
676
687
|
expect(raceStats.patch.firedDisconnected).to.equal(0);
|
|
677
688
|
expect(raceStats.replace.firedDisconnected).to.be.greaterThan(
|
|
678
689
|
0,
|