/** biome-ignore-all lint/correctness/useHookAtTopLevel: function is not a React hook */ import { useSuspensedPromise } from './use-suspended-promise.js'; describe('useSuspensedPromise', () => { beforeEach(() => {}); it('suspends (throw promise) on first call', () => { const mockPromise = Promise.resolve('test-result'); const promiseFn = vi.fn(() => mockPromise); const key = 'test-key-1'; let thrownValue: any; try { useSuspensedPromise(promiseFn, key); } catch (thrown) { thrownValue = thrown; } expect(thrownValue).toBeInstanceOf(Promise); expect(promiseFn).toHaveBeenCalledOnce(); }); it('returns cached result on subsequent calls after promise resolves', async () => { const result = 'test-result'; const promiseFn = vi.fn(() => Promise.resolve(result)); const key = 'test-key-2'; let thrownPromise: Promise; try { useSuspensedPromise(promiseFn, key); } catch (promise) { thrownPromise = promise as Promise; } await thrownPromise!; const cachedResult = useSuspensedPromise(promiseFn, key); expect(cachedResult).toBe(result); expect(promiseFn).toHaveBeenCalledOnce(); }); it('throws cached error on subsequent calls after promise rejects', async () => { const error = new Error('test-error'); const promiseFn = vi.fn(() => Promise.reject(error)); const key = 'test-key-3'; let thrownPromise: Promise; try { useSuspensedPromise(promiseFn, key); } catch (promise) { thrownPromise = promise as Promise; } try { await thrownPromise!; } catch {} let thrownError: any; try { useSuspensedPromise(promiseFn, key); } catch (thrown) { thrownError = thrown; } expect(thrownError).toBe(error); expect(promiseFn).toHaveBeenCalledOnce(); }); it('handles different keys independently', async () => { const result1 = 'result1'; const result2 = 'result2'; const promiseFn1 = vi.fn(() => Promise.resolve(result1)); const promiseFn2 = vi.fn(() => Promise.resolve(result2)); const key1 = 'test-key-4'; const key2 = 'test-key-5'; let promise1: Promise; let promise2: Promise; try { useSuspensedPromise(promiseFn1, key1); } catch (p) { promise1 = p as Promise; } try { useSuspensedPromise(promiseFn2, key2); } catch (p) { promise2 = p as Promise; } await Promise.all([promise1!, promise2!]); expect(useSuspensedPromise(promiseFn1, key1)).toBe(result1); expect(useSuspensedPromise(promiseFn2, key2)).toBe(result2); expect(promiseFn1).toHaveBeenCalledOnce(); expect(promiseFn2).toHaveBeenCalledOnce(); }); it('handles the same key with different promise functions', async () => { const result1 = 'result1'; const promiseFn1 = vi.fn(() => Promise.resolve(result1)); const promiseFn2 = vi.fn(() => Promise.resolve('result2')); const key = 'test-key-6'; let promise1: Promise; try { useSuspensedPromise(promiseFn1, key); } catch (p) { promise1 = p as Promise; } await promise1!; const cachedResult = useSuspensedPromise(promiseFn2, key); expect(cachedResult).toBe(result1); expect(promiseFn1).toHaveBeenCalledOnce(); expect(promiseFn2).not.toHaveBeenCalled(); }); it('handles promise that resolves to undefined', async () => { const promiseFn = vi.fn(() => Promise.resolve(undefined)); const key = 'test-key-7'; let thrownPromise: Promise; try { useSuspensedPromise(promiseFn, key); } catch (promise) { thrownPromise = promise as Promise; } await thrownPromise!; const result = useSuspensedPromise(promiseFn, key); expect(result).toBeUndefined(); }); it('handles promise that resolves to null', async () => { const promiseFn = vi.fn(() => Promise.resolve(null)); const key = 'test-key-8'; let thrownPromise: Promise; try { useSuspensedPromise(promiseFn, key); } catch (promise) { thrownPromise = promise as Promise; } await thrownPromise!; const result = useSuspensedPromise(promiseFn, key); expect(result).toBeNull(); }); it('handles promise that resolves to falsy values', async () => { const falsyValues = [false, 0, '', null, undefined]; for (let i = 0; i < falsyValues.length; i++) { const value = falsyValues[i]; const promiseFn = vi.fn(() => Promise.resolve(value)); const key = `test-key-falsy-${i}`; let thrownPromise: Promise; try { useSuspensedPromise(promiseFn, key); } catch (promise) { thrownPromise = promise as Promise; } await thrownPromise!; const result = useSuspensedPromise(promiseFn, key); expect(result).toBe(value); } }); it('handles promise that rejects with non-Error values', async () => { const rejectionValues = [ 'string error', 42, null, undefined, { message: 'object error' }, ]; for (let i = 0; i < rejectionValues.length; i++) { const value = rejectionValues[i]; const promiseFn = vi.fn(() => Promise.reject(value)); const key = `test-key-rejection-${i}`; let thrownPromise: Promise; try { useSuspensedPromise(promiseFn, key); } catch (promise) { thrownPromise = promise as Promise; } try { await thrownPromise!; } catch {} let thrownError: any; try { useSuspensedPromise(promiseFn, key); } catch (thrown) { thrownError = thrown; } expect(thrownError).toBe(value); } }); it('works with generic types', async () => { interface TestData { id: number; name: string; } const testData: TestData = { id: 1, name: 'test' }; const promiseFn = vi.fn(() => Promise.resolve(testData)); const key = 'test-key-9'; let thrownPromise: Promise; try { useSuspensedPromise(promiseFn, key); } catch (promise) { thrownPromise = promise as Promise; } await thrownPromise!; const result = useSuspensedPromise(promiseFn, key); expect(result).toEqual(testData); expect(result.id).toBe(1); expect(result.name).toBe('test'); }); it('maintains state across multiple calls with the same key', () => { const promiseFn = vi.fn(() => Promise.resolve('test')); const key = 'test-key-10'; let thrown1: any; let thrown2: any; let thrown3: any; try { useSuspensedPromise(promiseFn, key); } catch (e) { thrown1 = e; } try { useSuspensedPromise(promiseFn, key); } catch (e) { thrown2 = e; } try { useSuspensedPromise(promiseFn, key); } catch (e) { thrown3 = e; } expect(thrown1).toBeInstanceOf(Promise); expect(thrown2).toBeInstanceOf(Promise); expect(thrown3).toBeInstanceOf(Promise); expect(promiseFn).toHaveBeenCalledOnce(); }); });