/** * Folding a commit's changed paths back into the tree they came from. */ import { describe, expect, it } from 'vitest'; import { buildFileTree, filterFiles } from '../src/lib/file-tree.js'; /** A compact shape for comparing trees: "name" for a file, "name/" for a dir. */ function shape(nodes) { return nodes.map((node) => node.type === 'dir' ? { [`${node.name}/`]: shape(node.children) } : node.name, ); } const tree = (paths) => buildFileTree(paths.map((path) => ({ path }))); describe('buildFileTree', () => { it('keeps a root file at the root', () => { expect(shape(tree(['README.md']))).toEqual(['README.md']); }); it('nests a file under its directory', () => { expect(shape(tree(['src/index.js', 'README.md']))).toEqual([ { 'src/': ['index.js'] }, 'README.md', ]); }); it('folds a chain of directories with nothing else in them', () => { // The step through .github says nothing on its own, so it reads as one // row the way git and every diff viewer show it. expect(shape(tree(['.github/workflows/containers.yml']))).toEqual([ { '.github/workflows/': ['containers.yml'] }, ]); }); it('stops folding where a directory holds more than one thing', () => { expect(shape(tree(['a/b/one.txt', 'a/c/two.txt']))).toEqual([ { 'a/': [{ 'b/': ['one.txt'] }, { 'c/': ['two.txt'] }] }, ]); }); it('does not fold a directory holding one file', () => { // Folding here would hide the directory the file lives in. expect(shape(tree(['a/b/one.txt', 'a/two.txt']))).toEqual([ { 'a/': [{ 'b/': ['one.txt'] }, 'two.txt'] }, ]); }); it('sorts directories before files, each by name', () => { expect(shape(tree(['z.txt', 'a.txt', 'src/b.js', 'docs/c.md']))).toEqual([ { 'docs/': ['c.md'] }, { 'src/': ['b.js'] }, 'a.txt', 'z.txt', ]); }); it('carries the caller entry and the whole path on each file', () => { const files = [{ path: 'a/b/c.txt', status: 'added' }]; const [dir] = buildFileTree(files); const [file] = /** @type {any} */ (dir).children; expect(file.path).toBe('a/b/c.txt'); expect(file.file).toBe(files[0]); }); it('reads no files as no tree', () => { expect(buildFileTree([])).toEqual([]); }); it('groups several files in one folded directory', () => { expect(shape(tree(['a/b/one.txt', 'a/b/two.txt']))).toEqual([ { 'a/b/': ['one.txt', 'two.txt'] }, ]); }); }); describe('filterFiles', () => { const files = [ { path: 'packages/git/src/http.js' }, { path: 'packages/core/src/pds.js' }, { path: 'README.md' }, ]; it('matches anywhere in the path', () => { expect(filterFiles(files, 'git').map((f) => f.path)).toEqual([ 'packages/git/src/http.js', ]); expect(filterFiles(files, 'src/').length).toBe(2); }); it('ignores case and surrounding space', () => { expect(filterFiles(files, ' README ').map((f) => f.path)).toEqual([ 'README.md', ]); }); it('matches everything when nothing is typed', () => { expect(filterFiles(files, '')).toBe(files); expect(filterFiles(files, ' ')).toBe(files); }); });