Skip to content
Open
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
12 changes: 9 additions & 3 deletions docs/development.md
Original file line number Diff line number Diff line change
Expand Up @@ -381,9 +381,15 @@ its published title once, as `source`, which the interview shows in small print
beside the scenario so the problem can be found again afterwards. The published
id is in one file, `web/problem-pages.json`, which the browser fetches only to
open a link that still carries a published id, to read history saved before
pages had names, or when the candidate asks the lobby to show the titles on the
cards. The title reaches the live interview page only: saved history, the
downloaded report and the replay keep the scenario's.
pages had names, when the candidate asks the lobby to show the titles on the
cards, or to show or export a completed report. Completed reports, including
those opened from Past attempts or a replay, and downloaded Markdown keep the
scenario title and add an optional Original problem link with the published
title and LeetCode URL. Collapsed attempts do not request the map for this
link; opening or exporting their report does. Successful lookups are cached
for the page's lifetime. Missing or malformed mappings leave the link out,
and a failed lookup or a wait longer than ten seconds does not block a
Markdown download: it proceeds without the link.

None of this is secrecy. The page map, each page's `source` and every judge
are served to anyone who asks, and a candidate with developer tools can map a
Expand Down
113 changes: 113 additions & 0 deletions tests/browser/download.test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,119 @@
import { test } from "node:test";
import assert from "node:assert/strict";
import { downloadMarkdown, reportFilename } from "../../web/download.js";
import { functionBody, read } from "./source.js";

for (const [path, name] of [
["web/app.js", "downloadSavedReport"],
["web/interview.js", "downloadReport"],
]) {
function harness() {
const lookups = [];
const downloads = [];
const scope = {
pendingReportDownloads: new Set(),
selectionKey: (attempt) => attempt.id || attempt,
loadPageMapForDownload: () =>
new Promise((resolve, reject) => {
lookups.push({ resolve, reject });
}),
reportMarkdown: () => "# Report",
buildMarkdown: () => "# Report",
reportFilename,
problem: { page: "two-sum" },
downloadMarkdown: (...args) => downloads.push(args),
};
const download = new Function(
"scope",
`with (scope) {
${functionBody(read(path), name)}
}
return ${name};
}`,
)(scope);
const button = { disabled: false };
const attempt = { report: {}, problemId: "two-sum", at: "2026-10-23" };
const run = () =>
name === "downloadSavedReport"
? download(attempt, button)
: download(button);
return { run, button, lookups, downloads, scope, download, attempt };
}

test(`${name} exports once while pending and permits a later download`, async () => {
const h = harness();
const first = h.run();
assert.equal(h.button.disabled, true);
await h.run();
await h.run();
assert.equal(h.lookups.length, 1);
assert.equal(h.downloads.length, 0);
h.lookups[0].resolve({});
await first;
assert.equal(h.downloads.length, 1);
assert.equal(h.button.disabled, false);
const next = h.run();
assert.equal(h.button.disabled, true);
h.lookups[1].resolve(null);
await next;
assert.equal(h.downloads.length, 2);
assert.equal(h.button.disabled, false);
});

for (const failure of ["lookup", "export"]) {
test(`${name} restores the button after ${failure} fails`, async () => {
const h = harness();
const error = new Error("download failed");
const originalDownload = h.scope.downloadMarkdown;
if (failure === "export")
h.scope.downloadMarkdown = () => {
throw error;
};
const pending = h.run();
const rejected = assert.rejects(pending, error);
if (failure === "lookup") h.lookups[0].reject(error);
else h.lookups[0].resolve(null);
await rejected;
assert.equal(h.button.disabled, false);
h.scope.downloadMarkdown = originalDownload;
const retry = h.run();
h.lookups[1].resolve(null);
await retry;
assert.equal(h.downloads.length, 1);
assert.equal(h.button.disabled, false);
});
}

if (name === "downloadSavedReport") {
test("batch and row downloads share the pending guard for one report", async () => {
const h = harness();
h.attempt.id = "saved-report";
const batch = h.download(h.attempt);
await h.run();
await h.download({ ...h.attempt });
assert.equal(h.lookups.length, 1);
h.lookups[0].resolve(null);
await batch;
const retry = h.run();
assert.equal(h.button.disabled, true);
h.lookups[1].resolve(null);
await retry;
assert.equal(h.downloads.length, 2);
assert.equal(h.button.disabled, false);
});

test("different saved reports can export while another lookup is pending", async () => {
const h = harness();
const first = h.download({ ...h.attempt, id: "first" });
const second = h.download({ ...h.attempt, id: "second" });
assert.equal(h.lookups.length, 2);
h.lookups[0].resolve(null);
h.lookups[1].resolve(null);
await Promise.all([first, second]);
assert.equal(h.downloads.length, 2);
});
}
}

