department-of-veterans-affairs/vets-website

View on GitHub
src/applications/vaos/new-appointment/components/DateTimeRequestPage/index.unit.spec.js

Summary

Maintainability
F
3 days
Test Coverage
import React from 'react';
import MockDate from 'mockdate';
import { expect } from 'chai';
import moment from 'moment';
import userEvent from '@testing-library/user-event';

import { waitFor, within } from '@testing-library/dom';
import { Route } from 'react-router-dom';
import { mockFetch } from '@department-of-veterans-affairs/platform-testing/helpers';
import { FETCH_STATUS } from '../../../utils/constants';
import DateTimeRequestPage from '.';
import {
  createTestStore,
  renderWithStoreAndRouter,
  setCommunityCareFlow,
} from '../../../tests/mocks/setup';
import { mockSchedulingConfigurations } from '../../../tests/mocks/helpers';
import { getSchedulingConfigurationMock } from '../../../tests/mocks/mock';
import { createMockFacility } from '../../../tests/mocks/data';
import { mockFacilitiesFetch } from '../../../tests/mocks/fetch';

async function chooseMorningRequestSlot(screen) {
  const currentMonth = moment()
    .add(5, 'days')
    .format('MMMM');

  userEvent.click(
    screen
      .getAllByLabelText(new RegExp(currentMonth))
      .find(button => button.disabled === false),
  );

  userEvent.click(
    screen.getByRole('checkbox', {
      name: 'A.M. appointment',
    }),
  );
}

