@aquera/nile-elements 2.0.2 → 2.0.3
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/README.md +3 -0
- package/dist/index.cjs.js +1 -1
- package/dist/index.esm.js +1 -1
- package/dist/index.js +949 -640
- package/dist/nile-segmented-progress-bar/index.cjs.js +2 -0
- package/dist/nile-segmented-progress-bar/index.cjs.js.map +1 -0
- package/dist/nile-segmented-progress-bar/index.esm.js +1 -0
- package/dist/nile-segmented-progress-bar/nile-segmented-progress-bar.cjs.js +2 -0
- package/dist/nile-segmented-progress-bar/nile-segmented-progress-bar.cjs.js.map +1 -0
- package/dist/nile-segmented-progress-bar/nile-segmented-progress-bar.css.cjs.js +2 -0
- package/dist/nile-segmented-progress-bar/nile-segmented-progress-bar.css.cjs.js.map +1 -0
- package/dist/nile-segmented-progress-bar/nile-segmented-progress-bar.css.esm.js +243 -0
- package/dist/nile-segmented-progress-bar/nile-segmented-progress-bar.esm.js +69 -0
- package/dist/nile-wysiwyg-editor/engine/commands.cjs.js +1 -1
- package/dist/nile-wysiwyg-editor/engine/commands.cjs.js.map +1 -1
- package/dist/nile-wysiwyg-editor/engine/commands.esm.js +1 -1
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.js +1 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/nile-segmented-progress-bar/index.d.ts +2 -0
- package/dist/src/nile-segmented-progress-bar/index.js +2 -0
- package/dist/src/nile-segmented-progress-bar/index.js.map +1 -0
- package/dist/src/nile-segmented-progress-bar/nile-segmented-progress-bar.css.d.ts +9 -0
- package/dist/src/nile-segmented-progress-bar/nile-segmented-progress-bar.css.js +252 -0
- package/dist/src/nile-segmented-progress-bar/nile-segmented-progress-bar.css.js.map +1 -0
- package/dist/src/nile-segmented-progress-bar/nile-segmented-progress-bar.d.ts +135 -0
- package/dist/src/nile-segmented-progress-bar/nile-segmented-progress-bar.js +373 -0
- package/dist/src/nile-segmented-progress-bar/nile-segmented-progress-bar.js.map +1 -0
- package/dist/src/nile-segmented-progress-bar/nile-segmented-progress-bar.test.d.ts +1 -0
- package/dist/src/nile-segmented-progress-bar/nile-segmented-progress-bar.test.js +961 -0
- package/dist/src/nile-segmented-progress-bar/nile-segmented-progress-bar.test.js.map +1 -0
- package/dist/src/version.js +1 -1
- package/dist/src/version.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -1
- package/src/index.ts +5 -0
- package/src/nile-segmented-progress-bar/index.ts +5 -0
- package/src/nile-segmented-progress-bar/nile-segmented-progress-bar.css.ts +254 -0
- package/src/nile-segmented-progress-bar/nile-segmented-progress-bar.test.ts +1298 -0
- package/src/nile-segmented-progress-bar/nile-segmented-progress-bar.ts +437 -0
- package/vscode-html-custom-data.json +113 -0
|
@@ -0,0 +1,961 @@
|
|
|
1
|
+
import { expect, fixture, html } from '@open-wc/testing';
|
|
2
|
+
import './nile-segmented-progress-bar';
|
|
3
|
+
/** The reference "Run outcomes" data — sum 98365, with two sub-1% slivers. */
|
|
4
|
+
const RUN_OUTCOMES = [
|
|
5
|
+
{ label: 'Success', value: 45623 },
|
|
6
|
+
{ label: 'Failure', value: 50342 },
|
|
7
|
+
{ label: 'Errors', value: 682 },
|
|
8
|
+
{ label: 'Running', value: 166 },
|
|
9
|
+
{ label: 'Other', value: 1552 },
|
|
10
|
+
];
|
|
11
|
+
const SUM = 98365;
|
|
12
|
+
const segmentsOf = (el) => Array.from(el.shadowRoot.querySelectorAll('[part="segment"]'));
|
|
13
|
+
const trackOf = (el) => el.shadowRoot.querySelector('[part~="track"]');
|
|
14
|
+
const legendItemsOf = (el) => Array.from(el.shadowRoot.querySelectorAll('[part="legend-item"]'));
|
|
15
|
+
const tooltipsOf = (el) => Array.from(el.shadowRoot.querySelectorAll('nile-lite-tooltip'));
|
|
16
|
+
/** Mounts a bar inside a fixed-width wrapper so layout assertions are stable. */
|
|
17
|
+
async function mount(width, segments = RUN_OUTCOMES, minSegmentWidth = 2) {
|
|
18
|
+
const wrapper = await fixture(html `
|
|
19
|
+
<div style="width:${width}px">
|
|
20
|
+
<nile-segmented-progress-bar
|
|
21
|
+
label="Run outcomes"
|
|
22
|
+
.segments=${segments}
|
|
23
|
+
.minSegmentWidth=${minSegmentWidth}
|
|
24
|
+
></nile-segmented-progress-bar>
|
|
25
|
+
</div>
|
|
26
|
+
`);
|
|
27
|
+
const el = wrapper.querySelector('nile-segmented-progress-bar');
|
|
28
|
+
await el.updateComplete;
|
|
29
|
+
return el;
|
|
30
|
+
}
|
|
31
|
+
/** Runs `fn` with `console.error` captured, then restores it. */
|
|
32
|
+
async function captureErrors(fn) {
|
|
33
|
+
const errors = [];
|
|
34
|
+
const original = console.error;
|
|
35
|
+
console.error = (...args) => {
|
|
36
|
+
errors.push(args.map(arg => String(arg)).join(' '));
|
|
37
|
+
};
|
|
38
|
+
try {
|
|
39
|
+
await fn();
|
|
40
|
+
}
|
|
41
|
+
finally {
|
|
42
|
+
console.error = original;
|
|
43
|
+
}
|
|
44
|
+
return errors;
|
|
45
|
+
}
|
|
46
|
+
describe('NileSegmentedProgressBar', () => {
|
|
47
|
+
/* #region Boilerplate */
|
|
48
|
+
it('1. renders', async () => {
|
|
49
|
+
const el = await fixture(html `<nile-segmented-progress-bar></nile-segmented-progress-bar>`);
|
|
50
|
+
expect(el).to.exist;
|
|
51
|
+
});
|
|
52
|
+
it('2. shadow root', async () => {
|
|
53
|
+
const el = await fixture(html `<nile-segmented-progress-bar></nile-segmented-progress-bar>`);
|
|
54
|
+
expect(el.shadowRoot).to.not.be.null;
|
|
55
|
+
});
|
|
56
|
+
it('3. tag name', async () => {
|
|
57
|
+
const el = await fixture(html `<nile-segmented-progress-bar></nile-segmented-progress-bar>`);
|
|
58
|
+
expect(el.tagName.toLowerCase()).to.equal('nile-segmented-progress-bar');
|
|
59
|
+
});
|
|
60
|
+
it('4. is defined', async () => {
|
|
61
|
+
expect(customElements.get('nile-segmented-progress-bar')).to.exist;
|
|
62
|
+
});
|
|
63
|
+
it('5. shadow mode is open', async () => {
|
|
64
|
+
const el = await fixture(html `<nile-segmented-progress-bar></nile-segmented-progress-bar>`);
|
|
65
|
+
expect(el.shadowRoot.mode).to.equal('open');
|
|
66
|
+
});
|
|
67
|
+
/* #endregion */
|
|
68
|
+
/* #region Property defaults */
|
|
69
|
+
it('6. segments defaults to an empty array', async () => {
|
|
70
|
+
const el = await fixture(html `<nile-segmented-progress-bar></nile-segmented-progress-bar>`);
|
|
71
|
+
expect(el.segments).to.deep.equal([]);
|
|
72
|
+
});
|
|
73
|
+
it('7. label defaults empty', async () => {
|
|
74
|
+
const el = await fixture(html `<nile-segmented-progress-bar></nile-segmented-progress-bar>`);
|
|
75
|
+
expect(el.label).to.equal('');
|
|
76
|
+
});
|
|
77
|
+
it('8. showLegend defaults true', async () => {
|
|
78
|
+
const el = await fixture(html `<nile-segmented-progress-bar></nile-segmented-progress-bar>`);
|
|
79
|
+
expect(el.showLegend).to.be.true;
|
|
80
|
+
});
|
|
81
|
+
it('9. showTooltip defaults true', async () => {
|
|
82
|
+
const el = await fixture(html `<nile-segmented-progress-bar></nile-segmented-progress-bar>`);
|
|
83
|
+
expect(el.showTooltip).to.be.true;
|
|
84
|
+
});
|
|
85
|
+
it('10. showValues defaults true', async () => {
|
|
86
|
+
const el = await fixture(html `<nile-segmented-progress-bar></nile-segmented-progress-bar>`);
|
|
87
|
+
expect(el.showValues).to.be.true;
|
|
88
|
+
});
|
|
89
|
+
it('11. size defaults medium', async () => {
|
|
90
|
+
const el = await fixture(html `<nile-segmented-progress-bar></nile-segmented-progress-bar>`);
|
|
91
|
+
expect(el.size).to.equal('medium');
|
|
92
|
+
});
|
|
93
|
+
it('12. total defaults undefined', async () => {
|
|
94
|
+
const el = await fixture(html `<nile-segmented-progress-bar></nile-segmented-progress-bar>`);
|
|
95
|
+
expect(el.total).to.be.undefined;
|
|
96
|
+
});
|
|
97
|
+
it('13. rounded defaults true', async () => {
|
|
98
|
+
const el = await fixture(html `<nile-segmented-progress-bar></nile-segmented-progress-bar>`);
|
|
99
|
+
expect(el.rounded).to.be.true;
|
|
100
|
+
});
|
|
101
|
+
it('14. minSegmentWidth defaults 2', async () => {
|
|
102
|
+
const el = await fixture(html `<nile-segmented-progress-bar></nile-segmented-progress-bar>`);
|
|
103
|
+
expect(el.minSegmentWidth).to.equal(2);
|
|
104
|
+
});
|
|
105
|
+
it('15. tooltipPlacement defaults top', async () => {
|
|
106
|
+
const el = await fixture(html `<nile-segmented-progress-bar></nile-segmented-progress-bar>`);
|
|
107
|
+
expect(el.tooltipPlacement).to.equal('top');
|
|
108
|
+
});
|
|
109
|
+
/* #endregion */
|
|
110
|
+
/* #region Attributes */
|
|
111
|
+
it('16. label reflects to the label attribute', async () => {
|
|
112
|
+
const el = await fixture(html `<nile-segmented-progress-bar></nile-segmented-progress-bar>`);
|
|
113
|
+
el.label = 'Run outcomes';
|
|
114
|
+
await el.updateComplete;
|
|
115
|
+
expect(el.getAttribute('label')).to.equal('Run outcomes');
|
|
116
|
+
});
|
|
117
|
+
it('17. size reflects to the size attribute', async () => {
|
|
118
|
+
const el = await fixture(html `<nile-segmented-progress-bar></nile-segmented-progress-bar>`);
|
|
119
|
+
el.size = 'large';
|
|
120
|
+
await el.updateComplete;
|
|
121
|
+
expect(el.getAttribute('size')).to.equal('large');
|
|
122
|
+
});
|
|
123
|
+
it('18. size attribute sets the property', async () => {
|
|
124
|
+
const el = await fixture(html `<nile-segmented-progress-bar
|
|
125
|
+
size="small"
|
|
126
|
+
></nile-segmented-progress-bar>`);
|
|
127
|
+
expect(el.size).to.equal('small');
|
|
128
|
+
});
|
|
129
|
+
it('19. show-legend attribute maps to showLegend', async () => {
|
|
130
|
+
const el = await fixture(html `<nile-segmented-progress-bar
|
|
131
|
+
show-legend
|
|
132
|
+
></nile-segmented-progress-bar>`);
|
|
133
|
+
expect(el.showLegend).to.be.true;
|
|
134
|
+
});
|
|
135
|
+
it('20. show-tooltip attribute maps to showTooltip', async () => {
|
|
136
|
+
const el = await fixture(html `<nile-segmented-progress-bar
|
|
137
|
+
show-tooltip
|
|
138
|
+
></nile-segmented-progress-bar>`);
|
|
139
|
+
expect(el.showTooltip).to.be.true;
|
|
140
|
+
});
|
|
141
|
+
it('21. show-values attribute maps to showValues', async () => {
|
|
142
|
+
const el = await fixture(html `<nile-segmented-progress-bar
|
|
143
|
+
show-values
|
|
144
|
+
></nile-segmented-progress-bar>`);
|
|
145
|
+
expect(el.showValues).to.be.true;
|
|
146
|
+
});
|
|
147
|
+
it('22. min-segment-width attribute maps to minSegmentWidth', async () => {
|
|
148
|
+
const el = await fixture(html `<nile-segmented-progress-bar
|
|
149
|
+
min-segment-width="6"
|
|
150
|
+
></nile-segmented-progress-bar>`);
|
|
151
|
+
expect(el.minSegmentWidth).to.equal(6);
|
|
152
|
+
});
|
|
153
|
+
it('23. tooltip-placement attribute maps to tooltipPlacement', async () => {
|
|
154
|
+
const el = await fixture(html `<nile-segmented-progress-bar
|
|
155
|
+
tooltip-placement="bottom"
|
|
156
|
+
></nile-segmented-progress-bar>`);
|
|
157
|
+
expect(el.tooltipPlacement).to.equal('bottom');
|
|
158
|
+
});
|
|
159
|
+
it('24. total attribute maps to total as a number', async () => {
|
|
160
|
+
const el = await fixture(html `<nile-segmented-progress-bar
|
|
161
|
+
total="200"
|
|
162
|
+
></nile-segmented-progress-bar>`);
|
|
163
|
+
expect(el.total).to.equal(200);
|
|
164
|
+
});
|
|
165
|
+
/* #endregion */
|
|
166
|
+
/* #region Parts */
|
|
167
|
+
it('25. container part', async () => {
|
|
168
|
+
const el = await mount(800);
|
|
169
|
+
expect(el.shadowRoot.querySelector('[part="container"]')).to.exist;
|
|
170
|
+
});
|
|
171
|
+
it('26. label part renders when label is set', async () => {
|
|
172
|
+
const el = await mount(800);
|
|
173
|
+
const label = el.shadowRoot.querySelector('[part="label"]');
|
|
174
|
+
expect(label.textContent.trim()).to.equal('Run outcomes');
|
|
175
|
+
});
|
|
176
|
+
it('27. label part absent when label is empty', async () => {
|
|
177
|
+
const el = await fixture(html `<nile-segmented-progress-bar
|
|
178
|
+
.segments=${RUN_OUTCOMES}
|
|
179
|
+
></nile-segmented-progress-bar>`);
|
|
180
|
+
expect(el.shadowRoot.querySelector('[part="label"]')).to.be.null;
|
|
181
|
+
});
|
|
182
|
+
it('28. bar and track are the same element', async () => {
|
|
183
|
+
const el = await mount(800);
|
|
184
|
+
const bar = el.shadowRoot.querySelector('[part~="bar"]');
|
|
185
|
+
expect(bar).to.equal(trackOf(el));
|
|
186
|
+
});
|
|
187
|
+
it('29. segment parts', async () => {
|
|
188
|
+
const el = await mount(800);
|
|
189
|
+
expect(segmentsOf(el).length).to.equal(5);
|
|
190
|
+
});
|
|
191
|
+
it('30. legend part', async () => {
|
|
192
|
+
const el = await mount(800);
|
|
193
|
+
expect(el.shadowRoot.querySelector('[part="legend"]')).to.exist;
|
|
194
|
+
});
|
|
195
|
+
it('31. legend-item parts', async () => {
|
|
196
|
+
const el = await mount(800);
|
|
197
|
+
expect(legendItemsOf(el).length).to.equal(5);
|
|
198
|
+
});
|
|
199
|
+
it('32. legend-swatch part', async () => {
|
|
200
|
+
const el = await mount(800);
|
|
201
|
+
expect(el.shadowRoot.querySelectorAll('[part="legend-swatch"]').length).to.equal(5);
|
|
202
|
+
});
|
|
203
|
+
it('33. legend-label part text', async () => {
|
|
204
|
+
const el = await mount(800);
|
|
205
|
+
const labels = Array.from(el.shadowRoot.querySelectorAll('[part="legend-label"]')).map(node => node.textContent.trim());
|
|
206
|
+
expect(labels).to.deep.equal([
|
|
207
|
+
'Success',
|
|
208
|
+
'Failure',
|
|
209
|
+
'Errors',
|
|
210
|
+
'Running',
|
|
211
|
+
'Other',
|
|
212
|
+
]);
|
|
213
|
+
});
|
|
214
|
+
it('34. legend-value part text', async () => {
|
|
215
|
+
const el = await mount(800);
|
|
216
|
+
const values = Array.from(el.shadowRoot.querySelectorAll('[part="legend-value"]')).map(node => node.textContent.trim());
|
|
217
|
+
expect(values).to.deep.equal([
|
|
218
|
+
'45623',
|
|
219
|
+
'50342',
|
|
220
|
+
'682',
|
|
221
|
+
'166',
|
|
222
|
+
'1552',
|
|
223
|
+
]);
|
|
224
|
+
});
|
|
225
|
+
/* #endregion */
|
|
226
|
+
/* #region Classes and sizes */
|
|
227
|
+
it('35. small size class', async () => {
|
|
228
|
+
const el = await fixture(html `<nile-segmented-progress-bar
|
|
229
|
+
size="small"
|
|
230
|
+
></nile-segmented-progress-bar>`);
|
|
231
|
+
expect(el.shadowRoot.querySelector('.segmented-progress-bar--small')).to
|
|
232
|
+
.exist;
|
|
233
|
+
});
|
|
234
|
+
it('36. medium size class by default', async () => {
|
|
235
|
+
const el = await fixture(html `<nile-segmented-progress-bar></nile-segmented-progress-bar>`);
|
|
236
|
+
expect(el.shadowRoot.querySelector('.segmented-progress-bar--medium')).to
|
|
237
|
+
.exist;
|
|
238
|
+
});
|
|
239
|
+
it('37. large size class', async () => {
|
|
240
|
+
const el = await fixture(html `<nile-segmented-progress-bar
|
|
241
|
+
size="large"
|
|
242
|
+
></nile-segmented-progress-bar>`);
|
|
243
|
+
expect(el.shadowRoot.querySelector('.segmented-progress-bar--large')).to
|
|
244
|
+
.exist;
|
|
245
|
+
});
|
|
246
|
+
it('38. rounded class present by default', async () => {
|
|
247
|
+
const el = await fixture(html `<nile-segmented-progress-bar></nile-segmented-progress-bar>`);
|
|
248
|
+
expect(el.shadowRoot.querySelector('.segmented-progress-bar--rounded')).to
|
|
249
|
+
.exist;
|
|
250
|
+
});
|
|
251
|
+
it('39. rounded class removed when rounded is false', async () => {
|
|
252
|
+
const el = await fixture(html `<nile-segmented-progress-bar
|
|
253
|
+
.rounded=${false}
|
|
254
|
+
></nile-segmented-progress-bar>`);
|
|
255
|
+
expect(el.shadowRoot.querySelector('.segmented-progress-bar--rounded')).to
|
|
256
|
+
.be.null;
|
|
257
|
+
});
|
|
258
|
+
it('40. palette class rotates by index', async () => {
|
|
259
|
+
const el = await mount(800);
|
|
260
|
+
const classes = segmentsOf(el).map(segment => Array.from(segment.classList).find(name => name.includes('palette')));
|
|
261
|
+
expect(classes).to.deep.equal([
|
|
262
|
+
'segmented-progress-bar__palette-0',
|
|
263
|
+
'segmented-progress-bar__palette-1',
|
|
264
|
+
'segmented-progress-bar__palette-2',
|
|
265
|
+
'segmented-progress-bar__palette-3',
|
|
266
|
+
'segmented-progress-bar__palette-4',
|
|
267
|
+
]);
|
|
268
|
+
});
|
|
269
|
+
it('41. palette wraps after 10 segments', async () => {
|
|
270
|
+
const many = Array.from({ length: 11 }, (_, index) => ({
|
|
271
|
+
label: `S${index}`,
|
|
272
|
+
value: 10,
|
|
273
|
+
}));
|
|
274
|
+
const el = await mount(800, many);
|
|
275
|
+
expect(segmentsOf(el)[10].classList.contains('segmented-progress-bar__palette-0')).to.be.true;
|
|
276
|
+
});
|
|
277
|
+
it('42. custom color suppresses the palette class', async () => {
|
|
278
|
+
const el = await mount(800, [
|
|
279
|
+
{ label: 'A', value: 1, color: 'rgb(255, 0, 0)' },
|
|
280
|
+
]);
|
|
281
|
+
const segment = segmentsOf(el)[0];
|
|
282
|
+
expect(segment.className).to.not.contain('palette');
|
|
283
|
+
expect(segment.style.backgroundColor).to.equal('rgb(255, 0, 0)');
|
|
284
|
+
});
|
|
285
|
+
it('43. no custom color leaves inline background unset', async () => {
|
|
286
|
+
const el = await mount(800);
|
|
287
|
+
expect(segmentsOf(el)[0].style.backgroundColor).to.equal('');
|
|
288
|
+
});
|
|
289
|
+
it('44. custom color is applied to the legend swatch', async () => {
|
|
290
|
+
const el = await mount(800, [
|
|
291
|
+
{ label: 'A', value: 1, color: 'rgb(0, 128, 0)' },
|
|
292
|
+
]);
|
|
293
|
+
const swatch = el.shadowRoot.querySelector('[part="legend-swatch"]');
|
|
294
|
+
expect(swatch.style.backgroundColor).to.equal('rgb(0, 128, 0)');
|
|
295
|
+
});
|
|
296
|
+
/* #endregion */
|
|
297
|
+
/* #region Proportional math */
|
|
298
|
+
it('45. reference data flex-basis percentages', async () => {
|
|
299
|
+
const el = await mount(800);
|
|
300
|
+
const bases = segmentsOf(el).map(segment => parseFloat(segment.style.flexBasis));
|
|
301
|
+
RUN_OUTCOMES.forEach((segment, index) => {
|
|
302
|
+
expect(bases[index]).to.be.closeTo((segment.value / SUM) * 100, 0.01);
|
|
303
|
+
});
|
|
304
|
+
});
|
|
305
|
+
it('46. reference data flex-basis sums to 100%', async () => {
|
|
306
|
+
const el = await mount(800);
|
|
307
|
+
const total = segmentsOf(el).reduce((sum, segment) => sum + parseFloat(segment.style.flexBasis), 0);
|
|
308
|
+
expect(total).to.be.closeTo(100, 0.01);
|
|
309
|
+
});
|
|
310
|
+
it('47. omitted total means the segments fill 100%', async () => {
|
|
311
|
+
const el = await mount(800, [
|
|
312
|
+
{ label: 'A', value: 30 },
|
|
313
|
+
{ label: 'B', value: 70 },
|
|
314
|
+
]);
|
|
315
|
+
const bases = segmentsOf(el).map(segment => parseFloat(segment.style.flexBasis));
|
|
316
|
+
expect(bases).to.deep.equal([30, 70]);
|
|
317
|
+
});
|
|
318
|
+
it('48. total larger than the sum leaves an unfilled remainder', async () => {
|
|
319
|
+
const el = await fixture(html `<nile-segmented-progress-bar
|
|
320
|
+
.segments=${[
|
|
321
|
+
{ label: 'A', value: 30 },
|
|
322
|
+
{ label: 'B', value: 70 },
|
|
323
|
+
]}
|
|
324
|
+
.total=${200}
|
|
325
|
+
></nile-segmented-progress-bar>`);
|
|
326
|
+
const bases = segmentsOf(el).map(segment => parseFloat(segment.style.flexBasis));
|
|
327
|
+
expect(bases).to.deep.equal([15, 35]);
|
|
328
|
+
});
|
|
329
|
+
it('49. total smaller than the sum falls back to the sum', async () => {
|
|
330
|
+
let el;
|
|
331
|
+
const errors = await captureErrors(async () => {
|
|
332
|
+
el = await fixture(html `<nile-segmented-progress-bar
|
|
333
|
+
.segments=${[
|
|
334
|
+
{ label: 'A', value: 30 },
|
|
335
|
+
{ label: 'B', value: 70 },
|
|
336
|
+
]}
|
|
337
|
+
.total=${50}
|
|
338
|
+
></nile-segmented-progress-bar>`);
|
|
339
|
+
});
|
|
340
|
+
const bases = segmentsOf(el).map(segment => parseFloat(segment.style.flexBasis));
|
|
341
|
+
expect(bases).to.deep.equal([30, 70]);
|
|
342
|
+
expect(errors.join('\n')).to.contain('Invalid total (50)');
|
|
343
|
+
});
|
|
344
|
+
it('50. total equal to the sum fills 100%', async () => {
|
|
345
|
+
const el = await fixture(html `<nile-segmented-progress-bar
|
|
346
|
+
.segments=${[{ label: 'A', value: 40 }]}
|
|
347
|
+
.total=${40}
|
|
348
|
+
></nile-segmented-progress-bar>`);
|
|
349
|
+
expect(parseFloat(segmentsOf(el)[0].style.flexBasis)).to.equal(100);
|
|
350
|
+
});
|
|
351
|
+
it('51. only the largest segment may shrink', async () => {
|
|
352
|
+
const el = await mount(800);
|
|
353
|
+
const shrinks = segmentsOf(el).map(segment => segment.style.flexShrink);
|
|
354
|
+
expect(shrinks).to.deep.equal(['0', '1', '0', '0', '0']);
|
|
355
|
+
});
|
|
356
|
+
it('52. the largest segment has no min-width floor', async () => {
|
|
357
|
+
const el = await mount(800);
|
|
358
|
+
const minWidths = segmentsOf(el).map(segment => segment.style.minWidth);
|
|
359
|
+
expect(minWidths).to.deep.equal(['2px', '0px', '2px', '2px', '2px']);
|
|
360
|
+
});
|
|
361
|
+
it('53. minSegmentWidth is honoured on non-largest segments', async () => {
|
|
362
|
+
const el = await mount(800, RUN_OUTCOMES, 8);
|
|
363
|
+
const minWidths = segmentsOf(el).map(segment => segment.style.minWidth);
|
|
364
|
+
expect(minWidths).to.deep.equal(['8px', '0px', '8px', '8px', '8px']);
|
|
365
|
+
});
|
|
366
|
+
it('54. minSegmentWidth of 0 removes the floor', async () => {
|
|
367
|
+
const el = await mount(800, RUN_OUTCOMES, 0);
|
|
368
|
+
const minWidths = segmentsOf(el).map(segment => segment.style.minWidth);
|
|
369
|
+
expect(minWidths).to.deep.equal(['0px', '0px', '0px', '0px', '0px']);
|
|
370
|
+
});
|
|
371
|
+
it('55. negative minSegmentWidth is clamped to 0', async () => {
|
|
372
|
+
const el = await mount(800, RUN_OUTCOMES, -10);
|
|
373
|
+
expect(segmentsOf(el)[0].style.minWidth).to.equal('0px');
|
|
374
|
+
});
|
|
375
|
+
it('56. a single segment fills the whole bar', async () => {
|
|
376
|
+
const el = await mount(800, [{ label: 'Only', value: 5 }]);
|
|
377
|
+
const segments = segmentsOf(el);
|
|
378
|
+
expect(segments.length).to.equal(1);
|
|
379
|
+
expect(parseFloat(segments[0].style.flexBasis)).to.equal(100);
|
|
380
|
+
expect(segments[0].style.flexShrink).to.equal('1');
|
|
381
|
+
});
|
|
382
|
+
/* #endregion */
|
|
383
|
+
/* #region Layout — no overflow, no wrap */
|
|
384
|
+
[800, 360, 240].forEach((width, offset) => {
|
|
385
|
+
it(`${57 + offset}. track does not overflow at ${width}px`, async () => {
|
|
386
|
+
const el = await mount(width);
|
|
387
|
+
const track = trackOf(el);
|
|
388
|
+
expect(track.scrollWidth).to.equal(track.clientWidth);
|
|
389
|
+
});
|
|
390
|
+
});
|
|
391
|
+
it('60. tiny segments still add up to the full track width', async () => {
|
|
392
|
+
const el = await mount(360);
|
|
393
|
+
const track = trackOf(el);
|
|
394
|
+
const rendered = segmentsOf(el).reduce((sum, segment) => sum + segment.getBoundingClientRect().width, 0);
|
|
395
|
+
expect(rendered).to.be.closeTo(track.getBoundingClientRect().width, 0.5);
|
|
396
|
+
});
|
|
397
|
+
it('61. segments never wrap onto a second row', async () => {
|
|
398
|
+
const el = await mount(240);
|
|
399
|
+
const tops = segmentsOf(el).map(segment => Math.round(segment.getBoundingClientRect().top * 10) / 10);
|
|
400
|
+
expect(new Set(tops).size).to.equal(1);
|
|
401
|
+
});
|
|
402
|
+
it('62. the first segment starts at the track edge and the last ends at it', async () => {
|
|
403
|
+
const el = await mount(800);
|
|
404
|
+
const track = trackOf(el).getBoundingClientRect();
|
|
405
|
+
const segments = segmentsOf(el);
|
|
406
|
+
const first = segments[0].getBoundingClientRect();
|
|
407
|
+
const last = segments[segments.length - 1].getBoundingClientRect();
|
|
408
|
+
expect(first.left).to.be.closeTo(track.left, 0.5);
|
|
409
|
+
expect(last.right).to.be.closeTo(track.right, 0.5);
|
|
410
|
+
});
|
|
411
|
+
it('63. sub-1% segments stay at least minSegmentWidth wide', async () => {
|
|
412
|
+
const el = await mount(240);
|
|
413
|
+
const running = segmentsOf(el)[3].getBoundingClientRect().width;
|
|
414
|
+
expect(running).to.be.at.least(2);
|
|
415
|
+
});
|
|
416
|
+
it('64. a large minSegmentWidth still does not overflow', async () => {
|
|
417
|
+
const el = await mount(240, RUN_OUTCOMES, 12);
|
|
418
|
+
const track = trackOf(el);
|
|
419
|
+
expect(track.scrollWidth).to.equal(track.clientWidth);
|
|
420
|
+
});
|
|
421
|
+
it('65. many tiny segments against one dominant one do not overflow', async () => {
|
|
422
|
+
const el = await mount(240, [
|
|
423
|
+
{ label: 'Dominant', value: 99000 },
|
|
424
|
+
{ label: 'A', value: 40 },
|
|
425
|
+
{ label: 'B', value: 25 },
|
|
426
|
+
{ label: 'C', value: 12 },
|
|
427
|
+
{ label: 'D', value: 8 },
|
|
428
|
+
{ label: 'E', value: 3 },
|
|
429
|
+
{ label: 'F', value: 1 },
|
|
430
|
+
]);
|
|
431
|
+
const track = trackOf(el);
|
|
432
|
+
expect(track.scrollWidth).to.equal(track.clientWidth);
|
|
433
|
+
});
|
|
434
|
+
/* #endregion */
|
|
435
|
+
/* #region Zero, empty and invalid data */
|
|
436
|
+
it('66. zero-value segments render no bar segment', async () => {
|
|
437
|
+
const el = await mount(800, [
|
|
438
|
+
{ label: 'Success', value: 512 },
|
|
439
|
+
{ label: 'Failure', value: 0 },
|
|
440
|
+
{ label: 'Errors', value: 0 },
|
|
441
|
+
{ label: 'Running', value: 64 },
|
|
442
|
+
]);
|
|
443
|
+
expect(segmentsOf(el).length).to.equal(2);
|
|
444
|
+
});
|
|
445
|
+
it('67. zero-value segments still appear in the legend', async () => {
|
|
446
|
+
const el = await mount(800, [
|
|
447
|
+
{ label: 'Success', value: 512 },
|
|
448
|
+
{ label: 'Failure', value: 0 },
|
|
449
|
+
{ label: 'Errors', value: 0 },
|
|
450
|
+
{ label: 'Running', value: 64 },
|
|
451
|
+
]);
|
|
452
|
+
expect(legendItemsOf(el).length).to.equal(4);
|
|
453
|
+
});
|
|
454
|
+
it('68. an empty segments array renders without throwing', async () => {
|
|
455
|
+
const el = await mount(800, []);
|
|
456
|
+
expect(segmentsOf(el).length).to.equal(0);
|
|
457
|
+
expect(legendItemsOf(el).length).to.equal(0);
|
|
458
|
+
expect(trackOf(el)).to.exist;
|
|
459
|
+
});
|
|
460
|
+
it('69. an all-zero array renders an empty track and a full legend', async () => {
|
|
461
|
+
const el = await mount(800, [
|
|
462
|
+
{ label: 'A', value: 0 },
|
|
463
|
+
{ label: 'B', value: 0 },
|
|
464
|
+
{ label: 'C', value: 0 },
|
|
465
|
+
]);
|
|
466
|
+
expect(segmentsOf(el).length).to.equal(0);
|
|
467
|
+
expect(legendItemsOf(el).length).to.equal(3);
|
|
468
|
+
});
|
|
469
|
+
it('70. negative values are clamped to 0', async () => {
|
|
470
|
+
let el;
|
|
471
|
+
const errors = await captureErrors(async () => {
|
|
472
|
+
el = await mount(800, [
|
|
473
|
+
{ label: 'A', value: -5 },
|
|
474
|
+
{ label: 'B', value: 10 },
|
|
475
|
+
]);
|
|
476
|
+
});
|
|
477
|
+
expect(segmentsOf(el).length).to.equal(1);
|
|
478
|
+
expect(parseFloat(segmentsOf(el)[0].style.flexBasis)).to.equal(100);
|
|
479
|
+
expect(errors.join('\n')).to.contain('Invalid value (-5) detected at segment index 0');
|
|
480
|
+
});
|
|
481
|
+
it('71. NaN values are guarded to 0', async () => {
|
|
482
|
+
let el;
|
|
483
|
+
const errors = await captureErrors(async () => {
|
|
484
|
+
el = await mount(800, [
|
|
485
|
+
{ label: 'A', value: Number.NaN },
|
|
486
|
+
{ label: 'B', value: 10 },
|
|
487
|
+
]);
|
|
488
|
+
});
|
|
489
|
+
expect(segmentsOf(el).length).to.equal(1);
|
|
490
|
+
expect(errors.join('\n')).to.contain('Invalid value (NaN) detected at segment index 0');
|
|
491
|
+
});
|
|
492
|
+
it('72. clamped values still render as 0 in the legend', async () => {
|
|
493
|
+
let el;
|
|
494
|
+
await captureErrors(async () => {
|
|
495
|
+
el = await mount(800, [
|
|
496
|
+
{ label: 'A', value: -5 },
|
|
497
|
+
{ label: 'B', value: 10 },
|
|
498
|
+
]);
|
|
499
|
+
});
|
|
500
|
+
const values = Array.from(el.shadowRoot.querySelectorAll('[part="legend-value"]')).map(node => node.textContent.trim());
|
|
501
|
+
expect(values).to.deep.equal(['0', '10']);
|
|
502
|
+
});
|
|
503
|
+
it('73. Infinity is guarded to 0', async () => {
|
|
504
|
+
let el;
|
|
505
|
+
const errors = await captureErrors(async () => {
|
|
506
|
+
el = await mount(800, [
|
|
507
|
+
{ label: 'A', value: Number.POSITIVE_INFINITY },
|
|
508
|
+
{ label: 'B', value: 10 },
|
|
509
|
+
]);
|
|
510
|
+
});
|
|
511
|
+
expect(segmentsOf(el).length).to.equal(1);
|
|
512
|
+
expect(errors.join('\n')).to.contain('Invalid value (Infinity)');
|
|
513
|
+
});
|
|
514
|
+
it('74. a negative total is reported and ignored', async () => {
|
|
515
|
+
let el;
|
|
516
|
+
const errors = await captureErrors(async () => {
|
|
517
|
+
el = await fixture(html `<nile-segmented-progress-bar
|
|
518
|
+
.segments=${[{ label: 'A', value: 10 }]}
|
|
519
|
+
.total=${-1}
|
|
520
|
+
></nile-segmented-progress-bar>`);
|
|
521
|
+
});
|
|
522
|
+
expect(parseFloat(segmentsOf(el)[0].style.flexBasis)).to.equal(100);
|
|
523
|
+
expect(errors.join('\n')).to.contain('Invalid total (-1)');
|
|
524
|
+
});
|
|
525
|
+
it('75. a valid config logs nothing', async () => {
|
|
526
|
+
const errors = await captureErrors(async () => {
|
|
527
|
+
await mount(800);
|
|
528
|
+
});
|
|
529
|
+
expect(errors).to.deep.equal([]);
|
|
530
|
+
});
|
|
531
|
+
/* #endregion */
|
|
532
|
+
/* #region Percentage formatting */
|
|
533
|
+
it('76. percentages render to one decimal', async () => {
|
|
534
|
+
const el = await mount(800);
|
|
535
|
+
expect(segmentsOf(el)[0].getAttribute('aria-label')).to.equal('Success: 45623 (46.4%)');
|
|
536
|
+
});
|
|
537
|
+
it('77. sub-1% percentages keep their decimal', async () => {
|
|
538
|
+
const el = await mount(800);
|
|
539
|
+
expect(segmentsOf(el)[3].getAttribute('aria-label')).to.equal('Running: 166 (0.2%)');
|
|
540
|
+
});
|
|
541
|
+
it('78. whole percentages are trimmed', async () => {
|
|
542
|
+
const el = await mount(800, [
|
|
543
|
+
{ label: 'A', value: 1 },
|
|
544
|
+
{ label: 'B', value: 1 },
|
|
545
|
+
]);
|
|
546
|
+
expect(segmentsOf(el)[0].getAttribute('aria-label')).to.equal('A: 1 (50%)');
|
|
547
|
+
});
|
|
548
|
+
it('79. a 100% segment is trimmed to 100%', async () => {
|
|
549
|
+
const el = await mount(800, [{ label: 'A', value: 7 }]);
|
|
550
|
+
expect(segmentsOf(el)[0].getAttribute('aria-label')).to.equal('A: 7 (100%)');
|
|
551
|
+
});
|
|
552
|
+
it('80. zero segments describe themselves as 0%', async () => {
|
|
553
|
+
const el = await mount(800, [
|
|
554
|
+
{ label: 'A', value: 0 },
|
|
555
|
+
{ label: 'B', value: 10 },
|
|
556
|
+
]);
|
|
557
|
+
expect(legendItemsOf(el)[0].getAttribute('aria-label')).to.equal('A: 0 (0%)');
|
|
558
|
+
});
|
|
559
|
+
/* #endregion */
|
|
560
|
+
/* #region Accessibility */
|
|
561
|
+
it('81. the track is a group, not an img', async () => {
|
|
562
|
+
const el = await mount(800);
|
|
563
|
+
expect(trackOf(el).getAttribute('role')).to.equal('group');
|
|
564
|
+
});
|
|
565
|
+
it('82. the track carries the whole distribution as its accessible name', async () => {
|
|
566
|
+
const el = await mount(800);
|
|
567
|
+
expect(trackOf(el).getAttribute('aria-label')).to.equal('Run outcomes: Success: 45623 (46.4%), Failure: 50342 (51.2%), Errors: 682 (0.7%), Running: 166 (0.2%), Other: 1552 (1.6%)');
|
|
568
|
+
});
|
|
569
|
+
it('83. the track summary omits the prefix when there is no label', async () => {
|
|
570
|
+
const el = await fixture(html `<nile-segmented-progress-bar
|
|
571
|
+
.segments=${[{ label: 'A', value: 1 }]}
|
|
572
|
+
></nile-segmented-progress-bar>`);
|
|
573
|
+
expect(trackOf(el).getAttribute('aria-label')).to.equal('A: 1 (100%)');
|
|
574
|
+
});
|
|
575
|
+
it('84. the track summary reads "No data" when empty', async () => {
|
|
576
|
+
const el = await mount(800, []);
|
|
577
|
+
expect(trackOf(el).getAttribute('aria-label')).to.equal('Run outcomes: No data');
|
|
578
|
+
});
|
|
579
|
+
it('85. segments are focusable buttons', async () => {
|
|
580
|
+
const el = await mount(800);
|
|
581
|
+
segmentsOf(el).forEach(segment => {
|
|
582
|
+
expect(segment.getAttribute('role')).to.equal('button');
|
|
583
|
+
expect(segment.getAttribute('tabindex')).to.equal('0');
|
|
584
|
+
});
|
|
585
|
+
});
|
|
586
|
+
it('86. legend items are focusable buttons', async () => {
|
|
587
|
+
const el = await mount(800);
|
|
588
|
+
legendItemsOf(el).forEach(item => {
|
|
589
|
+
expect(item.getAttribute('role')).to.equal('button');
|
|
590
|
+
expect(item.getAttribute('tabindex')).to.equal('0');
|
|
591
|
+
});
|
|
592
|
+
});
|
|
593
|
+
it('87. legend swatches are hidden from assistive tech', async () => {
|
|
594
|
+
const el = await mount(800);
|
|
595
|
+
const swatches = el.shadowRoot.querySelectorAll('[part="legend-swatch"]');
|
|
596
|
+
Array.from(swatches).forEach(swatch => {
|
|
597
|
+
expect(swatch.getAttribute('aria-hidden')).to.equal('true');
|
|
598
|
+
});
|
|
599
|
+
});
|
|
600
|
+
it('88. segments carry their config index', async () => {
|
|
601
|
+
const el = await mount(800);
|
|
602
|
+
expect(segmentsOf(el).map(segment => segment.getAttribute('data-index'))).to.deep.equal(['0', '1', '2', '3', '4']);
|
|
603
|
+
});
|
|
604
|
+
it('89. no native buttons are rendered', async () => {
|
|
605
|
+
const el = await mount(800);
|
|
606
|
+
expect(el.shadowRoot.querySelectorAll('button').length).to.equal(0);
|
|
607
|
+
});
|
|
608
|
+
/* #endregion */
|
|
609
|
+
/* #region Tooltips */
|
|
610
|
+
it('90. every rendered segment is wrapped in a tooltip', async () => {
|
|
611
|
+
const el = await mount(800);
|
|
612
|
+
expect(tooltipsOf(el).length).to.equal(5);
|
|
613
|
+
});
|
|
614
|
+
it('91. tooltip content defaults to the segment description', async () => {
|
|
615
|
+
const el = await mount(800);
|
|
616
|
+
expect(tooltipsOf(el)[0].getAttribute('content')).to.equal('Success: 45623 (46.4%)');
|
|
617
|
+
});
|
|
618
|
+
it('92. a per-segment tooltip overrides the default', async () => {
|
|
619
|
+
const el = await mount(800, [
|
|
620
|
+
{ label: 'Documents', value: 4200, tooltip: '4.2 GB of documents' },
|
|
621
|
+
{ label: 'Media', value: 8800 },
|
|
622
|
+
]);
|
|
623
|
+
const tooltips = tooltipsOf(el);
|
|
624
|
+
expect(tooltips[0].getAttribute('content')).to.equal('4.2 GB of documents');
|
|
625
|
+
expect(tooltips[1].getAttribute('content')).to.equal('Media: 8800 (67.7%)');
|
|
626
|
+
});
|
|
627
|
+
it('93. an empty tooltip override falls back to the default', async () => {
|
|
628
|
+
const el = await mount(800, [{ label: 'A', value: 1, tooltip: '' }]);
|
|
629
|
+
expect(tooltipsOf(el)[0].getAttribute('content')).to.equal('A: 1 (100%)');
|
|
630
|
+
});
|
|
631
|
+
it('94. tooltipPlacement is forwarded', async () => {
|
|
632
|
+
const el = await fixture(html `<nile-segmented-progress-bar
|
|
633
|
+
tooltip-placement="bottom"
|
|
634
|
+
.segments=${[{ label: 'A', value: 1 }]}
|
|
635
|
+
></nile-segmented-progress-bar>`);
|
|
636
|
+
expect(tooltipsOf(el)[0].getAttribute('placement')).to.equal('bottom');
|
|
637
|
+
});
|
|
638
|
+
it('95. showTooltip false creates no tooltip wrappers at all', async () => {
|
|
639
|
+
const el = await fixture(html `<nile-segmented-progress-bar
|
|
640
|
+
.segments=${RUN_OUTCOMES}
|
|
641
|
+
.showTooltip=${false}
|
|
642
|
+
></nile-segmented-progress-bar>`);
|
|
643
|
+
expect(tooltipsOf(el).length).to.equal(0);
|
|
644
|
+
expect(segmentsOf(el).length).to.equal(5);
|
|
645
|
+
});
|
|
646
|
+
it('96. tooltips are display:contents so they never become flex items', async () => {
|
|
647
|
+
const el = await mount(800);
|
|
648
|
+
const tooltip = tooltipsOf(el)[0];
|
|
649
|
+
expect(getComputedStyle(tooltip).display).to.equal('contents');
|
|
650
|
+
});
|
|
651
|
+
it('97. the tooltip wrapper is the segment parent inside the track', async () => {
|
|
652
|
+
const el = await mount(800);
|
|
653
|
+
const segment = segmentsOf(el)[0];
|
|
654
|
+
expect(segment.parentElement.tagName.toLowerCase()).to.equal('nile-lite-tooltip');
|
|
655
|
+
expect(segment.parentElement.parentElement).to.equal(trackOf(el));
|
|
656
|
+
});
|
|
657
|
+
it('98. the tooltip layer does not break the 100% layout', async () => {
|
|
658
|
+
const el = await mount(360);
|
|
659
|
+
const track = trackOf(el);
|
|
660
|
+
expect(track.scrollWidth).to.equal(track.clientWidth);
|
|
661
|
+
const rendered = segmentsOf(el).reduce((sum, segment) => sum + segment.getBoundingClientRect().width, 0);
|
|
662
|
+
expect(rendered).to.be.closeTo(track.getBoundingClientRect().width, 0.5);
|
|
663
|
+
});
|
|
664
|
+
/* #endregion */
|
|
665
|
+
/* #region Events */
|
|
666
|
+
it('99. clicking a segment emits nile-segment-click once', async () => {
|
|
667
|
+
const el = await mount(800);
|
|
668
|
+
const events = [];
|
|
669
|
+
el.addEventListener('nile-segment-click', (event) => {
|
|
670
|
+
events.push(event);
|
|
671
|
+
});
|
|
672
|
+
segmentsOf(el)[2].click();
|
|
673
|
+
expect(events.length).to.equal(1);
|
|
674
|
+
});
|
|
675
|
+
it('100. the click payload carries segment, index and raw percentage', async () => {
|
|
676
|
+
const el = await mount(800);
|
|
677
|
+
const details = [];
|
|
678
|
+
el.addEventListener('nile-segment-click', (event) => {
|
|
679
|
+
details.push(event.detail);
|
|
680
|
+
});
|
|
681
|
+
segmentsOf(el)[0].click();
|
|
682
|
+
expect(details[0].segment).to.equal(el.segments[0]);
|
|
683
|
+
expect(details[0].index).to.equal(0);
|
|
684
|
+
expect(details[0].percentage).to.equal((45623 / SUM) * 100);
|
|
685
|
+
});
|
|
686
|
+
it('101. the percentage in the payload is unrounded', async () => {
|
|
687
|
+
const el = await mount(800);
|
|
688
|
+
let percentage = 0;
|
|
689
|
+
el.addEventListener('nile-segment-click', (event) => {
|
|
690
|
+
percentage = event.detail.percentage;
|
|
691
|
+
});
|
|
692
|
+
segmentsOf(el)[3].click();
|
|
693
|
+
expect(percentage).to.equal((166 / SUM) * 100);
|
|
694
|
+
expect(percentage).to.not.equal(0.2);
|
|
695
|
+
});
|
|
696
|
+
it('102. clicking a legend item emits the same payload', async () => {
|
|
697
|
+
const el = await mount(800);
|
|
698
|
+
const details = [];
|
|
699
|
+
el.addEventListener('nile-segment-click', (event) => {
|
|
700
|
+
details.push(event.detail);
|
|
701
|
+
});
|
|
702
|
+
legendItemsOf(el)[4].click();
|
|
703
|
+
expect(details.length).to.equal(1);
|
|
704
|
+
expect(details[0].index).to.equal(4);
|
|
705
|
+
expect(details[0].percentage).to.equal((1552 / SUM) * 100);
|
|
706
|
+
});
|
|
707
|
+
it('103. clicking a zero-value legend item still emits', async () => {
|
|
708
|
+
const el = await mount(800, [
|
|
709
|
+
{ label: 'A', value: 0 },
|
|
710
|
+
{ label: 'B', value: 10 },
|
|
711
|
+
]);
|
|
712
|
+
const details = [];
|
|
713
|
+
el.addEventListener('nile-segment-click', (event) => {
|
|
714
|
+
details.push(event.detail);
|
|
715
|
+
});
|
|
716
|
+
legendItemsOf(el)[0].click();
|
|
717
|
+
expect(details[0].index).to.equal(0);
|
|
718
|
+
expect(details[0].percentage).to.equal(0);
|
|
719
|
+
});
|
|
720
|
+
it('104. Enter on a segment emits exactly once', async () => {
|
|
721
|
+
const el = await mount(800);
|
|
722
|
+
const events = [];
|
|
723
|
+
el.addEventListener('nile-segment-click', (event) => {
|
|
724
|
+
events.push(event);
|
|
725
|
+
});
|
|
726
|
+
segmentsOf(el)[1].dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true }));
|
|
727
|
+
expect(events.length).to.equal(1);
|
|
728
|
+
expect(events[0].detail.index).to.equal(1);
|
|
729
|
+
});
|
|
730
|
+
it('105. Space on a segment emits exactly once', async () => {
|
|
731
|
+
const el = await mount(800);
|
|
732
|
+
const events = [];
|
|
733
|
+
el.addEventListener('nile-segment-click', (event) => {
|
|
734
|
+
events.push(event);
|
|
735
|
+
});
|
|
736
|
+
segmentsOf(el)[1].dispatchEvent(new KeyboardEvent('keydown', { key: ' ', bubbles: true }));
|
|
737
|
+
expect(events.length).to.equal(1);
|
|
738
|
+
});
|
|
739
|
+
it('106. legacy Spacebar key also activates', async () => {
|
|
740
|
+
const el = await mount(800);
|
|
741
|
+
const events = [];
|
|
742
|
+
el.addEventListener('nile-segment-click', (event) => {
|
|
743
|
+
events.push(event);
|
|
744
|
+
});
|
|
745
|
+
segmentsOf(el)[0].dispatchEvent(new KeyboardEvent('keydown', { key: 'Spacebar', bubbles: true }));
|
|
746
|
+
expect(events.length).to.equal(1);
|
|
747
|
+
});
|
|
748
|
+
it('107. Enter on a legend item emits once', async () => {
|
|
749
|
+
const el = await mount(800);
|
|
750
|
+
const events = [];
|
|
751
|
+
el.addEventListener('nile-segment-click', (event) => {
|
|
752
|
+
events.push(event);
|
|
753
|
+
});
|
|
754
|
+
legendItemsOf(el)[2].dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true }));
|
|
755
|
+
expect(events.length).to.equal(1);
|
|
756
|
+
expect(events[0].detail.index).to.equal(2);
|
|
757
|
+
});
|
|
758
|
+
it('108. Space is prevented so the page does not scroll', async () => {
|
|
759
|
+
const el = await mount(800);
|
|
760
|
+
const event = new KeyboardEvent('keydown', {
|
|
761
|
+
key: ' ',
|
|
762
|
+
bubbles: true,
|
|
763
|
+
cancelable: true,
|
|
764
|
+
});
|
|
765
|
+
segmentsOf(el)[0].dispatchEvent(event);
|
|
766
|
+
expect(event.defaultPrevented).to.be.true;
|
|
767
|
+
});
|
|
768
|
+
it('109. other keys do not activate a segment', async () => {
|
|
769
|
+
const el = await mount(800);
|
|
770
|
+
const events = [];
|
|
771
|
+
el.addEventListener('nile-segment-click', (event) => {
|
|
772
|
+
events.push(event);
|
|
773
|
+
});
|
|
774
|
+
['a', 'Tab', 'Escape', 'ArrowRight'].forEach(key => {
|
|
775
|
+
segmentsOf(el)[0].dispatchEvent(new KeyboardEvent('keydown', { key, bubbles: true, cancelable: true }));
|
|
776
|
+
});
|
|
777
|
+
expect(events.length).to.equal(0);
|
|
778
|
+
});
|
|
779
|
+
it('110. the event bubbles and is composed', async () => {
|
|
780
|
+
const el = await mount(800);
|
|
781
|
+
const received = [];
|
|
782
|
+
const listener = (event) => received.push(event);
|
|
783
|
+
document.addEventListener('nile-segment-click', listener);
|
|
784
|
+
try {
|
|
785
|
+
segmentsOf(el)[0].click();
|
|
786
|
+
}
|
|
787
|
+
finally {
|
|
788
|
+
document.removeEventListener('nile-segment-click', listener);
|
|
789
|
+
}
|
|
790
|
+
expect(received.length).to.equal(1);
|
|
791
|
+
expect(received[0].bubbles).to.be.true;
|
|
792
|
+
expect(received[0].composed).to.be.true;
|
|
793
|
+
});
|
|
794
|
+
/* #endregion */
|
|
795
|
+
/* #region Toggles */
|
|
796
|
+
it('111. showLegend false removes the legend', async () => {
|
|
797
|
+
const el = await fixture(html `<nile-segmented-progress-bar
|
|
798
|
+
.segments=${RUN_OUTCOMES}
|
|
799
|
+
.showLegend=${false}
|
|
800
|
+
></nile-segmented-progress-bar>`);
|
|
801
|
+
expect(el.shadowRoot.querySelector('[part="legend"]')).to.be.null;
|
|
802
|
+
expect(segmentsOf(el).length).to.equal(5);
|
|
803
|
+
});
|
|
804
|
+
it('112. showValues false removes only the legend values', async () => {
|
|
805
|
+
const el = await fixture(html `<nile-segmented-progress-bar
|
|
806
|
+
.segments=${RUN_OUTCOMES}
|
|
807
|
+
.showValues=${false}
|
|
808
|
+
></nile-segmented-progress-bar>`);
|
|
809
|
+
expect(el.shadowRoot.querySelectorAll('[part="legend-value"]').length).to
|
|
810
|
+
.equal(0);
|
|
811
|
+
expect(el.shadowRoot.querySelectorAll('[part="legend-label"]').length).to
|
|
812
|
+
.equal(5);
|
|
813
|
+
expect(legendItemsOf(el).length).to.equal(5);
|
|
814
|
+
});
|
|
815
|
+
/* #endregion */
|
|
816
|
+
/* #region Dynamic updates */
|
|
817
|
+
it('113. updating segments re-renders the bar', async () => {
|
|
818
|
+
const el = await mount(800, [{ label: 'A', value: 1 }]);
|
|
819
|
+
expect(segmentsOf(el).length).to.equal(1);
|
|
820
|
+
el.segments = RUN_OUTCOMES;
|
|
821
|
+
await el.updateComplete;
|
|
822
|
+
expect(segmentsOf(el).length).to.equal(5);
|
|
823
|
+
expect(parseFloat(segmentsOf(el)[0].style.flexBasis)).to.be.closeTo(46.381, 0.01);
|
|
824
|
+
});
|
|
825
|
+
it('114. updating total re-renders the proportions', async () => {
|
|
826
|
+
const el = await mount(800, [{ label: 'A', value: 50 }]);
|
|
827
|
+
expect(parseFloat(segmentsOf(el)[0].style.flexBasis)).to.equal(100);
|
|
828
|
+
el.total = 200;
|
|
829
|
+
await el.updateComplete;
|
|
830
|
+
expect(parseFloat(segmentsOf(el)[0].style.flexBasis)).to.equal(25);
|
|
831
|
+
});
|
|
832
|
+
it('115. updating size swaps the size class', async () => {
|
|
833
|
+
const el = await mount(800);
|
|
834
|
+
el.size = 'large';
|
|
835
|
+
await el.updateComplete;
|
|
836
|
+
expect(el.shadowRoot.querySelector('.segmented-progress-bar--large')).to
|
|
837
|
+
.exist;
|
|
838
|
+
expect(el.shadowRoot.querySelector('.segmented-progress-bar--medium')).to
|
|
839
|
+
.be.null;
|
|
840
|
+
});
|
|
841
|
+
it('116. updating label adds the label part', async () => {
|
|
842
|
+
const el = await fixture(html `<nile-segmented-progress-bar></nile-segmented-progress-bar>`);
|
|
843
|
+
expect(el.shadowRoot.querySelector('[part="label"]')).to.be.null;
|
|
844
|
+
el.label = 'Later';
|
|
845
|
+
await el.updateComplete;
|
|
846
|
+
expect(el.shadowRoot.querySelector('[part="label"]').textContent.trim()).to.equal('Later');
|
|
847
|
+
});
|
|
848
|
+
it('117. toggling showLegend at runtime adds and removes the legend', async () => {
|
|
849
|
+
const el = await mount(800);
|
|
850
|
+
expect(el.shadowRoot.querySelector('[part="legend"]')).to.exist;
|
|
851
|
+
el.showLegend = false;
|
|
852
|
+
await el.updateComplete;
|
|
853
|
+
expect(el.shadowRoot.querySelector('[part="legend"]')).to.be.null;
|
|
854
|
+
el.showLegend = true;
|
|
855
|
+
await el.updateComplete;
|
|
856
|
+
expect(el.shadowRoot.querySelector('[part="legend"]')).to.exist;
|
|
857
|
+
});
|
|
858
|
+
it('118. toggling showTooltip at runtime adds and removes the wrappers', async () => {
|
|
859
|
+
const el = await mount(800);
|
|
860
|
+
expect(tooltipsOf(el).length).to.equal(5);
|
|
861
|
+
el.showTooltip = false;
|
|
862
|
+
await el.updateComplete;
|
|
863
|
+
expect(tooltipsOf(el).length).to.equal(0);
|
|
864
|
+
el.showTooltip = true;
|
|
865
|
+
await el.updateComplete;
|
|
866
|
+
expect(tooltipsOf(el).length).to.equal(5);
|
|
867
|
+
});
|
|
868
|
+
it('119. toggling rounded at runtime swaps the class', async () => {
|
|
869
|
+
const el = await mount(800);
|
|
870
|
+
el.rounded = false;
|
|
871
|
+
await el.updateComplete;
|
|
872
|
+
expect(el.shadowRoot.querySelector('.segmented-progress-bar--rounded')).to
|
|
873
|
+
.be.null;
|
|
874
|
+
});
|
|
875
|
+
it('120. updating minSegmentWidth re-applies the floors', async () => {
|
|
876
|
+
const el = await mount(800);
|
|
877
|
+
el.minSegmentWidth = 10;
|
|
878
|
+
await el.updateComplete;
|
|
879
|
+
expect(segmentsOf(el)[2].style.minWidth).to.equal('10px');
|
|
880
|
+
const track = trackOf(el);
|
|
881
|
+
expect(track.scrollWidth).to.equal(track.clientWidth);
|
|
882
|
+
});
|
|
883
|
+
it('121. the largest index follows the data', async () => {
|
|
884
|
+
const el = await mount(800, [
|
|
885
|
+
{ label: 'A', value: 10 },
|
|
886
|
+
{ label: 'B', value: 90 },
|
|
887
|
+
]);
|
|
888
|
+
expect(segmentsOf(el).map(segment => segment.style.flexShrink)).to.deep.equal([
|
|
889
|
+
'0',
|
|
890
|
+
'1',
|
|
891
|
+
]);
|
|
892
|
+
el.segments = [
|
|
893
|
+
{ label: 'A', value: 90 },
|
|
894
|
+
{ label: 'B', value: 10 },
|
|
895
|
+
];
|
|
896
|
+
await el.updateComplete;
|
|
897
|
+
expect(segmentsOf(el).map(segment => segment.style.flexShrink)).to.deep.equal([
|
|
898
|
+
'1',
|
|
899
|
+
'0',
|
|
900
|
+
]);
|
|
901
|
+
});
|
|
902
|
+
it('122. going from data to an empty array clears the track', async () => {
|
|
903
|
+
const el = await mount(800);
|
|
904
|
+
el.segments = [];
|
|
905
|
+
await el.updateComplete;
|
|
906
|
+
expect(segmentsOf(el).length).to.equal(0);
|
|
907
|
+
expect(trackOf(el)).to.exist;
|
|
908
|
+
});
|
|
909
|
+
it('123. a non-array segments value is tolerated', async () => {
|
|
910
|
+
const el = await mount(800);
|
|
911
|
+
el.segments = null;
|
|
912
|
+
await el.updateComplete;
|
|
913
|
+
expect(segmentsOf(el).length).to.equal(0);
|
|
914
|
+
expect(trackOf(el)).to.exist;
|
|
915
|
+
});
|
|
916
|
+
/* #endregion */
|
|
917
|
+
/* #region Integration */
|
|
918
|
+
it('124. a non-array, non-nullish segments value is tolerated', async () => {
|
|
919
|
+
const el = await mount(800);
|
|
920
|
+
el.segments = {};
|
|
921
|
+
await el.updateComplete;
|
|
922
|
+
expect(segmentsOf(el).length).to.equal(0);
|
|
923
|
+
expect(trackOf(el).getAttribute('aria-label')).to.equal('Run outcomes: No data');
|
|
924
|
+
});
|
|
925
|
+
it('125. multiple instances are independent', async () => {
|
|
926
|
+
const wrapper = await fixture(html `
|
|
927
|
+
<div style="width:800px">
|
|
928
|
+
<nile-segmented-progress-bar
|
|
929
|
+
.segments=${[{ label: 'A', value: 1 }]}
|
|
930
|
+
></nile-segmented-progress-bar>
|
|
931
|
+
<nile-segmented-progress-bar
|
|
932
|
+
.segments=${RUN_OUTCOMES}
|
|
933
|
+
></nile-segmented-progress-bar>
|
|
934
|
+
</div>
|
|
935
|
+
`);
|
|
936
|
+
const bars = Array.from(wrapper.querySelectorAll('nile-segmented-progress-bar'));
|
|
937
|
+
await Promise.all(bars.map(bar => bar.updateComplete));
|
|
938
|
+
expect(segmentsOf(bars[0]).length).to.equal(1);
|
|
939
|
+
expect(segmentsOf(bars[1]).length).to.equal(5);
|
|
940
|
+
});
|
|
941
|
+
it('126. full reference configuration', async () => {
|
|
942
|
+
const el = await fixture(html `
|
|
943
|
+
<nile-segmented-progress-bar
|
|
944
|
+
label="Run outcomes"
|
|
945
|
+
size="large"
|
|
946
|
+
tooltip-placement="bottom"
|
|
947
|
+
.segments=${RUN_OUTCOMES}
|
|
948
|
+
></nile-segmented-progress-bar>
|
|
949
|
+
`);
|
|
950
|
+
expect(el.getAttribute('label')).to.equal('Run outcomes');
|
|
951
|
+
expect(el.getAttribute('size')).to.equal('large');
|
|
952
|
+
expect(segmentsOf(el).length).to.equal(5);
|
|
953
|
+
expect(legendItemsOf(el).length).to.equal(5);
|
|
954
|
+
expect(tooltipsOf(el).length).to.equal(5);
|
|
955
|
+
expect(el.shadowRoot.querySelector('.segmented-progress-bar--large')).to
|
|
956
|
+
.exist;
|
|
957
|
+
expect(trackOf(el).getAttribute('role')).to.equal('group');
|
|
958
|
+
});
|
|
959
|
+
/* #endregion */
|
|
960
|
+
});
|
|
961
|
+
//# sourceMappingURL=nile-segmented-progress-bar.test.js.map
|