<commit_before>var _ = require('underscore'),
    urlTools = require('../util/urlTools'),
    cssom = require('cssom-papandreou'),
    AssetGraph = require('../AssetGraph'),
    bundleStrategyByName = {};

function getDiscriminatorForRelation(relation) {
    var discriminatorFragments = [];
    if (relation.conditionalComments) {
        Array.prototype.push.apply(discriminatorFragments, _.pluck(relation.conditionalComments, 'nodeValue'));
    }
    if (relation.type === 'HtmlStyle') {
        discriminatorFragments.push(relation.node.getAttribute('media') || 'all');
    }
    return discriminatorFragments.join(':');
}

// Internal helper function. Reuses the parse trees of existing assets, so be careful!
function makeBundle(assetGraph, assetsToBundle) {
    if (assetsToBundle.length === 0) {
        throw new Error('makeBundle: Bundle must contain at least one asset');
    } else if (assetsToBundle.length === 1) {
        // Shortcut
        return [assetsToBundle[0]];
    }

    var type = assetsToBundle[0].type,
        incomingType = {Css: 'HtmlStyle', 'JavaScript': 'HtmlScript'}[type],
        parseTrees = _.pluck(assetsToBundle, 'parseTree'),
        constructorOptions = {};

    if (type === 'JavaScript') {
        var topLevelStatements = [];

        assetsToBundle.forEach(function (asset) {
            assetGraph.findRelations({from: asset, type: 'JavaScriptInclude'}, true).forEach(function (relation) {
                if (relation.parentNode === asset.parseTree[1]) {
                    // This INCLUDE relation is a top level statement, update its .parentNode property to point at
                    // the top level statements array of the bundle:
                    relation.parentNode = topLevelStatements;
                }
            });
            Array.prototype.push.apply(topLevelStatements, asset.parseTree[1]);
        });
        constructorOptions.parseTree = ['toplevel', topLevelStatements];

        constructorOptions.copyrightNoticeComments = [];
        assetsToBundle.forEach(function (javaScript) {
            if (javaScript.copyrightNoticeComments) {
                Array.prototype.push.apply(constructorOptions.copyrightNoticeComments, javaScript.copyrightNoticeComments);
            }
        });
    } else {
        // type === 'Css'
        constructorOptions.parseTree = new cssom.CSSStyleSheet();
        assetsToBundle.forEach(function (asset) {
            asset.parseTree.parentStyleSheet = constructorOptions.parseTree;
            Array.prototype.push.apply(constructorOptions.parseTree.cssRules, asset.parseTree.cssRules);
        });
    }

    var bundleAsset = new AssetGraph[type](constructorOptions);

    bundleAsset.url = urlTools.resolveUrl(assetGraph.root, bundleAsset.id + bundleAsset.extension);
    bundleAsset._outgoingRelations = assetGraph.findRelations({from: assetsToBundle});
    bundleAsset._outgoingRelations.forEach(function (outgoingRelation) {
        outgoingRelation.remove();
        outgoingRelation.from = bundleAsset;
    });

    var seenReferringAssets = {},
        incomingRelations = assetGraph.findRelations({type: incomingType, to: assetsToBundle});
    incomingRelations.forEach(function (incomingRelation) {
        if (!(incomingRelation.from.id in seenReferringAssets)) {
            var bundleRelation = new AssetGraph[incomingType]({
                to: bundleAsset
            });
            bundleRelation.attach(incomingRelation.from, 'before', incomingRelation);
            if (incomingType === 'HtmlStyle') {
                var media = incomingRelation.node.getAttribute('media');
                if (media && media !== 'all') {
                    bundleRelation.node.setAttribute('media', media);
                    bundleRelation.from.markDirty();
                }
            } else if (incomingType === 'HtmlScript') {
                for (var i = 0 ; i < incomingRelations.length ; i += 1) {
                    var otherIncomingRelation = incomingRelations[i];
                    if (otherIncomingRelation.node.hasAttribute('data-main')) {
                        var existingHtmlRequireJsMainRelations = assetGraph.findRelations({
                            from: otherIncomingRelation.from,
                            type: 'HtmlRequireJsMain'
                        }).filter(function (relation) {
                            return relation.node === otherIncomingRelation.node;
                        });
                        if (existingHtmlRequireJsMainRelations.length === 1) {
                            var existingHtmlRequireJsMainRelation = existingHtmlRequireJsMainRelations[0];
                            bundleRelation.node.setAttribute('data-main', otherIncomingRelation.node.getAttribute('data-main'));
                            existingHtmlRequireJsMainRelation.node = bundleRelation.node;
                            break;
                        } else {
                            throw new Error("transforms.bundleRelations: Unexpected number of existing HtmlRequireJsMain relations found");
                        }
                    }
                }
            }
            seenReferringAssets[incomingRelation.from.id] = true;
        }
        incomingRelation.detach();
    });

    assetGraph.addAsset(bundleAsset);

    assetsToBundle.forEach(function (asset) {
        if (assetGraph.findRelations({to: asset}).length === 0) {
            assetGraph.removeAsset(asset);
        }
    });
    return bundleAsset;
}

// Quick and dirty bundling strategy that gets you down to one <script> and one <link rel='stylesheet'>
// per document, but doesn't do any cross-page optimization.
bundleStrategyByName.oneBundlePerIncludingAsset = function (assetGraph, queryObj) {
    var assetsToBundleById = {},
        seenIncludingAssets = {},
        bundleAssets = [],
        relationsByIncludingAsset = {};

    assetGraph.findRelations(queryObj).forEach(function (relation) {
        assetsToBundleById[relation.to.id] = relation.to; // Means not in a bundle yet
        (relationsByIncludingAsset[relation.from.id] = relationsByIncludingAsset[relation.from.id] || []).push(relation);
    });

    Object.keys(relationsByIncludingAsset).forEach(function (includingAssetId) {
        var includingAsset = assetGraph.idIndex[includingAssetId],
            relationsToBundle = relationsByIncludingAsset[includingAssetId];

        _.unique(_.pluck(relationsToBundle, 'type')).forEach(function (relationType) {
            var currentBundle = [],
                bundleDiscriminator,
                relationsOfTypeToBundle = relationsToBundle.filter(function (relation) {
                    return relation.type === relationType;
                });

            function flushBundle() {
                if (currentBundle.length > 0) {
                    bundleAssets.push(makeBundle(assetGraph, currentBundle)); // FIXME
                    currentBundle = [];
                }
            }
            assetGraph.findRelations(assetGraph.constructor.query.or({type: relationType}, {type: 'HtmlConditionalComment'})).forEach(function (outgoingRelation) {
                if (outgoingRelation.type === 'HtmlConditionalComment') {
                    if (assetGraph.findRelations({from: outgoingRelation.to, type: relationType}, true).length > 0) {
                        flushBundle();
                    }
                } else if (relationsOfTypeToBundle.indexOf(outgoingRelation) !== -1) {
                    // Make sure that we don't bundle HtmlStyles with different media attributes together etc.:
                    var discriminator = getDiscriminatorForRelation(outgoingRelation);
                    if (bundleDiscriminator && discriminator !== bundleDiscriminator) {
                        flushBundle();
                    }
                    bundleDiscriminator = discriminator;
                    if (assetGraph.findRelations({to: outgoingRelation.to}).length > 1) {
                        currentBundle.push(outgoingRelation.to.clone(outgoingRelation));
                    } else {
                        currentBundle.push(outgoingRelation.to);
                    }
                } else {
                    flushBundle();
                }
            });
            flushBundle();
        });
    });
    return bundleAssets;
};

