kempo-ui 0.3.16 → 0.4.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.
Files changed (75) hide show
  1. package/dist/components/Progress.js +86 -0
  2. package/dist/components/Spinner.js +2 -2
  3. package/docs/components/accordion.html +3 -0
  4. package/docs/components/aside.html +3 -0
  5. package/docs/components/card.html +3 -0
  6. package/docs/components/code-editor.html +3 -0
  7. package/docs/components/color-picker.html +3 -0
  8. package/docs/components/combobox.html +3 -0
  9. package/docs/components/content-slider.html +3 -0
  10. package/docs/components/context.html +3 -0
  11. package/docs/components/dialog.html +3 -0
  12. package/docs/components/dropdown.html +3 -0
  13. package/docs/components/filter-list.html +3 -0
  14. package/docs/components/focus-capture.html +3 -0
  15. package/docs/components/html-editor.html +3 -0
  16. package/docs/components/hybrid-component.html +3 -0
  17. package/docs/components/icon.html +3 -0
  18. package/docs/components/import.html +3 -0
  19. package/docs/components/light-component.html +3 -0
  20. package/docs/components/nav-spacer.html +3 -0
  21. package/docs/components/nav.html +3 -0
  22. package/docs/components/photo-viewer.html +3 -0
  23. package/docs/components/progress.html +625 -0
  24. package/docs/components/resize.html +3 -0
  25. package/docs/components/shadow-component.html +3 -0
  26. package/docs/components/show-more.html +3 -0
  27. package/docs/components/sortable.html +3 -0
  28. package/docs/components/spinner.html +3 -0
  29. package/docs/components/split.html +3 -0
  30. package/docs/components/table.html +3 -0
  31. package/docs/components/tableControls.html +3 -0
  32. package/docs/components/tableCustomFields.html +3 -0
  33. package/docs/components/tableFetchRecords.html +3 -0
  34. package/docs/components/tableFieldSortHide.html +3 -0
  35. package/docs/components/tablePagination.html +3 -0
  36. package/docs/components/tablePlaceholder.html +3 -0
  37. package/docs/components/tableRecordEditing.html +3 -0
  38. package/docs/components/tableRecordFiltering.html +3 -0
  39. package/docs/components/tableRecordHiding.html +3 -0
  40. package/docs/components/tableRecordSearching.html +3 -0
  41. package/docs/components/tableRecordSelection.html +3 -0
  42. package/docs/components/tableRowControls.html +3 -0
  43. package/docs/components/tableServerSync.html +3 -0
  44. package/docs/components/tableSorting.html +3 -0
  45. package/docs/components/tabs.html +3 -0
  46. package/docs/components/tags.html +3 -0
  47. package/docs/components/theme-select.html +3 -0
  48. package/docs/components/theme-switcher.html +3 -0
  49. package/docs/components/timestamp.html +3 -0
  50. package/docs/components/toast.html +3 -0
  51. package/docs/components/toggle.html +3 -0
  52. package/docs/components/tree.html +3 -0
  53. package/docs/index.html +9 -0
  54. package/docs/src/components/Progress.js +86 -0
  55. package/docs/src/components/Spinner.js +2 -2
  56. package/docs/utils/context.html +3 -0
  57. package/docs/utils/cookie.html +3 -0
  58. package/docs/utils/debounce.html +3 -0
  59. package/docs/utils/drag.html +3 -0
  60. package/docs/utils/elevation.html +3 -0
  61. package/docs/utils/formatTimestamp.html +3 -0
  62. package/docs/utils/object.html +3 -0
  63. package/docs/utils/propConverters.html +3 -0
  64. package/docs/utils/string.html +3 -0
  65. package/docs/utils/theme.html +3 -0
  66. package/docs/utils/toTitleCase.html +3 -0
  67. package/docs/utils/type.html +3 -0
  68. package/docs/utils/wait.html +3 -0
  69. package/docs-src/components/progress.page.html +182 -0
  70. package/docs-src/index.page.html +6 -0
  71. package/docs-src/nav.fragment.html +3 -0
  72. package/package.json +3 -3
  73. package/src/components/Progress.js +148 -0
  74. package/src/components/Spinner.js +19 -18
  75. package/tests/components/Progress.browser-test.js +444 -0
