sass 3.7.4 → 4.0.0.alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (257) hide show
  1. checksums.yaml +13 -5
  2. data/.yardopts +1 -1
  3. data/CODE_OF_CONDUCT.md +1 -1
  4. data/CONTRIBUTING.md +1 -146
  5. data/MIT-LICENSE +1 -1
  6. data/README.md +25 -39
  7. data/Rakefile +274 -0
  8. data/VERSION +1 -1
  9. data/VERSION_DATE +1 -1
  10. data/lib/sass.rb +3 -3
  11. data/lib/sass/cache_stores/filesystem.rb +2 -2
  12. data/lib/sass/cache_stores/memory.rb +5 -4
  13. data/lib/sass/callbacks.rb +2 -2
  14. data/lib/sass/css.rb +12 -12
  15. data/lib/sass/engine.rb +44 -62
  16. data/lib/sass/environment.rb +7 -35
  17. data/lib/sass/error.rb +14 -14
  18. data/lib/sass/exec/base.rb +14 -3
  19. data/lib/sass/exec/sass_convert.rb +6 -20
  20. data/lib/sass/exec/sass_scss.rb +29 -5
  21. data/lib/sass/features.rb +2 -3
  22. data/lib/sass/importers/filesystem.rb +6 -11
  23. data/lib/sass/logger.rb +3 -8
  24. data/lib/sass/logger/base.rb +2 -19
  25. data/lib/sass/plugin.rb +2 -3
  26. data/lib/sass/plugin/compiler.rb +67 -48
  27. data/lib/sass/plugin/configuration.rb +3 -3
  28. data/lib/sass/plugin/merb.rb +1 -1
  29. data/lib/sass/plugin/rack.rb +3 -3
  30. data/lib/sass/plugin/staleness_checker.rb +3 -3
  31. data/lib/sass/railtie.rb +1 -1
  32. data/lib/sass/script.rb +3 -3
  33. data/lib/sass/script/css_parser.rb +15 -5
  34. data/lib/sass/script/functions.rb +121 -337
  35. data/lib/sass/script/lexer.rb +36 -102
  36. data/lib/sass/script/parser.rb +153 -529
  37. data/lib/sass/script/tree/funcall.rb +34 -42
  38. data/lib/sass/script/tree/interpolation.rb +26 -171
  39. data/lib/sass/script/tree/list_literal.rb +8 -23
  40. data/lib/sass/script/tree/map_literal.rb +2 -2
  41. data/lib/sass/script/tree/node.rb +3 -3
  42. data/lib/sass/script/tree/operation.rb +16 -43
  43. data/lib/sass/script/tree/string_interpolation.rb +43 -64
  44. data/lib/sass/script/tree/variable.rb +1 -1
  45. data/lib/sass/script/value.rb +0 -2
  46. data/lib/sass/script/value/arg_list.rb +1 -1
  47. data/lib/sass/script/value/base.rb +9 -27
  48. data/lib/sass/script/value/color.rb +18 -26
  49. data/lib/sass/script/value/helpers.rb +18 -44
  50. data/lib/sass/script/value/list.rb +14 -35
  51. data/lib/sass/script/value/map.rb +2 -2
  52. data/lib/sass/script/value/number.rb +16 -26
  53. data/lib/sass/script/value/string.rb +1 -30
  54. data/lib/sass/scss.rb +2 -0
  55. data/lib/sass/scss/css_parser.rb +3 -7
  56. data/lib/sass/scss/parser.rb +78 -196
  57. data/lib/sass/scss/rx.rb +14 -7
  58. data/lib/sass/scss/script_lexer.rb +15 -0
  59. data/lib/sass/scss/script_parser.rb +25 -0
  60. data/lib/sass/scss/static_parser.rb +55 -38
  61. data/lib/sass/selector.rb +10 -7
  62. data/lib/sass/selector/abstract_sequence.rb +12 -15
  63. data/lib/sass/selector/comma_sequence.rb +6 -24
  64. data/lib/sass/selector/pseudo.rb +6 -19
  65. data/lib/sass/selector/sequence.rb +16 -14
  66. data/lib/sass/selector/simple.rb +7 -9
  67. data/lib/sass/selector/simple_sequence.rb +12 -16
  68. data/lib/sass/shared.rb +1 -1
  69. data/lib/sass/source/map.rb +9 -7
  70. data/lib/sass/source/position.rb +4 -4
  71. data/lib/sass/stack.rb +3 -23
  72. data/lib/sass/tree/charset_node.rb +1 -1
  73. data/lib/sass/tree/comment_node.rb +1 -1
  74. data/lib/sass/tree/function_node.rb +3 -2
  75. data/lib/sass/tree/node.rb +3 -5
  76. data/lib/sass/tree/prop_node.rb +58 -49
  77. data/lib/sass/tree/rule_node.rb +8 -15
  78. data/lib/sass/tree/visitors/check_nesting.rb +23 -19
  79. data/lib/sass/tree/visitors/convert.rb +13 -15
  80. data/lib/sass/tree/visitors/cssize.rb +15 -4
  81. data/lib/sass/tree/visitors/deep_copy.rb +2 -2
  82. data/lib/sass/tree/visitors/extend.rb +14 -10
  83. data/lib/sass/tree/visitors/perform.rb +18 -29
  84. data/lib/sass/tree/visitors/set_options.rb +2 -2
  85. data/lib/sass/tree/visitors/to_css.rb +47 -77
  86. data/lib/sass/util.rb +311 -98
  87. data/lib/sass/util/cross_platform_random.rb +19 -0
  88. data/lib/sass/util/multibyte_string_scanner.rb +133 -127
  89. data/lib/sass/util/normalized_map.rb +8 -1
  90. data/lib/sass/util/ordered_hash.rb +192 -0
  91. data/lib/sass/version.rb +6 -2
  92. data/test/sass/cache_test.rb +131 -0
  93. data/test/sass/callbacks_test.rb +61 -0
  94. data/test/sass/compiler_test.rb +236 -0
  95. data/test/sass/conversion_test.rb +2171 -0
  96. data/test/sass/css2sass_test.rb +526 -0
  97. data/test/sass/data/hsl-rgb.txt +319 -0
  98. data/test/sass/encoding_test.rb +219 -0
  99. data/test/sass/engine_test.rb +3400 -0
  100. data/test/sass/exec_test.rb +86 -0
  101. data/test/sass/extend_test.rb +1719 -0
  102. data/test/sass/fixtures/test_staleness_check_across_importers.css +1 -0
  103. data/test/sass/fixtures/test_staleness_check_across_importers.scss +1 -0
  104. data/test/sass/functions_test.rb +1984 -0
  105. data/test/sass/importer_test.rb +421 -0
  106. data/test/sass/logger_test.rb +58 -0
  107. data/test/sass/mock_importer.rb +49 -0
  108. data/test/sass/more_results/more1.css +9 -0
  109. data/test/sass/more_results/more1_with_line_comments.css +26 -0
  110. data/test/sass/more_results/more_import.css +29 -0
  111. data/test/sass/more_templates/_more_partial.sass +2 -0
  112. data/test/sass/more_templates/more1.sass +23 -0
  113. data/test/sass/more_templates/more_import.sass +11 -0
  114. data/test/sass/plugin_test.rb +556 -0
  115. data/test/sass/results/alt.css +4 -0
  116. data/test/sass/results/basic.css +9 -0
  117. data/test/sass/results/cached_import_option.css +3 -0
  118. data/test/sass/results/compact.css +5 -0
  119. data/test/sass/results/complex.css +86 -0
  120. data/test/sass/results/compressed.css +1 -0
  121. data/test/sass/results/expanded.css +19 -0
  122. data/test/sass/results/filename_fn.css +3 -0
  123. data/test/sass/results/if.css +3 -0
  124. data/test/sass/results/import.css +31 -0
  125. data/test/sass/results/import_charset.css +5 -0
  126. data/test/sass/results/import_charset_1_8.css +5 -0
  127. data/test/sass/results/import_charset_ibm866.css +5 -0
  128. data/test/sass/results/import_content.css +1 -0
  129. data/test/sass/results/line_numbers.css +49 -0
  130. data/test/sass/results/mixins.css +95 -0
  131. data/test/sass/results/multiline.css +24 -0
  132. data/test/sass/results/nested.css +22 -0
  133. data/test/sass/results/options.css +1 -0
  134. data/test/sass/results/parent_ref.css +13 -0
  135. data/test/sass/results/script.css +16 -0
  136. data/test/sass/results/scss_import.css +31 -0
  137. data/test/sass/results/scss_importee.css +2 -0
  138. data/test/sass/results/subdir/nested_subdir/nested_subdir.css +1 -0
  139. data/test/sass/results/subdir/subdir.css +3 -0
  140. data/test/sass/results/units.css +11 -0
  141. data/test/sass/results/warn.css +0 -0
  142. data/test/sass/results/warn_imported.css +0 -0
  143. data/test/sass/script_conversion_test.rb +306 -0
  144. data/test/sass/script_test.rb +1206 -0
  145. data/test/sass/scss/css_test.rb +1281 -0
  146. data/test/sass/scss/rx_test.rb +160 -0
  147. data/test/sass/scss/scss_test.rb +4147 -0
  148. data/test/sass/scss/test_helper.rb +37 -0
  149. data/test/sass/source_map_test.rb +1055 -0
  150. data/test/sass/superselector_test.rb +210 -0
  151. data/test/sass/templates/_cached_import_option_partial.scss +1 -0
  152. data/test/sass/templates/_double_import_loop2.sass +1 -0
  153. data/test/sass/templates/_filename_fn_import.scss +11 -0
  154. data/test/sass/templates/_imported_charset_ibm866.sass +4 -0
  155. data/test/sass/templates/_imported_charset_utf8.sass +4 -0
  156. data/test/sass/templates/_imported_content.sass +3 -0
  157. data/test/sass/templates/_partial.sass +2 -0
  158. data/test/sass/templates/_same_name_different_partiality.scss +1 -0
  159. data/test/sass/templates/alt.sass +16 -0
  160. data/test/sass/templates/basic.sass +23 -0
  161. data/test/sass/templates/bork1.sass +2 -0
  162. data/test/sass/templates/bork2.sass +2 -0
  163. data/test/sass/templates/bork3.sass +2 -0
  164. data/test/sass/templates/bork4.sass +2 -0
  165. data/test/sass/templates/bork5.sass +3 -0
  166. data/test/sass/templates/cached_import_option.scss +3 -0
  167. data/test/sass/templates/compact.sass +17 -0
  168. data/test/sass/templates/complex.sass +305 -0
  169. data/test/sass/templates/compressed.sass +15 -0
  170. data/test/sass/templates/double_import_loop1.sass +1 -0
  171. data/test/sass/templates/expanded.sass +17 -0
  172. data/test/sass/templates/filename_fn.scss +18 -0
  173. data/test/sass/templates/if.sass +11 -0
  174. data/test/sass/templates/import.sass +12 -0
  175. data/test/sass/templates/import_charset.sass +9 -0
  176. data/test/sass/templates/import_charset_1_8.sass +6 -0
  177. data/test/sass/templates/import_charset_ibm866.sass +11 -0
  178. data/test/sass/templates/import_content.sass +4 -0
  179. data/test/sass/templates/importee.less +2 -0
  180. data/test/sass/templates/importee.sass +19 -0
  181. data/test/sass/templates/line_numbers.sass +13 -0
  182. data/test/sass/templates/mixin_bork.sass +5 -0
  183. data/test/sass/templates/mixins.sass +76 -0
  184. data/test/sass/templates/multiline.sass +20 -0
  185. data/test/sass/templates/nested.sass +25 -0
  186. data/test/sass/templates/nested_bork1.sass +2 -0
  187. data/test/sass/templates/nested_bork2.sass +2 -0
  188. data/test/sass/templates/nested_bork3.sass +2 -0
  189. data/test/sass/templates/nested_bork4.sass +2 -0
  190. data/test/sass/templates/nested_import.sass +2 -0
  191. data/test/sass/templates/nested_mixin_bork.sass +6 -0
  192. data/test/sass/templates/options.sass +2 -0
  193. data/test/sass/templates/parent_ref.sass +25 -0
  194. data/test/sass/templates/same_name_different_ext.sass +2 -0
  195. data/test/sass/templates/same_name_different_ext.scss +1 -0
  196. data/test/sass/templates/same_name_different_partiality.scss +1 -0
  197. data/test/sass/templates/script.sass +101 -0
  198. data/test/sass/templates/scss_import.scss +12 -0
  199. data/test/sass/templates/scss_importee.scss +1 -0
  200. data/test/sass/templates/single_import_loop.sass +1 -0
  201. data/test/sass/templates/subdir/import_up1.scss +1 -0
  202. data/test/sass/templates/subdir/import_up2.scss +1 -0
  203. data/test/sass/templates/subdir/nested_subdir/_nested_partial.sass +2 -0
  204. data/test/sass/templates/subdir/nested_subdir/nested_subdir.sass +3 -0
  205. data/test/sass/templates/subdir/subdir.sass +6 -0
  206. data/test/sass/templates/units.sass +11 -0
  207. data/test/sass/templates/warn.sass +3 -0
  208. data/test/sass/templates/warn_imported.sass +4 -0
  209. data/test/sass/test_helper.rb +8 -0
  210. data/test/sass/util/multibyte_string_scanner_test.rb +147 -0
  211. data/test/sass/util/normalized_map_test.rb +51 -0
  212. data/test/sass/util/subset_map_test.rb +91 -0
  213. data/test/sass/util_test.rb +438 -0
  214. data/test/sass/value_helpers_test.rb +179 -0
  215. data/test/test_helper.rb +110 -0
  216. data/vendor/listen/CHANGELOG.md +1 -0
  217. data/vendor/listen/CONTRIBUTING.md +38 -0
  218. data/vendor/listen/Gemfile +20 -0
  219. data/vendor/listen/Guardfile +8 -0
  220. data/vendor/listen/LICENSE +20 -0
  221. data/vendor/listen/README.md +349 -0
  222. data/vendor/listen/Rakefile +5 -0
  223. data/vendor/listen/Vagrantfile +96 -0
  224. data/vendor/listen/lib/listen.rb +54 -0
  225. data/vendor/listen/lib/listen/adapter.rb +327 -0
  226. data/vendor/listen/lib/listen/adapters/bsd.rb +75 -0
  227. data/vendor/listen/lib/listen/adapters/darwin.rb +48 -0
  228. data/vendor/listen/lib/listen/adapters/linux.rb +81 -0
  229. data/vendor/listen/lib/listen/adapters/polling.rb +58 -0
  230. data/vendor/listen/lib/listen/adapters/windows.rb +91 -0
  231. data/vendor/listen/lib/listen/directory_record.rb +406 -0
  232. data/vendor/listen/lib/listen/listener.rb +323 -0
  233. data/vendor/listen/lib/listen/turnstile.rb +32 -0
  234. data/vendor/listen/lib/listen/version.rb +3 -0
  235. data/vendor/listen/listen.gemspec +28 -0
  236. data/vendor/listen/spec/listen/adapter_spec.rb +149 -0
  237. data/vendor/listen/spec/listen/adapters/bsd_spec.rb +36 -0
  238. data/vendor/listen/spec/listen/adapters/darwin_spec.rb +37 -0
  239. data/vendor/listen/spec/listen/adapters/linux_spec.rb +47 -0
  240. data/vendor/listen/spec/listen/adapters/polling_spec.rb +68 -0
  241. data/vendor/listen/spec/listen/adapters/windows_spec.rb +30 -0
  242. data/vendor/listen/spec/listen/directory_record_spec.rb +1250 -0
  243. data/vendor/listen/spec/listen/listener_spec.rb +258 -0
  244. data/vendor/listen/spec/listen/turnstile_spec.rb +56 -0
  245. data/vendor/listen/spec/listen_spec.rb +67 -0
  246. data/vendor/listen/spec/spec_helper.rb +25 -0
  247. data/vendor/listen/spec/support/adapter_helper.rb +666 -0
  248. data/vendor/listen/spec/support/directory_record_helper.rb +57 -0
  249. data/vendor/listen/spec/support/fixtures_helper.rb +29 -0
  250. data/vendor/listen/spec/support/listeners_helper.rb +179 -0
  251. data/vendor/listen/spec/support/platform_helper.rb +15 -0
  252. metadata +217 -76
  253. data/extra/sass-spec-ref.sh +0 -40
  254. data/lib/sass/deprecation.rb +0 -55
  255. data/lib/sass/logger/delayed.rb +0 -50
  256. data/lib/sass/script/value/callable.rb +0 -25
  257. data/lib/sass/script/value/function.rb +0 -19
