sass 3.2.0.alpha.104 → 3.2.0.alpha.236

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 (49) hide show
  1. data/REVISION +1 -1
  2. data/Rakefile +2 -2
  3. data/VERSION +1 -1
  4. data/lib/sass/engine.rb +8 -1
  5. data/lib/sass/exec.rb +2 -2
  6. data/lib/sass/plugin/compiler.rb +51 -43
  7. data/lib/sass/script/color.rb +4 -9
  8. data/lib/sass/script/funcall.rb +11 -2
  9. data/lib/sass/script/functions.rb +16 -30
  10. data/lib/sass/script/lexer.rb +7 -1
  11. data/lib/sass/script/list.rb +2 -2
  12. data/lib/sass/script/literal.rb +8 -0
  13. data/lib/sass/script/null.rb +34 -0
  14. data/lib/sass/script/operation.rb +4 -0
  15. data/lib/sass/script/parser.rb +4 -2
  16. data/lib/sass/scss/parser.rb +1 -5
  17. data/lib/sass/scss/rx.rb +1 -1
  18. data/lib/sass/tree/directive_node.rb +5 -0
  19. data/lib/sass/tree/media_node.rb +3 -0
  20. data/lib/sass/tree/prop_node.rb +7 -3
  21. data/lib/sass/tree/visitors/base.rb +1 -1
  22. data/lib/sass/tree/visitors/check_nesting.rb +21 -18
  23. data/lib/sass/tree/visitors/convert.rb +1 -0
  24. data/lib/sass/tree/visitors/perform.rb +3 -2
  25. data/lib/sass/tree/visitors/to_css.rb +1 -0
  26. data/lib/sass/util.rb +28 -0
  27. data/test/sass/conversion_test.rb +33 -0
  28. data/test/sass/engine_test.rb +118 -3
  29. data/test/sass/functions_test.rb +56 -24
  30. data/test/sass/script_test.rb +60 -4
  31. data/test/sass/scss/scss_test.rb +10 -0
  32. data/vendor/listen/CHANGELOG.md +19 -1
  33. data/vendor/listen/README.md +27 -12
  34. data/vendor/listen/lib/listen/adapter.rb +9 -1
  35. data/vendor/listen/lib/listen/adapters/linux.rb +18 -7
  36. data/vendor/listen/lib/listen/adapters/windows.rb +7 -8
  37. data/vendor/listen/lib/listen/directory_record.rb +108 -48
  38. data/vendor/listen/lib/listen/listener.rb +28 -11
  39. data/vendor/listen/lib/listen/multi_listener.rb +6 -6
  40. data/vendor/listen/lib/listen/version.rb +1 -1
  41. data/vendor/listen/spec/listen/adapters/linux_spec.rb +11 -0
  42. data/vendor/listen/spec/listen/directory_record_spec.rb +268 -41
  43. data/vendor/listen/spec/listen/listener_spec.rb +8 -4
  44. data/vendor/listen/spec/listen/multi_listener_spec.rb +9 -4
  45. data/vendor/listen/spec/support/adapter_helper.rb +178 -0
  46. data/vendor/listen/spec/support/directory_record_helper.rb +24 -4
  47. data/vendor/listen/spec/support/listeners_helper.rb +11 -0
  48. metadata +11 -11
  49. data/lib/sass/plugin/listener.rb +0 -61
@@ -381,7 +381,9 @@ RUBY
381
381
 
382
382
  other_args, other_keywords = assert_expr(type)
383
383
  if keywords
384
- if other_keywords[name.underscored_name]
384
+ if !other_args.empty?
385
+ raise SyntaxError.new("Positional arguments must come before keyword arguments")
386
+ elsif other_keywords[name.underscored_name]
385
387
  raise SyntaxError.new("Keyword argument \"#{name.to_sass}\" passed more than once")
386
388
  end
387
389
  return other_args, keywords.merge(other_keywords)
@@ -453,7 +455,7 @@ RUBY
453
455
  end
454
456
 
455
457
  def literal
456
- (t = try_tok(:color, :bool)) && (return t.value)
458
+ (t = try_tok(:color, :bool, :null)) && (return t.value)
457
459
  end
458
460
 
459
461
  # It would be possible to have unified #assert and #try methods,
@@ -721,11 +721,7 @@ module Sass
721
721
  tok(SUBSTRINGMATCH)
722
722
  @expected = "identifier or string"
723
723
  ss