describe('VAOS Page: DateTimeRequestPage', () => {
  beforeEach(() => {
    mockFetch();
    MockDate.set(moment('2020-01-26T14:00:00'));
  });
  afterEach(() => {
    MockDate.reset();
  });
  it('should allow user to request date and time for a community care appointment', async () => {
    const store = createTestStore({
      newAppointment: {
        data: {},
        pages: [],
        eligibility: [],
        appointmentSlotsStatus: FETCH_STATUS.succeeded,
        previousPages: [],
      },
    });

    const screen = renderWithStoreAndRouter(
      <Route component={DateTimeRequestPage} />,
      {
        store,
      },
    );

    // it should display page heading
    expect(
      await screen.findByRole('heading', {
        level: 1,
        name: /Choose an appointment day and time/i,
      }),
    ).to.be.ok;

    expect(
      screen.getByRole('heading', {
        level: 2,
        name: moment()
          .add(5, 'days')
          .format('MMMM YYYY'),
      }),
    ).to.be.ok;

    const dayHeaders = screen
      .getAllByRole('columnheader')
      .map(el => el.querySelector('.sr-only').textContent);
    expect(dayHeaders).to.deep.equal([
      'Monday',
      'Tuesday',
      'Wednesday',
      'Thursday',
      'Friday',
    ]);

    // Find all available appointments for the current month
    let currentMonth = moment()
      .add(5, 'days')
      .format('MMMM');

    let buttons = screen
      .getAllByLabelText(new RegExp(currentMonth))
      .filter(button => button.disabled === false);

    // Towards the end of the month our 4 days out min date will be in the next
    // month, and we need to move the calendar to the next month before selecting a date
    if (!buttons.length) {
      userEvent.click(screen.getByText('Next'));
      await screen.findByRole('heading', {
        name: moment()
          .add(1, 'month')
          .format('MMMM YYYY'),
      });
      currentMonth = moment()
        .add(1, 'month')
        .format('MMMM');

      buttons = screen
        .getAllByLabelText(new RegExp(currentMonth))
        .filter(button => button.disabled === false);
    }

    // Get the first day in the second week of the month, which
    // should always be a Monday
    let mondayInMonth = within(screen.getAllByRole('rowgroup')[2]);
    mondayInMonth = within(mondayInMonth.getAllByRole('row')[2]);
    mondayInMonth = within(mondayInMonth.getAllByRole('cell')[0]);
    mondayInMonth = mondayInMonth.queryAllByRole('button')[0].textContent;

    // Verify the first button in the second week is actually a Monday
    expect(
      moment()
        .add(5, 'days')
        .date(mondayInMonth)
        .day(),
    ).to.equal(1);

    // it should allow the user to select morning for currently selected date
    userEvent.click(buttons[0]);

    // 2. Simulate user selecting a time
    let checkbox = screen.getByRole('checkbox', {
      name: 'A.M. appointment',
    });
    userEvent.click(checkbox);
    expect(buttons[0].getAttribute('aria-label')).to.contain('AM selected');

    // 3. Simulate user selecting another time
    checkbox = screen.getByRole('checkbox', {
      name: 'P.M. appointment',
    });
    userEvent.click(checkbox);

    expect(buttons[0]).to.contain.text('AM');
    expect(buttons[0]).to.contain.text('PM');
    expect(buttons[0].getAttribute('aria-label')).to.contain(
      'AM and PM selected',
    );
    // checks that the selected day matches the button used
    expect(
      screen.baseElement.querySelector('.vaos-calendar__day--selected'),
    ).to.contain.text(buttons[0].textContent);

    // 4. it should allow the user to submit the form
    let button = screen.getByText(/^Continue/);
    userEvent.click(button);
    await waitFor(() => {
      expect(screen.history.push.called).to.be.true;
    });

    // NOTE: Reset the spy!!!
    screen.history.push.reset();

    // 5. it should allow the user to go to the previous page
    button = screen.getByText(/^Back/);
    userEvent.click(button);
    await waitFor(() => {
      expect(screen.history.push.called).to.be.true;
    });
  });

  it('should allow the user to view different calendar months', async () => {
    const store = createTestStore({
      newAppointment: {
        data: {},
        pages: [],
        eligibility: [],
        appointmentSlotsStatus: FETCH_STATUS.succeeded,
        previousPages: [],
      },
    });

    const screen = renderWithStoreAndRouter(
      <Route component={DateTimeRequestPage} />,
      {
        store,
      },
    );

    // it should not allow the user to view the previous month if viewing the current month
    let button = await screen.findByText('Previous');
    userEvent.click(button);
    await waitFor(() => {
      expect(screen.history.push.called).to.be.false;
    });

    button = screen.getByText('Next');
    userEvent.click(button);
    expect(
      screen.getByRole('heading', {
        level: 2,
        name: moment()
          .add(1, 'M')
          .format('MMMM YYYY'),
      }),
    ).to.be.ok;

    button = screen.getByText('Previous');
    userEvent.click(button);
    expect(
      screen.getByRole('heading', {
        level: 2,
        name: moment().format('MMMM YYYY'),
      }),
    ).to.be.ok;
  });

  it('should display an alert when user selects more than 3 dates', async () => {
    const store = createTestStore({
      newAppointment: {
        data: {},
        pages: [],
        eligibility: [],
        appointmentSlotsStatus: FETCH_STATUS.succeeded,
        previousPages: [],
      },
    });

    const screen = renderWithStoreAndRouter(
      <Route component={DateTimeRequestPage} />,
      {
        store,
      },
    );

    // Find all available appointments for the current month
    const currentMonth = moment()
      .add(5, 'days')
      .format('MMMM');
    let buttons = screen
      .getAllByLabelText(new RegExp(currentMonth))
      .filter(button => button.disabled === false);

    if (buttons.length < 4) {
      userEvent.click(screen.getByText(/^Next/));
      const nextMonth = moment()
        .add(5, 'days')
        .add(1, 'month');
      await screen.findByRole('heading', {
        name: nextMonth.format('MMMM YYYY'),
      });
      buttons = screen
        .getAllByLabelText(new RegExp(nextMonth.format('MMMM')))
        .filter(button => button.disabled === false);
    }

    // it should display an alert when the users selects more than the allowed dates
    // 1. Simulate user selecting a date
    userEvent.click(buttons[0]);

    // 2. Simulate user selecting AM
    let checkbox = await screen.findByLabelText(/^A\.M\./i);
    userEvent.click(checkbox);

    // 3. Simulate user selecting another date
    userEvent.click(buttons[1]);

    // 3. Simulate user selecting PM
    checkbox = await screen.findByLabelText(/^P\.M\./i);
    userEvent.click(checkbox);

    // 4. Simulate user selecting another date
    userEvent.click(buttons[2]);

    // 5. Simulate user selecting AM
    checkbox = await screen.findByLabelText(/^A\.M\./i);
    userEvent.click(checkbox);

    // 6. Simulate user selecting another date
    userEvent.click(buttons[3]);

    // 7. Simulate user selecting PM which should result in error
    checkbox = await screen.findByLabelText(/^P\.M\./i);
    userEvent.click(checkbox);

    // 8 Simulate user submit the form
    const button = screen.getByText(/^Continue/);
    userEvent.click(button);

    // NOTE: alert doesn't have a name so search for text too
    expect(await screen.findByRole('alert')).to.be.ok;
    expect(screen.getByText(/You can only choose up to 3 dates/i)).to.be.ok;

    // alert goes away after unselecting
    userEvent.click(checkbox);
    await waitFor(() => expect(checkbox.checked).to.be.false);
    await waitFor(() => expect(screen.queryByRole('alert')).not.to.exist);
  });

  it('should display an alert when user selects 2 dates and multiple times', async () => {
    const store = createTestStore({
      newAppointment: {
        data: {},
        pages: [],
        eligibility: [],
        appointmentSlotsStatus: FETCH_STATUS.succeeded,
      },
    });

    const screen = renderWithStoreAndRouter(
      <Route component={DateTimeRequestPage} />,
      {
        store,
      },
    );

    // Find all available appointments for the current month
    const currentMonth = moment()
      .add(5, 'days')
      .format('MMMM');
    let buttons = screen
      .getAllByLabelText(new RegExp(currentMonth))
      .filter(button => button.disabled === false);

    if (buttons.length < 2) {
      userEvent.click(screen.getByText(/^Next/));
      const nextMonth = moment()
        .add(5, 'days')
        .add(1, 'month');
      await screen.findByRole('heading', {
        name: nextMonth.format('MMMM YYYY'),
      });
      buttons = screen
        .getAllByLabelText(new RegExp(nextMonth.format('MMMM')))
        .filter(button => button.disabled === false);
    }

    // it should display an alert when the users selects more than the allowed dates
    // 1. Simulate user selecting a date
    userEvent.click(buttons[0]);

    // 2. Simulate user selecting AM
    let checkbox = screen.getByRole('checkbox', {
      name: 'A.M. appointment',
    });
    userEvent.click(checkbox);

    // 3. Simulate user selecting PM
    checkbox = screen.getByRole('checkbox', {
      name: 'P.M. appointment',
    });
    userEvent.click(checkbox);

    // 4. Simulate user selecting another date
    userEvent.click(buttons[1]);

    // 5. Simulate user selecting AM
    checkbox = screen.getByRole('checkbox', {
      name: 'A.M. appointment',
    });
    userEvent.click(checkbox);

    // 6. Simulate user selecting PM which should result in error
    checkbox = await screen.findByRole('checkbox', {
      name: 'P.M. appointment',
    });
    userEvent.click(checkbox);

    // 7. it should not allow the user to submit the form
    const button = screen.getByText(/^Continue/);
    userEvent.click(button);
    await waitFor(() => {
      expect(screen.history.push.called).to.be.false;
    });

    // NOTE: alert doesn't have a name so search for text too
    expect(screen.getByRole('alert')).to.be.ok;
    expect(screen.getByText(/You can only choose up to 3 dates./i)).to.be.ok;
  });

  it('should display an alert when user submits the form with no dates selected', async () => {
    const store = createTestStore({
      newAppointment: {
        data: {},
        pages: [],
        eligibility: [],
        appointmentSlotsStatus: FETCH_STATUS.succeeded,
        previousPages: [],
      },
    });

    const screen = renderWithStoreAndRouter(
      <Route component={DateTimeRequestPage} />,
      {
        store,
      },
    );

    // it should display an alert when users tries to submit the form
    let button = await screen.findByText(/^Continue/);
    userEvent.click(button);

    // NOTE: alert doesn't have a name so search for text too
    expect(screen.getByRole('alert')).to.be.ok;
    expect(
      screen.getByText(
        'Please select at least one preferred date for your appointment. You can select up to three dates.',
      ),
    ).to.be.ok;

    // 1 it should not allow user to submit the form
    button = screen.getByText(/^Continue/);
    userEvent.click(button);
    await waitFor(() => {
      expect(screen.history.push.called).to.be.false;
    });
  });

  it('should not allow selections after max date', async () => {
    const store = createTestStore({
      newAppointment: {
        data: {},
        pages: [],
        eligibility: [],
        appointmentSlotsStatus: FETCH_STATUS.succeeded,
        previousPages: [],
      },
    });

    const screen = renderWithStoreAndRouter(
      <Route component={DateTimeRequestPage} />,
      {
        store,
      },
    );

    const datePastMax = moment()
      .add(121, 'days')
      // first monday after the max
      .day(8);
    const endMonth = datePastMax.format('MMMM YYYY');
    let monthHeader = screen.queryByText(endMonth);
    await screen.findByText(/next/i);
    // Doing the normal byText query doesn't grab the button element
    // and doing getAllByRole for buttons with a particular title is slow
    // because of how many buttons there are on the page
    const nextButton = screen
      .getAllByRole('button')
      .find(el => el.textContent.includes('Next'));

    while (!monthHeader && !nextButton.disabled) {
      userEvent.click(nextButton);
      monthHeader = screen.queryByText(endMonth);
    }

    if (monthHeader) {
      // if the max date is within a month we can get to, then make sure the
      // date is disabled
      const dateButton = screen.queryByText(datePastMax.format('D'));
      expect(dateButton.disabled).to.be.true;
    } else {
      // if the max date is in a month we can't get to, make sure next button is disabled
      expect(nextButton.disabled).to.be.true;
    }
  });

  describe('community care iterations flag is turned on', () => {
    beforeEach(() => {
      mockFacilitiesFetch({
        children: true,
        ids: ['983', '984'],
        facilities: [
          createMockFacility({
            id: '983',
          }),
          createMockFacility({
            id: '984',
          }),
        ],
      });
      mockSchedulingConfigurations(
        [
          getSchedulingConfigurationMock({
            id: '983',
            typeOfCareId: 'primaryCare',
            requestEnabled: true,
          }),
          getSchedulingConfigurationMock({
            id: '984',
            typeOfCareId: 'primaryCare',
            requestEnabled: true,
          }),
        ],
        true,
      );
    });
    it('should continue to closest city page', async () => {
      // Given the user has two or more supported parent sites
      // And the user is in the community care flow
      const store = await setCommunityCareFlow({
        registeredSites: ['983', '984'],
        parentSites: [{ id: '983' }, { id: '983GC' }],
        supportedSites: ['983', '983GC'],
      });

      // And the page has loaded
      const screen = renderWithStoreAndRouter(<DateTimeRequestPage />, {
        store,
      });

      expect(
        await screen.findByRole('heading', {
          level: 1,
          name: /Choose an appointment day and time/i,
        }),
      ).to.be.ok;

      // And the user has chosen a time slot
      await chooseMorningRequestSlot(screen);

      // When the user continues
      userEvent.click(screen.getByText(/^Continue/));

      // Then they're sent to the closest city selection page
      await waitFor(() => {
        expect(screen.history.push.lastCall.args[0]).to.equal(
          '/new-appointment/choose-closest-city',
        );
      });
    });

    it('should skip closest city page for single site', async () => {
      // Given the user has one supported parent site
      // And the user is in the community care flow
      const store = await setCommunityCareFlow({
        registeredSites: ['983'],
        parentSites: [{ id: '983' }, { id: '983GC' }],
        supportedSites: ['983'],
      });

      // And the page has loaded
      const screen = renderWithStoreAndRouter(<DateTimeRequestPage />, {
        store,
      });

      expect(
        await screen.findByRole('heading', {
          level: 1,
          name: /Choose an appointment day and time/i,
        }),
      ).to.be.ok;

      // And the user has chosen a time slot
      await chooseMorningRequestSlot(screen);

      // When the user continues
      userEvent.click(screen.getByText(/^Continue/));

      // Then they're sent to the preferences page
      await waitFor(() => {
        expect(screen.history.push.lastCall.args[0]).to.equal(
          '/new-appointment/community-care-preferences',
        );
      });
    });
  });
});