<commit_before>/**
 * Via handle, toggle a panel. Used together with e.g. dropdown + overlay. Tested
 * on IE10+.
 *
 * @author Andreas Nymark <andreas@nymark.me>
 * @license MIT License
 * @version 2
**/
var merl = merl || {};

merl.toggle = ( function( window, document ) {
	"use strict";

	var defs = {
			all: [],
			event: 'click',
			selectElem: '.Toggle',
			selectHandle: '.Toggle-handle',
			selectPanel: '.Toggle-panel',
			expanded: 'is-expanded',
			dataAttr: 'data-toggle',
			selectFocus: 'input, a',
			autoClose: true,
			keepOpen: true
		},
		instances = [],
		eventOpen = document.createEvent( 'Event' ),
		eventClose = document.createEvent( 'Event' );

	eventOpen.initEvent( 'merl.toggle.open', true, true);
	eventClose.initEvent( 'merl.toggle.close', true, true);

	/**
	 * Initiate plugin
	 *
	 * @method init
	 * @param {Object} options - override default settings
	**/
	var init = function( options ) {
		if( options ) {
			for( var o in options ) {
				defs[ o ] = options[ o ];
			}
		}
		defs.all = document.querySelectorAll( defs.selectElem );
		window.addEventListener( 'keydown', escapeToggle );
		setup();
	};

	/**
	 * Setup. Parse options, construct Toggles and keep a reference in
	 *
	 * @method setup
	**/
	var setup = function() {
		for ( var i = 0; i < defs.all.length; i++ ) {
			var each = defs.all[ i ],
				handle = each.querySelector( defs.selectHandle ),
				event = defs.event,
				alternate = null,
				limelight = defs.selectFocus,
				data = each.getAttribute( defs.dataAttr );

			if ( data ) {
				var d = JSON.parse( data );
				if( d.event ) event = d.event;
				if( d.handle ) handle = d.handle;
				if( d.alternate ) alternate = d.alternate;
				if( d.focus ) limelight = d.focus;
			}

			instances.push( new Toggle( each, event, handle, alternate, limelight ));
		}
		document.addEventListener( defs.event, handleKill );
	};

	/**
	 * Contructor
	 *
	 * @constructor Toggle
	 * @param {HTMLElement} parent -
	 * @param {MouseEvent} event - Event on handle
	 * @param {HTMLElement} handle - Handler of toggle
	 * @param {String} alternate - Alternative text when toggle is expanded
	 * @param {String} limelight - Element to focus on when panel is visible
	**/
	var Toggle = function ( parent, event, handle, alternate, limelight ) {
		var t = this;
		t.parent = parent;
		t.event = event;
		t.handle = handle;
		t.panel = parent.querySelector( defs.selectPanel );
		t.textDefault = handle.innerHTML;
		t.textAlternate = alternate;
		t.limelight = parent.querySelector( limelight );
		t.handle.addEventListener( t.event, t.handleTrigger.bind( t ) );
		t.handleLive();
	};

	Toggle.prototype = {

		/**
		 * Reset handle by removing expanded class and reset handle text to default.
		 *
		 * @method handleReset
		**/
		handleReset: function () {
			if ( this.textAlternate ) this.handle.innerHTML = this.textDefault;
			if ( this.parent.classList.contains( defs.expanded ) ) {
				this.parent.classList.remove( defs.expanded );
				document.dispatchEvent( eventClose );
				this.handle.focus();
			}
			this.handle.setAttribute( 'aria-expanded', 'false' );
			this.panel.setAttribute( 'aria-hidden', 'true' );
		},

		/**
		 * Changes ARIAs for each state and dispatch event.
		 *
		 * @method handleState
		**/
		handleState: function () {
			if( this.parent.classList.contains( defs.expanded ) ) {
				this.handle.setAttribute( 'aria-expanded', 'true' );
				this.panel.setAttribute( 'aria-hidden', 'false' );
				document.dispatchEvent( eventOpen );
			} else {
				this.handle.setAttribute( 'aria-expanded', 'false' );
				this.panel.setAttribute( 'aria-hidden', 'true' );
				document.dispatchEvent( eventClose );
			}
		},

		/**
		 * When handle is triggered, toggle expanded class and set alternate text. Also
		 * reset all other toggles.
		 *
		 * @method handleTrigger
		 * @param {MouseEvent} evt - Mouse event
		**/
		handleTrigger: function ( evt ) {
			if( this.event === defs.event ) {
				this.parent.classList.toggle( defs.expanded );
				this.handleState();
			}
			if( this.limelight ) {
				this.limelight.focus();
			}
			if( this.textAlternate && this.handle.innerHTML === this.textDefault ) {
				this.handle.innerHTML = this.textAlternate;
			} else if( this.textAlternate && this.handle.innerHTML === this.textAlternate ) {
				this.handle.innerHTML = this.textDefault;
			}
			for ( var i = 0; i < instances.length; i++ ) {
				var toggle = instances[ i ];
				if( toggle !== this && !defs.keepOpen ) {
					toggle.handleReset();
				}
			}
			evt.preventDefault();
		},

		/**
		 * Add ARIA if handle changes label.
		 *
		 * @method handleLive
		**/
		handleLive: function () {
			if ( this.textAlternate ) this.handle.setAttribute( 'aria-live', 'polite' );
		},
	};

	/**
	 * Remove all open toggles
	 *
	 * @method handleKill
	 * @param {MouseEvent} evt - Mouse event
	**/
	var handleKill = function ( evt ) {
		for ( var i = 0; i < instances.length; i++ ) {
			var toggle = instances[ i ];
			if( !toggle.parent.contains( evt.target ) && defs.autoClose && !defs.keepOpen ) {
				toggle.handleReset();
			}
		}
	};

	/**
	 * Remove all open toggles
	 *
	 * @method handleKill
	 * @param {MouseEvent} evt - Mouse event
	**/
	var closeAllToggles = function ( evt ) {
		for ( var i = 0; i < instances.length; i++ ) {
			var toggle = instances[ i ];
			toggle.handleReset();
		}
	};

	/**
	 * Remove all open toggles when user press escape key.
	 *
	 * @method escapeToggle
	 * @param {KeyEvent} evt - Key event
	**/
	var escapeToggle = function ( evt ) {
		if ( evt.keyCode === 27 ) {
			closeAllToggles();
		}
	};

	return {
		init: init,
		closeAll: handleKill,
		closeAllToggles: closeAllToggles,
	};

}( window, document ));
<commit_msg>Correctly dispatch event on parent element.<commit_after>/**
 * Via handle, toggle a panel. Used together with e.g. dropdown + overlay. Tested
 * on IE10+.
 *
 * @author Andreas Nymark <andreas@nymark.me>
 * @license MIT
 * @version 3
**/
var merl = merl || {};

merl.toggle = ( function( window, document ) {
	"use strict";

	var defs = {
			all: [],
			event: 'click',
			selectElem: '.Toggle',
			selectHandle: '.Toggle-handle',
			selectPanel: '.Toggle-panel',
			expanded: 'is-expanded',
			dataAttr: 'data-toggle',
			selectFocus: 'input, a',
			autoClose: true,
			keepOpen: false
		},
		instances = [],
		eventOpen = document.createEvent( 'Event' ),
		eventClose = document.createEvent( 'Event' );

	eventOpen.initEvent( 'merl.toggle.open', true, true);
	eventClose.initEvent( 'merl.toggle.close', true, true);

	/**
	 * Initiate plugin
	 *
	 * @method init
	 * @param {Object} options - override default settings
	**/
	var init = function( options ) {
		if( options ) {
			for( var o in options ) {
				defs[ o ] = options[ o ];
			}
		}
		defs.all = document.querySelectorAll( defs.selectElem );
		window.addEventListener( 'keydown', escapeToggle );
		setup();
	};

	/**
	 * Setup. Parse options, construct Toggles and keep a reference in
	 *
	 * @method setup
	**/
	var setup = function() {
		for ( var i = 0; i < defs.all.length; i++ ) {
			var each = defs.all[ i ],
				handle = each.querySelector( defs.selectHandle ),
				event = defs.event,
				alternate = null,
				limelight = defs.selectFocus,
				data = each.getAttribute( defs.dataAttr );

			if ( data ) {
				var d = JSON.parse( data );
				if( d.event ) event = d.event;
				if( d.handle ) handle = d.handle;
				if( d.alternate ) alternate = d.alternate;
				if( d.focus ) limelight = d.focus;
			}

			instances.push( new Toggle( each, event, handle, alternate, limelight ));
		}
		document.addEventListener( defs.event, handleKill );
	};

	/**
	 * Contructor
	 *
	 * @constructor Toggle
	 * @param {HTMLElement} parent -
	 * @param {MouseEvent} event - Event on handle
	 * @param {HTMLElement} handle - Handler of toggle
	 * @param {String} alternate - Alternative text when toggle is expanded
	 * @param {String} limelight - Element to focus on when panel is visible
	**/
	var Toggle = function ( parent, event, handle, alternate, limelight ) {
		var t = this;
		t.parent = parent;
		t.event = event;
		t.handle = handle;
		t.panel = t.parent.querySelector( defs.selectPanel );
		t.textDefault = handle.innerHTML;
		t.textAlternate = alternate;
		t.limelight = t.parent.querySelector( limelight );
		t.handle.addEventListener( t.event, t.handleTrigger.bind( t ) );
		t.handleLive();
	};

	Toggle.prototype = {

		/**
		 * Reset handle by removing expanded class and reset handle text to default.
		 *
		 * @method handleReset
		**/
		handleReset: function () {
			var t = this;
			if ( t.textAlternate ) t.handle.innerHTML = t.textDefault;
			if ( t.parent.classList.contains( defs.expanded ) ) {
				t.parent.classList.remove( defs.expanded );
				t.parent.dispatchEvent( eventClose );
				t.handle.focus();
			}
			t.handle.setAttribute( 'aria-expanded', 'false' );
			t.panel.setAttribute( 'aria-hidden', 'true' );
		},

		/**
		 * Changes ARIAs for each state and dispatch event.
		 *
		 * @method handleState
		**/
		handleState: function () {
			var t = this;
			if( t.parent.classList.contains( defs.expanded ) ) {
				t.handle.setAttribute( 'aria-expanded', 'true' );
				t.panel.setAttribute( 'aria-hidden', 'false' );
				t.parent.dispatchEvent( eventOpen );
			} else {
				t.handle.setAttribute( 'aria-expanded', 'false' );
				t.panel.setAttribute( 'aria-hidden', 'true' );
				t.parent.dispatchEvent( eventClose );
			}
		},

		/**
		 * When handle is triggered, toggle expanded class and set alternate text. Also
		 * reset all other toggles.
		 *
		 * @method handleTrigger
		 * @param {MouseEvent} evt - Mouse event
		**/
		handleTrigger: function ( evt ) {
			var t = this;
			if( t.event === defs.event ) {
				t.parent.classList.toggle( defs.expanded );
				t.handleState();
			}
			if( t.limelight ) {
				t.limelight.focus();
			}
			if( t.textAlternate && t.handle.innerHTML === t.textDefault ) {
				t.handle.innerHTML = t.textAlternate;
			} else if( t.textAlternate && t.handle.innerHTML === t.textAlternate ) {
				t.handle.innerHTML = t.textDefault;
			}
			for ( var i = 0; i < instances.length; i++ ) {
				var toggle = instances[ i ];
				if( toggle !== this && !defs.keepOpen ) {
					toggle.handleReset();
				}
			}
			evt.preventDefault();
		},

		/**
		 * Add ARIA if handle changes label.
		 *
		 * @method handleLive
		**/
		handleLive: function () {
			var t = this;
			if ( t.textAlternate ) t.handle.setAttribute( 'aria-live', 'polite' );
		},
	};

	/**
	 * Remove all open toggles
	 *
	 * @method handleKill
	 * @param {MouseEvent} evt - Mouse event
	**/
	var handleKill = function ( evt ) {
		for ( var i = 0; i < instances.length; i++ ) {
			var toggle = instances[ i ];
			if( !toggle.parent.contains( evt.target ) && defs.autoClose && !defs.keepOpen ) {
				toggle.handleReset();
			}
		}
	};

	/**
	 * Remove all open toggles
	 *
	 * @method handleKill
	 * @param {MouseEvent} evt - Mouse event
	**/
	var closeAllToggles = function ( evt ) {
		for ( var i = 0; i < instances.length; i++ ) {
			var toggle = instances[ i ];
			toggle.handleReset();
		}
	};

	/**
	 * Remove all open toggles when user press escape key.
	 *
	 * @method escapeToggle
	 * @param {KeyEvent} evt - Key event
	**/
	var escapeToggle = function ( evt ) {
		if ( evt.keyCode === 27 ) {
			closeAllToggles();
		}
	};

	return {
		init: init,
		closeAll: handleKill,
		closeAllToggles: closeAllToggles,
	};

}( window, document ));
<|endoftext|>


