define([
    'jquery',
    'lodash',
    'Squire',
    'test-helper',
    'bitbucket/internal/util/events'
], function(
    $,
    _,
    Squire,
    testHelper,
    events
) {

    'use strict';

    testHelper.setTemplates({
        'bitbucket.internal.feature.fileContent.conflictMessage': '<div class="CONFLICT"></div>',
        'bitbucket.internal.feature.fileContent.hunkSeparator': '<div class="SEPARATED skipped-container"><div class="content-above"></div><div class="showmore"><span>Show more</span></div><div class="content-below"></div></div>',
        'bitbucket.internal.feature.fileContent.unifiedDiffView.layout': '<div class=diff-editor"></div>',
        'bitbucket.internal.feature.fileContent.diffView.lineNumber': '<div class="line-number"></div>',
        'bitbucket.internal.feature.fileContent.diffView.lineNumberMarker': '<div class="line-number-marker line-locator" data-file-type="" data-line-type="" data-line-number=""></div>',
        'bitbucket.internal.widget.icons.icon': ''
    });

    var injector = new Squire();
    injector.setDefaultDeps(['bitbucket/internal/feature/file-content/unified-diff-view']);
    injector.mock('jquery', $);
    injector.mock('bitbucket/internal/util/events', events);
    injector.mock('bitbucket/internal/util/property', testHelper.mockPropertyUtil({
        'attachment.upload.max.size.data': 10485760
    }));

    function makeDiffView(test, data, fileChange, UnifiedDiffView) {
        test.diffView && test.diffView.destroy();
        test.$f.empty();
        test.diff = data.diff;
        test.fileChange = fileChange;
        test.diffView = new UnifiedDiffView(data, {
            commentMode : 'none',  // comments.commentMode.NONE - avoids loading the dependency
            $container : test.$f,
            fileChange : fileChange
        });
        // _attachScrollBehavior() is not being used in the test - we need to resolve the scroll manually
        test.diffView._attachScrollBehavior = function() { return $.Deferred().resolve(); };
        test.diffView.init();
        return test.diffView;
    }

    module('unified-diff-view', {
        setup: function() {
            this.$f = $('#qunit-fixture');
            sinon.stub(require('bitbucket/internal/util/performance'), 'frameBatchedMap', function(fn, batchLimit, runBatch) {
                return function(arr) {
                    var deferred = $.Deferred();
                    var out;
                    runBatch(function() {
                        out = _.map(arr, fn);
                    });
                    return deferred.resolve(out);
                };
            });
            this._events = events.chain();
            window.WRM = testHelper.mockWRM({
                'com.atlassian.bitbucket.server.bitbucket-web:determine-language.syntax-highlighters': {}
            });
        },
        teardown: function() {
            this.diffView && this.diffView.destroy();
            this.diffView = null;
            this.diff = null;
            this.fileChange = null;
            this.$f = null;
            require('bitbucket/internal/util/performance').frameBatchedMap.restore();
            this._events.destroy();
        }
    });

    var nbsp = '\xa0';

    injector.test('Has correct lines', function (assert, UnifiedDiffView) {
        var self = this;
        var done = assert.async();
        var diffView = makeDiffView(this, getDiffJSON(), getFileChange(), UnifiedDiffView);

        var expectedLines = _.chain(this.diff.hunks[0].segments)
                                .map(function(s) {
                                    var type;
                                    switch(s.type) {
                                        case 'CONTEXT': type = 'context'; break;
                                        case 'ADDED': type = 'added'; break;
                                        case 'REMOVED': type = 'removed'; break;
                                    }
                                    return _.map(s.lines, function(l) {
                                        return {
                                            from : (s.type === 'ADDED' ? nbsp : l.source) + '',
                                            to : (s.type === 'REMOVED' ? nbsp : l.destination) + '',
                                            lineType : s.type,
                                            text : l.line,
                                            type : type,
                                            isMarker : l.conflictMarker === 'MARKER',
                                            isOurs : l.conflictMarker === 'OURS',
                                            isTheirs : l.conflictMarker === 'THEIRS'
                                        };
                                    });
                                })
                                .flatten()
                                .value();

        // diff-view related tests require a setTimeout to wait at least one event loop so that
        // CodeMirror correctly renders all the items. Without this setTimeout the assertions will
        // fail due to elements being missing.
        setTimeout(function () {
            var $lines = self.$f.find('.CodeMirror-code > *');
            var $separators = self.$f.find('.SEPARATED');

            assert.equal($lines.length, expectedLines.length, 'Same number of lines as expected.');
            assert.equal($separators.length, 2, 'Should have leading and trailing separators.');
            // TODO - this check relies on markup shape. Change with unified diff view.
            assert.ok($separators.eq(0).is(':first-child'), 'First is at beginning.');
            assert.ok($separators.eq(1).is(':last-child'), 'Second is at end.');

            _.forEach(expectedLines, function(expectedLine, i) {
                var ith = i + (i > 10 && i < 14 ? 'th' : {1:'st', 2:'nd',3:'rd'}[i % 10] || 'th'); // Sorry, it bothers me.
                var $line = $lines.eq(i);
                assert.ok($line.hasClass(expectedLine.type), 'The ' + ith + ' line should be the type "' + expectedLine.type + '"');

                // NOTE: CodeMirror adds a zero-width space to empty lines and handles it transparently without affecting the document.
                // NOTE: So we're testing a leaky abstraction here:
                var zeroWidthSpace = "\u200B";
                assert.equal($line.find('pre').text(), expectedLine.text || zeroWidthSpace, 'The ' + ith + ' line text should be "' + expectedLine.text + '"');

                assert.strictEqual($line.find('.line-number-from').text(), expectedLine.from, 'The ' + ith + ' from number is correct.');
                assert.strictEqual($line.find('.line-number-to').text(), expectedLine.to, 'The ' + ith + ' to number is correct.');
                assert.strictEqual($line.find('.line-number-marker').attr('data-line-type'), expectedLine.lineType, 'The ' + ith + ' line type is correct.');

                assert.strictEqual($line.hasClass('conflict-marker'), expectedLine.isMarker, 'The ' + ith + ' line text conflict markerness is correct.');
                assert.strictEqual($line.hasClass('conflict-ours'), expectedLine.isOurs, 'The ' + ith + ' line text conflict oursness is correct.');
                assert.strictEqual($line.hasClass('conflict-theirs'), expectedLine.isTheirs, 'The ' + ith + ' line text conflict theirsness is correct.');
            });
            done();
        }, 100);

    });

    injector.test('Events', function (assert, UnifiedDiffView) {
        assert.expect(6);
        var dones = [assert.async(), assert.async(), assert.async()];
        this._events.on('bitbucket.internal.feature.fileContent.diffViewDataLoaded', function(data) {
            // deprecated
            assert.ok(true, 'diffViewDataLoaded event triggered');
            assert.ok(this === window || this === null, 'context is null');
            dones[0]();
        }).on('bitbucket.internal.feature.fileContent.diffViewContentLoaded', function(data) {
            assert.ok(true, 'diffViewContentLoaded event triggered');
            assert.ok(this === window || this === null, 'context is null');
            dones[1]();
        }).on('bitbucket.internal.feature.fileContent.diffViewContentChanged', function(data) {
            assert.ok(true, 'diffViewContentChanged event triggered');
            assert.ok(this === window || this === null, 'context is null');
            dones[2]();
        });

        makeDiffView(this, getDiffJSON(), getFileChange(), UnifiedDiffView);

    });

    function testContextExpansion(separatorIndex, hunks, lineChecks, hasMoreToExpand) {
        injector.test('Context Expanding: ' + separatorIndex, function(assert, UnifiedDiffView) {
            var done = assert.async();
            // Make a new diff view with hunked content.
            var diffView = makeDiffView(this, getHunkedDiffJSON(), getHunkedFileChange(), UnifiedDiffView);
            diffView._options.$container.off('click', '.skipped-container:not(.loading)');
            diffView._options.$container.on('click', '.SEPARATED', function(){
                var $el = $(this);
                return diffView._expandContextLines(diffView._options.fileChange, $el, hunks);
            });

            // diff-view related tests require a setTimeout to wait at least one event loop so that
            // CodeMirror correctly renders all the items. Without this setTimeout the assertions will
            // fail due to elements being missing.
            setTimeout(function() {
                var numSeparators = $('.SEPARATED').length;
                $('.SEPARATED:eq(' + separatorIndex + ')').trigger('click');

                setTimeout(function () {
                    _.forEach(lineChecks, function(check) {
                        var line = check.getLine(diffView);
                        assert.equal(diffView._editor.getLine(line), check.expectedContent, 'Correct content injected at line ' + line);
                    });
                    diffView._options.$container.off('click');

                    diffView._options.$container.find('.line').each(function(i) {
                        var editorHandle = diffView._editor.getLineHandle(i);
                        var editorText = editorHandle.text;
                        var unifiedHandle = diffView.getLineHandle(this);
                        var unifiedText = diffView.getLine(unifiedHandle);
                        assert.equal(unifiedText, editorText, 'StashLineHandle for ' + i + ' is up-to-date with CodeMirror.');
                    });

                    assert.equal($('.SEPARATED').length, numSeparators - (hasMoreToExpand ? 0 : 1), 'Separator should ' + (hasMoreToExpand ? 'stay' : 'go away'));
                    done();
                }, 10);
            }, 10);
        });
    }

    testContextExpansion(0, [
        {"segments": [
            {"type": "CONTEXT", "lines": [
                {"line": "New First Line Content"}
            ]}
        ], "sourceLine": 26, "destinationLine": 26}
    ], [{
        getLine : function() { return 0; },
        expectedContent : 'New First Line Content'
    }],
    true);

    testContextExpansion(1, [
        {"segments": [
            {"type": "CONTEXT", "lines": [
                {"line": "New Middle Line Top Hunk Content"}
            ]}
        ], "sourceLine": 62, "destinationLine": 63},
        {"segments": [
            {"type": "CONTEXT", "lines": [
                {"line": "New Middle Line Bottom Hunk Content"}
            ]}
        ], "sourceLine": 138, "destinationLine": 139}
    ], [{
        getLine : function() { return $('.SEPARATED:eq(1)').closest('.context').index() - 1; },
        expectedContent : "New Middle Line Top Hunk Content"
    }, {
        getLine : function() { return $('.SEPARATED:eq(1)').closest('.context').index(); },
        expectedContent : "New Middle Line Bottom Hunk Content"
    }],
    true);

    testContextExpansion(1, [
        {"segments": [
            {"type": "CONTEXT", "lines": [
                {"line": "New Middle Line"}
            ]}
        ], "sourceLine": 62, "destinationLine": 63}
    ], [{
        getLine : function() { return $('[data-line-number="62"][data-line-type="CONTEXT"]').closest('.context').index(); },
        expectedContent : "New Middle Line"
    }],
    false);

    testContextExpansion(2, [
        {"segments": [
            {"type": "CONTEXT", "lines": [
                {"line": "New Last Line Content"}
            ]}
        ], "sourceLine": 549, "destinationLine": 545}
    ], [{
        getLine : function(dv) { return dv._editor.lastLine(); },
        expectedContent : "New Last Line Content"
    }],
    true);

    // test last line is actually end
    testContextExpansion(2, [], [], false);


    injector.test('Ediff regions', function (assert, UnifiedDiffView) {
        var self = this;
        var done = assert.async();
        var ediffTestJSON = {
            diff : {
                "hunks": [{
                    "sourceLine": 1, "sourceSpan": 10, "destinationLine": 1, "destinationSpan": 10,
                    "segments"  : [
                        {
                            "type"    : "REMOVED", "lines": [
                            {"source": 1, "destination": 1, "line": "Line 1", "truncated": false},
                            {
                                "source"     : 2,
                                "destination": 2,
                                "line"       : "Line 2 Hello World",
                                "truncated"  : false,
                                changed      : ['Line'],
                                deleted      : [' World']
                            },
                            {"source": 3, "destination": 3, "line": "This will be a", "truncated": false},
                            {"source": 4, "destination": 4, "line": "multi-line-change", "truncated": false},
                            {
                                "source"     : 5,
                                "destination": 6,
                                "line"       : "A line (with)(more) [complex] marking()() because-word_boundaries.can/be\\different",
                                "truncated"  : false,
                                changed      : ['with', 'more', 'complex', 'because', 'word_boundaries.', '/', '\\'],
                                deleted      : ['()']
                            },
                        ], "truncated": false
                        },
                        {
                            "type"    : "ADDED", "lines": [
                            {
                                "source"     : 1,
                                "destination": 1,
                                "line"       : "This is Line 1",
                                "truncated"  : false,
                                added        : ['This is ']
                            },
                            {
                                "source"     : 2,
                                "destination": 2,
                                "line"       : "Number 2 Hello",
                                "truncated"  : false,
                                changed      : ['Number']
                            },
                            {"source": 3, "destination": 3, "line": "This will be a", "truncated": false},
                            {
                                "source"     : 4,
                                "destination": 4,
                                "line"       : "multi-line-change going",
                                "truncated"  : false,
                                added        : [' going']
                            },
                            {
                                "source"     : 5,
                                "destination": 5,
                                "line"       : "over more than 1 line",
                                "truncated"  : false,
                                added        : ['over more than 1 line']
                            },
                            {
                                "source"     : 6,
                                "destination": 6,
                                "line"       : "A line (that has)(some) [complicated] marking(hi) because_word-boundaries'can.be/different",
                                "truncated"  : false,
                                added        : ['hi'],
                                changed      : ['that has', 'some', 'complicated', 'because_word', 'boundaries\'', '.', '/']
                            },
                        ], "truncated": false
                        },
                    ]
                }]
            }
        };

        var diffView = makeDiffView(this, ediffTestJSON, getFileChange(), UnifiedDiffView);

        // diff-view related tests require a setTimeout to wait at least one event loop so that
        // CodeMirror correctly renders all the items. Without this setTimeout the assertions will
        // fail due to elements being missing.
        setTimeout(function () {
            var lineIndex = 0;
            var $lines = self.$f.find('.CodeMirror-code > *');
            // go through the lines and check the contain the expected regions
            _.each(ediffTestJSON.diff.hunks[0].segments, function(segment) {
                // go through the lines of this segment
                _.each(segment.lines, function(line) {
                    compareTokensForLine(line, $lines.eq(lineIndex), lineIndex, assert);
                    lineIndex++;
                });

            });
            done();
        }, 100);
    });

    function getTextArrayForEdiffMarkers($line, className) {
        var items = _.pluck($line.find(className), 'textContent');
        items = items.length ? items : undefined;
        return items;
    }

    function compareTokensForLine(line, $line, lineIndex, assert) {
        //get the deleted, changed and added items in a line of code in the editor
        var changed = getTextArrayForEdiffMarkers($line, '.ediff-change');
        var deleted = getTextArrayForEdiffMarkers($line, '.ediff-delete');
        var added = getTextArrayForEdiffMarkers($line, '.ediff-add');

        assert.deepEqual(line.changed, changed, 'Changed markers for line ' + lineIndex + ' match.');
        assert.deepEqual(line.deleted, deleted, 'Deleted markers for line ' + lineIndex + ' match.');
        assert.deepEqual(line.added, added, 'Added markers for line ' + lineIndex + ' match.');
    }


    // NOTE paired with getDiffJSON below
    function getFileChange() {
        return {
            commitRange :  {
                untilRevision : {
                    'id' : "6e4aec51ba340be0cfe4e7d657a1449843584ad2",
                    'displayId' : "6e4aec5",
                    'repository' : {
                        "slug":"stash",
                        "id":2416,
                        "name":"stash",
                        "scmId":"git",
                        "state":"AVAILABLE",
                        "statusMessage":"Available",
                        "forkable":true,
                        "project":{
                            "key":"STASH",
                            "id":2443,
                            "name":"Stash",
                            "description":"Source, Source, Baby!\r\n\r\nPlease don't create test repositories in this project! \\t\\n\\r",
                            "public":false,
                            "type":"NORMAL",
                            "link":{"url":"/projects/STASH","rel":"self"},
                            "avatarUrl":"/projects/STASH/avatar.png?s=64"
                        },
                        "public":false,
                        "cloneUrl":"https://aahmed@stash.dev.internal.atlassian.com/scm/stash/stash.git",
                        "link":{"url":"/projects/STASH/repos/stash/browse","rel":"self"}
                    },
                    'message' : "Something",
                    'author' : {},
                    'authorTimestamp' : 0,
                    'parents' : null,
                    'attributes' : null
                },
                sinceRevision : {
                    'id' : "269beebf313143200f2b51561e5f84e04cf5bd92",
                    'displayId' : "269beeb",
                    'repository' : {
                        "slug":"stash",
                        "id":2416,
                        "name":"stash",
                        "scmId":"git",
                        "state":"AVAILABLE",
                        "statusMessage":"Available",
                        "forkable":true,
                        "project":{
                            "key":"STASH",
                            "id":2443,
                            "name":"Stash",
                            "description":"Source, Source, Baby!\r\n\r\nPlease don't create test repositories in this project! \\t\\n\\r",
                            "public":false,
                            "type":"NORMAL",
                            "link":{"url":"/projects/STASH","rel":"self"},
                            "avatarUrl":"/projects/STASH/avatar.png?s=64"
                        },
                        "public":false,
                        "cloneUrl":"https://aahmed@stash.dev.internal.atlassian.com/scm/stash/stash.git",
                        "link":{"url":"/projects/STASH/repos/stash/browse","rel":"self"}
                    },
                    'message' : "Something",
                    'author' : {},
                    'authorTimestamp' : 0,
                    'parents' : null,
                    'attributes' : null
                }
            },
            srcPath : null,
            path : "plugins/ssh/src/main/resources/atlassian-plugin.xml",
            type : "MODIFY",
            nodeType: "FILE",
            conflict: {
                ourChange : {
                    "srcPath" : null,
                    "path" : "plugins/ssh/src/main/resources/atlassian-plugin.xml",
                    "type" : "MODIFY"
                },
                theirChange : {
                    "srcPath" : null,
                    "path" : "plugins/ssh/src/main/resources/atlassian-plugin.xml",
                    "type" : "MODIFY"
                }
            },
            diff : null,
            srcExecutable : false,
            executable : false,
            repository : {
                project : { key: 'PROJ'},
                slug : 'REPO'
            }
        };
    }

    // NOTE paired with getFileChange above
    function getDiffJSON() {
        return {
            diff: {
                //FIXME remove isLastPage when swithcing to unified-diff-view which doesn't need this hack
                isLastPage          : true,
                relevantContextLines: 10,
                "source"            : {
                    "components": ["plugins", "ssh", "src", "main", "resources", "atlassian-plugin.xml"],
                    "parent"    : "plugins/ssh/src/main/resources",
                    "name"      : "atlassian-plugin.xml",
                    "extension" : "xml",
                    "toString"  : "plugins/ssh/src/main/resources/atlassian-plugin.xml"
                },
                "destination"       : {
                    "components": ["plugins", "ssh", "src", "main", "resources", "atlassian-plugin.xml"],
                    "parent"    : "plugins/ssh/src/main/resources",
                    "name"      : "atlassian-plugin.xml",
                    "extension" : "xml",
                    "toString"  : "plugins/ssh/src/main/resources/atlassian-plugin.xml"
                },
                "hunks"             : [{
                    "sourceLine"     : 4,
                    "sourceSpan"     : 24,
                    "destinationLine": 4,
                    "destinationSpan": 34,
                    "segments"       : [{
                        "type"     : "CONTEXT",
                        "lines"    : [{
                            "source"     : 4,
                            "destination": 4,
                            "line"       : "        <version>${project.version}</version>",
                            "truncated"  : false
                        }, {
                            "source"     : 5,
                            "destination": 5,
                            "line"       : "        <vendor name=\"${project.organization.name}\" url=\"${project.organization.url}\" />",
                            "truncated"  : false
                        }, {
                            "source"     : 6,
                            "destination": 6,
                            "line"       : "    </plugin-info>",
                            "truncated"  : false
                        }, {
                            "source"     : 7,
                            "destination": 7,
                            "line"       : "",
                            "truncated"  : false
                        }, {
                            "source"     : 8,
                            "destination": 8,
                            "line"       : "    <!-- I18N -->",
                            "truncated"  : false
                        }, {
                            "source"     : 9,
                            "destination": 9,
                            "line"       : "",
                            "truncated"  : false
                        }, {
                            "source"     : 10,
                            "destination": 10,
                            "line"       : "    <resource type=\"i18n\" name=\"i18n\" location=\"i18n.messages\"/>",
                            "truncated"  : false
                        }, {
                            "source"     : 11,
                            "destination": 11,
                            "line"       : "",
                            "truncated"  : false
                        }, {
                            "source"     : 12,
                            "destination": 12,
                            "line"       : "    <!-- Component Imports -->",
                            "truncated"  : false
                        }, {
                            "source"     : 13,
                            "destination": 13,
                            "line"       : "",
                            "truncated"  : false
                        }],
                        "truncated": false
                    }, {
                        "type"     : "REMOVED",
                        "lines"    : [{
                            "source"     : 14,
                            "destination": 14,
                            "line"       : "    <component-import key=\"applicationPropertiesService\" interface=\"com.atlassian.stash.server.ApplicationPropertiesService\"/>",
                            "truncated"  : false
                        }],
                        "truncated": false
                    }, {
                        "type"     : "CONTEXT",
                        "lines"    : [{
                            "source"     : 15,
                            "destination": 14,
                            "line"       : "    <component-import key=\"activeObjects\" interface=\"com.atlassian.activeobjects.external.ActiveObjects\"/>",
                            "truncated"  : false
                        }],
                        "truncated": false
                    }, {
                        "type"     : "ADDED",
                        "lines"    : [{
                            "source"     : 16,
                            "destination": 15,
                            "line"       : "    <component-import key=\"applicationPropertiesService\" interface=\"com.atlassian.stash.server.ApplicationPropertiesService\"/>",
                            "truncated"  : false
                        }, {
                            "source"     : 16,
                            "destination": 16,
                            "line"       : "    <component-import key=\"authenticationController\" interface=\"com.atlassian.sal.api.auth.AuthenticationController\" />",
                            "truncated"  : false
                        }, {
                            "source"     : 16,
                            "destination": 17,
                            "line"       : "    <component-import key=\"eventPublisher\" interface=\"com.atlassian.event.api.EventPublisher\"/>",
                            "truncated"  : false
                        }],
                        "truncated": false
                    }, {
                        "type"     : "CONTEXT",
                        "lines"    : [{
                            "source"     : 16,
                            "destination": 18,
                            "line"       : "    <component-import key=\"executorService\" interface=\"java.util.concurrent.ExecutorService\"/>",
                            "truncated"  : false
                        }],
                        "truncated": false
                    }, {
                        "type"     : "ADDED",
                        "lines"    : [{
                            "source"        : 17,
                            "destination"   : 19,
                            "line"          : "<<<<<<<",
                            "truncated"     : false,
                            "conflictMarker": "MARKER"
                        }],
                        "truncated": false
                    }, {
                        "type"     : "CONTEXT",
                        "lines"    : [{
                            "source"        : 17,
                            "destination"   : 20,
                            "line"          : "    <component-import key=\"velocityRenderer\" interface=\"com.atlassian.templaterenderer.velocity.one.six.VelocityTemplateRenderer\" />",
                            "truncated"     : false,
                            "conflictMarker": "OURS"
                        }, {
                            "source"        : 18,
                            "destination"   : 21,
                            "line"          : "    <component-import key=\"pageBuilderService\" interface=\"com.atlassian.webresource.api.assembler.PageBuilderService\"/>",
                            "truncated"     : false,
                            "conflictMarker": "OURS"
                        }],
                        "truncated": false
                    }, {
                        "type"     : "ADDED",
                        "lines"    : [{
                            "source"        : 19,
                            "destination"   : 22,
                            "line"          : "=======",
                            "truncated"     : false,
                            "conflictMarker": "MARKER"
                        }, {
                            "source"        : 19,
                            "destination"   : 23,
                            "line"          : "    <component-import key=\"i18nService\" interface=\"com.atlassian.stash.i18n.I18nService\" />",
                            "truncated"     : false,
                            "conflictMarker": "THEIRS"
                        }, {
                            "source"        : 19,
                            "destination"   : 24,
                            "line"          : "    <component-import key=\"loginUriProvider\" interface=\"com.atlassian.sal.api.auth.LoginUriProvider\" />",
                            "truncated"     : false,
                            "conflictMarker": "THEIRS"
                        }, {
                            "source"        : 19,
                            "destination"   : 25,
                            "line"          : "    <component-import key=\"navBuilder\" interface=\"com.atlassian.stash.nav.NavBuilder\" />",
                            "truncated"     : false,
                            "conflictMarker": "THEIRS"
                        }, {
                            "source"        : 19,
                            "destination"   : 26,
                            "line"          : "    <component-import key=\"permissionService\" interface=\"com.atlassian.stash.user.PermissionService\" />",
                            "truncated"     : false,
                            "conflictMarker": "THEIRS"
                        }, {
                            "source"        : 19,
                            "destination"   : 27,
                            "line"          : "    <component-import key=\"permissionValidationService\" interface=\"com.atlassian.stash.user.PermissionValidationService\" />",
                            "truncated"     : false,
                            "conflictMarker": "THEIRS"
                        }, {
                            "source"        : 19,
                            "destination"   : 28,
                            "line"          : ">>>>>>>",
                            "truncated"     : false,
                            "conflictMarker": "MARKER"
                        }],
                        "truncated": false
                    }, {
                        "type"     : "CONTEXT",
                        "lines"    : [{
                            "source"     : 19,
                            "destination": 29,
                            "line"       : "    <component-import key=\"pluginSettingsFactory\" interface=\"com.atlassian.sal.api.pluginsettings.PluginSettingsFactory\" />",
                            "truncated"  : false
                        }, {
                            "source"     : 20,
                            "destination": 30,
                            "line"       : "    <component-import key=\"pluginAccessor\" interface=\"com.atlassian.plugin.PluginAccessor\" />",
                            "truncated"  : false
                        }, {
                            "source"     : 21,
                            "destination": 31,
                            "line"       : "    <component-import key=\"pluginEventManager\" interface=\"com.atlassian.plugin.event.PluginEventManager\" />",
                            "truncated"  : false
                        }, {
                            "source"     : 22,
                            "destination": 32,
                            "line"       : "    <component-import key=\"transactionTemplate\" interface=\"com.atlassian.sal.api.transaction.TransactionTemplate\" />",
                            "truncated"  : false
                        }, {
                            "source"     : 23,
                            "destination": 33,
                            "line"       : "    <component-import key=\"soyTemplateRenderer\" interface=\"com.atlassian.soy.renderer.SoyTemplateRenderer\" />",
                            "truncated"  : false
                        }, {
                            "source"     : 24,
                            "destination": 34,
                            "line"       : "    <component-import key=\"xsrfTokenAccessor\" interface=\"com.atlassian.sal.api.xsrf.XsrfTokenAccessor\" />",
                            "truncated"  : false
                        }, {
                            "source"     : 25,
                            "destination": 35,
                            "line"       : "    <component-import key=\"xsrfTokenValidator\" interface=\"com.atlassian.sal.api.xsrf.XsrfTokenValidator\" />",
                            "truncated"  : false
                        }, {
                            "source"     : 26,
                            "destination": 36,
                            "line"       : "    <component-import key=\"authenticationController\" interface=\"com.atlassian.sal.api.auth.AuthenticationController\" />",
                            "truncated"  : false
                        }, {
                            "source"     : 27,
                            "destination": 37,
                            "line"       : "    <component-import key=\"loginUriProvider\" interface=\"com.atlassian.sal.api.auth.LoginUriProvider\" />",
                            "truncated"  : false
                        }, {
                            "source"     : 28,
                            "destination": 38,
                            "line"       : "    <component-import key=\"navBuilder\" interface=\"com.atlassian.stash.nav.NavBuilder\" />",
                            "truncated"  : false
                        }],
                        "truncated": false
                    }],
                    "truncated"      : false
                }],
                "truncated"         : false
            }
        };
    }

    function getHunkedDiffJSON(){
        return {
            diff: {
                relevantContextLines: 10,
                "source"            : {
                    "components": ["1longfile.js"],
                    "parent"    : "",
                    "name"      : "1longfile.js",
                    "extension" : "js",
                    "toString"  : "1longfile.js"
                },
                "destination"       : {
                    "components": ["tree-and-diff-view.js"],
                    "parent"    : "",
                    "name"      : "tree-and-diff-view.js",
                    "extension" : "js",
                    "toString"  : "tree-and-diff-view.js"
                },
                "hunks"             : [
                    {
                        "sourceLine": 27, "sourceSpan": 35, "destinationLine": 27, "destinationSpan": 36, "segments": [
                        {
                            "type"    : "CONTEXT", "lines": [
                            {"source": 27, "destination": 27, "line": "    FileContentTypes,", "truncated": false},
                            {"source": 28, "destination": 28, "line": "    Revision,", "truncated": false},
                            {"source": 29, "destination": 29, "line": "    Path,", "truncated": false},
                            {"source": 30, "destination": 30, "line": "    FauxScrollbar,", "truncated": false},
                            {"source": 31, "destination": 31, "line": "    difftree,", "truncated": false},
                            {"source": 32, "destination": 32, "line": "    FileContent,", "truncated": false},
                            {"source": 33, "destination": 33, "line": "    diffImage,", "truncated": false},
                            {"source": 34, "destination": 34, "line": "    exports", "truncated": false},
                            {"source": 35, "destination": 35, "line": ") {", "truncated": false},
                            {
                                "source"     : 36,
                                "destination": 36,
                                "line"       : "    var DiffTree = difftree.DiffTree,",
                                "truncated"  : false
                            }
                        ], "truncated": false
                        },
                        {
                            "type"    : "REMOVED", "lines": [
                            {"source": 37, "destination": 37, "line": "        ROOT = \"ROOT\",", "truncated": false},
                            {"source": 38, "destination": 37, "line": "        _options,", "truncated": false}
                        ], "truncated": false
                        },
                        {
                            "type"    : "ADDED", "lines": [
                            {"source": 39, "destination": 37, "line": "        ROOT = \"ROOT\";", "truncated": false},
                            {"source": 39, "destination": 38, "line": "", "truncated": false},
                            {"source": 39, "destination": 39, "line": "    var _options;", "truncated": false}
                        ], "truncated": false
                        },
                        {
                            "type"    : "CONTEXT", "lines": [
                            {"source": 39, "destination": 40, "line": "", "truncated": false},
                            {"source": 40, "destination": 41, "line": "    //state", "truncated": false}
                        ], "truncated": false
                        },
                        {
                            "type"    : "REMOVED", "lines": [
                            {"source": 41, "destination": 42, "line": "        currentCommitRange,", "truncated": false}
                        ], "truncated": false
                        },
                        {
                            "type"    : "ADDED", "lines": [
                            {"source": 42, "destination": 42, "line": "    var currentCommitRange,", "truncated": false}
                        ], "truncated": false
                        },
                        {
                            "type"    : "CONTEXT", "lines": [
                            {"source": 42, "destination": 43, "line": "        currentFilePath,", "truncated": false}
                        ], "truncated": false
                        },
                        {
                            "type"    : "REMOVED", "lines": [
                            {
                                "source"     : 43,
                                "destination": 44,
                                "line"       : "        changingState = false",
                                "truncated"  : false
                            }
                        ], "truncated": false
                        },
                        {
                            "type"    : "ADDED", "lines": [
                            {
                                "source"     : 44,
                                "destination": 44,
                                "line"       : "        changingState = false;",
                                "truncated"  : false
                            }
                        ], "truncated": false
                        },
                        {
                            "type"    : "CONTEXT", "lines": [
                            {"source": 44, "destination": 45, "line": "", "truncated": false},
                            {
                                "source"     : 45,
                                "destination": 46,
                                "line"       : "    // components/features/widgets",
                                "truncated"  : false
                            }
                        ], "truncated": false
                        },
                        {
                            "type"    : "REMOVED", "lines": [
                            {"source": 46, "destination": 47, "line": "        currentDiffTree,", "truncated": false}
                        ], "truncated": false
                        },
                        {
                            "type"    : "ADDED", "lines": [
                            {"source": 47, "destination": 47, "line": "    var currentDiffTree,", "truncated": false}
                        ], "truncated": false
                        },
                        {
                            "type"    : "CONTEXT", "lines": [
                            {
                                "source"     : 47,
                                "destination": 48,
                                "line"       : "        diffTreesByCommitRangeId = {}, //cache for diff-tree's created for different CommitRanges",
                                "truncated"  : false
                            }
                        ], "truncated": false
                        },
                        {
                            "type"    : "REMOVED", "lines": [
                            {"source": 48, "destination": 49, "line": "        fileContent,", "truncated": false}
                        ], "truncated": false
                        },
                        {
                            "type"    : "ADDED", "lines": [
                            {"source": 49, "destination": 49, "line": "        fileContent;", "truncated": false}
                        ], "truncated": false
                        },
                        {
                            "type"    : "CONTEXT", "lines": [
                            {"source": 49, "destination": 50, "line": "", "truncated": false},
                            {
                                "source"     : 50,
                                "destination": 51,
                                "line"       : "    // Selectors for resizing commitPanes height and scrollbar & spinner",
                                "truncated"  : false
                            }
                        ], "truncated": false
                        },
                        {
                            "type"    : "REMOVED", "lines": [
                            {
                                "source"     : 51,
                                "destination": 52,
                                "line"       : "        $window = $(window),",
                                "truncated"  : false
                            }
                        ], "truncated": false
                        },
                        {
                            "type"    : "ADDED", "lines": [
                            {
                                "source"     : 52,
                                "destination": 52,
                                "line"       : "    var $window = $(window),",
                                "truncated"  : false
                            }
                        ], "truncated": false
                        },
                        {
                            "type"    : "CONTEXT", "lines": [
                            {"source": 52, "destination": 53, "line": "        $footer,", "truncated": false},
                            {"source": 53, "destination": 54, "line": "        $content,", "truncated": false},
                            {"source": 54, "destination": 55, "line": "        $container,", "truncated": false},
                            {"source": 55, "destination": 56, "line": "        $fauxScrollbar,", "truncated": false},
                            {"source": 56, "destination": 57, "line": "        $spinner,", "truncated": false},
                            {"source": 57, "destination": 58, "line": "        windowHeight,", "truncated": false},
                            {"source": 58, "destination": 59, "line": "        diffTreeMaxHeight,", "truncated": false},
                            {"source": 59, "destination": 60, "line": "        $commitFiles,", "truncated": false},
                            {
                                "source"     : 60,
                                "destination": 61,
                                "line"       : "        $commitFileContent,",
                                "truncated"  : false
                            },
                            {"source": 61, "destination": 62, "line": "        $fileTreeContainer,", "truncated": false}
                        ], "truncated": false
                        }
                    ], "truncated"  : false
                    },
                    {
                        "sourceLine"     : 139,
                        "sourceSpan"     : 25,
                        "destinationLine": 140,
                        "destinationSpan": 24,
                        "segments"       : [
                            {
                                "type"    : "CONTEXT", "lines": [
                                {"source": 139, "destination": 140, "line": "", "truncated": false},
                                {
                                    "source"     : 140,
                                    "destination": 141,
                                    "line"       : "        $container.height($container.height());",
                                    "truncated"  : false
                                },
                                {
                                    "source"     : 141,
                                    "destination": 142,
                                    "line"       : "        //temporarily set the height explicitly to the current height to stop the jump when the diffview is removed.",
                                    "truncated"  : false
                                },
                                {
                                    "source"     : 142,
                                    "destination": 143,
                                    "line"       : "        //cleaned up in onTreeAndDiffViewSizeChanged",
                                    "truncated"  : false
                                },
                                {
                                    "source"     : 143,
                                    "destination": 144,
                                    "line"       : "        var scrollTop = $window.scrollTop();",
                                    "truncated"  : false
                                },
                                {"source": 144, "destination": 145, "line": "", "truncated": false},
                                {
                                    "source"     : 145,
                                    "destination": 146,
                                    "line"       : "        return fileContent.init(fileChange, null, null, _options).done(function() {",
                                    "truncated"  : false
                                },
                                {
                                    "source"     : 146,
                                    "destination": 147,
                                    "line"       : "            $commitFileContent = $('#commit-file-content');",
                                    "truncated"  : false
                                },
                                {
                                    "source"     : 147,
                                    "destination": 148,
                                    "line"       : "            $diffViewToolbar = $commitFileContent.find('.source-commands');",
                                    "truncated"  : false
                                },
                                {
                                    "source"     : 148,
                                    "destination": 149,
                                    "line"       : "            $contentView = $commitFileContent.find('.content-view');",
                                    "truncated"  : false
                                }
                            ], "truncated": false
                            },
                            {
                                "type"    : "REMOVED", "lines": [
                                {"source": 538, "destination": 535, "line": "", "truncated": false}
                            ], "truncated": false
                            },
                            {
                                "type"    : "CONTEXT", "lines": [
                                {"source": 539, "destination": 535, "line": "    /**", "truncated": false},
                                {
                                    "source"     : 540,
                                    "destination": 536,
                                    "line"       : "     * @deprecated since 2.0. Use init() instead.",
                                    "truncated"  : false
                                },
                                {
                                    "source"     : 541,
                                    "destination": 537,
                                    "line"       : "     * @type {Function}",
                                    "truncated"  : false
                                },
                                {"source": 542, "destination": 538, "line": "     */", "truncated": false},
                                {
                                    "source"     : 543,
                                    "destination": 539,
                                    "line"       : "    exports.onReady = deprecate.fn(exports.init, 'feature/commit/tree-and-diff-view::onReady', 'feature/commit/tree-and-diff-view::init', '2.0', '3.0');",
                                    "truncated"  : false
                                },
                                {"source": 544, "destination": 540, "line": "", "truncated": false},
                                {
                                    "source"     : 545,
                                    "destination": 541,
                                    "line"       : "    exports.defaults = {",
                                    "truncated"  : false
                                },
                                {
                                    "source"     : 546,
                                    "destination": 542,
                                    "line"       : "        breadcrumbs : true,",
                                    "truncated"  : false
                                },
                                {
                                    "source"     : 547,
                                    "destination": 543,
                                    "line"       : "        sourceLink : true,",
                                    "truncated"  : false
                                },
                                {
                                    "source"     : 548,
                                    "destination": 544,
                                    "line"       : "        changeTypeLozenge : true,",
                                    "truncated"  : false
                                }
                            ], "truncated": false
                            }
                        ],
                        "truncated"      : false
                    }
                ],
                "truncated"         : false,
                "lineComments"      : []
            }
        };
    }

    function getHunkedFileChange() {
        return {
            "repository": {
                "slug": "testrepo",
                "id": 122,
                "name": "testrepo",
                "scmId": "git",
                "state": "AVAILABLE",
                "statusMessage": "Available",
                "forkable": true,
                "project": {"key": "PROJ", "id": 81, "name": "project", "public": false, "type": "NORMAL", "link": {"url": "/projects/PROJ", "rel": "self"}, "avatarUrl": "/stash/projects/PROJ/avatar.png?s=64"},
                "public": false,
                "cloneUrl": "http://admin@localhost:7990/bitbucket/scm/proj/testrepo.git",
                "link": {"url": "/projects/PROJ/repos/testrepo/browse", "rel": "self"}
            },
            "commitRange": {
                "pullRequest": {"id": 2,
                    "version": 21,
                    "title": "Whitespace changes",
                    "description": "* Commit message",
                    "state": "OPEN",
                    "open": true,
                    "closed": false,
                    "createdDate": "2013-10-31T05:47:18.020Z",
                    "updatedDate": "2013-11-20T05:15:53.763Z",
                    "fromRef": {"id": "refs/heads/whitespace-changes",
                        "displayId": "whitespace-changes",
                        "latestCommit": "7baf8a56785c34c103d688d5c9848bc7afa379ec",
                        "repository": {"slug": "testrepo",
                            "id": 122,
                            "name": "testrepo",
                            "scmId": "git",
                            "state": "AVAILABLE",
                            "statusMessage": "Available",
                            "forkable": true,
                            "project": {"key": "PROJ",
                                "id": 81,
                                "name": "project",
                                "public": false,
                                "type": "NORMAL",
                                "link": {"url": "/projects/PROJ",
                                    "rel": "self"},
                                "avatarUrl": "/stash/projects/PROJ/avatar.png?s=64"},
                            "public": false,
                            "cloneUrl": "http://admin@localhost:7990/bitbucket/scm/proj/testrepo.git",
                            "link": {"url": "/projects/PROJ/repos/testrepo/browse",
                                "rel": "self"}},
                        "isDefault": false},
                    "toRef": {"id": "refs/heads/master",
                        "displayId": "master",
                        "latestCommit": "fc61489c44846cc5a8dcbcf4904f9febb825981d",
                        "repository": {"slug": "testrepo",
                            "id": 122,
                            "name": "testrepo",
                            "scmId": "git",
                            "state": "AVAILABLE",
                            "statusMessage": "Available",
                            "forkable": true,
                            "project": {"key": "PROJ",
                                "id": 81,
                                "name": "project",
                                "public": false,
                                "type": "NORMAL",
                                "link": {"url": "/projects/PROJ",
                                    "rel": "self"},
                                "avatarUrl": "/stash/projects/PROJ/avatar.png?s=64"},
                            "public": false,
                            "cloneUrl": "http://admin@localhost:7990/bitbucket/scm/proj/testrepo.git",
                            "link": {"url": "/projects/PROJ/repos/testrepo/browse",
                                "rel": "self"}},
                        "isDefault": false},
                    "author": {"user": {"name": "admin",
                        "emailAddress": "kostya@atlassian.com",
                        "id": 1,
                        "displayName": "<script>alert('ådмин')</script> Very Long Naµe",
                        "active": true,
                        "slug": "admin",
                        "link": {"url": "/users/admin",
                            "rel": "self"},
                        "avatarUrl": "http://www.gravatar.com/avatar/8e02113a15176aec3b33f4df434afbaf.jpg?s=64&d=mm"},
                        "role": "AUTHOR",
                        "approved": false},
                    "reviewers": [],
                    "participants": [],
                    "link": {"url": "/projects/PROJ/repos/testrepo/pull-requests/2",
                        "rel": "self"},
                    "descriptionAsHtml": "<ul>\n  <li>Commit message.</li></ul>"},
                "untilRevision": {"id": "7baf8a56785c34c103d688d5c9848bc7afa379ec"},
                "sinceRevision": {"id": "fc61489c44846cc5a8dcbcf4904f9febb825981d"},
                "id": 2},
            "srcPath": {"_components": ["1longfile.js"]},
            "path": {"_components": ["tree-and-diff-view.js"]},
            "type": "COPY",
            "nodeType": "FILE",
            "srcExecutable": false,
            "executable": false};
    }

});