<commit_before>if (typeof diff_match_patch !== 'undefined') {
    diff_match_patch.prototype.diff_prettyHtml = function(diffs) {
        /* An override of prettyHthml from diff_match_patch. This
           one will not put any style attrs in the ins or del. */
        var html = [];
        for (var x = 0; x < diffs.length; x++) {
            var op = diffs[x][0];    // Operation (insert, delete, equal)
            var data = diffs[x][1];  // Text of change.
            var lines = data.split('\n');
            for (var t = 0; t < lines.length; t++) {
                /* A diff gets an empty element on the end (the last \n).
                   Unless the diff line in question does not have a new line on
                   the end. We can't just set lines.length - 1, because this
                   will just chop off lines. But if we don't trim these empty
                   lines we'll end up with lines between each diff. */
                if ((t + 1) == lines.length && lines[t] == '') {
                    continue;
                }
                switch (op) {
                    /* The syntax highlighter needs an extra space
                       to do it's work. */
                    case DIFF_INSERT:
                        html.push('+ ' + lines[t] + '\n');
                        break;
                    case DIFF_DELETE:
                        html.push('- ' + lines[t] + '\n');
                        break;
                    case DIFF_EQUAL:
                        html.push('  ' + lines[t] + '\n');
                        break;
                }
            }
        }
        return html.join('');
    };
}

/* Turn off double click on the syntax highlighter. */
SyntaxHighlighter.defaults['quick-code'] = false;

function bind_viewer(nodes) {
    $.each(nodes, function(x) {
        nodes['$'+x] = $(nodes[x]);
    });
    function Viewer() {
        this.nodes = nodes;
        this.wrapped = true;
        this.hidden = false;
        this.top = null;
        this.last = null;
        this.fix_vertically = function($inner, $outer) {
            var $self = this;
            if (!$self.top) {
                $self.top = $outer.position().top;
            }
            function update() {
                var sb_bottom = $self.top + $outer.height() - $inner.height();
                if ($(window).scrollTop() > sb_bottom) {
                    $inner.css({'position': 'absolute', 'top': sb_bottom});
                } else if ($(window).scrollTop() > $self.top) {
                    $inner.css({'position': 'fixed', 'top': 0});
                } else {
                    $inner.css({'position': 'absolute', 'top': $self.top});
                }
            }
            $(window).scroll(debounce(update), 200);
            update();
        };
        this.line_wrap = function($node, deleted) {
            /* SyntaxHighlighter doesn't produce linked line numbers or cope
               with wrapped text producing bigger line numbers.
               This fixes that. */
            var line_numbers = 1,
                deleted_line_numbers = 1;
            $node.each(function(){
                var $self = $(this),
                    $gutter = $self.find('td.gutter');
                    $code = $self.find('td.code');
                $gutter.find('div.line').each(function(i){
                    var $gutter_line = $(this),
                        $line = $code.find('.line:nth-child(' + (i+1) +')'),
                        height = $line.height(),
                        del = !!$line.children('code.comments').length,
                        link = $gutter_line.children('a');
                    /* If there's a deleted line, don't show a line number */
                    if (deleted && del) {
                        $gutter_line.html($('<a>', {'href': '#D' + deleted_line_numbers,
                                                    'name': 'D' + deleted_line_numbers,
                                                    'class': 'delete',
                                                    'style': 'height: ' + height + 'px',
                                                    'text': ' '}));
                        $line.addClass('delete');
                        deleted_line_numbers++;
                        return;
                    }
                    /* If a link already exists and the height is different
                       alter it. */
                    if (link.length) {
                        if (link.height() != height) {
                            link.css('height',  height + 'px');
                        }
                    } else {
                        /* Otherwise add in a link, occurs on first pass. */
                        var klass = $line.find('code.string').length ? 'add' : '';
                        $gutter_line.html($('<a>', {'href': '#L' + line_numbers,
                                                    'name': 'L' + line_numbers,
                                                    'class': klass,
                                                    'style': 'height: ' + height + 'px',
                                                    'text': line_numbers}));
                        $line.addClass(klass);
                        line_numbers++;
                    }
                });
            });
        };
        this.compute = function(node) {
            var $diff = node.find('#diff'),
                $content = node.find('#content');

            if ($content && !$diff.length) {
                SyntaxHighlighter.highlight($content);
                // Fix up the lines to be the way we want.
                // Note SyntaxHighlighter has nuked the node and replaced it.
                this.line_wrap(node.find('#content'), false);
            }

            if ($diff.length) {
                var dmp = new diff_match_patch();
                // Line diffs http://code.google.com/p/google-diff-match-patch/wiki/LineOrWordDiffs
                var a = dmp.diff_linesToChars_($diff.siblings('.right').text(), $diff.siblings('.left').text());
                var diffs = dmp.diff_main(a[0], a[1], false);
                dmp.diff_charsToLines_(diffs, a[2]);
                $diff.text(dmp.diff_prettyHtml(diffs)).show();

                SyntaxHighlighter.highlight($diff);
                // Note SyntaxHighlighter has nuked the node and replaced it.
                $diff = node.find('#diff');
                this.line_wrap($diff, true);

                /* Build out the diff bar based on the line numbers. */
                var $sb = $diff.siblings('.diff-bar').eq(0),
                    $lines = $diff.find('td.gutter div.line a');

                if ($lines.length) {
                    var state = {'start':0, 'type':$lines.eq(0).attr('class'),
                                 'href':$lines.eq(0).attr('href')};
                    for (var j = 1; j < $lines.length; j++) {
                        var $node = $lines.eq(j);
                        if (!$node.hasClass(state.type)) {
                            this.side_bar_append($sb, state, j, $lines.length);
                            state = {'start': j, 'type': $node.attr('class'),
                                     'href': $node.attr('href')};
                        }
                    }
                    $diff.addClass('diff-bar-height');
                    this.side_bar_append($sb, state, j, $lines.length);
                    this.fix_vertically($sb, $diff);
                    $sb.show();
                }
            }

            if (window.location.hash && window.location.hash != 'top') {
                window.location = window.location;
            }
        };
        this.side_bar_append = function($sb, state, k, total) {
            $sb.append($('<a>', {'href': state.href, 'class': state.type,
                                 'css': {'height': (((k-state.start)/total) * 100) + '%' }}));
        };
        this.toggle_leaf = function($leaf) {
            if ($leaf.hasClass('open')) {
                this.hide_leaf($leaf);
            } else {
                this.show_leaf($leaf);
            }
        };
        this.hide_leaf = function($leaf) {
            $leaf.removeClass('open').addClass('closed')
                 .closest('li').next('ul').hide();
        };
        this.show_leaf = function($leaf) {
            /* Exposes the leaves for a given set of node. */
            $leaf.removeClass('closed').addClass('open')
                 .closest('li').next('ul').show();
        };
        this.selected = function($link) {
            /* Exposes all the leaves to an element */
            $link.parentsUntil('ul.root').filter('ul').show()
                 .each(function() {
                        $(this).prev('li').find('a:first')
                               .removeClass('closed').addClass('open');
            });
            if ($('.breadcrumbs li').length > 2) {
                $('.breadcrumbs li').eq(2).text($link.attr('data-short'));
            } else {
                $('.breadcrumbs').append(format('<li>{0}</li>', $link.attr('data-short')));
            }
        };
        this.load = function($link) {
            /* Accepts a jQuery wrapped node, which is part of the tree.
               Hides content, shows spinner, gets the content and then
               shows it all. */
            var self = this,
                $old_wrapper = $('#content-wrapper');
            $old_wrapper.hide();
            this.nodes.$thinking.show();
            if (location.hash != 'top') {
                if (history.pushState !== undefined) {
                    this.last = $link.attr('href');
                    history.pushState({ path: $link.text() }, '', $link.attr('href') + '#top');
                }
            }
            $old_wrapper.load($link.attr('href').replace('/file/', '/fragment/') + ' #content-wrapper',
                function(response, status, xhr) {
                    self.nodes.$thinking.hide();
                    /* Cope with an error a little more nicely. */
                    if (status != 'error') {
                        $(this).children().unwrap();
                        var $new_wrapper = $('#content-wrapper');
                        self.compute($new_wrapper);
                        $new_wrapper.slideDown();
                        if (self.hidden) {
                            self.toggle_files('hide');
                        }
                    }
                }
            );
        };
        this.select = function($link) {
            /* Given a node, alters the tree and then loads the content. */
            this.nodes.$files.find('a.selected').each(function() {
                $(this).removeClass('selected');
            });
            $link.addClass('selected');
            this.selected($link);
            this.load($link);
        };
        this.get_selected = function() {
            var k = 0;
            $.each(this.nodes.$files.find('a.file'), function(i, el) {
                if ($(el).hasClass("selected")) {
                   k = i;
                }
            });
            return k;
        };
        this.toggle_wrap = function(state) {
            /* Toggles the content wrap in the page, starts off unwrapped */
            this.wrapped = (state == 'wrap' || !this.wrapped);
            $('code').toggleClass('wrap-toggle');
            this.line_wrap($('#content-wrapper'));
        };
        this.toggle_files = function(state) {
            this.hidden = (state == 'hide' || !this.hidden);
            if (this.hidden) {
                this.nodes.$files.hide();
                this.nodes.$commands.detach().appendTo('div.featured-inner:first');
                this.nodes.$thinking.addClass('full');
            } else {
                this.nodes.$files.show();
                this.nodes.$commands.detach().appendTo(this.nodes.$files);
                this.nodes.$thinking.removeClass('full');
            }
            $('#content-wrapper').toggleClass('full');
        };
    }

    var viewer = new Viewer();

    if (viewer.nodes.$files.find('li').length == 1) {
        viewer.toggle_files();
        $('#files-down').parent().hide();
        $('#files-up').parent().hide();
        $('#files-expand-all').parent().hide();
    }

    viewer.nodes.$files.find('.directory').click(_pd(function() {
        viewer.toggle_leaf($(this));
    }));

    $('#files-up').click(_pd(function() {
        var prev = viewer.get_selected() - 1;
        if (prev >= 0) {
            viewer.select(viewer.nodes.$files.find('a.file').eq(prev));
        }
    }));

    $('#files-down').click(_pd(function() {
        var next = viewer.nodes.$files.find('a.file').eq(viewer.get_selected() + 1);
        if (next.length) {
            viewer.select(next);
        }
    }));

    $('#files-wrap').click(_pd(function() {
        viewer.toggle_wrap();
    }));

    $('#files-hide').click(_pd(function() {
        viewer.toggle_files();
    }));

    $('#files-expand-all').click(_pd(function() {
        viewer.nodes.$files.find('a.closed').each(function() {
            viewer.show_leaf($(this));
        });
    }));

    viewer.nodes.$files.find('.file').click(_pd(function() {
        viewer.select($(this));
        viewer.toggle_wrap('wrap');
    }));

    $(window).bind('popstate', function() {
        if (viewer.last != location.pathname) {
            viewer.nodes.$files.find('.file').each(function() {
                if ($(this).attr('href') == location.pathname) {
                    viewer.select($(this));
                }
            });
        }
    });

    $(document).bind('keyup', _pd(function(e) {
        if (e.keyCode == 72) {
            $('#files-hide').trigger('click');
        } else if (e.keyCode == 75) {
            $('#files-up').trigger('click');
        } else if (e.keyCode == 74) {
            $('#files-down').trigger('click');
        } else if (e.keyCode == 87) {
            $('#files-wrap').trigger('click');
        } else if (e.keyCode == 69) {
            $('#files-expand-all').trigger('click');
        }
    }));
    return viewer;
}

