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:
@@ -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')
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user