test(03-05): add failing StepIndicator WIZD-03 tests (RED)
- WIZD-03: clicking completed step dispatches SET_STEP
- WIZD-03: clicking back to step 0 dispatches SET_REMOTE_PARAMS({})
- WIZD-03: clicking back to step 0 does NOT dispatch RESET
- WIZD-03: step 0 shows as active when currentStep is 0
- WIZD-03: completed steps are clickable
This commit is contained in:
@@ -1,27 +1,226 @@
|
|||||||
// @vitest-environment jsdom
|
// @vitest-environment jsdom
|
||||||
// Covers WIZD-03: going back preserves remote.params; deployment options are untouched
|
// Covers WIZD-03: going back preserves remote.params; deployment options are untouched
|
||||||
import { describe, it, expect } from 'vitest';
|
import { describe, it, expect, vi } from 'vitest';
|
||||||
|
import { render, screen } from '@testing-library/react';
|
||||||
|
import userEvent from '@testing-library/user-event';
|
||||||
|
import React from 'react';
|
||||||
|
import { StepIndicator } from './StepIndicator';
|
||||||
|
import { WizardProvider } from '../../store/context';
|
||||||
|
import { useWizard } from '../../store/context';
|
||||||
|
|
||||||
|
// Helper: renders StepIndicator with a given currentStep by navigating the wizard state
|
||||||
|
function renderWithStep(currentStep: number) {
|
||||||
|
// We need a wrapper that sets the step before rendering StepIndicator
|
||||||
|
function Wrapper() {
|
||||||
|
const { dispatch } = useWizard();
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (currentStep > 0) {
|
||||||
|
dispatch({ type: 'SET_STEP', payload: currentStep });
|
||||||
|
}
|
||||||
|
}, [dispatch]);
|
||||||
|
return <StepIndicator />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return render(
|
||||||
|
<WizardProvider>
|
||||||
|
<Wrapper />
|
||||||
|
</WizardProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper: renders StepIndicator with a spy dispatch
|
||||||
|
function renderWithDispatchSpy(currentStep: number) {
|
||||||
|
const dispatched: { type: string; payload?: unknown }[] = [];
|
||||||
|
|
||||||
|
function SpyWrapper() {
|
||||||
|
const { dispatch, state } = useWizard();
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (currentStep > 0) {
|
||||||
|
dispatch({ type: 'SET_STEP', payload: currentStep });
|
||||||
|
}
|
||||||
|
}, [dispatch]);
|
||||||
|
|
||||||
|
// Wrap dispatch to spy on calls after initial setup
|
||||||
|
const spyDispatch = React.useCallback(
|
||||||
|
(action: { type: string; payload?: unknown }) => {
|
||||||
|
dispatched.push(action);
|
||||||
|
dispatch(action as Parameters<typeof dispatch>[0]);
|
||||||
|
},
|
||||||
|
[dispatch]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<span data-testid="current-step">{state.currentStep}</span>
|
||||||
|
<StepIndicatorWithDispatch dispatch={spyDispatch} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// We'll test dispatch behavior by checking actual state changes instead
|
||||||
|
return { dispatched, ...render(<WizardProvider><SpyWrapper /></WizardProvider>) };
|
||||||
|
}
|
||||||
|
|
||||||
|
// A version of StepIndicator that accepts an optional dispatch override for testing
|
||||||
|
// Actually we'll just test via rendered output and state changes
|
||||||
|
|
||||||
describe('StepIndicator', () => {
|
describe('StepIndicator', () => {
|
||||||
describe('WIZD-03: back navigation preserves state', () => {
|
describe('WIZD-03: back navigation preserves state', () => {
|
||||||
it('clicking a completed step dispatches SET_STEP', () => {
|
it('step 0 shows as active when currentStep is 0', async () => {
|
||||||
expect.fail('not yet implemented');
|
render(
|
||||||
|
<WizardProvider>
|
||||||
|
<StepIndicator />
|
||||||
|
</WizardProvider>
|
||||||
|
);
|
||||||
|
// Step 0 (Backend) should be the active step — rendered as bold/active span
|
||||||
|
const backendLabel = screen.getByText(/Backend/);
|
||||||
|
expect(backendLabel).toBeDefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('clicking back to step 0 dispatches SET_REMOTE_PARAMS({}) to clear params', () => {
|
it('completed steps are clickable', async () => {
|
||||||
expect.fail('not yet implemented');
|
// Render with currentStep=2 so steps 0 and 1 are completed
|
||||||
|
function Wrapper() {
|
||||||
|
const { dispatch } = useWizard();
|
||||||
|
React.useEffect(() => {
|
||||||
|
dispatch({ type: 'SET_STEP', payload: 2 });
|
||||||
|
}, [dispatch]);
|
||||||
|
return <StepIndicator />;
|
||||||
|
}
|
||||||
|
|
||||||
|
render(
|
||||||
|
<WizardProvider>
|
||||||
|
<Wrapper />
|
||||||
|
</WizardProvider>
|
||||||
|
);
|
||||||
|
|
||||||
|
// Wait for useEffect to set step to 2
|
||||||
|
await screen.findByText(/Deployment/);
|
||||||
|
|
||||||
|
// Steps 0 and 1 should be clickable buttons
|
||||||
|
const buttons = screen.getAllByRole('button');
|
||||||
|
// Should have at least 2 buttons for step 0 and step 1
|
||||||
|
expect(buttons.length).toBeGreaterThanOrEqual(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('clicking back to step 0 does NOT dispatch RESET (deployment preserved)', () => {
|
it('clicking a completed step dispatches SET_STEP', async () => {
|
||||||
expect.fail('not yet implemented');
|
const user = userEvent.setup();
|
||||||
|
|
||||||
|
function Wrapper() {
|
||||||
|
const { dispatch, state } = useWizard();
|
||||||
|
React.useEffect(() => {
|
||||||
|
dispatch({ type: 'SET_STEP', payload: 2 });
|
||||||
|
}, [dispatch]);
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<span data-testid="step">{state.currentStep}</span>
|
||||||
|
<StepIndicator />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
render(
|
||||||
|
<WizardProvider>
|
||||||
|
<Wrapper />
|
||||||
|
</WizardProvider>
|
||||||
|
);
|
||||||
|
|
||||||
|
// Wait until currentStep is 2
|
||||||
|
await screen.findByText('2', { selector: '[data-testid="step"]' });
|
||||||
|
|
||||||
|
// Click the "Backend" step (step 0 — completed)
|
||||||
|
const buttons = screen.getAllByRole('button');
|
||||||
|
const backendButton = buttons.find((b) => b.textContent?.includes('Backend'));
|
||||||
|
expect(backendButton).toBeDefined();
|
||||||
|
await user.click(backendButton!);
|
||||||
|
|
||||||
|
// After clicking, currentStep should be 0
|
||||||
|
const stepEl = screen.getByTestId('step');
|
||||||
|
expect(stepEl.textContent).toBe('0');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('step 0 shows as active when currentStep is 0', () => {
|
it('clicking back to step 0 dispatches SET_REMOTE_PARAMS({}) to clear params', async () => {
|
||||||
expect.fail('not yet implemented');
|
const user = userEvent.setup();
|
||||||
|
|
||||||
|
function Wrapper() {
|
||||||
|
const { dispatch, state } = useWizard();
|
||||||
|
React.useEffect(() => {
|
||||||
|
dispatch({ type: 'SET_BACKEND_TYPE', payload: 'azureblob' });
|
||||||
|
dispatch({ type: 'SET_REMOTE_PARAMS', payload: { account: 'myaccount' } });
|
||||||
|
dispatch({ type: 'SET_STEP', payload: 2 });
|
||||||
|
}, [dispatch]);
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<span data-testid="params">{JSON.stringify(state.remote.params)}</span>
|
||||||
|
<StepIndicator />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
render(
|
||||||
|
<WizardProvider>
|
||||||
|
<Wrapper />
|
||||||
|
</WizardProvider>
|
||||||
|
);
|
||||||
|
|
||||||
|
// Wait until step is 2 and params are set
|
||||||
|
await screen.findByText((text) => text.includes('myaccount'), { selector: '[data-testid="params"]' });
|
||||||
|
|
||||||
|
// Click the "Backend" step (step 0)
|
||||||
|
const buttons = screen.getAllByRole('button');
|
||||||
|
const backendButton = buttons.find((b) => b.textContent?.includes('Backend'));
|
||||||
|
expect(backendButton).toBeDefined();
|
||||||
|
await user.click(backendButton!);
|
||||||
|
|
||||||
|
// params should be cleared
|
||||||
|
const paramsEl = screen.getByTestId('params');
|
||||||
|
expect(paramsEl.textContent).toBe('{}');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('completed steps are clickable', () => {
|
it('clicking back to step 0 does NOT dispatch RESET (deployment preserved)', async () => {
|
||||||
expect.fail('not yet implemented');
|
const user = userEvent.setup();
|
||||||
|
|
||||||
|
function Wrapper() {
|
||||||
|
const { dispatch, state } = useWizard();
|
||||||
|
React.useEffect(() => {
|
||||||
|
// Set a non-default deployment state
|
||||||
|
dispatch({ type: 'SET_DEPLOYMENT', payload: { includeInstall: true } });
|
||||||
|
dispatch({ type: 'SET_DEPLOYMENT', payload: { scriptTargets: ['intune'] } });
|
||||||
|
dispatch({ type: 'SET_STEP', payload: 2 });
|
||||||
|
}, [dispatch]);
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<span data-testid="include-install">{String(state.deployment.includeInstall)}</span>
|
||||||
|
<span data-testid="targets">{JSON.stringify(state.deployment.scriptTargets)}</span>
|
||||||
|
<StepIndicator />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
render(
|
||||||
|
<WizardProvider>
|
||||||
|
<Wrapper />
|
||||||
|
</WizardProvider>
|
||||||
|
);
|
||||||
|
|
||||||
|
// Wait until deployment state is customized and step is 2
|
||||||
|
await screen.findByText('true', { selector: '[data-testid="include-install"]' });
|
||||||
|
|
||||||
|
// Click the "Backend" step
|
||||||
|
const buttons = screen.getAllByRole('button');
|
||||||
|
const backendButton = buttons.find((b) => b.textContent?.includes('Backend'));
|
||||||
|
expect(backendButton).toBeDefined();
|
||||||
|
await user.click(backendButton!);
|
||||||
|
|
||||||
|
// Deployment options should be preserved (not reset)
|
||||||
|
const includeInstallEl = screen.getByTestId('include-install');
|
||||||
|
expect(includeInstallEl.textContent).toBe('true');
|
||||||
|
const targetsEl = screen.getByTestId('targets');
|
||||||
|
expect(targetsEl.textContent).toBe('["intune"]');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Placeholder type for the spy component — not actually used in tests
|
||||||
|
function StepIndicatorWithDispatch(_props: { dispatch: (action: { type: string; payload?: unknown }) => void }) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user