mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-05-11 00:10:40 +00:00
## Summary Adds `.claude/skills/reviewing-unit-tests/SKILL.md`, a project-scoped agent skill that gives reviewing agents a concrete rubric for evaluating Vitest unit-test diffs. ## Changes - **What**: New skill at `.claude/skills/reviewing-unit-tests/SKILL.md`. Mirrors the structural template of the peer skill `.claude/skills/writing-playwright-tests/SKILL.md` (frontmatter shape, table-driven sections, terse prescriptive prose). Description triggers on PR review of `*.test.ts` files. Codifies: alias-by-renaming detection, `vi.mocked()` scope rule, unnecessary-cast detection, divergence checks against `docs/testing/unit-testing.md` / `store-testing.md` / `component-testing.md` / `vitest-patterns.md`, redundant `mockClear()` flag, `vue-i18n`-must-not-be-mocked rule (with `testI18n` reference), bugfix regression-validity check, leading-close framing flag, testing-library requirement for new component tests. ## Review Focus - **Retrospective validation against PR #11737** (head SHA `4573d62450fd12ac6f06e5e491f8af84ccbd27de`, file `src/platform/assets/composables/useMediaAssetActions.test.ts`). Mentally running the skill's checklist against that diff flags: 1. ✅ `getToastAddMock` / `getI18nTMock` module-level helpers — caught by the "Renaming ≠ Restructuring" section + Mocking Smells row "Module-level helper functions wrapping mocked composable returns". 2. ✅ `as ReturnType<typeof vi.fn>` casts in those helpers — caught by the "`vi.mocked()` Scope" section + Mocking Smells row on stray casts. 3. ✅ `vi.mock('vue-i18n', ...)` — caught by Mocking Smells row directing to mount real `createI18n` per `docs/testing/vitest-patterns.md` and the shared `testI18n` in `src/components/searchbox/v2/__test__/testUtils.ts`. 4. ✅ `vi.mock('primevue/usetoast', ...)` — explicitly **not** flagged. The "Distinguish" paragraph after the Mocking Smells table marks trivially-shaped third-party hooks as acceptable to mock; "Don't Mock What You Don't Own" applies to behavior-rich APIs, not single-method composables. - **Reference style**: links to `docs/testing/*.md` and `docs/guidance/typescript.md` instead of restating their content, per the FE-511 acceptance criterion. - **Open question**: scope. The repo-local skill is the FE-511 deliverable; equivalent prescriptive rules already live in the user's global `~/.config/amp/AGENTS.md` "Code Review Rigor" section as a separate artifact. No duplication intended — the skill is the project-scoped surface a reviewing agent loads when entering this repo. Closes FE-511 ┆Issue is synchronized with this [Notion page](https://app.notion.com/p/PR-11777-docs-add-reviewing-unit-tests-skill-3526d73d365081848759e6a8fab942f0) by [Unito](https://www.unito.io) --------- Co-authored-by: Amp <amp@ampcode.com>
12 KiB
12 KiB
name, description
| name | description |
|---|---|
| reviewing-unit-tests | Use when reviewing Vitest unit-test diffs in ComfyUI_frontend, especially new mocks, store tests, component tests, or bugfix regression tests. |
Reviewing Unit Tests for ComfyUI_frontend
Overview
Review for behavior and current repo rules, not motion. Compare to authoritative rules, not prior diffs or legacy snippets.
Review Workflow
- Identify the test type: component, store, composable, util, or bugfix regression.
- Name the behavior the test proves. If you cannot say it in one sentence, request changes.
- Open the authoritative doc section before judging structure.
- Scan the red flags below.
- State the verdict first. Name the failure mode. Cite the doc or rule.
Source of Truth / Precedence
When docs and examples conflict, use this order:
- Explicit repo rules, lint rules, and note blocks.
docs/testing/vitest-patterns.md- Rule sections in
docs/testing/unit-testing.md,docs/testing/store-testing.md, anddocs/testing/component-testing.md - Example snippets
- Prior diffs
Apply these repo-specific clarifications:
docs/testing/component-testing.mdstarts with the authoritative rule: new component tests use@testing-library/vuewith@testing-library/user-event. The@vue/test-utilssnippets below it are legacy examples.docs/testing/store-testing.mdstill containsas anyexamples. Treat them as legacy snippets, not approval for new or edited test code.- If docs conflict, prefer the stricter newer rule and call out the doc ambiguity. Do not approve through it.
- Motion != fix.
30-Second Red Flags
| If you see... | Failure mode | Default action |
|---|---|---|
New @vue/test-utils import in a new component test |
legacy test API | Request changes |
vi.mock('vue-i18n', ...) |
mocked i18n | Request changes |
as any, @ts-expect-error, as Mock, as ReturnType<typeof vi.fn>, as unknown as X |
unnecessary cast or type escape | Request changes unless the author proves no safer type exists |
getXMock(), renamed wrapper, or helper that only returns a mocked value |
alias-by-renaming | Request changes |
beforeEach recreates the return object for a module-mocked composable or service |
shared mock setup drift | Request changes |
| Assertions only check defaults, mock plumbing, or CSS hooks | non-behavioral test | Request changes |
| Bugfix test has no proof it fails on pre-fix code | unproven regression | Request changes |
Rationalization Table
| Excuse | Reality |
|---|---|
| "I restructured the mocks" | If the indirection stayed, nothing improved. Flag alias-by-renaming. |
| "The docs do it" | Rule, note, and lint beat legacy snippet. Compare to the current rule, not the nearest example. |
| "TypeScript required the cast" | vi.mocked() usually narrows mock methods. Assertion-only references need no cast. |
"Putting it in beforeEach is DRY" |
Recreating module mock state in hooks hides singleton behavior and drifts from the documented pattern. |
| "It is only a nit" | Explicit repo-rule violations are never nits. |
| "No behavior changed, just cleanup" | Motion != fix. Ask what behavior got stronger. |
| "Mental revert is enough" | For bugfix tests, establish red on pre-fix code or ask the author to show it. |
Mocking Rules
- Fail helpers that do not remove repeated setup, encode domain meaning, or simplify assertions. Barely earning the abstraction is not enough.
- For composables with reactive or singleton state, define stable mock state inside the
vi.mock()factory. Access it per test via the composable itself. Seedocs/testing/unit-testing.md"Mocking Composables with Reactive State". - This does not ban local test data builders or per-test
vi.spyOn(...). - Mock seams, not the project-owned module you are trying to exercise. For store tests, prefer real Pinia plus
createTestingPinia({ stubActions: false })perdocs/testing/vitest-patterns.mdanddocs/testing/store-testing.md.
Alias-by-Renaming
// Before
const mockAdd = vi.hoisted(() => vi.fn())
// After: same indirection, new name
function getToastAddMock() {
return useToast().add
}
If the wrapper only renames or relays a mocked value, fail it. Inline the lookup at the call site or fetch the singleton mock via the documented pattern.
vi.mocked() Scope
| Use case | vi.mocked() required? |
|---|---|
.mockReturnValue, .mockResolvedValue, .mockImplementation |
Yes |
.mock.calls, .mock.results |
Yes |
expect(fn).toHaveBeenCalled() |
No |
expect(fn).toHaveBeenCalledWith(...) |
No |
- Flag casts whenever
vi.mocked()would narrow correctly. - Do not add
vi.mocked()around assertion-only references just for style.
Reset Hygiene
- Flag per-mock
mockClear()ormockReset()whenvi.clearAllMocks()orvi.resetAllMocks()already runs in the relevant hook chain. - Review for redundancy or broken state management. Do not bikeshed
clearAllMocksvsresetAllMocksunless behavior depends on it.
Third-Party Seams
- Distinguish trivial hooks from behavior-rich APIs.
- Mocking single-method third-party hooks like
primevue/usetoastis usually acceptable. - That exception does not justify mocking behavior-rich third-party modules.
vue-i18n
- Never mock
vue-i18nin component tests. - Use real
createI18nperdocs/testing/vitest-patterns.mdand the sharedtestI18nsetup.
Test-Body Rules
| Smell | Review bar |
|---|---|
| Change-detector test | Reject. Default values alone prove nothing. |
| Mock-only assertion | Accept collaborator-call assertions only when the call is the meaningful external effect and the test also exercises the triggering behavior. |
| Non-behavioral assertion | Reject tests that only check classes, utility hooks, or styling internals. |
New component test using @vue/test-utils |
Request changes. Use @testing-library/vue plus @testing-library/user-event. |
any, as any, or @ts-expect-error in new or edited test code |
Request changes unless the author proves no safer type exists. Legacy doc snippets do not authorize it. |
Bugfix Regression Proof
For fix: PRs or bugfix diffs:
- Identify the production change that fixes the bug.
- Verify the new test fails on pre-fix code, or ask the author to show it.
- If the test passes on broken code, request changes.
A regression test that never proves red does not pin the bug.
Review Output Rules
- State verdict before procedural questions.
- Do not lead with approval language like
LGTM, just one nitorapprove and move on?. - Name the failure mode directly:
alias-by-renaming,unnecessary cast,mocked i18n,mock-only assertion,unproven regression. - Link the authoritative doc section in the review comment.
- If an explicit repo rule, lint rule, or authoritative doc note is violated, do not downgrade it to "minor deviation" or "nit".
Quick Reference
| When you see... | Read this |
|---|---|
New vi.mock(...) for a composable |
docs/testing/unit-testing.md -> "Mocking Composables with Reactive State" |
| New store test or store mock | docs/testing/vitest-patterns.md setup + docs/testing/store-testing.md |
| New component test | Top note in docs/testing/component-testing.md |
vue-i18n in a component test |
docs/testing/vitest-patterns.md + src/components/searchbox/v2/__test__/testUtils.ts |
| Cast around a mock | docs/guidance/typescript.md -> "Type Assertion Hierarchy" |
Key Files to Read
| Purpose | Path |
|---|---|
| Composable mocking patterns | docs/testing/unit-testing.md |
| Store testing patterns | docs/testing/store-testing.md |
| Repo-wide Vitest setup defaults | docs/testing/vitest-patterns.md |
| Component testing rule for new tests | docs/testing/component-testing.md |
| Real i18n setup | src/components/searchbox/v2/__test__/testUtils.ts |