@@ -0,0 +1,86 @@
1
+ #!/usr/bin/env ruby
2
+ require File.dirname(__FILE__) + '/../test_helper'
3
+ require 'sass/util/test'
4
+ require 'tmpdir'
5
+
6
+ class ExecTest < MiniTest::Test
7
+ include Sass::Util::Test
8
+
9
+ def setup
10
+ @dir = Dir.mktmpdir
11
+ end
12
+
13
+ def teardown
14
+ FileUtils.rm_rf(@dir)
15
+ clean_up_sassc
16
+ end
17
+
18
+ def test_scss_t_expanded
19
+ src = get_path("src.scss")
20
+ dest = get_path("dest.css")
21
+ write(src, ".ruleset { margin: 0 }")
22
+ assert(exec(*%w[scss --sourcemap=none -t expanded --unix-newlines].push(src, dest)))
23
+ assert_equal(".ruleset {\n margin: 0;\n}\n", read(dest))
24
+ end
25
+
26
+ def test_sass_convert_T_sass
27
+ src = get_path("src.scss")
28
+ dest = get_path("dest.css")
29
+ write(src, ".ruleset { margin: 0 }")
30
+ assert(exec(*%w[sass-convert -T sass --unix-newlines].push(src, dest)))
31
+ assert_equal(".ruleset\n margin: 0\n", read(dest))
32
+ end
33
+
34
+ def test_sass_convert_T_sass_in_place
35
+ src = get_path("src.scss")
36
+ write(src, ".ruleset { margin: 0 }")
37
+ assert(exec(*%w[sass-convert -T sass --in-place --unix-newlines].push(src)))
38
+ assert_equal(".ruleset\n margin: 0\n", read(src))
39
+ end
40
+
41
+ def test_scss_t_expanded_no_unix_newlines
42
+ return skip "Can be run on Windows only" unless Sass::Util.windows?
43
+ src = get_path("src.scss")
44
+ dest = get_path("dest.css")
45
+ write(src, ".ruleset { margin: 0 }")
46
+ assert(exec(*%w[scss -t expanded].push(src, dest)))
47
+ assert_equal(".ruleset {\r\n margin: 0;\r\n}\r\n", read(dest))
48
+ end
49
+
50
+ def test_sass_convert_T_sass_no_unix_newlines
51
+ return skip "Can be run on Windows only" unless Sass::Util.windows?
52
+ src = get_path("src.scss")
53
+ dest = get_path("dest.sass")
54
+ write(src, ".ruleset { margin: 0 }")
55
+ assert(exec(*%w[sass-convert -T sass].push(src, dest)))
56
+ assert_equal(".ruleset\r\n margin: 0\r\n", read(dest))
57
+ end
58
+
59
+ def test_sass_convert_T_sass_in_place_no_unix_newlines
60
+ return skip "Can be run on Windows only" unless Sass::Util.windows?
61
+ src = get_path("src.scss")
62
+ write(src, ".ruleset { margin: 0 }")
63
+ assert(exec(*%w[sass-convert -T sass --in-place].push(src)))
64
+ assert_equal(".ruleset\r\n margin: 0\r\n", read(src))
65
+ end
66
+
67
+ private
68
+
69
+ def get_path(name)
70
+ File.join(@dir, name)
71
+ end
72
+
73
+ def read(file)
74
+ open(file, 'rb') {|f| f.read}
75
+ end
76
+
77
+ def write(file, content)
78
+ open(file, 'wb') {|f| f.write(content)}
79
+ end
80
+
81
+ def exec(script, *args)
82
+ script = File.dirname(__FILE__) + '/../../bin/' + script
83
+ ruby = File.join(RbConfig::CONFIG['bindir'], RbConfig::CONFIG['ruby_install_name'] + RbConfig::CONFIG['EXEEXT'])
84
+ system(ruby, script, *args)
85
+ end
86
+ end
@@ -0,0 +1,1719 @@
1
+ #!/usr/bin/env ruby
2
+ require File.dirname(__FILE__) + '/../test_helper'
3
+
4
+ class ExtendTest < MiniTest::Test
5
+ def test_basic
6
+ assert_equal <<CSS, render(<<SCSS)
7
+ .foo, .bar {
8
+ a: b; }
9
+ CSS
10
+ .foo {a: b}
11
+ .bar {@extend .foo}
12
+ SCSS
13
+
14
+ assert_equal <<CSS, render(<<SCSS)
15
+ .foo, .bar {
16
+ a: b; }
17
+ CSS
18
+ .bar {@extend .foo}
19
+ .foo {a: b}
20
+ SCSS
21
+
22
+ assert_equal <<CSS, render(<<SCSS)
23
+ .foo, .bar {
24
+ a: b; }
25
+
26
+ .bar {
27
+ c: d; }
28
+ CSS
29
+ .foo {a: b}
30
+ .bar {c: d; @extend .foo}
31
+ SCSS
32
+
33
+ assert_equal <<CSS, render(<<SCSS)
34
+ .foo, .bar {
35
+ a: b; }
36
+
37
+ .bar {
38
+ c: d; }
39
+ CSS
40
+ .foo {a: b}
41
+ .bar {@extend .foo; c: d}
42
+ SCSS
43
+ end
44
+
45
+ def test_indented_syntax
46
+ assert_equal <<CSS, render(<<SASS, :syntax => :sass)
47
+ .foo, .bar {
48
+ a: b; }
49
+ CSS
50
+ .foo
51
+ a: b
52
+ .bar
53
+ @extend .foo
54
+ SASS
55
+
56
+ assert_equal <<CSS, render(<<SASS, :syntax => :sass)
57
+ .foo, .bar {
58
+ a: b; }
59
+ CSS
60
+ .foo
61
+ a: b
62
+ .bar
63
+ @extend \#{".foo"}
64
+ SASS
65
+ end
66
+
67
+ def test_multiple_targets
68
+ assert_equal <<CSS, render(<<SCSS)
69
+ .foo, .bar {
70
+ a: b; }
71
+
72
+ .blip .foo, .blip .bar {
73
+ c: d; }
74
+ CSS
75
+ .foo {a: b}
76
+ .bar {@extend .foo}
77
+ .blip .foo {c: d}
78
+ SCSS
79
+ end
80
+
81
+ def test_multiple_extendees
82
+ assert_equal <<CSS, render(<<SCSS)
83
+ .foo, .baz {
84
+ a: b; }
85
+
86
+ .bar, .baz {
87
+ c: d; }
88
+ CSS
89
+ .foo {a: b}
90
+ .bar {c: d}
91
+ .baz {@extend .foo; @extend .bar}
92
+ SCSS
93
+ end
94
+
95
+ def test_multiple_extends_with_single_extender_and_single_target
96
+ assert_extends('.foo .bar', '.baz {@extend .foo; @extend .bar}',
97
+ '.foo .bar, .baz .bar, .foo .baz, .baz .baz')
98
+ assert_extends '.foo.bar', '.baz {@extend .foo; @extend .bar}', '.foo.bar, .baz'
99
+ end
100
+
101
+ def test_multiple_extends_with_multiple_extenders_and_single_target
102
+ assert_equal <<CSS, render(<<SCSS)
103
+ .foo .bar, .baz .bar, .foo .bang, .baz .bang {
104
+ a: b; }
105
+ CSS
106
+ .foo .bar {a: b}
107
+ .baz {@extend .foo}
108
+ .bang {@extend .bar}
109
+ SCSS
110
+
111
+ assert_equal <<CSS, render(<<SCSS)
112
+ .foo.bar, .bar.baz, .baz.bang, .foo.bang {
113
+ a: b; }
114
+ CSS
115
+ .foo.bar {a: b}
116
+ .baz {@extend .foo}
117
+ .bang {@extend .bar}
118
+ SCSS
119
+ end
120
+
121
+ def test_chained_extends
122
+ assert_equal <<CSS, render(<<SCSS)
123
+ .foo, .bar, .baz, .bip {
124
+ a: b; }
125
+ CSS
126
+ .foo {a: b}
127
+ .bar {@extend .foo}
128
+ .baz {@extend .bar}
129
+ .bip {@extend .bar}
130
+ SCSS
131
+ end
132
+
133
+ def test_dynamic_extendee
134
+ assert_extends '.foo', '.bar {@extend #{".foo"}}', '.foo, .bar'
135
+ assert_extends('[baz^="blip12px"]', '.bar {@extend [baz^="blip#{12px}"]}',
136
+ '[baz^="blip12px"], .bar')
137
+ end
138
+
139
+ def test_nested_target
140
+ assert_extends '.foo .bar', '.baz {@extend .bar}', '.foo .bar, .foo .baz'
141
+ end
142
+
143
+ def test_target_with_child
144
+ assert_extends '.foo .bar', '.baz {@extend .foo}', '.foo .bar, .baz .bar'
145
+ end
146
+
147
+ def test_class_unification
148
+ assert_unification '.foo.bar', '.baz {@extend .foo}', '.foo.bar, .bar.baz'
149
+ assert_unification '.foo.baz', '.baz {@extend .foo}', '.baz'
150
+ end
151
+
152
+ def test_id_unification
153
+ assert_unification '.foo.bar', '#baz {@extend .foo}', '.foo.bar, .bar#baz'
154
+ assert_unification '.foo#baz', '#baz {@extend .foo}', '#baz'
155
+
156
+ assert_extend_doesnt_match('#bar', '.foo', :failed_to_unify, 2) do
157
+ render_unification '.foo#baz', '#bar {@extend .foo}'
158
+ end
159
+ end
160
+
161
+ def test_universal_unification_with_simple_target
162
+ assert_unification '.foo', '* {@extend .foo}', '.foo, *'
163
+ assert_unification '.foo', '*|* {@extend .foo}', '.foo, *|*'
164
+ assert_unification '.foo.bar', '* {@extend .foo}', '.bar'
165
+ assert_unification '.foo.bar', '*|* {@extend .foo}', '.bar'
166
+ assert_unification '.foo.bar', 'ns|* {@extend .foo}', '.foo.bar, ns|*.bar'
167
+ end
168
+
169
+ def test_universal_unification_with_namespaceless_universal_target
170
+ assert_unification '*.foo', '* {@extend .foo}', '*'
171
+ assert_unification '*.foo', '*|* {@extend .foo}', '*'
172
+ assert_unification '*|*.foo', '* {@extend .foo}', '*|*.foo, *'
173
+ assert_unification '*|*.foo', '*|* {@extend .foo}', '*|*'
174
+ assert_unification '*.foo', 'ns|* {@extend .foo}', '*.foo, ns|*'
175
+ assert_unification '*|*.foo', 'ns|* {@extend .foo}', '*|*.foo, ns|*'
176
+ end
177
+
178
+ def test_universal_unification_with_namespaced_universal_target
179
+ assert_unification 'ns|*.foo', '* {@extend .foo}', 'ns|*'
180
+ assert_unification 'ns|*.foo', '*|* {@extend .foo}', 'ns|*'
181
+
182
+ assert_extend_doesnt_match('ns2|*', '.foo', :failed_to_unify, 2) do
183
+ render_unification 'ns1|*.foo', 'ns2|* {@extend .foo}'
184
+ end
185
+
186
+ assert_unification 'ns|*.foo', 'ns|* {@extend .foo}', 'ns|*'
187
+ end
188
+
189
+ def test_universal_unification_with_namespaceless_element_target
190
+ assert_unification 'a.foo', '* {@extend .foo}', 'a'
191
+ assert_unification 'a.foo', '*|* {@extend .foo}', 'a'
192
+ assert_unification '*|a.foo', '* {@extend .foo}', '*|a.foo, a'
193
+ assert_unification '*|a.foo', '*|* {@extend .foo}', '*|a'
194
+ assert_unification 'a.foo', 'ns|* {@extend .foo}', 'a.foo, ns|a'
195
+ assert_unification '*|a.foo', 'ns|* {@extend .foo}', '*|a.foo, ns|a'
196
+ end
197
+
198
+ def test_universal_unification_with_namespaced_element_target
199
+ assert_unification 'ns|a.foo', '* {@extend .foo}', 'ns|a'
200
+ assert_unification 'ns|a.foo', '*|* {@extend .foo}', 'ns|a'
201
+
202
+ assert_extend_doesnt_match('ns2|*', '.foo', :failed_to_unify, 2) do
203
+ render_unification 'ns1|a.foo', 'ns2|* {@extend .foo}'
204
+ end
205
+
206
+ assert_unification 'ns|a.foo', 'ns|* {@extend .foo}', 'ns|a'
207
+ end
208
+
209
+ def test_element_unification_with_simple_target
210
+ assert_unification '.foo', 'a {@extend .foo}', '.foo, a'
211
+ assert_unification '.foo.bar', 'a {@extend .foo}', '.foo.bar, a.bar'
212
+ assert_unification '.foo.bar', '*|a {@extend .foo}', '.foo.bar, *|a.bar'
213
+ assert_unification '.foo.bar', 'ns|a {@extend .foo}', '.foo.bar, ns|a.bar'
214
+ end
215
+
216
+ def test_element_unification_with_namespaceless_universal_target
217
+ assert_unification '*.foo', 'a {@extend .foo}', '*.foo, a'
218
+ assert_unification '*.foo', '*|a {@extend .foo}', '*.foo, a'
219
+ assert_unification '*|*.foo', 'a {@extend .foo}', '*|*.foo, a'
220
+ assert_unification '*|*.foo', '*|a {@extend .foo}', '*|*.foo, *|a'
221
+ assert_unification '*.foo', 'ns|a {@extend .foo}', '*.foo, ns|a'
222
+ assert_unification '*|*.foo', 'ns|a {@extend .foo}', '*|*.foo, ns|a'
223
+ end
224
+
225
+ def test_element_unification_with_namespaced_universal_target
226
+ assert_unification 'ns|*.foo', 'a {@extend .foo}', 'ns|*.foo, ns|a'
227
+ assert_unification 'ns|*.foo', '*|a {@extend .foo}', 'ns|*.foo, ns|a'
228
+
229
+ assert_extend_doesnt_match('ns2|a', '.foo', :failed_to_unify, 2) do
230
+ render_unification 'ns1|*.foo', 'ns2|a {@extend .foo}'
231
+ end
232
+
233
+ assert_unification 'ns|*.foo', 'ns|a {@extend .foo}', 'ns|*.foo, ns|a'
234
+ end
235
+
236
+ def test_element_unification_with_namespaceless_element_target
237
+ assert_unification 'a.foo', 'a {@extend .foo}', 'a'
238
+ assert_unification 'a.foo', '*|a {@extend .foo}', 'a'
239
+ assert_unification '*|a.foo', 'a {@extend .foo}', '*|a.foo, a'
240
+ assert_unification '*|a.foo', '*|a {@extend .foo}', '*|a'
241
+ assert_unification 'a.foo', 'ns|a {@extend .foo}', 'a.foo, ns|a'
242
+ assert_unification '*|a.foo', 'ns|a {@extend .foo}', '*|a.foo, ns|a'
243
+
244
+ assert_extend_doesnt_match('h1', '.foo', :failed_to_unify, 2) do
245
+ render_unification 'a.foo', 'h1 {@extend .foo}'
246
+ end
247
+ end
248
+
249
+ def test_element_unification_with_namespaced_element_target
250
+ assert_unification 'ns|a.foo', 'a {@extend .foo}', 'ns|a'
251
+ assert_unification 'ns|a.foo', '*|a {@extend .foo}', 'ns|a'
252
+
253
+ assert_extend_doesnt_match('ns2|a', '.foo', :failed_to_unify, 2) do
254
+ render_unification 'ns1|a.foo', 'ns2|a {@extend .foo}'
255
+ end
256
+
257
+ assert_unification 'ns|a.foo', 'ns|a {@extend .foo}', 'ns|a'
258
+ end
259
+
260
+ def test_attribute_unification
261
+ assert_unification '[foo=bar].baz', '[foo=baz] {@extend .baz}', '[foo=bar].baz, [foo=bar][foo=baz]'
262
+ assert_unification '[foo=bar].baz', '[foo^=bar] {@extend .baz}', '[foo=bar].baz, [foo=bar][foo^=bar]'
263
+ assert_unification '[foo=bar].baz', '[foot=bar] {@extend .baz}', '[foo=bar].baz, [foo=bar][foot=bar]'
264
+ assert_unification '[foo=bar].baz', '[ns|foo=bar] {@extend .baz}', '[foo=bar].baz, [foo=bar][ns|foo=bar]'
265
+ assert_unification '%-a [foo=bar].bar', '[foo=bar] {@extend .bar}', '-a [foo=bar]'
266
+ end
267
+
268
+ def test_pseudo_unification
269
+ assert_unification ':foo.baz', ':foo(2n+1) {@extend .baz}', ':foo.baz, :foo:foo(2n+1)'
270
+ assert_unification ':foo.baz', '::foo {@extend .baz}', ':foo.baz, :foo::foo'
271
+
272
+ assert_extend_doesnt_match('::bar', '.baz', :failed_to_unify, 2) do
273
+ render_unification '::foo.baz', '::bar {@extend .baz}'
274
+ end
275
+
276
+ assert_extend_doesnt_match('::foo(2n+1)', '.baz', :failed_to_unify, 2) do
277
+ render_unification '::foo.baz', '::foo(2n+1) {@extend .baz}'
278
+ end
279
+
280
+ assert_unification '::foo.baz', '::foo {@extend .baz}', '::foo'
281
+ assert_unification '::foo(2n+1).baz', '::foo(2n+1) {@extend .baz}', '::foo(2n+1)'
282
+ assert_unification ':foo.baz', ':bar {@extend .baz}', ':foo.baz, :foo:bar'
283
+ assert_unification '.baz:foo', ':after {@extend .baz}', '.baz:foo, :foo:after'
284
+ assert_unification '.baz:after', ':foo {@extend .baz}', '.baz:after, :foo:after'
285
+ assert_unification ':foo.baz', ':foo {@extend .baz}', ':foo'
286
+ end
287
+
288
+ def test_pseudoelement_remains_at_end_of_selector
289
+ assert_extends '.foo::bar', '.baz {@extend .foo}', '.foo::bar, .baz::bar'
290
+ assert_extends 'a.foo::bar', '.baz {@extend .foo}', 'a.foo::bar, a.baz::bar'
291
+ end
292
+
293
+ def test_pseudoclass_remains_at_end_of_selector
294
+ assert_extends '.foo:bar', '.baz {@extend .foo}', '.foo:bar, .baz:bar'
295
+ assert_extends 'a.foo:bar', '.baz {@extend .foo}', 'a.foo:bar, a.baz:bar'
296
+ end
297
+
298
+ def test_id_unification
299
+ assert_unification('#id.foo .bar', '#id.baz .qux {@extend .bar}',
300
+ '#id.foo .bar, #id.baz.foo .qux')
301
+ end
302
+
303
+ def test_root_unification
304
+ assert_extends(
305
+ ".foo:root .bar",
306
+ ".baz:root .qux {@extend .bar}",
307
+ ".foo:root .bar, .baz.foo:root .qux")
308
+ end
309
+
310
+ def test_not_remains_at_end_of_selector
311
+ assert_extends '.foo:not(.bar)', '.baz {@extend .foo}', '.foo:not(.bar), .baz:not(.bar)'
312
+ end
313
+
314
+ def test_pseudoelement_goes_lefter_than_pseudoclass
315
+ assert_extends '.foo::bar', '.baz:bang {@extend .foo}', '.foo::bar, .baz:bang::bar'
316
+ assert_extends '.foo:bar', '.baz::bang {@extend .foo}', '.foo:bar, .baz:bar::bang'
317
+ end
318
+
319
+ def test_pseudoelement_goes_lefter_than_not
320
+ assert_extends '.foo::bar', '.baz:not(.bang) {@extend .foo}', '.foo::bar, .baz:not(.bang)::bar'
321
+ assert_extends '.foo:not(.bang)', '.baz::bar {@extend .foo}', '.foo:not(.bang), .baz:not(.bang)::bar'
322
+ end
323
+
324
+ def test_negation_unification
325
+ assert_extends ':not(.foo).baz', ':not(.bar) {@extend .baz}', ':not(.foo).baz, :not(.foo):not(.bar)'
326
+ # Unifying to :not(.foo) here would reduce the specificity of the original selector.
327
+ assert_extends ':not(.foo).baz', ':not(.foo) {@extend .baz}', ':not(.foo).baz, :not(.foo)'
328
+ end
329
+
330
+ def test_prefixed_pseudoclass_unification
331
+ assert_unification(
332
+ ':nth-child(2n+1 of .foo).baz',
333
+ ':nth-child(2n of .foo) {@extend .baz}',
334
+ ':nth-child(2n+1 of .foo).baz, :nth-child(2n+1 of .foo):nth-child(2n of .foo)')
335
+
336
+ assert_unification(
337
+ ':nth-child(2n+1 of .foo).baz',
338
+ ':nth-child(2n+1 of .bar) {@extend .baz}',
339
+ ':nth-child(2n+1 of .foo).baz, :nth-child(2n+1 of .foo):nth-child(2n+1 of .bar)')
340
+
341
+ assert_unification(
342
+ ':nth-child(2n+1 of .foo).baz',
343
+ ':nth-child(2n+1 of .foo) {@extend .baz}',
344
+ ':nth-child(2n+1 of .foo)')
345
+ end
346
+
347
+ def test_extend_into_not
348
+ assert_extends(':not(.foo)', '.x {@extend .foo}', ':not(.foo):not(.x)')
349
+ assert_extends(':not(.foo.bar)', '.x {@extend .bar}', ':not(.foo.bar):not(.foo.x)')
350
+ assert_extends(
351
+ ':not(.foo.bar, .baz.bar)',
352
+ '.x {@extend .bar}',
353
+ ':not(.foo.bar, .foo.x, .baz.bar, .baz.x)')
354
+ end
355
+
356
+ def test_extend_into_mergeable_pseudoclasses
357
+ assert_extends(':matches(.foo)', '.x {@extend .foo}', ':matches(.foo, .x)')
358
+ assert_extends(':matches(.foo.bar)', '.x {@extend .bar}', ':matches(.foo.bar, .foo.x)')
359
+ assert_extends(
360
+ ':matches(.foo.bar, .baz.bar)',
361
+ '.x {@extend .bar}',
362
+ ':matches(.foo.bar, .foo.x, .baz.bar, .baz.x)')
363
+
364
+ assert_extends(':-moz-any(.foo)', '.x {@extend .foo}', ':-moz-any(.foo, .x)')
365
+ assert_extends(':current(.foo)', '.x {@extend .foo}', ':current(.foo, .x)')
366
+ assert_extends(':has(.foo)', '.x {@extend .foo}', ':has(.foo, .x)')
367
+ assert_extends(':host(.foo)', '.x {@extend .foo}', ':host(.foo, .x)')
368
+ assert_extends(':host-context(.foo)', '.x {@extend .foo}', ':host-context(.foo, .x)')
369
+ assert_extends(':nth-child(n of .foo)', '.x {@extend .foo}', ':nth-child(n of .foo, .x)')
370
+ assert_extends(
371
+ ':nth-last-child(n of .foo)',
372
+ '.x {@extend .foo}',
373
+ ':nth-last-child(n of .foo, .x)')
374
+ end
375
+
376
+ def test_complex_extend_into_pseudoclass
377
+ # Unlike other selectors, we don't allow complex selectors to be
378
+ # added to `:not` if they weren't there before. At time of
379
+ # writing, most browsers don't support that and will throw away
380
+ # the entire selector if it exists.
381
+ #assert_extends(':not(.bar)', '.x .y {@extend .bar}', ':not(.bar)')
382
+
383
+ # If the `:not()` already has a complex selector, we won't break
384
+ # anything by adding a new one.
385
+ assert_extends(':not(.baz .bar)', '.x .y {@extend .bar}',
386
+ ':not(.baz .bar):not(.baz .x .y):not(.x .baz .y)')
387
+
388
+ # If the `:not()` would only contain complex selectors, there's no
389
+ # harm in letting it continue to exist.
390
+ assert_extends(':not(%bar)', '.x .y {@extend %bar}', ':not(.x .y)')
391
+
392
+ assert_extends(':matches(.bar)', '.x .y {@extend .bar}', ':matches(.bar, .x .y)')
393
+ assert_extends(':current(.bar)', '.x .y {@extend .bar}', ':current(.bar, .x .y)')
394
+ assert_extends(':has(.bar)', '.x .y {@extend .bar}', ':has(.bar, .x .y)')
395
+ assert_extends(':host(.bar)', '.x .y {@extend .bar}', ':host(.bar, .x .y)')
396
+ assert_extends(':host-context(.bar)', '.x .y {@extend .bar}', ':host-context(.bar, .x .y)')
397
+ assert_extends(
398
+ ':-moz-any(.bar)',
399
+ '.x .y {@extend .bar}',
400
+ ':-moz-any(.bar, .x .y)')
401
+ assert_extends(
402
+ ':nth-child(n of .bar)',
403
+ '.x .y {@extend .bar}',
404
+ ':nth-child(n of .bar, .x .y)')
405
+ assert_extends(
406
+ ':nth-last-child(n of .bar)',
407
+ '.x .y {@extend .bar}',
408
+ ':nth-last-child(n of .bar, .x .y)')
409
+ end
410
+
411
+ def test_extend_over_selector_pseudoclass
412
+ assert_extends(':not(.foo)', '.x {@extend :not(.foo)}', ':not(.foo), .x')
413
+ assert_extends(
414
+ ':matches(.foo, .bar)',
415
+ '.x {@extend :matches(.foo, .bar)}',
416
+ ':matches(.foo, .bar), .x')
417
+ end
418
+
419
+ def test_matches_within_not
420
+ assert_extends(
421
+ ':not(.foo, .bar)',
422
+ ':matches(.x, .y) {@extend .foo}',
423
+ ':not(.foo, .x, .y, .bar)')
424
+ end
425
+
426
+ def test_pseudoclasses_merge
427
+ assert_extends(':matches(.foo)', ':matches(.bar) {@extend .foo}', ':matches(.foo, .bar)')
428
+ assert_extends(':-moz-any(.foo)', ':-moz-any(.bar) {@extend .foo}', ':-moz-any(.foo, .bar)')
429
+ assert_extends(':current(.foo)', ':current(.bar) {@extend .foo}', ':current(.foo, .bar)')
430
+ assert_extends(
431
+ ':nth-child(n of .foo)',
432
+ ':nth-child(n of .bar) {@extend .foo}',
433
+ ':nth-child(n of .foo, .bar)')
434
+ assert_extends(
435
+ ':nth-last-child(n of .foo)',
436
+ ':nth-last-child(n of .bar) {@extend .foo}',
437
+ ':nth-last-child(n of .foo, .bar)')
438
+ end
439
+
440
+ def test_nesting_pseudoclasses_merge
441
+ assert_extends(':has(.foo)', ':has(.bar) {@extend .foo}', ':has(.foo, :has(.bar))')
442
+ assert_extends(':host(.foo)', ':host(.bar) {@extend .foo}', ':host(.foo, :host(.bar))')
443
+ assert_extends(
444
+ ':host-context(.foo)',
445
+ ':host-context(.bar) {@extend .foo}',
446
+ ':host-context(.foo, :host-context(.bar))')
447
+ end
448
+
449
+ def test_not_unifies_with_unique_values
450
+ assert_unification('foo', ':not(bar) {@extend foo}', ':not(bar)')
451
+ assert_unification('#foo', ':not(#bar) {@extend #foo}', ':not(#bar)')
452
+ end
453
+
454
+ def test_not_adds_no_specificity
455
+ assert_specificity_equals(':not(.foo)', '.foo')
456
+ end
457
+
458
+ def test_matches_has_a_specificity_range
459
+ # `:matches(.foo, #bar)` has minimum specificity equal to that of `.foo`,
460
+ # which means `:matches(.foo, #bar) .a` can have less specificity than
461
+ # `#b.a`. Thus the selector generated by `#b.a` should be preserved.
462
+ assert_equal <<CSS, render(<<SCSS)
463
+ :matches(.foo, #bar) .a, :matches(.foo, #bar) #b.a {
464
+ a: b; }
465
+ CSS
466
+ :matches(.foo, #bar) %x {a: b}
467
+ .a {@extend %x}
468
+ #b.a {@extend %x}
469
+ SCSS
470
+
471
+ # `:matches(.foo, #bar)` has maximum specificity equal to that of `#bar`,
472
+ # which means `:matches(.foo, #bar).b` can have greater specificity than `.a
473
+ # .b`. Thus the selector generated by `:matches(.foo, #bar).b` should be
474
+ # preserved.
475
+ assert_equal <<CSS, render(<<SCSS)
476
+ .a .b, .a .b:matches(.foo, #bar) {
477
+ a: b; }
478
+ CSS
479
+ .a %x {a: b}
480
+ .b {@extend %x}
481
+ .b:matches(.foo, #bar) {@extend %x}
482
+ SCSS
483
+ end
484
+
485
+ def test_extend_into_not_and_normal_extend
486
+ assert_equal <<CSS, render(<<SCSS)
487
+ .x:not(.y):not(.bar), .foo:not(.y):not(.bar) {
488
+ a: b; }
489
+ CSS
490
+ .x:not(.y) {a: b}
491
+ .foo {@extend .x}
492
+ .bar {@extend .y}
493
+ SCSS
494
+ end
495
+
496
+ def test_extend_into_matches_and_normal_extend
497
+ assert_equal <<CSS, render(<<SCSS)
498
+ .x:matches(.y, .bar), .foo:matches(.y, .bar) {
499
+ a: b; }
500
+ CSS
501
+ .x:matches(.y) {a: b}
502
+ .foo {@extend .x}
503
+ .bar {@extend .y}
504
+ SCSS
505
+ end
506
+
507
+ def test_multilayer_pseudoclass_extend
508
+ assert_equal <<CSS, render(<<SCSS)
509
+ :matches(.x, .foo, .bar) {
510
+ a: b; }
511
+ CSS
512
+ :matches(.x) {a: b}
513
+ .foo {@extend .x}
514
+ .bar {@extend .foo}
515
+ SCSS
516
+ end
517
+
518
+ def test_root_only_allowed_at_root
519
+ assert_extends(':root .foo', '.bar .baz {@extend .foo}',
520
+ ':root .foo, :root .bar .baz')
521
+ assert_extends('.foo:root .bar', '.baz:root .bang {@extend .bar}',
522
+ '.foo:root .bar, .baz.foo:root .bang')
523
+ assert_extends('html:root .bar', 'xml:root .bang {@extend .bar}', 'html:root .bar')
524
+ assert_extends('.foo:root > .bar .x', '.baz:root .bang .y {@extend .x}',
525
+ '.foo:root > .bar .x, .baz.foo:root > .bar .bang .y')
526
+ end
527
+
528
+ def test_comma_extendee
529
+ assert_equal <<CSS, render(<<SCSS)
530
+ .foo, .baz {
531
+ a: b; }
532
+
533
+ .bar, .baz {
534
+ c: d; }
535
+ CSS
536
+ .foo {a: b}
537
+ .bar {c: d}
538
+ .baz {@extend .foo, .bar}
539
+ SCSS
540
+ end
541
+
542
+ def test_redundant_selector_elimination
543
+ assert_equal <<CSS, render(<<SCSS)
544
+ .foo.bar, .x, .y {
545
+ a: b; }
546
+ CSS
547
+ .foo.bar {a: b}
548
+ .x {@extend .foo, .bar}
549
+ .y {@extend .foo, .bar}
550
+ SCSS
551
+ end
552
+
553
+ def test_nested_pseudo_selectors
554
+ assert_equal <<CSS, render(<<SCSS)
555
+ .foo .bar:not(.baz), .bang .bar:not(.baz) {
556
+ a: b; }
557
+ CSS
558
+ .foo {
559
+ .bar:not(.baz) {a: b}
560
+ }
561
+ .bang {@extend .foo}
562
+ SCSS
563
+ end
564
+
565
+ ## Long Extendees
566
+
567
+ def test_long_extendee
568
+ assert_extends '.foo.bar', '.baz {@extend .foo.bar}', '.foo.bar, .baz'
569
+ end
570
+
571
+ def test_long_extendee_requires_all_selectors
572
+ assert_extend_doesnt_match('.baz', '.foo.bar', :not_found, 2) do
573
+ render_extends '.foo', '.baz {@extend .foo.bar}'
574
+ end
575
+ end
576
+
577
+ def test_long_extendee_matches_supersets
578
+ assert_extends '.foo.bar.bap', '.baz {@extend .foo.bar}', '.foo.bar.bap, .bap.baz'
579
+ end
580
+
581
+ def test_long_extendee_runs_unification
582
+ assert_extends 'ns|*.foo.bar', 'a.baz {@extend .foo.bar}', 'ns|*.foo.bar, ns|a.baz'
583
+ end
584
+
585
+ ## Long Extenders
586
+
587
+ def test_long_extender
588
+ assert_extends '.foo.bar', '.baz.bang {@extend .foo}', '.foo.bar, .bar.baz.bang'
589
+ end
590
+
591
+ def test_long_extender_runs_unification
592
+ assert_extends 'ns|*.foo.bar', 'a.baz {@extend .foo}', 'ns|*.foo.bar, ns|a.bar.baz'
593
+ end
594
+
595
+ def test_long_extender_aborts_unification
596
+ assert_extend_doesnt_match('h1.baz', '.foo', :failed_to_unify, 2) do
597
+ render_extends 'a.foo#bar', 'h1.baz {@extend .foo}'
598
+ end
599
+
600
+ assert_extend_doesnt_match('.bang#baz', '.foo', :failed_to_unify, 2) do
601
+ render_extends 'a.foo#bar', '.bang#baz {@extend .foo}'
602
+ end
603
+ end
604
+
605
+ ## Nested Extenders
606
+
607
+ def test_nested_extender
608
+ assert_extends '.foo', 'foo bar {@extend .foo}', '.foo, foo bar'
609
+ end
610
+
611
+ def test_nested_extender_runs_unification
612
+ assert_extends '.foo.bar', 'foo bar {@extend .foo}', '.foo.bar, foo bar.bar'
613
+ end
614
+
615
+ def test_nested_extender_aborts_unification
616
+ assert_extend_doesnt_match('foo bar', '.foo', :failed_to_unify, 2) do
617
+ render_extends 'baz.foo', 'foo bar {@extend .foo}'
618
+ end
619
+ end
620
+
621
+ def test_nested_extender_alternates_parents
622
+ assert_extends('.baz .bip .foo', 'foo .grank bar {@extend .foo}',
623
+ '.baz .bip .foo, .baz .bip foo .grank bar, foo .grank .baz .bip bar')
624
+ end
625
+
626
+ def test_nested_extender_unifies_identical_parents
627
+ assert_extends('.baz .bip .foo', '.baz .bip bar {@extend .foo}',
628
+ '.baz .bip .foo, .baz .bip bar')
629
+ end
630
+
631
+ def test_nested_extender_unifies_common_substring
632
+ assert_extends('.baz .bip .bap .bink .foo', '.brat .bip .bap bar {@extend .foo}',
633
+ '.baz .bip .bap .bink .foo, .baz .brat .bip .bap .bink bar, .brat .baz .bip .bap .bink bar')
634
+ end
635
+
636
+ def test_nested_extender_unifies_common_subseq
637
+ assert_extends('.a .x .b .y .foo', '.a .n .b .m bar {@extend .foo}',
638
+ '.a .x .b .y .foo, .a .x .n .b .y .m bar, .a .n .x .b .y .m bar, .a .x .n .b .m .y bar, .a .n .x .b .m .y bar')
639
+ end
640
+
641
+ def test_nested_extender_chooses_first_subseq
642
+ assert_extends('.a .b .c .d .foo', '.c .d .a .b .bar {@extend .foo}',
643
+ '.a .b .c .d .foo, .a .b .c .d .a .b .bar')
644
+ end
645
+
646
+ def test_nested_extender_counts_extended_subselectors
647
+ assert_extends('.a .bip.bop .foo', '.b .bip .bar {@extend .foo}',
648
+ '.a .bip.bop .foo, .a .b .bip.bop .bar, .b .a .bip.bop .bar')
649
+ end
650
+
651
+ def test_nested_extender_counts_extended_superselectors
652
+ assert_extends('.a .bip .foo', '.b .bip.bop .bar {@extend .foo}',
653
+ '.a .bip .foo, .a .b .bip.bop .bar, .b .a .bip.bop .bar')
654
+ end
655
+
656
+ def test_nested_extender_with_child_selector
657
+ assert_extends '.baz .foo', 'foo > bar {@extend .foo}', '.baz .foo, .baz foo > bar'
658
+ end
659
+
660
+ def test_nested_extender_finds_common_selectors_around_child_selector
661
+ assert_extends 'a > b c .c1', 'a c .c2 {@extend .c1}', 'a > b c .c1, a > b c .c2'
662
+ assert_extends 'a > b c .c1', 'b c .c2 {@extend .c1}', 'a > b c .c1, a > b c .c2'
663
+ end
664
+
665
+ def test_nested_extender_doesnt_find_common_selectors_around_adjacent_sibling_selector
666
+ assert_extends 'a + b c .c1', 'a c .c2 {@extend .c1}', 'a + b c .c1, a + b a c .c2, a a + b c .c2'
667
+ assert_extends 'a + b c .c1', 'a b .c2 {@extend .c1}', 'a + b c .c1, a a + b c .c2'
668
+ assert_extends 'a + b c .c1', 'b c .c2 {@extend .c1}', 'a + b c .c1, a + b c .c2'
669
+ end
670
+
671
+ def test_nested_extender_doesnt_find_common_selectors_around_sibling_selector
672
+ assert_extends 'a ~ b c .c1', 'a c .c2 {@extend .c1}', 'a ~ b c .c1, a ~ b a c .c2, a a ~ b c .c2'
673
+ assert_extends 'a ~ b c .c1', 'a b .c2 {@extend .c1}', 'a ~ b c .c1, a a ~ b c .c2'
674
+ assert_extends 'a ~ b c .c1', 'b c .c2 {@extend .c1}', 'a ~ b c .c1, a ~ b c .c2'
675
+ end
676
+
677
+ def test_nested_extender_doesnt_find_common_selectors_around_reference_selector
678
+ assert_extends 'a /for/ b c .c1', 'a c .c2 {@extend .c1}', 'a /for/ b c .c1, a /for/ b a c .c2, a a /for/ b c .c2'
679
+ assert_extends 'a /for/ b c .c1', 'a b .c2 {@extend .c1}', 'a /for/ b c .c1, a a /for/ b c .c2'
680
+ assert_extends 'a /for/ b c .c1', 'b c .c2 {@extend .c1}', 'a /for/ b c .c1, a /for/ b c .c2'
681
+ end
682
+
683
+ def test_nested_extender_with_early_child_selectors_doesnt_subseq_them
684
+ assert_extends('.bip > .bap .foo', '.grip > .bap .bar {@extend .foo}',
685
+ '.bip > .bap .foo, .bip > .bap .grip > .bap .bar, .grip > .bap .bip > .bap .bar')
686
+ assert_extends('.bap > .bip .foo', '.bap > .grip .bar {@extend .foo}',
687
+ '.bap > .bip .foo, .bap > .bip .bap > .grip .bar, .bap > .grip .bap > .bip .bar')
688
+ end
689
+
690
+ def test_nested_extender_with_child_selector_unifies
691
+ assert_extends '.baz.foo', 'foo > bar {@extend .foo}', '.baz.foo, foo > bar.baz'
692
+
693
+ assert_equal <<CSS, render(<<SCSS)
694
+ .baz > .foo, .baz > .bar {
695
+ a: b; }
696
+ CSS
697
+ .baz > {
698
+ .foo {a: b}
699
+ .bar {@extend .foo}
700
+ }
701
+ SCSS
702
+
703
+ assert_equal <<CSS, render(<<SCSS)
704
+ .foo .bar, .foo > .baz {
705
+ a: b; }
706
+ CSS
707
+ .foo {
708
+ .bar {a: b}
709
+ > .baz {@extend .bar}
710
+ }
711
+ SCSS
712
+ end
713
+
714
+ def test_nested_extender_with_early_child_selector
715
+ assert_equal <<CSS, render(<<SCSS)
716
+ .foo .bar, .foo .bip > .baz {
717
+ a: b; }
718
+ CSS
719
+ .foo {
720
+ .bar {a: b}
721
+ .bip > .baz {@extend .bar}
722
+ }
723
+ SCSS
724
+
725
+ assert_equal <<CSS, render(<<SCSS)
726
+ .foo .bip .bar, .foo .bip .foo > .baz {
727
+ a: b; }
728
+ CSS
729
+ .foo {
730
+ .bip .bar {a: b}
731
+ > .baz {@extend .bar}
732
+ }
733
+ SCSS
734
+
735
+ assert_extends '.foo > .bar', '.bip + .baz {@extend .bar}', '.foo > .bar, .foo > .bip + .baz'
736
+ assert_extends '.foo + .bar', '.bip > .baz {@extend .bar}', '.foo + .bar, .bip > .foo + .baz'
737
+ assert_extends '.foo > .bar', '.bip > .baz {@extend .bar}', '.foo > .bar, .bip.foo > .baz'
738
+ end
739
+
740
+ def test_nested_extender_with_trailing_child_selector
741
+ assert_raises(Sass::SyntaxError, "bar > can't extend: invalid selector") do
742
+ render("bar > {@extend .baz}")
743
+ end
744
+ end
745
+
746
+ def test_nested_extender_with_sibling_selector
747
+ assert_extends '.baz .foo', 'foo + bar {@extend .foo}', '.baz .foo, .baz foo + bar'
748
+ end
749
+
750
+ def test_nested_extender_with_hacky_selector
751
+ assert_extends('.baz .foo', 'foo + > > + bar {@extend .foo}',
752
+ '.baz .foo, .baz foo + > > + bar, foo .baz + > > + bar')
753
+ assert_extends '.baz .foo', '> > bar {@extend .foo}', '.baz .foo, > > .baz bar'
754
+ end
755
+
756
+ def test_nested_extender_merges_with_same_selector
757
+ assert_equal <<CSS, render(<<SCSS)
758
+ .foo .bar, .foo .baz {
759
+ a: b; }
760
+ CSS
761
+ .foo {
762
+ .bar {a: b}
763
+ .baz {@extend .bar} }
764
+ SCSS
765
+ end
766
+
767
+ def test_nested_extender_with_child_selector_merges_with_same_selector
768
+ assert_extends('.foo > .bar .baz', '.foo > .bar .bang {@extend .baz}',
769
+ '.foo > .bar .baz, .foo > .bar .bang')
770
+ end
771
+
772
+ # Combinator Unification
773
+
774
+ def test_combinator_unification_for_hacky_combinators
775
+ assert_extends '.a > + x', '.b y {@extend x}', '.a > + x, .a .b > + y, .b .a > + y'
776
+ assert_extends '.a x', '.b > + y {@extend x}', '.a x, .a .b > + y, .b .a > + y'
777
+ assert_extends '.a > + x', '.b > + y {@extend x}', '.a > + x, .a .b > + y, .b .a > + y'
778
+ assert_extends '.a ~ > + x', '.b > + y {@extend x}', '.a ~ > + x, .a .b ~ > + y, .b .a ~ > + y'
779
+ assert_extends '.a + > x', '.b > + y {@extend x}', '.a + > x'
780
+ assert_extends '.a + > x', '.b > + y {@extend x}', '.a + > x'
781
+ assert_extends '.a ~ > + .b > x', '.c > + .d > y {@extend x}', '.a ~ > + .b > x, .a .c ~ > + .d.b > y, .c .a ~ > + .d.b > y'
782
+ end
783
+
784
+ def test_combinator_unification_double_tilde
785
+ assert_extends '.a.b ~ x', '.a ~ y {@extend x}', '.a.b ~ x, .a.b ~ y'
786
+ assert_extends '.a ~ x', '.a.b ~ y {@extend x}', '.a ~ x, .a.b ~ y'
787
+ assert_extends '.a ~ x', '.b ~ y {@extend x}', '.a ~ x, .a ~ .b ~ y, .b ~ .a ~ y, .b.a ~ y'
788
+ assert_extends 'a.a ~ x', 'b.b ~ y {@extend x}', 'a.a ~ x, a.a ~ b.b ~ y, b.b ~ a.a ~ y'
789
+ end
790
+
791
+ def test_combinator_unification_tilde_plus
792
+ assert_extends '.a.b + x', '.a ~ y {@extend x}', '.a.b + x, .a.b + y'
793
+ assert_extends '.a + x', '.a.b ~ y {@extend x}', '.a + x, .a.b ~ .a + y, .a.b + y'
794
+ assert_extends '.a + x', '.b ~ y {@extend x}', '.a + x, .b ~ .a + y, .b.a + y'
795
+ assert_extends 'a.a + x', 'b.b ~ y {@extend x}', 'a.a + x, b.b ~ a.a + y'
796
+ assert_extends '.a.b ~ x', '.a + y {@extend x}', '.a.b ~ x, .a.b ~ .a + y, .a.b + y'
797
+ assert_extends '.a ~ x', '.a.b + y {@extend x}', '.a ~ x, .a.b + y'
798
+ assert_extends '.a ~ x', '.b + y {@extend x}', '.a ~ x, .a ~ .b + y, .a.b + y'
799
+ assert_extends 'a.a ~ x', 'b.b + y {@extend x}', 'a.a ~ x, a.a ~ b.b + y'
800
+ end
801
+
802
+ def test_combinator_unification_angle_sibling
803
+ assert_extends '.a > x', '.b ~ y {@extend x}', '.a > x, .a > .b ~ y'
804
+ assert_extends '.a > x', '.b + y {@extend x}', '.a > x, .a > .b + y'
805
+ assert_extends '.a ~ x', '.b > y {@extend x}', '.a ~ x, .b > .a ~ y'
806
+ assert_extends '.a + x', '.b > y {@extend x}', '.a + x, .b > .a + y'
807
+ end
808
+
809
+ def test_combinator_unification_double_angle
810
+ assert_extends '.a.b > x', '.b > y {@extend x}', '.a.b > x, .b.a > y'
811
+ assert_extends '.a > x', '.a.b > y {@extend x}', '.a > x, .a.b > y'
812
+ assert_extends '.a > x', '.b > y {@extend x}', '.a > x, .b.a > y'
813
+ assert_extends 'a.a > x', 'b.b > y {@extend x}', 'a.a > x'
814
+ end
815
+
816
+ def test_combinator_unification_double_plus
817
+ assert_extends '.a.b + x', '.b + y {@extend x}', '.a.b + x, .b.a + y'
818
+ assert_extends '.a + x', '.a.b + y {@extend x}', '.a + x, .a.b + y'
819
+ assert_extends '.a + x', '.b + y {@extend x}', '.a + x, .b.a + y'
820
+ assert_extends 'a.a + x', 'b.b + y {@extend x}', 'a.a + x'
821
+ end
822
+
823
+ def test_combinator_unification_angle_space
824
+ assert_extends '.a.b > x', '.a y {@extend x}', '.a.b > x, .a.b > y'
825
+ assert_extends '.a > x', '.a.b y {@extend x}', '.a > x, .a.b .a > y'
826
+ assert_extends '.a > x', '.b y {@extend x}', '.a > x, .b .a > y'
827
+ assert_extends '.a.b x', '.a > y {@extend x}', '.a.b x, .a.b .a > y'
828
+ assert_extends '.a x', '.a.b > y {@extend x}', '.a x, .a.b > y'
829
+ assert_extends '.a x', '.b > y {@extend x}', '.a x, .a .b > y'
830
+ end
831
+
832
+ def test_combinator_unification_plus_space
833
+ assert_extends '.a.b + x', '.a y {@extend x}', '.a.b + x, .a .a.b + y'
834
+ assert_extends '.a + x', '.a.b y {@extend x}', '.a + x, .a.b .a + y'
835
+ assert_extends '.a + x', '.b y {@extend x}', '.a + x, .b .a + y'
836
+ assert_extends '.a.b x', '.a + y {@extend x}', '.a.b x, .a.b .a + y'
837
+ assert_extends '.a x', '.a.b + y {@extend x}', '.a x, .a .a.b + y'
838
+ assert_extends '.a x', '.b + y {@extend x}', '.a x, .a .b + y'
839
+ end
840
+
841
+ def test_combinator_unification_nested
842
+ assert_extends '.a > .b + x', '.c > .d + y {@extend x}', '.a > .b + x, .c.a > .d.b + y'
843
+ assert_extends '.a > .b + x', '.c > y {@extend x}', '.a > .b + x, .c.a > .b + y'
844
+ end
845
+
846
+ def test_combinator_unification_with_newlines
847
+ assert_equal <<CSS, render(<<SCSS)
848
+ .a >
849
+ .b
850
+ + x, .c.a > .d.b + y {
851
+ a: b; }
852
+ CSS
853
+ .a >
854
+ .b
855
+ + x {a: b}
856
+ .c
857
+ > .d +
858
+ y {@extend x}
859
+ SCSS
860
+ end
861
+
862
+ # Loops
863
+
864
+ def test_extend_self_loop
865
+ assert_equal <<CSS, render(<<SCSS)
866
+ .foo {
867
+ a: b; }
868
+ CSS
869
+ .foo {a: b; @extend .foo}
870
+ SCSS
871
+ end
872
+
873
+ def test_basic_extend_loop
874
+ assert_equal <<CSS, render(<<SCSS)
875
+ .foo, .bar {
876
+ a: b; }
877
+
878
+ .bar, .foo {
879
+ c: d; }
880
+ CSS
881
+ .foo {a: b; @extend .bar}
882
+ .bar {c: d; @extend .foo}
883
+ SCSS
884
+ end
885
+
886
+ def test_three_level_extend_loop
887
+ assert_equal <<CSS, render(<<SCSS)
888
+ .foo, .baz, .bar {
889
+ a: b; }
890
+
891
+ .bar, .foo, .baz {
892
+ c: d; }
893
+
894
+ .baz, .bar, .foo {
895
+ e: f; }
896
+ CSS
897
+ .foo {a: b; @extend .bar}
898
+ .bar {c: d; @extend .baz}
899
+ .baz {e: f; @extend .foo}
900
+ SCSS
901
+ end
902
+
903
+ def test_nested_extend_loop
904
+ assert_equal <<CSS, render(<<SCSS)
905
+ .bar, .bar .foo {
906
+ a: b; }
907
+ .bar .foo {
908
+ c: d; }
909
+ CSS
910
+ .bar {
911
+ a: b;
912
+ .foo {c: d; @extend .bar}
913
+ }
914
+ SCSS
915
+ end
916
+
917
+ def test_cross_loop
918
+ # The first law of extend means the selector should stick around.
919
+ assert_equal <<CSS, render(<<SCSS)
920
+ .foo.bar, .foo, .bar {
921
+ a: b; }
922
+ CSS
923
+ .foo.bar {a: b}
924
+ .foo {@extend .bar}
925
+ .bar {@extend .foo}
926
+ SCSS
927
+ end
928
+
929
+ def test_multiple_extender_merges_with_superset_selector
930
+ assert_equal <<CSS, render(<<SCSS)
931
+ a.bar.baz, a.foo {
932
+ a: b; }
933
+ CSS
934
+ .foo {@extend .bar; @extend .baz}
935
+ a.bar.baz {a: b}
936
+ SCSS
937
+ end
938
+
939
+ def test_control_flow_if
940
+ assert_equal <<CSS, render(<<SCSS)
941
+ .true, .also-true {
942
+ color: green; }
943
+
944
+ .false, .also-false {
945
+ color: red; }
946
+ CSS
947
+ .true { color: green; }
948
+ .false { color: red; }
949
+ .also-true {
950
+ @if true { @extend .true; }
951
+ @else { @extend .false; }
952
+ }
953
+ .also-false {
954
+ @if false { @extend .true; }
955
+ @else { @extend .false; }
956
+ }
957
+ SCSS
958
+ end
959
+
960
+ def test_control_flow_for
961
+ assert_equal <<CSS, render(<<SCSS)
962
+ .base-0, .added {
963
+ color: green; }
964
+
965
+ .base-1, .added {
966
+ display: block; }
967
+
968
+ .base-2, .added {
969
+ border: 1px solid blue; }
970
+ CSS
971
+ .base-0 { color: green; }
972
+ .base-1 { display: block; }
973
+ .base-2 { border: 1px solid blue; }
974
+ .added {
975
+ @for $i from 0 to 3 {
976
+ @extend .base-\#{$i};
977
+ }
978
+ }
979
+ SCSS
980
+ end
981
+
982
+ def test_control_flow_while
983
+ assert_equal <<CSS, render(<<SCSS)
984
+ .base-0, .added {
985
+ color: green; }
986
+
987
+ .base-1, .added {
988
+ display: block; }
989
+
990
+ .base-2, .added {
991
+ border: 1px solid blue; }
992
+ CSS
993
+ .base-0 { color: green; }
994
+ .base-1 { display: block; }
995
+ .base-2 { border: 1px solid blue; }
996
+ .added {
997
+ $i : 0;
998
+ @while $i < 3 {
999
+ @extend .base-\#{$i};
1000
+ $i : $i + 1;
1001
+ }
1002
+ }
1003
+ SCSS
1004
+ end
1005
+
1006
+ def test_basic_placeholder_selector
1007
+ assert_extends '%foo', '.bar {@extend %foo}', '.bar'
1008
+ end
1009
+
1010
+ def test_unused_placeholder_selector
1011
+ assert_equal <<CSS, render(<<SCSS)
1012
+ .baz {
1013
+ color: blue; }
1014
+ CSS
1015
+ %foo {color: blue}
1016
+ %bar {color: red}
1017
+ .baz {@extend %foo}
1018
+ SCSS
1019
+ end
1020
+
1021
+ def test_placeholder_descendant_selector
1022
+ assert_extends '#context %foo a', '.bar {@extend %foo}', '#context .bar a'
1023
+ end
1024
+
1025
+ def test_semi_placeholder_selector
1026
+ assert_equal <<CSS, render(<<SCSS)
1027
+ .bar .baz {
1028
+ color: blue; }
1029
+ CSS
1030
+ #context %foo, .bar .baz {color: blue}
1031
+ SCSS
1032
+ end
1033
+
1034
+ def test_placeholder_selector_with_multiple_extenders
1035
+ assert_equal <<CSS, render(<<SCSS)
1036
+ .bar, .baz {
1037
+ color: blue; }
1038
+ CSS
1039
+ %foo {color: blue}
1040
+ .bar {@extend %foo}
1041
+ .baz {@extend %foo}
1042
+ SCSS
1043
+ end
1044
+
1045
+ def test_placeholder_selector_as_modifier
1046
+ assert_extend_doesnt_match('div', '%foo', :failed_to_unify, 3) do
1047
+ render(<<SCSS)
1048
+ a%foo.baz {color: blue}
1049
+ .bar {@extend %foo}
1050
+ div {@extend %foo}
1051
+ SCSS
1052
+ end
1053
+ end
1054
+
1055
+ def test_placeholder_interpolation
1056
+ assert_equal <<CSS, render(<<SCSS)
1057
+ .bar {
1058
+ color: blue; }
1059
+ CSS
1060
+ $foo: foo;
1061
+
1062
+ %\#{$foo} {color: blue}
1063
+ .bar {@extend %foo}
1064
+ SCSS
1065
+ end
1066
+
1067
+ def test_placeholder_in_selector_pseudoclass
1068
+ assert_equal <<CSS, render(<<SCSS)
1069
+ :matches(.bar, .baz) {
1070
+ color: blue; }
1071
+ CSS
1072
+ :matches(%foo) {color: blue}
1073
+ .bar {@extend %foo}
1074
+ .baz {@extend %foo}
1075
+ SCSS
1076
+ end
1077
+
1078
+ def test_media_in_placeholder_selector
1079
+ assert_equal <<CSS, render(<<SCSS)
1080
+ .baz {
1081
+ c: d; }
1082
+ CSS
1083
+ %foo {bar {@media screen {a: b}}}
1084
+ .baz {c: d}
1085
+ SCSS
1086
+ end
1087
+
1088
+ def test_extend_out_of_media
1089
+ assert_raise_message(Sass::SyntaxError, <<ERR) {render(<<SCSS)}
1090
+ You may not @extend an outer selector from within @media.
1091
+ You may only @extend selectors within the same directive.
1092
+ From "@extend .foo" on line 3 of test_extend_out_of_media_inline.scss.
1093
+ ERR
1094
+ .foo {a: b}
1095
+ @media screen {
1096
+ .bar {@extend .foo}
1097
+ }
1098
+ SCSS
1099
+ end
1100
+
1101
+ def test_extend_out_of_unknown_directive
1102
+ assert_raise_message(Sass::SyntaxError, <<ERR) {render(<<SCSS)}
1103
+ You may not @extend an outer selector from within @flooblehoof.
1104
+ You may only @extend selectors within the same directive.
1105
+ From "@extend .foo" on line 3 of test_extend_out_of_unknown_directive_inline.scss.
1106
+ ERR
1107
+ .foo {a: b}
1108
+ @flooblehoof {
1109
+ .bar {@extend .foo}
1110
+ }
1111
+ SCSS
1112
+ end
1113
+
1114
+ def test_extend_out_of_nested_directives
1115
+ assert_raise_message(Sass::SyntaxError, <<ERR) {render(<<SCSS)}
1116
+ You may not @extend an outer selector from within @flooblehoof.
1117
+ You may only @extend selectors within the same directive.
1118
+ From "@extend .foo" on line 4 of test_extend_out_of_nested_directives_inline.scss.
1119
+ ERR
1120
+ @media screen {
1121
+ .foo {a: b}
1122
+ @flooblehoof {
1123
+ .bar {@extend .foo}
1124
+ }
1125
+ }
1126
+ SCSS
1127
+ end
1128
+
1129
+ def test_extend_within_media
1130
+ assert_equal(<<CSS, render(<<SCSS))
1131
+ @media screen {
1132
+ .foo, .bar {
1133
+ a: b; } }
1134
+ CSS
1135
+ @media screen {
1136
+ .foo {a: b}
1137
+ .bar {@extend .foo}
1138
+ }
1139
+ SCSS
1140
+ end
1141
+
1142
+ def test_extend_within_unknown_directive
1143
+ assert_equal(<<CSS, render(<<SCSS))
1144
+ @flooblehoof {
1145
+ .foo, .bar {
1146
+ a: b; } }
1147
+ CSS
1148
+ @flooblehoof {
1149
+ .foo {a: b}
1150
+ .bar {@extend .foo}
1151
+ }
1152
+ SCSS
1153
+ end
1154
+
1155
+ def test_extend_within_nested_directives
1156
+ assert_equal(<<CSS, render(<<SCSS))
1157
+ @media screen {
1158
+ @flooblehoof {
1159
+ .foo, .bar {
1160
+ a: b; } } }
1161
+ CSS
1162
+ @media screen {
1163
+ @flooblehoof {
1164
+ .foo {a: b}
1165
+ .bar {@extend .foo}
1166
+ }
1167
+ }
1168
+ SCSS
1169
+ end
1170
+
1171
+ def test_extend_within_disparate_media
1172
+ assert_equal(<<CSS, render(<<SCSS))
1173
+ @media screen {
1174
+ .foo, .bar {
1175
+ a: b; } }
1176
+ CSS
1177
+ @media screen {.foo {a: b}}
1178
+ @media screen {.bar {@extend .foo}}
1179
+ SCSS
1180
+ end
1181
+
1182
+ def test_extend_within_disparate_unknown_directive
1183
+ assert_equal(<<CSS, render(<<SCSS))
1184
+ @flooblehoof {
1185
+ .foo, .bar {
1186
+ a: b; } }
1187
+ @flooblehoof {}
1188
+ CSS
1189
+ @flooblehoof {.foo {a: b}}
1190
+ @flooblehoof {.bar {@extend .foo}}
1191
+ SCSS
1192
+ end
1193
+
1194
+ def test_extend_within_disparate_nested_directives
1195
+ assert_equal(<<CSS, render(<<SCSS))
1196
+ @media screen {
1197
+ @flooblehoof {
1198
+ .foo, .bar {
1199
+ a: b; } } }
1200
+ @media screen {
1201
+ @flooblehoof {} }
1202
+ CSS
1203
+ @media screen {@flooblehoof {.foo {a: b}}}
1204
+ @media screen {@flooblehoof {.bar {@extend .foo}}}
1205
+ SCSS
1206
+ end
1207
+
1208
+ def test_extend_within_and_without_media
1209
+ assert_raise_message(Sass::SyntaxError, <<ERR) {render(<<SCSS)}
1210
+ You may not @extend an outer selector from within @media.
1211
+ You may only @extend selectors within the same directive.
1212
+ From "@extend .foo" on line 4 of test_extend_within_and_without_media_inline.scss.
1213
+ ERR
1214
+ .foo {a: b}
1215
+ @media screen {
1216
+ .foo {c: d}
1217
+ .bar {@extend .foo}
1218
+ }
1219
+ SCSS
1220
+ end
1221
+
1222
+ def test_extend_within_and_without_unknown_directive
1223
+ assert_raise_message(Sass::SyntaxError, <<ERR) {render(<<SCSS)}
1224
+ You may not @extend an outer selector from within @flooblehoof.
1225
+ You may only @extend selectors within the same directive.
1226
+ From "@extend .foo" on line 4 of test_extend_within_and_without_unknown_directive_inline.scss.
1227
+ ERR
1228
+ .foo {a: b}
1229
+ @flooblehoof {
1230
+ .foo {c: d}
1231
+ .bar {@extend .foo}
1232
+ }
1233
+ SCSS
1234
+ end
1235
+
1236
+ def test_extend_within_and_without_nested_directives
1237
+ assert_raise_message(Sass::SyntaxError, <<ERR) {render(<<SCSS)}
1238
+ You may not @extend an outer selector from within @flooblehoof.
1239
+ You may only @extend selectors within the same directive.
1240
+ From "@extend .foo" on line 5 of test_extend_within_and_without_nested_directives_inline.scss.
1241
+ ERR
1242
+ @media screen {
1243
+ .foo {a: b}
1244
+ @flooblehoof {
1245
+ .foo {c: d}
1246
+ .bar {@extend .foo}
1247
+ }
1248
+ }
1249
+ SCSS
1250
+ end
1251
+
1252
+ def test_extend_with_subject_transfers_subject_to_extender
1253
+ silence_warnings {assert_equal(<<CSS, render(<<SCSS))}
1254
+ foo bar! baz, foo .bip .bap! baz, .bip foo .bap! baz {
1255
+ a: b; }
1256
+ CSS
1257
+ foo bar! baz {a: b}
1258
+ .bip .bap {@extend bar}
1259
+ SCSS
1260
+
1261
+ silence_warnings {assert_equal(<<CSS, render(<<SCSS))}
1262
+ foo.x bar.y! baz.z, foo.x .bip bar.bap! baz.z, .bip foo.x bar.bap! baz.z {
1263
+ a: b; }
1264
+ CSS
1265
+ foo.x bar.y! baz.z {a: b}
1266
+ .bip .bap {@extend .y}
1267
+ SCSS
1268
+ end
1269
+
1270
+ def test_extend_with_subject_retains_subject_on_target
1271
+ silence_warnings {assert_equal(<<CSS, render(<<SCSS))}
1272
+ .foo! .bar, .foo! .bip .bap, .bip .foo! .bap {
1273
+ a: b; }
1274
+ CSS
1275
+ .foo! .bar {a: b}
1276
+ .bip .bap {@extend .bar}
1277
+ SCSS
1278
+ end
1279
+
1280
+ def test_extend_with_subject_transfers_subject_to_target
1281
+ silence_warnings {assert_equal(<<CSS, render(<<SCSS))}
1282
+ a.foo .bar, .bip a.bap! .bar {
1283
+ a: b; }
1284
+ CSS
1285
+ a.foo .bar {a: b}
1286
+ .bip .bap! {@extend .foo}
1287
+ SCSS
1288
+ end
1289
+
1290
+ def test_extend_with_subject_retains_subject_on_extender
1291
+ silence_warnings {assert_equal(<<CSS, render(<<SCSS))}
1292
+ .foo .bar, .foo .bip! .bap, .bip! .foo .bap {
1293
+ a: b; }
1294
+ CSS
1295
+ .foo .bar {a: b}
1296
+ .bip! .bap {@extend .bar}
1297
+ SCSS
1298
+ end
1299
+
1300
+ def test_extend_with_subject_fails_with_conflicting_subject
1301
+ silence_warnings {assert_equal(<<CSS, render(<<SCSS))}
1302
+ x! .bar {
1303
+ a: b; }
1304
+ CSS
1305
+ x! .bar {a: b}
1306
+ y! .bap {@extend .bar}
1307
+ SCSS
1308
+ end
1309
+
1310
+ def test_extend_warns_when_extendee_doesnt_exist
1311
+ assert_raise_message(Sass::SyntaxError, <<ERR) {render(<<SCSS)}
1312
+ ".foo" failed to @extend ".bar".
1313
+ The selector ".bar" was not found.
1314
+ Use "@extend .bar !optional" if the extend should be able to fail.
1315
+ ERR
1316
+ .foo {@extend .bar}
1317
+ SCSS
1318
+ end
1319
+
1320
+ def test_extend_warns_when_extension_fails
1321
+ assert_raise_message(Sass::SyntaxError, <<ERR) {render(<<SCSS)}
1322
+ "b.foo" failed to @extend ".bar".
1323
+ No selectors matching ".bar" could be unified with "b.foo".
1324
+ Use "@extend .bar !optional" if the extend should be able to fail.
1325
+ ERR
1326
+ a.bar {a: b}
1327
+ b.foo {@extend .bar}
1328
+ SCSS
1329
+ end
1330
+
1331
+ def test_extend_succeeds_when_one_extension_fails_but_others_dont
1332
+ assert_equal(<<CSS, render(<<SCSS))
1333
+ a.bar {
1334
+ a: b; }
1335
+
1336
+ .bar, b.foo {
1337
+ c: d; }
1338
+ CSS
1339
+ a.bar {a: b}
1340
+ .bar {c: d}
1341
+ b.foo {@extend .bar}
1342
+ SCSS
1343
+ end
1344
+
1345
+ def test_optional_extend_succeeds_when_extendee_doesnt_exist
1346
+ assert_equal("", render(<<SCSS))
1347
+ .foo {@extend .bar !optional}
1348
+ SCSS
1349
+ end
1350
+
1351
+ def test_optional_extend_succeeds_when_extension_fails
1352
+ assert_equal(<<CSS, render(<<SCSS))
1353
+ a.bar {
1354
+ a: b; }
1355
+ CSS
1356
+ a.bar {a: b}
1357
+ b.foo {@extend .bar !optional}
1358
+ SCSS
1359
+ end
1360
+
1361
+ # Regression Tests
1362
+
1363
+ def test_extend_with_middle_pseudo
1364
+ assert_equal(<<CSS, render(<<SCSS))
1365
+ .btn:active.focus, :active.focus:before {
1366
+ a: b; }
1367
+ CSS
1368
+ .btn:active.focus {a: b}
1369
+ :before {@extend .btn}
1370
+ SCSS
1371
+ end
1372
+
1373
+ def test_extend_parent_selector_suffix
1374
+ assert_equal <<CSS, render(<<SCSS)
1375
+ .a-b, .c {
1376
+ x: y; }
1377
+ CSS
1378
+ .a {&-b {x: y}}
1379
+ .c {@extend .a-b}
1380
+ SCSS
1381
+ end
1382
+
1383
+ def test_pseudo_element_superselector
1384
+ # Pseudo-elements shouldn't be removed in superselector calculations.
1385
+ assert_equal <<CSS, render(<<SCSS)
1386
+ a#bar, a#bar::fblthp {
1387
+ a: b; }
1388
+ CSS
1389
+ %x#bar {a: b} // Add an id to make the results have high specificity
1390
+ %y, %y::fblthp {@extend %x}
1391
+ a {@extend %y}
1392
+ SCSS
1393
+
1394
+ # Pseudo-classes can be removed when the second law allows.
1395
+ assert_equal <<CSS, render(<<SCSS)
1396
+ a#bar {
1397
+ a: b; }
1398
+ CSS
1399
+ %x#bar {a: b}
1400
+ %y, %y:fblthp {@extend %x}
1401
+ a {@extend %y}
1402
+ SCSS
1403
+
1404
+ # A few pseudo-elements can be written as pseudo-elements for historical
1405
+ # reasons. See http://www.w3.org/TR/selectors4/#pseudo-elements.
1406
+ %w[first-line first-letter before after].each do |pseudo|
1407
+ assert_equal <<CSS, render(<<SCSS)
1408
+ a#bar, a#bar:#{pseudo} {
1409
+ a: b; }
1410
+ CSS
1411
+ %x#bar {a: b}
1412
+ %y, %y:#{pseudo} {@extend %x}
1413
+ a {@extend %y}
1414
+ SCSS
1415
+ end
1416
+ end
1417
+
1418
+ def test_multiple_source_redundancy_elimination
1419
+ assert_equal <<CSS, render(<<SCSS)
1420
+ .test-case, .test-case:active {
1421
+ color: red; }
1422
+
1423
+ .test-case:hover {
1424
+ color: green; }
1425
+ CSS
1426
+ %default-color {color: red}
1427
+ %alt-color {color: green}
1428
+
1429
+ %default-style {
1430
+ @extend %default-color;
1431
+ &:hover {@extend %alt-color}
1432
+ &:active {@extend %default-color}
1433
+ }
1434
+
1435
+ .test-case {@extend %default-style}
1436
+ SCSS
1437
+ end
1438
+
1439
+ def test_nested_sibling_extend
1440
+ assert_equal <<CSS, render(<<SCSS)
1441
+ .parent .bar, .parent .foo {
1442
+ width: 2000px; }
1443
+ CSS
1444
+ .foo {@extend .bar}
1445
+
1446
+ .parent {
1447
+ .bar {
1448
+ width: 2000px;
1449
+ }
1450
+ .foo {
1451
+ @extend .bar
1452
+ }
1453
+ }
1454
+ SCSS
1455
+ end
1456
+
1457
+ def test_parent_and_sibling_extend
1458
+ assert_equal <<CSS, render(<<SCSS)
1459
+ .parent1 .parent2 .child1.child2, .parent2 .parent1 .child1.child2 {
1460
+ c: d; }
1461
+ CSS
1462
+ %foo %bar%baz {c: d}
1463
+
1464
+ .parent1 {
1465
+ @extend %foo;
1466
+ .child1 {@extend %bar}
1467
+ }
1468
+
1469
+ .parent2 {
1470
+ @extend %foo;
1471
+ .child2 {@extend %baz}
1472
+ }
1473
+ SCSS
1474
+ end
1475
+
1476
+ def test_nested_extend_specificity
1477
+ assert_equal <<CSS, render(<<SCSS)
1478
+ a :b, a :b:c {
1479
+ a: b; }
1480
+ CSS
1481
+ %foo {a: b}
1482
+
1483
+ a {
1484
+ :b {@extend %foo}
1485
+ :b:c {@extend %foo}
1486
+ }
1487
+ SCSS
1488
+ end
1489
+
1490
+ def test_nested_double_extend_optimization
1491
+ assert_equal <<CSS, render(<<SCSS)
1492
+ .parent1 .child {
1493
+ a: b; }
1494
+ CSS
1495
+ %foo %bar {
1496
+ a: b;
1497
+ }
1498
+
1499
+ .parent1 {
1500
+ @extend %foo;
1501
+
1502
+ .child {
1503
+ @extend %bar;
1504
+ }
1505
+ }
1506
+
1507
+ .parent2 {
1508
+ @extend %foo;
1509
+ }
1510
+ SCSS
1511
+ end
1512
+
1513
+ def test_extend_in_double_nested_media_query
1514
+ assert_equal <<CSS, render(<<SCSS)
1515
+ @media all and (orientation: landscape) {
1516
+ .bar {
1517
+ color: blue; } }
1518
+ CSS
1519
+ @media all {
1520
+ @media (orientation: landscape) {
1521
+ %foo {color: blue}
1522
+ .bar {@extend %foo}
1523
+ }
1524
+ }
1525
+ SCSS
1526
+ end
1527
+
1528
+ def test_partially_failed_extend
1529
+ assert_no_warning {assert_equal(<<CSS, render(<<SCSS))}
1530
+ .rc, test {
1531
+ color: white; }
1532
+
1533
+ .prices span.pill span.rc {
1534
+ color: red; }
1535
+ CSS
1536
+ test { @extend .rc; }
1537
+ .rc {color: white;}
1538
+ .prices span.pill span.rc {color: red;}
1539
+ SCSS
1540
+ end
1541
+
1542
+ def test_newline_near_combinator
1543
+ assert_equal <<CSS, render(<<SCSS)
1544
+ .a +
1545
+ .b x, .a +
1546
+ .b .c y, .c .a +
1547
+ .b y {
1548
+ a: b; }
1549
+ CSS
1550
+ .a +
1551
+ .b x {a: b}
1552
+ .c y {@extend x}
1553
+ SCSS
1554
+ end
1555
+
1556
+ def test_duplicated_selector_with_newlines
1557
+ assert_equal(<<CSS, render(<<SCSS))
1558
+ .example-1-1,
1559
+ .example-1-2,
1560
+ .my-page-1 .my-module-1-1,
1561
+ .example-1-3 {
1562
+ a: b; }
1563
+ CSS
1564
+ .example-1-1,
1565
+ .example-1-2,
1566
+ .example-1-3 {
1567
+ a: b;
1568
+ }
1569
+
1570
+ .my-page-1 .my-module-1-1 {@extend .example-1-2}
1571
+ SCSS
1572
+ end
1573
+
1574
+ def test_nested_selector_with_child_selector_hack_extendee
1575
+ assert_extends '> .foo', 'foo bar {@extend .foo}', '> .foo, > foo bar'
1576
+ end
1577
+
1578
+ def test_nested_selector_with_child_selector_hack_extender
1579
+ assert_extends '.foo .bar', '> foo bar {@extend .bar}', '.foo .bar, > .foo foo bar, > foo .foo bar'
1580
+ end
1581
+
1582
+ def test_nested_selector_with_child_selector_hack_extender_and_extendee
1583
+ assert_extends '> .foo', '> foo bar {@extend .foo}', '> .foo, > foo bar'
1584
+ end
1585
+
1586
+ def test_nested_selector_with_child_selector_hack_extender_and_sibling_selector_extendee
1587
+ assert_extends '~ .foo', '> foo bar {@extend .foo}', '~ .foo'
1588
+ end
1589
+
1590
+ def test_nested_selector_with_child_selector_hack_extender_and_extendee_and_newline
1591
+ assert_equal <<CSS, render(<<SCSS)
1592
+ > .foo, > flip,
1593
+ > foo bar {
1594
+ a: b; }
1595
+ CSS
1596
+ > .foo {a: b}
1597
+ flip,
1598
+ > foo bar {@extend .foo}
1599
+ SCSS
1600
+ end
1601
+
1602
+ def test_extended_parent_and_child_redundancy_elimination
1603
+ assert_equal <<CSS, render(<<SCSS)
1604
+ a b, d b, a c, d c {
1605
+ a: b; }
1606
+ CSS
1607
+ a {
1608
+ b {a: b}
1609
+ c {@extend b}
1610
+ }
1611
+ d {@extend a}
1612
+ SCSS
1613
+ end
1614
+
1615
+ def test_extend_redundancy_elimination_when_it_would_reduce_specificity
1616
+ assert_extends 'a', 'a.foo {@extend a}', 'a, a.foo'
1617
+ end
1618
+
1619
+ def test_extend_redundancy_elimination_when_it_would_preserve_specificity
1620
+ assert_extends '.bar a', 'a.foo {@extend a}', '.bar a'
1621
+ end
1622
+
1623
+ def test_extend_redundancy_elimination_never_eliminates_base_selector
1624
+ assert_extends 'a.foo', '.foo {@extend a}', 'a.foo, .foo'
1625
+ end
1626
+
1627
+ def test_extend_cross_branch_redundancy_elimination
1628
+ assert_equal <<CSS, render(<<SCSS)
1629
+ .a .c .d, .b .c .a .d {
1630
+ a: b; }
1631
+ CSS
1632
+ %x .c %y {a: b}
1633
+ .a, .b {@extend %x}
1634
+ .a .d {@extend %y}
1635
+ SCSS
1636
+
1637
+ assert_equal <<CSS, render(<<SCSS)
1638
+ .e .a .c .d, .a .c .e .d, .e .b .c .a .d, .b .c .a .e .d {
1639
+ a: b; }
1640
+ CSS
1641
+ .e %z {a: b}
1642
+ %x .c %y {@extend %z}
1643
+ .a, .b {@extend %x}
1644
+ .a .d {@extend %y}
1645
+ SCSS
1646
+ end
1647
+
1648
+ private
1649
+
1650
+ def assert_extend_doesnt_match(extender, target, reason, line, syntax = :scss)
1651
+ message = "\"#{extender}\" failed to @extend \"#{target}\"."
1652
+ reason =
1653
+ if reason == :not_found
1654
+ "The selector \"#{target}\" was not found."
1655
+ else
1656
+ "No selectors matching \"#{target}\" could be unified with \"#{extender}\"."
1657
+ end
1658
+
1659
+ assert_raise_message(Sass::SyntaxError, <<ERR) {yield}
1660
+ #{message}
1661
+ #{reason}
1662
+ Use "@extend #{target} !optional" if the extend should be able to fail.
1663
+ ERR
1664
+ end
1665
+
1666
+ def assert_unification(selector, extension, unified, nested = true)
1667
+ # Do some trickery so the first law of extend doesn't get in our way.
1668
+ assert_extends(
1669
+ "%-a #{selector}",
1670
+ extension + " -a {@extend %-a}",
1671
+ unified.split(', ').map {|s| "-a #{s}"}.join(', '))
1672
+ end
1673
+
1674
+ def assert_specificity_equals(sel1, sel2)
1675
+ assert_specificity_gte(sel1, sel2)
1676
+ assert_specificity_gte(sel2, sel1)
1677
+ end
1678
+
1679
+ def assert_specificity_gte(sel1, sel2)
1680
+ assert_equal <<CSS, render(<<SCSS)
1681
+ #{sel1} .a {
1682
+ a: b; }
1683
+ CSS
1684
+ #{sel1} %-a {a: b}
1685
+ .a {@extend %-a}
1686
+ #{sel2}.a {@extend %-a}
1687
+ SCSS
1688
+ end
1689
+
1690
+ def render_unification(selector, extension)
1691
+ render_extends(
1692
+ "%-a #{selector}",
1693
+ extension + " -a {@extend %-a}")
1694
+ end
1695
+
1696
+ def assert_extends(selector, extension, result)
1697
+ assert_equal <<CSS, render_extends(selector, extension)
1698
+ #{result} {
1699
+ a: b; }
1700
+ CSS
1701
+ end
1702
+
1703
+ def assert_extends_to_nothing(selector, extension)
1704
+ assert_equal '', render_extends(selector, extension)
1705
+ end
1706
+
1707
+ def render_extends(selector, extension)
1708
+ render(<<SCSS)
1709
+ #{selector} {a: b}
1710
+ #{extension}
1711
+ SCSS
1712
+ end
1713
+
1714
+ def render(sass, options = {})
1715
+ options = {:syntax => :scss}.merge(options)
1716
+ munge_filename options
1717
+ Sass::Engine.new(sass, options).render
1718
+ end
1719
+ end