ソースを参照

Merge branch 'master' into t/135

Piotrek Koszuliński 9 年 前
コミット
7a29eee51e

+ 0 - 10
ckeditor.js

@@ -7,7 +7,6 @@
 
 import Editor from './ckeditor5/editor.js';
 import Collection from './ckeditor5/utils/collection.js';
-import Config from './ckeditor5/utils/config.js';
 import CKEditorError from './ckeditor5/utils/ckeditorerror.js';
 import isArrayLike from './ckeditor5/utils/lib/lodash/isArrayLike.js';
 import clone from './ckeditor5/utils/lib/lodash/clone.js';
@@ -28,15 +27,6 @@ const CKEDITOR = {
 	instances: new Collection(),
 
 	/**
-	 * Holds global configuration defaults, which will be used by editor instances when such configurations are not
-	 * available on them directly.
-	 *
-	 * @readonly
-	 * @member {utils.Config} CKEDITOR.config
-	 */
-	config: new Config(),
-
-	/**
 	 * Creates an editor instance for the provided DOM element.
 	 *
 	 * The creation of editor instances is an asynchronous operation, therefore a promise is returned by this

+ 4 - 0
dev/tasks/build/tasks.js

@@ -336,5 +336,9 @@ module.exports = ( config ) => {
 	gulp.task( 'build:clean:js:esnext', () => tasks.clean.js( { formats: [ 'esnext' ] } ) );
 	gulp.task( 'build:js:esnext', [ 'build:clean:js:esnext' ], () => tasks.build.js( { formats: [ 'esnext' ] } ) );
 
+	// Tasks specific for testing under node.
+	gulp.task( 'build:clean:js:cjs', () => tasks.clean.js( { formats: [ 'cjs' ] } ) );
+	gulp.task( 'build:js:cjs', [ 'build:clean:js:cjs' ], () => tasks.build.js( { formats: [ 'cjs' ] } ) );
+
 	return tasks;
 };

+ 32 - 3
dev/tasks/build/utils.js

@@ -109,7 +109,7 @@ require( [ 'tests' ], bender.defer(), function( err ) {
 				conversionPipes.push(
 					filterTests,
 					transpileTests,
-					utils.appendBenderLauncher(),
+					format === 'amd' ? utils.appendBenderLauncher() : utils.noop(),
 					filterTests.restore
 				);
 			}
@@ -165,7 +165,7 @@ require( [ 'tests' ], bender.defer(), function( err ) {
 			plugins: utils.getBabelPlugins( format ),
 			// Ensure that all paths ends with '.js' because Require.JS (unlike Common.JS/System.JS)
 			// will not add it to module names which look like paths.
-			resolveModuleSource: utils.appendModuleExtension
+			resolveModuleSource: format == 'cjs' ? utils.resolveModuleSource : utils.appendModuleExtension
 		};
 	},
 
@@ -178,7 +178,7 @@ require( [ 'tests' ], bender.defer(), function( err ) {
 	getBabelOptionsForTests( format ) {
 		return {
 			plugins: utils.getBabelPlugins( format ),
-			resolveModuleSource: utils.appendModuleExtension,
+			resolveModuleSource: format == 'cjs' ? utils.resolveModuleSource : utils.appendModuleExtension,
 			moduleIds: true,
 			moduleId: 'tests'
 		};
@@ -596,6 +596,35 @@ require( [ 'tests' ], bender.defer(), function( err ) {
 				} )
 			);
 		} );
+	},
+
+	/**
+	 * Resolves CommonJS module source path.
+	 *
+	 * @param {String} source Module path passed to require() method.
+	 * @param {String} file Path to a file where require() method is called.
+	 * @returns {String} Fixed module path.
+	 */
+	resolveModuleSource( source, file ) {
+		// If path is relative - leave it as is.
+		if ( !path.isAbsolute( source ) ) {
+			return source;
+		}
+
+		// Find relative path of test file from cwd directory.
+		let testFile = path.relative( process.cwd(), file );
+
+		// Append `/` as all files uses it as root inside transpiled versions.
+		testFile = path.join( path.sep, testFile );
+
+		// Find relative path from test file to source.
+		let relativePath = path.relative( path.dirname( testFile ), path.dirname( source ) );
+		relativePath = path.join( relativePath, path.basename( source ) );
+
+		// Convert windows path to posix.
+		relativePath = relativePath.replace( /\\/g, '/' );
+
+		return utils.appendModuleExtension( ( relativePath.startsWith( '../' ) ? '' : './' ) + relativePath );
 	}
 };
 

+ 126 - 0
dev/tasks/test/tasks.js

