| 1 | /** |
| 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. |
| 3 | * |
| 4 | * This source code is licensed under the MIT license found in the |
| 5 | * LICENSE file in the root directory of this source tree. |
| 6 | */ |
| 7 | |
| 8 | import type {FileRev} from '../fileStackState'; |
| 9 | |
| 10 | import {FileStackState, Source} from '../fileStackState'; |
| 11 | |
| 12 | describe('FileStackState', () => { |
| 13 | const commonContents = ['b\nc\nd\n', 'a\nb\nc\nd\n', 'a\nb\nc\nd\ne\n', 'a\nc\nd\ne\n']; |
| 14 | const revLength = commonContents.length as FileRev; |
| 15 | |
| 16 | it('converts between formats', () => { |
| 17 | const stack = new FileStackState(commonContents); |
| 18 | const formats = [ |
| 19 | (s: FileStackState) => |
| 20 | new FileStackState(Source({type: 'plain', value: s.convertToPlainText(), revLength})), |
| 21 | (s: FileStackState) => |
| 22 | new FileStackState(Source({type: 'linelog', value: s.convertToLineLog(), revLength})), |
| 23 | (s: FileStackState) => |
| 24 | new FileStackState(Source({type: 'flatten', value: s.convertToFlattenLines(), revLength})), |
| 25 | ]; |
| 26 | formats.forEach(fromFormat => { |
| 27 | const fromState = fromFormat(stack); |
| 28 | formats.forEach(toFormat => { |
| 29 | const toState = toFormat(fromState); |
| 30 | expect(toState.revs()).toStrictEqual([...commonContents.keys()]); |
| 31 | expect(toState.revs().map(rev => toState.getRev(rev))).toStrictEqual(commonContents); |
| 32 | }); |
| 33 | }); |
| 34 | }); |
| 35 | |
| 36 | // Some features are thin wrappers around linelog. Their tests overlap |
| 37 | // with linelog tests. We prefer corner cases to be tested at the |
| 38 | // bottom layer. If you'd like to add more corner cases, consider |
| 39 | // adding them in linelog.test.ts. |
| 40 | |
| 41 | it('analyses dependency', () => { |
| 42 | const stack = new FileStackState(['b\n', 'a\nb\n', 'a\nb\nc\n']); |
| 43 | expect(stack.calculateDepMap()).toStrictEqual( |
| 44 | new Map([ |
| 45 | [1, new Set([0])], |
| 46 | [2, new Set([0])], |
| 47 | ]), |
| 48 | ); |
| 49 | }); |
| 50 | |
| 51 | it('supports blame', () => { |
| 52 | const stack = new FileStackState(commonContents); |
| 53 | expect(stack.blame(0 as FileRev)).toStrictEqual([0, 0, 0]); |
| 54 | expect(stack.blame(2 as FileRev)).toStrictEqual([1, 0, 0, 0, 2]); |
| 55 | }); |
| 56 | |
| 57 | it('supports editing text without affecting the stack', () => { |
| 58 | const stack = new FileStackState(commonContents).editText(0 as FileRev, 'b\nC\nD\n', false); |
| 59 | expect(stack.getRev(0 as FileRev)).toBe('b\nC\nD\n'); |
| 60 | expect(stack.getRev(1 as FileRev)).toBe('a\nb\nc\nd\n'); |
| 61 | }); |
| 62 | |
| 63 | it('supports editing text and updating the stack', () => { |
| 64 | const stack = new FileStackState(commonContents).editText(0 as FileRev, 'b\nC\nD\n', true); |
| 65 | expect(stack.getRev(0 as FileRev)).toBe('b\nC\nD\n'); |
| 66 | expect(stack.getRev(1 as FileRev)).toBe('a\nb\nC\nD\n'); |
| 67 | }); |
| 68 | |
| 69 | it('supports editing chunk at the given rev', () => { |
| 70 | // Edit rev 1 from rev 0's line ranges. |
| 71 | const stack = new FileStackState(commonContents).editChunk(0 as FileRev, 1, 3, 1 as FileRev, [ |
| 72 | 'C\n', |
| 73 | 'D\n', |
| 74 | ]); |
| 75 | // rev 0 is not changed. |
| 76 | expect(stack.getRev(0 as FileRev)).toBe('b\nc\nd\n'); |
| 77 | // rev 1 is edited. |
| 78 | expect(stack.getRev(1 as FileRev)).toBe('a\nb\nC\nD\n'); |
| 79 | }); |
| 80 | |
| 81 | it('supports remapping revs', () => { |
| 82 | const stack = new FileStackState(['a\n', 'a\nb\n', 'z\na\nb\n']).remapRevs( |
| 83 | new Map([ |
| 84 | [1 as FileRev, 2 as FileRev], |
| 85 | [2 as FileRev, 1 as FileRev], |
| 86 | ]), |
| 87 | ); |
| 88 | expect(stack.getRev(1 as FileRev)).toBe('z\na\n'); |
| 89 | expect(stack.getRev(2 as FileRev)).toBe('z\na\nb\n'); |
| 90 | }); |
| 91 | |
| 92 | it('supports moving lines between revs', () => { |
| 93 | let stack = new FileStackState(commonContents); |
| 94 | // Move +a from rev 1 to rev 2 (->). |
| 95 | stack = stack.moveLines(1 as FileRev, 0, 1, [], [1 as FileRev]); |
| 96 | expect(stack.getRev(1 as FileRev)).toBe('b\nc\nd\n'); |
| 97 | // Move -b from rev 3 (present in rev 2) to rev 2 (present in rev 1) (<-). |
| 98 | stack = stack.moveLines(2 as FileRev, 1, 2, [], [2 as FileRev]); |
| 99 | expect(stack.getRev(2 as FileRev)).toBe('a\nc\nd\ne\n'); |
| 100 | // Move +e from rev 2 to rev 1 (<-). |
| 101 | stack = stack.moveLines(2 as FileRev, 3, 4, [1 as FileRev], []); |
| 102 | expect(stack.getRev(1 as FileRev)).toBe('b\nc\nd\ne\n'); |
| 103 | expect(stack.convertToPlainText().toArray()).toStrictEqual([ |
| 104 | 'b\nc\nd\n', |
| 105 | 'b\nc\nd\ne\n', |
| 106 | 'a\nc\nd\ne\n', |
| 107 | 'a\nc\nd\ne\n', |
| 108 | ]); |
| 109 | }); |
| 110 | |
| 111 | it('supports appending text', () => { |
| 112 | let stack = new FileStackState([]); |
| 113 | expect(stack.source.revLength).toBe(0); |
| 114 | stack = stack.editText(0 as FileRev, 'a', false); |
| 115 | expect(stack.source.revLength).toBe(1); |
| 116 | stack = stack.editText(1 as FileRev, 'b', false); |
| 117 | expect(stack.source.revLength).toBe(2); |
| 118 | stack = stack.editText(2 as FileRev, 'c', true); |
| 119 | expect(stack.source.revLength).toBe(3); |
| 120 | expect(stack.getRev(2 as FileRev)).toBe('c'); |
| 121 | }); |
| 122 | }); |
| 123 | |