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