// Cross-page optimizing bundling strategy that never puts the same chunk in multiple bundles, but still tries
// to create as few bundles as possible. Also preserves inclusion order.
// FIXME: This bundling strategy is still quite buggy, please don't use it yet.
bundleStrategyByName.sharedBundles = function (assetGraph, queryObj) {
    var assetIndex = {},
        seenIncludingAssets = {},
        bundles = [],
        relationsByIncludingAsset = {};

    assetGraph.findRelations(queryObj).forEach(function (relation) {
        assetIndex[relation.to.id] = null; // Means not in a bundle yet
        seenIncludingAssets[relation.from.id] = relation.from;
        (relationsByIncludingAsset[relation.from.id] = relationsByIncludingAsset[relation.from.id] || []).push(relation);
    });

    function splitBundle(bundle, index) {
        var newBundle = bundle.splice(index);
        newBundle.forEach(function (asset) {
            assetIndex[asset.id] = newBundle;
        });
        bundles.push(newBundle);
        return newBundle;
    }

    _.values(seenIncludingAssets).forEach(function (includingAsset) {
        var relationsToBundle = relationsByIncludingAsset[includingAsset.id];

        _.unique(_.pluck(relationsToBundle, 'type')).forEach(function (relationType) {
            var outgoingRelations = assetGraph.findRelations({from: includingAsset, type: [relationType, 'HtmlConditionalComment']}, true), // includeUnresolved
                previousBundle,
                bundleDiscriminator,
                canAppendToPreviousBundle = false,
                previousBundleIndex;

            outgoingRelations.forEach(function (outgoingRelation) {
                if (outgoingRelation.type === 'HtmlConditionalComment') {
                    if (assetGraph.findRelations({from: outgoingRelation.to, type: relationType}).length > 0) {
                        canAppendToPreviousBundle = false;
                    }
                    return;
                }

                // Make sure that we don't bundle HtmlStyles with different media attributes together etc.:
                var discriminator = getDiscriminatorForRelation(outgoingRelation);
                if (bundleDiscriminator && discriminator !== bundleDiscriminator) {
                    canAppendToPreviousBundle = false;
                }
                bundleDiscriminator = discriminator;

                var existingBundle = assetIndex[outgoingRelation.to.id];
                if (existingBundle === null) {
                    // Not bundled yet, append to previousBundle if possible, else create a new one
                    if (canAppendToPreviousBundle) {
                        previousBundle.push(outgoingRelation.to);
                        previousBundleIndex = previousBundle.length - 1;
                    } else {
                        if (previousBundle && previousBundleIndex !== previousBundle.length - 1) {
                            splitBundle(previousBundle, previousBundleIndex);
                        }
                        previousBundle = [outgoingRelation.to];
                        previousBundleIndex = 0;
                        bundles.push(previousBundle);
                        canAppendToPreviousBundle = true;
                    }
                    assetIndex[outgoingRelation.to.id] = previousBundle;
                } else if (existingBundle) {
                    // Already in another bundle
                    canAppendToPreviousBundle = false;
                    var indexInExistingBundle = existingBundle.indexOf(outgoingRelation.to);
                    if (previousBundle && existingBundle === previousBundle) {
                        if (indexInExistingBundle === previousBundleIndex + 1) {
                            previousBundleIndex = indexInExistingBundle;
                        } else {
                            splitBundle(previousBundle, indexInExistingBundle + 1);
                            existingBundle = assetIndex[outgoingRelation.to.id];
                            indexInExistingBundle = existingBundle.indexOf(outgoingRelation.to);
                            if (indexInExistingBundle !== 0) {
                                existingBundle = splitBundle(existingBundle, indexInExistingBundle);
                            }
                            previousBundle = existingBundle;
                            previousBundleIndex = 0;
                        }
                    } else {
                        if (previousBundle && previousBundleIndex !== (previousBundle.length - 1)) {
                            splitBundle(previousBundle, previousBundleIndex + 1);
                        }
                        if (indexInExistingBundle !== 0) {
                            existingBundle = splitBundle(existingBundle, indexInExistingBundle);
                        }
                        previousBundle = existingBundle;
                        previousBundleIndex = 0;
                    }
                } else {
                    // The relation doesn't point at an asset matched by queryObj
                    previousBundle = null;
                    canAppendToPreviousBundle = false;
                }
            });
            // No more outgoing relations for this asset, make sure that the asset that was bundled
            // last is at the last position in its bundle:
            if (previousBundle && previousBundleIndex !== previousBundle.length - 1) {
                splitBundle(previousBundle, previousBundleIndex + 1);
            }
        });
    });

    return bundles.map(function (bundle) {
        return makeBundle(assetGraph, bundle);
    });
};

module.exports = function (queryObj, bundleStrategyName) {
    if (!bundleStrategyName) {
        bundleStrategyName = 'oneBundlePerIncludingAsset';
    } else if (!(bundleStrategyName in bundleStrategyByName)) {
        throw new Error("transforms.bundleRelations: Unknown bundle strategy: " + bundleStrategyName);
    }

    return function bundleRelations(assetGraph) {
        var bundleAssets = bundleStrategyByName[bundleStrategyName](assetGraph, queryObj);
        assetGraph.recomputeBaseAssets();
        bundleAssets.forEach(function (bundleAsset) {
            assetGraph.findRelations({to: bundleAsset}).forEach(function (incomingRelation) {
                incomingRelation.refreshHref();
            });
            assetGraph.findRelations({from: bundleAsset}).forEach(function (outgoingRelation) {
                outgoingRelation.refreshHref();
            });
        });
    };
};
<commit_msg>bundleRelations, sharedBundles: Don't process relations that have been detached.<commit_after>var _ = require('underscore'),
    urlTools = require('../util/urlTools'),
    cssom = require('cssom-papandreou'),
    AssetGraph = require('../AssetGraph'),
    bundleStrategyByName = {};

function getDiscriminatorForRelation(relation) {
    var discriminatorFragments = [];
    if (relation.conditionalComments) {
        Array.prototype.push.apply(discriminatorFragments, _.pluck(relation.conditionalComments, 'nodeValue'));
    }
    if (relation.type === 'HtmlStyle') {
        discriminatorFragments.push(relation.node.getAttribute('media') || 'all');
    }
    return discriminatorFragments.join(':');
}

