From 9002662b07ec7a979c1db9cdaae1049ea2872257 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leosvel=20P=C3=A9rez=20Espinosa?= Date: Mon, 21 Aug 2023 22:07:36 +0100 Subject: [PATCH] feat(detox): use helper to determine project name and root in application generator (#18674) --- .../detox/generators/application.json | 12 +++-- e2e/detox/src/detox.test.ts | 21 ++++++++ packages/detox/generators.json | 2 +- .../src/generators/application/application.ts | 12 ++++- .../application/lib/add-linting.spec.ts | 3 -- .../application/lib/add-project.spec.ts | 2 - .../generators/application/lib/add-project.ts | 8 +-- .../application/lib/create-files.spec.ts | 1 - .../application/lib/normalize-options.spec.ts | 29 +++++------ .../application/lib/normalize-options.ts | 52 ++++++------------- .../src/generators/application/schema.d.ts | 4 +- .../src/generators/application/schema.json | 8 ++- 12 files changed, 85 insertions(+), 69 deletions(-) diff --git a/docs/generated/packages/detox/generators/application.json b/docs/generated/packages/detox/generators/application.json index 8817e6e94914a..5b21bfd93c15e 100644 --- a/docs/generated/packages/detox/generators/application.json +++ b/docs/generated/packages/detox/generators/application.json @@ -1,6 +1,6 @@ { "name": "application", - "factory": "./src/generators/application/application#detoxApplicationGenerator", + "factory": "./src/generators/application/application#detoxApplicationGeneratorInternal", "schema": { "$schema": "http://json-schema.org/schema", "title": "Create Detox Configuration for the workspace", @@ -17,7 +17,8 @@ "type": "string", "description": "Name of the E2E Project.", "$default": { "$source": "argv", "index": 0 }, - "x-prompt": "What name would you like to use for the E2E project?" + "x-prompt": "What name would you like to use for the E2E project?", + "pattern": "^[a-zA-Z][^:]*$" }, "appName": { "type": "string", @@ -37,6 +38,11 @@ "type": "string", "description": "A directory where the project is placed relative to apps directory." }, + "projectNameAndRootFormat": { + "description": "Whether to generate the project name and root directory as provided (`as-provided`) or generate them composing their values and taking the configured layout into account (`derived`).", + "type": "string", + "enum": ["as-provided", "derived"] + }, "linter": { "description": "The tool to use for running lint checks.", "type": "string", @@ -66,7 +72,7 @@ "aliases": ["app"], "x-type": "application", "description": "Create a Detox application.", - "implementation": "/packages/detox/src/generators/application/application#detoxApplicationGenerator.ts", + "implementation": "/packages/detox/src/generators/application/application#detoxApplicationGeneratorInternal.ts", "hidden": false, "path": "/packages/detox/src/generators/application/schema.json", "type": "generator" diff --git a/e2e/detox/src/detox.test.ts b/e2e/detox/src/detox.test.ts index b96291ecccb5c..8124e06e1212a 100644 --- a/e2e/detox/src/detox.test.ts +++ b/e2e/detox/src/detox.test.ts @@ -47,6 +47,27 @@ describe('Detox', () => { expect(lintResults.combinedOutput).toContain('All files pass linting'); }); + it('should support generating projects with the new name and root format', async () => { + const appName = uniq('app1'); + + runCLI( + `generate @nx/react-native:app ${appName} --e2eTestRunner=detox --linter=eslint --install=false --project-name-and-root-format=as-provided` + ); + + // check files are generated without the layout directory ("apps/") and + // using the project name as the directory when no directory is provided + checkFilesExist( + `${appName}-e2e/.detoxrc.json`, + `${appName}-e2e/tsconfig.json`, + `${appName}-e2e/tsconfig.e2e.json`, + `${appName}-e2e/test-setup.ts`, + `${appName}-e2e/src/app.spec.ts` + ); + + const lintResults = await runCLIAsync(`lint ${appName}-e2e`); + expect(lintResults.combinedOutput).toContain('All files pass linting'); + }); + // TODO: @xiongemi please fix or remove this test xdescribe('React Native Detox MACOS-Tests', () => { if (isOSX()) { diff --git a/packages/detox/generators.json b/packages/detox/generators.json index 707dc4407f7af..c0c41461d4c44 100644 --- a/packages/detox/generators.json +++ b/packages/detox/generators.json @@ -25,7 +25,7 @@ "hidden": true }, "application": { - "factory": "./src/generators/application/application#detoxApplicationGenerator", + "factory": "./src/generators/application/application#detoxApplicationGeneratorInternal", "schema": "./src/generators/application/schema.json", "aliases": ["app"], "x-type": "application", diff --git a/packages/detox/src/generators/application/application.ts b/packages/detox/src/generators/application/application.ts index 735535b859440..65138b439d24f 100644 --- a/packages/detox/src/generators/application/application.ts +++ b/packages/detox/src/generators/application/application.ts @@ -14,7 +14,17 @@ import { normalizeOptions } from './lib/normalize-options'; import { Schema } from './schema'; export async function detoxApplicationGenerator(host: Tree, schema: Schema) { - const options = normalizeOptions(host, schema); + return await detoxApplicationGeneratorInternal(host, { + projectNameAndRootFormat: 'derived', + ...schema, + }); +} + +export async function detoxApplicationGeneratorInternal( + host: Tree, + schema: Schema +) { + const options = await normalizeOptions(host, schema); const initTask = await detoxInitGenerator(host, { ...options, diff --git a/packages/detox/src/generators/application/lib/add-linting.spec.ts b/packages/detox/src/generators/application/lib/add-linting.spec.ts index c9588d591d309..c2658f764c098 100644 --- a/packages/detox/src/generators/application/lib/add-linting.spec.ts +++ b/packages/detox/src/generators/application/lib/add-linting.spec.ts @@ -12,7 +12,6 @@ describe('Add Linting', () => { addProject(tree, { e2eName: 'my-app-e2e', e2eProjectName: 'my-app-e2e', - e2eProjectDirectory: 'apps', e2eProjectRoot: 'apps/my-app-e2e', appProject: 'my-app', appFileName: 'my-app', @@ -29,7 +28,6 @@ describe('Add Linting', () => { addLinting(tree, { e2eName: 'my-app-e2e', e2eProjectName: 'my-app-e2e', - e2eProjectDirectory: 'apps', e2eProjectRoot: 'apps/my-app-e2e', appProject: 'my-app', appFileName: 'my-app', @@ -50,7 +48,6 @@ describe('Add Linting', () => { addLinting(tree, { e2eName: 'my-app-e2e', e2eProjectName: 'my-app-e2e', - e2eProjectDirectory: 'apps', e2eProjectRoot: 'apps/my-app-e2e', appProject: 'my-app', appFileName: 'my-app', diff --git a/packages/detox/src/generators/application/lib/add-project.spec.ts b/packages/detox/src/generators/application/lib/add-project.spec.ts index 58160f595efa6..17032dfb560ed 100644 --- a/packages/detox/src/generators/application/lib/add-project.spec.ts +++ b/packages/detox/src/generators/application/lib/add-project.spec.ts @@ -31,7 +31,6 @@ describe('Add Project', () => { addProject(tree, { e2eName: 'my-app-e2e', e2eProjectName: 'my-app-e2e', - e2eProjectDirectory: 'apps', e2eProjectRoot: 'apps/my-app-e2e', appProject: 'my-app', appFileName: 'my-app', @@ -81,7 +80,6 @@ describe('Add Project', () => { addProject(tree, { e2eName: 'my-dir-my-app-e2e', e2eProjectName: 'my-dir-my-app-e2e', - e2eProjectDirectory: 'apps', e2eProjectRoot: 'apps/my-dir/my-app-e2e', appProject: 'my-dir-my-app', appFileName: 'my-app', diff --git a/packages/detox/src/generators/application/lib/add-project.ts b/packages/detox/src/generators/application/lib/add-project.ts index a4d3a29b48b0d..92b8d4824beb9 100644 --- a/packages/detox/src/generators/application/lib/add-project.ts +++ b/packages/detox/src/generators/application/lib/add-project.ts @@ -31,8 +31,8 @@ function getTargets(options: NormalizedSchema) { targets['test-ios'] = { executor: '@nx/detox:test', ...(options.framework === 'react-native' - ? reactNativeTestTarget('ios.sim', options.e2eName) - : expoTestTarget('ios.sim', options.e2eName)), + ? reactNativeTestTarget('ios.sim', options.e2eProjectName) + : expoTestTarget('ios.sim', options.e2eProjectName)), }; targets['build-android'] = { @@ -45,8 +45,8 @@ function getTargets(options: NormalizedSchema) { targets['test-android'] = { executor: '@nx/detox:test', ...(options.framework === 'react-native' - ? reactNativeTestTarget('android.emu', options.e2eName) - : expoTestTarget('android.emu', options.e2eName)), + ? reactNativeTestTarget('android.emu', options.e2eProjectName) + : expoTestTarget('android.emu', options.e2eProjectName)), }; return targets; diff --git a/packages/detox/src/generators/application/lib/create-files.spec.ts b/packages/detox/src/generators/application/lib/create-files.spec.ts index 2c8104e91aa49..d6b13ae193fd2 100644 --- a/packages/detox/src/generators/application/lib/create-files.spec.ts +++ b/packages/detox/src/generators/application/lib/create-files.spec.ts @@ -14,7 +14,6 @@ describe('Create Files', () => { createFiles(tree, { e2eName: 'my-app-e2e', e2eProjectName: 'my-app-e2e', - e2eProjectDirectory: 'apps', e2eProjectRoot: 'apps/my-app-e2e', appProject: 'my-app', appFileName: 'my-app', diff --git a/packages/detox/src/generators/application/lib/normalize-options.spec.ts b/packages/detox/src/generators/application/lib/normalize-options.spec.ts index 6a7587c88bf67..55a95f0c15adc 100644 --- a/packages/detox/src/generators/application/lib/normalize-options.spec.ts +++ b/packages/detox/src/generators/application/lib/normalize-options.spec.ts @@ -12,7 +12,7 @@ describe('Normalize Options', () => { appTree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' }); }); - it('should normalize options with name in kebab case', () => { + it('should normalize options with name in kebab case', async () => { addProjectConfiguration(appTree, 'my-app', { root: 'apps/my-app', targets: {}, @@ -23,12 +23,11 @@ describe('Normalize Options', () => { appProject: 'my-app', linter: Linter.EsLint, }; - const options = normalizeOptions(appTree, schema); + const options = await normalizeOptions(appTree, schema); expect(options).toEqual({ framework: 'react-native', e2eName: 'my-app-e2e', e2eProjectName: 'my-app-e2e', - e2eProjectDirectory: 'apps', e2eProjectRoot: 'apps/my-app-e2e', appProject: 'my-app', appFileName: 'my-app', @@ -40,7 +39,7 @@ describe('Normalize Options', () => { }); }); - it('should normalize options with name in camel case', () => { + it('should normalize options with name in camel case', async () => { addProjectConfiguration(appTree, 'my-app', { root: 'apps/my-app', targets: {}, @@ -50,7 +49,7 @@ describe('Normalize Options', () => { e2eName: 'myAppE2e', appProject: 'myApp', }; - const options = normalizeOptions(appTree, schema); + const options = await normalizeOptions(appTree, schema); expect(options).toEqual({ appClassName: 'MyApp', appDisplayName: 'MyApp', @@ -60,13 +59,12 @@ describe('Normalize Options', () => { e2eName: 'my-app-e2e', appProject: 'myApp', e2eProjectName: 'my-app-e2e', - e2eProjectDirectory: 'apps', e2eProjectRoot: 'apps/my-app-e2e', framework: 'react-native', }); }); - it('should normalize options with display name', () => { + it('should normalize options with display name', async () => { addProjectConfiguration(appTree, 'my-app', { root: 'apps/my-app', targets: {}, @@ -77,7 +75,7 @@ describe('Normalize Options', () => { appProject: 'myApp', appDisplayName: 'app display name', }; - const options = normalizeOptions(appTree, schema); + const options = await normalizeOptions(appTree, schema); expect(options).toEqual({ appDisplayName: 'app display name', appExpoName: 'appdisplayname', @@ -87,13 +85,12 @@ describe('Normalize Options', () => { e2eName: 'my-app-e2e', appProject: 'myApp', e2eProjectName: 'my-app-e2e', - e2eProjectDirectory: 'apps', e2eProjectRoot: 'apps/my-app-e2e', framework: 'react-native', }); }); - it('should normalize options with directory', () => { + it('should normalize options with directory', async () => { addProjectConfiguration(appTree, 'my-app', { root: 'apps/my-app', targets: {}, @@ -104,7 +101,7 @@ describe('Normalize Options', () => { appProject: 'my-app', e2eDirectory: 'directory', }; - const options = normalizeOptions(appTree, schema); + const options = await normalizeOptions(appTree, schema); expect(options).toEqual({ appProject: 'my-app', appClassName: 'MyApp', @@ -112,16 +109,15 @@ describe('Normalize Options', () => { appExpoName: 'MyApp', appFileName: 'my-app', appRoot: 'apps/my-app', - e2eProjectDirectory: 'apps/directory', e2eProjectRoot: 'apps/directory/my-app-e2e', - e2eName: 'my-app-e2e', + e2eName: 'directory-my-app-e2e', e2eDirectory: 'directory', e2eProjectName: 'directory-my-app-e2e', framework: 'react-native', }); }); - it('should normalize options with directory in its name', () => { + it('should normalize options with directory in its name', async () => { addProjectConfiguration(appTree, 'my-app', { root: 'apps/my-app', targets: {}, @@ -131,7 +127,7 @@ describe('Normalize Options', () => { e2eName: 'directory/my-app-e2e', appProject: 'my-app', }; - const options = normalizeOptions(appTree, schema); + const options = await normalizeOptions(appTree, schema); expect(options).toEqual({ appProject: 'my-app', appClassName: 'MyApp', @@ -140,8 +136,7 @@ describe('Normalize Options', () => { appFileName: 'my-app', appRoot: 'apps/my-app', e2eProjectRoot: 'apps/directory/my-app-e2e', - e2eProjectDirectory: 'apps', - e2eName: 'directory/my-app-e2e', + e2eName: 'directory-my-app-e2e', e2eProjectName: 'directory-my-app-e2e', framework: 'react-native', }); diff --git a/packages/detox/src/generators/application/lib/normalize-options.ts b/packages/detox/src/generators/application/lib/normalize-options.ts index 876ebe058419c..15ea4b4128782 100644 --- a/packages/detox/src/generators/application/lib/normalize-options.ts +++ b/packages/detox/src/generators/application/lib/normalize-options.ts @@ -1,10 +1,5 @@ -import { - getProjects, - getWorkspaceLayout, - joinPathFragments, - names, - Tree, -} from '@nx/devkit'; +import { names, readProjectConfiguration, Tree } from '@nx/devkit'; +import { determineProjectNameAndRootOptions } from '@nx/devkit/src/generators/project-name-and-root-utils'; import { Schema } from '../schema'; export interface NormalizedSchema extends Schema { @@ -13,41 +8,29 @@ export interface NormalizedSchema extends Schema { appExpoName: string; // the expo name of app to be tested in class case appRoot: string; // the root path of e2e project. e.g. apps/app-directory/app e2eProjectName: string; // the name of e2e project - e2eProjectDirectory: string; // root path the directory of e2e project directory. e,g. apps/e2e-directory e2eProjectRoot: string; // the root path of e2e project. e.g. apps/e2e-directory/e2e-app } -/** - * if options.e2eName = 'my-app-e2e' with no options.directory - * e2eProjectName = 'my-app', e2eProjectRoot = 'apps/my-app' - * if options.e2eName = 'my-app' with options.e2eDirectory = 'my-dir' - * e2eProjectName = 'my-dir-my-app', e2eProjectRoot = 'apps/my-dir/my-apps' - */ -export function normalizeOptions( +export async function normalizeOptions( host: Tree, options: Schema -): NormalizedSchema { - const { appsDir } = getWorkspaceLayout(host); - const e2eFileName = names(options.e2eName).fileName; - const e2eDirectoryFileName = options.e2eDirectory - ? names(options.e2eDirectory).fileName - : ''; - const e2eProjectName = ( - e2eDirectoryFileName - ? `${e2eDirectoryFileName}-${e2eFileName}` - : e2eFileName - ).replace(/\//g, '-'); - const e2eProjectDirectory = e2eDirectoryFileName - ? joinPathFragments(appsDir, e2eDirectoryFileName) - : appsDir; - const e2eProjectRoot = joinPathFragments(e2eProjectDirectory, e2eFileName); +): Promise { + const { projectName: e2eProjectName, projectRoot: e2eProjectRoot } = + await determineProjectNameAndRootOptions(host, { + name: options.e2eName, + projectType: 'application', + directory: options.e2eDirectory, + projectNameAndRootFormat: options.projectNameAndRootFormat, + callingGenerator: '@nx/detox:application', + }); const { fileName: appFileName, className: appClassName } = names( options.appName || options.appProject ); - const project = getProjects(host).get(options.appProject); - const appRoot = - project?.root || joinPathFragments(e2eProjectDirectory, appFileName); + const { root: appRoot } = readProjectConfiguration( + host, + names(options.appProject).fileName + ); return { ...options, @@ -56,9 +39,8 @@ export function normalizeOptions( appDisplayName: options.appDisplayName || appClassName, appExpoName: options.appDisplayName?.replace(/\s/g, '') || appClassName, appRoot, - e2eName: e2eFileName, + e2eName: e2eProjectName, e2eProjectName, - e2eProjectDirectory, e2eProjectRoot, }; } diff --git a/packages/detox/src/generators/application/schema.d.ts b/packages/detox/src/generators/application/schema.d.ts index 2a95b66bfb5ae..2fa7b27118db4 100644 --- a/packages/detox/src/generators/application/schema.d.ts +++ b/packages/detox/src/generators/application/schema.d.ts @@ -1,10 +1,12 @@ -import { Linter } from '@nx/linter'; +import type { ProjectNameAndRootFormat } from '@nx/devkit/src/generators/project-name-and-root-utils'; +import type { Linter } from '@nx/linter'; export interface Schema { appProject: string; // name of the project app to be tested (directory + app name in kebab class) appDisplayName?: string; // display name of the app to be tested appName?: string; // name of app to be tested if different form appProject, case insenstive e2eDirectory?: string; // the directory where e2e app going to be located + projectNameAndRootFormat?: ProjectNameAndRootFormat; e2eName: string; // name of the e2e app linter?: Linter; js?: boolean; diff --git a/packages/detox/src/generators/application/schema.json b/packages/detox/src/generators/application/schema.json index f0b07a65858e3..ba0fe35329e80 100644 --- a/packages/detox/src/generators/application/schema.json +++ b/packages/detox/src/generators/application/schema.json @@ -19,7 +19,8 @@ "$source": "argv", "index": 0 }, - "x-prompt": "What name would you like to use for the E2E project?" + "x-prompt": "What name would you like to use for the E2E project?", + "pattern": "^[a-zA-Z][^:]*$" }, "appName": { "type": "string", @@ -39,6 +40,11 @@ "type": "string", "description": "A directory where the project is placed relative to apps directory." }, + "projectNameAndRootFormat": { + "description": "Whether to generate the project name and root directory as provided (`as-provided`) or generate them composing their values and taking the configured layout into account (`derived`).", + "type": "string", + "enum": ["as-provided", "derived"] + }, "linter": { "description": "The tool to use for running lint checks.", "type": "string",