@testing-library/react#waitForElementToBeRemoved JavaScript Examples

The following examples show how to use @testing-library/react#waitForElementToBeRemoved. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: tooltip-tests.jest.js    From monday-ui-react-core with MIT License 6 votes vote down vote up
describe("Tooltip tests", () => {
  it("Should trigger onTooltipShow", async () => {
    const onTooltipShow = jest.fn();
    const { getByText } = render(
      <Tooltip content="content" onTooltipShow={onTooltipShow}>
        <div>hello</div>
      </Tooltip>
    );
    fireEvent.mouseOver(getByText("hello"));
    await waitFor(() => screen.getByText("content"));
    expect(onTooltipShow).toHaveBeenCalledTimes(1);
  });

  it("Should trigger onTooltipHide", async () => {
    const onTooltipHide = jest.fn();
    const { getByText } = render(
      <Tooltip content="content" onTooltipHide={onTooltipHide}>
        <div>hello</div>
      </Tooltip>
    );
    fireEvent.mouseOver(getByText("hello"));
    await waitFor(() => screen.getByText("content"));
    fireEvent.mouseLeave(getByText("hello"));
    await waitForElementToBeRemoved(() => screen.getByText("content"));
    expect(onTooltipHide).toHaveBeenCalledTimes(1);
  });
});
Example #2
Source File: app-test-final.js    From tutorial with MIT License 6 votes vote down vote up
test("it can delete a list", async () => {
  // One unassociated Reminder
  server.create("reminder");

  // A List with three associated Reminders
  let list = server.create("list", {
    reminders: server.createList("reminder", 3),
  });
  visit(`/${list.id}?open`);
  await waitForElementToBeRemoved(() => screen.getByText("Loading..."));

  userEvent.click(screen.getByTestId("delete-list"));

  await waitForElementToBeRemoved(() => screen.getByText("Loading..."));

  expect(screen.getByTestId("active-list-title")).toHaveTextContent(
    "Reminders"
  );
  expect(screen.getAllByTestId("reminder").length).toEqual(1);
  expect(server.db.lists.length).toEqual(0);
  expect(server.db.reminders.length).toEqual(1); // The associated reminders should have been destroyed
});
Example #3
Source File: app-test-final.js    From tutorial with MIT License 6 votes vote down vote up
test("it can add a reminder to a list", async () => {
  let list = server.create("list");

  visit(`/${list.id}?open`);
  await waitForElementToBeRemoved(() => screen.getByText("Loading..."));

  userEvent.click(screen.getByTestId("add-reminder"));
  userEvent.type(screen.getByTestId("new-reminder-text"), "Work out{enter}");
  userEvent.click(screen.getByTestId("save-new-reminder"));

  await waitForElementToBeRemoved(() =>
    screen.getByTestId("new-reminder-text")
  );

  expect(screen.getByText("Work out")).toBeInTheDocument();
  expect(server.db.reminders.length).toEqual(1);
  expect(server.db.reminders[0].listId).toEqual(list.id);
});
Example #4
Source File: app-test-final.js    From tutorial with MIT License 6 votes vote down vote up
test("it shows existing reminders", async () => {
  server.create("reminder", { text: "Walk the dog" });
  server.create("reminder", { text: "Take out the trash" });
  server.create("reminder", { text: "Work out" });

  visit("/");
  await waitForElementToBeRemoved(() => screen.getByText("Loading..."));

  expect(screen.getByText("Walk the dog")).toBeInTheDocument();
  expect(screen.getByText("Take out the trash")).toBeInTheDocument();
  expect(screen.getByText("Work out")).toBeInTheDocument();
});
Example #5
Source File: app-test-final.js    From tutorial with MIT License 6 votes vote down vote up
test("the all screen shows tags for associated reminders", async () => {
  server.create("reminder", { text: "Unassociated reminder" });

  let list = server.create("list", { name: "List 1" });
  server.create("reminder", { text: "Associated reminder", list });

  visit("/");
  await waitForElementToBeRemoved(() => screen.getByText("Loading..."));

  let [reminder1, reminder2] = screen.getAllByTestId("reminder");
  expect(reminder1).toHaveTextContent("Unassociated reminder");
  expect(reminder2).toHaveTextContent("Associated reminder");
  expect(reminder2.querySelector("[data-testid=list-tag]")).toHaveTextContent(
    "List 1"
  );
});
Example #6
Source File: app-test-final.js    From tutorial with MIT License 6 votes vote down vote up
test("it can delete a reminder", async () => {
  server.create("reminder", { text: "Work out" });

  visit("/");
  await waitForElementToBeRemoved(() => screen.getByText("Loading..."));

  userEvent.click(screen.getByTestId("delete-reminder"));

  expect(screen.queryByText("Work out")).not.toBeInTheDocument();
  expect(server.db.reminders.length).toEqual(0);
});
Example #7
Source File: app-test-final.js    From tutorial with MIT License 6 votes vote down vote up
test("it can create a list", async () => {
  visit("/");
  await waitForElementToBeRemoved(() => screen.getByText("Loading..."));

  userEvent.click(screen.getByTestId("toggle-sidebar"));
  userEvent.click(screen.getByTestId("add-list"));
  await userEvent.type(screen.getByTestId("new-list-text"), "Home");
  userEvent.click(screen.getByTestId("save-new-list"));

  await waitForElementToBeRemoved(() => screen.getByTestId("new-list-text"));
  await waitForElementToBeRemoved(() => screen.getByText("Loading..."));

  expect(screen.getByTestId("active-list-title")).toHaveTextContent("Home");
  expect(server.db.lists.length).toEqual(1);
});
Example #8
Source File: app-test-final.js    From tutorial with MIT License 6 votes vote down vote up
test("a list shows reminders for only that list", async () => {
  // Unassociated reminder
  server.create("reminder");

  // List with 3 associated reminders
  let list = server.create("list", {
    reminders: server.createList("reminder", 3),
  });

  visit(`/${list.id}?open`);
  await waitForElementToBeRemoved(() => screen.getByText("Loading..."));

  expect(screen.getAllByTestId("reminder").length).toEqual(3);
});
Example #9
Source File: logout.test.js    From inventory-management-web with MIT License 5 votes vote down vote up
it('logouts successfully', async () => {
  /* There is a bug related to localStorage testing. 
  Hence we use this method, but changing prototype is not recommeded */

  /* localStorage Mock* */
  const localStorageMock = {
    getItem: jest.fn(),
    setItem: jest.fn(),
    removeItem: jest.fn(),
  };
  Object.setPrototypeOf(global.localStorage, localStorageMock);

  /* axios Mock resolved value */
  axiosMock.post.mockResolvedValue({
    data: {},
  });
  const setMobileOpen = jest.fn();
  const setTabletOpen = jest.fn();
  const mobileOpen = false;
  const tabletOpen = false;

  render(
    <NavBar
      mobileOpen={mobileOpen}
      setMobileOpen={setMobileOpen}
      tabletOpen={tabletOpen}
      setTabletOpen={setTabletOpen}
    />,
    {
      wrapper: MemoryRouter,
    }
  );

  const logout = screen.getByRole('button', { name: /logout/i });

  fireEvent.click(logout);
  const agree = await screen.findByRole('button', { name: /^agree/i });
  fireEvent.click(agree);

  await waitForElementToBeRemoved(() =>
    screen.getByText(/Are you sure you wish to logout?/i)
  );

  expect(axiosMock.post).toHaveBeenCalledTimes(1);
  expect(localStorage.removeItem).toBeCalledWith('token');
  expect(localStorage.removeItem).toBeCalledWith('isStaff');
});
Example #10
Source File: BbbSettings.test.jsx    From frontend-app-course-authoring with GNU Affero General Public License v3.0 5 votes vote down vote up
describe('BBB Settings', () => {
  beforeEach(async () => {
    initializeMockApp({
      authenticatedUser: {
        userId: 3,
        username: 'abc123',
        administrator: false,
        roles: [],
      },
    });
    store = initializeStore(initialState);
    axiosMock = new MockAdapter(getAuthenticatedHttpClient());
    history.push(liveSettingsUrl);
  });

  test('Plan dropdown to be visible and enabled in UI', async () => {
    await mockStore({ emailSharing: true });
    renderComponent();

    const spinner = getByRole(container, 'status');
    await waitForElementToBeRemoved(spinner);

    expect(queryByTestId(container, 'plansDropDown')).toBeInTheDocument();
    expect(container.querySelector('select[name="tierType"]')).not.toBeDisabled();
  });

  it('Plan dropdown should display correct number of options', async () => {
    await mockStore({ emailSharing: true });
    renderComponent();
    const spinner = getByRole(container, 'status');
    await waitForElementToBeRemoved(spinner);
    const dropDown = queryByTestId(container, 'plansDropDown');
    expect(getAllByRole(dropDown, 'option').length).toBe(3);
  });

  test('Connect to support and PII sharing message is visible and plans selection is disabled, When pii sharing is disabled, ',
    async () => {
      await mockStore({ piiSharingAllowed: false });
      renderComponent();
      const spinner = getByRole(container, 'status');
      await waitForElementToBeRemoved(spinner);
      const requestPiiText = queryByTestId(container, 'request-pii-sharing');
      const helpRequestPiiText = queryByTestId(container, 'help-request-pii-sharing');
      expect(requestPiiText).toHaveTextContent(
        messages.requestPiiSharingEnableForBbb.defaultMessage.replaceAll('{provider}', 'BigBlueButton'),
      );
      expect(helpRequestPiiText).toHaveTextContent(messages.piiSharingEnableHelpTextBbb.defaultMessage);
      expect(container.querySelector('select[name="tierType"]')).toBeDisabled();
    });

  test('free plans message is visible when free plan is selected', async () => {
    await mockStore({ emailSharing: true, isFreeTier: true });
    renderComponent();
    const spinner = getByRole(container, 'status');
    await waitForElementToBeRemoved(spinner);
    const dropDown = container.querySelector('select[name="tierType"]');
    userEvent.selectOptions(
      dropDown,
     getByRole(dropDown, 'option', { name: 'Free' }),
    );
    expect(queryByTestId(container, 'free-plan-message')).toBeInTheDocument();
    expect(queryByTestId(container, 'free-plan-message')).toHaveTextContent(messages.freePlanMessage.defaultMessage);
  });
});
Example #11
Source File: DiscussionsSettings.test.jsx    From frontend-app-course-authoring with GNU Affero General Public License v3.0 5 votes vote down vote up
describe.each([
  { piiSharingAllowed: false },
  { piiSharingAllowed: true },
])('PII sharing fields test', ({ piiSharingAllowed }) => {
  const enablePIISharing = false;

  beforeEach(() => {
    initializeMockApp({
      authenticatedUser: {
        userId: 3,
        username: 'abc123',
        administrator: true,
        roles: [],
      },
    });

    store = initializeStore({
      models: {
        courseDetails: {
          [courseId]: {},
        },
      },
    });
    axiosMock = new MockAdapter(getAuthenticatedHttpClient());

    // Leave the DiscussionsSettings route after the test.
    history.push(`/course/${courseId}/pages-and-resources`);
    axiosMock.onGet(getDiscussionsProvidersUrl(courseId))
      .reply(200, generateProvidersApiResponse(false));
    axiosMock.onGet(getDiscussionsSettingsUrl(courseId))
      .reply(200, generatePiazzaApiResponse(piiSharingAllowed));
    renderComponent();
  });

  test(`${piiSharingAllowed ? 'shows PII share username/email field when piiSharingAllowed is true'
    : 'hides PII share username/email field when piiSharingAllowed is false'}`, async () => {
    history.push(`/course/${courseId}/pages-and-resources/discussion`);

    // This is an important line that ensures the spinner has been removed - and thus our main
    // content has been loaded - prior to proceeding with our expectations.
    await waitForElementToBeRemoved(screen.getByRole('status'));

    userEvent.click(queryByLabelText(container, 'Select Piazza'));
    userEvent.click(queryByText(container, messages.nextButton.defaultMessage));
    await waitForElementToBeRemoved(screen.getByRole('status'));
    if (enablePIISharing) {
      expect(queryByTestId(container, 'piiSharingFields')).toBeInTheDocument();
    } else {
      expect(queryByTestId(container, 'piiSharingFields')).not.toBeInTheDocument();
    }
  });
});
Example #12
Source File: DiscussionsSettings.test.jsx    From frontend-app-course-authoring with GNU Affero General Public License v3.0 5 votes vote down vote up
describe.each([
  { isAdmin: false, isAdminOnlyConfig: false },
  { isAdmin: false, isAdminOnlyConfig: true },
  { isAdmin: true, isAdminOnlyConfig: false },
  { isAdmin: true, isAdminOnlyConfig: true },
])('LTI Admin only config test', ({ isAdmin, isAdminOnlyConfig }) => {
  beforeEach(() => {
    initializeMockApp({
      authenticatedUser: {
        userId: 3,
        username: 'abc123',
        administrator: isAdmin,
        roles: [],
      },
    });

    store = initializeStore({
      models: {
        courseDetails: {
          [courseId]: {},
        },
      },
    });
    axiosMock = new MockAdapter(getAuthenticatedHttpClient());

    // Leave the DiscussionsSettings route after the test.
    history.push(`/course/${courseId}/pages-and-resources`);
    axiosMock.onGet(getDiscussionsProvidersUrl(courseId))
      .reply(200, generateProvidersApiResponse(isAdminOnlyConfig));
    axiosMock.onGet(getDiscussionsSettingsUrl(courseId))
      .reply(200, generatePiazzaApiResponse(true));
    renderComponent();
  });

  test(`successfully advances to settings step for lti when adminOnlyConfig=${isAdminOnlyConfig} and user ${isAdmin ? 'is' : 'is not'} admin `, async () => {
    const showLTIConfig = isAdmin;
    history.push(`/course/${courseId}/pages-and-resources/discussion`);

    // This is an important line that ensures the spinner has been removed - and thus our main
    // content has been loaded - prior to proceeding with our expectations.
    await waitForElementToBeRemoved(screen.getByRole('status'));

    userEvent.click(queryByLabelText(container, 'Select Piazza'));
    userEvent.click(queryByText(container, messages.nextButton.defaultMessage));
    await waitForElementToBeRemoved(screen.getByRole('status'));

    if (showLTIConfig) {
      expect(queryByText(container, ltiMessages.formInstructions.defaultMessage)).toBeInTheDocument();
      expect(queryByTestId(container, 'ltiConfigFields')).toBeInTheDocument();
    } else {
      expect(queryByText(container, ltiMessages.formInstructions.defaultMessage)).not.toBeInTheDocument();
      expect(queryByTestId(container, 'ltiConfigFields')).not.toBeInTheDocument();
    }
  });
});
Example #13
Source File: app-test-final.js    From tutorial with MIT License 5 votes vote down vote up
test("it shows a message when there are no reminders", async () => {
  visit("/");

  await waitForElementToBeRemoved(() => screen.getByText("Loading..."));

  expect(screen.getByText("All done!")).toBeInTheDocument();
});
Example #14
Source File: MappingProfileDetails.test.js    From ui-data-export with Apache License 2.0 4 votes vote down vote up
describe('MappingProfileDetails', () => {
  describe('rendering details for a mapping profile which is already in use', () => {
    const renderMappingProfileDetails = () => renderWithIntl(
      <MappingProfileDetailsContainer
        allTransformations={allMappingProfilesTransformations}
        isDefaultProfile
        isProfileUsed
      />,
      translationsProperties
    );

    it('should display mapping profile details', () => {
      renderMappingProfileDetails();

      const dialog = screen.getByRole('dialog');

      expect(dialog).toBeVisible();

      const headings = within(dialog).getAllByRole('heading', { name: mappingProfileWithTransformations.name });

      headings.forEach(heading => expect(heading).toBeVisible());

      expect(within(dialog).getByRole('button', { name: /collapse all/i })).toBeVisible();

      const summary = within(dialog).getByRole('region', { name: /summary/i });
      const transformations = within(dialog).getByRole('region', { name: /transformations/i });

      expect(summary).toBeVisible();
      expect(transformations).toBeVisible();

      const labelsAndValues = [
        'Record created: 12/4/2018 1:29 AM',
        'Record last updated: 12/4/2018 1:29 AM',
        commonTranslations.name,
        'AP Holdings 1',
        translations.description,
        'AP Holdings 1 description',
        translations.outputFormat,
        'MARC',
        commonTranslations.folioRecordType,
        commonTranslations['recordTypes.holdings'],
      ];

      labelsAndValues.forEach(el => expect(within(summary).getByText(el)).toBeVisible());
    });

    it('should display correct transformations fields headers', () => {
      renderMappingProfileDetails();

      expect(screen.getByRole('columnheader', { name: translations['mappingProfiles.transformations.fieldName'] })).toBeVisible();
      expect(screen.getByRole('columnheader', { name: translations['mappingProfiles.transformations.transformation'] })).toBeVisible();
    });

    it('should display correct transformations values', () => {
      renderMappingProfileDetails();
      const transformationListRows = getAllByRole(screen.getByRole('rowgroup'), 'row');

      expect(getByText(transformationListRows[0], 'Holdings - Call number - Call number')).toBeVisible();
      expect(getByText(transformationListRows[0], '11100$a')).toBeVisible();
      expect(getByText(transformationListRows[1], 'Holdings - Notes - Action note')).toBeVisible();
      expect(getByText(transformationListRows[1], '123 1$12')).toBeVisible();
    });
    it('should display action buttons in the proper state', () => {
      const { container } = renderMappingProfileDetails();
      const actionButton = container.querySelector('[data-test-pane-header-actions-button]');
      const disableButton = container.querySelector('[data-test-delete-profile-button]');
      const duplicateButton = container.querySelector('[data-test-duplicate-profile-button]');
      const deleteButton = container.querySelector('[data-test-delete-profile-button]');

      userEvent.click(actionButton);

      expect(disableButton).toBeDisabled();
      expect(duplicateButton).toBeEnabled();
      expect(deleteButton).toBeDisabled();
    });
  });
  describe('rendering details for a mapping profile which is already in use', () => {
    const renderMappingProfileDetailsWithMapping = () => renderWithIntl(
      <MappingProfileDetailsContainer mappingProfile={mappingProfileWithoutTransformations} />,
      translationsProperties
    );

    it('should display no value in description', () => {
      const { container } = renderMappingProfileDetailsWithMapping();
      const summaryDescriptionValue = container.querySelector('[data-test-mapping-profile-description]');

      expect(getByText(summaryDescriptionValue, '-')).toBeVisible();
    });
    it('should not display delete confirmation modal', () => {
      const { container } = renderMappingProfileDetailsWithMapping();
      const deleteConfirmationModal = container.querySelector('#delete-mapping-profile-confirmation-modal');

      expect(deleteConfirmationModal).not.toBeInTheDocument();
    });
    it('should not display transformation list', () => {
      const { container } = renderMappingProfileDetailsWithMapping();
      const transformationList = container.querySelector('#mapping-profile-transformations-list');

      expect(transformationList).not.toBeInTheDocument();
    });

    it('should display action buttons enabled', () => {
      const { container } = renderMappingProfileDetailsWithMapping();
      const actionButton = container.querySelector('[data-test-pane-header-actions-button]');
      const disableButton = container.querySelector('[data-test-delete-profile-button]');
      const duplicateButton = container.querySelector('[data-test-duplicate-profile-button]');
      const deleteButton = container.querySelector('[data-test-delete-profile-button]');

      userEvent.click(actionButton);

      expect(disableButton).toBeEnabled();
      expect(duplicateButton).toBeEnabled();
      expect(deleteButton).toBeEnabled();
    });
    describe('clicking on delete profiles button', () => {
      it('should display delete confirmation modal', async () => {
        renderMappingProfileDetailsWithMapping();
        const actionButton = document.querySelector('[data-test-pane-header-actions-button]');

        userEvent.click(actionButton);

        const deleteButton = document.querySelector('[data-test-delete-profile-button]');

        userEvent.click(deleteButton);

        const modal = screen.getAllByRole('dialog').find(dialog => within(dialog).getByRole('heading', { name: /delete/i }));

        expect(modal).toBeVisible();
        userEvent.click(within(modal).getByRole('button', { name: /cancel/i }));

        await waitForElementToBeRemoved(modal);
      });
    });
  });
  describe('rendering mapping profile details in loading state', () => {
    it('should display preloader', () => {
      const { container } = renderWithIntl(<MappingProfileDetailsContainer
        mappingProfile={mappingProfileWithoutTransformations}
        isLoading
        isProfileUsed
      />,
      translationsProperties);

      expect(container.querySelector('[data-test-preloader]')).toBeVisible();
    });
  });
});
Example #15
Source File: DuplicateMappingProfileRoute.test.js    From ui-data-export with Apache License 2.0 4 votes vote down vote up
describe('DuplicateMappingProfileRoute', () => {
  describe('rendering duplicate mapping profile page with profile data: success scenario', () => {
    const handleSubmitMock = jest.fn();
    const handleCancelMock = jest.fn();
    const handleSubmitNavigateMock = jest.fn();
    const sendCalloutMock = jest.fn();

    const renderDuplicateMappingProfileRoute = () => renderWithIntl(
      <DuplicateMappingProfileRouteContainer allTransformations={allMappingProfilesTransformations} />,
      translationsProperties
    );

    const renderDuplicateMappingProfileRouteWithMocks = () => renderWithIntl(
      <DuplicateMappingProfileRouteContainer
        allTransformations={allMappingProfilesTransformations}
        sendCallout={sendCalloutMock}
        onSubmit={handleSubmitMock}
        onSubmitNavigate={handleSubmitNavigateMock}
        onCancel={handleCancelMock}
      />,
      translationsProperties
    );

    beforeEach(() => {
      jest.clearAllMocks();
    });

    it('should display the form', () => {
      const { container } = renderDuplicateMappingProfileRoute();
      const form = container.querySelector('[data-test-full-screen-form]');

      expect(form).toBeVisible();
    });

    it('should display correct pane title', () => {
      renderDuplicateMappingProfileRoute();

      expect(getByText(paneHeader('New field mapping profile'), translations['mappingProfiles.newProfile'])).toBeVisible();
    });

    it('should have correct name field value', () => {
      renderDuplicateMappingProfileRoute();
      expect(nameField()).toHaveValue(`Copy of ${mappingProfile.name}`);
    });

    it('should display add transformations button with proper wording', () => {
      renderDuplicateMappingProfileRoute();

      expect(getByText(transformationsBtn('Add transformations'), translations['mappingProfiles.transformations.addTransformations'])).toBeVisible();
    });

    it('should have correct description field value', () => {
      renderDuplicateMappingProfileRoute();
      expect(descriptionField()).toHaveValue(mappingProfile.description);
    });

    it('should have enabled save button if there are no changes', () => {
      renderDuplicateMappingProfileRoute();
      expect(saveAndCloseBtn()).toBeEnabled();
    });

    it('should have correct folio record types field checked', () => {
      renderDuplicateMappingProfileRoute();

      expect(recordTypeInstance()).toBeChecked();
      expect(recordTypesSRS()).not.toBeChecked();
      expect(recordTypesHoldings()).not.toBeChecked();
      expect(recordTypesItem()).not.toBeChecked();
    });

    it('should display in form proper values', async () => {
      renderDuplicateMappingProfileRoute();

      userEvent.clear(nameField());
      userEvent.type(nameField(), 'Change name');
      userEvent.clear(descriptionField());
      userEvent.type(descriptionField(), 'Updated value');
      userEvent.type(recordTypesHoldings(), 'instance');
      userEvent.click(saveAndCloseBtn());

      expect(nameField()).toHaveValue('Change name');
      expect(descriptionField()).toHaveValue('Updated value');
      expect(recordTypesHoldings()).toBeChecked();
    });

    describe('clicking on cancel button', () => {
      it('should call cancel callback', () => {
        renderDuplicateMappingProfileRouteWithMocks();

        userEvent.click(screen.getByText(/cancel/i));

        expect(handleCancelMock).toHaveBeenCalled();
      });
    });

    describe('opening transformations modal', () => {
      it('should display correct transformations table with filled values', () => {
        renderDuplicateMappingProfileRoute();
        userEvent.click(transformationsBtn('Add transformations'));

        expect(columnHeaderFieldName()[0]).toBeVisible();
        expect(columnHeaderTransformation()[0]).toBeVisible();
        expect(transformationListCells()).toBeVisible();
      });

      it('should fill transformation field group on transformation modal correctly', () => {
        renderDuplicateMappingProfileRoute();

        userEvent.click(transformationsBtn('Add transformations'));

        const transformationFields = getTransformationFieldGroups();

        expect(transformationFields[2].marcField.input.value).toBe('900');
        expect(transformationFields[2].indicator1.input.value).toBe('');
        expect(transformationFields[2].indicator2.input.value).toBe('1');
        expect(transformationFields[2].subfield.input.value).toBe('$12');
      });

      it('should not show validation error when clearing transformation with empty indicator field', () => {
        const { container } = renderDuplicateMappingProfileRoute();

        userEvent.click(transformationsBtn('Add transformations'));

        const transformationFields = getTransformationFieldGroups();
        const modal = document.querySelector('[data-test-transformations-modal]');

        userEvent.type(transformationFields[2].marcField.input, '');
        userEvent.type(transformationFields[0].indicator2.input, '');
        userEvent.type(transformationFields[0].subfield.input, '');

        userEvent.dblClick(screen.getByLabelText('Select all fields'));

        userEvent.click(getByRole(modal, 'button', { name: 'Save & close' }));

        return waitForElementToBeRemoved(() => container.querySelector('[data-test-transformations-modal]'));
      });
    });

    it('should display transformation value', () => {
      renderDuplicateMappingProfileRoute();

      userEvent.click(transformationsBtn('Add transformations'));

      expect(getByText(transformationListRows()[0], '900 1$12')).toBeVisible();
    });

    describe('submitting the form - success case', () => {
      beforeEach(() => {
        handleSubmitMock.mockImplementationOnce(() => Promise.resolve());
      });

      it('should call submit callback', () => {
        renderDuplicateMappingProfileRouteWithMocks();

        userEvent.click(saveAndCloseBtn());

        expect(handleSubmitMock).toHaveBeenCalled();
      });

      it('should initiate displaying of success callout', async () => {
        renderDuplicateMappingProfileRouteWithMocks();

        userEvent.click(saveAndCloseBtn());

        await waitFor(() => {
          expect(sendCalloutMock.mock.calls[0][0]).not.toHaveProperty('type');
          expect(sendCalloutMock.mock.calls[0][0].message.props.id).toBe('ui-data-export.mappingProfiles.create.successCallout');
          expect(handleSubmitNavigateMock).toHaveBeenCalled();
        });
      });
      it('should call submit callback with proper values', () => {
        renderDuplicateMappingProfileRouteWithMocks();

        userEvent.clear(nameField());
        userEvent.type(nameField(), 'Change name');
        userEvent.clear(descriptionField());
        userEvent.type(descriptionField(), 'Updated value');
        userEvent.click(saveAndCloseBtn());

        expect(nameField()).toHaveValue('Change name');
        expect(descriptionField()).toHaveValue('Updated value');
        expect(handleSubmitMock).toHaveBeenCalledWith(expect.objectContaining({
          name: 'Change name',
          description: 'Updated value',
        }));
      });
    });

    describe('submitting the form - error case', () => {
      beforeEach(() => {
        handleSubmitMock.mockImplementationOnce(() => Promise.reject());
      });

      it('should call submit callback', () => {
        renderDuplicateMappingProfileRouteWithMocks();

        userEvent.click(saveAndCloseBtn());

        expect(handleSubmitMock).toHaveBeenCalled();
      });

      it('should initiate displaying of error callout', async () => {
        renderDuplicateMappingProfileRouteWithMocks();

        userEvent.click(saveAndCloseBtn());

        await waitFor(() => {
          expect(sendCalloutMock).toBeCalledWith(
            expect.objectContaining({ type: 'error' })
          );
          expect(sendCalloutMock.mock.calls[0][0].message.props.id).toBe('ui-data-export.mappingProfiles.create.errorCallout');
        });
      });
    });
  });
});
Example #16
Source File: DiscussionsSettings.test.jsx    From frontend-app-course-authoring with GNU Affero General Public License v3.0 4 votes vote down vote up
describe('DiscussionsSettings', () => {
  beforeEach(() => {
    initializeMockApp({
      authenticatedUser: {
        userId: 3,
        username: 'abc123',
        administrator: true,
        roles: [],
      },
    });

    store = initializeStore({
      models: {
        courseDetails: {
          [courseId]: {
            start: Date(),
          },
        },
      },
    });
    axiosMock = new MockAdapter(getAuthenticatedHttpClient());

    // Leave the DiscussionsSettings route after the test.
    history.push(`/course/${courseId}/pages-and-resources`);
  });

  describe('with successful network connections', () => {
    beforeEach(() => {
      axiosMock.onGet(getDiscussionsProvidersUrl(courseId))
        .reply(200, generateProvidersApiResponse(false));
      axiosMock.onGet(getDiscussionsSettingsUrl(courseId))
        .reply(200, generatePiazzaApiResponse(true));
      renderComponent();
    });

    test('sets selection step from routes', async () => {
      history.push(`/course/${courseId}/pages-and-resources/discussion`);

      // This is an important line that ensures the spinner has been removed - and thus our main
      // content has been loaded - prior to proceeding with our expectations.
      await waitForElementToBeRemoved(screen.getByRole('status'));

      expect(queryByTestId(container, 'appList')).toBeInTheDocument();
      expect(queryByTestId(container, 'appConfigForm')).not.toBeInTheDocument();
    });

    test('sets settings step from routes', async () => {
      history.push(`/course/${courseId}/pages-and-resources/discussion/configure/piazza`);

      // This is an important line that ensures the spinner has been removed - and thus our main
      // content has been loaded - prior to proceeding with our expectations.
      await waitForElementToBeRemoved(screen.getByRole('status'));

      expect(queryByTestId(container, 'appList')).not.toBeInTheDocument();
      expect(queryByTestId(container, 'appConfigForm')).toBeInTheDocument();
    });

    test('successfully advances to settings step for lti', async () => {
      history.push(`/course/${courseId}/pages-and-resources/discussion`);

      // This is an important line that ensures the spinner has been removed - and thus our main
      // content has been loaded - prior to proceeding with our expectations.
      await waitForElementToBeRemoved(screen.getByRole('status'));

      userEvent.click(queryByLabelText(container, 'Select Piazza'));
      userEvent.click(queryByText(container, messages.nextButton.defaultMessage));

      await waitForElementToBeRemoved(screen.getByRole('status'));

      expect(queryByTestId(container, 'appList')).not.toBeInTheDocument();
      expect(queryByTestId(container, 'appConfigForm')).toBeInTheDocument();
      expect(queryByTestId(container, 'ltiConfigForm')).toBeInTheDocument();
      expect(queryByTestId(container, 'legacyConfigForm')).not.toBeInTheDocument();
    });

    test('successfully advances to settings step for legacy', async () => {
      axiosMock.onGet(getDiscussionsProvidersUrl(courseId)).reply(200, generateProvidersApiResponse(false, 'legacy'));
      axiosMock.onGet(getDiscussionsSettingsUrl(courseId)).reply(200, legacyApiResponse);
      renderComponent();
      history.push(`/course/${courseId}/pages-and-resources/discussion`);

      // This is an important line that ensures the spinner has been removed - and thus our main
      // content has been loaded - prior to proceeding with our expectations.
      await waitForElementToBeRemoved(screen.getByRole('status'));

      userEvent.click(queryByLabelText(container, 'Select edX'));
      userEvent.click(queryByText(container, messages.nextButton.defaultMessage));

      await waitForElementToBeRemoved(screen.getByRole('status'));

      expect(queryByTestId(container, 'appList')).not.toBeInTheDocument();
      expect(queryByTestId(container, 'appConfigForm')).toBeInTheDocument();
      expect(queryByTestId(container, 'ltiConfigForm')).not.toBeInTheDocument();
      expect(queryByTestId(container, 'legacyConfigForm')).toBeInTheDocument();
    });

    test('successfully goes back to first step', async () => {
      history.push(`/course/${courseId}/pages-and-resources/discussion/configure/piazza`);

      // This is an important line that ensures the spinner has been removed - and thus our main
      // content has been loaded - prior to proceeding with our expectations.
      await waitForElementToBeRemoved(screen.getByRole('status'));

      expect(queryByTestId(container, 'appConfigForm')).toBeInTheDocument();

      userEvent.click(queryByText(container, appMessages.backButton.defaultMessage));

      expect(queryByTestId(container, 'appList')).toBeInTheDocument();
      expect(queryByTestId(container, 'appConfigForm')).not.toBeInTheDocument();
    });

    test('successfully closes the modal', async () => {
      history.push(`/course/${courseId}/pages-and-resources/discussion`);

      // This is an important line that ensures the spinner has been removed - and thus our main
      // content has been loaded - prior to proceeding with our expectations.
      await waitForElementToBeRemoved(screen.getByRole('status'));

      expect(queryByTestId(container, 'appList')).toBeInTheDocument();

      userEvent.click(queryByLabelText(container, 'Close'));

      expect(queryByTestId(container, 'appList')).not.toBeInTheDocument();
      expect(queryByTestId(container, 'appConfigForm')).not.toBeInTheDocument();

      expect(window.location.pathname).toEqual(`/course/${courseId}/pages-and-resources`);
    });

    test('successfully submit the modal', async () => {
      history.push(`/course/${courseId}/pages-and-resources/discussion`);

      axiosMock.onPost(getDiscussionsSettingsUrl(courseId)).reply(200, generatePiazzaApiResponse(true));

      // This is an important line that ensures the spinner has been removed - and thus our main
      // content has been loaded - prior to proceeding with our expectations.
      await waitForElementToBeRemoved(screen.getByRole('status'));

      userEvent.click(queryByLabelText(container, 'Select Piazza'));

      userEvent.click(getByRole(container, 'button', { name: 'Next' }));

      userEvent.click(await findByRole(container, 'button', { name: 'Save' }));

      // This is an important line that ensures the Close button has been removed, which implies that
      // the full screen modal has been closed following our click of Apply.  Once this has happened,
      // then it's safe to proceed with our expectations.
      await waitForElementToBeRemoved(queryByRole(container, 'button', { name: 'Close' }));

      await waitFor(() => expect(window.location.pathname).toEqual(`/course/${courseId}/pages-and-resources`));
    });

    test('requires confirmation if changing provider', async () => {
      axiosMock.onGet(`${getConfig().LMS_BASE_URL}/api/courses/v1/courses/${courseId}?username=abc123`).reply(200, courseDetailResponse);
      await executeThunk(fetchCourseDetail(courseId), store.dispatch);
      history.push(`/course/${courseId}/pages-and-resources/discussion`);

      // This is an important line that ensures the spinner has been removed - and thus our main
      // content has been loaded - prior to proceeding with our expectations.
      await waitForElementToBeRemoved(screen.getByRole('status'));

      userEvent.click(getByRole(container, 'checkbox', { name: 'Select Discourse' }));
      userEvent.click(getByRole(container, 'button', { name: 'Next' }));

      await findByRole(container, 'button', { name: 'Save' });
      userEvent.type(getByRole(container, 'textbox', { name: 'Consumer Key' }), 'key');
      userEvent.type(getByRole(container, 'textbox', { name: 'Consumer Secret' }), 'secret');
      userEvent.type(getByRole(container, 'textbox', { name: 'Launch URL' }), 'http://example.test');
      userEvent.click(getByRole(container, 'button', { name: 'Save' }));

      await waitFor(() => expect(getByRole(container, 'dialog', { name: 'OK' })).toBeInTheDocument());
    });

    test('can cancel confirmation', async () => {
      axiosMock.onGet(`${getConfig().LMS_BASE_URL}/api/courses/v1/courses/${courseId}?username=abc123`).reply(200, courseDetailResponse);
      await executeThunk(fetchCourseDetail(courseId), store.dispatch);
      history.push(`/course/${courseId}/pages-and-resources/discussion`);

      // This is an important line that ensures the spinner has been removed - and thus our main
      // content has been loaded - prior to proceeding with our expectations.
      await waitForElementToBeRemoved(screen.getByRole('status'));

      const discourseBox = getByRole(container, 'checkbox', { name: 'Select Discourse' });
      expect(discourseBox).not.toBeDisabled();
      userEvent.click(discourseBox);

      userEvent.click(getByRole(container, 'button', { name: 'Next' }));
      await waitForElementToBeRemoved(screen.getByRole('status'));
      expect(getByRole(container, 'heading', { name: 'Discourse' })).toBeInTheDocument();

      userEvent.type(getByRole(container, 'textbox', { name: 'Consumer Key' }), 'a');
      userEvent.type(getByRole(container, 'textbox', { name: 'Consumer Secret' }), 'secret');
      userEvent.type(getByRole(container, 'textbox', { name: 'Launch URL' }), 'http://example.test');
      userEvent.click(getByRole(container, 'button', { name: 'Save' }));

      await waitFor(() => expect(getByRole(container, 'dialog', { name: 'OK' })).toBeInTheDocument());
      userEvent.click(getByRole(container, 'button', { name: 'Cancel' }));

      expect(queryByRole(container, 'dialog', { name: 'Confirm' })).not.toBeInTheDocument();
      expect(queryByRole(container, 'dialog', { name: 'Configure discussion' }));
    });
  });

  describe('with network error fetchProviders API requests', () => {
    beforeEach(() => {
      // Expedient way of getting SUPPORT_URL into config.
      setConfig({
        ...getConfig(),
        SUPPORT_URL: 'http://support.edx.org',
      });

      axiosMock.onGet(getDiscussionsProvidersUrl(courseId)).networkError();
      axiosMock.onGet(getDiscussionsSettingsUrl(courseId)).networkError();
      renderComponent();
    });

    test('shows connection error alert', async () => {
      history.push(`/course/${courseId}/pages-and-resources/discussion`);

      // This is an important line that ensures the spinner has been removed - and thus our main
      // content has been loaded - prior to proceeding with our expectations.
      await waitForElementToBeRemoved(screen.getByRole('status'));

      const alert = queryByRole(container, 'alert');
      expect(alert).toBeInTheDocument();
      expect(alert.textContent).toEqual(expect.stringContaining('We encountered a technical error when loading this page.'));
      expect(alert.innerHTML).toEqual(expect.stringContaining(getConfig().SUPPORT_URL));
    });
  });

  describe('with network error postAppConfig API requests', () => {
    beforeEach(() => {
      // Expedient way of getting SUPPORT_URL into config.
      setConfig({
        ...getConfig(),
        SUPPORT_URL: 'http://support.edx.org',
      });

      axiosMock.onGet(getDiscussionsProvidersUrl(courseId))
        .reply(200, generateProvidersApiResponse());
      axiosMock.onGet(getDiscussionsSettingsUrl(courseId))
        .reply(200, piazzaApiResponse);
      axiosMock.onPost(getDiscussionsSettingsUrl(courseId)).networkError();
      renderComponent();
    });

    test('shows connection error alert at top of form', async () => {
      history.push(`/course/${courseId}/pages-and-resources/discussion/configure/piazza`);

      // This is an important line that ensures the spinner has been removed - and thus our main
      // content has been loaded - prior to proceeding with our expectations.
      await waitForElementToBeRemoved(screen.getByRole('status'));

      // Apply causes an async action to take place
      act(() => {
        userEvent.click(queryByText(container, appMessages.saveButton.defaultMessage));
      });

      await waitFor(() => expect(axiosMock.history.post.length).toBe(1));

      expect(queryByTestId(container, 'appConfigForm')).toBeInTheDocument();
      const alert = await findByRole(container, 'alert');
      expect(alert).toBeInTheDocument();
      expect(alert.textContent).toEqual(expect.stringContaining('We encountered a technical error when applying changes.'));
      expect(alert.innerHTML).toEqual(expect.stringContaining(getConfig().SUPPORT_URL));
    });
  });

  describe('with permission denied error for fetchProviders API requests', () => {
    beforeEach(() => {
      axiosMock.onGet(getDiscussionsProvidersUrl(courseId)).reply(403);
      axiosMock.onGet(getDiscussionsSettingsUrl(courseId)).reply(403);

      renderComponent();
    });

    test('shows permission denied alert', async () => {
      history.push(`/course/${courseId}/pages-and-resources/discussion`);

      // This is an important line that ensures the spinner has been removed - and thus our main
      // content has been loaded - prior to proceeding with our expectations.
      await waitForElementToBeRemoved(screen.getByRole('status'));

      const alert = queryByRole(container, 'alert');
      expect(alert).toBeInTheDocument();
      expect(alert.textContent).toEqual(expect.stringContaining('You are not authorized to view this page.'));
    });
  });

  describe('with permission denied error for postAppConfig API requests', () => {
    beforeEach(() => {
      axiosMock.onGet(getDiscussionsProvidersUrl(courseId))
        .reply(200, generateProvidersApiResponse());
      axiosMock.onGet(getDiscussionsSettingsUrl(courseId)).reply(200, piazzaApiResponse);
      axiosMock.onPost(getDiscussionsSettingsUrl(courseId)).reply(403);

      renderComponent();
    });

    test('shows permission denied alert at top of form', async () => {
      history.push(`/course/${courseId}/pages-and-resources/discussion/configure/piazza`);

      // This is an important line that ensures the spinner has been removed - and thus our main
      // content has been loaded - prior to proceeding with our expectations.
      await waitForElementToBeRemoved(screen.getByRole('status'));

      userEvent.click(getByRole(container, 'button', { name: 'Save' }));

      await waitFor(() => expect(axiosMock.history.post.length).toBe(1));

      expect(queryByTestId(container, 'appList')).not.toBeInTheDocument();
      expect(queryByTestId(container, 'appConfigForm')).not.toBeInTheDocument();

      // We don't technically leave the route in this case, though the modal is hidden.
      expect(window.location.pathname).toEqual(`/course/${courseId}/pages-and-resources/discussion/configure/piazza`);

      const alert = await findByRole(container, 'alert');
      expect(alert).toBeInTheDocument();
      expect(alert.textContent).toEqual(expect.stringContaining('You are not authorized to view this page.'));
    });
  });
});
Example #17
Source File: JobProfileDetails.test.js    From ui-data-export with Apache License 2.0 4 votes vote down vote up
describe('JobProfileDetails', () => {
  const stripes = {
    connect: Component => props => (
      <Component
        {... props}
        mutator={{}}
        resources={{}}
      />
    ),
  };
  const renderJobProfileDetails = () => {
    renderWithIntl(
      <SettingsComponentBuilder>
        <JobProfileDetails
          stripes={stripes}
          jobProfile={jobProfile}
          mappingProfile={mappingProfile}
          isDefaultProfile
          isProfileUsed
          onCancel={noop}
          onDelete={noop}
        />
      </SettingsComponentBuilder>,
      translationsProperties
    );
  };

  it('should display job profile details', () => {
    renderJobProfileDetails();

    const dialog = screen.getByRole('dialog');

    expect(dialog).toBeVisible();

    const headings = within(dialog).getAllByRole('heading', { name: jobProfile.name });

    headings.forEach(heading => expect(heading).toBeVisible());

    const summary = within(dialog).getByRole('region', { name: /summary/i });

    const labelsAndValues = [
      'Record created: 12/4/2018 11:22 AM',
      'Record last updated: 12/4/2018 1:28 PM',
      commonTranslations.name,
      jobProfile.name,
      translations.description,
      jobProfile.description,
      translations.mappingProfile,
      mappingProfile.name,
    ];

    labelsAndValues.forEach(el => expect(within(summary).getByText(el)).toBeVisible());
  });

  it('should display action buttons in the proper state', () => {
    renderJobProfileDetails();
    const actionButton = screen.getByText('Actions');

    userEvent.click(actionButton);

    const deleteButton = screen.getByText(commonTranslations.delete);
    const duplicateButton = screen.getByText(commonTranslations.duplicate);
    const editButton = screen.getByText(commonTranslations.edit);

    expect(deleteButton).toBeEnabled();
    expect(duplicateButton).toBeEnabled();
    expect(editButton).toBeEnabled();
  });

  describe('rendering details without description for a job profile which is not already in use', () => {
    const renderJobProfileWitoutDescription = () => {
      renderWithIntl(
        <SettingsComponentBuilder>
          <JobProfileDetails
            stripes={stripes}
            jobProfile={{
              ...jobProfile,
              description: null,
            }}
            mappingProfile={mappingProfile}
            isDefaultProfile={false}
            isProfileUsed={false}
            onCancel={noop}
          />
        </SettingsComponentBuilder>,
        translationsProperties
      );
    };

    it('should display no value in description', () => {
      renderJobProfileWitoutDescription();
      const description = document.querySelector('[data-test-job-profile-description]');

      expect(within(description).getByText('-')).toBeVisible();
    });

    it('should display action buttons in the proper state', () => {
      renderJobProfileWitoutDescription();
      const actionButton = screen.getByText('Actions');

      userEvent.click(actionButton);

      const deleteButton = screen.getByText(commonTranslations.delete);
      const duplicateButton = screen.getByText(commonTranslations.duplicate);
      const editButton = screen.getByText(commonTranslations.edit);

      expect(deleteButton).toBeEnabled();
      expect(duplicateButton).toBeEnabled();
      expect(editButton).toBeEnabled();
    });

    describe('clicking on delete profiles button', () => {
      it('should display delete confirmation modal', async () => {
        renderJobProfileWitoutDescription();
        const actionButton = screen.getByText('Actions');

        userEvent.click(actionButton);

        const deleteButton = screen.getByText(commonTranslations.delete);

        userEvent.click(deleteButton);

        const modal = screen.getAllByRole('dialog').find(dialog => within(dialog).getByRole('heading', { name: /delete/i }));

        expect(modal).toBeVisible();
        userEvent.click(within(modal).getByRole('button', { name: /cancel/i }));

        await waitForElementToBeRemoved(modal);
      });
    });
  });

  describe('rendering job profile details in loading state', () => {
    const renderJobProfileWithLoading = () => {
      renderWithIntl(
        <SettingsComponentBuilder>
          <JobProfileDetails
            stripes={stripes}
            isLoading
            isDefaultProfile={false}
            isProfileUsed
            onCancel={noop}
          />
        </SettingsComponentBuilder>,
        translationsProperties
      );
    };

    it('should display preloader', () => {
      renderJobProfileWithLoading();
      expect(document.querySelector('[data-test-preloader]')).toBeVisible();
    });
  });
});
Example #18
Source File: ChooseJobProfilePage.test.js    From ui-data-export with Apache License 2.0 4 votes vote down vote up
describe('ChooseJobProfile', () => {
  describe('rendering ChooseJobProfile', () => {
    const exportProfileSpy = jest.fn(Promise.resolve.bind(Promise));
    const pushHistorySpy = jest.fn();
    const mutator = buildMutator({ export: { POST: exportProfileSpy } });
    const location = { state: { fileDefinitionId: 'fileDefinitionId' } };
    let renderResult;

    beforeEach(() => {
      renderResult = renderWithIntl(
        <Router>
          <ChooseJobProfile
            resources={resources}
            mutator={mutator}
            history={{ push: pushHistorySpy }}
            location={location}
          />
        </Router>,
        translationsProperties
      );
    });

    afterEach(() => {
      jest.clearAllMocks();
    });

    it('should be visible', () => {
      const { container } = renderResult;

      expect(container.querySelector('#pane-results')).toBeVisible();
    });

    it('should display correct title and subtitle', () => {
      const { container } = renderResult;
      const paneHeader = container.querySelector('[data-test-pane-header]');

      expect(getByText(paneHeader, 'Select job profile to run the export')).toBeVisible();
      expect(getByText(paneHeader, '2 job profiles')).toBeVisible();
    });

    it('should place headers in correct order', () => {
      const { container } = renderResult;
      const headers = container.querySelectorAll('#search-results-list .mclHeader');

      expect(getByText(headers[0], 'Name')).toBeVisible();
      expect(getByText(headers[1], 'Description')).toBeVisible();
      expect(getByText(headers[2], 'Updated')).toBeVisible();
      expect(getByText(headers[3], 'Updated by')).toBeVisible();
    });

    it('should not display the confirmation modal', () => {
      const { container } = renderResult;
      const modal = container.querySelector('#choose-job-profile-confirmation-modal');

      expect(modal).not.toBeInTheDocument();
    });

    it('should display correct data for the first row', () => {
      const { container } = renderResult;
      const row = container.querySelector('.mclRow');
      const cells = row.querySelectorAll('.mclCell');

      expect(getByText(cells[0], 'A Lorem ipsum 1')).toBeVisible();
      expect(getByText(cells[1], 'Description 1')).toBeVisible();
      expect(getByText(cells[2], '12/4/2018')).toBeVisible();
      expect(getByText(cells[3], 'Donald S')).toBeVisible();
    });

    describe('clicking on row', () => {
      beforeEach(() => {
        const { container } = renderResult;
        const row = container.querySelector('.mclRow');

        userEvent.click(row);
      });

      it('should display modal with proper header', () => {
        expect(screen.getByText('Are you sure you want to run this job?')).toBeInTheDocument();
      });

      it('should display modal profile name in the body', () => {
        expect(screen.getByTestId('choose-job-select')).toBeVisible();
      });

      it('should display modal with proper wording for buttons', () => {
        const modal = document.querySelector('#choose-job-profile-confirmation-modal');

        expect(getByRole(modal, 'button', { name: 'Run' })).toBeVisible();
        expect(getByRole(modal, 'button', { name: 'Cancel' })).toBeVisible();
      });

      it('clicking on cancel button should close the modal', () => {
        const modal = document.querySelector('#choose-job-profile-confirmation-modal');

        userEvent.click(getByRole(modal, 'button', { name: 'Cancel' }));

        return waitForElementToBeRemoved(() => document.querySelector('#choose-job-profile-confirmation-modal'));
      });

      describe('clicking on confirm button - success case', () => {
        beforeEach(async () => {
          const modal = document.querySelector('#choose-job-profile-confirmation-modal');

          await userEvent.selectOptions(getByTestId(modal, 'choose-job-select'), 'Instances');

          userEvent.click(getByRole(modal, 'button', { name: 'Run' }));
        });

        it('should navigate to the landing page', () => {
          expect(pushHistorySpy).toHaveBeenCalledWith('/data-export');
        });
      });

      describe('clicking on confirm button - error case', () => {
        beforeEach(async () => {
          const modal = document.querySelector('#choose-job-profile-confirmation-modal');

          exportProfileSpy.mockImplementationOnce(Promise.reject.bind(Promise));

          await userEvent.selectOptions(getByTestId(modal, 'choose-job-select'), 'Instances');

          userEvent.click(getByRole(modal, 'button', { name: 'Run' }));
          await waitForElementToBeRemoved(() => document.querySelector('#choose-job-profile-confirmation-modal'));
        });

        it('should not navigate to the landing page', () => {
          expect(pushHistorySpy).not.toHaveBeenCalled();
        });
      });
    });
  });
});
Example #19
Source File: OpenedXConfigForm.test.jsx    From frontend-app-course-authoring with GNU Affero General Public License v3.0 4 votes vote down vote up
describe('OpenedXConfigForm', () => {
  let axiosMock;
  let store;
  let container;

  beforeEach(async () => {
    initializeMockApp({
      authenticatedUser: {
        userId: 3,
        username: 'abc123',
        administrator: true,
        roles: [],
      },
    });
    axiosMock = new MockAdapter(getAuthenticatedHttpClient());
    store = initializeStore();
  });

  afterEach(() => {
    axiosMock.reset();
  });

  const createComponent = (onSubmit = jest.fn(), formRef = createRef(), legacy = true) => {
    const wrapper = render(
      <AppProvider store={store}>
        <IntlProvider locale="en">
          <OpenedXConfigForm
            onSubmit={onSubmit}
            formRef={formRef}
            legacy={legacy}
          />
        </IntlProvider>
      </AppProvider>,
    );
    container = wrapper.container;
    return container;
  };

  const mockStore = async (mockResponse) => {
    axiosMock.onGet(getDiscussionsProvidersUrl(courseId)).reply(200, generateProvidersApiResponse());
    axiosMock.onGet(getDiscussionsSettingsUrl(courseId)).reply(200, mockResponse);
    await executeThunk(fetchProviders(courseId), store.dispatch);
    await executeThunk(fetchDiscussionSettings(courseId), store.dispatch);
    store.dispatch(selectApp({ appId: 'legacy' }));
  };

  test('title rendering', async () => {
    await mockStore(legacyApiResponse);
    createComponent();
    expect(container.querySelector('h3')).toHaveTextContent('edX');
  });

  test('new Open edX provider config', async () => {
    await mockStore({ ...legacyApiResponse, enable_in_context: true });
    createComponent(jest.fn(), createRef(), false);
    expect(queryByText(container, messages.visibilityInContext.defaultMessage)).toBeInTheDocument();
    expect(queryByText(container, messages.gradedUnitPagesLabel.defaultMessage)).toBeInTheDocument();
    expect(queryByText(container, messages.groupInContextSubsectionLabel.defaultMessage)).toBeInTheDocument();
    expect(queryByText(container, messages.allowUnitLevelVisibilityLabel.defaultMessage)).toBeInTheDocument();
  });

  test('calls onSubmit when the formRef is submitted', async () => {
    const formRef = createRef();
    const handleSubmit = jest.fn();

    await mockStore(legacyApiResponse);
    createComponent(handleSubmit, formRef);

    await act(async () => {
      formRef.current.submit();
    });

    expect(handleSubmit).toHaveBeenCalledWith(
      // Because we use defaultAppConfig as the initialValues of the form, and we haven't changed
      // any of the form inputs, this exact object shape is returned back to us, so we're reusing
      // it here.  It's not supposed to be 'the same object', it just happens to be.
      {
        ...defaultAppConfig(),
        divideByCohorts: false,
        divisionScheme: DivisionSchemes.COHORT,
      },
    );
  });

  test('default field states are correct, including removal of folded sub-fields', async () => {
    await mockStore({
      ...legacyApiResponse,
      plugin_configuration: {
         ...legacyApiResponse.plugin_configuration,
        reported_content_email_notifications_flag: true,
        divided_course_wide_discussions: [],
      },
    });
    createComponent();
    const { divideDiscussionIds } = defaultAppConfig(['13f106c6-6735-4e84-b097-0456cff55960', 'course']);

    // DivisionByGroupFields
    expect(container.querySelector('#divideByCohorts')).toBeInTheDocument();
    expect(container.querySelector('#divideByCohorts')).not.toBeChecked();
    expect(container.querySelector('#divideCourseTopicsByCohorts')).not.toBeInTheDocument();

    divideDiscussionIds.forEach(id => expect(
      container.querySelector(`#checkbox-${id}`),
    ).not.toBeInTheDocument());

    // AnonymousPostingFields
    expect(
      container.querySelector('#allowAnonymousPostsPeers'),
    ).toBeInTheDocument();
    expect(container.querySelector('#allowAnonymousPostsPeers')).not.toBeChecked();

    // ReportedContentEmailNotifications
    expect(container.querySelector('#reportedContentEmailNotifications')).toBeInTheDocument();
    expect(container.querySelector('#reportedContentEmailNotifications')).not.toBeChecked();

    // BlackoutDatesField
    expect(queryByText(container, messages.blackoutDatesLabel.defaultMessage)).toBeInTheDocument();
  });

  test('folded sub-fields are in the DOM when parents are enabled', async () => {
    await mockStore({
      ...legacyApiResponse,
      plugin_configuration: {
        ...legacyApiResponse.plugin_configuration,
        allow_anonymous: true,
        reported_content_email_notifications: true,
        reported_content_email_notifications_flag: true,
        always_divide_inline_discussions: true,
        divided_course_wide_discussions: [],
      },
    });
    createComponent();
    const { divideDiscussionIds } = defaultAppConfig(['13f106c6-6735-4e84-b097-0456cff55960', 'course']);

    // DivisionByGroupFields
    expect(container.querySelector('#divideByCohorts')).toBeInTheDocument();
    expect(container.querySelector('#divideByCohorts')).toBeChecked();
    expect(
      container.querySelector('#divideCourseTopicsByCohorts'),
    ).toBeInTheDocument();
    expect(
      container.querySelector('#divideCourseTopicsByCohorts'),
    ).not.toBeChecked();

    divideDiscussionIds.forEach(id => expect(
      container.querySelector(`#checkbox-${id}`),
    ).not.toBeInTheDocument());

    // AnonymousPostingFields
    expect(
      container.querySelector('#allowAnonymousPostsPeers'),
    ).toBeInTheDocument();
    expect(
      container.querySelector('#allowAnonymousPostsPeers'),
    ).not.toBeChecked();

    // ReportedContentEmailNotifications
    expect(container.querySelector('#reportedContentEmailNotifications')).toBeInTheDocument();
    expect(container.querySelector('#reportedContentEmailNotifications')).toBeChecked();
  });

  test('folded discussion topics are in the DOM when divideByCohorts and divideCourseWideTopics are enabled',
    async () => {
      await mockStore({
        ...legacyApiResponse,
        plugin_configuration: {
          ...legacyApiResponse.plugin_configuration,
          allow_anonymous: true,
          reported_content_email_notifications: true,
          reported_content_email_notifications_flag: true,
          always_divide_inline_discussions: true,
          divided_course_wide_discussions: ['13f106c6-6735-4e84-b097-0456cff55960', 'course'],
        },
      });
      createComponent();
      const { divideDiscussionIds } = defaultAppConfig(['13f106c6-6735-4e84-b097-0456cff55960', 'course']);

      // DivisionByGroupFields
      expect(container.querySelector('#divideByCohorts')).toBeInTheDocument();
      expect(container.querySelector('#divideByCohorts')).toBeChecked();
      expect(container.querySelector('#divideCourseTopicsByCohorts')).toBeInTheDocument();
      expect(container.querySelector('#divideCourseTopicsByCohorts')).toBeChecked();

      divideDiscussionIds.forEach(id => {
        expect(container.querySelector(`#checkbox-${id}`)).toBeInTheDocument();
        expect(container.querySelector(`#checkbox-${id}`)).toBeChecked();
      });
    });

  const updateTopicName = async (topicId, topicName) => {
    const topicCard = queryByTestId(container, topicId);

    await act(async () => { userEvent.click(queryByLabelText(topicCard, 'Expand')); });
    const topicInput = topicCard.querySelector('input');
    topicInput.focus();
    await act(async () => { fireEvent.change(topicInput, { target: { value: topicName } }); });
    topicInput.blur();

    return topicCard;
  };

  const assertTopicNameRequiredValidation = (topicCard, expectExists = true) => {
    const error = queryByText(topicCard, messages.discussionTopicRequired.defaultMessage);
    if (expectExists) { expect(error).toBeInTheDocument(); } else { expect(error).not.toBeInTheDocument(); }
  };

  const assertDuplicateTopicNameValidation = async (topicCard, expectExists = true) => {
    const error = queryByText(topicCard, messages.discussionTopicNameAlreadyExist.defaultMessage);
    if (expectExists) { expect(error).toBeInTheDocument(); } else { expect(error).not.toBeInTheDocument(); }
  };

  const assertHasErrorValidation = (expectExists = true) => {
    expect(store.getState().discussions.hasValidationError).toBe(expectExists);
  };

  test('show required error on field when leaving empty topic name',
    async () => {
      await mockStore(legacyApiResponse);
      createComponent();

      const topicCard = await updateTopicName('13f106c6-6735-4e84-b097-0456cff55960', '');
      await waitForElementToBeRemoved(queryByText(topicCard, messages.addTopicHelpText.defaultMessage));
      assertTopicNameRequiredValidation(topicCard);
      assertHasErrorValidation();
    });

  test('check field is not collapsible in case of error', async () => {
    await mockStore(legacyApiResponse);
    createComponent();

    const topicCard = await updateTopicName('13f106c6-6735-4e84-b097-0456cff55960', '');
    const collapseButton = queryByLabelText(topicCard, 'Collapse');
    await act(async () => userEvent.click(collapseButton));

    expect(collapseButton).toBeInTheDocument();
  });

  describe('Duplicate validation test cases', () => {
    let topicCard;
    let duplicateTopicCard;

    beforeEach(async () => {
      await mockStore(legacyApiResponse);
      createComponent();

      topicCard = await updateTopicName('course', 'edx');
      duplicateTopicCard = await updateTopicName('13f106c6-6735-4e84-b097-0456cff55960', 'EDX');
    });

    test('show duplicate errors on fields when passing duplicate topic name', async () => {
      await assertDuplicateTopicNameValidation(topicCard);
      await assertDuplicateTopicNameValidation(duplicateTopicCard);
      assertHasErrorValidation();
    });

    test('check duplicate error is removed on fields when name is fixed', async () => {
      const duplicateTopicInput = duplicateTopicCard.querySelector('input');
      duplicateTopicInput.focus();
      await act(async () => { userEvent.type(duplicateTopicInput, 'valid'); });
      duplicateTopicInput.blur();

      await waitForElementToBeRemoved(
        queryAllByText(topicCard, messages.discussionTopicNameAlreadyExist.defaultMessage),
      );

      await assertDuplicateTopicNameValidation(duplicateTopicCard, false);
      await assertDuplicateTopicNameValidation(topicCard, false);
      assertHasErrorValidation(false);
    });

    test('check duplicate error is removed on deleting duplicate topic', async () => {
      await act(async () => {
        userEvent.click(
          queryByLabelText(duplicateTopicCard, messages.deleteAltText.defaultMessage, { selector: 'button' }),
        );
      });

      await act(async () => {
        userEvent.click(
          queryByRole(container, 'button', { name: messages.deleteButton.defaultMessage }),
        );
      });

      await waitForElementToBeRemoved(queryByText(topicCard, messages.discussionTopicNameAlreadyExist.defaultMessage));

      expect(duplicateTopicCard).not.toBeInTheDocument();
      await assertDuplicateTopicNameValidation(topicCard, false);
      assertHasErrorValidation(false);
    });
  });
});
Example #20
Source File: species.test.js    From treetracker-admin-client with GNU Affero General Public License v3.0 4 votes vote down vote up
describe('species management', () => {
  let api;
  let speciesValues;
  // can be used to test routes and permissions

  beforeEach(() => {
    api = require('../../api/treeTrackerApi').default;

    api.getSpecies = jest.fn(() => {
      // log.debug('mock getSpecies:');
      return Promise.resolve(SPECIES);
    });

    api.createSpecies = jest.fn(() => {
      // log.debug('mock createSpecies');
      return Promise.resolve({
        id: 2,
        name: 'water melon',
        desc: 'fruit',
      });
    });

    api.getCaptureCountPerSpecies = jest.fn(() => {
      return Promise.resolve({ count: (Math.random() * 10) >> 0 });
    });

    speciesValues = {
      speciesList: SPECIES,
      speciesInput: '',
      speciesDesc: '',
      setSpeciesInput: () => {},
      loadSpeciesList: () => {},
      onChange: () => {},
      isNewSpecies: () => {},
      createSpecies: () => {},
      getSpeciesId: () => {},
      editSpecies: () => {},
      deleteSpecies: () => {},
      combineSpecies: () => {},
    };
  });

  afterEach(cleanup);

  describe('<SpeciesView /> renders page', () => {
    beforeEach(async () => {
      render(
        <BrowserRouter>
          <AppProvider>
            <SpeciesProvider value={speciesValues}>
              <SpeciesView />
            </SpeciesProvider>
          </AppProvider>
        </BrowserRouter>,
      );
      await act(() => api.getSpecies());
    });

    afterEach(cleanup);

    describe('it shows main page elements', () => {
      it('then shows "Add New Species" button', () => {
        expect(screen.getByText(/Add New Species/i)).toBeTruthy();
      });

      it('then shows "Combine Species" button', () => {
        expect(screen.getByText(/Combine Species/i)).toBeTruthy();
      });

      it('species list should be 2', () => {
        expect(speciesValues.speciesList).toHaveLength(3);
      });

      // shows a table with headers
      // shows navigation menu
    });

    describe('when the "Add New Species" button is clicked', () => {
      beforeEach(() => {
        userEvent.click(screen.getByText(/Add New Species/i));
      });

      it('see popup with species detail form', () => {
        expect(screen.getByText(/Species Detail/i)).toBeTruthy();
      });

      it('has inputs for name and description', () => {
        const dialog = screen.getByRole(/dialog/i);
        const item = within(dialog).getByLabelText(/name/i);
        expect(item).toBeTruthy();
      });

      it('has buttons to save and cancel', () => {
        const dialog = screen.getByRole(/dialog/i);
        expect(within(dialog).getByText(/save/i)).toBeTruthy();
      });
    });

    // [TODO]: MORE TESTS
    // when the combine species button is clicked
    //it fails if two species aren't selected
    //opens if 2+ species are selected
    //shows those species names in the popup
    //has inputs for name and description
    //has buttons to save and cancel

    describe('when creating a new species', () => {
      beforeEach(async () => {
        // await api.createSpecies({ name: 'water melon' });
        userEvent.click(screen.getByText(/Add New Species/i));
        const dialog = screen.getByRole(/dialog/i);
        const inputName = screen.getByLabelText(/name/i);
        const inputDesc = screen.getByLabelText(/description/i);
        const saveBtn = screen.getByText(/Save/i);

        userEvent.type(inputName, 'water melon');
        expect(inputName.value).toBe('water melon');

        userEvent.type(inputDesc, 'test');
        expect(inputDesc.value).toBe('test');

        expect(screen.getByDisplayValue('water melon')).toBeTruthy();
        expect(screen.getByDisplayValue('test')).toBeTruthy();

        userEvent.click(saveBtn);

        // mock-adding new species --- DOESN'T UPDATE state
        // speciesValues.speciesList.push({
        //   id: 2,
        //   name: 'water melon',
        //   desc: 'fruit',
        // });

        // wait for it... to complete & dialog to close
        waitForElementToBeRemoved(dialog);
        //---- the last 2 tests work w/o this line but get act() errors in the console.
        //---- the errors go away w/this line but then tests fail
      });

      afterEach(cleanup);

      it('api.createSpecies should be called with "water melon"', () => {
        expect(api.createSpecies.mock.calls[0][0].name).toBe('water melon');
      });

      // it('species list should be 3 (1 added)', () => {
      //   expect(speciesValues.speciesList).toHaveLength(3);
      // });

      // it('has 3 species', () => {
      //   const items = screen.getAllByTestId('species');
      //   // screen.logTestingPlaygroundURL();
      //   const speciesNames = items.map((el) => el.textContent);
      //   // console.log('speciesNames', speciesNames);
      //   expect(items).toHaveLength(3);
      // });

      // it('added species should show on the screen', () => {
      //   expect(screen.getByText('water melon')).toBeTruthy();
      // });
    });
  });
});
Example #21
Source File: Settings.test.jsx    From frontend-app-course-authoring with GNU Affero General Public License v3.0 4 votes vote down vote up
describe('LiveSettings', () => {
  beforeEach(async () => {
    initializeMockApp({
      authenticatedUser: {
        userId: 3,
        username: 'abc123',
        administrator: false,
        roles: [],
      },
    });
    store = initializeStore(initialState);
    axiosMock = new MockAdapter(getAuthenticatedHttpClient());
    history.push(liveSettingsUrl);
  });

  test('Live Configuration modal is visible', async () => {
    renderComponent();
    expect(queryByRole(container, 'dialog')).toBeVisible();
  });

  test('Displays "Configure Live" heading', async () => {
    renderComponent();
    const headingElement = queryByTestId(container, 'modal-title');
    expect(headingElement).toHaveTextContent(messages.heading.defaultMessage);
  });

  test('Displays title, helper text and badge when live configuration button is enabled', async () => {
    await mockStore({ enabled: true });
    renderComponent();

    const label = container.querySelector('label[for="enable-live-toggle"]');
    const helperText = container.querySelector('#enable-live-toggleHelpText');
    const enableBadge = queryByTestId(container, 'enable-badge');

    expect(label).toHaveTextContent(messages.enableLiveLabel.defaultMessage);
    expect(enableBadge).toHaveTextContent('Enabled');
    expect(helperText).toHaveTextContent(messages.enableLiveHelp.defaultMessage);
  });

  test('Displays title, helper text and hides badge when live configuration button is disabled', async () => {
    await mockStore({ enabled: false, piiSharingAllowed: false });
    renderComponent();

    const label = container.querySelector('label[for="enable-live-toggle"]');
    const helperText = container.querySelector('#enable-live-toggleHelpText');

    expect(label).toHaveTextContent('Live');
    expect(label.firstChild).not.toHaveTextContent('Enabled');
    expect(helperText).toHaveTextContent(messages.enableLiveHelp.defaultMessage);
  });

  test('Displays provider heading, helper text and all providers', async () => {
    await mockStore({
      enabled: true,
      piiSharingAllowed: true,
      usernameSharing: false,
      emailSharing: true,
    });
    renderComponent();

    const spinner = getByRole(container, 'status');
    await waitForElementToBeRemoved(spinner);
    const providers = queryByRole(container, 'group');
    const helperText = queryByTestId(container, 'helper-text');

    expect(providers.childElementCount).toBe(2);
    expect(providers).toHaveTextContent('Zoom');
    expect(providers).toHaveTextContent('BigBlueButton');
    expect(helperText).toHaveTextContent(
      messages.providerHelperText.defaultMessage.replace('{providerName}', 'Zoom'),
    );
  });

  test('Unable to save error should be shown on submission if a field is empty', async () => {
    const apiDefaultResponse = generateLiveConfigurationApiResponse(true, true);
    apiDefaultResponse.lti_configuration.lti_1p1_client_key = '';
    await mockStore({ emailSharing: false, piiSharingAllowed: false });
    renderComponent();

    const spinner = getByRole(container, 'status');
    await waitForElementToBeRemoved(spinner);

    const saveButton = queryByText(container, 'Save');

    await waitFor(async () => {
      await act(async () => fireEvent.click(saveButton));
      expect(queryByRole(container, 'alert')).toBeVisible();
    });
  });
});
Example #22
Source File: ZoomSettings.test.jsx    From frontend-app-course-authoring with GNU Affero General Public License v3.0 4 votes vote down vote up
describe('Zoom Settings', () => {
  beforeEach(async () => {
    initializeMockApp({
      authenticatedUser: {
        userId: 3,
        username: 'abc123',
        administrator: false,
        roles: [],
      },
    });
    store = initializeStore(initialState);
    axiosMock = new MockAdapter(getAuthenticatedHttpClient());
    history.push(liveSettingsUrl);
  });

  test('LTI fields are visible when pii sharing is enabled and email or username sharing required', async () => {
    await mockStore({ emailSharing: true });
    renderComponent();

    const spinner = getByRole(container, 'status');
    await waitForElementToBeRemoved(spinner);

    const consumerKey = container.querySelector('input[name="consumerKey"]').parentElement;
    const consumerSecret = container.querySelector('input[name="consumerSecret"]').parentElement;
    const launchUrl = container.querySelector('input[name="launchUrl"]').parentElement;
    const launchEmail = container.querySelector('input[name="launchEmail"]').parentElement;

    expect(consumerKey.firstChild).toBeVisible();
    expect(consumerKey.lastChild).toHaveTextContent(messages.consumerKey.defaultMessage);
    expect(consumerSecret.firstChild).toBeVisible();
    expect(consumerSecret.lastChild).toHaveTextContent(messages.consumerSecret.defaultMessage);
    expect(launchUrl.firstChild).toBeVisible();
    expect(launchUrl.lastChild).toHaveTextContent(messages.launchUrl.defaultMessage);
    expect(launchEmail.firstChild).toBeVisible();
    expect(launchEmail.lastChild).toHaveTextContent(messages.launchEmail.defaultMessage);
  });

  test(
    'Only connect to support message is visible when pii sharing is disabled and email or username sharing is required',
    async () => {
      await mockStore({ emailSharing: true, piiSharingAllowed: false });
      renderComponent();

      const spinner = getByRole(container, 'status');
      await waitForElementToBeRemoved(spinner);

      const requestPiiText = queryByTestId(container, 'request-pii-sharing');
      const consumerKey = container.querySelector('input[name="consumerKey"]');
      const consumerSecret = container.querySelector('input[name="consumerSecret"]');
      const launchUrl = container.querySelector('input[name="launchUrl"]');
      const launchEmail = container.querySelector('input[name="launchEmail"]');

      expect(requestPiiText).toHaveTextContent(
        messages.requestPiiSharingEnable.defaultMessage.replaceAll('{provider}', 'Zoom'),
      );
      expect(consumerKey).not.toBeInTheDocument();
      expect(consumerSecret).not.toBeInTheDocument();
      expect(launchUrl).not.toBeInTheDocument();
      expect(launchEmail).not.toBeInTheDocument();
    },
  );

  test('Provider Configuration should be displayed correctly', async () => {
    const apiDefaultResponse = generateLiveConfigurationApiResponse(true, true);
    await mockStore({ emailSharing: false, piiSharingAllowed: false });
    renderComponent();

    const spinner = getByRole(container, 'status');
    await waitForElementToBeRemoved(spinner);

    const consumerKey = container.querySelector('input[name="consumerKey"]');
    const consumerSecret = container.querySelector('input[name="consumerSecret"]');
    const launchUrl = container.querySelector('input[name="launchUrl"]');
    const launchEmail = container.querySelector('input[name="launchEmail"]');

    expect(consumerKey.value).toBe(apiDefaultResponse.lti_configuration.lti_1p1_client_key);
    expect(consumerSecret.value).toBe(apiDefaultResponse.lti_configuration.lti_1p1_client_secret);
    expect(launchUrl.value).toBe(apiDefaultResponse.lti_configuration.lti_1p1_launch_url);
    expect(launchEmail.value).toBe(
      apiDefaultResponse.lti_configuration.lti_config.additional_parameters.custom_instructor_email,
    );
  });
});
Example #23
Source File: ProctoredExamSettings.test.jsx    From frontend-app-course-authoring with GNU Affero General Public License v3.0 4 votes vote down vote up
describe('ProctoredExamSettings', () => {
  afterEach(() => {
    cleanup();
  });

  describe('Field dependencies', () => {
    beforeEach(async () => {
      initializeMockApp({
        authenticatedUser: {
          userId: 3,
          username: 'abc123',
          administrator: true,
          roles: [],
        },
      });

      store = initializeStore();
      axiosMock = new MockAdapter(getAuthenticatedHttpClient());

      axiosMock.onGet(
        StudioApiService.getProctoredExamSettingsUrl(defaultProps.courseId),
      ).reply(200, {
        proctored_exam_settings: {
          enable_proctored_exams: true,
          allow_proctoring_opt_out: false,
          proctoring_provider: 'mockproc',
          proctoring_escalation_email: '[email protected]',
          create_zendesk_tickets: true,
        },
        available_proctoring_providers: ['software_secure', 'proctortrack', 'mockproc'],
        course_start_date: '2070-01-01T00:00:00Z',
      });

      await act(async () => render(intlWrapper(<IntlProctoredExamSettings {...defaultProps} />)));
    });

    it('Updates Zendesk ticket field if proctortrack is provider', async () => {
      await waitFor(() => {
        screen.getByDisplayValue('mockproc');
      });
      const selectElement = screen.getByDisplayValue('mockproc');
      await act(async () => {
        fireEvent.change(selectElement, { target: { value: 'proctortrack' } });
      });
      const zendeskTicketInput = screen.getByTestId('createZendeskTicketsNo');
      expect(zendeskTicketInput.checked).toEqual(true);
    });

    it('Updates Zendesk ticket field if software_secure is provider', async () => {
      await waitFor(() => {
        screen.getByDisplayValue('mockproc');
      });
      const selectElement = screen.getByDisplayValue('mockproc');
      await act(async () => {
        fireEvent.change(selectElement, { target: { value: 'software_secure' } });
      });
      const zendeskTicketInput = screen.getByTestId('createZendeskTicketsYes');
      expect(zendeskTicketInput.checked).toEqual(true);
    });

    it('Does not update zendesk ticket field for any other provider', async () => {
      await waitFor(() => {
        screen.getByDisplayValue('mockproc');
      });
      const selectElement = screen.getByDisplayValue('mockproc');
      await act(async () => {
        fireEvent.change(selectElement, { target: { value: 'mockproc' } });
      });
      const zendeskTicketInput = screen.getByTestId('createZendeskTicketsYes');
      expect(zendeskTicketInput.checked).toEqual(true);
    });

    it('Hides all other fields when enabledProctorExam is false when first loaded', async () => {
      cleanup();
      // Overrides the handler defined in beforeEach.
      axiosMock.onGet(
        StudioApiService.getProctoredExamSettingsUrl(defaultProps.courseId),
      ).reply(200, {
        proctored_exam_settings: {
          enable_proctored_exams: false,
          allow_proctoring_opt_out: false,
          proctoring_provider: 'mockproc',
          proctoring_escalation_email: '[email protected]',
          create_zendesk_tickets: true,
        },
        available_proctoring_providers: ['software_secure', 'proctortrack', 'mockproc'],
        course_start_date: '2070-01-01T00:00:00Z',
      });

      await act(async () => render(intlWrapper(<IntlProctoredExamSettings {...defaultProps} />)));
      await waitFor(() => {
        screen.getByLabelText('Enable Proctored Exams');
      });
      const enabledProctoredExamCheck = screen.getByLabelText('Enable Proctored Exams');
      expect(enabledProctoredExamCheck.checked).toEqual(false);
      expect(screen.queryByText('Allow Opting Out of Proctored Exams')).toBeNull();
      expect(screen.queryByDisplayValue('mockproc')).toBeNull();
      expect(screen.queryByTestId('escalationEmail')).toBeNull();
      expect(screen.queryByTestId('createZendeskTicketsYes')).toBeNull();
      expect(screen.queryByTestId('createZendeskTicketsNo')).toBeNull();
    });

    it('Hides all other fields when enableProctoredExams toggled to false', async () => {
      await waitFor(() => {
        screen.getByLabelText('Enable Proctored Exams');
      });
      expect(screen.queryByText('Allow Opting Out of Proctored Exams')).toBeDefined();
      expect(screen.queryByDisplayValue('mockproc')).toBeDefined();
      expect(screen.queryByTestId('escalationEmail')).toBeDefined();
      expect(screen.queryByTestId('createZendeskTicketsYes')).toBeDefined();
      expect(screen.queryByTestId('createZendeskTicketsNo')).toBeDefined();

      let enabledProctorExamCheck = screen.getByLabelText('Enable Proctored Exams');
      expect(enabledProctorExamCheck.checked).toEqual(true);
      await act(async () => {
        fireEvent.click(enabledProctorExamCheck, { target: { value: false } });
      });
      enabledProctorExamCheck = screen.getByLabelText('Enable Proctored Exams');
      expect(enabledProctorExamCheck.checked).toEqual(false);
      expect(screen.queryByText('Allow Opting Out of Proctored Exams')).toBeNull();
      expect(screen.queryByDisplayValue('mockproc')).toBeNull();
      expect(screen.queryByTestId('escalationEmail')).toBeNull();
      expect(screen.queryByTestId('createZendeskTicketsYes')).toBeNull();
      expect(screen.queryByTestId('createZendeskTicketsNo')).toBeNull();
    });
  });

  describe('Validation with invalid escalation email', () => {
    beforeEach(async () => {
      initializeMockApp({
        authenticatedUser: {
          userId: 3,
          username: 'abc123',
          administrator: false,
          roles: [],
        },
      });

      axiosMock = new MockAdapter(getAuthenticatedHttpClient());

      axiosMock.onGet(
        StudioApiService.getProctoredExamSettingsUrl(defaultProps.courseId),
      ).reply(200, {
        proctored_exam_settings: {
          enable_proctored_exams: true,
          allow_proctoring_opt_out: false,
          proctoring_provider: 'proctortrack',
          proctoring_escalation_email: '[email protected]',
          create_zendesk_tickets: true,
        },
        available_proctoring_providers: ['software_secure', 'proctortrack', 'mockproc'],
        course_start_date: '2070-01-01T00:00:00Z',
      });

      axiosMock.onPost(
        StudioApiService.getProctoredExamSettingsUrl(defaultProps.courseId),
      ).reply(200, {});

      await act(async () => render(intlWrapper(<IntlProctoredExamSettings {...defaultProps} />)));
    });

    it('Creates an alert when no proctoring escalation email is provided with proctortrack selected', async () => {
      await waitFor(() => {
        screen.getByDisplayValue('proctortrack');
      });
      const selectEscalationEmailElement = screen.getByDisplayValue('[email protected]');
      await act(async () => {
        fireEvent.change(selectEscalationEmailElement, { target: { value: '' } });
      });
      const selectButton = screen.getByTestId('submissionButton');
      await act(async () => {
        fireEvent.click(selectButton);
      });

      // verify alert content and focus management
      const escalationEmailError = screen.getByTestId('proctortrackEscalationEmailError');
      expect(escalationEmailError.textContent).not.toBeNull();
      expect(document.activeElement).toEqual(escalationEmailError);

      // verify alert link links to offending input
      const errorLink = screen.getByTestId('proctorTrackEscalationEmailErrorLink');
      await act(async () => {
        fireEvent.click(errorLink);
      });
      const escalationEmailInput = screen.getByTestId('escalationEmail');
      expect(document.activeElement).toEqual(escalationEmailInput);
    });

    it('Creates an alert when invalid proctoring escalation email is provided with proctortrack selected', async () => {
      await waitFor(() => {
        screen.getByDisplayValue('proctortrack');
      });
      const selectEscalationEmailElement = screen.getByDisplayValue('[email protected]');
      await act(async () => {
        fireEvent.change(selectEscalationEmailElement, { target: { value: 'foo.bar' } });
      });
      const selectButton = screen.getByTestId('submissionButton');
      await act(async () => {
        fireEvent.click(selectButton);
      });

      // verify alert content and focus management
      const escalationEmailError = screen.getByTestId('proctortrackEscalationEmailError');
      expect(document.activeElement).toEqual(escalationEmailError);
      expect(escalationEmailError.textContent).not.toBeNull();
      expect(document.activeElement).toEqual(escalationEmailError);

      // verify alert link links to offending input
      const errorLink = screen.getByTestId('proctorTrackEscalationEmailErrorLink');
      await act(async () => {
        fireEvent.click(errorLink);
      });
      const escalationEmailInput = screen.getByTestId('escalationEmail');
      expect(document.activeElement).toEqual(escalationEmailInput);
    });

    it('Creates an alert when invalid proctoring escalation email is provided with proctoring disabled', async () => {
      await waitFor(() => {
        screen.getByDisplayValue('proctortrack');
      });
      const selectEscalationEmailElement = screen.getByDisplayValue('[email protected]');
      await act(async () => {
        fireEvent.change(selectEscalationEmailElement, { target: { value: 'foo.bar' } });
      });
      const enableProctoringElement = screen.getByLabelText('Enable Proctored Exams');
      await act(async () => fireEvent.click(enableProctoringElement));
      const selectButton = screen.getByTestId('submissionButton');
      await act(async () => {
        fireEvent.click(selectButton);
      });

      // verify alert content and focus management
      const escalationEmailError = screen.getByTestId('proctortrackEscalationEmailError');
      expect(document.activeElement).toEqual(escalationEmailError);
      expect(escalationEmailError.textContent).not.toBeNull();
      expect(document.activeElement).toEqual(escalationEmailError);
    });

    it('Has no error when invalid proctoring escalation email is provided with proctoring disabled', async () => {
      await waitFor(() => {
        screen.getByDisplayValue('proctortrack');
      });
      const selectEscalationEmailElement = screen.getByDisplayValue('[email protected]');
      await act(async () => {
        fireEvent.change(selectEscalationEmailElement, { target: { value: '' } });
      });
      const enableProctoringElement = screen.getByLabelText('Enable Proctored Exams');
      await act(async () => fireEvent.click(enableProctoringElement));
      const selectButton = screen.getByTestId('submissionButton');
      await act(async () => {
        fireEvent.click(selectButton);
      });

      // verify there is no escalation email alert, and focus has been set on save success alert
      expect(screen.queryByTestId('proctortrackEscalationEmailError')).toBeNull();

      const errorAlert = screen.getByTestId('saveSuccess');
      expect(errorAlert.textContent).toEqual(
        expect.stringContaining('Proctored exam settings saved successfully.'),
      );
      expect(document.activeElement).toEqual(errorAlert);
    });

    it('Has no error when valid proctoring escalation email is provided with proctortrack selected', async () => {
      await waitFor(() => {
        screen.getByDisplayValue('proctortrack');
      });
      const selectEscalationEmailElement = screen.getByDisplayValue('[email protected]');
      await act(async () => {
        fireEvent.change(selectEscalationEmailElement, { target: { value: '[email protected]' } });
      });
      const selectButton = screen.getByTestId('submissionButton');
      await act(async () => {
        fireEvent.click(selectButton);
      });

      // verify there is no escalation email alert, and focus has been set on save success alert
      expect(screen.queryByTestId('proctortrackEscalationEmailError')).toBeNull();

      const errorAlert = screen.getByTestId('saveSuccess');
      expect(errorAlert.textContent).toEqual(
        expect.stringContaining('Proctored exam settings saved successfully.'),
      );
      expect(document.activeElement).toEqual(errorAlert);
    });

    it('Escalation email field hidden when proctoring backend is not Proctortrack', async () => {
      await waitFor(() => {
        screen.getByDisplayValue('proctortrack');
      });
      const proctoringBackendSelect = screen.getByDisplayValue('proctortrack');
      const selectEscalationEmailElement = screen.getByTestId('escalationEmail');
      expect(selectEscalationEmailElement.value).toEqual('[email protected]');
      await act(async () => {
        fireEvent.change(proctoringBackendSelect, { target: { value: 'software_secure' } });
      });
      expect(screen.queryByTestId('escalationEmail')).toBeNull();
    });

    it('Escalation email Field Show when proctoring backend is switched back to Proctortrack', async () => {
      await waitFor(() => {
        screen.getByDisplayValue('proctortrack');
      });
      const proctoringBackendSelect = screen.getByDisplayValue('proctortrack');
      let selectEscalationEmailElement = screen.getByTestId('escalationEmail');
      await act(async () => {
        fireEvent.change(proctoringBackendSelect, { target: { value: 'software_secure' } });
      });
      expect(screen.queryByTestId('escalationEmail')).toBeNull();
      await act(async () => {
        fireEvent.change(proctoringBackendSelect, { target: { value: 'proctortrack' } });
      });
      expect(screen.queryByTestId('escalationEmail')).toBeDefined();
      selectEscalationEmailElement = screen.getByTestId('escalationEmail');
      expect(selectEscalationEmailElement.value).toEqual('[email protected]');
    });

    it('Submits form when "Enter" key is hit in the escalation email field', async () => {
      await waitFor(() => {
        screen.getByDisplayValue('proctortrack');
      });
      const selectEscalationEmailElement = screen.getByDisplayValue('[email protected]');
      await act(async () => {
        fireEvent.change(selectEscalationEmailElement, { target: { value: '' } });
      });
      await act(async () => {
        fireEvent.submit(selectEscalationEmailElement);
      });
      // if the error appears, the form has been submitted
      expect(screen.getByTestId('proctortrackEscalationEmailError')).toBeDefined();
    });
  });

  describe('Proctoring provider options', () => {
    const mockGetFutureCourseData = {
      proctored_exam_settings: {
        enable_proctored_exams: true,
        allow_proctoring_opt_out: false,
        proctoring_provider: 'mockproc',
        proctoring_escalation_email: '[email protected]',
        create_zendesk_tickets: true,
      },
      available_proctoring_providers: ['software_secure', 'proctortrack', 'mockproc'],
      course_start_date: '2099-01-01T00:00:00Z',
    };

    const mockGetPastCourseData = {
      proctored_exam_settings: {
        enable_proctored_exams: true,
        allow_proctoring_opt_out: false,
        proctoring_provider: 'mockproc',
        proctoring_escalation_email: '[email protected]',
        create_zendesk_tickets: true,
      },
      available_proctoring_providers: ['software_secure', 'proctortrack', 'mockproc'],
      course_start_date: '2013-01-01T00:00:00Z',
    };

    function setup(data, isAdmin) {
      initializeMockApp({
        authenticatedUser: {
          userId: 3,
          username: 'abc123',
          administrator: isAdmin,
          roles: [],
        },
      });

      axiosMock = new MockAdapter(getAuthenticatedHttpClient());
      axiosMock.onGet(StudioApiService.getProctoredExamSettingsUrl(defaultProps.courseId)).reply(200, data);
    }

    it('Disables irrelevant proctoring provider fields when user is not an administrator and it is after start date', async () => {
      setup(mockGetPastCourseData, false);
      await act(async () => render(intlWrapper(<IntlProctoredExamSettings {...defaultProps} />)));
      const providerOption = screen.getByTestId('proctortrack');
      expect(providerOption.hasAttribute('disabled')).toEqual(true);
    });

    it('Enables all proctoring provider options if user is not an administrator and it is before start date', async () => {
      setup(mockGetFutureCourseData, false);
      await act(async () => render(intlWrapper(<IntlProctoredExamSettings {...defaultProps} />)));
      const providerOption = screen.getByTestId('proctortrack');
      expect(providerOption.hasAttribute('disabled')).toEqual(false);
    });

    it('Enables all proctoring provider options if user administrator and it is after start date', async () => {
      setup(mockGetPastCourseData, true);
      await act(async () => render(intlWrapper(<IntlProctoredExamSettings {...defaultProps} />)));
      const providerOption = screen.getByTestId('proctortrack');
      expect(providerOption.hasAttribute('disabled')).toEqual(false);
    });

    it('Enables all proctoring provider options if user administrator and it is before start date', async () => {
      setup(mockGetFutureCourseData, true);
      await act(async () => render(intlWrapper(<IntlProctoredExamSettings {...defaultProps} />)));
      const providerOption = screen.getByTestId('proctortrack');
      expect(providerOption.hasAttribute('disabled')).toEqual(false);
    });
  });

  describe('Toggles field visibility based on user permissions', () => {
    function setup(isAdmin) {
      initializeMockApp({
        authenticatedUser: {
          userId: 3,
          username: 'abc123',
          administrator: isAdmin,
          roles: [],
        },
      });

      axiosMock = new MockAdapter(getAuthenticatedHttpClient());
      axiosMock.onGet(StudioApiService.getProctoredExamSettingsUrl(defaultProps.courseId)).reply(200, {
        proctored_exam_settings: {
          enable_proctored_exams: true,
          allow_proctoring_opt_out: false,
          proctoring_provider: 'mockproc',
          proctoring_escalation_email: '[email protected]',
          create_zendesk_tickets: true,
        },
        available_proctoring_providers: ['software_secure', 'proctortrack', 'mockproc'],
        course_start_date: '2070-01-01T00:00:00Z',
      });
    }

    it('Hides opting out and zendesk tickets for non edX staff', async () => {
      setup(false);
      await act(async () => render(intlWrapper(<IntlProctoredExamSettings {...defaultProps} />)));
      expect(screen.queryByTestId('allowOptingOutYes')).toBeNull();
      expect(screen.queryByTestId('createZendeskTicketsYes')).toBeNull();
    });

    it('Shows opting out and zendesk tickets for edX staff', async () => {
      setup(true);
      await act(async () => render(intlWrapper(<IntlProctoredExamSettings {...defaultProps} />)));
      expect(screen.queryByTestId('allowOptingOutYes')).not.toBeNull();
      expect(screen.queryByTestId('createZendeskTicketsYes')).not.toBeNull();
    });
  });

  describe('Connection states', () => {
    beforeEach(() => {
      initializeMockApp({
        authenticatedUser: {
          userId: 3,
          username: 'abc123',
          administrator: true,
          roles: [],
        },
      });
      axiosMock = new MockAdapter(getAuthenticatedHttpClient());
    });

    it('Shows the spinner before the connection is complete', async () => {
      await act(async () => {
        render(intlWrapper(<IntlProctoredExamSettings {...defaultProps} />));
        // This expectation is _inside_ the `act` intentionally, so that it executes immediately.
        const spinner = screen.getByRole('status');
        expect(spinner.textContent).toEqual('Loading...');
      });
    });

    it('Show connection error message when we suffer server side error', async () => {
      axiosMock.onGet(
        StudioApiService.getProctoredExamSettingsUrl(defaultProps.courseId),
      ).reply(500);

      await act(async () => render(intlWrapper(<IntlProctoredExamSettings {...defaultProps} />)));
      const connectionError = screen.getByTestId('connectionErrorAlert');
      expect(connectionError.textContent).toEqual(
        expect.stringContaining('We encountered a technical error when loading this page.'),
      );
    });

    it('Show permission error message when user do not have enough permission', async () => {
      axiosMock.onGet(
        StudioApiService.getProctoredExamSettingsUrl(defaultProps.courseId),
      ).reply(403);

      await act(async () => render(intlWrapper(<IntlProctoredExamSettings {...defaultProps} />)));
      const permissionError = screen.getByTestId('permissionDeniedAlert');
      expect(permissionError.textContent).toEqual(
        expect.stringContaining('You are not authorized to view this page'),
      );
    });
  });

  describe('Save settings', () => {
    beforeEach(() => {
      initializeMockApp({
        authenticatedUser: {
          userId: 3,
          username: 'abc123',
          administrator: true,
          roles: [],
        },
      });

      axiosMock = new MockAdapter(getAuthenticatedHttpClient(), { onNoMatch: 'throwException' });
      axiosMock.onGet(StudioApiService.getProctoredExamSettingsUrl(defaultProps.courseId)).reply(200, {
        proctored_exam_settings: {
          enable_proctored_exams: true,
          allow_proctoring_opt_out: false,
          proctoring_provider: 'mockproc',
          proctoring_escalation_email: '[email protected]',
          create_zendesk_tickets: true,
        },
        available_proctoring_providers: ['software_secure', 'proctortrack', 'mockproc'],
      });
    });

    it('Show spinner while saving', async () => {
      axiosMock.onPost(
        StudioApiService.getProctoredExamSettingsUrl(defaultProps.courseId),
      ).reply(200, 'success');

      await act(async () => render(intlWrapper(<IntlProctoredExamSettings {...defaultProps} />)));
      const submitButton = screen.getByTestId('submissionButton');
      expect(screen.queryByTestId('saveInProgress')).toBeFalsy();
      act(() => {
        fireEvent.click(submitButton);
      });

      const submitSpinner = screen.getByTestId('saveInProgress');
      expect(submitSpinner).toBeDefined();

      await waitForElementToBeRemoved(submitSpinner);
      expect(axiosMock.history.get.length).toBe(1);
      expect(axiosMock.history.post.length).toBe(1);
      expect(screen.queryByTestId('saveInProgress')).toBeFalsy();
    });

    it('Makes API call successfully with proctoring_escalation_email if proctortrack', async () => {
      axiosMock.onPost(
        StudioApiService.getProctoredExamSettingsUrl(defaultProps.courseId),
      ).reply(200, 'success');

      await act(async () => render(intlWrapper(<IntlProctoredExamSettings {...defaultProps} />)));
      // Make a change to the provider to proctortrack and set the email
      const selectElement = screen.getByDisplayValue('mockproc');
      await act(async () => {
        fireEvent.change(selectElement, { target: { value: 'proctortrack' } });
      });
      const escalationEmail = screen.getByTestId('escalationEmail');
      expect(escalationEmail.value).toEqual('[email protected]');
      await act(async () => {
        fireEvent.change(escalationEmail, { target: { value: '[email protected]' } });
      });
      expect(escalationEmail.value).toEqual('[email protected]');
      const submitButton = screen.getByTestId('submissionButton');
      await act(async () => {
        fireEvent.click(submitButton);
      });
      expect(axiosMock.history.post.length).toBe(1);
      expect(JSON.parse(axiosMock.history.post[0].data)).toEqual({
        proctored_exam_settings: {
          enable_proctored_exams: true,
          allow_proctoring_opt_out: false,
          proctoring_provider: 'proctortrack',
          proctoring_escalation_email: '[email protected]',
          create_zendesk_tickets: false,
        },
      });

      const errorAlert = screen.getByTestId('saveSuccess');
      expect(errorAlert.textContent).toEqual(
        expect.stringContaining('Proctored exam settings saved successfully.'),
      );
      expect(document.activeElement).toEqual(errorAlert);
    });

    it('Makes API call successfully without proctoring_escalation_email if not proctortrack', async () => {
      axiosMock.onPost(
        StudioApiService.getProctoredExamSettingsUrl(defaultProps.courseId),
      ).reply(200, 'success');

      await act(async () => render(intlWrapper(<IntlProctoredExamSettings {...defaultProps} />)));

      // make sure we have not selected proctortrack as the proctoring provider
      expect(screen.getByDisplayValue('mockproc')).toBeDefined();

      const submitButton = screen.getByTestId('submissionButton');
      await act(async () => {
        fireEvent.click(submitButton);
      });
      expect(axiosMock.history.post.length).toBe(1);
      expect(JSON.parse(axiosMock.history.post[0].data)).toEqual({
        proctored_exam_settings: {
          enable_proctored_exams: true,
          allow_proctoring_opt_out: false,
          proctoring_provider: 'mockproc',
          create_zendesk_tickets: true,
        },
      });

      const errorAlert = screen.getByTestId('saveSuccess');
      expect(errorAlert.textContent).toEqual(
        expect.stringContaining('Proctored exam settings saved successfully.'),
      );
      expect(document.activeElement).toEqual(errorAlert);
    });

    it('Makes API call generated error', async () => {
      axiosMock.onPost(
        StudioApiService.getProctoredExamSettingsUrl(defaultProps.courseId),
      ).reply(500);

      await act(async () => render(intlWrapper(<IntlProctoredExamSettings {...defaultProps} />)));
      const submitButton = screen.getByTestId('submissionButton');
      await act(async () => {
        fireEvent.click(submitButton);
      });
      expect(axiosMock.history.post.length).toBe(1);
      const errorAlert = screen.getByTestId('saveError');
      expect(errorAlert.textContent).toEqual(
        expect.stringContaining('We encountered a technical error while trying to save proctored exam settings'),
      );
      expect(document.activeElement).toEqual(errorAlert);
    });

    it('Manages focus correctly after different save statuses', async () => {
      // first make a call that will cause a save error
      axiosMock.onPost(
        StudioApiService.getProctoredExamSettingsUrl(defaultProps.courseId),
      ).replyOnce(500);

      await act(async () => render(intlWrapper(<IntlProctoredExamSettings {...defaultProps} />)));
      const submitButton = screen.getByTestId('submissionButton');
      await act(async () => {
        fireEvent.click(submitButton);
      });
      expect(axiosMock.history.post.length).toBe(1);
      const errorAlert = screen.getByTestId('saveError');
      expect(errorAlert.textContent).toEqual(
        expect.stringContaining('We encountered a technical error while trying to save proctored exam settings'),
      );
      expect(document.activeElement).toEqual(errorAlert);

      // now make a call that will allow for a successful save
      axiosMock.onPost(
        StudioApiService.getProctoredExamSettingsUrl(defaultProps.courseId),
      ).replyOnce(200, 'success');
      await act(async () => {
        fireEvent.click(submitButton);
      });

      expect(axiosMock.history.post.length).toBe(2);
      const successAlert = screen.getByTestId('saveSuccess');
      expect(successAlert.textContent).toEqual(
        expect.stringContaining('Proctored exam settings saved successfully.'),
      );
      expect(document.activeElement).toEqual(successAlert);
    });

    it('Include Zendesk ticket in post request if user is not an admin', async () => {
      // use non-admin user for test
      initializeMockApp({
        authenticatedUser: {
          userId: 4,
          username: 'abc1234',
          administrator: false,
          roles: [],
        },
      });
      axiosMock = new MockAdapter(getAuthenticatedHttpClient(), { onNoMatch: 'throwException' });
      axiosMock.onGet(StudioApiService.getProctoredExamSettingsUrl(defaultProps.courseId)).reply(200, {
        proctored_exam_settings: {
          enable_proctored_exams: true,
          allow_proctoring_opt_out: false,
          proctoring_provider: 'mockproc',
          proctoring_escalation_email: '[email protected]',
          create_zendesk_tickets: true,
        },
        available_proctoring_providers: ['software_secure', 'proctortrack', 'mockproc'],
      });
      axiosMock.onPost(
        StudioApiService.getProctoredExamSettingsUrl(defaultProps.courseId),
      ).reply(200, 'success');

      await act(async () => render(intlWrapper(<IntlProctoredExamSettings {...defaultProps} />)));
      // Make a change to the proctoring provider
      const selectElement = screen.getByDisplayValue('mockproc');
      await act(async () => {
        fireEvent.change(selectElement, { target: { value: 'proctortrack' } });
      });
      const submitButton = screen.getByTestId('submissionButton');
      await act(async () => {
        fireEvent.click(submitButton);
      });
      expect(axiosMock.history.post.length).toBe(1);
      expect(JSON.parse(axiosMock.history.post[0].data)).toEqual({
        proctored_exam_settings: {
          enable_proctored_exams: true,
          proctoring_provider: 'proctortrack',
          proctoring_escalation_email: '[email protected]',
          create_zendesk_tickets: false,
        },
      });
    });
  });
});