// Internal helper function. Reuses the parse trees of existing assets, so be careful!
function makeBundle(assetGraph, assetsToBundle) {
    if (assetsToBundle.length === 0) {
        throw new Error('makeBundle: Bundle must contain at least one asset');
    } else if (assetsToBundle.length === 1) {
        // Shortcut
        return [assetsToBundle[0]];
    }

    var type = assetsToBundle[0].type,
        incomingType = {Css: 'HtmlStyle', JavaScript: 'HtmlScript'}[type],
        parseTrees = _.pluck(assetsToBundle, 'parseTree'),
        constructorOptions = {};

    if (type === 'JavaScript') {
        var topLevelStatements = [];

        assetsToBundle.forEach(function (asset) {
            assetGraph.findRelations({from: asset, type: 'JavaScriptInclude'}, true).forEach(function (relation) {
                if (relation.parentNode === asset.parseTree[1]) {
                    // This INCLUDE relation is a top level statement, update its .parentNode property to point at
                    // the top level statements array of the bundle:
                    relation.parentNode = topLevelStatements;
                }
            });
            Array.prototype.push.apply(topLevelStatements, asset.parseTree[1]);
        });
        constructorOptions.parseTree = ['toplevel', topLevelStatements];

        constructorOptions.copyrightNoticeComments = [];
        assetsToBundle.forEach(function (javaScript) {
            if (javaScript.copyrightNoticeComments) {
                Array.prototype.push.apply(constructorOptions.copyrightNoticeComments, javaScript.copyrightNoticeComments);
            }
        });
    } else {
        // type === 'Css'
        constructorOptions.parseTree = new cssom.CSSStyleSheet();
        assetsToBundle.forEach(function (asset) {
            asset.parseTree.parentStyleSheet = constructorOptions.parseTree;
            Array.prototype.push.apply(constructorOptions.parseTree.cssRules, asset.parseTree.cssRules);
        });
    }

    var bundleAsset = new AssetGraph[type](constructorOptions);

    bundleAsset.url = urlTools.resolveUrl(assetGraph.root, bundleAsset.id + bundleAsset.extension);
    bundleAsset._outgoingRelations = assetGraph.findRelations({from: assetsToBundle});
    bundleAsset._outgoingRelations.forEach(function (outgoingRelation) {
        outgoingRelation.remove();
        outgoingRelation.from = bundleAsset;
    });

    var seenReferringAssets = {},
        incomingRelations = assetGraph.findRelations({type: incomingType, to: assetsToBundle});

    incomingRelations.forEach(function (incomingRelation) {
        if (!(incomingRelation.from.id in seenReferringAssets)) {
            var bundleRelation = new AssetGraph[incomingType]({
                to: bundleAsset
            });
            bundleRelation.attach(incomingRelation.from, 'before', incomingRelation);
            if (incomingType === 'HtmlStyle') {
                var media = incomingRelation.node.getAttribute('media');
                if (media && media !== 'all') {
                    bundleRelation.node.setAttribute('media', media);
                    bundleRelation.from.markDirty();
                }
            } else if (incomingType === 'HtmlScript') {
                for (var i = 0 ; i < incomingRelations.length ; i += 1) {
                    var otherIncomingRelation = incomingRelations[i];
                    if (otherIncomingRelation.assetGraph && otherIncomingRelation.node.hasAttribute('data-main')) {
                        var existingHtmlRequireJsMainRelations = assetGraph.findRelations({
                            from: otherIncomingRelation.from,
                            type: 'HtmlRequireJsMain'
                        }).filter(function (relation) {
                            return relation.node === otherIncomingRelation.node;
                        });
                        if (existingHtmlRequireJsMainRelations.length === 1) {
                            var existingHtmlRequireJsMainRelation = existingHtmlRequireJsMainRelations[0];
                            bundleRelation.node.setAttribute('data-main', otherIncomingRelation.node.getAttribute('data-main'));
                            existingHtmlRequireJsMainRelation.node = bundleRelation.node;
                            break;
                        } else {
                            throw new Error("transforms.bundleRelations: Unexpected number of existing HtmlRequireJsMain relations found");
                        }
                    }
                }
            }
            seenReferringAssets[incomingRelation.from.id] = true;
        }
        incomingRelation.detach();
    });

    assetGraph.addAsset(bundleAsset);

    assetsToBundle.forEach(function (asset) {
        if (assetGraph.findRelations({to: asset}).length === 0) {
            assetGraph.removeAsset(asset);
        }
    });
    return bundleAsset;
}

// Quick and dirty bundling strategy that gets you down to one <script> and one <link rel='stylesheet'>
// per document, but doesn't do any cross-page optimization.
bundleStrategyByName.oneBundlePerIncludingAsset = function (assetGraph, queryObj) {
    var assetsToBundleById = {},
        seenIncludingAssets = {},
        bundleAssets = [],
        relationsByIncludingAsset = {};

    assetGraph.findRelations(queryObj).forEach(function (relation) {
        assetsToBundleById[relation.to.id] = relation.to; // Means not in a bundle yet
        (relationsByIncludingAsset[relation.from.id] = relationsByIncludingAsset[relation.from.id] || []).push(relation);
    });

    Object.keys(relationsByIncludingAsset).forEach(function (includingAssetId) {
        var includingAsset = assetGraph.idIndex[includingAssetId],
            relationsToBundle = relationsByIncludingAsset[includingAssetId];

        _.unique(_.pluck(relationsToBundle, 'type')).forEach(function (relationType) {
            var currentBundle = [],
                bundleDiscriminator,
                relationsOfTypeToBundle = relationsToBundle.filter(function (relation) {
                    return relation.type === relationType;
                });

            function flushBundle() {
                if (currentBundle.length > 0) {
                    bundleAssets.push(makeBundle(assetGraph, currentBundle)); // FIXME
                    currentBundle = [];
                }
            }
            assetGraph.findRelations(assetGraph.constructor.query.or({type: relationType}, {type: 'HtmlConditionalComment'})).forEach(function (outgoingRelation) {
                if (outgoingRelation.type === 'HtmlConditionalComment') {
                    if (assetGraph.findRelations({from: outgoingRelation.to, type: relationType}, true).length > 0) {
                        flushBundle();
                    }
                } else if (relationsOfTypeToBundle.indexOf(outgoingRelation) !== -1) {
                    // Make sure that we don't bundle HtmlStyles with different media attributes together etc.:
                    var discriminator = getDiscriminatorForRelation(outgoingRelation);
                    if (bundleDiscriminator && discriminator !== bundleDiscriminator) {
                        flushBundle();
                    }
                    bundleDiscriminator = discriminator;
                    if (assetGraph.findRelations({to: outgoingRelation.to}).length > 1) {
                        currentBundle.push(outgoingRelation.to.clone(outgoingRelation));
                    } else {
                        currentBundle.push(outgoingRelation.to);
                    }
                } else {
                    flushBundle();
                }
            });
            flushBundle();
        });
    });
    return bundleAssets;
};

// Cross-page optimizing bundling strategy that never puts the same chunk in multiple bundles, but still tries
// to create as few bundles as possible. Also preserves inclusion order.
// FIXME: This bundling strategy is still quite buggy, please don't use it yet.
bundleStrategyByName.sharedBundles = function (assetGraph, queryObj) {
    var assetIndex = {},
        seenIncludingAssets = {},
        bundles = [],
        relationsByIncludingAsset = {};

    assetGraph.findRelations(queryObj).forEach(function (relation) {
        assetIndex[relation.to.id] = null; // Means not in a bundle yet
        seenIncludingAssets[relation.from.id] = relation.from;
        (relationsByIncludingAsset[relation.from.id] = relationsByIncludingAsset[relation.from.id] || []).push(relation);
    });

    function splitBundle(bundle, index) {
        var newBundle = bundle.splice(index);
        newBundle.forEach(function (asset) {
            assetIndex[asset.id] = newBundle;
        });
        bundles.push(newBundle);
        return newBundle;
    }

    _.values(seenIncludingAssets).forEach(function (includingAsset) {
        var relationsToBundle = relationsByIncludingAsset[includingAsset.id];

        _.unique(_.pluck(relationsToBundle, 'type')).forEach(function (relationType) {
            var outgoingRelations = assetGraph.findRelations({from: includingAsset, type: [relationType, 'HtmlConditionalComment']}, true), // includeUnresolved
                previousBundle,
                bundleDiscriminator,
                canAppendToPreviousBundle = false,
                previousBundleIndex;

            outgoingRelations.forEach(function (outgoingRelation) {
                if (outgoingRelation.type === 'HtmlConditionalComment') {
                    if (assetGraph.findRelations({from: outgoingRelation.to, type: relationType}).length > 0) {
                        canAppendToPreviousBundle = false;
                    }
                    return;
                }

                // Make sure that we don't bundle HtmlStyles with different media attributes together etc.:
                var discriminator = getDiscriminatorForRelation(outgoingRelation);
                if (bundleDiscriminator && discriminator !== bundleDiscriminator) {
                    canAppendToPreviousBundle = false;
                }
                bundleDiscriminator = discriminator;

                var existingBundle = assetIndex[outgoingRelation.to.id];
                if (existingBundle === null) {
                    // Not bundled yet, append to previousBundle if possible, else create a new one
                    if (canAppendToPreviousBundle) {
                        previousBundle.push(outgoingRelation.to);
                        previousBundleIndex = previousBundle.length - 1;
                    } else {
                        if (previousBundle && previousBundleIndex !== previousBundle.length - 1) {
                            splitBundle(previousBundle, previousBundleIndex);
                        }
                        previousBundle = [outgoingRelation.to];
                        previousBundleIndex = 0;
                        bundles.push(previousBundle);
                        canAppendToPreviousBundle = true;
                    }
                    assetIndex[outgoingRelation.to.id] = previousBundle;
                } else if (existingBundle) {
                    // Already in another bundle
                    canAppendToPreviousBundle = false;
                    var indexInExistingBundle = existingBundle.indexOf(outgoingRelation.to);
                    if (previousBundle && existingBundle === previousBundle) {
                        if (indexInExistingBundle === previousBundleIndex + 1) {
                            previousBundleIndex = indexInExistingBundle;
                        } else {
                            splitBundle(previousBundle, indexInExistingBundle + 1);
                            existingBundle = assetIndex[outgoingRelation.to.id];
                            indexInExistingBundle = existingBundle.indexOf(outgoingRelation.to);
                            if (indexInExistingBundle !== 0) {
                                existingBundle = splitBundle(existingBundle, indexInExistingBundle);
                            }
                            previousBundle = existingBundle;
                            previousBundleIndex = 0;
                        }
                    } else {
                        if (previousBundle && previousBundleIndex !== (previousBundle.length - 1)) {
                            splitBundle(previousBundle, previousBundleIndex + 1);
                        }
                        if (indexInExistingBundle !== 0) {
                            existingBundle = splitBundle(existingBundle, indexInExistingBundle);
                        }
                        previousBundle = existingBundle;
                        previousBundleIndex = 0;
                    }
                } else {
                    // The relation doesn't point at an asset matched by queryObj
                    previousBundle = null;
                    canAppendToPreviousBundle = false;
                }
            });
            // No more outgoing relations for this asset, make sure that the asset that was bundled
            // last is at the last position in its bundle:
            if (previousBundle && previousBundleIndex !== previousBundle.length - 1) {
                splitBundle(previousBundle, previousBundleIndex + 1);
            }
        });
    });

    return bundles.map(function (bundle) {
        return makeBundle(assetGraph, bundle);
    });
};

