<commit_before>const DEFAULTS = {
    INITIAL_RENDER_COUNT: 80,
    LOAD_COUNT: 20,
    instances: new Map(),
};

exports.get_filtered_items = (value, list, opts) => {
    /*
        This is used by the main object (see `create`),
        but we split it out to make it a bit easier
        to test.
    */
    const get_item = opts.get_item;

    if (!opts.filter) {
        if (get_item) {
            return list.map(get_item);
        }
        return [...list];
    }

    if (opts.filter.filterer) {
        if (get_item) {
            return opts.filter.filterer(
                list.map(get_item),
                value
            );
        }
        return opts.filter.filterer(list, value);
    }

    const predicate = (item) => {
        return opts.filter.predicate(item, value);
    };

    if (get_item) {
        const result = [];

        for (const key of list) {
            const item = get_item(key);
            if (predicate(item)) {
                result.push(item);
            }
        }

        return result;
    }

    return list.filter(predicate);
};

exports.alphabetic_sort = (prop) => {
    return function (a, b) {
        // The conversion to uppercase helps make the sorting case insensitive.
        const str1 = a[prop].toUpperCase();
        const str2 = b[prop].toUpperCase();

        if (str1 === str2) {
            return 0;
        } else if (str1 > str2) {
            return 1;
        }

        return -1;
    };
};

exports.numeric_sort = (prop) => {
    return function (a, b) {
        if (parseFloat(a[prop]) > parseFloat(b[prop])) {
            return 1;
        } else if (parseFloat(a[prop]) === parseFloat(b[prop])) {
            return 0;
        }

        return -1;
    };
};

exports.valid_filter_opts = (opts) => {
    if (!opts.filter) {
        return true;
    }
    if (opts.filter.predicate) {
        if (typeof opts.filter.predicate !== 'function') {
            blueslip.error('Filter predicate is not a function.');
            return false;
        }
        if (opts.filter.filterer) {
            blueslip.error('Filterer and predicate are mutually exclusive.');
            return false;
        }
    } else {
        if (typeof opts.filter.filterer !== 'function') {
            blueslip.error('Filter filterer is not a function (or missing).');
            return false;
        }
    }

    return true;
};

// @params
// container: jQuery object to append to.
// list: The list of items to progressively append.
// opts: An object of random preferences.
exports.create = function ($container, list, opts) {
    if (!opts) {
        blueslip.error('Need opts to create widget.');
        return;
    }

    if (opts.name && DEFAULTS.instances.get(opts.name)) {
        // Clear event handlers for prior widget.
        const old_widget = DEFAULTS.instances.get(opts.name);
        old_widget.clear_event_handlers();
    }

    const meta = {
        sorting_function: null,
        sorting_functions: new Map(),
        generic_sorting_functions: {
            alphabetic: exports.alphabetic_sort,
            numeric: exports.numeric_sort,
        },
        offset: 0,
        list: list,
        filtered_list: list,
        reverse_mode: false,
        filter_value: '',
    };

    if (!exports.valid_filter_opts(opts)) {
        return;
    }

    if (opts.get_item && typeof opts.get_item !== 'function') {
        blueslip.error('get_item should be a function');
        return;
    }

    const widget = {};

    widget.filter_and_sort = function () {
        meta.filtered_list = exports.get_filtered_items(
            meta.filter_value,
            meta.list,
            opts
        );

        if (meta.sorting_function) {
            meta.filtered_list.sort(
                meta.sorting_function
            );
        }

        if (meta.reverse_mode) {
            meta.filtered_list.reverse();
        }
    };

    // Reads the provided list (in the scope directly above)
    // and renders the next block of messages automatically
    // into the specified container.
    widget.render = function (how_many) {
        const load_count = how_many || DEFAULTS.LOAD_COUNT;

        // Stop once the offset reaches the length of the original list.
        if (meta.offset >= meta.filtered_list.length) {
            return;
        }

        const slice = meta.filtered_list.slice(meta.offset, meta.offset + load_count);

        const finish = blueslip.start_timing('list_render ' + opts.name);
        let html = "";
        for (const item of slice) {
            const s = opts.modifier(item);

            if (typeof s !== 'string') {
                blueslip.error('List item is not a string: ' + s);
                continue;
            }

            // append the HTML or nothing if corrupt (null, undef, etc.).
            if (s) {
                html += s;
            }
        }

        finish();

        $container.append($(html));
        meta.offset += load_count;
    };

    widget.clear = function () {
        $container.html("");
        meta.offset = 0;
    };

    widget.set_filter_value = function (filter_value) {
        meta.filter_value = filter_value;
    };

    widget.set_reverse_mode = function (reverse_mode) {
        meta.reverse_mode = reverse_mode;
    };

    // the sorting function is either the function or string that calls the
    // function to sort the list by. The prop is used for generic functions
    // that can be called to sort with a particular prop.
    widget.set_sorting_function = function (sorting_function, prop) {
        if (typeof sorting_function === "function") {
            meta.sorting_function = sorting_function;
        } else if (typeof sorting_function === "string") {
            if (typeof prop === "string") {
                /* eslint-disable max-len */
                meta.sorting_function = meta.generic_sorting_functions[sorting_function](prop);
            } else {
                meta.sorting_function = meta.sorting_functions.get(sorting_function);
            }
        }
    };

    widget.set_up_event_handlers = function () {
        meta.scroll_container = scroll_util.get_list_scrolling_container($container);

        // on scroll of the nearest scrolling container, if it hits the bottom
        // of the container then fetch a new block of items and render them.
        meta.scroll_container.on('scroll.list_widget_container', function () {
            if (this.scrollHeight - (this.scrollTop + this.clientHeight) < 10) {
                widget.render();
            }
        });

        if (opts.parent_container) {
            opts.parent_container.on('click.list_widget_sort', "[data-sort]", function () {
                exports.handle_sort($(this), widget);
            });
        }

        if (opts.filter && opts.filter.element) {
            opts.filter.element.on('input.list_widget_filter', function () {
                const value = this.value.toLocaleLowerCase();
                widget.set_filter_value(value);
                widget.hard_redraw();
            });
        }
    };

    widget.clear_event_handlers = function () {
        meta.scroll_container.off('scroll.list_widget_container');

        if (opts.parent_container) {
            opts.parent_container.off('click.list_widget_sort', "[data-sort]");
        }

        if (opts.filter && opts.filter.element) {
            opts.filter.element.off('input.list_widget_filter');
        }
    };

    widget.sort = function (sorting_function, prop) {
        widget.set_sorting_function(sorting_function, prop);
        widget.hard_redraw();
    };

    widget.clean_redraw = function () {
        widget.filter_and_sort();
        widget.clear();
        widget.render(DEFAULTS.INITIAL_RENDER_COUNT);
    };

    widget.hard_redraw = function () {
        widget.clean_redraw();
        if (opts.filter && opts.filter.onupdate) {
            opts.filter.onupdate();
        }
    };

    widget.replace_list_data = function (list) {
        /*
            We mostly use this widget for lists where you are
            not adding or removing rows, so when you do modify
            the list, we have a brute force solution.
        */
        meta.list = list;
        widget.hard_redraw();
    };

    widget.set_up_event_handlers();

    if (opts.sort_fields) {
        for (const [name, sorting_function] of Object.entries(opts.sort_fields)) {
            meta.sorting_functions.set(name, sorting_function);
        }
    }

    if (opts.init_sort) {
        widget.set_sorting_function(...opts.init_sort);
    }

    widget.clean_redraw();

    // Save the instance for potential future retrieval if a name is provided.
    if (opts.name) {
        DEFAULTS.instances.set(opts.name, widget);
    }

    return widget;
};

exports.get = function (name) {
    return DEFAULTS.instances.get(name) || false;
};

exports.handle_sort = function (th, list) {
    /*
        one would specify sort parameters like this:
            - name => sort alphabetic.
            - age  => sort numeric.
            - status => look up `status` in sort_fields
                        to find custom sort function

        */
    const sort_type = th.data("sort");
    const prop_name = th.data("sort-prop");

    if (th.hasClass("active")) {
        if (!th.hasClass("descend")) {
            th.addClass("descend");
        } else {
            th.removeClass("descend");
        }
    } else {
        th.siblings(".active").removeClass("active");
        th.addClass("active");
    }

    list.set_reverse_mode(th.hasClass("descend"));

    // if `prop_name` is defined, it will trigger the generic codepath,
    // and not if it is undefined.
    list.sort(sort_type, prop_name);
};

window.list_render = exports;
<commit_msg>list_render: Add validate_opts function.<commit_after>const DEFAULTS = {
    INITIAL_RENDER_COUNT: 80,
    LOAD_COUNT: 20,
    instances: new Map(),
};

// ----------------------------------------------------
// This function describes (programatically) how to use
// the list_render widget.
// ----------------------------------------------------

exports.validate_opts = (opts) => {
    if (opts.html_selector && typeof opts.html_selector !== 'function') {
        // We have an html_selector, but it is not a function.
        // This is a programming error.
        blueslip.error('html_selector should be a function.');
        return false;
    }
    return true;
};

exports.get_filtered_items = (value, list, opts) => {
    /*
        This is used by the main object (see `create`),
        but we split it out to make it a bit easier
        to test.
    */
    const get_item = opts.get_item;

    if (!opts.filter) {
        if (get_item) {
            return list.map(get_item);
        }
        return [...list];
    }

    if (opts.filter.filterer) {
        if (get_item) {
            return opts.filter.filterer(
                list.map(get_item),
                value
            );
        }
        return opts.filter.filterer(list, value);
    }

    const predicate = (item) => {
        return opts.filter.predicate(item, value);
    };

    if (get_item) {
        const result = [];

        for (const key of list) {
            const item = get_item(key);
            if (predicate(item)) {
                result.push(item);
            }
        }

        return result;
    }

    return list.filter(predicate);
};

exports.alphabetic_sort = (prop) => {
    return function (a, b) {
        // The conversion to uppercase helps make the sorting case insensitive.
        const str1 = a[prop].toUpperCase();
        const str2 = b[prop].toUpperCase();

        if (str1 === str2) {
            return 0;
        } else if (str1 > str2) {
            return 1;
        }

        return -1;
    };
};

exports.numeric_sort = (prop) => {
    return function (a, b) {
        if (parseFloat(a[prop]) > parseFloat(b[prop])) {
            return 1;
        } else if (parseFloat(a[prop]) === parseFloat(b[prop])) {
            return 0;
        }

        return -1;
    };
};

