addons/shared/__tests__/stringify.test.tsblame
View source
b69ab311/**
b69ab312 * Copyright (c) Meta Platforms, Inc. and affiliates.
b69ab313 *
b69ab314 * This source code is licensed under the MIT license found in the
b69ab315 * LICENSE file in the root directory of this source tree.
b69ab316 */
b69ab317
b69ab318import {filterFilesFromPatch, parsePatch} from '../patch/parse';
b69ab319import {stringifyPatch} from '../patch/stringify';
b69ab3110
b69ab3111describe('patch/stringify', () => {
b69ab3112 describe('round-trip conversion', () => {
b69ab3113 it('should round-trip basic modified patch', () => {
b69ab3114 const patch = `diff --git sapling/eden/scm/a sapling/eden/scm/a
b69ab3115--- sapling/eden/scm/a
b69ab3116+++ sapling/eden/scm/a
b69ab3117@@ -1,1 +1,2 @@
b69ab3118 1
b69ab3119+2
b69ab3120`;
b69ab3121 const parsed = parsePatch(patch);
b69ab3122 const stringified = stringifyPatch(parsed);
b69ab3123 expect(stringified).toEqual(patch);
b69ab3124 });
b69ab3125
b69ab3126 it('should round-trip rename', () => {
b69ab3127 const patch = `diff --git sapling/eden/scm/a sapling/eden/scm/b
b69ab3128rename from sapling/eden/scm/a
b69ab3129rename to sapling/eden/scm/b
b69ab3130`;
b69ab3131 const parsed = parsePatch(patch);
b69ab3132 const stringified = stringifyPatch(parsed);
b69ab3133 expect(stringified).toEqual(patch);
b69ab3134 });
b69ab3135
b69ab3136 it('should round-trip rename and modify', () => {
b69ab3137 const patch = `diff --git sapling/eden/addons/LICENSE sapling/eden/addons/LICENSE.bak
b69ab3138rename from sapling/eden/addons/LICENSE
b69ab3139rename to sapling/eden/addons/LICENSE.bak
b69ab3140--- sapling/eden/addons/LICENSE
b69ab3141+++ sapling/eden/addons/LICENSE.bak
b69ab3142@@ -2,6 +2,7 @@
b69ab3143
b69ab3144 Copyright (c) Meta Platforms, Inc. and its affiliates.
b69ab3145
b69ab3146+
b69ab3147`;
b69ab3148 const parsed = parsePatch(patch);
b69ab3149 const stringified = stringifyPatch(parsed);
b69ab3150 expect(stringified).toEqual(patch);
b69ab3151 });
b69ab3152
b69ab3153 it('should round-trip new file', () => {
b69ab3154 const patch = `diff --git sapling/eden/scm/c sapling/eden/scm/c
b69ab3155new file mode 100644
b69ab3156--- /dev/null
b69ab3157+++ sapling/eden/scm/c
b69ab3158@@ -0,0 +1,1 @@
b69ab3159+1
b69ab3160`;
b69ab3161 const parsed = parsePatch(patch);
b69ab3162 const stringified = stringifyPatch(parsed);
b69ab3163 expect(stringified).toEqual(patch);
b69ab3164 });
b69ab3165
b69ab3166 it('should round-trip new empty file', () => {
b69ab3167 const patch = `diff --git sapling/eden/addons/d sapling/eden/addons/d
b69ab3168new file mode 100644
b69ab3169`;
b69ab3170 const parsed = parsePatch(patch);
b69ab3171 const stringified = stringifyPatch(parsed);
b69ab3172 expect(stringified).toEqual(patch);
b69ab3173 });
b69ab3174
b69ab3175 it('should round-trip deleted file', () => {
b69ab3176 const patch = `diff --git sapling/eden/scm/a sapling/eden/scm/a
b69ab3177deleted file mode 100644
b69ab3178--- sapling/eden/scm/a
b69ab3179+++ /dev/null
b69ab3180@@ -1,1 +0,0 @@
b69ab3181-1
b69ab3182`;
b69ab3183 const parsed = parsePatch(patch);
b69ab3184 const stringified = stringifyPatch(parsed);
b69ab3185 expect(stringified).toEqual(patch);
b69ab3186 });
b69ab3187
b69ab3188 it('should round-trip copied file', () => {
b69ab3189 const patch = `diff --git sapling/eden/scm/a sapling/eden/scm/b
b69ab3190copy from sapling/eden/scm/a
b69ab3191copy to sapling/eden/scm/b
b69ab3192`;
b69ab3193 const parsed = parsePatch(patch);
b69ab3194 const stringified = stringifyPatch(parsed);
b69ab3195 expect(stringified).toEqual(patch);
b69ab3196 });
b69ab3197
b69ab3198 it('should round-trip multiple files', () => {
b69ab3199 const patch = `diff --git sapling/eden/scm/a sapling/eden/scm/a
b69ab31100--- sapling/eden/scm/a
b69ab31101+++ sapling/eden/scm/a
b69ab31102@@ -1,1 +1,2 @@
b69ab31103 1
b69ab31104+2
b69ab31105diff --git sapling/eden/scm/a sapling/eden/scm/b
b69ab31106copy from sapling/eden/scm/a
b69ab31107copy to sapling/eden/scm/b
b69ab31108diff --git sapling/eden/scm/c sapling/eden/scm/d
b69ab31109copy from sapling/eden/scm/c
b69ab31110copy to sapling/eden/scm/d
b69ab31111`;
b69ab31112 const parsed = parsePatch(patch);
b69ab31113 const stringified = stringifyPatch(parsed);
b69ab31114 expect(stringified).toEqual(patch);
b69ab31115 });
b69ab31116
b69ab31117 it('should round-trip file mode change', () => {
b69ab31118 const patch = `diff --git sapling/eden/scm/a sapling/eden/scm/a
b69ab31119old mode 100644
b69ab31120new mode 100755
b69ab31121`;
b69ab31122 const parsed = parsePatch(patch);
b69ab31123 const stringified = stringifyPatch(parsed);
b69ab31124 expect(stringified).toEqual(patch);
b69ab31125 });
b69ab31126
b69ab31127 it('should round-trip submodule modification', () => {
b69ab31128 const patch = `diff --git a/external/brotli b/external/brotli
b69ab31129--- a/external/brotli
b69ab31130+++ b/external/brotli
b69ab31131@@ -1,1 +1,1 @@
b69ab31132-Subproject commit 892110204ccf44fcd493ae415c9a69c470c2a9cf
b69ab31133+Subproject commit 57de5cc4288565a9c3a7af978ef15f0abf0ada1b
b69ab31134`;
b69ab31135 const parsed = parsePatch(patch);
b69ab31136 const stringified = stringifyPatch(parsed);
b69ab31137 expect(stringified).toEqual(patch);
b69ab31138 });
b69ab31139
b69ab31140 it('should round-trip added submodule', () => {
b69ab31141 const patch = `diff --git a/path/to/submodule b/path/to/submodule
b69ab31142new file mode 160000
b69ab31143--- /dev/null
b69ab31144+++ b/path/to/submodule
b69ab31145@@ -0,0 +1,1 @@
b69ab31146+Subproject commit 7ef4220022059b9b1e1d8ec4eea6f7abd011894f
b69ab31147`;
b69ab31148 const parsed = parsePatch(patch);
b69ab31149 const stringified = stringifyPatch(parsed);
b69ab31150 expect(stringified).toEqual(patch);
b69ab31151 });
b69ab31152 });
b69ab31153
b69ab31154 describe('hunk range formatting', () => {
b69ab31155 it('should format single line hunk with count', () => {
b69ab31156 const patch = `diff --git a/file.txt b/file.txt
b69ab31157--- a/file.txt
b69ab31158+++ b/file.txt
b69ab31159@@ -5,1 +5,2 @@
b69ab31160 line 5
b69ab31161+new line
b69ab31162`;
b69ab31163 const parsed = parsePatch(patch);
b69ab31164 const stringified = stringifyPatch(parsed);
b69ab31165 expect(stringified).toEqual(patch);
b69ab31166 });
b69ab31167
b69ab31168 it('should format empty old range', () => {
b69ab31169 const patch = `diff --git sapling/eden/scm/c sapling/eden/scm/c
b69ab31170new file mode 100644
b69ab31171--- /dev/null
b69ab31172+++ sapling/eden/scm/c
b69ab31173@@ -0,0 +1,3 @@
b69ab31174+line 1
b69ab31175+line 2
b69ab31176+line 3
b69ab31177`;
b69ab31178 const parsed = parsePatch(patch);
b69ab31179 const stringified = stringifyPatch(parsed);
b69ab31180 expect(stringified).toEqual(patch);
b69ab31181 });
b69ab31182
b69ab31183 it('should format empty new range', () => {
b69ab31184 const patch = `diff --git a/file.txt b/file.txt
b69ab31185--- a/file.txt
b69ab31186+++ b/file.txt
b69ab31187@@ -1,3 +0,0 @@
b69ab31188-line 1
b69ab31189-line 2
b69ab31190-line 3
b69ab31191`;
b69ab31192 const parsed = parsePatch(patch);
b69ab31193 const stringified = stringifyPatch(parsed);
b69ab31194 expect(stringified).toEqual(patch);
b69ab31195 });
b69ab31196 });
b69ab31197
b69ab31198 describe('line delimiter handling', () => {
b69ab31199 it('should preserve hunk line delimiters', () => {
b69ab31200 const patch = `diff --git a/file.txt b/file.txt
b69ab31201--- a/file.txt
b69ab31202+++ b/file.txt
b69ab31203@@ -1,1 +1,2 @@
b69ab31204 line 1\r
b69ab31205+line 2\r
b69ab31206`;
b69ab31207 const parsed = parsePatch(patch);
b69ab31208 const stringified = stringifyPatch(parsed);
b69ab31209 // Header lines use standard \n, but hunk content preserves \r
b69ab31210 expect(stringified).toEqual(patch);
b69ab31211 });
b69ab31212 });
b69ab31213
b69ab31214 describe('multiple hunks', () => {
b69ab31215 it('should handle multiple hunks in a single file', () => {
b69ab31216 const patch = `diff --git a/file.txt b/file.txt
b69ab31217--- a/file.txt
b69ab31218+++ b/file.txt
b69ab31219@@ -1,3 +1,4 @@
b69ab31220 line 1
b69ab31221+new line 1.5
b69ab31222 line 2
b69ab31223 line 3
b69ab31224@@ -10,2 +11,3 @@
b69ab31225 line 10
b69ab31226+new line 10.5
b69ab31227 line 11
b69ab31228`;
b69ab31229 const parsed = parsePatch(patch);
b69ab31230 const stringified = stringifyPatch(parsed);
b69ab31231 expect(stringified).toEqual(patch);
b69ab31232 });
b69ab31233 });
b69ab31234
b69ab31235 describe('no newline at end of file', () => {
b69ab31236 it('should handle backslash-no-newline marker', () => {
b69ab31237 const patch = `diff --git a/file.txt b/file.txt
b69ab31238--- a/file.txt
b69ab31239+++ b/file.txt
b69ab31240@@ -1,1 +1,1 @@
b69ab31241-old content
b69ab31242\\ No newline at end of file
b69ab31243+new content
b69ab31244\\ No newline at end of file
b69ab31245`;
b69ab31246 const parsed = parsePatch(patch);
b69ab31247 const stringified = stringifyPatch(parsed);
b69ab31248 expect(stringified).toEqual(patch);
b69ab31249 });
b69ab31250 });
b69ab31251});
b69ab31252
b69ab31253describe('patch/filterFilesFromPatch', () => {
b69ab31254 describe('basic filtering', () => {
b69ab31255 it('should filter out a single modified file', () => {
b69ab31256 const patch = `diff --git a/keep.ts b/keep.ts
b69ab31257--- a/keep.ts
b69ab31258+++ b/keep.ts
b69ab31259@@ -1,1 +1,2 @@
b69ab31260 line 1
b69ab31261+line 2
b69ab31262diff --git a/remove.ts b/remove.ts
b69ab31263--- a/remove.ts
b69ab31264+++ b/remove.ts
b69ab31265@@ -1,1 +1,2 @@
b69ab31266 original
b69ab31267+changed
b69ab31268`;
b69ab31269 const filtered = filterFilesFromPatch(patch, ['a/remove.ts']);
b69ab31270 const expected = `diff --git a/keep.ts b/keep.ts
b69ab31271--- a/keep.ts
b69ab31272+++ b/keep.ts
b69ab31273@@ -1,1 +1,2 @@
b69ab31274 line 1
b69ab31275+line 2
b69ab31276`;
b69ab31277 expect(filtered).toEqual(expected);
b69ab31278 });
b69ab31279
b69ab31280 it('should filter out multiple files', () => {
b69ab31281 const patch = `diff --git a/keep.ts b/keep.ts
b69ab31282--- a/keep.ts
b69ab31283+++ b/keep.ts
b69ab31284@@ -1,1 +1,2 @@
b69ab31285 line 1
b69ab31286+line 2
b69ab31287diff --git a/remove1.ts b/remove1.ts
b69ab31288--- a/remove1.ts
b69ab31289+++ b/remove1.ts
b69ab31290@@ -1,1 +1,1 @@
b69ab31291-old
b69ab31292+new
b69ab31293diff --git a/remove2.ts b/remove2.ts
b69ab31294--- a/remove2.ts
b69ab31295+++ b/remove2.ts
b69ab31296@@ -1,1 +1,1 @@
b69ab31297-old
b69ab31298+new
b69ab31299`;
b69ab31300 const filtered = filterFilesFromPatch(patch, ['a/remove1.ts', 'b/remove2.ts']);
b69ab31301 const expected = `diff --git a/keep.ts b/keep.ts
b69ab31302--- a/keep.ts
b69ab31303+++ b/keep.ts
b69ab31304@@ -1,1 +1,2 @@
b69ab31305 line 1
b69ab31306+line 2
b69ab31307`;
b69ab31308 expect(filtered).toEqual(expected);
b69ab31309 });
b69ab31310
b69ab31311 it('should return empty string when all files are filtered', () => {
b69ab31312 const patch = `diff --git a/remove.ts b/remove.ts
b69ab31313--- a/remove.ts
b69ab31314+++ b/remove.ts
b69ab31315@@ -1,1 +1,2 @@
b69ab31316 original
b69ab31317+changed
b69ab31318`;
b69ab31319 const filtered = filterFilesFromPatch(patch, ['a/remove.ts']);
b69ab31320 expect(filtered).toEqual('');
b69ab31321 });
b69ab31322
b69ab31323 it('should return original patch when no files match filter', () => {
b69ab31324 const patch = `diff --git a/keep.ts b/keep.ts
b69ab31325--- a/keep.ts
b69ab31326+++ b/keep.ts
b69ab31327@@ -1,1 +1,2 @@
b69ab31328 line 1
b69ab31329+line 2
b69ab31330`;
b69ab31331 const filtered = filterFilesFromPatch(patch, ['a/nonexistent.ts']);
b69ab31332 expect(filtered).toEqual(patch);
b69ab31333 });
b69ab31334 });
b69ab31335
b69ab31336 describe('path prefix handling', () => {
b69ab31337 it('should filter files with a/ prefix', () => {
b69ab31338 const patch = `diff --git a/file.ts b/file.ts
b69ab31339--- a/file.ts
b69ab31340+++ b/file.ts
b69ab31341@@ -1,1 +1,2 @@
b69ab31342 line 1
b69ab31343+line 2
b69ab31344`;
b69ab31345 const filtered = filterFilesFromPatch(patch, ['a/file.ts']);
b69ab31346 expect(filtered).toEqual('');
b69ab31347 });
b69ab31348
b69ab31349 it('should filter files with b/ prefix', () => {
b69ab31350 const patch = `diff --git a/file.ts b/file.ts
b69ab31351--- a/file.ts
b69ab31352+++ b/file.ts
b69ab31353@@ -1,1 +1,2 @@
b69ab31354 line 1
b69ab31355+line 2
b69ab31356`;
b69ab31357 const filtered = filterFilesFromPatch(patch, ['b/file.ts']);
b69ab31358 expect(filtered).toEqual('');
b69ab31359 });
b69ab31360
b69ab31361 it('should filter files without prefix', () => {
b69ab31362 const patch = `diff --git a/file.ts b/file.ts
b69ab31363--- a/file.ts
b69ab31364+++ b/file.ts
b69ab31365@@ -1,1 +1,2 @@
b69ab31366 line 1
b69ab31367+line 2
b69ab31368`;
b69ab31369 const filtered = filterFilesFromPatch(patch, ['file.ts']);
b69ab31370 expect(filtered).toEqual('');
b69ab31371 });
b69ab31372
b69ab31373 it('should handle paths with slashes', () => {
b69ab31374 const patch = `diff --git a/src/components/App.tsx b/src/components/App.tsx
b69ab31375--- a/src/components/App.tsx
b69ab31376+++ b/src/components/App.tsx
b69ab31377@@ -1,1 +1,2 @@
b69ab31378 import React from 'react';
b69ab31379+import {useState} from 'react';
b69ab31380`;
b69ab31381 const filtered = filterFilesFromPatch(patch, ['src/components/App.tsx']);
b69ab31382 expect(filtered).toEqual('');
b69ab31383 });
b69ab31384 });
b69ab31385
b69ab31386 describe('special file operations', () => {
b69ab31387 it('should filter renamed files by old name', () => {
b69ab31388 const patch = `diff --git a/old.ts b/new.ts
b69ab31389rename from a/old.ts
b69ab31390rename to b/new.ts
b69ab31391--- a/old.ts
b69ab31392+++ b/new.ts
b69ab31393@@ -1,1 +1,2 @@
b69ab31394 line 1
b69ab31395+line 2
b69ab31396`;
b69ab31397 const filtered = filterFilesFromPatch(patch, ['a/old.ts']);
b69ab31398 expect(filtered).toEqual('');
b69ab31399 });
b69ab31400
b69ab31401 it('should filter renamed files by new name', () => {
b69ab31402 const patch = `diff --git a/old.ts b/new.ts
b69ab31403rename from a/old.ts
b69ab31404rename to b/new.ts
b69ab31405--- a/old.ts
b69ab31406+++ b/new.ts
b69ab31407@@ -1,1 +1,2 @@
b69ab31408 line 1
b69ab31409+line 2
b69ab31410`;
b69ab31411 const filtered = filterFilesFromPatch(patch, ['b/new.ts']);
b69ab31412 expect(filtered).toEqual('');
b69ab31413 });
b69ab31414
b69ab31415 it('should filter new files', () => {
b69ab31416 const patch = `diff --git a/existing.ts b/existing.ts
b69ab31417--- a/existing.ts
b69ab31418+++ b/existing.ts
b69ab31419@@ -1,1 +1,2 @@
b69ab31420 line 1
b69ab31421+line 2
b69ab31422diff --git a/new.ts b/new.ts
b69ab31423new file mode 100644
b69ab31424--- /dev/null
b69ab31425+++ b/new.ts
b69ab31426@@ -0,0 +1,1 @@
b69ab31427+new content
b69ab31428`;
b69ab31429 const filtered = filterFilesFromPatch(patch, ['new.ts']);
b69ab31430 const expected = `diff --git a/existing.ts b/existing.ts
b69ab31431--- a/existing.ts
b69ab31432+++ b/existing.ts
b69ab31433@@ -1,1 +1,2 @@
b69ab31434 line 1
b69ab31435+line 2
b69ab31436`;
b69ab31437 expect(filtered).toEqual(expected);
b69ab31438 });
b69ab31439
b69ab31440 it('should filter deleted files', () => {
b69ab31441 const patch = `diff --git a/keep.ts b/keep.ts
b69ab31442--- a/keep.ts
b69ab31443+++ b/keep.ts
b69ab31444@@ -1,1 +1,2 @@
b69ab31445 line 1
b69ab31446+line 2
b69ab31447diff --git a/deleted.ts b/deleted.ts
b69ab31448deleted file mode 100644
b69ab31449--- a/deleted.ts
b69ab31450+++ /dev/null
b69ab31451@@ -1,1 +0,0 @@
b69ab31452-old content
b69ab31453`;
b69ab31454 const filtered = filterFilesFromPatch(patch, ['deleted.ts']);
b69ab31455 const expected = `diff --git a/keep.ts b/keep.ts
b69ab31456--- a/keep.ts
b69ab31457+++ b/keep.ts
b69ab31458@@ -1,1 +1,2 @@
b69ab31459 line 1
b69ab31460+line 2
b69ab31461`;
b69ab31462 expect(filtered).toEqual(expected);
b69ab31463 });
b69ab31464
b69ab31465 it('should filter copied files', () => {
b69ab31466 const patch = `diff --git a/original.ts b/copy.ts
b69ab31467copy from a/original.ts
b69ab31468copy to b/copy.ts
b69ab31469`;
b69ab31470 const filtered = filterFilesFromPatch(patch, ['copy.ts']);
b69ab31471 expect(filtered).toEqual('');
b69ab31472 });
b69ab31473
b69ab31474 it('should filter mode changes', () => {
b69ab31475 const patch = `diff --git a/script.sh b/script.sh
b69ab31476old mode 100644
b69ab31477new mode 100755
b69ab31478`;
b69ab31479 const filtered = filterFilesFromPatch(patch, ['script.sh']);
b69ab31480 expect(filtered).toEqual('');
b69ab31481 });
b69ab31482 });
b69ab31483
b69ab31484 describe('real-world use case: filtering generated files', () => {
b69ab31485 it('should remove generated code files from patch', () => {
b69ab31486 const patch = `diff --git a/src/manual.ts b/src/manual.ts
b69ab31487--- a/src/manual.ts
b69ab31488+++ b/src/manual.ts
b69ab31489@@ -1,3 +1,4 @@
b69ab31490 // Manually written code
b69ab31491 export function doSomething() {
b69ab31492+ console.log('new feature');
b69ab31493 }
b69ab31494diff --git a/generated/types.ts b/generated/types.ts
b69ab31495--- a/generated/types.ts
b69ab31496+++ b/generated/types.ts
b69ab31497@@ -1,100 +1,200 @@
b69ab31498-// Auto-generated - do not edit
b69ab31499+// Auto-generated - do not edit
b69ab31500+// Lots of noisy changes
b69ab31501 export type GeneratedType = string;
b69ab31502diff --git a/generated/schema.ts b/generated/schema.ts
b69ab31503--- a/generated/schema.ts
b69ab31504+++ b/generated/schema.ts
b69ab31505@@ -1,50 +1,100 @@
b69ab31506-// Auto-generated
b69ab31507+// Auto-generated
b69ab31508+// More noise
b69ab31509 export const schema = {};
b69ab31510`;
b69ab31511 const filtered = filterFilesFromPatch(patch, ['generated/types.ts', 'generated/schema.ts']);
b69ab31512 const expected = `diff --git a/src/manual.ts b/src/manual.ts
b69ab31513--- a/src/manual.ts
b69ab31514+++ b/src/manual.ts
b69ab31515@@ -1,3 +1,4 @@
b69ab31516 // Manually written code
b69ab31517 export function doSomething() {
b69ab31518+ console.log('new feature');
b69ab31519 }
b69ab31520`;
b69ab31521 expect(filtered).toEqual(expected);
b69ab31522 });
b69ab31523 });
b69ab31524
b69ab31525 describe('edge cases', () => {
b69ab31526 it('should handle empty patch', () => {
b69ab31527 const filtered = filterFilesFromPatch('', ['file.ts']);
b69ab31528 expect(filtered).toEqual('');
b69ab31529 });
b69ab31530
b69ab31531 it('should handle empty file list', () => {
b69ab31532 const patch = `diff --git a/file.ts b/file.ts
b69ab31533--- a/file.ts
b69ab31534+++ b/file.ts
b69ab31535@@ -1,1 +1,2 @@
b69ab31536 line 1
b69ab31537+line 2
b69ab31538`;
b69ab31539 const filtered = filterFilesFromPatch(patch, []);
b69ab31540 expect(filtered).toEqual(patch);
b69ab31541 });
b69ab31542
b69ab31543 it('should handle files with multiple hunks', () => {
b69ab31544 const patch = `diff --git a/file.ts b/file.ts
b69ab31545--- a/file.ts
b69ab31546+++ b/file.ts
b69ab31547@@ -1,3 +1,4 @@
b69ab31548 line 1
b69ab31549+new line
b69ab31550 line 2
b69ab31551 line 3
b69ab31552@@ -10,2 +11,3 @@
b69ab31553 line 10
b69ab31554+another new line
b69ab31555 line 11
b69ab31556`;
b69ab31557 const filtered = filterFilesFromPatch(patch, ['file.ts']);
b69ab31558 expect(filtered).toEqual('');
b69ab31559 });
b69ab31560 });
b69ab31561});