Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
9c450d3
test: add caniuse and w3c route tests
marcoscaceres Apr 25, 2026
14aa77f
feat(w3c): auto-refresh groups list every 24 hours
marcoscaceres Apr 26, 2026
256952a
fix(w3c): guard concurrent refresh and self-heal on corrupt JSON
marcoscaceres Apr 27, 2026
5f14fd3
Apply suggestions from code review
marcoscaceres Apr 27, 2026
c9c92b6
Apply suggestion from @Copilot
marcoscaceres Apr 27, 2026
e18c1fa
Apply suggestion from @Copilot
marcoscaceres Apr 27, 2026
edfd844
Apply suggestion from @Copilot
marcoscaceres Apr 27, 2026
4c64814
Apply suggestion from @Copilot
marcoscaceres Apr 27, 2026
8d47d75
Apply suggestion from @Copilot
marcoscaceres Apr 27, 2026
595b1b0
Apply suggestions from code review
marcoscaceres Apr 27, 2026
06a077c
fix(w3c): clean up group.ts - fix TDZ, duplicates, add retry/backoff
Copilot Apr 27, 2026
35dc30a
fix: resolve unaddressed review concerns in caniuse and w3c group tests
Copilot Apr 27, 2026
a85782b
Apply suggestion from @Copilot
marcoscaceres Apr 27, 2026
13f33f2
Apply suggestion from @Copilot
marcoscaceres Apr 27, 2026
153aa7e
Apply suggestion from @Copilot
marcoscaceres Apr 27, 2026
be80aca
Apply suggestion from @Copilot
marcoscaceres Apr 27, 2026
1314d8a
Merge branch 'main' into feat/auto-refresh-groups
marcoscaceres Apr 27, 2026
33c5e5f
fix(w3c): reloadGroups returns boolean, retry on any refresh failure
Copilot Apr 27, 2026
d3b6242
Merge branch 'main' into feat/auto-refresh-groups
marcoscaceres Apr 27, 2026
c840be6
Merge branch 'main' into feat/auto-refresh-groups
marcoscaceres Apr 28, 2026
9f88197
refactor(w3c): use BackgroundTaskQueue and webhook update model
marcoscaceres Apr 29, 2026
b2ecc13
fix(w3c): address Sid's review feedback on group update route
marcoscaceres May 3, 2026
4936436
fix(w3c): return 500 when groups reload fails after update
marcoscaceres May 3, 2026
8865a0b
Merge branch 'feat/auto-refresh-groups' into test/caniuse-w3c
marcoscaceres May 3, 2026
d0af01f
test(w3c): add reloadGroups() tests
marcoscaceres May 3, 2026
a1cb402
Merge remote-tracking branch 'origin/main' into test/caniuse-w3c
Copilot May 4, 2026
8e98064
Merge branch 'main' into test/caniuse-w3c
marcoscaceres May 4, 2026
30184b0
Merge remote-tracking branch 'origin/main' into test/caniuse-w3c
Copilot Jun 26, 2026
6630fce
test(w3c): mock api.w3.org in the group disambiguation test
marcoscaceres Jun 30, 2026
d7229ab
test: trim verbose caniuse/w3c/misc tests
marcoscaceres Jul 2, 2026
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
304 changes: 282 additions & 22 deletions tests/routes/caniuse/lib/index.test.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
import os from "node:os";
import path from "node:path";
import { promises as fs } from "node:fs";

import {
getData,
cache,
createResponseBody,
} from "../../../../build/routes/caniuse/lib/index.js";

const CANIUSE_DIR = path.join(os.tmpdir(), "caniuse");
import {
BROWSERS,
DEFAULT_BROWSERS,
SUPPORT_TITLES,
} from "../../../../build/routes/caniuse/lib/constants.js";

const CANIUSE_DIR = path.join(process.env.DATA_DIR, "caniuse");

/** Minimal valid ScraperOutput fixture */
const FIXTURE = {
Expand Down Expand Up @@ -35,18 +41,22 @@ async function removeFixture(name) {
describe("caniuse - getData", () => {
beforeEach(() => cache.clear());

it("returns null for empty feature string", async () => {
expect(await getData("")).toBeNull();
});

it("returns null for invalid characters (path traversal attempt)", async () => {
expect(await getData("../etc/passwd")).toBeNull();
expect(await getData("foo/bar")).toBeNull();
expect(await getData("feature name")).toBeNull();
});

it("returns null for non-existent feature", async () => {
expect(await getData("nonexistent-feature-xyz")).toBeNull();
it("returns null for empty, malformed, non-existent, and bare wf- features", async () => {
// Empty, path-traversal/invalid chars, unknown names, and the exact "wf-"
// and unresolvable "wf-" cases all resolve to no data.
for (const feature of [
"",
"../etc/passwd",
"foo/bar",
"feature name",
"nonexistent-feature-xyz",
"wf-",
"wf-no-such-feature-xyz",
]) {
expect(await getData(feature))
.withContext(`feature: "${feature}"`)
.toBeNull();
}
});

it("returns data for a known feature", async () => {
Expand All @@ -59,14 +69,6 @@ describe("caniuse - getData", () => {
}
});

it("returns null for wf- edge case (exactly 'wf-')", async () => {
expect(await getData("wf-")).toBeNull();
});

it("returns null for wf- feature where stripped name also has no data", async () => {
expect(await getData("wf-no-such-feature-xyz")).toBeNull();
});

it("falls back from wf- prefixed key to the stripped feature name", async () => {
await writeFixture("css-grid");
try {
Expand Down Expand Up @@ -100,3 +102,261 @@ describe("caniuse - getData", () => {
}
});
});

describe("caniuse - constants", () => {
describe("BROWSERS", () => {
it("is a Map", () => {
expect(BROWSERS).toBeInstanceOf(Map);
});

it("has expected browser names", () => {
const expected = [
"chrome",
"firefox",
"safari",
"edge",
"opera",
"ios_saf",
"samsung",
"and_chr",
"and_ff",
"android",
];
for (const name of expected) {
expect(BROWSERS.has(name))
.withContext(`missing browser: ${name}`)
.toBeTrue();
}
});

it("maps browser IDs to human-readable names", () => {
expect(BROWSERS.get("chrome")).toBe("Chrome");
expect(BROWSERS.get("firefox")).toBe("Firefox");
expect(BROWSERS.get("safari")).toBe("Safari");
expect(BROWSERS.get("edge")).toBe("Edge");
expect(BROWSERS.get("ios_saf")).toBe("Safari (iOS)");
expect(BROWSERS.get("and_chr")).toBe("Chrome (Android)");
});

it("does not contain empty values", () => {
for (const [key, value] of BROWSERS) {
expect(key).withContext("key is non-empty").toBeTruthy();
expect(value).withContext(`value for "${key}" is non-empty`).toBeTruthy();
}
});
});

describe("DEFAULT_BROWSERS", () => {
it("is an array", () => {
expect(Array.isArray(DEFAULT_BROWSERS)).toBeTrue();
});

it("is a valid subset of BROWSERS", () => {
for (const browser of DEFAULT_BROWSERS) {
expect(BROWSERS.has(browser))
.withContext(`"${browser}" should be in BROWSERS`)
.toBeTrue();
}
});

it("includes major browsers", () => {
expect(DEFAULT_BROWSERS).toContain("chrome");
expect(DEFAULT_BROWSERS).toContain("firefox");
expect(DEFAULT_BROWSERS).toContain("safari");
expect(DEFAULT_BROWSERS).toContain("edge");
});

it("does not include all browsers", () => {
expect(DEFAULT_BROWSERS.length).toBeLessThan(BROWSERS.size);
});
});

describe("SUPPORT_TITLES", () => {
it("is a Map", () => {
expect(SUPPORT_TITLES).toBeInstanceOf(Map);
});

it("has all expected support keys", () => {
const expectedKeys = ["y", "a", "n", "p", "u", "x", "d"];
for (const key of expectedKeys) {
expect(SUPPORT_TITLES.has(key))
.withContext(`missing support key: "${key}"`)
.toBeTrue();
}
});

it("maps single keys to descriptive titles", () => {
expect(SUPPORT_TITLES.get("y")).toBe("Supported.");
expect(SUPPORT_TITLES.get("n")).toBe("No support, or disabled by default.");
expect(SUPPORT_TITLES.get("a")).toBe(
"Almost supported (aka Partial support).",
);
expect(SUPPORT_TITLES.get("u")).toBe("Support unknown.");
expect(SUPPORT_TITLES.get("x")).toBe("Requires prefix to work.");
expect(SUPPORT_TITLES.get("p")).toBe("No support, but has Polyfill.");
expect(SUPPORT_TITLES.get("d")).toBe(
"Disabled by default (needs to be enabled).",
);
Comment thread
marcoscaceres marked this conversation as resolved.
});
Comment thread
marcoscaceres marked this conversation as resolved.
Comment thread
marcoscaceres marked this conversation as resolved.

it("returns undefined for unknown keys", () => {
expect(SUPPORT_TITLES.get("z")).toBeUndefined();
expect(SUPPORT_TITLES.get("")).toBeUndefined();
});
});
});

describe("caniuse - sanitizeBrowsersList (via createResponseBody)", () => {
beforeAll(async () => {
// Write fixtures into the CANIUSE_DIR the module already resolved at load time.
await fs.mkdir(CANIUSE_DIR, { recursive: true });

// Write a minimal fixture for a feature called "test-feature"
const fixtureData = {
all: {
chrome: [
["120", ["y"]],
["119", ["y"]],
["118", ["y"]],
["117", ["a"]],
["116", ["n"]],
],
firefox: [
["121", ["y"]],
["120", ["y"]],
["119", ["a"]],
],
safari: [
["17", ["y"]],
["16", ["a"]],
],
edge: [
["120", ["y"]],
],
opera: [
["100", ["n"]],
],
},
summary: {
chrome: [["120", ["y"]]],
firefox: [["121", ["y"]]],
safari: [["17", ["y"]]],
edge: [["120", ["y"]]],
opera: [["100", ["n"]]],
},
};
await writeFixture("test-feature", fixtureData);

// Write a fixture with compound and unknown support keys for HTML tests
const compoundFixture = {
all: {
chrome: [["120", ["y", "x"]]],
firefox: [["121", ["z"]]],
},
summary: {
chrome: [["120", ["y", "x"]]],
firefox: [["121", ["z"]]],
},
};
await writeFixture("compound-feature", compoundFixture);
});

afterAll(async () => {
await removeFixture("test-feature");
await removeFixture("compound-feature");
});

/** Build a json-format response body for the standard test-feature. */
function jsonBody(overrides = {}) {
return createResponseBody({
feature: "test-feature",
format: "json",
...overrides,
});
}

it("returns default browsers for undefined, non-array, and all-invalid input", async () => {
// undefined browsers, a non-array/non-"all" string, and an array whose
// entries are all invalid each fall back to DEFAULT_BROWSERS.
for (const browsers of [
undefined,
"invalid-string",
["not-a-browser", "also-invalid"],
]) {
const result = await jsonBody({ browsers });
expect(result).withContext(`browsers: ${JSON.stringify(browsers)}`).not.toBeNull();
const keys = Object.keys(result);
expect(keys).toContain("chrome");
expect(keys).toContain("firefox");
expect(keys).toContain("safari");
}
});

it("returns all browsers when 'all' is passed", async () => {
const result = await jsonBody({ browsers: "all" });
expect(result).not.toBeNull();
const keys = Object.keys(result);
expect(keys).toContain("chrome");
expect(keys).toContain("firefox");
expect(keys).toContain("safari");
expect(keys).toContain("edge");
expect(keys).toContain("opera");
});

it("filters invalid browser names from array input", async () => {
const result = await jsonBody({
browsers: ["chrome", "invalid-browser", "firefox"],
});
expect(result).not.toBeNull();
const keys = Object.keys(result);
expect(keys).toContain("chrome");
expect(keys).toContain("firefox");
expect(keys).not.toContain("invalid-browser");
});

it("returns null for non-existent feature", async () => {
const result = await createResponseBody({
feature: "nonexistent-feature",
format: "json",
});
expect(result).toBeNull();
});

it("defaults to 4 versions when none specified", async () => {
const result = await jsonBody({ browsers: ["chrome"] });
expect(result).not.toBeNull();
expect(result.chrome.length).toBe(4);
});

it("respects custom version count", async () => {
const result = await jsonBody({ browsers: ["chrome"], versions: 2 });
expect(result).not.toBeNull();
expect(result.chrome.length).toBe(2);
});

describe("HTML title attributes (getSupportTitle via formatAsHTML)", () => {
/** Build an html-format response body. */
function htmlBody(feature, browsers) {
return createResponseBody({ feature, browsers, format: "html" });
}

it("renders 'Supported.' title for a 'y' support key", async () => {
const html = await htmlBody("test-feature", ["chrome"]);
expect(html).not.toBeNull();
expect(html).toContain('title="Supported."');
});

it("renders compound title for ['y', 'x'] support keys", async () => {
const html = await htmlBody("compound-feature", ["chrome"]);
expect(html).not.toBeNull();
expect(html).toContain('title="Supported. Requires prefix to work."');
});

it("renders empty title for unknown support keys", async () => {
// firefox has ["z"] (unknown) in the compound fixture
const html = await htmlBody("compound-feature", ["firefox"]);
expect(html).not.toBeNull();
expect(html).toContain('title=""');
});
});
});
Loading