[Molecule-Platform-Evolvement-Manager]
Closes the third item from #2071 (Canvas test gaps follow-up). Builds
on the A2AEdge tests in PR #2143.
10 cases across 4 buckets:
**Render (2):**
- Default pill with `Cancel (N)` text + correct ARIA label
- Confirm dialog NOT visible until pill click
**Pill click (3):**
- Click flips to confirming view + stops propagation (so React Flow
doesn't interpret the click as a node selection)
- Confirm copy pluralizes correctly: count=1 → "Delete 1 workspace?",
count>1 → "Delete N workspaces?". Negative assertion guards against
the wrong-form regressing in either direction.
**No / cancel-confirm (1):**
- Click No → returns to pill, no API call, no store mutation
**Yes / cascade-delete (4):**
- Happy path: beginDelete locks the WHOLE subtree (root + children,
NOT unrelated workspace) → api.del("/workspaces/<id>?confirm=true")
→ optimistic store filter strips subtree, keeps unrelated → success
toast → endDelete in finally
- WS-event race: WS_REMOVED handler clears the root mid-flight. The
bail-out branch (`!postDeleteState.nodes.some(n => n.id === rootId)`)
must NOT then run a second optimistic filter. Pre-fix the post-await
subtree walk would miss any orphaned descendants whose parentId got
reparented upward by handleCanvasEvent — pinned now.
- Error path: api.del rejects → endDelete UNDOes the lock + error
toast surfaces the message → subtree STAYS in the store so the user
can retry / interact with the still-deploying nodes
- Non-Error rejection (e.g. string thrown directly): toast surfaces
the canned "Cancel failed" fallback instead of attempting `.message`
## Mocking
- `@/lib/api`, `@/components/Toaster`: simple spy mocks
- `@/store/canvas`: object that satisfies BOTH the selector pattern
(`useCanvasStore(s => s.x)`) AND `getState()` / `setState()` since
the cascade-delete handler walks the subtree via `getState()` and
mutates via `setState()` for the optimistic removal. `vi.hoisted`
preserves referential identity so the mock fns wired into the
state object are observed by every consumer.
## Test plan
- [x] All 10 cases pass locally (`vitest run OrgCancelButton.test.tsx` — ~990ms)
- [x] No changes to the SUT — pure additive coverage
- [ ] CI green
## #2071 progress after this PR
- [x] useTemplateDeploy (PR #2121)
- [x] A2AEdge (PR #2143)
- [x] OrgCancelButton (this PR)
- [ ] useDragHandlers — separate PR
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>