Skip to content

Commit

Permalink
fix(angular): switch to using jasmine-marbles for certain symbols
Browse files Browse the repository at this point in the history
  • Loading branch information
Coly010 committed Sep 6, 2022
1 parent a75e8f9 commit 0b4746e
Show file tree
Hide file tree
Showing 7 changed files with 232 additions and 26 deletions.
6 changes: 6 additions & 0 deletions packages/angular/migrations.json
Expand Up @@ -172,6 +172,12 @@
"version": "14.6.0-beta.0",
"description": "Update the @angular/cli package version to ~14.2.0.",
"factory": "./src/migrations/update-14-6-0/update-angular-cli"
},
"switch-to-jasmine-marbles": {
"cli": "nx",
"version": "15.0.0-beta.0",
"description": "Update the usages of @nrwl/angular/testing to import jasmine-marbles symbols from jasmine-marbles itself.",
"factory": "./src/migrations/update-15-0-0/switch-to-jasmine-marbles"
}
},
"packageJsonUpdates": {
Expand Down
1 change: 0 additions & 1 deletion packages/angular/ng-package.json
Expand Up @@ -12,7 +12,6 @@
"chalk",
"chokidar",
"ignore",
"jasmine-marbles",
"minimatch",
"rxjs-for-await",
"webpack-merge",
Expand Down
1 change: 0 additions & 1 deletion packages/angular/package.json
Expand Up @@ -51,7 +51,6 @@
"chokidar": "^3.5.1",
"http-server": "^14.1.0",
"ignore": "^5.0.4",
"jasmine-marbles": "~0.8.4",
"magic-string": "~0.26.2",
"minimatch": "3.0.5",
"semver": "7.3.4",
Expand Down
@@ -0,0 +1,93 @@
import switchToJasmineMarbles from './switch-to-jasmine-marbles';
import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing';
import { readJson } from '@nrwl/devkit';
import { jasmineMarblesVersion } from '../../utils/versions';

describe('switchToJasmineMarbles', () => {
it('should correctly migrate a file that is using imports from nrwl/angular/testing that exist in jasmine-marbles', () => {
// ARRANGE
const tree = createTreeWithEmptyWorkspace();
tree.write(
'a/b/mytest.spec.ts',
`import {hot, cold} from '@nrwl/angular/testing';`
);
tree.write(
'c/d/mytest.spec.ts',
`import {hot, getTestScheduler} from '@nrwl/angular/testing';`
);
tree.write(
'e/mytest.spec.ts',
`import {getTestScheduler, time} from '@nrwl/angular/testing';`
);

// ACT
switchToJasmineMarbles(tree);

// ASSERT
expect(tree.read('a/b/mytest.spec.ts', 'utf-8')).toMatchInlineSnapshot(`
"
import {hot,cold} from 'jasmine-marbles';"
`);
expect(tree.read('c/d/mytest.spec.ts', 'utf-8')).toMatchInlineSnapshot(`
"
import {hot,getTestScheduler} from 'jasmine-marbles';"
`);
expect(tree.read('e/mytest.spec.ts', 'utf-8')).toMatchInlineSnapshot(`
"
import {getTestScheduler,time} from 'jasmine-marbles';"
`);
});

it('should correctly migrate and split imports from nrwl/angular/testing that exist in jasmine-marbles and nrwl/angular/testing', () => {
// ARRANGE
const tree = createTreeWithEmptyWorkspace();
tree.write(
'a/b/mytest.spec.ts',
`import {hot, cold, readFirst} from '@nrwl/angular/testing';`
);
tree.write(
'c/d/mytest.spec.ts',
`import {hot, getTestScheduler, readAll} from '@nrwl/angular/testing';`
);
tree.write(
'e/mytest.spec.ts',
`import {getTestScheduler, time, readAll, readFirst} from '@nrwl/angular/testing';`
);

// ACT
switchToJasmineMarbles(tree);

// ASSERT
expect(tree.read('a/b/mytest.spec.ts', 'utf-8')).toMatchInlineSnapshot(`
"import {readFirst} from '@nrwl/angular/testing';
import {hot,cold} from 'jasmine-marbles';"
`);
expect(tree.read('c/d/mytest.spec.ts', 'utf-8')).toMatchInlineSnapshot(`
"import {readAll} from '@nrwl/angular/testing';
import {hot,getTestScheduler} from 'jasmine-marbles';"
`);
expect(tree.read('e/mytest.spec.ts', 'utf-8')).toMatchInlineSnapshot(`
"import {readAll,readFirst} from '@nrwl/angular/testing';
import {getTestScheduler,time} from 'jasmine-marbles';"
`);
});

it('should add jasmine-marbles as a dependency if it does not exist but uses jasmine-marbles symbols in files', () => {
// ARRANGE
const tree = createTreeWithEmptyWorkspace();
tree.write(
'a/b/mytest.spec.ts',
`import {hot, cold, readFirst} from '@nrwl/angular/testing';`
);

// ACT
switchToJasmineMarbles(tree);

// ASSERT

const jasmineMarblesDependency = readJson(tree, 'package.json')
.devDependencies['jasmine-marbles'];
expect(jasmineMarblesDependency).toBeTruthy();
expect(jasmineMarblesDependency).toBe(jasmineMarblesVersion);
});
});
@@ -0,0 +1,132 @@
import type { Tree } from '@nrwl/devkit';
import {
addDependenciesToPackageJson,
readJson,
visitNotIgnoredFiles,
} from '@nrwl/devkit';
import { extname } from 'path';
import { tsquery } from '@phenomnomnominal/tsquery';
import { jasmineMarblesVersion } from '@nrwl/angular/src/utils/versions';

export default function switchToJasmineMarbles(tree: Tree) {
const usesJasmineMarbles = replaceJasmineMarbleUsagesInFiles(tree);
addJasmineMarblesDevDependencyIfUsed(tree, usesJasmineMarbles);
}

function replaceJasmineMarbleUsagesInFiles(tree: Tree) {
let usesJasmineMarbles = false;

visitNotIgnoredFiles(tree, '/', (path) => {
if (extname(path) !== '.ts') {
return;
}

const fileContents = tree.read(path, 'utf-8');
if (!fileContents.includes('@nrwl/angular/testing')) {
return;
}

const NRWL_ANGULAR_TESTING_IMPORT_SELECTOR =
'ImportDeclaration:has(StringLiteral[value="@nrwl/angular/testing"])';
const ast = tsquery.ast(fileContents);
const nrwlAngularTestingImportNodes = tsquery(
ast,
NRWL_ANGULAR_TESTING_IMPORT_SELECTOR,
{ visitAllChildren: true }
);

if (
!nrwlAngularTestingImportNodes ||
nrwlAngularTestingImportNodes.length === 0
) {
return;
}

const jasmineMarblesExportsRegex = new RegExp(
/(hot|cold|getTestScheduler|time)/
);
if (
!jasmineMarblesExportsRegex.test(
nrwlAngularTestingImportNodes[0].getText()
)
) {
return;
}

const IMPORT_SPECIFIERS_SELECTOR = 'NamedImports > ImportSpecifier';
const importSpecifierNodes = tsquery(
nrwlAngularTestingImportNodes[0],
IMPORT_SPECIFIERS_SELECTOR,
{ visitAllChildren: true }
);

if (!importSpecifierNodes || importSpecifierNodes.length === 0) {
return;
}

const validNrwlTestingImports = [];
const validJasmineMarbleImports = [];
for (const node of importSpecifierNodes) {
const importSymbol = node.getText();
if (jasmineMarblesExportsRegex.test(importSymbol)) {
validJasmineMarbleImports.push(importSymbol);
} else {
validNrwlTestingImports.push(importSymbol);
}
}

if (!usesJasmineMarbles && validJasmineMarbleImports.length > 0) {
usesJasmineMarbles = true;
}

const newFileContents = `${fileContents.slice(
0,
nrwlAngularTestingImportNodes[0].getStart()
)}${
validNrwlTestingImports.length > 0
? `import {${validNrwlTestingImports.join(
','
)}} from '@nrwl/angular/testing';`
: ''
}
${
validJasmineMarbleImports.length > 0
? `import {${validJasmineMarbleImports.join(
','
)}} from 'jasmine-marbles';${fileContents.slice(
nrwlAngularTestingImportNodes[0].getEnd(),
-1
)}`
: ''
}`;

tree.write(path, newFileContents);
});
return usesJasmineMarbles;
}

function addJasmineMarblesDevDependencyIfUsed(
tree: Tree,
usesJasmineMarbles: boolean
) {
if (!usesJasmineMarbles) {
return;
}

const pkgJson = readJson(tree, 'package.json');
const jasmineMarblesDependency = pkgJson.dependencies['jasmine-marbles'];
const jasmineMarblesDevDependency =
pkgJson.devDependencies['jasmine-marbles'];

if (jasmineMarblesDependency || jasmineMarblesDevDependency) {
return;
}

addDependenciesToPackageJson(
tree,
{},
{
'jasmine-marbles': jasmineMarblesVersion,
}
);
}
1 change: 1 addition & 0 deletions packages/angular/src/utils/versions.ts
Expand Up @@ -30,3 +30,4 @@ export const jasmineSpecReporterVersion = '~7.0.0';
export const typesJasmineVersion = '~4.0.0';
export const typesJasminewd2Version = '~2.0.3';
export const typesNodeVersion = '16.11.7';
export const jasmineMarblesVersion = '^0.9.2';
24 changes: 0 additions & 24 deletions packages/angular/testing/index.ts
@@ -1,25 +1 @@
import {
cold as rxjsMarblesCold,
hot as rxjsMarblesHot,
getTestScheduler as rxjsMarblesTestScheduler,
time as rxjsMarblesTime,
} from 'jasmine-marbles';

/**
* @deprecated Import from 'jasmine-marbles' instead. Will be removed in Nx v15.
*/
export const cold = rxjsMarblesCold;
/**
* @deprecated Import from 'jasmine-marbles' instead. Will be removed in Nx v15.
*/
export const hot = rxjsMarblesHot;
/**
* @deprecated Import from 'jasmine-marbles' instead. Will be removed in Nx v15.
*/
export const getTestScheduler = rxjsMarblesTestScheduler;
/**
* @deprecated Import from 'jasmine-marbles' instead. Will be removed in Nx v15.
*/
export const time = rxjsMarblesTime;

export { readAll, readFirst } from './src/testing-utils';

0 comments on commit 0b4746e

Please sign in to comment.