Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 5 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"lint:fix": "eslint . --fix",
"test": "npm run test:unit && npm run test:web",
"test:unit": "npm run lint && jest --coverage --runInBand",
"test:web": "npm run build:code && webpack && rm -rf dist",
"test:web": "npm run build:code && webpack && npx playwright install chromium && node scripts/test-web-browser.js && rm -rf dist",
"coverage:bump": "jest-it-up"
},
"author": "BelfordZ<belfordz66@gmail.com>",
Expand All @@ -31,21 +31,22 @@
"!build/**/*.test.*"
],
"devDependencies": {
"@json-schema-tools/meta-schema": "^1.7.5",
"@eslint/js": "^10.0.1",
"@json-schema-tools/meta-schema": "^1.7.5",
"@types/isomorphic-fetch": "^0.0.39",
"@types/jest": "^29.1.1",
"@types/node": "^22.5.0",
"@typescript-eslint/eslint-plugin": "^8.2.0",
"@typescript-eslint/parser": "^8.2.0",
"eslint": "^10.12.0",
"jest": "^29.1.2",
"jest-it-up": "^4.0.1",
"playwright": "^1.64.1",
"ts-jest": "^29.2.5",
"typedoc": "^0.26.6",
"typescript": "^5.5.4",
"webpack": "^5.94.0",
"webpack-cli": "^5.1.4",
"jest-it-up": "^4.0.1"
"webpack-cli": "^5.1.4"
},
"dependencies": {
"@json-schema-spec/json-pointer": "^0.1.2",
Expand Down
151 changes: 151 additions & 0 deletions scripts/test-web-browser.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
/**
* In-browser tests for the web bundle (issue #21).
*
* `npm run test:web` builds the web entry (src/index-web.ts) with webpack
* into dist/bundle.js, then this script serves that bundle plus a test page
* from a local HTTP server, loads the page in a real headless Chromium
* (via Playwright) and exercises the resolver there.
*
* This is intentionally separate from the jest unit suite, which runs in
* Node: the web entry stubs the `file` protocol handler and relies on the
* browser's fetch, so only a real browser verifies it. The page and the
* JSON fixtures are served same-origin so Chromium's Private Network
* Access rules don't interfere.
*
* Run via: npm run test:web
*/
const fs = require("fs");
const http = require("http");
const path = require("path");
const { chromium } = require("playwright");

const BUNDLE = path.join(__dirname, "..", "dist", "bundle.js");

const DOC = {
title: "browser-doc",
type: "object",
definitions: {
foo: { type: "string" },
},
};

const PAGE = `<!doctype html>
<html><head><meta charset="utf-8"><title>reference-resolver web test</title></head>
<body><script src="/bundle.js"></script></body></html>`;

async function main() {
if (!fs.existsSync(BUNDLE)) {
console.error(`missing ${BUNDLE} — run the webpack build first (npm run test:web does this)`);
process.exit(2);
}
const bundle = fs.readFileSync(BUNDLE, "utf8");

const server = http.createServer((req, res) => {
if (req.url === "/bundle.js") {
res.writeHead(200, { "Content-Type": "text/javascript" });
res.end(bundle);
return;
}
if (req.url === "/doc.json") {
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify(DOC));
return;
}
res.writeHead(200, { "Content-Type": "text/html" });
res.end(PAGE);
});
await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
const baseUrl = `http://127.0.0.1:${server.address().port}`;

// Bypass the ambient egress proxy: these tests only talk to the local
// server, and proxying localhost is flaky and unnecessary.
const browser = await chromium.launch({ args: ["--no-proxy-server"] });
const page = await browser.newPage();
await page.goto(`${baseUrl}/`, { waitUntil: "load" });

const hasGlobal = await page.evaluate(() => typeof window.ReferenceResolver !== "undefined");
if (!hasGlobal) {
console.error("window.ReferenceResolver was not set by the bundle — check webpack library output");
process.exit(2);
}

const results = await page.evaluate(async (url) => {
const R = window.ReferenceResolver;
const out = [];

function eq(actual, expected) {
if (JSON.stringify(actual) !== JSON.stringify(expected)) {
throw new Error(`expected ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`);
}
}

async function throwsName(fn, name) {
try {
await fn();
} catch (e) {
if (e && e.name === name) {
return;
}
throw new Error(`expected error ${name}, got ${e && e.name}: ${e && e.message}`);
}
throw new Error(`expected error ${name}, but nothing was thrown`);
}

async function t(name, fn) {
try {
await fn();
out.push({ name, ok: true });
} catch (e) {
out.push({ name, ok: false, error: String((e && e.message) || e) });
}
}

await t("resolves internal refs", async () => {
eq(await R.resolve("#/properties/foo", { properties: { foo: "boo" } }), "boo");
});

await t("resolves http urls", async () => {
eq((await R.resolve(`${url}/doc.json`)).title, "browser-doc");
});

await t("applies hash fragment to http urls", async () => {
eq(await R.resolve(`${url}/doc.json#/definitions/foo`), { type: "string" });
});

await t("rejects file refs in the browser", async () => {
await throwsName(() => R.resolve("./src/test-obj.json"), "InvalidFileSystemPathError");
});

await t("errors on bad json pointer", async () => {
await throwsName(() => R.resolve("#/nope", { foo: { bar: true } }), "InvalidJsonPointerRefError");
});

await t("supports custom protocol handlers", async () => {
R.protocolHandlerMap.ipfs = () => Promise.resolve({ title: "foo", type: "string" });
eq(await R.resolve("ipfs://80088008800880088008"), { title: "foo", type: "string" });
});

return out;
}, baseUrl);

await browser.close();
server.close();

let failed = 0;
for (const r of results) {
console.log(`${r.ok ? "✓" : "✗"} ${r.name}${r.ok ? "" : ` — ${r.error}`}`);
if (!r.ok) {
failed++;
}
}
if (failed > 0) {
console.error(`${failed} browser test(s) failed`);
process.exit(1);
}
console.log("all browser tests passed");
}

main().catch((e) => {
console.error(e);
process.exit(1);
});
7 changes: 6 additions & 1 deletion webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ module.exports = {
entry: './build/index-web.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js'
filename: 'bundle.js',
library: {
name: 'ReferenceResolver',
type: 'window',
export: 'default',
},
}
};
Loading