module.exports = function (queryObj, bundleStrategyName) {
    if (!bundleStrategyName) {
        bundleStrategyName = 'oneBundlePerIncludingAsset';
    } else if (!(bundleStrategyName in bundleStrategyByName)) {
        throw new Error("transforms.bundleRelations: Unknown bundle strategy: " + bundleStrategyName);
    }

    return function bundleRelations(assetGraph) {
        var bundleAssets = bundleStrategyByName[bundleStrategyName](assetGraph, queryObj);
        assetGraph.recomputeBaseAssets();
        bundleAssets.forEach(function (bundleAsset) {
            assetGraph.findRelations({to: bundleAsset}).forEach(function (incomingRelation) {
                incomingRelation.refreshHref();
            });
            assetGraph.findRelations({from: bundleAsset}).forEach(function (outgoingRelation) {
                outgoingRelation.refreshHref();
            });
        });
    };
};
<|endoftext|>


<commit_before>var onPullRequestCommitPage = document.URL.indexOf("/pull/") != -1;
var onComparePage = document.URL.indexOf("/compare/") != -1;

var commits = document.getElementsByClassName('commit-links');
var pullCommits = document.getElementsByClassName('commit');

var foreach = Array.prototype.forEach;
foreach.call(commits, function(commit) {
    addCheckbox(commit, commit.children[0].attributes['data-clipboard-text'].value, true);
});
foreach.call(pullCommits, function(node) {
    var messageNode = node.children[2];
    var codeNode = messageNode.children[1];
    var commitRef = '';
    if (onComparePage) {
        var metaNode = node.children[3];
        var tokens = metaNode.children[0].children[0].attributes['href'].value.split("/");
        commitRef = tokens[tokens.length - 1];
    } else {
        commitRef = node.attributes['data-channel'].value.split(":")[2];
    }
    addCheckbox(messageNode, commitRef, false);
});

function addCheckbox(commitLinkNode, ref, floatLeft) {
    var container = document.createElement('span');
    container.className = 'commit-selector';
    container.style.marginTop = '5px';
    if (floatLeft) {
        container.style.float = 'left';
    } else {
        container.style.float = 'right';
    }

    var input = document.createElement('input');
    input.type = 'checkbox';
    input.name = 'box-' + ref;
    input.value = ref;
    input.onclick = compareDiffs;

    container.appendChild(input);
    commitLinkNode.insertBefore(container, commitLinkNode.lastChild);
}

function uniq(strs) {
    var known = {}, result = [],
        // avoid hasOwnProperty BS:
        munge = function(name) { return "~" + name; };
    for (var i = 0; i < strs.length; i++) {
        if (! known[munge(strs[i])]) {
            result.push(strs[i]);
            known[munge(strs[i])] = true;
        }
    }
    return result;
}

function getSelectedCommits() {
    var checkboxes = document.getElementsByClassName('commit-selector'),
        checked = Array.prototype.filter.call(
                      checkboxes,
                      function(n) {
                          return n.children[0].checked;
                      }),
        ids = Array.prototype.map.call(checked,
                  function(n) {
                      return n.children[0].value;
                  });
    return uniq(ids);
}

function propagateChecked(name, checked) {
    var checkboxes = document.getElementsByName(name);
    Array.prototype.map.call(checkboxes, function(n) { n.checked = checked });
}

function compareDiffs(event) {
    propagateChecked(event.currentTarget.name,
                     event.currentTarget.checked);
    if (event.currentTarget.checked) {
        var selectedDiffs = getSelectedCommits();
        if (selectedDiffs.length == 2) {
            var firstCommit = selectedDiffs[0].children[0].value;
            var secondCommit = selectedDiffs[1].children[0].value;
            if (onPullRequestCommitPage || onComparePage) {
                // commits in a Pull Request's Commits tab have time travelling DOWN
                window.location = compareUrl(document.URL, secondCommit, firstCommit);
            } else {
                // commits in a Pull Request's Commits tab have time travelling UP
                window.location = compareUrl(document.URL, firstCommit, secondCommit);
            }
        }
    }
}

function compareUrl(baseUrl, firstCommit, secondCommit) {
    var base = baseUrl.substring("https://".length, baseUrl.length);
    var tokens = base.split("/");
    var url = "https://" + tokens[0] + "/" + tokens[1] + "/" + tokens[2] + "/compare/" + secondCommit + "..." + firstCommit;
    return url;
}
<commit_msg>Push through @JacekLach's refactoring of selecting the selected diffs into the trigger logic.<commit_after>var onPullRequestCommitPage = document.URL.indexOf("/pull/") != -1;
var onComparePage = document.URL.indexOf("/compare/") != -1;

var commits = document.getElementsByClassName('commit-links');
var pullCommits = document.getElementsByClassName('commit');

var foreach = Array.prototype.forEach;
foreach.call(commits, function(commit) {
    addCheckbox(commit, commit.children[0].attributes['data-clipboard-text'].value, true);
});
foreach.call(pullCommits, function(node) {
    var messageNode = node.children[2];
    var codeNode = messageNode.children[1];
    var commitRef = '';
    if (onComparePage) {
        var metaNode = node.children[3];
        var tokens = metaNode.children[0].children[0].attributes['href'].value.split("/");
        commitRef = tokens[tokens.length - 1];
    } else {
        commitRef = node.attributes['data-channel'].value.split(":")[2];
    }
    addCheckbox(messageNode, commitRef, false);
});

function addCheckbox(commitLinkNode, ref, floatLeft) {
    var container = document.createElement('span');
    container.className = 'commit-selector';
    container.style.marginTop = '5px';
    if (floatLeft) {
        container.style.float = 'left';
    } else {
        container.style.float = 'right';
    }

    var input = document.createElement('input');
    input.type = 'checkbox';
    input.name = 'box-' + ref;
    input.value = ref;
    input.onclick = compareDiffs;

    container.appendChild(input);
    commitLinkNode.insertBefore(container, commitLinkNode.lastChild);
}

function uniq(strs) {
    var known = {}, result = [],
        // avoid hasOwnProperty BS:
        munge = function(name) { return "~" + name; };
    for (var i = 0; i < strs.length; i++) {
        if (! known[munge(strs[i])]) {
            result.push(strs[i]);
            known[munge(strs[i])] = true;
        }
    }
    return result;
}

