chore: merge executor worktree (10-05 Nyquist stubs)
This commit is contained in:
@@ -0,0 +1,96 @@
|
|||||||
|
---
|
||||||
|
phase: 10-ux-interaction
|
||||||
|
plan: "05"
|
||||||
|
subsystem: frontend-tests
|
||||||
|
tags: [wave-0, test-stubs, nyquist, ux, vitest]
|
||||||
|
dependency_graph:
|
||||||
|
requires: []
|
||||||
|
provides: [wave-0-stubs-ux-02-03-04-05-06-07-08-09-11-13-14]
|
||||||
|
affects: [plans/10-06, plans/10-07, plans/10-08, plans/10-09]
|
||||||
|
tech_stack:
|
||||||
|
added: []
|
||||||
|
patterns: [vitest-it-todo-stub-pattern, nyquist-red-before-green]
|
||||||
|
key_files:
|
||||||
|
created:
|
||||||
|
- frontend/src/components/storage/__tests__/StorageBrowser.skeleton.test.js
|
||||||
|
- frontend/src/components/layout/__tests__/AppSidebar.empty.test.js
|
||||||
|
- frontend/src/views/admin/__tests__/AdminAuditView.skeleton.test.js
|
||||||
|
- frontend/src/views/admin/__tests__/AdminUsersView.skeleton.test.js
|
||||||
|
- frontend/src/components/ui/__tests__/dropdown.test.js
|
||||||
|
- frontend/src/__tests__/keyboard.test.js
|
||||||
|
- frontend/src/components/layout/__tests__/OsDragOverlay.test.js
|
||||||
|
- frontend/src/components/storage/__tests__/StorageBrowser.dragmove.test.js
|
||||||
|
modified: []
|
||||||
|
decisions:
|
||||||
|
- "it.todo() stubs chosen over it.skip() — todo produces pending rather than skipped, giving clear Wave 1-4 promotion targets"
|
||||||
|
- "Separate describe blocks per requirement ID — each UX-XX maps to an isolatable test group"
|
||||||
|
metrics:
|
||||||
|
duration_minutes: 3
|
||||||
|
completed_date: "2026-06-15T18:13:21Z"
|
||||||
|
tasks_completed: 2
|
||||||
|
tasks_total: 2
|
||||||
|
files_created: 8
|
||||||
|
files_modified: 0
|
||||||
|
---
|
||||||
|
|
||||||
|
# Phase 10 Plan 05: Wave 0 xfail Test Stubs Summary
|
||||||
|
|
||||||
|
**One-liner:** 53 `it.todo` stubs across 8 files giving each of UX-02..09, UX-11, UX-13, UX-14 a promotion target before any Wave 1-4 implementation code is written.
|
||||||
|
|
||||||
|
## What Was Built
|
||||||
|
|
||||||
|
Created 8 Vitest stub test files using the Nyquist pattern (RED stubs before implementation). Each file contains `describe('UX-XX: ...')` blocks with `it.todo(...)` entries that Wave 1-4 implementing plans will promote to real assertions.
|
||||||
|
|
||||||
|
### Task 1: Skeleton + Dropdown Stubs (UX-02, UX-03, UX-04, UX-13, UX-14)
|
||||||
|
|
||||||
|
| File | Requirements | Stub Count |
|
||||||
|
|------|-------------|-----------|
|
||||||
|
| `StorageBrowser.skeleton.test.js` | UX-02, UX-13 | 7 |
|
||||||
|
| `AppSidebar.empty.test.js` | UX-03, UX-01 (micro), UX-14 | 10 |
|
||||||
|
| `AdminAuditView.skeleton.test.js` | UX-04, UX-01 (audit empty) | 5 |
|
||||||
|
| `AdminUsersView.skeleton.test.js` | UX-04 | 3 |
|
||||||
|
| `dropdown.test.js` | UX-13 | 4 |
|
||||||
|
|
||||||
|
**Task 1 commit:** `7597035` — 5 files, 29 it.todo stubs
|
||||||
|
|
||||||
|
### Task 2: Keyboard + OS-Drag + Drag-to-Move Stubs (UX-05..09, UX-11)
|
||||||
|
|
||||||
|
| File | Requirements | Stub Count |
|
||||||
|
|------|-------------|-----------|
|
||||||
|
| `keyboard.test.js` | UX-05, UX-06, UX-07, UX-08 | 11 |
|
||||||
|
| `OsDragOverlay.test.js` | UX-09 | 7 |
|
||||||
|
| `StorageBrowser.dragmove.test.js` | UX-11 | 6 |
|
||||||
|
|
||||||
|
**Task 2 commit:** `794ff42` — 3 files, 24 it.todo stubs
|
||||||
|
|
||||||
|
### Total
|
||||||
|
|
||||||
|
- **8 stub files** created
|
||||||
|
- **53 `it.todo` stubs** total
|
||||||
|
- **11 requirements** covered: UX-02, UX-03, UX-04, UX-05, UX-06, UX-07, UX-08, UX-09, UX-11, UX-13, UX-14
|
||||||
|
|
||||||
|
## Verification Results
|
||||||
|
|
||||||
|
- All 8 files exist at the specified paths
|
||||||
|
- Main repo test suite: 153/153 tests pass — no regressions
|
||||||
|
- Stub files contain no assertions; `it.todo()` produces "pending" status in Vitest, not failures
|
||||||
|
- Each requirement ID appears in at least one `describe('UX-XX: ...')` block
|
||||||
|
- Grep coverage count: 14 lines match `UX-0[23456789]|UX-1[1345]|UX-14` across all 8 files (minimum was 11)
|
||||||
|
|
||||||
|
## Deviations from Plan
|
||||||
|
|
||||||
|
None — plan executed exactly as written.
|
||||||
|
|
||||||
|
## Known Stubs
|
||||||
|
|
||||||
|
By design, all 53 test entries are `it.todo()` stubs. These are intentional Wave 0 placeholders — they exist specifically to be promoted to real `it(...)` assertions by the implementing waves (Waves 1-4, plans 10-06 through 10-11). They are not integration or correctness gaps.
|
||||||
|
|
||||||
|
## Threat Flags
|
||||||
|
|
||||||
|
None — this plan creates only test stub files with no source code changes, no new network surface, and no security-relevant implementation.
|
||||||
|
|
||||||
|
## Self-Check: PASSED
|
||||||
|
|
||||||
|
- All 8 files confirmed present on disk
|
||||||
|
- Commits 7597035 and 794ff42 confirmed in git log
|
||||||
|
- No unexpected file deletions in either commit
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import { describe, it } from 'vitest'
|
||||||
|
|
||||||
|
describe('UX-05: "/" focuses the search bar', () => {
|
||||||
|
it.todo('dispatching keydown "/" calls focus() on SearchBar input via App.vue routeViewRef chain')
|
||||||
|
it.todo('"/" does NOT redirect when an INPUT element has focus (guard: document.activeElement.tagName)')
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('UX-06: "Escape" clears active search (when no input focused)', () => {
|
||||||
|
it.todo('keydown Escape clears searchQuery via FileManagerView.clearSearch()')
|
||||||
|
it.todo('Escape does NOT clear search while inside a focused INPUT')
|
||||||
|
it.todo('Escape does NOT double-trigger when DocumentPreviewModal is open (modal owns its Escape handler)')
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('UX-07: "U" triggers the upload picker', () => {
|
||||||
|
it.todo('keydown "u" calls DropZone.triggerInput() through the ref chain (App→FileManagerView→StorageBrowser→DropZone)')
|
||||||
|
it.todo('"U" does NOT fire when input is focused')
|
||||||
|
it.todo('"U" is a no-op on routes that do not expose triggerUpload (optional chain)')
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('UX-08: "N" starts new folder input', () => {
|
||||||
|
it.todo('keydown "n" calls StorageBrowser.startNewFolder() via ref chain')
|
||||||
|
it.todo('"N" does NOT fire when input is focused')
|
||||||
|
it.todo('"N" is a no-op on CloudFolderView (does not expose startNewFolder)')
|
||||||
|
})
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import { describe, it } from 'vitest'
|
||||||
|
|
||||||
|
describe('UX-03: AppSidebar shows skeleton placeholders while loading', () => {
|
||||||
|
it.todo('renders skeleton rows in folder section when loadingRoots=true')
|
||||||
|
it.todo('renders skeleton rows in topics section when topicsStore.loading=true')
|
||||||
|
it.todo('renders skeleton rows in cloud section when loadingCloudConnections=true')
|
||||||
|
it.todo('Loading… text is removed from all three sections')
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('UX-01 (sidebar micro): EmptyState size=sm appears when each section is empty', () => {
|
||||||
|
it.todo('folders empty: EmptyState size=sm with icon=folder')
|
||||||
|
it.todo('topics empty: EmptyState size=sm with icon=tag')
|
||||||
|
it.todo('cloud empty: EmptyState size=sm with icon=cloud and a Settings link in #cta')
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('UX-14: AppSidebar no longer renders an inline "New" folder button', () => {
|
||||||
|
it.todo('no <button> with text "New" exists in the rendered template')
|
||||||
|
it.todo('startNewFolder/cancelNewFolder/submitNewFolder methods are removed')
|
||||||
|
it.todo('newFolderName/showNewFolderInput state is removed')
|
||||||
|
})
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import { describe, it } from 'vitest'
|
||||||
|
|
||||||
|
describe('UX-09: OsDragOverlay shows when OS files are dragged over the window', () => {
|
||||||
|
it.todo('overlay hidden by default (dragDepth=0)')
|
||||||
|
it.todo('dragenter with dataTransfer.types including "Files" shows overlay')
|
||||||
|
it.todo('dragenter without "Files" type is ignored (in-app element drag)')
|
||||||
|
it.todo('dragleave decrements depth; overlay hides when depth reaches 0')
|
||||||
|
it.todo('drop event emits files-dropped with the file list')
|
||||||
|
it.todo('drop resets dragDepth to 0 and hides overlay')
|
||||||
|
it.todo('overlay is teleported to body with z-[9998] (below toast z-[9999])')
|
||||||
|
})
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { describe, it } from 'vitest'
|
||||||
|
|
||||||
|
describe('UX-11: Drag-to-move document onto folder row', () => {
|
||||||
|
it.todo('dragOverFolderId set on @dragover applies bg-amber-50 ring-2 ring-inset ring-amber-300 to that folder row')
|
||||||
|
it.todo('drop on folder emits file-move with { fileId, folderId }')
|
||||||
|
it.todo('dragend resets draggingFile to null after nextTick (click-after-drag guard)')
|
||||||
|
it.todo('click on file row is suppressed when draggingFile is non-null')
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('UX-11 (toast wiring): doMove emits toast', () => {
|
||||||
|
it.todo('successful moveToFolder triggers useToastStore.show("Document moved", "success")')
|
||||||
|
it.todo('failed moveToFolder triggers useToastStore.show("Move failed: ...", "error")')
|
||||||
|
})
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import { describe, it } from 'vitest'
|
||||||
|
|
||||||
|
describe('UX-02: StorageBrowser shows skeleton rows during loading', () => {
|
||||||
|
it.todo('renders 5+ skeleton rows when loading=true (replaces Loading… text)')
|
||||||
|
it.todo('skeleton rows use grid-cols-[2rem_1fr_6rem_8rem_6rem] matching real row grid')
|
||||||
|
it.todo('skeleton rows use animate-pulse')
|
||||||
|
it.todo('Loading… text is absent when loading=true (skeleton replaces it)')
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('UX-13: StorageBrowser folder picker uses Teleport + getBoundingClientRect', () => {
|
||||||
|
it.todo('folder picker dropdown is teleported to body (escapes overflow container)')
|
||||||
|
it.todo('dropdown position reflects getBoundingClientRect of the trigger button')
|
||||||
|
it.todo('window scroll while open recalculates position')
|
||||||
|
})
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import { describe, it } from 'vitest'
|
||||||
|
|
||||||
|
describe('UX-13: Teleport + getBoundingClientRect dropdown pattern across components', () => {
|
||||||
|
it.todo('DocumentCard folder picker uses Teleport to body')
|
||||||
|
it.todo('DocumentCard folder picker position matches trigger getBoundingClientRect')
|
||||||
|
it.todo('FolderRow three-dot menu uses Teleport to body')
|
||||||
|
it.todo('FolderRow three-dot menu repositions on window scroll')
|
||||||
|
})
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import { describe, it } from 'vitest'
|
||||||
|
|
||||||
|
describe('UX-04: AdminAuditView shows skeleton table rows during loading', () => {
|
||||||
|
it.todo('renders 5+ skeleton <tr> rows when loading=true')
|
||||||
|
it.todo('skeleton rows have 5 columns matching the real table header')
|
||||||
|
it.todo('animated spinner Loading audit log… text is removed')
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('UX-01 (audit empty): EmptyState renders when entries is empty after load', () => {
|
||||||
|
it.todo('EmptyState icon=clipboardList headline="No entries found" appears when entries.length===0 and not loading')
|
||||||
|
it.todo('clear-filters button rendered in #cta slot')
|
||||||
|
})
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import { describe, it } from 'vitest'
|
||||||
|
|
||||||
|
describe('UX-04: AdminUsersView shows skeleton table rows during loading', () => {
|
||||||
|
it.todo('renders 5+ skeleton <tr> rows when loading=true')
|
||||||
|
it.todo('skeleton rows have 6 columns matching the real table header')
|
||||||
|
it.todo('animated spinner Loading users… text is removed')
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user