diff --git a/packages/hooks/src/__tests__/refTypes.spec.ts b/packages/hooks/src/__tests__/refTypes.spec.ts new file mode 100644 index 0000000000..752f63b715 --- /dev/null +++ b/packages/hooks/src/__tests__/refTypes.spec.ts @@ -0,0 +1,56 @@ +import { fileURLToPath } from 'node:url'; +import ts from 'typescript'; +import { describe, expect, test } from 'vitest'; + +const emitDeclaration = (sourceFile: string) => { + let declaration = ''; + const program = ts.createProgram([sourceFile], { + declaration: true, + emitDeclarationOnly: true, + jsx: ts.JsxEmit.React, + module: ts.ModuleKind.ESNext, + moduleResolution: ts.ModuleResolutionKind.Bundler, + skipLibCheck: true, + strictNullChecks: true, + target: ts.ScriptTarget.ES2015, + types: [], + }); + + const diagnostics = ts.getPreEmitDiagnostics(program); + if (diagnostics.length > 0) { + throw new Error( + ts.formatDiagnosticsWithColorAndContext(diagnostics, { + getCanonicalFileName: (fileName) => fileName, + getCurrentDirectory: () => process.cwd(), + getNewLine: () => '\n', + }), + ); + } + + const emitResult = program.emit(undefined, (fileName, text) => { + if (fileName.endsWith('.d.ts')) { + declaration = text; + } + }); + + expect(emitResult.emitSkipped).toBe(false); + return declaration; +}; + +describe('ref return type declarations', () => { + test.each([ + [ + 'useLatest', + '../useLatest/index.ts', + 'declare function useLatest(value: T): MutableRefObject;', + ], + [ + 'useUnmountedRef', + '../useUnmountedRef/index.tsx', + 'declare const useUnmountedRef: () => MutableRefObject;', + ], + ])('%s should expose a non-nullable mutable ref', (_hook, sourcePath, expected) => { + const sourceFile = fileURLToPath(new URL(sourcePath, import.meta.url)); + expect(emitDeclaration(sourceFile)).toContain(expected); + }); +}); diff --git a/packages/hooks/src/useLatest/index.ts b/packages/hooks/src/useLatest/index.ts index d9897e2bcf..e6c49f52d4 100644 --- a/packages/hooks/src/useLatest/index.ts +++ b/packages/hooks/src/useLatest/index.ts @@ -1,6 +1,7 @@ +import type { MutableRefObject } from 'react'; import { useRef } from 'react'; -function useLatest(value: T) { +function useLatest(value: T): MutableRefObject { const ref = useRef(value); ref.current = value; diff --git a/packages/hooks/src/useUnmountedRef/index.tsx b/packages/hooks/src/useUnmountedRef/index.tsx index 540786bf78..d03717a3ff 100644 --- a/packages/hooks/src/useUnmountedRef/index.tsx +++ b/packages/hooks/src/useUnmountedRef/index.tsx @@ -1,6 +1,7 @@ +import type { MutableRefObject } from 'react'; import { useEffect, useRef } from 'react'; -const useUnmountedRef = () => { +const useUnmountedRef = (): MutableRefObject => { const unmountedRef = useRef(false); useEffect(() => { unmountedRef.current = false;