diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b48cb0b8..8785a071 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -116,6 +116,7 @@ The scope must be one of the following: * ionic * nativescript * nest +* helpers #### Subject diff --git a/src/app.electron/index.ts b/src/app.electron/index.ts index 7b51a98e..6f892961 100644 --- a/src/app.electron/index.ts +++ b/src/app.electron/index.ts @@ -13,11 +13,13 @@ import { noop } from '@angular-devkit/schematics'; import { Schema as ApplicationOptions } from './schema'; -import { prerun, getPrefix, getNpmScope, stringUtils, addRootDeps, updateAngularProjects, updateNxProjects, formatFiles, getJsonFromFile, updatePackageScripts, addPostinstallers, applyAppNamingConvention, getGroupByName, getAppName } from '../utils'; +import { prerun, getPrefix, getNpmScope, stringUtils, addRootDeps, updateAngularProjects, updateNxProjects, formatFiles, getJsonFromFile, updatePackageScripts, addPostinstallers, applyAppNamingConvention, getGroupByName, getAppName, missingNameArgument } from '../utils'; export default function (options: ApplicationOptions) { if (!options.name) { - throw new SchematicsException(`Missing name argument. Provide a name for your Electron app. Example: ng g app.electron sample`); + throw new SchematicsException( + missingNameArgument('Provide a name for your Electron app.', 'ng g app.electron sample') + ); } if (!options.target) { throw new SchematicsException(`Missing target argument. Provide the name of the web app in your workspace to use inside the electron app. ie, web-myapp`); diff --git a/src/app.ionic/index.ts b/src/app.ionic/index.ts index a677af92..5eae902a 100644 --- a/src/app.ionic/index.ts +++ b/src/app.ionic/index.ts @@ -13,12 +13,14 @@ import { schematic, noop, } from '@angular-devkit/schematics'; -import { stringUtils, prerun, getNpmScope, getPrefix, addRootDeps, updatePackageScripts, updateAngularProjects, updateNxProjects, formatFiles, applyAppNamingConvention, getAppName } from '../utils'; +import { stringUtils, prerun, getNpmScope, getPrefix, addRootDeps, updatePackageScripts, updateAngularProjects, updateNxProjects, formatFiles, applyAppNamingConvention, getAppName, missingNameArgument } from '../utils'; import { Schema as ApplicationOptions } from './schema'; export default function (options: ApplicationOptions) { if (!options.name) { - throw new SchematicsException(`Missing name argument. Provide a name for your Ionic app. Example: ng g app.ionic sample`); + throw new SchematicsException( + missingNameArgument('Provide a name for your Ionic app.', 'ng g app.ionic sample') + ); } return chain([ diff --git a/src/app.nativescript/index.ts b/src/app.nativescript/index.ts index 282ac2d4..dd32723f 100644 --- a/src/app.nativescript/index.ts +++ b/src/app.nativescript/index.ts @@ -13,12 +13,14 @@ import { schematic, noop, } from '@angular-devkit/schematics'; -import { stringUtils, prerun, getNpmScope, getPrefix, addRootDeps, updatePackageScripts, updateAngularProjects, updateNxProjects, applyAppNamingConvention, getGroupByName, getAppName } from '../utils'; +import { stringUtils, prerun, getNpmScope, getPrefix, addRootDeps, updatePackageScripts, updateAngularProjects, updateNxProjects, applyAppNamingConvention, getGroupByName, getAppName, missingNameArgument } from '../utils'; import { Schema as ApplicationOptions } from './schema'; export default function (options: ApplicationOptions) { if (!options.name) { - throw new SchematicsException(`Missing name argument. Provide a name for your NativeScript app. Example: ng g app.nativescript sample`); + throw new SchematicsException( + missingNameArgument('Provide a name for your NativeScript app.', 'ng g app.nativescript sample') + ); } return chain([ diff --git a/src/app.nest/index.ts b/src/app.nest/index.ts index 2048e672..dd93eba3 100644 --- a/src/app.nest/index.ts +++ b/src/app.nest/index.ts @@ -26,13 +26,14 @@ import { updateNxProjects, prerun, applyAppNamingConvention, - getAppName + getAppName, + missingNameArgument } from "../utils"; export default function(options: ApplicationOptions) { if (!options.name) { throw new SchematicsException( - `Missing name argument. Provide a name for your Nest app. Example: ng g app.nest sample` + missingNameArgument('Provide a name for your Nest app.', 'ng g app.nest sample') ); } diff --git a/src/app.web/index.ts b/src/app.web/index.ts index 3f23ff78..0b2ff8e3 100644 --- a/src/app.web/index.ts +++ b/src/app.web/index.ts @@ -23,7 +23,8 @@ import { getJsonFromFile, applyAppNamingConvention, updateJsonFile, - formatFiles + formatFiles, + missingNameArgument } from "../utils"; import { Schema as ApplicationOptions } from "./schema"; @@ -31,7 +32,7 @@ let appName: string; export default function(options: ApplicationOptions) { if (!options.name) { throw new SchematicsException( - `Missing name argument. Provide a name for your web app. Example: ng g app my-app` + missingNameArgument('Provide a name for your Web app.', 'ng g app my-app') ); } appName = options.name; @@ -169,27 +170,30 @@ platformBrowserDynamic() function appCmpHtml() { return `
-

