From aa638e0fbd4ab3a316c7c7484db5c1de1dc84e52 Mon Sep 17 00:00:00 2001 From: Emmanouil Konstantinidis Date: Tue, 5 Jan 2021 16:48:45 +0000 Subject: [PATCH 01/10] feat: Create a wrapper around jest and run after launching the app --- src/cli/run.ts | 11 ++++++++++- src/jest-config.json | 3 +++ tsconfig.json | 5 +++-- 3 files changed, 16 insertions(+), 3 deletions(-) create mode 100644 src/jest-config.json diff --git a/src/cli/run.ts b/src/cli/run.ts index d9313b3e..d947ec59 100644 --- a/src/cli/run.ts +++ b/src/cli/run.ts @@ -54,12 +54,21 @@ export const runAndroid = async (config: Config, logger: Logger) => { export const runHandler = async (args: BuildRunOptions) => { const config = await getConfig(args.config); + const stdio = config.debug ? 'inherit' : 'ignore'; const logger = createLogger(config.debug); const runProject = args.platform === 'ios' ? runIOS : runAndroid; logger.info(`[OWL] Will run the app on ${args.platform}.`); - await runProject(config, logger); + const jestConfigPath = path.join(__dirname, '..', 'jest-config.json'); + const jestCommand = `jest --config=${jestConfigPath} --roots=${process.cwd()}`; + logger.info(`[OWL] Will use the jest config localed at ${jestConfigPath}.`); + logger.info(`[OWL] Will set the jest root to ${process.cwd()}.`); + + await execa.commandSync(jestCommand, { + stdio, + }); + logger.info(`[OWL] Successfully run the app on ${args.platform}.`); }; diff --git a/src/jest-config.json b/src/jest-config.json new file mode 100644 index 00000000..763aabd9 --- /dev/null +++ b/src/jest-config.json @@ -0,0 +1,3 @@ +{ + "testMatch": ["**/?(*.)+(spec|test|owl).[jt]s?(x)"] +} diff --git a/tsconfig.json b/tsconfig.json index 2e4c0bbb..8e8ae798 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -65,8 +65,9 @@ /* Advanced Options */ "skipLibCheck": true, /* Skip type checking of declaration files. */ - "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ + "forceConsistentCasingInFileNames": true, /* Disallow inconsistently-cased references to the same file. */ + "resolveJsonModule": true }, - "include": ["src/**/*"], + "include": ["src/**/*", "src/jest-config.json"], "exclude": ["src/**/*.test.ts"] } From dae5330a1e218cbdb1ee0e381c4fe92479f0efee Mon Sep 17 00:00:00 2001 From: Emmanouil Konstantinidis Date: Tue, 5 Jan 2021 17:59:27 +0000 Subject: [PATCH 02/10] =?UTF-8?q?feat:=20Take=20screenshot=20=F0=9F=93=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- src/cli/run.ts | 3 +++ src/cli/types.ts | 4 +++- src/index.test.ts | 6 ------ src/index.ts | 4 +--- src/take-screenshot.ts | 30 ++++++++++++++++++++++++++++++ tsconfig.json | 2 +- 7 files changed, 39 insertions(+), 12 deletions(-) delete mode 100644 src/index.test.ts create mode 100644 src/take-screenshot.ts diff --git a/package.json b/package.json index c9fc2b83..f83ce9e1 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "react-native-owl", "version": "0.0.1", "description": "Visual regression testing for React Native", - "main": "index.js", + "main": "./lib/index.js", "bin": { "owl": "./lib/cli/index.js" }, diff --git a/src/cli/run.ts b/src/cli/run.ts index d947ec59..815380de 100644 --- a/src/cli/run.ts +++ b/src/cli/run.ts @@ -68,6 +68,9 @@ export const runHandler = async (args: BuildRunOptions) => { await execa.commandSync(jestCommand, { stdio, + env: { + OWL_PLATFORM: args.platform, + }, }); logger.info(`[OWL] Successfully run the app on ${args.platform}.`); diff --git a/src/cli/types.ts b/src/cli/types.ts index d5be46ea..1c75845b 100644 --- a/src/cli/types.ts +++ b/src/cli/types.ts @@ -1,7 +1,9 @@ import { Arguments } from 'yargs'; +export type Platform = 'ios' | 'android'; + export interface BuildRunOptions extends Arguments { - platform: 'ios' | 'android'; + platform: Platform; config: string; } diff --git a/src/index.test.ts b/src/index.test.ts deleted file mode 100644 index 284a5df9..00000000 --- a/src/index.test.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { greetings } from '.'; - -test('greetings', () => { - const result = greetings(); - expect(result).toBe('Hello World'); -}); diff --git a/src/index.ts b/src/index.ts index 46a7fc99..193bf6d1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,3 +1 @@ -export const greetings = (): string => { - return 'Hello World'; -}; +export { takeScreenshot } from './take-screenshot'; diff --git a/src/take-screenshot.ts b/src/take-screenshot.ts new file mode 100644 index 00000000..bdb80b9b --- /dev/null +++ b/src/take-screenshot.ts @@ -0,0 +1,30 @@ +import execa from 'execa'; +import { promises as fs } from 'fs'; +import path from 'path'; + +import { Logger, Platform } from './cli/types'; +import { createLogger } from './logger'; + +const takeScreenshotIOS = async (logger: Logger) => { + const DEFAULT_FILENAME = 'screen.png'; + const DIR_NAME = '.owl'; + const cwd = path.join(process.cwd(), DIR_NAME); + + await fs.mkdir(cwd, { recursive: true }); + + const screenshotCommand = `xcrun simctl io booted screenshot ${DEFAULT_FILENAME}`; + await execa.command(screenshotCommand, { stdio: 'inherit', cwd }); + + logger.info(`[OWL] Screenshot saved to ${cwd}/${DEFAULT_FILENAME}.`); +}; + +const takeScreenshotAndroid = async (logger: Logger) => { + throw new Error('Screenshots for Android, coming soon.'); +}; + +export const takeScreenshot = async (): Promise => { + const platform = process.env.OWL_PLATFORM as Platform; + const logger = createLogger(true /* FIXME - GET DEBUG VALUE FROM CLI */); + const save = platform === 'ios' ? takeScreenshotIOS : takeScreenshotAndroid; + await save(logger); +}; diff --git a/tsconfig.json b/tsconfig.json index 8e8ae798..685c0c34 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -10,7 +10,7 @@ // "allowJs": true, /* Allow javascript files to be compiled. */ // "checkJs": true, /* Report errors in .js files. */ // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ - // "declaration": true, /* Generates corresponding '.d.ts' file. */ + "declaration": true, /* Generates corresponding '.d.ts' file. */ // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ // "sourceMap": true, /* Generates corresponding '.map' file. */ // "outFile": "./", /* Concatenate and emit output to single file. */ From 3e7ce5ae9a8298aff4da56ee30396fae92deee3e Mon Sep 17 00:00:00 2001 From: Emmanouil Konstantinidis Date: Wed, 6 Jan 2021 11:48:52 +0000 Subject: [PATCH 03/10] feat: Take screenshot for both iOS & Android --- src/cli/run.ts | 7 +++++-- src/take-screenshot.ts | 35 +++++++++++++++++++---------------- 2 files changed, 24 insertions(+), 18 deletions(-) diff --git a/src/cli/run.ts b/src/cli/run.ts index 815380de..1fc36263 100644 --- a/src/cli/run.ts +++ b/src/cli/run.ts @@ -13,6 +13,7 @@ export const getIOSBundleIdentifier = (appPath: string): string => { }; export const runIOS = async (config: Config, logger: Logger) => { + const stdio = config.debug ? 'inherit' : 'ignore'; const DEFAULT_BINARY_DIR = '/ios/build/Build/Products/Debug-iphonesimulator'; const cwd = config.ios?.binaryPath ? path.dirname(config.ios?.binaryPath) @@ -26,10 +27,10 @@ export const runIOS = async (config: Config, logger: Logger) => { const simulator = config.ios!.device.replace(/([ /])/g, '\\$1'); const installCommand = `xcrun simctl install ${simulator} ${appFilename}`; - await execa.command(installCommand, { stdio: 'inherit', cwd }); + await execa.command(installCommand, { stdio, cwd }); const launchCommand = `xcrun simctl launch ${simulator} ${bundleId}`; - await execa.command(launchCommand, { stdio: 'inherit' }); + await execa.command(launchCommand, { stdio }); }; export const runAndroid = async (config: Config, logger: Logger) => { @@ -63,6 +64,7 @@ export const runHandler = async (args: BuildRunOptions) => { const jestConfigPath = path.join(__dirname, '..', 'jest-config.json'); const jestCommand = `jest --config=${jestConfigPath} --roots=${process.cwd()}`; + logger.info(`[OWL] Will use the jest config localed at ${jestConfigPath}.`); logger.info(`[OWL] Will set the jest root to ${process.cwd()}.`); @@ -70,6 +72,7 @@ export const runHandler = async (args: BuildRunOptions) => { stdio, env: { OWL_PLATFORM: args.platform, + OWL_DEBUG: String(!!config.debug), }, }); diff --git a/src/take-screenshot.ts b/src/take-screenshot.ts index bdb80b9b..40526761 100644 --- a/src/take-screenshot.ts +++ b/src/take-screenshot.ts @@ -2,29 +2,32 @@ import execa from 'execa'; import { promises as fs } from 'fs'; import path from 'path'; -import { Logger, Platform } from './cli/types'; +import { Platform } from './cli/types'; import { createLogger } from './logger'; -const takeScreenshotIOS = async (logger: Logger) => { +export const takeScreenshot = async (): Promise => { + const platform = process.env.OWL_PLATFORM as Platform; + const debug = process.env.OWL_DEBUG === 'true'; + const stdio = debug ? 'inherit' : 'ignore'; + const logger = createLogger(!!debug); + const DEFAULT_FILENAME = 'screen.png'; const DIR_NAME = '.owl'; - const cwd = path.join(process.cwd(), DIR_NAME); + const cwd = path.join(process.cwd(), DIR_NAME, platform); await fs.mkdir(cwd, { recursive: true }); - const screenshotCommand = `xcrun simctl io booted screenshot ${DEFAULT_FILENAME}`; - await execa.command(screenshotCommand, { stdio: 'inherit', cwd }); - - logger.info(`[OWL] Screenshot saved to ${cwd}/${DEFAULT_FILENAME}.`); -}; + const screenshotCommand = + platform === 'ios' + ? `xcrun simctl io booted screenshot ${DEFAULT_FILENAME}` + : `adb exec-out screencap -p > ${DEFAULT_FILENAME}`; -const takeScreenshotAndroid = async (logger: Logger) => { - throw new Error('Screenshots for Android, coming soon.'); -}; + logger.info(`[OWL] Will run the screenshot command: ${screenshotCommand}.`); + await execa.command(screenshotCommand, { + stdio, + cwd, + shell: platform === 'android', + }); -export const takeScreenshot = async (): Promise => { - const platform = process.env.OWL_PLATFORM as Platform; - const logger = createLogger(true /* FIXME - GET DEBUG VALUE FROM CLI */); - const save = platform === 'ios' ? takeScreenshotIOS : takeScreenshotAndroid; - await save(logger); + logger.info(`[OWL] Screenshot saved to ${cwd}/${DEFAULT_FILENAME}.`); }; From b8cd8a99f2da86c69954b037f7e56c35ec55c447 Mon Sep 17 00:00:00 2001 From: Emmanouil Konstantinidis Date: Wed, 6 Jan 2021 15:20:38 +0000 Subject: [PATCH 04/10] chore: Logger method that always prints (overrides the debug option) --- src/cli/build.ts | 5 ++--- src/cli/run.ts | 7 +++---- src/cli/types.ts | 5 +++++ src/jest-config.json | 1 + src/logger.ts | 5 +++++ 5 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/cli/build.ts b/src/cli/build.ts index 17e2a1a0..e827bc1a 100644 --- a/src/cli/build.ts +++ b/src/cli/build.ts @@ -51,9 +51,8 @@ export const buildHandler = async (args: BuildRunOptions) => { const logger = createLogger(config.debug); const buildProject = args.platform === 'ios' ? buildIOS : buildAndroid; - logger.info( - `[OWL] Will build the app on ${args.platform} platform. Config file: ${args.config}` - ); + logger.print(`[OWL] Building the app on ${args.platform} platform.`); + logger.info(`[OWL] Using the config file ${args.config}.`); await buildProject(config, logger); diff --git a/src/cli/run.ts b/src/cli/run.ts index 1fc36263..4c25a064 100644 --- a/src/cli/run.ts +++ b/src/cli/run.ts @@ -55,11 +55,10 @@ export const runAndroid = async (config: Config, logger: Logger) => { export const runHandler = async (args: BuildRunOptions) => { const config = await getConfig(args.config); - const stdio = config.debug ? 'inherit' : 'ignore'; const logger = createLogger(config.debug); const runProject = args.platform === 'ios' ? runIOS : runAndroid; - logger.info(`[OWL] Will run the app on ${args.platform}.`); + logger.print(`[OWL] Running tests on ${args.platform}.`); await runProject(config, logger); const jestConfigPath = path.join(__dirname, '..', 'jest-config.json'); @@ -69,12 +68,12 @@ export const runHandler = async (args: BuildRunOptions) => { logger.info(`[OWL] Will set the jest root to ${process.cwd()}.`); await execa.commandSync(jestCommand, { - stdio, + stdio: 'inherit', env: { OWL_PLATFORM: args.platform, OWL_DEBUG: String(!!config.debug), }, }); - logger.info(`[OWL] Successfully run the app on ${args.platform}.`); + logger.print(`[OWL] Tests completed on ${args.platform}.`); }; diff --git a/src/cli/types.ts b/src/cli/types.ts index 1c75845b..664bb7b8 100644 --- a/src/cli/types.ts +++ b/src/cli/types.ts @@ -41,7 +41,12 @@ export type Config = { }; export type Logger = { + /** Will only output when the debug flag in the config is on. */ info: (message?: any, ...optionalParams: any[]) => void; + /** Will only output when the debug flag in the config is on. */ warn: (message?: any, ...optionalParams: any[]) => void; + /** Will only output when the debug flag in the config is on. */ error: (message?: any, ...optionalParams: any[]) => void; + /** Will always print output to the terminal - not depending on the debug flag. */ + print: (message?: any, ...optionalParams: any[]) => void; }; diff --git a/src/jest-config.json b/src/jest-config.json index 763aabd9..ef8a2432 100644 --- a/src/jest-config.json +++ b/src/jest-config.json @@ -1,3 +1,4 @@ { + "verbose": true, "testMatch": ["**/?(*.)+(spec|test|owl).[jt]s?(x)"] } diff --git a/src/logger.ts b/src/logger.ts index 5335217d..945063c8 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -19,9 +19,14 @@ export const createLogger = (isEnabled: boolean = false): Logger => { } }; + const print = (message?: any, ...optionalParams: any[]) => { + console.log(message, ...optionalParams); + }; + return { info, warn, error, + print, }; }; From 84ac75289a30554353e2ac52c0707fb14cdbf277 Mon Sep 17 00:00:00 2001 From: Emmanouil Konstantinidis Date: Wed, 6 Jan 2021 16:19:39 +0000 Subject: [PATCH 05/10] chore: Fix & write more tests --- src/cli/build.test.ts | 4 ++ src/cli/run.test.ts | 61 +++++++++++++++++++++--------- src/logger.test.ts | 88 ++++++++++++++++++++++++++----------------- 3 files changed, 101 insertions(+), 52 deletions(-) diff --git a/src/cli/build.test.ts b/src/cli/build.test.ts index abd398a3..e58e6ddb 100644 --- a/src/cli/build.test.ts +++ b/src/cli/build.test.ts @@ -141,6 +141,10 @@ describe('build.ts', () => { }, }; + beforeAll(() => { + jest.spyOn(global.console, 'log').mockImplementation(); + }); + it('builds an iOS project', async () => { jest.spyOn(configHelpers, 'getConfig').mockResolvedValueOnce(config); const call = async () => buildHandler(args); diff --git a/src/cli/run.test.ts b/src/cli/run.test.ts index af8a17bb..8b29c329 100644 --- a/src/cli/run.test.ts +++ b/src/cli/run.test.ts @@ -8,17 +8,13 @@ import * as run from './run'; describe('run.ts', () => { const logger = createLogger(); - const execMock = jest.spyOn(execa, 'command').mockImplementation(); const bundleIdIOS = 'org.reactjs.native.example.RNDemo'; - - beforeEach(() => { - execMock.mockReset(); - }); + const commandSyncMock = jest.spyOn(execa, 'commandSync'); describe('getIOSBundleIdentifier', () => { it('should return the bundle identifier', () => { - jest.spyOn(execa, 'commandSync').mockReturnValueOnce({ + commandSyncMock.mockReturnValueOnce({ stdout: bundleIdIOS, } as ExecaSyncReturnValue); @@ -59,13 +55,13 @@ describe('run.ts', () => { expect(execMock).toHaveBeenNthCalledWith( 1, 'xcrun simctl install iPhone\\ Simulator RNDemo.app', - { cwd, stdio: 'inherit' } + { cwd, stdio: 'ignore' } ); expect(execMock).toHaveBeenNthCalledWith( 2, `xcrun simctl launch iPhone\\ Simulator ${bundleIdIOS}`, - { stdio: 'inherit' } + { stdio: 'ignore' } ); expect(run.getIOSBundleIdentifier).toHaveBeenCalledWith(appPath); @@ -92,13 +88,13 @@ describe('run.ts', () => { expect(execMock).toHaveBeenNthCalledWith( 1, 'xcrun simctl install iPhone\\ Simulator RNDemo.app', - { cwd, stdio: 'inherit' } + { cwd, stdio: 'ignore' } ); expect(execMock).toHaveBeenNthCalledWith( 2, `xcrun simctl launch iPhone\\ Simulator ${bundleIdIOS}`, - { stdio: 'inherit' } + { stdio: 'ignore' } ); expect(run.getIOSBundleIdentifier).toHaveBeenCalledWith(binaryPath); @@ -186,20 +182,49 @@ describe('run.ts', () => { }, }; + const commandSyncMock = jest.spyOn(execa, 'commandSync'); + + beforeAll(() => { + jest.spyOn(global.console, 'log').mockImplementation(); + }); + + beforeEach(() => { + commandSyncMock.mockReset(); + }); + it('runs an iOS project', async () => { jest.spyOn(configHelpers, 'getConfig').mockResolvedValueOnce(config); - jest.spyOn(run, 'runIOS').mockImplementation(); - const call = async () => run.runHandler(args); - await expect(call()).resolves.not.toThrow(); - await expect(run.runIOS).toHaveBeenCalled(); + const mockRunIOS = jest.spyOn(run, 'runIOS').mockResolvedValueOnce(); + + await run.runHandler(args); + + await expect(mockRunIOS).toHaveBeenCalled(); + await expect(commandSyncMock).toHaveBeenCalledTimes(1); + await expect( + commandSyncMock + ).toHaveBeenCalledWith( + 'jest --config=/Users/manos/Projects/react-native-owl/src/jest-config.json --roots=/Users/manos/Projects/react-native-owl', + { env: { OWL_DEBUG: 'false', OWL_PLATFORM: 'ios' }, stdio: 'inherit' } + ); }); it('runs an Android project', async () => { jest.spyOn(configHelpers, 'getConfig').mockResolvedValueOnce(config); - jest.spyOn(run, 'runAndroid').mockImplementation(); - const call = async () => run.runHandler({ ...args, platform: 'android' }); - await expect(call()).resolves.not.toThrow(); - await expect(run.runAndroid).toHaveBeenCalled(); + const mockRunAndroid = jest + .spyOn(run, 'runAndroid') + .mockResolvedValueOnce(); + + await run.runHandler({ ...args, platform: 'android' }); + + await expect(mockRunAndroid).toHaveBeenCalled(); + await expect(commandSyncMock).toHaveBeenCalledTimes(1); + await expect(commandSyncMock).toHaveBeenCalledWith( + 'jest --config=/Users/manos/Projects/react-native-owl/src/jest-config.json --roots=/Users/manos/Projects/react-native-owl', + { + env: { OWL_DEBUG: 'false', OWL_PLATFORM: 'android' }, + stdio: 'inherit', + } + ); }); }); }); diff --git a/src/logger.test.ts b/src/logger.test.ts index 1c2948b0..9f495447 100644 --- a/src/logger.test.ts +++ b/src/logger.test.ts @@ -3,51 +3,71 @@ import { createLogger } from './logger'; describe('logger.ts', () => { const logMessage = 'Hello World'; - beforeAll(() => { - jest.spyOn(global.console, 'info'); - jest.spyOn(global.console, 'warn'); - jest.spyOn(global.console, 'error'); - }); + const logInfoMock = jest.spyOn(global.console, 'info'); + const logWarnMock = jest.spyOn(global.console, 'warn'); + const logErrorMock = jest.spyOn(global.console, 'error'); + const logPrintMock = jest.spyOn(global.console, 'log'); beforeEach(() => { - (console.info as any).mockReset(); - (console.warn as any).mockReset(); - (console.error as any).mockReset(); + logInfoMock.mockReset(); + logWarnMock.mockReset(); + logErrorMock.mockReset(); + logPrintMock.mockReset(); }); - it('should log a message - info', () => { - const logger = createLogger(true); - logger.info(logMessage); - expect(console.info).toHaveBeenCalledWith(logMessage); - }); + describe('info', () => { + it('should log a message', () => { + const logger = createLogger(true); + logger.info(logMessage); + expect(logInfoMock).toHaveBeenCalledWith(logMessage); + }); - it('should not log a message when disabled - info', () => { - const logger = createLogger(false); - logger.info(logMessage); - expect(console.info).not.toHaveBeenCalled(); + it('should not log a message when disabled', () => { + const logger = createLogger(false); + logger.info(logMessage); + expect(logInfoMock).not.toHaveBeenCalled(); + }); }); - it('should log a message - warn', () => { - const logger = createLogger(true); - logger.warn(logMessage); - expect(console.warn).toHaveBeenCalledWith(logMessage); - }); + describe('warn', () => { + it('should log a message', () => { + const logger = createLogger(true); + logger.warn(logMessage); + expect(logWarnMock).toHaveBeenCalledWith(logMessage); + }); - it('should not log a message when disabled - warn', () => { - const logger = createLogger(false); - logger.warn(logMessage); - expect(console.warn).not.toHaveBeenCalled(); + it('should not log a message when disabled', () => { + const logger = createLogger(false); + logger.warn(logMessage); + expect(logWarnMock).not.toHaveBeenCalled(); + }); }); - it('should log a message - error', () => { - const logger = createLogger(true); - logger.error(logMessage); - expect(console.error).toHaveBeenCalledWith(logMessage); + describe('error', () => { + it('should log a message', () => { + const logger = createLogger(true); + logger.error(logMessage); + expect(logErrorMock).toHaveBeenCalledWith(logMessage); + }); + + it('should not log a message when disabled', () => { + const logger = createLogger(false); + logger.error(logMessage); + expect(logErrorMock).not.toHaveBeenCalled(); + }); }); - it('should not log a message when disabled - error', () => { - const logger = createLogger(false); - logger.error(logMessage); - expect(console.error).not.toHaveBeenCalled(); + describe('print', () => { + it('should log a message', () => { + const logger = createLogger(true); + logger.print(logMessage); + expect(logPrintMock).toHaveBeenCalledWith(logMessage); + }); + + it('should still log a message when disabled', () => { + const logger = createLogger(false); + logger.print(logMessage); + expect(logPrintMock).toHaveBeenCalledWith(logMessage); + }); }); }); From f52f63e676f797e6c41cd44dcd9fc7e2cc297dc6 Mon Sep 17 00:00:00 2001 From: Emmanouil Konstantinidis Date: Wed, 6 Jan 2021 16:28:51 +0000 Subject: [PATCH 06/10] chore: Remove hardcoded path from tests --- src/cli/run.test.ts | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/src/cli/run.test.ts b/src/cli/run.test.ts index 8b29c329..3d1e56ee 100644 --- a/src/cli/run.test.ts +++ b/src/cli/run.test.ts @@ -182,6 +182,12 @@ describe('run.ts', () => { }, }; + const expectedJestCommand = `jest --config=${path.join( + process.cwd(), + 'src', + 'jest-config.json' + )} --roots=${path.join(process.cwd())}`; + const commandSyncMock = jest.spyOn(execa, 'commandSync'); beforeAll(() => { @@ -200,12 +206,10 @@ describe('run.ts', () => { await expect(mockRunIOS).toHaveBeenCalled(); await expect(commandSyncMock).toHaveBeenCalledTimes(1); - await expect( - commandSyncMock - ).toHaveBeenCalledWith( - 'jest --config=/Users/manos/Projects/react-native-owl/src/jest-config.json --roots=/Users/manos/Projects/react-native-owl', - { env: { OWL_DEBUG: 'false', OWL_PLATFORM: 'ios' }, stdio: 'inherit' } - ); + await expect(commandSyncMock).toHaveBeenCalledWith(expectedJestCommand, { + env: { OWL_DEBUG: 'false', OWL_PLATFORM: 'ios' }, + stdio: 'inherit', + }); }); it('runs an Android project', async () => { @@ -218,13 +222,10 @@ describe('run.ts', () => { await expect(mockRunAndroid).toHaveBeenCalled(); await expect(commandSyncMock).toHaveBeenCalledTimes(1); - await expect(commandSyncMock).toHaveBeenCalledWith( - 'jest --config=/Users/manos/Projects/react-native-owl/src/jest-config.json --roots=/Users/manos/Projects/react-native-owl', - { - env: { OWL_DEBUG: 'false', OWL_PLATFORM: 'android' }, - stdio: 'inherit', - } - ); + await expect(commandSyncMock).toHaveBeenCalledWith(expectedJestCommand, { + env: { OWL_DEBUG: 'false', OWL_PLATFORM: 'android' }, + stdio: 'inherit', + }); }); }); }); From a50061b66c90f6d20df64830fb87ec8e4b0ec7e7 Mon Sep 17 00:00:00 2001 From: Emmanouil Konstantinidis Date: Wed, 6 Jan 2021 16:32:48 +0000 Subject: [PATCH 07/10] chore: Mock logger helpers instead of console in tests(allows console.logs for debugging) --- src/cli/build.test.ts | 6 +++--- src/cli/run.test.ts | 6 +++--- src/logger.ts | 8 ++++---- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/cli/build.test.ts b/src/cli/build.test.ts index e58e6ddb..af3f2d42 100644 --- a/src/cli/build.test.ts +++ b/src/cli/build.test.ts @@ -3,11 +3,11 @@ import execa from 'execa'; import { buildAndroid, buildHandler, buildIOS } from './build'; import { BuildRunOptions, Config } from './types'; -import { createLogger } from '../logger'; import * as configHelpers from './config'; +import * as loggerHelpers from '../logger'; describe('build.ts', () => { - const logger = createLogger(); + const logger = loggerHelpers.createLogger(); const execMock = jest.spyOn(execa, 'command').mockImplementation(); beforeEach(() => { @@ -142,7 +142,7 @@ describe('build.ts', () => { }; beforeAll(() => { - jest.spyOn(global.console, 'log').mockImplementation(); + jest.spyOn(loggerHelpers, 'print').mockImplementation(); }); it('builds an iOS project', async () => { diff --git a/src/cli/run.test.ts b/src/cli/run.test.ts index 3d1e56ee..8d90ce9c 100644 --- a/src/cli/run.test.ts +++ b/src/cli/run.test.ts @@ -2,12 +2,12 @@ import path from 'path'; import execa, { ExecaSyncReturnValue } from 'execa'; import { BuildRunOptions, Config } from './types'; -import { createLogger } from '../logger'; +import * as loggerHelpers from '../logger'; import * as configHelpers from './config'; import * as run from './run'; describe('run.ts', () => { - const logger = createLogger(); + const logger = loggerHelpers.createLogger(); const bundleIdIOS = 'org.reactjs.native.example.RNDemo'; const commandSyncMock = jest.spyOn(execa, 'commandSync'); @@ -191,7 +191,7 @@ describe('run.ts', () => { const commandSyncMock = jest.spyOn(execa, 'commandSync'); beforeAll(() => { - jest.spyOn(global.console, 'log').mockImplementation(); + jest.spyOn(loggerHelpers, 'print').mockImplementation(); }); beforeEach(() => { diff --git a/src/logger.ts b/src/logger.ts index 945063c8..036512db 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -1,5 +1,9 @@ import { Logger } from './cli/types'; +export const print = (message?: any, ...optionalParams: any[]) => { + console.log(message, ...optionalParams); +}; + export const createLogger = (isEnabled: boolean = false): Logger => { const info = (message?: any, ...optionalParams: any[]) => { if (isEnabled) { @@ -19,10 +23,6 @@ export const createLogger = (isEnabled: boolean = false): Logger => { } }; - const print = (message?: any, ...optionalParams: any[]) => { - console.log(message, ...optionalParams); - }; - return { info, warn, From 8d6a215b339b8e778d496b543016abb6b2deee86 Mon Sep 17 00:00:00 2001 From: Emmanouil Konstantinidis Date: Wed, 6 Jan 2021 17:14:08 +0000 Subject: [PATCH 08/10] chore: Convert logger to class --- src/cli/build.test.ts | 8 +++----- src/cli/build.ts | 6 +++--- src/cli/run.test.ts | 9 +++------ src/cli/run.ts | 6 +++--- src/cli/types.ts | 11 ----------- src/logger.test.ts | 18 +++++++++--------- src/logger.ts | 43 +++++++++++++++++++++--------------------- src/take-screenshot.ts | 4 ++-- 8 files changed, 45 insertions(+), 60 deletions(-) diff --git a/src/cli/build.test.ts b/src/cli/build.test.ts index af3f2d42..867d7c35 100644 --- a/src/cli/build.test.ts +++ b/src/cli/build.test.ts @@ -3,11 +3,11 @@ import execa from 'execa'; import { buildAndroid, buildHandler, buildIOS } from './build'; import { BuildRunOptions, Config } from './types'; +import { Logger } from '../logger'; import * as configHelpers from './config'; -import * as loggerHelpers from '../logger'; describe('build.ts', () => { - const logger = loggerHelpers.createLogger(); + const logger = new Logger(); const execMock = jest.spyOn(execa, 'command').mockImplementation(); beforeEach(() => { @@ -141,9 +141,7 @@ describe('build.ts', () => { }, }; - beforeAll(() => { - jest.spyOn(loggerHelpers, 'print').mockImplementation(); - }); + jest.spyOn(Logger.prototype, 'print').mockImplementation(); it('builds an iOS project', async () => { jest.spyOn(configHelpers, 'getConfig').mockResolvedValueOnce(config); diff --git a/src/cli/build.ts b/src/cli/build.ts index e827bc1a..fa846a4d 100644 --- a/src/cli/build.ts +++ b/src/cli/build.ts @@ -1,8 +1,8 @@ import path from 'path'; import execa from 'execa'; -import { BuildRunOptions, Config, Logger } from './types'; -import { createLogger } from '../logger'; +import { BuildRunOptions, Config } from './types'; +import { Logger } from '../logger'; import { getConfig } from './config'; export const buildIOS = async ( @@ -48,7 +48,7 @@ export const buildAndroid = async ( export const buildHandler = async (args: BuildRunOptions) => { const config = await getConfig(args.config); - const logger = createLogger(config.debug); + const logger = new Logger(config.debug); const buildProject = args.platform === 'ios' ? buildIOS : buildAndroid; logger.print(`[OWL] Building the app on ${args.platform} platform.`); diff --git a/src/cli/run.test.ts b/src/cli/run.test.ts index 8d90ce9c..9438b27f 100644 --- a/src/cli/run.test.ts +++ b/src/cli/run.test.ts @@ -2,13 +2,12 @@ import path from 'path'; import execa, { ExecaSyncReturnValue } from 'execa'; import { BuildRunOptions, Config } from './types'; -import * as loggerHelpers from '../logger'; +import { Logger } from '../logger'; import * as configHelpers from './config'; import * as run from './run'; describe('run.ts', () => { - const logger = loggerHelpers.createLogger(); - + const logger = new Logger(); const bundleIdIOS = 'org.reactjs.native.example.RNDemo'; const commandSyncMock = jest.spyOn(execa, 'commandSync'); @@ -190,9 +189,7 @@ describe('run.ts', () => { const commandSyncMock = jest.spyOn(execa, 'commandSync'); - beforeAll(() => { - jest.spyOn(loggerHelpers, 'print').mockImplementation(); - }); + jest.spyOn(Logger.prototype, 'print').mockImplementation(); beforeEach(() => { commandSyncMock.mockReset(); diff --git a/src/cli/run.ts b/src/cli/run.ts index 4c25a064..aef28929 100644 --- a/src/cli/run.ts +++ b/src/cli/run.ts @@ -1,8 +1,8 @@ import path from 'path'; import execa from 'execa'; -import { BuildRunOptions, Config, Logger } from './types'; -import { createLogger } from '../logger'; +import { BuildRunOptions, Config } from './types'; +import { Logger } from '../logger'; import { getConfig } from './config'; export const getIOSBundleIdentifier = (appPath: string): string => { @@ -55,7 +55,7 @@ export const runAndroid = async (config: Config, logger: Logger) => { export const runHandler = async (args: BuildRunOptions) => { const config = await getConfig(args.config); - const logger = createLogger(config.debug); + const logger = new Logger(config.debug); const runProject = args.platform === 'ios' ? runIOS : runAndroid; logger.print(`[OWL] Running tests on ${args.platform}.`); diff --git a/src/cli/types.ts b/src/cli/types.ts index 664bb7b8..3eac219a 100644 --- a/src/cli/types.ts +++ b/src/cli/types.ts @@ -39,14 +39,3 @@ export type Config = { /** Prevents the CLI/library from printing any logs/output. */ debug?: boolean; }; - -export type Logger = { - /** Will only output when the debug flag in the config is on. */ - info: (message?: any, ...optionalParams: any[]) => void; - /** Will only output when the debug flag in the config is on. */ - warn: (message?: any, ...optionalParams: any[]) => void; - /** Will only output when the debug flag in the config is on. */ - error: (message?: any, ...optionalParams: any[]) => void; - /** Will always print output to the terminal - not depending on the debug flag. */ - print: (message?: any, ...optionalParams: any[]) => void; -}; diff --git a/src/logger.test.ts b/src/logger.test.ts index 9f495447..47853e03 100644 --- a/src/logger.test.ts +++ b/src/logger.test.ts @@ -1,4 +1,4 @@ -import { createLogger } from './logger'; +import { Logger } from './logger'; describe('logger.ts', () => { const logMessage = 'Hello World'; @@ -17,13 +17,13 @@ describe('logger.ts', () => { describe('info', () => { it('should log a message', () => { - const logger = createLogger(true); + const logger = new Logger(true); logger.info(logMessage); expect(logInfoMock).toHaveBeenCalledWith(logMessage); }); it('should not log a message when disabled', () => { - const logger = createLogger(false); + const logger = new Logger(false); logger.info(logMessage); expect(logInfoMock).not.toHaveBeenCalled(); }); @@ -31,13 +31,13 @@ describe('logger.ts', () => { describe('warn', () => { it('should log a message', () => { - const logger = createLogger(true); + const logger = new Logger(true); logger.warn(logMessage); expect(logWarnMock).toHaveBeenCalledWith(logMessage); }); it('should not log a message when disabled', () => { - const logger = createLogger(false); + const logger = new Logger(false); logger.warn(logMessage); expect(logWarnMock).not.toHaveBeenCalled(); }); @@ -45,13 +45,13 @@ describe('logger.ts', () => { describe('error', () => { it('should log a message', () => { - const logger = createLogger(true); + const logger = new Logger(true); logger.error(logMessage); expect(logErrorMock).toHaveBeenCalledWith(logMessage); }); it('should not log a message when disabled', () => { - const logger = createLogger(false); + const logger = new Logger(false); logger.error(logMessage); expect(logErrorMock).not.toHaveBeenCalled(); }); @@ -59,13 +59,13 @@ describe('logger.ts', () => { describe('print', () => { it('should log a message', () => { - const logger = createLogger(true); + const logger = new Logger(true); logger.print(logMessage); expect(logPrintMock).toHaveBeenCalledWith(logMessage); }); it('should still log a message when disabled', () => { - const logger = createLogger(false); + const logger = new Logger(false); logger.print(logMessage); expect(logPrintMock).toHaveBeenCalledWith(logMessage); }); diff --git a/src/logger.ts b/src/logger.ts index 036512db..9df66c00 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -1,32 +1,33 @@ -import { Logger } from './cli/types'; +export class Logger { + isEnabled: boolean; -export const print = (message?: any, ...optionalParams: any[]) => { - console.log(message, ...optionalParams); -}; + constructor(isEnabled: boolean = false) { + this.isEnabled = isEnabled; + } -export const createLogger = (isEnabled: boolean = false): Logger => { - const info = (message?: any, ...optionalParams: any[]) => { - if (isEnabled) { + /** Will only output when the debug flag in the config is on. */ + info(message?: any, ...optionalParams: any[]) { + if (this.isEnabled) { console.info(message, ...optionalParams); } - }; + } - const warn = (message?: any, ...optionalParams: any[]) => { - if (isEnabled) { + /** Will only output when the debug flag in the config is on. */ + warn(message?: any, ...optionalParams: any[]) { + if (this.isEnabled) { console.warn(message, ...optionalParams); } - }; + } - const error = (message?: any, ...optionalParams: any[]) => { - if (isEnabled) { + /** Will only output when the debug flag in the config is on. */ + error(message?: any, ...optionalParams: any[]) { + if (this.isEnabled) { console.error(message, ...optionalParams); } - }; + } - return { - info, - warn, - error, - print, - }; -}; + /** Will always print output to the terminal - not depending on the debug flag. */ + print(message?: any, ...optionalParams: any[]) { + console.log(message, ...optionalParams); + } +} diff --git a/src/take-screenshot.ts b/src/take-screenshot.ts index 40526761..0a009b93 100644 --- a/src/take-screenshot.ts +++ b/src/take-screenshot.ts @@ -3,13 +3,13 @@ import { promises as fs } from 'fs'; import path from 'path'; import { Platform } from './cli/types'; -import { createLogger } from './logger'; +import { Logger } from './logger'; export const takeScreenshot = async (): Promise => { const platform = process.env.OWL_PLATFORM as Platform; const debug = process.env.OWL_DEBUG === 'true'; const stdio = debug ? 'inherit' : 'ignore'; - const logger = createLogger(!!debug); + const logger = new Logger(!!debug); const DEFAULT_FILENAME = 'screen.png'; const DIR_NAME = '.owl'; From 61aa1a717cad9db9f790d2d65a202f3e644d756f Mon Sep 17 00:00:00 2001 From: Emmanouil Konstantinidis Date: Wed, 6 Jan 2021 17:23:22 +0000 Subject: [PATCH 09/10] chore: Tests for taking a screenshot --- src/take-screenshot.test.ts | 57 +++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 src/take-screenshot.test.ts diff --git a/src/take-screenshot.test.ts b/src/take-screenshot.test.ts new file mode 100644 index 00000000..14ef1d78 --- /dev/null +++ b/src/take-screenshot.test.ts @@ -0,0 +1,57 @@ +import execa from 'execa'; +import path from 'path'; + +import { takeScreenshot } from './take-screenshot'; + +describe('take-screenshot.ts', () => { + const commandMock = jest.spyOn(execa, 'command'); + + beforeAll(() => { + delete process.env.OWL_PLATFORM; + delete process.env.OWL_DEBUG; + }); + + beforeEach(() => { + commandMock.mockReset(); + }); + + describe('iOS', () => { + beforeAll(() => { + process.env.OWL_PLATFORM = 'ios'; + process.env.OWL_DEBUG = 'false'; + }); + + it('should take a screenshot', async () => { + await takeScreenshot(); + + expect(commandMock).toHaveBeenCalledWith( + 'xcrun simctl io booted screenshot screen.png', + { + cwd: path.join(process.cwd(), '.owl', 'ios'), + shell: false, + stdio: 'ignore', + } + ); + }); + }); + + describe('Android', () => { + beforeAll(() => { + process.env.OWL_PLATFORM = 'android'; + process.env.OWL_DEBUG = 'false'; + }); + + it('should take a screenshot', async () => { + await takeScreenshot(); + + expect(commandMock).toHaveBeenCalledWith( + 'adb exec-out screencap -p > screen.png', + { + cwd: path.join(process.cwd(), '.owl', 'android'), + shell: true, + stdio: 'ignore', + } + ); + }); + }); +}); From e793d70acd1c734679441748ef48f0206a811bff Mon Sep 17 00:00:00 2001 From: Emmanouil Konstantinidis Date: Wed, 6 Jan 2021 17:45:40 +0000 Subject: [PATCH 10/10] chore: Stricter filename match for .owl files --- src/jest-config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/jest-config.json b/src/jest-config.json index ef8a2432..69afa9b0 100644 --- a/src/jest-config.json +++ b/src/jest-config.json @@ -1,4 +1,4 @@ { "verbose": true, - "testMatch": ["**/?(*.)+(spec|test|owl).[jt]s?(x)"] + "testMatch": ["**/?(*.)+(owl).[jt]s?(x)"] }