$(document).ready(function() {
    var viewer = null;
    var nodes = { files: '#files', thinking: '#thinking', commands: '#commands' };
    function poll_file_extraction() {
        $.getJSON($('#extracting').attr('data-url'), function(json) {
            if (json && json.status) {
                $('#file-viewer').load(window.location.pathname + '?full=yes' + ' #file-viewer', function() {
                    $(this).children().unwrap();
                    viewer = bind_viewer(nodes);
                    viewer.selected(viewer.nodes.$files.find('a.selected'));
                    viewer.compute($('#content-wrapper'));
                });
            } else if (json) {
                var errors = false;
                $.each(json.msg, function(k) {
                    if (json.msg[k] !== null) {
                        errors = true;
                        $('<p>').text(json.msg[k]).appendTo($('#file-viewer div.error'));
                    }
                });
                if (errors) {
                    $('#file-viewer div.error').show();
                    $('#extracting').hide();
                } else {
                    setTimeout(poll_file_extraction, 2000);
                }
            }
        });
    }

    if ($('#extracting').length) {
        poll_file_extraction();
    } else if ($('#file-viewer').length) {
        viewer = bind_viewer(nodes);
        viewer.selected(viewer.nodes.$files.find('a.selected'));
        viewer.compute($('#content-wrapper'));
    }
});
<commit_msg>only tweak the highlighter if its there<commit_after>if (typeof diff_match_patch !== 'undefined') {
    diff_match_patch.prototype.diff_prettyHtml = function(diffs) {
        /* An override of prettyHthml from diff_match_patch. This
           one will not put any style attrs in the ins or del. */
        var html = [];
        for (var x = 0; x < diffs.length; x++) {
            var op = diffs[x][0];    // Operation (insert, delete, equal)
            var data = diffs[x][1];  // Text of change.
            var lines = data.split('\n');
            for (var t = 0; t < lines.length; t++) {
                /* A diff gets an empty element on the end (the last \n).
                   Unless the diff line in question does not have a new line on
                   the end. We can't just set lines.length - 1, because this
                   will just chop off lines. But if we don't trim these empty
                   lines we'll end up with lines between each diff. */
                if ((t + 1) == lines.length && lines[t] == '') {
                    continue;
                }
                switch (op) {
                    /* The syntax highlighter needs an extra space
                       to do it's work. */
                    case DIFF_INSERT:
                        html.push('+ ' + lines[t] + '\n');
                        break;
                    case DIFF_DELETE:
                        html.push('- ' + lines[t] + '\n');
                        break;
                    case DIFF_EQUAL:
                        html.push('  ' + lines[t] + '\n');
                        break;
                }
            }
        }
        return html.join('');
    };
}

if (typeof SyntaxHighlighter !== 'undefined') {
    /* Turn off double click on the syntax highlighter. */
    SyntaxHighlighter.defaults['quick-code'] = false;
}

