Skip to content

Commit 6bc34e5

Browse files
committed
tests: add frontend tests
1 parent c9dd5db commit 6bc34e5

File tree

12 files changed

+591
-35
lines changed

12 files changed

+591
-35
lines changed

.prettierrc

Lines changed: 0 additions & 7 deletions
This file was deleted.

frontend/pb.tsx

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,8 @@ export function PasteBin() {
7171

7272
const [darkModeSelect, setDarkModeSelect] = useState<DarkMode>(defaultDarkMode())
7373

74-
const systemDark = window.matchMedia("(prefers-color-scheme: dark)").matches
74+
// when matchMedia not available (e.g. in tests), set to light mode
75+
const systemDark = window.matchMedia ? window.matchMedia("(prefers-color-scheme: dark)").matches : false
7576
const isDark = darkModeSelect === "system" ? systemDark : darkModeSelect === "dark"
7677

7778
function showErrorMsg(err: string) {
@@ -137,7 +138,8 @@ export function PasteBin() {
137138

138139
if (contentType && contentType.startsWith("text/")) {
139140
setEditKind("edit")
140-
setPasteEdit(await resp.text())
141+
const t = await resp.text()
142+
setPasteEdit(t)
141143
} else {
142144
setEditKind("file")
143145
let pasteFilename = filename
@@ -241,6 +243,7 @@ export function PasteBin() {
241243
<Tooltip content="Toggle Dark Mode">
242244
<span
243245
className="absolute right-0"
246+
data-testid="pastebin-darkmode-toggle"
244247
onClick={() => {
245248
if (darkModeSelect === "system") {
246249
setDarkModeSelect("dark")
@@ -289,6 +292,7 @@ export function PasteBin() {
289292
<Tab key={"edit"} title="Edit">
290293
<Textarea
291294
isClearable
295+
data-testid="pastebin-edit"
292296
placeholder={isPasteLoading ? "Loading..." : "Edit your paste here"}
293297
isDisabled={isPasteLoading}
294298
className="px-0 py-0"
@@ -520,7 +524,8 @@ export function PasteBin() {
520524
)
521525

522526
return (
523-
<div
527+
<main
528+
data-testid="pastebin-main"
524529
className={
525530
"flex flex-col items-center min-h-screen font-sans bg-background text-foreground" +
526531
(isDark ? " dark" : " light")
@@ -537,6 +542,6 @@ export function PasteBin() {
537542
</div>
538543
{footer}
539544
{errorModal}
540-
</div>
545+
</main>
541546
)
542547
}

frontend/test/index.spec.tsx

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import { describe, it, expect, beforeAll, afterEach, afterAll } from "vitest"
2+
import { cleanup, render, screen } from "@testing-library/react"
3+
import { PasteBin } from "../pb.js"
4+
import { mockedPasteContent, mockedPasteUpload, server } from "./mock.js"
5+
6+
beforeAll(() => {
7+
server.listen()
8+
})
9+
10+
afterEach(() => {
11+
server.resetHandlers()
12+
cleanup()
13+
})
14+
15+
afterAll(() => {
16+
server.close()
17+
})
18+
19+
import "@testing-library/jest-dom/vitest"
20+
import { userEvent } from "@testing-library/user-event"
21+
22+
describe("Pastebin", () => {
23+
it("can upload", async () => {
24+
render(<PasteBin />)
25+
26+
const title = screen.getByText("Pastebin Worker")
27+
expect(title).toBeInTheDocument()
28+
29+
const editor = screen.getByPlaceholderText("Edit your paste here")
30+
expect(editor).toBeInTheDocument()
31+
await userEvent.type(editor, "something")
32+
33+
const submitter = screen.getByText("Upload")
34+
expect(submitter).toBeInTheDocument()
35+
expect(submitter).toBeEnabled()
36+
await userEvent.click(submitter)
37+
38+
expect(screen.getByText(mockedPasteUpload.url)).toBeInTheDocument()
39+
expect(screen.getByText(mockedPasteUpload.manageUrl)).toBeInTheDocument()
40+
})
41+
})
42+
43+
describe("Pastebin admin page", () => {
44+
it("renders admin page", async () => {
45+
Object.defineProperty(window, "location", {
46+
configurable: true,
47+
enumerable: true,
48+
value: new URL("https://example.com/abcd:xxxxxxxxx"),
49+
})
50+
render(<PasteBin />)
51+
52+
const edit = screen.getByTestId("pastebin-edit")
53+
await userEvent.click(edit) // meaningless click, just ensure useEffect is done
54+
expect(edit).toBeInTheDocument()
55+
expect((edit as HTMLTextAreaElement).value).toStrictEqual(mockedPasteContent)
56+
})
57+
})
58+
59+
describe("Pastebin dark mode", () => {
60+
it("renders light mode", async () => {
61+
render(<PasteBin />)
62+
63+
const main = screen.getByTestId("pastebin-main")
64+
const toggler = screen.getByTestId("pastebin-darkmode-toggle")
65+
expect(main).toHaveClass("light")
66+
await userEvent.click(toggler)
67+
expect(main).toHaveClass("dark")
68+
await userEvent.click(toggler)
69+
expect(main).toHaveClass("light")
70+
})
71+
})

frontend/test/mock.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { http, HttpResponse } from "msw"
2+
import { setupServer } from "msw/node"
3+
import { PasteResponse } from "../../src/shared.js"
4+
5+
export const mockedPasteUpload: PasteResponse = {
6+
url: "https://example.com/abcd",
7+
manageUrl: "https://example.com/abcd:aaaaaaaaaaaaaaaaaa",
8+
expireAt: "2025-05-01T00:00:00.000Z",
9+
expirationSeconds: 300,
10+
}
11+
12+
export const mockedPasteContent = "something"
13+
14+
export const server = setupServer(
15+
http.post("/", () => {
16+
return HttpResponse.json(mockedPasteUpload)
17+
}),
18+
http.get("/abcd", () => {
19+
return HttpResponse.text(mockedPasteContent)
20+
}),
21+
)

frontend/test/tsconfig.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"extends": "../tsconfig.json",
3+
"compilerOptions": {
4+
"types": ["vite/client", "../vite-env.d.ts", "@testing-library/jest-dom"]
5+
},
6+
"include": ["**/*.tsx", "**/*.ts"]
7+
}

frontend/utils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { NAME_REGEX, parseExpiration, parseExpirationReadable, PASSWD_SEP } from "../src/shared.js"
22

33
export const BaseUrl = DEPLOY_URL
4-
export const APIUrl = DEPLOY_URL || ""
4+
export const APIUrl = API_URL
55

66
export const maxExpirationSeconds = parseExpiration(MAX_EXPIRATION)!
77
export const maxExpirationReadable = parseExpirationReadable(MAX_EXPIRATION)!

frontend/vite-env.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
declare const DEPLOY_URL: string
2+
declare const API_URL: string
23
declare const REPO: string
34
declare const MAX_EXPIRATION: string
45
declare const DEFAULT_EXPIRATION: string

frontend/vite.config.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import * as toml from "toml"
66

77
export default defineConfig(({ mode }) => {
88
const wranglerConfigPath = "wrangler.toml"
9+
const devAPIUrl = "http://localhost:8787"
910
const wranglerConfigText = readFileSync(wranglerConfigPath, "utf8")
1011
const wranglerConfigParsed = toml.parse(wranglerConfigText)
1112

@@ -21,7 +22,8 @@ export default defineConfig(({ mode }) => {
2122
return {
2223
plugins: [react(), tailwindcss()],
2324
define: {
24-
DEPLOY_URL: mode === "development" ? JSON.stringify("http://localhost:8787") : JSON.stringify(deployUrl),
25+
DEPLOY_URL: mode === "development" ? JSON.stringify(devAPIUrl) : JSON.stringify(deployUrl),
26+
API_URL: mode === "development" ? JSON.stringify(devAPIUrl) : JSON.stringify(""),
2527
REPO: JSON.stringify(getVar("REPO")),
2628
MAX_EXPIRATION: JSON.stringify(getVar("MAX_EXPIRATION")),
2729
DEFAULT_EXPIRATION: JSON.stringify(getVar("DEFAULT_EXPIRATION")),

package.json

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,18 +24,31 @@
2424
"devDependencies": {
2525
"@cloudflare/vitest-pool-workers": "^0.8.22",
2626
"@craftamap/esbuild-plugin-html": "^0.9.0",
27+
"@testing-library/dom": "^10.4.0",
28+
"@testing-library/jest-dom": "^6.6.3",
29+
"@testing-library/react": "^16.3.0",
30+
"@testing-library/user-event": "^14.6.1",
2731
"@types/jquery": "^3.5.32",
2832
"@types/node": "^22.14.1",
2933
"@vitejs/plugin-react": "^4.4.1",
3034
"@vitest/coverage-istanbul": "3.1.1",
3135
"eslint": "^9.25.0",
36+
"jsdom": "^26.1.0",
37+
"msw": "^2.7.5",
3238
"prettier": "^3.5.3",
3339
"typescript": "^5.8.3",
3440
"typescript-eslint": "^8.30.1",
3541
"vite": "^6.3.3",
3642
"vitest": "3.1.1",
3743
"wrangler": "^4.13.2"
3844
},
45+
"prettier": {
46+
"singleQuote": false,
47+
"semi": false,
48+
"trailingComma": "all",
49+
"tabWidth": 2,
50+
"printWidth": 120
51+
},
3952
"dependencies": {
4053
"@heroui/react": "2.8.0-beta.2",
4154
"@tailwindcss/vite": "^4.1.4",

vitest.config.js

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,10 @@
1-
import { defineWorkersConfig } from "@cloudflare/vitest-pool-workers/config"
1+
import { defineConfig } from "vitest/config"
22

3-
const cfg = defineWorkersConfig({
3+
export default defineConfig({
44
test: {
55
coverage: {
66
provider: "istanbul", // v8 is not supported due for cf workers
77
reporter: ["text", "json-summary", "html", "json"],
88
},
9-
poolOptions: {
10-
workers: {
11-
wrangler: {
12-
configPath: "./wrangler.toml",
13-
},
14-
},
15-
},
169
},
1710
})
18-
19-
export default cfg

0 commit comments

Comments
 (0)