<commit_before>var Tracks = function ()
{
    var scale = 1.0
    var scrollArea, scalableArea, scalableElem, tracks
    var needle_plot
    var position = 0
    var dispatch = d3.dispatch('zoomAndMove')
    var first_scale_factor
    var sequence_elements, baseCharSize
    var is_ready = false
    var controls, box
    var is_animation_running

    var config = {
        scroll_step: 0.8, // how many screen (to left or right) should be scrolled after clicking scroll button
        zoom_step: 2,
        zoom_speed: 100,
        animations_speed: 300,
        animations_ease: 'quad',
        box: null,
        min_zoom: 1,
        max_zoom: 10
    };

    function configure(new_config)
    {
        // Automatic configuration update:
        update_object(config, new_config)
    }

    function calculate_zoom(direction)
    {
        // scale down slower toward 0
        var new_zoom = scale + direction * scale / 15;

        if(new_zoom > config.max_zoom)
        {
            return config.max_zoom
        }
        else if(new_zoom < config.min_zoom)
        {
            return config.min_zoom
        }
        else
        {
            return new_zoom
        }
    }

    function get_scale_factor()
    {
        var content_width = scalableElem.scrollWidth;
        var width = scalableArea.width();

        return width / content_width
    }

    function _setAAPosition(new_position, stop_callback, update_zoom)
    {
        position = trim_position(new_position)

        scrollTo(position)

        if(update_zoom)
            _setZoom(scale, true)

        if(!stop_callback && needle_plot)
        {
            needle_plot.setAAPosition(position, true)
        }
        dispatch.zoomAndMove(this)

    }

    function _getZoom()
    {
        return first_scale_factor * scale
    }

    function _setZoom(new_zoom, stop_callback)
    {
        return _setZoomAndMove(new_zoom, _getAAPosition(), stop_callback, true)
    }

    function trim_position(new_position, new_zoom, skip_zoom_effect)
    {
        if(new_position < 0)
            return 0
        else {
            var end = config.sequenceLength

            if(!skip_zoom_effect)
                end -= _visibleAminoacidsCount(new_zoom)

            if(new_position > end)
                return end
            else
                return new_position
        }
    }

    function _setZoomAndMove(new_zoom, new_pos, stop_callback, skip_animation, center_view)
    {
        new_pos = trim_position(new_pos, new_zoom)

        var styles = scalableElem.style;

        var invoke_callback = (!stop_callback && needle_plot)

        var initial_scale = scale

        var step = function(now, fx)
        {
            if(fx.prop === 'p')
            {
                if(center_view)
                    return

                position = now
                scrollTo(position)
                return
            }

            scale = now
            config.char_size = getCharSize()

            if(center_view)
            {
                // as I want the chosen aminoacid to remain in place when zooming:
                // central = first_visible + visible_count / 2
                // I use the central before zoom and central after zoom to find
                // the first_visible_after_zoom which satisfies condition: central=const
                // new_pos is the first visible aminoacid to be targeted,
                // position is the first visible aminoacid to be shown in this step
                position = new_pos + _visibleAminoacidsCount() / 2 * (1 - initial_scale / scale)
            }

            styles.transform = 'scaleX(' +  first_scale_factor * scale + ')'
            scrollTo(position)

            if(invoke_callback)
                needle_plot.setZoomAndAAPosition(scale, position, true)

        }

        is_animation_running = true
        $({s: scale, p: position})
            .animate(
                {s: new_zoom, p: new_pos},
                {
                    duration: skip_animation ? 0 : config.zoom_speed,
                    ease: config.animations_ease,
                    queue: false,
                    step: step,
                    complete: function() {
                        dispatch.zoomAndMove(this)
                        is_animation_running = false
                    }
                }
            )

    }

    function zoomKeepingCentral(direction)
    {
        var zoom = calculate_zoom(direction)
        var first_visible_aa = _getAAPosition()
        _setZoomAndMove(zoom, first_visible_aa, false, false, true)
    }

    function zoomIn()
    {
        zoomKeepingCentral(+config.zoom_step)
    }

    function zoomOut()
    {
        zoomKeepingCentral(-config.zoom_step)
    }

    function _getAAPosition()
    {
        return position
    }

    function _visibleAminoacidsCount(scale_to_use)
    {
        return config.min_zoom * config.sequenceLength / (scale_to_use ? scale_to_use : scale)
    }

    /**
     * Move by one screen in given direction
     * @param direction - move one screen right if +1, one screen left if -1
     * @param {boolean} animate - should the scrolling be animated?
     */
    function scroll(direction, animate)
    {
        var new_pos_screen = _getAAPosition() + _visibleAminoacidsCount() * direction
        new_pos_screen = trim_position(new_pos_screen)

        if(new_pos_screen === position)
            return

        is_animation_running = true
        $({screen_position: position})
            .animate(
                {screen_position: new_pos_screen},
                {
                    duration: animate ? config.animations_speed : 0,
                    ease: config.animations_ease,
                    queue: false,
                    step: function(now)
                    {
                        position = now
                        scrollTo(now)
                        needle_plot.setAAPosition(now, true)
                        dispatch.zoomAndMove(this)
                    },
                    complete: function() {
                        is_animation_running = false
                    }
                }
            )
    }

    function scrollLeft()
    {
        scroll(-config.scroll_step, true)
    }

    function scrollRight()
    {
        scroll(+config.scroll_step, true)
    }

    function computeBaseCharSize()
    {
        sequence_elements.wrapInner('');
        var span = sequence_elements.children('span');
        var charSize = span.innerWidth() / config.sequenceLength;
        span.contents().unwrap();
        return charSize;
    }

    function getCharSize()
    {
        return baseCharSize * _getZoom();
    }

    function getSequenceLength()
    {
        var seq = $.trim(sequence_elements.text())
        return seq.length
    }

    function scrollTo(new_position)
    {
        scrollArea.scrollLeft(new_position * config.char_size)
    }

    function parsePosition(position_string)
    {
        return trim_position(parseInt(position_string, 10), undefined, true)
    }

    function scrollToCallback()
    {
        var input = $(this).closest('.input-group').find('.scroll-to-input')

        // - 1: sequence is 1 based but position is 0 based
        var user_input = $(input).val()

        var pos
        var zoom

        var correct_expression = /^\s*(\d+)\s*([-|:]\s*(\d+))?\s*$/

        var matched_range = user_input.match(correct_expression)
        var feedback = $('.scroll-feedback')

        function warn() {
            feedback.html('')
        }

        if(matched_range)
        {
            // get range
            var range = [matched_range[1], matched_range[3]]

            // convert to int, trim if the values exceed protein length or 0
            range.map(parsePosition)

            // make first coordinate 0-based
            pos = range[0] - 1

            // if we have both parts of expression (two numbers)
            if(range[1] !== undefined)
            {
                var len = range[1] - range[0]

                // if user provides range with the second value being smaller,
                // it may be good to warn the user; moreover we do not want to
                // divide by zero.
                if(len < 1)
                    return warn()

                // calculate zoom such that we will see only the desired range
                zoom = config.min_zoom * config.sequenceLength / len

                // and trim it to max allowed zoom
                zoom = Math.min(zoom, config.max_zoom)
            }
            else
            {
                // zoom in as close to the mutation as we can get
                zoom = config.max_zoom

                // move the position to the center
                pos -= _visibleAminoacidsCount(zoom) / 2
            }
            // clear the feedback (the empty string is important!)
            feedback.html('')
        }
        else
        {
            // incorrect input
            warn()
        }

        // zoom and move: run callback, animate, do not attempt to center on given position
        _setZoomAndMove(zoom, pos, false, false, false)
    }

    function initButtons(buttons, func, context)
    {
        for(var i = 0; i < buttons.length; i++)
        {
            var button = $(buttons[i])
            if(context)
            {
                button.click($.proxy(func, context))
            }
            else
            {
                button.click(func)
            }
        }
    }

    function initFields(fields, func)
    {
        function call(e)
        {
            if(e.keyCode === 13)
            {
                func.call(this)
            }
        }

        for(var i = 0; i < fields.length; i++)
        {
            $(fields[i]).keyup(call)
        }
    }

    function initControls()
    {
        var buttons = controls.find('.scroll-left')
        initButtons(buttons, scrollLeft, scrollArea)

        buttons = controls.find('.scroll-right')
        initButtons(buttons, scrollRight, scrollArea)

        var innerDiv = box.children('.inner')

        buttons = controls.find('.zoom-out')
        initButtons(buttons, zoomOut, innerDiv)

        buttons = controls.find('.zoom-in')
        initButtons(buttons, zoomIn, innerDiv)

        buttons = controls.find('.scroll-to')
        initButtons(buttons, scrollToCallback)

        buttons = controls.find('.scroll-to-input')
        initFields(buttons, scrollToCallback)
    }

    function onManualScroll(event)
    {
        var scroll = $(event.target).scrollLeft()

        if(!is_animation_running)
        {
            _setAAPosition(scroll / config.char_size, false)
        }
    }

    var publicSpace = {
        init: function(new_config)
        {
            configure(new_config)

            box = $(config.box)
            tracks = box.find('.tracks')

            scrollArea = tracks.find('.scroll-area')
            scrollArea.on('scroll', onManualScroll)
            scalableArea = tracks.find('.scalable')
            scalableElem = scalableArea.get(0)

            var sequence = tracks.find('.sequence')
            sequence_elements = sequence.children('.elements')

            config.sequenceLength = getSequenceLength()
            config.char_size = getCharSize()

            controls = $($.find('.tracks-controls'))

            initControls()

            publicSpace.refreshScaleFactor()

            _setZoom(1)

            // initialize all popovers on tracks
            var tooltip = Tooltip()
            tooltip.init({
                id: 'tracks',
                template: function(data){
                    var elem = $(this)
                    var templated = (
                        '
' + elem.data('title') + '
' +
                        elem.data('content')
                    )
                    return templated
                },
                viewport: scrollArea.get(0)
            })

            var kinases = d3.selectAll('.has-tooltip')
                .each(function(data){
                    // if we add tooltips, let's remove original title tooltip
                    // and reuse this data in the new tooltip
                    $(this).data('title', this.title)
                    this.title = ''
                })
                .call(tooltip.bind)

            dispatch.on('zoomAndMove', function(){
                tooltip.moveToElement()
            })

            $('.subtracks_collapsed').click(function(){
                var track_name = $(this).data('track');
                $('.' + track_name + ' .collapsible').toggleClass('hidden')
            })

            publicSpace.show()

            is_ready = true;
        },
        setNeedlePlotInstance: function(instance)
        {
            needle_plot = instance
        },
        setZoom: _setZoom,
        setAAPosition: _setAAPosition,
        adjustMaxZoom: function()
        {
            // optimal max zoom is a zoom which allows to zoom in to a normal size of a character
            config.max_zoom = 1 / first_scale_factor
            return config.max_zoom
        },
        setZoomAndMove: _setZoomAndMove,
        refreshScaleFactor: function()
        {
            first_scale_factor = get_scale_factor();
            baseCharSize = computeBaseCharSize();
        },
        isReady: function()
        {
            return is_ready;
        },
        hide: function()
        {
            box.find('.tracks-box').addClass('invisible');
            controls.addClass('invisible');

            if(config.animations_speed)
                box.animate({height: 0}, config.animations_speed);
        },
        show: function()
        {
            var internal_box = box.find('.tracks-box');
            internal_box.removeClass('invisible');
            controls.removeClass('invisible');

            if(config.animations_speed)
                box.animate({height: internal_box.height()}, config.animations_speed);
        }

    }

    return publicSpace
}
<commit_msg>Fix result of parsePosition not being used in tracks controls callback<commit_after>var Tracks = function ()
{
    var scale = 1.0
    var scrollArea, scalableArea, scalableElem, tracks
    var needle_plot
    var position = 0
    var dispatch = d3.dispatch('zoomAndMove')
    var first_scale_factor
    var sequence_elements, baseCharSize
    var is_ready = false
    var controls, box
    var is_animation_running

    var config = {
        scroll_step: 0.8, // how many screen (to left or right) should be scrolled after clicking scroll button
        zoom_step: 2,
        zoom_speed: 100,
        animations_speed: 300,
        animations_ease: 'quad',
        box: null,
        min_zoom: 1,
        max_zoom: 10
    };

    function configure(new_config)
    {
        // Automatic configuration update:
        update_object(config, new_config)
    }

    function calculate_zoom(direction)
    {
        // scale down slower toward 0
        var new_zoom = scale + direction * scale / 15;

        if(new_zoom > config.max_zoom)
        {
            return config.max_zoom
        }
        else if(new_zoom < config.min_zoom)
        {
            return config.min_zoom
        }
        else
        {
            return new_zoom
        }
    }

    function get_scale_factor()
    {
        var content_width = scalableElem.scrollWidth;
        var width = scalableArea.width();

        return width / content_width
    }

    function _setAAPosition(new_position, stop_callback, update_zoom)
    {
        position = trim_position(new_position)

        scrollTo(position)

        if(update_zoom)
            _setZoom(scale, true)

        if(!stop_callback && needle_plot)
        {
            needle_plot.setAAPosition(position, true)
        }
        dispatch.zoomAndMove(this)

    }

    function _getZoom()
    {
        return first_scale_factor * scale
    }

    function _setZoom(new_zoom, stop_callback)
    {
        return _setZoomAndMove(new_zoom, _getAAPosition(), stop_callback, true)
    }

    function trim_position(new_position, new_zoom, skip_zoom_effect)
    {
        if(new_position < 0)
            return 0
        else {
            var end = config.sequenceLength

            if(!skip_zoom_effect)
                end -= _visibleAminoacidsCount(new_zoom)

            if(new_position > end)
                return end
            else
                return new_position
        }
    }

    function _setZoomAndMove(new_zoom, new_pos, stop_callback, skip_animation, center_view)
    {
        new_pos = trim_position(new_pos, new_zoom)

        var styles = scalableElem.style;

        var invoke_callback = (!stop_callback && needle_plot)

        var initial_scale = scale

        var step = function(now, fx)
        {
            if(fx.prop === 'p')
            {
                if(center_view)
                    return

                position = now
                scrollTo(position)
                return
            }

            scale = now
            config.char_size = getCharSize()

            if(center_view)
            {
                // as I want the chosen aminoacid to remain in place when zooming:
                // central = first_visible + visible_count / 2
                // I use the central before zoom and central after zoom to find
                // the first_visible_after_zoom which satisfies condition: central=const
                // new_pos is the first visible aminoacid to be targeted,
                // position is the first visible aminoacid to be shown in this step
                position = new_pos + _visibleAminoacidsCount() / 2 * (1 - initial_scale / scale)
            }

            styles.transform = 'scaleX(' +  first_scale_factor * scale + ')'
            scrollTo(position)

            if(invoke_callback)
                needle_plot.setZoomAndAAPosition(scale, position, true)

        }

        is_animation_running = true
        $({s: scale, p: position})
            .animate(
                {s: new_zoom, p: new_pos},
                {
                    duration: skip_animation ? 0 : config.zoom_speed,
                    ease: config.animations_ease,
                    queue: false,
                    step: step,
                    complete: function() {
                        dispatch.zoomAndMove(this)
                        is_animation_running = false
                    }
                }
            )

    }

    function zoomKeepingCentral(direction)
    {
        var zoom = calculate_zoom(direction)
        var first_visible_aa = _getAAPosition()
        _setZoomAndMove(zoom, first_visible_aa, false, false, true)
    }

    function zoomIn()
    {
        zoomKeepingCentral(+config.zoom_step)
    }

    function zoomOut()
    {
        zoomKeepingCentral(-config.zoom_step)
    }

    function _getAAPosition()
    {
        return position
    }

    function _visibleAminoacidsCount(scale_to_use)
    {
        return config.min_zoom * config.sequenceLength / (scale_to_use ? scale_to_use : scale)
    }

    /**
     * Move by one screen in given direction
     * @param direction - move one screen right if +1, one screen left if -1
     * @param {boolean} animate - should the scrolling be animated?
     */
    function scroll(direction, animate)
    {
        var new_pos_screen = _getAAPosition() + _visibleAminoacidsCount() * direction
        new_pos_screen = trim_position(new_pos_screen)

        if(new_pos_screen === position)
            return

        is_animation_running = true
        $({screen_position: position})
            .animate(
                {screen_position: new_pos_screen},
                {
                    duration: animate ? config.animations_speed : 0,
                    ease: config.animations_ease,
                    queue: false,
                    step: function(now)
                    {
                        position = now
                        scrollTo(now)
                        needle_plot.setAAPosition(now, true)
                        dispatch.zoomAndMove(this)
                    },
                    complete: function() {
                        is_animation_running = false
                    }
                }
            )
    }

    function scrollLeft()
    {
        scroll(-config.scroll_step, true)
    }

    function scrollRight()
    {
        scroll(+config.scroll_step, true)
    }

    function computeBaseCharSize()
    {
        sequence_elements.wrapInner('');
        var span = sequence_elements.children('span');
        var charSize = span.innerWidth() / config.sequenceLength;
        span.contents().unwrap();
        return charSize;
    }

    function getCharSize()
    {
        return baseCharSize * _getZoom();
    }

    function getSequenceLength()
    {
        var seq = $.trim(sequence_elements.text())
        return seq.length
    }

    function scrollTo(new_position)
    {
        scrollArea.scrollLeft(new_position * config.char_size)
    }

    function parsePosition(position_string)
    {
        if(position_string === undefined)
            return undefined
        return trim_position(parseInt(position_string, 10), undefined, true)
    }

    function scrollToCallback()
    {
        var input = $(this).closest('.input-group').find('.scroll-to-input')

        // - 1: sequence is 1 based but position is 0 based
        var user_input = $(input).val()

        var pos
        var zoom

        var correct_expression = /^\s*(\d+)\s*([-|:]\s*(\d+))?\s*$/

        var matched_range = user_input.match(correct_expression)
        var feedback = $('.scroll-feedback')

        function warn() {
            feedback.html('')
        }

        if(matched_range)
        {
            // get range
            var range = [matched_range[1], matched_range[3]]

            // convert to int, trim if the values exceed protein length or 0
            range = range.map(parsePosition)

            // make first coordinate 0-based
            pos = range[0] - 1

            // if we have both parts of expression (two numbers)
            if(range[1] !== undefined)
            {
                var len = range[1] - range[0]

                // if user provides range with the second value being smaller,
                // it may be good to warn the user; moreover we do not want to
                // divide by zero.
                if(len < 1)
                    return warn()

                // calculate zoom such that we will see only the desired range
                zoom = config.min_zoom * config.sequenceLength / len

                // and trim it to max allowed zoom
                zoom = Math.min(zoom, config.max_zoom)
            }
            else
            {
                // zoom in as close to the mutation as we can get
                zoom = config.max_zoom

                // move the position to the center
                pos -= _visibleAminoacidsCount(zoom) / 2
            }
            // clear the feedback (the empty string is important!)
            feedback.html('')
        }
        else
        {
            // incorrect input
            warn()
        }

        // zoom and move: run callback, animate, do not attempt to center on given position
        _setZoomAndMove(zoom, pos, false, false, false)
    }

    function initButtons(buttons, func, context)
    {
        for(var i = 0; i < buttons.length; i++)
        {
            var button = $(buttons[i])
            if(context)
            {
                button.click($.proxy(func, context))
            }
            else
            {
                button.click(func)
            }
        }
    }

    function initFields(fields, func)
    {
        function call(e)
        {
            if(e.keyCode === 13)
            {
                func.call(this)
            }
        }

        for(var i = 0; i < fields.length; i++)
        {
            $(fields[i]).keyup(call)
        }
    }

    function initControls()
    {
        var buttons = controls.find('.scroll-left')
        initButtons(buttons, scrollLeft, scrollArea)

        buttons = controls.find('.scroll-right')
        initButtons(buttons, scrollRight, scrollArea)

        var innerDiv = box.children('.inner')

        buttons = controls.find('.zoom-out')
        initButtons(buttons, zoomOut, innerDiv)

        buttons = controls.find('.zoom-in')
        initButtons(buttons, zoomIn, innerDiv)

        buttons = controls.find('.scroll-to')
        initButtons(buttons, scrollToCallback)

        buttons = controls.find('.scroll-to-input')
        initFields(buttons, scrollToCallback)
    }

    function onManualScroll(event)
    {
        var scroll = $(event.target).scrollLeft()

        if(!is_animation_running)
        {
            _setAAPosition(scroll / config.char_size, false)
        }
    }

    var publicSpace = {
        init: function(new_config)
        {
            configure(new_config)

            box = $(config.box)
            tracks = box.find('.tracks')

            scrollArea = tracks.find('.scroll-area')
            scrollArea.on('scroll', onManualScroll)
            scalableArea = tracks.find('.scalable')
            scalableElem = scalableArea.get(0)

            var sequence = tracks.find('.sequence')
            sequence_elements = sequence.children('.elements')

            config.sequenceLength = getSequenceLength()
            config.char_size = getCharSize()

            controls = $($.find('.tracks-controls'))

            initControls()

            publicSpace.refreshScaleFactor()

            _setZoom(1)

            // initialize all popovers on tracks
            var tooltip = Tooltip()
            tooltip.init({
                id: 'tracks',
                template: function(data){
                    var elem = $(this)
                    var templated = (
                        '
' + elem.data('title') + '
' +
                        elem.data('content')
                    )
                    return templated
                },
                viewport: scrollArea.get(0)
            })

            var kinases = d3.selectAll('.has-tooltip')
                .each(function(data){
                    // if we add tooltips, let's remove original title tooltip
                    // and reuse this data in the new tooltip
                    $(this).data('title', this.title)
                    this.title = ''
                })
                .call(tooltip.bind)

            dispatch.on('zoomAndMove', function(){
                tooltip.moveToElement()
            })

            $('.subtracks_collapsed').click(function(){
                var track_name = $(this).data('track');
                $('.' + track_name + ' .collapsible').toggleClass('hidden')
            })

            publicSpace.show()

            is_ready = true;
        },
        setNeedlePlotInstance: function(instance)
        {
            needle_plot = instance
        },
        setZoom: _setZoom,
        setAAPosition: _setAAPosition,
        adjustMaxZoom: function()
        {
            // optimal max zoom is a zoom which allows to zoom in to a normal size of a character
            config.max_zoom = 1 / first_scale_factor
            return config.max_zoom
        },
        setZoomAndMove: _setZoomAndMove,
        refreshScaleFactor: function()
        {
            first_scale_factor = get_scale_factor();
            baseCharSize = computeBaseCharSize();
        },
        isReady: function()
        {
            return is_ready;
        },
        hide: function()
        {
            box.find('.tracks-box').addClass('invisible');
            controls.addClass('invisible');

            if(config.animations_speed)
                box.animate({height: 0}, config.animations_speed);
        },
        show: function()
        {
            var internal_box = box.find('.tracks-box');
            internal_box.removeClass('invisible');
            controls.removeClass('invisible');

            if(config.animations_speed)
                box.animate({height: internal_box.height()}, config.animations_speed);
        }

    }

    return publicSpace
}
<|endoftext|>