<commit_before>const path = require('path');
const webpack = require('webpack');
const ExtractTextPlugin = require("extract-text-webpack-plugin");
const extractCSS = new ExtractTextPlugin('stylesheets/[name].css');

module.exports = (isTest, isDebug) => {
    const config = {
        entry: {
            main: './src/Main.tsx',
            server: './src/Server.tsx',
            test: './test/Main.ts',
            testConfiguration: './test/configuration/Configuration.ts',
            vendor: ['es6-promise', 'react-router', 'slug', 'ts-helpers']
        },
        output: {
            path: path.resolve(__dirname + '/dist'),
            publicPath: '/',
            filename: '[name].bundle.js'
        },
        devtool: 'source-map',
        resolve: {
            modules: [
                path.resolve(__dirname),
                path.resolve(__dirname, 'src'),
                path.resolve(__dirname, 'node_modules')
            ],
            extensions: ['.ts', '.tsx', '.js', '.css', '.less']
        },
        module: {
            rules: [
                {
                    test: /\.otf$/,
                    loader: 'url-loader?limit=100000'
                }, {
                    test: /\.css$/,
                    loader: extractCSS.extract([
                        'css-loader',
                        'postcss-loader'
                    ])
                }, {
                    test: /\.less$/,
                    loader: extractCSS.extract([
                        'css-loader',
                        'postcss-loader',
                        'less-loader'
                    ])
                }, {
                    test: /\.tsx?$/,
                    loader: 'ts-loader'
                }
            ]
        },
        plugins: [
            extractCSS
        ],
        // What information should be printed to the console
        stats: {
            colors: true,
            reasons: isDebug,
            hash: isDebug,
            version: isDebug,
            timings: true,
            chunks: isDebug,
            chunkModules: isDebug,
            cached: isDebug,
            cachedAssets: isDebug,
        },
    };

    if (isTest) {
        /**
         * Instruments TS source files for subsequent code coverage.
         * See https://github.com/deepsweet/istanbul-instrumenter-loader
         */
        config.module.loaders.push({
            enforce: 'post',
            test: /\.tsx?$/,
            loader: 'istanbul-instrumenter-loader',
            query: {
                esModules: true
            },
            include: [
                path.resolve(__dirname, 'src')
            ]
        });
    } else {
        config.plugins.push(new webpack.optimize.CommonsChunkPlugin({
            name: 'vendor',
            filename: 'vendor.bundle.js'
        }));
    }

    if (!isDebug) { // Only for production
        config.plugins.push(new webpack.optimize.UglifyJsPlugin({
            compress: {
                warnings: false
            },
            sourceMap: false,
            mangle: false
        }));
    } else { // Only for debug
        // Hot Module Replacement (HMR)
        const hotMiddlewareScript = 'webpack-hot-middleware/client';
        for (let entryName in config.entry) {
            if (entryName !== 'vendor') {
                let entryValue = config.entry[entryName];
                config.entry[entryName] = [entryValue, hotMiddlewareScript];
            }
        }
        config.plugins.push(new webpack.HotModuleReplacementPlugin());
        config.plugins.push(new webpack.NoErrorsPlugin());
    }

    return config;
};
<commit_msg>KCWA-84 Only build test sources when running unit tests<commit_after>const path = require('path');
const webpack = require('webpack');
const ExtractTextPlugin = require("extract-text-webpack-plugin");
const extractCSS = new ExtractTextPlugin('stylesheets/[name].css');

module.exports = (isTest, isDebug) => {
    const entries = {
        main: './src/Main.tsx',
        server: './src/Server.tsx',
        vendor: ['es6-promise', 'react-router', 'slug', 'ts-helpers']
    };
    const testEntries = Object.assign({
        test: './test/Main.ts',
        testConfiguration: './test/configuration/Configuration.ts',
    }, entries);

    const config = {
        entry: isTest ? testEntries : entries,
        output: {
            path: path.resolve(__dirname + '/dist'),
            publicPath: '/',
            filename: '[name].bundle.js'
        },
        devtool: 'source-map',
        resolve: {
            modules: [
                path.resolve(__dirname),
                path.resolve(__dirname, 'src'),
                path.resolve(__dirname, 'node_modules')
            ],
            extensions: ['.ts', '.tsx', '.js', '.css', '.less']
        },
        module: {
            rules: [
                {
                    test: /\.otf$/,
                    loader: 'url-loader?limit=100000'
                }, {
                    test: /\.css$/,
                    loader: extractCSS.extract([
                        'css-loader',
                        'postcss-loader'
                    ])
                }, {
                    test: /\.less$/,
                    loader: extractCSS.extract([
                        'css-loader',
                        'postcss-loader',
                        'less-loader'
                    ])
                }, {
                    test: /\.tsx?$/,
                    loader: 'ts-loader'
                }
            ]
        },
        plugins: [
            extractCSS
        ],
        // What information should be printed to the console
        stats: {
            colors: true,
            reasons: isDebug,
            hash: isDebug,
            version: isDebug,
            timings: true,
            chunks: isDebug,
            chunkModules: isDebug,
            cached: isDebug,
            cachedAssets: isDebug,
        },
    };

    if (isTest) {
        /**
         * Instruments TS source files for subsequent code coverage.
         * See https://github.com/deepsweet/istanbul-instrumenter-loader
         */
        config.module.rules.push({
            enforce: 'post',
            test: /\.tsx?$/,
            loader: 'istanbul-instrumenter-loader',
            query: {
                esModules: true
            },
            include: [
                path.resolve(__dirname, 'src')
            ]
        });
    } else {
        config.plugins.push(new webpack.optimize.CommonsChunkPlugin({
            name: 'vendor',
            filename: 'vendor.bundle.js'
        }));
    }

    if (!isDebug) { // Only for production
        config.plugins.push(new webpack.optimize.UglifyJsPlugin({
            compress: {
                warnings: false
            },
            sourceMap: false,
            mangle: false
        }));
    } else { // Only for debug
        // Hot Module Replacement (HMR)
        const hotMiddlewareScript = 'webpack-hot-middleware/client';
        for (let entryName in config.entry) {
            if (entryName !== 'vendor') {
                let entryValue = config.entry[entryName];
                config.entry[entryName] = [entryValue, hotMiddlewareScript];
            }
        }
        config.plugins.push(new webpack.HotModuleReplacementPlugin());
        config.plugins.push(new webpack.NoErrorsPlugin());
    }

    return config;
};
<|endoftext|>


<commit_before>var Observer = require('../../src/observer'),
    assert = require('assert'),
    Emitter = require('events').EventEmitter

describe('UNIT: Observer', function () {

    describe('Observing Object', function () {

        it('should attach hidden observer and values to the object', function () {
            var obj = {}, ob = new Emitter()
            ob.proxies = {}
            Observer.observe(obj, 'test', ob)
            assert.ok(obj.__observer__ instanceof Emitter)
            assert.ok(obj.__values__)
        })

        it('should emit set events with correct path', setTestFactory({
            obj: { a: 1, b: { c: 2 } },
            expects: [
                { key: 'test.a', val: 1 },
                { key: 'test.b.c', val: 3 }
            ],
            path: 'test'
        }))

        it('should emit multiple events when a nested object is set', setTestFactory({
            obj: { a: 1, b: { c: 2 } },
            expects: [
                { key: 'test.b', val: { c: 3 } },
                { key: 'test.b.c', val: 3, skip: true }
            ],
            path: 'test'
        }))

        it('should emit get events on tip values', getTestFactory({
            obj: { a: 1, b: { c: 2 } },
            expects: [
                'test.a',
                'test.b.c'
            ],
            path: 'test'
        }))

        it('should emit set when first observing', function () {
            var obj = { a: 1, b: { c: 2} },
                ob = new Emitter(), i = 0
            ob.proxies = {}
            var expects = [
                { key: 'test.a', val: obj.a },
                { key: 'test.b', val: obj.b },
                { key: 'test.b.c', val: obj.b.c }
            ]
            ob.on('set', function (key, val) {
                var exp = expects[i]
                assert.strictEqual(key, exp.key)
                assert.strictEqual(val, exp.val)
                i++
            })
            Observer.observe(obj, 'test', ob)
            assert.strictEqual(i, expects.length)
        })

        it('should emit set when watching an already observed object', function () {
            var obj = { a: 1, b: { c: 2} },
                ob1 = new Emitter(),
                ob2 = new Emitter(),
                i = 0
            ob1.proxies = {}
            ob2.proxies = {}
            Observer.observe(obj, 'test', ob1) // watch first time

            var expects = [
                { key: 'test.a', val: obj.a },
                { key: 'test.b', val: obj.b },
                { key: 'test.b.c', val: obj.b.c }
            ]
            ob2.on('set', function (key, val) {
                var exp = expects[i]
                assert.strictEqual(key, exp.key)
                assert.strictEqual(val, exp.val)
                i++
            })
            Observer.observe(obj, 'test', ob2) // watch again
            assert.strictEqual(i, expects.length)
        })

    })

    describe('Observing Array', function () {
        // body...
    })

})

function setTestFactory (opts) {
    return function () {
        var ob = new Emitter(),
            i  = 0,
            obj = opts.obj,
            expects = opts.expects
        ob.proxies = {}
        Observer.observe(obj, opts.path, ob)
        ob.on('set', function (key, val) {
            var expect = expects[i]
            assert.strictEqual(key, expect.key)
            assert.strictEqual(val, expect.val)
            i++
        })
        expects.forEach(function (expect) {
            if (expect.skip) return
            var path = expect.key.split('.'),
                j = 1,
                scope = obj
            while (j < path.length - 1) {
                scope = scope[path[j]]
                j++
            }
            scope[path[j]] = expect.val
        })
        assert.strictEqual(i, expects.length)
    }
}

function getTestFactory (opts) {
    return function () {
        var ob = new Emitter(),
            i  = 0,
            obj = opts.obj,
            expects = opts.expects
        ob.proxies = {}
        Observer.observe(obj, opts.path, ob)
        ob.on('get', function (key) {
            var expected = expects[i]
            assert.strictEqual(key, expected)
            i++
        })
        expects.forEach(function (key) {
            var path = key.split('.'),
                j = 1,
                scope = obj
            while (j < path.length) {
                scope = scope[path[j]]
                j++
            }
        })
        assert.strictEqual(i, expects.length)
    }
}<commit_msg>unit test - observer - array - scaffold<commit_after>var Observer = require('../../src/observer'),
    assert = require('assert'),
    Emitter = require('events').EventEmitter

