| # -*- coding: utf-8 -*- |
| require File.dirname(__FILE__) + '/../test_helper' |
| require File.dirname(__FILE__) + '/test_helper' |
| |
| class SourcemapTest < MiniTest::Test |
| def test_to_json_requires_args |
| _, sourcemap = render_with_sourcemap('') |
| assert_raises(ArgumentError) {sourcemap.to_json({})} |
| assert_raises(ArgumentError) {sourcemap.to_json({:css_path => 'foo'})} |
| assert_raises(ArgumentError) {sourcemap.to_json({:sourcemap_path => 'foo'})} |
| end |
| |
| def test_simple_mapping_scss |
| assert_parses_with_sourcemap <<SCSS, <<CSS, <<JSON |
| a { |
| foo: bar; |
| /* SOME COMMENT */ |
| font-size: 12px; |
| } |
| SCSS |
| a { |
| foo: bar; |
| /* SOME COMMENT */ |
| font-size: 12px; } |
| |
| /*# sourceMappingURL=test.css.map */ |
| CSS |
| { |
| "version": 3, |
| "mappings": "AAAA,CAAE;EACA,GAAG,EAAE,GAAG;EACV,kBAAkB;EAChB,SAAS,EAAE,IAAI", |
| "sources": ["test_simple_mapping_scss_inline.scss"], |
| "names": [], |
| "file": "test.css" |
| } |
| JSON |
| end |
| |
| def test_simple_mapping_sass |
| silence_warnings {assert_parses_with_sourcemap <<SASS, <<CSS, <<JSON, :syntax => :sass} |
| a |
| foo: bar |
| /* SOME COMMENT */ |
| :font-size 12px |
| SASS |
| a { |
| foo: bar; |
| /* SOME COMMENT */ |
| font-size: 12px; } |
| |
| /*# sourceMappingURL=test.css.map */ |
| CSS |
| { |
| "version": 3, |
| "mappings": "AAAA,CAAC;EACC,GAAG,EAAE,GAAG;;EAEP,SAAS,EAAC,IAAI", |
| "sources": ["test_simple_mapping_sass_inline.sass"], |
| "names": [], |
| "file": "test.css" |
| } |
| JSON |
| end |
| |
| def test_simple_mapping_with_file_uris |
| uri = Sass::Util.file_uri_from_path(File.absolute_path(filename_for_test(:scss))) |
| assert_parses_with_sourcemap <<SCSS, <<CSS, <<JSON, :sourcemap => :file |
| a { |
| foo: bar; |
| /* SOME COMMENT */ |
| font-size: 12px; |
| } |
| SCSS |
| a { |
| foo: bar; |
| /* SOME COMMENT */ |
| font-size: 12px; } |
| |
| /*# sourceMappingURL=test.css.map */ |
| CSS |
| { |
| "version": 3, |
| "mappings": "AAAA,CAAE;EACA,GAAG,EAAE,GAAG;EACV,kBAAkB;EAChB,SAAS,EAAE,IAAI", |
| "sources": ["#{uri}"], |
| "names": [], |
| "file": "test.css" |
| } |
| JSON |
| end |
| |
| def test_mapping_with_directory_scss |
| options = {:filename => "scss/style.scss", :output => "css/style.css"} |
| assert_parses_with_sourcemap <<SCSS, <<CSS, <<JSON, options |
| a { |
| foo: bar; |
| /* SOME COMMENT */ |
| font-size: 12px; |
| } |
| SCSS |
| a { |
| foo: bar; |
| /* SOME COMMENT */ |
| font-size: 12px; } |
| |
| /*# sourceMappingURL=style.css.map */ |
| CSS |
| { |
| "version": 3, |
| "mappings": "AAAA,CAAE;EACA,GAAG,EAAE,GAAG;EACV,kBAAkB;EAChB,SAAS,EAAE,IAAI", |
| "sources": ["../scss/style.scss"], |
| "names": [], |
| "file": "style.css" |
| } |
| JSON |
| end |
| |
| def test_mapping_with_directory_sass |
| options = {:filename => "sass/style.sass", :output => "css/style.css", :syntax => :sass} |
| silence_warnings {assert_parses_with_sourcemap <<SASS, <<CSS, <<JSON, options} |
| a |
| foo: bar |
| /* SOME COMMENT */ |
| :font-size 12px |
| SASS |
| a { |
| foo: bar; |
| /* SOME COMMENT */ |
| font-size: 12px; } |
| |
| /*# sourceMappingURL=style.css.map */ |
| CSS |
| { |
| "version": 3, |
| "mappings": "AAAA,CAAC;EACC,GAAG,EAAE,GAAG;;EAEP,SAAS,EAAC,IAAI", |
| "sources": ["../sass/style.sass"], |
| "names": [], |
| "file": "style.css" |
| } |
| JSON |
| end |
| |
| def test_simple_charset_mapping_scss |
| assert_parses_with_sourcemap <<SCSS, <<CSS, <<JSON |
| a { |
| fóó: bár; |
| } |
| SCSS |
| @charset "UTF-8"; |
| a { |
| fóó: bár; } |
| |
| /*# sourceMappingURL=test.css.map */ |
| CSS |
| { |
| "version": 3, |
| "mappings": ";AAAA,CAAE;EACA,GAAG,EAAE,GAAG", |
| "sources": ["test_simple_charset_mapping_scss_inline.scss"], |
| "names": [], |
| "file": "test.css" |
| } |
| JSON |
| end |
| |
| def test_simple_charset_mapping_sass |
| assert_parses_with_sourcemap <<SASS, <<CSS, <<JSON, :syntax => :sass |
| a |
| fóó: bár |
| SASS |
| @charset "UTF-8"; |
| a { |
| fóó: bár; } |
| |
| /*# sourceMappingURL=test.css.map */ |
| CSS |
| { |
| "version": 3, |
| "mappings": ";AAAA,CAAC;EACC,GAAG,EAAE,GAAG", |
| "sources": ["test_simple_charset_mapping_sass_inline.sass"], |
| "names": [], |
| "file": "test.css" |
| } |
| JSON |
| end |
| |
| def test_different_charset_than_encoding_scss |
| assert_parses_with_sourcemap(<<SCSS.force_encoding("IBM866"), <<CSS, <<JSON) |
| @charset "IBM866"; |
| f\x86\x86 { |
| \x86: b; |
| } |
| SCSS |
| @charset "UTF-8"; |
| fЖЖ { |
| Ж: b; } |
| |
| /*# sourceMappingURL=test.css.map */ |
| CSS |
| { |
| "version": 3, |
| "mappings": ";AACA,GAAI;EACF,CAAC,EAAE,CAAC", |
| "sources": ["test_different_charset_than_encoding_scss_inline.scss"], |
| "names": [], |
| "file": "test.css" |
| } |
| JSON |
| end |
| |
| def test_different_charset_than_encoding_sass |
| assert_parses_with_sourcemap(<<SASS.force_encoding("IBM866"), <<CSS, <<JSON, :syntax => :sass) |
| @charset "IBM866" |
| f\x86\x86 |
| \x86: b |
| SASS |
| @charset "UTF-8"; |
| fЖЖ { |
| Ж: b; } |
| |
| /*# sourceMappingURL=test.css.map */ |
| CSS |
| { |
| "version": 3, |
| "mappings": ";AACA,GAAG;EACD,CAAC,EAAE,CAAC", |
| "sources": ["test_different_charset_than_encoding_sass_inline.sass"], |
| "names": [], |
| "file": "test.css" |
| } |
| JSON |
| end |
| |
| def test_import_sourcemap_scss |
| assert_parses_with_mapping <<'SCSS', <<'CSS' |
| @import {{1}}url(foo){{/1}},{{2}}url(moo) {{/2}}, {{3}}url(bar) {{/3}}; |
| @import {{4}}url(baz) screen print{{/4}}; |
| SCSS |
| {{1}}@import url(foo){{/1}}; |
| {{2}}@import url(moo){{/2}}; |
| {{3}}@import url(bar){{/3}}; |
| {{4}}@import url(baz) screen print{{/4}}; |
| |
| /*# sourceMappingURL=test.css.map */ |
| CSS |
| end |
| |
| def test_import_sourcemap_sass |
| assert_parses_with_mapping <<'SASS', <<'CSS', :syntax => :sass |
| @import {{1}}foo.css{{/1}},{{2}}moo.css{{/2}}, {{3}}bar.css{{/3}} |
| @import {{4}}url(baz.css){{/4}} |
| @import {{5}}url(qux.css) screen print{{/5}} |
| SASS |
| {{1}}@import url(foo.css){{/1}}; |
| {{2}}@import url(moo.css){{/2}}; |
| {{3}}@import url(bar.css){{/3}}; |
| {{4}}@import url(baz.css){{/4}}; |
| {{5}}@import url(qux.css) screen print{{/5}}; |
| |
| /*# sourceMappingURL=test.css.map */ |
| CSS |
| end |
| |
| def test_media_sourcemap_scss |
| assert_parses_with_mapping <<'SCSS', <<'CSS' |
| {{1}}@media screen, tv {{/1}}{ |
| {{2}}body {{/2}}{ |
| {{3}}max-width{{/3}}: {{4}}1070px{{/4}}; |
| } |
| } |
| SCSS |
| {{1}}@media screen, tv{{/1}} { |
| {{2}}body{{/2}} { |
| {{3}}max-width{{/3}}: {{4}}1070px{{/4}}; } } |
| |
| /*# sourceMappingURL=test.css.map */ |
| CSS |
| end |
| |
| def test_media_sourcemap_sass |
| assert_parses_with_mapping <<'SASS', <<'CSS', :syntax => :sass |
| {{1}}@media screen, tv{{/1}} |
| {{2}}body{{/2}} |
| {{3}}max-width{{/3}}: {{4}}1070px{{/4}} |
| SASS |
| {{1}}@media screen, tv{{/1}} { |
| {{2}}body{{/2}} { |
| {{3}}max-width{{/3}}: {{4}}1070px{{/4}}; } } |
| |
| /*# sourceMappingURL=test.css.map */ |
| CSS |
| end |
| |
| def test_interpolation_and_vars_sourcemap_scss |
| assert_parses_with_mapping <<'SCSS', <<'CSS' |
| $te: "te"; |
| $teal: {{5}}teal{{/5}}; |
| {{1}}p {{/1}}{ |
| {{2}}con#{$te}nt{{/2}}: {{3}}"I a#{$te} #{5 + 10} pies!"{{/3}}; |
| {{4}}color{{/4}}: $teal; |
| } |
| |
| $name: foo; |
| $attr: border; |
| {{6}}p.#{$name} {{/6}}{ |
| {{7}}#{$attr}-color{{/7}}: {{8}}blue{{/8}}; |
| $font-size: 12px; |
| $line-height: 30px; |
| {{9}}font{{/9}}: {{10}}#{$font-size}/#{$line-height}{{/10}}; |
| } |
| SCSS |
| {{1}}p{{/1}} { |
| {{2}}content{{/2}}: {{3}}"I ate 15 pies!"{{/3}}; |
| {{4}}color{{/4}}: {{5}}teal{{/5}}; } |
| |
| {{6}}p.foo{{/6}} { |
| {{7}}border-color{{/7}}: {{8}}blue{{/8}}; |
| {{9}}font{{/9}}: {{10}}12px/30px{{/10}}; } |
| |
| /*# sourceMappingURL=test.css.map */ |
| CSS |
| end |
| |
| def test_interpolation_and_vars_sourcemap_sass |
| assert_parses_with_mapping <<'SASS', <<'CSS', :syntax => :sass |
| $te: "te" |
| $teal: {{5}}teal{{/5}} |
| {{1}}p{{/1}} |
| {{2}}con#{$te}nt{{/2}}: {{3}}"I a#{$te} #{5 + 10} pies!"{{/3}} |
| {{4}}color{{/4}}: $teal |
| |
| $name: foo |
| $attr: border |
| {{6}}p.#{$name}{{/6}} |
| {{7}}#{$attr}-color{{/7}}: {{8}}blue{{/8}} |
| $font-size: 12px |
| $line-height: 30px |
| {{9}}font{{/9}}: {{10}}#{$font-size}/#{$line-height}{{/10}} |
| SASS |
| {{1}}p{{/1}} { |
| {{2}}content{{/2}}: {{3}}"I ate 15 pies!"{{/3}}; |
| {{4}}color{{/4}}: {{5}}teal{{/5}}; } |
| |
| {{6}}p.foo{{/6}} { |
| {{7}}border-color{{/7}}: {{8}}blue{{/8}}; |
| {{9}}font{{/9}}: {{10}}12px/30px{{/10}}; } |
| |
| /*# sourceMappingURL=test.css.map */ |
| CSS |
| end |
| |
| def test_selectors_properties_sourcemap_scss |
| assert_parses_with_mapping <<'SCSS', <<'CSS' |
| $width: 2px; |
| $translucent-red: rgba(255, 0, 0, 0.5); |
| {{1}}a {{/1}}{ |
| {{9}}.special {{/9}}{ |
| {{10}}color{{/10}}: {{11}}red{{/11}}; |
| {{12}}&:hover {{/12}}{ |
| {{13}}foo{{/13}}: {{14}}bar{{/14}}; |
| {{15}}cursor{{/15}}: {{16}}e + -resize{{/16}}; |
| {{17}}color{{/17}}: {{18}}opacify($translucent-red, 0.3){{/18}}; |
| } |
| {{19}}&:after {{/19}}{ |
| {{20}}content{{/20}}: {{21}}"I ate #{5 + 10} pies #{$width} thick!"{{/21}}; |
| } |
| } |
| {{22}}&:active {{/22}}{ |
| {{23}}border{{/23}}: {{24}}$width solid black{{/24}}; |
| } |
| {{2}}/* SOME COMMENT */{{/2}} |
| {{3}}font{{/3}}: {{4}}2px/3px {{/4}}{ |
| {{5}}family{{/5}}: {{6}}fantasy{{/6}}; |
| {{7}}size{{/7}}: {{8}}1em + (2em * 3){{/8}}; |
| } |
| } |
| SCSS |
| {{1}}a{{/1}} { |
| {{2}}/* SOME COMMENT */{{/2}} |
| {{3}}font{{/3}}: {{4}}2px/3px{{/4}}; |
| {{5}}font-family{{/5}}: {{6}}fantasy{{/6}}; |
| {{7}}font-size{{/7}}: {{8}}7em{{/8}}; } |
| {{9}}a .special{{/9}} { |
| {{10}}color{{/10}}: {{11}}red{{/11}}; } |
| {{12}}a .special:hover{{/12}} { |
| {{13}}foo{{/13}}: {{14}}bar{{/14}}; |
| {{15}}cursor{{/15}}: {{16}}e-resize{{/16}}; |
| {{17}}color{{/17}}: {{18}}rgba(255, 0, 0, 0.8){{/18}}; } |
| {{19}}a .special:after{{/19}} { |
| {{20}}content{{/20}}: {{21}}"I ate 15 pies 2px thick!"{{/21}}; } |
| {{22}}a:active{{/22}} { |
| {{23}}border{{/23}}: {{24}}2px solid black{{/24}}; } |
| |
| /*# sourceMappingURL=test.css.map */ |
| CSS |
| end |
| |
| def test_selectors_properties_sourcemap_sass |
| assert_parses_with_mapping <<'SASS', <<'CSS', :syntax => :sass |
| $width: 2px |
| $translucent-red: rgba(255, 0, 0, 0.5) |
| {{1}}a{{/1}} |
| {{9}}.special{{/9}} |
| {{10}}color{{/10}}: {{11}}red{{/11}} |
| {{12}}&:hover{{/12}} |
| {{13}}foo{{/13}}: {{14}}bar{{/14}} |
| {{15}}cursor{{/15}}: {{16}}e + -resize{{/16}} |
| {{17}}color{{/17}}: {{18}}opacify($translucent-red, 0.3){{/18}} |
| {{19}}&:after{{/19}} |
| {{20}}content{{/20}}: {{21}}"I ate #{5 + 10} pies #{$width} thick!"{{/21}} |
| {{22}}&:active{{/22}} |
| {{23}}border{{/23}}: {{24}}$width solid black{{/24}} |
| |
| {{2}}/* SOME COMMENT */{{/2}} |
| {{3}}font{{/3}}: {{4}}2px/3px{{/4}} |
| {{5}}family{{/5}}: {{6}}fantasy{{/6}} |
| {{7}}size{{/7}}: {{8}}1em + (2em * 3){{/8}} |
| SASS |
| {{1}}a{{/1}} { |
| {{2}}/* SOME COMMENT */{{/2}} |
| {{3}}font{{/3}}: {{4}}2px/3px{{/4}}; |
| {{5}}font-family{{/5}}: {{6}}fantasy{{/6}}; |
| {{7}}font-size{{/7}}: {{8}}7em{{/8}}; } |
| {{9}}a .special{{/9}} { |
| {{10}}color{{/10}}: {{11}}red{{/11}}; } |
| {{12}}a .special:hover{{/12}} { |
| {{13}}foo{{/13}}: {{14}}bar{{/14}}; |
| {{15}}cursor{{/15}}: {{16}}e-resize{{/16}}; |
| {{17}}color{{/17}}: {{18}}rgba(255, 0, 0, 0.8){{/18}}; } |
| {{19}}a .special:after{{/19}} { |
| {{20}}content{{/20}}: {{21}}"I ate 15 pies 2px thick!"{{/21}}; } |
| {{22}}a:active{{/22}} { |
| {{23}}border{{/23}}: {{24}}2px solid black{{/24}}; } |
| |
| /*# sourceMappingURL=test.css.map */ |
| CSS |
| end |
| |
| def test_extend_sourcemap_scss |
| assert_parses_with_mapping <<'SCSS', <<'CSS' |
| {{1}}.error {{/1}}{ |
| {{2}}border{{/2}}: {{3}}1px #ff00aa{{/3}}; |
| {{4}}background-color{{/4}}: {{5}}#fdd{{/5}}; |
| } |
| {{6}}.seriousError {{/6}}{ |
| @extend .error; |
| {{7}}border-width{{/7}}: {{8}}3px{{/8}}; |
| } |
| SCSS |
| {{1}}.error, .seriousError{{/1}} { |
| {{2}}border{{/2}}: {{3}}1px #ff00aa{{/3}}; |
| {{4}}background-color{{/4}}: {{5}}#fdd{{/5}}; } |
| |
| {{6}}.seriousError{{/6}} { |
| {{7}}border-width{{/7}}: {{8}}3px{{/8}}; } |
| |
| /*# sourceMappingURL=test.css.map */ |
| CSS |
| end |
| |
| def test_extend_sourcemap_sass |
| assert_parses_with_mapping <<'SASS', <<'CSS', :syntax => :sass |
| {{1}}.error{{/1}} |
| {{2}}border{{/2}}: {{3}}1px #f00{{/3}} |
| {{4}}background-color{{/4}}: {{5}}#fdd{{/5}} |
| |
| {{6}}.seriousError{{/6}} |
| @extend .error |
| {{7}}border-width{{/7}}: {{8}}3px{{/8}} |
| SASS |
| {{1}}.error, .seriousError{{/1}} { |
| {{2}}border{{/2}}: {{3}}1px #f00{{/3}}; |
| {{4}}background-color{{/4}}: {{5}}#fdd{{/5}}; } |
| |
| {{6}}.seriousError{{/6}} { |
| {{7}}border-width{{/7}}: {{8}}3px{{/8}}; } |
| |
| /*# sourceMappingURL=test.css.map */ |
| CSS |
| end |
| |
| def test_for_sourcemap_scss |
| assert_parses_with_mapping <<'SCSS', <<'CSS' |
| @for $i from 1 through 3 { |
| {{1}}{{4}}{{7}}.item-#{$i} {{/1}}{{/4}}{{/7}}{ {{2}}{{5}}{{8}}width{{/2}}{{/5}}{{/8}}: {{3}}{{6}}{{9}}2em * $i{{/3}}{{/6}}{{/9}}; } |
| } |
| SCSS |
| {{1}}.item-1{{/1}} { |
| {{2}}width{{/2}}: {{3}}2em{{/3}}; } |
| |
| {{4}}.item-2{{/4}} { |
| {{5}}width{{/5}}: {{6}}4em{{/6}}; } |
| |
| {{7}}.item-3{{/7}} { |
| {{8}}width{{/8}}: {{9}}6em{{/9}}; } |
| |
| /*# sourceMappingURL=test.css.map */ |
| CSS |
| end |
| |
| def test_for_sourcemap_sass |
| assert_parses_with_mapping <<'SASS', <<'CSS', :syntax => :sass |
| @for $i from 1 through 3 |
| {{1}}{{4}}{{7}}.item-#{$i}{{/1}}{{/4}}{{/7}} |
| {{2}}{{5}}{{8}}width{{/2}}{{/5}}{{/8}}: {{3}}{{6}}{{9}}2em * $i{{/3}}{{/6}}{{/9}} |
| SASS |
| {{1}}.item-1{{/1}} { |
| {{2}}width{{/2}}: {{3}}2em{{/3}}; } |
| |
| {{4}}.item-2{{/4}} { |
| {{5}}width{{/5}}: {{6}}4em{{/6}}; } |
| |
| {{7}}.item-3{{/7}} { |
| {{8}}width{{/8}}: {{9}}6em{{/9}}; } |
| |
| /*# sourceMappingURL=test.css.map */ |
| CSS |
| end |
| |
| def test_while_sourcemap_scss |
| assert_parses_with_mapping <<'SCSS', <<'CSS' |
| $i: 6; |
| @while $i > 0 { |
| {{1}}{{4}}{{7}}.item-#{$i} {{/1}}{{/4}}{{/7}}{ {{2}}{{5}}{{8}}width{{/2}}{{/5}}{{/8}}: {{3}}{{6}}{{9}}2em * $i{{/3}}{{/6}}{{/9}}; } |
| $i: $i - 2 !global; |
| } |
| SCSS |
| {{1}}.item-6{{/1}} { |
| {{2}}width{{/2}}: {{3}}12em{{/3}}; } |
| |
| {{4}}.item-4{{/4}} { |
| {{5}}width{{/5}}: {{6}}8em{{/6}}; } |
| |
| {{7}}.item-2{{/7}} { |
| {{8}}width{{/8}}: {{9}}4em{{/9}}; } |
| |
| /*# sourceMappingURL=test.css.map */ |
| CSS |
| end |
| |
| def test_while_sourcemap_sass |
| assert_parses_with_mapping <<'SASS', <<'CSS', :syntax => :sass |
| $i: 6 |
| @while $i > 0 |
| {{1}}{{4}}{{7}}.item-#{$i}{{/1}}{{/4}}{{/7}} |
| {{2}}{{5}}{{8}}width{{/2}}{{/5}}{{/8}}: {{3}}{{6}}{{9}}2em * $i{{/3}}{{/6}}{{/9}} |
| $i: $i - 2 !global |
| SASS |
| {{1}}.item-6{{/1}} { |
| {{2}}width{{/2}}: {{3}}12em{{/3}}; } |
| |
| {{4}}.item-4{{/4}} { |
| {{5}}width{{/5}}: {{6}}8em{{/6}}; } |
| |
| {{7}}.item-2{{/7}} { |
| {{8}}width{{/8}}: {{9}}4em{{/9}}; } |
| |
| /*# sourceMappingURL=test.css.map */ |
| CSS |
| end |
| |
| def test_each_sourcemap_scss |
| assert_parses_with_mapping <<'SCSS', <<'CSS' |
| @each $animal in puma, sea-slug, egret, salamander { |
| {{1}}{{4}}{{7}}{{10}}.#{$animal}-icon {{/1}}{{/4}}{{/7}}{{/10}}{ |
| {{2}}{{5}}{{8}}{{11}}background-image{{/2}}{{/5}}{{/8}}{{/11}}: {{3}}{{6}}{{9}}{{12}}url('/images/#{$animal}.png'){{/3}}{{/6}}{{/9}}{{/12}}; |
| } |
| } |
| SCSS |
| {{1}}.puma-icon{{/1}} { |
| {{2}}background-image{{/2}}: {{3}}url("/images/puma.png"){{/3}}; } |
| |
| {{4}}.sea-slug-icon{{/4}} { |
| {{5}}background-image{{/5}}: {{6}}url("/images/sea-slug.png"){{/6}}; } |
| |
| {{7}}.egret-icon{{/7}} { |
| {{8}}background-image{{/8}}: {{9}}url("/images/egret.png"){{/9}}; } |
| |
| {{10}}.salamander-icon{{/10}} { |
| {{11}}background-image{{/11}}: {{12}}url("/images/salamander.png"){{/12}}; } |
| |
| /*# sourceMappingURL=test.css.map */ |
| CSS |
| end |
| |
| def test_each_sourcemap_sass |
| assert_parses_with_mapping <<'SASS', <<'CSS', :syntax => :sass |
| @each $animal in puma, sea-slug, egret, salamander |
| {{1}}{{4}}{{7}}{{10}}.#{$animal}-icon{{/1}}{{/4}}{{/7}}{{/10}} |
| {{2}}{{5}}{{8}}{{11}}background-image{{/2}}{{/5}}{{/8}}{{/11}}: {{3}}{{6}}{{9}}{{12}}url('/images/#{$animal}.png'){{/3}}{{/6}}{{/9}}{{/12}} |
| SASS |
| {{1}}.puma-icon{{/1}} { |
| {{2}}background-image{{/2}}: {{3}}url("/images/puma.png"){{/3}}; } |
| |
| {{4}}.sea-slug-icon{{/4}} { |
| {{5}}background-image{{/5}}: {{6}}url("/images/sea-slug.png"){{/6}}; } |
| |
| {{7}}.egret-icon{{/7}} { |
| {{8}}background-image{{/8}}: {{9}}url("/images/egret.png"){{/9}}; } |
| |
| {{10}}.salamander-icon{{/10}} { |
| {{11}}background-image{{/11}}: {{12}}url("/images/salamander.png"){{/12}}; } |
| |
| /*# sourceMappingURL=test.css.map */ |
| CSS |
| end |
| |
| def test_mixin_sourcemap_scss |
| assert_parses_with_mapping <<'SCSS', <<'CSS' |
| @mixin large-text { |
| font: { |
| {{2}}size{{/2}}: {{3}}20px{{/3}}; |
| {{4}}weight{{/4}}: {{5}}bold{{/5}}; |
| } |
| {{6}}color{{/6}}: {{7}}#ff0000{{/7}}; |
| } |
| |
| {{1}}.page-title {{/1}}{ |
| @include large-text; |
| {{8}}padding{{/8}}: {{9}}4px{{/9}}; |
| } |
| |
| @mixin dashed-border($color, $width: {{14}}1in{{/14}}) { |
| border: { |
| {{11}}{{18}}color{{/11}}{{/18}}: $color; |
| {{13}}{{20}}width{{/13}}{{/20}}: $width; |
| {{15}}{{22}}style{{/15}}{{/22}}: {{16}}{{23}}dashed{{/16}}{{/23}}; |
| } |
| } |
| |
| {{10}}p {{/10}}{ @include dashed-border({{12}}blue{{/12}}); } |
| {{17}}h1 {{/17}}{ @include dashed-border({{19}}blue{{/19}}, {{21}}2in{{/21}}); } |
| |
| @mixin box-shadow($shadows...) { |
| {{25}}-moz-box-shadow{{/25}}: {{26}}$shadows{{/26}}; |
| {{27}}-webkit-box-shadow{{/27}}: {{28}}$shadows{{/28}}; |
| {{29}}box-shadow{{/29}}: {{30}}$shadows{{/30}}; |
| } |
| |
| {{24}}.shadows {{/24}}{ |
| @include box-shadow(0px 4px 5px #666, 2px 6px 10px #999); |
| } |
| SCSS |
| {{1}}.page-title{{/1}} { |
| {{2}}font-size{{/2}}: {{3}}20px{{/3}}; |
| {{4}}font-weight{{/4}}: {{5}}bold{{/5}}; |
| {{6}}color{{/6}}: {{7}}#ff0000{{/7}}; |
| {{8}}padding{{/8}}: {{9}}4px{{/9}}; } |
| |
| {{10}}p{{/10}} { |
| {{11}}border-color{{/11}}: {{12}}blue{{/12}}; |
| {{13}}border-width{{/13}}: {{14}}1in{{/14}}; |
| {{15}}border-style{{/15}}: {{16}}dashed{{/16}}; } |
| |
| {{17}}h1{{/17}} { |
| {{18}}border-color{{/18}}: {{19}}blue{{/19}}; |
| {{20}}border-width{{/20}}: {{21}}2in{{/21}}; |
| {{22}}border-style{{/22}}: {{23}}dashed{{/23}}; } |
| |
| {{24}}.shadows{{/24}} { |
| {{25}}-moz-box-shadow{{/25}}: {{26}}0px 4px 5px #666, 2px 6px 10px #999{{/26}}; |
| {{27}}-webkit-box-shadow{{/27}}: {{28}}0px 4px 5px #666, 2px 6px 10px #999{{/28}}; |
| {{29}}box-shadow{{/29}}: {{30}}0px 4px 5px #666, 2px 6px 10px #999{{/30}}; } |
| |
| /*# sourceMappingURL=test.css.map */ |
| CSS |
| end |
| |
| def test_mixin_sourcemap_sass |
| silence_warnings {assert_parses_with_mapping <<'SASS', <<'CSS', :syntax => :sass} |
| =large-text |
| :font |
| {{2}}size{{/2}}: {{3}}20px{{/3}} |
| {{4}}weight{{/4}}: {{5}}bold{{/5}} |
| {{6}}color{{/6}}: {{7}}#ff0000{{/7}} |
| |
| {{1}}.page-title{{/1}} |
| +large-text |
| {{8}}padding{{/8}}: {{9}}4px{{/9}} |
| |
| =dashed-border($color, $width: {{14}}1in{{/14}}) |
| border: |
| {{11}}{{18}}color{{/11}}{{/18}}: $color |
| {{13}}{{20}}width{{/13}}{{/20}}: $width |
| {{15}}{{22}}style{{/15}}{{/22}}: {{16}}{{23}}dashed{{/16}}{{/23}} |
| |
| {{10}}p{{/10}} |
| +dashed-border({{12}}blue{{/12}}) |
| |
| {{17}}h1{{/17}} |
| +dashed-border({{19}}blue{{/19}}, {{21}}2in{{/21}}) |
| |
| =box-shadow($shadows...) |
| {{25}}-moz-box-shadow{{/25}}: {{26}}$shadows{{/26}} |
| {{27}}-webkit-box-shadow{{/27}}: {{28}}$shadows{{/28}} |
| {{29}}box-shadow{{/29}}: {{30}}$shadows{{/30}} |
| |
| {{24}}.shadows{{/24}} |
| +box-shadow(0px 4px 5px #666, 2px 6px 10px #999) |
| SASS |
| {{1}}.page-title{{/1}} { |
| {{2}}font-size{{/2}}: {{3}}20px{{/3}}; |
| {{4}}font-weight{{/4}}: {{5}}bold{{/5}}; |
| {{6}}color{{/6}}: {{7}}#ff0000{{/7}}; |
| {{8}}padding{{/8}}: {{9}}4px{{/9}}; } |
| |
| {{10}}p{{/10}} { |
| {{11}}border-color{{/11}}: {{12}}blue{{/12}}; |
| {{13}}border-width{{/13}}: {{14}}1in{{/14}}; |
| {{15}}border-style{{/15}}: {{16}}dashed{{/16}}; } |
| |
| {{17}}h1{{/17}} { |
| {{18}}border-color{{/18}}: {{19}}blue{{/19}}; |
| {{20}}border-width{{/20}}: {{21}}2in{{/21}}; |
| {{22}}border-style{{/22}}: {{23}}dashed{{/23}}; } |
| |
| {{24}}.shadows{{/24}} { |
| {{25}}-moz-box-shadow{{/25}}: {{26}}0px 4px 5px #666, 2px 6px 10px #999{{/26}}; |
| {{27}}-webkit-box-shadow{{/27}}: {{28}}0px 4px 5px #666, 2px 6px 10px #999{{/28}}; |
| {{29}}box-shadow{{/29}}: {{30}}0px 4px 5px #666, 2px 6px 10px #999{{/30}}; } |
| |
| /*# sourceMappingURL=test.css.map */ |
| CSS |
| end |
| |
| def test_function_sourcemap_scss |
| assert_parses_with_mapping <<'SCSS', <<'CSS' |
| $grid-width: 20px; |
| $gutter-width: 5px; |
| |
| @function grid-width($n) { |
| @return $n * $grid-width + ($n - 1) * $gutter-width; |
| } |
| {{1}}sidebar {{/1}}{ {{2}}width{{/2}}: {{3}}grid-width(5){{/3}}; } |
| SCSS |
| {{1}}sidebar{{/1}} { |
| {{2}}width{{/2}}: {{3}}120px{{/3}}; } |
| |
| /*# sourceMappingURL=test.css.map */ |
| CSS |
| end |
| |
| def test_function_sourcemap_sass |
| assert_parses_with_mapping <<'SASS', <<'CSS', :syntax => :sass |
| $grid-width: 20px |
| $gutter-width: 5px |
| |
| @function grid-width($n) |
| @return $n * $grid-width + ($n - 1) * $gutter-width |
| |
| {{1}}sidebar{{/1}} |
| {{2}}width{{/2}}: {{3}}grid-width(5){{/3}} |
| SASS |
| {{1}}sidebar{{/1}} { |
| {{2}}width{{/2}}: {{3}}120px{{/3}}; } |
| |
| /*# sourceMappingURL=test.css.map */ |
| CSS |
| end |
| |
| # Regression tests |
| |
| def test_properties_sass |
| silence_warnings {assert_parses_with_mapping <<SASS, <<CSS, :syntax => :sass} |
| {{1}}.foo{{/1}} |
| :{{2}}name{{/2}} {{3}}value{{/3}} |
| {{4}}name{{/4}}: {{5}}value{{/5}} |
| :{{6}}name{{/6}} {{7}}value{{/7}} |
| {{8}}name{{/8}}: {{9}}value{{/9}} |
| SASS |
| {{1}}.foo{{/1}} { |
| {{2}}name{{/2}}: {{3}}value{{/3}}; |
| {{4}}name{{/4}}: {{5}}value{{/5}}; |
| {{6}}name{{/6}}: {{7}}value{{/7}}; |
| {{8}}name{{/8}}: {{9}}value{{/9}}; } |
| |
| /*# sourceMappingURL=test.css.map */ |
| CSS |
| end |
| |
| def test_multiline_script_scss |
| assert_parses_with_mapping <<SCSS, <<CSS, :syntax => :scss |
| $var: {{3}}foo + |
| bar{{/3}}; {{1}}x {{/1}}{ {{2}}y{{/2}}: $var } |
| SCSS |
| {{1}}x{{/1}} { |
| {{2}}y{{/2}}: {{3}}foobar{{/3}}; } |
| |
| /*# sourceMappingURL=test.css.map */ |
| CSS |
| end |
| |
| def test_multiline_interpolation_source_range |
| engine = Sass::Engine.new(<<-SCSS, :cache => false, :syntax => :scss) |
| p { |
| filter: progid:DXImageTransform( |
| '\#{123}'); |
| } |
| SCSS |
| |
| interpolated = engine.to_tree.children. |
| first.children. |
| first.value.first.children[1] |
| assert_equal "123", interpolated.to_sass |
| range = interpolated.source_range |
| assert_equal 3, range.start_pos.line |
| assert_equal 14, range.start_pos.offset |
| assert_equal 3, range.end_pos.line |
| assert_equal 17, range.end_pos.offset |
| end |
| |
| def test_list_source_range |
| engine = Sass::Engine.new(<<-SCSS, :cache => false, :syntax => :scss) |
| @each $a, $b in (1, 2), (2, 4), (3, 6) { } |
| SCSS |
| list = engine.to_tree.children.first.list |
| assert_equal 1, list.source_range.start_pos.line |
| assert_equal 1, list.source_range.end_pos.line |
| assert_equal 16, list.source_range.start_pos.offset |
| assert_equal 38, list.source_range.end_pos.offset |
| end |
| |
| def test_sources_array_is_uri_escaped |
| map = Sass::Source::Map.new |
| importer = Sass::Importers::Filesystem.new('.') |
| map.add( |
| Sass::Source::Range.new( |
| Sass::Source::Position.new(0, 0), |
| Sass::Source::Position.new(0, 10), |
| 'source file.scss', |
| importer), |
| Sass::Source::Range.new( |
| Sass::Source::Position.new(0, 0), |
| Sass::Source::Position.new(0, 10), |
| nil, nil)) |
| |
| json = map.to_json(:css_path => 'output file.css', :sourcemap_path => 'output file.css.map') |
| assert_equal json, <<JSON.rstrip |
| { |
| "version": 3, |
| "mappings": "DADD,UAAU", |
| "sources": ["source%20file.scss"], |
| "names": [], |
| "file": "output%20file.css" |
| } |
| JSON |
| end |
| |
| def test_scss_comment_source_range |
| assert_parses_with_mapping <<SCSS, <<CSS, :syntax => :scss |
| $var: val; {{1}}/* text */{{/1}} |
| |
| {{2}}/* multiline |
| comment */{{/2}} |
| SCSS |
| {{1}}/* text */{{/1}} |
| {{2}}/* multiline |
| comment */{{/2}} |
| |
| /*# sourceMappingURL=test.css.map */ |
| CSS |
| end |
| |
| def test_sass_comment_source_range |
| assert_parses_with_mapping <<SASS, <<CSS, :syntax => :sass |
| {{1}}body{{/1}} |
| {{2}}/* text */{{/2}} |
| |
| {{3}}/* multiline |
| comment */{{/3}} |
| SASS |
| {{1}}body{{/1}} { |
| {{2}}/* text */{{/2}} } |
| |
| {{3}}/* multiline |
| * comment */{{/3}} |
| |
| /*# sourceMappingURL=test.css.map */ |
| CSS |
| end |
| |
| def test_scss_comment_interpolation_source_range |
| assert_parses_with_mapping <<SCSS, <<CSS, :syntax => :scss |
| $var: 2; {{1}}/* two \#{$var} and four \#{2 * $var} */{{/1}} |
| |
| {{2}}/* multiline |
| comment \#{ 2 + 2 } and \#{ 2 + |
| 2 } */{{/2}} |
| SCSS |
| {{1}}/* two 2 and four 4 */{{/1}} |
| {{2}}/* multiline |
| comment 4 and 4 */{{/2}} |
| |
| /*# sourceMappingURL=test.css.map */ |
| CSS |
| end |
| |
| def test_sass_comment_interpolation_source_range |
| assert_parses_with_mapping <<SASS, <<CSS, :syntax => :sass |
| $var: 2 |
| {{1}}/* two \#{$var} and four \#{2 * $var} */{{/1}} |
| |
| {{2}}/* multiline |
| comment \#{ 2 + 2 } and \#{ 2 + |
| 2 } */{{/2}} |
| SASS |
| {{1}}/* two 2 and four 4 */{{/1}} |
| {{2}}/* multiline |
| * comment 4 and 4 */{{/2}} |
| |
| /*# sourceMappingURL=test.css.map */ |
| CSS |
| end |
| |
| private |
| |
| ANNOTATION_REGEX = /\{\{(\/?)([^}]+)\}\}/ |
| |
| def build_ranges(text, file_name = nil) |
| ranges = Hash.new {|h, k| h[k] = []} |
| start_positions = {} |
| text.split("\n").each_with_index do |line_text, line| |
| line += 1 # lines shoud be 1-based |
| while (match = line_text.match(ANNOTATION_REGEX)) |
| closing = !match[1].empty? |
| name = match[2] |
| match_offsets = match.offset(0) |
| offset = match_offsets[0] + 1 # Offsets are 1-based in source maps. |
| assert(!closing || start_positions[name], "Closing annotation #{name} found before opening one.") |
| position = Sass::Source::Position.new(line, offset) |
| if closing |
| ranges[name] << Sass::Source::Range.new( |
| start_positions[name], position, file_name, |
| Sass::Importers::Filesystem.new('.')) |
| start_positions.delete name |
| else |
| assert(!start_positions[name], "Overlapping range annotation #{name} encountered on line #{line}") |
| start_positions[name] = position |
| end |
| line_text.slice!(match_offsets[0], match_offsets[1] - match_offsets[0]) |
| end |
| end |
| ranges |
| end |
| |
| def build_mapping_from_annotations(source, css, source_file_name) |
| source_ranges = build_ranges(source, source_file_name) |
| target_ranges = build_ranges(css) |
| map = Sass::Source::Map.new |
| source_ranges.map do |(name, sources)| |
| assert(sources.length == 1, "#{sources.length} source ranges encountered for annotation #{name}") |
| assert(target_ranges[name], "No target ranges for annotation #{name}") |
| target_ranges[name].map {|target_range| [sources.first, target_range]} |
| end. |
| flatten(1). |
| sort_by {|(_, target)| [target.start_pos.line, target.start_pos.offset]}. |
| each {|(s2, target)| map.add(s2, target)} |
| map |
| end |
| |
| def assert_parses_with_mapping(source, css, options={}) |
| options[:syntax] ||= :scss |
| input_filename = filename_for_test(options[:syntax]) |
| mapping = build_mapping_from_annotations(source, css, input_filename) |
| source.gsub!(ANNOTATION_REGEX, "") |
| css.gsub!(ANNOTATION_REGEX, "") |
| rendered, sourcemap = render_with_sourcemap(source, options) |
| assert_equal css.rstrip, rendered.rstrip |
| assert_sourcemaps_equal source, css, mapping, sourcemap |
| end |
| |
| def assert_positions_equal(expected, actual, lines, message = nil) |
| prefix = message ? message + ": " : "" |
| expected_location = lines[expected.line - 1] + "\n" + ("-" * (expected.offset - 1)) + "^" |
| actual_location = lines[actual.line - 1] + "\n" + ("-" * (actual.offset - 1)) + "^" |
| assert_equal(expected.line, actual.line, prefix + |
| "Expected #{expected.inspect}\n" + |
| expected_location + "\n\n" + |
| "But was #{actual.inspect}\n" + |
| actual_location) |
| assert_equal(expected.offset, actual.offset, prefix + |
| "Expected #{expected.inspect}\n" + |
| expected_location + "\n\n" + |
| "But was #{actual.inspect}\n" + |
| actual_location) |
| end |
| |
| def assert_ranges_equal(expected, actual, lines, prefix) |
| assert_positions_equal(expected.start_pos, actual.start_pos, lines, prefix + " start position") |
| assert_positions_equal(expected.end_pos, actual.end_pos, lines, prefix + " end position") |
| if expected.file.nil? |
| assert_nil(actual.file) |
| else |
| assert_equal(expected.file, actual.file) |
| end |
| end |
| |
| def assert_sourcemaps_equal(source, css, expected, actual) |
| assert_equal(expected.data.length, actual.data.length, <<MESSAGE) |
| Wrong number of mappings. Expected: |
| #{dump_sourcemap_as_expectation(source, css, expected).gsub(/^/, '| ')} |
| |
| Actual: |
| #{dump_sourcemap_as_expectation(source, css, actual).gsub(/^/, '| ')} |
| MESSAGE |
| source_lines = source.split("\n") |
| css_lines = css.split("\n") |
| expected.data.zip(actual.data) do |expected_mapping, actual_mapping| |
| assert_ranges_equal(expected_mapping.input, actual_mapping.input, source_lines, "Input") |
| assert_ranges_equal(expected_mapping.output, actual_mapping.output, css_lines, "Output") |
| end |
| end |
| |
| def assert_parses_with_sourcemap(source, css, sourcemap_json, options={}) |
| rendered, sourcemap = render_with_sourcemap(source, options) |
| css_path = options[:output] || "test.css" |
| sourcemap_path = Sass::Util.sourcemap_name(css_path) |
| rendered_json = sourcemap.to_json(:css_path => css_path, :sourcemap_path => sourcemap_path, :type => options[:sourcemap]) |
| |
| assert_equal css.rstrip, rendered.rstrip |
| assert_equal sourcemap_json.rstrip, rendered_json |
| end |
| |
| def render_with_sourcemap(source, options={}) |
| options[:syntax] ||= :scss |
| munge_filename options |
| engine = Sass::Engine.new(source, options) |
| engine.options[:cache] = false |
| sourcemap_path = Sass::Util.sourcemap_name(options[:output] || "test.css") |
| engine.render_with_sourcemap File.basename(sourcemap_path) |
| end |
| |
| def dump_sourcemap_as_expectation(source, css, sourcemap) |
| mappings_to_annotations(source, sourcemap.data.map {|d| d.input}) + "\n\n" + |
| "=" * 20 + " maps to:\n\n" + |
| mappings_to_annotations(css, sourcemap.data.map {|d| d.output}) |
| end |
| |
| def mappings_to_annotations(source, ranges) |
| additional_offsets = Hash.new(0) |
| lines = source.split("\n") |
| |
| add_annotation = lambda do |pos, str| |
| line_num = pos.line - 1 |
| line = lines[line_num] |
| offset = pos.offset + additional_offsets[line_num] - 1 |
| line << " " * (offset - line.length) if offset > line.length |
| line.insert(offset, str) |
| additional_offsets[line_num] += str.length |
| end |
| |
| ranges.each_with_index do |range, i| |
| add_annotation[range.start_pos, "{{#{i + 1}}}"] |
| add_annotation[range.end_pos, "{{/#{i + 1}}}"] |
| end |
| |
| return lines.join("\n") |
| end |
| end |