Browse Source

Add core es6 unit tests

Tristan VALCKE 7 years ago
parent
commit
dcd799ce6e

+ 464 - 0
test/unit/src/core/BufferAttribute.tests.js

@@ -0,0 +1,464 @@
+/**
+ * @author simonThiele / https://github.com/simonThiele
+ */
+/* global QUnit */
+
+import { BufferAttribute } from '../../../../src/core/BufferAttribute';
+import { Color } from '../../../../src/math/Color';
+import { Vector2 } from '../../../../src/math/Vector2';
+import { Vector3 } from '../../../../src/math/Vector3';
+import { Vector4 } from '../../../../src/math/Vector4';
+
+export default QUnit.module( 'Core', () => {
+
+	QUnit.module.todo( 'BufferAttribute', () => {
+
+		// INSTANCING
+		QUnit.test( "Instancing", ( assert ) => {
+
+			assert.throws(
+				function () {
+
+					var a = new BufferAttribute( [ 1, 2, 3, 4 ], 2, false );
+
+				},
+				/array should be a Typed Array/,
+				"Calling constructor with a simple array throws Error"
+			);
+
+		} );
+
+		// PROPERTIES
+		QUnit.test( "needsUpdate", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		// PUBLIC STUFF
+		QUnit.test( "isBufferAttribute", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "setArray", ( assert ) => {
+
+			var f32a = new Float32Array( [ 1, 2, 3, 4 ] );
+			var a = new BufferAttribute( f32a, 2, false );
+
+			a.setArray( f32a, 2 );
+
+			assert.strictEqual( a.count, 2, "Check item count" );
+			assert.strictEqual( a.array, f32a, "Check array" );
+
+			assert.throws(
+				function () {
+
+					a.setArray( [ 1, 2, 3, 4 ] );
+
+				},
+				/array should be a Typed Array/,
+				"Calling setArray with a simple array throws Error"
+			);
+
+		} );
+
+		QUnit.test( "setDynamic", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "copy", ( assert ) => {
+
+			var attr = new BufferAttribute( new Float32Array( [ 1, 2, 3, 4, 5, 6 ] ), 3 );
+			attr.setDynamic( true );
+			attr.needsUpdate = true;
+
+			var attrCopy = new BufferAttribute().copy( attr );
+
+			assert.ok( attr.count === attrCopy.count, 'count is equal' );
+			assert.ok( attr.itemSize === attrCopy.itemSize, 'itemSize is equal' );
+			assert.ok( attr.dynamic === attrCopy.dynamic, 'dynamic is equal' );
+			assert.ok( attr.array.length === attrCopy.array.length, 'array length is equal' );
+			assert.ok( attr.version === 1 && attrCopy.version === 0, 'version is not copied which is good' );
+
+		} );
+
+		QUnit.test( "copyAt", ( assert ) => {
+
+			var attr = new BufferAttribute( new Float32Array( [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ] ), 3 );
+			var attr2 = new BufferAttribute( new Float32Array( 9 ), 3 );
+
+			attr2.copyAt( 1, attr, 2 );
+			attr2.copyAt( 0, attr, 1 );
+			attr2.copyAt( 2, attr, 0 );
+
+			var i = attr.array;
+			var i2 = attr2.array; // should be [4, 5, 6, 7, 8, 9, 1, 2, 3]
+
+			assert.ok( i2[ 0 ] === i[ 3 ] && i2[ 1 ] === i[ 4 ] && i2[ 2 ] === i[ 5 ], 'chunck copied to correct place' );
+			assert.ok( i2[ 3 ] === i[ 6 ] && i2[ 4 ] === i[ 7 ] && i2[ 5 ] === i[ 8 ], 'chunck copied to correct place' );
+			assert.ok( i2[ 6 ] === i[ 0 ] && i2[ 7 ] === i[ 1 ] && i2[ 8 ] === i[ 2 ], 'chunck copied to correct place' );
+
+		} );
+
+		QUnit.test( "copyArray", ( assert ) => {
+
+			var f32a = new Float32Array( [ 5, 6, 7, 8 ] );
+			var a = new BufferAttribute( new Float32Array( [ 1, 2, 3, 4 ] ), 2, false );
+
+			a.copyArray( f32a );
+
+			assert.deepEqual( a.array, f32a, "Check array has new values" );
+
+		} );
+
+		QUnit.test( "copyColorsArray", ( assert ) => {
+
+			var attr = new BufferAttribute( new Float32Array( 6 ), 3 );
+
+			attr.copyColorsArray( [
+				new Color( 0, 0.5, 1 ),
+				new Color( 0.25, 1, 0 )
+			] );
+
+			var i = attr.array;
+			assert.ok( i[ 0 ] === 0 && i[ 1 ] === 0.5 && i[ 2 ] === 1, 'first color was copied correctly' );
+			assert.ok( i[ 3 ] === 0.25 && i[ 4 ] === 1 && i[ 5 ] === 0, 'second color was copied correctly' );
+
+		} );
+
+		QUnit.test( "copyIndicesArray", ( assert ) => {
+
+			var attr = new BufferAttribute( new Float32Array( 6 ), 3 );
+
+			attr.copyIndicesArray( [
+				{
+					a: 1,
+					b: 2,
+					c: 3
+				},
+				{
+					a: 4,
+					b: 5,
+					c: 6
+				}
+			] );
+
+			var i = attr.array;
+			assert.ok( i[ 0 ] === 1 && i[ 1 ] === 2 && i[ 2 ] === 3, 'first indices were copied correctly' );
+			assert.ok( i[ 3 ] === 4 && i[ 4 ] === 5 && i[ 5 ] === 6, 'second indices were copied correctly' );
+
+		} );
+
+		QUnit.test( "copyVector2sArray", ( assert ) => {
+
+			var attr = new BufferAttribute( new Float32Array( 4 ), 2 );
+
+			attr.copyVector2sArray( [
+				new Vector2( 1, 2 ),
+				new Vector2( 4, 5 )
+			] );
+
+			var i = attr.array;
+			assert.ok( i[ 0 ] === 1 && i[ 1 ] === 2, 'first vector was copied correctly' );
+			assert.ok( i[ 2 ] === 4 && i[ 3 ] === 5, 'second vector was copied correctly' );
+
+		} );
+
+		QUnit.test( "copyVector3sArray", ( assert ) => {
+
+			var attr = new BufferAttribute( new Float32Array( 6 ), 2 );
+
+			attr.copyVector3sArray( [
+				new Vector3( 1, 2, 3 ),
+				new Vector3( 10, 20, 30 )
+			] );
+
+			var i = attr.array;
+			assert.ok( i[ 0 ] === 1 && i[ 1 ] === 2 && i[ 2 ] === 3, 'first vector was copied correctly' );
+			assert.ok( i[ 3 ] === 10 && i[ 4 ] === 20 && i[ 5 ] === 30, 'second vector was copied correctly' );
+
+		} );
+
+		QUnit.test( "copyVector4sArray", ( assert ) => {
+
+			var attr = new BufferAttribute( new Float32Array( 8 ), 2 );
+
+			attr.copyVector4sArray( [
+				new Vector4( 1, 2, 3, 4 ),
+				new Vector4( 10, 20, 30, 40 )
+			] );
+
+			var i = attr.array;
+			assert.ok( i[ 0 ] === 1 && i[ 1 ] === 2 && i[ 2 ] === 3 && i[ 3 ] === 4, 'first vector was copied correctly' );
+			assert.ok( i[ 4 ] === 10 && i[ 5 ] === 20 && i[ 6 ] === 30 && i[ 7 ] === 40, 'second vector was copied correctly' );
+
+		} );
+
+		QUnit.test( "set", ( assert ) => {
+
+			var f32a = new Float32Array( [ 1, 2, 3, 4 ] );
+			var a = new BufferAttribute( f32a, 2, false );
+			var expected = new Float32Array( [ 9, 2, 8, 4 ] );
+
+			a.set( [ 9 ] );
+			a.set( [ 8 ], 2 );
+
+			assert.deepEqual( a.array, expected, "Check array has expected values" );
+
+		} );
+
+		QUnit.test( "set[X, Y, Z, W, XYZ, XYZW]/get[X, Y, Z, W]", ( assert ) => {
+
+			var f32a = new Float32Array( [ 1, 2, 3, 4, 5, 6, 7, 8 ] );
+			var a = new BufferAttribute( f32a, 4, false );
+			var expected = new Float32Array( [ 1, 2, - 3, - 4, - 5, - 6, 7, 8 ] );
+
+			a.setX( 1, a.getX( 1 ) * - 1 );
+			a.setY( 1, a.getY( 1 ) * - 1 );
+			a.setZ( 0, a.getZ( 0 ) * - 1 );
+			a.setW( 0, a.getW( 0 ) * - 1 );
+
+			assert.deepEqual( a.array, expected, "Check all set* calls set the correct values" );
+
+		} );
+
+		QUnit.test( "setXY", ( assert ) => {
+
+			var f32a = new Float32Array( [ 1, 2, 3, 4 ] );
+			var a = new BufferAttribute( f32a, 2, false );
+			var expected = new Float32Array( [ - 1, - 2, 3, 4 ] );
+
+			a.setXY( 0, - 1, - 2 );
+
+			assert.deepEqual( a.array, expected, "Check for the correct values" );
+
+		} );
+
+		QUnit.test( "setXYZ", ( assert ) => {
+
+			var f32a = new Float32Array( [ 1, 2, 3, 4, 5, 6 ] );
+			var a = new BufferAttribute( f32a, 3, false );
+			var expected = new Float32Array( [ 1, 2, 3, - 4, - 5, - 6 ] );
+
+			a.setXYZ( 1, - 4, - 5, - 6 );
+
+			assert.deepEqual( a.array, expected, "Check for the correct values" );
+
+		} );
+
+		QUnit.test( "setXYZW", ( assert ) => {
+
+			var f32a = new Float32Array( [ 1, 2, 3, 4 ] );
+			var a = new BufferAttribute( f32a, 4, false );
+			var expected = new Float32Array( [ - 1, - 2, - 3, - 4 ] );
+
+			a.setXYZW( 0, - 1, - 2, - 3, - 4 );
+
+			assert.deepEqual( a.array, expected, "Check for the correct values" );
+
+		} );
+
+		QUnit.test( "onUpload", ( assert ) => {
+
+			var a = new BufferAttribute();
+			var func = function () { };
+
+			a.onUpload( func );
+
+			assert.strictEqual( a.onUploadCallback, func, "Check callback was set properly" );
+
+		} );
+
+		QUnit.test( "clone", ( assert ) => {
+
+			var attr = new BufferAttribute( new Float32Array( [ 1, 2, 3, 4, 0.12, - 12 ] ), 2 );
+			var attrCopy = attr.clone();
+
+			assert.ok( attr.array.length === attrCopy.array.length, 'attribute was cloned' );
+			for ( var i = 0; i < attr.array.length; i ++ ) {
+
+				assert.ok( attr.array[ i ] === attrCopy.array[ i ], 'array item is equal' );
+
+			}
+
+		} );
+
+		// OTHERS
+		QUnit.test( "count", ( assert ) => {
+
+			assert.ok(
+				new BufferAttribute( new Float32Array( [ 1, 2, 3, 4, 5, 6 ] ), 3 ).count === 2,
+				'count is equal to the number of chunks'
+			);
+
+		} );
+
+	} );
+
+	QUnit.module.todo( 'Int8BufferAttribute', () => {
+
+		// INHERITANCE
+		QUnit.test( "Extending", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		// INSTANCING
+		QUnit.test( "Instancing", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+	} );
+
+	QUnit.module.todo( 'Uint8BufferAttribute', () => {
+
+		// INHERITANCE
+		QUnit.test( "Extending", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		// INSTANCING
+		QUnit.test( "Instancing", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+	} );
+
+	QUnit.module.todo( 'Uint8ClampedBufferAttribute', () => {
+
+		// INHERITANCE
+		QUnit.test( "Extending", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		// INSTANCING
+		QUnit.test( "Instancing", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+	} );
+
+	QUnit.module.todo( 'Int16BufferAttribute', () => {
+
+		// INHERITANCE
+		QUnit.test( "Extending", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		// INSTANCING
+		QUnit.test( "Instancing", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+	} );
+
+	QUnit.module.todo( 'Uint16BufferAttribute', () => {
+
+		// INHERITANCE
+		QUnit.test( "Extending", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		// INSTANCING
+		QUnit.test( "Instancing", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+	} );
+
+	QUnit.module.todo( 'Int32BufferAttribute', () => {
+
+		// INHERITANCE
+		QUnit.test( "Extending", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		// INSTANCING
+		QUnit.test( "Instancing", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+	} );
+
+	QUnit.module.todo( 'Uint32BufferAttribute', () => {
+
+		// INHERITANCE
+		QUnit.test( "Extending", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		// INSTANCING
+		QUnit.test( "Instancing", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+	} );
+
+	QUnit.module.todo( 'Float32BufferAttribute', () => {
+
+		// INHERITANCE
+		QUnit.test( "Extending", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		// INSTANCING
+		QUnit.test( "Instancing", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+	} );
+
+	QUnit.module.todo( 'Float64BufferAttribute', () => {
+
+		// INHERITANCE
+		QUnit.test( "Extending", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		// INSTANCING
+		QUnit.test( "Instancing", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+	} );
+
+} );

+ 975 - 0
test/unit/src/core/BufferGeometry.tests.js

@@ -0,0 +1,975 @@
+/**
+ * @author simonThiele / https://github.com/simonThiele
+ * @author TristanVALCKE / https://github.com/Itee
+ */
+/* global QUnit */
+
+import { BufferGeometry } from '../../../../src/core/BufferGeometry';
+import { JSONLoader } from '../../../../src/loaders/JSONLoader';
+import { arrayMax } from '../../../../src/utils';
+import { DirectGeometry } from '../../../../src/core/DirectGeometry';
+import {
+	BufferAttribute,
+	Uint16BufferAttribute,
+	Uint32BufferAttribute,
+	Float32BufferAttribute
+} from '../../../../src/core/BufferAttribute';
+import { Vector3 } from '../../../../src/math/Vector3';
+import { Matrix4 } from '../../../../src/math/Matrix4';
+import { Sphere } from '../../../../src/math/Sphere';
+import { Vector2 } from '../../../../src/math/Vector2';
+import { Geometry } from '../../../../src/core/Geometry';
+import { Face3 } from '../../../../src/core/Face3';
+import { Mesh } from '../../../../src/objects/Mesh';
+import { Color } from '../../../../src/math/Color';
+import { LineSegments as Line } from '../../../../src/objects/LineSegments.js';
+import {
+	x,
+	y,
+	z
+} from '../math/Constants.tests';
+
+var DegToRad = Math.PI / 180;
+
+function bufferAttributeEquals( a, b, tolerance ) {
+
+	tolerance = tolerance || 0.0001;
+
+	if ( a.count !== b.count || a.itemSize !== b.itemSize ) {
+
+		return false;
+
+	}
+
+	for ( var i = 0, il = a.count * a.itemSize; i < il; i ++ ) {
+
+		var delta = a[ i ] - b[ i ];
+		if ( delta > tolerance ) {
+
+			return false;
+
+		}
+
+	}
+
+	return true;
+
+}
+
+function getBBForVertices( vertices ) {
+
+	var geometry = new BufferGeometry();
+
+	geometry.addAttribute( "position", new BufferAttribute( new Float32Array( vertices ), 3 ) );
+	geometry.computeBoundingBox();
+
+	return geometry.boundingBox;
+
+}
+
+function getBSForVertices( vertices ) {
+
+	var geometry = new BufferGeometry();
+
+	geometry.addAttribute( "position", new BufferAttribute( new Float32Array( vertices ), 3 ) );
+	geometry.computeBoundingSphere();
+
+	return geometry.boundingSphere;
+
+}
+
+function getNormalsForVertices( vertices, assert ) {
+
+	var geometry = new BufferGeometry();
+
+	geometry.addAttribute( "position", new BufferAttribute( new Float32Array( vertices ), 3 ) );
+
+	geometry.computeVertexNormals();
+
+	assert.ok( geometry.attributes.normal !== undefined, "normal attribute was created" );
+
+	return geometry.attributes.normal.array;
+
+}
+
+function comparePositions( pos, v ) {
+
+	return (
+		pos[ 0 ] === v[ 0 ].x && pos[ 1 ] === v[ 0 ].y && pos[ 2 ] === v[ 0 ].z &&
+		pos[ 3 ] === v[ 1 ].x && pos[ 4 ] === v[ 1 ].y && pos[ 5 ] === v[ 1 ].z &&
+		pos[ 6 ] === v[ 2 ].x && pos[ 7 ] === v[ 2 ].y && pos[ 8 ] === v[ 2 ].z
+	);
+
+}
+
+function compareColors( col, c ) {
+
+	return (
+		col[ 0 ] === c[ 0 ].r && col[ 1 ] === c[ 0 ].g && col[ 2 ] === c[ 0 ].b &&
+		col[ 3 ] === c[ 1 ].r && col[ 4 ] === c[ 1 ].g && col[ 5 ] === c[ 1 ].b &&
+		col[ 6 ] === c[ 2 ].r && col[ 7 ] === c[ 2 ].g && col[ 8 ] === c[ 2 ].b
+	);
+
+}
+
+function compareUvs( uvs, u ) {
+
+	return (
+		uvs[ 0 ] === u[ 0 ].x && uvs[ 1 ] === u[ 0 ].y &&
+		uvs[ 2 ] === u[ 1 ].x && uvs[ 3 ] === u[ 1 ].y &&
+		uvs[ 4 ] === u[ 2 ].x && uvs[ 5 ] === u[ 2 ].y
+	);
+
+}
+
+export default QUnit.module( 'Core', () => {
+
+	QUnit.module.todo( 'BufferGeometry', () => {
+
+		// INHERITANCE
+		QUnit.test( "Extending", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		// INSTANCING
+		QUnit.test( "Instancing", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		// PUBLIC STUFF
+		QUnit.test( "isBufferGeometry", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "setIndex/getIndex", ( assert ) => {
+
+			var a = new BufferGeometry();
+			var uint16 = [ 1, 2, 3 ];
+			var uint32 = [ 65535, 65536, 65537 ];
+			var str = "foo";
+
+			a.setIndex( uint16 );
+			assert.ok( a.getIndex() instanceof Uint16BufferAttribute, "Index has the right type" );
+			assert.deepEqual( a.getIndex().array, new Uint16Array( uint16 ), "Small index gets stored correctly" );
+
+			a.setIndex( uint32 );
+			assert.ok( a.getIndex() instanceof Uint32BufferAttribute, "Index has the right type" );
+			assert.deepEqual( a.getIndex().array, new Uint32Array( uint32 ), "Large index gets stored correctly" );
+
+			a.setIndex( str );
+			assert.strictEqual( a.getIndex(), str, "Weird index gets stored correctly" );
+
+		} );
+
+		QUnit.test( "getAttribute", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+		QUnit.test( "add / delete Attribute", ( assert ) => {
+
+			var geometry = new BufferGeometry();
+			var attributeName = "position";
+
+			assert.ok( geometry.attributes[ attributeName ] === undefined, 'no attribute defined' );
+
+			geometry.addAttribute( attributeName, new BufferAttribute( new Float32Array( [ 1, 2, 3 ], 1 ) ) );
+
+			assert.ok( geometry.attributes[ attributeName ] !== undefined, 'attribute is defined' );
+
+			geometry.removeAttribute( attributeName );
+
+			assert.ok( geometry.attributes[ attributeName ] === undefined, 'no attribute defined' );
+
+		} );
+
+		QUnit.test( "addGroup", ( assert ) => {
+
+			var a = new BufferGeometry();
+			var expected = [
+				{
+					start: 0,
+					count: 1,
+					materialIndex: 0
+				},
+				{
+					start: 1,
+					count: 2,
+					materialIndex: 2
+				}
+			];
+
+			a.addGroup( 0, 1, 0 );
+			a.addGroup( 1, 2, 2 );
+
+			assert.deepEqual( a.groups, expected, "Check groups were stored correctly and in order" );
+
+			a.clearGroups();
+			assert.strictEqual( a.groups.length, 0, "Check groups were deleted correctly" );
+
+		} );
+		QUnit.test( "clearGroups", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "setDrawRange", ( assert ) => {
+
+			var a = new BufferGeometry();
+
+			a.setDrawRange( 1.0, 7 );
+
+			assert.deepEqual( a.drawRange, {
+				start: 1,
+				count: 7
+			}, "Check draw range was stored correctly" );
+
+		} );
+
+		QUnit.test( "applyMatrix", ( assert ) => {
+
+			var geometry = new BufferGeometry();
+			geometry.addAttribute( "position", new BufferAttribute( new Float32Array( 6 ), 3 ) );
+
+			var matrix = new Matrix4().set(
+				1, 0, 0, 1.5,
+				0, 1, 0, - 2,
+				0, 0, 1, 3,
+				0, 0, 0, 1
+			);
+			geometry.applyMatrix( matrix );
+
+			var position = geometry.attributes.position.array;
+			var m = matrix.elements;
+			assert.ok( position[ 0 ] === m[ 12 ] && position[ 1 ] === m[ 13 ] && position[ 2 ] === m[ 14 ], "position was extracted from matrix" );
+			assert.ok( position[ 3 ] === m[ 12 ] && position[ 4 ] === m[ 13 ] && position[ 5 ] === m[ 14 ], "position was extracted from matrix twice" );
+			assert.ok( geometry.attributes.position.version === 1, "version was increased during update" );
+
+		} );
+
+		QUnit.test( "rotateX/Y/Z", ( assert ) => {
+
+			var geometry = new BufferGeometry();
+			geometry.addAttribute( "position", new BufferAttribute( new Float32Array( [ 1, 2, 3, 4, 5, 6 ] ), 3 ) );
+
+			var pos = geometry.attributes.position.array;
+
+			geometry.rotateX( 180 * DegToRad );
+
+			// object was rotated around x so all items should be flipped but the x ones
+			assert.ok( pos[ 0 ] === 1 && pos[ 1 ] === - 2 && pos[ 2 ] === - 3 &&
+				pos[ 3 ] === 4 && pos[ 4 ] === - 5 && pos[ 5 ] === - 6, "vertices were rotated around x by 180 degrees" );
+
+			geometry.rotateY( 180 * DegToRad );
+
+			// vertices were rotated around y so all items should be flipped again but the y ones
+			assert.ok( pos[ 0 ] === - 1 && pos[ 1 ] === - 2 && pos[ 2 ] === 3 &&
+				pos[ 3 ] === - 4 && pos[ 4 ] === - 5 && pos[ 5 ] === 6, "vertices were rotated around y by 180 degrees" );
+
+			geometry.rotateZ( 180 * DegToRad );
+
+			// vertices were rotated around z so all items should be flipped again but the z ones
+			assert.ok( pos[ 0 ] === 1 && pos[ 1 ] === 2 && pos[ 2 ] === 3 &&
+				pos[ 3 ] === 4 && pos[ 4 ] === 5 && pos[ 5 ] === 6, "vertices were rotated around z by 180 degrees" );
+
+		} );
+
+		QUnit.test( "translate", ( assert ) => {
+
+			var geometry = new BufferGeometry();
+			geometry.addAttribute( "position", new BufferAttribute( new Float32Array( [ 1, 2, 3, 4, 5, 6 ] ), 3 ) );
+
+			var pos = geometry.attributes.position.array;
+
+			geometry.translate( 10, 20, 30 );
+
+			assert.ok( pos[ 0 ] === 11 && pos[ 1 ] === 22 && pos[ 2 ] === 33 &&
+				pos[ 3 ] === 14 && pos[ 4 ] === 25 && pos[ 5 ] === 36, "vertices were translated" );
+
+		} );
+
+		QUnit.test( "scale", ( assert ) => {
+
+			var geometry = new BufferGeometry();
+			geometry.addAttribute( "position", new BufferAttribute( new Float32Array( [ - 1, - 1, - 1, 2, 2, 2 ] ), 3 ) );
+
+			var pos = geometry.attributes.position.array;
+
+			geometry.scale( 1, 2, 3 );
+
+			assert.ok( pos[ 0 ] === - 1 && pos[ 1 ] === - 2 && pos[ 2 ] === - 3 &&
+				pos[ 3 ] === 2 && pos[ 4 ] === 4 && pos[ 5 ] === 6, "vertices were scaled" );
+
+		} );
+
+		QUnit.test( "lookAt", ( assert ) => {
+
+			var a = new BufferGeometry();
+			var vertices = new Float32Array( [
+				- 1.0, - 1.0, 1.0,
+				1.0, - 1.0, 1.0,
+				1.0, 1.0, 1.0,
+
+				1.0, 1.0, 1.0,
+				- 1.0, 1.0, 1.0,
+				- 1.0, - 1.0, 1.0
+			] );
+			a.addAttribute( 'position', new BufferAttribute( vertices, 3 ) );
+
+			var sqrt = Math.sqrt( 2 );
+			var expected = new Float32Array( [
+				1, 0, - sqrt,
+				- 1, 0, - sqrt,
+				- 1, sqrt, 0,
+
+				- 1, sqrt, 0,
+				1, sqrt, 0,
+				1, 0, - sqrt
+			] );
+
+			a.lookAt( new Vector3( 0, 1, - 1 ) );
+
+			assert.ok( bufferAttributeEquals( a.attributes.position.array, expected ), "Rotation is correct" );
+
+		} );
+
+		QUnit.test( "center", ( assert ) => {
+
+			var geometry = new BufferGeometry();
+			geometry.addAttribute( "position", new BufferAttribute( new Float32Array( [
+				- 1, - 1, - 1,
+				1, 1, 1,
+				4, 4, 4
+			] ), 3 ) );
+
+			geometry.center();
+
+			var pos = geometry.attributes.position.array;
+
+			// the boundingBox should go from (-1, -1, -1) to (4, 4, 4) so it has a size of (5, 5, 5)
+			// after centering it the vertices should be placed between (-2.5, -2.5, -2.5) and (2.5, 2.5, 2.5)
+			assert.ok( pos[ 0 ] === - 2.5 && pos[ 1 ] === - 2.5 && pos[ 2 ] === - 2.5 &&
+				pos[ 3 ] === - 0.5 && pos[ 4 ] === - 0.5 && pos[ 5 ] === - 0.5 &&
+				pos[ 6 ] === 2.5 && pos[ 7 ] === 2.5 && pos[ 8 ] === 2.5, "vertices were replaced by boundingBox dimensions" );
+
+		} );
+
+		QUnit.test( "setFromObject", ( assert ) => {
+
+			var lineGeo = new Geometry();
+			lineGeo.vertices.push(
+				new Vector3( - 10, 0, 0 ),
+				new Vector3( 0, 10, 0 ),
+				new Vector3( 10, 0, 0 )
+			);
+
+			lineGeo.colors.push(
+				new Color( 1, 0, 0 ),
+				new Color( 0, 1, 0 ),
+				new Color( 0, 0, 1 )
+			);
+
+			var line = new Line( lineGeo, null );
+			var geometry = new BufferGeometry().setFromObject( line );
+
+			var pos = geometry.attributes.position.array;
+			var col = geometry.attributes.color.array;
+			var v = lineGeo.vertices;
+			var c = lineGeo.colors;
+
+			assert.ok(
+				// position exists
+				pos !== undefined &&
+
+				// vertex arrays have the same size
+				v.length * 3 === pos.length &&
+
+				// there are three complete vertices (each vertex contains three values)
+				geometry.attributes.position.count === 3 &&
+
+				// check if both arrays contains the same data
+				pos[ 0 ] === v[ 0 ].x && pos[ 1 ] === v[ 0 ].y && pos[ 2 ] === v[ 0 ].z &&
+				pos[ 3 ] === v[ 1 ].x && pos[ 4 ] === v[ 1 ].y && pos[ 5 ] === v[ 1 ].z &&
+				pos[ 6 ] === v[ 2 ].x && pos[ 7 ] === v[ 2 ].y && pos[ 8 ] === v[ 2 ].z
+				, "positions are equal" );
+
+			assert.ok(
+				// color exists
+				col !== undefined &&
+
+				// color arrays have the same size
+				c.length * 3 === col.length &&
+
+				// there are three complete colors (each color contains three values)
+				geometry.attributes.color.count === 3 &&
+
+				// check if both arrays contains the same data
+				col[ 0 ] === c[ 0 ].r && col[ 1 ] === c[ 0 ].g && col[ 2 ] === c[ 0 ].b &&
+				col[ 3 ] === c[ 1 ].r && col[ 4 ] === c[ 1 ].g && col[ 5 ] === c[ 1 ].b &&
+				col[ 6 ] === c[ 2 ].r && col[ 7 ] === c[ 2 ].g && col[ 8 ] === c[ 2 ].b
+				, "colors are equal" );
+
+		} );
+		QUnit.test( "setFromObject (more)", ( assert ) => {
+
+			var lineGeo = new Geometry();
+			lineGeo.vertices.push(
+				new Vector3( - 10, 0, 0 ),
+				new Vector3( 0, 10, 0 ),
+				new Vector3( 10, 0, 0 )
+			);
+
+			lineGeo.colors.push(
+				new Color( 1, 0, 0 ),
+				new Color( 0, 1, 0 ),
+				new Color( 0, 0, 1 )
+			);
+
+			lineGeo.computeBoundingBox();
+			lineGeo.computeBoundingSphere();
+
+			var line = new Line( lineGeo );
+			var geometry = new BufferGeometry().setFromObject( line );
+
+			assert.ok( geometry.boundingBox.equals( lineGeo.boundingBox ), "BoundingBox was set correctly" );
+			assert.ok( geometry.boundingSphere.equals( lineGeo.boundingSphere ), "BoundingSphere was set correctly" );
+
+			var pos = geometry.attributes.position.array;
+			var col = geometry.attributes.color.array;
+			var v = lineGeo.vertices;
+			var c = lineGeo.colors;
+
+			// adapted from setFromObject QUnit.test (way up)
+			assert.notStrictEqual( pos, undefined, "Position attribute exists" );
+			assert.strictEqual( v.length * 3, pos.length, "Vertex arrays have the same size" );
+			assert.strictEqual( geometry.attributes.position.count, 3, "Correct number of vertices" );
+			assert.ok( comparePositions( pos, v ), "Positions are identical" );
+
+			assert.notStrictEqual( col, undefined, "Color attribute exists" );
+			assert.strictEqual( c.length * 3, col.length, "Color arrays have the same size" );
+			assert.strictEqual( geometry.attributes.color.count, 3, "Correct number of colors" );
+			assert.ok( compareColors( col, c ), "Colors are identical" );
+
+			// setFromObject with a Mesh as object
+			lineGeo.faces.push( new Face3( 0, 1, 2 ) );
+			var lineMesh = new Mesh( lineGeo );
+			var geometry = new BufferGeometry().setFromObject( lineMesh );
+
+			// no colors
+			var pos = geometry.attributes.position.array;
+			var v = lineGeo.vertices;
+
+			assert.notStrictEqual( pos, undefined, "Mesh: position attribute exists" );
+			assert.strictEqual( v.length * 3, pos.length, "Mesh: vertex arrays have the same size" );
+			assert.strictEqual( geometry.attributes.position.count, 3, "Mesh: correct number of vertices" );
+			assert.ok( comparePositions( pos, v ), "Mesh: positions are identical" );
+
+		} );
+
+		QUnit.test( "updateFromObject", ( assert ) => {
+
+			var geo = new Geometry();
+
+			geo.vertices.push(
+				new Vector3( - 10, 0, 0 ),
+				new Vector3( 0, 10, 0 ),
+				new Vector3( 10, 0, 0 )
+			);
+
+			geo.faces.push( new Face3( 0, 1, 2 ) );
+
+			geo.faces[ 0 ].vertexColors.push(
+				new Color( 1, 0, 0 ),
+				new Color( 0, 1, 0 ),
+				new Color( 0, 0, 1 )
+			);
+
+			geo.faceVertexUvs[ 0 ] = [
+				[
+					new Vector2( 0, 0 ),
+					new Vector2( 1, 0 ),
+					new Vector2( 1, 1 )
+				]
+			];
+
+			geo.computeFaceNormals();
+			geo.computeVertexNormals();
+
+			geo.verticesNeedUpdate = true;
+			geo.normalsNeedUpdate = true;
+			geo.colorsNeedUpdate = true;
+			geo.uvsNeedUpdate = true;
+			geo.groupsNeedUpdate = true;
+
+			var mesh = new Mesh( geo );
+			var geometry = new BufferGeometry();
+
+			geometry.updateFromObject( mesh ); // first call to create the underlying structure (DirectGeometry)
+			geometry.updateFromObject( mesh ); // second time to actually go thru the motions and update
+
+			var pos = geometry.attributes.position.array;
+			var col = geometry.attributes.color.array;
+			var norm = geometry.attributes.normal.array;
+			var uvs = geometry.attributes.uv.array;
+			var v = geo.vertices;
+			var c = geo.faces[ 0 ].vertexColors;
+			var n = geo.faces[ 0 ].vertexNormals;
+			var u = geo.faceVertexUvs[ 0 ][ 0 ];
+
+			assert.notStrictEqual( pos, undefined, "Position attribute exists" );
+			assert.strictEqual( v.length * 3, pos.length, "Both arrays have the same size" );
+			assert.strictEqual( geometry.attributes.position.count, v.length, "Correct number of vertices" );
+			assert.ok( comparePositions( pos, v ), "Positions are identical" );
+
+			assert.notStrictEqual( col, undefined, "Color attribute exists" );
+			assert.strictEqual( c.length * 3, col.length, "Both arrays have the same size" );
+			assert.strictEqual( geometry.attributes.color.count, c.length, "Correct number of colors" );
+			assert.ok( compareColors( col, c ), "Colors are identical" );
+
+			assert.notStrictEqual( norm, undefined, "Normal attribute exists" );
+			assert.strictEqual( n.length * 3, norm.length, "Both arrays have the same size" );
+			assert.strictEqual( geometry.attributes.normal.count, n.length, "Correct number of normals" );
+			assert.ok( comparePositions( norm, n ), "Normals are identical" );
+
+			assert.notStrictEqual( uvs, undefined, "UV attribute exists" );
+			assert.strictEqual( u.length * 2, uvs.length, "Both arrays have the same size" );
+			assert.strictEqual( geometry.attributes.uv.count, u.length, "Correct number of UV coordinates" );
+			assert.ok( compareUvs( uvs, u ), "UVs are identical" );
+
+		} );
+
+		QUnit.test( "fromGeometry/fromDirectGeometry", ( assert ) => {
+
+			var a = new BufferGeometry();
+			// BoxGeometry is a bit too simple but works fine in a pinch
+			// var b = new BoxGeometry( 1, 1, 1 );
+			// b.mergeVertices();
+			// b.computeVertexNormals();
+			// b.computeBoundingBox();
+			// b.computeBoundingSphere();
+			var asyncDone = assert.async(); // tell QUnit we're done with asserts
+
+			var loader = new JSONLoader();
+			loader.load( "../../examples/models/skinned/simple/simple.js", function ( modelGeometry ) {
+
+				a.fromGeometry( modelGeometry );
+
+				var attr;
+				var geometry = new DirectGeometry().fromGeometry( modelGeometry );
+
+				var positions = new Float32Array( geometry.vertices.length * 3 );
+				attr = new BufferAttribute( positions, 3 ).copyVector3sArray( geometry.vertices );
+				assert.ok( bufferAttributeEquals( a.attributes.position, attr ), "Vertices are identical" );
+
+				if ( geometry.normals.length > 0 ) {
+
+					var normals = new Float32Array( geometry.normals.length * 3 );
+					attr = new BufferAttribute( normals, 3 ).copyVector3sArray( geometry.normals );
+					assert.ok( bufferAttributeEquals( a.attributes.normal, attr ), "Normals are identical" );
+
+				}
+
+				if ( geometry.colors.length > 0 ) {
+
+					var colors = new Float32Array( geometry.colors.length * 3 );
+					attr = new BufferAttribute( colors, 3 ).copyColorsArray( geometry.colors );
+					assert.ok( bufferAttributeEquals( a.attributes.color, attr ), "Colors are identical" );
+
+				}
+
+				if ( geometry.uvs.length > 0 ) {
+
+					var uvs = new Float32Array( geometry.uvs.length * 2 );
+					attr = new BufferAttribute( uvs, 2 ).copyVector2sArray( geometry.uvs );
+					assert.ok( bufferAttributeEquals( a.attributes.uv, attr ), "UVs are identical" );
+
+				}
+
+				if ( geometry.uvs2.length > 0 ) {
+
+					var uvs2 = new Float32Array( geometry.uvs2.length * 2 );
+					attr = new BufferAttribute( uvs2, 2 ).copyVector2sArray( geometry.uvs2 );
+					assert.ok( bufferAttributeEquals( a.attributes.uv2, attr ), "UV2s are identical" );
+
+				}
+
+				if ( geometry.indices.length > 0 ) {
+
+					var TypeArray = arrayMax( geometry.indices ) > 65535 ? Uint32Array : Uint16Array;
+					var indices = new TypeArray( geometry.indices.length * 3 );
+					attr = new BufferAttribute( indices, 1 ).copyIndicesArray( geometry.indices );
+					assert.ok( bufferAttributeEquals( a.indices, attr ), "Indices are identical" );
+
+				}
+
+				// groups
+				assert.deepEqual( a.groups, geometry.groups, "Groups are identical" );
+
+				// morphs
+				if ( geometry.morphTargets !== undefined ) {
+
+					for ( var name in geometry.morphTargets ) {
+
+						var morphTargets = geometry.morphTargets[ name ];
+
+						for ( var i = 0, l = morphTargets.length; i < l; i ++ ) {
+
+							var morphTarget = morphTargets[ i ];
+
+							attr = new Float32BufferAttribute( morphTarget.length * 3, 3 );
+							attr.copyVector3sArray( morphTarget );
+
+							assert.ok(
+								bufferAttributeEquals( a.morphAttributes[ name ][ i ], attr ),
+								"MorphTargets #" + i + " are identical"
+							);
+
+						}
+
+					}
+
+				}
+
+				// skinning
+				if ( geometry.skinIndices.length > 0 ) {
+
+					attr = new Float32BufferAttribute( geometry.skinIndices.length * 4, 4 );
+					attr.copyVector4sArray( geometry.skinIndices );
+					assert.ok( bufferAttributeEquals( a.attributes.skinIndex, attr ), "SkinIndices are identical" );
+
+				}
+
+				if ( geometry.skinWeights.length > 0 ) {
+
+					attr = new Float32BufferAttribute( geometry.skinWeights.length * 4, 4 );
+					attr.copyVector4sArray( geometry.skinWeights );
+					assert.ok( bufferAttributeEquals( a.attributes.skinWeight, attr ), "SkinWeights are identical" );
+
+				}
+
+				// TODO
+				// DirectGeometry doesn't actually copy boundingSphere and boundingBox yet,
+				// so they're always null
+				if ( geometry.boundingSphere !== null ) {
+
+					assert.ok( a.boundingSphere.equals( geometry.boundingSphere ), "BoundingSphere is identical" );
+
+				}
+
+				if ( geometry.boundingBox !== null ) {
+
+					assert.ok( a.boundingBox.equals( geometry.boundingBox ), "BoundingBox is identical" );
+
+				}
+
+				asyncDone();
+
+			} );
+
+		} );
+
+		QUnit.test( "computeBoundingBox", ( assert ) => {
+
+			var bb = getBBForVertices( [ - 1, - 2, - 3, 13, - 2, - 3.5, - 1, - 20, 0, - 4, 5, 6 ] );
+
+			assert.ok( bb.min.x === - 4 && bb.min.y === - 20 && bb.min.z === - 3.5, "min values are set correctly" );
+			assert.ok( bb.max.x === 13 && bb.max.y === 5 && bb.max.z === 6, "max values are set correctly" );
+
+			var bb = getBBForVertices( [ - 1, - 1, - 1 ] );
+
+			assert.ok( bb.min.x === bb.max.x && bb.min.y === bb.max.y && bb.min.z === bb.max.z, "since there is only one vertex, max and min are equal" );
+			assert.ok( bb.min.x === - 1 && bb.min.y === - 1 && bb.min.z === - 1, "since there is only one vertex, min and max are this vertex" );
+
+		} );
+
+		QUnit.test( "computeBoundingSphere", ( assert ) => {
+
+			var bs = getBSForVertices( [ - 10, 0, 0, 10, 0, 0 ] );
+
+			assert.ok( bs.radius === ( 10 + 10 ) / 2, "radius is equal to deltaMinMax / 2" );
+			assert.ok( bs.center.x === 0 && bs.center.y === 0 && bs.center.y === 0, "bounding sphere is at ( 0, 0, 0 )" );
+
+			var bs = getBSForVertices( [ - 5, 11, - 3, 5, - 11, 3 ] );
+			var radius = new Vector3( 5, 11, 3 ).length();
+
+			assert.ok( bs.radius === radius, "radius is equal to directionLength" );
+			assert.ok( bs.center.x === 0 && bs.center.y === 0 && bs.center.y === 0, "bounding sphere is at ( 0, 0, 0 )" );
+
+		} );
+
+		QUnit.test( "computeFaceNormals", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "computeVertexNormals", ( assert ) => {
+
+			// get normals for a counter clockwise created triangle
+			var normals = getNormalsForVertices( [ - 1, 0, 0, 1, 0, 0, 0, 1, 0 ], assert );
+
+			assert.ok( normals[ 0 ] === 0 && normals[ 1 ] === 0 && normals[ 2 ] === 1,
+				"first normal is pointing to screen since the the triangle was created counter clockwise" );
+
+			assert.ok( normals[ 3 ] === 0 && normals[ 4 ] === 0 && normals[ 5 ] === 1,
+				"second normal is pointing to screen since the the triangle was created counter clockwise" );
+
+			assert.ok( normals[ 6 ] === 0 && normals[ 7 ] === 0 && normals[ 8 ] === 1,
+				"third normal is pointing to screen since the the triangle was created counter clockwise" );
+
+			// get normals for a clockwise created triangle
+			var normals = getNormalsForVertices( [ 1, 0, 0, - 1, 0, 0, 0, 1, 0 ], assert );
+
+			assert.ok( normals[ 0 ] === 0 && normals[ 1 ] === 0 && normals[ 2 ] === - 1,
+				"first normal is pointing to screen since the the triangle was created clockwise" );
+
+			assert.ok( normals[ 3 ] === 0 && normals[ 4 ] === 0 && normals[ 5 ] === - 1,
+				"second normal is pointing to screen since the the triangle was created clockwise" );
+
+			assert.ok( normals[ 6 ] === 0 && normals[ 7 ] === 0 && normals[ 8 ] === - 1,
+				"third normal is pointing to screen since the the triangle was created clockwise" );
+
+			var normals = getNormalsForVertices( [ 0, 0, 1, 0, 0, - 1, 1, 1, 0 ], assert );
+
+			// the triangle is rotated by 45 degrees to the right so the normals of the three vertices
+			// should point to (1, -1, 0).normalized(). The simplest solution is to check against a normalized
+			// vector (1, -1, 0) but you will get calculation errors because of floating calculations so another
+			// valid technique is to create a vector which stands in 90 degrees to the normals and calculate the
+			// dot product which is the cos of the angle between them. This should be < floating calculation error
+			// which can be taken from Number.EPSILON
+			var direction = new Vector3( 1, 1, 0 ).normalize(); // a vector which should have 90 degrees difference to normals
+			var difference = direction.dot( new Vector3( normals[ 0 ], normals[ 1 ], normals[ 2 ] ) );
+			assert.ok( difference < Number.EPSILON, "normal is equal to reference vector" );
+
+			// get normals for a line should be NAN because you need min a triangle to calculate normals
+			var normals = getNormalsForVertices( [ 1, 0, 0, - 1, 0, 0 ], assert );
+			for ( var i = 0; i < normals.length; i ++ ) {
+
+				assert.ok( ! normals[ i ], "normals can't be calculated which is good" );
+
+			}
+
+		} );
+		QUnit.test( "computeVertexNormals (indexed)", ( assert ) => {
+
+			var sqrt = 0.5 * Math.sqrt( 2 );
+			var normal = new BufferAttribute( new Float32Array( [
+				- 1, 0, 0, - 1, 0, 0, - 1, 0, 0,
+				sqrt, sqrt, 0, sqrt, sqrt, 0, sqrt, sqrt, 0,
+				- 1, 0, 0
+			] ), 3 );
+			var position = new BufferAttribute( new Float32Array( [
+				0.5, 0.5, 0.5, 0.5, 0.5, - 0.5, 0.5, - 0.5, 0.5,
+				0.5, - 0.5, - 0.5, - 0.5, 0.5, - 0.5, - 0.5, 0.5, 0.5,
+				- 0.5, - 0.5, - 0.5
+			] ), 3 );
+			var index = new BufferAttribute( new Uint16Array( [
+				0, 2, 1, 2, 3, 1, 4, 6, 5, 6, 7, 5
+			] ), 1 );
+
+			var a = new BufferGeometry();
+			a.addAttribute( "position", position );
+			a.computeVertexNormals();
+			assert.ok(
+				bufferAttributeEquals( normal, a.getAttribute( "normal" ) ),
+				"Regular geometry: first computed normals are correct"
+			);
+
+			// a second time to see if the existing normals get properly deleted
+			a.computeVertexNormals();
+			assert.ok(
+				bufferAttributeEquals( normal, a.getAttribute( "normal" ) ),
+				"Regular geometry: second computed normals are correct"
+			);
+
+			// indexed geometry
+			var a = new BufferGeometry();
+			a.addAttribute( "position", position );
+			a.setIndex( index );
+			a.computeVertexNormals();
+			assert.ok( bufferAttributeEquals( normal, a.getAttribute( "normal" ) ), "Indexed geometry: computed normals are correct" );
+
+		} );
+
+		QUnit.test( "merge", ( assert ) => {
+
+			var geometry1 = new BufferGeometry();
+			geometry1.addAttribute( "attrName", new BufferAttribute( new Float32Array( [ 1, 2, 3, 0, 0, 0 ] ), 3 ) );
+
+			var geometry2 = new BufferGeometry();
+			geometry2.addAttribute( "attrName", new BufferAttribute( new Float32Array( [ 4, 5, 6 ] ), 3 ) );
+
+			var attr = geometry1.attributes.attrName.array;
+
+			geometry1.merge( geometry2, 1 );
+
+			// merged array should be 1, 2, 3, 4, 5, 6
+			for ( var i = 0; i < attr.length; i ++ ) {
+
+				assert.ok( attr[ i ] === i + 1, "" );
+
+			}
+
+			geometry1.merge( geometry2 );
+			assert.ok( attr[ 0 ] === 4 && attr[ 1 ] === 5 && attr[ 2 ] === 6, "copied the 3 attributes without offset" );
+
+		} );
+
+		QUnit.test( "normalizeNormals", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "toNonIndexed", ( assert ) => {
+
+			var geometry = new BufferGeometry();
+			var vertices = new Float32Array( [
+				0.5, 0.5, 0.5, 0.5, 0.5, - 0.5, 0.5, - 0.5, 0.5, 0.5, - 0.5, - 0.5
+			] );
+			var index = new BufferAttribute( new Uint16Array( [ 0, 2, 1, 2, 3, 1 ] ) );
+			var expected = new Float32Array( [
+				0.5, 0.5, 0.5, 0.5, - 0.5, 0.5, 0.5, 0.5, - 0.5,
+				0.5, - 0.5, 0.5, 0.5, - 0.5, - 0.5, 0.5, 0.5, - 0.5
+			] );
+
+			geometry.addAttribute( 'position', new BufferAttribute( vertices, 3 ) );
+			geometry.setIndex( index );
+
+			var nonIndexed = geometry.toNonIndexed();
+
+			assert.deepEqual( nonIndexed.getAttribute( "position" ).array, expected, "Expected vertices" );
+
+		} );
+
+		QUnit.test( "toJSON", ( assert ) => {
+
+			var index = new BufferAttribute( new Uint16Array( [ 0, 1, 2, 3 ] ), 1 );
+			var attribute1 = new BufferAttribute( new Uint16Array( [ 1, 3, 5, 7 ] ), 1 );
+			var a = new BufferGeometry();
+			a.name = "JSONQUnit.test";
+			// a.parameters = { "placeholder": 0 };
+			a.addAttribute( "attribute1", attribute1 );
+			a.setIndex( index );
+			a.addGroup( 0, 1, 2 );
+			a.boundingSphere = new Sphere( new Vector3( x, y, z ), 0.5 );
+			var j = a.toJSON();
+
+			var gold = {
+				"metadata": {
+					"version": 4.5,
+					"type": "BufferGeometry",
+					"generator": "BufferGeometry.toJSON"
+				},
+				"uuid": a.uuid,
+				"type": "BufferGeometry",
+				"name": "JSONQUnit.test",
+				"data": {
+					"attributes": {
+						"attribute1": {
+							"itemSize": 1,
+							"type": "Uint16Array",
+							"array": [ 1, 3, 5, 7 ],
+							"normalized": false
+						}
+					},
+					"index": {
+						"type": "Uint16Array",
+						"array": [ 0, 1, 2, 3 ]
+					},
+					"groups": [
+						{
+							"start": 0,
+							"count": 1,
+							"materialIndex": 2
+						}
+					],
+					"boundingSphere": {
+						"center": [ 2, 3, 4 ],
+						"radius": 0.5
+					}
+				}
+			};
+
+			assert.deepEqual( j, gold, "Generated JSON is as expected" );
+
+		} );
+
+		QUnit.test( "clone", ( assert ) => {
+
+			var a = new BufferGeometry();
+			a.addAttribute( "attribute1", new BufferAttribute( new Float32Array( [ 1, 2, 3, 4, 5, 6 ] ), 3 ) );
+			a.addAttribute( "attribute2", new BufferAttribute( new Float32Array( [ 0, 1, 3, 5, 6 ] ), 1 ) );
+			a.addGroup( 0, 1, 2 );
+			a.computeBoundingBox();
+			a.computeBoundingSphere();
+			a.setDrawRange( 0, 1 );
+			var b = a.clone();
+
+			assert.notEqual( a, b, "A new object was created" );
+			assert.notEqual( a.id, b.id, "New object has a different GUID" );
+
+			assert.strictEqual(
+				Object.keys( a.attributes ).count, Object.keys( b.attributes ).count,
+				"Both objects have the same amount of attributes"
+			);
+			assert.ok(
+				bufferAttributeEquals( a.getAttribute( "attribute1" ), b.getAttribute( "attribute1" ) ),
+				"First attributes buffer is identical"
+			);
+			assert.ok(
+				bufferAttributeEquals( a.getAttribute( "attribute2" ), b.getAttribute( "attribute2" ) ),
+				"Second attributes buffer is identical"
+			);
+
+			assert.deepEqual( a.groups, b.groups, "Groups are identical" );
+
+			assert.ok( a.boundingBox.equals( b.boundingBox ), "BoundingBoxes are equal" );
+			assert.ok( a.boundingSphere.equals( b.boundingSphere ), "BoundingSpheres are equal" );
+
+			assert.strictEqual( a.drawRange.start, b.drawRange.start, "DrawRange start is identical" );
+			assert.strictEqual( a.drawRange.count, b.drawRange.count, "DrawRange count is identical" );
+
+		} );
+
+		QUnit.test( "copy", ( assert ) => {
+
+			var geometry = new BufferGeometry();
+			geometry.addAttribute( "attrName", new BufferAttribute( new Float32Array( [ 1, 2, 3, 4, 5, 6 ] ), 3 ) );
+			geometry.addAttribute( "attrName2", new BufferAttribute( new Float32Array( [ 0, 1, 3, 5, 6 ] ), 1 ) );
+
+			var copy = new BufferGeometry().copy( geometry );
+
+			assert.ok( copy !== geometry && geometry.id !== copy.id, "new object was created" );
+
+			Object.keys( geometry.attributes ).forEach( function ( key ) {
+
+				var attribute = geometry.attributes[ key ];
+				assert.ok( attribute !== undefined, "all attributes where copied" );
+
+				for ( var i = 0; i < attribute.array.length; i ++ ) {
+
+					assert.ok( attribute.array[ i ] === copy.attributes[ key ].array[ i ], "values of the attribute are equal" );
+
+				}
+
+			} );
+
+		} );
+
+		QUnit.test( "dispose", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+	} );
+
+} );

+ 90 - 0
test/unit/src/core/Clock.tests.js

@@ -0,0 +1,90 @@
+/**
+ * @author simonThiele / https://github.com/simonThiele
+ * @author TristanVALCKE / https://github.com/Itee
+ */
+/* global QUnit */
+
+import { Clock } from '../../../../src/core/Clock';
+
+export default QUnit.module( 'Core', () => {
+
+	QUnit.module.todo( 'Clock', () => {
+
+		function mockPerformance() {
+
+			self.performance = {
+				deltaTime: 0,
+
+				next: function ( delta ) {
+
+					this.deltaTime += delta;
+
+				},
+
+				now: function () {
+
+					return this.deltaTime;
+
+				}
+
+			};
+
+		}
+
+		// INSTANCING
+		QUnit.test( "Instancing", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		// PUBLIC STUFF
+		QUnit.test( "start", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "stop", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "getElapsedTime", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "getDelta", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		// OTHERS
+		QUnit.test( "clock with performance", ( assert ) => {
+
+			mockPerformance();
+
+			var clock = new Clock( false );
+
+			clock.start();
+
+			self.performance.next( 123 );
+			assert.numEqual( clock.getElapsedTime(), 0.123, "okay" );
+
+			self.performance.next( 100 );
+			assert.numEqual( clock.getElapsedTime(), 0.223, "okay" );
+
+			clock.stop();
+
+			self.performance.next( 1000 );
+			assert.numEqual( clock.getElapsedTime(), 0.223, "don't update time if the clock was stopped" );
+
+		} );
+
+	} );
+
+} );

+ 246 - 0
test/unit/src/core/DirectGeometry.tests.js

@@ -0,0 +1,246 @@
+/**
+ * @author moraxy / https://github.com/moraxy
+ * @author TristanVALCKE / https://github.com/Itee
+ */
+/* global QUnit */
+
+import { DirectGeometry } from '../../../../src/core/DirectGeometry';
+import { JSONLoader } from '../../../../src/loaders/JSONLoader';
+import { Vector2 } from '../../../../src/math/Vector2';
+import { Face3 } from '../../../../src/core/Face3';
+import { Geometry } from '../../../../src/core/Geometry';
+
+export default QUnit.module( 'Core', () => {
+
+	QUnit.module.todo( 'DirectGeometry', () => {
+
+		// INSTANCING
+		QUnit.test( "Instancing", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		// PUBLIC STUFF
+		QUnit.test( "computeGroups", ( assert ) => {
+
+			var a = new DirectGeometry();
+			var b = new Geometry();
+			var expected = [
+				{ start: 0, materialIndex: 0, count: 3 },
+				{ start: 3, materialIndex: 1, count: 3 },
+				{ start: 6, materialIndex: 2, count: 6 }
+			];
+
+			// we only care for materialIndex
+			b.faces.push(
+				new Face3( 0, 0, 0, undefined, undefined, 0 ),
+				new Face3( 0, 0, 0, undefined, undefined, 1 ),
+				new Face3( 0, 0, 0, undefined, undefined, 2 ),
+				new Face3( 0, 0, 0, undefined, undefined, 2 )
+			);
+
+			a.computeGroups( b );
+
+			assert.deepEqual( a.groups, expected, "Groups are as expected" );
+
+		} );
+
+		QUnit.test( "fromGeometry", ( assert ) => {
+
+			var a = new DirectGeometry();
+
+			var asyncDone = assert.async(); // tell QUnit when we're done with async stuff
+
+			var loader = new JSONLoader();
+			loader.load( "../../examples/models/skinned/simple/simple.js", function ( geometry ) {
+
+				a.fromGeometry( geometry );
+
+				var tmp = new DirectGeometry();
+				tmp.computeGroups( geometry );
+				assert.deepEqual( a.groups, tmp.groups, "Check groups" );
+
+				var morphTargets = geometry.morphTargets;
+				var morphTargetsLength = morphTargets.length;
+
+				var morphTargetsPosition;
+
+				if ( morphTargetsLength > 0 ) {
+
+					morphTargetsPosition = [];
+
+					for ( var i = 0; i < morphTargetsLength; i ++ ) {
+
+						morphTargetsPosition[ i ] = [];
+
+					}
+
+					morphTargets.position = morphTargetsPosition;
+
+				}
+
+				var morphNormals = geometry.morphNormals;
+				var morphNormalsLength = morphNormals.length;
+
+				var morphTargetsNormal;
+
+				if ( morphNormalsLength > 0 ) {
+
+					morphTargetsNormal = [];
+
+					for ( var i = 0; i < morphNormalsLength; i ++ ) {
+
+						morphTargetsNormal[ i ] = [];
+
+					}
+
+					morphTargets.normal = morphTargetsNormal;
+
+				}
+
+				var vertices = [];
+				var normals = [];
+				var colors = [];
+				var uvs = [];
+				var uvs2 = [];
+				var skinIndices = [];
+				var skinWeights = [];
+
+				var hasFaceVertexUv = geometry.faceVertexUvs[ 0 ] && geometry.faceVertexUvs[ 0 ].length > 0;
+				var hasFaceVertexUv2 = geometry.faceVertexUvs[ 1 ] && geometry.faceVertexUvs[ 1 ].length > 0;
+
+				var hasSkinIndices = geometry.skinIndices.length === geometry.vertices.length;
+				var hasSkinWeights = geometry.skinWeights.length === geometry.vertices.length;
+
+				for ( var i = 0; i < geometry.faces.length; i ++ ) {
+
+					var face = geometry.faces[ i ];
+
+					vertices.push(
+						geometry.vertices[ face.a ],
+						geometry.vertices[ face.b ],
+						geometry.vertices[ face.c ]
+					);
+
+					var vertexNormals = face.vertexNormals;
+
+					if ( vertexNormals.length === 3 ) {
+
+						normals.push( vertexNormals[ 0 ], vertexNormals[ 1 ], vertexNormals[ 2 ] );
+
+					} else {
+
+						normals.push( face.normal, face.normal, face.normal );
+
+					}
+
+					var vertexColors = face.vertexColors;
+
+					if ( vertexColors.length === 3 ) {
+
+						colors.push( vertexColors[ 0 ], vertexColors[ 1 ], vertexColors[ 2 ] );
+
+					} else {
+
+						colors.push( face.color, face.color, face.color );
+
+					}
+
+					if ( hasFaceVertexUv === true ) {
+
+						var vertexUvs = geometry.faceVertexUvs[ 0 ][ i ];
+
+						if ( vertexUvs !== undefined ) {
+
+							uvs.push( vertexUvs[ 0 ], vertexUvs[ 1 ], vertexUvs[ 2 ] );
+
+						} else {
+
+							uvs.push( new Vector2(), new Vector2(), new Vector2() );
+
+						}
+
+					}
+
+					if ( hasFaceVertexUv2 === true ) {
+
+						var vertexUvs = geometry.faceVertexUvs[ 1 ][ i ];
+
+						if ( vertexUvs !== undefined ) {
+
+							uvs2.push( vertexUvs[ 0 ], vertexUvs[ 1 ], vertexUvs[ 2 ] );
+
+						} else {
+
+							uvs2.push( new Vector2(), new Vector2(), new Vector2() );
+
+						}
+
+					}
+
+					// morphs
+
+					for ( var j = 0; j < morphTargetsLength; j ++ ) {
+
+						var morphTarget = morphTargets[ j ].vertices;
+
+						morphTargetsPosition[ j ].push(
+							morphTarget[ face.a ],
+							morphTarget[ face.b ],
+							morphTarget[ face.c ]
+						);
+
+					}
+
+					for ( var j = 0; j < morphNormalsLength; j ++ ) {
+
+						var morphNormal = morphNormals[ j ].vertexNormals[ i ];
+
+						morphTargetsNormal[ j ].push( morphNormal.a, morphNormal.b, morphNormal.c );
+
+					}
+
+					// skins
+
+					if ( hasSkinIndices ) {
+
+						skinIndices.push(
+							geometry.skinIndices[ face.a ],
+							geometry.skinIndices[ face.b ],
+							geometry.skinIndices[ face.c ]
+						);
+
+					}
+
+					if ( hasSkinWeights ) {
+
+						skinWeights.push(
+							geometry.skinWeights[ face.a ],
+							geometry.skinWeights[ face.b ],
+							geometry.skinWeights[ face.c ]
+						);
+
+					}
+
+				}
+
+				assert.deepEqual( a.vertices, vertices, "Vertices are identical" );
+				assert.deepEqual( a.normals, normals, "Normals are identical" );
+				assert.deepEqual( a.colors, colors, "Colors are identical" );
+				assert.deepEqual( a.uvs, uvs, "UV coordinates are identical" );
+				assert.deepEqual( a.uvs2, uvs2, "UV(2) coordinates are identical" );
+				assert.deepEqual( a.skinIndices, skinIndices, "SkinIndices are identical" );
+				assert.deepEqual( a.skinWeights, skinWeights, "SkinWeights are identical" );
+				assert.deepEqual( a.morphTargetsPosition, morphTargetsPosition, "MorphTargets (Position) are identical" );
+				assert.deepEqual( a.morphTargetsNormal, morphTargetsNormal, "MorphTargets (Normals) are identical" );
+
+				asyncDone();
+
+			} );
+
+		} );
+
+	} );
+
+} );

+ 97 - 0
test/unit/src/core/EventDispatcher.tests.js

@@ -0,0 +1,97 @@
+/**
+ * @author simonThiele / https://github.com/simonThiele
+ * @author TristanVALCKE / https://github.com/Itee
+ */
+/* global QUnit */
+
+import { EventDispatcher } from '../../../../src/core/EventDispatcher';
+
+export default QUnit.module( 'Core', () => {
+
+	QUnit.module.todo( 'EventDispatcher', () => {
+
+		// INSTANCING
+		QUnit.test( "Instancing", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		// PUBLIC STUFF
+		QUnit.test( "addEventListener", ( assert ) => {
+
+			var eventDispatcher = new EventDispatcher();
+
+			var listener = {};
+			eventDispatcher.addEventListener( 'anyType', listener );
+
+			assert.ok( eventDispatcher._listeners.anyType.length === 1, "listener with unknown type was added" );
+			assert.ok( eventDispatcher._listeners.anyType[ 0 ] === listener, "listener with unknown type was added" );
+
+			eventDispatcher.addEventListener( 'anyType', listener );
+
+			assert.ok( eventDispatcher._listeners.anyType.length === 1, "can't add one listener twice to same type" );
+			assert.ok( eventDispatcher._listeners.anyType[ 0 ] === listener, "listener is still there" );
+
+		} );
+
+		QUnit.test( "hasEventListener", ( assert ) => {
+
+			var eventDispatcher = new EventDispatcher();
+
+			var listener = {};
+			eventDispatcher.addEventListener( 'anyType', listener );
+
+			assert.ok( eventDispatcher.hasEventListener( 'anyType', listener ), "listener was found" );
+			assert.ok( ! eventDispatcher.hasEventListener( 'anotherType', listener ), "listener was not found which is good" );
+
+		} );
+
+		QUnit.test( "removeEventListener", ( assert ) => {
+
+			var eventDispatcher = new EventDispatcher();
+
+			var listener = {};
+
+			assert.ok( eventDispatcher._listeners === undefined, "there are no listeners by default" );
+
+			eventDispatcher.addEventListener( 'anyType', listener );
+			assert.ok( Object.keys( eventDispatcher._listeners ).length === 1 &&
+				eventDispatcher._listeners.anyType.length === 1, "if a listener was added, there is a new key" );
+
+			eventDispatcher.removeEventListener( 'anyType', listener );
+			assert.ok( eventDispatcher._listeners.anyType.length === 0, "listener was deleted" );
+
+			eventDispatcher.removeEventListener( 'unknownType', listener );
+			assert.ok( eventDispatcher._listeners.unknownType === undefined, "unknown types will be ignored" );
+
+			eventDispatcher.removeEventListener( 'anyType', undefined );
+			assert.ok( eventDispatcher._listeners.anyType.length === 0, "undefined listeners are ignored" );
+
+		} );
+
+		QUnit.test( "dispatchEvent", ( assert ) => {
+
+			var eventDispatcher = new EventDispatcher();
+
+			var callCount = 0;
+			var listener = function () {
+
+				callCount ++;
+
+			};
+
+			eventDispatcher.addEventListener( 'anyType', listener );
+			assert.ok( callCount === 0, "no event, no call" );
+
+			eventDispatcher.dispatchEvent( { type: 'anyType' } );
+			assert.ok( callCount === 1, "one event, one call" );
+
+			eventDispatcher.dispatchEvent( { type: 'anyType' } );
+			assert.ok( callCount === 2, "two events, two calls" );
+
+		} );
+
+	} );
+
+} );

+ 90 - 0
test/unit/src/core/Face3.tests.js

@@ -0,0 +1,90 @@
+/**
+ * @author simonThiele / https://github.com/simonThiele
+ * @author TristanVALCKE / https://github.com/Itee
+ */
+/* global QUnit */
+
+import { Face3 } from '../../../../src/core/Face3';
+import { Color } from '../../../../src/math/Color';
+import { Vector3 } from '../../../../src/math/Vector3';
+
+export default QUnit.module( 'Core', () => {
+
+	QUnit.module.todo( 'Face3', () => {
+
+		// INSTANCING
+		QUnit.test( "Instancing", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		// PUBLIC STUFF
+		QUnit.test( "copy", ( assert ) => {
+
+			var instance = new Face3( 0, 1, 2, new Vector3( 0, 1, 0 ), new Color( 0.25, 0.5, 0.75 ), 2 );
+			var copiedInstance = instance.copy( instance );
+
+			checkCopy( copiedInstance, assert );
+			checkVertexAndColors( copiedInstance, assert );
+
+		} );
+
+		QUnit.test( "copy (more)", ( assert ) => {
+
+			var instance = new Face3( 0, 1, 2,
+				[ new Vector3( 0, 1, 0 ), new Vector3( 1, 0, 1 ) ],
+				[ new Color( 0.25, 0.5, 0.75 ), new Color( 1, 0, 0.4 ) ],
+				2 );
+			var copiedInstance = instance.copy( instance );
+
+			checkCopy( copiedInstance, assert );
+			checkVertexAndColorArrays( copiedInstance, assert );
+
+		} );
+
+		QUnit.test( "clone", ( assert ) => {
+
+			var instance = new Face3( 0, 1, 2, new Vector3( 0, 1, 0 ), new Color( 0.25, 0.5, 0.75 ), 2 );
+			var copiedInstance = instance.clone();
+
+			checkCopy( copiedInstance, assert );
+			checkVertexAndColors( copiedInstance, assert );
+
+		} );
+
+		function checkCopy( copiedInstance, assert ) {
+
+			assert.ok( copiedInstance instanceof Face3, "copy created the correct type" );
+			assert.ok(
+				copiedInstance.a === 0 &&
+				copiedInstance.b === 1 &&
+				copiedInstance.c === 2 &&
+				copiedInstance.materialIndex === 2
+				, "properties where copied" );
+
+		}
+
+		function checkVertexAndColors( copiedInstance, assert ) {
+
+			assert.ok(
+				copiedInstance.normal.x === 0 && copiedInstance.normal.y === 1 && copiedInstance.normal.z === 0 &&
+				copiedInstance.color.r === 0.25 && copiedInstance.color.g === 0.5 && copiedInstance.color.b === 0.75
+				, "properties where copied" );
+
+		}
+
+		function checkVertexAndColorArrays( copiedInstance, assert ) {
+
+			assert.ok(
+				copiedInstance.vertexNormals[ 0 ].x === 0 && copiedInstance.vertexNormals[ 0 ].y === 1 && copiedInstance.vertexNormals[ 0 ].z === 0 &&
+				copiedInstance.vertexNormals[ 1 ].x === 1 && copiedInstance.vertexNormals[ 1 ].y === 0 && copiedInstance.vertexNormals[ 1 ].z === 1 &&
+				copiedInstance.vertexColors[ 0 ].r === 0.25 && copiedInstance.vertexColors[ 0 ].g === 0.5 && copiedInstance.vertexColors[ 0 ].b === 0.75 &&
+				copiedInstance.vertexColors[ 1 ].r === 1 && copiedInstance.vertexColors[ 1 ].g === 0 && copiedInstance.vertexColors[ 1 ].b === 0.4
+				, "properties where copied" );
+
+		}
+
+	} );
+
+} );

+ 483 - 0
test/unit/src/core/Geometry.tests.js

@@ -0,0 +1,483 @@
+/**
+ * @author simonThiele / https://github.com/simonThiele
+ */
+/* global QUnit */
+
+import { Geometry } from '../../../../src/core/Geometry';
+import { BufferAttribute } from '../../../../src/core/BufferAttribute';
+import { BufferGeometry } from '../../../../src/core/BufferGeometry';
+import { BoxBufferGeometry } from '../../../../src/geometries/BoxGeometry';
+import { DodecahedronGeometry } from '../../../../src/geometries/DodecahedronGeometry';
+import { Vector3 } from '../../../../src/math/Vector3';
+import { Matrix4 } from '../../../../src/math/Matrix4';
+import { Face3 } from '../../../../src/core/Face3';
+import {
+	x,
+	y,
+	z,
+	eps
+} from '../math/Constants.tests';
+
+function getGeometryByParams( x1, y1, z1, x2, y2, z2, x3, y3, z3 ) {
+
+	var geometry = new Geometry();
+
+	// a triangle
+	geometry.vertices = [
+		new Vector3( x1, y1, z1 ),
+		new Vector3( x2, y2, z2 ),
+		new Vector3( x3, y3, z3 )
+	];
+
+	return geometry;
+
+}
+
+function getGeometry() {
+
+	return getGeometryByParams( - 0.5, 0, 0, 0.5, 0, 0, 0, 1, 0 );
+
+}
+
+export default QUnit.module( 'Core', () => {
+
+	QUnit.module.todo( 'Geometry', () => {
+
+		// INHERITANCE
+		QUnit.test( "Extending", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		// INSTANCING
+		QUnit.test( "Instancing", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		// PUBLIC STUFF
+		QUnit.test( "isGeometry", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "applyMatrix", ( assert ) => {
+
+			var geometry = getGeometry();
+			geometry.faces.push( new Face3( 0, 1, 2 ) );
+			var m = new Matrix4();
+			var expectedVerts = [
+				new Vector3( 1.5, 3, 4 ),
+				new Vector3( 2.5, 3, 4 ),
+				new Vector3( 2, 3, 5 )
+			];
+			var v0, v1, v2;
+
+			m.makeRotationX( Math.PI / 2 );
+			m.setPosition( new Vector3( x, y, z ) );
+
+			geometry.applyMatrix( m );
+
+			v0 = geometry.vertices[ 0 ];
+			v1 = geometry.vertices[ 1 ];
+			v2 = geometry.vertices[ 2 ];
+			assert.ok(
+				Math.abs( v0.x - expectedVerts[ 0 ].x ) <= eps &&
+				Math.abs( v0.y - expectedVerts[ 0 ].y ) <= eps &&
+				Math.abs( v0.z - expectedVerts[ 0 ].z ) <= eps,
+				"First vertex is as expected"
+			);
+			assert.ok(
+				Math.abs( v1.x - expectedVerts[ 1 ].x ) <= eps &&
+				Math.abs( v1.y - expectedVerts[ 1 ].y ) <= eps &&
+				Math.abs( v1.z - expectedVerts[ 1 ].z ) <= eps,
+				"Second vertex is as expected"
+			);
+			assert.ok(
+				Math.abs( v2.x - expectedVerts[ 2 ].x ) <= eps &&
+				Math.abs( v2.y - expectedVerts[ 2 ].y ) <= eps &&
+				Math.abs( v2.z - expectedVerts[ 2 ].z ) <= eps,
+				"Third vertex is as expected"
+			);
+
+		} );
+
+		QUnit.test( "rotateX", ( assert ) => {
+
+			var geometry = getGeometry();
+
+			var matrix = new Matrix4();
+			matrix.makeRotationX( Math.PI / 2 ); // 90 degree
+
+			geometry.applyMatrix( matrix );
+
+			var v0 = geometry.vertices[ 0 ], v1 = geometry.vertices[ 1 ], v2 = geometry.vertices[ 2 ];
+			assert.ok( v0.x === - 0.5 && v0.y === 0 && v0.z === 0, "first vertex was rotated" );
+			assert.ok( v1.x === 0.5 && v1.y === 0 && v1.z === 0, "second vertex was rotated" );
+			assert.ok( v2.x === 0 && v2.y < Number.EPSILON && v2.z === 1, "third vertex was rotated" );
+
+		} );
+
+		QUnit.test( "rotateY", ( assert ) => {
+
+			var geometry = getGeometry();
+
+			var matrix = new Matrix4();
+			matrix.makeRotationY( Math.PI ); // 180 degrees
+
+			geometry.applyMatrix( matrix );
+
+			var v0 = geometry.vertices[ 0 ], v1 = geometry.vertices[ 1 ], v2 = geometry.vertices[ 2 ];
+			assert.ok( v0.x === 0.5 && v0.y === 0 && v0.z < Number.EPSILON, "first vertex was rotated" );
+			assert.ok( v1.x === - 0.5 && v1.y === 0 && v1.z < Number.EPSILON, "second vertex was rotated" );
+			assert.ok( v2.x === 0 && v2.y === 1 && v2.z === 0, "third vertex was rotated" );
+
+		} );
+
+		QUnit.test( "rotateZ", ( assert ) => {
+
+			var geometry = getGeometry();
+
+			var matrix = new Matrix4();
+			matrix.makeRotationZ( Math.PI / 2 * 3 ); // 270 degrees
+
+			geometry.applyMatrix( matrix );
+
+			var v0 = geometry.vertices[ 0 ], v1 = geometry.vertices[ 1 ], v2 = geometry.vertices[ 2 ];
+			assert.ok( v0.x < Number.EPSILON && v0.y === 0.5 && v0.z === 0, "first vertex was rotated" );
+			assert.ok( v1.x < Number.EPSILON && v1.y === - 0.5 && v1.z === 0, "second vertex was rotated" );
+			assert.ok( v2.x === 1 && v2.y < Number.EPSILON && v2.z === 0, "third vertex was rotated" );
+
+		} );
+
+		QUnit.test( "translate", ( assert ) => {
+
+			var a = getGeometry();
+			var expected = [
+				new Vector3( - 2.5, 3, - 4 ),
+				new Vector3( - 1.5, 3, - 4 ),
+				new Vector3( - 2, 4, - 4 )
+			];
+			var v;
+
+			a.translate( - x, y, - z );
+
+			for ( var i = 0; i < a.vertices.length; i ++ ) {
+
+				v = a.vertices[ i ];
+				assert.ok(
+					Math.abs( v.x - expected[ i ].x ) <= eps &&
+					Math.abs( v.y - expected[ i ].y ) <= eps &&
+					Math.abs( v.z - expected[ i ].z ) <= eps,
+					"Vertex #" + i + " was translated as expected"
+				);
+
+			}
+
+		} );
+
+		QUnit.test( "scale", ( assert ) => {
+
+			var a = getGeometry();
+			var expected = [
+				new Vector3( - 1, 0, 0 ),
+				new Vector3( 1, 0, 0 ),
+				new Vector3( 0, 3, 0 )
+			];
+			var v;
+
+			a.scale( 2, 3, 4 );
+
+			for ( var i = 0; i < a.vertices.length; i ++ ) {
+
+				v = a.vertices[ i ];
+				assert.ok(
+					Math.abs( v.x - expected[ i ].x ) <= eps &&
+					Math.abs( v.y - expected[ i ].y ) <= eps &&
+					Math.abs( v.z - expected[ i ].z ) <= eps,
+					"Vertex #" + i + " was scaled as expected"
+				);
+
+			}
+
+		} );
+
+		QUnit.test( "lookAt", ( assert ) => {
+
+			var a = getGeometry();
+			var expected = [
+				new Vector3( - 0.5, 0, 0 ),
+				new Vector3( 0.5, 0, 0 ),
+				new Vector3( 0, 0.5 * Math.sqrt( 2 ), 0.5 * Math.sqrt( 2 ) )
+			];
+
+			a.lookAt( new Vector3( 0, - 1, 1 ) );
+
+			for ( var i = 0; i < a.vertices.length; i ++ ) {
+
+				var v = a.vertices[ i ];
+				assert.ok(
+					Math.abs( v.x - expected[ i ].x ) <= eps &&
+					Math.abs( v.y - expected[ i ].y ) <= eps &&
+					Math.abs( v.z - expected[ i ].z ) <= eps,
+					"Vertex #" + i + " was adjusted as expected"
+				);
+
+			}
+
+		} );
+
+		QUnit.test( "fromBufferGeometry", ( assert ) => {
+
+			var bufferGeometry = new BufferGeometry();
+			bufferGeometry.addAttribute( 'position', new BufferAttribute( new Float32Array( [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ] ), 3 ) );
+			bufferGeometry.addAttribute( 'color', new BufferAttribute( new Float32Array( [ 0, 0, 0, 0.5, 0.5, 0.5, 1, 1, 1 ] ), 3 ) );
+			bufferGeometry.addAttribute( 'normal', new BufferAttribute( new Float32Array( [ 0, 1, 0, 1, 0, 1, 1, 1, 0 ] ), 3 ) );
+			bufferGeometry.addAttribute( 'uv', new BufferAttribute( new Float32Array( [ 0, 0, 0, 1, 1, 1 ] ), 2 ) );
+			bufferGeometry.addAttribute( 'uv2', new BufferAttribute( new Float32Array( [ 0, 0, 0, 1, 1, 1 ] ), 2 ) );
+
+			var geometry = new Geometry().fromBufferGeometry( bufferGeometry );
+
+			var colors = geometry.colors;
+			assert.ok(
+				colors[ 0 ].r === 0 && colors[ 0 ].g === 0 && colors[ 0 ].b === 0 &&
+				colors[ 1 ].r === 0.5 && colors[ 1 ].g === 0.5 && colors[ 1 ].b === 0.5 &&
+				colors[ 2 ].r === 1 && colors[ 2 ].g === 1 && colors[ 2 ].b === 1
+				, "colors were created well" );
+
+			var vertices = geometry.vertices;
+			assert.ok(
+				vertices[ 0 ].x === 1 && vertices[ 0 ].y === 2 && vertices[ 0 ].z === 3 &&
+				vertices[ 1 ].x === 4 && vertices[ 1 ].y === 5 && vertices[ 1 ].z === 6 &&
+				vertices[ 2 ].x === 7 && vertices[ 2 ].y === 8 && vertices[ 2 ].z === 9
+				, "vertices were created well" );
+
+			var vNormals = geometry.faces[ 0 ].vertexNormals;
+			assert.ok(
+				vNormals[ 0 ].x === 0 && vNormals[ 0 ].y === 1 && vNormals[ 0 ].z === 0 &&
+				vNormals[ 1 ].x === 1 && vNormals[ 1 ].y === 0 && vNormals[ 1 ].z === 1 &&
+				vNormals[ 2 ].x === 1 && vNormals[ 2 ].y === 1 && vNormals[ 2 ].z === 0
+				, "vertex normals were created well" );
+
+		} );
+
+		QUnit.test( "center", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "normalize", ( assert ) => {
+
+			var geometry = getGeometry();
+			geometry.computeLineDistances();
+
+			var distances = geometry.lineDistances;
+			assert.ok( distances[ 0 ] === 0, "distance to the 1st point is 0" );
+			assert.ok( distances[ 1 ] === 1 + distances[ 0 ], "distance from the 1st to the 2nd is sqrt(2nd - 1st) + distance - 1" );
+			assert.ok( distances[ 2 ] === Math.sqrt( 0.5 * 0.5 + 1 ) + distances[ 1 ], "distance from the 1st to the 3nd is sqrt(3rd - 2nd) + distance - 1" );
+
+		} );
+
+		QUnit.test( "normalize (actual)", ( assert ) => {
+
+			var a = getGeometry();
+			var sqrt = 0.5 * Math.sqrt( 2 );
+			var expected = [
+				new Vector3( - sqrt, - sqrt, 0 ),
+				new Vector3( sqrt, - sqrt, 0 ),
+				new Vector3( 0, sqrt, 0 )
+			];
+			var v;
+
+			a.normalize();
+
+			for ( var i = 0; i < a.vertices.length; i ++ ) {
+
+				v = a.vertices[ i ];
+				assert.ok(
+					Math.abs( v.x - expected[ i ].x ) <= eps &&
+					Math.abs( v.y - expected[ i ].y ) <= eps &&
+					Math.abs( v.z - expected[ i ].z ) <= eps,
+					"Vertex #" + i + " was normalized as expected"
+				);
+
+			}
+
+		} );
+
+		QUnit.test( "computeFaceNormals", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "computeVertexNormals", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "computeFlatVertexNormals", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "computeMorphNormals", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "computeLineDistances", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "computeBoundingBox", ( assert ) => {
+
+			var a = new DodecahedronGeometry();
+
+			a.computeBoundingBox();
+			assert.strictEqual( a.boundingBox.isEmpty(), false, "Bounding box isn't empty" );
+
+			var allIn = true;
+			for ( var i = 0; i < a.vertices.length; i ++ ) {
+
+				if ( ! a.boundingBox.containsPoint( a.vertices[ i ] ) ) {
+
+					allIn = false;
+
+				}
+
+			}
+			assert.strictEqual( allIn, true, "All vertices are inside the box" );
+
+		} );
+
+		QUnit.test( "computeBoundingSphere", ( assert ) => {
+
+			var a = new DodecahedronGeometry();
+
+			a.computeBoundingSphere();
+
+			var allIn = true;
+			for ( var i = 0; i < a.vertices.length; i ++ ) {
+
+				if ( ! a.boundingSphere.containsPoint( a.vertices[ i ] ) ) {
+
+					allIn = false;
+
+				}
+
+			}
+			assert.strictEqual( allIn, true, "All vertices are inside the bounding sphere" );
+
+		} );
+
+		QUnit.test( "merge", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "mergeMesh", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "mergeVertices", ( assert ) => {
+
+			var a = new Geometry();
+			var b = new BoxBufferGeometry( 1, 1, 1 );
+			var verts, faces, removed;
+
+			a.fromBufferGeometry( b );
+
+			removed = a.mergeVertices();
+			verts = a.vertices.length;
+			faces = a.faces.length;
+
+			assert.strictEqual( removed, 16, "Removed the expected number of vertices" );
+			assert.strictEqual( verts, 8, "Minimum number of vertices remaining" );
+			assert.strictEqual( faces, 12, "Minimum number of faces remaining" );
+
+		} );
+
+		QUnit.test( "sortFacesByMaterialIndex", ( assert ) => {
+
+			var box = new BoxBufferGeometry( 1, 1, 1 );
+			var a = new Geometry().fromBufferGeometry( box );
+			var expected = [ 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5 ];
+
+			a.faces.reverse(); // a bit too simple probably, still missing stuff like checking new UVs
+			a.sortFacesByMaterialIndex();
+
+			var indices = [];
+
+			for ( var i = 0; i < a.faces.length; i ++ ) {
+
+				indices.push( a.faces[ i ].materialIndex );
+
+			}
+
+			assert.deepEqual( indices, expected, "Faces in correct order" );
+
+		} );
+
+		QUnit.test( "toJSON", ( assert ) => {
+
+			var a = getGeometry();
+			var gold = {
+				"metadata": {
+					"version": 4.5,
+					"type": "Geometry",
+					"generator": "Geometry.toJSON"
+				},
+				"uuid": null,
+				"type": "Geometry",
+				"data": {
+					"vertices": [ - 0.5, 0, 0, 0.5, 0, 0, 0, 1, 0 ],
+					"normals": [ 0, 0, 1 ],
+					"faces": [ 50, 0, 1, 2, 0, 0, 0, 0, 0 ]
+				}
+			};
+			var json;
+
+			a.faces.push( new Face3( 0, 1, 2 ) );
+			a.computeFaceNormals();
+			a.computeVertexNormals();
+
+			json = a.toJSON();
+			json.uuid = null;
+			assert.deepEqual( json, gold, "Generated JSON is as expected" );
+
+		} );
+
+		QUnit.test( "clone", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "copy", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "dispose", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+	} );
+
+} );

+ 51 - 0
test/unit/src/core/InstancedBufferAttribute.tests.js

@@ -0,0 +1,51 @@
+/**
+ * @author simonThiele / https://github.com/simonThiele
+ */
+/* global QUnit */
+
+import { InstancedBufferAttribute } from '../../../../src/core/InstancedBufferAttribute';
+
+export default QUnit.module( 'Core', () => {
+
+	QUnit.module.todo( 'InstancedBufferAttribute', () => {
+
+		// INHERITANCE
+		QUnit.test( "Extending", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		// INSTANCING
+		QUnit.test( "Instancing", ( assert ) => {
+
+			var instance = new InstancedBufferAttribute( new Float32Array( 10 ), 2 );
+			assert.ok( instance.meshPerAttribute === 1, "ok" );
+
+			var instance = new InstancedBufferAttribute( new Float32Array( 10 ), 2, 123 );
+			assert.ok( instance.meshPerAttribute === 123, "ok" );
+
+		} );
+
+		// PUBLIC STUFF
+		QUnit.test( "copy", ( assert ) => {
+
+			var array = new Float32Array( [ 1, 2, 3, 7, 8, 9 ] );
+			var instance = new InstancedBufferAttribute( array, 2, 123 );
+			var copiedInstance = instance.copy( instance );
+
+			assert.ok( copiedInstance instanceof InstancedBufferAttribute, "the clone has the correct type" );
+			assert.ok( copiedInstance.itemSize === 2, "itemSize was copied" );
+			assert.ok( copiedInstance.meshPerAttribute === 123, "meshPerAttribute was copied" );
+
+			for ( var i = 0; i < array.length; i ++ ) {
+
+				assert.ok( copiedInstance.array[ i ] === array[ i ], "array was copied" );
+
+			}
+
+		} );
+
+	} );
+
+} );

+ 87 - 0
test/unit/src/core/InstancedBufferGeometry.tests.js

@@ -0,0 +1,87 @@
+/**
+ * @author simonThiele / https://github.com/simonThiele
+ */
+/* global QUnit */
+
+import { InstancedBufferGeometry } from '../../../../src/core/InstancedBufferGeometry';
+import { BufferAttribute } from '../../../../src/core/BufferAttribute';
+
+export default QUnit.module( 'Core', () => {
+
+	QUnit.module.todo( 'InstancedBufferGeometry', () => {
+
+		function createClonableMock() {
+
+			return {
+				callCount: 0,
+				clone: function () {
+
+					this.callCount ++;
+					return this;
+
+				}
+			};
+
+		}
+
+		// INHERITANCE
+		QUnit.test( "Extending", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		// INSTANCING
+		QUnit.test( "Instancing", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		// PUBLIC STUFF
+		QUnit.test( "copy", ( assert ) => {
+
+			var instanceMock1 = {};
+			var instanceMock2 = {};
+			var indexMock = createClonableMock();
+			var defaultAttribute1 = new BufferAttribute( new Float32Array( [ 1 ] ) );
+			var defaultAttribute2 = new BufferAttribute( new Float32Array( [ 2 ] ) );
+
+			var instance = new InstancedBufferGeometry();
+
+			instance.addGroup( 0, 10, instanceMock1 );
+			instance.addGroup( 10, 5, instanceMock2 );
+			instance.setIndex( indexMock );
+			instance.addAttribute( 'defaultAttribute1', defaultAttribute1 );
+			instance.addAttribute( 'defaultAttribute2', defaultAttribute2 );
+
+			var copiedInstance = new InstancedBufferGeometry().copy( instance );
+
+			assert.ok( copiedInstance instanceof InstancedBufferGeometry, "the clone has the correct type" );
+
+			assert.equal( copiedInstance.index, indexMock, "index was copied" );
+			assert.equal( copiedInstance.index.callCount, 1, "index.clone was called once" );
+
+			assert.ok( copiedInstance.attributes[ 'defaultAttribute1' ] instanceof BufferAttribute, "attribute was created" );
+			assert.deepEqual( copiedInstance.attributes[ 'defaultAttribute1' ].array, defaultAttribute1.array, "attribute was copied" );
+			assert.deepEqual( copiedInstance.attributes[ 'defaultAttribute2' ].array, defaultAttribute2.array, "attribute was copied" );
+
+			assert.equal( copiedInstance.groups[ 0 ].start, 0, "group was copied" );
+			assert.equal( copiedInstance.groups[ 0 ].count, 10, "group was copied" );
+			assert.equal( copiedInstance.groups[ 0 ].materialIndex, instanceMock1, "group was copied" );
+
+			assert.equal( copiedInstance.groups[ 1 ].start, 10, "group was copied" );
+			assert.equal( copiedInstance.groups[ 1 ].count, 5, "group was copied" );
+			assert.equal( copiedInstance.groups[ 1 ].materialIndex, instanceMock2, "group was copied" );
+
+		} );
+
+		QUnit.test( "clone", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+	} );
+
+} );

+ 48 - 0
test/unit/src/core/InstancedInterleavedBuffer.tests.js

@@ -0,0 +1,48 @@
+/**
+ * @author simonThiele / https://github.com/simonThiele
+ */
+/* global QUnit */
+
+import { InstancedInterleavedBuffer } from '../../../../src/core/InstancedInterleavedBuffer';
+
+export default QUnit.module( 'Core', () => {
+
+	QUnit.module.todo( 'InstancedInterleavedBuffer', () => {
+
+		// INHERITANCE
+		QUnit.test( "Extending", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		// INSTANCING
+		QUnit.test( "Instancing", ( assert ) => {
+
+			var array = new Float32Array( [ 1, 2, 3, 7, 8, 9 ] );
+			var instance = new InstancedInterleavedBuffer( array, 3 );
+
+			assert.ok( instance.meshPerAttribute === 1, "ok" );
+
+		} );
+
+		// PUBLIC STUFF
+		QUnit.test( "isInstancedInterleavedBuffer", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "copy", ( assert ) => {
+
+			var array = new Float32Array( [ 1, 2, 3, 7, 8, 9 ] );
+			var instance = new InstancedInterleavedBuffer( array, 3 );
+			var copiedInstance = instance.copy( instance );
+
+			assert.ok( copiedInstance.meshPerAttribute === 1, "additional attribute was copied" );
+
+		} );
+
+	} );
+
+} );

+ 151 - 0
test/unit/src/core/InterleavedBuffer.tests.js

@@ -0,0 +1,151 @@
+/**
+ * @author simonThiele / https://github.com/simonThiele
+ */
+/* global QUnit */
+
+import { InterleavedBuffer } from '../../../../src/core/InterleavedBuffer';
+
+export default QUnit.module( 'Core', () => {
+
+	QUnit.module.todo( 'InterleavedBuffer', () => {
+
+		function checkInstanceAgainstCopy( instance, copiedInstance, assert ) {
+
+			assert.ok( copiedInstance instanceof InterleavedBuffer, "the clone has the correct type" );
+
+			for ( var i = 0; i < instance.array.length; i ++ ) {
+
+				assert.ok( copiedInstance.array[ i ] === instance.array[ i ], "array was copied" );
+
+			}
+
+			assert.ok( copiedInstance.stride === instance.stride, "stride was copied" );
+			assert.ok( copiedInstance.dynamic === true, "dynamic was copied" );
+
+		}
+
+		// INSTANCING
+		QUnit.test( "Instancing", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		// PROPERTIES
+		QUnit.test( "needsUpdate", ( assert ) => {
+
+			var a = new InterleavedBuffer( new Float32Array( [ 1, 2, 3, 4 ], 2 ) );
+
+			a.needsUpdate = true;
+
+			assert.strictEqual( a.version, 1, "Check version increased" );
+
+		} );
+
+		// PUBLIC STUFF
+		QUnit.test( "isInterleavedBuffer", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "setArray", ( assert ) => {
+
+			var f32a = new Float32Array( [ 1, 2, 3, 4 ] );
+			var f32b = new Float32Array( [] );
+			var a = new InterleavedBuffer( f32a, 2, false );
+
+			a.setArray( f32a );
+
+			assert.strictEqual( a.count, 2, "Check item count for non-empty array" );
+			assert.strictEqual( a.array, f32a, "Check array itself" );
+
+			a.setArray( f32b );
+
+			assert.strictEqual( a.count, 0, "Check item count for empty array" );
+			assert.strictEqual( a.array, f32b, "Check array itself" );
+
+			assert.throws(
+				function () {
+
+					a.setArray( [ 1, 2, 3, 4 ] );
+
+				},
+				/array should be a Typed Array/,
+				"Calling setArray with a non-typed array throws Error"
+			);
+
+		} );
+
+		QUnit.test( "setDynamic", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "copy", ( assert ) => {
+
+			var array = new Float32Array( [ 1, 2, 3, 7, 8, 9 ] );
+			var instance = new InterleavedBuffer( array, 3 );
+			instance.setDynamic( true );
+
+			checkInstanceAgainstCopy( instance, instance.copy( instance ), assert );
+
+		} );
+
+		QUnit.test( "copyAt", ( assert ) => {
+
+			var a = new InterleavedBuffer( new Float32Array( [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ] ), 3 );
+			var b = new InterleavedBuffer( new Float32Array( 9 ), 3 );
+			var expected = new Float32Array( [ 4, 5, 6, 7, 8, 9, 1, 2, 3 ] );
+
+			b.copyAt( 1, a, 2 );
+			b.copyAt( 0, a, 1 );
+			b.copyAt( 2, a, 0 );
+
+			assert.deepEqual( b.array, expected, "Check the right values were replaced" );
+
+		} );
+
+		QUnit.test( "set", ( assert ) => {
+
+			var instance = new InterleavedBuffer( new Float32Array( [ 1, 2, 3, 7, 8, 9 ] ), 3 );
+
+			instance.set( [ 0, - 1 ] );
+			assert.ok( instance.array[ 0 ] === 0 && instance.array[ 1 ] === - 1, "replace at first by default" );
+
+		} );
+
+		QUnit.test( "clone", ( assert ) => {
+
+			var array = new Float32Array( [ 1, 2, 3, 7, 8, 9 ] );
+			var instance = new InterleavedBuffer( array, 3 );
+			instance.setDynamic( true );
+
+			checkInstanceAgainstCopy( instance, instance.clone(), assert );
+
+		} );
+
+		QUnit.test( "onUpload", ( assert ) => {
+
+			var a = new InterleavedBuffer();
+			var func = function () { };
+
+			a.onUpload( func );
+
+			assert.strictEqual( a.onUploadCallback, func, "Check callback was set properly" );
+
+		} );
+
+		// OTHERS
+		QUnit.test( "count", ( assert ) => {
+
+			var instance = new InterleavedBuffer( new Float32Array( [ 1, 2, 3, 7, 8, 9 ] ), 3 );
+
+			assert.equal( instance.count, 2, "count is calculated via array length / stride" );
+
+		} );
+
+	} );
+
+} );

+ 125 - 0
test/unit/src/core/InterleavedBufferAttribute.tests.js

@@ -0,0 +1,125 @@
+/**
+ * @author simonThiele / https://github.com/simonThiele
+ * @author TristanVALCKE / https://github.com/Itee
+ */
+/* global QUnit */
+
+import { InterleavedBuffer } from '../../../../src/core/InterleavedBuffer';
+import { InterleavedBufferAttribute } from '../../../../src/core/InterleavedBufferAttribute';
+
+export default QUnit.module( 'Core', () => {
+
+	QUnit.module.todo( 'InterleavedBufferAttribute', () => {
+
+		// INSTANCING
+		QUnit.test( "Instancing", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		// PROPERTIES
+		QUnit.test( "count", ( assert ) => {
+
+			var buffer = new InterleavedBuffer( new Float32Array( [ 1, 2, 3, 7, 8, 9 ] ), 3 );
+			var instance = new InterleavedBufferAttribute( buffer, 2, 0 );
+
+			assert.ok( instance.count === 2, "count is calculated via array length / stride" );
+
+		} );
+
+		QUnit.test( "array", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		// PUBLIC STUFF
+		// setY, setZ and setW are calculated in the same way so not QUnit.testing this
+		// TODO: ( you can't be sure that will be the case in future, or a mistake was introduce in one off them ! )
+		QUnit.test( "setX", ( assert ) => {
+
+			var buffer = new InterleavedBuffer( new Float32Array( [ 1, 2, 3, 7, 8, 9 ] ), 3 );
+			var instance = new InterleavedBufferAttribute( buffer, 2, 0 );
+
+			instance.setX( 0, 123 );
+			instance.setX( 1, 321 );
+
+			assert.ok( instance.data.array[ 0 ] === 123 &&
+				instance.data.array[ 3 ] === 321, "x was calculated correct based on index and default offset" );
+
+			var buffer = new InterleavedBuffer( new Float32Array( [ 1, 2, 3, 7, 8, 9 ] ), 3 );
+			var instance = new InterleavedBufferAttribute( buffer, 2, 1 );
+
+			instance.setX( 0, 123 );
+			instance.setX( 1, 321 );
+
+			// the offset was defined as 1, so go one step futher in the array
+			assert.ok( instance.data.array[ 1 ] === 123 &&
+				instance.data.array[ 4 ] === 321, "x was calculated correct based on index and default offset" );
+
+		} );
+
+		QUnit.test( "setY", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "setZ", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "setW", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "getX", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "getY", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "getZ", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "getW", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "setXY", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "setXYZ", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "setXYZW", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+	} );
+
+} );

+ 118 - 0
test/unit/src/core/Layers.tests.js

@@ -0,0 +1,118 @@
+/**
+ * @author moraxy / https://github.com/moraxy
+ */
+/* global QUnit */
+
+import { Layers } from '../../../../src/core/Layers';
+
+export default QUnit.module( 'Core', () => {
+
+	QUnit.module.todo( 'Layers', () => {
+
+		// INSTANCING
+		QUnit.test( "Instancing", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		// PUBLIC STUFF
+		QUnit.test( "set", ( assert ) => {
+
+			var a = new Layers();
+
+			a.set( 0 );
+			assert.strictEqual( a.mask, 1, "Set channel 0" );
+
+			a.set( 1 );
+			assert.strictEqual( a.mask, 2, "Set channel 1" );
+
+			a.set( 2 );
+			assert.strictEqual( a.mask, 4, "Set channel 2" );
+
+		} );
+
+		QUnit.test( "enable", ( assert ) => {
+
+			var a = new Layers();
+
+			a.set( 0 );
+			a.enable( 0 );
+			assert.strictEqual( a.mask, 1, "Enable channel 0 with mask 0" );
+
+			a.set( 0 );
+			a.enable( 1 );
+			assert.strictEqual( a.mask, 3, "Enable channel 1 with mask 0" );
+
+			a.set( 1 );
+			a.enable( 0 );
+			assert.strictEqual( a.mask, 3, "Enable channel 0 with mask 1" );
+
+			a.set( 1 );
+			a.enable( 1 );
+			assert.strictEqual( a.mask, 2, "Enable channel 1 with mask 1" );
+
+		} );
+
+		QUnit.test( "toggle", ( assert ) => {
+
+			var a = new Layers();
+
+			a.set( 0 );
+			a.toggle( 0 );
+			assert.strictEqual( a.mask, 0, "Toggle channel 0 with mask 0" );
+
+			a.set( 0 );
+			a.toggle( 1 );
+			assert.strictEqual( a.mask, 3, "Toggle channel 1 with mask 0" );
+
+			a.set( 1 );
+			a.toggle( 0 );
+			assert.strictEqual( a.mask, 3, "Toggle channel 0 with mask 1" );
+
+			a.set( 1 );
+			a.toggle( 1 );
+			assert.strictEqual( a.mask, 0, "Toggle channel 1 with mask 1" );
+
+		} );
+
+		QUnit.test( "disable", ( assert ) => {
+
+			var a = new Layers();
+
+			a.set( 0 );
+			a.disable( 0 );
+			assert.strictEqual( a.mask, 0, "Disable channel 0 with mask 0" );
+
+			a.set( 0 );
+			a.disable( 1 );
+			assert.strictEqual( a.mask, 1, "Disable channel 1 with mask 0" );
+
+			a.set( 1 );
+			a.disable( 0 );
+			assert.strictEqual( a.mask, 2, "Disable channel 0 with mask 1" );
+
+			a.set( 1 );
+			a.disable( 1 );
+			assert.strictEqual( a.mask, 0, "Disable channel 1 with mask 1" );
+
+		} );
+
+		QUnit.test( "test", ( assert ) => {
+
+			var a = new Layers();
+			var b = new Layers();
+
+			assert.ok( a.test( b ), "Start out true" );
+
+			a.set( 1 );
+			assert.notOk( a.test( b ), "Set channel 1 in a and fail the QUnit.test" );
+
+			b.toggle( 1 );
+			assert.ok( a.test( b ), "Toggle channel 1 in b and pass again" );
+
+		} );
+
+	} );
+
+} );

+ 619 - 0
test/unit/src/core/Object3D.tests.js

@@ -0,0 +1,619 @@
+/**
+ * @author simonThiele / https://github.com/simonThiele
+ * @author TristanVALCKE / https://github.com/Itee
+ */
+/* global QUnit */
+
+import { Object3D } from '../../../../src/core/Object3D';
+import { Vector3 } from '../../../../src/math/Vector3';
+import { Euler } from '../../../../src/math/Euler';
+import { Quaternion } from '../../../../src/math/Quaternion';
+import { Matrix4 } from '../../../../src/math/Matrix4';
+import {
+	x,
+	y,
+	z,
+	w,
+	eps
+} from '../math/Constants.tests';
+
+export default QUnit.module( 'Core', () => {
+
+	QUnit.module.todo( 'Object3D', () => {
+
+		var RadToDeg = 180 / Math.PI;
+
+		var eulerEquals = function ( a, b, tolerance ) {
+
+			tolerance = tolerance || 0.0001;
+
+			if ( a.order != b.order ) {
+
+				return false;
+
+			}
+
+			return (
+				Math.abs( a.x - b.x ) <= tolerance &&
+				Math.abs( a.y - b.y ) <= tolerance &&
+				Math.abs( a.z - b.z ) <= tolerance
+			);
+
+		};
+
+		// INHERITANCE
+		QUnit.test( "Extending", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		// INSTANCING
+		QUnit.test( "Instancing", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		// STATIC STUFF
+		QUnit.test( "DefaultUp", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "DefaultMatrixAutoUpdate", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		// PUBLIC STUFF
+		QUnit.test( "isObject3D", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "onBeforeRender", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "onAfterRender", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "applyMatrix", ( assert ) => {
+
+			var a = new Object3D();
+			var m = new Matrix4();
+			var expectedPos = new Vector3( x, y, z );
+			var expectedQuat = new Quaternion( 0.5 * Math.sqrt( 2 ), 0, 0, 0.5 * Math.sqrt( 2 ) );
+
+			m.makeRotationX( Math.PI / 2 );
+			m.setPosition( new Vector3( x, y, z ) );
+
+			a.applyMatrix( m );
+
+			assert.deepEqual( a.position, expectedPos, "Position has the expected values" );
+			assert.ok(
+				Math.abs( a.quaternion.x - expectedQuat.x ) <= eps &&
+				Math.abs( a.quaternion.y - expectedQuat.y ) <= eps &&
+				Math.abs( a.quaternion.z - expectedQuat.z ) <= eps,
+				"Quaternion has the expected values"
+			);
+
+		} );
+
+		QUnit.test( "applyQuaternion", ( assert ) => {
+
+			var a = new Object3D();
+			var sqrt = 0.5 * Math.sqrt( 2 );
+			var quat = new Quaternion( 0, sqrt, 0, sqrt );
+			var expected = new Quaternion( sqrt / 2, sqrt / 2, 0, 0 );
+
+			a.quaternion.set( 0.25, 0.25, 0.25, 0.25 );
+			a.applyQuaternion( quat );
+
+			assert.ok(
+				Math.abs( a.quaternion.x - expected.x ) <= eps &&
+				Math.abs( a.quaternion.y - expected.y ) <= eps &&
+				Math.abs( a.quaternion.z - expected.z ) <= eps,
+				"Quaternion has the expected values"
+			);
+
+		} );
+
+		QUnit.test( "setRotationFromAxisAngle", ( assert ) => {
+
+			var a = new Object3D();
+			var axis = new Vector3( 0, 1, 0 );
+			var angle = Math.PI;
+			var expected = new Euler( - Math.PI, 0, - Math.PI );
+
+			a.setRotationFromAxisAngle( axis, angle );
+			assert.ok( eulerEquals( a.getWorldRotation(), expected ), "Correct values after rotation" );
+
+			axis.set( 1, 0, 0 );
+			var angle = 0;
+			expected.set( 0, 0, 0 );
+
+			a.setRotationFromAxisAngle( axis, angle );
+			assert.ok( eulerEquals( a.getWorldRotation(), expected ), "Correct values after zeroing" );
+
+		} );
+
+		QUnit.test( "setRotationFromEuler", ( assert ) => {
+
+			var a = new Object3D();
+			var rotation = new Euler( ( 45 / RadToDeg ), 0, Math.PI );
+			var expected = rotation.clone(); // bit obvious
+
+			a.setRotationFromEuler( rotation );
+			assert.ok( eulerEquals( a.getWorldRotation(), expected ), "Correct values after rotation" );
+
+		} );
+
+		QUnit.test( "setRotationFromMatrix", ( assert ) => {
+
+			var a = new Object3D();
+			var m = new Matrix4();
+			var eye = new Vector3( 0, 0, 0 );
+			var target = new Vector3( 0, 1, - 1 );
+			var up = new Vector3( 0, 1, 0 );
+
+			m.lookAt( eye, target, up );
+			a.setRotationFromMatrix( m );
+			assert.numEqual( a.getWorldRotation().x * RadToDeg, 45, "Correct rotation angle" );
+
+		} );
+
+		QUnit.test( "setRotationFromQuaternion", ( assert ) => {
+
+			var a = new Object3D();
+			var rotation = new Quaternion().setFromEuler( new Euler( Math.PI, 0, - Math.PI ) );
+			var expected = new Euler( Math.PI, 0, - Math.PI );
+
+			a.setRotationFromQuaternion( rotation );
+			assert.ok( eulerEquals( a.getWorldRotation(), expected ), "Correct values after rotation" );
+
+		} );
+
+		QUnit.test( "rotateOnAxis", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "rotateOnWorldAxis", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "rotateX", ( assert ) => {
+
+			var obj = new Object3D();
+
+			var angleInRad = 1.562;
+			obj.rotateX( angleInRad );
+
+			assert.numEqual( obj.rotation.x, angleInRad, "x is equal" );
+
+		} );
+
+		QUnit.test( "rotateY", ( assert ) => {
+
+			var obj = new Object3D();
+
+			var angleInRad = - 0.346;
+			obj.rotateY( angleInRad );
+
+			assert.numEqual( obj.rotation.y, angleInRad, "y is equal" );
+
+		} );
+
+		QUnit.test( "rotateZ", ( assert ) => {
+
+			var obj = new Object3D();
+
+			var angleInRad = 1;
+			obj.rotateZ( angleInRad );
+
+			assert.numEqual( obj.rotation.z, angleInRad, "z is equal" );
+
+		} );
+
+		QUnit.test( "translateOnAxis", ( assert ) => {
+
+			var obj = new Object3D();
+
+			obj.translateOnAxis( new Vector3( 1, 0, 0 ), 1 );
+			obj.translateOnAxis( new Vector3( 0, 1, 0 ), 1.23 );
+			obj.translateOnAxis( new Vector3( 0, 0, 1 ), - 4.56 );
+
+			assert.propEqual( obj.position, {
+				x: 1,
+				y: 1.23,
+				z: - 4.56
+			} );
+
+		} );
+
+		QUnit.test( "translateX", ( assert ) => {
+
+			var obj = new Object3D();
+			obj.translateX( 1.234 );
+
+			assert.numEqual( obj.position.x, 1.234, "x is equal" );
+
+		} );
+
+		QUnit.test( "translateY", ( assert ) => {
+
+			var obj = new Object3D();
+			obj.translateY( 1.234 );
+
+			assert.numEqual( obj.position.y, 1.234, "y is equal" );
+
+		} );
+
+		QUnit.test( "translateZ", ( assert ) => {
+
+			var obj = new Object3D();
+			obj.translateZ( 1.234 );
+
+			assert.numEqual( obj.position.z, 1.234, "z is equal" );
+
+		} );
+
+		QUnit.test( "localToWorld", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "worldToLocal", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "lookAt", ( assert ) => {
+
+			var obj = new Object3D();
+			obj.lookAt( new Vector3( 0, - 1, 1 ) );
+
+			assert.numEqual( obj.rotation.x * RadToDeg, 45, "x is equal" );
+
+		} );
+
+		QUnit.test( "add/remove", ( assert ) => {
+
+			var a = new Object3D();
+			var child1 = new Object3D();
+			var child2 = new Object3D();
+
+			assert.strictEqual( a.children.length, 0, "Starts with no children" );
+
+			a.add( child1 );
+			assert.strictEqual( a.children.length, 1, "The first child was added" );
+			assert.strictEqual( a.children[ 0 ], child1, "It's the right one" );
+
+			a.add( child2 );
+			assert.strictEqual( a.children.length, 2, "The second child was added" );
+			assert.strictEqual( a.children[ 1 ], child2, "It's the right one" );
+			assert.strictEqual( a.children[ 0 ], child1, "The first one is still there" );
+
+			a.remove( child1 );
+			assert.strictEqual( a.children.length, 1, "The first child was removed" );
+			assert.strictEqual( a.children[ 0 ], child2, "The second one is still there" );
+
+			a.add( child1 );
+			a.remove( child1, child2 );
+			assert.strictEqual( a.children.length, 0, "Both children were removed at once" );
+
+			child1.add( child2 );
+			assert.strictEqual( child1.children.length, 1, "The second child was added to the first one" );
+			a.add( child2 );
+			assert.strictEqual( a.children.length, 1, "The second one was added to the parent (no remove)" );
+			assert.strictEqual( a.children[ 0 ], child2, "The second one is now the parent's child again" );
+			assert.strictEqual( child1.children.length, 0, "The first one no longer has any children" );
+
+		} );
+
+		QUnit.test( "getObjectById/getObjectByName/getObjectByProperty", ( assert ) => {
+
+			var parent = new Object3D();
+			var childName = new Object3D();
+			var childId = new Object3D(); // id = parent.id + 2
+			var childNothing = new Object3D();
+
+			parent.prop = true;
+			childName.name = "foo";
+			parent.add( childName, childId, childNothing );
+
+			assert.strictEqual( parent.getObjectByProperty( 'prop', true ), parent, "Get parent by its own property" );
+			assert.strictEqual( parent.getObjectByName( "foo" ), childName, "Get child by name" );
+			assert.strictEqual( parent.getObjectById( parent.id + 2 ), childId, "Get child by Id" );
+			assert.strictEqual(
+				parent.getObjectByProperty( 'no-property', 'no-value' ), undefined,
+				"Unknown property results in undefined"
+			);
+
+		} );
+
+		QUnit.test( "getWorldPosition", ( assert ) => {
+
+			var a = new Object3D();
+			var b = new Object3D();
+			var expectedSingle = new Vector3( x, y, z );
+			var expectedParent = new Vector3( x, y, 0 );
+			var expectedChild = new Vector3( x, y, 7 + ( z - z ) );
+
+			a.translateX( x );
+			a.translateY( y );
+			a.translateZ( z );
+
+			assert.deepEqual(
+				a.getWorldPosition(), expectedSingle,
+				"WorldPosition as expected for single object"
+			);
+
+			// translate child and then parent
+			b.translateZ( 7 );
+			a.add( b );
+			a.translateZ( - z );
+
+			assert.deepEqual( a.getWorldPosition(), expectedParent, "WorldPosition as expected for parent" );
+			assert.deepEqual( b.getWorldPosition(), expectedChild, "WorldPosition as expected for child" );
+
+		} );
+
+		QUnit.test( "getWorldQuaternion", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "getWorldRotation", ( assert ) => {
+
+			var obj = new Object3D();
+
+			obj.lookAt( new Vector3( 0, - 1, 1 ) );
+			assert.numEqual( obj.getWorldRotation().x * RadToDeg, 45, "x is equal" );
+
+			obj.lookAt( new Vector3( 1, 0, 0 ) );
+			assert.numEqual( obj.getWorldRotation().y * RadToDeg, 90, "y is equal" );
+
+		} );
+
+		QUnit.test( "getWorldScale", ( assert ) => {
+
+			var a = new Object3D();
+			var m = new Matrix4().makeScale( x, y, z );
+			var expected = new Vector3( x, y, z );
+
+			a.applyMatrix( m );
+
+			assert.deepEqual( a.getWorldScale(), expected, "WorldScale as expected" );
+
+		} );
+
+		QUnit.test( "getWorldDirection", ( assert ) => {
+
+			var a = new Object3D();
+			var expected = new Vector3( 0, - 0.5 * Math.sqrt( 2 ), 0.5 * Math.sqrt( 2 ) );
+			var dir;
+
+			a.lookAt( new Vector3( 0, - 1, 1 ) );
+			dir = a.getWorldDirection();
+
+			assert.ok(
+				Math.abs( dir.x - expected.x ) <= eps &&
+				Math.abs( dir.y - expected.y ) <= eps &&
+				Math.abs( dir.z - expected.z ) <= eps,
+				"Direction has the expected values"
+			);
+
+		} );
+
+		QUnit.test( "raycast", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "traverse/traverseVisible/traverseAncestors", ( assert ) => {
+
+			var a = new Object3D();
+			var b = new Object3D();
+			var c = new Object3D();
+			var d = new Object3D();
+			var names = [];
+			var expectedNormal = [ "parent", "child", "childchild 1", "childchild 2" ];
+			var expectedVisible = [ "parent", "child", "childchild 2" ];
+			var expectedAncestors = [ "child", "parent" ];
+
+			a.name = "parent";
+			b.name = "child";
+			c.name = "childchild 1";
+			c.visible = false;
+			d.name = "childchild 2";
+
+			b.add( c );
+			b.add( d );
+			a.add( b );
+
+			a.traverse( function ( obj ) {
+
+				names.push( obj.name );
+
+			} );
+			assert.deepEqual( names, expectedNormal, "Traversed objects in expected order" );
+
+			var names = [];
+			a.traverseVisible( function ( obj ) {
+
+				names.push( obj.name );
+
+			} );
+			assert.deepEqual( names, expectedVisible, "Traversed visible objects in expected order" );
+
+			var names = [];
+			c.traverseAncestors( function ( obj ) {
+
+				names.push( obj.name );
+
+			} );
+			assert.deepEqual( names, expectedAncestors, "Traversed ancestors in expected order" );
+
+		} );
+
+		QUnit.test( "updateMatrix", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "updateMatrixWorld", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "toJSON", ( assert ) => {
+
+			var a = new Object3D();
+			var child = new Object3D();
+			var childChild = new Object3D();
+
+			a.name = "a's name";
+			a.matrix.set( 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 );
+			a.visible = false;
+			a.castShadow = true;
+			a.receiveShadow = true;
+			a.userData[ "foo" ] = "bar";
+
+			child.uuid = "5D4E9AE8-DA61-4912-A575-71A5BE3D72CD";
+			childChild.uuid = "B43854B3-E970-4E85-BD41-AAF8D7BFA189";
+			child.add( childChild );
+			a.add( child );
+
+			var gold = {
+				"metadata": {
+					"version": 4.5,
+					"type": "Object",
+					"generator": "Object3D.toJSON"
+				},
+				"object": {
+					"uuid": "0A1E4F43-CB5B-4097-8F82-DC2969C0B8C2",
+					"type": "Object3D",
+					"name": "a's name",
+					"castShadow": true,
+					"receiveShadow": true,
+					"visible": false,
+					"userData": { "foo": "bar" },
+					"matrix": [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ],
+					"children": [
+						{
+							"uuid": "5D4E9AE8-DA61-4912-A575-71A5BE3D72CD",
+							"type": "Object3D",
+							"matrix": [ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 ],
+							"children": [
+								{
+									"uuid": "B43854B3-E970-4E85-BD41-AAF8D7BFA189",
+									"type": "Object3D",
+									"matrix": [ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 ]
+								}
+							]
+						}
+					]
+				}
+			};
+
+			// hacks
+			var out = a.toJSON();
+			out.object.uuid = "0A1E4F43-CB5B-4097-8F82-DC2969C0B8C2";
+
+			assert.deepEqual( out, gold, "JSON is as expected" );
+
+		} );
+
+		QUnit.test( "clone", ( assert ) => {
+
+			var a;
+			var b = new Object3D();
+
+			assert.strictEqual( a, undefined, "Undefined pre-clone()" );
+
+			a = b.clone();
+			assert.notStrictEqual( a, b, "Defined but seperate instances post-clone()" );
+
+			a.uuid = b.uuid;
+			assert.deepEqual( a, b, "But identical properties" );
+
+		} );
+
+		QUnit.test( "copy", ( assert ) => {
+
+			var a = new Object3D();
+			var b = new Object3D();
+			var child = new Object3D();
+			var childChild = new Object3D();
+
+			a.name = "original";
+			b.name = "to-be-copied";
+
+			b.position.set( x, y, z );
+			b.quaternion.set( x, y, z, w );
+			b.scale.set( 2, 3, 4 );
+
+			// bogus QUnit.test values
+			b.matrix.set( 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 );
+			b.matrixWorld.set( 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2 );
+
+			b.matrixAutoUpdate = false;
+			b.matrixWorldNeedsUpdate = true;
+
+			b.layers.mask = 2;
+			b.visible = false;
+
+			b.castShadow = true;
+			b.receiveShadow = true;
+
+			b.frustumCulled = false;
+			b.renderOrder = 1;
+
+			b.userData[ "foo" ] = "bar";
+
+			child.add( childChild );
+			b.add( child );
+
+			assert.notDeepEqual( a, b, "Objects are not equal pre-copy()" );
+			a.copy( b, true );
+
+			// check they're all unique instances
+			assert.ok(
+				a.uuid !== b.uuid &&
+				a.children[ 0 ].uuid !== b.children[ 0 ].uuid &&
+				a.children[ 0 ].children[ 0 ].uuid !== b.children[ 0 ].children[ 0 ].uuid,
+				"UUIDs are all different"
+			);
+
+			// and now fix that
+			a.uuid = b.uuid;
+			a.children[ 0 ].uuid = b.children[ 0 ].uuid;
+			a.children[ 0 ].children[ 0 ].uuid = b.children[ 0 ].children[ 0 ].uuid;
+
+			assert.deepEqual( a, b, "Objects are equal post-copy()" );
+
+		} );
+
+	} );
+
+} );

+ 201 - 0
test/unit/src/core/Raycaster.tests.js

@@ -0,0 +1,201 @@
+/**
+ * @author simonThiele / https://github.com/simonThiele
+ */
+/* global QUnit */
+
+import { Raycaster } from '../../../../src/core/Raycaster';
+import { Vector3 } from '../../../../src/math/Vector3';
+import { Mesh } from '../../../../src/objects/Mesh';
+import { SphereGeometry } from '../../../../src/geometries/SphereGeometry';
+import { PerspectiveCamera } from '../../../../src/cameras/PerspectiveCamera';
+import { OrthographicCamera } from '../../../../src/cameras/OrthographicCamera';
+
+function checkRayDirectionAgainstReferenceVector( rayDirection, refVector, assert ) {
+
+	assert.ok( refVector.x - rayDirection.x <= Number.EPSILON && refVector.y - rayDirection.y <= Number.EPSILON && refVector.z - rayDirection.z <= Number.EPSILON, "camera is pointing to" +
+		" the same direction as expected" );
+
+}
+
+function getRaycaster() {
+
+	return new Raycaster(
+		new Vector3( 0, 0, 0 ),
+		new Vector3( 0, 0, - 1 ),
+		1,
+		100
+	);
+
+}
+
+function getObjectsToCheck() {
+
+	var objects = [];
+
+	var sphere1 = getSphere();
+	sphere1.position.set( 0, 0, - 10 );
+	sphere1.name = 1;
+	objects.push( sphere1 );
+
+	var sphere11 = getSphere();
+	sphere11.position.set( 0, 0, 1 );
+	sphere11.name = 11;
+	sphere1.add( sphere11 );
+
+	var sphere12 = getSphere();
+	sphere12.position.set( 0, 0, - 1 );
+	sphere12.name = 12;
+	sphere1.add( sphere12 );
+
+	var sphere2 = getSphere();
+	sphere2.position.set( - 5, 0, - 5 );
+	sphere2.name = 2;
+	objects.push( sphere2 );
+
+	for ( var i = 0; i < objects.length; i ++ ) {
+
+		objects[ i ].updateMatrixWorld();
+
+	}
+
+	return objects;
+
+}
+
+function getSphere() {
+
+	return new Mesh( new SphereGeometry( 1, 100, 100 ) );
+
+}
+
+export default QUnit.module( 'Core', () => {
+
+	QUnit.module.todo( 'Raycaster', () => {
+
+		// INSTANCING
+		QUnit.test( "Instancing", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		// PUBLIC STUFF
+		QUnit.test( "linePrecision", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "set", ( assert ) => {
+
+			var origin = new Vector3( 0, 0, 0 );
+			var direction = new Vector3( 0, 0, - 1 );
+			var a = new Raycaster( origin.clone(), direction.clone() );
+
+			assert.deepEqual( a.ray.origin, origin, "Origin is correct" );
+			assert.deepEqual( a.ray.direction, direction, "Direction is correct" );
+
+			origin.set( 1, 1, 1 );
+			direction.set( - 1, 0, 0 );
+			a.set( origin, direction );
+
+			assert.deepEqual( a.ray.origin, origin, "Origin was set correctly" );
+			assert.deepEqual( a.ray.direction, direction, "Direction was set correctly" );
+
+		} );
+
+		QUnit.test( "setFromCamera (Perspective)", ( assert ) => {
+
+			var raycaster = new Raycaster();
+			var rayDirection = raycaster.ray.direction;
+			var camera = new PerspectiveCamera( 90, 1, 1, 1000 );
+
+			raycaster.setFromCamera( {
+				x: 0,
+				y: 0
+			}, camera );
+			assert.ok( rayDirection.x === 0, rayDirection.y === 0, rayDirection.z === - 1,
+				"camera is looking straight to -z and so does the ray in the middle of the screen" );
+
+			var step = 0.1;
+
+			for ( var x = - 1; x <= 1; x += step ) {
+
+				for ( var y = - 1; y <= 1; y += step ) {
+
+					raycaster.setFromCamera( {
+						x,
+						y
+					}, camera );
+
+					var refVector = new Vector3( x, y, - 1 ).normalize();
+
+					checkRayDirectionAgainstReferenceVector( rayDirection, refVector, assert );
+
+				}
+
+			}
+
+		} );
+
+		QUnit.test( "setFromCamera (Orthographic)", ( assert ) => {
+
+			var raycaster = new Raycaster();
+			var rayOrigin = raycaster.ray.origin;
+			var rayDirection = raycaster.ray.direction;
+			var camera = new OrthographicCamera( - 1, 1, 1, - 1, 0, 1000 );
+			var expectedOrigin = new Vector3( 0, 0, 0 );
+			var expectedDirection = new Vector3( 0, 0, - 1 );
+
+			raycaster.setFromCamera( {
+				x: 0,
+				y: 0
+			}, camera );
+			assert.deepEqual( rayOrigin, expectedOrigin, "Ray origin has the right coordinates" );
+			assert.deepEqual( rayDirection, expectedDirection, "Camera and Ray are pointing towards -z" );
+
+		} );
+
+		QUnit.test( "intersectObject", ( assert ) => {
+
+			var raycaster = getRaycaster();
+			var objectsToCheck = getObjectsToCheck();
+
+			assert.ok( raycaster.intersectObject( objectsToCheck[ 0 ] ).length === 1,
+				"no recursive search should lead to one hit" );
+
+			assert.ok( raycaster.intersectObject( objectsToCheck[ 0 ], true ).length === 3,
+				"recursive search should lead to three hits" );
+
+			var intersections = raycaster.intersectObject( objectsToCheck[ 0 ], true );
+			for ( var i = 0; i < intersections.length - 1; i ++ ) {
+
+				assert.ok( intersections[ i ].distance <= intersections[ i + 1 ].distance, "intersections are sorted" );
+
+			}
+
+		} );
+
+		QUnit.test( "intersectObjects", ( assert ) => {
+
+			var raycaster = getRaycaster();
+			var objectsToCheck = getObjectsToCheck();
+
+			assert.ok( raycaster.intersectObjects( objectsToCheck ).length === 1,
+				"no recursive search should lead to one hit" );
+
+			assert.ok( raycaster.intersectObjects( objectsToCheck, true ).length === 3,
+				"recursive search should lead to three hits" );
+
+			var intersections = raycaster.intersectObjects( objectsToCheck, true );
+			for ( var i = 0; i < intersections.length - 1; i ++ ) {
+
+				assert.ok( intersections[ i ].distance <= intersections[ i + 1 ].distance, "intersections are sorted" );
+
+			}
+
+		} );
+
+	} );
+
+} );

+ 49 - 0
test/unit/src/core/Uniform.tests.js

@@ -0,0 +1,49 @@
+/**
+ * @author moraxy / https://github.com/moraxy
+ */
+/* global QUnit */
+
+import { Uniform } from '../../../../src/core/Uniform';
+import { Vector3 } from '../../../../src/math/Vector3';
+import {
+	x,
+	y,
+	z
+} from '../math/Constants.tests';
+
+export default QUnit.module( 'Core', () => {
+
+	QUnit.module.todo( 'Uniform', () => {
+
+		// INSTANCING
+		QUnit.test( "Instancing", ( assert ) => {
+
+			var a;
+			var b = new Vector3( x, y, z );
+
+			a = new Uniform( 5 );
+			assert.strictEqual( a.value, 5, "New constructor works with simple values" );
+
+			a = new Uniform( b );
+			assert.ok( a.value.equals( b ), "New constructor works with complex values" );
+
+		} );
+
+		// PUBLIC STUFF
+		QUnit.test( "clone", ( assert ) => {
+
+			var a = new Uniform( 23 );
+			var b = a.clone();
+
+			assert.strictEqual( b.value, a.value, "clone() with simple values works" );
+
+			var a = new Uniform( new Vector3( 1, 2, 3 ) );
+			var b = a.clone();
+
+			assert.ok( b.value.equals( a.value ), "clone() with complex values works" );
+
+		} );
+
+	} );
+
+} );