1import { fs } from 'memfs'; 2import path from 'path'; 3import temporary from 'tempy'; 4 5import * as GitIgnore from '../mergeGitIgnorePaths'; 6import { removeFromGitIgnore, upsertGitIgnoreContents } from '../mergeGitIgnorePaths'; 7 8const testRoot = temporary.directory(); 9beforeAll(async () => { 10 await fs.promises.mkdir(testRoot, { recursive: true }); 11}); 12afterAll(async () => { 13 await fs.promises.rm(testRoot, { recursive: true, force: true }); 14}); 15 16const gitignore1 = ` 17# hello world 18*/foo 19 20ios/ 21 22/android 23## bar 24 25`; 26 27// A gitignore with an old generated section 28const gitignore2 = `a 29b 30${GitIgnore.createGeneratedHeaderComment('...')} 31foo 32bar 33${GitIgnore.generatedFooterComment} 34c 35d`; 36 37// A broken generated section 38const gitignore3 = `a 39b 40${GitIgnore.createGeneratedHeaderComment('...')} 41foo 42bar 43c 44d`; 45 46// Footer is before header 47const gitignore4 = `a 48b 49${GitIgnore.generatedFooterComment} 50foo 51bar 52${GitIgnore.createGeneratedHeaderComment('...')} 53c 54d`; 55 56it(`sanitizes comments, new lines, and sort order`, () => { 57 expect(GitIgnore.getSanitizedGitIgnoreLines(gitignore1)).toStrictEqual([ 58 '*/foo', 59 '/android ', 60 'ios/', 61 ]); 62}); 63 64describe('removeGeneratedGitIgnoreContents', () => { 65 it(`removes a generated gitignore`, () => { 66 expect(GitIgnore.removeGeneratedGitIgnoreContents(gitignore2)?.split('\n')).toStrictEqual([ 67 'a', 68 'b', 69 'c', 70 'd', 71 ]); 72 }); 73 it(`removes nothing from a regular gitignore`, () => { 74 expect(GitIgnore.removeGeneratedGitIgnoreContents(gitignore1)).toBe(null); 75 }); 76 it(`removes nothing when the generated footer is missing`, () => { 77 expect(GitIgnore.removeGeneratedGitIgnoreContents(gitignore3)).toBe(null); 78 }); 79 it(`removes nothing when the footer precede the header`, () => { 80 expect(GitIgnore.removeGeneratedGitIgnoreContents(gitignore4)).toBe(null); 81 }); 82}); 83 84describe('mergeGitIgnore', () => { 85 it(`skips merging if the target file is missing`, async () => { 86 // fs 87 const projectRoot = path.join(testRoot, 'merge-git-ignore-skip-when-target-missing'); 88 await fs.promises.mkdir(projectRoot, { recursive: true }); 89 // Setup 90 91 const targetGitIgnorePath = path.join(projectRoot, '.gitignore'); 92 // Skip writing a gitignore 93 94 const sourceGitIgnorePath = path.join(projectRoot, '.gitignore-other'); 95 await fs.promises.writeFile( 96 sourceGitIgnorePath, 97 [ 98 'alpha', 99 'beta', 100 // in the future we may want to merge this value with the existing matching value 101 // or maybe we could keep the code simple and not do that :] 102 'bar', 103 ].join('\n') 104 ); 105 106 expect(GitIgnore.mergeGitIgnorePaths(targetGitIgnorePath, sourceGitIgnorePath)).toBe(null); 107 expect(fs.existsSync(targetGitIgnorePath)).toBe(false); 108 }); 109 it(`merges two git ignore files in the filesystem`, async () => { 110 // fs 111 const projectRoot = path.join(testRoot, 'merge-git-ignore-works'); 112 await fs.promises.mkdir(projectRoot, { recursive: true }); 113 // Setup 114 const targetGitIgnorePath = path.join(projectRoot, '.gitignore'); 115 await fs.promises.writeFile( 116 targetGitIgnorePath, 117 [ 118 'foo', 119 // Test a duplicate value 120 'bar', 121 ].join('\n') 122 ); 123 124 const sourceGitIgnorePath = path.join(projectRoot, '.gitignore-other'); 125 await fs.promises.writeFile( 126 sourceGitIgnorePath, 127 [ 128 'alpha', 129 'beta', 130 // in the future we may want to merge this value with the existing matching value 131 // or maybe we could keep the code simple and not do that :] 132 'bar', 133 ].join('\n') 134 ); 135 136 const results = GitIgnore.mergeGitIgnorePaths(targetGitIgnorePath, sourceGitIgnorePath); 137 expect(results).not.toBe(null); 138 expect(results?.contents).toMatch( 139 /generated expo-cli sync-69a5afdba5ff28bbd11618f94ae2dc4bfdfd7cae/ 140 ); 141 expect(results?.contents).toMatch(/foo/); 142 expect(results?.contents).toMatch(/alpha/); 143 expect(results?.didMerge).toBe(true); 144 145 expect(fs.readFileSync(targetGitIgnorePath, 'utf8')).toBe(results?.contents); 146 }); 147}); 148 149describe(removeFromGitIgnore, () => { 150 it('can remove a line from gitignore', async () => { 151 const targetGitIgnorePath = path.join(testRoot, './removeFromGitIgnore'); 152 await fs.promises.writeFile(targetGitIgnorePath, gitignore1); 153 removeFromGitIgnore(targetGitIgnorePath, '*/foo'); 154 155 expect(fs.readFileSync(targetGitIgnorePath, 'utf8')).toBe(` 156# hello world 157 158ios/ 159 160/android 161## bar 162 163`); 164 }); 165 166 it('will remove the generated section if it is empty', async () => { 167 const targetGitIgnorePath = path.join(testRoot, './removeFromGitIgnore-remove-generated'); 168 await fs.promises.writeFile( 169 targetGitIgnorePath, 170 ` 171# hello world 172*/foo 173 174ios/ 175 176/android 177## bar 178 179 180# @generated expo-cli sync-4f49d69613b186e71104c7ca1b26c1e5b78c9193 181# The following patterns were generated by expo-cli 182 183test-string 184# @end expo-cli` 185 ); 186 removeFromGitIgnore(targetGitIgnorePath, 'test-string'); 187 188 expect(fs.readFileSync(targetGitIgnorePath, 'utf8')).toBe(` 189# hello world 190*/foo 191 192ios/ 193 194/android 195## bar 196 197 198`); 199 }); 200}); 201 202describe(upsertGitIgnoreContents, () => { 203 it('can create a new gitignore file', async () => { 204 const targetGitIgnorePath = path.join(testRoot, './upsertGitIgnoreContents-create'); 205 const results = upsertGitIgnoreContents(targetGitIgnorePath, 'test-string'); 206 207 expect(results?.contents).toBe(` 208# @generated expo-cli sync-4f49d69613b186e71104c7ca1b26c1e5b78c9193 209# The following patterns were generated by expo-cli 210 211test-string 212# @end expo-cli`); 213 214 expect(fs.readFileSync(targetGitIgnorePath, 'utf8')).toBe(results?.contents); 215 }); 216 217 it('can update an existing gitignore file', async () => { 218 const targetGitIgnorePath = path.join(testRoot, './upsertGitIgnoreContents-update'); 219 await fs.promises.writeFile(targetGitIgnorePath, gitignore1); 220 221 const results = upsertGitIgnoreContents(targetGitIgnorePath, 'test-string'); 222 223 expect(results?.contents).toBe(` 224# hello world 225*/foo 226 227ios/ 228 229/android 230## bar 231 232 233# @generated expo-cli sync-4f49d69613b186e71104c7ca1b26c1e5b78c9193 234# The following patterns were generated by expo-cli 235 236test-string 237# @end expo-cli`); 238 239 expect(fs.readFileSync(targetGitIgnorePath, 'utf8')).toBe(results?.contents); 240 }); 241 242 it('can append an existing gitignore file', async () => { 243 const targetGitIgnorePath = path.join(testRoot, './upsertGitIgnoreContents-merge'); 244 await fs.promises.writeFile( 245 targetGitIgnorePath, 246 `# hello world 247*/foo 248 249ios/ 250 251/android 252## bar 253 254 255# @generated expo-cli sync-4f49d69613b186e71104c7ca1b26c1e5b78c9193 256# The following patterns were generated by expo-cli 257 258test-string 259# @end expo-cli` 260 ); 261 262 const results = upsertGitIgnoreContents(targetGitIgnorePath, 'another-test-string'); 263 264 expect(results?.contents).toBe(`# hello world 265*/foo 266 267ios/ 268 269/android 270## bar 271 272 273# @generated expo-cli sync-f479b6a00a92ff9cb6fa390e462eed9d41287dc1 274# The following patterns were generated by expo-cli 275 276test-string 277another-test-string 278# @end expo-cli`); 279 280 expect(fs.readFileSync(targetGitIgnorePath, 'utf8')).toBe(results?.contents); 281 }); 282 283 it('will ignore the update if it already exists', async () => { 284 const targetGitIgnorePath = path.join(testRoot, './upsertGitIgnoreContents-ignore'); 285 await fs.promises.writeFile( 286 targetGitIgnorePath, 287 `# hello world 288*/foo 289 290ios/ 291 292/android 293## bar 294 295 296# @generated expo-cli sync-4f49d69613b186e71104c7ca1b26c1e5b78c9193 297# The following patterns were generated by expo-cli 298 299test-string 300# @end expo-cli` 301 ); 302 303 const results = upsertGitIgnoreContents(targetGitIgnorePath, 'test-string'); 304 305 expect(results).toBeNull(); 306 }); 307}); 308