55 * Use of this source code is governed by an MIT-style license that can be
66 * found in the LICENSE file at https://angular.io/license
77 */
8+
9+ // tslint:disable:no-implicit-dependencies
10+
811import { Architect } from '@angular-devkit/architect' ;
912import { normalize , virtualFs } from '@angular-devkit/core' ;
13+ import { last , tap } from 'rxjs/operators' ;
14+ import { promisify } from 'util' ;
1015import { createArchitect , host , karmaTargetSpec } from '../test-utils' ;
1116
17+ // In each of the test below we'll have to call setTimeout to wait for the coverage
18+ // analysis to be done. This is because karma-coverage performs the analysis
19+ // asynchronously but the promise that it returns is not awaited by Karma.
20+ // Coverage analysis begins when onRunComplete() is invoked, and output files
21+ // are subsequently written to disk. For more information, see
22+ // https://github.com/karma-runner/karma-coverage/blob/32acafa90ed621abd1df730edb44ae55a4009c2c/lib/reporter.js#L221
23+
24+ const setTimeoutPromise = promisify ( setTimeout ) ;
25+
1226describe ( 'Karma Builder code coverage' , ( ) => {
1327 const coverageFilePath = normalize ( 'coverage/lcov.info' ) ;
1428 let architect : Architect ;
@@ -23,16 +37,19 @@ describe('Karma Builder code coverage', () => {
2337 it ( 'supports code coverage option' , async ( ) => {
2438 const run = await architect . scheduleTarget ( karmaTargetSpec , { codeCoverage : true } ) ;
2539
26- await expectAsync ( run . result ) . toBeResolvedTo ( jasmine . objectContaining ( { success : true } ) ) ;
40+ const { success} = await run . result ;
41+ expect ( success ) . toBe ( true ) ;
2742
2843 await run . stop ( ) ;
2944
45+ await setTimeoutPromise ( 1000 ) ;
46+
3047 const exists = host . scopedSync ( ) . exists ( coverageFilePath ) ;
31- expect ( exists ) . toBe ( true ) ;
48+ expect ( exists ) . toBe ( true , ` ${ coverageFilePath } does not exist` ) ;
3249
3350 if ( exists ) {
3451 const content = virtualFs . fileBufferToString ( host . scopedSync ( ) . read ( coverageFilePath ) ) ;
35- expect ( content ) . toContain ( 'polyfills .ts' ) ;
52+ expect ( content ) . toContain ( 'app.component .ts' ) ;
3653 expect ( content ) . toContain ( 'test.ts' ) ;
3754 }
3855 } , 120000 ) ;
@@ -41,23 +58,24 @@ describe('Karma Builder code coverage', () => {
4158 const overrides = {
4259 codeCoverage : true ,
4360 codeCoverageExclude : [
44- 'src/polyfills.ts' ,
4561 '**/test.ts' ,
4662 ] ,
4763 } ;
4864
4965 const run = await architect . scheduleTarget ( karmaTargetSpec , overrides ) ;
5066
51- await expectAsync ( run . result ) . toBeResolvedTo ( jasmine . objectContaining ( { success : true } ) ) ;
67+ const { success} = await run . result ;
68+ expect ( success ) . toBe ( true ) ;
5269
5370 await run . stop ( ) ;
5471
72+ await setTimeoutPromise ( 1000 ) ;
73+
5574 const exists = host . scopedSync ( ) . exists ( coverageFilePath ) ;
5675 expect ( exists ) . toBe ( true ) ;
5776
5877 if ( exists ) {
5978 const content = virtualFs . fileBufferToString ( host . scopedSync ( ) . read ( coverageFilePath ) ) ;
60- expect ( content ) . not . toContain ( 'polyfills.ts' ) ;
6179 expect ( content ) . not . toContain ( 'test.ts' ) ;
6280 }
6381 } , 120000 ) ;
@@ -98,10 +116,13 @@ describe('Karma Builder code coverage', () => {
98116
99117 const run = await architect . scheduleTarget ( karmaTargetSpec , { codeCoverage : true } ) ;
100118
101- await expectAsync ( run . result ) . toBeResolvedTo ( jasmine . objectContaining ( { success : true } ) ) ;
119+ const { success} = await run . result ;
120+ expect ( success ) . toBe ( true ) ;
102121
103122 await run . stop ( ) ;
104123
124+ await setTimeoutPromise ( 1000 ) ;
125+
105126 const exists = host . scopedSync ( ) . exists ( coverageFilePath ) ;
106127 expect ( exists ) . toBe ( true ) ;
107128
@@ -111,20 +132,18 @@ describe('Karma Builder code coverage', () => {
111132 }
112133 } , 120000 ) ;
113134
114- it ( `should fail when coverage is below threhold and 'emitWarning' is false` , async ( ) => {
115- host . replaceInFile ( 'karma.conf.js' , 'fixWebpackSourcePaths: true' ,
116- `
117- fixWebpackSourcePaths: true,
118- thresholds: {
119- emitWarning: false,
120- global: {
121- statements: 100,
122- lines: 100,
123- branches: 100,
124- functions: 100
135+ it ( 'should exit with non-zero code when coverage is below threshold' , async ( ) => {
136+ host . replaceInFile ( 'karma.conf.js' , 'coverageReporter: {' , `
137+ coverageReporter: {
138+ check: {
139+ global: {
140+ statements: 100,
141+ lines: 100,
142+ branches: 100,
143+ functions: 100
144+ }
125145 },
126- }` ,
127- ) ;
146+ ` ) ;
128147
129148 host . appendToFile ( 'src/app/app.component.ts' , `
130149 export function nonCovered(): boolean {
@@ -133,7 +152,21 @@ describe('Karma Builder code coverage', () => {
133152 ` ) ;
134153
135154 const run = await architect . scheduleTarget ( karmaTargetSpec , { codeCoverage : true } ) ;
136- await expectAsync ( run . result ) . toBeResolvedTo ( jasmine . objectContaining ( { success : false } ) ) ;
155+
156+ // In incremental mode, karma-coverage does not have the ability to mark a
157+ // run as failed if code coverage does not pass. This is because it does
158+ // the coverage asynchoronously and Karma does not await the promise
159+ // returned by the plugin.
160+ expect ( ( await run . result ) . success ) . toBeTrue ( ) ;
161+
162+ // However the program must exit with non-zero exit code.
163+ // This is a more common use case of coverage testing and must be supported.
164+ await run . output . pipe (
165+ last ( ) ,
166+ tap ( buildEvent => expect ( buildEvent . success ) . toBeFalse ( ) ) ,
167+ ) . toPromise ( ) ;
168+
137169 await run . stop ( ) ;
170+
138171 } , 120000 ) ;
139172} ) ;
0 commit comments