test("report filenames use local timestamps and safe scenario names", () => {
assert.equal(
Expand Down
1 change: 1 addition & 0 deletions tests/browser/history.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,7 @@ test("the response window panel says what the number is worth, in words a test c
"",
"/lib.js",
"/render.js",
"/problem-data.js",
"/api/reports",
"button",
"div",
Expand Down
105 changes: 98 additions & 7 deletions tests/browser/lobby.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3791,6 +3791,36 @@ lobbyTest(
},
);

lobbyTest(
"opening a saved report fetches and shows its original problem",
async (page) => {
reports = [savedAttempt(EASY[0])];
const fetched = [];
page.on("request", (request) =>
fetched.push(new URL(request.url()).pathname),
);
await lobby(page);
assert.ok(!fetched.includes("/problem-pages.json"));

const history = page.locator("#attempt-history");
assert.equal(
await history.locator(".report-original-problem").textContent(),
"",
);
await history.getByRole("button", { name: "Open report" }).click();
const link = history.getByRole("link", { name: "Two Sum" });
await link.waitFor();
assert.equal(
await link.getAttribute("href"),
"https://leetcode.com/problems/two-sum/",
);
assert.equal(
fetched.filter((path) => path === "/problem-pages.json").length,
1,
);
},
);

lobbyTest(
"a problem already passed is not what gets recommended",
async (page) => {
Expand Down Expand Up @@ -4004,6 +4034,10 @@ for (const storage of ["account", "device"]) {
assert.ok(
markdown.startsWith(`# Interview Report - Saved report\n_${when}_`),
);
assert.match(
markdown,
/Original problem: \[Two Sum\]\(https:\/\/leetcode\.com\/problems\/two-sum\/\)/,
);
assert.ok(markdown.includes(`Stored summary ${index}`));
assert.ok(
markdown.includes(
Expand Down Expand Up @@ -4048,8 +4082,15 @@ const recordDownloadNames = (page) =>
return click.call(this);
};
});
const takeDownloadNames = (page) =>
page.evaluate(() => window.downloadNames.splice(0).sort());
const takeDownloadNames = async (page, count = 0) => {
// Report exports may still be waiting for the optional problem map.
if (count)
await page.waitForFunction(
(count) => window.downloadNames.length >= count,
count,
);
return page.evaluate(() => window.downloadNames.splice(0).sort());
};

for (const storage of ["account", "device"]) {
lobbyTest(
Expand Down Expand Up @@ -4129,7 +4170,7 @@ for (const storage of ["account", "device"]) {
page.on("download", onDownload);
try {
await downloadSelected.click();
assert.deepEqual(await takeDownloadNames(page), [
assert.deepEqual(await takeDownloadNames(page, 2), [
filename(0),
filename(2),
]);
Expand Down Expand Up @@ -4220,15 +4261,15 @@ lobbyTest(
assert.equal(await toggle.textContent(), "Select all");
assert.equal(await downloadSelected.textContent(), "Download selected (1)");
await downloadSelected.click();
assert.deepEqual(await takeDownloadNames(page), [filename(3)]);
assert.deepEqual(await takeDownloadNames(page, 1), [filename(3)]);
// One file is one download, which no browser asks about.
assert.equal(await note.textContent(), "");

await page.selectOption("#progress-language", "cpp");
assert.equal(await box("b").isChecked(), true);
assert.equal(await toggle.textContent(), "Deselect all");
await downloadSelected.click();
assert.deepEqual(await takeDownloadNames(page), [filename(2)]);
assert.deepEqual(await takeDownloadNames(page, 1), [filename(2)]);
await toggle.click();
assert.equal(await box("b").isChecked(), false);
assert.equal(await downloadSelected.isDisabled(), true);
Expand Down Expand Up @@ -4292,7 +4333,7 @@ lobbyTest(
await downloadSelected.click();
assert.deepEqual(asked, []);
assert.deepEqual(
await takeDownloadNames(page),
await takeDownloadNames(page, 5),
[2, 3, 4, 5, 6].map(filename),
);

Expand All @@ -4307,7 +4348,7 @@ lobbyTest(
await downloadSelected.click();
assert.equal(asked.length, 2);
assert.deepEqual(
await takeDownloadNames(page),
await takeDownloadNames(page, 6),
[1, 2, 3, 4, 5, 6].map(filename),
);
},
Expand Down Expand Up @@ -4478,6 +4519,56 @@ for (const storage of ["device", "account"]) {
);
}

lobbyTest(
"a stalled problem map allows only one pending saved report download",
async (page) => {
reports = [savedAttempt(EASY[0])];
await page.addInitScript(() => {
const fetch = window.fetch.bind(window);
window.mapRequested = false;
window.mapDeadline = null;
window.fetch = (path, options) => {
if (path !== "/problem-pages.json") return fetch(path, options);
window.mapRequested = true;
return new Promise(() => {});
};
const setTimeout = window.setTimeout.bind(window);
window.setTimeout = (callback, delay, ...args) => {
if (delay !== 10_000) return setTimeout(callback, delay, ...args);
window.mapDeadline = () => callback(...args);
return 0;
};
});
await lobby(page);
assert.equal(await page.evaluate(() => window.mapRequested), false);

const downloading = page.waitForEvent("download");
const button = page.getByRole("button", { name: /^Download report/ });
await button.click();
await page.waitForFunction(() => window.mapRequested && window.mapDeadline);
assert.equal(await button.isDisabled(), true);
await button.dispatchEvent("click");
await button.dispatchEvent("click");
await page.evaluate(() => window.mapDeadline());
const download = await downloading;
assert.equal(await download.failure(), null);
const chunks = [];
for await (const chunk of await download.createReadStream())
chunks.push(chunk);
const markdown = Buffer.concat(chunks).toString("utf8");
assert.match(markdown, /^# Interview Report/);
assert.doesNotMatch(markdown, /Original problem:/);
assert.equal(await button.isEnabled(), true);
const retrying = page.waitForEvent("download");
await button.click();
assert.equal(await button.isDisabled(), true);
await page.evaluate(() => window.mapDeadline());
assert.equal(await (await retrying).failure(), null);
assert.equal(await button.isEnabled(), true);
},
{ acceptDownloads: true },
);

lobbyTest("try again selects the problem", async (page) => {
reports = [savedAttempt(EASY[0])];
await lobby(page);
Expand Down
Loading