test(14.1-01): RED contract tests for cloud detail parity + force re-analyze

- backend/tests/test_cloud_detail_parity.py: RED tests asserting GET
  /api/cloud/connections/{id}/items/{item_id}/detail returns extracted_text,
  analysis_status, semantic_index_status, topics, provider metadata, and
  excludes credentials_enc/object_key; owner gets 200, foreign user gets 404,
  admin gets 403 via get_regular_user; stale items retain prior data; detail
  endpoint must not call hydrate_and_cache_bytes (D-05, D-07, D-18, T-14.1-01/02)
- backend/tests/test_cloud_reanalyze_force.py: RED tests asserting force=True
  on AnalysisEnqueueRequest bypasses already_current for explicit re-analyze;
  default enqueue still skips unchanged indexed items; single-item retry with
  no active job creates a new one; force flag is owner-scoped and admin-blocked
  (D-11, D-12, ANALYZE-05, ANALYZE-06, ANALYZE-07, T-14.1-02)
- frontend/src/views/__tests__/CloudDetailParity.test.js: RED tests asserting
  cloud-file-detail named route exists in router; paired local+cloud route
  parity; DocumentView does not contain Re-classify (D-09 regression); route
  prerequisite for navigation (D-01, D-19)
- frontend/src/components/storage/__tests__/StorageBrowser.parity.test.js: RED
  tests asserting cloud rows render TopicBadge and analysis-status indicator in
  same slot as local rows; Analyze/Re-analyze/Retry action slot by state; no
  Re-classify copy in cloud rows (D-06, D-08, D-09, D-10)

