Skip to content

Commit 0f89d15

Browse files
committed
add tests
1 parent fc8fef9 commit 0f89d15

File tree

9 files changed

+1912
-50
lines changed

9 files changed

+1912
-50
lines changed

package-lock.json

Lines changed: 1355 additions & 47 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@
77
"dev": "vite",
88
"build": "tsc -b && vite build",
99
"lint": "eslint .",
10-
"preview": "vite preview"
10+
"preview": "vite preview",
11+
"test": "vitest",
12+
"test:ui": "vitest --ui",
13+
"test:coverage": "vitest --coverage"
1114
},
1215
"dependencies": {
1316
"@servicestack/react": "^2.0.8",
@@ -22,19 +25,26 @@
2225
"@tailwindcss/forms": "^0.5.10",
2326
"@tailwindcss/postcss": "^4.1.17",
2427
"@tailwindcss/typography": "^0.5.19",
28+
"@testing-library/jest-dom": "^6.9.1",
29+
"@testing-library/react": "^16.3.0",
30+
"@testing-library/user-event": "^14.6.1",
2531
"@types/node": "^24.6.0",
2632
"@types/react": "^19.1.16",
2733
"@types/react-dom": "^19.1.9",
2834
"@vitejs/plugin-react": "^5.0.4",
35+
"@vitest/ui": "^4.0.8",
2936
"eslint": "^9.36.0",
3037
"eslint-plugin-react-hooks": "^5.2.0",
3138
"eslint-plugin-react-refresh": "^0.4.22",
3239
"globals": "^16.4.0",
40+
"happy-dom": "^20.0.10",
41+
"jsdom": "^27.1.0",
3342
"postcss": "^8.5.6",
3443
"tailwindcss": "^4.0.0",
3544
"typescript": "~5.9.3",
3645
"typescript-eslint": "^8.45.0",
37-
"vite": "npm:rolldown-vite@7.1.14"
46+
"vite": "npm:rolldown-vite@7.1.14",
47+
"vitest": "^4.0.8"
3848
},
3949
"overrides": {
4050
"vite": "npm:rolldown-vite@7.1.14"

src/App.test.tsx

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { describe, it, expect } from 'vitest'
2+
import { render, screen, fireEvent } from '@testing-library/react'
3+
import App from './App'
4+
5+
describe('App', () => {
6+
it('should render the app', () => {
7+
render(<App />)
8+
expect(screen.getByText('Vite + React')).toBeInTheDocument()
9+
})
10+
11+
it('should display initial count of 0', () => {
12+
render(<App />)
13+
expect(screen.getByRole('button')).toHaveTextContent('count is 0')
14+
})
15+
16+
it('should increment count when button is clicked', () => {
17+
render(<App />)
18+
const button = screen.getByRole('button')
19+
20+
fireEvent.click(button)
21+
expect(button).toHaveTextContent('count is 1')
22+
23+
fireEvent.click(button)
24+
expect(button).toHaveTextContent('count is 2')
25+
})
26+
27+
it('should render Vite logo', () => {
28+
render(<App />)
29+
const viteLogo = screen.getByAltText('Vite logo')
30+
expect(viteLogo).toBeInTheDocument()
31+
expect(viteLogo).toHaveAttribute('src')
32+
})
33+
34+
it('should render React logo', () => {
35+
render(<App />)
36+
const reactLogo = screen.getByAltText('React logo')
37+
expect(reactLogo).toBeInTheDocument()
38+
})
39+
40+
it('should have links to documentation', () => {
41+
render(<App />)
42+
const links = screen.getAllByRole('link')
43+
expect(links).toHaveLength(2)
44+
expect(links[0]).toHaveAttribute('href', 'https://vite.dev')
45+
expect(links[1]).toHaveAttribute('href', 'https://react.dev')
46+
})
47+
48+
it('should display HMR message', () => {
49+
render(<App />)
50+
expect(screen.getByText(/Edit/)).toBeInTheDocument()
51+
expect(screen.getByText(/src\/App.tsx/)).toBeInTheDocument()
52+
})
53+
})
54+
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import { describe, it, expect } from 'vitest'
2+
import { render, screen } from '@testing-library/react'
3+
import CodeBlock from './CodeBlock'
4+
5+
describe('CodeBlock', () => {
6+
it('should render code content', () => {
7+
const code = 'const hello = "world";'
8+
const { container } = render(<CodeBlock code={code} />)
9+
const codeElement = container.querySelector('code')
10+
expect(codeElement).toBeInTheDocument()
11+
expect(codeElement?.textContent).toContain('const')
12+
expect(codeElement?.textContent).toContain('hello')
13+
})
14+
15+
it('should use tsx as default language', () => {
16+
const code = 'const test = 123;'
17+
const { container } = render(<CodeBlock code={code} />)
18+
const pre = container.querySelector('pre')
19+
expect(pre).toBeInTheDocument()
20+
})
21+
22+
it('should accept custom language', () => {
23+
const code = 'print("hello")'
24+
const { container } = render(<CodeBlock code={code} language="python" />)
25+
const pre = container.querySelector('pre')
26+
expect(pre).toBeInTheDocument()
27+
})
28+
29+
it('should apply custom className', () => {
30+
const code = 'test'
31+
const customClass = 'my-custom-class'
32+
const { container } = render(<CodeBlock code={code} className={customClass} />)
33+
expect(container.firstChild).toHaveClass(customClass)
34+
})
35+
36+
it('should render multiline code', () => {
37+
const code = `function test() {
38+
return true;
39+
}`
40+
const { container } = render(<CodeBlock code={code} />)
41+
const codeElement = container.querySelector('code')
42+
expect(codeElement?.textContent).toContain('function')
43+
expect(codeElement?.textContent).toContain('test')
44+
expect(codeElement?.textContent).toContain('return')
45+
expect(codeElement?.textContent).toContain('true')
46+
})
47+
48+
it('should handle empty code', () => {
49+
const { container } = render(<CodeBlock code="" />)
50+
expect(container.querySelector('code')).toBeInTheDocument()
51+
})
52+
53+
it('should handle special characters in code', () => {
54+
const code = 'const str = "<div>&nbsp;</div>";'
55+
const { container } = render(<CodeBlock code={code} />)
56+
const codeElement = container.querySelector('code')
57+
expect(codeElement?.textContent).toContain('const')
58+
expect(codeElement?.textContent).toContain('str')
59+
})
60+
})
61+
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
import { describe, it, expect, beforeEach, vi } from 'vitest'
2+
import { render, screen } from '@testing-library/react'
3+
import GalleryLayout from './GalleryLayout'
4+
5+
// Mock the DarkModeToggle component from @servicestack/react
6+
vi.mock('@servicestack/react', () => ({
7+
DarkModeToggle: () => <div data-testid="dark-mode-toggle">Dark Mode Toggle</div>
8+
}))
9+
10+
describe('GalleryLayout', () => {
11+
beforeEach(() => {
12+
// Reset window.location.pathname before each test
13+
Object.defineProperty(window, 'location', {
14+
value: { pathname: '/' },
15+
writable: true,
16+
})
17+
})
18+
19+
it('should render children content', () => {
20+
render(
21+
<GalleryLayout>
22+
<div>Test Content</div>
23+
</GalleryLayout>
24+
)
25+
expect(screen.getByText('Test Content')).toBeInTheDocument()
26+
})
27+
28+
it('should render title when provided', () => {
29+
render(
30+
<GalleryLayout title="Test Title">
31+
<div>Content</div>
32+
</GalleryLayout>
33+
)
34+
expect(screen.getByText('Test Title')).toBeInTheDocument()
35+
})
36+
37+
it('should not render title when not provided', () => {
38+
const { container } = render(
39+
<GalleryLayout>
40+
<div>Content</div>
41+
</GalleryLayout>
42+
)
43+
const h1 = container.querySelector('h1')
44+
expect(h1).toBeNull()
45+
})
46+
47+
it('should render header with logo and site name', () => {
48+
render(<GalleryLayout><div>Content</div></GalleryLayout>)
49+
expect(screen.getByText('React Component Gallery')).toBeInTheDocument()
50+
})
51+
52+
it('should render React logo SVG', () => {
53+
const { container } = render(<GalleryLayout><div>Content</div></GalleryLayout>)
54+
const svg = container.querySelector('svg')
55+
expect(svg).toBeInTheDocument()
56+
expect(svg).toHaveClass('w-8', 'h-8', 'text-[#61DAFB]')
57+
})
58+
59+
it('should render DarkModeToggle', () => {
60+
render(<GalleryLayout><div>Content</div></GalleryLayout>)
61+
expect(screen.getByTestId('dark-mode-toggle')).toBeInTheDocument()
62+
})
63+
64+
it('should render navigation sidebar', () => {
65+
render(<GalleryLayout><div>Content</div></GalleryLayout>)
66+
expect(screen.getByText('Getting Started')).toBeInTheDocument()
67+
expect(screen.getByText('Component Gallery')).toBeInTheDocument()
68+
expect(screen.getByText('Library')).toBeInTheDocument()
69+
})
70+
71+
it('should render all navigation links', () => {
72+
render(<GalleryLayout><div>Content</div></GalleryLayout>)
73+
74+
// Getting Started
75+
expect(screen.getByText('Installation')).toBeInTheDocument()
76+
77+
// Component Gallery
78+
expect(screen.getByText('AutoQueryGrid')).toBeInTheDocument()
79+
expect(screen.getByText('DataGrid')).toBeInTheDocument()
80+
expect(screen.getByText('Auto Forms')).toBeInTheDocument()
81+
expect(screen.getByText('FileInput')).toBeInTheDocument()
82+
expect(screen.getByText('TagInput')).toBeInTheDocument()
83+
expect(screen.getByText('Combobox')).toBeInTheDocument()
84+
expect(screen.getByText('Autocomplete')).toBeInTheDocument()
85+
expect(screen.getByText('Markdown Editor')).toBeInTheDocument()
86+
87+
// Library
88+
expect(screen.getByText('useMetadata')).toBeInTheDocument()
89+
expect(screen.getByText('useClient')).toBeInTheDocument()
90+
expect(screen.getByText('useAuth')).toBeInTheDocument()
91+
})
92+
93+
it('should highlight active link based on current path', () => {
94+
Object.defineProperty(window, 'location', {
95+
value: { pathname: '/gallery/autoquerygrid' },
96+
writable: true,
97+
})
98+
99+
const { container } = render(<GalleryLayout><div>Content</div></GalleryLayout>)
100+
const activeLink = container.querySelector('a[href="/gallery/autoquerygrid"]')
101+
102+
expect(activeLink).toHaveClass('text-indigo-600', 'dark:text-indigo-400', 'font-semibold')
103+
})
104+
105+
it('should apply hover styles to non-active links', () => {
106+
Object.defineProperty(window, 'location', {
107+
value: { pathname: '/' },
108+
writable: true,
109+
})
110+
111+
const { container } = render(<GalleryLayout><div>Content</div></GalleryLayout>)
112+
const link = container.querySelector('a[href="/gallery/autoquerygrid"]')
113+
114+
expect(link).toHaveClass('text-gray-700', 'dark:text-gray-300')
115+
})
116+
117+
it('should have correct link hrefs', () => {
118+
const { container } = render(<GalleryLayout><div>Content</div></GalleryLayout>)
119+
120+
const installLink = container.querySelector('a[href="/gallery/install"]')
121+
expect(installLink).toBeInTheDocument()
122+
123+
const autoQueryLink = container.querySelector('a[href="/gallery/autoquerygrid"]')
124+
expect(autoQueryLink).toBeInTheDocument()
125+
126+
const useMetadataLink = container.querySelector('a[href="/gallery/use-metadata"]')
127+
expect(useMetadataLink).toBeInTheDocument()
128+
})
129+
130+
it('should render main content area with prose styling', () => {
131+
const { container } = render(
132+
<GalleryLayout>
133+
<div>Test Content</div>
134+
</GalleryLayout>
135+
)
136+
137+
const main = container.querySelector('main')
138+
expect(main).toHaveClass('prose', 'prose-slate', 'dark:prose-invert')
139+
})
140+
141+
it('should have responsive layout classes', () => {
142+
const { container } = render(<GalleryLayout><div>Content</div></GalleryLayout>)
143+
144+
const aside = container.querySelector('aside')
145+
expect(aside).toHaveClass('hidden', 'lg:block')
146+
})
147+
})
148+

0 commit comments

Comments
 (0)