Skip to content

Commit 08f5d26

Browse files
davidagustinclaude
andcommitted
fix(problems): comprehensive audit fixes across all frameworks
CRITICAL fixes: - fe-react-render-nothing: remove incorrect NaN claim, simplify willRender - fe-js-async-await-pattern: rename to "Function-Based Data Extraction", fix hints that falsely referenced async/await - fe-js-request-queue: fix text saying process() when sample uses run() - fe-react-memo-shallow: fix text "return true if changed" to match shallowEqual semantics (returns true when equal) - codeValidator: always wrap return expressions in parens so object literals with trailing semicolons are not parsed as blocks WARNING fixes: - fe-vue-watch-effect: fix text "changes from 5 to 10" when counter is 10 - fe-react-fetch-race-condition: clarify isStale/isFresh semantics in text - 14 native-js problems: fix "implement X" text where setupCode already provides the implementation (curry, pipe, compose, partial, iterator, strategy, decorator, deep-clone, immutable-update, conditional-render, lazy-render, promise-all-sim) - 2 react problems: fix "implement" text (effect-deps-compare, useSyncExternalStore) - 2 vue problems: fix "no setup needed" when setupCode provides functions (event-once, event-bus) - Angular signal-update: accept any variable name in update callback instead of hardcoding "v" - Angular decorator patterns: accept TypeScript definite assignment (!) for @input, @ViewChild, @ViewChildren, @ContentChild Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent cfdf24b commit 08f5d26

File tree

5 files changed

+48
-43
lines changed

5 files changed

+48
-43
lines changed

