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