18F/identity-idp

View on GitHub
app/javascript/packages/form-steps/use-history-param.spec.tsx

Summary

Maintainability
A
0 mins
Test Coverage
import { render, act } from '@testing-library/react';
import { renderHook } from '@testing-library/react-hooks';
import userEvent from '@testing-library/user-event';
import useHistoryParam, { getStepParam } from './use-history-param';

describe('getStepParam', () => {
  it('returns step', () => {
    const path = 'step';
    const result = getStepParam(path);

    expect(result).to.equal('step');
  });
});

describe('useHistoryParam', () => {
  function TestComponent({
    initialValue,
    validValues,
  }: {
    initialValue?: string;
    validValues?: string[];
  }) {
    const [count = 0, setCount] = useHistoryParam(initialValue, validValues);

    return (
      <>
        {/* Disable reason: https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/issues/566 */}
        {/* eslint-disable-next-line jsx-a11y/label-has-associated-control */}
        <label>
          Count: <input value={count} onChange={(event) => setCount(event.target.value)} />
        </label>
        <button type="button" onClick={() => setCount(String(Number(count) + 1))}>
          Increment
        </button>
      </>
    );
  }

  it('returns undefined value if absent from initial URL', () => {
    const { getByDisplayValue } = render(<TestComponent />);

    expect(getByDisplayValue('0')).to.be.ok();
  });

  it('returns initial value if present in initial URL', () => {
    window.location.hash = '#5';
    const { getByDisplayValue } = render(<TestComponent />);

    expect(getByDisplayValue('5')).to.be.ok();
  });

  it('syncs by setter', async () => {
    const { getByText, getByDisplayValue } = render(<TestComponent />);

    await userEvent.click(getByText('Increment'));

    expect(getByDisplayValue('1')).to.be.ok();
    expect(window.location.hash).to.equal('#1');
    await userEvent.click(getByText('Increment'));

    expect(getByDisplayValue('2')).to.be.ok();
    expect(window.location.hash).to.equal('#2');
  });

  it('scrolls to top on programmatic history manipulation', async () => {
    const { getByText } = render(<TestComponent />);

    window.scrollX = 100;
    window.scrollY = 100;

    await userEvent.click(getByText('Increment'));

    expect(window.scrollX).to.equal(0);
    expect(window.scrollY).to.equal(0);

    window.scrollX = 100;
    window.scrollY = 100;

    window.history.back();

    expect(window.scrollX).to.equal(100);
    expect(window.scrollY).to.equal(100);
  });

  it('syncs by history events', async () => {
    const { getByText, getByDisplayValue, findByDisplayValue } = render(<TestComponent />);

    await userEvent.click(getByText('Increment'));

    expect(getByDisplayValue('1')).to.be.ok();
    expect(window.location.hash).to.equal('#1');

    await userEvent.click(getByText('Increment'));

    expect(getByDisplayValue('2')).to.be.ok();
    expect(window.location.hash).to.equal('#2');

    window.history.back();

    expect(await findByDisplayValue('1')).to.be.ok();
    expect(window.location.hash).to.equal('#1');

    window.history.back();

    expect(await findByDisplayValue('0')).to.be.ok();
    expect(window.location.hash).to.equal('');
  });

  it('encodes parameter names and values', async () => {
    const { getByDisplayValue } = render(<TestComponent />);

    const input = getByDisplayValue('0');
    await userEvent.type(input, ' 1');

    expect(window.location.hash).to.equal('#0%201');
  });

  it('syncs across instances', () => {
    const inst1 = renderHook(() => useHistoryParam());
    const inst2 = renderHook(() => useHistoryParam());

    const [, setPath1] = inst1.result.current;
    setPath1('root');

    const [path2] = inst2.result.current;
    expect(path2).to.equal('root');
  });

  context('when specifying valid values', () => {
    it('syncs by history events for a valid value', async () => {
      const { getByText, getByDisplayValue, findByDisplayValue } = render(
        <TestComponent validValues={['0', '1']} />,
      );
      expect(getByDisplayValue('0')).to.be.ok();

      await userEvent.click(getByText('Increment'));

      expect(getByDisplayValue('1')).to.be.ok();
      expect(window.location.hash).to.equal('#1');

      act(() => {
        window.history.back();
      });

      expect(await findByDisplayValue('0')).to.be.ok();
      expect(window.location.hash).to.equal('');
    });

    it('maintains value (does not sync) by history events for an invalid value', async () => {
      const { getByDisplayValue } = render(<TestComponent validValues={['0', '1']} />);
      expect(getByDisplayValue('0')).to.be.ok();
      const popstateHandled = new Promise((resolve) =>
        window.addEventListener('popstate', resolve, { once: true }),
      );

      act(() => {
        window.location.hash = '#wrong';
      });

      await popstateHandled;
      expect(getByDisplayValue('0')).to.be.ok();
      expect(window.location.hash).to.equal('#wrong');
    });
  });
});