diff --git a/CHANGELOG.md b/CHANGELOG.md index cdbc0aa85..988d4564f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +## [2.0.8](https://github.com/NativeScript/nx/compare/2.0.7...2.0.8) (2021-12-18) + + +### Features + +* unit testing executor and configuration ([98f0216](https://github.com/NativeScript/nx/commit/98f02167df538d9a1828a88f7116ef534238ebb1)) + + + ## [2.0.7](https://github.com/NativeScript/nx/compare/2.0.6...2.0.7) (2021-11-20) diff --git a/package.json b/package.json index 8d1a570d5..d9911cc5f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "nativescript-nx-plugins", - "version": "2.0.7", + "version": "2.0.8", "license": "MIT", "scripts": { "nx": "nx", diff --git a/packages/nx/README.md b/packages/nx/README.md index f7d022a78..a7a6884f6 100644 --- a/packages/nx/README.md +++ b/packages/nx/README.md @@ -25,6 +25,7 @@ - [Develop on simulators and devices](#develop-on-simulators-and-devices) - [Configuration options](#configuration-options) - [Run with a specific configuration](#run-with-a-specific-configuration) + - [Run tests](#run-tests) - [Create a build](#create-a-build) - [Clean](#clean) - [Create NativeScript library](#create-nativescript-library) @@ -192,7 +193,7 @@ The options follow the [NativeScript command line option flags](https://docs.nat Here's an example app config: -``` +```json "nativescript-mobile": { "projectType": "application", "root": "apps/nativescript-mobile/", @@ -253,6 +254,17 @@ Here's an example app config: } } }, + "test": { + "executor": "@nativescript/nx:test", + "outputs": ["coverage/apps/nativescript-mobile"], + "options": { + "coverage": false + }, + "configurations": { + "android": {}, + "ios": {} + } + }, "clean": { "builder": "@nativescript/nx:build", "options": { @@ -277,6 +289,46 @@ npx nx run :android:prod npx nx run :ios:prod ``` +#### Run tests + +**Android:** + +```sh +npx nx run :test:android +``` + +**iOS:** (Mac only) + +```sh +npx nx run :test:ios +``` + +You can generate coverage reports by using the flag with iOS or Android, for example: + +```sh +npx nx run :test:ios --coverage +``` + +You can also set this option in the config, for example: + +```json +"test": { + "executor": "@nativescript/nx:test", + "outputs": ["coverage/apps/nativescript-mobile"], + "options": { + "coverage": true // can set to always be on for both platforms + }, + "configurations": { + "android": { + "coverage": false // or can override per platform if needed + }, + "ios": { + "coverage": true + } + } +} +``` + #### Create a build Instead of running the app on a simulator or device you can create a build for the purposes of distribution/release. Various release settings will be needed for iOS and Android which can be passed as additional command line arguments. [See more in the NativeScript docs here](https://docs.nativescript.org/releasing.html#overview). Any additional cli flags as stated in the docs can be passed on the end of the `nx build` command that follows. diff --git a/packages/nx/builders.json b/packages/nx/builders.json index 406ddf217..95b7522e6 100644 --- a/packages/nx/builders.json +++ b/packages/nx/builders.json @@ -5,6 +5,11 @@ "implementation": "./src/builders/build/builder", "schema": "./src/builders/build/schema.json", "description": "NativeScript builder" + }, + "test": { + "implementation": "./src/builders/test/builder", + "schema": "./src/builders/test/schema.json", + "description": "Start the NativeScript unit test runner" } } } diff --git a/packages/nx/package.json b/packages/nx/package.json index ba920a5f1..a236525ec 100644 --- a/packages/nx/package.json +++ b/packages/nx/package.json @@ -1,6 +1,6 @@ { "name": "@nativescript/nx", - "version": "2.0.7", + "version": "2.0.8", "description": "NativeScript Plugin for Nx", "repository": { "type": "git", diff --git a/packages/nx/src/builders/test/builder.spec.ts b/packages/nx/src/builders/test/builder.spec.ts new file mode 100644 index 000000000..93bbdcbe5 --- /dev/null +++ b/packages/nx/src/builders/test/builder.spec.ts @@ -0,0 +1,35 @@ +import { Architect } from '@angular-devkit/architect'; +import { TestingArchitectHost } from '@angular-devkit/architect/testing'; +import { schema } from '@angular-devkit/core'; +import { join } from 'path'; +import { TestBuilderSchema } from './schema'; +import runBuilder from './builder'; + +const options: TestBuilderSchema = { + codeCoverage: false, + platform: 'ios', +}; + +xdescribe('NativeScript Test Builder', () => { + const context = { + logger: { + info: (args) => { + console.log(args); + }, + }, + } as any; + let architect: Architect; + let architectHost: TestingArchitectHost; + + beforeEach(async () => { + const registry = new schema.CoreSchemaRegistry(); + registry.addPostTransform(schema.transforms.addUndefinedDefaults); + + architectHost = new TestingArchitectHost('/root', '/root'); + architect = new Architect(architectHost, registry); + + // This will either take a Node package name, or a path to the directory + // for the package.json file. + await architectHost.addBuilderFromPackage(join(__dirname, '../../..')); + }); +}); diff --git a/packages/nx/src/builders/test/builder.ts b/packages/nx/src/builders/test/builder.ts new file mode 100644 index 000000000..6d4cbd6ac --- /dev/null +++ b/packages/nx/src/builders/test/builder.ts @@ -0,0 +1,110 @@ +import { ExecutorContext, convertNxExecutor } from '@nrwl/devkit'; +import * as childProcess from 'child_process'; +import { TestBuilderSchema } from './schema'; + +export function runBuilder(options: TestBuilderSchema, context: ExecutorContext): Promise<{ success: boolean }> { + return new Promise((resolve, reject) => { + const projectConfig = context.workspace.projects[context.projectName]; + // console.log('context.projectName:', context.projectName); + const projectCwd = projectConfig.root; + // console.log('projectCwd:', projectCwd); + // console.log('context.targetName:', context.targetName); + // console.log('context.configurationName:', context.configurationName); + // console.log('context.target.options:', context.target.options); + + let targetConfigName = ''; + if (context.configurationName && context.configurationName !== 'build') { + targetConfigName = context.configurationName; + } + + // determine if any trailing args that need to be added to run/build command + const configTarget = targetConfigName ? `:${targetConfigName}` : ''; + const projectTargetCmd = `${context.projectName}:${context.targetName}${configTarget}`; + const projectTargetCmdIndex = process.argv.findIndex((c) => c === projectTargetCmd); + // const additionalCliFlagArgs = []; + // if (process.argv.length > projectTargetCmdIndex+1) { + // additionalCliFlagArgs.push(...process.argv.slice(projectTargetCmdIndex+1, process.argv.length)); + // // console.log('additionalCliFlagArgs:', additionalCliFlagArgs); + // } + + const throwPlatformError = () => { + throw new Error(`Configuration must exist for 'ios' or 'android' or options.platform should be set for this target.`); + } + + const nsOptions = ['test']; + + const fileReplacements: Array = []; + let configOptions; + if (context.target.configurations) { + configOptions = context.target.configurations[targetConfigName]; + // console.log('configOptions:', configOptions) + + if (configOptions) { + if (['ios', 'android'].includes(targetConfigName)) { + nsOptions.push(targetConfigName); + } else if (options.platform) { + nsOptions.push(options.platform); + } else { + throwPlatformError(); + } + if (configOptions.coverage) { + nsOptions.push('--env.codeCoverage'); + } + if (configOptions.fileReplacements) { + for (const r of configOptions.fileReplacements) { + fileReplacements.push(`${r.replace.replace(projectCwd, './')}:${r.with.replace(projectCwd, './')}`); + } + } + } + } + + const hasPlatform = nsOptions.filter(o => ['ios', 'android'].includes(o)).length > 0; + if (!hasPlatform) { + throwPlatformError(); + } + + if (options.coverage && !nsOptions.includes('--env.codeCoverage')) { + // allow target override for all configurations + nsOptions.push('--env.codeCoverage'); + } + + if (options.device) { + nsOptions.push('--device'); + nsOptions.push(options.device); + } + + if (fileReplacements.length) { + // console.log('fileReplacements:', fileReplacements); + nsOptions.push('--env.replace'); + nsOptions.push(fileReplacements.join(',')); + } + // always add --force (unless explicity set to false) for now since within Nx we use @nativescript/webpack at root only and the {N} cli shows a blocking error if not within the app + if (options?.force !== false) { + nsOptions.push('--force'); + } + + // additional args after -- should be passed through + const argSeparator = process.argv.findIndex((arg) => arg === '--'); + let additionalArgs = []; + if (argSeparator >= 0) { + additionalArgs = process.argv.slice(argSeparator + 1); + } + + console.log('---'); + console.log(`Running NativeScript unit tests within ${projectCwd}`); + console.log(' '); + console.log([`ns`, ...nsOptions, ...additionalArgs].join(' ')); + console.log('---'); + // console.log('command:', [`ns`, ...nsOptions].join(' ')); + const child = childProcess.spawn(/^win/.test(process.platform) ? 'ns.cmd' : 'ns', [...nsOptions, ...additionalArgs], { + cwd: projectCwd, + stdio: 'inherit', + }); + child.on('close', (code) => { + console.log(`Done.`); + resolve({ success: code === 0 }); + }); + }); +} + +export default convertNxExecutor(runBuilder); diff --git a/packages/nx/src/builders/test/schema.d.ts b/packages/nx/src/builders/test/schema.d.ts new file mode 100644 index 000000000..89b754539 --- /dev/null +++ b/packages/nx/src/builders/test/schema.d.ts @@ -0,0 +1,8 @@ +import { JsonObject } from '@angular-devkit/core'; + +export interface TestBuilderSchema extends JsonObject { + platform?: 'ios' | 'android'; + coverage?: boolean; + device?: string; + force?: boolean; +} diff --git a/packages/nx/src/builders/test/schema.json b/packages/nx/src/builders/test/schema.json new file mode 100644 index 000000000..306e836b2 --- /dev/null +++ b/packages/nx/src/builders/test/schema.json @@ -0,0 +1,28 @@ +{ + "$schema": "http://json-schema.org/schema", + "title": "Start the NativeScript unit test runner", + "description": "", + "type": "object", + "properties": { + "platform": { + "type": "string", + "description": "Platform to test." + }, + "coverage": { + "type": "boolean", + "default": false, + "description": "Enable code coverage reports." + }, + "device": { + "type": "string", + "description": "Device identifier to run tests on.", + "alias": "d" + }, + "force": { + "type": "boolean", + "default": true, + "description": "If true, skips the application compatibility checks and forces npm i to ensure all dependencies are installed. Otherwise, the command will check the application compatibility with the current CLI version and could fail requiring ns migrate." + } + }, + "required": [] +}