feat(12-03): connection-ID routing, rename, session folder memory, thin views
- Route /cloud/:connectionId/:folderId replaces /cloud/:provider/:folderId - api/cloud.js: getCloudFoldersByConnectionId + renameCloudConnection - cloudConnections store: rename, selectConnection, setBrowseState, defaultDisplayName, session storage helpers - CloudStorageView: thin — passes connectionRoots to StorageBrowser - CloudFolderView: thin — uses connectionId param, never provider slug - SettingsCloudTab: inline rename input for each active connection - 27 tests pass: store, view, settings
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
|
||||
import { setActivePinia, createPinia } from 'pinia'
|
||||
|
||||
// Mock api/client.js — no real HTTP calls in unit tests (CLAUDE.md W4)
|
||||
@@ -7,14 +7,22 @@ vi.mock('../../api/client.js', () => ({
|
||||
disconnectCloud: vi.fn(),
|
||||
connectWebDav: vi.fn(),
|
||||
updateDefaultStorage: vi.fn(),
|
||||
renameCloudConnection: vi.fn(),
|
||||
getCloudFoldersByConnectionId: vi.fn(),
|
||||
}))
|
||||
|
||||
import { useCloudConnectionsStore } from '../cloudConnections.js'
|
||||
import { useCloudConnectionsStore, saveLastFolder, loadLastFolder } from '../cloudConnections.js'
|
||||
import * as api from '../../api/client.js'
|
||||
|
||||
beforeEach(() => {
|
||||
setActivePinia(createPinia())
|
||||
vi.clearAllMocks()
|
||||
// Clear sessionStorage between tests
|
||||
sessionStorage.clear()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
sessionStorage.clear()
|
||||
})
|
||||
|
||||
describe('useCloudConnectionsStore', () => {
|
||||
@@ -56,4 +64,95 @@ describe('useCloudConnectionsStore', () => {
|
||||
await store.disconnectAll()
|
||||
expect(store.connections).toHaveLength(0)
|
||||
})
|
||||
|
||||
// Rename tests
|
||||
it('rename updates the connection in state on success', async () => {
|
||||
api.renameCloudConnection.mockResolvedValue({
|
||||
id: 'conn-1', provider: 'google_drive', display_name: 'My Drive',
|
||||
})
|
||||
const store = useCloudConnectionsStore()
|
||||
store.connections = [{ id: 'conn-1', provider: 'google_drive', display_name: 'Google Drive' }]
|
||||
await store.rename('conn-1', 'My Drive')
|
||||
expect(store.connections[0].display_name).toBe('My Drive')
|
||||
expect(api.renameCloudConnection).toHaveBeenCalledWith('conn-1', 'My Drive')
|
||||
})
|
||||
|
||||
it('rename rejects on empty display name', async () => {
|
||||
const store = useCloudConnectionsStore()
|
||||
await expect(store.rename('conn-1', '')).rejects.toThrow()
|
||||
})
|
||||
|
||||
it('rename rejects on whitespace-only display name', async () => {
|
||||
const store = useCloudConnectionsStore()
|
||||
await expect(store.rename('conn-1', ' ')).rejects.toThrow()
|
||||
})
|
||||
|
||||
it('rename propagates API error', async () => {
|
||||
api.renameCloudConnection.mockRejectedValue(new Error('API error'))
|
||||
const store = useCloudConnectionsStore()
|
||||
store.connections = [{ id: 'conn-1', provider: 'google_drive', display_name: 'Google Drive' }]
|
||||
await expect(store.rename('conn-1', 'New Name')).rejects.toThrow('API error')
|
||||
})
|
||||
|
||||
// Two same-provider connections render as separate roots
|
||||
it('two google_drive connections render as separate roots with UUID suffixes in defaultDisplayName', () => {
|
||||
const store = useCloudConnectionsStore()
|
||||
store.connections = [
|
||||
{ id: 'aabb-ccdd-1', provider: 'google_drive', display_name: null },
|
||||
{ id: 'eeff-gggg-2', provider: 'google_drive', display_name: null },
|
||||
]
|
||||
const nameA = store.defaultDisplayName(store.connections[0])
|
||||
const nameB = store.defaultDisplayName(store.connections[1])
|
||||
// Both should contain "Google Drive" and a 4-char suffix
|
||||
expect(nameA).toContain('Google Drive')
|
||||
expect(nameB).toContain('Google Drive')
|
||||
expect(nameA).toContain('aabb')
|
||||
expect(nameB).toContain('eeff')
|
||||
})
|
||||
|
||||
it('single connection has no suffix in defaultDisplayName', () => {
|
||||
const store = useCloudConnectionsStore()
|
||||
store.connections = [{ id: 'conn-1', provider: 'google_drive' }]
|
||||
expect(store.defaultDisplayName(store.connections[0])).toBe('Google Drive')
|
||||
})
|
||||
|
||||
// Connection UUID based URLs
|
||||
it('selectConnection sets selectedConnectionId', () => {
|
||||
const store = useCloudConnectionsStore()
|
||||
store.connections = [{ id: 'uuid-123', provider: 'onedrive' }]
|
||||
store.selectConnection('uuid-123')
|
||||
expect(store.selectedConnectionId).toBe('uuid-123')
|
||||
expect(store.selectedConnection?.id).toBe('uuid-123')
|
||||
})
|
||||
|
||||
// Session storage for folder navigation
|
||||
it('saveLastFolder stores folder in sessionStorage', () => {
|
||||
saveLastFolder('conn-abc', 'Documents/Work')
|
||||
expect(sessionStorage.getItem('docuvault:cloud:folder:conn-abc')).toBe('Documents/Work')
|
||||
})
|
||||
|
||||
it('loadLastFolder retrieves stored folder', () => {
|
||||
sessionStorage.setItem('docuvault:cloud:folder:conn-xyz', 'Photos')
|
||||
expect(loadLastFolder('conn-xyz')).toBe('Photos')
|
||||
})
|
||||
|
||||
it('saveLastFolder removes key for root folder', () => {
|
||||
sessionStorage.setItem('docuvault:cloud:folder:conn-abc', 'Docs')
|
||||
saveLastFolder('conn-abc', 'root')
|
||||
expect(sessionStorage.getItem('docuvault:cloud:folder:conn-abc')).toBeNull()
|
||||
})
|
||||
|
||||
it('loadLastFolder returns null when nothing stored', () => {
|
||||
expect(loadLastFolder('nonexistent')).toBeNull()
|
||||
})
|
||||
|
||||
it('sessionStorage stores only folder references, not tokens', () => {
|
||||
// Ensure save only stores the folderId string under the namespaced key
|
||||
saveLastFolder('conn-123', 'Projects/Secret')
|
||||
const stored = sessionStorage.getItem('docuvault:cloud:folder:conn-123')
|
||||
expect(stored).toBe('Projects/Secret')
|
||||
// Stored value is a plain string path, not a JSON object with credentials
|
||||
expect(typeof stored).toBe('string')
|
||||
expect(stored.startsWith('{')).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user