microsoft/vscode-react-native

Public

mirrored fromhttps://github.com/microsoft/vscode-react-nativeAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
c7f1165c059665e9baf0897641ef4f63773f36ed

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

src/test/resources/reactNative022.ts

178lines · 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
4import * as Q from "q";
5import * as path from "path";
6import * as assert from "assert";
7
8import {PromiseUtil} from "../../common/node/promise";
9
10import * as reactNative from "../../common/reactNative";
11import {ISpawnResult} from "../../common/node/childProcess";
12import {FileSystem} from "../../common/node/fileSystem";
13import {Package} from "../../common/node/package";
14import {Recording, Simulator} from "./processExecution/simulator";
15import {DeviceHelper} from "./simulators/deviceHelper";
16import {APKSerializer} from "./simulators/apkSerializer";
17
18const resourcesPath = path.join(__dirname, "../../../src/test/resources/");
19const sampleRNProjectPath = path.join(resourcesPath, "sampleReactNative022Project");
20const processExecutionsRecordingsPath = path.join(resourcesPath, "processExecutionsRecordings");
21
22export type IReactNative = reactNative.IReactNative;
23
24/* This class simulates calling the React-Native CLI v0.22. It currently supports react-native init
25 and react-native run-android. The events used in react-native run-android were all generated from
26 real executions of the process, so the simulation will be as close as possible to the real cli. */
27export class ReactNative022 implements IReactNative {
28 private static ANDROID_APK_RELATIVE_PATH = "android/app/build/outputs/apk/app-debug.apk";
29
30 private simulator: Simulator = new Simulator({
31 beforeStart: () => this.readAndroidPackageName(), // 1. We read the package.json to verify this is a RN project
32 outputBased: [
33 {
34 eventPattern: /:app:assembleDebug/,
35 action: () => this.createAPK(), // 2. We compile the application.
36 },
37 {
38 eventPattern: /Installed on [0-9]+ devices*\./,
39 action: () => this.installAppInAllDevices(), // 3. We install it on all available devices.
40 },
41 ],
42 beforeSuccess: (stdout: string, stderr: string) => // 4. If we didn't had any errors after starting to launch the app,
43 this.launchApp(stdout, stderr), // it means we were succesful
44 });
45
46 private recording: Recording;
47
48 private androidPackageName: string;
49 private projectRoot: string;
50 private androidAPKPath: string;
51
52 constructor(private deviceHelper: DeviceHelper, private fileSystem: FileSystem) {
53 assert(this.deviceHelper, "deviceHelper shouldn't be null");
54 assert(this.fileSystem, "fileSystem shouldn't be null");
55 }
56
57 public loadRecordingFromName(recordingName: string): Q.Promise<void> {
58 return this.loadRecordingFromFile(path.join(processExecutionsRecordingsPath, `${recordingName}.json`));
59 }
60
61 public loadRecordingFromFile(recordingPath: string): Q.Promise<void> {
62 return Q({})
63 .then(() => {
64 return new FileSystem().readFile(recordingPath);
65 }).then(fileContents => {
66 this.loadRecording(JSON.parse(fileContents));
67 });
68 }
69
70 public loadRecording(recording: Recording): void {
71 assert(recording, "recording shouldn't be null");
72 this.recording = recording;
73 }
74
75 public createProject(projectRoot: string, projectName: string): Q.Promise<void> {
76 return Q({})
77 .then(() => {
78 this.fileSystem.makeDirectoryRecursiveSync(projectRoot);
79 return this.readDefaultProjectFile("package.json");
80 }).then(defaultContents => {
81 const reactNativeConfiguration = JSON.parse(defaultContents);
82 reactNativeConfiguration.name = projectName;
83 const reactNativeConfigurationFormatted = JSON.stringify(reactNativeConfiguration);
84 return this.fileSystem.writeFile(this.getPackageJsonPath(projectRoot), reactNativeConfigurationFormatted);
85 }).then(() => {
86 return this.fileSystem.mkDir(this.getAndroidProjectPath(projectRoot));
87 });
88 }
89
90 public runAndroid(projectRoot: string): ISpawnResult {
91 this.projectRoot = projectRoot;
92 this.simulator.simulate(this.recording).done();
93 return this.simulator.spawn();
94 }
95
96 private getAndroidProjectPath(projectRoot = this.projectRoot): string {
97 return path.join(projectRoot, "android");
98 }
99
100 private getPackageJsonPath(projectRoot: string): string {
101 return new Package(projectRoot, { fileSystem: this.fileSystem }).informationJsonFilePath();
102 }
103
104 private readAndroidPackageName(): Q.Promise<void> {
105 return new Package(this.projectRoot, { fileSystem: this.fileSystem }).name().then(name => {
106 this.androidPackageName = `com.${name.toLowerCase()}`;
107 });
108 }
109
110 private createAPK(): Q.Promise<void> {
111 return this.isAndroidProjectPresent().then(isPresent => {
112 if (!isPresent) {
113 return Q.reject<void>(new Error("The recording expects the Android project to be present, but it's not"));
114 }
115 }).then(() => {
116 this.androidAPKPath = path.join(this.projectRoot, ReactNative022.ANDROID_APK_RELATIVE_PATH);
117 return new APKSerializer(this.fileSystem).writeApk(this.androidAPKPath, { packageName: this.androidPackageName });
118 });
119 }
120
121 private isAndroidProjectPresent(): Q.Promise<boolean> {
122 // TODO: Make more checks as neccesary for the tests
123 return this.fileSystem.directoryExists(this.getAndroidProjectPath());
124 }
125
126 private installAppInAllDevices(): Q.Promise<void> {
127 return new PromiseUtil().reduce(this.deviceHelper.getConnectedDevices(), device => this.installAppInDevice(device.id));
128 }
129
130 private installAppInDevice(deviceId: string): Q.Promise<void> {
131 return this.deviceHelper.isDeviceOnline(deviceId).then(isOnline => {
132 if (isOnline) {
133 return this.deviceHelper.installApp(this.androidAPKPath, deviceId);
134 } else {
135 // TODO: Figure out what's the right thing to do here
136 }
137 });
138 }
139
140 private launchApp(stdout: string, stderr: string): Q.Promise<void> {
141 /*
142 Sample output we want to accept:
143 BUILD SUCCESSFUL
144
145 Total time: 9.052 secs
146 Starting the app (C:\Program Files (x86)\Android\android-sdk/platform-tools/adb shell am start -n com.sampleapplication/.MainActivity)...
147 Starting: Intent { cmp=com.sampleapplication/.MainActivity }
148
149
150 Sample output we don't to accept:
151 BUILD SUCCESSFUL
152
153 Total time: 9.052 secs
154 Starting the app (C:\Program Files (x86)\Android\android-sdk/platform-tools/adb shell am start -n com.sampleapplication/.MainActivity)...
155 Starting: Intent { cmp=com.sampleapplication/.MainActivity }
156 Error: some error happened
157 **/
158 const succesfulOutputEnd = `Starting the app \\(.*adb shell am start -n ([^ /]+)\/\\.MainActivity\\)\\.\\.\\.\\s+`
159 + `Starting: Intent { cmp=([^ /]+)\/\\.MainActivity }\\s+$`;
160 const matches = stdout.match(new RegExp(succesfulOutputEnd));
161 if (matches) {
162 if (matches.length === 3 && matches[1] === this.androidPackageName && matches[2] === this.androidPackageName) {
163 return this.deviceHelper.launchApp(this.projectRoot, this.androidPackageName);
164 } else {
165 return Q.reject<void>(new Error("There was an error while trying to match the Starting the app messages."
166 + "Expected to match the pattern and recognize the expected android package name, but it failed."
167 + `Expected android package name: ${this.androidPackageName}. Actual matches: ${JSON.stringify(matches)}`));
168 }
169 } else {
170 // The record doesn't indicate that the app was launched, so we don't do anything
171 }
172 }
173
174 private readDefaultProjectFile(relativeFilePath: string): Q.Promise<string> {
175 const realFileSystem = new FileSystem(); // We always use the real file system (not the mock one) to read the sample project
176 return realFileSystem.readFile(path.join(sampleRNProjectPath, relativeFilePath));
177 }
178}
179