Skip to content

Commit 8bc84fe

Browse files
committed
Stepper label provider tests
1 parent 3cc29cb commit 8bc84fe

7 files changed

Lines changed: 248 additions & 68 deletions

File tree

packages/nimble-components/src/anchor-step/template.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,18 @@ AnchorOptions
5757
<span class="current-label">${x => (x.selected ? popupIconCurrentLabel.getValueFor(x) : '')}</span>
5858
<div class="step-indicator"><slot name="step-indicator"><span aria-hidden="true">${x => x.stepInternals.position}</span></slot></div>
5959
<div class="icon-severity">
60-
${when(x => x.severity === Severity.error, html<StepPattern>`<${iconExclamationMarkTag} role="img" aria-label="${x => popupIconErrorLabel.getValueFor(x)}"></${iconExclamationMarkTag}>`)}
61-
${when(x => x.severity === Severity.warning, html<StepPattern>`<${iconTriangleFilledTag} role="img" aria-label="${x => popupIconWarningLabel.getValueFor(x)}"></${iconTriangleFilledTag}>`)}
62-
${when(x => x.severity === Severity.success, html<StepPattern>`<${iconCheckTag} role="img" aria-label="${x => popupIconCompletedLabel.getValueFor(x)}"></${iconCheckTag}>`)}
60+
${when(
61+
x => x.severity === Severity.error,
62+
html<StepPattern>`<${iconExclamationMarkTag} role="img" aria-label="${x => popupIconErrorLabel.getValueFor(x)}"></${iconExclamationMarkTag}>`
63+
)}
64+
${when(
65+
x => x.severity === Severity.warning,
66+
html<StepPattern>`<${iconTriangleFilledTag} role="img" aria-label="${x => popupIconWarningLabel.getValueFor(x)}"></${iconTriangleFilledTag}>`
67+
)}
68+
${when(
69+
x => x.severity === Severity.success,
70+
html<StepPattern>`<${iconCheckTag} role="img" aria-label="${x => popupIconCompletedLabel.getValueFor(x)}"></${iconCheckTag}>`
71+
)}
6372
</div>
6473
</div>
6574
<div class="top-spacer"></div>
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import type { AnchorStep } from '..';
2+
import { StepBasePageObject } from '../../patterns/step/testing/step-base.pageobject';
3+
4+
/**
5+
* Page object for steps
6+
*/
7+
export class AnchorStepPageObject extends StepBasePageObject<AnchorStep> {
8+
9+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { iconCheckTag } from '../../../icons/check';
2+
import { iconExclamationMarkTag } from '../../../icons/exclamation-mark';
3+
import { iconTriangleFilledTag } from '../../../icons/triangle-filled';
4+
import type { StepPattern } from '../types';
5+
6+
/**
7+
* Page object base for step and anchor step
8+
*/
9+
export abstract class StepBasePageObject<T extends StepPattern = StepPattern> {
10+
public constructor(
11+
protected readonly element: T,
12+
) {}
13+
14+
public getSuccessSeverityLabel(): string {
15+
const label = this.element.shadowRoot?.querySelector(`.icon-severity ${iconCheckTag}[aria-label]`)?.ariaLabel;
16+
if (typeof label !== 'string') {
17+
throw new Error('Success severity label not found');
18+
}
19+
return label;
20+
}
21+
22+
public getErrorSeverityLabel(): string {
23+
const label = this.element.shadowRoot?.querySelector(`.icon-severity ${iconExclamationMarkTag}[aria-label]`)?.ariaLabel;
24+
if (typeof label !== 'string') {
25+
throw new Error('Error severity label not found');
26+
}
27+
return label;
28+
}
29+
30+
public getWarningSeverityLabel(): string {
31+
const label = this.element.shadowRoot?.querySelector(`.icon-severity ${iconTriangleFilledTag}[aria-label]`)?.ariaLabel;
32+
if (typeof label !== 'string') {
33+
throw new Error('Warning severity label not found');
34+
}
35+
return label;
36+
}
37+
38+
public getSelectedStateLabel(): string {
39+
const label = this.element.shadowRoot?.querySelector('.current-label')?.textContent;
40+
if (typeof label !== 'string') {
41+
throw new Error('Selected state label not found');
42+
}
43+
return label;
44+
}
45+
}

packages/nimble-components/src/step/template.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,18 @@ ButtonOptions
6161
<span class="current-label">${x => (x.selected ? popupIconCurrentLabel.getValueFor(x) : '')}</span>
6262
<div class="step-indicator"><slot name="step-indicator"><span aria-hidden="true">${x => x.stepInternals.position}</span></slot></div>
6363
<div class="icon-severity">
64-
${when(x => x.severity === Severity.error, html<StepPattern>`<${iconExclamationMarkTag} role="img" aria-label="${x => popupIconErrorLabel.getValueFor(x)}"></${iconExclamationMarkTag}>`)}
65-
${when(x => x.severity === Severity.warning, html<StepPattern>`<${iconTriangleFilledTag} role="img" aria-label="${x => popupIconWarningLabel.getValueFor(x)}"></${iconTriangleFilledTag}>`)}
66-
${when(x => x.severity === Severity.success, html<StepPattern>`<${iconCheckTag} role="img" aria-label="${x => popupIconCompletedLabel.getValueFor(x)}"></${iconCheckTag}>`)}
64+
${when(
65+
x => x.severity === Severity.error,
66+
html<StepPattern>`<${iconExclamationMarkTag} role="img" aria-label="${x => popupIconErrorLabel.getValueFor(x)}"></${iconExclamationMarkTag}>`
67+
)}
68+
${when(
69+
x => x.severity === Severity.warning,
70+
html<StepPattern>`<${iconTriangleFilledTag} role="img" aria-label="${x => popupIconWarningLabel.getValueFor(x)}"></${iconTriangleFilledTag}>`
71+
)}
72+
${when(
73+
x => x.severity === Severity.success,
74+
html<StepPattern>`<${iconCheckTag} role="img" aria-label="${x => popupIconCompletedLabel.getValueFor(x)}"></${iconCheckTag}>`
75+
)}
6776
</div>
6877
</div>
6978
<div class="top-spacer"></div>
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import type { Step } from '..';
2+
import { StepBasePageObject } from '../../patterns/step/testing/step-base.pageobject';
3+
4+
/**
5+
* Page object for steps
6+
*/
7+
export class StepPageObject extends StepBasePageObject<Step> {
8+
9+
}

packages/nimble-components/src/stepper/styles.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,10 @@ export const styles = css`
88
:host {
99
border: none;
1010
gap: ${smallPadding};
11-
overflow-x: auto;
12-
overflow-y: hidden;
1311
}
1412
1513
:host([orientation="vertical"]) {
1614
flex-direction: column;
17-
overflow-x: hidden;
18-
overflow-y: auto;
1915
}
2016
2117
ol {

packages/nimble-components/src/stepper/tests/stepper.spec.ts

Lines changed: 161 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,16 @@
1-
import { html, observable, ref, when } from '@ni/fast-element';
1+
import { html, observable, ref } from '@ni/fast-element';
22
import { parameterizeSuite } from '@ni/jasmine-parameterized';
33
import { Stepper, stepperTag } from '..';
44
import { fixture, type Fixture } from '../../utilities/tests/fixture';
55
import { Step, stepTag } from '../../step';
6-
import { anchorStepTag } from '../../anchor-step';
6+
import { AnchorStep, anchorStepTag } from '../../anchor-step';
7+
import { themeProviderTag } from '../../theme-provider';
8+
import { LabelProviderCore, labelProviderCoreTag } from '../../label-provider/core';
9+
import { StepPageObject } from '../../step/testing/step.pageobject';
10+
import { AnchorStepPageObject } from '../../anchor-step/testing/anchor-step.pageobject';
11+
import type { StepBasePageObject } from '../../patterns/step/testing/step-base.pageobject';
12+
import { Severity } from '../../patterns/severity/types';
13+
import { waitForUpdatesAsync } from '../../testing/async-helpers';
714

815
describe('Stepper', () => {
916
it('can construct an element instance', () => {
@@ -14,89 +21,185 @@ describe('Stepper', () => {
1421
expect(document.createElement(stepperTag).orientation).toBe('horizontal');
1522
});
1623

24+
function createStepPageObject(step: Step | AnchorStep): StepBasePageObject {
25+
return step instanceof Step ? new StepPageObject(step) : new AnchorStepPageObject(step);
26+
}
27+
1728
const stepperTests = [
18-
{ name: 'step', stepType: stepTag },
19-
{ name: 'anchor step', stepType: anchorStepTag },
29+
{ name: 'step', stepTypeTag: stepTag },
30+
{ name: 'anchor step', stepTypeTag: anchorStepTag },
2031
] as const;
2132
parameterizeSuite(stepperTests, (suite, name, value) => {
2233
suite(`with ${name}`, () => {
23-
class FixtureModel {
24-
@observable public step1!: Step;
25-
@observable public step2?: Step;
26-
@observable public multiple = false;
27-
}
28-
29-
async function setup(source: FixtureModel): Promise<Fixture<Stepper>> {
30-
return await fixture<Stepper>(
31-
html<FixtureModel>`<${stepperTag}>
32-
<${value.stepType} ${ref('step1')}></${value.stepType}>
33-
${when(x => x.multiple, html<FixtureModel>`<${value.stepType} ${ref('step2')}></${value.stepType}>`)}
34-
</${stepperTag}>`,
35-
{ source }
36-
);
37-
}
38-
3934
describe('count single', () => {
40-
it('configures stepInternals last', async () => {
41-
const model = new FixtureModel();
42-
const { connect, disconnect } = await setup(model);
35+
class Model {
36+
@observable public step!: Step | AnchorStep;
37+
public stepPageObject!: StepBasePageObject;
38+
}
39+
40+
async function setup(): Promise<Fixture<Stepper> & { model: Model }> {
41+
const model = new Model();
42+
const result = await fixture<Stepper>(
43+
html<Model>`
44+
<${stepperTag}>
45+
<${value.stepTypeTag} ${ref('step')}></${value.stepTypeTag}>
46+
</${stepperTag}>
47+
`,
48+
{ source: model }
49+
);
50+
model.stepPageObject = createStepPageObject(model.step);
51+
return { ...result, model };
52+
}
53+
54+
let element: Stepper;
55+
let connect: () => Promise<void>;
56+
let disconnect: () => Promise<void>;
57+
let model: Model;
58+
59+
beforeEach(async () => {
60+
({ connect, element, model, disconnect } = await setup());
4361
await connect();
44-
expect(model.step1.stepInternals.last).toBe(true);
45-
expect(model.step2).toBe(undefined);
46-
await disconnect();
4762
});
4863

49-
it('configures stepInternals orientation default', async () => {
50-
const model = new FixtureModel();
51-
const { connect, disconnect } = await setup(model);
52-
await connect();
53-
expect(model.step1.stepInternals.orientation).toBe('horizontal');
54-
expect(model.step2).toBe(undefined);
64+
afterEach(async () => {
5565
await disconnect();
5666
});
5767

58-
it('configures stepInternals orientation vertical set', async () => {
59-
const model = new FixtureModel();
60-
const { connect, element, disconnect } = await setup(model);
68+
it('configures stepInternals last', () => {
69+
expect(model.step.stepInternals.last).toBe(true);
70+
});
71+
72+
it('configures stepInternals orientation default', () => {
73+
expect(model.step.stepInternals.orientation).toBe('horizontal');
74+
});
75+
76+
it('configures stepInternals orientation vertical', async () => {
6177
element.orientation = 'vertical';
62-
await connect();
63-
expect(model.step1.stepInternals.orientation).toBe('vertical');
64-
expect(model.step2).toBe(undefined);
65-
await disconnect();
78+
await waitForUpdatesAsync();
79+
expect(model.step.stepInternals.orientation).toBe('vertical');
6680
});
6781
});
6882

6983
describe('count multiple', () => {
70-
it('to configure stepInternals last', async () => {
71-
const model = new FixtureModel();
72-
model.multiple = true;
73-
const { connect, disconnect } = await setup(model);
84+
class Model {
85+
@observable public step1!: Step | AnchorStep;
86+
public step1PageObject!: StepBasePageObject;
87+
@observable public step2!: Step | AnchorStep;
88+
public step2PageObject!: StepBasePageObject;
89+
}
90+
91+
async function setup(): Promise<Fixture<Stepper> & { model: Model }> {
92+
const model = new Model();
93+
const result = await fixture<Stepper>(
94+
html<Model>`
95+
<${stepperTag}>
96+
<${value.stepTypeTag} ${ref('step1')}></${value.stepTypeTag}>
97+
<${value.stepTypeTag} ${ref('step2')}></${value.stepTypeTag}>
98+
</${stepperTag}>
99+
`,
100+
{ source: model }
101+
);
102+
model.step1PageObject = createStepPageObject(model.step1);
103+
model.step2PageObject = createStepPageObject(model.step2);
104+
return { ...result, model };
105+
}
106+
107+
let element: Stepper;
108+
let connect: () => Promise<void>;
109+
let disconnect: () => Promise<void>;
110+
let model: Model;
111+
112+
beforeEach(async () => {
113+
({ connect, element, model, disconnect } = await setup());
74114
await connect();
75-
expect(model.step1.stepInternals.last).toBe(false);
76-
expect(model.step2!.stepInternals.last).toBe(true);
115+
});
116+
117+
afterEach(async () => {
77118
await disconnect();
78119
});
79120

80-
it('configures stepInternals orientation default', async () => {
81-
const model = new FixtureModel();
82-
model.multiple = true;
83-
const { connect, disconnect } = await setup(model);
84-
await connect();
121+
it('to configure stepInternals last', () => {
122+
expect(model.step1.stepInternals.last).toBe(false);
123+
expect(model.step2.stepInternals.last).toBe(true);
124+
});
125+
126+
it('configures stepInternals orientation default', () => {
85127
expect(model.step1.stepInternals.orientation).toBe('horizontal');
86-
expect(model.step2!.stepInternals.orientation).toBe('horizontal');
87-
await disconnect();
128+
expect(model.step2.stepInternals.orientation).toBe('horizontal');
88129
});
89130

90-
it('configures stepInternals orientation vertical set', async () => {
91-
const model = new FixtureModel();
92-
model.multiple = true;
93-
const { connect, element, disconnect } = await setup(model);
131+
it('configures stepInternals orientation vertical', async () => {
94132
element.orientation = 'vertical';
95-
await connect();
133+
await waitForUpdatesAsync();
96134
expect(model.step1.stepInternals.orientation).toBe('vertical');
97-
expect(model.step2!.stepInternals.orientation).toBe('vertical');
135+
expect(model.step2.stepInternals.orientation).toBe('vertical');
136+
});
137+
});
138+
139+
describe('with label provider', () => {
140+
class Model {
141+
@observable public step!: Step | AnchorStep;
142+
public stepPageObject!: StepBasePageObject;
143+
@observable public labelProvider!: LabelProviderCore;
144+
}
145+
146+
async function setup(): Promise<Fixture<Stepper> & { model: Model }> {
147+
const model = new Model();
148+
const result = await fixture<Stepper>(
149+
html<Model>`
150+
<${themeProviderTag}>
151+
<${labelProviderCoreTag} ${ref('labelProvider')}></${labelProviderCoreTag}>
152+
<${stepperTag}>
153+
<${value.stepTypeTag} ${ref('step')}></${value.stepTypeTag}>
154+
</${stepperTag}>
155+
</${themeProviderTag}>
156+
`,
157+
{ source: model }
158+
);
159+
model.stepPageObject = createStepPageObject(model.step);
160+
return { ...result, model };
161+
}
162+
163+
let connect: () => Promise<void>;
164+
let disconnect: () => Promise<void>;
165+
let model: Model;
166+
167+
beforeEach(async () => {
168+
({ connect, model, disconnect } = await setup());
169+
await connect();
170+
});
171+
172+
afterEach(async () => {
98173
await disconnect();
99174
});
175+
176+
it('uses CoreLabelProvider popupIconCompleted for the error icon label', async () => {
177+
model.step.severity = Severity.error;
178+
model.labelProvider.popupIconError = 'Custom error';
179+
await waitForUpdatesAsync();
180+
expect(model.stepPageObject.getErrorSeverityLabel()).toBe('Custom error');
181+
});
182+
183+
it('uses CoreLabelProvider popupIconWarning for the warning icon label', async () => {
184+
model.step.severity = Severity.warning;
185+
model.labelProvider.popupIconWarning = 'Custom warning';
186+
await waitForUpdatesAsync();
187+
expect(model.stepPageObject.getWarningSeverityLabel()).toBe('Custom warning');
188+
});
189+
190+
it('uses CoreLabelProvider popupIconCompleted for the success icon label', async () => {
191+
model.step.severity = Severity.success;
192+
model.labelProvider.popupIconCompleted = 'Custom success';
193+
await waitForUpdatesAsync();
194+
expect(model.stepPageObject.getSuccessSeverityLabel()).toBe('Custom success');
195+
});
196+
197+
it('uses CoreLabelProvider popupIconCurrent for the success icon label', async () => {
198+
model.step.selected = true;
199+
model.labelProvider.popupIconCurrent = 'Custom current';
200+
await waitForUpdatesAsync();
201+
expect(model.stepPageObject.getSelectedStateLabel()).toBe('Custom current');
202+
});
100203
});
101204
});
102205
});

0 commit comments

Comments
 (0)