Compare commits

..

3 Commits

Author SHA1 Message Date
Mike Angell
9bb533985e styling fixes 2019-10-04 15:25:06 +10:00
Mike Angell
1c6a5d91fe Merge branch 'master' into sort-numeric 2019-10-04 15:20:00 +10:00
Justin Li
2f0a2c66f3 Add sort_numeric filter 2018-09-13 17:16:50 -04:00
101 changed files with 2077 additions and 2225 deletions

View File

@@ -1,5 +1,3 @@
# Recommended rubocop version: ~> 0.78.0
AllCops: AllCops:
Exclude: Exclude:
- 'db/schema.rb' - 'db/schema.rb'
@@ -22,7 +20,7 @@ Style/Alias:
- prefer_alias - prefer_alias
- prefer_alias_method - prefer_alias_method
Layout/HashAlignment: Layout/AlignHash:
EnforcedHashRocketStyle: key EnforcedHashRocketStyle: key
EnforcedColonStyle: key EnforcedColonStyle: key
EnforcedLastArgumentHashStyle: ignore_implicit EnforcedLastArgumentHashStyle: ignore_implicit
@@ -32,7 +30,7 @@ Layout/HashAlignment:
- ignore_implicit - ignore_implicit
- ignore_explicit - ignore_explicit
Layout/ParameterAlignment: Layout/AlignParameters:
EnforcedStyle: with_fixed_indentation EnforcedStyle: with_fixed_indentation
SupportedStyles: SupportedStyles:
- with_first_parameter - with_first_parameter
@@ -174,7 +172,7 @@ Naming/FileName:
Regex: Regex:
IgnoreExecutableScripts: true IgnoreExecutableScripts: true
Layout/FirstArgumentIndentation: Layout/IndentFirstArgument:
EnforcedStyle: consistent EnforcedStyle: consistent
SupportedStyles: SupportedStyles:
- consistent - consistent
@@ -227,7 +225,7 @@ Layout/IndentationConsistency:
Layout/IndentationWidth: Layout/IndentationWidth:
Width: 2 Width: 2
Layout/FirstArrayElementIndentation: Layout/IndentFirstArrayElement:
EnforcedStyle: consistent EnforcedStyle: consistent
SupportedStyles: SupportedStyles:
- special_inside_parentheses - special_inside_parentheses
@@ -235,10 +233,10 @@ Layout/FirstArrayElementIndentation:
- align_brackets - align_brackets
IndentationWidth: IndentationWidth:
Layout/AssignmentIndentation: Layout/IndentAssignment:
IndentationWidth: IndentationWidth:
Layout/FirstHashElementIndentation: Layout/IndentFirstHashElement:
EnforcedStyle: consistent EnforcedStyle: consistent
SupportedStyles: SupportedStyles:
- special_inside_parentheses - special_inside_parentheses
@@ -342,9 +340,9 @@ Style/PercentQLiterals:
Naming/PredicateName: Naming/PredicateName:
NamePrefix: NamePrefix:
- is_ - is_
ForbiddenPrefixes: NamePrefixBlacklist:
- is_ - is_
AllowedMethods: NameWhitelist:
- is_a? - is_a?
Exclude: Exclude:
- 'spec/**/*' - 'spec/**/*'
@@ -469,7 +467,7 @@ Style/TernaryParentheses:
- require_no_parentheses - require_no_parentheses
AllowSafeAssignment: true AllowSafeAssignment: true
Layout/TrailingEmptyLines: Layout/TrailingBlankLines:
EnforcedStyle: final_newline EnforcedStyle: final_newline
SupportedStyles: SupportedStyles:
- final_newline - final_newline
@@ -480,7 +478,7 @@ Style/TrivialAccessors:
AllowPredicates: true AllowPredicates: true
AllowDSLWriters: false AllowDSLWriters: false
IgnoreClassMethods: false IgnoreClassMethods: false
AllowedMethods: Whitelist:
- to_ary - to_ary
- to_a - to_a
- to_c - to_c
@@ -511,7 +509,7 @@ Style/WhileUntilModifier:
Metrics/BlockNesting: Metrics/BlockNesting:
Max: 3 Max: 3
Layout/LineLength: Metrics/LineLength:
Max: 120 Max: 120
AllowHeredoc: true AllowHeredoc: true
AllowURI: true AllowURI: true
@@ -563,7 +561,7 @@ Lint/UnusedMethodArgument:
Naming/AccessorMethodName: Naming/AccessorMethodName:
Enabled: true Enabled: true
Layout/ArrayAlignment: Layout/AlignArray:
Enabled: true Enabled: true
Style/ArrayJoin: Style/ArrayJoin:
@@ -821,13 +819,13 @@ Layout/TrailingWhitespace:
Style/UnlessElse: Style/UnlessElse:
Enabled: true Enabled: true
Style/RedundantCapitalW: Style/UnneededCapitalW:
Enabled: true Enabled: true
Style/RedundantInterpolation: Style/UnneededInterpolation:
Enabled: true Enabled: true
Style/RedundantPercentQ: Style/UnneededPercentQ:
Enabled: true Enabled: true
Style/VariableInterpolation: Style/VariableInterpolation:
@@ -842,7 +840,7 @@ Style/WhileUntilDo:
Style/ZeroLengthPredicate: Style/ZeroLengthPredicate:
Enabled: true Enabled: true
Layout/HeredocIndentation: Layout/IndentHeredoc:
EnforcedStyle: squiggly EnforcedStyle: squiggly
Lint/AmbiguousOperator: Lint/AmbiguousOperator:
@@ -866,7 +864,7 @@ Lint/DeprecatedClassMethods:
Lint/DuplicateMethods: Lint/DuplicateMethods:
Enabled: true Enabled: true
Lint/DuplicateHashKey: Lint/DuplicatedKey:
Enabled: true Enabled: true
Lint/EachWithObjectArgument: Lint/EachWithObjectArgument:
@@ -893,8 +891,8 @@ Lint/FloatOutOfRange:
Lint/FormatParameterMismatch: Lint/FormatParameterMismatch:
Enabled: true Enabled: true
Lint/SuppressedException: Lint/HandleExceptions:
AllowComments: true Enabled: true
Lint/ImplicitStringConcatenation: Lint/ImplicitStringConcatenation:
Description: Checks for adjacent string literals on the same line, which could Description: Checks for adjacent string literals on the same line, which could
@@ -949,7 +947,7 @@ Lint/ShadowedException:
Lint/ShadowingOuterLocalVariable: Lint/ShadowingOuterLocalVariable:
Enabled: true Enabled: true
Lint/RedundantStringCoercion: Lint/StringConversionInInterpolation:
Enabled: true Enabled: true
Lint/UnderscorePrefixedVariableName: Lint/UnderscorePrefixedVariableName:
@@ -958,13 +956,13 @@ Lint/UnderscorePrefixedVariableName:
Lint/UnifiedInteger: Lint/UnifiedInteger:
Enabled: true Enabled: true
Lint/RedundantCopDisableDirective: Lint/UnneededCopDisableDirective:
Enabled: true Enabled: true
Lint/RedundantCopEnableDirective: Lint/UnneededCopEnableDirective:
Enabled: true Enabled: true
Lint/RedundantSplatExpansion: Lint/UnneededSplatExpansion:
Enabled: true Enabled: true
Lint/UnreachableCode: Lint/UnreachableCode:

View File

@@ -18,7 +18,7 @@ Lint/InheritException:
# Cop supports --auto-correct. # Cop supports --auto-correct.
# Configuration parameters: AutoCorrect, AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns. # Configuration parameters: AutoCorrect, AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns.
# URISchemes: http, https # URISchemes: http, https
Layout/LineLength: Metrics/LineLength:
Max: 294 Max: 294
# Offense count: 44 # Offense count: 44

View File

@@ -4,8 +4,8 @@ cache: bundler
rvm: rvm:
- 2.4 - 2.4
- 2.5 - 2.5
- 2.6 - &latest_ruby 2.6
- &latest_ruby 2.7 - 2.7
- ruby-head - ruby-head
matrix: matrix:

View File

@@ -18,7 +18,7 @@ group :benchmark, :test do
end end
group :test do group :test do
gem 'rubocop', '~> 0.78.0', require: false gem 'rubocop', '~> 0.74.0', require: false
gem 'rubocop-performance', require: false gem 'rubocop-performance', require: false
platform :mri, :truffleruby do platform :mri, :truffleruby do

View File

@@ -1,11 +1,5 @@
# Liquid Change Log # Liquid Change Log
### Unreleased
* Split Strainer class as a factory and a template (#1208) [Thierry Joyal]
* Remove handling of a nil context in the Strainer class (#1218) [Thierry Joyal]
* StaticRegisters#fetch to raise on missing key (#1250) [Thierry Joyal]
## 4.0.3 / 2019-03-12 ## 4.0.3 / 2019-03-12
### Fixed ### Fixed

View File

@@ -80,24 +80,22 @@ It is also recommended that you use it in the template editors of existing apps
By default, the renderer doesn't raise or in any other way notify you if some variables or filters are missing, i.e. not passed to the `render` method. By default, the renderer doesn't raise or in any other way notify you if some variables or filters are missing, i.e. not passed to the `render` method.
You can improve this situation by passing `strict_variables: true` and/or `strict_filters: true` options to the `render` method. You can improve this situation by passing `strict_variables: true` and/or `strict_filters: true` options to the `render` method.
When one of these options is set to true, all errors about undefined variables and undefined filters will be stored in an `errors` array on the `Liquid::Context` instance used for rendering. When one of these options is set to true, all errors about undefined variables and undefined filters will be stored in `errors` array of a `Liquid::Template` instance.
Here are some examples: Here are some examples:
```ruby ```ruby
template = Liquid::Template.parse("{{x}} {{y}} {{z.a}} {{z.b}}") template = Liquid::Template.parse("{{x}} {{y}} {{z.a}} {{z.b}}")
context = Liquid::Context.new({ 'x' => 1, 'z' => { 'a' => 2 } }) template.render({ 'x' => 1, 'z' => { 'a' => 2 } }, { strict_variables: true })
template.render(context, { strict_variables: true })
#=> '1 2 ' # when a variable is undefined, it's rendered as nil #=> '1 2 ' # when a variable is undefined, it's rendered as nil
context.errors template.errors
#=> [#<Liquid::UndefinedVariable: Liquid error: undefined variable y>, #<Liquid::UndefinedVariable: Liquid error: undefined variable b>] #=> [#<Liquid::UndefinedVariable: Liquid error: undefined variable y>, #<Liquid::UndefinedVariable: Liquid error: undefined variable b>]
``` ```
```ruby ```ruby
template = Liquid::Template.parse("{{x | filter1 | upcase}}") template = Liquid::Template.parse("{{x | filter1 | upcase}}")
context = Liquid::Context.new({ 'x' => 'foo' }) template.render({ 'x' => 'foo' }, { strict_filters: true })
template.render(context, { strict_filters: true })
#=> '' # when at least one filter in the filter chain is undefined, a whole expression is rendered as nil #=> '' # when at least one filter in the filter chain is undefined, a whole expression is rendered as nil
context.errors template.errors
#=> [#<Liquid::UndefinedFilter: Liquid error: undefined filter filter1>] #=> [#<Liquid::UndefinedFilter: Liquid error: undefined filter filter1>]
``` ```
@@ -113,4 +111,4 @@ template.render!({ 'x' => 1}, { strict_variables: true })
To help track usages of a feature or code path in production, we have released opt-in usage tracking. To enable this, we provide an empty `Liquid:: Usage.increment` method which you can customize to your needs. The feature is well suited to https://github.com/Shopify/statsd-instrument. However, the choice of implementation is up to you. To help track usages of a feature or code path in production, we have released opt-in usage tracking. To enable this, we provide an empty `Liquid:: Usage.increment` method which you can customize to your needs. The feature is well suited to https://github.com/Shopify/statsd-instrument. However, the choice of implementation is up to you.
Once you have enabled usage tracking, we recommend reporting any events through Github Issues that your system may be logging. It is highly likely this event has been added to consider deprecating or improving code specific to this event, so please raise any concerns. Once you have enabled usage tracking, we recommend reporting any events through Github Issues that your system may be logging. It is highly likely this event has been added to consider deprecating or improving code specific to this event, so please raise any concerns.

View File

@@ -11,7 +11,7 @@ desc('run test suite with default parser')
Rake::TestTask.new(:base_test) do |t| Rake::TestTask.new(:base_test) do |t|
t.libs << '.' << 'lib' << 'test' t.libs << '.' << 'lib' << 'test'
t.test_files = FileList['test/{integration,unit}/**/*_test.rb'] t.test_files = FileList['test/{integration,unit}/**/*_test.rb']
t.verbose = false t.verbose = false
end end
desc('run test suite with warn error mode') desc('run test suite with warn error mode')

View File

@@ -12,16 +12,16 @@ class LiquidServlet < WEBrick::HTTPServlet::AbstractServlet
private private
def handle(_type, req, res) def handle(_type, req, res)
@request = req @request = req
@response = res @response = res
@request.path_info =~ /(\w+)\z/ @request.path_info =~ /(\w+)\z/
@action = Regexp.last_match(1) || 'index' @action = Regexp.last_match(1) || 'index'
@assigns = send(@action) if respond_to?(@action) @assigns = send(@action) if respond_to?(@action)
@response['Content-Type'] = "text/html" @response['Content-Type'] = "text/html"
@response.status = 200 @response.status = 200
@response.body = Liquid::Template.parse(read_template).render(@assigns, filters: [ProductsFilter]) @response.body = Liquid::Template.parse(read_template).render(@assigns, filters: [ProductsFilter])
end end
def read_template(filename = @action) def read_template(filename = @action)

View File

@@ -57,8 +57,7 @@ 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'
require 'liquid/strainer_template'
require 'liquid/expression' require 'liquid/expression'
require 'liquid/context' require 'liquid/context'
require 'liquid/parser_switching' require 'liquid/parser_switching'
@@ -81,7 +80,6 @@ require 'liquid/partial_cache'
require 'liquid/usage' require 'liquid/usage'
require 'liquid/register' require 'liquid/register'
require 'liquid/static_registers' require 'liquid/static_registers'
require 'liquid/template_factory'
# Load all the tags of the standard library # Load all the tags of the standard library
# #

View File

@@ -1,21 +1,19 @@
# frozen_string_literal: true # frozen_string_literal: true
require 'English'
module Liquid module Liquid
class BlockBody class BlockBody
LiquidTagToken = /\A\s*(\w+)\s*(.*?)\z/o LiquidTagToken = /\A\s*(\w+)\s*(.*?)\z/o
FullToken = /\A#{TagStart}#{WhitespaceControl}?(\s*)(\w+)(\s*)(.*?)#{WhitespaceControl}?#{TagEnd}\z/om FullToken = /\A#{TagStart}#{WhitespaceControl}?(\s*)(\w+)(\s*)(.*?)#{WhitespaceControl}?#{TagEnd}\z/om
ContentOfVariable = /\A#{VariableStart}#{WhitespaceControl}?(.*?)#{WhitespaceControl}?#{VariableEnd}\z/om ContentOfVariable = /\A#{VariableStart}#{WhitespaceControl}?(.*?)#{WhitespaceControl}?#{VariableEnd}\z/om
WhitespaceOrNothing = /\A\s*\z/ WhitespaceOrNothing = /\A\s*\z/
TAGSTART = "{%" TAGSTART = "{%"
VARSTART = "{{" VARSTART = "{{"
attr_reader :nodelist attr_reader :nodelist
def initialize def initialize
@nodelist = [] @nodelist = []
@blank = true @blank = true
end end
def parse(tokenizer, parse_context, &block) def parse(tokenizer, parse_context, &block)
@@ -37,7 +35,7 @@ module Liquid
return yield token, token return yield token, token
end end
tag_name = Regexp.last_match(1) tag_name = Regexp.last_match(1)
markup = Regexp.last_match(2) markup = Regexp.last_match(2)
unless (tag = registered_tags[tag_name]) unless (tag = registered_tags[tag_name])
# end parsing if we reach an unknown tag and let the caller decide # end parsing if we reach an unknown tag and let the caller decide
# determine how to proceed # determine how to proceed
@@ -53,21 +51,6 @@ module Liquid
yield nil, nil yield nil, nil
end end
# @api private
def self.unknown_tag_in_liquid_tag(end_tag_name, end_tag_markup)
yield end_tag_name, end_tag_markup
ensure
Usage.increment("liquid_tag_contains_outer_tag") unless $ERROR_INFO.is_a?(SyntaxError)
end
private def parse_liquid_tag(markup, parse_context, &block)
liquid_tag_tokenizer = Tokenizer.new(markup, line_number: parse_context.line_number, for_liquid_tag: true)
parse_for_liquid_tag(liquid_tag_tokenizer, parse_context) do |end_tag_name, end_tag_markup|
next unless end_tag_name
self.class.unknown_tag_in_liquid_tag(end_tag_name, end_tag_markup, &block)
end
end
private def parse_for_document(tokenizer, parse_context, &block) private def parse_for_document(tokenizer, parse_context, &block)
while (token = tokenizer.shift) while (token = tokenizer.shift)
next if token.empty? next if token.empty?
@@ -78,7 +61,7 @@ module Liquid
raise_missing_tag_terminator(token, parse_context) raise_missing_tag_terminator(token, parse_context)
end end
tag_name = Regexp.last_match(2) tag_name = Regexp.last_match(2)
markup = Regexp.last_match(4) markup = Regexp.last_match(4)
if parse_context.line_number if parse_context.line_number
# newlines inside the tag should increase the line number, # newlines inside the tag should increase the line number,
@@ -87,8 +70,8 @@ module Liquid
end end
if tag_name == 'liquid' if tag_name == 'liquid'
parse_liquid_tag(markup, parse_context, &block) liquid_tag_tokenizer = Tokenizer.new(markup, line_number: parse_context.line_number, for_liquid_tag: true)
next next parse_for_liquid_tag(liquid_tag_tokenizer, parse_context, &block)
end end
unless (tag = registered_tags[tag_name]) unless (tag = registered_tags[tag_name])

View File

@@ -35,11 +35,10 @@ module Liquid
attr_accessor :left, :operator, :right attr_accessor :left, :operator, :right
def initialize(left = nil, operator = nil, right = nil) def initialize(left = nil, operator = nil, right = nil)
@left = left @left = left
@operator = operator @operator = operator
@right = right @right = right
@child_relation = nil
@child_relation = nil
@child_condition = nil @child_condition = nil
end end
@@ -63,12 +62,12 @@ module Liquid
end end
def or(condition) def or(condition)
@child_relation = :or @child_relation = :or
@child_condition = condition @child_condition = condition
end end
def and(condition) def and(condition)
@child_relation = :and @child_relation = :and
@child_condition = condition @child_condition = condition
end end
@@ -116,7 +115,7 @@ module Liquid
# return this as the result. # return this as the result.
return context.evaluate(left) if op.nil? return context.evaluate(left) if op.nil?
left = context.evaluate(left) left = context.evaluate(left)
right = context.evaluate(right) right = context.evaluate(right)
operation = self.class.operators[op] || raise(Liquid::ArgumentError, "Unknown operator #{op}") operation = self.class.operators[op] || raise(Liquid::ArgumentError, "Unknown operator #{op}")

View File

@@ -34,14 +34,15 @@ module Liquid
@strict_variables = false @strict_variables = false
@resource_limits = resource_limits || ResourceLimits.new(Template.default_resource_limits) @resource_limits = resource_limits || ResourceLimits.new(Template.default_resource_limits)
@base_scope_depth = 0 @base_scope_depth = 0
squash_instance_assigns_with_environments
self.exception_renderer = Template.default_exception_renderer self.exception_renderer = Template.default_exception_renderer
if rethrow_errors if rethrow_errors
self.exception_renderer = ->(_e) { raise } self.exception_renderer = ->(_e) { raise }
end end
@interrupts = [] @interrupts = []
@filters = [] @filters = []
@global_filter = nil @global_filter = nil
end end
# rubocop:enable Metrics/ParameterLists # rubocop:enable Metrics/ParameterLists
@@ -51,7 +52,7 @@ module Liquid
end end
def strainer def strainer
@strainer ||= StrainerFactory.create(self, @filters) @strainer ||= Strainer.create(self, @filters)
end end
# Adds filters to this context. # Adds filters to this context.
@@ -86,7 +87,7 @@ module Liquid
def handle_error(e, line_number = nil) def handle_error(e, line_number = nil)
e = internal_error unless e.is_a?(Liquid::Error) e = internal_error unless e.is_a?(Liquid::Error)
e.template_name ||= template_name e.template_name ||= template_name
e.line_number ||= line_number e.line_number ||= line_number
errors.push(e) errors.push(e)
exception_renderer.call(e).to_s exception_renderer.call(e).to_s
end end
@@ -137,11 +138,11 @@ module Liquid
static_environments: static_environments, static_environments: static_environments,
registers: StaticRegisters.new(registers) registers: StaticRegisters.new(registers)
).tap do |subcontext| ).tap do |subcontext|
subcontext.base_scope_depth = base_scope_depth + 1 subcontext.base_scope_depth = base_scope_depth + 1
subcontext.exception_renderer = exception_renderer subcontext.exception_renderer = exception_renderer
subcontext.filters = @filters subcontext.filters = @filters
subcontext.strainer = nil subcontext.strainer = nil
subcontext.errors = errors subcontext.errors = errors
subcontext.warnings = warnings subcontext.warnings = warnings
end end
end end
@@ -187,7 +188,7 @@ module Liquid
try_variable_find_in_environments(key, raise_on_not_found: raise_on_not_found) try_variable_find_in_environments(key, raise_on_not_found: raise_on_not_found)
end end
variable = variable.to_liquid variable = variable.to_liquid
variable.context = self if variable.respond_to?(:context=) variable.context = self if variable.respond_to?(:context=)
variable variable
@@ -245,5 +246,16 @@ module Liquid
rescue Liquid::InternalError => exc rescue Liquid::InternalError => exc
exc exc
end end
def squash_instance_assigns_with_environments
@scopes.last.each_key do |k|
@environments.each do |env|
if env.key?(k)
scopes.last[k] = lookup_and_evaluate(env, k)
break
end
end
end
end # squash_instance_assigns_with_environments
end # Context end # Context
end # Liquid end # Liquid

View File

@@ -40,18 +40,19 @@ module Liquid
end end
end end
ArgumentError = Class.new(Error) ArgumentError = Class.new(Error)
ContextError = Class.new(Error) ContextError = Class.new(Error)
FileSystemError = Class.new(Error) FileSystemError = Class.new(Error)
StandardError = Class.new(Error) StandardError = Class.new(Error)
SyntaxError = Class.new(Error) SyntaxError = Class.new(Error)
StackLevelError = Class.new(Error) StackLevelError = Class.new(Error)
MemoryError = Class.new(Error) TaintedError = Class.new(Error)
ZeroDivisionError = Class.new(Error) MemoryError = Class.new(Error)
FloatDomainError = Class.new(Error) ZeroDivisionError = Class.new(Error)
UndefinedVariable = Class.new(Error) FloatDomainError = Class.new(Error)
UndefinedVariable = Class.new(Error)
UndefinedDropMethod = Class.new(Error) UndefinedDropMethod = Class.new(Error)
UndefinedFilter = Class.new(Error) UndefinedFilter = Class.new(Error)
MethodOverrideError = Class.new(Error) MethodOverrideError = Class.new(Error)
InternalError = Class.new(Error) InternalError = Class.new(Error)
end end

View File

@@ -47,7 +47,7 @@ module Liquid
attr_accessor :root attr_accessor :root
def initialize(root, pattern = "_%s.liquid") def initialize(root, pattern = "_%s.liquid")
@root = root @root = root
@pattern = pattern @pattern = pattern
end end

View File

@@ -3,10 +3,10 @@
module Liquid module Liquid
class ForloopDrop < Drop class ForloopDrop < Drop
def initialize(name, length, parentloop) def initialize(name, length, parentloop)
@name = name @name = name
@length = length @length = length
@parentloop = parentloop @parentloop = parentloop
@index = 0 @index = 0
end end
attr_reader :name, :length, :parentloop attr_reader :name, :length, :parentloop

View File

@@ -15,12 +15,12 @@ module Liquid
'?' => :question, '?' => :question,
'-' => :dash, '-' => :dash,
}.freeze }.freeze
IDENTIFIER = /[a-zA-Z_][\w-]*\??/ IDENTIFIER = /[a-zA-Z_][\w-]*\??/
SINGLE_STRING_LITERAL = /'[^\']*'/ SINGLE_STRING_LITERAL = /'[^\']*'/
DOUBLE_STRING_LITERAL = /"[^\"]*"/ DOUBLE_STRING_LITERAL = /"[^\"]*"/
NUMBER_LITERAL = /-?\d+(\.\d+)?/ NUMBER_LITERAL = /-?\d+(\.\d+)?/
DOTDOT = /\.\./ DOTDOT = /\.\./
COMPARISON_OPERATOR = /==|!=|<>|<=?|>=?|contains(?=\s)/ COMPARISON_OPERATOR = /==|!=|<>|<=?|>=?|contains(?=\s)/
WHITESPACE_OR_NOTHING = /\s*/ WHITESPACE_OR_NOTHING = /\s*/
def initialize(input) def initialize(input)
@@ -33,7 +33,7 @@ module Liquid
until @ss.eos? until @ss.eos?
@ss.skip(WHITESPACE_OR_NOTHING) @ss.skip(WHITESPACE_OR_NOTHING)
break if @ss.eos? break if @ss.eos?
tok = if (t = @ss.scan(COMPARISON_OPERATOR)) tok = if (t = @ss.scan(COMPARISON_OPERATOR))
[:comparison, t] [:comparison, t]
elsif (t = @ss.scan(SINGLE_STRING_LITERAL)) elsif (t = @ss.scan(SINGLE_STRING_LITERAL))
[:string, t] [:string, t]
@@ -46,7 +46,7 @@ module Liquid
elsif (t = @ss.scan(DOTDOT)) elsif (t = @ss.scan(DOTDOT))
[:dotdot, t] [:dotdot, t]
else else
c = @ss.getch c = @ss.getch
if (s = SPECIALS[c]) if (s = SPECIALS[c])
[s, c] [s, c]
else else

View File

@@ -7,11 +7,9 @@ module Liquid
def initialize(options = {}) def initialize(options = {})
@template_options = options ? options.dup : {} @template_options = options ? options.dup : {}
@locale = @template_options[:locale] ||= I18n.new
@locale = @template_options[:locale] ||= I18n.new
@warnings = [] @warnings = []
self.depth = 0
self.depth = 0
self.partial = false self.partial = false
end end
@@ -22,7 +20,6 @@ module Liquid
def partial=(value) def partial=(value)
@partial = value @partial = value
@options = value ? partial_options : @template_options @options = value ? partial_options : @template_options
@error_mode = @options[:error_mode] || Template.error_mode @error_mode = @options[:error_mode] || Template.error_mode
end end

View File

@@ -11,7 +11,7 @@ module Liquid
end end
def initialize(node, callbacks) def initialize(node, callbacks)
@node = node @node = node
@callbacks = callbacks @callbacks = callbacks
end end

View File

@@ -3,9 +3,9 @@
module Liquid module Liquid
class Parser class Parser
def initialize(input) def initialize(input)
l = Lexer.new(input) l = Lexer.new(input)
@tokens = l.tokenize @tokens = l.tokenize
@p = 0 # pointer to current location @p = 0 # pointer to current location
end end
def jump(point) def jump(point)

View File

@@ -8,10 +8,10 @@ module Liquid
when :lax then lax_parse(markup) when :lax then lax_parse(markup)
when :warn when :warn
begin begin
strict_parse_with_error_context(markup) return strict_parse_with_error_context(markup)
rescue SyntaxError => e rescue SyntaxError => e
parse_context.warnings << e parse_context.warnings << e
lax_parse(markup) return lax_parse(markup)
end end
end end
end end
@@ -21,7 +21,7 @@ module Liquid
def strict_parse_with_error_context(markup) def strict_parse_with_error_context(markup)
strict_parse(markup) strict_parse(markup)
rescue SyntaxError => e rescue SyntaxError => e
e.line_number = line_number e.line_number = line_number
e.markup_context = markup_context(markup) e.markup_context = markup_context(markup)
raise e raise e
end end

View File

@@ -8,14 +8,10 @@ module Liquid
return cached if cached return cached if cached
file_system = (context.registers[:file_system] ||= Liquid::Template.file_system) file_system = (context.registers[:file_system] ||= Liquid::Template.file_system)
source = file_system.read_template_file(template_name) source = file_system.read_template_file(template_name)
parse_context.partial = true parse_context.partial = true
template_factory = (context.registers[:template_factory] ||= Liquid::TemplateFactory.new) partial = Liquid::Template.parse(source, parse_context)
template = template_factory.for(template_name)
partial = template.parse(source, parse_context)
cached_partials[template_name] = partial cached_partials[template_name] = partial
ensure ensure
parse_context.partial = false parse_context.partial = false

View File

@@ -46,7 +46,7 @@ module Liquid
include Enumerable include Enumerable
class Timing class Timing
attr_reader :code, :partial, :line_number, :children, :total_time, :self_time attr_reader :code, :partial, :line_number, :children
def initialize(node, partial) def initialize(node, partial)
@code = node.respond_to?(:raw) ? node.raw : node @code = node.respond_to?(:raw) ? node.raw : node
@@ -64,18 +64,7 @@ module Liquid
end end
def finish def finish
@end_time = Time.now @end_time = Time.now
@total_time = @end_time - @start_time
if @children.empty?
@self_time = @total_time
else
total_children_time = 0
@children.each do |child|
total_children_time += child.total_time
end
@self_time = @total_time - total_children_time
end
end end
def render_time def render_time
@@ -109,14 +98,14 @@ module Liquid
Thread.current[:liquid_profiler] Thread.current[:liquid_profiler]
end end
def initialize(partial_name = "<root>") def initialize
@partial_stack = [partial_name] @partial_stack = ["<root>"]
@root_timing = Timing.new("", current_partial) @root_timing = Timing.new("", current_partial)
@timing_stack = [@root_timing] @timing_stack = [@root_timing]
@render_start_at = Time.now @render_start_at = Time.now
@render_end_at = @render_start_at @render_end_at = @render_start_at
end end
def start def start

View File

@@ -4,7 +4,7 @@ module Liquid
class RangeLookup class RangeLookup
def self.parse(start_markup, end_markup) def self.parse(start_markup, end_markup)
start_obj = Expression.parse(start_markup) start_obj = Expression.parse(start_markup)
end_obj = Expression.parse(end_markup) end_obj = Expression.parse(end_markup)
if start_obj.respond_to?(:evaluate) || end_obj.respond_to?(:evaluate) if start_obj.respond_to?(:evaluate) || end_obj.respond_to?(:evaluate)
new(start_obj, end_obj) new(start_obj, end_obj)
else else
@@ -14,12 +14,12 @@ module Liquid
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
end end
def evaluate(context) def evaluate(context)
start_int = to_integer(context.evaluate(@start_obj)) start_int = to_integer(context.evaluate(@start_obj))
end_int = to_integer(context.evaluate(@end_obj)) end_int = to_integer(context.evaluate(@end_obj))
start_int..end_int start_int..end_int
end end

View File

@@ -20,7 +20,7 @@ module Liquid
def increment(tag) def increment(tag)
@disabled_tags[tag] ||= 0 @disabled_tags[tag] ||= 0
@disabled_tags[tag] += 1 @disabled_tags[tag] += 1
end end
def decrement(tag) def decrement(tag)

View File

@@ -7,8 +7,8 @@ module Liquid
def initialize(limits) def initialize(limits)
@render_length_limit = limits[:render_length_limit] @render_length_limit = limits[:render_length_limit]
@render_score_limit = limits[:render_score_limit] @render_score_limit = limits[:render_score_limit]
@assign_score_limit = limits[:assign_score_limit] @assign_score_limit = limits[:assign_score_limit]
reset reset
end end

View File

@@ -13,7 +13,7 @@ module Liquid
"'" => '&#39;', "'" => '&#39;',
}.freeze }.freeze
HTML_ESCAPE_ONCE_REGEXP = /["><']|&(?!([a-zA-Z]+|(#\d+));)/ HTML_ESCAPE_ONCE_REGEXP = /["><']|&(?!([a-zA-Z]+|(#\d+));)/
STRIP_HTML_BLOCKS = Regexp.union( STRIP_HTML_BLOCKS = Regexp.union(
%r{<script.*?</script>}m, %r{<script.*?</script>}m,
/<!--.*?-->/m, /<!--.*?-->/m,
%r{<style.*?</style>}m %r{<style.*?</style>}m
@@ -41,7 +41,7 @@ module Liquid
end end
def escape(input) def escape(input)
CGI.escapeHTML(input.to_s) unless input.nil? CGI.escapeHTML(input.to_s).untaint unless input.nil?
end end
alias_method :h, :escape alias_method :h, :escape
@@ -77,24 +77,19 @@ module Liquid
def truncate(input, length = 50, truncate_string = "...") def truncate(input, length = 50, truncate_string = "...")
return if input.nil? return if input.nil?
input_str = input.to_s input_str = input.to_s
length = Utils.to_integer(length) length = Utils.to_integer(length)
truncate_string_str = truncate_string.to_s truncate_string_str = truncate_string.to_s
l = length - truncate_string_str.length l = length - truncate_string_str.length
l = 0 if l < 0 l = 0 if l < 0
input_str.length > length ? input_str[0...l].concat(truncate_string_str) : input_str input_str.length > length ? input_str[0...l].concat(truncate_string_str) : input_str
end end
def truncatewords(input, words = 15, truncate_string = "...") def truncatewords(input, words = 15, truncate_string = "...")
return if input.nil? return if input.nil?
wordlist = input.to_s.split wordlist = input.to_s.split
words = Utils.to_integer(words) words = Utils.to_integer(words)
l = words - 1 l = words - 1
l = 0 if l < 0 l = 0 if l < 0
wordlist.length > l ? wordlist[0..l].join(" ").concat(truncate_string.to_s) : input wordlist.length > l ? wordlist[0..l].join(" ").concat(truncate_string.to_s) : input
end end
@@ -120,7 +115,7 @@ module Liquid
end end
def strip_html(input) def strip_html(input)
empty = '' empty = ''
result = input.to_s.gsub(STRIP_HTML_BLOCKS, empty) result = input.to_s.gsub(STRIP_HTML_BLOCKS, empty)
result.gsub!(STRIP_HTML_TAGS, empty) result.gsub!(STRIP_HTML_TAGS, empty)
result result
@@ -133,13 +128,13 @@ module Liquid
# Join elements of the array with certain character between them # Join elements of the array with certain character between them
def join(input, glue = ' ') def join(input, glue = ' ')
InputIterator.new(input, context).join(glue) InputIterator.new(input).join(glue)
end end
# Sort elements of the array # Sort elements of the array
# provide optional property with which to sort an array of hashes or drops # provide optional property with which to sort an array of hashes or drops
def sort(input, property = nil) def sort(input, property = nil)
ary = InputIterator.new(input, context) ary = InputIterator.new(input)
return [] if ary.empty? return [] if ary.empty?
@@ -159,7 +154,7 @@ module Liquid
# Sort elements of an array ignoring case if strings # Sort elements of an array ignoring case if strings
# provide optional property with which to sort an array of hashes or drops # provide optional property with which to sort an array of hashes or drops
def sort_natural(input, property = nil) def sort_natural(input, property = nil)
ary = InputIterator.new(input, context) ary = InputIterator.new(input)
return [] if ary.empty? return [] if ary.empty?
@@ -179,7 +174,7 @@ module Liquid
# Filter the elements of an array to those with a certain property value. # Filter the elements of an array to those with a certain property value.
# By default the target is any truthy value. # By default the target is any truthy value.
def where(input, property, target_value = nil) def where(input, property, target_value = nil)
ary = InputIterator.new(input, context) ary = InputIterator.new(input)
if ary.empty? if ary.empty?
[] []
@@ -198,10 +193,27 @@ module Liquid
end end
end end
# Sort elements of an array in numeric order
# provide optional property with which to sort an array of hashes or drops
def sort_numeric(input, property = nil)
ary = InputIterator.new(input)
if property.nil?
ary.sort do |a, b|
Utils.to_number(a) <=> Utils.to_number(b)
end
elsif ary.empty? # The next two cases assume a non-empty array.
[]
elsif ary.first.respond_to?(:[]) && !ary.first[property].nil?
ary.sort do |a, b|
Utils.to_number(a[property]) <=> Utils.to_number(b[property])
end
end
end
# Remove duplicate elements from an array # Remove duplicate elements from an array
# provide optional property with which to determine uniqueness # provide optional property with which to determine uniqueness
def uniq(input, property = nil) def uniq(input, property = nil)
ary = InputIterator.new(input, context) ary = InputIterator.new(input)
if property.nil? if property.nil?
ary.uniq ary.uniq
@@ -218,13 +230,13 @@ module Liquid
# Reverse the elements of an array # Reverse the elements of an array
def reverse(input) def reverse(input)
ary = InputIterator.new(input, context) ary = InputIterator.new(input)
ary.reverse ary.reverse
end end
# map/collect on a given property # map/collect on a given property
def map(input, property) def map(input, property)
InputIterator.new(input, context).map do |e| InputIterator.new(input).map do |e|
e = e.call if e.is_a?(Proc) e = e.call if e.is_a?(Proc)
if property == "to_liquid" if property == "to_liquid"
@@ -241,7 +253,7 @@ module Liquid
# Remove nils within an array # Remove nils within an array
# provide optional property with which to check for nil # provide optional property with which to check for nil
def compact(input, property = nil) def compact(input, property = nil)
ary = InputIterator.new(input, context) ary = InputIterator.new(input)
if property.nil? if property.nil?
ary.compact ary.compact
@@ -285,7 +297,7 @@ module Liquid
unless array.respond_to?(:to_ary) unless array.respond_to?(:to_ary)
raise ArgumentError, "concat filter requires an array argument" raise ArgumentError, "concat filter requires an array argument"
end end
InputIterator.new(input, context).concat(array) InputIterator.new(input).concat(array)
end end
# prepend a string to another # prepend a string to another
@@ -426,26 +438,17 @@ module Liquid
result.is_a?(BigDecimal) ? result.to_f : result result.is_a?(BigDecimal) ? result.to_f : result
end end
# Set a default value when the input is nil, false or empty def default(input, default_value = '')
# if !input || input.respond_to?(:empty?) && input.empty?
# Example: Usage.increment("default_filter_received_false_value") if input == false # See https://github.com/Shopify/liquid/issues/1127
# {{ product.title | default: "No Title" }} default_value
# else
# Use `allow_false` when an input should only be tested against nil or empty and not false. input
# end
# Example:
# {{ product.title | default: "No Title", allow_false: true }}
#
def default(input, default_value = '', options = {})
options = {} unless options.is_a?(Hash)
false_check = options['allow_false'] ? input.nil? : !input
false_check || (input.respond_to?(:empty?) && input.empty?) ? default_value : input
end end
private private
attr_reader :context
def raise_property_error(property) def raise_property_error(property)
raise Liquid::ArgumentError, "cannot select the property '#{property}'" raise Liquid::ArgumentError, "cannot select the property '#{property}'"
end end
@@ -474,9 +477,8 @@ module Liquid
class InputIterator class InputIterator
include Enumerable include Enumerable
def initialize(input, context) def initialize(input)
@context = context @input = if input.is_a?(Array)
@input = if input.is_a?(Array)
input.flatten input.flatten
elsif input.is_a?(Hash) elsif input.is_a?(Hash)
[input] [input]
@@ -514,7 +516,6 @@ module Liquid
def each def each
@input.each do |e| @input.each do |e|
e.context = @context if e.respond_to?(:context=)
yield(e.respond_to?(:to_liquid) ? e.to_liquid : e) yield(e.respond_to?(:to_liquid) ? e.to_liquid : e)
end end
end end

View File

@@ -2,10 +2,10 @@
module Liquid module Liquid
class StaticRegisters class StaticRegisters
attr_reader :static attr_reader :static, :registers
def initialize(registers = {}) def initialize(registers = {})
@static = registers.is_a?(StaticRegisters) ? registers.static : registers @static = registers.is_a?(StaticRegisters) ? registers.static : registers
@registers = {} @registers = {}
end end
@@ -25,16 +25,8 @@ module Liquid
@registers.delete(key) @registers.delete(key)
end end
UNDEFINED = Object.new def fetch(key, default = nil)
key?(key) ? self[key] : default
def fetch(key, default = UNDEFINED, &block)
if @registers.key?(key)
@registers.fetch(key)
elsif default != UNDEFINED
@static.fetch(key, default, &block)
else
@static.fetch(key, &block)
end
end end
def key?(key) def key?(key)

68
lib/liquid/strainer.rb Normal file
View File

@@ -0,0 +1,68 @@
# frozen_string_literal: true
require 'set'
module Liquid
# Strainer is the parent class for the filters system.
# New filters are mixed into the strainer class which is then instantiated for each liquid template render run.
#
# The Strainer only allows method calls defined in filters given to it via Strainer.global_filter,
# Context#add_filters or Template.register_filter
class Strainer #:nodoc:
@@global_strainer = Class.new(Strainer) do
@filter_methods = Set.new
end
@@strainer_class_cache = Hash.new do |hash, filters|
hash[filters] = Class.new(@@global_strainer) do
@filter_methods = @@global_strainer.filter_methods.dup
filters.each { |f| add_filter(f) }
end
end
def initialize(context)
@context = context
end
class << self
attr_reader :filter_methods
end
def self.add_filter(filter)
raise ArgumentError, "Expected module but got: #{filter.class}" unless filter.is_a?(Module)
unless 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(', ')}"
else
send(:include, filter)
@filter_methods.merge(filter.public_instance_methods.map(&:to_s))
end
end
end
def self.global_filter(filter)
@@strainer_class_cache.clear
@@global_strainer.add_filter(filter)
end
def self.invokable?(method)
@filter_methods.include?(method.to_s)
end
def self.create(context, filters = [])
@@strainer_class_cache[filters].new(context)
end
def invoke(method, *args)
if self.class.invokable?(method)
send(method, *args)
elsif @context&.strict_filters
raise Liquid::UndefinedFilter, "undefined filter #{method}"
else
args.first
end
rescue ::ArgumentError => e
raise Liquid::ArgumentError, e.message, e.backtrace
end
end
end

View File

@@ -1,36 +0,0 @@
# frozen_string_literal: true
module Liquid
# StrainerFactory is the factory for the filters system.
module StrainerFactory
extend self
def add_global_filter(filter)
strainer_class_cache.clear
global_filters << filter
end
def create(context, filters = [])
strainer_from_cache(filters).new(context)
end
private
def global_filters
@global_filters ||= []
end
def strainer_from_cache(filters)
strainer_class_cache[filters] ||= begin
klass = Class.new(StrainerTemplate)
global_filters.each { |f| klass.add_filter(f) }
filters.each { |f| klass.add_filter(f) }
klass
end
end
def strainer_class_cache
@strainer_class_cache ||= {}
end
end
end

View File

@@ -1,53 +0,0 @@
# frozen_string_literal: true
require 'set'
module Liquid
# StrainerTemplate is the computed class for the filters system.
# New filters are mixed into the strainer class which is then instantiated for each liquid template render run.
#
# The Strainer only allows method calls defined in filters given to it via StrainerFactory.add_global_filter,
# Context#add_filters or Template.register_filter
class StrainerTemplate
def initialize(context)
@context = context
end
class << self
def add_filter(filter)
return if 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(', ')}"
end
include(filter)
filter_methods.merge(filter.public_instance_methods.map(&:to_s))
end
def invokable?(method)
filter_methods.include?(method.to_s)
end
private
def filter_methods
@filter_methods ||= Set.new
end
end
def invoke(method, *args)
if self.class.invokable?(method)
send(method, *args)
elsif @context.strict_filters
raise Liquid::UndefinedFilter, "undefined filter #{method}"
else
args.first
end
rescue ::ArgumentError => e
raise Liquid::ArgumentError, e.message, e.backtrace
end
end
end

View File

@@ -4,10 +4,10 @@ module Liquid
class TablerowloopDrop < Drop class TablerowloopDrop < Drop
def initialize(length, cols) def initialize(length, cols)
@length = length @length = length
@row = 1 @row = 1
@col = 1 @col = 1
@cols = cols @cols = cols
@index = 0 @index = 0
end end
attr_reader :length, :col, :row attr_reader :length, :col, :row

View File

@@ -25,10 +25,10 @@ module Liquid
end end
def initialize(tag_name, markup, parse_context) def initialize(tag_name, markup, parse_context)
@tag_name = tag_name @tag_name = tag_name
@markup = markup @markup = markup
@parse_context = parse_context @parse_context = parse_context
@line_number = parse_context.line_number @line_number = parse_context.line_number
end end
def parse(_tokens) def parse(_tokens)

View File

@@ -12,15 +12,19 @@ module Liquid
class Assign < Tag class Assign < Tag
Syntax = /(#{VariableSignature}+)\s*=\s*(.*)\s*/om Syntax = /(#{VariableSignature}+)\s*=\s*(.*)\s*/om
def self.syntax_error_translation_key
"errors.syntax.assign"
end
attr_reader :to, :from attr_reader :to, :from
def initialize(tag_name, markup, options) def initialize(tag_name, markup, options)
super super
if markup =~ Syntax if markup =~ Syntax
@to = Regexp.last_match(1) @to = Regexp.last_match(1)
@from = Variable.new(Regexp.last_match(2), options) @from = Variable.new(Regexp.last_match(2), options)
else else
raise SyntaxError, options[:locale].t('errors.syntax.assign') raise SyntaxError, options[:locale].t(self.class.syntax_error_translation_key)
end end
end end

View File

@@ -24,10 +24,10 @@ module Liquid
case markup case markup
when NamedSyntax when NamedSyntax
@variables = variables_from_string(Regexp.last_match(2)) @variables = variables_from_string(Regexp.last_match(2))
@name = Expression.parse(Regexp.last_match(1)) @name = Expression.parse(Regexp.last_match(1))
when SimpleSyntax when SimpleSyntax
@variables = variables_from_string(markup) @variables = variables_from_string(markup)
@name = @variables.to_s @name = @variables.to_s
else else
raise SyntaxError, options[:locale].t("errors.syntax.cycle") raise SyntaxError, options[:locale].t("errors.syntax.cycle")
end end
@@ -36,7 +36,7 @@ module Liquid
def render_to_output_buffer(context, output) def render_to_output_buffer(context, output)
context.registers[:cycle] ||= {} context.registers[:cycle] ||= {}
key = context.evaluate(@name) key = context.evaluate(@name)
iteration = context.registers[:cycle][key].to_i iteration = context.registers[:cycle][key].to_i
val = context.evaluate(@variables[iteration]) val = context.evaluate(@variables[iteration])
@@ -50,9 +50,9 @@ module Liquid
output << val output << val
iteration += 1 iteration += 1
iteration = 0 if iteration >= @variables.size iteration = 0 if iteration >= @variables.size
context.registers[:cycle][key] = iteration context.registers[:cycle][key] = iteration
output output
end end

View File

@@ -88,10 +88,10 @@ module Liquid
def lax_parse(markup) def lax_parse(markup)
if markup =~ Syntax if markup =~ Syntax
@variable_name = Regexp.last_match(1) @variable_name = Regexp.last_match(1)
collection_name = Regexp.last_match(2) collection_name = Regexp.last_match(2)
@reversed = !!Regexp.last_match(3) @reversed = !!Regexp.last_match(3)
@name = "#{@variable_name}-#{collection_name}" @name = "#{@variable_name}-#{collection_name}"
@collection_name = Expression.parse(collection_name) @collection_name = Expression.parse(collection_name)
markup.scan(TagAttributes) do |key, value| markup.scan(TagAttributes) do |key, value|
set_attribute(key, value) set_attribute(key, value)
@@ -105,11 +105,9 @@ module Liquid
p = Parser.new(markup) p = Parser.new(markup)
@variable_name = p.consume(:id) @variable_name = p.consume(:id)
raise SyntaxError, options[:locale].t("errors.syntax.for_invalid_in") unless p.id?('in') raise SyntaxError, options[:locale].t("errors.syntax.for_invalid_in") unless p.id?('in')
collection_name = p.expression
collection_name = p.expression @name = "#{@variable_name}-#{collection_name}"
@collection_name = Expression.parse(collection_name) @collection_name = Expression.parse(collection_name)
@name = "#{@variable_name}-#{collection_name}"
@reversed = p.id?('reversed') @reversed = p.id?('reversed')
while p.look(:id) && p.look(:colon, 1) while p.look(:id) && p.look(:colon, 1)
@@ -158,7 +156,7 @@ module Liquid
def render_segment(context, output, segment) def render_segment(context, output, segment)
for_stack = context.registers[:for_stack] ||= [] for_stack = context.registers[:for_stack] ||= []
length = segment.length length = segment.length
context.stack do context.stack do
loop_vars = Liquid::ForloopDrop.new(@name, length, for_stack[-1]) loop_vars = Liquid::ForloopDrop.new(@name, length, for_stack[-1])

View File

@@ -12,9 +12,9 @@ module Liquid
# There are {% if count < 5 %} less {% else %} more {% endif %} items than you need. # There are {% if count < 5 %} less {% else %} more {% endif %} items than you need.
# #
class If < Block class If < Block
Syntax = /(#{QuotedFragment})\s*([=!<>a-z_]+)?\s*(#{QuotedFragment})?/o Syntax = /(#{QuotedFragment})\s*([=!<>a-z_]+)?\s*(#{QuotedFragment})?/o
ExpressionsAndOperators = /(?:\b(?:\s?and\s?|\s?or\s?)\b|(?:\s*(?!\b(?:\s?and\s?|\s?or\s?)\b)(?:#{QuotedFragment}|\S+)\s*)+)/o ExpressionsAndOperators = /(?:\b(?:\s?and\s?|\s?or\s?)\b|(?:\s*(?!\b(?:\s?and\s?|\s?or\s?)\b)(?:#{QuotedFragment}|\S+)\s*)+)/o
BOOLEAN_OPERATORS = %w(and or).freeze BOOLEAN_OPERATORS = %w(and or).freeze
attr_reader :blocks attr_reader :blocks

View File

@@ -16,23 +16,21 @@ module Liquid
# {% include 'product' for products %} # {% include 'product' for products %}
# #
class Include < Tag class Include < Tag
SYNTAX = /(#{QuotedFragment}+)(\s+(?:with|for)\s+(#{QuotedFragment}+))?(\s+(?:as)\s+(#{VariableSegment}+))?/o Syntax = /(#{QuotedFragment}+)(\s+(?:with|for)\s+(#{QuotedFragment}+))?/o
Syntax = SYNTAX
attr_reader :template_name_expr, :variable_name_expr, :attributes attr_reader :template_name_expr, :variable_name_expr, :attributes
def initialize(tag_name, markup, options) def initialize(tag_name, markup, options)
super super
if markup =~ SYNTAX if markup =~ Syntax
template_name = Regexp.last_match(1) template_name = Regexp.last_match(1)
variable_name = Regexp.last_match(3) variable_name = Regexp.last_match(3)
@alias_name = Regexp.last_match(5)
@variable_name_expr = variable_name ? Expression.parse(variable_name) : nil @variable_name_expr = variable_name ? Expression.parse(variable_name) : nil
@template_name_expr = Expression.parse(template_name) @template_name_expr = Expression.parse(template_name)
@attributes = {} @attributes = {}
markup.scan(TagAttributes) do |key, value| markup.scan(TagAttributes) do |key, value|
@attributes[key] = Expression.parse(value) @attributes[key] = Expression.parse(value)
@@ -56,7 +54,7 @@ module Liquid
parse_context: parse_context parse_context: parse_context
) )
context_variable_name = @alias_name || template_name.split('/').last context_variable_name = template_name.split('/').last
variable = if @variable_name_expr variable = if @variable_name_expr
context.evaluate(@variable_name_expr) context.evaluate(@variable_name_expr)
@@ -65,10 +63,10 @@ module Liquid
end end
old_template_name = context.template_name old_template_name = context.template_name
old_partial = context.partial old_partial = context.partial
begin begin
context.template_name = template_name context.template_name = template_name
context.partial = true context.partial = true
context.stack do context.stack do
@attributes.each do |key, value| @attributes.each do |key, value|
context[key] = context.evaluate(value) context[key] = context.evaluate(value)
@@ -86,7 +84,7 @@ module Liquid
end end
ensure ensure
context.template_name = old_template_name context.template_name = old_template_name
context.partial = old_partial context.partial = old_partial
end end
output output

View File

@@ -2,8 +2,7 @@
module Liquid module Liquid
class Render < Tag class Render < Tag
FOR = 'for' SYNTAX = /(#{QuotedString})#{QuotedFragment}*/o
SYNTAX = /(#{QuotedString}+)(\s+(with|#{FOR})\s+(#{QuotedFragment}+))?(\s+(?:as)\s+(#{VariableSegment}+))?/o
disable_tags "include" disable_tags "include"
@@ -15,13 +14,8 @@ module Liquid
raise SyntaxError, options[:locale].t("errors.syntax.render") unless markup =~ SYNTAX raise SyntaxError, options[:locale].t("errors.syntax.render") unless markup =~ SYNTAX
template_name = Regexp.last_match(1) template_name = Regexp.last_match(1)
with_or_for = Regexp.last_match(3)
variable_name = Regexp.last_match(4)
@alias_name = Regexp.last_match(6)
@variable_name_expr = variable_name ? Expression.parse(variable_name) : nil
@template_name_expr = Expression.parse(template_name) @template_name_expr = Expression.parse(template_name)
@for = (with_or_for == FOR)
@attributes = {} @attributes = {}
markup.scan(TagAttributes) do |key, value| markup.scan(TagAttributes) do |key, value|
@@ -44,29 +38,13 @@ module Liquid
parse_context: parse_context parse_context: parse_context
) )
context_variable_name = @alias_name || template_name.split('/').last inner_context = context.new_isolated_subcontext
inner_context.template_name = template_name
render_partial_func = ->(var, forloop) { inner_context.partial = true
inner_context = context.new_isolated_subcontext @attributes.each do |key, value|
inner_context.template_name = template_name inner_context[key] = context.evaluate(value)
inner_context.partial = true
inner_context['forloop'] = forloop if forloop
@attributes.each do |key, value|
inner_context[key] = context.evaluate(value)
end
inner_context[context_variable_name] = var unless var.nil?
partial.render_to_output_buffer(inner_context, output)
forloop&.send(:increment!)
}
variable = @variable_name_expr ? context.evaluate(@variable_name_expr) : nil
if @for && variable.respond_to?(:each) && variable.respond_to?(:count)
forloop = Liquid::ForloopDrop.new(template_name, variable.count, nil)
variable.each { |var| render_partial_func.call(var, forloop) }
else
render_partial_func.call(variable, nil)
end end
partial.render_to_output_buffer(inner_context, output)
output output
end end

View File

@@ -9,9 +9,9 @@ module Liquid
def initialize(tag_name, markup, options) def initialize(tag_name, markup, options)
super super
if markup =~ Syntax if markup =~ Syntax
@variable_name = Regexp.last_match(1) @variable_name = Regexp.last_match(1)
@collection_name = Expression.parse(Regexp.last_match(2)) @collection_name = Expression.parse(Regexp.last_match(2))
@attributes = {} @attributes = {}
markup.scan(TagAttributes) do |key, value| markup.scan(TagAttributes) do |key, value|
@attributes[key] = Expression.parse(value) @attributes[key] = Expression.parse(value)
end end
@@ -24,10 +24,11 @@ module Liquid
(collection = context.evaluate(@collection_name)) || (return '') (collection = context.evaluate(@collection_name)) || (return '')
from = @attributes.key?('offset') ? context.evaluate(@attributes['offset']).to_i : 0 from = @attributes.key?('offset') ? context.evaluate(@attributes['offset']).to_i : 0
to = @attributes.key?('limit') ? from + context.evaluate(@attributes['limit']).to_i : nil to = @attributes.key?('limit') ? from + context.evaluate(@attributes['limit']).to_i : nil
collection = Utils.slice_collection(collection, from, to) collection = Utils.slice_collection(collection, from, to)
length = collection.length
length = collection.length
cols = context.evaluate(@attributes['cols']).to_i cols = context.evaluate(@attributes['cols']).to_i

View File

@@ -16,13 +16,15 @@ module Liquid
# #
class Template class Template
attr_accessor :root attr_accessor :root
attr_reader :warnings attr_reader :resource_limits, :warnings
@@file_system = BlankFileSystem.new
class TagRegistry class TagRegistry
include Enumerable include Enumerable
def initialize def initialize
@tags = {} @tags = {}
@cache = {} @cache = {}
end end
@@ -61,63 +63,103 @@ module Liquid
# :lax acts like liquid 2.5 and silently ignores malformed tags in most cases. # :lax acts like liquid 2.5 and silently ignores malformed tags in most cases.
# :warn is the default and will give deprecation warnings when invalid syntax is used. # :warn is the default and will give deprecation warnings when invalid syntax is used.
# :strict will enforce correct syntax. # :strict will enforce correct syntax.
attr_accessor :error_mode attr_writer :error_mode
Template.error_mode = :lax
# Sets how strict the taint checker should be.
# :lax is the default, and ignores the taint flag completely
# :warn adds a warning, but does not interrupt the rendering
# :error raises an error when tainted output is used
attr_writer :taint_mode
attr_accessor :default_exception_renderer attr_accessor :default_exception_renderer
Template.default_exception_renderer = lambda do |exception| Template.default_exception_renderer = lambda do |exception|
exception exception
end end
attr_accessor :file_system def file_system
Template.file_system = BlankFileSystem.new @@file_system
end
attr_accessor :tags def file_system=(obj)
Template.tags = TagRegistry.new @@file_system = obj
private :tags= end
def register_tag(name, klass) def register_tag(name, klass)
tags[name.to_s] = klass tags[name.to_s] = klass
end end
attr_accessor :registers def tags
Template.registers = {} @tags ||= TagRegistry.new
private :registers= end
def add_register(name, klass) def add_register(name, klass)
registers[name.to_sym] = klass registers[name.to_sym] = klass
end end
def registers
@registers ||= {}
end
def error_mode
@error_mode ||= :lax
end
def taint_mode
@taint_mode ||= :lax
end
# Pass a module with filter methods which should be available # Pass a module with filter methods which should be available
# to all liquid views. Good for registering the standard library # to all liquid views. Good for registering the standard library
def register_filter(mod) def register_filter(mod)
StrainerFactory.add_global_filter(mod) Strainer.global_filter(mod)
end end
attr_accessor :default_resource_limits def default_resource_limits
Template.default_resource_limits = {} @default_resource_limits ||= {}
private :default_resource_limits= end
# creates a new <tt>Template</tt> object from liquid source code # creates a new <tt>Template</tt> object from liquid source code
# To enable profiling, pass in <tt>profile: true</tt> as an option. # To enable profiling, pass in <tt>profile: true</tt> as an option.
# See Liquid::Profiler for more information # See Liquid::Profiler for more information
def parse(source, options = {}) def parse(source, options = {})
new.parse(source, options) template = Template.new
template.parse(source, options)
end end
end end
def initialize
@rethrow_errors = false
@resource_limits = ResourceLimits.new(self.class.default_resource_limits)
end
# Parse source code. # Parse source code.
# Returns self for easy chaining # Returns self for easy chaining
def parse(source, options = {}) def parse(source, options = {})
@options = options @options = options
@profiling = options[:profile] @profiling = options[:profile]
@line_numbers = options[:line_numbers] || @profiling @line_numbers = options[:line_numbers] || @profiling
parse_context = options.is_a?(ParseContext) ? options : ParseContext.new(options) parse_context = options.is_a?(ParseContext) ? options : ParseContext.new(options)
@root = Document.parse(tokenize(source), parse_context) @root = Document.parse(tokenize(source), parse_context)
@warnings = parse_context.warnings @warnings = parse_context.warnings
self self
end end
def registers
@registers ||= {}
end
def assigns
@assigns ||= {}
end
def instance_assigns
@instance_assigns ||= {}
end
def errors
@errors ||= []
end
# Render takes a hash with local variables. # Render takes a hash with local variables.
# #
# if you use the same filters over and over again consider registering them globally # if you use the same filters over and over again consider registering them globally
@@ -132,17 +174,36 @@ module Liquid
# * <tt>registers</tt> : hash with register variables. Those can be accessed from # * <tt>registers</tt> : hash with register variables. Those can be accessed from
# filters and tags and might be useful to integrate liquid more with its host application # filters and tags and might be useful to integrate liquid more with its host application
# #
def render(assigns_or_context = nil, options = nil) def render(*args)
return '' if @root.nil? return '' if @root.nil?
context = coerce_context(assigns_or_context) context = case args.first
when Liquid::Context
c = args.shift
if @rethrow_errors
c.exception_renderer = ->(_e) { raise }
end
c
when Liquid::Drop
drop = args.shift
drop.context = Context.new([drop, assigns], instance_assigns, registers, @rethrow_errors, @resource_limits)
when Hash
Context.new([args.shift, assigns], instance_assigns, registers, @rethrow_errors, @resource_limits)
when nil
Context.new(assigns, instance_assigns, registers, @rethrow_errors, @resource_limits)
else
raise ArgumentError, "Expected Hash or Liquid::Context as parameter"
end
output = nil output = nil
context_register = context.registers.is_a?(StaticRegisters) ? context.registers.static : context.registers context_register = context.registers.is_a?(StaticRegisters) ? context.registers.static : context.registers
case options case args.last
when Hash when Hash
options = args.pop
output = options[:output] if options[:output] output = options[:output] if options[:output]
options[:registers]&.each do |key, register| options[:registers]&.each do |key, register|
@@ -151,11 +212,11 @@ module Liquid
apply_options_to_context(context, options) apply_options_to_context(context, options)
when Module, Array when Module, Array
context.add_filters(options) context.add_filters(args.pop)
end end
Template.registers.each do |key, register| Template.registers.each do |key, register|
context_register[key] = register unless context_register.key?(key) context_register[key] = register
end end
# Retrying a render resets resource usage # Retrying a render resets resource usage
@@ -169,15 +230,14 @@ module Liquid
end end
rescue Liquid::MemoryError => e rescue Liquid::MemoryError => e
context.handle_error(e) context.handle_error(e)
ensure
@errors = context.errors
end end
end end
def render!(assigns_or_context = nil, options = nil) def render!(*args)
context = coerce_context(assigns_or_context) @rethrow_errors = true
# rethrow errors render(*args)
context.exception_renderer = ->(_e) { raise }
render(context, options)
end end
def render_to_output_buffer(context, output) def render_to_output_buffer(context, output)
@@ -186,22 +246,6 @@ module Liquid
private private
def coerce_context(assigns_or_context)
case assigns_or_context
when Liquid::Context
assigns_or_context
when Liquid::Drop
drop = assigns_or_context
drop.context = Context.build(environments: [drop])
when Hash
Context.build(environments: [assigns_or_context])
when nil
Context.build
else
raise ArgumentError, "Expected Hash or Liquid::Context as parameter"
end
end
def tokenize(source) def tokenize(source)
Tokenizer.new(source, @line_numbers) Tokenizer.new(source, @line_numbers)
end end
@@ -210,7 +254,7 @@ module Liquid
if @profiling && !context.partial if @profiling && !context.partial
raise "Profiler not loaded, require 'liquid/profiler' first" unless defined?(Liquid::Profiler) raise "Profiler not loaded, require 'liquid/profiler' first" unless defined?(Liquid::Profiler)
@profiler = Profiler.new(context.template_name) @profiler = Profiler.new
@profiler.start @profiler.start
begin begin
@@ -225,10 +269,10 @@ module Liquid
def apply_options_to_context(context, options) def apply_options_to_context(context, options)
context.add_filters(options[:filters]) if options[:filters] context.add_filters(options[:filters]) if options[:filters]
context.global_filter = options[:global_filter] if options[:global_filter] context.global_filter = options[:global_filter] if options[:global_filter]
context.exception_renderer = options[:exception_renderer] if options[:exception_renderer] context.exception_renderer = options[:exception_renderer] if options[:exception_renderer]
context.strict_variables = options[:strict_variables] if options[:strict_variables] context.strict_variables = options[:strict_variables] if options[:strict_variables]
context.strict_filters = options[:strict_filters] if options[:strict_filters] context.strict_filters = options[:strict_filters] if options[:strict_filters]
end end
end end
end end

View File

@@ -1,9 +0,0 @@
# frozen_string_literal: true
module Liquid
class TemplateFactory
def for(_template_name)
Liquid::Template.new
end
end
end

View File

@@ -5,10 +5,10 @@ module Liquid
attr_reader :line_number, :for_liquid_tag attr_reader :line_number, :for_liquid_tag
def initialize(source, line_numbers = false, line_number: nil, for_liquid_tag: false) def initialize(source, line_numbers = false, line_number: nil, for_liquid_tag: false)
@source = source @source = source
@line_number = line_number || (line_numbers ? 1 : nil) @line_number = line_number || (line_numbers ? 1 : nil)
@for_liquid_tag = for_liquid_tag @for_liquid_tag = for_liquid_tag
@tokens = tokenize @tokens = tokenize
end end
def shift def shift

View File

@@ -12,7 +12,7 @@ module Liquid
def self.slice_collection_using_each(collection, from, to) def self.slice_collection_using_each(collection, from, to)
segments = [] segments = []
index = 0 index = 0
# Maintains Ruby 1.8.7 String#each behaviour on 1.9 # Maintains Ruby 1.8.7 String#each behaviour on 1.9
if collection.is_a?(String) if collection.is_a?(String)

View File

@@ -12,10 +12,10 @@ module Liquid
# {{ user | link }} # {{ user | link }}
# #
class Variable class Variable
FilterMarkupRegex = /#{FilterSeparator}\s*(.*)/om FilterMarkupRegex = /#{FilterSeparator}\s*(.*)/om
FilterParser = /(?:\s+|#{QuotedFragment}|#{ArgumentSeparator})+/o FilterParser = /(?:\s+|#{QuotedFragment}|#{ArgumentSeparator})+/o
FilterArgsRegex = /(?:#{FilterArgumentSeparator}|#{ArgumentSeparator})\s*((?:\w+\s*\:\s*)?#{QuotedFragment})/o FilterArgsRegex = /(?:#{FilterArgumentSeparator}|#{ArgumentSeparator})\s*((?:\w+\s*\:\s*)?#{QuotedFragment})/o
JustTagAttributes = /\A#{TagAttributes}\z/o JustTagAttributes = /\A#{TagAttributes}\z/o
MarkupWithQuotedFragment = /(#{QuotedFragment})(.*)/om MarkupWithQuotedFragment = /(#{QuotedFragment})(.*)/om
attr_accessor :filters, :name, :line_number attr_accessor :filters, :name, :line_number
@@ -25,10 +25,10 @@ module Liquid
include ParserSwitching include ParserSwitching
def initialize(markup, parse_context) def initialize(markup, parse_context)
@markup = markup @markup = markup
@name = nil @name = nil
@parse_context = parse_context @parse_context = parse_context
@line_number = parse_context.line_number @line_number = parse_context.line_number
parse_with_selected_parser(markup) parse_with_selected_parser(markup)
end end
@@ -45,9 +45,9 @@ module Liquid
@filters = [] @filters = []
return unless markup =~ MarkupWithQuotedFragment return unless markup =~ MarkupWithQuotedFragment
name_markup = Regexp.last_match(1) name_markup = Regexp.last_match(1)
filter_markup = Regexp.last_match(2) filter_markup = Regexp.last_match(2)
@name = Expression.parse(name_markup) @name = Expression.parse(name_markup)
if filter_markup =~ FilterMarkupRegex if filter_markup =~ FilterMarkupRegex
filters = Regexp.last_match(1).scan(FilterParser) filters = Regexp.last_match(1).scan(FilterParser)
filters.each do |f| filters.each do |f|
@@ -86,7 +86,9 @@ module Liquid
context.invoke(filter_name, output, *filter_args) context.invoke(filter_name, output, *filter_args)
end end
context.apply_global_filter(obj) obj = context.apply_global_filter(obj)
taint_check(context, obj)
obj
end end
def render_to_output_buffer(context, output) def render_to_output_buffer(context, output)
@@ -113,11 +115,11 @@ module Liquid
private private
def parse_filter_expressions(filter_name, unparsed_args) def parse_filter_expressions(filter_name, unparsed_args)
filter_args = [] filter_args = []
keyword_args = nil keyword_args = nil
unparsed_args.each do |a| unparsed_args.each do |a|
if (matches = a.match(JustTagAttributes)) if (matches = a.match(JustTagAttributes))
keyword_args ||= {} keyword_args ||= {}
keyword_args[matches[1]] = Expression.parse(matches[2]) keyword_args[matches[1]] = Expression.parse(matches[2])
else else
filter_args << Expression.parse(a) filter_args << Expression.parse(a)
@@ -140,6 +142,25 @@ module Liquid
parsed_args parsed_args
end end
def taint_check(context, obj)
return unless obj.tainted?
return if Template.taint_mode == :lax
@markup =~ QuotedFragment
name = Regexp.last_match(0)
error = TaintedError.new("variable '#{name}' is tainted and was not escaped")
error.line_number = line_number
error.template_name = context.template_name
case Template.taint_mode
when :warn
context.warnings << error
when :error
raise error
end
end
class ParseTreeVisitor < Liquid::ParseTreeVisitor class ParseTreeVisitor < Liquid::ParseTreeVisitor
def children def children
[@node.name] + @node.filters.flatten [@node.name] + @node.filters.flatten

View File

@@ -3,7 +3,7 @@
module Liquid module Liquid
class VariableLookup class VariableLookup
SQUARE_BRACKETED = /\A\[(.*)\]\z/m SQUARE_BRACKETED = /\A\[(.*)\]\z/m
COMMAND_METHODS = ['size', 'first', 'last'].freeze COMMAND_METHODS = ['size', 'first', 'last'].freeze
attr_reader :name, :lookups attr_reader :name, :lookups
@@ -20,7 +20,7 @@ module Liquid
end end
@name = name @name = name
@lookups = lookups @lookups = lookups
@command_flags = 0 @command_flags = 0
@lookups.each_index do |i| @lookups.each_index do |i|
@@ -34,7 +34,7 @@ module Liquid
end end
def evaluate(context) def evaluate(context)
name = context.evaluate(@name) name = context.evaluate(@name)
object = context.find_variable(name) object = context.find_variable(name)
@lookups.each_index do |i| @lookups.each_index do |i|
@@ -47,7 +47,7 @@ module Liquid
(object.respond_to?(:fetch) && key.is_a?(Integer))) (object.respond_to?(:fetch) && key.is_a?(Integer)))
# if its a proc we will replace the entry with the proc # if its a proc we will replace the entry with the proc
res = context.lookup_and_evaluate(object, key) res = context.lookup_and_evaluate(object, key)
object = res.to_liquid object = res.to_liquid
# Some special cases. If the part wasn't in square brackets and # Some special cases. If the part wasn't in square brackets and

View File

@@ -27,6 +27,6 @@ Gem::Specification.new do |s|
s.require_path = "lib" s.require_path = "lib"
s.add_development_dependency('rake', '~> 13.0') s.add_development_dependency('rake', '~> 11.3')
s.add_development_dependency('minitest') s.add_development_dependency('minitest')
end end

View File

@@ -7,7 +7,7 @@ Liquid::Template.error_mode = ARGV.first.to_sym if ARGV.first
profiler = ThemeRunner.new profiler = ThemeRunner.new
Benchmark.ips do |x| Benchmark.ips do |x|
x.time = 10 x.time = 10
x.warmup = 5 x.warmup = 5
puts puts

View File

@@ -21,8 +21,8 @@ class Profiler
end end
def profile(phase, &block) def profile(phase, &block)
print(LOG_LABEL) print LOG_LABEL
print("#{phase}.. ".ljust(10)) print "#{phase}.. ".ljust(10)
report = MemoryProfiler.report(&block) report = MemoryProfiler.report(&block)
puts 'Done.' puts 'Done.'
@headings << phase.capitalize @headings << phase.capitalize

View File

@@ -8,7 +8,7 @@ class CommentForm < Liquid::Block
if markup =~ Syntax if markup =~ Syntax
@variable_name = Regexp.last_match(1) @variable_name = Regexp.last_match(1)
@attributes = {} @attributes = {}
else else
raise SyntaxError, "Syntax Error in 'comment_form' - Valid syntax: comment_form [article]" raise SyntaxError, "Syntax Error in 'comment_form' - Valid syntax: comment_form [article]"
end end

View File

@@ -8,7 +8,7 @@ class Paginate < Liquid::Block
if markup =~ Syntax if markup =~ Syntax
@collection_name = Regexp.last_match(1) @collection_name = Regexp.last_match(1)
@page_size = if Regexp.last_match(2) @page_size = if Regexp.last_match(2)
Regexp.last_match(3).to_i Regexp.last_match(3).to_i
else else
20 20

View File

@@ -31,7 +31,7 @@ module ShopFilter
def link_to_vendor(vendor) def link_to_vendor(vendor)
if vendor if vendor
link_to(vendor, url_for_vendor(vendor), vendor) link_to vendor, url_for_vendor(vendor), vendor
else else
'Unknown Vendor' 'Unknown Vendor'
end end
@@ -39,7 +39,7 @@ module ShopFilter
def link_to_type(type) def link_to_type(type)
if type if type
link_to(type, url_for_type(type), type) link_to type, url_for_type(type), type
else else
'Unknown Vendor' 'Unknown Vendor'
end end
@@ -60,7 +60,7 @@ module ShopFilter
case style case style
when 'original' when 'original'
'/files/shops/random_number/' + url return '/files/shops/random_number/' + url
when 'grande', 'large', 'medium', 'compact', 'small', 'thumb', 'icon' when 'grande', 'large', 'medium', 'compact', 'small', 'thumb', 'icon'
"/files/shops/random_number/products/#{Regexp.last_match(1)}_#{style}.#{Regexp.last_match(2)}" "/files/shops/random_number/products/#{Regexp.last_match(1)}_#{style}.#{Regexp.last_match(2)}"
else else

View File

@@ -58,9 +58,9 @@ class ThemeRunner
# `render` is called to benchmark just the render portion of liquid # `render` is called to benchmark just the render portion of liquid
def render def render
@compiled_tests.each do |test| @compiled_tests.each do |test|
tmpl = test[:tmpl] tmpl = test[:tmpl]
assigns = test[:assigns] assigns = test[:assigns]
layout = test[:layout] layout = test[:layout]
if layout if layout
assigns['content_for_layout'] = tmpl.render!(assigns) assigns['content_for_layout'] = tmpl.render!(assigns)
@@ -73,29 +73,26 @@ class ThemeRunner
private private
def compile_and_render(template, layout, assigns, page_template) def compile_and_render(template, layout, assigns, page_template, template_file)
assigns = assigns.merge( compiled_test = compile_test(template, layout, assigns, page_template, template_file)
'page_title' => 'Page title',
'template' => page_template,
)
compiled_test = compile_test(template, layout, assigns)
assigns['content_for_layout'] = compiled_test[:tmpl].render!(assigns) assigns['content_for_layout'] = compiled_test[:tmpl].render!(assigns)
compiled_test[:layout].render!(assigns) if layout compiled_test[:layout].render!(assigns) if layout
end end
def compile_all_tests def compile_all_tests
@compiled_tests = [] @compiled_tests = []
each_test do |liquid, layout, assigns, _page_template, _template_name| each_test do |liquid, layout, assigns, page_template, template_name|
@compiled_tests << compile_test(liquid, layout, assigns) @compiled_tests << compile_test(liquid, layout, assigns, page_template, template_name)
end end
@compiled_tests @compiled_tests
end end
def compile_test(template, layout, assigns) def compile_test(template, layout, assigns, page_template, template_file)
parsed_template = Liquid::Template.parse(template).dup tmpl = init_template(page_template, template_file)
parsed_template = tmpl.parse(template).dup
if layout if layout
parsed_layout = Liquid::Template.parse(layout) parsed_layout = tmpl.parse(layout)
{ tmpl: parsed_template, assigns: assigns, layout: parsed_layout } { tmpl: parsed_template, assigns: assigns, layout: parsed_layout }
else else
{ tmpl: parsed_template, assigns: assigns } { tmpl: parsed_template, assigns: assigns }
@@ -110,7 +107,16 @@ class ThemeRunner
@tests.each do |test_hash| @tests.each do |test_hash|
# Compute page_template outside of profiler run, uninteresting to profiler # Compute page_template outside of profiler run, uninteresting to profiler
page_template = File.basename(test_hash[:template_name], File.extname(test_hash[:template_name])) page_template = File.basename(test_hash[:template_name], File.extname(test_hash[:template_name]))
yield(test_hash[:liquid], test_hash[:layout], assigns, page_template) yield(test_hash[:liquid], test_hash[:layout], assigns, page_template, test_hash[:template_name])
end end
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))
tmpl
end
end end

View File

@@ -10,9 +10,9 @@ class AssignTest < Minitest::Test
{% assign this-thing = 'Print this-thing' %} {% assign this-thing = 'Print this-thing' %}
{{ this-thing }} {{ this-thing }}
END_TEMPLATE END_TEMPLATE
template = Template.parse(template_source) template = Template.parse(template_source)
rendered = template.render! rendered = template.render!
assert_equal("Print this-thing", rendered.strip) assert_equal "Print this-thing", rendered.strip
end end
def test_assigned_variable def test_assigned_variable

View File

@@ -96,9 +96,9 @@ class BlankTest < Minitest::Test
def test_include_is_blank def test_include_is_blank
Liquid::Template.file_system = BlankTestFileSystem.new Liquid::Template.file_system = BlankTestFileSystem.new
assert_template_result("foobar" * (N + 1), wrap("{% include 'foobar' %}")) assert_template_result "foobar" * (N + 1), wrap("{% include 'foobar' %}")
assert_template_result(" foobar " * (N + 1), wrap("{% include ' foobar ' %}")) assert_template_result " foobar " * (N + 1), wrap("{% include ' foobar ' %}")
assert_template_result(" " * (N + 1), wrap(" {% include ' ' %} ")) assert_template_result " " * (N + 1), wrap(" {% include ' ' %} ")
end end
def test_case_is_blank def test_case_is_blank

View File

@@ -9,6 +9,6 @@ class BlockTest < Minitest::Test
exc = assert_raises(SyntaxError) do exc = assert_raises(SyntaxError) do
Template.parse("{% if true %}{% endunless %}") Template.parse("{% if true %}{% endunless %}")
end end
assert_equal(exc.message, "Liquid syntax error: 'endunless' is not a valid delimiter for if tags. use endif") assert_equal exc.message, "Liquid syntax error: 'endunless' is not a valid delimiter for if tags. use endif"
end end
end end

View File

@@ -14,9 +14,9 @@ class CaptureTest < Minitest::Test
{% capture this-thing %}Print this-thing{% endcapture %} {% capture this-thing %}Print this-thing{% endcapture %}
{{ this-thing }} {{ this-thing }}
END_TEMPLATE END_TEMPLATE
template = Template.parse(template_source) template = Template.parse(template_source)
rendered = template.render! rendered = template.render!
assert_equal("Print this-thing", rendered.strip) assert_equal "Print this-thing", rendered.strip
end end
def test_capture_to_variable_from_outer_scope_if_existing def test_capture_to_variable_from_outer_scope_if_existing
@@ -30,9 +30,9 @@ class CaptureTest < Minitest::Test
{% endif %} {% endif %}
{{var}} {{var}}
END_TEMPLATE END_TEMPLATE
template = Template.parse(template_source) template = Template.parse(template_source)
rendered = template.render! rendered = template.render!
assert_equal("test-string", rendered.gsub(/\s/, '')) assert_equal "test-string", rendered.gsub(/\s/, '')
end end
def test_assigning_from_capture def test_assigning_from_capture
@@ -45,8 +45,8 @@ class CaptureTest < Minitest::Test
{% endfor %} {% endfor %}
{{ first }}-{{ second }} {{ first }}-{{ second }}
END_TEMPLATE END_TEMPLATE
template = Template.parse(template_source) template = Template.parse(template_source)
rendered = template.render! rendered = template.render!
assert_equal("3-3", rendered.gsub(/\s/, '')) assert_equal "3-3", rendered.gsub(/\s/, '')
end end
end # CaptureTest end # CaptureTest

View File

@@ -9,13 +9,13 @@ class DocumentTest < Minitest::Test
exc = assert_raises(SyntaxError) do exc = assert_raises(SyntaxError) do
Template.parse("{% else %}") Template.parse("{% else %}")
end end
assert_equal(exc.message, "Liquid syntax error: Unexpected outer 'else' tag") assert_equal exc.message, "Liquid syntax error: Unexpected outer 'else' tag"
end end
def test_unknown_tag def test_unknown_tag
exc = assert_raises(SyntaxError) do exc = assert_raises(SyntaxError) do
Template.parse("{% foo %}") Template.parse("{% foo %}")
end end
assert_equal(exc.message, "Liquid syntax error: Unknown tag 'foo'") assert_equal exc.message, "Liquid syntax error: Unknown tag 'foo'"
end end
end end

View File

@@ -49,6 +49,10 @@ class ProductDrop < Liquid::Drop
ContextDrop.new ContextDrop.new
end end
def user_input
(+"foo").taint
end
protected protected
def callmenot def callmenot
@@ -107,61 +111,82 @@ class DropsTest < Minitest::Test
def test_product_drop def test_product_drop
tpl = Liquid::Template.parse(' ') tpl = Liquid::Template.parse(' ')
assert_equal(' ', tpl.render!('product' => ProductDrop.new)) assert_equal ' ', tpl.render!('product' => ProductDrop.new)
end
def test_rendering_raises_on_tainted_attr
with_taint_mode(:error) do
tpl = Liquid::Template.parse('{{ product.user_input }}')
assert_raises TaintedError do
tpl.render!('product' => ProductDrop.new)
end
end
end
def test_rendering_warns_on_tainted_attr
with_taint_mode(:warn) do
tpl = Liquid::Template.parse('{{ product.user_input }}')
context = Context.new('product' => ProductDrop.new)
tpl.render!(context)
assert_equal [Liquid::TaintedError], context.warnings.map(&:class)
assert_equal "variable 'product.user_input' is tainted and was not escaped", context.warnings.first.to_s(false)
end
end
def test_rendering_doesnt_raise_on_escaped_tainted_attr
with_taint_mode(:error) do
tpl = Liquid::Template.parse('{{ product.user_input | escape }}')
tpl.render!('product' => ProductDrop.new)
end
end end
def test_drop_does_only_respond_to_whitelisted_methods def test_drop_does_only_respond_to_whitelisted_methods
assert_equal("", Liquid::Template.parse("{{ product.inspect }}").render!('product' => ProductDrop.new)) assert_equal "", Liquid::Template.parse("{{ product.inspect }}").render!('product' => ProductDrop.new)
assert_equal("", Liquid::Template.parse("{{ product.pretty_inspect }}").render!('product' => ProductDrop.new)) assert_equal "", Liquid::Template.parse("{{ product.pretty_inspect }}").render!('product' => ProductDrop.new)
assert_equal("", Liquid::Template.parse("{{ product.whatever }}").render!('product' => ProductDrop.new)) assert_equal "", Liquid::Template.parse("{{ product.whatever }}").render!('product' => ProductDrop.new)
assert_equal("", Liquid::Template.parse('{{ product | map: "inspect" }}').render!('product' => ProductDrop.new)) assert_equal "", Liquid::Template.parse('{{ product | map: "inspect" }}').render!('product' => ProductDrop.new)
assert_equal("", Liquid::Template.parse('{{ product | map: "pretty_inspect" }}').render!('product' => ProductDrop.new)) assert_equal "", Liquid::Template.parse('{{ product | map: "pretty_inspect" }}').render!('product' => ProductDrop.new)
assert_equal("", Liquid::Template.parse('{{ product | map: "whatever" }}').render!('product' => ProductDrop.new)) assert_equal "", Liquid::Template.parse('{{ product | map: "whatever" }}').render!('product' => ProductDrop.new)
end end
def test_drops_respond_to_to_liquid def test_drops_respond_to_to_liquid
assert_equal("text1", Liquid::Template.parse("{{ product.to_liquid.texts.text }}").render!('product' => ProductDrop.new)) assert_equal "text1", Liquid::Template.parse("{{ product.to_liquid.texts.text }}").render!('product' => ProductDrop.new)
assert_equal("text1", Liquid::Template.parse('{{ product | map: "to_liquid" | map: "texts" | map: "text" }}').render!('product' => ProductDrop.new)) assert_equal "text1", Liquid::Template.parse('{{ product | map: "to_liquid" | map: "texts" | map: "text" }}').render!('product' => ProductDrop.new)
end end
def test_text_drop def test_text_drop
output = Liquid::Template.parse(' {{ product.texts.text }} ').render!('product' => ProductDrop.new) output = Liquid::Template.parse(' {{ product.texts.text }} ').render!('product' => ProductDrop.new)
assert_equal(' text1 ', output) assert_equal ' text1 ', output
end end
def test_catchall_unknown_method def test_catchall_unknown_method
output = Liquid::Template.parse(' {{ product.catchall.unknown }} ').render!('product' => ProductDrop.new) output = Liquid::Template.parse(' {{ product.catchall.unknown }} ').render!('product' => ProductDrop.new)
assert_equal(' catchall_method: unknown ', output) assert_equal ' catchall_method: unknown ', output
end end
def test_catchall_integer_argument_drop def test_catchall_integer_argument_drop
output = Liquid::Template.parse(' {{ product.catchall[8] }} ').render!('product' => ProductDrop.new) output = Liquid::Template.parse(' {{ product.catchall[8] }} ').render!('product' => ProductDrop.new)
assert_equal(' catchall_method: 8 ', output) assert_equal ' catchall_method: 8 ', output
end end
def test_text_array_drop def test_text_array_drop
output = Liquid::Template.parse('{% for text in product.texts.array %} {{text}} {% endfor %}').render!('product' => ProductDrop.new) output = Liquid::Template.parse('{% for text in product.texts.array %} {{text}} {% endfor %}').render!('product' => ProductDrop.new)
assert_equal(' text1 text2 ', output) assert_equal ' text1 text2 ', output
end end
def test_context_drop def test_context_drop
output = Liquid::Template.parse(' {{ context.bar }} ').render!('context' => ContextDrop.new, 'bar' => "carrot") output = Liquid::Template.parse(' {{ context.bar }} ').render!('context' => ContextDrop.new, 'bar' => "carrot")
assert_equal(' carrot ', output) assert_equal ' carrot ', output
end
def test_context_drop_array_with_map
output = Liquid::Template.parse(' {{ contexts | map: "bar" }} ').render!('contexts' => [ContextDrop.new, ContextDrop.new], 'bar' => "carrot")
assert_equal(' carrotcarrot ', output)
end end
def test_nested_context_drop def test_nested_context_drop
output = Liquid::Template.parse(' {{ product.context.foo }} ').render!('product' => ProductDrop.new, 'foo' => "monkey") output = Liquid::Template.parse(' {{ product.context.foo }} ').render!('product' => ProductDrop.new, 'foo' => "monkey")
assert_equal(' monkey ', output) assert_equal ' monkey ', output
end end
def test_protected def test_protected
output = Liquid::Template.parse(' {{ product.callmenot }} ').render!('product' => ProductDrop.new) output = Liquid::Template.parse(' {{ product.callmenot }} ').render!('product' => ProductDrop.new)
assert_equal(' ', output) assert_equal ' ', output
end end
def test_object_methods_not_allowed def test_object_methods_not_allowed
@@ -172,40 +197,40 @@ class DropsTest < Minitest::Test
end end
def test_scope def test_scope
assert_equal('1', Liquid::Template.parse('{{ context.scopes }}').render!('context' => ContextDrop.new)) assert_equal '1', Liquid::Template.parse('{{ context.scopes }}').render!('context' => ContextDrop.new)
assert_equal('2', Liquid::Template.parse('{%for i in dummy%}{{ context.scopes }}{%endfor%}').render!('context' => ContextDrop.new, 'dummy' => [1])) assert_equal '2', Liquid::Template.parse('{%for i in dummy%}{{ context.scopes }}{%endfor%}').render!('context' => ContextDrop.new, 'dummy' => [1])
assert_equal('3', Liquid::Template.parse('{%for i in dummy%}{%for i in dummy%}{{ context.scopes }}{%endfor%}{%endfor%}').render!('context' => ContextDrop.new, 'dummy' => [1])) assert_equal '3', Liquid::Template.parse('{%for i in dummy%}{%for i in dummy%}{{ context.scopes }}{%endfor%}{%endfor%}').render!('context' => ContextDrop.new, 'dummy' => [1])
end end
def test_scope_though_proc def test_scope_though_proc
assert_equal('1', Liquid::Template.parse('{{ s }}').render!('context' => ContextDrop.new, 's' => proc { |c| c['context.scopes'] })) assert_equal '1', Liquid::Template.parse('{{ s }}').render!('context' => ContextDrop.new, 's' => proc { |c| c['context.scopes'] })
assert_equal('2', Liquid::Template.parse('{%for i in dummy%}{{ s }}{%endfor%}').render!('context' => ContextDrop.new, 's' => proc { |c| c['context.scopes'] }, 'dummy' => [1])) assert_equal '2', Liquid::Template.parse('{%for i in dummy%}{{ s }}{%endfor%}').render!('context' => ContextDrop.new, 's' => proc { |c| c['context.scopes'] }, 'dummy' => [1])
assert_equal('3', Liquid::Template.parse('{%for i in dummy%}{%for i in dummy%}{{ s }}{%endfor%}{%endfor%}').render!('context' => ContextDrop.new, 's' => proc { |c| c['context.scopes'] }, 'dummy' => [1])) assert_equal '3', Liquid::Template.parse('{%for i in dummy%}{%for i in dummy%}{{ s }}{%endfor%}{%endfor%}').render!('context' => ContextDrop.new, 's' => proc { |c| c['context.scopes'] }, 'dummy' => [1])
end end
def test_scope_with_assigns def test_scope_with_assigns
assert_equal('variable', Liquid::Template.parse('{% assign a = "variable"%}{{a}}').render!('context' => ContextDrop.new)) assert_equal 'variable', Liquid::Template.parse('{% assign a = "variable"%}{{a}}').render!('context' => ContextDrop.new)
assert_equal('variable', Liquid::Template.parse('{% assign a = "variable"%}{%for i in dummy%}{{a}}{%endfor%}').render!('context' => ContextDrop.new, 'dummy' => [1])) assert_equal 'variable', Liquid::Template.parse('{% assign a = "variable"%}{%for i in dummy%}{{a}}{%endfor%}').render!('context' => ContextDrop.new, 'dummy' => [1])
assert_equal('test', Liquid::Template.parse('{% assign header_gif = "test"%}{{header_gif}}').render!('context' => ContextDrop.new)) assert_equal 'test', Liquid::Template.parse('{% assign header_gif = "test"%}{{header_gif}}').render!('context' => ContextDrop.new)
assert_equal('test', Liquid::Template.parse("{% assign header_gif = 'test'%}{{header_gif}}").render!('context' => ContextDrop.new)) assert_equal 'test', Liquid::Template.parse("{% assign header_gif = 'test'%}{{header_gif}}").render!('context' => ContextDrop.new)
end end
def test_scope_from_tags def test_scope_from_tags
assert_equal('1', Liquid::Template.parse('{% for i in context.scopes_as_array %}{{i}}{% endfor %}').render!('context' => ContextDrop.new, 'dummy' => [1])) assert_equal '1', Liquid::Template.parse('{% for i in context.scopes_as_array %}{{i}}{% endfor %}').render!('context' => ContextDrop.new, 'dummy' => [1])
assert_equal('12', Liquid::Template.parse('{%for a in dummy%}{% for i in context.scopes_as_array %}{{i}}{% endfor %}{% endfor %}').render!('context' => ContextDrop.new, 'dummy' => [1])) assert_equal '12', Liquid::Template.parse('{%for a in dummy%}{% for i in context.scopes_as_array %}{{i}}{% endfor %}{% endfor %}').render!('context' => ContextDrop.new, 'dummy' => [1])
assert_equal('123', Liquid::Template.parse('{%for a in dummy%}{%for a in dummy%}{% for i in context.scopes_as_array %}{{i}}{% endfor %}{% endfor %}{% endfor %}').render!('context' => ContextDrop.new, 'dummy' => [1])) assert_equal '123', Liquid::Template.parse('{%for a in dummy%}{%for a in dummy%}{% for i in context.scopes_as_array %}{{i}}{% endfor %}{% endfor %}{% endfor %}').render!('context' => ContextDrop.new, 'dummy' => [1])
end end
def test_access_context_from_drop def test_access_context_from_drop
assert_equal('123', Liquid::Template.parse('{%for a in dummy%}{{ context.loop_pos }}{% endfor %}').render!('context' => ContextDrop.new, 'dummy' => [1, 2, 3])) assert_equal '123', Liquid::Template.parse('{%for a in dummy%}{{ context.loop_pos }}{% endfor %}').render!('context' => ContextDrop.new, 'dummy' => [1, 2, 3])
end end
def test_enumerable_drop def test_enumerable_drop
assert_equal('123', Liquid::Template.parse('{% for c in collection %}{{c}}{% endfor %}').render!('collection' => EnumerableDrop.new)) assert_equal '123', Liquid::Template.parse('{% for c in collection %}{{c}}{% endfor %}').render!('collection' => EnumerableDrop.new)
end end
def test_enumerable_drop_size def test_enumerable_drop_size
assert_equal('3', Liquid::Template.parse('{{collection.size}}').render!('collection' => EnumerableDrop.new)) assert_equal '3', Liquid::Template.parse('{{collection.size}}').render!('collection' => EnumerableDrop.new)
end end
def test_enumerable_drop_will_invoke_liquid_method_missing_for_clashing_method_names def test_enumerable_drop_will_invoke_liquid_method_missing_for_clashing_method_names
@@ -225,7 +250,7 @@ class DropsTest < Minitest::Test
assert_equal "3", Liquid::Template.parse("{{collection[\"#{method}\"]}}").render!('collection' => EnumerableDrop.new) assert_equal "3", Liquid::Template.parse("{{collection[\"#{method}\"]}}").render!('collection' => EnumerableDrop.new)
end end
assert_equal("yes", Liquid::Template.parse("{% if collection contains 3 %}yes{% endif %}").render!('collection' => RealEnumerableDrop.new)) assert_equal "yes", Liquid::Template.parse("{% if collection contains 3 %}yes{% endif %}").render!('collection' => RealEnumerableDrop.new)
[:min, :first].each do |method| [:min, :first].each do |method|
assert_equal "1", Liquid::Template.parse("{{collection.#{method}}}").render!('collection' => RealEnumerableDrop.new) assert_equal "1", Liquid::Template.parse("{{collection.#{method}}}").render!('collection' => RealEnumerableDrop.new)
@@ -236,22 +261,22 @@ class DropsTest < Minitest::Test
end end
def test_empty_string_value_access def test_empty_string_value_access
assert_equal('', Liquid::Template.parse('{{ product[value] }}').render!('product' => ProductDrop.new, 'value' => '')) assert_equal '', Liquid::Template.parse('{{ product[value] }}').render!('product' => ProductDrop.new, 'value' => '')
end end
def test_nil_value_access def test_nil_value_access
assert_equal('', Liquid::Template.parse('{{ product[value] }}').render!('product' => ProductDrop.new, 'value' => nil)) assert_equal '', Liquid::Template.parse('{{ product[value] }}').render!('product' => ProductDrop.new, 'value' => nil)
end end
def test_default_to_s_on_drops def test_default_to_s_on_drops
assert_equal('ProductDrop', Liquid::Template.parse("{{ product }}").render!('product' => ProductDrop.new)) assert_equal 'ProductDrop', Liquid::Template.parse("{{ product }}").render!('product' => ProductDrop.new)
assert_equal('EnumerableDrop', Liquid::Template.parse('{{ collection }}').render!('collection' => EnumerableDrop.new)) assert_equal 'EnumerableDrop', Liquid::Template.parse('{{ collection }}').render!('collection' => EnumerableDrop.new)
end end
def test_invokable_methods def test_invokable_methods
assert_equal(%w(to_liquid catchall context texts).to_set, ProductDrop.invokable_methods) assert_equal %w(to_liquid catchall user_input context texts).to_set, ProductDrop.invokable_methods
assert_equal(%w(to_liquid scopes_as_array loop_pos scopes).to_set, ContextDrop.invokable_methods) assert_equal %w(to_liquid scopes_as_array loop_pos scopes).to_set, ContextDrop.invokable_methods
assert_equal(%w(to_liquid size max min first count).to_set, EnumerableDrop.invokable_methods) assert_equal %w(to_liquid size max min first count).to_set, EnumerableDrop.invokable_methods
assert_equal(%w(to_liquid max min sort count first).to_set, RealEnumerableDrop.invokable_methods) assert_equal %w(to_liquid max min sort count first).to_set, RealEnumerableDrop.invokable_methods
end end
end # DropsTest end # DropsTest

View File

@@ -35,34 +35,31 @@ class ErrorHandlingTest < Minitest::Test
TEXT TEXT
output = Liquid::Template.parse(template, line_numbers: true).render('errors' => ErrorDrop.new) output = Liquid::Template.parse(template, line_numbers: true).render('errors' => ErrorDrop.new)
assert_equal(expected, output) assert_equal expected, output
end end
def test_standard_error def test_standard_error
template = Liquid::Template.parse(' {{ errors.standard_error }} ') template = Liquid::Template.parse(' {{ errors.standard_error }} ')
context = Liquid::Context.new('errors' => ErrorDrop.new) assert_equal ' Liquid error: standard error ', template.render('errors' => ErrorDrop.new)
assert_equal(' Liquid error: standard error ', template.render(context))
assert_equal(1, context.errors.size) assert_equal 1, template.errors.size
assert_equal(StandardError, context.errors.first.class) assert_equal StandardError, template.errors.first.class
end end
def test_syntax def test_syntax
template = Liquid::Template.parse(' {{ errors.syntax_error }} ') template = Liquid::Template.parse(' {{ errors.syntax_error }} ')
context = Liquid::Context.new('errors' => ErrorDrop.new) assert_equal ' Liquid syntax error: syntax error ', template.render('errors' => ErrorDrop.new)
assert_equal(' Liquid syntax error: syntax error ', template.render(context))
assert_equal(1, context.errors.size) assert_equal 1, template.errors.size
assert_equal(SyntaxError, context.errors.first.class) assert_equal SyntaxError, template.errors.first.class
end end
def test_argument def test_argument
template = Liquid::Template.parse(' {{ errors.argument_error }} ') template = Liquid::Template.parse(' {{ errors.argument_error }} ')
context = Liquid::Context.new('errors' => ErrorDrop.new) assert_equal ' Liquid error: argument error ', template.render('errors' => ErrorDrop.new)
assert_equal(' Liquid error: argument error ', template.render(context))
assert_equal(1, context.errors.size) assert_equal 1, template.errors.size
assert_equal(ArgumentError, context.errors.first.class) assert_equal ArgumentError, template.errors.first.class
end end
def test_missing_endtag_parse_time_error def test_missing_endtag_parse_time_error
@@ -81,10 +78,9 @@ class ErrorHandlingTest < Minitest::Test
def test_lax_unrecognized_operator def test_lax_unrecognized_operator
template = Liquid::Template.parse(' {% if 1 =! 2 %}ok{% endif %} ', error_mode: :lax) template = Liquid::Template.parse(' {% if 1 =! 2 %}ok{% endif %} ', error_mode: :lax)
context = Liquid::Context.new('errors' => ErrorDrop.new) assert_equal ' Liquid error: Unknown operator =! ', template.render
assert_equal(' Liquid error: Unknown operator =! ', template.render(context)) assert_equal 1, template.errors.size
assert_equal(1, context.errors.size) assert_equal Liquid::ArgumentError, template.errors.first.class
assert_equal(Liquid::ArgumentError, context.errors.first.class)
end end
def test_with_line_numbers_adds_numbers_to_parser_errors def test_with_line_numbers_adds_numbers_to_parser_errors
@@ -128,8 +124,8 @@ class ErrorHandlingTest < Minitest::Test
error_mode: :warn, error_mode: :warn,
line_numbers: true) line_numbers: true)
assert_equal(['Liquid syntax error (line 4): Unexpected character = in "1 =! 2"'], assert_equal ['Liquid syntax error (line 4): Unexpected character = in "1 =! 2"'],
template.warnings.map(&:message)) template.warnings.map(&:message)
end end
def test_parsing_strict_with_line_numbers_adds_numbers_to_lexer_errors def test_parsing_strict_with_line_numbers_adds_numbers_to_lexer_errors
@@ -145,7 +141,7 @@ class ErrorHandlingTest < Minitest::Test
line_numbers: true) line_numbers: true)
end end
assert_equal('Liquid syntax error (line 4): Unexpected character = in "1 =! 2"', err.message) assert_equal 'Liquid syntax error (line 4): Unexpected character = in "1 =! 2"', err.message
end end
def test_syntax_errors_in_nested_blocks_have_correct_line_number def test_syntax_errors_in_nested_blocks_have_correct_line_number
@@ -162,37 +158,37 @@ class ErrorHandlingTest < Minitest::Test
line_numbers: true) line_numbers: true)
end end
assert_equal("Liquid syntax error (line 5): Unknown tag 'foo'", err.message) assert_equal "Liquid syntax error (line 5): Unknown tag 'foo'", err.message
end end
def test_strict_error_messages def test_strict_error_messages
err = assert_raises(SyntaxError) do err = assert_raises(SyntaxError) do
Liquid::Template.parse(' {% if 1 =! 2 %}ok{% endif %} ', error_mode: :strict) Liquid::Template.parse(' {% if 1 =! 2 %}ok{% endif %} ', error_mode: :strict)
end end
assert_equal('Liquid syntax error: Unexpected character = in "1 =! 2"', err.message) assert_equal 'Liquid syntax error: Unexpected character = in "1 =! 2"', err.message
err = assert_raises(SyntaxError) do err = assert_raises(SyntaxError) do
Liquid::Template.parse('{{%%%}}', error_mode: :strict) Liquid::Template.parse('{{%%%}}', error_mode: :strict)
end end
assert_equal('Liquid syntax error: Unexpected character % in "{{%%%}}"', err.message) assert_equal 'Liquid syntax error: Unexpected character % in "{{%%%}}"', err.message
end end
def test_warnings def test_warnings
template = Liquid::Template.parse('{% if ~~~ %}{{%%%}}{% else %}{{ hello. }}{% endif %}', error_mode: :warn) template = Liquid::Template.parse('{% if ~~~ %}{{%%%}}{% else %}{{ hello. }}{% endif %}', error_mode: :warn)
assert_equal(3, template.warnings.size) assert_equal 3, template.warnings.size
assert_equal('Unexpected character ~ in "~~~"', template.warnings[0].to_s(false)) assert_equal 'Unexpected character ~ in "~~~"', template.warnings[0].to_s(false)
assert_equal('Unexpected character % in "{{%%%}}"', template.warnings[1].to_s(false)) assert_equal 'Unexpected character % in "{{%%%}}"', template.warnings[1].to_s(false)
assert_equal('Expected id but found end_of_string in "{{ hello. }}"', template.warnings[2].to_s(false)) assert_equal 'Expected id but found end_of_string in "{{ hello. }}"', template.warnings[2].to_s(false)
assert_equal('', template.render) assert_equal '', template.render
end end
def test_warning_line_numbers def test_warning_line_numbers
template = Liquid::Template.parse("{% if ~~~ %}\n{{%%%}}{% else %}\n{{ hello. }}{% endif %}", error_mode: :warn, line_numbers: true) template = Liquid::Template.parse("{% if ~~~ %}\n{{%%%}}{% else %}\n{{ hello. }}{% endif %}", error_mode: :warn, line_numbers: true)
assert_equal('Liquid syntax error (line 1): Unexpected character ~ in "~~~"', template.warnings[0].message) assert_equal 'Liquid syntax error (line 1): Unexpected character ~ in "~~~"', template.warnings[0].message
assert_equal('Liquid syntax error (line 2): Unexpected character % in "{{%%%}}"', template.warnings[1].message) assert_equal 'Liquid syntax error (line 2): Unexpected character % in "{{%%%}}"', template.warnings[1].message
assert_equal('Liquid syntax error (line 3): Expected id but found end_of_string in "{{ hello. }}"', template.warnings[2].message) assert_equal 'Liquid syntax error (line 3): Expected id but found end_of_string in "{{ hello. }}"', template.warnings[2].message
assert_equal(3, template.warnings.size) assert_equal 3, template.warnings.size
assert_equal([1, 2, 3], template.warnings.map(&:line_number)) assert_equal [1, 2, 3], template.warnings.map(&:line_number)
end end
# Liquid should not catch Exceptions that are not subclasses of StandardError, like Interrupt and NoMemoryError # Liquid should not catch Exceptions that are not subclasses of StandardError, like Interrupt and NoMemoryError
@@ -206,11 +202,10 @@ class ErrorHandlingTest < Minitest::Test
def test_default_exception_renderer_with_internal_error def test_default_exception_renderer_with_internal_error
template = Liquid::Template.parse('This is a runtime error: {{ errors.runtime_error }}', line_numbers: true) template = Liquid::Template.parse('This is a runtime error: {{ errors.runtime_error }}', line_numbers: true)
context = Liquid::Context.new('errors' => ErrorDrop.new) output = template.render('errors' => ErrorDrop.new)
output = template.render(context)
assert_equal('This is a runtime error: Liquid error (line 1): internal', output) assert_equal 'This is a runtime error: Liquid error (line 1): internal', output
assert_equal([Liquid::InternalError], context.errors.map(&:class)) assert_equal [Liquid::InternalError], template.errors.map(&:class)
end end
def test_setting_default_exception_renderer def test_setting_default_exception_renderer
@@ -222,30 +217,28 @@ class ErrorHandlingTest < Minitest::Test
} }
template = Liquid::Template.parse('This is a runtime error: {{ errors.argument_error }}') template = Liquid::Template.parse('This is a runtime error: {{ errors.argument_error }}')
context = Liquid::Context.new('errors' => ErrorDrop.new) output = template.render('errors' => ErrorDrop.new)
output = template.render(context)
assert_equal('This is a runtime error: ', output) assert_equal 'This is a runtime error: ', output
assert_equal([Liquid::ArgumentError], context.errors.map(&:class)) assert_equal [Liquid::ArgumentError], template.errors.map(&:class)
ensure ensure
Liquid::Template.default_exception_renderer = old_exception_renderer if old_exception_renderer Liquid::Template.default_exception_renderer = old_exception_renderer if old_exception_renderer
end end
def test_exception_renderer_exposing_non_liquid_error def test_exception_renderer_exposing_non_liquid_error
template = Liquid::Template.parse('This is a runtime error: {{ errors.runtime_error }}', line_numbers: true) template = Liquid::Template.parse('This is a runtime error: {{ errors.runtime_error }}', line_numbers: true)
exceptions = [] exceptions = []
handler = ->(e) { handler = ->(e) {
exceptions << e exceptions << e
e.cause e.cause
} }
context = Liquid::Context.new('errors' => ErrorDrop.new) output = template.render({ 'errors' => ErrorDrop.new }, exception_renderer: handler)
output = template.render(context, exception_renderer: handler)
assert_equal('This is a runtime error: runtime error', output) assert_equal 'This is a runtime error: runtime error', output
assert_equal([Liquid::InternalError], exceptions.map(&:class)) assert_equal [Liquid::InternalError], exceptions.map(&:class)
assert_equal(exceptions, context.errors) assert_equal exceptions, template.errors
assert_equal('#<RuntimeError: runtime error>', exceptions.first.cause.inspect) assert_equal '#<RuntimeError: runtime error>', exceptions.first.cause.inspect
end end
class TestFileSystem class TestFileSystem
@@ -257,16 +250,14 @@ class ErrorHandlingTest < Minitest::Test
def test_included_template_name_with_line_numbers def test_included_template_name_with_line_numbers
old_file_system = Liquid::Template.file_system old_file_system = Liquid::Template.file_system
context = Liquid::Context.new('errors' => ErrorDrop.new)
begin begin
Liquid::Template.file_system = TestFileSystem.new Liquid::Template.file_system = TestFileSystem.new
template = Liquid::Template.parse("Argument error:\n{% include 'product' %}", line_numbers: true) template = Liquid::Template.parse("Argument error:\n{% include 'product' %}", line_numbers: true)
page = template.render(context) page = template.render('errors' => ErrorDrop.new)
ensure ensure
Liquid::Template.file_system = old_file_system Liquid::Template.file_system = old_file_system
end end
assert_equal("Argument error:\nLiquid error (product line 1): argument error", page) assert_equal "Argument error:\nLiquid error (product line 1): argument error", page
assert_equal("product", context.errors.first.template_name) assert_equal "product", template.errors.first.template_name
end end
end end

View File

@@ -41,13 +41,13 @@ class FiltersTest < Minitest::Test
@context['var'] = 1000 @context['var'] = 1000
@context.add_filters(MoneyFilter) @context.add_filters(MoneyFilter)
assert_equal(' 1000$ ', Template.parse("{{var | money}}").render(@context)) assert_equal ' 1000$ ', Template.parse("{{var | money}}").render(@context)
end end
def test_underscore_in_filter_name def test_underscore_in_filter_name
@context['var'] = 1000 @context['var'] = 1000
@context.add_filters(MoneyFilter) @context.add_filters(MoneyFilter)
assert_equal(' 1000$ ', Template.parse("{{var | money_with_underscore}}").render(@context)) assert_equal ' 1000$ ', Template.parse("{{var | money_with_underscore}}").render(@context)
end end
def test_second_filter_overwrites_first def test_second_filter_overwrites_first
@@ -55,103 +55,103 @@ class FiltersTest < Minitest::Test
@context.add_filters(MoneyFilter) @context.add_filters(MoneyFilter)
@context.add_filters(CanadianMoneyFilter) @context.add_filters(CanadianMoneyFilter)
assert_equal(' 1000$ CAD ', Template.parse("{{var | money}}").render(@context)) assert_equal ' 1000$ CAD ', Template.parse("{{var | money}}").render(@context)
end end
def test_size def test_size
@context['var'] = 'abcd' @context['var'] = 'abcd'
@context.add_filters(MoneyFilter) @context.add_filters(MoneyFilter)
assert_equal('4', Template.parse("{{var | size}}").render(@context)) assert_equal '4', Template.parse("{{var | size}}").render(@context)
end end
def test_join def test_join
@context['var'] = [1, 2, 3, 4] @context['var'] = [1, 2, 3, 4]
assert_equal("1 2 3 4", Template.parse("{{var | join}}").render(@context)) assert_equal "1 2 3 4", Template.parse("{{var | join}}").render(@context)
end end
def test_sort def test_sort
@context['value'] = 3 @context['value'] = 3
@context['numbers'] = [2, 1, 4, 3] @context['numbers'] = [2, 1, 4, 3]
@context['words'] = ['expected', 'as', 'alphabetic'] @context['words'] = ['expected', 'as', 'alphabetic']
@context['arrays'] = ['flower', 'are'] @context['arrays'] = ['flower', 'are']
@context['case_sensitive'] = ['sensitive', 'Expected', 'case'] @context['case_sensitive'] = ['sensitive', 'Expected', 'case']
assert_equal('1 2 3 4', Template.parse("{{numbers | sort | join}}").render(@context)) assert_equal '1 2 3 4', Template.parse("{{numbers | sort | join}}").render(@context)
assert_equal('alphabetic as expected', Template.parse("{{words | sort | join}}").render(@context)) assert_equal 'alphabetic as expected', Template.parse("{{words | sort | join}}").render(@context)
assert_equal('3', Template.parse("{{value | sort}}").render(@context)) assert_equal '3', Template.parse("{{value | sort}}").render(@context)
assert_equal('are flower', Template.parse("{{arrays | sort | join}}").render(@context)) assert_equal 'are flower', Template.parse("{{arrays | sort | join}}").render(@context)
assert_equal('Expected case sensitive', Template.parse("{{case_sensitive | sort | join}}").render(@context)) assert_equal 'Expected case sensitive', Template.parse("{{case_sensitive | sort | join}}").render(@context)
end end
def test_sort_natural def test_sort_natural
@context['words'] = ['case', 'Assert', 'Insensitive'] @context['words'] = ['case', 'Assert', 'Insensitive']
@context['hashes'] = [{ 'a' => 'A' }, { 'a' => 'b' }, { 'a' => 'C' }] @context['hashes'] = [{ 'a' => 'A' }, { 'a' => 'b' }, { 'a' => 'C' }]
@context['objects'] = [TestObject.new('A'), TestObject.new('b'), TestObject.new('C')] @context['objects'] = [TestObject.new('A'), TestObject.new('b'), TestObject.new('C')]
# Test strings # Test strings
assert_equal('Assert case Insensitive', Template.parse("{{words | sort_natural | join}}").render(@context)) assert_equal 'Assert case Insensitive', Template.parse("{{words | sort_natural | join}}").render(@context)
# Test hashes # Test hashes
assert_equal('A b C', Template.parse("{{hashes | sort_natural: 'a' | map: 'a' | join}}").render(@context)) assert_equal 'A b C', Template.parse("{{hashes | sort_natural: 'a' | map: 'a' | join}}").render(@context)
# Test objects # Test objects
assert_equal('A b C', Template.parse("{{objects | sort_natural: 'a' | map: 'a' | join}}").render(@context)) assert_equal 'A b C', Template.parse("{{objects | sort_natural: 'a' | map: 'a' | join}}").render(@context)
end end
def test_compact def test_compact
@context['words'] = ['a', nil, 'b', nil, 'c'] @context['words'] = ['a', nil, 'b', nil, 'c']
@context['hashes'] = [{ 'a' => 'A' }, { 'a' => nil }, { 'a' => 'C' }] @context['hashes'] = [{ 'a' => 'A' }, { 'a' => nil }, { 'a' => 'C' }]
@context['objects'] = [TestObject.new('A'), TestObject.new(nil), TestObject.new('C')] @context['objects'] = [TestObject.new('A'), TestObject.new(nil), TestObject.new('C')]
# Test strings # Test strings
assert_equal('a b c', Template.parse("{{words | compact | join}}").render(@context)) assert_equal 'a b c', Template.parse("{{words | compact | join}}").render(@context)
# Test hashes # Test hashes
assert_equal('A C', Template.parse("{{hashes | compact: 'a' | map: 'a' | join}}").render(@context)) assert_equal 'A C', Template.parse("{{hashes | compact: 'a' | map: 'a' | join}}").render(@context)
# Test objects # Test objects
assert_equal('A C', Template.parse("{{objects | compact: 'a' | map: 'a' | join}}").render(@context)) assert_equal 'A C', Template.parse("{{objects | compact: 'a' | map: 'a' | join}}").render(@context)
end end
def test_strip_html def test_strip_html
@context['var'] = "<b>bla blub</a>" @context['var'] = "<b>bla blub</a>"
assert_equal("bla blub", Template.parse("{{ var | strip_html }}").render(@context)) assert_equal "bla blub", Template.parse("{{ var | strip_html }}").render(@context)
end end
def test_strip_html_ignore_comments_with_html def test_strip_html_ignore_comments_with_html
@context['var'] = "<!-- split and some <ul> tag --><b>bla blub</a>" @context['var'] = "<!-- split and some <ul> tag --><b>bla blub</a>"
assert_equal("bla blub", Template.parse("{{ var | strip_html }}").render(@context)) assert_equal "bla blub", Template.parse("{{ var | strip_html }}").render(@context)
end end
def test_capitalize def test_capitalize
@context['var'] = "blub" @context['var'] = "blub"
assert_equal("Blub", Template.parse("{{ var | capitalize }}").render(@context)) assert_equal "Blub", Template.parse("{{ var | capitalize }}").render(@context)
end end
def test_nonexistent_filter_is_ignored def test_nonexistent_filter_is_ignored
@context['var'] = 1000 @context['var'] = 1000
assert_equal('1000', Template.parse("{{ var | xyzzy }}").render(@context)) assert_equal '1000', Template.parse("{{ var | xyzzy }}").render(@context)
end end
def test_filter_with_keyword_arguments def test_filter_with_keyword_arguments
@context['surname'] = 'john' @context['surname'] = 'john'
@context['input'] = 'hello %{first_name}, %{last_name}' @context['input'] = 'hello %{first_name}, %{last_name}'
@context.add_filters(SubstituteFilter) @context.add_filters(SubstituteFilter)
output = Template.parse(%({{ input | substitute: first_name: surname, last_name: 'doe' }})).render(@context) output = Template.parse(%({{ input | substitute: first_name: surname, last_name: 'doe' }})).render(@context)
assert_equal('hello john, doe', output) assert_equal 'hello john, doe', output
end end
def test_override_object_method_in_filter def test_override_object_method_in_filter
assert_equal("tap overridden", Template.parse("{{var | tap}}").render!({ 'var' => 1000 }, filters: [OverrideObjectMethodFilter])) assert_equal "tap overridden", Template.parse("{{var | tap}}").render!({ 'var' => 1000 }, filters: [OverrideObjectMethodFilter])
# tap still treated as a non-existent filter # tap still treated as a non-existent filter
assert_equal("1000", Template.parse("{{var | tap}}").render!('var' => 1000)) assert_equal "1000", Template.parse("{{var | tap}}").render!('var' => 1000)
end end
end end
@@ -167,8 +167,8 @@ class FiltersInTemplate < Minitest::Test
end end
def test_local_filter_with_deprecated_syntax def test_local_filter_with_deprecated_syntax
assert_equal(" 1000$ CAD ", Template.parse("{{1000 | money}}").render!(nil, CanadianMoneyFilter)) assert_equal " 1000$ CAD ", Template.parse("{{1000 | money}}").render!(nil, CanadianMoneyFilter)
assert_equal(" 1000$ CAD ", Template.parse("{{1000 | money}}").render!(nil, [CanadianMoneyFilter])) assert_equal " 1000$ CAD ", Template.parse("{{1000 | money}}").render!(nil, [CanadianMoneyFilter])
end end
end # FiltersTest end # FiltersTest

View File

@@ -42,84 +42,84 @@ class OutputTest < Minitest::Test
text = %( {{best_cars}} ) text = %( {{best_cars}} )
expected = %( bmw ) expected = %( bmw )
assert_equal(expected, Template.parse(text).render!(@assigns)) assert_equal expected, Template.parse(text).render!(@assigns)
end end
def test_variable_traversing_with_two_brackets def test_variable_traversing_with_two_brackets
text = %({{ site.data.menu[include.menu][include.locale] }}) text = %({{ site.data.menu[include.menu][include.locale] }})
assert_equal("it works!", Template.parse(text).render!( assert_equal "it works!", Template.parse(text).render!(
"site" => { "data" => { "menu" => { "foo" => { "bar" => "it works!" } } } }, "site" => { "data" => { "menu" => { "foo" => { "bar" => "it works!" } } } },
"include" => { "menu" => "foo", "locale" => "bar" } "include" => { "menu" => "foo", "locale" => "bar" }
)) )
end end
def test_variable_traversing def test_variable_traversing
text = %( {{car.bmw}} {{car.gm}} {{car.bmw}} ) text = %( {{car.bmw}} {{car.gm}} {{car.bmw}} )
expected = %( good bad good ) expected = %( good bad good )
assert_equal(expected, Template.parse(text).render!(@assigns)) assert_equal expected, Template.parse(text).render!(@assigns)
end end
def test_variable_piping def test_variable_piping
text = %( {{ car.gm | make_funny }} ) text = %( {{ car.gm | make_funny }} )
expected = %( LOL ) expected = %( LOL )
assert_equal(expected, Template.parse(text).render!(@assigns, filters: [FunnyFilter])) assert_equal expected, Template.parse(text).render!(@assigns, filters: [FunnyFilter])
end end
def test_variable_piping_with_input def test_variable_piping_with_input
text = %( {{ car.gm | cite_funny }} ) text = %( {{ car.gm | cite_funny }} )
expected = %( LOL: bad ) expected = %( LOL: bad )
assert_equal(expected, Template.parse(text).render!(@assigns, filters: [FunnyFilter])) assert_equal expected, Template.parse(text).render!(@assigns, filters: [FunnyFilter])
end end
def test_variable_piping_with_args def test_variable_piping_with_args
text = %! {{ car.gm | add_smiley : ':-(' }} ! text = %! {{ car.gm | add_smiley : ':-(' }} !
expected = %| bad :-( | expected = %| bad :-( |
assert_equal(expected, Template.parse(text).render!(@assigns, filters: [FunnyFilter])) assert_equal expected, Template.parse(text).render!(@assigns, filters: [FunnyFilter])
end end
def test_variable_piping_with_no_args def test_variable_piping_with_no_args
text = %( {{ car.gm | add_smiley }} ) text = %( {{ car.gm | add_smiley }} )
expected = %| bad :-) | expected = %| bad :-) |
assert_equal(expected, Template.parse(text).render!(@assigns, filters: [FunnyFilter])) assert_equal expected, Template.parse(text).render!(@assigns, filters: [FunnyFilter])
end end
def test_multiple_variable_piping_with_args def test_multiple_variable_piping_with_args
text = %! {{ car.gm | add_smiley : ':-(' | add_smiley : ':-('}} ! text = %! {{ car.gm | add_smiley : ':-(' | add_smiley : ':-('}} !
expected = %| bad :-( :-( | expected = %| bad :-( :-( |
assert_equal(expected, Template.parse(text).render!(@assigns, filters: [FunnyFilter])) assert_equal expected, Template.parse(text).render!(@assigns, filters: [FunnyFilter])
end end
def test_variable_piping_with_multiple_args def test_variable_piping_with_multiple_args
text = %( {{ car.gm | add_tag : 'span', 'bar'}} ) text = %( {{ car.gm | add_tag : 'span', 'bar'}} )
expected = %( <span id="bar">bad</span> ) expected = %( <span id="bar">bad</span> )
assert_equal(expected, Template.parse(text).render!(@assigns, filters: [FunnyFilter])) assert_equal expected, Template.parse(text).render!(@assigns, filters: [FunnyFilter])
end end
def test_variable_piping_with_variable_args def test_variable_piping_with_variable_args
text = %( {{ car.gm | add_tag : 'span', car.bmw}} ) text = %( {{ car.gm | add_tag : 'span', car.bmw}} )
expected = %( <span id="good">bad</span> ) expected = %( <span id="good">bad</span> )
assert_equal(expected, Template.parse(text).render!(@assigns, filters: [FunnyFilter])) assert_equal expected, Template.parse(text).render!(@assigns, filters: [FunnyFilter])
end end
def test_multiple_pipings def test_multiple_pipings
text = %( {{ best_cars | cite_funny | paragraph }} ) text = %( {{ best_cars | cite_funny | paragraph }} )
expected = %( <p>LOL: bmw</p> ) expected = %( <p>LOL: bmw</p> )
assert_equal(expected, Template.parse(text).render!(@assigns, filters: [FunnyFilter])) assert_equal expected, Template.parse(text).render!(@assigns, filters: [FunnyFilter])
end end
def test_link_to def test_link_to
text = %( {{ 'Typo' | link_to: 'http://typo.leetsoft.com' }} ) text = %( {{ 'Typo' | link_to: 'http://typo.leetsoft.com' }} )
expected = %( <a href="http://typo.leetsoft.com">Typo</a> ) expected = %( <a href="http://typo.leetsoft.com">Typo</a> )
assert_equal(expected, Template.parse(text).render!(@assigns, filters: [FunnyFilter])) assert_equal expected, Template.parse(text).render!(@assigns, filters: [FunnyFilter])
end end
end # OutputTest end # OutputTest

View File

@@ -238,7 +238,7 @@ class ParseTreeVisitorTest < Minitest::Test
def traversal(template) def traversal(template)
ParseTreeVisitor ParseTreeVisitor
.for(Template.parse(template).root) .for(Template.parse(template).root)
.add_callback_for(VariableLookup) { |node| node.name } # rubocop:disable Style/SymbolProc .add_callback_for(VariableLookup, &:name)
end end
def visit(template) def visit(template)

View File

@@ -7,7 +7,7 @@ class ParsingQuirksTest < Minitest::Test
def test_parsing_css def test_parsing_css
text = " div { font-weight: bold; } " text = " div { font-weight: bold; } "
assert_equal(text, Template.parse(text).render!) assert_equal text, Template.parse(text).render!
end end
def test_raise_on_single_close_bracet def test_raise_on_single_close_bracet
@@ -29,7 +29,7 @@ class ParsingQuirksTest < Minitest::Test
end end
def test_error_on_empty_filter def test_error_on_empty_filter
assert(Template.parse("{{test}}")) assert Template.parse("{{test}}")
with_error_mode(:lax) do with_error_mode(:lax) do
assert Template.parse("{{|test}}") assert Template.parse("{{|test}}")
@@ -64,15 +64,15 @@ class ParsingQuirksTest < Minitest::Test
end end
def test_no_error_on_lax_empty_filter def test_no_error_on_lax_empty_filter
assert(Template.parse("{{test |a|b|}}", error_mode: :lax)) assert Template.parse("{{test |a|b|}}", error_mode: :lax)
assert(Template.parse("{{test}}", error_mode: :lax)) assert Template.parse("{{test}}", error_mode: :lax)
assert(Template.parse("{{|test|}}", error_mode: :lax)) assert Template.parse("{{|test|}}", error_mode: :lax)
end end
def test_meaningless_parens_lax def test_meaningless_parens_lax
with_error_mode(:lax) do with_error_mode(:lax) do
assigns = { 'b' => 'bar', 'c' => 'baz' } assigns = { 'b' => 'bar', 'c' => 'baz' }
markup = "a == 'foo' or (b == 'bar' and c == 'baz') or false" markup = "a == 'foo' or (b == 'bar' and c == 'baz') or false"
assert_template_result(' YES ', "{% if #{markup} %} YES {% endif %}", assigns) assert_template_result(' YES ', "{% if #{markup} %} YES {% endif %}", assigns)
end end
end end

View File

@@ -19,35 +19,35 @@ class RenderProfilingTest < Minitest::Test
t = Template.parse("{{ 'a string' | upcase }}") t = Template.parse("{{ 'a string' | upcase }}")
t.render! t.render!
assert_nil(t.profiler) assert_nil t.profiler
end end
def test_parse_makes_available_simple_profiling def test_parse_makes_available_simple_profiling
t = Template.parse("{{ 'a string' | upcase }}", profile: true) t = Template.parse("{{ 'a string' | upcase }}", profile: true)
t.render! t.render!
assert_equal(1, t.profiler.length) assert_equal 1, t.profiler.length
node = t.profiler[0] node = t.profiler[0]
assert_equal(" 'a string' | upcase ", node.code) assert_equal " 'a string' | upcase ", node.code
end end
def test_render_ignores_raw_strings_when_profiling def test_render_ignores_raw_strings_when_profiling
t = Template.parse("This is raw string\nstuff\nNewline", profile: true) t = Template.parse("This is raw string\nstuff\nNewline", profile: true)
t.render! t.render!
assert_equal(0, t.profiler.length) assert_equal 0, t.profiler.length
end end
def test_profiling_includes_line_numbers_of_liquid_nodes def test_profiling_includes_line_numbers_of_liquid_nodes
t = Template.parse("{{ 'a string' | upcase }}\n{% increment test %}", profile: true) t = Template.parse("{{ 'a string' | upcase }}\n{% increment test %}", profile: true)
t.render! t.render!
assert_equal(2, t.profiler.length) assert_equal 2, t.profiler.length
# {{ 'a string' | upcase }} # {{ 'a string' | upcase }}
assert_equal(1, t.profiler[0].line_number) assert_equal 1, t.profiler[0].line_number
# {{ increment test }} # {{ increment test }}
assert_equal(2, t.profiler[1].line_number) assert_equal 2, t.profiler[1].line_number
end end
def test_profiling_includes_line_numbers_of_included_partials def test_profiling_includes_line_numbers_of_included_partials
@@ -57,9 +57,9 @@ class RenderProfilingTest < Minitest::Test
included_children = t.profiler[0].children included_children = t.profiler[0].children
# {% assign template_name = 'a_template' %} # {% assign template_name = 'a_template' %}
assert_equal(1, included_children[0].line_number) assert_equal 1, included_children[0].line_number
# {{ template_name }} # {{ template_name }}
assert_equal(2, included_children[1].line_number) assert_equal 2, included_children[1].line_number
end end
def test_profiling_times_the_rendering_of_tokens def test_profiling_times_the_rendering_of_tokens
@@ -67,14 +67,14 @@ class RenderProfilingTest < Minitest::Test
t.render! t.render!
node = t.profiler[0] node = t.profiler[0]
refute_nil(node.render_time) refute_nil node.render_time
end end
def test_profiling_times_the_entire_render def test_profiling_times_the_entire_render
t = Template.parse("{% include 'a_template' %}", profile: true) t = Template.parse("{% include 'a_template' %}", profile: true)
t.render! t.render!
assert(t.profiler.total_render_time >= 0, "Total render time was not calculated") assert t.profiler.total_render_time >= 0, "Total render time was not calculated"
end end
def test_profiling_uses_include_to_mark_children def test_profiling_uses_include_to_mark_children
@@ -82,7 +82,7 @@ class RenderProfilingTest < Minitest::Test
t.render! t.render!
include_node = t.profiler[1] include_node = t.profiler[1]
assert_equal(2, include_node.children.length) assert_equal 2, include_node.children.length
end end
def test_profiling_marks_children_with_the_name_of_included_partial def test_profiling_marks_children_with_the_name_of_included_partial
@@ -134,38 +134,23 @@ class RenderProfilingTest < Minitest::Test
timing_count += 1 timing_count += 1
end end
assert_equal(2, timing_count) assert_equal 2, timing_count
end end
def test_profiling_marks_children_of_if_blocks def test_profiling_marks_children_of_if_blocks
t = Template.parse("{% if true %} {% increment test %} {{ test }} {% endif %}", profile: true) t = Template.parse("{% if true %} {% increment test %} {{ test }} {% endif %}", profile: true)
t.render! t.render!
assert_equal(1, t.profiler.length) assert_equal 1, t.profiler.length
assert_equal(2, t.profiler[0].children.length) assert_equal 2, t.profiler[0].children.length
end end
def test_profiling_marks_children_of_for_blocks def test_profiling_marks_children_of_for_blocks
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"]) t.render!("collection" => ["one", "two"])
assert_equal(1, t.profiler.length) assert_equal 1, t.profiler.length
# Will profile each invocation of the for block # Will profile each invocation of the for block
assert_equal(2, t.profiler[0].children.length) assert_equal 2, t.profiler[0].children.length
end
def test_profiling_supports_self_time
t = Template.parse("{% for item in collection %} {{ item }} {% endfor %}", profile: true)
t.render!("collection" => ["one", "two"])
leaf = t.profiler[0].children[0]
assert_operator(leaf.self_time, :>, 0)
end
def test_profiling_supports_total_time
t = Template.parse("{% if true %} {% increment test %} {{ test }} {% endif %}", profile: true)
t.render!
assert_operator(t.profiler[0].total_time, :>, 0)
end end
end end

View File

@@ -16,31 +16,31 @@ class SecurityTest < Minitest::Test
end end
def test_no_instance_eval def test_no_instance_eval
text = %( {{ '1+1' | instance_eval }} ) text = %( {{ '1+1' | instance_eval }} )
expected = %( 1+1 ) expected = %( 1+1 )
assert_equal(expected, Template.parse(text).render!(@assigns)) assert_equal expected, Template.parse(text).render!(@assigns)
end end
def test_no_existing_instance_eval def test_no_existing_instance_eval
text = %( {{ '1+1' | __instance_eval__ }} ) text = %( {{ '1+1' | __instance_eval__ }} )
expected = %( 1+1 ) expected = %( 1+1 )
assert_equal(expected, Template.parse(text).render!(@assigns)) assert_equal expected, Template.parse(text).render!(@assigns)
end end
def test_no_instance_eval_after_mixing_in_new_filter def test_no_instance_eval_after_mixing_in_new_filter
text = %( {{ '1+1' | instance_eval }} ) text = %( {{ '1+1' | instance_eval }} )
expected = %( 1+1 ) expected = %( 1+1 )
assert_equal(expected, Template.parse(text).render!(@assigns)) assert_equal expected, Template.parse(text).render!(@assigns)
end end
def test_no_instance_eval_later_in_chain def test_no_instance_eval_later_in_chain
text = %( {{ '1+1' | add_one | instance_eval }} ) text = %( {{ '1+1' | add_one | instance_eval }} )
expected = %( 1+1 + 1 ) expected = %( 1+1 + 1 )
assert_equal(expected, Template.parse(text).render!(@assigns, filters: SecurityFilter)) assert_equal expected, Template.parse(text).render!(@assigns, filters: SecurityFilter)
end end
def test_does_not_add_filters_to_symbol_table def test_does_not_add_filters_to_symbol_table
@@ -49,32 +49,32 @@ class SecurityTest < Minitest::Test
test = %( {{ "some_string" | a_bad_filter }} ) test = %( {{ "some_string" | a_bad_filter }} )
template = Template.parse(test) template = Template.parse(test)
assert_equal([], (Symbol.all_symbols - current_symbols)) assert_equal [], (Symbol.all_symbols - current_symbols)
template.render! template.render!
assert_equal([], (Symbol.all_symbols - current_symbols)) assert_equal [], (Symbol.all_symbols - current_symbols)
end end
def test_does_not_add_drop_methods_to_symbol_table def test_does_not_add_drop_methods_to_symbol_table
current_symbols = Symbol.all_symbols current_symbols = Symbol.all_symbols
assigns = { 'drop' => Drop.new } assigns = { 'drop' => Drop.new }
assert_equal("", Template.parse("{{ drop.custom_method_1 }}", assigns).render!) assert_equal "", Template.parse("{{ drop.custom_method_1 }}", assigns).render!
assert_equal("", Template.parse("{{ drop.custom_method_2 }}", assigns).render!) assert_equal "", Template.parse("{{ drop.custom_method_2 }}", assigns).render!
assert_equal("", Template.parse("{{ drop.custom_method_3 }}", assigns).render!) assert_equal "", Template.parse("{{ drop.custom_method_3 }}", assigns).render!
assert_equal([], (Symbol.all_symbols - current_symbols)) assert_equal [], (Symbol.all_symbols - current_symbols)
end end
def test_max_depth_nested_blocks_does_not_raise_exception def test_max_depth_nested_blocks_does_not_raise_exception
depth = Liquid::Block::MAX_DEPTH depth = Liquid::Block::MAX_DEPTH
code = "{% if true %}" * depth + "rendered" + "{% endif %}" * depth code = "{% if true %}" * depth + "rendered" + "{% endif %}" * depth
assert_equal("rendered", Template.parse(code).render!) assert_equal "rendered", Template.parse(code).render!
end end
def test_more_than_max_depth_nested_blocks_raises_exception def test_more_than_max_depth_nested_blocks_raises_exception
depth = Liquid::Block::MAX_DEPTH + 1 depth = Liquid::Block::MAX_DEPTH + 1
code = "{% if true %}" * depth + "rendered" + "{% endif %}" * depth code = "{% if true %}" * depth + "rendered" + "{% endif %}" * depth
assert_raises(Liquid::StackLevelError) do assert_raises(Liquid::StackLevelError) do
Template.parse(code).render! Template.parse(code).render!
end end

View File

@@ -60,34 +60,34 @@ class StandardFiltersTest < Minitest::Test
end end
def test_size def test_size
assert_equal(3, @filters.size([1, 2, 3])) assert_equal 3, @filters.size([1, 2, 3])
assert_equal(0, @filters.size([])) assert_equal 0, @filters.size([])
assert_equal(0, @filters.size(nil)) assert_equal 0, @filters.size(nil)
end end
def test_downcase def test_downcase
assert_equal('testing', @filters.downcase("Testing")) assert_equal 'testing', @filters.downcase("Testing")
assert_equal('', @filters.downcase(nil)) assert_equal '', @filters.downcase(nil)
end end
def test_upcase def test_upcase
assert_equal('TESTING', @filters.upcase("Testing")) assert_equal 'TESTING', @filters.upcase("Testing")
assert_equal('', @filters.upcase(nil)) assert_equal '', @filters.upcase(nil)
end end
def test_slice def test_slice
assert_equal('oob', @filters.slice('foobar', 1, 3)) assert_equal 'oob', @filters.slice('foobar', 1, 3)
assert_equal('oobar', @filters.slice('foobar', 1, 1000)) assert_equal 'oobar', @filters.slice('foobar', 1, 1000)
assert_equal('', @filters.slice('foobar', 1, 0)) assert_equal '', @filters.slice('foobar', 1, 0)
assert_equal('o', @filters.slice('foobar', 1, 1)) assert_equal 'o', @filters.slice('foobar', 1, 1)
assert_equal('bar', @filters.slice('foobar', 3, 3)) assert_equal 'bar', @filters.slice('foobar', 3, 3)
assert_equal('ar', @filters.slice('foobar', -2, 2)) assert_equal 'ar', @filters.slice('foobar', -2, 2)
assert_equal('ar', @filters.slice('foobar', -2, 1000)) assert_equal 'ar', @filters.slice('foobar', -2, 1000)
assert_equal('r', @filters.slice('foobar', -1)) assert_equal 'r', @filters.slice('foobar', -1)
assert_equal('', @filters.slice(nil, 0)) assert_equal '', @filters.slice(nil, 0)
assert_equal('', @filters.slice('foobar', 100, 10)) assert_equal '', @filters.slice('foobar', 100, 10)
assert_equal('', @filters.slice('foobar', -100, 10)) assert_equal '', @filters.slice('foobar', -100, 10)
assert_equal('oob', @filters.slice('foobar', '1', '3')) assert_equal 'oob', @filters.slice('foobar', '1', '3')
assert_raises(Liquid::ArgumentError) do assert_raises(Liquid::ArgumentError) do
@filters.slice('foobar', nil) @filters.slice('foobar', nil)
end end
@@ -98,116 +98,119 @@ class StandardFiltersTest < Minitest::Test
def test_slice_on_arrays def test_slice_on_arrays
input = 'foobar'.split(//) input = 'foobar'.split(//)
assert_equal(%w(o o b), @filters.slice(input, 1, 3)) assert_equal %w(o o b), @filters.slice(input, 1, 3)
assert_equal(%w(o o b a r), @filters.slice(input, 1, 1000)) assert_equal %w(o o b a r), @filters.slice(input, 1, 1000)
assert_equal(%w(), @filters.slice(input, 1, 0)) assert_equal %w(), @filters.slice(input, 1, 0)
assert_equal(%w(o), @filters.slice(input, 1, 1)) assert_equal %w(o), @filters.slice(input, 1, 1)
assert_equal(%w(b a r), @filters.slice(input, 3, 3)) assert_equal %w(b a r), @filters.slice(input, 3, 3)
assert_equal(%w(a r), @filters.slice(input, -2, 2)) assert_equal %w(a r), @filters.slice(input, -2, 2)
assert_equal(%w(a r), @filters.slice(input, -2, 1000)) assert_equal %w(a r), @filters.slice(input, -2, 1000)
assert_equal(%w(r), @filters.slice(input, -1)) assert_equal %w(r), @filters.slice(input, -1)
assert_equal(%w(), @filters.slice(input, 100, 10)) assert_equal %w(), @filters.slice(input, 100, 10)
assert_equal(%w(), @filters.slice(input, -100, 10)) assert_equal %w(), @filters.slice(input, -100, 10)
end end
def test_truncate def test_truncate
assert_equal('1234...', @filters.truncate('1234567890', 7)) assert_equal '1234...', @filters.truncate('1234567890', 7)
assert_equal('1234567890', @filters.truncate('1234567890', 20)) assert_equal '1234567890', @filters.truncate('1234567890', 20)
assert_equal('...', @filters.truncate('1234567890', 0)) assert_equal '...', @filters.truncate('1234567890', 0)
assert_equal('1234567890', @filters.truncate('1234567890')) assert_equal '1234567890', @filters.truncate('1234567890')
assert_equal("测试...", @filters.truncate("测试测试测试测试", 5)) assert_equal "测试...", @filters.truncate("测试测试测试测试", 5)
assert_equal('12341', @filters.truncate("1234567890", 5, 1)) assert_equal '12341', @filters.truncate("1234567890", 5, 1)
end end
def test_split def test_split
assert_equal(['12', '34'], @filters.split('12~34', '~')) assert_equal ['12', '34'], @filters.split('12~34', '~')
assert_equal(['A? ', ' ,Z'], @filters.split('A? ~ ~ ~ ,Z', '~ ~ ~')) assert_equal ['A? ', ' ,Z'], @filters.split('A? ~ ~ ~ ,Z', '~ ~ ~')
assert_equal(['A?Z'], @filters.split('A?Z', '~')) assert_equal ['A?Z'], @filters.split('A?Z', '~')
assert_equal([], @filters.split(nil, ' ')) assert_equal [], @filters.split(nil, ' ')
assert_equal(['A', 'Z'], @filters.split('A1Z', 1)) assert_equal ['A', 'Z'], @filters.split('A1Z', 1)
end end
def test_escape def test_escape
assert_equal('&lt;strong&gt;', @filters.escape('<strong>')) assert_equal '&lt;strong&gt;', @filters.escape('<strong>')
assert_equal('1', @filters.escape(1)) assert_equal '1', @filters.escape(1)
assert_equal('2001-02-03', @filters.escape(Date.new(2001, 2, 3))) assert_equal '2001-02-03', @filters.escape(Date.new(2001, 2, 3))
assert_nil(@filters.escape(nil)) assert_nil @filters.escape(nil)
end end
def test_h def test_h
assert_equal('&lt;strong&gt;', @filters.h('<strong>')) assert_equal '&lt;strong&gt;', @filters.h('<strong>')
assert_equal('1', @filters.h(1)) assert_equal '1', @filters.h(1)
assert_equal('2001-02-03', @filters.h(Date.new(2001, 2, 3))) assert_equal '2001-02-03', @filters.h(Date.new(2001, 2, 3))
assert_nil(@filters.h(nil)) assert_nil @filters.h(nil)
end end
def test_escape_once def test_escape_once
assert_equal('&lt;strong&gt;Hulk&lt;/strong&gt;', @filters.escape_once('&lt;strong&gt;Hulk</strong>')) assert_equal '&lt;strong&gt;Hulk&lt;/strong&gt;', @filters.escape_once('&lt;strong&gt;Hulk</strong>')
end end
def test_url_encode def test_url_encode
assert_equal('foo%2B1%40example.com', @filters.url_encode('foo+1@example.com')) assert_equal 'foo%2B1%40example.com', @filters.url_encode('foo+1@example.com')
assert_equal('1', @filters.url_encode(1)) assert_equal '1', @filters.url_encode(1)
assert_equal('2001-02-03', @filters.url_encode(Date.new(2001, 2, 3))) assert_equal '2001-02-03', @filters.url_encode(Date.new(2001, 2, 3))
assert_nil(@filters.url_encode(nil)) assert_nil @filters.url_encode(nil)
end end
def test_url_decode def test_url_decode
assert_equal('foo bar', @filters.url_decode('foo+bar')) assert_equal 'foo bar', @filters.url_decode('foo+bar')
assert_equal('foo bar', @filters.url_decode('foo%20bar')) assert_equal 'foo bar', @filters.url_decode('foo%20bar')
assert_equal('foo+1@example.com', @filters.url_decode('foo%2B1%40example.com')) assert_equal 'foo+1@example.com', @filters.url_decode('foo%2B1%40example.com')
assert_equal('1', @filters.url_decode(1)) assert_equal '1', @filters.url_decode(1)
assert_equal('2001-02-03', @filters.url_decode(Date.new(2001, 2, 3))) assert_equal '2001-02-03', @filters.url_decode(Date.new(2001, 2, 3))
assert_nil(@filters.url_decode(nil)) assert_nil @filters.url_decode(nil)
exception = assert_raises Liquid::ArgumentError do exception = assert_raises Liquid::ArgumentError do
@filters.url_decode('%ff') @filters.url_decode('%ff')
end end
assert_equal('Liquid error: invalid byte sequence in UTF-8', exception.message) assert_equal 'Liquid error: invalid byte sequence in UTF-8', exception.message
end end
def test_truncatewords def test_truncatewords
assert_equal('one two three', @filters.truncatewords('one two three', 4)) assert_equal 'one two three', @filters.truncatewords('one two three', 4)
assert_equal('one two...', @filters.truncatewords('one two three', 2)) assert_equal 'one two...', @filters.truncatewords('one two three', 2)
assert_equal('one two three', @filters.truncatewords('one two three')) assert_equal 'one two three', @filters.truncatewords('one two three')
assert_equal( assert_equal 'Two small (13&#8221; x 5.5&#8221; x 10&#8221; high) baskets fit inside one large basket (13&#8221;...', @filters.truncatewords('Two small (13&#8221; x 5.5&#8221; x 10&#8221; high) baskets fit inside one large basket (13&#8221; x 16&#8221; x 10.5&#8221; high) with cover.', 15)
'Two small (13&#8221; x 5.5&#8221; x 10&#8221; high) baskets fit inside one large basket (13&#8221;...', assert_equal "测试测试测试测试", @filters.truncatewords('测试测试测试测试', 5)
@filters.truncatewords('Two small (13&#8221; x 5.5&#8221; x 10&#8221; high) baskets fit inside one large basket (13&#8221; x 16&#8221; x 10.5&#8221; high) with cover.', 15) assert_equal 'one two1', @filters.truncatewords("one two three", 2, 1)
)
assert_equal("测试测试测试测试", @filters.truncatewords('测试测试测试测试', 5))
assert_equal('one two1', @filters.truncatewords("one two three", 2, 1))
end end
def test_strip_html def test_strip_html
assert_equal('test', @filters.strip_html("<div>test</div>")) assert_equal 'test', @filters.strip_html("<div>test</div>")
assert_equal('test', @filters.strip_html("<div id='test'>test</div>")) assert_equal 'test', @filters.strip_html("<div id='test'>test</div>")
assert_equal('', @filters.strip_html("<script type='text/javascript'>document.write('some stuff');</script>")) assert_equal '', @filters.strip_html("<script type='text/javascript'>document.write('some stuff');</script>")
assert_equal('', @filters.strip_html("<style type='text/css'>foo bar</style>")) assert_equal '', @filters.strip_html("<style type='text/css'>foo bar</style>")
assert_equal('test', @filters.strip_html("<div\nclass='multiline'>test</div>")) assert_equal 'test', @filters.strip_html("<div\nclass='multiline'>test</div>")
assert_equal('test', @filters.strip_html("<!-- foo bar \n test -->test")) assert_equal 'test', @filters.strip_html("<!-- foo bar \n test -->test")
assert_equal('', @filters.strip_html(nil)) assert_equal '', @filters.strip_html(nil)
# Quirk of the existing implementation # Quirk of the existing implementation
assert_equal('foo;', @filters.strip_html("<<<script </script>script>foo;</script>")) assert_equal 'foo;', @filters.strip_html("<<<script </script>script>foo;</script>")
end end
def test_join 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('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)) assert_equal '1121314', @filters.join([1, 2, 3, 4], 1)
end end
def test_sort def test_sort
assert_equal([1, 2, 3, 4], @filters.sort([4, 3, 2, 1])) assert_equal [1, 2, 3, 4], @filters.sort([4, 3, 2, 1])
assert_equal([{ "a" => 1 }, { "a" => 2 }, { "a" => 3 }, { "a" => 4 }], @filters.sort([{ "a" => 4 }, { "a" => 3 }, { "a" => 1 }, { "a" => 2 }], "a")) 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_numeric
assert_equal ['1', '2', '3', '10'], @filters.sort_numeric(['10', '3', '2', '1'])
assert_equal [{ "a" => '1' }, { "a" => '2' }, { "a" => '3' }, { "a" => '10' }],
@filters.sort_numeric([{ "a" => '10' }, { "a" => '3' }, { "a" => '1' }, { "a" => '2' }], "a")
end end
def test_sort_with_nils def test_sort_with_nils
assert_equal([1, 2, 3, 4, nil], @filters.sort([nil, 4, 3, 2, 1])) assert_equal [1, 2, 3, 4, nil], @filters.sort([nil, 4, 3, 2, 1])
assert_equal([{ "a" => 1 }, { "a" => 2 }, { "a" => 3 }, { "a" => 4 }, {}], @filters.sort([{ "a" => 4 }, { "a" => 3 }, {}, { "a" => 1 }, { "a" => 2 }], "a")) assert_equal [{ "a" => 1 }, { "a" => 2 }, { "a" => 3 }, { "a" => 4 }, {}], @filters.sort([{ "a" => 4 }, { "a" => 3 }, {}, { "a" => 1 }, { "a" => 2 }], "a")
end end
def test_sort_when_property_is_sometimes_missing_puts_nils_last def test_sort_when_property_is_sometimes_missing_puts_nils_last
input = [ input = [
{ "price" => 4, "handle" => "alpha" }, { "price" => 4, "handle" => "alpha" },
{ "handle" => "beta" }, { "handle" => "beta" },
{ "price" => 1, "handle" => "gamma" }, { "price" => 1, "handle" => "gamma" },
@@ -221,21 +224,21 @@ class StandardFiltersTest < Minitest::Test
{ "handle" => "delta" }, { "handle" => "delta" },
{ "handle" => "beta" }, { "handle" => "beta" },
] ]
assert_equal(expectation, @filters.sort(input, "price")) assert_equal expectation, @filters.sort(input, "price")
end end
def test_sort_natural def test_sort_natural
assert_equal(["a", "B", "c", "D"], @filters.sort_natural(["c", "D", "a", "B"])) assert_equal ["a", "B", "c", "D"], @filters.sort_natural(["c", "D", "a", "B"])
assert_equal([{ "a" => "a" }, { "a" => "B" }, { "a" => "c" }, { "a" => "D" }], @filters.sort_natural([{ "a" => "D" }, { "a" => "c" }, { "a" => "a" }, { "a" => "B" }], "a")) assert_equal [{ "a" => "a" }, { "a" => "B" }, { "a" => "c" }, { "a" => "D" }], @filters.sort_natural([{ "a" => "D" }, { "a" => "c" }, { "a" => "a" }, { "a" => "B" }], "a")
end end
def test_sort_natural_with_nils def test_sort_natural_with_nils
assert_equal(["a", "B", "c", "D", nil], @filters.sort_natural([nil, "c", "D", "a", "B"])) assert_equal ["a", "B", "c", "D", nil], @filters.sort_natural([nil, "c", "D", "a", "B"])
assert_equal([{ "a" => "a" }, { "a" => "B" }, { "a" => "c" }, { "a" => "D" }, {}], @filters.sort_natural([{ "a" => "D" }, { "a" => "c" }, {}, { "a" => "a" }, { "a" => "B" }], "a")) assert_equal [{ "a" => "a" }, { "a" => "B" }, { "a" => "c" }, { "a" => "D" }, {}], @filters.sort_natural([{ "a" => "D" }, { "a" => "c" }, {}, { "a" => "a" }, { "a" => "B" }], "a")
end end
def test_sort_natural_when_property_is_sometimes_missing_puts_nils_last def test_sort_natural_when_property_is_sometimes_missing_puts_nils_last
input = [ input = [
{ "price" => "4", "handle" => "alpha" }, { "price" => "4", "handle" => "alpha" },
{ "handle" => "beta" }, { "handle" => "beta" },
{ "price" => "1", "handle" => "gamma" }, { "price" => "1", "handle" => "gamma" },
@@ -249,7 +252,7 @@ class StandardFiltersTest < Minitest::Test
{ "handle" => "delta" }, { "handle" => "delta" },
{ "handle" => "beta" }, { "handle" => "beta" },
] ]
assert_equal(expectation, @filters.sort_natural(input, "price")) assert_equal expectation, @filters.sort_natural(input, "price")
end end
def test_sort_natural_case_check def test_sort_natural_case_check
@@ -271,12 +274,12 @@ class StandardFiltersTest < Minitest::Test
{ "key" => "Z" }, { "key" => "Z" },
{ "fake" => "t" }, { "fake" => "t" },
] ]
assert_equal(expectation, @filters.sort_natural(input, "key")) assert_equal expectation, @filters.sort_natural(input, "key")
assert_equal(["a", "b", "c", "X", "Y", "Z"], @filters.sort_natural(["X", "Y", "Z", "a", "b", "c"])) assert_equal ["a", "b", "c", "X", "Y", "Z"], @filters.sort_natural(["X", "Y", "Z", "a", "b", "c"])
end end
def test_sort_empty_array def test_sort_empty_array
assert_equal([], @filters.sort([], "a")) assert_equal [], @filters.sort([], "a")
end end
def test_sort_invalid_property def test_sort_invalid_property
@@ -292,7 +295,11 @@ class StandardFiltersTest < Minitest::Test
end end
def test_sort_natural_empty_array def test_sort_natural_empty_array
assert_equal([], @filters.sort_natural([], "a")) assert_equal [], @filters.sort_natural([], "a")
end
def test_sort_numeric_empty_array
assert_equal [], @filters.sort_numeric([], "a")
end end
def test_sort_natural_invalid_property def test_sort_natural_invalid_property
@@ -308,26 +315,26 @@ class StandardFiltersTest < Minitest::Test
end end
def test_legacy_sort_hash def test_legacy_sort_hash
assert_equal([{ a: 1, b: 2 }], @filters.sort(a: 1, b: 2)) assert_equal [{ a: 1, b: 2 }], @filters.sort(a: 1, b: 2)
end end
def test_numerical_vs_lexicographical_sort def test_numerical_vs_lexicographical_sort
assert_equal([2, 10], @filters.sort([10, 2])) assert_equal [2, 10], @filters.sort([10, 2])
assert_equal([{ "a" => 2 }, { "a" => 10 }], @filters.sort([{ "a" => 10 }, { "a" => 2 }], "a")) assert_equal [{ "a" => 2 }, { "a" => 10 }], @filters.sort([{ "a" => 10 }, { "a" => 2 }], "a")
assert_equal(["10", "2"], @filters.sort(["10", "2"])) assert_equal ["10", "2"], @filters.sort(["10", "2"])
assert_equal([{ "a" => "10" }, { "a" => "2" }], @filters.sort([{ "a" => "10" }, { "a" => "2" }], "a")) assert_equal [{ "a" => "10" }, { "a" => "2" }], @filters.sort([{ "a" => "10" }, { "a" => "2" }], "a")
end end
def test_uniq def test_uniq
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 testdrop = TestDrop.new
assert_equal([testdrop], @filters.uniq([testdrop, TestDrop.new], 'test')) assert_equal [testdrop], @filters.uniq([testdrop, TestDrop.new], 'test')
end end
def test_uniq_empty_array def test_uniq_empty_array
assert_equal([], @filters.uniq([], "a")) assert_equal [], @filters.uniq([], "a")
end end
def test_uniq_invalid_property def test_uniq_invalid_property
@@ -343,7 +350,7 @@ class StandardFiltersTest < Minitest::Test
end end
def test_compact_empty_array def test_compact_empty_array
assert_equal([], @filters.compact([], "a")) assert_equal [], @filters.compact([], "a")
end end
def test_compact_invalid_property def test_compact_invalid_property
@@ -359,51 +366,51 @@ class StandardFiltersTest < Minitest::Test
end end
def test_reverse def test_reverse
assert_equal([4, 3, 2, 1], @filters.reverse([1, 2, 3, 4])) assert_equal [4, 3, 2, 1], @filters.reverse([1, 2, 3, 4])
end end
def test_legacy_reverse_hash def test_legacy_reverse_hash
assert_equal([{ a: 1, b: 2 }], @filters.reverse(a: 1, b: 2)) assert_equal [{ a: 1, b: 2 }], @filters.reverse(a: 1, b: 2)
end end
def test_map def test_map
assert_equal([1, 2, 3, 4], @filters.map([{ "a" => 1 }, { "a" => 2 }, { "a" => 3 }, { "a" => 4 }], 'a')) assert_equal [1, 2, 3, 4], @filters.map([{ "a" => 1 }, { "a" => 2 }, { "a" => 3 }, { "a" => 4 }], 'a')
assert_template_result('abc', "{{ ary | map:'foo' | map:'bar' }}", assert_template_result 'abc', "{{ ary | map:'foo' | map:'bar' }}",
'ary' => [{ 'foo' => { 'bar' => 'a' } }, { 'foo' => { 'bar' => 'b' } }, { 'foo' => { 'bar' => 'c' } }]) 'ary' => [{ 'foo' => { 'bar' => 'a' } }, { 'foo' => { 'bar' => 'b' } }, { 'foo' => { 'bar' => 'c' } }]
end end
def test_map_doesnt_call_arbitrary_stuff def test_map_doesnt_call_arbitrary_stuff
assert_template_result("", '{{ "foo" | map: "__id__" }}') assert_template_result "", '{{ "foo" | map: "__id__" }}'
assert_template_result("", '{{ "foo" | map: "inspect" }}') assert_template_result "", '{{ "foo" | map: "inspect" }}'
end end
def test_map_calls_to_liquid def test_map_calls_to_liquid
t = TestThing.new t = TestThing.new
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_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 }] }
end end
def test_legacy_map_on_hashes_with_dynamic_key def test_legacy_map_on_hashes_with_dynamic_key
template = "{% assign key = 'foo' %}{{ thing | map: key | map: 'bar' }}" template = "{% assign key = 'foo' %}{{ thing | map: key | map: 'bar' }}"
hash = { "foo" => { "bar" => 42 } } hash = { "foo" => { "bar" => 42 } }
assert_template_result("42", template, "thing" => hash) assert_template_result "42", template, "thing" => hash
end end
def test_sort_calls_to_liquid def test_sort_calls_to_liquid
t = TestThing.new t = TestThing.new
Liquid::Template.parse('{{ foo | sort: "whatever" }}').render("foo" => [t]) Liquid::Template.parse('{{ foo | sort: "whatever" }}').render("foo" => [t])
assert(t.foo > 0) assert t.foo > 0
end end
def test_map_over_proc def test_map_over_proc
drop = TestDrop.new drop = TestDrop.new
p = proc { drop } p = proc { drop }
templ = '{{ procs | map: "test" }}' templ = '{{ procs | map: "test" }}'
assert_template_result("testfoo", templ, "procs" => [p]) assert_template_result "testfoo", templ, "procs" => [p]
end end
def test_map_over_drops_returning_procs def test_map_over_drops_returning_procs
@@ -416,11 +423,11 @@ class StandardFiltersTest < Minitest::Test
}, },
] ]
templ = '{{ drops | map: "proc" }}' templ = '{{ drops | map: "proc" }}'
assert_template_result("foobar", templ, "drops" => drops) assert_template_result "foobar", templ, "drops" => drops
end end
def test_map_works_on_enumerables def test_map_works_on_enumerables
assert_template_result("123", '{{ foo | map: "foo" }}', "foo" => TestEnumerable.new) assert_template_result "123", '{{ foo | map: "foo" }}', "foo" => TestEnumerable.new
end end
def test_map_returns_empty_on_2d_input_array def test_map_returns_empty_on_2d_input_array
@@ -447,42 +454,42 @@ class StandardFiltersTest < Minitest::Test
end end
def test_sort_works_on_enumerables def test_sort_works_on_enumerables
assert_template_result("213", '{{ foo | sort: "bar" | map: "foo" }}', "foo" => TestEnumerable.new) assert_template_result "213", '{{ foo | sort: "bar" | map: "foo" }}', "foo" => TestEnumerable.new
end end
def test_first_and_last_call_to_liquid def test_first_and_last_call_to_liquid
assert_template_result('foobar', '{{ foo | first }}', 'foo' => [ThingWithToLiquid.new]) assert_template_result 'foobar', '{{ foo | first }}', 'foo' => [ThingWithToLiquid.new]
assert_template_result('foobar', '{{ foo | last }}', 'foo' => [ThingWithToLiquid.new]) assert_template_result 'foobar', '{{ foo | last }}', 'foo' => [ThingWithToLiquid.new]
end end
def test_truncate_calls_to_liquid def test_truncate_calls_to_liquid
assert_template_result("wo...", '{{ foo | truncate: 5 }}', "foo" => TestThing.new) assert_template_result "wo...", '{{ foo | truncate: 5 }}', "foo" => TestThing.new
end end
def test_date def test_date
assert_equal('May', @filters.date(Time.parse("2006-05-05 10:00:00"), "%B")) assert_equal 'May', @filters.date(Time.parse("2006-05-05 10:00:00"), "%B")
assert_equal('June', @filters.date(Time.parse("2006-06-05 10:00:00"), "%B")) assert_equal 'June', @filters.date(Time.parse("2006-06-05 10:00:00"), "%B")
assert_equal('July', @filters.date(Time.parse("2006-07-05 10:00:00"), "%B")) assert_equal 'July', @filters.date(Time.parse("2006-07-05 10:00:00"), "%B")
assert_equal('May', @filters.date("2006-05-05 10:00:00", "%B")) assert_equal 'May', @filters.date("2006-05-05 10:00:00", "%B")
assert_equal('June', @filters.date("2006-06-05 10:00:00", "%B")) assert_equal 'June', @filters.date("2006-06-05 10:00:00", "%B")
assert_equal('July', @filters.date("2006-07-05 10:00:00", "%B")) assert_equal 'July', @filters.date("2006-07-05 10:00:00", "%B")
assert_equal('2006-07-05 10:00:00', @filters.date("2006-07-05 10:00:00", "")) assert_equal '2006-07-05 10:00:00', @filters.date("2006-07-05 10:00:00", "")
assert_equal('2006-07-05 10:00:00', @filters.date("2006-07-05 10:00:00", "")) assert_equal '2006-07-05 10:00:00', @filters.date("2006-07-05 10:00:00", "")
assert_equal('2006-07-05 10:00:00', @filters.date("2006-07-05 10:00:00", "")) assert_equal '2006-07-05 10:00:00', @filters.date("2006-07-05 10:00:00", "")
assert_equal('2006-07-05 10:00:00', @filters.date("2006-07-05 10:00:00", nil)) assert_equal '2006-07-05 10:00:00', @filters.date("2006-07-05 10:00:00", nil)
assert_equal('07/05/2006', @filters.date("2006-07-05 10:00:00", "%m/%d/%Y")) assert_equal '07/05/2006', @filters.date("2006-07-05 10:00:00", "%m/%d/%Y")
assert_equal("07/16/2004", @filters.date("Fri Jul 16 01:00:00 2004", "%m/%d/%Y")) assert_equal "07/16/2004", @filters.date("Fri Jul 16 01:00:00 2004", "%m/%d/%Y")
assert_equal(Date.today.year.to_s, @filters.date('now', '%Y')) assert_equal Date.today.year.to_s, @filters.date('now', '%Y')
assert_equal(Date.today.year.to_s, @filters.date('today', '%Y')) assert_equal Date.today.year.to_s, @filters.date('today', '%Y')
assert_equal(Date.today.year.to_s, @filters.date('Today', '%Y')) assert_equal Date.today.year.to_s, @filters.date('Today', '%Y')
assert_nil(@filters.date(nil, "%B")) assert_nil @filters.date(nil, "%B")
assert_equal('', @filters.date('', "%B")) assert_equal '', @filters.date('', "%B")
with_timezone("UTC") do with_timezone("UTC") do
assert_equal "07/05/2006", @filters.date(1152098955, "%m/%d/%Y") assert_equal "07/05/2006", @filters.date(1152098955, "%m/%d/%Y")
@@ -491,169 +498,169 @@ class StandardFiltersTest < Minitest::Test
end end
def test_first_last def test_first_last
assert_equal(1, @filters.first([1, 2, 3])) assert_equal 1, @filters.first([1, 2, 3])
assert_equal(3, @filters.last([1, 2, 3])) assert_equal 3, @filters.last([1, 2, 3])
assert_nil(@filters.first([])) assert_nil @filters.first([])
assert_nil(@filters.last([])) assert_nil @filters.last([])
end end
def test_replace def test_replace
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 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 '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 '2 1 1 1', @filters.replace_first('1 1 1 1', 1, 2)
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 }}"
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_equal ' ', @filters.remove("1 1 1 1", 1)
assert_equal('a a a', @filters.remove_first("a a a a", 'a ')) 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 ' 1 1 1', @filters.remove_first("1 1 1 1", 1)
assert_template_result('a a a', "{{ 'a a a a' | remove_first: 'a ' }}") assert_template_result 'a a a', "{{ 'a a a a' | remove_first: 'a ' }}"
end end
def test_pipes_in_string_arguments def test_pipes_in_string_arguments
assert_template_result('foobar', "{{ 'foo|bar' | remove: '|' }}") assert_template_result 'foobar', "{{ 'foo|bar' | remove: '|' }}"
end end
def test_strip def test_strip
assert_template_result('ab c', "{{ source | strip }}", 'source' => " ab c ") assert_template_result 'ab c', "{{ source | strip }}", 'source' => " ab c "
assert_template_result('ab c', "{{ source | strip }}", 'source' => " \tab c \n \t") assert_template_result 'ab c', "{{ source | strip }}", 'source' => " \tab c \n \t"
end end
def test_lstrip def test_lstrip
assert_template_result('ab c ', "{{ source | lstrip }}", 'source' => " ab c ") assert_template_result 'ab c ', "{{ source | lstrip }}", 'source' => " ab c "
assert_template_result("ab c \n \t", "{{ source | lstrip }}", 'source' => " \tab c \n \t") assert_template_result "ab c \n \t", "{{ source | lstrip }}", 'source' => " \tab c \n \t"
end end
def test_rstrip def test_rstrip
assert_template_result(" ab c", "{{ source | rstrip }}", 'source' => " ab c ") assert_template_result " ab c", "{{ source | rstrip }}", 'source' => " ab c "
assert_template_result(" \tab c", "{{ source | rstrip }}", 'source' => " \tab c \n \t") assert_template_result " \tab c", "{{ source | rstrip }}", 'source' => " \tab c \n \t"
end end
def test_strip_newlines def test_strip_newlines
assert_template_result('abc', "{{ source | strip_newlines }}", 'source' => "a\nb\nc") assert_template_result 'abc', "{{ source | strip_newlines }}", 'source' => "a\nb\nc"
assert_template_result('abc', "{{ source | strip_newlines }}", 'source' => "a\r\nb\nc") assert_template_result 'abc', "{{ source | strip_newlines }}", 'source' => "a\r\nb\nc"
end end
def test_newlines_to_br def test_newlines_to_br
assert_template_result("a<br />\nb<br />\nc", "{{ source | newline_to_br }}", 'source' => "a\nb\nc") assert_template_result "a<br />\nb<br />\nc", "{{ source | newline_to_br }}", 'source' => "a\nb\nc"
end end
def test_plus def test_plus
assert_template_result("2", "{{ 1 | plus:1 }}") assert_template_result "2", "{{ 1 | plus:1 }}"
assert_template_result("2.0", "{{ '1' | plus:'1.0' }}") assert_template_result "2.0", "{{ '1' | plus:'1.0' }}"
assert_template_result("5", "{{ price | plus:'2' }}", 'price' => NumberLikeThing.new(3)) assert_template_result "5", "{{ price | plus:'2' }}", 'price' => NumberLikeThing.new(3)
end end
def test_minus def test_minus
assert_template_result("4", "{{ input | minus:operand }}", 'input' => 5, 'operand' => 1) assert_template_result "4", "{{ input | minus:operand }}", 'input' => 5, 'operand' => 1
assert_template_result("2.3", "{{ '4.3' | minus:'2' }}") assert_template_result "2.3", "{{ '4.3' | minus:'2' }}"
assert_template_result("5", "{{ price | minus:'2' }}", 'price' => NumberLikeThing.new(7)) assert_template_result "5", "{{ price | minus:'2' }}", 'price' => NumberLikeThing.new(7)
end end
def test_abs def test_abs
assert_template_result("17", "{{ 17 | abs }}") assert_template_result "17", "{{ 17 | abs }}"
assert_template_result("17", "{{ -17 | abs }}") assert_template_result "17", "{{ -17 | abs }}"
assert_template_result("17", "{{ '17' | abs }}") assert_template_result "17", "{{ '17' | abs }}"
assert_template_result("17", "{{ '-17' | abs }}") assert_template_result "17", "{{ '-17' | abs }}"
assert_template_result("0", "{{ 0 | abs }}") assert_template_result "0", "{{ 0 | abs }}"
assert_template_result("0", "{{ '0' | abs }}") assert_template_result "0", "{{ '0' | abs }}"
assert_template_result("17.42", "{{ 17.42 | abs }}") assert_template_result "17.42", "{{ 17.42 | abs }}"
assert_template_result("17.42", "{{ -17.42 | abs }}") assert_template_result "17.42", "{{ -17.42 | abs }}"
assert_template_result("17.42", "{{ '17.42' | abs }}") assert_template_result "17.42", "{{ '17.42' | abs }}"
assert_template_result("17.42", "{{ '-17.42' | abs }}") assert_template_result "17.42", "{{ '-17.42' | abs }}"
end end
def test_times def test_times
assert_template_result("12", "{{ 3 | times:4 }}") assert_template_result "12", "{{ 3 | times:4 }}"
assert_template_result("0", "{{ 'foo' | times:4 }}") assert_template_result "0", "{{ 'foo' | times:4 }}"
assert_template_result("6", "{{ '2.1' | times:3 | replace: '.','-' | plus:0}}") assert_template_result "6", "{{ '2.1' | times:3 | replace: '.','-' | plus:0}}"
assert_template_result("7.25", "{{ 0.0725 | times:100 }}") assert_template_result "7.25", "{{ 0.0725 | times:100 }}"
assert_template_result("-7.25", '{{ "-0.0725" | times:100 }}') assert_template_result "-7.25", '{{ "-0.0725" | times:100 }}'
assert_template_result("7.25", '{{ "-0.0725" | times: -100 }}') assert_template_result "7.25", '{{ "-0.0725" | times: -100 }}'
assert_template_result("4", "{{ price | times:2 }}", 'price' => NumberLikeThing.new(2)) assert_template_result "4", "{{ price | times:2 }}", 'price' => NumberLikeThing.new(2)
end end
def test_divided_by def test_divided_by
assert_template_result("4", "{{ 12 | divided_by:3 }}") assert_template_result "4", "{{ 12 | divided_by:3 }}"
assert_template_result("4", "{{ 14 | divided_by:3 }}") assert_template_result "4", "{{ 14 | divided_by:3 }}"
assert_template_result("5", "{{ 15 | divided_by:3 }}") assert_template_result "5", "{{ 15 | divided_by:3 }}"
assert_equal("Liquid error: divided by 0", Template.parse("{{ 5 | divided_by:0 }}").render) assert_equal "Liquid error: divided by 0", Template.parse("{{ 5 | divided_by:0 }}").render
assert_template_result("0.5", "{{ 2.0 | divided_by:4 }}") assert_template_result "0.5", "{{ 2.0 | divided_by:4 }}"
assert_raises(Liquid::ZeroDivisionError) do assert_raises(Liquid::ZeroDivisionError) do
assert_template_result "4", "{{ 1 | modulo: 0 }}" assert_template_result "4", "{{ 1 | modulo: 0 }}"
end end
assert_template_result("5", "{{ price | divided_by:2 }}", 'price' => NumberLikeThing.new(10)) assert_template_result "5", "{{ price | divided_by:2 }}", 'price' => NumberLikeThing.new(10)
end end
def test_modulo def test_modulo
assert_template_result("1", "{{ 3 | modulo:2 }}") assert_template_result "1", "{{ 3 | modulo:2 }}"
assert_raises(Liquid::ZeroDivisionError) do assert_raises(Liquid::ZeroDivisionError) do
assert_template_result "4", "{{ 1 | modulo: 0 }}" assert_template_result "4", "{{ 1 | modulo: 0 }}"
end end
assert_template_result("1", "{{ price | modulo:2 }}", 'price' => NumberLikeThing.new(3)) assert_template_result "1", "{{ price | modulo:2 }}", 'price' => NumberLikeThing.new(3)
end end
def test_round def test_round
assert_template_result("5", "{{ input | round }}", 'input' => 4.6) assert_template_result "5", "{{ input | round }}", 'input' => 4.6
assert_template_result("4", "{{ '4.3' | round }}") assert_template_result "4", "{{ '4.3' | round }}"
assert_template_result("4.56", "{{ input | round: 2 }}", 'input' => 4.5612) assert_template_result "4.56", "{{ input | round: 2 }}", 'input' => 4.5612
assert_raises(Liquid::FloatDomainError) do assert_raises(Liquid::FloatDomainError) do
assert_template_result "4", "{{ 1.0 | divided_by: 0.0 | round }}" assert_template_result "4", "{{ 1.0 | divided_by: 0.0 | round }}"
end end
assert_template_result("5", "{{ price | round }}", 'price' => NumberLikeThing.new(4.6)) assert_template_result "5", "{{ price | round }}", 'price' => NumberLikeThing.new(4.6)
assert_template_result("4", "{{ price | round }}", 'price' => NumberLikeThing.new(4.3)) assert_template_result "4", "{{ price | round }}", 'price' => NumberLikeThing.new(4.3)
end end
def test_ceil def test_ceil
assert_template_result("5", "{{ input | ceil }}", 'input' => 4.6) assert_template_result "5", "{{ input | ceil }}", 'input' => 4.6
assert_template_result("5", "{{ '4.3' | ceil }}") assert_template_result "5", "{{ '4.3' | ceil }}"
assert_raises(Liquid::FloatDomainError) do assert_raises(Liquid::FloatDomainError) do
assert_template_result "4", "{{ 1.0 | divided_by: 0.0 | ceil }}" assert_template_result "4", "{{ 1.0 | divided_by: 0.0 | ceil }}"
end end
assert_template_result("5", "{{ price | ceil }}", 'price' => NumberLikeThing.new(4.6)) assert_template_result "5", "{{ price | ceil }}", 'price' => NumberLikeThing.new(4.6)
end end
def test_floor def test_floor
assert_template_result("4", "{{ input | floor }}", 'input' => 4.6) assert_template_result "4", "{{ input | floor }}", 'input' => 4.6
assert_template_result("4", "{{ '4.3' | floor }}") assert_template_result "4", "{{ '4.3' | floor }}"
assert_raises(Liquid::FloatDomainError) do assert_raises(Liquid::FloatDomainError) do
assert_template_result "4", "{{ 1.0 | divided_by: 0.0 | floor }}" assert_template_result "4", "{{ 1.0 | divided_by: 0.0 | floor }}"
end end
assert_template_result("5", "{{ price | floor }}", 'price' => NumberLikeThing.new(5.4)) assert_template_result "5", "{{ price | floor }}", 'price' => NumberLikeThing.new(5.4)
end end
def test_at_most def test_at_most
assert_template_result("4", "{{ 5 | at_most:4 }}") assert_template_result "4", "{{ 5 | at_most:4 }}"
assert_template_result("5", "{{ 5 | at_most:5 }}") assert_template_result "5", "{{ 5 | at_most:5 }}"
assert_template_result("5", "{{ 5 | at_most:6 }}") assert_template_result "5", "{{ 5 | at_most:6 }}"
assert_template_result("4.5", "{{ 4.5 | at_most:5 }}") assert_template_result "4.5", "{{ 4.5 | at_most:5 }}"
assert_template_result("5", "{{ width | at_most:5 }}", 'width' => NumberLikeThing.new(6)) assert_template_result "5", "{{ width | at_most:5 }}", 'width' => NumberLikeThing.new(6)
assert_template_result("4", "{{ width | at_most:5 }}", 'width' => NumberLikeThing.new(4)) assert_template_result "4", "{{ width | at_most:5 }}", 'width' => NumberLikeThing.new(4)
assert_template_result("4", "{{ 5 | at_most: width }}", 'width' => NumberLikeThing.new(4)) assert_template_result "4", "{{ 5 | at_most: width }}", 'width' => NumberLikeThing.new(4)
end end
def test_at_least def test_at_least
assert_template_result("5", "{{ 5 | at_least:4 }}") assert_template_result "5", "{{ 5 | at_least:4 }}"
assert_template_result("5", "{{ 5 | at_least:5 }}") assert_template_result "5", "{{ 5 | at_least:5 }}"
assert_template_result("6", "{{ 5 | at_least:6 }}") assert_template_result "6", "{{ 5 | at_least:6 }}"
assert_template_result("5", "{{ 4.5 | at_least:5 }}") assert_template_result "5", "{{ 4.5 | at_least:5 }}"
assert_template_result("6", "{{ width | at_least:5 }}", 'width' => NumberLikeThing.new(6)) assert_template_result "6", "{{ width | at_least:5 }}", 'width' => NumberLikeThing.new(6)
assert_template_result("5", "{{ width | at_least:5 }}", 'width' => NumberLikeThing.new(4)) assert_template_result "5", "{{ width | at_least:5 }}", 'width' => NumberLikeThing.new(4)
assert_template_result("6", "{{ 5 | at_least: width }}", 'width' => NumberLikeThing.new(6)) assert_template_result "6", "{{ 5 | at_least: width }}", 'width' => NumberLikeThing.new(6)
end end
def test_append def test_append
@@ -663,9 +670,9 @@ class StandardFiltersTest < Minitest::Test
end end
def test_concat def test_concat
assert_equal([1, 2, 3, 4], @filters.concat([1, 2], [3, 4])) assert_equal [1, 2, 3, 4], @filters.concat([1, 2], [3, 4])
assert_equal([1, 2, 'a'], @filters.concat([1, 2], ['a'])) assert_equal [1, 2, 'a'], @filters.concat([1, 2], ['a'])
assert_equal([1, 2, 10], @filters.concat([1, 2], [10])) assert_equal [1, 2, 10], @filters.concat([1, 2], [10])
assert_raises(Liquid::ArgumentError, "concat filter requires an array argument") do assert_raises(Liquid::ArgumentError, "concat filter requires an array argument") do
@filters.concat([1, 2], 10) @filters.concat([1, 2], 10)
@@ -679,23 +686,12 @@ class StandardFiltersTest < Minitest::Test
end end
def test_default def test_default
assert_equal("foo", @filters.default("foo", "bar")) assert_equal "foo", @filters.default("foo", "bar")
assert_equal("bar", @filters.default(nil, "bar")) assert_equal "bar", @filters.default(nil, "bar")
assert_equal("bar", @filters.default("", "bar")) assert_equal "bar", @filters.default("", "bar")
assert_equal("bar", @filters.default(false, "bar")) assert_equal "bar", @filters.default(false, "bar")
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' }}")
end
def test_default_handle_false
assert_equal("foo", @filters.default("foo", "bar", "allow_false" => true))
assert_equal("bar", @filters.default(nil, "bar", "allow_false" => true))
assert_equal("bar", @filters.default("", "bar", "allow_false" => true))
assert_equal(false, @filters.default(false, "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 }}")
end end
def test_cannot_access_private_methods def test_cannot_access_private_methods
@@ -720,8 +716,8 @@ class StandardFiltersTest < Minitest::Test
{ "handle" => "delta", "ok" => true }, { "handle" => "delta", "ok" => true },
] ]
assert_equal(expectation, @filters.where(input, "ok", true)) assert_equal expectation, @filters.where(input, "ok", true)
assert_equal(expectation, @filters.where(input, "ok")) assert_equal expectation, @filters.where(input, "ok")
end end
def test_where_no_key_set def test_where_no_key_set
@@ -737,13 +733,13 @@ class StandardFiltersTest < Minitest::Test
{ "handle" => "delta", "ok" => true }, { "handle" => "delta", "ok" => true },
] ]
assert_equal(expectation, @filters.where(input, "ok", true)) assert_equal expectation, @filters.where(input, "ok", true)
assert_equal(expectation, @filters.where(input, "ok")) assert_equal expectation, @filters.where(input, "ok")
end end
def test_where_non_array_map_input def test_where_non_array_map_input
assert_equal([{ "a" => "ok" }], @filters.where({ "a" => "ok" }, "a", "ok")) assert_equal [{ "a" => "ok" }], @filters.where({ "a" => "ok" }, "a", "ok")
assert_equal([], @filters.where({ "a" => "not ok" }, "a", "ok")) assert_equal [], @filters.where({ "a" => "not ok" }, "a", "ok")
end end
def test_where_indexable_but_non_map_value def test_where_indexable_but_non_map_value
@@ -758,57 +754,14 @@ class StandardFiltersTest < Minitest::Test
{ "message" => "Hallo!", "language" => "German" }, { "message" => "Hallo!", "language" => "German" },
] ]
assert_equal([{ "message" => "Bonjour!", "language" => "French" }], @filters.where(input, "language", "French")) assert_equal [{ "message" => "Bonjour!", "language" => "French" }], @filters.where(input, "language", "French")
assert_equal([{ "message" => "Hallo!", "language" => "German" }], @filters.where(input, "language", "German")) assert_equal [{ "message" => "Hallo!", "language" => "German" }], @filters.where(input, "language", "German")
assert_equal([{ "message" => "Hello!", "language" => "English" }], @filters.where(input, "language", "English")) assert_equal [{ "message" => "Hello!", "language" => "English" }], @filters.where(input, "language", "English")
end end
def test_where_array_of_only_unindexable_values def test_where_array_of_only_unindexable_values
assert_nil(@filters.where([nil], "ok", true)) assert_nil @filters.where([nil], "ok", true)
assert_nil(@filters.where([nil], "ok")) assert_nil @filters.where([nil], "ok")
end
def test_all_filters_never_raise_non_liquid_exception
test_drop = TestDrop.new
test_drop.context = Context.new
test_enum = TestEnumerable.new
test_enum.context = Context.new
test_types = [
"foo",
123,
0,
0.0,
-1234.003030303,
-99999999,
1234.38383000383830003838300,
nil,
true,
false,
TestThing.new,
test_drop,
test_enum,
["foo", "bar"],
{ "foo" => "bar" },
{ foo: "bar" },
[{ "foo" => "bar" }, { "foo" => 123 }, { "foo" => nil }, { "foo" => true }, { "foo" => ["foo", "bar"] }],
{ 1 => "bar" },
["foo", 123, nil, true, false, Drop, ["foo"], { foo: "bar" }],
]
test_types.each do |first|
test_types.each do |other|
(@filters.methods - Object.methods).each do |method|
arg_count = @filters.method(method).arity
arg_count *= -1 if arg_count < 0
inputs = [first]
inputs << ([other] * (arg_count - 1)) if arg_count > 1
begin
@filters.send(method, *inputs)
rescue Liquid::ArgumentError, Liquid::ZeroDivisionError
nil
end
end
end
end
end end
def test_where_no_target_value def test_where_no_target_value
@@ -819,13 +772,13 @@ class StandardFiltersTest < Minitest::Test
{ "bar" => true }, { "bar" => true },
] ]
assert_equal([{ "foo" => true }, { "foo" => "for sure" }], @filters.where(input, "foo")) assert_equal [{ "foo" => true }, { "foo" => "for sure" }], @filters.where(input, "foo")
end end
private private
def with_timezone(tz) def with_timezone(tz)
old_tz = ENV['TZ'] old_tz = ENV['TZ']
ENV['TZ'] = tz ENV['TZ'] = tz
yield yield
ensure ensure

View File

@@ -8,8 +8,8 @@ class BreakTagTest < Minitest::Test
# tests that no weird errors are raised if break is called outside of a # tests that no weird errors are raised if break is called outside of a
# block # block
def test_break_with_no_block def test_break_with_no_block
assigns = { 'i' => 1 } assigns = { 'i' => 1 }
markup = '{% break %}' markup = '{% break %}'
expected = '' expected = ''
assert_template_result(expected, markup, assigns) assert_template_result(expected, markup, assigns)

View File

@@ -8,8 +8,8 @@ class ContinueTagTest < Minitest::Test
# tests that no weird errors are raised if continue is called outside of a # tests that no weird errors are raised if continue is called outside of a
# block # block
def test_continue_with_no_block def test_continue_with_no_block
assigns = {} assigns = {}
markup = '{% continue %}' markup = '{% continue %}'
expected = '' expected = ''
assert_template_result(expected, markup, assigns) assert_template_result(expected, markup, assigns)

View File

@@ -106,7 +106,7 @@ HERE
end end
def test_limiting_with_invalid_limit def test_limiting_with_invalid_limit
assigns = { 'array' => [1, 2, 3, 4, 5, 6, 7, 8, 9, 0] } assigns = { 'array' => [1, 2, 3, 4, 5, 6, 7, 8, 9, 0] }
template = <<-MKUP template = <<-MKUP
{% for i in array limit: true offset: 1 %} {% for i in array limit: true offset: 1 %}
{{ i }} {{ i }}
@@ -120,7 +120,7 @@ HERE
end end
def test_limiting_with_invalid_offset def test_limiting_with_invalid_offset
assigns = { 'array' => [1, 2, 3, 4, 5, 6, 7, 8, 9, 0] } assigns = { 'array' => [1, 2, 3, 4, 5, 6, 7, 8, 9, 0] }
template = <<-MKUP template = <<-MKUP
{% for i in array limit: 1 offset: true %} {% for i in array limit: 1 offset: true %}
{{ i }} {{ i }}
@@ -134,8 +134,8 @@ HERE
end end
def test_dynamic_variable_limiting def test_dynamic_variable_limiting
assigns = { 'array' => [1, 2, 3, 4, 5, 6, 7, 8, 9, 0] } assigns = { 'array' => [1, 2, 3, 4, 5, 6, 7, 8, 9, 0] }
assigns['limit'] = 2 assigns['limit'] = 2
assigns['offset'] = 2 assigns['offset'] = 2
assert_template_result('34', '{%for i in array limit: limit offset: offset %}{{ i }}{%endfor%}', assigns) assert_template_result('34', '{%for i in array limit: limit offset: offset %}{{ i }}{%endfor%}', assigns)
@@ -152,8 +152,8 @@ HERE
end end
def test_pause_resume def test_pause_resume
assigns = { 'array' => { 'items' => [1, 2, 3, 4, 5, 6, 7, 8, 9, 0] } } assigns = { 'array' => { 'items' => [1, 2, 3, 4, 5, 6, 7, 8, 9, 0] } }
markup = <<-MKUP markup = <<-MKUP
{%for i in array.items limit: 3 %}{{i}}{%endfor%} {%for i in array.items limit: 3 %}{{i}}{%endfor%}
next next
{%for i in array.items offset:continue limit: 3 %}{{i}}{%endfor%} {%for i in array.items offset:continue limit: 3 %}{{i}}{%endfor%}
@@ -171,8 +171,8 @@ HERE
end end
def test_pause_resume_limit def test_pause_resume_limit
assigns = { 'array' => { 'items' => [1, 2, 3, 4, 5, 6, 7, 8, 9, 0] } } assigns = { 'array' => { 'items' => [1, 2, 3, 4, 5, 6, 7, 8, 9, 0] } }
markup = <<-MKUP markup = <<-MKUP
{%for i in array.items limit:3 %}{{i}}{%endfor%} {%for i in array.items limit:3 %}{{i}}{%endfor%}
next next
{%for i in array.items offset:continue limit:3 %}{{i}}{%endfor%} {%for i in array.items offset:continue limit:3 %}{{i}}{%endfor%}
@@ -190,8 +190,8 @@ HERE
end end
def test_pause_resume_big_limit def test_pause_resume_big_limit
assigns = { 'array' => { 'items' => [1, 2, 3, 4, 5, 6, 7, 8, 9, 0] } } assigns = { 'array' => { 'items' => [1, 2, 3, 4, 5, 6, 7, 8, 9, 0] } }
markup = <<-MKUP markup = <<-MKUP
{%for i in array.items limit:3 %}{{i}}{%endfor%} {%for i in array.items limit:3 %}{{i}}{%endfor%}
next next
{%for i in array.items offset:continue limit:3 %}{{i}}{%endfor%} {%for i in array.items offset:continue limit:3 %}{{i}}{%endfor%}
@@ -209,8 +209,8 @@ HERE
end end
def test_pause_resume_big_offset def test_pause_resume_big_offset
assigns = { 'array' => { 'items' => [1, 2, 3, 4, 5, 6, 7, 8, 9, 0] } } assigns = { 'array' => { 'items' => [1, 2, 3, 4, 5, 6, 7, 8, 9, 0] } }
markup = '{%for i in array.items limit:3 %}{{i}}{%endfor%} markup = '{%for i in array.items limit:3 %}{{i}}{%endfor%}
next next
{%for i in array.items offset:continue limit:3 %}{{i}}{%endfor%} {%for i in array.items offset:continue limit:3 %}{{i}}{%endfor%}
next next
@@ -226,26 +226,26 @@ HERE
def test_for_with_break def test_for_with_break
assigns = { 'array' => { 'items' => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] } } assigns = { 'array' => { 'items' => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] } }
markup = '{% for i in array.items %}{% break %}{% endfor %}' markup = '{% for i in array.items %}{% break %}{% endfor %}'
expected = "" expected = ""
assert_template_result(expected, markup, assigns) assert_template_result(expected, markup, assigns)
markup = '{% for i in array.items %}{{ i }}{% break %}{% endfor %}' markup = '{% for i in array.items %}{{ i }}{% break %}{% endfor %}'
expected = "1" expected = "1"
assert_template_result(expected, markup, assigns) assert_template_result(expected, markup, assigns)
markup = '{% for i in array.items %}{% break %}{{ i }}{% endfor %}' markup = '{% for i in array.items %}{% break %}{{ i }}{% endfor %}'
expected = "" expected = ""
assert_template_result(expected, markup, assigns) assert_template_result(expected, markup, assigns)
markup = '{% for i in array.items %}{{ i }}{% if i > 3 %}{% break %}{% endif %}{% endfor %}' markup = '{% for i in array.items %}{{ i }}{% if i > 3 %}{% break %}{% endif %}{% endfor %}'
expected = "1234" expected = "1234"
assert_template_result(expected, markup, assigns) assert_template_result(expected, markup, assigns)
# tests to ensure it only breaks out of the local for loop # tests to ensure it only breaks out of the local for loop
# and not all of them. # and not all of them.
assigns = { 'array' => [[1, 2], [3, 4], [5, 6]] } assigns = { 'array' => [[1, 2], [3, 4], [5, 6]] }
markup = '{% for item in array %}' \ markup = '{% for item in array %}' \
'{% for i in item %}' \ '{% for i in item %}' \
'{% if i == 1 %}' \ '{% if i == 1 %}' \
'{% break %}' \ '{% break %}' \
@@ -257,8 +257,8 @@ HERE
assert_template_result(expected, markup, assigns) assert_template_result(expected, markup, assigns)
# test break does nothing when unreached # test break does nothing when unreached
assigns = { 'array' => { 'items' => [1, 2, 3, 4, 5] } } assigns = { 'array' => { 'items' => [1, 2, 3, 4, 5] } }
markup = '{% for i in array.items %}{% if i == 9999 %}{% break %}{% endif %}{{ i }}{% endfor %}' markup = '{% for i in array.items %}{% if i == 9999 %}{% break %}{% endif %}{{ i }}{% endfor %}'
expected = '12345' expected = '12345'
assert_template_result(expected, markup, assigns) assert_template_result(expected, markup, assigns)
end end
@@ -266,29 +266,29 @@ HERE
def test_for_with_continue def test_for_with_continue
assigns = { 'array' => { 'items' => [1, 2, 3, 4, 5] } } assigns = { 'array' => { 'items' => [1, 2, 3, 4, 5] } }
markup = '{% for i in array.items %}{% continue %}{% endfor %}' markup = '{% for i in array.items %}{% continue %}{% endfor %}'
expected = "" expected = ""
assert_template_result(expected, markup, assigns) assert_template_result(expected, markup, assigns)
markup = '{% for i in array.items %}{{ i }}{% continue %}{% endfor %}' markup = '{% for i in array.items %}{{ i }}{% continue %}{% endfor %}'
expected = "12345" expected = "12345"
assert_template_result(expected, markup, assigns) assert_template_result(expected, markup, assigns)
markup = '{% for i in array.items %}{% continue %}{{ i }}{% endfor %}' markup = '{% for i in array.items %}{% continue %}{{ i }}{% endfor %}'
expected = "" expected = ""
assert_template_result(expected, markup, assigns) assert_template_result(expected, markup, assigns)
markup = '{% for i in array.items %}{% if i > 3 %}{% continue %}{% endif %}{{ i }}{% endfor %}' markup = '{% for i in array.items %}{% if i > 3 %}{% continue %}{% endif %}{{ i }}{% endfor %}'
expected = "123" expected = "123"
assert_template_result(expected, markup, assigns) assert_template_result(expected, markup, assigns)
markup = '{% for i in array.items %}{% if i == 3 %}{% continue %}{% else %}{{ i }}{% endif %}{% endfor %}' markup = '{% for i in array.items %}{% if i == 3 %}{% continue %}{% else %}{{ i }}{% endif %}{% endfor %}'
expected = "1245" expected = "1245"
assert_template_result(expected, markup, assigns) assert_template_result(expected, markup, assigns)
# tests to ensure it only continues the local for loop and not all of them. # tests to ensure it only continues the local for loop and not all of them.
assigns = { 'array' => [[1, 2], [3, 4], [5, 6]] } assigns = { 'array' => [[1, 2], [3, 4], [5, 6]] }
markup = '{% for item in array %}' \ markup = '{% for item in array %}' \
'{% for i in item %}' \ '{% for i in item %}' \
'{% if i == 1 %}' \ '{% if i == 1 %}' \
'{% continue %}' \ '{% continue %}' \
@@ -300,8 +300,8 @@ HERE
assert_template_result(expected, markup, assigns) assert_template_result(expected, markup, assigns)
# test continue does nothing when unreached # test continue does nothing when unreached
assigns = { 'array' => { 'items' => [1, 2, 3, 4, 5] } } assigns = { 'array' => { 'items' => [1, 2, 3, 4, 5] } }
markup = '{% for i in array.items %}{% if i == 9999 %}{% continue %}{% endif %}{{ i }}{% endfor %}' markup = '{% for i in array.items %}{% if i == 9999 %}{% continue %}{% endif %}{{ i }}{% endfor %}'
expected = '12345' expected = '12345'
assert_template_result(expected, markup, assigns) assert_template_result(expected, markup, assigns)
end end
@@ -350,7 +350,7 @@ HERE
end end
def test_inner_for_over_empty_input def test_inner_for_over_empty_input
assert_template_result('oo', '{% for a in (1..2) %}o{% for b in empty %}{% endfor %}{% endfor %}') assert_template_result 'oo', '{% for a in (1..2) %}o{% for b in empty %}{% endfor %}{% endfor %}'
end end
def test_blank_string_not_iterable def test_blank_string_not_iterable
@@ -389,41 +389,41 @@ HERE
end end
def test_iterate_with_each_when_no_limit_applied def test_iterate_with_each_when_no_limit_applied
loader = LoaderDrop.new([1, 2, 3, 4, 5]) loader = LoaderDrop.new([1, 2, 3, 4, 5])
assigns = { 'items' => loader } assigns = { 'items' => loader }
expected = '12345' expected = '12345'
template = '{% for item in items %}{{item}}{% endfor %}' template = '{% for item in items %}{{item}}{% endfor %}'
assert_template_result(expected, template, assigns) assert_template_result(expected, template, assigns)
assert(loader.each_called) assert loader.each_called
assert(!loader.load_slice_called) assert !loader.load_slice_called
end end
def test_iterate_with_load_slice_when_limit_applied def test_iterate_with_load_slice_when_limit_applied
loader = LoaderDrop.new([1, 2, 3, 4, 5]) loader = LoaderDrop.new([1, 2, 3, 4, 5])
assigns = { 'items' => loader } assigns = { 'items' => loader }
expected = '1' expected = '1'
template = '{% for item in items limit:1 %}{{item}}{% endfor %}' template = '{% for item in items limit:1 %}{{item}}{% endfor %}'
assert_template_result(expected, template, assigns) assert_template_result(expected, template, assigns)
assert(!loader.each_called) assert !loader.each_called
assert(loader.load_slice_called) assert loader.load_slice_called
end end
def test_iterate_with_load_slice_when_limit_and_offset_applied def test_iterate_with_load_slice_when_limit_and_offset_applied
loader = LoaderDrop.new([1, 2, 3, 4, 5]) loader = LoaderDrop.new([1, 2, 3, 4, 5])
assigns = { 'items' => loader } assigns = { 'items' => loader }
expected = '34' expected = '34'
template = '{% for item in items offset:2 limit:2 %}{{item}}{% endfor %}' template = '{% for item in items offset:2 limit:2 %}{{item}}{% endfor %}'
assert_template_result(expected, template, assigns) assert_template_result(expected, template, assigns)
assert(!loader.each_called) assert !loader.each_called
assert(loader.load_slice_called) assert loader.load_slice_called
end end
def test_iterate_with_load_slice_returns_same_results_as_without def test_iterate_with_load_slice_returns_same_results_as_without
loader = LoaderDrop.new([1, 2, 3, 4, 5]) loader = LoaderDrop.new([1, 2, 3, 4, 5])
loader_assigns = { 'items' => loader } loader_assigns = { 'items' => loader }
array_assigns = { 'items' => [1, 2, 3, 4, 5] } array_assigns = { 'items' => [1, 2, 3, 4, 5] }
expected = '34' expected = '34'
template = '{% for item in items offset:2 limit:2 %}{{item}}{% endfor %}' template = '{% for item in items offset:2 limit:2 %}{{item}}{% endfor %}'
assert_template_result(expected, template, loader_assigns) assert_template_result(expected, template, loader_assigns)
assert_template_result(expected, template, array_assigns) assert_template_result(expected, template, array_assigns)
end end
@@ -435,6 +435,6 @@ HERE
Liquid::Template.parse('{% for i in (1..2) %}{{ standard_error }}{% endfor %}').render!(context) Liquid::Template.parse('{% for i in (1..2) %}{{ standard_error }}{% endfor %}').render!(context)
end end
assert(context.registers[:for_stack].empty?) assert context.registers[:for_stack].empty?
end end
end end

View File

@@ -45,7 +45,7 @@ class IfElseTagTest < Minitest::Test
def test_comparison_of_strings_containing_and_or_or def test_comparison_of_strings_containing_and_or_or
awful_markup = "a == 'and' and b == 'or' and c == 'foo and bar' and d == 'bar or baz' and e == 'foo' and foo and bar" awful_markup = "a == 'and' and b == 'or' and c == 'foo and bar' and d == 'bar or baz' and e == 'foo' and foo and bar"
assigns = { 'a' => 'and', 'b' => 'or', 'c' => 'foo and bar', 'd' => 'bar or baz', 'e' => 'foo', 'foo' => true, 'bar' => true } assigns = { 'a' => 'and', 'b' => 'or', 'c' => 'foo and bar', 'd' => 'bar or baz', 'e' => 'foo', 'foo' => true, 'bar' => true }
assert_template_result(' YES ', "{% if #{awful_markup} %} YES {% endif %}", assigns) assert_template_result(' YES ', "{% if #{awful_markup} %} YES {% endif %}", assigns)
end end

View File

@@ -8,9 +8,6 @@ class TestFileSystem
when "product" when "product"
"Product: {{ product.title }} " "Product: {{ product.title }} "
when "product_alias"
"Product: {{ product.title }} "
when "locale_variables" when "locale_variables"
"Locale: {{echo1}} {{echo2}}" "Locale: {{echo1}} {{echo2}}"
@@ -54,7 +51,7 @@ class CountingFileSystem
attr_reader :count attr_reader :count
def read_template_file(_template_path) def read_template_file(_template_path)
@count ||= 0 @count ||= 0
@count += 1 @count += 1
'from CountingFileSystem' 'from CountingFileSystem'
end end
end end
@@ -85,66 +82,56 @@ class IncludeTagTest < Minitest::Test
end end
def test_include_tag_looks_for_file_system_in_registers_first def test_include_tag_looks_for_file_system_in_registers_first
assert_equal('from OtherFileSystem', assert_equal 'from OtherFileSystem',
Template.parse("{% include 'pick_a_source' %}").render!({}, registers: { file_system: OtherFileSystem.new })) Template.parse("{% include 'pick_a_source' %}").render!({}, registers: { file_system: OtherFileSystem.new })
end end
def test_include_tag_with def test_include_tag_with
assert_template_result("Product: Draft 151cm ", assert_template_result "Product: Draft 151cm ",
"{% include 'product' with products[0] %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }]) "{% include 'product' with products[0] %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }]
end
def test_include_tag_with_alias
assert_template_result("Product: Draft 151cm ",
"{% include 'product_alias' with products[0] as product %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }])
end
def test_include_tag_for_alias
assert_template_result("Product: Draft 151cm Product: Element 155cm ",
"{% include 'product_alias' for products as product %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }])
end end
def test_include_tag_with_default_name def test_include_tag_with_default_name
assert_template_result("Product: Draft 151cm ", assert_template_result "Product: Draft 151cm ",
"{% include 'product' %}", "product" => { 'title' => 'Draft 151cm' }) "{% include 'product' %}", "product" => { 'title' => 'Draft 151cm' }
end end
def test_include_tag_for def test_include_tag_for
assert_template_result("Product: Draft 151cm Product: Element 155cm ", assert_template_result "Product: Draft 151cm Product: Element 155cm ",
"{% include 'product' for products %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }]) "{% include 'product' for products %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }]
end end
def test_include_tag_with_local_variables def test_include_tag_with_local_variables
assert_template_result("Locale: test123 ", "{% include 'locale_variables' echo1: 'test123' %}") assert_template_result "Locale: test123 ", "{% include 'locale_variables' echo1: 'test123' %}"
end end
def test_include_tag_with_multiple_local_variables def test_include_tag_with_multiple_local_variables
assert_template_result("Locale: test123 test321", assert_template_result "Locale: test123 test321",
"{% include 'locale_variables' echo1: 'test123', echo2: 'test321' %}") "{% include 'locale_variables' echo1: 'test123', echo2: 'test321' %}"
end end
def test_include_tag_with_multiple_local_variables_from_context def test_include_tag_with_multiple_local_variables_from_context
assert_template_result("Locale: test123 test321", assert_template_result "Locale: test123 test321",
"{% include 'locale_variables' echo1: echo1, echo2: more_echos.echo2 %}", "{% include 'locale_variables' echo1: echo1, echo2: more_echos.echo2 %}",
'echo1' => 'test123', 'more_echos' => { "echo2" => 'test321' }) 'echo1' => 'test123', 'more_echos' => { "echo2" => 'test321' }
end end
def test_included_templates_assigns_variables def test_included_templates_assigns_variables
assert_template_result("bar", "{% include 'assignments' %}{{ foo }}") assert_template_result "bar", "{% include 'assignments' %}{{ foo }}"
end end
def test_nested_include_tag def test_nested_include_tag
assert_template_result("body body_detail", "{% include 'body' %}") assert_template_result "body body_detail", "{% include 'body' %}"
assert_template_result("header body body_detail footer", "{% include 'nested_template' %}") assert_template_result "header body body_detail footer", "{% include 'nested_template' %}"
end end
def test_nested_include_with_variable def test_nested_include_with_variable
assert_template_result("Product: Draft 151cm details ", assert_template_result "Product: Draft 151cm details ",
"{% include 'nested_product_template' with product %}", "product" => { "title" => 'Draft 151cm' }) "{% include 'nested_product_template' with product %}", "product" => { "title" => 'Draft 151cm' }
assert_template_result("Product: Draft 151cm details Product: Element 155cm details ", assert_template_result "Product: Draft 151cm details Product: Element 155cm details ",
"{% include 'nested_product_template' for products %}", "products" => [{ "title" => 'Draft 151cm' }, { "title" => 'Element 155cm' }]) "{% include 'nested_product_template' for products %}", "products" => [{ "title" => 'Draft 151cm' }, { "title" => 'Element 155cm' }]
end end
def test_recursively_included_template_does_not_produce_endless_loop def test_recursively_included_template_does_not_produce_endless_loop
@@ -162,41 +149,41 @@ class IncludeTagTest < Minitest::Test
end end
def test_dynamically_choosen_template def test_dynamically_choosen_template
assert_template_result("Test123", "{% include template %}", "template" => 'Test123') assert_template_result "Test123", "{% include template %}", "template" => 'Test123'
assert_template_result("Test321", "{% include template %}", "template" => 'Test321') assert_template_result "Test321", "{% include template %}", "template" => 'Test321'
assert_template_result("Product: Draft 151cm ", "{% include template for product %}", assert_template_result "Product: Draft 151cm ", "{% include template for product %}",
"template" => 'product', 'product' => { 'title' => 'Draft 151cm' }) "template" => 'product', 'product' => { 'title' => 'Draft 151cm' }
end end
def test_include_tag_caches_second_read_of_same_partial def test_include_tag_caches_second_read_of_same_partial
file_system = CountingFileSystem.new file_system = CountingFileSystem.new
assert_equal('from CountingFileSystemfrom CountingFileSystem', assert_equal 'from CountingFileSystemfrom CountingFileSystem',
Template.parse("{% include 'pick_a_source' %}{% include 'pick_a_source' %}").render!({}, registers: { file_system: file_system })) Template.parse("{% include 'pick_a_source' %}{% include 'pick_a_source' %}").render!({}, registers: { file_system: file_system })
assert_equal(1, file_system.count) assert_equal 1, file_system.count
end end
def test_include_tag_doesnt_cache_partials_across_renders def test_include_tag_doesnt_cache_partials_across_renders
file_system = CountingFileSystem.new file_system = CountingFileSystem.new
assert_equal('from CountingFileSystem', assert_equal 'from CountingFileSystem',
Template.parse("{% include 'pick_a_source' %}").render!({}, registers: { file_system: file_system })) Template.parse("{% include 'pick_a_source' %}").render!({}, registers: { file_system: file_system })
assert_equal(1, file_system.count) assert_equal 1, file_system.count
assert_equal('from CountingFileSystem', assert_equal 'from CountingFileSystem',
Template.parse("{% include 'pick_a_source' %}").render!({}, registers: { file_system: file_system })) Template.parse("{% include 'pick_a_source' %}").render!({}, registers: { file_system: file_system })
assert_equal(2, file_system.count) assert_equal 2, file_system.count
end end
def test_include_tag_within_if_statement def test_include_tag_within_if_statement
assert_template_result("foo_if_true", "{% if true %}{% include 'foo_if_true' %}{% endif %}") assert_template_result "foo_if_true", "{% if true %}{% include 'foo_if_true' %}{% endif %}"
end end
def test_custom_include_tag def test_custom_include_tag
original_tag = Liquid::Template.tags['include'] original_tag = Liquid::Template.tags['include']
Liquid::Template.tags['include'] = CustomInclude Liquid::Template.tags['include'] = CustomInclude
begin begin
assert_equal("custom_foo", assert_equal "custom_foo",
Template.parse("{% include 'custom_foo' %}").render!) Template.parse("{% include 'custom_foo' %}").render!
ensure ensure
Liquid::Template.tags['include'] = original_tag Liquid::Template.tags['include'] = original_tag
end end
@@ -206,8 +193,8 @@ class IncludeTagTest < Minitest::Test
original_tag = Liquid::Template.tags['include'] original_tag = Liquid::Template.tags['include']
Liquid::Template.tags['include'] = CustomInclude Liquid::Template.tags['include'] = CustomInclude
begin begin
assert_equal("custom_foo_if_true", assert_equal "custom_foo_if_true",
Template.parse("{% if true %}{% include 'custom_foo_if_true' %}{% endif %}").render!) Template.parse("{% if true %}{% include 'custom_foo_if_true' %}{% endif %}").render!
ensure ensure
Liquid::Template.tags['include'] = original_tag Liquid::Template.tags['include'] = original_tag
end end
@@ -217,9 +204,8 @@ class IncludeTagTest < Minitest::Test
Liquid::Template.file_system = TestFileSystem.new Liquid::Template.file_system = TestFileSystem.new
a = Liquid::Template.parse(' {% include "nested_template" %}') a = Liquid::Template.parse(' {% include "nested_template" %}')
context = Liquid::Context.new a.render!
a.render!(context) assert_empty a.errors
assert_empty(context.errors)
end end
def test_passing_options_to_included_templates def test_passing_options_to_included_templates
@@ -249,23 +235,22 @@ class IncludeTagTest < Minitest::Test
end end
def test_including_via_variable_value def test_including_via_variable_value
assert_template_result("from TestFileSystem", "{% assign page = 'pick_a_source' %}{% include page %}") assert_template_result "from TestFileSystem", "{% assign page = 'pick_a_source' %}{% include page %}"
assert_template_result("Product: Draft 151cm ", "{% assign page = 'product' %}{% include page %}", "product" => { 'title' => 'Draft 151cm' }) assert_template_result "Product: Draft 151cm ", "{% assign page = 'product' %}{% include page %}", "product" => { 'title' => 'Draft 151cm' }
assert_template_result("Product: Draft 151cm ", "{% assign page = 'product' %}{% include page for foo %}", "foo" => { 'title' => 'Draft 151cm' }) assert_template_result "Product: Draft 151cm ", "{% assign page = 'product' %}{% include page for foo %}", "foo" => { 'title' => 'Draft 151cm' }
end end
def test_including_with_strict_variables def test_including_with_strict_variables
template = Liquid::Template.parse("{% include 'simple' %}", error_mode: :warn) template = Liquid::Template.parse("{% include 'simple' %}", error_mode: :warn)
context = Liquid::Context.new template.render(nil, strict_variables: true)
template.render(context, strict_variables: true)
assert_equal([], context.errors) assert_equal [], template.errors
end end
def test_break_through_include def test_break_through_include
assert_template_result("1", "{% for i in (1..3) %}{{ i }}{% break %}{{ i }}{% endfor %}") assert_template_result "1", "{% for i in (1..3) %}{{ i }}{% break %}{{ i }}{% endfor %}"
assert_template_result("1", "{% for i in (1..3) %}{{ i }}{% include 'break' %}{{ i }}{% endfor %}") assert_template_result "1", "{% for i in (1..3) %}{{ i }}{% include 'break' %}{{ i }}{% endfor %}"
end end
end # IncludeTagTest end # IncludeTagTest

View File

@@ -81,18 +81,6 @@ class LiquidTagTest < Minitest::Test
assert_match_syntax_error("syntax error (line 3): Unknown tag 'error'", "{% liquid echo ''\n \n error %}") assert_match_syntax_error("syntax error (line 3): Unknown tag 'error'", "{% liquid echo ''\n \n error %}")
end end
def test_nested_liquid_tag
assert_usage_increment("liquid_tag_contains_outer_tag", times: 0) do
assert_template_result('good', <<~LIQUID)
{%- if true %}
{%- liquid
echo "good"
%}
{%- endif -%}
LIQUID
end
end
def test_cannot_open_blocks_living_past_a_liquid_tag def test_cannot_open_blocks_living_past_a_liquid_tag
assert_match_syntax_error("syntax error (line 3): 'if' tag was never closed", <<~LIQUID) assert_match_syntax_error("syntax error (line 3): 'if' tag was never closed", <<~LIQUID)
{%- liquid {%- liquid
@@ -103,13 +91,11 @@ class LiquidTagTest < Minitest::Test
end end
def test_quirk_can_close_blocks_created_before_a_liquid_tag def test_quirk_can_close_blocks_created_before_a_liquid_tag
assert_usage_increment("liquid_tag_contains_outer_tag") do assert_template_result("42", <<~LIQUID)
assert_template_result("42", <<~LIQUID) {%- if true -%}
{%- if true -%} 42
42 {%- liquid endif -%}
{%- liquid endif -%} LIQUID
LIQUID
end
end end
def test_liquid_tag_in_raw def test_liquid_tag_in_raw

View File

@@ -6,23 +6,23 @@ class RawTagTest < Minitest::Test
include Liquid include Liquid
def test_tag_in_raw def test_tag_in_raw
assert_template_result('{% comment %} test {% endcomment %}', assert_template_result '{% comment %} test {% endcomment %}',
'{% raw %}{% comment %} test {% endcomment %}{% endraw %}') '{% raw %}{% comment %} test {% endcomment %}{% endraw %}'
end end
def test_output_in_raw def test_output_in_raw
assert_template_result('{{ test }}', '{% raw %}{{ test }}{% endraw %}') assert_template_result '{{ test }}', '{% raw %}{{ test }}{% endraw %}'
end end
def test_open_tag_in_raw def test_open_tag_in_raw
assert_template_result(' Foobar {% invalid ', '{% raw %} Foobar {% invalid {% endraw %}') assert_template_result ' Foobar {% invalid ', '{% raw %} Foobar {% invalid {% endraw %}'
assert_template_result(' Foobar invalid %} ', '{% raw %} Foobar invalid %} {% endraw %}') assert_template_result ' Foobar invalid %} ', '{% raw %} Foobar invalid %} {% endraw %}'
assert_template_result(' Foobar {{ invalid ', '{% raw %} Foobar {{ invalid {% endraw %}') assert_template_result ' Foobar {{ invalid ', '{% raw %} Foobar {{ invalid {% endraw %}'
assert_template_result(' Foobar invalid }} ', '{% raw %} Foobar invalid }} {% endraw %}') assert_template_result ' Foobar invalid }} ', '{% raw %} Foobar invalid }} {% endraw %}'
assert_template_result(' Foobar {% invalid {% {% endraw ', '{% raw %} Foobar {% invalid {% {% endraw {% endraw %}') assert_template_result ' Foobar {% invalid {% {% endraw ', '{% raw %} Foobar {% invalid {% {% endraw {% endraw %}'
assert_template_result(' Foobar {% {% {% ', '{% raw %} Foobar {% {% {% {% endraw %}') assert_template_result ' Foobar {% {% {% ', '{% raw %} Foobar {% {% {% {% endraw %}'
assert_template_result(' test {% raw %} {% endraw %}', '{% raw %} test {% raw %} {% {% endraw %}endraw %}') assert_template_result ' test {% raw %} {% endraw %}', '{% raw %} test {% raw %} {% {% endraw %}endraw %}'
assert_template_result(' Foobar {{ invalid 1', '{% raw %} Foobar {{ invalid {% endraw %}{{ 1 }}') assert_template_result ' Foobar {{ invalid 1', '{% raw %} Foobar {{ invalid {% endraw %}{{ 1 }}'
end end
def test_invalid_raw def test_invalid_raw

View File

@@ -7,44 +7,70 @@ class RenderTagTest < Minitest::Test
def test_render_with_no_arguments def test_render_with_no_arguments
Liquid::Template.file_system = StubFileSystem.new('source' => 'rendered content') Liquid::Template.file_system = StubFileSystem.new('source' => 'rendered content')
assert_template_result('rendered content', '{% render "source" %}') assert_template_result 'rendered content', '{% render "source" %}'
end end
def test_render_tag_looks_for_file_system_in_registers_first def test_render_tag_looks_for_file_system_in_registers_first
file_system = StubFileSystem.new('pick_a_source' => 'from register file system') file_system = StubFileSystem.new('pick_a_source' => 'from register file system')
assert_equal('from register file system', assert_equal 'from register file system',
Template.parse('{% render "pick_a_source" %}').render!({}, registers: { file_system: file_system })) Template.parse('{% render "pick_a_source" %}').render!({}, registers: { file_system: file_system })
end end
def test_render_passes_named_arguments_into_inner_scope def test_render_passes_named_arguments_into_inner_scope
Liquid::Template.file_system = StubFileSystem.new('product' => '{{ inner_product.title }}') Liquid::Template.file_system = StubFileSystem.new('product' => '{{ inner_product.title }}')
assert_template_result('My Product', '{% render "product", inner_product: outer_product %}', assert_template_result 'My Product', '{% render "product", inner_product: outer_product %}',
'outer_product' => { 'title' => 'My Product' }) 'outer_product' => { 'title' => 'My Product' }
end end
def test_render_accepts_literals_as_arguments def test_render_accepts_literals_as_arguments
Liquid::Template.file_system = StubFileSystem.new('snippet' => '{{ price }}') Liquid::Template.file_system = StubFileSystem.new('snippet' => '{{ price }}')
assert_template_result('123', '{% render "snippet", price: 123 %}') assert_template_result '123', '{% render "snippet", price: 123 %}'
end end
def test_render_accepts_multiple_named_arguments def test_render_accepts_multiple_named_arguments
Liquid::Template.file_system = StubFileSystem.new('snippet' => '{{ one }} {{ two }}') Liquid::Template.file_system = StubFileSystem.new('snippet' => '{{ one }} {{ two }}')
assert_template_result('1 2', '{% render "snippet", one: 1, two: 2 %}') assert_template_result '1 2', '{% render "snippet", one: 1, two: 2 %}'
end end
def test_render_does_not_inherit_parent_scope_variables def test_render_does_not_inherit_parent_scope_variables
Liquid::Template.file_system = StubFileSystem.new('snippet' => '{{ outer_variable }}') Liquid::Template.file_system = StubFileSystem.new('snippet' => '{{ outer_variable }}')
assert_template_result('', '{% assign outer_variable = "should not be visible" %}{% render "snippet" %}') assert_template_result '', '{% assign outer_variable = "should not be visible" %}{% render "snippet" %}'
end end
def test_render_does_not_inherit_variable_with_same_name_as_snippet def test_render_does_not_inherit_variable_with_same_name_as_snippet
Liquid::Template.file_system = StubFileSystem.new('snippet' => '{{ snippet }}') Liquid::Template.file_system = StubFileSystem.new('snippet' => '{{ snippet }}')
assert_template_result('', "{% assign snippet = 'should not be visible' %}{% render 'snippet' %}") assert_template_result '', "{% assign snippet = 'should not be visible' %}{% render 'snippet' %}"
end
def test_render_sets_the_correct_template_name_for_errors
Liquid::Template.file_system = StubFileSystem.new('snippet' => '{{ unsafe }}')
with_taint_mode :error do
template = Liquid::Template.parse('{% render "snippet", unsafe: unsafe %}')
context = Context.new('unsafe' => (+'unsafe').tap(&:taint))
template.render(context)
assert_equal [Liquid::TaintedError], template.errors.map(&:class)
assert_equal 'snippet', template.errors.first.template_name
end
end
def test_render_sets_the_correct_template_name_for_warnings
Liquid::Template.file_system = StubFileSystem.new('snippet' => '{{ unsafe }}')
with_taint_mode :warn do
template = Liquid::Template.parse('{% render "snippet", unsafe: unsafe %}')
context = Context.new('unsafe' => (+'unsafe').tap(&:taint))
template.render(context)
assert_equal [Liquid::TaintedError], context.warnings.map(&:class)
assert_equal 'snippet', context.warnings.first.template_name
end
end end
def test_render_does_not_mutate_parent_scope def test_render_does_not_mutate_parent_scope
Liquid::Template.file_system = StubFileSystem.new('snippet' => '{% assign inner = 1 %}') Liquid::Template.file_system = StubFileSystem.new('snippet' => '{% assign inner = 1 %}')
assert_template_result('', "{% render 'snippet' %}{{ inner }}") assert_template_result '', "{% render 'snippet' %}{{ inner }}"
end end
def test_nested_render_tag def test_nested_render_tag
@@ -52,7 +78,7 @@ class RenderTagTest < Minitest::Test
'one' => "one {% render 'two' %}", 'one' => "one {% render 'two' %}",
'two' => 'two' 'two' => 'two'
) )
assert_template_result('one two', "{% render 'one' %}") assert_template_result 'one two', "{% render 'one' %}"
end end
def test_recursively_rendered_template_does_not_produce_endless_loop def test_recursively_rendered_template_does_not_produce_endless_loop
@@ -82,43 +108,43 @@ class RenderTagTest < Minitest::Test
def test_include_tag_caches_second_read_of_same_partial def test_include_tag_caches_second_read_of_same_partial
file_system = StubFileSystem.new('snippet' => 'echo') file_system = StubFileSystem.new('snippet' => 'echo')
assert_equal('echoecho', assert_equal 'echoecho',
Template.parse('{% render "snippet" %}{% render "snippet" %}') Template.parse('{% render "snippet" %}{% render "snippet" %}')
.render!({}, registers: { file_system: file_system })) .render!({}, registers: { file_system: file_system })
assert_equal(1, file_system.file_read_count) assert_equal 1, file_system.file_read_count
end end
def test_render_tag_doesnt_cache_partials_across_renders def test_render_tag_doesnt_cache_partials_across_renders
file_system = StubFileSystem.new('snippet' => 'my message') file_system = StubFileSystem.new('snippet' => 'my message')
assert_equal('my message', assert_equal 'my message',
Template.parse('{% include "snippet" %}').render!({}, registers: { file_system: file_system })) Template.parse('{% include "snippet" %}').render!({}, registers: { file_system: file_system })
assert_equal(1, file_system.file_read_count) assert_equal 1, file_system.file_read_count
assert_equal('my message', assert_equal 'my message',
Template.parse('{% include "snippet" %}').render!({}, registers: { file_system: file_system })) Template.parse('{% include "snippet" %}').render!({}, registers: { file_system: file_system })
assert_equal(2, file_system.file_read_count) assert_equal 2, file_system.file_read_count
end end
def test_render_tag_within_if_statement def test_render_tag_within_if_statement
Liquid::Template.file_system = StubFileSystem.new('snippet' => 'my message') Liquid::Template.file_system = StubFileSystem.new('snippet' => 'my message')
assert_template_result('my message', '{% if true %}{% render "snippet" %}{% endif %}') assert_template_result 'my message', '{% if true %}{% render "snippet" %}{% endif %}'
end end
def test_break_through_render def test_break_through_render
Liquid::Template.file_system = StubFileSystem.new('break' => '{% break %}') Liquid::Template.file_system = StubFileSystem.new('break' => '{% break %}')
assert_template_result('1', '{% for i in (1..3) %}{{ i }}{% break %}{{ i }}{% endfor %}') assert_template_result '1', '{% for i in (1..3) %}{{ i }}{% break %}{{ i }}{% endfor %}'
assert_template_result('112233', '{% for i in (1..3) %}{{ i }}{% render "break" %}{{ i }}{% endfor %}') assert_template_result '112233', '{% for i in (1..3) %}{{ i }}{% render "break" %}{{ i }}{% endfor %}'
end end
def test_increment_is_isolated_between_renders def test_increment_is_isolated_between_renders
Liquid::Template.file_system = StubFileSystem.new('incr' => '{% increment %}') Liquid::Template.file_system = StubFileSystem.new('incr' => '{% increment %}')
assert_template_result('010', '{% increment %}{% increment %}{% render "incr" %}') assert_template_result '010', '{% increment %}{% increment %}{% render "incr" %}'
end end
def test_decrement_is_isolated_between_renders def test_decrement_is_isolated_between_renders
Liquid::Template.file_system = StubFileSystem.new('decr' => '{% decrement %}') Liquid::Template.file_system = StubFileSystem.new('decr' => '{% decrement %}')
assert_template_result('-1-2-1', '{% decrement %}{% decrement %}{% render "decr" %}') assert_template_result '-1-2-1', '{% decrement %}{% decrement %}{% render "decr" %}'
end end
def test_includes_will_not_render_inside_render_tag def test_includes_will_not_render_inside_render_tag
@@ -127,7 +153,7 @@ class RenderTagTest < Minitest::Test
'test_include' => '{% include "foo" %}' 'test_include' => '{% include "foo" %}'
) )
assert_template_result('include usage is not allowed in this context', '{% render "test_include" %}') assert_template_result 'include usage is not allowed in this context', '{% render "test_include" %}'
end end
def test_includes_will_not_render_inside_nested_sibling_tags def test_includes_will_not_render_inside_nested_sibling_tags
@@ -137,73 +163,6 @@ class RenderTagTest < Minitest::Test
'test_include' => '{% include "foo" %}' 'test_include' => '{% include "foo" %}'
) )
assert_template_result('include usage is not allowed in this contextinclude usage is not allowed in this context', '{% render "nested_render_with_sibling_include" %}') assert_template_result 'include usage is not allowed in this contextinclude usage is not allowed in this context', '{% render "nested_render_with_sibling_include" %}'
end
def test_render_tag_with
Liquid::Template.file_system = StubFileSystem.new(
'product' => "Product: {{ product.title }} ",
'product_alias' => "Product: {{ product.title }} ",
)
assert_template_result("Product: Draft 151cm ",
"{% render 'product' with products[0] %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }])
end
def test_render_tag_with_alias
Liquid::Template.file_system = StubFileSystem.new(
'product' => "Product: {{ product.title }} ",
'product_alias' => "Product: {{ product.title }} ",
)
assert_template_result("Product: Draft 151cm ",
"{% render 'product_alias' with products[0] as product %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }])
end
def test_render_tag_for_alias
Liquid::Template.file_system = StubFileSystem.new(
'product' => "Product: {{ product.title }} ",
'product_alias' => "Product: {{ product.title }} ",
)
assert_template_result("Product: Draft 151cm Product: Element 155cm ",
"{% render 'product_alias' for products as product %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }])
end
def test_render_tag_for
Liquid::Template.file_system = StubFileSystem.new(
'product' => "Product: {{ product.title }} ",
'product_alias' => "Product: {{ product.title }} ",
)
assert_template_result("Product: Draft 151cm Product: Element 155cm ",
"{% render 'product' for products %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }])
end
def test_render_tag_forloop
Liquid::Template.file_system = StubFileSystem.new(
'product' => "Product: {{ product.title }} {% if forloop.first %}first{% endif %} {% if forloop.last %}last{% endif %} index:{{ forloop.index }} ",
)
assert_template_result("Product: Draft 151cm first index:1 Product: Element 155cm last index:2 ",
"{% render 'product' for products %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }])
end
def test_render_tag_for_drop
Liquid::Template.file_system = StubFileSystem.new(
'loop' => "{{ value.foo }}",
)
assert_template_result("123",
"{% render 'loop' for loop as value %}", "loop" => TestEnumerable.new)
end
def test_render_tag_with_drop
Liquid::Template.file_system = StubFileSystem.new(
'loop' => "{{ value }}",
)
assert_template_result("TestEnumerable",
"{% render 'loop' with loop as value %}", "loop" => TestEnumerable.new)
end end
end end

View File

@@ -174,13 +174,13 @@ class StandardTagTest < Minitest::Test
def test_assign_from_case def test_assign_from_case
# Example from the shopify forums # Example from the shopify forums
code = "{% case collection.handle %}{% when 'menswear-jackets' %}{% assign ptitle = 'menswear' %}{% when 'menswear-t-shirts' %}{% assign ptitle = 'menswear' %}{% else %}{% assign ptitle = 'womenswear' %}{% endcase %}{{ ptitle }}" code = "{% case collection.handle %}{% when 'menswear-jackets' %}{% assign ptitle = 'menswear' %}{% when 'menswear-t-shirts' %}{% assign ptitle = 'menswear' %}{% else %}{% assign ptitle = 'womenswear' %}{% endcase %}{{ ptitle }}"
template = Liquid::Template.parse(code) template = Liquid::Template.parse(code)
assert_equal("menswear", template.render!("collection" => { 'handle' => 'menswear-jackets' })) assert_equal "menswear", template.render!("collection" => { 'handle' => 'menswear-jackets' })
assert_equal("menswear", template.render!("collection" => { 'handle' => 'menswear-t-shirts' })) assert_equal "menswear", template.render!("collection" => { 'handle' => 'menswear-t-shirts' })
assert_equal("womenswear", template.render!("collection" => { 'handle' => 'x' })) assert_equal "womenswear", template.render!("collection" => { 'handle' => 'x' })
assert_equal("womenswear", template.render!("collection" => { 'handle' => 'y' })) assert_equal "womenswear", template.render!("collection" => { 'handle' => 'y' })
assert_equal("womenswear", template.render!("collection" => { 'handle' => 'z' })) assert_equal "womenswear", template.render!("collection" => { 'handle' => 'z' })
end end
def test_case_when_or def test_case_when_or
@@ -214,7 +214,7 @@ class StandardTagTest < Minitest::Test
end end
def test_assign def test_assign
assert_template_result('variable', '{% assign a = "variable"%}{{a}}') assert_template_result 'variable', '{% assign a = "variable"%}{{a}}'
end end
def test_assign_unassigned def test_assign_unassigned
@@ -223,11 +223,11 @@ class StandardTagTest < Minitest::Test
end end
def test_assign_an_empty_string def test_assign_an_empty_string
assert_template_result('', '{% assign a = ""%}{{a}}') assert_template_result '', '{% assign a = ""%}{{a}}'
end end
def test_assign_is_global def test_assign_is_global
assert_template_result('variable', '{%for i in (1..2) %}{% assign a = "variable"%}{% endfor %}{{a}}') assert_template_result 'variable', '{%for i in (1..2) %}{% assign a = "variable"%}{% endfor %}{{a}}'
end end
def test_case_detects_bad_syntax def test_case_detects_bad_syntax
@@ -293,6 +293,6 @@ class StandardTagTest < Minitest::Test
end end
def test_multiline_tag def test_multiline_tag
assert_template_result('0 1 2 3', "0{%\nfor i in (1..3)\n%} {{\ni\n}}{%\nendfor\n%}") assert_template_result '0 1 2 3', "0{%\nfor i in (1..3)\n%} {{\ni\n}}{%\nendfor\n%}"
end end
end # StandardTagTest end # StandardTagTest

View File

@@ -7,75 +7,75 @@ class StatementsTest < Minitest::Test
def test_true_eql_true def test_true_eql_true
text = ' {% if true == true %} true {% else %} false {% endif %} ' text = ' {% if true == true %} true {% else %} false {% endif %} '
assert_template_result(' true ', text) assert_template_result ' true ', text
end end
def test_true_not_eql_true def test_true_not_eql_true
text = ' {% if true != true %} true {% else %} false {% endif %} ' text = ' {% if true != true %} true {% else %} false {% endif %} '
assert_template_result(' false ', text) assert_template_result ' false ', text
end end
def test_true_lq_true def test_true_lq_true
text = ' {% if 0 > 0 %} true {% else %} false {% endif %} ' text = ' {% if 0 > 0 %} true {% else %} false {% endif %} '
assert_template_result(' false ', text) assert_template_result ' false ', text
end end
def test_one_lq_zero def test_one_lq_zero
text = ' {% if 1 > 0 %} true {% else %} false {% endif %} ' text = ' {% if 1 > 0 %} true {% else %} false {% endif %} '
assert_template_result(' true ', text) assert_template_result ' true ', text
end end
def test_zero_lq_one def test_zero_lq_one
text = ' {% if 0 < 1 %} true {% else %} false {% endif %} ' text = ' {% if 0 < 1 %} true {% else %} false {% endif %} '
assert_template_result(' true ', text) assert_template_result ' true ', text
end end
def test_zero_lq_or_equal_one def test_zero_lq_or_equal_one
text = ' {% if 0 <= 0 %} true {% else %} false {% endif %} ' text = ' {% if 0 <= 0 %} true {% else %} false {% endif %} '
assert_template_result(' true ', text) assert_template_result ' true ', text
end end
def test_zero_lq_or_equal_one_involving_nil def test_zero_lq_or_equal_one_involving_nil
text = ' {% if null <= 0 %} true {% else %} false {% endif %} ' text = ' {% if null <= 0 %} true {% else %} false {% endif %} '
assert_template_result(' false ', text) assert_template_result ' false ', text
text = ' {% if 0 <= null %} true {% else %} false {% endif %} ' text = ' {% if 0 <= null %} true {% else %} false {% endif %} '
assert_template_result(' false ', text) assert_template_result ' false ', text
end end
def test_zero_lqq_or_equal_one def test_zero_lqq_or_equal_one
text = ' {% if 0 >= 0 %} true {% else %} false {% endif %} ' text = ' {% if 0 >= 0 %} true {% else %} false {% endif %} '
assert_template_result(' true ', text) assert_template_result ' true ', text
end end
def test_strings def test_strings
text = " {% if 'test' == 'test' %} true {% else %} false {% endif %} " text = " {% if 'test' == 'test' %} true {% else %} false {% endif %} "
assert_template_result(' true ', text) assert_template_result ' true ', text
end end
def test_strings_not_equal def test_strings_not_equal
text = " {% if 'test' != 'test' %} true {% else %} false {% endif %} " text = " {% if 'test' != 'test' %} true {% else %} false {% endif %} "
assert_template_result(' false ', text) assert_template_result ' false ', text
end end
def test_var_strings_equal def test_var_strings_equal
text = ' {% if var == "hello there!" %} true {% else %} false {% endif %} ' text = ' {% if var == "hello there!" %} true {% else %} false {% endif %} '
assert_template_result(' true ', text, 'var' => 'hello there!') assert_template_result ' true ', text, 'var' => 'hello there!'
end end
def test_var_strings_are_not_equal def test_var_strings_are_not_equal
text = ' {% if "hello there!" == var %} true {% else %} false {% endif %} ' text = ' {% if "hello there!" == var %} true {% else %} false {% endif %} '
assert_template_result(' true ', text, 'var' => 'hello there!') assert_template_result ' true ', text, 'var' => 'hello there!'
end end
def test_var_and_long_string_are_equal def test_var_and_long_string_are_equal
text = " {% if var == 'hello there!' %} true {% else %} false {% endif %} " text = " {% if var == 'hello there!' %} true {% else %} false {% endif %} "
assert_template_result(' true ', text, 'var' => 'hello there!') assert_template_result ' true ', text, 'var' => 'hello there!'
end end
def test_var_and_long_string_are_equal_backwards def test_var_and_long_string_are_equal_backwards
text = " {% if 'hello there!' == var %} true {% else %} false {% endif %} " text = " {% if 'hello there!' == var %} true {% else %} false {% endif %} "
assert_template_result(' true ', text, 'var' => 'hello there!') assert_template_result ' true ', text, 'var' => 'hello there!'
end end
# def test_is_nil # def test_is_nil
@@ -87,27 +87,27 @@ class StatementsTest < Minitest::Test
def test_is_collection_empty def test_is_collection_empty
text = ' {% if array == empty %} true {% else %} false {% endif %} ' text = ' {% if array == empty %} true {% else %} false {% endif %} '
assert_template_result(' true ', text, 'array' => []) assert_template_result ' true ', text, 'array' => []
end end
def test_is_not_collection_empty def test_is_not_collection_empty
text = ' {% if array == empty %} true {% else %} false {% endif %} ' text = ' {% if array == empty %} true {% else %} false {% endif %} '
assert_template_result(' false ', text, 'array' => [1, 2, 3]) assert_template_result ' false ', text, 'array' => [1, 2, 3]
end end
def test_nil def test_nil
text = ' {% if var == nil %} true {% else %} false {% endif %} ' text = ' {% if var == nil %} true {% else %} false {% endif %} '
assert_template_result(' true ', text, 'var' => nil) assert_template_result ' true ', text, 'var' => nil
text = ' {% if var == null %} true {% else %} false {% endif %} ' text = ' {% if var == null %} true {% else %} false {% endif %} '
assert_template_result(' true ', text, 'var' => nil) assert_template_result ' true ', text, 'var' => nil
end end
def test_not_nil def test_not_nil
text = ' {% if var != nil %} true {% else %} false {% endif %} ' text = ' {% if var != nil %} true {% else %} false {% endif %} '
assert_template_result(' true ', text, 'var' => 1) assert_template_result ' true ', text, 'var' => 1
text = ' {% if var != null %} true {% else %} false {% endif %} ' text = ' {% if var != null %} true {% else %} false {% endif %} '
assert_template_result(' true ', text, 'var' => 1) assert_template_result ' true ', text, 'var' => 1
end end
end # StatementsTest end # StatementsTest

View File

@@ -19,10 +19,10 @@ class UnlessElseTagTest < Minitest::Test
end end
def test_unless_in_loop def test_unless_in_loop
assert_template_result('23', '{% for i in choices %}{% unless i %}{{ forloop.index }}{% endunless %}{% endfor %}', 'choices' => [1, nil, false]) assert_template_result '23', '{% for i in choices %}{% unless i %}{{ forloop.index }}{% endunless %}{% endfor %}', 'choices' => [1, nil, false]
end end
def test_unless_else_in_loop def test_unless_else_in_loop
assert_template_result(' TRUE 2 3 ', '{% for i in choices %}{% unless i %} {{ forloop.index }} {% else %} TRUE {% endunless %}{% endfor %}', 'choices' => [1, nil, false]) assert_template_result ' TRUE 2 3 ', '{% for i in choices %}{% unless i %} {{ forloop.index }} {% else %} TRUE {% endunless %}{% endfor %}', 'choices' => [1, nil, false]
end end
end # UnlessElseTest end # UnlessElseTest

View File

@@ -38,6 +38,12 @@ end
class TemplateTest < Minitest::Test class TemplateTest < Minitest::Test
include Liquid include Liquid
def test_instance_assigns_persist_on_same_template_object_between_parses
t = Template.new
assert_equal 'from instance assigns', t.parse("{% assign foo = 'from instance assigns' %}{{ foo }}").render!
assert_equal 'from instance assigns', t.parse("{{ foo }}").render!
end
def test_warnings_is_not_exponential_time def test_warnings_is_not_exponential_time
str = "false" str = "false"
100.times do 100.times do
@@ -45,169 +51,184 @@ class TemplateTest < Minitest::Test
end end
t = Template.parse(str) t = Template.parse(str)
assert_equal([], Timeout.timeout(1) { t.warnings }) assert_equal [], Timeout.timeout(1) { t.warnings }
end
def test_instance_assigns_persist_on_same_template_parsing_between_renders
t = Template.new.parse("{{ foo }}{% assign foo = 'foo' %}{{ foo }}")
assert_equal 'foo', t.render!
assert_equal 'foofoo', t.render!
end end
def test_custom_assigns_do_not_persist_on_same_template def test_custom_assigns_do_not_persist_on_same_template
t = Template.new t = Template.new
assert_equal('from custom assigns', t.parse("{{ foo }}").render!('foo' => 'from custom assigns')) assert_equal 'from custom assigns', t.parse("{{ foo }}").render!('foo' => 'from custom assigns')
assert_equal('', t.parse("{{ foo }}").render!) assert_equal '', t.parse("{{ foo }}").render!
end
assert_equal('from instance assigns', t.parse("{% assign foo = 'from instance assigns' %}{{ foo }}").render!) def test_custom_assigns_squash_instance_assigns
assert_equal('from custom assigns', t.parse("{{ foo }}").render!('foo' => 'from custom assigns')) t = Template.new
assert_equal 'from instance assigns', t.parse("{% assign foo = 'from instance assigns' %}{{ foo }}").render!
assert_equal 'from custom assigns', t.parse("{{ foo }}").render!('foo' => 'from custom assigns')
end
def test_persistent_assigns_squash_instance_assigns
t = Template.new
assert_equal 'from instance assigns', t.parse("{% assign foo = 'from instance assigns' %}{{ foo }}").render!
t.assigns['foo'] = 'from persistent assigns'
assert_equal 'from persistent assigns', t.parse("{{ foo }}").render!
end
def test_lambda_is_called_once_from_persistent_assigns_over_multiple_parses_and_renders
t = Template.new
t.assigns['number'] = -> {
@global ||= 0
@global += 1
}
assert_equal '1', t.parse("{{number}}").render!
assert_equal '1', t.parse("{{number}}").render!
assert_equal '1', t.render!
@global = nil
end end
def test_lambda_is_called_once_from_custom_assigns_over_multiple_parses_and_renders def test_lambda_is_called_once_from_custom_assigns_over_multiple_parses_and_renders
t = Template.new t = Template.new
assigns = { 'number' => -> { assigns = { 'number' => -> {
@global ||= 0 @global ||= 0
@global += 1 @global += 1
} } } }
assert_equal('1', t.parse("{{number}}").render!(assigns)) assert_equal '1', t.parse("{{number}}").render!(assigns)
assert_equal('1', t.parse("{{number}}").render!(assigns)) assert_equal '1', t.parse("{{number}}").render!(assigns)
assert_equal('1', t.render!(assigns)) assert_equal '1', t.render!(assigns)
@global = nil @global = nil
end end
def test_resource_limits_works_with_custom_length_method def test_resource_limits_works_with_custom_length_method
t = Template.parse("{% assign foo = bar %}") t = Template.parse("{% assign foo = bar %}")
context = Liquid::Context.new("bar" => SomethingWithLength.new) t.resource_limits.render_length_limit = 42
context.resource_limits.render_length_limit = 42 assert_equal "", t.render!("bar" => SomethingWithLength.new)
assert_equal("", t.render!(context))
end end
def test_resource_limits_render_length def test_resource_limits_render_length
t = Template.parse("0123456789") t = Template.parse("0123456789")
context = Liquid::Context.new t.resource_limits.render_length_limit = 5
context.resource_limits.render_length_limit = 5 assert_equal "Liquid error: Memory limits exceeded", t.render
assert_equal("Liquid error: Memory limits exceeded", t.render(context)) assert t.resource_limits.reached?
assert(context.resource_limits.reached?)
context.resource_limits.render_length_limit = 10 t.resource_limits.render_length_limit = 10
assert_equal("0123456789", t.render!) assert_equal "0123456789", t.render!
refute_nil(context.resource_limits.render_length) refute_nil t.resource_limits.render_length
end end
def test_resource_limits_render_score def test_resource_limits_render_score
t = Template.parse("{% for a in (1..10) %} {% for a in (1..10) %} foo {% endfor %} {% endfor %}") t = Template.parse("{% for a in (1..10) %} {% for a in (1..10) %} foo {% endfor %} {% endfor %}")
context = Liquid::Context.new t.resource_limits.render_score_limit = 50
context.resource_limits.render_score_limit = 50 assert_equal "Liquid error: Memory limits exceeded", t.render
assert_equal("Liquid error: Memory limits exceeded", t.render(context)) assert t.resource_limits.reached?
assert(context.resource_limits.reached?)
t = Template.parse("{% for a in (1..100) %} foo {% endfor %}") t = Template.parse("{% for a in (1..100) %} foo {% endfor %}")
context.resource_limits.render_score_limit = 50 t.resource_limits.render_score_limit = 50
assert_equal("Liquid error: Memory limits exceeded", t.render(context)) assert_equal "Liquid error: Memory limits exceeded", t.render
assert(context.resource_limits.reached?) assert t.resource_limits.reached?
context.resource_limits.render_score_limit = 200 t.resource_limits.render_score_limit = 200
assert_equal((" foo " * 100), t.render!(context)) assert_equal (" foo " * 100), t.render!
refute_nil(context.resource_limits.render_score) refute_nil t.resource_limits.render_score
end end
def test_resource_limits_assign_score def test_resource_limits_assign_score
t = Template.parse("{% assign foo = 42 %}{% assign bar = 23 %}") t = Template.parse("{% assign foo = 42 %}{% assign bar = 23 %}")
context = Liquid::Context.new t.resource_limits.assign_score_limit = 1
context.resource_limits.assign_score_limit = 1 assert_equal "Liquid error: Memory limits exceeded", t.render
assert_equal("Liquid error: Memory limits exceeded", t.render(context)) assert t.resource_limits.reached?
assert(context.resource_limits.reached?)
context.resource_limits.assign_score_limit = 2 t.resource_limits.assign_score_limit = 2
assert_equal("", t.render!(context)) assert_equal "", t.render!
refute_nil(context.resource_limits.assign_score) refute_nil t.resource_limits.assign_score
end end
def test_resource_limits_assign_score_counts_bytes_not_characters def test_resource_limits_assign_score_counts_bytes_not_characters
t = Template.parse("{% assign foo = 'すごい' %}") t = Template.parse("{% assign foo = 'すごい' %}")
context = Liquid::Context.new t.render
t.render(context) assert_equal 9, t.resource_limits.assign_score
assert_equal(9, context.resource_limits.assign_score)
t = Template.parse("{% capture foo %}すごい{% endcapture %}") t = Template.parse("{% capture foo %}すごい{% endcapture %}")
t.render(context) t.render
assert_equal(9, context.resource_limits.assign_score) assert_equal 9, t.resource_limits.assign_score
end end
def test_resource_limits_assign_score_nested def test_resource_limits_assign_score_nested
t = Template.parse("{% assign foo = 'aaaa' | reverse %}") t = Template.parse("{% assign foo = 'aaaa' | reverse %}")
context = Liquid::Context.new t.resource_limits.assign_score_limit = 3
context.resource_limits.assign_score_limit = 3 assert_equal "Liquid error: Memory limits exceeded", t.render
assert_equal("Liquid error: Memory limits exceeded", t.render(context)) assert t.resource_limits.reached?
assert(context.resource_limits.reached?)
context.resource_limits.assign_score_limit = 5 t.resource_limits.assign_score_limit = 5
assert_equal("", t.render!(context)) assert_equal "", t.render!
end end
def test_resource_limits_aborts_rendering_after_first_error def test_resource_limits_aborts_rendering_after_first_error
t = Template.parse("{% for a in (1..100) %} foo1 {% endfor %} bar {% for a in (1..100) %} foo2 {% endfor %}") t = Template.parse("{% for a in (1..100) %} foo1 {% endfor %} bar {% for a in (1..100) %} foo2 {% endfor %}")
context = Liquid::Context.new t.resource_limits.render_score_limit = 50
context.resource_limits.render_score_limit = 50 assert_equal "Liquid error: Memory limits exceeded", t.render
assert_equal("Liquid error: Memory limits exceeded", t.render(context)) assert t.resource_limits.reached?
assert(context.resource_limits.reached?)
end end
def test_resource_limits_hash_in_template_gets_updated_even_if_no_limits_are_set def test_resource_limits_hash_in_template_gets_updated_even_if_no_limits_are_set
t = Template.parse("{% for a in (1..100) %} {% assign foo = 1 %} {% endfor %}") t = Template.parse("{% for a in (1..100) %} {% assign foo = 1 %} {% endfor %}")
context = Liquid::Context.new t.render!
t.render!(context) assert t.resource_limits.assign_score > 0
assert(context.resource_limits.assign_score > 0) assert t.resource_limits.render_score > 0
assert(context.resource_limits.render_score > 0) assert t.resource_limits.render_length > 0
assert(context.resource_limits.render_length > 0)
end end
def test_render_length_persists_between_blocks def test_render_length_persists_between_blocks
t = Template.parse("{% if true %}aaaa{% endif %}") t = Template.parse("{% if true %}aaaa{% endif %}")
context = Liquid::Context.new t.resource_limits.render_length_limit = 7
context.resource_limits.render_length_limit = 7 assert_equal "Liquid error: Memory limits exceeded", t.render
assert_equal("Liquid error: Memory limits exceeded", t.render(context)) t.resource_limits.render_length_limit = 8
context.resource_limits.render_length_limit = 8 assert_equal "aaaa", t.render
assert_equal("aaaa", t.render(context))
t = Template.parse("{% if true %}aaaa{% endif %}{% if true %}bbb{% endif %}") t = Template.parse("{% if true %}aaaa{% endif %}{% if true %}bbb{% endif %}")
context = Liquid::Context.new t.resource_limits.render_length_limit = 13
context.resource_limits.render_length_limit = 13 assert_equal "Liquid error: Memory limits exceeded", t.render
assert_equal("Liquid error: Memory limits exceeded", t.render(context)) t.resource_limits.render_length_limit = 14
context.resource_limits.render_length_limit = 14 assert_equal "aaaabbb", t.render
assert_equal("aaaabbb", t.render(context))
t = Template.parse("{% if true %}a{% endif %}{% if true %}b{% endif %}{% if true %}a{% endif %}{% if true %}b{% endif %}{% if true %}a{% endif %}{% if true %}b{% endif %}") t = Template.parse("{% if true %}a{% endif %}{% if true %}b{% endif %}{% if true %}a{% endif %}{% if true %}b{% endif %}{% if true %}a{% endif %}{% if true %}b{% endif %}")
context = Liquid::Context.new t.resource_limits.render_length_limit = 5
context.resource_limits.render_length_limit = 5 assert_equal "Liquid error: Memory limits exceeded", t.render
assert_equal("Liquid error: Memory limits exceeded", t.render(context)) t.resource_limits.render_length_limit = 11
context.resource_limits.render_length_limit = 11 assert_equal "Liquid error: Memory limits exceeded", t.render
assert_equal("Liquid error: Memory limits exceeded", t.render(context)) t.resource_limits.render_length_limit = 12
context.resource_limits.render_length_limit = 12 assert_equal "ababab", t.render
assert_equal("ababab", t.render(context))
end end
def test_render_length_uses_number_of_bytes_not_characters def test_render_length_uses_number_of_bytes_not_characters
t = Template.parse("{% if true %}すごい{% endif %}") t = Template.parse("{% if true %}すごい{% endif %}")
context = Liquid::Context.new t.resource_limits.render_length_limit = 10
context.resource_limits.render_length_limit = 10 assert_equal "Liquid error: Memory limits exceeded", t.render
assert_equal("Liquid error: Memory limits exceeded", t.render(context)) t.resource_limits.render_length_limit = 18
context.resource_limits.render_length_limit = 18 assert_equal "すごい", t.render
assert_equal("すごい", t.render(context))
end end
def test_default_resource_limits_unaffected_by_render_with_context def test_default_resource_limits_unaffected_by_render_with_context
context = Context.new context = Context.new
t = Template.parse("{% for a in (1..100) %} {% assign foo = 1 %} {% endfor %}") t = Template.parse("{% for a in (1..100) %} {% assign foo = 1 %} {% endfor %}")
t.render!(context) t.render!(context)
assert(context.resource_limits.assign_score > 0) assert context.resource_limits.assign_score > 0
assert(context.resource_limits.render_score > 0) assert context.resource_limits.render_score > 0
assert(context.resource_limits.render_length > 0) assert context.resource_limits.render_length > 0
end end
def test_can_use_drop_as_context def test_can_use_drop_as_context
t = Template.new t = Template.new
t.registers['lulz'] = 'haha'
drop = TemplateContextDrop.new drop = TemplateContextDrop.new
context = Liquid::Context.build(environments: drop, registers: { 'lulz' => 'haha' }) assert_equal 'fizzbuzz', t.parse('{{foo}}').render!(drop)
drop.context = context assert_equal 'bar', t.parse('{{bar}}').render!(drop)
assert_equal('fizzbuzz', t.parse('{{foo}}').render!(context)) assert_equal 'haha', t.parse("{{baz}}").render!(drop)
assert_equal('bar', t.parse('{{bar}}').render!(context))
assert_equal('haha', t.parse("{{baz}}").render!(context))
end end
def test_render_bang_force_rethrow_errors_on_passed_context def test_render_bang_force_rethrow_errors_on_passed_context
@@ -217,20 +238,20 @@ class TemplateTest < Minitest::Test
e = assert_raises RuntimeError do e = assert_raises RuntimeError do
t.render!(context) t.render!(context)
end end
assert_equal('ruby error in drop', e.message) assert_equal 'ruby error in drop', e.message
end end
def test_exception_renderer_that_returns_string def test_exception_renderer_that_returns_string
exception = nil exception = nil
handler = ->(e) { handler = ->(e) {
exception = e exception = e
'<!-- error -->' '<!-- error -->'
} }
output = Template.parse("{{ 1 | divided_by: 0 }}").render({}, exception_renderer: handler) output = Template.parse("{{ 1 | divided_by: 0 }}").render({}, exception_renderer: handler)
assert(exception.is_a?(Liquid::ZeroDivisionError)) assert exception.is_a?(Liquid::ZeroDivisionError)
assert_equal('<!-- error -->', output) assert_equal '<!-- error -->', output
end end
def test_exception_renderer_that_raises def test_exception_renderer_that_raises
@@ -241,46 +262,44 @@ class TemplateTest < Minitest::Test
raise raise
}) })
end end
assert(exception.is_a?(Liquid::ZeroDivisionError)) assert exception.is_a?(Liquid::ZeroDivisionError)
end end
def test_global_filter_option_on_render def test_global_filter_option_on_render
global_filter_proc = ->(output) { "#{output} filtered" } global_filter_proc = ->(output) { "#{output} filtered" }
rendered_template = Template.parse("{{name}}").render({ "name" => "bob" }, global_filter: global_filter_proc) rendered_template = Template.parse("{{name}}").render({ "name" => "bob" }, global_filter: global_filter_proc)
assert_equal('bob filtered', rendered_template) assert_equal 'bob filtered', rendered_template
end end
def test_global_filter_option_when_native_filters_exist def test_global_filter_option_when_native_filters_exist
global_filter_proc = ->(output) { "#{output} filtered" } global_filter_proc = ->(output) { "#{output} filtered" }
rendered_template = Template.parse("{{name | upcase}}").render({ "name" => "bob" }, global_filter: global_filter_proc) rendered_template = Template.parse("{{name | upcase}}").render({ "name" => "bob" }, global_filter: global_filter_proc)
assert_equal('BOB filtered', rendered_template) assert_equal 'BOB filtered', rendered_template
end end
def test_undefined_variables def test_undefined_variables
t = Template.parse("{{x}} {{y}} {{z.a}} {{z.b}} {{z.c.d}}") t = Template.parse("{{x}} {{y}} {{z.a}} {{z.b}} {{z.c.d}}")
context = Liquid::Context.new('x' => 33, 'z' => { 'a' => 32, 'c' => { 'e' => 31 } }) result = t.render({ 'x' => 33, 'z' => { 'a' => 32, 'c' => { 'e' => 31 } } }, strict_variables: true)
result = t.render(context, strict_variables: true)
assert_equal('33 32 ', result) assert_equal '33 32 ', result
assert_equal(3, context.errors.count) assert_equal 3, t.errors.count
assert_instance_of(Liquid::UndefinedVariable, context.errors[0]) assert_instance_of Liquid::UndefinedVariable, t.errors[0]
assert_equal('Liquid error: undefined variable y', context.errors[0].message) assert_equal 'Liquid error: undefined variable y', t.errors[0].message
assert_instance_of(Liquid::UndefinedVariable, context.errors[1]) assert_instance_of Liquid::UndefinedVariable, t.errors[1]
assert_equal('Liquid error: undefined variable b', context.errors[1].message) assert_equal 'Liquid error: undefined variable b', t.errors[1].message
assert_instance_of(Liquid::UndefinedVariable, context.errors[2]) assert_instance_of Liquid::UndefinedVariable, t.errors[2]
assert_equal('Liquid error: undefined variable d', context.errors[2].message) assert_equal 'Liquid error: undefined variable d', t.errors[2].message
end end
def test_nil_value_does_not_raise def test_nil_value_does_not_raise
Liquid::Template.error_mode = :strict Liquid::Template.error_mode = :strict
t = Template.parse("some{{x}}thing") t = Template.parse("some{{x}}thing")
context = Liquid::Context.new('x' => nil) result = t.render!({ 'x' => nil }, strict_variables: true)
result = t.render!(context, strict_variables: true)
assert_equal(0, context.errors.count) assert_equal 0, t.errors.count
assert_equal('something', result) assert_equal 'something', result
end end
def test_undefined_variables_raise def test_undefined_variables_raise
@@ -294,13 +313,11 @@ class TemplateTest < Minitest::Test
def test_undefined_drop_methods def test_undefined_drop_methods
d = DropWithUndefinedMethod.new d = DropWithUndefinedMethod.new
t = Template.new.parse('{{ foo }} {{ woot }}') t = Template.new.parse('{{ foo }} {{ woot }}')
context = Liquid::Context.new(d) result = t.render(d, strict_variables: true)
d.context = context
result = t.render(context, strict_variables: true)
assert_equal('foo ', result) assert_equal 'foo ', result
assert_equal(1, context.errors.count) assert_equal 1, t.errors.count
assert_instance_of(Liquid::UndefinedDropMethod, context.errors[0]) assert_instance_of Liquid::UndefinedDropMethod, t.errors[0]
end end
def test_undefined_drop_methods_raise def test_undefined_drop_methods_raise
@@ -319,13 +336,12 @@ class TemplateTest < Minitest::Test
"-#{v}-" "-#{v}-"
end end
end end
context = Liquid::Context.new('a' => 123, 'x' => 'foo') result = t.render({ 'a' => 123, 'x' => 'foo' }, filters: [filters], strict_filters: true)
result = t.render(context, filters: [filters], strict_filters: true)
assert_equal('123 ', result) assert_equal '123 ', result
assert_equal(1, context.errors.count) assert_equal 1, t.errors.count
assert_instance_of(Liquid::UndefinedFilter, context.errors[0]) assert_instance_of Liquid::UndefinedFilter, t.errors[0]
assert_equal('Liquid error: undefined filter somefilter1', context.errors[0].message) assert_equal 'Liquid error: undefined filter somefilter1', t.errors[0].message
end end
def test_undefined_filters_raise def test_undefined_filters_raise
@@ -339,54 +355,10 @@ class TemplateTest < Minitest::Test
def test_using_range_literal_works_as_expected def test_using_range_literal_works_as_expected
t = Template.parse("{% assign foo = (x..y) %}{{ foo }}") t = Template.parse("{% assign foo = (x..y) %}{{ foo }}")
result = t.render('x' => 1, 'y' => 5) result = t.render('x' => 1, 'y' => 5)
assert_equal('1..5', result) assert_equal '1..5', result
t = Template.parse("{% assign nums = (x..y) %}{% for num in nums %}{{ num }}{% endfor %}") t = Template.parse("{% assign nums = (x..y) %}{% for num in nums %}{{ num }}{% endfor %}")
result = t.render('x' => 1, 'y' => 5) result = t.render('x' => 1, 'y' => 5)
assert_equal('12345', result) assert_equal '12345', result
end
def test_render_uses_correct_disabled_tags_instance
Liquid::Template.file_system = StubFileSystem.new(
'foo' => 'bar',
'test_include' => '{% include "foo" %}'
)
disabled_tags = DisabledTags.new
context = Context.build(registers: { disabled_tags: disabled_tags })
source = "{% render 'test_include' %}"
parse_context = Liquid::ParseContext.new(line_numbers: true, error_mode: :strict)
document = Document.parse(Liquid::Tokenizer.new(source, true), parse_context)
assert_equal("include usage is not allowed in this context", document.render(context))
end
def test_render_sets_context_static_register_when_register_key_does_exist
disabled_tags_for_test = DisabledTags.new
Template.add_register(:disabled_tags, disabled_tags_for_test)
t = Template.parse("{% if true %} Test Template {% endif %}")
context = Context.new
refute(context.registers.key?(:disabled_tags))
t.render(context)
assert(context.registers.key?(:disabled_tags))
assert_equal(disabled_tags_for_test, context.registers[:disabled_tags])
end
def test_render_does_not_override_context_static_register_when_register_key_exists
context = Context.new
context.registers[:random_register] = nil
Template.add_register(:random_register, {})
t = Template.parse("{% if true %} Test Template {% endif %}")
t.render(context)
assert_nil(context.registers[:random_register])
assert(context.registers.key?(:random_register))
end end
end end

View File

@@ -69,7 +69,7 @@ class TrimModeTest < Minitest::Test
# Make sure the trim isn't applied to standard tags # Make sure the trim isn't applied to standard tags
def test_standard_tags def test_standard_tags
whitespace = ' ' whitespace = ' '
text = <<-END_TEMPLATE text = <<-END_TEMPLATE
<div> <div>
<p> <p>
{% if true %} {% if true %}
@@ -110,58 +110,58 @@ class TrimModeTest < Minitest::Test
# Make sure the trim isn't too agressive # Make sure the trim isn't too agressive
def test_no_trim_output def test_no_trim_output
text = '<p>{{- \'John\' -}}</p>' text = '<p>{{- \'John\' -}}</p>'
expected = '<p>John</p>' expected = '<p>John</p>'
assert_template_result(expected, text) assert_template_result(expected, text)
end end
# Make sure the trim isn't too agressive # Make sure the trim isn't too agressive
def test_no_trim_tags def test_no_trim_tags
text = '<p>{%- if true -%}yes{%- endif -%}</p>' text = '<p>{%- if true -%}yes{%- endif -%}</p>'
expected = '<p>yes</p>' expected = '<p>yes</p>'
assert_template_result(expected, text) assert_template_result(expected, text)
text = '<p>{%- if false -%}no{%- endif -%}</p>' text = '<p>{%- if false -%}no{%- endif -%}</p>'
expected = '<p></p>' expected = '<p></p>'
assert_template_result(expected, text) assert_template_result(expected, text)
end end
def test_single_line_outer_tag def test_single_line_outer_tag
text = '<p> {%- if true %} yes {% endif -%} </p>' text = '<p> {%- if true %} yes {% endif -%} </p>'
expected = '<p> yes </p>' expected = '<p> yes </p>'
assert_template_result(expected, text) assert_template_result(expected, text)
text = '<p> {%- if false %} no {% endif -%} </p>' text = '<p> {%- if false %} no {% endif -%} </p>'
expected = '<p></p>' expected = '<p></p>'
assert_template_result(expected, text) assert_template_result(expected, text)
end end
def test_single_line_inner_tag def test_single_line_inner_tag
text = '<p> {% if true -%} yes {%- endif %} </p>' text = '<p> {% if true -%} yes {%- endif %} </p>'
expected = '<p> yes </p>' expected = '<p> yes </p>'
assert_template_result(expected, text) assert_template_result(expected, text)
text = '<p> {% if false -%} no {%- endif %} </p>' text = '<p> {% if false -%} no {%- endif %} </p>'
expected = '<p> </p>' expected = '<p> </p>'
assert_template_result(expected, text) assert_template_result(expected, text)
end end
def test_single_line_post_tag def test_single_line_post_tag
text = '<p> {% if true -%} yes {% endif -%} </p>' text = '<p> {% if true -%} yes {% endif -%} </p>'
expected = '<p> yes </p>' expected = '<p> yes </p>'
assert_template_result(expected, text) assert_template_result(expected, text)
text = '<p> {% if false -%} no {% endif -%} </p>' text = '<p> {% if false -%} no {% endif -%} </p>'
expected = '<p> </p>' expected = '<p> </p>'
assert_template_result(expected, text) assert_template_result(expected, text)
end end
def test_single_line_pre_tag def test_single_line_pre_tag
text = '<p> {%- if true %} yes {%- endif %} </p>' text = '<p> {%- if true %} yes {%- endif %} </p>'
expected = '<p> yes </p>' expected = '<p> yes </p>'
assert_template_result(expected, text) assert_template_result(expected, text)
text = '<p> {%- if false %} no {%- endif %} </p>' text = '<p> {%- if false %} no {%- endif %} </p>'
expected = '<p> </p>' expected = '<p> </p>'
assert_template_result(expected, text) assert_template_result(expected, text)
end end
@@ -330,7 +330,7 @@ class TrimModeTest < Minitest::Test
assert_template_result(expected, text) assert_template_result(expected, text)
whitespace = ' ' whitespace = ' '
text = <<-END_TEMPLATE text = <<-END_TEMPLATE
<div> <div>
<p> <p>
{% if false -%} {% if false -%}
@@ -504,7 +504,7 @@ class TrimModeTest < Minitest::Test
def test_raw_output def test_raw_output
whitespace = ' ' whitespace = ' '
text = <<-END_TEMPLATE text = <<-END_TEMPLATE
<div> <div>
{% raw %} {% raw %}
{%- if true -%} {%- if true -%}

View File

@@ -7,73 +7,92 @@ class VariableTest < Minitest::Test
def test_simple_variable def test_simple_variable
template = Template.parse(%({{test}})) template = Template.parse(%({{test}}))
assert_equal('worked', template.render!('test' => 'worked')) assert_equal 'worked', template.render!('test' => 'worked')
assert_equal('worked wonderfully', template.render!('test' => 'worked wonderfully')) assert_equal 'worked wonderfully', template.render!('test' => 'worked wonderfully')
end end
def test_variable_render_calls_to_liquid def test_variable_render_calls_to_liquid
assert_template_result('foobar', '{{ foo }}', 'foo' => ThingWithToLiquid.new) assert_template_result 'foobar', '{{ foo }}', 'foo' => ThingWithToLiquid.new
end end
def test_simple_with_whitespaces def test_simple_with_whitespaces
template = Template.parse(%( {{ test }} )) template = Template.parse(%( {{ test }} ))
assert_equal(' worked ', template.render!('test' => 'worked')) assert_equal ' worked ', template.render!('test' => 'worked')
assert_equal(' worked wonderfully ', template.render!('test' => 'worked wonderfully')) assert_equal ' worked wonderfully ', template.render!('test' => 'worked wonderfully')
end end
def test_ignore_unknown def test_ignore_unknown
template = Template.parse(%({{ test }})) template = Template.parse(%({{ test }}))
assert_equal('', template.render!) assert_equal '', template.render!
end end
def test_using_blank_as_variable_name def test_using_blank_as_variable_name
template = Template.parse("{% assign foo = blank %}{{ foo }}") template = Template.parse("{% assign foo = blank %}{{ foo }}")
assert_equal('', template.render!) assert_equal '', template.render!
end end
def test_using_empty_as_variable_name def test_using_empty_as_variable_name
template = Template.parse("{% assign foo = empty %}{{ foo }}") template = Template.parse("{% assign foo = empty %}{{ foo }}")
assert_equal('', template.render!) assert_equal '', template.render!
end end
def test_hash_scoping def test_hash_scoping
template = Template.parse(%({{ test.test }})) template = Template.parse(%({{ test.test }}))
assert_equal('worked', template.render!('test' => { 'test' => 'worked' })) assert_equal 'worked', template.render!('test' => { 'test' => 'worked' })
end end
def test_false_renders_as_false def test_false_renders_as_false
assert_equal('false', Template.parse("{{ foo }}").render!('foo' => false)) assert_equal 'false', Template.parse("{{ foo }}").render!('foo' => false)
assert_equal('false', Template.parse("{{ false }}").render!) assert_equal 'false', Template.parse("{{ false }}").render!
end end
def test_nil_renders_as_empty_string def test_nil_renders_as_empty_string
assert_equal('', Template.parse("{{ nil }}").render!) assert_equal '', Template.parse("{{ nil }}").render!
assert_equal('cat', Template.parse("{{ nil | append: 'cat' }}").render!) assert_equal 'cat', Template.parse("{{ nil | append: 'cat' }}").render!
end
def test_preset_assigns
template = Template.parse(%({{ test }}))
template.assigns['test'] = 'worked'
assert_equal 'worked', template.render!
end end
def test_reuse_parsed_template def test_reuse_parsed_template
template = Template.parse(%({{ greeting }} {{ name }})) template = Template.parse(%({{ greeting }} {{ name }}))
assert_equal('Hello Tobi', template.render!('greeting' => 'Hello', 'name' => 'Tobi')) template.assigns['greeting'] = 'Goodbye'
assert_equal('Goodbye Brian', template.render!('greeting' => 'Goodbye', 'name' => 'Brian')) assert_equal 'Hello Tobi', template.render!('greeting' => 'Hello', 'name' => 'Tobi')
assert_equal 'Hello ', template.render!('greeting' => 'Hello', 'unknown' => 'Tobi')
assert_equal 'Hello Brian', template.render!('greeting' => 'Hello', 'name' => 'Brian')
assert_equal 'Goodbye Brian', template.render!('name' => 'Brian')
assert_equal({ 'greeting' => 'Goodbye' }, template.assigns)
end
def test_assigns_not_polluted_from_template
template = Template.parse(%({{ test }}{% assign test = 'bar' %}{{ test }}))
template.assigns['test'] = 'baz'
assert_equal 'bazbar', template.render!
assert_equal 'bazbar', template.render!
assert_equal 'foobar', template.render!('test' => 'foo')
assert_equal 'bazbar', template.render!
end end
def test_hash_with_default_proc def test_hash_with_default_proc
template = Template.parse(%(Hello {{ test }})) template = Template.parse(%(Hello {{ test }}))
assigns = Hash.new { |_h, k| raise "Unknown variable '#{k}'" } assigns = Hash.new { |_h, k| raise "Unknown variable '#{k}'" }
assigns['test'] = 'Tobi' assigns['test'] = 'Tobi'
assert_equal('Hello Tobi', template.render!(assigns)) assert_equal 'Hello Tobi', template.render!(assigns)
assigns.delete('test') assigns.delete('test')
e = assert_raises(RuntimeError) do e = assert_raises(RuntimeError) do
template.render!(assigns) template.render!(assigns)
end end
assert_equal("Unknown variable 'test'", e.message) assert_equal "Unknown variable 'test'", e.message
end end
def test_multiline_variable def test_multiline_variable
assert_equal('worked', Template.parse("{{\ntest\n}}").render!('test' => 'worked')) assert_equal 'worked', Template.parse("{{\ntest\n}}").render!('test' => 'worked')
end end
def test_render_symbol def test_render_symbol
assert_template_result('bar', '{{ foo }}', 'foo' => :bar) assert_template_result 'bar', '{{ foo }}', 'foo' => :bar
end end
end end

View File

@@ -38,55 +38,44 @@ module Minitest
include Liquid include Liquid
def assert_template_result(expected, template, assigns = {}, message = nil) def assert_template_result(expected, template, assigns = {}, message = nil)
assert_equal(expected, Template.parse(template, line_numbers: true).render!(assigns), message) assert_equal expected, Template.parse(template, line_numbers: true).render!(assigns), message
end end
def assert_template_result_matches(expected, template, assigns = {}, message = nil) def assert_template_result_matches(expected, template, assigns = {}, message = nil)
return assert_template_result(expected, template, assigns, message) unless expected.is_a?(Regexp) return assert_template_result(expected, template, assigns, message) unless expected.is_a?(Regexp)
assert_match(expected, Template.parse(template, line_numbers: true).render!(assigns), message) assert_match expected, Template.parse(template, line_numbers: true).render!(assigns), message
end end
def assert_match_syntax_error(match, template, assigns = {}) def assert_match_syntax_error(match, template, assigns = {})
exception = assert_raises(Liquid::SyntaxError) do exception = assert_raises(Liquid::SyntaxError) do
Template.parse(template, line_numbers: true).render(assigns) Template.parse(template, line_numbers: true).render(assigns)
end end
assert_match(match, exception.message) assert_match match, exception.message
end
def assert_usage_increment(name, times: 1)
old_method = Liquid::Usage.method(:increment)
calls = 0
begin
Liquid::Usage.singleton_class.send(:remove_method, :increment)
Liquid::Usage.define_singleton_method(:increment) do |got_name|
calls += 1 if got_name == name
old_method.call(got_name)
end
yield
ensure
Liquid::Usage.singleton_class.send(:remove_method, :increment)
Liquid::Usage.define_singleton_method(:increment, old_method)
end
assert_equal(times, calls, "Number of calls to Usage.increment with #{name.inspect}")
end end
def with_global_filter(*globals) def with_global_filter(*globals)
original_global_filters = Liquid::StrainerFactory.instance_variable_get(:@global_filters) original_global_strainer = Liquid::Strainer.class_variable_get(:@@global_strainer)
Liquid::StrainerFactory.instance_variable_set(:@global_filters, []) Liquid::Strainer.class_variable_set(:@@global_strainer, Class.new(Liquid::Strainer) do
globals.each do |global| @filter_methods = Set.new
Liquid::StrainerFactory.add_global_filter(global) end)
end Liquid::Strainer.class_variable_get(:@@strainer_class_cache).clear
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 yield
ensure ensure
Liquid::StrainerFactory.send(:strainer_class_cache).clear Liquid::Strainer.class_variable_get(:@@strainer_class_cache).clear
Liquid::StrainerFactory.instance_variable_set(:@global_filters, original_global_filters) Liquid::Strainer.class_variable_set(:@@global_strainer, original_global_strainer)
end
def with_taint_mode(mode)
old_mode = Liquid::Template.taint_mode
Liquid::Template.taint_mode = mode
yield
ensure
Liquid::Template.taint_mode = old_mode
end end
def with_error_mode(mode) def with_error_mode(mode)
@@ -139,7 +128,7 @@ class StubFileSystem
def initialize(values) def initialize(values)
@file_read_count = 0 @file_read_count = 0
@values = values @values = values
end end
def read_template_file(template_path) def read_template_file(template_path)
@@ -147,16 +136,3 @@ class StubFileSystem
@values.fetch(template_path) @values.fetch(template_path)
end end
end end
class StubTemplateFactory
attr_reader :count
def initialize
@count = 0
end
def for(_template_name)
@count += 1
Liquid::Template.new
end
end

View File

@@ -7,42 +7,42 @@ class BlockUnitTest < Minitest::Test
def test_blankspace def test_blankspace
template = Liquid::Template.parse(" ") template = Liquid::Template.parse(" ")
assert_equal([" "], template.root.nodelist) assert_equal [" "], template.root.nodelist
end end
def test_variable_beginning def test_variable_beginning
template = Liquid::Template.parse("{{funk}} ") template = Liquid::Template.parse("{{funk}} ")
assert_equal(2, template.root.nodelist.size) assert_equal 2, template.root.nodelist.size
assert_equal(Variable, template.root.nodelist[0].class) assert_equal Variable, template.root.nodelist[0].class
assert_equal(String, template.root.nodelist[1].class) assert_equal String, template.root.nodelist[1].class
end end
def test_variable_end def test_variable_end
template = Liquid::Template.parse(" {{funk}}") template = Liquid::Template.parse(" {{funk}}")
assert_equal(2, template.root.nodelist.size) assert_equal 2, template.root.nodelist.size
assert_equal(String, template.root.nodelist[0].class) assert_equal String, template.root.nodelist[0].class
assert_equal(Variable, template.root.nodelist[1].class) assert_equal Variable, template.root.nodelist[1].class
end end
def test_variable_middle def test_variable_middle
template = Liquid::Template.parse(" {{funk}} ") template = Liquid::Template.parse(" {{funk}} ")
assert_equal(3, template.root.nodelist.size) assert_equal 3, template.root.nodelist.size
assert_equal(String, template.root.nodelist[0].class) assert_equal String, template.root.nodelist[0].class
assert_equal(Variable, template.root.nodelist[1].class) assert_equal Variable, template.root.nodelist[1].class
assert_equal(String, template.root.nodelist[2].class) assert_equal String, template.root.nodelist[2].class
end end
def test_variable_many_embedded_fragments def test_variable_many_embedded_fragments
template = Liquid::Template.parse(" {{funk}} {{so}} {{brother}} ") template = Liquid::Template.parse(" {{funk}} {{so}} {{brother}} ")
assert_equal(7, template.root.nodelist.size) assert_equal 7, template.root.nodelist.size
assert_equal([String, Variable, String, Variable, String, Variable, String], assert_equal [String, Variable, String, Variable, String, Variable, String],
block_types(template.root.nodelist)) block_types(template.root.nodelist)
end end
def test_with_block def test_with_block
template = Liquid::Template.parse(" {% comment %} {% endcomment %} ") template = Liquid::Template.parse(" {% comment %} {% endcomment %} ")
assert_equal([String, Comment, String], block_types(template.root.nodelist)) assert_equal [String, Comment, String], block_types(template.root.nodelist)
assert_equal(3, template.root.nodelist.size) assert_equal 3, template.root.nodelist.size
end end
def test_with_custom_tag def test_with_custom_tag
@@ -63,7 +63,7 @@ class BlockUnitTest < Minitest::Test
assert_equal 'hello', template.render assert_equal 'hello', template.render
buf = +'' buf = +''
output = template.render({}, output: buf) output = template.render({}, output: buf)
assert_equal 'hello', output assert_equal 'hello', output
assert_equal 'hello', buf assert_equal 'hello', buf
@@ -81,7 +81,7 @@ class BlockUnitTest < Minitest::Test
assert_equal 'foohellobar', template.render assert_equal 'foohellobar', template.render
buf = +'' buf = +''
output = template.render({}, output: buf) output = template.render({}, output: buf)
assert_equal 'foohellobar', output assert_equal 'foohellobar', output
assert_equal 'foohellobar', buf assert_equal 'foohellobar', buf

View File

@@ -10,154 +10,154 @@ 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
assert_equal(true, Condition.new(1, '==', 1).evaluate) assert_equal true, Condition.new(1, '==', 1).evaluate
end end
def test_default_operators_evalute_true def test_default_operators_evalute_true
assert_evaluates_true(1, '==', 1) assert_evaluates_true 1, '==', 1
assert_evaluates_true(1, '!=', 2) assert_evaluates_true 1, '!=', 2
assert_evaluates_true(1, '<>', 2) assert_evaluates_true 1, '<>', 2
assert_evaluates_true(1, '<', 2) assert_evaluates_true 1, '<', 2
assert_evaluates_true(2, '>', 1) assert_evaluates_true 2, '>', 1
assert_evaluates_true(1, '>=', 1) assert_evaluates_true 1, '>=', 1
assert_evaluates_true(2, '>=', 1) assert_evaluates_true 2, '>=', 1
assert_evaluates_true(1, '<=', 2) assert_evaluates_true 1, '<=', 2
assert_evaluates_true(1, '<=', 1) assert_evaluates_true 1, '<=', 1
# negative numbers # negative numbers
assert_evaluates_true(1, '>', -1) assert_evaluates_true 1, '>', -1
assert_evaluates_true(-1, '<', 1) assert_evaluates_true(-1, '<', 1)
assert_evaluates_true(1.0, '>', -1.0) assert_evaluates_true 1.0, '>', -1.0
assert_evaluates_true(-1.0, '<', 1.0) assert_evaluates_true(-1.0, '<', 1.0)
end end
def test_default_operators_evalute_false def test_default_operators_evalute_false
assert_evaluates_false(1, '==', 2) assert_evaluates_false 1, '==', 2
assert_evaluates_false(1, '!=', 1) assert_evaluates_false 1, '!=', 1
assert_evaluates_false(1, '<>', 1) assert_evaluates_false 1, '<>', 1
assert_evaluates_false(1, '<', 0) assert_evaluates_false 1, '<', 0
assert_evaluates_false(2, '>', 4) assert_evaluates_false 2, '>', 4
assert_evaluates_false(1, '>=', 3) assert_evaluates_false 1, '>=', 3
assert_evaluates_false(2, '>=', 4) assert_evaluates_false 2, '>=', 4
assert_evaluates_false(1, '<=', 0) assert_evaluates_false 1, '<=', 0
assert_evaluates_false(1, '<=', 0) assert_evaluates_false 1, '<=', 0
end end
def test_contains_works_on_strings def test_contains_works_on_strings
assert_evaluates_true('bob', 'contains', 'o') assert_evaluates_true 'bob', 'contains', 'o'
assert_evaluates_true('bob', 'contains', 'b') assert_evaluates_true 'bob', 'contains', 'b'
assert_evaluates_true('bob', 'contains', 'bo') assert_evaluates_true 'bob', 'contains', 'bo'
assert_evaluates_true('bob', 'contains', 'ob') assert_evaluates_true 'bob', 'contains', 'ob'
assert_evaluates_true('bob', 'contains', 'bob') assert_evaluates_true 'bob', 'contains', 'bob'
assert_evaluates_false('bob', 'contains', 'bob2') assert_evaluates_false 'bob', 'contains', 'bob2'
assert_evaluates_false('bob', 'contains', 'a') assert_evaluates_false 'bob', 'contains', 'a'
assert_evaluates_false('bob', 'contains', '---') assert_evaluates_false 'bob', 'contains', '---'
end end
def test_invalid_comparation_operator def test_invalid_comparation_operator
assert_evaluates_argument_error(1, '~~', 0) assert_evaluates_argument_error 1, '~~', 0
end end
def test_comparation_of_int_and_str def test_comparation_of_int_and_str
assert_evaluates_argument_error('1', '>', 0) assert_evaluates_argument_error '1', '>', 0
assert_evaluates_argument_error('1', '<', 0) assert_evaluates_argument_error '1', '<', 0
assert_evaluates_argument_error('1', '>=', 0) assert_evaluates_argument_error '1', '>=', 0
assert_evaluates_argument_error('1', '<=', 0) assert_evaluates_argument_error '1', '<=', 0
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
assert_nil(Condition.new(2, '>', {}).evaluate) assert_nil Condition.new(2, '>', {}).evaluate
assert_equal(false, 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' => 1 }, '==', 'a' => 1).evaluate
assert_equal(true, Condition.new({ 'a' => 2 }, 'contains', 'a').evaluate) assert_equal true, Condition.new({ 'a' => 2 }, 'contains', 'a').evaluate
end end
def test_contains_works_on_arrays def test_contains_works_on_arrays
@context = Liquid::Context.new @context = Liquid::Context.new
@context['array'] = [1, 2, 3, 4, 5] @context['array'] = [1, 2, 3, 4, 5]
array_expr = VariableLookup.new("array") array_expr = VariableLookup.new("array")
assert_evaluates_false(array_expr, 'contains', 0) assert_evaluates_false array_expr, 'contains', 0
assert_evaluates_true(array_expr, 'contains', 1) assert_evaluates_true array_expr, 'contains', 1
assert_evaluates_true(array_expr, 'contains', 2) assert_evaluates_true array_expr, 'contains', 2
assert_evaluates_true(array_expr, 'contains', 3) assert_evaluates_true array_expr, 'contains', 3
assert_evaluates_true(array_expr, 'contains', 4) assert_evaluates_true array_expr, 'contains', 4
assert_evaluates_true(array_expr, 'contains', 5) assert_evaluates_true array_expr, 'contains', 5
assert_evaluates_false(array_expr, 'contains', 6) assert_evaluates_false array_expr, 'contains', 6
assert_evaluates_false(array_expr, 'contains', "1") assert_evaluates_false array_expr, 'contains', "1"
end end
def test_contains_returns_false_for_nil_operands def test_contains_returns_false_for_nil_operands
@context = Liquid::Context.new @context = Liquid::Context.new
assert_evaluates_false(VariableLookup.new('not_assigned'), 'contains', '0') assert_evaluates_false VariableLookup.new('not_assigned'), 'contains', '0'
assert_evaluates_false(0, 'contains', VariableLookup.new('not_assigned')) assert_evaluates_false 0, 'contains', VariableLookup.new('not_assigned')
end end
def test_contains_return_false_on_wrong_data_type def test_contains_return_false_on_wrong_data_type
assert_evaluates_false(1, 'contains', 0) assert_evaluates_false 1, 'contains', 0
end end
def test_contains_with_string_left_operand_coerces_right_operand_to_string def test_contains_with_string_left_operand_coerces_right_operand_to_string
assert_evaluates_true(' 1 ', 'contains', 1) assert_evaluates_true ' 1 ', 'contains', 1
assert_evaluates_false(' 1 ', 'contains', 2) assert_evaluates_false ' 1 ', 'contains', 2
end end
def test_or_condition def test_or_condition
condition = Condition.new(1, '==', 2) condition = Condition.new(1, '==', 2)
assert_equal(false, condition.evaluate) 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
condition.or(Condition.new(1, '==', 1)) condition.or(Condition.new(1, '==', 1))
assert_equal(true, condition.evaluate) assert_equal true, condition.evaluate
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
condition.and(Condition.new(2, '==', 2)) condition.and(Condition.new(2, '==', 2))
assert_equal(true, condition.evaluate) assert_equal true, condition.evaluate
condition.and(Condition.new(2, '==', 1)) condition.and(Condition.new(2, '==', 1))
assert_equal(false, condition.evaluate) assert_equal false, condition.evaluate
end end
def test_should_allow_custom_proc_operator def test_should_allow_custom_proc_operator
Condition.operators['starts_with'] = proc { |_cond, left, right| left =~ /^#{right}/ } Condition.operators['starts_with'] = proc { |_cond, left, right| left =~ /^#{right}/ }
assert_evaluates_true('bob', 'starts_with', 'b') assert_evaluates_true 'bob', 'starts_with', 'b'
assert_evaluates_false('bob', 'starts_with', 'o') assert_evaluates_false 'bob', 'starts_with', 'o'
ensure ensure
Condition.operators.delete('starts_with') Condition.operators.delete('starts_with')
end end
def test_left_or_right_may_contain_operators def test_left_or_right_may_contain_operators
@context = Liquid::Context.new @context = Liquid::Context.new
@context['one'] = @context['another'] = "gnomeslab-and-or-liquid" @context['one'] = @context['another'] = "gnomeslab-and-or-liquid"
assert_evaluates_true(VariableLookup.new("one"), '==', VariableLookup.new("another")) assert_evaluates_true VariableLookup.new("one"), '==', VariableLookup.new("another")
end end
private private
def assert_evaluates_true(left, op, right) def assert_evaluates_true(left, op, right)
assert(Condition.new(left, op, right).evaluate(@context), assert Condition.new(left, op, right).evaluate(@context),
"Evaluated false: #{left} #{op} #{right}") "Evaluated false: #{left} #{op} #{right}"
end end
def assert_evaluates_false(left, op, right) def assert_evaluates_false(left, op, right)
assert(!Condition.new(left, op, right).evaluate(@context), assert !Condition.new(left, op, right).evaluate(@context),
"Evaluated true: #{left} #{op} #{right}") "Evaluated true: #{left} #{op} #{right}"
end end
def assert_evaluates_argument_error(left, op, right) def assert_evaluates_argument_error(left, op, right)

View File

@@ -46,7 +46,7 @@ end
class CounterDrop < Liquid::Drop class CounterDrop < Liquid::Drop
def count def count
@count ||= 0 @count ||= 0
@count += 1 @count += 1
end end
end end
@@ -55,9 +55,9 @@ class ArrayLike
end end
def [](index) def [](index)
@counts ||= [] @counts ||= []
@counts[index] ||= 0 @counts[index] ||= 0
@counts[index] += 1 @counts[index] += 1
end end
def to_liquid def to_liquid
@@ -74,34 +74,34 @@ class ContextUnitTest < Minitest::Test
def test_variables def test_variables
@context['string'] = 'string' @context['string'] = 'string'
assert_equal('string', @context['string']) assert_equal 'string', @context['string']
@context['num'] = 5 @context['num'] = 5
assert_equal(5, @context['num']) assert_equal 5, @context['num']
@context['time'] = Time.parse('2006-06-06 12:00:00') @context['time'] = Time.parse('2006-06-06 12:00:00')
assert_equal(Time.parse('2006-06-06 12:00:00'), @context['time']) assert_equal Time.parse('2006-06-06 12:00:00'), @context['time']
@context['date'] = Date.today @context['date'] = Date.today
assert_equal(Date.today, @context['date']) assert_equal Date.today, @context['date']
now = Time.now now = Time.now
@context['datetime'] = now @context['datetime'] = now
assert_equal(now, @context['datetime']) assert_equal now, @context['datetime']
@context['bool'] = true @context['bool'] = true
assert_equal(true, @context['bool']) assert_equal true, @context['bool']
@context['bool'] = false @context['bool'] = false
assert_equal(false, @context['bool']) assert_equal false, @context['bool']
@context['nil'] = nil @context['nil'] = nil
assert_nil(@context['nil']) assert_nil @context['nil']
assert_nil(@context['nil']) assert_nil @context['nil']
end end
def test_variables_not_existing def test_variables_not_existing
assert_nil(@context['does_not_exist']) assert_nil @context['does_not_exist']
end end
def test_scoping def test_scoping
@@ -122,20 +122,20 @@ class ContextUnitTest < Minitest::Test
def test_length_query def test_length_query
@context['numbers'] = [1, 2, 3, 4] @context['numbers'] = [1, 2, 3, 4]
assert_equal(4, @context['numbers.size']) assert_equal 4, @context['numbers.size']
@context['numbers'] = { 1 => 1, 2 => 2, 3 => 3, 4 => 4 } @context['numbers'] = { 1 => 1, 2 => 2, 3 => 3, 4 => 4 }
assert_equal(4, @context['numbers.size']) assert_equal 4, @context['numbers.size']
@context['numbers'] = { 1 => 1, 2 => 2, 3 => 3, 4 => 4, 'size' => 1000 } @context['numbers'] = { 1 => 1, 2 => 2, 3 => 3, 4 => 4, 'size' => 1000 }
assert_equal(1000, @context['numbers.size']) assert_equal 1000, @context['numbers.size']
end end
def test_hyphenated_variable def test_hyphenated_variable
@context['oh-my'] = 'godz' @context['oh-my'] = 'godz'
assert_equal('godz', @context['oh-my']) assert_equal 'godz', @context['oh-my']
end end
def test_add_filter def test_add_filter
@@ -147,13 +147,13 @@ class ContextUnitTest < Minitest::Test
context = Context.new context = Context.new
context.add_filters(filter) context.add_filters(filter)
assert_equal('hi? hi!', context.invoke(:hi, 'hi?')) assert_equal 'hi? hi!', context.invoke(:hi, 'hi?')
context = Context.new context = Context.new
assert_equal('hi?', context.invoke(:hi, 'hi?')) assert_equal 'hi?', context.invoke(:hi, 'hi?')
context.add_filters(filter) context.add_filters(filter)
assert_equal('hi? hi!', context.invoke(:hi, 'hi?')) assert_equal 'hi? hi!', context.invoke(:hi, 'hi?')
end end
def test_only_intended_filters_make_it_there def test_only_intended_filters_make_it_there
@@ -164,75 +164,75 @@ class ContextUnitTest < Minitest::Test
end end
context = Context.new context = Context.new
assert_equal("Wookie", context.invoke("hi", "Wookie")) assert_equal "Wookie", context.invoke("hi", "Wookie")
context.add_filters(filter) context.add_filters(filter)
assert_equal("Wookie hi!", context.invoke("hi", "Wookie")) assert_equal "Wookie hi!", context.invoke("hi", "Wookie")
end end
def test_add_item_in_outer_scope def test_add_item_in_outer_scope
@context['test'] = 'test' @context['test'] = 'test'
@context.push @context.push
assert_equal('test', @context['test']) assert_equal 'test', @context['test']
@context.pop @context.pop
assert_equal('test', @context['test']) assert_equal 'test', @context['test']
end end
def test_add_item_in_inner_scope def test_add_item_in_inner_scope
@context.push @context.push
@context['test'] = 'test' @context['test'] = 'test'
assert_equal('test', @context['test']) assert_equal 'test', @context['test']
@context.pop @context.pop
assert_nil(@context['test']) assert_nil @context['test']
end end
def test_hierachical_data def test_hierachical_data
@context['hash'] = { "name" => 'tobi' } @context['hash'] = { "name" => 'tobi' }
assert_equal('tobi', @context['hash.name']) assert_equal 'tobi', @context['hash.name']
assert_equal('tobi', @context['hash["name"]']) assert_equal 'tobi', @context['hash["name"]']
end end
def test_keywords def test_keywords
assert_equal(true, @context['true']) assert_equal true, @context['true']
assert_equal(false, @context['false']) assert_equal false, @context['false']
end end
def test_digits def test_digits
assert_equal(100, @context['100']) assert_equal 100, @context['100']
assert_equal(100.00, @context['100.00']) assert_equal 100.00, @context['100.00']
end end
def test_strings def test_strings
assert_equal("hello!", @context['"hello!"']) assert_equal "hello!", @context['"hello!"']
assert_equal("hello!", @context["'hello!'"]) assert_equal "hello!", @context["'hello!'"]
end end
def test_merge def test_merge
@context.merge("test" => "test") @context.merge("test" => "test")
assert_equal('test', @context['test']) assert_equal 'test', @context['test']
@context.merge("test" => "newvalue", "foo" => "bar") @context.merge("test" => "newvalue", "foo" => "bar")
assert_equal('newvalue', @context['test']) assert_equal 'newvalue', @context['test']
assert_equal('bar', @context['foo']) assert_equal 'bar', @context['foo']
end end
def test_array_notation def test_array_notation
@context['test'] = [1, 2, 3, 4, 5] @context['test'] = [1, 2, 3, 4, 5]
assert_equal(1, @context['test[0]']) assert_equal 1, @context['test[0]']
assert_equal(2, @context['test[1]']) assert_equal 2, @context['test[1]']
assert_equal(3, @context['test[2]']) assert_equal 3, @context['test[2]']
assert_equal(4, @context['test[3]']) assert_equal 4, @context['test[3]']
assert_equal(5, @context['test[4]']) assert_equal 5, @context['test[4]']
end end
def test_recoursive_array_notation def test_recoursive_array_notation
@context['test'] = { 'test' => [1, 2, 3, 4, 5] } @context['test'] = { 'test' => [1, 2, 3, 4, 5] }
assert_equal(1, @context['test.test[0]']) assert_equal 1, @context['test.test[0]']
@context['test'] = [{ 'test' => 'worked' }] @context['test'] = [{ 'test' => 'worked' }]
assert_equal('worked', @context['test[0].test']) assert_equal 'worked', @context['test[0].test']
end end
def test_hash_to_array_transition def test_hash_to_array_transition
@@ -243,177 +243,177 @@ class ContextUnitTest < Minitest::Test
'Red' => ['660000', '993333', 'CC6666', 'FF9999'], 'Red' => ['660000', '993333', 'CC6666', 'FF9999'],
} }
assert_equal('003366', @context['colors.Blue[0]']) assert_equal '003366', @context['colors.Blue[0]']
assert_equal('FF9999', @context['colors.Red[3]']) assert_equal 'FF9999', @context['colors.Red[3]']
end end
def test_try_first def test_try_first
@context['test'] = [1, 2, 3, 4, 5] @context['test'] = [1, 2, 3, 4, 5]
assert_equal(1, @context['test.first']) assert_equal 1, @context['test.first']
assert_equal(5, @context['test.last']) assert_equal 5, @context['test.last']
@context['test'] = { 'test' => [1, 2, 3, 4, 5] } @context['test'] = { 'test' => [1, 2, 3, 4, 5] }
assert_equal(1, @context['test.test.first']) assert_equal 1, @context['test.test.first']
assert_equal(5, @context['test.test.last']) assert_equal 5, @context['test.test.last']
@context['test'] = [1] @context['test'] = [1]
assert_equal(1, @context['test.first']) assert_equal 1, @context['test.first']
assert_equal(1, @context['test.last']) assert_equal 1, @context['test.last']
end end
def test_access_hashes_with_hash_notation def test_access_hashes_with_hash_notation
@context['products'] = { 'count' => 5, 'tags' => ['deepsnow', 'freestyle'] } @context['products'] = { 'count' => 5, 'tags' => ['deepsnow', 'freestyle'] }
@context['product'] = { 'variants' => [{ 'title' => 'draft151cm' }, { 'title' => 'element151cm' }] } @context['product'] = { 'variants' => [{ 'title' => 'draft151cm' }, { 'title' => 'element151cm' }] }
assert_equal(5, @context['products["count"]']) assert_equal 5, @context['products["count"]']
assert_equal('deepsnow', @context['products["tags"][0]']) assert_equal 'deepsnow', @context['products["tags"][0]']
assert_equal('deepsnow', @context['products["tags"].first']) assert_equal 'deepsnow', @context['products["tags"].first']
assert_equal('draft151cm', @context['product["variants"][0]["title"]']) assert_equal 'draft151cm', @context['product["variants"][0]["title"]']
assert_equal('element151cm', @context['product["variants"][1]["title"]']) assert_equal 'element151cm', @context['product["variants"][1]["title"]']
assert_equal('draft151cm', @context['product["variants"][0]["title"]']) assert_equal 'draft151cm', @context['product["variants"][0]["title"]']
assert_equal('element151cm', @context['product["variants"].last["title"]']) assert_equal 'element151cm', @context['product["variants"].last["title"]']
end end
def test_access_variable_with_hash_notation def test_access_variable_with_hash_notation
@context['foo'] = 'baz' @context['foo'] = 'baz'
@context['bar'] = 'foo' @context['bar'] = 'foo'
assert_equal('baz', @context['["foo"]']) assert_equal 'baz', @context['["foo"]']
assert_equal('baz', @context['[bar]']) assert_equal 'baz', @context['[bar]']
end end
def test_access_hashes_with_hash_access_variables def test_access_hashes_with_hash_access_variables
@context['var'] = 'tags' @context['var'] = 'tags'
@context['nested'] = { 'var' => 'tags' } @context['nested'] = { 'var' => 'tags' }
@context['products'] = { 'count' => 5, 'tags' => ['deepsnow', 'freestyle'] } @context['products'] = { 'count' => 5, 'tags' => ['deepsnow', 'freestyle'] }
assert_equal('deepsnow', @context['products[var].first']) assert_equal 'deepsnow', @context['products[var].first']
assert_equal('freestyle', @context['products[nested.var].last']) assert_equal 'freestyle', @context['products[nested.var].last']
end end
def test_hash_notation_only_for_hash_access def test_hash_notation_only_for_hash_access
@context['array'] = [1, 2, 3, 4, 5] @context['array'] = [1, 2, 3, 4, 5]
@context['hash'] = { 'first' => 'Hello' } @context['hash'] = { 'first' => 'Hello' }
assert_equal(1, @context['array.first']) assert_equal 1, @context['array.first']
assert_nil(@context['array["first"]']) assert_nil @context['array["first"]']
assert_equal('Hello', @context['hash["first"]']) assert_equal 'Hello', @context['hash["first"]']
end end
def test_first_can_appear_in_middle_of_callchain def test_first_can_appear_in_middle_of_callchain
@context['product'] = { 'variants' => [{ 'title' => 'draft151cm' }, { 'title' => 'element151cm' }] } @context['product'] = { 'variants' => [{ 'title' => 'draft151cm' }, { 'title' => 'element151cm' }] }
assert_equal('draft151cm', @context['product.variants[0].title']) assert_equal 'draft151cm', @context['product.variants[0].title']
assert_equal('element151cm', @context['product.variants[1].title']) assert_equal 'element151cm', @context['product.variants[1].title']
assert_equal('draft151cm', @context['product.variants.first.title']) assert_equal 'draft151cm', @context['product.variants.first.title']
assert_equal('element151cm', @context['product.variants.last.title']) assert_equal 'element151cm', @context['product.variants.last.title']
end end
def test_cents def test_cents
@context.merge("cents" => HundredCentes.new) @context.merge("cents" => HundredCentes.new)
assert_equal(100, @context['cents']) assert_equal 100, @context['cents']
end end
def test_nested_cents def test_nested_cents
@context.merge("cents" => { 'amount' => HundredCentes.new }) @context.merge("cents" => { 'amount' => HundredCentes.new })
assert_equal(100, @context['cents.amount']) assert_equal 100, @context['cents.amount']
@context.merge("cents" => { 'cents' => { 'amount' => HundredCentes.new } }) @context.merge("cents" => { 'cents' => { 'amount' => HundredCentes.new } })
assert_equal(100, @context['cents.cents.amount']) assert_equal 100, @context['cents.cents.amount']
end end
def test_cents_through_drop def test_cents_through_drop
@context.merge("cents" => CentsDrop.new) @context.merge("cents" => CentsDrop.new)
assert_equal(100, @context['cents.amount']) assert_equal 100, @context['cents.amount']
end end
def test_nested_cents_through_drop def test_nested_cents_through_drop
@context.merge("vars" => { "cents" => CentsDrop.new }) @context.merge("vars" => { "cents" => CentsDrop.new })
assert_equal(100, @context['vars.cents.amount']) assert_equal 100, @context['vars.cents.amount']
end end
def test_drop_methods_with_question_marks def test_drop_methods_with_question_marks
@context.merge("cents" => CentsDrop.new) @context.merge("cents" => CentsDrop.new)
assert(@context['cents.non_zero?']) assert @context['cents.non_zero?']
end end
def test_context_from_within_drop def test_context_from_within_drop
@context.merge("test" => '123', "vars" => ContextSensitiveDrop.new) @context.merge("test" => '123', "vars" => ContextSensitiveDrop.new)
assert_equal('123', @context['vars.test']) assert_equal '123', @context['vars.test']
end end
def test_nested_context_from_within_drop def test_nested_context_from_within_drop
@context.merge("test" => '123', "vars" => { "local" => ContextSensitiveDrop.new }) @context.merge("test" => '123', "vars" => { "local" => ContextSensitiveDrop.new })
assert_equal('123', @context['vars.local.test']) assert_equal '123', @context['vars.local.test']
end end
def test_ranges def test_ranges
@context.merge("test" => '5') @context.merge("test" => '5')
assert_equal((1..5), @context['(1..5)']) assert_equal (1..5), @context['(1..5)']
assert_equal((1..5), @context['(1..test)']) assert_equal (1..5), @context['(1..test)']
assert_equal((5..5), @context['(test..test)']) assert_equal (5..5), @context['(test..test)']
end end
def test_cents_through_drop_nestedly def test_cents_through_drop_nestedly
@context.merge("cents" => { "cents" => CentsDrop.new }) @context.merge("cents" => { "cents" => CentsDrop.new })
assert_equal(100, @context['cents.cents.amount']) assert_equal 100, @context['cents.cents.amount']
@context.merge("cents" => { "cents" => { "cents" => CentsDrop.new } }) @context.merge("cents" => { "cents" => { "cents" => CentsDrop.new } })
assert_equal(100, @context['cents.cents.cents.amount']) assert_equal 100, @context['cents.cents.cents.amount']
end end
def test_drop_with_variable_called_only_once def test_drop_with_variable_called_only_once
@context['counter'] = CounterDrop.new @context['counter'] = CounterDrop.new
assert_equal(1, @context['counter.count']) assert_equal 1, @context['counter.count']
assert_equal(2, @context['counter.count']) assert_equal 2, @context['counter.count']
assert_equal(3, @context['counter.count']) assert_equal 3, @context['counter.count']
end end
def test_drop_with_key_called_only_once def test_drop_with_key_called_only_once
@context['counter'] = CounterDrop.new @context['counter'] = CounterDrop.new
assert_equal(1, @context['counter["count"]']) assert_equal 1, @context['counter["count"]']
assert_equal(2, @context['counter["count"]']) assert_equal 2, @context['counter["count"]']
assert_equal(3, @context['counter["count"]']) assert_equal 3, @context['counter["count"]']
end end
def test_proc_as_variable def test_proc_as_variable
@context['dynamic'] = proc { 'Hello' } @context['dynamic'] = proc { 'Hello' }
assert_equal('Hello', @context['dynamic']) assert_equal 'Hello', @context['dynamic']
end end
def test_lambda_as_variable def test_lambda_as_variable
@context['dynamic'] = proc { 'Hello' } @context['dynamic'] = proc { 'Hello' }
assert_equal('Hello', @context['dynamic']) assert_equal 'Hello', @context['dynamic']
end end
def test_nested_lambda_as_variable def test_nested_lambda_as_variable
@context['dynamic'] = { "lambda" => proc { 'Hello' } } @context['dynamic'] = { "lambda" => proc { 'Hello' } }
assert_equal('Hello', @context['dynamic.lambda']) assert_equal 'Hello', @context['dynamic.lambda']
end end
def test_array_containing_lambda_as_variable def test_array_containing_lambda_as_variable
@context['dynamic'] = [1, 2, proc { 'Hello' }, 4, 5] @context['dynamic'] = [1, 2, proc { 'Hello' }, 4, 5]
assert_equal('Hello', @context['dynamic[2]']) assert_equal 'Hello', @context['dynamic[2]']
end end
def test_lambda_is_called_once def test_lambda_is_called_once
@context['callcount'] = proc { @context['callcount'] = proc {
@global ||= 0 @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 @global = nil
end end
@@ -421,13 +421,13 @@ class ContextUnitTest < Minitest::Test
def test_nested_lambda_is_called_once def test_nested_lambda_is_called_once
@context['callcount'] = { "lambda" => proc { @context['callcount'] = { "lambda" => proc {
@global ||= 0 @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 @global = nil
end end
@@ -435,13 +435,13 @@ class ContextUnitTest < Minitest::Test
def test_lambda_in_array_is_called_once def test_lambda_in_array_is_called_once
@context['callcount'] = [1, 2, proc { @context['callcount'] = [1, 2, proc {
@global ||= 0 @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 @global = nil
end end
@@ -451,13 +451,13 @@ class ContextUnitTest < Minitest::Test
@context['magic'] = proc { @context.registers[:magic] } @context['magic'] = proc { @context.registers[:magic] }
assert_equal(345392, @context['magic']) assert_equal 345392, @context['magic']
end end
def test_to_liquid_and_context_at_first_level def test_to_liquid_and_context_at_first_level
@context['category'] = Category.new("foobar") @context['category'] = Category.new("foobar")
assert_kind_of(CategoryDrop, @context['category']) assert_kind_of CategoryDrop, @context['category']
assert_equal(@context, @context['category'].context) assert_equal @context, @context['category'].context
end end
def test_interrupt_avoids_object_allocations def test_interrupt_avoids_object_allocations
@@ -469,8 +469,8 @@ class ContextUnitTest < Minitest::Test
def test_context_initialization_with_a_proc_in_environment def test_context_initialization_with_a_proc_in_environment
contx = Context.new([test: ->(c) { c['poutine'] }], test: :foo) contx = Context.new([test: ->(c) { c['poutine'] }], test: :foo)
assert(contx) assert contx
assert_nil(contx['poutine']) assert_nil contx['poutine']
end end
def test_apply_global_filter def test_apply_global_filter
@@ -479,7 +479,7 @@ class ContextUnitTest < Minitest::Test
context = Context.new context = Context.new
context.global_filter = global_filter_proc context.global_filter = global_filter_proc
assert_equal('hi filtered', context.apply_global_filter('hi')) assert_equal 'hi filtered', context.apply_global_filter('hi')
end end
def test_static_environments_are_read_with_lower_priority_than_environments def test_static_environments_are_read_with_lower_priority_than_environments
@@ -488,13 +488,13 @@ class ContextUnitTest < Minitest::Test
environments: { 'shadowed' => 'dynamic' } environments: { 'shadowed' => 'dynamic' }
) )
assert_equal('dynamic', context['shadowed']) assert_equal 'dynamic', context['shadowed']
assert_equal('static', context['unshadowed']) assert_equal 'static', context['unshadowed']
end end
def test_apply_global_filter_when_no_global_filter_exist def test_apply_global_filter_when_no_global_filter_exist
context = Context.new context = Context.new
assert_equal('hi', context.apply_global_filter('hi')) assert_equal 'hi', context.apply_global_filter('hi')
end end
def test_new_isolated_subcontext_does_not_inherit_variables def test_new_isolated_subcontext_does_not_inherit_variables
@@ -502,28 +502,28 @@ class ContextUnitTest < Minitest::Test
super_context['my_variable'] = 'some value' super_context['my_variable'] = 'some value'
subcontext = super_context.new_isolated_subcontext subcontext = super_context.new_isolated_subcontext
assert_nil(subcontext['my_variable']) assert_nil subcontext['my_variable']
end end
def test_new_isolated_subcontext_inherits_static_environment def test_new_isolated_subcontext_inherits_static_environment
super_context = Context.build(static_environments: { 'my_environment_value' => 'my value' }) super_context = Context.build(static_environments: { 'my_environment_value' => 'my value' })
subcontext = super_context.new_isolated_subcontext subcontext = super_context.new_isolated_subcontext
assert_equal('my value', subcontext['my_environment_value']) assert_equal 'my value', subcontext['my_environment_value']
end end
def test_new_isolated_subcontext_inherits_resource_limits def test_new_isolated_subcontext_inherits_resource_limits
resource_limits = ResourceLimits.new({}) resource_limits = ResourceLimits.new({})
super_context = Context.new({}, {}, {}, false, resource_limits) super_context = Context.new({}, {}, {}, false, resource_limits)
subcontext = super_context.new_isolated_subcontext subcontext = super_context.new_isolated_subcontext
assert_equal(resource_limits, subcontext.resource_limits) assert_equal resource_limits, subcontext.resource_limits
end end
def test_new_isolated_subcontext_inherits_exception_renderer def test_new_isolated_subcontext_inherits_exception_renderer
super_context = Context.new super_context = Context.new
super_context.exception_renderer = ->(_e) { 'my exception message' } super_context.exception_renderer = ->(_e) { 'my exception message' }
subcontext = super_context.new_isolated_subcontext subcontext = super_context.new_isolated_subcontext
assert_equal('my exception message', subcontext.handle_error(Liquid::Error.new)) assert_equal 'my exception message', subcontext.handle_error(Liquid::Error.new)
end end
def test_new_isolated_subcontext_does_not_inherit_non_static_registers def test_new_isolated_subcontext_does_not_inherit_non_static_registers
@@ -532,21 +532,21 @@ class ContextUnitTest < Minitest::Test
} }
super_context = Context.new({}, {}, StaticRegisters.new(registers)) super_context = Context.new({}, {}, StaticRegisters.new(registers))
super_context.registers[:my_register] = :my_alt_value super_context.registers[:my_register] = :my_alt_value
subcontext = super_context.new_isolated_subcontext subcontext = super_context.new_isolated_subcontext
assert_equal(:my_value, subcontext.registers[:my_register]) assert_equal :my_value, subcontext.registers[:my_register]
end end
def test_new_isolated_subcontext_inherits_static_registers def test_new_isolated_subcontext_inherits_static_registers
super_context = Context.build(registers: { my_register: :my_value }) super_context = Context.build(registers: { my_register: :my_value })
subcontext = super_context.new_isolated_subcontext subcontext = super_context.new_isolated_subcontext
assert_equal(:my_value, subcontext.registers[:my_register]) assert_equal :my_value, subcontext.registers[:my_register]
end end
def test_new_isolated_subcontext_registers_do_not_pollute_context def test_new_isolated_subcontext_registers_do_not_pollute_context
super_context = Context.build(registers: { my_register: :my_value }) super_context = Context.build(registers: { my_register: :my_value })
subcontext = super_context.new_isolated_subcontext subcontext = super_context.new_isolated_subcontext
subcontext.registers[:my_register] = :my_alt_value subcontext.registers[:my_register] = :my_alt_value
assert_equal(:my_value, super_context.registers[:my_register]) assert_equal :my_value, super_context.registers[:my_register]
end end
def test_new_isolated_subcontext_inherits_filters def test_new_isolated_subcontext_inherits_filters
@@ -558,22 +558,22 @@ class ContextUnitTest < Minitest::Test
super_context = Context.new super_context = Context.new
super_context.add_filters([my_filter]) super_context.add_filters([my_filter])
subcontext = super_context.new_isolated_subcontext subcontext = super_context.new_isolated_subcontext
template = Template.parse('{{ 123 | my_filter }}') template = Template.parse('{{ 123 | my_filter }}')
assert_equal('my filter result', template.render(subcontext)) assert_equal 'my filter result', template.render(subcontext)
end end
private private
def assert_no_object_allocations def assert_no_object_allocations
unless RUBY_ENGINE == 'ruby' unless RUBY_ENGINE == 'ruby'
skip("stackprof needed to count object allocations") skip "stackprof needed to count object allocations"
end end
require 'stackprof' require 'stackprof'
profile = StackProf.run(mode: :object) do profile = StackProf.run(mode: :object) do
yield yield
end end
assert_equal(0, profile[:samples]) assert_equal 0, profile[:samples]
end end
end # ContextTest end # ContextTest

View File

@@ -13,8 +13,8 @@ class FileSystemUnitTest < Minitest::Test
def test_local def test_local
file_system = Liquid::LocalFileSystem.new("/some/path") file_system = Liquid::LocalFileSystem.new("/some/path")
assert_equal("/some/path/_mypartial.liquid", file_system.full_path("mypartial")) assert_equal "/some/path/_mypartial.liquid", file_system.full_path("mypartial")
assert_equal("/some/path/dir/_mypartial.liquid", file_system.full_path("dir/mypartial")) assert_equal "/some/path/dir/_mypartial.liquid", file_system.full_path("dir/mypartial")
assert_raises(FileSystemError) do assert_raises(FileSystemError) do
file_system.full_path("../dir/mypartial") file_system.full_path("../dir/mypartial")
@@ -31,7 +31,7 @@ class FileSystemUnitTest < Minitest::Test
def test_custom_template_filename_patterns def test_custom_template_filename_patterns
file_system = Liquid::LocalFileSystem.new("/some/path", "%s.html") file_system = Liquid::LocalFileSystem.new("/some/path", "%s.html")
assert_equal("/some/path/mypartial.html", file_system.full_path("mypartial")) assert_equal "/some/path/mypartial.html", file_system.full_path("mypartial")
assert_equal("/some/path/dir/mypartial.html", file_system.full_path("dir/mypartial")) assert_equal "/some/path/dir/mypartial.html", file_system.full_path("dir/mypartial")
end end
end # FileSystemTest end # FileSystemTest

View File

@@ -10,15 +10,15 @@ class I18nUnitTest < Minitest::Test
end end
def test_simple_translate_string def test_simple_translate_string
assert_equal("less is more", @i18n.translate("simple")) assert_equal "less is more", @i18n.translate("simple")
end end
def test_nested_translate_string def test_nested_translate_string
assert_equal("something wasn't right", @i18n.translate("errors.syntax.oops")) assert_equal "something wasn't right", @i18n.translate("errors.syntax.oops")
end end
def test_single_string_interpolation def test_single_string_interpolation
assert_equal("something different", @i18n.translate("whatever", something: "different")) assert_equal "something different", @i18n.translate("whatever", something: "different")
end end
# def test_raises_translation_error_on_undefined_interpolation_key # def test_raises_translation_error_on_undefined_interpolation_key
@@ -34,6 +34,6 @@ class I18nUnitTest < Minitest::Test
end end
def test_sets_default_path_to_en def test_sets_default_path_to_en
assert_equal(I18n::DEFAULT_LOCALE, I18n.new.path) assert_equal I18n::DEFAULT_LOCALE, I18n.new.path
end end
end end

View File

@@ -7,42 +7,42 @@ class LexerUnitTest < Minitest::Test
def test_strings def test_strings
tokens = Lexer.new(%( 'this is a test""' "wat 'lol'")).tokenize tokens = Lexer.new(%( 'this is a test""' "wat 'lol'")).tokenize
assert_equal([[:string, %('this is a test""')], [:string, %("wat 'lol'")], [:end_of_string]], tokens) assert_equal [[:string, %('this is a test""')], [:string, %("wat 'lol'")], [:end_of_string]], tokens
end end
def test_integer def test_integer
tokens = Lexer.new('hi 50').tokenize tokens = Lexer.new('hi 50').tokenize
assert_equal([[:id, 'hi'], [:number, '50'], [:end_of_string]], tokens) assert_equal [[:id, 'hi'], [:number, '50'], [:end_of_string]], tokens
end end
def test_float def test_float
tokens = Lexer.new('hi 5.0').tokenize tokens = Lexer.new('hi 5.0').tokenize
assert_equal([[:id, 'hi'], [:number, '5.0'], [:end_of_string]], tokens) assert_equal [[:id, 'hi'], [:number, '5.0'], [:end_of_string]], tokens
end end
def test_comparison def test_comparison
tokens = Lexer.new('== <> contains ').tokenize tokens = Lexer.new('== <> contains ').tokenize
assert_equal([[:comparison, '=='], [:comparison, '<>'], [:comparison, 'contains'], [:end_of_string]], tokens) assert_equal [[:comparison, '=='], [:comparison, '<>'], [:comparison, 'contains'], [:end_of_string]], tokens
end end
def test_specials def test_specials
tokens = Lexer.new('| .:').tokenize tokens = Lexer.new('| .:').tokenize
assert_equal([[:pipe, '|'], [:dot, '.'], [:colon, ':'], [:end_of_string]], tokens) assert_equal [[:pipe, '|'], [:dot, '.'], [:colon, ':'], [:end_of_string]], tokens
tokens = Lexer.new('[,]').tokenize tokens = Lexer.new('[,]').tokenize
assert_equal([[:open_square, '['], [:comma, ','], [:close_square, ']'], [:end_of_string]], tokens) assert_equal [[:open_square, '['], [:comma, ','], [:close_square, ']'], [:end_of_string]], tokens
end end
def test_fancy_identifiers def test_fancy_identifiers
tokens = Lexer.new('hi five?').tokenize tokens = Lexer.new('hi five?').tokenize
assert_equal([[:id, 'hi'], [:id, 'five?'], [:end_of_string]], tokens) assert_equal [[:id, 'hi'], [:id, 'five?'], [:end_of_string]], tokens
tokens = Lexer.new('2foo').tokenize tokens = Lexer.new('2foo').tokenize
assert_equal([[:number, '2'], [:id, 'foo'], [:end_of_string]], tokens) assert_equal [[:number, '2'], [:id, 'foo'], [:end_of_string]], tokens
end end
def test_whitespace def test_whitespace
tokens = Lexer.new("five|\n\t ==").tokenize tokens = Lexer.new("five|\n\t ==").tokenize
assert_equal([[:id, 'five'], [:pipe, '|'], [:comparison, '=='], [:end_of_string]], tokens) assert_equal [[:id, 'five'], [:pipe, '|'], [:comparison, '=='], [:end_of_string]], tokens
end end
def test_unexpected_character def test_unexpected_character

View File

@@ -7,72 +7,72 @@ class ParserUnitTest < Minitest::Test
def test_consume def test_consume
p = Parser.new("wat: 7") p = Parser.new("wat: 7")
assert_equal('wat', p.consume(:id)) assert_equal 'wat', p.consume(:id)
assert_equal(':', p.consume(:colon)) assert_equal ':', p.consume(:colon)
assert_equal('7', p.consume(:number)) assert_equal '7', p.consume(:number)
end end
def test_jump def test_jump
p = Parser.new("wat: 7") p = Parser.new("wat: 7")
p.jump(2) p.jump(2)
assert_equal('7', p.consume(:number)) assert_equal '7', p.consume(:number)
end end
def test_consume? def test_consume?
p = Parser.new("wat: 7") p = Parser.new("wat: 7")
assert_equal('wat', p.consume?(:id)) assert_equal 'wat', p.consume?(:id)
assert_equal(false, p.consume?(:dot)) assert_equal false, p.consume?(:dot)
assert_equal(':', p.consume(:colon)) assert_equal ':', p.consume(:colon)
assert_equal('7', p.consume?(:number)) assert_equal '7', p.consume?(:number)
end end
def test_id? def test_id?
p = Parser.new("wat 6 Peter Hegemon") p = Parser.new("wat 6 Peter Hegemon")
assert_equal('wat', p.id?('wat')) assert_equal 'wat', p.id?('wat')
assert_equal(false, p.id?('endgame')) assert_equal false, p.id?('endgame')
assert_equal('6', p.consume(:number)) assert_equal '6', p.consume(:number)
assert_equal('Peter', p.id?('Peter')) assert_equal 'Peter', p.id?('Peter')
assert_equal(false, p.id?('Achilles')) assert_equal false, p.id?('Achilles')
end end
def test_look def test_look
p = Parser.new("wat 6 Peter Hegemon") p = Parser.new("wat 6 Peter Hegemon")
assert_equal(true, p.look(:id)) assert_equal true, p.look(:id)
assert_equal('wat', p.consume(:id)) assert_equal 'wat', p.consume(:id)
assert_equal(false, p.look(:comparison)) assert_equal false, p.look(:comparison)
assert_equal(true, p.look(:number)) assert_equal true, p.look(:number)
assert_equal(true, p.look(:id, 1)) assert_equal true, p.look(:id, 1)
assert_equal(false, p.look(:number, 1)) assert_equal false, p.look(:number, 1)
end end
def test_expressions def test_expressions
p = Parser.new("hi.there hi?[5].there? hi.there.bob") p = Parser.new("hi.there hi?[5].there? hi.there.bob")
assert_equal('hi.there', p.expression) assert_equal 'hi.there', p.expression
assert_equal('hi?[5].there?', p.expression) assert_equal 'hi?[5].there?', p.expression
assert_equal('hi.there.bob', p.expression) assert_equal 'hi.there.bob', p.expression
p = Parser.new("567 6.0 'lol' \"wut\"") p = Parser.new("567 6.0 'lol' \"wut\"")
assert_equal('567', p.expression) assert_equal '567', p.expression
assert_equal('6.0', p.expression) assert_equal '6.0', p.expression
assert_equal("'lol'", p.expression) assert_equal "'lol'", p.expression
assert_equal('"wut"', p.expression) assert_equal '"wut"', p.expression
end end
def test_ranges def test_ranges
p = Parser.new("(5..7) (1.5..9.6) (young..old) (hi[5].wat..old)") p = Parser.new("(5..7) (1.5..9.6) (young..old) (hi[5].wat..old)")
assert_equal('(5..7)', p.expression) assert_equal '(5..7)', p.expression
assert_equal('(1.5..9.6)', p.expression) assert_equal '(1.5..9.6)', p.expression
assert_equal('(young..old)', p.expression) assert_equal '(young..old)', p.expression
assert_equal('(hi[5].wat..old)', p.expression) assert_equal '(hi[5].wat..old)', p.expression
end end
def test_arguments def test_arguments
p = Parser.new("filter: hi.there[5], keyarg: 7") p = Parser.new("filter: hi.there[5], keyarg: 7")
assert_equal('filter', p.consume(:id)) assert_equal 'filter', p.consume(:id)
assert_equal(':', p.consume(:colon)) assert_equal ':', p.consume(:colon)
assert_equal('hi.there[5]', p.argument) assert_equal 'hi.there[5]', p.argument
assert_equal(',', p.consume(:comma)) assert_equal ',', p.consume(:comma)
assert_equal('keyarg: 7', p.argument) assert_equal 'keyarg: 7', p.argument
end end
def test_invalid_expression def test_invalid_expression

View File

@@ -16,12 +16,12 @@ class PartialCacheUnitTest < Minitest::Test
parse_context: Liquid::ParseContext.new parse_context: Liquid::ParseContext.new
) )
assert_equal('my partial body', partial.render) assert_equal 'my partial body', partial.render
end end
def test_reads_from_the_file_system_only_once_per_file def test_reads_from_the_file_system_only_once_per_file
file_system = StubFileSystem.new('my_partial' => 'some partial body') file_system = StubFileSystem.new('my_partial' => 'some partial body')
context = Liquid::Context.build( context = Liquid::Context.build(
registers: { file_system: file_system } registers: { file_system: file_system }
) )
@@ -33,11 +33,11 @@ class PartialCacheUnitTest < Minitest::Test
) )
end end
assert_equal(1, file_system.file_read_count) assert_equal 1, file_system.file_read_count
end end
def test_cache_state_is_stored_per_context def test_cache_state_is_stored_per_context
parse_context = Liquid::ParseContext.new parse_context = Liquid::ParseContext.new
shared_file_system = StubFileSystem.new( shared_file_system = StubFileSystem.new(
'my_partial' => 'my shared value' 'my_partial' => 'my shared value'
) )
@@ -66,12 +66,12 @@ class PartialCacheUnitTest < Minitest::Test
parse_context: parse_context parse_context: parse_context
) )
assert_equal(2, shared_file_system.file_read_count) assert_equal 2, shared_file_system.file_read_count
end end
def test_cache_is_not_broken_when_a_different_parse_context_is_used def test_cache_is_not_broken_when_a_different_parse_context_is_used
file_system = StubFileSystem.new('my_partial' => 'some partial body') file_system = StubFileSystem.new('my_partial' => 'some partial body')
context = Liquid::Context.build( context = Liquid::Context.build(
registers: { file_system: file_system } registers: { file_system: file_system }
) )
@@ -88,41 +88,6 @@ class PartialCacheUnitTest < Minitest::Test
# Technically what we care about is that the file was parsed twice, # Technically what we care about is that the file was parsed twice,
# but measuring file reads is an OK proxy for this. # but measuring file reads is an OK proxy for this.
assert_equal(1, file_system.file_read_count) assert_equal 1, file_system.file_read_count
end
def test_uses_default_template_factory_when_no_template_factory_found_in_register
context = Liquid::Context.build(
registers: {
file_system: StubFileSystem.new('my_partial' => 'my partial body'),
}
)
partial = Liquid::PartialCache.load(
'my_partial',
context: context,
parse_context: Liquid::ParseContext.new
)
assert_equal('my partial body', partial.render)
end
def test_uses_template_factory_register_if_present
template_factory = StubTemplateFactory.new
context = Liquid::Context.build(
registers: {
file_system: StubFileSystem.new('my_partial' => 'my partial body'),
template_factory: template_factory,
}
)
partial = Liquid::PartialCache.load(
'my_partial',
context: context,
parse_context: Liquid::ParseContext.new
)
assert_equal('my partial body', partial.render)
assert_equal(1, template_factory.count)
end end
end end

View File

@@ -6,41 +6,41 @@ class RegexpUnitTest < Minitest::Test
include Liquid include Liquid
def test_empty def test_empty
assert_equal([], ''.scan(QuotedFragment)) assert_equal [], ''.scan(QuotedFragment)
end end
def test_quote def test_quote
assert_equal(['"arg 1"'], '"arg 1"'.scan(QuotedFragment)) assert_equal ['"arg 1"'], '"arg 1"'.scan(QuotedFragment)
end end
def test_words def test_words
assert_equal(['arg1', 'arg2'], 'arg1 arg2'.scan(QuotedFragment)) assert_equal ['arg1', 'arg2'], 'arg1 arg2'.scan(QuotedFragment)
end end
def test_tags def test_tags
assert_equal(['<tr>', '</tr>'], '<tr> </tr>'.scan(QuotedFragment)) assert_equal ['<tr>', '</tr>'], '<tr> </tr>'.scan(QuotedFragment)
assert_equal(['<tr></tr>'], '<tr></tr>'.scan(QuotedFragment)) assert_equal ['<tr></tr>'], '<tr></tr>'.scan(QuotedFragment)
assert_equal(['<style', 'class="hello">', '</style>'], %(<style class="hello">' </style>).scan(QuotedFragment)) assert_equal ['<style', 'class="hello">', '</style>'], %(<style class="hello">' </style>).scan(QuotedFragment)
end end
def test_double_quoted_words def test_double_quoted_words
assert_equal(['arg1', 'arg2', '"arg 3"'], 'arg1 arg2 "arg 3"'.scan(QuotedFragment)) assert_equal ['arg1', 'arg2', '"arg 3"'], 'arg1 arg2 "arg 3"'.scan(QuotedFragment)
end end
def test_single_quoted_words def test_single_quoted_words
assert_equal(['arg1', 'arg2', "'arg 3'"], 'arg1 arg2 \'arg 3\''.scan(QuotedFragment)) assert_equal ['arg1', 'arg2', "'arg 3'"], 'arg1 arg2 \'arg 3\''.scan(QuotedFragment)
end end
def test_quoted_words_in_the_middle def test_quoted_words_in_the_middle
assert_equal(['arg1', 'arg2', '"arg 3"', 'arg4'], 'arg1 arg2 "arg 3" arg4 '.scan(QuotedFragment)) assert_equal ['arg1', 'arg2', '"arg 3"', 'arg4'], 'arg1 arg2 "arg 3" arg4 '.scan(QuotedFragment)
end end
def test_variable_parser def test_variable_parser
assert_equal(['var'], 'var'.scan(VariableParser)) assert_equal ['var'], 'var'.scan(VariableParser)
assert_equal(['var', 'method'], 'var.method'.scan(VariableParser)) assert_equal ['var', 'method'], 'var.method'.scan(VariableParser)
assert_equal(['var', '[method]'], 'var[method]'.scan(VariableParser)) assert_equal ['var', '[method]'], 'var[method]'.scan(VariableParser)
assert_equal(['var', '[method]', '[0]'], 'var[method][0]'.scan(VariableParser)) assert_equal ['var', '[method]', '[0]'], 'var[method][0]'.scan(VariableParser)
assert_equal(['var', '["method"]', '[0]'], 'var["method"][0]'.scan(VariableParser)) assert_equal ['var', '["method"]', '[0]'], 'var["method"][0]'.scan(VariableParser)
assert_equal(['var', '[method]', '[0]', 'method'], 'var[method][0].method'.scan(VariableParser)) assert_equal ['var', '[method]', '[0]', 'method'], 'var[method][0].method'.scan(VariableParser)
end end
end # RegexpTest end # RegexpTest

View File

@@ -5,152 +5,244 @@ require 'test_helper'
class StaticRegistersUnitTest < Minitest::Test class StaticRegistersUnitTest < Minitest::Test
include Liquid include Liquid
def test_set def set
static_register = StaticRegisters.new(a: 1, b: 2) static_register = StaticRegisters.new
static_register[:b] = 22 static_register[nil] = true
static_register[:c] = 33 static_register[1] = :one
static_register[:one] = "one"
static_register["two"] = "three"
static_register["two"] = 3
static_register[false] = nil
assert_equal(1, static_register[:a]) assert_equal({ nil => true, 1 => :one, :one => "one", "two" => 3, false => nil }, static_register.registers)
assert_equal(22, static_register[:b])
assert_equal(33, static_register[:c]) static_register
end end
def test_get_missing_key def test_get
static_register = StaticRegisters.new static_register = set
assert_nil(static_register[:missing]) assert_equal true, static_register[nil]
assert_equal :one, static_register[1]
assert_equal "one", static_register[:one]
assert_equal 3, static_register["two"]
assert_nil static_register[false]
assert_nil static_register["unknown"]
end end
def test_delete def test_delete
static_register = StaticRegisters.new(a: 1, b: 2) static_register = set
static_register[:b] = 22
static_register[:c] = 33
assert_nil(static_register.delete(:a)) assert_equal true, static_register.delete(nil)
assert_equal :one, static_register.delete(1)
assert_equal "one", static_register.delete(:one)
assert_equal 3, static_register.delete("two")
assert_nil static_register.delete(false)
assert_nil static_register.delete("unknown")
assert_equal(22, static_register.delete(:b)) assert_equal({}, static_register.registers)
assert_equal(33, static_register.delete(:c))
assert_nil(static_register[:c])
assert_nil(static_register.delete(:d))
end end
def test_fetch def test_fetch
static_register = StaticRegisters.new(a: 1, b: 2) static_register = set
static_register[:b] = 22
static_register[:c] = 33
assert_equal(1, static_register.fetch(:a)) assert_equal true, static_register.fetch(nil)
assert_equal(1, static_register.fetch(:a, "default")) assert_equal :one, static_register.fetch(1)
assert_equal(22, static_register.fetch(:b)) assert_equal "one", static_register.fetch(:one)
assert_equal(22, static_register.fetch(:b, "default")) assert_equal 3, static_register.fetch("two")
assert_equal(33, static_register.fetch(:c)) assert_nil static_register.fetch(false)
assert_equal(33, static_register.fetch(:c, "default")) assert_nil static_register.fetch("unknown")
end
assert_raises(KeyError) do def test_fetch_default
static_register.fetch(:d) static_register = StaticRegisters.new
end
assert_equal("default", static_register.fetch(:d, "default"))
result = static_register.fetch(:d) { "default" } assert_equal true, static_register.fetch(nil, true)
assert_equal("default", result) assert_equal :one, static_register.fetch(1, :one)
assert_equal "one", static_register.fetch(:one, "one")
result = static_register.fetch(:d, "default 1") { "default 2" } assert_equal 3, static_register.fetch("two", 3)
assert_equal("default 2", result) assert_nil static_register.fetch(false, nil)
end end
def test_key def test_key
static_register = StaticRegisters.new(a: 1, b: 2) static_register = set
static_register[:b] = 22
static_register[:c] = 33
assert_equal(true, static_register.key?(:a)) assert_equal true, static_register.key?(nil)
assert_equal(true, static_register.key?(:b)) assert_equal true, static_register.key?(1)
assert_equal(true, static_register.key?(:c)) assert_equal true, static_register.key?(:one)
assert_equal(false, static_register.key?(:d)) assert_equal true, static_register.key?("two")
assert_equal true, static_register.key?(false)
assert_equal false, static_register.key?("unknown")
assert_equal false, static_register.key?(true)
end
def set_with_static
static_register = StaticRegisters.new(nil => true, 1 => :one, :one => "one", "two" => 3, false => nil)
static_register[nil] = false
static_register["two"] = 4
static_register[true] = "foo"
assert_equal({ nil => true, 1 => :one, :one => "one", "two" => 3, false => nil }, static_register.static)
assert_equal({ nil => false, "two" => 4, true => "foo" }, static_register.registers)
static_register
end
def test_get_with_static
static_register = set_with_static
assert_equal false, static_register[nil]
assert_equal :one, static_register[1]
assert_equal "one", static_register[:one]
assert_equal 4, static_register["two"]
assert_equal "foo", static_register[true]
assert_nil static_register[false]
end
def test_delete_with_static
static_register = set_with_static
assert_equal false, static_register.delete(nil)
assert_equal 4, static_register.delete("two")
assert_equal "foo", static_register.delete(true)
assert_nil static_register.delete("unknown")
assert_nil static_register.delete(:one)
assert_equal({}, static_register.registers)
assert_equal({ nil => true, 1 => :one, :one => "one", "two" => 3, false => nil }, static_register.static)
end
def test_fetch_with_static
static_register = set_with_static
assert_equal false, static_register.fetch(nil)
assert_equal :one, static_register.fetch(1)
assert_equal "one", static_register.fetch(:one)
assert_equal 4, static_register.fetch("two")
assert_equal "foo", static_register.fetch(true)
assert_nil static_register.fetch(false)
end
def test_key_with_static
static_register = set_with_static
assert_equal true, static_register.key?(nil)
assert_equal true, static_register.key?(1)
assert_equal true, static_register.key?(:one)
assert_equal true, static_register.key?("two")
assert_equal true, static_register.key?(false)
assert_equal false, static_register.key?("unknown")
assert_equal true, static_register.key?(true)
end end
def test_static_register_can_be_frozen def test_static_register_can_be_frozen
static_register = StaticRegisters.new(a: 1) static_register = set_with_static
static_register.static.freeze static = static_register.static.freeze
assert_raises(RuntimeError) do assert_raises(RuntimeError) do
static_register.static[:a] = "foo" static["two"] = "foo"
end end
assert_raises(RuntimeError) do assert_raises(RuntimeError) do
static_register.static[:b] = "foo" static["unknown"] = "foo"
end end
assert_raises(RuntimeError) do assert_raises(RuntimeError) do
static_register.static.delete(:a) static.delete("two")
end
assert_raises(RuntimeError) do
static_register.static.delete(:c)
end end
end end
def test_new_static_retains_static def test_new_static_retains_static
static_register = StaticRegisters.new(a: 1, b: 2) static_register = StaticRegisters.new(nil => true, 1 => :one, :one => "one", "two" => 3, false => nil)
static_register[:b] = 22 static_register["one"] = 1
static_register[:c] = 33 static_register["two"] = 2
static_register["three"] = 3
new_static_register = StaticRegisters.new(static_register) new_register = StaticRegisters.new(static_register)
new_static_register[:b] = 222 assert_equal({}, new_register.registers)
newest_static_register = StaticRegisters.new(new_static_register) new_register["one"] = 4
newest_static_register[:c] = 333 new_register["two"] = 5
new_register["three"] = 6
assert_equal(1, static_register[:a]) newest_register = StaticRegisters.new(new_register)
assert_equal(22, static_register[:b]) assert_equal({}, newest_register.registers)
assert_equal(33, static_register[:c])
assert_equal(1, new_static_register[:a]) newest_register["one"] = 7
assert_equal(222, new_static_register[:b]) newest_register["two"] = 8
assert_nil(new_static_register[:c]) newest_register["three"] = 9
assert_equal(1, newest_static_register[:a]) assert_equal({ "one" => 1, "two" => 2, "three" => 3 }, static_register.registers)
assert_equal(2, newest_static_register[:b]) assert_equal({ "one" => 4, "two" => 5, "three" => 6 }, new_register.registers)
assert_equal(333, newest_static_register[:c]) assert_equal({ "one" => 7, "two" => 8, "three" => 9 }, newest_register.registers)
assert_equal({ nil => true, 1 => :one, :one => "one", "two" => 3, false => nil }, static_register.static)
assert_equal({ nil => true, 1 => :one, :one => "one", "two" => 3, false => nil }, new_register.static)
assert_equal({ nil => true, 1 => :one, :one => "one", "two" => 3, false => nil }, newest_register.static)
end end
def test_multiple_instances_are_unique def test_multiple_instances_are_unique
static_register_1 = StaticRegisters.new(a: 1, b: 2) static_register = StaticRegisters.new(nil => true, 1 => :one, :one => "one", "two" => 3, false => nil)
static_register_1[:b] = 22 static_register["one"] = 1
static_register_1[:c] = 33 static_register["two"] = 2
static_register["three"] = 3
static_register_2 = StaticRegisters.new(a: 10, b: 20) new_register = StaticRegisters.new(foo: :bar)
static_register_2[:b] = 220 assert_equal({}, new_register.registers)
static_register_2[:c] = 330
assert_equal({ a: 1, b: 2 }, static_register_1.static) new_register["one"] = 4
assert_equal(1, static_register_1[:a]) new_register["two"] = 5
assert_equal(22, static_register_1[:b]) new_register["three"] = 6
assert_equal(33, static_register_1[:c])
assert_equal({ a: 10, b: 20 }, static_register_2.static) newest_register = StaticRegisters.new(bar: :foo)
assert_equal(10, static_register_2[:a]) assert_equal({}, newest_register.registers)
assert_equal(220, static_register_2[:b])
assert_equal(330, static_register_2[:c]) newest_register["one"] = 7
newest_register["two"] = 8
newest_register["three"] = 9
assert_equal({ "one" => 1, "two" => 2, "three" => 3 }, static_register.registers)
assert_equal({ "one" => 4, "two" => 5, "three" => 6 }, new_register.registers)
assert_equal({ "one" => 7, "two" => 8, "three" => 9 }, newest_register.registers)
assert_equal({ nil => true, 1 => :one, :one => "one", "two" => 3, false => nil }, static_register.static)
assert_equal({ foo: :bar }, new_register.static)
assert_equal({ bar: :foo }, newest_register.static)
end end
def test_initialization_reused_static_same_memory_object def test_can_update_static_directly_and_updates_all_instances
static_register_1 = StaticRegisters.new(a: 1, b: 2) static_register = StaticRegisters.new(nil => true, 1 => :one, :one => "one", "two" => 3, false => nil)
static_register_1[:b] = 22 static_register["one"] = 1
static_register_1[:c] = 33 static_register["two"] = 2
static_register["three"] = 3
static_register_2 = StaticRegisters.new(static_register_1) new_register = StaticRegisters.new(static_register)
assert_equal({}, new_register.registers)
assert_equal(1, static_register_2[:a]) assert_equal({ nil => true, 1 => :one, :one => "one", "two" => 3, false => nil }, static_register.static)
assert_equal(2, static_register_2[:b])
assert_nil(static_register_2[:c])
static_register_1.static[:b] = 222 new_register["one"] = 4
static_register_1.static[:c] = 333 new_register["two"] = 5
new_register["three"] = 6
new_register.static["four"] = 10
assert_same(static_register_1.static, static_register_2.static) newest_register = StaticRegisters.new(new_register)
assert_equal({}, newest_register.registers)
assert_equal({ nil => true, 1 => :one, :one => "one", "two" => 3, false => nil, "four" => 10 }, new_register.static)
newest_register["one"] = 7
newest_register["two"] = 8
newest_register["three"] = 9
new_register.static["four"] = 5
new_register.static["five"] = 15
assert_equal({ "one" => 1, "two" => 2, "three" => 3 }, static_register.registers)
assert_equal({ "one" => 4, "two" => 5, "three" => 6 }, new_register.registers)
assert_equal({ "one" => 7, "two" => 8, "three" => 9 }, newest_register.registers)
assert_equal({ nil => true, 1 => :one, :one => "one", "two" => 3, false => nil, "four" => 5, "five" => 15 }, newest_register.static)
assert_equal({ nil => true, 1 => :one, :one => "one", "two" => 3, false => nil, "four" => 5, "five" => 15 }, static_register.static)
assert_equal({ nil => true, 1 => :one, :one => "one", "two" => 3, false => nil, "four" => 5, "five" => 15 }, new_register.static)
end end
end end

View File

@@ -1,100 +0,0 @@
# frozen_string_literal: true
require 'test_helper'
class StrainerFactoryUnitTest < Minitest::Test
include Liquid
module AccessScopeFilters
def public_filter
"public"
end
def private_filter
"private"
end
private :private_filter
end
StrainerFactory.add_global_filter(AccessScopeFilters)
module LateAddedFilter
def late_added_filter(_input)
"filtered"
end
end
def setup
@context = Context.build
end
def test_strainer
strainer = StrainerFactory.create(@context)
assert_equal(5, strainer.invoke('size', 'input'))
assert_equal("public", strainer.invoke("public_filter"))
end
def test_stainer_raises_argument_error
strainer = StrainerFactory.create(@context)
assert_raises(Liquid::ArgumentError) do
strainer.invoke("public_filter", 1)
end
end
def test_stainer_argument_error_contains_backtrace
strainer = StrainerFactory.create(@context)
exception = assert_raises(Liquid::ArgumentError) do
strainer.invoke("public_filter", 1)
end
assert_match(
/\ALiquid error: wrong number of arguments \((1 for 0|given 1, expected 0)\)\z/,
exception.message
)
assert_equal(exception.backtrace[0].split(':')[0], __FILE__)
end
def test_strainer_only_invokes_public_filter_methods
strainer = StrainerFactory.create(@context)
assert_equal(false, strainer.class.invokable?('__test__'))
assert_equal(false, strainer.class.invokable?('test'))
assert_equal(false, strainer.class.invokable?('instance_eval'))
assert_equal(false, strainer.class.invokable?('__send__'))
assert_equal(true, strainer.class.invokable?('size')) # from the standard lib
end
def test_strainer_returns_nil_if_no_filter_method_found
strainer = StrainerFactory.create(@context)
assert_nil(strainer.invoke("private_filter"))
assert_nil(strainer.invoke("undef_the_filter"))
end
def test_strainer_returns_first_argument_if_no_method_and_arguments_given
strainer = StrainerFactory.create(@context)
assert_equal("password", strainer.invoke("undef_the_method", "password"))
end
def test_strainer_only_allows_methods_defined_in_filters
strainer = StrainerFactory.create(@context)
assert_equal("1 + 1", strainer.invoke("instance_eval", "1 + 1"))
assert_equal("puts", strainer.invoke("__send__", "puts", "Hi Mom"))
assert_equal("has_method?", strainer.invoke("invoke", "has_method?", "invoke"))
end
def test_strainer_uses_a_class_cache_to_avoid_method_cache_invalidation
a = Module.new
b = Module.new
strainer = StrainerFactory.create(@context, [a, b])
assert_kind_of(StrainerTemplate, strainer)
assert_kind_of(a, strainer)
assert_kind_of(b, strainer)
assert_kind_of(Liquid::StandardFilters, strainer)
end
def test_add_global_filter_clears_cache
assert_equal('input', StrainerFactory.create(@context).invoke('late_added_filter', 'input'))
StrainerFactory.add_global_filter(LateAddedFilter)
assert_equal('filtered', StrainerFactory.create(nil).invoke('late_added_filter', 'input'))
end
end

View File

@@ -1,82 +0,0 @@
# frozen_string_literal: true
require 'test_helper'
class StrainerTemplateUnitTest < Minitest::Test
include Liquid
def test_add_filter_when_wrong_filter_class
c = Context.new
s = c.strainer
wrong_filter = ->(v) { v.reverse }
exception = assert_raises(TypeError) do
s.class.add_filter(wrong_filter)
end
assert_equal(exception.message, "wrong argument type Proc (expected Module)")
end
module PrivateMethodOverrideFilter
private
def public_filter
"overriden as private"
end
end
def test_add_filter_raises_when_module_privately_overrides_registered_public_methods
strainer = Context.new.strainer
error = assert_raises(Liquid::MethodOverrideError) do
strainer.class.add_filter(PrivateMethodOverrideFilter)
end
assert_equal('Liquid error: Filter overrides registered public methods as non public: public_filter', error.message)
end
module ProtectedMethodOverrideFilter
protected
def public_filter
"overriden as protected"
end
end
def test_add_filter_raises_when_module_overrides_registered_public_method_as_protected
strainer = Context.new.strainer
error = assert_raises(Liquid::MethodOverrideError) do
strainer.class.add_filter(ProtectedMethodOverrideFilter)
end
assert_equal('Liquid error: Filter overrides registered public methods as non public: public_filter', error.message)
end
module PublicMethodOverrideFilter
def public_filter
"public"
end
end
def test_add_filter_does_not_raise_when_module_overrides_previously_registered_method
strainer = Context.new.strainer
with_global_filter do
strainer.class.add_filter(PublicMethodOverrideFilter)
assert(strainer.class.send(:filter_methods).include?('public_filter'))
end
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

View File

@@ -0,0 +1,167 @@
# frozen_string_literal: true
require 'test_helper'
class StrainerUnitTest < Minitest::Test
include Liquid
module AccessScopeFilters
def public_filter
"public"
end
def private_filter
"private"
end
private :private_filter
end
Strainer.global_filter(AccessScopeFilters)
def test_strainer
strainer = Strainer.create(nil)
assert_equal 5, strainer.invoke('size', 'input')
assert_equal "public", strainer.invoke("public_filter")
end
def test_stainer_raises_argument_error
strainer = Strainer.create(nil)
assert_raises(Liquid::ArgumentError) do
strainer.invoke("public_filter", 1)
end
end
def test_stainer_argument_error_contains_backtrace
strainer = Strainer.create(nil)
begin
strainer.invoke("public_filter", 1)
rescue Liquid::ArgumentError => e
assert_match(
/\ALiquid error: wrong number of arguments \((1 for 0|given 1, expected 0)\)\z/,
e.message
)
assert_equal e.backtrace[0].split(':')[0], __FILE__
end
end
def test_strainer_only_invokes_public_filter_methods
strainer = Strainer.create(nil)
assert_equal false, strainer.class.invokable?('__test__')
assert_equal false, strainer.class.invokable?('test')
assert_equal false, strainer.class.invokable?('instance_eval')
assert_equal false, strainer.class.invokable?('__send__')
assert_equal true, strainer.class.invokable?('size') # from the standard lib
end
def test_strainer_returns_nil_if_no_filter_method_found
strainer = Strainer.create(nil)
assert_nil strainer.invoke("private_filter")
assert_nil strainer.invoke("undef_the_filter")
end
def test_strainer_returns_first_argument_if_no_method_and_arguments_given
strainer = Strainer.create(nil)
assert_equal "password", strainer.invoke("undef_the_method", "password")
end
def test_strainer_only_allows_methods_defined_in_filters
strainer = Strainer.create(nil)
assert_equal "1 + 1", strainer.invoke("instance_eval", "1 + 1")
assert_equal "puts", strainer.invoke("__send__", "puts", "Hi Mom")
assert_equal "has_method?", strainer.invoke("invoke", "has_method?", "invoke")
end
def test_strainer_uses_a_class_cache_to_avoid_method_cache_invalidation
a = Module.new
b = Module.new
strainer = Strainer.create(nil, [a, b])
assert_kind_of Strainer, strainer
assert_kind_of a, strainer
assert_kind_of b, strainer
assert_kind_of Liquid::StandardFilters, strainer
end
def test_add_filter_when_wrong_filter_class
c = Context.new
s = c.strainer
wrong_filter = ->(v) { v.reverse }
assert_raises ArgumentError do
s.class.add_filter(wrong_filter)
end
end
module PrivateMethodOverrideFilter
private
def public_filter
"overriden as private"
end
end
def test_add_filter_raises_when_module_privately_overrides_registered_public_methods
strainer = Context.new.strainer
error = assert_raises(Liquid::MethodOverrideError) do
strainer.class.add_filter(PrivateMethodOverrideFilter)
end
assert_equal 'Liquid error: Filter overrides registered public methods as non public: public_filter', error.message
end
module ProtectedMethodOverrideFilter
protected
def public_filter
"overriden as protected"
end
end
def test_add_filter_raises_when_module_overrides_registered_public_method_as_protected
strainer = Context.new.strainer
error = assert_raises(Liquid::MethodOverrideError) do
strainer.class.add_filter(ProtectedMethodOverrideFilter)
end
assert_equal 'Liquid error: Filter overrides registered public methods as non public: public_filter', error.message
end
module PublicMethodOverrideFilter
def public_filter
"public"
end
end
def test_add_filter_does_not_raise_when_module_overrides_previously_registered_method
strainer = Context.new.strainer
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

View File

@@ -7,8 +7,8 @@ class TagUnitTest < Minitest::Test
def test_tag def test_tag
tag = Tag.parse('tag', "", Tokenizer.new(""), ParseContext.new) tag = Tag.parse('tag', "", Tokenizer.new(""), ParseContext.new)
assert_equal('liquid::tag', tag.name) assert_equal 'liquid::tag', tag.name
assert_equal('', tag.render(Context.new)) assert_equal '', tag.render(Context.new)
end end
def test_return_raw_text_of_tag def test_return_raw_text_of_tag
@@ -18,7 +18,7 @@ class TagUnitTest < Minitest::Test
def test_tag_name_should_return_name_of_the_tag def test_tag_name_should_return_name_of_the_tag
tag = Tag.parse("some_tag", "", Tokenizer.new(""), ParseContext.new) tag = Tag.parse("some_tag", "", Tokenizer.new(""), ParseContext.new)
assert_equal('some_tag', tag.tag_name) assert_equal 'some_tag', tag.tag_name
end end
def test_custom_tags_have_a_default_render_to_output_buffer_method_for_backwards_compatibility def test_custom_tags_have_a_default_render_to_output_buffer_method_for_backwards_compatibility
@@ -33,7 +33,7 @@ class TagUnitTest < Minitest::Test
assert_equal 'hello', template.render assert_equal 'hello', template.render
buf = +'' buf = +''
output = template.render({}, output: buf) output = template.render({}, output: buf)
assert_equal 'hello', output assert_equal 'hello', output
assert_equal 'hello', buf assert_equal 'hello', buf
@@ -51,7 +51,7 @@ class TagUnitTest < Minitest::Test
assert_equal 'foohellobar', template.render assert_equal 'foohellobar', template.render
buf = +'' buf = +''
output = template.render({}, output: buf) output = template.render({}, output: buf)
assert_equal 'foohellobar', output assert_equal 'foohellobar', output
assert_equal 'foohellobar', buf assert_equal 'foohellobar', buf

View File

@@ -7,6 +7,6 @@ class CaseTagUnitTest < Minitest::Test
def test_case_nodelist def test_case_nodelist
template = Liquid::Template.parse('{% case var %}{% when true %}WHEN{% else %}ELSE{% endcase %}') template = Liquid::Template.parse('{% case var %}{% when true %}WHEN{% else %}ELSE{% endcase %}')
assert_equal(['WHEN', 'ELSE'], template.root.nodelist[0].nodelist.map(&:nodelist).flatten) assert_equal ['WHEN', 'ELSE'], template.root.nodelist[0].nodelist.map(&:nodelist).flatten
end end
end end

View File

@@ -5,11 +5,11 @@ require 'test_helper'
class ForTagUnitTest < Minitest::Test class ForTagUnitTest < Minitest::Test
def test_for_nodelist def test_for_nodelist
template = Liquid::Template.parse('{% for item in items %}FOR{% endfor %}') template = Liquid::Template.parse('{% for item in items %}FOR{% endfor %}')
assert_equal(['FOR'], template.root.nodelist[0].nodelist.map(&:nodelist).flatten) assert_equal ['FOR'], template.root.nodelist[0].nodelist.map(&:nodelist).flatten
end end
def test_for_else_nodelist def test_for_else_nodelist
template = Liquid::Template.parse('{% for item in items %}FOR{% else %}ELSE{% endfor %}') template = Liquid::Template.parse('{% for item in items %}FOR{% else %}ELSE{% endfor %}')
assert_equal(['FOR', 'ELSE'], template.root.nodelist[0].nodelist.map(&:nodelist).flatten) assert_equal ['FOR', 'ELSE'], template.root.nodelist[0].nodelist.map(&:nodelist).flatten
end end
end end

View File

@@ -1,12 +0,0 @@
# frozen_string_literal: true
require 'test_helper'
class TemplateFactoryUnitTest < Minitest::Test
include Liquid
def test_for_returns_liquid_template_instance
template = TemplateFactory.new.for("anything")
assert_instance_of(Liquid::Template, template)
end
end

View File

@@ -8,7 +8,7 @@ class TemplateUnitTest < Minitest::Test
def test_sets_default_localization_in_document def test_sets_default_localization_in_document
t = Template.new t = Template.new
t.parse('{%comment%}{%endcomment%}') t.parse('{%comment%}{%endcomment%}')
assert_instance_of(I18n, t.root.nodelist[0].options[:locale]) assert_instance_of I18n, t.root.nodelist[0].options[:locale]
end end
def test_sets_default_localization_in_context_with_quick_initialization def test_sets_default_localization_in_context_with_quick_initialization
@@ -16,13 +16,13 @@ class TemplateUnitTest < Minitest::Test
t.parse('{%comment%}{%endcomment%}', locale: I18n.new(fixture("en_locale.yml"))) t.parse('{%comment%}{%endcomment%}', locale: I18n.new(fixture("en_locale.yml")))
locale = t.root.nodelist[0].options[:locale] locale = t.root.nodelist[0].options[:locale]
assert_instance_of(I18n, locale) assert_instance_of I18n, locale
assert_equal(fixture("en_locale.yml"), locale.path) assert_equal fixture("en_locale.yml"), locale.path
end end
def test_with_cache_classes_tags_returns_the_same_class def test_with_cache_classes_tags_returns_the_same_class
original_cache_setting = Liquid.cache_classes original_cache_setting = Liquid.cache_classes
Liquid.cache_classes = true Liquid.cache_classes = true
original_klass = Class.new original_klass = Class.new
Object.send(:const_set, :CustomTag, original_klass) Object.send(:const_set, :CustomTag, original_klass)
@@ -33,7 +33,7 @@ class TemplateUnitTest < Minitest::Test
new_klass = Class.new new_klass = Class.new
Object.send(:const_set, :CustomTag, new_klass) Object.send(:const_set, :CustomTag, new_klass)
assert(Template.tags['custom'].equal?(original_klass)) assert Template.tags['custom'].equal?(original_klass)
ensure ensure
Object.send(:remove_const, :CustomTag) Object.send(:remove_const, :CustomTag)
Template.tags.delete('custom') Template.tags.delete('custom')
@@ -42,7 +42,7 @@ class TemplateUnitTest < Minitest::Test
def test_without_cache_classes_tags_reloads_the_class def test_without_cache_classes_tags_reloads_the_class
original_cache_setting = Liquid.cache_classes original_cache_setting = Liquid.cache_classes
Liquid.cache_classes = false Liquid.cache_classes = false
original_klass = Class.new original_klass = Class.new
Object.send(:const_set, :CustomTag, original_klass) Object.send(:const_set, :CustomTag, original_klass)
@@ -53,7 +53,7 @@ class TemplateUnitTest < Minitest::Test
new_klass = Class.new new_klass = Class.new
Object.send(:const_set, :CustomTag, new_klass) Object.send(:const_set, :CustomTag, new_klass)
assert(Template.tags['custom'].equal?(new_klass)) assert Template.tags['custom'].equal?(new_klass)
ensure ensure
Object.send(:remove_const, :CustomTag) Object.send(:remove_const, :CustomTag)
Template.tags.delete('custom') Template.tags.delete('custom')
@@ -64,24 +64,17 @@ class TemplateUnitTest < Minitest::Test
def test_tags_delete def test_tags_delete
Template.register_tag('fake', FakeTag) Template.register_tag('fake', FakeTag)
assert_equal(FakeTag, Template.tags['fake']) assert_equal FakeTag, Template.tags['fake']
Template.tags.delete('fake') Template.tags.delete('fake')
assert_nil(Template.tags['fake']) assert_nil Template.tags['fake']
end end
def test_tags_can_be_looped_over def test_tags_can_be_looped_over
Template.register_tag('fake', FakeTag) Template.register_tag('fake', FakeTag)
result = Template.tags.map { |name, klass| [name, klass] } result = Template.tags.map { |name, klass| [name, klass] }
assert(result.include?(["fake", "TemplateUnitTest::FakeTag"])) assert result.include?(["fake", "TemplateUnitTest::FakeTag"])
ensure ensure
Template.tags.delete('fake') Template.tags.delete('fake')
end end
class TemplateSubclass < Liquid::Template
end
def test_template_inheritance
assert_equal("foo", TemplateSubclass.parse("foo").render)
end
end end

View File

@@ -4,37 +4,37 @@ require 'test_helper'
class TokenizerTest < Minitest::Test class TokenizerTest < Minitest::Test
def test_tokenize_strings def test_tokenize_strings
assert_equal([' '], tokenize(' ')) assert_equal [' '], tokenize(' ')
assert_equal(['hello world'], tokenize('hello world')) assert_equal ['hello world'], tokenize('hello world')
end end
def test_tokenize_variables def test_tokenize_variables
assert_equal(['{{funk}}'], tokenize('{{funk}}')) assert_equal ['{{funk}}'], tokenize('{{funk}}')
assert_equal([' ', '{{funk}}', ' '], tokenize(' {{funk}} ')) assert_equal [' ', '{{funk}}', ' '], tokenize(' {{funk}} ')
assert_equal([' ', '{{funk}}', ' ', '{{so}}', ' ', '{{brother}}', ' '], tokenize(' {{funk}} {{so}} {{brother}} ')) assert_equal [' ', '{{funk}}', ' ', '{{so}}', ' ', '{{brother}}', ' '], tokenize(' {{funk}} {{so}} {{brother}} ')
assert_equal([' ', '{{ funk }}', ' '], tokenize(' {{ funk }} ')) assert_equal [' ', '{{ funk }}', ' '], tokenize(' {{ funk }} ')
end end
def test_tokenize_blocks def test_tokenize_blocks
assert_equal(['{%comment%}'], tokenize('{%comment%}')) assert_equal ['{%comment%}'], tokenize('{%comment%}')
assert_equal([' ', '{%comment%}', ' '], tokenize(' {%comment%} ')) assert_equal [' ', '{%comment%}', ' '], tokenize(' {%comment%} ')
assert_equal([' ', '{%comment%}', ' ', '{%endcomment%}', ' '], tokenize(' {%comment%} {%endcomment%} ')) assert_equal [' ', '{%comment%}', ' ', '{%endcomment%}', ' '], tokenize(' {%comment%} {%endcomment%} ')
assert_equal([' ', '{% comment %}', ' ', '{% endcomment %}', ' '], tokenize(" {% comment %} {% endcomment %} ")) assert_equal [' ', '{% comment %}', ' ', '{% endcomment %}', ' '], tokenize(" {% comment %} {% endcomment %} ")
end end
def test_calculate_line_numbers_per_token_with_profiling def test_calculate_line_numbers_per_token_with_profiling
assert_equal([1], tokenize_line_numbers("{{funk}}")) assert_equal [1], tokenize_line_numbers("{{funk}}")
assert_equal([1, 1, 1], tokenize_line_numbers(" {{funk}} ")) assert_equal [1, 1, 1], tokenize_line_numbers(" {{funk}} ")
assert_equal([1, 2, 2], tokenize_line_numbers("\n{{funk}}\n")) assert_equal [1, 2, 2], tokenize_line_numbers("\n{{funk}}\n")
assert_equal([1, 1, 3], tokenize_line_numbers(" {{\n funk \n}} ")) assert_equal [1, 1, 3], tokenize_line_numbers(" {{\n funk \n}} ")
end end
private private
def tokenize(source) def tokenize(source)
tokenizer = Liquid::Tokenizer.new(source) tokenizer = Liquid::Tokenizer.new(source)
tokens = [] tokens = []
while (t = tokenizer.shift) while (t = tokenizer.shift)
tokens << t tokens << t
end end
@@ -42,7 +42,7 @@ class TokenizerTest < Minitest::Test
end end
def tokenize_line_numbers(source) def tokenize_line_numbers(source)
tokenizer = Liquid::Tokenizer.new(source, true) tokenizer = Liquid::Tokenizer.new(source, true)
line_numbers = [] line_numbers = []
loop do loop do
line_number = tokenizer.line_number line_number = tokenizer.line_number

Some files were not shown because too many files have changed in this diff Show More