Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save lqze/5ee4702498169a56f7de9bb019dbcf0f to your computer and use it in GitHub Desktop.

Select an option

Save lqze/5ee4702498169a56f7de9bb019dbcf0f to your computer and use it in GitHub Desktop.
Example of Complex Composite Component using a stepper.
// index.ts
export * from './stepper';
export * from './stepper-context';
export * from './stepper.types';
export * from './use-stepper';
/* eslint-disable @typescript-eslint/no-empty-function */
import { createContext } from 'react';
import { StepperContextInterface } from '.';
const StepperContext = createContext({} as StepperContextInterface);
StepperContext.displayName = 'StepperContext';
export { StepperContext };
/* eslint-disable i18next/no-literal-string */
import { render, screen } from '@testing-library/react';
import { FC } from 'react';
import { Stepper, useStepper } from '.';
const StepOneSimpleComponent: FC = () => {
const { step, nextStep } = useStepper();
return step === 0 ? (
<>
<div>I am step one</div>
<button onClick={nextStep}>Next</button>
</>
) : null;
};
const StepTwoSimpleComponent: FC = () => {
const { step, nextStep, prevStep } = useStepper();
return step === 1 ? (
<>
<div>I am step two</div>
<button onClick={prevStep}>Previous</button>
<button onClick={nextStep}>Next</button>
</>
) : null;
};
const StepThreeSimpleComponent: FC = () => {
const { step, nextStep, prevStep } = useStepper();
return step === 2 ? (
<>
<div>I am step three</div>
<button onClick={prevStep}>Previous</button>
<button onClick={nextStep}>Next</button>
</>
) : null;
};
const renderStepper = ({ initialStepCount = 0, ...renderOptions } = {}) => {
return render(
<Stepper initialStepCount={initialStepCount}>
<StepOneSimpleComponent />
<StepTwoSimpleComponent />
<StepThreeSimpleComponent />
</Stepper>,
{ ...renderOptions }
);
};
describe('Given our <Stepper> component has three arbitrary children', () => {
const nextClick = () => {
const nextButton = screen.getByText(/Next/i);
nextButton.click();
};
const prevClick = () => {
const prevButton = screen.getByText(/Previous/i);
prevButton.click();
};
describe('When initialStep is one', () => {
test('Only StepOneSimpleComponent should be visible', () => {
const { getByText, queryByText } = renderStepper();
expect(getByText(/I am step one/i)).toBeInTheDocument();
expect(queryByText(/I am step two/i)).not.toBeInTheDocument();
expect(queryByText(/I am step three/i)).not.toBeInTheDocument();
});
});
describe('When initialStep is two', () => {
test('Only StepTwoSimpleComponent should be visible', () => {
const { getByText, queryByText } = renderStepper({ initialStepCount: 1 });
expect(getByText(/I am step two/i)).toBeInTheDocument();
expect(queryByText(/I am step one/i)).not.toBeInTheDocument();
expect(queryByText(/I am step three/i)).not.toBeInTheDocument();
});
});
describe('When initialStep is three', () => {
test('Only StepThreeSimpleComponent should be visible', () => {
const { getByText, queryByText } = renderStepper({ initialStepCount: 2 });
expect(getByText(/I am step three/i)).toBeInTheDocument();
expect(queryByText(/I am step one/i)).not.toBeInTheDocument();
expect(queryByText(/I am step two/i)).not.toBeInTheDocument();
});
});
describe('When we press next on step one', () => {
test('Only StepTwoSimpleComponent should be visible', () => {
const { getByText, queryByText } = renderStepper();
nextClick();
expect(getByText(/I am step two/i)).toBeInTheDocument();
expect(queryByText(/I am step one/i)).not.toBeInTheDocument();
expect(queryByText(/I am step three/i)).not.toBeInTheDocument();
});
});
describe('When we press next on step two', () => {
test('Only StepThreeSimpleComponent should be visible', () => {
const { getByText, queryByText } = renderStepper({ initialStepCount: 1 });
nextClick();
expect(getByText(/I am step three/i)).toBeInTheDocument();
expect(queryByText(/I am step one/i)).not.toBeInTheDocument();
expect(queryByText(/I am step two/i)).not.toBeInTheDocument();
});
});
describe('When we press previous on step two', () => {
test('Only StepOneSimpleComponent should be visible', () => {
const { getByText, queryByText } = renderStepper({ initialStepCount: 1 });
prevClick();
expect(getByText(/I am step one/i)).toBeInTheDocument();
expect(queryByText(/I am step two/i)).not.toBeInTheDocument();
expect(queryByText(/I am step three/i)).not.toBeInTheDocument();
});
});
describe('When we press next on step three', () => {
test('No component should be visible', () => {
const { queryByText } = renderStepper({ initialStepCount: 2 });
nextClick();
expect(queryByText(/I am step one/i)).not.toBeInTheDocument();
expect(queryByText(/I am step two/i)).not.toBeInTheDocument();
expect(queryByText(/I am step three/i)).not.toBeInTheDocument();
});
});
});
import { FC, useCallback, useState } from 'react';
import { StepperContext } from '.';
/**
* Uses our StepperContext to manage the state of the stepper
*/
export const Stepper: FC<{
initialStepCount?: number;
}> = ({ children, initialStepCount = 0 }) => {
const [step, setStep] = useState(initialStepCount);
const nextStep = useCallback(() => {
setStep(step + 1);
}, [step]);
const prevStep = useCallback(() => {
if (step > 0) {
setStep(step - 1);
}
}, [step]);
return (
<StepperContext.Provider
value={{
step,
nextStep,
prevStep
}}
>
{children}
</StepperContext.Provider>
);
};
export interface StepperContextInterface<T> {
step: number;
// increment step by 1
nextStep: () => void;
// decrement step by 1
prevStep: () => void;
}
import { useContext } from 'react';
import { StepperContext } from './stepper-context';
function useStepper() {
const ctx = useContext(StepperContext);
if (ctx === undefined) {
throw new Error('useStepper must be used within a Stepper');
}
return ctx;
}
export { useStepper };
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment