Files
my-protfolio/tests/runtime-server.test.mjs
Amirhossein Khalili f34bea05cd
Some checks failed
Test and deploy / Verify production build (push) Has been cancelled
Test and deploy / Deploy production (push) Has been cancelled
perf: optimize portfolio runtime delivery
Replace the blocking asset loader with progressive rendering, defer expensive media and Three.js work, and reduce hydration and animation costs. Move deployment from a static Nginx export to the standalone Next.js runtime with optimized dynamic images.
2026-09-09 12:43:37 +03:30

92 lines
3.5 KiB
JavaScript

import assert from 'node:assert/strict';
import { spawn } from 'node:child_process';
import { access, cp, readFile } from 'node:fs/promises';
import path from 'node:path';
import test from 'node:test';
const projectRoot = path.resolve('.');
const standaloneRoot = path.join(projectRoot, '.next', 'standalone');
const port = 43217;
const origin = `http://127.0.0.1:${port}`;
async function waitForServer(process, getLogs) {
const deadline = Date.now() + 60_000;
while (Date.now() < deadline) {
if (process.exitCode !== null) throw new Error('standalone server exited early');
try {
const response = await fetch(origin, { signal: AbortSignal.timeout(10_000) });
if (response.ok) return;
} catch {}
await new Promise((resolve) => setTimeout(resolve, 150));
}
throw new Error(`standalone server did not become ready\n${getLogs()}`);
}
test('the standalone server renders both locales and optimizes local images', async (t) => {
await access(path.join(standaloneRoot, 'server.js'));
await cp(path.join(projectRoot, 'public'), path.join(standaloneRoot, 'public'), { recursive: true });
await cp(
path.join(projectRoot, '.next', 'static'),
path.join(standaloneRoot, '.next', 'static'),
{ recursive: true },
);
const server = spawn(process.execPath, ['server.js'], {
cwd: standaloneRoot,
env: { ...process.env, HOSTNAME: '127.0.0.1', PORT: String(port) },
stdio: ['ignore', 'pipe', 'pipe'],
});
let serverLogs = '';
server.stdout.on('data', (chunk) => { serverLogs += chunk; });
server.stderr.on('data', (chunk) => { serverLogs += chunk; });
t.after(() => server.kill());
await waitForServer(server, () => serverLogs);
const [englishResponse, persianResponse] = await Promise.all([
fetch(`${origin}/`),
fetch(`${origin}/fa`),
]);
assert.equal(englishResponse.status, 200);
assert.equal(persianResponse.status, 200);
const [englishHtml, persianHtml] = await Promise.all([
englishResponse.text(),
persianResponse.text(),
]);
assert.match(englishHtml, /<html[^>]*lang="en"[^>]*dir="ltr"/i);
assert.match(englishHtml, /href="\/fa"/i);
assert.match(englishHtml, /hreflang="fa"/i);
assert.match(englishHtml, />Software Engineer</);
assert.doesNotMatch(englishHtml, /id="site-loader"/);
assert.match(persianHtml, /<html[^>]*lang="fa"[^>]*dir="rtl"/i);
assert.match(persianHtml, /سلام، من امیرحسین خلیلی هستم/);
const staticAsset = englishHtml.match(/(?:src|href)="(\/_next\/static\/[^"?#]+)/)?.[1];
assert.ok(staticAsset, 'expected a built static asset');
assert.equal((await fetch(`${origin}${staticAsset}`)).status, 200);
const optimized = await fetch(
`${origin}/_next/image?url=%2Fimages%2Favatar.png&w=256&q=75`,
{ headers: { Accept: 'image/webp' } },
);
assert.equal(optimized.status, 200);
assert.equal(optimized.headers.get('content-type'), 'image/webp');
assert.match(optimized.headers.get('cache-control') ?? '', /max-age=/);
});
test('Persian source copy uses standard characters and punctuation', async () => {
const localizedFiles = [
'src/i18n/content.ts',
'src/data/projects.ts',
'src/data/experience.ts',
'src/data/education.ts',
'src/data/milestones.ts',
];
const source = (
await Promise.all(localizedFiles.map((file) => readFile(file, 'utf8')))
).join('\n');
assert.doesNotMatch(source, /[يك]/, 'use Persian ی and ک instead of Arabic forms');
assert.doesNotMatch(source, /[—–]/, 'use Persian punctuation instead of em/en dashes');
});