Skip to content

Commit

Permalink
feat(nx-flutter): migrate to Nrwl's DevKit executors/generators API
Browse files Browse the repository at this point in the history
BREAKING CHANGE: Nx workspace v11 is now the minimum version required to use this plugin.

In fact, all builders/schematics have been rewritten into executors/generators using its new `@nrwl/devkit` API.
  • Loading branch information
tinesoft committed Apr 2, 2021
1 parent 5bc0290 commit 8c72ed5
Show file tree
Hide file tree
Showing 22 changed files with 376 additions and 667 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ import {
runNxCommandAsync,
uniq,
} from '@nrwl/nx-plugin/testing';
import * as inquirer from 'inquirer';

jest.mock('inquirer'); // we mock 'inquirer' to bypass the interactive prompt
import * as inquirer from 'inquirer';

describe('nx-flutter e2e', () => {
xdescribe('nx-flutter e2e', () => {

beforeEach(() => {
jest.spyOn(inquirer, 'prompt').mockResolvedValue({
Expand All @@ -19,20 +19,18 @@ describe('nx-flutter e2e', () => {
});
});

afterEach(() =>
(inquirer.prompt as jest.MockedFunction<
typeof inquirer.prompt
>).mockRestore()
);
afterEach(() => {
jest.resetAllMocks();
});

it('should create nx-flutter project with default options', async (done) => {
const appName = uniq('nx-flutter');
ensureNxProject('@nxrocks/nx-flutter', 'dist/packages/nx-flutter');
await runNxCommandAsync(`generate @nxrocks/nx-flutter:create ${appName} --interactive=false`);

const builders = [
const executors = [
{ name: 'analyze', output: `Analyzing ${appName}` },

//{ name: 'assemble', output: `Assembling ${appName}` },
//{ name: 'attach', output: `Attaching ${appName}` },

Expand Down Expand Up @@ -64,16 +62,16 @@ describe('nx-flutter e2e', () => {
{ name: 'test', output: `All tests passed!` },
];

for(const builder of builders){
const result = await runNxCommandAsync(`run ${appName}:${builder.name}`);
expect(result.stdout).toContain(builder.output);
for (const executor of executors) {
const result = await runNxCommandAsync(`run ${appName}:${executor.name}`);
expect(result.stdout).toContain(executor.output);
}

expect(() =>
checkFilesExist(`apps/${appName}/pubspec.yaml`)
).not.toThrow();
done();
}, 300000);
}, 180000);

it('should create nx-flutter project with given options', async (done) => {
const appName = uniq('nx-flutter');
Expand All @@ -89,29 +87,29 @@ describe('nx-flutter e2e', () => {
ensureNxProject('@nxrocks/nx-flutter', 'dist/packages/nx-flutter');
await runNxCommandAsync(`generate @nxrocks/nx-flutter:create ${appName} --interactive=false --org=${org} --description="${description}" --androidLanguage=${androidLanguage} --iosLanguage=${iosLanguage} --template=${template} --platforms="${platforms}" --pub=${pub} --offline=${offline} `);

const builders = [
const executors = [

{ name: 'clean', output: `Deleting flutter_export_environment.sh...` },
{ name: 'format', output: `Done in ` },
{ name: 'test', output: `All tests passed!` },
];

for(const builder of builders){
const result = await runNxCommandAsync(`run ${appName}:${builder.name}`);
expect(result.stdout).toContain(builder.output);
for (const executor of executors) {
const result = await runNxCommandAsync(`run ${appName}:${executor.name}`);
expect(result.stdout).toContain(executor.output);
}

expect(() =>
checkFilesExist(`apps/${appName}/pubspec.yaml`,
`apps/${appName}/android/build.gradle`,
`apps/${appName}/ios/Runner.xcodeproj`,
`apps/${appName}/android/app/src/main/java/com/tinesoft/${appName.replace('-','_')}/MainActivity.java`
)
`apps/${appName}/android/build.gradle`,
`apps/${appName}/ios/Runner.xcodeproj`,
`apps/${appName}/android/app/src/main/java/com/tinesoft/${appName.replace('-', '_')}/MainActivity.java`
)
).not.toThrow();
done();
}, 600000);
}, 180000);

xdescribe('--directory', () => {
describe('--directory', () => {
it('should create src in the specified directory', async (done) => {
const appName = uniq('nx-flutter');
ensureNxProject('@nxrocks/nx-flutter', 'dist/packages/nx-flutter');
Expand Down
9 changes: 8 additions & 1 deletion e2e/nx-flutter-e2e/tsconfig.spec.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,12 @@
"module": "commonjs",
"types": ["jest", "node"]
},
"include": ["**/*.spec.ts", "**/*.d.ts"]
"include": [
"**/*.test.ts",
"**/*.spec.ts",
"**/*.spec.tsx",
"**/*.spec.js",
"**/*.spec.jsx",
"**/*.d.ts"
]
}
5 changes: 0 additions & 5 deletions packages/nx-flutter/builders.json

This file was deleted.

13 changes: 0 additions & 13 deletions packages/nx-flutter/collection.json

This file was deleted.

4 changes: 4 additions & 0 deletions packages/nx-flutter/executors.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"executors": {
}
}
12 changes: 12 additions & 0 deletions packages/nx-flutter/generators.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"name": "nx-flutter",
"version": "0.0.1",
"generators": {
"application": {
"factory": "./src/generators/application/generator",
"schema": "./src/generators/application/schema.json",
"description": "Generator to generate the application",
"aliases": ["app", "create"]
}
}
}
6 changes: 4 additions & 2 deletions packages/nx-flutter/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
"access": "public"
},
"main": "src/index.js",
"schematics": "./collection.json",
"builders": "./builders.json",
"generators": "./generators.json",
"executors": "./executors.json",
"license": "MIT",
"author": "Tine Kondo ",
"repository": {
Expand All @@ -28,6 +28,8 @@
"dart"
],
"dependencies": {
"@nrwl/workspace": "*",
"@nrwl/devkit": "*",
"inquirer": "^7.3.3"
}
}
171 changes: 171 additions & 0 deletions packages/nx-flutter/src/generators/application/generator.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
import { Tree, logger, readProjectConfiguration } from '@nrwl/devkit';
import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing';

import each from 'jest-each';

import { applicationGenerator } from './generator';
import { ApplicationGeneratorOptions } from './schema';

jest.mock('child_process'); // we need to mock 'execSync' so that it doesn't really run 'flutter' (reserved to e2e testing) (see __mocks__/child_process.js)

jest.mock('inquirer'); // we mock 'inquirer' to bypass the interactive prompt
import * as inquirer from 'inquirer';


const appCommands = [
{ key: 'assemble', value: 'assemble' },
{ key: 'attach', value: 'attach' },
{ key: 'drive', value: 'drive' },
{ key: 'genL10n', value: 'gen-l10n' },
{ key: 'install', value: 'install' },
{ key: 'run', value: 'run' },
];

const pluginOrModOnlyCommands = [
{ key: 'buildAar', value: 'build aar' },
];

const androidOnlyCommands = [
{ key: 'buildAar', value: 'build aar' },
{ key: 'buildApk', value: 'build apk' },
{ key: 'buildAppbundle', value: 'build appbundle' },
{ key: 'buildBundle', value: 'build bundle' },
];

const iOsOnlyCommands = [
{ key: 'buildIos', value: 'build ios' },
{ key: 'buildIosFramework', value: 'build ios-framework' },
{ key: 'buildIpa', value: 'build ipa' },
];

describe('application generator', () => {
let tree: Tree;
const options: ApplicationGeneratorOptions = {
name: 'testapp',
template: 'app',
platforms: ['android', 'ios', 'web', 'linux', 'windows', 'macos'],
interactive: true
};


beforeEach(() => {
tree = createTreeWithEmptyWorkspace();

jest.spyOn(inquirer, 'prompt').mockResolvedValue({
platforms: options.platforms,
androidLanguage: 'kotlin',
iosLanguage: 'swift'
});
jest.spyOn(logger, 'info');
});

afterEach(() => {
jest.resetAllMocks();
}
);

it('should update workspace.json', async () => {

await applicationGenerator(tree, options);
const project = readProjectConfiguration(tree, options.name);
expect(project.root).toBe(`apps/${options.name}`);

const commonCommands = [
{ key: 'analyze', value: 'analyze' },
{ key: 'clean', value: 'clean' },
{ key: 'format', value: `format ${project.root}/*` },
{ key: 'test', value: 'test' },
];

const commands = [...commonCommands, ...appCommands, ...pluginOrModOnlyCommands, ...androidOnlyCommands, ...iOsOnlyCommands];
commands.forEach(e => {
expect(project.targets[e.key].executor).toBe('@nrwl/workspace:run-commands');
expect(project.targets[e.key].options.command).toBe(`flutter ${e.value}`);
});
});

each`
template | shouldPromptTempate
${'app'} | ${true}
${'plugin'} | ${true}
${'package'}| ${false}
${'module'} | ${false}
`.it('should prompt user to select "platforms" when generating "$template": $shouldPromptTempate', async ({ template, shouldPromptTempate }) => {

await applicationGenerator(tree, { ...options, template: template });

expect(inquirer.prompt).toHaveBeenCalledWith(
expect.arrayContaining([
expect.objectContaining({
when: shouldPromptTempate,
name: 'platforms',
type: 'checkbox',
choices: expect.arrayContaining([
{
value: "android",
name: "Android platform",
checked: true,
},
{
value: "ios",
name: "iOS platform",
checked: true,
},
{
value: "linux",
name: "Linux platform",
checked: true,
},
{
value: "windows",
name: "Windows platform",
checked: true,
},
{
value: "macos",
name: "MacOS platform",
checked: true,
},
{
value: "web",
name: "Web platform",
checked: true,
}
]),
}),
expect.objectContaining({
name: 'androidLanguage',
type: 'list',
default: 'kotlin',
choices: expect.arrayContaining([
{
value: "java",
name: "Java"
},
{
value: "kotlin",
name: "Kotlin"
}
]),
message: "Which Android language would you like to use?",
}),
expect.objectContaining({
name: 'iosLanguage',
type: 'list',
default: 'swift',
choices: expect.arrayContaining([
{
value: "objc",
name: "Objective-C"
},
{
value: "swift",
name: "Swift"
}
]),
message: "Which iOS language would you like to use?",
})
])
);
});
});
Loading

0 comments on commit 8c72ed5

Please sign in to comment.