<commit_before>const moment               = require('moment');
const requireHighstock     = require('./common').requireHighstock;
const Tick                 = require('./tick');
const ViewPopupUI          = require('../user/view_popup/view_popup.ui');
const BinarySocket         = require('../../base/socket');
const CommonFunctions      = require('../../../_common/common_functions');
const localize             = require('../../../_common/localize').localize;

const TickDisplay = (() => {
    let number_of_ticks,
        display_symbol,
        contract_start_ms,
        contract_category,
        set_barrier,
        barrier,
        abs_barrier,
        display_decimals,
        show_contract_result,
        contract_sentiment,
        ticks_needed,
        x_indicators,
        chart,
        Highcharts,
        applicable_ticks,
        contract_barrier,
        contract_start_moment,
        counter,
        spots_list,
        tick_underlying,
        tick_count,
        tick_longcode,
        tick_display_name,
        tick_date_start,
        absolute_barrier,
        tick_shortcode,
        tick_init,
        subscribe,
        responseID,
        entry_spot,
        reset_spot_plotted,
        sell_spot_time,
        exit_tick_time;

    let id_render = 'tick_chart';

    const initialize = (data, options) => {
        // setting up globals
        number_of_ticks      = parseInt(data.number_of_ticks);
        display_symbol       = data.display_symbol;
        contract_start_ms    = parseInt(data.contract_start) * 1000;
        contract_category    = data.contract_category;
        set_barrier          = !contract_category.match('digits');
        barrier              = data.barrier;
        abs_barrier          = data.abs_barrier;
        display_decimals     = data.display_decimals || 2;
        show_contract_result = data.show_contract_result;
        reset_spot_plotted   = false;

        if (options.id_render) {
            id_render = options.id_render;
        }

        if (data.show_contract_result) {
            contract_sentiment = data.contract_sentiment;
        }

        const minimize = data.show_contract_result;
        const end_time = parseInt(data.contract_start) + parseInt((number_of_ticks + 2) * 5);

        setXIndicators();
        requireHighstock((Highstock) => {
            Highcharts = Highstock;
            initializeChart({
                minimize,
                plot_from: data.previous_tick_epoch * 1000,
                plot_to  : new Date(end_time * 1000).getTime(),
                width    : data.width ? data.width : undefined,
            }, options);
        });
    };

    const setXIndicators = () => {
        const exit_tick_index = number_of_ticks - 1;
        if (contract_category.match('asian')) {
            ticks_needed = number_of_ticks;
            x_indicators = {
                _0: { label: 'Entry Spot', id: 'start_tick' },
            };
            x_indicators[`_${exit_tick_index}`] = {
                label: 'Exit Spot',
                id   : 'exit_tick',
            };
        } else if (contract_category.match('callput')) {
            ticks_needed = number_of_ticks + 1;
            x_indicators = {
                _0: { label: 'Entry Spot', id: 'entry_tick' },
            };
            x_indicators[`_${number_of_ticks}`] = {
                label: 'Exit Spot',
                id   : 'exit_tick',
            };
        } else if (contract_category.match('touchnotouch')) {
            ticks_needed = number_of_ticks + 1;
            x_indicators = {
                _0: { label: 'Entry Spot', id: 'entry_tick' },
            };
        } else if (contract_category.match('digits')) {
            ticks_needed = number_of_ticks;
            x_indicators = {
                _0: { label: 'Tick 1', id: 'start_tick' },
            };
            x_indicators[`_${exit_tick_index}`] = {
                label: `Tick ${number_of_ticks}`,
                id   : 'last_tick',
            };
        } else {
            x_indicators = {};
        }
    };

    const initializeChart = (config, data) => {
        chart = new Highcharts.Chart({
            chart: {
                type           : 'line',
                renderTo       : id_render,
                width          : config.width || (config.minimize ? 394 : null),
                height         : config.minimize ? 143 : null,
                backgroundColor: null,
                events         : { load: plot(config.plot_from, config.plot_to) },
                marginLeft     : 50,
            },
            credits: { enabled: false },
            tooltip: {
                formatter() {
                    const new_y = this.y.toFixed(display_decimals);
                    const mom   = moment.utc(applicable_ticks[this.x].epoch * 1000).format('dddd, MMM D, HH:mm:ss');
                    return `${mom}<br/>${display_symbol} ${new_y}`;
                },
            },
            xAxis: {
                type  : 'linear',
                min   : 0,
                max   : number_of_ticks + 1,
                labels: { enabled: false },
            },
            yAxis: {
                opposite: false,
                labels  : {
                    align: 'left',
                    x    : 0,
                },
                title: '',
            },
            series: [{
                data: [],
            }],
            title    : '',
            exporting: { enabled: false, enableImages: false },
            legend   : { enabled: false },
        });
        Highcharts.setOptions({
            lang: { thousandsSep: ',' },
        });
        if (data) {
            dispatch(data);
        }
    };

    const applyChartBackgroundColor = (tick) => {
        if (!show_contract_result) return;

        const chart_container = $(`#${id_render}`);

        const winning = contract_sentiment === 'up' && tick.quote > contract_barrier
            || contract_sentiment === 'down' && tick.quote < contract_barrier;

        chart_container.css('background-color', winning
            ? 'rgba(46,136,54,0.198039)'
            : 'rgba(204,0,0,0.098039)');
    };

    const addBarrier = () => {
        if (!set_barrier) {
            return;
        }

        const barrier_type = contract_category.match('asian') ? 'asian' : 'static';

        if (barrier_type === 'static') {
            const first_quote = applicable_ticks[0].quote;
            let barrier_quote = first_quote;

            if (barrier) {
                let final_barrier = barrier_quote + parseFloat(barrier);
                // sometimes due to rounding issues, result is 1.009999 while it should
                // be 1.01
                final_barrier = Number(`${Math.round(`${final_barrier}e${display_decimals}`)}e-${display_decimals}`);
                barrier_quote = final_barrier;
            } else if (abs_barrier) {
                barrier_quote = parseFloat(abs_barrier);
            }

            chart.yAxis[0].addPlotLine({
                id    : 'tick-barrier',
                value : barrier_quote,
                label : { text: `Barrier (${barrier_quote})`, align: 'center' },
                color : 'green',
                width : 2,
                zIndex: 2,
            });
            contract_barrier = barrier_quote;
            set_barrier      = false;
        }

        if (barrier_type === 'asian') {
            let total = 0;
            for (let i = 0; i < applicable_ticks.length; i++) {
                total += parseFloat(applicable_ticks[i].quote);
            }
            // round calculated barrier
            const calc_barrier = (total / applicable_ticks.length).toFixed(parseInt(display_decimals) + 1);

            chart.yAxis[0].removePlotLine('tick-barrier');
            chart.yAxis[0].addPlotLine({
                id   : 'tick-barrier',
                value: calc_barrier,
                color: 'green',
                label: {
                    text : `Average (${calc_barrier})`,
                    align: 'center',
                },
                width : 2,
                zIndex: 2,
            });
            contract_barrier = calc_barrier;
        }
        if (contract_barrier) {
            CommonFunctions.elementInnerHtml(CommonFunctions.getElementById('contract_purchase_barrier'), `${localize('Barrier')}: ${contract_barrier}`);
        }
    };

    const add = (indicator) => {
        chart.xAxis[0].addPlotLine({
            value    : indicator.index,
            id       : indicator.id,
            label    : { text: indicator.label, x: /start_tick|entry_tick/.test(indicator.id) ? -15 : 5 },
            color    : indicator.color || '#e98024',
            width    : 2,
            zIndex   : 2,
            dashStyle: indicator.dashStyle || '',
        });
    };

    const plot = () => {
        contract_start_moment = moment(contract_start_ms).utc();
        counter               = 0;
        applicable_ticks      = [];
    };

    const dispatch = (data) => {
        const tick_chart = CommonFunctions.getElementById(id_render);

        if (!CommonFunctions.isVisible(tick_chart) || !data || (!data.tick && !data.history)) {
            return;
        }

        if (subscribe && data.tick && document.getElementById('sell_content_wrapper')) {
            responseID = data.tick.id;
            ViewPopupUI.storeSubscriptionID(responseID);
        }

        let epoches,
            spots2,
            chart_display_decimals;

        if (document.getElementById('sell_content_wrapper')) {
            if (data.tick) {
                Tick.details(data);
                if (!chart_display_decimals) {
                    chart_display_decimals = data.tick.quote.split('.')[1].length || 2;
                }
            } else if (data.history) {
                if (!chart_display_decimals) {
                    chart_display_decimals = data.history.prices[0].split('.')[1].length || 2;
                }
            }
            if (!tick_init) {
                let category = 'callput';
                if (/asian/i.test(tick_shortcode)) {
                    category = 'asian';
                } else if (/digit/i.test(tick_shortcode)) {
                    category = 'digits';
                } else if (/touch/i.test(tick_shortcode)) {
                    category = 'touchnotouch';
                } else if (/reset/i.test(tick_shortcode)) {
                    category = 'reset';
                }
                initialize({
                    symbol              : tick_underlying,
                    number_of_ticks     : tick_count,
                    contract_category   : category,
                    longcode            : tick_longcode,
                    display_symbol      : tick_display_name,
                    contract_start      : tick_date_start,
                    abs_barrier         : absolute_barrier,
                    display_decimals    : chart_display_decimals,
                    show_contract_result: 0,
                }, data);
                spots_list = {};
                tick_init  = 'initialized';
                return;
            }
        }
        if (data.tick) {
            spots2  = Tick.spots();
            epoches = Object.keys(spots2).sort((a, b) => a - b);
        } else if (data.history) {
            epoches = data.history.times;
        }

        const has_finished = applicable_ticks && ticks_needed && applicable_ticks.length >= ticks_needed;
        const has_sold = sell_spot_time
            && applicable_ticks
            && applicable_ticks.find(({ epoch }) => epoch === sell_spot_time) !== undefined;

        if (!has_finished && !has_sold) {
            for (let d = 0; d < epoches.length; d++) {
                let tick;
                if (data.tick) {
                    tick = {
                        epoch: parseInt(epoches[d]),
                        quote: parseFloat(spots2[epoches[d]]),
                    };
                } else if (data.history) {
                    tick = {
                        epoch: parseInt(data.history.times[d]),
                        quote: parseFloat(data.history.prices[d]),
                    };
                }

                if (contract_start_moment && tick.epoch > contract_start_moment.unix() && !spots_list[tick.epoch]) {
                    if (!chart || !chart.series) return;
                    chart.series[0].addPoint([counter, tick.quote], true, false);
                    applicable_ticks.push(tick);
                    spots_list[tick.epoch] = tick.quote;
                    const indicator_key    = `_${counter}`;

                    if (contract_category === 'touchnotouch' && tick.epoch === sell_spot_time) {
                        x_indicators[indicator_key] = {
                            index: counter,
                            label: sell_spot_time === exit_tick_time
                                ? 'Exit Spot'
                                : 'Sell Spot',
                            dashStyle: 'Dash',
                        };
                    }

                    if (typeof x_indicators[indicator_key] !== 'undefined') {
                        x_indicators[indicator_key].index = counter;

                        add(x_indicators[indicator_key]);
                    }

                    addBarrier();
                    applyChartBackgroundColor(tick);
                    counter++;
                }
            }
            if (contract_category === 'reset' && data.history) {
                plotResetSpot();
            }
        }
    };

    const removePlotLine = (id, type = 'y') => {
        if (!chart) return;
        chart[(`${type}Axis`)][0].removePlotLine(id);
    };

    const plotResetSpot = (r_barrier) => {
        if (reset_spot_plotted || !chart) return;

        const reset_time_index = Math.floor(number_of_ticks / 2);
        const entry_barrier    = entry_spot * 1;
        const reset_barrier    = r_barrier || absolute_barrier * 1;

        if (entry_barrier !== reset_barrier) {
            removePlotLine('tick-barrier', 'y');

            chart.yAxis[0].addPlotLine({
                id    : 'tick-reset-barrier',
                value : reset_barrier,
                label : { text: `Reset Barrier (${reset_barrier})`, align: 'center' },
                color : 'green',
                width : 2,
                zIndex: 2,
            });
            chart.yAxis[0].addPlotLine({
                id       : 'tick-barrier',
                value    : entry_barrier,
                label    : { text: `Barrier (${entry_barrier})`, align: 'center' },
                color    : 'green',
                width    : 2,
                zIndex   : 2,
                dashStyle: 'dot',
            });

            x_indicators[`_${reset_time_index}`] = {
                index: reset_time_index,
                label: 'Reset Time',
                id   : 'reset_tick',
                color: '#000',
            };
            x_indicators[`_${reset_time_index}`].index = reset_time_index;
            add(x_indicators[`_${reset_time_index}`]);

            reset_spot_plotted = true;
        }
    };

    const updateChart = (data, contract) => {
        subscribe = 'false';
        if (contract_category === 'touchnotouch' && data.is_sold && applicable_ticks) {
            sell_spot_time = +contract.sell_spot_time;
            exit_tick_time = +contract.exit_tick_time;

            const index = applicable_ticks.findIndex(({ epoch }) => epoch === sell_spot_time);

            if (index >= 0) {
                const indicator_key = `_${index}`;

                x_indicators[indicator_key] = {
                    index,
                    label: sell_spot_time === exit_tick_time
                        ? 'Exit Spot'
                        : 'Sell Spot',
                    dashStyle: 'Dash',
                };
                add(x_indicators[indicator_key]);
            }
        } else if (contract) {
            tick_underlying   = contract.underlying;
            tick_count        = contract.tick_count;
            tick_longcode     = contract.longcode;
            tick_display_name = contract.display_name;
            tick_date_start   = contract.date_start;
            absolute_barrier  = contract.barrier;
            tick_shortcode    = contract.shortcode;
            tick_init         = '';
            entry_spot        = contract.entry_spot;
            sell_spot_time    = +contract.sell_spot_time;
            exit_tick_time    = +contract.exit_tick_time;

            const request     = {
                ticks_history: contract.underlying,
                start        : contract.date_start,
                end          : 'latest',
            };
            if (contract.current_spot_time < contract.date_expiry) {
                request.subscribe = 1;
                subscribe         = 'true';
            } else if (+contract.sell_spot_time < contract.date_expiry) {
                request.end = contract.sell_spot_time;
            } else {
                request.end = contract.date_expiry;
            }

            BinarySocket.send(request, { callback: dispatch });
        } else {
            dispatch(data);
        }
    };

    return {
        plotResetSpot,
        updateChart,
        init      : initialize,
        resetSpots: () => { spots_list = {}; },
    };
})();

