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)
This commit is contained in:
curo1305
2026-06-16 19:19:02 +02:00
parent bac5dcfe3d
commit 339f5a0c82
3 changed files with 142 additions and 0 deletions
+21
View File
@@ -136,3 +136,24 @@ describe('UX-08: "N" starts new folder input', () => {
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')
})
})
@@ -0,0 +1,53 @@
import { describe, it, expect, vi } from 'vitest'
import { mount } from '@vue/test-utils'
import { createPinia, setActivePinia } from 'pinia'
import StorageBrowser from '../StorageBrowser.vue'
const globalStubs = {
BreadcrumbBar: true,
SearchBar: true,
SortControls: true,
DropZone: true,
UploadProgress: true,
TopicBadge: true,
AppIcon: true,
EmptyState: true,
}
function mountBrowser(mode, breadcrumb = []) {
setActivePinia(createPinia())
return mount(StorageBrowser, {
props: {
mode,
breadcrumb,
documents: [],
folders: [],
files: [],
topicColorFn: () => '#000',
loading: false,
},
global: { stubs: globalStubs },
})
}
describe('Gap 2: showSearch visible at root for local and cloud modes', () => {
it("mode='local', breadcrumb=[] → showSearch is true (root-level local)", () => {
const w = mountBrowser('local', [])
expect(w.vm.showSearch).toBe(true)
})
it("mode='local', breadcrumb=[{name:'Folder'}] → showSearch is true (non-root local)", () => {
const w = mountBrowser('local', [{ name: 'Folder' }])
expect(w.vm.showSearch).toBe(true)
})
it("mode='cloud', breadcrumb=[] → showSearch is true (cloud root)", () => {
const w = mountBrowser('cloud', [])
expect(w.vm.showSearch).toBe(true)
})
it("mode='shared' → showSearch is false", () => {
const w = mountBrowser('shared', [])
expect(w.vm.showSearch).toBe(false)
})
})
@@ -0,0 +1,68 @@
import { describe, it, expect, vi } from 'vitest'
import { mount, flushPromises } from '@vue/test-utils'
import { nextTick } from 'vue'
import TreeItem from '../TreeItem.vue'
vi.mock('../AppIcon.vue', () => ({ default: { template: '<span/>' } }))
function mountItem(overrides = {}) {
return mount(TreeItem, {
props: {
label: 'Test',
loadChildren: vi.fn().mockResolvedValue([]),
expandable: true,
...overrides,
},
global: { stubs: { RouterLink: true } },
})
}
describe('Gap 1: sidebar shimmer rows', () => {
it('renders animate-pulse elements when loading=true (expanded via refresh)', async () => {
// First expand the item (so expanded=true, childrenLoaded=true)
let resolveFn1
const loadChildren = vi.fn()
.mockReturnValueOnce(Promise.resolve([])) // first load resolves immediately
.mockReturnValue(new Promise(resolve => { resolveFn1 = resolve })) // refresh never resolves
const w = mountItem({ loadChildren })
// Expand by awaiting toggleExpand — sets expanded=true after load completes
const setupState = w.vm.$.setupState
await setupState.toggleExpand()
await flushPromises()
// Now call refresh() without awaiting — it calls load() which sets loading=true while expanded stays true
setupState.refresh() // NOT awaited — loading=true while promise is pending
await nextTick() // let Vue update the DOM
const pulseEls = w.findAll('.animate-pulse')
expect(pulseEls.length).toBeGreaterThanOrEqual(2)
// Cleanup
if (resolveFn1) resolveFn1([])
await flushPromises()
})
it('does not render "Loading" text when loading=true (expanded via refresh)', async () => {
let resolveFn1
const loadChildren = vi.fn()
.mockReturnValueOnce(Promise.resolve([]))
.mockReturnValue(new Promise(resolve => { resolveFn1 = resolve }))
const w = mountItem({ loadChildren })
const setupState = w.vm.$.setupState
await setupState.toggleExpand()
await flushPromises()
setupState.refresh()
await nextTick()
expect(w.text()).not.toContain('Loading')
if (resolveFn1) resolveFn1([])
await flushPromises()
})
})
describe('Gap 1 regression: non-loading branches unchanged', () => {
it('shows Empty text when loadChildren resolves to [] and item is expanded', async () => {
const w = mountItem({ loadChildren: vi.fn().mockResolvedValue([]) })
const toggleExpand = w.vm.$.setupState.toggleExpand
await toggleExpand()
await flushPromises()
await nextTick()
expect(w.text()).toContain('Empty')
})
})