diff --git a/src/cli/common.js b/src/cli/common.js index 4533ad78..97e69241 100644 --- a/src/cli/common.js +++ b/src/cli/common.js @@ -19,7 +19,8 @@ function cli(api) { "ignore" : { "format" : "", "description" : "Indicate which rules to ignore completely." }, "exclude-list": { "format" : "", "description" : "Indicate which files/directories to exclude from being linted." }, "config" : { "format" : "", "description" : "Reads csslint options from specified file." }, - "version" : { "format" : "", "description" : "Outputs the current version number." } + "version" : { "format" : "", "description" : "Outputs the current version number." }, + "name-pattern": { "format" : "", "description" : "Indicate which pattern naming should follow." } }; //------------------------------------------------------------------------- @@ -140,7 +141,7 @@ function cli(api) { function processFile(relativeFilePath, options) { var input = api.readFile(relativeFilePath), ruleset = filterRules(options), - result = CSSLint.verify(input, gatherRules(options, ruleset)), + result = CSSLint.verify(input, gatherRules(options, ruleset), options), formatter = CSSLint.getFormatter(options.format || "text"), messages = result.messages || [], output, diff --git a/src/core/CSSLint.js b/src/core/CSSLint.js index 4ebbfa15..b6c5b7b0 100644 --- a/src/core/CSSLint.js +++ b/src/core/CSSLint.js @@ -169,10 +169,11 @@ var CSSLint = (function() { * @param {Object} ruleset (Optional) List of rules to apply. If null, then * all rules are used. If a rule has a value of 1 then it's a warning, * a value of 2 means it's an error. + * @param {Object} options (Optional) options for processing * @return {Object} Results of the verification. * @method verify */ - api.verify = function(text, ruleset) { + api.verify = function(text, ruleset, options) { var i = 0, reporter, @@ -245,7 +246,7 @@ var CSSLint = (function() { for (i in ruleset) { if (ruleset.hasOwnProperty(i) && ruleset[i]) { if (rules[i]) { - rules[i].init(parser, reporter); + rules[i].init(parser, reporter, options); } } } diff --git a/src/core/Reporter.js b/src/core/Reporter.js index cdaf73dc..9362c402 100644 --- a/src/core/Reporter.js +++ b/src/core/Reporter.js @@ -133,7 +133,7 @@ Reporter.prototype = { line : line, col : col, message : message, - evidence: this.lines[line-1], + evidence: this.lines[line-1] || "", rule : rule }); }, diff --git a/src/rules/accessibility.js b/src/rules/accessibility.js new file mode 100644 index 00000000..74f21d43 --- /dev/null +++ b/src/rules/accessibility.js @@ -0,0 +1,306 @@ +/* + * Rule: Check the accessibility for known issues + * + * This rule covers most common accessibility mistakes - color, size, positioning, + * hidden content, and limited interactions (e.g., pointer vs keyboard vs touch). + * An example stylesheet with these problems is below. + * + * .inaccessible-color { + * background-color: #fff; + * color: #ffe; + * } + * .inaccessible-size { + * font-size: 3px; + * height: 0; + * width: 0; + * } + * .inaccessible-positioning { + * left: 200%; + * position: absolute; + * top: 200%; + * } + * .inaccessible-content { + * content: "You will never see this"; + * display: none; + * overflow: hidden; + * visibility: hidden; + * } + * .inaccessible-focus:focus { + * outline: none; + * } + * .inaccessible-hover:hover, + * .inaccessible-hover:focus { + * font-weight: bold; + * outline: none; + * } + * + * The other potential issue is overriding a user's stylesheet, which is commonly + * done with the use of the "!important" indicator. Use of the !important keyword + * causes general specificity issues not limited to accessibility, so it has been + * kept in its own rule, "important". + */ + +CSSLint.addRule({ + + // rule information + id: "accessibility", + name: "Check accessibility issues", + desc: "Checks common accessibility issues such as color contrast and inaccessible content", + browsers: "All", + + // initialization + init: function(parser, reporter) { + "use strict"; + var rule = this, + isfontface = false, + iskeyframes = false, + ispage = false, + ispagemargin = false, + ismedia = false; + + // we can skip evaluation if the rule is for paged media, + // animation, or a specific font + function skipEval() { + return isfontface || iskeyframes || ispage || ispagemargin; + } + + // sets up the rule object + function startRule(event) { + var selNdx = event.selectors.length - 1, + prtNdx, + modNdx, + selObj, + prtObj, + modObj; + + // initialize the rule object + rule.bgColor = false; + rule.fgColor = false; + rule.col = event.col; + rule.line = event.line; + rule.outline = false; + rule.propCount = 0; + rule.pseudos = { + active: false, + after: false, + before: false, + focus: false, + hover: false + }; + rule.selectors = event.selectors; + + while (selNdx > -1) { + selObj = event.selectors[selNdx]; + prtObj = selObj.parts; + prtNdx = prtObj ? prtObj.length - 1 : -1; + while (prtNdx > -1) { + modObj = prtObj[prtNdx].modifiers; + modNdx = modObj ? modObj.length - 1 : -1; + while (modNdx > -1) { + switch (modObj[modNdx].text.toLowerCase()) { + case ":active": + rule.pseudos.active = true; + break; + case ":after": + rule.pseudos.after = true; + break; + case ":before": + rule.pseudos.before = true; + break; + case ":focus": + rule.pseudos.focus = true; + break; + case ":hover": + rule.pseudos.hover = true; + break; + } + modNdx -= 1; + } + prtNdx -= 1; + } + selNdx -= 1; + } + } + + // event handler for end of rule + function endRule() { + var BRIGHTNESS = 126, + HUE = 501, + brightness, + hue; + + // calculate the brightness of a color + function calcBrightness(cd) { + var value = (299 * cd.red) + (587 * cd.green) + (114 * cd.blue); + + value = value / 1000; + + return value; + } + + // calculate the hue difference in color + function calcHue(fg, bg) { + var value = Math.abs(fg.red - bg.red) + + Math.abs(fg.green - bg.green) + + Math.abs(fg.blue - bg.blue); + + return value; + } + + // if this is not a rule type we should skip, do a final evaluation + if (!skipEval()) { + // check for :focus and :active when :hover is used + if (rule.pseudos.hover && (!rule.pseudos.focus || !rule.pseudos.active)) { + reporter.report("Use of :hover without :active and :focus poses accessibility issues.", rule.line, rule.col, rule); + } + + // check to make sure color is defined with background and that contrast is high enough + if (rule.bgColor) { + if (!rule.fgColor) { + reporter.report("Explicitly set color any time background color is used.", rule.line, rule.col, rule); + } else { + // calculate the contrast + brightness = Math.abs(calcBrightness(rule.bgColor) - calcBrightness(rule.fgColor)); + hue = calcHue(rule.fgColor, rule.bgColor); + + if (brightness < BRIGHTNESS || hue < HUE) { + reporter.report("There is not enough contrast between background and foreground colors.", rule.line, rule.col, rule); + } + } + } + + // check to make sure outline is not the only property modified + if (rule.outline && rule.propCount === 1) { + reporter.report("Outline should not be hidden without other visual changes.", rule.line, rule.col, rule); + } + } + } + + // set up listeners + parser.addListener("startfontface", function() { + isfontface = true; + startRule(); + }); + parser.addListener("startkeyframes", function() { + iskeyframes = true; + }); + parser.addListener("startpage", function() { + ispage = true; + }); + parser.addListener("startpagemargin", function() { + ispagemargin = true; + }); + parser.addListener("startmedia", function() { + ismedia = true; + }); + parser.addListener("startrule", startRule); + + parser.addListener("property", function(event) { + var name = event.property.toString().toLowerCase(), + parts = event.value.parts; + + // get the color values used + function getColor() { + var ndx = parts.length - 1, + color; + + // if value is type 'color', red, green, and blue are also set + while (ndx > -1 && !color) { + if (parts[ndx].type === "color") { + color = { red:parts[ndx].red, blue:parts[ndx].blue, green:parts[ndx].green }; + } + ndx -= 1; + } + + return color; + } + + // if this is not a rule type we should skip, do a final evaluation + if (!skipEval()) { + // increment the property count + rule.propCount += 1; + + switch (name) { + case "background-color": + // check for specification of color and contrast + rule.bgColor = getColor(); + break; + case "background": + // check for specification of color and contrast + rule.bgColor = getColor(); + break; + case "color": + // check for specification of background-color and contrast + // check to make sure color is not transparent + rule.fgColor = getColor(); + break; + case "content": + reporter.report("Content specified in a stylesheet is not available to assistive technology.", event.line, event.col, rule); + break; + case "display": + // display none removes content from AT + if (event.value.text === "none") { + reporter.report("Hidden content is not accessible.", event.line, event.col, rule); + } + break; + case "font-size": + case "margin": + case "padding": + // check to make sure relative size is used + if (/p(c|t|x)|mm|q|cm|in/i.test(event.value.text)) { + reporter.report("Absolute lengths are not accessible.", event.line, event.col, rule); + } + break; + case "height": + if (event.value.parts[0].value === 0 && !rule.pseudos.after && !rule.pseudos.before) { + reporter.report("Hidden content is not accessible.", event.line, event.col, rule); + } + break; + case "outline": + if (event.value.toString() === "none" || event.value.toString() === "0") { + rule.outline = true; + } + break; + case "overflow": + if (event.value.text === "hidden") { + reporter.report("Hidden content is not accessible.", event.line, event.col, rule); + } + break; + case "position": + if (event.value.text === "absolute") { + reporter.report("Discoverability of absolutely positioned content may problematic.", event.line, event.col, rule); + } + break; + case "visibility": + if (event.value.text === "hidden") { + reporter.report("Hidden content is not accessible.", event.line, event.col, rule); + } + break; + case "width": + if (event.value.parts[0].value === 0 && !rule.pseudos.after && !rule.pseudos.before) { + reporter.report("Hidden content is not accessible.", event.line, event.col, rule); + } + break; + } + } + }); + + parser.addListener("endfontface", function() { + isfontface = false; + }); + parser.addListener("endkeyframes", function() { + iskeyframes = false; + }); + parser.addListener("endpage", function() { + ispage = false; + }); + parser.addListener("endpagemargin", function() { + ispagemargin = false; + }); + parser.addListener("endmedia", function() { + ismedia = false; + }); + parser.addListener("endrule", endRule); + } + +}); diff --git a/src/rules/background-color.js b/src/rules/background-color.js new file mode 100644 index 00000000..86050233 --- /dev/null +++ b/src/rules/background-color.js @@ -0,0 +1,77 @@ +/* + * Rule: Set color any time background-color is set + * + */ + +CSSLint.addRule({ + + // rule information + id: "background-color", + name: "Set color any time background-color is set", + desc: "Checks for a value for color whenever background-color is set", + browsers: "All", + + // initialization + init: function(parser, reporter) { + "use strict"; + var rule = this, + bgColorDef, + colorDef; + + + function startRule(event) { + bgColorDef = false; + rule.col = event.col; + rule.line = event.line; + } + + // event handler for end of rules + function endRule() { + if (bgColorDef) { + if (!colorDef) { + reporter.report("Explicitly set color any time background color is used.", rule.line, rule.col, rule); + } else if (bgColorDef.red === colorDef.red && bgColorDef.green === colorDef.green && bgColorDef.blue === colorDef.blue) { + reporter.report("Text color is the same as the background color.", rule.line, rule.col, rule); + } + } + } + + parser.addListener("startrule", startRule); + parser.addListener("startfontface", startRule); + + // check for use of "background" with color, "background-color", and "color" + parser.addListener("property", function(event) { + var name = event.property.toString().toLowerCase(), + parts = event.value.parts; + + // get the color values used + function getColor() { + var ndx = parts.length - 1, + color; + + // if value is type 'color', red, green, and blue are also set + while (ndx > -1 && !color) { + if (parts[ndx].type === "color") { + color = { red:parts[ndx].red, blue:parts[ndx].blue, green:parts[ndx].green }; + } + ndx -= 1; + } + + return color; + } + + if (name === "background-color") { + bgColorDef = getColor(); + } else if (name === "background") { + bgColorDef = getColor(); + } else if (name === "color") { + colorDef = getColor(); + } + }); + + parser.addListener("endrule", endRule); + parser.addListener("endfontface", endRule); + + } + +}); diff --git a/src/rules/color-contrast.js b/src/rules/color-contrast.js new file mode 100644 index 00000000..e83a2b8d --- /dev/null +++ b/src/rules/color-contrast.js @@ -0,0 +1,101 @@ +/* + * Rule: Check the color contrast any time background color and foreground color are set + */ + +CSSLint.addRule({ + + // rule information + id: "color-contrast", + name: "Check color contast", + desc: "Checks color contrast when background color and foreground color are set", + browsers: "All", + + // initialization + init: function(parser, reporter) { + "use strict"; + var rule = this, + bgColorDef, + colorDef; + + + function startRule(event) { + bgColorDef = false; + colorDef = false; + rule.col = event.col; + rule.line = event.line; + } + + // event handler for end of rule + function endRule() { + var BRIGHTNESS = 126, + HUE = 501, + brightness, + hue; + + // calculate the brightness of a color + function calcBrightness(cd) { + var value = (299 * cd.red) + (587 * cd.green) + (114 * cd.blue); + + value = value / 1000; + + return value; + } + + function calcHue(fg, bg) { + var value = Math.abs(fg.red - bg.red) + + Math.abs(fg.green - bg.green) + + Math.abs(fg.blue - bg.blue); + + return value; + } + + if (bgColorDef && colorDef) { + // calculate the contrast + brightness = Math.abs(calcBrightness(bgColorDef) - calcBrightness(colorDef)); + hue = calcHue(colorDef, bgColorDef); + + if (brightness < BRIGHTNESS || hue < HUE) { + reporter.report("There is not enough contrast between background and foreground colors.", rule.line, rule.col, rule); + } + } + } + + parser.addListener("startrule", startRule); + parser.addListener("startfontface", startRule); + + // check for use of "background" with color, "background-color", and "color" + parser.addListener("property", function(event) { + var name = event.property.toString().toLowerCase(), + parts = event.value.parts; + + // get the color values used + function getColor() { + var ndx = parts.length - 1, + color; + + // if value is type 'color', red, green, and blue are also set + while (ndx > -1 && !color) { + if (parts[ndx].type === "color") { + color = { red: parts[ndx].red, green: parts[ndx].green, blue: parts[ndx].blue }; + } + ndx -= 1; + } + + return color; + } + + if (name === "background-color") { + bgColorDef = getColor(); + } else if (name === "background") { + bgColorDef = getColor(); + } else if (name === "color") { + colorDef = getColor(); + } + }); + + parser.addListener("endrule", endRule); + parser.addListener("endfontface", endRule); + + } + +}); diff --git a/src/rules/lowercase.js b/src/rules/lowercase.js new file mode 100644 index 00000000..e7a45496 --- /dev/null +++ b/src/rules/lowercase.js @@ -0,0 +1,32 @@ +/* + * Rule: properties and functions should be lowercase + */ + +CSSLint.addRule({ + + // rule information + id: "lowercase", + name: "Require lowercase properties and functions", + desc: "All properties and functions should be lowercase", + browsers: "All", + + // initialization + init: function(parser, reporter) { + "use strict"; + var rule = this; + + parser.addListener("property", function(event) { + var prop = event.property.text, + func = /([a-z\-]+)\(/i.exec(event.value.text); + + if (prop !== prop.toLowerCase()) { + reporter.report("Property is not lowercase.", event.line, event.col, rule); + } else if (func) { + if (func[1] !== func[1].toLowerCase()) { + reporter.report("Function is not lowercase.", event.line, event.col, rule); + } + } + }); + } + +}); diff --git a/src/rules/no-filter.js b/src/rules/no-filter.js new file mode 100644 index 00000000..9ffd0f6e --- /dev/null +++ b/src/rules/no-filter.js @@ -0,0 +1,28 @@ +/* + * Rule: Filter is experimental and may have unexpected behavior + */ + +CSSLint.addRule({ + + // rule information + id: "no-filter", + name: "Disallow filter", + desc: "Filter does not have a stable specification and should not be used", + url: "https://github.com/hrobertking/csslint/wiki/Disallow-filter", + browsers: "All", + + // initialization + init: function(parser, reporter) { + "use strict"; + var rule = this; + + // report a use of 'filter' + parser.addListener("property", function(event) { + if (event.property.toString().toLowerCase() === "filter") { + reporter.report("The filter property is not allowed", event.line, event.col, rule); + } + }); + + } + +}); diff --git a/src/rules/no-tabs.js b/src/rules/no-tabs.js new file mode 100644 index 00000000..1d8dde23 --- /dev/null +++ b/src/rules/no-tabs.js @@ -0,0 +1,31 @@ +/* + * Rule: Don't use tabs, use space + * + */ + +CSSLint.addRule({ + + // rule information + id: "no-tabs", + name: "Disallow tabs", + desc: "Checks for tab", + browsers: "All", + + // initialization + init: function(parser, reporter) { + "use strict"; + var rule = this, + sheet; + + parser.addListener("startstylesheet", function() { + sheet = this._tokenStream._reader._input.split(/\n/); + }); + + parser.addListener("property", function(event) { + if (/\t/.test(sheet[event.line - 1])) { + reporter.report("The TAB character is not allowed.", event.line, event.col, rule); + } + }); + } + +}); diff --git a/src/rules/obsolete-properties.js b/src/rules/obsolete-properties.js new file mode 100644 index 00000000..6f67da77 --- /dev/null +++ b/src/rules/obsolete-properties.js @@ -0,0 +1,80 @@ +/* + * Rule: Properties should not be obsolete + */ + +CSSLint.addRule({ + + // rule information + id: "obsolete-properties", + name: "Disallow use of obsolete properties", + desc: "Properties should not be obsolete.", + browsers: "All", + + // initialization + init: function(parser, reporter) { + "use strict"; + var rule = this, + // properties recognized by parser-lib that are non-standard or obsolete + obsolete = [ + "appearance", + "azimuth", + "behavior", + "binding", + "drop-initial-after-adjust", + "drop-initial-after-align", + "drop-initial-before-adjust", + "drop-initial-before-align", + "drop-initial-size", + "drop-initial-value", + "fit", + "fit-position", + "grid-cell-stacking", + "grid-column-align", + "grid-column-sizing", + "grid-column-span", + "grid-columns", + "grid-flow", + "grid-layer", + "grid-row-align", + "grid-row-sizing", + "grid-row-span", + "grid-rows", + "hyphenate-after", + "hyphenate-before", + "hyphenate-character", + "hyphenate-lines", + "hyphenate-resource", + "ime-mode", + "marquee-direction", + "marquee-play-count", + "marquee-speed", + "marquee-style", + "move-to", + "nav-index", + "overflow-style", + "page-policy", + "punctuation-trim", + "rotation", + "rotation-point", + "ruby-overhang", + "ruby-span", + "target", + "target-name", + "target-new", + "target-position", + "text-outline", + "text-wrap", + "user-modify", + "white-space-collapse" + ]; + + parser.addListener("property", function(event) { + var name = event.property.toString().toLowerCase(); + + if (obsolete.indexOf(name) > -1) { + reporter.report("Non-standard or obsolete property '" + name + "'.", event.line, event.col, rule); + } + }); + } + +}); diff --git a/src/rules/rules-max.js b/src/rules/rules-max.js new file mode 100644 index 00000000..a5d23917 --- /dev/null +++ b/src/rules/rules-max.js @@ -0,0 +1,29 @@ +/* + * Rule: Warn when there are 500 or more rules in a stylesheet + */ + +CSSLint.addRule({ + + // rule information + id: "rules-max", + name: "Warn when 500+ rules are present", + desc: "Will warn when the rule count in a stylesheet crosses the 500 threshold.", + browsers: "IE", + + // initialization + init: function(parser, reporter) { + "use strict"; + var rule = this, ruleCount = 0, MAX_RULES = 499; + + parser.addListener("startrule", function() { + ruleCount += 1; + }); + + parser.addListener("endstylesheet", function() { + if (ruleCount > MAX_RULES) { + reporter.report("There are too many rules (" + ruleCount + ") in the stylesheet.", 0, 0, rule); + } + }); + } + +}); diff --git a/src/rules/selector-pattern.js b/src/rules/selector-pattern.js new file mode 100644 index 00000000..7ed6f46c --- /dev/null +++ b/src/rules/selector-pattern.js @@ -0,0 +1,57 @@ +/* + * Rule: warn when the selector naming convention is not followed + */ + +CSSLint.addRule({ + + // rule information + id: "selector-pattern", + name: "Warn when the naming convention is not followed", + desc: "Will warn when the selector naming convention is not followed", + browsers: "All", + + // initialization + init: function(parser, reporter, options) { + "use strict"; + var rule = this, + rxCamel = { "accept": /^[a-z]+([0-9]+|[A-Z][a-z]+)?/, "reject": /[\-\_]+/ }, + rxSnake = { "accept": /^[a-z][a-z0-9\_]+$/, "reject": /\-/ }, + rxDashs = { "accept": /^[a-z][a-z0-9\-]*$/, "reject": /\_/ }, + choice = options ? options["name-pattern"] : "bem", + pattern; + + choice = (choice || "bem").toString().toLowerCase(); + switch (choice) { + case "camelcase": + pattern = rxCamel; + break; + case "snakecase": + pattern = rxSnake; + break; + default: + pattern = rxDashs; + break; + } + + parser.addListener("startrule", function(event) { + var sel, + pts, + cls, + bites; + + for (sel = 0; sel < event.selectors.length; sel += 1) { + for (pts = 0; pts < event.selectors[sel].parts.length; pts += 1) { + if (event.selectors[sel].parts[pts].type === 8) { + bites = event.selectors[sel].parts[pts].text.split(/\#|\./); + for (cls = 0; cls < bites.length; cls += 1) { + if (bites[cls] && (!pattern.accept.test(bites[cls]) || pattern.reject.test(bites[cls]))) { + reporter.report("Selector naming convention (" + choice + ") not followed.", event.line, event.col, rule); + } + } + } + } + } + }); + } + +}); diff --git a/src/rules/transparent-color.js b/src/rules/transparent-color.js new file mode 100644 index 00000000..dbad00a9 --- /dev/null +++ b/src/rules/transparent-color.js @@ -0,0 +1,37 @@ +/* + * Rule: Set color to a visible value + * + */ + +CSSLint.addRule({ + + // rule information + id: "transparent-color", + name: "Named value 'transparent' used for color", + desc: "Checks for a value for color that is transparent", + browsers: "All", + + // initialization + init: function(parser, reporter) { + "use strict"; + var rule = this; + + // check for use of "transparent" as a value for "color" + parser.addListener("property", function(event) { + var name = event.property.toString().toLowerCase(), + parts = event.value.parts, + ndx = parts.length - 1; + + if (name === "color") { + while (ndx > -1) { + if (parts[ndx].type === "identifier" && parts[ndx].value === "transparent") { + reporter.report("Color cannot be 'transparent'.", event.line, event.col, rule); + break; + } + ndx -= 1; + } + } + }); + } + +}); diff --git a/tests/rules/accessibility.js b/tests/rules/accessibility.js new file mode 100644 index 00000000..c94473f6 --- /dev/null +++ b/tests/rules/accessibility.js @@ -0,0 +1,212 @@ +(function() { + "use strict"; + var Assert = YUITest.Assert; + + YUITest.TestRunner.add(new YUITest.TestCase({ + + name: "accessibility rule errors", + + // overriding user stylesheets is tested using the "important" rule - see notes in the "accessibility" rule + + // background and foreground color + "background color specified in shorthand without color should result in a warning": function() { + var result = CSSLint.verify(".foo{background: #f00 url('smiley.gif') no-repeat fixed center;}", { "accessibility": 1 }); + + Assert.areEqual(1, result.messages.length); + Assert.areEqual("warning", result.messages[0].type); + Assert.areEqual("Explicitly set color any time background color is used.", result.messages[0].message); + }, + + "background-color without color should result in a warning": function() { + var result = CSSLint.verify(".foo{background-color: #f00;}", { "accessibility": 1 }); + + Assert.areEqual(1, result.messages.length); + Assert.areEqual("warning", result.messages[0].type); + Assert.areEqual("Explicitly set color any time background color is used.", result.messages[0].message); + }, + + "background specified without enough color contrast should result in a warning": function() { + var result = CSSLint.verify(".foo{background: #fff url('smiley.gif') no-repeat fixed center; color: #fff;}", { "accessibility": 1 }); + + Assert.areEqual(1, result.messages.length); + Assert.areEqual("warning", result.messages[0].type); + Assert.areEqual("There is not enough contrast between background and foreground colors.", result.messages[0].message); + }, + + "background-color without enough color contrast should result in a warning": function() { + var result = CSSLint.verify(".foo{background-color: rgb(255, 255, 255); color: rgb(200, 200, 200);}", { "accessibility": 1 }); + + Assert.areEqual(1, result.messages.length); + Assert.areEqual("warning", result.messages[0].type); + Assert.areEqual("There is not enough contrast between background and foreground colors.", result.messages[0].message); + }, + + "background with enough color contrast should not result in a warning": function() { + var result = CSSLint.verify(".foo{background: #fff url('smiley.gif') no-repeat fixed center; color: #000;}", { "accessibility": 1 }); + + Assert.areEqual(0, result.messages.length); + }, + + "background-color with enough color contrast should not result in a warning": function() { + var result = CSSLint.verify(".foo{background-color: #fff; color: #000;}", { "accessibility": 1 }); + + Assert.areEqual(0, result.messages.length); + }, + + // content + "specifying content should result in a warning": function() { + var result = CSSLint.verify(".foo{content:'anything';}", { "accessibility": 1 }); + + Assert.areEqual(1, result.messages.length); + Assert.areEqual("warning", result.messages[0].type); + Assert.areEqual("Content specified in a stylesheet is not available to assistive technology.", result.messages[0].message); + }, + + // display + "display none should result in a warning": function() { + var result = CSSLint.verify(".foo{display: none;}", { "accessibility": 1 }); + + Assert.areEqual(1, result.messages.length); + Assert.areEqual("warning", result.messages[0].type); + Assert.areEqual("Hidden content is not accessible.", result.messages[0].message); + }, + + "display values other than none should not result in a warning": function() { + var result = CSSLint.verify(".foo{display: block; display: inline-block; display: inline;}", { "accessibility": 1 }); + + Assert.areEqual(0, result.messages.length); + }, + + // absolute lengths + "absolute lengths for font-size should result in an error": function() { + var result = CSSLint.verify(".foo{font-size: 16px;}", { "accessibility": 1 }); + + Assert.areEqual(1, result.messages.length); + Assert.areEqual("warning", result.messages[0].type); + Assert.areEqual("Absolute lengths are not accessible.", result.messages[0].message); + }, + + "relative lengths for font-size should not result in an error": function() { + var result = CSSLint.verify(".foo{font-size: 1em;}", { "accessibility": 1 }); + + Assert.areEqual(0, result.messages.length); + }, + + "absolute lengths for margin should result in an error": function() { + var result = CSSLint.verify(".foo{margin: 3px;}", { "accessibility": 1 }); + + Assert.areEqual(1, result.messages.length); + Assert.areEqual("warning", result.messages[0].type); + Assert.areEqual("Absolute lengths are not accessible.", result.messages[0].message); + }, + + "relative lengths for margin should not result in an error": function() { + var result = CSSLint.verify(".foo{margin: 1em;}", { "accessibility": 1 }); + + Assert.areEqual(0, result.messages.length); + }, + + "absolute lengths for padding should result in an error": function() { + var result = CSSLint.verify(".foo{padding: 3px;}", { "accessibility": 1 }); + + Assert.areEqual(1, result.messages.length); + Assert.areEqual("warning", result.messages[0].type); + Assert.areEqual("Absolute lengths are not accessible.", result.messages[0].message); + }, + + "relative lengths for padding should not result in an error": function() { + var result = CSSLint.verify(".foo{padding: 1em;}", { "accessibility": 1 }); + + Assert.areEqual(0, result.messages.length); + }, + + // height and width of zero + "height of zero should result in a warning": function() { + var result = CSSLint.verify(".foo{height: 0;}", { "accessibility": 1 }); + + Assert.areEqual(1, result.messages.length); + Assert.areEqual("warning", result.messages[0].type); + Assert.areEqual("Hidden content is not accessible.", result.messages[0].message); + }, + + "non-zero height should not result in a warning": function() { + var result = CSSLint.verify(".foo{height: 1em;}", { "accessibility": 1 }); + + Assert.areEqual(0, result.messages.length); + }, + + "width of zero should result in a warning": function() { + var result = CSSLint.verify(".foo{width: 0;}", { "accessibility": 1 }); + + Assert.areEqual(1, result.messages.length); + Assert.areEqual("warning", result.messages[0].type); + Assert.areEqual("Hidden content is not accessible.", result.messages[0].message); + }, + + "non-zero width should not result in a warning": function() { + var result = CSSLint.verify(".foo{width: 1em;}", { "accessibility": 1 }); + + Assert.areEqual(0, result.messages.length); + }, + + // outline should not be used alone + "outline used alone should result in a warning": function() { + var result = CSSLint.verify(".foo{outline: none;}", { "accessibility": 1 }); + + Assert.areEqual(1, result.messages.length); + Assert.areEqual("warning", result.messages[0].type); + Assert.areEqual("Outline should not be hidden without other visual changes.", result.messages[0].message); + }, + + "outline used with other properties should not result in a warning": function() { + var result = CSSLint.verify(".foo{font-weight: bold; outline: none;}", { "accessibility": 1 }); + + Assert.areEqual(0, result.messages.length); + }, + + // hidden overflow + "hidden overflow should result in a warning": function() { + var result = CSSLint.verify(".foo{overflow: hidden;}", { "accessibility": 1 }); + + Assert.areEqual(1, result.messages.length); + Assert.areEqual("warning", result.messages[0].type); + Assert.areEqual("Hidden content is not accessible.", result.messages[0].message); + }, + "non-hidden overflow should not result in a warning": function() { + var result = CSSLint.verify(".foo{overflow: auto; overflow: visible; overflow: scroll;}", { "accessibility": 1 }); + + Assert.areEqual(0, result.messages.length); + }, + + // absolutely positioned elements + "absolutely positioned elements should result in a warning": function() { + var result = CSSLint.verify(".foo{position: absolute;}", { "accessibility": 1 }); + + Assert.areEqual(1, result.messages.length); + Assert.areEqual("warning", result.messages[0].type); + Assert.areEqual("Discoverability of absolutely positioned content may problematic.", result.messages[0].message); + }, + + "non-absolutely positioned elements should not result in a warning": function() { + var result = CSSLint.verify(".foo{position: relative; position: static;}", { "accessibility": 1 }); + + Assert.areEqual(0, result.messages.length); + }, + + // visibility + "visibility hidden should result in a warning": function() { + var result = CSSLint.verify(".foo{visibility: hidden;}", { "accessibility": 1 }); + + Assert.areEqual(1, result.messages.length); + Assert.areEqual("warning", result.messages[0].type); + Assert.areEqual("Hidden content is not accessible.", result.messages[0].message); + }, + + "visibility visible should not result in a warning": function() { + var result = CSSLint.verify(".foo{visibility: visible;}", { "accessibility": 1 }); + + Assert.areEqual(0, result.messages.length); + } + })); + +})(); diff --git a/tests/rules/background-color.js b/tests/rules/background-color.js new file mode 100644 index 00000000..e731d43e --- /dev/null +++ b/tests/rules/background-color.js @@ -0,0 +1,54 @@ +(function() { + "use strict"; + var Assert = YUITest.Assert; + + YUITest.TestRunner.add(new YUITest.TestCase({ + + name: "background-color Rule Errors", + + "background color specified in shorthand without color should result in a warning": function() { + var result = CSSLint.verify(".foo{background: #f00 url('smiley.gif') no-repeat fixed center;}", { "background-color": 1 }); + + Assert.areEqual(1, result.messages.length); + Assert.areEqual("warning", result.messages[0].type); + Assert.areEqual("Explicitly set color any time background color is used.", result.messages[0].message); + }, + + "background-color without color should result in a warning": function() { + var result = CSSLint.verify(".foo{background-color: #f00;}", { "background-color": 1 }); + + Assert.areEqual(1, result.messages.length); + Assert.areEqual("warning", result.messages[0].type); + Assert.areEqual("Explicitly set color any time background color is used.", result.messages[0].message); + }, + + "background color specified in shorthand with same color value should result in a warning": function() { + var result = CSSLint.verify(".foo{background: #f00 url('smiley.gif') no-repeat fixed center; color: #f00;}", { "background-color": 1 }); + + Assert.areEqual(1, result.messages.length); + Assert.areEqual("warning", result.messages[0].type); + Assert.areEqual("Text color is the same as the background color.", result.messages[0].message); + }, + + "background-color with same color value should result in a warning": function() { + var result = CSSLint.verify(".foo{background-color: #f00; color: #f00;}", { "background-color": 1 }); + + Assert.areEqual(1, result.messages.length); + Assert.areEqual("warning", result.messages[0].type); + Assert.areEqual("Text color is the same as the background color.", result.messages[0].message); + }, + + "background color specified in shorthand with different color should not result in a warning": function() { + var result = CSSLint.verify(".foo{background: #f00 url('smiley.gif') no-repeat fixed center; color: rgb(255, 255, 0);}", { "background-color": 1 }); + + Assert.areEqual(0, result.messages.length); + }, + + "background-color with different color should not result in a warning": function() { + var result = CSSLint.verify(".foo{background-color: #f00; color: rgb(255, 255, 0);}", { "background-color": 1 }); + + Assert.areEqual(0, result.messages.length); + } + })); + +})(); diff --git a/tests/rules/color-contrast.js b/tests/rules/color-contrast.js new file mode 100644 index 00000000..8dddf3e5 --- /dev/null +++ b/tests/rules/color-contrast.js @@ -0,0 +1,38 @@ +(function() { + "use strict"; + var Assert = YUITest.Assert; + + YUITest.TestRunner.add(new YUITest.TestCase({ + + name: "color-contrast rule errors", + + "background specified without enough color contrast should result in a warning": function() { + var result = CSSLint.verify(".foo{background: #fff url('smiley.gif') no-repeat fixed center; color: #c8c8c8;}", { "color-contrast": 1 }); + + Assert.areEqual(1, result.messages.length); + Assert.areEqual("warning", result.messages[0].type); + Assert.areEqual("There is not enough contrast between background and foreground colors.", result.messages[0].message); + }, + + "background-color without enough color contrast should result in a warning": function() { + var result = CSSLint.verify(".foo{background-color: rgb(255, 255, 255); color: rgb(200, 200, 200);}", { "color-contrast": 1 }); + + Assert.areEqual(1, result.messages.length); + Assert.areEqual("warning", result.messages[0].type); + Assert.areEqual("There is not enough contrast between background and foreground colors.", result.messages[0].message); + }, + + "background with enough color contrast should not result in a warning": function() { + var result = CSSLint.verify(".foo{background: #fff url('smiley.gif') no-repeat fixed center; color: #000;}", { "background-color": 1 }); + + Assert.areEqual(0, result.messages.length); + }, + + "background-color with enough color contrast should not result in a warning": function() { + var result = CSSLint.verify(".foo{background-color: #fff; color: #000;}", { "background-color": 1 }); + + Assert.areEqual(0, result.messages.length); + } + })); + +})(); diff --git a/tests/rules/lowercase.js b/tests/rules/lowercase.js new file mode 100644 index 00000000..2ac54210 --- /dev/null +++ b/tests/rules/lowercase.js @@ -0,0 +1,38 @@ +(function() { + "use strict"; + var Assert = YUITest.Assert; + + YUITest.TestRunner.add(new YUITest.TestCase({ + + name: "lowercase rule errors", + + "A property that is not in lowercase should result in a warning": function() { + var result = CSSLint.verify("h1 { Border-Radius: 5px; }", { "lowercase": 1 }); + + Assert.areEqual(1, result.messages.length); + Assert.areEqual("warning", result.messages[0].type); + Assert.areEqual("Property is not lowercase.", result.messages[0].message); + }, + + "A property that is in lowercase should not result in a warning": function() { + var result = CSSLint.verify("h1 { border-radius: 5px; }", { "lowercase": 1 }); + + Assert.areEqual(0, result.messages.length); + }, + + "A function that is not in lowercase should result in a warning": function() { + var result = CSSLint.verify("h1 { color: RGB(0, 0, 0); }", { "lowercase": 1 }); + + Assert.areEqual(1, result.messages.length); + Assert.areEqual("warning", result.messages[0].type); + Assert.areEqual("Function is not lowercase.", result.messages[0].message); + }, + + "A function that is in lowercase should not result in a warning": function() { + var result = CSSLint.verify("h1 { color: rgb(0, 0, 0); }", { "lowercase": 1 }); + + Assert.areEqual(0, result.messages.length); + } + })); + +})(); diff --git a/tests/rules/no-filter.js b/tests/rules/no-filter.js new file mode 100644 index 00000000..153e3602 --- /dev/null +++ b/tests/rules/no-filter.js @@ -0,0 +1,18 @@ +(function() { + "use strict"; + + var Assert = YUITest.Assert; + + YUITest.TestRunner.add(new YUITest.TestCase({ + name: "Unstable filter property", + + "Use of 'filter' should result in a warning": function() { + var result = CSSLint.verify("h1 { filter: alpha(opacity=40); }", { "no-filter": 1 }); + + Assert.areEqual(1, result.messages.length); + Assert.areEqual("warning", result.messages[0].type); + Assert.areEqual("The filter property is not allowed", result.messages[0].message); + } + })); +})(); + diff --git a/tests/rules/no-tabs.js b/tests/rules/no-tabs.js new file mode 100644 index 00000000..4d34413a --- /dev/null +++ b/tests/rules/no-tabs.js @@ -0,0 +1,19 @@ +(function() { + "use strict"; + var Assert = YUITest.Assert; + + YUITest.TestRunner.add(new YUITest.TestCase({ + + name: "no-tabs rule errors", + + "tabs in a stylesheet": function() { + var result = CSSLint.verify(".foo{\ttext-indent: -100px;}", { "no-tabs": 1 }); + + Assert.areEqual(1, result.messages.length); + Assert.areEqual("warning", result.messages[0].type); + Assert.areEqual("The TAB character is not allowed.", result.messages[0].message); + } + + })); + +})(); diff --git a/tests/rules/obsolete-properties.js b/tests/rules/obsolete-properties.js new file mode 100644 index 00000000..796ea2f6 --- /dev/null +++ b/tests/rules/obsolete-properties.js @@ -0,0 +1,416 @@ +(function() { + "use strict"; + var Assert = YUITest.Assert; + + YUITest.TestRunner.add(new YUITest.TestCase({ + + name: "obsolete property errors", + + setUp: function() { + this.message = /Non\-standard or obsolete property /; + }, + + "appearance should result in an error": function() { + var result = CSSLint.verify("li { appearance: none; }", { "obsolete-properties": 1 }), + isErr = this.message.test(result.messages[0].message); + + Assert.areEqual(1, result.messages.length); + Assert.areEqual("warning", result.messages[0].type); + Assert.areEqual(isErr, true); + }, + "azimuth": function() { + var result = CSSLint.verify("li { azimuth: left-side; }", { "obsolete-properties": 1 }), + isErr = this.message.test(result.messages[0].message); + + Assert.areEqual(1, result.messages.length); + Assert.areEqual("warning", result.messages[0].type); + Assert.areEqual(isErr, true); + }, + "behavior": function() { + var result = CSSLint.verify("li { behavior: url(my-behavior.htc); }", { "obsolete-properties": 1 }), + isErr = this.message.test(result.messages[0].message); + + Assert.areEqual(1, result.messages.length); + Assert.areEqual("warning", result.messages[0].type); + Assert.areEqual(isErr, true); + }, + "binding": function() { + var result = CSSLint.verify("li { binding: url(my-behavior.htc); }", { "obsolete-properties": 1 }), + isErr = this.message.test(result.messages[0].message); + + Assert.areEqual(1, result.messages.length); + Assert.areEqual("warning", result.messages[0].type); + Assert.areEqual(isErr, true); + }, + "drop-initial-after-adjust": function() { + var result = CSSLint.verify("li { drop-initial-after-adjust: url(my-behavior.htc); }", { "obsolete-properties": 1 }), + isErr = this.message.test(result.messages[0].message); + + Assert.areEqual(1, result.messages.length); + Assert.areEqual("warning", result.messages[0].type); + Assert.areEqual(isErr, true); + }, + "drop-initial-after-align": function() { + var result = CSSLint.verify("li { drop-initial-after-align: url(my-behavior.htc); }", { "obsolete-properties": 1 }), + isErr = this.message.test(result.messages[0].message); + + Assert.areEqual(1, result.messages.length); + Assert.areEqual("warning", result.messages[0].type); + Assert.areEqual(isErr, true); + }, + "drop-initial-before-adjust": function() { + var result = CSSLint.verify("li { drop-initial: url(my-behavior.htc); }", { "obsolete-properties": 1 }), + isErr = this.message.test(result.messages[0].message); + + Assert.areEqual(1, result.messages.length); + Assert.areEqual("warning", result.messages[0].type); + Assert.areEqual(isErr, true); + }, + "drop-initial-before-align": function() { + var result = CSSLint.verify("li { drop-initial-before-align: url(my-behavior.htc); }", { "obsolete-properties": 1 }), + isErr = this.message.test(result.messages[0].message); + + Assert.areEqual(1, result.messages.length); + Assert.areEqual("warning", result.messages[0].type); + Assert.areEqual(isErr, true); + }, + "drop-initial-size": function() { + var result = CSSLint.verify("li { drop-initial-size: url(my-behavior.htc); }", { "obsolete-properties": 1 }), + isErr = this.message.test(result.messages[0].message); + + Assert.areEqual(1, result.messages.length); + Assert.areEqual("warning", result.messages[0].type); + Assert.areEqual(isErr, true); + }, + "drop-initial-value": function() { + var result = CSSLint.verify("li { drop-initial-value: url(my-behavior.htc); }", { "obsolete-properties": 1 }), + isErr = this.message.test(result.messages[0].message); + + Assert.areEqual(1, result.messages.length); + Assert.areEqual("warning", result.messages[0].type); + Assert.areEqual(isErr, true); + }, + "fit": function() { + var result = CSSLint.verify("li { fit: url(my-behavior.htc); }", { "obsolete-properties": 1 }), + isErr = this.message.test(result.messages[0].message); + + Assert.areEqual(1, result.messages.length); + Assert.areEqual("warning", result.messages[0].type); + Assert.areEqual(isErr, true); + }, + "fit-position": function() { + var result = CSSLint.verify("li { fit-position: url(my-behavior.htc); }", { "obsolete-properties": 1 }), + isErr = this.message.test(result.messages[0].message); + + Assert.areEqual(1, result.messages.length); + Assert.areEqual("warning", result.messages[0].type); + Assert.areEqual(isErr, true); + }, + "grid-cell-stacking": function() { + var result = CSSLint.verify("li { grid-cell-stacking: url(my-behavior.htc); }", { "obsolete-properties": 1 }), + isErr = this.message.test(result.messages[0].message); + + Assert.areEqual(1, result.messages.length); + Assert.areEqual("warning", result.messages[0].type); + Assert.areEqual(isErr, true); + }, + "grid-columns": function() { + var result = CSSLint.verify("li { grid-columns: url(my-behavior.htc); }", { "obsolete-properties": 1 }), + isErr = this.message.test(result.messages[0].message); + + Assert.areEqual(1, result.messages.length); + Assert.areEqual("warning", result.messages[0].type); + Assert.areEqual(isErr, true); + }, + "grid-column-align": function() { + var result = CSSLint.verify("li { grid-column-align: url(my-behavior.htc); }", { "obsolete-properties": 1 }), + isErr = this.message.test(result.messages[0].message); + + Assert.areEqual(1, result.messages.length); + Assert.areEqual("warning", result.messages[0].type); + Assert.areEqual(isErr, true); + }, + "grid-column-sizing": function() { + var result = CSSLint.verify("li { grid-column-sizing: url(my-behavior.htc); }", { "obsolete-properties": 1 }), + isErr = this.message.test(result.messages[0].message); + + Assert.areEqual(1, result.messages.length); + Assert.areEqual("warning", result.messages[0].type); + Assert.areEqual(isErr, true); + }, + "grid-column-span": function() { + var result = CSSLint.verify("li { grid-column-span: url(my-behavior.htc); }", { "obsolete-properties": 1 }), + isErr = this.message.test(result.messages[0].message); + + Assert.areEqual(1, result.messages.length); + Assert.areEqual("warning", result.messages[0].type); + Assert.areEqual(isErr, true); + }, + "grid-flow": function() { + var result = CSSLint.verify("li { grid-flow: url(my-behavior.htc); }", { "obsolete-properties": 1 }), + isErr = this.message.test(result.messages[0].message); + + Assert.areEqual(1, result.messages.length); + Assert.areEqual("warning", result.messages[0].type); + Assert.areEqual(isErr, true); + }, + "grid-layer": function() { + var result = CSSLint.verify("li { grid-layer: url(my-behavior.htc); }", { "obsolete-properties": 1 }), + isErr = this.message.test(result.messages[0].message); + + Assert.areEqual(1, result.messages.length); + Assert.areEqual("warning", result.messages[0].type); + Assert.areEqual(isErr, true); + }, + "grid-rows": function() { + var result = CSSLint.verify("li { grid-rows: url(my-behavior.htc); }", { "obsolete-properties": 1 }), + isErr = this.message.test(result.messages[0].message); + + Assert.areEqual(1, result.messages.length); + Assert.areEqual("warning", result.messages[0].type); + Assert.areEqual(isErr, true); + }, + "grid-row-align": function() { + var result = CSSLint.verify("li { grid-row-align: url(my-behavior.htc); }", { "obsolete-properties": 1 }), + isErr = this.message.test(result.messages[0].message); + + Assert.areEqual(1, result.messages.length); + Assert.areEqual("warning", result.messages[0].type); + Assert.areEqual(isErr, true); + }, + "grid-row-span": function() { + var result = CSSLint.verify("li { grid-row-span: url(my-behavior.htc); }", { "obsolete-properties": 1 }), + isErr = this.message.test(result.messages[0].message); + + Assert.areEqual(1, result.messages.length); + Assert.areEqual("warning", result.messages[0].type); + Assert.areEqual(isErr, true); + }, + "grid-row-sizing": function() { + var result = CSSLint.verify("li { grid-row-sizing: url(my-behavior.htc); }", { "obsolete-properties": 1 }), + isErr = this.message.test(result.messages[0].message); + + Assert.areEqual(1, result.messages.length); + Assert.areEqual("warning", result.messages[0].type); + Assert.areEqual(isErr, true); + }, + "hyphenate-after": function() { + var result = CSSLint.verify("li { hyphenate-after: url(y-behavior.htc); }", { "obsolete-properties": 1 }), + isErr = this.message.test(result.messages[0].message); + + Assert.areEqual(1, result.messages.length); + Assert.areEqual("warning", result.messages[0].type); + Assert.areEqual(isErr, true); + }, + "hyphenate-before": function() { + var result = CSSLint.verify("li { hyphenate-before: url(my-behavior.htc); }", { "obsolete-properties": 1 }), + isErr = this.message.test(result.messages[0].message); + + Assert.areEqual(1, result.messages.length); + Assert.areEqual("warning", result.messages[0].type); + Assert.areEqual(isErr, true); + }, + "hyphenate-character": function() { + var result = CSSLint.verify("li { hyphenate-character: url(my-behavior.htc); }", { "obsolete-properties": 1 }), + isErr = this.message.test(result.messages[0].message); + + Assert.areEqual(1, result.messages.length); + Assert.areEqual("warning", result.messages[0].type); + Assert.areEqual(isErr, true); + }, + "hyphenate-lines": function() { + var result = CSSLint.verify("li { hyphenate-lines: url(my-behavior.htc); }", { "obsolete-properties": 1 }), + isErr = this.message.test(result.messages[0].message); + + Assert.areEqual(1, result.messages.length); + Assert.areEqual("warning", result.messages[0].type); + Assert.areEqual(isErr, true); + }, + "hyphenate-resource": function() { + var result = CSSLint.verify("li { hyphenate-resource: url(my-behavior.htc); }", { "obsolete-properties": 1 }), + isErr = this.message.test(result.messages[0].message); + + Assert.areEqual(1, result.messages.length); + Assert.areEqual("warning", result.messages[0].type); + Assert.areEqual(isErr, true); + }, + "ime-mode": function() { + var result = CSSLint.verify("li { ime-mode: url(my-behavior.htc); }", { "obsolete-properties": 1 }), + isErr = this.message.test(result.messages[0].message); + + Assert.areEqual(1, result.messages.length); + Assert.areEqual("warning", result.messages[0].type); + Assert.areEqual(isErr, true); + }, + "marquee-direction": function() { + var result = CSSLint.verify("li { marquee-diretion: url(my-behavior.htc); }", { "obsolete-properties": 1 }), + isErr = this.message.test(result.messages[0].message); + + Assert.areEqual(1, result.messages.length); + Assert.areEqual("warning", result.messages[0].type); + Assert.areEqual(isErr, true); + }, + "marquee-play-count": function() { + var result = CSSLint.verify("li { marquee-play-count: url(my-behavior.htc); }", { "obsolete-properties": 1 }), + isErr = this.message.test(result.messages[0].message); + + Assert.areEqual(1, result.messages.length); + Assert.areEqual("warning", result.messages[0].type); + Assert.areEqual(isErr, true); + }, + "marquee-speed": function() { + var result = CSSLint.verify("li { marquee-speed: url(my-behavior.htc); }", { "obsolete-properties": 1 }), + isErr = this.message.test(result.messages[0].message); + + Assert.areEqual(1, result.messages.length); + Assert.areEqual("warning", result.messages[0].type); + Assert.areEqual(isErr, true); + }, + "marquee-style": function() { + var result = CSSLint.verify("li { marquee-style: url(my-behavior.htc); }", { "obsolete-properties": 1 }), + isErr = this.message.test(result.messages[0].message); + + Assert.areEqual(1, result.messages.length); + Assert.areEqual("warning", result.messages[0].type); + Assert.areEqual(isErr, true); + }, + "move-to": function() { + var result = CSSLint.verify("li { move-to: url(my-behavior.htc); }", { "obsolete-properties": 1 }), + isErr = this.message.test(result.messages[0].message); + + Assert.areEqual(1, result.messages.length); + Assert.areEqual("warning", result.messages[0].type); + Assert.areEqual(isErr, true); + }, + "nav-index": function() { + var result = CSSLint.verify("li { nav-index: url(my-behavior.htc); }", { "obsolete-properties": 1 }), + isErr = this.message.test(result.messages[0].message); + + Assert.areEqual(1, result.messages.length); + Assert.areEqual("warning", result.messages[0].type); + Assert.areEqual(isErr, true); + }, + "overflow-style": function() { + var result = CSSLint.verify("li { overflow-style: url(my-behavior.htc); }", { "obsolete-properties": 1 }), + isErr = this.message.test(result.messages[0].message); + + Assert.areEqual(1, result.messages.length); + Assert.areEqual("warning", result.messages[0].type); + Assert.areEqual(isErr, true); + }, + "page-policy": function() { + var result = CSSLint.verify("li { page-policy: url(my-behavior.htc); }", { "obsolete-properties": 1 }), + isErr = this.message.test(result.messages[0].message); + + Assert.areEqual(1, result.messages.length); + Assert.areEqual("warning", result.messages[0].type); + Assert.areEqual(isErr, true); + }, + "punctuation-trim": function() { + var result = CSSLint.verify("li { punctuation-trim: url(my-behavior.htc); }", { "obsolete-properties": 1 }), + isErr = this.message.test(result.messages[0].message); + + Assert.areEqual(1, result.messages.length); + Assert.areEqual("warning", result.messages[0].type); + Assert.areEqual(isErr, true); + }, + "rotation": function() { + var result = CSSLint.verify("li { rotation: url(my-behavior.htc); }", { "obsolete-properties": 1 }), + isErr = this.message.test(result.messages[0].message); + + Assert.areEqual(1, result.messages.length); + Assert.areEqual("warning", result.messages[0].type); + Assert.areEqual(isErr, true); + }, + "rotation-point": function() { + var result = CSSLint.verify("li { rotation-point: url(my-behavior.htc); }", { "obsolete-properties": 1 }), + isErr = this.message.test(result.messages[0].message); + + Assert.areEqual(1, result.messages.length); + Assert.areEqual("warning", result.messages[0].type); + Assert.areEqual(isErr, true); + }, + "ruby-overhang": function() { + var result = CSSLint.verify("li { ruby-overhang: url(my-behavior.htc); }", { "obsolete-properties": 1 }), + isErr = this.message.test(result.messages[0].message); + + Assert.areEqual(1, result.messages.length); + Assert.areEqual("warning", result.messages[0].type); + Assert.areEqual(isErr, true); + }, + "ruby-span": function() { + var result = CSSLint.verify("li { ruby-span: url(my-behavior.htc); }", { "obsolete-properties": 1 }), + isErr = this.message.test(result.messages[0].message); + + Assert.areEqual(1, result.messages.length); + Assert.areEqual("warning", result.messages[0].type); + Assert.areEqual(isErr, true); + }, + "target": function() { + var result = CSSLint.verify("li { target: url(my-behavior.htc); }", { "obsolete-properties": 1 }), + isErr = this.message.test(result.messages[0].message); + + Assert.areEqual(1, result.messages.length); + Assert.areEqual("warning", result.messages[0].type); + Assert.areEqual(isErr, true); + }, + "target-name": function() { + var result = CSSLint.verify("li { target-name: url(my-behavior.htc); }", { "obsolete-properties": 1 }), + isErr = this.message.test(result.messages[0].message); + + Assert.areEqual(1, result.messages.length); + Assert.areEqual("warning", result.messages[0].type); + Assert.areEqual(isErr, true); + }, + "target-new": function() { + var result = CSSLint.verify("li { target-new: url(my-behavior.htc); }", { "obsolete-properties": 1 }), + isErr = this.message.test(result.messages[0].message); + + Assert.areEqual(1, result.messages.length); + Assert.areEqual("warning", result.messages[0].type); + Assert.areEqual(isErr, true); + }, + "target-position": function() { + var result = CSSLint.verify("li { target-position: url(my-behavior.htc); }", { "obsolete-properties": 1 }), + isErr = this.message.test(result.messages[0].message); + + Assert.areEqual(1, result.messages.length); + Assert.areEqual("warning", result.messages[0].type); + Assert.areEqual(isErr, true); + }, + "text-outline": function() { + var result = CSSLint.verify("li { text-outline: url(my-behavior.htc); }", { "obsolete-properties": 1 }), + isErr = this.message.test(result.messages[0].message); + + Assert.areEqual(1, result.messages.length); + Assert.areEqual("warning", result.messages[0].type); + Assert.areEqual(isErr, true); + }, + "text-wrap": function() { + var result = CSSLint.verify("li { text-wrap: url(my-behavior.htc); }", { "obsolete-properties": 1 }), + isErr = this.message.test(result.messages[0].message); + + Assert.areEqual(1, result.messages.length); + Assert.areEqual("warning", result.messages[0].type); + Assert.areEqual(isErr, true); + }, + "user-modify": function() { + var result = CSSLint.verify("li { user-modify: url(my-behavior.htc); }", { "obsolete-properties": 1 }), + isErr = this.message.test(result.messages[0].message); + + Assert.areEqual(1, result.messages.length); + Assert.areEqual("warning", result.messages[0].type); + Assert.areEqual(isErr, true); + }, + "white-space-collapse": function() { + var result = CSSLint.verify("li { white-space-collapse: url(my-behavior.htc); }", { "obsolete-properties": 1 }), + isErr = this.message.test(result.messages[0].message); + + Assert.areEqual(1, result.messages.length); + Assert.areEqual("warning", result.messages[0].type); + Assert.areEqual(isErr, true); + } + + })); + +})(); diff --git a/tests/rules/rules-max.js b/tests/rules/rules-max.js new file mode 100644 index 00000000..7eba43cc --- /dev/null +++ b/tests/rules/rules-max.js @@ -0,0 +1,52 @@ +(function() { + "use strict"; + + var Assert = YUITest.Assert, MAX_RULES = 499; + + YUITest.TestRunner.add(new YUITest.TestCase({ + + name: "rules-max errors", + + /** + * generate a 500 rule stylesheet + */ + setUp: function() { + var ndx; + + this.count = 0; + this.cssA = ""; + this.cssB = ""; + + function num(n) { + return ("000" + n).substr(-3); + } + + for (ndx = 1; ndx < MAX_RULES; ndx += 1) { + this.cssA += ".rule-a-" + num(ndx) + " { display: inline-block; }"; + this.count += 1; + } + + this.cssB = this.cssA; + for (ndx = 1; ndx < MAX_RULES; ndx += 1) { + this.cssB += ".rule-b-" + num(ndx) + " { display: block; }"; + this.count += 1; + } + }, + + "Using 499 or fewer rules should not result in a warning": function() { + var result = CSSLint.verify(this.cssA, { "rules-max": 1 }); + + Assert.areEqual(0, result.messages.length); + }, + + "Using 500 or more rules should result in a warning": function() { + var result = CSSLint.verify(this.cssB, { "rules-max": 1 }); + + Assert.areEqual(1, result.messages.length); + Assert.areEqual("warning", result.messages[0].type); + Assert.areEqual("There are too many rules (" + this.count + ") in the stylesheet.", result.messages[0].message); + } + + })); + +})(); diff --git a/tests/rules/selector-pattern.js b/tests/rules/selector-pattern.js new file mode 100644 index 00000000..f692eeb8 --- /dev/null +++ b/tests/rules/selector-pattern.js @@ -0,0 +1,102 @@ +(function() { + "use strict"; + + var Assert = YUITest.Assert; + + YUITest.TestRunner.add(new YUITest.TestCase({ + + name: "selector naming convention errors", + + "Subselectors following the camelcase pattern should result in an error when BEM is used": function() { + var result = CSSLint.verify("div .fooBar { display: block; }", { "selector-pattern": 1 }), + msg = /Selector naming convention \(\w+\) not followed\./; + + Assert.areEqual(1, result.messages.length); + Assert.areEqual("warning", result.messages[0].type); + Assert.areEqual(msg.test(result.messages[0].message), true); + }, + + "Selectors following the camelcase pattern should result in an error when BEM is used": function() { + var result = CSSLint.verify(".fooBar { display: block; }", { "selector-pattern": 1 }), + msg = /Selector naming convention \(\w+\) not followed\./; + + Assert.areEqual(1, result.messages.length); + Assert.areEqual("warning", result.messages[0].type); + Assert.areEqual(msg.test(result.messages[0].message), true); + }, + + "Subselectors following the camelcase pattern should not result in an error when camelcase is used": function() { + var result = CSSLint.verify("div .fooBar { display: block; }", { "selector-pattern": 1 }, { "name-pattern": "camelcase"}); + + Assert.areEqual(0, result.messages.length); + }, + + "Selectors following the camelcase pattern should not result in an error when camelcase is used": function() { + var result = CSSLint.verify(".fooBar { display: block; }", { "selector-pattern": 1 }, { "name-pattern": "camelcase"}); + + Assert.areEqual(0, result.messages.length); + }, + + "Subselectors following the snakecase pattern should result in an error when BEM is used": function() { + var result = CSSLint.verify("div .foo_bar { display: block; }", { "selector-pattern": 1 }), + msg = /Selector naming convention \(\w+\) not followed\./; + + Assert.areEqual(1, result.messages.length); + Assert.areEqual("warning", result.messages[0].type); + Assert.areEqual(msg.test(result.messages[0].message), true); + }, + + "Selectors following the snakecase pattern should result in an error when BEM is used": function() { + var result = CSSLint.verify(".foo_bar { display: block; }", { "selector-pattern": 1 }), + msg = /Selector naming convention \(\w+\) not followed\./; + + Assert.areEqual(1, result.messages.length); + Assert.areEqual("warning", result.messages[0].type); + Assert.areEqual(msg.test(result.messages[0].message), true); + }, + + "Subselectors following the snakecase pattern should not result in an error when snakecase is used": function() { + var result = CSSLint.verify("div .foo_bar { display: block; }", { "selector-pattern": 1 }, { "name-pattern": "snakecase"}); + + Assert.areEqual(0, result.messages.length); + }, + + "Selectors following the snakecase pattern should not result in an error when snakecase is used": function() { + var result = CSSLint.verify(".foo_bar { display: block; }", { "selector-pattern": 1 }, { "name-pattern": "snakecase"}); + + Assert.areEqual(0, result.messages.length); + }, + + "Subselectors following the BEM pattern should result in an error when BEM is not used": function() { + var result = CSSLint.verify("div .foo-bar { display: block; }", { "selector-pattern": 1 }, { "name-pattern": "camelcase"}), + msg = /Selector naming convention \(\w+\) not followed\./; + + Assert.areEqual(1, result.messages.length); + Assert.areEqual("warning", result.messages[0].type); + Assert.areEqual(msg.test(result.messages[0].message), true); + }, + + "Selectors following the BEM pattern should result in an error when BEM is not used": function() { + var result = CSSLint.verify(".foo-bar { display: block; }", { "selector-pattern": 1 }, { "name-pattern": "snakecase"}), + msg = /Selector naming convention \(\w+\) not followed\./; + + Assert.areEqual(1, result.messages.length); + Assert.areEqual("warning", result.messages[0].type); + Assert.areEqual(msg.test(result.messages[0].message), true); + }, + + "Subselectors following the BEM pattern should not result in an error": function() { + var result = CSSLint.verify("div .foo-bar { display: block; }", { "selector-pattern": 1 }); + + Assert.areEqual(0, result.messages.length); + }, + + "Selectors following the BEM pattern should not result in an error": function() { + var result = CSSLint.verify(".foo-bar { display: block; }", { "selector-pattern": 1 }); + + Assert.areEqual(0, result.messages.length); + } + + })); + +})(); diff --git a/tests/rules/transparent-color.js b/tests/rules/transparent-color.js new file mode 100644 index 00000000..302a0324 --- /dev/null +++ b/tests/rules/transparent-color.js @@ -0,0 +1,16 @@ +(function() { + "use strict"; + var Assert = YUITest.Assert; + + YUITest.TestRunner.add(new YUITest.TestCase({ + + name: "transparent color Errors", + + "A 'transparent' value for color should result in a warning": function() { + var result = CSSLint.verify("h1 { color: transparent; }", { "transparent-color": 1 }); + Assert.areEqual(1, result.messages.length); + Assert.areEqual("warning", result.messages[0].type); + Assert.areEqual("Color cannot be 'transparent'.", result.messages[0].message); + } + })); +})();