refactor(compiler-cli): setup compilation mode to enable generating linker code (#38938)
This is a precursor to introducing the Angular linker. As an initial step, a compiler option to configure the compilation mode is introduced. This option is initially internal until the linker is considered ready. PR Close #38938
This commit is contained in:
parent
eec9f4a84e
commit
9d04b95166
|
@ -10,7 +10,7 @@ import * as ts from 'typescript';
|
|||
import {IncrementalBuild} from '../../../src/ngtsc/incremental/api';
|
||||
import {NOOP_PERF_RECORDER} from '../../../src/ngtsc/perf';
|
||||
import {ClassDeclaration, Decorator} from '../../../src/ngtsc/reflection';
|
||||
import {DecoratorHandler, DtsTransformRegistry, HandlerFlags, Trait, TraitCompiler} from '../../../src/ngtsc/transform';
|
||||
import {CompilationMode, DecoratorHandler, DtsTransformRegistry, HandlerFlags, Trait, TraitCompiler} from '../../../src/ngtsc/transform';
|
||||
import {NgccReflectionHost} from '../host/ngcc_host';
|
||||
import {isDefined} from '../utils';
|
||||
|
||||
|
@ -26,7 +26,7 @@ export class NgccTraitCompiler extends TraitCompiler {
|
|||
private ngccReflector: NgccReflectionHost) {
|
||||
super(
|
||||
handlers, ngccReflector, NOOP_PERF_RECORDER, new NoIncrementalBuild(),
|
||||
/* compileNonExportedClasses */ true, new DtsTransformRegistry());
|
||||
/* compileNonExportedClasses */ true, CompilationMode.FULL, new DtsTransformRegistry());
|
||||
}
|
||||
|
||||
get analyzedFiles(): ts.SourceFile[] {
|
||||
|
|
|
@ -48,7 +48,7 @@ runInEachFileSystem(() => {
|
|||
'analyze',
|
||||
'register',
|
||||
'resolve',
|
||||
'compile',
|
||||
'compileFull',
|
||||
]);
|
||||
// Only detect the Component and Directive decorators
|
||||
handler.detect.and.callFake(
|
||||
|
@ -95,7 +95,7 @@ runInEachFileSystem(() => {
|
|||
});
|
||||
// The "test" compilation result is just the name of the decorator being compiled
|
||||
// (suffixed with `(compiled)`)
|
||||
(handler.compile as any).and.callFake((decl: ts.Declaration, analysis: any) => {
|
||||
(handler.compileFull as any).and.callFake((decl: ts.Declaration, analysis: any) => {
|
||||
logs.push(`compile: ${(decl as any).name.text}@${analysis.decoratorName} (resolved: ${
|
||||
analysis.resolved})`);
|
||||
return `@${analysis.decoratorName} (compiled)`;
|
||||
|
@ -414,7 +414,7 @@ runInEachFileSystem(() => {
|
|||
expect(testHandler.analyze).toHaveBeenCalled();
|
||||
expect(testHandler.register).not.toHaveBeenCalled();
|
||||
expect(testHandler.resolve).not.toHaveBeenCalled();
|
||||
expect(testHandler.compile).not.toHaveBeenCalled();
|
||||
expect(testHandler.compileFull).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should report resolve diagnostics to the `diagnosticHandler` callback', () => {
|
||||
|
@ -436,7 +436,7 @@ runInEachFileSystem(() => {
|
|||
expect(testHandler.analyze).toHaveBeenCalled();
|
||||
expect(testHandler.register).toHaveBeenCalled();
|
||||
expect(testHandler.resolve).toHaveBeenCalled();
|
||||
expect(testHandler.compile).not.toHaveBeenCalled();
|
||||
expect(testHandler.compileFull).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -452,7 +452,7 @@ runInEachFileSystem(() => {
|
|||
analyze(): AnalysisOutput<unknown> {
|
||||
throw new Error('analyze should not have been called');
|
||||
}
|
||||
compile(): CompileResult {
|
||||
compileFull(): CompileResult {
|
||||
throw new Error('compile should not have been called');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -209,7 +209,7 @@ class DetectDecoratorHandler implements DecoratorHandler<unknown, unknown, unkno
|
|||
return {};
|
||||
}
|
||||
|
||||
compile(node: ClassDeclaration): CompileResult|CompileResult[] {
|
||||
compileFull(node: ClassDeclaration): CompileResult|CompileResult[] {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
@ -227,7 +227,7 @@ class DiagnosticProducingHandler implements DecoratorHandler<unknown, unknown, u
|
|||
return {diagnostics: [makeDiagnostic(9999, node, 'test diagnostic')]};
|
||||
}
|
||||
|
||||
compile(node: ClassDeclaration): CompileResult|CompileResult[] {
|
||||
compileFull(node: ClassDeclaration): CompileResult|CompileResult[] {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -309,7 +309,7 @@ class TestHandler implements DecoratorHandler<unknown, unknown, unknown> {
|
|||
return {};
|
||||
}
|
||||
|
||||
compile(node: ClassDeclaration): CompileResult|CompileResult[] {
|
||||
compileFull(node: ClassDeclaration): CompileResult|CompileResult[] {
|
||||
this.log.push(this.name + ':compile:' + node.name.text);
|
||||
return [];
|
||||
}
|
||||
|
|
|
@ -585,7 +585,7 @@ export class ComponentDecoratorHandler implements
|
|||
return {data};
|
||||
}
|
||||
|
||||
compile(
|
||||
compileFull(
|
||||
node: ClassDeclaration, analysis: Readonly<ComponentAnalysisData>,
|
||||
resolution: Readonly<ComponentResolutionData>, pool: ConstantPool): CompileResult[] {
|
||||
const meta: R3ComponentMetadata = {...analysis.meta, ...resolution};
|
||||
|
|
|
@ -151,7 +151,7 @@ export class DirectiveDecoratorHandler implements
|
|||
return {diagnostics: diagnostics.length > 0 ? diagnostics : undefined};
|
||||
}
|
||||
|
||||
compile(
|
||||
compileFull(
|
||||
node: ClassDeclaration, analysis: Readonly<DirectiveHandlerData>,
|
||||
resolution: Readonly<unknown>, pool: ConstantPool): CompileResult[] {
|
||||
const meta = analysis.meta;
|
||||
|
|
|
@ -87,7 +87,7 @@ export class InjectableDecoratorHandler implements
|
|||
this.injectableRegistry.registerInjectable(node);
|
||||
}
|
||||
|
||||
compile(node: ClassDeclaration, analysis: Readonly<InjectableHandlerData>): CompileResult[] {
|
||||
compileFull(node: ClassDeclaration, analysis: Readonly<InjectableHandlerData>): CompileResult[] {
|
||||
const res = compileIvyInjectable(analysis.meta);
|
||||
const statements = res.statements;
|
||||
const results: CompileResult[] = [];
|
||||
|
|
|
@ -371,7 +371,7 @@ export class NgModuleDecoratorHandler implements
|
|||
}
|
||||
}
|
||||
|
||||
compile(
|
||||
compileFull(
|
||||
node: ClassDeclaration, analysis: Readonly<NgModuleAnalysis>,
|
||||
resolution: Readonly<NgModuleResolution>): CompileResult[] {
|
||||
// Merge the injector imports (which are 'exports' that were later found to be NgModules)
|
||||
|
|
|
@ -133,7 +133,7 @@ export class PipeDecoratorHandler implements DecoratorHandler<Decorator, PipeHan
|
|||
return {};
|
||||
}
|
||||
|
||||
compile(node: ClassDeclaration, analysis: Readonly<PipeHandlerData>): CompileResult[] {
|
||||
compileFull(node: ClassDeclaration, analysis: Readonly<PipeHandlerData>): CompileResult[] {
|
||||
const meta = analysis.meta;
|
||||
const res = compilePipeFromMetadata(meta);
|
||||
const factoryRes = compileNgFactoryDefField({
|
||||
|
|
|
@ -22,7 +22,7 @@ runInEachFileSystem(() => {
|
|||
const {handler, TestClass, ɵprov, analysis} =
|
||||
setupHandler(/* errorOnDuplicateProv */ true);
|
||||
try {
|
||||
handler.compile(TestClass, analysis);
|
||||
handler.compileFull(TestClass, analysis);
|
||||
return fail('Compilation should have failed');
|
||||
} catch (err) {
|
||||
if (!(err instanceof FatalDiagnosticError)) {
|
||||
|
@ -39,7 +39,7 @@ runInEachFileSystem(() => {
|
|||
() => {
|
||||
const {handler, TestClass, ɵprov, analysis} =
|
||||
setupHandler(/* errorOnDuplicateProv */ false);
|
||||
const res = handler.compile(TestClass, analysis);
|
||||
const res = handler.compileFull(TestClass, analysis);
|
||||
expect(res).not.toContain(jasmine.objectContaining({name: 'ɵprov'}));
|
||||
});
|
||||
});
|
||||
|
|
|
@ -48,6 +48,22 @@ export interface TestOnlyOptions {
|
|||
tracePerformance?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Options that specify compilation target.
|
||||
*/
|
||||
export interface TargetOptions {
|
||||
/**
|
||||
* Specifies the compilation mode to use. The following modes are available:
|
||||
* - 'full': generates fully AOT compiled code using Ivy instructions.
|
||||
* - 'partial': generates code in a stable, but intermediate form suitable to be published to NPM.
|
||||
*
|
||||
* To become public once the linker is ready.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
compilationMode?: 'full'|'partial';
|
||||
}
|
||||
|
||||
/**
|
||||
* A merged interface of all of the various Angular compiler options, as well as the standard
|
||||
* `ts.CompilerOptions`.
|
||||
|
@ -56,4 +72,5 @@ export interface TestOnlyOptions {
|
|||
*/
|
||||
export interface NgCompilerOptions extends ts.CompilerOptions, LegacyNgcOptions, BazelAndG3Options,
|
||||
NgcCompatibilityOptions, StrictTemplateOptions,
|
||||
TestOnlyOptions, I18nOptions, MiscOptions {}
|
||||
TestOnlyOptions, I18nOptions, TargetOptions,
|
||||
MiscOptions {}
|
||||
|
|
|
@ -27,7 +27,7 @@ import {entryPointKeyFor, NgModuleRouteAnalyzer} from '../../routing';
|
|||
import {ComponentScopeReader, LocalModuleScopeRegistry, MetadataDtsModuleScopeResolver} from '../../scope';
|
||||
import {generatedFactoryTransform} from '../../shims';
|
||||
import {ivySwitchTransform} from '../../switch';
|
||||
import {aliasTransformFactory, declarationTransformFactory, DecoratorHandler, DtsTransformRegistry, ivyTransformFactory, TraitCompiler} from '../../transform';
|
||||
import {aliasTransformFactory, CompilationMode, declarationTransformFactory, DecoratorHandler, DtsTransformRegistry, ivyTransformFactory, TraitCompiler} from '../../transform';
|
||||
import {TemplateTypeCheckerImpl} from '../../typecheck';
|
||||
import {OptimizeFor, TemplateTypeChecker, TypeCheckingConfig, TypeCheckingProgramStrategy} from '../../typecheck/api';
|
||||
import {isTemplateDiagnostic} from '../../typecheck/diagnostics';
|
||||
|
@ -761,9 +761,11 @@ export class NgCompiler {
|
|||
this.closureCompilerEnabled, injectableRegistry, this.options.i18nInLocale),
|
||||
];
|
||||
|
||||
const compilationMode =
|
||||
this.options.compilationMode === 'partial' ? CompilationMode.PARTIAL : CompilationMode.FULL;
|
||||
const traitCompiler = new TraitCompiler(
|
||||
handlers, reflector, this.perfRecorder, this.incrementalDriver,
|
||||
this.options.compileNonExportedClasses !== false, dtsTransforms);
|
||||
this.options.compileNonExportedClasses !== false, compilationMode, dtsTransforms);
|
||||
|
||||
const templateTypeChecker = new TemplateTypeCheckerImpl(
|
||||
this.tsProgram, this.typeCheckingProgramStrategy, traitCompiler,
|
||||
|
|
|
@ -15,6 +15,21 @@ import {ClassDeclaration, Decorator} from '../../reflection';
|
|||
import {ImportManager} from '../../translator';
|
||||
import {TypeCheckContext} from '../../typecheck/api';
|
||||
|
||||
/**
|
||||
* Specifies the compilation mode that is used for the compilation.
|
||||
*/
|
||||
export enum CompilationMode {
|
||||
/**
|
||||
* Generates fully AOT compiled code using Ivy instructions.
|
||||
*/
|
||||
FULL,
|
||||
|
||||
/**
|
||||
* Generates code using a stable, but intermediate format suitable to be published to NPM.
|
||||
*/
|
||||
PARTIAL,
|
||||
}
|
||||
|
||||
export enum HandlerPrecedence {
|
||||
/**
|
||||
* Handler with PRIMARY precedence cannot overlap - there can only be one on a given class.
|
||||
|
@ -147,10 +162,25 @@ export interface DecoratorHandler<D, A, R> {
|
|||
/**
|
||||
* Generate a description of the field which should be added to the class, including any
|
||||
* initialization code to be generated.
|
||||
*
|
||||
* If the compilation mode is configured as partial, and an implementation of `compilePartial` is
|
||||
* provided, then this method is not called.
|
||||
*/
|
||||
compile(
|
||||
compileFull(
|
||||
node: ClassDeclaration, analysis: Readonly<A>, resolution: Readonly<R>,
|
||||
constantPool: ConstantPool): CompileResult|CompileResult[];
|
||||
|
||||
/**
|
||||
* Generates code for the decorator using a stable, but intermediate format suitable to be
|
||||
* published to NPM. This code is meant to be processed by the linker to achieve the final AOT
|
||||
* compiled code.
|
||||
*
|
||||
* If present, this method is used if the compilation mode is configured as partial, otherwise
|
||||
* `compileFull` is.
|
||||
*/
|
||||
compilePartial?
|
||||
(node: ClassDeclaration, analysis: Readonly<A>, resolution: Readonly<R>): CompileResult
|
||||
|CompileResult[];
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -17,7 +17,7 @@ import {ClassDeclaration, Decorator, ReflectionHost} from '../../reflection';
|
|||
import {ProgramTypeCheckAdapter, TypeCheckContext} from '../../typecheck/api';
|
||||
import {getSourceFile, isExported} from '../../util/src/typescript';
|
||||
|
||||
import {AnalysisOutput, CompileResult, DecoratorHandler, HandlerFlags, HandlerPrecedence, ResolveResult} from './api';
|
||||
import {AnalysisOutput, CompilationMode, CompileResult, DecoratorHandler, HandlerFlags, HandlerPrecedence, ResolveResult} from './api';
|
||||
import {DtsTransformRegistry} from './declaration';
|
||||
import {PendingTrait, Trait, TraitState} from './trait';
|
||||
|
||||
|
@ -88,7 +88,8 @@ export class TraitCompiler implements ProgramTypeCheckAdapter {
|
|||
private handlers: DecoratorHandler<unknown, unknown, unknown>[],
|
||||
private reflector: ReflectionHost, private perf: PerfRecorder,
|
||||
private incrementalBuild: IncrementalBuild<ClassRecord, unknown>,
|
||||
private compileNonExportedClasses: boolean, private dtsTransforms: DtsTransformRegistry) {
|
||||
private compileNonExportedClasses: boolean, private compilationMode: CompilationMode,
|
||||
private dtsTransforms: DtsTransformRegistry) {
|
||||
for (const handler of handlers) {
|
||||
this.handlersByName.set(handler.name, handler);
|
||||
}
|
||||
|
@ -479,8 +480,17 @@ export class TraitCompiler implements ProgramTypeCheckAdapter {
|
|||
}
|
||||
|
||||
const compileSpan = this.perf.start('compileClass', original);
|
||||
const compileMatchRes =
|
||||
trait.handler.compile(clazz, trait.analysis, trait.resolution, constantPool);
|
||||
|
||||
let compileRes: CompileResult|CompileResult[];
|
||||
if (this.compilationMode === CompilationMode.PARTIAL &&
|
||||
trait.handler.compilePartial !== undefined) {
|
||||
compileRes = trait.handler.compilePartial(clazz, trait.analysis, trait.resolution);
|
||||
} else {
|
||||
compileRes =
|
||||
trait.handler.compileFull(clazz, trait.analysis, trait.resolution, constantPool);
|
||||
}
|
||||
|
||||
const compileMatchRes = compileRes;
|
||||
this.perf.stop(compileSpan);
|
||||
if (Array.isArray(compileMatchRes)) {
|
||||
for (const result of compileMatchRes) {
|
||||
|
|
|
@ -5,13 +5,16 @@
|
|||
* 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 {ConstantPool} from '@angular/compiler';
|
||||
import * as o from '@angular/compiler/src/output/output_ast';
|
||||
|
||||
import {absoluteFrom} from '../../file_system';
|
||||
import {runInEachFileSystem} from '../../file_system/testing';
|
||||
import {NOOP_INCREMENTAL_BUILD} from '../../incremental';
|
||||
import {NOOP_PERF_RECORDER} from '../../perf';
|
||||
import {TypeScriptReflectionHost} from '../../reflection';
|
||||
import {makeProgram} from '../../testing';
|
||||
import {DtsTransformRegistry, TraitCompiler} from '../../transform';
|
||||
import {ClassDeclaration, Decorator, isNamedClassDeclaration, TypeScriptReflectionHost} from '../../reflection';
|
||||
import {getDeclaration, makeProgram} from '../../testing';
|
||||
import {CompilationMode, DetectResult, DtsTransformRegistry, TraitCompiler} from '../../transform';
|
||||
import {AnalysisOutput, CompileResult, DecoratorHandler, HandlerPrecedence} from '../src/api';
|
||||
|
||||
runInEachFileSystem(() => {
|
||||
|
@ -30,7 +33,7 @@ runInEachFileSystem(() => {
|
|||
analyze(): AnalysisOutput<unknown> {
|
||||
throw new Error('analyze should not have been called');
|
||||
}
|
||||
compile(): CompileResult {
|
||||
compileFull(): CompileResult {
|
||||
throw new Error('compile should not have been called');
|
||||
}
|
||||
}
|
||||
|
@ -43,12 +46,133 @@ runInEachFileSystem(() => {
|
|||
const reflectionHost = new TypeScriptReflectionHost(checker);
|
||||
const compiler = new TraitCompiler(
|
||||
[new FakeDecoratorHandler()], reflectionHost, NOOP_PERF_RECORDER, NOOP_INCREMENTAL_BUILD,
|
||||
true, new DtsTransformRegistry());
|
||||
true, CompilationMode.FULL, new DtsTransformRegistry());
|
||||
const sourceFile = program.getSourceFile('lib.d.ts')!;
|
||||
const analysis = compiler.analyzeSync(sourceFile);
|
||||
|
||||
expect(sourceFile.isDeclarationFile).toBe(true);
|
||||
expect(analysis).toBeFalsy();
|
||||
});
|
||||
|
||||
describe('compilation mode', () => {
|
||||
class PartialDecoratorHandler implements DecoratorHandler<{}, {}, unknown> {
|
||||
name = 'PartialDecoratorHandler';
|
||||
precedence = HandlerPrecedence.PRIMARY;
|
||||
|
||||
detect(node: ClassDeclaration, decorators: Decorator[]|null): DetectResult<{}>|undefined {
|
||||
if (node.name.text !== 'Partial') {
|
||||
return undefined;
|
||||
}
|
||||
return {trigger: node, decorator: null, metadata: {}};
|
||||
}
|
||||
|
||||
analyze(): AnalysisOutput<unknown> {
|
||||
return {analysis: {}};
|
||||
}
|
||||
|
||||
compileFull(): CompileResult {
|
||||
return {
|
||||
name: 'compileFull',
|
||||
initializer: o.literal(true),
|
||||
statements: [],
|
||||
type: o.BOOL_TYPE
|
||||
};
|
||||
}
|
||||
|
||||
compilePartial(): CompileResult {
|
||||
return {
|
||||
name: 'compilePartial',
|
||||
initializer: o.literal(true),
|
||||
statements: [],
|
||||
type: o.BOOL_TYPE
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class FullDecoratorHandler implements DecoratorHandler<{}, {}, unknown> {
|
||||
name = 'FullDecoratorHandler';
|
||||
precedence = HandlerPrecedence.PRIMARY;
|
||||
|
||||
detect(node: ClassDeclaration, decorators: Decorator[]|null): DetectResult<{}>|undefined {
|
||||
if (node.name.text !== 'Full') {
|
||||
return undefined;
|
||||
}
|
||||
return {trigger: node, decorator: null, metadata: {}};
|
||||
}
|
||||
|
||||
analyze(): AnalysisOutput<unknown> {
|
||||
return {analysis: {}};
|
||||
}
|
||||
|
||||
compileFull(): CompileResult {
|
||||
return {
|
||||
name: 'compileFull',
|
||||
initializer: o.literal(true),
|
||||
statements: [],
|
||||
type: o.BOOL_TYPE
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
it('should run partial compilation when implemented if compilation mode is partial', () => {
|
||||
const {program} = makeProgram([{
|
||||
name: _('/test.ts'),
|
||||
contents: `
|
||||
export class Full {}
|
||||
export class Partial {}
|
||||
`,
|
||||
}]);
|
||||
const checker = program.getTypeChecker();
|
||||
const reflectionHost = new TypeScriptReflectionHost(checker);
|
||||
const compiler = new TraitCompiler(
|
||||
[new PartialDecoratorHandler(), new FullDecoratorHandler()], reflectionHost,
|
||||
NOOP_PERF_RECORDER, NOOP_INCREMENTAL_BUILD, true, CompilationMode.PARTIAL,
|
||||
new DtsTransformRegistry());
|
||||
const sourceFile = program.getSourceFile('test.ts')!;
|
||||
compiler.analyzeSync(sourceFile);
|
||||
compiler.resolve();
|
||||
|
||||
const partialDecl =
|
||||
getDeclaration(program, _('/test.ts'), 'Partial', isNamedClassDeclaration);
|
||||
const partialResult = compiler.compile(partialDecl, new ConstantPool())!;
|
||||
expect(partialResult.length).toBe(1);
|
||||
expect(partialResult[0].name).toBe('compilePartial');
|
||||
|
||||
const fullDecl = getDeclaration(program, _('/test.ts'), 'Full', isNamedClassDeclaration);
|
||||
const fullResult = compiler.compile(fullDecl, new ConstantPool())!;
|
||||
expect(fullResult.length).toBe(1);
|
||||
expect(fullResult[0].name).toBe('compileFull');
|
||||
});
|
||||
|
||||
it('should run full compilation if compilation mode is full', () => {
|
||||
const {program} = makeProgram([{
|
||||
name: _('/test.ts'),
|
||||
contents: `
|
||||
export class Full {}
|
||||
export class Partial {}
|
||||
`,
|
||||
}]);
|
||||
const checker = program.getTypeChecker();
|
||||
const reflectionHost = new TypeScriptReflectionHost(checker);
|
||||
const compiler = new TraitCompiler(
|
||||
[new PartialDecoratorHandler(), new FullDecoratorHandler()], reflectionHost,
|
||||
NOOP_PERF_RECORDER, NOOP_INCREMENTAL_BUILD, true, CompilationMode.FULL,
|
||||
new DtsTransformRegistry());
|
||||
const sourceFile = program.getSourceFile('test.ts')!;
|
||||
compiler.analyzeSync(sourceFile);
|
||||
compiler.resolve();
|
||||
|
||||
const partialDecl =
|
||||
getDeclaration(program, _('/test.ts'), 'Partial', isNamedClassDeclaration);
|
||||
const partialResult = compiler.compile(partialDecl, new ConstantPool())!;
|
||||
expect(partialResult.length).toBe(1);
|
||||
expect(partialResult[0].name).toBe('compileFull');
|
||||
|
||||
const fullDecl = getDeclaration(program, _('/test.ts'), 'Full', isNamedClassDeclaration);
|
||||
const fullResult = compiler.compile(fullDecl, new ConstantPool())!;
|
||||
expect(fullResult.length).toBe(1);
|
||||
expect(fullResult[0].name).toBe('compileFull');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue