Files
kite/frontend/src/__tests__/keyboard.test.js
T
curo1305 339f5a0c82 test(10-13): regression tests for all 6 UAT gaps
- keyboard.test.js: Gap 4 test — matched.find(r => r.instances?.default) resolves to FileManagerView
- TreeItem.test.js: Gap 1 tests — animate-pulse present, 'Loading' text absent during shimmer state
- StorageBrowser.showSearch.test.js: Gap 2 tests — showSearch true for local+cloud at root
- All 219 tests pass (8 new, 211 existing)
2026-06-16 19:19:02 +02:00

160 lines
5.7 KiB
JavaScript

import { describe, it, expect, vi, beforeEach } from 'vitest'
import { mount, flushPromises } from '@vue/test-utils'
import { setActivePinia, createPinia } from 'pinia'
import { createRouter, createMemoryHistory } from 'vue-router'
import FileManagerView from '../views/FileManagerView.vue'
vi.mock('../api/client.js', () => ({
listFolders: vi.fn().mockResolvedValue({ items: [] }),
listDocuments: vi.fn().mockResolvedValue({ items: [], total: 0 }),
getFolder: vi.fn().mockResolvedValue({ id: 'f1', name: 'Test', breadcrumb: [] }),
createFolder: vi.fn(),
renameFolder: vi.fn(),
deleteFolder: vi.fn(),
moveDocument: vi.fn(),
deleteDocument: vi.fn().mockResolvedValue(null),
getSharedWithMe: vi.fn().mockResolvedValue([]),
listTopics: vi.fn().mockResolvedValue([]),
getMyQuota: vi.fn().mockResolvedValue({ used_bytes: 0, limit_bytes: 104857600 }),
}))
vi.mock('../stores/auth.js', () => ({
useAuthStore: () => ({
user: { email: 'test@example.com', role: 'user' },
accessToken: 'fake-token',
fetchQuota: vi.fn().mockResolvedValue(null),
}),
}))
vi.mock('../stores/topics.js', () => ({
useTopicsStore: () => ({
topics: [],
loading: false,
fetchTopics: vi.fn().mockResolvedValue(null),
}),
}))
vi.mock('../components/ui/BreadcrumbBar.vue', () => ({
default: { template: '<nav/>', props: ['segments', 'rootLabel'], emits: ['navigate'] },
}))
vi.mock('../components/upload/DropZone.vue', () => ({
default: { template: '<div class="dropzone"/>', emits: ['files-selected'] },
}))
vi.mock('../components/upload/UploadProgress.vue', () => ({
default: { template: '<div/>', props: ['items'] },
}))
vi.mock('../components/folders/FolderDeleteModal.vue', () => ({
default: { template: '<div/>', props: ['folder'], emits: ['confirm', 'cancel'] },
}))
vi.mock('../components/sharing/ShareModal.vue', () => ({
default: { template: '<div/>', props: ['doc'], emits: ['close'] },
}))
vi.mock('../components/documents/SearchBar.vue', () => ({
default: { template: '<input/>', props: ['modelValue'] },
}))
vi.mock('../components/documents/SortControls.vue', () => ({
default: { template: '<div/>', props: ['sort', 'order'], emits: ['change'] },
}))
vi.mock('../components/topics/TopicBadge.vue', () => ({
default: { template: '<span/>', props: ['name', 'color'] },
}))
function makeRouter() {
return createRouter({
history: createMemoryHistory(),
routes: [
{ path: '/', component: FileManagerView },
{ path: '/folders/:folderId', component: FileManagerView },
],
})
}
async function mountFileManager(path = '/') {
setActivePinia(createPinia())
const router = makeRouter()
await router.push(path)
await router.isReady()
const w = mount(FileManagerView, {
global: { plugins: [router], stubs: { QuotaBar: true, AppSpinner: true } },
})
await flushPromises()
return w
}
describe('UX-05: "/" focuses the search bar', () => {
it('FileManagerView exposes focusSearch() that delegates to browserRef.focusSearch', async () => {
const w = await mountFileManager()
const focusSpy = vi.fn()
const browserRef = w.vm.$.setupState.browserRef
if (browserRef?.value) browserRef.value.focusSearch = focusSpy
expect(typeof w.vm.focusSearch).toBe('function')
})
it('calling focusSearch() on the exposed interface does not throw when no ref mounted', async () => {
const w = await mountFileManager()
expect(() => w.vm.focusSearch()).not.toThrow()
})
it('focusSearch is part of defineExpose (accessible via vm)', async () => {
const w = await mountFileManager()
expect(w.vm.focusSearch).toBeDefined()
})
})
describe('UX-06: "Escape" clears active search (when no input focused)', () => {
it('FileManagerView exposes clearSearch() method', async () => {
const w = await mountFileManager()
expect(typeof w.vm.clearSearch).toBe('function')
})
it('calling clearSearch() does not throw', async () => {
const w = await mountFileManager()
expect(() => w.vm.clearSearch()).not.toThrow()
})
})
describe('UX-07: "U" triggers the upload picker', () => {
it('FileManagerView exposes triggerUpload() method', async () => {
const w = await mountFileManager()
expect(typeof w.vm.triggerUpload).toBe('function')
})
it('calling triggerUpload() does not throw', async () => {
const w = await mountFileManager()
expect(() => w.vm.triggerUpload()).not.toThrow()
})
})
describe('UX-08: "N" starts new folder input', () => {
it('FileManagerView exposes startNewFolder() method', async () => {
const w = await mountFileManager()
expect(typeof w.vm.startNewFolder).toBe('function')
})
it('calling startNewFolder() via exposed interface delegates to browserRef without throwing', async () => {
const w = await mountFileManager()
expect(() => w.vm.startNewFolder()).not.toThrow()
})
})
describe('Gap 4: getFileManagerInstance resolves to actual component, not RouterView proxy', () => {
it('matched.find(r => r.instances?.default) returns an object with focusSearch defined when mounted via router-view', async () => {
setActivePinia(createPinia())
const router = makeRouter()
await router.push('/')
await router.isReady()
// Mount via a router-view wrapper so Vue Router populates r.instances.default
const { defineComponent, h } = await import('vue')
const { RouterView } = await import('vue-router')
const App = defineComponent({ render: () => h(RouterView) })
mount(App, {
global: { plugins: [router], stubs: { QuotaBar: true, AppSpinner: true } },
})
await flushPromises()
const instance = router.currentRoute.value.matched.find(r => r.instances?.default)?.instances?.default
expect(instance).not.toBeNull()
expect(instance).toBeDefined()
expect(typeof instance.focusSearch).toBe('function')
})
})