diff --git a/.npmignore b/.npmignore
index 62d1cd3..aadad2d 100644
--- a/.npmignore
+++ b/.npmignore
@@ -8,4 +8,5 @@ tsconfig.tsbuildinfo
*.test.*
*.spec.*
vitest.config.ts
-setupTests.ts
\ No newline at end of file
+setupTests.ts
+coverage
\ No newline at end of file
diff --git a/README.md b/README.md
index b689220..b5a51d2 100644
--- a/README.md
+++ b/README.md
@@ -11,6 +11,10 @@ Building blocks for UI applications at RISC Zero.
>
> When making code changes, please have the [Biome VSCode extension](https://marketplace.visualstudio.com/items?itemName=biomejs.biome) installed.
-### Tests
+### 🧪 Tests Coverage
-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
+```md
+| Statements | Branches | Functions | Lines |
+| --------------------------- | ----------------------- | ------------------------- | ----------------- |
+| ![Statements](https://img.shields.io/badge/statements-19.05%25-red.svg?style=flat) | ![Branches](https://img.shields.io/badge/branches-58.18%25-red.svg?style=flat) | ![Functions](https://img.shields.io/badge/functions-33.33%25-red.svg?style=flat) | ![Lines](https://img.shields.io/badge/lines-19.05%25-red.svg?style=flat) |
+```
diff --git a/avatar.test.tsx b/avatar.test.tsx
new file mode 100644
index 0000000..6b04a6b
--- /dev/null
+++ b/avatar.test.tsx
@@ -0,0 +1,37 @@
+import { render, screen } from '@testing-library/react';
+import { createRef } from 'react';
+import { Avatar, AvatarFallback } from './avatar';
+
+describe('Avatar', () => {
+ test('renders without crashing', () => {
+ render();
+ const avatarElement = screen.getByTestId('avatar');
+ expect(avatarElement).toBeInTheDocument();
+ });
+
+ test('forwards ref correctly', () => {
+ const ref = createRef();
+ render();
+ expect(ref.current).not.toBeNull();
+ });
+});
+
+describe('AvatarFallback', () => {
+ test('renders without crashing', () => {
+ render();
+ const avatarFallbackElement = screen.getByTestId('avatar-fallback');
+ expect(avatarFallbackElement).toBeInTheDocument();
+ });
+
+ test('forwards ref correctly', () => {
+ const ref = createRef();
+ render();
+ expect(ref.current).not.toBeNull();
+ });
+
+ test('displays the initials inside', () => {
+ render(CC);
+ const avatarFallbackText = screen.getByText('CC');
+ expect(avatarFallbackText).toBeInTheDocument();
+ });
+});
\ No newline at end of file
diff --git a/avatar.tsx b/avatar.tsx
index b410aa0..18bccb2 100644
--- a/avatar.tsx
+++ b/avatar.tsx
@@ -10,6 +10,7 @@ const Avatar = forwardRef<
>(({ className, ...rest }, ref) => (
@@ -28,6 +29,7 @@ const AvatarFallback = forwardRef<
>(({ className, ...rest }, ref) => (
diff --git a/badge.test.tsx b/badge.test.tsx
new file mode 100644
index 0000000..eddcec1
--- /dev/null
+++ b/badge.test.tsx
@@ -0,0 +1,22 @@
+import { render, screen } from '@testing-library/react';
+import { Badge } from './badge';
+
+describe('Badge', () => {
+ test('renders without crashing', () => {
+ render();
+ const badgeElement = screen.getByTestId('badge');
+ expect(badgeElement).toBeInTheDocument();
+ });
+
+ test('applies correct classes based on variant prop', () => {
+ render();
+ const badgeElement = screen.getByTestId('badge');
+ expect(badgeElement).toHaveClass('border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80');
+ });
+
+ test('forwards additional props to rendered div element', () => {
+ render();
+ const badgeElement = screen.getByTestId('badge');
+ expect(badgeElement).toBeInTheDocument();
+ });
+});
\ No newline at end of file
diff --git a/badge.tsx b/badge.tsx
index 572d422..3a896f2 100644
--- a/badge.tsx
+++ b/badge.tsx
@@ -24,7 +24,7 @@ export interface BadgeProps extends HTMLAttributes, VariantProps
}
function Badge({ className, variant, ...rest }: BadgeProps) {
- return ;
+ return ;
}
export { Badge, badgeVariants };
diff --git a/checkbox.test.tsx b/checkbox.test.tsx
new file mode 100644
index 0000000..fd0252d
--- /dev/null
+++ b/checkbox.test.tsx
@@ -0,0 +1,29 @@
+import { render, screen } from '@testing-library/react';
+import { Checkbox } from './checkbox';
+import { createRef } from 'react';
+
+describe('Checkbox', () => {
+ test('renders without crashing', () => {
+ render();
+ const checkboxElement = screen.getByRole('checkbox');
+ expect(checkboxElement).toBeInTheDocument();
+ });
+
+ test('forwards ref correctly', () => {
+ const ref = createRef();
+ render();
+ expect(ref.current).not.toBeNull();
+ });
+
+ test('applies correct class names', () => {
+ render();
+ const checkboxElement = screen.getByRole('checkbox');
+ expect(checkboxElement).toHaveClass('test-class');
+ });
+
+ test('renders CheckIcon when checked', () => {
+ render();
+ const checkIconElement = screen.getByTestId('check-icon');
+ expect(checkIconElement).toBeInTheDocument();
+ });
+});
\ No newline at end of file
diff --git a/checkbox.tsx b/checkbox.tsx
index e725a3c..60bf56a 100644
--- a/checkbox.tsx
+++ b/checkbox.tsx
@@ -18,7 +18,7 @@ const Checkbox = forwardRef<
{...rest}
>
-
+
));
diff --git a/constants.test.ts b/constants.test.ts
new file mode 100644
index 0000000..1f87ad2
--- /dev/null
+++ b/constants.test.ts
@@ -0,0 +1,13 @@
+import { RISC0_PRIVACY_POLICY_URL, RISC0_TERMS_OF_SERVICE_URL } from './constants';
+
+describe('Constants', () => {
+ it('should have correct RISC0_PRIVACY_POLICY_URL', () => {
+ expect(typeof RISC0_PRIVACY_POLICY_URL).toBe('string');
+ expect(RISC0_PRIVACY_POLICY_URL).toBe('https://www.risczero.com/policy');
+ });
+
+ it('should have correct RISC0_TERMS_OF_SERVICE_URL', () => {
+ expect(typeof RISC0_TERMS_OF_SERVICE_URL).toBe('string');
+ expect(RISC0_TERMS_OF_SERVICE_URL).toBe('https://www.risczero.com/terms-of-service');
+ });
+});
\ No newline at end of file
diff --git a/hooks/use-event-listener.test.ts b/hooks/use-event-listener.test.ts
index b641975..74ea2b2 100644
--- a/hooks/use-event-listener.test.ts
+++ b/hooks/use-event-listener.test.ts
@@ -1,5 +1,5 @@
-import { renderHook } from '@testing-library/react-hooks';
-import type { RefObject } from 'react';
+import { renderHook, act } from '@testing-library/react-hooks';
+import { useRef, type RefObject } from 'react';
import { useEventListener } from './use-event-listener';
import { vi } from 'vitest';
@@ -29,4 +29,72 @@ describe('useEventListener', () => {
expect(handler).not.toHaveBeenCalled();
});
+
+ 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-local-storage.test.ts b/hooks/use-local-storage.test.ts
deleted file mode 100644
index e643dd0..0000000
--- a/hooks/use-local-storage.test.ts
+++ /dev/null
@@ -1,74 +0,0 @@
-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/input.test.tsx b/input.test.tsx
new file mode 100644
index 0000000..20f9c2f
--- /dev/null
+++ b/input.test.tsx
@@ -0,0 +1,31 @@
+import { render, screen } from '@testing-library/react';
+import { Input } from './input';
+import { createRef } from 'react';
+
+describe('Input', () => {
+ test('renders without crashing', () => {
+ render();
+ const inputElement = screen.getByRole('textbox');
+ expect(inputElement).toBeInTheDocument();
+ });
+
+ test('forwards ref correctly', () => {
+ const ref = createRef();
+ render();
+ expect(ref.current).not.toBeNull();
+ });
+
+ test('renders startIcon correctly', () => {
+ const StartIcon = () => ;
+ render(} />);
+ const startIconElement = screen.getByTestId('start-icon');
+ expect(startIconElement).toBeInTheDocument();
+ });
+
+ test('renders endIcon correctly', () => {
+ const EndIcon = () => ;
+ render(} />);
+ const endIconElement = screen.getByTestId('end-icon');
+ expect(endIconElement).toBeInTheDocument();
+ });
+});
\ No newline at end of file
diff --git a/package.json b/package.json
index 7df4eff..ad661de 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "@risc0/ui",
- "version": "0.0.67",
+ "version": "0.0.68",
"sideEffects": false,
"type": "module",
"scripts": {
@@ -8,7 +8,7 @@
"check": "tsc && bunx @biomejs/biome check . --apply-unsafe",
"prepare": "npx husky",
"sort-package": "bunx sort-package-json 'package.json'",
- "test": "bun test spec && vitest run --silent"
+ "test": "vitest run --silent --coverage && istanbul-badges-readme"
},
"dependencies": {
"@radix-ui/react-avatar": "1.0.4",
@@ -50,7 +50,9 @@
"@types/jest": "29.5.12",
"@types/lodash-es": "4.17.12",
"@vitejs/plugin-react-swc": "3.6.0",
+ "@vitest/coverage-v8": "1.6.0",
"happy-dom": "14.10.1",
+ "istanbul-badges-readme": "1.9.0",
"vitest": "1.6.0"
},
"peerDependencies": {
diff --git a/textarea.test.tsx b/textarea.test.tsx
new file mode 100644
index 0000000..4ac2248
--- /dev/null
+++ b/textarea.test.tsx
@@ -0,0 +1,17 @@
+import { render, screen } from '@testing-library/react';
+import { Textarea } from './textarea';
+import { createRef } from 'react';
+
+describe('Textarea', () => {
+ test('renders without crashing', () => {
+ render();
+ const textareaElement = screen.getByRole('textbox');
+ expect(textareaElement).toBeInTheDocument();
+ });
+
+ test('forwards ref correctly', () => {
+ const ref = createRef();
+ render();
+ expect(ref.current).not.toBeNull();
+ });
+});
\ No newline at end of file
diff --git a/utils/join-words.spec.ts b/utils/join-words.test.ts
similarity index 100%
rename from utils/join-words.spec.ts
rename to utils/join-words.test.ts
diff --git a/utils/parse-json.spec.ts b/utils/parse-json.test.ts
similarity index 100%
rename from utils/parse-json.spec.ts
rename to utils/parse-json.test.ts
diff --git a/utils/truncate.spec.ts b/utils/truncate.test.ts
similarity index 100%
rename from utils/truncate.spec.ts
rename to utils/truncate.test.ts
diff --git a/vitest.config.ts b/vitest.config.ts
index 475b2ff..60966fb 100644
--- a/vitest.config.ts
+++ b/vitest.config.ts
@@ -10,6 +10,8 @@ export default defineConfig({
globals: true,
restoreMocks: true,
setupFiles: "./setupTests.ts",
- include: ["**/*.test.?(c|m)[jt]s?(x)"],
+ coverage: {
+ reporter: ["json-summary", "text"],
+ },
},
});