166 lines
5.7 KiB
TypeScript
166 lines
5.7 KiB
TypeScript
import { Component, signal, ViewChild } from '@angular/core';
|
|
import { TestBed } from '@angular/core/testing';
|
|
import { StepperComponent } from './stepper.component';
|
|
import { StepComponent } from './step.component';
|
|
|
|
@Component({
|
|
imports: [StepperComponent, StepComponent],
|
|
template: `
|
|
<app-stepper #stepper [disabled]="stepperDisabled()">
|
|
<app-step label="Step 1" [isValid]="step1Valid()">
|
|
<div id="content-1">Content 1</div>
|
|
</app-step>
|
|
<app-step label="Step 2" [isValid]="step2Valid()">
|
|
<div id="content-2">Content 2</div>
|
|
</app-step>
|
|
</app-stepper>
|
|
`,
|
|
})
|
|
class TestHostComponent {
|
|
@ViewChild('stepper') stepper!: StepperComponent;
|
|
step1Valid = signal(true);
|
|
step2Valid = signal(true);
|
|
stepperDisabled = signal(false);
|
|
}
|
|
|
|
describe('StepperComponent & StepComponent', () => {
|
|
async function setup() {
|
|
await TestBed.configureTestingModule({
|
|
imports: [TestHostComponent, StepperComponent, StepComponent],
|
|
}).compileComponents();
|
|
|
|
const fixture = TestBed.createComponent(TestHostComponent);
|
|
fixture.detectChanges();
|
|
return { fixture, component: fixture.componentInstance };
|
|
}
|
|
|
|
it('renders step labels in the indicator bar', async () => {
|
|
const { fixture } = await setup();
|
|
const labels = fixture.nativeElement.querySelectorAll('.stepper-header__label');
|
|
expect(labels.length).toBe(2);
|
|
expect(labels[0].textContent.trim()).toBe('Step 1');
|
|
expect(labels[1].textContent.trim()).toBe('Step 2');
|
|
});
|
|
|
|
it('activates the first step by default and shows its content', async () => {
|
|
const { fixture } = await setup();
|
|
const content1 = fixture.nativeElement.querySelector('#content-1');
|
|
const content2 = fixture.nativeElement.querySelector('#content-2');
|
|
|
|
expect(content1).not.toBeNull();
|
|
expect(content2).toBeNull();
|
|
});
|
|
|
|
it('can initialize on a specified step', async () => {
|
|
await TestBed.configureTestingModule({
|
|
imports: [StepperComponent, StepComponent],
|
|
}).compileComponents();
|
|
|
|
const fixture = TestBed.createComponent(StepperComponent);
|
|
fixture.componentRef.setInput('initialStepIndex', 1);
|
|
fixture.detectChanges();
|
|
|
|
expect(fixture.componentInstance.currentStepIndex()).toBe(1);
|
|
});
|
|
|
|
it('advances to step 2 when next() is called and current step is valid', async () => {
|
|
const { fixture, component } = await setup();
|
|
|
|
component.stepper.next();
|
|
fixture.detectChanges();
|
|
|
|
const content1 = fixture.nativeElement.querySelector('#content-1');
|
|
const content2 = fixture.nativeElement.querySelector('#content-2');
|
|
|
|
expect(content1).toBeNull();
|
|
expect(content2).not.toBeNull();
|
|
expect(component.stepper.currentStepIndex()).toBe(1);
|
|
});
|
|
|
|
it('does not advance to step 2 when next() is called if current step is invalid', async () => {
|
|
const { fixture, component } = await setup();
|
|
component.step1Valid.set(false);
|
|
fixture.detectChanges();
|
|
|
|
component.stepper.next();
|
|
fixture.detectChanges();
|
|
|
|
const content1 = fixture.nativeElement.querySelector('#content-1');
|
|
const content2 = fixture.nativeElement.querySelector('#content-2');
|
|
|
|
expect(content1).not.toBeNull();
|
|
expect(content2).toBeNull();
|
|
expect(component.stepper.currentStepIndex()).toBe(0);
|
|
});
|
|
|
|
it('goes back to step 1 when previous() is called', async () => {
|
|
const { fixture, component } = await setup();
|
|
|
|
component.stepper.next();
|
|
fixture.detectChanges();
|
|
expect(component.stepper.currentStepIndex()).toBe(1);
|
|
|
|
component.stepper.previous();
|
|
fixture.detectChanges();
|
|
|
|
expect(component.stepper.currentStepIndex()).toBe(0);
|
|
expect(fixture.nativeElement.querySelector('#content-1')).not.toBeNull();
|
|
});
|
|
|
|
it('marks the first circle as active by default', async () => {
|
|
const { fixture } = await setup();
|
|
const items = fixture.nativeElement.querySelectorAll('.stepper-header__item');
|
|
expect(items[0].classList.contains('is-active')).toBe(true);
|
|
expect(items[1].classList.contains('is-active')).toBe(false);
|
|
});
|
|
|
|
it('marks step 1 as completed and step 2 as active after advancing', async () => {
|
|
const { fixture, component } = await setup();
|
|
|
|
component.stepper.next();
|
|
fixture.detectChanges();
|
|
|
|
const items = fixture.nativeElement.querySelectorAll('.stepper-header__item');
|
|
expect(items[0].classList.contains('is-completed')).toBe(true);
|
|
expect(items[1].classList.contains('is-active')).toBe(true);
|
|
});
|
|
|
|
it('allows navigating back to a completed step via goToStep()', async () => {
|
|
const { fixture, component } = await setup();
|
|
|
|
component.stepper.next();
|
|
fixture.detectChanges();
|
|
expect(component.stepper.currentStepIndex()).toBe(1);
|
|
|
|
component.stepper.goToStep(0);
|
|
fixture.detectChanges();
|
|
|
|
expect(component.stepper.currentStepIndex()).toBe(0);
|
|
expect(fixture.nativeElement.querySelector('#content-1')).not.toBeNull();
|
|
});
|
|
|
|
it('blocks all navigation and shows visual feedback while disabled', async () => {
|
|
const { fixture, component } = await setup();
|
|
component.stepperDisabled.set(true);
|
|
fixture.detectChanges();
|
|
|
|
component.stepper.next();
|
|
expect(component.stepper.currentStepIndex()).toBe(0);
|
|
|
|
component.stepperDisabled.set(false);
|
|
fixture.detectChanges();
|
|
component.stepper.next();
|
|
expect(component.stepper.currentStepIndex()).toBe(1);
|
|
|
|
component.stepperDisabled.set(true);
|
|
fixture.detectChanges();
|
|
component.stepper.previous();
|
|
component.stepper.goToStep(0);
|
|
|
|
expect(component.stepper.currentStepIndex()).toBe(1);
|
|
const header = fixture.nativeElement.querySelector('.stepper-header');
|
|
expect(header.classList.contains('is-disabled')).toBe(true);
|
|
expect(header.getAttribute('aria-disabled')).toBe('true');
|
|
});
|
|
});
|