chore(ng-schematics): refactor away user-side complexity (#10750)

This commit is contained in:
Nikolay Vitkov 2023-08-18 11:09:26 +02:00 committed by GitHub
parent de719dbb86
commit 35dc2d8840
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 294 additions and 309 deletions

View File

@ -14,7 +14,7 @@ ng add @puppeteer/ng-schematics
Or you can use the same command followed by the [options](#options) below.
Currently, this schematic supports the following test frameworks:
Currently, this schematic supports the following test runners:
- [**Jasmine**](https://jasmine.github.io/)
- [**Jest**](https://jestjs.io/)
@ -31,12 +31,9 @@ ng e2e
When adding schematics to your project you can to provide following options:
| Option | Description | Value | Required |
| -------------------- | ----------------------------------------------------------------------------------------------------------------------- | ------------------------------------------ | -------- |
| `--isDefaultTester` | When true, replaces default `ng e2e` command. | `boolean` | `true` |
| `--exportConfig` | When true, creates an empty [Puppeteer configuration](https://pptr.dev/guides/configuration) file. (`.puppeteerrc.cjs`) | `boolean` | `true` |
| `--testingFramework` | The testing framework to install along side Puppeteer. | `"jasmine"`, `"jest"`, `"mocha"`, `"node"` | `true` |
| `--port` | The port to spawn server for E2E. If default is used `ng serve` and `ng e2e` will not run side-by-side. | `number` | `4200` |
| Option | Description | Value | Required |
| -------------- | ------------------------------------------------------ | ------------------------------------------ | -------- |
| `--testRunner` | The testing framework to install along side Puppeteer. | `"jasmine"`, `"jest"`, `"mocha"`, `"node"` | `true` |
## Creating a single test file
@ -59,7 +56,7 @@ Update either `e2e` or `puppeteer` (depending on the initial setup) to:
"options": {
"commands": [...],
"devServerTarget": "sandbox:serve",
"testingFramework": "<TestingFramework>",
"testRunner": "<TestRunner>",
"port": 8080
},
...

View File

@ -14,7 +14,7 @@ ng add @puppeteer/ng-schematics
Or you can use the same command followed by the [options](#options) below.
Currently, this schematic supports the following test frameworks:
Currently, this schematic supports the following test runners:
- [**Jasmine**](https://jasmine.github.io/)
- [**Jest**](https://jestjs.io/)
@ -31,12 +31,9 @@ ng e2e
When adding schematics to your project you can to provide following options:
| Option | Description | Value | Required |
| -------------------- | ----------------------------------------------------------------------------------------------------------------------- | ------------------------------------------ | -------- |
| `--isDefaultTester` | When true, replaces default `ng e2e` command. | `boolean` | `true` |
| `--exportConfig` | When true, creates an empty [Puppeteer configuration](https://pptr.dev/guides/configuration) file. (`.puppeteerrc.cjs`) | `boolean` | `true` |
| `--testingFramework` | The testing framework to install along side Puppeteer. | `"jasmine"`, `"jest"`, `"mocha"`, `"node"` | `true` |
| `--port` | The port to spawn server for E2E. If default is used `ng serve` and `ng e2e` will not run side-by-side. | `number` | `4200` |
| Option | Description | Value | Required |
| -------------- | ------------------------------------------------------ | ------------------------------------------ | -------- |
| `--testRunner` | The testing framework to install along side Puppeteer. | `"jasmine"`, `"jest"`, `"mocha"`, `"node"` | `true` |
## Creating a single test file
@ -59,7 +56,7 @@ Update either `e2e` or `puppeteer` (depending on the initial setup) to:
"options": {
"commands": [...],
"devServerTarget": "sandbox:serve",
"testingFramework": "<TestingFramework>",
"testRunner": "<TestRunner>",
"port": 8080
},
...

View File

@ -9,6 +9,8 @@ import {
} from '@angular-devkit/architect';
import {JsonObject} from '@angular-devkit/core';
import {TestRunner} from '../../schematics/utils/types.js';
import {PuppeteerBuilderOptions} from './types.js';
const terminalStyles = {
@ -39,13 +41,35 @@ function getExecutable(command: string[]) {
};
}
function updateExecutablePath(command: string, root?: string) {
let path = 'node_modules/.bin/';
if (root && root !== '' && command === TestRunner.Node) {
const nested = root
.split('/')
.map(() => {
return '../';
})
.join('');
path = `${nested}${path}${command}`;
} else {
path = `./${path}${command}`;
}
return path;
}
async function executeCommand(context: BuilderContext, command: string[]) {
let project: JsonObject;
if (context.target) {
project = await context.getProjectMetadata(context.target.project);
command[0] = updateExecutablePath(command[0]!, String(project['root']));
}
await new Promise(async (resolve, reject) => {
context.logger.debug(`Trying to execute command - ${command.join(' ')}.`);
const {executable, args, error} = getExecutable(command);
let path = context.workspaceRoot;
if (context.target) {
const project = await context.getProjectMetadata(context.target.project);
path = `${path}/${project['root']}`;
}
@ -123,8 +147,13 @@ async function executeE2ETest(
try {
server = await startServer(options, context);
const commands = options.commands;
message('\n Building tests 🛠️ ... \n', context);
await executeCommand(context, [`tsc`, '-p', 'e2e/tsconfig.json']);
message('\n Running tests 🧪 ... \n', context);
for (const command of options.commands) {
for (const command of commands) {
await executeCommand(context, command);
}

View File

@ -6,10 +6,15 @@
"factory": "./ng-add/index#ngAdd",
"schema": "./ng-add/schema.json"
},
"test": {
"e2e": {
"description": "Create a single test file",
"factory": "./test/index#test",
"schema": "./test/schema.json"
"factory": "./e2e/index#e2e",
"schema": "./e2e/schema.json"
},
"config": {
"description": "Eject Puppeteer config file",
"factory": "./config/index#config",
"schema": "./config/schema.json"
}
}
}

View File

@ -1,4 +1,4 @@
/**
* @type {import("puppeteer").Configuration}
*/
module.exports = {};
export {};

View File

@ -0,0 +1,44 @@
/**
* Copyright 2022 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {chain, Rule, SchematicContext, Tree} from '@angular-devkit/schematics';
import {addFilesSingle} from '../utils/files.js';
import {TestRunner, AngularProject} from '../utils/types.js';
// You don't have to export the function as default. You can also have more than one rule
// factory per file.
export function config(): Rule {
return (tree: Tree, context: SchematicContext) => {
return chain([addPuppeteerConfig()])(tree, context);
};
}
function addPuppeteerConfig(): Rule {
return (tree: Tree, context: SchematicContext) => {
context.logger.debug('Adding Puppeteer config file.');
return addFilesSingle(tree, context, '', {root: ''} as AngularProject, {
// No-op here to fill types
options: {
testRunner: TestRunner.Jasmine,
port: 4200,
},
applyPath: './files',
relativeToWorkspacePath: `/`,
});
};
}

View File

@ -0,0 +1,8 @@
{
"$schema": "http://json-schema.org/schema",
"$id": "Puppeteer",
"title": "Puppeteer Config Schema",
"type": "object",
"properties": {},
"required": []
}

View File

@ -1,13 +1,17 @@
<% if(testingFramework == 'node') { %>
<% if(testRunner == 'node') { %>
import * as assert from 'assert';
import {describe, it} from 'node:test';
<% } %><% if(testingFramework == 'mocha') { %>
<% } %><% if(testRunner == 'mocha') { %>
import * as assert from 'assert';
<% } %>
import {setupBrowserHooks, getBrowserState} from './utils';
describe('<%= classify(name) %>', function () {
<% if(route) { %>
setupBrowserHooks('<%= route %>');
<% } else { %>
setupBrowserHooks();
<% } %>
it('', async function () {
const {page} = getBrowserState();
});

View File

@ -25,19 +25,19 @@ import {
import {addCommonFiles} from '../utils/files.js';
import {getAngularConfig} from '../utils/json.js';
import {
TestingFramework,
TestRunner,
SchematicsSpec,
SchematicsOptions,
AngularProject,
PuppeteerSchematicsConfig,
} from '../utils/types.js';
// You don't have to export the function as default. You can also have more than one rule
// factory per file.
export function test(userArgs: Record<string, string>): Rule {
export function e2e(userArgs: Record<string, string>): Rule {
const options = parseUserTestArgs(userArgs);
return (tree: Tree, context: SchematicContext) => {
return chain([addSpecFile(options)])(tree, context);
return chain([addE2EFile(options)])(tree, context);
};
}
@ -51,14 +51,19 @@ function parseUserTestArgs(userArgs: Record<string, string>): SchematicsSpec {
if ('n' in userArgs) {
options['name'] = userArgs['n'];
}
if ('r' in userArgs) {
options['route'] = userArgs['n'];
}
return options as SchematicsSpec;
}
function findTestingOption<Property extends keyof SchematicsOptions>(
function findTestingOption<
Property extends keyof PuppeteerSchematicsConfig['options'],
>(
[name, project]: [string, AngularProject | undefined],
property: Property
): SchematicsOptions[Property] {
): PuppeteerSchematicsConfig['options'][Property] {
if (!project) {
throw new Error(`Project "${name}" not found.`);
}
@ -76,7 +81,7 @@ function findTestingOption<Property extends keyof SchematicsOptions>(
throw new Error(`Can't find property "${property}" for project "${name}".`);
}
function addSpecFile(options: SchematicsSpec): Rule {
function addE2EFile(options: SchematicsSpec): Rule {
return async (tree: Tree, context: SchematicContext) => {
context.logger.debug('Adding Spec file.');
@ -96,10 +101,7 @@ function addSpecFile(options: SchematicsSpec): Rule {
);
}
const testingFramework = findTestingOption(
foundProject,
'testingFramework'
);
const testRunner = findTestingOption(foundProject, 'testRunner');
const port = findTestingOption(foundProject, 'port');
context.logger.debug('Creating Spec file.');
@ -111,10 +113,11 @@ function addSpecFile(options: SchematicsSpec): Rule {
{
options: {
name: options.name,
testingFramework,
route: options.route,
testRunner,
// Node test runner does not support glob patterns
// It looks for files `*.test.js`
ext: testingFramework === TestingFramework.Node ? 'test' : 'e2e',
ext: testRunner === TestRunner.Node ? 'test' : 'e2e',
port,
},
}

View File

@ -1,7 +1,7 @@
{
"$schema": "http://json-schema.org/schema",
"$id": "Puppeteer",
"title": "Puppeteer Spec Schema",
"title": "Puppeteer E2E Schema",
"type": "object",
"properties": {
"name": {
@ -20,6 +20,14 @@
"index": 1
},
"alias": "p"
},
"route": {
"type": "string",
"$default": {
"$source": "argv",
"index": 1
},
"alias": "r"
}
},
"required": []

View File

@ -1,7 +1,7 @@
<% if(testingFramework == 'node') { %>
<% if(testRunner == 'node') { %>
import * as assert from 'assert';
import {describe, it} from 'node:test';
<% } %><% if(testingFramework == 'mocha') { %>
<% } %><% if(testRunner == 'mocha') { %>
import * as assert from 'assert';
<% } %>
import {setupBrowserHooks, getBrowserState} from './utils';
@ -12,9 +12,9 @@ describe('App test', function () {
const {page} = getBrowserState();
const element = await page.waitForSelector('text/<%= project %> app is running!');
<% if(testingFramework == 'jasmine' || testingFramework == 'jest') { %>
<% if(testRunner == 'jasmine' || testRunner == 'jest') { %>
expect(element).not.toBeNull();
<% } %><% if(testingFramework == 'mocha' || testingFramework == 'node') { %>
<% } %><% if(testRunner == 'mocha' || testRunner == 'node') { %>
assert.ok(element);
<% } %>
});

View File

@ -1,4 +1,4 @@
<% if(testingFramework == 'node') { %>
<% if(testRunner == 'node') { %>
import {before, beforeEach, after, afterEach} from 'node:test';
<% } %>
import * as puppeteer from 'puppeteer';
@ -7,33 +7,35 @@ const baseUrl = '<%= baseUrl %>';
let browser: puppeteer.Browser;
let page: puppeteer.Page;
export function setupBrowserHooks(): void {
<% if(testingFramework == 'jasmine' || testingFramework == 'jest') { %>
export function setupBrowserHooks(path = '/'): void {
<% if(testRunner == 'jasmine' || testRunner == 'jest') { %>
beforeAll(async () => {
browser = await puppeteer.launch({
headless: 'new'
});
});
<% } %><% if(testingFramework == 'mocha' || testingFramework == 'node') { %>
<% } %><% if(testRunner == 'mocha' || testRunner == 'node') { %>
before(async () => {
browser = await puppeteer.launch();
browser = await puppeteer.launch({
headless: 'new'
});
});
<% } %>
beforeEach(async () => {
page = await browser.newPage();
await page.goto(baseUrl);
await page.goto(`${baseUrl}${path}`);
});
afterEach(async () => {
await page.close();
});
<% if(testingFramework == 'jasmine' || testingFramework == 'jest') { %>
<% if(testRunner == 'jasmine' || testRunner == 'jest') { %>
afterAll(async () => {
await browser.close();
});
<% } %><% if(testingFramework == 'mocha' || testingFramework == 'node') { %>
<% } %><% if(testRunner == 'mocha' || testRunner == 'node') { %>
after(async () => {
await browser.close();
});

View File

@ -1,11 +1,10 @@
{
"extends": "<%= tsConfigPath %>",
"compilerOptions": {<% if(testingFramework == 'jest') { %>
"esModuleInterop": true,<% } %><% if(testingFramework == 'node') { %>
"compilerOptions": {
"module": "CommonJS",
"rootDir": "tests/",
"outDir": "build/",<% } %>
"types": ["<%= testingFramework %>"]
"outDir": "build/",
"types": ["<%= testRunner %>"]
},
"include": ["tests/**/*.ts"]
}

View File

@ -1,4 +0,0 @@
require('@babel/register')({
extensions: ['.js', '.ts'],
presets: ['@babel/preset-env', '@babel/preset-typescript'],
});

View File

@ -1,8 +1,9 @@
{
"spec_dir": "e2e",
"spec_files": ["**/*[eE]2[eE].ts"],
"helpers": ["helpers/babel.js", "helpers/**/*.{js|ts}"],
"spec_files": ["**/*[eE]2[eE].js"],
"helpers": ["helpers/**/*.?(m)js"],
"env": {
"failSpecWithNoExpectations": true,
"stopSpecOnExpectationFailure": false,
"random": true
}

View File

@ -3,9 +3,8 @@
* https://jestjs.io/docs/configuration
*/
/** @type {import('ts-jest').JestConfigWithTsJest} */
/** @type {import('jest').Config} */
module.exports = {
testMatch: ['<rootDir>/tests/**/?(*.)+(e2e).[tj]s?(x)'],
preset: 'ts-jest',
testMatch: ['<rootDir>/tests/**/?(*.)+(e2e).js?(x)'],
testEnvironment: 'node',
};

View File

@ -1,4 +1,3 @@
module.exports = {
file: ['e2e/babel.js'],
spec: './e2e/tests/**/*.e2e.ts',
spec: './e2e/tests/**/*.e2e.js',
};

View File

@ -1,4 +0,0 @@
require('@babel/register')({
extensions: ['.js', '.ts'],
presets: ['@babel/preset-env', '@babel/preset-typescript'],
});

View File

@ -21,9 +21,9 @@ import {concatMap, map, scan} from 'rxjs/operators';
import {
addCommonFiles as addCommonFilesHelper,
addFilesSingle,
addFrameworkFiles,
getNgCommandName,
hasE2ETester,
} from '../utils/files.js';
import {getAngularConfig} from '../utils/json.js';
import {
@ -35,55 +35,24 @@ import {
type NodePackage,
updateAngularJsonScripts,
} from '../utils/packages.js';
import {
TestingFramework,
type SchematicsOptions,
AngularProject,
} from '../utils/types.js';
import {TestRunner, type SchematicsOptions} from '../utils/types.js';
const DEFAULT_PORT = 4200;
// You don't have to export the function as default. You can also have more than one rule
// factory per file.
export function ngAdd(userArgs: Record<string, string>): Rule {
const options = parseUserAddArgs(userArgs);
export function ngAdd(options: SchematicsOptions): Rule {
return (tree: Tree, context: SchematicContext) => {
return chain([
addDependencies(options),
addPuppeteerConfig(options),
addCommonFiles(options),
addOtherFiles(options),
updateScripts(options),
updateScripts(),
updateAngularConfig(options),
])(tree, context);
};
}
function parseUserAddArgs(userArgs: Record<string, string>): SchematicsOptions {
const options: Partial<SchematicsOptions> = {
...userArgs,
};
if ('p' in userArgs) {
options['port'] = Number(userArgs['p']);
}
if ('t' in userArgs) {
options['testingFramework'] = userArgs['t'] as TestingFramework;
}
if ('c' in userArgs) {
options['exportConfig'] =
typeof userArgs['c'] === 'string'
? userArgs['c'] === 'true'
: userArgs['c'];
}
if ('d' in userArgs) {
options['isDefaultTester'] =
typeof userArgs['d'] === 'string'
? userArgs['d'] === 'true'
: userArgs['d'];
}
return options as SchematicsOptions;
}
function addDependencies(options: SchematicsOptions): Rule {
return (tree: Tree, context: SchematicContext) => {
context.logger.debug('Adding dependencies to "package.json"');
@ -108,15 +77,15 @@ function addDependencies(options: SchematicsOptions): Rule {
};
}
function updateScripts(options: SchematicsOptions): Rule {
function updateScripts(): Rule {
return (tree: Tree, context: SchematicContext): Tree => {
context.logger.debug('Updating "package.json" scripts');
const angularJson = getAngularConfig(tree);
const projects = Object.keys(angularJson['projects']);
const {projects} = getAngularConfig(tree);
const projectsKeys = Object.keys(projects);
if (projects.length === 1) {
const name = getNgCommandName(options);
const prefix = options.isDefaultTester ? '' : `run ${projects[0]}:`;
if (projectsKeys.length === 1) {
const name = getNgCommandName(projects);
const prefix = hasE2ETester(projects) ? `run ${projectsKeys[0]}:` : '';
return addPackageJsonScripts(tree, [
{
name,
@ -128,22 +97,6 @@ function updateScripts(options: SchematicsOptions): Rule {
};
}
function addPuppeteerConfig(options: SchematicsOptions): Rule {
return (tree: Tree, context: SchematicContext) => {
context.logger.debug('Adding Puppeteer config file.');
if (options.exportConfig) {
return addFilesSingle(tree, context, '', {root: ''} as AngularProject, {
options: options,
applyPath: './files/base',
relativeToWorkspacePath: `/`,
});
}
return tree;
};
}
function addCommonFiles(options: SchematicsOptions): Rule {
return (tree: Tree, context: SchematicContext) => {
context.logger.debug('Adding Puppeteer base files.');
@ -152,8 +105,8 @@ function addCommonFiles(options: SchematicsOptions): Rule {
return addCommonFilesHelper(tree, context, projects, {
options: {
...options,
ext:
options.testingFramework === TestingFramework.Node ? 'test' : 'e2e',
port: DEFAULT_PORT,
ext: options.testRunner === TestRunner.Node ? 'test' : 'e2e',
},
});
};
@ -165,7 +118,10 @@ function addOtherFiles(options: SchematicsOptions): Rule {
const {projects} = getAngularConfig(tree);
return addFrameworkFiles(tree, context, projects, {
options,
options: {
...options,
port: DEFAULT_PORT,
},
});
};
}

View File

@ -4,25 +4,13 @@
"title": "Puppeteer Install Schema",
"type": "object",
"properties": {
"isDefaultTester": {
"type": "boolean",
"default": true,
"alias": "d",
"x-prompt": "Use Puppeteer as default `ng e2e` command?"
},
"exportConfig": {
"type": "boolean",
"default": false,
"alias": "c",
"x-prompt": "Export default Puppeteer config file?"
},
"testingFramework": {
"testRunner": {
"type": "string",
"enum": ["jasmine", "jest", "mocha", "node"],
"default": "jasmine",
"alias": "t",
"x-prompt": {
"message": "With what Testing Library do you wish to integrate?",
"message": "Which test runners do you wish to use?",
"type": "list",
"items": [
{
@ -43,12 +31,6 @@
}
]
}
},
"port": {
"type": ["number"],
"default": 4200,
"alias": "p",
"x-prompt": "On which port to spawn test server on?"
}
},
"required": []

View File

@ -28,15 +28,16 @@ import {
url,
} from '@angular-devkit/schematics';
import {AngularProject, SchematicsOptions, TestingFramework} from './types.js';
import {AngularProject, SchematicsOptions, TestRunner} from './types.js';
export interface FilesOptions {
options: {
testingFramework: TestingFramework;
testRunner: TestRunner;
port: number;
name?: string;
exportConfig?: boolean;
ext?: string;
route?: string;
};
applyPath: string;
relativeToWorkspacePath: string;
@ -137,50 +138,41 @@ export function addFrameworkFiles(
projects: Record<string, AngularProject>,
filesOptions: Omit<FilesOptions, 'applyPath' | 'relativeToWorkspacePath'>
): any {
const testingFramework = filesOptions.options.testingFramework;
const testRunner = filesOptions.options.testRunner;
const options: FilesOptions = {
...filesOptions,
applyPath: `./files/${testingFramework}`,
applyPath: `./files/${testRunner}`,
relativeToWorkspacePath: `/`,
};
return addFilesToProjects(tree, context, projects, options);
}
export function getScriptFromOptions(
options: SchematicsOptions,
root?: string
): string[][] {
let path = 'node_modules/.bin';
if (root && root !== '') {
const nested = root
.split('/')
.map(() => {
return '../';
})
.join('');
path = `${nested}${path}`;
} else {
path = `./${path}`;
}
switch (options.testingFramework) {
case TestingFramework.Jasmine:
return [[`${path}/jasmine`, '--config=./e2e/support/jasmine.json']];
case TestingFramework.Jest:
return [[`${path}/jest`, '-c', 'e2e/jest.config.js']];
case TestingFramework.Mocha:
return [[`${path}/mocha`, '--config=./e2e/.mocharc.js']];
case TestingFramework.Node:
return [
[`${path}/tsc`, '-p', 'e2e/tsconfig.json'],
['node', '--test', '--test-reporter', 'spec', 'e2e/build/'],
];
export function getScriptFromOptions(options: SchematicsOptions): string[][] {
switch (options.testRunner) {
case TestRunner.Jasmine:
return [[`jasmine`, '--config=./e2e/support/jasmine.json']];
case TestRunner.Jest:
return [[`jest`, '-c', 'e2e/jest.config.js']];
case TestRunner.Mocha:
return [[`mocha`, '--config=./e2e/.mocharc.js']];
case TestRunner.Node:
return [['node', '--test', '--test-reporter', 'spec', 'e2e/build/']];
}
}
export function getNgCommandName(options: SchematicsOptions): string {
if (options.isDefaultTester) {
export function hasE2ETester(
projects: Record<string, AngularProject>
): boolean {
return Object.values(projects).some((project: AngularProject) => {
return Boolean(project.architect?.e2e);
});
}
export function getNgCommandName(
projects: Record<string, AngularProject>
): string {
if (!hasE2ETester(projects)) {
return 'e2e';
}
return 'puppeteer';

View File

@ -24,7 +24,7 @@ import {
getJsonFileAsObject,
getObjectAsJson,
} from './json.js';
import {SchematicsOptions, TestingFramework} from './types.js';
import {SchematicsOptions, TestRunner} from './types.js';
export interface NodePackage {
name: string;
version: string;
@ -115,24 +115,18 @@ export function getDependenciesFromOptions(
options: SchematicsOptions
): string[] {
const dependencies = ['puppeteer'];
const babelPackages = [
'@babel/core',
'@babel/register',
'@babel/preset-env',
'@babel/preset-typescript',
];
switch (options.testingFramework) {
case TestingFramework.Jasmine:
dependencies.push('jasmine', ...babelPackages);
switch (options.testRunner) {
case TestRunner.Jasmine:
dependencies.push('jasmine');
break;
case TestingFramework.Jest:
dependencies.push('jest', '@types/jest', 'ts-jest');
case TestRunner.Jest:
dependencies.push('jest', '@types/jest');
break;
case TestingFramework.Mocha:
dependencies.push('mocha', '@types/mocha', ...babelPackages);
case TestRunner.Mocha:
dependencies.push('mocha', '@types/mocha');
break;
case TestingFramework.Node:
case TestRunner.Node:
dependencies.push('@types/node');
break;
}
@ -168,14 +162,10 @@ export function updateAngularJsonScripts(
overwrite = true
): Tree {
const angularJson = getAngularConfig(tree);
const name = getNgCommandName(options);
const port = options.port !== 4200 ? Number(options.port) : undefined;
const name = getNgCommandName(angularJson.projects);
Object.keys(angularJson['projects']).forEach(project => {
const commands = getScriptFromOptions(
options,
angularJson['projects'][project]!.root
);
const commands = getScriptFromOptions(options);
const e2eScript = [
{
name,
@ -184,8 +174,7 @@ export function updateAngularJsonScripts(
options: {
commands,
devServerTarget: `${project}:serve`,
testingFramework: options.testingFramework,
port,
testRunner: options.testRunner,
},
configurations: {
production: {

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
export enum TestingFramework {
export enum TestRunner {
Jasmine = 'jasmine',
Jest = 'jest',
Mocha = 'mocha',
@ -22,15 +22,15 @@ export enum TestingFramework {
}
export interface SchematicsOptions {
isDefaultTester: boolean;
exportConfig: boolean;
testingFramework: TestingFramework;
port: number;
testRunner: TestRunner;
}
export interface PuppeteerSchematicsConfig {
builder: string;
options: SchematicsOptions;
options: {
port: number;
testRunner: TestRunner;
};
}
export interface AngularProject {
root: string;
@ -46,4 +46,5 @@ export interface AngularJson {
export interface SchematicsSpec {
name: string;
project?: string;
route?: string;
}

View File

@ -0,0 +1,26 @@
import expect from 'expect';
import {
buildTestingTree,
getMultiProjectFile,
setupHttpHooks,
} from './utils.js';
describe('@puppeteer/ng-schematics: config', () => {
setupHttpHooks();
describe('Single Project', () => {
it('should create default file', async () => {
const tree = await buildTestingTree('config', 'single');
expect(tree.files).toContain('/.puppeteerrc.mjs');
});
});
describe('Multi projects', () => {
it('should create default file', async () => {
const tree = await buildTestingTree('config', 'multi');
expect(tree.files).toContain('/.puppeteerrc.mjs');
expect(tree.files).not.toContain(getMultiProjectFile('.puppeteerrc.mjs'));
});
});
});

View File

@ -6,12 +6,12 @@ import {
setupHttpHooks,
} from './utils.js';
describe('@puppeteer/ng-schematics: test', () => {
describe('@puppeteer/ng-schematics: e2e', () => {
setupHttpHooks();
describe('Single Project', () => {
it('should create default file', async () => {
const tree = await buildTestingTree('test', 'single', {
const tree = await buildTestingTree('e2e', 'single', {
name: 'myTest',
});
expect(tree.files).toContain('/e2e/tests/my-test.e2e.ts');
@ -19,9 +19,9 @@ describe('@puppeteer/ng-schematics: test', () => {
});
it('should create Node file', async () => {
const tree = await buildTestingTree('test', 'single', {
const tree = await buildTestingTree('e2e', 'single', {
name: 'myTest',
testingFramework: 'node',
testRunner: 'node',
});
expect(tree.files).not.toContain('/e2e/tests/my-test.e2e.ts');
expect(tree.files).toContain('/e2e/tests/my-test.test.ts');
@ -30,7 +30,7 @@ describe('@puppeteer/ng-schematics: test', () => {
describe('Multi projects', () => {
it('should create default file', async () => {
const tree = await buildTestingTree('test', 'multi', {
const tree = await buildTestingTree('e2e', 'multi', {
name: 'myTest',
});
expect(tree.files).toContain(
@ -42,9 +42,9 @@ describe('@puppeteer/ng-schematics: test', () => {
});
it('should create Node file', async () => {
const tree = await buildTestingTree('test', 'multi', {
const tree = await buildTestingTree('e2e', 'multi', {
name: 'myTest',
testingFramework: 'node',
testRunner: 'node',
});
expect(tree.files).not.toContain(
getMultiProjectFile('e2e/tests/my-test.e2e.ts')

View File

@ -5,6 +5,7 @@ import {
getAngularJsonScripts,
getMultiProjectFile,
getPackageJson,
runSchematic,
setupHttpHooks,
} from './utils.js';
@ -30,49 +31,36 @@ describe('@puppeteer/ng-schematics: ng-add', () => {
});
});
it('should update create proper "ng" command for non default tester', async () => {
const tree = await buildTestingTree('ng-add', 'single', {
isDefaultTester: false,
});
let tree = await buildTestingTree('ng-add', 'single');
// Re-run schematic to have e2e populated
tree = await runSchematic(tree, 'ng-add');
const {scripts} = getPackageJson(tree);
const {builder} = getAngularJsonScripts(tree, false);
expect(scripts['puppeteer']).toBe('ng run sandbox:puppeteer');
expect(builder).toBe('@puppeteer/ng-schematics:puppeteer');
});
it('should create Puppeteer config', async () => {
const {files} = await buildTestingTree('ng-add', 'single', {
exportConfig: true,
});
expect(files).toContain('/.puppeteerrc.cjs');
});
it('should not create Puppeteer config', async () => {
const {files} = await buildTestingTree('ng-add', 'single', {
exportConfig: false,
});
const {files} = await buildTestingTree('ng-add', 'single');
expect(files).not.toContain('/.puppeteerrc.cjs');
});
it('should create Jasmine files and update "package.json"', async () => {
const tree = await buildTestingTree('ng-add', 'single', {
testingFramework: 'jasmine',
testRunner: 'jasmine',
});
const {devDependencies} = getPackageJson(tree);
const {options} = getAngularJsonScripts(tree);
expect(tree.files).toContain('/e2e/support/jasmine.json');
expect(tree.files).toContain('/e2e/helpers/babel.js');
expect(devDependencies).toContain('jasmine');
expect(devDependencies).toContain('@babel/core');
expect(devDependencies).toContain('@babel/register');
expect(devDependencies).toContain('@babel/preset-typescript');
expect(options['commands']).toEqual([
[`./node_modules/.bin/jasmine`, '--config=./e2e/support/jasmine.json'],
[`jasmine`, '--config=./e2e/support/jasmine.json'],
]);
});
it('should create Jest files and update "package.json"', async () => {
const tree = await buildTestingTree('ng-add', 'single', {
testingFramework: 'jest',
testRunner: 'jest',
});
const {devDependencies} = getPackageJson(tree);
const {options} = getAngularJsonScripts(tree);
@ -80,32 +68,27 @@ describe('@puppeteer/ng-schematics: ng-add', () => {
expect(tree.files).toContain('/e2e/jest.config.js');
expect(devDependencies).toContain('jest');
expect(devDependencies).toContain('@types/jest');
expect(devDependencies).toContain('ts-jest');
expect(options['commands']).toEqual([
[`./node_modules/.bin/jest`, '-c', 'e2e/jest.config.js'],
[`jest`, '-c', 'e2e/jest.config.js'],
]);
});
it('should create Mocha files and update "package.json"', async () => {
const tree = await buildTestingTree('ng-add', 'single', {
testingFramework: 'mocha',
testRunner: 'mocha',
});
const {devDependencies} = getPackageJson(tree);
const {options} = getAngularJsonScripts(tree);
expect(tree.files).toContain('/e2e/.mocharc.js');
expect(tree.files).toContain('/e2e/babel.js');
expect(devDependencies).toContain('mocha');
expect(devDependencies).toContain('@types/mocha');
expect(devDependencies).toContain('@babel/core');
expect(devDependencies).toContain('@babel/register');
expect(devDependencies).toContain('@babel/preset-typescript');
expect(options['commands']).toEqual([
[`./node_modules/.bin/mocha`, '--config=./e2e/.mocharc.js'],
[`mocha`, '--config=./e2e/.mocharc.js'],
]);
});
it('should create Node files', async () => {
const tree = await buildTestingTree('ng-add', 'single', {
testingFramework: 'node',
testRunner: 'node',
});
const {options} = getAngularJsonScripts(tree);
@ -113,25 +96,15 @@ describe('@puppeteer/ng-schematics: ng-add', () => {
expect(tree.files).not.toContain('/e2e/tests/app.e2e.ts');
expect(tree.files).toContain('/e2e/tests/app.test.ts');
expect(options['commands']).toEqual([
[`./node_modules/.bin/tsc`, '-p', 'e2e/tsconfig.json'],
['node', '--test', '--test-reporter', 'spec', 'e2e/build/'],
]);
});
it('should not create port option', async () => {
it('should not create port value', async () => {
const tree = await buildTestingTree('ng-add');
const {options} = getAngularJsonScripts(tree);
expect(options['port']).toBeUndefined();
});
it('should create port option when specified', async () => {
const port = 8080;
const tree = await buildTestingTree('ng-add', 'single', {
port,
});
const {options} = getAngularJsonScripts(tree);
expect(options['port']).toBe(port);
});
});
describe('Multi projects', () => {
@ -153,33 +126,24 @@ describe('@puppeteer/ng-schematics: ng-add', () => {
});
});
it('should update create proper "ng" command for non default tester', async () => {
const tree = await buildTestingTree('ng-add', 'multi', {
isDefaultTester: false,
});
let tree = await buildTestingTree('ng-add', 'multi');
// Re-run schematic to have e2e populated
tree = await runSchematic(tree, 'ng-add');
const {scripts} = getPackageJson(tree);
const {builder} = getAngularJsonScripts(tree, false);
expect(scripts['puppeteer']).toBe('ng run sandbox:puppeteer');
expect(builder).toBe('@puppeteer/ng-schematics:puppeteer');
});
it('should create Puppeteer config', async () => {
const {files} = await buildTestingTree('ng-add', 'multi', {
exportConfig: true,
});
expect(files).toContain('/.puppeteerrc.cjs');
});
it('should not create Puppeteer config', async () => {
const {files} = await buildTestingTree('ng-add', 'multi', {
exportConfig: false,
});
const {files} = await buildTestingTree('ng-add', 'multi');
expect(files).not.toContain(getMultiProjectFile('.puppeteerrc.cjs'));
expect(files).not.toContain('/.puppeteerrc.cjs');
});
it('should create Jasmine files and update "package.json"', async () => {
const tree = await buildTestingTree('ng-add', 'multi', {
testingFramework: 'jasmine',
testRunner: 'jasmine',
});
const {devDependencies} = getPackageJson(tree);
const {options} = getAngularJsonScripts(tree);
@ -187,21 +151,14 @@ describe('@puppeteer/ng-schematics: ng-add', () => {
expect(tree.files).toContain(
getMultiProjectFile('e2e/support/jasmine.json')
);
expect(tree.files).toContain(getMultiProjectFile('e2e/helpers/babel.js'));
expect(devDependencies).toContain('jasmine');
expect(devDependencies).toContain('@babel/core');
expect(devDependencies).toContain('@babel/register');
expect(devDependencies).toContain('@babel/preset-typescript');
expect(options['commands']).toEqual([
[
`../../node_modules/.bin/jasmine`,
'--config=./e2e/support/jasmine.json',
],
[`jasmine`, '--config=./e2e/support/jasmine.json'],
]);
});
it('should create Jest files and update "package.json"', async () => {
const tree = await buildTestingTree('ng-add', 'multi', {
testingFramework: 'jest',
testRunner: 'jest',
});
const {devDependencies} = getPackageJson(tree);
const {options} = getAngularJsonScripts(tree);
@ -209,32 +166,27 @@ describe('@puppeteer/ng-schematics: ng-add', () => {
expect(tree.files).toContain(getMultiProjectFile('e2e/jest.config.js'));
expect(devDependencies).toContain('jest');
expect(devDependencies).toContain('@types/jest');
expect(devDependencies).toContain('ts-jest');
expect(options['commands']).toEqual([
[`../../node_modules/.bin/jest`, '-c', 'e2e/jest.config.js'],
[`jest`, '-c', 'e2e/jest.config.js'],
]);
});
it('should create Mocha files and update "package.json"', async () => {
const tree = await buildTestingTree('ng-add', 'multi', {
testingFramework: 'mocha',
testRunner: 'mocha',
});
const {devDependencies} = getPackageJson(tree);
const {options} = getAngularJsonScripts(tree);
expect(tree.files).toContain(getMultiProjectFile('e2e/.mocharc.js'));
expect(tree.files).toContain(getMultiProjectFile('e2e/babel.js'));
expect(devDependencies).toContain('mocha');
expect(devDependencies).toContain('@types/mocha');
expect(devDependencies).toContain('@babel/core');
expect(devDependencies).toContain('@babel/register');
expect(devDependencies).toContain('@babel/preset-typescript');
expect(options['commands']).toEqual([
[`../../node_modules/.bin/mocha`, '--config=./e2e/.mocharc.js'],
[`mocha`, '--config=./e2e/.mocharc.js'],
]);
});
it('should create Node files', async () => {
const tree = await buildTestingTree('ng-add', 'multi', {
testingFramework: 'node',
testRunner: 'node',
});
const {options} = getAngularJsonScripts(tree);
@ -246,24 +198,14 @@ describe('@puppeteer/ng-schematics: ng-add', () => {
getMultiProjectFile('e2e/tests/app.test.ts')
);
expect(options['commands']).toEqual([
[`../../node_modules/.bin/tsc`, '-p', 'e2e/tsconfig.json'],
['node', '--test', '--test-reporter', 'spec', 'e2e/build/'],
]);
});
it('should not create port option', async () => {
it('should not create port value', async () => {
const tree = await buildTestingTree('ng-add');
const {options} = getAngularJsonScripts(tree);
expect(options['port']).toBeUndefined();
});
it('should create port option when specified', async () => {
const port = 8080;
const tree = await buildTestingTree('ng-add', 'multi', {
port,
});
const {options} = getAngularJsonScripts(tree);
expect(options['port']).toBe(port);
});
});
});

View File

@ -74,7 +74,7 @@ export function getMultiProjectFile(file: string): string {
}
export async function buildTestingTree(
command: 'ng-add' | 'test',
command: 'ng-add' | 'e2e' | 'config',
type: 'single' | 'multi' = 'single',
userOptions?: Record<string, any>
): Promise<UnitTestTree> {
@ -83,9 +83,7 @@ export async function buildTestingTree(
join(__dirname, '../../lib/schematics/collection.json')
);
const options = {
isDefaultTester: true,
exportConfig: false,
testingFramework: 'jasmine',
testRunner: 'jasmine',
...userOptions,
};
let workingTree: UnitTestTree;
@ -121,3 +119,15 @@ export async function buildTestingTree(
return await runner.runSchematic(command, options, workingTree);
}
export async function runSchematic(
tree: UnitTestTree,
command: 'ng-add' | 'test',
options?: Record<string, any>
): Promise<UnitTestTree> {
const runner = new SchematicTestRunner(
'schematics',
join(__dirname, '../../lib/schematics/collection.json')
);
return await runner.runSchematic(command, options, tree);
}