724
- if val = tok(IDENT)
725
- val = [val]
726
- else
727
- val = expr!(:interp_string)
728
- end
724
+ val = interp_ident || expr!(:interp_string)
729
725
  ss
730
726
  end
731
727
  tok(/\]/)
@@ -130,7 +130,7 @@ module Sass
130
130
  # about 50 characters. This mitigates the problem of exponential parsing
131
131
  # time when a value has a long string of valid, parsable content followed
132
132
  # by something invalid.
133
- STATIC_VALUE = /(-?#{NMSTART}|#{STRING_NOINTERP}|[ \t](?!%)|#[a-f0-9]|[,%]|#{NUM}|\!important){0,50}([;}])/i
133
+ STATIC_VALUE = /(-?#{NMSTART}|#{STRING_NOINTERP}|[ \t](?!%)|#[a-f0-9]|[,%]|#{NUM}|\!important){1,50}([;}])/i
134
134
  STATIC_SELECTOR = /(#{NMCHAR}|[ \t]|[,>+*]|[:#.]#{NMSTART}){0,50}([{])/i
135
135
  end
136
136
  end
@@ -33,5 +33,10 @@ module Sass::Tree
33
33
  node.resolved_value = value
34
34
  node
35
35
  end
36
+
37
+ # @return [String] The name of the directive, including `@`.
38
+ def name
39
+ value.first.gsub(/ .*$/, '')
40
+ end
36
41
  end
37
42
  end
@@ -27,6 +27,9 @@ module Sass::Tree
27
27
  # @see DirectiveNode#value
28
28
  def value; raise NotImplementedError; end
29
29
 
30
+ # @see DirectiveNode#name
31
+ def name; '@media'; end
32
+
30
33
  # @see DirectiveNode#resolved_value
31
34
  def resolved_value
32
35
  @resolved_value ||= "@media #{query.to_css}"
@@ -92,15 +92,19 @@ module Sass::Tree
92
92
  "#{initial}#{name}#{mid} #{self.class.val_to_sass(value, opts)}".rstrip
93
93
  end
94
94
 
95
+ # A property node is invisible if its value is empty.
96
+ #
97
+ # @return [Boolean]
98
+ def invisible?
99
+ resolved_value.empty?
100
+ end
101
+
95
102
  private
96
103
 
97
104
  def check!
98
105
  if @options[:property_syntax] && @options[:property_syntax] != @prop_syntax
99
106
  raise Sass::SyntaxError.new(
100
107
  "Illegal property syntax: can't use #{@prop_syntax} syntax when :property_syntax => #{@options[:property_syntax].inspect} is set.")
101
- elsif resolved_value.empty?
102
- raise Sass::SyntaxError.new("Invalid property: #{declaration.dump} (no value)." +
103
- pseudo_class_selector_message)
104
108
  end
105
109
  end
106
110
 
@@ -33,7 +33,7 @@ module Sass::Tree::Visitors
33
33
  # @return [Object] The return value of the `visit_*` method for this node.
34
34
  def visit(node)
35
35
  method = "visit_#{node_name node}"
36
- if self.respond_to?(method)
36
+ if self.respond_to?(method, true)
37
37
  self.send(method, node) {visit_children(node)}
38
38
  else
39
39
  visit_children(node)
@@ -2,13 +2,14 @@
2
2
  class Sass::Tree::Visitors::CheckNesting < Sass::Tree::Visitors::Base
3
3
  protected
4
4
 
5
+ def initialize
6
+ @parents = []
7
+ end
8
+
5
9
  def visit(node)
6
- if error = (@parent && (
7
- try_send("invalid_#{node_name @parent}_child?", @parent, node) ||
8
- try_send("invalid_#{node_name node}_parent?", @parent, node))) ||
9
- (@real_parent && (
10
- try_send("invalid_#{node_name @real_parent}_real_child?", @real_parent, node) ||
11
- try_send("invalid_#{node_name node}_real_parent?", @real_parent, node)))
10
+ if error = @parent && (
11
+ try_send("invalid_#{node_name @parent}_child?", @parent, node) ||
12
+ try_send("invalid_#{node_name node}_parent?", @parent, node))
12
13
  raise Sass::SyntaxError.new(error)
13
14
  end
14
15
  super
@@ -23,11 +24,11 @@ class Sass::Tree::Visitors::CheckNesting < Sass::Tree::Visitors::Base
23
24
  def visit_children(parent)
24
25
  old_parent = @parent
25
26
  @parent = parent unless is_any_of?(parent, SCRIPT_NODES)
26
- old_real_parent, @real_parent = @real_parent, parent
27
+ @parents.push parent
27
28
  super
28
29
  ensure
29
30
  @parent = old_parent
30
- @real_parent = old_real_parent
31
+ @parents.pop
31
32
  end
32
33
 
33
34
  def visit_root(node)
@@ -68,7 +69,15 @@ class Sass::Tree::Visitors::CheckNesting < Sass::Tree::Visitors::Base
68
69
  VALID_EXTEND_PARENTS = [Sass::Tree::RuleNode, Sass::Tree::MixinDefNode, Sass::Tree::MixinNode]
69
70
  def invalid_extend_parent?(parent, child)
70
71
  unless is_any_of?(parent, VALID_EXTEND_PARENTS)
71
- "Extend directives may only be used within rules."
72
+ return "Extend directives may only be used within rules."
73
+ end
74
+
75
+ if directive = @parents.find {|p| p.is_a?(Sass::Tree::DirectiveNode)}
76
+ return <<ERR.rstrip
77
+ @extend may not be used within directives (e.g. #{directive.name}).
78
+
79
+ This will only work once @extend is supported natively in the browser.
80
+ ERR
72
81
  end
73
82
  end
74
83
 
@@ -86,12 +95,10 @@ class Sass::Tree::Visitors::CheckNesting < Sass::Tree::Visitors::Base
86
95
  end
87
96
  end
88
97
 
89
- VALID_IMPORT_PARENTS = [
90
- Sass::Tree::IfNode, Sass::Tree::ForNode, Sass::Tree::WhileNode,
91
- Sass::Tree::EachNode, Sass::Tree::MixinDefNode, Sass::Tree::MixinNode
92
- ]
98
+ INVALID_IMPORT_PARENTS = CONTROL_NODES +
99
+ [Sass::Tree::MixinDefNode, Sass::Tree::MixinNode]
93
100
  def invalid_import_parent?(parent, child)
94
- if is_any_of?(@real_parent, VALID_IMPORT_PARENTS)
101
+ unless (@parents.map {|p| p.class} & INVALID_IMPORT_PARENTS).empty?
95
102
  return "Import directives may not be used within control directives or mixins."
96
103
  end
97
104
  return if parent.is_a?(Sass::Tree::RootNode)
@@ -106,10 +113,6 @@ class Sass::Tree::Visitors::CheckNesting < Sass::Tree::Visitors::Base
106
113
  raise e
107
114
  end
108
115
 
109
- def invalid_import_real_parent?(parent, child)
110
-
111
- end
112
-
113
116
  def invalid_mixindef_parent?(parent, child)
114
117
  "Mixins may only be defined at the root of a document." unless parent.is_a?(Sass::Tree::RootNode)
115
118
  end
@@ -131,6 +131,7 @@ class Sass::Tree::Visitors::Convert < Sass::Tree::Visitors::Base
131
131
  elsif node.expr; "else if"
132
132
  else; "else"
133
133
  end
134
+ @is_else = false
134
135
  str = "#{tab_str}@#{name}"
135
136
  str << " #{node.expr.to_sass(@options)}" if node.expr
136
137
  str << yield
@@ -198,7 +198,7 @@ END
198
198
  elsif default
199
199
  default.perform(env)
200
200
  end)
201
- raise Sass::SyntaxError.new("Mixin #{node.name} is missing parameter #{var.inspect}.") unless env.var(var.name)
201
+ raise Sass::SyntaxError.new("Mixin #{node.name} is missing argument #{var.inspect}.") unless env.var(var.name)
202
202
  env
203
203
  end
204
204
  environment.caller = Sass::Environment.new(@environment)
@@ -261,7 +261,8 @@ END
261
261
 
262
262
  # Loads the new variable value into the environment.
263
263
  def visit_variable(node)
264
- return [] if node.guarded && !@environment.var(node.name).nil?
264
+ var = @environment.var(node.name)
265
+ return [] if node.guarded && var && !var.null?
265
266
  val = node.expr.perform(@environment)
266
267
  @environment.set_var(node.name, val)
267
268
  []
@@ -122,6 +122,7 @@ class Sass::Tree::Visitors::ToCss < Sass::Tree::Visitors::Base
122
122
  end
123
123
 
124
124
  def visit_prop(node)
125
+ return if node.resolved_value.empty?
125
126
  tab_str = ' ' * (@tabs + node.tabs)
126
127
  if node.style == :compressed
127
128
  "#{tab_str}#{node.resolved_name}:#{node.resolved_value}"
@@ -256,6 +256,26 @@ module Sass
256
256
  arr
257
257
  end
258
258
 
259
+ # Asserts that `value` falls within `range` (inclusive), leaving
260
+ # room for slight floating-point errors.
261
+ #
262
+ # @param name [String] The name of the value. Used in the error message.
263
+ # @param range [Range] The allowed range of values.
264
+ # @param value [Numeric, Sass::Script::Number] The value to check.
265
+ # @param unit [String] The unit of the value. Used in error reporting.
266
+ # @return [Numeric] `value` adjusted to fall within range, if it
267
+ # was outside by a floating-point margin.
268
+ def check_range(name, range, value, unit='')
269
+ grace = (-0.00001..0.00001)
270
+ str = value.to_s
271
+ value = value.value if value.is_a?(Sass::Script::Number)
272
+ return value if range.include?(value)
273
+ return range.first if grace.include?(value - range.first)
274
+ return range.last if grace.include?(value - range.last)
275
+ raise ArgumentError.new(
276
+ "#{name} #{str} must be between #{range.first}#{unit} and #{range.last}#{unit}")
277
+ end
278
+
259
279
  # Returns information about the caller of the previous method.
260
280
  #
261
281
  # @param entry [String] An entry in the `#caller` list, or a similarly formatted string
@@ -422,6 +442,14 @@ module Sass
422
442
  RUBY_ENGINE == "ironruby"
423
443
  end
424
444
 
445
+ # Like `Dir.glob`, but works with backslash-separated paths on Windows.
446
+ #
447
+ # @param path [String]
448
+ def glob(path)
449
+ path = path.gsub('\\', '/') if windows?
450
+ Dir.glob(path)
451
+ end
452
+
425
453
  ## Cross-Ruby-Version Compatibility
426
454
 
427
455
  # Whether or not this is running under Ruby 1.8 or lower.
@@ -1525,6 +1525,39 @@ $foo: ();
1525
1525
  SCSS
1526
1526
  end
1527
1527
 
1528
+ def test_nested_if_statements
1529
+ assert_renders(<<SASS, <<SCSS)
1530
+ @if $foo
1531
+ one
1532
+ a: b
1533
+ @else
1534
+ @if $bar
1535
+ two
1536
+ a: b
1537
+ @else
1538
+ three
1539
+ a: b
1540
+ SASS
1541
+ @if $foo {
1542
+ one {
1543
+ a: b;
1544
+ }
1545
+ }
1546
+ @else {
1547
+ @if $bar {
1548
+ two {
1549
+ a: b;
1550
+ }
1551
+ }
1552
+ @else {
1553
+ three {
1554
+ a: b;
1555
+ }
1556
+ }
1557
+ }
1558
+ SCSS
1559
+ end
1560
+
1528
1561
  private
1529
1562
 
1530
1563
  def assert_sass_to_sass(sass, options = {})
@@ -64,7 +64,9 @@ MSG
64
64
  "@import templates/basic\n foo" => "Illegal nesting: Nothing may be nested beneath import directives.",
65
65
  "foo\n @import foo.css" => "CSS import directives may only be used at the root of a document.",
66
66
  "@if true\n @import foo" => "Import directives may not be used within control directives or mixins.",
67
+ "@if true\n .foo\n @import foo" => "Import directives may not be used within control directives or mixins.",
67
68
  "@mixin foo\n @import foo" => "Import directives may not be used within control directives or mixins.",
69
+ "@mixin foo\n .foo\n @import foo" => "Import directives may not be used within control directives or mixins.",
68
70
  "@import foo;" => "Invalid @import: expected end of line, was \";\".",
69
71
  '$foo: "bar" "baz" !' => %Q{Invalid CSS after ""bar" "baz" ": expected expression (e.g. 1px, bold), was "!"},
70
72
  '$foo: "bar" "baz" $' => %Q{Invalid CSS after ""bar" "baz" ": expected expression (e.g. 1px, bold), was "$"}, #'
@@ -92,7 +94,7 @@ MSG
92
94
  "=a($b: 1, $c)" => "Required argument $c must come before any optional arguments.",
93
95
  "=a($b: 1)\n a: $b\ndiv\n +a(1,2)" => "Mixin a takes 1 argument but 2 were passed.",
94
96
  "=a($b: 1)\n a: $b\ndiv\n +a(1,$c: 3)" => "Mixin a doesn't have an argument named $c",
95
- "=a($b)\n a: $b\ndiv\n +a" => "Mixin a is missing parameter $b.",
97
+ "=a($b)\n a: $b\ndiv\n +a" => "Mixin a is missing argument $b.",
96
98
  "@function foo()\n 1 + 2" => "Functions can only contain variable declarations and control directives.",
97
99
  "@function foo()\n foo: bar" => "Functions can only contain variable declarations and control directives.",
98
100
  "@function foo()\n foo: bar\n @return 3" => ["Functions can only contain variable declarations and control directives.", 2],
@@ -104,7 +106,7 @@ MSG
104
106
  "@function foo()\n @return" => 'Invalid @return: expected expression.',
105
107
  "@function foo()\n @return 1\n $var: val" => 'Illegal nesting: Nothing may be nested beneath return directives.',
106
108
  "foo\n @function bar()\n @return 1" => ['Functions may only be defined at the root of a document.', 2],
107
- "@function foo($a)\n @return 1\na\n b: foo()" => 'Function foo is missing parameter $a.',
109
+ "@function foo($a)\n @return 1\na\n b: foo()" => 'Function foo is missing argument $a',
108
110
  "@function foo()\n @return 1\na\n b: foo(2)" => 'Wrong number of arguments (1 for 0) for `foo\'',
109
111
  "@function foo()\n @return 1\na\n b: foo($a: 1)" => "Function foo doesn't have an argument named $a",
110
112
  "@function foo()\n @return 1\na\n b: foo($a: 1, $b: 2)" => "Function foo doesn't have the following arguments: $a, $b",
@@ -144,6 +146,8 @@ MSG
144
146
  "$var: true\n@while $var\n @extend .bar\n $var: false" => ["Extend directives may only be used within rules.", 3],
145
147
  "@for $i from 0 to 1\n @extend .bar" => ["Extend directives may only be used within rules.", 2],
146
148
  "@mixin foo\n @extend .bar\n@include foo" => ["Extend directives may only be used within rules.", 2],
149
+ "@media screen\n .bar\n @extend .foo" => "@extend may not be used within directives (e.g. @media).\n\nThis will only work once @extend is supported natively in the browser.",
150
+ "@flooblehoof\n .bar\n @extend .foo" => "@extend may not be used within directives (e.g. @flooblehoof).\n\nThis will only work once @extend is supported natively in the browser.",
147
151
  "foo\n &a\n b: c" => ["Invalid CSS after \"&\": expected \"{\", was \"a\"\n\n\"a\" may only be used at the beginning of a selector.", 2],
148
152
  "foo\n &1\n b: c" => ["Invalid CSS after \"&\": expected \"{\", was \"1\"\n\n\"1\" may only be used at the beginning of a selector.", 2],
149
153
  "foo %\n a: b" => ['Invalid CSS after "foo %": expected placeholder name, was ""', 1],
@@ -1011,7 +1015,7 @@ SASS
1011
1015
 
1012
1016
  def test_debug_info_without_filename
1013
1017
  assert_equal(<<CSS, Sass::Engine.new(<<SASS, :debug_info => true).render)
1014
- @media -sass-debug-info{filename{font-family:}line{font-family:\\000031}}
1018
+ @media -sass-debug-info{filename{}line{font-family:\\000031}}
1015
1019
  foo {
1016
1020
  a: b; }
1017
1021
  CSS
@@ -1100,6 +1104,7 @@ SASS
1100
1104
  def test_guarded_assign
1101
1105
  assert_equal("foo {\n a: b; }\n", render(%Q{$foo: b\n$foo: c !default\nfoo\n a: $foo}))
1102
1106
  assert_equal("foo {\n a: b; }\n", render(%Q{$foo: b !default\nfoo\n a: $foo}))
1107
+ assert_equal("foo {\n a: b; }\n", render(%Q{$foo: null\n$foo: b !default\nfoo\n a: $foo}))
1103
1108
  end
1104
1109
 
1105
1110
  def test_mixins
@@ -1185,6 +1190,35 @@ two
1185
1190
  +foo(#fff, 2px)
1186
1191
  three
1187
1192
  +foo(#fff, 2px, 3px)
1193
+ SASS
1194
+ assert_equal(<<CSS, render(<<SASS))
1195
+ one {
1196
+ color: white;
1197
+ padding: 1px;
1198
+ margin: 4px; }
1199
+
1200
+ two {
1201
+ color: white;
1202
+ padding: 2px;
1203
+ margin: 5px; }
1204
+
1205
+ three {
1206
+ color: white;
1207
+ padding: 2px;
1208
+ margin: 3px; }
1209
+ CSS
1210
+ $a: 5px
1211
+ =foo($a, $b: 1px, $c: null)
1212
+ $c: 3px + $b !default
1213
+ color: $a
1214
+ padding: $b
1215
+ margin: $c
1216
+ one
1217
+ +foo(#fff)
1218
+ two
1219
+ +foo(#fff, 2px)
1220
+ three
1221
+ +foo(#fff, 2px, 3px)
1188
1222
  SASS
1189
1223
  end
1190
1224
 
@@ -1271,6 +1305,58 @@ bar
1271
1305
  SASS
1272
1306
  end
1273
1307
 
1308
+ def test_function_with_missing_argument
1309
+ render(<<SASS)
1310
+ @function plus($var1, $var2)
1311
+ @return $var1 + $var2
1312
+
1313
+ bar
1314
+ a: plus($var2: bar)
1315
+ SASS
1316
+ flunk("Expected exception")
1317
+ rescue Sass::SyntaxError => e
1318
+ assert_equal("Function plus is missing argument $var1", e.message)
1319
+ end
1320
+
1321
+ def test_function_with_extra_argument
1322
+ render(<<SASS)
1323
+ @function plus($var1, $var2)
1324
+ @return $var1 + $var2
1325
+
1326
+ bar
1327
+ a: plus($var1: foo, $var2: bar, $var3: baz)
1328
+ SASS
1329
+ flunk("Expected exception")
1330
+ rescue Sass::SyntaxError => e
1331
+ assert_equal("Function plus doesn't have an argument named $var3", e.message)
1332
+ end
1333
+
1334
+ def test_function_with_positional_and_keyword_argument
1335
+ render(<<SASS)
1336
+ @function plus($var1, $var2)
1337
+ @return $var1 + $var2
1338
+
1339
+ bar
1340
+ a: plus(foo, bar, $var2: baz)
1341
+ SASS
1342
+ flunk("Expected exception")
1343
+ rescue Sass::SyntaxError => e
1344
+ assert_equal("Function plus was passed argument $var2 both by position and by name", e.message)
1345
+ end
1346
+
1347
+ def test_function_with_keyword_before_positional_argument
1348
+ render(<<SASS)
1349
+ @function plus($var1, $var2)
1350
+ @return $var1 + $var2
1351
+
1352
+ bar
1353
+ a: plus($var2: foo, bar)
1354
+ SASS
1355
+ flunk("Expected exception")
1356
+ rescue Sass::SyntaxError => e
1357
+ assert_equal("Positional arguments must come before keyword arguments", e.message)
1358
+ end
1359
+
1274
1360
  def test_function_with_if
1275
1361
  assert_equal(<<CSS, render(<<SASS))
1276
1362
  bar {
@@ -1341,6 +1427,15 @@ SASS
1341
1427
  def test_if_directive
1342
1428
  assert_equal("a {\n b: 1; }\n", render(<<SASS))
1343
1429
  $var: true
1430
+ a
1431
+ @if $var
1432
+ b: 1
1433
+ @if not $var
1434
+ b: 2
1435
+ SASS
1436
+
1437
+ assert_equal("a {\n b: 2; }\n", render(<<SASS))
1438
+ $var: null
1344
1439
  a
1345
1440
  @if $var
1346
1441
  b: 1
@@ -1743,6 +1838,26 @@ This selector doesn't have any properties and will not be rendered.
1743
1838
  END
1744
1839
  end
1745
1840
 
1841
+ def test_nonprinting_empty_property
1842
+ assert_equal(<<CSS, render(<<SASS))
1843
+ a {
1844
+ c: "";
1845
+ e: f; }
1846
+ CSS
1847
+ $null-value: null
1848
+ $empty-string: ''
1849
+ $empty-list: (null)
1850
+ a
1851
+ b: $null-value
1852
+ c: $empty-string
1853
+ d: $empty-list
1854
+ e: f
1855
+
1856
+ g
1857
+ h: null
1858
+ SASS
1859
+ end
1860
+
1746
1861
  def test_root_level_pseudo_class_with_new_properties
1747
1862
  assert_equal(<<CSS, render(<<SASS, :property_syntax => :new))
1748
1863
  :focus {