/*
 * Copyright (c) 2015 - present Adobe Systems Incorporated. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *
 */

/*global describe, beforeEach, afterEach, it, expect */

define(function (require, exports, module) {
    "use strict";

    // Load dependent modules.
    var SpecRunnerUtils     = brackets.getModule("spec/SpecRunnerUtils"),
        SVGCodeHints        = require("./main");

    describe("SVG Code Hints", function () {
        var testContent, testDocument, testEditor;

        // SVG Content that we will be using to run tests against.
        testContent =   "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n" +
                        "<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\"\n" +
                        "     width=\"200\" height=\"200\" preserveAspectRatio=\"xMinYMin meet\">\n" +
                        "    <title>Brackets SVG Code Hints</title>\n" +
                        "    <rect width=\"200\" height=\"200\" baseline-shift=\"baseline\" alignment-baseline=\"alphabetic\" stroke-width=\"1\" color=\"\"></rect>\n" +
                        "    <rect width='160' height='160' x='20' y='20' baseline-shift='super' alignment-baseline='baseline' color='rent' fill='transparent' />\n" +
                        "    <g>\n" +
                        "        \n" +
                        "    </g>\n" +
                        "</svg>\n";

        beforeEach(function () {
            // Create a mock svg document to run tests against.
            var mockEditor = SpecRunnerUtils.createMockEditor(testContent, "svg", {
                startLine: 0,
                endLine: 10
            });
            testEditor = mockEditor.editor;
            testDocument = mockEditor.doc;
        });

        afterEach(function () {
            testEditor.destroy();
            testEditor = null;
        });

        // Returns a list of hints.
        function extractHintList(hints) {
            return $.map(hints, function ($node) {
                return $node.text();
            });
        }

        // Verifies the availability of hints.
        function expectHints(provider) {
            expect(provider.hasHints(testEditor, null)).toBe(true);
            var hintObj = provider.getHints();
            expect(hintObj).toBeTruthy();
            return hintObj.hints;
        }

        // Verifies the non-availability of hints.
        function expectNoHints(provider) {
            expect(provider.hasHints(testEditor, null)).toBe(false);
        }

        // Verifies the presence of a hint.
        function verifyHints(hintList, expectedHint) {
            var hints = extractHintList(hintList);
            expect(hints[0]).toBe(expectedHint);
        }

        // Verifies the exclusion of an unexpected hint.
        function verifyHintsExcluded(hintList, unexpectedHint) {
            var hints = extractHintList(hintList);
            expect(hints.indexOf(unexpectedHint)).toBe(-1);
        }

        // Inserts the hint in document.
        function selectHint(provider, expectedHint) {
            var hintList = expectHints(provider),
                hints = extractHintList(hintList);
            expect(hints.indexOf(expectedHint)).not.toBe(-1);
            return provider.insertHint(expectedHint);
        }

        // Used to test token at given positions.
        function expectTokenAt(pos, string, type) {
            var token = testEditor._codeMirror.getTokenAt(pos);
            expect(token.string).toBe(string);
            expect(token.type).toBe(type);
        }

        // Helper functions for testing cursor position / selection range
        function fixPos(pos) {
            if (!("sticky" in pos)) {
                pos.sticky = null;
            }
            return pos;
        }

        // Used to test cursor position.
        function expectCursorAt(pos) {
            var selection = testEditor.getSelection();
            expect(fixPos(selection.start)).toEqual(fixPos(selection.end));
            expect(fixPos(selection.start)).toEqual(fixPos(pos));
        }

        describe("Tag Hinting", function () {
            it("should hint at < before tag name", function () {
                // After < in <svg
                testEditor.setCursorPos({line: 1, ch: 1});
                var hintList = expectHints(SVGCodeHints.hintProvider);
                verifyHints(hintList, "a");
            });

            it("should hint inside the tag name", function () {
                // After <sv in <svg
                testEditor.setCursorPos({line: 1, ch: 3});
                var hintList = expectHints(SVGCodeHints.hintProvider);
                verifyHints(hintList, "svg");
            });

            it("should hint at the end of the tag", function () {
                // After <svg in <svg
                testEditor.setCursorPos({line: 1, ch: 4});
                var hintList = expectHints(SVGCodeHints.hintProvider);
                verifyHints(hintList, "svg");
            });

            it("should NOT hint in closing tag between < and /", function () {
                // Between < and / in </title>
                testEditor.setCursorPos({line: 3, ch: 35});
                expectNoHints(SVGCodeHints.hintProvider);

                // In case we have space between < and /
                testDocument.replaceRange(" ", {line: 3, ch: 35});
                testEditor.setCursorPos({line: 3, ch: 36});
                expectNoHints(SVGCodeHints.hintProvider);
            });

            it("should NOT hint in closing tag after </", function () {
                // After </ in </title>
                testEditor.setCursorPos({line: 3, ch: 36});
                expectNoHints(SVGCodeHints.hintProvider);
            });

            it("should NOT hint in the middle of closing tag", function () {
                // After </tit in </title>
                testEditor.setCursorPos({line: 3, ch: 39});
                expectNoHints(SVGCodeHints.hintProvider);
            });

            it("should NOT hint at the end of closing tag", function () {
                // Before > in </title>
                testEditor.setCursorPos({line: 3, ch: 41});
                expectNoHints(SVGCodeHints.hintProvider);
            });

            it("should NOT hint after the first space in closing tag", function () {
                // Before > in </title >
                testDocument.replaceRange(" ", {line: 3, ch: 41});
                testEditor.setCursorPos({line: 3, ch: 42});
                expectNoHints(SVGCodeHints.hintProvider);
            });

            // A whitespace between < and tag name makes xml invalid.
            it("should NOT hint in case there is a whitespace between < and tag name", function () {
                // After < in first < rect
                testDocument.replaceRange(" ", {line: 4, ch: 5});
                testEditor.setCursorPos({line: 4, ch: 6});
                expectNoHints(SVGCodeHints.hintProvider);
            });

            it("should NOT hint after first character in an invalid tag", function () {
                // After < r in first < rect
                testDocument.replaceRange(" ", {line: 4, ch: 5});
                testEditor.setCursorPos({line: 4, ch: 7});
                expectNoHints(SVGCodeHints.hintProvider);
            });

            it("should NOT hint in the middle of invalid tag", function () {
                // After < rec in < rect
                testDocument.replaceRange(" ", {line: 4, ch: 5});
                testEditor.setCursorPos({line: 4, ch: 9});
                expectNoHints(SVGCodeHints.hintProvider);
            });

            it("should NOT hint inside the content of a tag", function () {
                // After > in <title>
                testEditor.setCursorPos({line: 3, ch: 11});
                expectNoHints(SVGCodeHints.hintProvider);

                // After Brackets in <title>Brackets
                testEditor.setCursorPos({line: 3, ch: 20});
                expectNoHints(SVGCodeHints.hintProvider);

                // Between <rect></rect>
                testEditor.setCursorPos({line: 4, ch: 119});
                expectNoHints(SVGCodeHints.hintProvider);

                // After <title>+space in <title> Brackets
                testDocument.replaceRange(" ", {line: 3, ch: 11});
                testEditor.setCursorPos({line: 3, ch: 12});
                expectNoHints(SVGCodeHints.hintProvider);
            });

            it("should not hint after the closed tag", function () {
                // After </rect>
                testEditor.setCursorPos({line: 4, ch: 126});
                expectNoHints(SVGCodeHints.hintProvider);

                // After space at </rect>+space
                testDocument.replaceRange(" ", {line: 4, ch: 126});
                testEditor.setCursorPos({line: 4, ch: 127});
                expectNoHints(SVGCodeHints.hintProvider);

                // After />
                testEditor.setCursorPos({line: 5, ch: 136});
                expectNoHints(SVGCodeHints.hintProvider);

                // After space in />+space
                testDocument.replaceRange(" ", {line: 5, ch: 136});
                testEditor.setCursorPos({line: 5, ch: 137});
                expectNoHints(SVGCodeHints.hintProvider);

                // After </g>
                testEditor.setCursorPos({line: 8, ch: 8});
                expectNoHints(SVGCodeHints.hintProvider);

                // After space in </g>+space
                testDocument.replaceRange(" ", {line: 8, ch: 8});
                testEditor.setCursorPos({line: 8, ch: 9});
                expectNoHints(SVGCodeHints.hintProvider);
            });

            it("should hint inside XML declaration", function () {
                // After < in <?xml
                testEditor.setCursorPos({line: 0, ch: 1});
                expectNoHints(SVGCodeHints.hintProvider);

                // After <?xm
                testEditor.setCursorPos({line: 0, ch: 4});
                expectNoHints(SVGCodeHints.hintProvider);

                // After <?xml+space
                testEditor.setCursorPos({line: 0, ch: 6});
                expectNoHints(SVGCodeHints.hintProvider);
            });
        });

        describe("Attribute Hinting", function () {

            it("should hint after first space after tag", function () {
                // After <rect
                testEditor.setCursorPos({line: 4, ch: 10});
                var hintList = expectHints(SVGCodeHints.hintProvider);
                verifyHints(hintList, "class");
            });

            it("should hint after first character of the attribute", function () {
                // After <rect w
                testEditor.setCursorPos({line: 4, ch: 11});
                var hintList = expectHints(SVGCodeHints.hintProvider);
                verifyHints(hintList, "width");
            });

            it("should hint in the middle of the attribute", function () {
                // After <rect wid
                testEditor.setCursorPos({line: 4, ch: 13});
                var hintList = expectHints(SVGCodeHints.hintProvider);
                verifyHints(hintList, "width");
            });

            it("should hint at the end of the attribute", function () {
                // After <rect width
                testEditor.setCursorPos({line: 4, ch: 15});
                var hintList = expectHints(SVGCodeHints.hintProvider);
                verifyHints(hintList, "width");
            });

            it("should NOT hint if we have whitespace between attribute and =", function () {
                // Before = in <rect width =
                testDocument.replaceRange("  ", {line: 4, ch: 15});
                testEditor.setCursorPos({line: 4, ch: 16});
                expectNoHints(SVGCodeHints.hintProvider);
            });

            it("should NOT hint in case there is a space between < and tag name", function () {
                // After space in < rect
                testDocument.replaceRange(" ", {line: 4, ch: 5});
                testEditor.setCursorPos({line: 4, ch: 11});
                expectNoHints(SVGCodeHints.hintProvider);
            });

            it("should exclude hints if they have already been used", function () {
                // In first <rect, after <rect+space
                testEditor.setCursorPos({line: 4, ch: 10});
                var hintLint = expectHints(SVGCodeHints.hintProvider);
                verifyHintsExcluded(hintLint, "width");
                verifyHintsExcluded(hintLint, "height");
                verifyHintsExcluded(hintLint, "baseline-shift");
                verifyHintsExcluded(hintLint, "alignment-baseline");
                verifyHintsExcluded(hintLint, "stroke-width");

                // In the second <rect, after <rect+space
                testEditor.setCursorPos({line: 5, ch: 10});
                hintLint = expectHints(SVGCodeHints.hintProvider);
                verifyHintsExcluded(hintLint, "width");
                verifyHintsExcluded(hintLint, "height");
                verifyHintsExcluded(hintLint, "x");
                verifyHintsExcluded(hintLint, "y");
                verifyHintsExcluded(hintLint, "baseline-shift");
                verifyHintsExcluded(hintLint, "alignment-baseline");
            });

            it("should NOT exclude current token from hints", function () {
                var hintList, hints;

                // After <rect w
                testEditor.setCursorPos({line: 4, ch: 11});
                hintList = expectHints(SVGCodeHints.hintProvider);
                hints    = extractHintList(hintList);
                expect(hints.indexOf("width")).not.toBe(-1);

                // After <rect widt
                testEditor.setCursorPos({line: 4, ch: 14});
                hintList = expectHints(SVGCodeHints.hintProvider);
                hints = extractHintList(hintList);
                expect(hints.indexOf("width")).not.toBe(-1);

                // After <rect width
                testEditor.setCursorPos({line: 5, ch: 15});
                hintList = expectHints(SVGCodeHints.hintProvider);
                hints = extractHintList(hintList);
                expect(hints.indexOf("width")).not.toBe(-1);
            });
        });

        describe("Value Hinting", function () {
            it("should hint after =", function () {
                // After baseline-shift= in second rect.
                testEditor.setCursorPos({line: 5, ch: 64});
                var hintList = expectHints(SVGCodeHints.hintProvider);
                verifyHints(hintList, "baseline");
            });

            it("should hint after ='", function () {
                // After baseline-shift='
                testEditor.setCursorPos({line: 5, ch: 65});
                var hintList = expectHints(SVGCodeHints.hintProvider);
                verifyHints(hintList, "baseline");
            });

            it("should hint after =\"", function () {
                // After baseline-shift="
                testEditor.setCursorPos({line: 4, ch: 51});
                var hintList = expectHints(SVGCodeHints.hintProvider);
                verifyHints(hintList, "baseline");
            });

            it("should hint after first character", function () {
                // After baseline-shift="b
                testEditor.setCursorPos({line: 4, ch: 52});
                var hintList = expectHints(SVGCodeHints.hintProvider);
                verifyHints(hintList, "baseline");
            });

            it("should hint in the middle of value", function () {
                // After baseline-shift="base
                testEditor.setCursorPos({line: 4, ch: 55});
                var hintList = expectHints(SVGCodeHints.hintProvider);
                verifyHints(hintList, "baseline");
            });

            it("should hint at the end of the value", function () {
                // After baseline-shift="baseline
                testEditor.setCursorPos({line: 4, ch: 59});
                var hintList = expectHints(SVGCodeHints.hintProvider);
                verifyHints(hintList, "baseline");
            });

            it("should hint for first attribute in multiple options", function () {
                // After preserveAspectRatio="x
                testEditor.setCursorPos({line: 2, ch: 52});
                var hintList = expectHints(SVGCodeHints.hintProvider);
                verifyHints(hintList, "xMaxYMax");
            });

            it("should hint for second value in multiple options", function () {
                // After m in meet
                testEditor.setCursorPos({line: 2, ch: 61});
                var hintList = expectHints(SVGCodeHints.hintProvider);
                verifyHints(hintList, "meet");
            });

            it("should hint in middle of value in muliple options with empty query", function () {
                // Between xMinYMid and meet
                testDocument.replaceRange(" ", {line: 2, ch: 59});
                testEditor.setCursorPos({ line: 2, ch: 60});
                var hintLint = expectHints(SVGCodeHints.hintProvider);
                verifyHints(hintLint, "none");
            });

            it("should hint in middle of value in multiple options with a query", function () {
                // Between xMinYMid and meet
                testDocument.replaceRange(" sli", {line: 2, ch: 59});
                testEditor.setCursorPos({line: 2, ch: 63});
                var hintLint = expectHints(SVGCodeHints.hintProvider);
                verifyHints(hintLint, "slice");
            });

            it("should NOT hint in case cursor is out of right quote", function () {
                // After meet"
                testEditor.setCursorPos({line: 2, ch: 65});
                expectNoHints(SVGCodeHints.hintProvider);
            });

            it("should NOT hint in an invalid tag", function () {
                // After fill=" in < rect
                testDocument.replaceRange(" ", {line: 4, ch: 5});
                testEditor.setCursorPos({line: 4, ch: 42});
                expectNoHints(SVGCodeHints.hintProvider);
            });

            it("should exclude a value if it is already been used", function () {
                // Between xMinYMid and meet
                testDocument.replaceRange(" ", {line: 2, ch: 59});
                testEditor.setCursorPos({ line: 2, ch: 60});
                var hintLint = expectHints(SVGCodeHints.hintProvider);
                verifyHintsExcluded(hintLint, "xMinYMin");
                verifyHintsExcluded(hintLint, "meet");
            });

            it("should NOT exclude current query from hints", function () {
                var hintList, hints;

                // After xMinYMid
                testDocument.replaceRange(" xMax", {line: 2, ch: 59});
                testEditor.setCursorPos({line: 2, ch: 64});
                hintList = expectHints(SVGCodeHints.hintProvider);
                hints = extractHintList(hintList);
                expect(hints.indexOf("xMaxYMax")).not.toBe(-1);
            });
        });

        describe("Color names and swatches", function () {
            it("should show color swatches", function () {
                // After color="
                testEditor.setCursorPos({line: 4, ch: 117});
                var hints = expectHints(SVGCodeHints.hintProvider);
                verifyHints(hints, "aliceblue"); // first hint should be aliceblue
                expect(hints[0].find(".color-swatch").length).toBe(1);
                // CEF 2623 will output "aliceblue" whereas earlier versions give "rgb(240, 248, 255)",
                // so we need this ugly hack to make sure this test passes on both
                expect(hints[0].find(".color-swatch").css("backgroundColor")).toMatch(/^rgb\(240, 248, 255\)$|aliceblue/);
            });

            it("should always include transparent and currentColor and they should not have a swatch, but class no-swatch-margin", function () {
                // After color='rent
                testEditor.setCursorPos({line: 5, ch: 113});
                var hints = expectHints(SVGCodeHints.hintProvider);
                verifyHints(hints, "currentColor"); // first hint should be currentColor
                expect(hints[0].find(".color-swatch").length).toBe(0); // no swatch for currentColor
                expect(hints[2].find(".color-swatch").length).toBe(0); // no swatch for transparent
                expect(hints[0].hasClass("no-swatch-margin")).toBeTruthy(); // no-swatch-margin applied to currentColor
                expect(hints[2].hasClass("no-swatch-margin")).toBeTruthy(); // no-swatch-margin applied to transparent
            });

            it("should remove class no-swatch-margin from transparent if it's the only one in the list", function () {
                // After fill='transparent
                testEditor.setCursorPos({line: 5, ch: 132});
                var hints = expectHints(SVGCodeHints.hintProvider);
                verifyHints(hints, "transparent");
                expect(hints.length).toBe(1); // transparent should be the only hint
                expect(hints[0].find(".color-swatch").length).toBe(0); // no swatch for transparent
                expect(hints[0].hasClass("no-swatch-margin")).toBeFalsy(); // no-swatch-margin not applied to transparent
            });
        });

        describe("Tag Insertion", function () {
            it("should insert if query is empty", function () {
                // After < inside <g>
                testDocument.replaceRange("<", { line: 7, ch: 8});
                testEditor.setCursorPos({line: 7, ch: 9});
                selectHint(SVGCodeHints.hintProvider, "a");
                expectTokenAt({line: 7, ch: 10}, "a", "tag");
                expectCursorAt({line: 7, ch: 10});
            });

            it("should insert if query is one character long", function () {
                // After <d inside <g>
                testDocument.replaceRange("<d", {line: 7, ch: 8});
                testEditor.setCursorPos({line: 7, ch: 10});
                selectHint(SVGCodeHints.hintProvider, "defs");
                expectTokenAt({line: 7, ch: 11}, "defs", "tag");
                expectCursorAt({line: 7, ch: 13});
            });

            it("should insert if query is complete", function () {
                // After <defs inside <g>
                testDocument.replaceRange("<defs", {line: 7, ch: 8});
                testEditor.setCursorPos({line: 7, ch: 13});
                selectHint(SVGCodeHints.hintProvider, "defs");
                expectTokenAt({line: 7, ch: 13}, "defs", "tag");
                expectCursorAt({line: 7, ch: 13});
            });
        });

        describe("Attribute Insertion", function () {
            it("should insert if query is empty", function () {
                // After <defs+space inside <g>
                testDocument.replaceRange("<defs ", {line: 7, ch: 8});
                testEditor.setCursorPos({line: 7, ch: 14});
                selectHint(SVGCodeHints.hintProvider, "alignment-baseline");
                expectTokenAt({line: 7, ch: 32}, "alignment-baseline", "attribute");
                expectTokenAt({line: 7, ch: 33}, "=", null);
                expectTokenAt({line: 7, ch: 35}, "\"\"", "string");
                expectCursorAt({line: 7, ch: 34});
            });

            it("should insert if query is one character long", function () {
                // After <defs b inside <g>
                testDocument.replaceRange("<defs b", {line: 7, ch: 8});
                testEditor.setCursorPos({line: 7, ch: 15});
                selectHint(SVGCodeHints.hintProvider, "baseline-shift");
                expectTokenAt({line: 7, ch: 28}, "baseline-shift", "attribute");
                expectTokenAt({line: 7, ch: 29}, "=", null);
                expectTokenAt({line: 7, ch: 31}, "\"\"", "string");
                expectCursorAt({line: 7, ch: 30});
            });

            it("should insert if query is complete", function () {
                // After <defs clip-path inside <g>
                testDocument.replaceRange("<defs clip-path", {line: 7, ch: 8});
                testEditor.setCursorPos({line: 7, ch: 23});
                selectHint(SVGCodeHints.hintProvider, "clip-path");
                expectTokenAt({line: 7, ch: 23}, "clip-path", "attribute");
                expectTokenAt({line: 7, ch: 24}, "=", null);
                expectTokenAt({line: 7, ch: 26}, "\"\"", "string");
                expectCursorAt({line: 7, ch: 25});
            });

            it("should NOT overide =", function () {
                // Between clip-path and "inherit" in <g>
                testDocument.replaceRange("<defs clip-path=\"inherit\"", {line: 7, ch: 8});
                testEditor.setCursorPos({line: 7, ch: 18});
                expectTokenAt({line: 7, ch: 24}, "=", null);
                selectHint(SVGCodeHints.hintProvider, "clip-rule");
                expectTokenAt({line: 7, ch: 23}, "clip-rule", "attribute");
                expectTokenAt({line: 7, ch: 24}, "=", null);
                expectCursorAt({line: 7, ch: 23});
            });
        });

        describe("Value Insertion", function () {
            it("should insert if = is typed after an attribute", function () {
                // after clip-path= inside <g>
                testDocument.replaceRange("<defs clip-path=", {line: 7, ch: 8});
                testEditor.setCursorPos({line: 7, ch: 24});
                selectHint(SVGCodeHints.hintProvider, "inherit");
                expectTokenAt({line: 7, ch: 24}, "=", null);
                expectTokenAt({line: 7, ch: 33}, "\"inherit\"", "string");
                expectCursorAt({line: 7, ch: 33});
            });

            it("should insert if =\" is typed after an attribute", function () {
                // After clip-path=" inside <g>
                testDocument.replaceRange("<defs clip-path=\"", {line: 7, ch: 8});
                testEditor.setCursorPos({line: 7, ch: 25});
                selectHint(SVGCodeHints.hintProvider, "inherit");
                expectTokenAt({line: 7, ch: 24}, "=", null);
                expectTokenAt({line: 7, ch: 33}, "\"inherit\"", "string");
                expectCursorAt({line: 7, ch: 33});
            });

            it("should insert if =' is typed after an attribute", function () {
                // After =' inside <g>
                testDocument.replaceRange("<defs clip-path='", {line: 7, ch: 8});
                testEditor.setCursorPos({line: 7, ch: 25});
                selectHint(SVGCodeHints.hintProvider, "inherit");
                expectTokenAt({line: 7, ch: 24}, "=", null);
                expectTokenAt({line: 7, ch: 33}, "'inherit'", "string");
                expectCursorAt({line: 7, ch: 33});
            });

            it("should insert if first character is typed after =\"", function () {
                // After clip-path="i inside <g>
                testDocument.replaceRange("<defs clip-path=\"i", {line: 7, ch: 8});
                testEditor.setCursorPos({line: 7, ch: 26});
                selectHint(SVGCodeHints.hintProvider, "inherit");
                expectTokenAt({line: 7, ch: 24}, "=", null);
                expectTokenAt({line: 7, ch: 33}, "\"inherit\"", "string");
                expectCursorAt({line: 7, ch: 33});
            });

            it("should insert if first character is typed after ='", function () {
                // After clip-path='i inside <g>
                testDocument.replaceRange("<defs clip-path='i", {line: 7, ch: 8});
                testEditor.setCursorPos({line: 7, ch: 26});
                selectHint(SVGCodeHints.hintProvider, "inherit");
                expectTokenAt({line: 7, ch: 24}, "=", null);
                expectTokenAt({line: 7, ch: 33}, "'inherit'", "string");
                expectCursorAt({line: 7, ch: 33});
            });

            it("should insert if we are in middle of query after =\"", function () {
                // After clip-path="inhe inside <g>
                testDocument.replaceRange("<defs clip-path=\"inhe", {line: 7, ch: 8});
                testEditor.setCursorPos({line: 7, ch: 29});
                selectHint(SVGCodeHints.hintProvider, "inherit");
                expectTokenAt({line: 7, ch: 24}, "=", null);
                expectTokenAt({line: 7, ch: 33}, "\"inherit\"", "string");
                expectCursorAt({line: 7, ch: 33});
            });

            it("should insert if we are in middle of query after ='", function () {
                // After clip-path='inhe inside <g>
                testDocument.replaceRange("<defs clip-path='inhe", {line: 7, ch: 8});
                testEditor.setCursorPos({line: 7, ch: 29});
                selectHint(SVGCodeHints.hintProvider, "inherit");
                expectTokenAt({line: 7, ch: 24}, "=", null);
                expectTokenAt({line: 7, ch: 33}, "'inherit'", "string");
                expectCursorAt({line: 7, ch: 33});
            });

            it("should insert if we are in the end of value after ='", function () {
                // Before last ' in clip-path='inherit' inside <g>
                testDocument.replaceRange("<defs clip-path='inherit'", {line: 7, ch: 8});
                testEditor.setCursorPos({line: 7, ch: 32});
                selectHint(SVGCodeHints.hintProvider, "inherit");
                expectTokenAt({line: 7, ch: 24}, "=", null);
                expectTokenAt({line: 7, ch: 33}, "'inherit'", "string");
                expectCursorAt({line: 7, ch: 33});
            });

            it("should insert if we are in the end of value after =\"", function () {
                // Before last " in clip-path="inherit" inside <g>
                testDocument.replaceRange("<defs clip-path=\"inherit\"", {line: 7, ch: 8});
                testEditor.setCursorPos({line: 7, ch: 32});
                selectHint(SVGCodeHints.hintProvider, "inherit");
                expectTokenAt({line: 7, ch: 24}, "=", null);
                expectTokenAt({line: 7, ch: 33}, "\"inherit\"", "string");
                expectCursorAt({line: 7, ch: 33});
            });

            it("should insert value to left in a multiple options attribute", function () {
                // Between "" in transform="" inside <g>
                testDocument.replaceRange("<rect transform=\"\"", {line: 7, ch: 8});
                testEditor.setCursorPos({line: 7, ch: 25});
                selectHint(SVGCodeHints.hintProvider, "matrix()");
                expectTokenAt({line: 7, ch: 24}, "=", null);
                expectTokenAt({line: 7, ch: 34}, "\"matrix()\"", "string");
                expectCursorAt({line: 7, ch: 34});
            });

            it("should insert value to the right in a multiple options attribute", function () {
                // After "matrix() " inside <g>
                testDocument.replaceRange("<rect transform=\"matrix() \"", {line: 7, ch: 8});
                testEditor.setCursorPos({line: 7, ch: 34});
                selectHint(SVGCodeHints.hintProvider, "rotate()");
                expectTokenAt({line: 7, ch: 24}, "=", null);
                expectTokenAt({line: 7, ch: 34}, "\"matrix() rotate()\"", "string");
                expectCursorAt({line: 7, ch: 43});
            });

            it("should insert value in the middle in a multiple options attribute", function () {
                // Between matrix() and rotate() in "matrix()  rotate()"
                testDocument.replaceRange("<rect transform=\"matrix()  rotate()\"", {line: 7, ch: 8});
                testEditor.setCursorPos({line: 7, ch: 34});
                selectHint(SVGCodeHints.hintProvider, "scale()");
                expectTokenAt({line: 7, ch: 24}, "=", null);
                expectTokenAt({line: 7, ch: 34}, "\"matrix() scale() rotate()\"", "string");
                expectCursorAt({line: 7, ch: 41});
            });
        });
    });
});
