@mudah-cli/testing 0.8.0 → 0.9.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"test-app.js","sourceRoot":"","sources":["../src/test-app.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"test-app.js","sourceRoot":"","sources":["../src/test-app.ts"],"names":[],"mappings":"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"}
@@ -1,7 +1,38 @@
1
1
  import { type Layout, type TreeNode } from '@mudah-cli/tui';
2
+ import { type Theme } from '@mudah-cli/ui';
3
+ import { type ColorExpectation } from './snapshot-assert.js';
2
4
  export interface TestTuiOptions {
3
5
  cols?: number;
4
6
  rows?: number;
7
+ /** Directory for snapshot files. Default: `test/fixtures` relative to CWD. */
8
+ snapshotDir?: string;
9
+ }
10
+ export type TestTuiAction = {
11
+ kind: 'send';
12
+ name: string;
13
+ ch?: string;
14
+ } | {
15
+ kind: 'paste';
16
+ text: string;
17
+ } | {
18
+ kind: 'click';
19
+ x: number;
20
+ y: number;
21
+ } | {
22
+ kind: 'wheel';
23
+ direction: 'up' | 'down';
24
+ } | {
25
+ kind: 'resize';
26
+ cols: number;
27
+ rows: number;
28
+ };
29
+ export interface TestTuiMeasure {
30
+ /** Wall time of the last `send`/`paste`/`click`/`wheel`/`resize` apply. */
31
+ sendMs: number;
32
+ /** Wall time of the snapshot render after that action. */
33
+ renderMs: number;
34
+ /** Number of recorded actions on the current history stack. */
35
+ actions: number;
5
36
  }
6
37
  /**
7
38
  * Headless TUI harness. No PTY. Mount a layout, inject keys and clicks,
@@ -12,12 +43,21 @@ export interface TestTuiOptions {
12
43
  * tui.send('down').send('enter');
13
44
  * expect(tui.snapshot()).toContain('▸');
14
45
  * expect(tui.tree().role).toBe('Column');
46
+ * tui.matchSnapshot('picker-01', 'happy path');
15
47
  * ```
16
48
  */
17
49
  export declare class TestTui {
18
50
  readonly root: Layout;
19
51
  cols: number;
20
52
  rows: number;
53
+ private readonly snapshotDir;
54
+ private readonly actions;
55
+ /** `snapshot()` after mount, then after each action. */
56
+ private readonly frames;
57
+ private historyIndex;
58
+ private replayFrame;
59
+ private lastSendMs;
60
+ private lastRenderMs;
21
61
  private constructor();
22
62
  static mount(root: Layout, options?: TestTuiOptions): TestTui;
23
63
  send(name: string, ch?: string): this;
@@ -25,7 +65,37 @@ export declare class TestTui {
25
65
  click(x: number, y: number): this;
26
66
  wheel(direction: 'up' | 'down'): this;
27
67
  resize(cols: number, rows: number): this;
68
+ /**
69
+ * Step back one recorded visual frame. `snapshot()` / `frame()` then
70
+ * return the stored frame. A later mutating call truncates the redo stack.
71
+ */
72
+ undo(): this;
73
+ /** Step forward one recorded visual frame after {@link undo}. */
74
+ redo(): this;
75
+ /** Recorded actions on the current (possibly truncated) stack. */
76
+ history(): readonly TestTuiAction[];
77
+ /** Timing of the last recorded action. */
78
+ measure(): TestTuiMeasure;
79
+ /** Throw if the last action's send+render exceeded `budgetMs`. */
80
+ expectFast(budgetMs: number): this;
28
81
  frame(): string[];
29
- snapshot(): string;
82
+ snapshot(options?: {
83
+ color?: boolean;
84
+ }): string;
85
+ /** Assert a fragment in the color snapshot is painted with `hex`. */
86
+ toHaveColor(expectation: ColorExpectation, theme?: Theme): this;
87
+ /** Alias of {@link expectFast}. */
88
+ toBeFast(budgetMs: number): this;
30
89
  tree(): TreeNode;
90
+ /**
91
+ * Assert that the current snapshot matches a stored baseline file.
92
+ * When `UPDATE_SNAPSHOT=1` is in the environment, writes the file instead.
93
+ */
94
+ matchSnapshot(name: string, description?: string): void;
95
+ private record;
96
+ private liveFrame;
97
+ private liveSnapshot;
98
+ private colorSnapshot;
31
99
  }
100
+ /** Named helper matching the ROADMAP `toBeFast` assertion. */
101
+ export declare function assertFast(tui: TestTui, budgetMs: number): void;
package/dist/test-tui.js CHANGED
@@ -1,4 +1,9 @@
1
- import { dumpTree } from '@mudah-cli/tui';
1
+ import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
2
+ import { dirname, join } from 'node:path';
3
+ import { blitLines, dumpTree, ScreenBuffer } from '@mudah-cli/tui';
4
+ import { paint, sleekDark } from '@mudah-cli/ui';
5
+ import { assertHasColor } from './snapshot-assert.js';
6
+ import { diffSnapshots } from './visual-diff.js';
2
7
  /**
3
8
  * Headless TUI harness. No PTY. Mount a layout, inject keys and clicks,
4
9
  * read an ASCII frame or a JSON tree.
@@ -8,49 +13,189 @@ import { dumpTree } from '@mudah-cli/tui';
8
13
  * tui.send('down').send('enter');
9
14
  * expect(tui.snapshot()).toContain('▸');
10
15
  * expect(tui.tree().role).toBe('Column');
16
+ * tui.matchSnapshot('picker-01', 'happy path');
11
17
  * ```
12
18
  */
13
19
  export class TestTui {
14
20
  root;
15
21
  cols;
16
22
  rows;
17
- constructor(root, cols, rows) {
23
+ snapshotDir;
24
+ actions = [];
25
+ /** `snapshot()` after mount, then after each action. */
26
+ frames = [];
27
+ historyIndex = 0;
28
+ replayFrame = null;
29
+ lastSendMs = 0;
30
+ lastRenderMs = 0;
31
+ constructor(root, cols, rows, snapshotDir) {
18
32
  this.root = root;
19
33
  this.cols = cols;
20
34
  this.rows = rows;
35
+ this.snapshotDir = snapshotDir;
21
36
  this.root.resize(cols, rows);
37
+ this.frames.push(this.liveSnapshot());
22
38
  }
23
39
  static mount(root, options = {}) {
24
- return new TestTui(root, options.cols ?? 80, options.rows ?? 24);
40
+ return new TestTui(root, options.cols ?? 80, options.rows ?? 24, options.snapshotDir ?? join(process.cwd(), 'test', 'fixtures'));
25
41
  }
26
42
  send(name, ch) {
27
43
  const event = { name, ch: ch ?? (name.length === 1 ? name : undefined) };
28
- this.root.handleKey(event);
29
- return this;
44
+ return this.record({ kind: 'send', name, ...(event.ch !== undefined ? { ch: event.ch } : {}) }, () => {
45
+ this.root.handleKey(event);
46
+ });
30
47
  }
31
48
  paste(text) {
32
- this.root.handleKey({ name: 'paste', paste: text });
33
- return this;
49
+ return this.record({ kind: 'paste', text }, () => {
50
+ this.root.handleKey({ name: 'paste', paste: text });
51
+ });
34
52
  }
35
53
  click(x, y) {
36
- this.root.handleMouse(click(x, y));
37
- return this;
54
+ return this.record({ kind: 'click', x, y }, () => {
55
+ this.root.handleMouse(click(x, y));
56
+ });
38
57
  }
39
58
  wheel(direction) {
40
- this.root.handleMouse({
41
- ...click(0, 0),
42
- buttons: { left: false, middle: false, right: false, extra: false },
43
- wheel: direction,
59
+ return this.record({ kind: 'wheel', direction }, () => {
60
+ this.root.handleMouse({
61
+ ...click(0, 0),
62
+ buttons: { left: false, middle: false, right: false, extra: false },
63
+ wheel: direction,
64
+ });
44
65
  });
45
- return this;
46
66
  }
47
67
  resize(cols, rows) {
48
- this.cols = cols;
49
- this.rows = rows;
50
- this.root.resize(cols, rows);
68
+ return this.record({ kind: 'resize', cols, rows }, () => {
69
+ this.cols = cols;
70
+ this.rows = rows;
71
+ this.root.resize(cols, rows);
72
+ });
73
+ }
74
+ /**
75
+ * Step back one recorded visual frame. `snapshot()` / `frame()` then
76
+ * return the stored frame. A later mutating call truncates the redo stack.
77
+ */
78
+ undo() {
79
+ if (this.historyIndex <= 0)
80
+ return this;
81
+ this.historyIndex -= 1;
82
+ this.replayFrame = this.frames[this.historyIndex] ?? null;
83
+ return this;
84
+ }
85
+ /** Step forward one recorded visual frame after {@link undo}. */
86
+ redo() {
87
+ if (this.historyIndex >= this.frames.length - 1) {
88
+ this.replayFrame = null;
89
+ return this;
90
+ }
91
+ this.historyIndex += 1;
92
+ this.replayFrame =
93
+ this.historyIndex >= this.frames.length - 1 ? null : (this.frames[this.historyIndex] ?? null);
94
+ return this;
95
+ }
96
+ /** Recorded actions on the current (possibly truncated) stack. */
97
+ history() {
98
+ return this.actions.slice();
99
+ }
100
+ /** Timing of the last recorded action. */
101
+ measure() {
102
+ return {
103
+ sendMs: this.lastSendMs,
104
+ renderMs: this.lastRenderMs,
105
+ actions: this.actions.length,
106
+ };
107
+ }
108
+ /** Throw if the last action's send+render exceeded `budgetMs`. */
109
+ expectFast(budgetMs) {
110
+ const took = this.lastSendMs + this.lastRenderMs;
111
+ if (took > budgetMs) {
112
+ throw new Error(`[test-tui] Expected last action to finish within ${String(budgetMs)}ms, took ${took.toFixed(2)}ms` +
113
+ ` (send ${this.lastSendMs.toFixed(2)}ms + render ${this.lastRenderMs.toFixed(2)}ms)`);
114
+ }
51
115
  return this;
52
116
  }
53
117
  frame() {
118
+ if (this.replayFrame !== null) {
119
+ const lines = this.replayFrame.split('\n');
120
+ const out = lines.slice(0, this.rows);
121
+ while (out.length < this.rows)
122
+ out.push('');
123
+ return out;
124
+ }
125
+ return this.liveFrame();
126
+ }
127
+ snapshot(options) {
128
+ if (options?.color === true)
129
+ return this.colorSnapshot();
130
+ if (this.replayFrame !== null)
131
+ return this.replayFrame;
132
+ return this.liveSnapshot();
133
+ }
134
+ /** Assert a fragment in the color snapshot is painted with `hex`. */
135
+ toHaveColor(expectation, theme = sleekDark) {
136
+ assertHasColor(this.colorSnapshot(theme), expectation);
137
+ return this;
138
+ }
139
+ /** Alias of {@link expectFast}. */
140
+ toBeFast(budgetMs) {
141
+ return this.expectFast(budgetMs);
142
+ }
143
+ tree() {
144
+ this.root.resize(this.cols, this.rows);
145
+ return dumpTree(this.root);
146
+ }
147
+ /**
148
+ * Assert that the current snapshot matches a stored baseline file.
149
+ * When `UPDATE_SNAPSHOT=1` is in the environment, writes the file instead.
150
+ */
151
+ matchSnapshot(name, description) {
152
+ const snapshotPath = join(this.snapshotDir, `${name}.snapshot`);
153
+ const current = this.snapshot();
154
+ if (process.env['UPDATE_SNAPSHOT'] === '1' || process.argv.includes('--update')) {
155
+ mkdirSync(dirname(snapshotPath), { recursive: true });
156
+ writeFileSync(snapshotPath, current, 'utf8');
157
+ return;
158
+ }
159
+ let baseline;
160
+ try {
161
+ baseline = readFileSync(snapshotPath, 'utf8');
162
+ }
163
+ catch {
164
+ throw new Error(`[test-tui] Snapshot file not found: ${snapshotPath}\n` +
165
+ `Run with UPDATE_SNAPSHOT=1 to create it.\n` +
166
+ (description ? `Description: ${description}\n` : '') +
167
+ `Snapshot content:\n---\n${current}\n---`);
168
+ }
169
+ if (current !== baseline) {
170
+ const diff = diffSnapshots(baseline, current);
171
+ throw new Error(`[test-tui] Snapshot mismatch: ${name}` +
172
+ (description ? ` (${description})` : '') +
173
+ '\n--- expected ---\n' +
174
+ baseline +
175
+ '\n--- actual ---\n' +
176
+ current +
177
+ (diff === '' ? '' : `\n--- diff ---\n${diff}`));
178
+ }
179
+ }
180
+ record(action, apply) {
181
+ if (this.historyIndex < this.frames.length - 1) {
182
+ this.frames.length = this.historyIndex + 1;
183
+ this.actions.length = this.historyIndex;
184
+ }
185
+ this.replayFrame = null;
186
+ const t0 = performance.now();
187
+ apply();
188
+ const t1 = performance.now();
189
+ const frame = this.liveSnapshot();
190
+ const t2 = performance.now();
191
+ this.lastSendMs = t1 - t0;
192
+ this.lastRenderMs = t2 - t1;
193
+ this.actions.push(action);
194
+ this.frames.push(frame);
195
+ this.historyIndex = this.frames.length - 1;
196
+ return this;
197
+ }
198
+ liveFrame() {
54
199
  this.root.resize(this.cols, this.rows);
55
200
  const lines = this.root.render();
56
201
  const out = lines.slice(0, this.rows);
@@ -58,14 +203,51 @@ export class TestTui {
58
203
  out.push('');
59
204
  return out;
60
205
  }
61
- snapshot() {
62
- return this.frame().join('\n').replace(/[ \t]+\n/g, '\n').trimEnd();
206
+ liveSnapshot() {
207
+ return this.liveFrame()
208
+ .join('\n')
209
+ .replace(/[ \t]+\n/g, '\n')
210
+ .trimEnd();
63
211
  }
64
- tree() {
212
+ colorSnapshot(theme = sleekDark) {
65
213
  this.root.resize(this.cols, this.rows);
66
- return dumpTree(this.root);
214
+ const buffer = new ScreenBuffer(this.cols, this.rows);
215
+ blitLines(buffer, this.root.render());
216
+ const lines = [];
217
+ for (let y = 0; y < this.rows; y++) {
218
+ let line = '';
219
+ let run = '';
220
+ let style = '';
221
+ const flush = () => {
222
+ if (run.length === 0)
223
+ return;
224
+ const hex = style === '' ? undefined : theme.colors[style];
225
+ line += hex ? paint(hex, run, 24) : run;
226
+ run = '';
227
+ };
228
+ for (let x = 0; x < this.cols; x++) {
229
+ const cell = buffer.getCell(x, y);
230
+ if (cell.char === '')
231
+ continue;
232
+ if (cell.style !== style) {
233
+ flush();
234
+ style = cell.style;
235
+ }
236
+ run += cell.char;
237
+ }
238
+ flush();
239
+ lines.push(line);
240
+ }
241
+ return lines
242
+ .join('\n')
243
+ .replace(/[ \t]+\n/g, '\n')
244
+ .trimEnd();
67
245
  }
68
246
  }
247
+ /** Named helper matching the ROADMAP `toBeFast` assertion. */
248
+ export function assertFast(tui, budgetMs) {
249
+ tui.expectFast(budgetMs);
250
+ }
69
251
  function click(x, y) {
70
252
  return {
71
253
  x,
@@ -1 +1 @@
1
- {"version":3,"file":"test-tui.js","sourceRoot":"","sources":["../src/test-tui.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"test-tui.js","sourceRoot":"","sources":["../src/test-tui.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ import type { TreeNode } from '@mudah-cli/tui';
2
+ /**
3
+ * Character-aware visual diff of two `snapshot()` strings.
4
+ *
5
+ * Unchanged lines are prefixed with a space, deletions with `-`,
6
+ * additions with `+`. When a single line is substituted, an extra
7
+ * space-prefixed line shows inline `[-deleted-]{+inserted+}` markers.
8
+ */
9
+ export declare function diffSnapshots(expected: string, actual: string): string;
10
+ /**
11
+ * Structural diff of two `tree()` nodes. Same `+` / `-` / space markers
12
+ * as {@link diffSnapshots}, keyed by a JSON-path-like prefix.
13
+ */
14
+ export declare function diffTrees(expected: TreeNode, actual: TreeNode): string;
@@ -0,0 +1,192 @@
1
+ /**
2
+ * Character-aware visual diff of two `snapshot()` strings.
3
+ *
4
+ * Unchanged lines are prefixed with a space, deletions with `-`,
5
+ * additions with `+`. When a single line is substituted, an extra
6
+ * space-prefixed line shows inline `[-deleted-]{+inserted+}` markers.
7
+ */
8
+ export function diffSnapshots(expected, actual) {
9
+ if (expected === actual)
10
+ return '';
11
+ const aLines = expected.split('\n');
12
+ const bLines = actual.split('\n');
13
+ const ops = diffSequence(aLines, bLines);
14
+ const out = [];
15
+ let i = 0;
16
+ while (i < ops.length) {
17
+ const op = ops[i];
18
+ if (op === undefined)
19
+ break;
20
+ if (op.kind === 'eq') {
21
+ out.push(` ${op.value}`);
22
+ i += 1;
23
+ continue;
24
+ }
25
+ const dels = [];
26
+ const ins = [];
27
+ while (i < ops.length && ops[i]?.kind === 'del') {
28
+ dels.push(ops[i].value);
29
+ i += 1;
30
+ }
31
+ while (i < ops.length && ops[i]?.kind === 'ins') {
32
+ ins.push(ops[i].value);
33
+ i += 1;
34
+ }
35
+ if (dels.length === 1 && ins.length === 1) {
36
+ const before = dels[0] ?? '';
37
+ const after = ins[0] ?? '';
38
+ out.push(`-${before}`);
39
+ out.push(`+${after}`);
40
+ const inline = inlineCharDiff(before, after);
41
+ if (inline !== '')
42
+ out.push(` ${inline}`);
43
+ }
44
+ else {
45
+ for (const line of dels)
46
+ out.push(`-${line}`);
47
+ for (const line of ins)
48
+ out.push(`+${line}`);
49
+ }
50
+ }
51
+ return out.join('\n');
52
+ }
53
+ /**
54
+ * Structural diff of two `tree()` nodes. Same `+` / `-` / space markers
55
+ * as {@link diffSnapshots}, keyed by a JSON-path-like prefix.
56
+ */
57
+ export function diffTrees(expected, actual) {
58
+ const lines = [];
59
+ diffNode(expected, actual, '$', lines);
60
+ return lines.join('\n');
61
+ }
62
+ function diffNode(expected, actual, path, lines) {
63
+ if (expected === undefined && actual === undefined)
64
+ return;
65
+ if (expected === undefined && actual !== undefined) {
66
+ lines.push(`+ ${path} ${summarize(actual)}`);
67
+ return;
68
+ }
69
+ if (actual === undefined && expected !== undefined) {
70
+ lines.push(`- ${path} ${summarize(expected)}`);
71
+ return;
72
+ }
73
+ if (expected === undefined || actual === undefined)
74
+ return;
75
+ compareField(path, 'role', expected.role, actual.role, lines);
76
+ compareField(path, 'name', expected.name, actual.name, lines);
77
+ compareField(path, 'value', expected.value, actual.value, lines);
78
+ compareField(path, 'href', expected.href, actual.href, lines);
79
+ compareField(path, 'focused', expected.focused, actual.focused, lines);
80
+ const expectedBounds = expected.bounds === undefined ? undefined : JSON.stringify(expected.bounds);
81
+ const actualBounds = actual.bounds === undefined ? undefined : JSON.stringify(actual.bounds);
82
+ if (expectedBounds !== actualBounds) {
83
+ lines.push(`- ${path}.bounds ${expectedBounds ?? 'undefined'}`);
84
+ lines.push(`+ ${path}.bounds ${actualBounds ?? 'undefined'}`);
85
+ }
86
+ const expectedKids = expected.children ?? [];
87
+ const actualKids = actual.children ?? [];
88
+ if (expectedKids.length !== actualKids.length) {
89
+ lines.push(`- ${path}.children.length ${String(expectedKids.length)}`);
90
+ lines.push(`+ ${path}.children.length ${String(actualKids.length)}`);
91
+ }
92
+ const n = Math.max(expectedKids.length, actualKids.length);
93
+ for (let i = 0; i < n; i++) {
94
+ diffNode(expectedKids[i], actualKids[i], `${path}.children[${String(i)}]`, lines);
95
+ }
96
+ }
97
+ function compareField(path, key, expected, actual, lines) {
98
+ if (sameValue(expected, actual))
99
+ return;
100
+ lines.push(`- ${path}.${key} ${fmt(expected)}`);
101
+ lines.push(`+ ${path}.${key} ${fmt(actual)}`);
102
+ }
103
+ function sameValue(a, b) {
104
+ if (Object.is(a, b))
105
+ return true;
106
+ if (a === undefined || b === undefined || a === null || b === null)
107
+ return a === b;
108
+ if (typeof a === 'object' || typeof b === 'object') {
109
+ return JSON.stringify(a) === JSON.stringify(b);
110
+ }
111
+ return false;
112
+ }
113
+ function fmt(value) {
114
+ if (value === undefined)
115
+ return 'undefined';
116
+ if (typeof value === 'string')
117
+ return JSON.stringify(value);
118
+ return JSON.stringify(value);
119
+ }
120
+ function summarize(node) {
121
+ const bits = [`role=${node.role}`];
122
+ if (node.name !== undefined)
123
+ bits.push(`name=${JSON.stringify(node.name)}`);
124
+ return `{${bits.join(' ')}}`;
125
+ }
126
+ function inlineCharDiff(expected, actual) {
127
+ if (expected === actual)
128
+ return '';
129
+ const ops = diffSequence([...expected], [...actual]);
130
+ let out = '';
131
+ let i = 0;
132
+ while (i < ops.length) {
133
+ const op = ops[i];
134
+ if (op === undefined)
135
+ break;
136
+ if (op.kind === 'eq') {
137
+ out += op.value;
138
+ i += 1;
139
+ continue;
140
+ }
141
+ let deleted = '';
142
+ let inserted = '';
143
+ while (i < ops.length && ops[i]?.kind === 'del') {
144
+ deleted += ops[i].value;
145
+ i += 1;
146
+ }
147
+ while (i < ops.length && ops[i]?.kind === 'ins') {
148
+ inserted += ops[i].value;
149
+ i += 1;
150
+ }
151
+ if (deleted !== '')
152
+ out += `[-${deleted}-]`;
153
+ if (inserted !== '')
154
+ out += `{+${inserted}+}`;
155
+ }
156
+ return out;
157
+ }
158
+ function diffSequence(a, b) {
159
+ const n = a.length;
160
+ const m = b.length;
161
+ const dp = Array.from({ length: n + 1 }, () => new Array(m + 1).fill(0));
162
+ for (let i = 1; i <= n; i++) {
163
+ const row = dp[i];
164
+ const prev = dp[i - 1];
165
+ if (row === undefined || prev === undefined)
166
+ continue;
167
+ for (let j = 1; j <= m; j++) {
168
+ row[j] = a[i - 1] === b[j - 1] ? (prev[j - 1] ?? 0) + 1 : Math.max(prev[j] ?? 0, row[j - 1] ?? 0);
169
+ }
170
+ }
171
+ const ops = [];
172
+ let i = n;
173
+ let j = m;
174
+ while (i > 0 || j > 0) {
175
+ if (i > 0 && j > 0 && a[i - 1] === b[j - 1]) {
176
+ ops.push({ kind: 'eq', value: a[i - 1] ?? '' });
177
+ i -= 1;
178
+ j -= 1;
179
+ }
180
+ else if (j > 0 && (i === 0 || (dp[i]?.[j - 1] ?? 0) >= (dp[i - 1]?.[j] ?? 0))) {
181
+ ops.push({ kind: 'ins', value: b[j - 1] ?? '' });
182
+ j -= 1;
183
+ }
184
+ else {
185
+ ops.push({ kind: 'del', value: a[i - 1] ?? '' });
186
+ i -= 1;
187
+ }
188
+ }
189
+ ops.reverse();
190
+ return ops;
191
+ }
192
+ //# sourceMappingURL=visual-diff.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"visual-diff.js","sourceRoot":"","sources":["../src/visual-diff.ts"],"names":[],"mappings":"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"}