import { afterEach, describe, expect, it } from 'vitest' import { effectScope, ref, type EffectScope } from 'vue' import { usePickerNavigation } from '../../composables/filePicker/usePickerNavigation' import { usePickerSelection } from '../../composables/filePicker/usePickerSelection' import type { PickerCapability, PickerNode, PickerSelectionMode } from '../../types/filePicker' const scopes: EffectScope[] = [] afterEach(() => scopes.splice(0).forEach(scope => scope.stop())) function setup(nodes: PickerNode[]) { const scope = effectScope() scopes.push(scope) return scope.run(() => { const capability = ref({ formats: ['raw'] }) const hidden = ref('') const mode = ref('folder+files') const nav = usePickerNavigation(() => ({ listRoot: async () => [], listChildren: async () => [], search: async () => [], }), () => ({}), node => node.path !== hidden.value) nav.register(nodes) const selection = usePickerSelection(nav, () => mode.value, () => capability.value, () => false) const progress = (path: string) => selection.folderProgress(nav.nodes.value.get(path)!) return { nav, selection, capability, hidden, mode, progress } })! } describe('file picker folder progress', () => { it('tracks exclusions, selection modes, replaced paths and changing ancestors', () => { const { nav, selection, capability, hidden, mode, progress } = setup([ { kind: 'folder', path: 'root', name: 'root', fileCount: 2 }, { kind: 'folder', path: 'a', name: 'a', parentPath: 'root', fileCount: 2 }, { kind: 'folder', path: 'b', name: 'b', parentPath: 'root', fileCount: 0 }, { kind: 'file', path: 'f', name: 'f.raw', format: 'raw', parentPath: 'a' }, { kind: 'file', path: 'g', name: 'g.txt', format: 'txt', parentPath: 'a' }, ]) selection.keys.value.add('root') expect(progress('root')).toBe('1 / 2') expect(progress('a')).toBe('1 / 2') expect(progress('b')).toBe('0 / 0') selection.dropped.value.add('a') expect(progress('root')).toBe('0 / 2') expect(progress('a')).toBe('0 / 2') selection.dropped.value.clear() capability.value.formats.push('txt') expect(progress('a')).toBe('2 / 2') nav.register([{ kind: 'file', path: 'f', name: 'f.raw', format: 'raw', parentPath: 'b' }]) expect(progress('a')).toBe('1 / 2') expect(progress('b')).toBe('1 / 0') expect(progress('root')).toBe('2 / 2') nav.nodes.value.get('b')!.parentPath = null expect(progress('root')).toBe('1 / 2') selection.keys.value.add('b') expect(progress('b')).toBe('1 / 0') hidden.value = 'b' expect(progress('b')).toBe('0 / 0') mode.value = 'folder' expect(progress('a')).toBe('0 / 2') expect(progress('root')).toBe('1 / 2') nav.nodes.value.delete('g') expect(progress('root')).toBe('0 / 2') expect(selection.folderProgress({ kind: 'folder', path: 'unknown', name: 'unknown' })).toBe('') expect(selection.folderProgress(nav.nodes.value.get('f')!)).toBe('') }) it('counts each file once for cyclic or missing ancestors', () => { const { nav, selection, progress } = setup([ { kind: 'folder', path: 'a', name: 'a', parentPath: 'b', fileCount: 1 }, { kind: 'folder', path: 'b', name: 'b', parentPath: 'a', fileCount: 1 }, { kind: 'file', path: 'f', name: 'f.raw', format: 'raw', parentPath: 'a' }, ]) selection.keys.value.add('a') expect(progress('a')).toBe('1 / 1') expect(progress('b')).toBe('1 / 1') nav.nodes.value.delete('a') expect(selection.folderProgress({ kind: 'folder', path: 'a', name: 'a', fileCount: 1 })).toBe('1 / 1') expect(progress('b')).toBe('0 / 1') }) it('does not rescan every selected file for each folder row', () => { const count = 200 const { nav, selection, progress } = setup([ { kind: 'folder', path: 'root', name: 'root', fileCount: count }, ]) let parentReads = 0 for (let i = 0; i < count; i++) { nav.register([{ kind: 'folder', path: `d${i}`, name: `d${i}`, parentPath: 'root', fileCount: 1 }]) nav.nodes.value.set(`f${i}`, { kind: 'file', path: `f${i}`, name: `${i}.raw`, format: 'raw', get parentPath() { parentReads++; return `d${i}` }, }) } selection.keys.value.add('root') expect(selection.chosen.value.files).toHaveLength(count) parentReads = 0 for (let i = 0; i < count; i++) expect(progress(`d${i}`)).toBe('1 / 1') expect(parentReads).toBeLessThanOrEqual(count * 2) }) })