<commit_before>'use strict';

var NAME = 'webpack-runtime-analyzer';
var parseQuery = require('loader-utils/lib/parseQuery.js');
var webpack = require.main.require('webpack');
var RequestShortener = require.main.require('webpack/lib/RequestShortener');
var fork = require('child_process').fork;
var rempl = require('rempl');
var path = require('path');
var fs = require('fs');
var opn = require('opn');
var openInEditor = require('open-in-editor');
var requestShortener;
var SourceMapConsumer = require('source-map').SourceMapConsumer;

function isObject(obj) {
    return typeof obj == 'object' && obj;
}

function cloneArray(array) {
    return array.map(function(el) {
        if (Array.isArray(el)) {
            return cloneArray(el);
        }

        if (isObject(el) && el.constructor == Object) {
            return deepExtend({}, el);
        }

        return el;
    });
}

function deepExtend(target) {
    var sources = Array.prototype.slice.call(arguments, 1);

    if (typeof target != 'object' || !target) {
        return;
    }

    for (var i = 0; i < sources.length; i++) {
        var source = sources[i];

        if (isObject(source)) {
            for (var sourceKey in source) {
                if (source.hasOwnProperty(sourceKey)) {
                    var value = source[sourceKey];

                    if (Array.isArray(value)) {
                        target[sourceKey] = cloneArray(value);
                    } else if (isObject(value) && value.constructor == Object) {
                        target[sourceKey] = deepExtend({}, value);
                    } else {
                        target[sourceKey] = value;
                    }
                }
            }
        }
    }

    return target;
}

function getRetained(module, modulesMap, exclude) {
    var stack = [module.id];
    var visited = {};

    while (stack.length) {
        var currentModuleId = stack.pop();
        var currentModule = modulesMap[currentModuleId];

        if (!exclude || currentModule != exclude) {
            for (var i = 0; i < currentModule.dependencies.length; i++) {
                var dependency = currentModule.dependencies[i];

                if (!visited[dependency]) {
                    stack.push(dependency);
                    visited[dependency] = true;
                }
            }
        }
    }

    return Object.keys(visited);
}

function getModuleId(module, compiler) {
    // webpack 1.x capability
    function makeRelative(compiler, identifier) {
        var context = compiler.context;

        return identifier
            .split('|')
            .map(function(str) {
                return str
                    .split('!')
                    .map(function(str) {
                        return path.relative(context, str);
                    })
                    .join('!');
            })
            .join('|');
    }

    if (!module.portableId) {
        module.portableId = makeRelative(compiler, module.identifier())
    }

    return module.portableId;
}

function handleFile(file, handledFiles) {
    if (handledFiles.hasOwnProperty(file)) {
        return handledFiles[file];
    }

    if (fs.existsSync(file)) {
        var stats = fs.statSync(file);

        if (!stats.isFile) {
            return null;
        }

        handledFiles[file] = {
            name: file,
            short: requestShortener.shorten(file),
            size: stats.size
        };

        return handledFiles[file];
    }

    return null;
}

function getModuleLoaders(module, handledFiles) {
    return (module.loaders || []).map(function(loaderInfo) {
        var options = loaderInfo.options;

        if (typeof loaderInfo.options == 'string') {
            if (loaderInfo.options[0] != '?') {
                loaderInfo.options = '?' + loaderInfo.options;
            }

            options = parseQuery(loaderInfo.options);
        }

        return {
            file: handleFile(loaderInfo.loader, handledFiles),
            options: options
        };
    });
}

function startRemplServer(plugin) {
    console.info('Starting rempl server...');

    fork(path.resolve(__dirname, 'server.js'), { silent: true })
        .once('exit', function(code) {
            console.error('\n[ERROR] RuntimeAnalyzerPlugin exited with code', code);
            process.exit(code);
        })
        .on('message', function(data) {
            if (data && data.event === 'server-started') {
                plugin.publisher.wsendpoint = data.endpoint;
                plugin.publisher.connectWs(data.endpoint);

                if (plugin.options.open) {
                    opn(data.endpoint);
                }
            }
        })
        .send({
            name: NAME,
            port: plugin.options.port
        });
}

function createPublisher(plugin, compiler, options) {
    // use require.main.require to get version of inspecting webpack
    // but not a webpack version uses as dependency
    var webpackVersion = require.main.require('webpack/package.json').version;
    var modulesTypeMap = {
        NormalModule: 'normal',
        MultiModule: 'multi',
        ContextModule: 'context',
        DelegatedModule: 'delegated',
        ExternalModule: 'external'
    };
    var editor = openInEditor.configure({});
    var getWebUI = rempl.scriptFromFile(options.ui);
    var publisher = rempl.createPublisher(NAME, function(settings, callback) {
        if (settings.dev) {
            return callback(null, 'url', 'http://localhost:8001/src/ui/');
        }

        getWebUI(settings, callback);
    }, {
        ws: options.mode !== 'standalone'
    });

    publisher.provide('openInEditor', function(path, cb) {
        if (editor) {
            editor.open(path)
                .then(function() {
                    cb({ ok: true });
                }, function(err) {
                    cb({ ok: false, error: err })
                });
        } else {
            cb({ ok: false, error: 'Editor is not specified' })
        }
    });

    var status = publisher.ns('status');
    var progress = publisher.ns('progress');
    var profile = publisher.ns('profile');
    var stats;

    compiler.plugin('compilation', function(compilation) {
        stats = compilation;
        requestShortener = new RequestShortener(compilation.options.context || process.cwd());
    });

    compiler.apply(new webpack.ProgressPlugin(function(percent) {
        status.publish('compiling');
        progress.publish(percent);
    }));

    compiler.plugin('emit', function(compilation, done) {
        var assets = [];
        var modulesMap = {};
        var chunksMap = {};
        var modules;
        var chunks;
        var fileModulesMap = {};
        var entryPoints = {};
        var handledFiles = {};

        for (var assetName in compilation.assets) {
            if (compilation.assets.hasOwnProperty(assetName)) {
                assets.push({
                    name: assetName,
                    size: compilation.assets[assetName].size()
                });
            }
        }

        chunks = compilation.chunks.map(function(chunk) {
            var chunkInfo = {
                id: chunk.id,
                name: chunk.name,
                size: chunk.size({}),
                hash: chunk.renderedHash,
                files: chunk.files,
                reasons: chunk.parents.map(function(chunk) {
                    return chunk.id;
                }),
                dependencies: chunk.modules.map(function(module) {
                    return getModuleId(module, compiler);
                }),
                rendered: chunk.rendered,
                // webpack 1.x capability
                initial: typeof chunk.isInitial == 'function' ? chunk.isInitial() : chunk.initial,
                // webpack 1.x capability
                entry: typeof chunk.hasRuntime == 'function' ? chunk.hasRuntime() : chunk.entry
            };

            chunksMap[chunkInfo.id] = chunksMap;

            return chunkInfo
        });

        modules = compilation.modules.map(function(module) {
            var moduleType = modulesTypeMap[module.constructor.name] || 'unknown';
            var resource = module.resource ? handleFile(module.resource, handledFiles) : null;

            // webpack 1.x capability
            if (/^1\./.test(webpackVersion)) {
                var identifier = module.identifier();

                if (!identifier.indexOf('multi')) {
                    moduleType = 'multi';
                } else if (module.regExp) {
                    moduleType = 'context';
                } else if (!identifier.indexOf('delegated')) {
                    moduleType = 'delegated';
                } else if (!identifier.indexOf('external')) {
                    moduleType = 'external';
                } else if (resource) {
                    moduleType = 'normal';
                } else {
                    moduleType = 'unknown';
                }
            }

            var moduleInfo = {
                id: getModuleId(module, compiler),
                index: module.index,
                type: moduleType,
                name: module.readableIdentifier(requestShortener),
                size: module.size(),
                rawRequest: module.rawRequest,
                userRequest: module.userRequest,
                context: module.context,
                resource: resource,
                loaders: getModuleLoaders(module, handledFiles),
                dependencies: module.dependencies
                    .filter(function(dependency) {
                        return dependency.module;
                    })
                    .map(function(dependency) {
                        return getModuleId(dependency.module, compiler);
                    }),
                reasons: module.reasons
                    .filter(function(reason) {
                        return reason.dependency && reason.module;
                    })
                    .map(function(reason) {
                        return getModuleId(reason.module);
                    })
            };

            if (resource) {
                var mapItem = fileModulesMap[resource.name];

                if (mapItem) {
                    mapItem.push(moduleInfo.id);
                } else {
                    fileModulesMap[resource.name] = [moduleInfo.id];
                }
            }

            modulesMap[moduleInfo.id] = moduleInfo;
            moduleInfo.isEntry = !moduleInfo.reasons.length;

            if (moduleInfo.isEntry) {
                entryPoints[moduleInfo.id] = true;
            }

            return moduleInfo;
        });

        entryPoints = Object.keys(entryPoints);

        // console.time('Retained + Exclusive');

        modules.forEach(function(module) {
            var allSubtreeExceptSelected = {};

            module.retained = getRetained(module, modulesMap);

            if (module.retained.indexOf(module.id) == -1) {
                module.retained.push(module.id);
            }

            for (var i = 0; i < entryPoints.length; i++) {
                var nodes = getRetained(modulesMap[entryPoints[i]], modulesMap, module);

                for (var j = 0; j < nodes.length; j++) {
                    allSubtreeExceptSelected[nodes[j]] = true;
                }
            }

            module.exclusive = module.retained.filter(function(exclusive) {
                return exclusive != module.id && !allSubtreeExceptSelected[exclusive];
            });

            var ix = module.retained.indexOf(module.id);

            if (ix > -1) {
                module.retained.splice(ix, 1)
            }
        });

        // console.timeEnd('Retained + Exclusive');

        var profileData = {
            version: webpackVersion,
            hash: compilation.hash,
            context: compilation.compiler.context,
            assets: assets,
            chunks: chunks,
            modules: modules,
            moduleLinks: compilation.modules.reduce(function(prev, current) {
                var links = current.reasons
                    .filter(function(reason) {
                        return reason.dependency && reason.module;
                    }).map(function(reason) {
                        var reasonInfo = {
                            from: getModuleId(reason.module, compiler),
                            to: getModuleId(current, compiler)
                        };

                        if (!reason.module.useSourceMap) {
                            return reasonInfo;
                        }

                        // todo handle string value
                        var location = isObject(reason.dependency.loc) ? deepExtend({}, reason.dependency.loc) : null;

                        if (!location || !location.start) {
                            return reasonInfo;
                        }

                        var variable = reason.module.variables
                            .filter(function(variable) {
                                return variable.dependencies.some(function(dependency) {
                                    return dependency.module == current;
                                });
                            })[0];

                        if (variable) {
                            reasonInfo.position = null;
                            reasonInfo.variable = {
                                name: variable.name,
                                expression: variable.expression
                            }
                        } else {
                            var originalSource = reason.module.originalSource ?
                                reason.module.originalSource() :
                                reason.module._source;
                            var sourceMapConsumer = new SourceMapConsumer(originalSource.map());
                            var originalPosition = sourceMapConsumer.originalPositionFor(location.start);

                            if (isFinite(originalPosition.line)) {
                                reasonInfo.position = {
                                    line: originalPosition.line,
                                    column: originalPosition.column,
                                };

                                if (isFinite(originalPosition.column)) {
                                    reasonInfo.position.column++
                                }
                            }
                        }

                        return reasonInfo;
                    });

                return prev.concat(links);
            }, []),
            fileLinks: Object.keys(fileModulesMap).map(function(fileName) {
                return {
                    id: fileName,
                    file: fileName,
                    modules: fileModulesMap[fileName]
                };
            }),
            errors: compilation.errors.map(function(error) {
                return {
                    message: error.message,
                    module: error.module && getModuleId(error.module, compiler)
                };
            }),
            warnings: compilation.warnings.map(function(warning) {
                return {
                    message: warning.message,
                    module: warning.module && getModuleId(warning.module, compiler)
                };
            })
        };

        profile.publish(profileData);

        done();

        if (publisher.wsendpoint) {
            // use timer to output link after all the stats is shown
            setTimeout(function() {
                console.log('\nWebpack Runtime Analyzer is available on', publisher.wsendpoint);
            }, 0);
        }
    });

    compiler.plugin('compile', function() {
        status.publish('compiling');
    });

    compiler.plugin('invalid', function() {
        status.publish('invalidated');
    });

    compiler.plugin('done', function() {
        status.publish(stats.errors.length ? 'failed' : 'success');
    });

    compiler.plugin('failed', function() {
        status.publish('failed');
    });

    return publisher;
}

