feat(10-06): wire breadcrumb mapping + toast call sites; delete FolderBreadcrumb

- FileManagerView: add mappedBreadcrumb computed ({id,label} from foldersStore.breadcrumb)
- FileManagerView: import useToastStore; add toast.show() to doMove, doDeleteDoc, onFilesSelected
- FileManagerView: remove console.error from doMove/doDeleteDoc (toast communicates errors)
- CloudFolderView: add mappedBreadcrumb computed; pass to StorageBrowser :breadcrumb
- Delete FolderBreadcrumb.vue + FolderBreadcrumb.test.js (no dead code per CLAUDE.md)
- FileManagerView.test.js: replace dead FolderBreadcrumb mock with BreadcrumbBar mock
- StorageBrowser.skeleton.test.js: remove unused FolderBreadcrumb stub
- Full suite: 164/164 pass, 0 failures
This commit is contained in:
curo1305
2026-06-15 20:26:06 +02:00
parent d040e77548
commit 9ea51d6401
6 changed files with 35 additions and 183 deletions
@@ -1,68 +0,0 @@
<template>
<nav aria-label="Folder navigation">
<ol class="flex items-center gap-1 text-sm flex-wrap">
<!-- Root "Home" segment -->
<li class="flex items-center gap-1">
<button
@click="emit('navigate', null)"
class="text-indigo-600 hover:underline font-medium"
>
Home
</button>
</li>
<template v-for="(segment, idx) in visibleSegments" :key="segment.id ?? 'ellipsis-' + idx">
<!-- Separator -->
<li class="shrink-0" aria-hidden="true">
<svg class="w-3 h-3 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
</svg>
</li>
<!-- Ellipsis (non-navigable) -->
<li v-if="segment.id === 'ellipsis'" class="flex items-center">
<span class="px-2 py-1 text-gray-400"></span>
</li>
<!-- Last segment (current folder, non-clickable) -->
<li v-else-if="idx === visibleSegments.length - 1" class="flex items-center">
<span class="text-gray-900 font-medium">{{ segment.name }}</span>
</li>
<!-- Clickable segment -->
<li v-else class="flex items-center">
<button
@click="emit('navigate', segment.id)"
class="text-indigo-600 hover:underline font-medium"
>
{{ segment.name }}
</button>
</li>
</template>
</ol>
</nav>
</template>
<script setup>
import { computed } from 'vue'
const props = defineProps({
segments: {
type: Array,
default: () => [],
},
})
const emit = defineEmits(['navigate'])
const visibleSegments = computed(() => {
if (props.segments.length > 4) {
return [
props.segments[0],
{ id: 'ellipsis', name: '…' },
...props.segments.slice(-2),
]
}
return props.segments
})
</script>
@@ -1,108 +0,0 @@
import { describe, it, expect } from 'vitest'
import { mount } from '@vue/test-utils'
import FolderBreadcrumb from '../FolderBreadcrumb.vue'
function seg(id, name) { return { id, name } }
describe('FolderBreadcrumb', () => {
it('always renders a "Home" / "Folders" root button', () => {
const w = mount(FolderBreadcrumb, { props: { segments: [] } })
expect(w.find('button').exists()).toBe(true)
})
it('clicking root button emits navigate(null)', async () => {
const w = mount(FolderBreadcrumb, { props: { segments: [] } })
await w.find('button').trigger('click')
expect(w.emitted('navigate')).toBeTruthy()
expect(w.emitted('navigate')[0]).toEqual([null])
})
it('renders intermediate segments as clickable buttons', () => {
const w = mount(FolderBreadcrumb, {
props: { segments: [seg('r1', 'Root'), seg('f1', 'Test')] },
})
// "Root" is intermediate (not last), "Test" is last (plain text)
const buttons = w.findAll('button')
// first button is "Home/Folders", second is "Root"
expect(buttons.length).toBe(2)
expect(buttons[1].text()).toBe('Root')
})
it('clicking intermediate segment emits navigate(id)', async () => {
const w = mount(FolderBreadcrumb, {
props: { segments: [seg('r1', 'Root'), seg('f1', 'Test')] },
})
const buttons = w.findAll('button')
await buttons[1].trigger('click') // "Root" button
expect(w.emitted('navigate')).toBeTruthy()
expect(w.emitted('navigate')[0]).toEqual(['r1'])
})
it('renders last segment as plain non-interactive text', () => {
const w = mount(FolderBreadcrumb, {
props: { segments: [seg('r1', 'Root'), seg('f1', 'Test')] },
})
// Last segment "Test" should be a <span>, not a button
const spans = w.findAll('span')
const lastSpan = spans.find(s => s.text() === 'Test')
expect(lastSpan).toBeTruthy()
})
it('last segment is NOT clickable (no navigate event)', async () => {
const w = mount(FolderBreadcrumb, {
props: { segments: [seg('r1', 'Root'), seg('f1', 'Test')] },
})
const spans = w.findAll('span')
const lastSpan = spans.find(s => s.text() === 'Test')
if (lastSpan) await lastSpan.trigger('click')
// navigate should NOT have been emitted by clicking the last segment
const navigateEvents = (w.emitted('navigate') || []).filter(e => e[0] === 'f1')
expect(navigateEvents.length).toBe(0)
})
it('single segment: just root button + last segment as text', () => {
const w = mount(FolderBreadcrumb, {
props: { segments: [seg('f1', 'OnlyFolder')] },
})
// Only the "Home" button and "OnlyFolder" as plain text
const buttons = w.findAll('button')
expect(buttons.length).toBe(1) // just "Home"
expect(w.text()).toContain('OnlyFolder')
})
it('collapses >4 segments with ellipsis, preserving first and last two', () => {
const segments = [
seg('a', 'A'), seg('b', 'B'), seg('c', 'C'),
seg('d', 'D'), seg('e', 'E'),
]
const w = mount(FolderBreadcrumb, { props: { segments } })
const text = w.text()
expect(text).toContain('A') // first preserved
expect(text).toContain('…') // ellipsis present
expect(text).toContain('D') // second-to-last preserved
expect(text).toContain('E') // last preserved
expect(text).not.toContain('B') // middle segments collapsed
expect(text).not.toContain('C')
})
it('3 segments: all rendered without ellipsis', () => {
const segments = [seg('a', 'A'), seg('b', 'B'), seg('c', 'C')]
const w = mount(FolderBreadcrumb, { props: { segments } })
const text = w.text()
expect(text).toContain('A')
expect(text).toContain('B')
expect(text).toContain('C')
expect(text).not.toContain('…')
})
it('deep 3-level path: clicking middle segment navigates correctly', async () => {
const segments = [seg('root', 'Root'), seg('mid', 'Mid'), seg('cur', 'Current')]
const w = mount(FolderBreadcrumb, { props: { segments } })
const buttons = w.findAll('button')
// buttons[0] = Home, buttons[1] = Root, buttons[2] = Mid
await buttons[2].trigger('click')
const events = w.emitted('navigate') || []
const midClicks = events.filter(e => e[0] === 'mid')
expect(midClicks.length).toBe(1)
})
})
@@ -5,7 +5,6 @@ import StorageBrowser from '../StorageBrowser.vue'
const globalStubs = {
BreadcrumbBar: true,
FolderBreadcrumb: true,
SearchBar: true,
SortControls: true,
DropZone: true,