module.exports = TickDisplay;
<commit_msg>add comma to barrier value label, display entry & exit spot line<commit_after>const moment               = require('moment');
const requireHighstock     = require('./common').requireHighstock;
const Tick                 = require('./tick');
const ViewPopupUI          = require('../user/view_popup/view_popup.ui');
const BinarySocket         = require('../../base/socket');
const addComma             = require('../../../_common/base/currency_base').addComma;
const CommonFunctions      = require('../../../_common/common_functions');
const localize             = require('../../../_common/localize').localize;

const TickDisplay = (() => {
    let number_of_ticks,
        display_symbol,
        contract_start_ms,
        contract_category,
        set_barrier,
        barrier,
        abs_barrier,
        display_decimals,
        show_contract_result,
        contract_sentiment,
        ticks_needed,
        x_indicators,
        chart,
        Highcharts,
        applicable_ticks,
        contract_barrier,
        contract_start_moment,
        counter,
        spots_list,
        tick_underlying,
        tick_count,
        tick_longcode,
        tick_display_name,
        tick_date_start,
        absolute_barrier,
        tick_shortcode,
        tick_init,
        subscribe,
        responseID,
        entry_spot,
        reset_spot_plotted,
        sell_spot_time,
        exit_tick_time;

    let id_render = 'tick_chart';

    const initialize = (data, options) => {
        // setting up globals
        number_of_ticks      = parseInt(data.number_of_ticks);
        display_symbol       = data.display_symbol;
        contract_start_ms    = parseInt(data.contract_start) * 1000;
        contract_category    = data.contract_category;
        set_barrier          = !contract_category.match('digits');
        barrier              = data.barrier;
        abs_barrier          = data.abs_barrier;
        display_decimals     = data.display_decimals || 2;
        show_contract_result = data.show_contract_result;
        reset_spot_plotted   = false;

        if (options.id_render) {
            id_render = options.id_render;
        }

        if (data.show_contract_result) {
            contract_sentiment = data.contract_sentiment;
        }

        const minimize = data.show_contract_result;
        const end_time = parseInt(data.contract_start) + parseInt((number_of_ticks + 2) * 5);

        setXIndicators();
        requireHighstock((Highstock) => {
            Highcharts = Highstock;
            initializeChart({
                minimize,
                plot_from: data.previous_tick_epoch * 1000,
                plot_to  : new Date(end_time * 1000).getTime(),
                width    : data.width ? data.width : undefined,
            }, options);
        });
    };

    const setXIndicators = () => {
        const exit_tick_index = number_of_ticks - 1;
        if (contract_category.match('asian')) {
            ticks_needed = number_of_ticks;
            x_indicators = {
                _0: { label: 'Entry Spot', id: 'start_tick' },
            };
            x_indicators[`_${exit_tick_index}`] = {
                label: 'Exit Spot',
                id   : 'exit_tick',
            };
        } else if (contract_category.match('callput') || contract_category.match('reset')) {
            ticks_needed = number_of_ticks + 1;
            x_indicators = {
                _0: { label: 'Entry Spot', id: 'entry_tick' },
            };
            x_indicators[`_${number_of_ticks}`] = {
                label: 'Exit Spot',
                id   : 'exit_tick',
            };
        } else if (contract_category.match('touchnotouch')) {
            ticks_needed = number_of_ticks + 1;
            x_indicators = {
                _0: { label: 'Entry Spot', id: 'entry_tick' },
            };
        } else if (contract_category.match('digits')) {
            ticks_needed = number_of_ticks;
            x_indicators = {
                _0: { label: 'Tick 1', id: 'start_tick' },
            };
            x_indicators[`_${exit_tick_index}`] = {
                label: `Tick ${number_of_ticks}`,
                id   : 'last_tick',
            };
        } else {
            x_indicators = {};
        }
    };

    const initializeChart = (config, data) => {
        chart = new Highcharts.Chart({
            chart: {
                type           : 'line',
                renderTo       : id_render,
                width          : config.width || (config.minimize ? 394 : null),
                height         : config.minimize ? 143 : null,
                backgroundColor: null,
                events         : { load: plot(config.plot_from, config.plot_to) },
                marginLeft     : 50,
            },
            credits: { enabled: false },
            tooltip: {
                formatter() {
                    const new_y = this.y.toFixed(display_decimals);
                    const mom   = moment.utc(applicable_ticks[this.x].epoch * 1000).format('dddd, MMM D, HH:mm:ss');
                    return `${mom}<br/>${display_symbol} ${new_y}`;
                },
            },
            xAxis: {
                type  : 'linear',
                min   : 0,
                max   : number_of_ticks + 1,
                labels: { enabled: false },
            },
            yAxis: {
                opposite: false,
                labels  : {
                    align: 'left',
                    x    : 0,
                },
                title: '',
            },
            series: [{
                data: [],
            }],
            title    : '',
            exporting: { enabled: false, enableImages: false },
            legend   : { enabled: false },
        });
        Highcharts.setOptions({
            lang: { thousandsSep: ',' },
        });
        if (data) {
            dispatch(data);
        }
    };

    const applyChartBackgroundColor = (tick) => {
        if (!show_contract_result) return;

        const chart_container = $(`#${id_render}`);

        const winning = contract_sentiment === 'up' && tick.quote > contract_barrier
            || contract_sentiment === 'down' && tick.quote < contract_barrier;

        chart_container.css('background-color', winning
            ? 'rgba(46,136,54,0.198039)'
            : 'rgba(204,0,0,0.098039)');
    };

    const addBarrier = () => {
        if (!set_barrier) {
            return;
        }

        const barrier_type = contract_category.match('asian') ? 'asian' : 'static';

        if (barrier_type === 'static') {
            const first_quote = applicable_ticks[0].quote;
            let barrier_quote = first_quote;

            if (barrier) {
                let final_barrier = barrier_quote + parseFloat(barrier);
                // sometimes due to rounding issues, result is 1.009999 while it should
                // be 1.01
                final_barrier = Number(`${Math.round(`${final_barrier}e${display_decimals}`)}e-${display_decimals}`);
                barrier_quote = final_barrier;
            } else if (abs_barrier) {
                barrier_quote = parseFloat(abs_barrier);
            }

            chart.yAxis[0].addPlotLine({
                id    : 'tick-barrier',
                value : barrier_quote,
                label : { text: `Barrier (${addComma(barrier_quote)})`, align: 'center' },
                color : 'green',
                width : 2,
                zIndex: 2,
            });
            contract_barrier = barrier_quote;
            set_barrier      = false;
        }

        if (barrier_type === 'asian') {
            let total = 0;
            for (let i = 0; i < applicable_ticks.length; i++) {
                total += parseFloat(applicable_ticks[i].quote);
            }
            // round calculated barrier
            const calc_barrier = (total / applicable_ticks.length).toFixed(parseInt(display_decimals) + 1);

            chart.yAxis[0].removePlotLine('tick-barrier');
            chart.yAxis[0].addPlotLine({
                id   : 'tick-barrier',
                value: calc_barrier,
                color: 'green',
                label: {
                    text : `Average (${calc_barrier})`,
                    align: 'center',
                },
                width : 2,
                zIndex: 2,
            });
            contract_barrier = calc_barrier;
        }
        if (contract_barrier) {
            CommonFunctions.elementInnerHtml(CommonFunctions.getElementById('contract_purchase_barrier'), `${localize('Barrier')}: ${contract_barrier}`);
        }
    };

    const add = (indicator) => {
        chart.xAxis[0].addPlotLine({
            value    : indicator.index,
            id       : indicator.id,
            label    : { text: indicator.label, x: /start_tick|entry_tick/.test(indicator.id) ? -15 : 5 },
            color    : indicator.color || '#e98024',
            width    : 2,
            zIndex   : 2,
            dashStyle: indicator.dashStyle || '',
        });
    };

    const plot = () => {
        contract_start_moment = moment(contract_start_ms).utc();
        counter               = 0;
        applicable_ticks      = [];
    };

    const dispatch = (data) => {
        const tick_chart = CommonFunctions.getElementById(id_render);

        if (!CommonFunctions.isVisible(tick_chart) || !data || (!data.tick && !data.history)) {
            return;
        }

        if (subscribe && data.tick && document.getElementById('sell_content_wrapper')) {
            responseID = data.tick.id;
            ViewPopupUI.storeSubscriptionID(responseID);
        }

        let epoches,
            spots2,
            chart_display_decimals;

        if (document.getElementById('sell_content_wrapper')) {
            if (data.tick) {
                Tick.details(data);
                if (!chart_display_decimals) {
                    chart_display_decimals = data.tick.quote.split('.')[1].length || 2;
                }
            } else if (data.history) {
                if (!chart_display_decimals) {
                    chart_display_decimals = data.history.prices[0].split('.')[1].length || 2;
                }
            }
            if (!tick_init) {
                let category = 'callput';
                if (/asian/i.test(tick_shortcode)) {
                    category = 'asian';
                } else if (/digit/i.test(tick_shortcode)) {
                    category = 'digits';
                } else if (/touch/i.test(tick_shortcode)) {
                    category = 'touchnotouch';
                } else if (/reset/i.test(tick_shortcode)) {
                    category = 'reset';
                }
                initialize({
                    symbol              : tick_underlying,
                    number_of_ticks     : tick_count,
                    contract_category   : category,
                    longcode            : tick_longcode,
                    display_symbol      : tick_display_name,
                    contract_start      : tick_date_start,
                    abs_barrier         : absolute_barrier,
                    display_decimals    : chart_display_decimals,
                    show_contract_result: 0,
                }, data);
                spots_list = {};
                tick_init  = 'initialized';
                return;
            }
        }
        if (data.tick) {
            spots2  = Tick.spots();
            epoches = Object.keys(spots2).sort((a, b) => a - b);
        } else if (data.history) {
            epoches = data.history.times;
        }

        const has_finished = applicable_ticks && ticks_needed && applicable_ticks.length >= ticks_needed;
        const has_sold = sell_spot_time
            && applicable_ticks
            && applicable_ticks.find(({ epoch }) => epoch === sell_spot_time) !== undefined;

        if (!has_finished && !has_sold) {
            for (let d = 0; d < epoches.length; d++) {
                let tick;
                if (data.tick) {
                    tick = {
                        epoch: parseInt(epoches[d]),
                        quote: parseFloat(spots2[epoches[d]]),
                    };
                } else if (data.history) {
                    tick = {
                        epoch: parseInt(data.history.times[d]),
                        quote: parseFloat(data.history.prices[d]),
                    };
                }

                if (contract_start_moment && tick.epoch > contract_start_moment.unix() && !spots_list[tick.epoch]) {
                    if (!chart || !chart.series) return;
                    chart.series[0].addPoint([counter, tick.quote], true, false);
                    applicable_ticks.push(tick);
                    spots_list[tick.epoch] = tick.quote;
                    const indicator_key    = `_${counter}`;

                    if (contract_category === 'touchnotouch' && tick.epoch === sell_spot_time) {
                        x_indicators[indicator_key] = {
                            index: counter,
                            label: sell_spot_time === exit_tick_time
                                ? 'Exit Spot'
                                : 'Sell Spot',
                            dashStyle: 'Dash',
                        };
                    }

                    if (typeof x_indicators[indicator_key] !== 'undefined') {
                        x_indicators[indicator_key].index = counter;

                        add(x_indicators[indicator_key]);
                    }

                    addBarrier();
                    applyChartBackgroundColor(tick);
                    counter++;
                }
            }
            if (contract_category === 'reset' && data.history) {
                plotResetSpot();
            }
        }
    };

    const removePlotLine = (id, type = 'y') => {
        if (!chart) return;
        chart[(`${type}Axis`)][0].removePlotLine(id);
    };

    const plotResetSpot = (r_barrier) => {
        if (reset_spot_plotted || !chart) return;

        const reset_time_index = Math.floor(number_of_ticks / 2);
        const entry_barrier    = entry_spot * 1;
        const reset_barrier    = r_barrier || absolute_barrier * 1;

        if (entry_barrier !== reset_barrier) {
            removePlotLine('tick-barrier', 'y');

            chart.yAxis[0].addPlotLine({
                id    : 'tick-reset-barrier',
                value : reset_barrier,
                label : { text: `Reset Barrier (${addComma(reset_barrier)})`, align: 'center' },
                color : 'green',
                width : 2,
                zIndex: 2,
            });
            chart.yAxis[0].addPlotLine({
                id       : 'tick-barrier',
                value    : entry_barrier,
                label    : { text: `Barrier (${addComma(entry_barrier)})`, align: 'center' },
                color    : 'green',
                width    : 2,
                zIndex   : 2,
                dashStyle: 'dot',
            });

            x_indicators[`_${reset_time_index}`] = {
                index: reset_time_index,
                label: 'Reset Time',
                id   : 'reset_tick',
                color: '#000',
            };
            x_indicators[`_${reset_time_index}`].index = reset_time_index;
            add(x_indicators[`_${reset_time_index}`]);

            reset_spot_plotted = true;
        }
    };

    const updateChart = (data, contract) => {
        subscribe = 'false';
        if (contract_category === 'touchnotouch' && data.is_sold && applicable_ticks) {
            sell_spot_time = +contract.sell_spot_time;
            exit_tick_time = +contract.exit_tick_time;

            const index = applicable_ticks.findIndex(({ epoch }) => epoch === sell_spot_time);

            if (index >= 0) {
                const indicator_key = `_${index}`;

                x_indicators[indicator_key] = {
                    index,
                    label: sell_spot_time === exit_tick_time
                        ? 'Exit Spot'
                        : 'Sell Spot',
                    dashStyle: 'Dash',
                };
                add(x_indicators[indicator_key]);
            }
        } else if (contract) {
            tick_underlying   = contract.underlying;
            tick_count        = contract.tick_count;
            tick_longcode     = contract.longcode;
            tick_display_name = contract.display_name;
            tick_date_start   = contract.date_start;
            absolute_barrier  = contract.barrier;
            tick_shortcode    = contract.shortcode;
            tick_init         = '';
            entry_spot        = contract.entry_spot;
            sell_spot_time    = +contract.sell_spot_time;
            exit_tick_time    = +contract.exit_tick_time;

            const request     = {
                ticks_history: contract.underlying,
                start        : contract.date_start,
                end          : 'latest',
            };
            if (contract.current_spot_time < contract.date_expiry) {
                request.subscribe = 1;
                subscribe         = 'true';
            } else if (+contract.sell_spot_time < contract.date_expiry) {
                request.end = contract.sell_spot_time;
            } else {
                request.end = contract.date_expiry;
            }

            BinarySocket.send(request, { callback: dispatch });
        } else {
            dispatch(data);
        }
    };

    return {
        plotResetSpot,
        updateChart,
        init      : initialize,
        resetSpots: () => { spots_list = {}; },
    };
})();

module.exports = TickDisplay;
<|endoftext|>


<commit_before>// Check view width, add navbar height as offset if on desktop
function checkWidth() {
    var mq = window.matchMedia("(max-width: 1199px)");
    return (mq.matches ? 50 : document.getElementById('navigation').scrollHeight);
}

function toggleMobileMenu() {
    const toggleButton = document.getElementById('toggle-menu');
    const navbar       = document.getElementById('navigation');
    const navbar_item  = document.getElementsByClassName('navbar-collapse')[0];
    toggleButton.addEventListener('click', function (e) {
        e.stopPropagation();
        navbar.classList[navbar.classList.contains('expand') ? 'remove' : 'add']('expand');
        navbar_item.classList[navbar_item.classList.contains('expand') ? 'remove' : 'add']('expand');
    });
}

function checkBrowser() {
    const isFirefox = typeof InstallTrigger !== 'undefined';   // Firefox 1.0+
    const isIE = /*@cc_on!@*/false || !!document.documentMode; // Internet Explorer 6-11
    return (isFirefox || isIE);
}

// scrollTo function with animation
// - Gist reference: https://gist.github.com/andjosh/6764939
function scrollTo(to, duration) {
    if (!duration) duration = 1000;
    var start = window.pageYOffset,
        change = to - start,
        currentTime = 0,
        increment = 20;

    const animateScroll = function(){
        currentTime += increment;
        var val = Math.easeInOutQuad(currentTime, start, change, duration);
        document.body.scrollTop = val;
        document.documentElement.scrollTop = val;
        if(currentTime < duration) {
            setTimeout(animateScroll, increment);
        }
    };
    animateScroll();
}

Math.easeInOutQuad = function (current_time, start_value, change_in_value, duration) {
    current_time /= duration / 2;
    if (current_time < 1) return change_in_value / 2 * current_time * current_time + start_value;
    current_time--;
    return -change_in_value / 2 * (current_time * (current_time - 2) - 1) + start_value;
};

function getParamValue(url, key) {
    var regex   = new RegExp('[?&]' + key + '(=([^&#]*)|&|#|$)');
    var results = regex.exec(url);
    if (!results || !results[2]) return '';
    return decodeURIComponent(results[2].replace(/\+/g, ' '));
}

function getLanguage() {
    var all_languages = [ 'en', 'de', 'es', 'fr', 'id', 'it', 'ja', 'pl', 'pt', 'ru', 'th', 'vi', 'zh_cn', 'zh_tw' ];
    var language = window.location.href.toLowerCase().split('/').slice(3).find(function(l) { return all_languages.indexOf(l) >= 0; });
    return language || 'en';
}

function wsConnect() {
    return new WebSocket('wss://blue.binaryws.com/websockets/v3?app_id=1&l=' + getLanguage());
}

function wsSend(ws, request) {
    if (ws && request && typeof request === 'object') {
        ws.send(JSON.stringify(request));
    }
}

function setSession(key, value) {
    if (typeof sessionStorage !== 'undefined') {
        try {
            sessionStorage.setItem(key, value);
            return true;
        } catch (e) {
            return false;
        }
    }
}

// NodeList foreach polyfill
if (window.NodeList && !NodeList.prototype.forEach) {
    NodeList.prototype.forEach = function (callback, thisArg) {
        thisArg = thisArg || window;
        for (var i = 0; i < this.length; i++) {
            callback.call(thisArg, this[i], i, this);
        }
    };
}
<commit_msg>Revert common.js changes<commit_after>// Check view width, add navbar height as offset if on desktop
function checkWidth() {
    let mq = window.matchMedia("(max-width: 1199px)");
    return (mq.matches ? 50 : document.getElementById('navigation').scrollHeight);
}

function toggleMobileMenu() {
    const toggleButton = document.getElementById('toggle-menu');
    const navbar       = document.getElementById('navigation');
    const navbar_item  = document.getElementsByClassName('navbar-collapse')[0];
    toggleButton.addEventListener('click', function (e) {
        e.stopPropagation();
        navbar.classList[navbar.classList.contains('expand') ? 'remove' : 'add']('expand');
        navbar_item.classList[navbar_item.classList.contains('expand') ? 'remove' : 'add']('expand');
    });
}

function checkBrowser() {
    const isFirefox = typeof InstallTrigger !== 'undefined';   // Firefox 1.0+
    const isIE = /*@cc_on!@*/false || !!document.documentMode; // Internet Explorer 6-11
    return (isFirefox || isIE);
}

// scrollTo function with animation
// - Gist reference: https://gist.github.com/andjosh/6764939
function scrollTo(to, duration) {
    if (!duration) duration = 1000;
    let start = window.pageYOffset,
        change = to - start,
        currentTime = 0,
        increment = 20;

    const animateScroll = function(){
        currentTime += increment;
        let val = Math.easeInOutQuad(currentTime, start, change, duration);
        document.body.scrollTop = val;
        document.documentElement.scrollTop = val;
        if(currentTime < duration) {
            setTimeout(animateScroll, increment);
        }
    };
    animateScroll();
}

Math.easeInOutQuad = function (current_time, start_value, change_in_value, duration) {
    current_time /= duration / 2;
    if (current_time < 1) return change_in_value / 2 * current_time * current_time + start_value;
    current_time--;
    return -change_in_value / 2 * (current_time * (current_time - 2) - 1) + start_value;
};

function getParamValue(url, key) {
    let regex   = new RegExp('[?&]' + key + '(=([^&#]*)|&|#|$)');
    let results = regex.exec(url);
    if (!results || !results[2]) return '';
    return decodeURIComponent(results[2].replace(/\+/g, ' '));
}

function getLanguage() {
    let all_languages = [ 'en', 'de', 'es', 'fr', 'id', 'it', 'ja', 'pl', 'pt', 'ru', 'th', 'vi', 'zh_cn', 'zh_tw' ];
    let language = window.location.href.toLowerCase().split('/').slice(3).find(function(l) { return all_languages.indexOf(l) >= 0; });
    return language || 'en';
}

function wsConnect() {
    return new WebSocket('wss://blue.binaryws.com/websockets/v3?app_id=1&l=' + getLanguage());
}

function wsSend(ws, request) {
    if (ws && request && typeof request === 'object') {
        ws.send(JSON.stringify(request));
    }
}

function setSession(key, value) {
    if (typeof sessionStorage !== 'undefined') {
        try {
            sessionStorage.setItem(key, value);
            return true;
        } catch (e) {
            return false;
        }
    }
}

// NodeList foreach polyfill
if (window.NodeList && !NodeList.prototype.forEach) {
    NodeList.prototype.forEach = function (callback, thisArg) {
        thisArg = thisArg || window;
        for (let i = 0; i < this.length; i++) {
            callback.call(thisArg, this[i], i, this);
        }
    };
}
<|endoftext|>


<commit_before>
var get, post, put, del, head, options, trace, connect;

/**
 *
 * @param url, data, callback, type
 * data = xml | json | text
 * post(url, data, headers, type, callback)
 */
(function() {
    var formatData = function(xhr, type) {
        var data = xhr.responseText;
        type = type.toLowerCase();
        if (type === "xml") {
			return new XML(data.replace(/<\?xml.*?\?>/, "").replace(/<!--[\s\S]*?-->/g, ""));
		} else if (type === "json") {
			return parse(data);
        } else {
			return data;
		}
    };

    var string = function(o) {
        return typeof o === 'string' || o instanceof String;
    };

    var getError = function(method, type) {
        return "Invalid argument for " + method.toLowerCase() + "() method : " + type;
    };

    var httpCall = function(method, args) {
        var xhr = new XMLHttpRequest();
        var url = args[0];
        var data = null;
        var callback = null;
        var dataType = "text";
        var that = this;
        var type = typeof url;
        var headers = null;
        var count = args.length;
        method = method.toUpperCase();

        if (count === 0 || count > 5) {
            throw "Invalid number of arguments for " + method + "() method : " + count;
        }

        if (!string(type)) {
            throw getError(method, type);
        }

        if (count === 2) {
            if (args[1] !== null) {
                type = typeof args[1];
                if (type === "object" || string(type)) {
                    data = args[1];
                } else if (type === "function") {
                    callback = args[1];
                } else {
                    throw getError(method, type);
                }
            }
        } else if (count === 3) {
            if (args[1] != null) {
                type = typeof args[1];
                if (type === "object" || string(type)) {
                    data = args[1];
                } else {
                    throw getError(method, type);
                }
            }

            if (args[2] != null) {
                type = typeof args[2];
                if (type === "function") {
                    callback = args[2];
                } else if (string(type)) {
                    dataType = args[2];
                } else if (type === "object") {
                    headers = args[2];
                } else {
                    throw getError(method, type);
                }
            }
        } else if (count === 4) {
            if (args[1] != null) {
                type = typeof args[1];
                if (type === "object" || string(type)) {
                    data = args[1];
                } else {
                    throw getError(method, type);
                }
            }

            if (args[2] != null) {
                type = typeof args[2];
                if (string(type)) {
                    dataType = args[2];
                } else if (type === "object") {
                    headers = args[2];
                } else {
                    throw getError(method, type);
                }
            }

            if (args[3] != null) {
                type = typeof args[3];
                if (type === "function") {
                    callback = args[3];
                } else if (string(type)) {
                    if (!string(args[2])) {
                        dataType = args[3];
                    } else {
                        throw getError(method, type);
                    }
                } else {
                    throw getError(method, type);
                }
            }
        } else if (count === 5) {
            if (args[1] != null) {
                type = typeof args[1];
                if (type === "object" || string(type)) {
                    data = args[1];
                } else {
                    throw getError(method, type);
                }
            }

            if (args[2] != null) {
                type = typeof args[2];
                if (type === "object") {
                    headers = args[2];
                } else {
                    throw getError(method, type);
                }
            }

            if (args[3] != null) {
                type = typeof args[3];
                if (string(type)) {
                    dataType = args[3];
                } else {
                    throw getError(method, type);
                }
            }

            if (args[4] != null) {
                type = typeof args[4];
                if (type === "function") {
                    callback = args[4];
                } else {
                    throw getError(method, type);
                }
            }
        }

        var query;
        var first = true;
        if (string(data)) {
            query = data;
        } else {
            query = "";
            for (var name in data) {
                query = query || "";
                if (data.hasOwnProperty(name)) {
                    query += (first ? "" : "&") + name + "=" + encodeURIComponent(data[name]);
                    first = first ? false : first;
                }
            }
        }

        if (method === "GET") {
            url = query ? url + "?" + query : url;
            query = null;
        } else if (method === "POST") {
            xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
        }
        for (name in headers) {
            if (headers.hasOwnProperty(name)) {
                xhr.setRequestHeader(name, headers[name]);
            }
        }

        if (callback) {
            xhr.onreadystatechange = function() {
                if (xhr.readyState === 4 && xhr.status === 200) {
                    callback.call(that, formatData(xhr, dataType), xhr);
                }
            };
        }
        xhr.open(method, url, callback !== null);
        xhr.send(query);
        return callback !== null ? xhr : {
            data : formatData(xhr, dataType),
            xhr : xhr
        };
    };

    get = function() {
        return httpCall("GET", arguments);
    };

    post = function() {
        return httpCall("POST", arguments);
    };

    put = function() {
        return httpCall("PUT", arguments);
    };

    del = function() {
        return httpCall("DELETE", arguments);
    };

    head = function() {
        return httpCall("HEAD", arguments);
    };

    options = function() {
        return httpCall("OPTIONS", arguments);
    };

    trace = function() {
        return httpCall("TRACE", arguments);
    };

    connect = function() {
        return httpCall("CONNECT", arguments);
    };
})();<commit_msg>fixing an issue in HTTP methods<commit_after>
var get, post, put, del, head, options, trace, connect;

/**
 *
 * @param url, data, callback, type
 * data = xml | json | text
 * post(url, data, headers, type, callback)
 */
(function() {
    var formatData = function(xhr, type) {
        var data = xhr.responseText;
        type = type.toLowerCase();
        if (type === "xml") {
			return new XML(data.replace(/<\?xml.*?\?>/, "").replace(/<!--[\s\S]*?-->/g, ""));
		} else if (type === "json") {
			return parse(data);
        } else {
			return data;
		}
    };

    var string = function(o) {
        return typeof o === 'string' || o instanceof String;
    };

    var getError = function(method, type) {
        return "Invalid argument for " + method.toLowerCase() + "() method : " + type;
    };

    var httpCall = function(method, args) {
        var xhr = new XMLHttpRequest();
        var url = args[0];
        var data = null;
        var callback = null;
        var dataType = "text";
        var that = this;
        var type = typeof url;
        var headers = null;
        var count = args.length;
        method = method.toUpperCase();

        if (count === 0 || count > 5) {
            throw "Invalid number of arguments for " + method + "() method : " + count;
        }

        if (!string(url)) {
            throw getError(method, type);
        }

        if (count === 2) {
            if (args[1] !== null) {
                type = typeof args[1];
                if (type === "object" || string(args[1])) {
                    data = args[1];
                } else if (type === "function") {
                    callback = args[1];
                } else {
                    throw getError(method, type);
                }
            }
        } else if (count === 3) {
            if (args[1] != null) {
                type = typeof args[1];
                if (type === "object" || string(args[1])) {
                    data = args[1];
                } else {
                    throw getError(method, type);
                }
            }

            if (args[2] != null) {
                type = typeof args[2];
                if (type === "function") {
                    callback = args[2];
                } else if (string(args[2])) {
                    dataType = args[2];
                } else if (type === "object") {
                    headers = args[2];
                } else {
                    throw getError(method, type);
                }
            }
        } else if (count === 4) {
            if (args[1] != null) {
                type = typeof args[1];
                if (type === "object" || string(args[1])) {
                    data = args[1];
                } else {
                    throw getError(method, type);
                }
            }

            if (args[2] != null) {
                type = typeof args[2];
                if (string(args[2])) {
                    dataType = args[2];
                } else if (type === "object") {
                    headers = args[2];
                } else {
                    throw getError(method, type);
                }
            }

            if (args[3] != null) {
                type = typeof args[3];
                if (type === "function") {
                    callback = args[3];
                } else if (string(args[3])) {
                    if (!string(args[2])) {
                        dataType = args[3];
                    } else {
                        throw getError(method, type);
                    }
                } else {
                    throw getError(method, type);
                }
            }
        } else if (count === 5) {
            if (args[1] != null) {
                type = typeof args[1];
                if (type === "object" || string(args[1])) {
                    data = args[1];
                } else {
                    throw getError(method, type);
                }
            }

            if (args[2] != null) {
                type = typeof args[2];
                if (type === "object") {
                    headers = args[2];
                } else {
                    throw getError(method, type);
                }
            }

            if (args[3] != null) {
                type = typeof args[3];
                if (string(args[3])) {
                    dataType = args[3];
                } else {
                    throw getError(method, type);
                }
            }

            if (args[4] != null) {
                type = typeof args[4];
                if (type === "function") {
                    callback = args[4];
                } else {
                    throw getError(method, type);
                }
            }
        }

        var query;
        var first = true;
        if (string(data)) {
            query = data;
        } else {
            query = "";
            for (var name in data) {
                query = query || "";
                if (data.hasOwnProperty(name)) {
                    query += (first ? "" : "&") + name + "=" + encodeURIComponent(data[name]);
                    first = first ? false : first;
                }
            }
        }

        if (method === "GET") {
            url = query ? url + "?" + query : url;
            query = null;
        } else if (method === "POST") {
            xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
        }
        for (name in headers) {
            if (headers.hasOwnProperty(name)) {
                xhr.setRequestHeader(name, headers[name]);
            }
        }

        if (callback) {
            xhr.onreadystatechange = function() {
                if (xhr.readyState === 4 && xhr.status === 200) {
                    callback.call(that, formatData(xhr, dataType), xhr);
                }
            };
        }
        xhr.open(method, url, callback !== null);
        xhr.send(query);
        return callback !== null ? xhr : {
            data : formatData(xhr, dataType),
            xhr : xhr
        };
    };

    get = function() {
        return httpCall("GET", arguments);
    };

    post = function() {
        return httpCall("POST", arguments);
    };

    put = function() {
        return httpCall("PUT", arguments);
    };

    del = function() {
        return httpCall("DELETE", arguments);
    };

    head = function() {
        return httpCall("HEAD", arguments);
    };

    options = function() {
        return httpCall("OPTIONS", arguments);
    };

    trace = function() {
        return httpCall("TRACE", arguments);
    };

    connect = function() {
        return httpCall("CONNECT", arguments);
    };
})();<|endoftext|>


<commit_before>/**
 * @license Copyright 2021 The Lighthouse Authors. All Rights Reserved.
 * 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.
 */
'use strict';

/* eslint-env jest */

import jsdom from 'jsdom';
import {jest} from '@jest/globals';

import {Util} from '../../renderer/util.js';
import {DOM} from '../../renderer/dom.js';
import {DetailsRenderer} from '../../renderer/details-renderer.js';
import {CategoryRenderer} from '../../renderer/category-renderer.js';
import {ReportRenderer} from '../../renderer/report-renderer.js';
import sampleResultsOrig from '../../../lighthouse-core/test/results/sample_v2.json';