describe('UNIT: Observer', function () {

    describe('Observing Object', function () {

        it('should attach hidden observer and values to the object', function () {
            var obj = {}, ob = new Emitter()
            ob.proxies = {}
            Observer.observe(obj, 'test', ob)
            assert.ok(obj.__observer__ instanceof Emitter)
            assert.ok(obj.__values__)
        })

        it('should emit set events with correct path', setTestFactory({
            obj: { a: 1, b: { c: 2 } },
            expects: [
                { key: 'test.a', val: 1 },
                { key: 'test.b.c', val: 3 }
            ],
            path: 'test'
        }))

        it('should emit multiple events when a nested object is set', setTestFactory({
            obj: { a: 1, b: { c: 2 } },
            expects: [
                { key: 'test.b', val: { c: 3 } },
                { key: 'test.b.c', val: 3, skip: true }
            ],
            path: 'test'
        }))

        it('should emit get events on tip values', getTestFactory({
            obj: { a: 1, b: { c: 2 } },
            expects: [
                'test.a',
                'test.b.c'
            ],
            path: 'test'
        }))

        it('should emit set when first observing', function () {
            var obj = { a: 1, b: { c: 2} },
                ob = new Emitter(), i = 0
            ob.proxies = {}
            var expects = [
                { key: 'test.a', val: obj.a },
                { key: 'test.b', val: obj.b },
                { key: 'test.b.c', val: obj.b.c }
            ]
            ob.on('set', function (key, val) {
                var exp = expects[i]
                assert.strictEqual(key, exp.key)
                assert.strictEqual(val, exp.val)
                i++
            })
            Observer.observe(obj, 'test', ob)
            assert.strictEqual(i, expects.length)
        })

        it('should emit set when watching an already observed object', function () {
            var obj = { a: 1, b: { c: 2} },
                ob1 = new Emitter(),
                ob2 = new Emitter(),
                i = 0
            ob1.proxies = {}
            ob2.proxies = {}
            Observer.observe(obj, 'test', ob1) // watch first time

            var expects = [
                { key: 'test.a', val: obj.a },
                { key: 'test.b', val: obj.b },
                { key: 'test.b.c', val: obj.b.c }
            ]
            ob2.on('set', function (key, val) {
                var exp = expects[i]
                assert.strictEqual(key, exp.key)
                assert.strictEqual(val, exp.val)
                i++
            })
            Observer.observe(obj, 'test', ob2) // watch again
            assert.strictEqual(i, expects.length)
        })

    })

    describe('Observing Array', function () {

        var arr = [],
            ob = new Emitter()
        ob.proxies = {}
        Observer.observe(arr, 'test', ob)

        it('should attach the hidden observer', function () {
            assert.ok(arr.__observer__ instanceof Emitter)
        })

        it('should overwrite the native array mutator methods', function () {
            ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'].forEach(function (method) {
                assert.notStrictEqual(arr[method], Array.prototype[method])
            })
        })

        it('should emit set for .length when it mutates', function () {
            var emitted = false
            ob.once('set', function (key, val) {
                assert.strictEqual(key, 'test.length')
                assert.strictEqual(val, 1)
                emitted = true
            })
            arr.push(1)
            assert.ok(emitted)
        })

        describe('Mutator Methods', function () {

            it('push', function () {
                // body...
            })

            it('pop', function () {
                // body...
            })

            it('shift', function () {
                // body...
            })

            it('unshift', function () {
                // body...
            })

            it('splice', function () {
                // body...
            })

            it('sort', function () {
                // body...
            })

            it('reverse', function () {
                // body...
            })

        })

        describe('Augmentations', function () {

            it('remove', function () {
                // body...
            })

            it('replace', function () {
                // body...
            })

            it('mutateFilter', function () {
                // body...
            })

        })

    })

})

function setTestFactory (opts) {
    return function () {
        var ob = new Emitter(),
            i  = 0,
            obj = opts.obj,
            expects = opts.expects
        ob.proxies = {}
        Observer.observe(obj, opts.path, ob)
        ob.on('set', function (key, val) {
            var expect = expects[i]
            assert.strictEqual(key, expect.key)
            assert.strictEqual(val, expect.val)
            i++
        })
        expects.forEach(function (expect) {
            if (expect.skip) return
            var path = expect.key.split('.'),
                j = 1,
                scope = obj
            while (j < path.length - 1) {
                scope = scope[path[j]]
                j++
            }
            scope[path[j]] = expect.val
        })
        assert.strictEqual(i, expects.length)
    }
}

function getTestFactory (opts) {
    return function () {
        var ob = new Emitter(),
            i  = 0,
            obj = opts.obj,
            expects = opts.expects
        ob.proxies = {}
        Observer.observe(obj, opts.path, ob)
        ob.on('get', function (key) {
            var expected = expects[i]
            assert.strictEqual(key, expected)
            i++
        })
        expects.forEach(function (key) {
            var path = key.split('.'),
                j = 1,
                scope = obj
            while (j < path.length) {
                scope = scope[path[j]]
                j++
            }
        })
        assert.strictEqual(i, expects.length)
    }
}<|endoftext|>


<commit_before>$(window).keyup(function (event) {
    if (event.keyCode === 27 && $('#contextmenu').length) {
        $('#blocker, #contextmenu').remove();
    }
});

function showContextMenu(buttons, event) {
    var contextmenu = $('
')
        .css({
            'top': event.pageY,
            'left': event.pageX
        });

    $.each(buttons, function(i, item) {
        $('
')
            .text(item.title)
            .data('li', item.li)
            .data('callback', item.callback)
            .click(function() {
                $(this).data('callback')($(this).data('li'));
                $('#blocker, #contextmenu').remove();
            })
            .appendTo(contextmenu);
    });

	// Move menu if it overflows
	var bottom = parseInt(contextmenu.css('top')) + (30*buttons.length);
	var diff = bottom - $(window).height();
	if (diff > 0)
		contextmenu.css('top', parseInt(contextmenu.css('top')) - diff + 'px');

    // Set up a blocker div that closes the menu when clicked
    var blocker = $('

').mousedown(function(event) {
        $('#blocker, #contextmenu').remove();
        event.stopPropagation();
    });

    blocker.appendTo('body');
    contextmenu.appendTo('body');

    // Finally, prevent contextmenu on our contextmenu :)
    $('#blocker, #contextmenu, #contextmenu li.option').bind('contextmenu', function (event) {
        event.preventDefault();
    });
}

function showPlaylistContextMenu(event) {
    event.preventDefault();

    var buttons = [
		{
            title: 'Import from Spotify',
            li: $(this),
            callback: function(li) {
				li.find('.spotify-importer').remove();
				li.removeClass('draggable');
				var div = $('#spotify-importer')
					.clone()
					.addClass('spotify-importer')
                    .bind('contextmenu', function(event) {
                        event.stopPropagation();
                    })
					.click(function(event) {
						event.stopPropagation();
					});
				li.append(div);
				div.show();
				var importer = new SpotifyImporter();

				// cancel
				li.find('.spotify-importer .cancel').one('click', function() {
					importer.cancel();
					li.find('.spotify-importer').fadeOut('normal', function() {
						li.find('.spotify-importer').remove();
					});
					li.addClass('draggable');
				});

				// start
				li.find('.spotify-importer .start').one('click', function() {
					importer.start(
						li.find('.spotify-importer textarea').val(),
						li.data('model'),
						function() {
							// callbackUpdate
							// li.click();
							li.find('.spotify-importer .added').text(importer.added);
							li.find('.spotify-importer .max').text('/'+importer.max);
						},
						function() {
							// callbackDone
							li.find('.spotify-importer').fadeOut('normal', function() {
								li.find('.spotify-importer').remove();
							});
							li.click();
							li.addClass('draggable');
						}
					);
				});
            }
        },
        {
            title: 'Rename',
            li: $(this),
            callback: function(li) {
                var input = $('<input type="text"/>')
                    .addClass('rename')
                    .val(li.text())
                    .data('original', li.text())
                    .data('li', li)
                    .blur(function(event) {
                        li.text($(this).data('original'));
                        input.remove();
                    })
                    .keyup(function(event) {
                        switch (event.keyCode) {
                            case 13: // RETURN
                            input.remove();
                            var playlist = li.data('model');
                            playlist.rename(input.val());
                            playlistManager.save();
                            constructPlaylistsMenu();
                            break;
                            case 27: // ESC
                            $(this).blur();
                            break;
                        }
                        event.stopPropagation();
                    });

                li.html('');
                li.append(input);
                input.focus().select();
            }
        },
        {
            title: 'Delete',
            li: $(this),
            callback: function(li) {
                var index = li.index();
                    playlist = playlistManager.getPlaylist(index);

                if (confirm("Are you sure you want to delete this playlist (" + playlist.title + ")?")) {
                    playlistManager.deletePlaylist(index);
                    playlistManager.save();
                    constructPlaylistsMenu();
                }
            }
        }
    ];

    if (logged_in) {
        buttons.push({
            title: 'Sync',
            li: $(this),
            callback: function(li) {
                li.data('model').sync(function() {
                    playlistManager.save();
                    li.addClass('remote');
                });
            }
        });
    }

    if (logged_in && $(this).data('model').remoteId) {
        buttons.push({
            title: 'Unsync',
            li: $(this),
            callback: function(li) {
                li.data('model').unsync();
                playlistManager.save();
                li.removeClass('remote');
            }
        });
    }

    showContextMenu(buttons, event);
}

function showResultsItemContextMenu(event) {
    event.preventDefault();

    var buttons = [
        {
            title: 'Watch on YouTube',
            li: $(this),
            callback: function(li) {
                window.open('http://www.youtube.com/watch?v=' + li.data('videoId'));
            }
        },
		{
            title: 'Queue',
            li: $(this),
            callback: function(li) {
                Player.addToPlayOrder(li);
            }
        },
		{
			title: 'Show related',
			li: $(this),
			callback: function(li) {
				var videoTag = li.data('videoId');
				$('#results').html('');
				var url = "http://gdata.youtube.com/feeds/api/videos/" + videoTag + "/related?callback=?";
				var params = {
					'alt': 'json-in-script',
					'max-results': 50,
					'prettyprint': true,
					'v': 2
				};
				$.getJSON(url, params, function(data) {
					$.each(data.feed.entry, function(i, item) {
						var url = item['id']['$t'];
						var videoId = url.match('video:(.*)$')[1];
						var title = item['title']['$t'];
						if (item['gd$rating'])
							var rating = item['gd$rating']['average'];
						var resultItem = createResultsItem(title, videoId, rating);
						resultItem.appendTo($('#results'));
					});
				});
				Search.selectSearchResults();
			}
		}
    ];

    buttons = $.merge(buttons, $(this).data('additionalMenuButtons'));

    showContextMenu(buttons, event);
}
<commit_msg>Don't show sync option when already synced<commit_after>$(window).keyup(function (event) {
    if (event.keyCode === 27 && $('#contextmenu').length) {
        $('#blocker, #contextmenu').remove();
    }
});

