/*
 * Copyright (c) 2013 - 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, it, xit, expect, beforeEach, afterEach */

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

    var SpecRunnerUtils = brackets.getModule("spec/SpecRunnerUtils"),
        testContentCSS  = require("text!unittest-files/regions.css"),
        testContentHTML = require("text!unittest-files/region-template.html"),
        CSSCodeHints    = require("main");

    describe("CSS Code Hinting", function () {

        var defaultContent = "@media screen { \n" +
                             " body { \n" +
                             " }\n" +
                             "} \n" +
                             ".selector { \n" +
                             " \n" +
                             " b\n" +
                             " bord\n" +
                             " border-\n" +
                             " border-colo\n" +
                             " border-color: red;\n" +      // line: 10
                             " d\n" +
                             " disp\n" +
                             " display: \n" +
                             " display: in\n" +
                             " bordborder: \n" +
                             " color\n" +
                             "} \n";
                             
        var defaultHTMLContent = "<html> \n" +
                                 "<head> \n" +
                                 "</head> \n" +
                                 "<body> \n" +
                                 "<div style=' \n" + // line 4
                                 " \n" +
                                 " b\n" +
                                 " bord\n" +
                                 " border-\n" +
                                 " border-colo\n" +
                                 " border-color: red;'>\n" + // line 10
                                 "</div> \n" +
                                 "</body> \n" +
                                 "</html> \n";

        var testDocument, testEditor;

        /*
         * Create a mockup editor with the given content and language id.
         *
         * @param {string} content - content for test window
         * @param {string} languageId
         */
        function setupTest(content, languageId) {
            var mock = SpecRunnerUtils.createMockEditor(content, languageId);
            testDocument = mock.doc;
            testEditor = mock.editor;
        }

        function tearDownTest() {
            SpecRunnerUtils.destroyMockEditor(testDocument);
            testEditor = null;
            testDocument = null;
        }

        function extractHintList(hints) {
            return $.map(hints, function ($node) {
                return $node.text();
            });
        }

        // Ask provider for hints at current cursor position; expect it to return some
        function expectHints(provider, implicitChar, returnWholeObj) {
            expect(provider.hasHints(testEditor, implicitChar)).toBe(true);
            var hintsObj = provider.getHints();
            expect(hintsObj).toBeTruthy();
            // return just the array of hints if returnWholeObj is falsy
            return returnWholeObj ? hintsObj : extractHintList(hintsObj.hints);
        }

        // Ask provider for hints at current cursor position; expect it NOT to return any
        function expectNoHints(provider, implicitChar) {
            expect(provider.hasHints(testEditor, implicitChar)).toBe(false);
        }

        function verifyAttrHints(hintList, expectedFirstHint) {
            expect(hintList.indexOf("div")).toBe(-1);
            expect(hintList[0]).toBe(expectedFirstHint);
        }

        // compares lists to ensure they are the same
        function verifyListsAreIdentical(hintList, values) {
            var i;
            expect(hintList.length).toBe(values.length);
            for (i = 0; i < values.length; i++) {
                expect(hintList[i]).toBe(values[i]);
            }
        }


        function selectHint(provider, expectedHint, implicitChar) {
            var hintList = expectHints(provider, implicitChar);
            expect(hintList.indexOf(expectedHint)).not.toBe(-1);
            return provider.insertHint(expectedHint);
        }

        // Helper function for testing cursor position
        function fixPos(pos) {
            if (!("sticky" in pos)) {
                pos.sticky = null;
            }
            return pos;
        }
        function expectCursorAt(pos) {
            var selection = testEditor.getSelection();
            expect(fixPos(selection.start)).toEqual(fixPos(selection.end));
            expect(fixPos(selection.start)).toEqual(fixPos(pos));
        }

        // Helper function to
        // a) ensure the hintList and the list with the available values have the same size
        // b) ensure that all possible values are mentioned in the hintList
        function verifyAllValues(hintList, values) {
            expect(hintList.length).toBe(values.length);
            expect(hintList.sort().toString()).toBe(values.sort().toString());
        }

        describe("CSS properties in general (selection of correct property based on input)", function () {

            beforeEach(function () {
                // create Editor instance (containing a CodeMirror instance)
                var mock = SpecRunnerUtils.createMockEditor(defaultContent, "css");
                testEditor = mock.editor;
                testDocument = mock.doc;
            });

            afterEach(function () {
                SpecRunnerUtils.destroyMockEditor(testDocument);
                testEditor = null;
                testDocument = null;
            });

            it("should list all prop-name hints right after curly bracket", function () {
                testEditor.setCursorPos({ line: 4, ch: 11 });    // after {
                var hintList = expectHints(CSSCodeHints.cssPropHintProvider);
                verifyAttrHints(hintList, "align-content");  // filtered on "empty string"
            });

            it("should list all prop-name hints in new line", function () {
                testEditor.setCursorPos({ line: 5, ch: 1 });

                var hintList = expectHints(CSSCodeHints.cssPropHintProvider);
                verifyAttrHints(hintList, "align-content");  // filtered on "empty string"
            });

            it("should list all prop-name hints starting with 'b' in new line", function () {
                testEditor.setCursorPos({ line: 6, ch: 2 });

                var hintList = expectHints(CSSCodeHints.cssPropHintProvider);
                verifyAttrHints(hintList, "backface-visibility");  // filtered on "b"
            });

            it("should list all prop-name hints starting with 'bord' ", function () {
                // insert semicolon after previous rule to avoid incorrect tokenizing
                testDocument.replaceRange(";", { line: 6, ch: 2 });

                testEditor.setCursorPos({ line: 7, ch: 5 });
                var hintList = expectHints(CSSCodeHints.cssPropHintProvider);
                verifyAttrHints(hintList, "border");  // filtered on "bord"
            });

            it("should list all prop-name hints starting with 'border-' ", function () {
                // insert semicolon after previous rule to avoid incorrect tokenizing
                testDocument.replaceRange(";", { line: 7, ch: 5 });

                testEditor.setCursorPos({ line: 8, ch: 8 });
                var hintList = expectHints(CSSCodeHints.cssPropHintProvider);
                verifyAttrHints(hintList, "border-bottom");  // filtered on "border-"
            });

            it("should list only prop-name hint border-color", function () {
                // insert semicolon after previous rule to avoid incorrect tokenizing
                testDocument.replaceRange(";", { line: 8, ch: 8 });

                testEditor.setCursorPos({ line: 9, ch: 12 });
                var hintList = expectHints(CSSCodeHints.cssPropHintProvider);
                verifyAttrHints(hintList, "border-color");  // filtered on "border-color"
                verifyListsAreIdentical(hintList, ["border-color",
                                                   "border-left-color",
                                                   "border-top-color",
                                                   "border-bottom-color",
                                                   "border-right-color"]);
            });

            it("should list prop-name hints at end of property-value finished by ;", function () {
                testEditor.setCursorPos({ line: 10, ch: 19 });    // after ;
                var hintList = expectHints(CSSCodeHints.cssPropHintProvider);
                verifyAttrHints(hintList, "align-content");  // filtered on "empty string"
            });

            it("should NOT list prop-name hints right before curly bracket", function () {
                testEditor.setCursorPos({ line: 4, ch: 10 });    // inside .selector, before {
                expectNoHints(CSSCodeHints.cssPropHintProvider);
            });

            it("should NOT list prop-name hints after declaration of mediatype", function () {
                testEditor.setCursorPos({ line: 0, ch: 15 });    // after {
                expectNoHints(CSSCodeHints.cssPropHintProvider);
            });

            it("should NOT list prop-name hints if previous property is not closed properly", function () {
                testEditor.setCursorPos({ line: 16, ch: 6 });   // cursor directly after color
                expectNoHints(CSSCodeHints.cssPropHintProvider);
            });
            it("should NOT list prop-name hints in media type declaration", function () {
                testEditor.setCursorPos({ line: 0, ch: 1 });
                expect(CSSCodeHints.cssPropHintProvider.hasHints(testEditor, 'm')).toBe(false);
            });
        });

        describe("CSS property hint insertion", function () {
            beforeEach(function () {
                // create Editor instance (containing a CodeMirror instance)
                var mock = SpecRunnerUtils.createMockEditor(defaultContent, "css");
                testEditor = mock.editor;
                testDocument = mock.doc;
            });

            afterEach(function () {
                SpecRunnerUtils.destroyMockEditor(testDocument);
                testEditor = null;
                testDocument = null;
            });

            it("should insert colon prop-name selected", function () {
                // insert semicolon after previous rule to avoid incorrect tokenizing
                testDocument.replaceRange(";", { line: 6, ch: 2 });

                testEditor.setCursorPos({ line: 7, ch: 5 });   // cursor after 'bord'
                selectHint(CSSCodeHints.cssPropHintProvider, "border");
                expect(testDocument.getLine(7)).toBe(" border: ");
                expectCursorAt({ line: 7, ch: 9 });
            });

            it("should not insert semicolon after prop-value selected", function () {
                testDocument.replaceRange(";", { line: 12, ch: 5 });
                testEditor.setCursorPos({ line: 13, ch: 10 });   // cursor after 'display: '
                selectHint(CSSCodeHints.cssPropHintProvider, "block");
                expect(testDocument.getLine(13)).toBe(" display: block");
            });

            it("should insert prop-name directly after semicolon", function () {
                testEditor.setCursorPos({ line: 10, ch: 19 });   // cursor after red;
                selectHint(CSSCodeHints.cssPropHintProvider, "align-content");
                expect(testDocument.getLine(10)).toBe(" border-color: red;align-content: ");
            });

            it("should insert nothing but the closure(semicolon) if prop-value is fully written", function () {
                testDocument.replaceRange(";", { line: 15, ch: 13 }); // insert text ;
                testEditor.setCursorPos({ line: 16, ch: 6 });   // cursor directly after color
                selectHint(CSSCodeHints.cssPropHintProvider, "color");
                expect(testDocument.getLine(16)).toBe(" color: ");
                expectCursorAt({ line: 16, ch: 8 });
            });

            it("should insert prop-name before an existing one", function () {
                testEditor.setCursorPos({ line: 10, ch: 1 });   // cursor before border-color:
                selectHint(CSSCodeHints.cssPropHintProvider, "float");
                expect(testDocument.getLine(10)).toBe(" float:  border-color: red;");
                expectCursorAt({ line: 10, ch: 8 });
            });

            it("should insert prop-name before an existing one when invoked with an implicit character", function () {
                testDocument.replaceRange("f", { line: 10, ch: 1 }); // insert "f" before border-color:
                testEditor.setCursorPos({ line: 10, ch: 2 });        // set cursor before border-color:
                selectHint(CSSCodeHints.cssPropHintProvider, "float", "f");
                expect(testDocument.getLine(10)).toBe(" float:  border-color: red;");
                expectCursorAt({ line: 10, ch: 8 });
            });

            it("should replace the existing prop-value with the new selection", function () {
                testDocument.replaceRange(";", { line: 12, ch: 5 });
                testDocument.replaceRange("block", { line: 13, ch: 10 });
                testEditor.setCursorPos({ line: 13, ch: 10 });   // cursor before block
                selectHint(CSSCodeHints.cssPropHintProvider, "none");
                expect(testDocument.getLine(13)).toBe(" display: none");
                expectCursorAt({ line: 13, ch: 14 });
            });

            xit("should start new hinting whenever there is a whitespace last stringliteral", function () {
                // topic: multi-value properties
                // this needs to be discussed, whether or not this behaviour is aimed for
                // if so, changes to CSSUtils.getInfoAt need to be done imho to classify this
                testDocument.replaceRange(" ", { line: 16, ch: 6 }); // insert whitespace after color
                testEditor.setCursorPos({ line: 16, ch: 7 });   // cursor one whitespace after color
                selectHint(CSSCodeHints.cssPropHintProvider, "color");
                expect(testDocument.getLine(16)).toBe(" color color:");
                expectCursorAt({ line: 16, ch: 13 });
            });
        });

        describe("CSS prop-value hints", function () {
            beforeEach(function () {
                // create Editor instance (containing a CodeMirror instance)
                var mock = SpecRunnerUtils.createMockEditor(defaultContent, "css");
                testEditor = mock.editor;
                testDocument = mock.doc;
            });

            afterEach(function () {
                SpecRunnerUtils.destroyMockEditor(testDocument);
                testEditor = null;
                testDocument = null;
            });

            it("should list all prop-values for 'display' after colon", function () {
                // insert semicolon after previous rule to avoid incorrect tokenizing
                testDocument.replaceRange(";", { line: 12, ch: 5 });

                testEditor.setCursorPos({ line: 13, ch: 9 });
                var hintList = expectHints(CSSCodeHints.cssPropHintProvider);
                verifyAttrHints(hintList, "block");  // filtered after "display:"
            });

            it("should list all prop-values for 'display' after colon and whitespace", function () {
                // insert semicolon after previous rule to avoid incorrect tokenizing
                testDocument.replaceRange(";", { line: 12, ch: 5 });

                testEditor.setCursorPos({ line: 13, ch: 10 });
                var hintList = expectHints(CSSCodeHints.cssPropHintProvider);
                verifyAttrHints(hintList, "block");  // filtered after "display: "
            });

            it("should list all prop-values starting with 'in' for 'display' after colon and whitespace", function () {
                // insert semicolon after previous rule to avoid incorrect tokenizing
                testDocument.replaceRange(";", { line: 13, ch: 10 });

                testEditor.setCursorPos({ line: 14, ch: 12 });
                var hintList = expectHints(CSSCodeHints.cssPropHintProvider);
                verifyAttrHints(hintList, "inherit");  // filtered after "display: in"
            });

            it("should NOT list prop-value hints for unknown prop-name", function () {
                testEditor.setCursorPos({ line: 15, ch: 12 });  // at bordborder:
                expectNoHints(CSSCodeHints.cssPropHintProvider);
            });

        });

        describe("CSS hint provider inside mixed htmlfiles", function () {
            var defaultContent = "<html> \n" +
                                 "<head><style>.selector{display: none;}</style></head> \n" +
                                 "<body> <style> \n" +
                                 " body { \n" +
                                 "    background-color: red; \n" +
                                 " \n" +
                                 "} \n" +
                                 "</style>\n" +
                                 "<div class='selector'></div>\n" +
                                 "<style> .foobar { \n" +
                                 " colo </style>\n" +
                                 "</body></html>";

            beforeEach(function () {
                // create dummy Document for the Editor
                var mock = SpecRunnerUtils.createMockEditor(defaultContent, "html");
                testEditor = mock.editor;
                testDocument = mock.doc;
            });

            afterEach(function () {
                SpecRunnerUtils.destroyMockEditor(testDocument);
                testEditor = null;
                testDocument = null;
            });

            it("should list prop-name hints right after curly bracket", function () {
                testEditor.setCursorPos({ line: 3, ch: 7 });  // inside body-selector, after {
                expectHints(CSSCodeHints.cssPropHintProvider);
            });

            it("should list prop-name hints inside single-line styletags at start", function () {
                testEditor.setCursorPos({ line: 1, ch: 23 });  // inside style, after {
                expectHints(CSSCodeHints.cssPropHintProvider);
            });

            it("should list prop-name hints inside single-line styletags after semicolon", function () {
                testEditor.setCursorPos({ line: 1, ch: 37 });  // inside style, after ;
                expectHints(CSSCodeHints.cssPropHintProvider);
            });

            it("should list prop-name hints inside multi-line styletags with cursor in first line", function () {
                testEditor.setCursorPos({ line: 9, ch: 18 });   // inside style, after {
                expectHints(CSSCodeHints.cssPropHintProvider);
            });

            it("should list prop-name hints inside multi-line styletags with cursor in last line", function () {
                testEditor.setCursorPos({ line: 10, ch: 5 });    // inside style, after colo
                var hintList = expectHints(CSSCodeHints.cssPropHintProvider);
                verifyListsAreIdentical(hintList, ["color",
                                                   "border-color",
                                                   "background-color",
                                                   "border-left-color",
                                                   "border-top-color",
                                                   "outline-color",
                                                   "border-bottom-color",
                                                   "border-right-color",
                                                   "text-decoration-color",
                                                   "text-emphasis-color",
                                                   "column-count",
                                                   "column-rule-color",
                                                   "background-blend-mode"]);
            });

            it("should NOT list prop-name hints between closed styletag and new opening styletag", function () {
                testEditor.setCursorPos({ line: 8, ch: 0 });    // right before <div
                expectNoHints(CSSCodeHints.cssPropHintProvider);
            });

            it("should NOT list hints right before curly bracket", function () {
                testEditor.setCursorPos({ line: 3, ch: 6 });    // inside body-selector, before {
                expectNoHints(CSSCodeHints.cssPropHintProvider);
            });

            it("should NOT list hints inside head-tag", function () {
                testEditor.setCursorPos({ line: 1, ch: 6 });    // between <head> and </head> {
                expectNoHints(CSSCodeHints.cssPropHintProvider);
            });

        });
        
        describe("CSS Hint provider in style attribute value context for html mode", function () {

            beforeEach(function () {
                // create Editor instance (containing a CodeMirror instance)
                var mock = SpecRunnerUtils.createMockEditor(defaultHTMLContent, "html");
                testEditor = mock.editor;
                testDocument = mock.doc;
            });

            afterEach(function () {
                SpecRunnerUtils.destroyMockEditor(testDocument);
                testEditor = null;
                testDocument = null;
            });
            
            it("should list all prop-name hints right after the open quote for style value context", function () {
                testEditor.setCursorPos({ line: 4, ch: 12 });    // after "='"
                var hintList = expectHints(CSSCodeHints.cssPropHintProvider);
                verifyAttrHints(hintList, "align-content");  // filtered on "empty string"
            });

            it("should list all prop-name hints in new line for style value context", function () {
                testEditor.setCursorPos({ line: 5, ch: 0 });

                var hintList = expectHints(CSSCodeHints.cssPropHintProvider);
                verifyAttrHints(hintList, "align-content");  // filtered on "empty string"
            });

            it("should list all prop-name hints starting with 'b' in new line for style value context", function () {
                testEditor.setCursorPos({ line: 6, ch: 2 });

                var hintList = expectHints(CSSCodeHints.cssPropHintProvider);
                verifyAttrHints(hintList, "backface-visibility");  // filtered on "b"
            });

            it("should list all prop-name hints starting with 'bord' for style value context", function () {
                // insert semicolon after previous rule to avoid incorrect tokenizing
                testDocument.replaceRange(";", { line: 6, ch: 2 });

                testEditor.setCursorPos({ line: 7, ch: 5 });
                var hintList = expectHints(CSSCodeHints.cssPropHintProvider);
                verifyAttrHints(hintList, "border");  // filtered on "bord"
            });

            it("should list all prop-name hints starting with 'border-' for style value context", function () {
                // insert semicolon after previous rule to avoid incorrect tokenizing
                testDocument.replaceRange(";", { line: 7, ch: 5 });

                testEditor.setCursorPos({ line: 8, ch: 8 });
                var hintList = expectHints(CSSCodeHints.cssPropHintProvider);
                verifyAttrHints(hintList, "border-bottom");  // filtered on "border-"
            });

            it("should list only prop-name hint border-color for style value context", function () {
                // insert semicolon after previous rule to avoid incorrect tokenizing
                testDocument.replaceRange(";", { line: 8, ch: 8 });

                testEditor.setCursorPos({ line: 9, ch: 12 });
                var hintList = expectHints(CSSCodeHints.cssPropHintProvider);
                verifyAttrHints(hintList, "border-color");  // filtered on "border-color"
                verifyListsAreIdentical(hintList, ["border-color",
                                                   "border-left-color",
                                                   "border-top-color",
                                                   "border-bottom-color",
                                                   "border-right-color"]);
            });

            it("should list prop-name hints at end of property-value finished by ; for style value context", function () {
                testEditor.setCursorPos({ line: 10, ch: 19 });    // after ;
                var hintList = expectHints(CSSCodeHints.cssPropHintProvider);
                verifyAttrHints(hintList, "align-content");  // filtered on "empty string"
            });

            it("should NOT list prop-name hints right before style value context", function () {
                testEditor.setCursorPos({ line: 4, ch: 11 });    // after =
                expectNoHints(CSSCodeHints.cssPropHintProvider);
            });

            it("should NOT list prop-name hints after style value context", function () {
                testEditor.setCursorPos({ line: 10, ch: 20 });    // after "'"
                expectNoHints(CSSCodeHints.cssPropHintProvider);
            });
            
        });


        describe("CSS hint provider in other filecontext (e.g. javascript)", function () {
            var defaultContent = "function foobar (args) { \n " +
                                 "    /* do sth */ \n" +
                                 "    return 1; \n" +
                                 "} \n";
            beforeEach(function () {
                // create dummy Document for the Editor
                var mock = SpecRunnerUtils.createMockEditor(defaultContent, "javascript");
                testEditor = mock.editor;
                testDocument = mock.doc;
            });

            afterEach(function () {
                SpecRunnerUtils.destroyMockEditor(testDocument);
                testEditor = null;
                testDocument = null;
            });

            it("should NOT list hints after function declaration", function () {
                testEditor.setCursorPos({ line: 0, ch: 24 });    // after {  after function declaration
                expectNoHints(CSSCodeHints.cssPropHintProvider);
            });
        });

        describe("CSS hint provider cursor placement inside value functions", function () {
            var defaultContent = ".selector { \n" + // line 0
                                 "shape-inside:\n" + // line 1
                                 "}\n"; // line 2

            beforeEach(function () {
                // create dummy Document for the Editor
                var mock = SpecRunnerUtils.createMockEditor(defaultContent, "css");
                testEditor = mock.editor;
                testDocument = mock.doc;
            });

            afterEach(function () {
                SpecRunnerUtils.destroyMockEditor(testDocument);
                testEditor = null;
                testDocument = null;
            });

            it("should should place the cursor between the parens of the value function", function () {
                var expectedString = "shape-inside:polygon()";

                testEditor.setCursorPos({ line: 1, ch: 15 });    // after shape-inside
                expectHints(CSSCodeHints.cssPropHintProvider);
                selectHint(CSSCodeHints.cssPropHintProvider, "polygon()");
                expect(testDocument.getLine(1).length).toBe(expectedString.length);
                expect(testDocument.getLine(1)).toBe(expectedString);
                expectCursorAt({ line: 1, ch: expectedString.length - 1 });
            });
        });

        describe("CSS hint provider for regions and exclusions", function () {
            var defaultContent = ".selector { \n" + // line 0
                                 " shape-inside: \n;" + // line 1
                                 " shape-outside: \n;" + // line 2
                                 " region-fragment: \n;" + // line 3
                                 " region-break-after: \n;" + // line 4
                                 " region-break-inside: \n;" + // line 5
                                 " region-break-before: \n;" + // line 6
                                 " -ms-region\n;" + // line 7
                                 " -webkit-region\n;" + // line 8
                                 " flow-from: \n;" + // line 9
                                 " flow-into: \n;" + // line 10
                                 "}\n"; // line 11

            beforeEach(function () {
                // create dummy Document for the Editor
                var mock = SpecRunnerUtils.createMockEditor(defaultContent, "css");
                testEditor = mock.editor;
                testDocument = mock.doc;
            });

            afterEach(function () {
                SpecRunnerUtils.destroyMockEditor(testDocument);
                testEditor = null;
                testDocument = null;
            });

            it("should list 7 value-name hints for shape-inside", function () {
                testEditor.setCursorPos({ line: 1, ch: 15 });    // after shape-inside
                var hintList = expectHints(CSSCodeHints.cssPropHintProvider);
                verifyAttrHints(hintList, "auto");  // first hint should be auto
                verifyAllValues(hintList, ["auto", "circle()", "ellipse()", "inherit", "outside-shape", "polygon()", "rectangle()"]);
            });

            it("should list 16 value-name hints for shape-outside", function () {
                testEditor.setCursorPos({ line: 2, ch: 16 });    // after shape-outside
                var hintList = expectHints(CSSCodeHints.cssPropHintProvider);
                verifyAttrHints(hintList, "border-box");  // first hint should be border-box
                verifyAllValues(hintList, ["none", "inherit", "circle()", "ellipse()", "polygon()", "inset()", "margin-box", "border-box", "padding-box", "content-box", "url()", "image()", "linear-gradient()", "radial-gradient()", "repeating-linear-gradient()", "repeating-radial-gradient()"]);
            });

            it("should list 2 value-name hints for region-fragment", function () {
                testEditor.setCursorPos({ line: 3, ch: 18 });    // after region-fragment
                var hintList = expectHints(CSSCodeHints.cssPropHintProvider);
                verifyAttrHints(hintList, "auto");  // first hint should be auto
                verifyAllValues(hintList, ["auto", "break"]);
            });

            it("should list 11 value-name hints for region-break-after", function () {
                testEditor.setCursorPos({ line: 4, ch: 21 });    // after region-break-after
                var hintList = expectHints(CSSCodeHints.cssPropHintProvider);
                verifyAttrHints(hintList, "always");  // first hint should be always
                verifyAllValues(hintList, ["always", "auto", "avoid", "avoid-column", "avoid-page", "avoid-region", "column", "left", "page", "region", "right"]);
            });

            it("should list 5 value-name hints for region-break-inside", function () {
                testEditor.setCursorPos({ line: 5, ch: 22 });    // after region-break-inside
                var hintList = expectHints(CSSCodeHints.cssPropHintProvider);
                verifyAttrHints(hintList, "auto");  // first hint should be auto
                verifyAllValues(hintList, ["auto", "avoid", "avoid-column", "avoid-page", "avoid-region"]);
            });

            it("should list 11 value-name hints for region-break-before", function () {
                testEditor.setCursorPos({ line: 6, ch: 23 });    // after region-break-before
                var hintList = expectHints(CSSCodeHints.cssPropHintProvider);
                verifyAttrHints(hintList, "always");  // first hint should be always
                verifyAllValues(hintList, ["always", "auto", "avoid", "avoid-column", "avoid-page", "avoid-region", "column", "left", "page", "region", "right"]);
            });

            // TODO: Need to add vendor prefixed properties for CSS code hint provider.
            xit("should list 4 value-name hints for vendor prefixed region-* properties", function () {
                testEditor.setCursorPos({ line: 7, ch: 16 });    // after -ms-region
                var hintList = expectHints(CSSCodeHints.cssPropHintProvider);
                verifyAttrHints(hintList, "region-break-after");  // first hint should be region-break-after
                verifyAllValues(hintList, ["region-break-after", "region-break-before", "region-break-inside", "region-fragment"]);

                testEditor.setCursorPos({ line: 8, ch: 20 });    // after -webkit-region
                hintList = expectHints(CSSCodeHints.cssPropHintProvider);
                verifyAttrHints(hintList, "region-break-after");  // first hint should be region-break-after
                verifyAllValues(hintList, ["region-break-after", "region-break-before", "region-break-inside", "region-fragment"]);
            });

            it("should list 2 value-name hints for flow-from", function () {
                testEditor.setCursorPos({ line: 9, ch: 12 });    // after flow-from
                var hintList = expectHints(CSSCodeHints.cssPropHintProvider);
                verifyAttrHints(hintList, "inherit");  // first hint should be inherit
                verifyAllValues(hintList, ["inherit", "none"]);
            });

            it("should list 1 value-name hint for flow-into", function () {
                testEditor.setCursorPos({ line: 10, ch: 12 });    // after flow-into
                var hintList = expectHints(CSSCodeHints.cssPropHintProvider);
                verifyAttrHints(hintList, "none");  // first hint should be none
                verifyAllValues(hintList, ["none"]);
            });
        });

        describe("Named flow hints for flow-into and flow-from properties in a CSS file", function () {
            beforeEach(function () {
                setupTest(testContentCSS, "css");
            });

            afterEach(function () {
                tearDownTest();
            });

            it("should list more than 2 value hints for flow-from", function () {
                testEditor.setCursorPos({ line: 66, ch: 15 });    // after flow-from
                var hintList = expectHints(CSSCodeHints.cssPropHintProvider);
                verifyAttrHints(hintList, "edge-code_now_shipping");  // first hint should be edge-code_now_shipping
                verifyAllValues(hintList, ["edge-code_now_shipping", "inherit", "jeff", "lim", "main", "none", "randy"]);
            });

            it("should list more than 1 value hint for flow-into", function () {
                testEditor.setCursorPos({ line: 77, ch: 4 });
                selectHint(CSSCodeHints.cssPropHintProvider, "flow-into");
                expect(testDocument.getLine(77)).toBe("    flow-into: ");
                expectCursorAt({ line: 77, ch: 15 });

                var hintList = expectHints(CSSCodeHints.cssPropHintProvider);
                verifyAttrHints(hintList, "edge-code_now_shipping");  // first hint should be edge-code_now_shipping
                verifyAllValues(hintList, ["edge-code_now_shipping", "jeff", "lim", "main", "none", "randy"]);
            });

            it("should NOT include partially entered named flow value in hint list", function () {
                // Insert a letter for a new named flow after flow-from: on line 66
                testDocument.replaceRange("m", { line: 66, ch: 15 });

                testEditor.setCursorPos({ line: 66, ch: 16 });    // after flow-from: m
                var hintList = expectHints(CSSCodeHints.cssPropHintProvider);
                verifyListsAreIdentical(hintList, ["main", "lim"]);
            });

        });

        describe("Named flow hints inside a style block of an HTML", function () {
            beforeEach(function () {
                setupTest(testContentHTML, "html");
            });

            afterEach(function () {
                tearDownTest();
            });

            it("should include only 2 named flows available in the style block for flow-from", function () {
                testEditor.setCursorPos({ line: 28, ch: 21 });    // after flow-from
                var hintList = expectHints(CSSCodeHints.cssPropHintProvider);
                verifyAttrHints(hintList, "article");  // first hint should be article
                verifyAllValues(hintList, ["article", "inherit", "none", "regionC"]);
            });

            it("should include only 2 named flows available in the style block for flow-into", function () {
                testEditor.setCursorPos({ line: 34, ch: 21 });
                var hintList = expectHints(CSSCodeHints.cssPropHintProvider);
                verifyAttrHints(hintList, "article");  // first hint should be article
                verifyAllValues(hintList, ["article", "none", "regionC"]);
            });

            it("should NOT include partially entered named flow value in hint list", function () {
                // Insert a letter for a new named flow after flow-from: on line 28
                testDocument.replaceRange("m", { line: 28, ch: 21 });

                testEditor.setCursorPos({ line: 28, ch: 22 });    // after flow-from: m
                var hintList = expectHints(CSSCodeHints.cssPropHintProvider);
                verifyAllValues(hintList, []);
            });

            it("should NOT show named flow available inisde HTML text", function () {
                // Insert a letter for a new named flow after flow-from: on line 28
                testDocument.replaceRange("some", { line: 28, ch: 21 });

                testEditor.setCursorPos({ line: 28, ch: 25 });    // after flow-from: some
                var hintList = expectHints(CSSCodeHints.cssPropHintProvider);
                // some-named-flow should not be in the hint list since it is inside HTML text
                verifyAllValues(hintList, []);
            });
        });

        describe("Color names and swatches in a CSS file", function () {
            beforeEach(function () {
                setupTest(testContentCSS, "css");
            });

            afterEach(function () {
                tearDownTest();
            });

            it("should list color names for color", function () {
                testEditor.setCursorPos({ line: 98, ch: 11 }); // after color
                var hintList = expectHints(CSSCodeHints.cssPropHintProvider);
                verifyAttrHints(hintList, "aliceblue"); // first hint should be aliceblue
            });

            it("should show color swatches for background-color", function () {
                testEditor.setCursorPos({ line: 99, ch: 22 }); // after background-color
                var hints = expectHints(CSSCodeHints.cssPropHintProvider, undefined, true).hints;
                expect(hints[0].text()).toBe("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 filter out color names appropriately", function () {
                testEditor.setCursorPos({ line: 100, ch: 27 }); // after border-left-color
                var hintList = expectHints(CSSCodeHints.cssPropHintProvider);
                verifyAttrHints(hintList, "deeppink"); // first hint should be deeppink
                verifyAllValues(hintList, ["deeppink", "deepskyblue"]);
            });

            it("should always include transparent and currentColor and they should not have a swatch, but class no-swatch-margin", function () {
                testEditor.setCursorPos({ line: 101, ch: 22 }); // after border-color
                var hints = expectHints(CSSCodeHints.cssPropHintProvider, undefined, true).hints,
                    hintList = extractHintList(hints);
                verifyAttrHints(hintList, "currentColor"); // first hint should be currentColor
                verifyAllValues(hintList, ["currentColor", "darkmagenta", "transparent"]);
                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 () {
                testEditor.setCursorPos({ line: 103, ch: 22 }); // after color
                var hints = expectHints(CSSCodeHints.cssPropHintProvider, undefined, true).hints,
                    hintList = extractHintList(hints);
                verifyAllValues(hintList, ["transparent"]);
                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
            });

            it("should insert color names correctly", function () {
                var expectedString  = "    border-left-color: deeppink;",
                    line            = 100;

                testEditor.setCursorPos({ line: line, ch: 27 }); // after border-left-color
                expectHints(CSSCodeHints.cssPropHintProvider);
                selectHint(CSSCodeHints.cssPropHintProvider, "deeppink");
                expect(testDocument.getLine(line).length).toBe(expectedString.length);
                expect(testDocument.getLine(line)).toBe(expectedString);
                expectCursorAt({ line: line, ch: expectedString.length - 1 });
            });


            it("should not display color names for unrelated properties", function () {
                testEditor.setCursorPos({ line: 102, ch: 12 }); // after height
                var hintList = expectHints(CSSCodeHints.cssPropHintProvider);
                expect(hintList.indexOf("aliceblue")).toBe(-1);
            });
        });

        describe("Should not invoke CSS hints on space key", function () {
            beforeEach(function () {
                setupTest(testContentHTML, "html");
            });

            afterEach(function () {
                tearDownTest();
            });

            it("should not trigger CSS property name hints with space key", function () {
                testEditor.setCursorPos({ line: 25, ch: 11 });    // after {
                expectNoHints(CSSCodeHints.cssPropHintProvider, " ");
            });

            it("should not trigger CSS property value hints with space key", function () {
                testEditor.setCursorPos({ line: 28, ch: 21 });    // after flow-from
                expectNoHints(CSSCodeHints.cssPropHintProvider, " ");
            });
        });
    });
});