describe('ReportRendererAxe', () => {
  describe('with aXe', () => {
    let axe;
    let renderer;
    let sampleResults;

    beforeAll(async () => {
      global.console.warn = jest.fn();

      const {window} = new jsdom.JSDOM();
      const dom = new DOM(window.document);
      const detailsRenderer = new DetailsRenderer(dom);
      const categoryRenderer = new CategoryRenderer(dom, detailsRenderer);
      renderer = new ReportRenderer(dom, categoryRenderer);
      sampleResults = Util.prepareReportResult(sampleResultsOrig);

      // Needed by axe-core
      // https://github.com/dequelabs/axe-core/blob/581c441c/doc/examples/jsdom/test/a11y.js#L24
      global.window = global.self = window;
      global.Node = global.self.Node;
      global.Element = global.self.Element;

      // axe-core must be imported after the global polyfills
      axe = (await import('axe-core')).default;
    });

    afterAll(() => {
      global.window = undefined;
      global.Node = undefined;
      global.Element = undefined;
    });

    it('renders without axe violations', async () => {
      const container = renderer._dom.document().createElement('main');
      const output = renderer.renderReport(sampleResults, container);

      renderer._dom.document().body.appendChild(container);

      const config = {
        rules: {
          // Reports may have duplicate ids
          // https://github.com/GoogleChrome/lighthouse/issues/9432
          'duplicate-id': {enabled: false},
          'duplicate-id-aria': {enabled: false},
          'landmark-no-duplicate-contentinfo': {enabled: false},
          // The following rules are disable for axe-core + jsdom compatibility
          // https://github.com/dequelabs/axe-core/tree/b573b1c1/doc/examples/jest_react#to-run-the-example
          'color-contrast': {enabled: false},
          'link-in-text-block': {enabled: false},
          // Report has empty links prior to i18n-ing.
          'link-name': {enabled: false},
          // May not be a real issue. https://github.com/dequelabs/axe-core/issues/2958
          'nested-interactive': {enabled: false},
        },
      };

      const axeResults = await axe.run(output, config);
      expect(axeResults.violations).toEqual([]);
    },
    // This test takes 40s on fast hardware, and 50-60s on GHA (but can take longer).
    // https://github.com/dequelabs/axe-core/tree/b573b1c1/doc/examples/jest_react#timeout-issues
    /* timeout= */ process.env.CI ? 200_000 : 60_000
    );
  });
});
<commit_msg>report: port axe test to pptr (#13450)<commit_after>/**
 * @license Copyright 2021 The Lighthouse Authors. All Rights Reserved.
 * 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.
 */
'use strict';

/* eslint-env jest */

import puppeteer from 'puppeteer';

import sampleResults from '../../../lighthouse-core/test/results/sample_v2.json';
import reportGenerator from '../../generator/report-generator.js';
import axeLib from '../../../lighthouse-core/lib/axe.js';

describe('ReportRendererAxe', () => {
  describe('with aXe', () => {
    let browser;

    beforeAll(async () => {
      browser = await puppeteer.launch();
    });

    afterAll(async () => {
      await browser.close();
    });

    it('renders without axe violations', async () => {
      const page = await browser.newPage();
      const htmlReport = reportGenerator.generateReportHtml(sampleResults);
      await page.setContent(htmlReport);

      // Superset of Lighthouse's aXe config
      const config = {
        runOnly: {
          type: 'tag',
          values: [
            'wcag2a',
            'wcag2aa',
          ],
        },
        resultTypes: ['violations', 'inapplicable'],
        rules: {
          'tabindex': {enabled: true},
          'accesskeys': {enabled: true},
          'heading-order': {enabled: true},
          'meta-viewport': {enabled: true},
          'aria-treeitem-name': {enabled: true},
        },
      };

      await page.evaluate(axeLib.source);
      // eslint-disable-next-line no-undef
      const axeResults = await page.evaluate(config => axe.run(config), config);

      expect(axeResults.violations).toMatchObject([
        // Color contrast failure only pops up if this pptr is run headfully.
        // There are currently 27 problematic nodes, primarily audit display text and explanations.
        // TODO: fix these failures, regardless.
        // {
        //   id: 'color-contrast',
        // },
        {
          id: 'duplicate-id',
          nodes: [
            // We use these audits in multiple categories. Makes sense.
            {html: '
'},
            {html: '
'},
            {html: '
'},
          ],
        },
      ]);
    },
    // This test takes 10s on fast hardware, but can take longer in CI.
    // https://github.com/dequelabs/axe-core/tree/b573b1c1/doc/examples/jest_react#timeout-issues
    /* timeout= */ 20_000
    );
  });
});
<|endoftext|>


<commit_before>// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// 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.

// The original file lives here: http://go/cross_domain_channel.js

/**
 * @fileoverview Implements a cross-domain communication channel. A
 * typical web page is prevented by browser security from sending
 * request, such as a XMLHttpRequest, to other servers than the ones
 * from which it came. The Jsonp class provides a workaround by
 * using dynamically generated script tags. Typical usage:.
 *
 * var jsonp = new goog.net.Jsonp(new goog.Uri('http://my.host.com/servlet'));
 * var payload = { 'foo': 1, 'bar': true };
 * jsonp.send(payload, function(reply) { alert(reply) });
 *
 * This script works in all browsers that are currently supported by
 * the Google Maps API, which is IE 6.0+, Firefox 0.8+, Safari 1.2.4+,
 * Netscape 7.1+, Mozilla 1.4+, Opera 8.02+.
 *
 */

goog.provide('goog.net.Jsonp');

goog.require('goog.Uri');
goog.require('goog.net.jsloader');

// WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
//
// This class allows us (Google) to send data from non-Google and thus
// UNTRUSTED pages to our servers. Under NO CIRCUMSTANCES return
// anything sensitive, such as session or cookie specific data. Return
// only data that you want parties external to Google to have. Also
// NEVER use this method to send data from web pages to untrusted
// servers, or redirects to unknown servers (www.google.com/cache,
// /q=xx&btnl, /url, www.googlepages.com, etc.)
//
// WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING

/**
 * Creates a new cross domain channel that sends data to the specified
 * host URL. By default, if no reply arrives within 5s, the channel
 * assumes the call failed to complete successfully.
 *
 * @param {goog.Uri|string} uri The Uri of the server side code that receives
 *     data posted through this channel (e.g.,
 *     "http://maps.google.com/maps/geo").
 *
 * @param {string=} opt_callbackParamName The parameter name that is used to
 *     specify the callback. Defaults to "callback".
 *
 * @constructor
 * @final
 */
goog.net.Jsonp = function(uri, opt_callbackParamName) {
  /**
   * The uri_ object will be used to encode the payload that is sent to the
   * server.
   * @type {goog.Uri}
   * @private
   */
  this.uri_ = new goog.Uri(uri);

  /**
   * This is the callback parameter name that is added to the uri.
   * @type {string}
   * @private
   */
  this.callbackParamName_ =
      opt_callbackParamName ? opt_callbackParamName : 'callback';

  /**
   * The length of time, in milliseconds, this channel is prepared
   * to wait for for a request to complete. The default value is 5 seconds.
   * @type {number}
   * @private
   */
  this.timeout_ = 5000;

  /**
   * The nonce to use in the dynamically generated script tags. This is used for
   * allowing the script callbacks to execute when the page has an enforced
   * Content Security Policy.
   * @type {string}
   * @private
   */
  this.nonce_ = '';
};

/**
 * The prefix for the callback name which will be stored on goog.global.
 */
goog.net.Jsonp.CALLBACKS = '_callbacks_';

/**
 * Used to generate unique callback IDs. The counter must be global because
 * all channels share a common callback object.
 * @private
 */
goog.net.Jsonp.scriptCounter_ = 0;

/**
 * Static private method which returns the global unique callback id.
 *
 * @param {string} id The id of the script node.
 * @return {string} A global unique id used to store callback on goog.global
 *     object.
 * @private
 */
goog.net.Jsonp.getCallbackId_ = function(id) {
  return goog.net.Jsonp.CALLBACKS + '__' + id;
};

/**
 * Sets the length of time, in milliseconds, this channel is prepared
 * to wait for for a request to complete. If the call is not competed
 * within the set time span, it is assumed to have failed. To wait
 * indefinitely for a request to complete set the timout to a negative
 * number.
 *
 * @param {number} timeout The length of time before calls are
 * interrupted.
 */
goog.net.Jsonp.prototype.setRequestTimeout = function(timeout) {
  this.timeout_ = timeout;
};

/**
 * Returns the current timeout value, in milliseconds.
 *
 * @return {number} The timeout value.
 */
goog.net.Jsonp.prototype.getRequestTimeout = function() {
  return this.timeout_;
};

/**
 * Sets the nonce value for CSP. This nonce value will be added to any created
 * script elements and must match the nonce provided in the
 * Content-Security-Policy header sent by the server for the callback to pass
 * CSP enforcement.
 *
 * @param {string} nonce The CSP nonce value.
 */
goog.net.Jsonp.prototype.setNonce = function(nonce) {
  this.nonce_ = nonce;
};

/**
 * Sends the given payload to the URL specified at the construction
 * time. The reply is delivered to the given replyCallback. If the
 * errorCallback is specified and the reply does not arrive within the
 * timeout period set on this channel, the errorCallback is invoked
 * with the original payload.
 *
 * If no reply callback is specified, then the response is expected to
 * consist of calls to globally registered functions. No &callback=
 * URL parameter will be sent in the request, and the script element
 * will be cleaned up after the timeout.
 *
 * @param {Object=} opt_payload Name-value pairs.  If given, these will be
 *     added as parameters to the supplied URI as GET parameters to the
 *     given server URI.
 *
 * @param {Function=} opt_replyCallback A function expecting one
 *     argument, called when the reply arrives, with the response data.
 *
 * @param {Function=} opt_errorCallback A function expecting one
 *     argument, called on timeout, with the payload (if given), otherwise
 *     null.
 *
 * @param {string=} opt_callbackParamValue Value to be used as the
 *     parameter value for the callback parameter (callbackParamName).
 *     To be used when the value needs to be fixed by the client for a
 *     particular request, to make use of the cached responses for the request.
 *     NOTE: If multiple requests are made with the same
 *     opt_callbackParamValue, only the last call will work whenever the
 *     response comes back.
 *
 * @return {!Object} A request descriptor that may be used to cancel this
 *     transmission, or null, if the message may not be cancelled.
 */
goog.net.Jsonp.prototype.send = function(
    opt_payload, opt_replyCallback, opt_errorCallback, opt_callbackParamValue) {

  var payload = opt_payload || null;

  var id = opt_callbackParamValue ||
      '_' + (goog.net.Jsonp.scriptCounter_++).toString(36) +
          goog.now().toString(36);
  var callbackId = goog.net.Jsonp.getCallbackId_(id);

  // Create a new Uri object onto which this payload will be added
  var uri = this.uri_.clone();
  if (payload) {
    goog.net.Jsonp.addPayloadToUri_(payload, uri);
  }

  if (opt_replyCallback) {
    var reply = goog.net.Jsonp.newReplyHandler_(id, opt_replyCallback);
    // Register the callback on goog.global to make it discoverable
    // by jsonp response.
    goog.global[callbackId] = reply;
    uri.setParameterValues(this.callbackParamName_, callbackId);
  }

  var options = {timeout: this.timeout_, cleanupWhenDone: true};
  if (this.nonce_) {
    options.attributes = {'nonce': this.nonce_};
  }

  var deferred = goog.net.jsloader.load(uri.toString(), options);
  var error = goog.net.Jsonp.newErrorHandler_(id, payload, opt_errorCallback);
  deferred.addErrback(error);

  return {id_: id, deferred_: deferred};
};

/**
 * Cancels a given request. The request must be exactly the object returned by
 * the send method.
 *
 * @param {Object} request The request object returned by the send method.
 */
goog.net.Jsonp.prototype.cancel = function(request) {
  if (request) {
    if (request.deferred_) {
      request.deferred_.cancel();
    }
    if (request.id_) {
      goog.net.Jsonp.cleanup_(request.id_, false);
    }
  }
};

/**
 * Creates a timeout callback that calls the given timeoutCallback with the
 * original payload.
 *
 * @param {string} id The id of the script node.
 * @param {Object} payload The payload that was sent to the server.
 * @param {Function=} opt_errorCallback The function called on timeout.
 * @return {!Function} A zero argument function that handles callback duties.
 * @private
 */
goog.net.Jsonp.newErrorHandler_ = function(id, payload, opt_errorCallback) {
  /**
   * When we call across domains with a request, this function is the
   * timeout handler. Once it's done executing the user-specified
   * error-handler, it removes the script node and original function.
   */
  return function() {
    goog.net.Jsonp.cleanup_(id, false);
    if (opt_errorCallback) {
      opt_errorCallback(payload);
    }
  };
};

/**
 * Creates a reply callback that calls the given replyCallback with data
 * returned by the server.
 *
 * @param {string} id The id of the script node.
 * @param {Function} replyCallback The function called on reply.
 * @return {!Function} A reply callback function.
 * @private
 */
goog.net.Jsonp.newReplyHandler_ = function(id, replyCallback) {
  /**
   * This function is the handler for the all-is-well response. It
   * clears the error timeout handler, calls the user's handler, then
   * removes the script node and itself.
   *
   * @param {...Object} var_args The response data sent from the server.
   */
  var handler = function(var_args) {
    goog.net.Jsonp.cleanup_(id, true);
    replyCallback.apply(undefined, arguments);
  };
  return handler;
};

/**
 * Removes the reply handler registered on goog.global object.
 *
 * @param {string} id The id of the script node to be removed.
 * @param {boolean} deleteReplyHandler If true, delete the reply handler
 *     instead of setting it to nullFunction (if we know the callback could
 *     never be called again).
 * @private
 */
goog.net.Jsonp.cleanup_ = function(id, deleteReplyHandler) {
  var callbackId = goog.net.Jsonp.getCallbackId_(id);
  if (goog.global[callbackId]) {
    if (deleteReplyHandler) {
      try {
        delete goog.global[callbackId];
      } catch (e) {
        // NOTE: Workaround to delete property on 'window' in IE <= 8, see:
        // http://stackoverflow.com/questions/1073414/deleting-a-window-property-in-ie
        goog.global[callbackId] = undefined;
      }
    } else {
      // Removing the script tag doesn't necessarily prevent the script
      // from firing, so we make the callback a noop.
      goog.global[callbackId] = goog.nullFunction;
    }
  }
};

/**
 * Returns URL encoded payload. The payload should be a map of name-value
 * pairs, in the form {"foo": 1, "bar": true, ...}.  If the map is empty,
 * the URI will be unchanged.
 *
 * <p>The method uses hasOwnProperty() to assure the properties are on the
 * object, not on its prototype.
 *
 * @param {!Object} payload A map of value name pairs to be encoded.
 *     A value may be specified as an array, in which case a query parameter
 *     will be created for each value, e.g.:
 *     {"foo": [1,2]} will encode to "foo=1&foo=2".
 *
 * @param {!goog.Uri} uri A Uri object onto which the payload key value pairs
 *     will be encoded.
 *
 * @return {!goog.Uri} A reference to the Uri sent as a parameter.
 * @private
 */
goog.net.Jsonp.addPayloadToUri_ = function(payload, uri) {
  for (var name in payload) {
    // NOTE(user): Safari/1.3 doesn't have hasOwnProperty(). In that
    // case, we iterate over all properties as a very lame workaround.
    if (!payload.hasOwnProperty || payload.hasOwnProperty(name)) {
      uri.setParameterValues(name, payload[name]);
    }
  }
  return uri;
};

// WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
//
// This class allows us (Google) to send data from non-Google and thus
// UNTRUSTED pages to our servers. Under NO CIRCUMSTANCES return
// anything sensitive, such as session or cookie specific data. Return
// only data that you want parties external to Google to have. Also
// NEVER use this method to send data from web pages to untrusted
// servers, or redirects to unknown servers (www.google.com/cache,
// /q=xx&btnl, /url, www.googlepages.com, etc.)
//
// WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
<commit_msg>Use goog.net.jsloader.safeLoad instead of deprecated .load.<commit_after>// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// 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.

// The original file lives here: http://go/cross_domain_channel.js

/**
 * @fileoverview Implements a cross-domain communication channel. A
 * typical web page is prevented by browser security from sending
 * request, such as a XMLHttpRequest, to other servers than the ones
 * from which it came. The Jsonp class provides a workaround by
 * using dynamically generated script tags. Typical usage:.
 *
 * var jsonp = new goog.net.Jsonp(new goog.Uri('http://my.host.com/servlet'));
 * var payload = { 'foo': 1, 'bar': true };
 * jsonp.send(payload, function(reply) { alert(reply) });
 *
 * This script works in all browsers that are currently supported by
 * the Google Maps API, which is IE 6.0+, Firefox 0.8+, Safari 1.2.4+,
 * Netscape 7.1+, Mozilla 1.4+, Opera 8.02+.
 *
 */

goog.provide('goog.net.Jsonp');

goog.require('goog.Uri');
goog.require('goog.html.legacyconversions');
goog.require('goog.net.jsloader');

// WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
//
// This class allows us (Google) to send data from non-Google and thus
// UNTRUSTED pages to our servers. Under NO CIRCUMSTANCES return
// anything sensitive, such as session or cookie specific data. Return
// only data that you want parties external to Google to have. Also
// NEVER use this method to send data from web pages to untrusted
// servers, or redirects to unknown servers (www.google.com/cache,
// /q=xx&btnl, /url, www.googlepages.com, etc.)
//
// WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING

/**
 * Creates a new cross domain channel that sends data to the specified
 * host URL. By default, if no reply arrives within 5s, the channel
 * assumes the call failed to complete successfully.
 *
 * @param {goog.Uri|string} uri The Uri of the server side code that receives
 *     data posted through this channel (e.g.,
 *     "http://maps.google.com/maps/geo").
 *
 * @param {string=} opt_callbackParamName The parameter name that is used to
 *     specify the callback. Defaults to "callback".
 *
 * @constructor
 * @final
 */
goog.net.Jsonp = function(uri, opt_callbackParamName) {
  /**
   * The uri_ object will be used to encode the payload that is sent to the
   * server.
   * @type {goog.Uri}
   * @private
   */
  this.uri_ = new goog.Uri(uri);

  /**
   * This is the callback parameter name that is added to the uri.
   * @type {string}
   * @private
   */
  this.callbackParamName_ =
      opt_callbackParamName ? opt_callbackParamName : 'callback';

  /**
   * The length of time, in milliseconds, this channel is prepared
   * to wait for for a request to complete. The default value is 5 seconds.
   * @type {number}
   * @private
   */
  this.timeout_ = 5000;

  /**
   * The nonce to use in the dynamically generated script tags. This is used for
   * allowing the script callbacks to execute when the page has an enforced
   * Content Security Policy.
   * @type {string}
   * @private
   */
  this.nonce_ = '';
};

/**
 * The prefix for the callback name which will be stored on goog.global.
 */
goog.net.Jsonp.CALLBACKS = '_callbacks_';

/**
 * Used to generate unique callback IDs. The counter must be global because
 * all channels share a common callback object.
 * @private
 */
goog.net.Jsonp.scriptCounter_ = 0;

/**
 * Static private method which returns the global unique callback id.
 *
 * @param {string} id The id of the script node.
 * @return {string} A global unique id used to store callback on goog.global
 *     object.
 * @private
 */
goog.net.Jsonp.getCallbackId_ = function(id) {
  return goog.net.Jsonp.CALLBACKS + '__' + id;
};

/**
 * Sets the length of time, in milliseconds, this channel is prepared
 * to wait for for a request to complete. If the call is not competed
 * within the set time span, it is assumed to have failed. To wait
 * indefinitely for a request to complete set the timout to a negative
 * number.
 *
 * @param {number} timeout The length of time before calls are
 * interrupted.
 */
goog.net.Jsonp.prototype.setRequestTimeout = function(timeout) {
  this.timeout_ = timeout;
};

/**
 * Returns the current timeout value, in milliseconds.
 *
 * @return {number} The timeout value.
 */
goog.net.Jsonp.prototype.getRequestTimeout = function() {
  return this.timeout_;
};

/**
 * Sets the nonce value for CSP. This nonce value will be added to any created
 * script elements and must match the nonce provided in the
 * Content-Security-Policy header sent by the server for the callback to pass
 * CSP enforcement.
 *
 * @param {string} nonce The CSP nonce value.
 */
goog.net.Jsonp.prototype.setNonce = function(nonce) {
  this.nonce_ = nonce;
};

/**
 * Sends the given payload to the URL specified at the construction
 * time. The reply is delivered to the given replyCallback. If the
 * errorCallback is specified and the reply does not arrive within the
 * timeout period set on this channel, the errorCallback is invoked
 * with the original payload.
 *
 * If no reply callback is specified, then the response is expected to
 * consist of calls to globally registered functions. No &callback=
 * URL parameter will be sent in the request, and the script element
 * will be cleaned up after the timeout.
 *
 * @param {Object=} opt_payload Name-value pairs.  If given, these will be
 *     added as parameters to the supplied URI as GET parameters to the
 *     given server URI.
 *
 * @param {Function=} opt_replyCallback A function expecting one
 *     argument, called when the reply arrives, with the response data.
 *
 * @param {Function=} opt_errorCallback A function expecting one
 *     argument, called on timeout, with the payload (if given), otherwise
 *     null.
 *
 * @param {string=} opt_callbackParamValue Value to be used as the
 *     parameter value for the callback parameter (callbackParamName).
 *     To be used when the value needs to be fixed by the client for a
 *     particular request, to make use of the cached responses for the request.
 *     NOTE: If multiple requests are made with the same
 *     opt_callbackParamValue, only the last call will work whenever the
 *     response comes back.
 *
 * @return {!Object} A request descriptor that may be used to cancel this
 *     transmission, or null, if the message may not be cancelled.
 */
goog.net.Jsonp.prototype.send = function(
    opt_payload, opt_replyCallback, opt_errorCallback, opt_callbackParamValue) {

  var payload = opt_payload || null;

  var id = opt_callbackParamValue ||
      '_' + (goog.net.Jsonp.scriptCounter_++).toString(36) +
          goog.now().toString(36);
  var callbackId = goog.net.Jsonp.getCallbackId_(id);

  // Create a new Uri object onto which this payload will be added
  var uri = this.uri_.clone();
  if (payload) {
    goog.net.Jsonp.addPayloadToUri_(payload, uri);
  }

  if (opt_replyCallback) {
    var reply = goog.net.Jsonp.newReplyHandler_(id, opt_replyCallback);
    // Register the callback on goog.global to make it discoverable
    // by jsonp response.
    goog.global[callbackId] = reply;
    uri.setParameterValues(this.callbackParamName_, callbackId);
  }

  var options = {timeout: this.timeout_, cleanupWhenDone: true};
  if (this.nonce_) {
    options.attributes = {'nonce': this.nonce_};
  }

  var deferred = goog.net.jsloader.safeLoad(
      goog.html.legacyconversions.trustedResourceUrlFromString(uri.toString()),
      options);
  var error = goog.net.Jsonp.newErrorHandler_(id, payload, opt_errorCallback);
  deferred.addErrback(error);

  return {id_: id, deferred_: deferred};
};

/**
 * Cancels a given request. The request must be exactly the object returned by
 * the send method.
 *
 * @param {Object} request The request object returned by the send method.
 */
goog.net.Jsonp.prototype.cancel = function(request) {
  if (request) {
    if (request.deferred_) {
      request.deferred_.cancel();
    }
    if (request.id_) {
      goog.net.Jsonp.cleanup_(request.id_, false);
    }
  }
};

/**
 * Creates a timeout callback that calls the given timeoutCallback with the
 * original payload.
 *
 * @param {string} id The id of the script node.
 * @param {Object} payload The payload that was sent to the server.
 * @param {Function=} opt_errorCallback The function called on timeout.
 * @return {!Function} A zero argument function that handles callback duties.
 * @private
 */
goog.net.Jsonp.newErrorHandler_ = function(id, payload, opt_errorCallback) {
  /**
   * When we call across domains with a request, this function is the
   * timeout handler. Once it's done executing the user-specified
   * error-handler, it removes the script node and original function.
   */
  return function() {
    goog.net.Jsonp.cleanup_(id, false);
    if (opt_errorCallback) {
      opt_errorCallback(payload);
    }
  };
};

/**
 * Creates a reply callback that calls the given replyCallback with data
 * returned by the server.
 *
 * @param {string} id The id of the script node.
 * @param {Function} replyCallback The function called on reply.
 * @return {!Function} A reply callback function.
 * @private
 */
goog.net.Jsonp.newReplyHandler_ = function(id, replyCallback) {
  /**
   * This function is the handler for the all-is-well response. It
   * clears the error timeout handler, calls the user's handler, then
   * removes the script node and itself.
   *
   * @param {...Object} var_args The response data sent from the server.
   */
  var handler = function(var_args) {
    goog.net.Jsonp.cleanup_(id, true);
    replyCallback.apply(undefined, arguments);
  };
  return handler;
};

/**
 * Removes the reply handler registered on goog.global object.
 *
 * @param {string} id The id of the script node to be removed.
 * @param {boolean} deleteReplyHandler If true, delete the reply handler
 *     instead of setting it to nullFunction (if we know the callback could
 *     never be called again).
 * @private
 */
goog.net.Jsonp.cleanup_ = function(id, deleteReplyHandler) {
  var callbackId = goog.net.Jsonp.getCallbackId_(id);
  if (goog.global[callbackId]) {
    if (deleteReplyHandler) {
      try {
        delete goog.global[callbackId];
      } catch (e) {
        // NOTE: Workaround to delete property on 'window' in IE <= 8, see:
        // http://stackoverflow.com/questions/1073414/deleting-a-window-property-in-ie
        goog.global[callbackId] = undefined;
      }
    } else {
      // Removing the script tag doesn't necessarily prevent the script
      // from firing, so we make the callback a noop.
      goog.global[callbackId] = goog.nullFunction;
    }
  }
};

/**
 * Returns URL encoded payload. The payload should be a map of name-value
 * pairs, in the form {"foo": 1, "bar": true, ...}.  If the map is empty,
 * the URI will be unchanged.
 *
 * <p>The method uses hasOwnProperty() to assure the properties are on the
 * object, not on its prototype.
 *
 * @param {!Object} payload A map of value name pairs to be encoded.
 *     A value may be specified as an array, in which case a query parameter
 *     will be created for each value, e.g.:
 *     {"foo": [1,2]} will encode to "foo=1&foo=2".
 *
 * @param {!goog.Uri} uri A Uri object onto which the payload key value pairs
 *     will be encoded.
 *
 * @return {!goog.Uri} A reference to the Uri sent as a parameter.
 * @private
 */
goog.net.Jsonp.addPayloadToUri_ = function(payload, uri) {
  for (var name in payload) {
    // NOTE(user): Safari/1.3 doesn't have hasOwnProperty(). In that
    // case, we iterate over all properties as a very lame workaround.
    if (!payload.hasOwnProperty || payload.hasOwnProperty(name)) {
      uri.setParameterValues(name, payload[name]);
    }
  }
  return uri;
};

// WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
//
// This class allows us (Google) to send data from non-Google and thus
// UNTRUSTED pages to our servers. Under NO CIRCUMSTANCES return
// anything sensitive, such as session or cookie specific data. Return
// only data that you want parties external to Google to have. Also
// NEVER use this method to send data from web pages to untrusted
// servers, or redirects to unknown servers (www.google.com/cache,
// /q=xx&btnl, /url, www.googlepages.com, etc.)
//
// WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
<|endoftext|>


<commit_before>import PropTypes from 'prop-types'
import React from 'react'
import Debug from 'debug'
import {omit} from 'lodash'

const debug = Debug('sanity-imagetool')
const supportsTouch = typeof window !== 'undefined' && 'ontouchstart' in window

// Returns a component that emits `onDragStart, `onDrag` and `onDragEnd` events.
// It handles mouse/touch events the same way
// - `onDragStart` is called with the {x, y} positions relative from the dom node (e.g. where the mousedown event happened)
// - `onDrag` and `onDragEnd` are both called with the {x, y} difference from the previous position
export default function makeDragAware(Component) {
  return class DragAware extends React.PureComponent {
    static propTypes = {
      onDragStart: PropTypes.func.isRequired,
      onDrag: PropTypes.func.isRequired,
      onDragEnd: PropTypes.func.isRequired,
      readOnly: PropTypes.bool,
    }

    currentPos = null
    isDragging = false

    componentDidMount() {
      if (supportsTouch) {
        document.body.addEventListener('touchmove', this.handleTouchMove, {passive: false})
        document.body.addEventListener('touchend', this.handleDragEnd)
        document.body.addEventListener('touchcancel', this.handleDragCancel)
      } else {
        document.body.addEventListener('mousemove', this.handleDrag)
        document.body.addEventListener('mouseup', this.handleDragEnd)
        document.body.addEventListener('mouseleave', this.handleDragCancel)
      }
    }

    componentWillUnmount() {
      if (supportsTouch) {
        document.body.removeEventListener('touchmove', this.handleTouchMove, {passive: false})
        document.body.removeEventListener('touchend', this.handleDragEnd, {passive: false})
        document.body.removeEventListener('touchcancel', this.handleDragCancel)
      } else {
        document.body.removeEventListener('mousemove', this.handleDrag)
        document.body.removeEventListener('mouseup', this.handleDragEnd)
        document.body.removeEventListener('mouseleave', this.handleDragCancel)
      }
    }

    handleTouchMove = (event) => {
      // Disables mobile scroll by touch
      if (this.isDragging) {
        event.preventDefault()
      }
    }

    handleDragStart = (event) => {
      const {onDragStart} = this.props

      if (this.isDragging) {
        debug('Start cancelled, already a drag in progress')
        return
      }

      this.isDragging = true
      const nextPos = getPos(event)
      debug('Drag started %o', nextPos)
      onDragStart(
        getPositionRelativeToRect(nextPos.x, nextPos.y, this.domNode.getBoundingClientRect())
      )

      this.currentPos = nextPos
    }

    handleDrag = (event) => {
      if (!this.isDragging) {
        return
      }
      const {onDrag} = this.props
      const nextPos = getPos(event)
      const diff = diffPos(nextPos, this.currentPos)
      onDrag(diff)
      debug('moving by %o', diff)
      this.currentPos = nextPos
    }

    handleDragEnd = (event) => {
      const {onDragEnd} = this.props
      if (!this.isDragging) {
        return
      }
      const nextPos = getPos(event)
      onDragEnd(
        getPositionRelativeToRect(nextPos.x, nextPos.y, this.domNode.getBoundingClientRect())
      )
      this.isDragging = false
      this.currentPos = null
      debug('Done moving %o', nextPos)
    }

    handleDragCancel = (event) => {
      if (!this.isDragging) {
        return
      }
      const {onDragEnd} = this.props
      this.isDragging = false
      onDragEnd(
        getPositionRelativeToRect(
          this.currentPos.x,
          this.currentPos.y,
          this.domNode.getBoundingClientRect()
        )
      )
      this.currentPos = null
    }

    setDomNode = (node) => {
      this.domNode = node
    }

    render() {
      const {readOnly} = this.props
      return (
        <Component
          ref={this.setDomNode}
          onTouchStart={!readOnly && this.handleDragStart}
          onMouseDown={!readOnly && this.handleDragStart}
          onTouchMove={!readOnly && this.handleDrag}
          {...omit(this.props, ['onDragStart', 'onDragEnd', 'onDrag'])}
        />
      )
    }
  }
}

function getPositionRelativeToRect(x, y, rect) {
  return {
    x: x - rect.left,
    y: y - rect.top,
  }
}

function getPos(event) {
  if (supportsTouch) {
    return event.touches.length
      ? {x: event.touches[0].clientX, y: event.touches[0].clientY}
      : {x: 0, y: 0}
  }

  return {
    x: event.clientX,
    y: event.clientY,
  }
}

function diffPos(pos, otherPos) {
  return {
    x: pos.x - otherPos.x,
    y: pos.y - otherPos.y,
  }
}
<commit_msg>fix(imagetool): avoid calling callbacks when read only<commit_after>import PropTypes from 'prop-types'
import React from 'react'
import Debug from 'debug'

const debug = Debug('sanity-imagetool')
const supportsTouch = typeof window !== 'undefined' && 'ontouchstart' in window

// Returns a component that emits `onDragStart, `onDrag` and `onDragEnd` events.
// It handles mouse/touch events the same way
// - `onDragStart` is called with the {x, y} positions relative from the dom node (e.g. where the mousedown event happened)
// - `onDrag` and `onDragEnd` are both called with the {x, y} difference from the previous position
export default function makeDragAware(Component) {
  return class DragAware extends React.PureComponent {
    static propTypes = {
      onDragStart: PropTypes.func.isRequired,
      onDrag: PropTypes.func.isRequired,
      onDragEnd: PropTypes.func.isRequired,
      readOnly: PropTypes.bool,
    }

    currentPos = null
    isDragging = false

    componentDidMount() {
      if (supportsTouch) {
        document.body.addEventListener('touchmove', this.handleTouchMove, {passive: false})
        document.body.addEventListener('touchend', this.handleDragEnd)
        document.body.addEventListener('touchcancel', this.handleDragCancel)
      } else {
        document.body.addEventListener('mousemove', this.handleDrag)
        document.body.addEventListener('mouseup', this.handleDragEnd)
        document.body.addEventListener('mouseleave', this.handleDragCancel)
      }
    }

    componentWillUnmount() {
      if (supportsTouch) {
        document.body.removeEventListener('touchmove', this.handleTouchMove, {passive: false})
        document.body.removeEventListener('touchend', this.handleDragEnd, {passive: false})
        document.body.removeEventListener('touchcancel', this.handleDragCancel)
      } else {
        document.body.removeEventListener('mousemove', this.handleDrag)
        document.body.removeEventListener('mouseup', this.handleDragEnd)
        document.body.removeEventListener('mouseleave', this.handleDragCancel)
      }
    }

    handleTouchMove = (event) => {
      // Disables mobile scroll by touch
      if (this.isDragging) {
        event.preventDefault()
      }
    }

    handleDragStart = (event) => {
      const {onDragStart, readOnly} = this.props

      if (readOnly) {
        return
      }

      if (this.isDragging) {
        debug('Start cancelled, already a drag in progress')
        return
      }

      this.isDragging = true
      const nextPos = getPos(event)
      debug('Drag started %o', nextPos)
      onDragStart(
        getPositionRelativeToRect(nextPos.x, nextPos.y, this.domNode.getBoundingClientRect())
      )

      this.currentPos = nextPos
    }

    handleDrag = (event) => {
      if (!this.isDragging || this.props.readOnly) {
        return
      }
      const {onDrag} = this.props
      const nextPos = getPos(event)
      const diff = diffPos(nextPos, this.currentPos)
      onDrag(diff)
      debug('moving by %o', diff)
      this.currentPos = nextPos
    }

    handleDragEnd = (event) => {
      const {onDragEnd, readOnly} = this.props
      if (!this.isDragging || readOnly) {
        return
      }
      const nextPos = getPos(event)
      onDragEnd(
        getPositionRelativeToRect(nextPos.x, nextPos.y, this.domNode.getBoundingClientRect())
      )
      this.isDragging = false
      this.currentPos = null
      debug('Done moving %o', nextPos)
    }

    handleDragCancel = (event) => {
      if (!this.isDragging || this.props.readOnly) {
        return
      }
      const {onDragEnd} = this.props
      this.isDragging = false
      onDragEnd(
        getPositionRelativeToRect(
          this.currentPos.x,
          this.currentPos.y,
          this.domNode.getBoundingClientRect()
        )
      )
      this.currentPos = null
    }

    setDomNode = (node) => {
      this.domNode = node
    }

    render() {
      const {readOnly, onDragStart, onDragEnd, onDrag, ...rest} = this.props
      return (
        <Component
          ref={this.setDomNode}
          onTouchStart={readOnly ? undefined : this.handleDragStart}
          onMouseDown={readOnly ? undefined : this.handleDragStart}
          onTouchMove={readOnly ? undefined : this.handleDrag}
          {...rest}
        />
      )
    }
  }
}

function getPositionRelativeToRect(x, y, rect) {
  return {
    x: x - rect.left,
    y: y - rect.top,
  }
}

function getPos(event) {
  if (supportsTouch) {
    return event.touches.length
      ? {x: event.touches[0].clientX, y: event.touches[0].clientY}
      : {x: 0, y: 0}
  }

  return {
    x: event.clientX,
    y: event.clientY,
  }
}

function diffPos(pos, otherPos) {
  return {
    x: pos.x - otherPos.x,
    y: pos.y - otherPos.y,
  }
}
<|endoftext|>