feat(12-03): breadcrumb freshness, formatRelativeTime, v0.1.6, docs

- BreadcrumbBar: refreshing spinner, fresh checkmark (fades 3s), stale warning banner
- Accessible labels and role=status for all freshness states
- formatters.js: add formatRelativeTime (shared, no duplication)
- Version bump 0.1.5 → 0.1.6 in backend/main.py and frontend/package.json
- AGENTS.md: update state, shared module map (formatRelativeTime)
- README.md: unified connection-root browsing and capability explanations feature text
- 17 BreadcrumbBar tests pass; 323 total; production build clean
This commit is contained in:
curo1305
2026-06-18 23:31:50 +02:00
parent 44244335a1
commit c6c0742267
7 changed files with 229 additions and 11 deletions
@@ -1,5 +1,6 @@
import { describe, it, expect } from 'vitest'
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 } } }
@@ -114,3 +115,83 @@ describe('BreadcrumbBar', () => {
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')
})
})