#!/usr/bin/env python # -*- coding: utf-8 -*- import unittest import cssbeautifier class CSSBeautifierTest(unittest.TestCase): def resetOptions(self): false = False true = True self.options = cssbeautifier.default_options() self.options.indent_size = 1 self.options.indent_char = '\t' self.options.selector_separator_newline = true self.options.end_with_newline = false self.options.newline_between_rules = false def testGenerated(self): self.resetOptions() test_fragment = self.decodesto t = self.decodesto false = False true = True {{#default_options}} self.options.{{name}} = {{&value}} {{/default_options}} {{#groups}} {{^matrix}} # {{&name}} {{#options}} self.options.{{name}} = {{&value}} {{/options}} {{#tests}} {{#test_line}}.{{/test_line}} {{/tests}} {{/matrix}} {{#matrix}} # {{&name}} - ({{#matrix_context_string}}.{{/matrix_context_string}}) {{#options}} self.options.{{name}} = {{&value}} {{/options}} {{#tests}} {{#test_line}}.{{/test_line}} {{/tests}} {{/matrix}} {{/groups}} def testNewline(self): self.resetOptions() t = self.decodesto self.options.end_with_newline = True t("", "\n") t("\n", "\n") t(".tabs{}\n", ".tabs {}\n") t(".tabs{}", ".tabs {}\n") def testBasics(self): self.resetOptions() t = self.decodesto t("", "") t("\n", "") t(".tabs{}\n", ".tabs {}") t(".tabs{}", ".tabs {}") t(".tabs{color:red}", ".tabs {\n\tcolor: red\n}") t(".tabs{color:rgb(255, 255, 0)}", ".tabs {\n\tcolor: rgb(255, 255, 0)\n}") t(".tabs{background:url('back.jpg')}", ".tabs {\n\tbackground: url('back.jpg')\n}") t("#bla, #foo{color:red}", "#bla,\n#foo {\n\tcolor: red\n}") t("@media print {.tab{}}", "@media print {\n\t.tab {}\n}") t("@media print {.tab{background-image:url(foo@2x.png)}}", "@media print {\n\t.tab {\n\t\tbackground-image: url(foo@2x.png)\n\t}\n}") t("a:before {\n" + "\tcontent: 'a{color:black;}\"\"\\'\\'\"\\n\\n\\na{color:black}\';\n" + "}"); # may not eat the space before "[" t('html.js [data-custom="123"] {\n\topacity: 1.00;\n}') t('html.js *[data-custom="123"] {\n\topacity: 1.00;\n}') # lead-in whitespace determines base-indent. # lead-in newlines are stripped. t("\n\na, img {padding: 0.2px}", "a,\nimg {\n\tpadding: 0.2px\n}") t(" a, img {padding: 0.2px}", " a,\n img {\n \tpadding: 0.2px\n }") t(" \t \na, img {padding: 0.2px}", " \t a,\n \t img {\n \t \tpadding: 0.2px\n \t }") t("\n\n a, img {padding: 0.2px}", "a,\nimg {\n\tpadding: 0.2px\n}") def testSeperateSelectors(self): self.resetOptions() t = self.decodesto t("#bla, #foo{color:red}", "#bla,\n#foo {\n\tcolor: red\n}") t("a, img {padding: 0.2px}", "a,\nimg {\n\tpadding: 0.2px\n}") def testBlockNesting(self): self.resetOptions() t = self.decodesto t("#foo {\n\tbackground-image: url(foo@2x.png);\n\t@font-face {\n\t\tfont-family: 'Bitstream Vera Serif Bold';\n\t\tsrc: url('http://developer.mozilla.org/@api/deki/files/2934/=VeraSeBd.ttf');\n\t}\n}") t("@media screen {\n\t#foo:hover {\n\t\tbackground-image: url(foo@2x.png);\n\t}\n\t@font-face {\n\t\tfont-family: 'Bitstream Vera Serif Bold';\n\t\tsrc: url('http://developer.mozilla.org/@api/deki/files/2934/=VeraSeBd.ttf');\n\t}\n}") # @font-face { # font-family: 'Bitstream Vera Serif Bold'; # src: url('http://developer.mozilla.org/@api/deki/files/2934/=VeraSeBd.ttf'); # } # @media screen { # #foo:hover { # background-image: url(foo.png); # } # @media screen and (min-device-pixel-ratio: 2) { # @font-face { # font-family: 'Helvetica Neue' # } # #foo:hover { # background-image: url(foo@2x.png); # } # } # } t("@font-face {\n\tfont-family: 'Bitstream Vera Serif Bold';\n\tsrc: url('http://developer.mozilla.org/@api/deki/files/2934/=VeraSeBd.ttf');\n}\n@media screen {\n\t#foo:hover {\n\t\tbackground-image: url(foo.png);\n\t}\n\t@media screen and (min-device-pixel-ratio: 2) {\n\t\t@font-face {\n\t\t\tfont-family: 'Helvetica Neue'\n\t\t}\n\t\t#foo:hover {\n\t\t\tbackground-image: url(foo@2x.png);\n\t\t}\n\t}\n}") def testOptions(self): self.resetOptions() self.options.indent_size = 2 self.options.indent_char = ' ' self.options.selector_separator_newline = False t = self.decodesto # pseudo-classes and pseudo-elements t("#foo:hover {\n background-image: url(foo@2x.png)\n}") t("#foo *:hover {\n color: purple\n}") t("::selection {\n color: #ff0000;\n}") # TODO: don't break nested pseduo-classes t("@media screen {.tab,.bat:hover {color:red}}", "@media screen {\n .tab, .bat:hover {\n color: red\n }\n}") # particular edge case with braces and semicolons inside tags that allows custom text t( "a:not(\"foobar\\\";{}omg\"){\ncontent: 'example\\';{} text';\ncontent: \"example\\\";{} text\";}", "a:not(\"foobar\\\";{}omg\") {\n content: 'example\\';{} text';\n content: \"example\\\";{} text\";\n}") def testLessCss(self): self.resetOptions() t = self.decodesto t('.well{ \n @well-bg:@bg-color;@well-fg:@fg-color;}','.well {\n\t@well-bg: @bg-color;\n\t@well-fg: @fg-color;\n}') t('.well {&.active {\nbox-shadow: 0 1px 1px @border-color, 1px 0 1px @border-color;}}', '.well {\n' + '\t&.active {\n' + '\t\tbox-shadow: 0 1px 1px @border-color, 1px 0 1px @border-color;\n' + '\t}\n' + '}') t('a {\n' + '\tcolor: blue;\n' + '\t&:hover {\n' + '\t\tcolor: green;\n' + '\t}\n' + '\t& & &&&.active {\n' + '\t\tcolor: green;\n' + '\t}\n' + '}') # Not sure if this is sensible # but I believe it is correct to not remove the space in "&: hover". t('a {\n' + '\t&: hover {\n' + '\t\tcolor: green;\n' + '\t}\n' + '}'); # import t('@import "test";'); # don't break nested pseudo-classes t("a:first-child{color:red;div:first-child{color:black;}}", "a:first-child {\n\tcolor: red;\n\tdiv:first-child {\n\t\tcolor: black;\n\t}\n}"); # handle SASS/LESS parent reference t("div{&:first-letter {text-transform: uppercase;}}", "div {\n\t&:first-letter {\n\t\ttext-transform: uppercase;\n\t}\n}"); # nested modifiers (&:hover etc) t(".tabs{&:hover{width:10px;}}", ".tabs {\n\t&:hover {\n\t\twidth: 10px;\n\t}\n}") t(".tabs{&.big{width:10px;}}", ".tabs {\n\t&.big {\n\t\twidth: 10px;\n\t}\n}") t(".tabs{&>big{width:10px;}}", ".tabs {\n\t&>big {\n\t\twidth: 10px;\n\t}\n}") t(".tabs{&+.big{width:10px;}}", ".tabs {\n\t&+.big {\n\t\twidth: 10px;\n\t}\n}") # nested rules t(".tabs{.child{width:10px;}}", ".tabs {\n\t.child {\n\t\twidth: 10px;\n\t}\n}") # variables t("@myvar:10px;.tabs{width:10px;}", "@myvar: 10px;\n.tabs {\n\twidth: 10px;\n}") t("@myvar:10px; .tabs{width:10px;}", "@myvar: 10px;\n.tabs {\n\twidth: 10px;\n}") def decodesto(self, input, expectation=None): if expectation == None: expectation = input self.assertMultiLineEqual( cssbeautifier.beautify(input, self.options), expectation) # if the expected is different from input, run it again # expected output should be unchanged when run twice. if not expectation != input: self.assertMultiLineEqual( cssbeautifier.beautify(expectation, self.options), expectation) # Everywhere we do newlines, they should be replaced with opts.eol self.options.eol = '\r\\n'; expectation = expectation.replace('\n', '\r\n') self.assertMultiLineEqual( cssbeautifier.beautify(input, self.options), expectation) input = input.replace('\n', '\r\n') self.assertMultiLineEqual( cssbeautifier.beautify(input, self.options), expectation) self.options.eol = '\n' if __name__ == '__main__': unittest.main()