kempo-ui 0.4.0 → 0.4.2
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/bin/get_icon.js +0 -0
- package/bin/highlight_code.js +0 -0
- package/bin/icon.js +0 -0
- package/bin/list_icons.js +0 -0
- package/dist/components/Slider.js +188 -0
- package/dist/components/Split.js +31 -13
- package/docs/components/accordion.html +4 -0
- package/docs/components/aside.html +4 -0
- package/docs/components/card.html +4 -0
- package/docs/components/code-editor.html +4 -0
- package/docs/components/color-picker.html +4 -0
- package/docs/components/combobox.html +4 -0
- package/docs/components/content-slider.html +4 -0
- package/docs/components/context.html +4 -0
- package/docs/components/dialog.html +4 -0
- package/docs/components/dropdown.html +4 -0
- package/docs/components/filter-list.html +4 -0
- package/docs/components/focus-capture.html +4 -0
- package/docs/components/html-editor.html +4 -0
- package/docs/components/hybrid-component.html +4 -0
- package/docs/components/icon.html +4 -0
- package/docs/components/import.html +4 -0
- package/docs/components/light-component.html +4 -0
- package/docs/components/nav-spacer.html +4 -0
- package/docs/components/nav.html +4 -0
- package/docs/components/photo-viewer.html +4 -0
- package/docs/components/progress.html +4 -0
- package/docs/components/resize.html +4 -0
- package/docs/components/shadow-component.html +4 -0
- package/docs/components/show-more.html +4 -0
- package/docs/components/slider.html +781 -0
- package/docs/components/sortable.html +4 -0
- package/docs/components/spinner.html +4 -0
- package/docs/components/split.html +32 -3
- package/docs/components/table.html +4 -0
- package/docs/components/tableControls.html +4 -0
- package/docs/components/tableCustomFields.html +4 -0
- package/docs/components/tableFetchRecords.html +4 -0
- package/docs/components/tableFieldSortHide.html +4 -0
- package/docs/components/tablePagination.html +4 -0
- package/docs/components/tablePlaceholder.html +4 -0
- package/docs/components/tableRecordEditing.html +4 -0
- package/docs/components/tableRecordFiltering.html +4 -0
- package/docs/components/tableRecordHiding.html +4 -0
- package/docs/components/tableRecordSearching.html +4 -0
- package/docs/components/tableRecordSelection.html +4 -0
- package/docs/components/tableRowControls.html +4 -0
- package/docs/components/tableServerSync.html +4 -0
- package/docs/components/tableSorting.html +4 -0
- package/docs/components/tabs.html +4 -0
- package/docs/components/tags.html +4 -0
- package/docs/components/theme-select.html +4 -0
- package/docs/components/theme-switcher.html +4 -0
- package/docs/components/timestamp.html +4 -0
- package/docs/components/toast.html +4 -0
- package/docs/components/toggle.html +4 -0
- package/docs/components/tree.html +4 -0
- package/docs/index.html +10 -0
- package/docs/src/components/Slider.js +188 -0
- package/docs/src/components/Split.js +31 -13
- package/docs/utils/context.html +4 -0
- package/docs/utils/cookie.html +4 -0
- package/docs/utils/debounce.html +4 -0
- package/docs/utils/drag.html +4 -0
- package/docs/utils/elevation.html +4 -0
- package/docs/utils/formatTimestamp.html +4 -0
- package/docs/utils/object.html +4 -0
- package/docs/utils/propConverters.html +4 -0
- package/docs/utils/string.html +4 -0
- package/docs/utils/theme.html +4 -0
- package/docs/utils/toTitleCase.html +4 -0
- package/docs/utils/type.html +4 -0
- package/docs/utils/wait.html +4 -0
- package/docs-src/components/slider.page.html +331 -0
- package/docs-src/components/split.page.html +28 -3
- package/docs-src/index.page.html +6 -0
- package/docs-src/nav.fragment.html +4 -0
- package/llms.txt +1 -0
- package/package.json +1 -1
- package/src/components/Slider.js +480 -0
- package/src/components/Split.js +163 -132
- package/tests/components/Slider.browser-test.js +823 -0
|
@@ -0,0 +1,823 @@
|
|
|
1
|
+
import Slider from '../../src/components/Slider.js';
|
|
2
|
+
|
|
3
|
+
const createSlider = async (attrs = {}) => {
|
|
4
|
+
const container = document.createElement('div');
|
|
5
|
+
const parts = [];
|
|
6
|
+
if(attrs.value !== undefined) parts.push(`value="${attrs.value}"`);
|
|
7
|
+
if(attrs.name !== undefined) parts.push(`name="${attrs.name}"`);
|
|
8
|
+
if(attrs.min !== undefined) parts.push(`min="${attrs.min}"`);
|
|
9
|
+
if(attrs.max !== undefined) parts.push(`max="${attrs.max}"`);
|
|
10
|
+
if(attrs.steps !== undefined) parts.push(`steps="${attrs.steps}"`);
|
|
11
|
+
if(attrs.format !== undefined) parts.push(`format="${attrs.format}"`);
|
|
12
|
+
if(attrs.disabled) parts.push('disabled');
|
|
13
|
+
if(attrs.vertical) parts.push('vertical');
|
|
14
|
+
if(attrs.tooltip) parts.push('tooltip');
|
|
15
|
+
container.innerHTML = `<k-slider ${parts.join(' ')}>${attrs.label || ''}</k-slider>`;
|
|
16
|
+
document.body.appendChild(container);
|
|
17
|
+
const el = container.querySelector('k-slider');
|
|
18
|
+
await el.updateComplete;
|
|
19
|
+
return { container, el };
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const cleanup = (container) => {
|
|
23
|
+
if(container && container.parentNode){
|
|
24
|
+
container.parentNode.removeChild(container);
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export default {
|
|
29
|
+
/*
|
|
30
|
+
Element Creation
|
|
31
|
+
*/
|
|
32
|
+
'should create slider element': async ({pass, fail}) => {
|
|
33
|
+
const { container, el } = await createSlider();
|
|
34
|
+
if(!el){
|
|
35
|
+
cleanup(container);
|
|
36
|
+
return fail('Slider element should be created');
|
|
37
|
+
}
|
|
38
|
+
if(!(el instanceof Slider)){
|
|
39
|
+
cleanup(container);
|
|
40
|
+
return fail('Element should be instance of Slider');
|
|
41
|
+
}
|
|
42
|
+
cleanup(container);
|
|
43
|
+
pass('Slider element created correctly');
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
'should have shadow root': async ({pass, fail}) => {
|
|
47
|
+
const { container, el } = await createSlider();
|
|
48
|
+
if(!el.shadowRoot){
|
|
49
|
+
cleanup(container);
|
|
50
|
+
return fail('Slider should have shadow root');
|
|
51
|
+
}
|
|
52
|
+
cleanup(container);
|
|
53
|
+
pass('Slider has shadow root');
|
|
54
|
+
},
|
|
55
|
+
|
|
56
|
+
/*
|
|
57
|
+
Default Properties
|
|
58
|
+
*/
|
|
59
|
+
'should have default value of "0"': async ({pass, fail}) => {
|
|
60
|
+
const { container, el } = await createSlider();
|
|
61
|
+
if(el.value !== '0'){
|
|
62
|
+
cleanup(container);
|
|
63
|
+
return fail(`Expected value "0", got "${el.value}"`);
|
|
64
|
+
}
|
|
65
|
+
cleanup(container);
|
|
66
|
+
pass('Default value is "0"');
|
|
67
|
+
},
|
|
68
|
+
|
|
69
|
+
'should have default min of 0': async ({pass, fail}) => {
|
|
70
|
+
const { container, el } = await createSlider();
|
|
71
|
+
if(el.min !== 0){
|
|
72
|
+
cleanup(container);
|
|
73
|
+
return fail(`Expected min 0, got ${el.min}`);
|
|
74
|
+
}
|
|
75
|
+
cleanup(container);
|
|
76
|
+
pass('Default min is 0');
|
|
77
|
+
},
|
|
78
|
+
|
|
79
|
+
'should have default max of 100': async ({pass, fail}) => {
|
|
80
|
+
const { container, el } = await createSlider();
|
|
81
|
+
if(el.max !== 100){
|
|
82
|
+
cleanup(container);
|
|
83
|
+
return fail(`Expected max 100, got ${el.max}`);
|
|
84
|
+
}
|
|
85
|
+
cleanup(container);
|
|
86
|
+
pass('Default max is 100');
|
|
87
|
+
},
|
|
88
|
+
|
|
89
|
+
'should have default steps of null': async ({pass, fail}) => {
|
|
90
|
+
const { container, el } = await createSlider();
|
|
91
|
+
if(el.steps !== null){
|
|
92
|
+
cleanup(container);
|
|
93
|
+
return fail(`Expected steps null, got ${el.steps}`);
|
|
94
|
+
}
|
|
95
|
+
cleanup(container);
|
|
96
|
+
pass('Default steps is null');
|
|
97
|
+
},
|
|
98
|
+
|
|
99
|
+
'should have default disabled of false': async ({pass, fail}) => {
|
|
100
|
+
const { container, el } = await createSlider();
|
|
101
|
+
if(el.disabled !== false){
|
|
102
|
+
cleanup(container);
|
|
103
|
+
return fail(`Expected disabled false, got ${el.disabled}`);
|
|
104
|
+
}
|
|
105
|
+
cleanup(container);
|
|
106
|
+
pass('Default disabled is false');
|
|
107
|
+
},
|
|
108
|
+
|
|
109
|
+
'should have default vertical of false': async ({pass, fail}) => {
|
|
110
|
+
const { container, el } = await createSlider();
|
|
111
|
+
if(el.vertical !== false){
|
|
112
|
+
cleanup(container);
|
|
113
|
+
return fail(`Expected vertical false, got ${el.vertical}`);
|
|
114
|
+
}
|
|
115
|
+
cleanup(container);
|
|
116
|
+
pass('Default vertical is false');
|
|
117
|
+
},
|
|
118
|
+
|
|
119
|
+
'should not be a range by default': async ({pass, fail}) => {
|
|
120
|
+
const { container, el } = await createSlider();
|
|
121
|
+
if(el.isRange !== false){
|
|
122
|
+
cleanup(container);
|
|
123
|
+
return fail(`Expected isRange false, got ${el.isRange}`);
|
|
124
|
+
}
|
|
125
|
+
cleanup(container);
|
|
126
|
+
pass('Default isRange is false');
|
|
127
|
+
},
|
|
128
|
+
|
|
129
|
+
/*
|
|
130
|
+
Property Reflection
|
|
131
|
+
*/
|
|
132
|
+
'should reflect value attribute': async ({pass, fail}) => {
|
|
133
|
+
const { container, el } = await createSlider({ value: '50' });
|
|
134
|
+
if(el.value !== '50'){
|
|
135
|
+
cleanup(container);
|
|
136
|
+
return fail(`Expected value "50", got "${el.value}"`);
|
|
137
|
+
}
|
|
138
|
+
cleanup(container);
|
|
139
|
+
pass('Value attribute reflects correctly');
|
|
140
|
+
},
|
|
141
|
+
|
|
142
|
+
'should reflect min attribute': async ({pass, fail}) => {
|
|
143
|
+
const { container, el } = await createSlider({ min: 10 });
|
|
144
|
+
if(el.min !== 10){
|
|
145
|
+
cleanup(container);
|
|
146
|
+
return fail(`Expected min 10, got ${el.min}`);
|
|
147
|
+
}
|
|
148
|
+
cleanup(container);
|
|
149
|
+
pass('Min attribute reflects correctly');
|
|
150
|
+
},
|
|
151
|
+
|
|
152
|
+
'should reflect max attribute': async ({pass, fail}) => {
|
|
153
|
+
const { container, el } = await createSlider({ max: 200 });
|
|
154
|
+
if(el.max !== 200){
|
|
155
|
+
cleanup(container);
|
|
156
|
+
return fail(`Expected max 200, got ${el.max}`);
|
|
157
|
+
}
|
|
158
|
+
cleanup(container);
|
|
159
|
+
pass('Max attribute reflects correctly');
|
|
160
|
+
},
|
|
161
|
+
|
|
162
|
+
'should reflect steps attribute': async ({pass, fail}) => {
|
|
163
|
+
const { container, el } = await createSlider({ steps: '0,25,50,75,100' });
|
|
164
|
+
if(el.steps !== '0,25,50,75,100'){
|
|
165
|
+
cleanup(container);
|
|
166
|
+
return fail(`Expected steps "0,25,50,75,100", got ${el.steps}`);
|
|
167
|
+
}
|
|
168
|
+
cleanup(container);
|
|
169
|
+
pass('Steps attribute reflects correctly');
|
|
170
|
+
},
|
|
171
|
+
|
|
172
|
+
/*
|
|
173
|
+
stepValues Getter
|
|
174
|
+
*/
|
|
175
|
+
'should parse step values correctly': async ({pass, fail}) => {
|
|
176
|
+
const { container, el } = await createSlider({ steps: '10,30,50,70,90' });
|
|
177
|
+
const sv = el.stepValues;
|
|
178
|
+
if(!sv || sv.length !== 5 || sv[0] !== 10 || sv[4] !== 90){
|
|
179
|
+
cleanup(container);
|
|
180
|
+
return fail(`Expected [10,30,50,70,90], got ${JSON.stringify(sv)}`);
|
|
181
|
+
}
|
|
182
|
+
cleanup(container);
|
|
183
|
+
pass('stepValues parsed correctly');
|
|
184
|
+
},
|
|
185
|
+
|
|
186
|
+
'should return null stepValues when no steps': async ({pass, fail}) => {
|
|
187
|
+
const { container, el } = await createSlider();
|
|
188
|
+
if(el.stepValues !== null){
|
|
189
|
+
cleanup(container);
|
|
190
|
+
return fail(`Expected null, got ${JSON.stringify(el.stepValues)}`);
|
|
191
|
+
}
|
|
192
|
+
cleanup(container);
|
|
193
|
+
pass('stepValues is null when no steps');
|
|
194
|
+
},
|
|
195
|
+
|
|
196
|
+
/*
|
|
197
|
+
lower / upper Getters
|
|
198
|
+
*/
|
|
199
|
+
'lower getter should return numeric value': async ({pass, fail}) => {
|
|
200
|
+
const { container, el } = await createSlider({ value: '42' });
|
|
201
|
+
if(el.lower !== 42){
|
|
202
|
+
cleanup(container);
|
|
203
|
+
return fail(`Expected lower 42, got ${el.lower}`);
|
|
204
|
+
}
|
|
205
|
+
cleanup(container);
|
|
206
|
+
pass('lower getter works');
|
|
207
|
+
},
|
|
208
|
+
|
|
209
|
+
'lower getter should return first part of range value': async ({pass, fail}) => {
|
|
210
|
+
const { container, el } = await createSlider({ value: '20,80' });
|
|
211
|
+
if(el.lower !== 20){
|
|
212
|
+
cleanup(container);
|
|
213
|
+
return fail(`Expected lower 20, got ${el.lower}`);
|
|
214
|
+
}
|
|
215
|
+
cleanup(container);
|
|
216
|
+
pass('lower getter works for range');
|
|
217
|
+
},
|
|
218
|
+
|
|
219
|
+
'upper getter should return second part of range value': async ({pass, fail}) => {
|
|
220
|
+
const { container, el } = await createSlider({ value: '20,80' });
|
|
221
|
+
if(el.upper !== 80){
|
|
222
|
+
cleanup(container);
|
|
223
|
+
return fail(`Expected upper 80, got ${el.upper}`);
|
|
224
|
+
}
|
|
225
|
+
cleanup(container);
|
|
226
|
+
pass('upper getter works for range');
|
|
227
|
+
},
|
|
228
|
+
|
|
229
|
+
'upper getter should return max when not in range mode': async ({pass, fail}) => {
|
|
230
|
+
const { container, el } = await createSlider({ value: '50' });
|
|
231
|
+
if(el.upper !== 100){
|
|
232
|
+
cleanup(container);
|
|
233
|
+
return fail(`Expected upper 100, got ${el.upper}`);
|
|
234
|
+
}
|
|
235
|
+
cleanup(container);
|
|
236
|
+
pass('upper getter defaults to max');
|
|
237
|
+
},
|
|
238
|
+
|
|
239
|
+
/*
|
|
240
|
+
isRange Getter
|
|
241
|
+
*/
|
|
242
|
+
'isRange should be true when value has comma': async ({pass, fail}) => {
|
|
243
|
+
const { container, el } = await createSlider({ value: '20,80' });
|
|
244
|
+
if(el.isRange !== true){
|
|
245
|
+
cleanup(container);
|
|
246
|
+
return fail(`Expected isRange true, got ${el.isRange}`);
|
|
247
|
+
}
|
|
248
|
+
cleanup(container);
|
|
249
|
+
pass('isRange is true for comma-separated value');
|
|
250
|
+
},
|
|
251
|
+
|
|
252
|
+
'isRange should be false for single value': async ({pass, fail}) => {
|
|
253
|
+
const { container, el } = await createSlider({ value: '50' });
|
|
254
|
+
if(el.isRange !== false){
|
|
255
|
+
cleanup(container);
|
|
256
|
+
return fail(`Expected isRange false, got ${el.isRange}`);
|
|
257
|
+
}
|
|
258
|
+
cleanup(container);
|
|
259
|
+
pass('isRange is false for single value');
|
|
260
|
+
},
|
|
261
|
+
|
|
262
|
+
/*
|
|
263
|
+
setValue Method
|
|
264
|
+
*/
|
|
265
|
+
'should have setValue method': async ({pass, fail}) => {
|
|
266
|
+
const { container, el } = await createSlider();
|
|
267
|
+
if(typeof el.setValue !== 'function'){
|
|
268
|
+
cleanup(container);
|
|
269
|
+
return fail('Slider should have setValue method');
|
|
270
|
+
}
|
|
271
|
+
cleanup(container);
|
|
272
|
+
pass('Slider has setValue method');
|
|
273
|
+
},
|
|
274
|
+
|
|
275
|
+
'setValue should update value': async ({pass, fail}) => {
|
|
276
|
+
const { container, el } = await createSlider();
|
|
277
|
+
el.setValue(42);
|
|
278
|
+
if(el.value !== '42'){
|
|
279
|
+
cleanup(container);
|
|
280
|
+
return fail(`Expected value "42", got "${el.value}"`);
|
|
281
|
+
}
|
|
282
|
+
cleanup(container);
|
|
283
|
+
pass('setValue updates value');
|
|
284
|
+
},
|
|
285
|
+
|
|
286
|
+
'setValue should clamp to min': async ({pass, fail}) => {
|
|
287
|
+
const { container, el } = await createSlider({ min: 10, max: 100 });
|
|
288
|
+
el.setValue(5);
|
|
289
|
+
if(el.lower !== 10){
|
|
290
|
+
cleanup(container);
|
|
291
|
+
return fail(`Expected lower 10, got ${el.lower}`);
|
|
292
|
+
}
|
|
293
|
+
cleanup(container);
|
|
294
|
+
pass('setValue clamps to min');
|
|
295
|
+
},
|
|
296
|
+
|
|
297
|
+
'setValue should clamp to max': async ({pass, fail}) => {
|
|
298
|
+
const { container, el } = await createSlider({ min: 0, max: 50 });
|
|
299
|
+
el.setValue(80);
|
|
300
|
+
if(el.lower !== 50){
|
|
301
|
+
cleanup(container);
|
|
302
|
+
return fail(`Expected lower 50, got ${el.lower}`);
|
|
303
|
+
}
|
|
304
|
+
cleanup(container);
|
|
305
|
+
pass('setValue clamps to max');
|
|
306
|
+
},
|
|
307
|
+
|
|
308
|
+
'setValue should snap to nearest step': async ({pass, fail}) => {
|
|
309
|
+
const { container, el } = await createSlider({ steps: '0,25,50,75,100' });
|
|
310
|
+
el.setValue(30);
|
|
311
|
+
if(el.lower !== 25){
|
|
312
|
+
cleanup(container);
|
|
313
|
+
return fail(`Expected lower 25, got ${el.lower}`);
|
|
314
|
+
}
|
|
315
|
+
cleanup(container);
|
|
316
|
+
pass('setValue snaps to nearest step');
|
|
317
|
+
},
|
|
318
|
+
|
|
319
|
+
'setValue should not change value when disabled': async ({pass, fail}) => {
|
|
320
|
+
const { container, el } = await createSlider({ value: '20', disabled: true });
|
|
321
|
+
el.setValue(80);
|
|
322
|
+
if(el.lower !== 20){
|
|
323
|
+
cleanup(container);
|
|
324
|
+
return fail(`Expected lower 20, got ${el.lower}`);
|
|
325
|
+
}
|
|
326
|
+
cleanup(container);
|
|
327
|
+
pass('setValue does nothing when disabled');
|
|
328
|
+
},
|
|
329
|
+
|
|
330
|
+
/*
|
|
331
|
+
Events
|
|
332
|
+
*/
|
|
333
|
+
'should dispatch change event when value changes': async ({pass, fail}) => {
|
|
334
|
+
const { container, el } = await createSlider({ value: '0' });
|
|
335
|
+
let eventFired = false;
|
|
336
|
+
let eventValue = null;
|
|
337
|
+
el.addEventListener('change', (e) => {
|
|
338
|
+
eventFired = true;
|
|
339
|
+
eventValue = e.detail.value;
|
|
340
|
+
});
|
|
341
|
+
el.setValue(50);
|
|
342
|
+
await el.updateComplete;
|
|
343
|
+
if(!eventFired){
|
|
344
|
+
cleanup(container);
|
|
345
|
+
return fail('Change event should have fired');
|
|
346
|
+
}
|
|
347
|
+
if(eventValue !== '50'){
|
|
348
|
+
cleanup(container);
|
|
349
|
+
return fail(`Expected event value "50", got "${eventValue}"`);
|
|
350
|
+
}
|
|
351
|
+
cleanup(container);
|
|
352
|
+
pass('Change event dispatched correctly');
|
|
353
|
+
},
|
|
354
|
+
|
|
355
|
+
'should not dispatch change event when value stays the same': async ({pass, fail}) => {
|
|
356
|
+
const { container, el } = await createSlider({ value: '50' });
|
|
357
|
+
let eventFired = false;
|
|
358
|
+
el.addEventListener('change', () => {
|
|
359
|
+
eventFired = true;
|
|
360
|
+
});
|
|
361
|
+
el.setValue(50);
|
|
362
|
+
await el.updateComplete;
|
|
363
|
+
if(eventFired){
|
|
364
|
+
cleanup(container);
|
|
365
|
+
return fail('Change event should not fire when value unchanged');
|
|
366
|
+
}
|
|
367
|
+
cleanup(container);
|
|
368
|
+
pass('No change event when value unchanged');
|
|
369
|
+
},
|
|
370
|
+
|
|
371
|
+
/*
|
|
372
|
+
Percentage Getter
|
|
373
|
+
*/
|
|
374
|
+
'should calculate percentage correctly': async ({pass, fail}) => {
|
|
375
|
+
const { container, el } = await createSlider({ value: '50', min: 0, max: 100 });
|
|
376
|
+
if(el.percentage !== 50){
|
|
377
|
+
cleanup(container);
|
|
378
|
+
return fail(`Expected 50%, got ${el.percentage}%`);
|
|
379
|
+
}
|
|
380
|
+
cleanup(container);
|
|
381
|
+
pass('Percentage calculated correctly');
|
|
382
|
+
},
|
|
383
|
+
|
|
384
|
+
'should calculate percentage with custom range': async ({pass, fail}) => {
|
|
385
|
+
const { container, el } = await createSlider({ value: '30', min: 20, max: 40 });
|
|
386
|
+
if(el.percentage !== 50){
|
|
387
|
+
cleanup(container);
|
|
388
|
+
return fail(`Expected 50%, got ${el.percentage}%`);
|
|
389
|
+
}
|
|
390
|
+
cleanup(container);
|
|
391
|
+
pass('Percentage with custom range calculated correctly');
|
|
392
|
+
},
|
|
393
|
+
|
|
394
|
+
/*
|
|
395
|
+
Rendering
|
|
396
|
+
*/
|
|
397
|
+
'should render track element': async ({pass, fail}) => {
|
|
398
|
+
const { container, el } = await createSlider();
|
|
399
|
+
const track = el.shadowRoot.querySelector('#track');
|
|
400
|
+
if(!track){
|
|
401
|
+
cleanup(container);
|
|
402
|
+
return fail('Track element should exist');
|
|
403
|
+
}
|
|
404
|
+
cleanup(container);
|
|
405
|
+
pass('Track element rendered');
|
|
406
|
+
},
|
|
407
|
+
|
|
408
|
+
'should render thumb element': async ({pass, fail}) => {
|
|
409
|
+
const { container, el } = await createSlider();
|
|
410
|
+
const thumb = el.shadowRoot.querySelector('.thumb');
|
|
411
|
+
if(!thumb){
|
|
412
|
+
cleanup(container);
|
|
413
|
+
return fail('Thumb element should exist');
|
|
414
|
+
}
|
|
415
|
+
cleanup(container);
|
|
416
|
+
pass('Thumb element rendered');
|
|
417
|
+
},
|
|
418
|
+
|
|
419
|
+
'should render fill element': async ({pass, fail}) => {
|
|
420
|
+
const { container, el } = await createSlider();
|
|
421
|
+
const fill = el.shadowRoot.querySelector('#fill');
|
|
422
|
+
if(!fill){
|
|
423
|
+
cleanup(container);
|
|
424
|
+
return fail('Fill element should exist');
|
|
425
|
+
}
|
|
426
|
+
cleanup(container);
|
|
427
|
+
pass('Fill element rendered');
|
|
428
|
+
},
|
|
429
|
+
|
|
430
|
+
'should render step dots when steps are set': async ({pass, fail}) => {
|
|
431
|
+
const { container, el } = await createSlider({ steps: '0,25,50,75,100' });
|
|
432
|
+
const dots = el.shadowRoot.querySelectorAll('.step-dot');
|
|
433
|
+
if(dots.length !== 5){
|
|
434
|
+
cleanup(container);
|
|
435
|
+
return fail(`Expected 5 step dots, got ${dots.length}`);
|
|
436
|
+
}
|
|
437
|
+
cleanup(container);
|
|
438
|
+
pass('Step dots rendered correctly');
|
|
439
|
+
},
|
|
440
|
+
|
|
441
|
+
'should not render step dots when no steps': async ({pass, fail}) => {
|
|
442
|
+
const { container, el } = await createSlider();
|
|
443
|
+
const dots = el.shadowRoot.querySelectorAll('.step-dot');
|
|
444
|
+
if(dots.length !== 0){
|
|
445
|
+
cleanup(container);
|
|
446
|
+
return fail(`Expected 0 step dots, got ${dots.length}`);
|
|
447
|
+
}
|
|
448
|
+
cleanup(container);
|
|
449
|
+
pass('No step dots when no steps');
|
|
450
|
+
},
|
|
451
|
+
|
|
452
|
+
/*
|
|
453
|
+
Vertical Mode
|
|
454
|
+
*/
|
|
455
|
+
'should have vertical attribute when set': async ({pass, fail}) => {
|
|
456
|
+
const { container, el } = await createSlider({ vertical: true, value: '50' });
|
|
457
|
+
if(!el.vertical){
|
|
458
|
+
cleanup(container);
|
|
459
|
+
return fail('Expected vertical to be true');
|
|
460
|
+
}
|
|
461
|
+
if(!el.hasAttribute('vertical')){
|
|
462
|
+
cleanup(container);
|
|
463
|
+
return fail('Expected vertical attribute on element');
|
|
464
|
+
}
|
|
465
|
+
cleanup(container);
|
|
466
|
+
pass('Vertical attribute set correctly');
|
|
467
|
+
},
|
|
468
|
+
|
|
469
|
+
'vertical slider should render track': async ({pass, fail}) => {
|
|
470
|
+
const { container, el } = await createSlider({ vertical: true, value: '40' });
|
|
471
|
+
const track = el.shadowRoot.querySelector('#track');
|
|
472
|
+
const fill = el.shadowRoot.querySelector('#fill');
|
|
473
|
+
if(!track || !fill){
|
|
474
|
+
cleanup(container);
|
|
475
|
+
return fail('Vertical slider should render track and fill');
|
|
476
|
+
}
|
|
477
|
+
cleanup(container);
|
|
478
|
+
pass('Vertical slider renders track and fill');
|
|
479
|
+
},
|
|
480
|
+
|
|
481
|
+
'vertical slider should render step dots': async ({pass, fail}) => {
|
|
482
|
+
const { container, el } = await createSlider({ vertical: true, steps: '0,50,100' });
|
|
483
|
+
const dots = el.shadowRoot.querySelectorAll('.step-dot');
|
|
484
|
+
if(dots.length !== 3){
|
|
485
|
+
cleanup(container);
|
|
486
|
+
return fail(`Expected 3 step dots, got ${dots.length}`);
|
|
487
|
+
}
|
|
488
|
+
cleanup(container);
|
|
489
|
+
pass('Vertical step dots rendered');
|
|
490
|
+
},
|
|
491
|
+
|
|
492
|
+
/*
|
|
493
|
+
Range Mode (comma-separated value)
|
|
494
|
+
*/
|
|
495
|
+
'comma-separated value should enable range mode': async ({pass, fail}) => {
|
|
496
|
+
const { container, el } = await createSlider({ value: '20,80' });
|
|
497
|
+
if(!el.isRange){
|
|
498
|
+
cleanup(container);
|
|
499
|
+
return fail('Expected isRange to be true');
|
|
500
|
+
}
|
|
501
|
+
cleanup(container);
|
|
502
|
+
pass('Comma-separated value enables range');
|
|
503
|
+
},
|
|
504
|
+
|
|
505
|
+
'range mode should render two thumbs': async ({pass, fail}) => {
|
|
506
|
+
const { container, el } = await createSlider({ value: '20,80' });
|
|
507
|
+
const thumbs = el.shadowRoot.querySelectorAll('.thumb');
|
|
508
|
+
if(thumbs.length !== 2){
|
|
509
|
+
cleanup(container);
|
|
510
|
+
return fail(`Expected 2 thumbs, got ${thumbs.length}`);
|
|
511
|
+
}
|
|
512
|
+
cleanup(container);
|
|
513
|
+
pass('Range mode renders two thumbs');
|
|
514
|
+
},
|
|
515
|
+
|
|
516
|
+
'single mode should render one thumb': async ({pass, fail}) => {
|
|
517
|
+
const { container, el } = await createSlider({ value: '50' });
|
|
518
|
+
const thumbs = el.shadowRoot.querySelectorAll('.thumb');
|
|
519
|
+
if(thumbs.length !== 1){
|
|
520
|
+
cleanup(container);
|
|
521
|
+
return fail(`Expected 1 thumb, got ${thumbs.length}`);
|
|
522
|
+
}
|
|
523
|
+
cleanup(container);
|
|
524
|
+
pass('Single mode renders one thumb');
|
|
525
|
+
},
|
|
526
|
+
|
|
527
|
+
'upperPercentage should be correct': async ({pass, fail}) => {
|
|
528
|
+
const { container, el } = await createSlider({ value: '0,50' });
|
|
529
|
+
if(el.upperPercentage !== 50){
|
|
530
|
+
cleanup(container);
|
|
531
|
+
return fail(`Expected 50%, got ${el.upperPercentage}%`);
|
|
532
|
+
}
|
|
533
|
+
cleanup(container);
|
|
534
|
+
pass('upperPercentage calculated correctly');
|
|
535
|
+
},
|
|
536
|
+
|
|
537
|
+
/*
|
|
538
|
+
setUpper Method
|
|
539
|
+
*/
|
|
540
|
+
'should have setUpper method': async ({pass, fail}) => {
|
|
541
|
+
const { container, el } = await createSlider({ value: '10,90' });
|
|
542
|
+
if(typeof el.setUpper !== 'function'){
|
|
543
|
+
cleanup(container);
|
|
544
|
+
return fail('Slider should have setUpper method');
|
|
545
|
+
}
|
|
546
|
+
cleanup(container);
|
|
547
|
+
pass('Slider has setUpper method');
|
|
548
|
+
},
|
|
549
|
+
|
|
550
|
+
'setUpper should update upper value': async ({pass, fail}) => {
|
|
551
|
+
const { container, el } = await createSlider({ value: '10,90' });
|
|
552
|
+
el.setUpper(60);
|
|
553
|
+
if(el.upper !== 60){
|
|
554
|
+
cleanup(container);
|
|
555
|
+
return fail(`Expected upper 60, got ${el.upper}`);
|
|
556
|
+
}
|
|
557
|
+
cleanup(container);
|
|
558
|
+
pass('setUpper updates upper value');
|
|
559
|
+
},
|
|
560
|
+
|
|
561
|
+
'setUpper should not go below lower': async ({pass, fail}) => {
|
|
562
|
+
const { container, el } = await createSlider({ value: '50,80' });
|
|
563
|
+
el.setUpper(30);
|
|
564
|
+
if(el.upper !== 80){
|
|
565
|
+
cleanup(container);
|
|
566
|
+
return fail(`Expected upper 80, got ${el.upper}`);
|
|
567
|
+
}
|
|
568
|
+
cleanup(container);
|
|
569
|
+
pass('setUpper does not go below lower');
|
|
570
|
+
},
|
|
571
|
+
|
|
572
|
+
'setValue should not exceed upper in range mode': async ({pass, fail}) => {
|
|
573
|
+
const { container, el } = await createSlider({ value: '20,60' });
|
|
574
|
+
el.setValue(80);
|
|
575
|
+
if(el.lower !== 20){
|
|
576
|
+
cleanup(container);
|
|
577
|
+
return fail(`Expected lower 20, got ${el.lower}`);
|
|
578
|
+
}
|
|
579
|
+
cleanup(container);
|
|
580
|
+
pass('setValue does not exceed upper');
|
|
581
|
+
},
|
|
582
|
+
|
|
583
|
+
'setUpper should snap to nearest step': async ({pass, fail}) => {
|
|
584
|
+
const { container, el } = await createSlider({ value: '0,100', steps: '0,25,50,75,100' });
|
|
585
|
+
el.setUpper(60);
|
|
586
|
+
if(el.upper !== 50){
|
|
587
|
+
cleanup(container);
|
|
588
|
+
return fail(`Expected upper 50, got ${el.upper}`);
|
|
589
|
+
}
|
|
590
|
+
cleanup(container);
|
|
591
|
+
pass('setUpper snaps to nearest step');
|
|
592
|
+
},
|
|
593
|
+
|
|
594
|
+
'setUpper should do nothing when not in range mode': async ({pass, fail}) => {
|
|
595
|
+
const { container, el } = await createSlider({ value: '50' });
|
|
596
|
+
el.setUpper(80);
|
|
597
|
+
if(el.isRange){
|
|
598
|
+
cleanup(container);
|
|
599
|
+
return fail('Should not become range mode');
|
|
600
|
+
}
|
|
601
|
+
cleanup(container);
|
|
602
|
+
pass('setUpper does nothing outside range mode');
|
|
603
|
+
},
|
|
604
|
+
|
|
605
|
+
'range change event should include comma-separated value': async ({pass, fail}) => {
|
|
606
|
+
const { container, el } = await createSlider({ value: '10,90' });
|
|
607
|
+
let detail = null;
|
|
608
|
+
el.addEventListener('change', (e) => {
|
|
609
|
+
detail = e.detail;
|
|
610
|
+
});
|
|
611
|
+
el.setUpper(70);
|
|
612
|
+
await el.updateComplete;
|
|
613
|
+
if(!detail){
|
|
614
|
+
cleanup(container);
|
|
615
|
+
return fail('Change event should have fired');
|
|
616
|
+
}
|
|
617
|
+
if(detail.value !== '10,70'){
|
|
618
|
+
cleanup(container);
|
|
619
|
+
return fail(`Expected event value "10,70", got "${detail.value}"`);
|
|
620
|
+
}
|
|
621
|
+
cleanup(container);
|
|
622
|
+
pass('Range change event has comma-separated value');
|
|
623
|
+
},
|
|
624
|
+
|
|
625
|
+
/*
|
|
626
|
+
Vertical Range
|
|
627
|
+
*/
|
|
628
|
+
'vertical range should render two thumbs': async ({pass, fail}) => {
|
|
629
|
+
const { container, el } = await createSlider({ vertical: true, value: '20,80' });
|
|
630
|
+
const thumbs = el.shadowRoot.querySelectorAll('.thumb');
|
|
631
|
+
if(thumbs.length !== 2){
|
|
632
|
+
cleanup(container);
|
|
633
|
+
return fail(`Expected 2 thumbs, got ${thumbs.length}`);
|
|
634
|
+
}
|
|
635
|
+
cleanup(container);
|
|
636
|
+
pass('Vertical range renders two thumbs');
|
|
637
|
+
},
|
|
638
|
+
|
|
639
|
+
/*
|
|
640
|
+
Form Association
|
|
641
|
+
*/
|
|
642
|
+
'should be form-associated': async ({pass, fail}) => {
|
|
643
|
+
if(Slider.formAssociated !== true){
|
|
644
|
+
return fail('Expected static formAssociated to be true');
|
|
645
|
+
}
|
|
646
|
+
pass('Slider is form-associated');
|
|
647
|
+
},
|
|
648
|
+
|
|
649
|
+
'should have name property': async ({pass, fail}) => {
|
|
650
|
+
const { container, el } = await createSlider({ name: 'volume' });
|
|
651
|
+
if(el.name !== 'volume'){
|
|
652
|
+
cleanup(container);
|
|
653
|
+
return fail(`Expected name "volume", got "${el.name}"`);
|
|
654
|
+
}
|
|
655
|
+
cleanup(container);
|
|
656
|
+
pass('Name property works');
|
|
657
|
+
},
|
|
658
|
+
|
|
659
|
+
/*
|
|
660
|
+
Format & Tooltip
|
|
661
|
+
*/
|
|
662
|
+
'should have default format of null': async ({pass, fail}) => {
|
|
663
|
+
const { container, el } = await createSlider();
|
|
664
|
+
if(el.format !== null){
|
|
665
|
+
cleanup(container);
|
|
666
|
+
return fail(`Expected format null, got ${el.format}`);
|
|
667
|
+
}
|
|
668
|
+
cleanup(container);
|
|
669
|
+
pass('Default format is null');
|
|
670
|
+
},
|
|
671
|
+
|
|
672
|
+
'should reflect format attribute': async ({pass, fail}) => {
|
|
673
|
+
const { container, el } = await createSlider({ format: '$0.00' });
|
|
674
|
+
if(el.format !== '$0.00'){
|
|
675
|
+
cleanup(container);
|
|
676
|
+
return fail(`Expected format "$0.00", got "${el.format}"`);
|
|
677
|
+
}
|
|
678
|
+
cleanup(container);
|
|
679
|
+
pass('Format attribute reflects correctly');
|
|
680
|
+
},
|
|
681
|
+
|
|
682
|
+
'formatValue should format with pattern': async ({pass, fail}) => {
|
|
683
|
+
const { container, el } = await createSlider({ format: '$0.00' });
|
|
684
|
+
const result = el.formatValue(25);
|
|
685
|
+
if(result !== '$25.00'){
|
|
686
|
+
cleanup(container);
|
|
687
|
+
return fail(`Expected "$25.00", got "${result}"`);
|
|
688
|
+
}
|
|
689
|
+
cleanup(container);
|
|
690
|
+
pass('formatValue formats correctly');
|
|
691
|
+
},
|
|
692
|
+
|
|
693
|
+
'formatValue should return raw number when no format': async ({pass, fail}) => {
|
|
694
|
+
const { container, el } = await createSlider();
|
|
695
|
+
const result = el.formatValue(42);
|
|
696
|
+
if(result !== '42'){
|
|
697
|
+
cleanup(container);
|
|
698
|
+
return fail(`Expected "42", got "${result}"`);
|
|
699
|
+
}
|
|
700
|
+
cleanup(container);
|
|
701
|
+
pass('formatValue returns raw number without format');
|
|
702
|
+
},
|
|
703
|
+
|
|
704
|
+
'formatValue should respect decimal precision': async ({pass, fail}) => {
|
|
705
|
+
const { container, el } = await createSlider({ format: '0.0%' });
|
|
706
|
+
const result = el.formatValue(12.456);
|
|
707
|
+
if(result !== '12.5%'){
|
|
708
|
+
cleanup(container);
|
|
709
|
+
return fail(`Expected "12.5%", got "${result}"`);
|
|
710
|
+
}
|
|
711
|
+
cleanup(container);
|
|
712
|
+
pass('formatValue respects decimal precision');
|
|
713
|
+
},
|
|
714
|
+
|
|
715
|
+
'tooltip should appear when thumb is active': async ({pass, fail}) => {
|
|
716
|
+
const { container, el } = await createSlider({ value: '50', tooltip: true });
|
|
717
|
+
el.handleThumbDown('lower', { preventDefault: () => {}, stopPropagation: () => {} });
|
|
718
|
+
await el.updateComplete;
|
|
719
|
+
const tooltip = el.shadowRoot.querySelector('.tooltip');
|
|
720
|
+
if(!tooltip){
|
|
721
|
+
window.dispatchEvent(new MouseEvent('mouseup'));
|
|
722
|
+
cleanup(container);
|
|
723
|
+
return fail('Tooltip should appear when dragging');
|
|
724
|
+
}
|
|
725
|
+
if(tooltip.textContent !== '50'){
|
|
726
|
+
window.dispatchEvent(new MouseEvent('mouseup'));
|
|
727
|
+
cleanup(container);
|
|
728
|
+
return fail(`Expected tooltip "50", got "${tooltip.textContent}"`);
|
|
729
|
+
}
|
|
730
|
+
window.dispatchEvent(new MouseEvent('mouseup'));
|
|
731
|
+
await el.updateComplete;
|
|
732
|
+
const gone = el.shadowRoot.querySelector('.tooltip');
|
|
733
|
+
if(gone){
|
|
734
|
+
cleanup(container);
|
|
735
|
+
return fail('Tooltip should disappear after releasing');
|
|
736
|
+
}
|
|
737
|
+
cleanup(container);
|
|
738
|
+
pass('Tooltip appears and disappears correctly');
|
|
739
|
+
},
|
|
740
|
+
|
|
741
|
+
'tooltip should not appear without tooltip attribute': async ({pass, fail}) => {
|
|
742
|
+
const { container, el } = await createSlider({ value: '50' });
|
|
743
|
+
el.handleThumbDown('lower', { preventDefault: () => {}, stopPropagation: () => {} });
|
|
744
|
+
await el.updateComplete;
|
|
745
|
+
const tooltip = el.shadowRoot.querySelector('.tooltip');
|
|
746
|
+
window.dispatchEvent(new MouseEvent('mouseup'));
|
|
747
|
+
if(tooltip){
|
|
748
|
+
cleanup(container);
|
|
749
|
+
return fail('Tooltip should not appear without tooltip attribute');
|
|
750
|
+
}
|
|
751
|
+
cleanup(container);
|
|
752
|
+
pass('No tooltip without attribute');
|
|
753
|
+
},
|
|
754
|
+
|
|
755
|
+
'tooltip should use format': async ({pass, fail}) => {
|
|
756
|
+
const { container, el } = await createSlider({ value: '25', format: '$0.00', tooltip: true });
|
|
757
|
+
el.handleThumbDown('lower', { preventDefault: () => {}, stopPropagation: () => {} });
|
|
758
|
+
await el.updateComplete;
|
|
759
|
+
const tooltip = el.shadowRoot.querySelector('.tooltip');
|
|
760
|
+
if(!tooltip || tooltip.textContent !== '$25.00'){
|
|
761
|
+
window.dispatchEvent(new MouseEvent('mouseup'));
|
|
762
|
+
cleanup(container);
|
|
763
|
+
return fail(`Expected tooltip "$25.00", got "${tooltip ? tooltip.textContent : 'none'}"`);
|
|
764
|
+
}
|
|
765
|
+
window.dispatchEvent(new MouseEvent('mouseup'));
|
|
766
|
+
cleanup(container);
|
|
767
|
+
pass('Tooltip uses format attribute');
|
|
768
|
+
},
|
|
769
|
+
|
|
770
|
+
'formattedValue should return formatted single value': async ({pass, fail}) => {
|
|
771
|
+
const { container, el } = await createSlider({ value: '50', format: '$0.00' });
|
|
772
|
+
if(el.formattedValue !== '$50.00'){
|
|
773
|
+
cleanup(container);
|
|
774
|
+
return fail(`Expected "$50.00", got "${el.formattedValue}"`);
|
|
775
|
+
}
|
|
776
|
+
cleanup(container);
|
|
777
|
+
pass('formattedValue works for single value');
|
|
778
|
+
},
|
|
779
|
+
|
|
780
|
+
'formattedValue should return formatted range value': async ({pass, fail}) => {
|
|
781
|
+
const { container, el } = await createSlider({ value: '20,80', format: '$0.00' });
|
|
782
|
+
if(el.formattedValue !== '$20.00,$80.00'){
|
|
783
|
+
cleanup(container);
|
|
784
|
+
return fail(`Expected "$20.00,$80.00", got "${el.formattedValue}"`);
|
|
785
|
+
}
|
|
786
|
+
cleanup(container);
|
|
787
|
+
pass('formattedValue works for range value');
|
|
788
|
+
},
|
|
789
|
+
|
|
790
|
+
'formattedValue should return raw value without format': async ({pass, fail}) => {
|
|
791
|
+
const { container, el } = await createSlider({ value: '50' });
|
|
792
|
+
if(el.formattedValue !== '50'){
|
|
793
|
+
cleanup(container);
|
|
794
|
+
return fail(`Expected "50", got "${el.formattedValue}"`);
|
|
795
|
+
}
|
|
796
|
+
cleanup(container);
|
|
797
|
+
pass('formattedValue returns raw value without format');
|
|
798
|
+
},
|
|
799
|
+
|
|
800
|
+
'change event should use formatted value': async ({pass, fail}) => {
|
|
801
|
+
const { container, el } = await createSlider({ value: '0', format: '$0.00' });
|
|
802
|
+
let eventValue = null;
|
|
803
|
+
el.addEventListener('change', (e) => { eventValue = e.detail.value; });
|
|
804
|
+
el.setValue(50);
|
|
805
|
+
await el.updateComplete;
|
|
806
|
+
if(eventValue !== '$50.00'){
|
|
807
|
+
cleanup(container);
|
|
808
|
+
return fail(`Expected change event value "$50.00", got "${eventValue}"`);
|
|
809
|
+
}
|
|
810
|
+
cleanup(container);
|
|
811
|
+
pass('Change event uses formatted value');
|
|
812
|
+
},
|
|
813
|
+
|
|
814
|
+
'tooltip property defaults to false': async ({pass, fail}) => {
|
|
815
|
+
const { container, el } = await createSlider();
|
|
816
|
+
if(el.tooltip !== false){
|
|
817
|
+
cleanup(container);
|
|
818
|
+
return fail(`Expected tooltip false, got ${el.tooltip}`);
|
|
819
|
+
}
|
|
820
|
+
cleanup(container);
|
|
821
|
+
pass('Default tooltip is false');
|
|
822
|
+
},
|
|
823
|
+
};
|