@testing-library/dom#waitFor JavaScript Examples

The following examples show how to use @testing-library/dom#waitFor. 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: utils.spec.js    From alpine-magic-helpers with MIT License 7 votes vote down vote up
test('waitUntilReady > returns an empty string while the component is not ready', async () => {
    window.test = () => waitUntilReady(document.querySelector('div'), document.querySelector('p'), () => {
        return 'done'
    })
    document.body.innerHTML = `
        <div></div>
        <p x-data="{foo: 'bar', baz() {return test()}}">
            <span x-text="foo"></span>
            <span x-text="baz()"></span>
        </p>
    `
    Alpine.start()

    // Make sure Alpine kicked in
    await waitFor(() => {
        expect(document.querySelectorAll('span')[0].textContent).toBe('bar')
    })

    // Div doesn't have __x yet so the alpine component should wait
    expect(document.querySelectorAll('span')[1].textContent).toBe('')

    // We simulate a component being finally ready
    document.querySelector('div').__x = true

    // The callback should finally resolve and return the final value
    await waitFor(() => {
        expect(document.querySelectorAll('span')[1].textContent).toBe('done')
    })
})
Example #2
Source File: createCellSetByExpression.test.js    From ui with MIT License 6 votes vote down vote up
describe('createCellSetByExpression', () => {
  beforeEach(() => {
    jest.clearAllMocks();
  });

  it('Dispatces the correct body', async () => {
    await store.dispatch(createCellSetByExpression(experimentId, mockData));

    const params = fetchWork.mock.calls[0];
    expect(params).toMatchSnapshot();
  });

  it('Throws notification on error', async () => {
    fetchWork.mockImplementationOnce(() => Promise.reject(new Error('some error')));

    expect(async () => {
      await store.dispatch(createCellSetByExpression(experimentId, mockData));
    }).rejects.toThrow();

    waitFor(() => {
      expect(pushNotificationMessage).toHaveBeenCalledTimes(1);
      expect(pushNotificationMessage).toHaveBeenCalledWith('error', endUserMessages.ERROR_FETCHING_CELL_SETS);
    });
  });
});
Example #3
Source File: constructor.test.js    From emoji-picker-element with Apache License 2.0 6 votes vote down vote up
describe('constructor', () => {
  beforeEach(basicBeforeEach)
  afterEach(basicAfterEach)

  async function testWithDefaults (...args) {
    const picker = new Picker(...args)
    document.body.appendChild(picker)
    const container = picker.shadowRoot.querySelector('.picker')
    await tick(20)

    await waitFor(() => expect(getByRole(container, 'menuitem', { name: /?/ })).toBeVisible())
    expect(getByRole(container, 'menuitem', { name: /?/ })).toBeVisible()

    expect(fetch).toHaveBeenCalledTimes(1)
    expect(fetch).toHaveBeenLastCalledWith(DEFAULT_DATA_SOURCE, undefined)

    document.body.removeChild(picker)
    await tick(20)
  }

  test('contructor with undefined options works', async () => {
    await testWithDefaults()
  })

  test('contructor with empty options object works', async () => {
    await testWithDefaults({})
  })

  test('contructor with just dataSource option works', async () => {
    await testWithDefaults({ dataSource: DEFAULT_DATA_SOURCE })
  })

  test('contructor with just locale option works', async () => {
    await testWithDefaults({ locale: DEFAULT_LOCALE })
  })
})
Example #4
Source File: LibraryAccessForm.spec.jsx    From frontend-app-library-authoring with GNU Affero General Public License v3.0 6 votes vote down vote up
testSuite('<LibraryAccessForm />', () => {
  it('Renders an error for the email field', async () => {
    const library = libraryFactory();
    const props = { library, errorFields: { email: 'Too difficult to remember.' }, ...commonMocks() };
    const { container } = await ctxRender(<LibraryAccessFormContainer {...props} />);
    expect(getByText(container, /Too difficult/)).toBeTruthy();
  });
  it('Submits and adds a new user.', async () => {
    const library = libraryFactory();
    const props = { library, ...commonMocks() };
    const { addUser } = props;
    const user = userFactory();
    addUser.mockImplementation(() => immediate(user));
    const { container } = await ctxRender(<LibraryAccessFormContainer {...props} />);
    const emailField = getByLabelText(container, 'Email');
    fireEvent.change(emailField, { target: { value: '[email protected]' } });
    const submitButton = getByRole(container, 'button', { name: /Submit/ });
    fireEvent.click(submitButton);
    await waitFor(() => expect(addUser).toHaveBeenCalledWith({
      libraryId: library.id, data: { email: '[email protected]', access_level: LIBRARY_ACCESS.READ },
    }));
    expect(emailField.value).toBe('');
  });
  it('Closes out', async () => {
    const library = libraryFactory();
    const props = { library, ...commonMocks() };
    const { setShowAdd } = props;
    const { container } = await ctxRender(
      <LibraryAccessFormContainer
        {...props}
      />,
    );
    const button = getByRole(container, 'button', { name: /Cancel/ });
    fireEvent.click(button);
    expect(setShowAdd).toHaveBeenCalledWith(false);
  });
});
Example #5
Source File: component.spec.js    From alpine-magic-helpers with MIT License 6 votes vote down vote up
test('$component > component can access magic properties', async () => {
    document.body.innerHTML = `
        <div x-data>
            <p x-text="$component('ext').$refs.bob.textContent"></p>
        </div>
        <div x-id="ext" x-data="{foo: 'bar'}">
            <span x-ref="bob" x-text="foo"></span>
        </div>
    `

    Alpine.start()

    await waitFor(() => {
        expect(document.querySelector('p').textContent).toEqual('bar')
    })
})
Example #6
Source File: errors.test.js    From emoji-picker-element with Apache License 2.0 5 votes vote down vote up
describe('errors', () => {
  let errorSpy

  beforeEach(() => {
    basicBeforeEach()
    errorSpy = jest.spyOn(global.console, 'error').mockImplementation()
  })
  afterEach(async () => {
    await basicAfterEach()
    errorSpy.mockRestore()
  })

  // seems not possible to do
  test('throws error when setting the database', async () => {
    const picker = new Picker({ dataSource: ALL_EMOJI, locale: 'en' })
    document.body.appendChild(picker)
    await tick(20)
    expect(() => {
      picker.database = null
    }).toThrow(/database is read-only/)
    await tick(20)
    document.body.removeChild(picker)
    await tick(20)
  })

  // can't seem to get jest to ignore these expected errors
  test('offline shows an error', async () => {
    const dataSource = 'error.json'

    fetch.get(dataSource, { body: null, status: 500 })
    fetch.head(dataSource, { body: null, status: 500 })

    const picker = new Picker({ dataSource })
    const container = picker.shadowRoot
    document.body.appendChild(picker)

    await tick(20)

    await expect(picker.database.ready()).rejects.toThrow()

    await waitFor(() => expect(getByRole(container, 'alert').innerHTML).toContain('Could not load'))

    await new Database({ dataSource: ALL_EMOJI }).delete() // use different dataSource so it deletes properly
    document.body.removeChild(picker)
    await tick(20)
  })

  test('slow networks show "Loading"', async () => {
    const dataSource = 'slow.json'

    fetch.get(dataSource, () => new Response(JSON.stringify(truncatedEmoji), { headers: { ETag: 'W/slow' } }),
      { delay: 1500 })
    fetch.head(dataSource, () => new Response(null, { headers: { ETag: 'W/slow' } }),
      { delay: 1500 })

    const picker = new Picker({ dataSource })
    const container = picker.shadowRoot
    document.body.appendChild(picker)
    await tick(20)

    await waitFor(() => expect(getByRole(container, 'alert').innerHTML).toContain('Loading'), { timeout: 2000 })
    await waitFor(() => expect(getByRole(container, 'menuitem', { name: /?/ })).toBeVisible(), { timeout: 2000 })

    await new Database({ dataSource }).delete()
    document.body.removeChild(picker)
    await tick(20)
  }, 5000)
})
Example #7
Source File: undo.spec.js    From alpine-magic-helpers with MIT License 5 votes vote down vote up
test('$undo > component can keep track after component is replaced with a clone', async () => {
    document.body.innerHTML = `
        <div id="original" x-data="{ number: 0 }" x-init="$track()">
            <button
                class="increment"
                @click="number = number + 1"
                x-text="number"></button>
            <button
                class="undo"
                @click="$undo()"></button>
        </div>
        <div id="insert-component-here"></div>
    `

    Alpine.start()

    document.querySelector('#original .increment').click()

    await waitFor(() => {
        expect(document.querySelector('#original').__x.$data.$history.length).toEqual(1)
    })

    document.querySelector('#insert-component-here').innerHTML = `
        <div id="clone" x-data="{ number: 0 }" x-init="$track()">
            <button
                class="increment"
                @click="number = number + 1"
                x-text="number"></button>
            <span
                class="undo"
                @click="$undo()"></span>
        </div>
    `

    Alpine.clone(document.querySelector('#original').__x, document.querySelector('#clone'))
    document.querySelector('#original').remove()

    await waitFor(() => {
        expect(document.querySelector('#clone').__x.$data.$history.length).toEqual(1)
        expect(document.querySelector('#clone .increment').textContent).toEqual('1')
    })

    document.querySelector('#clone .undo').click()

    await waitFor(() => {
        expect(document.querySelector('#clone .increment').textContent).toEqual('0')
        expect(document.querySelector('#clone').__x.$data.$history.length).toEqual(0)
    })

    await waitFor(() => {
        expect(document.querySelector('#clone .increment').textContent).toEqual('0')
        expect(document.querySelector('#clone').__x.$data.$history.length).toEqual(0)
    })
})
Example #8
Source File: noResizeObserver.test.js    From emoji-picker-element with Apache License 2.0 5 votes vote down vote up
// TODO: we can remove these tests when/if we stop supporting browsers without ResizeObserver
// https://caniuse.com/resizeobserver