exports.valid_filter_opts = (opts) => {
    if (!opts.filter) {
        return true;
    }
    if (opts.filter.predicate) {
        if (typeof opts.filter.predicate !== 'function') {
            blueslip.error('Filter predicate is not a function.');
            return false;
        }
        if (opts.filter.filterer) {
            blueslip.error('Filterer and predicate are mutually exclusive.');
            return false;
        }
    } else {
        if (typeof opts.filter.filterer !== 'function') {
            blueslip.error('Filter filterer is not a function (or missing).');
            return false;
        }
    }

    return true;
};

// @params
// container: jQuery object to append to.
// list: The list of items to progressively append.
// opts: An object of random preferences.
exports.create = function ($container, list, opts) {
    if (!opts) {
        blueslip.error('Need opts to create widget.');
        return;
    }

    if (!exports.validate_opts(opts)) {
        return;
    }

    if (opts.name && DEFAULTS.instances.get(opts.name)) {
        // Clear event handlers for prior widget.
        const old_widget = DEFAULTS.instances.get(opts.name);
        old_widget.clear_event_handlers();
    }

    const meta = {
        sorting_function: null,
        sorting_functions: new Map(),
        generic_sorting_functions: {
            alphabetic: exports.alphabetic_sort,
            numeric: exports.numeric_sort,
        },
        offset: 0,
        list: list,
        filtered_list: list,
        reverse_mode: false,
        filter_value: '',
    };

    if (!exports.valid_filter_opts(opts)) {
        return;
    }

    if (opts.get_item && typeof opts.get_item !== 'function') {
        blueslip.error('get_item should be a function');
        return;
    }

    const widget = {};

    widget.filter_and_sort = function () {
        meta.filtered_list = exports.get_filtered_items(
            meta.filter_value,
            meta.list,
            opts
        );

        if (meta.sorting_function) {
            meta.filtered_list.sort(
                meta.sorting_function
            );
        }

        if (meta.reverse_mode) {
            meta.filtered_list.reverse();
        }
    };

    // Reads the provided list (in the scope directly above)
    // and renders the next block of messages automatically
    // into the specified container.
    widget.render = function (how_many) {
        const load_count = how_many || DEFAULTS.LOAD_COUNT;

        // Stop once the offset reaches the length of the original list.
        if (meta.offset >= meta.filtered_list.length) {
            return;
        }

        const slice = meta.filtered_list.slice(meta.offset, meta.offset + load_count);

        const finish = blueslip.start_timing('list_render ' + opts.name);
        let html = "";
        for (const item of slice) {
            const s = opts.modifier(item);

            if (typeof s !== 'string') {
                blueslip.error('List item is not a string: ' + s);
                continue;
            }

            // append the HTML or nothing if corrupt (null, undef, etc.).
            if (s) {
                html += s;
            }
        }

        finish();

        $container.append($(html));
        meta.offset += load_count;
    };

    widget.clear = function () {
        $container.html("");
        meta.offset = 0;
    };

    widget.set_filter_value = function (filter_value) {
        meta.filter_value = filter_value;
    };

    widget.set_reverse_mode = function (reverse_mode) {
        meta.reverse_mode = reverse_mode;
    };

    // the sorting function is either the function or string that calls the
    // function to sort the list by. The prop is used for generic functions
    // that can be called to sort with a particular prop.
    widget.set_sorting_function = function (sorting_function, prop) {
        if (typeof sorting_function === "function") {
            meta.sorting_function = sorting_function;
        } else if (typeof sorting_function === "string") {
            if (typeof prop === "string") {
                /* eslint-disable max-len */
                meta.sorting_function = meta.generic_sorting_functions[sorting_function](prop);
            } else {
                meta.sorting_function = meta.sorting_functions.get(sorting_function);
            }
        }
    };

    widget.set_up_event_handlers = function () {
        meta.scroll_container = scroll_util.get_list_scrolling_container($container);

        // on scroll of the nearest scrolling container, if it hits the bottom
        // of the container then fetch a new block of items and render them.
        meta.scroll_container.on('scroll.list_widget_container', function () {
            if (this.scrollHeight - (this.scrollTop + this.clientHeight) < 10) {
                widget.render();
            }
        });

        if (opts.parent_container) {
            opts.parent_container.on('click.list_widget_sort', "[data-sort]", function () {
                exports.handle_sort($(this), widget);
            });
        }

        if (opts.filter && opts.filter.element) {
            opts.filter.element.on('input.list_widget_filter', function () {
                const value = this.value.toLocaleLowerCase();
                widget.set_filter_value(value);
                widget.hard_redraw();
            });
        }
    };

    widget.clear_event_handlers = function () {
        meta.scroll_container.off('scroll.list_widget_container');

        if (opts.parent_container) {
            opts.parent_container.off('click.list_widget_sort', "[data-sort]");
        }

        if (opts.filter && opts.filter.element) {
            opts.filter.element.off('input.list_widget_filter');
        }
    };

    widget.sort = function (sorting_function, prop) {
        widget.set_sorting_function(sorting_function, prop);
        widget.hard_redraw();
    };

    widget.clean_redraw = function () {
        widget.filter_and_sort();
        widget.clear();
        widget.render(DEFAULTS.INITIAL_RENDER_COUNT);
    };

    widget.hard_redraw = function () {
        widget.clean_redraw();
        if (opts.filter && opts.filter.onupdate) {
            opts.filter.onupdate();
        }
    };

    widget.replace_list_data = function (list) {
        /*
            We mostly use this widget for lists where you are
            not adding or removing rows, so when you do modify
            the list, we have a brute force solution.
        */
        meta.list = list;
        widget.hard_redraw();
    };

    widget.set_up_event_handlers();

    if (opts.sort_fields) {
        for (const [name, sorting_function] of Object.entries(opts.sort_fields)) {
            meta.sorting_functions.set(name, sorting_function);
        }
    }

    if (opts.init_sort) {
        widget.set_sorting_function(...opts.init_sort);
    }

    widget.clean_redraw();

    // Save the instance for potential future retrieval if a name is provided.
    if (opts.name) {
        DEFAULTS.instances.set(opts.name, widget);
    }

    return widget;
};

exports.get = function (name) {
    return DEFAULTS.instances.get(name) || false;
};

exports.handle_sort = function (th, list) {
    /*
        one would specify sort parameters like this:
            - name => sort alphabetic.
            - age  => sort numeric.
            - status => look up `status` in sort_fields
                        to find custom sort function

        */
    const sort_type = th.data("sort");
    const prop_name = th.data("sort-prop");

    if (th.hasClass("active")) {
        if (!th.hasClass("descend")) {
            th.addClass("descend");
        } else {
            th.removeClass("descend");
        }
    } else {
        th.siblings(".active").removeClass("active");
        th.addClass("active");
    }

    list.set_reverse_mode(th.hasClass("descend"));

    // if `prop_name` is defined, it will trigger the generic codepath,
    // and not if it is undefined.
    list.sort(sort_type, prop_name);
};

window.list_render = exports;
<|endoftext|>


