8
0
Просмотр исходного кода

Tests and docs for the builder.

Piotrek Koszuliński 10 лет назад
Родитель
Сommit
0b8016d6ce
4 измененных файлов с 383 добавлено и 87 удалено
  1. 8 6
      dev/tasks/gulp/build/tasks.js
  2. 80 24
      dev/tasks/gulp/build/utils.js
  3. 15 8
      dev/tests/build/tasks.js
  4. 280 49
      dev/tests/build/utils.js

+ 8 - 6
dev/tasks/gulp/build/tasks.js

@@ -78,7 +78,7 @@ module.exports = ( config ) => {
 
 				return gulp.src( glob, { nodir: true } )
 					.pipe( watch ? gulpWatch( glob ) : utils.noop() )
-					.pipe( utils.wrapCKEditor5Package() );
+					.pipe( utils.renameCKEditor5Files() );
 			},
 
 			/**
@@ -124,7 +124,7 @@ module.exports = ( config ) => {
 				} );
 
 				return merge.apply( null, streams )
-					.pipe( utils.unpackPackages() );
+					.pipe( utils.renamePackageFiles() );
 			}
 		},
 
@@ -154,7 +154,7 @@ module.exports = ( config ) => {
 			//
 			// The flow looks like follows:
 			//
-			// 1. codeStream
+			// 1. codeStream (including logger)
 			// 2. inputStream
 			// 3. conversionStream (may throw)
 			// 4. outputStream
@@ -168,9 +168,11 @@ module.exports = ( config ) => {
 			// Multipipe and gulp-mirror seem to work this way, so we get a single error emitter.
 			const formats = options.formats.split( ',' );
 			const codeStream = tasks.src.all( options.watch )
-				.on( 'data', ( file ) => {
-					gutil.log( `Processing '${ gutil.colors.cyan( file.path ) }'...` );
-				} );
+				.pipe(
+					utils.noop( ( file ) => {
+						gutil.log( `Processing '${ gutil.colors.cyan( file.path ) }'...` );
+					} )
+				);
 			const conversionStreamGenerator = utils.getConversionStreamGenerator( distDir );
 			const outputStream = utils.noop();
 

+ 80 - 24
dev/tasks/gulp/build/utils.js

@@ -14,7 +14,7 @@ const through = require( 'through2' );
 
 const utils = {
 	/**
-	 * Code which can be appended to transpiled (into AMD) test files in order to
+	 * Code which can be appended to a transpiled (into AMD) test files in order to
 	 * load the 'tests' module and defer launching Bender until it's ready.
 	 *
 	 * Note: This code will not be transpiled so keep it in ES5.
@@ -29,12 +29,21 @@ require( [ 'tests' ], bender.defer(), function( err ) {
 `,
 
 	/**
-	 * Creates a pass-through stream.
+	 * Creates a simple duplex stream.
 	 *
+	 * @param {Function} [callback] A callback which will be executed with each chunk.
 	 * @returns {Stream}
 	 */