function bind_viewer(nodes) {
    $.each(nodes, function(x) {
        nodes['$'+x] = $(nodes[x]);
    });
    function Viewer() {
        this.nodes = nodes;
        this.wrapped = true;
        this.hidden = false;
        this.top = null;
        this.last = null;
        this.fix_vertically = function($inner, $outer) {
            var $self = this;
            if (!$self.top) {
                $self.top = $outer.position().top;
            }
            function update() {
                var sb_bottom = $self.top + $outer.height() - $inner.height();
                if ($(window).scrollTop() > sb_bottom) {
                    $inner.css({'position': 'absolute', 'top': sb_bottom});
                } else if ($(window).scrollTop() > $self.top) {
                    $inner.css({'position': 'fixed', 'top': 0});
                } else {
                    $inner.css({'position': 'absolute', 'top': $self.top});
                }
            }
            $(window).scroll(debounce(update), 200);
            update();
        };
        this.line_wrap = function($node, deleted) {
            /* SyntaxHighlighter doesn't produce linked line numbers or cope
               with wrapped text producing bigger line numbers.
               This fixes that. */
            var line_numbers = 1,
                deleted_line_numbers = 1;
            $node.each(function(){
                var $self = $(this),
                    $gutter = $self.find('td.gutter');
                    $code = $self.find('td.code');
                $gutter.find('div.line').each(function(i){
                    var $gutter_line = $(this),
                        $line = $code.find('.line:nth-child(' + (i+1) +')'),
                        height = $line.height(),
                        del = !!$line.children('code.comments').length,
                        link = $gutter_line.children('a');
                    /* If there's a deleted line, don't show a line number */
                    if (deleted && del) {
                        $gutter_line.html($('<a>', {'href': '#D' + deleted_line_numbers,
                                                    'name': 'D' + deleted_line_numbers,
                                                    'class': 'delete',
                                                    'style': 'height: ' + height + 'px',
                                                    'text': ' '}));
                        $line.addClass('delete');
                        deleted_line_numbers++;
                        return;
                    }
                    /* If a link already exists and the height is different
                       alter it. */
                    if (link.length) {
                        if (link.height() != height) {
                            link.css('height',  height + 'px');
                        }
                    } else {
                        /* Otherwise add in a link, occurs on first pass. */
                        var klass = $line.find('code.string').length ? 'add' : '';
                        $gutter_line.html($('<a>', {'href': '#L' + line_numbers,
                                                    'name': 'L' + line_numbers,
                                                    'class': klass,
                                                    'style': 'height: ' + height + 'px',
                                                    'text': line_numbers}));
                        $line.addClass(klass);
                        line_numbers++;
                    }
                });
            });
        };
        this.compute = function(node) {
            var $diff = node.find('#diff'),
                $content = node.find('#content');

            if ($content && !$diff.length) {
                SyntaxHighlighter.highlight($content);
                // Fix up the lines to be the way we want.
                // Note SyntaxHighlighter has nuked the node and replaced it.
                this.line_wrap(node.find('#content'), false);
            }

            if ($diff.length) {
                var dmp = new diff_match_patch();
                // Line diffs http://code.google.com/p/google-diff-match-patch/wiki/LineOrWordDiffs
                var a = dmp.diff_linesToChars_($diff.siblings('.right').text(), $diff.siblings('.left').text());
                var diffs = dmp.diff_main(a[0], a[1], false);
                dmp.diff_charsToLines_(diffs, a[2]);
                $diff.text(dmp.diff_prettyHtml(diffs)).show();

                SyntaxHighlighter.highlight($diff);
                // Note SyntaxHighlighter has nuked the node and replaced it.
                $diff = node.find('#diff');
                this.line_wrap($diff, true);

                /* Build out the diff bar based on the line numbers. */
                var $sb = $diff.siblings('.diff-bar').eq(0),
                    $lines = $diff.find('td.gutter div.line a');

                if ($lines.length) {
                    var state = {'start':0, 'type':$lines.eq(0).attr('class'),
                                 'href':$lines.eq(0).attr('href')};
                    for (var j = 1; j < $lines.length; j++) {
                        var $node = $lines.eq(j);
                        if (!$node.hasClass(state.type)) {
                            this.side_bar_append($sb, state, j, $lines.length);
                            state = {'start': j, 'type': $node.attr('class'),
                                     'href': $node.attr('href')};
                        }
                    }
                    $diff.addClass('diff-bar-height');
                    this.side_bar_append($sb, state, j, $lines.length);
                    this.fix_vertically($sb, $diff);
                    $sb.show();
                }
            }

            if (window.location.hash && window.location.hash != 'top') {
                window.location = window.location;
            }
        };
        this.side_bar_append = function($sb, state, k, total) {
            $sb.append($('<a>', {'href': state.href, 'class': state.type,
                                 'css': {'height': (((k-state.start)/total) * 100) + '%' }}));
        };
        this.toggle_leaf = function($leaf) {
            if ($leaf.hasClass('open')) {
                this.hide_leaf($leaf);
            } else {
                this.show_leaf($leaf);
            }
        };
        this.hide_leaf = function($leaf) {
            $leaf.removeClass('open').addClass('closed')
                 .closest('li').next('ul').hide();
        };
        this.show_leaf = function($leaf) {
            /* Exposes the leaves for a given set of node. */
            $leaf.removeClass('closed').addClass('open')
                 .closest('li').next('ul').show();
        };
        this.selected = function($link) {
            /* Exposes all the leaves to an element */
            $link.parentsUntil('ul.root').filter('ul').show()
                 .each(function() {
                        $(this).prev('li').find('a:first')
                               .removeClass('closed').addClass('open');
            });
            if ($('.breadcrumbs li').length > 2) {
                $('.breadcrumbs li').eq(2).text($link.attr('data-short'));
            } else {
                $('.breadcrumbs').append(format('<li>{0}</li>', $link.attr('data-short')));
            }
        };
        this.load = function($link) {
            /* Accepts a jQuery wrapped node, which is part of the tree.
               Hides content, shows spinner, gets the content and then
               shows it all. */
            var self = this,
                $old_wrapper = $('#content-wrapper');
            $old_wrapper.hide();
            this.nodes.$thinking.show();
            if (location.hash != 'top') {
                if (history.pushState !== undefined) {
                    this.last = $link.attr('href');
                    history.pushState({ path: $link.text() }, '', $link.attr('href') + '#top');
                }
            }
            $old_wrapper.load($link.attr('href').replace('/file/', '/fragment/') + ' #content-wrapper',
                function(response, status, xhr) {
                    self.nodes.$thinking.hide();
                    /* Cope with an error a little more nicely. */
                    if (status != 'error') {
                        $(this).children().unwrap();
                        var $new_wrapper = $('#content-wrapper');
                        self.compute($new_wrapper);
                        $new_wrapper.slideDown();
                        if (self.hidden) {
                            self.toggle_files('hide');
                        }
                    }
                }
            );
        };
        this.select = function($link) {
            /* Given a node, alters the tree and then loads the content. */
            this.nodes.$files.find('a.selected').each(function() {
                $(this).removeClass('selected');
            });
            $link.addClass('selected');
            this.selected($link);
            this.load($link);
        };
        this.get_selected = function() {
            var k = 0;
            $.each(this.nodes.$files.find('a.file'), function(i, el) {
                if ($(el).hasClass("selected")) {
                   k = i;
                }
            });
            return k;
        };
        this.toggle_wrap = function(state) {
            /* Toggles the content wrap in the page, starts off unwrapped */
            this.wrapped = (state == 'wrap' || !this.wrapped);
            $('code').toggleClass('wrap-toggle');
            this.line_wrap($('#content-wrapper'));
        };
        this.toggle_files = function(state) {
            this.hidden = (state == 'hide' || !this.hidden);
            if (this.hidden) {
                this.nodes.$files.hide();
                this.nodes.$commands.detach().appendTo('div.featured-inner:first');
                this.nodes.$thinking.addClass('full');
            } else {
                this.nodes.$files.show();
                this.nodes.$commands.detach().appendTo(this.nodes.$files);
                this.nodes.$thinking.removeClass('full');
            }
            $('#content-wrapper').toggleClass('full');
        };
    }

    var viewer = new Viewer();

    if (viewer.nodes.$files.find('li').length == 1) {
        viewer.toggle_files();
        $('#files-down').parent().hide();
        $('#files-up').parent().hide();
        $('#files-expand-all').parent().hide();
    }

    viewer.nodes.$files.find('.directory').click(_pd(function() {
        viewer.toggle_leaf($(this));
    }));

    $('#files-up').click(_pd(function() {
        var prev = viewer.get_selected() - 1;
        if (prev >= 0) {
            viewer.select(viewer.nodes.$files.find('a.file').eq(prev));
        }
    }));

    $('#files-down').click(_pd(function() {
        var next = viewer.nodes.$files.find('a.file').eq(viewer.get_selected() + 1);
        if (next.length) {
            viewer.select(next);
        }
    }));

    $('#files-wrap').click(_pd(function() {
        viewer.toggle_wrap();
    }));

    $('#files-hide').click(_pd(function() {
        viewer.toggle_files();
    }));

    $('#files-expand-all').click(_pd(function() {
        viewer.nodes.$files.find('a.closed').each(function() {
            viewer.show_leaf($(this));
        });
    }));

    viewer.nodes.$files.find('.file').click(_pd(function() {
        viewer.select($(this));
        viewer.toggle_wrap('wrap');
    }));

    $(window).bind('popstate', function() {
        if (viewer.last != location.pathname) {
            viewer.nodes.$files.find('.file').each(function() {
                if ($(this).attr('href') == location.pathname) {
                    viewer.select($(this));
                }
            });
        }
    });

    $(document).bind('keyup', _pd(function(e) {
        if (e.keyCode == 72) {
            $('#files-hide').trigger('click');
        } else if (e.keyCode == 75) {
            $('#files-up').trigger('click');
        } else if (e.keyCode == 74) {
            $('#files-down').trigger('click');
        } else if (e.keyCode == 87) {
            $('#files-wrap').trigger('click');
        } else if (e.keyCode == 69) {
            $('#files-expand-all').trigger('click');
        }
    }));
    return viewer;
}

