diff --git a/.travis.yml b/.travis.yml index 31cec87..d317b2c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,9 @@ language: ruby rvm: - - 2.0 - 2.1 - 2.2 + - 2.3.3 - ruby-head - jruby-head # - rbx-2 @@ -19,6 +19,10 @@ matrix: allow_failures: - rvm: jruby-head +install: + - gem install rainbow -v 2.2.1 + - bundle install + script: "bundle exec rake" notifications: diff --git a/Gemfile b/Gemfile index 44e39e7..4229b05 100644 --- a/Gemfile +++ b/Gemfile @@ -3,12 +3,15 @@ source 'https://rubygems.org' gemspec gem 'stackprof', platforms: :mri_21 +group :benchmark, :test do + gem 'benchmark-ips' +end + group :test do gem 'spy', '0.4.1' - gem 'benchmark-ips' gem 'rubocop', '0.34.2' platform :mri do - gem 'liquid-c', github: 'Shopify/liquid-c', ref: '1fa04f1d3d4fdb5cc33bcc090334ab097ce2c10c' + gem 'liquid-c', github: 'Shopify/liquid-c', ref: 'bd53db95de3d44d631e7c5a267c3d934e66107dd' end end diff --git a/History.md b/History.md index 8e7a483..2dc8f3d 100644 --- a/History.md +++ b/History.md @@ -1,8 +1,10 @@ # Liquid Change Log -## 4.0.0 / not yet released / branch "master" +## 4.0.0 / 2016-12-14 / branch "4-0-stable" ### Changed +* Render an opaque internal error by default for non-Liquid::Error (#835) [Dylan Thacker-Smith] +* Ruby 2.0 support dropped (#832) [Dylan Thacker-Smith] * Add to_number Drop method to allow custom drops to work with number filters (#731) * Add strict_variables and strict_filters options to detect undefined references (#691) * Improve loop performance (#681) [Florian Weingarten] @@ -18,11 +20,13 @@ * Add concat filter to concatenate arrays (#429) [Diogo Beato] * Ruby 1.9 support dropped (#491) [Justin Li] * Liquid::Template.file_system's read_template_file method is no longer passed the context. (#441) [James Reid-Smith] -* Remove support for `liquid_methods` +* Remove `liquid_methods` (See https://github.com/Shopify/liquid/pull/568 for replacement) * Liquid::Template.register_filter raises when the module overrides registered public methods as private or protected (#705) [Gaurav Chande] ### Fixed + * Fix variable names being detected as an operator when starting with contains (#788) [Michael Angell] +* Fix include tag used with strict_variables (#828) [QuickPay] * Fix map filter when value is a Proc (#672) [Guillaume Malette] * Fix truncate filter when value is not a string (#672) [Guillaume Malette] * Fix behaviour of escape filter when input is nil (#665) [Tanel Jakobsoo] diff --git a/circle.yml b/circle.yml index fec889a..5e13430 100644 --- a/circle.yml +++ b/circle.yml @@ -1,3 +1,3 @@ machine: ruby: - version: ruby-2.0 + version: ruby-2.1 diff --git a/lib/liquid/block_body.rb b/lib/liquid/block_body.rb index 00811c1..6c9b680 100644 --- a/lib/liquid/block_body.rb +++ b/lib/liquid/block_body.rb @@ -96,7 +96,8 @@ module Liquid context.handle_error(e, token.line_number) output << nil rescue ::StandardError => e - output << context.handle_error(e, token.line_number) + line_number = token.is_a?(String) ? nil : token.line_number + output << context.handle_error(e, line_number) end end @@ -106,7 +107,7 @@ module Liquid private def render_node(node, context) - node_output = (node.respond_to?(:render) ? node.render(context) : node) + node_output = node.is_a?(String) ? node : node.render(context) node_output = node_output.is_a?(Array) ? node_output.join : node_output.to_s context.resource_limits.render_length += node_output.length diff --git a/lib/liquid/condition.rb b/lib/liquid/condition.rb index 9573f6b..7db9cf3 100644 --- a/lib/liquid/condition.rb +++ b/lib/liquid/condition.rb @@ -110,7 +110,7 @@ module Liquid if operation.respond_to?(:call) operation.call(self, left, right) - elsif left.respond_to?(operation) && right.respond_to?(operation) + elsif left.respond_to?(operation) && right.respond_to?(operation) && !left.is_a?(Hash) && !right.is_a?(Hash) begin left.send(operation, right) rescue ::ArgumentError => e diff --git a/lib/liquid/context.rb b/lib/liquid/context.rb index d41af49..566c5c6 100644 --- a/lib/liquid/context.rb +++ b/lib/liquid/context.rb @@ -13,7 +13,7 @@ module Liquid # context['bob'] #=> nil class Context class Context attr_reader :scopes, :errors, :registers, :environments, :resource_limits - attr_accessor :exception_handler, :template_name, :partial, :global_filter, :strict_variables, :strict_filters + attr_accessor :exception_renderer, :template_name, :partial, :global_filter, :strict_variables, :strict_filters def initialize(environments = {}, outer_scope = {}, registers = {}, rethrow_errors = false, resource_limits = nil) @environments = [environments].flatten @@ -27,8 +27,9 @@ module Liquid @this_stack_used = false + self.exception_renderer = Template.default_exception_renderer if rethrow_errors - self.exception_handler = ->(e) { raise } + self.exception_renderer = ->(e) { raise } end @interrupts = [] @@ -74,30 +75,11 @@ module Liquid end def handle_error(e, line_number = nil) - if e.is_a?(Liquid::Error) - e.template_name ||= template_name - e.line_number ||= line_number - end - - output = nil - - if exception_handler - result = exception_handler.call(e) - case result - when Exception - e = result - if e.is_a?(Liquid::Error) - e.template_name ||= template_name - e.line_number ||= line_number - end - when String - output = result - else - raise if result - end - end + e = internal_error unless e.is_a?(Liquid::Error) + e.template_name ||= template_name + e.line_number ||= line_number errors.push(e) - output || Liquid::Error.render(e) + exception_renderer.call(e).to_s end def invoke(method, *args) @@ -178,7 +160,7 @@ module Liquid end # Fetches an object starting at the local scope and then moving up the hierachy - def find_variable(key) + def find_variable(key, raise_on_not_found: true) # This was changed from find() to find_index() because this is a very hot # path and find_index() is optimized in MRI to reduce object allocation index = @scopes.find_index { |s| s.key?(key) } @@ -188,7 +170,7 @@ module Liquid if scope.nil? @environments.each do |e| - variable = lookup_and_evaluate(e, key) + variable = lookup_and_evaluate(e, key, raise_on_not_found: raise_on_not_found) unless variable.nil? scope = e break @@ -197,7 +179,7 @@ module Liquid end scope ||= @environments.last || @scopes.last - variable ||= lookup_and_evaluate(scope, key) + variable ||= lookup_and_evaluate(scope, key, raise_on_not_found: raise_on_not_found) variable = variable.to_liquid variable.context = self if variable.respond_to?(:context=) @@ -205,8 +187,8 @@ module Liquid variable end - def lookup_and_evaluate(obj, key) - if @strict_variables && obj.respond_to?(:key?) && !obj.key?(key) + def lookup_and_evaluate(obj, key, raise_on_not_found: true) + if @strict_variables && raise_on_not_found && obj.respond_to?(:key?) && !obj.key?(key) raise Liquid::UndefinedVariable, "undefined variable #{key}" end @@ -221,6 +203,13 @@ module Liquid private + def internal_error + # raise and catch to set backtrace and cause on exception + raise Liquid::InternalError, 'internal' + rescue Liquid::InternalError => exc + exc + end + def squash_instance_assigns_with_environments @scopes.last.each_key do |k| @environments.each do |env| diff --git a/lib/liquid/errors.rb b/lib/liquid/errors.rb index 6dcd05e..defa5ea 100644 --- a/lib/liquid/errors.rb +++ b/lib/liquid/errors.rb @@ -17,14 +17,6 @@ module Liquid str end - def self.render(e) - if e.is_a?(Liquid::Error) - e.to_s - else - "Liquid error: #{e}" - end - end - private def message_prefix @@ -60,4 +52,5 @@ module Liquid UndefinedDropMethod = Class.new(Error) UndefinedFilter = Class.new(Error) MethodOverrideError = Class.new(Error) + InternalError = Class.new(Error) end diff --git a/lib/liquid/locales/en.yml b/lib/liquid/locales/en.yml index e69eb3e..9a259bf 100644 --- a/lib/liquid/locales/en.yml +++ b/lib/liquid/locales/en.yml @@ -22,3 +22,5 @@ tag_never_closed: "'%{block_name}' tag was never closed" meta_syntax_error: "Liquid syntax error: #{e.message}" table_row: "Syntax Error in 'table_row loop' - Valid syntax: table_row [item] in [collection] cols=3" + argument: + include: "Argument error in tag 'include' - Illegal template name" diff --git a/lib/liquid/standardfilters.rb b/lib/liquid/standardfilters.rb index 90dfdd3..48056f3 100644 --- a/lib/liquid/standardfilters.rb +++ b/lib/liquid/standardfilters.rb @@ -65,9 +65,10 @@ module Liquid return if input.nil? input_str = input.to_s length = Utils.to_integer(length) - l = length - truncate_string.length + truncate_string_str = truncate_string.to_s + l = length - truncate_string_str.length l = 0 if l < 0 - input_str.length > length ? input_str[0...l] + truncate_string : input_str + input_str.length > length ? input_str[0...l] + truncate_string_str : input_str end def truncatewords(input, words = 15, truncate_string = "...".freeze) @@ -76,7 +77,7 @@ module Liquid words = Utils.to_integer(words) l = words - 1 l = 0 if l < 0 - wordlist.length > l ? wordlist[0..l].join(" ".freeze) + truncate_string : input + wordlist.length > l ? wordlist[0..l].join(" ".freeze) + truncate_string.to_s : input end # Split input string into an array of substrings separated by given pattern. @@ -85,7 +86,7 @@ module Liquid #
{{ post | split '//' | first }}
# def split(input, pattern) - input.to_s.split(pattern) + input.to_s.split(pattern.to_s) end def strip(input) @@ -124,7 +125,15 @@ module Liquid elsif ary.empty? # The next two cases assume a non-empty array. [] elsif ary.first.respond_to?(:[]) && !ary.first[property].nil? - ary.sort { |a, b| a[property] <=> b[property] } + ary.sort do |a, b| + a = a[property] + b = b[property] + if a && b + a <=> b + else + a ? -1 : 1 + end + end end end @@ -375,7 +384,7 @@ module Liquid end def join(glue) - to_a.join(glue) + to_a.join(glue.to_s) end def concat(args) diff --git a/lib/liquid/strainer.rb b/lib/liquid/strainer.rb index 441e335..76d56d2 100644 --- a/lib/liquid/strainer.rb +++ b/lib/liquid/strainer.rb @@ -27,7 +27,7 @@ module Liquid def self.add_filter(filter) raise ArgumentError, "Expected module but got: #{filter.class}" unless filter.is_a?(Module) - unless self.class.include?(filter) + unless self.include?(filter) invokable_non_public_methods = (filter.private_instance_methods + filter.protected_instance_methods).select { |m| invokable?(m) } if invokable_non_public_methods.any? raise MethodOverrideError, "Filter overrides registered public methods as non public: #{invokable_non_public_methods.join(', ')}" @@ -39,6 +39,7 @@ module Liquid end def self.global_filter(filter) + @@strainer_class_cache.clear @@global_strainer.add_filter(filter) end diff --git a/lib/liquid/tags/for.rb b/lib/liquid/tags/for.rb index 55011b3..5d5691f 100644 --- a/lib/liquid/tags/for.rb +++ b/lib/liquid/tags/for.rb @@ -23,7 +23,7 @@ module Liquid # {{ item.name }} # {% end %} # - # To reverse the for loop simply use {% for item in collection reversed %} + # To reverse the for loop simply use {% for item in collection reversed %} (note that the flag's spelling is different to the filter `reverse`) # # == Available variables: # diff --git a/lib/liquid/tags/include.rb b/lib/liquid/tags/include.rb index ba970cc..a800703 100644 --- a/lib/liquid/tags/include.rb +++ b/lib/liquid/tags/include.rb @@ -42,14 +42,15 @@ module Liquid def render(context) template_name = context.evaluate(@template_name_expr) - partial = load_cached_partial(template_name, context) + raise ArgumentError.new(options[:locale].t("errors.argument.include")) unless template_name + partial = load_cached_partial(template_name, context) context_variable_name = template_name.split('/'.freeze).last variable = if @variable_name_expr context.evaluate(@variable_name_expr) else - context.find_variable(template_name) + context.find_variable(template_name, raise_on_not_found: false) end old_template_name = context.template_name diff --git a/lib/liquid/template.rb b/lib/liquid/template.rb index 40985ed..31a67e4 100644 --- a/lib/liquid/template.rb +++ b/lib/liquid/template.rb @@ -69,6 +69,11 @@ module Liquid # :error raises an error when tainted output is used attr_writer :taint_mode + attr_accessor :default_exception_renderer + Template.default_exception_renderer = lambda do |exception| + exception + end + def file_system @@file_system end @@ -167,7 +172,7 @@ module Liquid c = args.shift if @rethrow_errors - c.exception_handler = ->(e) { raise } + c.exception_renderer = ->(e) { raise } end c @@ -241,7 +246,7 @@ module Liquid def apply_options_to_context(context, options) context.add_filters(options[:filters]) if options[:filters] context.global_filter = options[:global_filter] if options[:global_filter] - context.exception_handler = options[:exception_handler] if options[:exception_handler] + context.exception_renderer = options[:exception_renderer] if options[:exception_renderer] context.strict_variables = options[:strict_variables] if options[:strict_variables] context.strict_filters = options[:strict_filters] if options[:strict_filters] end diff --git a/lib/liquid/version.rb b/lib/liquid/version.rb index 823405a..af15e07 100644 --- a/lib/liquid/version.rb +++ b/lib/liquid/version.rb @@ -1,4 +1,4 @@ # encoding: utf-8 module Liquid - VERSION = "4.0.0.rc2" + VERSION = "4.0.0" end diff --git a/liquid.gemspec b/liquid.gemspec index 5b00a0d..3affbed 100644 --- a/liquid.gemspec +++ b/liquid.gemspec @@ -15,6 +15,7 @@ Gem::Specification.new do |s| s.license = "MIT" # s.description = "A secure, non-evaling end user template engine with aesthetic markup." + s.required_ruby_version = ">= 2.1.0" s.required_rubygems_version = ">= 1.3.7" s.test_files = Dir.glob("{test}/**/*") @@ -24,6 +25,6 @@ Gem::Specification.new do |s| s.require_path = "lib" - s.add_development_dependency 'rake' + s.add_development_dependency 'rake', '~> 11.3' s.add_development_dependency 'minitest' end diff --git a/performance/benchmark.rb b/performance/benchmark.rb index a6f8185..68c568c 100644 --- a/performance/benchmark.rb +++ b/performance/benchmark.rb @@ -5,7 +5,7 @@ Liquid::Template.error_mode = ARGV.first.to_sym if ARGV.first profiler = ThemeRunner.new Benchmark.ips do |x| - x.time = 60 + x.time = 10 x.warmup = 5 puts @@ -13,5 +13,6 @@ Benchmark.ips do |x| puts x.report("parse:") { profiler.compile } - x.report("parse & run:") { profiler.run } + x.report("render:") { profiler.render } + x.report("parse & render:") { profiler.run } end diff --git a/performance/theme_runner.rb b/performance/theme_runner.rb index 6ac7917..9f6a1fc 100644 --- a/performance/theme_runner.rb +++ b/performance/theme_runner.rb @@ -21,53 +21,100 @@ class ThemeRunner end end - # Load all templates into memory, do this now so that - # we don't profile IO. + # Initialize a new liquid ThemeRunner instance + # Will load all templates into memory, do this now so that we don't profile IO. def initialize @tests = Dir[__dir__ + '/tests/**/*.liquid'].collect do |test| next if File.basename(test) == 'theme.liquid' theme_path = File.dirname(test) + '/theme.liquid' - - [File.read(test), (File.file?(theme_path) ? File.read(theme_path) : nil), test] + { + liquid: File.read(test), + layout: (File.file?(theme_path) ? File.read(theme_path) : nil), + template_name: test + } end.compact + + compile_all_tests end + # `compile` will test just the compilation portion of liquid without any templates def compile - # Dup assigns because will make some changes to them - - @tests.each do |liquid, layout, template_name| - tmpl = Liquid::Template.new - tmpl.parse(liquid) - tmpl = Liquid::Template.new - tmpl.parse(layout) + @tests.each do |test_hash| + Liquid::Template.new.parse(test_hash[:liquid]) + Liquid::Template.new.parse(test_hash[:layout]) end end + # `run` is called to benchmark rendering and compiling at the same time def run - # Dup assigns because will make some changes to them - assigns = Database.tables.dup - - @tests.each do |liquid, layout, template_name| - # Compute page_tempalte outside of profiler run, uninteresting to profiler - page_template = File.basename(template_name, File.extname(template_name)) + each_test do |liquid, layout, assigns, page_template, template_name| compile_and_render(liquid, layout, assigns, page_template, template_name) end end + # `render` is called to benchmark just the render portion of liquid + def render + @compiled_tests.each do |test| + tmpl = test[:tmpl] + assigns = test[:assigns] + layout = test[:layout] + + if layout + assigns['content_for_layout'] = tmpl.render!(assigns) + layout.render!(assigns) + else + tmpl.render!(assigns) + end + end + end + + private + def compile_and_render(template, layout, assigns, page_template, template_file) + compiled_test = compile_test(template, layout, assigns, page_template, template_file) + assigns['content_for_layout'] = compiled_test[:tmpl].render!(assigns) + compiled_test[:layout].render!(assigns) if layout + end + + def compile_all_tests + @compiled_tests = [] + each_test do |liquid, layout, assigns, page_template, template_name| + @compiled_tests << compile_test(liquid, layout, assigns, page_template, template_name) + end + @compiled_tests + end + + def compile_test(template, layout, assigns, page_template, template_file) + tmpl = init_template(page_template, template_file) + parsed_template = tmpl.parse(template).dup + + if layout + parsed_layout = tmpl.parse(layout) + { tmpl: parsed_template, assigns: assigns, layout: parsed_layout } + else + { tmpl: parsed_template, assigns: assigns } + end + end + + # utility method with similar functionality needed in `compile_all_tests` and `run` + def each_test + # Dup assigns because will make some changes to them + assigns = Database.tables.dup + + @tests.each do |test_hash| + # Compute page_template outside of profiler run, uninteresting to profiler + page_template = File.basename(test_hash[:template_name], File.extname(test_hash[:template_name])) + yield(test_hash[:liquid], test_hash[:layout], assigns, page_template, test_hash[:template_name]) + end + end + + # set up a new Liquid::Template object for use in `compile_and_render` and `compile_test` + def init_template(page_template, template_file) tmpl = Liquid::Template.new tmpl.assigns['page_title'] = 'Page title' tmpl.assigns['template'] = page_template tmpl.registers[:file_system] = ThemeRunner::FileSystem.new(File.dirname(template_file)) - - content_for_layout = tmpl.parse(template).render!(assigns) - - if layout - assigns['content_for_layout'] = content_for_layout - tmpl.parse(layout).render!(assigns) - else - content_for_layout - end + tmpl end end diff --git a/test/integration/error_handling_test.rb b/test/integration/error_handling_test.rb index 95d65db..ba81861 100644 --- a/test/integration/error_handling_test.rb +++ b/test/integration/error_handling_test.rb @@ -202,22 +202,40 @@ class ErrorHandlingTest < Minitest::Test end end - def test_exception_handler_with_string_result - template = Liquid::Template.parse('This is an argument error: {{ errors.argument_error }}') - output = template.render({ 'errors' => ErrorDrop.new }, exception_handler: ->(e) { '' }) - assert_equal 'This is an argument error: ', output - assert_equal [ArgumentError], template.errors.map(&:class) - end - - class InternalError < Liquid::Error - end - - def test_exception_handler_with_exception_result + def test_default_exception_renderer_with_internal_error template = Liquid::Template.parse('This is a runtime error: {{ errors.runtime_error }}', line_numbers: true) - handler = ->(e) { e.is_a?(Liquid::Error) ? e : InternalError.new('internal') } - output = template.render({ 'errors' => ErrorDrop.new }, exception_handler: handler) + + output = template.render({ 'errors' => ErrorDrop.new }) + assert_equal 'This is a runtime error: Liquid error (line 1): internal', output - assert_equal [InternalError], template.errors.map(&:class) + assert_equal [Liquid::InternalError], template.errors.map(&:class) + end + + def test_setting_default_exception_renderer + old_exception_renderer = Liquid::Template.default_exception_renderer + exceptions = [] + Liquid::Template.default_exception_renderer = ->(e) { exceptions << e; '' } + template = Liquid::Template.parse('This is a runtime error: {{ errors.argument_error }}') + + output = template.render({ 'errors' => ErrorDrop.new }) + + assert_equal 'This is a runtime error: ', output + assert_equal [Liquid::ArgumentError], template.errors.map(&:class) + ensure + Liquid::Template.default_exception_renderer = old_exception_renderer if old_exception_renderer + end + + def test_exception_renderer_exposing_non_liquid_error + template = Liquid::Template.parse('This is a runtime error: {{ errors.runtime_error }}', line_numbers: true) + exceptions = [] + handler = ->(e) { exceptions << e; e.cause } + + output = template.render({ 'errors' => ErrorDrop.new }, exception_renderer: handler) + + assert_equal 'This is a runtime error: runtime error', output + assert_equal [Liquid::InternalError], exceptions.map(&:class) + assert_equal exceptions, template.errors + assert_equal '#', exceptions.first.cause.inspect end class TestFileSystem diff --git a/test/integration/standard_filter_test.rb b/test/integration/standard_filter_test.rb index a20e1c3..e607b76 100644 --- a/test/integration/standard_filter_test.rb +++ b/test/integration/standard_filter_test.rb @@ -115,15 +115,15 @@ class StandardFiltersTest < Minitest::Test assert_equal '...', @filters.truncate('1234567890', 0) assert_equal '1234567890', @filters.truncate('1234567890') assert_equal "测试...", @filters.truncate("测试测试测试测试", 5) + assert_equal '12341', @filters.truncate("1234567890", 5, 1) end def test_split assert_equal ['12', '34'], @filters.split('12~34', '~') assert_equal ['A? ', ' ,Z'], @filters.split('A? ~ ~ ~ ,Z', '~ ~ ~') assert_equal ['A?Z'], @filters.split('A?Z', '~') - # Regexp works although Liquid does not support. - assert_equal ['A', 'Z'], @filters.split('AxZ', /x/) assert_equal [], @filters.split(nil, ' ') + assert_equal ['A', 'Z'], @filters.split('A1Z', 1) end def test_escape @@ -154,6 +154,7 @@ class StandardFiltersTest < Minitest::Test assert_equal 'one two three', @filters.truncatewords('one two three') assert_equal 'Two small (13” x 5.5” x 10” high) baskets fit inside one large basket (13”...', @filters.truncatewords('Two small (13” x 5.5” x 10” high) baskets fit inside one large basket (13” x 16” x 10.5” high) with cover.', 15) assert_equal "测试测试测试测试", @filters.truncatewords('测试测试测试测试', 5) + assert_equal 'one two1', @filters.truncatewords("one two three", 2, 1) end def test_strip_html @@ -169,6 +170,7 @@ class StandardFiltersTest < Minitest::Test def test_join assert_equal '1 2 3 4', @filters.join([1, 2, 3, 4]) assert_equal '1 - 2 - 3 - 4', @filters.join([1, 2, 3, 4], ' - ') + assert_equal '1121314', @filters.join([1, 2, 3, 4], 1) end def test_sort @@ -176,6 +178,24 @@ class StandardFiltersTest < Minitest::Test assert_equal [{ "a" => 1 }, { "a" => 2 }, { "a" => 3 }, { "a" => 4 }], @filters.sort([{ "a" => 4 }, { "a" => 3 }, { "a" => 1 }, { "a" => 2 }], "a") end + def test_sort_when_property_is_sometimes_missing_puts_nils_last + input = [ + { "price" => 4, "handle" => "alpha" }, + { "handle" => "beta" }, + { "price" => 1, "handle" => "gamma" }, + { "handle" => "delta" }, + { "price" => 2, "handle" => "epsilon" } + ] + expectation = [ + { "price" => 1, "handle" => "gamma" }, + { "price" => 2, "handle" => "epsilon" }, + { "price" => 4, "handle" => "alpha" }, + { "handle" => "delta" }, + { "handle" => "beta" } + ] + assert_equal expectation, @filters.sort(input, "price") + end + def test_sort_empty_array assert_equal [], @filters.sort([], "a") end diff --git a/test/integration/tags/include_tag_test.rb b/test/integration/tags/include_tag_test.rb index 87dfe80..25af662 100644 --- a/test/integration/tags/include_tag_test.rb +++ b/test/integration/tags/include_tag_test.rb @@ -217,6 +217,17 @@ class IncludeTagTest < Minitest::Test end end + def test_render_raise_argument_error_when_template_is_undefined + assert_raises(Liquid::ArgumentError) do + template = Liquid::Template.parse('{% include undefined_variable %}') + template.render! + end + assert_raises(Liquid::ArgumentError) do + template = Liquid::Template.parse('{% include nil %}') + template.render! + end + end + def test_including_via_variable_value assert_template_result "from TestFileSystem", "{% assign page = 'pick_a_source' %}{% include page %}" @@ -224,4 +235,11 @@ class IncludeTagTest < Minitest::Test assert_template_result "Product: Draft 151cm ", "{% assign page = 'product' %}{% include page for foo %}", "foo" => { 'title' => 'Draft 151cm' } end + + def test_including_with_strict_variables + template = Liquid::Template.parse("{% include 'simple' %}", error_mode: :warn) + template.render(nil, strict_variables: true) + + assert_equal [], template.errors + end end # IncludeTagTest diff --git a/test/integration/template_test.rb b/test/integration/template_test.rb index 8ad616d..253b976 100644 --- a/test/integration/template_test.rb +++ b/test/integration/template_test.rb @@ -215,16 +215,20 @@ class TemplateTest < Minitest::Test assert_equal 'ruby error in drop', e.message end - def test_exception_handler_doesnt_reraise_if_it_returns_false + def test_exception_renderer_that_returns_string exception = nil - Template.parse("{{ 1 | divided_by: 0 }}").render({}, exception_handler: ->(e) { exception = e; false }) + handler = ->(e) { exception = e; '' } + + output = Template.parse("{{ 1 | divided_by: 0 }}").render({}, exception_renderer: handler) + assert exception.is_a?(Liquid::ZeroDivisionError) + assert_equal '', output end - def test_exception_handler_does_reraise_if_it_returns_true + def test_exception_renderer_that_raises exception = nil assert_raises(Liquid::ZeroDivisionError) do - Template.parse("{{ 1 | divided_by: 0 }}").render({}, exception_handler: ->(e) { exception = e; true }) + Template.parse("{{ 1 | divided_by: 0 }}").render({}, exception_renderer: ->(e) { exception = e; raise }) end assert exception.is_a?(Liquid::ZeroDivisionError) end diff --git a/test/unit/condition_unit_test.rb b/test/unit/condition_unit_test.rb index 8dfc788..6b0cc34 100644 --- a/test/unit/condition_unit_test.rb +++ b/test/unit/condition_unit_test.rb @@ -64,6 +64,14 @@ class ConditionUnitTest < Minitest::Test assert_evaluates_argument_error '1', '<=', 0 end + def test_hash_compare_backwards_compatibility + assert_equal nil, Condition.new({}, '>', 2).evaluate + assert_equal nil, Condition.new(2, '>', {}).evaluate + assert_equal false, Condition.new({}, '==', 2).evaluate + assert_equal true, Condition.new({ 'a' => 1 }, '==', { 'a' => 1 }).evaluate + assert_equal true, Condition.new({ 'a' => 2 }, 'contains', 'a').evaluate + end + def test_contains_works_on_arrays @context = Liquid::Context.new @context['array'] = [1, 2, 3, 4, 5] diff --git a/test/unit/strainer_unit_test.rb b/test/unit/strainer_unit_test.rb index 5cdf2e8..5ce2100 100644 --- a/test/unit/strainer_unit_test.rb +++ b/test/unit/strainer_unit_test.rb @@ -133,4 +133,32 @@ class StrainerUnitTest < Minitest::Test strainer.class.add_filter(PublicMethodOverrideFilter) assert strainer.class.filter_methods.include?('public_filter') end + + module LateAddedFilter + def late_added_filter(input) + "filtered" + end + end + + def test_global_filter_clears_cache + assert_equal 'input', Strainer.create(nil).invoke('late_added_filter', 'input') + Strainer.global_filter(LateAddedFilter) + assert_equal 'filtered', Strainer.create(nil).invoke('late_added_filter', 'input') + end + + def test_add_filter_does_not_include_already_included_module + mod = Module.new do + class << self + attr_accessor :include_count + def included(mod) + self.include_count += 1 + end + end + self.include_count = 0 + end + strainer = Context.new.strainer + strainer.class.add_filter(mod) + strainer.class.add_filter(mod) + assert_equal 1, mod.include_count + end end # StrainerTest