All files / diff/src/format/__tests__ util.ts

100% Statements 46/46
100% Branches 18/18
100% Functions 4/4
100% Lines 39/39

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 7416x 16x       16x 8633x 159860x 8633x             16x         12864x 12864x         16x 6431x 6431x 6431x 6431x                         16x 1005x 1005x 1005x 1758x 1758x 4671x 1758x 1758x 1758x 1758x 13992x 10702x 10702x   13992x 3290x 3290x 10702x 7602x 7602x   13992x   1758x 1758x   4542x 1005x    
import * as lines from '../../lines';
import {diffKeys} from '../hunks';
import {type Hunk, HUNK_OP_TYPE} from '../types';
 
/** Serializers yield chunks; tests compare whole documents. */
export const text = (chunks: Iterable<string>): string => {
  let out = '';
  for (const chunk of chunks) out += chunk;
  return out;
};
 
/**
 * A file, as a command reads one: lines without terminators plus whether the
 * last one is terminated. `'a\nb'` is two lines, the second unterminated.
 */
export class File {
  public readonly lines: string[];
  public readonly noEol: boolean;
 
  constructor(content: string) {
    this.noEol = content !== '' && !content.endsWith('\n');
    this.lines = content === '' ? [] : (this.noEol ? content : content.slice(0, -1)).split('\n');
  }
}
 
/** The whole pipeline a `diff` command runs, up to the writers. */
export const diff = (a: string, b: string) => {
  const src = new File(a);
  const dst = new File(b);
  const patch = lines.diff(diffKeys(src.lines, src.noEol), diffKeys(dst.lines, dst.noEol));
  return {
    src: src.lines,
    dst: dst.lines,
    patch,
    opts: {srcNoEol: src.noEol, dstNoEol: dst.noEol},
  };
};
 
/**
 * Replays hunks against `src`, checking every line number and context line on
 * the way. A hunk set can be plausible in every printed column and still be
 * unapplicable; this is the property that catches it.
 */
export const replay = (src: string[], hunks: Hunk[]): string[] => {
  const out: string[] = [];
  let si = 0;
  for (const hunk of hunks) {
    const start = hunk.oldCount ? hunk.oldStart - 1 : hunk.oldStart;
    expect(start).toBeGreaterThanOrEqual(si);
    while (si < start) out.push(src[si++]);
    expect(hunk.newStart).toBe(hunk.newCount ? out.length + 1 : out.length);
    let oldSeen = 0;
    let newSeen = 0;
    for (const line of hunk.lines) {
      if (line.op !== HUNK_OP_TYPE.INS) {
        expect(src[si]).toBe(line.text);
        oldSeen++;
      }
      if (line.op === HUNK_OP_TYPE.INS) {
        out.push(line.text);
        newSeen++;
      } else if (line.op === HUNK_OP_TYPE.EQL) {
        out.push(src[si]);
        newSeen++;
      }
      if (line.op !== HUNK_OP_TYPE.INS) si++;
    }
    expect(oldSeen).toBe(hunk.oldCount);
    expect(newSeen).toBe(hunk.newCount);
  }
  while (si < src.length) out.push(src[si++]);
  return out;
};