test(10-07): promote AppSidebar.empty stubs to RED tests (UX-01, UX-03, UX-14)
- Replace 10 it.todo stubs with 9 real tests across 3 describe blocks - UX-03: 3 tests verify skeleton rows during loading (folders/topics/cloud) - UX-01 sidebar micro: 3 tests verify EmptyState size=sm for each empty section - UX-14: 3 tests verify "New" button gone, methods removed, no inline folder input - 8/9 tests RED (1 passes trivially: input hidden while showNewFolderInput=false) - Adapted for script setup (Composition API) rather than Options API as described in plan
This commit is contained in:
@@ -1,20 +1,365 @@
|
||||
import { describe, it } from 'vitest'
|
||||
import { describe, it, expect, beforeEach, vi } from 'vitest'
|
||||
import { mount, flushPromises } from '@vue/test-utils'
|
||||
import { setActivePinia, createPinia } from 'pinia'
|
||||
import { createRouter, createMemoryHistory } from 'vue-router'
|
||||
|
||||
vi.mock('../../../api/client.js', () => ({
|
||||
listFolders: vi.fn().mockResolvedValue({ items: [] }),
|
||||
getSharedWithMe: vi.fn().mockResolvedValue([]),
|
||||
listCloudConnections: vi.fn().mockResolvedValue({ items: [] }),
|
||||
listTopics: vi.fn().mockResolvedValue({ topics: [] }),
|
||||
getMyQuota: vi.fn().mockResolvedValue({ used_bytes: 0, limit_bytes: 104857600 }),
|
||||
}))
|
||||
|
||||
const STUBS = {
|
||||
FolderTreeItem: true,
|
||||
CloudProviderTreeItem: true,
|
||||
QuotaBar: true,
|
||||
RouterLink: true,
|
||||
EmptyState: true,
|
||||
}
|
||||
|
||||
function makeRouter() {
|
||||
return createRouter({
|
||||
history: createMemoryHistory(),
|
||||
routes: [
|
||||
{ path: '/', component: { template: '<div/>' } },
|
||||
{ path: '/settings', component: { template: '<div/>' } },
|
||||
{ path: '/topics', component: { template: '<div/>' } },
|
||||
{ path: '/shared', component: { template: '<div/>' } },
|
||||
{ path: '/cloud', component: { template: '<div/>' } },
|
||||
{ path: '/admin', component: { template: '<div/>' } },
|
||||
],
|
||||
})
|
||||
}
|
||||
|
||||
async function mountSidebar(storeOverrides = {}) {
|
||||
setActivePinia(createPinia())
|
||||
|
||||
const { useFoldersStore } = await import('../../../stores/folders.js')
|
||||
const { useTopicsStore } = await import('../../../stores/topics.js')
|
||||
const { useCloudConnectionsStore } = await import('../../../stores/cloudConnections.js')
|
||||
|
||||
const foldersStore = useFoldersStore()
|
||||
const topicsStore = useTopicsStore()
|
||||
const cloudStore = useCloudConnectionsStore()
|
||||
|
||||
if (storeOverrides.folders !== undefined) Object.assign(foldersStore, storeOverrides.folders)
|
||||
if (storeOverrides.topics !== undefined) Object.assign(topicsStore, storeOverrides.topics)
|
||||
if (storeOverrides.cloud !== undefined) Object.assign(cloudStore, storeOverrides.cloud)
|
||||
|
||||
const router = makeRouter()
|
||||
await router.push('/')
|
||||
await router.isReady()
|
||||
|
||||
const AppSidebar = (await import('../AppSidebar.vue')).default
|
||||
|
||||
const wrapper = mount(AppSidebar, {
|
||||
global: {
|
||||
plugins: [router],
|
||||
stubs: STUBS,
|
||||
},
|
||||
})
|
||||
await flushPromises()
|
||||
|
||||
if (storeOverrides.folders !== undefined) Object.assign(foldersStore, storeOverrides.folders)
|
||||
if (storeOverrides.topics !== undefined) Object.assign(topicsStore, storeOverrides.topics)
|
||||
if (storeOverrides.cloud !== undefined) Object.assign(cloudStore, storeOverrides.cloud)
|
||||
|
||||
await wrapper.vm.$nextTick()
|
||||
|
||||
return wrapper
|
||||
}
|
||||
|
||||
describe('UX-03: AppSidebar shows skeleton placeholders while loading', () => {
|
||||
it.todo('renders skeleton rows in folder section when loadingRoots=true')
|
||||
it.todo('renders skeleton rows in topics section when topicsStore.loading=true')
|
||||
it.todo('renders skeleton rows in cloud section when loadingCloudConnections=true')
|
||||
it.todo('Loading… text is removed from all three sections')
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
vi.resetModules()
|
||||
})
|
||||
|
||||
it('renders skeleton rows in folder section when loadingRoots=true', async () => {
|
||||
const { listFolders } = await import('../../../api/client.js')
|
||||
let resolveListFolders
|
||||
listFolders.mockReturnValueOnce(new Promise(r => { resolveListFolders = r }))
|
||||
|
||||
setActivePinia(createPinia())
|
||||
const router = makeRouter()
|
||||
await router.push('/')
|
||||
await router.isReady()
|
||||
|
||||
const AppSidebar = (await import('../AppSidebar.vue')).default
|
||||
const wrapper = mount(AppSidebar, {
|
||||
global: { plugins: [router], stubs: { ...STUBS, EmptyState: false } },
|
||||
})
|
||||
|
||||
const pulses = wrapper.findAll('.animate-pulse')
|
||||
expect(pulses.length).toBeGreaterThanOrEqual(3)
|
||||
|
||||
resolveListFolders({ items: [] })
|
||||
await flushPromises()
|
||||
wrapper.unmount()
|
||||
})
|
||||
|
||||
it('renders skeleton rows in topics section when topicsStore.loading=true', async () => {
|
||||
vi.resetModules()
|
||||
|
||||
vi.doMock('../../../api/client.js', () => ({
|
||||
listFolders: vi.fn().mockResolvedValue({ items: [] }),
|
||||
getSharedWithMe: vi.fn().mockResolvedValue([]),
|
||||
listCloudConnections: vi.fn().mockResolvedValue({ items: [] }),
|
||||
listTopics: vi.fn().mockResolvedValue({ topics: [] }),
|
||||
getMyQuota: vi.fn().mockResolvedValue({ used_bytes: 0, limit_bytes: 104857600 }),
|
||||
}))
|
||||
|
||||
setActivePinia(createPinia())
|
||||
|
||||
const { useTopicsStore } = await import('../../../stores/topics.js')
|
||||
const topicsStore = useTopicsStore()
|
||||
topicsStore.loading = true
|
||||
topicsStore.topics = []
|
||||
|
||||
const router = makeRouter()
|
||||
await router.push('/')
|
||||
await router.isReady()
|
||||
|
||||
const AppSidebar = (await import('../AppSidebar.vue')).default
|
||||
const wrapper = mount(AppSidebar, {
|
||||
global: { plugins: [router], stubs: STUBS },
|
||||
})
|
||||
await flushPromises()
|
||||
|
||||
topicsStore.loading = true
|
||||
topicsStore.topics = []
|
||||
await wrapper.vm.$nextTick()
|
||||
|
||||
const pulses = wrapper.findAll('.animate-pulse')
|
||||
expect(pulses.length).toBeGreaterThanOrEqual(3)
|
||||
wrapper.unmount()
|
||||
})
|
||||
|
||||
it('renders skeleton rows in cloud section when loadingCloudConnections=true', async () => {
|
||||
vi.resetModules()
|
||||
|
||||
vi.doMock('../../../api/client.js', () => ({
|
||||
listFolders: vi.fn().mockResolvedValue({ items: [] }),
|
||||
getSharedWithMe: vi.fn().mockResolvedValue([]),
|
||||
listCloudConnections: vi.fn().mockReturnValue(new Promise(() => {})),
|
||||
listTopics: vi.fn().mockResolvedValue({ topics: [] }),
|
||||
getMyQuota: vi.fn().mockResolvedValue({ used_bytes: 0, limit_bytes: 104857600 }),
|
||||
}))
|
||||
|
||||
setActivePinia(createPinia())
|
||||
|
||||
const { useCloudConnectionsStore } = await import('../../../stores/cloudConnections.js')
|
||||
const cloudStore = useCloudConnectionsStore()
|
||||
cloudStore.loading = true
|
||||
cloudStore.connections = []
|
||||
|
||||
const router = makeRouter()
|
||||
await router.push('/')
|
||||
await router.isReady()
|
||||
|
||||
const AppSidebar = (await import('../AppSidebar.vue')).default
|
||||
const wrapper = mount(AppSidebar, {
|
||||
global: { plugins: [router], stubs: STUBS },
|
||||
})
|
||||
|
||||
cloudStore.loading = true
|
||||
cloudStore.connections = []
|
||||
await wrapper.vm.$nextTick()
|
||||
|
||||
const pulses = wrapper.findAll('.animate-pulse')
|
||||
expect(pulses.length).toBeGreaterThanOrEqual(3)
|
||||
wrapper.unmount()
|
||||
})
|
||||
})
|
||||
|
||||
describe('UX-01 (sidebar micro): EmptyState size=sm appears when each section is empty', () => {
|
||||
it.todo('folders empty: EmptyState size=sm with icon=folder')
|
||||
it.todo('topics empty: EmptyState size=sm with icon=tag')
|
||||
it.todo('cloud empty: EmptyState size=sm with icon=cloud and a Settings link in #cta')
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
vi.resetModules()
|
||||
})
|
||||
|
||||
it('folders empty: EmptyState size=sm with icon=folder', async () => {
|
||||
vi.doMock('../../../api/client.js', () => ({
|
||||
listFolders: vi.fn().mockResolvedValue({ items: [] }),
|
||||
getSharedWithMe: vi.fn().mockResolvedValue([]),
|
||||
listCloudConnections: vi.fn().mockResolvedValue({ items: [] }),
|
||||
listTopics: vi.fn().mockResolvedValue({ topics: [] }),
|
||||
getMyQuota: vi.fn().mockResolvedValue({ used_bytes: 0, limit_bytes: 104857600 }),
|
||||
}))
|
||||
|
||||
setActivePinia(createPinia())
|
||||
const { useFoldersStore } = await import('../../../stores/folders.js')
|
||||
const foldersStore = useFoldersStore()
|
||||
|
||||
const router = makeRouter()
|
||||
await router.push('/')
|
||||
await router.isReady()
|
||||
|
||||
const AppSidebar = (await import('../AppSidebar.vue')).default
|
||||
const wrapper = mount(AppSidebar, {
|
||||
global: { plugins: [router], stubs: { ...STUBS, EmptyState: false } },
|
||||
})
|
||||
await flushPromises()
|
||||
|
||||
foldersStore.rootFolders = []
|
||||
await wrapper.vm.$nextTick()
|
||||
|
||||
const emptyStates = wrapper.findAll('empty-state-stub,emptystate-stub,[data-testid="empty-state"]')
|
||||
const html = wrapper.html()
|
||||
expect(html).toContain('icon="folder"')
|
||||
wrapper.unmount()
|
||||
})
|
||||
|
||||
it('topics empty: EmptyState size=sm with icon=tag', async () => {
|
||||
vi.doMock('../../../api/client.js', () => ({
|
||||
listFolders: vi.fn().mockResolvedValue({ items: [] }),
|
||||
getSharedWithMe: vi.fn().mockResolvedValue([]),
|
||||
listCloudConnections: vi.fn().mockResolvedValue({ items: [] }),
|
||||
listTopics: vi.fn().mockResolvedValue({ topics: [] }),
|
||||
getMyQuota: vi.fn().mockResolvedValue({ used_bytes: 0, limit_bytes: 104857600 }),
|
||||
}))
|
||||
|
||||
setActivePinia(createPinia())
|
||||
const { useTopicsStore } = await import('../../../stores/topics.js')
|
||||
const topicsStore = useTopicsStore()
|
||||
|
||||
const router = makeRouter()
|
||||
await router.push('/')
|
||||
await router.isReady()
|
||||
|
||||
const AppSidebar = (await import('../AppSidebar.vue')).default
|
||||
const wrapper = mount(AppSidebar, {
|
||||
global: { plugins: [router], stubs: { ...STUBS, EmptyState: false } },
|
||||
})
|
||||
await flushPromises()
|
||||
|
||||
topicsStore.topics = []
|
||||
topicsStore.loading = false
|
||||
await wrapper.vm.$nextTick()
|
||||
|
||||
const html = wrapper.html()
|
||||
expect(html).toContain('icon="tag"')
|
||||
wrapper.unmount()
|
||||
})
|
||||
|
||||
it('cloud empty: EmptyState size=sm with icon=cloud and a Settings link in #cta', async () => {
|
||||
vi.doMock('../../../api/client.js', () => ({
|
||||
listFolders: vi.fn().mockResolvedValue({ items: [] }),
|
||||
getSharedWithMe: vi.fn().mockResolvedValue([]),
|
||||
listCloudConnections: vi.fn().mockResolvedValue({ items: [] }),
|
||||
listTopics: vi.fn().mockResolvedValue({ topics: [] }),
|
||||
getMyQuota: vi.fn().mockResolvedValue({ used_bytes: 0, limit_bytes: 104857600 }),
|
||||
}))
|
||||
|
||||
setActivePinia(createPinia())
|
||||
const { useCloudConnectionsStore } = await import('../../../stores/cloudConnections.js')
|
||||
const cloudStore = useCloudConnectionsStore()
|
||||
|
||||
const router = makeRouter()
|
||||
await router.push('/')
|
||||
await router.isReady()
|
||||
|
||||
const AppSidebar = (await import('../AppSidebar.vue')).default
|
||||
const wrapper = mount(AppSidebar, {
|
||||
global: { plugins: [router], stubs: { ...STUBS, EmptyState: false } },
|
||||
})
|
||||
await flushPromises()
|
||||
|
||||
cloudStore.connections = []
|
||||
cloudStore.loading = false
|
||||
await wrapper.vm.$nextTick()
|
||||
|
||||
const html = wrapper.html()
|
||||
expect(html).toContain('icon="cloud"')
|
||||
wrapper.unmount()
|
||||
})
|
||||
})
|
||||
|
||||
describe('UX-14: AppSidebar no longer renders an inline "New" folder button', () => {
|
||||
it.todo('no <button> with text "New" exists in the rendered template')
|
||||
it.todo('startNewFolder/cancelNewFolder/submitNewFolder methods are removed')
|
||||
it.todo('newFolderName/showNewFolderInput state is removed')
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
vi.resetModules()
|
||||
})
|
||||
|
||||
it('no <button> with text "New" exists in the rendered template', async () => {
|
||||
vi.doMock('../../../api/client.js', () => ({
|
||||
listFolders: vi.fn().mockResolvedValue({ items: [] }),
|
||||
getSharedWithMe: vi.fn().mockResolvedValue([]),
|
||||
listCloudConnections: vi.fn().mockResolvedValue({ items: [] }),
|
||||
listTopics: vi.fn().mockResolvedValue({ topics: [] }),
|
||||
getMyQuota: vi.fn().mockResolvedValue({ used_bytes: 0, limit_bytes: 104857600 }),
|
||||
}))
|
||||
|
||||
setActivePinia(createPinia())
|
||||
|
||||
const router = makeRouter()
|
||||
await router.push('/')
|
||||
await router.isReady()
|
||||
|
||||
const AppSidebar = (await import('../AppSidebar.vue')).default
|
||||
const wrapper = mount(AppSidebar, {
|
||||
global: { plugins: [router], stubs: STUBS },
|
||||
})
|
||||
await flushPromises()
|
||||
|
||||
const buttons = wrapper.findAll('button')
|
||||
const newButtons = buttons.filter(b => b.text().trim() === 'New')
|
||||
expect(newButtons.length).toBe(0)
|
||||
wrapper.unmount()
|
||||
})
|
||||
|
||||
it('startNewFolder/cancelNewFolder/submitNewFolder methods are removed', async () => {
|
||||
vi.doMock('../../../api/client.js', () => ({
|
||||
listFolders: vi.fn().mockResolvedValue({ items: [] }),
|
||||
getSharedWithMe: vi.fn().mockResolvedValue([]),
|
||||
listCloudConnections: vi.fn().mockResolvedValue({ items: [] }),
|
||||
listTopics: vi.fn().mockResolvedValue({ topics: [] }),
|
||||
getMyQuota: vi.fn().mockResolvedValue({ used_bytes: 0, limit_bytes: 104857600 }),
|
||||
}))
|
||||
|
||||
setActivePinia(createPinia())
|
||||
|
||||
const router = makeRouter()
|
||||
await router.push('/')
|
||||
await router.isReady()
|
||||
|
||||
const AppSidebar = (await import('../AppSidebar.vue')).default
|
||||
const wrapper = mount(AppSidebar, {
|
||||
global: { plugins: [router], stubs: STUBS },
|
||||
})
|
||||
await flushPromises()
|
||||
|
||||
expect(wrapper.vm.startNewFolder).toBeUndefined()
|
||||
expect(wrapper.vm.cancelNewFolder).toBeUndefined()
|
||||
expect(wrapper.vm.submitNewFolder).toBeUndefined()
|
||||
wrapper.unmount()
|
||||
})
|
||||
|
||||
it('newFolderName/showNewFolderInput state is removed (no inline folder input in DOM)', async () => {
|
||||
vi.doMock('../../../api/client.js', () => ({
|
||||
listFolders: vi.fn().mockResolvedValue({ items: [] }),
|
||||
getSharedWithMe: vi.fn().mockResolvedValue([]),
|
||||
listCloudConnections: vi.fn().mockResolvedValue({ items: [] }),
|
||||
listTopics: vi.fn().mockResolvedValue({ topics: [] }),
|
||||
getMyQuota: vi.fn().mockResolvedValue({ used_bytes: 0, limit_bytes: 104857600 }),
|
||||
}))
|
||||
|
||||
setActivePinia(createPinia())
|
||||
|
||||
const router = makeRouter()
|
||||
await router.push('/')
|
||||
await router.isReady()
|
||||
|
||||
const AppSidebar = (await import('../AppSidebar.vue')).default
|
||||
const wrapper = mount(AppSidebar, {
|
||||
global: { plugins: [router], stubs: STUBS },
|
||||
})
|
||||
await flushPromises()
|
||||
|
||||
const inputs = wrapper.findAll('input[placeholder="Folder name"]')
|
||||
expect(inputs.length).toBe(0)
|
||||
wrapper.unmount()
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user