1import { vol } from 'memfs';
2import typedFetch from 'node-fetch';
3import typedPrompts from 'prompts';
4
5import {
6  ensureExampleExists,
7  GithubContent,
8  promptExamplesAsync,
9  sanitizeScriptsAsync,
10} from '../Examples';
11import { env } from '../utils/env';
12
13jest.mock('fs');
14jest.mock('node-fetch');
15jest.mock('prompts');
16
17const fetch = typedFetch as jest.MockedFunction<typeof typedFetch>;
18const prompts = typedPrompts as jest.MockedFunction<typeof typedPrompts>;
19
20describe(ensureExampleExists, () => {
21  it('resolves when example exists', async () => {
22    fetch.mockResolvedValue({ ok: true, status: 200 } as any);
23    await expect(ensureExampleExists('test')).resolves.not.toThrow();
24  });
25
26  it('rejects when example does note exists', async () => {
27    fetch.mockResolvedValue({ ok: false, status: 404 } as any);
28    await expect(() => ensureExampleExists('test')).rejects.toThrow(/example.*does not exist/i);
29  });
30
31  it('throws when running into rate limits', async () => {
32    fetch.mockResolvedValue({ ok: false, status: 403 } as any);
33    await expect(() => ensureExampleExists('test')).rejects.toThrow(
34      /unexpected GitHub API response/i
35    );
36  });
37});
38
39describe(promptExamplesAsync, () => {
40  it('throws when in CI mode', async () => {
41    const spy = jest.spyOn(env, 'CI', 'get').mockReturnValue(true);
42    await expect(() => promptExamplesAsync()).rejects.toThrowError(/cannot prompt/i);
43    spy.mockRestore();
44  });
45
46  it('prompts examples and return selected example', async () => {
47    // Make this test run in CI
48    const spy = jest.spyOn(env, 'CI', 'get').mockReturnValue(false);
49    const examples: GithubContent[] = [
50      { name: 'test-1', path: 'test-1', type: 'dir' },
51      { name: 'test-2', path: 'test-2', type: 'dir' },
52    ];
53
54    fetch.mockResolvedValue({ ok: true, json: () => Promise.resolve(examples) } as any);
55    prompts.mockResolvedValue({ answer: 'test-1' });
56
57    await expect(promptExamplesAsync()).resolves.toBe('test-1');
58    expect(prompts).toHaveBeenCalledWith(
59      expect.objectContaining({
60        choices: expect.arrayContaining([
61          { title: 'test-1', value: 'test-1' },
62          { title: 'test-2', value: 'test-2' },
63        ]),
64      })
65    );
66
67    spy.mockRestore();
68  });
69});
70
71describe(sanitizeScriptsAsync, () => {
72  afterEach(() => vol.reset());
73
74  it('adds default scripts for managed apps', async () => {
75    vol.fromJSON({
76      '/project/package.json': JSON.stringify({
77        name: 'project',
78        version: '0.0.0',
79      }),
80    });
81
82    await sanitizeScriptsAsync('/project');
83    const packageJson = JSON.parse(String(vol.readFileSync('/project/package.json')));
84
85    expect(packageJson.scripts).toMatchObject({
86      start: 'expo start',
87      android: 'expo start --android',
88      ios: 'expo start --ios',
89      web: 'expo start --web',
90    });
91  });
92
93  it('adds default scripts for bare apps', async () => {
94    vol.fromJSON({
95      '/project/android/build.gradle': 'fake-gradle',
96      '/project/ios/Podfile': 'fake-podfile',
97      '/project/package.json': JSON.stringify({
98        name: 'project',
99        version: '0.0.0',
100      }),
101    });
102
103    await sanitizeScriptsAsync('/project');
104    const packageJson = JSON.parse(String(vol.readFileSync('/project/package.json')));
105
106    expect(packageJson.scripts).toMatchObject({
107      start: 'expo start --dev-client',
108      android: 'expo run:android',
109      ios: 'expo run:ios',
110      web: 'expo start --web',
111    });
112  });
113
114  it('does not overwrite existing scripts', async () => {
115    vol.fromJSON({
116      '/project/package.json': JSON.stringify({
117        name: 'project',
118        version: '0.0.0',
119        scripts: {
120          start: 'node start.js',
121        },
122      }),
123    });
124
125    await sanitizeScriptsAsync('/project');
126    const packageJson = JSON.parse(String(vol.readFileSync('/project/package.json')));
127
128    expect(packageJson.scripts).toMatchObject({
129      start: 'node start.js',
130      android: 'expo start --android',
131      ios: 'expo start --ios',
132      web: 'expo start --web',
133    });
134  });
135});
136