@@ -0,0 +1,148 @@
1
+ import { html, css } from '../lit-all.min.js';
2
+ import ShadowComponent from './ShadowComponent.js';
3
+ import { boolExists } from '../utils/propConverters.js';
4
+
5
+ export default class Progress extends ShadowComponent {
6
+ static properties = {
7
+ percentage: { type: String },
8
+ offset: { type: String },
9
+ color: { type: String },
10
+ label: { type: Boolean, converter: boolExists },
11
+ indeterminate: { type: String, reflect: true }
12
+ };
13
+
14
+ constructor() {
15
+ super();
16
+ this.percentage = '25';
17
+ this.offset = '0';
18
+ this.color = 'var(--c_primary)';
19
+ this.indeterminate = null;
20
+ }
21
+
22
+ /*
23
+ Public Members
24
+ */
25
+ get bars(){
26
+ const percentages = (this.percentage||'25').split('|').map(p=>p.trim());
27
+ const offsets = (this.offset||0).split('|').map(o=>o.trim());
28
+ const colors = (this.color||'var(--c_primary)').split('|').map(c=>c.trim());
29
+ return percentages.map( (percentage, i) => {
30
+ let offset = offsets[0];
31
+ if(offsets.length > i) offset = offsets[i];
32
+ let color = colors[0];
33
+ if(colors.length > i) color = colors[i];
34
+ return {
35
+ percentage,
36
+ offset,
37
+ color
38
+ }
39
+ });
40
+ }
41
+
42
+ /*
43
+ Rendering
44
+ */
45
+ render() {
46
+ const [{color}] = this.bars;
47
+ if(this.indeterminate !== null) return html`
48
+ <div id="bar1" class="bar indeterminate" style="background:${color};animation-duration:${this.indeterminate||'2s'}"></div>
49
+ <div id="bar2" class="bar indeterminate" style="background:${color};animation-duration:${this.indeterminate||'2s'}"></div>
50
+ `;
51
+ return html`${
52
+ this.bars.map(({
53
+ percentage,
54
+ offset,
55
+ color
56
+ }) => html`
57
+ <div
58
+ class="bar"
59
+ style="left:${offset}%;width:${percentage}%;background:${color}"
60
+ >
61
+ ${this.label?html`<div class="label">${percentage}%</div>`:''}
62
+ </div>
63
+ `)
64
+ }`;
65
+ }
66
+
67
+ /*
68
+ Styles
69
+ */
70
+ static styles = css`
71
+ :host {
72
+ --radius: 99999px;
73
+
74
+ display: block;
75
+ height: 1rem;
76
+ width: 100%;
77
+ background-color: var(--c_border);
78
+ position: relative;
79
+ overflow: hidden;
80
+ color: var(--tc_on_primary);
81
+ font-size: 0.75rem;
82
+ border-radius: var(--radius);
83
+ }
84
+
85
+ .bar {
86
+ display: flex;
87
+ align-items: center;
88
+ height: 100%;
89
+ transition: width 0.3s ease;
90
+ position: absolute;
91
+ left: 0;
92
+ border-radius: var(--radius);
93
+ }
94
+ .label {
95
+ padding: 0 var(--spacer);
96
+ }
97
+
98
+ :host([indeterminate]) .bar {
99
+ width: 25%;
100
+ transition: none;
101
+ }
102
+ :host([indeterminate]) #bar1 {
103
+ animation-name: indeterminate-bar1;
104
+ animation-timing-function: linear;
105
+ animation-iteration-count: infinite;
106
+ }
107
+ :host([indeterminate]) #bar2 {
108
+ animation-name: indeterminate-bar2;
109
+ animation-timing-function: linear;
110
+ animation-iteration-count: infinite;
111
+ }
112
+
113
+ @keyframes indeterminate-bar1 {
114
+ 0% {
115
+ left: 0%;
116
+ }
117
+ 100% {
118
+ left: 100%;
119
+ }
120
+ }
121
+ @keyframes indeterminate-bar2 {
122
+ 0% {
123
+ left: 0%;
124
+ opacity: 1;
125
+ }
126
+ 74% {
127
+ opacity: 1;
128
+ }
129
+ 75% {
130
+ left: 75%;
131
+ opacity: 0;
132
+ }
133
+ 76% {
134
+ left: -24%;
135
+ opacity: 0;
136
+ }
137
+ 76.5% {
138
+ opacity: 1;
139
+ }
140
+ 100% {
141
+ left: 0%;
142
+ opacity: 1;
143
+ }
144
+ }
145
+ `;
146
+ }
147
+
148
+ customElements.define('k-progress', Progress);
@@ -13,6 +13,25 @@ export default class Spinner extends ShadowComponent {
13
13
  this.variant = 'spinner';
14
14
  }
