Skip to content

Commit 417da54

Browse files
committed
Hide LeetCode names in interviews by default
Honor the lobby's source visibility choice on the interview page. Previously, the lobby hid published names while the interview still showed the `LeetCode:` line. Read the existing preference through a shared storage helper and pass an explicit `showSources` value into the pure renderer. Storage failures default to off, while reports and history keep their existing behavior. Closes #201
1 parent f7b5452 commit 417da54

6 files changed

Lines changed: 112 additions & 34 deletions

File tree

‎tests/browser/hide-examples.test.js‎

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
launchChromium,
1414
startStaticServer,
1515
} from "./source.js";
16+
import { SHOW_SOURCES_KEY } from "../../web/lib.js";
1617

1718
let browser = null;
1819
let server = null;
@@ -79,6 +80,54 @@ test("the worked examples can be hidden, and stay hidden on the next visit", asy
7980
}
8081
});
8182

83+
test("the interview hides the published problem name when the toggle is off", async (t) => {
84+
if (!browser) return t.skip("playwright chromium unavailable");
85+
const page = await browser.newPage();
86+
try {
87+
await page.goto(`${base}/interview.html?problem=chargeback-pair-match`, {
88+
waitUntil: "domcontentloaded",
89+
});
90+
await page.evaluate(
91+
(key) => localStorage.setItem(key, "0"),
92+
SHOW_SOURCES_KEY,
93+
);
94+
await page.reload({ waitUntil: "domcontentloaded" });
95+
await page
96+
.locator("#problem-panel .problem-detail")
97+
.waitFor({ state: "attached" });
98+
assert.doesNotMatch(
99+
await page.locator("#problem-panel").textContent(),
100+
/LeetCode:/,
101+
);
102+
} finally {
103+
await page.close();
104+
}
105+
});
106+
107+
test("the interview shows the published problem name when the toggle is on", async (t) => {
108+
if (!browser) return t.skip("playwright chromium unavailable");
109+
const page = await browser.newPage();
110+
try {
111+
await page.goto(`${base}/interview.html?problem=chargeback-pair-match`, {
112+
waitUntil: "domcontentloaded",
113+
});
114+
await page.evaluate(
115+
(key) => localStorage.setItem(key, "1"),
116+
SHOW_SOURCES_KEY,
117+
);
118+
await page.reload({ waitUntil: "domcontentloaded" });
119+
await page
120+
.locator("#problem-panel .problem-detail")
121+
.waitFor({ state: "attached" });
122+
assert.match(
123+
await page.locator("#problem-panel").textContent(),
124+
/LeetCode: Two Sum/,
125+
);
126+
} finally {
127+
await page.close();
128+
}
129+
});
130+
82131
// The Add a case placeholder is the judge's first input, a worked case in its
83132
// own right, so hiding the examples has to hide it too.
84133
test("hiding the examples also hides the Add a case placeholder", async (t) => {

‎tests/browser/render.test.js‎

Lines changed: 42 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1105,17 +1105,20 @@ test("report markup marks a no-hire and an empty editor", () => {
11051105
});
11061106

11071107
test("problem markup escapes every field a problem carries", () => {
1108-
const body = problemMarkup({
1109-
source: "<script>t</script>",
1110-
brief: ["<script>s</script>"],
1111-
examples: [
1112-
{
1113-
input: "<script>i</script>",
1114-
output: "<script>o</script>",
1115-
explanation: "<script>x</script>",
1116-
},
1117-
],
1118-
});
1108+
const body = problemMarkup(
1109+
{
1110+
source: "<script>t</script>",
1111+
brief: ["<script>s</script>"],
1112+
examples: [
1113+
{
1114+
input: "<script>i</script>",
1115+
output: "<script>o</script>",
1116+
explanation: "<script>x</script>",
1117+
},
1118+
],
1119+
},
1120+
{ showSources: true },
1121+
);
11191122

11201123
assert.doesNotMatch(
11211124
body,
@@ -1143,19 +1146,39 @@ test("problem markup shows the scenario and leaves the limits to be asked for",
11431146
});
11441147

11451148
test("problem markup names the published title once, small, and only when it has one", () => {
1146-
const body = problemMarkup({
1147-
source: "Two Sum",
1148-
brief: ["Match the disputed charge."],
1149-
examples: [{ input: "a", output: "b" }],
1150-
});
1149+
const body = problemMarkup(
1150+
{
1151+
source: "Two Sum",
1152+
brief: ["Match the disputed charge."],
1153+
examples: [{ input: "a", output: "b" }],
1154+
},
1155+
{ showSources: true },
1156+
);
11511157
assert.equal(body.match(/Two Sum/g).length, 1);
11521158
assert.match(body, /<p class="problem-source">LeetCode: Two Sum<\/p>/);
11531159

1154-
const unnamed = problemMarkup({
1160+
const unnamed = problemMarkup(
1161+
{
1162+
brief: ["one"],
1163+
examples: [{ input: "a", output: "b" }],
1164+
},
1165+
{ showSources: true },
1166+
);
1167+
assert.doesNotMatch(unnamed, /problem-source|LeetCode|undefined/);
1168+
});
1169+
1170+
test("problem markup hides published titles by default", () => {
1171+
const problem = {
1172+
source: "Two Sum",
11551173
brief: ["one"],
11561174
examples: [{ input: "a", output: "b" }],
1157-
});
1158-
assert.doesNotMatch(unnamed, /problem-source|LeetCode|undefined/);
1175+
};
1176+
1177+
assert.doesNotMatch(problemMarkup(problem), /LeetCode:/);
1178+
assert.match(
1179+
problemMarkup(problem, { showSources: true }),
1180+
/LeetCode: Two Sum/,
1181+
);
11591182
});
11601183

11611184
test("problem markup omits the explanation line rather than printing undefined", () => {

‎web/app.js‎

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1-
import { FRAMEWORKS, codingLoop, interviewMode, languageLabel } from "./lib.js";
1+
import {
2+
FRAMEWORKS,
3+
SHOW_SOURCES_KEY,
4+
codingLoop,
5+
interviewMode,
6+
languageLabel,
7+
} from "./lib.js";
28
import {
39
clearReportHistory,
410
deleteReport,
@@ -156,13 +162,8 @@ for (const card of cards) mark(card.button, false);
156162
// The published names are off unless the candidate turns them on, and the
157163
// choice is theirs to keep between visits. A convenience, so storage that
158164
// throws (a private window, blocked site data) just means off.
159-
const SHOW_SOURCES_KEY = "codetrial.showProblemSources";
160165
const showSources = document.querySelector("#show-sources");
161-
try {
162-
showSources.checked = localStorage.getItem(SHOW_SOURCES_KEY) === "1";
163-
} catch {
164-
/* off */
165-
}
166+
showSources.checked = readStored(SHOW_SOURCES_KEY) === "1";
166167
// The published names are not in the page: they arrive with the map, fetched
167168
// the first time the candidate turns them on.
168169
const applySources = async () => {
@@ -185,11 +186,7 @@ const applySources = async () => {
185186
};
186187
showSources.addEventListener("change", () => {
187188
void applySources();
188-
try {
189-
localStorage.setItem(SHOW_SOURCES_KEY, showSources.checked ? "1" : "0");
190-
} catch {
191-
/* the page still shows what was chosen */
192-
}
189+
writeStored(SHOW_SOURCES_KEY, showSources.checked ? "1" : "0");
193190
});
194191
void applySources();
195192
const levels = [...document.querySelectorAll('[name="difficulty"]')];

‎web/interview.js‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ import {
7676
roomInterviewer,
7777
sanitizeReport,
7878
sessionReport,
79+
SHOW_SOURCES_KEY,
7980
testPayload,
8081
timeWarningPayload,
8182
topics,
@@ -2028,6 +2029,7 @@ function renderProblem() {
20282029
recordStage();
20292030
nodes.problemPanel.innerHTML = problemMarkup(problem, {
20302031
examples: !nodes.hideExamples.checked,
2032+
showSources: readStored(SHOW_SOURCES_KEY) === "1",
20312033
});
20322034
}
20332035

‎web/lib.js‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ const HTML_ESCAPES = {
99
"'": "&#39;",
1010
};
1111

12+
export const SHOW_SOURCES_KEY = "codetrial.showProblemSources";
13+
1214
export function escapeHtml(value) {
1315
return String(value).replace(/[&<>"']/g, (char) => HTML_ESCAPES[char]);
1416
}

‎web/render.js‎

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -205,10 +205,15 @@ function sourceEventMarkup(event) {
205205
// No constraints section and no source statement: the page carries the scenario
206206
// the interview poses, and the limits and edge-case policies are what the
207207
// candidate asks Jim for, the way they would ask a person. The published title
208-
// is named once, small, so the problem can be found again after the interview.
208+
// is left out unless showSources is set, the lobby's "Show the LeetCode
209+
// problem" choice; then it is named once, small, so the problem can be found
210+
// again after the interview.
209211
// The worked examples can be left out, so the Example step starts from cases
210212
// the candidate proposes instead of ones already on screen.
211-
export function problemMarkup(problem, { examples = true } = {}) {
213+
export function problemMarkup(
214+
problem,
215+
{ examples = true, showSources = false } = {},
216+
) {
212217
const example = (example, index) => `
213218
<section>
214219
<h2>Example ${index + 1}</h2>
@@ -224,7 +229,7 @@ export function problemMarkup(problem, { examples = true } = {}) {
224229
return `
225230
<div class="problem-detail">
226231
<p class="interview-kicker">Interview exercise</p>
227-
${problem.source ? `<p class="problem-source">LeetCode: ${escapeHtml(problem.source)}</p>` : ""}
232+
${showSources && problem.source ? `<p class="problem-source">LeetCode: ${escapeHtml(problem.source)}</p>` : ""}
228233
${problem.requestedPage ? `<p class="muted small">The link asked for an exercise this bank does not have, so this is the default exercise.</p>` : ""}
229234
${problem.brief.map((text) => `<p>${escapeHtml(text)}</p>`).join("")}
230235
${

0 commit comments

Comments
 (0)