microsoft/vscode-react-native
Publicmirrored from https://github.com/microsoft/vscode-react-nativeAvailable
test/resources/reactNative065.ts
208lines · modecode
| 1 | // Copyright (c) Microsoft Corporation. All rights reserved. |
| 2 | // Licensed under the MIT license. See LICENSE file in the project root for details. |
| 3 | |
| 4 | import * as path from "path"; |
| 5 | import assert = require("assert"); |
| 6 | import { PromiseUtil } from "../../src/common/node/promise"; |
| 7 | import { IAndroidRunOptions } from "../../src/extension/launchArgs"; |
| 8 | import { ISpawnResult } from "../../src/common/node/childProcess"; |
| 9 | import { FileSystem } from "../../src/common/node/fileSystem"; |
| 10 | import { Package } from "../../src/common/node/package"; |
| 11 | import { Recording, Simulator } from "./processExecution/simulator"; |
| 12 | import { AdbHelper } from "../../src/extension/android/adb"; |
| 13 | import { APKSerializer } from "./simulators/apkSerializer"; |
| 14 | |
| 15 | const sampleRNProjectPath = path.join(__dirname, "sampleReactNativeProject"); |
| 16 | const processExecutionsRecordingsPath = path.join(__dirname, "processExecutionsRecordings"); |
| 17 | |
| 18 | // This class simulates calling the React-Native CLI v0.65. |
| 19 | export class ReactNative065 { |
| 20 | public static DEFAULT_PROJECT_FILE = path.join(sampleRNProjectPath, "package.json"); |
| 21 | |
| 22 | private static ANDROID_APK_RELATIVE_PATH = "android/app/build/outputs/apk/app-debug.apk"; |
| 23 | |
| 24 | private projectFileContent!: string; |
| 25 | |
| 26 | private simulator: Simulator = new Simulator({ |
| 27 | beforeStart: () => this.readAndroidPackageName(), // 1. We read the package.json to verify this is a RN project |
| 28 | outputBased: [ |
| 29 | { |
| 30 | eventPattern: /:app:assembleDebug/, |
| 31 | action: () => this.createAPK(), // 2. We compile the application. |
| 32 | }, |
| 33 | { |
| 34 | eventPattern: /Installed on [0-9]+ devices*\./, |
| 35 | action: () => this.installAppInAllDevices(), // 3. We install it on all available devices. |
| 36 | }, |
| 37 | ], |
| 38 | beforeSuccess: ( |
| 39 | stdout: string, |
| 40 | stderr: string, // 4. If we didn't had any errors after starting to launch the app, |
| 41 | ) => this.launchApp(stdout, stderr), // it means we were succesful |
| 42 | }); |
| 43 | |
| 44 | private recording!: Recording; |
| 45 | |
| 46 | private androidPackageName!: string; |
| 47 | private projectRoot!: string; |
| 48 | private androidAPKPath!: string; |
| 49 | |
| 50 | constructor(private fileSystem: FileSystem, private adbHelper: AdbHelper) { |
| 51 | assert(this.fileSystem, "fileSystem shouldn't be null"); |
| 52 | } |
| 53 | |
| 54 | public fromProjectFileContent(content: string): this { |
| 55 | this.projectFileContent = content; |
| 56 | return this; |
| 57 | } |
| 58 | |
| 59 | public loadRecordingFromName(recordingName: string): Promise<void> { |
| 60 | return this.loadRecordingFromFile( |
| 61 | path.join(processExecutionsRecordingsPath, `${recordingName}.json`), |
| 62 | ); |
| 63 | } |
| 64 | |
| 65 | public loadRecordingFromString(recordingContent: string): Promise<void> { |
| 66 | return Promise.resolve(this.loadRecording(JSON.parse(recordingContent))); |
| 67 | } |
| 68 | |
| 69 | public loadRecordingFromFile(recordingPath: string): Promise<void> { |
| 70 | return new FileSystem().readFile(recordingPath).then(fileContents => { |
| 71 | this.loadRecording(JSON.parse(fileContents.toString())); |
| 72 | }); |
| 73 | } |
| 74 | |
| 75 | public loadRecording(recording: Recording): void { |
| 76 | assert(recording, "recording shouldn't be null"); |
| 77 | this.recording = recording; |
| 78 | } |
| 79 | |
| 80 | public createProject(projectRoot: string, projectName: string): Promise<void> { |
| 81 | return Promise.resolve() |
| 82 | .then(() => { |
| 83 | this.fileSystem.makeDirectoryRecursiveSync(projectRoot); |
| 84 | return this.projectFileContent !== undefined |
| 85 | ? this.projectFileContent |
| 86 | : this.readDefaultProjectFile(); |
| 87 | }) |
| 88 | .then(defaultContents => { |
| 89 | const reactNativeConfiguration = JSON.parse(defaultContents.toString()); |
| 90 | reactNativeConfiguration.name = projectName; |
| 91 | const reactNativeConfigurationFormatted = JSON.stringify(reactNativeConfiguration); |
| 92 | return this.fileSystem.writeFile( |
| 93 | this.getPackageJsonPath(projectRoot), |
| 94 | reactNativeConfigurationFormatted, |
| 95 | ); |
| 96 | }) |
| 97 | .then(() => { |
| 98 | return this.fileSystem.mkDir(this.getAndroidProjectPath(projectRoot)); |
| 99 | }); |
| 100 | } |
| 101 | |
| 102 | public runAndroid(runOptions: IAndroidRunOptions): ISpawnResult { |
| 103 | this.projectRoot = runOptions.projectRoot; |
| 104 | this.simulator.simulate(this.recording).then(() => {}); |
| 105 | return this.simulator.spawn(); |
| 106 | } |
| 107 | |
| 108 | private getAndroidProjectPath(projectRoot = this.projectRoot): string { |
| 109 | return path.join(projectRoot, "android"); |
| 110 | } |
| 111 | |
| 112 | private getPackageJsonPath(projectRoot: string): string { |
| 113 | return new Package(projectRoot, { fileSystem: this.fileSystem }).informationJsonFilePath(); |
| 114 | } |
| 115 | |
| 116 | private readAndroidPackageName(): Promise<void> { |
| 117 | return new Package(this.projectRoot, { fileSystem: this.fileSystem }).name().then(name => { |
| 118 | this.androidPackageName = `com.${name.toLowerCase()}`; |
| 119 | }); |
| 120 | } |
| 121 | |
| 122 | private createAPK(): Promise<void> { |
| 123 | return this.isAndroidProjectPresent() |
| 124 | .then(isPresent => { |
| 125 | return isPresent |
| 126 | ? void 0 |
| 127 | : Promise.reject<void>( |
| 128 | new Error( |
| 129 | "The recording expects the Android project to be present, but it's not", |
| 130 | ), |
| 131 | ); |
| 132 | }) |
| 133 | .then(() => { |
| 134 | this.androidAPKPath = path.join( |
| 135 | this.projectRoot, |
| 136 | ReactNative065.ANDROID_APK_RELATIVE_PATH, |
| 137 | ); |
| 138 | return new APKSerializer(this.fileSystem).writeApk(this.androidAPKPath, { |
| 139 | packageName: this.androidPackageName, |
| 140 | }); |
| 141 | }); |
| 142 | } |
| 143 | |
| 144 | private isAndroidProjectPresent(): Promise<boolean> { |
| 145 | // TODO: Make more checks as neccesary for the tests |
| 146 | return this.fileSystem.directoryExists(this.getAndroidProjectPath()); |
| 147 | } |
| 148 | |
| 149 | private installAppInAllDevices(): Promise<void> { |
| 150 | let devices = this.adbHelper.getConnectedTargets(); |
| 151 | return PromiseUtil.reduce(devices, device => this.installAppInDevice(device.id)); |
| 152 | } |
| 153 | |
| 154 | private installAppInDevice(deviceId: string): Promise<void> { |
| 155 | throw Error("Mock not implemented"); |
| 156 | } |
| 157 | |
| 158 | private launchApp(stdout: string, stderr: string): Promise<void> { |
| 159 | /* |
| 160 | Sample output we want to accept: |
| 161 | BUILD SUCCESSFUL |
| 162 | |
| 163 | Total time: 9.052 secs |
| 164 | Starting the app (C:\Program Files (x86)\Android\android-sdk/platform-tools/adb shell am start -n com.sampleapplication/.MainActivity)... |
| 165 | Starting: Intent { cmp=com.sampleapplication/.MainActivity } |
| 166 | |
| 167 | |
| 168 | Sample output we don't to accept: |
| 169 | BUILD SUCCESSFUL |
| 170 | |
| 171 | Total time: 9.052 secs |
| 172 | Starting the app (C:\Program Files (x86)\Android\android-sdk/platform-tools/adb shell am start -n com.sampleapplication/.MainActivity)... |
| 173 | Starting: Intent { cmp=com.sampleapplication/.MainActivity } |
| 174 | Error: some error happened |
| 175 | **/ |
| 176 | const succesfulOutputEnd = |
| 177 | `Starting the app \\(.*adb shell am start -n ([^ /]+)\/\\.MainActivity\\)\\.\\.\\.\\s+` + |
| 178 | `Starting: Intent { cmp=([^ /]+)\/\\.MainActivity }\\s+$`; |
| 179 | const matches = stdout.match(new RegExp(succesfulOutputEnd)); |
| 180 | if (matches) { |
| 181 | if ( |
| 182 | matches.length === 3 && |
| 183 | matches[1] === this.androidPackageName && |
| 184 | matches[2] === this.androidPackageName |
| 185 | ) { |
| 186 | return this.adbHelper.launchApp(this.projectRoot, this.androidPackageName); |
| 187 | } else { |
| 188 | return Promise.reject<void>( |
| 189 | new Error( |
| 190 | "There was an error while trying to match the Starting the app messages." + |
| 191 | "Expected to match the pattern and recognize the expected android package name, but it failed." + |
| 192 | `Expected android package name: ${ |
| 193 | this.androidPackageName |
| 194 | }. Actual matches: ${JSON.stringify(matches)}`, |
| 195 | ), |
| 196 | ); |
| 197 | } |
| 198 | } else { |
| 199 | // The record doesn't indicate that the app was launched, so we don't do anything |
| 200 | return Promise.resolve(); |
| 201 | } |
| 202 | } |
| 203 | |
| 204 | private readDefaultProjectFile(): Promise<string | Buffer> { |
| 205 | const realFileSystem = new FileSystem(); // We always use the real file system (not the mock one) to read the sample project |
| 206 | return realFileSystem.readFile(ReactNative065.DEFAULT_PROJECT_FILE); |
| 207 | } |
| 208 | } |
| 209 | |