<commit_before>/**
 *
 * Copyright 2015, Institute for Systems Biology
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

require.config({
    baseUrl: STATIC_FILES_URL+'js/',
    paths: {
        jquery: 'libs/jquery-1.11.1.min',
        bootstrap: 'libs/bootstrap.min',
        jqueryui: 'libs/jquery-ui.min',
        session_security: 'session_security',
        underscore: 'libs/underscore-min',
    },
    shim: {
        'bootstrap': ['jquery'],
        'session_security': ['jquery'],
    }
});

require([
    'jquery',
    'jqueryui',
    'bootstrap',
    'session_security',
    'base'
], function($, jqueryui, bootstrap, session_security, base) {

    // Resets forms in modals on cancel. Suppressed warning when leaving page with dirty forms
    $('.modal').on('hide.bs.modal', function() {
        var form = $(this).find('form')[0];
        if(form){
            form.reset();
        }
    });

    $('input[name="adjust-datasets"]:radio').change(function() {
        if ($('input[name="adjust-datasets"]:checked').val() === 'alter') {
            $('#datasets-adjust-div').show();
            $('#register-sa input[name="adjust-datasets"][value="remove"]').remove();
            $('.register-sa-div').hide();
            $('.register-sa-btn').attr("disabled","disabled");
        } else {
            $('input[name="datasets"]').attr('checked',false);
            hide_verification();
            $('#datasets-adjust-div').hide();
            $('.register-sa-div').show();
            $('.register-sa-btn').removeAttr("disabled");
        }
    });

    $('#verify-sa div input').change(function() {
       // $('.register-sa-btn').attr("disabled","disabled");
        console.warn("hello world");
        if ($('input[name="adjust-datasets"]').length) {
            if ($('input[name="adjust-datasets"]:checked').val() === 'alter') {
                $('.register-sa-div').hide();
                $('.register-sa-btn').attr("disabled","disabled");
            }
        }
        hide_verification();
    });

    $('#verify-sa').on('submit', function(e) {
        // #user-sa is only on the registration page, not on the adjustment page
        $('#user_sa').length > 0 && $('#user_sa').val($('#user_sa').val().trim());
        $('#js-messages').empty();
        $('#invalid-sa-id').hide();
        e.preventDefault();
        e.stopPropagation();

        // #user-sa is only on the registration page, not on the adjustment page
        if($('#user_sa').length > 0 && $('#user_sa').val().match(/[^A-Za-z0-9\-@\.]/g)) {
            $('#provided-sa-id').text($('#user_sa').val());
            $('#invalid-sa-id').show();
            return false;
        } else {
            $('#provided-sa-id').val('');
            $('#invalid-sa-id').hide();
        }

        var $this = $(this);
        var fields = $this.serialize();
        var user_ver_div = $('.user-verification');
        user_ver_div.hide();
        $('.results_summary').hide();
        $('.cannot-register').hide();
        $('.dcf_analysis_reg_sas').hide();
        $('.dcf_analysis_project').hide();
        $('.dcf_analysis_sas').hide();
        $('.dcf_analysis_data').hide();

        $('.have_verified_div').hide();
        $('.register-sa-div').hide();
        $('.verify-pending').show();

        $this.find('input[type="submit"]').prop('disabled', 'disabled');
        $.ajax({
            url: $this.attr('action'),
            data: fields,
            method: 'POST',
            success: function(data) {
                $('.summary_statement').empty()
                if (data['all_user_datasets_verified']) {
                   $('.summary_statement').append("The project, service account, and users were verified. The registration can proceed.")
                } else {
                    $('.summary_statement').append("Problems were found during verification. Please review the summary below.")
                }
                $('.results_summary').show();
                var tbody = user_ver_div.find('tbody');
                tbody.empty();
                $('.verify-pending').hide();
                var register_form = $('form#register-sa');
                var user_input = register_form.find('input[name="user_sa"]');
                var dataset_input = register_form.find('input[name="datasets"]');
                if (user_input.length === 0) {
                    register_form.append('<input type="hidden" name="user_sa" value="' + data['user_sa'] + '"/>');
                } else {
                    user_input.val(data['user_sa']);
                }
                if (dataset_input.length === 0) {
                    register_form.append('<input type="hidden" name="datasets" value="' + data['datasets'] + '"/>');
                } else {
                    dataset_input.val(data['datasets']);
                }

                /*
                ** Moving to DCF verification, we no longer enumerate the *Datasets* for each user,
                ** but we still enumerate the registration & linking status:
                */

                var roles = data['roles'];
                for (var email in roles) {
                    var member = roles[email];
                    var tr = $('

');
                    tr.append('' + email + '	');
                    if (member['registered_user']) {
                        tr.append('	');
                    } else {
                        tr.append('	');
                    }
                    if (member['nih_registered']) {
                        tr.append('	');
                    } else {
                        tr.append('	');
                    }
                    tbody.append(tr);
                }

                var dcf_analysis_reg_sas_div = $('.dcf_analysis_reg_sas');
                $('.registered_sa_statement').empty()
                $('.registered_sa_statement').append(data['dcf_messages']['dcf_analysis_reg_sas_summary']);

                var dcf_analysis_project_div = $('.dcf_analysis_project');
                $('.project_statement').empty()
                $('.project_statement').append(data['dcf_messages']['dcf_analysis_project_summary']);

                $('.membership_statement').empty()
                $('.membership_statement').append(data['dcf_messages']['dcf_analysis_project_members']);

                var dcf_analysis_sas_div = $('.dcf_analysis_sas');
                var dcf_analysis_sas = dcf_analysis_sas_div.find('tbody');
                var dcf_msg = data['dcf_messages']['dcf_analysis_sas'];
                dcf_analysis_sas.empty();
                for (var key in dcf_msg) {
                    var tr = $('

');
                    var msg = dcf_msg[key]
                    tr.append('' + msg['id'] + '	');
                    if (msg['ok']) {
                        tr.append('	');
                    } else {
                        tr.append('	');
                    }
                    tr.append('' + msg['err'] + '	');
                    dcf_analysis_sas.append(tr);
                }

                var dcf_analysis_data_div = $('.dcf_analysis_data');
                $('.data_summary_statement').empty()
                $('.data_summary_statement').append(data['dcf_messages']['dcf_analysis_data_summary']);
                var dcf_analysis_data = dcf_analysis_data_div.find('tbody');
                var dcf_msg = data['dcf_messages']['dcf_analysis_data'];
                dcf_analysis_data.empty();
                for (var key in dcf_msg) {
                    var tr = $('

');
                    var msg = dcf_msg[key]
                    tr.append('' + msg['id'] + '	');
                    if (msg['ok']) {
                        tr.append('	');
                    } else {
                        tr.append('	');
                    }
                    tr.append('' + msg['err'] + '	');
                    dcf_analysis_data.append(tr);
                }

                // This was to handle full removal, which no longer uses a verification step
                //if ($('input[name="adjust-datasets"]').length) {
                //    if ($('input[name="adjust-datasets"][value="remove"]:checked').length > 0) {
                //        var remove_all = $('input[value="remove"]').clone();
                //        console.log(remove_all);
                //        remove_all.attr("type", "hidden");
                //        register_form.append(remove_all[0]);
                //    }
                //}

                user_ver_div.show();
                dcf_analysis_reg_sas_div.show();
                dcf_analysis_project_div.show();
                dcf_analysis_sas_div.show();
                dcf_analysis_data_div.show();

                $this.find('input[type="submit"]').prop('disabled', '');

                $('.have_verified_div').hide();
                $('.register-sa-div').hide();
                $('.cannot-register').hide();

                // If no datasets were requested, or, they were and verification came out clean, allow registration
                if(data['datasets'].length <= 0 || data['all_user_datasets_verified']) {
                    $('.have_verified_div').show();
                    $('.register-sa-div').show();
                    $('.register-sa-btn').removeAttr("disabled","disabled");
                } else {
                    $('.cannot-register').show();
                    $('.retry-btn').removeAttr("disabled");
                }
            },
            error: function (xhr) {
                var responseJSON = $.parseJSON(xhr.responseText);
                $('.verify-pending').hide();
                $('.verify-sa-btn').prop('disabled', '');
                // If we received a redirect, honor that
                if(responseJSON.redirect) {
                    base.setReloadMsg(responseJSON.level || "error",responseJSON.message);
                    // hide and reset the form
                    $('#verify-sa').hide();
                    $('#verify-sa')[0].reset();
                    window.location = responseJSON.redirect;
                } else {
                    base.showJsMessage(responseJSON.level || "error",responseJSON.message,true);
                }
            }
        });
        return false;
    });

    $('#register-sa').on('submit', function(e) {
        $('.register-sa-btn').attr("disabled","disabled");
        $('#verify-sa').hide();
        $('#verify-sa')[0].reset();
        $('.register-pending').show();
    });

    function hide_verification() {
        var user_ver_div = $('.user-verification');
        var table_body = user_ver_div.find('tbody');

        var dcf_analysis_project_div = $('.dcf_analysis_project');

        var dcf_analysis_sas_div = $('.dcf_analysis_sas');
        var dcf_analysis_sas = dcf_analysis_sas_div.find('tbody');

        var dcf_analysis_data_div = $('.dcf_analysis_data');
        var dcf_analysis_data = dcf_analysis_data_div.find('tbody');

        var dcf_analysis_reg_sas_div = $('.dcf_analysis_reg_sas');
        var have_verified_div = $('.have_verified_div');

        dcf_analysis_project_div.hide();
        dcf_analysis_sas_div.hide();
        dcf_analysis_data_div.hide();
        dcf_analysis_reg_sas_div.hide();
        have_verified_div.hide();

        var register_form = $('form#register-sa');
        var dataset_input = register_form.find('input[name="datasets"]');
        if (dataset_input.length > 0) {
            dataset_input.val([]);
        }

        $('.cannot-register').hide();
        $('.results_summary').hide();
        user_ver_div.hide();

        table_body.empty();
        dcf_analysis_sas.empty();
        dcf_analysis_data.empty();
    }

    $('.retry-btn').on('click', function(e) {
        $('.retry-btn').attr("disabled","disabled");
        hide_verification();
        $('#verify-sa').submit();
    });

});<commit_msg>Fixes for 10/3 testing issues<commit_after>/**
 *
 * Copyright 2015, Institute for Systems Biology
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

require.config({
    baseUrl: STATIC_FILES_URL+'js/',
    paths: {
        jquery: 'libs/jquery-1.11.1.min',
        bootstrap: 'libs/bootstrap.min',
        jqueryui: 'libs/jquery-ui.min',
        session_security: 'session_security',
        underscore: 'libs/underscore-min',
    },
    shim: {
        'bootstrap': ['jquery'],
        'session_security': ['jquery'],
    }
});

require([
    'jquery',
    'jqueryui',
    'bootstrap',
    'session_security',
    'base'
], function($, jqueryui, bootstrap, session_security, base) {

    // Resets forms in modals on cancel. Suppressed warning when leaving page with dirty forms
    $('.modal').on('hide.bs.modal', function() {
        var form = $(this).find('form')[0];
        if(form){
            form.reset();
        }
    });

    $('input[name="adjust-datasets"]:radio').change(function() {
        if ($('input[name="adjust-datasets"]:checked').val() === 'alter') {
            $('#datasets-adjust-div').show();
            $('#register-sa input[name="adjust-datasets"][value="remove"]').remove();
            $('.register-sa-div').hide();
            $('.register-sa-btn').attr("disabled","disabled");
        } else {
            $('input[name="datasets"]').attr('checked',false);
            hide_verification();
            $('#datasets-adjust-div').hide();
            $('.register-sa-div').show();
            $('.register-sa-btn').removeAttr("disabled");
        }
    });

    $('#verify-sa div input').change(function() {
       // $('.register-sa-btn').attr("disabled","disabled");
        console.warn("hello world");
        if ($('input[name="adjust-datasets"]').length) {
            if ($('input[name="adjust-datasets"]:checked').val() === 'alter') {
                $('.register-sa-div').hide();
                $('.register-sa-btn').attr("disabled","disabled");
            }
        }
        hide_verification();
    });

    $('#verify-sa').on('submit', function(e) {
        // #user-sa is only on the registration page, not on the adjustment page
        $('#user_sa').length > 0 && $('#user_sa').val($('#user_sa').val().trim());
        $('#js-messages').empty();
        $('#invalid-sa-id').hide();
        e.preventDefault();
        e.stopPropagation();

        // #user-sa is only on the registration page, not on the adjustment page
        if($('#user_sa').length > 0 && $('#user_sa').val().match(/[^A-Za-z0-9\-@\.]/g)) {
            $('#provided-sa-id').text($('#user_sa').val());
            $('#invalid-sa-id').show();
            return false;
        } else {
            $('#provided-sa-id').val('');
            $('#invalid-sa-id').hide();
        }

        var $this = $(this);
        var fields = $this.serialize();
        var user_ver_div = $('.user-verification');
        user_ver_div.hide();
        $('.results_summary').hide();
        $('.cannot-register').hide();
        $('.dcf_analysis_reg_sas').hide();
        $('.dcf_analysis_project').hide();
        $('.dcf_analysis_sas').hide();
        $('.dcf_analysis_data').hide();

        $('.have_verified_div').hide();
        $('.register-sa-div').hide();
        $('.verify-pending').show();

        $this.find('input[type="submit"]').prop('disabled', 'disabled');
        $.ajax({
            url: $this.attr('action'),
            data: fields,
            method: 'POST',
            success: function(data) {
                $('.summary_statement').empty()
                if (data['all_user_datasets_verified']) {
                   $('.summary_statement').append("The project, service account, and users were verified. The registration can proceed.")
                } else {
                    $('.summary_statement').append("Problems were found during verification. Please review the summary below.")
                }
                $('.results_summary').show();
                var tbody = user_ver_div.find('tbody');
                tbody.empty();
                $('.verify-pending').hide();
                var register_form = $('form#register-sa');
                var user_input = register_form.find('input[name="user_sa"]');
                var dataset_input = register_form.find('input[name="datasets"]');
                if (user_input.length === 0) {
                    register_form.append('<input type="hidden" name="user_sa" value="' + data['user_sa'] + '"/>');
                } else {
                    user_input.val(data['user_sa']);
                }
                if (dataset_input.length === 0) {
                    register_form.append('<input type="hidden" name="datasets" value="' + data['datasets'] + '"/>');
                } else {
                    dataset_input.val(data['datasets']);
                }

                /*
                ** Moving to DCF verification, we no longer enumerate the *Datasets* for each user,
                ** but we still enumerate the registration & linking status:
                */

                var roles = data['roles'];
                for (var email in roles) {
                    var member = roles[email];
                    var tr = $('

');
                    tr.append('' + email + '	');
                    if (member['registered_user']) {
                        tr.append('	');
                    } else {
                        tr.append('	');
                    }
                    if (member['nih_registered']) {
                        tr.append('	');
                    } else {
                        tr.append('	');
                    }
                    tbody.append(tr);
                }

                if ('dcf_messages' in data) {
                    var dcf_analysis_reg_sas_div = $('.dcf_analysis_reg_sas');
                    $('.registered_sa_statement').empty()
                    $('.registered_sa_statement').append(data['dcf_messages']['dcf_analysis_reg_sas_summary']);

                    var dcf_analysis_project_div = $('.dcf_analysis_project');
                    $('.project_statement').empty()
                    $('.project_statement').append(data['dcf_messages']['dcf_analysis_project_summary']);

                    $('.membership_statement').empty()
                    $('.membership_statement').append(data['dcf_messages']['dcf_analysis_project_members']);

                    var dcf_analysis_sas_div = $('.dcf_analysis_sas');
                    var dcf_analysis_sas = dcf_analysis_sas_div.find('tbody');
                    var dcf_msg = data['dcf_messages']['dcf_analysis_sas'];
                    dcf_analysis_sas.empty();
                    for (var key in dcf_msg) {
                        var tr = $('

');
                        var msg = dcf_msg[key]
                        tr.append('' + msg['id'] + '	');
                        if (msg['ok']) {
                            tr.append('	');
                        } else {
                            tr.append('	');
                        }
                        tr.append('' + msg['err'] + '	');
                        dcf_analysis_sas.append(tr);
                    }

                    var dcf_analysis_data_div = $('.dcf_analysis_data');
                    $('.data_summary_statement').empty()
                    $('.data_summary_statement').append(data['dcf_messages']['dcf_analysis_data_summary']);
                    var dcf_analysis_data = dcf_analysis_data_div.find('tbody');
                    var dcf_msg = data['dcf_messages']['dcf_analysis_data'];
                    dcf_analysis_data.empty();
                    for (var key in dcf_msg) {
                        var tr = $('

');
                        var msg = dcf_msg[key]
                        tr.append('' + msg['id'] + '	');
                        if (msg['ok']) {
                            tr.append('	');
                        } else {
                            tr.append('	');
                        }
                        tr.append('' + msg['err'] + '	');
                        dcf_analysis_data.append(tr);
                    }

                    dcf_analysis_reg_sas_div.show();
                    dcf_analysis_project_div.show();
                    dcf_analysis_sas_div.show();
                    dcf_analysis_data_div.show();
                }
                user_ver_div.show();

                $this.find('input[type="submit"]').prop('disabled', '');

                $('.have_verified_div').hide();
                $('.register-sa-div').hide();
                $('.cannot-register').hide();

                // If no datasets were requested, or, they were and verification came out clean, allow registration
                if(data['datasets'].length <= 0 || data['all_user_datasets_verified']) {
                    $('.have_verified_div').show();
                    $('.register-sa-div').show();
                    $('.register-sa-btn').removeAttr("disabled","disabled");
                } else {
                    $('.cannot-register').show();
                    $('.retry-btn').removeAttr("disabled");
                }
            },
            error: function (xhr) {
                var responseJSON = $.parseJSON(xhr.responseText);
                $('.verify-pending').hide();
                $('.verify-sa-btn').prop('disabled', '');
                // If we received a redirect, honor that
                if(responseJSON.redirect) {
                    base.setReloadMsg(responseJSON.level || "error",responseJSON.message);
                    // hide and reset the form
                    $('#verify-sa').hide();
                    $('#verify-sa')[0].reset();
                    window.location = responseJSON.redirect;
                } else {
                    base.showJsMessage(responseJSON.level || "error",responseJSON.message,true);
                }
            }
        });
        return false;
    });

    $('#register-sa').on('submit', function(e) {
        $('.register-sa-btn').attr("disabled","disabled");
        $('#verify-sa').hide();
        $('#verify-sa')[0].reset();
        $('.register-pending').show();
    });

    function hide_verification() {
        var user_ver_div = $('.user-verification');
        var table_body = user_ver_div.find('tbody');

        var dcf_analysis_project_div = $('.dcf_analysis_project');

        var dcf_analysis_sas_div = $('.dcf_analysis_sas');
        var dcf_analysis_sas = dcf_analysis_sas_div.find('tbody');

        var dcf_analysis_data_div = $('.dcf_analysis_data');
        var dcf_analysis_data = dcf_analysis_data_div.find('tbody');

        var dcf_analysis_reg_sas_div = $('.dcf_analysis_reg_sas');
        var have_verified_div = $('.have_verified_div');

        dcf_analysis_project_div.hide();
        dcf_analysis_sas_div.hide();
        dcf_analysis_data_div.hide();
        dcf_analysis_reg_sas_div.hide();
        have_verified_div.hide();

        var register_form = $('form#register-sa');
        var dataset_input = register_form.find('input[name="datasets"]');
        if (dataset_input.length > 0) {
            dataset_input.val([]);
        }

        $('.cannot-register').hide();
        $('.results_summary').hide();
        user_ver_div.hide();

        table_body.empty();
        dcf_analysis_sas.empty();
        dcf_analysis_data.empty();
    }

    $('.retry-btn').on('click', function(e) {
        $('.retry-btn').attr("disabled","disabled");
        hide_verification();
        $('#verify-sa').submit();
    });

});<|endoftext|>