function getSelectedCommits() {
    var checkboxes = document.getElementsByClassName('commit-selector'),
        checked = Array.prototype.filter.call(
                      checkboxes,
                      function(n) {
                          return n.children[0].checked;
                      }),
        ids = Array.prototype.map.call(checked,
                  function(n) {
                      return n.children[0].value;
                  });
    return uniq(ids);
}

function propagateChecked(name, checked) {
    var checkboxes = document.getElementsByName(name);
    Array.prototype.map.call(checkboxes, function(n) { n.checked = checked });
}

function compareDiffs(event) {
    propagateChecked(event.currentTarget.name,
                     event.currentTarget.checked);
    if (event.currentTarget.checked) {
        var selectedDiffs = getSelectedCommits();
        if (selectedDiffs.length == 2) {
            var firstCommit = selectedDiffs[0];
            var secondCommit = selectedDiffs[1];
            if (onPullRequestCommitPage || onComparePage) {
                // commits in a Pull Request's Commits tab have time travelling DOWN
                window.location = compareUrl(document.URL, secondCommit, firstCommit);
            } else {
                // commits in a Pull Request's Commits tab have time travelling UP
                window.location = compareUrl(document.URL, firstCommit, secondCommit);
            }
        }
    }
}

function compareUrl(baseUrl, firstCommit, secondCommit) {
    var base = baseUrl.substring("https://".length, baseUrl.length);
    var tokens = base.split("/");
    var url = "https://" + tokens[0] + "/" + tokens[1] + "/" + tokens[2] + "/compare/" + secondCommit + "..." + firstCommit;
    return url;
}
<|endoftext|>


<commit_before>/** @babel */

export default class FilePatchSelection {
  constructor (hunks) {
    this.hunks = hunks
  }

  updateHunks (hunks) {
    this.hunks = hunks
    this.lineSelectionHead = Math.min(this.lineSelectionHead, this.lineSelectionTail)
    this.lineSelectionTail = this.lineSelectionHead
  }

  selectLine (hunk, line, expand = false) {
    const lineIndex = this.getLineIndex(hunk, line)
    this.lineSelectionHead = lineIndex
    if (!expand) this.lineSelectionTail = lineIndex
  }

  getSelectedLines () {
    const selectedLines = []
    const lineSelectionStart = Math.min(this.lineSelectionHead, this.lineSelectionTail)
    const lineSelectionEnd = Math.max(this.lineSelectionHead, this.lineSelectionTail)

    let hunkStartIndex = 0
    for (let hunk of this.hunks) {
      const hunkLines = hunk.getChangedLines()
      const hunkEndIndex = hunkStartIndex + hunkLines.length
      if (lineSelectionStart <= hunkEndIndex && lineSelectionEnd >= hunkStartIndex) {
        const sliceStart = Math.max(0, lineSelectionStart - hunkStartIndex)
        const sliceEnd = lineSelectionEnd - hunkStartIndex + 1
        selectedLines.push(...hunkLines.slice(sliceStart, sliceEnd))
      }
      hunkStartIndex = hunkEndIndex
    }

    return selectedLines
  }

  getLineIndex (containingHunk, line) {
    let index = 0
    for (let hunk of this.hunks) {
      const hunkLines = hunk.getChangedLines()
      if (hunk === containingHunk) {
        index += hunkLines.indexOf(line)
        break
      } else {
        index += hunkLines.length
      }
    }

    if (index < 0) throw new Error('Hunk does not contain line')
    return index
  }
}
<commit_msg>Store selections as absolute indices<commit_after>/** @babel */

export default class FilePatchSelection {
  constructor (hunks) {
    this.hunks = hunks
  }

  updateHunks (hunks) {
    const lineIndexOfPreviousSelection = Math.min(this.lineSelectionHead, this.lineSelectionTail)

    let changedLineIndexOfPreviousSelection = 0
    let lineIndex = 0
    outerLoop: for (let hunk of this.hunks) {
      for (let line of hunk.lines) {
        if (lineIndex === lineIndexOfPreviousSelection) break outerLoop
        if (line.isChanged()) changedLineIndexOfPreviousSelection++
        lineIndex++
      }
    }

    this.hunks = hunks

    let lineIndexOfNewSelection = 0
    let changedLineIndex = 0
    outerLoop: for (let hunk of this.hunks) {
      for (let line of hunk.lines) {
        if (line.isChanged()) {
          if (changedLineIndex === changedLineIndexOfPreviousSelection) break outerLoop
          changedLineIndex++
        }
        lineIndexOfNewSelection++
      }
    }

    this.lineSelectionHead = lineIndexOfNewSelection
    this.lineSelectionTail = lineIndexOfNewSelection
  }

  selectLine (hunk, line, expand = false) {
    const lineIndex = this.getLineIndex(hunk, line)
    this.lineSelectionHead = lineIndex
    if (!expand) this.lineSelectionTail = lineIndex
  }

  getSelectedLines () {
    const selectedLines = []
    const lineSelectionStart = Math.min(this.lineSelectionHead, this.lineSelectionTail)
    const lineSelectionEnd = Math.max(this.lineSelectionHead, this.lineSelectionTail)

    let hunkStartIndex = 0
    for (let hunk of this.hunks) {
      const hunkEndIndex = hunkStartIndex + hunk.lines.length
      if (lineSelectionStart <= hunkEndIndex && lineSelectionEnd >= hunkStartIndex) {
        const sliceStart = Math.max(0, lineSelectionStart - hunkStartIndex)
        const sliceEnd = lineSelectionEnd - hunkStartIndex + 1
        selectedLines.push(...hunk.lines.slice(sliceStart, sliceEnd).filter(l => l.isChanged()))
      }
      hunkStartIndex = hunkEndIndex
    }

    return selectedLines
  }

  getLineIndex (containingHunk, line) {
    let index = 0
    for (let hunk of this.hunks) {
      if (hunk === containingHunk) {
        index += hunk.lines.indexOf(line)
        break
      } else {
        index += hunk.lines.length
      }
    }

    if (index < 0) throw new Error('Hunk does not contain line')
    return index
  }
}
<|endoftext|>


<commit_before>var toStringFormatter = function(answer){
	return answer.toString();
};

var toLowerCaseFormatter = function(answer){
	return answer.toLowerCase();
};

var stringFormUtils = require('string-form-utils');
var fullwidthFormatter = function(answer){
	return stringFormUtils.transformToHalfwidth(answer);
};

var removeSpaceFormatter = function(answer){
	return answer.replace(/\s/g, '');
};

var numberFormatter = function(answer){
	var numberRegx = /^[\d,\.]+$/
	if(numberRegx.test(answer)){
		return answer.replace(/,/g, '');
	}
	return answer;
};

var formatters = [
	toStringFormatter,
	toLowerCaseFormatter,
	fullwidthFormatter,
	removeSpaceFormatter,
	numberFormatter
];

var format = function (answer){
	var result = answer;
	for(var i=0 ; i<formatters.length ; i++){
		result = formatters[i](result);
	}
	return result;
};

var equals = function (answer1, answer2){
	return  format(answer1) == format(answer2)
};

var answerFormatter = {
	format : format,
	equals : equals
};

if (typeof module !== "undefined" && module !== null) {
  module.exports = answerFormatter;
}

if (typeof window !== "undefined" && window !== null) {
  window.answerFormatter = answerFormatter;
}<commit_msg>change numberFormatter's regex<commit_after>var toStringFormatter = function(answer){
	return answer.toString();
};

var toLowerCaseFormatter = function(answer){
	return answer.toLowerCase();
};

var stringFormUtils = require('string-form-utils');
var fullwidthFormatter = function(answer){
	return stringFormUtils.transformToHalfwidth(answer);
};

var removeSpaceFormatter = function(answer){
	return answer.replace(/\s/g, '');
};

var numberFormatter = function(answer){
	return answer.replace(/(\d?),(\d?)/g, "$1$2");
};

