diff --git a/lib/liquid/condition.rb b/lib/liquid/condition.rb index a4096551..eb674ced 100644 --- a/lib/liquid/condition.rb +++ b/lib/liquid/condition.rb @@ -160,10 +160,9 @@ module Liquid end # Implement empty? semantics + # Note: nil is NOT empty (but IS blank). empty? checks if a collection has zero elements. def liquid_empty?(value) case value - when NilClass - true when String, Array, Hash value.empty? else diff --git a/lib/liquid/standardfilters.rb b/lib/liquid/standardfilters.rb index d17a699c..24e135d9 100644 --- a/lib/liquid/standardfilters.rb +++ b/lib/liquid/standardfilters.rb @@ -768,7 +768,8 @@ module Liquid # @liquid_syntax array | first # @liquid_return [untyped] def first(array) - return array[0] if array.is_a?(String) + # ActiveSupport returns "" for empty strings, not nil + return array[0] || "" if array.is_a?(String) array.first if array.respond_to?(:first) end @@ -780,7 +781,8 @@ module Liquid # @liquid_syntax array | last # @liquid_return [untyped] def last(array) - return array[-1] if array.is_a?(String) + # ActiveSupport returns "" for empty strings, not nil + return array[-1] || "" if array.is_a?(String) array.last if array.respond_to?(:last) end diff --git a/lib/liquid/variable_lookup.rb b/lib/liquid/variable_lookup.rb index bc9bbd97..4fba2a65 100644 --- a/lib/liquid/variable_lookup.rb +++ b/lib/liquid/variable_lookup.rb @@ -71,8 +71,9 @@ module Liquid object = object.send(key).to_liquid # Handle string first/last like ActiveSupport does (returns first/last character) + # ActiveSupport returns "" for empty strings, not nil elsif lookup_command?(i) && object.is_a?(String) && (key == "first" || key == "last") - object = key == "first" ? object[0] : object[-1] + object = key == "first" ? (object[0] || "") : (object[-1] || "") # No key was present with the desired value and it wasn't one of the directly supported # keywords either. The only thing we got left is to return nil or diff --git a/test/integration/standard_filter_test.rb b/test/integration/standard_filter_test.rb index f81af489..ef5f1549 100644 --- a/test/integration/standard_filter_test.rb +++ b/test/integration/standard_filter_test.rb @@ -635,10 +635,12 @@ class StandardFiltersTest < Minitest::Test # This enables template patterns like: # {{ product.title | first }} => "S" (for "Snowboard") # {{ customer.name | last }} => "h" (for "Smith") + # + # Note: ActiveSupport returns "" for empty strings, not nil. assert_equal('f', @filters.first('foo')) assert_equal('o', @filters.last('foo')) - assert_nil(@filters.first('')) - assert_nil(@filters.last('')) + assert_equal('', @filters.first('')) + assert_equal('', @filters.last('')) end def test_first_last_on_unicode_strings diff --git a/test/unit/condition_unit_test.rb b/test/unit/condition_unit_test.rb index 2ac83275..d7f35f60 100644 --- a/test/unit/condition_unit_test.rb +++ b/test/unit/condition_unit_test.rb @@ -353,6 +353,16 @@ class ConditionUnitTest < Minitest::Test assert_evaluates_true(VariableLookup.new('empty_hash'), '==', empty_literal) end + def test_nil_is_not_empty + # nil is NOT empty - empty? checks if a collection has zero elements. + # nil is not a collection, so it cannot be empty. + # This differs from blank: nil IS blank, but nil is NOT empty. + @context['nil_value'] = nil + empty_literal = Condition.class_variable_get(:@@method_literals)['empty'] + + assert_evaluates_false(VariableLookup.new('nil_value'), '==', empty_literal) + end + private def assert_evaluates_true(left, op, right)