<commit_before>// GENERAL SETTINGS

var debug = true;
var bombsite = 'B';
var roundLength = 1.5 * 60;
var bombTimer = 30;
var codeLength = 4; // chars
var armingSteps = 3;
var disarmingSteps = 2;

// Game variables
var armed, disarmed;
var armingProgress, disarmingProgress;
var locked = true;
var winner = NaN;
var gameOver = false;

// Start round
function initGame(site) {
    bombsite = site;

    // Set bombsite characters
    $('.sitename').text(site);

    // Set main image
    $('body').removeClass('site-a site-b');
    $('body').addClass('site-' + site.toLowerCase());

    // Load sounds
    initSounds(site);
}

function startGame() {
    winner = NaN;
    gameOver = false;
    clearOverlay();
    bombProgress(0, 'neutral');
    $('#timer-text').text('ROUND TIMER');

    // Start clock
    maxTime = roundLength;
    timerValue = maxTime + 1;
    startTimer();

    armed = false;
    disarmed = false;
    armingProgress = 0;
    disarmingProgress = 0;

    // Lock keypad
    lock();

    // Start music
    play('wind');
}

function keypadFinished() {
    if (!armed) {
        // Arming
        armingProgress += 1;
        progress = armingProgress / armingSteps;
        if (progress > 0.99) {
            bombProgress(progress, 'armed');
            armingComplete();
        } else {
            bombProgress(progress, 'arming');
            delayLock();
        }
    } else {
        // Disarming
        disarmingProgress += 1;
        progress = disarmingProgress / disarmingSteps;
        if (progress > 0.99) {
            bombProgress(progress, 'disarmed');
            disarmingComplete();
        } else {
            bombProgress(progress, 'disarming');
            delayLock();
        }
    }
}

