<commit_before>/* global module:false */
module.exports = function(grunt) {
	var port = grunt.option('port') || 8000;
	var base = grunt.option('base') || '.';

	// Project configuration
	grunt.initConfig({
		pkg: grunt.file.readJSON('package.json'),
		meta: {
			banner:
				'/*!\n' +
				' * reveal.js <%= pkg.version %> (<%= grunt.template.today("yyyy-mm-dd, HH:MM") %>)\n' +
				' * http://lab.hakim.se/reveal-js\n' +
				' * MIT licensed\n' +
				' *\n' +
				' * Copyright (C) 2016 Hakim El Hattab, http://hakim.se\n' +
				' */'
		},

		qunit: {
			files: [ 'test/*.html' ]
		},

		uglify: {
			options: {
				banner: '<%= meta.banner %>\n'
			},
			build: {
				src: 'js/reveal.js',
				dest: 'js/reveal.min.js'
			}
		},

		sass: {
			core: {
				files: {
					'css/reveal.css': 'css/reveal.scss',
				}
			},
			themes: {
				files: [
					{
						expand: true,
						cwd: 'css/theme/source',
						src: ['*.scss'],
						dest: 'css/theme',
						ext: '.css'
					}
				]
			}
		},

		autoprefixer: {
			dist: {
				src: 'css/reveal.css'
			}
		},

		cssmin: {
			compress: {
				files: {
					'css/reveal.min.css': [ 'css/reveal.css' ]
				}
			}
		},

		jshint: {
			options: {
				curly: false,
				eqeqeq: true,
				immed: true,
				latedef: true,
				newcap: true,
				noarg: true,
				sub: true,
				undef: true,
				eqnull: true,
				browser: true,
				expr: true,
				globals: {
					head: false,
					module: false,
					console: false,
					unescape: false,
					define: false,
					exports: false
				}
			},
			files: [ 'Gruntfile.js', 'js/reveal.js' ]
		},

		connect: {
			server: {
				options: {
					port: port,
					base: base,
					livereload: true,
					open: true
				}
			}
		},

		zip: {
			'reveal-js-presentation.zip': [
				'index.html',
				'css/**',
				'js/**',
				'lib/**',
				'images/**',
				'plugin/**',
				'**.md'
			]
		},

		watch: {
			js: {
				files: [ 'Gruntfile.js', 'js/reveal.js' ],
				tasks: 'js'
			},
			theme: {
				files: [ 'css/theme/source/*.scss', 'css/theme/template/*.scss' ],
				tasks: 'css-themes'
			},
			css: {
				files: [ 'css/reveal.scss' ],
				tasks: 'css-core'
			},
			html: {
				files: [ '*.html']
			},
			markdown: {
				files: [ '*.md' ]
			},
			options: {
				livereload: true
			}
		}

	});

	// Dependencies
	grunt.loadNpmTasks( 'grunt-contrib-qunit' );
	grunt.loadNpmTasks( 'grunt-contrib-jshint' );
	grunt.loadNpmTasks( 'grunt-contrib-cssmin' );
	grunt.loadNpmTasks( 'grunt-contrib-uglify' );
	grunt.loadNpmTasks( 'grunt-contrib-watch' );
	grunt.loadNpmTasks( 'grunt-sass' );
	grunt.loadNpmTasks( 'grunt-contrib-connect' );
	grunt.loadNpmTasks( 'grunt-autoprefixer' );
	grunt.loadNpmTasks( 'grunt-zip' );

	// Default task
	grunt.registerTask( 'default', [ 'css', 'js' ] );

	// JS task
	grunt.registerTask( 'js', [ 'jshint', 'uglify', 'qunit' ] );

	// Theme CSS
	grunt.registerTask( 'css-themes', [ 'sass:themes' ] );

	// Core framework CSS
	grunt.registerTask( 'css-core', [ 'sass:core', 'autoprefixer', 'cssmin' ] );

	// All CSS
	grunt.registerTask( 'css', [ 'sass', 'autoprefixer', 'cssmin' ] );

	// Package presentation to archive
	grunt.registerTask( 'package', [ 'default', 'zip' ] );

	// Serve presentation locally
	grunt.registerTask( 'serve', [ 'connect', 'watch' ] );

	// Run tests
	grunt.registerTask( 'test', [ 'jshint', 'qunit' ] );

};
<commit_msg>Watching templates<commit_after>/* global module:false */
module.exports = function(grunt) {
	var port = grunt.option('port') || 8000;
	var base = grunt.option('base') || '.';

	// Project configuration
	grunt.initConfig({
		pkg: grunt.file.readJSON('package.json'),
		meta: {
			banner:
				'/*!\n' +
				' * reveal.js <%= pkg.version %> (<%= grunt.template.today("yyyy-mm-dd, HH:MM") %>)\n' +
				' * http://lab.hakim.se/reveal-js\n' +
				' * MIT licensed\n' +
				' *\n' +
				' * Copyright (C) 2016 Hakim El Hattab, http://hakim.se\n' +
				' */'
		},

		qunit: {
			files: [ 'test/*.html' ]
		},

		uglify: {
			options: {
				banner: '<%= meta.banner %>\n'
			},
			build: {
				src: 'js/reveal.js',
				dest: 'js/reveal.min.js'
			}
		},

		sass: {
			core: {
				files: {
					'css/reveal.css': 'css/reveal.scss',
				}
			},
			themes: {
				files: [
					{
						expand: true,
						cwd: 'css/theme/source',
						src: ['*.scss'],
						dest: 'css/theme',
						ext: '.css'
					}
				]
			}
		},

		autoprefixer: {
			dist: {
				src: 'css/reveal.css'
			}
		},

		cssmin: {
			compress: {
				files: {
					'css/reveal.min.css': [ 'css/reveal.css' ]
				}
			}
		},

		jshint: {
			options: {
				curly: false,
				eqeqeq: true,
				immed: true,
				latedef: true,
				newcap: true,
				noarg: true,
				sub: true,
				undef: true,
				eqnull: true,
				browser: true,
				expr: true,
				globals: {
					head: false,
					module: false,
					console: false,
					unescape: false,
					define: false,
					exports: false
				}
			},
			files: [ 'Gruntfile.js', 'js/reveal.js' ]
		},

		connect: {
			server: {
				options: {
					port: port,
					base: base,
					livereload: true,
					open: true
				}
			}
		},

		zip: {
			'reveal-js-presentation.zip': [
				'index.html',
				'css/**',
				'js/**',
				'lib/**',
				'images/**',
				'plugin/**',
				'**.md'
			]
		},

		watch: {
			js: {
				files: [ 'Gruntfile.js', 'js/reveal.js' ],
				tasks: 'js'
			},
			theme: {
				files: [ 'css/theme/source/*.scss', 'css/theme/template/*.scss' ],
				tasks: 'css-themes'
			},
			css: {
				files: [ 'css/reveal.scss' ],
				tasks: 'css-core'
			},
			html: {
				files: [ '*.html', 'templates/*.html' ]
			},
			markdown: {
				files: [ '*.md' ]
			},
			options: {
				livereload: true
			}
		}

	});

	// Dependencies
	grunt.loadNpmTasks( 'grunt-contrib-qunit' );
	grunt.loadNpmTasks( 'grunt-contrib-jshint' );
	grunt.loadNpmTasks( 'grunt-contrib-cssmin' );
	grunt.loadNpmTasks( 'grunt-contrib-uglify' );
	grunt.loadNpmTasks( 'grunt-contrib-watch' );
	grunt.loadNpmTasks( 'grunt-sass' );
	grunt.loadNpmTasks( 'grunt-contrib-connect' );
	grunt.loadNpmTasks( 'grunt-autoprefixer' );
	grunt.loadNpmTasks( 'grunt-zip' );

	// Default task
	grunt.registerTask( 'default', [ 'css', 'js' ] );

	// JS task
	grunt.registerTask( 'js', [ 'jshint', 'uglify', 'qunit' ] );

	// Theme CSS
	grunt.registerTask( 'css-themes', [ 'sass:themes' ] );

	// Core framework CSS
	grunt.registerTask( 'css-core', [ 'sass:core', 'autoprefixer', 'cssmin' ] );

	// All CSS
	grunt.registerTask( 'css', [ 'sass', 'autoprefixer', 'cssmin' ] );

	// Package presentation to archive
	grunt.registerTask( 'package', [ 'default', 'zip' ] );

	// Serve presentation locally
	grunt.registerTask( 'serve', [ 'connect', 'watch' ] );

	// Run tests
	grunt.registerTask( 'test', [ 'jshint', 'qunit' ] );

};
<|endoftext|>


<commit_before>/*globals chai, httpplease, httppleaseplugins, describe, it */
'use strict';

var
    assert = chai.assert,
    http = httpplease,
    plugins = httppleaseplugins,
    testServerUrl = 'http://localhost:4001';

describe('httpplease', function () {
    it('performs a get request', function (done) {
        http.get(testServerUrl + '/getjson', function (err, res) {
            assert.equal(res.text, JSON.stringify({
                hello: 'world'
            }));
            done();
        });
    });
    it('calls onload', function (done) {
        http.get({
            url: testServerUrl + '/getjson',
            onload: function (res) {
                assert.equal(res.text, JSON.stringify({
                    hello: 'world'
                }));
                done();
            }
        });
    });
    it('identifies errors', function (done) {
        http.get(testServerUrl + '/404', function (err, res) {
            assert.equal(err.status, 404);
            assert(err.isHttpError);
            done();
        });
    });
    it('calls onerror', function (done) {
        http.get({
            url: testServerUrl + '/404',
            onerror: function (err) {
                assert.equal(err.status, 404);
                assert(err.isHttpError);
                done();
            }
        });
    });
    it('obeys the errorOn404 option', function (done) {
        http.get({
            url: testServerUrl + '/404',
            errorOn404: false
        }, function (err, res) {
            assert.isUndefined(err);
            assert(res.isHttpError);
            done();
        });
    });
    it('sends headers', function (done) {
        var req;
        req = {
            url: testServerUrl + '/headers',
            headers: {
                hello: 'world'
            }
        };
        http.get(req, function (err, res) {
            var json;
            json = JSON.parse(res.text);
            assert.equal(json.hello, 'world');
            done();
        });
    });
    describe('defaults', function () {
        it('adds defaults', function () {
            assert.equal(http.defaults({
                dummy: 5
            }).defaults().dummy, 5);
        });
        it("doesn't mutate the http function", function () {
            http.defaults({
                dummy: 5
            });
            assert.isUndefined(http.defaults().dummy);
        });
    });
    describe('use', function () {
        it('adds a plugin', function () {
            var startCount;
            startCount = http.plugins().length;
            assert.equal(http.use({}).plugins().length, startCount + 1);
        });
        it("doesn't mutate the http function", function () {
            var startCount;
            startCount = http.plugins().length;
            http.use({});
            assert.equal(http.plugins().length, startCount);
        });
    });
    describe('bare', function () {
        it('creates a http function without plugins', function () {
            assert.equal(http.use({}).bare().plugins().length, 0);
        });
    });
});