$(document).ready(function() {
    var viewer = null;
    var nodes = { files: '#files', thinking: '#thinking', commands: '#commands' };
    function poll_file_extraction() {
        $.getJSON($('#extracting').attr('data-url'), function(json) {
            if (json && json.status) {
                $('#file-viewer').load(window.location.pathname + '?full=yes' + ' #file-viewer', function() {
                    $(this).children().unwrap();
                    viewer = bind_viewer(nodes);
                    viewer.selected(viewer.nodes.$files.find('a.selected'));
                    viewer.compute($('#content-wrapper'));
                });
            } else if (json) {
                var errors = false;
                $.each(json.msg, function(k) {
                    if (json.msg[k] !== null) {
                        errors = true;
                        $('<p>').text(json.msg[k]).appendTo($('#file-viewer div.error'));
                    }
                });
                if (errors) {
                    $('#file-viewer div.error').show();
                    $('#extracting').hide();
                } else {
                    setTimeout(poll_file_extraction, 2000);
                }
            }
        });
    }

    if ($('#extracting').length) {
        poll_file_extraction();
    } else if ($('#file-viewer').length) {
        viewer = bind_viewer(nodes);
        viewer.selected(viewer.nodes.$files.find('a.selected'));
        viewer.compute($('#content-wrapper'));
    }
});
<|endoftext|>


<commit_before>const fs = require('fs');
const P = require('bluebird');
const program = require('commander');
const chalk = require('chalk');
const DB = require('./services/db');
const TableAnalyzer = require('./services/table-analyzer');
const Dumper = require('./services/dumper');
const authenticator = require('./services/authenticator');
const Prompter = require('./services/prompter');
const logger = require('./services/logger');

function isDirectoryExist(path) {
  try {
    fs.accessSync(path, fs.F_OK);
    return true;
  } catch (e) {
    return false;
  }
}

program
  .description('Generate the admin panel of your web application based on the database schema.')
  .option('-c, --connection-url <connectionUrl>', 'Enter the database credentials with a connection URL')
  .option('--no-db', 'Use Lumber without a database.')
  .parse(process.argv);

(async () => {
  let config;

  if (program.connectionUrl) {
    config = await Prompter(program, [
      'dbConnectionUrl',
      'appHostname',
      'appPort',
      'appName',
    ]);
  } else if (!program.db) {
    config = await Prompter(program, [
      'appHostname',
      'appPort',
      'appName',
    ]);
  } else {
    config = await Prompter(program, [
      'dbDialect',
      'dbName',
      'dbSchema',
      'dbHostname',
      'dbPort',
      'dbUser',
      'dbPassword',
      'mongodbSrv',
      'ssl',
      'dbStorage',
      'appHostname',
      'appPort',
      'appName',
    ]);
  }

  // NOTICE: Ensure the project directory doesn't exist yet.
  const path = `${process.cwd()}/${config.appName}`;
  if (isDirectoryExist(path)) {
    logger.error(
      `The directory ${chalk.red(path)} already exists.`,
      'Please retry with a new directory.',
    );
    process.exit(1);
  }

  let schema = {};
  if (program.db) {
    const db = await new DB().connect(config);
    schema = await new TableAnalyzer(db, config).perform();
  }

  let project;
  try {
    project = await authenticator.createProject(config);
  } catch (error) {
    if (error.message === 'Unauthorized') {
      logger.error(
        'You are unauthorized to connect to Forest Admin.',
        `Please try the ${chalk.blue('lumber login')} command.`,
      );
    } else if (error.message === 'Conflict') {
      logger.error(
        `You already have a project named ${chalk.red(config.appName)}.`,
        'Please choose another name for this new project.',
      );
    } else {
      logger.error(
        'You are unauthorized to connect to Forest Admin.',
        `An unexpected error occured. Please create a Github issue with following error: ${chalk.red(error)}.`,
      );
    }
    process.exit(1);
  }

  const dumper = await new Dumper(project, config);

  await P.each(Object.keys(schema), async (table) => {
    await dumper.dump(table, schema[table].fields, schema[table].references);
  });

  console.log('\n');
  logger.success(`Hooray, ${chalk.green('installation success')}!`);

  console.log(`change directory: \n $ ${chalk.blue(`cd ${config.appName}`)}\n`);
  console.log(`install dependencies: \n $ ${chalk.blue('npm install')}\n`);
  console.log(`run your admin panel application: \n $ ${chalk.blue('npm start')}\n`);

  process.exit(0);
})().catch((error) => {
  logger.error(
    'Cannot generate your project.',
    `An unexpected error occured. Please create a Github issue with following error: ${chalk.red(error)}.`,
  );
  process.exit(1);
});
<commit_msg>Print the full stack trace when catching an error in the generate command<commit_after>const fs = require('fs');
const P = require('bluebird');
const program = require('commander');
const chalk = require('chalk');
const DB = require('./services/db');
const TableAnalyzer = require('./services/table-analyzer');
const Dumper = require('./services/dumper');
const authenticator = require('./services/authenticator');
const Prompter = require('./services/prompter');
const logger = require('./services/logger');

function isDirectoryExist(path) {
  try {
    fs.accessSync(path, fs.F_OK);
    return true;
  } catch (e) {
    return false;
  }
}

program
  .description('Generate the admin panel of your web application based on the database schema.')
  .option('-c, --connection-url <connectionUrl>', 'Enter the database credentials with a connection URL')
  .option('--no-db', 'Use Lumber without a database.')
  .parse(process.argv);

(async () => {
  let config;

  if (program.connectionUrl) {
    config = await Prompter(program, [
      'dbConnectionUrl',
      'appHostname',
      'appPort',
      'appName',
    ]);
  } else if (!program.db) {
    config = await Prompter(program, [
      'appHostname',
      'appPort',
      'appName',
    ]);
  } else {
    config = await Prompter(program, [
      'dbDialect',
      'dbName',
      'dbSchema',
      'dbHostname',
      'dbPort',
      'dbUser',
      'dbPassword',
      'mongodbSrv',
      'ssl',
      'dbStorage',
      'appHostname',
      'appPort',
      'appName',
    ]);
  }

  // NOTICE: Ensure the project directory doesn't exist yet.
  const path = `${process.cwd()}/${config.appName}`;
  if (isDirectoryExist(path)) {
    logger.error(
      `The directory ${chalk.red(path)} already exists.`,
      'Please retry with a new directory.',
    );
    process.exit(1);
  }

  let schema = {};
  if (program.db) {
    const db = await new DB().connect(config);
    schema = await new TableAnalyzer(db, config).perform();
  }

  let project;
  try {
    project = await authenticator.createProject(config);
  } catch (error) {
    if (error.message === 'Unauthorized') {
      logger.error(
        'You are unauthorized to connect to Forest Admin.',
        `Please try the ${chalk.blue('lumber login')} command.`,
      );
    } else if (error.message === 'Conflict') {
      logger.error(
        `You already have a project named ${chalk.red(config.appName)}.`,
        'Please choose another name for this new project.',
      );
    } else {
      logger.error(
        'You are unauthorized to connect to Forest Admin.',
        `An unexpected error occured. Please create a Github issue with following error: ${chalk.red(error)}.`,
      );
    }
    process.exit(1);
  }

  const dumper = await new Dumper(project, config);

  await P.each(Object.keys(schema), async (table) => {
    await dumper.dump(table, schema[table].fields, schema[table].references);
  });

  console.log('\n');
  logger.success(`Hooray, ${chalk.green('installation success')}!`);

  console.log(`change directory: \n $ ${chalk.blue(`cd ${config.appName}`)}\n`);
  console.log(`install dependencies: \n $ ${chalk.blue('npm install')}\n`);
  console.log(`run your admin panel application: \n $ ${chalk.blue('npm start')}\n`);

  process.exit(0);
})().catch((error) => {
  logger.error(
    'Cannot generate your project.',
    'An unexpected error occured. Please create a Github issue with following error:',
  );
  console.log(error);
  process.exit(1);
});
<|endoftext|>


