feat(curriculum): add workshop-word-counter to JS v9 cert (#64012)

Co-authored-by: Jessica Wilkins <67210629+jdwilkin4@users.noreply.github.com>
Co-authored-by: jdwilkin4 <jwilkin4@hotmail.com>
This commit is contained in:
l3onhard
2026-04-20 19:35:47 +02:00
committed by GitHub
parent cba60d8511
commit b72906085e
14 changed files with 729 additions and 0 deletions
+6
View File
@@ -4860,6 +4860,12 @@
"Loops are an essential part of JavaScript. That's why the following lectures have been prepared for you to learn about the different types of loops and how they work, and also how iteration works."
]
},
"workshop-word-counter": {
"title": "Build a Word Counter",
"intro": [
"In this workshop, you will practice using <code>for...of</code> loops by building a function that counts the occurrences of a string in an array of strings."
]
},
"workshop-sentence-analyzer": {
"title": "Build a Sentence Analyzer",
"intro": [
@@ -0,0 +1,41 @@
---
id: 691f7bbf76229bb97c60827d
title: Step 1
challengeType: 1
dashedName: step-1
---
# --description--
In this workshop, you will practice using `for...of` loops by building a function that counts how often a string appears in an array of strings.
Before you write that function, you will create a simpler one that logs each character of a string to the console.
Start by defining an empty function called `printCharacters` with the parameter `str`.
# --hints--
You should create a `printCharacters` function.
```js
assert.isFunction(printCharacters);
```
Your `printCharacters` function should have one parameter called `str`.
```js
const codeWithoutJSComments = __helpers.removeJSComments(code);
const functionRegex = __helpers.functionRegex('printCharacters', ['str']);
assert.match(codeWithoutJSComments, functionRegex);
```
# --seed--
## --seed-contents--
```js
--fcc-editable-region--
--fcc-editable-region--
```
@@ -0,0 +1,53 @@
---
id: 6920c9cda1c7f056f75e4b1a
title: Step 2
challengeType: 1
dashedName: step-2
---
# --description--
Now its time to write your first `for...of` loop. In the preceding lessons, you learned that you can use a `for...of` loop to iterate over values from an iterable (e.g., strings or arrays).
Here is an example for a `for...of` loop:
```js
for (const num of [1, 2, 3]) {
// code block to be executed
}
```
Add a `for...of` loop with an empty code block inside your function. It should iterate over every character in the `str` argument.
# --hints--
Your `printCharacters` function should have a `for...of` loop inside its code block.
```js
const codeWithoutJSComments = __helpers.removeJSComments(code);
const normalizedCode = __helpers.removeWhiteSpace(codeWithoutJSComments);
assert.match(normalizedCode, /functionprintCharacters\(str\)\{for\(\S+of\S+\)\{\S*?\}\}/);
```
Your `for...of` loop should iterate over the `str` argument of your function.
```js
const codeWithoutJSComments = __helpers.removeJSComments(code);
const forLoopRegex = /function printCharacters\(str\) \{\s*for\s*\(([\S\s]*?)\)\s*\{[\S\s]*?\}\s*\}/;
const forLoopCode = codeWithoutJSComments.match(forLoopRegex)[1];
assert.match(forLoopCode, /(const|let|var)\s+\w+\s+of\s+str/);
```
# --seed--
## --seed-contents--
```js
function printCharacters(str) {
--fcc-editable-region--
--fcc-editable-region--
}
```
@@ -0,0 +1,47 @@
---
id: 6920d189e36b6b60a6330fee
title: Step 3
challengeType: 1
dashedName: step-3
---
# --description--
Now, inside the loop, log the `char` variable to the console.
# --hints--
You should have a `console.log` statement with `char` as its argument inside the loop.
```js
const codeWithoutJSComments = __helpers.removeJSComments(code);
const functionRegex = __helpers.functionRegex('printCharacters', ['str'], { capture: true });
const functionCode = codeWithoutJSComments.match(functionRegex)[1];
const normalizedFunctionCode = __helpers.removeWhiteSpace(functionCode);
const loopCodeBlock = normalizedFunctionCode.match(/for\(constcharofstr\)\{(\S*?)}/)[1]
assert.match(loopCodeBlock, /console\.log\(char\)/);
```
When the `printCharacters` function is called with the argument `'test'`, the `console.log()` statement inside the function should be called four times with the following arguments, in order: `'t'`, `'e'`, `'s'`, and `'t'`. Instead, it was called with the following arguments: `--fcc-actual--`.
```js
const consoleLogSpy = __helpers.spyOn(console, 'log');
printCharacters('test');
assert.deepEqual(consoleLogSpy.calls, [['t'], ['e'], ['s'], ['t']]);
consoleLogSpy.restore();
```
# --seed--
## --seed-contents--
```js
function printCharacters(str) {
for (const char of str) {
--fcc-editable-region--
--fcc-editable-region--
}
}
```
@@ -0,0 +1,35 @@
---
id: 6920dae65fb8bb6949bcdaba
title: Step 4
challengeType: 1
dashedName: step-4
---
# --description--
To see how the loop inside `printCharacters` behaves, call it with the argument `"hello"`.
# --hints--
You should call the function `printCharacters` with `"hello"` as its argument.
```js
const codeWithoutJSComments = __helpers.removeJSComments(code);
const normalizedCode = __helpers.removeWhiteSpace(codeWithoutJSComments);
assert.match(normalizedCode, /printCharacters\(('|"|`)hello\1\)/);
```
# --seed--
## --seed-contents--
```js
function printCharacters(str) {
for (const char of str) {
console.log(char);
}
}
--fcc-editable-region--
--fcc-editable-region--
```
@@ -0,0 +1,46 @@
---
id: 6921d688b8d58581d34459b9
title: Step 5
challengeType: 1
dashedName: step-5
---
# --description--
In the next few steps, you will build a function that counts how often a string appears in an array of strings.
To start, define an empty function named `getMatchedWordCount` with the parameters `sentence` and `match` in that order.
# --hints--
You should create a `getMatchedWordCount` function.
```js
assert.isFunction(getMatchedWordCount);
```
Your `getMatchedWordCount` function should have two parameters: `sentence` and `match`, in that order.
```js
const codeWithoutJSComments = __helpers.removeJSComments(code);
const functionRegex = __helpers.functionRegex('getMatchedWordCount', ['sentence', 'match']);
assert.match(codeWithoutJSComments, functionRegex);
```
# --seed--
## --seed-contents--
```js
function printCharacters(str) {
for (const char of str) {
console.log(char);
}
}
printCharacters("hello");
--fcc-editable-region--
--fcc-editable-region--
```
@@ -0,0 +1,83 @@
---
id: 6921d90b5ce50185505d2004
title: Step 6
challengeType: 1
dashedName: step-6
---
# --description--
Since the `getMatchedWordCount` function needs to return a numerical count, you will start by setting up a counter variable.
Create a variable named `count` using the `let` keyword, initialize it with the value `0`, and add a `return` statement that returns the `count` variable.
# --hints--
You should declare the `count` variable inside the function `getMatchedWordCount`.
```js
const codeWithoutJSComments = __helpers.removeJSComments(code);
const functionRegex = __helpers.functionRegex('getMatchedWordCount', ['sentence', 'match'], { capture: true });
const functionCode = codeWithoutJSComments.match(functionRegex)[1];
const normalizedFunctionCode = __helpers.removeWhiteSpace(functionCode);
assert.match(normalizedFunctionCode, /(const|let|var)count/);
```
To allow the `count` variable to be incremented later, declare it with the `let` keyword.
```js
const codeWithoutJSComments = __helpers.removeJSComments(code);
const functionRegex = __helpers.functionRegex('getMatchedWordCount', ['sentence', 'match'], { capture: true });
const functionCode = codeWithoutJSComments.match(functionRegex)[1];
const normalizedFunctionCode = __helpers.removeWhiteSpace(functionCode);
assert.match(normalizedFunctionCode, /letcount/);
```
Your function should contain an assignment of the value `0` to the `count` variable.
```js
const codeWithoutJSComments = __helpers.removeJSComments(code);
const functionRegex = __helpers.functionRegex('getMatchedWordCount', ['sentence', 'match'], { capture: true });
const functionCode = codeWithoutJSComments.match(functionRegex)[1];
const normalizedFunctionCode = __helpers.removeWhiteSpace(functionCode);
assert.match(normalizedFunctionCode, /count=0/);
```
Your function should contain a `return` statement that returns the `count` variable.
```js
const codeWithoutJSComments = __helpers.removeJSComments(code);
const functionRegex = __helpers.functionRegex('getMatchedWordCount', ['sentence', 'match'], { capture: true });
const functionCode = codeWithoutJSComments.match(functionRegex)[1];
const normalizedFunctionCode = __helpers.removeWhiteSpace(functionCode);
assert.match(normalizedFunctionCode, /returncount/);
```
Your `getMatchedWordCount` function should return the value `0`.
```js
assert.strictEqual(getMatchedWordCount(["foo", "bar"], "foo"), 0);
```
# --seed--
## --seed-contents--
```js
function printCharacters(str) {
for (const char of str) {
console.log(char);
}
}
printCharacters("hello");
function getMatchedWordCount(sentence, match) {
--fcc-editable-region--
--fcc-editable-region--
}
```
@@ -0,0 +1,51 @@
---
id: 6921dae10700e300f26994e3
title: Step 7
challengeType: 1
dashedName: step-7
---
# --description--
To view the output of your `getMatchedWordCount` function in its current state, add a `console.log` statement below it. Inside the `console.log()`, call the function with the argument `["I", "really", "really", "really", "like", "to", "code"]` for the `sentence` parameter and `"really"` for the `match` parameter.
# --hints--
You should call the function `getMatchedWordCount` inside a `console.log` statement.
```js
const codeWithoutJSComments = __helpers.removeJSComments(code);
const normalizedCode = __helpers.removeWhiteSpace(codeWithoutJSComments);
assert.match(normalizedCode, /console\.log\(getMatchedWordCount\(\S*?\)\)/);
```
You should call the function `getMatchedWordCount` with the argument `["I", "really", "really", "really", "like", "to", "code"]` for the `sentence` parameter and `"really"` for the `match` parameter.
```js
const codeWithoutJSComments = __helpers.removeJSComments(code);
const normalizedCode = __helpers.removeWhiteSpace(codeWithoutJSComments);
const normalizedConsoleLogCode = normalizedCode.match(/console\.log\(getMatchedWordCount\(\S*?\)\)/)[0];
assert.match(normalizedConsoleLogCode, /\[('|"|`)I\1,('|"|`)really\2,('|"|`)really\3,('|"|`)really\4,('|"|`)like\5,('|"|`)to\6,('|"|`)code\7\],('|"|`)really\8/);
```
# --seed--
## --seed-contents--
```js
function printCharacters(str) {
for (const char of str) {
console.log(char);
}
}
printCharacters("hello");
function getMatchedWordCount(sentence, match) {
let count = 0;
return count;
}
--fcc-editable-region--
--fcc-editable-region--
```
@@ -0,0 +1,61 @@
---
id: 6921de8d2a111d0341cff702
title: Step 8
challengeType: 1
dashedName: step-8
---
# --description--
Great, your function returns a `0`, but it doesnt count anything yet.
To fix this, inside `getMatchedWordCount`, create a `for...of` loop with an empty code block that iterates over each word in `sentence`.
# --hints--
Your `getMatchedWordCount` function should have a `for...of` loop inside its code block.
```js
const codeWithoutJSComments = __helpers.removeJSComments(code);
const normalizedCode = __helpers.removeWhiteSpace(codeWithoutJSComments);
assert.match(normalizedCode, /functiongetMatchedWordCount\(sentence,match\)\{letcount=0\;for\(\S+of\S+\)\{\S*?\}returncount;\}/);
```
Your `for...of` loop should iterate over the `sentence` argument of your function.
```js
const codeWithoutJSComments = __helpers.removeJSComments(code);
const forLoopRegex = /function getMatchedWordCount\(sentence, match\) \{\s*let count = 0\;\s*for\s*\(([\S\s]*?)\)\s*\{[\S\s]*?\}\s*return count\;\s*\}/;
const forLoopCode = codeWithoutJSComments.match(forLoopRegex)[1];
assert.match(forLoopCode, /(const|let|var)\s+\w+\s+of\s+sentence/);
```
# --seed--
## --seed-contents--
```js
function printCharacters(str) {
for (const char of str) {
console.log(char);
}
}
printCharacters("hello");
function getMatchedWordCount(sentence, match) {
let count = 0;
--fcc-editable-region--
--fcc-editable-region--
return count;
}
console.log(
getMatchedWordCount(
["I", "really", "really", "really", "like", "to", "code"],
"really"
)
);
```
@@ -0,0 +1,88 @@
---
id: 6921e10ae2681205b516fbe6
title: Step 10
challengeType: 1
dashedName: step-10
---
# --description--
Now it's time to add the logic to your loop that increments `count` appropriately.
Inside the loop, use a conditional statement to increment `count` by `1` if the variable `word` equals the variable `match`. Otherwise, leave the value of `count` unchanged.
# --hints--
Calling `getMatchedWordCount([], "bar")`, `getMatchedWordCount(["foo"], "bar")`, `getMatchedWordCount(["foo", "bar", "foo"], "bar")`, and `getMatchedWordCount(["foo", "bar", "foo"], "foo")` in that order should each return a value of type `"number"`; instead, it returned values of type `--fcc-actual--`.
```js
const obj = { method: (arg1, arg2) => typeof getMatchedWordCount(arg1, arg2) };
const spy = __helpers.spyOn(obj, 'method');
obj.method([], "bar");
obj.method(["foo"], "bar");
obj.method(["foo", "bar", "foo"], "bar");
obj.method(["foo", "bar", "foo"], "foo");
assert.deepEqual(spy.returns, ["number", "number", "number", "number"]);
spy.restore();
```
`getMatchedWordCount([], "bar")` should return `0`, but it returnes `--fcc-actual--`.
```js
const resultString = String(getMatchedWordCount([], "bar"));
assert.strictEqual(resultString, "0");
```
`getMatchedWordCount(["foo"], "bar")` should return `0`, but it returnes `--fcc-actual--`.
```js
const resultString = String(getMatchedWordCount(["foo"], "bar"));
assert.equal(resultString, "0");
```
`getMatchedWordCount(["foo", "bar", "foo"], "bar")` should return `1`, but it returnes `--fcc-actual--`.
```js
const resultString = String(getMatchedWordCount(["foo", "bar", "foo"], "bar"));
assert.equal(resultString, "1");
```
`getMatchedWordCount(["foo", "bar", "foo"], "foo")` should return `2`, but it returnes `--fcc-actual--`.
```js
const resultString = String(getMatchedWordCount(["foo", "bar", "foo"], "foo"));
assert.equal(resultString, "2");
```
# --seed--
## --seed-contents--
```js
function printCharacters(str) {
for (const char of str) {
console.log(char);
}
}
printCharacters("hello");
function getMatchedWordCount(sentence, match) {
let count = 0;
for (const word of sentence) {
--fcc-editable-region--
--fcc-editable-region--
console.log(`Checking "${word}" against "${match}" | Running count: ${count}`);
}
return count;
}
console.log(
getMatchedWordCount(
["I", "really", "really", "really", "like", "to", "code"],
"really"
)
);
```
@@ -0,0 +1,114 @@
---
id: 69220a03729c43082ad3e9f6
title: Step 11
challengeType: 1
dashedName: step-11
---
# --description--
Youve finished working on your `getMatchedWordCount` function!
Now you are going to test it with different data to see how it behaves.
Add a new `console.log` statement below the existing one that calls `getMatchedWordCount` with these parameters:
- sentence: `["Do", "not", "fear", "the", "dandy", "lion"]`
- match: `"dandy"`
Congratulations! Youve completed this workshop.
# --hints--
You should have a second `console.log` statement that calls the `getMatchedWordCount` function inside.
```js
const codeWithoutJSComments = __helpers.removeJSComments(code);
const normalizedCode = __helpers.removeWhiteSpace(codeWithoutJSComments);
const normalizedConsoleLogCode = normalizedCode.match(/console\.log\(getMatchedWordCount\(\S*?\)\)/g)[1];
assert.exists(normalizedConsoleLogCode);
```
In your second `console.log` statement, call the `getMatchedWordCount` function with `["Do", "not", "fear", "the", "dandy", "lion"]` as the `sentence` argument and `"dandy"` as the `match` argument.
```js
const codeWithoutJSComments = __helpers.removeJSComments(code);
const normalizedCode = __helpers.removeWhiteSpace(codeWithoutJSComments);
const normalizedConsoleLogCode = normalizedCode.match(/console\.log\(getMatchedWordCount\(\S*?\)\)/g)[1];
assert.match(normalizedConsoleLogCode, /\[('|"|`)Do\1,('|"|`)not\2,('|"|`)fear\3,('|"|`)the\4,('|"|`)dandy\5,('|"|`)lion\6\],('|"|`)dandy\7/);
```
# --seed--
## --seed-contents--
```js
function printCharacters(str) {
for (const char of str) {
console.log(char);
}
}
printCharacters("hello");
function getMatchedWordCount(sentence, match) {
let count = 0;
for (const word of sentence) {
if (word === match) {
count++;
}
console.log(`Checking "${word}" against "${match}" | Running count: ${count}`);
}
return count;
}
console.log(
getMatchedWordCount(
["I", "really", "really", "really", "like", "to", "code"],
"really"
)
);
--fcc-editable-region--
--fcc-editable-region--
```
# --solutions--
```js
function printCharacters(str) {
for (const char of str) {
console.log(char);
}
}
printCharacters("hello");
function getMatchedWordCount(sentence, match) {
let count = 0;
for (const word of sentence) {
if (word === match) {
count++;
}
console.log(`Checking "${word}" against "${match}" | Running count: ${count}`);
}
return count;
}
console.log(
getMatchedWordCount(
["I", "really", "really", "really", "like", "to", "code"],
"really"
)
);
console.log(
getMatchedWordCount(
["Do", "not", "fear", "the", "dandy", "lion"],
"dandy"
)
);
```
@@ -0,0 +1,81 @@
---
id: 6978bd465213832659f3338b
title: Step 9
challengeType: 1
dashedName: step-9
---
# --description--
Now, inside the loop, log the following template literal to the console: `Checking "${word}" against "${match}" | Running count: ${count}`
# --hints--
You should have exactly one `console.log` statement inside the loop in your `getMatchedWordCount` function.
```js
const codeWithoutJSComments = __helpers.removeJSComments(code);
const normalizedCode = __helpers.removeWhiteSpace(codeWithoutJSComments);
const functionRegex = /(functiongetMatchedWordCount\(sentence,match\)\{\S*?returncount;\})/;
const normalizedFunctionCode = normalizedCode.match(functionRegex)[1];
console.log(normalizedFunctionCode);
assert.match(normalizedFunctionCode, /for\(constwordofsentence\)\{\S*?console\.log\(\S*?\)\S*?\}/);
assert.notMatch(normalizedFunctionCode, /for\(constwordofsentence\)\{\S*?(console\.log\(\S*?\))\S*?(console\.log\(\S*?\))\S*?\}/);
```
Your `console.log` statement inside the loop in your `getMatchedWordCount` function should use a template literal with the following placeholders: `${word}`, `${match}`, and `${count}`.
```js
const codeWithoutJSComments = __helpers.removeJSComments(code);
const normalizedCode = __helpers.removeWhiteSpace(codeWithoutJSComments);
const functionRegex = /(functiongetMatchedWordCount\(sentence,match\)\{\S*?returncount;\})/;
const normalizedFunctionCode = normalizedCode.match(functionRegex)[1];
const consoleLogArguments = normalizedFunctionCode.match(/for\(constwordofsentence\)\{\S*?console\.log\((\S*?)\)\S*?\}/)[1]
const templateLiteral = consoleLogArguments.match(/(^\`\S*\`$)/)[1];
assert.include(templateLiteral, "${word}");
assert.include(templateLiteral, "${match}");
assert.include(templateLiteral, "${count}");
```
When the `getMatchedWordCount` function is called with `(['foo'], 'bar')`, the `console.log()` statement inside the function should be called once with the string `Checking "foo" against "bar" | Running count: 0`. Instead, it was called with each string in the following comma-separated list: `--fcc-actual--`.
```js
const consoleLogSpy = __helpers.spyOn(console, 'log');
getMatchedWordCount(['foo'], 'bar');
assert.deepEqual(consoleLogSpy.calls, [['Checking "foo" against "bar" | Running count: 0']]);
consoleLogSpy.restore();
```
# --seed--
## --seed-contents--
```js
function printCharacters(str) {
for (const char of str) {
console.log(char);
}
}
printCharacters("hello");
function getMatchedWordCount(sentence, match) {
let count = 0;
for (const word of sentence) {
--fcc-editable-region--
--fcc-editable-region--
}
return count;
}
console.log(
getMatchedWordCount(
["I", "really", "really", "really", "like", "to", "code"],
"really"
)
);
```
@@ -0,0 +1,22 @@
{
"isUpcomingChange": false,
"dashedName": "workshop-word-counter",
"helpCategory": "JavaScript",
"blockLayout": "challenge-grid",
"challengeOrder": [
{ "id": "691f7bbf76229bb97c60827d", "title": "Step 1" },
{ "id": "6920c9cda1c7f056f75e4b1a", "title": "Step 2" },
{ "id": "6920d189e36b6b60a6330fee", "title": "Step 3" },
{ "id": "6920dae65fb8bb6949bcdaba", "title": "Step 4" },
{ "id": "6921d688b8d58581d34459b9", "title": "Step 5" },
{ "id": "6921d90b5ce50185505d2004", "title": "Step 6" },
{ "id": "6921dae10700e300f26994e3", "title": "Step 7" },
{ "id": "6921de8d2a111d0341cff702", "title": "Step 8" },
{ "id": "6978bd465213832659f3338b", "title": "Step 9" },
{ "id": "6921e10ae2681205b516fbe6", "title": "Step 10" },
{ "id": "69220a03729c43082ad3e9f6", "title": "Step 11" }
],
"blockLabel": "workshop",
"usesMultifileEditor": true,
"hasEditableBoundaries": true
}
@@ -97,6 +97,7 @@
"dashedName": "javascript-loops",
"blocks": [
"lecture-working-with-loops",
"workshop-word-counter",
"workshop-sentence-analyzer",
"workshop-space-mission-roster",
"workshop-heritage-library-catalog",