<commit_before>
_r('form_templates'); // see app.initiaize

var App = Backbone.Router.extend({

  models: {},
  collections: {},
  views: {},
  formHandler: {},
  current: {
    module: null,
    action: null,
    view: null
  },
  _templates: {},
  history: [],

  initialize: function (spec) {
    var self = this;
    this.config = {
      default_module: 'main',
      default_action: 'index',
      $content: $('#content'),
      $breadcrumb: $('#breadcrumb'),
      $navigation: $('#navigation')
    };
    _.extend(this.config, spec);

    this.route('*args', 'routeToView', this.router);

    this.template('form', 'input', {}, function () {
      // make sure we have the form templates loaded so we can safely call them from other templates
      _r('form_templates', true);
    });

    var reload_navigation = function (refresh_active) {
      self.template('page', 'top_navigation', {}, function (html) {
        self.config.$navigation.html(html);
        if (refresh_active) {
          self.navigation();
        }
      });
    }
    reload_navigation();
    this.bind('login', function () {
      if (self.current.module !== null) {
        _.delay(reload_navigation,0,true);
      }
    });
  },

  base: {}, // backbone.extend.js

  router: function(route, force_rerender){
    var module = this.config.default_module,
        action = this.config.default_action,
        parameters = [],
        previous = this.current,
        self = this,
        orig_route = route;

    self.closeOverlay();
    self.history_add(orig_route);

    if (route !== '') {
      route = route.split('/');
      module = route[0].toLowerCase();
      if (route.length > 1 && route[1])
        action = route[1].toLowerCase();
      if (route.length > 2) {
        route.splice(0, 2);
        parameters = route;
      }
    }

    var data_expired = this.current.view !== null ? this.current.view.isExpired() : false;

    if (this.current.module === module && this.current.action === action) {
      if (this.current.view !== null && ! force_rerender && ! data_expired) {
        console.log('data not stale, not reloading view');
        return false;
      }
    }

    try {
      this.view(module, action, null, function (view) {
        self.current = {
          module: module,
          action: action,
          view: view,
          route: orig_route
        };
        self.navigation();
        self.breadcrumb(parameters);
      });
    } catch(e) {
      this.current = previous;
      if (e !== 'view_stop') {
        $.jGrowl('Sorry, there was an error while trying to process your action');
        console.log('Routing error in route '+route+':');
        console.log(e.stack);
        app.back();
      } else {
        console.log('view stopped rendering');
      }
    }
  },

  view: function(module, action, $el, callback) {
    if (typeof (callback) !== 'function') {
      callback = $.noop;
    }
    var view;
    var after_render = function () {
      if ($el.hasClass('main_content')) {
        $el.siblings().remove();
      }
      callback(view);
    };

    if (!$el) {
      $el = $('

')
              .appendTo('#content')
              .addClass('main_content');
    }
    $el.data('module', module);
    $el.data('action', action);

    if ( ! this.views.hasOwnProperty(module) || ! this.views[module].hasOwnProperty(action) ) {
      // try to just load a template without a proper view
      console.log('No view found, trying to render default view. ('+module+':'+action+')');
      view = new this.base.pageView(module, action, $el);
      view.render();
    } else {
      view = new this.views[module][action](module, action, $el);
    }
    if (view.rendered) {
      after_render();
    } else {
      view.once('rendered', after_render);
      view.once('error', after_render);
    }
    return view;
  },

  go: function (str) {
    this.navigate('#'+str, true);
  },

  reload: function (force) {
    this.router(this.history[0] || '/', force);
  },

  back: function () {
    this.history.shift();
    this.navigate(this.history[0] || '', true);
  },

  history_add: function (route) {
    if (route !== this.history[0]) {
      this.history.unshift(route);
    }
    if (this.history.length > 20) {
      this.history.splice(20);
    }
  },

  breadcrumb: function (parameters) {
    return;
    var locals = _.extend({
        parameters: parameters && parameters.length && parameters[0].match(/[\d]*/) && parameters[0]
      }, this.current),
      self = this;
    this.template('page', 'breadcrumb', locals, function (html) {
      self.config.$breadcrumb.html(html);
    });
  },

  navigation: function  ()  {
    var self = this;
    var $nav = this.config.$navigation.children('ul.nav');
    $nav
      .find('li')
        .removeClass('active')
    var $nav_matches = $nav
      .children('li')
        .has('a[href^="#'+self.current.module+'/'+self.current.action+'"]')
          .first()
            .addClass('active');
    if ($nav_matches.length === 0) {
      $nav
        .children('li')
          .has('a[href^="#'+this.current.module+'"]')
            .first()
              .addClass('active');
    }

    var $subnav = $nav
      .find('ul.sub_navigation')
        .empty();
    this.template(this.current.module, 'sub_navigation', {
        _t: function (name) {
          return $.t(name, 'general', self.current.module);
        }
      }, function (html) {
      $subnav
        .html(html)
        .find('li')
          .removeClass('active')
        .has('a[href^="#'+self.current.module+'/'+self.current.action+'"]')
          .addClass('active');
    });
  },

  _templates: {},
  jade: require('jade'),
  template: function (module, name, locals, callback) {
    var self = this,
    tmpl_module;

    locals = locals || {};

    _.extend(locals, {
      partial: function (name) {
        var args = _.toArray(arguments);
        args.shift();
        var locals_ = _.extend({},{
          parentLocals: locals,
          _t: locals._t,
          args: args
        });
        return self.template(module, name, locals_);
      },
      form: function (element, params) {
        params._t = locals._t;
        return self.template('form', element, params);
      }
    });

    var options = {
      layout: false,
      colons: true
    };

    // TODO: cache templates in localStorage
    if (this._templates.hasOwnProperty(module) && this._templates[module].hasOwnProperty(name)) {
      var html = this._templates[module][name](locals);
      if (typeof(callback) === 'function') {
        callback(html);
      } else {
        return html;
      }
    } else if ( ! this._templates.hasOwnProperty(module)) {
      if (typeof(callback) !== 'function') {
        console.dir(this._templates);
        console.dir(arguments);
        throw new Error('Can\'t call _template without a callback if the template module was not loaded yet! (might be an invalid template call)');
      }
      tmpl_module = $('

').appendTo('#templates');
      if (module === null) {
        debugger;
      }
      $.get('/templates/tmpl-'+module+'.html', function (data) {
        var found = false;
        if (!data) {
          console.log('Template module "'+module+'" not found.');
          return callback(false);
        }
        self._templates[module] = {};

        tmpl_module.append(data).children('script').each(function (i, val) {
          var tmpl = self.jade.compile(val.innerHTML, options),
              loaded_name = val.getAttribute('name');
          self._templates[module][loaded_name] = tmpl;
          if (loaded_name === name) {
            found = tmpl;
          }
        }).end().remove();
        if (!found) {
          console.log('Template view "'+name+'" not found in module "'+module+'"');
          callback(false);
        } else {
          callback(found(locals));
        }
      }, 'html');
    } else {
      console.log('Template "'+name+'" in module "'+module+'" not found.');
      callback(false);
    }
  }
});<commit_msg>Refactor navigation rendering flow to make sure a navigation is rendered even if no view is rendered<commit_after>
_r('form_templates'); // see app.initiaize

var App = Backbone.Router.extend({

  models: {},
  collections: {},
  views: {},
  formHandler: {},
  current: {
    module: null,
    action: null,
    view: null
  },
  _templates: {},
  history: [],

  initialize: function (spec) {
    var self = this;
    this.config = {
      default_module: 'main',
      default_action: 'index',
      $content: $('#content'),
      $breadcrumb: $('#breadcrumb'),
      $navigation: $('#navigation')
    };
    _.extend(this.config, spec);

    this.route('*args', 'routeToView', this.router);

    this.template('form', 'input', {}, function () {
      // make sure we have the form templates loaded so we can safely call them from other templates
      _r('form_templates', true);
    });

    this.bind('login', function () {
      if (self.current.module !== null) {
        _.delay(self.navigation,0,true);
      }
    });
    self.navigation();
  },

  base: {}, // backbone.extend.js

  router: function(route, force_rerender){
    var module = this.config.default_module,
        action = this.config.default_action,
        parameters = [],
        previous = this.current,
        self = this,
        orig_route = route;

    self.closeOverlay();
    self.history_add(orig_route);

    if (route !== '') {
      route = route.split('/');
      module = route[0].toLowerCase();
      if (route.length > 1 && route[1])
        action = route[1].toLowerCase();
      if (route.length > 2) {
        route.splice(0, 2);
        parameters = route;
      }
    }

    var data_expired = this.current.view !== null ? this.current.view.isExpired() : false;

    if (this.current.module === module && this.current.action === action) {
      if (this.current.view !== null && ! force_rerender && ! data_expired) {
        console.log('data not stale, not reloading view');
        return false;
      }
    }

    try {
      this.view(module, action, null, function (view) {
        self.current = {
          module: module,
          action: action,
          view: view,
          route: orig_route
        };
        self.navigation();
        self.breadcrumb(parameters);
      });
    } catch(e) {
      this.current = previous;
      if (e !== 'view_stop') {
        $.jGrowl('Sorry, there was an error while trying to process your action');
        console.log('Routing error in route '+route+':');
        console.log(e.stack);
        self.back();
      } else {
        console.log('view stopped rendering');
      }
    }
  },

  view: function(module, action, $el, callback) {
    if (typeof (callback) !== 'function') {
      callback = $.noop;
    }
    var view;
    var after_render = function () {
      if ($el.hasClass('main_content')) {
        $el.siblings().remove();
      }
      callback(view);
    };

    if (!$el) {
      $el = $('

')
              .appendTo('#content')
              .addClass('main_content');
    }
    $el.data('module', module);
    $el.data('action', action);

    if ( ! this.views.hasOwnProperty(module) || ! this.views[module].hasOwnProperty(action) ) {
      // try to just load a template without a proper view
      console.log('No view found, trying to render default view. ('+module+':'+action+')');
      view = new this.base.pageView(module, action, $el);
      view.render();
    } else {
      view = new this.views[module][action](module, action, $el);
    }
    if (view.rendered) {
      after_render();
    } else {
      view.once('rendered', after_render);
      view.once('error', after_render);
    }
    return view;
  },

  go: function (str) {
    this.navigate('#'+str, true);
  },

  reload: function (force) {
    this.router(this.history[0] || '/', force);
  },

  back: function () {
    this.history.shift();
    this.navigate(this.history[0] || '', true);
    if (this.history.length > 0) {
      this.current = {
        module: this.config.default_module,
        action: this.config.default_action,
        route: '/'
      };
      this.navigation();
    }
  },

  history_add: function (route) {
    if (route !== this.history[0]) {
      this.history.unshift(route);
    }
    if (this.history.length > 20) {
      this.history.splice(20);
    }
  },

  breadcrumb: function (parameters) {
    return;
    var locals = _.extend({
        parameters: parameters && parameters.length && parameters[0].match(/[\d]*/) && parameters[0]
      }, this.current),
      self = this;
    this.template('page', 'breadcrumb', locals, function (html) {
      self.config.$breadcrumb.html(html);
    });
  },

  navigation: function  ()  {
    var self = this;
    var $nav = this.config.$navigation;
    self.template('page', 'top_navigation', {}, function (html) {
      $nav
        .html(html)
        .find('li')
          .removeClass('active');
      var $nav_matches = $nav
        .find('.nav > li')
          .has('a[href^="#'+self.current.module+'/'+self.current.action+'"]')
            .first()
              .addClass('active');
      if ($nav_matches.length === 0) {
        $nav
          .find('.nav > li')
            .has('a[href^="#'+self.current.module+'"]')
              .first()
                .addClass('active');
      }

      var $subnav = $nav
        .find('ul.sub_navigation')
          .empty();
      if (self.current.module) {
        self.template(self.current.module, 'sub_navigation', {
            _t: function (name) {
              return $.t(name, 'general', self.current.module);
            }
          }, function (html) {
          $subnav
            .html(html)
            .find('li')
              .removeClass('active')
            .has('a[href^="#'+self.current.module+'/'+self.current.action+'"]')
              .addClass('active');
        });
      }
    });
  },

  _templates: {},
  jade: require('jade'),
  template: function (module, name, locals, callback) {
    var self = this,
    tmpl_module;

    locals = locals || {};

    _.extend(locals, {
      partial: function (name) {
        var args = _.toArray(arguments);
        args.shift();
        var locals_ = _.extend({},{
          parentLocals: locals,
          _t: locals._t,
          args: args
        });
        return self.template(module, name, locals_);
      },
      form: function (element, params) {
        params._t = locals._t;
        return self.template('form', element, params);
      }
    });

    var options = {
      layout: false,
      colons: true
    };

    // TODO: cache templates in localStorage
    if (this._templates.hasOwnProperty(module) && this._templates[module].hasOwnProperty(name)) {
      var html = this._templates[module][name](locals);
      if (typeof(callback) === 'function') {
        callback(html);
      } else {
        return html;
      }
    } else if ( ! this._templates.hasOwnProperty(module)) {
      if (typeof(callback) !== 'function') {
        console.dir(this._templates);
        console.dir(arguments);
        throw new Error('Can\'t call _template without a callback if the template module was not loaded yet! (might be an invalid template call)');
      }
      tmpl_module = $('

').appendTo('#templates');
      if (module === null) {
        debugger;
      }
      $.get('/templates/tmpl-'+module+'.html', function (data) {
        var found = false;
        if (!data) {
          console.log('Template module "'+module+'" not found.');
          return callback(false);
        }
        self._templates[module] = {};

        tmpl_module.append(data).children('script').each(function (i, val) {
          var tmpl = self.jade.compile(val.innerHTML, options),
              loaded_name = val.getAttribute('name');
          self._templates[module][loaded_name] = tmpl;
          if (loaded_name === name) {
            found = tmpl;
          }
        }).end().remove();
        if (!found) {
          console.log('Template view "'+name+'" not found in module "'+module+'"');
          callback(false);
        } else {
          callback(found(locals));
        }
      }, 'html');
    } else {
      console.log('Template "'+name+'" in module "'+module+'" not found.');
      callback(false);
    }
  }
});<|endoftext|>


<commit_before>const Discord = require('discord.js');
// 3040-309F : hiragana
// 30A0-30FF : katakana
// FF66-FF9D : half-width katakana
// 4E00-9FAF : common and uncommon kanji
exports.REGEX_JPN = /[\u3040-\u30FF]|[\uFF66-\uFF9D]|[\u4E00-\u9FAF]/;
exports.REGEX_ENG = /[a-vx-zA-VX-Z]|[ａ-ｖｘ-ｚＡ-ＶＸ-Ｚ]/;
exports.REGEX_URL = /https?:\/\/(www\.)?\S{2,256}\.[a-z]{2,6}\S*/g;

exports.REGEX_CUSTOM_EMOTES = /(<a?:[\S]+:\d+>)/g;
exports.REGEX_EMOJIS = /(?:[\u2700-\u27bf]|(?:\ud83c[\udde6-\uddff]){2}|[\ud800-\udbff][\udc00-\udfff]|[\u0023-\u0039]\ufe0f?\u20e3|\u3299|\u3297|\u303d|\u3030|\u24c2|\ud83c[\udd70-\udd71]|\ud83c[\udd7e-\udd7f]|\ud83c\udd8e|\ud83c[\udd91-\udd9a]|\ud83c[\udde6-\uddff]|\ud83c[\ude01-\ude02]|\ud83c\ude1a|\ud83c\ude2f|\ud83c[\ude32-\ude3a]|\ud83c[\ude50-\ude51]|\u203c|\u2049|[\u25aa-\u25ab]|\u25b6|\u25c0|[\u25fb-\u25fe]|\u00a9|\u00ae|\u2122|\u2139|\ud83c\udc04|[\u2600-\u26FF]|\u2b05|\u2b06|\u2b07|\u2b1b|\u2b1c|\u2b50|\u2b55|\u231a|\u231b|\u2328|\u23cf|[\u23e9-\u23f3]|[\u23f8-\u23fa]|\ud83c\udccf|\u2934|\u2935|[\u2190-\u21ff])/g;
exports.REGEX_USER = /<@!?\d+>/g;
exports.REGEX_CHAN = /<#\d+>/g;
exports.REGEX_ROLE = /<@&\d+>/g;
exports.REGEX_ID = /<(@!?|#|@&|a?:[\S]+:)\d+>/g;

exports.searchUser = function(message, content, server, bot) {
  let mentions = message.mentions.users;
  content = content.trim();
  if (mentions.size != 0) {
    return mentions.first();
  } else if (content != '') { // search name
    let regex = content[0] == '*';
    if (regex) {
      let r = new RegExp(content.substr(1, content.length), 'i');
      for (let id in server.users) {
        let u = server.guild.members.get(id); // TODO change to fetch?
        if (u == undefined) continue; // if left
        if (r.test(u.user.tag) || r.test(u.nickname)) {
          return u.user;
        }
      }
    } else {
      content = content.toLowerCase();
      for (let id in server.users) {
        let u = server.guild.members.get(id);
        if (id == content) {
          return bot.fetchUser(id); // This returns a Promise
        }
        if (u == undefined) continue; // user left
        if (u.user.tag.toLowerCase().startsWith(content) || (u.nickname && u.nickname.toLowerCase().startsWith(content))) {
          return u.user;
        }
      }
    }
  }
  return null;
};

exports.LANG = Object.freeze({
  ENG : 1,
  JPN : 1 << 1,
  OTH : 1 << 2,
  ESC : 1 << 3
});

exports.lang = function(content) {
  let jpCount = 0;
  let enCount = 0;
  let other = 0;
  let result = 0;
  content = content.replace(exports.REGEX_URL, '');
  content = content.replace(exports.REGEX_ID, '');
  content = content.replace(/o.o/i, '');
  for (let l of content) {
    if (l == '*' || l == '＊') {
      result |= exports.LANG.ESC;
    }
    if (exports.REGEX_JPN.test(l)) {
      jpCount++;
    } else if (exports.REGEX_ENG.test(l)) {
      enCount++;
    } else if (!/[\sw]/i.test(l)){
      other++;
    }
  }
  if (jpCount == enCount) {
    return result | exports.LANG.OTH;
  }
  if (jpCount < 3 && enCount < 3 && other > 0) return result | exports.LANG.OTH; // it's probably a face
  return  jpCount * 1.7 > enCount ? result | exports.LANG.JPN : result | exports.LANG.ENG;
};

exports.postLogs = function(msg, server) {
  let embed = new Discord.RichEmbed();
  let date = new Date(msg.time);
  embed.setAuthor(`${msg.atag} ID: ${msg.aid}` ,msg.apfp);
  if (msg.del) { // message was deleted
    embed.title = `Message Deleted after ${msg.dur} seconds`;
    embed.description = msg.con;
    embed.color = Number('0xDB3C3C');
  } else { // message was edited
    embed.title = `Message Edited after ${msg.dur} seconds`;
    embed.addField('Before:', `${msg.con}`, false);
    embed.addField('After:', `${msg.acon}`, false);
    embed.color = Number('0xff9933');
  }
  embed.setFooter(`#${msg.ch}`);
  embed.timestamp = date;
  if (msg.img != '') { // if != null
    embed.addField('imgur link', msg.img, false);
    embed.setThumbnail(msg.img);
  }
  let chan = server.guild.channels.get(server.modLog); // #mod_log
  if (!chan) return;
  chan.send({embed});
};

exports.paginate = async function(msg, embed, list, authorID, searchRank, bot) {
  await msg.react('◀');
  await msg.react('▶');
  msg.react('🔻');
  msg.react('⏮');
  let authorPage = Math.floor((searchRank - 1) / 25);
  let maxPageNum = Math.floor(list.length / 25);
  let pageNum = 0;
  async function reload() {
    for (let i = 0; i < 25; i++) {
      let rank = i + pageNum * 25;
      if (list[rank]) {
        let [key, val, username] = list[rank];
        if (!username) {
          let user = await bot.fetchUser(key);
          if (!user) continue;
          username = user.username;
          list[rank][2] = username;
        }
        embed.fields[i] = {name: `${rank + 1}) ${username}`, value: val, inline:true };
      } else {
        embed.fields.length = i;
        break;
      }
    }
    msg.edit({embed});
  }
  const filter = (reaction, user) => /[◀▶⏭]/.test(reaction.emoji.name) && user.id === authorID;
  const collector = msg.createReactionCollector(filter, { time: 60 * 1000 });
  collector.client.on('messageReactionRemove', collector.listener);
  collector.on('collect', r => {
    switch(r.emoji.name) {
    case '▶':
      if (pageNum < maxPageNum) {
        pageNum++;
        reload();
      }
      break;
    case '◀':
      if (pageNum > 0) {
        pageNum--;
        reload();
      }
      break;
    case '🔻':
      if (pageNum != authorPage) {
        pageNum = authorPage;
        reload();
      }
      break;
    case '⏮':
      if (pageNum != 0) {
        pageNum = 0;
        reload();
      }
      break;
    }
  });
  collector.on('end', () => {
    msg.clearReactions();
    collector.client.removeListener('messageReactionRemove', collector.listener);
  });
};<commit_msg>paginate test 4<commit_after>const Discord = require('discord.js');
// 3040-309F : hiragana
// 30A0-30FF : katakana
// FF66-FF9D : half-width katakana
// 4E00-9FAF : common and uncommon kanji
exports.REGEX_JPN = /[\u3040-\u30FF]|[\uFF66-\uFF9D]|[\u4E00-\u9FAF]/;
exports.REGEX_ENG = /[a-vx-zA-VX-Z]|[ａ-ｖｘ-ｚＡ-ＶＸ-Ｚ]/;
exports.REGEX_URL = /https?:\/\/(www\.)?\S{2,256}\.[a-z]{2,6}\S*/g;

exports.REGEX_CUSTOM_EMOTES = /(<a?:[\S]+:\d+>)/g;
exports.REGEX_EMOJIS = /(?:[\u2700-\u27bf]|(?:\ud83c[\udde6-\uddff]){2}|[\ud800-\udbff][\udc00-\udfff]|[\u0023-\u0039]\ufe0f?\u20e3|\u3299|\u3297|\u303d|\u3030|\u24c2|\ud83c[\udd70-\udd71]|\ud83c[\udd7e-\udd7f]|\ud83c\udd8e|\ud83c[\udd91-\udd9a]|\ud83c[\udde6-\uddff]|\ud83c[\ude01-\ude02]|\ud83c\ude1a|\ud83c\ude2f|\ud83c[\ude32-\ude3a]|\ud83c[\ude50-\ude51]|\u203c|\u2049|[\u25aa-\u25ab]|\u25b6|\u25c0|[\u25fb-\u25fe]|\u00a9|\u00ae|\u2122|\u2139|\ud83c\udc04|[\u2600-\u26FF]|\u2b05|\u2b06|\u2b07|\u2b1b|\u2b1c|\u2b50|\u2b55|\u231a|\u231b|\u2328|\u23cf|[\u23e9-\u23f3]|[\u23f8-\u23fa]|\ud83c\udccf|\u2934|\u2935|[\u2190-\u21ff])/g;
exports.REGEX_USER = /<@!?\d+>/g;
exports.REGEX_CHAN = /<#\d+>/g;
exports.REGEX_ROLE = /<@&\d+>/g;
exports.REGEX_ID = /<(@!?|#|@&|a?:[\S]+:)\d+>/g;

exports.searchUser = function(message, content, server, bot) {
  let mentions = message.mentions.users;
  content = content.trim();
  if (mentions.size != 0) {
    return mentions.first();
  } else if (content != '') { // search name
    let regex = content[0] == '*';
    if (regex) {
      let r = new RegExp(content.substr(1, content.length), 'i');
      for (let id in server.users) {
        let u = server.guild.members.get(id); // TODO change to fetch?
        if (u == undefined) continue; // if left
        if (r.test(u.user.tag) || r.test(u.nickname)) {
          return u.user;
        }
      }
    } else {
      content = content.toLowerCase();
      for (let id in server.users) {
        let u = server.guild.members.get(id);
        if (id == content) {
          return bot.fetchUser(id); // This returns a Promise
        }
        if (u == undefined) continue; // user left
        if (u.user.tag.toLowerCase().startsWith(content) || (u.nickname && u.nickname.toLowerCase().startsWith(content))) {
          return u.user;
        }
      }
    }
  }
  return null;
};

exports.LANG = Object.freeze({
  ENG : 1,
  JPN : 1 << 1,
  OTH : 1 << 2,
  ESC : 1 << 3
});

exports.lang = function(content) {
  let jpCount = 0;
  let enCount = 0;
  let other = 0;
  let result = 0;
  content = content.replace(exports.REGEX_URL, '');
  content = content.replace(exports.REGEX_ID, '');
  content = content.replace(/o.o/i, '');
  for (let l of content) {
    if (l == '*' || l == '＊') {
      result |= exports.LANG.ESC;
    }
    if (exports.REGEX_JPN.test(l)) {
      jpCount++;
    } else if (exports.REGEX_ENG.test(l)) {
      enCount++;
    } else if (!/[\sw]/i.test(l)){
      other++;
    }
  }
  if (jpCount == enCount) {
    return result | exports.LANG.OTH;
  }
  if (jpCount < 3 && enCount < 3 && other > 0) return result | exports.LANG.OTH; // it's probably a face
  return  jpCount * 1.7 > enCount ? result | exports.LANG.JPN : result | exports.LANG.ENG;
};

exports.postLogs = function(msg, server) {
  let embed = new Discord.RichEmbed();
  let date = new Date(msg.time);
  embed.setAuthor(`${msg.atag} ID: ${msg.aid}` ,msg.apfp);
  if (msg.del) { // message was deleted
    embed.title = `Message Deleted after ${msg.dur} seconds`;
    embed.description = msg.con;
    embed.color = Number('0xDB3C3C');
  } else { // message was edited
    embed.title = `Message Edited after ${msg.dur} seconds`;
    embed.addField('Before:', `${msg.con}`, false);
    embed.addField('After:', `${msg.acon}`, false);
    embed.color = Number('0xff9933');
  }
  embed.setFooter(`#${msg.ch}`);
  embed.timestamp = date;
  if (msg.img != '') { // if != null
    embed.addField('imgur link', msg.img, false);
    embed.setThumbnail(msg.img);
  }
  let chan = server.guild.channels.get(server.modLog); // #mod_log
  if (!chan) return;
  chan.send({embed});
};

exports.paginate = async function(msg, embed, list, authorID, searchRank, bot) {
  await msg.react('◀');
  await msg.react('▶');
  await msg.react('🔻');
  msg.react('⏮');
  let authorPage = Math.floor((searchRank - 1) / 25);
  let maxPageNum = Math.floor(list.length / 25);
  let pageNum = 0;
  async function reload() {
    for (let i = 0; i < 25; i++) {
      let rank = i + pageNum * 25;
      if (list[rank]) {
        let [key, val, username] = list[rank];
        if (!username) {
          let user = await bot.fetchUser(key);
          if (!user) continue;
          username = user.username;
          list[rank][2] = username;
        }
        embed.fields[i] = {name: `${rank + 1}) ${username}`, value: val, inline:true };
      } else {
        embed.fields.length = i;
        break;
      }
    }
    msg.edit({embed});
  }
  const filter = (reaction, user) => /[◀▶🔻⏮]/.test(reaction.emoji.name) && user.id === authorID;
  const collector = msg.createReactionCollector(filter, { time: 60 * 1000 });
  collector.client.on('messageReactionRemove', collector.listener);
  collector.on('collect', r => {
    switch(r.emoji.name) {
    case '▶':
      if (pageNum < maxPageNum) {
        pageNum++;
        reload();
      }
      break;
    case '◀':
      if (pageNum > 0) {
        pageNum--;
        reload();
      }
      break;
    case '🔻':
      if (pageNum != authorPage) {
        pageNum = authorPage;
        reload();
      }
      break;
    case '⏮':
      if (pageNum != 0) {
        pageNum = 0;
        reload();
      }
      break;
    }
  });
  collector.on('end', () => {
    msg.clearReactions();
    collector.client.removeListener('messageReactionRemove', collector.listener);
  });
};<|endoftext|>


<commit_before>/* global XMLHttpRequest, expect */

function loadBinaryResource (url) {
  const req = new XMLHttpRequest()
  req.open('GET', url, false)
   // XHR binary charset opt by Marcus Granado 2006 [http://mgran.blogspot.com]
  req.overrideMimeType('text\/plain; charset=x-user-defined');
  req.send(null)
  if (req.status !== 200) {
    throw new Error('Unable to load file')
  }
  return unescape(encodeURIComponent(req.response));
}

function sendReference (filename, data) {
  const req = new XMLHttpRequest()
  req.open('POST', `http://localhost:9090/${filename}`, true)
  req.onload = e => {
    console.log(e)
  }
  req.send(data)
}

const resetCreationDate = input =>
  input.replace(
    /\/CreationDate \(D:(.*?)\)/,
    '/CreationDate (D:19871210000000+00\'00\'\)'
  )

/**
 * Find a better way to set this
 * @type {Boolean}
 */
window.comparePdf = (actual, expectedFile, suite) => {
  let pdf
  try {
    pdf = loadBinaryResource(`/base/tests/${suite}/reference/${expectedFile}`)
  } catch (error) {
    sendReference(`/tests/${suite}/reference/${expectedFile}`, resetCreationDate(actual))
    pdf = actual
  }
  const expected = resetCreationDate(pdf).trim()
  actual = resetCreationDate(actual.trim())

  expect(actual).toEqual(expected)
}
<commit_msg>Update compare.js<commit_after>/* global XMLHttpRequest, expect */

function loadBinaryResource (url) {
  const req = new XMLHttpRequest()
  req.open('GET', url, false)
   // XHR binary charset opt by Marcus Granado 2006 [http://mgran.blogspot.com]
  req.overrideMimeType('text\/plain; charset=x-user-defined');
  req.send(null)
  if (req.status !== 200) {
    throw new Error('Unable to load file')
  }
      var responseText = req.responseText;
    var responseTextLen = req.length;
    var binary = ''
    for (var j = 0; j < responseTextLen; j+=1) {
        binary += String.fromCharCode(responseText.charCodeAt(j) & 0xff)
    }
    var base64data = window.btoa(binary);
  return window.atob(base64data);
}

function sendReference (filename, data) {
  const req = new XMLHttpRequest()
  req.open('POST', `http://localhost:9090/${filename}`, true)
  req.onload = e => {
    console.log(e)
  }
  req.send(data)
}

const resetCreationDate = input =>
  input.replace(
    /\/CreationDate \(D:(.*?)\)/,
    '/CreationDate (D:19871210000000+00\'00\'\)'
  )

/**
 * Find a better way to set this
 * @type {Boolean}
 */
window.comparePdf = (actual, expectedFile, suite) => {
  let pdf
  try {
    pdf = loadBinaryResource(`/base/tests/${suite}/reference/${expectedFile}`)
  } catch (error) {
    sendReference(`/tests/${suite}/reference/${expectedFile}`, resetCreationDate(actual))
    pdf = actual
  }
  const expected = resetCreationDate(pdf).trim()
  actual = resetCreationDate(actual.trim())

  expect(actual).toEqual(expected)
}
<|endoftext|>


<commit_before>import fs from 'fs'
import path from 'path'

import { run, css, html } from './util/run'

test('variants', () => {
  let config = {
    darkMode: 'class',
    content: [path.resolve(__dirname, './variants.test.html')],
    corePlugins: { preflight: false },
  }

  let input = css`
    @tailwind base;
    @tailwind components;
    @tailwind utilities;
  `

  return run(input, config).then((result) => {
    let expectedPath = path.resolve(__dirname, './variants.test.css')
    let expected = fs.readFileSync(expectedPath, 'utf8')

    expect(result.css).toMatchFormattedCss(expected)
  })
})

test('order matters and produces different behaviour', () => {
  let config = {
    content: [
      {
        raw: html`

        `,
      },
    ],
  }

  return run('@tailwind utilities', config).then((result) => {
    return expect(result.css).toMatchFormattedCss(css`
      .hover\:file\:bg-pink-600::file-selector-button:hover {
        --tw-bg-opacity: 1;
        background-color: rgb(219 39 119 / var(--tw-bg-opacity));
      }

      .file\:hover\:bg-pink-600:hover::file-selector-button {
        --tw-bg-opacity: 1;
        background-color: rgb(219 39 119 / var(--tw-bg-opacity));
      }
    `)
  })
})

describe('custom advanced variants', () => {
  test('prose-headings usage on its own', () => {
    let config = {
      content: [
        {
          raw: html` <div class="prose-headings:text-center"></div> `,
        },
      ],
      plugins: [
        function ({ addVariant }) {
          addVariant('prose-headings', ':where(&) :is(h1, h2, h3, h4)')
        },
      ],
    }

    return run('@tailwind components;@tailwind utilities', config).then((result) => {
      return expect(result.css).toMatchFormattedCss(css`
        :where(.prose-headings\:text-center) :is(h1, h2, h3, h4) {
          text-align: center;
        }
      `)
    })
  })

  test('prose-headings with another "simple" variant', () => {
    let config = {
      content: [
        {
          raw: html`

          `,
        },
      ],
      plugins: [
        function ({ addVariant }) {
          addVariant('prose-headings', ':where(&) :is(h1, h2, h3, h4)')
        },
      ],
    }

    return run('@tailwind components;@tailwind utilities', config).then((result) => {
      return expect(result.css).toMatchFormattedCss(css`
        :where(.hover\:prose-headings\:text-center) :is(h1, h2, h3, h4):hover {
          text-align: center;
        }

        :where(.prose-headings\:hover\:text-center:hover) :is(h1, h2, h3, h4) {
          text-align: center;
        }
      `)
    })
  })

  test('prose-headings with another "complex" variant', () => {
    let config = {
      content: [
        {
          raw: html`

          `,
        },
      ],
      plugins: [
        function ({ addVariant }) {
          addVariant('prose-headings', ':where(&) :is(h1, h2, h3, h4)')
        },
      ],
    }

    return run('@tailwind utilities', config).then((result) => {
      return expect(result.css).toMatchFormattedCss(css`
        .group:hover :where(.group-hover\:prose-headings\:text-center) :is(h1, h2, h3, h4) {
          text-align: center;
        }

        :where(.group:hover .prose-headings\:group-hover\:text-center) :is(h1, h2, h3, h4) {
          text-align: center;
        }
      `)
    })
  })
})

test('stacked peer variants', async () => {
  let config = {
    content: [{ raw: 'peer-disabled:peer-focus:peer-hover:border-blue-500' }],
    corePlugins: { preflight: false },
  }

  let input = css`
    @tailwind base;
    @tailwind components;
    @tailwind utilities;
  `

  let expected = css`
    .peer:disabled:focus:hover ~ .peer-disabled\:peer-focus\:peer-hover\:border-blue-500 {
      --tw-border-opacity: 1;
      border-color: rgb(59 130 246 / var(--tw-border-opacity));
    }
  `

  let result = await run(input, config)
  expect(result.css).toIncludeCss(expected)
})

it('should properly handle keyframes with multiple variants', async () => {
  let config = {
    content: [
      {
        raw: 'animate-spin hover:animate-spin focus:animate-spin hover:animate-bounce focus:animate-bounce',
      },
    ],
  }

  let input = css`
    @tailwind components;
    @tailwind utilities;
  `

  let result = await run(input, config)
  expect(result.css).toMatchFormattedCss(css`
    @keyframes spin {
      to {
        transform: rotate(360deg);
      }
    }

    .animate-spin {
      animation: spin 1s linear infinite;
    }

    @keyframes spin {
      to {
        transform: rotate(360deg);
      }
    }

    .hover\:animate-spin:hover {
      animation: spin 1s linear infinite;
    }

    @keyframes bounce {
      0%,
      100% {
        transform: translateY(-25%);
        animation-timing-function: cubic-bezier(0.8, 0, 1, 1);
      }
      50% {
        transform: none;
        animation-timing-function: cubic-bezier(0, 0, 0.2, 1);
      }
    }

    .hover\:animate-bounce:hover {
      animation: bounce 1s infinite;
    }

    @keyframes spin {
      to {
        transform: rotate(360deg);
      }
    }

    .focus\:animate-spin:focus {
      animation: spin 1s linear infinite;
    }

    @keyframes bounce {
      0%,
      100% {
        transform: translateY(-25%);
        animation-timing-function: cubic-bezier(0.8, 0, 1, 1);
      }
      50% {
        transform: none;
        animation-timing-function: cubic-bezier(0, 0, 0.2, 1);
      }
    }

    .focus\:animate-bounce:focus {
      animation: bounce 1s infinite;
    }
  `)
})

test('custom addVariant with more complex media query params', () => {
  let config = {
    content: [
      {
        raw: html` <div class="magic:text-center"></div> `,
      },
    ],
    plugins: [
      function ({ addVariant }) {
        addVariant('magic', '@media screen and (max-wdith: 600px)')
      },
    ],
  }

  return run('@tailwind components;@tailwind utilities', config).then((result) => {
    return expect(result.css).toMatchFormattedCss(css`
      @media screen and (max-wdith: 600px) {
        .magic\:text-center {
          text-align: center;
        }
      }
    `)
  })
})

test('custom addVariant with nested media & format shorthand', () => {
  let config = {
    content: [
      {
        raw: html` <div class="magic:text-center"></div> `,
      },
    ],
    plugins: [
      function ({ addVariant }) {
        addVariant('magic', '@supports (hover: hover) { @media print { &:disabled } }')
      },
    ],
  }

  return run('@tailwind components;@tailwind utilities', config).then((result) => {
    return expect(result.css).toMatchFormattedCss(css`
      @supports (hover: hover) {
        @media print {
          .magic\:text-center:disabled {
            text-align: center;
          }
        }
      }
    `)
  })
})
<commit_msg>Add test for using variants with multi-class selectors<commit_after>import fs from 'fs'
import path from 'path'

import { run, css, html } from './util/run'

test('variants', () => {
  let config = {
    darkMode: 'class',
    content: [path.resolve(__dirname, './variants.test.html')],
    corePlugins: { preflight: false },
  }

  let input = css`
    @tailwind base;
    @tailwind components;
    @tailwind utilities;
  `

  return run(input, config).then((result) => {
    let expectedPath = path.resolve(__dirname, './variants.test.css')
    let expected = fs.readFileSync(expectedPath, 'utf8')

    expect(result.css).toMatchFormattedCss(expected)
  })
})

test('order matters and produces different behaviour', () => {
  let config = {
    content: [
      {
        raw: html`

        `,
      },
    ],
  }

  return run('@tailwind utilities', config).then((result) => {
    return expect(result.css).toMatchFormattedCss(css`
      .hover\:file\:bg-pink-600::file-selector-button:hover {
        --tw-bg-opacity: 1;
        background-color: rgb(219 39 119 / var(--tw-bg-opacity));
      }

      .file\:hover\:bg-pink-600:hover::file-selector-button {
        --tw-bg-opacity: 1;
        background-color: rgb(219 39 119 / var(--tw-bg-opacity));
      }
    `)
  })
})

describe('custom advanced variants', () => {
  test('prose-headings usage on its own', () => {
    let config = {
      content: [
        {
          raw: html` <div class="prose-headings:text-center"></div> `,
        },
      ],
      plugins: [
        function ({ addVariant }) {
          addVariant('prose-headings', ':where(&) :is(h1, h2, h3, h4)')
        },
      ],
    }

    return run('@tailwind components;@tailwind utilities', config).then((result) => {
      return expect(result.css).toMatchFormattedCss(css`
        :where(.prose-headings\:text-center) :is(h1, h2, h3, h4) {
          text-align: center;
        }
      `)
    })
  })

  test('prose-headings with another "simple" variant', () => {
    let config = {
      content: [
        {
          raw: html`

          `,
        },
      ],
      plugins: [
        function ({ addVariant }) {
          addVariant('prose-headings', ':where(&) :is(h1, h2, h3, h4)')
        },
      ],
    }

    return run('@tailwind components;@tailwind utilities', config).then((result) => {
      return expect(result.css).toMatchFormattedCss(css`
        :where(.hover\:prose-headings\:text-center) :is(h1, h2, h3, h4):hover {
          text-align: center;
        }

        :where(.prose-headings\:hover\:text-center:hover) :is(h1, h2, h3, h4) {
          text-align: center;
        }
      `)
    })
  })

  test('prose-headings with another "complex" variant', () => {
    let config = {
      content: [
        {
          raw: html`

          `,
        },
      ],
      plugins: [
        function ({ addVariant }) {
          addVariant('prose-headings', ':where(&) :is(h1, h2, h3, h4)')
        },
      ],
    }

    return run('@tailwind utilities', config).then((result) => {
      return expect(result.css).toMatchFormattedCss(css`
        .group:hover :where(.group-hover\:prose-headings\:text-center) :is(h1, h2, h3, h4) {
          text-align: center;
        }

        :where(.group:hover .prose-headings\:group-hover\:text-center) :is(h1, h2, h3, h4) {
          text-align: center;
        }
      `)
    })
  })

  test('using variants with multi-class selectors', () => {
    let config = {
      content: [
        {
          raw: html` <div class="screen:parent screen:child"></div> `,
        },
      ],
      plugins: [
        function ({ addVariant, addComponents }) {
          addComponents({
            '.parent .child': {
              foo: 'bar',
            },
          })
          addVariant('screen', '@media screen')
        },
      ],
    }

    return run('@tailwind components;@tailwind utilities', config).then((result) => {
      return expect(result.css).toMatchFormattedCss(css`
        @media screen {
          .screen\:parent .child {
            foo: bar;
          }
          .parent .screen\:child {
            foo: bar;
          }
        }
      `)
    })
  })
})

test('stacked peer variants', async () => {
  let config = {
    content: [{ raw: 'peer-disabled:peer-focus:peer-hover:border-blue-500' }],
    corePlugins: { preflight: false },
  }

  let input = css`
    @tailwind base;
    @tailwind components;
    @tailwind utilities;
  `

  let expected = css`
    .peer:disabled:focus:hover ~ .peer-disabled\:peer-focus\:peer-hover\:border-blue-500 {
      --tw-border-opacity: 1;
      border-color: rgb(59 130 246 / var(--tw-border-opacity));
    }
  `

  let result = await run(input, config)
  expect(result.css).toIncludeCss(expected)
})

it('should properly handle keyframes with multiple variants', async () => {
  let config = {
    content: [
      {
        raw: 'animate-spin hover:animate-spin focus:animate-spin hover:animate-bounce focus:animate-bounce',
      },
    ],
  }

  let input = css`
    @tailwind components;
    @tailwind utilities;
  `

  let result = await run(input, config)
  expect(result.css).toMatchFormattedCss(css`
    @keyframes spin {
      to {
        transform: rotate(360deg);
      }
    }

    .animate-spin {
      animation: spin 1s linear infinite;
    }

    @keyframes spin {
      to {
        transform: rotate(360deg);
      }
    }

    .hover\:animate-spin:hover {
      animation: spin 1s linear infinite;
    }

    @keyframes bounce {
      0%,
      100% {
        transform: translateY(-25%);
        animation-timing-function: cubic-bezier(0.8, 0, 1, 1);
      }
      50% {
        transform: none;
        animation-timing-function: cubic-bezier(0, 0, 0.2, 1);
      }
    }

    .hover\:animate-bounce:hover {
      animation: bounce 1s infinite;
    }

    @keyframes spin {
      to {
        transform: rotate(360deg);
      }
    }

    .focus\:animate-spin:focus {
      animation: spin 1s linear infinite;
    }

    @keyframes bounce {
      0%,
      100% {
        transform: translateY(-25%);
        animation-timing-function: cubic-bezier(0.8, 0, 1, 1);
      }
      50% {
        transform: none;
        animation-timing-function: cubic-bezier(0, 0, 0.2, 1);
      }
    }

    .focus\:animate-bounce:focus {
      animation: bounce 1s infinite;
    }
  `)
})

test('custom addVariant with more complex media query params', () => {
  let config = {
    content: [
      {
        raw: html` <div class="magic:text-center"></div> `,
      },
    ],
    plugins: [
      function ({ addVariant }) {
        addVariant('magic', '@media screen and (max-wdith: 600px)')
      },
    ],
  }

  return run('@tailwind components;@tailwind utilities', config).then((result) => {
    return expect(result.css).toMatchFormattedCss(css`
      @media screen and (max-wdith: 600px) {
        .magic\:text-center {
          text-align: center;
        }
      }
    `)
  })
})

test('custom addVariant with nested media & format shorthand', () => {
  let config = {
    content: [
      {
        raw: html` <div class="magic:text-center"></div> `,
      },
    ],
    plugins: [
      function ({ addVariant }) {
        addVariant('magic', '@supports (hover: hover) { @media print { &:disabled } }')
      },
    ],
  }

  return run('@tailwind components;@tailwind utilities', config).then((result) => {
    return expect(result.css).toMatchFormattedCss(css`
      @supports (hover: hover) {
        @media print {
          .magic\:text-center:disabled {
            text-align: center;
          }
        }
      }
    `)
  })
})
<|endoftext|>