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: `
Content 1
Content 2
` }) class TestHostComponent { @ViewChild('stepper') stepper!: StepperComponent; step1Valid = signal(true); step2Valid = signal(true); } 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('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(); }); });