diff --git a/src/extensions/dev/CSSCodeHints/CSSAttributes.json b/src/extensions/dev/CSSCodeHints/CSSAttributes.json new file mode 100644 index 00000000000..99e6b43f698 --- /dev/null +++ b/src/extensions/dev/CSSCodeHints/CSSAttributes.json @@ -0,0 +1,187 @@ +{ + "align-content": {"values": ["center", "flex-end", "flex-start", "space-around", "space-between", "stretch"]}, + "align-items": {"values": ["baseline", "center", "flex-end", "flex-start", "stretch"]}, + "align-self": {"values": ["auto", "baseline", "center", "flex-end", "flex-start", "stretch"]}, + "animation": {"values": []}, + "animation-delay": {"values": []}, + "animation-direction": {"values": ["alternate", "alternate-reverse", "normal", "reverse"]}, + "animation-duration": {"values": []}, + "animation-fill-mode": {"values": ["backwards", "both", "forwards", "none"]}, + "animation-iteration-count": {"values": []}, + "animation-name": {"values": ["none"]}, + "animation-play-state": {"values": ["paused", "running"]}, + "animation-timing-function": {"values": ["cubic-bezier()", "ease", "ease-in", "ease-in-out", "ease-out", "linear", "step-end", "step-start", "steps()"]}, + "backface-visibility": {"values": ["hidden", "visible"]}, + "background": {"values": []}, + "background-attachment": {"values": ["fixed", "local", "scroll", "inherit"]}, + "background-clip": {"values": ["border-box", "content-box", "padding-box", "inherit"]}, + "background-color": {"values": ["currentColor", "transparent", "inherit"]}, + "background-image": {"values": ["image()", "linear-gradient()", "radial-gradient()", "repeating-linear-gradient()", "repeating-radial-gradient()"]}, + "background-origin": {"values": ["border-box", "content-box", "padding-box", "inherit"]}, + "background-position": {"values": ["left", "center", "right", "bottom", "top"]}, + "background-repeat": {"values": ["no-repeat", "repeat", "repeat-x", "repeat-y", "round", "space"]}, + "background-size": {"values": ["auto", "contain", "cover"]}, + "border": {"values": []}, + "border-collapse": {"values": ["collapse", "separate", "inherit"]}, + "border-color": {"values": ["currentColor", "transparent", "inherit"]}, + "border-spacing": {"values": ["inherit"]}, + "border-style": {"values": ["dashed", "dotted", "double", "groove", "hidden", "inset", "none", "outset", "ridge", "solid", "inherit"]}, + "border-bottom": {"values": []}, + "border-bottom-color": {"values": ["currentColor", "transparent", "inherit"]}, + "border-bottom-left-radius": {"values": []}, + "border-bottom-right-radius": {"values": []}, + "border-bottom-style": {"values": ["dashed", "dotted", "double", "groove", "hidden", "inset", "none", "outset", "ridge", "solid", "inherit"]}, + "border-bottom-width": {"values": ["medium", "thin", "thick", "inherit"]}, + "border-image": {"values": []}, + "border-image-outset": {"values": []}, + "border-image-slice": {"values": []}, + "border-image-source": {"values": []}, + "border-image-repeat": {"values": ["repeat", "round", "space", "stretch"]}, + "border-image-width": {"values": ["auto"]}, + "border-left": {"values": []}, + "border-left-color": {"values": ["currentColor", "transparent", "inherit"]}, + "border-left-style": {"values": ["dashed", "dotted", "double", "groove", "hidden", "inset", "none", "outset", "ridge", "solid", "inherit"]}, + "border-left-width": {"values": ["medium", "thin", "thick", "inherit"]}, + "border-radius": {"values": []}, + "border-right": {"values": []}, + "border-right-color": {"values": ["currentColor", "transparent", "inherit"]}, + "border-right-style": {"values": ["dashed", "dotted", "double", "groove", "hidden", "inset", "none", "outset", "ridge", "solid", "inherit"]}, + "border-right-width": {"values": ["medium", "thin", "thick", "inherit"]}, + "border-top": {"values": []}, + "border-top-color": {"values": ["currentColor", "transparent", "inherit"]}, + "border-top-left-radius": {"values": []}, + "border-top-right-radius": {"values": []}, + "border-top-style": {"values": ["dashed", "dotted", "double", "groove", "hidden", "inset", "none", "outset", "ridge", "solid", "inherit"]}, + "border-top-width": {"values": ["medium", "thin", "thick", "inherit"]}, + "box-decoration-break": {"values": []}, + "box-shadow": {"values": []}, + "box-sizing": {"values": ["border-box", "content-box", "padding-box", "inherit"]}, + "bottom": {"values": []}, + "break-after": {"values": ["always", "auto", "avoid", "avoid-column", "avoid-page", "column", "left", "page", "right"]}, + "break-before": {"values": ["always", "auto", "avoid", "avoid-column", "avoid-page", "column", "left", "page", "right"]}, + "break-inside": {"values": ["auto", "avoid", "avoid-column", "avoid-page"]}, + "caption-side": {"values": ["bottom", "top", "inherit"]}, + "clear": {"values": ["both", "left", "none", "right", "inherit"]}, + "clip": {"values": ["auto", "inherit"]}, + "color": {"values": []}, + "columns": {"values": []}, + "column-count": {"values": []}, + "column-fill": {"values": ["auto", "balance"]}, + "column-gap": {"values": ["normal"]}, + "column-rule": {"values": []}, + "column-rule-color": {"values": ["currentColor"]}, + "column-rule-style": {"values": ["dashed", "dotted", "double", "groove", "hidden", "inset", "none", "outset", "ridge", "solid", "inherit"]}, + "column-rule-width": {"values": ["medium", "thin", "thick", "inherit"]}, + "column-span": {"values": ["all", "none"]}, + "column-width": {"values": ["auto", "inherit"]}, + "content": {"values": ["attr()", "close-quote", "no-close-quote", "no-open-quote", "normal", "none", "open-quote", "inherit"]}, + "counter-increment": {"values": ["none", "inherit"]}, + "counter-reset": {"values": ["none", "inherit"]}, + "cursor": {"values": ["auto", "crosshair", "e-resize", "default", "help", "move", "n-resize", "ne-resize", "nw-resize", "pointer", "progress", "s-resize", "se-resize", "sw-resize", "text", "w-resize", "wait", "inherit"]}, + "direction": {"values": ["ltr", "rtl", "inherit"]}, + "display": {"values": ["block", "flex", "inline", "inline-block", "inline-flex", "inline-table", "list-item", "none", "table", "table-caption", "table-cell", "table-column", "table-column-group", "table-footer-group", "table-header-group", "table-row", "table-row-group", "inherit"]}, + "empty-cells": {"values": ["hide", "show", "inherit"]}, + "flex": {"values": ["none"]}, + "flex-basis": {"values": []}, + "flex-direction": {"values": ["column", "column-reverse", "row", "row-reverse"]}, + "flex-flow": {"values": ["column", "column-reverse", "nowrap", "row", "row-reverse", "wrap", "wrap-reverse"]}, + "flex-grow": {"values": []}, + "flex-shrink": {"values": []}, + "flex-wrap": {"values": ["nowrap", "wrap", "wrap-reverse"]}, + "float": {"values": ["left", "none", "inherit"]}, + "font": {"values": []}, + "font-feature-settings": {"values": []}, + "font-kerning": {"values": ["auto", "none", "normal"]}, + "font-language-override": {"values": ["normal"]}, + "font-size": {"values": []}, + "font-size-adjust": {"values": ["auto", "none"]}, + "font-stretch": {"values": ["condensed", "expanded", "extra-condensed", "extra-expanded", "normal", "semi-condensed", "semi-expanded", "ultra-condensed", "ultra-expanded"]}, + "font-style": {"values": ["italic", "normal", "oblique", "inherit"]}, + "font-synthesis": {"values": ["none", "style", "weight"]}, + "font-variant": {"values": ["normal", "small-caps", "inherit"]}, + "font-weight": {"values": ["bold", "bolder", "lighter", "normal", "100", "200", "300", "400", "500", "600", "700", "800", "900", "inherit"]}, + "height": {"values": ["auto", "inherit"]}, + "hyphens": {"values": ["auto", "manual", "none"]}, + "image-orientation": {"values": []}, + "image-resolution": {"values": ["from-image", "snap"]}, + "justify-content": {"values": ["center", "flex-end", "flex-start", "space-around", "space-between"]}, + "left": {"values": ["auto", "inherit"]}, + "letter-spacing": {"values": ["normal", "inherit"]}, + "line-height": {"values": ["normal", "inherit"]}, + "list-style": {"values": []}, + "list-style-image": {"values": []}, + "list-style-position": {"values": ["inside", "outside", "inherit"]}, + "list-style-type": {"values": ["armenian", "circle", "decimal", "decimal-leading-zero", "disc", "georgian", "lower-alpha", "lower-greek", "lower-latin", "lower-roman", "none", "square", "upper-alpha", "upper-latin", "upper-roman", "inherit"]}, + "margin": {"values": []}, + "margin-bottom": {"values": []}, + "margin-left": {"values": []}, + "margin-right": {"values": []}, + "margin-top": {"values": []}, + "max-height": {"values": ["none", "inherit"]}, + "max-width": {"values": ["none", "inherit"]}, + "min-height": {"values": ["inherit"]}, + "min-width": {"values": ["inherit"]}, + "object-fit": {"values": ["contain", "cover", "fill", "none", "scale-down"]}, + "object-position": {"values": ["left", "center", "right", "bottom", "top"]}, + "order": {"values": []}, + "orphans": {"values": ["inherit"]}, + "outline": {"values": []}, + "outline-color": {"values": ["invert", "inherit"]}, + "outline-offset": {"values": ["inherit"]}, + "outline-style": {"values": ["dashed", "dotted", "double", "groove", "hidden", "inset", "none", "outset", "ridge", "solid", "inherit"]}, + "outline-width": {"values": ["medium", "thin", "thick", "inherit"]}, + "overflow": {"values": ["auto", "hidden", "scroll", "visible", "inherit"]}, + "overflow-x": {"values": ["auto", "hidden", "scroll", "visible", "inherit"]}, + "overflow-y": {"values": ["auto", "hidden", "scroll", "visible", "inherit"]}, + "padding": {"values": []}, + "padding-bottom": {"values": []}, + "padding-left": {"values": []}, + "padding-right": {"values": []}, + "padding-top": {"values": []}, + "page-break-after": {"values": ["always", "auto", "avoid", "left", "right", "inherit"]}, + "page-break-before": {"values": ["always", "auto", "avoid", "left", "right", "inherit"]}, + "page-break-inside": {"values": ["auto", "avoid", "inherit"]}, + "perspective": {"values": ["none"]}, + "perspective-origin": {"values": ["bottom", "center", "left", "right", "top"]}, + "position": {"values": ["absolute", "fixed", "relative", "static", "sticky", "inherit"]}, + "quotes": {"values": ["none", "inherit"]}, + "resize": {"values": ["both", "horizontal", "none", "vertical", "inherit"]}, + "right": {"values": ["auto", "inherit"]}, + "src": {"values": []}, + "table-layout": {"values": ["auto", "fixed", "inherit"]}, + "text-align": {"values": ["center", "left", "justify", "right", "inherit"]}, + "text-align-last": {"values": ["center", "left", "justify", "right", "inherit"]}, + "text-decoration": {"values": ["line-through", "none", "overline", "underline", "inherit"]}, + "text-decoration-color": {"values": ["currentColor"]}, + "text-decoration-line": {"values": ["line-through", "none", "overline", "underline"]}, + "text-decoration-skip": {"values": ["edges", "ink", "none", "objects", "spaces"]}, + "text-decoration-style": {"values": ["dashed", "dotted", "double", "solid", "wavy"]}, + "text-emphasis": {"values": []}, + "text-emphasis-color": {"values": ["currentColor"]}, + "text-emphasis-position": {"values": ["above", "below", "left", "right"]}, + "text-emphasis-style": {"values": ["circle", "dot", "double-circle", "filled", "none", "open", "sesame", "triangle"]}, + "text-indent": {"values": []}, + "text-overflow": {"values": ["clip", "ellipsis", "inherit"]}, + "text-shadow": {"values": []}, + "text-transform": {"values": ["capitalize", "full-width", "lowercase", "none", "uppercase", "inherit"]}, + "text-underline-position": {"values": ["alphabetic", "auto", "below", "left", "right"]}, + "top": {"values": ["auto", "inherit"]}, + "transform": {"values": ["matrix()", "matrix3d()", "perspective()", "rotate()", "rotate3d()", "rotateX()", "rotateY()", "rotateZ()", "scale()", "scale3d()", "scaleX()", "scaleY()", "scaleZ()", "skewX()", "skewY()", "translate()", "translate3d()", "translateX()", "translateY()", "translateZ()"]}, + "transform-origin": {"values": ["bottom", "center", "left", "right", "top"]}, + "transform-style": {"values": ["flat", "preserve-3d"]}, + "transition": {"values": []}, + "transition-delay": {"values": []}, + "transition-duration": {"values": []}, + "transition-property": {"values": ["all", "none"]}, + "transition-timing-function": {"values": ["cubic-bezier()", "ease", "ease-in", "ease-in-out", "ease-out", "linear", "step-end", "step-start", "steps()"]}, + "unicode-bidi": {"values": ["bidi-override", "embed", "normal", "inherit"]}, + "unicode-range": {"values": []}, + "vertical-align": {"values": ["baseline", "bottom", "middle", "sub", "super", "text-bottom", "text-top", "top", "inherit"]}, + "visibility": {"values": ["collapse", "hidden", "visible", "inherit"]}, + "white-space": {"values": ["normal", "nowrap", "pre", "pre-line", "pre-wrap", "inherit"]}, + "widows": {"values": []}, + "width": {"values": ["auto", "inherit"]}, + "word-spacing": {"values": []}, + "word-wrap": {"values": ["break-word", "normal"]}, + "z-index": {"values": ["auto", "inherit"]} +} \ No newline at end of file diff --git a/src/extensions/dev/CSSCodeHints/main.js b/src/extensions/dev/CSSCodeHints/main.js new file mode 100644 index 00000000000..059bd8be81e --- /dev/null +++ b/src/extensions/dev/CSSCodeHints/main.js @@ -0,0 +1,162 @@ +/*jslint vars: true, plusplus: true, devel: true, nomen: true, indent: 4, maxerr: 50 */ +/*global define, brackets, $, window */ + +define(function (require, exports, module) { + "use strict"; + + var CodeHintManager = brackets.getModule("editor/CodeHintManager"), + CSSUtils = brackets.getModule("language/CSSUtils"), + CSSAttributes = require("text!CSSAttributes.json"), + attributes = JSON.parse(CSSAttributes); + + /** + * @constructor + */ + function CssAttrHints() { + this.primaryTriggerKeys = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-"; + this.secondaryTriggerKeys = " :;"; + } + + /** + * Determines whether HTML tag hints are available in the current editor + * context. + * + * @param {Editor} editor + * A non-null editor object for the active window. + * + * @param {String} implicitChar + * Either null, if the hinting request was explicit, or a single character + * that represents the last insertion and that indicates an implicit + * hinting request. + * + * @return {Boolean} + * Determines whether the current provider is able to provide hints for + * the given editor context and, in case implicitChar is non- null, + * whether it is appropriate to do so. + */ + CssAttrHints.prototype.hasHints = function (editor, implicitChar) { + this.editor = editor; + var cursor = this.editor.getCursorPos(); + + this.info = CSSUtils.getInfoAtPos(editor, cursor); + // console.log(this.info); + + if (implicitChar === null) { + if (this.info.context === CSSUtils.PROP_NAME || this.info.context === CSSUtils.PROP_VALUE) { + return true; + } + } else { + return (this.primaryTriggerKeys.indexOf(implicitChar) !== -1) || + (this.secondaryTriggerKeys.indexOf(implicitChar) !== -1); + } + + return false; + }; + + /** + * Returns a list of availble HTML tag hints if possible for the current + * editor context. + * + * @return {Object, match: String, + * selectInitial: Boolean>} + * Null if the provider wishes to end the hinting session. Otherwise, a + * response object that provides 1. a sorted array hints that consists + * of strings; 2. a string match that is used by the manager to emphasize + * matching substrings when rendering the hint list; and 3. a boolean that + * indicates whether the first result, if one exists, should be selected + * by default in the hint list window. + */ + CssAttrHints.prototype.getHints = function (implicitChar) { + this.info = CSSUtils.getInfoAtPos(this.editor, this.editor.getCursorPos()); + + var needle = this.info.name, + valueNeedle = "", + context = this.info.context, + result, + selectInitial = true; + + + if (this.secondaryTriggerKeys.indexOf(implicitChar) !== -1) { + selectInitial = false; + } + + if (context === CSSUtils.PROP_VALUE) { + if (!attributes[needle]) { + return null; + } else { + + if (this.info.values.length > 0) { + valueNeedle = this.info.values[this.info.values.length - 1].trim(); + } + + result = $.map(attributes[needle].values, function (pvalue, pindex) { + if (pvalue.indexOf(valueNeedle) === 0) { + return pvalue; + } + }).sort(); + + return { + hints: result, + match: valueNeedle, + selectInitial: selectInitial + }; + } + } else if (context === CSSUtils.PROP_NAME) { + result = $.map(attributes, function (pvalues, pname) { + if (pname.indexOf(needle) === 0) { + return pname; + } + }).sort(); + + return { + hints: result, + match: needle, + selectInitial: selectInitial + }; + } + return null; + }; + + /** + * Inserts a given HTML tag hint into the current editor context. + * + * @param {String} hint + * The hint to be inserted into the editor context. + * + * @return {Boolean} + * Indicates whether the manager should follow hint insertion with an + * additional explicit hint request. + */ + CssAttrHints.prototype.insertHint = function (hint) { + var offset = this.info.offset, + cursor = this.editor.getCursorPos(), + closure = "", + start = {line: -1, ch: -1}, + end = {line: -1, ch: -1}, + keepHints = false; + + if (this.info.context === CSSUtils.PROP_NAME) { + closure = ": "; + keepHints = true; + } else if (this.info.context === CSSUtils.PROP_VALUE) { + closure = ";"; + } + + hint = hint + closure; + + start.line = end.line = cursor.line; + start.ch = cursor.ch - offset; + end.ch = start.ch + hint.length; + + this.editor.document.replaceRange(hint, start, end); + + return keepHints; + }; + + var cssAttrHints = new CssAttrHints(); + CodeHintManager.registerHintProvider(cssAttrHints, ["css"], 0); + + // For unit testing + exports.attrHintProvider = cssAttrHints; + +}); \ No newline at end of file diff --git a/src/extensions/dev/CSSCodeHints/unittests.js b/src/extensions/dev/CSSCodeHints/unittests.js new file mode 100644 index 00000000000..509622328d7 --- /dev/null +++ b/src/extensions/dev/CSSCodeHints/unittests.js @@ -0,0 +1,320 @@ +/*jslint vars: true, plusplus: true, devel: true, browser: true, nomen: true, indent: 4, maxerr: 50 */ +/*global define, describe, it, xit, expect, beforeEach, afterEach, waitsFor, runs, $, brackets, waitsForDone */ + +define(function (require, exports, module) { + "use strict"; + + var SpecRunnerUtils = brackets.getModule("spec/SpecRunnerUtils"), + Editor = brackets.getModule("editor/Editor").Editor, + CodeHintManager = brackets.getModule("editor/CodeHintManager"), + CSSCodeHints = require("main"); + + /* set indentation to one, to make use of tabs for the following testContent */ + Editor.setIndentUnit(1); + + 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 testWindow; + var testDocument, testEditor; + + beforeEach(function () { + // create Editor instance (containing a CodeMirror instance) + $("body").append("
"); + + // create dummy Document for the Editor + testDocument = SpecRunnerUtils.createMockDocument(defaultContent); + testEditor = new Editor(testDocument, true, "css", $("#editor").get(0), {}); + }); + + afterEach(function () { + testEditor.destroy(); + testEditor = null; + $("#editor").remove(); + testDocument = null; + }); + + // Ask provider for hints at current cursor position; expect it to return some + function expectHints(provider) { + expect(provider.hasHints(testEditor, null)).toBe(true); + var hintsObj = provider.getHints(); + expect(hintsObj).not.toBeNull(); + return hintsObj.hints; // return just the array of hints + } + + // Ask provider for hints at current cursor position; expect it NOT to return any + function expectNoHints(provider) { + expect(provider.hasHints(testEditor, null)).toBe(false); + } + + // Expect hintList to contain attribute names, starting with given value + function verifyAttrHints(hintList, expectedFirstHint) { + expect(hintList.indexOf("div")).toBe(-1); // make sure tag names aren't sneaking in there + expect(hintList[0]).toBe(expectedFirstHint); + } + + + function selectHint(provider, expectedHint) { + var hintList = expectHints(provider); + expect(hintList.indexOf(expectedHint)).not.toBe(-1); + return provider.insertHint(expectedHint); + //provider.handleSelect(expectedHint, testEditor, testEditor.getCursorPos(), true); + } + + // Helper function for testing cursor position + function expectCursorAt(pos) { + var selection = testEditor.getSelection(); + expect(selection.start).toEqual(selection.end); + expect(selection.start).toEqual(pos); + } + + describe("CSS attributes in general (selection of correct attribute based on input)", function () { + + it("should list all hints right after curly bracket", function () { + testEditor.setCursorPos({ line: 4, ch: 11 }); // after { + var hintList = expectHints(CSSCodeHints.attrHintProvider); + verifyAttrHints(hintList, "align-content"); // filtered on "empty string" + }); + + it("should list all hints in new line", function () { + testEditor.setCursorPos({ line: 5, ch: 1 }); + + var hintList = expectHints(CSSCodeHints.attrHintProvider); + verifyAttrHints(hintList, "align-content"); // filtered on "empty string" + }); + + it("should list all hints starting with 'b' in new line", function () { + testEditor.setCursorPos({ line: 6, ch: 2 }); + + var hintList = expectHints(CSSCodeHints.attrHintProvider); + verifyAttrHints(hintList, "backface-visibility"); // filtered on "b" + }); + + it("should list all hints starting with 'bord' ", function () { + testDocument.replaceRange(";", { line: 6, ch: 2 }); // insert colon after previous rule to avoid incorrect tokenizing + testEditor.setCursorPos({ line: 7, ch: 5 }); + + var hintList = expectHints(CSSCodeHints.attrHintProvider); + verifyAttrHints(hintList, "border"); // filtered on "bord" + }); + + it("should list all hints starting with 'border-' ", function () { + testDocument.replaceRange(";", { line: 7, ch: 5 }); // insert colon after previous rule to avoid incorrect tokenizing + testEditor.setCursorPos({ line: 8, ch: 8 }); + + var hintList = expectHints(CSSCodeHints.attrHintProvider); + verifyAttrHints(hintList, "border-bottom"); // filtered on "border-" + }); + + it("should list only hint border-color", function () { + testDocument.replaceRange(";", { line: 8, ch: 8 }); // insert colon after previous rule to avoid incorrect tokenizing + testEditor.setCursorPos({ line: 9, ch: 12 }); + + var hintList = expectHints(CSSCodeHints.attrHintProvider); + verifyAttrHints(hintList, "border-color"); // filtered on "border-color" + expect(hintList.length).toBe(1); + }); + + it("should list hints at end of existing attribute+value finished by ;", function () { + testEditor.setCursorPos({ line: 10, ch: 19 }); // after ; + var hintList = expectHints(CSSCodeHints.attrHintProvider); + verifyAttrHints(hintList, "align-content"); // filtered on "empty string" + }); + + it("should list hints right after curly bracket", function () { + testEditor.setCursorPos({ line: 4, ch: 11 }); // inside .selector, after { + expectHints(CSSCodeHints.attrHintProvider); + }); + + it("should NOT list hints right before curly bracket", function () { + testEditor.setCursorPos({ line: 4, ch: 10 }); // inside .selector, before { + expectNoHints(CSSCodeHints.attrHintProvider); + }); + it("should NOT list hints after declaration of mediatype", function () { + testEditor.setCursorPos({ line: 0, ch: 15 }); // after { + expectNoHints(CSSCodeHints.attrHintProvider); + }); + }); + + + describe("CSS attribute handleSelect", function () { + it("should insert colon followed by whitespace after attribute", function () { + testDocument.replaceRange(";", { line: 6, ch: 2 }); // insert colon after previous rule to avoid incorrect tokenizing + testEditor.setCursorPos({ line: 7, ch: 5 }); // cursor after 'bord' + selectHint(CSSCodeHints.attrHintProvider, "border"); + expect(testDocument.getLine(7)).toBe(" border: "); + expectCursorAt({ line: 7, ch: 9 }); + }); + + it("should insert semicolon followed by newline after value added", function () { + testDocument.replaceRange(";", { line: 12, ch: 5 }); // insert colon after previous rule to avoid incorrect tokenizing + testEditor.setCursorPos({ line: 13, ch: 10 }); // cursor after 'display: ' + selectHint(CSSCodeHints.attrHintProvider, "block"); + expect(testDocument.getLine(13)).toBe(" display: block;"); + // expectCursorAt({ line: 10, ch: 4 }); + }); + + it("should insert attribute directly after semicolon ", function () { + testEditor.setCursorPos({ line: 10, ch: 19 }); // cursor after red; + selectHint(CSSCodeHints.attrHintProvider, "align-content"); + expect(testDocument.getLine(10)).toBe(" border-color: red;align-content: "); + // expectCursorAt({ line: 10, ch: 4 }); + }); + + it("should insert nothing if previous property not closed properly", function () { + testEditor.setCursorPos({ line: 16, ch: 6 }); // cursor directly after color + expectNoHints(CSSCodeHints.attrHintProvider); + }); + + it("should insert nothing but the closure if propertyvalue is already complete", function () { + testDocument.replaceRange(";", { line: 15, ch: 13 }); // insert text ; + testEditor.setCursorPos({ line: 16, ch: 6 }); // cursor directly after color + selectHint(CSSCodeHints.attrHintProvider, "color"); + expect(testDocument.getLine(16)).toBe(" color: "); + expectCursorAt({ line: 16, ch: 8 }); + }); + + xit("should start new selection whenever there is a whitespace to last stringliteral", function () { + // 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.attrHintProvider, "color"); + expect(testDocument.getLine(16)).toBe(" color color: "); + expectCursorAt({ line: 16, ch: 14 }); + }); + }); + + describe("CSS attribute value hints", function () { + it("should list all display-values after colon", function () { + testDocument.replaceRange(";", { line: 12, ch: 5 }); // insert colon after previous rule to avoid incorrect tokenizing + testEditor.setCursorPos({ line: 13, ch: 9 }); + + var hintList = expectHints(CSSCodeHints.attrHintProvider); + verifyAttrHints(hintList, "block"); // filtered after "display:" + }); + + it("should list all display-values after colon and whitespace", function () { + testDocument.replaceRange(";", { line: 12, ch: 5 }); // insert colon after previous rule to avoid incorrect tokenizing + testEditor.setCursorPos({ line: 13, ch: 10 }); + + var hintList = expectHints(CSSCodeHints.attrHintProvider); + verifyAttrHints(hintList, "block"); // filtered after "display: " + }); + + it("should list all display-values after colon and whitespace", function () { + testDocument.replaceRange(";", { line: 13, ch: 10 }); // insert colon after previous rule to avoid incorrect tokenizing + testEditor.setCursorPos({ line: 14, ch: 12 }); + + var hintList = expectHints(CSSCodeHints.attrHintProvider); + verifyAttrHints(hintList, "inherit"); // filtered after "display: in" + }); + + it("should NOT list hints for unknown attribute", function () { + testEditor.setCursorPos({ line: 15, ch: 12 }); // at borborder: + expectNoHints(CSSCodeHints.attrHintProvider); + }); + + }); + + describe("CSS attribute hint provider inside mixed htmlfiles", function () { + var defaultContent = " \n" + + " \n" + + " \n" + + "
\n" + + "\n" + + ""; + + beforeEach(function () { + // create dummy Document for the Editor + testDocument = SpecRunnerUtils.createMockDocument(defaultContent); + testEditor = new Editor(testDocument, true, "htmlmixed", $("#editor").get(0), {}); + }); + + it("should list hints right after curly bracket", function () { + testEditor.setCursorPos({ line: 3, ch: 7 }); // inside body-selector, after { + expectHints(CSSCodeHints.attrHintProvider); + }); + + it("should list hints inside oneline styletags at start", function () { + testEditor.setCursorPos({ line: 1, ch: 23 }); // inside style, after { + expectHints(CSSCodeHints.attrHintProvider); + }); + + it("should list hints inside oneline styletags after ;", function () { + testEditor.setCursorPos({ line: 1, ch: 37 }); // inside style, after ; + expectHints(CSSCodeHints.attrHintProvider); + }); + + it("should list hints inside multiline styletags with cursor in first line", function () { + testEditor.setCursorPos({ line: 9, ch: 18 }); // inside style, after { + expectHints(CSSCodeHints.attrHintProvider); + }); + + it("should list hints inside multiline styletags with cursor in last line", function () { + testEditor.setCursorPos({ line: 10, ch: 5 }); // inside style, after colo + var hintList = expectHints(CSSCodeHints.attrHintProvider); + verifyAttrHints(hintList, "color"); // filtered on "colo" + expect(hintList.length).toBe(1); + }); + + it("should NOT list hints between closed styletag and new opening style tag", function () { + testEditor.setCursorPos({ line: 8, ch: 0 }); // right before
and { + expectNoHints(CSSCodeHints.attrHintProvider); + }); + + }); + + + describe("CSS attribute 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 + testDocument = SpecRunnerUtils.createMockDocument(defaultContent); + testEditor = new Editor(testDocument, true, "javascript", $("#editor").get(0), {}); + }); + + it("should NOT list hints after function declaration", function () { + testEditor.setCursorPos({ line: 0, ch: 24 }); // after { after function declaration + expectNoHints(CSSCodeHints.attrHintProvider); + }); + }); + }); +}); \ No newline at end of file