From e0037bf6cf25ac3e35e6aa9cef8981b11cd2f2b9 Mon Sep 17 00:00:00 2001 From: Cohan Carpentier Date: Tue, 7 May 2024 09:28:43 -0400 Subject: [PATCH] tests: add tests --- .npmignore | 3 +- README.md | 6 ++- hooks/use-event-listener.test.ts | 32 ++++++++++++++ hooks/use-local-storage.test.ts | 74 ++++++++++++++++++++++++++++++++ hooks/use-mounted.test.ts | 12 ++++++ package.json | 16 ++++--- utils/parse-json.spec.ts | 25 +++++++++++ utils/parse-json.ts | 4 +- vitest.config.ts | 14 ++++++ 9 files changed, 177 insertions(+), 9 deletions(-) create mode 100644 hooks/use-event-listener.test.ts create mode 100644 hooks/use-local-storage.test.ts create mode 100644 hooks/use-mounted.test.ts create mode 100644 utils/parse-json.spec.ts create mode 100644 vitest.config.ts diff --git a/.npmignore b/.npmignore index cc5ad0b..777ebe2 100644 --- a/.npmignore +++ b/.npmignore @@ -6,4 +6,5 @@ biome.jsonc components.json tsconfig.tsbuildinfo *.test.* -*.spec.* \ No newline at end of file +*.spec.* +vitest.config.ts \ No newline at end of file diff --git a/README.md b/README.md index 2d9ea21..b689220 100644 --- a/README.md +++ b/README.md @@ -9,4 +9,8 @@ Building blocks for UI applications at RISC Zero. > #### General > -> When making code changes, please have the [Biome VSCode extension](https://marketplace.visualstudio.com/items?itemName=biomejs.biome) installed. \ No newline at end of file +> When making code changes, please have the [Biome VSCode extension](https://marketplace.visualstudio.com/items?itemName=biomejs.biome) installed. + +### Tests + +All test files named with the pattern `*.spec.*` are ran using `bun test` while all files named `*.test.*` are ran through `vitest`. \ No newline at end of file diff --git a/hooks/use-event-listener.test.ts b/hooks/use-event-listener.test.ts new file mode 100644 index 0000000..b641975 --- /dev/null +++ b/hooks/use-event-listener.test.ts @@ -0,0 +1,32 @@ +import { renderHook } from '@testing-library/react-hooks'; +import type { RefObject } from 'react'; +import { useEventListener } from './use-event-listener'; +import { vi } from 'vitest'; + +describe('useEventListener', () => { + it('should call handler when the event is triggered', () => { + const handler = vi.fn(); + const event = new Event('click'); + const ref: RefObject = { current: document.createElement('div') }; + + renderHook(() => useEventListener('click', handler, ref)); + + ref.current?.dispatchEvent(event); + + expect(handler).toHaveBeenCalledWith(event); + }); + + it('should not call handler after unmount', () => { + const handler = vi.fn(); + const event = new Event('click'); + const ref: RefObject = { current: document.createElement('div') }; + + const { unmount } = renderHook(() => useEventListener('click', handler, ref)); + + unmount(); + + ref.current?.dispatchEvent(event); + + expect(handler).not.toHaveBeenCalled(); + }); +}); \ No newline at end of file diff --git a/hooks/use-local-storage.test.ts b/hooks/use-local-storage.test.ts new file mode 100644 index 0000000..e643dd0 --- /dev/null +++ b/hooks/use-local-storage.test.ts @@ -0,0 +1,74 @@ +import { renderHook, act } from '@testing-library/react-hooks'; +import { useRef } from 'react'; +import { useEventListener } from './use-event-listener'; +import { vi } from 'vitest'; + +describe('useEventListener', () => { + it('adds event listener to window when no element is provided', () => { + const handler = vi.fn(); + const { unmount } = renderHook(() => useEventListener('click', handler)); + + act(() => { + window.dispatchEvent(new Event('click')); + }); + + expect(handler).toHaveBeenCalled(); + + unmount(); + }); + + it('adds event listener to provided element', () => { + const handler = vi.fn(); + const { result, unmount } = renderHook(() => { + const ref = useRef(document.createElement('div')); + useEventListener('click', handler, ref); + return ref; + }); + + act(() => { + result.current?.current?.dispatchEvent(new Event('click')); + }); + + expect(handler).toHaveBeenCalled(); + + unmount(); + }); + + it('removes event listener when component unmounts', () => { + const handler = vi.fn(); + const { unmount } = renderHook(() => useEventListener('click', handler)); + + unmount(); + + act(() => { + window.dispatchEvent(new Event('click')); + }); + + expect(handler).not.toHaveBeenCalled(); + }); + + it('updates event listener when handler changes', () => { + const handler1 = vi.fn(); + const handler2 = vi.fn(); + const { rerender, unmount } = renderHook( + ({ handler }) => useEventListener('click', handler), + { initialProps: { handler: handler1 } } + ); + + act(() => { + window.dispatchEvent(new Event('click')); + }); + + expect(handler1).toHaveBeenCalled(); + + rerender({ handler: handler2 }); + + act(() => { + window.dispatchEvent(new Event('click')); + }); + + expect(handler2).toHaveBeenCalled(); + + unmount(); + }); +}); \ No newline at end of file diff --git a/hooks/use-mounted.test.ts b/hooks/use-mounted.test.ts new file mode 100644 index 0000000..120c411 --- /dev/null +++ b/hooks/use-mounted.test.ts @@ -0,0 +1,12 @@ +import { renderHook } from '@testing-library/react-hooks'; +import { useMounted } from './use-mounted'; + +describe('useMounted', () => { + it('should return true after the component has been mounted', () => { + const { result, waitForNextUpdate } = renderHook(() => useMounted()); + + waitForNextUpdate().then(() => { + expect(result.current).toBe(true); + }); + }); +}); \ No newline at end of file diff --git a/package.json b/package.json index 435239e..6961bd9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@risc0/ui", - "version": "0.0.64", + "version": "0.0.65", "sideEffects": false, "type": "module", "scripts": { @@ -8,10 +8,9 @@ "check": "tsc && bunx @biomejs/biome check . --apply-unsafe", "prepare": "npx husky", "sort-package": "bunx sort-package-json 'package.json'", - "test": "bun test spec" + "test": "bun test spec && vitest run --silent" }, "dependencies": { - "@biomejs/biome": "1.7.3", "@radix-ui/react-avatar": "1.0.4", "@radix-ui/react-checkbox": "1.0.4", "@radix-ui/react-dialog": "1.0.5", @@ -28,8 +27,6 @@ "@radix-ui/react-switch": "1.0.3", "@radix-ui/react-tabs": "1.0.4", "@radix-ui/react-tooltip": "1.0.7", - "@types/jest": "29.5.12", - "@types/lodash-es": "4.17.12", "autoprefixer": "10.4.19", "class-variance-authority": "0.7.0", "clsx": "2.1.1", @@ -45,6 +42,15 @@ "tailwindcss-animate": "1.0.7", "typescript": "5.4.5" }, + "devDependencies": { + "@biomejs/biome": "1.7.3", + "@testing-library/react-hooks": "8.0.1", + "@types/jest": "29.5.12", + "@types/lodash-es": "4.17.12", + "@vitejs/plugin-react-swc": "3.6.0", + "happy-dom": "14.10.1", + "vitest": "1.6.0" + }, "peerDependencies": { "@types/react": "^18", "@types/react-dom": "^18", diff --git a/utils/parse-json.spec.ts b/utils/parse-json.spec.ts new file mode 100644 index 0000000..1551b88 --- /dev/null +++ b/utils/parse-json.spec.ts @@ -0,0 +1,25 @@ +import { parseJson } from './parse-json'; + +describe('parseJson', () => { + it('should return an object when a valid JSON string is passed', () => { + const jsonString = '{"key":"value"}'; + expect(parseJson(jsonString)).toEqual({ key: 'value' }); + }); + + it('should return undefined when an invalid JSON string is passed', () => { + const invalidJsonString = '{key:value}'; + expect(parseJson(invalidJsonString)).toBeUndefined(); + }); + + it('should return undefined when "undefined" is passed', () => { + expect(parseJson("undefined")).toBeUndefined(); + }); + + it('should return undefined when null is passed', () => { + expect(parseJson(null)).toBeUndefined(); + }); + + it('should return undefined when an empty string is passed', () => { + expect(parseJson("")).toBeUndefined(); + }); +}); \ No newline at end of file diff --git a/utils/parse-json.ts b/utils/parse-json.ts index 28b2c01..8ce2726 100644 --- a/utils/parse-json.ts +++ b/utils/parse-json.ts @@ -4,7 +4,7 @@ export const parseJson = (value?: string | null): T | undefined => { return value === "undefined" ? undefined : (JSON.parse(value ?? "") as T); } catch { console.error("parsing error on", { value }); - - return undefined; } + + return; }; diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 0000000..da5a0c2 --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,14 @@ +/// + +import react from "@vitejs/plugin-react-swc"; +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + plugins: [react()], + test: { + environment: "happy-dom", + globals: true, + restoreMocks: true, + include: ["**/*.test.?(c|m)[jt]s?(x)"], + }, +});