var formatters = [
	toStringFormatter,
	toLowerCaseFormatter,
	fullwidthFormatter,
	removeSpaceFormatter,
	numberFormatter
];

var format = function (answer){
	var result = answer;
	for(var i=0 ; i<formatters.length ; i++){
		result = formatters[i](result);
	}
	return result;
};

var equals = function (answer1, answer2){
	return  format(answer1) == format(answer2)
};

var answerFormatter = {
	format : format,
	equals : equals
};

if (typeof module !== "undefined" && module !== null) {
  module.exports = answerFormatter;
}

if (typeof window !== "undefined" && window !== null) {
  window.answerFormatter = answerFormatter;
}<|endoftext|>


<commit_before>// Allowing window to be overriden for testing
function QueryParams (_window) {
  if (_window === undefined) _window = window;

  this.get = function () {
    var qs = _window.location.hash.substr(1);

    if (_window.location.search.length > 0) {
      // use legacy query params instead of hash
      _window.location.hash = _window.location.search.substr(1);
      _window.location.search = '';
    }

    var params = {};
    var parts = qs.split('&');
    for (var x in parts) {
      var keyValue = parts[x].split('=');
      if (keyValue[0] !== '') {
        params[keyValue[0]] = keyValue[1];
      }
    }
    return params;
  }

  this.update = function (params) {
    var currentParams = this.get();
    var keys = Object.keys(params);
    for (var x in keys) {
      currentParams[keys[x]] = params[keys[x]];
    }
    var queryString = '#';
    var updatedKeys = Object.keys(currentParams);
    for (var y in updatedKeys) {
      queryString += updatedKeys[y] + '=' + currentParams[updatedKeys[y]] + '&';
    }
    _window.location.hash = queryString.slice(0, -1);
  }

}

module.exports = QueryParams;
<commit_msg>Fixed formatting<commit_after>// Allowing window to be overriden for testing
function QueryParams (_window) {
  if (_window === undefined) _window = window;

  this.get = function () {
    var qs = _window.location.hash.substr(1);

    if (_window.location.search.length > 0) {
      // use legacy query params instead of hash
      _window.location.hash = _window.location.search.substr(1);
      _window.location.search = '';
    }

    var params = {};
    var parts = qs.split('&');
    for (var x in parts) {
      var keyValue = parts[x].split('=');
      if (keyValue[0] !== '') {
        params[keyValue[0]] = keyValue[1];
      }
    }
    return params;
  };

  this.update = function (params) {
    var currentParams = this.get();
    var keys = Object.keys(params);
    for (var x in keys) {
      currentParams[keys[x]] = params[keys[x]];
    }
    var queryString = '#';
    var updatedKeys = Object.keys(currentParams);
    for (var y in updatedKeys) {
      queryString += updatedKeys[y] + '=' + currentParams[updatedKeys[y]] + '&';
    }
    _window.location.hash = queryString.slice(0, -1);
  };
}

module.exports = QueryParams;
<|endoftext|>


<commit_before>/*
 This file is part of MyConference.

 MyConference is free software: you can redistribute it and/or modify
 it under the terms of the GNU Affero General Public License version 3
 as published by the Free Software Foundation.

 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 Affero General Public License for more details.

 You should find a copy of the GNU Affero General Public License in the
 root directory along with this program.
 If not, see http://www.gnu.org/licenses/agpl-3.0.html.
 */
describe('Feedback', function () {
  var scope,
    backendServiceMock,
    ctrl,
    ionicPopupMock,
    attendedDeferred,
    resolveDeferred,
    eventDeferred,
    alreadyFeedbackedDeferred;

  beforeEach(module('starter'));
  beforeEach(module(function ($provide, $urlRouterProvider) {
    $provide.value('$ionicTemplateCache', function () {
    });
    $urlRouterProvider.deferIntercept();
  }));
  beforeEach(inject(function ($rootScope, $controller, $q, $httpBackend) {
    $httpBackend.whenGET('locales/de.json').respond(200, '');

    attendedDeferred = $q.defer();
    alreadyFeedbackedDeferred = $q.defer();
    eventDeferred = $q.defer();
    resolveDeferred = $q.defer();
    resolveDeferred.resolve([]);
    backendServiceMock = {
      isCurrentUserOrganizer: jasmine.createSpy("isCurrentUserOrganizer").and.returnValue(resolveDeferred.promise),
      getEventById: jasmine.createSpy("getEventById").and.returnValue(eventDeferred.promise),
      isCurrentUserRegisteredForEvent: jasmine.createSpy("isCurrentUserRegisteredForEvent").and.returnValue(resolveDeferred.promise),
      isCurrentUserAttendedForEvent: jasmine.createSpy("isCurrentUserAttendedForEvent").and.returnValue(attendedDeferred.promise),
      getFileDetails: jasmine.createSpy("getFileDetails").and.returnValue(resolveDeferred),
      loadAgendaWithParams: jasmine.createSpy("loadAgendaWithParams").and.returnValue(resolveDeferred.promise),
      uploadFile: jasmine.createSpy("uploadFile").and.returnValue(resolveDeferred.promise),
      deleteFile: jasmine.createSpy("deleteFile").and.returnValue(resolveDeferred.promise),
      addCurrentUserToEvent: jasmine.createSpy("addCurrentUserToEvent").and.returnValue(resolveDeferred.promise),
      getOrganisers: jasmine.createSpy("getOrganisers").and.returnValue(resolveDeferred.promise),
      currentUser: jasmine.createSpy("currentUser").and.returnValue(resolveDeferred.promise),
      sendPushNotificationToUsers: jasmine.createSpy("sendPushNotificationToUsers").and.returnValue(resolveDeferred.promise),
      removeCurrentUserFromEvent: jasmine.createSpy("removeCurrentUserFromEvent").and.returnValue(resolveDeferred.promise),
      deleteAgenda: jasmine.createSpy("deleteAgenda").and.returnValue(resolveDeferred.promise),
      deleteEvent: jasmine.createSpy("deleteEvent").and.returnValue(resolveDeferred.promise),
      hasCurrentUserAlreadyGivenFeedback: jasmine.createSpy("hasCurrentUserAlreadyGivenFeedback").and.returnValue(alreadyFeedbackedDeferred.promise),
      getUser: jasmine.createSpy("getUser").and.returnValue(resolveDeferred.promise),
      fetchCurrentUser: jasmine.createSpy("fetchCurrentUser").and.returnValue(resolveDeferred.promise),
      addingAgenda: jasmine.createSpy("addingAgenda").and.returnValue(resolveDeferred.promise)
    };

    ionicPopupMock = jasmine.createSpyObj('$ionicPopup spy', ['alert']);
    scope = $rootScope.$new();
    ctrl = $controller('EventCtrl', {
      $scope: scope,
      $ionicPopup: ionicPopupMock,
      backendService: backendServiceMock,
      $stateParams: {eventId: 1}
    });

    now = new Date(2016, 6, 4, 13, 22, 9);
    jasmine.clock().mockDate(now);
    twoDaysAgo = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 2, 0, 0, 0);
    today = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 0, 0, 0);
    inThreeDays = new Date(now.getFullYear(), now.getMonth(), now.getDate() + 3, 0, 0, 0);
    threeHoursBefore = new Date(0, 0, 0, now.getHours() - 3, now.getMinutes(), now.getSeconds());
    oneHourBefore = new Date(0, 0, 0, now.getHours() - 1, now.getMinutes(), now.getSeconds());
    fiveMinutesAfter = new Date(0, 0, 0, now.getHours(), now.getMinutes() + 5, now.getSeconds());
    fourHoursAfter = new Date(0, 0, 0, now.getHours() + 4, now.getMinutes(), now.getSeconds());
  }));

  describe("isFeedbackAllowed", function () {
    describe("with attended user who did not feedbacked", function () {
      beforeEach(function () {
        attendedDeferred.resolve(true);
        alreadyFeedbackedDeferred.resolve(false);
      });
      it("should not display the Feedback button for future event", function () {
        eventDeferred.resolve(
          {
            'data': {
              "title": "test",
              "date": inThreeDays.toISOString(),
              "begin": oneHourBefore,
              "end": fourHoursAfter
            }
          }
        );
        scope.$digest();
        expect(scope.isFeedbackAllowed).toBe(false);
      });

      it("should display the Feedback button for current event", function () {
        eventDeferred.resolve({
          'data': {
            "title": "test",
            "date": today.toISOString(),
            "begin": oneHourBefore,
            "end": fiveMinutesAfter
          }
        });
        scope.$digest();
        expect(scope.isFeedbackAllowed).toBe(true);
      });

      it("should display the Feedback button for event that ended less than 48h ago", function () {
        eventDeferred.resolve({
          'data': {
            "title": "test",
            "date": twoDaysAgo.toISOString(),
            "begin": oneHourBefore,
            "end": fourHoursAfter
          }
        });
        scope.$digest();
        expect(scope.isFeedbackAllowed).toBe(true);
      });

      it("should not display the Feedback button for event that ended more than 48h ago", function () {
        eventDeferred.resolve({
          'data': {
            "title": "test",
            "date": twoDaysAgo.toISOString(),
            "begin": threeHoursBefore,
            "end": oneHourBefore
          }
        });
        scope.$digest();
        expect(scope.isFeedbackAllowed).toBe(false);
      });
    });

    describe("with attended user who already feedbacked", function () {
      beforeEach(function () {
        attendedDeferred.resolve(true);
        alreadyFeedbackedDeferred.resolve(true);
      });
      it("should not display the Feedback button for attended user who already feedbacked", function () {
        eventDeferred.resolve(
          {
            'data': {
              "title": "test",
              "date": inThreeDays.toISOString(),
              "begin": oneHourBefore,
              "end": fourHoursAfter
            }
          }
        );
        scope.$digest();
        expect(scope.isFeedbackAllowed).toBe(false);
      });
    });

    describe("with not attended user", function () {
      beforeEach(function () {
        attendedDeferred.resolve(false);
        alreadyFeedbackedDeferred.resolve(false);
      });
      it("should not display the Feedback button for unattended user", function () {
        eventDeferred.resolve(
          {
            'data': {
              "title": "test",
              "date": inThreeDays.toISOString(),
              "begin": oneHourBefore,
              "end": fourHoursAfter
            }
          }
        );
        scope.$digest();
        expect(scope.isFeedbackAllowed).toBe(false);
      });
    });
  })

  describe("areFeedbackResultsVisible", function () {
    beforeEach(function () {
      attendedDeferred.resolve(true);
      alreadyFeedbackedDeferred.resolve(false);
    });
    it("should not display the Feedback results button for future event", function () {
      eventDeferred.resolve(
        {
          'data': {
            "title": "test",
            "date": inThreeDays,
            "begin": oneHourBefore,
            "end": fourHoursAfter
          }
        }
      );
      scope.$digest();
      expect(scope.areFeedbackResultsVisible).toBe(false);
    });

    it("should not display the Feedback results button for current event", function () {
      eventDeferred.resolve({
        'data': {
          "title": "test",
          "date": today,
          "begin": oneHourBefore,
          "end": fiveMinutesAfter
        }
      });
      scope.$digest();
      expect(scope.areFeedbackResultsVisible).toBe(false);
    });

    it("should display the Feedback results button for event that ended", function () {
      eventDeferred.resolve({
        'data': {
          "title": "test",
          "date": today,
          "begin": threeHoursBefore,
          "end": oneHourBefore
        }
      });
      scope.$digest();
      expect(scope.areFeedbackResultsVisible).toBe(true);
    });
  })
});
<commit_msg>Adapt test cases<commit_after>/*
 This file is part of MyConference.

 MyConference is free software: you can redistribute it and/or modify
 it under the terms of the GNU Affero General Public License version 3
 as published by the Free Software Foundation.

 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 Affero General Public License for more details.

 You should find a copy of the GNU Affero General Public License in the
 root directory along with this program.
 If not, see http://www.gnu.org/licenses/agpl-3.0.html.
 */
