feat(12.1-03): align cloud browser contract — kind, provider_item_id, server freshness

- CloudFolderView: folders/files classified by kind=folder/file (remove is_dir)
- CloudFolderView: navigateTo uses item.provider_item_id for route param (not item.id)
- CloudFolderView: handleBreadcrumbNavigate uses named route; opaque refs intact
- CloudFolderView: setBrowseState maps server freshness.refresh_state verbatim
- CloudFolderView: refreshedAt = server last_refreshed_at (not new Date())
- CloudFolderView: breadcrumb lineage built explicitly from visited nodes
- CloudProviderTreeItem: loadChildren filters by kind=folder (not is_dir)
- CloudFolderTreeItem: expandable = kind===folder; icon = kind===folder
- CloudFolderTreeItem: navigate() uses provider_item_id (not folder.id)
- CloudFolderTreeItem: loadChildren uses provider_item_id as parent_ref
- CloudFolderTreeItem: isActive checks provider_item_id first
All 82 focused tests pass; full suite: 369/369
This commit is contained in:
curo1305
2026-06-22 08:49:19 +02:00
parent 9974fca2cb
commit dc3e1725da
4 changed files with 168 additions and 58 deletions
@@ -189,6 +189,10 @@ describe('renders_kind_folder_and_file_in_shared_browser', () => {
capturedFolders = this.folders
capturedFiles = this.files
},
updated() {
capturedFolders = this.folders
capturedFiles = this.files
},
}
const w = mount(CloudFolderView, {
global: { stubs: { StorageBrowser: CapturingStub } },
@@ -213,16 +217,20 @@ describe('folder_click_uses_provider_item_id_not_id', () => {
capabilities: null,
freshness: { refresh_state: 'fresh', last_refreshed_at: '2026-06-22T00:00:00Z' },
})
let navigateHandler = null
let didEmit = false
const CapturingStub = {
template: '<div data-test="storage-browser" />',
props: ['folders', 'files', 'mode', 'breadcrumb', 'uploadQueue', 'loading',
'emptyMessage', 'emptyHint', 'capabilities', 'connectionRoot',
'folderFreshness', 'lastRefreshedAt', 'byteAvailability'],
emits: ['folder-navigate'],
mounted() {
// Simulate a folder-navigate event with the first folder item
this.$emit('folder-navigate', this.folders[0])
mounted() {},
updated() {
// Emit folder-navigate once folders are populated (after load completes)
if (this.folders.length > 0 && !didEmit) {
didEmit = true
this.$emit('folder-navigate', this.folders[0])
}
},
}
const w = mount(CloudFolderView, {
@@ -230,18 +238,29 @@ describe('folder_click_uses_provider_item_id_not_id', () => {
})
await flushPromises()
// navigateTo must use provider_item_id for the route param, not DocuVault id
// The route must contain provider_item_id value, not the uuid id
expect(mockPush).toHaveBeenCalled()
const pushArg = mockPush.mock.calls[0][0]
// Must use provider_item_id in the route, not the DocuVault UUID
if (typeof pushArg === 'string') {
expect(pushArg).toContain(FOLDER_A.provider_item_id)
expect(pushArg).not.toContain(FOLDER_A.id)
} else if (typeof pushArg === 'object') {
// Named route object — params must carry provider_item_id
const paramValues = Object.values(pushArg.params ?? {})
expect(paramValues.some(v => String(v).includes(FOLDER_A.provider_item_id) || v === FOLDER_A.provider_item_id)).toBe(true)
}
// Find the push call that carries provider_item_id (not the session-restore push)
const hasPidRef = mockPush.mock.calls.some(call => {
const arg = call[0]
if (typeof arg === 'string') return arg.includes(FOLDER_A.provider_item_id)
if (typeof arg === 'object') {
const paramValues = Object.values(arg.params ?? {})
return paramValues.some(v => String(v) === FOLDER_A.provider_item_id)
}
return false
})
expect(hasPidRef).toBe(true)
// Must NOT use DocuVault UUID as route param
const hasDocuVaultId = mockPush.mock.calls.some(call => {
const arg = call[0]
if (typeof arg === 'string') return arg.includes(FOLDER_A.id)
if (typeof arg === 'object') {
const paramValues = Object.values(arg.params ?? {})
return paramValues.some(v => String(v) === FOLDER_A.id)
}
return false
})
expect(hasDocuVaultId).toBe(false)
})
})
@@ -259,6 +278,7 @@ describe('stable_docuvault_id_remains_row_key', () => {
'emptyMessage', 'emptyHint', 'capabilities', 'connectionRoot',
'folderFreshness', 'lastRefreshedAt', 'byteAvailability'],
mounted() { capturedFolders = this.folders },
updated() { capturedFolders = this.folders },
}
const w = mount(CloudFolderView, {
global: { stubs: { StorageBrowser: CapturingStub } },
@@ -279,14 +299,22 @@ describe('opaque_reference_round_trip', () => {
capabilities: null,
freshness: { refresh_state: 'fresh', last_refreshed_at: '2026-06-22T00:00:00Z' },
})
let capturedFolders = []
let didEmit = false
const CapturingStub = {
template: '<div data-test="storage-browser" />',
props: ['folders', 'files', 'mode', 'breadcrumb', 'uploadQueue', 'loading',
'emptyMessage', 'emptyHint', 'capabilities', 'connectionRoot',
'folderFreshness', 'lastRefreshedAt', 'byteAvailability'],
emits: ['folder-navigate'],
mounted() {
this.$emit('folder-navigate', this.folders[0])
mounted() { capturedFolders = this.folders },
updated() {
capturedFolders = this.folders
// Emit folder-navigate once folders are populated (after load completes)
if (this.folders.length > 0 && !didEmit) {
didEmit = true
this.$emit('folder-navigate', this.folders[0])
}
},
}
const w = mount(CloudFolderView, {
@@ -295,15 +323,19 @@ describe('opaque_reference_round_trip', () => {
await flushPromises()
// Navigation must use provider_item_id verbatim — no splitting or decoding
expect(mockPush).toHaveBeenCalled()
const pushArg = mockPush.mock.calls[0][0]
// The provider_item_id must not be split or have reserved chars stripped
if (typeof pushArg === 'object') {
const paramValues = Object.values(pushArg.params ?? {})
// At least one param must equal or contain the full opaque ref
expect(
paramValues.some(v => String(v) === FOLDER_OPAQUE.provider_item_id)
).toBe(true)
}
// Find the push call that was triggered by folder-navigate (not the initial replace)
const pushCalls = mockPush.mock.calls
// At least one call should carry the opaque provider_item_id
const hasOpaqueRef = pushCalls.some(call => {
const arg = call[0]
if (typeof arg === 'string') return arg.includes(FOLDER_OPAQUE.provider_item_id)
if (typeof arg === 'object') {
const paramValues = Object.values(arg.params ?? {})
return paramValues.some(v => String(v) === FOLDER_OPAQUE.provider_item_id)
}
return false
})
expect(hasOpaqueRef).toBe(true)
})
})