Skip to content

Commit ec4d21c

Browse files
committed
Add unit tests for preview-app-livesync-service
1 parent aac3668 commit ec4d21c

File tree

2 files changed

+347
-4
lines changed

2 files changed

+347
-4
lines changed

lib/services/livesync/playground/preview-app-livesync-service.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ import { PreviewSdkEventNames } from "./preview-app-constants";
44
import { APP_FOLDER_NAME, APP_RESOURCES_FOLDER_NAME, TNS_MODULES_FOLDER_NAME } from "../../../constants";
55

66
export class PreviewAppLiveSyncService implements IPreviewAppLiveSyncService {
7+
private excludedFileExtensions = [".ts", ".sass", ".scss", ".less"];
8+
private excludedFiles = [".DS_Store"];
9+
710
constructor(private $fs: IFileSystem,
811
private $logger: ILogger,
912
private $platformService: IPlatformService,
@@ -67,15 +70,14 @@ export class PreviewAppLiveSyncService implements IPreviewAppLiveSyncService {
6770
if (files) {
6871
files = files.map(file => path.join(platformsAppFolderPath, path.relative(appFolderPath, file)));
6972
} else {
70-
const excludedProjectDirsAndFiles = [TNS_MODULES_FOLDER_NAME, APP_RESOURCES_FOLDER_NAME, "*.ts", "*.sass", "*.scss", ".less"];
71-
files = this.$projectFilesManager.getProjectFiles(platformsAppFolderPath, excludedProjectDirsAndFiles);
73+
files = this.$projectFilesManager.getProjectFiles(platformsAppFolderPath);
7274
}
7375

7476
const filesToTransfer = files
7577
.filter(file => file.indexOf(TNS_MODULES_FOLDER_NAME) === -1)
7678
.filter(file => file.indexOf(APP_RESOURCES_FOLDER_NAME) === -1)
77-
.filter(file => path.basename(file) !== "main.aot.js")
78-
.filter(file => path.basename(file) !== ".DS_Store");
79+
.filter(file => !_.includes(this.excludedFiles, path.basename(file)))
80+
.filter(file => !_.includes(this.excludedFileExtensions, path.extname(file)));
7981

8082
this.$logger.trace(`Transferring ${filesToTransfer.join("\n")}.`);
8183

Lines changed: 341 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,341 @@
1+
import { Yok } from "../../../lib/common/yok";
2+
import { LoggerStub } from "../../stubs";
3+
import { FilePayload, Device } from "nativescript-preview-sdk";
4+
import { EventEmitter } from "events";
5+
import { PreviewAppLiveSyncService } from "../../../lib/services/livesync/playground/preview-app-livesync-service";
6+
import * as chai from "chai";
7+
import * as path from "path";
8+
import { ProjectFilesManager } from "../../../lib/common/services/project-files-manager";
9+
10+
interface ITestCase {
11+
name: string;
12+
appFiles?: string[];
13+
expectedFiles?: string[];
14+
actOptions?: IActOptions;
15+
assertOptions?: IAssertOptions;
16+
}
17+
18+
interface IActOptions {
19+
emitDeviceConnected: boolean;
20+
}
21+
22+
interface IAssertOptions {
23+
checkWarnings?: boolean;
24+
checkQrCode?: boolean;
25+
}
26+
27+
interface IActInput {
28+
previewAppLiveSyncService?: IPreviewAppLiveSyncService;
29+
previewSdkService?: IPreviewSdkService;
30+
projectFiles?: string[];
31+
actOptions?: IActOptions;
32+
}
33+
34+
let isGenerateQrCodeForCurrentAppCalled = false;
35+
let applyChangesParams: FilePayload[] = [];
36+
let readTextParams: string[] = [];
37+
let warnParams: string[] = [];
38+
const nativeFilesWarning = "Unable to apply changes from App_Resources folder. You need to build your application in order to make changes in App_Resources folder.";
39+
40+
const projectDirPath = "/path/to/my/project";
41+
const platformsDirPath = path.join(projectDirPath, "platforms");
42+
43+
const deviceMockData = <Device>{ };
44+
const defaultProjectFiles = [
45+
"my/test/file1.js",
46+
"my/test/file2.js",
47+
"my/test/file3.js",
48+
"my/test/nested/file1.js",
49+
"my/test/nested/file2.js",
50+
"my/test/nested/file3.js"
51+
];
52+
const syncFilesMockData = {
53+
projectDir: projectDirPath,
54+
appFilesUpdaterOptions: {
55+
release: false,
56+
bundle: false
57+
},
58+
env: { }
59+
};
60+
61+
class PreviewSdkServiceMock extends EventEmitter implements IPreviewSdkService {
62+
public get qrCodeUrl() {
63+
return "my_cool_qr_code_url";
64+
}
65+
66+
public connectedDevices: Device[] = [deviceMockData];
67+
public initialize() { /* empty block */ }
68+
69+
public async applyChanges(files: FilePayload[]) {
70+
applyChangesParams.push(...files);
71+
}
72+
73+
public stop() { /* empty block */ }
74+
}
75+
76+
class LoggerMock extends LoggerStub {
77+
warn(...args: string[]): void {
78+
warnParams.push(...args);
79+
}
80+
}
81+
82+
function createTestInjector(options?: {
83+
projectFiles?: string[]
84+
}) {
85+
options = options || {};
86+
87+
const injector = new Yok();
88+
injector.register("logger", LoggerMock);
89+
injector.register("platformService", {
90+
preparePlatform: async () => ({})
91+
});
92+
injector.register("platformsData", {
93+
getPlatformData: () => ({
94+
appDestinationDirectoryPath: platformsDirPath
95+
})
96+
});
97+
injector.register("projectDataService", {
98+
getProjectData: () => ({
99+
projectDir: projectDirPath
100+
})
101+
});
102+
injector.register("previewSdkService", PreviewSdkServiceMock);
103+
injector.register("previewAppPluginsService", {
104+
comparePluginsOnDevice: async () => ({})
105+
});
106+
injector.register("projectFilesManager", ProjectFilesManager);
107+
injector.register("playgroundQrCodeGenerator", {
108+
generateQrCodeForCurrentApp: async () => {
109+
isGenerateQrCodeForCurrentAppCalled = true;
110+
}
111+
});
112+
injector.register("previewAppLiveSyncService", PreviewAppLiveSyncService);
113+
injector.register("fs", {
114+
readText: (filePath: string) => {
115+
readTextParams.push(filePath);
116+
},
117+
enumerateFilesInDirectorySync: (projectFilesPath: string) => {
118+
if (options.projectFiles) {
119+
return options.projectFiles.map(file => path.join(projectDirPath, file));
120+
}
121+
122+
return defaultProjectFiles.map(file => path.join(platformsDirPath, "app", file));
123+
}
124+
});
125+
injector.register("localToDevicePathDataFactory", {});
126+
injector.register("projectFilesProvider", {});
127+
128+
return injector;
129+
}
130+
131+
function arrange(options?: { projectFiles ?: string[] }) {
132+
options = options || {};
133+
134+
const injector = createTestInjector({ projectFiles: options.projectFiles });
135+
const previewAppLiveSyncService: IPreviewAppLiveSyncService = injector.resolve("previewAppLiveSyncService");
136+
const previewSdkService: IPreviewSdkService = injector.resolve("previewSdkService");
137+
138+
return {
139+
previewAppLiveSyncService,
140+
previewSdkService
141+
};
142+
}
143+
144+
async function initialSync(input?: IActInput) {
145+
input = input || {};
146+
147+
const { previewAppLiveSyncService, previewSdkService, actOptions } = input;
148+
149+
await previewAppLiveSyncService.initialSync(syncFilesMockData);
150+
if (actOptions.emitDeviceConnected) {
151+
previewSdkService.emit("onDeviceConnected", deviceMockData);
152+
}
153+
}
154+
155+
async function syncFiles(input?: IActInput) {
156+
input = input || { };
157+
158+
const { previewAppLiveSyncService, previewSdkService, projectFiles, actOptions } = input;
159+
160+
if (actOptions.emitDeviceConnected) {
161+
previewSdkService.emit("onDeviceConnected", deviceMockData);
162+
}
163+
164+
await previewAppLiveSyncService.syncFiles(syncFilesMockData, projectFiles);
165+
}
166+
167+
async function assert(expectedFiles: string[], options?: IAssertOptions) {
168+
options = options || {};
169+
170+
chai.assert.deepEqual(applyChangesParams, mapFiles(expectedFiles));
171+
172+
if (options.checkWarnings) {
173+
chai.assert.deepEqual(warnParams, [nativeFilesWarning]);
174+
}
175+
176+
if (options.checkQrCode) {
177+
chai.assert.isTrue(isGenerateQrCodeForCurrentAppCalled);
178+
}
179+
}
180+
181+
function reset() {
182+
isGenerateQrCodeForCurrentAppCalled = false;
183+
applyChangesParams = [];
184+
readTextParams = [];
185+
warnParams = [];
186+
}
187+
188+
function mapFiles(files: string[]): FilePayload[] {
189+
if (!files) {
190+
return [];
191+
}
192+
193+
return files.map(file => {
194+
return {
195+
event: "change",
196+
file: path.relative(path.join(platformsDirPath, "app"), path.join(platformsDirPath, "app", file)),
197+
fileContents: undefined,
198+
binary: false
199+
};
200+
});
201+
}
202+
203+
function setDefaults(testCase: ITestCase): ITestCase {
204+
if (!testCase.actOptions) {
205+
testCase.actOptions = {
206+
emitDeviceConnected: true
207+
};
208+
}
209+
210+
return testCase;
211+
}
212+
213+
function execute(options: {
214+
testCases: ITestCase[],
215+
act: (input: IActInput) => Promise<void>
216+
}) {
217+
const { testCases, act } = options;
218+
219+
testCases.forEach(testCase => {
220+
testCase = setDefaults(testCase);
221+
222+
it(`${testCase.name}`, async () => {
223+
const projectFiles = testCase.appFiles ? testCase.appFiles.map(file => path.join(projectDirPath, "app", file)) : null;
224+
const { previewAppLiveSyncService, previewSdkService } = arrange({ projectFiles });
225+
await act.apply(null, [{ previewAppLiveSyncService, previewSdkService, projectFiles, actOptions: testCase.actOptions }]);
226+
await assert(testCase.expectedFiles, testCase.assertOptions);
227+
});
228+
});
229+
}
230+
231+
describe("previewAppLiveSyncService", () => {
232+
describe("initialSync", () => {
233+
afterEach(() => reset());
234+
235+
let testCases: ITestCase[] = [
236+
{
237+
name: "should generate qrcode when no devices are emitted",
238+
actOptions: {
239+
emitDeviceConnected: false
240+
}
241+
},
242+
{
243+
name: "should generate qrcode when devices are emitted"
244+
}
245+
];
246+
247+
testCases = testCases.map(testCase => {
248+
testCase.assertOptions = { checkQrCode: true };
249+
return testCase;
250+
});
251+
252+
execute({ testCases, act: initialSync });
253+
});
254+
255+
describe("syncFiles", () => {
256+
afterEach(() => reset());
257+
258+
const excludeFilesTestCases: ITestCase[] = [
259+
{
260+
name: ".ts files",
261+
appFiles: ["dir1/file.js", "file.ts"],
262+
expectedFiles: ["dir1/file.js"]
263+
},
264+
{
265+
name: ".sass files",
266+
appFiles: ["myDir1/mySubDir/myfile.css", "myDir1/mySubDir/myfile.sass"],
267+
expectedFiles: ["myDir1/mySubDir/myfile.css"]
268+
},
269+
{
270+
name: ".scss files",
271+
appFiles: ["myDir1/mySubDir/myfile1.css", "myDir1/mySubDir/myfile.scss", "my/file.js"],
272+
expectedFiles: ["myDir1/mySubDir/myfile1.css", "my/file.js"]
273+
},
274+
{
275+
name: ".less files",
276+
appFiles: ["myDir1/mySubDir/myfile1.css", "myDir1/mySubDir/myfile.less", "my/file.js"],
277+
expectedFiles: ["myDir1/mySubDir/myfile1.css", "my/file.js"]
278+
},
279+
{
280+
name: ".DS_Store file",
281+
appFiles: ["my/test/file.js", ".DS_Store"],
282+
expectedFiles: ["my/test/file.js"]
283+
}
284+
];
285+
286+
const nativeFilesTestCases: ITestCase[] = [
287+
{
288+
name: "Android manifest is changed",
289+
appFiles: ["App_Resources/Android/src/main/AndroidManifest.xml"],
290+
expectedFiles: []
291+
},
292+
{
293+
name: "Android app.gradle is changed",
294+
appFiles: ["App_Resources/Android/app.gradle"],
295+
expectedFiles: []
296+
},
297+
{
298+
name: "iOS Info.plist is changed",
299+
appFiles: ["App_Resources/iOS/Info.plist"],
300+
expectedFiles: []
301+
},
302+
{
303+
name: "iOS build.xcconfig is changed",
304+
appFiles: ["App_Resources/iOS/build.xcconfig"],
305+
expectedFiles: []
306+
}
307+
];
308+
309+
const noAppFilesTestCases: ITestCase[] = [
310+
{
311+
name: "should transfer correctly default project files",
312+
expectedFiles: defaultProjectFiles
313+
}
314+
];
315+
316+
const testCategories = [
317+
{
318+
name: "should exclude",
319+
testCases: excludeFilesTestCases
320+
},
321+
{
322+
name: "should show warning and not transfer native files when",
323+
testCases: nativeFilesTestCases.map(testCase => {
324+
testCase.assertOptions = { checkWarnings: true };
325+
return testCase;
326+
})
327+
},
328+
{
329+
name: "should handle correctly when no files are provided",
330+
testCases: noAppFilesTestCases
331+
}
332+
];
333+
334+
testCategories.forEach(category => {
335+
describe(`${category.name}`, () => {
336+
const testCases = category.testCases;
337+
execute({ testCases, act: syncFiles });
338+
});
339+
});
340+
});
341+
});

0 commit comments

Comments
 (0)