import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest' import { mount } from '@vue/test-utils' import { nextTick } from 'vue' import BreadcrumbBar from '../BreadcrumbBar.vue' const STUBS = { global: { stubs: { AppIcon: true } } } function seg(id, label) { return { id, label } } function staticSeg(label) { return { label } } describe('BreadcrumbBar', () => { it('renders rootLabel button when showRoot=true', () => { const w = mount(BreadcrumbBar, { props: { rootLabel: 'Home', segments: [] }, ...STUBS, }) expect(w.find('button').text()).toBe('Home') }) it('does NOT render root button when showRoot=false', () => { const w = mount(BreadcrumbBar, { props: { showRoot: false, segments: [staticSeg('Users')] }, ...STUBS, }) expect(w.findAll('button').length).toBe(0) }) it('clicking root button emits navigate(null)', async () => { const w = mount(BreadcrumbBar, { props: { segments: [] }, ...STUBS, }) await w.find('button').trigger('click') expect(w.emitted('navigate')).toBeTruthy() expect(w.emitted('navigate')[0]).toEqual([null]) }) it('last segment renders as a non-clickable ', () => { const w = mount(BreadcrumbBar, { props: { segments: [seg('a', 'A'), seg('b', 'B')] }, ...STUBS, }) const text = w.text() expect(text).toContain('B') const buttonWithB = w.findAll('button').filter(b => b.text() === 'B') expect(buttonWithB.length).toBe(0) }) it('clicking intermediate segment emits navigate(segment.id)', async () => { const w = mount(BreadcrumbBar, { props: { segments: [seg('r1', 'Root'), seg('f1', 'Test')] }, ...STUBS, }) const buttons = w.findAll('button') const rootBtn = buttons.find(b => b.text() === 'Root') await rootBtn.trigger('click') expect(w.emitted('navigate')).toBeTruthy() expect(w.emitted('navigate')[0]).toEqual(['r1']) }) it('segments without id render as plain text even when intermediate', () => { const w = mount(BreadcrumbBar, { props: { showRoot: false, segments: [staticSeg('Admin'), staticSeg('Users')], }, ...STUBS, }) expect(w.findAll('button').length).toBe(0) }) it('>4 segments collapse to first + ellipsis + last two', () => { const segments = [ seg('a', 'Alpha'), seg('b', 'Beta'), seg('c', 'Gamma'), seg('d', 'Delta'), seg('e', 'Epsilon'), ] const w = mount(BreadcrumbBar, { props: { segments }, ...STUBS, }) const text = w.text() expect(text).toContain('Alpha') expect(text).toContain('…') expect(text).toContain('Delta') expect(text).toContain('Epsilon') expect(text).not.toContain('Beta') expect(text).not.toContain('Gamma') }) it('rootLabel defaults to "Home"', () => { const w = mount(BreadcrumbBar, { props: { segments: [] }, ...STUBS, }) expect(w.find('button').text()).toBe('Home') }) it('custom rootLabel "Cloud" renders correctly', () => { const w = mount(BreadcrumbBar, { props: { rootLabel: 'Cloud', segments: [] }, ...STUBS, }) expect(w.find('button').text()).toBe('Cloud') }) it('renders chevronRight separator between segments', () => { const w = mount(BreadcrumbBar, { props: { segments: [seg('a', 'A'), seg('b', 'B')] }, ...STUBS, }) const appIcons = w.findAll('app-icon-stub') expect(appIcons.length).toBeGreaterThanOrEqual(1) }) }) describe('BreadcrumbBar freshness indicator', () => { beforeEach(() => { vi.useFakeTimers() }) afterEach(() => { vi.useRealTimers() }) it('shows refreshing spinner with accessible label when folderFreshness="refreshing"', () => { const w = mount(BreadcrumbBar, { props: { segments: [], folderFreshness: 'refreshing' }, ...STUBS, }) const indicator = w.find('[data-freshness="refreshing"]') expect(indicator.exists()).toBe(true) expect(indicator.attributes('aria-label')).toBe('Refreshing folder contents') }) it('shows fresh checkmark with accessible label when folderFreshness="fresh"', async () => { const w = mount(BreadcrumbBar, { props: { segments: [], folderFreshness: 'fresh' }, ...STUBS, }) // Wait for watcher to update showFreshMark await nextTick() const fresh = w.find('[data-freshness="fresh"]') expect(fresh.exists()).toBe(true) expect(fresh.attributes('aria-label')).toBe('Folder contents are up to date') }) it('fresh indicator fades after 3 seconds', async () => { const w = mount(BreadcrumbBar, { props: { segments: [], folderFreshness: 'fresh' }, ...STUBS, }) await nextTick() expect(w.vm.showFreshMark).toBe(true) // Advance timer by 3s vi.advanceTimersByTime(3001) await nextTick() expect(w.vm.showFreshMark).toBe(false) }) it('warning indicator persists when folderFreshness="stale"', async () => { const w = mount(BreadcrumbBar, { props: { segments: [], folderFreshness: 'stale', lastRefreshedAt: '2026-01-01T00:00:00Z' }, ...STUBS, }) const stale = w.find('[data-freshness="stale"]') expect(stale.exists()).toBe(true) }) it('stale warning banner is shown with last-update info', async () => { const w = mount(BreadcrumbBar, { props: { segments: [], folderFreshness: 'stale', lastRefreshedAt: '2026-01-01T00:00:00Z' }, ...STUBS, }) const banner = w.find('[data-test="stale-warning-banner"]') expect(banner.exists()).toBe(true) expect(banner.text()).toContain('Could not refresh folder contents') }) it('no freshness indicator shown when folderFreshness is null', () => { const w = mount(BreadcrumbBar, { props: { segments: [], folderFreshness: null }, ...STUBS, }) expect(w.find('[data-test="freshness-indicator"]').exists()).toBe(false) }) it('freshness indicator role="status" for accessible announcement', async () => { const w = mount(BreadcrumbBar, { props: { segments: [], folderFreshness: 'refreshing' }, ...STUBS, }) const indicator = w.find('[data-freshness="refreshing"]') expect(indicator.attributes('role')).toBe('status') }) })