- Welcome to an Angular CLI app built with Nrwl Nx and xplat! -

+

+ Welcome to ${appName}! +

+

+ An Angular CLI app built with Nrwl Nx and xplat. +

+
-

Nx

+

Nx

An open source toolkit for enterprise Angular applications. Nx is designed to help you create and build enterprise grade Angular applications. It provides an opinionated approach to application project structure and patterns.

Quick Start & Documentation

- Watch a 5-minute video on how to get started with Nx. + Watch a 5-minute video on how to get started with Nx. -

{{'hello' | translate}}

+

{{'hello' | translate}}

Try things out

- Learn more about xplat. + Learn more about xplat generators.
`; } @@ -258,7 +262,7 @@ describe('AppComponent', () => { }) ); it( - 'should render title in a h1 tag', + 'should render xplat hello in a h2 tag', async(() => { spyOn(translate, 'getBrowserLang').and.returnValue('en'); translate.use('en'); @@ -274,7 +278,7 @@ describe('AppComponent', () => { http.verify(); fixture.detectChanges(); - expect(compiled.querySelector('h1').textContent).toContain( + expect(compiled.querySelector('h2').textContent).toContain( 'Hello xplat' ); }) diff --git a/src/utils/errors.ts b/src/utils/errors.ts index d7179221..ab167f88 100644 --- a/src/utils/errors.ts +++ b/src/utils/errors.ts @@ -12,6 +12,18 @@ export function unsupportedHelperError(helper: string) { return `${helper} is not a supported helper. Currently supported: ${supportedHelpers}` } +export function helperTargetError(helper: string) { + return `The xplat-helper "${helper}" requires the --target flag.` +} + +export function helperMissingPlatforms() { + return `Missing platforms argument. Example: ng g xplat-helper imports --platforms=nativescript`; +} + +export function missingNameArgument(description: string = '', example: string = '') { + return `Missing name argument. ${description} ${example ? 'Example: ' + example : ''}`; +} + export function noPlatformError() { return `You must specify which platforms you wish to generate support for. For example: ng g xplat --prefix=foo --platforms=${supportedPlatforms.join(',')}` } diff --git a/src/utils/general.ts b/src/utils/general.ts index 0c96bded..fcc477bf 100644 --- a/src/utils/general.ts +++ b/src/utils/general.ts @@ -55,7 +55,7 @@ export interface NodeDependency { // list of all supported helpers // TODO: add more convenient helpers (like firebase or Travis ci support files) -export const supportedHelpers = ['imports']; +export const supportedHelpers = ['imports', 'applitools']; let npmScope: string; // selector prefix to use when generating various boilerplate for xplat support @@ -92,6 +92,10 @@ export function setTest() { isTest = true; } +export function isTesting() { + return isTest; +} + export function serializeJson(json: any): string { return `${JSON.stringify(json, null, 2)}\n`; } diff --git a/src/xplat-helper/applitools/index.ts b/src/xplat-helper/applitools/index.ts new file mode 100644 index 00000000..e908da5a --- /dev/null +++ b/src/xplat-helper/applitools/index.ts @@ -0,0 +1,11 @@ +import { Schema as HelperOptions } from "../schema"; +import { isTesting } from "../../utils"; + +export * from './web/support'; + +export function applitools_logNote(options: HelperOptions) { + if (!isTesting()) { + console.log(`Applitools support added for: ${options.target}`); + console.log(`Ensure your APPLITOOLS_API_KEY environment variable is set: https://applitools.com/tutorials/cypress.html#step-by-step-guide-run-the-demo-app`); + } +} \ No newline at end of file diff --git a/src/xplat-helper/applitools/web/support.ts b/src/xplat-helper/applitools/web/support.ts new file mode 100644 index 00000000..39c2330b --- /dev/null +++ b/src/xplat-helper/applitools/web/support.ts @@ -0,0 +1,126 @@ +import { + Tree, SchematicContext, noop, +} from "@angular-devkit/schematics"; +import { + updateTsConfig, createOrUpdate, updatePackageScripts, getJsonFromFile, updateJsonFile +} from "../../../utils"; +import { Schema as HelperOptions } from "../../schema"; + +export function supportApplitools_Web( + helperChains: Array, + options: HelperOptions +) { + return (tree: Tree, context: SchematicContext) => { + // update support index + helperChains.push( + createOrUpdate( + tree, + `/apps/${options.target}-e2e/src/support/index.ts`, + updateCypressIndex() + ) + ); + // update plugin index + helperChains.push( + createOrUpdate( + tree, + `/apps/${options.target}-e2e/src/plugins/index.ts`, + updateCypressPlugins() + ) + ); + // ensure supportFile points to updates + const cypressConfigPath = `/apps/${options.target}-e2e/cypress.json`; + const cypressConfig = getJsonFromFile(tree, cypressConfigPath); + // console.log('cypressConfig:', cypressConfig); + // plugin path is always defined so ensure support matches + const pluginsFilePath = cypressConfig.pluginsFile; + // console.log('pluginsFilePath:', pluginsFilePath); + const outputPath = pluginsFilePath.split('plugins/')[0]; + cypressConfig.supportFile = `${outputPath}support/index.js`; + // console.log('cypressConfig.supportFile:', cypressConfig.supportFile); + helperChains.push(updateJsonFile(tree, cypressConfigPath, cypressConfig)); + // update sample test + helperChains.push( + createOrUpdate( + tree, + `/apps/${options.target}-e2e/src/integration/app.spec.ts`, + updateSampleTest() + ) + ); + }; +} + +function updateCypressIndex() { + return `// *********************************************************** +// This example support/index.js is processed and +// loaded automatically before your test files. +// +// This is a great place to put global configuration and +// behavior that modifies Cypress. +// +// You can change the location of this file or turn off +// automatically serving support files with the +// 'supportFile' configuration option. +// +// You can read more here: +// https://on.cypress.io/configuration +// *********************************************************** + +// Applitools support +import '@applitools/eyes-cypress/commands'; + +// Import commands.js using ES2015 syntax: +import './commands'; +`; +} + +function updateCypressPlugins() { + return `// *********************************************************** +// This example plugins/index.js can be used to load plugins +// +// You can change the location of this file or turn off loading +// the plugins file with the 'pluginsFile' configuration option. +// +// You can read more here: +// https://on.cypress.io/plugins-guide +// *********************************************************** + +// This function is called when a project is opened or re-opened (e.g. due to +// the project's config changing) + +module.exports = (on: any, config: any) => { + // 'on' is used to hook into various events Cypress emits + // 'config' is the resolved Cypress config +}; + +// Applitools +require('@applitools/eyes-cypress')(module); +`; +} + +function updateSampleTest() { + return `import { getGreeting } from '../support/app.po'; + +describe('Hello Nx', () => { + beforeEach(() => cy.visit('/')); + + it('should display welcome message', () => { + + // start applitools test + (cy).eyesOpen({ + appName: 'myapp', + testName: 'Welcome message', + browser: { width: 800, height: 600 }, + }); + + // check window with applitools + (cy).eyesCheckWindow('Main Page'); + + // standard cypress testing + getGreeting().contains('Welcome to web-myapp!'); + + // end applitools test + (cy).eyesClose(); + }); +}); +`; +} \ No newline at end of file diff --git a/src/xplat-helper/imports/index.ts b/src/xplat-helper/imports/index.ts new file mode 100644 index 00000000..cb1c3921 --- /dev/null +++ b/src/xplat-helper/imports/index.ts @@ -0,0 +1 @@ +export * from './nativescript/support'; \ No newline at end of file diff --git a/src/xplat-helper/nativescript/imports/_files/@nativescript/angular/core.ts b/src/xplat-helper/imports/nativescript/_files/@nativescript/angular/core.ts similarity index 100% rename from src/xplat-helper/nativescript/imports/_files/@nativescript/angular/core.ts rename to src/xplat-helper/imports/nativescript/_files/@nativescript/angular/core.ts diff --git a/src/xplat-helper/nativescript/imports/_files/@nativescript/angular/index.ts b/src/xplat-helper/imports/nativescript/_files/@nativescript/angular/index.ts similarity index 100% rename from src/xplat-helper/nativescript/imports/_files/@nativescript/angular/index.ts rename to src/xplat-helper/imports/nativescript/_files/@nativescript/angular/index.ts diff --git a/src/xplat-helper/nativescript/imports/_files/@nativescript/core.ts b/src/xplat-helper/imports/nativescript/_files/@nativescript/core.ts similarity index 100% rename from src/xplat-helper/nativescript/imports/_files/@nativescript/core.ts rename to src/xplat-helper/imports/nativescript/_files/@nativescript/core.ts diff --git a/src/xplat-helper/nativescript/imports/_files/@nativescript/ui.ts b/src/xplat-helper/imports/nativescript/_files/@nativescript/ui.ts similarity index 100% rename from src/xplat-helper/nativescript/imports/_files/@nativescript/ui.ts rename to src/xplat-helper/imports/nativescript/_files/@nativescript/ui.ts diff --git a/src/xplat-helper/imports/nativescript/support.ts b/src/xplat-helper/imports/nativescript/support.ts new file mode 100644 index 00000000..ab8ad1fd --- /dev/null +++ b/src/xplat-helper/imports/nativescript/support.ts @@ -0,0 +1,70 @@ +import { + Tree, SchematicContext, +} from "@angular-devkit/schematics"; +import { + updateTsConfig +} from "../../../utils"; +import { Schema as HelperOptions } from "../../schema"; + +export function supportImports_NativeScript( + helperChains: Array, + options: HelperOptions +) { + return (tree: Tree, context: SchematicContext) => { + let pathRef = `xplat/nativescript/utils/@nativescript/*`; + // update root tsconfig + helperChains.push( + updateTsConfig(tree, (tsConfig: any) => { + const updates: any = {}; + updates[`@nativescript/*`] = [pathRef]; + if (tsConfig) { + if (!tsConfig.compilerOptions) { + tsConfig.compilerOptions = {}; + } + tsConfig.compilerOptions.paths = { + ...(tsConfig.compilerOptions.paths || {}), + ...updates + }; + } + }) + ); + + // update all {N} app tsconfig's + const appsDir = tree.getDir("apps"); + const appFolders = appsDir.subdirs; + pathRef = `../../${pathRef}`; + + // update {N} apps and configs + for (const dir of appFolders) { + // console.log(dir); + if ( + dir.indexOf("nativescript-") === 0 || + dir.indexOf("-nativescript") === 0 + ) { + const appDir = `${appsDir.path}/${dir}`; + // console.log('appDir:', appDir); + + helperChains.push( + updateTsConfig( + tree, + (tsConfig: any) => { + const updates: any = {}; + updates[`@nativescript/*`] = [pathRef]; + if (tsConfig) { + if (!tsConfig.compilerOptions) { + tsConfig.compilerOptions = {}; + } + tsConfig.compilerOptions.paths = { + ...(tsConfig.compilerOptions.paths || {}), + ...updates + }; + } + }, + null, + `${appDir}/` + ) + ); + } + } + }; +} diff --git a/src/xplat-helper/index.ts b/src/xplat-helper/index.ts index 541b9ffc..4bae50ac 100644 --- a/src/xplat-helper/index.ts +++ b/src/xplat-helper/index.ts @@ -29,67 +29,158 @@ import { PlatformTypes, supportedPlatforms, unsupportedPlatformError, + helperMissingPlatforms, supportedHelpers, unsupportedHelperError, - updateTsConfig + updateTsConfig, + helperTargetError, + missingNameArgument } from "../utils"; import { Schema as HelperOptions } from "./schema"; +// Helpers +import { supportApplitools_Web, applitools_logNote } from "./applitools"; +import { supportImports_NativeScript } from "./imports"; +// Configuration options for each helper +interface ISupportConfig { + platforms: Array; + requiresTarget?: boolean; + moveTo?: (platform: PlatformTypes, target?: string) => string; + additionalSupport?: ( + platform: PlatformTypes, + helperChains: Array, + options: HelperOptions + ) => (tree: Tree, context: SchematicContext) => void; +} +interface IHelperSupportConfig { + imports: ISupportConfig; + applitools: ISupportConfig; +} +// Mapping config of what each helper supports +const helperSupportConfig: IHelperSupportConfig = { + imports: { + platforms: ["nativescript"], + moveTo: function(platform: PlatformTypes, target?: string) { + return `xplat/${platform}/utils`; + }, + additionalSupport: function( + platform: PlatformTypes, + helperChains: Array, + options: HelperOptions + ) { + switch (platform) { + case "nativescript": + return supportImports_NativeScript(helperChains, options); + } + } + }, + applitools: { + platforms: ["web"], + requiresTarget: true, + additionalSupport: function( + platform: PlatformTypes, + helperChains: Array, + options: HelperOptions + ) { + switch (platform) { + case "web": + return supportApplitools_Web(helperChains, options); + } + } + } +}; let helpers: Array = []; -let platforms: Array = []; +let platforms: Array = []; export default function(options: HelperOptions) { if (!options.name) { throw new SchematicsException( - `Missing name argument. Provide a comma delimited list of helpers to generate. Example: ng g xplat-helper imports` - ); - } - if (!options.platforms) { - throw new SchematicsException( - `Missing platforms argument. Example: ng g xplat-helper imports --platforms=nativescript` + missingNameArgument('Provide a comma delimited list of helpers to generate.', 'ng g xplat-helper imports') ); } - helpers = options.name.split(","); - platforms = options.platforms.split(","); + platforms = >( + (options.platforms ? options.platforms.split(",") : []) + ); const helperChains = []; - for (const platform of platforms) { - if (supportedPlatforms.includes(platform)) { - for (const helper of helpers) { - if (supportedHelpers.includes(helper)) { - const moveTo = getMoveTo(platform, helper); - helperChains.push((tree: Tree, context: SchematicContext) => { - return addHelperFiles( - options, - platform, - helper, - moveTo - )(tree, context); - }); - // aside from adding files above, process any additional modifications - helperChains.push((tree: Tree, context: SchematicContext) => { - return processSupportingFiles( - helperChains, - options, - platform, - helper - ); - }); - } else { - throw new SchematicsException(unsupportedHelperError(helper)); + const processHelpers = (platform?: PlatformTypes) => { + for (const helper of helpers) { + if (supportedHelpers.includes(helper)) { + // get helper support config + const supportConfig: ISupportConfig = helperSupportConfig[helper]; + if (!platform) { + // when using targeting the platforms argument can be ommitted + // however when doing so it relies on platform being in the target name + if (supportConfig.requiresTarget && options.target) { + for (const p of supportedPlatforms) { + const match = options.target.match(p); + if (match) { + platform = match[0]; + break; + } + } + } + } + // if platform is still falsey, error out + if (!platform) { + throw new SchematicsException(helperMissingPlatforms()); + } + // throw is target required and it's missing + if (supportConfig.requiresTarget && !options.target) { + throw new SchematicsException(helperTargetError(helper)); + } + + if (supportConfig.platforms.includes(platform)) { + if (supportConfig.moveTo) { + // add files for the helper + const moveTo = supportConfig.moveTo(platform, options.target); + helperChains.push((tree: Tree, context: SchematicContext) => { + return addHelperFiles(options, platform, helper, moveTo)( + tree, + context + ); + }); + } + + if (supportConfig.additionalSupport) { + // process additional support modifications + helperChains.push((tree: Tree, context: SchematicContext) => { + return supportConfig.additionalSupport( + platform, + helperChains, + options + )(tree, context); + }); + } } + } else { + throw new SchematicsException(unsupportedHelperError(helper)); } - } else { - throw new SchematicsException(unsupportedPlatformError(platform)); } + }; + + if (platforms.length) { + for (const platform of platforms) { + if (supportedPlatforms.includes(platform)) { + processHelpers(platform); + } else { + throw new SchematicsException(unsupportedPlatformError(platform)); + } + } + } else { + processHelpers(); } return chain([ prerun(options), // add helper chains - ...helperChains - // TODO: add refactor code to update per the helper where applicable + ...helperChains, + // log additional notes + (tree: Tree) => { + logNotes(options, helpers); + return noop(); + } ]); } @@ -101,7 +192,7 @@ function addHelperFiles( ): Rule { return branchAndMerge( mergeWith( - apply(url(`./${platform}/${helper}/_files`), [ + apply(url(`./${helper}/${platform}/_files`), [ template({ ...(options as any), utils: stringUtils, @@ -115,77 +206,12 @@ function addHelperFiles( ); } -function getMoveTo(platform: PlatformTypes, helper: string) { - let moveTo = `xplat/${platform}/utils`; // default - // TODO: define custom moveTo locations for various helpers - // switch (helper) { - // case "imports": - // break; - // } - return moveTo; -} - -function processSupportingFiles( - helperChains: Array, - options: HelperOptions, - platform: PlatformTypes, - helper: string -) { - return (tree: Tree) => { +function logNotes(options: HelperOptions, helpers: string[]) { + for (const helper of helpers) { switch (helper) { - case "imports": - switch (platform) { - case "nativescript": - let pathRef = `xplat/nativescript/utils/@nativescript/*`; - // update root tsconfig - helperChains.push(updateTsConfig(tree, (tsConfig: any) => { - const updates: any = {}; - updates[`@nativescript/*`] = [ - pathRef - ]; - if (tsConfig) { - if (!tsConfig.compilerOptions) { - tsConfig.compilerOptions = {}; - } - tsConfig.compilerOptions.paths = { - ...(tsConfig.compilerOptions.paths || {}), - ...updates - }; - } - })); - - // update all {N} app tsconfig's - const appsDir = tree.getDir("apps"); - const appFolders = appsDir.subdirs; - pathRef = `../../${pathRef}`; - - // update {N} apps and configs - for (const dir of appFolders) { - // console.log(dir); - if (dir.indexOf('nativescript-') === 0 || dir.indexOf('-nativescript') === 0) { - const appDir = `${appsDir.path}/${dir}`; - // console.log('appDir:', appDir); - - helperChains.push(updateTsConfig(tree, (tsConfig: any) => { - const updates: any = {}; - updates[`@nativescript/*`] = [ - pathRef - ]; - if (tsConfig) { - if (!tsConfig.compilerOptions) { - tsConfig.compilerOptions = {}; - } - tsConfig.compilerOptions.paths = { - ...(tsConfig.compilerOptions.paths || {}), - ...updates - }; - } - }, null, `${appDir}/`)); - } - } - break; - } + case "applitools": + applitools_logNote(options); break; } - }; + } } diff --git a/src/xplat-helper/index_spec.ts b/src/xplat-helper/index_spec.ts index 375afd4b..f6c79b0b 100644 --- a/src/xplat-helper/index_spec.ts +++ b/src/xplat-helper/index_spec.ts @@ -1,22 +1,20 @@ import { Tree, VirtualTree } from '@angular-devkit/schematics'; -import { SchematicTestRunner } from '@angular-devkit/schematics/testing'; +import { SchematicTestRunner, UnitTestTree } from '@angular-devkit/schematics/testing'; import { getFileContent } from '@schematics/angular/utility/test'; import * as path from 'path'; -import { Schema as AppOptions } from '../app.nativescript/schema'; +import { Schema as AppWebOptions } from '../app.web/schema'; +import { Schema as AppNativeScriptOptions } from '../app.nativescript/schema'; import { Schema as XPlatOptions } from '../xplat/schema'; import { Schema as HelperOptions } from './schema'; -import { stringUtils, isInModuleMetadata, createEmptyWorkspace } from '../utils'; +import { stringUtils, isInModuleMetadata, createEmptyWorkspace, setTest } from '../utils'; +setTest(); describe('xplat-helper schematic', () => { const schematicRunner = new SchematicTestRunner( '@nstudio/schematics', path.join(__dirname, '../collection.json'), ); - const defaultOptions: HelperOptions = { - name: 'imports', - platforms: 'nativescript' - }; let appTree: Tree; @@ -25,7 +23,7 @@ describe('xplat-helper schematic', () => { appTree = createEmptyWorkspace(appTree); }); - it('should create all files for the helper', () => { + it('imports: should create all files', () => { const optionsXplat: XPlatOptions = { npmScope: 'testing', prefix: 'tt', @@ -33,7 +31,7 @@ describe('xplat-helper schematic', () => { }; appTree = schematicRunner.runSchematic('xplat', optionsXplat, appTree); - const appOptions: AppOptions = { + const appOptions: AppNativeScriptOptions = { name: 'foo', npmScope: 'testing', sample: true, @@ -42,7 +40,10 @@ describe('xplat-helper schematic', () => { // console.log('appTree:', appTree); appTree = schematicRunner.runSchematic('app.nativescript', appOptions, appTree); - const options: HelperOptions = { ...defaultOptions }; + const options: HelperOptions = { + name: 'imports', + platforms: 'nativescript' + }; // console.log('appTree:', appTree); const tree = schematicRunner.runSchematic('xplat-helper', options, appTree); const files = tree.files; @@ -65,6 +66,98 @@ describe('xplat-helper schematic', () => { expect(fileContent.compilerOptions.paths['@nativescript/*'][0]).toBe('../../xplat/nativescript/utils/@nativescript/*'); }); + it('applitools: should create all files', async () => { + const optionsXplat: XPlatOptions = { + npmScope: 'testing', + prefix: 'tt', + platforms: 'web,nativescript' + }; + + appTree = schematicRunner.runSchematic('xplat', optionsXplat, appTree); + const appOptions: AppWebOptions = { + name: 'foo', + prefix: 'tt', + e2eTestRunner: 'cypress' + }; + // console.log('appTree:', appTree); + appTree = await schematicRunner.runSchematicAsync('app', appOptions, appTree).toPromise(); + + const cypressJsonPath = '/apps/web-foo-e2e/cypress.json'; + let fileContent = getFileContent(appTree, cypressJsonPath); + let cypressJson = JSON.parse(fileContent); + expect(cypressJson.supportFile).toBe(false); + + const options: HelperOptions = { + name: 'applitools', + target: 'web-foo' + }; + // console.log('appTree:', appTree); + const tree = schematicRunner.runSchematic('xplat-helper', options, appTree); + const files = tree.files; + // console.log(files); + + expect(files.indexOf('/apps/web-foo-e2e/src/support/index.ts')).toBeGreaterThanOrEqual(0); + expect(files.indexOf('/apps/web-foo-e2e/src/plugins/index.ts')).toBeGreaterThanOrEqual(0); + // modified testing files + let filePath = '/apps/web-foo-e2e/src/support/index.ts'; + fileContent = getFileContent(tree, filePath) + // console.log(fileContent); + + expect(fileContent.indexOf('@applitools/eyes-cypress/commands')).toBeGreaterThanOrEqual(0); + + filePath = '/apps/web-foo-e2e/src/plugins/index.ts'; + fileContent = getFileContent(tree, filePath) + // console.log(fileContent); + + expect(fileContent.indexOf(`require('@applitools/eyes-cypress')(module);`)).toBeGreaterThanOrEqual(0); + + fileContent = getFileContent(tree, cypressJsonPath); + // console.log(fileContent); + cypressJson = JSON.parse(fileContent); + expect(cypressJson.supportFile).toBe(`../../dist/out-tsc/apps/web-foo-e2e/src/support/index.js`); + + filePath = '/apps/web-foo-e2e/src/integration/app.spec.ts'; + fileContent = getFileContent(tree, filePath) + // console.log(fileContent); + + expect(fileContent.indexOf(`eyesOpen`)).toBeGreaterThanOrEqual(0); + }); + + it('applitools: should throw if target is missing', async () => { + const optionsXplat: XPlatOptions = { + npmScope: 'testing', + prefix: 'tt', + platforms: 'web,nativescript' + }; + + appTree = schematicRunner.runSchematic('xplat', optionsXplat, appTree); + const appOptions: AppWebOptions = { + name: 'foo', + prefix: 'tt', + e2eTestRunner: 'cypress' + }; + // console.log('appTree:', appTree); + appTree = await schematicRunner.runSchematicAsync('app', appOptions, appTree).toPromise(); + + const cypressJsonPath = '/apps/web-foo-e2e/cypress.json'; + let fileContent = getFileContent(appTree, cypressJsonPath); + let cypressJson = JSON.parse(fileContent); + expect(cypressJson.supportFile).toBe(false); + + const options: HelperOptions = { + name: 'applitools', + platforms: 'web' + }; + // console.log('appTree:', appTree); + let tree; + + expect( + () => (tree = schematicRunner.runSchematic('xplat-helper', options, appTree)) + ).toThrowError( + `The xplat-helper "applitools" requires the --target flag.` + ); + }); + it('generating helper for a platform where the helper is not supported should not do anything', () => { const optionsXplat: XPlatOptions = { npmScope: 'testing', diff --git a/src/xplat-helper/schema.d.ts b/src/xplat-helper/schema.d.ts index 7bbd69b7..60b57eb4 100644 --- a/src/xplat-helper/schema.d.ts +++ b/src/xplat-helper/schema.d.ts @@ -7,8 +7,18 @@ export interface Schema { * Target platforms to generate helpers for. */ platforms?: string; + /** + * Optional target when adding helpers + */ + target?: string; /** * Skip refactoring code to support the helper where supported. + * TODO + * "skipRefactor": { + "type": "boolean", + "description": "Skip refactoring code to support the helper where supported.", + "default": false + } */ - skipRefactor?: boolean; + // skipRefactor?: boolean; } diff --git a/src/xplat-helper/schema.json b/src/xplat-helper/schema.json index 79eaa8e4..7398b7c5 100644 --- a/src/xplat-helper/schema.json +++ b/src/xplat-helper/schema.json @@ -17,10 +17,9 @@ "type": "string", "description": "Target platforms to generate helpers for." }, - "skipRefactor": { - "type": "boolean", - "description": "Skip refactoring code to support the helper where supported.", - "default": false + "target": { + "type": "string", + "description": "Some helpers support targeting to generate the helper for a specific target. ie: ng g xplat-helper applitools --target=web-myapp" } }, "required": []