function armingComplete() {
    console.log('Arming complete');
    delayLock();
    armed = true;

    // Set timer to bomb timer
    $('#timer-text').text('BOMB TIMER');
    maxTime = bombTimer;
    timerValue = maxTime + 1;

    // TODO Send "Bomb has been planted" messages
    play('planted');
}

function disarmingComplete() {
    console.log('Disarming complete');
    play('defused');

    winner = 'CT';
    endGame();
}

function kaboom() {
    console.log('Kaboom');

    bombProgress(1, 'exploded');
    play('explosion');

    play('win-ct');
    winner = 'TR';
    endGame();
}

function timerReached() {
    console.log('Timer reached');
    winner = 'CT';
    play('win-ct');

    endGame();
}

function endGame() {
    stopTimer();
    locked = true;

    showOverlay(winner + ' won', '');
    stop('wind');
    play('background');
    gameOver = true;
}

// Timer
var maxTime = roundLength;
var timerValue = maxTime + 1;
var beepTimer;
var odd = true;

function startTimer() {
    beepTimer = setInterval(timer, 500);
    odd = false;
    timer();
}

function stopTimer() {
    clearInterval(beepTimer);
}

function timer() {
    odd = !odd;

    if (odd) {
        // Every second time do not run full timer.
        if (armed && timerValue <= 10)
            play('beep');
        return;
    }
    else {
        if (armed) play('beep');
    }

    // Change time
    timerValue -= 1;
    advanceClock();

    // Kaboom
    if (timerValue == 0) {
        if (armed)
            kaboom();
        else
            timerReached();
        clearInterval(beepTimer);
    }

    // Blinking LED
    if (armed) {
        // Show LED
        $('#led').removeClass('hidden');
        // Hide LED
        window.setTimeout(function() {
            $('#led').addClass('hidden');
        }, 200);
    }
}

function advanceClock() {
    // Set countdown clock
    var minutes = Math.floor(timerValue / 60);
    var seconds = timerValue - (minutes * 60);
    if (seconds < 10) seconds = '0' + seconds;
    $('#timer-minutes').text(minutes);
    $('#timer-seconds').text(seconds);
    // Set progress bar
    var width = 100 * ((maxTime - timerValue) / maxTime);
    $('#progressbar').css('width', width + '%');
}

var bombTexts = {
    'neutral': 'Not armed',
    'arming': 'Arming',
    'armed': 'Armed',
    'disarming': 'Disarming',
    'disarmed': 'Disarmed',
    'exploded': 'Exploded'
}

// Bomb bar
function bombProgress(progress, state) {
    // Act on completed stage.
    $('#bomb-progress-container').removeClass('neutral arming armed disarming disarmed');
    $('#bomb-progress-container').addClass(state);
    $('#message').text(bombTexts[state]);
    $('#bomb-progress').css('width', (progress * 100) + '%');
}

// Overlay messages
function showOverlay(message) {
    $('#overlay-message').text(message);
    $('#overlay').removeClass('notvisible');
}

function clearOverlay() {
    $('#overlay').addClass('notvisible');
}

// Keypad
var code = '';
var guessed = '';
var next = '';

function initCodepad(len) {
    code = randomCode(len);
    guessed = '';
    next = nextNumber();
    highlight(next);
    var d = '';
    for (var i = 0; i < len; i++) d += '-';
    $('#display').text(d);
    $('#display').removeClass('correct');
}

function randomCode(len) {
    var min = 10 ** (len - 1);
    var max = 10 ** len - 1;
    return '' + Math.floor(Math.random()*(max-min+1)+min);
}

function nextNumber() {
    for (var i = 0; i < code.length; i++) {
        if (i == guessed.length) {
            return code[i];
        }
    }
}

function highlight(number) {
    // First remove highlight
    $('.cp').removeClass('highlight');

    // Wait a little and mark a new key
    window.setTimeout(function() {
        $('#code'+number).addClass('highlight');
    }, 500);
}

function numberPressed(number) {
    if (locked) {
        flashLock();
        play('beep');
        return;
    }
    if (!armed) {
        progress = armingProgress / armingSteps;
        bombProgress(progress, 'arming');
    }
    if (armed) {
        progress = disarmingProgress / disarmingSteps;
        bombProgress(progress, 'disarming');
    }

    if (number == next) {
        guessed += number;

        // Set display
        var d = guessed;
        for (var i = d.length; i < code.length; i++) d += '-';
        $('#display').text(d);

        // Get next number
        next = nextNumber();
        highlight(next);

        if (guessed == code) {
            // Guessed full code
            d += '-OK'
            $('#display').text(d);
            $('#display').addClass('correct');

            keypadFinished();
            play('done');
        }
        else {
            play('press');
        }
    } else {
        // Wrong keypress
        play('beep');

        // Display ERROR
        $('#display').addClass('wrong');
        $('#display').text('YOLO');

        // Shake wrong key
        locked = true;
        $('#code' + number).addClass('shake');
        window.setTimeout(function () {
            $('#code' + number).removeClass('shake');
            $('#display').removeClass('wrong');
            // Make lock visible
            lock();
        }, 900);
    }
}

function lock() {
    // TODO get right QR code and regular code
    //
    locked = true;
    $('#lock').removeClass('notvisible');
}

function delayLock() {
    window.setTimeout(lock, 1000);
}

function unlock() {
    locked = false;
    initCodepad(codeLength);
    $('#lock').addClass('notvisible');

    if (armed && !disarmingProgress) bombProgress(0, 'disarming');
}

function flashLock() {
    $('#lock-image').addClass('shake');
    window.setTimeout(function () {
        $('#lock-image').removeClass('shake');
    }, 500);
}

// Sound bank
var sounds = {};

function initSounds(bombsite) {
    var folder = 'static/bombsite/'
    var suffix = bombsite + '.mp3'
    var s = sounds
    s['background'] = new Audio(folder + 'background1.mp3');
    s['beep'] = new Audio(folder + 'beep.mp3');
    s['beep'].volume = 0.15;
    s['defused'] = new Audio(folder + 'defused.mp3');
    s['done'] = new Audio(folder + 'done.mp3');
    s['done'].volume = 0.5;
    s['explosion'] = new Audio(folder + 'explosion' + suffix);
    s['planted'] = new Audio(folder + 'planted.mp3');
    s['press'] = new Audio(folder + 'press.mp3');
    s['press'].volume = 0.6;
    s['win-ct'] = new Audio(folder + 'win-ct.mp3');
    s['win-tr'] = new Audio(folder + 'win-tr.mp3');
    s['wind'] = new Audio(folder + 'wind' + suffix);
    s['wind'].loop = true;
}

