~ knotless Search/
↑↓ move↵ open⇧↵ all results

chadtmiller.com/pds.js

An atproto PDS in JavaScript, for Cloudflare Workers and Node

0
0
main
104 lines · 3.1 KB Raw History
/**
 * 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);
  });
});