website
git clone https://git.pyrossh.dev/website
木 Personal website of pyrossh. Built with astrojs, shiki, vite.
tests/pages.test.js
| 09c6a56 | 1 | import { after, before, describe, test } from "node:test"; |
| 09c6a56 | 2 | import assert from "node:assert/strict"; |
| 09c6a56 | 3 | import path from "node:path"; |
| 09c6a56 | 4 | import { fileURLToPath } from "node:url"; |
| 09c6a56 | 5 | import { chromium } from "playwright"; |
| 09c6a56 | 6 | import { startStaticServer } from "./helpers/static-server.js"; |
| 09c6a56 | 7 | |
| 09c6a56 | 8 | const __dirname = path.dirname(fileURLToPath(import.meta.url)); |
| 09c6a56 | 9 | const distDir = path.join(__dirname, "..", "dist"); |
| 09c6a56 | 10 | |
| 09c6a56 | 11 | let server; |
| 09c6a56 | 12 | let browser; |
| 09c6a56 | 13 | let page; |
| 09c6a56 | 14 | const consoleErrors = []; |
| 09c6a56 | 15 | |
| 09c6a56 | 16 | before(async () => { |
| 09c6a56 | 17 | server = await startStaticServer(distDir); |
| 09c6a56 | 18 | browser = await chromium.launch(); |
| 09c6a56 | 19 | page = await browser.newPage(); |
| 09c6a56 | 20 | page.on("console", (msg) => { |
| 09c6a56 | 21 | if (msg.type() === "error") consoleErrors.push(`[${page.url()}] ${msg.text()}`); |
| 09c6a56 | 22 | }); |
| 09c6a56 | 23 | page.on("pageerror", (err) => { |
| 09c6a56 | 24 | consoleErrors.push(`[${page.url()}] ${err.message}`); |
| 09c6a56 | 25 | }); |
| 09c6a56 | 26 | }); |
| 09c6a56 | 27 | |
| 09c6a56 | 28 | after(async () => { |
| 09c6a56 | 29 | await browser?.close(); |
| 09c6a56 | 30 | await server?.close(); |
| 09c6a56 | 31 | }); |
| 09c6a56 | 32 | |
| 09c6a56 | 33 | async function goto(urlPath) { |
| 09c6a56 | 34 | const response = await page.goto(`${server.url}${urlPath}`, { waitUntil: "domcontentloaded" }); |
| 09c6a56 | 35 | return response; |
| 09c6a56 | 36 | } |
| 09c6a56 | 37 | |
| 09c6a56 | 38 | describe("static pages", () => { |
| 09c6a56 | 39 | test("home page renders", async () => { |
| 09c6a56 | 40 | const response = await goto("/"); |
| 09c6a56 | 41 | assert.equal(response.status(), 200); |
| 09c6a56 | 42 | await assert.doesNotReject(page.locator("h1.title").waitFor()); |
| 09c6a56 | 43 | assert.match(await page.title(), /pyrossh/); |
| 09c6a56 | 44 | }); |
| 09c6a56 | 45 | |
| 09c6a56 | 46 | test("cv page renders", async () => { |
| 09c6a56 | 47 | const response = await goto("/cv/"); |
| 09c6a56 | 48 | assert.equal(response.status(), 200); |
| 09c6a56 | 49 | await assert.doesNotReject(page.locator("h1").waitFor()); |
| 09c6a56 | 50 | assert.equal(await page.locator("img[alt='Resume']").count(), 1); |
| 09c6a56 | 51 | }); |
| 09c6a56 | 52 | |
| 09c6a56 | 53 | test("posts index lists posts", async () => { |
| 09c6a56 | 54 | const response = await goto("/posts/"); |
| 09c6a56 | 55 | assert.equal(response.status(), 200); |
| 09c6a56 | 56 | const links = page.locator("main a[href^='/posts/']"); |
| 09c6a56 | 57 | assert.ok((await links.count()) > 0, "expected at least one post link"); |
| 09c6a56 | 58 | }); |
| 09c6a56 | 59 | |
| 09c6a56 | 60 | test("a post page renders", async () => { |
| 09c6a56 | 61 | const response = await goto("/posts/react-powertools-swr/"); |
| 09c6a56 | 62 | assert.equal(response.status(), 200); |
| 09c6a56 | 63 | assert.match(await page.title(), /React Powertools/); |
| 09c6a56 | 64 | }); |
| 09c6a56 | 65 | |
| 09c6a56 | 66 | test("only-bible-app pages render", async () => { |
| 09c6a56 | 67 | for (const url of ["/only-bible-app/", "/only-bible-app/privacy-policy/", "/only-bible-app/terms-and-conditions/"]) { |
| 09c6a56 | 68 | const response = await goto(url); |
| 09c6a56 | 69 | assert.equal(response.status(), 200, `expected 200 for ${url}`); |
| 09c6a56 | 70 | } |
| 09c6a56 | 71 | }); |
| 09c6a56 | 72 | |
| 09c6a56 | 73 | test("robots.txt and rss.xml are served", async () => { |
| 09c6a56 | 74 | const robots = await goto("/robots.txt"); |
| 09c6a56 | 75 | assert.equal(robots.status(), 200); |
| 09c6a56 | 76 | assert.match(await page.content(), /User-agent/); |
| 09c6a56 | 77 | |
| 09c6a56 | 78 | const rss = await goto("/rss.xml"); |
| 09c6a56 | 79 | assert.equal(rss.status(), 200); |
| 09c6a56 | 80 | }); |
| 09c6a56 | 81 | |
| 09c6a56 | 82 | test("unknown route returns the 404 page", async () => { |
| 09c6a56 | 83 | const response = await goto("/this-page-does-not-exist/"); |
| 09c6a56 | 84 | assert.equal(response.status(), 404); |
| 09c6a56 | 85 | assert.match(await page.locator("h1").textContent(), /404/); |
| 09c6a56 | 86 | }); |
| 09c6a56 | 87 | }); |
| 09c6a56 | 88 | |
| 09c6a56 | 89 | describe("repo pages", () => { |
| 09c6a56 | 90 | const repoId = "website"; |
| 09c6a56 | 91 | |
| 09c6a56 | 92 | test("repo readme page renders", async () => { |
| 09c6a56 | 93 | const response = await goto(`/repos/${repoId}/`); |
| 09c6a56 | 94 | assert.equal(response.status(), 200); |
| 09c6a56 | 95 | await assert.doesNotReject(page.locator(".repo .header h1").waitFor()); |
| 09c6a56 | 96 | assert.equal(await page.locator(".nav a.active-tab").textContent(), "Readme"); |
| 09c6a56 | 97 | }); |
| 09c6a56 | 98 | |
| 09c6a56 | 99 | test("repo commits page lists commits", async () => { |
| 09c6a56 | 100 | const response = await goto(`/repos/${repoId}/commits/`); |
| 09c6a56 | 101 | assert.equal(response.status(), 200); |
| 09c6a56 | 102 | const commitLinks = page.locator(`a[href^='/repos/${repoId}/commits/']`); |
| 09c6a56 | 103 | assert.ok((await commitLinks.count()) > 0, "expected at least one commit link"); |
| 09c6a56 | 104 | }); |
| 09c6a56 | 105 | |
| 09c6a56 | 106 | test("a single commit page renders", async () => { |
| 09c6a56 | 107 | await goto(`/repos/${repoId}/commits/`); |
| 09c6a56 | 108 | const firstCommitHref = await page.locator(`a[href^='/repos/${repoId}/commits/']`).first().getAttribute("href"); |
| 09c6a56 | 109 | const response = await goto(firstCommitHref); |
| 09c6a56 | 110 | assert.equal(response.status(), 200); |
| 09c6a56 | 111 | assert.equal(await page.locator(".nav a.active-tab").textContent(), "Commits"); |
| 09c6a56 | 112 | }); |
| 09c6a56 | 113 | |
| 09c6a56 | 114 | test("repo files index renders the tree and a default file", async () => { |
| 09c6a56 | 115 | const response = await goto(`/repos/${repoId}/files/`); |
| 09c6a56 | 116 | assert.equal(response.status(), 200); |
| 09c6a56 | 117 | assert.ok((await page.locator(".file-explorer-tree").count()) > 0); |
| 09c6a56 | 118 | }); |
| 09c6a56 | 119 | |
| 09c6a56 | 120 | test("a specific file, its history and blame views render", async () => { |
| 09c6a56 | 121 | const filePath = "eleventy.config.js"; |
| 09c6a56 | 122 | const fileRes = await goto(`/repos/${repoId}/files/${filePath}/`); |
| 09c6a56 | 123 | assert.equal(fileRes.status(), 200); |
| 09c6a56 | 124 | |
| 09c6a56 | 125 | const historyRes = await goto(`/repos/${repoId}/files/${filePath}/history/`); |
| 09c6a56 | 126 | assert.equal(historyRes.status(), 200); |
| 09c6a56 | 127 | |
| 09c6a56 | 128 | const blameRes = await goto(`/repos/${repoId}/files/${filePath}/blame/`); |
| 09c6a56 | 129 | assert.equal(blameRes.status(), 200); |
| 09c6a56 | 130 | }); |
| 09c6a56 | 131 | |
| 09c6a56 | 132 | test("repo nav tabs swap content via htmx without a full reload", async () => { |
| 09c6a56 | 133 | await goto(`/repos/${repoId}/`); |
| 09c6a56 | 134 | await page.click(".nav a[href='/repos/website/files/']"); |
| 09c6a56 | 135 | await page.waitForURL(`${server.url}/repos/${repoId}/files/`); |
| 09c6a56 | 136 | assert.equal(await page.locator(".nav a.active-tab").textContent(), "Code"); |
| 09c6a56 | 137 | }); |
| 09c6a56 | 138 | }); |
| 09c6a56 | 139 | |
| 09c6a56 | 140 | test("no unexpected console errors were logged across visited pages", () => { |
| 09c6a56 | 141 | // The deliberate 404 test above triggers a browser-level "failed to load |
| 09c6a56 | 142 | // resource" console entry for its own navigation request — that's the |
| 09c6a56 | 143 | // behavior under test there, not a bug, so it's excluded here. |
| 09c6a56 | 144 | const unexpected = consoleErrors.filter((msg) => !msg.includes("this-page-does-not-exist")); |
| 09c6a56 | 145 | assert.deepEqual(unexpected, []); |
| 09c6a56 | 146 | }); |