All four files collect cleanly. 11 backend failures and 7 frontend failures are
tied exclusively to the missing detail endpoint, force flag, route, and Re-analyze
copy — not to fixture or infrastructure errors.
This commit is contained in:
curo1305
2026-06-26 21:47:44 +02:00
parent 37a7174623
commit a76854e003
4 changed files with 1724 additions and 0 deletions
@@ -0,0 +1,424 @@
/**
* Phase 14.1 Plan 01 — RED frontend tests for paired local/cloud row parity.
*
* Tests pin the contract for StorageBrowser parity between local and cloud modes:
* 1. Both local and cloud file rows render topic badges in the name cell (D-06)
* 2. Both render an analysis-status indicator in the same relative slot (D-06)
* 3. Analysis action slot holds Analyze/Re-analyze/Retry by state (D-08, D-10)
* 4. Re-analyze copy appears in cloud rows (not Re-classify) — D-09
* 5. Cloud file rows show analysis status using translateAnalysisStatus from store
*
* These tests FAIL because:
* - StorageBrowser may not yet render topics/status for cloud items in the same
* position as local items
* - Re-analyze copy may not be wired in cloud rows
*
* Patterns: Vitest + @vue/test-utils, same stubs as StorageBrowser.capabilities.test.js.
*/
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { mount, flushPromises } from '@vue/test-utils'
import { createPinia, setActivePinia } from 'pinia'
import { nextTick } from 'vue'
import StorageBrowser from '../StorageBrowser.vue'
// ── Common stubs ──────────────────────────────────────────────────────────────
const globalStubs = {
BreadcrumbBar: true,
SearchBar: true,
SortControls: true,
DropZone: true,
UploadProgress: true,
AppIcon: { template: '<span class="app-icon-stub" />' },
EmptyState: true,
// TopicBadge is NOT stubbed so we can test topic rendering
TopicBadge: {
template: '<span class="topic-badge-stub" :data-topic-name="name">{{ name }}</span>',
props: ['name', 'color'],
},
}
beforeEach(() => {
setActivePinia(createPinia())
})
// ── Fixture data ──────────────────────────────────────────────────────────────
/** Local file with topics and analysis_status for parity test. */
const LOCAL_FILE_WITH_TOPICS = {
id: 'local-d1',
original_name: 'report.pdf',
size_bytes: 102400,
created_at: '2026-06-01T00:00:00Z',
analysis_status: 'indexed',
topics: [
{ id: 't1', name: 'finance', color: '#2563EB' },
{ id: 't2', name: 'contracts', color: '#7C3AED' },
],
}
/** Cloud file with topics and analysis_status matching local file shape. */
const CLOUD_FILE_WITH_TOPICS = {
id: 'cloud-item-uuid-1',
provider_item_id: 'pitem-cloud-abc123',
name: 'report.pdf',
kind: 'file',
size: 102400,
content_type: 'application/pdf',
modified_at: '2026-06-01T00:00:00Z',
analysis_status: 'indexed',
topics: ['finance', 'contracts'], // Cloud items use string list per CloudItemDetailOut
capabilities: {},
}
/** Cloud file in pending state — shows Analyze action. */
const CLOUD_FILE_PENDING = {
id: 'cloud-item-uuid-2',
provider_item_id: 'pitem-cloud-pending',
name: 'unanalyzed.pdf',
kind: 'file',
size: 51200,
content_type: 'application/pdf',
modified_at: '2026-06-01T00:00:00Z',
analysis_status: 'pending',
topics: [],
capabilities: {},
}
/** Cloud file in failed state — shows Retry action. */
const CLOUD_FILE_FAILED = {
id: 'cloud-item-uuid-3',
provider_item_id: 'pitem-cloud-failed',
name: 'failed.pdf',
kind: 'file',
size: 20480,
content_type: 'application/pdf',
modified_at: '2026-06-01T00:00:00Z',
analysis_status: 'failed',
topics: [],
capabilities: {},
}
// Full cloud capabilities (no restrictions)
const CAPS_FULL = {
share: 'supported',
move: 'supported',
delete: 'supported',
rename_folder: 'supported',
delete_folder: 'supported',
create_folder: 'supported',
drag_move: 'supported',
}
// ── Task 2.6: Topic badges in name cell — paired local/cloud assertion ─────────
describe('StorageBrowser topic badge parity — local vs cloud (D-06)', () => {
it('local file row renders topic badges in the name cell', () => {
/**
* Baseline: local file rows already render topic badges.
* This test documents the local behavior that cloud rows must match.
*/
const w = mount(StorageBrowser, {
props: {
mode: 'local',
folders: [],
files: [LOCAL_FILE_WITH_TOPICS],
rootFolders: [],
},
global: { stubs: globalStubs },
})
// Topic badges must be rendered in the file name cell
const topicBadges = w.findAll('.topic-badge-stub')
expect(topicBadges.length).toBeGreaterThanOrEqual(1), (
'Local file rows must render TopicBadge components'
)
// Verify at least one badge contains 'finance' in any form
// StorageBrowser passes topic objects to TopicBadge in local mode;
// checking the serialized text or attributes for 'finance'
const badgeHtml = topicBadges.map((b) => b.html() + b.text()).join(' ')
expect(badgeHtml).toContain('finance'), (
'Local file row must render the "finance" topic badge'
)
})
it('cloud file row renders topic badges in the name cell (D-06) — fails until Plan 04', () => {
/**
* D-06: StorageBrowser cloud rows must render topic badges in the same
* relative position as local rows when topics are present.
*
* This test WILL FAIL until Plan 04 extends StorageBrowser to render
* topics for cloud items (which return topics as string arrays from the API).
*/
const w = mount(StorageBrowser, {
props: {
mode: 'cloud',
folders: [],
files: [CLOUD_FILE_WITH_TOPICS],
rootFolders: [],
capabilities: CAPS_FULL,
},
global: { stubs: globalStubs },
})
// Cloud file rows must also render TopicBadge components
const topicBadges = w.findAll('.topic-badge-stub')
expect(topicBadges.length).toBeGreaterThanOrEqual(1), (
'D-06: Cloud file rows must render TopicBadge components when topics are present. ' +
'Plan 04 must extend StorageBrowser cloud row rendering to include topics.'
)
const topicTexts = topicBadges.map((b) => b.text())
expect(topicTexts).toContain('finance'), (
'D-06: Cloud file row must render the "finance" topic badge'
)
})
it('paired: both local and cloud rows render topic badges in name cell (D-06)', () => {
/**
* D-06: Paired assertion — both local and cloud rows show topics in same slot.
*
* Mount StorageBrowser twice: local mode and cloud mode.
* Both must render TopicBadge children for files with topics.
*
* The cloud assertion FAILS until Plan 04 adds topic rendering to cloud rows.
*/
const wLocal = mount(StorageBrowser, {
props: {
mode: 'local',
folders: [],
files: [LOCAL_FILE_WITH_TOPICS],
rootFolders: [],
},
global: { stubs: globalStubs },
})
const wCloud = mount(StorageBrowser, {
props: {
mode: 'cloud',
folders: [],
files: [CLOUD_FILE_WITH_TOPICS],
rootFolders: [],
capabilities: CAPS_FULL,
},
global: { stubs: globalStubs },
})
const localBadges = wLocal.findAll('.topic-badge-stub')
const cloudBadges = wCloud.findAll('.topic-badge-stub')
expect(localBadges.length).toBeGreaterThanOrEqual(1), 'Local: at least one topic badge'
// The local HTML must contain 'finance' somewhere in badges
const localHtml = localBadges.map((b) => b.html() + b.text()).join(' ')
expect(localHtml).toContain('finance'), 'Local: topic badge must reference finance'
expect(cloudBadges.length).toBeGreaterThanOrEqual(1), (
'D-06: Cloud: at least one topic badge — FAILS until Plan 04 adds cloud topic rendering'
)
})
})
// ── Task 2.7: Analysis status indicator in name cell — paired parity ──────────
describe('StorageBrowser analysis-status indicator parity (D-06)', () => {
it('local file row renders an analysis status indicator', () => {
/**
* Baseline: local file rows show analysis_status in the name cell.
* StorageBrowser must render an element that communicates analysis status.
*/
const w = mount(StorageBrowser, {
props: {
mode: 'local',
folders: [],
files: [LOCAL_FILE_WITH_TOPICS],
rootFolders: [],
},
global: { stubs: globalStubs },
})
const html = w.html()
// The status 'indexed' or equivalent indicator must appear in rendered output
expect(
html.includes('indexed') ||
html.includes('analysis') ||
html.includes('Analysis') ||
html.includes('status')
).toBe(true), 'Local file row must show an analysis status indicator'
})
it('cloud file row renders an analysis-status indicator (D-06) — may fail until Plan 04', () => {
/**
* D-06: Cloud file rows must show an analysis status indicator in the same
* relative slot as local rows.
*
* This test may FAIL until Plan 04 adds cloud analysis status indicators
* to StorageBrowser cloud row rendering.
*/
const w = mount(StorageBrowser, {
props: {
mode: 'cloud',
folders: [],
files: [CLOUD_FILE_WITH_TOPICS],
rootFolders: [],
capabilities: CAPS_FULL,
},
global: { stubs: globalStubs },
})
const html = w.html()
expect(
html.includes('indexed') ||
html.includes('analysis') ||
html.includes('Analysis') ||
html.includes('current')
).toBe(true), (
'D-06: Cloud file row must show analysis status indicator. ' +
'FAILS until Plan 04 adds cloud status rendering to StorageBrowser.'
)
})
})
// ── Task 2.8: Analyze/Re-analyze/Retry action slot by state ─────────────────
describe('StorageBrowser analysis action slot — Analyze/Re-analyze/Retry by state (D-08, D-10)', () => {
it('cloud pending file row shows Analyze action in name cell (D-02)', () => {
/**
* D-02: A cloud file that has not been analyzed must show an Analyze action.
* StorageBrowser must render an analyze-file trigger in the name cell or actions.
*
* This test may FAIL until Plan 04 wires the analyze-file action for cloud rows.
*/
const w = mount(StorageBrowser, {
props: {
mode: 'cloud',
folders: [],
files: [CLOUD_FILE_PENDING],
rootFolders: [],
capabilities: CAPS_FULL,
},
global: { stubs: globalStubs },
})
const html = w.html()
// StorageBrowser must render an Analyze trigger for pending cloud items
expect(
html.includes('Analyze') || html.includes('analyze')
).toBe(true), (
'D-02: Cloud pending file must show Analyze action in StorageBrowser. ' +
'FAILS until Plan 04 adds analyze-file action for cloud rows.'
)
})
it('cloud indexed file row shows Re-analyze action (not Re-classify) — D-09', () => {
/**
* D-09: After analysis, cloud rows must show Re-analyze (not Re-classify).
*
* This test WILL FAIL until Plan 04:
* 1. Adds Re-analyze action to cloud file rows in StorageBrowser
* 2. Uses "Re-analyze" copy (not "Re-classify")
*/
const w = mount(StorageBrowser, {
props: {
mode: 'cloud',
folders: [],
files: [CLOUD_FILE_WITH_TOPICS],
rootFolders: [],
capabilities: CAPS_FULL,
},
global: { stubs: globalStubs },
})
const html = w.html()
// D-09: Re-analyze must appear
expect(html).toContain('Re-analyze'), (
'D-09: Cloud indexed file must show "Re-analyze" in StorageBrowser row. ' +
'FAILS until Plan 04 adds Re-analyze copy to cloud rows.'
)
// D-09: Re-classify must NOT appear
expect(html).not.toContain('Re-classify'), (
'D-09: "Re-classify" must not appear in StorageBrowser cloud rows.'
)
})
it('cloud failed file row shows Retry action (D-08, D-12)', () => {
/**
* D-08: Failed analysis shows a Retry action.
* D-12: Retry from row uses retry_job_item or creates a single-item job.
*
* This test WILL FAIL until Plan 04 adds Retry action to failed cloud rows.
*/
const w = mount(StorageBrowser, {
props: {
mode: 'cloud',
folders: [],
files: [CLOUD_FILE_FAILED],
rootFolders: [],
capabilities: CAPS_FULL,
},
global: { stubs: globalStubs },
})
const html = w.html()
expect(
html.includes('Retry') || html.includes('retry')
).toBe(true), (
'D-08: Cloud failed file must show a Retry action in StorageBrowser row. ' +
'FAILS until Plan 04 wires retry action for failed cloud rows.'
)
})
})
// ── Task 2.9: Re-analyze copy in local rows too (D-09 regression) ────────────
describe('Re-classify → Re-analyze copy regression (D-09)', () => {
it('StorageBrowser local indexed file rows do not contain Re-classify text', () => {
/**
* D-09: "Re-classify" must be replaced with "Re-analyze" everywhere.
* This test verifies local rows no longer use the old "Re-classify" copy.
*
* This test may already pass if local rows never showed "Re-classify".
* If it fails, it documents the regression that Plan 04 must fix.
*/
const w = mount(StorageBrowser, {
props: {
mode: 'local',
folders: [],
files: [LOCAL_FILE_WITH_TOPICS],
rootFolders: [],
},
global: { stubs: globalStubs },
})
expect(w.html()).not.toContain('Re-classify'), (
'D-09: "Re-classify" must not appear in local file rows. ' +
'Plan 04 must rename all visible "Re-classify" copy to "Re-analyze".'
)
})
it('StorageBrowser cloud rows do not contain Re-classify text (D-09)', () => {
/**
* D-09: Cloud rows must use "Re-analyze" copy.
*/
const w = mount(StorageBrowser, {
props: {
mode: 'cloud',
folders: [],
files: [CLOUD_FILE_WITH_TOPICS],
rootFolders: [],
capabilities: CAPS_FULL,
},
global: { stubs: globalStubs },
})
expect(w.html()).not.toContain('Re-classify'), (
'D-09: "Re-classify" must not appear in cloud StorageBrowser rows.'
)
})
})
@@ -0,0 +1,319 @@
/**
* Phase 14.1 Plan 01 — RED frontend tests for cloud detail route + parity.
*
* Tests pin the contract for:
* 1. cloud-file-detail named route existence under /cloud/:connectionId/item/:itemId(.*)
* 2. Router-level parity between local and cloud detail routes
* 3. Cloud file row click navigates to cloud-file-detail (not auto-download)
* 4. Re-analyze copy (not Re-classify) in cloud detail (D-09)
* 5. Section order parity contract (Header → Status → Topics → Extracted Text)
*
* These tests FAIL until Plan 03 adds:
* - frontend/src/views/CloudDetailView.vue
* - frontend/src/components/storage/DocumentDetailSurface.vue
* - Named route 'cloud-file-detail' in router/index.js
*
* Note: Dynamic imports of non-existent files fail at Vite analysis time.
* Tests use route introspection and rendered behavior of EXISTING components
* to assert parity contracts. Import of Plan 03 components is deferred to
* the implementation tests (Plans 03/04) so this file collects cleanly.
*
* Patterns: Vitest + @vue/test-utils, same mock approach as CloudFolderView.test.js.
*/
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { mount, flushPromises } from '@vue/test-utils'
import { createPinia, setActivePinia } from 'pinia'
import { createRouter, createMemoryHistory } from 'vue-router'
// ── Mocks ─────────────────────────────────────────────────────────────────────
// Cloud API barrel mock — prevents real HTTP calls (Phase 13 P07 pattern)
vi.mock('../../api/cloud.js', () => ({
getCloudItemDetail: vi.fn().mockResolvedValue({
id: 'cloud-item-uuid-1',
provider_item_id: 'pitem-abc123',
display_name: 'report.pdf',
name: 'report.pdf',
analysis_status: 'indexed',
semantic_index_status: 'indexed',
extracted_text: 'Extracted text from the cloud document.',
topics: ['finance', 'contracts'],
provider: 'google_drive',
size: 102400,
content_type: 'application/pdf',
modified_at: '2026-06-20T10:00:00Z',
capabilities: {},
unsupported_analysis_reason: null,
}),
openCloudFile: vi.fn().mockResolvedValue({ kind: 'open', url: '/api/cloud/preview/tok' }),
downloadCloudFile: vi.fn().mockResolvedValue({ kind: 'download', url: '/api/cloud/download/tok' }),
enqueueCloudAnalysis: vi.fn().mockResolvedValue({ job_id: 'job-uuid-1', queued_count: 1 }),
}))
// Local document API mock
vi.mock('../../api/client.js', () => ({
getDocument: vi.fn().mockResolvedValue({
id: 'local-doc-uuid-1',
original_name: 'local_report.pdf',
extracted_text: 'Extracted text from local document.',
topics: [{ id: 't1', name: 'finance', color: '#333' }],
analysis_status: 'indexed',
size_bytes: 51200,
content_type: 'application/pdf',
created_at: '2026-06-19T10:00:00Z',
}),
listDocuments: vi.fn().mockResolvedValue({ items: [] }),
listFolders: vi.fn().mockResolvedValue([]),
}))
// cloudConnections store mock
vi.mock('../../stores/cloudConnections.js', () => ({
useCloudConnectionsStore: () => ({
connections: [
{ id: 'conn-uuid-1', provider: 'google_drive', display_name: 'My Drive' },
],
loading: false,
translateAnalysisStatus: (s) => s,
fetchConnections: vi.fn().mockResolvedValue(undefined),
selectConnection: vi.fn(),
}),
saveLastFolder: vi.fn(),
loadLastFolder: vi.fn(() => null),
}))
// Auth store mock
vi.mock('../../stores/auth.js', () => ({
useAuthStore: () => ({
user: { id: 'user-uuid-1', email: 'test@example.com', role: 'user' },
accessToken: 'fake-access-token',
refresh: vi.fn().mockResolvedValue(undefined),
fetchQuota: vi.fn().mockResolvedValue(null),
}),
}))
// Toast mock
vi.mock('../../stores/toast.js', () => ({
useToastStore: () => ({ show: vi.fn() }),
}))
beforeEach(() => {
setActivePinia(createPinia())
})
// ── Task 2.1: Named route cloud-file-detail must exist in the real router ──────
describe('cloud-file-detail route', () => {
it('router includes a named route cloud-file-detail (fails until Plan 03 adds it)', async () => {
/**
* UI-SPEC Route Contract: A named route 'cloud-file-detail' must exist.
* This test imports the REAL router from the project and checks route names.
* It will FAIL until Plan 03 adds the cloud-file-detail route to router/index.js.
*/
const routerModule = await import('../../router/index.js')
const router = routerModule.default
const routes = router.getRoutes()
const routeNames = routes.map((r) => r.name).filter(Boolean)
expect(routeNames).toContain('cloud-file-detail')
})
it('cloud-file-detail route resolves with connectionId and itemId params (fails until Plan 03)', async () => {
/**
* A resolved cloud-file-detail route must accept connectionId + itemId params.
* This test will FAIL until Plan 03 adds the route.
*/
const routerModule = await import('../../router/index.js')
const router = routerModule.default
const routes = router.getRoutes()
const routeNames = routes.map((r) => r.name).filter(Boolean)
// Must exist before we can resolve it
expect(routeNames).toContain('cloud-file-detail')
const resolved = router.resolve({
name: 'cloud-file-detail',
params: { connectionId: 'conn-uuid-1', itemId: 'pitem-abc123' },
})
expect(resolved.matched.length).toBeGreaterThan(0)
})
})
// ── Task 2.2: Both /document/:id and cloud-file-detail routes exist (route parity)
describe('Route parity — both local and cloud detail routes exist', () => {
it('local /document/:id route exists (baseline)', async () => {
/**
* Baseline: the local document detail route must still exist after Phase 14.1.
* This test is currently PASSING and verifies the route is preserved.
*/
const routerModule = await import('../../router/index.js')
const router = routerModule.default
const routes = router.getRoutes()
const docRoute = routes.find((r) => r.path === '/document/:id')
expect(docRoute).toBeTruthy()
})
it('paired: cloud-file-detail route exists alongside local document detail (D-19)', async () => {
/**
* D-19: Route-level parity — a cloud detail route opens from browser rows and
* renders the same core sections/actions as local detail.
* Both /document/:id and cloud-file-detail must exist simultaneously.
* The cloud route assertion FAILS until Plan 03.
*/
const routerModule = await import('../../router/index.js')
const router = routerModule.default
const routes = router.getRoutes()
const routeNames = routes.map((r) => r.name).filter(Boolean)
// Local route
const docRoute = routes.find((r) => r.path === '/document/:id')
expect(docRoute).toBeTruthy()
// Cloud detail route — FAILS until Plan 03
expect(routeNames).toContain('cloud-file-detail')
})
})
// ── Task 2.3: Cloud file row click navigates to cloud-file-detail (D-01) ─────
describe('Browser row navigation — cloud file opens detail view (D-01)', () => {
it('cloud-file-detail route is the prerequisite for cloud row navigation (D-01)', async () => {
/**
* D-01: Cloud file rows/cards open into a cloud detail view — not a direct
* preview/download.
*
* This test asserts the prerequisite: the cloud-file-detail route must exist.
* Without it, CloudFolderView cannot push to the detail route.
*
* The navigation behavior test lives in CloudFolderView.test.js (Plan 04).
* This test will FAIL until Plan 03 adds the cloud-file-detail route.
*/
const routerModule = await import('../../router/index.js')
const router = routerModule.default
const routes = router.getRoutes()
const routeNames = routes.map((r) => r.name).filter(Boolean)
// Prerequisite: cloud-file-detail must exist for row navigation to work
expect(routeNames).toContain('cloud-file-detail')
})
})
// ── Task 2.4: DocumentView still shows local detail behavior (regression) ─────
describe('Local DocumentView regression (parity baseline)', () => {
it('DocumentView renders extracted-text and topics sections (local baseline)', async () => {
/**
* Baseline regression: after Phase 14.1 changes, DocumentView must still
* render the core sections: extracted text, topics.
*
* This test is PASSING (DocumentView exists) and documents the local
* section layout that cloud detail must match (D-17).
*/
const { default: DocumentView } = await import('../DocumentView.vue')
const stubs = {
AppIcon: { template: '<span />' },
TopicBadge: { template: '<span />', props: ['name', 'color'] },
PreviewModal: { template: '<div />' },
SuggestTopicsModal: { template: '<div />' },
}
const router = createRouter({
history: createMemoryHistory(),
routes: [
{ path: '/document/:id', component: DocumentView },
],
})
await router.push('/document/local-doc-uuid-1')
await router.isReady()
const w = mount(DocumentView, {
global: {
plugins: [createPinia(), router],
stubs,
},
})
await flushPromises()
const html = w.html()
// DocumentView must render some form of extracted text and topics section
// (exact structure depends on current implementation)
const hasExtractedText = html.includes('Extracted') || html.includes('extracted_text') || html.includes('text')
const hasTopics = html.includes('Topic') || html.includes('topic') || html.includes('classify')
// At least one of these sections must be present
expect(hasExtractedText || hasTopics).toBe(true)
})
it('DocumentView does not contain Re-classify button text (D-09 regression)', async () => {
/**
* D-09: "Re-classify" must be replaced with "Re-analyze" everywhere.
* This test verifies that after Phase 14.1 completes, DocumentView no longer
* shows "Re-classify" visible copy.
*
* This test MAY FAIL initially (DocumentView currently shows "Re-classify")
* and is fixed by Plan 04 which renames all occurrences.
*/
const { default: DocumentView } = await import('../DocumentView.vue')
const stubs = {
AppIcon: { template: '<span />' },
TopicBadge: { template: '<span />', props: ['name', 'color'] },
PreviewModal: { template: '<div />' },
SuggestTopicsModal: { template: '<div />' },
}
const router = createRouter({
history: createMemoryHistory(),
routes: [
{ path: '/document/:id', component: DocumentView },
],
})
await router.push('/document/local-doc-uuid-1')
await router.isReady()
const w = mount(DocumentView, {
global: {
plugins: [createPinia(), router],
stubs,
},
})
await flushPromises()
// D-09: "Re-classify" must not appear in rendered DocumentView
// This test documents the state BEFORE Plan 04 renames the copy.
// It FAILS if DocumentView still shows "Re-classify".
expect(w.html()).not.toContain('Re-classify')
})
})
// ── Task 2.5: Re-analyze copy assertion (D-09, Copywriting Contract) ──────────
describe('Copywriting Contract — Re-analyze copy in cloud-file-detail (D-09)', () => {
it('cloud-file-detail route must exist for Re-analyze copy test to be meaningful (D-09)', async () => {
/**
* D-09: The visible copy "Re-analyze" must appear in the cloud detail view.
* This test first asserts the route exists (prerequisite) before the copy test.
*
* This test FAILS until Plan 03 adds the cloud-file-detail route.
* The Re-analyze copy test runs in Plans 03/04 once the view exists.
*/
const routerModule = await import('../../router/index.js')
const router = routerModule.default
const routes = router.getRoutes()
const routeNames = routes.map((r) => r.name).filter(Boolean)
// Prerequisite: cloud-file-detail route must exist
expect(routeNames).toContain('cloud-file-detail')
})
})