describe('plugins', function () {
    describe('jsonparser', function () {
        it('is used for error responses', function (done) {
            http.use(plugins.jsonparser).get(testServerUrl + '/404', function (err, res) {
                assert.deepEqual(err.body, {
                    sad: 'panda'
                });
                done();
            });
        });
        it('parses json responses', function (done) {
            http.use(plugins.jsonparser).get(testServerUrl + '/getjson', function (err, res) {
                assert.deepEqual(res.body, {
                    hello: 'world'
                });
                done();
            });
        });
        function parseWithContentType(ct, isJsonType) {
            var res;
            if (isJsonType == null) {
                isJsonType = true;
            }
            res = {
                contentType: ct,
                body: '{"hello": "world"}'
            };
            plugins.jsonparser.processResponse(res);
            if (isJsonType) {
                assert.deepEqual(res.body, {
                    hello: 'world'
                });
            } else {
                assert.equal(res.body, '{"hello": "world"}');
            }
        }
        it('honors the content type', function () {
            parseWithContentType('application/json');
            parseWithContentType('text/json');
            parseWithContentType('text/json');
            parseWithContentType('text/something+json');
            parseWithContentType('application/json; charset=utf-8');
            parseWithContentType('application/jsonp', false);
        });
    });
    describe('jsonrequest', function () {
        it('adds an Accept header', function () {
            var req = new http.Request();
            plugins.jsonrequest.processRequest(req);
            assert.equal(req.header('Accept'), 'application/json');
        });
        it('formats the request', function () {
            var req;
            req = new http.Request({
                body: {
                    hello: 'world'
                }
            });
            plugins.jsonrequest.processRequest(req);
            assert.equal(req.body, '{"hello":"world"}');
            assert.equal(req.header('Content-Type'), 'application/json');
        });
    });
    describe('setprotocol', function () {
        var currentProtocol = typeof window === 'undefined' ? 'http:' : window.location.protocol,
            otherProtocol = currentProtocol === 'http:' ? 'https:' : 'http:';

        it('uses http by default', function () {
            var req = new http.Request('//example.com/');
            plugins.setprotocol.processRequest(req);
            assert.equal(req.url, 'http://example.com/');
        });
        it('can be invoked to provide options', function () {
            var req = new http.Request('//example.com/');
            plugins
                .setprotocol({defaultProtocol: currentProtocol})
                .processRequest(req);
            assert.equal(req.url, currentProtocol + '//example.com/');
        });
        it('can override protocols', function () {
            var req = new http.Request(otherProtocol + '//example.com/');
            plugins
                .setprotocol({defaultProtocol: currentProtocol, override: true})
                .processRequest(req);
            assert.equal(req.url, currentProtocol + '//example.com/');
        });
    });
});
<commit_msg>Test that requests can be aborted<commit_after>/*globals chai, httpplease, httppleaseplugins, describe, it */
'use strict';

var
    assert = chai.assert,
    http = httpplease,
    plugins = httppleaseplugins,
    testServerUrl = 'http://localhost:4001';

describe('httpplease', function () {
    it('performs a get request', function (done) {
        http.get(testServerUrl + '/getjson', function (err, res) {
            assert.equal(res.text, JSON.stringify({
                hello: 'world'
            }));
            done();
        });
    });
    it('calls onload', function (done) {
        http.get({
            url: testServerUrl + '/getjson',
            onload: function (res) {
                assert.equal(res.text, JSON.stringify({
                    hello: 'world'
                }));
                done();
            }
        });
    });
    it('identifies errors', function (done) {
        http.get(testServerUrl + '/404', function (err, res) {
            assert.equal(err.status, 404);
            assert(err.isHttpError);
            done();
        });
    });
    it('calls onerror', function (done) {
        http.get({
            url: testServerUrl + '/404',
            onerror: function (err) {
                assert.equal(err.status, 404);
                assert(err.isHttpError);
                done();
            }
        });
    });
    it('obeys the errorOn404 option', function (done) {
        http.get({
            url: testServerUrl + '/404',
            errorOn404: false
        }, function (err, res) {
            assert.isUndefined(err);
            assert(res.isHttpError);
            done();
        });
    });
    it('sends headers', function (done) {
        var req;
        req = {
            url: testServerUrl + '/headers',
            headers: {
                hello: 'world'
            }
        };
        http.get(req, function (err, res) {
            var json;
            json = JSON.parse(res.text);
            assert.equal(json.hello, 'world');
            done();
        });
    });
    describe('abort', function () {
        it('cancels requests', function (done) {
            http
                .get(testServerUrl + '/getjson', function (err, res) {
                    assert.equal(err && err.name, 'Cancel');
                    assert(err.request && err.request.aborted);
                    done();
                })
                .abort();
        });
    });
    describe('defaults', function () {
        it('adds defaults', function () {
            assert.equal(http.defaults({
                dummy: 5
            }).defaults().dummy, 5);
        });
        it("doesn't mutate the http function", function () {
            http.defaults({
                dummy: 5
            });
            assert.isUndefined(http.defaults().dummy);
        });
    });
    describe('use', function () {
        it('adds a plugin', function () {
            var startCount;
            startCount = http.plugins().length;
            assert.equal(http.use({}).plugins().length, startCount + 1);
        });
        it("doesn't mutate the http function", function () {
            var startCount;
            startCount = http.plugins().length;
            http.use({});
            assert.equal(http.plugins().length, startCount);
        });
    });
    describe('bare', function () {
        it('creates a http function without plugins', function () {
            assert.equal(http.use({}).bare().plugins().length, 0);
        });
    });
});

describe('plugins', function () {
    describe('jsonparser', function () {
        it('is used for error responses', function (done) {
            http.use(plugins.jsonparser).get(testServerUrl + '/404', function (err, res) {
                assert.deepEqual(err.body, {
                    sad: 'panda'
                });
                done();
            });
        });
        it('parses json responses', function (done) {
            http.use(plugins.jsonparser).get(testServerUrl + '/getjson', function (err, res) {
                assert.deepEqual(res.body, {
                    hello: 'world'
                });
                done();
            });
        });
        function parseWithContentType(ct, isJsonType) {
            var res;
            if (isJsonType == null) {
                isJsonType = true;
            }
            res = {
                contentType: ct,
                body: '{"hello": "world"}'
            };
            plugins.jsonparser.processResponse(res);
            if (isJsonType) {
                assert.deepEqual(res.body, {
                    hello: 'world'
                });
            } else {
                assert.equal(res.body, '{"hello": "world"}');
            }
        }
        it('honors the content type', function () {
            parseWithContentType('application/json');
            parseWithContentType('text/json');
            parseWithContentType('text/json');
            parseWithContentType('text/something+json');
            parseWithContentType('application/json; charset=utf-8');
            parseWithContentType('application/jsonp', false);
        });
    });
    describe('jsonrequest', function () {
        it('adds an Accept header', function () {
            var req = new http.Request();
            plugins.jsonrequest.processRequest(req);
            assert.equal(req.header('Accept'), 'application/json');
        });
        it('formats the request', function () {
            var req;
            req = new http.Request({
                body: {
                    hello: 'world'
                }
            });
            plugins.jsonrequest.processRequest(req);
            assert.equal(req.body, '{"hello":"world"}');
            assert.equal(req.header('Content-Type'), 'application/json');
        });
    });
    describe('setprotocol', function () {
        var currentProtocol = typeof window === 'undefined' ? 'http:' : window.location.protocol,
            otherProtocol = currentProtocol === 'http:' ? 'https:' : 'http:';

        it('uses http by default', function () {
            var req = new http.Request('//example.com/');
            plugins.setprotocol.processRequest(req);
            assert.equal(req.url, 'http://example.com/');
        });
        it('can be invoked to provide options', function () {
            var req = new http.Request('//example.com/');
            plugins
                .setprotocol({defaultProtocol: currentProtocol})
                .processRequest(req);
            assert.equal(req.url, currentProtocol + '//example.com/');
        });
        it('can override protocols', function () {
            var req = new http.Request(otherProtocol + '//example.com/');
            plugins
                .setprotocol({defaultProtocol: currentProtocol, override: true})
                .processRequest(req);
            assert.equal(req.url, currentProtocol + '//example.com/');
        });
    });
});
<|endoftext|>


<commit_before>'use strict';

module.exports = function (grunt) {

    // Load grunt tasks automatically
    require('load-grunt-tasks')(grunt, {
        pattern: [
            'assemble',
            'grunt-*'
        ]
    });

    // configurable paths
    var config = {
        app: 'app',
        dist: 'dist'
    };

    grunt.initConfig({

        // Project settings
        config: config,

        // Watches files for changes and runs tasks based on the changed files
        watch: {
            gruntfile: {
                files: ['Gruntfile.js'],
                tasks: ['wiredep']
            },
            bower: {
                files: ['bower.json'],
                tasks: ['wiredep']
            },
            livereload: {
                options: {
                    livereload: '<%= connect.options.livereload %>'
                },
                files: [
                    '{.tmp,<%= config.app %>}/{,**/}*.{html,css,js,json}',
                    '<%= config.app %>/media/{,**/}*.{png,jpg,jpeg,gif,webp,svg}',
                    '!<%= config.app %>/media/icons/*'
                ]
            },
            assemble: {
                files: [
                    '<%= config.app %>/data/{,*/}*.{yml,json}',
                    '<%= config.app %>/{layouts,pages,partials}/{,**/}*.hbs'
                ],
                tasks: [
                    'assemble',
                    'wiredep'
                ]
            },
            js: {
                files: [
                    '<%= config.app %>/scripts/{,*/}*.js'
                ],
                tasks: ['jshint'],
                options: {
                    spawn: false,
                },
            },
            sass: {
                files: ['<%= config.app %>/styles/{,*/}*.scss'],
                tasks: [
                    'sass:server',
                    'autoprefixer'
                ]
            },
            webfont: {
                files: [
                    '<%= config.app %>/media/icons/*.svg'
                ],
                tasks: ['webfont']
            },
        },

        // The actual grunt server settings
        connect: {
            options: {
                port: 9000,
                open: true,
                livereload: 0,
                hostname: 'localhost'
            },
            livereload: {
                options: {
                    middleware: function(connect) {
                        return [
                            connect.static('.tmp'),
                            connect().use('/bower_components', connect.static('./bower_components')),
                            connect.static(config.app)
                        ];
                    }
                }
            },
            dist: {
                options: {
                    base: '<%= config.dist %>',
                    livereload: false
                }
            }
        },

        // Empties folders to start fresh
        clean: {
            dist: {
                files: [{
                    dot: true,
                    src: [
                        '.tmp',
                        '.sass-cache',
                        '<%= config.dist %>/*',
                        '!<%= config.dist %>/.git*'
                    ]
                }]
            },
            server: '.tmp'
        },

        // Generate pages
        assemble: {
            options: {
                layout: 'default.hbs',
                layoutdir: '<%= config.app %>/layouts',
                assets: '<%= config.app %>',
                data: '<%= config.app %>/data/*.{json,yml}',
                partials: '<%= config.app %>/partials/{,*/}*.hbs'
            },
            pages: {
                files: [{
                    expand: true,
                    cwd: '<%= config.app %>/pages',
                    dest: '.tmp',
                    src: '{,**/}*.hbs'
                }]
            }
        },

        // Compile Sass
        sass: {
            options: {
                sourceMap: true,
                includePaths: [
                    '<%= config.app %>',
                    'bower_components'
                ]
            },
            server: {
                files: [{
                    expand: true,
                    cwd: '<%= config.app %>',
                    src: ['{,*/}*.scss'],
                    dest: '.tmp',
                    ext: '.css'
                }]
            }
        },

        // Add vendor prefixed styles
        autoprefixer: {
            options: {
                browsers: ['> 1%', 'last 2 versions', 'Firefox ESR', 'Opera 12.1'],
                map: {
                    prev: '.tmp/styles/'
                }
            },
            dist: {
                files: [{
                    expand: true,
                    cwd: '.tmp/',
                    src: '{,*/}*.css',
                    dest: '.tmp/'
                }]
            }
        },

        // Generate custom icon font
        webfont: {
            icons: {
                src: '<%= config.app %>/media/icons/*.svg',
                dest: '.tmp/media/fonts',
                destCss: '.tmp/',
                options: {
                    engine: 'node',
                    hashes: false,
                    stylesheet: 'css',
                    relativeFontPath: '/media/fonts',
                    templateOptions: {
                        classPrefix: 'icon-',
                    }
                }
            }
        },

        // Reads HTML for usemin blocks to enable smart builds that automatically
        // concat, minify and revision files. Creates configurations in memory so
        // additional tasks can operate on them
        useminPrepare: {
            options: {
                dest: '<%= config.dist %>'
            },
            html: '.tmp/index.html'
        },

        // Performs rewrites based on rev and the useminPrepare configuration
        usemin: {
            options: {
                assetsDirs: ['<%= config.dist %>']
            },
            html: ['<%= config.dist %>/{,**/}*.html'],
            css: ['<%= config.dist %>/{,*/}*.css']
        },

        // The following *-min tasks produce minified files in the dist folder
        imagemin: {
            dist: {
                files: [{
                    expand: true,
                    cwd: '<%= config.app %>/media',
                    src: '{,**/}*.{png,jpg,jpeg}',
                    dest: '<%= config.dist %>/media'
                }]
            }
        },
        svgmin: {
            dist: {
                files: [{
                    expand: true,
                    cwd: '<%= config.app %>/media',
                    src: '{,**/}*.svg',
                    dest: '<%= config.dist %>/media'
                }]
            }
        },
        htmlmin: {
            dist: {
                options: {
                    collapseBooleanAttributes: true,
                    collapseWhitespace: true,
                    conservativeCollapse: true,
                    removeAttributeQuotes: true,
                    removeCommentsFromCDATA: true,
                    removeEmptyAttributes: true,
                    removeOptionalTags: true,
                    removeRedundantAttributes: false,
                    useShortDoctype: true
                },
                files: [{
                    expand: true,
                    cwd: '<%= config.dist %>',
                    src: '{,**/}*.html',
                    dest: '<%= config.dist %>'
                }]
            }
        },

        // Put files not handled in other tasks here
        copy: {
            dist: {
                files: [{
                    expand: true,
                    dot: true,
                    cwd: '<%= config.app %>',
                    dest: '<%= config.dist %>',
                    src: [
                        '.htaccess',
                        '*.txt',
                        'media/{,**/}*.{webp,gif,ico}',
                        'media/fonts/*'
                    ]
                },{
                    expand: true,
                    dot: true,
                    cwd: '<%= config.app %>',
                    dest: '<%= config.dist %>',
                    flatten: true,
                    src: [
                        'media/favicon.ico'
                    ]
                },{
                    expand: true,
                    dot: true,
                    cwd: '.tmp',
                    dest: '<%= config.dist %>',
                    src: [
                        'media/fonts/*',
                    ]
                },{
                    expand: true,
                    dot: true,
                    cwd: '.tmp',
                    dest: '<%= config.dist %>',
                    src: [
                        '{,**/}*.html',
                        '!icons.html'
                    ]
                },{
                    expand: true,
                    dot: true,
                    cwd: 'bower_components',
                    dest: '<%= config.dist %>/bower_components',
                    src: [
                        'bootstrap-sass-official/assets/fonts/bootstrap/*'
                    ]
                }]
            }
        },

        // Automatically inject Bower components into the HTML file
        wiredep: {
            app: {
                src: '.tmp/{,**/}*.html',
                ignorePath: '../',
                exclude: []
            }
        },

        // Make sure code styles are up to par and there are no obvious mistakes
        jshint: {
            options: {
                jshintrc: '.jshintrc',
                force: true,
                reporter: require('jshint-stylish')
            },
            all: [
                'Gruntfile.js',
                '<%= config.app %>/scripts/{,*/}*.js'
            ]
        },

        // Pick an unused port for livereload
        portPick: {
            livereload: {
                targets: ['connect.options.livereload']
            }
        },

        // Renames files for browser caching purposes
        filerev: {
            dist: {
                src: [
                    '<%= config.dist %>/scripts/{,*/}*.js',
                    '<%= config.dist %>/styles/{,*/}*.css',
                    '<%= config.dist %>/{media,bower_components}/{,**/}*.{png,jpg,jpeg,gif,webp,svg,eot,ttf,woff,woff2}'
                ]
            }
        },

        // Run some tasks in parallel to speed up build process
        concurrent: {
            server: [
                'webfont',
                'sass:server',
            ],
            dist: [
                'webfont',
                'sass',
                'imagemin',
                'svgmin'
            ]
        },

    });

    grunt.registerTask('serve', function (target) {
        if (target === 'dist') {
            return grunt.task.run(['build', 'connect:dist:keepalive']);
        }

        grunt.task.run([
            'clean:server',
            'assemble',
            'wiredep',
            'portPick:livereload',
            'concurrent:server',
            'autoprefixer',
            'connect:livereload',
            'watch'
        ]);
    });

    grunt.registerTask('test', [
        'jshint'
    ]);

    grunt.registerTask('build', [
        'clean:dist',
        'assemble',
        'wiredep',
        'useminPrepare',
        'concurrent:dist',
        'autoprefixer',
        'concat',
        'cssmin',
        'uglify',
        'copy:dist',
        'filerev',
        'usemin',
        'htmlmin'
    ]);

    grunt.registerTask('default', [
        'jshint',
        'build'
    ]);
};
<commit_msg>Enabling portPick for connect starting at port 9000<commit_after>'use strict';

