#!/usr/bin/env ruby require 'minitest/autorun' require File.dirname(__FILE__) + '/../test_helper' require 'sass/css' class CSS2SassTest < MiniTest::Test def test_basic css = <<CSS h1 { color: red; } CSS assert_equal(<<SASS, css2sass(css)) h1 color: red SASS assert_equal(<<SASS, css2sass(css, :old => true)) h1 :color red SASS end def test_nesting assert_equal(<<SASS, css2sass(<<CSS)) li display: none a text-decoration: none span color: yellow &:hover text-decoration: underline SASS li { display: none; } li a { text-decoration: none; } li a span { color: yellow; } li a:hover { text-decoration: underline; } CSS end def test_no_nesting_around_rules assert_equal(<<SASS, css2sass(<<CSS)) div .warning color: #d21a19 span .debug cursor: crosshair div .debug cursor: default SASS div .warning { color: #d21a19; } span .debug { cursor: crosshair;} div .debug { cursor: default; } CSS end def test_comments_multiline css = <<CSS /* comment */ elephant.rawr { rampages: excessively; } /* actual multiline comment */ span.turkey { isdinner: true; } .turducken { /* Sounds funny doesn't it */ chimera: not_really; } #overhere { bored: sorta; /* it's for a good cause */ better_than: thread_pools; } #one_more { finally: srsly; } /* just a line here */ CSS sass = <<SASS /* comment elephant.rawr rampages: excessively /* actual multiline *comment span.turkey isdinner: true .turducken /* Sounds funny * doesn't it chimera: not_really #overhere bored: sorta /* it's for a good * cause better_than: thread_pools #one_more finally: srsly /* just a line here SASS assert_equal(sass, css2sass(css)) end def test_fold_commas assert_equal(<<SASS, css2sass(<<CSS)) li .one, .two color: red SASS li .one { color: red; } li .two { color: red; } CSS assert_equal(<<SASS, css2sass(<<CSS)) .one color: green .two color: green color: red .three color: red SASS .one, .two { color: green; } .two, .three { color: red; } CSS end def test_bad_formatting assert_equal(<<SASS, css2sass(<<CSS)) hello parent: true there parent: false who hoo: false why y: true when wen: nao down_here yeah: true SASS hello { parent: true; } hello there { parent: false; } hello who { hoo: false; } hello why { y: true; } hello when { wen: nao; } down_here { yeah: true; } CSS end def test_comments_in_selectors assert_equal(<<SASS, css2sass(<<CSS)) .js #location-navigation-form .form-submit, #business-listing-form .form-submit, #detailTabs ul, #detailsEnhanced #addTags, #locationSearchList, #moreHoods display: none #navListLeft display: none SASS .js #location-navigation-form .form-submit, .js #business-listing-form .form-submit, .js #detailTabs ul, /* .js #addReview, */ /* .js #addTags, */ .js #detailsEnhanced #addTags, .js #locationSearchList, .js #moreHoods, #navListLeft { display: none; } CSS end def test_pseudo_classes_are_escaped assert_equal(<<SASS, css2sass(<<CSS)) \\:focus a: b \\:foo bar: baz SASS :focus {a: b;} :focus :foo {bar: baz;} CSS end def test_subject silence_warnings {assert_equal(<<SASS, css2sass(<<CSS))} .foo .bar! .baz a: b .bip c: d .bar .bonk e: f .flip! &.bar a: b &.baz c: d SASS .foo .bar! .baz {a: b;} .foo .bar! .bip {c: d;} .foo .bar .bonk {e: f;} .flip.bar! {a: b;} .flip.baz! {c: d;} CSS end def test_keyframes assert_equal(<<SASS, css2sass(<<CSS)) @keyframes dash from stroke-dasharray: 1,200 stroke-dashoffset: 0 50% stroke-dasharray: 89,200 stroke-dashoffset: -35 to stroke-dasharray: 89,200 stroke-dashoffset: -124 SASS @keyframes dash { from { stroke-dasharray: 1,200; stroke-dashoffset: 0; } 50% { stroke-dasharray: 89,200; stroke-dashoffset: -35; } to { stroke-dasharray: 89,200; stroke-dashoffset: -124; } } CSS end # Regressions def test_nesting_with_matching_property assert_equal(<<SASS, css2sass(<<CSS)) ul width: 10px div width: 20px article width: 10px p width: 20px SASS ul {width: 10px} ul div {width: 20px} article {width: 10px} article p {width: 20px} CSS end def test_empty_rule assert_equal(<<SASS, css2sass(<<CSS)) a SASS a {} CSS end def test_empty_rule_with_selector_combinator assert_equal(<<SASS, css2sass(<<CSS)) a color: red > b SASS a {color: red} a > b {} CSS end def test_nesting_within_media assert_equal(<<SASS, css2sass(<<CSS)) @media all .next .vevent padding-left: 0 padding-right: 0 SASS @media all { .next .vevent { padding-left: 0; padding-right: 0; } } CSS end def test_multiline_selector_within_media_and_with_child_selector assert_equal(<<SASS, css2sass(<<CSS)) @media all foo bar, baz padding-left: 0 padding-right: 0 SASS @media all { foo bar, baz { padding-left: 0; padding-right: 0; } } CSS end def test_double_comma assert_equal(<<SASS, css2sass(<<CSS)) foo, bar a: b SASS foo, , bar { a: b } CSS end def test_selector_splitting assert_equal(<<SASS, css2sass(<<CSS)) .foo > .bar a: b .baz c: d SASS .foo>.bar {a: b} .foo>.baz {c: d} CSS assert_equal(<<SASS, css2sass(<<CSS)) .foo &::bar a: b &::baz c: d SASS .foo::bar {a: b} .foo::baz {c: d} CSS end def test_triple_nesting assert_equal(<<SASS, css2sass(<<CSS)) .foo .bar .baz a: b SASS .foo .bar .baz {a: b} CSS assert_equal(<<SASS, css2sass(<<CSS)) .bar > .baz c: d SASS .bar > .baz {c: d} CSS end # Error reporting def test_error_reporting css2sass("foo") assert(false, "Expected exception") rescue Sass::SyntaxError => err assert_equal(1, err.sass_line) assert_equal('Invalid CSS after "foo": expected "{", was ""', err.message) end def test_error_reporting_in_line css2sass("foo\nbar }\nbaz") assert(false, "Expected exception") rescue Sass::SyntaxError => err assert_equal(2, err.sass_line) assert_equal('Invalid CSS after "bar ": expected "{", was "}"', err.message) end def test_error_truncate_after css2sass("#{"a" * 16}foo") assert(false, "Expected exception") rescue Sass::SyntaxError => err assert_equal(1, err.sass_line) assert_equal('Invalid CSS after "...aaaaaaaaaaaafoo": expected "{", was ""', err.message) end def test_error_truncate_was css2sass("foo }foo#{"a" * 15}") assert(false, "Expected exception") rescue Sass::SyntaxError => err assert_equal(1, err.sass_line) assert_equal('Invalid CSS after "foo ": expected "{", was "}fooaaaaaaaaaaa..."', err.message) end def test_error_doesnt_truncate_after_when_elipsis_would_add_length css2sass("#{"a" * 15}foo") assert(false, "Expected exception") rescue Sass::SyntaxError => err assert_equal(1, err.sass_line) assert_equal('Invalid CSS after "aaaaaaaaaaaaaaafoo": expected "{", was ""', err.message) end def test_error_doesnt_truncate_was_when_elipsis_would_add_length css2sass("foo }foo#{"a" * 14}") assert(false, "Expected exception") rescue Sass::SyntaxError => err assert_equal(1, err.sass_line) assert_equal('Invalid CSS after "foo ": expected "{", was "}fooaaaaaaaaaaaaaa"', err.message) end def test_error_gets_rid_of_trailing_newline_for_after css2sass("foo \n ") assert(false, "Expected exception") rescue Sass::SyntaxError => err assert_equal(2, err.sass_line) assert_equal('Invalid CSS after "foo": expected "{", was ""', err.message) end def test_error_gets_rid_of_trailing_newline_for_was css2sass("foo \n }foo") assert(false, "Expected exception") rescue Sass::SyntaxError => err assert_equal(2, err.sass_line) assert_equal('Invalid CSS after "foo": expected "{", was "}foo"', err.message) end # Encodings unless Sass::Util.ruby1_8? def test_encoding_error css2sass("foo\nbar\nb\xFEaz".force_encoding("utf-8")) assert(false, "Expected exception") rescue Sass::SyntaxError => e assert_equal(3, e.sass_line) assert_equal('Invalid UTF-8 character "\xFE"', e.message) end def test_ascii_incompatible_encoding_error template = "foo\nbar\nb_z".encode("utf-16le") template[9] = "\xFE".force_encoding("utf-16le") css2sass(template) assert(false, "Expected exception") rescue Sass::SyntaxError => e assert_equal(3, e.sass_line) assert_equal('Invalid UTF-16LE character "\xFE"', e.message) end end private def css2sass(string, opts={}) Sass::CSS.new(string, opts).render end end