describe('Feedback', function () {
  var scope,
    backendServiceMock,
    ctrl,
    ionicPopupMock,
    attendedDeferred,
    resolveDeferred,
    eventDeferred,
    alreadyFeedbackedDeferred;

  beforeEach(module('starter'));
  beforeEach(module(function ($provide, $urlRouterProvider) {
    $provide.value('$ionicTemplateCache', function () {
    });
    $urlRouterProvider.deferIntercept();
  }));
  beforeEach(inject(function ($rootScope, $controller, $q, $httpBackend) {
    $httpBackend.whenGET('locales/de.json').respond(200, '');

    attendedDeferred = $q.defer();
    alreadyFeedbackedDeferred = $q.defer();
    eventDeferred = $q.defer();
    resolveDeferred = $q.defer();
    resolveDeferred.resolve([]);
    backendServiceMock = {
      isCurrentUserOrganizer: jasmine.createSpy("isCurrentUserOrganizer").and.returnValue(resolveDeferred.promise),
      getEventById: jasmine.createSpy("getEventById").and.returnValue(eventDeferred.promise),
      isCurrentUserRegisteredForEvent: jasmine.createSpy("isCurrentUserRegisteredForEvent").and.returnValue(resolveDeferred.promise),
      isCurrentUserAttendedForEvent: jasmine.createSpy("isCurrentUserAttendedForEvent").and.returnValue(attendedDeferred.promise),
      getFileDetails: jasmine.createSpy("getFileDetails").and.returnValue(resolveDeferred),
      loadAgendaWithParams: jasmine.createSpy("loadAgendaWithParams").and.returnValue(resolveDeferred.promise),
      uploadFile: jasmine.createSpy("uploadFile").and.returnValue(resolveDeferred.promise),
      deleteFile: jasmine.createSpy("deleteFile").and.returnValue(resolveDeferred.promise),
      addCurrentUserToEvent: jasmine.createSpy("addCurrentUserToEvent").and.returnValue(resolveDeferred.promise),
      getOrganisers: jasmine.createSpy("getOrganisers").and.returnValue(resolveDeferred.promise),
      currentUser: jasmine.createSpy("currentUser").and.returnValue(resolveDeferred.promise),
      sendPushNotificationToUsers: jasmine.createSpy("sendPushNotificationToUsers").and.returnValue(resolveDeferred.promise),
      removeCurrentUserFromEvent: jasmine.createSpy("removeCurrentUserFromEvent").and.returnValue(resolveDeferred.promise),
      deleteAgenda: jasmine.createSpy("deleteAgenda").and.returnValue(resolveDeferred.promise),
      deleteEvent: jasmine.createSpy("deleteEvent").and.returnValue(resolveDeferred.promise),
      hasCurrentUserAlreadyGivenFeedback: jasmine.createSpy("hasCurrentUserAlreadyGivenFeedback").and.returnValue(alreadyFeedbackedDeferred.promise),
      getUser: jasmine.createSpy("getUser").and.returnValue(resolveDeferred.promise),
      fetchCurrentUser: jasmine.createSpy("fetchCurrentUser").and.returnValue(resolveDeferred.promise),
      addingAgenda: jasmine.createSpy("addingAgenda").and.returnValue(resolveDeferred.promise)
    };

    ionicPopupMock = jasmine.createSpyObj('$ionicPopup spy', ['alert']);
    scope = $rootScope.$new();
    ctrl = $controller('EventCtrl', {
      $scope: scope,
      $ionicPopup: ionicPopupMock,
      backendService: backendServiceMock,
      $stateParams: {eventId: 1}
    });

    now = new Date(2016, 6, 4, 13, 22, 9);
    jasmine.clock().mockDate(now);
    twoDaysAgo = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 2, 0, 0, 0);
    today = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 0, 0, 0);
    inThreeDays = new Date(now.getFullYear(), now.getMonth(), now.getDate() + 3, 0, 0, 0);
    threeHoursBefore = new Date(0, 0, 0, now.getHours() - 3, now.getMinutes(), now.getSeconds());
    oneHourBefore = new Date(0, 0, 0, now.getHours() - 1, now.getMinutes(), now.getSeconds());
    fiveMinutesAfter = new Date(0, 0, 0, now.getHours(), now.getMinutes() + 5, now.getSeconds());
    fourHoursAfter = new Date(0, 0, 0, now.getHours() + 4, now.getMinutes(), now.getSeconds());
  }));

  describe("isFeedbackAllowed", function () {
    describe("with attended user who did not feedbacked", function () {
      beforeEach(function () {
        attendedDeferred.resolve(true);
        alreadyFeedbackedDeferred.resolve(false);
      });
      it("should not display the Feedback button for future event", function () {
        eventDeferred.resolve(
          {
            'data': {
              "title": "test",
              "date": inThreeDays.toISOString(),
              "begin": oneHourBefore,
              "end": fourHoursAfter
            }
          }
        );
        scope.$digest();
        expect(scope.isFeedbackAllowed).toBe(false);
      });

      it("should display the Feedback button for current event", function () {
        eventDeferred.resolve({
          'data': {
            "title": "test",
            "date": today.toISOString(),
            "begin": oneHourBefore,
            "end": fiveMinutesAfter
          }
        });
        scope.$digest();
        expect(scope.isFeedbackAllowed).toBe(true);
      });

      it("should display the Feedback button for event that ended less than 48h ago", function () {
        eventDeferred.resolve({
          'data': {
            "title": "test",
            "date": twoDaysAgo.toISOString(),
            "begin": oneHourBefore,
            "end": fourHoursAfter
          }
        });
        scope.$digest();
        expect(scope.isFeedbackAllowed).toBe(true);
      });

      it("should not display the Feedback button for event that ended more than 48h ago", function () {
        eventDeferred.resolve({
          'data': {
            "title": "test",
            "date": twoDaysAgo.toISOString(),
            "begin": threeHoursBefore,
            "end": oneHourBefore
          }
        });
        scope.$digest();
        expect(scope.isFeedbackAllowed).toBe(false);
      });
    });

    describe("with attended user who already feedbacked", function () {
      beforeEach(function () {
        attendedDeferred.resolve(true);
        alreadyFeedbackedDeferred.resolve(true);
      });
      it("should not display the Feedback button for attended user who already feedbacked", function () {
        eventDeferred.resolve(
          {
            'data': {
              "title": "test",
              "date": inThreeDays.toISOString(),
              "begin": oneHourBefore,
              "end": fourHoursAfter
            }
          }
        );
        scope.$digest();
        expect(scope.isFeedbackAllowed).toBe(false);
      });
    });

    describe("with not attended user", function () {
      beforeEach(function () {
        attendedDeferred.resolve(false);
        alreadyFeedbackedDeferred.resolve(false);
      });
      it("should not display the Feedback button for unattended user", function () {
        eventDeferred.resolve(
          {
            'data': {
              "title": "test",
              "date": inThreeDays.toISOString(),
              "begin": oneHourBefore,
              "end": fourHoursAfter
            }
          }
        );
        scope.$digest();
        expect(scope.isFeedbackAllowed).toBe(false);
      });
    });
  })

  describe("areFeedbackResultsVisible", function () {
    beforeEach(function () {
      attendedDeferred.resolve(true);
      alreadyFeedbackedDeferred.resolve(false);
    });
    it("should not display the Feedback results button for future event", function () {
      eventDeferred.resolve(
        {
          'data': {
            "title": "test",
            "date": inThreeDays.toISOString(),
            "begin": oneHourBefore,
            "end": fourHoursAfter
          }
        }
      );
      scope.$digest();
      expect(scope.areFeedbackResultsVisible).toBe(false);
    });

    it("should not display the Feedback results button for current event", function () {
      eventDeferred.resolve({
        'data': {
          "title": "test",
          "date": today.toISOString(),
          "begin": oneHourBefore,
          "end": fiveMinutesAfter
        }
      });
      scope.$digest();
      expect(scope.areFeedbackResultsVisible).toBe(false);
    });

    it("should display the Feedback results button for event that ended", function () {
      eventDeferred.resolve({
        'data': {
          "title": "test",
          "date": today.toISOString(),
          "begin": threeHoursBefore,
          "end": oneHourBefore
        }
      });
      scope.$digest();
      expect(scope.areFeedbackResultsVisible).toBe(true);
    });
  })
});
<|endoftext|>


