Skip to content
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
},
Expand Down
6 changes: 4 additions & 2 deletions src/cli/build.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ import execa from 'execa';

import { buildAndroid, buildHandler, buildIOS } from './build';
import { BuildRunOptions, Config } from './types';
import { createLogger } from '../logger';
import { Logger } from '../logger';
import * as configHelpers from './config';

describe('build.ts', () => {
const logger = createLogger();
const logger = new Logger();
const execMock = jest.spyOn(execa, 'command').mockImplementation();

beforeEach(() => {
Expand Down Expand Up @@ -141,6 +141,8 @@ describe('build.ts', () => {
},
};

jest.spyOn(Logger.prototype, 'print').mockImplementation();

it('builds an iOS project', async () => {
jest.spyOn(configHelpers, 'getConfig').mockResolvedValueOnce(config);
const call = async () => buildHandler(args);
Expand Down
11 changes: 5 additions & 6 deletions src/cli/build.ts
Original file line number Diff line number Diff line change
@@ -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 (
Expand Down Expand Up @@ -48,12 +48,11 @@ 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.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);

Expand Down
65 changes: 44 additions & 21 deletions src/cli/run.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,18 @@ import path from 'path';
import execa, { ExecaSyncReturnValue } from 'execa';

import { BuildRunOptions, Config } from './types';
import { createLogger } from '../logger';
import { Logger } from '../logger';
import * as configHelpers from './config';
import * as run from './run';

describe('run.ts', () => {
const logger = createLogger();
const execMock = jest.spyOn(execa, 'command').mockImplementation();

const logger = new Logger();
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<any>);

Expand Down Expand Up @@ -59,13 +54,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);
Expand All @@ -92,13 +87,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);
Expand Down Expand Up @@ -186,20 +181,48 @@ 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');

jest.spyOn(Logger.prototype, 'print').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(expectedJestCommand, {
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(expectedJestCommand, {
env: { OWL_DEBUG: 'false', OWL_PLATFORM: 'android' },
stdio: 'inherit',
});
});
});
});
30 changes: 22 additions & 8 deletions src/cli/run.ts
Original file line number Diff line number Diff line change
@@ -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 => {
Expand All @@ -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)
Expand All @@ -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) => {
Expand All @@ -54,12 +55,25 @@ 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.info(`[OWL] Will run the app on ${args.platform}.`);

logger.print(`[OWL] Running tests on ${args.platform}.`);
await runProject(config, logger);

logger.info(`[OWL] Successfully run the app on ${args.platform}.`);
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: 'inherit',
env: {
OWL_PLATFORM: args.platform,
OWL_DEBUG: String(!!config.debug),
},
});

logger.print(`[OWL] Tests completed on ${args.platform}.`);
};
10 changes: 3 additions & 7 deletions src/cli/types.ts
Original file line number Diff line number Diff line change
@@ -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;
}

Expand Down Expand Up @@ -37,9 +39,3 @@ export type Config = {
/** Prevents the CLI/library from printing any logs/output. */
debug?: boolean;
};

export type Logger = {
info: (message?: any, ...optionalParams: any[]) => void;
warn: (message?: any, ...optionalParams: any[]) => void;
error: (message?: any, ...optionalParams: any[]) => void;
};
6 changes: 0 additions & 6 deletions src/index.test.ts

This file was deleted.

4 changes: 1 addition & 3 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1 @@
export const greetings = (): string => {
return 'Hello World';
};
export { takeScreenshot } from './take-screenshot';
4 changes: 4 additions & 0 deletions src/jest-config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"verbose": true,
"testMatch": ["**/?(*.)+(owl).[jt]s?(x)"]
}
90 changes: 55 additions & 35 deletions src/logger.test.ts
Original file line number Diff line number Diff line change
@@ -1,53 +1,73 @@
import { createLogger } from './logger';
import { Logger } 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 = new Logger(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 = new Logger(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 = new Logger(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 = new Logger(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 = new Logger(true);
logger.error(logMessage);
expect(logErrorMock).toHaveBeenCalledWith(logMessage);
});

it('should not log a message when disabled', () => {
const logger = new Logger(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 = new Logger(true);
logger.print(logMessage);
expect(logPrintMock).toHaveBeenCalledWith(logMessage);
});

it('should still log a message when disabled', () => {
const logger = new Logger(false);
logger.print(logMessage);
expect(logPrintMock).toHaveBeenCalledWith(logMessage);
});
});
});
Loading