Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(detox): use helper to determine project name and root in application generator #18674

Merged
merged 4 commits into from
Aug 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
12 changes: 9 additions & 3 deletions docs/generated/packages/detox/generators/application.json
Original file line number Diff line number Diff line change
@@ -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",
Expand All @@ -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",
Expand All @@ -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",
Expand Down Expand Up @@ -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"
Expand Down
21 changes: 21 additions & 0 deletions e2e/detox/src/detox.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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()) {
Expand Down
2 changes: 1 addition & 1 deletion packages/detox/generators.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
12 changes: 11 additions & 1 deletion packages/detox/src/generators/application/application.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand All @@ -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',
Expand All @@ -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',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -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',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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'] = {
Expand All @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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: {},
Expand All @@ -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',
Expand All @@ -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: {},
Expand All @@ -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',
Expand All @@ -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: {},
Expand All @@ -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',
Expand All @@ -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: {},
Expand All @@ -104,24 +101,23 @@ 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',
appDisplayName: 'MyApp',
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: {},
Expand All @@ -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',
Expand All @@ -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',
});
Expand Down
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -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<NormalizedSchema> {
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,
Expand All @@ -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,
};
}
4 changes: 3 additions & 1 deletion packages/detox/src/generators/application/schema.d.ts
Original file line number Diff line number Diff line change
@@ -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;
Expand Down