Skip to content

Commit 21734b5

Browse files
committed
test(e2e): add e2e
1 parent 300df51 commit 21734b5

1 file changed

Lines changed: 319 additions & 0 deletions

File tree

tests/e2e/e2e.test.ts

Lines changed: 319 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -782,6 +782,102 @@ test("e2e: framework adapters mount, update, unmount and fallback in browser", a
782782
}
783783
});
784784

785+
test("e2e: renderPlanInBrowser covers auto-pin-latest and manifest-only modes", async (t) => {
786+
const e2eTempRoot = path.join(REPO_ROOT, ".tmp");
787+
await mkdir(e2eTempRoot, { recursive: true });
788+
const tempDir = await mkdtemp(
789+
path.join(e2eTempRoot, "renderify-e2e-autopin-manifest-modes-"),
790+
);
791+
const harnessSourcePath = path.join(tempDir, "autopin-manifest-harness.ts");
792+
const harnessBundlePath = path.join(tempDir, "autopin-manifest-harness.js");
793+
const port = await allocatePort();
794+
const baseUrl = `http://127.0.0.1:${port}`;
795+
796+
let browser: Browser | undefined;
797+
let closeHarnessServer: (() => Promise<void>) | undefined;
798+
799+
try {
800+
const runtimeIndexPath = path
801+
.join(REPO_ROOT, "packages", "runtime", "src", "index.ts")
802+
.replace(/\\/g, "/");
803+
const harnessSource = AUTOPIN_MANIFEST_HARNESS_SOURCE.replace(
804+
"__RUNTIME_INDEX_PATH__",
805+
runtimeIndexPath,
806+
);
807+
808+
await writeFile(harnessSourcePath, harnessSource, "utf8");
809+
810+
const esbuildResult = await runCommand("pnpm", [
811+
"exec",
812+
"esbuild",
813+
harnessSourcePath,
814+
"--bundle",
815+
"--format=esm",
816+
"--platform=browser",
817+
"--target=es2022",
818+
`--outfile=${harnessBundlePath}`,
819+
]);
820+
assert.equal(esbuildResult.code, 0, esbuildResult.stderr);
821+
822+
const fsPromises = await import("node:fs/promises");
823+
const harnessBundle = await fsPromises.readFile(harnessBundlePath, "utf8");
824+
825+
closeHarnessServer = await startAutoPinManifestHarnessServer({
826+
port,
827+
harnessBundle,
828+
});
829+
830+
try {
831+
browser = await chromium.launch({ headless: true });
832+
} catch (error) {
833+
t.skip(
834+
`playwright chromium is unavailable: ${error instanceof Error ? error.message : String(error)}`,
835+
);
836+
return;
837+
}
838+
839+
const page = await browser.newPage();
840+
await page.goto(baseUrl, {
841+
waitUntil: "networkidle",
842+
});
843+
844+
const report = (await page.evaluate(async () => {
845+
// @ts-expect-error runtime-served test harness module
846+
const harness = await import("/auto-pin-harness.js");
847+
return await harness.runAutoPinManifestE2E(window.location.origin);
848+
})) as {
849+
autoPinHtml: string;
850+
autoPinDiagnostics: string[];
851+
autoPinSecuritySafe: boolean;
852+
manifestOnlyHtml: string;
853+
manifestOnlyDiagnostics: string[];
854+
manifestOnlySecuritySafe: boolean;
855+
};
856+
857+
assert.match(report.autoPinHtml, /Today:\s*1970-01-01/);
858+
assert.doesNotMatch(report.autoPinHtml, /fallback root/);
859+
assert.equal(
860+
report.autoPinDiagnostics.includes("RUNTIME_MANIFEST_MISSING"),
861+
false,
862+
);
863+
assert.equal(report.autoPinSecuritySafe, true);
864+
865+
assert.match(report.manifestOnlyHtml, /Today:\s*1970-01-01/);
866+
assert.doesNotMatch(report.manifestOnlyHtml, /fallback root/);
867+
assert.equal(
868+
report.manifestOnlyDiagnostics.includes("RUNTIME_MANIFEST_MISSING"),
869+
false,
870+
);
871+
assert.equal(report.manifestOnlySecuritySafe, true);
872+
} finally {
873+
await browser?.close();
874+
if (closeHarnessServer) {
875+
await closeHarnessServer();
876+
}
877+
await rm(tempDir, { recursive: true, force: true });
878+
}
879+
});
880+
785881
function toBase64Url(value: string): string {
786882
return Buffer.from(value, "utf8").toString("base64url");
787883
}
@@ -1240,6 +1336,229 @@ async function startFrameworkAdapterHarnessServer(input: {
12401336
return () => closeServer(server);
12411337
}
12421338

1339+
async function startAutoPinManifestHarnessServer(input: {
1340+
port: number;
1341+
harnessBundle: string;
1342+
}): Promise<() => Promise<void>> {
1343+
const html = [
1344+
"<!doctype html>",
1345+
"<html>",
1346+
" <head>",
1347+
' <meta charset="utf-8" />',
1348+
" <title>Renderify AutoPin Manifest E2E</title>",
1349+
" </head>",
1350+
" <body>",
1351+
' <main id="app"></main>',
1352+
" </body>",
1353+
"</html>",
1354+
].join("\n");
1355+
1356+
const formatModuleSource = [
1357+
"export function format(input, pattern = 'yyyy-MM-dd') {",
1358+
" const value = input instanceof Date ? input : new Date(input);",
1359+
" if (pattern !== 'yyyy-MM-dd') {",
1360+
" throw new Error('unsupported format pattern: ' + pattern);",
1361+
" }",
1362+
" return value.toISOString().slice(0, 10);",
1363+
"}",
1364+
].join("\n");
1365+
1366+
const packageJsonSource = JSON.stringify({
1367+
name: "date-fns",
1368+
version: "4.1.0",
1369+
exports: {
1370+
".": {
1371+
import: {
1372+
default: "./index.js",
1373+
},
1374+
},
1375+
"./format": {
1376+
import: {
1377+
default: "./format.js",
1378+
},
1379+
},
1380+
},
1381+
});
1382+
1383+
const server = createServer((req, res) => {
1384+
const url = new URL(req.url ?? "/", "http://127.0.0.1");
1385+
1386+
if (req.method === "GET" && url.pathname === "/") {
1387+
const body = Buffer.from(html, "utf8");
1388+
res.statusCode = 200;
1389+
res.setHeader("content-type", "text/html; charset=utf-8");
1390+
res.setHeader("content-length", body.length);
1391+
res.end(body);
1392+
return;
1393+
}
1394+
1395+
if (req.method === "GET" && url.pathname === "/auto-pin-harness.js") {
1396+
const body = Buffer.from(input.harnessBundle, "utf8");
1397+
res.statusCode = 200;
1398+
res.setHeader("content-type", "text/javascript; charset=utf-8");
1399+
res.setHeader("content-length", body.length);
1400+
res.end(body);
1401+
return;
1402+
}
1403+
1404+
if (req.method === "GET" && url.pathname === "/npm:date-fns") {
1405+
const body = Buffer.from("4.1.0", "utf8");
1406+
res.statusCode = 200;
1407+
res.setHeader("content-type", "text/plain; charset=utf-8");
1408+
res.setHeader("content-length", body.length);
1409+
res.end(body);
1410+
return;
1411+
}
1412+
1413+
if (
1414+
req.method === "GET" &&
1415+
url.pathname === "/npm:date-fns@4.1.0/package.json"
1416+
) {
1417+
const body = Buffer.from(packageJsonSource, "utf8");
1418+
res.statusCode = 200;
1419+
res.setHeader("content-type", "application/json; charset=utf-8");
1420+
res.setHeader("content-length", body.length);
1421+
res.end(body);
1422+
return;
1423+
}
1424+
1425+
if (
1426+
req.method === "GET" &&
1427+
url.pathname === "/npm:date-fns@4.1.0/format.js"
1428+
) {
1429+
const body = Buffer.from(formatModuleSource, "utf8");
1430+
res.statusCode = 200;
1431+
res.setHeader("content-type", "text/javascript; charset=utf-8");
1432+
res.setHeader("content-length", body.length);
1433+
res.end(body);
1434+
return;
1435+
}
1436+
1437+
const notFound = Buffer.from("not found", "utf8");
1438+
res.statusCode = 404;
1439+
res.setHeader("content-type", "text/plain; charset=utf-8");
1440+
res.setHeader("content-length", notFound.length);
1441+
res.end(notFound);
1442+
});
1443+
1444+
await new Promise<void>((resolve, reject) => {
1445+
server.once("error", reject);
1446+
server.listen(input.port, "127.0.0.1", () => {
1447+
server.off("error", reject);
1448+
resolve();
1449+
});
1450+
});
1451+
1452+
return () => closeServer(server);
1453+
}
1454+
1455+
const AUTOPIN_MANIFEST_HARNESS_SOURCE = `
1456+
import { JspmModuleLoader, renderPlanInBrowser } from "__RUNTIME_INDEX_PATH__";
1457+
1458+
const mountRoot = (id) => {
1459+
const existing = document.getElementById(id);
1460+
if (existing) {
1461+
existing.remove();
1462+
}
1463+
1464+
const container = document.createElement("section");
1465+
container.id = id;
1466+
document.body.appendChild(container);
1467+
return container;
1468+
};
1469+
1470+
const createPlan = (id, manifest) => ({
1471+
specVersion: "runtime-plan/v1",
1472+
id,
1473+
version: 1,
1474+
capabilities: {
1475+
domWrite: true,
1476+
maxExecutionMs: 8000,
1477+
},
1478+
root: {
1479+
type: "element",
1480+
tag: "section",
1481+
children: [{ type: "text", value: "fallback root" }],
1482+
},
1483+
...(manifest ? { moduleManifest: manifest } : {}),
1484+
source: {
1485+
language: "js",
1486+
runtime: "renderify",
1487+
code: [
1488+
'import { format } from "date-fns/format";',
1489+
"export default function App() {",
1490+
" return {",
1491+
" type: 'element',",
1492+
" tag: 'section',",
1493+
" children: [",
1494+
" { type: 'text', value: 'Today: ' + format(new Date(0), 'yyyy-MM-dd') },",
1495+
" ],",
1496+
" };",
1497+
"}",
1498+
].join("\\n"),
1499+
},
1500+
});
1501+
1502+
export async function runAutoPinManifestE2E(baseUrl) {
1503+
const autoPinMount = mountRoot("auto-pin-mode");
1504+
const autoPinLoader = new JspmModuleLoader({
1505+
cdnBaseUrl: baseUrl,
1506+
});
1507+
const autoPinResult = await renderPlanInBrowser(
1508+
createPlan("auto_pin_latest_default"),
1509+
{
1510+
target: autoPinMount,
1511+
autoPinModuleLoader: autoPinLoader,
1512+
autoPinFetchTimeoutMs: 4000,
1513+
securityInitialization: { profile: "relaxed" },
1514+
runtimeOptions: {
1515+
moduleLoader: autoPinLoader,
1516+
remoteFallbackCdnBases: [],
1517+
remoteFetchTimeoutMs: 4000,
1518+
remoteFetchRetries: 0,
1519+
remoteFetchBackoffMs: 10,
1520+
},
1521+
},
1522+
);
1523+
1524+
const manifestOnlyMount = mountRoot("manifest-only-mode");
1525+
const manifestOnlyLoader = new JspmModuleLoader({
1526+
cdnBaseUrl: baseUrl,
1527+
});
1528+
const manifestOnlyResult = await renderPlanInBrowser(
1529+
createPlan("manifest_only_mode", {
1530+
"date-fns/format": {
1531+
resolvedUrl: baseUrl + "/npm:date-fns@4.1.0/format.js",
1532+
version: "4.1.0",
1533+
},
1534+
}),
1535+
{
1536+
target: manifestOnlyMount,
1537+
autoPinLatestModuleManifest: false,
1538+
securityInitialization: { profile: "relaxed" },
1539+
runtimeOptions: {
1540+
moduleLoader: manifestOnlyLoader,
1541+
remoteFallbackCdnBases: [],
1542+
remoteFetchTimeoutMs: 4000,
1543+
remoteFetchRetries: 0,
1544+
remoteFetchBackoffMs: 10,
1545+
},
1546+
},
1547+
);
1548+
1549+
return {
1550+
autoPinHtml: autoPinMount.innerHTML,
1551+
autoPinDiagnostics: autoPinResult.execution.diagnostics.map((item) => item.code),
1552+
autoPinSecuritySafe: autoPinResult.security.safe,
1553+
manifestOnlyHtml: manifestOnlyMount.innerHTML,
1554+
manifestOnlyDiagnostics: manifestOnlyResult.execution.diagnostics.map(
1555+
(item) => item.code,
1556+
),
1557+
manifestOnlySecuritySafe: manifestOnlyResult.security.safe,
1558+
};
1559+
}
1560+
`;
1561+
12431562
const FRAMEWORK_ADAPTERS_HARNESS_SOURCE = `
12441563
import { h, render } from "preact";
12451564
import {

0 commit comments

Comments
 (0)