diff --git a/apps/web/app/freelancers/[username]/page.tsx b/apps/web/app/freelancers/[username]/page.tsx index 24c780368e..76a6e0a5b3 100644 --- a/apps/web/app/freelancers/[username]/page.tsx +++ b/apps/web/app/freelancers/[username]/page.tsx @@ -1,9 +1,24 @@ +import { freelancers } from "../../../lib/mock"; + export default function FreelancerProfilePage({ params }: { params: { username: string } }) { + const freelancer = freelancers.find((profile) => profile.username === params.username); + + if (!freelancer) { + return ( +
+

Freelancer not found

+

No mock freelancer profile exists for {params.username}.

+

Return to freelancer search to open an available profile.

+
+ ); + } + return (
-

Freelancer Profile

-

Profile: {params.username}

-

Portfolio, reviews, and active proposals appear here.

+

{freelancer.username}

+

{freelancer.rate}

+

{freelancer.skills.join(" ยท ")}

+

Portfolio, reviews, and active proposals for {freelancer.username} appear here.

); } diff --git a/apps/web/package.json b/apps/web/package.json index 5782b10176..2a34cf8d5b 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -4,7 +4,8 @@ "scripts": { "dev": "next dev -p 3000", "build": "next build", - "start": "next start -p 3000" + "start": "next start -p 3000", + "test": "node --test test/*.test.js" }, "devDependencies": { "@types/node": "22.15.19", diff --git a/apps/web/test/freelancer-profile-route.test.js b/apps/web/test/freelancer-profile-route.test.js new file mode 100644 index 0000000000..2ccc022a99 --- /dev/null +++ b/apps/web/test/freelancer-profile-route.test.js @@ -0,0 +1,70 @@ +const assert = require("node:assert/strict"); +const fs = require("node:fs"); +const path = require("node:path"); +const test = require("node:test"); +const ts = require("typescript"); + +const WEB_ROOT = path.resolve(__dirname, ".."); + +function transpileToTemp(sourcePath, tempRoot) { + const relativePath = path.relative(WEB_ROOT, sourcePath); + const outputPath = path.join(tempRoot, relativePath).replace(/\.(tsx|ts)$/, ".js"); + const source = fs.readFileSync(sourcePath, "utf8"); + const compiled = ts.transpileModule(source, { + compilerOptions: { + esModuleInterop: true, + jsx: ts.JsxEmit.ReactJSX, + module: ts.ModuleKind.CommonJS, + target: ts.ScriptTarget.ES2020 + }, + fileName: sourcePath + }); + + fs.mkdirSync(path.dirname(outputPath), { recursive: true }); + fs.writeFileSync(outputPath, compiled.outputText); + return outputPath; +} + +function loadProfilePage() { + const tempRoot = fs.mkdtempSync(path.join(WEB_ROOT, ".tmp-freelancer-profile-route-")); + transpileToTemp(path.join(WEB_ROOT, "lib", "mock.ts"), tempRoot); + const pagePath = transpileToTemp(path.join(WEB_ROOT, "app", "freelancers", "[username]", "page.tsx"), tempRoot); + const Page = require(pagePath).default; + fs.rmSync(tempRoot, { recursive: true, force: true }); + return Page; +} + +function textFromElement(node) { + if (node === null || node === undefined || typeof node === "boolean") { + return ""; + } + if (typeof node === "string" || typeof node === "number") { + return String(node); + } + if (Array.isArray(node)) { + return node.map(textFromElement).join(" "); + } + if (node.props) { + return textFromElement(node.props.children); + } + return ""; +} + +test("known freelancer usernames render matching mock profile details", () => { + const Page = loadProfilePage(); + const renderedText = textFromElement(Page({ params: { username: "maya-dev" } })); + + assert.match(renderedText, /maya-dev/); + assert.match(renderedText, /Next\.js/); + assert.match(renderedText, /TypeScript/); + assert.match(renderedText, /\$65\/hr/); +}); + +test("unknown freelancer usernames render a clear not-found fallback", () => { + const Page = loadProfilePage(); + const renderedText = textFromElement(Page({ params: { username: "missing-user" } })); + + assert.match(renderedText, /Freelancer not found/i); + assert.match(renderedText, /missing-user/); + assert.doesNotMatch(renderedText, /Portfolio, reviews, and active proposals appear here/); +});