function RuntimeAnalyzerPlugin(options) {
    this.publisher = null;

    // mix with default options
    this.options = deepExtend({
        mode: 'standalone',
        port: 0,
        watchModeOnly: true,
        ui: path.resolve(__dirname, '../../dist/script.js')
    }, options);
}

RuntimeAnalyzerPlugin.prototype.apply = function(compiler) {
    var options = this.options;

    compiler.plugin('watch-run', pluginFn.bind(this, true));

    if (!this.options.watchModeOnly) {
        compiler.plugin('run', pluginFn.bind(this, false));
    }

    function pluginFn(isWatchMode, watchingOrCompiler, done) {
        if (isWatchMode) {
            this.watching = watchingOrCompiler;
        }

        if (!this.publisher) {
            this.publisher = createPublisher(this, compiler, options);

            // run server after publisher is created
            // since server uses publisher when started
            if (options.mode === 'standalone') {
                startRemplServer(this);
            }
        }

        done();
    }
};

module.exports = RuntimeAnalyzerPlugin;
<commit_msg>convert paths on windows to unix-style (fixes #14)<commit_after>'use strict';

var NAME = 'webpack-runtime-analyzer';
var parseQuery = require('loader-utils/lib/parseQuery.js');
var webpack = require.main.require('webpack');
var RequestShortener = require.main.require('webpack/lib/RequestShortener');
var fork = require('child_process').fork;
var rempl = require('rempl');
var path = require('path');
var fs = require('fs');
var opn = require('opn');
var openInEditor = require('open-in-editor');
var requestShortener;
var SourceMapConsumer = require('source-map').SourceMapConsumer;

function isObject(obj) {
    return typeof obj == 'object' && obj;
}

function cloneArray(array) {
    return array.map(function(el) {
        if (Array.isArray(el)) {
            return cloneArray(el);
        }

        if (isObject(el) && el.constructor == Object) {
            return deepExtend({}, el);
        }

        return el;
    });
}

function deepExtend(target) {
    var sources = Array.prototype.slice.call(arguments, 1);

    if (typeof target != 'object' || !target) {
        return;
    }

    for (var i = 0; i < sources.length; i++) {
        var source = sources[i];

        if (isObject(source)) {
            for (var sourceKey in source) {
                if (source.hasOwnProperty(sourceKey)) {
                    var value = source[sourceKey];

                    if (Array.isArray(value)) {
                        target[sourceKey] = cloneArray(value);
                    } else if (isObject(value) && value.constructor == Object) {
                        target[sourceKey] = deepExtend({}, value);
                    } else {
                        target[sourceKey] = value;
                    }
                }
            }
        }
    }

    return target;
}

function getRetained(module, modulesMap, exclude) {
    var stack = [module.id];
    var visited = {};

    while (stack.length) {
        var currentModuleId = stack.pop();
        var currentModule = modulesMap[currentModuleId];

        if (!exclude || currentModule != exclude) {
            for (var i = 0; i < currentModule.dependencies.length; i++) {
                var dependency = currentModule.dependencies[i];

                if (!visited[dependency]) {
                    stack.push(dependency);
                    visited[dependency] = true;
                }
            }
        }
    }

    return Object.keys(visited);
}

function unixpath(value) {
    if (typeof value === 'string' && process.platform === 'win32') {
        return value
            .replace(/(^|!)[a-z]+\:/gi, '$1')
            .replace(/\\/g, '/');
    }

    return value;
}

function getModuleId(module, compiler) {
    // webpack 1.x capability
    function makeRelative(compiler, identifier) {
        var context = compiler.context;

        return identifier
            .split('|')
            .map(function(str) {
                return str
                    .split('!')
                    .map(function(str) {
                        return unixpath(path.relative(context, str));
                    })
                    .join('!');
            })
            .join('|');
    }

    if (!module.portableId) {
        module.portableId = makeRelative(compiler, module.identifier())
    }

    return module.portableId;
}

function handleFile(file, handledFiles) {
    if (handledFiles.hasOwnProperty(file)) {
        return handledFiles[file];
    }

    if (fs.existsSync(file)) {
        var stats = fs.statSync(file);

        if (!stats.isFile) {
            return null;
        }

        handledFiles[file] = {
            name: unixpath(file),
            short: requestShortener.shorten(file),
            size: stats.size
        };

        return handledFiles[file];
    }

    return null;
}

function getModuleLoaders(module, handledFiles) {
    return (module.loaders || []).map(function(loaderInfo) {
        var options = loaderInfo.options;

        if (typeof loaderInfo.options == 'string') {
            if (loaderInfo.options[0] != '?') {
                loaderInfo.options = '?' + loaderInfo.options;
            }

            options = parseQuery(loaderInfo.options);
        }

        return {
            file: handleFile(loaderInfo.loader, handledFiles),
            options: options
        };
    });
}

function startRemplServer(plugin) {
    console.info('Starting rempl server...');

    fork(path.resolve(__dirname, 'server.js'), { silent: true })
        .once('exit', function(code) {
            console.error('\n[ERROR] RuntimeAnalyzerPlugin exited with code', code);
            process.exit(code);
        })
        .on('message', function(data) {
            if (data && data.event === 'server-started') {
                plugin.publisher.wsendpoint = data.endpoint;
                plugin.publisher.connectWs(data.endpoint);

                if (plugin.options.open) {
                    opn(data.endpoint);
                }
            }
        })
        .send({
            name: NAME,
            port: plugin.options.port
        });
}

function createPublisher(plugin, compiler, options) {
    // use require.main.require to get version of inspecting webpack
    // but not a webpack version uses as dependency
    var webpackVersion = require.main.require('webpack/package.json').version;
    var modulesTypeMap = {
        NormalModule: 'normal',
        MultiModule: 'multi',
        ContextModule: 'context',
        DelegatedModule: 'delegated',
        ExternalModule: 'external'
    };
    var editor = openInEditor.configure({});
    var getWebUI = rempl.scriptFromFile(options.ui);
    var publisher = rempl.createPublisher(NAME, function(settings, callback) {
        if (settings.dev) {
            return callback(null, 'url', 'http://localhost:8001/src/ui/');
        }

        getWebUI(settings, callback);
    }, {
        ws: options.mode !== 'standalone'
    });

    publisher.provide('openInEditor', function(path, cb) {
        if (editor) {
            editor.open(path)
                .then(function() {
                    cb({ ok: true });
                }, function(err) {
                    cb({ ok: false, error: err })
                });
        } else {
            cb({ ok: false, error: 'Editor is not specified' })
        }
    });

    var status = publisher.ns('status');
    var progress = publisher.ns('progress');
    var profile = publisher.ns('profile');
    var stats;

    compiler.plugin('compilation', function(compilation) {
        stats = compilation;
        requestShortener = new RequestShortener(compilation.options.context || process.cwd());
    });

    compiler.apply(new webpack.ProgressPlugin(function(percent) {
        status.publish('compiling');
        progress.publish(percent);
    }));

    compiler.plugin('emit', function(compilation, done) {
        var assets = [];
        var modulesMap = {};
        var chunksMap = {};
        var modules;
        var chunks;
        var fileModulesMap = {};
        var entryPoints = {};
        var handledFiles = {};

        for (var assetName in compilation.assets) {
            if (compilation.assets.hasOwnProperty(assetName)) {
                assets.push({
                    name: assetName,
                    size: compilation.assets[assetName].size()
                });
            }
        }

        chunks = compilation.chunks.map(function(chunk) {
            var chunkInfo = {
                id: chunk.id,
                name: chunk.name,
                size: chunk.size({}),
                hash: chunk.renderedHash,
                files: chunk.files,
                reasons: chunk.parents.map(function(chunk) {
                    return chunk.id;
                }),
                dependencies: chunk.modules.map(function(module) {
                    return getModuleId(module, compiler);
                }),
                rendered: chunk.rendered,
                // webpack 1.x capability
                initial: typeof chunk.isInitial == 'function' ? chunk.isInitial() : chunk.initial,
                // webpack 1.x capability
                entry: typeof chunk.hasRuntime == 'function' ? chunk.hasRuntime() : chunk.entry
            };

            chunksMap[chunkInfo.id] = chunksMap;

            return chunkInfo
        });

        modules = compilation.modules.map(function(module) {
            var moduleType = modulesTypeMap[module.constructor.name] || 'unknown';
            var resource = module.resource ? handleFile(module.resource, handledFiles) : null;

            // webpack 1.x capability
            if (/^1\./.test(webpackVersion)) {
                var identifier = module.identifier();

                if (!identifier.indexOf('multi')) {
                    moduleType = 'multi';
                } else if (module.regExp) {
                    moduleType = 'context';
                } else if (!identifier.indexOf('delegated')) {
                    moduleType = 'delegated';
                } else if (!identifier.indexOf('external')) {
                    moduleType = 'external';
                } else if (resource) {
                    moduleType = 'normal';
                } else {
                    moduleType = 'unknown';
                }
            }

            var moduleInfo = {
                id: getModuleId(module, compiler),
                index: module.index,
                type: moduleType,
                name: module.readableIdentifier(requestShortener),
                size: module.size(),
                rawRequest: unixpath(module.rawRequest),
                userRequest: unixpath(module.userRequest),
                context: unixpath(module.context),
                resource: resource,
                loaders: getModuleLoaders(module, handledFiles),
                dependencies: module.dependencies
                    .filter(function(dependency) {
                        return dependency.module;
                    })
                    .map(function(dependency) {
                        return getModuleId(dependency.module, compiler);
                    }),
                reasons: module.reasons
                    .filter(function(reason) {
                        return reason.dependency && reason.module;
                    })
                    .map(function(reason) {
                        return getModuleId(reason.module);
                    })
            };

            if (resource) {
                var mapItem = fileModulesMap[resource.name];

                if (mapItem) {
                    mapItem.push(moduleInfo.id);
                } else {
                    fileModulesMap[resource.name] = [moduleInfo.id];
                }
            }

            modulesMap[moduleInfo.id] = moduleInfo;
            moduleInfo.isEntry = !moduleInfo.reasons.length;

            if (moduleInfo.isEntry) {
                entryPoints[moduleInfo.id] = true;
            }

            return moduleInfo;
        });

        entryPoints = Object.keys(entryPoints);

        // console.time('Retained + Exclusive');

        modules.forEach(function(module) {
            var allSubtreeExceptSelected = {};

            module.retained = getRetained(module, modulesMap);

            if (module.retained.indexOf(module.id) == -1) {
                module.retained.push(module.id);
            }

            for (var i = 0; i < entryPoints.length; i++) {
                var nodes = getRetained(modulesMap[entryPoints[i]], modulesMap, module);

                for (var j = 0; j < nodes.length; j++) {
                    allSubtreeExceptSelected[nodes[j]] = true;
                }
            }

            module.exclusive = module.retained.filter(function(exclusive) {
                return exclusive != module.id && !allSubtreeExceptSelected[exclusive];
            });

            var ix = module.retained.indexOf(module.id);

            if (ix > -1) {
                module.retained.splice(ix, 1)
            }
        });

        // console.timeEnd('Retained + Exclusive');

        var profileData = {
            version: webpackVersion,
            hash: compilation.hash,
            context: unixpath(compilation.compiler.context),
            assets: assets,
            chunks: chunks,
            modules: modules,
            moduleLinks: compilation.modules.reduce(function(prev, current) {
                var links = current.reasons
                    .filter(function(reason) {
                        return reason.dependency && reason.module;
                    }).map(function(reason) {
                        var reasonInfo = {
                            from: getModuleId(reason.module, compiler),
                            to: getModuleId(current, compiler)
                        };

                        if (!reason.module.useSourceMap) {
                            return reasonInfo;
                        }

                        // todo handle string value
                        var location = isObject(reason.dependency.loc) ? deepExtend({}, reason.dependency.loc) : null;

                        if (!location || !location.start) {
                            return reasonInfo;
                        }

                        var variable = reason.module.variables
                            .filter(function(variable) {
                                return variable.dependencies.some(function(dependency) {
                                    return dependency.module == current;
                                });
                            })[0];

                        if (variable) {
                            reasonInfo.position = null;
                            reasonInfo.variable = {
                                name: variable.name,
                                expression: variable.expression
                            }
                        } else {
                            var originalSource = reason.module.originalSource ?
                                reason.module.originalSource() :
                                reason.module._source;
                            var sourceMapConsumer = new SourceMapConsumer(originalSource.map());
                            var originalPosition = sourceMapConsumer.originalPositionFor(location.start);

                            if (isFinite(originalPosition.line)) {
                                reasonInfo.position = {
                                    line: originalPosition.line,
                                    column: originalPosition.column,
                                };

                                if (isFinite(originalPosition.column)) {
                                    reasonInfo.position.column++
                                }
                            }
                        }

                        return reasonInfo;
                    });

                return prev.concat(links);
            }, []),
            fileLinks: Object.keys(fileModulesMap).map(function(fileName) {
                return {
                    id: fileName,
                    file: fileName,
                    modules: fileModulesMap[fileName]
                };
            }),
            errors: compilation.errors.map(function(error) {
                return {
                    message: error.message,
                    module: error.module && getModuleId(error.module, compiler)
                };
            }),
            warnings: compilation.warnings.map(function(warning) {
                return {
                    message: warning.message,
                    module: warning.module && getModuleId(warning.module, compiler)
                };
            })
        };

        profile.publish(profileData);

        done();

        if (publisher.wsendpoint) {
            // use timer to output link after all the stats is shown
            setTimeout(function() {
                console.log('\nWebpack Runtime Analyzer is available on', publisher.wsendpoint);
            }, 0);
        }
    });

    compiler.plugin('compile', function() {
        status.publish('compiling');
    });

    compiler.plugin('invalid', function() {
        status.publish('invalidated');
    });

    compiler.plugin('done', function() {
        status.publish(stats.errors.length ? 'failed' : 'success');
    });

    compiler.plugin('failed', function() {
        status.publish('failed');
    });

    return publisher;
}

function RuntimeAnalyzerPlugin(options) {
    this.publisher = null;

    // mix with default options
    this.options = deepExtend({
        mode: 'standalone',
        port: 0,
        watchModeOnly: true,
        ui: path.resolve(__dirname, '../../dist/script.js')
    }, options);
}

RuntimeAnalyzerPlugin.prototype.apply = function(compiler) {
    var options = this.options;

    compiler.plugin('watch-run', pluginFn.bind(this, true));

    if (!this.options.watchModeOnly) {
        compiler.plugin('run', pluginFn.bind(this, false));
    }

    function pluginFn(isWatchMode, watchingOrCompiler, done) {
        if (isWatchMode) {
            this.watching = watchingOrCompiler;
        }

        if (!this.publisher) {
            this.publisher = createPublisher(this, compiler, options);

            // run server after publisher is created
            // since server uses publisher when started
            if (options.mode === 'standalone') {
                startRemplServer(this);
            }
        }

        done();
    }
};

module.exports = RuntimeAnalyzerPlugin;
<|endoftext|>


<commit_before>'use strict'

const chai = require('chai')
const RomanNumber = require('../lib/roman-converter')
const util = require('util')

chai.should()
const expect = chai.expect

/* global describe it */
/* eslint no-new: off */

describe('RomanNumber', function () {
  describe('constructor', function () {
    it('should throw value required on null ', function () {
      (function () {
        new RomanNumber()
      }).should.throw('value required')
    })
    it('should throw value required on undefined ', function () {
      (function () {
        new RomanNumber()
      }).should.throw('value required')
    })
    it('should throw invalid range on too big number', function () {
      (function () {
        new RomanNumber(9999)
      }).should.throw('invalid range')
    })
    it('should throw invalid value on unexpected input', function () {
      (function () {
        new RomanNumber(new Date())
      }).should.throw('invalid value')
    })
  })
  describe.only('parse', function () {
    const testValues = [
      [null, new Error('value required')],
      ['', new Error('value required')],
      ['I', 1],
      ['III', 3],
      ['IIII', 4],
      ['IV', 4],
      ['V', 5],
      ['1473', new Error('invalid value')],
      ['CDXXIX', 429],
      ['CD1X', new Error('invalid value')],
      ['error', new Error('invalid value')],
      ['MCDLXXXII', 1482],
      ['MCMLXXX', 1980],
      ['MMMMCMXCIX', 4999],
      ['MMMMDMXCIX', new Error('invalid value')]
    ]

    testValues.forEach(([value, result]) => {
      if (result instanceof Error) {
        it(`should throw ${result.message} on ${util.inspect(value)}`, function () {
          expect(() => {
            new RomanNumber(value)
          }).to.throw(result.message)
        })

        return
      }

      it(`should return ${util.inspect(result)} on ${util.inspect(value)}`, function () {
        const romanNumber = new RomanNumber(value)

        romanNumber.toInt().should.equal(result)
      })
    })
  })
})
<commit_msg>Added missing test values<commit_after>'use strict'

const chai = require('chai')
const RomanNumber = require('../lib/roman-converter')
const util = require('util')

chai.should()
const expect = chai.expect

/* global describe it */
/* eslint no-new: off */

describe('RomanNumber', function () {
  describe('constructor', function () {
    it('should throw value required on null ', function () {
      (function () {
        new RomanNumber()
      }).should.throw('value required')
    })
    it('should throw value required on undefined ', function () {
      (function () {
        new RomanNumber()
      }).should.throw('value required')
    })
    it('should throw invalid range on too big number', function () {
      (function () {
        new RomanNumber(9999)
      }).should.throw('invalid range')
    })
    it('should throw invalid value on unexpected input', function () {
      (function () {
        new RomanNumber(new Date())
      }).should.throw('invalid value')
    })
  })

  describe('parse', function () {
    const testValues = [
      [null, new Error('value required')],
      ['', new Error('value required')],
      [0, 0],
      [1, 1],
      [3, 3],
      [4, 4],
      [5, 5],
      ['I', 1],
      ['III', 3],
      ['IIII', 4], // FIXME: only 3 repeating digits allowed
      ['IV', 4],
      ['V', 5],
      [1968, 1968],
      ['1473', new Error('invalid value')],
      [2999, 2999],
      [3000, 3000],
      [10000, new Error('invalid range')],
      ['CDXXIX', 429],
      ['CD1X', new Error('invalid value')],
      ['error', new Error('invalid value')],
      ['MCDLXXXII', 1482],
      ['MCMLXXX', 1980],
      ['MMMMCMXCIX', 4999],
      ['MMMMDMXCIX', new Error('invalid value')]
    ]

    testValues.forEach(([value, result]) => {
      if (result instanceof Error) {
        it(`should throw ${result.message} on ${util.inspect(value)}`, function () {
          expect(() => {
            new RomanNumber(value)
          }).to.throw(result.message)
        })

        return
      }

      it(`should return ${util.inspect(result)} on ${util.inspect(value)}`, function () {
        const romanNumber = new RomanNumber(value)

        romanNumber.toInt().should.equal(result)
      })
    })
  })
})
<|endoftext|>


<commit_before>import {
  validateIsObject,
  combine,
} from '../validators.js';
import {
  each,
} from '../utils.js';

const pluginHash = {
  compile(compiler, schemaDef, schemaOptions) {
    if (schemaDef instanceof compiler.Schema.Hash) {
      const valueSchema = compiler.compile(schemaDef.valueSchemaDef, schemaOptions);
      const keySchema = compiler.compile(schemaDef.keySchemaDef, schemaOptions);
      return {
        typeName: `hash of ${valueSchema.typeName}`,
        compiled: true,
        validate: combine([
          validateIsObject,
          (value) => {
            let hasErrors = false;
            const errors = {};
            each(value, (x, key) => {
              const keyErrors = keySchema.validate(key);
              if (keyErrors) {
                hasErrors = true;
                errors[key] = keyErrors;
              } else {
                const valueErrors = valueSchema.validate(x);
                if (valueErrors) {
                  hasErrors = true;
                }
                errors[key] = valueErrors;
              }
            });
            return hasErrors ? { errors } : undefined;
          },
        ]),
        getSubSchema: () => valueSchema,
      };
    }
    return null;
  },
  mixin(Schema) {
    class Hash {
      constructor({ value: valueSchemaDef, key: keySchemaDef = String }) {
        Object.assign(this, {
          keySchemaDef,
          valueSchemaDef,
        });
      }
    }
    Object.assign(Schema, {
      Hash,
      hash: (schemaDef, schemaOptions) =>
        new Schema(new Hash(schemaDef), schemaOptions),
    });
  },
};

export default pluginHash;
<commit_msg>Prevent passing hash options to value/ key schemas<commit_after>import {
  validateIsObject,
  combine,
} from '../validators.js';
import {
  each,
} from '../utils.js';

const pluginHash = {
  compile(compiler, schemaDef) {
    if (schemaDef instanceof compiler.Schema.Hash) {
      const valueSchema = compiler.compile(schemaDef.valueSchemaDef);
      const keySchema = compiler.compile(schemaDef.keySchemaDef);
      return {
        typeName: `hash of ${valueSchema.typeName}`,
        compiled: true,
        validate: combine([
          validateIsObject,
          (value) => {
            let hasErrors = false;
            const errors = {};
            each(value, (x, key) => {
              const keyErrors = keySchema.validate(key);
              if (keyErrors) {
                hasErrors = true;
                errors[key] = keyErrors;
              } else {
                const valueErrors = valueSchema.validate(x);
                if (valueErrors) {
                  hasErrors = true;
                }
                errors[key] = valueErrors;
              }
            });
            return hasErrors ? { errors } : undefined;
          },
        ]),
        getSubSchema: () => valueSchema,
      };
    }
    return null;
  },
  mixin(Schema) {
    class Hash {
      constructor({ value: valueSchemaDef, key: keySchemaDef = String }) {
        Object.assign(this, {
          keySchemaDef,
          valueSchemaDef,
        });
      }
    }
    Object.assign(Schema, {
      Hash,
      hash: (schemaDef, schemaOptions) =>
        new Schema(new Hash(schemaDef), schemaOptions),
    });
  },
};

export default pluginHash;
<|endoftext|>


<commit_before>if (typeof(exports) === 'undefined') {
  exports = {};
}

var fingerId = 0
  , handId = 0
  , frameId =0
  , lastGestureState = false;

var fakeController = exports.fakeController = function(opts) {
  opts = _.defaults(opts || {}, {supressAnimationLoop: false, frameEventName: "deviceFrame", enableHeartbeat:false, version: 1})
  var controller = new Leap.Controller(opts)
  var connection = controller.connection;

  lastGestureState = opts.enableGestures;

  connection.setupSocket = function() {
    setTimeout(function() { connection.handleOpen() }, 1)
    var socket = {
      messages: [],
      send: function(message) {
        socket.messages.push(message);
      },
      close: function() { }
    };
    connection.on('connect', function() {
      connection.handleData(JSON.stringify({version: opts.version}))
    });
    return socket;
  }
  return controller;
}

var fakeFrame = exports.fakeFrame = function(opts) {
  if (opts === undefined) opts = {};

  handId = 0
  fingerId = 0

  var frame = {
    id: opts.id || ++frameId,
    valid: true,
    timestamp: frameId,
    pointables: _(opts.fingers || 0).times(function() { return fakeFinger() }),
    hands: opts.handData || _(opts.hands || 0).times(function() { return fakeHand() }),
    r: opts.rotation || [[0,1,2], [2,3,4], [2,3,4]],
    t: opts.translation || [1, 2, 3],
    interactionBox: {center: [1,2,3], size: [1,2,3]},
    currentFramerate: 10
  };
  if (opts.gestures) {
    frame.gestures = opts.gestures;
  }
  return frame;
};

var fakeGesture = exports.fakeGesture = function(opts) {
  if (opts === undefined) opts = {
    type:"circle",
    center:[2,3,4],
    normal:[5,6,7],
    progress: 10,
    radius: 20
  };
  return opts;
};

var fakeHand = exports.fakeHand = function(opts) {
  if (opts === undefined) opts = {};
  handId++
  return {
    id: handId - 1,
    valid: true,
    palmPosition: [1,2,3],
    direction: [1,2,3],
    palmVelocity: [1,2,3],
    palmNormal: [1,2,3],
    sphereCenter:[1,2,3],
    r: (opts && opts.rotation) || [[0,1,2], [2,3,4], [2,3,4]],
    t: (opts && opts.translation) || [1, 2, 3],
    timeVisible: 10,
    stabilizedPalmPosition: [1,2,3]
  }
}

var fakeFinger = exports.fakeFinger = function() {
  fingerId++
  return {
    id: fingerId - 1,
    handId: 0,
    length: 5,
    tool: false,
    width: 5,
    direction: [10, 10, 10],
    tipPosition: [10, 10, 10],
    stabilizedTipPosition: [10, 10, 10],
    tipVelocity: [10, 10, 10],
    touchZone: "none",
    touchDistance: 5,
    timeVisible: 10
  }
}
<commit_msg>fix field name in fakeFrame<commit_after>if (typeof(exports) === 'undefined') {
  exports = {};
}

var fingerId = 0
  , handId = 0
  , frameId =0
  , lastGestureState = false;

var fakeController = exports.fakeController = function(opts) {
  opts = _.defaults(opts || {}, {supressAnimationLoop: false, frameEventName: "deviceFrame", enableHeartbeat:false, version: 1})
  var controller = new Leap.Controller(opts)
  var connection = controller.connection;

  lastGestureState = opts.enableGestures;

  connection.setupSocket = function() {
    setTimeout(function() { connection.handleOpen() }, 1)
    var socket = {
      messages: [],
      send: function(message) {
        socket.messages.push(message);
      },
      close: function() { }
    };
    connection.on('connect', function() {
      connection.handleData(JSON.stringify({version: opts.version}))
    });
    return socket;
  }
  return controller;
}

var fakeFrame = exports.fakeFrame = function(opts) {
  if (opts === undefined) opts = {};

  handId = 0
  fingerId = 0

  var frame = {
    id: opts.id || ++frameId,
    valid: true,
    timestamp: frameId,
    pointables: _(opts.fingers || 0).times(function() { return fakeFinger() }),
    hands: opts.handData || _(opts.hands || 0).times(function() { return fakeHand() }),
    r: opts.rotation || [[0,1,2], [2,3,4], [2,3,4]],
    t: opts.translation || [1, 2, 3],
    interactionBox: {center: [1,2,3], size: [1,2,3]},
    currentFrameRate: 10
  };
  if (opts.gestures) {
    frame.gestures = opts.gestures;
  }
  return frame;
};

var fakeGesture = exports.fakeGesture = function(opts) {
  if (opts === undefined) opts = {
    type:"circle",
    center:[2,3,4],
    normal:[5,6,7],
    progress: 10,
    radius: 20
  };
  return opts;
};

var fakeHand = exports.fakeHand = function(opts) {
  if (opts === undefined) opts = {};
  handId++
  return {
    id: handId - 1,
    valid: true,
    palmPosition: [1,2,3],
    direction: [1,2,3],
    palmVelocity: [1,2,3],
    palmNormal: [1,2,3],
    sphereCenter:[1,2,3],
    r: (opts && opts.rotation) || [[0,1,2], [2,3,4], [2,3,4]],
    t: (opts && opts.translation) || [1, 2, 3],
    timeVisible: 10,
    stabilizedPalmPosition: [1,2,3]
  }
}

var fakeFinger = exports.fakeFinger = function() {
  fingerId++
  return {
    id: fingerId - 1,
    handId: 0,
    length: 5,
    tool: false,
    width: 5,
    direction: [10, 10, 10],
    tipPosition: [10, 10, 10],
    stabilizedTipPosition: [10, 10, 10],
    tipVelocity: [10, 10, 10],
    touchZone: "none",
    touchDistance: 5,
    timeVisible: 10
  }
}
<|endoftext|>


<commit_before>/*
 *	Copyright 2016 Victorien Elvinger
 *
 *	This program is free software: you can redistribute it and/or modify
 *	it under the terms of the GNU General Public License as published by
 *	the Free Software Foundation, either version 3 of the License, or
 * 	(at your option) any later version.
 *
 *	This program is distributed in the hope that it will be useful,
 *	but WITHOUT ANY WARRANTY; without even the implied warranty of
 *	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *	GNU General Public License for more details.
 *
 *	You should have received a copy of the GNU General Public License
 *	along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

import test from "ava"
import {InfiniteString} from "../lib/infinitestring.js"

test("has-next-prefix-less", (t) => {
    const repeatredValue = Symbol()
    const stream = new InfiniteString([], repeatredValue)

    t.true(stream.hasNext())
})

test("has-next-prefix-less-null-value", (t) => {
    const repeatredValue = null
    const stream = new InfiniteString([], repeatredValue)

    t.true(stream.hasNext())
})

test("next-prefix-less", (t) => {
    const repeatredValue = Symbol()
    const stream = new InfiniteString([], repeatredValue)

    t.is(stream.next(), repeatredValue)
})

test("next-with-prefix", (t) => {
    const repeatredValue = Symbol()
    const prefix = [Symbol(), Symbol()]
    const stream = new InfiniteString(prefix, repeatredValue)

    t.is(stream.next(), prefix[0])
    t.is(stream.next(), prefix[1])
    t.is(stream.next(), repeatredValue)
})

<commit_msg>refactor(test(infinitestring.js)): fix hasNext tests<commit_after>/*
 *	Copyright 2016 Victorien Elvinger
 *
 *	This program is free software: you can redistribute it and/or modify
 *	it under the terms of the GNU General Public License as published by
 *	the Free Software Foundation, either version 3 of the License, or
 * 	(at your option) any later version.
 *
 *	This program is distributed in the hope that it will be useful,
 *	but WITHOUT ANY WARRANTY; without even the implied warranty of
 *	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *	GNU General Public License for more details.
 *
 *	You should have received a copy of the GNU General Public License
 *	along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

import test from "ava"
import {InfiniteString} from "../lib/infinitestring.js"

test("has-next-prefix-less", (t) => {
    const repeatredValue = Symbol()
    const stream = new InfiniteString([], repeatredValue)

    t.not(stream.next(), undefined)
})

test("has-next-prefix-less-null-value", (t) => {
    const repeatredValue = null
    const stream = new InfiniteString([], repeatredValue)

    t.not(stream.next(), undefined)
})

test("next-prefix-less", (t) => {
    const repeatredValue = Symbol()
    const stream = new InfiniteString([], repeatredValue)

    t.is(stream.next(), repeatredValue)
})

test("next-with-prefix", (t) => {
    const repeatredValue = Symbol()
    const prefix = [Symbol(), Symbol()]
    const stream = new InfiniteString(prefix, repeatredValue)

    t.is(stream.next(), prefix[0])
    t.is(stream.next(), prefix[1])
    t.is(stream.next(), repeatredValue)
})
<|endoftext|>


<commit_before>import './support';

import SystemdInstaller from '../src/job-installers/systemd';
import DockerInstaller from '../src/job-installers/docker';
import TaskSchedulerInstaller from '../src/job-installers/task-scheduler';
import {installJob} from '../src/job-installer-api';

const d = require('debug')('surf-test:job-installers');

describe('systemd job installer', function() {
  beforeEach(function() {
    this.fixture = new SystemdInstaller();
    this.sampleName = 'example-csharp';
    this.sampleCommand = 'surf-build -r https://github.com/surf-build/example-csharp -- surf-build -n "surf"';
  });

  it('should capture Surf environment variables', async function() {
    process.env.SURF_TEST_ENV_VAR = 'hello';
    let result = await this.fixture.installJob(this.sampleName, this.sampleCommand, true);
    delete process.env.SURF_TEST_ENV_VAR;

    d(result);

    expect(
      result[`${this.sampleName}.service`].split('\n')
        .find((l) => l.match(/Environment.*SURF_TEST_ENV_VAR.*hello/))
    ).to.be.ok;
  });

  it('should have the command as ExecPath', async function() {
    let result = await this.fixture.installJob(this.sampleName, this.sampleCommand, true);

    let execStartLine = result[`${this.sampleName}.service`].split('\n')
      .find((l) => l.match(/ExecStart/));
    expect(execStartLine.indexOf(this.sampleCommand) > 0).to.be.ok;
  });

  it('should ensure that absolute paths are rooted', async function() {
    let result = await this.fixture.installJob(this.sampleName, 'ls -al', true);

    let execStartLine = result[`${this.sampleName}.service`].split('\n').find((l) => l.match(/ExecStart/));
    expect(execStartLine.indexOf('/usr/bin/ls') > 0).to.be.ok;
  });
});

describe('docker job installer', function() {
  beforeEach(function() {
    this.fixture = new DockerInstaller();
    this.sampleName = 'example-csharp';
    this.sampleCommand = 'surf-build -r https://github.com/surf-build/example-csharp -- surf-build -n "surf"';
  });

  it('should capture Surf environment variables', async function() {
    process.env.SURF_TEST_ENV_VAR = 'hello';
    let result = await this.fixture.installJob(this.sampleName, this.sampleCommand, true);
    delete process.env.SURF_TEST_ENV_VAR;

    d(result);

    expect(
      result['Dockerfile'].split('\n')
        .find((l) => l.match(/ENV .*SURF_TEST_ENV_VAR.*hello/))
    ).to.be.ok;
  });

  it('should have the command as CMD', async function() {
    expect((await this.fixture.getAffinityForJob(this.sampleName, this.sampleCommand)) > 0).to.be.ok;

    let result = await this.fixture.installJob(this.sampleName, this.sampleCommand, true);

    let execStartLine = result['Dockerfile'].split('\n').find((l) => l.match(/CMD/));
    expect(execStartLine.indexOf(this.sampleCommand.split(' ')[0]) > 0).to.be.ok;
  });
});

describe('Task scheduler job installer', function() {
  if (process.platform !== 'win32') return;

  beforeEach(function() {
    this.fixture = new TaskSchedulerInstaller();
    this.sampleName = 'example-csharp';
    this.sampleCommand = 'surf-build -r https://github.com/surf-build/example-csharp -- surf-build -n "surf"';
  });

  it('should capture Surf environment variables', async function() {
    process.env.SURF_TEST_ENV_VAR = 'hello';
    let result = await this.fixture.installJob(this.sampleName, this.sampleCommand, true);
    delete process.env.SURF_TEST_ENV_VAR;

    d(result);

    expect(
      result[`${name}.cmd`].split('\n')
        .find((l) => l.match(/SET.*SURF_TEST_ENV_VAR.*hello/))
    ).to.be.ok;
  });

  it('should have the command', async function() {
    expect((await this.fixture.getAffinityForJob(this.sampleName, this.sampleCommand)) > 0).to.be.ok;

    let result = await this.fixture.installJob(this.sampleName, this.sampleCommand, true);

    let execStartLine = result[`${name}.cmd`].split('\n')
      .find((l) => l.match(/surf-build/));
    expect(execStartLine).to.be.ok;
  });
});

describe('Job installer API', function() {
  beforeEach(function() {
    this.sampleName = 'example-csharp';
    this.sampleCommand = 'surf-build -r https://github.com/surf-build/example-csharp -- surf-build -n "surf"';
  });

  it('should return some content', async function() {
    let result = await installJob(this.sampleName, this.sampleCommand, true);
    let keys = Object.keys(result);
    expect(result[keys[0]].split('\n').length > 2).to.be.ok;
  });

  it('should allow us to explicitly select the Docker API', async function() {
    let result = await installJob(this.sampleName, this.sampleCommand, true, 'docker');

    let lines = result['Dockerfile'].split('\n');

    expect(lines.length > 2).to.be.ok;
    expect(lines.find((x) => x.match(/^CMD /))).to.be.ok;
    expect(lines.find((x) => x.match(/^ExecPath/))).not.to.be.ok;
  });
});
<commit_msg>hwhoops<commit_after>import './support';

import SystemdInstaller from '../src/job-installers/systemd';
import DockerInstaller from '../src/job-installers/docker';
import TaskSchedulerInstaller from '../src/job-installers/task-scheduler';
import {installJob} from '../src/job-installer-api';

const d = require('debug')('surf-test:job-installers');

describe('systemd job installer', function() {
  beforeEach(function() {
    this.fixture = new SystemdInstaller();
    this.sampleName = 'example-csharp';
    this.sampleCommand = 'surf-build -r https://github.com/surf-build/example-csharp -- surf-build -n "surf"';
  });

  it('should capture Surf environment variables', async function() {
    process.env.SURF_TEST_ENV_VAR = 'hello';
    let result = await this.fixture.installJob(this.sampleName, this.sampleCommand, true);
    delete process.env.SURF_TEST_ENV_VAR;

    d(result);

    expect(
      result[`${this.sampleName}.service`].split('\n')
        .find((l) => l.match(/Environment.*SURF_TEST_ENV_VAR.*hello/))
    ).to.be.ok;
  });

  it('should have the command as ExecPath', async function() {
    let result = await this.fixture.installJob(this.sampleName, this.sampleCommand, true);

    let execStartLine = result[`${this.sampleName}.service`].split('\n')
      .find((l) => l.match(/ExecStart/));
    expect(execStartLine.indexOf(this.sampleCommand) > 0).to.be.ok;
  });

  it('should ensure that absolute paths are rooted', async function() {
    let result = await this.fixture.installJob(this.sampleName, 'ls -al', true);

    let execStartLine = result[`${this.sampleName}.service`].split('\n').find((l) => l.match(/ExecStart/));
    expect(execStartLine.indexOf('/usr/bin/ls') > 0).to.be.ok;
  });
});

describe('docker job installer', function() {
  beforeEach(function() {
    this.fixture = new DockerInstaller();
    this.sampleName = 'example-csharp';
    this.sampleCommand = 'surf-build -r https://github.com/surf-build/example-csharp -- surf-build -n "surf"';
  });

  it('should capture Surf environment variables', async function() {
    process.env.SURF_TEST_ENV_VAR = 'hello';
    let result = await this.fixture.installJob(this.sampleName, this.sampleCommand, true);
    delete process.env.SURF_TEST_ENV_VAR;

    d(result);

    expect(
      result['Dockerfile'].split('\n')
        .find((l) => l.match(/ENV .*SURF_TEST_ENV_VAR.*hello/))
    ).to.be.ok;
  });

  it('should have the command as CMD', async function() {
    expect((await this.fixture.getAffinityForJob(this.sampleName, this.sampleCommand)) > 0).to.be.ok;

    let result = await this.fixture.installJob(this.sampleName, this.sampleCommand, true);

    let execStartLine = result['Dockerfile'].split('\n').find((l) => l.match(/CMD/));
    expect(execStartLine.indexOf(this.sampleCommand.split(' ')[0]) > 0).to.be.ok;
  });
});

describe('Task scheduler job installer', function() {
  if (process.platform !== 'win32') return;

  beforeEach(function() {
    this.fixture = new TaskSchedulerInstaller();
    this.sampleName = 'example-csharp';
    this.sampleCommand = 'surf-build -r https://github.com/surf-build/example-csharp -- surf-build -n "surf"';
  });

  it('should capture Surf environment variables', async function() {
    process.env.SURF_TEST_ENV_VAR = 'hello';
    let result = await this.fixture.installJob(this.sampleName, this.sampleCommand, true);
    delete process.env.SURF_TEST_ENV_VAR;

    d(result);

    expect(
      result[`${this.sampleName}.cmd`].split('\n')
        .find((l) => l.match(/SET.*SURF_TEST_ENV_VAR.*hello/))
    ).to.be.ok;
  });

  it('should have the command', async function() {
    expect((await this.fixture.getAffinityForJob(this.sampleName, this.sampleCommand)) > 0).to.be.ok;

    let result = await this.fixture.installJob(this.sampleName, this.sampleCommand, true);

    let execStartLine = result[`${this.sampleName}.cmd`].split('\n')
      .find((l) => l.match(/surf-build/));
    expect(execStartLine).to.be.ok;
  });
});

describe('Job installer API', function() {
  beforeEach(function() {
    this.sampleName = 'example-csharp';
    this.sampleCommand = 'surf-build -r https://github.com/surf-build/example-csharp -- surf-build -n "surf"';
  });

  it('should return some content', async function() {
    let result = await installJob(this.sampleName, this.sampleCommand, true);
    let keys = Object.keys(result);
    expect(result[keys[0]].split('\n').length > 2).to.be.ok;
  });

  it('should allow us to explicitly select the Docker API', async function() {
    let result = await installJob(this.sampleName, this.sampleCommand, true, 'docker');

    let lines = result['Dockerfile'].split('\n');

    expect(lines.length > 2).to.be.ok;
    expect(lines.find((x) => x.match(/^CMD /))).to.be.ok;
    expect(lines.find((x) => x.match(/^ExecPath/))).not.to.be.ok;
  });
});
<|endoftext|>


<commit_before>'use strict';

var path = require('path');

var grunt = require('grunt');

var exec = require('../tasks/lib/helpers/exec');
var messages = require('../tasks/lib/messages');
var gitHelper = require('../tasks/lib/helpers/git');
var dependenciesHelper = require('../tasks/lib/helpers/dependencies');
var stdoutEqual = require('./helpers/stdout_equal.js');
var version = require('../tasks/lib/version');

/*
  ======== A Handy Little Nodeunit Reference ========
  https://github.com/caolan/nodeunit

  Test methods:
    test.expect(numAssertions)
    test.done()
  Test assertions:
    test.ok(value, [message])
    test.equal(actual, expected, [message])
    test.notEqual(actual, expected, [message])
    test.deepEqual(actual, expected, [message])
    test.notDeepEqual(actual, expected, [message])
    test.strictEqual(actual, expected, [message])
    test.notStrictEqual(actual, expected, [message])
    test.throws(block, [error], [message])
    test.doesNotThrow(block, [error], [message])
    test.ifError(value)
*/

exports.sg_release = {

  setUp: function (done) {
    // setup here if necessary
    done();
  },

  // ---

  noGitAvailable: function (test) {
    test.expect(1);

    var gitExecName = gitHelper.execName;
    var gruntFatal = grunt.fatal;

    gitHelper.execName = 'nonExistingGitCommand';
    grunt.fatal = function mockGruntFatal(msg) {
      test.equal(msg, messages.noGitError);
    };

    gitHelper.check(grunt);

    gitHelper.execName = gitExecName;
    grunt.fatal = gruntFatal;

    test.done();
  },

  // ---

  testDependencies: function (test) {
    test.expect(0);

    // TODO: would be better to have folder passed as a Gruntfile option
    // but grunt-contrib-nodeunit doesn't really have an option for that
    // https://github.com/gruntjs/grunt-contrib-nodeunit/issues/26
    var dir = path.resolve('tmp');

    function checkBowerInstall() {
      dependenciesHelper.checkInstall(grunt, dir, 'bower', test.done);
    }

    dependenciesHelper.checkInstall(grunt, dir, 'npm', checkBowerInstall);
  },

  // ---

  testReleaseVersion: function (test) {
    test.expect(1);

    var defaultVersion = '1.2.0';

    version.releaseQuestion.default = defaultVersion;
    version.getRelease(grunt, function() {
      test.equal(grunt.option('setversion'), defaultVersion);
      test.done();
    }, true);
  },

  // ---

  testNextVersion: function (test) {
    test.expect(1);

    var defaultVersion = '1.2.1-rc';

    version.releaseQuestion.default = defaultVersion;
    version.getNext(grunt, function() {
      test.equal(grunt.option('setversion'), defaultVersion);
      test.done();
    }, true);
  },

  // ---

  testCreateNewBranch: function (test) {
    test.expect(1);

    var dir = path.resolve('tmp');
    var releaseVersion = '1.2.0';
    var branchName = '-b ' + 'release' + '/' + releaseVersion;
    gitHelper.checkout(grunt, dir, branchName, function () {
      exec('git branch', {
        grunt: grunt,
        dir: dir,
        done: function (stdout) {
          // output should contain the new branch name
          test.notEqual(stdout.indexOf('release/' + releaseVersion), -1);
          test.done();
        }
      });
    });
  },

  // ---

  testCheckoutMaster: function (test) {
    test.expect(1);

    var dir = path.resolve('tmp');
    gitHelper.checkout(grunt, dir, 'master', function (stdout) {
      exec('git branch', {
        grunt: grunt,
        dir: dir,
        done: function (stdout) {
          // output should contain correct current branch indication
          test.notEqual(stdout.indexOf('* master'), -1);
          test.done();
        }
      });
    });
  },

  // ---

  testMergeIntoMaster: function (test) {
    test.expect(1);

    var dir = path.resolve('tmp');
    var releaseVersion = '1.2.0';
    gitHelper.merge(grunt, dir, 'release/' + releaseVersion, messages.mergeToMasterMsg, function (stdout) {
      // output should contain success message
      test.notEqual(stdout.indexOf('Merge made'), -1);
      test.done();
    });
  }

};

<commit_msg>Refactored temp dir and release branch variables on tests<commit_after>'use strict';

var path = require('path');

var grunt = require('grunt');

var exec = require('../tasks/lib/helpers/exec');
var messages = require('../tasks/lib/messages');
var gitHelper = require('../tasks/lib/helpers/git');
var dependenciesHelper = require('../tasks/lib/helpers/dependencies');
var stdoutEqual = require('./helpers/stdout_equal.js');
var version = require('../tasks/lib/version');

/*
  ======== A Handy Little Nodeunit Reference ========
  https://github.com/caolan/nodeunit

  Test methods:
    test.expect(numAssertions)
    test.done()
  Test assertions:
    test.ok(value, [message])
    test.equal(actual, expected, [message])
    test.notEqual(actual, expected, [message])
    test.deepEqual(actual, expected, [message])
    test.notDeepEqual(actual, expected, [message])
    test.strictEqual(actual, expected, [message])
    test.notStrictEqual(actual, expected, [message])
    test.throws(block, [error], [message])
    test.doesNotThrow(block, [error], [message])
    test.ifError(value)
*/

// TODO: would be better to have folder passed as a Gruntfile option
// but grunt-contrib-nodeunit doesn't really have an option for that
// https://github.com/gruntjs/grunt-contrib-nodeunit/issues/26
var dir = path.resolve('tmp');
var releaseVersion = '1.2.0';
var releaseBranchName = 'release/v' + releaseVersion;

exports.sg_release = {

  setUp: function (done) {
    // setup here if necessary
    done();
  },

  // ---

  noGitAvailable: function (test) {
    test.expect(1);

    var gitExecName = gitHelper.execName;
    var gruntFatal = grunt.fatal;

    gitHelper.execName = 'nonExistingGitCommand';
    grunt.fatal = function mockGruntFatal(msg) {
      test.equal(msg, messages.noGitError);
    };

    gitHelper.check(grunt);

    gitHelper.execName = gitExecName;
    grunt.fatal = gruntFatal;

    test.done();
  },

  // ---

  testDependencies: function (test) {
    test.expect(0);

    function checkBowerInstall() {
      dependenciesHelper.checkInstall(grunt, dir, 'bower', test.done);
    }

    dependenciesHelper.checkInstall(grunt, dir, 'npm', checkBowerInstall);
  },

  // ---

  testReleaseVersion: function (test) {
    test.expect(1);

    var defaultVersion = releaseVersion;

    version.releaseQuestion.default = defaultVersion;
    version.getRelease(grunt, function() {
      test.equal(grunt.option('setversion'), defaultVersion);
      test.done();
    }, true);
  },

  // ---

  testNextVersion: function (test) {
    test.expect(1);

    var defaultVersion = '1.2.1-rc';

    version.releaseQuestion.default = defaultVersion;
    version.getNext(grunt, function() {
      test.equal(grunt.option('setversion'), defaultVersion);
      test.done();
    }, true);
  },

  // ---

  testCreateNewBranch: function (test) {
    test.expect(1);

    var releaseVersion = '1.2.0';
    gitHelper.checkout(grunt, dir, '-b ' + releaseBranchName, function () {
      exec('git branch', {
        grunt: grunt,
        dir: dir,
        done: function (stdout) {
          // output should contain the new branch name
          test.notEqual(stdout.indexOf(releaseBranchName), -1);
          test.done();
        }
      });
    });
  },

  // ---

  testCheckoutMaster: function (test) {
    test.expect(1);

    gitHelper.checkout(grunt, dir, 'master', function (stdout) {
      exec('git branch', {
        grunt: grunt,
        dir: dir,
        done: function (stdout) {
          // output should contain correct current branch indication
          test.notEqual(stdout.indexOf('* master'), -1);
          test.done();
        }
      });
    });
  },

  // ---

  testMergeIntoMaster: function (test) {
    test.expect(1);

    gitHelper.merge(grunt, dir, releaseBranchName, messages.mergeToMasterMsg, function (stdout) {
      // output should contain success message
      test.notEqual(stdout.indexOf('Merge made'), -1);
      test.done();
    });
  }

};

<|endoftext|>


<commit_before>// Global configuration uese for tests.
require('babel-core/register')({
  "presets": ["focus"]
});

const chai = require('chai');
const sinon = require('sinon');
const sinonChai = require('sinon-chai');
const chaiSubset = require('chai-subset');
chai.use(chaiSubset);
chai.use(sinonChai);

// Js dom
/*import jsdom from 'jsdom';
global.document = jsdom.jsdom('<!doctype html>');
global.window = document.defaultView;
global.navigator = {
    userAgent: 'node.js'
};
*/
const React = require('react');
// Globals
global.React = require('react');
global.ReactDOM = require('react-dom');
global.expect = chai.expect;
global.sinon = sinon;
global.TestUtils = require('react-addons-test-utils');
//global.sandbox = require('./sandbox');
global.componentHandler = {upgradeElement: function(){}};

// take all properties of the window object and also attach it to the
// from mocha-jsdom https://github.com/rstacruz/mocha-jsdom/blob/master/index.js#L80
/*function propagateToGlobal (window) {
    for (const key in window) {
        if (!window.hasOwnProperty(key)) {continue; }
        if (key in global) {continue; }
        global[key] = window[key];
    }
}
// mocha global object
propagateToGlobal(window);
*/

process.on('unhandledRejection', (error)=>{
    console.error('Unhandled Promise Rejection:');
    console.error(error && error.stack || error);
});
<commit_msg>[test] async await polyfill<commit_after>require('babel-polyfill');
// Global configuration uese for tests.
require('babel-core/register')({
  "presets": ["focus"]
});

const chai = require('chai');
const sinon = require('sinon');
const sinonChai = require('sinon-chai');
const chaiSubset = require('chai-subset');
chai.use(chaiSubset);
chai.use(sinonChai);

// Js dom
/*import jsdom from 'jsdom';
global.document = jsdom.jsdom('<!doctype html>');
global.window = document.defaultView;
global.navigator = {
    userAgent: 'node.js'
};
*/
const React = require('react');
// Globals
global.React = require('react');
global.ReactDOM = require('react-dom');
global.expect = chai.expect;
global.sinon = sinon;
global.TestUtils = require('react-addons-test-utils');
//global.sandbox = require('./sandbox');
global.componentHandler = {upgradeElement: function(){}};

// take all properties of the window object and also attach it to the
// from mocha-jsdom https://github.com/rstacruz/mocha-jsdom/blob/master/index.js#L80
/*function propagateToGlobal (window) {
    for (const key in window) {
        if (!window.hasOwnProperty(key)) {continue; }
        if (key in global) {continue; }
        global[key] = window[key];
    }
}
// mocha global object
propagateToGlobal(window);
*/

process.on('unhandledRejection', (error)=>{
    console.error('Unhandled Promise Rejection:');
    console.error(error && error.stack || error);
});
<|endoftext|>


<commit_before>'use strict';

const {makeBus} = require('../../src/utils')();

const router = () => {
    const emit = makeBus();
    const use = makeBus();
    require('../../src/modules/router')({emit, use}, window);
    require('../../src/modules/router.register')({emit, use}, window);
    require('../../src/modules/router.fetch')({emit, use}, window);
    return {emit, use};
};

describe('@okwolo/router', () => {
    beforeEach(() => {
        window.history.pushState({}, '', '/');
    });

    describe('emit', () => {
        describe('redirect', () => {
            it('should reject malformed paths', () => {
                const {emit} = router();
                expect(() => emit({redirect: undefined}))
                    .toThrow(/path/);
            });

            it('should accept no params', () => {
                const {emit} = router();
                expect(() => emit({redirect: {path: '/', params: undefined}}))
                    .not.toThrow(Error);
            });

            it('should change the pathname', () => {
                const {emit} = router();
                emit({redirect: {path: '/test/xyz'}});
                expect(window.location.pathname)
                    .toBe('/test/xyz');
            });

            it('should use a queue', () => {
                const test = jest.fn();
                const {emit, use} = router();
                use({route: {
                    path: '/xxx',
                    handler: () => {
                        emit({show: {path: '/test'}});
                        test();
                    },
                }});
                use({route: {
                    path: '/test',
                    handler: () => {
                        expect(test)
                            .toHaveBeenCalled();
                    },
                }});
                emit({redirect: {path: '/xxx'}});
            });

            it('should accumulate params and pass them to the handler', () => {
                const test = jest.fn();
                const {emit, use} = router();
                use({route: {
                    path: '/user/:id/fetch/:field',
                    handler: test,
                }});
                emit({redirect: {path: '/user/123/fetch/name'}});
                expect(test)
                    .toHaveBeenCalledWith({id: '123', field: 'name'});
            });
        });

        describe('show', () => {
            it('should reject malformed paths', () => {
                const {emit} = router();
                expect(() => emit({show: undefined}))
                    .toThrow(/path/);
            });

            it('should accept no params', () => {
                const {emit} = router();
                emit({show: {path: '/', params: undefined}});
            });

            it('should not change the pathname', () => {
                const {emit} = router();
                emit({show: {path: '/test/xyz'}});
                expect(window.location.pathname)
                    .not.toBe('/test/xyz');
            });

            it('should use a queue', () => {
                const test = jest.fn();
                const {emit, use} = router();
                use({route: {
                    path: '/xxx',
                    handler: () => {
                        emit({redirect: {path: '/test'}});
                        test();
                    },
                }});
                use({route: {
                    path: '/test',
                    handler: () => {
                        expect(test)
                            .toHaveBeenCalled();
                    },
                }});
                emit({show: {path: '/xxx'}});
            });

            it('should accumulate params and pass them to the handler', () => {
                const test = jest.fn();
                const {emit, use} = router();
                use({route: {
                    path: '/user/:id/fetch/:field',
                    handler: test,
                }});
                emit({show: {path: '/user/123/fetch/name'}});
                expect(test)
                    .toHaveBeenCalledWith({id: '123', field: 'name'});
            });
        });
    });

    describe('use', () => {
        describe('route', () => {
            it('should reject malformed paths', () => {
                const {use} = router();
                expect(() => {
                    use({route: {
                        path: {},
                        handler: () => {},
                    }});
                })
                    .toThrow(/path/);
            });

            it('should reject malformed handler', () => {
                const {use} = router();
                expect(() => {
                    use({route: {
                        path: '',
                        handler: '',
                    }});
                })
                    .toThrow(/handler/);
            });

            it('should check the current pathname against new routes', () => {
                const test = jest.fn();
                window.history.pushState({}, '', '/test');
                const {use} = router();
                use({route: {
                    path: '/test',
                    handler: test,
                }});
                expect(test)
                    .toHaveBeenCalled();
            });

            it('should save routes for future redirects', () => {
                const test = jest.fn();
                const {emit, use} = router();
                use({route: {
                    path: '/test',
                    handler: test,
                }});
                emit({redirect: {path: '/test'}});
                expect(test)
                    .toHaveBeenCalled();
            });

            it('should prioritize the earliest routes', () => {
                const test1 = jest.fn();
                const test2 = jest.fn();
                const {emit, use} = router();
                use({route: {
                    path: '/test',
                    handler: test1,
                }});
                use({route: {
                    path: '/*',
                    handler: test2,
                }});
                emit({redirect: {path: '/test'}});
                expect(test1)
                    .toHaveBeenCalled();
            });
        });

        describe('base', () => {
            it('should reject malformed inputs', () => {
                const {use} = router();
                expect(() => {
                    use({base: true});
                })
                    .toThrow(/base/);
            });

            it('should add the base url to all new pathnames', () => {
                const {emit, use} = router();
                use({base: '/testBase'});
                emit({redirect: {path: '/test'}});
                expect(window.location.pathname)
                    .toBe('/testBase/test');
            });

            it('should be applied to the current pathname', () => {
                const test = jest.fn();
                window.history.pushState({}, '', '/testBase/test');
                const {use} = router();
                use({route: {
                    path: '/test',
                    handler: test,
                }});
                expect(test)
                    .toHaveBeenCalledTimes(0);
                use({base: '/testBase'});
                expect(test)
                    .toHaveBeenCalled();
            });

            it('should not accept regex patterns', () => {
                const test = jest.fn();
                const {emit, use} = router();
                use({route: {
                    path: '/test',
                    handler: test,
                }});
                expect(test)
                    .toHaveBeenCalledTimes(0);
                emit({redirect: {path: '/abcdef/test'}});
                use({base: '/.[^/]*'});
                expect(test)
                    .toHaveBeenCalledTimes(0);
            });
        });

        describe('register', () => {
            it('should reject malformed register', () => {
                const {use} = router();
                expect(() => use({register: true}))
                    .toThrow(/register/);
            });

            it('should be used to register routes', () => {
                const {use} = router();
                let test = jest.fn();
                use({register: test});
                const handler = () => {};
                use({route: {
                    path: '/',
                    handler,
                }});
                expect(test)
                    .toHaveBeenCalledWith(undefined, '/', handler);
            });
        });

        describe('fetch', () => {
            it('should reject malformed fetch', () => {
                const {use} = router();
                expect(() => use({fetch: true}))
                    .toThrow(/fetch/);
            });

            it('should be used to fetch routes', () => {
                const {emit, use} = router();
                let test = jest.fn();
                use({fetch: test});
                emit({redirect: {path: '/redirect', params: {redirect: true}}});
                emit({show: {path: '/show', params: {show: true}}});
                expect(test)
                    .toHaveBeenCalledWith(undefined, '/redirect', {redirect: true});
                expect(test)
                    .toHaveBeenCalledWith(undefined, '/show', {show: true});
            });
        });
    });
});
<commit_msg>refactored router tests to make module dependencies clearer<commit_after>'use strict';

const core = require('../../src/core');
const r = require('../../src/modules/router');
const rr = require('../../src/modules/router.register');
const rf = require('../../src/modules/router.fetch');

const okwolo = (...modules) => core({modules})(null, window);

describe('router', () => {
    beforeEach(() => {
        window.history.pushState({}, '', '/');
    });

    describe('emit', () => {
        describe('redirect', () => {
            it('should reject malformed paths', () => {
                const {emit} = okwolo(r);
                expect(() => emit({redirect: undefined}))
                    .toThrow(/path/);
            });

            it('should accept no params', () => {
                const {emit} = okwolo(r, rf);
                expect(() => emit({redirect: {path: '/', params: undefined}}))
                    .not.toThrow(Error);
            });

            it('should change the pathname', () => {
                const {emit} = okwolo(r, rf);
                emit({redirect: {path: '/test/xyz'}});
                expect(window.location.pathname)
                    .toBe('/test/xyz');
            });

            it('should use a queue', () => {
                const test = jest.fn();
                const {emit, use} = okwolo(r, rr, rf);
                use({route: {
                    path: '/xxx',
                    handler: () => {
                        emit({show: {path: '/test'}});
                        test();
                    },
                }});
                use({route: {
                    path: '/test',
                    handler: () => {
                        expect(test)
                            .toHaveBeenCalled();
                    },
                }});
                emit({redirect: {path: '/xxx'}});
            });

            it('should accumulate params and pass them to the handler', () => {
                const test = jest.fn();
                const {emit, use} = okwolo(r, rr, rf);
                use({route: {
                    path: '/user/:id/fetch/:field',
                    handler: test,
                }});
                emit({redirect: {path: '/user/123/fetch/name'}});
                expect(test)
                    .toHaveBeenCalledWith({id: '123', field: 'name'});
            });
        });

        describe('show', () => {
            it('should reject malformed paths', () => {
                const {emit} = okwolo(r);
                expect(() => emit({show: undefined}))
                    .toThrow(/path/);
            });

            it('should accept no params', () => {
                const {emit} = okwolo(r, rf);
                emit({show: {path: '/', params: undefined}});
            });

            it('should not change the pathname', () => {
                const {emit} = okwolo(r, rf);
                emit({show: {path: '/test/xyz'}});
                expect(window.location.pathname)
                    .not.toBe('/test/xyz');
            });

            it('should use a queue', () => {
                const test = jest.fn();
                const {emit, use} = okwolo(r, rr, rf);
                use({route: {
                    path: '/xxx',
                    handler: () => {
                        emit({redirect: {path: '/test'}});
                        test();
                    },
                }});
                use({route: {
                    path: '/test',
                    handler: () => {
                        expect(test)
                            .toHaveBeenCalled();
                    },
                }});
                emit({show: {path: '/xxx'}});
            });

            it('should accumulate params and pass them to the handler', () => {
                const test = jest.fn();
                const {emit, use} = okwolo(r, rr, rf);
                use({route: {
                    path: '/user/:id/fetch/:field',
                    handler: test,
                }});
                emit({show: {path: '/user/123/fetch/name'}});
                expect(test)
                    .toHaveBeenCalledWith({id: '123', field: 'name'});
            });
        });
    });

    describe('use', () => {
        describe('route', () => {
            it('should reject malformed paths', () => {
                const {use} = okwolo(r);
                expect(() => {
                    use({route: {
                        path: {},
                        handler: () => {},
                    }});
                })
                    .toThrow(/path/);
            });

            it('should reject malformed handler', () => {
                const {use} = okwolo(r);
                expect(() => {
                    use({route: {
                        path: '',
                        handler: '',
                    }});
                })
                    .toThrow(/handler/);
            });

            it('should check the current pathname against new routes', () => {
                const test = jest.fn();
                window.history.pushState({}, '', '/test');
                const {use} = okwolo(r, rr, rf);
                use({route: {
                    path: '/test',
                    handler: test,
                }});
                expect(test)
                    .toHaveBeenCalled();
            });

            it('should save routes for future redirects', () => {
                const test = jest.fn();
                const {emit, use} = okwolo(r, rr, rf);
                use({route: {
                    path: '/test',
                    handler: test,
                }});
                emit({redirect: {path: '/test'}});
                expect(test)
                    .toHaveBeenCalled();
            });

            it('should prioritize the earliest routes', () => {
                const test1 = jest.fn();
                const test2 = jest.fn();
                const {emit, use} = okwolo(r, rr, rf);
                use({route: {
                    path: '/test',
                    handler: test1,
                }});
                use({route: {
                    path: '/*',
                    handler: test2,
                }});
                emit({redirect: {path: '/test'}});
                expect(test1)
                    .toHaveBeenCalled();
            });
        });

        describe('base', () => {
            it('should reject malformed inputs', () => {
                const {use} = okwolo(r);
                expect(() => {
                    use({base: true});
                })
                    .toThrow(/base/);
            });

            it('should add the base url to all new pathnames', () => {
                const {emit, use} = okwolo(r, rf);
                use({base: '/testBase'});
                emit({redirect: {path: '/test'}});
                expect(window.location.pathname)
                    .toBe('/testBase/test');
            });

            it('should be applied to the current pathname', () => {
                const test = jest.fn();
                window.history.pushState({}, '', '/testBase/test');
                const {use} = okwolo(r, rr, rf);
                use({route: {
                    path: '/test',
                    handler: test,
                }});
                expect(test)
                    .toHaveBeenCalledTimes(0);
                use({base: '/testBase'});
                expect(test)
                    .toHaveBeenCalled();
            });

            it('should not accept regex patterns', () => {
                const test = jest.fn();
                const {emit, use} = okwolo(r, rr, rf);
                use({route: {
                    path: '/test',
                    handler: test,
                }});
                expect(test)
                    .toHaveBeenCalledTimes(0);
                emit({redirect: {path: '/abcdef/test'}});
                use({base: '/.[^/]*'});
                expect(test)
                    .toHaveBeenCalledTimes(0);
            });
        });

        describe('register', () => {
            it('should reject malformed register', () => {
                const {use} = okwolo(r);
                expect(() => use({register: true}))
                    .toThrow(/register/);
            });

            it('should be used to register routes', () => {
                const {use} = okwolo(r, rf);
                let test = jest.fn();
                use({register: test});
                const handler = () => {};
                use({route: {
                    path: '/',
                    handler,
                }});
                expect(test)
                    .toHaveBeenCalledWith(undefined, '/', handler);
            });

            it('should use register\'s return value as the new store', () => {
                const test = jest.fn();
                const {use} = okwolo(r, rr, rf);
                const temp = [];
                let first = true;
                use({register: (store, path, handler) => {
                    if (first) {
                        first = false;
                        return temp;
                    }
                    test();
                    expect(store)
                        .toBe(temp);
                }});
                use({route: {
                    path: '/',
                    handler: () => 0,
                }});
                use({route: {
                    path: '/',
                    handler: () => 0,
                }});
                expect(test)
                    .toHaveBeenCalled();
            });
        });

        describe('fetch', () => {
            it('should reject malformed fetch', () => {
                const {use} = okwolo(r);
                expect(() => use({fetch: true}))
                    .toThrow(/fetch/);
            });

            it('should be used to fetch routes', () => {
                const {emit, use} = okwolo(r);
                let test = jest.fn();
                use({fetch: test});
                emit({redirect: {path: '/redirect', params: {redirect: true}}});
                emit({show: {path: '/show', params: {show: true}}});
                expect(test)
                    .toHaveBeenCalledWith(undefined, '/redirect', {redirect: true});
                expect(test)
                    .toHaveBeenCalledWith(undefined, '/show', {show: true});
            });
        });
    });

    describe('api', () => {
        describe('redirect', () => {
            it('should add redirect to the api', () => {
                const test = jest.fn();
                const app = okwolo(({use}) => {
                    use.on('api', (api) => {
                        expect(api.redirect)
                            .toBeInstanceOf(Function);
                        test();
                    });
                }, r);
                expect(test)
                    .toHaveBeenCalled();
                expect(app.redirect)
                    .toBeInstanceOf(Function);
            });

            it('should emit a redirect event', () => {
                const test = jest.fn();
                const app = okwolo(({emit}) => {
                    emit.on('redirect', test);
                }, r, rf);
                expect(test)
                    .toHaveBeenCalledTimes(0);
                app.redirect('/');
                expect(test)
                    .toHaveBeenCalledTimes(1);
            });
        });

        describe('show', () => {
            it('should add show to the api', () => {
                const test = jest.fn();
                const app = okwolo(({use}) => {
                    use.on('api', (api) => {
                        expect(api.show)
                            .toBeInstanceOf(Function);
                        test();
                    });
                }, r);
                expect(test)
                    .toHaveBeenCalled();
                expect(app.show)
                    .toBeInstanceOf(Function);
            });

            it('should emit a show event', () => {
                const test = jest.fn();
                const app = okwolo(({emit}) => {
                    emit.on('show', test);
                }, r, rf);
                expect(test)
                    .toHaveBeenCalledTimes(0);
                app.show('/');
                expect(test)
                    .toHaveBeenCalledTimes(1);
            });
        });
    });

    describe('primary', () => {
        it('should replace the primary', () => {
            const test = jest.fn();
            okwolo(({use}) => {
                use.on('primary', test);
            }, r);
            expect(test)
                .toHaveBeenCalled();
        });

        it('should use a builder if no path is provided', () => {
            const test = jest.fn();
            const app = okwolo(r, ({use}) => {
                use.on('builder', test);
            });
            expect(test)
                .toHaveBeenCalledTimes(0);
            app(() => 0);
            expect(test)
                .toHaveBeenCalledTimes(1);
        });

        it('should use a route that uses a builder when a path is given', () => {
            const testBuilder = jest.fn();
            const testRoute = jest.fn();
            const app = okwolo(r, rr, rf, ({use}) => {
                use.on('route', testRoute);
                use.on('builder', testBuilder);
            });
            expect(testBuilder)
                .toHaveBeenCalledTimes(0);
            expect(testRoute)
                .toHaveBeenCalledTimes(0);
            app('/test', () => 0);
            expect(testRoute)
                .toHaveBeenCalledTimes(1);
            expect(testBuilder)
                .toHaveBeenCalledTimes(0);
            app.show('/test');
            expect(testBuilder)
                .toHaveBeenCalledTimes(1);
        });

        it('should pass route handler params to the builder generator', () => {
            const test = jest.fn();
            const app = okwolo(r, rr, rf);
            const params = {test, app};
            app('/test', test);
            app.redirect('/test', params);
            expect(test)
                .toHaveBeenCalledWith(params);
        });
    });
});
<|endoftext|>