@testing-library/react#queryByRole JavaScript Examples

The following examples show how to use @testing-library/react#queryByRole. 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: AppList.test.jsx    From frontend-app-course-authoring with GNU Affero General Public License v3.0 5 votes vote down vote up
describe('AppList', () => {
  let axiosMock;
  let store;
  let container;

  function createComponent(screenWidth = breakpoints.extraLarge.minWidth) {
    return (
      <AppProvider store={store}>
        <ResponsiveContext.Provider value={{ width: screenWidth }}>
          <IntlProvider locale="en" messages={{}}>
            <AppList />
          </IntlProvider>
        </ResponsiveContext.Provider>
      </AppProvider>
    );
  }

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

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

  const mockStore = async (mockResponse, screenWidth = breakpoints.extraLarge.minWidth) => {
    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);
    const component = createComponent(screenWidth);
    const wrapper = render(component);
    container = wrapper.container;
  };

  test('display a card for each available app', async () => {
    await mockStore(piazzaApiResponse);
    const appCount = store.getState().discussions.appIds.length;
    expect(queryAllByRole(container, 'radio')).toHaveLength(appCount);
  });

  test('displays the FeaturesTable at desktop sizes', async () => {
    await mockStore(piazzaApiResponse);
    expect(queryByRole(container, 'table')).toBeInTheDocument();
  });

  test('hides the FeaturesTable at mobile sizes', async () => {
    await mockStore(piazzaApiResponse, breakpoints.extraSmall.maxWidth);
    expect(queryByRole(container, 'table')).not.toBeInTheDocument();
  });

  test('hides the FeaturesList at desktop sizes', async () => {
    await mockStore(piazzaApiResponse);
    expect(queryByText(container, messages['supportedFeatureList-mobile-show'].defaultMessage)).not.toBeInTheDocument();
  });

  test('displays the FeaturesList at mobile sizes', async () => {
    await mockStore(piazzaApiResponse, breakpoints.extraSmall.maxWidth);
    const appCount = store.getState().discussions.appIds.length;
    expect(queryAllByText(container, messages['supportedFeatureList-mobile-show'].defaultMessage)).toHaveLength(appCount);
  });

  test('selectApp is called when an app is clicked', async () => {
    await mockStore(piazzaApiResponse);
    userEvent.click(getByLabelText(container, 'Select Piazza'));
    const clickedCard = getByRole(container, 'radio', { checked: true });
    expect(queryByLabelText(clickedCard, 'Select Piazza')).toBeInTheDocument();
  });
});
Example #2
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 #3
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 #4
Source File: TopicItem.test.jsx    From frontend-app-course-authoring with GNU Affero General Public License v3.0 4 votes vote down vote up
describe('TopicItem', () => {
  let axiosMock;
  let store;
  let container;

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

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

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

  const createComponent = (props) => {
    const wrapper = render(
      <AppProvider store={store}>
        <IntlProvider locale="en">
          <Formik initialValues={appConfig}>
            <TopicItem {...props} />
          </Formik>
        </IntlProvider>
      </AppProvider>,
    );
    container = wrapper.container;
  };

  const mockStore = async (mockResponse) => {
    axiosMock.onGet(getDiscussionsProvidersUrl(courseId)).reply(200, mockResponse);
    await executeThunk(fetchProviders(courseId), store.dispatch);
  };

  test('displays a collapsible card for discussion topic', async () => {
    await mockStore(legacyApiResponse);
    createComponent(generalTopic);

    expect(queryAllByTestId(container, 'course')).toHaveLength(1);
  });

  test('displays collapse view of general discussion topic', async () => {
    await mockStore(legacyApiResponse);
    createComponent(generalTopic);

    const generalTopicNode = queryByTestId(container, 'course');
    expect(queryByLabelText(generalTopicNode, 'Expand')).toBeInTheDocument();
    expect(queryByLabelText(generalTopicNode, 'Collapse')).not.toBeInTheDocument();
    expect(queryByText(generalTopicNode, 'General')).toBeInTheDocument();
  });

  test('displays expand view of general discussion topic', async () => {
    await mockStore(legacyApiResponse);
    createComponent(generalTopic);

    const generalTopicNode = queryByTestId(container, 'course');
    userEvent.click(queryByLabelText(generalTopicNode, 'Expand'));

    expect(queryByLabelText(generalTopicNode, 'Expand')).not.toBeInTheDocument();
    expect(queryByLabelText(generalTopicNode, 'Collapse')).toBeInTheDocument();
    expect(queryByRole(generalTopicNode, 'button', { name: 'Delete Topic' })).not.toBeInTheDocument();
    expect(generalTopicNode.querySelector('input')).toBeInTheDocument();
  });

  test('displays expand view of additional discussion topic', async () => {
    await mockStore(legacyApiResponse);
    createComponent(additionalTopic);

    const topicCard = queryByTestId(container, '13f106c6-6735-4e84-b097-0456cff55960');
    userEvent.click(queryByLabelText(topicCard, 'Expand'));

    expect(queryByLabelText(topicCard, 'Expand')).not.toBeInTheDocument();
    expect(queryByLabelText(topicCard, 'Collapse')).toBeInTheDocument();
    expect(queryByRole(topicCard, 'button', { name: 'Delete Topic' })).toBeInTheDocument();
    expect(topicCard.querySelector('input')).toBeInTheDocument();
  });

  test('renders delete topic popup with providerName, label, helping text, a delete and a cancel button', async () => {
    await mockStore(legacyApiResponse);
    createComponent(additionalTopic);

    const topicCard = queryByTestId(container, '13f106c6-6735-4e84-b097-0456cff55960');
    userEvent.click(queryByLabelText(topicCard, 'Expand'));
    userEvent.click(queryByRole(topicCard, 'button', { name: 'Delete Topic' }));

    expect(queryAllByText(container, messages.discussionTopicDeletionLabel.defaultMessage)).toHaveLength(1);
    expect(queryByText(container, messages.discussionTopicDeletionLabel.defaultMessage)).toBeInTheDocument();
    expect(queryByText(container, messages.discussionTopicDeletionHelp.defaultMessage)).toBeInTheDocument();
    expect(queryByText(container, messages.discussionTopicDeletionHelp.defaultMessage)).toBeInTheDocument();
    expect(queryByText(container, messages.deleteButton.defaultMessage)).toBeInTheDocument();
    expect(queryByText(container, messages.cancelButton.defaultMessage)).toBeInTheDocument();
  });

  test('shows help text on field focus', async () => {
    await mockStore(legacyApiResponse);
    createComponent(additionalTopic);

    const topicCard = queryByTestId(container, '13f106c6-6735-4e84-b097-0456cff55960');
    userEvent.click(queryByLabelText(topicCard, 'Expand'));
    topicCard.querySelector('input').focus();

    expect(queryByText(topicCard, messages.addTopicHelpText.defaultMessage)).toBeInTheDocument();
  });
});
Example #5
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();
    });
  });
});