先读题:三个任务、一条硬约束、四个测试Read the question first: three tasks, one rule you must not break, four tests
在写第一行代码之前,把题目、约束和判卷标准全部摸清。Before writing a single line of code, get clear on the task, the constraints, and how it is graded.
这一页有什么On this page7
- 01 题目原文The task text as it is given
- 02 用初学者能看懂的话重写一遍The same task written in plainer words
- 03 「不得修改任何 data-testid」具体是什么意思What do not change any data-testid actually means
- 04 先跑一遍测试,拿到基线Run the tests first, to get a baseline
- 05 三道题的落点:一个文件,三个函数Where the three tasks land: one file, three functions
- 练习 · 动手做Practice
- 迁移模式Transfer
- 用自己的话复述三个 Task 的验收标准Restate the acceptance criteria of the three tasks in your own words
- 知道「不得修改任何 data-testid」具体意味着什么不能动Know exactly what you may not touch when the task says do not change any data-testid
- 会跑测试,并且知道跑的是哪四条Run the tests, and know which four tests are running
- 认出题目里没写但测试在查的那一条Spot the one requirement the task text leaves out but the tests still check
这一节本身就是考点。考场上最贵的错误不是写错代码,是「没读清题就开始写」——比如把删除写成按 title 删、把更新写成删了再加。This lesson is itself part of the exam. The most expensive mistake is not bad code, it is starting to write before you have read the task properly: deleting by title instead of by id, or updating an item by removing it and adding it again.
react-notes-app/README.md三个 Task 的原文与约束The three tasks as written, with their constraints
react-notes-app/README.mdreact-notes-app/src/NoteManager.test.tsx四个判卷测试The four tests that decide the marks
react-notes-app/src/NoteManager.test.tsxreact-notes-app/src/components/NoteManager/index.tsx三道题的落点
提醒:源项目在磁盘上是做完的版本 —— 下面就是答案。想自己先写一遍的话,现在关上。Heads up: on disk this project is the finished version — what follows is the answer. Close this if you want to write it yourself first.
react-notes-app/src/components/NoteManager/index.tsx题目原文The task text as it is given
先看没有加工过的版本。Read the version that has not been reworded yet.
这是 react-notes-app/README.md 里 Q1 部分的原文, 一个字没改:
This is the Q1 section of react-notes-app/README.md, word for word:
react-notes-app/README.md用初学者能看懂的话重写一遍The same task written in plainer words
题目里每个词都是要求。挑出来逐条对应。Every word in the task is a requirement. Pull them out one at a time.
| 题目里的词 | 它到底在要求什么 | 对应的技术动作 |
|---|---|---|
| 「新 note 进入表格」 | 列表末尾多一条,原有的都还在 | [...prev, note] |
| 「按 id 被移除」 | 比较的依据必须是 id,不是 title、不是下标。 同名笔记只删对的那条。 | filter(n => n.id !== id) |
| 「内容回填进表单」 | 输入框里出现这条笔记原有的 title 和 content | useEffect(…, [noteToEdit]) |
| 「按钮变 Update」 | 按钮文字从 Add 变成 Update,字面一致 | {noteToEdit ? "Update" : "Add"} |
| 「原位置更新」 | 改完这条还在原来那一行,顺序不变。这排除了「先删再加」的写法。 | map(…) 三元替换 |
| 「退出编辑模式」 | 提交后按钮回到 Add,表单清空,不再处于编辑态 | setNoteToEdit(null) |
「按 id」和「原位置」是这道题真正的分水岭。两个要求都指向同一件事:出题人想看你会不会用filter 和 map 精确操作数组, 而不是用「删掉再塞回去」这种糊弄过去的写法。
| The words in the brief | What they actually demand | The technical move |
|---|---|---|
| “the new note enters the table” | One more row at the end, everything already there stays | [...prev, note] |
| “removed by id” | The comparison has to be the id, not the title, not the index. With two same-named notes, only the right one goes. | filter(n => n.id !== id) |
| “content filled back into the form” | The inputs show this note’s existing title and content | useEffect(…, [noteToEdit]) |
| “the button turns into Update” | The button text goes from Add to Update, spelled exactly so | {noteToEdit ? "Update" : "Add"} |
| “updated in place” | After the edit it is still on the same row, order unchanged. This rules out delete-then-append. | map(…) with a ternary |
| “leave edit mode” | After submit the button is Add again, the form is empty, and you are no longer editing | setNoteToEdit(null) |
“By id” and “in place” are the real dividing lines here. Both point at the same thing: the author wants to see whether you can work on an array precisely with filter and map, instead of fudging it with “delete it and stuff it back in”.
「不得修改任何 data-testid」具体是什么意思What do not change any data-testid actually means
不只是「别改那串字符」,还包括「别让那个元素消失」。It is not only do not change that string. It also means do not let that element disappear.
项目里一共 6 个 data-testid, 测试全靠它们定位元素:
| testid | 在哪个元素上 | 测试用它做什么 |
|---|---|---|
note-manager | NoteManager 最外层 div | (当前测试没直接用) |
note-form | form 元素 | (当前测试没直接用) |
form-input | 标题输入框 | 往里打字、clear 后重打 |
form-textarea | 内容文本域 | 往里打字 |
form-submit-button | 提交按钮 | 点击、断言 disabled、断言文字是 Update |
notes-list | tbody | 断言它的 textContent 含 / 不含某段文字 |
延伸出三条不能碰的红线:
- 不能改字符串。
form-input改成title-input, 测试getByTestId("form-input")直接抛错。 - 不能让元素条件性消失。比如给空列表加一个「暂无笔记」的分支、把
tbody整个换掉 ——getByTestId("notes-list")会找不到元素而抛错,而第 3 个测试恰恰要在删除后断言它。 - 行内按钮的文字也是隐性契约。测试用
getByRole("button", { name: "Delete" })定位,所以Delete/Edit这两个词也不能改(改成 Remove 就找不到了)。 这一条 README 没写,只能从测试里读出来。
The project has 6 data-testid attributes in total, and the tests find every element through them:
| testid | Which element it sits on | What the tests do with it |
|---|---|---|
note-manager | The outermost div of NoteManager | (not used directly by the current tests) |
note-form | The form element | (not used directly by the current tests) |
form-input | The title input | Type into it, clear it and type again |
form-textarea | The content textarea | Type into it |
form-submit-button | The submit button | Click it, assert disabled, assert the text is Update |
notes-list | The tbody | Assert its textContent does / does not contain a piece of text |
Three red lines follow from that:
- You cannot change the string. Rename
form-inputtotitle-inputandgetByTestId("form-input")throws on the spot. - You cannot let the element disappear conditionally. Say you add a “no notes yet” branch for the empty list, or swap out the whole
tbody—getByTestId("notes-list")finds nothing and throws, and the third test asserts on it right after a delete. - The inline button text is an implicit contract too. The tests locate them with
getByRole("button", { name: "Delete" }), so the wordsDelete/Editare frozen as well (rename one to Remove and it is gone). The README does not say this; you can only read it out of the tests.
先跑一遍测试,拿到基线Run the tests first, to get a baseline
改代码之前先知道现在是什么状态 —— 这个习惯值几十分。Know where you stand before you change any code. This habit is worth a lot of points.
上一门课讲过:这个项目没有 test script,npm test 会报 Missing script。要用 npx。
本机实测的结果如下 —— 注意这是项目当前磁盘状态的结果。 如果你拿到的是挖空版(TODO 还在), 这里会看到 3 个失败、1 个通过(只有 disabled 那条会过)。
An earlier module said it: this project has no test script, so npm test reports Missing script. Use npx.
Here is what it printed on this machine — note that this is the result for the project as it sits on disk. If what you got is the hollowed-out version with the TODOs still in place, you would see 3 failures and 1 pass (only the disabled one gets through).
react-notes-app三道题的落点:一个文件,三个函数Where the three tasks land: one file, three functions
先把要改的地方框出来,再动手。Mark the places you have to change, then start writing.
NoteForm、NoteTable、NoteItem 三个文件都不需要改 —— 它们已经完整了。所有逻辑都落在NoteManager 的三个 handler 上:
handleSubmitNote→ Task 1 + Task 3 的后半handleDelete→ Task 2handleEdit→ Task 3 的前半
下面是这个文件的完整最终形态(也就是参考答案)。先别细看 —— 接下来三节会一题一题推导出来。 现在只要看清「结构长什么样」。
NoteForm, NoteTable and NoteItem need no changes at all — they are already complete. All the logic lands on the three handlers inside NoteManager:
handleSubmitNote→ Task 1 plus the back half of Task 3handleDelete→ Task 2handleEdit→ the front half of Task 3
Below is the finished shape of that file — the reference answer. Do not study it yet — the next three lessons derive it one task at a time. For now just see what the structure looks like.
react-notes-app/src/components/NoteManager/index.tsx动手做Get your hands on it
填空只是过渡。真正掌握的标准,是在没有答案的时候从头写出来 —— 所以做完 L2 之后一定要往 L3、L4 走。Filling blanks is a stepping stone. The real bar is writing it from nothing, so once L2 is comfortable, push on to L3 and L4.
README 只说了「不得修改任何 data-testid」。 下面哪些改动也会让现有测试失败?(多选)
The README says only that no data-testid may be changed. Which of the changes below also break the existing tests? (more than one)
这题是多选。More than one answer is correct.
拿到这个项目,最合理的动作顺序是什么?
You just received this project. What is the most sensible order to work in?
换一道题也能用Works on other problems too
考试不会原题重考。真正能带走的是「看到这种信号 → 伸手去拿这个解法」。The exam will not reuse the same question. What you take away is the reflex: see this signal, reach for that solution.
- 三个 Task 的分水岭是「按 id」和「原位置」两个词。The two phrases that separate the three tasks are by id and in place.
- data-testid 不能改字符串,也不能让那个元素条件性消失。You may not change a data-testid string, and you may not let that element disappear under some condition.
- 行内按钮文字 Delete / Edit / Update 是测试依赖的隐性契约。The row button labels Delete, Edit, and Update are an unwritten contract the tests depend on.
- 第 2 个测试查的 disabled 是 README 没写的要求 —— 测试也是题面。The disabled check in the second test is a requirement the README never states. The tests are part of the task too.
- 三道题全部落在 NoteManager 的三个 handler 上,其余三个组件不用改。All three tasks land on three handlers in NoteManager. The other three components need no changes.