Appearance
1.4 用 Mock 数据跑通首屏
AI CODING · STAGE 1 · MOCK FIRST SCREEN
先让 domain test 失败,再补最小实现。规则变绿后接 component 和 page,最后看 diff、跑全量检查、走四条 H5 路径。
时间 2–3 小时Mock 5 records验收 11 tests + 4 paths
RED
test 指向缺失的 module / export保存真实失败输出
import 或环境错误先修
GREEN
stages 4 passed · recommendations 7 passedH5 build exit 0
四条页面路径结果正确
确认工作分支
bash
git branch --show-current
git status --short当前 branch 应为 learner/week-01。开始前 working tree 应为空。若还在 practice/ai-workflow,回到 0.4 重新从 starter 建 branch。
本节的顺序固定:
text
stage test RED
→ stage implementation GREEN
→ recommendation test RED
→ recommendation domain + data GREEN
→ component + page
→ full test + build + browser先完成 domain 和 data,再执行 1.3 的 component 任务卡。收藏、persistence、API 和 AI 解释不进入这次 diff。
Stage RED
先创建 apps/web/src/domain/stages.test.ts,写四项:
STAGES的 ID 唯一。isStageId接受pregnancy、baby-0-6、baby-6-12、toddler-1-3。isStageId拒绝 contract 之外的future-stage。getStage("baby-6-12").name等于“宝宝 6–12 月”。
Implementation 还不存在。先看 test diff,再运行:
bash
git add apps/web/src/domain/stages.test.ts
git diff --cached -- apps/web/src/domain/stages.test.ts
npm run test:web -- stages.test.ts合理的 RED 是找不到 ./stages,或所需 export 尚未定义。拼错 import、Node 版本错误或 dependency 安装失败不算业务 RED。
保留 terminal 中第一处失败。代码块里的“期望失败”不能代替实际输出。
Stage test 关键部分
ts
import { describe, expect, test } from "vitest";
import { getStage, isStageId, STAGES } from "./stages";
describe("stages", () => {
test("has unique stage ids", () => {
const ids = STAGES.map((stage) => stage.id);
expect(new Set(ids).size).toBe(ids.length);
});
test("accepts all canonical stage ids", () => {
expect(isStageId("pregnancy")).toBe(true);
expect(isStageId("baby-0-6")).toBe(true);
expect(isStageId("baby-6-12")).toBe(true);
expect(isStageId("toddler-1-3")).toBe(true);
});
test("rejects an unknown stage id", () => {
expect(isStageId("future-stage")).toBe(false);
});
test("gets the baby 6–12 month stage", () => {
expect(getStage("baby-6-12").name).toBe("宝宝 6–12 月");
});
});第一次运行时,真实 RED 通常接近:
text
FAIL src/domain/stages.test.ts
Error: Failed to load url ./stages
Test Files 1 failed若输出是 npm: command not found、unsupported Node version 或 lockfile 安装失败,先修环境。那不是测试证明的业务缺口。
Stage GREEN
创建 apps/web/src/domain/stages.ts:
- 导出四个固定阶段,顺序与 1.1 一致。
- 从
STAGES推导StageId,不要写成string。 isStageId(value)使用 TypeScript type predicate。getStage(id)返回匹配阶段。
运行同一条定向测试:
bash
npm run test:web -- stages.test.ts预期 4 passed。如果把 StageId 写成 string,runtime test 可能通过,调用方却失去类型收窄。看类型定义,不只看绿灯。
最小实现不需要 class 或 store:
ts
export const STAGES = [
{ id: "pregnancy", name: "孕期", shortName: "孕期" },
{ id: "baby-0-6", name: "宝宝 0–6 月", shortName: "0–6 月" },
{ id: "baby-6-12", name: "宝宝 6–12 月", shortName: "6–12 月" },
{ id: "toddler-1-3", name: "宝宝 1–3 岁", shortName: "1–3 岁" },
] as const;
export type StageId = (typeof STAGES)[number]["id"];
export type Stage = (typeof STAGES)[number];
export function isStageId(value: string): value is StageId {
return STAGES.some((stage) => stage.id === value);
}getStage 仍应按 ID 查找,并在 impossible state 抛出包含 ID 的错误。不要为了避免 throw 把返回值改成 Stage | undefined;调用方传入的是已经收窄的 StageId。
Recommendation RED
创建 apps/web/src/domain/recommendations.test.ts。它应覆盖:
baby-6-12返回两条推荐。- 空输入返回空数组。
- 能按 ID 找到“南瓜泥入门”。
- 未知 ID 返回空结果。
- 5 个 recommendation ID 唯一。
- Mock 数据保持不可变。
- “南瓜泥入门”保留可执行的注意事项。
此时不要创建 recommendation implementation 或 Mock data。先暂存 test 并运行:
bash
git add apps/web/src/domain/recommendations.test.ts
git diff --cached -- apps/web/src/domain/recommendations.test.ts
npm run test:web -- recommendations.test.ts合理的 RED 是缺少 recommendations module 或 export。保留实际失败输出。
两条最能锁住用户行为的 test 是:
ts
test("returns two recommendations for babies 6–12 months old", () => {
const results = recommendationsForStage(recommendations, "baby-6-12");
expect(results).toHaveLength(2);
expect(results.every((item) => item.stageIds.includes("baby-6-12"))).toBe(true);
});
test("returns undefined for an unknown recommendation id", () => {
expect(findRecommendation(recommendations, "rec-unknown")).toBeUndefined();
});第一条对应首页默认结果,第二条对应详情空状态。其余五条检查数据安全和内容契约,不需要在 page 再写一遍。
Recommendation GREEN
依次创建:
text
apps/web/src/domain/recommendations.ts
apps/web/src/data/recommendations.tsDomain 文件定义 Recommendation,实现按 StageId 筛选和按 recommendation ID 查找。Data 文件保存 5 条 Mock 内容,并引用 domain type。
推荐项使用 stageIds。首页不再写一份筛选条件,component 也不判断推荐属于哪个阶段。
bash
npm run test:web -- recommendations.test.ts预期 7 passed。若为了变绿删除 assertion、放宽数量或把数据复制进 test,退回修改。
Domain 只保留纯函数:
ts
export function recommendationsForStage(
items: readonly Recommendation[],
stageId: StageId,
): readonly Recommendation[] {
return items.filter((item) => item.stageIds.includes(stageId));
}
export function findRecommendation(
items: readonly Recommendation[],
id: string,
): Recommendation | undefined {
return items.find((item) => item.id === id);
}Mock data 的单条结构如下。五条内容都使用同一接口,不为某个页面增加特有字段:
ts
{
id: "rec-pumpkin-puree",
stageIds: ["baby-6-12"],
category: "food",
title: "南瓜泥入门",
summary: "蒸熟南瓜后压成细泥,少量单一食材开始尝试。",
reason: "质地柔软、味道清甜,便于观察宝宝对新食材的接受度。",
notice: "首次少量尝试;出现皮疹、呕吐、腹泻等不适时暂停添加;症状明显、持续或呼吸困难时及时就医。",
}测试要求 dataset、item 和嵌套的 stageIds 都不可变。只冻结最外层数组不够。
接 component 和 page
依次执行 1.3 保存的 component 任务卡和详情页任务卡。第一张卡的 Allowed files 只有:
text
apps/web/src/components/StagePicker.vue
apps/web/src/components/RecommendationCard.vue
apps/web/src/pages/index/index.vueStagePicker 接收 stages、modelValue,发出 update:modelValue(StageId)。RecommendationCard 接收 item,发出 open(recommendationId)。首页持有状态、调用筛选函数并处理导航。
两张任务卡分开执行。不要把 component、首页、详情页和 pages.json 合并成一次无法定位问题的大改动。
首页接线的关键行只有这些:
ts
const selectedStage = ref<StageId>("baby-6-12");
const visibleRecommendations = computed(() =>
recommendationsForStage(RECOMMENDATIONS, selectedStage.value),
);
function openRecommendation(id: string) {
uni.navigateTo({ url: `/pages/detail/index?id=${encodeURIComponent(id)}` });
}vue
<StagePicker v-model="selectedStage" :stages="STAGES" />
<RecommendationCard
v-for="item in visibleRecommendations"
:key="item.id"
:item="item"
@open="openRecommendation"
/>详情页只负责读 query 和调用查找函数:
ts
const recommendationId = ref("");
const item = computed(() =>
findRecommendation(RECOMMENDATIONS, recommendationId.value),
);
onLoad((query) => {
recommendationId.value = typeof query?.id === "string" ? query.id : "";
});展示分支使用 v-if="item";空分支显示固定文案。不要用可选链把整个页面静默渲染为空白。
先看 Diff,再跑全量
bash
git diff --stat
git diff检查这些问题:
- 是否出现 allowed files 以外的 package、config 或 lockfile 改动。
- 阶段常量是否只在
domain/stages.ts。 - 筛选和查找是否只在 domain。
- component 是否只通过 props / events 通信。
- 详情页是否读取
query.id,而不是复制 Mock 数据。 - test expectation 是否保持原契约。
Diff 没有越界后再跑:
bash
npm run test:web
npm run build:web预期 11 项测试通过,H5 build exit 0。
教学仓 checkpoint 的关键输出如下,版本号和耗时可能不同:
text
Test Files 2 passed (2)
Tests 11 passed (11)text
Compiler version: 5.03(vue3)
Compiling...
DONE Build complete.uni-app 有新版本 或 Vite CJS deprecation 是当前工具链 warning,不等于 build 失败。判断标准仍是 command exit code 和最后的 build 结果。
两个不能接受的“修复”
为了让数量测试变绿而改 expectation:
diff
-expect(results).toHaveLength(2)
+expect(results.length).toBeGreaterThan(0)这会把明确需求放宽成“有数据就行”。恢复原 assertion,修 Mock stageIds 或筛选函数。
为了让详情显示内容而在 page 复制数据:
diff
-const item = computed(() => findRecommendation(RECOMMENDATIONS, recommendationId.value))
+const item = computed(() => ({
+ id: recommendationId.value,
+ title: "南瓜泥入门",
+ reason: "...",
+ notice: "...",
+}))这绕过了 domain lookup,也让不存在 ID 永远无法进入空状态。详情页继续使用 findRecommendation。
浏览器走四条路径
启动 H5:
bash
npm run dev:web按顺序操作:
- 首次打开首页,默认选中“6–12 月”,列表显示两条推荐。
- 切换到“孕期”,列表只显示“产检资料收纳清单”。
- 打开“南瓜泥入门”,详情显示推荐依据和注意事项。
- 把地址中的详情 ID 改成不存在的值,页面显示“没有找到这条推荐”。
测试通过只说明 domain 规则没有跑偏。这四条路径检查 component event、页面跳转和详情查询。
再用 Tab 聚焦阶段按钮和推荐卡片,按 Enter、Space,行为应与点击一致。这是额外的 accessibility check,不替代四条主路径。
提交阶段 1
bash
git status --short
git add apps/web/src/domain/stages.test.ts \
apps/web/src/domain/stages.ts \
apps/web/src/domain/recommendations.test.ts \
apps/web/src/domain/recommendations.ts \
apps/web/src/data/recommendations.ts \
apps/web/src/components/StagePicker.vue \
apps/web/src/components/RecommendationCard.vue \
apps/web/src/pages/index/index.vue \
apps/web/src/pages/detail/index.vue \
apps/web/src/pages.json
git diff --cached重新读 cached diff。列表中出现不存在的文件,说明对应任务还没有完成;不要用 git add -A 把未知文件一起带进 commit。提交信息写清 domain 或用户链路,不用“AI 已完成”这类描述。提交后保存短 hash:
bash
git rev-parse --short HEAD常见失败定位
| 症状 | 先查 | 重跑 |
|---|---|---|
| test 找不到 module | test 的相对 import、文件名与 export | 对应定向 test |
| 默认数量或孕期数量不对 | Mock stageIds、筛选函数、首页默认阶段 | npm run test:web -- recommendations.test.ts |
| 点击卡片没有正确详情 | card 的 open event、首页的 id 导航 | npm run dev:web |
| 详情始终为空 | query.id 和 domain 查找函数 | 打开“南瓜泥入门” |
| H5 build 报多个错误 | terminal 第一处文件和行号 | npm run build:web |
先修第一处错误。不要修改 test expectation,别在 page 或 component 里复制数据绕过问题。
四条路径和全量检查都通过后,进入 阶段 2 · 前端业务闭环。