-	noop() {
-		return new PassThrough( { objectMode: true } );
+	noop( callback ) {
+		if ( !callback ) {
+			return new PassThrough( { objectMode: true } );
+		}
+
+		return through( { objectMode: true }, ( file, encoding, throughCallback ) => {
+			callback( file );
+
+			throughCallback( null, file );
+		} );
 	},
 
 	/**
@@ -64,16 +73,18 @@ require( [ 'tests' ], bender.defer(), function( err ) {
 			conversionPipes.push( utils.pickVersionedFile( format ) );
 
 			if ( format != 'esnext' ) {
-				const filterCode = gulpFilter( ( file ) => {
-					return utils.isNotTestFile( file ) && utils.isJSFile( file );
+				// Convert src files.
+				const filterSource = gulpFilter( ( file ) => {
+					return utils.isSourceFile( file ) && utils.isJSFile( file );
 				}, { restore: true } );
-				const transpileCode = utils.transpile( format, utils.getBabelOptionsForCode( format ) );
+				const transpileSource = utils.transpile( format, utils.getBabelOptionsForSource( format ) );
 				conversionPipes.push(
-					filterCode,
-					transpileCode,
-					filterCode.restore
+					filterSource,
+					transpileSource,
+					filterSource.restore
 				);
 
+				// Convert test files.
 				const filterTests = gulpFilter( ( file ) => {
 					return utils.isTestFile( file ) && utils.isJSFile( file );
 				}, { restore: true } );
@@ -87,10 +98,10 @@ require( [ 'tests' ], bender.defer(), function( err ) {
 			}
 
 			conversionPipes.push(
-				utils.dist( distDir, format )
-					.on( 'data', ( file ) => {
-						gutil.log( `Finished writing '${ gutil.colors.cyan( file.path ) }'` );
-					} )
+				utils.dist( distDir, format ),
+				utils.noop( ( file ) => {
+					gutil.log( `Finished writing '${ gutil.colors.cyan( file.path ) }'` );
+				} )
 			);
 
 			pipes.push( multipipe.apply( null, conversionPipes ) );
@@ -114,7 +125,13 @@ require( [ 'tests' ], bender.defer(), function( err ) {
 			} );
 	},
 
-	getBabelOptionsForCode( format ) {
+	/**
+	 * Returns an object with Babel options for the source code.
+	 *
+	 * @param {String} format
+	 * @returns {Object} options
+	 */
+	getBabelOptionsForSource( format ) {
 		return {
 			plugins: utils.getBabelPlugins( format ),
 			// Ensure that all paths ends with '.js' because Require.JS (unlike Common.JS/System.JS)
@@ -123,6 +140,12 @@ require( [ 'tests' ], bender.defer(), function( err ) {
 		};
 	},
 
+	/**
+	 * Returns an object with Babel options for the test code.
+	 *
+	 * @param {String} format
+	 * @returns {Object} options
+	 */
 	getBabelOptionsForTests( format ) {
 		return {
 			plugins: utils.getBabelPlugins( format ),
@@ -132,6 +155,12 @@ require( [ 'tests' ], bender.defer(), function( err ) {
 		};
 	},
 
+	/**
+	 * Returns an array of Babel plugins to use.
+	 *
+	 * @param {String} format
+	 * @returns {Array}
+	 */
 	getBabelPlugins( format ) {
 		const babelModuleTranspilers = {
 			amd: 'amd',
@@ -155,6 +184,11 @@ require( [ 'tests' ], bender.defer(), function( err ) {
 		];
 	},
 
+	/**
+	 * Appends the {@link #benderLauncherCode} at the end of the file.
+	 *
+	 * @returns {Stream}
+	 */
 	appendBenderLauncher() {
 		return through( { objectMode: true }, ( file, encoding, callback ) => {
 			file.contents = new Buffer( file.contents.toString() + utils.benderLauncherCode );
@@ -180,16 +214,15 @@ require( [ 'tests' ], bender.defer(), function( err ) {
 		} );
 	},
 
-	// TODO
-	// Update the names of the next two methods and their docs.
-
 	/**
-	 * Moves files out of `ckeditor5-xxx/src/*` directories to `ckeditor5-xxx/*`.
-	 * And `ckeditor5-xxx/tests/*` to `tests/ckeditor5-xxx/`.
+	 * Processes paths of files inside CKEditor5 packages.
+	 *
+	 * * `ckeditor5-xxx/src/foo/bar.js` -> `ckeditor5/xxx/foo/bar.js`
+	 * * `ckeditor5-xxx/tests/foo/bar.js` -> `tests/xxx/foo/bar.js`
 	 *
 	 * @returns {Stream}
 	 */
-	unpackPackages() {
+	renamePackageFiles() {
 		return rename( ( file ) => {
 			const dirFrags = file.dirname.split( path.sep );
 
@@ -207,10 +240,12 @@ require( [ 'tests' ], bender.defer(), function( err ) {
 				// Remove 'src/'.
 				dirFrags.splice( 1, 1 );
 
+				// And prepend 'ckeditor5/'.
 				dirFrags.unshift( 'ckeditor5' );
 			} else if ( firstFrag == 'tests' ) {
 				// Remove 'tests/' from the package dir.
 				dirFrags.splice( 1, 1 );
+
 				// And prepend 'tests/'.
 				dirFrags.unshift( 'tests' );
 			} else {
@@ -222,11 +257,14 @@ require( [ 'tests' ], bender.defer(), function( err ) {
 	},
 
 	/**
-	 * Adds `ckeditor5/` to a file path.
+	 * Processes paths of files inside the main CKEditor5 package.
+	 *
+	 * * `src/foo/bar.js` -> `ckeditor5/foo/bar.js`
+	 * * `tests/foo/bar.js` -> `tests/foo/bar.js`
 	 *
 	 * @returns {Stream}
 	 */
-	wrapCKEditor5Package() {
+	renameCKEditor5Files() {
 		return rename( ( file ) => {
 			const dirFrags = file.dirname.split( path.sep );
 			const firstFrag = dirFrags[ 0 ];
@@ -257,6 +295,12 @@ require( [ 'tests' ], bender.defer(), function( err ) {
 		return source;
 	},
 
+	/**
+	 * Checks whether a file is a test file.
+	 *
+	 * @param {Vinyl} file
+	 * @returns {Boolean}
+	 */
 	isTestFile( file ) {
 		// TODO this should be based on bender configuration (config.tests.*.paths).
 		if ( !file.relative.startsWith( 'tests/' ) ) {
@@ -268,10 +312,22 @@ require( [ 'tests' ], bender.defer(), function( err ) {
 		return !dirFrags.some( dirFrag => dirFrag.startsWith( '_' ) );
 	},
 
-	isNotTestFile( file ) {
+	/**
+	 * Checks whether a file is a source file.
+	 *
+	 * @param {Vinyl} file
+	 * @returns {Boolean}
+	 */
+	isSourceFile( file ) {
 		return !utils.isTestFile( file );
 	},
 
+	/**
+	 * Checks whether a file is a JS file.
+	 *
+	 * @param {Vinyl} file
+	 * @returns {Boolean}
+	 */
 	isJSFile( file ) {
 		return file.path.endsWith( '.js' );
 	}

+ 15 - 8
dev/tests/build/tasks.js

@@ -17,6 +17,7 @@ const expect = chai.expect;
 const gutil = require( 'gulp-util' );
 const gulp = require( 'gulp' );
 const path = require( 'path' );
+const through = require( 'through2' );
 
 describe( 'build-tasks', () => {
 	let sandbox, tasks;
@@ -69,8 +70,8 @@ describe( 'build-tasks', () => {
 			sinon.assert.calledTwice( gulpSrcSpy );
 			sinon.assert.calledTwice( statStub );
 
-			expect( gulpSrcSpy.firstCall.args[ 0 ] ).to.equal( path.join( 'node_modules', 'ckeditor5-core', 'src/**/*.js' ) );
-			expect( gulpSrcSpy.secondCall.args[ 0 ] ).to.equal( path.join( 'node_modules', 'ckeditor5-toolbar', 'src/**/*.js' ) );
+			expect( gulpSrcSpy.firstCall.args[ 0 ] ).to.equal( path.join( 'node_modules', 'ckeditor5-core', '@(src|tests)', '**', '*' ) );
+			expect( gulpSrcSpy.secondCall.args[ 0 ] ).to.equal( path.join( 'node_modules', 'ckeditor5-toolbar', '@(src|tests)', '**', '*' ) );
 		} );
 
 		it( 'should skip files and resolve symbolic links', () => {
@@ -112,6 +113,8 @@ describe( 'build-tasks', () => {
 				} )
 			];
 
+			let written = 0;
+
 			// Stub input stream.
 			sandbox.stub( tasks.src, 'all', () => {
 				const fakeInputStream = new stream.Readable( { objectMode: true } );
@@ -124,19 +127,23 @@ describe( 'build-tasks', () => {
 
 			// Stub output stream.
 			sandbox.stub( utils, 'dist', () => {
-				const fakeOutputStream = new stream.Writable( { objectMode: true } );
-				fakeOutputStream._write = ( file, encoding, done ) => {
+				return through( { objectMode: true }, ( file, encoding, cb ) => {
+					written++;
+
 					const result = babel.transform( code, { plugins: [ 'transform-es2015-modules-amd' ] } );
 					// Check if provided code was transformed by babel.
 					expect( file.contents.toString() ).to.equal( result.code );
-					done();
-				};
 
-				return fakeOutputStream;
+					cb( null, file );
+				} );
 			} );
 
 			const conversionStream = build();
-			conversionStream.on( 'finish', () => done() );
+
+			conversionStream.on( 'finish', () => {
+				expect( written ).to.equal( 1 );
+				done();
+			} );
 		} );
 	} );
 } );

+ 280 - 49
dev/tests/build/utils.js

@@ -15,6 +15,7 @@ const gutil = require( 'gulp-util' );
 const path = require( 'path' );
 const stream = require( 'stream' );
 const Vinyl = require( 'vinyl' );
+const through = require( 'through2' );
 
 describe( 'build-utils', () => {
 	const utils = require( '../../tasks/gulp/build/utils' );
@@ -34,6 +35,17 @@ describe( 'build-utils', () => {
 			const ret = utils.noop();
 			expect( ret instanceof PassThrough ).to.equal( true );
 		} );
+
+		it( 'should return a duplex stream when given a callback and call that callback', () => {
+			const spy = sinon.spy();
+			const ret = utils.noop( spy );
+
+			ret.write( 'foo' );
+
+			expect( spy.called ).to.equal( true );
+			expect( ret.writable ).to.equal( true );
+			expect( ret.readable ).to.equal( true );
+		} );
 	} );
 
 	describe( 'dist', () => {
@@ -50,37 +62,35 @@ describe( 'build-utils', () => {
 	} );
 
 	describe( 'transpile', () => {
-		it( 'should throw an exception when incorrect format is provided', () => {
-			const transpileSpy = sandbox.spy( utils, 'transpile' );
-			const format = 'incorrect-format';
-
-			expect( () => {
-				transpileSpy( format );
-			} ).to.throw( Error, `Incorrect format: ${ format }` );
-		} );
-
 		it( 'should return babel transform stream', ( done ) => {
-			const modulePath = '../files/utils/lib';
-			const babelStream = utils.transpile( 'amd' );
 			const Stream = stream.Stream;
+			const modulePath = '../files/utils/lib';
 			const appendModuleExtensionSpy = sandbox.spy( utils, 'appendModuleExtension' );
 
+			const babelStream = utils.transpile( 'amd', utils.getBabelOptionsForTests( 'amd' ) );
+
 			expect( babelStream instanceof Stream ).to.equal( true );
 			expect( babelStream.readable ).to.equal( true );
 			expect( babelStream.writable ).to.equal( true );
 
-			babelStream.on( 'finish', ( ) => {
+			babelStream.on( 'finish', () => {
 				sinon.assert.calledOnce( appendModuleExtensionSpy );
-				sinon.assert.calledWithExactly( appendModuleExtensionSpy, modulePath );
-				expect( appendModuleExtensionSpy.firstCall.returnValue ).to.equal( modulePath + '.js' );
+				expect( appendModuleExtensionSpy.args[ 0 ][ 0 ] ).to.equal( modulePath );
+
 				done();
 			} );
 
+			babelStream.pipe(
+				utils.noop( ( file ) => {
+					expect( file.contents.toString() ).to.match( /define\(\'tests\'/ );
+				} )
+			);
+
 			babelStream.write( new Vinyl( {
 				cwd: '/',
 				base: '/test/',
 				path: '/test/file.js',
-				contents: new Buffer( `import * as lib from '${ modulePath }'` )
+				contents: new Buffer( `import * as lib from '${ modulePath }';` )
 			} ) );
 
 			babelStream.end();
@@ -108,7 +118,88 @@ describe( 'build-utils', () => {
 		} );
 	} );
 
+	describe( 'getBabelOptionsForSource', () => {
+		it( 'should return plugins for amd format', () => {
+			const plugins = [ 'foo' ];
+			sandbox.stub( utils, 'getBabelPlugins', () => plugins );
+
+			const options = utils.getBabelOptionsForSource( 'format' );
+
+			expect( options ).to.have.property( 'plugins', plugins );
+			expect( options ).to.have.property( 'resolveModuleSource' );
+		} );
+	} );
+
+	describe( 'getBabelOptionsForTests', () => {
+		it( 'should return plugins for amd format', () => {
+			const plugins = [ 'foo' ];
+			sandbox.stub( utils, 'getBabelPlugins', () => plugins );
+
+			const options = utils.getBabelOptionsForTests( 'format' );
+
+			expect( options ).to.have.property( 'plugins', plugins );
+			expect( options ).to.have.property( 'resolveModuleSource' );
+			expect( options ).to.have.property( 'moduleIds', true );
+			expect( options ).to.have.property( 'moduleId', 'tests' );
+		} );
+	} );
+
+	describe( 'getBabelPlugins', () => {
+		it( 'should return plugins for amd format', () => {
+			expect( utils.getBabelPlugins( 'amd' ) ).to.be.an( 'array' );
+		} );
+
+		it( 'should throw an exception when incorrect format is provided', () => {
+			const format = 'incorrect-format';
+
+			expect( () => {
+				utils.getBabelPlugins( format );
+			} ).to.throw( Error, `Incorrect format: ${ format }` );
+		} );
+	} );
+
+	describe( 'getBabelPlugins', () => {
+		it( 'should return plugins for amd format', () => {
+			expect( utils.getBabelPlugins( 'amd' ) ).to.be.an( 'array' );
+		} );
+
+		it( 'should throw an exception when incorrect format is provided', () => {
+			const format = 'incorrect-format';
+
+			expect( () => {
+				utils.getBabelPlugins( format );
+			} ).to.throw( Error, `Incorrect format: ${ format }` );
+		} );
+	} );
+
 	describe( 'getConversionStreamGenerator', () => {
+		beforeEach( () => {
+			sandbox.stub( utils, 'getBabelOptionsForSource', () => 'src' );
+			sandbox.stub( utils, 'getBabelOptionsForTests', () => 'tests' );
+
+			// Stub to avoid writing to the fs.
+			sandbox.stub( utils, 'dist', () => utils.noop() );
+
+			// The transpile converted with append to file contents what was
+			// passed to it as an options object and that's a result of getBabelOptions*,
+			// which is stubbed above (will return 'src' or 'tests').
+			sandbox.stub( utils, 'transpile', ( format, options ) => {
+				return through( { objectMode: true }, ( file, encoding, callback ) => {
+					file.contents = new Buffer( file.contents.toString() + ';' + format + ';' + options );
+
+					callback( null, file );
+				} );
+			} );
+
+			sandbox.stub( utils, 'appendBenderLauncher', () => {
+				return through( { objectMode: true }, ( file, encoding, callback ) => {
+					file.contents = new Buffer( file.contents.toString() + ';launcher' );
+
+					callback( null, file );
+				} );
+			} );
+		} );
+
 		it( 'should return function that can be used for creating conversion streams', () => {
 			const distDir = 'dist/';
 			const formats = [ 'amd', 'cjs', 'esnext' ];
@@ -117,21 +208,72 @@ describe( 'build-utils', () => {
 
 			expect( streams.length ).to.equal( formats.length );
 		} );
+
+		describe( 'created conversion stream', () => {
+			it( 'should process source JS file', ( done ) => {
+				const distDir = 'dist/';
+				const formats = [ 'amd' ];
+				const fn = utils.getConversionStreamGenerator( distDir );
+				const streams = formats.reduce( fn, [] );
+
+				expect( streams ).to.have.length( 1 );
+
+				const stream = streams[ 0 ];
+
+				stream.pipe(
+					utils.noop( ( file ) => {
+						expect( file.contents.toString() ).to.equal( 'foo();amd;src' );
+						done();
+					} )
+				);
+
+				stream.write( new Vinyl( {
+					cwd: './',
+					path: 'ckeditor5/core/file.js',
+					contents: new Buffer( 'foo()' )
+				} ) );
+			} );
+		} );
+
+		describe( 'created conversion stream', () => {
+			it( 'should process test file', ( done ) => {
+				const distDir = 'dist/';
+				const formats = [ 'cjs' ];
+				const fn = utils.getConversionStreamGenerator( distDir );
+				const streams = formats.reduce( fn, [] );
+
+				expect( streams ).to.have.length( 1 );
+
+				const stream = streams[ 0 ];
+
+				stream.pipe(
+					utils.noop( ( file ) => {
+						expect( file.contents.toString() ).to.equal( 'foo();cjs;tests;launcher' );
+						done();
+					} )
+				);
+
+				stream.write( new Vinyl( {
+					cwd: './',
+					path: 'tests/core/file.js',
+					contents: new Buffer( 'foo()' )
+				} ) );
+			} );
+		} );
 	} );
 
 	describe( 'pickVersionedFile', () => {
 		it( 'should rename file for provided format', ( done ) => {
 			const rename = utils.pickVersionedFile( 'amd' );
 
-			rename.once( 'finish', () => {
-				done();
-			} );
-
-			rename.on( 'data', ( data ) => {
-				expect( data.basename ).to.equal( 'load.js' );
-			} );
+			rename.pipe(
+				utils.noop( ( data ) => {
+					expect( data.basename ).to.equal( 'load.js' );
+					done();
+				} )
+			);
 
-			rename.write(  new Vinyl( {
+			rename.write( new Vinyl( {
 				cwd: '/',
 				base: '/test/',
 				path: '/test/load__amd.js',
@@ -142,21 +284,39 @@ describe( 'build-utils', () => {
 		} );
 	} );
 
-	describe( 'unpackPackages', () => {
-		it( 'should move files to correct directories', ( done ) => {
-			const rename = utils.unpackPackages();
+	describe( 'renamePackageFiles', () => {
+		it( 'should move source files to correct directories', ( done ) => {
+			const rename = utils.renamePackageFiles();
 
-			rename.once( 'finish', () => {
-				done();
-			} );
+			rename.pipe(
+				utils.noop( ( data ) => {
+					expect( data.path ).to.equal( 'ckeditor5/core/foo/file.js' );
+					done();
+				} )
+			);
 
-			rename.on( 'data', ( data ) => {
-				expect( data.path ).to.equal( 'ckeditor5-core/file.js' );
-			} );
+			rename.write( new Vinyl( {
+				cwd: './',
+				path: 'ckeditor5-core/src/foo/file.js',
+				contents: new Buffer( '' )
+			} ) );
+
+			rename.end();
+		} );
+
+		it( 'should move test files to correct directories', ( done ) => {
+			const rename = utils.renamePackageFiles();
+
+			rename.pipe(
+				utils.noop( ( data ) => {
+					expect( data.path ).to.equal( 'tests/core/foo/file.js' );
+					done();
+				} )
+			);
 
 			rename.write( new Vinyl( {
 				cwd: './',
-				path: 'ckeditor5-core/src/file.js',
+				path: 'ckeditor5-core/tests/foo/file.js',
 				contents: new Buffer( '' )
 			} ) );
 
@@ -164,7 +324,7 @@ describe( 'build-utils', () => {
 		} );
 
 		it( 'should throw error when wrong path provided 1', () => {
-			const rename = utils.unpackPackages();
+			const rename = utils.renamePackageFiles();
 
 			expect( () => {
 				rename.write( new Vinyl( {
@@ -176,7 +336,7 @@ describe( 'build-utils', () => {
 		} );
 
 		it( 'should throw error when wrong path provided 2', () => {
-			const rename = utils.unpackPackages();
+			const rename = utils.renamePackageFiles();
 
 			expect( () => {
 				rename.write( new Vinyl( {
@@ -188,31 +348,59 @@ describe( 'build-utils', () => {
 		} );
 	} );
 
-	describe( 'wrapCKEditor5Package', () => {
-		it( 'should add `ckeditor5/` to a file path', ( done ) => {
-			const rename = utils.wrapCKEditor5Package();
-			const filePath = './test/file.js';
-			const path = require( 'path' );
+	describe( 'renameCKEditor5Files', () => {
+		it( 'should move source files to correct directories', ( done ) => {
+			const rename = utils.renameCKEditor5Files();
 
-			rename.once( 'finish', () => {
-				done();
-			} );
+			rename.pipe(
+				utils.noop( ( data ) => {
+					expect( data.path ).to.equal( 'ckeditor5/foo/file.js' );
+					done();
+				} )
+			);
 
-			rename.on( 'data', ( data ) => {
-				expect( data.path ).to.equal( path.join( 'ckeditor5', filePath ) );
-			} );
+			rename.write( new Vinyl( {
+				cwd: './',
+				path: 'src/foo/file.js',
+				contents: new Buffer( '' )
+			} ) );
+
+			rename.end();
+		} );
+
+		it( 'should move test files to correct directories', ( done ) => {
+			const rename = utils.renameCKEditor5Files();
+
+			rename.pipe(
+				utils.noop( ( data ) => {
+					expect( data.path ).to.equal( 'tests/foo/file.js' );
+					done();
+				} )
+			);
 
-			rename.write(  new Vinyl( {
+			rename.write( new Vinyl( {
 				cwd: './',
-				path: filePath,
+				path: 'tests/foo/file.js',
 				contents: new Buffer( '' )
 			} ) );
 
 			rename.end();
 		} );
+
+		it( 'should throw error when wrong path provided 1', () => {
+			const rename = utils.renameCKEditor5Files();
+
+			expect( () => {
+				rename.write( new Vinyl( {
+					cwd: './',
+					path: 'plugin/src/file.js',
+					contents: new Buffer( '' )
+				} ) );
+			} ).to.throw( Error );
+		} );
 	} );
 
-	describe( 'appendModuleExtension', (  ) => {
+	describe( 'appendModuleExtension', () => {
 		it( 'appends module extension when path provided', () => {
 			const filePath = './path/to/file';
 			const source = utils.appendModuleExtension( filePath );
@@ -234,4 +422,47 @@ describe( 'build-utils', () => {
 			expect( source ).to.equal( module );
 		} );
 	} );
+
+	describe( 'appendBenderLauncher', () => {
+		it( 'appends the launcher code to a file', ( done ) => {
+			const stream = utils.appendBenderLauncher();
+
+			stream.pipe(
+				utils.noop( ( data ) => {
+					expect( data.contents.toString() ).equal( 'foo();' + utils.benderLauncherCode );
+					done();
+				} )
+			);
+
+			stream.write( new Vinyl( {
+				cwd: './',
+				path: 'tests/file.js',
+				contents: new Buffer( 'foo();' )
+			} ) );
+
+			stream.end();
+		} );
+	} );
+
+	describe( 'isTestFile', () => {
+		function test( path, expected ) {
+			it( `returns ${ expected} for ${ path }`, () => {
+				const file = new Vinyl( {
+					cwd: './',
+					path: path,
+					contents: new Buffer( '' )
+				} );
+
+				expect( utils.isTestFile( file ) ).to.equal( expected );
+			} );
+		}
+
+		test( 'tests/file.js', true );
+		test( 'tests/foo/file.js', true );
+		test( 'tests/tests.js', true );
+
+		test( 'foo/file.js', false );
+		test( 'foo/tests/file.js', false );
+		test( 'tests/_foo/file.js', false );
+	} );
 } );