Skip to content

Commit 7dfeb12

Browse files
test(coverage): A-M pages/components to >=95% line coverage (#133)
* test(coverage): drive A-M pages/components to >=95% line coverage Adds vitest/@testing-library coverage for the first half (filenames A-M) of src/pages and src/components: new tests for previously-untested pages (Blog, Login, LoginCallback, Incidents, Checkout, Docs, ForAgents, Contracts) and components (CustomDomainPanel, CodeBlock, CustomerDetailDrawer, IssuePromoModal, Common pills/Sparkline/PromptCard), plus extended tests for BillingPage (checkout error + verify-email gate + UpdatePaymentButton + invoice period + formatAsOf), DeploymentsPage (status filter/bucket/sort/ no-match), and DeployDetailPage (stack-kind view chrome + tabs + Promote upsell). Covers loading/error/empty/success states and user interactions. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * build: add missing @vitest/coverage-v8 devDependency The `coverage` CI job runs `npm test -- --coverage` but the provider was never declared in package.json, so the job has failed on every PR since #130 un-masked it (MISSING DEPENDENCY @vitest/coverage-v8). Pin it to the installed vitest version (1.6.1) so the coverage gate runs. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * test(docs): make DocsPage TOC test resilient to empty docs corpus The `coverage` CI job runs `vitest run` directly without the fetch-content prebuild, so the .content/docs glob is empty and SECTIONS has zero entries. Assert the TOC <ol> exists rather than requiring a populated section list. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude <claude@anthropic.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 8a2699b commit 7dfeb12

16 files changed

Lines changed: 1675 additions & 1 deletion

src/components/CodeBlock.test.tsx

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/* CodeBlock.test.tsx — fenced code block: highlight + copy. */
2+
3+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'
4+
import { render, screen, waitFor, cleanup } from '@testing-library/react'
5+
import userEvent from '@testing-library/user-event'
6+
7+
const copyMock = vi.fn()
8+
vi.mock('./Common', async () => {
9+
const actual = await vi.importActual<typeof import('./Common')>('./Common')
10+
return { ...actual, copyToClipboard: (...a: any[]) => copyMock(...a) }
11+
})
12+
13+
import { CodeBlock } from './CodeBlock'
14+
15+
beforeEach(() => { vi.clearAllMocks() })
16+
afterEach(() => cleanup())
17+
18+
describe('CodeBlock', () => {
19+
it('renders monochrome with no language', () => {
20+
render(<CodeBlock code="just text" lang={null} />)
21+
expect(screen.getByText('just text')).toBeTruthy()
22+
expect(document.querySelector('pre')!.getAttribute('data-lang')).toBe('')
23+
expect(document.querySelector('.code-block-lang')).toBeNull()
24+
})
25+
26+
it('renders an unknown language as monochrome', () => {
27+
render(<CodeBlock code="x = 1" lang="rust" />)
28+
expect(screen.getByText('x = 1')).toBeTruthy()
29+
expect(document.querySelector('pre')!.getAttribute('data-lang')).toBe('')
30+
})
31+
32+
it('highlights bash with the lang label', () => {
33+
render(<CodeBlock code={'# comment\ncurl -X POST "url" 42'} lang="sh" />)
34+
expect(document.querySelector('.code-block-lang')!.textContent).toBe('bash')
35+
expect(document.querySelector('.tok-comment')).toBeTruthy()
36+
expect(document.querySelector('.tok-keyword')).toBeTruthy()
37+
expect(document.querySelector('.tok-flag')).toBeTruthy()
38+
expect(document.querySelector('.tok-number')).toBeTruthy()
39+
expect(document.querySelector('.tok-string')).toBeTruthy()
40+
})
41+
42+
it('highlights json keys, strings, numbers, bools', () => {
43+
render(<CodeBlock code={'{"k": "v", "n": 3, "b": true, "z": null}'} lang="jsonc" />)
44+
expect(document.querySelector('.tok-key')).toBeTruthy()
45+
expect(document.querySelector('.tok-string')).toBeTruthy()
46+
expect(document.querySelector('.tok-number')).toBeTruthy()
47+
expect(document.querySelector('.tok-bool')).toBeTruthy()
48+
})
49+
50+
it('highlights yaml keys, comments, numbers, bools, strings', () => {
51+
render(<CodeBlock code={'# c\nkey: "val"\nnum: 5\nflag: yes'} lang="yml" />)
52+
expect(document.querySelector('.code-block-lang')!.textContent).toBe('yaml')
53+
expect(document.querySelector('.tok-comment')).toBeTruthy()
54+
expect(document.querySelector('.tok-key')).toBeTruthy()
55+
expect(document.querySelector('.tok-number')).toBeTruthy()
56+
expect(document.querySelector('.tok-bool')).toBeTruthy()
57+
expect(document.querySelector('.tok-string')).toBeTruthy()
58+
})
59+
60+
it('copies code and flashes "Copied!"', async () => {
61+
copyMock.mockResolvedValue(true)
62+
render(<CodeBlock code="echo hi" lang="bash" />)
63+
const btn = screen.getByRole('button', { name: 'Copy code to clipboard' })
64+
await userEvent.click(btn)
65+
expect(copyMock).toHaveBeenCalledWith('echo hi')
66+
await waitFor(() => expect(screen.getByText('Copied!')).toBeTruthy())
67+
})
68+
69+
it('does not flash when the copy fails', async () => {
70+
copyMock.mockResolvedValue(false)
71+
render(<CodeBlock code="echo hi" lang="bash" />)
72+
await userEvent.click(screen.getByRole('button', { name: 'Copy code to clipboard' }))
73+
expect(screen.queryByText('Copied!')).toBeNull()
74+
})
75+
})
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
/* Common.pills.test.tsx — render coverage for the small Common.tsx
2+
* presentational exports (StatusPill, RolePill, ScopePill, Sparkline,
3+
* Skeleton) and the PromptCard copy paths. */
4+
5+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'
6+
import { render, screen, waitFor, cleanup } from '@testing-library/react'
7+
import userEvent from '@testing-library/user-event'
8+
9+
import {
10+
StatusPill,
11+
RolePill,
12+
ScopePill,
13+
Sparkline,
14+
Skeleton,
15+
PromptCard,
16+
} from './Common'
17+
18+
let writeText: ReturnType<typeof vi.fn>
19+
beforeEach(() => {
20+
vi.clearAllMocks()
21+
writeText = vi.fn().mockResolvedValue(undefined)
22+
Object.defineProperty(navigator, 'clipboard', {
23+
value: { writeText }, configurable: true, writable: true,
24+
})
25+
})
26+
afterEach(() => cleanup())
27+
28+
describe('StatusPill', () => {
29+
it('maps running→healthy and deploying→building', () => {
30+
const { rerender } = render(<StatusPill status="running" />)
31+
expect(document.querySelector('.status-pill.healthy')!.textContent).toBe('healthy')
32+
rerender(<StatusPill status="deploying" />)
33+
expect(document.querySelector('.status-pill.building')!.textContent).toBe('building')
34+
})
35+
it('renders failed, expired (→stopped) and a fallback', () => {
36+
const { rerender } = render(<StatusPill status="failed" />)
37+
expect(document.querySelector('.status-pill.failed')).toBeTruthy()
38+
rerender(<StatusPill status="expired" />)
39+
expect(document.querySelector('.status-pill.stopped')!.textContent).toBe('expired')
40+
rerender(<StatusPill status={'stopped' as any} />)
41+
expect(document.querySelector('.status-pill.stopped')).toBeTruthy()
42+
})
43+
})
44+
45+
describe('RolePill', () => {
46+
it('adds the role modifier class only for owner/admin', () => {
47+
const { rerender } = render(<RolePill role={'owner' as any} />)
48+
expect(document.querySelector('.role-pill.owner')).toBeTruthy()
49+
rerender(<RolePill role={'admin' as any} />)
50+
expect(document.querySelector('.role-pill.admin')).toBeTruthy()
51+
rerender(<RolePill role={'member' as any} />)
52+
expect(document.querySelector('.role-pill')!.className.trim()).toBe('role-pill')
53+
})
54+
})
55+
56+
describe('ScopePill', () => {
57+
it('renders write, agent, and read variants', () => {
58+
const { rerender } = render(<ScopePill scope="write" />)
59+
expect(screen.getByText(/clickable/)).toBeTruthy()
60+
rerender(<ScopePill scope="agent" />)
61+
expect(screen.getByText(/agent surface/)).toBeTruthy()
62+
rerender(<ScopePill scope="read" />)
63+
expect(screen.getByText(/mirror/)).toBeTruthy()
64+
})
65+
})
66+
67+
describe('Sparkline + Skeleton', () => {
68+
it('renders a polyline for the given points', () => {
69+
render(<Sparkline points={[1, 5, 3, 8]} />)
70+
const poly = document.querySelector('svg.sparkline polyline')
71+
expect(poly).toBeTruthy()
72+
expect(poly!.getAttribute('points')!.split(' ').length).toBe(4)
73+
})
74+
it('renders a single-point sparkline without dividing by zero', () => {
75+
render(<Sparkline points={[4]} />)
76+
expect(document.querySelector('svg.sparkline polyline')).toBeTruthy()
77+
})
78+
it('renders a skeleton with default and custom sizes', () => {
79+
const { rerender } = render(<Skeleton />)
80+
expect(document.querySelector('span.skel')).toBeTruthy()
81+
rerender(<Skeleton width={50} height={8} />)
82+
expect(document.querySelector('span.skel')).toBeTruthy()
83+
})
84+
})
85+
86+
describe('PromptCard copy', () => {
87+
it('copies the fallback prompt when no promptText is given', async () => {
88+
render(<PromptCard title="Make a DB" prompt="provision pg" method="POST" endpoint="/db/new" hint="hint" />)
89+
await userEvent.click(screen.getByTestId('copy-prompt'))
90+
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('/db/new'))
91+
await waitFor(() => expect(screen.getByTestId('copy-prompt').textContent).toContain('copied'))
92+
})
93+
94+
it('copies explicit promptText and the curl command', async () => {
95+
render(<PromptCard title="Get" prompt="x" promptText="custom prompt" method="GET" endpoint="/healthz" danger />)
96+
await userEvent.click(screen.getByTestId('copy-prompt'))
97+
expect(writeText).toHaveBeenLastCalledWith('custom prompt')
98+
await userEvent.click(screen.getByTestId('copy-curl'))
99+
expect(writeText).toHaveBeenLastCalledWith(expect.stringContaining('curl -X GET'))
100+
await waitFor(() => expect(screen.getByTestId('copy-curl').textContent).toContain('copied'))
101+
})
102+
103+
it('warns and does not flash when copy fails', async () => {
104+
writeText.mockRejectedValue(new Error('denied'))
105+
// Force the execCommand fallback to also fail.
106+
;(document as any).execCommand = vi.fn(() => false)
107+
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {})
108+
render(<PromptCard title="t" prompt="p" method="DELETE" endpoint="/x" />)
109+
await userEvent.click(screen.getByTestId('copy-curl'))
110+
await waitFor(() => expect(warn).toHaveBeenCalled())
111+
expect(screen.getByTestId('copy-curl').textContent).toContain('copy curl')
112+
warn.mockRestore()
113+
})
114+
})

0 commit comments

Comments
 (0)