Compare commits

..
Author SHA1 Message Date
Andy Waite 965ea3cfb1 WIP 2022-03-14 12:03:47 -04:00
Thierry JoyalandGitHub 7357dcf185 Merge pull request #1536 from Shopify/flaky-profiler-test-v2
Add artificial execution time in profiler tests
2022-03-07 10:06:13 -05:00
Thierry JoyalandGitHub 68c3827ef2 Merge pull request #1525 from Shopify/standardfilter/fix-missing-context-on-iterations
[StandardFilter] Fix missing @context on iterations
2022-03-07 09:19:18 -05:00
Thierry Joyal 4af38bc549 [StandardFilter] Fix missing @context on iterations 2022-03-07 09:17:07 -05:00
Thierry Joyal df241abf70 Add artificial execution time in profiler tests 2022-03-07 08:50:03 -05:00
10f8337209 Test Ruby 3.1 in CI (#1533)
Co-authored-by: Dylan Thacker-Smith <[email protected]>
2022-03-04 13:23:04 -05:00
Thierry JoyalandGitHub 5ed0410a8b Merge pull request #1534 from Shopify/context-test-cleanup
Context test cleanup
2022-03-04 11:31:00 -05:00
Thierry Joyal 0f5220c391 ContextTest: Classes to use appropriate ancestor 2022-03-04 09:14:57 -05:00
Thierry Joyal 7a23f46fab ContextTest: Cleanup global variable assignments 2022-03-04 09:10:38 -05:00
Peter ZhuandGitHub 3f7edf00b9 Merge pull request #1531 from Shopify/pz-array-fetch-warning
Fix warning about block and default value
2022-03-02 16:25:23 -05:00
Thierry JoyalandGitHub b4a2a79e26 Merge pull request #1527 from Shopify/condition/receive-mandatory-context-argument
Condition#evaluate to receive mandatory context argument
2022-03-02 15:04:24 -05:00
Thierry Joyal 1d2bee1f60 Condition#evaluate to receive mandatory context argument 2022-03-02 14:35:31 -05:00
Peter Zhu 01e6eec97a Fix warning about block and default value
Ruby's Array#fetch accepts either a default value or a block, but not
both. If both are passed in, then it uses the block and outputs this
warning:

```
lib/liquid/static_registers.rb:34: warning: block supersedes default value argument
```
2022-03-02 14:33:12 -05:00
Jean Boussier fbdab19358 We're in 2022... 2022-03-02 18:27:25 +01:00
Thierry JoyalandGitHub ce85ac5d3d Merge pull request #1529 from Shopify/tests/standard-filters-with-context
StandardFiltersTest: Initialize following production code paths with context
2022-03-02 08:44:16 -05:00
Thierry Joyal c0ffee16a3 StandardFiltersTest: Initialize following production code paths with context 2022-03-01 16:01:00 +00:00
Jean Boussier a7eb33fa39 Release 5.2.0 2022-03-01 16:18:49 +01:00
Jean byroot BoussierandGitHub 1a85e98793 Merge pull request #1524 from Shopify/global-constant-cache
Eagerly cache global filters
2022-03-01 16:14:31 +01:00
Jean Boussier c588337aac Eagerly cache global filters
Including a module can cause Ruby's global constant cache to be busted
if the included module contain constants. So that's something you don't
want to happen at "runtime", otherwise it will severely degrade performance
and if you are using YJIT or MJIT most of the compiled code will be invalidated.

To limit the impact of this, we can pre-include the global filters,
as they're generally registered during boot, that limits the problem
to non-global filters.
2022-03-01 13:40:40 +01:00
Dylan Thacker-SmithandGitHub 97f7922457 Add missing changelog entry for PR #1518 (#1521) 2022-02-24 14:46:26 -05:00
0d83e64cfe Add replace_last and remove_last filters (#1422)
Co-authored-by: ADTC <[email protected]>
Co-authored-by: Dylan Thacker-Smith <[email protected]>
2022-02-24 14:02:15 -05:00
Dylan Thacker-SmithandGitHub 0d5e01ae98 Fix some internal errors in filters from invalid input. (#1476)
These fixes came from improving the corresponding test, so these might not
actually be causing problems in practice.
2022-02-24 09:17:37 -05:00
Charles-Philippe ClermontandGitHub 15eaa49e48 Merge pull request #1518 from Shopify/fix/kwarg-key-name-liquid-c-inconsistency
Fix kwarg parsing inconsistency with Liquid::C
2022-02-14 13:22:12 -05:00
Tobias LütkeandGitHub 91c54c579d Merge pull request #1477 from Watson1978/performance
Increase parsing performance
2022-02-14 12:25:19 -05:00
Charles-P. Clermont 1310c4978d Fix kwarg parsing inconsistency with Liquid::C
Liquid::C parses liquid filter arguments with dashes in them, Liquid does not.

For tags that accept kwargs and dumps them on the HTML tag, this is an important feature.

e.g. {{ ... | image_tag: loading: 'lazy', data-something: 'value!' }}

Without this change, Liquid would incorrectly parse the
`data-something` kwarg as a single argument and would skip over the
invalid characters.

See https://github.com/Shopify/theme-check/issues/539 for more context
2022-02-11 15:10:07 -05:00
shainaraskasandGitHub 3de1db3c3a Merge pull request #1509 from Shopify/1508-shopify-docs-link
Fix Shopify documentation link
2022-01-20 12:06:16 -05:00
Shaina Raskas 03522caaf8 fix Shopify documentation link 2022-01-20 09:40:48 -05:00
Shaina Raskas 7acea2a9c9 Revert "fix Shopify documentation link"
This reverts commit d8ef698539.
2022-01-20 09:37:26 -05:00
Shaina Raskas d8ef698539 fix Shopify documentation link 2022-01-20 09:27:00 -05:00
Watson ebdfdb80e5 Detect quoted string using String#{start_with?, end_with?} to reduce Regexp#=== calling 2021-09-26 04:30:49 +09:00
Watson 95e9fa5010 Use String#=~ and Regexp.last_match instead to retrieve the markup content
If the first value is only used obtained with String#scan,
it will increase the performance if replace with `String#=~` and `Regexp.last_match`.

### Environment
- MacBook Air (M1, 2020)
- macOS 12.0 beta 7
- Apple M1
- Ruby 3.0.2

### Test code
```ruby
require 'benchmark/ips'

WhitespaceControl           = '-'
VariableStart               = /\{\{/
VariableEnd                 = /\}\}/

ContentOfVariable   = /\A#{VariableStart}#{WhitespaceControl}?(.*?)#{WhitespaceControl}?#{VariableEnd}\z/om
token = "{{item.product.featured_image | product_img_url: 'thumb' }}"

Benchmark.ips do |x|
  x.report("String#scan")  { token.scan(ContentOfVariable) {|content| break }  }
  x.report("String#match") { m = token.match(ContentOfVariable); m[1] }
  x.report("String#=~")    { token =~ ContentOfVariable; Regexp.last_match(1) }

  x.compare!
end
```

### Result
```
Warming up --------------------------------------
         String#scan   135.724k i/100ms
        String#match   117.397k i/100ms
           String#=~   151.637k i/100ms
Calculating -------------------------------------
         String#scan      1.351M (± 0.8%) i/s -      6.786M in   5.021955s
        String#match      1.169M (± 1.3%) i/s -      5.870M in   5.020429s
           String#=~      1.520M (± 0.9%) i/s -      7.733M in   5.087427s

Comparison:
           String#=~:  1520250.9 i/s
         String#scan:  1351399.0 i/s - 1.12x  (± 0.00) slower
        String#match:  1169384.1 i/s - 1.30x  (± 0.00) slower
```
2021-09-26 04:12:53 +09:00
Anders SøgaardandGitHub db3999a008 Improve where filter tests (#1472) 2021-09-16 10:02:39 -04:00
Marc-André CournoyerandGitHub 10e2aa8d5b Merge pull request #1471 from Shopify/bump-5.1.0
Prep for 5.1.0 release
2021-09-15 15:15:13 -04:00
7c4114671b Remove to_liquid_value fixes in changelog
Co-authored-by: Dylan Thacker-Smith <[email protected]>
2021-09-13 10:53:46 -04:00
Marc-André Cournoyer b01de9d325 Prep for 5.1.0 release 2021-09-09 14:28:45 -04:00
Zac ClayandGitHub a5369c26a8 Add missing quote in comment (#1468) 2021-09-09 14:23:11 -04:00
Charles-Philippe ClermontandGitHub a03de8f9ea Merge pull request #1470 from Shopify/fix/range-parse-tree-visitor
Add ParseTreeVisitor to RangeLookup
2021-09-09 11:39:14 -04:00
Charles-P. Clermont e86fe27259 Fix lint 2021-09-09 11:22:39 -04:00
Charles-P. Clermont c8906d05b9 Add ParseTreeVisitor to RangeLookup 2021-09-09 11:22:39 -04:00
Michael GoandGitHub 50d1a2ffc9 Merge pull request #1458 from Shopify/use-to-liquid-value-with-conditions
use Utils.to_liquid_value on conditionals
2021-06-15 12:18:14 -03:00
Michael GoandGitHub aa8ce87b96 Merge pull request #1454 from Shopify/default-filter-with-to-liquid-value
utilize input's to_liquid_value on default filter
2021-06-14 15:43:56 -03:00
Peter ZhuandGitHub 698f5e0d96 Merge pull request #1456 from Shopify/pz-psych-4-unsafe-load
Fix benchmark for breaking change in Psych 4
2021-06-10 13:14:13 -04:00
Peter Zhu 996bfe0c82 Fix benchmark for breaking change in Psych 4
Psych 4 introduces a breaking change (ruby/psych#487) where
`Psych#load`/`Psych#load_file` now default to safe loading,
meaning that YAML references are not allowed anymore. This
commit changes the benchmark to use `Psych#unsafe_load_file`
when it's available.
2021-06-10 13:01:16 -04:00
Michael GoandGitHub be81c9ae5a Merge pull request #1457 from Shopify/fix-unless-to-use-to-liquid-value-helper
fix unless to use to_liquid_value helper with multiple conditions
2021-06-10 14:00:19 -03:00
Michael Go edd4d70aee fix unless to use to_liquid_value helper with multiple conditions 2021-06-10 13:28:17 -03:00
Michael Go ac66dbbafe utilize input's to_liquid_value on default filter 2021-06-08 15:20:53 -03:00
26 changed files with 365 additions and 139 deletions
+1 -1
View File
@@ -7,7 +7,7 @@ jobs:
matrix: matrix:
entry: entry:
- { ruby: 2.5, allowed-failure: false } # minimum supported - { ruby: 2.5, allowed-failure: false } # minimum supported
- { ruby: 3.0, allowed-failure: false } # latest - { ruby: 3.1, allowed-failure: false } # latest
- { ruby: ruby-head, allowed-failure: true } - { ruby: ruby-head, allowed-failure: true }
name: test (${{ matrix.entry.ruby }}) name: test (${{ matrix.entry.ruby }})
steps: steps:
+22 -1
View File
@@ -1,12 +1,33 @@
# Liquid Change Log # Liquid Change Log
## 5.0.2 (unreleased) ## 5.3.0 (unreleased)
### Fixes
* StandardFilter: Fix missing @context on iterations (#1525) [Thierry Joyal]
### Deprecation
* Condition#evaluate to require mandatory context argument in Liquid 6.0.0 (#1527) [Thierry Joyal]
## 5.2.0 2022-03-01
### Features
* Add `remove_last`, and `replace_last` filters (#1422) [Anders Hagbard]
* Eagerly cache global filters (#1524) [Jean Boussier]
### Fixes
* Fix some internal errors in filters from invalid input (#1476) [Dylan Thacker-Smith]
* Allow dash in filter kwarg name for consistency with Liquid::C (#1518) [CP Clermont]
## 5.1.0 / 2021-09-09
### Features ### Features
* Add `base64_encode`, `base64_decode`, `base64_url_safe_encode`, and `base64_url_safe_decode` filters (#1450) [Daniel Insley] * Add `base64_encode`, `base64_decode`, `base64_url_safe_encode`, and `base64_url_safe_decode` filters (#1450) [Daniel Insley]
* Introduce `to_liquid_value` in `Liquid::Drop` (#1441) [Michael Go]
### Fixes ### Fixes
* Fix support for using a String subclass for the liquid source (#1421) [Dylan Thacker-Smith] * Fix support for using a String subclass for the liquid source (#1421) [Dylan Thacker-Smith]
* Add `ParseTreeVisitor` to `RangeLookup` (#1470) [CP Clermont]
* Translate `RangeError` to `Liquid::Error` for `truncatewords` with large int (#1431) [Dylan Thacker-Smith]
## 5.0.1 / 2021-03-24 ## 5.0.1 / 2021-03-24
+2 -2
View File
@@ -5,7 +5,7 @@
* [Contributing guidelines](CONTRIBUTING.md) * [Contributing guidelines](CONTRIBUTING.md)
* [Version history](History.md) * [Version history](History.md)
* [Liquid documentation from Shopify](http://docs.shopify.com/themes/liquid-basics) * [Liquid documentation from Shopify](https://shopify.dev/api/liquid)
* [Liquid Wiki at GitHub](https://github.com/Shopify/liquid/wiki) * [Liquid Wiki at GitHub](https://github.com/Shopify/liquid/wiki)
* [Website](http://liquidmarkup.org/) * [Website](http://liquidmarkup.org/)
@@ -56,7 +56,7 @@ For standard use you can just pass it the content of a file and call render with
Setting the error mode of Liquid lets you specify how strictly you want your templates to be interpreted. Setting the error mode of Liquid lets you specify how strictly you want your templates to be interpreted.
Normally the parser is very lax and will accept almost anything without error. Unfortunately this can make Normally the parser is very lax and will accept almost anything without error. Unfortunately this can make
it very hard to debug and can lead to unexpected behaviour. it very hard to debug and can lead to unexpected behaviour.
Liquid also comes with a stricter parser that can be used when editing templates to give better error messages Liquid also comes with a stricter parser that can be used when editing templates to give better error messages
when templates are invalid. You can enable this new parser like this: when templates are invalid. You can enable this new parser like this:
+2 -2
View File
@@ -36,7 +36,7 @@ module Liquid
VariableIncompleteEnd = /\}\}?/ VariableIncompleteEnd = /\}\}?/
QuotedString = /"[^"]*"|'[^']*'/ QuotedString = /"[^"]*"|'[^']*'/
QuotedFragment = /#{QuotedString}|(?:[^\s,\|'"]|#{QuotedString})+/o QuotedFragment = /#{QuotedString}|(?:[^\s,\|'"]|#{QuotedString})+/o
TagAttributes = /(\w+)\s*\:\s*(#{QuotedFragment})/o TagAttributes = /(\w[\w-]*)\s*\:\s*(#{QuotedFragment})/o
AnyStartingTag = /#{TagStart}|#{VariableStart}/o AnyStartingTag = /#{TagStart}|#{VariableStart}/o
PartialTemplateParser = /#{TagStart}.*?#{TagEnd}|#{VariableStart}.*?#{VariableIncompleteEnd}/om PartialTemplateParser = /#{TagStart}.*?#{TagEnd}|#{VariableStart}.*?#{VariableIncompleteEnd}/om
TemplateParser = /(#{PartialTemplateParser}|#{AnyStartingTag})/om TemplateParser = /(#{PartialTemplateParser}|#{AnyStartingTag})/om
@@ -59,8 +59,8 @@ require 'liquid/forloop_drop'
require 'liquid/extensions' require 'liquid/extensions'
require 'liquid/errors' require 'liquid/errors'
require 'liquid/interrupts' require 'liquid/interrupts'
require 'liquid/strainer_factory'
require 'liquid/strainer_template' require 'liquid/strainer_template'
require 'liquid/strainer_factory'
require 'liquid/expression' require 'liquid/expression'
require 'liquid/context' require 'liquid/context'
require 'liquid/parser_switching' require 'liquid/parser_switching'
+2 -2
View File
@@ -231,8 +231,8 @@ module Liquid
end end
def create_variable(token, parse_context) def create_variable(token, parse_context)
token.scan(ContentOfVariable) do |content| if token =~ ContentOfVariable
markup = content.first markup = Regexp.last_match(1)
return Variable.new(markup, parse_context) return Variable.new(markup, parse_context)
end end
BlockBody.raise_missing_variable_terminator(token, parse_context) BlockBody.raise_missing_variable_terminator(token, parse_context)
+8 -2
View File
@@ -8,7 +8,7 @@ module Liquid
# c = Condition.new(1, '==', 1) # c = Condition.new(1, '==', 1)
# c.evaluate #=> true # c.evaluate #=> true
# #
class Condition #:nodoc: class Condition # :nodoc:
@@operators = { @@operators = {
'==' => ->(cond, left, right) { cond.send(:equal_variables, left, right) }, '==' => ->(cond, left, right) { cond.send(:equal_variables, left, right) },
'!=' => ->(cond, left, right) { !cond.send(:equal_variables, left, right) }, '!=' => ->(cond, left, right) { !cond.send(:equal_variables, left, right) },
@@ -61,7 +61,7 @@ module Liquid
@child_condition = nil @child_condition = nil
end end
def evaluate(context = Context.new) def evaluate(context = deprecated_default_context)
condition = self condition = self
result = nil result = nil
loop do loop do
@@ -150,6 +150,12 @@ module Liquid
end end
end end
def deprecated_default_context
warn("DEPRECATION WARNING: Condition#evaluate without a context argument is deprecated" \
" and will be removed from Liquid 6.0.0.")
Context.new
end
class ParseTreeVisitor < Liquid::ParseTreeVisitor class ParseTreeVisitor < Liquid::ParseTreeVisitor
def children def children
[ [
+1 -1
View File
@@ -124,7 +124,7 @@ module Liquid
# context['var'] = 'hi' # context['var'] = 'hi'
# end # end
# #
# context['var] #=> nil # context['var'] #=> nil
def stack(new_scope = {}) def stack(new_scope = {})
push(new_scope) push(new_scope)
yield yield
+11 -10
View File
@@ -10,21 +10,23 @@ module Liquid
'empty' => '' 'empty' => ''
}.freeze }.freeze
SINGLE_QUOTED_STRING = /\A\s*'(.*)'\s*\z/m INTEGERS_REGEX = /\A(-?\d+)\z/
DOUBLE_QUOTED_STRING = /\A\s*"(.*)"\s*\z/m FLOATS_REGEX = /\A(-?\d[\d\.]+)\z/
INTEGERS_REGEX = /\A\s*(-?\d+)\s*\z/
FLOATS_REGEX = /\A\s*(-?\d[\d\.]+)\s*\z/
# Use an atomic group (?>...) to avoid pathological backtracing from # Use an atomic group (?>...) to avoid pathological backtracing from
# malicious input as described in https://github.com/Shopify/liquid/issues/1357 # malicious input as described in https://github.com/Shopify/liquid/issues/1357
RANGES_REGEX = /\A\s*\(\s*(?>(\S+)\s*\.\.)\s*(\S+)\s*\)\s*\z/ RANGES_REGEX = /\A\(\s*(?>(\S+)\s*\.\.)\s*(\S+)\s*\)\z/
def self.parse(markup) def self.parse(markup)
return nil unless markup
markup = markup.strip
if (markup.start_with?('"') && markup.end_with?('"')) ||
(markup.start_with?("'") && markup.end_with?("'"))
return markup[1..-2]
end
case markup case markup
when nil
nil
when SINGLE_QUOTED_STRING, DOUBLE_QUOTED_STRING
Regexp.last_match(1)
when INTEGERS_REGEX when INTEGERS_REGEX
Regexp.last_match(1).to_i Regexp.last_match(1).to_i
when RANGES_REGEX when RANGES_REGEX
@@ -32,7 +34,6 @@ module Liquid
when FLOATS_REGEX when FLOATS_REGEX
Regexp.last_match(1).to_f Regexp.last_match(1).to_f
else else
markup = markup.strip
if LITERALS.key?(markup) if LITERALS.key?(markup)
LITERALS[markup] LITERALS[markup]
else else
+8
View File
@@ -12,6 +12,8 @@ module Liquid
end end
end end
attr_reader :start_obj, :end_obj
def initialize(start_obj, end_obj) def initialize(start_obj, end_obj)
@start_obj = start_obj @start_obj = start_obj
@end_obj = end_obj @end_obj = end_obj
@@ -35,5 +37,11 @@ module Liquid
Utils.to_integer(input) Utils.to_integer(input)
end end
end end
class ParseTreeVisitor < Liquid::ParseTreeVisitor
def children
[@node.start_obj, @node.end_obj]
end
end
end end
end end
+66 -20
View File
@@ -22,7 +22,14 @@ module Liquid
) )
STRIP_HTML_TAGS = /<.*?>/m STRIP_HTML_TAGS = /<.*?>/m
# Return the size of an array or of an string # @public_docs
# @title Size
# @syntax The syntax
# @summary The summary
# @type filter
# @category My category
# @description
# Return the size of an array or of an string
def size(input) def size(input)
input.respond_to?(:size) ? input.size : 0 input.respond_to?(:size) ? input.size : 0
end end
@@ -213,17 +220,23 @@ module Liquid
if ary.empty? if ary.empty?
[] []
elsif ary.first.respond_to?(:[]) && target_value.nil? elsif target_value.nil?
begin ary.select do |item|
ary.select { |item| item[property] } item[property]
rescue TypeError rescue TypeError
raise_property_error(property) raise_property_error(property)
rescue NoMethodError
return nil unless item.respond_to?(:[])
raise
end end
elsif ary.first.respond_to?(:[]) else
begin ary.select do |item|
ary.select { |item| item[property] == target_value } item[property] == target_value
rescue TypeError rescue TypeError
raise_property_error(property) raise_property_error(property)
rescue NoMethodError
return nil unless item.respond_to?(:[])
raise
end end
end end
end end
@@ -237,11 +250,14 @@ module Liquid
ary.uniq ary.uniq
elsif ary.empty? # The next two cases assume a non-empty array. elsif ary.empty? # The next two cases assume a non-empty array.
[] []
elsif ary.first.respond_to?(:[]) else
begin ary.uniq do |item|
ary.uniq { |a| a[property] } item[property]
rescue TypeError rescue TypeError
raise_property_error(property) raise_property_error(property)
rescue NoMethodError
return nil unless item.respond_to?(:[])
raise
end end
end end
end end
@@ -277,11 +293,14 @@ module Liquid
ary.compact ary.compact
elsif ary.empty? # The next two cases assume a non-empty array. elsif ary.empty? # The next two cases assume a non-empty array.
[] []
elsif ary.first.respond_to?(:[]) else
begin ary.reject do |item|
ary.reject { |a| a[property].nil? } item[property].nil?
rescue TypeError rescue TypeError
raise_property_error(property) raise_property_error(property)
rescue NoMethodError
return nil unless item.respond_to?(:[])
raise
end end
end end
end end
@@ -296,14 +315,34 @@ module Liquid
input.to_s.sub(string.to_s, replacement.to_s) input.to_s.sub(string.to_s, replacement.to_s)
end end
# Replace the last occurrences of a string with another
def replace_last(input, string, replacement)
input = input.to_s
string = string.to_s
replacement = replacement.to_s
start_index = input.rindex(string)
return input unless start_index
output = input.dup
output[start_index, string.length] = replacement
output
end
# remove a substring # remove a substring
def remove(input, string) def remove(input, string)
input.to_s.gsub(string.to_s, '') replace(input, string, '')
end end
# remove the first occurrences of a substring # remove the first occurrences of a substring
def remove_first(input, string) def remove_first(input, string)
input.to_s.sub(string.to_s, '') replace_first(input, string, '')
end
# remove the last occurences of a substring
def remove_last(input, string)
replace_last(input, string, '')
end end
# add one string to another # add one string to another
@@ -468,7 +507,7 @@ module Liquid
# #
def default(input, default_value = '', options = {}) def default(input, default_value = '', options = {})
options = {} unless options.is_a?(Hash) options = {} unless options.is_a?(Hash)
false_check = options['allow_false'] ? input.nil? : !input false_check = options['allow_false'] ? input.nil? : !Liquid::Utils.to_liquid_value(input)
false_check || (input.respond_to?(:empty?) && input.empty?) ? default_value : input false_check || (input.respond_to?(:empty?) && input.empty?) ? default_value : input
end end
@@ -486,10 +525,16 @@ module Liquid
end end
def nil_safe_compare(a, b) def nil_safe_compare(a, b)
if !a.nil? && !b.nil? result = a <=> b
a <=> b
if result
result
elsif a.nil?
1
elsif b.nil?
-1
else else
a.nil? ? 1 : -1 raise Liquid::ArgumentError, "cannot sort values of incompatible types"
end end
end end
@@ -544,8 +589,9 @@ module Liquid
def each def each
@input.each do |e| @input.each do |e|
e = e.respond_to?(:to_liquid) ? e.to_liquid : e
e.context = @context if e.respond_to?(:context=) e.context = @context if e.respond_to?(:context=)
yield(e.respond_to?(:to_liquid) ? e.to_liquid : e) yield(e)
end end
end end
end end
+5 -1
View File
@@ -31,7 +31,11 @@ module Liquid
if @registers.key?(key) if @registers.key?(key)
@registers.fetch(key) @registers.fetch(key)
elsif default != UNDEFINED elsif default != UNDEFINED
@static.fetch(key, default, &block) if block_given?
@static.fetch(key, &block)
else
@static.fetch(key, default)
end
else else
@static.fetch(key, &block) @static.fetch(key, &block)
end end
+11 -10
View File
@@ -7,25 +7,26 @@ module Liquid
def add_global_filter(filter) def add_global_filter(filter)
strainer_class_cache.clear strainer_class_cache.clear
global_filters << filter GlobalCache.add_filter(filter)
end end
def create(context, filters = []) def create(context, filters = [])
strainer_from_cache(filters).new(context) strainer_from_cache(filters).new(context)
end end
GlobalCache = Class.new(StrainerTemplate)
private private
def global_filters
@global_filters ||= []
end
def strainer_from_cache(filters) def strainer_from_cache(filters)
strainer_class_cache[filters] ||= begin if filters.empty?
klass = Class.new(StrainerTemplate) GlobalCache
global_filters.each { |f| klass.add_filter(f) } else
filters.each { |f| klass.add_filter(f) } strainer_class_cache[filters] ||= begin
klass klass = Class.new(GlobalCache)
filters.each { |f| klass.add_filter(f) }
klass
end
end end
end end
+5
View File
@@ -31,6 +31,11 @@ module Liquid
filter_methods.include?(method.to_s) filter_methods.include?(method.to_s)
end end
def inherited(subclass)
super
subclass.instance_variable_set(:@filter_methods, @filter_methods.dup)
end
private private
def filter_methods def filter_methods
+11 -5
View File
@@ -1,14 +1,20 @@
# frozen_string_literal: true # frozen_string_literal: true
# @public_docs
module Liquid module Liquid
# Assign sets a variable in your template. # @public_docs
# @title Assign
# @syntax The syntax
# @summary The summary
# @type tag
# @description
# Assign sets a variable in your template.
# #
# {% assign foo = 'monkey' %} # {% assign foo = 'monkey' %}
# #
# You can then use the variable later in the page. # You can then use the variable later in the page.
#
# {{ foo }}
# #
# {{ foo }}
class Assign < Tag class Assign < Tag
Syntax = /(#{VariableSignature}+)\s*=\s*(.*)\s*/om Syntax = /(#{VariableSignature}+)\s*=\s*(.*)\s*/om
+3 -2
View File
@@ -21,8 +21,9 @@ module Liquid
# After the first condition unless works just like if # After the first condition unless works just like if
@blocks[1..-1].each do |block| @blocks[1..-1].each do |block|
result = block.evaluate(context) result = Liquid::Utils.to_liquid_value(
result = result.to_liquid_value if result.is_a?(Liquid::Drop) block.evaluate(context)
)
if result if result
return block.attachment.render_to_output_buffer(context, output) return block.attachment.render_to_output_buffer(context, output)
+1 -1
View File
@@ -2,5 +2,5 @@
# frozen_string_literal: true # frozen_string_literal: true
module Liquid module Liquid
VERSION = "5.0.2.alpha" VERSION = "5.3.0.alpha"
end end
+9 -1
View File
@@ -3,11 +3,19 @@
require 'yaml' require 'yaml'
module Database module Database
DATABASE_FILE_PATH = "#{__dir__}/vision.database.yml"
# Load the standard vision toolkit database and re-arrage it to be simply exportable # Load the standard vision toolkit database and re-arrage it to be simply exportable
# to liquid as assigns. All this is based on Shopify # to liquid as assigns. All this is based on Shopify
def self.tables def self.tables
@tables ||= begin @tables ||= begin
db = YAML.load_file("#{__dir__}/vision.database.yml") db =
if YAML.respond_to?(:unsafe_load_file) # Only Psych 4+ can use unsafe_load_file
# unsafe_load_file is needed for YAML references
YAML.unsafe_load_file(DATABASE_FILE_PATH)
else
YAML.load_file(DATABASE_FILE_PATH)
end
# From vision source # From vision source
db['products'].each do |product| db['products'].each do |product|
+12 -14
View File
@@ -24,7 +24,7 @@ class ContextSensitiveDrop < Liquid::Drop
end end
end end
class Category < Liquid::Drop class Category
attr_accessor :name attr_accessor :name
def initialize(name) def initialize(name)
@@ -36,8 +36,9 @@ class Category < Liquid::Drop
end end
end end
class CategoryDrop class CategoryDrop < Liquid::Drop
attr_accessor :category, :context attr_accessor :category, :context
def initialize(category) def initialize(category)
@category = category @category = category
end end
@@ -405,45 +406,42 @@ class ContextTest < Minitest::Test
end end
def test_lambda_is_called_once def test_lambda_is_called_once
@global = 0
@context['callcount'] = proc { @context['callcount'] = proc {
@global ||= 0 @global += 1
@global += 1
@global.to_s @global.to_s
} }
assert_equal('1', @context['callcount']) assert_equal('1', @context['callcount'])
assert_equal('1', @context['callcount']) assert_equal('1', @context['callcount'])
assert_equal('1', @context['callcount']) assert_equal('1', @context['callcount'])
@global = nil
end end
def test_nested_lambda_is_called_once def test_nested_lambda_is_called_once
@global = 0
@context['callcount'] = { "lambda" => proc { @context['callcount'] = { "lambda" => proc {
@global ||= 0 @global += 1
@global += 1
@global.to_s @global.to_s
} } } }
assert_equal('1', @context['callcount.lambda']) assert_equal('1', @context['callcount.lambda'])
assert_equal('1', @context['callcount.lambda']) assert_equal('1', @context['callcount.lambda'])
assert_equal('1', @context['callcount.lambda']) assert_equal('1', @context['callcount.lambda'])
@global = nil
end end
def test_lambda_in_array_is_called_once def test_lambda_in_array_is_called_once
@global = 0
@context['callcount'] = [1, 2, proc { @context['callcount'] = [1, 2, proc {
@global ||= 0 @global += 1
@global += 1
@global.to_s @global.to_s
}, 4, 5] }, 4, 5]
assert_equal('1', @context['callcount[2]']) assert_equal('1', @context['callcount[2]'])
assert_equal('1', @context['callcount[2]']) assert_equal('1', @context['callcount[2]'])
assert_equal('1', @context['callcount[2]']) assert_equal('1', @context['callcount[2]'])
@global = nil
end end
def test_access_to_context_from_proc def test_access_to_context_from_proc
+24
View File
@@ -0,0 +1,24 @@
# frozen_string_literal: true
require 'test_helper'
class FilterKwargTest < Minitest::Test
module KwargFilter
def html_tag(_tag, attributes)
attributes
.map { |key, value| "#{key}='#{value}'" }
.join(' ')
end
end
include Liquid
def test_can_parse_data_kwargs
with_global_filter(KwargFilter) do
assert_equal(
"data-src='src' data-widths='100, 200'",
Template.parse("{{ 'img' | html_tag: data-src: 'src', data-widths: '100, 200' }}").render(nil, nil)
)
end
end
end
+33 -6
View File
@@ -3,6 +3,27 @@
require 'test_helper' require 'test_helper'
class ProfilerTest < Minitest::Test class ProfilerTest < Minitest::Test
class TestDrop < Liquid::Drop
def initialize(value)
super()
@value = value
end
def to_s
artificial_execution_time
@value
end
private
# Monotonic clock precision fluctuate based on the operating system
# By introducing a small sleep we ensure ourselves to register a non zero unit of time
def artificial_execution_time
sleep(Process.clock_getres(Process::CLOCK_MONOTONIC))
end
end
include Liquid include Liquid
class ProfilingFileSystem class ProfilingFileSystem
@@ -198,16 +219,22 @@ class ProfilerTest < Minitest::Test
def test_profiling_supports_self_time def test_profiling_supports_self_time
t = Template.parse("{% for item in collection %} {{ item }} {% endfor %}", profile: true) t = Template.parse("{% for item in collection %} {{ item }} {% endfor %}", profile: true)
t.render!("collection" => ["one", "two"]) collection = [
leaf = t.profiler[0].children[0] TestDrop.new("one"),
TestDrop.new("two"),
]
output = t.render!("collection" => collection)
assert_equal(" one two ", output)
assert_operator(leaf.self_time, :>, 0) leaf = t.profiler[0].children[0]
assert_operator(leaf.self_time, :>, 0.0)
end end
def test_profiling_supports_total_time def test_profiling_supports_total_time
t = Template.parse("{% if true %} {% increment test %} {{ test }} {% endif %}", profile: true) t = Template.parse("{% if true %} {{ test }} {% endif %}", profile: true)
t.render! output = t.render!("test" => TestDrop.new("one"))
assert_equal(" one ", output)
assert_operator(t.profiler[0].total_time, :>, 0) assert_operator(t.profiler[0].total_time, :>, 0.0)
end end
end end
+81 -32
View File
@@ -3,10 +3,6 @@
require 'test_helper' require 'test_helper'
class Filters
include Liquid::StandardFilters
end
class TestThing class TestThing
attr_reader :foo attr_reader :foo
@@ -29,8 +25,24 @@ class TestThing
end end
class TestDrop < Liquid::Drop class TestDrop < Liquid::Drop
def test def initialize(value:)
"testfoo" @value = value
end
attr_reader :value
def registers
@context.registers
end
end
class TestModel
def initialize(value:)
@value = value
end
def to_liquid
TestDrop.new(value: @value)
end end
end end
@@ -53,10 +65,13 @@ class NumberLikeThing < Liquid::Drop
end end
class StandardFiltersTest < Minitest::Test class StandardFiltersTest < Minitest::Test
Filters = Class.new(Liquid::StrainerTemplate)
Filters.add_filter(Liquid::StandardFilters)
include Liquid include Liquid
def setup def setup
@filters = Filters.new @filters = Filters.new(Context.new)
end end
def test_size def test_size
@@ -259,8 +274,8 @@ class StandardFiltersTest < Minitest::Test
{ "price" => 1, "handle" => "gamma" }, { "price" => 1, "handle" => "gamma" },
{ "price" => 2, "handle" => "epsilon" }, { "price" => 2, "handle" => "epsilon" },
{ "price" => 4, "handle" => "alpha" }, { "price" => 4, "handle" => "alpha" },
{ "handle" => "delta" },
{ "handle" => "beta" }, { "handle" => "beta" },
{ "handle" => "delta" },
] ]
assert_equal(expectation, @filters.sort(input, "price")) assert_equal(expectation, @filters.sort(input, "price"))
end end
@@ -363,8 +378,9 @@ class StandardFiltersTest < Minitest::Test
assert_equal(["foo"], @filters.uniq("foo")) assert_equal(["foo"], @filters.uniq("foo"))
assert_equal([1, 3, 2, 4], @filters.uniq([1, 1, 3, 2, 3, 1, 4, 3, 2, 1])) assert_equal([1, 3, 2, 4], @filters.uniq([1, 1, 3, 2, 3, 1, 4, 3, 2, 1]))
assert_equal([{ "a" => 1 }, { "a" => 3 }, { "a" => 2 }], @filters.uniq([{ "a" => 1 }, { "a" => 3 }, { "a" => 1 }, { "a" => 2 }], "a")) assert_equal([{ "a" => 1 }, { "a" => 3 }, { "a" => 2 }], @filters.uniq([{ "a" => 1 }, { "a" => 3 }, { "a" => 1 }, { "a" => 2 }], "a"))
testdrop = TestDrop.new test_drop = TestDrop.new(value: "test")
assert_equal([testdrop], @filters.uniq([testdrop, TestDrop.new], 'test')) test_drop_alternate = TestDrop.new(value: "test")
assert_equal([test_drop], @filters.uniq([test_drop, test_drop_alternate], 'value'))
end end
def test_uniq_empty_array def test_uniq_empty_array
@@ -423,6 +439,16 @@ class StandardFiltersTest < Minitest::Test
assert_template_result("woot: 1", '{{ foo | map: "whatever" }}', "foo" => [t]) assert_template_result("woot: 1", '{{ foo | map: "whatever" }}', "foo" => [t])
end end
def test_map_calls_context=
model = TestModel.new(value: "test")
template = Template.parse('{{ foo | map: "registers" }}')
template.registers[:test] = 1234
template.assigns['foo'] = [model]
assert_template_result("{:test=>1234}", template.render!)
end
def test_map_on_hashes def test_map_on_hashes
assert_template_result("4217", '{{ thing | map: "foo" | map: "bar" }}', assert_template_result("4217", '{{ thing | map: "foo" | map: "bar" }}',
"thing" => { "foo" => [{ "bar" => 42 }, { "bar" => 17 }] }) "thing" => { "foo" => [{ "bar" => 42 }, { "bar" => 17 }] })
@@ -441,9 +467,9 @@ class StandardFiltersTest < Minitest::Test
end end
def test_map_over_proc def test_map_over_proc
drop = TestDrop.new drop = TestDrop.new(value: "testfoo")
p = proc { drop } p = proc { drop }
templ = '{{ procs | map: "test" }}' templ = '{{ procs | map: "value" }}'
assert_template_result("testfoo", templ, "procs" => [p]) assert_template_result("testfoo", templ, "procs" => [p])
end end
@@ -539,19 +565,31 @@ class StandardFiltersTest < Minitest::Test
end end
def test_replace def test_replace
assert_equal('2 2 2 2', @filters.replace('1 1 1 1', '1', 2)) assert_equal('b b b b', @filters.replace('a a a a', 'a', 'b'))
assert_equal('2 2 2 2', @filters.replace('1 1 1 1', 1, 2)) assert_equal('2 2 2 2', @filters.replace('1 1 1 1', 1, 2))
assert_equal('2 1 1 1', @filters.replace_first('1 1 1 1', '1', 2)) assert_equal('1 1 1 1', @filters.replace('1 1 1 1', 2, 3))
assert_template_result('2 2 2 2', "{{ '1 1 1 1' | replace: '1', 2 }}")
assert_equal('b a a a', @filters.replace_first('a a a a', 'a', 'b'))
assert_equal('2 1 1 1', @filters.replace_first('1 1 1 1', 1, 2)) assert_equal('2 1 1 1', @filters.replace_first('1 1 1 1', 1, 2))
assert_equal('1 1 1 1', @filters.replace_first('1 1 1 1', 2, 3))
assert_template_result('2 1 1 1', "{{ '1 1 1 1' | replace_first: '1', 2 }}") assert_template_result('2 1 1 1', "{{ '1 1 1 1' | replace_first: '1', 2 }}")
assert_equal('a a a b', @filters.replace_last('a a a a', 'a', 'b'))
assert_equal('1 1 1 2', @filters.replace_last('1 1 1 1', 1, 2))
assert_equal('1 1 1 1', @filters.replace_last('1 1 1 1', 2, 3))
assert_template_result('1 1 1 2', "{{ '1 1 1 1' | replace_last: '1', 2 }}")
end end
def test_remove def test_remove
assert_equal(' ', @filters.remove("a a a a", 'a')) assert_equal(' ', @filters.remove("a a a a", 'a'))
assert_equal(' ', @filters.remove("1 1 1 1", 1)) assert_template_result(' ', "{{ '1 1 1 1' | remove: 1 }}")
assert_equal('a a a', @filters.remove_first("a a a a", 'a '))
assert_equal(' 1 1 1', @filters.remove_first("1 1 1 1", 1)) assert_equal('b a a', @filters.remove_first("a b a a", 'a '))
assert_template_result('a a a', "{{ 'a a a a' | remove_first: 'a ' }}") assert_template_result(' 1 1 1', "{{ '1 1 1 1' | remove_first: 1 }}")
assert_equal('a a b', @filters.remove_last("a a b a", ' a'))
assert_template_result('1 1 1 ', "{{ '1 1 1 1' | remove_last: 1 }}")
end end
def test_pipes_in_string_arguments def test_pipes_in_string_arguments
@@ -728,6 +766,8 @@ class StandardFiltersTest < Minitest::Test
assert_equal("bar", @filters.default([], "bar")) assert_equal("bar", @filters.default([], "bar"))
assert_equal("bar", @filters.default({}, "bar")) assert_equal("bar", @filters.default({}, "bar"))
assert_template_result('bar', "{{ false | default: 'bar' }}") assert_template_result('bar', "{{ false | default: 'bar' }}")
assert_template_result('bar', "{{ drop | default: 'bar' }}", 'drop' => BooleanDrop.new(false))
assert_template_result('Yay', "{{ drop | default: 'bar' }}", 'drop' => BooleanDrop.new(true))
end end
def test_default_handle_false def test_default_handle_false
@@ -738,6 +778,8 @@ class StandardFiltersTest < Minitest::Test
assert_equal("bar", @filters.default([], "bar", "allow_false" => true)) assert_equal("bar", @filters.default([], "bar", "allow_false" => true))
assert_equal("bar", @filters.default({}, "bar", "allow_false" => true)) assert_equal("bar", @filters.default({}, "bar", "allow_false" => true))
assert_template_result('false', "{{ false | default: 'bar', allow_false: true }}") assert_template_result('false', "{{ false | default: 'bar', allow_false: true }}")
assert_template_result('Nay', "{{ drop | default: 'bar', allow_false: true }}", 'drop' => BooleanDrop.new(false))
assert_template_result('Yay', "{{ drop | default: 'bar', allow_false: true }}", 'drop' => BooleanDrop.new(true))
end end
def test_cannot_access_private_methods def test_cannot_access_private_methods
@@ -766,6 +808,18 @@ class StandardFiltersTest < Minitest::Test
assert_equal(expectation, @filters.where(input, "ok")) assert_equal(expectation, @filters.where(input, "ok"))
end end
def test_where_string_keys
input = [
"alpha", "beta", "gamma", "delta"
]
expectation = [
"beta",
]
assert_equal(expectation, @filters.where(input, "be"))
end
def test_where_no_key_set def test_where_no_key_set
input = [ input = [
{ "handle" => "alpha", "ok" => true }, { "handle" => "alpha", "ok" => true },
@@ -811,7 +865,7 @@ class StandardFiltersTest < Minitest::Test
end end
def test_all_filters_never_raise_non_liquid_exception def test_all_filters_never_raise_non_liquid_exception
test_drop = TestDrop.new test_drop = TestDrop.new(value: "test")
test_drop.context = Context.new test_drop.context = Context.new
test_enum = TestEnumerable.new test_enum = TestEnumerable.new
test_enum.context = Context.new test_enum.context = Context.new
@@ -836,19 +890,14 @@ class StandardFiltersTest < Minitest::Test
{ 1 => "bar" }, { 1 => "bar" },
["foo", 123, nil, true, false, Drop, ["foo"], { foo: "bar" }], ["foo", 123, nil, true, false, Drop, ["foo"], { foo: "bar" }],
] ]
test_types.each do |first| StandardFilters.public_instance_methods(false).each do |method|
test_types.each do |other| arg_count = @filters.method(method).arity
(@filters.methods - Object.methods).each do |method| arg_count *= -1 if arg_count < 0
arg_count = @filters.method(method).arity
arg_count *= -1 if arg_count < 0 test_types.repeated_permutation(arg_count) do |args|
inputs = [first] @filters.send(method, *args)
inputs << ([other] * (arg_count - 1)) if arg_count > 1 rescue Liquid::Error
begin nil
@filters.send(method, *inputs)
rescue Liquid::ArgumentError, Liquid::ZeroDivisionError
nil
end
end
end end
end end
end end
+10 -10
View File
@@ -72,21 +72,21 @@ module Minitest
end end
def with_global_filter(*globals) def with_global_filter(*globals)
original_global_filters = Liquid::StrainerFactory.instance_variable_get(:@global_filters) original_global_cache = Liquid::StrainerFactory::GlobalCache
Liquid::StrainerFactory.instance_variable_set(:@global_filters, []) Liquid::StrainerFactory.send(:remove_const, :GlobalCache)
globals.each do |global| Liquid::StrainerFactory.const_set(:GlobalCache, Class.new(Liquid::StrainerTemplate))
Liquid::StrainerFactory.add_global_filter(global)
end
Liquid::StrainerFactory.send(:strainer_class_cache).clear
globals.each do |global| globals.each do |global|
Liquid::Template.register_filter(global) Liquid::Template.register_filter(global)
end end
yield
ensure
Liquid::StrainerFactory.send(:strainer_class_cache).clear Liquid::StrainerFactory.send(:strainer_class_cache).clear
Liquid::StrainerFactory.instance_variable_set(:@global_filters, original_global_filters) begin
yield
ensure
Liquid::StrainerFactory.send(:remove_const, :GlobalCache)
Liquid::StrainerFactory.const_set(:GlobalCache, original_global_cache)
Liquid::StrainerFactory.send(:strainer_class_cache).clear
end
end end
def with_error_mode(mode) def with_error_mode(mode)
+27 -14
View File
@@ -10,8 +10,8 @@ class ConditionUnitTest < Minitest::Test
end end
def test_basic_condition def test_basic_condition
assert_equal(false, Condition.new(1, '==', 2).evaluate) assert_equal(false, Condition.new(1, '==', 2).evaluate(Context.new))
assert_equal(true, Condition.new(1, '==', 1).evaluate) assert_equal(true, Condition.new(1, '==', 1).evaluate(Context.new))
end end
def test_default_operators_evalute_true def test_default_operators_evalute_true
@@ -67,11 +67,11 @@ class ConditionUnitTest < Minitest::Test
end end
def test_hash_compare_backwards_compatibility def test_hash_compare_backwards_compatibility
assert_nil(Condition.new({}, '>', 2).evaluate) assert_nil(Condition.new({}, '>', 2).evaluate(Context.new))
assert_nil(Condition.new(2, '>', {}).evaluate) assert_nil(Condition.new(2, '>', {}).evaluate(Context.new))
assert_equal(false, Condition.new({}, '==', 2).evaluate) assert_equal(false, Condition.new({}, '==', 2).evaluate(Context.new))
assert_equal(true, Condition.new({ 'a' => 1 }, '==', 'a' => 1).evaluate) assert_equal(true, Condition.new({ 'a' => 1 }, '==', 'a' => 1).evaluate(Context.new))
assert_equal(true, Condition.new({ 'a' => 2 }, 'contains', 'a').evaluate) assert_equal(true, Condition.new({ 'a' => 2 }, 'contains', 'a').evaluate(Context.new))
end end
def test_contains_works_on_arrays def test_contains_works_on_arrays
@@ -106,30 +106,29 @@ class ConditionUnitTest < Minitest::Test
def test_or_condition def test_or_condition
condition = Condition.new(1, '==', 2) condition = Condition.new(1, '==', 2)
assert_equal(false, condition.evaluate(Context.new))
assert_equal(false, condition.evaluate)
condition.or(Condition.new(2, '==', 1)) condition.or(Condition.new(2, '==', 1))
assert_equal(false, condition.evaluate) assert_equal(false, condition.evaluate(Context.new))
condition.or(Condition.new(1, '==', 1)) condition.or(Condition.new(1, '==', 1))
assert_equal(true, condition.evaluate) assert_equal(true, condition.evaluate(Context.new))
end end
def test_and_condition def test_and_condition
condition = Condition.new(1, '==', 1) condition = Condition.new(1, '==', 1)
assert_equal(true, condition.evaluate) assert_equal(true, condition.evaluate(Context.new))
condition.and(Condition.new(2, '==', 2)) condition.and(Condition.new(2, '==', 2))
assert_equal(true, condition.evaluate) assert_equal(true, condition.evaluate(Context.new))
condition.and(Condition.new(2, '==', 1)) condition.and(Condition.new(2, '==', 1))
assert_equal(false, condition.evaluate) assert_equal(false, condition.evaluate(Context.new))
end end
def test_should_allow_custom_proc_operator def test_should_allow_custom_proc_operator
@@ -148,6 +147,20 @@ class ConditionUnitTest < Minitest::Test
assert_evaluates_true(VariableLookup.new("one"), '==', VariableLookup.new("another")) assert_evaluates_true(VariableLookup.new("one"), '==', VariableLookup.new("another"))
end end
def test_default_context_is_deprecated
if Gem::Version.new(Liquid::VERSION) >= Gem::Version.new('6.0.0')
flunk("Condition#evaluate without a context argument is to be removed")
end
_out, err = capture_io do
assert_equal(true, Condition.new(1, '==', 1).evaluate)
end
expected = "DEPRECATION WARNING: Condition#evaluate without a context argument is deprecated" \
" and will be removed from Liquid 6.0.0."
assert_includes(err.lines.map(&:strip), expected)
end
private private
def assert_evaluates_true(left, op, right) def assert_evaluates_true(left, op, right)
+7
View File
@@ -159,6 +159,13 @@ class ParseTreeVisitorTest < Minitest::Test
) )
end end
def test_for_range
assert_equal(
["test"],
visit(%({% for x in (1..test) %}{% endfor %}))
)
end
def test_tablerow_in def test_tablerow_in
assert_equal( assert_equal(
["test"], ["test"],
+2 -1
View File
@@ -52,7 +52,8 @@ class StrainerFactoryUnitTest < Minitest::Test
/\ALiquid error: wrong number of arguments \((1 for 0|given 1, expected 0)\)\z/, /\ALiquid error: wrong number of arguments \((1 for 0|given 1, expected 0)\)\z/,
exception.message exception.message
) )
assert_equal(exception.backtrace[0].split(':')[0], __FILE__) source = AccessScopeFilters.instance_method(:public_filter).source_location
assert_equal(source.map(&:to_s), exception.backtrace[0].split(':')[0..1])
end end
def test_strainer_only_invokes_public_filter_methods def test_strainer_only_invokes_public_filter_methods
+1 -1
View File
@@ -57,8 +57,8 @@ class StrainerTemplateUnitTest < Minitest::Test
end end
def test_add_filter_does_not_raise_when_module_overrides_previously_registered_method def test_add_filter_does_not_raise_when_module_overrides_previously_registered_method
strainer = Context.new.strainer
with_global_filter do with_global_filter do
strainer = Context.new.strainer
strainer.class.add_filter(PublicMethodOverrideFilter) strainer.class.add_filter(PublicMethodOverrideFilter)
assert(strainer.class.send(:filter_methods).include?('public_filter')) assert(strainer.class.send(:filter_methods).include?('public_filter'))
end end