function play(sound) {
    // Pause and time reset is to make sure
    // it restarts when still playing.
    sounds[sound].pause();
    sounds[sound].currentTime = 0;
    sounds[sound].play();
}

function stop(sound) {
    sounds[sound].pause();
    sounds[sound].currentTime = 0;
}

// Act on keypress
function keypress(e) {
    var key = e.which || e.keyCode;
    // Enter was pressed
    if (debug && key == 13) {
        if (gameOver) startGame();
        else if (locked) unlock();
        return;
    }

    // Ensure entered key is a number
    if(48 <= key && key <=57) {
        var number = parseInt(String.fromCharCode(key));
        numberPressed(number);
        return;
    }

    if (locked) flashLock();
    play('beep');
}

// Finally start game
window.onload = function() {
    // Setup game
    initGame(bombsite);
    startGame();

    // Register keypress listener
    window.addEventListener("keypress", keypress);
}
<commit_msg>Fixed wrong link<commit_after>// GENERAL SETTINGS

var debug = true;
var bombsite = 'B';
var roundLength = 1.5 * 60;
var bombTimer = 30;
var codeLength = 4; // chars
var armingSteps = 3;
var disarmingSteps = 2;

// Game variables
var armed, disarmed;
var armingProgress, disarmingProgress;
var locked = true;
var winner = NaN;
var gameOver = false;

// Start round
function initGame(site) {
    bombsite = site;

    // Set bombsite characters
    $('.sitename').text(site);

    // Set main image
    $('body').removeClass('site-a site-b');
    $('body').addClass('site-' + site.toLowerCase());

    // Load sounds
    initSounds(site);
}

function startGame() {
    winner = NaN;
    gameOver = false;
    clearOverlay();
    bombProgress(0, 'neutral');
    $('#timer-text').text('ROUND TIMER');

    // Start clock
    maxTime = roundLength;
    timerValue = maxTime + 1;
    startTimer();

    armed = false;
    disarmed = false;
    armingProgress = 0;
    disarmingProgress = 0;

    // Lock keypad
    lock();

    // Start music
    play('wind');
}

function keypadFinished() {
    if (!armed) {
        // Arming
        armingProgress += 1;
        progress = armingProgress / armingSteps;
        if (progress > 0.99) {
            bombProgress(progress, 'armed');
            armingComplete();
        } else {
            bombProgress(progress, 'arming');
            delayLock();
        }
    } else {
        // Disarming
        disarmingProgress += 1;
        progress = disarmingProgress / disarmingSteps;
        if (progress > 0.99) {
            bombProgress(progress, 'disarmed');
            disarmingComplete();
        } else {
            bombProgress(progress, 'disarming');
            delayLock();
        }
    }
}

function armingComplete() {
    console.log('Arming complete');
    delayLock();
    armed = true;

    // Set timer to bomb timer
    $('#timer-text').text('BOMB TIMER');
    maxTime = bombTimer;
    timerValue = maxTime + 1;

    // TODO Send "Bomb has been planted" messages
    play('planted');
}

function disarmingComplete() {
    console.log('Disarming complete');
    play('defused');

    winner = 'CT';
    endGame();
}

function kaboom() {
    console.log('Kaboom');

    bombProgress(1, 'exploded');
    play('explosion');

    play('win-ct');
    winner = 'TR';
    endGame();
}

function timerReached() {
    console.log('Timer reached');
    winner = 'CT';
    play('win-ct');

    endGame();
}

function endGame() {
    stopTimer();
    locked = true;

    showOverlay(winner + ' won', '');
    stop('wind');
    play('background');
    gameOver = true;
}

// Timer
var maxTime = roundLength;
var timerValue = maxTime + 1;
var beepTimer;
var odd = true;

function startTimer() {
    beepTimer = setInterval(timer, 500);
    odd = false;
    timer();
}

function stopTimer() {
    clearInterval(beepTimer);
}

function timer() {
    odd = !odd;

    if (odd) {
        // Every second time do not run full timer.
        if (armed && timerValue <= 10)
            play('beep');
        return;
    }
    else {
        if (armed) play('beep');
    }

    // Change time
    timerValue -= 1;
    advanceClock();

    // Kaboom
    if (timerValue == 0) {
        if (armed)
            kaboom();
        else
            timerReached();
        clearInterval(beepTimer);
    }

    // Blinking LED
    if (armed) {
        // Show LED
        $('#led').removeClass('hidden');
        // Hide LED
        window.setTimeout(function() {
            $('#led').addClass('hidden');
        }, 200);
    }
}

function advanceClock() {
    // Set countdown clock
    var minutes = Math.floor(timerValue / 60);
    var seconds = timerValue - (minutes * 60);
    if (seconds < 10) seconds = '0' + seconds;
    $('#timer-minutes').text(minutes);
    $('#timer-seconds').text(seconds);
    // Set progress bar
    var width = 100 * ((maxTime - timerValue) / maxTime);
    $('#progressbar').css('width', width + '%');
}

var bombTexts = {
    'neutral': 'Not armed',
    'arming': 'Arming',
    'armed': 'Armed',
    'disarming': 'Disarming',
    'disarmed': 'Disarmed',
    'exploded': 'Exploded'
}

// Bomb bar
function bombProgress(progress, state) {
    // Act on completed stage.
    $('#bomb-progress-container').removeClass('neutral arming armed disarming disarmed');
    $('#bomb-progress-container').addClass(state);
    $('#message').text(bombTexts[state]);
    $('#bomb-progress').css('width', (progress * 100) + '%');
}

// Overlay messages
function showOverlay(message) {
    $('#overlay-message').text(message);
    $('#overlay').removeClass('notvisible');
}

function clearOverlay() {
    $('#overlay').addClass('notvisible');
}

// Keypad
var code = '';
var guessed = '';
var next = '';

function initCodepad(len) {
    code = randomCode(len);
    guessed = '';
    next = nextNumber();
    highlight(next);
    var d = '';
    for (var i = 0; i < len; i++) d += '-';
    $('#display').text(d);
    $('#display').removeClass('correct');
}

function randomCode(len) {
    var min = 10 ** (len - 1);
    var max = 10 ** len - 1;
    return '' + Math.floor(Math.random()*(max-min+1)+min);
}

function nextNumber() {
    for (var i = 0; i < code.length; i++) {
        if (i == guessed.length) {
            return code[i];
        }
    }
}

function highlight(number) {
    // First remove highlight
    $('.cp').removeClass('highlight');

    // Wait a little and mark a new key
    window.setTimeout(function() {
        $('#code'+number).addClass('highlight');
    }, 500);
}

function numberPressed(number) {
    if (locked) {
        flashLock();
        play('beep');
        return;
    }
    if (!armed) {
        progress = armingProgress / armingSteps;
        bombProgress(progress, 'arming');
    }
    if (armed) {
        progress = disarmingProgress / disarmingSteps;
        bombProgress(progress, 'disarming');
    }

    if (number == next) {
        guessed += number;

        // Set display
        var d = guessed;
        for (var i = d.length; i < code.length; i++) d += '-';
        $('#display').text(d);

        // Get next number
        next = nextNumber();
        highlight(next);

        if (guessed == code) {
            // Guessed full code
            d += '-OK'
            $('#display').text(d);
            $('#display').addClass('correct');

            keypadFinished();
            play('done');
        }
        else {
            play('press');
        }
    } else {
        // Wrong keypress
        play('beep');

        // Display ERROR
        $('#display').addClass('wrong');
        $('#display').text('YOLO');

        // Shake wrong key
        locked = true;
        $('#code' + number).addClass('shake');
        window.setTimeout(function () {
            $('#code' + number).removeClass('shake');
            $('#display').removeClass('wrong');
            // Make lock visible
            lock();
        }, 900);
    }
}

function lock() {
    // TODO get right QR code and regular code
    //
    locked = true;
    $('#lock').removeClass('notvisible');
}

function delayLock() {
    window.setTimeout(lock, 1000);
}

function unlock() {
    locked = false;
    initCodepad(codeLength);
    $('#lock').addClass('notvisible');

    if (armed && !disarmingProgress) bombProgress(0, 'disarming');
}

function flashLock() {
    $('#lock-image').addClass('shake');
    window.setTimeout(function () {
        $('#lock-image').removeClass('shake');
    }, 500);
}

// Sound bank
var sounds = {};

function initSounds(bombsite) {
    var folder = 'static/bombsite/'
    var suffix = bombsite + '.mp3'
    var s = sounds
    s['background'] = new Audio(folder + 'background.mp3');
    s['beep'] = new Audio(folder + 'beep.mp3');
    s['beep'].volume = 0.15;
    s['defused'] = new Audio(folder + 'defused.mp3');
    s['done'] = new Audio(folder + 'done.mp3');
    s['done'].volume = 0.5;
    s['explosion'] = new Audio(folder + 'explosion' + suffix);
    s['planted'] = new Audio(folder + 'planted.mp3');
    s['press'] = new Audio(folder + 'press.mp3');
    s['press'].volume = 0.6;
    s['win-ct'] = new Audio(folder + 'win-ct.mp3');
    s['win-tr'] = new Audio(folder + 'win-tr.mp3');
    s['wind'] = new Audio(folder + 'wind' + suffix);
    s['wind'].loop = true;
}

function play(sound) {
    // Pause and time reset is to make sure
    // it restarts when still playing.
    sounds[sound].pause();
    sounds[sound].currentTime = 0;
    sounds[sound].play();
}

function stop(sound) {
    sounds[sound].pause();
    sounds[sound].currentTime = 0;
}

// Act on keypress
function keypress(e) {
    var key = e.which || e.keyCode;
    // Enter was pressed
    if (debug && key == 13) {
        if (gameOver) startGame();
        else if (locked) unlock();
        return;
    }

    // Ensure entered key is a number
    if(48 <= key && key <=57) {
        var number = parseInt(String.fromCharCode(key));
        numberPressed(number);
        return;
    }

    if (locked) flashLock();
    play('beep');
}

