Skip to content

Commit d9b13d5

Browse files
committed
refactor(progressbar): optimize the code
- refactor the base spec - update the value for screen reader in _updateARIA - move the variables form host to the base element - optimize the styles - change the indeterminate animation for the fluent theme
1 parent 26f4ae4 commit d9b13d5

17 files changed

+467
-409
lines changed
Lines changed: 156 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1,104 +1,188 @@
1-
import { elementUpdated, expect, fixture, html } from '@open-wc/testing';
2-
import { IgcProgressBaseComponent } from './base.js';
3-
4-
class TestProgressBaseComponent extends IgcProgressBaseComponent {
5-
protected override render() {
6-
return html`<div part="test">${this.renderDefaultSlot()}</div>`;
7-
}
8-
}
9-
10-
customElements.define('test-progress-base', TestProgressBaseComponent);
11-
12-
describe('IgcProgressBaseComponent', () => {
13-
let component: TestProgressBaseComponent;
1+
import {
2+
defineCE,
3+
elementUpdated,
4+
expect,
5+
fixture,
6+
html,
7+
unsafeStatic,
8+
} from '@open-wc/testing';
9+
import { LitElement, css } from 'lit';
10+
import { clamp, formatString } from '../common/util.js';
11+
12+
describe('IgcProgressDerivedComponent', () => {
13+
let tag: string;
14+
let instance: LitElement & {
15+
value: number;
16+
max: number;
17+
indeterminate: boolean;
18+
labelFormat: string;
19+
};
20+
21+
before(() => {
22+
// Define a new component that extends LitElement for testing purposes
23+
tag = defineCE(
24+
class extends LitElement {
25+
static override styles = css`
26+
[part='test'] {
27+
background-color: lightgray;
28+
}
29+
`;
30+
31+
static override get properties() {
32+
return {
33+
value: { type: Number },
34+
max: { type: Number },
35+
indeterminate: { type: Boolean },
36+
labelFormat: { type: String },
37+
};
38+
}
39+
40+
public value = 0;
41+
public max = 100;
42+
public indeterminate = false;
43+
public labelFormat = '';
44+
45+
constructor() {
46+
super();
47+
this.value = 0;
48+
this.max = 100;
49+
this.indeterminate = false;
50+
this.labelFormat = '';
51+
}
52+
53+
override updated(changedProperties: Map<string, unknown>) {
54+
if (changedProperties.has('value')) {
55+
this._clampValue();
56+
}
57+
super.updated(changedProperties);
58+
}
59+
60+
private _clampValue(): void {
61+
this.value = clamp(this.value, 0, this.max);
62+
}
63+
64+
protected renderLabelFormat() {
65+
return formatString(this.labelFormat, this.value, this.max);
66+
}
67+
68+
protected override render() {
69+
return html`
70+
<div part="test">${this.renderLabel()}</div>
71+
<slot></slot>
72+
`;
73+
}
74+
75+
protected renderLabel() {
76+
if (this.labelFormat) {
77+
return html`<span part="value">${this.renderLabelFormat()}</span>`;
78+
}
79+
return html`<span part="value">${this.value}</span>`;
80+
}
81+
}
82+
);
83+
});
1484

1585
beforeEach(async () => {
16-
component = await fixture<TestProgressBaseComponent>(
17-
html`<test-progress-base></test-progress-base>`
18-
);
86+
const tagName = unsafeStatic(tag);
87+
instance = (await fixture(
88+
html`<${tagName}></${tagName}>`
89+
)) as LitElement & {
90+
value: number;
91+
max: number;
92+
indeterminate: boolean;
93+
labelFormat: string;
94+
};
1995
});
2096

2197
describe('Shared Logic', () => {
2298
it('clamps value to max and min correctly', async () => {
23-
component.value = 200; // Exceeds max
24-
component.max = 100;
25-
await elementUpdated(component);
26-
27-
expect(component.value).to.equal(100); // Value clamped to max
28-
29-
component.value = -50; // Below min
30-
await elementUpdated(component);
31-
32-
expect(component.value).to.equal(0); // Value clamped to min
99+
const cases = [
100+
{ value: 200, max: 100, expected: 100 },
101+
{ value: -50, max: 100, expected: 0 },
102+
{ value: 75, max: 50, expected: 50 },
103+
];
104+
105+
for (const { value, max, expected } of cases) {
106+
instance.value = value;
107+
instance.max = max;
108+
await elementUpdated(instance);
109+
expect(instance.value).to.equal(expected);
110+
}
33111
});
34112

35-
it('updates value when max is reduced below current value', async () => {
36-
component.value = 75;
37-
component.max = 50;
38-
await elementUpdated(component);
113+
it('applies custom label format', async () => {
114+
instance.labelFormat = 'Step {0} of {1}';
115+
instance.value = 5;
116+
instance.max = 10;
39117

40-
expect(component.value).to.equal(50); // Adjusted to max
118+
await elementUpdated(instance);
119+
const label = instance.shadowRoot?.querySelector('[part~="value"]');
120+
expect(label?.textContent).to.equal('Step 5 of 10');
41121
});
122+
});
42123

43-
it('does not update value when max is increased', async () => {
44-
component.value = 50;
45-
component.max = 150;
46-
await elementUpdated(component);
124+
describe('Indeterminate State', () => {
125+
it('resets ARIA attributes when indeterminate', async () => {
126+
instance.indeterminate = true;
127+
await elementUpdated(instance);
47128

48-
expect(component.value).to.equal(50); // Remains unchanged
49-
});
129+
expect(instance.getAttribute('aria-valuenow')).to.be.null;
130+
expect(instance.getAttribute('aria-valuetext')).to.be.null;
50131

51-
it('does not update ARIA attributes when indeterminate is true', async () => {
52-
component.indeterminate = true;
53-
await elementUpdated(component);
132+
instance.indeterminate = false;
133+
instance.value = 30;
134+
await elementUpdated(instance);
54135

55-
expect(component.getAttribute('aria-valuenow')).to.be.null;
56-
expect(component.getAttribute('aria-valuetext')).to.be.null;
136+
expect(instance.getAttribute('aria-valuenow')).to.equal('30');
57137
});
58138
});
59139

60-
describe('Lifecycle Behavior', () => {
61-
it('correctly sets ARIA attributes on connectedCallback', async () => {
62-
const element = document.createElement('test-progress-base');
63-
document.body.appendChild(element);
64-
65-
await elementUpdated(element);
140+
describe('Fractional Progress', () => {
141+
it('sets fractional progress correctly', async () => {
142+
instance.value = 25.55;
143+
instance.max = 100;
66144

67-
expect(element.getAttribute('aria-valuenow')).to.equal('0');
68-
expect(element.getAttribute('aria-valuemax')).to.equal('100');
69-
70-
document.body.removeChild(element);
71-
});
72-
});
145+
await elementUpdated(instance);
73146

74-
describe('CSS Variables', () => {
75-
it('updates CSS variables correctly based on value and max', async () => {
76-
component.value = 50;
77-
component.max = 200;
78-
await elementUpdated(component);
147+
const base = instance.shadowRoot?.querySelector('[part="base"]');
148+
const styles = getComputedStyle(base as HTMLElement);
79149

80-
const styles = getComputedStyle(component);
81150
expect(styles.getPropertyValue('--_progress-whole').trim()).to.equal(
82-
'25'
83-
); // 50 / 200 * 100
151+
'25.55'
152+
);
84153
expect(styles.getPropertyValue('--_progress-integer').trim()).to.equal(
85154
'25'
86155
);
156+
expect(styles.getPropertyValue('--_progress-fraction').trim()).to.equal(
157+
'55'
158+
);
87159
});
88-
});
89160

90-
describe('Slots and Rendering', () => {
91-
it('renders the default slot', async () => {
92-
const slot = component.shadowRoot?.querySelector('slot');
93-
expect(slot).to.exist;
161+
it('adds "fraction" part when fraction value is present', async () => {
162+
instance.value = 25.55;
163+
instance.max = 100;
164+
165+
await elementUpdated(instance);
166+
167+
const label = instance.shadowRoot?.querySelector('[part~="value"]');
168+
expect(label?.getAttribute('part')).to.contain('fraction');
94169
});
95170

96-
it('hides the label when hideLabel is true', async () => {
97-
component.hideLabel = true;
98-
await elementUpdated(component);
171+
it('does not add "fraction" part when no fraction value', async () => {
172+
instance.value = 25;
173+
instance.max = 100;
174+
175+
await elementUpdated(instance);
99176

100-
const label = component.shadowRoot?.querySelector('[part~="label"]');
101-
expect(label).to.be.null;
177+
const label = instance.shadowRoot?.querySelector('[part~="value"]');
178+
expect(label?.getAttribute('part')).not.to.contain('fraction');
179+
});
180+
});
181+
182+
describe('Slots and Rendering', () => {
183+
it('renders slot content', async () => {
184+
const slot = instance.shadowRoot?.querySelector('slot');
185+
expect(slot).to.exist;
102186
});
103187
});
104188
});

0 commit comments

Comments
 (0)