chadtmiller.com/pds.js
An atproto PDS in JavaScript, for Cloudflare Workers and Node
0
0
/**
* 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 });
});
});