@@ -0,0 +1,126 @@
+/* jshint node: true, esnext: true */
+
+'use strict';
+
+const gulp = require( 'gulp' );
+const istanbul = require( 'gulp-istanbul' );
+const gutil = require( 'gulp-util' );
+const mocha = require( 'gulp-mocha' );
+const chai = require( 'chai' );
+const filterBy = require( 'gulp-filter-by' );
+const filter = require( 'gulp-filter' );
+const sinon = require( 'sinon' );
+const devTools = require( '../dev/utils/tools' );
+const semver = require( 'semver' );
+const buildUtils = require( '../build/utils' );
+const benderConfig = require( '../../../bender' );
+
+/**
+ * Defines Node.js testing task.
+ *
+ * To run tests under node:
+ *
+ *		gulp test:node
+ *
+ * To run build before testing:
+ *
+ *		gulp test:node:build
+ *
+ * To run testing with code coverage:
+ *
+ *		gulp test:node:coverage
+ */
+module.exports = () => {
+	const ignoreRegexp = /\/\* ?bender-tags:.*\bbrowser-only\b.*\*\//;
+
+	// Inject globals before running tests.
+	global.should = chai.should;
+	global.expect = chai.expect;
+	global.assert = chai.assert;
+	global.sinon = sinon;
+
+	const tasks = {
+		/**
+		 * Is set to `true` when code coverage report will be displayed.
+		 *
+		 * @type {Boolean}
+		 */
+		coverage: false,
+
+		/**
+		 * Prepares files for coverage report.
+		 *
+		 * @returns {Stream}
+		 */
+		prepareCoverage() {
+			const src = benderConfig.coverage.paths.map( ( item ) => {
+				return item.replace( 'build/amd/', 'build/cjs/' );
+			} );
+			tasks.coverage = true;
+
+			return gulp.src( src )
+				.pipe( istanbul() )
+				.pipe( istanbul.hookRequire() );
+		},
+
+		/**
+		 * Runs tests in Node.js environment.
+		 *
+		 * @returns {Stream}
+		 */
+		testInNode() {
+			const minVersion = '6.0.0';
+
+			if ( semver.lt( process.version, minVersion ) ) {
+				throw new gutil.PluginError( {
+					plugin: 'test-node',
+					message: `Wrong Node.js version. Please use Node.js in version v${ minVersion } or higher.`
+				} );
+			}
+
+			const benderSrc = benderConfig.tests.all.paths.map( ( item ) => {
+				return item.replace( 'build/amd/', 'build/cjs/' ) + '/*.js' ;
+			} );
+
+			const src = [
+				...benderSrc,
+				'!build/cjs/tests/{ui,ui-*}/**/*.js',
+				'!build/cjs/tests/theme-*/**/*.js'
+			];
+
+			return gulp.src( src )
+				.pipe( tasks.skipManual() )
+				.pipe( tasks.skipIgnored() )
+				.pipe( mocha( { reporter: 'progress' } ) )
+				.pipe( tasks.coverage ? istanbul.writeReports() : buildUtils.noop() );
+		},
+
+		/**
+		 * Removes manual test files from source stream. It checks if the markdown file with the same name exists.
+		 *
+		 * @returns {Stream}
+		 */
+		skipManual() {
+			return filter( ( file ) => {
+				return !devTools.isFile( file.path.slice( 0, -3 ) + '.md' );
+			} );
+		},
+
+		/**
+		 * Skips test files that are marked to be ignored when testing outside browser.
+		 * To ignore file, add `browser-only` to bender-tags comment in test file.
+		 *
+		 * @returns {Stream}
+		 */
+		skipIgnored() {
+			return filterBy( file => !file.contents.toString().match( ignoreRegexp ) );
+		}
+	};
+
+	gulp.task( 'test:node:pre-coverage', [ 'build:js:cjs' ], tasks.prepareCoverage );
+	gulp.task( 'test:node', tasks.testInNode );
+	gulp.task( 'test:node:build', [ 'build:js:cjs' ] , tasks.testInNode );
+	gulp.task( 'test:node:coverage', [ 'build:js:cjs', 'test:node:pre-coverage' ], tasks.testInNode );
+
+	return tasks;
+};

+ 74 - 4
dev/tests/build/utils.js

@@ -123,10 +123,22 @@ describe( 'build-utils', () => {
 			const plugins = [ 'foo' ];
 			sandbox.stub( utils, 'getBabelPlugins', () => plugins );
 
-			const options = utils.getBabelOptionsForSource( 'format' );
+			const options = utils.getBabelOptionsForSource( 'amd' );
 
 			expect( options ).to.have.property( 'plugins', plugins );
 			expect( options ).to.have.property( 'resolveModuleSource' );
+			expect( options.resolveModuleSource ).to.equal( utils.appendModuleExtension );
+		} );
+
+		it( 'should return plugins for cjs format', () => {
+			const plugins = [ 'foo' ];
+			sandbox.stub( utils, 'getBabelPlugins', () => plugins );
+
+			const options = utils.getBabelOptionsForSource( 'cjs' );
+
+			expect( options ).to.have.property( 'plugins', plugins );
+			expect( options ).to.have.property( 'resolveModuleSource' );
+			expect( options.resolveModuleSource ).to.equal( utils.resolveModuleSource );
 		} );
 	} );
 
@@ -135,12 +147,26 @@ describe( 'build-utils', () => {
 			const plugins = [ 'foo' ];
 			sandbox.stub( utils, 'getBabelPlugins', () => plugins );
 
-			const options = utils.getBabelOptionsForTests( 'format' );
+			const options = utils.getBabelOptionsForTests( 'amd' );
+
+			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' );
+			expect( options.resolveModuleSource ).to.equal( utils.appendModuleExtension );
+		} );
+
+		it( 'should return plugins for cjs format', () => {
+			const plugins = [ 'foo' ];
+			sandbox.stub( utils, 'getBabelPlugins', () => plugins );
+
+			const options = utils.getBabelOptionsForTests( 'cjs' );
 
 			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' );
+			expect( options.resolveModuleSource ).to.equal( utils.resolveModuleSource );
 		} );
 	} );
 
@@ -149,6 +175,10 @@ describe( 'build-utils', () => {
 			expect( utils.getBabelPlugins( 'amd' ) ).to.be.an( 'array' );
 		} );
 