module.exports = function (grunt) {

    // Load grunt tasks automatically
    require('load-grunt-tasks')(grunt, {
        pattern: [
            'assemble',
            'grunt-*'
        ]
    });

    // configurable paths
    var config = {
        app: 'app',
        dist: 'dist'
    };

    grunt.initConfig({

        // Project settings
        config: config,

        // Watches files for changes and runs tasks based on the changed files
        watch: {
            gruntfile: {
                files: ['Gruntfile.js'],
                tasks: ['wiredep']
            },
            bower: {
                files: ['bower.json'],
                tasks: ['wiredep']
            },
            livereload: {
                options: {
                    livereload: '<%= connect.options.livereload %>'
                },
                files: [
                    '{.tmp,<%= config.app %>}/{,**/}*.{html,css,js,json}',
                    '<%= config.app %>/media/{,**/}*.{png,jpg,jpeg,gif,webp,svg}',
                    '!<%= config.app %>/media/icons/*'
                ]
            },
            assemble: {
                files: [
                    '<%= config.app %>/data/{,*/}*.{yml,json}',
                    '<%= config.app %>/{layouts,pages,partials}/{,**/}*.hbs'
                ],
                tasks: [
                    'assemble',
                    'wiredep'
                ]
            },
            js: {
                files: [
                    '<%= config.app %>/scripts/{,*/}*.js'
                ],
                tasks: ['jshint'],
                options: {
                    spawn: false,
                },
            },
            sass: {
                files: ['<%= config.app %>/styles/{,*/}*.scss'],
                tasks: [
                    'sass:server',
                    'autoprefixer'
                ]
            },
            webfont: {
                files: [
                    '<%= config.app %>/media/icons/*.svg'
                ],
                tasks: ['webfont']
            },
        },

        // The actual grunt server settings
        connect: {
            options: {
                port: 0,
                open: true,
                livereload: 0,
                hostname: 'localhost'
            },
            livereload: {
                options: {
                    middleware: function(connect) {
                        return [
                            connect.static('.tmp'),
                            connect().use('/bower_components', connect.static('./bower_components')),
                            connect.static(config.app)
                        ];
                    }
                }
            },
            dist: {
                options: {
                    base: '<%= config.dist %>',
                    livereload: false
                }
            }
        },

        // Empties folders to start fresh
        clean: {
            dist: {
                files: [{
                    dot: true,
                    src: [
                        '.tmp',
                        '.sass-cache',
                        '<%= config.dist %>/*',
                        '!<%= config.dist %>/.git*'
                    ]
                }]
            },
            server: '.tmp'
        },

        // Generate pages
        assemble: {
            options: {
                layout: 'default.hbs',
                layoutdir: '<%= config.app %>/layouts',
                assets: '<%= config.app %>',
                data: '<%= config.app %>/data/*.{json,yml}',
                partials: '<%= config.app %>/partials/{,*/}*.hbs'
            },
            pages: {
                files: [{
                    expand: true,
                    cwd: '<%= config.app %>/pages',
                    dest: '.tmp',
                    src: '{,**/}*.hbs'
                }]
            }
        },

        // Compile Sass
        sass: {
            options: {
                sourceMap: true,
                includePaths: [
                    '<%= config.app %>',
                    'bower_components'
                ]
            },
            server: {
                files: [{
                    expand: true,
                    cwd: '<%= config.app %>',
                    src: ['{,*/}*.scss'],
                    dest: '.tmp',
                    ext: '.css'
                }]
            }
        },

        // Add vendor prefixed styles
        autoprefixer: {
            options: {
                browsers: ['> 1%', 'last 2 versions', 'Firefox ESR', 'Opera 12.1'],
                map: {
                    prev: '.tmp/styles/'
                }
            },
            dist: {
                files: [{
                    expand: true,
                    cwd: '.tmp/',
                    src: '{,*/}*.css',
                    dest: '.tmp/'
                }]
            }
        },

        // Generate custom icon font
        webfont: {
            icons: {
                src: '<%= config.app %>/media/icons/*.svg',
                dest: '.tmp/media/fonts',
                destCss: '.tmp/',
                options: {
                    engine: 'node',
                    hashes: false,
                    stylesheet: 'css',
                    relativeFontPath: '/media/fonts',
                    templateOptions: {
                        classPrefix: 'icon-',
                    }
                }
            }
        },

        // Reads HTML for usemin blocks to enable smart builds that automatically
        // concat, minify and revision files. Creates configurations in memory so
        // additional tasks can operate on them
        useminPrepare: {
            options: {
                dest: '<%= config.dist %>'
            },
            html: '.tmp/index.html'
        },

        // Performs rewrites based on rev and the useminPrepare configuration
        usemin: {
            options: {
                assetsDirs: ['<%= config.dist %>']
            },
            html: ['<%= config.dist %>/{,**/}*.html'],
            css: ['<%= config.dist %>/{,*/}*.css']
        },

        // The following *-min tasks produce minified files in the dist folder
        imagemin: {
            dist: {
                files: [{
                    expand: true,
                    cwd: '<%= config.app %>/media',
                    src: '{,**/}*.{png,jpg,jpeg}',
                    dest: '<%= config.dist %>/media'
                }]
            }
        },
        svgmin: {
            dist: {
                files: [{
                    expand: true,
                    cwd: '<%= config.app %>/media',
                    src: '{,**/}*.svg',
                    dest: '<%= config.dist %>/media'
                }]
            }
        },
        htmlmin: {
            dist: {
                options: {
                    collapseBooleanAttributes: true,
                    collapseWhitespace: true,
                    conservativeCollapse: true,
                    removeAttributeQuotes: true,
                    removeCommentsFromCDATA: true,
                    removeEmptyAttributes: true,
                    removeOptionalTags: true,
                    removeRedundantAttributes: false,
                    useShortDoctype: true
                },
                files: [{
                    expand: true,
                    cwd: '<%= config.dist %>',
                    src: '{,**/}*.html',
                    dest: '<%= config.dist %>'
                }]
            }
        },

        // Put files not handled in other tasks here
        copy: {
            dist: {
                files: [{
                    expand: true,
                    dot: true,
                    cwd: '<%= config.app %>',
                    dest: '<%= config.dist %>',
                    src: [
                        '.htaccess',
                        '*.txt',
                        'media/{,**/}*.{webp,gif,ico}',
                        'media/fonts/*'
                    ]
                },{
                    expand: true,
                    dot: true,
                    cwd: '<%= config.app %>',
                    dest: '<%= config.dist %>',
                    flatten: true,
                    src: [
                        'media/favicon.ico'
                    ]
                },{
                    expand: true,
                    dot: true,
                    cwd: '.tmp',
                    dest: '<%= config.dist %>',
                    src: [
                        'media/fonts/*',
                    ]
                },{
                    expand: true,
                    dot: true,
                    cwd: '.tmp',
                    dest: '<%= config.dist %>',
                    src: [
                        '{,**/}*.html',
                        '!icons.html'
                    ]
                },{
                    expand: true,
                    dot: true,
                    cwd: 'bower_components',
                    dest: '<%= config.dist %>/bower_components',
                    src: [
                        'bootstrap-sass-official/assets/fonts/bootstrap/*'
                    ]
                }]
            }
        },

        // Automatically inject Bower components into the HTML file
        wiredep: {
            app: {
                src: '.tmp/{,**/}*.html',
                ignorePath: '../',
                exclude: []
            }
        },

        // Make sure code styles are up to par and there are no obvious mistakes
        jshint: {
            options: {
                jshintrc: '.jshintrc',
                force: true,
                reporter: require('jshint-stylish')
            },
            all: [
                'Gruntfile.js',
                '<%= config.app %>/scripts/{,*/}*.js'
            ]
        },

        // Pick an unused port for livereload
        portPick: {
            connect: {
                options: {
                    port: 9000
                },
                targets: ['connect.options.port']
            },
            livereload: {
                targets: ['connect.options.livereload']
            }
        },

        // Renames files for browser caching purposes
        filerev: {
            dist: {
                src: [
                    '<%= config.dist %>/scripts/{,*/}*.js',
                    '<%= config.dist %>/styles/{,*/}*.css',
                    '<%= config.dist %>/{media,bower_components}/{,**/}*.{png,jpg,jpeg,gif,webp,svg,eot,ttf,woff,woff2}'
                ]
            }
        },

        // Run some tasks in parallel to speed up build process
        concurrent: {
            server: [
                'webfont',
                'sass:server',
            ],
            dist: [
                'webfont',
                'sass',
                'imagemin',
                'svgmin'
            ]
        },

    });

    grunt.registerTask('serve', function (target) {
        if (target === 'dist') {
            return grunt.task.run(['build', 'connect:dist:keepalive']);
        }

        grunt.task.run([
            'clean:server',
            'assemble',
            'wiredep',
            'portPick',
            'concurrent:server',
            'autoprefixer',
            'connect:livereload',
            'watch'
        ]);
    });

    grunt.registerTask('test', [
        'jshint'
    ]);

    grunt.registerTask('build', [
        'clean:dist',
        'assemble',
        'wiredep',
        'useminPrepare',
        'concurrent:dist',
        'autoprefixer',
        'concat',
        'cssmin',
        'uglify',
        'copy:dist',
        'filerev',
        'usemin',
        'htmlmin'
    ]);

    grunt.registerTask('default', [
        'jshint',
        'build'
    ]);
};
<|endoftext|>