<commit_before>var BW = BW || {};
BW.MapImplementation = BW.MapImplementation || {};
BW.MapImplementation.OL3 = BW.MapImplementation.OL3 || {};
BW.MapImplementation.OL3.Sources = BW.MapImplementation.OL3.Sources || {};

BW.MapImplementation.OL3.Sources.Wmts = function(bwSubLayer){
    var projection = new ol.proj.Projection({
        code: bwSubLayer.coordinate_system,
        extent: bwSubLayer.extent,
        units: bwSubLayer.extentUnits
    });

    var projectionExtent = projection.getExtent();
    var size = ol.extent.getWidth(projectionExtent) / 256;
    var resolutions = new Array(14);
    var matrixIds = new Array(14);
    var numZoomLevels = 18;
    var matrixSet = bwSubLayer.matrixSet;
    if (matrixSet === null || matrixSet === '' || matrixSet === undefined)
    {
           matrixSet=bwSubLayer.coordinate_system;
    }
    for (var z = 0; z < numZoomLevels; ++z) {
        resolutions[z] = size / Math.pow(2, z);
        matrixIds[z] = matrixSet + ":" + z;
    }

    return new ol.source.WMTS({
        url: bwSubLayer.url,
        layer: bwSubLayer.name,
        format: bwSubLayer.format,
        projection: projection,
        matrixSet: matrixSet,
        crossOrigin: bwSubLayer.crossOrigin,
        tileGrid: new ol.tilegrid.WMTS({
            origin: ol.extent.getTopLeft(projectionExtent),
            resolutions: resolutions,
            matrixIds: matrixIds
        })
    });
};
<commit_msg>use sublayer values for numZoomlevels instead of magic numbers<commit_after>var BW = BW || {};
BW.MapImplementation = BW.MapImplementation || {};
BW.MapImplementation.OL3 = BW.MapImplementation.OL3 || {};
BW.MapImplementation.OL3.Sources = BW.MapImplementation.OL3.Sources || {};

BW.MapImplementation.OL3.Sources.Wmts = function(bwSubLayer){
    var projection = new ol.proj.Projection({
        code: bwSubLayer.coordinate_system,
        extent: bwSubLayer.extent,
        units: bwSubLayer.extentUnits
    });

    var projectionExtent = projection.getExtent();
    var size = ol.extent.getWidth(projectionExtent) / 256;
    var resolutions = new Array(bwSubLayer.numZoomLevels);
    var matrixIds = new Array(bwSubLayer.numZoomLevels);
    var numZoomLevels = bwSubLayer.numZoomLevels;
    var matrixSet = bwSubLayer.matrixSet;
    if (matrixSet === null || matrixSet === '' || matrixSet === undefined)
    {
           matrixSet=bwSubLayer.coordinate_system;
    }
    for (var z = 0; z < numZoomLevels; ++z) {
        resolutions[z] = size / Math.pow(2, z);
        matrixIds[z] = matrixSet + ":" + z;
    }

    return new ol.source.WMTS({
        url: bwSubLayer.url,
        layer: bwSubLayer.name,
        format: bwSubLayer.format,
        projection: projection,
        matrixSet: matrixSet,
        crossOrigin: bwSubLayer.crossOrigin,
        tileGrid: new ol.tilegrid.WMTS({
            origin: ol.extent.getTopLeft(projectionExtent),
            resolutions: resolutions,
            matrixIds: matrixIds
        })
    });
};
<|endoftext|>