@testing-library/react#within JavaScript Examples

The following examples show how to use @testing-library/react#within. 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: Provider.test.jsx    From covid with GNU General Public License v3.0 6 votes vote down vote up
test('a multiple context consumer is rendered properly when rendered inside the multi-provider', async () => {
  const rendered = render(
    <Provider>
      <WrappedChild data-value='test-props' />
    </Provider>
  )
  const child = rendered.getByRole('child')
  expect(child).toBeInTheDocument();

  ['Bcn', 'Map', 'Chart'].forEach((backend) => {
    const testBackend = within(child).getByRole(backend)
    expect(testBackend).toHaveTextContent(`class ${backend}DataHandler`)
  })
})
Example #2
Source File: Skiplinks.test.js    From react-dsfr with MIT License 6 votes vote down vote up
describe('<SkipLinks />', () => {
  it('should renders SkipLinks properly', () => {
    render(
      <Skiplinks data-testid="skiplinks">
        <SkiplinkItem href="#" data-testid="skiplinkitem1">Accéder au contenu</SkiplinkItem>
        <SkiplinkItem href="#" data-test-id="skiplinkitem2">Accéder au menu</SkiplinkItem>
        <SkiplinkItem href="#" data-test--id="skiplinkitem3">Accéder à la recherche</SkiplinkItem>
        <SkiplinkItem href="#" data-test="skiplinkitem4">Accéder au footer</SkiplinkItem>
      </Skiplinks>,
    );
    const skiplinks = screen.getByTestId('skiplinks');
    expect(skiplinks).toBeInTheDocument();
    expect(skiplinks).toMatchSnapshot();
    const links = screen.getByRole('list');
    const { getAllByRole } = within(links);
    const items = getAllByRole('listitem');
    expect(items.length).toBe(4);
  });
});
Example #3
Source File: delivery-type.test.js    From horondi_client_fe with MIT License 6 votes vote down vote up
describe('<DeliveryType />', () => {
  it('Radio group should be rendered', () => {
    render(<DeliveryType {...props} deliveryType='COURIER' />);

    const radiogroup = screen.getByRole('radiogroup', { name: 'Delivery type' });
    expect(radiogroup).toBeInTheDocument();
  });

  it('setDeliveryType function should be called with correct argument', () => {
    render(<DeliveryType {...props} deliveryType='NOVAPOST' />);

    expect(props.setDeliveryType).toHaveBeenCalledWith('NOVAPOST');
  });

  it('handleCourierOrganizationChange function should triggered on select change', () => {
    render(<DeliveryType {...props} deliveryType='COURIER' />);

    const selectWrapper = screen.getByTestId('courierOrganization');
    const selectComponent = within(selectWrapper).getByRole('button');

    fireEvent.mouseDown(selectComponent);

    const popup = within(screen.getByRole('listbox'));

    fireEvent.click(popup.getAllByRole('option')[0]);
  });
});
Example #4
Source File: worldwide.spec.js    From horondi_client_fe with MIT License 6 votes vote down vote up
describe('tests for worldwide delivery component', () => {
  it('worldwide delivery component should be rendered correctly', () => {
    render(<Worldwide {...props} />);

    const heading = screen.getByRole('heading', { level: 3 });

    const statesWrapper = screen.getByTestId('stateOrProvince');
    const statesInput = within(statesWrapper).getByRole('textbox');

    expect(heading).toBeInTheDocument();
    expect(statesInput).toHaveAttribute('disabled');
  });

  it('test typing in worldwideCity input', () => {
    render(<Worldwide {...props} values={{ worldWideCountry: 'Ukraine' }} />);

    const citiesWrapper = screen.getByTestId('worldWideCity');
    const citiesInput = within(citiesWrapper).getByRole('textbox');

    fireEvent.change(citiesInput, { target: { value: 'city' } });
    expect(citiesInput).toHaveAttribute('value', 'city');
  });
});
Example #5
Source File: delivery.spec.js    From horondi_admin with MIT License 6 votes vote down vote up
describe('tests for delivery component', () => {
  it('should render delivery component with worldwide fields', () => {
    render(
      <Delivery
        setFieldValue={setFieldValue}
        data={{ delivery: { ...deliveryProps, sentBy: 'WORLDWIDE' } }}
      />
    );

    const statesWrapper = screen.getByTestId('stateOrProvince');
    const statesInput = within(statesWrapper).getByRole('textbox');

    expect(statesInput).toHaveAttribute('disabled');
  });

  it('should render delivery component with ukrpost courier or novapost courier fields', () => {
    render(
      <Delivery
        setFieldValue={setFieldValue}
        data={{ delivery: { ...deliveryProps, sentBy: 'UKRPOSTCOURIER' } }}
      />
    );

    const cityInputWrapper = screen.getByTestId('delivery.courier.city');
    const cityInput = within(cityInputWrapper).getByRole('textbox');

    expect(cityInput).toBeInTheDocument();
  });
});
Example #6
Source File: worldwide.spec.js    From horondi_admin with MIT License 6 votes vote down vote up
describe('tests for worldwide delivery component', () => {
  it('worldwide delivery component should be rendered correctly', () => {
    render(<Worldwide {...props} setFieldValue={setFieldValue} />);

    const heading = screen.getByRole('heading', { level: 3 });

    const statesWrapper = screen.getByTestId('stateOrProvince');
    const statesInput = within(statesWrapper).getByRole('textbox');

    expect(heading).toBeInTheDocument();
    expect(statesInput).toHaveAttribute('disabled');
  });

  it('test typing in worldwideCity input', () => {
    render(
      <Worldwide
        {...props}
        values={{ worldWideCountry: 'Ukraine' }}
        setFieldValue={setFieldValue}
      />
    );

    const citiesWrapper = screen.getByTestId('worldWideCity');
    const citiesInput = within(citiesWrapper).getByRole('textbox');

    fireEvent.change(citiesInput, { target: { value: 'city' } });
    expect(citiesInput).toHaveAttribute('value', 'city');
  });
});
Example #7
Source File: Pagination.test.js    From react-dsfr with MIT License 6 votes vote down vote up
describe('<Pagination />', () => {
  it('should render pagination properly', () => {
    render(
      <Pagination buildURL={(page) => `page${page}`} currentPage={8} pageCount={15} data-testid="pagination" />,
    );
    const pagination = screen.getByTestId('pagination');
    expect(pagination.className).toBe('fr-pagination');
    const pages = screen.getByRole('list');
    const { getAllByRole } = within(pages);
    const items = getAllByRole('listitem');
    expect(items.length).toBe(13);
    expect(pagination).toMatchSnapshot();
  });

  it('should render state pagination properly', () => {
    render(
      <Pagination onClick={() => {}} currentPage={8} pageCount={15} data-testid="pagination" />,
    );
    const pagination = screen.getByTestId('pagination');
    expect(pagination.className).toBe('fr-pagination');
    const pages = screen.getByRole('list');
    const { getAllByRole } = within(pages);
    const items = getAllByRole('listitem');
    expect(items.length).toBe(13);
    expect(pagination).toMatchSnapshot();
  });
});
Example #8
Source File: App.test.js    From HackerRank-React-Basic with MIT License 6 votes vote down vote up
expectArticles = (articles, expectedArticles) => {
  expect(articles).toHaveLength(expectedArticles.length);
  articles.forEach((article, i) => {
    const title = within(article).getByTestId("article-title").textContent;
    const upvotes = within(article).getByTestId("article-upvotes").textContent;
    const date = within(article).getByTestId("article-date").textContent;
    const expectedArticle = expectedArticles[i];
    expect([title, upvotes, date]).toEqual([expectedArticle.title, expectedArticle.upvotes.toString(), expectedArticle.date]);
  });
}
Example #9
Source File: setup.js    From ui-data-export with Apache License 2.0 6 votes vote down vote up
checkJobProfileFormState = async (form, { title }) => {
  const formTitle = await within(form).findByText(title);
  const nameInput = await within(form).findByLabelText(/Name/i);
  const mappingProfileInput = await within(form).findByLabelText(/Mapping profile/i);
  const descriptionInput = await within(form).findByLabelText('Description');

  expect(form).toBeVisible();
  expect(formTitle).toBeVisible();
  expect(nameInput).toBeVisible();
  expect(mappingProfileInput).toBeVisible();

  expect(descriptionInput).toBeVisible();

  expect(nameInput).toBeEnabled();
  expect(descriptionInput).toBeEnabled();
  expect(mappingProfileInput).toBeEnabled();

  return {
    formTitle,
    nameInput,
    mappingProfileInput,
    descriptionInput,
  };
}
Example #10
Source File: swap_card.spec.js    From astroport-lbp-frontend with MIT License 6 votes vote down vote up
async function waitForBalances({ fromBalance, toBalance }) {
  const [fromBalanceLabel, toBalanceLabel] = screen.getAllByText('Balance:');

  if(fromBalance !== undefined) {
    expect(await within(fromBalanceLabel.parentElement).findByText(fromBalance)).toBeInTheDocument();
  }

  if(toBalance !== undefined) {
    expect(await within(toBalanceLabel.parentElement).findByText(toBalance)).toBeInTheDocument();
  }
}
Example #11
Source File: setup.js    From ui-data-export with Apache License 2.0 5 votes vote down vote up
transformationListCells = () => within(transformationListRows()[0]).getByText('Instance - Resource title')
Example #12
Source File: testingUtility.js    From lens-extension-cc with MIT License 5 votes vote down vote up
customWithin = function (el) {
  const result = within(el);
  const boundQueries = getBoundQueries(el);
  return { ...result, ...boundQueries };
}
Example #13
Source File: current_token_sale.spec.js    From astroport-lbp-frontend with MIT License 5 votes vote down vote up
describe('CurrentTokenSale', () => {
  it('fetches and displays data for current token sale', async () => {
    fetchUSTExchangeRate.mockResolvedValue(0.99);
    getWeights.mockResolvedValue([5, 95]);
    getPool.mockResolvedValue({
      assets: [
        {
          info: {
            native_token: {
              denom: 'uusd'
            }
          },
          amount: '5000000000000', // 5,000,000.000000
          start_weight: '2',
          end_weight: '60'
        },
        {
          info: {
            token: {
              contract_addr: 'terra123'
            }
          },
          amount: '42000000123456', // 42,000,000.123456
          start_weight: '98',
          end_weight: '40'
        }
      ],
      total_share: '60000000'
    });

    const pair = buildPair({
      contractAddr: 'terra1',
      tokenContractAddr: 'terra123',
      endTime: Math.floor((new Date(2021, 5, 18, 11, 10)).getTime() / 1000),
      description: 'A brand new token for sale!'
    });

    const saleTokenInfo = {
      symbol: 'FOO',
      decimals: 6
    };

    const dateNowSpy = jest
      .spyOn(Date, 'now')
      .mockImplementation(() => new Date(2021, 5, 16, 8).getTime());

    render(<CurrentTokenSale pair={pair} saleTokenInfo={saleTokenInfo} />);

    const priceCard = (await screen.findByText('Price')).closest('div');
    const coinsRemainingCard = (await screen.findByText('Coins Remaining')).closest('div');
    const timeRemainingCard = (await screen.findByText('Time Remaining')).closest('div');
    const currentWeightCard = (await screen.findByText('Current Weight')).closest('div');
    const aboutCard = (await screen.findByText('About')).closest('div');

    // ((500000000000 / 5) / (42000000123456 / 95)) = 2.261904755 * $0.99 = $2.239285714
    expect(within(priceCard).getByText('$2.24')).toBeInTheDocument();

    expect(within(coinsRemainingCard).getByText('42,000,000.123456')).toBeInTheDocument();

    // 2021-06-16 @ 8am -> 2021-06-18 @ 11:10am
    expect(within(timeRemainingCard).getByText('2d : 3h : 10m')).toBeInTheDocument();

    expect(within(currentWeightCard).getByText('5 : 95')).toBeInTheDocument();

    expect(within(aboutCard).getByText('A brand new token for sale!')).toBeInTheDocument();

    expect(getWeights).toHaveBeenCalledWith(mockTerraClient, 'terra1', 'uusd');
    expect(getPool).toHaveBeenCalledWith(mockTerraClient, 'terra1');

    dateNowSpy.mockRestore();
  });
});
Example #14
Source File: CreateMappingProfileFormRoute.test.js    From ui-data-export with Apache License 2.0 4 votes vote down vote up
describe('CreateMappingProfileFormRoute', () => {
  describe('creating new mapping profile', () => {
    const onSubmitNavigateMock = jest.fn();
    const onSubmitMock = jest.fn();
    const sendCalloutMock = jest.fn();

    beforeEach(() => {
      renderWithIntl(
        <CreateMappingProfileFormRouteContainer
          allTransformations={allMappingProfilesTransformations}
          sendCallout={sendCalloutMock}
          onSubmitNavigate={onSubmitNavigateMock}
          onSubmit={onSubmitMock}
        />,
        translationsProperties
      );
    });

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

    it('should initiate creating of mapping profile with correct values', async () => {
      const name = 'New mapping profile';
      const submitFormButton = screen.getByRole('button', { name: 'Save & close' });

      userEvent.type(screen.getByLabelText('Name*'), name);
      userEvent.click(recordTypesHoldings());
      userEvent.click(screen.getByRole('button', { name: 'Add transformations' }));

      const modal = screen.getByRole('document');
      const saveTransrormationsButton = within(modal).getByRole('button', { name: 'Save & close' });
      const tableRow = screen.getByRole('row', { name: 'Holdings - Call number - Call number' });
      const checkbox = within(tableRow).getByRole('checkbox');
      const textFields = within(tableRow).getAllByRole('textbox');

      userEvent.click(checkbox);
      userEvent.type(textFields[0], '500');
      userEvent.type(textFields[3], '$a');
      userEvent.click(saveTransrormationsButton);

      userEvent.click(submitFormButton);

      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(onSubmitNavigateMock).toHaveBeenCalled();
      });
    });

    it('should display validation error when name field is empty', () => {
      userEvent.click(recordTypesHoldings());
      userEvent.click(saveAndCloseBtn());

      expect(getByText(document.querySelector('[data-test-mapping-profile-form-name]'), 'Please enter a value')).toBeVisible();
      expect(screen.getByLabelText('Name*')).not.toBeValid();
      expect(onSubmitMock).not.toBeCalled();
    });

    it('should initiate displaying of error callout', async () => {
      onSubmitMock.mockImplementationOnce(() => Promise.reject());

      const submitFormButton = screen.getByRole('button', { name: 'Save & close' });

      userEvent.type(screen.getByLabelText('Name*'), 'Name');
      userEvent.click(screen.getByRole('checkbox', { name: 'Holdings' }));
      userEvent.click(screen.getByRole('button', { name: 'Add transformations' }));

      const modal = screen.getByRole('document');
      const saveTransrormationsButton = within(modal).getByRole('button', { name: 'Save & close' });
      const tableRow = screen.getByRole('row', { name: 'Holdings - Call number - Call number' });
      const checkbox = within(tableRow).getByRole('checkbox');
      const textFields = within(tableRow).getAllByRole('textbox');

      userEvent.click(checkbox);
      userEvent.type(textFields[0], '500');
      userEvent.type(textFields[3], '$a');
      userEvent.click(saveTransrormationsButton);

      userEvent.click(submitFormButton);

      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 #15
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 #16
Source File: JobProfileDetailsRoute.test.js    From ui-data-export with Apache License 2.0 4 votes vote down vote up
describe('JobProfileDetails', () => {
  let server;

  beforeEach(() => {
    server = new Pretender();
  });

  afterEach(() => {
    server.shutdown();
  });

  describe('rendering details for a job profile without job profile data', () => {
    it('should display preloader', async () => {
      server.get('/data-export/mapping-profiles/:id', () => [
        200,
        { 'content-type': 'application/json' },
        JSON.stringify(mappingProfile),
      ]);
      setupJobProfileDetailsRoute();

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

      expect(dialog).toBeVisible();
      expect(document.querySelector('[data-test-preloader]')).toBeVisible();
    });

    it('should history includes location.search', () => {
      server.get('/data-export/mapping-profiles/:id', () => [
        200,
        { 'content-type': 'application/json' },
        JSON.stringify(mappingProfile),
      ]);
      setupJobProfileDetailsRoute();

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

      expect(cancelButton).toBeEnabled();

      userEvent.click(cancelButton);

      expect(history.some(el => el.includes('?location'))).toBeTruthy();
    });
  });

  describe('rendering details for a job profile without mapping profile data', () => {
    it('should display preloader', async () => {
      server.get('/data-export/job-profiles/:id', () => [
        200,
        { 'content-type': 'application/json' },
        JSON.stringify(jobProfile),
      ]);
      setupJobProfileDetailsRoute({ matchParams: { id: JOB_PROFILE_ID } });

      expect(document.querySelector('[data-test-preloader]')).toBeVisible();
    });
  });

  describe('rendering details for a job with mapping profile', () => {
    it('should display preloader for default job profile', async () => {
      server.get('/data-export/job-profiles/:id', () => [
        200,
        { 'content-type': 'application/json' },
        JSON.stringify(jobProfile),
      ]);
      server.get('/data-export/mapping-profiles/:id', () => [
        200,
        { 'content-type': 'application/json' },
        JSON.stringify(mappingProfile),
      ]);

      setupJobProfileDetailsRoute({ matchParams: { id: JOB_PROFILE_ID } });

      expect(document.querySelector('[data-test-preloader]')).toBeVisible();
    });
  });

  describe('rendering details for non default job profile without job execution data', () => {
    const nonDefaultJobProfileId = 'job-profile-id';

    it('should display job profile details for non default job profile', async () => {
      server.get('/data-export/job-profiles/:id', () => [
        200,
        { 'content-type': 'application/json' },
        JSON.stringify({
          ...jobProfile,
          id: nonDefaultJobProfileId,
        }),
      ]);
      server.get('/data-export/mapping-profiles/:id', () => [
        200,
        { 'content-type': 'application/json' },
        JSON.stringify(mappingProfile),
      ]);
      setupJobProfileDetailsRoute({ matchParams: { id: nonDefaultJobProfileId } });

      const summary = await screen.findByRole('region', { name: /summary/i });

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

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

  describe('rendering details for default job profile with job execution data', () => {
    it('should display job profile details', async () => {
      server.get('/data-export/job-profiles/:id', () => [
        200,
        { 'content-type': 'application/json' },
        JSON.stringify(jobProfile),
      ]);
      server.get('/data-export/mapping-profiles/:id', () => [
        200,
        { 'content-type': 'application/json' },
        JSON.stringify(mappingProfile),
      ]);

      setupJobProfileDetailsRoute();

      const summary = await screen.findByRole('region', { name: /summary/i });

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

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

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

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

      labelsAndValues.forEach(el => expect(within(summary).getByText(el)).toBeVisible());
    });
  });
});
Example #17
Source File: Tabs-test.js    From Lambda with MIT License 4 votes vote down vote up
describe('<Tabs />', () => {
  beforeEach(() => resetIdCounter());

  beforeAll(() => {
    // eslint-disable-next-line no-console
    console.error = (error, ...args) => {
      if (args.length > 0 && typeof error === 'string') {
        if (error.endsWith('%s%s')) {
          throw new Error(format(error.slice(0, -2), ...args.slice(0, -1)));
        }
        throw new Error(format(error, ...args));
      }
      throw new Error(error);
    };
  });

  describe('props', () => {
    test('should have sane defaults', () => {
      expectToMatchSnapshot(createTabs());
    });

    test('should honor positive defaultIndex prop', () => {
      expectToMatchSnapshot(createTabs({ defaultIndex: 1 }));
    });

    test('should honor negative defaultIndex prop', () => {
      expectToMatchSnapshot(createTabs({ defaultIndex: -1 }));
    });

    test('should call onSelect when selection changes', () => {
      const called = { index: -1, last: -1 };
      render(
        createTabs({
          onSelect(index, last) {
            called.index = index;
            called.last = last;
          },
        }),
      );

      userEvent.click(screen.getByTestId('tab2'));

      expect(called.index).toBe(1);
      expect(called.last).toBe(0);
    });

    test('should accept className', () => {
      expectToMatchSnapshot(createTabs({ className: 'foobar' }));
    });

    test('should accept domRef', () => {
      let domNode;
      render(
        createTabs({
          domRef: (node) => {
            domNode = node;
          },
        }),
      );

      expect(domNode).not.toBeUndefined();
      expect(domNode.className).toBe('react-tabs');
    });
  });

  describe('child props', () => {
    test('should reset ids correctly', () => {
      expectToMatchSnapshot(createTabs());

      resetIdCounter();

      expectToMatchSnapshot(createTabs());
    });
  });

  describe('interaction', () => {
    describe('mouse', () => {
      test('should update selectedIndex when clicked', () => {
        render(createTabs());
        userEvent.click(screen.getByTestId('tab2'));

        assertTabSelected(2);
      });

      test('should update selectedIndex when tab child is clicked', () => {
        render(createTabs());
        userEvent.click(screen.getByTestId('tab3'));

        assertTabSelected(3);
      });

      test('should not change selectedIndex when clicking a disabled tab', () => {
        render(createTabs({ defaultIndex: 0 }));
        userEvent.click(screen.getByTestId('tab4'));

        assertTabSelected(1);
      });
    });

    describe('keyboard', () => {
      test('should update selectedIndex when arrow right key pressed', () => {
        render(createTabs());
        const element = screen.getByTestId('tab1');
        userEvent.click(element);
        userEvent.type(element, '{arrowright}');

        assertTabSelected(2);
      });

      test('should update selectedIndex when arrow left key pressed (RTL)', () => {
        render(createTabs({ direction: 'rtl' }));
        const element = screen.getByTestId('tab1');
        userEvent.click(element);
        userEvent.type(element, '{arrowleft}');

        assertTabSelected(2);
      });

      test.skip('should not change selectedIndex when arrow left key pressed on a disabled tab', () => {
        render(createTabs());
        const element = screen.getByTestId('tab4');
        userEvent.click(element);
        userEvent.type(element, '{arrowleft}');

        assertTabSelected(1);
      });
    });
  });

  describe('performance', () => {
    test('should only render the selected tab panel', () => {
      render(createTabs());
      const tabPanels = screen.getAllByRole('tabpanel');

      expect(tabPanels[0]).toHaveTextContent('Hello Tab1');
      expect(tabPanels[1]).toHaveTextContent('');
      expect(tabPanels[2]).toHaveTextContent('');
      expect(tabPanels[3]).toHaveTextContent('');

      userEvent.click(screen.getByTestId('tab2'));

      expect(tabPanels[0]).toHaveTextContent('');
      expect(tabPanels[1]).toHaveTextContent('Hello Tab2');
      expect(tabPanels[2]).toHaveTextContent('');
      expect(tabPanels[3]).toHaveTextContent('');

      userEvent.click(screen.getByTestId('tab3'));

      expect(tabPanels[0]).toHaveTextContent('');
      expect(tabPanels[1]).toHaveTextContent('');
      expect(tabPanels[2]).toHaveTextContent('Hello Tab3');
      expect(tabPanels[3]).toHaveTextContent('');
    });

    test('should render all tabs if forceRenderTabPanel is true', () => {
      expectToMatchSnapshot(createTabs({ forceRenderTabPanel: true }));
    });
  });

  describe('validation', () => {
    test('should result with warning when tabs/panels are imbalanced', () => {
      expect(() =>
        render(
          <Tabs>
            <TabList>
              <Tab>Foo</Tab>
            </TabList>
          </Tabs>,
        ),
      ).toThrowErrorMatchingSnapshot();
    });

    test('should result with warning when tab outside of tablist', () => {
      expect(() =>
        render(
          <Tabs>
            <TabList>
              <Tab>Foo</Tab>
            </TabList>
            <Tab>Foo</Tab>
            <TabPanel />
            <TabPanel />
          </Tabs>,
        ),
      ).toThrowErrorMatchingSnapshot();
    });

    test('should result with warning when multiple tablist components exist', () => {
      expect(() =>
        render(
          <Tabs>
            <TabList>
              <Tab>Foo</Tab>
            </TabList>
            <TabList>
              <Tab>Foo</Tab>
            </TabList>
            <TabPanel />
            <TabPanel />
          </Tabs>,
        ),
      ).toThrowErrorMatchingSnapshot();
    });

    test('should result with warning when onSelect missing when selectedIndex set', () => {
      expect(() =>
        render(
          <Tabs selectedIndex={1}>
            <TabList>
              <Tab>Foo</Tab>
            </TabList>
            <TabPanel>Foo</TabPanel>
          </Tabs>,
        ),
      ).toThrowErrorMatchingSnapshot();
    });

    test('should result with warning when defaultIndex and selectedIndex set', () => {
      expect(() =>
        render(
          <Tabs selectedIndex={1} defaultIndex={1}>
            <TabList>
              <Tab>Foo</Tab>
            </TabList>
            <TabPanel>Foo</TabPanel>
          </Tabs>,
        ),
      ).toThrowErrorMatchingSnapshot();
    });

    test('should result with warning when tabs/panels are imbalanced and it should ignore non tab children', () => {
      expect(() =>
        render(
          <Tabs>
            <TabList>
              <Tab>Foo</Tab>
              <div>+</div>
            </TabList>

            <TabPanel>Hello Foo</TabPanel>
            <TabPanel>Hello Bar</TabPanel>
          </Tabs>,
        ),
      ).toThrowErrorMatchingSnapshot();
    });

    test('should allow random order for elements', () => {
      expectToMatchSnapshot(
        <Tabs forceRenderTabPanel>
          <TabPanel>Hello Foo</TabPanel>
          <TabList>
            <Tab>Foo</Tab>
            <Tab>Bar</Tab>
          </TabList>
          <TabPanel>Hello Bar</TabPanel>
        </Tabs>,
      );
    });

    test('should not throw a warning when wrong element is found', () => {
      expectToMatchSnapshot(
        <Tabs>
          <TabList>
            <Tab />
            <div />
          </TabList>
          <TabPanel />
        </Tabs>,
      );
    });

    test('should be okay with rendering without any children', () => {
      expectToMatchSnapshot(<Tabs />);
    });

    test('should be okay with rendering just TabList', () => {
      expectToMatchSnapshot(
        <Tabs>
          <TabList />
        </Tabs>,
      );
    });

    test('should gracefully render null', () => {
      expectToMatchSnapshot(
        <Tabs>
          <TabList>
            <Tab>Tab A</Tab>
            {false && <Tab>Tab B</Tab>}
          </TabList>
          <TabPanel>Content A</TabPanel>
          {false && <TabPanel>Content B</TabPanel>}
        </Tabs>,
      );
    });

    test('should support nested tabs', () => {
      render(
        <Tabs data-testid="first">
          <TabList>
            <Tab data-testid="tab1" />
            <Tab />
          </TabList>
          <TabPanel data-testid="panel1">
            Hello Tab1
            <Tabs data-testid="second">
              <TabList>
                <Tab />
                <Tab data-testid="tab2" />
              </TabList>
              <TabPanel />
              <TabPanel data-testid="panel2">Hello Tab2</TabPanel>
            </Tabs>
          </TabPanel>
          <TabPanel />
        </Tabs>,
      );

      userEvent.click(within(screen.getByTestId('second')).getByTestId('tab2'));

      assertTabSelected(1);
      assertTabSelected(2, within(screen.getByTestId('second')));
    });

    test('should allow other DOM nodes', () => {
      expectToMatchSnapshot(
        <Tabs>
          <div id="tabs-nav-wrapper">
            <button type="button">Left</button>
            <div className="tabs-container">
              <TabList>
                <Tab />
                <Tab />
              </TabList>
            </div>
            <button type="button">Right</button>
          </div>
          <div className="tab-panels">
            <TabPanel />
            <TabPanel />
          </div>
        </Tabs>,
      );
    });
  });

  test('should pass through custom properties', () => {
    expectToMatchSnapshot(<Tabs data-tooltip="Tooltip contents" />);
  });

  test('should not add known props to dom', () => {
    expectToMatchSnapshot(<Tabs defaultIndex={3} />);
  });

  test('should cancel if event handler returns false', () => {
    render(createTabs({ onSelect: () => false }));

    assertTabSelected(1);

    userEvent.click(screen.getByTestId('tab2'));
    assertTabSelected(1);

    userEvent.click(screen.getByTestId('tab3'));
    assertTabSelected(1);
  });

  test('should trigger onSelect handler when clicking', () => {
    let wasClicked = false;
    render(
      createTabs({
        onSelect: () => {
          wasClicked = true;
        },
      }),
    );

    assertTabSelected(1);

    userEvent.click(screen.getByTestId('tab2'));
    assertTabSelected(2);
    expect(wasClicked).toBe(true);
  });

  test('should trigger onSelect handler when clicking on open tab', () => {
    let wasClicked = false;
    render(
      createTabs({
        onSelect: () => {
          wasClicked = true;
        },
      }),
    );

    assertTabSelected(1);

    userEvent.click(screen.getByTestId('tab1'));
    assertTabSelected(1);
    expect(wasClicked).toBe(true);
  });

  test('should switch tabs if setState is called within onSelect', () => {
    class Wrap extends React.Component {
      state = {};

      handleSelect = () => this.setState({ foo: 'bar' });

      render() {
        const { foo } = this.state;
        return createTabs({
          onSelect: this.handleSelect,
          className: foo,
        });
      }
    }

    render(<Wrap />);

    userEvent.click(screen.getByTestId('tab2'));
    assertTabSelected(2);

    userEvent.click(screen.getByTestId('tab3'));
    assertTabSelected(3);
  });

  test('should allow for higher order components', () => {
    expectToMatchSnapshot(
      <Tabs>
        <TabListWrapper>
          <TabWrapper>Foo</TabWrapper>
          <TabWrapper>Bar</TabWrapper>
        </TabListWrapper>
        <TabPanelWrapper>Foo</TabPanelWrapper>
        <TabPanelWrapper>Bar</TabPanelWrapper>
      </Tabs>,
    );
  });

  test('should allow string children', () => {
    expectToMatchSnapshot(
      <Tabs>
        Foo
        <TabList>
          Foo
          <Tab>Foo</Tab>
          Foo
          <Tab>Bar</Tab>
          Foo
        </TabList>
        <TabPanel>Bar</TabPanel>
        <TabPanel>Foo</TabPanel>
        Foo
      </Tabs>,
    );
  });
});
Example #18
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 #19
Source File: CommentsView.test.jsx    From frontend-app-discussions with GNU Affero General Public License v3.0 4 votes vote down vote up
describe('CommentsView', () => {
  beforeEach(async () => {
    initializeMockApp({
      authenticatedUser: {
        userId: 3,
        username: 'abc123',
        administrator: true,
        roles: [],
      },
    });

    store = initializeStore();
    Factory.resetAll();
    axiosMock = new MockAdapter(getAuthenticatedHttpClient());
    axiosMock.onGet(threadsApiUrl)
      .reply(200, Factory.build('threadsResult'));
    axiosMock.onPatch(new RegExp(`${commentsApiUrl}*`)).reply(({
      url,
      data,
    }) => {
      const commentId = url.match(/comments\/(?<id>[a-z1-9-]+)\//).groups.id;
      const {
        rawBody,
      } = camelCaseObject(JSON.parse(data));
      return [200, Factory.build('comment', {
        id: commentId,
        rendered_body: rawBody,
        raw_body: rawBody,
      })];
    });
    axiosMock.onPost(commentsApiUrl)
      .reply(({ data }) => {
        const {
          rawBody,
          threadId,
        } = camelCaseObject(JSON.parse(data));
        return [200, Factory.build(
          'comment',
          {
            rendered_body: rawBody,
            raw_body: rawBody,
            thread_id: threadId,
          },
        )];
      });

    await executeThunk(fetchThreads(courseId), store.dispatch, store.getState);
    mockAxiosReturnPagedComments();
    mockAxiosReturnPagedCommentsResponses();
  });

  describe('for all post types', () => {
    function assertLastUpdateData(data) {
      expect(JSON.parse(axiosMock.history.patch[axiosMock.history.patch.length - 1].data)).toMatchObject(data);
    }

    it('should show and hide the editor', async () => {
      renderComponent(discussionPostId);
      await waitFor(() => screen.findByText('comment number 1', { exact: false }));
      await act(async () => {
        fireEvent.click(
          screen.getByRole('button', { name: /add a response/i }),
        );
      });
      expect(screen.queryByTestId('tinymce-editor')).toBeInTheDocument();
      await act(async () => {
        fireEvent.click(screen.getByRole('button', { name: /cancel/i }));
      });
      expect(screen.queryByTestId('tinymce-editor')).not.toBeInTheDocument();
    });
    it('should allow posting a response', async () => {
      renderComponent(discussionPostId);
      await waitFor(() => screen.findByText('comment number 1', { exact: false }));
      await act(async () => {
        fireEvent.click(
          screen.getByRole('button', { name: /add a response/i }),
        );
      });
      act(() => {
        fireEvent.change(screen.getByTestId('tinymce-editor'), { target: { value: 'testing123' } });
      });

      await act(async () => {
        fireEvent.click(
          screen.getByText(/submit/i),
        );
      });
      expect(screen.queryByTestId('tinymce-editor')).not.toBeInTheDocument();
      await waitFor(async () => expect(await screen.findByText('testing123', { exact: false })).toBeInTheDocument());
    });
    it('should allow posting a comment', async () => {
      renderComponent(discussionPostId);
      await waitFor(() => screen.findByText('comment number 1', { exact: false }));
      await act(async () => {
        fireEvent.click(
          screen.getAllByRole('button', { name: /add a comment/i })[0],
        );
      });
      act(() => {
        fireEvent.change(screen.getByTestId('tinymce-editor'), { target: { value: 'testing123' } });
      });

      await act(async () => {
        fireEvent.click(
          screen.getByText(/submit/i),
        );
      });
      expect(screen.queryByTestId('tinymce-editor')).not.toBeInTheDocument();
      await waitFor(async () => expect(await screen.findByText('testing123', { exact: false })).toBeInTheDocument());
    });
    it('should allow editing an existing comment', async () => {
      renderComponent(discussionPostId);
      await waitFor(() => screen.findByText('comment number 1', { exact: false }));
      await act(async () => {
        fireEvent.click(
          // The first edit menu is for the post, the second will be for the first comment.
          screen.getAllByRole('button', { name: /actions menu/i })[1],
        );
      });
      await act(async () => {
        fireEvent.click(screen.getByRole('button', { name: /edit/i }));
      });
      act(() => {
        fireEvent.change(screen.getByTestId('tinymce-editor'), { target: { value: 'testing123' } });
      });
      await act(async () => {
        fireEvent.click(screen.getByRole('button', { name: /submit/i }));
      });
      await waitFor(async () => {
        expect(await screen.findByText('testing123', { exact: false })).toBeInTheDocument();
      });
    });

    async function setupCourseConfig(reasonCodesEnabled = true) {
      axiosMock.onGet(`${courseConfigApiUrl}${courseId}/`).reply(200, {
        user_is_privileged: true,
        reason_codes_enabled: reasonCodesEnabled,
        editReasons: [
          { code: 'reason-1', label: 'reason 1' },
          { code: 'reason-2', label: 'reason 2' },
        ],
        postCloseReasons: [
          { code: 'reason-1', label: 'reason 1' },
          { code: 'reason-2', label: 'reason 2' },
        ],
      });
      axiosMock.onGet(`${courseConfigApiUrl}${courseId}/settings`).reply(200, {});
      await executeThunk(fetchCourseConfig(courseId), store.dispatch, store.getState);
    }

    it('should show reason codes when editing an existing comment', async () => {
      setupCourseConfig();
      renderComponent(discussionPostId);
      await waitFor(() => screen.findByText('comment number 1', { exact: false }));
      await act(async () => {
        fireEvent.click(
          // The first edit menu is for the post, the second will be for the first comment.
          screen.getAllByRole('button', { name: /actions menu/i })[1],
        );
      });
      await act(async () => {
        fireEvent.click(screen.getByRole('button', { name: /edit/i }));
      });
      expect(screen.queryByRole('combobox', { name: /reason for editing/i })).toBeInTheDocument();
      expect(screen.getAllByRole('option', { name: /reason \d/i })).toHaveLength(2);
      await act(async () => {
        fireEvent.change(screen.queryByRole('combobox', { name: /reason for editing/i }), { target: { value: null } });
      });
      await act(async () => {
        fireEvent.change(screen.queryByRole('combobox', { name: /reason for editing/i }), { target: { value: 'reason-1' } });
      });
      await act(async () => {
        fireEvent.change(screen.getByTestId('tinymce-editor'), { target: { value: 'testing123' } });
      });
      await act(async () => {
        fireEvent.click(screen.getByRole('button', { name: /submit/i }));
      });
      assertLastUpdateData({ edit_reason_code: 'reason-1' });
    });

    it('should show reason codes when closing a post', async () => {
      setupCourseConfig();
      renderComponent(discussionPostId);
      await act(async () => {
        fireEvent.click(
          // The first edit menu is for the post
          screen.getAllByRole('button', {
            name: /actions menu/i,
          })[0],
        );
      });
      expect(screen.queryByRole('dialog', { name: /close post/i })).not.toBeInTheDocument();
      await act(async () => {
        fireEvent.click(screen.getByRole('button', { name: /close/i }));
      });
      expect(screen.queryByRole('dialog', { name: /close post/i })).toBeInTheDocument();
      expect(screen.queryByRole('combobox', { name: /reason/i })).toBeInTheDocument();
      expect(screen.getAllByRole('option', { name: /reason \d/i })).toHaveLength(2);
      await act(async () => {
        fireEvent.change(screen.queryByRole('combobox', { name: /reason/i }), { target: { value: 'reason-1' } });
      });
      await act(async () => {
        fireEvent.click(screen.getByRole('button', { name: /close post/i }));
      });
      expect(screen.queryByRole('dialog', { name: /close post/i })).not.toBeInTheDocument();
      assertLastUpdateData({ closed: true, close_reason_code: 'reason-1' });
    });

    it('should close the post directly if reason codes are not enabled', async () => {
      setupCourseConfig(false);
      renderComponent(discussionPostId);
      await act(async () => {
        fireEvent.click(
          // The first edit menu is for the post
          screen.getAllByRole('button', { name: /actions menu/i })[0],
        );
      });
      expect(screen.queryByRole('dialog', { name: /close post/i })).not.toBeInTheDocument();
      await act(async () => {
        fireEvent.click(screen.getByRole('button', { name: /close/i }));
      });
      expect(screen.queryByRole('dialog', { name: /close post/i })).not.toBeInTheDocument();
      assertLastUpdateData({ closed: true });
    });

    it.each([true, false])(
      'should reopen the post directly when reason codes enabled=%s',
      async (reasonCodesEnabled) => {
        setupCourseConfig(reasonCodesEnabled);
        renderComponent(closedPostId);
        await act(async () => {
          fireEvent.click(
            // The first edit menu is for the post
            screen.getAllByRole('button', { name: /actions menu/i })[0],
          );
        });
        expect(screen.queryByRole('dialog', { name: /close post/i })).not.toBeInTheDocument();
        await act(async () => {
          fireEvent.click(screen.getByRole('button', { name: /reopen/i }));
        });
        expect(screen.queryByRole('dialog', { name: /close post/i })).not.toBeInTheDocument();
        assertLastUpdateData({ closed: false });
      },
    );

    it('should show the editor if the post is edited', async () => {
      setupCourseConfig(false);
      renderComponent(discussionPostId);
      await act(async () => {
        fireEvent.click(
          // The first edit menu is for the post
          screen.getAllByRole('button', { name: /actions menu/i })[0],
        );
      });
      await act(async () => {
        fireEvent.click(screen.getByRole('button', { name: /edit/i }));
      });
      expect(testLocation.pathname).toBe(`/${courseId}/posts/${discussionPostId}/edit`);
    });
    it('should allow pinning the post', async () => {
      renderComponent(discussionPostId);
      await act(async () => {
        fireEvent.click(
          // The first edit menu is for the post
          screen.getAllByRole('button', { name: /actions menu/i })[0],
        );
      });
      await act(async () => {
        fireEvent.click(screen.getByRole('button', { name: /pin/i }));
      });
      assertLastUpdateData({ pinned: false });
    });
    it('should allow reporting the post', async () => {
      renderComponent(discussionPostId);
      await act(async () => {
        fireEvent.click(
          // The first edit menu is for the post
          screen.getAllByRole('button', { name: /actions menu/i })[0],
        );
      });
      await act(async () => {
        fireEvent.click(screen.getByRole('button', { name: /report/i }));
      });
      assertLastUpdateData({ abuse_flagged: true });
    });

    it('handles liking a comment', async () => {
      renderComponent(discussionPostId);

      // Wait for the content to load
      await screen.findByText('comment number 7', { exact: false });
      const view = screen.getByTestId('comment-comment-1');

      const likeButton = within(view).getByRole('button', { name: /like/i });
      await act(async () => {
        fireEvent.click(likeButton);
      });
      expect(axiosMock.history.patch).toHaveLength(2);
      expect(JSON.parse(axiosMock.history.patch[1].data)).toMatchObject({ voted: true });
    });

    it.each([
      ['endorsing comments', 'Endorse', { endorsed: true }],
      ['reporting comments', 'Report', { abuse_flagged: true }],
    ])('handles %s', async (label, buttonLabel, patchData) => {
      renderComponent(discussionPostId);

      // Wait for the content to load
      await screen.findByText('comment number 7', { exact: false });

      // There should be three buttons, one for the post, the second for the
      // comment and the third for a response to that comment
      const actionButtons = screen.queryAllByRole('button', { name: /actions menu/i });
      await act(async () => {
        fireEvent.click(actionButtons[1]);
      });
      await act(async () => {
        fireEvent.click(screen.getByRole('button', { name: buttonLabel }));
      });
      expect(axiosMock.history.patch).toHaveLength(2);
      expect(JSON.parse(axiosMock.history.patch[1].data)).toMatchObject(patchData);
    });
  });

  describe('for discussion thread', () => {
    const findLoadMoreCommentsButton = () => screen.findByTestId('load-more-comments');

    it('shown spinner when post isn\'t loaded', async () => {
      renderComponent('unloaded-id');
      expect(await screen.findByTestId('loading-indicator'))
        .toBeInTheDocument();
    });

    it('initially loads only the first page', async () => {
      renderComponent(discussionPostId);
      expect(await screen.findByText('comment number 1', { exact: false }))
        .toBeInTheDocument();
      expect(screen.queryByText('comment number 2', { exact: false }))
        .not
        .toBeInTheDocument();
    });

    it('pressing load more button will load next page of comments', async () => {
      renderComponent(discussionPostId);

      const loadMoreButton = await findLoadMoreCommentsButton();
      fireEvent.click(loadMoreButton);

      await screen.findByText('comment number 1', { exact: false });
      await screen.findByText('comment number 2', { exact: false });
    });

    it('newly loaded comments are appended to the old ones', async () => {
      renderComponent(discussionPostId);

      const loadMoreButton = await findLoadMoreCommentsButton();
      fireEvent.click(loadMoreButton);

      await screen.findByText('comment number 1', { exact: false });
      // check that comments from the first page are also displayed
      expect(screen.queryByText('comment number 2', { exact: false }))
        .toBeInTheDocument();
    });

    it('load more button is hidden when no more comments pages to load', async () => {
      const totalPages = 2;
      renderComponent(discussionPostId);

      const loadMoreButton = await findLoadMoreCommentsButton();
      for (let page = 1; page < totalPages; page++) {
        fireEvent.click(loadMoreButton);
      }

      await screen.findByText('comment number 2', { exact: false });
      await expect(findLoadMoreCommentsButton())
        .rejects
        .toThrow();
    });
  });

  describe('for question thread', () => {
    const findLoadMoreCommentsButtons = () => screen.findAllByTestId('load-more-comments');

    it('initially loads only the first page', async () => {
      act(() => renderComponent(questionPostId));
      expect(await screen.findByText('comment number 3', { exact: false }))
        .toBeInTheDocument();
      expect(await screen.findByText('endorsed comment number 5', { exact: false }))
        .toBeInTheDocument();
      expect(screen.queryByText('comment number 4', { exact: false }))
        .not
        .toBeInTheDocument();
    });

    it('pressing load more button will load next page of comments', async () => {
      act(() => {
        renderComponent(questionPostId);
      });

      const [loadMoreButtonEndorsed, loadMoreButtonUnendorsed] = await findLoadMoreCommentsButtons();
      // Both load more buttons should show
      expect(await findLoadMoreCommentsButtons()).toHaveLength(2);
      expect(await screen.findByText('unendorsed comment number 3', { exact: false }))
        .toBeInTheDocument();
      expect(await screen.findByText('endorsed comment number 5', { exact: false }))
        .toBeInTheDocument();
      // Comments from next page should not be loaded yet.
      expect(await screen.queryByText('endorsed comment number 6', { exact: false }))
        .not
        .toBeInTheDocument();
      expect(await screen.queryByText('unendorsed comment number 4', { exact: false }))
        .not
        .toBeInTheDocument();

      await act(async () => {
        fireEvent.click(loadMoreButtonEndorsed);
      });
      // Endorsed comment from next page should be loaded now.
      await waitFor(() => expect(screen.queryByText('endorsed comment number 6', { exact: false }))
        .toBeInTheDocument());
      // Unendorsed comment from next page should not be loaded yet.
      expect(await screen.queryByText('unendorsed comment number 4', { exact: false }))
        .not
        .toBeInTheDocument();
      // Now only one load more buttons should show, for unendorsed comments
      expect(await findLoadMoreCommentsButtons()).toHaveLength(1);
      await act(async () => {
        fireEvent.click(loadMoreButtonUnendorsed);
      });
      // Unendorsed comment from next page should be loaded now.
      await waitFor(() => expect(screen.queryByText('unendorsed comment number 4', { exact: false }))
        .toBeInTheDocument());
      await expect(findLoadMoreCommentsButtons()).rejects.toThrow();
    });
  });

  describe('comments responses', () => {
    const findLoadMoreCommentsResponsesButton = () => screen.findByTestId('load-more-comments-responses');

    it('initially loads only the first page', async () => {
      renderComponent(discussionPostId);

      await waitFor(() => screen.findByText('comment number 7', { exact: false }));
      expect(screen.queryByText('comment number 8', { exact: false })).not.toBeInTheDocument();
    });

    it('pressing load more button will load next page of responses', async () => {
      renderComponent(discussionPostId);

      const loadMoreButton = await findLoadMoreCommentsResponsesButton();
      await act(async () => {
        fireEvent.click(loadMoreButton);
      });

      await screen.findByText('comment number 8', { exact: false });
    });

    it('newly loaded responses are appended to the old ones', async () => {
      renderComponent(discussionPostId);

      const loadMoreButton = await findLoadMoreCommentsResponsesButton();
      await act(async () => {
        fireEvent.click(loadMoreButton);
      });

      await screen.findByText('comment number 8', { exact: false });
      // check that comments from the first page are also displayed
      expect(screen.queryByText('comment number 7', { exact: false })).toBeInTheDocument();
    });

    it('load more button is hidden when no more responses pages to load', async () => {
      const totalPages = 2;
      renderComponent(discussionPostId);

      const loadMoreButton = await findLoadMoreCommentsResponsesButton();
      for (let page = 1; page < totalPages; page++) {
        act(() => {
          fireEvent.click(loadMoreButton);
        });
      }

      await screen.findByText('comment number 8', { exact: false });
      await expect(findLoadMoreCommentsResponsesButton())
        .rejects
        .toThrow();
    });

    it('handles liking a comment', async () => {
      renderComponent(discussionPostId);

      // Wait for the content to load
      await screen.findByText('comment number 7', { exact: false });
      const view = screen.getByTestId('comment-comment-1');

      const likeButton = within(view).getByRole('button', { name: /like/i });
      await act(async () => {
        fireEvent.click(likeButton);
      });
      expect(axiosMock.history.patch).toHaveLength(2);
      expect(JSON.parse(axiosMock.history.patch[1].data)).toMatchObject({ voted: true });
    });

    it.each([
      ['endorsing comments', 'Endorse', { endorsed: true }],
      ['reporting comments', 'Report', { abuse_flagged: true }],
    ])('handles %s', async (label, buttonLabel, patchData) => {
      renderComponent(discussionPostId);

      // Wait for the content to load
      await screen.findByText('comment number 7', { exact: false });

      // There should be three buttons, one for the post, the second for the
      // comment and the third for a response to that comment
      const actionButtons = screen.queryAllByRole('button', { name: /actions menu/i });
      await act(async () => {
        fireEvent.click(actionButtons[1]);
      });
      await act(async () => {
        fireEvent.click(screen.getByRole('button', { name: buttonLabel }));
      });
      expect(axiosMock.history.patch).toHaveLength(2);
      expect(JSON.parse(axiosMock.history.patch[1].data)).toMatchObject(patchData);
    });
  });

  describe.each([
    { component: 'post', testId: 'post-thread-1' },
    { component: 'comment', testId: 'comment-comment-1' },
    { component: 'reply', testId: 'reply-comment-7' },
  ])('delete confirmation modal', ({
    component,
    testId,
  }) => {
    test(`for ${component}`, async () => {
      renderComponent(discussionPostId);
      // Wait for the content to load
      await waitFor(() => expect(screen.queryByText('comment number 7', { exact: false })).toBeInTheDocument());
      const content = screen.getByTestId(testId);
      const actionsButton = within(content).getAllByRole('button', { name: /actions menu/i })[0];
      await act(async () => {
        fireEvent.click(actionsButton);
      });
      expect(screen.queryByRole('dialog', { name: /delete \w+/i, exact: false })).not.toBeInTheDocument();
      const deleteButton = within(content).queryByRole('button', { name: /delete/i });
      await act(async () => {
        fireEvent.click(deleteButton);
      });
      expect(screen.queryByRole('dialog', { name: /delete \w+/i, exact: false })).toBeInTheDocument();
      await act(async () => {
        fireEvent.click(screen.queryByRole('button', { name: /delete/i }));
      });
      expect(screen.queryByRole('dialog', { name: /delete \w+/i, exact: false })).not.toBeInTheDocument();
    });
  });
});
Example #20
Source File: TopicsView.test.jsx    From frontend-app-discussions with GNU Affero General Public License v3.0 4 votes vote down vote up
describe('TopicsView', () => {
  describe.each(['legacy', 'openedx'])('%s provider', (provider) => {
    let inContextTopics;
    let globalTopics;
    let categories;
    beforeEach(() => {
      initializeMockApp({
        authenticatedUser: {
          userId: 3,
          username: 'abc123',
          administrator: true,
          roles: [],
        },
      });

      store = initializeStore({
        config: { provider },
        blocks: {
          topics: {},
        },
      });
      Factory.resetAll();
      axiosMock = new MockAdapter(getAuthenticatedHttpClient());
      lastLocation = undefined;
    });

    async function setupMockResponse() {
      if (provider === 'legacy') {
        axiosMock
          .onGet(topicsApiUrl)
          .reply(200, {
            courseware_topics: Factory.buildList('category', 2),
            non_courseware_topics: Factory.buildList('topic', 3, {}, { topicPrefix: 'ncw' }),
          });
        await executeThunk(fetchCourseTopics(courseId), store.dispatch, store.getState);
        const state = store.getState();
        categories = state.topics.categoryIds;
        globalTopics = selectNonCoursewareTopics(state);
        inContextTopics = selectCoursewareTopics(state);
      } else {
        const blocksAPIResponse = getBlocksAPIResponse(true);
        const ids = Object.values(blocksAPIResponse.blocks).filter(block => block.type === 'vertical')
          .map(block => block.block_id);
        const deletedIds = [
          'block-v1:edX+DemoX+Demo_Course+type@vertical+block@deleted-vertical-1',
          'block-v1:edX+DemoX+Demo_Course+type@vertical+block@deleted-vertical-2',
        ];
        const data = [
          ...Factory.buildList('topic.v2', 2, { usage_key: null }, { topicPrefix: 'ncw' }),
          ...ids.map(id => Factory.build('topic.v2', { id })),
          ...deletedIds.map(id => Factory.build('topic.v2', { id, enabled_in_context: false }, { topicPrefix: 'archived ' })),
        ];

        axiosMock
          .onGet(topicsv2ApiUrl)
          .reply(200, data);
        axiosMock.onGet(blocksAPIURL)
          .reply(200, getBlocksAPIResponse(true));
        axiosMock.onAny().networkError();
        await executeThunk(fetchCourseBlocks(courseId, 'abc123'), store.dispatch, store.getState);
        await executeThunk(fetchCourseTopics(courseId), store.dispatch, store.getState);
        const state = store.getState();
        categories = selectSequences(state);
        globalTopics = selectNonCoursewareTopics(state);
        inContextTopics = selectCoursewareTopics(state);
      }
    }

    it('displays non-courseware topics', async () => {
      await setupMockResponse();
      renderComponent();

      globalTopics.forEach(topic => {
        expect(screen.queryByText(topic.name)).toBeInTheDocument();
      });
    });

    it('displays non-courseware outside of a topic group', async () => {
      await setupMockResponse();
      renderComponent();

      categories.forEach(category => {
        // For the new provider categories are blocks so use the display name
        // otherwise use the category itself which is a string
        expect(screen.queryByText(category.displayName || category)).toBeInTheDocument();
      });

      const topicGroups = screen.queryAllByTestId('topic-group');
      // For the new provider there should be a section for archived topics
      expect(topicGroups).toHaveLength(
        provider === DiscussionProvider.LEGACY
          ? categories.length
          : categories.length + 1,
      );
    });

    if (provider === DiscussionProvider.OPEN_EDX) {
      it('displays archived topics', async () => {
        await setupMockResponse();
        renderComponent();
        const archivedTopicGroup = screen.queryAllByTestId('topic-group').pop();
        expect(archivedTopicGroup).toHaveTextContent(/archived/i);
        const archivedTopicLinks = within(archivedTopicGroup).queryAllByRole('link');
        expect(archivedTopicLinks).toHaveLength(2);
      });
    }

    it('displays courseware topics', async () => {
      await setupMockResponse();
      renderComponent();

      inContextTopics.forEach(topic => {
        expect(screen.queryByText(topic.name)).toBeInTheDocument();
      });
    });

    it('clicking on courseware topic (category) takes to category page', async () => {
      await setupMockResponse();
      renderComponent();

      const categoryName = categories[0].displayName || categories[0];
      const categoryPath = provider === 'legacy' ? categoryName : categories[0].id;
      const topic = await screen.findByText(categoryName);
      fireEvent.click(topic);
      expect(lastLocation.pathname.endsWith(`/category/${categoryPath}`)).toBeTruthy();
    });
  });
});
Example #21
Source File: withHandlerGenerator.test.jsx    From covid with GNU General Public License v3.0 4 votes vote down vote up
test("withHandlerGenerator correctly generates a HOC to create a Wrapped component with data as a prop", async () => {
  // Generate a testing HOC
  const withIndex = (WrappedComponent, name = "index") =>
    withHandlerGenerator(
      withBackendHandlerHOC,
      ({ testParam }) => ({ testParam }),
      ({ testParam }, Handler, setIndex) => {
        const handler = new Handler(testParam);
        return handler.index(setIndex);
      },
      name,
      WrappedComponent
    );

  // Use the generated testing HOC to pass `index` as a prop
  const TestComponent = withIndex(
    ({ index: index_, role, testParam, ...props }) => {
      const { testParam: testParamIndex, ...index } = index_;
      return (
        <span role={role}>
          <span data-testid="index" {...index} />
          <span data-testid="testParam" value={testParam} />
          <span data-testid="testParamIndex" value={testParamIndex} />
        </span>
      );
    }
  );

  let rendered;

  await act(async () => {
    const paramValue = "test-value";
    rendered = render(
      <TestComponent role="test-component" testParam={paramValue} />
    );

    // Initial <Loading/> state
    const wrapperInitial = rendered.getByRole("wrapper");
    expect(wrapperInitial).toBeInTheDocument();

    const loading = within(wrapperInitial).getByRole("loading");
    expect(loading).toBeInTheDocument();

    await delay(20);

    // Final state, with `index` as a prop in the wrapped component
    const wrapper = rendered.getByRole("wrapper");
    expect(wrapper).toBeInTheDocument();

    const wrapped = within(wrapper).getByRole("test-component");
    expect(wrapped).toBeInTheDocument();

    const index = within(wrapped).getByRole("tested");
    expect(index).toBeInTheDocument();
    expect(index.getAttribute("data-testid")).toBe("index");

    const testParam = within(wrapped).getByTestId("testParam");
    expect(testParam).toBeInTheDocument();

    const testParamIndex = within(wrapped).getByTestId("testParamIndex");
    expect(testParamIndex).toBeInTheDocument();

    expect(testParamIndex.getAttribute("value")).toBe(paramValue);
    expect(testParamIndex.getAttribute("value")).toBe(
      testParam.getAttribute("value")
    );
  });

  await act(async () => {
    const paramValue = "test-value-2";
    rendered.rerender(
      <TestComponent role="test-component" testParam={paramValue} />
    );

    await delay(0);

    const testParam = rendered.getByTestId("testParam");
    expect(testParam).toBeInTheDocument();

    const testParamIndex = rendered.getByTestId("testParamIndex");
    expect(testParamIndex).toBeInTheDocument();

    expect(testParamIndex.getAttribute("value")).toBe(paramValue);
    expect(testParamIndex.getAttribute("value")).toBe(
      testParam.getAttribute("value")
    );
  });
});
Example #22
Source File: swap_card.spec.js    From astroport-lbp-frontend with MIT License 4 votes vote down vote up
describe('SwapCard', () => {
  const pair = buildPair({
    contractAddr: 'terra1',
    tokenContractAddr: 'terra2'
  });

  const saleTokenInfo = {
    symbol: 'FOO',
    decimals: 5
  };

  const ustExchangeRate = 0.99;

  let onSwapTxMined;

  beforeEach(() => {
    onSwapTxMined = jest.fn();
  });

  function renderCard({ ustPrice } = {}) {
    render(
      <SwapCard
        pair={pair}
        saleTokenInfo={saleTokenInfo}
        ustExchangeRate={ustExchangeRate}
        ustPrice={ustPrice || new Dec(1)}
        onSwapTxMined={onSwapTxMined}
      />
    );
  }

  it('runs simulation, populates "to" field with simulated amount received, and calculates price impact', async () => {
    getSimulation.mockResolvedValue({
      return_amount: '200000000' // 0.50 UST valuation
    });

    getBalance.mockResolvedValue(2000 * 1e6); // Simulation does a basic balance check

    renderCard({ ustPrice: new Dec(0.49) });

    // Wait for balance
    await waitForBalances({ fromBalance: '2,000' });

    const fromInput = screen.getByLabelText('From');
    const toInput = screen.getByLabelText('To (estimated)');

    await act(async () => {
      // We need to delay between inputs otherwise we end up with a field value of "1"
      await userEvent.type(fromInput, '1000', { delay: 1 });
    });

    // "From" value is correctly converted to USD
    const fromField = fromInput.closest('.border');
    expect(within(fromField).getByText('($990.00)')).toBeInTheDocument(); // 1000 * 0.99

    // "To" value is properly set to value returned by simulation
    expect(toInput).toHaveDisplayValue('2000');

    // "To" value is correctly converted to USD
    const toField = toInput.closest('.border');
    expect(within(toField).getByText('($970.20)')).toBeInTheDocument(); // 2000 * 0.49 * .99

    // Simulated price is $0.01 higher than the spot price ($0.49),
    // so the price impact is $0.01/$0.49 = 0.0204
    expect(getDescriptionByTermEl(screen.getByText('Price Impact'))).toHaveTextContent('2.04%');

    expect(getSimulation).toHaveBeenCalledWith(
      mockTerraClient,
      'terra1',
      new Int(1000000000),
      {
        native_token: {
          denom: 'uusd'
        }
      }
    );
  });

  it('runs reverse simulation and populates "from" field with simulated amount required', async () => {
    getReverseSimulation.mockResolvedValue({
      offer_amount: '42000000'
    });

    // Simulation does a basic balance check
    getBalance.mockResolvedValue(2000 * 1e6);
    getTokenBalance.mockResolvedValue(2000 * 1e6);

    renderCard({ ustPrice: new Dec(5.95) });

    const toInput = screen.getByLabelText('To (estimated)');

    // Wait for balances
    await waitForBalances({ fromBalance: '2,000' });

    await act(async () => {
      await userEvent.type(toInput, '7');
    });

    // "From" value is properly set to value returned by reverse simulation
    expect(screen.getByLabelText('From')).toHaveDisplayValue('42');

    // Simulated price is $0.05 higher than the spot price ($5.95),
    // so the price impact is $0.05/$5.95 = 0.0084
    expect(getDescriptionByTermEl(screen.getByText('Price Impact'))).toHaveTextContent('0.84%');

    expect(getReverseSimulation).toHaveBeenCalledWith(
      mockTerraClient,
      'terra1',
      new Int(700000),
      {
        token: {
          contract_addr: 'terra2'
        }
      }
    );
  });

  it('runs new simulation when assets are reversed', async () => {
    getSimulation.mockImplementation((_, pairAddress, amount, offerAssetInfo) => {
      if(offerAssetInfo.native_token) {
        // Mocked response when offer asset is the native token
        return {
          return_amount: '210000000' // 5 decimals
        };
      } else {
        // Mocked response when offer asset is the sale token
        return {
          return_amount: '2000000' // 6 decimals
        }
      }
    });

    getBalance.mockResolvedValue(2000 * 1e6);
    getTokenBalance.mockResolvedValue(2000 * 1e6);

    renderCard({ ustPrice: new Dec(.48) });

    // Wait for balances
    await waitForBalances({ fromBalance: '2,000' });

    // First enter a from value (UST -> FOO)
    const fromInput = screen.getByLabelText('From');
    await act(async () => {
      await userEvent.type(fromInput, '4');
    });

    // Assert simulated value set
    expect(screen.getByLabelText('To (estimated)')).toHaveDisplayValue('2100');

    // Reverse the assets (FOO -> UST)
    await act(async () => {
      await userEvent.click(screen.getByRole('button', { name: 'Reverse assets' }));
    });

    // "To" value is properly set to value returned by simulation
    expect(screen.getByLabelText('To (estimated)')).toHaveDisplayValue('2');

    // Simulated price is $0.02 higher than the spot price ($0.48),
    // so the price impact is $0.02/$0.48 = 0.0417
    expect(getDescriptionByTermEl(screen.getByText('Price Impact'))).toHaveTextContent('4.17%');

    // First simulation when initial "from" amount was entered
    expect(getSimulation).toHaveBeenCalledWith(
      mockTerraClient,
      'terra1',
      new Int(4 * 1e6), // 6 decimals
      {
        native_token: {
          denom: 'uusd'
        }
      }
    );

    // Second simulation when "from" asset was changed
    expect(getSimulation).toHaveBeenCalledWith(
      mockTerraClient,
      'terra1',
      new Int(4 * 1e5), // 5 decimals
      {
        token: {
          contract_addr: 'terra2'
        }
      }
    );
  });

  it('performs native -> token swap, displays success message, and updates balances', async () => {
    // Simulation is performed on input change
    getSimulation.mockResolvedValue({
      return_amount: String(5 * 1e5)
    });

    // Before balances
    getBalance.mockResolvedValueOnce(2000000);
    getTokenBalance.mockResolvedValueOnce(0);

    // After balances
    getBalance.mockResolvedValueOnce(1000000);
    getTokenBalance.mockResolvedValueOnce(5 * 1e5);

    // Mock fee fetching
    const fee = jest.fn();
    estimateFee.mockResolvedValue(fee);

    // Successful post
    postMsg.mockResolvedValue({ txhash: '123ABC' });

    // Stub out balance check
    sufficientBalance.mockResolvedValue(true);

    renderCard();

    // Initial balances
    await waitForBalances({ fromBalance: '2', toBalance: '0' });

    const fromInput = screen.getByLabelText('From');
    await act(async () => {
      await userEvent.type(fromInput, '1');
    });

    // Mock mined tx to trigger balance update
    mockTerraClient.tx.txInfo.mockResolvedValue({});

    await act(async () => {
      await userEvent.click(screen.getByRole('button', { name: 'Swap' }));
    });

    expect(screen.getByText('Transaction Complete')).toBeInTheDocument();

    const txLink = screen.getByRole('link', { name: '123ABC' });
    expect(txLink).toBeInTheDocument();
    expect(txLink.getAttribute('href')).toEqual('https://finder.terra.money/testnet/tx/123ABC');

    await act(async () => {
      await userEvent.click(screen.getByRole('button', { name: 'Continue' }));
    });

    // New balances
    await waitForBalances({ fromBalance: '1', toBalance: '5' });

    // Estimates fee and posts message with estimated fee
    const msg = buildSwapFromNativeTokenMsg({
      walletAddress: 'terra42',
      pair,
      intAmount: new Int(1e6)
    });
    expect(estimateFee).toHaveBeenCalledTimes(1);
    expect(estimateFee).toHaveBeenCalledWith(mockTerraClient, msg);

    expect(postMsg).toHaveBeenCalledTimes(1);
    expect(postMsg).toHaveBeenCalledWith(mockTerraClient, { msg, fee });

    // Fetches tx info
    expect(mockTerraClient.tx.txInfo).toHaveBeenCalledWith('123ABC');

    // Invokes callback
    expect(onSwapTxMined).toHaveBeenCalledTimes(1);
  });

  it('performs token -> native token swap, displays success message, and updates balances', async () => {
    // Simulation is performed on input change
    getSimulation.mockResolvedValue({
      return_amount: String(1e6)
    });

    // Before balances
    getTokenBalance.mockResolvedValueOnce(10 * 1e5);
    getBalance.mockResolvedValueOnce(0);

    // After balances
    getTokenBalance.mockResolvedValueOnce(5 * 1e5);
    getBalance.mockResolvedValueOnce(1e6);

    // Mock fee fetching
    const fee = jest.fn();
    estimateFee.mockResolvedValue(fee);

    // Successful post
    postMsg.mockResolvedValue({ txhash: 'ABC123' });

    // Stub out balance check
    sufficientBalance.mockResolvedValue(true);

    renderCard();

    // Initial balances
    await waitForBalances({ fromBalance: '0', toBalance: '10' });

    // Reverse the assets (FOO -> UST)
    await act(async () => {
      await userEvent.click(screen.getByRole('button', { name: 'Reverse assets' }));
    });

    const fromInput = screen.getByLabelText('From');
    await act(async () => {
      await userEvent.type(fromInput, '5');
    });

    // Mock mined tx to trigger balance update
    mockTerraClient.tx.txInfo.mockResolvedValue({});

    await act(async () => {
      await userEvent.click(screen.getByRole('button', { name: 'Swap' }));
    });

    expect(screen.getByText('Transaction Complete')).toBeInTheDocument();

    const txLink = screen.getByRole('link', { name: 'ABC123' });
    expect(txLink).toBeInTheDocument();
    expect(txLink.getAttribute('href')).toEqual('https://finder.terra.money/testnet/tx/ABC123');

    await act(async () => {
      await userEvent.click(screen.getByRole('button', { name: 'Continue' }));
    });

    // New balances
    await waitForBalances({ fromBalance: '5', toBalance: '1' });

    // Estimates fee and posts message with estimated fee
    const msg = buildSwapFromContractTokenMsg({
      walletAddress: 'terra42',
      pair,
      intAmount: new Int(5e5)
    });
    expect(estimateFee).toHaveBeenCalledTimes(1);
    expect(estimateFee).toHaveBeenCalledWith(mockTerraClient, msg);

    expect(postMsg).toHaveBeenCalledTimes(1);
    expect(postMsg).toHaveBeenCalledWith(mockTerraClient, { msg, fee });

    // Fetches tx info
    expect(mockTerraClient.tx.txInfo).toHaveBeenCalledWith('ABC123');
  });

  it('performs swap after setting from amount to balance less fees when swapping from native token', async () => {
    getBalance.mockResolvedValue(new Int(1000 * 1e6));
    getTokenBalance.mockResolvedValue(new Int(0));

    const fee = new StdFee(200000, new Coins(
      [new Coin('uusd', 999999)]
    ));
    feeForMaxNativeToken.mockResolvedValue(fee);

    // Setting max from asset triggers a forward simulation
    getSimulation.mockResolvedValue({ return_amount: '500000000' });

    // Successful post
    postMsg.mockResolvedValue({ txhash: '123ABC' });

    // Stub out balance check
    sufficientBalance.mockResolvedValue(true);

    renderCard();

    // Wait for balances to load
    await waitForBalances({ fromBalance: '1,000' });

    await act(async () => {
      await userEvent.click(screen.getByRole('button', { name: 'Max' }));
    });

    // "From" value is properly set to value balance less fees
    expect(screen.getByLabelText('From')).toHaveDisplayValue('999.000001');

    // Perform swap
    await act(async () => {
      await userEvent.click(screen.getByRole('button', { name: 'Swap' }));
    });

    expect(screen.getByText('Transaction Complete')).toBeInTheDocument();

    // Posts message with max fee
    const msg = buildSwapFromNativeTokenMsg({
      walletAddress: 'terra42',
      pair,
      intAmount: new Int(999000001)
    });
    expect(postMsg).toHaveBeenCalledTimes(1);
    expect(postMsg).toHaveBeenCalledWith(mockTerraClient, { msg, fee });

    // Does not estimate fee for from amount
    // (this is calculated differently for "max" amount)
    expect(estimateFee).not.toHaveBeenCalled();
  });

  it('performs swap after setting from amount to balance of contract token', async () => {
    getBalance.mockResolvedValue(new Int(1000 * 1e6));
    getTokenBalance.mockResolvedValue(new Int(5000 * 1e5));

    const fee = new StdFee(200000, new Coins(
      [new Coin('uusd', 30000)]
    ));
    estimateFee.mockResolvedValue(fee);

    // Setting max from asset triggers a forward simulation
    getSimulation.mockResolvedValue({ return_amount: '1000000000' });

    // Successful post
    postMsg.mockResolvedValue({ txhash: '123ABC' });

    // Stub out balance check
    sufficientBalance.mockResolvedValue(true);

    renderCard();

    // Wait for balances to load
    await waitForBalances({ fromBalance: '1,000', toBalance: '5,000' });

    // Reverse the assets (FOO -> UST)
    await act(async () => {
      await userEvent.click(screen.getByRole('button', { name: 'Reverse assets' }));
    });

    // Use max FOO tokens
    await act(async () => {
      await userEvent.click(screen.getByRole('button', { name: 'Max' }));
    });

    // "From" value is properly set to entire token balance
    expect(screen.getByLabelText('From')).toHaveDisplayValue('5000');

    // Perform swap
    await act(async () => {
      await userEvent.click(screen.getByRole('button', { name: 'Swap' }));
    });

    expect(screen.getByText('Transaction Complete')).toBeInTheDocument();

    // Posts message with max contract tokens
    // and still estimates fee (uusd)
    const msg = buildSwapFromContractTokenMsg({
      walletAddress: 'terra42',
      pair,
      intAmount: new Int(5000 * 1e5)
    });
    expect(estimateFee).toHaveBeenCalledTimes(1);
    expect(estimateFee).toHaveBeenCalledWith(mockTerraClient, msg);

    expect(postMsg).toHaveBeenCalledTimes(1);
    expect(postMsg).toHaveBeenCalledWith(mockTerraClient, { msg, fee });
  });

  it('conveys error state to user and does not invoke onSwapTxMined callback if extension responds with error when sending message', async() => {
    // Simulation is performed on input change
    getSimulation.mockResolvedValue({
      return_amount: String(1e6)
    });

    getBalance.mockResolvedValue(10 * 1e6);
    getTokenBalance.mockResolvedValue(0);
    sufficientBalance.mockResolvedValue(true);

    // Failed post
    postMsg.mockRejectedValue({ code: 1 });

    renderCard();

    // Wait for balances
    await waitForBalances({ fromBalance: '10' });

    await act(async () => {
      await userEvent.type(screen.getByLabelText('From'), '5');
    });

    await act(async () => {
      await userEvent.click(screen.getByRole('button', { name: 'Swap' }));
    });

    expect(screen.queryByText('Error submitting transaction')).toBeInTheDocument();

    await act(async () => {
      await userEvent.click(screen.getByRole('button', { name: 'Continue' }));
    });

    expect(screen.queryByText('Error submitting transaction')).not.toBeInTheDocument();

    // Does not invoke callback
    expect(onSwapTxMined).not.toHaveBeenCalled();
  });

  it('displays and reports error when an error is thrown while selecting max balance', async() => {
    getBalance.mockResolvedValue(new Int(1000 * 1e6));
    getTokenBalance.mockResolvedValue(new Int(0));

    const mockError = jest.fn();
    feeForMaxNativeToken.mockRejectedValue(mockError);

    renderCard();

    // Wait for balances to load
    await waitForBalances({ fromBalance: '1,000' });

    await act(async () => {
      await userEvent.click(screen.getByRole('button', { name: 'Max' }));
    });

    expect(screen.queryByText('Unable to swap max balance')).toBeInTheDocument();
    expect(reportException).toHaveBeenCalledTimes(1);
    expect(reportException).toHaveBeenCalledWith(mockError);
  });

  it('displays and reports error when simulation fails', async () => {
    const mockError = jest.fn();
    getSimulation.mockRejectedValue(mockError);

    getBalance.mockResolvedValue(2000 * 1e6); // Simulation does a basic balance check

    renderCard({ ustPrice: new Dec(0.49) });

    // Wait for balance
    await waitForBalances({ fromBalance: '2,000' });

    const fromInput = screen.getByLabelText('From');
    const toInput = screen.getByLabelText('To (estimated)');

    await act(async () => {
      await userEvent.type(fromInput, '1');
    });

    expect(screen.queryByText('Simulation failed')).toBeInTheDocument();

    // "To" value is not set
    expect(toInput).toHaveDisplayValue('');

    // "To" value is still $0
    const toField = toInput.closest('.border');
    expect(within(toField).getByText('($0.00)')).toBeInTheDocument();

    // Price impact is not calculated or displayed
    expect(screen.queryByText('Price Impact')).not.toBeInTheDocument();

    // Error is reported
    expect(reportException).toHaveBeenCalledTimes(1);
    expect(reportException).toHaveBeenCalledWith(mockError);
  });

  it('runs simulation and calculates price impact when from balance is insufficient, but displays error and does not calculate fees', async () => {
    getSimulation.mockResolvedValue({
      return_amount: '200000000' // 0.50 UST valuation
    });

    getBalance.mockResolvedValue(50 * 1e6);

    renderCard({ ustPrice: new Dec(0.49) });

    // Wait for balance
    await waitForBalances({ fromBalance: '50' });

    const fromInput = screen.getByLabelText('From');
    const toInput = screen.getByLabelText('To (estimated)');

    await act(async () => {
      // We need to delay between inputs otherwise we end up with a field value of "1"
      await userEvent.type(fromInput, '1000', { delay: 1 });
    });

    // "From" value is correctly converted to USD
    const fromField = fromInput.closest('.border');
    expect(within(fromField).getByText('($990.00)')).toBeInTheDocument(); // 1000 * 0.99

    // "To" value is properly set to value returned by simulation
    expect(toInput).toHaveDisplayValue('2000');

    // "To" value is correctly converted to USD
    const toField = toInput.closest('.border');
    expect(within(toField).getByText('($970.20)')).toBeInTheDocument(); // 2000 * 0.49 * .99

    // Simulated price is $0.01 higher than the spot price ($0.49),
    // so the price impact is $0.01/$0.49 = 0.0204
    expect(getDescriptionByTermEl(screen.getByText('Price Impact'))).toHaveTextContent('2.04%');

    expect(screen.queryByText('Not enough UST')).toBeInTheDocument();

    expect(getSimulation).toHaveBeenCalledWith(
      mockTerraClient,
      'terra1',
      new Int(1000000000),
      {
        native_token: {
          denom: 'uusd'
        }
      }
    );

    // Fees should have been estimated for each key stroke up until "10",
    // then "100" and "1000" exceeded the balance of 50
    expect(estimateFee).toHaveBeenCalledTimes(2);
  });

  it('displays pending state while waiting for tx to be mined', async () => {
    jest.useFakeTimers();

    // Simulation is performed on input change
    getSimulation.mockResolvedValue({
      return_amount: String(5 * 1e5)
    });

    // Before balances
    getBalance.mockResolvedValueOnce(2000000);
    getTokenBalance.mockResolvedValueOnce(0);

    // After balances
    getBalance.mockResolvedValueOnce(1000000);
    getTokenBalance.mockResolvedValueOnce(5 * 1e5);

    // Successful post
    postMsg.mockResolvedValue({ txhash: '123ABC' });

    // Stub out balance check
    sufficientBalance.mockResolvedValue(true);

    renderCard();

    // Initial balances
    await waitForBalances({ fromBalance: '2', toBalance: '0' });

    const fromInput = screen.getByLabelText('From');
    await act(async () => {
      await userEvent.type(fromInput, '1');
    });

    // Mock pending tx (404)
    mockTerraClient.tx.txInfo.mockRejectedValue();

    await act(async () => {
      await userEvent.click(screen.getByRole('button', { name: 'Swap' }));
    });

    expect(screen.getByText('Please Wait')).toBeInTheDocument();

    let txLink = screen.getByRole('link', { name: '123ABC' });
    expect(txLink).toBeInTheDocument();
    expect(txLink.getAttribute('href')).toEqual('https://finder.terra.money/testnet/tx/123ABC');

    // Mock mined tx
    mockTerraClient.tx.txInfo.mockResolvedValue({});

    // Blockchain is polled every 5s until tx is mined
    act(() => {
      jest.advanceTimersByTime(5000);
    });

    expect(await screen.findByText('Transaction Complete')).toBeInTheDocument();

    txLink = screen.getByRole('link', { name: '123ABC' });
    expect(txLink).toBeInTheDocument();
    expect(txLink.getAttribute('href')).toEqual('https://finder.terra.money/testnet/tx/123ABC');

    await act(async () => {
      await userEvent.click(screen.getByRole('button', { name: 'Continue' }));
    });

    // New balances
    await waitForBalances({ fromBalance: '1', toBalance: '5' });

    // Invokes callback
    expect(onSwapTxMined).toHaveBeenCalledTimes(1);
  });
});
Example #23
Source File: app.spec.js    From astroport-lbp-frontend with MIT License 4 votes vote down vote up
describe('App', () => {
  it('renders Scheduled and Previous Token Sales cards', async () => {
    const dateNowSpy = jest
      .spyOn(Date, 'now')
      .mockImplementation(() => new Date(2021, 5, 9).getTime());

    // Mock toLocaleString to always use en-US locale in EDT timezone
    const toLocaleStringSpy = jest.spyOn(Date.prototype, 'toLocaleString');
    toLocaleStringSpy.mockImplementation(
      function (locale, options) {
        return new Intl.DateTimeFormat('en-US', { ...options, timeZone: 'America/New_York' }).format(this);
      }
    )

    const currentPair = buildPair({
      startTime: Math.floor(Date.UTC(2021, 5, 8, 12)/1000),
      endTime: Math.floor(Date.UTC(2021, 5, 10, 12)/1000),
      tokenContractAddr: 'terra3',
      contractAddr: 'terra3-pair-addr'
    });

    // This pair would be displayed as scheduled if permitted
    const unpermittedPair = buildPair({
      startTime: Math.floor(Date.UTC(2021, 5, 10, 12)/1000),
      endTime: Math.floor(Date.UTC(2021, 5, 14, 12)/1000),
      tokenContractAddr: 'terra4',
      contractAddr: 'terra4-pair-addr'
    });

    getLBPs.mockResolvedValue([
      buildPair({
        startTime: Math.floor(Date.UTC(2021, 0, 1, 12)/1000),
        endTime: Math.floor(Date.UTC(2021, 0, 4, 12)/1000),
        tokenContractAddr: 'terra1',
        contractAddr: 'terra1-pair-addr'
      }),
      buildPair({
        startTime: Math.floor(Date.UTC(2021, 5, 10, 12)/1000),
        endTime: Math.floor(Date.UTC(2021, 5, 14, 12)/1000),
        tokenContractAddr: 'terra2',
        contractAddr: 'terra2-pair-addr'
      }),
      unpermittedPair,
      currentPair
    ]);

    getPairInfo.mockResolvedValue(currentPair);

    getTokenInfo.mockImplementation((_, address) => (
      {
        terra1: {
          name: 'Foo'
        },
        terra2: {
          name: 'Bar'
        },
        terra3: {
          name: 'Baz'
        },
        terra4: {
          name: 'Bad'
        }
      }[address]
    ));

    render(<App />);

    // Heading with sale token name
    expect(await screen.findByText('Baz Token Sale')).toBeInTheDocument();

    // Current token info component
    expect(await screen.findByText('Current Token Info')).toBeInTheDocument();

    // Tokens are in the correct cards with the correct time/dates
    const scheduledCard = (await screen.findByText('Scheduled Token Sales')).closest('div')
    const previousCard = (await screen.findByText('Previous Token Sales')).closest('div')

    const barCell = await within(scheduledCard).findByText('Bar');
    expect(barCell).toBeInTheDocument();
    expect(within(barCell.closest('tr')).queryByText('06/10/2021, 08:00 AM EDT')).toBeInTheDocument();

    const fooCell = await within(previousCard).findByText('Foo')
    expect(fooCell).toBeInTheDocument();
    expect(within(fooCell.closest('tr')).queryByText('01/01/2021 - 01/04/2021')).toBeInTheDocument();

    // Tokens are not present in the wrong cards
    expect(within(scheduledCard).queryByText('Foo')).toBeNull();
    expect(within(scheduledCard).queryByText('Baz')).toBeNull();
    expect(within(previousCard).queryByText('Bar')).toBeNull();
    expect(within(previousCard).queryByText('Baz')).toBeNull();

    // It should have fetched info for the current sale
    expect(getPairInfo).toHaveBeenCalledTimes(1);
    expect(getPairInfo).toHaveBeenCalledWith(expect.anything(), 'terra3-pair-addr');

    // Unpermitted pair should never be displayed
    expect(screen.queryByText('Bad')).not.toBeInTheDocument();

    dateNowSpy.mockRestore();
  });

  it('displays partial wallet address after successful browser extension connection', async () => {
    connectExtension.mockResolvedValue({ address: 'terra1234567890' });

    const currentPair = buildPair({ contractAddr: 'terra1-pair-addr' });

    getLBPs.mockResolvedValue([
      currentPair
    ]);

    getPairInfo.mockResolvedValue(currentPair);

    getTokenInfo.mockResolvedValue({
      name: 'Foo'
    });

    render(<App />);

    // Wait for data to load and Connect Wallet button to become visible
    await screen.findByText('Connect Wallet');

    // Wallet address should not yet be displayed
    expect(screen.queryByText('567890')).toBeNull();

    await act(async () => {
      await userEvent.click(screen.getByText('Connect Wallet'));
    })

    expect(screen.getByText('terra1...567890')).toBeInTheDocument();
  });

  it('automatically reconnects extension if it was connected previously', async () => {
    const getItemSpy = jest.spyOn(window.localStorage.__proto__, 'getItem');
    getItemSpy.mockImplementation((key) => {
      return {
        terraStationExtensionPreviouslyConnected: true
      }[key]
    });

    connectExtension.mockResolvedValue({ address: 'terra1234567890' });

    const currentPair = buildPair({ contractAddr: 'terra1-pair-addr' });

    getLBPs.mockResolvedValue([
      currentPair
    ]);

    getPairInfo.mockResolvedValue(currentPair);

    getTokenInfo.mockResolvedValue({
      name: 'Foo'
    });

    render(<App />);

    expect(await screen.findByText('terra1...567890')).toBeInTheDocument();

    expect(getItemSpy).toHaveBeenCalledTimes(1);
    expect(getItemSpy).toHaveBeenCalledWith('terraStationExtensionPreviouslyConnected');
  });

  it('disconnects wallet', async () => {
    const getItemSpy = jest.spyOn(window.localStorage.__proto__, 'getItem');
    const removeItemSpy = jest.spyOn(window.localStorage.__proto__, 'removeItem');
    getItemSpy.mockImplementation((key) => {
      return {
        terraStationExtensionPreviouslyConnected: true
      }[key]
    });

    connectExtension.mockResolvedValue({ address: 'terra1234567890' });

    const currentPair = buildPair({ contractAddr: 'terra1-pair-addr'} );

    getLBPs.mockResolvedValue([
      currentPair
    ]);

    getPairInfo.mockResolvedValue(currentPair);

    getTokenInfo.mockResolvedValue({
      name: 'Foo'
    });

    render(<App />);

    expect(await screen.findByText('terra1...567890')).toBeInTheDocument();

    await act(async () => {
      await userEvent.click(screen.getByRole('button', { name: 'Disconnect wallet' }));
    })

    expect(screen.queryByText('terra1...567890')).not.toBeInTheDocument();

    expect(getItemSpy).toHaveBeenCalledTimes(1);
    expect(getItemSpy).toHaveBeenCalledWith('terraStationExtensionPreviouslyConnected');

    expect(removeItemSpy).toHaveBeenCalledTimes(1);
    expect(removeItemSpy).toHaveBeenCalledWith('terraStationExtensionPreviouslyConnected');
  });
});
Example #24
Source File: PeoplePage.test.jsx    From sgmr-service with MIT License 4 votes vote down vote up
describe('People page', () => {
  const mockAxios = new MockAdapter(axios);
  beforeEach(() => {
    mockAxios.reset();
    NotificationBanner.mockReturnValue(null);
  });

  it('should show the list of people saved on your account', async () => {
    mockAxios
      .onGet(`${PEOPLE_URL}`, 'people')
      .reply(200, MockedAccountPeopleList);
    renderPage();

    await waitFor(() => {
      expect(screen.getByText('Saved people')).toBeInTheDocument();
      expect(screen.getByText('Fred Flintstone')).toBeInTheDocument();
      expect(screen.getByText('1Fred')).toBeInTheDocument();
      expect(screen.getByText('01/01/2025')).toBeInTheDocument();
      expect(screen.getByText('Barney Rubble')).toBeInTheDocument();
      expect(screen.getByText('2Barney')).toBeInTheDocument();
      expect(screen.getByText('01/12/2030')).toBeInTheDocument();
      expect(screen.getByText('Add new person')).toHaveClass('govuk-button govuk-button--secondary');
    });
  });

  it('should show the the h1 & add person button if there are 0 people saved on your account', async () => {
    mockAxios
      .onGet(`${PEOPLE_URL}`, 'people')
      .reply(204);
    renderPage();

    await waitFor(() => {
      expect(screen.getByText('Saved people')).toBeInTheDocument();
      expect(screen.getByText('Add new person')).toHaveClass('govuk-button govuk-button--secondary');
    });
  });

  it('should sort the list of people alphabetically by last name, firstname', async () => {
    mockAxios
      .onGet(`${PEOPLE_URL}`, 'people')
      .reply(200, MockedAccountPeopleUnsortedList);
    renderPage();

    await waitFor(() => {
      const rows = screen.getAllByTestId('row');
      // test first names appear in correct order based on sort by lastName>firstName
      expect(within(rows[0]).queryByText('Fred Flintstone')).toBeInTheDocument();
      expect(within(rows[1]).queryByText('Pebbles Flintstone')).toBeInTheDocument();
      expect(within(rows[2]).queryByText('Wilma Flintstone')).toBeInTheDocument();
      expect(within(rows[3]).queryByText('BamBam Rubble')).toBeInTheDocument();
      expect(within(rows[4]).queryByText('Barney Rubble')).toBeInTheDocument();
      expect(within(rows[5]).queryByText('Betty Rubble')).toBeInTheDocument();
    });
  });

  it('should take you to the add person form when you click add new person', async () => {
    mockAxios
      .onGet(`${PEOPLE_URL}`, 'people')
      .reply(200, MockedAccountPeopleList);
    renderPage();

    await waitFor(() => {
      expect(screen.getByText('Add new person')).toHaveClass('govuk-button govuk-button--secondary');
      fireEvent.click(screen.getByText('Add new person'));
    });

    await waitFor(() => {
      expect(mockHistoryPush).toHaveBeenCalledWith('/people/save-person/page-1');
    });
  });

  it('should take you to the DELETE person page when you click remove', async () => {
    mockAxios
      .onGet(`${PEOPLE_URL}`, 'people')
      .reply(200, MockedAccountPeopleList);
    renderPage();

    await waitFor(() => {
      expect(screen.getByText('Fred Flintstone')).toBeInTheDocument();
      expect(screen.getAllByText('Remove')).toHaveLength(3);
      fireEvent.click(screen.getAllByText('Remove')[0]);
    });

    await waitFor(() => {
      expect(mockHistoryPush).toHaveBeenCalledWith('/people/1/delete');
    });
  });

  it('should take you to the edit person form when you click update', async () => {
    mockAxios
      .onGet(`${PEOPLE_URL}`, 'people')
      .reply(200, MockedAccountPeopleList);
    renderPage();

    await waitFor(() => {
      expect(screen.getByText('Fred Flintstone')).toBeInTheDocument();
      expect(screen.getAllByText('Update')).toHaveLength(3);
      fireEvent.click(screen.getAllByText('Update')[2]);
    });

    await waitFor(() => {
      expect(mockHistoryPush).toHaveBeenCalledWith('/people/edit-person/page-1', { peopleId: '3', source: 'edit' });
    });
  });
});
Example #25
Source File: App.test.js    From Simplify-Testing-with-React-Testing-Library with MIT License 4 votes vote down vote up
describe('Integration: Budget App', () => {
  function setOneDollarIncome() {
    user.click(screen.getByText(/set income/i));
    user.type(screen.getByRole('spinbutton'), '1');
    user.click(screen.getByText(/submit/i));
  }

  function createCarBudget(amount = '5') {
    user.click(screen.getByText(/create new budget/i));
    user.selectOptions(screen.getByRole('combobox', { name: /category/i }), [
      screen.getByText('Auto'),
    ]);
    user.type(screen.getByRole('spinbutton'), amount);
    user.click(screen.getByText(/add budget/i));
  }

  test('SetIncome, given income amount, sets income', () => {
    render(<App />);

    setOneDollarIncome();
    const leftOverBudget = screen.getByText(/left over:/i);
    const leftOverBudgetAmount = within(leftOverBudget).getByText(/\$1/i);

    expect(leftOverBudgetAmount).toBeInTheDocument();
    expect(
      screen.getByRole('heading', { name: /income: \$1/i })
    ).toBeInTheDocument();
  });

  describe('CreateNewBudget', () => {
    test.each`
      budgetAmount | spending           | leftOver
      ${'4'}       | ${'Spending: $5'}  | ${'$-4'}
      ${'5'}       | ${'Spending: $5'}  | ${'$-4'}
      ${'6'}       | ${'Spending: $10'} | ${'$-9'}
    `(
      'given budget, updates budget summary',
      ({ budgetAmount, spending, leftOver }) => {
        render(<App />);
        setOneDollarIncome();

        createCarBudget(budgetAmount);
        const leftOverBudget = screen.getByText(/left over:/i);
        const leftOverBudgetAmount = within(leftOverBudget).getByText(leftOver);

        expect(leftOverBudgetAmount).toBeInTheDocument();
        expect(
          screen.getByRole('heading', { name: spending })
        ).toBeInTheDocument();
      }
    );
    test('given budget, displays budget chart', () => {
      render(<App />);
      setOneDollarIncome();
      createCarBudget();

      expect(screen.getByTestId('chart')).toBeInTheDocument();
    });
  });
  describe('Budget', () => {
    test('given budget, displays details', () => {
      render(<App />);
      setOneDollarIncome();
      createCarBudget();

      const budgetList = screen.getByRole('listitem');

      expect(within(budgetList).getByText(/auto/i)).toBeInTheDocument();
      expect(
        screen.getByRole('heading', { name: /\$0 of \$5/i })
      ).toBeInTheDocument();
    });

    test('given budget expense, updates budget progress', () => {
      render(<App />);
      setOneDollarIncome();
      createCarBudget();

      user.click(screen.getByRole('button', { name: /arrowright/i }));

      expect(
        screen.getByRole('heading', { name: /\$5 of \$5/i })
      ).toBeInTheDocument();
    });
  });

  test('DeleteBudget, given deleted budget, budget removed from DOM', () => {
    render(<App />);
    setOneDollarIncome();
    createCarBudget();

    user.click(screen.getByLabelText(/trash can/i));

    expect(screen.queryByRole('listitem')).not.toBeInTheDocument();
  });
});
Example #26
Source File: verify.test.js    From treetracker-admin-client with GNU Affero General Public License v3.0 4 votes vote down vote up
describe('Verify', () => {
  let growerApi;
  let captureApi;
  //mock the growers api
  growerApi = require('../../api/growers').default;

  growerApi.getCount = () => {
    log.debug('mock getCount:');
    return Promise.resolve({ count: 2 });
  };
  growerApi.getGrower = () => {
    log.debug('mock getGrower:');
    return Promise.resolve(GROWER);
  };
  growerApi.getGrowerRegistrations = () => {
    log.debug('mock getGrowerRegistrations:');
    return Promise.resolve([]);
  };
  growerApi.getGrowerSelfies = (id) => {
    log.debug('mock getGrowerSelfies:');
    return Promise.resolve([{ planterPhotoUrl: '' }, { planterPhotoUrl: '' }]);
  };

  // mock the treeTrackerApi
  captureApi = require('../../api/treeTrackerApi').default;

  captureApi.getCaptureImages = () => {
    log.debug('mock getCaptureImages:');
    return Promise.resolve(CAPTURES);
  };
  captureApi.getCaptureCount = () => {
    log.debug('mock getCaptureCount:');
    return Promise.resolve({ count: 4 });
  };
  captureApi.getCaptureById = (_id) => {
    log.debug('mock getCaptureById:');
    return Promise.resolve(CAPTURE);
  };
  captureApi.getSpecies = () => {
    log.debug('mock getSpecies:');
    return Promise.resolve(SPECIES);
  };
  captureApi.getSpeciesById = (_id) => {
    log.debug('mock getSpeciesById:');
    return Promise.resolve(SPECIES[0]);
  };
  captureApi.getCaptureCountPerSpecies = () => {
    log.debug('mock getCaptureCountPerSpecies:');
    return Promise.resolve({ count: 7 });
  };
  captureApi.getTags = () => {
    log.debug('mock getTags:');
    return Promise.resolve(TAGS);
  };
  captureApi.getTagById = (_id) => {
    log.debug('mock getTagById:');
    return Promise.resolve(TAG);
  };
  captureApi.getOrganizations = () => {
    log.debug('mock getOrganizations:');
    return Promise.resolve(ORGS);
  };

  describe('with default values', () => {
    beforeEach(async () => {
      render(
        <ThemeProvider theme={theme}>
          <BrowserRouter>
            <AppProvider value={{ orgList: ORGS }}>
              <GrowerContext.Provider value={growerValues}>
                <VerifyProvider value={verifyValues}>
                  <SpeciesProvider value={speciesValues}>
                    <TagsContext.Provider value={tagsValues}>
                      <Verify />
                    </TagsContext.Provider>
                  </SpeciesProvider>
                </VerifyProvider>
              </GrowerContext.Provider>
            </AppProvider>
          </BrowserRouter>
        </ThemeProvider>
      );

      await act(() => captureApi.getCaptureImages());
      await act(() => captureApi.getCaptureCount());
      // await act(() => captureApi.getTags());
    });

    afterEach(cleanup);

    it('renders filter top', () => {
      const filter = screen.getByRole('button', { name: /filter/i });
      userEvent.click(filter);
      // screen.logTestingPlaygroundURL();

      const verifyStatus = screen.getByLabelText(/awaiting verification/i);
      expect(verifyStatus).toBeInTheDocument();

      const tokenStatus = screen.getByLabelText(/token status/i);
      expect(tokenStatus).toBeInTheDocument();
    });

    it('renders number of applied filters', async () => {
      const filter = screen.getByRole('button', { name: /filter 1/i });
      userEvent.click(filter);
      expect(screen.getByText(/awaiting verification/i)).toBeInTheDocument();
      //data won't actually be filtered but filters should be selected
      //why was this set to expect 2 filters?
      expect(verifyValues.filter.countAppliedFilters()).toBe(1);

      let dropdown = screen.getByTestId('org-dropdown');
      expect(dropdown).toBeInTheDocument();
      let button = within(dropdown).getByRole('button', {
        name: /all/i,
      });
      userEvent.click(button);
      // the actual list of orgs is displayed in a popup that is not part of FilterTop
      // this list is the default list
      const orglist = screen.getByRole('listbox');

      const orgSelected = screen.getByRole('option', { name: /not set/i });

      userEvent.selectOptions(orglist, orgSelected);

      userEvent.click(screen.getByText(/apply/i));
      expect(screen.getByRole('button', { name: /filter 1/i })).toBeTruthy();
      //this function is still returning 1
      expect(verifyValues.filter.countAppliedFilters()).toBe(1);
    });

    // it('renders side panel', () => {
    //   // screen.logTestingPlaygroundURL();
    //   // expect(screen.getByText(/planters per page: 24/i));
    // });

    it('renders captures gallery', () => {
      const pageSize = screen.getAllByText(/captures per page:/i);
      expect(pageSize).toHaveLength(2);

      expect(screen.getByText(/4 captures/i));
    });

    it('renders capture details', () => {
      const captureDetails = screen.getAllByRole('button', {
        name: /capture details/i,
      });
      expect(captureDetails).toHaveLength(4);
      userEvent.click(captureDetails[0]);
      expect(screen.getByText(/capture data/i)).toBeInTheDocument();
      expect(screen.getByText(/grower identifier/i)).toBeInTheDocument();
      expect(screen.getByText(/[email protected]/i)).toBeInTheDocument();
      expect(screen.getByText(/device identifier/i)).toBeInTheDocument();
      // expect(screen.getByText(/1 - abcdef123456/i)).toBeInTheDocument();
      expect(screen.getByText(/verification status/i)).toBeInTheDocument();
      expect(screen.getByText(/token status/i)).toBeInTheDocument();
    });

    it('renders grower details', () => {
      const growerDetails = screen.getAllByRole('button', {
        name: /grower details/i,
      });
      expect(growerDetails).toHaveLength(4);
      userEvent.click(growerDetails[0]);
      // screen.logTestingPlaygroundURL();

      expect(screen.getByText(/country/i)).toBeInTheDocument();
      expect(screen.getByText(/organization/i)).toBeInTheDocument();
      expect(screen.getByText(/person ID/i)).toBeInTheDocument();
      expect(screen.getByText(/ID:/i)).toBeInTheDocument();
      expect(screen.getByText(/email address/i)).toBeInTheDocument();
      expect(screen.getByText(/phone number/i)).toBeInTheDocument();
      expect(screen.getByText(/registered/i)).toBeInTheDocument();
    });

    // it('renders edit planter', () => {
    //   const planterDetails = screen.getAllByRole('button', {
    //     name: /planter details/i,
    //   });
    //   userEvent.click(planterDetails[0]);

    //   screen.logTestingPlaygroundURL();
    //   //
    //   const editPlanter = screen.getByTestId(/edit-planter/i);
    //   expect(editPlanter).toBeInTheDocument();
    //   userEvent.click(editPlanter);
    // });
  });
});
Example #27
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 #28
Source File: regions.test.js    From treetracker-admin-client with GNU Affero General Public License v3.0 4 votes vote down vote up
describe('region management', () => {
  let treeTrackerApi;
  let regionsApi;
  let regionValues;

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

    regionsApi.getRegions = jest.fn(() => {
      return Promise.resolve({
        query: {
          count: REGIONS.length,
        },
        regions: REGIONS,
      });
    });

    regionsApi.getRegion = jest.fn((id) => {
      const region = REGIONS.find((reg) => id === reg.id);
      return Promise.resolve({ region });
    });

    regionsApi.getCollections = jest.fn(() => {
      return Promise.resolve({
        query: {
          count: REGION_COLLECTIONS.length,
        },
        collections: REGION_COLLECTIONS,
      });
    });

    regionsApi.getCollection = jest.fn((id) => {
      const collection = REGION_COLLECTIONS.find((coll) => id === coll.id);
      return Promise.resolve({ collection });
    });

    regionsApi.upload = jest.fn(() => {
      return Promise.resolve(REGIONS[0]);
    });

    regionsApi.updateRegion = jest.fn(() => {
      return Promise.resolve(REGIONS[0]);
    });

    regionsApi.updateCollection = jest.fn(() => {
      return Promise.resolve(REGION_COLLECTIONS[0]);
    });

    treeTrackerApi.getOrganizations = jest.fn(() => {
      return Promise.resolve(ORGS);
    });

    regionValues = {
      regions: REGIONS,
      collections: REGION_COLLECTIONS,
      pageSize: 25,
      regionCount: null,
      collectionCount: null,
      currentPage: 0,
      filter: new FilterRegion(),
      isLoading: false,
      showCollections: false,
      changePageSize: () => {},
      changeCurrentPage: () => {},
      changeSort: () => {},
      setShowCollections: () => {},
      loadRegions: () => {},
      loadCollections: () => {},
      getRegion: () => {},
      upload: () => {},
      updateRegion: () => {},
      updateCollection: () => {},
      updateFilter: () => {},
      deleteRegion: () => {},
      deleteCollection: () => {},
    };
  });

  afterEach(cleanup);

  describe('<Regions /> renders page', () => {
    beforeEach(async () => {
      render(
        <BrowserRouter>
          <AppProvider value={{ orgList: ORGS }}>
            <RegionProvider value={regionValues}>
              <RegionsView />
            </RegionProvider>
          </AppProvider>
        </BrowserRouter>
      );
      await act(() => regionsApi.getRegions());
    });

    afterEach(cleanup);

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

      it('species list should be 2', () => {
        expect(regionValues.regions).toHaveLength(2);
      });

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

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

      it('see popup with upload region or collection form', () => {
        expect(
          screen.getByText(/Upload New Region or Collection/i)
        ).toBeTruthy();
      });

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

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

    describe('regions table', () => {
      it('shows the table header', () => {
        const table = screen.getByRole(/table/i);
        expect(within(table).getByText(/name/i)).toBeTruthy();
        expect(within(table).getByText(/owner/i)).toBeTruthy();
        expect(within(table).getByText(/collection/i)).toBeTruthy();
        expect(within(table).getByText(/properties/i)).toBeTruthy();
        expect(within(table).getByText(/shown on org map/i)).toBeTruthy();
        expect(within(table).getByText(/statistics calculated/i)).toBeTruthy();
      });

      it('shows a region record', () => {
        const table = screen.getByRole(/table/i);
        expect(within(table).getByText(REGIONS[0].name)).toBeTruthy();
        expect(within(table).getAllByText(ORGS[0].name)).toBeTruthy();
        expect(
          within(table).getAllByText(REGION_COLLECTIONS[0].name)
        ).toBeTruthy();
        expect(within(table).getByText(REGIONS[0].properties.Id)).toBeTruthy();
      });
    });
  });
});
Example #29
Source File: organizations.test.js    From treetracker-admin-client with GNU Affero General Public License v3.0 4 votes vote down vote up
describe('CaptureFilter organizations', () => {
  let api;

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

    api.getOrganizations = () => {
      // log.debug('mock getOrganizations:');
      return Promise.resolve(ORGS);
    };
  });

  describe('CaptureFilter', () => {
    describe('w/o data in context', () => {
      let component;

      beforeEach(async () => {
        component = (
          <AppProvider>
            <CaptureFilter />
          </AppProvider>
        );
      });

      afterEach(cleanup);

      it('renders without crashing', () => {
        const div = document.createElement('div');
        ReactDOM.render(component, div);
        ReactDOM.unmountComponentAtNode(div);
      });

      it('renders text "Verification Status" ', () => {
        render(component);
        expect(screen.getByText('Verification Status')).toBeInTheDocument();
      });

      it('renders "Start Date" input ', () => {
        render(component);
        let input = screen.getByRole('textbox', { name: 'Start Date' });
        expect(input).toBeInTheDocument();
      });

      it('renders "End Date" input ', () => {
        render(component);
        let input = screen.getByRole('textbox', { name: 'End Date' });
        expect(input).toBeInTheDocument();
      });

      it('renders Species dropdown ', () => {
        render(component);
        let dropdown = screen.getByTestId('species-dropdown');
        expect(dropdown).toBeInTheDocument();
      });

      it('renders Tags dropdown ', () => {
        render(component);
        let dropdown = screen.getByTestId('tag-dropdown');
        expect(dropdown).toBeInTheDocument();
      });

      it('renders Organization dropdown ', () => {
        render(component);
        let dropdown = screen.getByTestId('org-dropdown');
        expect(dropdown).toBeInTheDocument();
      });

      it('renders default orgList when dropdown clicked ', () => {
        render(component);
        let dropdown = screen.getByTestId('org-dropdown');
        expect(dropdown).toBeInTheDocument();

        let button = within(dropdown).getByRole('button', {
          name: /all/i,
        });

        userEvent.click(button);

        // the actual list of orgs is displayed in a popup that is not part of CaptureFilter
        // this list is the default list
        const orglist = screen.getByRole('listbox');
        const orgs = within(orglist).getAllByTestId('org-item');
        const listItems = orgs.map((org) => org.textContent);
        console.log('default orgList', listItems);

        expect(orgs).toHaveLength(2);
      });
    });

    describe('w/ data in context', () => {
      let orgs;
      let component;

      beforeEach(async () => {
        orgs = await api.getOrganizations();
        component = (
          <AppProvider value={{ orgList: orgs }}>
            <CaptureFilter />
          </AppProvider>
        );
        // render(component);
        await act(() => api.getOrganizations());
      });

      afterEach(cleanup);

      it('api loaded 2 organizations', () => {
        expect(orgs).toHaveLength(2);
      });

      it('renders Organization dropdown ', () => {
        render(component);
        let dropdown = screen.getByTestId('org-dropdown');
        expect(dropdown).toBeInTheDocument();
      });

      it('renders default orgList when dropdown clicked ', () => {
        render(component);
        let dropdown = screen.getByTestId('org-dropdown');
        expect(dropdown).toBeInTheDocument();

        let button = within(dropdown).getByRole('button', { name: /all/i });

        userEvent.click(button);

        // screen.logTestingPlaygroundURL();

        // the actual list of orgs is displayed in a popup that is not part of CaptureFilter
        const orglist = screen.getByRole('listbox');
        const orgs = within(orglist).getAllByTestId('org-item');
        const listItems = orgs.map((org) => org.textContent);
        console.log('default orgList', listItems);

        // two default options + two orgs
        expect(orgs).toHaveLength(4);
      });
    });

    // describe('context data renders in child', () => {
    //   let orgs;
    //   let component;

    //   beforeEach(async () => {
    //     component = (
    //       <AppProvider>
    //         <AppContext.Consumer>
    //           {(value) => <p>Received: {value.orgList}</p>}
    //         </AppContext.Consumer>
    //       </AppProvider>
    //     );

    //     render(component);

    //     await act(() => api.getOrganizations());
    //   });

    //   // just tests the mock api, not what's showing on the page
    //   it('api loaded 2 organizations', () => {
    //     expect(orgs).toHaveLength(2);
    //   });

    //   it('renders text "Dummy Org" ', () => {
    //     // screen.debug(); // shows structure in console
    //     screen.logTestingPlaygroundURL();
    //     // expect(screen.getByText(/^Received:/).textContent).toBe('Received: ');
    //     expect(screen.getByText('Dummy Org')).toBeInTheDocument();
    //   });
    // });
  });
});