<commit_before>

module('jaylist module');

test('create a list', function () {
		expect(1);

		var xs = list();
		ok(xs, 'list created');
});

test('add: add items to a list', function () {
		expect(6);

		var xs = list();
		var ys = list();

		deepEqual(xs.add('string', 'strval'), 'strval', 'Added ("string", "strval").');
		deepEqual(xs.add('number', 18593214), 18593214, 'Added ("number", 18593214).');
		deepEqual(xs.add('boolean',   false),    false, 'Added ("boolean",   false).');
		deepEqual(xs.add('object',	     ys),       ys, 'Added ("object",       ys).');
		deepEqual(xs.add('array', [1,2,3,4]), [1,2,3,4],'Added ("array", [1,2,3,4]).');
		deepEqual(xs.add('array', [4,3,2,1]), [4,3,2,1],'Added ("array", [4,3,2,1]).');
});

test('hasKey: check a list for keys', function () {
		expect(6);

		var xs = list();
		var ys = list();

		xs.add('string', 'strval');
		xs.add('number', 18593214);
		xs.add('boolean,    false);
		xs.add('object',       ys);
		xs.add('array', [1,2,3,4]);

		ok(xs.hasKey('string'), 'Has key "string".');
		ok(xs.hasKey('number'), 'Has key "number".');
		ok(xs.hasKey('boolean'),'Has key "boolean".');
		ok(xs.hasKey('object'), 'Has key "object".');
		ok(xs.hasKey('array'),  'Has key "array".');
		ok(!xs.hasKey('missing'),'Missing key "missing".');
});

test('get: retrieve values using a key', function () {
		expect(5);

		var xs = list();
		var ys = list();

		xs.add('string', 'strval');
		xs.add('number', 18593214);
		xs.add('boolean,    false);
		xs.add('object',       ys);
		xs.add('array', [1,2,3,4]);

		deepEqual(xs.get('string'), 'strval', 'Retrieved the value of "string".');
		deepEqual(xs.get('number'), 18593214, 'Retrieved the value of "number".');
		deepEqual(xs.get('boolean'),false,    'Retrieved the value of "boolean".');
		deepEqual(xs.get('object'), ys,       'Retrieved the value of "object".');
		deepEqual(xs.get('array'),  [1,2,3,4],'Retrieved the value of "array".');
});

test('remove: remove values using a key', function () {
		expect(10);

		var xs = list();
		var ys = list();

		xs.add('string', 'strval');
		xs.add('number', 18593214);
		xs.add('boolean,    false);
		xs.add('object',       ys);
		xs.add('array', [1,2,3,4]);

		deepEqual(xs.remove('string'), 'strval', 'Removed the value of "string".');
		ok(!xs.get('string'), '"string" removed.');

		deepEqual(xs.remove('number'), 18593214, 'Removed the value of "number".');
		ok(!xs.get('number'), '"number" removed.');

		deepEqual(xs.remove('boolean'),false,    'Removed the value of "boolean".');
		ok(!xs.get('boolean'), '"boolean" removed.');

		deepEqual(xs.remove('object'), ys,       'Removed the value of "object".');
		ok(!xs.get('object'), '"object" removed.');

		deepEqual(xs.remove('array'),  [1,2,3,4],'Removed the value of "array".');
		ok(!xs.get('array'), '"array" removed.');
});

test('keys: return the keys in the list', function () {
		expect(1);

		var xs = list();
		var ys = list();

		xs.add('string', 'strval');
		xs.add('number', 18593214);
		xs.add('boolean,    false);
		xs.add('object',       ys);
		xs.add('array', [1,2,3,4]);

		deepEqual(xs.keys(), ['string', 'number', 'boolean', 'object', 'array'], 'Popped the value of "string".');
});

test('values: return values in the list, function () {
		expect(1);

		var xs = list();
		var ys = list();

		xs.add('string', 'strval');
		xs.add('number', 18593214);
		xs.add('boolean,    false);
		xs.add('object',       ys);
		xs.add('array', [1,2,3,4]);

		deepEqual(xs.values(), ['strval', 18593214, false, ys, [1,2,3,4]], 'Popped the value of "string".');
});

test('items: return (key, value) pairs in the list, function () {
		expect(1);

		var xs = list();
		var ys = list();

		xs.add('string', 'strval');
		xs.add('number', 18593214);
		xs.add('boolean,    false);
		xs.add('object',       ys);
		xs.add('array', [1,2,3,4]);

		deepEqual(xs.values(), [['string', 'strval'], ['number', 18593214],
														['boolean',   false], ['object',       ys],
														['array', [1,2,3,4]]], 'Popped the value of "string".');
});

test('len: return the number of items in the list', function () {
		expect(2);

		var xs = list();
		var ys = list();

		xs.add('string', 'strval');
		xs.add('number', 18593214);
		xs.add('boolean,    false);
		xs.add('object',       ys);
		xs.add('array', [1,2,3,4]);

		equal(xs.len(), 5, 'List of length 5.');
		equal(ys.len(), 0, 'List of length 0.');
});

test('clear: empty the list', function () {
		expect(2);

		var xs = list();
		var ys = list();

		xs.add('string', 'strval');
		xs.add('number', 18593214);
		xs.add('boolean,    false);
		xs.add('object',       ys);
		xs.add('array', [1,2,3,4]);

		equal(xs.len(), 5, 'List of length 5.');
		xs.clear();
		equal(xs.len(), 0, 'List of length 0.');
});

test('update: add the contents of one list to another', function () {
		expect(1);

		var xs = list();
		var ys = list();

		xs.add('string', 'strval');
		xs.add('number', 18593214);
		xs.add('boolean,    false);
		xs.add('object',       ys);
		xs.add('array', [1,2,3,4]);

		ys.update(xs);
		deepEqual(xs, ys, 'List updated.');
});

test('copy: return a deep copy of the list', function () {
		expect(1);

		var ws;
		var xs = list();
		var ys = list();

		xs.add('string', 'strval');
		xs.add('number', 18593214);
		xs.add('boolean,    false);
		xs.add('object',       ys);
		xs.add('array', [1,2,3,4]);

		ws = xs.copy();
		deepEqual(xs, ws, 'List copied.');
});

test('each: execute a callback for each key in the list', function () {
		expect(1);

		var xs = list();
		var ys = list();

		xs.add('string', 'strval');
		xs.add('number', 18593214);
		xs.add('boolean,    false);
		xs.add('object',       ys);
		xs.add('array', [1,2,3,4]);

		xs.each(xs.remove);
		equal(xs.len(), 0, 'Callback `remove` executed.');
});

test('next: loop-style iteration', function () {
		expect(5);

		var xs = list();
		var ys = list();

		xs.add('string', 'strval');
		xs.add('number', 18593214);
		xs.add('boolean,    false);
		xs.add('object',       ys);
		xs.add('array', [1,2,3,4]);

		deepEqual(xs.next(),  'strval', '`string` retrieved.');
		deepEqual(xs.next(),  18593214, '`number` retrieved.');
		deepEqual(xs.next(),     false, '`boolean` retrieved.');
		deepEqual(xs.next(),        ys, '`object` retrieved.');
		deepEqual(xs.next(), [1,2,3,4], '`array` retrieved.');
		deepEqual(xs.next(), undefined, 'iteration complete.');
});

test('object: returns the list in object-literal form', function () {
		expect(1);

		var xs = list();
		var ys = list();

		xs.add('string', 'strval');
		xs.add('number', 18593214);
		xs.add('boolean,    false);
		xs.add('object',       ys);
		xs.add('array', [1,2,3,4]);

		deepEqual(xs.object(),  {'string' : 'strval',
														 'number' : 18593214,
														 'boolean': false,
														 'object' : ys,
														 'array'  : [1,2,3,4]}, 'Object retrieved.');
});

test('isEqual: list equality', function () {
		expect(1);

		var xs = list();
		var ys = list();

		xs.add('string', 'strval');
		xs.add('number', 18593214);
		xs.add('boolean,    false);
		xs.add('object',       ys);
		xs.add('array', [1,2,3,4]);

		ys.add('string', 'strval');
		ys.add('number', 18593214);
		ys.add('boolean,    false);
		ys.add('object',       ys);
		ys.add('array', [1,2,3,4]);

		ok(isEqual(xs, ys), 'Lists are equal.');
});

<commit_msg>fixed typos & broken tests<commit_after>// Jaylist tests.

module('jaylist module');

test('create a list', function () {
		expect(1);

		var xs = list();
		ok(xs, 'list created');
});

test('add: add items to a list', function () {
		expect(6);

		var xs = list();
		var ys = list();

		deepEqual(xs.add('string', 'strval'), 'strval', 'Added ("string", "strval").');
		deepEqual(xs.add('number', 18593214), 18593214, 'Added ("number", 18593214).');
		deepEqual(xs.add('boolean',   false),    false, 'Added ("boolean",   false).');
		deepEqual(xs.add('object',	     ys),       ys, 'Added ("object",       ys).');
		deepEqual(xs.add('array', [1,2,3,4]), [1,2,3,4],'Added ("array", [1,2,3,4]).');
		deepEqual(xs.add('array', [4,3,2,1]), [4,3,2,1],'Added ("array", [4,3,2,1]).');
});

test('hasKey: check a list for keys', function () {
		expect(6);

		var xs = list();
		var ys = list();

		xs.add('string', 'strval');
		xs.add('number', 18593214);
		xs.add('boolean',   false);
		xs.add('object',       ys);
		xs.add('array', [1,2,3,4]);

		ok(xs.hasKey('string'), 'Has key "string".');
		ok(xs.hasKey('number'), 'Has key "number".');
		ok(xs.hasKey('boolean'),'Has key "boolean".');
		ok(xs.hasKey('object'), 'Has key "object".');
		ok(xs.hasKey('array'),  'Has key "array".');
		ok(!xs.hasKey('missing'),'Missing key "missing".');
});

test('get: retrieve values using a key', function () {
		expect(5);

		var xs = list();
		var ys = list();

		xs.add('string', 'strval');
		xs.add('number', 18593214);
		xs.add('boolean',    false);
		xs.add('object',       ys);
		xs.add('array', [1,2,3,4]);

		deepEqual(xs.get('string'), 'strval', 'Retrieved the value of "string".');
		deepEqual(xs.get('number'), 18593214, 'Retrieved the value of "number".');
		deepEqual(xs.get('boolean'),false,    'Retrieved the value of "boolean".');
		deepEqual(xs.get('object'), ys,       'Retrieved the value of "object".');
		deepEqual(xs.get('array'),  [1,2,3,4],'Retrieved the value of "array".');
});

test('remove: remove values using a key', function () {
		expect(10);

		var xs = list();
		var ys = list();

		xs.add('string', 'strval');
		xs.add('number', 18593214);
		xs.add('boolean',    false);
		xs.add('object',       ys);
		xs.add('array', [1,2,3,4]);

		deepEqual(xs.remove('string'), 'strval', 'Removed the value of "string".');
		ok(!xs.get('string'), '"string" removed.');

		deepEqual(xs.remove('number'), 18593214, 'Removed the value of "number".');
		ok(!xs.get('number'), '"number" removed.');

		deepEqual(xs.remove('boolean'),false,    'Removed the value of "boolean".');
		ok(!xs.get('boolean'), '"boolean" removed.');

		deepEqual(xs.remove('object'), ys,       'Removed the value of "object".');
		ok(!xs.get('object'), '"object" removed.');

		deepEqual(xs.remove('array'),  [1,2,3,4],'Removed the value of "array".');
		ok(!xs.get('array'), '"array" removed.');
});

test('keys: return the keys in the list', function () {
		expect(1);

		var xs = list();
		var ys = list();

		xs.add('string', 'strval');
		xs.add('number', 18593214);
		xs.add('boolean',    false);
		xs.add('object',       ys);
		xs.add('array', [1,2,3,4]);

		deepEqual(xs.keys(), ['string', 'number', 'boolean', 'object', 'array'], 'Popped the value of "string".');
});

test('values: return values in the list', function () {
		expect(1);

		var xs = list();
		var ys = list();

		xs.add('string', 'strval');
		xs.add('number', 18593214);
		xs.add('boolean',    false);
		xs.add('object',       ys);
		xs.add('array', [1,2,3,4]);

		deepEqual(xs.values(), ['strval', 18593214, false, ys, [1,2,3,4]], 'Popped the value of "string".');
});

test('items: return (key, value) pairs in the list', function () {
		expect(1);

		var xs = list();

		xs.add('string', 'strval');
		xs.add('number', 18593214);
		xs.add('boolean',    false);
		xs.add('array', [1,2,3,4]);

		deepEqual(xs.items(), [['string', 'strval'], ['number', 18593214],
													 ['boolean',   false], ['array', [1,2,3,4]]], 'Retrieved [key, value] list.');
});

test('len: return the number of items in the list', function () {
		expect(2);

		var xs = list();
		var ys = list();

		xs.add('string', 'strval');
		xs.add('number', 18593214);
		xs.add('boolean',    false);
		xs.add('object',       ys);
		xs.add('array', [1,2,3,4]);

		equal(xs.len(), 5, 'List of length 5.');
		equal(ys.len(), 0, 'List of length 0.');
});

test('clear: empty the list', function () {
		expect(2);

		var xs = list();
		var ys = list();

		xs.add('string', 'strval');
		xs.add('number', 18593214);
		xs.add('boolean',    false);
		xs.add('object',       ys);
		xs.add('array', [1,2,3,4]);

		equal(xs.len(), 5, 'List of length 5.');
		xs.clear();
		equal(xs.len(), 0, 'List of length 0.');
});

test('update: add the contents of one list to another', function () {
		expect(1);

		var xs = list();
		var ys = list();
		var zs = list();

		xs.add('string', 'strval');
		xs.add('number', 18593214);
		xs.add('boolean',    false);
		xs.add('object',       zs);
		xs.add('array', [1,2,3,4]);

		ys.update(xs);
		deepEqual(xs._table, ys._table, 'List updated.');
});

test('copy: return a deep copy of the list', function () {
		expect(1);

		var ws;
		var xs = list();
		var ys = list();

		xs.add('string', 'strval');
		xs.add('number', 18593214);
		xs.add('boolean',    false);
		xs.add('array', [1,2,3,4]);

		ws = xs.copy();
		deepEqual(xs._table, ws._table, 'List copied.');
});

test('each: execute a callback for each key in the list', function () {
		expect(1);

		var xs = list();
		var ys = list();

		xs.add('string', 'strval');
		xs.add('number', 18593214);
		xs.add('boolean',    false);
		xs.add('object',       ys);
		xs.add('array', [1,2,3,4]);

		xs.each(function(key){xs.remove(key);});
		equal(xs.len(), 0, 'Callback `remove` executed.');
});

test('next: loop-style iteration', function () {
		expect(6);

		var xs = list();
		var ys = list();

		xs.add('string', 'strval');
		xs.add('number', 18593214);
		xs.add('boolean',    false);
		xs.add('object',       ys);
		xs.add('array', [1,2,3,4]);

		deepEqual(xs.next(), 'string', '`string` retrieved.');
		deepEqual(xs.next(), 'number', '`number` retrieved.');
		deepEqual(xs.next(), 'boolean', '`boolean` retrieved.');
		deepEqual(xs.next(), 'object', '`object` retrieved.');
		deepEqual(xs.next(), 'array', '`array` retrieved.');
		deepEqual(xs.next(), undefined, 'iteration complete.');
});

test('object: returns the list in object-literal form', function () {
		expect(1);

		var xs = list();
		var ys = list();

		xs.add('string', 'strval');
		xs.add('number', 18593214);
		xs.add('boolean',    false);
		xs.add('object',       ys);
		xs.add('array', [1,2,3,4]);

		deepEqual(xs.object(),  {'string' : 'strval',
														 'number' : 18593214,
														 'boolean': false,
														 'object' : ys,
														 'array'  : [1,2,3,4]}, 'Object retrieved.');
});

test('isEqual: list equality', function () {
		expect(2);

		var xs = list();
		var ys = list();
		var zs = list();

		xs.add('string', 'strval');
		xs.add('number', 18593214);
		xs.add('boolean',    false);
		xs.add('object',       zs);
		xs.add('array', [1,2,3,4]);

		ys.add('string', 'strval');
		ys.add('number', 18593214);
		ys.add('boolean',    false);
		ys.add('object',       zs);
		ys.add('array', [1,2,3,4]);

		ok(xs.isEqual(ys), 'Lists are equal.');
		ok(ys.isEqual(xs), 'Lists are equal.');
});

<|endoftext|>


<commit_before>/*!
 * Grunt file
 */

/*jshint node:true */
module.exports = function ( grunt ) {
	grunt.loadNpmTasks( 'grunt-banana-checker' );
	grunt.loadNpmTasks( 'grunt-contrib-clean' );
	grunt.loadNpmTasks( 'grunt-contrib-concat-sourcemaps' );
	grunt.loadNpmTasks( 'grunt-contrib-copy' );
	grunt.loadNpmTasks( 'grunt-contrib-csslint' );
	grunt.loadNpmTasks( 'grunt-contrib-cssmin' );
	grunt.loadNpmTasks( 'grunt-contrib-jshint' );
	grunt.loadNpmTasks( 'grunt-contrib-less' );
	grunt.loadNpmTasks( 'grunt-contrib-uglify' );
	grunt.loadNpmTasks( 'grunt-contrib-watch' );
	grunt.loadNpmTasks( 'grunt-csscomb' );
	grunt.loadNpmTasks( 'grunt-exec' );
	grunt.loadNpmTasks( 'grunt-file-exists' );
	grunt.loadNpmTasks( 'grunt-cssjanus' );
	grunt.loadNpmTasks( 'grunt-jscs' );
	grunt.loadNpmTasks( 'grunt-karma' );
	grunt.loadNpmTasks( 'grunt-svg2png' );
	grunt.loadTasks( 'build/tasks' );

	var modules = grunt.file.readJSON( 'build/modules.json' ),
		lessFiles = {
			raster: {},
			vector: {},
			mixed: {}
		},
		colorizeSvgFiles = {},
		originalLessFiles = {},
		concatCssFiles = {},
		rtlFiles = {
			'demos/styles/demo.rtl.css': 'demos/styles/demo.css'
		},
		minBanner = '/*! OOjs UI v<%= pkg.version %> | http://oojs.mit-license.org */';

	( function () {
		var distFile, target, module, moduleStyleFiles,
			path = require( 'path' );
		function processFile( fileName ) {
			if ( path.extname( fileName ) === '.json' ) {
				var
					theme = path.basename( path.dirname( fileName ) ),
					lessFileName = fileName.replace( /\.json$/, '.less' ).replace( /^src\//, 'dist/tmp/' );
				colorizeSvgFiles[ fileName ] = {
					options: grunt.file.readJSON( fileName ),
					srcDir: 'src/themes/' + theme,
					destDir: 'dist/themes/' + theme,
					// This should not be needed, but our dist directory structure is weird
					cssPrependPath: 'themes/' + theme + '/',
					destLessFile: lessFileName
				};
				return lessFileName;
			}
			return fileName;
		}
		for ( module in modules ) {
			if ( modules[ module ].styles ) {
				moduleStyleFiles = modules[ module ].styles;
				for ( target in lessFiles ) {
					distFile = 'dist/' + module + ( target !== 'mixed' ? '.' + target : '' ) + '.css';

					originalLessFiles[ distFile ] = moduleStyleFiles;
					lessFiles[ target ][ distFile ] = moduleStyleFiles.map( processFile );

					// Concat isn't doing much other than prepending the banner...
					concatCssFiles[ distFile ] = distFile;
					rtlFiles[ distFile.replace( '.css', '.rtl.css' ) ] = distFile;
				}
			}
		}
	}() );

	function strip( str ) {
		var path = require( 'path' );
		// http://gruntjs.com/configuring-tasks#building-the-files-object-dynamically
		// http://gruntjs.com/api/grunt.file#grunt.file.expandmapping
		return function ( dest, src ) {
			return path.join( dest, src.replace( str, '' ) );
		};
	}

	grunt.initConfig( {
		pkg: grunt.file.readJSON( 'package.json' ),

		// Build
		clean: {
			build: 'dist/*',
			tmp: 'dist/tmp'
		},
		fileExists: {
			src: ( function () {
				var distFile,
					files = modules[ 'oojs-ui' ].scripts.slice();

				for ( distFile in originalLessFiles ) {
					files.push.apply( files, originalLessFiles[ distFile ] );
				}

				return files;
			}() )
		},
		typos: {
			options: {
				typos: 'build/typos.json'
			},
			src: '{src,php}/**/*.{js,json,less,css}'
		},
		concat: {
			options: {
				banner: grunt.file.read( 'build/banner.txt' )
			},
			js: {
				files: {
					'dist/oojs-ui.js': modules[ 'oojs-ui' ].scripts,
					'dist/oojs-ui-apex.js': modules[ 'oojs-ui-apex' ].scripts,
					'dist/oojs-ui-mediawiki.js': modules[ 'oojs-ui-mediawiki' ].scripts
				}
			},
			css: {
				files: concatCssFiles
			}
		},

		// Build – Code
		uglify: {
			options: {
				banner: minBanner,
				sourceMap: true,
				sourceMapIncludeSources: true,
				report: 'gzip'
			},
			js: {
				expand: true,
				src: 'dist/*.js',
				ext: '.min.js',
				extDot: 'last'
			}
		},

		// Build – Styling
		less: {
			distRaster: {
				options: {
					ieCompat: true,
					report: 'gzip',
					modifyVars: {
						'oo-ui-distribution': 'raster',
						'oo-ui-default-image-ext': 'png'
					}
				},
				files: lessFiles.raster
			},
			distVector: {
				options: {
					ieCompat: false,
					report: 'gzip',
					modifyVars: {
						'oo-ui-distribution': 'vector',
						'oo-ui-default-image-ext': 'svg'
					}
				},
				files: lessFiles.vector
			},
			distMixed: {
				options: {
					ieCompat: false,
					report: 'gzip',
					modifyVars: {
						'oo-ui-distribution': 'mixed',
						'oo-ui-default-image-ext': 'png'
					}
				},
				files: lessFiles.mixed
			}
		},
		cssjanus: {
			dist: {
				options: {
					generateExactDuplicates: true
				},
				files: rtlFiles
			}
		},
		csscomb: {
			dist: {
				expand: true,
				src: 'dist/*.css'
			}
		},
		copy: {
			imagesCommon: {
				src: 'src/styles/images/*.cur',
				dest: 'dist/images/',
				expand: true,
				flatten: true
			},
			imagesApex: {
				src: 'src/themes/apex/images/**/*.{png,gif}',
				dest: 'dist/themes/apex/images/',
				expand: true,
				rename: strip( 'src/themes/apex/images/' )
			},
			imagesMediaWiki: {
				src: 'src/themes/mediawiki/images/**/*.{png,gif}',
				dest: 'dist/themes/mediawiki/images/',
				expand: true,
				rename: strip( 'src/themes/mediawiki/images/' )
			},
			i18n: {
				src: 'i18n/*.json',
				expand: true,
				dest: 'dist/'
			},
			jsduck: {
				src: '{lib,dist}/**/*',
				dest: 'docs/',
				expand: true
			}
		},
		colorizeSvg: colorizeSvgFiles,
		svg2png: {
			dist: {
				src: 'dist/{images,themes}/**/*.svg'
			}
		},
		cssmin: {
			options: {
				keepSpecialComments: 0,
				banner: minBanner,
				compatibility: 'ie8',
				report: 'gzip'
			},
			dist: {
				expand: true,
				src: 'dist/*.css',
				ext: '.min.css',
				extDot: 'last'
			}
		},

		// Lint – Code
		jshint: {
			options: {
				jshintrc: true
			},
			dev: [
				'*.js',
				'{build,demos,src,tests}/**/*.js',
				'!tests/JSPHP.test.js'
			]
		},
		jscs: {
			dev: [
				'<%= jshint.dev %>',
				'!demos/{dist,lib}/**'
			]
		},

		// Lint – Styling
		csslint: {
			options: {
				csslintrc: '.csslintrc'
			},
			all: [
				'{demos,src}/**/*.css',
				'!demos/{dist,lib}/**'
			]
		},

		// Lint – i18n
		banana: {
			all: 'i18n/'
		},

		// Test
		exec: {
			rubyTestSuiteGenerator: {
				command: 'ruby bin/testsuitegenerator.rb src php > tests/JSPHP-suite.json'
			},
			phpGenerateJSPHPForKarma: {
				command: 'php ../bin/generate-JSPHP-for-karma.php > JSPHP.test.js',
				cwd: './tests'
			}
		},
		karma: {
			options: {
				frameworks: [ 'qunit' ],
				files: [
					'lib/jquery.js',
					'lib/oojs.jquery.js',
					'dist/oojs-ui.js',
					'dist/oojs-ui-apex.js',
					'dist/oojs-ui-mediawiki.js',
					'tests/QUnit.assert.equalDomElement.js',
					'tests/**/*.test.js'
				],
				reporters: [ 'dots' ],
				singleRun: true,
				browserDisconnectTimeout: 5000,
				browserDisconnectTolerance: 2,
				autoWatch: false
			},
			main: {
				browsers: [ 'Chrome' ],
				preprocessors: {
					'dist/*.js': [ 'coverage' ]
				},
				reporters: [ 'dots', 'coverage' ],
				coverageReporter: { reporters: [
					{ type: 'html', dir: 'dist/coverage/' },
					{ type: 'text-summary', dir: 'dist/coverage/' }
				] }
			},
			other: {
				browsers: [ 'Firefox' ]
			}
		},

		// Development
		watch: {
			files: [
				'<%= jshint.dev %>',
				'<%= csslint.all %>',
				'{demos,src}/**/*.less',
				'.{csslintrc,jscsrc,jshintignore,jshintrc}'
			],
			tasks: 'quick-build'
		}
	} );

	grunt.registerTask( 'pre-test', function () {
		// Only create Source maps when doing a git-build for testing and local
		// development. Distributions for export should not, as the map would
		// be pointing at "../src".
		grunt.config.set( 'concat.js.options.sourceMap', true );
		grunt.config.set( 'concat.js.options.sourceMapStyle', 'link' );
	} );

	grunt.registerTask( 'pre-git-build', function () {
		var done = this.async();
		require( 'child_process' ).exec( 'git rev-parse HEAD', function ( err, stout, stderr ) {
			if ( !stout || err || stderr ) {
				grunt.log.err( err || stderr );
				done( false );
				return;
			}
			grunt.config.set( 'pkg.version', grunt.config( 'pkg.version' ) + '-pre (' + stout.slice( 0, 10 ) + ')' );
			grunt.verbose.writeln( 'Added git HEAD to pgk.version' );
			done();
		} );
	} );

	grunt.registerTask( 'build-code', [ 'concat:js', 'uglify' ] );
	grunt.registerTask( 'build-styling', [
		'colorizeSvg', 'less', 'copy:imagesCommon',
		'copy:imagesApex', 'copy:imagesMediaWiki', 'svg2png',
		'concat:css', 'cssjanus', 'csscomb', 'cssmin'
	] );
	grunt.registerTask( 'build-i18n', [ 'copy:i18n' ] );
	grunt.registerTask( 'build-tests', [ 'exec:rubyTestSuiteGenerator', 'exec:phpGenerateJSPHPForKarma' ] );
	grunt.registerTask( 'build', [ 'clean:build', 'fileExists', 'typos', 'build-code', 'build-styling', 'build-i18n', 'build-tests', 'clean:tmp' ] );

	grunt.registerTask( 'git-build', [ 'pre-git-build', 'build' ] );

	// Quickly build a no-frills vector-only ltr-only version for development
	grunt.registerTask( 'quick-build', [
		'pre-git-build', 'clean:build', 'fileExists', 'typos',
		'concat:js',
		'colorizeSvg', 'less:distVector',
		'copy:imagesApex', 'copy:imagesMediaWiki',
		'build-i18n'
	] );

	grunt.registerTask( 'lint', [ 'jshint', 'jscs', 'csslint', 'banana' ] );
	grunt.registerTask( 'test', [ 'pre-test', 'git-build', 'lint', 'karma:main', 'karma:other' ] );

	grunt.registerTask( 'default', 'test' );
};
<commit_msg>build: Add a 'generated automatically' banner to demo.rtl.css<commit_after>/*!
 * Grunt file
 */

/*jshint node:true */
module.exports = function ( grunt ) {
	grunt.loadNpmTasks( 'grunt-banana-checker' );
	grunt.loadNpmTasks( 'grunt-contrib-clean' );
	grunt.loadNpmTasks( 'grunt-contrib-concat-sourcemaps' );
	grunt.loadNpmTasks( 'grunt-contrib-copy' );
	grunt.loadNpmTasks( 'grunt-contrib-csslint' );
	grunt.loadNpmTasks( 'grunt-contrib-cssmin' );
	grunt.loadNpmTasks( 'grunt-contrib-jshint' );
	grunt.loadNpmTasks( 'grunt-contrib-less' );
	grunt.loadNpmTasks( 'grunt-contrib-uglify' );
	grunt.loadNpmTasks( 'grunt-contrib-watch' );
	grunt.loadNpmTasks( 'grunt-csscomb' );
	grunt.loadNpmTasks( 'grunt-exec' );
	grunt.loadNpmTasks( 'grunt-file-exists' );
	grunt.loadNpmTasks( 'grunt-cssjanus' );
	grunt.loadNpmTasks( 'grunt-jscs' );
	grunt.loadNpmTasks( 'grunt-karma' );
	grunt.loadNpmTasks( 'grunt-svg2png' );
	grunt.loadTasks( 'build/tasks' );

	var modules = grunt.file.readJSON( 'build/modules.json' ),
		lessFiles = {
			raster: {},
			vector: {},
			mixed: {}
		},
		colorizeSvgFiles = {},
		originalLessFiles = {},
		concatCssFiles = {},
		rtlFiles = {
			'demos/styles/demo.rtl.css': 'demos/styles/demo.css'
		},
		minBanner = '/*! OOjs UI v<%= pkg.version %> | http://oojs.mit-license.org */';

	( function () {
		var distFile, target, module, moduleStyleFiles,
			path = require( 'path' );
		function processFile( fileName ) {
			if ( path.extname( fileName ) === '.json' ) {
				var
					theme = path.basename( path.dirname( fileName ) ),
					lessFileName = fileName.replace( /\.json$/, '.less' ).replace( /^src\//, 'dist/tmp/' );
				colorizeSvgFiles[ fileName ] = {
					options: grunt.file.readJSON( fileName ),
					srcDir: 'src/themes/' + theme,
					destDir: 'dist/themes/' + theme,
					// This should not be needed, but our dist directory structure is weird
					cssPrependPath: 'themes/' + theme + '/',
					destLessFile: lessFileName
				};
				return lessFileName;
			}
			return fileName;
		}
		for ( module in modules ) {
			if ( modules[ module ].styles ) {
				moduleStyleFiles = modules[ module ].styles;
				for ( target in lessFiles ) {
					distFile = 'dist/' + module + ( target !== 'mixed' ? '.' + target : '' ) + '.css';

					originalLessFiles[ distFile ] = moduleStyleFiles;
					lessFiles[ target ][ distFile ] = moduleStyleFiles.map( processFile );

					// Concat isn't doing much other than prepending the banner...
					concatCssFiles[ distFile ] = distFile;
					rtlFiles[ distFile.replace( '.css', '.rtl.css' ) ] = distFile;
				}
			}
		}
	}() );

	function strip( str ) {
		var path = require( 'path' );
		// http://gruntjs.com/configuring-tasks#building-the-files-object-dynamically
		// http://gruntjs.com/api/grunt.file#grunt.file.expandmapping
		return function ( dest, src ) {
			return path.join( dest, src.replace( str, '' ) );
		};
	}

	grunt.initConfig( {
		pkg: grunt.file.readJSON( 'package.json' ),

		// Build
		clean: {
			build: 'dist/*',
			tmp: 'dist/tmp'
		},
		fileExists: {
			src: ( function () {
				var distFile,
					files = modules[ 'oojs-ui' ].scripts.slice();

				for ( distFile in originalLessFiles ) {
					files.push.apply( files, originalLessFiles[ distFile ] );
				}

				return files;
			}() )
		},
		typos: {
			options: {
				typos: 'build/typos.json'
			},
			src: '{src,php}/**/*.{js,json,less,css}'
		},
		concat: {
			options: {
				banner: grunt.file.read( 'build/banner.txt' )
			},
			js: {
				files: {
					'dist/oojs-ui.js': modules[ 'oojs-ui' ].scripts,
					'dist/oojs-ui-apex.js': modules[ 'oojs-ui-apex' ].scripts,
					'dist/oojs-ui-mediawiki.js': modules[ 'oojs-ui-mediawiki' ].scripts
				}
			},
			css: {
				files: concatCssFiles
			},
			demoCss: {
				options: {
					banner: '/** This file is generated automatically. Do not modify it. */\n\n'
				},
				files: {
					'demos/styles/demo.rtl.css': 'demos/styles/demo.rtl.css'
				}
			}
		},

		// Build – Code
		uglify: {
			options: {
				banner: minBanner,
				sourceMap: true,
				sourceMapIncludeSources: true,
				report: 'gzip'
			},
			js: {
				expand: true,
				src: 'dist/*.js',
				ext: '.min.js',
				extDot: 'last'
			}
		},

		// Build – Styling
		less: {
			distRaster: {
				options: {
					ieCompat: true,
					report: 'gzip',
					modifyVars: {
						'oo-ui-distribution': 'raster',
						'oo-ui-default-image-ext': 'png'
					}
				},
				files: lessFiles.raster
			},
			distVector: {
				options: {
					ieCompat: false,
					report: 'gzip',
					modifyVars: {
						'oo-ui-distribution': 'vector',
						'oo-ui-default-image-ext': 'svg'
					}
				},
				files: lessFiles.vector
			},
			distMixed: {
				options: {
					ieCompat: false,
					report: 'gzip',
					modifyVars: {
						'oo-ui-distribution': 'mixed',
						'oo-ui-default-image-ext': 'png'
					}
				},
				files: lessFiles.mixed
			}
		},
		cssjanus: {
			dist: {
				options: {
					generateExactDuplicates: true
				},
				files: rtlFiles
			}
		},
		csscomb: {
			dist: {
				expand: true,
				src: 'dist/*.css'
			}
		},
		copy: {
			imagesCommon: {
				src: 'src/styles/images/*.cur',
				dest: 'dist/images/',
				expand: true,
				flatten: true
			},
			imagesApex: {
				src: 'src/themes/apex/images/**/*.{png,gif}',
				dest: 'dist/themes/apex/images/',
				expand: true,
				rename: strip( 'src/themes/apex/images/' )
			},
			imagesMediaWiki: {
				src: 'src/themes/mediawiki/images/**/*.{png,gif}',
				dest: 'dist/themes/mediawiki/images/',
				expand: true,
				rename: strip( 'src/themes/mediawiki/images/' )
			},
			i18n: {
				src: 'i18n/*.json',
				expand: true,
				dest: 'dist/'
			},
			jsduck: {
				src: '{lib,dist}/**/*',
				dest: 'docs/',
				expand: true
			}
		},
		colorizeSvg: colorizeSvgFiles,
		svg2png: {
			dist: {
				src: 'dist/{images,themes}/**/*.svg'
			}
		},
		cssmin: {
			options: {
				keepSpecialComments: 0,
				banner: minBanner,
				compatibility: 'ie8',
				report: 'gzip'
			},
			dist: {
				expand: true,
				src: 'dist/*.css',
				ext: '.min.css',
				extDot: 'last'
			}
		},

		// Lint – Code
		jshint: {
			options: {
				jshintrc: true
			},
			dev: [
				'*.js',
				'{build,demos,src,tests}/**/*.js',
				'!tests/JSPHP.test.js'
			]
		},
		jscs: {
			dev: [
				'<%= jshint.dev %>',
				'!demos/{dist,lib}/**'
			]
		},

		// Lint – Styling
		csslint: {
			options: {
				csslintrc: '.csslintrc'
			},
			all: [
				'{demos,src}/**/*.css',
				'!demos/{dist,lib}/**'
			]
		},

		// Lint – i18n
		banana: {
			all: 'i18n/'
		},

		// Test
		exec: {
			rubyTestSuiteGenerator: {
				command: 'ruby bin/testsuitegenerator.rb src php > tests/JSPHP-suite.json'
			},
			phpGenerateJSPHPForKarma: {
				command: 'php ../bin/generate-JSPHP-for-karma.php > JSPHP.test.js',
				cwd: './tests'
			}
		},
		karma: {
			options: {
				frameworks: [ 'qunit' ],
				files: [
					'lib/jquery.js',
					'lib/oojs.jquery.js',
					'dist/oojs-ui.js',
					'dist/oojs-ui-apex.js',
					'dist/oojs-ui-mediawiki.js',
					'tests/QUnit.assert.equalDomElement.js',
					'tests/**/*.test.js'
				],
				reporters: [ 'dots' ],
				singleRun: true,
				browserDisconnectTimeout: 5000,
				browserDisconnectTolerance: 2,
				autoWatch: false
			},
			main: {
				browsers: [ 'Chrome' ],
				preprocessors: {
					'dist/*.js': [ 'coverage' ]
				},
				reporters: [ 'dots', 'coverage' ],
				coverageReporter: { reporters: [
					{ type: 'html', dir: 'dist/coverage/' },
					{ type: 'text-summary', dir: 'dist/coverage/' }
				] }
			},
			other: {
				browsers: [ 'Firefox' ]
			}
		},

		// Development
		watch: {
			files: [
				'<%= jshint.dev %>',
				'<%= csslint.all %>',
				'{demos,src}/**/*.less',
				'.{csslintrc,jscsrc,jshintignore,jshintrc}'
			],
			tasks: 'quick-build'
		}
	} );

	grunt.registerTask( 'pre-test', function () {
		// Only create Source maps when doing a git-build for testing and local
		// development. Distributions for export should not, as the map would
		// be pointing at "../src".
		grunt.config.set( 'concat.js.options.sourceMap', true );
		grunt.config.set( 'concat.js.options.sourceMapStyle', 'link' );
	} );

	grunt.registerTask( 'pre-git-build', function () {
		var done = this.async();
		require( 'child_process' ).exec( 'git rev-parse HEAD', function ( err, stout, stderr ) {
			if ( !stout || err || stderr ) {
				grunt.log.err( err || stderr );
				done( false );
				return;
			}
			grunt.config.set( 'pkg.version', grunt.config( 'pkg.version' ) + '-pre (' + stout.slice( 0, 10 ) + ')' );
			grunt.verbose.writeln( 'Added git HEAD to pgk.version' );
			done();
		} );
	} );

	grunt.registerTask( 'build-code', [ 'concat:js', 'uglify' ] );
	grunt.registerTask( 'build-styling', [
		'colorizeSvg', 'less', 'copy:imagesCommon',
		'copy:imagesApex', 'copy:imagesMediaWiki', 'svg2png',
		'concat:css', 'cssjanus', 'concat:demoCss', 'csscomb', 'cssmin'
	] );
	grunt.registerTask( 'build-i18n', [ 'copy:i18n' ] );
	grunt.registerTask( 'build-tests', [ 'exec:rubyTestSuiteGenerator', 'exec:phpGenerateJSPHPForKarma' ] );
	grunt.registerTask( 'build', [ 'clean:build', 'fileExists', 'typos', 'build-code', 'build-styling', 'build-i18n', 'build-tests', 'clean:tmp' ] );

	grunt.registerTask( 'git-build', [ 'pre-git-build', 'build' ] );

	// Quickly build a no-frills vector-only ltr-only version for development
	grunt.registerTask( 'quick-build', [
		'pre-git-build', 'clean:build', 'fileExists', 'typos',
		'concat:js',
		'colorizeSvg', 'less:distVector',
		'copy:imagesApex', 'copy:imagesMediaWiki',
		'build-i18n'
	] );

	grunt.registerTask( 'lint', [ 'jshint', 'jscs', 'csslint', 'banana' ] );
	grunt.registerTask( 'test', [ 'pre-test', 'git-build', 'lint', 'karma:main', 'karma:other' ] );

	grunt.registerTask( 'default', 'test' );
};
<|endoftext|>


<commit_before>describe( 'ractive-events-tap', function () {
	var fixture;

	beforeEach( function () {
		fixture = document.createElement( 'div' );
		document.body.appendChild( fixture );
	});

	afterEach( function () {
		document.body.removeChild( fixture );
	});

	it( 'Mousedown followed by click results in a tap event', function () {
		var ractive, tapped;

		ractive = new Ractive({
			el: fixture,
			template: 'tap me',
			debug: true
		});

		ractive.on( 'tap', function () {
			tapped = true;
		});

		assert.equal( tapped, undefined );
		simulant.fire( ractive.nodes.test, 'mousedown' );
		simulant.fire( ractive.nodes.test, 'click' );
		assert.equal( tapped, true );
	});

	it( 'Pressing spacebar on a focused button results in a tap event', function ( done ) {
		var ractive, node, tapped;

		ractive = new Ractive({
			el: fixture,
			template: '<button id="test" on-tap="tap">tap me</button>'
		});

		node = ractive.nodes.test;

		ractive.on( 'tap', function () {
			tapped = true;
		});

		assert.equal( tapped, undefined );

		simulant.fire( node, 'keydown', { which: 32 });
		assert.equal( tapped, undefined );

		node.focus();
		assert.equal( document.activeElement, node );
		simulant.fire( node, 'keydown', { which: 32 });

		setTimeout( function () {
			assert.ok( tapped, 'was tapped' );
			done();
		}, 100 );
	});

	it( 'IE Edge window.pointerEnabled == undefined should still result in a tap event', function ( done ) {
		var ractive, node, tapped;

		window.navigator.pointerEnabled = undefined;
		window.PointerEvent = new PointerEvent("pointerdown",
			{
				pointerId: 1,
				bubbles: true,
				cancelable: true,
				pointerType: "touch",
				width: 100,
				height: 100,
				isPrimary: true
			});

		ractive = new Ractive({
			el: fixture,
			template: '<button id="test" on-tap="tap">tap me</button>',
			debug: true
		});

		node = ractive.nodes.test;

		ractive.on( 'tap', function () {
			tapped = true;
		});

		assert.equal( tapped, undefined );
		simulant.fire( node, 'mousedown' );
		simulant.fire( node, 'pointerdown' );
		simulant.fire( node, 'pointerup' );
		assert.equal( tapped, true );

		setTimeout( function () {
			assert.equal( tapped, true );
			done();
		}, 100 );
	});
});
<commit_msg>chore(tests.js): remove the mock for PointerEvent<commit_after>describe( 'ractive-events-tap', function () {
	var fixture;

	beforeEach( function () {
		fixture = document.createElement( 'div' );
		document.body.appendChild( fixture );
	});

	afterEach( function () {
		document.body.removeChild( fixture );
	});

	it( 'Mousedown followed by click results in a tap event', function () {
		var ractive, tapped;

		ractive = new Ractive({
			el: fixture,
			template: 'tap me',
			debug: true
		});

		ractive.on( 'tap', function () {
			tapped = true;
		});

		assert.equal( tapped, undefined );
		simulant.fire( ractive.nodes.test, 'mousedown' );
		simulant.fire( ractive.nodes.test, 'click' );
		assert.equal( tapped, true );
	});

	it( 'Pressing spacebar on a focused button results in a tap event', function ( done ) {
		var ractive, node, tapped;

		ractive = new Ractive({
			el: fixture,
			template: '<button id="test" on-tap="tap">tap me</button>'
		});

		node = ractive.nodes.test;

		ractive.on( 'tap', function () {
			tapped = true;
		});

		assert.equal( tapped, undefined );

		simulant.fire( node, 'keydown', { which: 32 });
		assert.equal( tapped, undefined );

		node.focus();
		assert.equal( document.activeElement, node );
		simulant.fire( node, 'keydown', { which: 32 });

		setTimeout( function () {
			assert.ok( tapped, 'was tapped' );
			done();
		}, 100 );
	});

	it( 'IE Edge window.pointerEnabled == undefined should still result in a tap event', function ( done ) {
		var ractive, node, tapped;

		window.navigator.pointerEnabled = undefined;
		/** NOTE removed mocking PointerEvent, it should be handled in the Browser itself. Ex. Chrome 56+
		 * Because window.PointerEvent = new PointerEvent() is just very incorrect.
		 * window.PointerEvent = new PointerEvent("pointerdown", {}); */

		ractive = new Ractive({
			el: fixture,
			template: '<button id="test" on-tap="tap">tap me</button>',
			debug: true
		});

		node = ractive.nodes.test;

		ractive.on( 'tap', function () {
			tapped = true;
		});

		assert.equal( tapped, undefined );
		simulant.fire( node, 'mousedown' );
		simulant.fire( node, 'pointerdown' );
		simulant.fire( node, 'pointerup' );
		assert.equal( tapped, true );

		setTimeout( function () {
			assert.equal( tapped, true );
			done();
		}, 100 );
	});
});
<|endoftext|>


<commit_before>/**
 * module.exports ... is required for things to work
 * @param {Object} grunt instance of Grunt.
 */
module.exports = function(grunt) {
  'use strict';

  grunt.loadNpmTasks('grunt-contrib-clean');
  grunt.loadNpmTasks('grunt-contrib-copy');
  grunt.loadNpmTasks('grunt-contrib-concat');
  grunt.loadNpmTasks('grunt-contrib-uglify');
  grunt.loadNpmTasks('grunt-contrib-requirejs');
  grunt.loadNpmTasks('grunt-contrib-watch');
  grunt.loadNpmTasks('grunt-jslint');
  grunt.loadNpmTasks('grunt-mocha');

  grunt.initConfig({

    clean: {
      build: {
        src: ['BlinkForms*.js*', 'js/*.min.js']
      }
    },

    jslint: {
      files: [
        '**/*.js'
      ],
      exclude: [
        'node_modules/**',
        'js/lib/**',
        'js/build/**',
        'BlinkForms*.js'
      ],
      directives: {
        browser: true,
        es5: true,
        nomen: true,
        todo: true,
        sloppy: true, // we force strict-mode separately
        white: true,
        predef: [
          // pre-defined globals
          'module',
          'define',
          'require',
          // globals we assume have been loaded
          '$',
          '_',
          'Backbone',
          'rivets',
          'BlinkForms',
          'Q'
        ]
      },
      options: {
        failOnError: true
      }
    },

    requirejs: {
      compile: {
        options: {
          baseUrl: 'js',
          dir: 'js/build',
//          optimize: 'uglify2',
          optimize: 'none',
          uglify: {
            max_line_length: 80
          },
          uglify2: {
            output: {
              max_line_len: 80
            },
            warnings: false
          },
          paths: {
            backbone: 'empty:',
            jquery: 'empty:',
            jquerymobile: 'empty:',
            q: 'empty:',
            underscore: 'empty:',
            rivets: 'empty:',
            // testing libraries
            chai: 'empty:',
            mocha: 'empty:'
          },
          modules: [
            {
              name: 'main'
            },
            {
              name: 'views/jqm',
              exclude: ['main']
            }
          ]
        }
      }
    },

    concat: {
      core: {
        src: [
          'parts/00-start.frag',
          'js/lib/almond-0.2.4.js',
          'js/build/main.js',
          'parts/99-end-core.frag'
        ],
        dest: 'BlinkForms.js'
      },
      jqm: {
        src: [
          'parts/00-start.frag',
          'js/lib/almond-0.2.5.js',
          'js/build/main.js',
          'js/build/views/jqm.js',
          'parts/99-end-jqm.frag'
        ],
        dest: 'BlinkForms-jQueryMobile.js'
      }
    },

    uglify: {
      default: {
        options: {
          sourceMap: 'BlinkForms-jQueryMobile.js.map',
          beautify: {
            width: 80,
            max_line_len: 80
          }
        },
        files: {
          'BlinkForms-jQueryMobile.min.js': ['BlinkForms-jQueryMobile.js']
        }
      }

    },

    mocha: {
      all: {
        src: ['test/*/index.html'],
        options: {
          run: false
        }
      }
    },

    watch: {
      src: {
        files: ['js/**/*.js'],
        tasks: 'default'
      }
    }

  });

  grunt.registerTask('default', [
    'jslint',
    'clean',
    'requirejs',
    'concat',
    'uglify',
    'mocha'
  ]);

};

<commit_msg>add "build" and "test" Grunt tasks for quicker access<commit_after>/**
 * module.exports ... is required for things to work
 * @param {Object} grunt instance of Grunt.
 */
module.exports = function(grunt) {
  'use strict';

  grunt.loadNpmTasks('grunt-contrib-clean');
  grunt.loadNpmTasks('grunt-contrib-copy');
  grunt.loadNpmTasks('grunt-contrib-concat');
  grunt.loadNpmTasks('grunt-contrib-uglify');
  grunt.loadNpmTasks('grunt-contrib-requirejs');
  grunt.loadNpmTasks('grunt-contrib-watch');
  grunt.loadNpmTasks('grunt-jslint');
  grunt.loadNpmTasks('grunt-mocha');

  grunt.initConfig({

    clean: {
      build: {
        src: ['BlinkForms*.js*', 'js/*.min.js']
      }
    },

    jslint: {
      files: [
        '**/*.js'
      ],
      exclude: [
        'node_modules/**',
        'js/lib/**',
        'js/build/**',
        'BlinkForms*.js'
      ],
      directives: {
        browser: true,
        es5: true,
        nomen: true,
        todo: true,
        sloppy: true, // we force strict-mode separately
        white: true,
        predef: [
          // pre-defined globals
          'module',
          'define',
          'require',
          // globals we assume have been loaded
          '$',
          '_',
          'Backbone',
          'rivets',
          'BlinkForms',
          'Q'
        ]
      },
      options: {
        failOnError: true
      }
    },

    requirejs: {
      compile: {
        options: {
          baseUrl: 'js',
          dir: 'js/build',
//          optimize: 'uglify2',
          optimize: 'none',
          uglify: {
            max_line_length: 80
          },
          uglify2: {
            output: {
              max_line_len: 80
            },
            warnings: false
          },
          paths: {
            backbone: 'empty:',
            jquery: 'empty:',
            jquerymobile: 'empty:',
            q: 'empty:',
            underscore: 'empty:',
            rivets: 'empty:',
            // testing libraries
            chai: 'empty:',
            mocha: 'empty:'
          },
          modules: [
            {
              name: 'main'
            },
            {
              name: 'views/jqm',
              exclude: ['main']
            }
          ]
        }
      }
    },

    concat: {
      core: {
        src: [
          'parts/00-start.frag',
          'js/lib/almond-0.2.4.js',
          'js/build/main.js',
          'parts/99-end-core.frag'
        ],
        dest: 'BlinkForms.js'
      },
      jqm: {
        src: [
          'parts/00-start.frag',
          'js/lib/almond-0.2.5.js',
          'js/build/main.js',
          'js/build/views/jqm.js',
          'parts/99-end-jqm.frag'
        ],
        dest: 'BlinkForms-jQueryMobile.js'
      }
    },

    uglify: {
      default: {
        options: {
          sourceMap: 'BlinkForms-jQueryMobile.js.map',
          beautify: {
            width: 80,
            max_line_len: 80
          }
        },
        files: {
          'BlinkForms-jQueryMobile.min.js': ['BlinkForms-jQueryMobile.js']
        }
      }

    },

    mocha: {
      all: {
        src: ['test/*/index.html'],
        options: {
          run: false
        }
      }
    },

    watch: {
      src: {
        files: ['js/**/*.js'],
        tasks: 'default'
      }
    }

  });

  grunt.registerTask('default', [
    'jslint',
    'clean',
    'requirejs',
    'concat',
    'uglify',
    'mocha'
  ]);

  grunt.registerTask('build', [
    'clean',
    'requirejs',
    'concat',
    'uglify'
  ]);

  grunt.registerTask('test', [
    'build',
    'mocha'
  ]);

};

<|endoftext|>


<commit_before>var assert = require("assert");
var crypto = require("crypto");
var wrap = require("../lib/index.js").wrap;

describe("optimism", function () {
  it("sanity", function () {
    assert.strictEqual(typeof wrap, "function");
  });

  it("works with single functions", function () {
    var test = wrap(function (x) {
      return x + salt;
    }, {
      makeCacheKey(x) {
        return x;
      }
    });

    var salt = "salt";
    assert.strictEqual(test("a"), "asalt");

    salt = "NaCl";
    assert.strictEqual(test("a"), "asalt");
    assert.strictEqual(test("b"), "bNaCl");

    test.dirty("a");
    assert.strictEqual(test("a"), "aNaCl");
  });

  it("works with two layers of functions", function () {
    var files = {
      "a.js": new Buffer("ay"),
      "b.js": new Buffer("bee")
    };

    var read = wrap(function (path) {
      return files[path];
    });

    var hash = wrap(function (paths) {
      var h = crypto.createHash("sha1");
      paths.forEach(function (path) {
        h.update(read(path));
      });
      return h.digest("hex");
    });

    var hash1 = hash(["a.js", "b.js"]);
    files["a.js"] += "yy";
    var hash2 = hash(["a.js", "b.js"]);
    read.dirty("a.js");
    var hash3 = hash(["a.js", "b.js"]);

    assert.strictEqual(hash1, hash2);
    assert.notStrictEqual(hash1, hash3);
  });
});
<commit_msg>Avoid method shorthand syntax in test/tests.js.<commit_after>var assert = require("assert");
var crypto = require("crypto");
var wrap = require("../lib/index.js").wrap;

describe("optimism", function () {
  it("sanity", function () {
    assert.strictEqual(typeof wrap, "function");
  });

  it("works with single functions", function () {
    var test = wrap(function (x) {
      return x + salt;
    }, {
      makeCacheKey: function (x) {
        return x;
      }
    });

    var salt = "salt";
    assert.strictEqual(test("a"), "asalt");

    salt = "NaCl";
    assert.strictEqual(test("a"), "asalt");
    assert.strictEqual(test("b"), "bNaCl");

    test.dirty("a");
    assert.strictEqual(test("a"), "aNaCl");
  });

  it("works with two layers of functions", function () {
    var files = {
      "a.js": new Buffer("ay"),
      "b.js": new Buffer("bee")
    };

    var read = wrap(function (path) {
      return files[path];
    });

    var hash = wrap(function (paths) {
      var h = crypto.createHash("sha1");
      paths.forEach(function (path) {
        h.update(read(path));
      });
      return h.digest("hex");
    });

    var hash1 = hash(["a.js", "b.js"]);
    files["a.js"] += "yy";
    var hash2 = hash(["a.js", "b.js"]);
    read.dirty("a.js");
    var hash3 = hash(["a.js", "b.js"]);

    assert.strictEqual(hash1, hash2);
    assert.notStrictEqual(hash1, hash3);
  });
});
<|endoftext|>


<commit_before>module.exports = function(grunt) {

  // Project configuration.
  grunt.initConfig({
    pkg: grunt.file.readJSON('package.json'),
    watch: {
      css: {
        files: [
          'sass/*.sass',
          'sass/*.scss'
        ],
        tasks: ['compass']
      },
      js: {
        files: [
          'javascripts/*.js',
          'javascripts/lib/*.js',
          'Gruntfile.js'
        ],
        tasks: ['jshint']
      }
    },
    compass: {
      dist: {
        options: {
          sassDir: 'sass',
          cssDir: 'stylesheets',
          outputStyle: 'compressed'
        }
      }
    },
    uglify: {
      my_target: {
        files: {
          'javascripts/site.min.js': ['javascripts/site.js'],
          'javascripts/fourohfour.min.js': ['javascripts/fourohfour.js']
        }
      }
    },
    jshint: {
      options: {
        jshintrc: '.jshintrc'
      },
      all: ['Gruntfile.js', 'javascripts/*.js']
    }

  });

  // Load the Grunt plugins.
  grunt.loadNpmTasks('grunt-contrib-compass');
  grunt.loadNpmTasks('grunt-contrib-watch');
  grunt.loadNpmTasks('grunt-contrib-jshint');
  grunt.loadNpmTasks('grunt-contrib-uglify');

  // Register the default tasks.
  grunt.registerTask('default', ['watch', 'jshint', 'qunit', 'concat', 'uglify']);

};
<commit_msg>Another update to grunt file<commit_after>module.exports = function(grunt) {

  // Project configuration.
  grunt.initConfig({
    pkg: grunt.file.readJSON('package.json'),
    watch: {
      css: {
        files: [
          'sass/*.sass',
          'sass/*.scss'
        ],
        tasks: ['compass']
      },
      js: {
        files: [
          'javascripts/*.js',
          'javascripts/lib/*.js',
          'Gruntfile.js'
        ],
        tasks: ['jshint']
      }
    },
    compass: {
      dist: {
        options: {
          sassDir: 'sass',
          cssDir: 'stylesheets',
          outputStyle: 'compressed'
        }
      }
    },
    uglify: {
      my_target: {
        files: {
          'javascripts/site.min.js': ['javascripts/site.js'],
          'javascripts/fourohfour.min.js': ['javascripts/fourohfour.js']
        }
      }
    },
    jshint: {
      options: {
        jshintrc: '.jshintrc'
      },
      all: ['Gruntfile.js', 'javascripts/*.js']
    }

  });

  // Load the Grunt plugins.
  grunt.loadNpmTasks('grunt-contrib-compass');
  grunt.loadNpmTasks('grunt-contrib-watch');
  grunt.loadNpmTasks('grunt-contrib-jshint');
  grunt.loadNpmTasks('grunt-contrib-uglify');

  // Register the default tasks.
  grunt.registerTask('default', ['watch', 'jshint', 'uglify']);

};
<|endoftext|>