lib/codeValidator.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -157,14 +157,15 @@ export function validateJavaScript(
157157
// Last line is also a statement (function def, if, etc.) — run everything, return undefined
158158
fullCode = trimmedAnswer;
159159
} else if (lastLine.endsWith(';')) {
160-
fullCode = `${precedingLines}\nreturn ${lastLine.slice(0, -1)};`;
160+
fullCode = `${precedingLines}\nreturn (${lastLine.slice(0, -1)});`;
161161
} else {
162162
fullCode = `${precedingLines}\nreturn (${lastLine});`;
163163
}
164164
} else {
165-
// Pure expression
165+
// Pure expression — always wrap in parens so object literals are not
166+
// parsed as blocks (e.g. `{ a: 1 }` without parens is a block + label)
166167
if (trimmedAnswer.endsWith(';')) {
167-
fullCode = `return ${trimmedAnswer.slice(0, -1)};`;
168+
fullCode = `return (${trimmedAnswer.slice(0, -1)});`;
168169
} else {
169170
fullCode = `return (${trimmedAnswer});`;
170171
}

lib/frontend-drills/problems/angular.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,10 @@ export const angularProblems: FrontendDrillProblem[] = [
5454
setupCode: `class MyComponent {}`,
5555
expected: '@Input decorated property',
5656
sample: `@Input() name: string;`,
57-
validPatterns: [/@Input\s*\(\s*\)\s+name\s*:\s*string/, /@Input\s*\(\s*\)\s+name\s*[;:]/],
57+
validPatterns: [
58+
/@Input\s*\(\s*\)\s+name\s*!?\s*:\s*string/,
59+
/@Input\s*\(\s*\)\s+name\s*!?\s*[;:]/,
60+
],
5861
realWorldExample:
5962
'In a YouTube-style app, a VideoCard component receives a video title and thumbnail URL from its parent via @Input -- this is how parent components pass data down to children.',
6063
hints: [
@@ -508,7 +511,7 @@ onClick(event: Event) {}`,
508511
setupCode: `class MyComponent {}`,
509512
expected: '@ViewChild with template reference',
510513
sample: `@ViewChild('myInput') inputRef: ElementRef;`,
511-
validPatterns: [/@ViewChild\s*\(\s*['"]myInput['"]\s*\)\s+inputRef\s*:\s*ElementRef/],
514+
validPatterns: [/@ViewChild\s*\(\s*['"]myInput['"]\s*\)\s+inputRef\s*!?\s*:\s*ElementRef/],
512515
realWorldExample:
513516
'Notion auto-focuses the title input when you create a new page -- @ViewChild grabs a reference to that input element so the component can call .focus() on it.',
514517
hints: [
@@ -530,7 +533,7 @@ onClick(event: Event) {}`,
530533
expected: '@ViewChildren with QueryList',
531534
sample: `@ViewChildren(ItemComponent) items: QueryList<ItemComponent>;`,
532535
validPatterns: [
533-
/@ViewChildren\s*\(\s*ItemComponent\s*\)\s+items\s*:\s*QueryList\s*<\s*ItemComponent\s*>/,
536+
/@ViewChildren\s*\(\s*ItemComponent\s*\)\s+items\s*!?\s*:\s*QueryList\s*<\s*ItemComponent\s*>/,
534537
],
535538
realWorldExample:
536539
'A Kanban board like Trello needs references to all card components in a column to calculate drag-drop positions -- @ViewChildren returns a live QueryList of matching children.',
@@ -615,7 +618,9 @@ onClick(event: Event) {}`,
615618
setupCode: `class MyComponent {}`,
616619
expected: '@ContentChild with TemplateRef',
617620
sample: `@ContentChild('header') headerContent: TemplateRef<any>;`,
618-
validPatterns: [/@ContentChild\s*\(\s*['"]header['"]\s*\)\s+headerContent\s*:\s*TemplateRef/],
621+
validPatterns: [
622+
/@ContentChild\s*\(\s*['"]header['"]\s*\)\s+headerContent\s*!?\s*:\s*TemplateRef/,
623+
],
619624
realWorldExample:
620625
"A reusable modal component in an Angular Material-style library accesses the projected header template via @ContentChild to position it in the modal's title bar.",
621626
hints: [
@@ -888,7 +893,7 @@ count.set(5);`,
888893
setupCode: `const signal = (val: any) => { const s = () => val; s.set = (v: any) => { val = v; }; s.update = (fn: any) => { val = fn(val); }; return s; };\nconst count = signal(10);`,
889894
expected: 'Signal update with function',
890895
sample: `count.update(v => v + 1);`,
891-
validPatterns: [/count\.update\s*\(\s*v\s*=>\s*v\s*\+\s*1\s*\)/],
896+
validPatterns: [/count\.update\s*\(\s*\(?\s*\w+\s*\)?\s*=>\s*\w+\s*\+\s*1\s*\)/],
892897
realWorldExample:
893898
"A 'Like' button on Instagram increments a counter based on the current value -- signal.update() derives the new state from the old without a separate read step.",
894899
hints: [

lib/frontend-drills/problems/native-js.ts

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -219,8 +219,8 @@ typeof mockFetch`,
219219
framework: 'native-js',
220220
category: 'Data Fetching',
221221
difficulty: 'medium',
222-
title: 'Async/Await Data Extraction',
223-
text: 'Create a function that calls mockFetch() and returns the data property from the result.',
222+
title: 'Function-Based Data Extraction',
223+
text: 'Create a function that calls mockFetch() and returns the data property from the result. The validator checks that you defined a function (via typeof).',
224224
setup: 'A mockFetch function that returns { data: [1, 2, 3] } synchronously.',
225225
setupCode: `const mockFetch = () => ({ data: [1, 2, 3] });`,
226226
expected: 'function',
@@ -230,11 +230,11 @@ typeof mockFetch`,
230230
}
231231
typeof getData`,
232232
realWorldExample:
233-
'Netflix uses async/await to fetch your personalized recommendations from their API and extract the movie list from the response payload.',
233+
'Netflix wraps API calls in reusable functions so the same fetch-and-extract logic can be called from multiple components without duplicating code.',
234234
hints: [
235-
'Use async function keyword',
236-
'Use await to wait for the promise',
237-
'Return the data property',
235+
'Define a named function using the function keyword',
236+
'Call mockFetch() inside the function and store the result',
237+
'Return the data property from the result object',
238238
],
239239
tags: ['async', 'await', 'Promise', 'data'],
240240
},
@@ -1075,7 +1075,7 @@ checkPasswordStrength(testPassword)`,
10751075
category: 'Common Patterns',
10761076
difficulty: 'medium',
10771077
title: 'Currying',
1078-
text: 'Implement a curry function that transforms a function taking 3 arguments into a chain of single-argument functions. Return the result.',
1078+
text: 'A curry utility is provided that transforms a 3-argument function into a chain of single-argument calls. Use the curried add3 to compute the sum of three values.',
10791079
setup: 'A curry utility.',
10801080
setupCode: `function curry(fn) {\n return function(a) {\n return function(b) {\n return function(c) {\n return fn(a, b, c);\n };\n };\n };\n}\nconst add3 = curry((a, b, c) => a + b + c);`,
10811081
expected: 15,
@@ -1095,7 +1095,7 @@ checkPasswordStrength(testPassword)`,
10951095
category: 'Common Patterns',
10961096
difficulty: 'easy',
10971097
title: 'Partial Application',
1098-
text: 'Implement a partial function that pre-fills some arguments. Return the result of calling the partially applied function.',
1098+
text: 'A partial application utility is provided along with a double function (multiply pre-filled with 2). Call double with the remaining arguments to get the product.',
10991099
setup: 'A partial application utility.',
11001100
setupCode: `function partial(fn, ...preArgs) {\n return function(...laterArgs) {\n return fn(...preArgs, ...laterArgs);\n };\n}\nconst multiply = (a, b, c) => a * b * c;\nconst double = partial(multiply, 2);`,
11011101
expected: 30,
@@ -1115,7 +1115,7 @@ checkPasswordStrength(testPassword)`,
11151115
category: 'Common Patterns',
11161116
difficulty: 'easy',
11171117
title: 'Pipe Function',
1118-
text: 'Implement a pipe function that composes functions left to right. Pass a value through the pipeline and return the result.',
1118+
text: 'A pipe function is provided that composes functions left to right. A transform pipeline is built from three operations (+1, *2, -3). Pass 4 through the pipeline and return the result.',
11191119
setup: 'A pipe utility.',
11201120
setupCode: `function pipe(...fns) {\n return (x) => fns.reduce((acc, fn) => fn(acc), x);\n}\nconst transform = pipe(\n x => x + 1,\n x => x * 2,\n x => x - 3\n);`,
11211121
expected: 7,
@@ -1132,7 +1132,7 @@ checkPasswordStrength(testPassword)`,
11321132
category: 'Common Patterns',
11331133
difficulty: 'easy',
11341134
title: 'Compose Function',
1135-
text: 'Implement a compose function that composes functions right to left (opposite of pipe). Return the result.',
1135+
text: 'A compose function is provided that composes functions right to left (opposite of pipe). A transform pipeline is built from three operations. Pass 4 through it and return the result.',
11361136
setup: 'A compose utility.',
11371137
setupCode: `function compose(...fns) {\n return (x) => fns.reduceRight((acc, fn) => fn(acc), x);\n}\nconst transform = compose(\n x => x - 3,\n x => x * 2,\n x => x + 1\n);`,
11381138
expected: 7,
@@ -1194,7 +1194,7 @@ checkPasswordStrength(testPassword)`,
11941194
category: 'Common Patterns',
11951195
difficulty: 'medium',
11961196
title: 'Custom Iterator: Range',
1197-
text: 'Implement a range iterator that yields numbers from start to end (exclusive). Use Symbol.iterator protocol. Spread it into an array.',
1197+
text: 'A range iterator is provided that yields numbers from start to end (exclusive) using the Symbol.iterator protocol. Spread range(2, 7) into an array and return it.',
11981198
setup: 'A range iterable object.',
11991199
setupCode: `function range(start, end) {\n return {\n [Symbol.iterator]() {\n let current = start;\n return {\n next() {\n if (current < end) return { value: current++, done: false };\n return { done: true };\n }\n };\n }\n };\n}`,
12001200
expected: [2, 3, 4, 5, 6],
@@ -1277,7 +1277,7 @@ checkPasswordStrength(testPassword)`,
12771277
category: 'Common Patterns',
12781278
difficulty: 'medium',
12791279
title: 'Strategy Pattern',
1280-
text: 'Implement a sorter that accepts different comparison strategies. Sort an array with ascending and descending strategies. Return both results.',
1280+
text: 'A strategy-based sorter is provided with ascending and descending comparators. Use sortWith to sort the data array with both strategies and return both results as an object.',
12811281
setup: 'A strategy-based sorter.',
12821282
setupCode: `const strategies = {\n ascending: (a, b) => a - b,\n descending: (a, b) => b - a\n};\nfunction sortWith(arr, strategyName) {\n return [...arr].sort(strategies[strategyName]);\n}\nconst data = [3, 1, 4, 1, 5, 9];`,
12831283
expected: { asc: [1, 1, 3, 4, 5, 9], desc: [9, 5, 4, 3, 1, 1] },
@@ -1294,7 +1294,7 @@ checkPasswordStrength(testPassword)`,
12941294
category: 'Common Patterns',
12951295
difficulty: 'medium',
12961296
title: 'Decorator Pattern',
1297-
text: 'Implement decorators that wrap a base function to add logging and timing metadata. Chain two decorators and return the enriched result.',
1297+
text: 'Decorators withLogging and withTimestamp are provided, along with a base function already wrapped in both. Call the decorated function with 5 and return the enriched result.',
12981298
setup: 'A decorator chain.',
12991299
setupCode: `function withLogging(fn) {\n return function(...args) {\n const result = fn(...args);\n return { ...result, logged: true };\n };\n}\nfunction withTimestamp(fn) {\n return function(...args) {\n const result = fn(...args);\n return { ...result, timestamp: 12345 };\n };\n}\nconst base = (x) => ({ value: x * 2 });\nconst decorated = withTimestamp(withLogging(base));`,
13001300
expected: { value: 10, logged: true, timestamp: 12345 },
@@ -1314,7 +1314,7 @@ checkPasswordStrength(testPassword)`,
13141314
category: 'Common Patterns',
13151315
difficulty: 'easy',
13161316
title: 'Immutable Object Update',
1317-
text: 'Update a nested object immutably: change user.address.city to "Seattle" without mutating the original. Return both the original city and the new city.',
1317+
text: 'An immutable update is demonstrated: the original object and an updated copy (with city changed to "Seattle") are provided. Return both the original and new city values to verify independence.',
13181318
setup: 'A nested object to update immutably.',
13191319
setupCode: `const original = { user: { name: "Alice", address: { city: "Portland", state: "OR" } } };\nconst updated = {\n ...original,\n user: {\n ...original.user,\n address: { ...original.user.address, city: "Seattle" }\n }\n};`,
13201320
expected: { originalCity: 'Portland', newCity: 'Seattle' },
@@ -1331,7 +1331,7 @@ checkPasswordStrength(testPassword)`,
13311331
category: 'Common Patterns',
13321332
difficulty: 'hard',
13331333
title: 'Deep Clone',
1334-
text: 'Implement a deepClone function that handles objects, arrays, and primitives. Clone a nested structure and verify independence.',
1334+
text: 'A deepClone function is provided. A source object has been cloned and the clone mutated. Return properties from both source and clone to verify they are independent.',
13351335
setup: 'A deep clone utility.',
13361336
setupCode: `function deepClone(obj) {\n if (obj === null || typeof obj !== "object") return obj;\n if (Array.isArray(obj)) return obj.map(item => deepClone(item));\n const cloned = {};\n for (const key of Object.keys(obj)) cloned[key] = deepClone(obj[key]);\n return cloned;\n}\nconst src = { a: 1, b: [2, 3], c: { d: 4 } };\nconst copy = deepClone(src);\ncopy.b.push(99);\ncopy.c.d = 999;`,
13371337
expected: { srcB: [2, 3], copyB: [2, 3, 99], srcD: 4, copyD: 999 },
@@ -1436,7 +1436,7 @@ checkPasswordStrength(testPassword)`,
14361436
category: 'Rendering',
14371437
difficulty: 'easy',
14381438
title: 'Conditional Rendering',
1439-
text: 'Given a user object that may or may not have a name, render a greeting or a login prompt. Return the HTML string.',
1439+
text: 'A renderGreeting function is provided that returns a greeting for named users or a login prompt otherwise. It has been called with a logged-in user and null. Return both results.',
14401440
setup: 'User objects for conditional rendering.',
14411441
setupCode: `function renderGreeting(user) {\n if (user && user.name) return \`<h1>Hello, \${user.name}!</h1>\`;\n return "<h1>Please log in</h1>";\n}\nconst loggedIn = renderGreeting({ name: "Alice" });\nconst loggedOut = renderGreeting(null);`,
14421442
expected: { loggedIn: '<h1>Hello, Alice!</h1>', loggedOut: '<h1>Please log in</h1>' },
@@ -1545,7 +1545,7 @@ checkPasswordStrength(testPassword)`,
15451545
category: 'Rendering',
15461546
difficulty: 'medium',
15471547
title: 'Lazy Render Queue',
1548-
text: 'Implement a render queue that collects render tasks and processes them in batches of a given size. Return the rendered batch results.',
1548+
text: 'A render queue with add and flush methods is provided (batch size 2). Three render tasks have been queued. Two flushes have been called. Return both batch results.',
15491549
setup: 'A lazy render queue.',
15501550
setupCode: `function createRenderQueue(batchSize) {\n const queue = [];\n return {\n add(renderFn) { queue.push(renderFn); },\n flush() {\n const batch = queue.splice(0, batchSize);\n return batch.map(fn => fn());\n }\n };\n}\nconst rq = createRenderQueue(2);\nrq.add(() => "<div>1</div>");\nrq.add(() => "<div>2</div>");\nrq.add(() => "<div>3</div>");\nconst batch1 = rq.flush();\nconst batch2 = rq.flush();`,
15511551
expected: { batch1: ['<div>1</div>', '<div>2</div>'], batch2: ['<div>3</div>'] },
@@ -1775,7 +1775,7 @@ checkPasswordStrength(testPassword)`,
17751775
category: 'Data Fetching',
17761776
difficulty: 'easy',
17771777
title: 'Promise.all Simulation',
1778-
text: 'Implement a synchronous version of Promise.all: given an array of values, apply a transform to each and collect the results into a single array.',
1778+
text: 'A collectAll function is provided that applies a transform to each item in an array. Use it to multiply each value by 10 and return the results.',
17791779
setup: 'Values to transform and collect.',
17801780
setupCode: `const values = [1, 2, 3];\nfunction collectAll(items, fn) {\n return items.map(fn);\n}`,
17811781
expected: [10, 20, 30],
@@ -1855,7 +1855,7 @@ checkPasswordStrength(testPassword)`,
18551855
category: 'Data Fetching',
18561856
difficulty: 'medium',
18571857
title: 'Request Queue',
1858-
text: 'Write a createQueue() returning {add(fn), process(), getResults()}. add enqueues a function. process runs all queued functions in order, collecting return values. Add three functions, process them, and return the results.',
1858+
text: 'Write a createQueue() returning {add(fn), run(), getResults()}. add enqueues a function. run executes all queued functions in order, collecting return values. Add three functions, run them, and return the results.',
18591859
setup: 'No setup needed.',
18601860
setupCode: `// Implement a sequential request queue`,
18611861
expected: ['A', 'B', 'C'],

0 commit comments

Comments
 (0)