Last active
October 3, 2022 07:52
-
-
Save lqze/5ee4702498169a56f7de9bb019dbcf0f to your computer and use it in GitHub Desktop.
Example of Complex Composite Component using a stepper.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // index.ts | |
| export * from './stepper'; | |
| export * from './stepper-context'; | |
| export * from './stepper.types'; | |
| export * from './use-stepper'; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* eslint-disable @typescript-eslint/no-empty-function */ | |
| import { createContext } from 'react'; | |
| import { StepperContextInterface } from '.'; | |
| const StepperContext = createContext({} as StepperContextInterface); | |
| StepperContext.displayName = 'StepperContext'; | |
| export { StepperContext }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* 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(); | |
| }); | |
| }); | |
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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> | |
| ); | |
| }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| export interface StepperContextInterface<T> { | |
| step: number; | |
| // increment step by 1 | |
| nextStep: () => void; | |
| // decrement step by 1 | |
| prevStep: () => void; | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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