109bb6093SEvan Bacon/* eslint-env jest */ 209bb6093SEvan Baconimport JsonFile from '@expo/json-file'; 3e1bb5bdfSKudo Chienimport execa, { ExecaError } from 'execa'; 409bb6093SEvan Baconimport fs from 'fs/promises'; 509bb6093SEvan Baconimport klawSync from 'klaw-sync'; 609bb6093SEvan Baconimport path from 'path'; 709bb6093SEvan Bacon 809bb6093SEvan Baconimport { 909bb6093SEvan Bacon execute, 1009bb6093SEvan Bacon projectRoot, 1109bb6093SEvan Bacon getLoadedModulesAsync, 1209bb6093SEvan Bacon bin, 1309bb6093SEvan Bacon setupTestProjectAsync, 14c94ad8a2SEvan Bacon installAsync, 1509bb6093SEvan Bacon} from './utils'; 1609bb6093SEvan Bacon 1709bb6093SEvan Baconconst originalForceColor = process.env.FORCE_COLOR; 18c94ad8a2SEvan Baconconst originalCI = process.env.CI; 1909bb6093SEvan BaconbeforeAll(async () => { 2009bb6093SEvan Bacon await fs.mkdir(projectRoot, { recursive: true }); 2109bb6093SEvan Bacon process.env.FORCE_COLOR = '0'; 22c94ad8a2SEvan Bacon process.env.CI = '1'; 2309bb6093SEvan Bacon}); 2409bb6093SEvan BaconafterAll(() => { 2509bb6093SEvan Bacon process.env.FORCE_COLOR = originalForceColor; 26c94ad8a2SEvan Bacon process.env.CI = originalCI; 2709bb6093SEvan Bacon}); 2809bb6093SEvan Bacon 2909bb6093SEvan Baconit('loads expected modules by default', async () => { 3009bb6093SEvan Bacon const modules = await getLoadedModulesAsync(`require('../../build/src/install').expoInstall`); 3109bb6093SEvan Bacon expect(modules).toStrictEqual([ 324067174dSWill Schurman '../node_modules/ansi-styles/index.js', 3309bb6093SEvan Bacon '../node_modules/arg/index.js', 3409bb6093SEvan Bacon '../node_modules/chalk/source/index.js', 3509bb6093SEvan Bacon '../node_modules/chalk/source/util.js', 3609bb6093SEvan Bacon '../node_modules/has-flag/index.js', 3709bb6093SEvan Bacon '../node_modules/supports-color/index.js', 3809bb6093SEvan Bacon '@expo/cli/build/src/install/index.js', 3909bb6093SEvan Bacon '@expo/cli/build/src/log.js', 4009bb6093SEvan Bacon '@expo/cli/build/src/utils/args.js', 4109bb6093SEvan Bacon ]); 4209bb6093SEvan Bacon}); 4309bb6093SEvan Bacon 4409bb6093SEvan Baconit('runs `npx install install --help`', async () => { 4509bb6093SEvan Bacon const results = await execute('install', '--help'); 4609bb6093SEvan Bacon expect(results.stdout).toMatchInlineSnapshot(` 4709bb6093SEvan Bacon " 4883d464dcSEvan Bacon Info 4909bb6093SEvan Bacon Install a module or other package to a project 5009bb6093SEvan Bacon 5109bb6093SEvan Bacon Usage 5283d464dcSEvan Bacon $ npx expo install 5309bb6093SEvan Bacon 5409bb6093SEvan Bacon Options 5583d464dcSEvan Bacon --check Check which installed packages need to be updated 5683d464dcSEvan Bacon --fix Automatically update any invalid package versions 5709bb6093SEvan Bacon --npm Use npm to install dependencies. Default when package-lock.json exists 5809bb6093SEvan Bacon --yarn Use Yarn to install dependencies. Default when yarn.lock exists 59*9b1b5ec6SEvan Bacon --bun Use bun to install dependencies. Default when bun.lockb exists 606caf5755SEvan Bacon --pnpm Use pnpm to install dependencies. Default when pnpm-lock.yaml exists 6183d464dcSEvan Bacon -h, --help Usage info 6209bb6093SEvan Bacon 6309bb6093SEvan Bacon Additional options can be passed to the underlying install command by using -- 6483d464dcSEvan Bacon $ npx expo install react -- --verbose 6509bb6093SEvan Bacon > yarn add react --verbose 6609bb6093SEvan Bacon " 6709bb6093SEvan Bacon `); 6809bb6093SEvan Bacon}); 6909bb6093SEvan Bacon 7009bb6093SEvan Baconit( 7109bb6093SEvan Bacon 'runs `npx expo install expo-sms`', 7209bb6093SEvan Bacon async () => { 7309bb6093SEvan Bacon const projectRoot = await setupTestProjectAsync('basic-install', 'with-blank'); 7409bb6093SEvan Bacon // `npx expo install expo-sms` 7509bb6093SEvan Bacon await execa('node', [bin, 'install', 'expo-sms'], { cwd: projectRoot }); 7609bb6093SEvan Bacon 7709bb6093SEvan Bacon // List output files with sizes for snapshotting. 7809bb6093SEvan Bacon // This is to make sure that any changes to the output are intentional. 7909bb6093SEvan Bacon // Posix path formatting is used to make paths the same across OSes. 8009bb6093SEvan Bacon const files = klawSync(projectRoot) 8109bb6093SEvan Bacon .map((entry) => { 8209bb6093SEvan Bacon if (entry.path.includes('node_modules') || !entry.stats.isFile()) { 8309bb6093SEvan Bacon return null; 8409bb6093SEvan Bacon } 8509bb6093SEvan Bacon return path.posix.relative(projectRoot, entry.path); 8609bb6093SEvan Bacon }) 8709bb6093SEvan Bacon .filter(Boolean); 8809bb6093SEvan Bacon 8909bb6093SEvan Bacon const pkg = await JsonFile.readAsync(path.resolve(projectRoot, 'package.json')); 9009bb6093SEvan Bacon 9109bb6093SEvan Bacon // Added expected package 92e1bb5bdfSKudo Chien const pkgDependencies = pkg.dependencies as Record<string, string>; 93425e7f7fSEvan Bacon expect(pkgDependencies['expo-sms']).toBe('~11.0.0'); 9409bb6093SEvan Bacon expect(pkg.devDependencies).toEqual({ 9509bb6093SEvan Bacon '@babel/core': '^7.12.9', 9609bb6093SEvan Bacon }); 9709bb6093SEvan Bacon 9809bb6093SEvan Bacon // Added new packages 99e1bb5bdfSKudo Chien expect(Object.keys(pkg.dependencies ?? {}).sort()).toStrictEqual([ 10009bb6093SEvan Bacon 'expo', 10109bb6093SEvan Bacon 'expo-sms', 10209bb6093SEvan Bacon 'react', 10309bb6093SEvan Bacon 'react-native', 10409bb6093SEvan Bacon ]); 10509bb6093SEvan Bacon 10609bb6093SEvan Bacon expect(files).toStrictEqual(['App.js', 'app.json', 'package.json', 'yarn.lock']); 10709bb6093SEvan Bacon }, 10809bb6093SEvan Bacon // Could take 45s depending on how fast npm installs 10909bb6093SEvan Bacon 60 * 1000 11009bb6093SEvan Bacon); 111c94ad8a2SEvan Bacon 112c94ad8a2SEvan Baconit( 113c94ad8a2SEvan Bacon 'runs `npx expo install --check` fails', 114c94ad8a2SEvan Bacon async () => { 115c94ad8a2SEvan Bacon const projectRoot = await setupTestProjectAsync('install-check-fail', 'with-blank'); 116c94ad8a2SEvan Bacon await installAsync(projectRoot, ['add', '[email protected]', '[email protected]']); 117c94ad8a2SEvan Bacon 118c94ad8a2SEvan Bacon let pkg = await JsonFile.readAsync(path.resolve(projectRoot, 'package.json')); 119c94ad8a2SEvan Bacon // Added expected package 120e1bb5bdfSKudo Chien let pkgDependencies = pkg.dependencies as Record<string, string>; 121e1bb5bdfSKudo Chien expect(pkgDependencies['expo-sms']).toBe('1.0.0'); 122c94ad8a2SEvan Bacon 123c94ad8a2SEvan Bacon try { 124c94ad8a2SEvan Bacon await execa('node', [bin, 'install', '--check'], { cwd: projectRoot }); 125c94ad8a2SEvan Bacon throw new Error('SHOULD NOT HAPPEN'); 126e1bb5bdfSKudo Chien } catch (e) { 127e1bb5bdfSKudo Chien const error = e as ExecaError; 128fa5d5955SEvan Bacon expect(error.stderr).toMatch(/expo-auth-session@1\.0\.0 - expected version: ~3\.\d\.\d/); 129425e7f7fSEvan Bacon expect(error.stderr).toMatch(/expo-sms@1\.0\.0 - expected version: ~11\.\d\.\d/); 130fb2f654cSEvan Bacon expect(error.stderr).toMatch(/npx expo install --fix/); 131c94ad8a2SEvan Bacon } 132c94ad8a2SEvan Bacon 133c94ad8a2SEvan Bacon await expect( 134c94ad8a2SEvan Bacon execa('node', [bin, 'install', 'expo-sms', '--check'], { cwd: projectRoot }) 135425e7f7fSEvan Bacon ).rejects.toThrowError(/expo-sms@1\.0\.0 - expected version: ~11\.\d\.\d/); 136c94ad8a2SEvan Bacon 137c94ad8a2SEvan Bacon // Check doesn't fix packages 138c94ad8a2SEvan Bacon pkg = await JsonFile.readAsync(path.resolve(projectRoot, 'package.json')); 139c94ad8a2SEvan Bacon // Added expected package 140e1bb5bdfSKudo Chien pkgDependencies = pkg.dependencies as Record<string, string>; 141e1bb5bdfSKudo Chien expect(pkgDependencies['expo-sms']).toBe('1.0.0'); 142c94ad8a2SEvan Bacon }, 143c94ad8a2SEvan Bacon // Could take 45s depending on how fast npm installs 144c94ad8a2SEvan Bacon 60 * 1000 145c94ad8a2SEvan Bacon); 146c94ad8a2SEvan Bacon 147c94ad8a2SEvan Baconit( 148c94ad8a2SEvan Bacon 'runs `npx expo install --fix` fails', 149c94ad8a2SEvan Bacon async () => { 150c94ad8a2SEvan Bacon const projectRoot = await setupTestProjectAsync('install-fix-fail', 'with-blank'); 151c94ad8a2SEvan Bacon await installAsync(projectRoot, ['add', '[email protected]', '[email protected]']); 152c94ad8a2SEvan Bacon 153c94ad8a2SEvan Bacon await execa('node', [bin, 'install', '--fix', 'expo-sms'], { cwd: projectRoot }); 154c94ad8a2SEvan Bacon 155c94ad8a2SEvan Bacon // Ensure the versions are invalid 156c94ad8a2SEvan Bacon await expect( 157c94ad8a2SEvan Bacon execa('node', [bin, 'install', '--check'], { cwd: projectRoot }) 158c94ad8a2SEvan Bacon ).rejects.toThrow(); 159c94ad8a2SEvan Bacon 160c94ad8a2SEvan Bacon // Check doesn't fix packages 161c94ad8a2SEvan Bacon let pkg = await JsonFile.readAsync(path.resolve(projectRoot, 'package.json')); 162c94ad8a2SEvan Bacon // Added expected package 163e1bb5bdfSKudo Chien let pkgDependencies = pkg.dependencies as Record<string, string>; 164425e7f7fSEvan Bacon expect(pkgDependencies['expo-sms']).toBe('~11.0.0'); 165c94ad8a2SEvan Bacon 166c94ad8a2SEvan Bacon // Didn't fix expo-auth-session since we didn't pass it in 167e1bb5bdfSKudo Chien expect(pkgDependencies['expo-auth-session']).toBe('1.0.0'); 168c94ad8a2SEvan Bacon 169c94ad8a2SEvan Bacon // Fix all versions 170c94ad8a2SEvan Bacon await execa('node', [bin, 'install', '--fix'], { cwd: projectRoot }); 171c94ad8a2SEvan Bacon 172c94ad8a2SEvan Bacon // Check that the versions are fixed 173c94ad8a2SEvan Bacon pkg = await JsonFile.readAsync(path.resolve(projectRoot, 'package.json')); 174c94ad8a2SEvan Bacon 175c94ad8a2SEvan Bacon // Didn't fix expo-auth-session since we didn't pass it in 176e1bb5bdfSKudo Chien pkgDependencies = pkg.dependencies as Record<string, string>; 177425e7f7fSEvan Bacon expect(pkgDependencies['expo-auth-session']).toBe('~3.8.0'); 178c94ad8a2SEvan Bacon }, 179c94ad8a2SEvan Bacon // Could take 45s depending on how fast npm installs 180c94ad8a2SEvan Bacon 60 * 1000 181c94ad8a2SEvan Bacon); 182