diff --git a/packages/angular/pwa/pwa/index.ts b/packages/angular/pwa/pwa/index.ts
index 937b461577..d0b16bc89b 100644
--- a/packages/angular/pwa/pwa/index.ts
+++ b/packages/angular/pwa/pwa/index.ts
@@ -12,7 +12,6 @@ import {
SchematicsException,
Tree,
apply,
- branchAndMerge,
chain,
externalSchematic,
mergeWith,
@@ -38,18 +37,8 @@ function addServiceWorker(options: PwaOptions): Rule {
}
function getIndent(text: string): string {
- let indent = '';
- let hitNonSpace = false;
- text.split('')
- .forEach(char => {
- if (char === ' ' && !hitNonSpace) {
- indent += ' ';
- } else {
- hitNonSpace = true;
- }
- }, 0);
-
- return indent;
+ // This RegExp is guaranteed to match any string (even if it is a zero-length match).
+ return (/^ */.exec(text) as RegExpExecArray)[0];
}
function updateIndexFile(options: PwaOptions): Rule {
@@ -70,43 +59,32 @@ function updateIndexFile(options: PwaOptions): Rule {
const content = buffer.toString();
const lines = content.split('\n');
let closingHeadTagLineIndex = -1;
- let closingHeadTagLine = '';
let closingBodyTagLineIndex = -1;
- let closingBodyTagLine = '';
- lines.forEach((line: string, index: number) => {
- if (/<\/head>/.test(line) && closingHeadTagLineIndex === -1) {
- closingHeadTagLine = line;
+ lines.forEach((line, index) => {
+ if (closingHeadTagLineIndex === -1 && /<\/head>/.test(line)) {
closingHeadTagLineIndex = index;
- }
-
- if (/<\/body>/.test(line) && closingBodyTagLineIndex === -1) {
- closingBodyTagLine = line;
+ } else if (closingBodyTagLineIndex === -1 && /<\/body>/.test(line)) {
closingBodyTagLineIndex = index;
}
});
- const headTagIndent = getIndent(closingHeadTagLine) + ' ';
+ const headIndent = getIndent(lines[closingHeadTagLineIndex]) + ' ';
const itemsToAddToHead = [
'',
'',
];
- const textToInsertIntoHead = itemsToAddToHead
- .map(text => headTagIndent + text)
- .join('\n');
-
- const bodyTagIndent = getIndent(closingBodyTagLine) + ' ';
- const itemsToAddToBody
- = '';
-
- const textToInsertIntoBody = bodyTagIndent + itemsToAddToBody;
+ const bodyIndent = getIndent(lines[closingBodyTagLineIndex]) + ' ';
+ const itemsToAddToBody = [
+ '',
+ ];
const updatedIndex = [
...lines.slice(0, closingHeadTagLineIndex),
- textToInsertIntoHead,
+ ...itemsToAddToHead.map(line => headIndent + line),
...lines.slice(closingHeadTagLineIndex, closingBodyTagLineIndex),
- textToInsertIntoBody,
- ...lines.slice(closingBodyTagLineIndex),
+ ...itemsToAddToBody.map(line => bodyIndent + line),
+ ...lines.slice(closingHeadTagLineIndex),
].join('\n');
host.overwrite(path, updatedIndex);
@@ -137,12 +115,9 @@ function addManifestToAssetsConfig(options: PwaOptions) {
['build', 'test'].forEach((target) => {
const applyTo = architect[target].options;
+ const assets = applyTo.assets || (applyTo.assets = []);
- if (!applyTo.assets) {
- applyTo.assets = [assetEntry];
- } else {
- applyTo.assets.push(assetEntry);
- }
+ assets.push(assetEntry);
});
@@ -163,27 +138,24 @@ export default function (options: PwaOptions): Rule {
throw new SchematicsException(`PWA requires a project type of "application".`);
}
- const assetPath = join(project.root as Path, 'src', 'assets');
const sourcePath = join(project.root as Path, 'src');
+ const assetsPath = join(sourcePath, 'assets');
options.title = options.title || options.project;
- const templateSource = apply(url('./files/assets'), [
- template({
- ...options,
- }),
- move(assetPath),
+ const rootTemplateSource = apply(url('./files/root'), [
+ template({ ...options }),
+ move(sourcePath),
+ ]);
+ const assetsTemplateSource = apply(url('./files/assets'), [
+ template({ ...options }),
+ move(assetsPath),
]);
return chain([
addServiceWorker(options),
- branchAndMerge(chain([
- mergeWith(templateSource),
- ])),
- mergeWith(apply(url('./files/root'), [
- template({...options}),
- move(sourcePath),
- ])),
+ mergeWith(rootTemplateSource),
+ mergeWith(assetsTemplateSource),
updateIndexFile(options),
addManifestToAssetsConfig(options),
])(host, context);
diff --git a/packages/schematics/angular/application/index.ts b/packages/schematics/angular/application/index.ts
index 80a71d0153..2ac81fab66 100644
--- a/packages/schematics/angular/application/index.ts
+++ b/packages/schematics/angular/application/index.ts
@@ -23,6 +23,7 @@ import {
url,
} from '@angular-devkit/schematics';
import { Schema as E2eOptions } from '../e2e/schema';
+import { PATH_TO_PACKAGE_JSON, addDependencies } from '../utility/add-dependencies';
import {
WorkspaceProject,
WorkspaceSchema,
@@ -59,32 +60,21 @@ import { Schema as ApplicationOptions } from './schema';
// }
function addDependenciesToPackageJson() {
- return (host: Tree) => {
- const packageJsonPath = 'package.json';
+ const depsToAdd = [
+ `@angular-devkit/build-angular@${latestVersions.DevkitBuildAngular}`,
+ `@angular/compiler-cli@${latestVersions.Angular}`,
+ `typescript@${latestVersions.TypeScript}`,
+ ];
+ const rule = addDependencies(depsToAdd, true);
- if (!host.exists('package.json')) { return host; }
-
- const source = host.read('package.json');
- if (!source) { return host; }
-
- const sourceText = source.toString('utf-8');
- const json = JSON.parse(sourceText);
-
- if (!json['devDependencies']) {
- json['devDependencies'] = {};
+ return (host: Tree, context: SchematicContext) => {
+ if (!host.exists(PATH_TO_PACKAGE_JSON)) {
+ // The default rule throws if `package.json` does not exist.
+ // Here, we want to be more lenient.
+ return host;
}
- json.devDependencies = {
- '@angular/compiler-cli': latestVersions.Angular,
- '@angular-devkit/build-angular': latestVersions.DevkitBuildAngular,
- 'typescript': latestVersions.TypeScript,
- // De-structure last keeps existing user dependencies.
- ...json.devDependencies,
- };
-
- host.overwrite(packageJsonPath, JSON.stringify(json, null, 2));
-
- return host;
+ return rule(host, context);
};
}
diff --git a/packages/schematics/angular/application/index_spec.ts b/packages/schematics/angular/application/index_spec.ts
index fd988ace57..9529d2b1cf 100644
--- a/packages/schematics/angular/application/index_spec.ts
+++ b/packages/schematics/angular/application/index_spec.ts
@@ -139,39 +139,43 @@ describe('Application Schematic', () => {
describe(`update package.json`, () => {
it(`should add build-angular to devDependencies`, () => {
const tree = schematicRunner.runSchematic('application', defaultOptions, workspaceTree);
+ const pkg = JSON.parse(tree.readContent('package.json'));
- const packageJson = JSON.parse(tree.readContent('package.json'));
- expect(packageJson.devDependencies['@angular-devkit/build-angular'])
+ expect(pkg.devDependencies['@angular-devkit/build-angular'])
.toEqual(latestVersions.DevkitBuildAngular);
});
it('should use the latest known versions in package.json', () => {
const tree = schematicRunner.runSchematic('application', defaultOptions, workspaceTree);
- const pkg = JSON.parse(tree.readContent('/package.json'));
+ const pkg = JSON.parse(tree.readContent('package.json'));
+
expect(pkg.devDependencies['@angular/compiler-cli']).toEqual(latestVersions.Angular);
expect(pkg.devDependencies['typescript']).toEqual(latestVersions.TypeScript);
});
it(`should not override existing users dependencies`, () => {
- const oldPackageJson = workspaceTree.readContent('package.json');
- workspaceTree.overwrite('package.json', oldPackageJson.replace(
+ const oldPkg = workspaceTree.readContent('package.json');
+ workspaceTree.overwrite('package.json', oldPkg.replace(
`"typescript": "${latestVersions.TypeScript}"`,
`"typescript": "~2.5.2"`,
));
const tree = schematicRunner.runSchematic('application', defaultOptions, workspaceTree);
- const packageJson = JSON.parse(tree.readContent('package.json'));
- expect(packageJson.devDependencies.typescript).toEqual('~2.5.2');
+ const pkg = JSON.parse(tree.readContent('package.json'));
+
+ expect(pkg.devDependencies.typescript).toEqual('~2.5.2');
});
it(`should not modify the file when --skipPackageJson`, () => {
- const tree = schematicRunner.runSchematic('application', {
+ const options = {
name: 'foo',
skipPackageJson: true,
- }, workspaceTree);
+ };
+
+ const tree = schematicRunner.runSchematic('application', options, workspaceTree);
+ const pkg = JSON.parse(tree.readContent('package.json'));
- const packageJson = JSON.parse(tree.readContent('package.json'));
- expect(packageJson.devDependencies['@angular-devkit/build-angular']).toBeUndefined();
+ expect(pkg.devDependencies['@angular-devkit/build-angular']).toBeUndefined();
});
});
diff --git a/packages/schematics/angular/service-worker/files/ngsw-config.json b/packages/schematics/angular/service-worker/files/ngsw-config.json
index 7c6b82cdb4..fa9410f347 100644
--- a/packages/schematics/angular/service-worker/files/ngsw-config.json
+++ b/packages/schematics/angular/service-worker/files/ngsw-config.json
@@ -1,24 +1,26 @@
{
"index": "/index.html",
- "assetGroups": [{
- "name": "app",
- "installMode": "prefetch",
- "resources": {
- "files": [
- "/favicon.ico",
- "/index.html",
- "/*.css",
- "/*.js"
- ]
+ "assetGroups": [
+ {
+ "name": "app",
+ "installMode": "prefetch",
+ "resources": {
+ "files": [
+ "/favicon.ico",
+ "/index.html",
+ "/*.css",
+ "/*.js"
+ ]
+ }
+ }, {
+ "name": "assets",
+ "installMode": "lazy",
+ "updateMode": "prefetch",
+ "resources": {
+ "files": [
+ "/assets/**"
+ ]
+ }
}
- }, {
- "name": "assets",
- "installMode": "lazy",
- "updateMode": "prefetch",
- "resources": {
- "files": [
- "/assets/**"
- ]
- }
- }]
-}
\ No newline at end of file
+ ]
+}
diff --git a/packages/schematics/angular/service-worker/index.ts b/packages/schematics/angular/service-worker/index.ts
index c8d2c602f8..6e664fdd11 100644
--- a/packages/schematics/angular/service-worker/index.ts
+++ b/packages/schematics/angular/service-worker/index.ts
@@ -10,7 +10,6 @@ import {
SchematicContext,
SchematicsException,
Tree,
- UpdateRecorder,
apply,
chain,
mergeWith,
@@ -20,17 +19,13 @@ import {
} from '@angular-devkit/schematics';
import { NodePackageInstallTask } from '@angular-devkit/schematics/tasks';
import * as ts from 'typescript';
+import { addDependencies } from '../utility/add-dependencies';
import { addSymbolToNgModuleMetadata, insertImport, isImported } from '../utility/ast-utils';
import { InsertChange } from '../utility/change';
-import {
- getWorkspace,
- getWorkspacePath,
-} from '../utility/config';
+import { getWorkspace, getWorkspacePath } from '../utility/config';
import { getAppModulePath } from '../utility/ng-ast-utils';
import { Schema as ServiceWorkerOptions } from './schema';
-const packageJsonPath = '/package.json';
-
function updateConfigFile(options: ServiceWorkerOptions): Rule {
return (host: Tree, context: SchematicContext) => {
context.logger.debug('updating config file.');
@@ -68,26 +63,6 @@ function updateConfigFile(options: ServiceWorkerOptions): Rule {
};
}
-function addDependencies(): Rule {
- return (host: Tree, context: SchematicContext) => {
- const packageName = '@angular/service-worker';
- context.logger.debug(`adding dependency (${packageName})`);
- const buffer = host.read(packageJsonPath);
- if (buffer === null) {
- throw new SchematicsException('Could not find package.json');
- }
-
- const packageObject = JSON.parse(buffer.toString());
-
- const ngCoreVersion = packageObject.dependencies['@angular/core'];
- packageObject.dependencies[packageName] = ngCoreVersion;
-
- host.overwrite(packageJsonPath, JSON.stringify(packageObject, null, 2));
-
- return host;
- };
-}
-
function updateAppModule(options: ServiceWorkerOptions): Rule {
return (host: Tree, context: SchematicContext) => {
context.logger.debug('Updating appmodule');
@@ -134,7 +109,7 @@ function updateAppModule(options: ServiceWorkerOptions): Rule {
// register SW in app module
const importText =
- `ServiceWorkerModule.register('/ngsw-worker.js', { enabled: environment.production })`;
+ `ServiceWorkerModule.register('ngsw-worker.js', { enabled: environment.production })`;
moduleSource = getTsSourceFile(host, modulePath);
const metadataChanges = addSymbolToNgModuleMetadata(
moduleSource, modulePath, 'imports', importText);
@@ -185,7 +160,7 @@ export default function (options: ServiceWorkerOptions): Rule {
return chain([
mergeWith(templateSource),
updateConfigFile(options),
- addDependencies(),
+ addDependencies(['@angular/service-worker@{{NG_VERSION}}']),
updateAppModule(options),
])(host, context);
};
diff --git a/packages/schematics/angular/service-worker/index_spec.ts b/packages/schematics/angular/service-worker/index_spec.ts
index 9a724e4dcf..fae453d353 100644
--- a/packages/schematics/angular/service-worker/index_spec.ts
+++ b/packages/schematics/angular/service-worker/index_spec.ts
@@ -87,8 +87,8 @@ describe('Service Worker Schematic', () => {
const tree = schematicRunner.runSchematic('service-worker', defaultOptions, appTree);
const pkgText = tree.readContent('/projects/bar/src/app/app.module.ts');
// tslint:disable-next-line:max-line-length
- const regex = /ServiceWorkerModule\.register\('\/ngsw-worker.js\', { enabled: environment.production }\)/;
- expect(pkgText).toMatch(regex);
+ const expectedText = 'ServiceWorkerModule.register(\'ngsw-worker.js\', { enabled: environment.production })';
+ expect(pkgText).toContain(expectedText);
});
it('should put the ngsw-config.json file in the project root', () => {
diff --git a/packages/schematics/angular/utility/add-dependencies.ts b/packages/schematics/angular/utility/add-dependencies.ts
new file mode 100644
index 0000000000..5f1b09db1b
--- /dev/null
+++ b/packages/schematics/angular/utility/add-dependencies.ts
@@ -0,0 +1,82 @@
+
+/**
+ * @license
+ * Copyright Google Inc. All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+import { Rule, SchematicContext, SchematicsException, Tree } from '@angular-devkit/schematics';
+import { latestVersions } from './latest-versions';
+
+interface PackageJson {
+ dependencies?: Object & { [key: string]: string };
+ devDependencies?: Object & { [key: string]: string };
+}
+
+export const PATH_TO_PACKAGE_JSON = '/package.json';
+
+/**
+ * Generate a `Rule` for adding the specified dependencies/devDependencies to `package.json`.
+ *
+ * @param depsToAdd - A list of dependencies to add in the form `@`. Using the
+ * special value `{{NG_VERSION}}` as version, will use the same version as `@angular/core` (if
+ * present in `package.json`) or the latest Angular version.
+ * @param dev - If `true`, add the specified dependencies to `devDependencies`. Default: `false`
+ */
+export function addDependencies(depsToAdd: string[], dev = false): Rule {
+ return (host: Tree, context: SchematicContext) => {
+ const buffer = host.read(PATH_TO_PACKAGE_JSON);
+ if (buffer === null) {
+ throw new SchematicsException(`Could not read '${PATH_TO_PACKAGE_JSON}'.`);
+ }
+
+ const pkg = JSON.parse(buffer.toString()) as PackageJson;
+ const destinationKey = dev ? 'devDependencies' : 'dependencies';
+ const destination = pkg[destinationKey] || {};
+ const ngVersion = (pkg.dependencies && pkg.dependencies['@angular/core']) ||
+ latestVersions.Angular;
+
+ depsToAdd
+ .map(dep => {
+ const [pkgName, version] = dep.split(/(?!^)@/);
+
+ if (!version) {
+ throw new SchematicsException(
+ `Missing version info for dependency '${dep}'. (Expected '${pkgName}@'.)`);
+ }
+
+ return {pkgName, version: (version === '{{NG_VERSION}}') ? ngVersion : version};
+ })
+ .filter(({pkgName}) => {
+ // Do not overwrite existing user dependencies.
+ if (destination.hasOwnProperty(pkgName)) {
+ context.logger.debug(`Skipping existing ${dev ? 'dev ' : ''}dependency '${pkgName}'`);
+
+ return false;
+ }
+
+ return true;
+ })
+ .forEach(({pkgName, version}) => {
+ context.logger.debug(`Adding ${dev ? 'dev ' : ''}dependency (${pkgName} @ ${version})`);
+ destination[pkgName] = version;
+ });
+
+ pkg[destinationKey] = sortKeys(destination);
+ host.overwrite(PATH_TO_PACKAGE_JSON, JSON.stringify(pkg, null, 2) + '\n');
+
+ return host;
+ };
+}
+
+// tslint:disable-next-line:no-any
+function sortKeys(unsortedObj: T): T {
+ const sortedObj = {} as T;
+
+ Object.keys(unsortedObj)
+ .sort()
+ .forEach(key => sortedObj[key] = unsortedObj[key]);
+
+ return sortedObj;
+}
diff --git a/packages/schematics/angular/utility/add-dependencies_spec.ts b/packages/schematics/angular/utility/add-dependencies_spec.ts
new file mode 100644
index 0000000000..c0ebe7a286
--- /dev/null
+++ b/packages/schematics/angular/utility/add-dependencies_spec.ts
@@ -0,0 +1,173 @@
+/**
+ * @license
+ * Copyright Google Inc. All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+// import { Path } from '@angular-devkit/core';
+import { EmptyTree, SchematicContext, Tree } from '@angular-devkit/schematics';
+import { PATH_TO_PACKAGE_JSON, addDependencies } from './add-dependencies';
+import { latestVersions } from './latest-versions';
+
+
+describe('addDependencies()', () => {
+ let host: Tree;
+ let mockContext: SchematicContext;
+ let debugSpy: jasmine.Spy;
+
+ const createPackageJson = (obj = {}) => host.create(PATH_TO_PACKAGE_JSON, JSON.stringify(obj));
+ const readPackageJson = () => JSON.parse((host.read(PATH_TO_PACKAGE_JSON) as Buffer).toString());
+
+ beforeEach(() => {
+ host = new EmptyTree();
+ debugSpy = jasmine.createSpy('debug');
+ mockContext = { logger: { debug: debugSpy } } as any; // tslint:disable-line:no-any
+ });
+
+ it('should add the specified dependencies', () => {
+ createPackageJson();
+ addDependencies(['foo@latest', 'bar@next', `@baz/q-u-x@~4.2`])(host, mockContext);
+ const pkg = readPackageJson();
+
+ expect(pkg.devDependencies).toBeUndefined();
+ expect(pkg.dependencies).toEqual({
+ foo: 'latest',
+ bar: 'next',
+ '@baz/q-u-x': '~4.2',
+ });
+
+ expect(debugSpy).toHaveBeenCalledTimes(3);
+ expect(debugSpy.calls.allArgs()).toEqual([
+ ['Adding dependency (foo @ latest)'],
+ ['Adding dependency (bar @ next)'],
+ ['Adding dependency (@baz/q-u-x @ ~4.2)'],
+ ]);
+ });
+
+ it('should add the specified devDependencies', () => {
+ createPackageJson();
+ addDependencies(['foo@latest', 'bar@next', `@baz/q-u-x@~4.2`], true)(host, mockContext);
+ const pkg = readPackageJson();
+
+ expect(pkg.dependencies).toBeUndefined();
+ expect(pkg.devDependencies).toEqual({
+ foo: 'latest',
+ bar: 'next',
+ '@baz/q-u-x': '~4.2',
+ });
+
+ expect(debugSpy).toHaveBeenCalledTimes(3);
+ expect(debugSpy.calls.allArgs()).toEqual([
+ ['Adding dev dependency (foo @ latest)'],
+ ['Adding dev dependency (bar @ next)'],
+ ['Adding dev dependency (@baz/q-u-x @ ~4.2)'],
+ ]);
+ });
+
+ it('should sort the dependencies alphabetically', () => {
+ createPackageJson({ dependencies: { '@z/zz': '26', bbb: '2'} });
+ addDependencies(['@c/cc@3', 'aaa@1', `yyy@25`])(host, mockContext);
+ const pkg = readPackageJson();
+
+ expect(Object.keys(pkg.dependencies)).toEqual(['@c/cc', '@z/zz', 'aaa', 'bbb', 'yyy']);
+ });
+
+ it('should sort the devDependencies alphabetically', () => {
+ createPackageJson({ devDependencies: { '@z/zz': '26', bbb: '2'} });
+ addDependencies(['@c/cc@3', 'aaa@1', `yyy@25`], true)(host, mockContext);
+ const pkg = readPackageJson();
+
+ expect(Object.keys(pkg.devDependencies)).toEqual(['@c/cc', '@z/zz', 'aaa', 'bbb', 'yyy']);
+ });
+
+ it('should not overwrite existing user dependencies', () => {
+ createPackageJson({ dependencies: { foo: 'existing' } });
+ addDependencies(['foo@added', 'bar@added'])(host, mockContext);
+ const pkg = readPackageJson();
+
+ expect(pkg.dependencies).toEqual({
+ foo: 'existing',
+ bar: 'added',
+ });
+
+ expect(debugSpy).toHaveBeenCalledTimes(2);
+ expect(debugSpy.calls.allArgs()).toEqual([
+ ['Skipping existing dependency \'foo\''],
+ ['Adding dependency (bar @ added)'],
+ ]);
+ });
+
+ it('should not overwrite existing user devDependencies', () => {
+ createPackageJson({ devDependencies: { bar: 'existing' } });
+ addDependencies(['foo@added', 'bar@added'], true)(host, mockContext);
+ const pkg = readPackageJson();
+
+ expect(pkg.devDependencies).toEqual({
+ foo: 'added',
+ bar: 'existing',
+ });
+
+ expect(debugSpy).toHaveBeenCalledTimes(2);
+ expect(debugSpy.calls.allArgs()).toEqual([
+ ['Skipping existing dev dependency \'bar\''],
+ ['Adding dev dependency (foo @ added)'],
+ ]);
+ });
+
+ describe('with `{{NG_VERSION}}`', () => {
+ it('should use the version of `@angular/core`', () => {
+ createPackageJson({ dependencies: { '@angular/core': '3.0.0-not' } });
+ addDependencies(['foo@version', 'bar@{{NG_VERSION}}'], false)(host, mockContext);
+ addDependencies(['baz@{{NG_VERSION}}', 'qux@version'], true)(host, mockContext);
+ const pkg = readPackageJson();
+
+ expect(pkg.dependencies).toEqual({
+ '@angular/core': '3.0.0-not',
+ foo: 'version',
+ bar: '3.0.0-not',
+ });
+ expect(pkg.devDependencies).toEqual({
+ baz: '3.0.0-not',
+ qux: 'version',
+ });
+ });
+
+ it('should use the latest Angular version if `@angular/core` is not in `dependencies`', () => {
+ createPackageJson({ devDependencies: { '@angular/core': '3.0.0-not' } });
+ addDependencies(['foo@version', 'bar@{{NG_VERSION}}'], false)(host, mockContext);
+ addDependencies(['baz@{{NG_VERSION}}', 'qux@version'], true)(host, mockContext);
+ const pkg = readPackageJson();
+
+ expect(pkg.dependencies).toEqual({
+ foo: 'version',
+ bar: latestVersions.Angular,
+ });
+ expect(pkg.devDependencies).toEqual({
+ '@angular/core': '3.0.0-not',
+ baz: latestVersions.Angular,
+ qux: 'version',
+ });
+ });
+ });
+
+ it('should throw if any dependency has no specified version', () => {
+ createPackageJson();
+ const rule1 = addDependencies(['foo@version', '@bar'], false);
+ const rule2 = addDependencies(['baz@version', 'qux@'], true);
+
+ expect(() => rule1(host, mockContext))
+ .toThrowError('Missing version info for dependency \'@bar\'. (Expected \'@bar@\'.)');
+ expect(() => rule2(host, mockContext))
+ .toThrowError('Missing version info for dependency \'qux@\'. (Expected \'qux@\'.)');
+
+ expect(readPackageJson()).toEqual({});
+ });
+
+ it('should throw if unable to read `package.json`', () => {
+ spyOn(host, 'read').and.returnValue(null);
+ const rule = addDependencies([]);
+
+ expect(() => rule(host, mockContext)).toThrowError(`Could not read '${PATH_TO_PACKAGE_JSON}'.`);
+ });
+});