15
15
 
16
+
17
+
18
+ /*
19
+ Rendering
20
+ */
21
+ render() {
22
+ switch(this.variant) {
23
+ case 'dots':
24
+ return html`<div class="dots"><span></span><span></span><span></span></div>`;
25
+ case 'bars':
26
+ return html`<div class="bars"><span></span><span></span><span></span><span></span></div>`;
27
+ case 'pulse':
28
+ return html`<div class="pulse"></div>`;
29
+ case 'ring':
30
+ return html`<div class="ring"></div>`;
31
+ default:
32
+ return html`<div class="spinner"></div>`;
33
+ }
34
+ }
16
35
  /*
17
36
  Styles
18
37
  */
@@ -129,24 +148,6 @@ export default class Spinner extends ShadowComponent {
129
148
  100% { transform: rotate(360deg); border-color: var(--spinner-color) transparent transparent transparent; }
130
149
  }
131
150
  `;
132
-
133
- /*
134
- Rendering
135
- */
136
- render() {
137
- switch(this.variant) {
138
- case 'dots':
139
- return html`<div class="dots"><span></span><span></span><span></span></div>`;
140
- case 'bars':
141
- return html`<div class="bars"><span></span><span></span><span></span><span></span></div>`;
142
- case 'pulse':
143
- return html`<div class="pulse"></div>`;
144
- case 'ring':
145
- return html`<div class="ring"></div>`;
146
- default:
147
- return html`<div class="spinner"></div>`;
148
- }
149
- }
150
151
  }
151
152
 
152
153
  customElements.define('k-spinner', Spinner);
@@ -0,0 +1,444 @@
1
+ import Progress from '../../src/components/Progress.js';
2
+
3
+ const createProgress = async (options = {}) => {
4
+ const progress = document.createElement('k-progress');
5
+ if(options.percentage !== undefined) progress.percentage = options.percentage;
6
+ if(options.offset !== undefined) progress.offset = options.offset;
7
+ if(options.color !== undefined) progress.color = options.color;
8
+ if(options.label !== undefined) progress.label = options.label;
9
+ if(options.indeterminate !== undefined) progress.indeterminate = options.indeterminate;
10
+ document.body.appendChild(progress);
11
+ await progress.updateComplete;
12
+ return progress;
13
+ };
14
+
15
+ const cleanup = (element) => {
16
+ if(element && element.parentNode){
17
+ element.parentNode.removeChild(element);
18
+ }
19
+ };
20
+
21
+ export default {
22
+ /*
23
+ Progress Element Tests
24
+ */
25
+ 'should create progress element': async ({pass, fail}) => {
26
+ const progress = await createProgress();
27
+
28
+ if(!progress){
29
+ cleanup(progress);
30
+ fail('Progress element should be created');
31
+ return;
32
+ }
33
+
34
+ if(!(progress instanceof Progress)){
35
+ cleanup(progress);
36
+ fail('Element should be instance of Progress');
37
+ return;
38
+ }
39
+
40
+ cleanup(progress);
41
+ pass('Progress element created correctly');
42
+ },
43
+
44
+ 'should have shadow root': async ({pass, fail}) => {
45
+ const progress = await createProgress();
46
+
47
+ if(!progress.shadowRoot){
48
+ cleanup(progress);
49
+ fail('Progress should have shadow root');
50
+ return;
51
+ }
52
+
53
+ cleanup(progress);
54
+ pass('Progress has shadow root');
55
+ },
56
+
57
+ /*
58
+ Default Properties Tests
59
+ */
60
+ 'should have default percentage of 25': async ({pass, fail}) => {
61
+ const progress = await createProgress();
62
+
63
+ if(progress.percentage !== '25'){
64
+ cleanup(progress);
65
+ fail(`Expected default percentage "25", got "${progress.percentage}"`);
66
+ return;
67
+ }
68
+
69
+ cleanup(progress);
70
+ pass('Default percentage is 25');
71
+ },
72
+
73
+ 'should have default offset of 0': async ({pass, fail}) => {
74
+ const progress = await createProgress();
75
+
76
+ if(progress.offset !== '0'){
77
+ cleanup(progress);
78
+ fail(`Expected default offset "0", got "${progress.offset}"`);
79
+ return;
80
+ }
81
+
82
+ cleanup(progress);
83
+ pass('Default offset is 0');
84
+ },
85
+
86
+ 'should have default color of var(--c_primary)': async ({pass, fail}) => {
87
+ const progress = await createProgress();
88
+
89
+ if(progress.color !== 'var(--c_primary)'){
90
+ cleanup(progress);
91
+ fail(`Expected default color "var(--c_primary)", got "${progress.color}"`);
92
+ return;
93
+ }
94
+
95
+ cleanup(progress);
96
+ pass('Default color is var(--c_primary)');
97
+ },
98
+
99
+ 'should have default label as undefined': async ({pass, fail}) => {
100
+ const progress = await createProgress();
101
+
102
+ if(progress.label !== undefined){
103
+ cleanup(progress);
104
+ fail(`Expected default label undefined, got ${progress.label}`);
105
+ return;
106
+ }
107
+
108
+ cleanup(progress);
109
+ pass('Default label is undefined');
110
+ },
111
+
112
+ 'should have default indeterminate as null': async ({pass, fail}) => {
113
+ const progress = await createProgress();
114
+
115
+ if(progress.indeterminate !== null){
116
+ cleanup(progress);
117
+ fail(`Expected default indeterminate null, got "${progress.indeterminate}"`);
118
+ return;
119
+ }
120
+
121
+ cleanup(progress);
122
+ pass('Default indeterminate is null');
123
+ },
124
+
125
+ /*
126
+ Property Tests
127
+ */
128
+ 'should set percentage property': async ({pass, fail}) => {
129
+ const progress = await createProgress({ percentage: '50' });
130
+
131
+ if(progress.percentage !== '50'){
132
+ cleanup(progress);
133
+ fail(`Expected percentage "50", got "${progress.percentage}"`);
134
+ return;
135
+ }
136
+
137
+ cleanup(progress);
138
+ pass('Percentage property is set correctly');
139
+ },
140
+
141
+ 'should set offset property': async ({pass, fail}) => {
142
+ const progress = await createProgress({ offset: '25' });
143
+
144
+ if(progress.offset !== '25'){
145
+ cleanup(progress);
146
+ fail(`Expected offset "25", got "${progress.offset}"`);
147
+ return;
148
+ }
149
+
150
+ cleanup(progress);
151
+ pass('Offset property is set correctly');
152
+ },
153
+
154
+ 'should set color property': async ({pass, fail}) => {
155
+ const progress = await createProgress({ color: 'green' });
156
+
157
+ if(progress.color !== 'green'){
158
+ cleanup(progress);
159
+ fail(`Expected color "green", got "${progress.color}"`);
160
+ return;
161
+ }
162
+
163
+ cleanup(progress);
164
+ pass('Color property is set correctly');
165
+ },
166
+
167
+ 'should set label property': async ({pass, fail}) => {
168
+ const progress = await createProgress({ label: true });
169
+
170
+ if(progress.label !== true){
171
+ cleanup(progress);
172
+ fail(`Expected label true, got ${progress.label}`);
173
+ return;
174
+ }
175
+
176
+ cleanup(progress);
177
+ pass('Label property is set correctly');
178
+ },
179
+
180
+ 'should set indeterminate property with duration': async ({pass, fail}) => {
181
+ const progress = await createProgress({ indeterminate: '3s' });
182
+
183
+ if(progress.indeterminate !== '3s'){
184
+ cleanup(progress);
185
+ fail(`Expected indeterminate "3s", got "${progress.indeterminate}"`);
186
+ return;
187
+ }
188
+
189
+ cleanup(progress);
190
+ pass('Indeterminate property is set correctly with duration');
191
+ },
192
+
193
+ /*
194
+ Attribute Reflection Tests
195
+ */
196
+ 'should not reflect percentage attribute': async ({pass, fail}) => {
197
+ const progress = await createProgress({ percentage: '75' });
198
+
199
+ if(progress.hasAttribute('percentage')){
200
+ cleanup(progress);
201
+ fail('Progress should not have percentage attribute (not reflecting)');
202
+ return;
203
+ }
204
+
205
+ cleanup(progress);
206
+ pass('Percentage property does not reflect to attribute');
207
+ },
208
+
209
+ 'should reflect indeterminate attribute': async ({pass, fail}) => {
210
+ const progress = await createProgress({ indeterminate: '2s' });
211
+
212
+ if(!progress.hasAttribute('indeterminate')){
213
+ cleanup(progress);
214
+ fail('Progress should have indeterminate attribute');
215
+ return;
216
+ }
217
+
218
+ if(progress.getAttribute('indeterminate') !== '2s'){
219
+ cleanup(progress);
220
+ fail(`Expected attribute "2s", got "${progress.getAttribute('indeterminate')}"`);
221
+ return;
222
+ }
223
+
224
+ cleanup(progress);
225
+ pass('Indeterminate attribute reflects property');
226
+ },
227
+
228
+ /*
229
+ Bars Getter Tests
230
+ */
231
+ 'should parse single bar correctly': async ({pass, fail}) => {
232
+ const progress = await createProgress({ percentage: '50', color: 'blue', offset: '10' });
233
+ const bars = progress.bars;
234
+
235
+ if(!Array.isArray(bars) || bars.length !== 1){
236
+ cleanup(progress);
237
+ fail(`Expected 1 bar, got ${bars.length}`);
238
+ return;
239
+ }
240
+
241
+ const bar = bars[0];
242
+ if(bar.percentage !== '50' || bar.color !== 'blue' || bar.offset !== '10'){
243
+ cleanup(progress);
244
+ fail(`Expected bar {percentage: '50', color: 'blue', offset: '10'}, got ${JSON.stringify(bar)}`);
245
+ return;
246
+ }
247
+
248
+ cleanup(progress);
249
+ pass('Single bar parsed correctly');
250
+ },
251
+
252
+ 'should parse multiple bars with pipe delimiter': async ({pass, fail}) => {
253
+ const progress = await createProgress({
254
+ percentage: '25|30',
255
+ color: 'red|blue',
256
+ offset: '0|50'
257
+ });
258
+ const bars = progress.bars;
259
+
260
+ if(!Array.isArray(bars) || bars.length !== 2){
261
+ cleanup(progress);
262
+ fail(`Expected 2 bars, got ${bars.length}`);
263
+ return;
264
+ }
265
+
266
+ if(bars[0].percentage !== '25' || bars[0].color !== 'red' || bars[0].offset !== '0'){
267
+ cleanup(progress);
268
+ fail(`Expected first bar {percentage: '25', color: 'red', offset: '0'}, got ${JSON.stringify(bars[0])}`);
269
+ return;
270
+ }
271
+
272
+ if(bars[1].percentage !== '30' || bars[1].color !== 'blue' || bars[1].offset !== '50'){
273
+ cleanup(progress);
274
+ fail(`Expected second bar {percentage: '30', color: 'blue', offset: '50'}, got ${JSON.stringify(bars[1])}`);
275
+ return;
276
+ }
277
+
278
+ cleanup(progress);
279
+ pass('Multiple bars parsed correctly');
280
+ },
281
+
282
+ 'should handle missing offset for additional bars': async ({pass, fail}) => {
283
+ const progress = await createProgress({
284
+ percentage: '25|30',
285
+ color: 'red|blue'
286
+ });
287
+ const bars = progress.bars;
288
+
289
+ if(bars[0].offset !== '0' || bars[1].offset !== '0'){
290
+ cleanup(progress);
291
+ fail(`Expected both offsets to be '0', got "${bars[0].offset}" and "${bars[1].offset}"`);
292
+ return;
293
+ }
294
+
295
+ cleanup(progress);
296
+ pass('Missing offset defaults to first offset for additional bars');
297
+ },
298
+
299
+ 'should support gradient colors': async ({pass, fail}) => {
300
+ const gradient = 'linear-gradient(to right, red 0%, blue 100%)';
301
+ const progress = await createProgress({ color: gradient });
302
+
303
+ if(progress.color !== gradient){
304
+ cleanup(progress);
305
+ fail(`Expected gradient color to be preserved, got "${progress.color}"`);
306
+ return;
307
+ }
308
+
309
+ cleanup(progress);
310
+ pass('Gradient colors are supported');
311
+ },
312
+
313
+ /*
314
+ Rendering Tests
315
+ */
316
+ 'should render bar element in shadow dom': async ({pass, fail}) => {
317
+ const progress = await createProgress({ percentage: '50' });
318
+ const bar = progress.shadowRoot.querySelector('.bar');
319
+
320
+ if(!bar){
321
+ cleanup(progress);
322
+ fail('Bar element should be rendered in shadow DOM');
323
+ return;
324
+ }
325
+
326
+ cleanup(progress);
327
+ pass('Bar element rendered correctly');
328
+ },
329
+
330
+ 'should apply color style to bar': async ({pass, fail}) => {
331
+ const progress = await createProgress({ percentage: '50', color: 'rgb(255, 0, 0)' });
332
+ const bar = progress.shadowRoot.querySelector('.bar');
333
+ const style = bar.getAttribute('style');
334
+
335
+ if(!style || !style.includes('rgb(255, 0, 0)')){
336
+ cleanup(progress);
337
+ fail(`Expected color style to include "rgb(255, 0, 0)", got "${style}"`);
338
+ return;
339
+ }
340
+
341
+ cleanup(progress);
342
+ pass('Color style applied correctly to bar');
343
+ },
344
+
345
+ 'should render label when label property is true': async ({pass, fail}) => {
346
+ const progress = await createProgress({ percentage: '33', label: true });
347
+ const label = progress.shadowRoot.querySelector('.label');
348
+
349
+ if(!label){
350
+ cleanup(progress);
351
+ fail('Label element should be rendered when label property is true');
352
+ return;
353
+ }
354
+
355
+ if(!label.textContent.includes('33')){
356
+ cleanup(progress);
357
+ fail(`Expected label to contain "33", got "${label.textContent}"`);
358
+ return;
359
+ }
360
+
361
+ cleanup(progress);
362
+ pass('Label rendered correctly with percentage');
363
+ },
364
+
365
+ 'should not render label when label property is false': async ({pass, fail}) => {
366
+ const progress = await createProgress({ percentage: '50', label: false });
367
+ const label = progress.shadowRoot.querySelector('.label');
368
+
369
+ if(label){
370
+ cleanup(progress);
371
+ fail('Label element should not be rendered when label property is false');
372
+ return;
373
+ }
374
+
375
+ cleanup(progress);
376
+ pass('Label not rendered when label is false');
377
+ },
378
+
379
+ 'should render indeterminate bars': async ({pass, fail}) => {
380
+ const progress = await createProgress({ indeterminate: '2s' });
381
+ const bars = progress.shadowRoot.querySelectorAll('.bar');
382
+
383
+ if(!bars || bars.length !== 2){
384
+ cleanup(progress);
385
+ fail(`Expected 2 bars for indeterminate, got ${bars.length}`);
386
+ return;
387
+ }
388
+
389
+ if(!bars[0].id || !bars[1].id){
390
+ cleanup(progress);
391
+ fail('Indeterminate bars should have ids');
392
+ return;
393
+ }
394
+
395
+ cleanup(progress);
396
+ pass('Indeterminate bars rendered correctly');
397
+ },
398
+
399
+ 'should apply animation duration to indeterminate bars': async ({pass, fail}) => {
400
+ const progress = await createProgress({ indeterminate: '5s' });
401
+ const bar1 = progress.shadowRoot.querySelector('#bar1');
402
+
403
+ if(!bar1){
404
+ cleanup(progress);
405
+ fail('Bar 1 should exist');
406
+ return;
407
+ }
408
+
409
+ const style = bar1.getAttribute('style');
410
+ if(!style || !style.includes('5s')){
411
+ cleanup(progress);
412
+ fail(`Expected animation duration "5s" in style, got "${style}"`);
413
+ return;
414
+ }
415
+
416
+ cleanup(progress);
417
+ pass('Animation duration applied to indeterminate bars');
418
+ },
419
+
420
+ 'should render multiple regular bars': async ({pass, fail}) => {
421
+ const progress = await createProgress({
422
+ percentage: '25|30',
423
+ color: 'red|blue',
424
+ offset: '0|50'
425
+ });
426
+ const bars = progress.shadowRoot.querySelectorAll('.bar');
427
+
428
+ if(!bars || bars.length !== 2){
429
+ cleanup(progress);
430
+ fail(`Expected 2 bars, got ${bars.length}`);
431
+ return;
432
+ }
433
+
434
+ const styles = Array.from(bars).map(b => b.getAttribute('style'));
435
+ if(!styles[0].includes('25%') || !styles[1].includes('30%')){
436
+ cleanup(progress);
437
+ fail(`Expected width percentages "25%" and "30%", got ${styles.join(', ')}`);
438
+ return;
439
+ }
440
+
441
+ cleanup(progress);
442
+ pass('Multiple regular bars rendered correctly');
443
+ },
444
+ };