// Finally start game
window.onload = function() {
    // Setup game
    initGame(bombsite);
    startGame();

    // Register keypress listener
    window.addEventListener("keypress", keypress);
}
<|endoftext|>


<commit_before>describe("ModalMissionCompleteMap", function () {
    var $uiModalMissionCompleteFixture;
    var neighborhood;
    var mission;
    var map;

    // Mocks
    function MissionMock () {
        this.properties = {
            coverage: null,
            label: null,
            distance: null,
            distanceFt: null,
            distanceMi: null
        };
    }
    MissionMock.prototype.getProperty = function (key) {
        return this.properties[key];
    };

    function NeighborhoodMock() {
        this.properties = {
            name: null,
            regionId: null,
            center: null,
            geoJSON: null
        };
    }
    NeighborhoodMock.prototype.getProperty = function (key) {
        return this.properties[key];
    };
    NeighborhoodMock.prototype.center = function () {
        return this.properties.center;
    };
    NeighborhoodMock.prototype.getGeoJSON = function () {
        return this.properties.geoJSON;
    };

    function TaskContainerMock(){
        this.properties = {};
    }

    function TaskMock() {
        this.properties = {
            streetEdgeId: null,
            feature: null
        };
    }

    TaskMock.prototype.getStreetEdgeId = function () {
        return this.properties.streetEdgeId;
    };

    TaskMock.prototype.getFeature = function () {
        return this.properties.feature;
    };

    TaskMock.prototype.getGeoJSON = function () {
        var geoJSON = {
            "type":"FeatureCollection",
            "features": [this.properties.feature]
        };
        return geoJSON;
    };

    beforeEach(function () {
        $uiModalMissionCompleteFixture = $('

');

        uiModalMissionComplete = {};
        uiModalMissionComplete.map = $uiModalMissionCompleteFixture;

        this.uiModalMissionComplete = uiModalMissionComplete;

        map = new ModalMissionCompleteMap(uiModalMissionComplete);

        mission = new MissionMock();
        mission.properties.distanceMi = 0.7575;
        mission.properties.distance = 1219.2;
        mission.properties.distanceFt = 4000;
        mission.properties.coverage = 0.07575;
        mission.properties.auditDistanceFt = 2000;
        mission.properties.auditDistanceMi = 0.3788;
        mission.properties.auditDistance = 609;
        mission.properties.label = "distance-mission";
        neighborhood = new NeighborhoodMock();
        neighborhood.properties.name = "Test Neighborhood";
        neighborhood.properties.geoJSON = {
               "type":"Feature",
               "properties":{

               },
               "geometry":{
                  "type":"Polygon",
                  "coordinates":[
                     [
                        [
                           -76.9238,
                           38.9035
                        ],
                        [
                           -76.9239,
                           38.9032
                        ],
                        [
                           -76.9240,
                           38.9028
                        ]
                     ]
                  ]
               }
            };
    });

    describe("ModalMissionCompleteMap object declaration", function () {
      it("should initialize attributes", function () {
        expect(map).not.toBe(null);
        expect(map._southWest).not.toBe(null);
        expect(map._northEast).not.toBe(null);
        expect(map._bound).not.toBe(null);
        expect(map._map).not.toBe(null);
        expect(map._overlayPolygon).toBe(null);
        expect(map._overlayPolygonLayer).toBe(null);
        expect(map._ui).not.toBe(null);
      });
    });

    describe("linestringToPoint method", function(){
        it("should convert FeatureCollection of LineString to FeatureCollection of Point", function() {
          // GeoJSON FeatureCollection of LineStrings
          var c = {"type":"FeatureCollection",
                      "features":[
                      {"type":"Feature","geometry":{"type":"LineString",
                      "coordinates":[
                      [-77.041402,38.8764389],
                      [-77.059005,38.8864323],
                      [-77.063005,38.8864250],
                      [-77.063005,38.8964180],
                      [-77.069005,38.8964180]
                      ]
                      }},
                      {"type":"Feature","geometry":{"type":"LineString",
                      "coordinates":[
                      [-77.069005,38.9164120],
                      [-77.075005,38.9164120],
                      [-77.075005,38.9364080],
                      [-77.092005,38.9564080]
                      ]
                      }}
                      ]
                  };
            var p = map._linestringToPoint(c);
            expect(p.type).toBe('FeatureCollection');
            expect(p.features[0].geometry.type).toBe('Point');
        });
    });

    describe("`show` method", function () {
        it("should open a modal window", function () {
          expect(uiModalMissionComplete).not.toBe(null);
            map.hide();
            expect(map._ui.map.css('top')).toBe('500px');
            expect(map._ui.map.css('left')).toBe('-500px');

            map.show();
            expect(map._ui.map.css('top')).toBe('0px');
            expect(map._ui.map.css('left')).toBe('15px');
        });

        it("should create map overlays", function (){
            map.show();
            var leafletOverlayPane = $('.leaflet-overlay-pane');
            expect(leafletOverlayPane).not.toBe(null);
            map.hide();
        });
    });

    describe("updateStreetSegments method", function () {
        beforeEach(function (done) {
            var len;
            var completedTaskGeoJSONList = [
                  {
                     "type":"Feature",
                     "geometry":{
                        "type":"LineString",
                        "coordinates":[
                           [
                              -77.0345804,
                              38.9026435
                           ],
                           [
                              -77.15,
                              38.9026425
                           ]
                        ]
                     },
                     "properties":{
                        "street_edge_id":26392,
                        "x1":-77.03458404541016,
                        "y1":38.902645111083984,
                        "x2":-77.03369140625,
                        "y2":38.90264129638672,
                        "task_start":"2016-07-29 12:37:03.347",
                        "completed":false
                     }
                },
                {
                   "type":"Feature",
                   "geometry":{
                      "type":"LineString",
                      "coordinates":[
                         [
                            -76.956585,
                            38.893561
                         ],
                         [
                            -76.956577,
                            38.894767
                         ]
                      ]
                   },
                   "properties":{
                      "street_edge_id":1853,
                      "x1":-76.95657348632812,
                      "y1":38.89476776123047,
                      "x2":-76.95658874511719,
                      "y2":38.89356231689453,
                      "task_start":"2015-11-16 23:20:19.46",
                      "completed":false
                   }
            }];
            var missionTaskGeoJSONList = [
                {
                "type":"Feature",
               "geometry":{
                  "type":"LineString",
                  "coordinates":[
                     [
                        -76.9574863,
                        38.8965631
                     ],
                     [
                        -76.9562874,
                        38.8963878
                     ],
                     [
                        -76.9549743,
                        38.8961824
                     ],
                     [
                        -76.9527683,
                        38.8958242
                     ],
                     [
                        -76.9503329,
                        38.895415
                     ],
                     [
                        -76.94929,
                        38.8952549
                     ],
                     [
                        -76.9491136,
                        38.8951775
                     ]
                  ]
               },
               "properties":{
                  "street_edge_id":25004,
                  "x1":-76.94911193847656,
                  "y1":38.89517593383789,
                  "x2":-76.95748901367188,
                  "y2":38.89656448364258,
                  "task_start":"2015-11-16 23:20:19.46",
                  "completed":false
               }
            }
            ];
            var missionTasks = [];
            var completedTasks = [];
            len = completedTaskGeoJSONList.length;
            for(var i = 0; i < len; i ++){
                var task = new TaskMock();
                task.properties.feature = completedTaskGeoJSONList[i];
                task.properties.streetEdgeId = completedTaskGeoJSONList[i].properties.street_edge_id;
                completedTasks.push(task);
            }
            len = missionTaskGeoJSONList.length;
            for(var i = 0; i < len; i ++){
                var task = new TaskMock();
                task.properties.feature = missionTaskGeoJSONList[i];
                task.properties.streetEdgeId = missionTaskGeoJSONList[i].properties.street_edge_id;
                missionTasks.push(task);
            }
            map.show();
            map.updateStreetSegments(missionTasks, completedTasks);
            // pause for segements to render
            setTimeout(function () {done();}, 3000);
          });

        it("should visualize segments", function () {
          expect(uiModalMissionComplete.map.find(".leaflet-zoom-hide").length).not.toBe(0);
          map.hide();
        });
    });

    describe("update method", function (){
        beforeEach(function (done) {
          map.show();
          map.update(mission, neighborhood);
          // pause for map animation to run
          setTimeout(function () {done();}, 1000);
        });
        it("should render a neighborhood", function () {
            expect(map._overlayPolygonLayer).not.toBe(undefined);
            expect(map._overlayPolygonLayer._layers).not.toBe(undefined);

            var layer = map._overlayPolygonLayer._layers;
            // leaflet assigns the layer an id which can be different
            for(var key in layer){
              var layerStyles = layer[key].options;
              expect(layerStyles.fillColor).toBe('rgb(110, 110, 110)');
              expect(layerStyles.opacity).toBe(0);
              expect(layerStyles.fillOpacity).toBe(0.25);
            }
            map.hide();
        });
    });
});
<commit_msg>global variable<commit_after>describe("ModalMissionCompleteMap", function () {
    var $uiModalMissionCompleteFixture;
    var neighborhood;
    var mission;
    var map;
    var uiModalMissionComplete;

    // Mocks
    function MissionMock () {
        this.properties = {
            coverage: null,
            label: null,
            distance: null,
            distanceFt: null,
            distanceMi: null
        };
    }
    MissionMock.prototype.getProperty = function (key) {
        return this.properties[key];
    };

    function NeighborhoodMock() {
        this.properties = {
            name: null,
            regionId: null,
            center: null,
            geoJSON: null
        };
    }
    NeighborhoodMock.prototype.getProperty = function (key) {
        return this.properties[key];
    };
    NeighborhoodMock.prototype.center = function () {
        return this.properties.center;
    };
    NeighborhoodMock.prototype.getGeoJSON = function () {
        return this.properties.geoJSON;
    };

    function TaskContainerMock(){
        this.properties = {};
    }

    function TaskMock() {
        this.properties = {
            streetEdgeId: null,
            feature: null
        };
    }

    TaskMock.prototype.getStreetEdgeId = function () {
        return this.properties.streetEdgeId;
    };

    TaskMock.prototype.getFeature = function () {
        return this.properties.feature;
    };

    TaskMock.prototype.getGeoJSON = function () {
        var geoJSON = {
            "type":"FeatureCollection",
            "features": [this.properties.feature]
        };
        return geoJSON;
    };

    beforeEach(function () {
        $uiModalMissionCompleteFixture = $('

');

        uiModalMissionComplete = {};
        uiModalMissionComplete.map = $uiModalMissionCompleteFixture;

        this.uiModalMissionComplete = uiModalMissionComplete;

        map = new ModalMissionCompleteMap(uiModalMissionComplete);

        mission = new MissionMock();
        mission.properties.distanceMi = 0.7575;
        mission.properties.distance = 1219.2;
        mission.properties.distanceFt = 4000;
        mission.properties.coverage = 0.07575;
        mission.properties.auditDistanceFt = 2000;
        mission.properties.auditDistanceMi = 0.3788;
        mission.properties.auditDistance = 609;
        mission.properties.label = "distance-mission";
        neighborhood = new NeighborhoodMock();
        neighborhood.properties.name = "Test Neighborhood";
        neighborhood.properties.geoJSON = {
               "type":"Feature",
               "properties":{

               },
               "geometry":{
                  "type":"Polygon",
                  "coordinates":[
                     [
                        [
                           -76.9238,
                           38.9035
                        ],
                        [
                           -76.9239,
                           38.9032
                        ],
                        [
                           -76.9240,
                           38.9028
                        ]
                     ]
                  ]
               }
            };
    });

    describe("ModalMissionCompleteMap object declaration", function () {
      it("should initialize attributes", function () {
        expect(map).not.toBe(null);
        expect(map._southWest).not.toBe(null);
        expect(map._northEast).not.toBe(null);
        expect(map._bound).not.toBe(null);
        expect(map._map).not.toBe(null);
        expect(map._overlayPolygon).toBe(null);
        expect(map._overlayPolygonLayer).toBe(null);
        expect(map._ui).not.toBe(null);
      });
    });

    describe("linestringToPoint method", function(){
        it("should convert FeatureCollection of LineString to FeatureCollection of Point", function() {
          // GeoJSON FeatureCollection of LineStrings
          var c = {"type":"FeatureCollection",
                      "features":[
                      {"type":"Feature","geometry":{"type":"LineString",
                      "coordinates":[
                      [-77.041402,38.8764389],
                      [-77.059005,38.8864323],
                      [-77.063005,38.8864250],
                      [-77.063005,38.8964180],
                      [-77.069005,38.8964180]
                      ]
                      }},
                      {"type":"Feature","geometry":{"type":"LineString",
                      "coordinates":[
                      [-77.069005,38.9164120],
                      [-77.075005,38.9164120],
                      [-77.075005,38.9364080],
                      [-77.092005,38.9564080]
                      ]
                      }}
                      ]
                  };
            var p = map._linestringToPoint(c);
            expect(p.type).toBe('FeatureCollection');
            expect(p.features[0].geometry.type).toBe('Point');
        });
    });

    describe("`show` method", function () {
        it("should open a modal window", function () {
          expect(uiModalMissionComplete).not.toBe(null);
            map.hide();
            expect(map._ui.map.css('top')).toBe('500px');
            expect(map._ui.map.css('left')).toBe('-500px');

            map.show();
            expect(map._ui.map.css('top')).toBe('0px');
            expect(map._ui.map.css('left')).toBe('15px');
        });

        it("should create map overlays", function (){
            map.show();
            var leafletOverlayPane = $('.leaflet-overlay-pane');
            expect(leafletOverlayPane).not.toBe(null);
            map.hide();
        });
    });

    describe("updateStreetSegments method", function () {
        beforeEach(function (done) {
            var len;
            var completedTaskGeoJSONList = [
                  {
                     "type":"Feature",
                     "geometry":{
                        "type":"LineString",
                        "coordinates":[
                           [
                              -77.0345804,
                              38.9026435
                           ],
                           [
                              -77.15,
                              38.9026425
                           ]
                        ]
                     },
                     "properties":{
                        "street_edge_id":26392,
                        "x1":-77.03458404541016,
                        "y1":38.902645111083984,
                        "x2":-77.03369140625,
                        "y2":38.90264129638672,
                        "task_start":"2016-07-29 12:37:03.347",
                        "completed":false
                     }
                },
                {
                   "type":"Feature",
                   "geometry":{
                      "type":"LineString",
                      "coordinates":[
                         [
                            -76.956585,
                            38.893561
                         ],
                         [
                            -76.956577,
                            38.894767
                         ]
                      ]
                   },
                   "properties":{
                      "street_edge_id":1853,
                      "x1":-76.95657348632812,
                      "y1":38.89476776123047,
                      "x2":-76.95658874511719,
                      "y2":38.89356231689453,
                      "task_start":"2015-11-16 23:20:19.46",
                      "completed":false
                   }
            }];
            var missionTaskGeoJSONList = [
                {
                "type":"Feature",
               "geometry":{
                  "type":"LineString",
                  "coordinates":[
                     [
                        -76.9574863,
                        38.8965631
                     ],
                     [
                        -76.9562874,
                        38.8963878
                     ],
                     [
                        -76.9549743,
                        38.8961824
                     ],
                     [
                        -76.9527683,
                        38.8958242
                     ],
                     [
                        -76.9503329,
                        38.895415
                     ],
                     [
                        -76.94929,
                        38.8952549
                     ],
                     [
                        -76.9491136,
                        38.8951775
                     ]
                  ]
               },
               "properties":{
                  "street_edge_id":25004,
                  "x1":-76.94911193847656,
                  "y1":38.89517593383789,
                  "x2":-76.95748901367188,
                  "y2":38.89656448364258,
                  "task_start":"2015-11-16 23:20:19.46",
                  "completed":false
               }
            }
            ];
            var missionTasks = [];
            var completedTasks = [];
            len = completedTaskGeoJSONList.length;
            for(var i = 0; i < len; i ++){
                var task = new TaskMock();
                task.properties.feature = completedTaskGeoJSONList[i];
                task.properties.streetEdgeId = completedTaskGeoJSONList[i].properties.street_edge_id;
                completedTasks.push(task);
            }
            len = missionTaskGeoJSONList.length;
            for(var i = 0; i < len; i ++){
                var task = new TaskMock();
                task.properties.feature = missionTaskGeoJSONList[i];
                task.properties.streetEdgeId = missionTaskGeoJSONList[i].properties.street_edge_id;
                missionTasks.push(task);
            }
            map.show();
            map.updateStreetSegments(missionTasks, completedTasks);
            // pause for segements to render
            setTimeout(function () {done();}, 3000);
          });

        it("should visualize segments", function () {
          expect(uiModalMissionComplete.map.find(".leaflet-zoom-hide").length).not.toBe(0);
          map.hide();
        });
    });

    describe("update method", function (){
        beforeEach(function (done) {
          map.show();
          map.update(mission, neighborhood);
          // pause for map animation to run
          setTimeout(function () {done();}, 1000);
        });
        it("should render a neighborhood", function () {
            expect(map._overlayPolygonLayer).not.toBe(undefined);
            expect(map._overlayPolygonLayer._layers).not.toBe(undefined);

            var layer = map._overlayPolygonLayer._layers;
            // leaflet assigns the layer an id which can be different
            for(var key in layer){
              var layerStyles = layer[key].options;
              expect(layerStyles.fillColor).toBe('rgb(110, 110, 110)');
              expect(layerStyles.opacity).toBe(0);
              expect(layerStyles.fillOpacity).toBe(0.25);
            }
            map.hide();
        });
    });
});
<|endoftext|>


<commit_before>// @flow

import React from "react";
import injectSheet from "react-jss";

type Props = {
  classes: any,
  title: string,
  children: any
};

const Window = ({ classes, title, children }: Props) => (

{title}

      {children}

);

const styles = {
  container: {
    padding: "1em",
    flex: "1",
    display: "flex",
    flexDirection: "column",
    position: "relative"
  },
  overlay: {
    background: "#ffffff",
    flex: "1",
    position: "relative",
    padding: "1em",
    boxShadow: "0px 0px 15px 0px #799cb7",
    borderRadius: "2px",
    overflow: "hidden",
    display: "flex",
    flexDirection: "column"
  },
  title: {
    fontSize: "1em",
    margin: "-1em -1em 1em -1em",
    padding: "1em",
    color: "#f9fbfc",
    background: "#5c6477",
    fontWeight: "normal",
    boxShadow: "0px 0px 15px 0px #799cb7"
  }
};

export default injectSheet(styles)(Window);
<commit_msg>Fix style for execution window (#40)<commit_after>// @flow

import React from "react";
import injectSheet from "react-jss";

type Props = {
  classes: any,
  title: string,
  children: any
};

const Window = ({ classes, title, children }: Props) => (

{title}

      {children}

);

const styles = {
  container: {
    position: "fixed",
    top: "0",
    left: "0",
    right: "0",
    bottom: "0",
    padding: "2em",
    display: "flex",
    flexDirection: "column",
    zIndex: "99999",
    background: "rgba(53, 57, 68, 0.95)"
  },
  overlay: {
    background: "#ffffff",
    flex: "1",
    position: "relative",
    padding: "1em",
    boxShadow: "0px 0px 15px 0px #12202b",
    borderRadius: "2px",
    overflow: "hidden",
    display: "flex",
    flexDirection: "column"
  },
  title: {
    fontSize: "1em",
    margin: "-1em -1em 1em -1em",
    padding: "1em",
    color: "#f9fbfc",
    background: "#5c6477",
    fontWeight: "normal",
    boxShadow: "0px 0px 15px 0px #799cb7"
  }
};

export default injectSheet(styles)(Window);
<|endoftext|>


<commit_before>/**
 * @fileOverview Main backend js file
 */

var express = require('express');
var path = require('path');
var bodyParser = require('body-parser');

var parse = require('./parse');

var app = express();

// middleware for JSON POST body data
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({
  extended: true
}));

app.set('port', (process.env.PORT || 5000));

app.get('/', function(req, res) {
  res.render('index');
});

app.post('/parse', function(req, res) {
  res.send(req.body.dataString);
  console.log(req.body);
  // parse dataString and return JSON obj
});

var server = app.listen(app.get('port'), function() {
  console.log('App listening at http://localhost:%s/', app.get('port'));
});

app.set('views', path.join(__dirname, '../front/views'));
app.set('view engine', 'jade');

app.use(express.static('front/static'));
<commit_msg>call parse function on main app file<commit_after>/**
 * @fileOverview Main backend js file
 */

var express = require('express');
var path = require('path');
var bodyParser = require('body-parser');

var parse = require('./parse');

var app = express();

// middleware for JSON POST body data
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({
  extended: true
}));

app.set('port', (process.env.PORT || 5000));

app.get('/', function(req, res) {
  res.render('index');
});

app.post('/parse', function(req, res) {
  res.send(req.body.dataString);
  console.log(req.body);
  parse.lpParser(req.body.dataString);
});

var server = app.listen(app.get('port'), function() {
  console.log('App listening at http://localhost:%s/', app.get('port'));
});

app.set('views', path.join(__dirname, '../front/views'));
app.set('view engine', 'jade');

app.use(express.static('front/static'));
<|endoftext|>


<commit_before>/**
 * @license
 * Copyright 2015 The Lovefield Project 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.
 */
goog.setTestOnly();
goog.require('goog.Promise');
goog.require('goog.testing.TestCase');
goog.require('goog.testing.jsunit');
goog.require('goog.testing.recordFunction');
goog.require('lf.Global');
goog.require('lf.backstore.Firebase');
goog.require('lf.backstore.FirebaseRawBackStore');
goog.require('lf.cache.DefaultCache');
goog.require('lf.index.MemoryIndexStore');
goog.require('lf.service');
goog.require('lf.testing.backstore.MockSchema');

/** @type {!Firebase} */
var fb;

/** @type {!lf.Global} */
var global;

/** @type {!lf.index.MemoryIndexStore} */
var indexStore;

/** @type {!lf.cache.Cache} */
var cache;

/** @type {!lf.schema.Database} */
var schema;

/** @type {boolean} */
var manualMode;

/** @const {!Object} */
var CONTENTS = {'id': 'hello', 'name': 'world'};

/** @const {!Object} */
var CONTENTS2 = {'id': 'hello2', 'name': 'world2'};

// TODO(arthurhsu): remove this workaround once Closure fixes the setUpPage bug.
/** @type {!IThenable} */
var whenReady;

/** @return {!IThenable<!Firebase>} */
function getFirebaseRef() {
  var resolver = goog.Promise.withResolver();

  var ref = new Firebase(window['FIREBASE_URL']);
  ref.authWithCustomToken(window['FIREBASE_TOKEN'], function(err, authData) {
    if (err) {
      resolver.reject(err);
    } else {
      resolver.resolve(ref);
    }
  });

  return resolver.promise;
}

function setUpPage() {
  manualMode = window['MANUAL_MODE'] || false;
  if (!manualMode) {
    return;
  }

  // Firebase WebSocket timeout is 30 seconds, so make sure this timeout is
  // greater than that value, otherwise it can flake.
  goog.testing.TestCase.getActiveTestCase().promiseTimeout = 40 * 1000;  // 40s

  whenReady = getFirebaseRef().then(function(ref) {
    fb = ref;
  });
}

function setUp() {
  if (!manualMode) {
    return;
  }

  indexStore = new lf.index.MemoryIndexStore();
  schema = new lf.testing.backstore.MockSchema();
  schema.setName('msfr' + Date.now().toString() +
      Math.floor(Math.random() * 1000).toString());
  cache = new lf.cache.DefaultCache(schema);

  global = lf.Global.get();
  global.registerService(lf.service.CACHE, cache);
  global.registerService(lf.service.INDEX_STORE, indexStore);
  global.registerService(lf.service.SCHEMA, schema);
  return whenReady;
}

function tearDown() {
  if (!manualMode) {
    return;
  }

  fb.child(schema.name()).remove();
}

// Tests that onUpgrade function is still called with version 0 for a new DB
// instance.
function testNewDBInstance() {
  if (!manualMode) {
    return;
  }

  /** @type {!goog.testing.recordFunction.Type} */
  var onUpgrade = goog.testing.recordFunction(function(rawDb) {
    assertEquals(0, rawDb.getVersion());
    return goog.Promise.resolve();
  });

  var db = new lf.backstore.Firebase(schema, fb);
  // cast onUpgrade because it's now typed as goog.testing.recordFunction.Type
  return db
      .init(/** @type {function(!lf.raw.BackStore): !IThenable} */ (onUpgrade))
      .then(function() {
        onUpgrade.assertCallCount(1);
      });
}

// Tests new tables are created automatically
function testAddTable() {
  if (!manualMode) {
    return;
  }

  var db = new lf.backstore.Firebase(schema, fb);
  var validate = function(tables) {
    var tableNames = schema.tables().map(function(table) {
      return table.getName();
    }).sort();
    assertArrayEquals(tableNames, Object.keys(tables).sort());
  };
  var numTables = 0;

  return db.init().then(function() {
    return lf.backstore.FirebaseRawBackStore.getValue(db.getRef(), '@table');
  }).then(function(tables) {
    numTables = Object.keys(tables).length;
    assertTrue(numTables != 0);
    validate(tables);
    db.close();
    schema.setVersion(2);
    db = new lf.backstore.Firebase(schema, fb);
    return db.init();
  }).then(function() {
    return lf.backstore.FirebaseRawBackStore.getValue(db.getRef(), '@table');
  }).then(function(tables) {
    assertEquals(numTables + 1, Object.keys(tables).length);
    validate(tables);
  });
}

function testDump() {
  if (!manualMode) {
    return;
  }

  var dump;
  var onUpgrade = function(rawDb) {
    assertEquals(1, rawDb.getVersion());
    return rawDb.dump().then(function(contents) {
      dump = contents;
    });
  };

  var db = new lf.backstore.Firebase(schema, fb);
  return db.init().then(function() {
    var row1 = { 'R': 1, 'T': 0, 'P': CONTENTS };
    var row2 = { 'R': 1, 'T': 1, 'P': CONTENTS2 };
    return goog.Promise.all([
      lf.backstore.FirebaseRawBackStore.setValue(db.getRef().child(1), row1),
      lf.backstore.FirebaseRawBackStore.setValue(db.getRef().child(2), row2)
    ]);
  }).then(function() {
    db.close();
    schema.setVersion(2);
    db = new lf.backstore.Firebase(schema, fb);
    return db.init(onUpgrade);
  }).then(function() {
    assertObjectEquals(dump['tableA'][0], CONTENTS);
    assertObjectEquals(dump['tableB'][0], CONTENTS2);
  });
}

/** @return {IThenable<!lf.backstore.Firebase>} */
function commonSetUp() {
  var db = new lf.backstore.Firebase(schema, fb);
  return db.init().then(function() {
    var row1 = { 'R': 1, 'T': 0, 'P': CONTENTS };
    var row2 = { 'R': 1, 'T': 0, 'P': CONTENTS2 };
    return goog.Promise.all([
      lf.backstore.FirebaseRawBackStore.setValue(db.getRef().child('1'), row1),
      lf.backstore.FirebaseRawBackStore.setValue(db.getRef().child('2'), row2)
    ]);
  }).then(function() {
    return db;
  });
}

/**
 * @param {!lf.backstore.Firebase} db
 * @param {!function(number, !Object)} verify
 * @return {IThenable}
 */
function commonVerify(db, verify) {
  return goog.Promise.all([
    lf.backstore.FirebaseRawBackStore.getValue(db.getRef(), '1'),
    lf.backstore.FirebaseRawBackStore.getValue(db.getRef(), '2')
  ]).then(function(results) {
    results.forEach(function(result, index) {
      verify(index, result);
    });
  });
}

function testDropTable() {
  if (!manualMode) {
    return;
  }

  var onUpgrade = function(rawDb) {
    assertEquals(1, rawDb.getVersion());
    return rawDb.dropTable('tableA');
  };

  var db;
  return commonSetUp().then(function(instance) {
    db = instance;
    db.close();
    schema.setVersion(2);
    schema.setDropTableA(true);
    db = new lf.backstore.Firebase(schema, fb);
    return db.init(onUpgrade);
  }).then(function() {
    db.getRef().orderByKey().once('value', function(snapshot) {
      assertFalse(snapshot.hasChild('1'));
      assertFalse(snapshot.hasChild('2'));
      assertTrue(snapshot.hasChild('@table'));
      assertFalse(snapshot.child('@table').hasChild('tableA'));
    });
  });
}

function testAddTableColumn() {
  if (!manualMode) {
    return;
  }

  var onUpgrade = function(rawDb) {
    assertEquals(1, rawDb.getVersion());
    return rawDb.addTableColumn('tableA', 'foo', 23);
  };

  var db;
  /**
   * @param {number} index
   * @param {!Object} row
   */
  var verify = function(index, row) {
    var expected = (index == 0) ? CONTENTS : CONTENTS2;
    assertEquals(2, row['R']);
    assertEquals(0, row['T']);
    assertEquals(23, row['P']['foo']);
    assertEquals(expected['id'], row['P']['id']);
    assertEquals(expected['name'], row['P']['name']);
  };

  return commonSetUp().then(function(instance) {
    db = instance;
    db.close();
    schema.setVersion(2);
    db = new lf.backstore.Firebase(schema, fb);
    return db.init(onUpgrade);
  }).then(function() {
    return commonVerify(db, verify);
  });
}

function testDropTableColumn() {
  if (!manualMode) {
    return;
  }

  var onUpgrade = function(rawDb) {
    assertEquals(1, rawDb.getVersion());
    return rawDb.dropTableColumn('tableA', 'name');
  };

  var db;
  /**
   * @param {number} index
   * @param {!Object} row
   */
  var verify = function(index, row) {
    var expected = (index == 0) ? CONTENTS : CONTENTS2;
    assertEquals(2, row['R']);
    assertEquals(0, row['T']);
    assertEquals(expected['id'], row['P']['id']);
    assertEquals(undefined, row['P']['name']);
  };

  return commonSetUp().then(function(instance) {
    db = instance;
    db.close();
    schema.setVersion(2);
    db = new lf.backstore.Firebase(schema, fb);
    return db.init(onUpgrade);
  }).then(function() {
    return commonVerify(db, verify);
  });
}

function testRenameTableColumn() {
  if (!manualMode) {
    return;
  }

  var onUpgrade = function(rawDb) {
    assertEquals(1, rawDb.getVersion());
    return rawDb.renameTableColumn('tableA', 'name', 'nick');
  };

  var db;
  /**
   * @param {number} index
   * @param {!Object} row
   */
  var verify = function(index, row) {
    var expected = (index == 0) ? CONTENTS : CONTENTS2;
    assertEquals(2, row['R']);
    assertEquals(0, row['T']);
    assertEquals(expected['id'], row['P']['id']);
    assertEquals(expected['name'], row['P']['nick']);
  };

  return commonSetUp().then(function(instance) {
    db = instance;
    db.close();
    schema.setVersion(2);
    db = new lf.backstore.Firebase(schema, fb);
    return db.init(onUpgrade);
  }).then(function() {
    return commonVerify(db, verify);
  });
}
<commit_msg>Add non-nullable modifier to return type of functions never returning null.<commit_after>/**
 * @license
 * Copyright 2015 The Lovefield Project 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.
 */
goog.setTestOnly();
goog.require('goog.Promise');
goog.require('goog.testing.TestCase');
goog.require('goog.testing.jsunit');
goog.require('goog.testing.recordFunction');
goog.require('lf.Global');
goog.require('lf.backstore.Firebase');
goog.require('lf.backstore.FirebaseRawBackStore');
goog.require('lf.cache.DefaultCache');
goog.require('lf.index.MemoryIndexStore');
goog.require('lf.service');
goog.require('lf.testing.backstore.MockSchema');

/** @type {!Firebase} */
var fb;

/** @type {!lf.Global} */
var global;

/** @type {!lf.index.MemoryIndexStore} */
var indexStore;

/** @type {!lf.cache.Cache} */
var cache;

/** @type {!lf.schema.Database} */
var schema;

/** @type {boolean} */
var manualMode;

/** @const {!Object} */
var CONTENTS = {'id': 'hello', 'name': 'world'};

/** @const {!Object} */
var CONTENTS2 = {'id': 'hello2', 'name': 'world2'};

// TODO(arthurhsu): remove this workaround once Closure fixes the setUpPage bug.
/** @type {!IThenable} */
var whenReady;

/** @return {!IThenable<!Firebase>} */
function getFirebaseRef() {
  var resolver = goog.Promise.withResolver();

  var ref = new Firebase(window['FIREBASE_URL']);
  ref.authWithCustomToken(window['FIREBASE_TOKEN'], function(err, authData) {
    if (err) {
      resolver.reject(err);
    } else {
      resolver.resolve(ref);
    }
  });

  return resolver.promise;
}

function setUpPage() {
  manualMode = window['MANUAL_MODE'] || false;
  if (!manualMode) {
    return;
  }

  // Firebase WebSocket timeout is 30 seconds, so make sure this timeout is
  // greater than that value, otherwise it can flake.
  goog.testing.TestCase.getActiveTestCase().promiseTimeout = 40 * 1000;  // 40s

  whenReady = getFirebaseRef().then(function(ref) {
    fb = ref;
  });
}

function setUp() {
  if (!manualMode) {
    return;
  }

  indexStore = new lf.index.MemoryIndexStore();
  schema = new lf.testing.backstore.MockSchema();
  schema.setName('msfr' + Date.now().toString() +
      Math.floor(Math.random() * 1000).toString());
  cache = new lf.cache.DefaultCache(schema);

  global = lf.Global.get();
  global.registerService(lf.service.CACHE, cache);
  global.registerService(lf.service.INDEX_STORE, indexStore);
  global.registerService(lf.service.SCHEMA, schema);
  return whenReady;
}

function tearDown() {
  if (!manualMode) {
    return;
  }

  fb.child(schema.name()).remove();
}

// Tests that onUpgrade function is still called with version 0 for a new DB
// instance.
function testNewDBInstance() {
  if (!manualMode) {
    return;
  }

  /** @type {!goog.testing.recordFunction.Type} */
  var onUpgrade = goog.testing.recordFunction(function(rawDb) {
    assertEquals(0, rawDb.getVersion());
    return goog.Promise.resolve();
  });

  var db = new lf.backstore.Firebase(schema, fb);
  // cast onUpgrade because it's now typed as goog.testing.recordFunction.Type
  return db
      .init(/** @type {function(!lf.raw.BackStore): !IThenable} */ (onUpgrade))
      .then(function() {
        onUpgrade.assertCallCount(1);
      });
}

// Tests new tables are created automatically
function testAddTable() {
  if (!manualMode) {
    return;
  }

  var db = new lf.backstore.Firebase(schema, fb);
  var validate = function(tables) {
    var tableNames = schema.tables().map(function(table) {
      return table.getName();
    }).sort();
    assertArrayEquals(tableNames, Object.keys(tables).sort());
  };
  var numTables = 0;

  return db.init().then(function() {
    return lf.backstore.FirebaseRawBackStore.getValue(db.getRef(), '@table');
  }).then(function(tables) {
    numTables = Object.keys(tables).length;
    assertTrue(numTables != 0);
    validate(tables);
    db.close();
    schema.setVersion(2);
    db = new lf.backstore.Firebase(schema, fb);
    return db.init();
  }).then(function() {
    return lf.backstore.FirebaseRawBackStore.getValue(db.getRef(), '@table');
  }).then(function(tables) {
    assertEquals(numTables + 1, Object.keys(tables).length);
    validate(tables);
  });
}

function testDump() {
  if (!manualMode) {
    return;
  }

  var dump;
  var onUpgrade = function(rawDb) {
    assertEquals(1, rawDb.getVersion());
    return rawDb.dump().then(function(contents) {
      dump = contents;
    });
  };

  var db = new lf.backstore.Firebase(schema, fb);
  return db.init().then(function() {
    var row1 = { 'R': 1, 'T': 0, 'P': CONTENTS };
    var row2 = { 'R': 1, 'T': 1, 'P': CONTENTS2 };
    return goog.Promise.all([
      lf.backstore.FirebaseRawBackStore.setValue(db.getRef().child(1), row1),
      lf.backstore.FirebaseRawBackStore.setValue(db.getRef().child(2), row2)
    ]);
  }).then(function() {
    db.close();
    schema.setVersion(2);
    db = new lf.backstore.Firebase(schema, fb);
    return db.init(onUpgrade);
  }).then(function() {
    assertObjectEquals(dump['tableA'][0], CONTENTS);
    assertObjectEquals(dump['tableB'][0], CONTENTS2);
  });
}

/** @return {!IThenable<!lf.backstore.Firebase>} */
function commonSetUp() {
  var db = new lf.backstore.Firebase(schema, fb);
  return db.init().then(function() {
    var row1 = { 'R': 1, 'T': 0, 'P': CONTENTS };
    var row2 = { 'R': 1, 'T': 0, 'P': CONTENTS2 };
    return goog.Promise.all([
      lf.backstore.FirebaseRawBackStore.setValue(db.getRef().child('1'), row1),
      lf.backstore.FirebaseRawBackStore.setValue(db.getRef().child('2'), row2)
    ]);
  }).then(function() {
    return db;
  });
}

/**
 * @param {!lf.backstore.Firebase} db
 * @param {!function(number, !Object)} verify
 * @return {!IThenable}
 */
function commonVerify(db, verify) {
  return goog.Promise.all([
    lf.backstore.FirebaseRawBackStore.getValue(db.getRef(), '1'),
    lf.backstore.FirebaseRawBackStore.getValue(db.getRef(), '2')
  ]).then(function(results) {
    results.forEach(function(result, index) {
      verify(index, result);
    });
  });
}

function testDropTable() {
  if (!manualMode) {
    return;
  }

  var onUpgrade = function(rawDb) {
    assertEquals(1, rawDb.getVersion());
    return rawDb.dropTable('tableA');
  };

  var db;
  return commonSetUp().then(function(instance) {
    db = instance;
    db.close();
    schema.setVersion(2);
    schema.setDropTableA(true);
    db = new lf.backstore.Firebase(schema, fb);
    return db.init(onUpgrade);
  }).then(function() {
    db.getRef().orderByKey().once('value', function(snapshot) {
      assertFalse(snapshot.hasChild('1'));
      assertFalse(snapshot.hasChild('2'));
      assertTrue(snapshot.hasChild('@table'));
      assertFalse(snapshot.child('@table').hasChild('tableA'));
    });
  });
}

function testAddTableColumn() {
  if (!manualMode) {
    return;
  }

  var onUpgrade = function(rawDb) {
    assertEquals(1, rawDb.getVersion());
    return rawDb.addTableColumn('tableA', 'foo', 23);
  };

  var db;
  /**
   * @param {number} index
   * @param {!Object} row
   */
  var verify = function(index, row) {
    var expected = (index == 0) ? CONTENTS : CONTENTS2;
    assertEquals(2, row['R']);
    assertEquals(0, row['T']);
    assertEquals(23, row['P']['foo']);
    assertEquals(expected['id'], row['P']['id']);
    assertEquals(expected['name'], row['P']['name']);
  };

  return commonSetUp().then(function(instance) {
    db = instance;
    db.close();
    schema.setVersion(2);
    db = new lf.backstore.Firebase(schema, fb);
    return db.init(onUpgrade);
  }).then(function() {
    return commonVerify(db, verify);
  });
}

function testDropTableColumn() {
  if (!manualMode) {
    return;
  }

  var onUpgrade = function(rawDb) {
    assertEquals(1, rawDb.getVersion());
    return rawDb.dropTableColumn('tableA', 'name');
  };

  var db;
  /**
   * @param {number} index
   * @param {!Object} row
   */
  var verify = function(index, row) {
    var expected = (index == 0) ? CONTENTS : CONTENTS2;
    assertEquals(2, row['R']);
    assertEquals(0, row['T']);
    assertEquals(expected['id'], row['P']['id']);
    assertEquals(undefined, row['P']['name']);
  };

  return commonSetUp().then(function(instance) {
    db = instance;
    db.close();
    schema.setVersion(2);
    db = new lf.backstore.Firebase(schema, fb);
    return db.init(onUpgrade);
  }).then(function() {
    return commonVerify(db, verify);
  });
}

function testRenameTableColumn() {
  if (!manualMode) {
    return;
  }

  var onUpgrade = function(rawDb) {
    assertEquals(1, rawDb.getVersion());
    return rawDb.renameTableColumn('tableA', 'name', 'nick');
  };

  var db;
  /**
   * @param {number} index
   * @param {!Object} row
   */
  var verify = function(index, row) {
    var expected = (index == 0) ? CONTENTS : CONTENTS2;
    assertEquals(2, row['R']);
    assertEquals(0, row['T']);
    assertEquals(expected['id'], row['P']['id']);
    assertEquals(expected['name'], row['P']['nick']);
  };

  return commonSetUp().then(function(instance) {
    db = instance;
    db.close();
    schema.setVersion(2);
    db = new lf.backstore.Firebase(schema, fb);
    return db.init(onUpgrade);
  }).then(function() {
    return commonVerify(db, verify);
  });
}
<|endoftext|>