+		it( 'should return plugins for cjs format', () => {
+			expect( utils.getBabelPlugins( 'cjs' ) ).to.be.an( 'array' );
+		} );
+
 		it( 'should throw an exception when incorrect format is provided', () => {
 			const format = 'incorrect-format';
 
@@ -236,7 +266,31 @@ describe( 'build-utils', () => {
 		} );
 
 		describe( 'created conversion stream', () => {
-			it( 'should process test file', ( done ) => {
+			it( 'should process test file in amd format', ( done ) => {
+				const buildDir = 'build/';
+				const formats = [ 'amd' ];
+				const fn = utils.getConversionStreamGenerator( buildDir );
+				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;tests;launcher' );
+						done();
+					} )
+				);
+
+				stream.write( new Vinyl( {
+					cwd: './',
+					path: 'tests/core/file.js',
+					contents: new Buffer( 'foo()' )
+				} ) );
+			} );
+
+			it( 'should process test file in cjs format', ( done ) => {
 				const buildDir = 'build/';
 				const formats = [ 'cjs' ];
 				const fn = utils.getConversionStreamGenerator( buildDir );
@@ -248,7 +302,7 @@ describe( 'build-utils', () => {
 
 				stream.pipe(
 					utils.noop( ( file ) => {
-						expect( file.contents.toString() ).to.equal( 'foo();cjs;tests;launcher' );
+						expect( file.contents.toString() ).to.equal( 'foo();cjs;tests' );
 						done();
 					} )
 				);
@@ -730,4 +784,20 @@ describe( 'build-utils', () => {
 			expect( streams ).to.have.length( 2 );
 		} );
 	} );
+
+	describe( 'resolveModuleSource', () => {
+		it( 'does not modify relative source paths', () => {
+			const source = '../module';
+			const resolved = utils.resolveModuleSource( source, '' );
+			expect( resolved ).to.equal( source );
+		} );
+
+		it( 'resolves absolute source paths', () => {
+			const source = '/ckeditor5/path/to/module.js';
+			const file = path.join( process.cwd(), 'tests', 'module', 'module.js' );
+
+			const resolved = utils.resolveModuleSource( source, file );
+			expect( resolved ).to.equal( '../../ckeditor5/path/to/module.js' );
+		} );
+	} );
 } );

+ 78 - 0
dev/tests/test/tasks.js

@@ -0,0 +1,78 @@
+/**
+ * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/* global describe, it, sinon */
+
+'use strict';
+
+const tasks = require( '../../tasks/test/tasks' )();
+const buildUtils = require( '../../tasks/build/utils' );
+const devTools = require( '../../tasks/dev/utils/tools' );
+const Vinyl = require( 'vinyl' );
+
+describe( 'test-node', () => {
+	describe( 'skipManual', () => {
+		it( 'should skip manual tests', ( done ) => {
+			const stream = tasks.skipManual();
+			const spy = sinon.spy();
+			const stub = sinon.stub( devTools, 'isFile', ( file ) => {
+				return file == 'file1.md';
+			} );
+			const unitTestFile = new Vinyl( {
+				cwd: './',
+				path: 'file2.js',
+				contents: null
+			} );
+			const manualTestFile = new Vinyl( {
+				cwd: './',
+				path: 'file1.js',
+				contents: null
+			} );
+
+			stream.pipe( buildUtils.noop( spy ) );
+
+			stream.once( 'finish', () => {
+				sinon.assert.calledOnce( spy );
+				sinon.assert.calledWithExactly( spy, unitTestFile );
+				done();
+			} );
+
+			stream.write( manualTestFile );
+			stream.write( unitTestFile );
+
+			stream.end();
+			stub.restore();
+		} );
+	} );
+
+	describe( 'skipIgnored', () => {
+		it( 'should skip files marked to ignore', ( done ) => {
+			const stream = tasks.skipIgnored();
+			const spy = sinon.spy();
+			const unitTestFile = new Vinyl( {
+				cwd: './',
+				path: 'file2.js',
+				contents: new Buffer( '' )
+			} );
+			const manualTestFile = new Vinyl( {
+				cwd: './',
+				path: 'file1.js',
+				contents: new Buffer( '/* bender-tags: tag, browser-only */' )
+			} );
+			const noop = buildUtils.noop( spy );
+			noop.once( 'finish', () => {
+				sinon.assert.calledOnce( spy );
+				sinon.assert.calledWithExactly( spy, unitTestFile );
+				done();
+			} );
+
+			stream.pipe( noop );
+			stream.write( manualTestFile );
+			stream.write( unitTestFile );
+
+			stream.end();
+		} );
+	} );
+} );

+ 1 - 0
gulpfile.js

@@ -18,6 +18,7 @@ const config = {
 require( './dev/tasks/build/tasks' )( config );
 require( './dev/tasks/dev/tasks' )( config );
 require( './dev/tasks/lint/tasks' )( config );
+require( './dev/tasks/test/tasks' )( config );
 require( './dev/tasks/docs/tasks' )( config );
 
 gulp.task( 'default', [ 'build' ] );

+ 6 - 0
package.json

@@ -9,7 +9,9 @@
   ],
   "dependencies": {
     "ckeditor5-creator-classic": "ckeditor/ckeditor5-creator-classic#t/1",
+    "ckeditor5-delete": "ckeditor/ckeditor5-delete",
     "ckeditor5-engine": "ckeditor/ckeditor5-engine",
+    "ckeditor5-enter": "ckeditor/ckeditor5-enter",
     "ckeditor5-theme-lark": "ckeditor/ckeditor5-theme-lark",
     "ckeditor5-ui": "ckeditor/ckeditor5-ui#t/22",
     "ckeditor5-ui-default": "ckeditor/ckeditor5-ui-default#t/10",
@@ -36,10 +38,13 @@
     "gulp": "^3.9.0",
     "gulp-babel": "^6.1.0",
     "gulp-filter": "^3.0.1",
+    "gulp-filter-by": "^1.2.0",
+    "gulp-istanbul": "^0.10.4",
     "gulp-jscs": "^3.0.2",
     "gulp-jsdoc3": "^0.2.0",
     "gulp-jshint": "^2.0.0",
     "gulp-mirror": "^1",
+    "gulp-mocha": "^2.2.0",
     "gulp-plumber": "^1.1.0",
     "gulp-rename": "^1.2.2",
     "gulp-replace": "^0.5.4",
@@ -62,6 +67,7 @@
     "node-sass": "^3.7.0",
     "replace": "^0.3.0",
     "run-sequence": "^1.1.5",
+    "semver": "^5.1.0",
     "shelljs": "^0",
     "sinon": "^1.17.0",
     "through2": "^2.0.0"

+ 2 - 6
src/editor.js

@@ -6,7 +6,7 @@
 'use strict';
 
 import ObservableMixin from './utils/observablemixin.js';
-import EditorConfig from './editorconfig.js';
+import Config from './utils/config.js';
 import PluginCollection from './plugincollection.js';
 import EditableCollection from './editablecollection.js';
 import CKEditorError from './utils/ckeditorerror.js';
@@ -52,14 +52,10 @@ export default class Editor {
 		/**
 		 * Holds all configurations specific to this editor instance.
 		 *
-		 * This instance of the {@link utils.Config} class is customized so its {@link utils.Config#get} method will retrieve
-		 * global configurations available in {@link CKEDITOR.config} if configurations are not found in the
-		 * instance itself.
-		 *
 		 * @readonly
 		 * @member {utils.Config} ckeditor5.Editor#config
 		 */
-		this.config = config = new EditorConfig( config );
+		this.config = config = new Config( config );
 
 		/**
 		 * The plugins loaded and in use by this editor instance.

+ 0 - 35
src/editorconfig.js

@@ -1,35 +0,0 @@
-/**
- * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
- * For licensing, see LICENSE.md.
- */
-
-'use strict';
-
-import CKEDITOR from '../ckeditor.js';
-import Config from './utils/config.js';
-
-/**
- * Handles a configuration dictionary for an editor instance.
- *
- * The basic difference between {@link ckeditor5.EditorConfig} and {@link utils.Config} is that {@link ckeditor5.EditorConfig#get} retrieves
- * configurations from {@link CKEDITOR#config} if they are not found.
- *
- * @memberOf ckeditor5
- * @extends utils.Config
- */
-export default class EditorConfig extends Config {
-	/**
-	 * @inheritDoc
-	 */
-	get() {
-		// Try to take it from this editor instance.
-		let value = super.get.apply( this, arguments );
-
-		// If the configuration is not defined in the instance, try to take it from CKEDITOR.config.
-		if ( typeof value == 'undefined' ) {
-			value = super.get.apply( CKEDITOR.config, arguments );
-		}
-
-		return value;
-	}
-}

+ 2 - 2
tests/_utils-tests/module__amd.js

@@ -23,9 +23,9 @@ describe( 'amdTestUtils', () => {
 		} );
 
 		it( 'generates an absolute path from a simple path', () => {
-			const path = getModulePath( 'engine/treeview' );
+			const path = getModulePath( 'engine/dataController' );
 
-			expect( path ).to.equal( '/ckeditor5/engine/treeview.js' );
+			expect( path ).to.equal( '/ckeditor5/engine/dataController.js' );
 		} );
 
 		it( 'does not process an absolute path', () => {

+ 103 - 0
tests/_utils-tests/module__cjs.js

@@ -0,0 +1,103 @@
+/**
+ * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/* global require, process */
+
+'use strict';
+
+import testUtils from '/tests/ckeditor5/_utils/utils.js';
+import moduleTestUtils from '/tests/ckeditor5/_utils/module.js';
+
+testUtils.createSinonSandbox();
+
+const path = require( 'path' );
+const cjsDir = path.join( process.cwd(), 'build', 'cjs' );
+
+describe( 'module utilities', () => {
+	const getModulePath = moduleTestUtils.getModulePath;
+
+	describe( 'getModulePath()', () => {
+		it( 'generates absolute path from a plugin name', () => {
+			const modulePath = getModulePath( 'foo' );
+
+			expect( modulePath ).to.equal( path.join( cjsDir,  '/ckeditor5/foo/foo.js' ) );
+		} );
+
+		it( 'generates an absolute path from a simple path', () => {
+			const modulePath = getModulePath( 'core/editor' );
+
+			expect( modulePath ).to.equal( path.join( cjsDir, '/ckeditor5/core/editor.js' ) );
+		} );
+
+		it( 'does not process an absolute path', () => {
+			const modulePath = getModulePath( '/foo/bar/bom.js' );
+
+			expect( modulePath ).to.equal( path.join( cjsDir, '/foo/bar/bom.js' ) );
+		} );
+	} );
+
+	describe( 'define()', () => {
+		it( 'defines a module using mockery', () => {
+			const mockery = require( 'mockery' );
+			const spy = testUtils.sinon.spy( mockery, 'registerMock' );
+
+			moduleTestUtils.define( 'test1', [ '/ckeditor.js', 'bar' ],  () => {}  );
+
+			expect( spy.calledOnce ).to.be.true;
+			expect( spy.args[ 0 ][ 0 ] ).to.equal( getModulePath( 'test1' ) );
+		} );
+
+		it( 'works with module name and body', () => {
+			const mockery = require( 'mockery' );
+			const spy = testUtils.sinon.spy( mockery, 'registerMock' );
+			const bodySpy = testUtils.sinon.spy( () => {} );
+
+			moduleTestUtils.define( 'test1', bodySpy );
+
+			expect( spy.calledOnce ).to.be.true;
+			expect( spy.args[ 0 ][ 0 ] ).to.equal( getModulePath( 'test1' ) );
+			expect( bodySpy.calledOnce ).to.be.true;
+
+			// No dependencies are passed - check if no arguments were passed to the function.
+			expect( bodySpy.args[ 0 ].length ).to.equal( 0 );
+		} );
+
+		// Note: this test only checks whether CommonJS version of `define()` doesn't totally fail when creating a
+		// circular dependency. The value of dependencies are not available anyway inside the
+		// amdTestUtils.define() callbacks because we lose the late-binding by immediately mapping modules to
+		// their default exports.
+		it( 'works with circular dependencies', () => {
+			moduleTestUtils.define( 'test-circular-a', [ 'test-circular-b' ], () => {
+				return 'a';
+			} );
+
+			moduleTestUtils.define( 'test-circular-b', [ 'test-circular-a' ], () => {
+				return 'b';
+			} );
+
+			const a = require( moduleTestUtils.getModulePath( 'test-circular-a' ) );
+			expect( a ).to.have.property( 'default', 'a' );
+
+			const b = require( moduleTestUtils.getModulePath( 'test-circular-b' ) );
+			expect( b ).to.have.property( 'default', 'b' );
+		} );
+	} );
+
+	describe( 'require', () => {
+		it( 'creates object with loaded modules', () => {
+			// Create first module using mockery library.
+			const mockery = require( 'mockery' );
+			mockery.registerMock( moduleTestUtils.getModulePath( 'module1' ), { default: 'foo' } );
+
+			// Create second module using define.
+			moduleTestUtils.define( 'module2', () => 'bar' );
+
+			const loadedModules = moduleTestUtils.require( { module1: 'module1',  module2: 'module2' } );
+
+			expect( loadedModules.module1 ).to.equal( 'foo' );
+			expect( loadedModules.module2 ).to.equal( 'bar' );
+		} );
+	} );
+} );

+ 2 - 2
tests/_utils/module__amd.js

@@ -100,10 +100,10 @@ const utils = {
 	 * This method uses {@link #getModulePath} to process module and dependency paths so you need to use
 	 * the simplified notation.
 	 *
-	 *		const modules = amdTestUtils.require( { treeView: 'engine/treeview/treeview' } );
+	 *		const modules = amdTestUtils.require( { modelDocument: 'engine/model/document' } );
 	 *
 	 *		// Later on, inside tests:
-	 *		const TreeView = modules.treeView;
+	 *		const ModelDocument = modules.modelDocument;
 	 *
 	 * @params {Object} modules The object (`ref => modulePath`) with modules to be loaded.
 	 * @returns {Object} The object that will hold the loaded modules.

+ 121 - 0
tests/_utils/module__cjs.js

@@ -0,0 +1,121 @@
+/**
+ * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/* globals require, process */
+
+'use strict';
+
+const mockery = require( 'mockery' );
+mockery.enable( {
+	warnOnReplace: false,
+	warnOnUnregistered: false
+} );
+const mocked = [];
+
+const path = require( 'path' );
+
+/**
+ * CommonJS tools related to CKEditor.
+ */
+const utils = {
+	/**
+	 * Helper for generating an absolute module path from a simplified name.
+	 *
+	 * Transforms:
+	 *
+	 * * `foo` -> `/path/dist/cjs/ckeditor5/foo/foo.js`
+	 * * `foo/bar` -> `/path/dist/cjs/ckeditor5/foo/bar.js`
+	 * * `/ckeditor5/foo.js` -> `/path/dist/cjs/ckeditor5/foo.js`
+	 *
+	 * @param {String} modulePath The simplified path.
+	 * @returns {String} The real path.
+	 */
+	getModulePath( modulePath ) {
+		// Do nothing – path is already absolute.
+		if ( modulePath.startsWith( '/' ) ) {
+			return path.join( process.cwd(), 'build', 'cjs', modulePath );
+		}
+
+		if ( modulePath.indexOf( '/' ) < 0 ) {
+			modulePath = modulePath + '/' + modulePath;
+		}
+
+		return path.join( process.cwd(), 'build', 'cjs', 'ckeditor5', modulePath + '.js' );
+	},
+
+	/**
+	 * Defines module in AMD style using CommonJS modules.
+	 *
+	 * This method uses {@link #getModulePath} to process module and dependency paths so you need to use
+	 * the simplified notation.
+	 *
+	 * For simplicity the dependencies passed to the `body` will be unwrapped
+	 * from the ES6 module object (so only the default export will be available). Also the returned value
+	 * will be automatically handled as a default export.
+	 *
+	 * See also module__amd.js file description.
+	 *
+	 * @param {String} path Path to the module.
+	 * @param {String[]} deps Dependencies of the module.
+	 * @param {Function} body Module body.
+	 */
+	define( path, deps, body ) {
+		if ( arguments.length == 2 ) {
+			body = deps;
+			deps = [];
+		}
+
+		deps = deps
+			.map( ( dependency ) => utils.getModulePath( dependency ) )
+			.map( ( dependency )  => {
+				// Checking if module is already mocked - if module was not mocked it might be a real module.
+				// Using require.resolve to check if module really exists without loading it ( it throws if module is
+				// not present). When module is not mocked and does not exist it will be undefined in module body.
+				try {
+					if ( mocked.indexOf( dependency ) < 0 ) {
+						// Test if required module exists without loading it.
+						require.resolve( dependency );
+					}
+				} catch ( e ) {
+					return;
+				}
+
+				// Return only default export.
+				return require( dependency ).default;
+			} );
+
+		mocked.push( utils.getModulePath( path ) );
+		mockery.registerMock( utils.getModulePath( path ), {
+			default: body.apply( this, deps )
+		} );
+	},
+
+	/**
+	 * Gets an object which holds the CKEditor modules. This function uses synchronous CommonJS `require()`
+	 * method, which means that after executing this method all modules are already loaded.
+	 *
+	 * This method uses {@link #getModulePath} to process module and dependency paths so you need to use
+	 * the simplified notation.
+	 *
+	 *		const modules = amdTestUtils.require( { editor: 'core/Editor' } );
+	 *
+	 *		// Later on, inside tests:
+	 *		const Editor = modules.editor;
+	 *
+	 * @params {Object} modules The object (`ref => modulePath`) with modules to be loaded.
+	 * @returns {Object} The object that will hold the loaded modules.
+	 */
+	require( modules ) {
+		const resolved = {};
+
+		for ( let name in modules ) {
+			resolved[ name ] = require( utils.getModulePath( modules[ name ] ) ).default;
+		}
+
+		return resolved;
+	}
+};
+
+export default utils;

+ 2 - 7
tests/ckeditor.js

@@ -3,13 +3,14 @@
  * For licensing, see LICENSE.md.
  */
 
+/* bender-tags: browser-only */
+
 'use strict';
 
 import testUtils from '/tests/ckeditor5/_utils/utils.js';
 
 import CKEDITOR from '/ckeditor.js';
 import Editor from '/ckeditor5/editor.js';
-import Config from '/ckeditor5/utils/config.js';
 import CKEditorError from '/ckeditor5/utils/ckeditorerror.js';
 
 const content = document.getElementById( 'content' );
@@ -212,9 +213,3 @@ describe( 'create', () => {
 		}
 	} );
 } );
-
-describe( 'config', () => {
-	it( 'should be an instance of Config', () => {
-		expect( CKEDITOR.config ).to.be.an.instanceof( Config );
-	} );
-} );

+ 2 - 0
tests/command/command.js

@@ -3,6 +3,8 @@
  * For licensing, see LICENSE.md.
  */
 
+/* bender-tags: browser-only */
+
 'use strict';
 
 import Editor from '/ckeditor5/editor.js';

+ 1 - 1
tests/creator/standardcreator.js

@@ -3,7 +3,7 @@
  * For licensing, see LICENSE.md.
  */
 
-/* bender-tags: creator */
+/* bender-tags: creator, browser-only */
 
 'use strict';
 

+ 2 - 0
tests/editable.js

@@ -3,6 +3,8 @@
  * For licensing, see LICENSE.md.
  */
 
+/* bender-tags: browser-only */
+
 'use strict';
 
 import Editor from '/ckeditor5/editor.js';

+ 170 - 0
tests/editor/baseeditor.js

@@ -0,0 +1,170 @@
+/**
+ * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/* bender-tags: editor */
+
+'use strict';
+
+import Editor from '/ckeditor5/editor.js';
+import Command from '/ckeditor5/command/command.js';
+import Locale from '/ckeditor5/utils/locale.js';
+import CKEditorError from '/ckeditor5/utils/ckeditorerror.js';
+import Document from '/ckeditor5/engine/treemodel/document.js';
+
+describe( 'Editor', () => {
+	describe( 'locale', () => {
+		it( 'is instantiated and t() is exposed', () => {
+			const editor = new Editor();
+
+			expect( editor.locale ).to.be.instanceof( Locale );
+			expect( editor.t ).to.equal( editor.locale.t );
+		} );
+
+		it( 'is configured with the config.lang', () => {
+			const editor = new Editor( null, { lang: 'pl' } );
+
+			expect( editor.locale.lang ).to.equal( 'pl' );
+		} );
+	} );
+
+	describe( 'destroy', () => {
+		it( 'should fire "destroy"', () => {
+			const editor = new Editor();
+			let spy = sinon.spy();
+
+			editor.on( 'destroy', spy );
+
+			return editor.destroy().then( () => {
+				expect( spy.calledOnce ).to.be.true;
+			} );
+		} );
+
+		// Note: Tests for destroying creators are in creator/creator.js.
+		// When destroying creator will be generalized to destroying plugins,
+		// move that code here.
+	} );
+
+	describe( 'execute', () => {
+		it( 'should execute specified command', () => {
+			const editor = new Editor();
+
+			let command = new Command( editor );
+			sinon.spy( command, '_execute' );
+
+			editor.commands.set( 'commandName', command );
+			editor.execute( 'commandName' );
+
+			expect( command._execute.calledOnce ).to.be.true;
+		} );
+
+		it( 'should throw an error if specified command has not been added', () => {
+			const editor = new Editor();
+
+			expect( () => {
+				editor.execute( 'command' );
+			} ).to.throw( CKEditorError, /^editor-command-not-found:/ );
+		} );
+	} );
+
+	describe( 'setData', () => {
+		let editor;
+
+		beforeEach( () => {
+			editor = new Editor();
+
+			editor.document = new Document();
+			editor.data = {
+				set: sinon.spy()
+			};
+		} );
+
+		it( 'should set data of the first root', () => {
+			editor.document.createRoot( 'firstRoot', 'div' );
+
+			editor.setData( 'foo' );
+
+			expect( editor.data.set.calledOnce ).to.be.true;
+			expect( editor.data.set.calledWithExactly( 'firstRoot', 'foo' ) ).to.be.true;
+		} );
+
+		it( 'should set data of the specified root', () => {
+			editor.setData( 'foo', 'someRoot' );
+
+			expect( editor.data.set.calledOnce ).to.be.true;
+			expect( editor.data.set.calledWithExactly( 'someRoot', 'foo' ) ).to.be.true;
+		} );
+
+		it( 'should throw when no roots', () => {
+			expect( () => {
+				editor.setData( 'foo' );
+			} ).to.throw( CKEditorError, /^editor-no-editable-roots:/ );
+		} );
+
+		it( 'should throw when more than one root and no root name given', () => {
+			editor.document.createRoot( 'firstRoot', 'div' );
+			editor.document.createRoot( 'secondRoot', 'div' );
+
+			expect( () => {
+				editor.setData( 'foo' );
+			} ).to.throw( CKEditorError, /^editor-editable-root-name-missing:/ );
+		} );
+
+		it( 'should throw when no data controller', () => {
+			expect( () => {
+				editor.data = null;
+
+				editor.setData( 'foo' );
+			} ).to.throw( CKEditorError, /^editor-no-datacontroller:/ );
+		} );
+	} );
+
+	describe( 'getData', () => {
+		let editor;
+
+		beforeEach( () => {
+			editor = new Editor();
+
+			editor.document = new Document();
+			editor.data = {
+				get( rootName ) {
+					return `data for ${ rootName }`;
+				}
+			};
+		} );
+
+		it( 'should get data from the first root', () => {
+			editor.document.createRoot( 'firstRoot', 'div' );
+
+			expect( editor.getData() ).to.equal( 'data for firstRoot' );
+		} );
+
+		it( 'should get data from the specified root', () => {
+			expect( editor.getData( 'someRoot' ) ).to.equal( 'data for someRoot' );
+		} );
+
+		it( 'should throw when no roots', () => {
+			expect( () => {
+				editor.getData();
+			} ).to.throw( CKEditorError, /^editor-no-editable-roots:/ );
+		} );
+
+		it( 'should throw when more than one root and no root name given', () => {
+			editor.document.createRoot( 'firstRoot', 'div' );
+			editor.document.createRoot( 'secondRoot', 'div' );
+
+			expect( () => {
+				editor.getData();
+			} ).to.throw( CKEditorError, /^editor-editable-root-name-missing:/ );
+		} );
+
+		it( 'should throw when no data controller', () => {
+			expect( () => {
+				editor.data = null;
+
+				editor.getData();
+			} ).to.throw( CKEditorError, /^editor-no-datacontroller:/ );
+		} );
+	} );
+} );

+ 2 - 2
tests/editor/creator.js

@@ -3,9 +3,9 @@
  * For licensing, see LICENSE.md.
  */
 
-'use strict';
+/* bender-tags: editor, creator, browser-only */
 
-/* bender-tags: editor, creator */
+'use strict';
 
 import moduleUtils from '/tests/ckeditor5/_utils/module.js';
 import testUtils from '/tests/ckeditor5/_utils/utils.js';

+ 8 - 166
tests/editor/editor.js

@@ -3,21 +3,17 @@
  * For licensing, see LICENSE.md.
  */
 
-'use strict';
+/* bender-tags: editor, browser-only */
 
-/* bender-tags: editor */
+'use strict';
 
 import moduleUtils from '/tests/ckeditor5/_utils/module.js';
 import testUtils from '/tests/ckeditor5/_utils/utils.js';
 import Editor from '/ckeditor5/editor.js';
-import EditorConfig from '/ckeditor5/editorconfig.js';
+import Plugin from '/ckeditor5/plugin.js';
+import Config from '/ckeditor5/utils/config.js';
 import PluginCollection from '/ckeditor5/plugincollection.js';
 import EditableCollection from '/ckeditor5/editablecollection.js';
-import Plugin from '/ckeditor5/plugin.js';
-import Command from '/ckeditor5/command/command.js';
-import Locale from '/ckeditor5/utils/locale.js';
-import CKEditorError from '/ckeditor5/utils/ckeditorerror.js';
-import Document from '/ckeditor5/engine/treemodel/document.js';
 
 const pluginClasses = {};
 
@@ -41,7 +37,7 @@ describe( 'Editor', () => {
 			const editor = new Editor();
 
 			expect( editor ).to.have.property( 'elements', null );
-			expect( editor.config ).to.be.an.instanceof( EditorConfig );
+			expect( editor.config ).to.be.an.instanceof( Config );
 			expect( editor.editables ).to.be.an.instanceof( EditableCollection );
 			expect( editor.commands ).to.be.an.instanceof( Map );
 
@@ -50,29 +46,6 @@ describe( 'Editor', () => {
 		} );
 	} );
 
-	describe( 'config', () => {
-		it( 'should be an instance of EditorConfig', () => {
-			const editor = new Editor();
-
-			expect( editor.config ).to.be.an.instanceof( EditorConfig );
-		} );
-	} );
-
-	describe( 'locale', () => {
-		it( 'is instantiated and t() is exposed', () => {
-			const editor = new Editor();
-
-			expect( editor.locale ).to.be.instanceof( Locale );
-			expect( editor.t ).to.equal( editor.locale.t );
-		} );
-
-		it( 'is configured with the config.lang', () => {
-			const editor = new Editor( null, { lang: 'pl' } );
-
-			expect( editor.locale.lang ).to.equal( 'pl' );
-		} );
-	} );
-
 	describe( 'plugins', () => {
 		it( 'should be empty on new editor', () => {
 			const editor = new Editor();
@@ -210,142 +183,11 @@ describe( 'Editor', () => {
 		} );
 	} );
 
-	describe( 'destroy', () => {
-		it( 'should fire "destroy"', () => {
-			const editor = new Editor();
-			let spy = sinon.spy();
-
-			editor.on( 'destroy', spy );
-
-			return editor.destroy().then( () => {
-				expect( spy.calledOnce ).to.be.true;
-			} );
-		} );
-
-		// Note: Tests for destroying creators are in creator/creator.js.
-		// When destroying creator will be generalized to destroying plugins,
-		// move that code here.
-	} );
-
-	describe( 'execute', () => {
-		it( 'should execute specified command', () => {
-			const editor = new Editor();
-
-			let command = new Command( editor );
-			sinon.spy( command, '_execute' );
-
-			editor.commands.set( 'commandName', command );
-			editor.execute( 'commandName' );
-
-			expect( command._execute.calledOnce ).to.be.true;
-		} );
-
-		it( 'should throw an error if specified command has not been added', () => {
+	describe( 'plugins', () => {
+		it( 'should be empty on new editor', () => {
 			const editor = new Editor();
 
-			expect( () => {
-				editor.execute( 'command' );
-			} ).to.throw( CKEditorError, /^editor-command-not-found:/ );
-		} );
-	} );
-
-	describe( 'setData', () => {
-		let editor;
-
-		beforeEach( () => {
-			editor = new Editor();
-
-			editor.document = new Document();
-			editor.data = {
-				set: sinon.spy()
-			};
-		} );
-
-		it( 'should set data of the first root', () => {
-			editor.document.createRoot( 'firstRoot', 'div' );
-
-			editor.setData( 'foo' );
-
-			expect( editor.data.set.calledOnce ).to.be.true;
-			expect( editor.data.set.calledWithExactly( 'firstRoot', 'foo' ) ).to.be.true;
-		} );
-
-		it( 'should set data of the specified root', () => {
-			editor.setData( 'foo', 'someRoot' );
-
-			expect( editor.data.set.calledOnce ).to.be.true;
-			expect( editor.data.set.calledWithExactly( 'someRoot', 'foo' ) ).to.be.true;
-		} );
-
-		it( 'should throw when no roots', () => {
-			expect( () => {
-				editor.setData( 'foo' );
-			} ).to.throw( CKEditorError, /^editor-no-editable-roots:/ );
-		} );
-
-		it( 'should throw when more than one root and no root name given', () => {
-			editor.document.createRoot( 'firstRoot', 'div' );
-			editor.document.createRoot( 'secondRoot', 'div' );
-
-			expect( () => {
-				editor.setData( 'foo' );
-			} ).to.throw( CKEditorError, /^editor-editable-root-name-missing:/ );
-		} );
-
-		it( 'should throw when no data controller', () => {
-			expect( () => {
-				editor.data = null;
-
-				editor.setData( 'foo' );
-			} ).to.throw( CKEditorError, /^editor-no-datacontroller:/ );
-		} );
-	} );
-
-	describe( 'getData', () => {
-		let editor;
-
-		beforeEach( () => {
-			editor = new Editor();
-
-			editor.document = new Document();
-			editor.data = {
-				get( rootName ) {
-					return `data for ${ rootName }`;
-				}
-			};
-		} );
-
-		it( 'should get data from the first root', () => {
-			editor.document.createRoot( 'firstRoot', 'div' );
-
-			expect( editor.getData() ).to.equal( 'data for firstRoot' );
-		} );
-
-		it( 'should get data from the specified root', () => {
-			expect( editor.getData( 'someRoot' ) ).to.equal( 'data for someRoot' );
-		} );
-
-		it( 'should throw when no roots', () => {
-			expect( () => {
-				editor.getData();
-			} ).to.throw( CKEditorError, /^editor-no-editable-roots:/ );
-		} );
-
-		it( 'should throw when more than one root and no root name given', () => {
-			editor.document.createRoot( 'firstRoot', 'div' );
-			editor.document.createRoot( 'secondRoot', 'div' );
-
-			expect( () => {
-				editor.getData();
-			} ).to.throw( CKEditorError, /^editor-editable-root-name-missing:/ );
-		} );
-
-		it( 'should throw when no data controller', () => {
-			expect( () => {
-				editor.data = null;
-
-				editor.getData();
-			} ).to.throw( CKEditorError, /^editor-no-datacontroller:/ );
+			expect( getPlugins( editor ) ).to.be.empty;
 		} );
 	} );
 } );

+ 0 - 41
tests/editorconfig.js

@@ -1,41 +0,0 @@
-/**
- * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
- * For licensing, see LICENSE.md.
- */
-
-'use strict';
-
-import EditorConfig from '/ckeditor5/editorconfig.js';
-import CKEDITOR from '/ckeditor.js';
-
-let config;
-
-beforeEach( () => {
-	config = new EditorConfig( {
-		test: 1
-	} );
-} );
-
-describe( 'constructor', () => {
-	it( 'should set configurations', () => {
-		expect( config ).to.have.property( 'test' ).to.equal( 1 );
-	} );
-} );
-
-describe( 'get', () => {
-	it( 'should retrieve a configuration', () => {
-		expect( config.get( 'test' ) ).to.equal( 1 );
-	} );
-
-	it( 'should fallback to CKEDITOR.config', () => {
-		CKEDITOR.config.set( {
-			globalConfig: 2
-		} );
-
-		expect( config.get( 'globalConfig' ) ).to.equal( 2 );
-	} );
-
-	it( 'should return undefined for non existing configuration', () => {
-		expect( config.get( 'invalid' ) ).to.be.undefined();
-	} );
-} );

+ 2 - 0
tests/path.js

@@ -3,6 +3,8 @@
  * For licensing, see LICENSE.md.
  */
 
+/* bender-tags: browser-only */
+
 'use strict';
 
 import path from '/ckeditor5/path.js';

+ 2 - 0
tests/plugin.js

@@ -3,6 +3,8 @@
  * For licensing, see LICENSE.md.
  */
 
+/* bender-tags: browser-only */
+
 'use strict';
 
 import Plugin from '/ckeditor5/plugin.js';

+ 2 - 0
tests/plugincollection.js

@@ -3,6 +3,8 @@
  * For licensing, see LICENSE.md.
  */
 
+/* bender-tags: browser-only */
+
 'use strict';
 
 import moduleUtils from '/tests/ckeditor5/_utils/module.js';