describe('ResizeObserver unsupported', () => {
  let picker
  let container
  let oldResizeObserver

  beforeEach(async () => {
    basicBeforeEach()

    oldResizeObserver = global.ResizeObserver
    delete global.ResizeObserver
    resetResizeObserverSupported()

    picker = new Picker({ dataSource: ALL_EMOJI })
    document.body.appendChild(picker)
    container = picker.shadowRoot.querySelector('.picker')
    await tick(40)
  })

  afterEach(async () => {
    await tick(40)
    document.body.removeChild(picker)
    await tick(40)
    await new Database({ dataSource: ALL_EMOJI }).delete()
    await tick(40)
    await basicAfterEach()

    global.ResizeObserver = oldResizeObserver
    resetResizeObserverSupported()
  })

  test('basic picker test', async () => {
    const numInGroup1 = truncatedEmoji.filter(_ => _.group === 0).length
    const numInGroup2 = truncatedEmoji.filter(_ => _.group === 1).length

    await waitFor(() => expect(getByRole(container, 'button', { name: 'Choose a skin tone (currently Default)' })).toBeVisible())
    expect(getAllByRole(container, 'tab')).toHaveLength(groups.length)

    expect(getByRole(container, 'tab', { name: 'Smileys and emoticons', selected: true })).toBeVisible()
    await waitFor(() => expect(
      testingLibrary.getAllByRole(getByRole(container, 'tabpanel'), 'menuitem')).toHaveLength(numInGroup1)
    )

    expect(getByRole(container, 'tab', { name: 'People and body' })).toBeVisible()
    fireEvent.click(getByRole(container, 'tab', { name: 'People and body' }))

    await waitFor(() => expect(
      testingLibrary.getAllByRole(getByRole(container, 'tabpanel'), 'menuitem')).toHaveLength(numInGroup2))

    expect(getByRole(container, 'tab', { name: 'People and body', selected: true })).toBeVisible()
  })
})
Example #9
Source File: component.spec.js    From alpine-magic-helpers with MIT License 5 votes vote down vote up
test('$parent > component can access granparent scope', async () => {
    document.body.innerHTML = `
        <div x-data="{foo: 'bar', getFoo() {return this.foo}}">
            <p x-text="foo"></p>
            <div x-data>
                <div x-data>
                    <p x-text="$parent.$parent.foo"></p>
                    <p x-text="$parent.$parent.getFoo()"></p>
                    <button @click="$parent.$parent.foo = 'baz'"></button>
                </div>
                <button @click="$parent.foo = 'bob'"></button>
            </div>
            <button @click="foo = 'qux'"></button>
        </div>
    `

    Alpine.start()

    await waitFor(() => {
        expect(document.querySelectorAll('p')[0].textContent).toEqual('bar')
        expect(document.querySelectorAll('p')[1].textContent).toEqual('bar')
        expect(document.querySelectorAll('p')[2].textContent).toEqual('bar')
    })

    document.querySelectorAll('button')[0].click()

    await waitFor(() => {
        expect(document.querySelectorAll('p')[0].textContent).toEqual('baz')
        expect(document.querySelectorAll('p')[1].textContent).toEqual('baz')
        expect(document.querySelectorAll('p')[2].textContent).toEqual('baz')
    })

    document.querySelectorAll('button')[1].click()

    await waitFor(() => {
        expect(document.querySelectorAll('p')[0].textContent).toEqual('bob')
        expect(document.querySelectorAll('p')[1].textContent).toEqual('bob')
        expect(document.querySelectorAll('p')[2].textContent).toEqual('bob')
    })

    document.querySelectorAll('button')[2].click()

    await waitFor(() => {
        expect(document.querySelectorAll('p')[0].textContent).toEqual('qux')
        expect(document.querySelectorAll('p')[1].textContent).toEqual('qux')
        expect(document.querySelectorAll('p')[2].textContent).toEqual('qux')
    })
})
Example #10
Source File: element.test.js    From emoji-picker-element with Apache License 2.0 4 votes vote down vote up
describe('element tests', () => {
  describe('UI tests', () => {
    let picker
    let container

    beforeEach(async () => {
      basicBeforeEach()
      mockFrenchDataSource()
      picker = new Picker({ dataSource: ALL_EMOJI, locale: 'en' })
      container = picker.shadowRoot
      document.body.appendChild(picker)
      await tick(20)
    })
    afterEach(async () => {
      document.body.removeChild(picker)
      await tick(20)
      await new Database({ dataSource: FR_EMOJI, locale: 'fr' }).delete()
      await new Database({ dataSource: ALL_EMOJI, locale: 'en' }).delete()
      await tick(20)
      await basicAfterEach()
    })

    test('changing locale/dataSource prop causes only one network request', async () => {
      expect(fetch).toHaveBeenCalledTimes(1)
      expect(fetch).toHaveBeenLastCalledWith(ALL_EMOJI, undefined)
      await type(getByRole(container, 'combobox'), 'monkey face')
      await waitFor(() => expect(getAllByRole(container, 'option')).toHaveLength(1), {
        timeout: 2000
      })
      expect(getByRole(container, 'option', { name: /?/ })).toBeVisible()
      picker.locale = 'fr'
      picker.dataSource = FR_EMOJI
      await tick(20)
      expect(fetch).toHaveBeenCalledTimes(2)
      expect(fetch).toHaveBeenLastCalledWith(FR_EMOJI, undefined)
      await clear(getByRole(container, 'combobox'))
      await type(getByRole(container, 'combobox'), 'singe tête')
      await waitFor(() => expect(getAllByRole(container, 'option')).toHaveLength(1))
      expect(getByRole(container, 'option', { name: /?/ })).toBeVisible()
    })

    test('changing locale/dataSource attr causes only one network request', async () => {
      expect(fetch).toHaveBeenCalledTimes(1)
      expect(fetch).toHaveBeenLastCalledWith(ALL_EMOJI, undefined)
      await type(getByRole(container, 'combobox'), 'monkey face')
      await waitFor(() => expect(getAllByRole(container, 'option')).toHaveLength(1), {
        timeout: 2000
      })
      expect(getByRole(container, 'option', { name: /?/ })).toBeVisible()
      picker.setAttribute('locale', 'fr')
      picker.setAttribute('data-source', FR_EMOJI)
      await tick(20)
      expect(fetch).toHaveBeenCalledTimes(2)
      expect(fetch).toHaveBeenLastCalledWith(FR_EMOJI, undefined)
      await clear(getByRole(container, 'combobox'))
      await type(getByRole(container, 'combobox'), 'singe tête')
      await waitFor(() => expect(getAllByRole(container, 'option')).toHaveLength(1))
      expect(getByRole(container, 'option', { name: /?/ })).toBeVisible()
    })

    test('can dynamically change i18n', async () => {
      picker.i18n = frI18n
      await tick(10)
      expect(getByRole(container, 'combobox', { name: 'Recherche' })).toBeVisible()
      expect(getByRole(container, 'tab', { name: 'Sourires et emoticons' })).toBeVisible()
      expect(getByRole(container, 'button', { name: 'Choose a skin tone (currently Défaut)' })).toBeVisible()
    })

    test('can change default skin tone emoji', async () => {
      expect(picker.skinToneEmoji).toBe(DEFAULT_SKIN_TONE_EMOJI)
      expect(getByRole(container, 'button', { name: /Choose a skin tone/ }).innerHTML)
        .toContain(DEFAULT_SKIN_TONE_EMOJI)
      picker.skinToneEmoji = '?'
      await waitFor(() => expect(getByRole(container, 'button', { name: /Choose a skin tone/ }).innerHTML).toContain('?'))
      picker.skinToneEmoji = '?'
      await waitFor(() => expect(getByRole(container, 'button', { name: /Choose a skin tone/ }).innerHTML).toContain('?'))
    })

    test('can get the locale/dataSource', () => {
      expect(picker.locale).toBe('en')
      expect(picker.dataSource).toBe(ALL_EMOJI)
    })
  })

  describe('defaults test', () => {
    beforeEach(() => {
      fetch.get(DEFAULT_DATA_SOURCE, () => new Response(JSON.stringify(truncatedEmoji), { headers: { ETag: 'W/aaa' } }))
      fetch.head(DEFAULT_DATA_SOURCE, () => new Response(null, { headers: { ETag: 'W/aaa' } }))
    })

    afterEach(basicAfterEach)

    test('has a default locale/dataSource', async () => {
      const picker = new Picker()
      document.body.appendChild(picker)
      const container = picker.shadowRoot
      await tick(20)

      await waitFor(() => expect(getByRole(container, 'menuitem', { name: /?/ })).toBeVisible())

      await new Database().delete()
      await tick(20)
      await document.body.removeChild(picker)
      await tick(20)
    })
  })
})
Example #11
Source File: processUpload.test.js    From ui with MIT License 4 votes vote down vote up
describe('processUpload', () => {
  beforeEach(() => {
    jest.clearAllMocks();

    fetchMock.resetMocks();
    fetchMock.doMock();
    fetchMock.mockResponse(JSON.stringify('theSignedUrl'), { status: 200 });

    store = mockStore(initialState);
  });

  it('Uploads and updates redux correctly when there are no errors with cellranger v3', async () => {
    const mockAxiosCalls = [];
    const uploadSuccess = (params) => {
      mockAxiosCalls.push(params);
      return Promise.resolve('Resolved');
    };

    axios.request.mockImplementationOnce(uploadSuccess)
      .mockImplementationOnce(uploadSuccess)
      .mockImplementationOnce(uploadSuccess);

    await processUpload(
      getValidFiles('v3'),
      sampleType,
      store.getState().samples,
      mockExperimentId,
      store.dispatch,
    );

    // Wait for uploads to be made
    await waitForActions(
      store,
      new Array(3).fill({
        type: SAMPLES_FILE_UPDATE,
        payload: { fileDiff: { upload: { status: UploadStatus.UPLOADED } } },
      }),
      { matcher: waitForActions.matchers.containing },
    );

    // Three axios put calls are made
    expect(mockAxiosCalls.length).toBe(3);
    // Each put call is made with the correct information
    expect(mockAxiosCalls[0].data).toEqual('loadedGzippedFile');
    expect(mockAxiosCalls[1].data).toEqual('loadedGzippedFile');
    expect(mockAxiosCalls[2].data).toEqual('loadedGzippedFile');

    // Wait until all put promises are resolved
    await flushPromises();

    const fileUpdateActions = store.getActions().filter(
      (action) => action.type === SAMPLES_FILE_UPDATE,
    );
    const uploadProperties = fileUpdateActions.map((action) => action.payload.fileDiff.upload);
    const uploadingStatusProperties = uploadProperties.filter(
      ({ status }) => status === UploadStatus.UPLOADING,
    );
    const uploadedStatusProperties = uploadProperties.filter(
      ({ status }) => status === UploadStatus.UPLOADED,
    );

    // There are 3 files actions with status uploading
    expect(uploadingStatusProperties.length).toEqual(3);
    // There are 3 files actions with status uploaded
    expect(uploadedStatusProperties.length).toEqual(3);

    // Calls to loadAndCompressIfNecessary are done correctly
    expect(loadAndCompressIfNecessary.mock.calls.map((calls) => calls[0])).toMatchSnapshot();

    // If we trigger onCompression then an action to update uploadStatus to COMPRESSING is made
    const onCompression = loadAndCompressIfNecessary.mock.calls[0][1];
    onCompression();

    await waitForActions(
      store,
      [{
        type: SAMPLES_FILE_UPDATE,
        payload: { fileDiff: { upload: { status: UploadStatus.COMPRESSING } } },
      }],
      { matcher: waitForActions.matchers.containing },
    );

    // axios request calls are correct
    expect(axios.request.mock.calls.map((call) => call[0])).toMatchSnapshot();

    // If we trigger axios onUploadProgress it updates the progress correctly
    axios.request.mock.calls[0][0].onUploadProgress({ loaded: 20, total: 40 });

    await waitForActions(
      store,
      [{
        type: SAMPLES_FILE_UPDATE,
        payload: { fileDiff: { upload: { status: UploadStatus.UPLOADING, progress: 50 } } },
      }],
      { matcher: waitForActions.matchers.containing },
    );
  });

  it('Uploads and updates redux correctly when there are no errors with cellranger v2', async () => {
    const mockAxiosCalls = [];
    const uploadSuccess = (params) => {
      mockAxiosCalls.push(params);
      return Promise.resolve('Resolved');
    };

    axios.request.mockImplementationOnce(uploadSuccess)
      .mockImplementationOnce(uploadSuccess)
      .mockImplementationOnce(uploadSuccess);

    await processUpload(
      getValidFiles('v2'),
      sampleType,
      store.getState().samples,
      mockExperimentId,
      store.dispatch,
    );

    // Wait for uploads to be made
    await waitForActions(
      store,
      new Array(3).fill({
        type: SAMPLES_FILE_UPDATE,
        payload: { fileDiff: { upload: { status: UploadStatus.UPLOADED } } },
      }),
      { matcher: waitForActions.matchers.containing },
    );

    // Three axios put calls are made
    expect(mockAxiosCalls.length).toBe(3);
    // Each put call is made with the correct information
    expect(mockAxiosCalls[0].data).toEqual('loadedGzippedFile');
    expect(mockAxiosCalls[1].data).toEqual('loadedGzippedFile');
    expect(mockAxiosCalls[2].data).toEqual('loadedGzippedFile');

    // Wait until all put promises are resolved
    await flushPromises();

    const fileUpdateActions = store.getActions().filter(
      (action) => action.type === SAMPLES_FILE_UPDATE,
    );
    const uploadProperties = fileUpdateActions.map((action) => action.payload.fileDiff.upload);
    const uploadingStatusProperties = uploadProperties.filter(
      ({ status }) => status === UploadStatus.UPLOADING,
    );
    const uploadedStatusProperties = uploadProperties.filter(
      ({ status }) => status === UploadStatus.UPLOADED,
    );

    // There are 3 files actions with status uploading
    expect(uploadingStatusProperties.length).toEqual(3);
    // There are 3 files actions with status uploaded
    expect(uploadedStatusProperties.length).toEqual(3);

    // Calls to loadAndCompressIfNecessary are done correctly
    expect(loadAndCompressIfNecessary.mock.calls.map((calls) => calls[0])).toMatchSnapshot();

    // If we trigger onCompression then an action to update uploadStatus to COMPRESSING is made
    const onCompression = loadAndCompressIfNecessary.mock.calls[0][1];
    onCompression();

    await waitForActions(
      store,
      [{
        type: SAMPLES_FILE_UPDATE,
        payload: { fileDiff: { upload: { status: UploadStatus.COMPRESSING } } },
      }],
      { matcher: waitForActions.matchers.containing },
    );

    // axios request calls are correct
    expect(axios.request.mock.calls.map((call) => call[0])).toMatchSnapshot();

    // If we trigger axios onUploadProgress it updates the progress correctly
    axios.request.mock.calls[0][0].onUploadProgress({ loaded: 20, total: 40 });

    await waitForActions(
      store,
      [{
        type: SAMPLES_FILE_UPDATE,
        payload: { fileDiff: { upload: { status: UploadStatus.UPLOADING, progress: 50 } } },
      }],
      { matcher: waitForActions.matchers.containing },
    );
  });

  it('Updates redux correctly when there are file load and compress errors', async () => {
    const invalidFiles = getValidFiles('v3').map((file) => ({ ...file, valid: false }));

    await processUpload(
      invalidFiles,
      sampleType,
      store.getState().samples,
      mockExperimentId,
      store.dispatch,
    );

    // Wait for uploads to be made
    await waitForActions(
      store,
      new Array(3).fill({
        type: SAMPLES_FILE_UPDATE,
        payload: { fileDiff: { upload: { status: UploadStatus.FILE_READ_ERROR } } },
      }),
      { matcher: waitForActions.matchers.containing },
    );

    const fileUpdateActions = store.getActions().filter(
      (action) => action.type === SAMPLES_FILE_UPDATE,
    );

    const uploadProperties = fileUpdateActions.map((action) => action.payload.fileDiff.upload);

    const uploadingFileProperties = uploadProperties.filter(
      ({ status }) => status === UploadStatus.UPLOADING,
    );
    const errorFileProperties = uploadProperties.filter(
      ({ status }) => status === UploadStatus.FILE_READ_ERROR,
    );
    const uploadedFileProperties = uploadProperties.filter(
      ({ status }) => status === UploadStatus.UPLOADED,
    );
    // There are 3 files actions with status uploading
    expect(uploadingFileProperties.length).toEqual(3);
    // There are 3 files actions with status upload error
    expect(errorFileProperties.length).toEqual(3);
    // There are no file actions with status successfully uploaded
    expect(uploadedFileProperties.length).toEqual(0);
  });

  it('Updates redux correctly when there are file upload errors', async () => {
    const mockAxiosCalls = [];

    const uploadError = (params) => {
      mockAxiosCalls.push(params);
      return Promise.reject(new Error('Error'));
    };

    axios.request.mockImplementationOnce(uploadError)
      .mockImplementationOnce(uploadError)
      .mockImplementationOnce(uploadError);

    await processUpload(
      getValidFiles('v3'),
      sampleType,
      store.getState().samples,
      'errorProjectUuid',
      store.dispatch,
    );

    // Wait for uploads to be made
    await waitForActions(
      store,
      new Array(3).fill({
        type: SAMPLES_FILE_UPDATE,
        payload: { fileDiff: { upload: { status: UploadStatus.UPLOAD_ERROR } } },
      }),
      { matcher: waitForActions.matchers.containing },
    );

    const fileUpdateActions = store.getActions().filter(
      (action) => action.type === SAMPLES_FILE_UPDATE,
    );

    const uploadProperties = fileUpdateActions.map((action) => action.payload.fileDiff.upload);

    const uploadingFileProperties = uploadProperties.filter(
      ({ status }) => status === UploadStatus.UPLOADING,
    );

    const errorFileProperties = uploadProperties.filter(
      ({ status }) => status === UploadStatus.UPLOAD_ERROR,
    );

    const uploadedFileProperties = uploadProperties.filter(
      ({ status }) => status === UploadStatus.UPLOADED,
    );

    // There are 3 files actions with status uploading
    expect(uploadingFileProperties.length).toEqual(3);
    // There are 3 files actions with status upload error
    expect(errorFileProperties.length).toEqual(3);
    // There are no file actions with status successfully uploaded
    expect(uploadedFileProperties.length).toEqual(0);
  });

  it('Should not upload files if there are errors creating samples in the api', async () => {
    fetchMock.mockReject(new Error('Error'));

    await processUpload(
      getValidFiles('v3'),
      sampleType,
      store.getState().samples,
      mockExperimentId,
      store.dispatch,
    );

    // We do not expect uploads to happen
    await waitFor(() => {
      expect(axios.request).not.toHaveBeenCalled();
    });
  });
});
Example #12
Source File: useProfileHandlerWithCallout.test.js    From ui-data-export with Apache License 2.0 4 votes vote down vote up
describe('useProfileHandlerWithCallout', () => {
  const onActionCompleteMock = jest.fn();

  const hookProps = {
    errorMessageId: 'ui-data-export.mappingProfiles.create.errorCallout',
    successMessageId: 'ui-data-export.mappingProfiles.create.successCallout',
    onActionComplete: onActionCompleteMock,
  };

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

  it('should display success callout', () => {
    render(
      <ContextComponent>
        <Component
          hookProps={{
            ...hookProps,
            onAction: jest.fn(),
          }}
        />
      </ContextComponent>
    );
    userEvent.click(screen.getByTestId('send-callout'));

    waitFor(() => {
      const callout = document.querySelector('[data-test-callout-element]');

      expect(callout).toBeInTheDocument();
      expect(callout.classList.contains('success')).toBeTruthy();
      expect(onActionCompleteMock).toBeCalled();
    });
  });

  it('should display error callout and cancel after error', () => {
    const onActionMock = () => Promise.reject();

    render(
      <ContextComponent>
        <Component
          hookProps={{
            ...hookProps,
            onAction: onActionMock,
            isCanceledAfterError: true,
          }}
        />
      </ContextComponent>
    );
    userEvent.click(screen.getByTestId('send-callout'));

    waitFor(() => {
      const callout = document.querySelector('[data-test-callout-element]');

      expect(callout).toBeInTheDocument();
      expect(callout.classList.contains('error')).toBeTruthy();
      expect(onActionCompleteMock).toBeCalled();
    });
  });

  it('should display error callout and not cancel after error', () => {
    const onActionMock = () => Promise.reject();

    render(
      <ContextComponent>
        <Component
          hookProps={{
            ...hookProps,
            onAction: onActionMock,
            isCanceledAfterError: false,
          }}
        />
      </ContextComponent>
    );
    userEvent.click(screen.getByTestId('send-callout'));

    waitFor(() => {
      const callout = document.querySelector('[data-test-callout-element]');

      expect(callout).toBeInTheDocument();
      expect(callout.classList.contains('error')).toBeTruthy();
      expect(onActionCompleteMock).not.toBeCalled();
    });
  });
});
Example #13
Source File: thunks.spec.js    From frontend-app-library-authoring with GNU Affero General Public License v3.0 4 votes vote down vote up
testSuite('Library detail thunks', () => {
  const dispatch = jest.fn();
  beforeEach(() => {
    useRealThunks(true);
  });
  afterEach(() => {
    // Have to manually reset some things since debounce may live on.
    debouncedBlockSearch.cancel();
    api.getBlocks.fn.mockReset();
    api.getLibraryDetail.fn.mockReset();
    dispatch.mockReset();
  });

  const checkError = (attr, error) => {
    try {
      normalizeErrors(error);
      // Should not reach here.
      expect(true).toBe(false);
    } catch (normalized) {
      expect(dispatch).toHaveBeenCalledWith(actions.libraryAuthoringFailed(
        { attr, errorFields: normalized.fields, errorMessage: normalized.message },
      ));
    }
  };

  const checkRequested = (attr) => {
    expect(dispatch).toHaveBeenCalledWith(actions.libraryAuthoringRequest({ attr }));
  };

  const checkSuccess = (attr, value) => {
    expect(dispatch).toHaveBeenCalledWith(actions.libraryAuthoringSuccess({ attr, value }));
  };

  it('Fetches library details', async () => {
    const library = libraryFactory();
    api.getLibraryDetail.fn.mockImplementation(() => immediate(library));
    await fetchLibraryDetail(dispatch)({ libraryId: library.id });
    expect(api.getLibraryDetail.fn).toHaveBeenCalledWith(library.id);
    checkRequested('library');
    checkSuccess('library', library);
  });

  it('Handles a library details failure', async () => {
    const error = new Error('It borked');
    api.getLibraryDetail.fn.mockImplementation(() => instaFail(error));
    await fetchLibraryDetail(dispatch)({ libraryId: 'test' });
    checkError('library', error);
  });

  it('Fetches blocks', async () => {
    const library = libraryFactory();
    const blocks = makeN(blockFactoryLine([], { library }), 3);
    api.getBlocks.fn.mockImplementation(() => immediate(blocks));
    await fetchBlocks(dispatch)({ libraryId: library.id });
    expect(api.getBlocks.fn).toHaveBeenCalledWith({ libraryId: library.id });
    checkRequested('blocks');
    checkSuccess('blocks', blocks);
  });

  it('Fetches blocks with a search query', async () => {
    const library = libraryFactory();
    const blocks = makeN(blockFactoryLine([], { library }), 3);
    api.getBlocks.fn.mockImplementation(() => immediate(blocks));
    await fetchBlocks(dispatch)({ libraryId: library.id, query: 'test' });
    expect(api.getBlocks.fn).toHaveBeenCalledWith({ libraryId: library.id, query: 'test' });
    checkRequested('blocks');
    checkSuccess('blocks', blocks);
  });

  it('Handles a block-fetching failure', async () => {
    const error = new Error('It borked!');
    api.getBlocks.fn.mockImplementation(() => instaFail(error));
    await fetchBlocks(dispatch)({ libraryId: 'test' });
    checkRequested('blocks');
    checkError('blocks', error);
  });

  it('Creates a library block', async () => {
    const block = blockFactory();
    const blockSpec = { block_type: 'video', description_id: 'test' };
    const paginationParams = {
      page: 1,
      page_size: 20,
    };

    api.createLibraryBlock.fn.mockImplementation(() => immediate(block));
    api.getBlocks.fn.mockImplementation(() => immediate({ data: [block], count: 1 }));

    await createBlock(dispatch)({
      libraryId: 'testLibrary', data: blockSpec, paginationParams, query: '', types: '',
    });

    expect(dispatch).toHaveBeenCalledWith(actions.libraryAuthoringRequest({ attr: 'blocks' }));
    expect(api.createLibraryBlock.fn).toHaveBeenCalledWith({ libraryId: 'testLibrary', data: blockSpec });
    expect(api.getBlocks.fn).toHaveBeenCalledWith({
      libraryId: 'testLibrary',
      paginationParams,
      query: '',
      types: '',
    });

    // checkRequested('blocks');
    expect(dispatch).toHaveBeenCalledWith(actions.libraryAuthoringSuccess({
      value: {
        data: [block],
        count: 1,
      },
      attr: 'blocks',
    }));
  });

  it('Handles a block creation failure', async () => {
    const error = new Error('It borked!');
    api.createLibraryBlock.fn.mockImplementation(() => instaFail(error));
    const blockSpec = { block_type: 'video', description_id: 'test' };
    await createBlock(dispatch)({ libraryId: 'testLibrary', data: blockSpec });
    checkRequested('blocks');
    checkError('blocks', error);
  });

  it('Commits changes', async () => {
    const library = libraryFactory();
    api.commitLibraryChanges.fn.mockImplementation(() => immediate(null));
    api.getLibraryDetail.fn.mockImplementation(() => immediate(library));
    await commitLibraryChanges(dispatch)({ libraryId: 'test' });
    expect(api.commitLibraryChanges.fn).toHaveBeenCalledWith('test');
    expect(api.getLibraryDetail.fn).toHaveBeenCalledWith('test');
    checkRequested('library');
    checkSuccess('library', library);
  });

  it('Handles a commit failure', async () => {
    const error = new Error('It borked!');
    api.commitLibraryChanges.fn.mockImplementation(() => instaFail(error));
    await commitLibraryChanges(dispatch)({ libraryId: 'test' });
    checkRequested('library');
    checkError('library', error);
  });

  it('Handles a refetch failure, post-commit', async () => {
    const error = new Error('It borked!');
    api.commitLibraryChanges.fn.mockImplementation(() => immediate(null));
    api.getLibraryDetail.fn.mockImplementation(() => instaFail(error));
    await commitLibraryChanges(dispatch)({ libraryId: 'test' });
    checkRequested('library');
    checkError('library', error);
  });

  it('Reverts library changes', async () => {
    const library = libraryFactory();
    const blocks = makeN(blockFactoryLine([], { library }), 3);
    api.revertLibraryChanges.fn.mockImplementation(() => immediate(null));
    api.getLibraryDetail.fn.mockImplementation(() => immediate(library));
    api.getBlocks.fn.mockImplementation(() => immediate(blocks));
    await revertLibraryChanges(dispatch)({ libraryId: 'test' });
  });

  it('Handles a failure during reversion', async () => {
    const error = new Error('It borked!');
    api.revertLibraryChanges.fn.mockImplementation(() => instaFail(error));
    await revertLibraryChanges(dispatch)({ libraryId: 'test' });
    checkRequested('library');
    checkError('library', error);
  });

  it('Handles a failure during refetch after reversion', async () => {
    const error = new Error('It borked!');
    api.revertLibraryChanges.fn.mockImplementation(() => immediate(null));
    api.getLibraryDetail.fn.mockImplementation(() => instaFail(error));
    await revertLibraryChanges(dispatch)({ libraryId: 'test' });
    checkRequested('library');
    checkError('library', error);
    checkError('blocks', error);
  });

  it('Searches for blocks', async () => {
    const library = libraryFactory();
    const blocks = makeN(blockFactoryLine([], { library }), 3);
    api.getBlocks.fn.mockImplementation(() => immediate(blocks));
    await searchLibrary(dispatch)({ libraryId: library.id, query: 'test', types: ['video'] });
    await waitFor(() => checkRequested('blocks'));
    checkSuccess('blocks', blocks);
    expect(api.getBlocks.fn).toHaveBeenCalledWith({ libraryId: library.id, query: 'test', types: ['video'] });
  });
});