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

chadtmiller.com/pds.js

An atproto PDS in JavaScript, for Cloudflare Workers and Node

0
0
main
181 lines · 6.0 KB Raw History
/**
 * The address of a line in a diff: what a click writes into the fragment, and
 * what a reader arriving on that fragment gets back.
 */

import { describe, expect, it } from 'vitest';
import {
  extendSelection,
  extendSpan,
  inSelection,
  lineFragment,
  lineId,
  parseLineFragment,
  parseSourceFragment,
  sourceFragment,
  sourceLineId,
} from '../src/lib/line-links.js';

/** @param {number|null} oldLine @param {number|null} newLine */
const row = (oldLine, newLine) => ({ oldLine, newLine });

describe('lineFragment and parseLineFragment', () => {
  it('carries one line there and back', () => {
    const selection = { path: 'src/pds.js', side: 'new', start: 12, end: 12 };
    const fragment = lineFragment(selection);
    expect(fragment).toBe('line-src/pds.js-new:12');
    expect(parseLineFragment(fragment)).toEqual(selection);
  });

  it('carries a span there and back', () => {
    const selection = { path: 'src/pds.js', side: 'old', start: 12, end: 20 };
    const fragment = lineFragment(selection);
    expect(fragment).toBe('line-src/pds.js-old:12-20');
    expect(parseLineFragment(fragment)).toEqual(selection);
  });

  // A path holds hyphens more often than not, and the fragment ends with a
  // number that could be read as part of one.
  it('reads a path with hyphens in it', () => {
    const selection = {
      path: 'packages/git-ui/src/diff-file.jsx',
      side: 'new',
      start: 3,
      end: 9,
    };
    expect(parseLineFragment(lineFragment(selection))).toEqual(selection);
  });

  it('answers nothing to a fragment naming something else', () => {
    expect(parseLineFragment('comment-src/pds.js-new:12')).toBeNull();
    expect(parseLineFragment('')).toBeNull();
    expect(parseLineFragment('line-src/pds.js-both:12')).toBeNull();
  });

  it('orders the ends of a span written backwards', () => {
    expect(parseLineFragment('line-a.js-new:20-12')).toEqual({
      path: 'a.js',
      side: 'new',
      start: 12,
      end: 20,
    });
  });

  it('names the row a link lands on', () => {
    expect(lineId('src/pds.js', 'new:12')).toBe('line-src/pds.js-new:12');
  });
});

describe('extendSelection', () => {
  const at = { path: 'a.js', side: 'new', at: 20 };

  it('selects one line on a plain click', () => {
    expect(extendSelection(null, at)).toEqual({
      path: 'a.js',
      side: 'new',
      start: 20,
      end: 20,
    });
  });

  it('takes the span from the line already selected', () => {
    const first = { path: 'a.js', side: 'new', start: 12, end: 12 };
    expect(extendSelection(first, at, true)).toEqual({
      path: 'a.js',
      side: 'new',
      start: 12,
      end: 20,
    });
  });

  // The line already selected is one end of the span, so a shift-click above
  // it takes the lines between rather than starting again.
  it('takes a span upwards from the line already selected', () => {
    const first = { path: 'a.js', side: 'new', start: 30, end: 34 };
    expect(extendSelection(first, at, true)).toEqual({
      path: 'a.js',
      side: 'new',
      start: 20,
      end: 30,
    });
  });

  it('starts again on another file or another side', () => {
    const elsewhere = { path: 'b.js', side: 'new', start: 12, end: 12 };
    expect(extendSelection(elsewhere, at, true).start).toBe(20);
    const otherSide = { path: 'a.js', side: 'old', start: 12, end: 12 };
    expect(extendSelection(otherSide, at, true).start).toBe(20);
  });
});

describe('inSelection', () => {
  const selection = { path: 'a.js', side: 'new', start: 12, end: 20 };

  it('marks the rows the selection names', () => {
    expect(inSelection(selection, 'a.js', row(8, 12))).toBe(true);
    expect(inSelection(selection, 'a.js', row(null, 16))).toBe(true);
    expect(inSelection(selection, 'a.js', row(8, 11))).toBe(false);
  });

  it('marks nothing in another file, or with nothing selected', () => {
    expect(inSelection(selection, 'b.js', row(null, 16))).toBe(false);
    expect(inSelection(null, 'a.js', row(null, 16))).toBe(false);
  });

  // A deleted row is on the old side alone, which is the side it is addressed
  // by, so a selection on the new side passes over it.
  it('marks a deleted row only from the old side', () => {
    expect(inSelection(selection, 'a.js', row(16, null))).toBe(false);
    const old = { path: 'a.js', side: 'old', start: 12, end: 20 };
    expect(inSelection(old, 'a.js', row(16, null))).toBe(true);
  });
});

describe('sourceFragment and parseSourceFragment', () => {
  it('carries a line and a span there and back', () => {
    expect(sourceFragment({ start: 12, end: 12 })).toBe('L12');
    expect(sourceFragment({ start: 12, end: 20 })).toBe('L12-20');
    expect(parseSourceFragment('L12')).toEqual({ start: 12, end: 12 });
    expect(parseSourceFragment('L12-20')).toEqual({ start: 12, end: 20 });
  });

  // A forge writes the second number with its own L, and a reader pasting one
  // of those addresses means the same span.
  it('reads a span whose second number carries an L', () => {
    expect(parseSourceFragment('L12-L20')).toEqual({ start: 12, end: 20 });
  });

  it('answers nothing to a fragment naming something else', () => {
    expect(parseSourceFragment('line-a.js-new:12')).toBeNull();
    expect(parseSourceFragment('readme')).toBeNull();
  });

  it('names the row a link lands on', () => {
    expect(sourceLineId(12)).toBe('L12');
    expect(sourceLineId(12)).toBe(sourceFragment({ start: 12, end: 12 }));
  });
});

describe('extendSpan', () => {
  it('selects one line on a plain click', () => {
    expect(extendSpan({ start: 4, end: 9 }, 20)).toEqual({
      start: 20,
      end: 20,
    });
  });

  it('takes the span from the line already selected, either way', () => {
    expect(extendSpan({ start: 12, end: 12 }, 20, true)).toEqual({
      start: 12,
      end: 20,
    });
    expect(extendSpan({ start: 30, end: 34 }, 20, true)).toEqual({
      start: 20,
      end: 30,
    });
  });

  it('selects one line when there is nothing to reach from', () => {
    expect(extendSpan(null, 20, true)).toEqual({ start: 20, end: 20 });
  });
});