Files
teleport/web/packages/shared/hooks/useAsync.test.ts
T
Jeff Pihach 3f75acb163 Add license headers to web files that were missing it. (#21315)
* Lint the web files but not node_modules or vendored files.

* Add license headers to web files that were missing it.

* Add missing licenses.
2023-02-07 16:27:31 +00:00

174 lines
5.4 KiB
TypeScript

/**
* Copyright 2023 Gravitational, Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { renderHook, act } from '@testing-library/react-hooks';
import { useAsync, CanceledError } from './useAsync';
test('run returns a promise which resolves with the attempt data', async () => {
const returnValue = Symbol();
const { result, waitForNextUpdate } = renderHook(() =>
useAsync(() => Promise.resolve(returnValue))
);
let [, run] = result.current;
let promise: Promise<[symbol, Error]>;
act(() => {
promise = run();
});
await waitForNextUpdate();
await expect(promise).resolves.toEqual([returnValue, null]);
});
test('run resolves the promise to an error and does not update the state on unmount when the callback returns a resolved promise', async () => {
const { result, unmount } = renderHook(() =>
useAsync(() => Promise.resolve(Symbol()))
);
let [, run] = result.current;
let promise: Promise<[symbol, Error]>;
act(() => {
promise = run();
});
unmount();
await expect(promise).resolves.toEqual([null, new CanceledError()]);
const [attempt] = result.current;
expect(attempt.status).toBe('processing');
});
test('run resolves the promise to an error and does not update the state on unmount when the callback returns a rejected promise', async () => {
const { result, unmount } = renderHook(() =>
useAsync(() => Promise.reject(new Error('oops')))
);
let [, run] = result.current;
let promise: Promise<[symbol, Error]>;
act(() => {
promise = run();
});
unmount();
await expect(promise).resolves.toEqual([null, new CanceledError()]);
const [attempt] = result.current;
expect(attempt.status).toBe('processing');
});
test('run resolves the promise to an error after being re-run when the callback returns a resolved promise', async () => {
const { result, waitForNextUpdate } = renderHook(() =>
useAsync((count: number) => Promise.resolve(count))
);
let [, run] = result.current;
let firstRunPromise: Promise<[number, Error]>;
act(() => {
firstRunPromise = run(1);
});
act(() => {
run(2);
});
await waitForNextUpdate();
await expect(firstRunPromise).resolves.toEqual([null, new CanceledError()]);
});
test('run does not update state after being re-run when the callback returns a resolved promise', async () => {
let resolveFirstPromise, resolveSecondPromise;
const firstPromise = new Promise(resolve => {
resolveFirstPromise = resolve;
});
const secondPromise = new Promise(resolve => {
resolveSecondPromise = resolve;
});
const { result } = renderHook(() =>
// Passing a promise lets us control when the callback resolves.
useAsync((promise: Promise<unknown>) => promise)
);
let [, run] = result.current;
await act(async () => {
// Start two runs, one after the other.
const firstRunPromise = run(firstPromise);
const secondRunPromise = run(secondPromise);
// Once the first promise resolves, it should see that another one was started. The first
// promise should return early with an error.
resolveFirstPromise();
await firstRunPromise;
const attemptAfterFirstPromise = result.current[0];
expect(attemptAfterFirstPromise.status).toBe('processing');
resolveSecondPromise();
await secondRunPromise;
});
});
test('run resolves the promise to an error after being re-run when the callback returns a rejected promise', async () => {
const { result, waitForNextUpdate } = renderHook(() =>
useAsync((count: number) => Promise.reject(new Error(`oops ${count}`)))
);
let [, run] = result.current;
let firstRunPromise: Promise<[number, Error]>;
act(() => {
firstRunPromise = run(1);
});
act(() => {
run(2);
});
await waitForNextUpdate();
await expect(firstRunPromise).resolves.toEqual([null, new CanceledError()]);
});
test('run does not update state after being re-run when the callback returns a rejected promise', async () => {
let rejectFirstPromise, rejectSecondPromise;
const firstPromise = new Promise((resolve, reject) => {
rejectFirstPromise = reject;
});
const secondPromise = new Promise((resolve, reject) => {
rejectSecondPromise = reject;
});
const { result } = renderHook(() =>
// Passing a promise lets us control when the callback resolves.
useAsync((promise: Promise<unknown>) => promise)
);
let [, run] = result.current;
await act(async () => {
// Start two runs, one after the other.
const firstRunPromise = run(firstPromise);
const secondRunPromise = run(secondPromise);
// Once the first promise resolves, it should see that another one was started. The first
// promise should return early with an error.
rejectFirstPromise();
await firstRunPromise;
const attemptAfterFirstPromise = result.current[0];
expect(attemptAfterFirstPromise.status).toBe('processing');
rejectSecondPromise();
await secondRunPromise;
});
});