<commit_before><commit_msg>Cleared scripts.js<commit_after>// Tracer bullet !
// It's alive !
<|endoftext|>


<commit_before>const {default: logger} = require('../lib/logger.ts');
const isValidUTF8 = require('utf-8-validate');
const encodingJapanese = require('encoding-japanese');
const libmime = require('libmime');

const sanitizeCode = (input) => ["`", input.replace(/`/g, "'"), "`"].join('');
const sanitizePreformatted = (input) => ["```", input.replace(/`/g, "'"), "```"].join("\n");

const decodeMailSubject = (subject) => libmime.decodeWords(subject);
module.exports.decodeMailSubject = decodeMailSubject;

const decodeMailBody = (text) => {
    let buf = Buffer.from(text, 'base64');
    if (!isValidUTF8(buf)) {
        buf = Buffer.from(text);
    }
    return encodingJapanese.convert(buf, {
        to: 'UNICODE',
        type: 'string',
    });
};
module.exports.decodeMailBody = decodeMailBody;

module.exports.server = ({webClient: slack}) => async (fastify) => {
    fastify.post('/api/smtp-hook', async (req, res) => {
        try {
            // internal hook
            if (req.raw.ip !== '127.0.0.1') return res.code(403);

            const {addresses, subject, body} = req.body;

            const decodedSubject = decodeMailSubject(subject);
            const text = decodeMailBody(body.text);

            await slack.chat.postMessage({
                channel: process.env.CHANNEL_PRLOG,
                username: 'Email Notifier',
                icon_emoji: ':email:',
                text: [
                    `MAILFROM: ${sanitizeCode(addresses.mailfrom)}`,
                    `TO: ${sanitizeCode(addresses.to)}`,
                    ...(addresses.cc ? [`CC: ${sanitizeCode(addresses.cc)}`] : []),
                    `FROM: ${sanitizeCode(addresses.from)}`,
                    `SUBJECT: ${sanitizeCode(decodedSubject)}`,
                ].join("\n"),
                attachments: [{
                    text,
                }],
            });

            return res.send('ok');
        } catch (e) {
            logger.error('mail-hook error:', e);

            await slack.chat.postMessage({
                channel: process.env.CHANNEL_PRLOG,
                username: 'Email Notifier',
                icon_emoji: ':email:',
                text: 'sorry :cry:\n an error occured while processing email.',
            });

            return res.code(500).send('error');
        }
    });
};
<commit_msg>mail-hook: logger<commit_after>const {default: _logger} = require('../lib/logger.ts');
const isValidUTF8 = require('utf-8-validate');
const encodingJapanese = require('encoding-japanese');
const libmime = require('libmime');

const logger = _logger.child({bot: 'mail-hook'});

const sanitizeCode = (input) => ["`", input.replace(/`/g, "'"), "`"].join('');
const sanitizePreformatted = (input) => ["```", input.replace(/`/g, "'"), "```"].join("\n");

const decodeMailSubject = (subject) => libmime.decodeWords(subject);
module.exports.decodeMailSubject = decodeMailSubject;

const decodeMailBody = (text) => {
    let buf = Buffer.from(text, 'base64');
    if (!isValidUTF8(buf)) {
        buf = Buffer.from(text);
    }
    return encodingJapanese.convert(buf, {
        to: 'UNICODE',
        type: 'string',
    });
};
module.exports.decodeMailBody = decodeMailBody;

module.exports.server = ({webClient: slack}) => async (fastify) => {
    fastify.post('/api/smtp-hook', async (req, res) => {
        try {
            // internal hook
            if (req.raw.ip !== '127.0.0.1') return res.code(403);

            const {addresses, subject, body} = req.body;

            const decodedSubject = decodeMailSubject(subject);
            const text = decodeMailBody(body.text);

            await slack.chat.postMessage({
                channel: process.env.CHANNEL_PRLOG,
                username: 'Email Notifier',
                icon_emoji: ':email:',
                text: [
                    `MAILFROM: ${sanitizeCode(addresses.mailfrom)}`,
                    `TO: ${sanitizeCode(addresses.to)}`,
                    ...(addresses.cc ? [`CC: ${sanitizeCode(addresses.cc)}`] : []),
                    `FROM: ${sanitizeCode(addresses.from)}`,
                    `SUBJECT: ${sanitizeCode(decodedSubject)}`,
                ].join("\n"),
                attachments: [{
                    text,
                }],
            });

            return res.send('ok');
        } catch (e) {
            logger.error('error', {error: e});

            await slack.chat.postMessage({
                channel: process.env.CHANNEL_PRLOG,
                username: 'Email Notifier',
                icon_emoji: ':email:',
                text: 'sorry :cry:\n an error occured while processing email.',
            });

            return res.code(500).send('error');
        }
    });
};
<|endoftext|>


<commit_before>/*
 * Ping the APIs more than one URL test suite
 * Acceptance criteria:
 * The script should also have the ability to accept an array of URLs
 * with identifiers, example:
 * [
 *    {"identifier": "yahoo homepage", "url": "http://www.yahoo.com"},
 *    {"identifier": "microsoft homepage", "url": "http://www.microsoft.com"}
 * ]
 *
 * If it is passed an array the results should also contain the identifier.
 */

var
  vows = require('vows'),
  assert = require('assert');

var
  quero = {},
  validUrls = [
  {identifier: 'nodejs homepage', url: 'http://nodejs.org'},
  {identifier: 'npm homepage', url: 'http://npmjs.org'},
  {identifier: 'yahoo homepage', url: 'http://www.yahoo.com'},
  {identifier: 'google homepage', url: 'http://google.com'}
],
  invalidUrls = [
  {identifier: 'nodejs homepage', url: 'http://nodejs.org'},
  {idexntifier: 'npm homepage', url: 'http://npmjs.org'}
];

vows.describe('Should be able to ping more than URLs')
  .addBatch({
  'Load module': {
    topic: function () {
      quero = require('./../');
      return quero;
    },
    'should be loaded': function (topic) {
      assert.isObject(topic);
      assert.isNotNull(topic);
    },
    'should have a ping function': function (topic) {
      assert.isFunction(topic.ping);
    },
    'Handle one URL': {
      topic: function (quero) {
        quero.ping(invalidUrls, this.callback);
      },
      'should return a result if succeed': function (err, result) {
        assert.isNotNull(err);
        assert.throws(err, Error);
        assert.equal(err.message, 'We need one or more of valid URLs');
        assert.isUndefined(result);
      }
    }
  }
})
  .export(module);

vows.describe('Should be able to ping more than URLs')
  .addBatch({
  'Load module': {
    topic: function () {
      quero = require('./../');
      return quero;
    },
    'should be loaded': function (topic) {
      assert.isObject(topic);
      assert.isNotNull(topic);
    },
    'should have a ping function': function (topic) {
      assert.isFunction(topic.ping);
    },
    'Handle one URL': {
      topic: function (quero) {
        quero.ping(validUrls, this.callback);
      },
      'should return a result if succeed': function (err, result) {
        assert.isNull(err);
        assert.isArray(result);
      }
    }
  }
})
  .export(module);<commit_msg>Simplify the test suite<commit_after>/*
 * Ping the APIs more than one URL test suite
 * Acceptance criteria:
 * The script should also have the ability to accept an array of URLs
 * with identifiers, example:
 * [
 *    {"identifier": "yahoo homepage", "url": "http://www.yahoo.com"},
 *    {"identifier": "microsoft homepage", "url": "http://www.microsoft.com"}
 * ]
 *
 * If it is passed an array the results should also contain the identifier.
 */

var
  vows = require('vows'),
  assert = require('assert');

var
  quero = require('./../'),
  validUrls = [
  {identifier: 'nodejs homepage', url: 'http://nodejs.org'},
  {identifier: 'npm homepage', url: 'http://npmjs.org'},
  {identifier: 'yahoo homepage', url: 'http://www.yahoo.com'},
  {identifier: 'google homepage', url: 'http://google.com'}
],
  invalidUrlsIdentifier = [
  {identifier: 'nodejs homepage', url: 'http://nodejs.org'},
  {idexntifier: 'npm homepage', url: 'http://npmjs.org'}
],
  invalidUrls = [
  {identifier: 'nodejs homepage', url: 'http://nodejs.org'},
  {identifier: 'npm homepage', urlo: 'http://npmjs.org'}
];

vows.describe('Should load the module')
  .addBatch({
  'Load the module': {
    topic: quero,
    'should load the module': function (topic) {
      assert.include(topic, 'ping');
      assert.isFunction(topic.ping);
    }
  }
})
  .export(module);

vows.describe('Should be able to ping more than one URLs')
  .addBatch({
  'Ping with valid urls': {
    topic: function () {
      quero.ping(validUrls, this.callback);
    },
    'should return a result if succeed': function (err, result) {
      assert.isNull(err);
      assert.isArray(result);
    }
  },
  'Ping with invalid urls indentifier format': {
    topic: function () {
      quero.ping(invalidUrlsIdentifier, this.callback);
    },
    'should throw an Error': function (err, result) {
      assert.isNotNull(err);
      assert.throws(err, Error);
      assert.equal(err.message, 'We need one or more of valid URLs');
      assert.isUndefined(result);
    }
  },
  'Ping with invalid urls format': {
    topic: function () {
      quero.ping(invalidUrls, this.callback);
    },
    'should throw an Error': function (err, result) {
      assert.isNotNull(err);
      assert.throws(err, Error);
      assert.equal(err.message, 'We need one or more of valid URLs');
      assert.isUndefined(result);
    }
  }
})
  .export(module);<|endoftext|>


<commit_before>var util = require('util');
var bleno = require('bleno');
var redis = require("redis");

var publisher  = redis.createClient();
var channel = '/dbc/webui2uart';

var BlenoCharacteristic = bleno.Characteristic;

var characterID = '9a10ba1d-cd1c-4f00-9cca-1f3178d5fe8a';

var DBCCharacteristic = function() {
  DBCCharacteristic.super_.call(this, {
    uuid: characterID,
    properties: ['read', 'write', 'notify'],
    value: null
  });

  this._value = new Buffer(0);
  this._updateValueCallback = null;
};

util.inherits(DBCCharacteristic, BlenoCharacteristic);

DBCCharacteristic.prototype.onReadRequest = function(offset, callback) {
  console.log('DBCCharacteristic - onReadRequest: value = ' + this._value.toString('hex'));

  callback(this.RESULT_SUCCESS, this._value);
};

DBCCharacteristic.prototype.onWriteRequest = function(data, offset, withoutResponse, callback) {
  this._value = data;

  //console.log('DBCCharacteristic - onWriteRequest: value = ' + this._value.toString('hex'));
  console.log('DBCCharacteristic - onWriteRequest: value=<',this._value,'>');
  publisher.publish(channel, this._value);
  if (this._updateValueCallback) {
    console.log('DBCCharacteristic - onWriteRequest: notifying');

    this._updateValueCallback(this._value);
  }

  callback(this.RESULT_SUCCESS);
};

DBCCharacteristic.prototype.onSubscribe = function(maxValueSize, updateValueCallback) {
  console.log('DBCCharacteristic - onSubscribe');

  this._updateValueCallback = updateValueCallback;
};

DBCCharacteristic.prototype.onUnsubscribe = function() {
  console.log('DBCCharacteristic - onUnsubscribe');

  this._updateValueCallback = null;
};

module.exports = DBCCharacteristic;
<commit_msg>Update characteristic.js<commit_after>var util = require('util');
var bleno = require('bleno');
var redis = require("redis");

var publisher  = redis.createClient();
var channel = '/dbc/webui2uart';

var BlenoCharacteristic = bleno.Characteristic;

var characterID = '9a10ba1d-cd1c-4f00-9cca-1f3178d5fe8a';

var DBCCharacteristic = function() {
  DBCCharacteristic.super_.call(this, {
    uuid: characterID,
    properties: ['read', 'write', 'notify'],
    value: null
  });

  this._value = new Buffer(0);
  this._updateValueCallback = null;
};

util.inherits(DBCCharacteristic, BlenoCharacteristic);

DBCCharacteristic.prototype.onReadRequest = function(offset, callback) {
  console.log('DBCCharacteristic - onReadRequest: value = ' + this._value.toString('hex'));

  callback(this.RESULT_SUCCESS, this._value);
};

DBCCharacteristic.prototype.onWriteRequest = function(data, offset, withoutResponse, callback) {
  this._value = data;

  //console.log('DBCCharacteristic - onWriteRequest: value = ' + this._value.toString('hex'));
  console.log('DBCCharacteristic - onWriteRequest: value=<',this._value.toString('utf8'),'>');
  publisher.publish(channel, this._value).toString('utf8');
  if (this._updateValueCallback) {
    console.log('DBCCharacteristic - onWriteRequest: notifying');

    this._updateValueCallback(this._value);
  }

  callback(this.RESULT_SUCCESS);
};

DBCCharacteristic.prototype.onSubscribe = function(maxValueSize, updateValueCallback) {
  console.log('DBCCharacteristic - onSubscribe');

  this._updateValueCallback = updateValueCallback;
};

DBCCharacteristic.prototype.onUnsubscribe = function() {
  console.log('DBCCharacteristic - onUnsubscribe');

  this._updateValueCallback = null;
};

module.exports = DBCCharacteristic;
<|endoftext|>


<commit_before>import {matchMediaQuery} from "../../../../dom/events";
import QUnit from "qunit";
import {findOne} from "../../../../dom/traverse";

const matchQueryList = [];

QUnit.module("dom/events/matchMediaQuery()", {
    beforeEach: () => {
        findOne("#qunit-fixture").innerHTML = `

        `;
    },
    afterEach: () => {
        matchQueryList.forEach((callback, index) => {
            callback();

            matchQueryList.splice(index, 1);
        });
    }
});

QUnit.test(
    "matchMediaQuery() on window media query matches without immediate execution",
    (assert) =>
    {
        assert.expect(0);

        const callback = () => {
            assert.ok(false, "Media query matches, but should not.");
        };

        matchQueryList.push(matchMediaQuery(`(min-width: 0px)`, callback, false));
    }
);

QUnit.test(
    "matchMediaQuery() on window media query matches with immediate execution",
    (assert) =>
    {
        const done = assert.async(1);

        assert.expect(1);

        const callback = (matches) => {
            assert.ok(matches, "Media query matches.");
            done();
        };

        matchQueryList.push(matchMediaQuery(`(min-width: 0px)`, callback));
    }
);

QUnit.test(
    "matchMediaQuery() on window media query doesn't match without immediate execution",
    (assert) =>
    {
        assert.expect(0);

        const callback = (matches) => {
            assert.notOk(matches, "Media query does not match.");
        };

        matchQueryList.push(matchMediaQuery(`(min-width: ${window.innerWidth + 1}px)`, callback, false));
    }
);

QUnit.test(
    "matchMediaQuery() on window media query doesn't match with immediate execution",
    (assert) =>
    {
        const done = assert.async(1);

        assert.expect(1);

        const callback = (matches) => {
            assert.notOk(matches, "Media query does not match.");
            done();
        };

        matchQueryList.push(matchMediaQuery(`(min-width: ${window.innerWidth + 1}px)`, callback));
    }
);

QUnit.test(
    "matchMediaQuery() window.matchMedia was called correctly",
    (assert) =>
    {
        let realMatchMedia = window.matchMedia;
        assert.expect(1);

        window.matchMedia = (query) => {
            assert.strictEqual(query, "screen and (min-width: 400px)");

            return {
                matches: true,
                addListener: () => {},
            };
        };

        matchMediaQuery("screen and (min-width: 400px)", () => {});

        window.matchMedia = realMatchMedia;
    }
);

QUnit.test(
    "matchMediaQuery() listener was called immediately and correctly",
    (assert) =>
    {
        let realMatchMedia = window.matchMedia;
        assert.expect(1);

        window.matchMedia = () => {
            return {
                matches: true,
                addListener: () => {},
            };
        };

        matchMediaQuery("screen and (min-width: 400px)", (matches) => {
            assert.ok(matches, "The listener was called with a matching query.");
        });

        window.matchMedia = realMatchMedia;
    }
);

QUnit.test(
    "matchMediaQuery() addListener was called immediately and correctly",
    (assert) =>
    {
        let realMatchMedia = window.matchMedia;
        assert.expect(1);

        window.matchMedia = () => {
            return {
                matches: true,
                addListener: (eventHandler) => {
                    eventHandler({matches: true});
                },
            };
        };

        matchMediaQuery("screen and (min-width: 400px)", (matches) => {
            assert.ok(matches, "The listener was called with a matching query.");
        }, false);

        window.matchMedia = realMatchMedia;
    }
);

QUnit.test(
    "matchMediaQuery() addEventListener was called immediately and correctly",
    (assert) =>
    {
        let realMatchMedia = window.matchMedia;
        assert.expect(2);

        window.matchMedia = () => {
            return {
                matches: true,
                addEventListener: (eventType, eventHandler) => {
                    assert.strictEqual(eventType, "change");
                    eventHandler({matches: true});
                },
            };
        };

        matchMediaQuery("screen and (min-width: 400px)", (matches) => {
            assert.ok(matches, "The listener was called with a matching query.");
        }, false);

        window.matchMedia = realMatchMedia;
    }
);
<commit_msg>save the realMatchMedia globally in tests<commit_after>import {matchMediaQuery} from "../../../../dom/events";
import QUnit from "qunit";
import {findOne} from "../../../../dom/traverse";

const matchQueryList = [];
const realMatchMedia = window.matchMedia;

QUnit.module("dom/events/matchMediaQuery()", {
    beforeEach: () => {
        findOne("#qunit-fixture").innerHTML = `

        `;
    },
    afterEach: () => {
        matchQueryList.forEach((callback, index) => {
            callback();

            matchQueryList.splice(index, 1);
        });
    }
});

QUnit.test(
    "matchMediaQuery() on window media query matches without immediate execution",
    (assert) =>
    {
        assert.expect(0);

        const callback = () => {
            assert.ok(false, "Media query matches, but should not.");
        };

        matchQueryList.push(matchMediaQuery(`(min-width: 0px)`, callback, false));
    }
);

QUnit.test(
    "matchMediaQuery() on window media query matches with immediate execution",
    (assert) =>
    {
        const done = assert.async(1);

        assert.expect(1);

        const callback = (matches) => {
            assert.ok(matches, "Media query matches.");
            done();
        };

        matchQueryList.push(matchMediaQuery(`(min-width: 0px)`, callback));
    }
);

QUnit.test(
    "matchMediaQuery() on window media query doesn't match without immediate execution",
    (assert) =>
    {
        assert.expect(0);

        const callback = (matches) => {
            assert.notOk(matches, "Media query does not match.");
        };

        matchQueryList.push(matchMediaQuery(`(min-width: ${window.innerWidth + 1}px)`, callback, false));
    }
);

QUnit.test(
    "matchMediaQuery() on window media query doesn't match with immediate execution",
    (assert) =>
    {
        const done = assert.async(1);

        assert.expect(1);

        const callback = (matches) => {
            assert.notOk(matches, "Media query does not match.");
            done();
        };

        matchQueryList.push(matchMediaQuery(`(min-width: ${window.innerWidth + 1}px)`, callback));
    }
);

QUnit.test(
    "matchMediaQuery() window.matchMedia was called correctly",
    (assert) =>
    {
        assert.expect(1);

        window.matchMedia = (query) => {
            assert.strictEqual(query, "screen and (min-width: 400px)");

            return {
                matches: true,
                addListener: () => {},
            };
        };

        matchMediaQuery("screen and (min-width: 400px)", () => {});

        window.matchMedia = realMatchMedia;
    }
);

QUnit.test(
    "matchMediaQuery() listener was called immediately and correctly",
    (assert) =>
    {
        assert.expect(1);

        window.matchMedia = () => {
            return {
                matches: true,
                addListener: () => {},
            };
        };

        matchMediaQuery("screen and (min-width: 400px)", (matches) => {
            assert.ok(matches, "The listener was called with a matching query.");
        });

        window.matchMedia = realMatchMedia;
    }
);

QUnit.test(
    "matchMediaQuery() addListener was called immediately and correctly",
    (assert) =>
    {
        assert.expect(1);

        window.matchMedia = () => {
            return {
                matches: true,
                addListener: (eventHandler) => {
                    eventHandler({matches: true});
                },
            };
        };

        matchMediaQuery("screen and (min-width: 400px)", (matches) => {
            assert.ok(matches, "The listener was called with a matching query.");
        }, false);

        window.matchMedia = realMatchMedia;
    }
);

QUnit.test(
    "matchMediaQuery() addEventListener was called immediately and correctly",
    (assert) =>
    {
        assert.expect(2);

        window.matchMedia = () => {
            return {
                matches: true,
                addEventListener: (eventType, eventHandler) => {
                    assert.strictEqual(eventType, "change");
                    eventHandler({matches: true});
                },
            };
        };

        matchMediaQuery("screen and (min-width: 400px)", (matches) => {
            assert.ok(matches, "The listener was called with a matching query.");
        }, false);

        window.matchMedia = realMatchMedia;
    }
);
<|endoftext|>


<commit_before>
const expect = require('chai').expect;
const RG = require('../../../client/src/battles');

const worldConf = require('../../../client/data/conf.world');

const isGUICommand = () => {};
const doGUICommand = () => {};

describe('Function: Saving/restoring a game', function() {
    this.timeout(60000);
    // 1. Create a new game
    // 2. Do some stuff as user
    // 3. Save game
    // 4. Restore game
    // 5. Assert that changes were saved
    it('should be able to save/restore game for user', () => {
        const gameConf = {
            cols: 80,
            rows: 60,
            levels: 2,
            playerLevel: 'Medium',
            sqrPerMonster: 40,
            sqrPerItem: 100,
            debugMode: 'World',
            loadedPlayer: null,
            loadedLevel: null,
            playerName: 'Player1',
            world: worldConf
        };
        const gameFactory = new RG.Factory.Game();
        console.log('Now creating a new game');
        const game = gameFactory.createNewGame(gameConf);
        game.setGUICallbacks(isGUICommand, doGUICommand);

        const numLevelsBefore = game.getLevels().length;
        const levelIDsBefore = game.getLevels().map(level => level.getID());
        console.log('Now serializing the game.');
        let json = game.toJSON();
        console.log(JSON.stringify(json, null, ' '));
        const levelIDsJSON = json.levels.map(level => level.id);
        expect(levelIDsJSON, 'Level IDs in JSON are preserved')
            .to.deep.equal(levelIDsBefore);

        for (let i = 0; i < 2; i++) {
            const fromJSON = new RG.Game.FromJSON();
            console.log('Now restoring game from serialized object.');
            const restGame = fromJSON.createGame(json);

            console.log('Starting the checks for this test');
            expect(restGame.getPlayer().getName())
                .to.equal(gameConf.playerName);

            const numLevelsAfter = restGame.getLevels().length;
            const levelIDsAfter = restGame.getLevels()
                .map(level => level.getID());

            expect(numLevelsAfter, 'Levels must match after restore'
                ).to.equal(numLevelsBefore);

            expect(levelIDsAfter, 'Level IDs are preserved')
                .to.deep.equal(levelIDsBefore);

            const places = restGame.getPlaces();
            expect(Object.keys(places)).to.have.length(1);
            const world = places[worldConf.name];
            expect(world.getName()).to.equal(worldConf.name);
            console.log('Now serializing the game.');
            json = restGame.toJSON();
            console.log(JSON.stringify(json, null, ' '));
        }
    });
});
<commit_msg>Added test to verify that parents in levels are kept between from/toJSON cycles.<commit_after>
const expect = require('chai').expect;
const RG = require('../../../client/src/battles');

const worldConf = require('../../../client/data/conf.world');

const isGUICommand = () => {};
const doGUICommand = () => {};

describe('Function: Saving/restoring a game', function() {
    this.timeout(60000);
    // 1. Create a new game
    // 2. Do some stuff as user
    // 3. Save game
    // 4. Restore game
    // 5. Assert that changes were saved
    it('should be able to save/restore game for user', () => {
        const gameConf = {
            cols: 80,
            rows: 60,
            levels: 2,
            playerLevel: 'Medium',
            sqrPerMonster: 40,
            sqrPerItem: 100,
            debugMode: 'World',
            loadedPlayer: null,
            loadedLevel: null,
            playerName: 'Player1',
            world: worldConf
        };
        const gameFactory = new RG.Factory.Game();
        console.log('Now creating a new game');
        const game = gameFactory.createNewGame(gameConf);
        game.setGUICallbacks(isGUICommand, doGUICommand);

        const parentsBefore = game.getLevels().map(level => level.getParent());
        const numLevelsBefore = game.getLevels().length;
        const levelIDsBefore = game.getLevels().map(level => level.getID());
        console.log('Now serializing the game.');
        let json = game.toJSON();
        // console.log(JSON.stringify(json, null, ' '));
        const levelIDsJSON = json.levels.map(level => level.id);
        expect(levelIDsJSON, 'Level IDs in JSON are preserved')
            .to.deep.equal(levelIDsBefore);

        for (let i = 0; i < 2; i++) {
            const fromJSON = new RG.Game.FromJSON();
            console.log(`Restoring game ${i} from serialized object.`);
            const restGame = fromJSON.createGame(json);

            console.log('Starting the checks for this test');
            expect(restGame.getPlayer().getName())
                .to.equal(gameConf.playerName);

            const parentsAfter = game.getLevels()
                .map(level => level.getParent());
            const numLevelsAfter = restGame.getLevels().length;
            const levelIDsAfter = restGame.getLevels()
                .map(level => level.getID());

            expect(numLevelsAfter, 'Levels must match after restore')
                .to.equal(numLevelsBefore);

            expect(levelIDsAfter, 'Level IDs are preserved')
                .to.deep.equal(levelIDsBefore);
            expect(parentsAfter, 'Level parents are preserved')
                .to.deep.equal(parentsBefore);

            const places = restGame.getPlaces();
            expect(Object.keys(places)).to.have.length(1);
            const world = places[worldConf.name];
            expect(world.getName()).to.equal(worldConf.name);
            console.log('Now serializing the game.');
            json = restGame.toJSON();
            // console.log(JSON.stringify(json, null, ' '));
        }
    });
});
<|endoftext|>


<commit_before>'use strict';

module.exports = class HandlePizzaOrder {

	// コンストラクター。このスキルで必要とする、または指定することができるパラメータを設定します。
    constructor() {
        this.required_parameter = {
            pizza: {
                message_to_confirm: {
                    line: {
                        type: "template",
                        altText: "ご注文のピザはお決まりでしょうか？ マルゲリータ、マリナーラからお選びください。",
                        template: {
                            type: "buttons",
                            text: "ご注文のピザはお決まりでしょうか？",
                            actions: [
                                {type:"postback",label:"マルゲリータ",data:"マルゲリータ"},
                                {type:"postback",label:"マリナーラ",data:"マリナーラ"}
                            ]
                        }
                    },
                    facebook: {
                        text: "ご注文のピザはお決まりでしょうか？",
                        quick_replies: [
                            {content_type:"text",title:"マルゲリータ",payload:"マルゲリータ"},
                            {content_type:"text",title:"マリナーラ",payload:"マリナーラ"}
                        ]
                    }
                }
            },
            size: {
                message_to_confirm: {
                    line: {
                        type: "template",
                        altText: "サイズはいかがいたしましょうか？ S、M、Lからお選びください。",
                        template: {
                            type: "buttons",
                            text: "サイズはいかがいたしましょうか？",
                            actions: [
                                {type:"postback",label:"S",data:"S"},
                                {type:"postback",label:"M",data:"M"},
                                {type:"postback",label:"L",data:"L"}
                            ]
                        }
                    },
                    facebook: {
                        text: "サイズはいかがいたしましょうか？",
                        quick_replies: [
                            {content_type:"text",title:"S",payload:"S"},
                            {content_type:"text",title:"M",payload:"M"},
                            {content_type:"text",title:"M",payload:"L"}
                        ]
                    }
                }
            },
            address: {
                message_to_confirm: {
                    line: {
                        type: "text",
                        text: "お届け先の住所を教えていただけますか？"
                    },
                    facebook: {
                        text: "お届け先の住所を教えていただけますか？"
                    }
                }
            },
            name: {
                message_to_confirm: {
                    line: {
                        type: "text",
                        text: "最後に、お客様のお名前を教えていただけますか？"
                    },
                    facebook: {
                        text: "最後に、お客様のお名前を教えていただけますか？"
                    }
                }
            }
        };
    }

    // パラメーターが全部揃ったら実行する処理を記述します。
    finish(bot, bot_event, context){
        let messages = [bot.create_message(`${context.confirmed.name}様、ご注文ありがとうございました！${context.confirmed.pizza}の${context.confirmed.size}サイズを30分以内にご指定の${context.confirmed.address}までにお届けに上がります。`)];
        return bot.reply(bot_event, messages);
    }
};
<commit_msg>added sample skill "handle-pizza-order"<commit_after>'use strict';

module.exports = class HandlePizzaOrder {

	// コンストラクター。このスキルで必要とする、または指定することができるパラメータを設定します。
    constructor() {
        this.required_parameter = {
            pizza: {
                message_to_confirm: {
                    line: {
                        type: "template",
                        altText: "ご注文のピザはお決まりでしょうか？ マルゲリータ、マリナーラからお選びください。",
                        template: {
                            type: "buttons",
                            text: "ご注文のピザはお決まりでしょうか？",
                            actions: [
                                {type:"postback",label:"マルゲリータ",data:"マルゲリータ"},
                                {type:"postback",label:"マリナーラ",data:"マリナーラ"}
                            ]
                        }
                    },
                    facebook: {
                        text: "ご注文のピザはお決まりでしょうか？",
                        quick_replies: [
                            {content_type:"text",title:"マルゲリータ",payload:"マルゲリータ"},
                            {content_type:"text",title:"マリナーラ",payload:"マリナーラ"}
                        ]
                    }
                }
            },
            size: {
                message_to_confirm: {
                    line: {
                        type: "template",
                        altText: "サイズはいかがいたしましょうか？ S、M、Lからお選びください。",
                        template: {
                            type: "buttons",
                            text: "サイズはいかがいたしましょうか？",
                            actions: [
                                {type:"postback",label:"S",data:"S"},
                                {type:"postback",label:"M",data:"M"},
                                {type:"postback",label:"L",data:"L"}
                            ]
                        }
                    },
                    facebook: {
                        text: "サイズはいかがいたしましょうか？",
                        quick_replies: [
                            {content_type:"text",title:"S",payload:"S"},
                            {content_type:"text",title:"M",payload:"M"},
                            {content_type:"text",title:"L",payload:"L"}
                        ]
                    }
                }
            },
            address: {
                message_to_confirm: {
                    line: {
                        type: "text",
                        text: "お届け先の住所を教えていただけますか？"
                    },
                    facebook: {
                        text: "お届け先の住所を教えていただけますか？"
                    }
                }
            },
            name: {
                message_to_confirm: {
                    line: {
                        type: "text",
                        text: "最後に、お客様のお名前を教えていただけますか？"
                    },
                    facebook: {
                        text: "最後に、お客様のお名前を教えていただけますか？"
                    }
                }
            }
        };
    }

    // パラメーターが全部揃ったら実行する処理を記述します。
    finish(bot, bot_event, context){
        let messages = [bot.create_message(`${context.confirmed.name} 様、ご注文ありがとうございました！${context.confirmed.pizza}の${context.confirmed.size}サイズを30分以内にご指定の${context.confirmed.address}までにお届けに上がります。`)];
        return bot.reply(bot_event, messages);
    }
};
<|endoftext|>