Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(compiler): use replace-resources transformer from Angular #708

Merged
merged 1 commit into from
Jan 6, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 1 addition & 4 deletions e2e/__tests__/calc/calc.component.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import { Component, OnInit, Input } from '@angular/core';
import { Observable } from 'rxjs';

const image = require('e2e/test-app-v10/src/assets/its_something.png');
import * as image from 'e2e/test-app-v10/src/assets/its_something.png';

@Component({
selector: 'app-calc',
Expand All @@ -23,7 +21,6 @@ export class CalcComponent implements OnInit {
@Input() hasAClass = false;
prop1: number;
image: string;
observable$: Observable<string>;

constructor() {
this.init();
Expand Down
2 changes: 1 addition & 1 deletion e2e/__tests__/forward-ref/forward-ref.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { forwardRef, Inject, Injector } from '@angular/core';

const shouldSkipTest = process.env.NG_VERSION === 'v9' || process.env.SKIP_TEST === 'true';
const shouldSkipTest = process.env.NG_VERSION === 'v9' || process.env.ISOLATED_MODULES === 'true';
const skipTest = shouldSkipTest ? test.skip : test

if (shouldSkipTest) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Component } from '@angular/core';
@Component({
selector: 'app-simple-with-styles',
templateUrl: './simple-with-styles.component.html',
styleUrls: ['./simple-with-styles.scss'],
// we have to setup styles this way, since simple styles/styleUrs properties will be removed (jest does not unit test styles)
styles: [`
.some-class { color: red }
Expand Down
3 changes: 3 additions & 0 deletions e2e/__tests__/simple-with-styles/simple-with-styles.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
h1 {
font-size: 1.6rem;
}
1 change: 1 addition & 0 deletions e2e/test-app-v10/src/typings.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ declare var module: NodeModule;
interface NodeModule {
id: string;
}
declare module '*.png';
1 change: 1 addition & 0 deletions e2e/test-app-v11/src/typings.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ declare var module: NodeModule;
interface NodeModule {
id: string;
}
declare module '*.png';
1 change: 1 addition & 0 deletions e2e/test-app-v9/src/typings.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ declare var module: NodeModule;
interface NodeModule {
id: string;
}
declare module '*.png';
17 changes: 12 additions & 5 deletions scripts/e2e.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,18 @@ const executeTest = (projectRealPath) => {
logger.log();

logger.log('setting NG_VERSION environment variable');
logger.log();
const projectName = projectRealPath.match(/([^\\/]*)\/*$/)[1];
process.env.NG_VERSION = projectName.substring(projectName.lastIndexOf('-') + 1);

// then we install it in the repo
logger.log('ensuring all dependencies of target project are installed');
logger.log();

execa.sync('yarn', ['install'], { cwd: projectRealPath });

logger.log('cleaning old assets in target project');
logger.log();

const testCasesDest = join(projectRealPath, 'src', '__tests__');
const presetDir = join(projectRealPath, 'node_modules', 'jest-preset-angular');
Expand All @@ -42,6 +45,7 @@ const executeTest = (projectRealPath) => {
mkdirSync(presetDir);

logger.log('copying distributed assets to target project');
logger.log();

copySync(join(cwd, 'jest-preset.js'), `${presetDir}/jest-preset.js`);
copySync(join(cwd, 'ngcc-jest-processor.js'), `${presetDir}/ngcc-jest-processor.js`);
Expand All @@ -50,6 +54,7 @@ const executeTest = (projectRealPath) => {
copySync(join(cwd, 'build'), `${presetDir}/build`);

logger.log('copying test cases to target project');
logger.log();

copySync(join(cwd, 'e2e', '__tests__'), testCasesDest);

Expand All @@ -66,7 +71,7 @@ const executeTest = (projectRealPath) => {
// cmdESMIso.push(...jestArgs);
}

logger.log('starting non isolatedModules tests');
logger.log('STARTING NONE ISOLATED MODULES TESTS');
logger.log();
logger.log('starting the CJS tests using:', ...cmdCjsUnIso);
logger.log();
Expand All @@ -77,11 +82,13 @@ const executeTest = (projectRealPath) => {
env: process.env,
});

logger.log('starting isolatedModules tests');
logger.log();
logger.log('setting SKIP_TEST environment variable for isolatedModules true');
process.env.SKIP_TEST = 'true';
logger.log('STARTING ISOLATED MODULES TESTS');
logger.log();
logger.log('setting ISOLATED_MODULES environment variable for isolatedModules true');
process.env.ISOLATED_MODULES = 'true';

logger.log();
logger.log('starting the CommonJS tests using:', ...cmdCjsIso);
logger.log();

Expand All @@ -104,7 +111,7 @@ const executeTest = (projectRealPath) => {

execa.sync('rimraf', [testCasesDest]);
delete process.env.NG_VERSION;
delete process.env.SKIP_TEST;
delete process.env.ISOLATED_MODULES;
};

const cwd = process.cwd();
Expand Down
12 changes: 12 additions & 0 deletions src/__tests__/__helpers__/test-helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
export const jestCfgStub = {
testMatch: ['**/__tests__/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)'],
testRegex: ['(/__tests__/.*|(\\\\.|/)(test|spec))\\\\.[jt]sx?$'],
globals: {
'ts-jest': {
diagnostics: {
pretty: false,
},
},
},
} as any; // eslint-disable-line @typescript-eslint/no-explicit-any
1 change: 1 addition & 0 deletions src/__tests__/__mocks__/app.component.html
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<h1>App works</h1>
3 changes: 3 additions & 0 deletions src/__tests__/__mocks__/app.component.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
h1 {
font-size: 1.6rem;
}
9 changes: 8 additions & 1 deletion src/__tests__/__mocks__/app.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,14 @@ import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
styleUrls: ['./app.component.scss', './foo.component.css'],
styles: [
`
h1 {
font-size: 1.6rem;
}
`,
],
})
export class AppComponent {
title = 'test-app-v10';
Expand Down
46 changes: 46 additions & 0 deletions src/__tests__/__snapshots__/replace-resources.spec.ts.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Replace resources transformer should use inline-files + strip-styles for isolatedModules true 1`] = `
"\\"use strict\\";
Object.defineProperty(exports, \\"__esModule\\", { value: true });
exports.AppComponent = void 0;
const tslib_1 = require(\\"tslib\\");
const core_1 = require(\\"@angular/core\\");
let AppComponent = class AppComponent {
constructor() {
this.title = 'test-app-v10';
}
};
AppComponent = tslib_1.__decorate([
core_1.Component({
selector: 'app-root',
template: require('./app.component.html'),
styleUrls: [],
styles: [],
})
], AppComponent);
exports.AppComponent = AppComponent;
//# "
`;

exports[`Replace resources transformer should use replaceResources transformer from @angular/compiler-cli for isolatedModules false 1`] = `
"\\"use strict\\";
Object.defineProperty(exports, \\"__esModule\\", { value: true });
exports.AppComponent = void 0;
const tslib_1 = require(\\"tslib\\");
const core_1 = require(\\"@angular/core\\");
let AppComponent = class AppComponent {
constructor() {
this.title = 'test-app-v10';
}
};
AppComponent = tslib_1.__decorate([
core_1.Component({
selector: 'app-root',
template: require(\\"./app.component.html\\"),
styles: []
})
], AppComponent);
exports.AppComponent = AppComponent;
//# "
`;
52 changes: 52 additions & 0 deletions src/__tests__/replace-resources.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { readFileSync } from 'fs';
import { join } from 'path';
import { SOURCE_MAPPING_PREFIX } from 'ts-jest/dist/compiler/compiler-utils';

import { NgJestConfig } from '../config/ng-jest-config';
import { jestCfgStub } from './__helpers__/test-helpers';
import { NgJestCompiler } from '../compiler/ng-jest-compiler';

describe('Replace resources transformer', () => {
const fileName = join(__dirname, '__mocks__', 'app.component.ts');
const fileContent = readFileSync(fileName, 'utf-8');

test('should use replaceResources transformer from @angular/compiler-cli for isolatedModules false', () => {
const ngJestConfig = new NgJestConfig({
...jestCfgStub,
globals: {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
'ts-jest': {
...jestCfgStub.globals['ts-jest'],
isolatedModules: false,
},
},
});
const compiler = new NgJestCompiler(ngJestConfig, new Map());

// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const emittedResult = compiler.getCompiledOutput(fileName, fileContent, false)!;

// Source map is different based on file location which can fail on CI, so we only compare snapshot for js
expect(emittedResult.substring(0, emittedResult.indexOf(SOURCE_MAPPING_PREFIX))).toMatchSnapshot();
});

test('should use inline-files + strip-styles for isolatedModules true', () => {
const ngJestConfig = new NgJestConfig({
...jestCfgStub,
globals: {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
'ts-jest': {
...jestCfgStub.globals['ts-jest'],
isolatedModules: true,
},
},
});
const compiler = new NgJestCompiler(ngJestConfig, new Map());

// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const emittedResult = compiler.getCompiledOutput(fileName, fileContent, false)!;

// Source map is different based on file location which can fail on CI, so we only compare snapshot for js
expect(emittedResult.substring(0, emittedResult.indexOf(SOURCE_MAPPING_PREFIX))).toMatchSnapshot();
});
});
31 changes: 17 additions & 14 deletions src/compiler/ng-jest-compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { factory as downlevelCtor } from '../transformers/downlevel-ctor';
import { factory as inlineFiles } from '../transformers/inline-files';
import { factory as stripStyles } from '../transformers/strip-styles';
import { NgJestCompilerHost } from './compiler-host';
import { replaceResources } from '../transformers/replace-resources';

export class NgJestCompiler implements CompilerInstance {
private _compilerOptions!: CompilerOptions;
Expand Down Expand Up @@ -36,15 +37,9 @@ export class NgJestCompiler implements CompilerInstance {

getCompiledOutput(fileName: string, fileContent: string, supportsStaticESM: boolean): string {
const customTransformers = this.ngJestConfig.customTransformers;
const transformers = {
...customTransformers,
before: [
// hoisting from `ts-jest` or other before transformers
...(customTransformers.before as ts.TransformerFactory<ts.SourceFile>[]),
inlineFiles(this.ngJestConfig),
stripStyles(this.ngJestConfig),
],
};
const isAppPath = (fileName: string) => !fileName.endsWith('.ngfactory.ts') && !fileName.endsWith('.ngstyle.ts');
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const getTypeChecker = () => this._program!.getTypeChecker();
if (this._program) {
const allDiagnostics = [];
if (!this._rootNames.includes(fileName)) {
Expand All @@ -60,17 +55,17 @@ export class NgJestCompiler implements CompilerInstance {

const sourceFile = this._program.getSourceFile(fileName);
const emitResult = this._program.emit(sourceFile, undefined, undefined, undefined, {
...transformers,
...customTransformers,
before: [
// hoisting from `ts-jest` or other before transformers
...transformers.before,
...(customTransformers.before as ts.TransformerFactory<ts.SourceFile>[]),
/**
* Downlevel constructor parameters for DI support. This is required to support forwardRef in ES2015 due to
* TDZ issues. This wrapper is needed here due to the program not being available until after
* the transformers are created. Also because program can be updated so we can't push this transformer in
* _createCompilerHost
*/
downlevelCtor(this._program),
replaceResources(isAppPath, getTypeChecker),
],
});
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
Expand Down Expand Up @@ -110,7 +105,15 @@ export class NgJestCompiler implements CompilerInstance {

const result: ts.TranspileOutput = this._ts.transpileModule(fileContent, {
fileName,
transformers,
transformers: {
...customTransformers,
before: [
// hoisting from `ts-jest` or other before transformers
...(customTransformers.before as ts.TransformerFactory<ts.SourceFile>[]),
inlineFiles(this.ngJestConfig),
stripStyles(this.ngJestConfig),
],
},
compilerOptions: {
...this._compilerOptions,
module: moduleKind,
Expand All @@ -127,7 +130,7 @@ export class NgJestCompiler implements CompilerInstance {
}
}

private _setupOptions({ parsedTsConfig }: NgJestConfig) {
private _setupOptions({ parsedTsConfig }: NgJestConfig): void {
this._logger.debug({ parsedTsConfig }, '_setupOptions: initializing compiler config');

this._compilerOptions = { ...parsedTsConfig.options };
Expand Down
12 changes: 12 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/** Angular component decorator templateUrl property name */
export const TEMPLATE_URL = 'templateUrl';
/** Angular component decorator styleUrls property name */
export const STYLE_URLS = 'styleUrls';
/** Angular component decorator styles property name */
export const STYLES = 'styles';
/** Angular component decorator template property name */
export const TEMPLATE = 'template';
/** Node require function name */
export const REQUIRE = 'require';
/** Angular component decorator name */
export const COMPONENT = 'Component';
28 changes: 17 additions & 11 deletions src/transformers/inline-files.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,22 +31,16 @@ import type {
Visitor,
PropertyAssignment,
LiteralLikeNode,
StringLiteral,
} from 'typescript';
import { getCreateStringLiteral, ConfigSet } from './transform-utils';
import type { ConfigSet } from 'ts-jest/dist/config/config-set';

import { TEMPLATE_URL, STYLE_URLS, REQUIRE, TEMPLATE } from '../constants';

// replace original ts-jest ConfigSet with this simple interface, as it would require
// jest-preset-angular to add several babel devDependencies to get the other types
// inside the ConfigSet right

/** Angular component decorator TemplateUrl property name */
const TEMPLATE_URL = 'templateUrl';
/** Angular component decorator StyleUrls property name */
const STYLE_URLS = 'styleUrls';
/** Angular component decorator Template property name */
const TEMPLATE = 'template';
/** Node require function name */
const REQUIRE = 'require';

/**
* Property names anywhere in an angular project to transform
*/
Expand Down Expand Up @@ -74,8 +68,20 @@ export function factory(cs: ConfigSet): (ctx: TransformationContext) => Transfor
* Our compiler (typescript, or a module with typescript-like interface)
*/
const ts = cs.compilerModule;
function getCreateStringLiteral(): typeof ts.createStringLiteral {
if (ts.createStringLiteral && typeof ts.createStringLiteral === 'function') {
return ts.createStringLiteral;
}

const createStringLiteral = getCreateStringLiteral(ts);
return function createStringLiteral(text: string) {
const node = <StringLiteral>ts.createNode(ts.SyntaxKind.StringLiteral, -1, -1);
node.text = text;
node.flags |= ts.NodeFlags.Synthesized;

return node;
};
}
const createStringLiteral = getCreateStringLiteral();

/**
* Traverses the AST down to the relevant assignments anywhere in the file
Expand Down