bugfix(detox): allow to pass in appName to detox (#12436)

This commit is contained in:
Emily Xiong 2022-10-12 10:49:10 -04:00 committed by GitHub
parent e74a54d139
commit 0c7c4822e0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 868 additions and 177 deletions

View File

@ -48,27 +48,35 @@
"description": "Create Detox Configuration for the workspace.",
"type": "object",
"properties": {
"project": {
"appProject": {
"type": "string",
"description": "The name of the frontend project to test.",
"description": "Name of the frontend project to be tested.",
"$default": { "$source": "projectName" },
"x-prompt": "What is the name of the frontend project to test?"
},
"name": {
"e2eName": {
"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?"
},
"appName": {
"type": "string",
"description": "Name of the app to be tested if different from appProject"
},
"appDisplayName": {
"type": "string",
"description": "Display name of the app to be tested if different from appProject"
},
"framework": {
"type": "string",
"description": "App framework to test",
"enum": ["react-native", "expo"],
"x-prompt": "What app framework should detox test?"
},
"directory": {
"e2eDirectory": {
"type": "string",
"description": "A directory where the project is placed."
"description": "A directory where the project is placed relative to apps directory."
},
"linter": {
"description": "The tool to use for running lint checks.",
@ -92,7 +100,7 @@
"default": false
}
},
"required": ["name", "project", "framework"],
"required": ["e2eName", "appProject", "framework"],
"presets": []
},
"aliases": ["app"],

View File

@ -24,8 +24,8 @@ describe('detox application generator', () => {
});
await detoxApplicationGenerator(tree, {
name: 'my-app-e2e',
project: 'my-app',
e2eName: 'my-app-e2e',
appProject: 'my-app',
linter: Linter.None,
framework: 'react-native',
});
@ -34,6 +34,43 @@ describe('detox application generator', () => {
it('should generate files', () => {
expect(tree.exists('apps/my-app-e2e/.detoxrc.json')).toBeTruthy();
expect(tree.exists('apps/my-app-e2e/src/app.spec.ts')).toBeTruthy();
const detoxrc = tree.read('apps/my-app-e2e/.detoxrc.json').toString();
// Strip trailing commas
const detoxrcJson = JSON.parse(
detoxrc.replace(/(?<=(true|false|null|["\d}\]])\s*),(?=\s*[}\]])/g, '')
);
const appsDetoxrcJson = detoxrcJson['apps'];
expect(appsDetoxrcJson).toEqual({
'android.debug': {
binaryPath:
'../../my-app/android/app/build/outputs/apk/debug/app-debug.apk',
build:
'cd ../../my-app/android && ./gradlew assembleDebug assembleAndroidTest -DtestBuildType=debug',
type: 'android.apk',
},
'android.release': {
binaryPath:
'../../my-app/android/app/build/outputs/apk/release/app-release.apk',
build:
'cd ../../my-app/android && ./gradlew assembleRelease assembleAndroidTest -DtestBuildType=release',
type: 'android.apk',
},
'ios.debug': {
binaryPath:
'../../my-app/ios/build/Build/Products/Debug-iphonesimulator/MyApp.app',
build:
"cd ../../my-app/ios && xcodebuild -workspace MyApp.xcworkspace -scheme MyApp -configuration Debug -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 13' -derivedDataPath ./build -quiet",
type: 'ios.app',
},
'ios.release': {
binaryPath:
'../../my-app/ios/build/Build/Products/Release-iphonesimulator/MyApp.app',
build:
"cd ../../my-app/ios && xcodebuild -workspace MyApp.xcworkspace -scheme MyApp -configuration Release -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 13' -derivedDataPath ./build -quiet",
type: 'ios.app',
},
});
});
it('should add update `workspace.json` file', async () => {
@ -49,16 +86,16 @@ describe('detox application generator', () => {
});
});
describe('with directory specified', () => {
describe('with directory specified that is same as e2e project', () => {
beforeEach(async () => {
addProjectConfiguration(tree, 'my-dir-my-app', {
root: 'my-dir/my-app',
});
await detoxApplicationGenerator(tree, {
name: 'my-app-e2e',
directory: 'my-dir',
project: 'my-dir-my-app',
e2eName: 'my-app-e2e',
e2eDirectory: 'my-dir',
appProject: 'my-dir-my-app',
linter: Linter.None,
framework: 'react-native',
});
@ -69,6 +106,45 @@ describe('detox application generator', () => {
expect(
tree.exists('apps/my-dir/my-app-e2e/src/app.spec.ts')
).toBeTruthy();
const detoxrc = tree
.read('apps/my-dir/my-app-e2e/.detoxrc.json')
.toString();
// Strip trailing commas
const detoxrcJson = JSON.parse(
detoxrc.replace(/(?<=(true|false|null|["\d}\]])\s*),(?=\s*[}\]])/g, '')
);
const appsDetoxrcJson = detoxrcJson['apps'];
expect(appsDetoxrcJson).toEqual({
'android.debug': {
binaryPath:
'../../../my-dir/my-app/android/app/build/outputs/apk/debug/app-debug.apk',
build:
'cd ../../../my-dir/my-app/android && ./gradlew assembleDebug assembleAndroidTest -DtestBuildType=debug',
type: 'android.apk',
},
'android.release': {
binaryPath:
'../../../my-dir/my-app/android/app/build/outputs/apk/release/app-release.apk',
build:
'cd ../../../my-dir/my-app/android && ./gradlew assembleRelease assembleAndroidTest -DtestBuildType=release',
type: 'android.apk',
},
'ios.debug': {
binaryPath:
'../../../my-dir/my-app/ios/build/Build/Products/Debug-iphonesimulator/MyDirMyApp.app',
build:
"cd ../../../my-dir/my-app/ios && xcodebuild -workspace MyDirMyApp.xcworkspace -scheme MyDirMyApp -configuration Debug -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 13' -derivedDataPath ./build -quiet",
type: 'ios.app',
},
'ios.release': {
binaryPath:
'../../../my-dir/my-app/ios/build/Build/Products/Release-iphonesimulator/MyDirMyApp.app',
build:
"cd ../../../my-dir/my-app/ios && xcodebuild -workspace MyDirMyApp.xcworkspace -scheme MyDirMyApp -configuration Release -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 13' -derivedDataPath ./build -quiet",
type: 'ios.app',
},
});
});
it('should add update `workspace.json` file', async () => {
@ -84,6 +160,81 @@ describe('detox application generator', () => {
});
});
describe('with directory specified that is different from as e2e project', () => {
beforeEach(async () => {
addProjectConfiguration(tree, 'my-dir-my-app', {
root: 'my-dir/my-app',
});
await detoxApplicationGenerator(tree, {
e2eName: 'my-app-e2e',
e2eDirectory: 'e2e-dir',
appProject: 'my-dir-my-app',
linter: Linter.None,
framework: 'react-native',
});
});
it('should generate files', () => {
expect(tree.exists('apps/e2e-dir/my-app-e2e/.detoxrc.json')).toBeTruthy();
expect(
tree.exists('apps/e2e-dir/my-app-e2e/src/app.spec.ts')
).toBeTruthy();
const detoxrc = tree
.read('apps/e2e-dir/my-app-e2e/.detoxrc.json')
.toString();
// Strip trailing commas
const detoxrcJson = JSON.parse(
detoxrc.replace(/(?<=(true|false|null|["\d}\]])\s*),(?=\s*[}\]])/g, '')
);
const appsDetoxrcJson = detoxrcJson['apps'];
expect(appsDetoxrcJson).toEqual({
'android.debug': {
binaryPath:
'../../../my-dir/my-app/android/app/build/outputs/apk/debug/app-debug.apk',
build:
'cd ../../../my-dir/my-app/android && ./gradlew assembleDebug assembleAndroidTest -DtestBuildType=debug',
type: 'android.apk',
},
'android.release': {
binaryPath:
'../../../my-dir/my-app/android/app/build/outputs/apk/release/app-release.apk',
build:
'cd ../../../my-dir/my-app/android && ./gradlew assembleRelease assembleAndroidTest -DtestBuildType=release',
type: 'android.apk',
},
'ios.debug': {
binaryPath:
'../../../my-dir/my-app/ios/build/Build/Products/Debug-iphonesimulator/MyDirMyApp.app',
build:
"cd ../../../my-dir/my-app/ios && xcodebuild -workspace MyDirMyApp.xcworkspace -scheme MyDirMyApp -configuration Debug -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 13' -derivedDataPath ./build -quiet",
type: 'ios.app',
},
'ios.release': {
binaryPath:
'../../../my-dir/my-app/ios/build/Build/Products/Release-iphonesimulator/MyDirMyApp.app',
build:
"cd ../../../my-dir/my-app/ios && xcodebuild -workspace MyDirMyApp.xcworkspace -scheme MyDirMyApp -configuration Release -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 13' -derivedDataPath ./build -quiet",
type: 'ios.app',
},
});
});
it('should add update `workspace.json` file', async () => {
const workspaceJson = readJson(tree, 'workspace.json');
const project = workspaceJson.projects['e2e-dir-my-app-e2e'];
expect(project.root).toEqual('apps/e2e-dir/my-app-e2e');
});
it('should update nx.json', async () => {
const project = readProjectConfiguration(tree, 'e2e-dir-my-app-e2e');
expect(project.tags).toEqual([]);
expect(project.implicitDependencies).toEqual(['my-dir-my-app']);
});
});
describe('with directory in name', () => {
beforeEach(async () => {
addProjectConfiguration(tree, 'my-dir-my-app', {
@ -91,8 +242,8 @@ describe('detox application generator', () => {
});
await detoxApplicationGenerator(tree, {
name: 'my-dir/my-app-e2e',
project: 'my-dir-my-app',
e2eName: 'my-dir/my-app-e2e',
appProject: 'my-dir-my-app',
linter: Linter.None,
framework: 'react-native',
});
@ -103,6 +254,143 @@ describe('detox application generator', () => {
expect(
tree.exists('apps/my-dir/my-app-e2e/src/app.spec.ts')
).toBeTruthy();
const detoxrc = tree
.read('apps/my-dir/my-app-e2e/.detoxrc.json')
.toString();
// Strip trailing commas
const detoxrcJson = JSON.parse(
detoxrc.replace(/(?<=(true|false|null|["\d}\]])\s*),(?=\s*[}\]])/g, '')
);
const appsDetoxrcJson = detoxrcJson['apps'];
expect(appsDetoxrcJson).toEqual({
'android.debug': {
binaryPath:
'../../../my-dir/my-app/android/app/build/outputs/apk/debug/app-debug.apk',
build:
'cd ../../../my-dir/my-app/android && ./gradlew assembleDebug assembleAndroidTest -DtestBuildType=debug',
type: 'android.apk',
},
'android.release': {
binaryPath:
'../../../my-dir/my-app/android/app/build/outputs/apk/release/app-release.apk',
build:
'cd ../../../my-dir/my-app/android && ./gradlew assembleRelease assembleAndroidTest -DtestBuildType=release',
type: 'android.apk',
},
'ios.debug': {
binaryPath:
'../../../my-dir/my-app/ios/build/Build/Products/Debug-iphonesimulator/MyDirMyApp.app',
build:
"cd ../../../my-dir/my-app/ios && xcodebuild -workspace MyDirMyApp.xcworkspace -scheme MyDirMyApp -configuration Debug -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 13' -derivedDataPath ./build -quiet",
type: 'ios.app',
},
'ios.release': {
binaryPath:
'../../../my-dir/my-app/ios/build/Build/Products/Release-iphonesimulator/MyDirMyApp.app',
build:
"cd ../../../my-dir/my-app/ios && xcodebuild -workspace MyDirMyApp.xcworkspace -scheme MyDirMyApp -configuration Release -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 13' -derivedDataPath ./build -quiet",
type: 'ios.app',
},
});
});
it('should add update `workspace.json` file', async () => {
const workspaceJson = readJson(tree, 'workspace.json');
const project = workspaceJson.projects['my-dir-my-app-e2e'];
expect(project.root).toEqual('apps/my-dir/my-app-e2e');
});
it('should update nx.json', async () => {
const project = readProjectConfiguration(tree, 'my-dir-my-app-e2e');
expect(project.tags).toEqual([]);
expect(project.implicitDependencies).toEqual(['my-dir-my-app']);
});
});
describe('expo', () => {
beforeEach(async () => {
addProjectConfiguration(tree, 'my-dir-my-app', {
root: 'my-dir/my-app',
});
await detoxApplicationGenerator(tree, {
e2eName: 'my-dir/my-app-e2e',
appProject: 'my-dir-my-app',
linter: Linter.None,
framework: 'expo',
});
});
it('should generate files', () => {
expect(tree.exists('apps/my-dir/my-app-e2e/.detoxrc.json')).toBeTruthy();
expect(
tree.exists('apps/my-dir/my-app-e2e/src/app.spec.ts')
).toBeTruthy();
const detoxrc = tree
.read('apps/my-dir/my-app-e2e/.detoxrc.json')
.toString();
// Strip trailing commas
const detoxrcJson = JSON.parse(
detoxrc.replace(/(?<=(true|false|null|["\d}\]])\s*),(?=\s*[}\]])/g, '')
);
const appsDetoxrcJson = detoxrcJson['apps'];
expect(appsDetoxrcJson).toEqual({
'android.debug': {
binaryPath:
'../../../my-dir/my-app/android/app/build/outputs/apk/debug/app-debug.apk',
build:
'cd ../../../my-dir/my-app/android && ./gradlew assembleDebug assembleAndroidTest -DtestBuildType=debug',
type: 'android.apk',
},
'android.eas': {
binaryPath: '../../../my-dir/my-app/dist/MyDirMyApp.apk',
build:
'npx nx run my-dir-my-app:download --platform android --output=my-dir/my-app/dist/',
type: 'ios.app',
},
'android.local': {
binaryPath: '../../../my-dir/my-app/dist/MyDirMyApp.apk',
build:
'npx nx run my-dir-my-app:build --platform android --profile preview --wait --local --no-interactive --output=my-dir/my-app/dist/',
type: 'ios.app',
},
'android.release': {
binaryPath:
'../../../my-dir/my-app/android/app/build/outputs/apk/release/app-release.apk',
build:
'cd ../../../my-dir/my-app/android && ./gradlew assembleRelease assembleAndroidTest -DtestBuildType=release',
type: 'android.apk',
},
'ios.debug': {
binaryPath:
'../../../my-dir/my-app/ios/build/Build/Products/Debug-iphonesimulator/MyDirMyApp.app',
build:
"cd ../../../my-dir/my-app/ios && xcodebuild -workspace MyDirMyApp.xcworkspace -scheme MyDirMyApp -configuration Debug -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 13' -derivedDataPath ./build -quiet",
type: 'ios.app',
},
'ios.eas': {
binaryPath: '../../../my-dir/my-app/dist/MyDirMyApp.app',
build:
'npx nx run my-dir-my-app:download --platform ios --distribution simulator --output=my-dir/my-app/dist/',
type: 'ios.app',
},
'ios.local': {
binaryPath: '../../../my-dir/my-app/dist/MyDirMyApp.app',
build:
'npx nx run my-dir-my-app:build --platform ios --profile preview --wait --local --no-interactive --output=my-dir/my-app/dist/',
type: 'ios.app',
},
'ios.release': {
binaryPath:
'../../../my-dir/my-app/ios/build/Build/Products/Release-iphonesimulator/MyDirMyApp.app',
build:
"cd ../../../my-dir/my-app/ios && xcodebuild -workspace MyDirMyApp.xcworkspace -scheme MyDirMyApp -configuration Release -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 13' -derivedDataPath ./build -quiet",
type: 'ios.app',
},
});
});
it('should add update `workspace.json` file', async () => {
@ -125,8 +413,8 @@ describe('detox application generator', () => {
it('should extend from tsconfig.base.json', async () => {
await detoxApplicationGenerator(tree, {
name: 'my-app-e2e',
project: 'my-app',
e2eName: 'my-app-e2e',
appProject: 'my-app',
linter: Linter.None,
framework: 'react-native',
});
@ -139,8 +427,8 @@ describe('detox application generator', () => {
tree.rename('tsconfig.base.json', 'tsconfig.json');
await detoxApplicationGenerator(tree, {
name: 'my-app-e2e',
project: 'my-app',
e2eName: 'my-app-e2e',
appProject: 'my-app',
linter: Linter.None,
framework: 'react-native',
});

View File

@ -4,46 +4,46 @@
"apps": {
"ios.debug": {
"type": "ios.app",
"build": "cd ../<%= appFileName %>/ios && xcodebuild -workspace <%= appClassName %>.xcworkspace -scheme <%= appClassName %> -configuration Debug -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 13' -derivedDataPath ./build -quiet",
"binaryPath": "../<%= appFileName %>/ios/build/Build/Products/Debug-iphonesimulator/<%= appClassName %>.app"
"build": "cd <%= offsetFromRoot %><%= appRoot %>/ios && xcodebuild -workspace <%= appClassName %>.xcworkspace -scheme <%= appClassName %> -configuration Debug -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 13' -derivedDataPath ./build -quiet",
"binaryPath": "<%= offsetFromRoot %><%= appRoot %>/ios/build/Build/Products/Debug-iphonesimulator/<%= appClassName %>.app"
},
"ios.release": {
"type": "ios.app",
"build": "cd ../<%= appFileName %>/ios && xcodebuild -workspace <%= appClassName %>.xcworkspace -scheme <%= appClassName %> -configuration Release -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 13' -derivedDataPath ./build -quiet",
"binaryPath": "../<%= appFileName %>/ios/build/Build/Products/Release-iphonesimulator/<%= appClassName %>.app"
"build": "cd <%= offsetFromRoot %><%= appRoot %>/ios && xcodebuild -workspace <%= appClassName %>.xcworkspace -scheme <%= appClassName %> -configuration Release -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 13' -derivedDataPath ./build -quiet",
"binaryPath": "<%= offsetFromRoot %><%= appRoot %>/ios/build/Build/Products/Release-iphonesimulator/<%= appClassName %>.app"
},
<% if (framework === 'expo') { %>
"ios.eas": {
"type": "ios.app",
"build": "<%= exec %> nx run <%= appFileName %>:download --platform ios --distribution simulator --output=<%= projectDirectory %>/<%= appFileName %>/dist/",
"binaryPath": "../<%= appFileName %>/dist/<%= appDisplayName %>.app"
"build": "<%= exec %> nx run <%= appFileName %>:download --platform ios --distribution simulator --output=<%= appRoot %>/dist/",
"binaryPath": "<%= offsetFromRoot %><%= appRoot %>/dist/<%= appExpoName %>.app"
},
"ios.local": {
"type": "ios.app",
"build": "<%= exec %> nx run <%= appFileName %>:build --platform ios --profile preview --wait --local --no-interactive --output=<%= projectDirectory %>/<%= appFileName %>/dist/",
"binaryPath": "../<%= appFileName %>/dist/<%= appDisplayName %>.app"
"build": "<%= exec %> nx run <%= appFileName %>:build --platform ios --profile preview --wait --local --no-interactive --output=<%= appRoot %>/dist/",
"binaryPath": "<%= offsetFromRoot %><%= appRoot %>/dist/<%= appExpoName %>.app"
},
<% } %>
"android.debug": {
"type": "android.apk",
"build": "cd ../<%= appFileName %>/android && ./gradlew assembleDebug assembleAndroidTest -DtestBuildType=debug",
"binaryPath": "../<%= appFileName %>/android/app/build/outputs/apk/debug/app-debug.apk"
"build": "cd <%= offsetFromRoot %><%= appRoot %>/android && ./gradlew assembleDebug assembleAndroidTest -DtestBuildType=debug",
"binaryPath": "<%= offsetFromRoot %><%= appRoot %>/android/app/build/outputs/apk/debug/app-debug.apk"
},
"android.release": {
"type": "android.apk",
"build": "cd ../<%= appFileName %>/android && ./gradlew assembleRelease assembleAndroidTest -DtestBuildType=release",
"binaryPath": "../<%= appFileName %>/android/app/build/outputs/apk/release/app-release.apk"
"build": "cd <%= offsetFromRoot %><%= appRoot %>/android && ./gradlew assembleRelease assembleAndroidTest -DtestBuildType=release",
"binaryPath": "<%= offsetFromRoot %><%= appRoot %>/android/app/build/outputs/apk/release/app-release.apk"
},
<% if (framework === 'expo') { %>
"android.eas": {
"type": "ios.app",
"build": "<%= exec %> nx run <%= appFileName %>:download --platform android --output=<%= projectDirectory %>/<%= appFileName %>/dist/",
"binaryPath": "../<%= appFileName %>/dist/<%= appDisplayName %>.apk"
"build": "<%= exec %> nx run <%= appFileName %>:download --platform android --output=<%= appRoot %>/dist/",
"binaryPath": "<%= offsetFromRoot %><%= appRoot %>/dist/<%= appExpoName %>.apk"
},
"android.local": {
"type": "ios.app",
"build": "<%= exec %> nx run <%= appFileName %>:build --platform android --profile preview --wait --local --no-interactive --output=<%= projectDirectory %>/<%= appFileName %>/dist/",
"binaryPath": "../<%= appFileName %>/dist/<%= appDisplayName %>.apk"
"build": "<%= exec %> nx run <%= appFileName %>:build --platform android --profile preview --wait --local --no-interactive --output=<%= appRoot %>/dist/",
"binaryPath": "<%= offsetFromRoot %><%= appRoot %>/dist/<%= appExpoName %>.apk"
},
<% } %>
},

View File

@ -1,5 +1,5 @@
{
"preset": "../../jest.preset",
"preset": "<%= offsetFromRoot %>jest.preset",
"testEnvironment": "./environment",
"testRunner": "jest-circus/runner",
"testTimeout": 120000,

View File

@ -6,6 +6,6 @@ describe('<%= appClassName %>', () => {
});
it('should display welcome message', async () => {
await expect(element(by.id('heading'))).toHaveText('Welcome <%= appClassName %> 👋');
await expect(element(by.id('heading'))).toHaveText('Welcome <%= appDisplayName %> 👋');
});
});

View File

@ -4,7 +4,7 @@ import { NormalizedSchema } from './normalize-options';
export function addGitIgnoreEntry(host: Tree, options: NormalizedSchema) {
if (host.exists('.gitignore')) {
let content = host.read('.gitignore', 'utf-8');
content = `${content}\n${options.projectRoot}/artifacts\n`;
content = `${content}\n${options.e2eProjectRoot}/artifacts\n`;
host.write('.gitignore', content);
} else {
logger.warn(`Couldn't find .gitignore file to update`);

View File

@ -10,14 +10,16 @@ describe('Add Linting', () => {
beforeEach(async () => {
tree = createTreeWithEmptyV1Workspace();
addProject(tree, {
name: 'my-app-e2e',
projectName: 'my-app-e2e',
projectDirectory: 'apps',
projectRoot: 'apps/my-app-e2e',
project: 'my-app',
e2eName: 'my-app-e2e',
e2eProjectName: 'my-app-e2e',
e2eProjectDirectory: 'apps',
e2eProjectRoot: 'apps/my-app-e2e',
appProject: 'my-app',
appFileName: 'my-app',
appClassName: 'MyApp',
appDisplayName: 'MyApp',
appExpoName: 'MyApp',
appRoot: 'apps/my-app',
linter: Linter.EsLint,
framework: 'react-native',
});
@ -25,14 +27,16 @@ describe('Add Linting', () => {
it('should add update `workspace.json` file properly when eslint is passed', () => {
addLinting(tree, {
name: 'my-app-e2e',
projectName: 'my-app-e2e',
projectDirectory: 'apps',
projectRoot: 'apps/my-app-e2e',
project: 'my-app',
e2eName: 'my-app-e2e',
e2eProjectName: 'my-app-e2e',
e2eProjectDirectory: 'apps',
e2eProjectRoot: 'apps/my-app-e2e',
appProject: 'my-app',
appFileName: 'my-app',
appClassName: 'MyApp',
appDisplayName: 'MyApp',
appExpoName: 'MyApp',
appRoot: 'apps/my-app',
linter: Linter.EsLint,
framework: 'react-native',
});
@ -44,14 +48,16 @@ describe('Add Linting', () => {
it('should not add lint target when "none" is passed', async () => {
addLinting(tree, {
name: 'my-app-e2e',
projectName: 'my-app-e2e',
projectDirectory: 'apps',
projectRoot: 'apps/my-app-e2e',
project: 'my-app',
e2eName: 'my-app-e2e',
e2eProjectName: 'my-app-e2e',
e2eProjectDirectory: 'apps',
e2eProjectRoot: 'apps/my-app-e2e',
appProject: 'my-app',
appFileName: 'my-app',
appClassName: 'MyApp',
appDisplayName: 'MyApp',
appExpoName: 'MyApp',
appRoot: 'apps/my-app',
linter: Linter.None,
framework: 'react-native',
});

View File

@ -16,22 +16,22 @@ export async function addLinting(host: Tree, options: NormalizedSchema) {
const lintTask = await lintProjectGenerator(host, {
linter: options.linter,
project: options.projectName,
project: options.e2eProjectName,
tsConfigPaths: [
joinPathFragments(options.projectRoot, 'tsconfig.app.json'),
joinPathFragments(options.e2eProjectRoot, 'tsconfig.app.json'),
],
eslintFilePatterns: [`${options.projectRoot}/**/*.{ts,tsx,js,jsx}`],
eslintFilePatterns: [`${options.e2eProjectRoot}/**/*.{ts,tsx,js,jsx}`],
skipFormat: true,
});
const reactEslintJson = createReactEslintJson(
options.projectRoot,
options.e2eProjectRoot,
options.setParserOptionsProject
);
updateJson(
host,
joinPathFragments(options.projectRoot, '.eslintrc.json'),
joinPathFragments(options.e2eProjectRoot, '.eslintrc.json'),
() => reactEslintJson
);

View File

@ -29,14 +29,16 @@ describe('Add Project', () => {
describe('app at root', () => {
beforeEach(() => {
addProject(tree, {
name: 'my-app-e2e',
projectName: 'my-app-e2e',
projectDirectory: 'apps',
projectRoot: 'apps/my-app-e2e',
project: 'my-app',
e2eName: 'my-app-e2e',
e2eProjectName: 'my-app-e2e',
e2eProjectDirectory: 'apps',
e2eProjectRoot: 'apps/my-app-e2e',
appProject: 'my-app',
appFileName: 'my-app',
appClassName: 'MyApp',
appDisplayName: 'MyApp',
appExpoName: 'MyApp',
appRoot: 'apps/my-app',
linter: Linter.EsLint,
framework: 'react-native',
});
@ -77,14 +79,16 @@ describe('Add Project', () => {
describe('app with directory', () => {
beforeEach(() => {
addProject(tree, {
name: 'my-dir-my-app-e2e',
projectName: 'my-dir-my-app-e2e',
projectDirectory: 'apps',
projectRoot: 'apps/my-dir/my-app-e2e',
project: 'my-dir-my-app',
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',
appClassName: 'MyApp',
appDisplayName: 'MyApp',
appExpoName: 'MyApp',
appRoot: 'apps/my-dir/my-app',
linter: Linter.EsLint,
framework: 'react-native',
});

View File

@ -12,13 +12,13 @@ import {
import { NormalizedSchema } from './normalize-options';
export function addProject(host: Tree, options: NormalizedSchema) {
addProjectConfiguration(host, options.projectName, {
root: options.projectRoot,
sourceRoot: `${options.projectRoot}/src`,
addProjectConfiguration(host, options.e2eProjectName, {
root: options.e2eProjectRoot,
sourceRoot: `${options.e2eProjectRoot}/src`,
projectType: 'application',
targets: { ...getTargets(options) },
tags: [],
implicitDependencies: options.project ? [options.project] : undefined,
implicitDependencies: [options.appProject],
});
}
@ -35,8 +35,8 @@ function getTargets(options: NormalizedSchema) {
targets['test-ios'] = {
executor: '@nrwl/detox:test',
...(options.framework === 'react-native'
? reactNativeTestTarget('ios.sim', options.name)
: expoTestTarget('ios.sim', options.name)),
? reactNativeTestTarget('ios.sim', options.e2eName)
: expoTestTarget('ios.sim', options.e2eName)),
};
targets['build-android'] = {
@ -49,8 +49,8 @@ function getTargets(options: NormalizedSchema) {
targets['test-android'] = {
executor: '@nrwl/detox:test',
...(options.framework === 'react-native'
? reactNativeTestTarget('android.emu', options.name)
: expoTestTarget('android.emu', options.name)),
? reactNativeTestTarget('android.emu', options.e2eName)
: expoTestTarget('android.emu', options.e2eName)),
};
return targets;

View File

@ -12,14 +12,16 @@ describe('Create Files', () => {
it('should generate files', () => {
createFiles(tree, {
name: 'my-app-e2e',
projectName: 'my-app-e2e',
projectDirectory: 'apps',
projectRoot: 'apps/my-app-e2e',
project: 'my-app',
e2eName: 'my-app-e2e',
e2eProjectName: 'my-app-e2e',
e2eProjectDirectory: 'apps',
e2eProjectRoot: 'apps/my-app-e2e',
appProject: 'my-app',
appFileName: 'my-app',
appClassName: 'MyApp',
appDisplayName: 'MyApp',
appExpoName: 'MyApp',
appRoot: 'apps/my-app',
linter: Linter.EsLint,
framework: 'react-native',
});

View File

@ -11,11 +11,14 @@ import { join } from 'path';
import { NormalizedSchema } from './normalize-options';
export function createFiles(host: Tree, options: NormalizedSchema) {
generateFiles(host, join(__dirname, '../files/app'), options.projectRoot, {
generateFiles(host, join(__dirname, '../files/app'), options.e2eProjectRoot, {
...options,
exec: getPackageManagerCommand(detectPackageManager(host.root)).exec,
offsetFromRoot: offsetFromRoot(options.projectRoot),
rootTsConfigPath: getRelativePathToRootTsConfig(host, options.projectRoot),
offsetFromRoot: offsetFromRoot(options.e2eProjectRoot),
rootTsConfigPath: getRelativePathToRootTsConfig(
host,
options.e2eProjectRoot
),
});
if (options.js) {
toJS(host);

View File

@ -32,17 +32,17 @@ export function expoBuildTarget(platform: 'ios.sim' | 'android.emu') {
export function reactNativeTestTarget(
platform: 'ios.sim' | 'android.emu',
name: string
e2eName: string
) {
return {
options: {
detoxConfiguration: `${platform}.debug`,
buildTarget: `${name}:build-ios`,
buildTarget: `${e2eName}:build-ios`,
},
configurations: {
production: {
detoxConfiguration: `${platform}.release`,
buildTarget: `${name}:build-ios:production`,
buildTarget: `${e2eName}:build-ios:production`,
},
},
};
@ -50,25 +50,25 @@ export function reactNativeTestTarget(
export function expoTestTarget(
platform: 'ios.sim' | 'android.emu',
name: string
e2eName: string
) {
return {
options: {
detoxConfiguration: `${platform}.eas`,
buildTarget: `${name}:build-ios`,
buildTarget: `${e2eName}:build-ios`,
},
configurations: {
local: {
detoxConfiguration: `${platform}.local`,
buildTarget: `${name}:build-ios:local`,
buildTarget: `${e2eName}:build-ios:local`,
},
bare: {
detoxConfiguration: `${platform}.debug`,
buildTarget: `${name}:build-ios:bare`,
buildTarget: `${e2eName}:build-ios:bare`,
},
production: {
detoxConfiguration: `${platform}.release`,
buildTarget: `${name}:build-ios:production`,
buildTarget: `${e2eName}:build-ios:production`,
},
},
};

View File

@ -1,6 +1,7 @@
import { addProjectConfiguration, Tree } from '@nrwl/devkit';
import { createTreeWithEmptyV1Workspace } from '@nrwl/devkit/testing';
import { Linter } from '@nrwl/linter';
import { Schema } from '../schema';
import { normalizeOptions } from './normalize-options';
@ -18,21 +19,23 @@ describe('Normalize Options', () => {
});
const schema: Schema = {
framework: 'react-native',
name: 'my-app-e2e',
project: 'my-app',
e2eName: 'my-app-e2e',
appProject: 'my-app',
linter: Linter.EsLint,
};
const options = normalizeOptions(appTree, schema);
expect(options).toEqual({
framework: 'react-native',
name: 'my-app-e2e',
projectName: 'my-app-e2e',
projectDirectory: 'apps',
projectRoot: 'apps/my-app-e2e',
project: 'my-app',
e2eName: 'my-app-e2e',
e2eProjectName: 'my-app-e2e',
e2eProjectDirectory: 'apps',
e2eProjectRoot: 'apps/my-app-e2e',
appProject: 'my-app',
appFileName: 'my-app',
appClassName: 'MyApp',
appDisplayName: 'MyApp',
appExpoName: 'MyApp',
appRoot: 'apps/my-app',
linter: Linter.EsLint,
});
});
@ -44,19 +47,21 @@ describe('Normalize Options', () => {
});
const schema: Schema = {
framework: 'react-native',
name: 'myAppE2e',
project: 'myApp',
e2eName: 'myAppE2e',
appProject: 'myApp',
};
const options = normalizeOptions(appTree, schema);
expect(options).toEqual({
appClassName: 'MyApp',
appDisplayName: 'MyApp',
appExpoName: 'MyApp',
appFileName: 'my-app',
name: 'my-app-e2e',
project: 'myApp',
projectName: 'my-app-e2e',
projectDirectory: 'apps',
projectRoot: 'apps/my-app-e2e',
appRoot: 'apps/my-app',
e2eName: 'my-app-e2e',
appProject: 'myApp',
e2eProjectName: 'my-app-e2e',
e2eProjectDirectory: 'apps',
e2eProjectRoot: 'apps/my-app-e2e',
framework: 'react-native',
});
});
@ -68,21 +73,22 @@ describe('Normalize Options', () => {
});
const schema: Schema = {
framework: 'react-native',
name: 'myAppE2e',
project: 'myApp',
displayName: 'app display name',
e2eName: 'myAppE2e',
appProject: 'myApp',
appDisplayName: 'app display name',
};
const options = normalizeOptions(appTree, schema);
expect(options).toEqual({
displayName: 'app display name',
appDisplayName: 'app display name',
appExpoName: 'appdisplayname',
appClassName: 'MyApp',
appDisplayName: 'AppDisplayName',
appFileName: 'my-app',
name: 'my-app-e2e',
project: 'myApp',
projectName: 'my-app-e2e',
projectDirectory: 'apps',
projectRoot: 'apps/my-app-e2e',
appRoot: 'apps/my-app',
e2eName: 'my-app-e2e',
appProject: 'myApp',
e2eProjectName: 'my-app-e2e',
e2eProjectDirectory: 'apps',
e2eProjectRoot: 'apps/my-app-e2e',
framework: 'react-native',
});
});
@ -94,21 +100,23 @@ describe('Normalize Options', () => {
});
const schema: Schema = {
framework: 'react-native',
name: 'my-app-e2e',
project: 'my-app',
directory: 'directory',
e2eName: 'my-app-e2e',
appProject: 'my-app',
e2eDirectory: 'directory',
};
const options = normalizeOptions(appTree, schema);
expect(options).toEqual({
project: 'my-app',
appProject: 'my-app',
appClassName: 'MyApp',
appDisplayName: 'MyApp',
appExpoName: 'MyApp',
appFileName: 'my-app',
projectDirectory: 'apps/directory',
projectRoot: 'apps/directory/my-app-e2e',
name: 'my-app-e2e',
directory: 'directory',
projectName: 'directory-my-app-e2e',
appRoot: 'apps/my-app',
e2eProjectDirectory: 'apps/directory',
e2eProjectRoot: 'apps/directory/my-app-e2e',
e2eName: 'my-app-e2e',
e2eDirectory: 'directory',
e2eProjectName: 'directory-my-app-e2e',
framework: 'react-native',
});
});
@ -120,19 +128,21 @@ describe('Normalize Options', () => {
});
const schema: Schema = {
framework: 'react-native',
name: 'directory/my-app-e2e',
project: 'my-app',
e2eName: 'directory/my-app-e2e',
appProject: 'my-app',
};
const options = normalizeOptions(appTree, schema);
expect(options).toEqual({
project: 'my-app',
appProject: 'my-app',
appClassName: 'MyApp',
appExpoName: 'MyApp',
appDisplayName: 'MyApp',
appFileName: 'my-app',
projectRoot: 'apps/directory/my-app-e2e',
projectDirectory: 'apps',
name: 'directory/my-app-e2e',
projectName: 'directory-my-app-e2e',
appRoot: 'apps/my-app',
e2eProjectRoot: 'apps/directory/my-app-e2e',
e2eProjectDirectory: 'apps',
e2eName: 'directory/my-app-e2e',
e2eProjectName: 'directory-my-app-e2e',
framework: 'react-native',
});
});

View File

@ -1,4 +1,5 @@
import {
getProjects,
getWorkspaceLayout,
joinPathFragments,
names,
@ -9,49 +10,55 @@ import { Schema } from '../schema';
export interface NormalizedSchema extends Schema {
appFileName: string; // the file name of app to be tested
appClassName: string; // the class name of app to be tested
appDisplayName: string; // the display name of the app to be tested
projectName: string; // the name of e2e project
projectDirectory: string; // the directory of e2e project
projectRoot: string; // the root path of e2e project
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.name = 'my-app-e2e' with no options.directory
* projectName = 'my-app', projectRoot = 'apps/my-app'
* if options.name = 'my-app' with options.directory = 'my-dir'
* projectName = 'my-dir-my-app', projectRoot = 'apps/my-dir/my-apps'
* 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(
host: Tree,
options: Schema
): NormalizedSchema {
const { appsDir } = getWorkspaceLayout(host);
const fileName = names(options.name).fileName;
const directoryFileName = options.directory
? names(options.directory).fileName
const e2eFileName = names(options.e2eName).fileName;
const e2eDirectoryFileName = options.e2eDirectory
? names(options.e2eDirectory).fileName
: '';
const projectName = (
directoryFileName ? `${directoryFileName}-${fileName}` : fileName
).replace(new RegExp('/', 'g'), '-');
const projectDirectory = directoryFileName
? joinPathFragments(appsDir, directoryFileName)
const e2eProjectName = (
e2eDirectoryFileName
? `${e2eDirectoryFileName}-${e2eFileName}`
: e2eFileName
).replace(/\//g, '-');
const e2eProjectDirectory = e2eDirectoryFileName
? joinPathFragments(appsDir, e2eDirectoryFileName)
: appsDir;
const projectRoot = joinPathFragments(projectDirectory, fileName);
const e2eProjectRoot = joinPathFragments(e2eProjectDirectory, e2eFileName);
const { fileName: appFileName, className: appClassName } = names(
options.project
options.appName || options.appProject
);
const project = getProjects(host).get(options.appProject);
const appRoot =
project?.root || joinPathFragments(e2eProjectDirectory, appFileName);
return {
...options,
appFileName,
appClassName,
appDisplayName: options.displayName
? names(options.displayName).className
: appClassName,
name: fileName,
projectName,
projectDirectory,
projectRoot,
appDisplayName: options.appDisplayName || appClassName,
appExpoName: options.appDisplayName?.replace(/\s/g, '') || appClassName,
appRoot,
e2eName: e2eFileName,
e2eProjectName,
e2eProjectDirectory,
e2eProjectRoot,
};
}

View File

@ -1,10 +1,11 @@
import { Linter } from '@nrwl/linter';
export interface Schema {
project: string; // name of the project app to be tested
displayName?: string; // display name of the mobile app
name: string; // name of the e2e app
directory?: string;
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
e2eName: string; // name of the e2e app
linter?: Linter;
js?: boolean;
skipFormat?: boolean;

View File

@ -4,15 +4,15 @@
"description": "Create Detox Configuration for the workspace.",
"type": "object",
"properties": {
"project": {
"appProject": {
"type": "string",
"description": "The name of the frontend project to test.",
"description": "Name of the frontend project to be tested.",
"$default": {
"$source": "projectName"
},
"x-prompt": "What is the name of the frontend project to test?"
},
"name": {
"e2eName": {
"type": "string",
"description": "Name of the E2E Project.",
"$default": {
@ -21,15 +21,23 @@
},
"x-prompt": "What name would you like to use for the E2E project?"
},
"appName": {
"type": "string",
"description": "Name of the app to be tested if different from appProject"
},
"appDisplayName": {
"type": "string",
"description": "Display name of the app to be tested if different from appProject"
},
"framework": {
"type": "string",
"description": "App framework to test",
"enum": ["react-native", "expo"],
"x-prompt": "What app framework should detox test?"
},
"directory": {
"e2eDirectory": {
"type": "string",
"description": "A directory where the project is placed."
"description": "A directory where the project is placed relative to apps directory."
},
"linter": {
"description": "The tool to use for running lint checks.",
@ -53,5 +61,5 @@
"default": false
}
},
"required": ["name", "project", "framework"]
"required": ["e2eName", "appProject", "framework"]
}

View File

@ -89,4 +89,244 @@ describe('app', () => {
expect(appTree.exists('apps/my-app/.eslintrc.json')).toBe(true);
});
describe('detox', () => {
it('should create e2e app with directory', async () => {
await expoApplicationGenerator(appTree, {
name: 'myApp',
directory: 'myDir',
linter: Linter.EsLint,
e2eTestRunner: 'detox',
js: false,
skipFormat: false,
unitTestRunner: 'none',
});
const workspaceJson = readWorkspaceConfiguration(appTree);
const projects = getProjects(appTree);
expect(projects.get('my-dir-my-app').root).toEqual('apps/my-dir/my-app');
expect(workspaceJson.defaultProject).toEqual('my-dir-my-app');
expect(
appTree.exists('apps/my-dir/my-app-e2e/.detoxrc.json')
).toBeTruthy();
const detoxrc = appTree.read(
'apps/my-dir/my-app-e2e/.detoxrc.json',
'utf-8'
);
// Strip trailing commas
const detoxrcJson = JSON.parse(
detoxrc.replace(/(?<=(true|false|null|["\d}\]])\s*),(?=\s*[}\]])/g, '')
);
expect(detoxrcJson.apps).toEqual({
'android.debug': {
binaryPath:
'../../../apps/my-dir/my-app/android/app/build/outputs/apk/debug/app-debug.apk',
build:
'cd ../../../apps/my-dir/my-app/android && ./gradlew assembleDebug assembleAndroidTest -DtestBuildType=debug',
type: 'android.apk',
},
'android.eas': {
binaryPath: '../../../apps/my-dir/my-app/dist/MyApp.apk',
build:
'npx nx run my-app:download --platform android --output=apps/my-dir/my-app/dist/',
type: 'ios.app',
},
'android.local': {
binaryPath: '../../../apps/my-dir/my-app/dist/MyApp.apk',
build:
'npx nx run my-app:build --platform android --profile preview --wait --local --no-interactive --output=apps/my-dir/my-app/dist/',
type: 'ios.app',
},
'android.release': {
binaryPath:
'../../../apps/my-dir/my-app/android/app/build/outputs/apk/release/app-release.apk',
build:
'cd ../../../apps/my-dir/my-app/android && ./gradlew assembleRelease assembleAndroidTest -DtestBuildType=release',
type: 'android.apk',
},
'ios.debug': {
binaryPath:
'../../../apps/my-dir/my-app/ios/build/Build/Products/Debug-iphonesimulator/MyApp.app',
build:
"cd ../../../apps/my-dir/my-app/ios && xcodebuild -workspace MyApp.xcworkspace -scheme MyApp -configuration Debug -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 13' -derivedDataPath ./build -quiet",
type: 'ios.app',
},
'ios.eas': {
binaryPath: '../../../apps/my-dir/my-app/dist/MyApp.app',
build:
'npx nx run my-app:download --platform ios --distribution simulator --output=apps/my-dir/my-app/dist/',
type: 'ios.app',
},
'ios.local': {
binaryPath: '../../../apps/my-dir/my-app/dist/MyApp.app',
build:
'npx nx run my-app:build --platform ios --profile preview --wait --local --no-interactive --output=apps/my-dir/my-app/dist/',
type: 'ios.app',
},
'ios.release': {
binaryPath:
'../../../apps/my-dir/my-app/ios/build/Build/Products/Release-iphonesimulator/MyApp.app',
build:
"cd ../../../apps/my-dir/my-app/ios && xcodebuild -workspace MyApp.xcworkspace -scheme MyApp -configuration Release -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 13' -derivedDataPath ./build -quiet",
type: 'ios.app',
},
});
});
it('should create e2e app without directory', async () => {
await expoApplicationGenerator(appTree, {
name: 'myApp',
linter: Linter.EsLint,
e2eTestRunner: 'detox',
js: false,
skipFormat: false,
unitTestRunner: 'none',
});
const workspaceJson = readWorkspaceConfiguration(appTree);
const projects = getProjects(appTree);
expect(projects.get('my-app').root).toEqual('apps/my-app');
expect(workspaceJson.defaultProject).toEqual('my-app');
expect(appTree.exists('apps/my-app-e2e/.detoxrc.json')).toBeTruthy();
const detoxrc = appTree.read('apps/my-app-e2e/.detoxrc.json', 'utf-8');
// Strip trailing commas
const detoxrcJson = JSON.parse(
detoxrc.replace(/(?<=(true|false|null|["\d}\]])\s*),(?=\s*[}\]])/g, '')
);
expect(detoxrcJson.apps).toEqual({
'android.debug': {
binaryPath:
'../../apps/my-app/android/app/build/outputs/apk/debug/app-debug.apk',
build:
'cd ../../apps/my-app/android && ./gradlew assembleDebug assembleAndroidTest -DtestBuildType=debug',
type: 'android.apk',
},
'android.eas': {
binaryPath: '../../apps/my-app/dist/MyApp.apk',
build:
'npx nx run my-app:download --platform android --output=apps/my-app/dist/',
type: 'ios.app',
},
'android.local': {
binaryPath: '../../apps/my-app/dist/MyApp.apk',
build:
'npx nx run my-app:build --platform android --profile preview --wait --local --no-interactive --output=apps/my-app/dist/',
type: 'ios.app',
},
'android.release': {
binaryPath:
'../../apps/my-app/android/app/build/outputs/apk/release/app-release.apk',
build:
'cd ../../apps/my-app/android && ./gradlew assembleRelease assembleAndroidTest -DtestBuildType=release',
type: 'android.apk',
},
'ios.debug': {
binaryPath:
'../../apps/my-app/ios/build/Build/Products/Debug-iphonesimulator/MyApp.app',
build:
"cd ../../apps/my-app/ios && xcodebuild -workspace MyApp.xcworkspace -scheme MyApp -configuration Debug -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 13' -derivedDataPath ./build -quiet",
type: 'ios.app',
},
'ios.eas': {
binaryPath: '../../apps/my-app/dist/MyApp.app',
build:
'npx nx run my-app:download --platform ios --distribution simulator --output=apps/my-app/dist/',
type: 'ios.app',
},
'ios.local': {
binaryPath: '../../apps/my-app/dist/MyApp.app',
build:
'npx nx run my-app:build --platform ios --profile preview --wait --local --no-interactive --output=apps/my-app/dist/',
type: 'ios.app',
},
'ios.release': {
binaryPath:
'../../apps/my-app/ios/build/Build/Products/Release-iphonesimulator/MyApp.app',
build:
"cd ../../apps/my-app/ios && xcodebuild -workspace MyApp.xcworkspace -scheme MyApp -configuration Release -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 13' -derivedDataPath ./build -quiet",
type: 'ios.app',
},
});
});
it('should create e2e app with display name', async () => {
await expoApplicationGenerator(appTree, {
name: 'myApp',
displayName: 'my app name',
linter: Linter.EsLint,
e2eTestRunner: 'detox',
js: false,
skipFormat: false,
unitTestRunner: 'none',
});
const workspaceJson = readWorkspaceConfiguration(appTree);
const projects = getProjects(appTree);
expect(projects.get('my-app').root).toEqual('apps/my-app');
expect(workspaceJson.defaultProject).toEqual('my-app');
expect(appTree.exists('apps/my-app-e2e/.detoxrc.json')).toBeTruthy();
const detoxrc = appTree.read('apps/my-app-e2e/.detoxrc.json', 'utf-8');
// Strip trailing commas
const detoxrcJson = JSON.parse(
detoxrc.replace(/(?<=(true|false|null|["\d}\]])\s*),(?=\s*[}\]])/g, '')
);
expect(detoxrcJson.apps).toEqual({
'android.debug': {
binaryPath:
'../../apps/my-app/android/app/build/outputs/apk/debug/app-debug.apk',
build:
'cd ../../apps/my-app/android && ./gradlew assembleDebug assembleAndroidTest -DtestBuildType=debug',
type: 'android.apk',
},
'android.eas': {
binaryPath: '../../apps/my-app/dist/myappname.apk',
build:
'npx nx run my-app:download --platform android --output=apps/my-app/dist/',
type: 'ios.app',
},
'android.local': {
binaryPath: '../../apps/my-app/dist/myappname.apk',
build:
'npx nx run my-app:build --platform android --profile preview --wait --local --no-interactive --output=apps/my-app/dist/',
type: 'ios.app',
},
'android.release': {
binaryPath:
'../../apps/my-app/android/app/build/outputs/apk/release/app-release.apk',
build:
'cd ../../apps/my-app/android && ./gradlew assembleRelease assembleAndroidTest -DtestBuildType=release',
type: 'android.apk',
},
'ios.debug': {
binaryPath:
'../../apps/my-app/ios/build/Build/Products/Debug-iphonesimulator/MyApp.app',
build:
"cd ../../apps/my-app/ios && xcodebuild -workspace MyApp.xcworkspace -scheme MyApp -configuration Debug -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 13' -derivedDataPath ./build -quiet",
type: 'ios.app',
},
'ios.eas': {
binaryPath: '../../apps/my-app/dist/myappname.app',
build:
'npx nx run my-app:download --platform ios --distribution simulator --output=apps/my-app/dist/',
type: 'ios.app',
},
'ios.local': {
binaryPath: '../../apps/my-app/dist/myappname.app',
build:
'npx nx run my-app:build --platform ios --profile preview --wait --local --no-interactive --output=apps/my-app/dist/',
type: 'ios.app',
},
'ios.release': {
binaryPath:
'../../apps/my-app/ios/build/Build/Products/Release-iphonesimulator/MyApp.app',
build:
"cd ../../apps/my-app/ios && xcodebuild -workspace MyApp.xcworkspace -scheme MyApp -configuration Release -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 13' -derivedDataPath ./build -quiet",
type: 'ios.app',
},
});
});
});
});

View File

@ -11,9 +11,11 @@ export async function addDetox(host: Tree, options: NormalizedSchema) {
return detoxApplicationGenerator(host, {
...options,
linter: Linter.EsLint,
name: `${options.name}-e2e`,
directory: options.directory,
project: options.projectName,
e2eName: `${options.name}-e2e`,
e2eDirectory: options.directory,
appProject: options.projectName,
appDisplayName: options.displayName,
appName: options.name,
framework: 'expo',
setParserOptionsProject: options.setParserOptionsProject,
});

View File

@ -79,4 +79,114 @@ describe('app', () => {
const tsconfig = readJson(appTree, 'apps/my-app/tsconfig.json');
expect(tsconfig.extends).toEqual('../../tsconfig.json');
});
describe('detox', () => {
it('should create e2e app with directory', async () => {
await reactNativeApplicationGenerator(appTree, {
name: 'myApp',
directory: 'myDir',
linter: Linter.EsLint,
e2eTestRunner: 'detox',
install: false,
});
const workspaceJson = readWorkspaceConfiguration(appTree);
const projects = getProjects(appTree);
expect(projects.get('my-dir-my-app').root).toEqual('apps/my-dir/my-app');
expect(workspaceJson.defaultProject).toEqual('my-dir-my-app');
expect(
appTree.exists('apps/my-dir/my-app-e2e/.detoxrc.json')
).toBeTruthy();
const detoxrc = appTree.read(
'apps/my-dir/my-app-e2e/.detoxrc.json',
'utf-8'
);
// Strip trailing commas
const detoxrcJson = JSON.parse(
detoxrc.replace(/(?<=(true|false|null|["\d}\]])\s*),(?=\s*[}\]])/g, '')
);
expect(detoxrcJson.apps).toEqual({
'android.debug': {
binaryPath:
'../../../apps/my-dir/my-app/android/app/build/outputs/apk/debug/app-debug.apk',
build:
'cd ../../../apps/my-dir/my-app/android && ./gradlew assembleDebug assembleAndroidTest -DtestBuildType=debug',
type: 'android.apk',
},
'android.release': {
binaryPath:
'../../../apps/my-dir/my-app/android/app/build/outputs/apk/release/app-release.apk',
build:
'cd ../../../apps/my-dir/my-app/android && ./gradlew assembleRelease assembleAndroidTest -DtestBuildType=release',
type: 'android.apk',
},
'ios.debug': {
binaryPath:
'../../../apps/my-dir/my-app/ios/build/Build/Products/Debug-iphonesimulator/MyApp.app',
build:
"cd ../../../apps/my-dir/my-app/ios && xcodebuild -workspace MyApp.xcworkspace -scheme MyApp -configuration Debug -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 13' -derivedDataPath ./build -quiet",
type: 'ios.app',
},
'ios.release': {
binaryPath:
'../../../apps/my-dir/my-app/ios/build/Build/Products/Release-iphonesimulator/MyApp.app',
build:
"cd ../../../apps/my-dir/my-app/ios && xcodebuild -workspace MyApp.xcworkspace -scheme MyApp -configuration Release -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 13' -derivedDataPath ./build -quiet",
type: 'ios.app',
},
});
});
it('should create e2e app without directory', async () => {
await reactNativeApplicationGenerator(appTree, {
name: 'myApp',
linter: Linter.EsLint,
e2eTestRunner: 'detox',
install: false,
});
const workspaceJson = readWorkspaceConfiguration(appTree);
const projects = getProjects(appTree);
expect(projects.get('my-app').root).toEqual('apps/my-app');
expect(workspaceJson.defaultProject).toEqual('my-app');
expect(appTree.exists('apps/my-app-e2e/.detoxrc.json')).toBeTruthy();
const detoxrc = appTree.read('apps/my-app-e2e/.detoxrc.json', 'utf-8');
// Strip trailing commas
const detoxrcJson = JSON.parse(
detoxrc.replace(/(?<=(true|false|null|["\d}\]])\s*),(?=\s*[}\]])/g, '')
);
expect(detoxrcJson.apps).toEqual({
'android.debug': {
binaryPath:
'../../apps/my-app/android/app/build/outputs/apk/debug/app-debug.apk',
build:
'cd ../../apps/my-app/android && ./gradlew assembleDebug assembleAndroidTest -DtestBuildType=debug',
type: 'android.apk',
},
'android.release': {
binaryPath:
'../../apps/my-app/android/app/build/outputs/apk/release/app-release.apk',
build:
'cd ../../apps/my-app/android && ./gradlew assembleRelease assembleAndroidTest -DtestBuildType=release',
type: 'android.apk',
},
'ios.debug': {
binaryPath:
'../../apps/my-app/ios/build/Build/Products/Debug-iphonesimulator/MyApp.app',
build:
"cd ../../apps/my-app/ios && xcodebuild -workspace MyApp.xcworkspace -scheme MyApp -configuration Debug -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 13' -derivedDataPath ./build -quiet",
type: 'ios.app',
},
'ios.release': {
binaryPath:
'../../apps/my-app/ios/build/Build/Products/Release-iphonesimulator/MyApp.app',
build:
"cd ../../apps/my-app/ios && xcodebuild -workspace MyApp.xcworkspace -scheme MyApp -configuration Release -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 13' -derivedDataPath ./build -quiet",
type: 'ios.app',
},
});
});
});
});

View File

@ -11,9 +11,11 @@ export async function addDetox(host: Tree, options: NormalizedSchema) {
return detoxApplicationGenerator(host, {
...options,
linter: Linter.EsLint,
name: `${options.name}-e2e`,
directory: options.directory,
project: options.projectName,
e2eName: `${options.name}-e2e`,
e2eDirectory: options.directory,
appProject: options.projectName,
appDisplayName: options.displayName,
appName: options.name,
framework: 'react-native',
});
}

View File

@ -8,10 +8,10 @@ import { join } from 'path';
import { Schema } from '../schema';
export interface NormalizedSchema extends Schema {
className: string;
projectName: string;
appProjectRoot: string;
lowerCaseName: string;
className: string; // app name in class name
projectName: string; // directory + app name in kebab case
appProjectRoot: string; // app directory path
lowerCaseName: string; // app name in lower case
iosProjectRoot: string;
androidProjectRoot: string;
parsedTags: string[];
@ -32,7 +32,7 @@ export function normalizeOptions(
? `${directoryName}/${fileName}`
: fileName;
const appProjectName = projectDirectory.replace(new RegExp('/', 'g'), '-');
const appProjectName = projectDirectory.replace(/\//g, '-');
const appProjectRoot = joinPathFragments(appsDir, projectDirectory);
const iosProjectRoot = join(appProjectRoot, 'ios');