function showContextMenu(buttons, event) {
    var contextmenu = $('
')
        .css({
            'top': event.pageY,
            'left': event.pageX
        });

    $.each(buttons, function(i, item) {
        $('
')
            .text(item.title)
            .data('li', item.li)
            .data('callback', item.callback)
            .click(function() {
                $(this).data('callback')($(this).data('li'));
                $('#blocker, #contextmenu').remove();
            })
            .appendTo(contextmenu);
    });

	// Move menu if it overflows
	var bottom = parseInt(contextmenu.css('top')) + (30*buttons.length);
	var diff = bottom - $(window).height();
	if (diff > 0)
		contextmenu.css('top', parseInt(contextmenu.css('top')) - diff + 'px');

    // Set up a blocker div that closes the menu when clicked
    var blocker = $('

').mousedown(function(event) {
        $('#blocker, #contextmenu').remove();
        event.stopPropagation();
    });

    blocker.appendTo('body');
    contextmenu.appendTo('body');

    // Finally, prevent contextmenu on our contextmenu :)
    $('#blocker, #contextmenu, #contextmenu li.option').bind('contextmenu', function (event) {
        event.preventDefault();
    });
}

function showPlaylistContextMenu(event) {
    event.preventDefault();

    var buttons = [
		{
            title: 'Import from Spotify',
            li: $(this),
            callback: function(li) {
				li.find('.spotify-importer').remove();
				li.removeClass('draggable');
				var div = $('#spotify-importer')
					.clone()
					.addClass('spotify-importer')
                    .bind('contextmenu', function(event) {
                        event.stopPropagation();
                    })
					.click(function(event) {
						event.stopPropagation();
					});
				li.append(div);
				div.show();
				var importer = new SpotifyImporter();

				// cancel
				li.find('.spotify-importer .cancel').one('click', function() {
					importer.cancel();
					li.find('.spotify-importer').fadeOut('normal', function() {
						li.find('.spotify-importer').remove();
					});
					li.addClass('draggable');
				});

				// start
				li.find('.spotify-importer .start').one('click', function() {
					importer.start(
						li.find('.spotify-importer textarea').val(),
						li.data('model'),
						function() {
							// callbackUpdate
							// li.click();
							li.find('.spotify-importer .added').text(importer.added);
							li.find('.spotify-importer .max').text('/'+importer.max);
						},
						function() {
							// callbackDone
							li.find('.spotify-importer').fadeOut('normal', function() {
								li.find('.spotify-importer').remove();
							});
							li.click();
							li.addClass('draggable');
						}
					);
				});
            }
        },
        {
            title: 'Rename',
            li: $(this),
            callback: function(li) {
                var input = $('<input type="text"/>')
                    .addClass('rename')
                    .val(li.text())
                    .data('original', li.text())
                    .data('li', li)
                    .blur(function(event) {
                        li.text($(this).data('original'));
                        input.remove();
                    })
                    .keyup(function(event) {
                        switch (event.keyCode) {
                            case 13: // RETURN
                            input.remove();
                            var playlist = li.data('model');
                            playlist.rename(input.val());
                            playlistManager.save();
                            constructPlaylistsMenu();
                            break;
                            case 27: // ESC
                            $(this).blur();
                            break;
                        }
                        event.stopPropagation();
                    });

                li.html('');
                li.append(input);
                input.focus().select();
            }
        },
        {
            title: 'Delete',
            li: $(this),
            callback: function(li) {
                var index = li.index();
                    playlist = playlistManager.getPlaylist(index);

                if (confirm("Are you sure you want to delete this playlist (" + playlist.title + ")?")) {
                    playlistManager.deletePlaylist(index);
                    playlistManager.save();
                    constructPlaylistsMenu();
                }
            }
        }
    ];

    if (logged_in && !$(this).data('model').remoteId) {
        buttons.push({
            title: 'Sync',
            li: $(this),
            callback: function(li) {
                li.data('model').sync(function() {
                    playlistManager.save();
                    li.addClass('remote');
                });
            }
        });
    }

    if (logged_in && $(this).data('model').remoteId) {
        buttons.push({
            title: 'Unsync',
            li: $(this),
            callback: function(li) {
                li.data('model').unsync();
                playlistManager.save();
                li.removeClass('remote');
            }
        });
    }

    showContextMenu(buttons, event);
}

function showResultsItemContextMenu(event) {
    event.preventDefault();

    var buttons = [
        {
            title: 'Watch on YouTube',
            li: $(this),
            callback: function(li) {
                window.open('http://www.youtube.com/watch?v=' + li.data('videoId'));
            }
        },
		{
            title: 'Queue',
            li: $(this),
            callback: function(li) {
                Player.addToPlayOrder(li);
            }
        },
		{
			title: 'Show related',
			li: $(this),
			callback: function(li) {
				var videoTag = li.data('videoId');
				$('#results').html('');
				var url = "http://gdata.youtube.com/feeds/api/videos/" + videoTag + "/related?callback=?";
				var params = {
					'alt': 'json-in-script',
					'max-results': 50,
					'prettyprint': true,
					'v': 2
				};
				$.getJSON(url, params, function(data) {
					$.each(data.feed.entry, function(i, item) {
						var url = item['id']['$t'];
						var videoId = url.match('video:(.*)$')[1];
						var title = item['title']['$t'];
						if (item['gd$rating'])
							var rating = item['gd$rating']['average'];
						var resultItem = createResultsItem(title, videoId, rating);
						resultItem.appendTo($('#results'));
					});
				});
				Search.selectSearchResults();
			}
		}
    ];

    buttons = $.merge(buttons, $(this).data('additionalMenuButtons'));

    showContextMenu(buttons, event);
}
<|endoftext|>


<commit_before>var utils = require('../util/test.js');
var Ammo = require('../lib/ammo.js');
var THREE = require('../lib/three.js');
var factory = require('../factory.js');
var _ = require('../lib/underscore.js');
var $ = require('../lib/jquery.js');
var Editor = require('../util/json-ui.js');

var test = {
};

function clearObjects() {
  factory.destroyAll();
  $('#triggers').empty();
}

function makeTest(bodyA, bodyB, connectorA, connectorB, type, constraint) {
  return function () {
    factory.setScope(type);
    var pack = {};
    pack.scene = {s1: {}};
    pack.body = {};
    pack.body[bodyA.id] = bodyA;
    pack.body[bodyB.id] = bodyB;
    pack.connector = {};
    pack.connector[connectorA.id] = connectorA;
    pack.connector[connectorB.id] = connectorB;
    pack.constraint = {};
    constraint = utils.deepCopy(constraint);
    constraint.type = constraint.id = type;
    pack.constraint[constraint.id] = constraint;
    pack.monitor = {m1: {camera: 'satellite', lookAt: bodyA.id, distance: 15}};
    pack.light = {
      l1: {position: {x: 5, z: -5}},
      l2: {position: {x: -7, y: 6, z: 5}, color: 0x8899bb},
      l3: {position: {y: -5, z: 1}, color: 0x445566}
    };
    factory.loadScene(pack, {
      axisHelper: 0,
      wireframe: false,
      webWorker: false,
      autoStart: true,
      connectorHelper: 0.7,
      canvasContainer: '#container'
    });
    if (inputs[type]) {
      inputs[type](type);
    }
  };
}

var inputs = {
  hinge: function (type) {
    var template = {
      servo: {
        'angle(°)': {type: 'range', min: -180, max: 180, step: 5}
      },
      motor: {
        velocity: {type: 'range', min: -5, max: 5, step: 1},
        binary: {type: 'range', min: 0, max: 50, step: 1}
      }
    };
    var ui = {
      servo: {
        'angle(°)': 0,
        set: function () {
          var c = factory.getObject('constraint', type);
          var angle = this.getValues().servo['angle(°)'];
          angle = angle * Math.PI / 180;
          factory.method.constraint.setAngle.call(c, angle);
        },
        relax: function () {
          var c = factory.getObject('constraint', type);
          factory.method.constraint.relax.call(c);
        }
      },
      motor: {
        velocity: 1,
        binary: 1,
        enable: function () {
          var c = factory.getObject('constraint', type);
          var values = this.getValues().motor;
          factory.method.constraint.enableMotor.call(c, values.velocity, values.binary);
        },
        disable: function () {
          var c = factory.getObject('constraint', type);
          factory.method.constraint.disableMotor.call(c);
        }
      }
    };
    var editor = new Editor();
    editor.useTemplate(template);
    editor.setValues(ui);
    editor.showEditor('#triggers');
  },
  gear: function (actuator) {
    actuator = 'motor';
    var template = {
      servo: {
        'angle(°)': {type: 'range', min: -180, max: 180, step: 5}
      },
      motor: {
        velocity: {type: 'range', min: -5, max: 5, step: 1},
        binary: {type: 'range', min: 0, max: 50, step: 1}
      }
    };
    var ui = {
      servo: {
        'angle(°)': 0,
        set: function () {
          var c = factory.getObject('constraint', actuator);
          console.log(c);
          var angle = this.getValues().servo['angle(°)'];
          angle = angle * Math.PI / 180;
          factory.method.constraint.setAngle.call(c, angle);
        },
        relax: function () {
          var c = factory.getObject('constraint', actuator);
          factory.method.constraint.relax.call(c);
        }
      },
      motor: {
        velocity: 1,
        binary: 1,
        enable: function () {
          var c = factory.getObject('constraint', actuator);
          var values = this.getValues().motor;
          factory.method.constraint.enableMotor.call(c, values.velocity, values.binary);
        },
        disable: function () {
          var c = factory.getObject('constraint', actuator);
          factory.method.constraint.disableMotor.call(c);
        }
      }
    };
    var editor = new Editor();
    editor.useTemplate(template);
    editor.setValues(ui);
    editor.showEditor('#triggers');
  }
};

function addAllTests() {
  var bodyA = {
    id: 'a',
    group: 'body',
    type: 'basic',
    shape: { type: 'box', dx: 2, dz: 2, dy: 2, segments: 2 },
    position: {  x: 0.5, y: 0.5, z: 0.5 },
    rotation: { x: 0.5, y: 0.3, z: 0 },
    material: {type: 'phong', color: 0x338855, opacity: 0.5, transparent: true},
    mass: 0
  };
  var bodyB = {
    id: 'b',
    group: 'body',
    type: 'basic',
    shape: { type: 'box', dx: 2, dy: 2, dz: 2, segments: 2 },
    position: { x: -2, y: -2, z: -2},
    rotation: { x: 0, y: 0, z: 0 },
    material: {type: 'phong', color: 0x991122, opacity: 0.9, transparent: true},
    mass: 1
  };
  var connectorA = {
    id: 'cA',
    group: 'connector',
    type: 'relative',
    body: bodyA.id,
    base: {y: -1.01},
    up: {y: 1},
    front: {x: 1}
  };
  var connectorB = {
    id: 'cB',
    group: 'connector',
    type: 'relative',
    body: bodyB.id,
    base: {x: 1, y: 1, z: 1.01},
    up: {y: 1},
    front: {x: 1}
  };
  var constraintOptions = {
    a: connectorA.id,
    b: connectorB.id,
    bodyA: bodyA.id,
    bodyB: bodyB.id,
    ratio: 1,
    approach: true
  };
  var type;
  var ca, cb;
  //point
  type = 'point';
  test[type] = makeTest(bodyA, bodyB, connectorA, connectorB, type, constraintOptions);

  type = 'hinge';
  ca = utils.deepCopy(connectorA);
  cb = utils.deepCopy(connectorB);
  ca.base = {x: -1, y: -1, z: -1};
  ca.up = {z: 1};
  cb.base = {x: 1, y: 1, z: 1};
  cb.up = {z: 1};
  test[type] = makeTest(bodyA, bodyB, ca, cb, type, constraintOptions);

  type = 'slider';
  ca = utils.deepCopy(connectorA);
  cb = utils.deepCopy(connectorB);
  ca.base = {x: 0, y: 0, z: 0};
  ca.up = {z: 1};
  ca.front = {y: 1};
  cb.base = {x: 0, y: 0, z: 5};
  cb.up = {z: 1};
  cb.front = {y: 1};
  var bodyBCopy = utils.deepCopy(bodyB);
  bodyBCopy.position = {z: -5, x: 4, y: 5};
  test[type] = makeTest(bodyA, bodyBCopy, ca, cb, type, constraintOptions);

}

function gearTest() {
  var type = 'gear';
  factory.setScope(type);
  var pack = {
    body: {
      wall: {
        type: 'basic',
        shape: { type: 'box', dx: 2, dz: 6, dy: 6, segments: 4 },
        position: { x: -1 },
        material: {type: 'phong', color: 0x335588, opacity: 0.5, transparent: true, wireframe: true},
        mass: 0,
        connector: {
          wA: {
            base: {x: 1, y: 2, z: 2},
            up: {x: 1},
            front: {z: 1}
          },
          wB: {
            base: {x: 1, y: -2, z: -2},
            up: {x: 1},
            front: {z: 1}
          }
        }
      },
      a: {
        type: 'basic',
        shape: { type: 'cylinder', r: 4, dy: 2, segments: 8},
        position: {x: 1, y: 3, z: 3},
        rotation: {z: -Math.PI/2},
        material: {type: 'phong', color: 0x338855, opacity: 0.5, transparent: true},
        mass: 1,
        connector: {
          'cA': {
            base: {y: -1},
            up: {y: 1},
            front: {z: 1}
          }
        }
      },
      b: {
        type: 'basic',
        shape: { type: 'cylinder', r: 1, dy: 2, segments: 8 },
        rotation: {z: Math.PI/2},
        material: {type: 'phong', color: 0x885555, opacity: 0.5, transparent: true},
        mass: 1,
        connector: {
          'cB': {
            base: {y: -1},
            up: {y: 1},
            front: {z: 1}
          }
        }
      }
    },
    constraint: {
      motor: {
        type: 'hinge',
        a: 'wA',
        b: 'cA',
        bodyA: 'wall',
        bodyB: 'a',
        approach: true
      },
      wb: {
        type: 'hinge',
        a: 'wB',
        b: 'cB',
        bodyA: 'wall',
        bodyB: 'b',
        approach: true
      },
      gear: {
        type: 'gear',
        a: 'cA',
        b: 'cB',
        bodyA: 'a',
        bodyB: 'b',
        ratio: 0.25,
        approach: false
      }
    },
    scene: {s1: {}},
    monitor: {m1: {camera: 'satellite', lookAt: 'wall', distance: 15}},
    light: {
      l1: {position: {x: 5, z: -5}},
      l2: {position: {x: -7, y: 6, z: 5}, color: 0x8899bb},
      l3: {position: {y: -5, z: 1}, color: 0x445566}
    }
  };

  factory.loadScene(pack, {
    axisHelper: 2,
    wireframe: false,
    webWorker: false,
    autoStart: true,
    connectorHelper: 0.7,
    canvasContainer: '#container'
  });

  if (inputs[type]) {
    inputs[type](type);
  }
}

addAllTests();
test['gear'] = gearTest;

//test.all = utils.all(test, 1);
module.exports.test = test;
module.exports.clearObjects = clearObjects;
<commit_msg>enhanced gear test<commit_after>var utils = require('../util/test.js');
var Ammo = require('../lib/ammo.js');
var THREE = require('../lib/three.js');
var factory = require('../factory.js');
var _ = require('../lib/underscore.js');
var $ = require('../lib/jquery.js');
var Editor = require('../util/json-ui.js');

var test = {
};

function clearObjects() {
  factory.destroyAll();
  $('#triggers').empty();
}

function makeTest(bodyA, bodyB, connectorA, connectorB, type, constraint) {
  return function () {
    factory.setScope(type);
    var pack = {};
    pack.scene = {s1: {}};
    pack.body = {};
    pack.body[bodyA.id] = bodyA;
    pack.body[bodyB.id] = bodyB;
    pack.connector = {};
    pack.connector[connectorA.id] = connectorA;
    pack.connector[connectorB.id] = connectorB;
    pack.constraint = {};
    constraint = utils.deepCopy(constraint);
    constraint.type = constraint.id = type;
    pack.constraint[constraint.id] = constraint;
    pack.monitor = {m1: {camera: 'satellite', lookAt: bodyA.id, distance: 15}};
    pack.light = {
      l1: {position: {x: 5, z: -5}},
      l2: {position: {x: -7, y: 6, z: 5}, color: 0x8899bb},
      l3: {position: {y: -5, z: 1}, color: 0x445566}
    };
    factory.loadScene(pack, {
      axisHelper: 0,
      wireframe: false,
      webWorker: false,
      autoStart: true,
      connectorHelper: 0.7,
      canvasContainer: '#container'
    });
    if (inputs[type]) {
      inputs[type](type);
    }
  };
}

var inputs = {
  hinge: function (type) {
    var template = {
      servo: {
        'angle(°)': {type: 'range', min: -180, max: 180, step: 5}
      },
      motor: {
        velocity: {type: 'range', min: -5, max: 5, step: 1},
        binary: {type: 'range', min: 0, max: 50, step: 1}
      }
    };
    var ui = {
      servo: {
        'angle(°)': 0,
        set: function () {
          var c = factory.getObject('constraint', type);
          var angle = this.getValues().servo['angle(°)'];
          angle = angle * Math.PI / 180;
          factory.method.constraint.setAngle.call(c, angle);
        },
        relax: function () {
          var c = factory.getObject('constraint', type);
          factory.method.constraint.relax.call(c);
        }
      },
      motor: {
        velocity: 1,
        binary: 1,
        enable: function () {
          var c = factory.getObject('constraint', type);
          var values = this.getValues().motor;
          factory.method.constraint.enableMotor.call(c, values.velocity, values.binary);
        },
        disable: function () {
          var c = factory.getObject('constraint', type);
          factory.method.constraint.disableMotor.call(c);
        }
      }
    };
    var editor = new Editor();
    editor.useTemplate(template);
    editor.setValues(ui);
    editor.showEditor('#triggers');
  },
  gear: function (actuator) {
    actuator = 'motor';
    var template = {
      servo: {
        'angle(°)': {type: 'range', min: -180, max: 180, step: 5}
      },
      motor: {
        velocity: {type: 'range', min: -5, max: 5, step: 1},
        binary: {type: 'range', min: 0, max: 50, step: 1}
      }
    };
    var ui = {
      servo: {
        'angle(°)': 0,
        set: function () {
          var c = factory.getObject('constraint', actuator);
          console.log(c);
          var angle = this.getValues().servo['angle(°)'];
          angle = angle * Math.PI / 180;
          factory.method.constraint.setAngle.call(c, angle);
        },
        relax: function () {
          var c = factory.getObject('constraint', actuator);
          factory.method.constraint.relax.call(c);
        }
      },
      motor: {
        velocity: 1,
        binary: 1,
        enable: function () {
          var c = factory.getObject('constraint', actuator);
          var values = this.getValues().motor;
          factory.method.constraint.enableMotor.call(c, values.velocity, values.binary);
        },
        disable: function () {
          var c = factory.getObject('constraint', actuator);
          factory.method.constraint.disableMotor.call(c);
        }
      }
    };
    var editor = new Editor();
    editor.useTemplate(template);
    editor.setValues(ui);
    editor.showEditor('#triggers');
  }
};

function addAllTests() {
  var bodyA = {
    id: 'a',
    group: 'body',
    type: 'basic',
    shape: { type: 'box', dx: 2, dz: 2, dy: 2, segments: 2 },
    position: {  x: 0.5, y: 0.5, z: 0.5 },
    rotation: { x: 0.5, y: 0.3, z: 0 },
    material: {type: 'phong', color: 0x338855, opacity: 0.5, transparent: true},
    mass: 0
  };
  var bodyB = {
    id: 'b',
    group: 'body',
    type: 'basic',
    shape: { type: 'box', dx: 2, dy: 2, dz: 2, segments: 2 },
    position: { x: -2, y: -2, z: -2},
    rotation: { x: 0, y: 0, z: 0 },
    material: {type: 'phong', color: 0x991122, opacity: 0.9, transparent: true},
    mass: 1
  };
  var connectorA = {
    id: 'cA',
    group: 'connector',
    type: 'relative',
    body: bodyA.id,
    base: {y: -1.01},
    up: {y: 1},
    front: {x: 1}
  };
  var connectorB = {
    id: 'cB',
    group: 'connector',
    type: 'relative',
    body: bodyB.id,
    base: {x: 1, y: 1, z: 1.01},
    up: {y: 1},
    front: {x: 1}
  };
  var constraintOptions = {
    a: connectorA.id,
    b: connectorB.id,
    bodyA: bodyA.id,
    bodyB: bodyB.id,
    ratio: 1,
    approach: true
  };
  var type;
  var ca, cb;
  //point
  type = 'point';
  test[type] = makeTest(bodyA, bodyB, connectorA, connectorB, type, constraintOptions);

  type = 'hinge';
  ca = utils.deepCopy(connectorA);
  cb = utils.deepCopy(connectorB);
  ca.base = {x: -1, y: -1, z: -1};
  ca.up = {z: 1};
  cb.base = {x: 1, y: 1, z: 1};
  cb.up = {z: 1};
  test[type] = makeTest(bodyA, bodyB, ca, cb, type, constraintOptions);

  type = 'slider';
  ca = utils.deepCopy(connectorA);
  cb = utils.deepCopy(connectorB);
  ca.base = {x: 0, y: 0, z: 0};
  ca.up = {z: 1};
  ca.front = {y: 1};
  cb.base = {x: 0, y: 0, z: 5};
  cb.up = {z: 1};
  cb.front = {y: 1};
  var bodyBCopy = utils.deepCopy(bodyB);
  bodyBCopy.position = {z: -5, x: 4, y: 5};
  test[type] = makeTest(bodyA, bodyBCopy, ca, cb, type, constraintOptions);

}

function gearTest() {
  var type = 'gear';
  factory.setScope(type);
  var pack = {
    body: {
      wall: {
        type: 'basic',
        shape: { type: 'box', dx: 1.9, dz: 10, dy: 6, segments: 5 },
        position: { x: -1 },
        material: {type: 'phong', color: 0x335588, opacity: 0.5, transparent: true, wireframe: true},
        mass: 0,
        connector: {
          wA: {
            base: {x: 1, y: 2, z: 2},
            up: {x: 1},
            front: {z: 1}
          },
          wB: {
            base: {x: 1, y: -2, z: -2},
            up: {x: 1},
            front: {z: 1}
          },
          wC: {
            base: {x: 1, y: 0, z: -4},
            up: {x: 1},
            front: {z: 1}
          }
        }
      },
      a: {
        type: 'basic',
        shape: { type: 'cylinder', r: 4, dy: 2, segments: 8},
        position: {x: 1, y: 3, z: 3},
        rotation: {z: -Math.PI / 2},
        material: {type: 'phong', color: 0x338855, opacity: 0.5, transparent: true},
        mass: 1,
        connector: {
          'cA': {
            base: {y: -1},
            up: {y: 1},
            front: {z: 1}
          },
          'cwa': {
            base: {y: 1, z: 3},
            up: {y: 1},
            front: {z: 1}
          }
        }
      },
      weight: {
        type: 'basic',
        shape: { type: 'cylinder', r: 0.5, dy: 2},
        rotation: {z: Math.PI / 2},
        material: {type: 'phong', color: 0x335522, opacity: 0.5, transparent: true},
        mass: 2,
        connector: {
          'cww': {
            base: {y: -1},
            up: {y: 1},
            front: {z: 1}
          }
        }
      },
      b: {
        type: 'basic',
        shape: { type: 'cylinder', r: 1, dy: 2, segments: 8 },
        rotation: {z: Math.PI / 2},
        material: {type: 'phong', color: 0x885555, opacity: 0.5, transparent: true},
        mass: 1,
        connector: {
          'cB': {
            base: {y: -1},
            up: {y: 1},
            front: {z: 1}
          }
        }
      },
      c: {
        type: 'basic',
        shape: { type: 'cylinder', r: 1.5, dy: 2, segments: 8 },
        rotation: {z: Math.PI / 2},
        material: {type: 'phong', color: 0x335577, opacity: 0.5, transparent: true},
        mass: 1,
        connector: {
          'cC': {
            base: {y: -1},
            up: {y: 1},
            front: {z: 1}
          }
        }
      }
    },
    constraint: {
      motor: {
        type: 'hinge',
        a: 'wA',
        b: 'cA',
        bodyA: 'wall',
        bodyB: 'a',
        approach: true
      },
      wcons: {
        type: 'hinge',
        a: 'cwa',
        b: 'cww',
        bodyA: 'a',
        bodyB: 'weight',
        approach: true
      },
      wb: {
        type: 'hinge',
        a: 'wB',
        b: 'cB',
        bodyA: 'wall',
        bodyB: 'b',
        approach: true
      },
      wc: {
        type: 'hinge',
        a: 'wC',
        b: 'cC',
        bodyA: 'wall',
        bodyB: 'c',
        approach: true
      },
      gear: {
        type: 'gear',
        a: 'cA',
        b: 'cB',
        bodyA: 'a',
        bodyB: 'b',
        ratio: 0.25,
        approach: false
      },
      gear2: {
        type: 'gear',
        a: 'cB',
        b: 'cC',
        bodyA: 'b',
        bodyB: 'c',
        ratio: 1.5,
        approach: false
      }
    },
    scene: {s1: {}},
    monitor: {m1: {camera: 'satellite', lookAt: 'wall', distance: 15}},
    light: {
      l1: {position: {x: 5, z: -5}},
      l2: {position: {x: -7, y: 6, z: 5}, color: 0x8899bb},
      l3: {position: {y: -5, z: 1}, color: 0x445566}
    }
  };

  factory.loadScene(pack, {
    axisHelper: 2,
    wireframe: false,
    webWorker: false,
    autoStart: true,
    connectorHelper: 0.7,
    canvasContainer: '#container'
  });

  if (inputs[type]) {
    inputs[type](type);
  }
}

addAllTests();
test['gear'] = gearTest;

//test.all = utils.all(test, 1);
module.exports.test = test;
module.exports.clearObjects = clearObjects;
<|endoftext|>


<commit_before>const path = require('path');
const webpack = require('webpack');
const ExtractTextPlugin = require("extract-text-webpack-plugin");
const extractCSS = new ExtractTextPlugin('stylesheets/[name].css');

module.exports = (isTest, isDebug) => {
    const config = {
        entry: {
            main: './src/Main.tsx',
            server: './src/Server.tsx',
            test: './test/Main.ts',
            testConfiguration: './test/configuration/Configuration.ts',
            vendor: ['es6-promise', 'react-router', 'slug', 'ts-helpers']
        },
        output: {
            path: path.resolve(__dirname + '/dist'),
            filename: '[name].bundle.js'
        },
        devtool: 'source-map',
        resolve: {
            modules: [
                path.resolve(__dirname),
                path.resolve(__dirname, 'src'),
                path.resolve(__dirname, 'node_modules')
            ],
            extensions: ['.ts', '.tsx', '.js', '.css', '.less']
        },
        module: {
            loaders: [{
                test: /\.otf$/,
                loader: 'url-loader?limit=100000'
            }, {
                test: /\.css$/,
                loader: extractCSS.extract(['css-loader'])
            }, {
                test: /\.less$/,
                loader: extractCSS.extract(['css-loader', 'less-loader'])
            }, {
                test: /\.tsx?$/,
                loader: 'ts-loader'
            }]
        },
        plugins: [
            extractCSS
        ]
    };

    if (isTest) {
        /**
         * Instruments TS source files for subsequent code coverage.
         * See https://github.com/deepsweet/istanbul-instrumenter-loader
         */
        config.module.loaders.push({
            enforce: 'post',
            test: /\.tsx?$/,
            loader: 'istanbul-instrumenter-loader',
            include: [
                path.resolve(__dirname, 'src')
            ]
        });
    } else {
        config.plugins.push(new webpack.optimize.CommonsChunkPlugin({
            name: 'vendor',
            filename: 'vendor.bundle.js'
        }));
    }

    if (!isDebug) {
        config.plugins.push(new webpack.optimize.UglifyJsPlugin({
            compress: {
                warnings: false
            },
            sourceMap: false,
            mangle: false
        }));
    }

    return config;
};
<commit_msg>KCWA-86 | Added esModules query option for istanbul-instrumenter-loader<commit_after>const path = require('path');
const webpack = require('webpack');
const ExtractTextPlugin = require("extract-text-webpack-plugin");
const extractCSS = new ExtractTextPlugin('stylesheets/[name].css');

module.exports = (isTest, isDebug) => {
    const config = {
        entry: {
            main: './src/Main.tsx',
            server: './src/Server.tsx',
            test: './test/Main.ts',
            testConfiguration: './test/configuration/Configuration.ts',
            vendor: ['es6-promise', 'react-router', 'slug', 'ts-helpers']
        },
        output: {
            path: path.resolve(__dirname + '/dist'),
            filename: '[name].bundle.js'
        },
        devtool: 'source-map',
        resolve: {
            modules: [
                path.resolve(__dirname),
                path.resolve(__dirname, 'src'),
                path.resolve(__dirname, 'node_modules')
            ],
            extensions: ['.ts', '.tsx', '.js', '.css', '.less']
        },
        module: {
            loaders: [{
                test: /\.otf$/,
                loader: 'url-loader?limit=100000'
            }, {
                test: /\.css$/,
                loader: extractCSS.extract(['css-loader'])
            }, {
                test: /\.less$/,
                loader: extractCSS.extract(['css-loader', 'less-loader'])
            }, {
                test: /\.tsx?$/,
                loader: 'ts-loader'
            }]
        },
        plugins: [
            extractCSS
        ]
    };

    if (isTest) {
        /**
         * Instruments TS source files for subsequent code coverage.
         * See https://github.com/deepsweet/istanbul-instrumenter-loader
         */
        config.module.loaders.push({
            enforce: 'post',
            test: /\.tsx?$/,
            loader: 'istanbul-instrumenter-loader',
            query: {
                esModules: true
            },
            include: [
                path.resolve(__dirname, 'src')
            ]
        });
    } else {
        config.plugins.push(new webpack.optimize.CommonsChunkPlugin({
            name: 'vendor',
            filename: 'vendor.bundle.js'
        }));
    }

    if (!isDebug) {
        config.plugins.push(new webpack.optimize.UglifyJsPlugin({
            compress: {
                warnings: false
            },
            sourceMap: false,
            mangle: false
        }));
    }

    return config;
};
<|endoftext|>


<commit_before>var fs = require('fs');
var path = require('path');
var expect = require('chai').expect;

var ttycolor = require('../..');
var ansi = ttycolor.ansi;
var log = path.join(__dirname, '..', '..', 'log', 'out.log');
var file = null;

describe('ttycolor:', function() {
  beforeEach(function(done) {
    file = fs.createWriteStream(log, {flags: 'w'});
    file.on('open', function(fd) {
      done();
    });
  });
  it('should export properties', function(done) {
    expect(ttycolor.ansi).to.be.a('function');
    expect(ttycolor.attributes).to.be.an('object');
    expect(ttycolor.background).to.be.an('object');
    expect(ttycolor.colors).to.be.an('array');
    expect(ttycolor.console).to.be.an('object');
    expect(ttycolor.debug).to.be.a('function');
    expect(ttycolor.foreground).to.be.an('object');
    expect(ttycolor.stringify).to.be.a('function');
    done();
  });
  it('should write to stream', function(done) {
    var input = 'value';
    function cb(value) {
      expect(value).to.be.a('string').that.equals(input);
      var contents = fs.readFileSync(log);
      expect(contents.toString()).to.be.a('string').that.equals(input);
      file.end();
      done();
    }
    console.write({stream: file, callback: cb}, '%s', ansi(input).white().underline());
  });
  it('should return empty string with no arguments', function(done) {
    var result = ttycolor.debug();
    expect(result).to.be.a('string').that.equals('');
    done();
  });
  it('should not touch percent', function(done) {
    var result = ttycolor.debug('%', 'value');
    expect(result).to.be.a('string').that.equals('% value');
    result = ttycolor.debug('%d%', 100);
    expect(result).to.be.a('string').that.equals('100%');
    done();
  });
  it('should return vanilla string', function(done) {
    var input = 'value';
    var result = ttycolor.debug('%s', input);
    expect(result).to.be.a('string').that.equals(input);
    done();
  });
  it('should return vanilla number', function(done) {
    var input = 3.14;
    var result = ttycolor.debug('%s', input);
    expect(result).to.be.a('string').that.equals('' + input);
    done();
  });
  it('should return vanilla json', function(done) {
    var input = {message: 'json'};
    var result = ttycolor.debug('%j', input);
    expect(result).to.be.a('string').that.equals(JSON.stringify(input));
    done();
  });
  it('should handle extra format arguments', function(done) {
    var expected = 'foo:bar baz';
    var result = ttycolor.debug('%s:%s', 'foo', 'bar', 'baz');
    expect(result).to.be.a('string').that.equals(expected);
    done();
  });
  it('should handle too few format arguments', function(done) {
    var expected = 'foo:%s';
    var result = ttycolor.debug('%s:%s', 'foo');
    expect(result).to.be.a('string').that.equals(expected);
    done();
  });
  it('should handle non-string format', function(done) {
    var expected = '1 2 3';
    var result = ttycolor.debug(1, 2, 3);
    expect(result).to.be.a('string').that.equals(expected);
    done();
  });
  it('should handle undefined format', function(done) {
    var expected = 'undefined';
    var result = ttycolor.debug(undefined);
    expect(result).to.be.a('string').that.equals(expected);
    done();
  });
  it('should handle null format', function(done) {
    var expected = 'null';
    var result = ttycolor.debug(null);
    expect(result).to.be.a('string').that.equals(expected);
    done();
  });
  it('should handle true format', function(done) {
    var expected = 'true';
    var result = ttycolor.debug(true);
    expect(result).to.be.a('string').that.equals(expected);
    done();
  });
  it('should handle false format', function(done) {
    var expected = 'false';
    var result = ttycolor.debug(false);
    expect(result).to.be.a('string').that.equals(expected);
    done();
  });
  it('should handle object format', function(done) {
    var expected = '{}';
    var result = ttycolor.debug({});
    expect(result).to.be.a('string').that.equals(expected);
    done();
  });
  it('should handle array format', function(done) {
    var expected = '[]';
    var result = ttycolor.debug([]);
    expect(result).to.be.a('string').that.equals(expected);
    done();
  });
  it('should return escape sequence from stringify', function(done) {
    var input = 'value';
    var expected = '\u001b[1;37m' + input + '\u001b[0m';
    var result = ttycolor.stringify(
      input,
      ttycolor.foreground.white,
      ttycolor.attributes.bright);
      expect(result).to.be.a('string').that.equals(expected);
    done();
  });
  it('should handle format as ansi instance', function(done) {
    var input = 'value';
    var expected = '\u001b[1;37m' + input + '\u001b[0m';
    var result = ttycolor.debug(ansi(input).white().bright());
    expect(result).to.be.a('string').that.equals(expected);
    done();
  });
  it('should handle attribute only chains', function(done) {
    var def = ttycolor.attributes;
    var keys = Object.keys(def), v, result;
    keys.forEach(function(k) {
      //console.log(k);
      v = def[k];
      expected = '\u001b[' + v + ';'
        + ttycolor.foreground.normal + 'm' + k + '\u001b[0m';
      result = ttycolor.debug('%s', ansi(k)[k]());
      expect(result).to.be.a('string').that.equals(expected);
    });
    done();
  });
  it('should handle multiple attribute only chains', function(done) {
    var input = 'value';
    var expected = '\u001b['
      + ttycolor.attributes.bright + ';'
      + ttycolor.foreground.normal + 'm' + '\u001b['
      + ttycolor.attributes.underline + ';'
      + ttycolor.foreground.normal + 'm' + input + '\u001b[0m' + '\u001b[0m';
    var result = ttycolor.debug('%s', ansi(input).underline().bright());
    expect(result).to.be.a('string').that.equals(expected);
    done();
  });
  it('should handle color+attribute chains', function(done) {
    var input = 'value';
    var expected = '\u001b[1;37m' + input + '\u001b[0m';
    var result = ttycolor.debug('%s', ansi(input).white().bright());
    expect(result).to.be.a('string').that.equals(expected);
    result = ttycolor.debug('%s', ansi(input).bright().white());
    expect(result).to.be.a('string').that.equals(expected);
    done();
  });
  it('should handle color only chains', function(done) {
    var def = ttycolor.foreground;
    var keys = Object.keys(def), v, result;
    keys.forEach(function(k) {
      v = def[k];
      expected = '\u001b[' + v + 'm' + k + '\u001b[0m';
      result = ttycolor.debug('%s', ansi(k)[k]());
      expect(result).to.be.a('string').that.equals(expected);
    });
    done();
  });
  it('should handle background only chains', function(done) {
    var def = ttycolor.background;
    var keys = Object.keys(def), v, result;
    keys.forEach(function(k) {
      v = def[k];
      expected = '\u001b[' + v + 'm' + k + '\u001b[0m';
      result = ttycolor.debug('%s', ansi(k).bg()[k]());
      expect(result).to.be.a('string').that.equals(expected);
    });
    done();
  });
  it('should handle foreground/background chains', function(done) {
    var fore = ttycolor.foreground;
    var back = ttycolor.background;
    var keys = Object.keys(fore), fg, bg, result;
    keys.forEach(function(k) {
      fg = fore[k];
      bg = back[k];
      expected = '\u001b[' + bg + 'm' + '\u001b['
        + fg + 'm' + k + '\u001b[0m' + '\u001b[0m';
      result = ttycolor.debug('%s', ansi(k)[k]().bg()[k]());
      //console.dir(result);
      expect(result).to.be.a('string').that.equals(expected);
    });
    done();
  });
  // TODO
  //it('should handle background/foreground chains', function(done) {
    //var fore = ttycolor.foreground;
    //var back = ttycolor.background;
    //var keys = Object.keys(fore), fg, bg, result;
    //keys.forEach(function(k) {
      //fg = fore[k];
      //bg = back[k];
      //expected = '\u001b[' + fg + 'm' + '\u001b['
        //+ bg + 'm' + k + '\u001b[0m' + '\u001b[0m';
      //result = ttycolor.debug('%s', ansi(k).bg()[k]()[k]());
      ////console.dir(result);
      //expect(result).to.be.a('string').that.equals(expected);
    //});
    //done();
  //});
})
<commit_msg>Update assertions.<commit_after>var fs = require('fs');
var path = require('path');
var expect = require('chai').expect;

var ttycolor = require('../..');
var ansi = ttycolor.ansi;
var log = path.join(__dirname, '..', '..', 'log', 'out.log');
var file = null;

describe('ttycolor:', function() {
  beforeEach(function(done) {
    file = fs.createWriteStream(log, {flags: 'w'});
    file.on('open', function(fd) {
      done();
    });
  });
  it('should export properties', function(done) {
    expect(ttycolor.ansi).to.be.a('function');
    expect(ttycolor.attributes).to.be.an('object');
    expect(ttycolor.background).to.be.an('object');
    expect(ttycolor.colors).to.be.an('array');
    expect(ttycolor.console).to.be.an('object');
    expect(ttycolor.debug).to.be.a('function');
    expect(ttycolor.foreground).to.be.an('object');
    expect(ttycolor.stringify).to.be.a('function');
    done();
  });
  it('should write to stream', function(done) {
    var input = 'value';
    function cb(value) {
      expect(value).to.be.a('string').that.equals(input);
      var contents = fs.readFileSync(log);
      expect(contents.toString()).to.be.a('string').that.equals(input);
      file.end();
      done();
    }
    console.write({stream: file, callback: cb}, '%s', ansi(input).white().underline());
  });
  it('should return empty string with no arguments', function(done) {
    var result = ttycolor.debug();
    expect(result).to.be.a('string').that.equals('');
    done();
  });
  it('should not touch percent', function(done) {
    var result = ttycolor.debug('%', 'value');
    expect(result).to.be.a('string').that.equals('% value');
    result = ttycolor.debug('%d%', 100);
    expect(result).to.be.a('string').that.equals('100%');
    done();
  });
  it('should return vanilla string', function(done) {
    var input = 'value';
    var result = ttycolor.debug('%s', input);
    expect(result).to.be.a('string').that.equals(input);
    done();
  });
  it('should return vanilla number', function(done) {
    var input = 3.14;
    var result = ttycolor.debug('%s', input);
    expect(result).to.be.a('string').that.equals('' + input);
    done();
  });
  it('should return vanilla json', function(done) {
    var input = {message: 'json'};
    var result = ttycolor.debug('%j', input);
    expect(result).to.be.a('string').that.equals(JSON.stringify(input));
    done();
  });
  it('should handle extra format arguments', function(done) {
    var expected = 'foo:bar baz';
    var result = ttycolor.debug('%s:%s', 'foo', 'bar', 'baz');
    expect(result).to.be.a('string').that.equals(expected);
    done();
  });
  it('should handle too few format arguments', function(done) {
    var expected = 'foo:%s';
    var result = ttycolor.debug('%s:%s', 'foo');
    expect(result).to.be.a('string').that.equals(expected);
    done();
  });
  it('should handle non-string format', function(done) {
    var expected = '1 2 3';
    var result = ttycolor.debug(1, 2, 3);
    expect(result).to.be.a('string').that.equals(expected);
    done();
  });
  it('should handle undefined format', function(done) {
    var expected = 'undefined';
    var result = ttycolor.debug(undefined);
    expect(result).to.be.a('string').that.equals(expected);
    done();
  });
  it('should handle null format', function(done) {
    var expected = 'null';
    var result = ttycolor.debug(null);
    expect(result).to.be.a('string').that.equals(expected);
    done();
  });
  it('should handle true format', function(done) {
    var expected = 'true';
    var result = ttycolor.debug(true);
    expect(result).to.be.a('string').that.equals(expected);
    done();
  });
  it('should handle false format', function(done) {
    var expected = 'false';
    var result = ttycolor.debug(false);
    expect(result).to.be.a('string').that.equals(expected);
    done();
  });
  it('should handle object format', function(done) {
    var expected = '{}';
    var result = ttycolor.debug({});
    expect(result).to.be.a('string').that.equals(expected);
    done();
  });
  it('should handle array format', function(done) {
    var expected = '[]';
    var result = ttycolor.debug([]);
    expect(result).to.be.a('string').that.equals(expected);
    done();
  });
  it('should return escape sequence from stringify', function(done) {
    var input = 'value';
    var expected = '\u001b[1;37m' + input + '\u001b[0m';
    var result = ttycolor.stringify(
      input,
      ttycolor.foreground.white,
      ttycolor.attributes.bright);
      expect(result).to.be.a('string').that.equals(expected);
    done();
  });
  it('should handle format as ansi instance', function(done) {
    var input = 'value';
    var expected = '\u001b[1;37m' + input + '\u001b[0m';
    var result = ttycolor.debug(ansi(input).white().bright());
    expect(result).to.be.a('string').that.equals(expected);
    done();
  });
  it('should handle attribute only chains', function(done) {
    var def = ttycolor.attributes;
    var keys = Object.keys(def), v, result;
    keys.forEach(function(k) {
      //console.log(k);
      v = def[k];
      expected = '\u001b[' + v + ';'
        + ttycolor.foreground.normal + 'm' + k + '\u001b[0m';
      result = ttycolor.debug('%s', ansi(k)[k]());
      expect(result).to.be.a('string').that.equals(expected);
    });
    done();
  });
  it('should handle multiple attribute only chains', function(done) {
    var input = 'value';
    var expected = '\u001b['
      + ttycolor.attributes.bright + ';'
      + ttycolor.foreground.normal + 'm' + '\u001b['
      + ttycolor.attributes.underline + ';'
      + ttycolor.foreground.normal + 'm' + input + '\u001b[0m' + '\u001b[0m';
    var result = ttycolor.debug('%s', ansi(input).underline().bright());
    expect(result).to.be.a('string').that.equals(expected);
    done();
  });
  it('should handle color/attribute chains', function(done) {
    var input = 'value';
    var expected = '\u001b[1;37m' + input + '\u001b[0m';
    var result = ttycolor.debug('%s', ansi(input).white().bright());
    expect(result).to.be.a('string').that.equals(expected);
    done();
  });
  it('should handle attribute/color chains', function(done) {
    var input = 'value';
    var expected = '\u001b[1;37m' + input + '\u001b[0m';
    var result = ttycolor.debug('%s', ansi(input).bright().white());
    expect(result).to.be.a('string').that.equals(expected);
    done();
  });
  it('should handle color only chains', function(done) {
    var def = ttycolor.foreground;
    var keys = Object.keys(def), v, result;
    keys.forEach(function(k) {
      v = def[k];
      expected = '\u001b[' + v + 'm' + k + '\u001b[0m';
      result = ttycolor.debug('%s', ansi(k)[k]());
      expect(result).to.be.a('string').that.equals(expected);
    });
    done();
  });
  it('should handle background only chains', function(done) {
    var def = ttycolor.background;
    var keys = Object.keys(def), v, result;
    keys.forEach(function(k) {
      v = def[k];
      expected = '\u001b[' + v + 'm' + k + '\u001b[0m';
      result = ttycolor.debug('%s', ansi(k).bg()[k]());
      expect(result).to.be.a('string').that.equals(expected);
    });
    done();
  });
  it('should handle foreground/background chains', function(done) {
    var fore = ttycolor.foreground;
    var back = ttycolor.background;
    var keys = Object.keys(fore), fg, bg, result;
    keys.forEach(function(k) {
      fg = fore[k];
      bg = back[k];
      expected = '\u001b[' + bg + 'm' + '\u001b['
        + fg + 'm' + k + '\u001b[0m' + '\u001b[0m';
      result = ttycolor.debug('%s', ansi(k)[k]().bg()[k]());
      //console.dir(result);
      expect(result).to.be.a('string').that.equals(expected);
    });
    done();
  });
  // TODO
  //it('should handle background/foreground chains', function(done) {
    //var fore = ttycolor.foreground;
    //var back = ttycolor.background;
    //var keys = Object.keys(fore), fg, bg, result;
    //keys.forEach(function(k) {
      //fg = fore[k];
      //bg = back[k];
      //expected = '\u001b[' + fg + 'm' + '\u001b['
        //+ bg + 'm' + k + '\u001b[0m' + '\u001b[0m';
      //result = ttycolor.debug('%s', ansi(k).bg()[k]()[k]());
      ////console.dir(result);
      //expect(result).to.be.a('string').that.equals(expected);
    //});
    //done();
  //});
})
<|endoftext|>


<commit_before>import { Component, ShaderLib } from '../core'
import { assignDefaults } from '../utils'
import { ScopedContext } from '../scope'
import { ShaderDefines } from './defines'
import injectDefines from 'glsl-inject-defines'

export class Shader extends Component {
  static defaults() { return { ...ShaderLib.defaults(), defines: {} } }
  constructor(ctx, initialState = {}) {
    assignDefaults(initialState, Shader.defaults())
    const { shaderName } = initialState
    const contextCache = {}
    const shaderCache = {}
    const shaderLib = new ShaderLib({ ...initialState })

    const injectShaderDefines = Component.compose(
      new ShaderDefines(ctx, { ...initialState.defines }),
    )

    let injectContext = null

    let fragmentShaderUncompiled = null
    let vertexShaderUncompiled = null
    let fragmentShader = null
    let vertexShader = null

    super(ctx, initialState, update)
    function update(state, block, previousState) {
      injectShaderDefines((reglContext) => {
        let {forceCompile = false} = state
        const {defines} = reglContext

        if (Object.keys(defines).length) {
          if (shaderLib.preprocessor.define(defines)) {
            forceCompile = true
          }
        }

        if (forceCompile || shouldCompile(reglContext, state)) {
          compile(reglContext, state)
        }

        setInjectContext(reglContext, state)

        if ('function' == typeof injectContext) {
          injectContext(state, block)
        } else {
          block(state)
        }
      })
    }

    function getShaderFromCache(reglContext, currentState, shader) {
      shader = getViableShader(reglContext, currentState, shader)
      return shaderCache[shaderLib.hash(shader)]
    }

    function setInjectContext(reglContext, currentState) {
      const opts = {
        context: {
          fragmentShader: ({fragmentShader: fs}) => fragmentShader || fs,
          vertexShader: ({vertexShader: vs }) => vertexShader || vs,
        }
      }

      const requestedFragmentShader = getShaderFromCache(
        reglContext,
        currentState,
        currentState.fragmentShader)

      const requestedVertexShader = getShaderFromCache(
        reglContext,
        currentState,
        currentState.vertexShader)

      if (requestedFragmentShader && requestedFragmentShader != fragmentShader) {
        fragmentShader = requestedFragmentShader
      }

      if (requestedVertexShader && requestedVertexShader != vertexShader) {
        vertexShader = requestedVertexShader
      }

      if ('string' == typeof vertexShader) { opts.vert = vertexShader }
      if ('string' == typeof fragmentShader) { opts.frag = fragmentShader }

      if ('string' == typeof opts.vert || 'string' == typeof opts.frag) {
        const hash = [shaderLib.hash(opts.vert), shaderLib.hash(opts.frag)]
        .filter(Boolean).join('')
        if (null == contextCache[hash]) {
          injectContext = ctx.regl(opts)
          contextCache[hash] = injectContext
          injectContext.opts = opts
        } else {
          injectContext = contextCache[hash]
        }
      }
    }

    function compile(reglContext, currentState) {
      if (!isShaderCached(currentState.vertexShader)) {
        compileVertexShader()
      }

      if (!isShaderCached(currentState.fragmentShader)) {
        compileFragmentShader()
      }

      function compileShader(type, shader) {
        let compiled = null
        let uncompiled = null
        if (isViableShader(shader)) {
          uncompiled = getViableShader(reglContext, currentState, shader)
          compiled = shaderLib.compile(`${shaderName} (${type})`, uncompiled)
          compiled = shaderLib.preprocess(compiled)
          return {compiled, uncompiled}
        }
        return null
      }

      function isShaderCached(shader) {
        return Boolean(getShaderFromCache(reglContext, currentState, shader))
      }

      function compileVertexShader() {
        const result = compileShader('vertex', currentState.vertexShader)
        if (result) {
          vertexShader = result.compiled
          vertexShaderUncompiled = result.uncompiled
          shaderCache[shaderLib.hash(vertexShaderUncompiled)] = vertexShader
        }
      }

      function compileFragmentShader() {
        const result = compileShader('fragment', currentState.fragmentShader)
        if (result) {
          fragmentShader = result.compiled
          fragmentShaderUncompiled = result.uncompiled
          shaderCache[shaderLib.hash(fragmentShaderUncompiled)] = fragmentShader
        }
      }
    }

    function getViableShader(reglContext, currentState, shader) {
      const {defines} = shaderLib
      if ('string' == typeof shader) {
        return injectDefines(shader, defines)
      } else if ('function' == typeof shader) {
        return injectDefines(shader(reglContext, currentState), defines)
      }
    }

    function isViableShader(shader) {
      return ['string', 'function'].indexOf(typeof shader) > -1
    }

    function shouldCompile(reglContext, currentState) {
      let needsCompile = false
      check('function' != typeof injectContext)
      checkShader(vertexShaderUncompiled, currentState.vertexShader)
      checkShader(fragmentShaderUncompiled, currentState.fragmentShader)
      return needsCompile

      function check(cond) {
        if (cond) { needsCompile = true }
      }

      function checkShader(current, next) {
        let cond = false
        next = getViableShader(reglContext, currentState, next)
        if (shaderCache[shaderLib.hash(next)]) {
          return check(true)
        } else if ('string' != typeof current && next) {
          return check(true)
        } else if ('string' == typeof next && current != next) {
          return check(true)
        }
      }
    }
  }
}
<commit_msg>refactor(src/shader/shader.js): Clean up cache logic<commit_after>import { Component, ShaderLib } from '../core'
import { assignDefaults } from '../utils'
import { ScopedContext } from '../scope'
import { ShaderDefines } from './defines'

export class Shader extends Component {
  static defaults() { return { ...ShaderLib.defaults(), defines: {} } }
  constructor(ctx, initialState = {}) {
    assignDefaults(initialState, Shader.defaults())
    const { shaderName } = initialState
    const contextCache = {}
    const shaderCache = {}
    const shaderLib = new ShaderLib({ ...initialState })

    const injectShaderDefines = Component.compose(
      new ShaderDefines(ctx, { ...initialState.defines }),
    )

    let didDefinesChange = false

    let injectContext = null

    let fragmentShaderUncompiled = null
    let vertexShaderUncompiled = null
    let fragmentShader = null
    let vertexShader = null

    let hashMap = {}

    super(ctx, initialState, update)
    function update(state, block, previousState) {
      injectShaderDefines((reglContext) => {
        let {forceCompile = false} = state
        const {defines} = reglContext

        if (Object.keys(defines).length) {
          if (shaderLib.preprocessor.define(defines)) {
            didDefinesChange = true
            forceCompile = true
          }
        }

        if (forceCompile || shouldCompile(reglContext, state)) {
          compile(reglContext, state)
        }

        setInjectContext(reglContext, state)

        didDefinesChange = false
        if ('function' == typeof injectContext) {
          injectContext(state, block)
        } else {
          block(state)
        }
      })
    }

    function hash(str) {
      if (hashMap[str]) { return hashMap[str] }
      return (hashMap[str] = shaderLib.hash(str))
    }

    function getShaderFromCache(reglContext, currentState, shader) {
      shader = getViableShader(reglContext, currentState, shader)
      return shaderCache[hash(shader)]
    }

    function setInjectContext(reglContext, currentState) {
      const opts = {
        context: {
          fragmentShader: ({fragmentShader: fs}) => fragmentShader || fs,
          vertexShader: ({vertexShader: vs }) => vertexShader || vs,
        }
      }

      if (!currentState.fragmentShader && !currentState.vertexShader) {
        return
      }

      const requestedFragmentShader = getShaderFromCache(
        reglContext,
        currentState,
        currentState.fragmentShader)

      const requestedVertexShader = getShaderFromCache(
        reglContext,
        currentState,
        currentState.vertexShader)

      if (!requestedVertexShader && !requestedFragmentShader) {
        return
      }

      if (requestedFragmentShader && requestedFragmentShader != fragmentShader) {
        fragmentShader = requestedFragmentShader
      }

      if (requestedVertexShader && requestedVertexShader != vertexShader) {
        vertexShader = requestedVertexShader
      }

      if ('string' == typeof fragmentShader) {
        opts.frag = fragmentShader
      }

      if ('string' == typeof vertexShader) {
        opts.vert = vertexShader
      }

      if ('string' == typeof opts.vert || 'string' == typeof opts.frag) {
        if (injectContext) {
          if (injectContext.opts.vert == opts.vert) {
            if (injectContext.opts.frag == opts.frag) {
              return
            }
          }
        }
        const id = ((o) => [o.vert||'', o.frag||''].map(hash).join(''))(opts)
        if (null == contextCache[id]) {
          injectContext = ctx.regl(opts)
          contextCache[id] = injectContext
          injectContext.opts = opts
        } else if (contextCache[id] != injectContext) {
          injectContext = contextCache[id]
        }
      }
    }

    function compile(reglContext, currentState) {
      if (didDefinesChange || !isShaderCached(currentState.vertexShader)) {
        compileVertexShader()
      }

      if (didDefinesChange || !isShaderCached(currentState.fragmentShader)) {
        compileFragmentShader()
      }

      function compileShader(type, shader) {
        let compiled = null
        let uncompiled = null
        if (isViableShader(shader)) {
          uncompiled = getViableShader(reglContext, currentState, shader)
          compiled = shaderLib.compile(`${shaderName} (${type})`, uncompiled)
          compiled = shaderLib.preprocess(compiled)
          return {compiled, uncompiled}
        }
        return null
      }

      function isShaderCached(shader) {
        return Boolean(getShaderFromCache(reglContext, currentState, shader))
      }

      function compileVertexShader() {
        const result = compileShader('vertex', currentState.vertexShader)
        if (result) {
          vertexShader = result.compiled
          vertexShaderUncompiled = result.uncompiled
          shaderCache[hash(vertexShaderUncompiled)] = vertexShader
        }
      }

      function compileFragmentShader() {
        const result = compileShader('fragment', currentState.fragmentShader)
        if (result) {
          fragmentShader = result.compiled
          fragmentShaderUncompiled = result.uncompiled
          shaderCache[hash(fragmentShaderUncompiled)] = fragmentShader
        }
      }
    }

    function getViableShader(reglContext, currentState, shader) {
      const {defines} = shaderLib
      let source = null
      if ('string' == typeof shader) {
        source = shader
      } else if ('function' == typeof shader) {
        source = shader(reglContext, currentState)
      }
      return source
    }

    function isViableShader(shader) {
      return ['string', 'function'].indexOf(typeof shader) > -1
    }

    function shouldCompile(reglContext, currentState) {
      let needsCompile = false
      check('function' != typeof injectContext)
      checkShader(vertexShaderUncompiled, currentState.vertexShader)
      checkShader(fragmentShaderUncompiled, currentState.fragmentShader)
      return needsCompile

      function check(cond) {
        if (cond) { needsCompile = true }
      }

      function checkShader(current, next) {
        let cond = false
        next = getViableShader(reglContext, currentState, next)
        if (shaderCache[hash(next)]) {
          return check(false)
        } else if ('string' != typeof current && 'string' == typeof next) {
          return check(true)
        } else if ('string' == typeof next && current != next) {
          return check(true)
        }
      }
    }
  }
}
<|endoftext|>