Compare commits

...

48 Commits

Author SHA1 Message Date
Dylan Thacker-Smith
d5c31861f9 Fix theme runner for benchmarks 2020-08-18 10:04:42 -04:00
Dylan Thacker-Smith
895e63e40a Remove now unnecessary squash_instance_assigns_with_environments
Since it was just there to make sure instance assigns were overriden
when rending more than once with the same template instance.  It is
no longer necessary because the template no longer stores instance_assigns
which need to be overriden.
2020-08-18 10:04:42 -04:00
Dylan Thacker-Smith
219168e89f Remove mutable render state from Liquid::Template
Use Liquid::Context to access this mutable state after rendering or to have
state that is persisted between mutations
2020-08-18 10:04:41 -04:00
Thierry Joyal
a1d982ca76 Merge pull request #1272 from Shopify/StaticRegisters/add-test-coverage
[StaticRegisters] Add test coverage
2020-08-04 08:31:45 -04:00
Thierry Joyal
03be7f1ee3 [StaticRegisters] Add test coverage 2020-07-28 10:23:51 -04:00
Dylan Thacker-Smith
1ced4eaf10 Merge pull request #1268 from Shopify/remove-taint-checking
Remove support for taint checking
2020-07-25 21:27:46 -04:00
Dylan Thacker-Smith
4970167726 Bump rake development dependency
Gets rid of a deprecation warning when running the tests.
2020-07-23 16:23:18 -04:00
Dylan Thacker-Smith
065ccbc4aa Remove support for taint checking 2020-07-23 16:22:46 -04:00
Feken Baboyan
1feaa63813 Merge pull request #1258 from Shopify/fix-context-overriding-in-templates
Fix how Template overrides static registers when #render is invoked
2020-05-28 09:32:31 -04:00
Feken Baboyan
8541c6be35 make Template override static registers only when the register key is not defined 2020-05-28 09:08:03 -04:00
Thierry Joyal
18654526c8 Merge pull request #1257 from Shopify/StaticRegisters/remove-registers-attr-reader
[StaticRegisters] Remove registers attr_reader
2020-05-22 14:01:37 -04:00
Thierry Joyal
bd1f7f9492 [StaticRegisters] Remove assertion for delete to not remove static content 2020-05-22 13:42:44 -04:00
Thierry Joyal
40d75dd283 Update test/unit/static_registers_unit_test.rb
Co-authored-by: Dylan Thacker-Smith <dylan.smith@shopify.com>
2020-05-22 12:15:52 -04:00
Thierry Joyal
f5011365f1 [StaticRegisters] Remove registers attr_reader 2020-05-22 10:51:07 -04:00
Thierry Joyal
ebbd046c92 Merge pull request #1250 from Shopify/static-registers/fetch-raise-on-missing
[StaticRegisters] Fetch raise on missing
2020-05-22 09:56:05 -04:00
Thierry Joyal
b9979088ec [StaticRegisters] Fetch raise on missing
Co-authored-by: Dylan Thacker-Smith <dylan.smith@shopify.com>
2020-05-22 09:35:47 -04:00
Dylan Thacker-Smith
bd0e53bd2e Merge pull request #1239 from Shopify/remove-bad-arity-assumption
Fix ParseTreeVisitorTest for ruby-head
2020-05-21 14:02:04 -04:00
Thierry Joyal
4b586f4105 Merge pull request #1251 from Shopify/travis/optional-head
[Travis] Optional head
2020-05-21 13:51:11 -04:00
Thierry Joyal
0410119d5f [Travis] Optional head 2020-05-21 12:45:14 -04:00
Dylan Thacker-Smith
c2f67398d0 Allow ruby-head failures
Ignore an object allocation test failure on ruby-head for now.
2020-03-31 10:53:49 -04:00
Dylan Thacker-Smith
81149344a5 Fix ParseTreeVisitorTest for ruby-head 2020-03-31 10:53:46 -04:00
Dylan Thacker-Smith
e9b649b345 Fix Liquid::Template inheritance (#1227)
self.class.default_resource_limits would return `nil` in a subclass, since
the attribute isn't set on subclasses.
2020-01-21 15:09:22 -05:00
Celso Dantas
9c538f4237 Merge pull request #1207 from Shopify/moving-const-to-const
Use String literal instead
2020-01-20 12:37:57 -05:00
Celso Dantas
c08a358a2b Use String literal instead of using a class method
The class method string definition is not needed here, so it can be removed.
2020-01-16 09:42:32 -05:00
Justin Li
dbaef5e79b Merge pull request #1180 from Shopify/test-all-filters
Test all filters against random data to detect exceptions
2020-01-13 15:52:44 -05:00
Dylan Thacker-Smith
48a155a213 Initialize Liquid::Template class attributes eagerly instead of lazily (#1223) 2020-01-10 17:42:01 -05:00
Dylan Thacker-Smith
c69a9a77c6 Merge pull request #1215 from Shopify/nested-liquid-tag
Fix liquid tag nested in outer block
2020-01-09 19:24:52 -05:00
Dylan Thacker-Smith
ef79fa3898 style: Avoid deep nesting for parsing the liquid tag in the block body 2020-01-09 19:13:13 -05:00
Dylan Thacker-Smith
f7ad602bfc Fix liquid tag nested in outer block 2020-01-09 19:13:13 -05:00
Dylan Thacker-Smith
ffd6049ba2 Merge pull request #1222 from Shopify/bump-ci-ruby
Test against the latest ruby in CI
2020-01-09 18:12:36 -05:00
Dylan Thacker-Smith
b3ad54c0c2 Test against the latest ruby in CI 2020-01-09 13:15:34 -05:00
Dylan Thacker-Smith
67eca3f58d Upgrade rubocop and style guide for ruby 2.7 compatibility 2020-01-09 13:15:34 -05:00
Thierry Joyal
0847bf560f Merge pull request #1218 from Shopify/strainer/remove-safe-navigation
Remove handling of a nil context in the Strainer class
2020-01-07 11:54:49 -05:00
Dylan Thacker-Smith
8074565c3e Merge pull request #1216 from Shopify/unsupported-taint-mode
Remove support for taint_mode on ruby versions that don't support it
2020-01-07 10:12:36 -05:00
Thierry Joyal
24e81267b9 Merge pull request #1208 from Shopify/strainer/revisit
[Strainer] Separate factory from template
2020-01-06 13:56:22 -05:00
Thierry Joyal
c0ffee3ff9 [Strainer] Remove safe navigation 2020-01-06 18:34:13 +00:00
Thierry Joyal
c0ffeeef26 [Strainer] Separate factory from template 2020-01-06 17:45:25 +00:00
Dylan Thacker-Smith
22dbf90b7d Try to stay compatible with ruby-head 2019-12-19 11:12:55 -05:00
Dylan Thacker-Smith
40c68c9c83 Remove support for taint_mode on ruby versions that don't support it 2019-12-19 11:12:51 -05:00
Martin Morissette
b7f0f158ab Merge pull request #1212 from Shopify/template-factory
Introduce template factory
2019-12-19 08:52:22 -05:00
Martin Morissette
d8f31046a9 Introduce template factory 2019-12-17 21:45:08 -05:00
Martin Morissette
6c6382ed69 Merge pull request #1213 from Shopify/allow-failures-ruby-27
Make ruby 2.7 optional in CI
2019-12-16 09:46:12 -05:00
Martin Morissette
53ba1372f9 Make ruby 2.7 optional in CI 2019-12-16 08:59:26 -05:00
Mike Angell
57c9cf64eb Allow render to handle with and for correctly (#1193)
* Allow render to handle with and for correctly

* code improvements
2019-10-23 04:12:46 +10:00
Alessandro Diogo Brückheimer
e83b1e4159 Add ForceEqualSignAlignment to .rubocop.yml (#1190)
* Add ForceEqualSignAlignment to .rubocop.yml

* Revert ForceEqualSignAlignment cop

* Update method alignment

* Undo addition of whitespace to improve readability

* Fix missing alignment
2019-10-21 21:18:48 +10:00
Mike Angell
3784020a8d [New Feature] Add forloop inside render tag when using for syntax (#1191)
* Add forloop to render for syntax

* Remove forloop guard
2019-10-17 23:06:13 +10:00
Mike Angell
b0f8c2c03e Remove error logging 2019-10-05 01:13:35 +10:00
Mike Angell
37e40673ff Filter test 2019-10-04 17:00:54 +10:00
86 changed files with 1182 additions and 1129 deletions

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,5 +1,11 @@
# 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
### Fixed

View File

@@ -80,22 +80,24 @@ 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.
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 `errors` array of a `Liquid::Template` instance.
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.
Here are some examples:
```ruby
template = Liquid::Template.parse("{{x}} {{y}} {{z.a}} {{z.b}}")
template.render({ 'x' => 1, 'z' => { 'a' => 2 } }, { strict_variables: true })
context = Liquid::Context.new({ 'x' => 1, 'z' => { 'a' => 2 } })
template.render(context, { strict_variables: true })
#=> '1 2 ' # when a variable is undefined, it's rendered as nil
template.errors
context.errors
#=> [#<Liquid::UndefinedVariable: Liquid error: undefined variable y>, #<Liquid::UndefinedVariable: Liquid error: undefined variable b>]
```
```ruby
template = Liquid::Template.parse("{{x | filter1 | upcase}}")
template.render({ 'x' => 'foo' }, { strict_filters: true })
context = Liquid::Context.new({ 'x' => 'foo' })
template.render(context, { strict_filters: true })
#=> '' # when at least one filter in the filter chain is undefined, a whole expression is rendered as nil
template.errors
context.errors
#=> [#<Liquid::UndefinedFilter: Liquid error: undefined filter filter1>]
```
@@ -111,4 +113,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.
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|
t.libs << '.' << 'lib' << 'test'
t.test_files = FileList['test/{integration,unit}/**/*_test.rb']
t.verbose = false
t.verbose = false
end
desc('run test suite with warn error mode')

View File

@@ -12,16 +12,16 @@ class LiquidServlet < WEBrick::HTTPServlet::AbstractServlet
private
def handle(_type, req, res)
@request = req
@request = req
@response = res
@request.path_info =~ /(\w+)\z/
@action = Regexp.last_match(1) || 'index'
@action = Regexp.last_match(1) || 'index'
@assigns = send(@action) if respond_to?(@action)
@response['Content-Type'] = "text/html"
@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
def read_template(filename = @action)

View File

@@ -57,7 +57,8 @@ require 'liquid/forloop_drop'
require 'liquid/extensions'
require 'liquid/errors'
require 'liquid/interrupts'
require 'liquid/strainer'
require 'liquid/strainer_factory'
require 'liquid/strainer_template'
require 'liquid/expression'
require 'liquid/context'
require 'liquid/parser_switching'
@@ -80,6 +81,7 @@ require 'liquid/partial_cache'
require 'liquid/usage'
require 'liquid/register'
require 'liquid/static_registers'
require 'liquid/template_factory'
# Load all the tags of the standard library
#

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -64,7 +64,7 @@ module Liquid
end
def finish
@end_time = Time.now
@end_time = Time.now
@total_time = @end_time - @start_time
if @children.empty?
@@ -112,11 +112,11 @@ module Liquid
def initialize(partial_name = "<root>")
@partial_stack = [partial_name]
@root_timing = Timing.new("", current_partial)
@root_timing = Timing.new("", current_partial)
@timing_stack = [@root_timing]
@render_start_at = Time.now
@render_end_at = @render_start_at
@render_end_at = @render_start_at
end
def start

View File

@@ -4,7 +4,7 @@ module Liquid
class RangeLookup
def self.parse(start_markup, end_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)
new(start_obj, end_obj)
else
@@ -14,12 +14,12 @@ module Liquid
def initialize(start_obj, end_obj)
@start_obj = start_obj
@end_obj = end_obj
@end_obj = end_obj
end
def evaluate(context)
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
end

View File

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

View File

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

View File

@@ -13,7 +13,7 @@ module Liquid
"'" => '&#39;',
}.freeze
HTML_ESCAPE_ONCE_REGEXP = /["><']|&(?!([a-zA-Z]+|(#\d+));)/
STRIP_HTML_BLOCKS = Regexp.union(
STRIP_HTML_BLOCKS = Regexp.union(
%r{<script.*?</script>}m,
/<!--.*?-->/m,
%r{<style.*?</style>}m
@@ -41,7 +41,7 @@ module Liquid
end
def escape(input)
CGI.escapeHTML(input.to_s).untaint unless input.nil?
CGI.escapeHTML(input.to_s) unless input.nil?
end
alias_method :h, :escape
@@ -77,19 +77,24 @@ module Liquid
def truncate(input, length = 50, truncate_string = "...")
return if input.nil?
input_str = input.to_s
length = Utils.to_integer(length)
length = Utils.to_integer(length)
truncate_string_str = truncate_string.to_s
l = length - truncate_string_str.length
l = 0 if l < 0
input_str.length > length ? input_str[0...l].concat(truncate_string_str) : input_str
end
def truncatewords(input, words = 15, truncate_string = "...")
return if input.nil?
wordlist = input.to_s.split
words = Utils.to_integer(words)
words = Utils.to_integer(words)
l = words - 1
l = 0 if l < 0
wordlist.length > l ? wordlist[0..l].join(" ").concat(truncate_string.to_s) : input
end
@@ -115,7 +120,7 @@ module Liquid
end
def strip_html(input)
empty = ''
empty = ''
result = input.to_s.gsub(STRIP_HTML_BLOCKS, empty)
result.gsub!(STRIP_HTML_TAGS, empty)
result
@@ -471,7 +476,7 @@ module Liquid
def initialize(input, context)
@context = context
@input = if input.is_a?(Array)
@input = if input.is_a?(Array)
input.flatten
elsif input.is_a?(Hash)
[input]

View File

@@ -2,10 +2,10 @@
module Liquid
class StaticRegisters
attr_reader :static, :registers
attr_reader :static
def initialize(registers = {})
@static = registers.is_a?(StaticRegisters) ? registers.static : registers
@static = registers.is_a?(StaticRegisters) ? registers.static : registers
@registers = {}
end
@@ -25,8 +25,16 @@ module Liquid
@registers.delete(key)
end
def fetch(key, default = nil)
key?(key) ? self[key] : default
UNDEFINED = Object.new
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
def key?(key)

View File

@@ -1,68 +0,0 @@
# 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

@@ -0,0 +1,36 @@
# 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

@@ -0,0 +1,53 @@
# 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
def initialize(length, cols)
@length = length
@row = 1
@col = 1
@cols = cols
@index = 0
@row = 1
@col = 1
@cols = cols
@index = 0
end
attr_reader :length, :col, :row

View File

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

View File

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

View File

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

View File

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

View File

@@ -29,10 +29,10 @@ module Liquid
template_name = Regexp.last_match(1)
variable_name = Regexp.last_match(3)
@alias_name = Regexp.last_match(5)
@alias_name = Regexp.last_match(5)
@variable_name_expr = variable_name ? Expression.parse(variable_name) : nil
@template_name_expr = Expression.parse(template_name)
@attributes = {}
@attributes = {}
markup.scan(TagAttributes) do |key, value|
@attributes[key] = Expression.parse(value)
@@ -65,10 +65,10 @@ module Liquid
end
old_template_name = context.template_name
old_partial = context.partial
old_partial = context.partial
begin
context.template_name = template_name
context.partial = true
context.partial = true
context.stack do
@attributes.each do |key, value|
context[key] = context.evaluate(value)
@@ -86,7 +86,7 @@ module Liquid
end
ensure
context.template_name = old_template_name
context.partial = old_partial
context.partial = old_partial
end
output

View File

@@ -2,7 +2,8 @@
module Liquid
class Render < Tag
SYNTAX = /(#{QuotedString}+)(\s+(?:with|for)\s+(#{QuotedFragment}+))?(\s+(?:as)\s+(#{VariableSegment}+))?/o
FOR = 'for'
SYNTAX = /(#{QuotedString}+)(\s+(with|#{FOR})\s+(#{QuotedFragment}+))?(\s+(?:as)\s+(#{VariableSegment}+))?/o
disable_tags "include"
@@ -14,11 +15,13 @@ module Liquid
raise SyntaxError, options[:locale].t("errors.syntax.render") unless markup =~ SYNTAX
template_name = Regexp.last_match(1)
variable_name = Regexp.last_match(3)
with_or_for = Regexp.last_match(3)
variable_name = Regexp.last_match(4)
@alias_name = Regexp.last_match(5)
@alias_name = Regexp.last_match(6)
@variable_name_expr = variable_name ? Expression.parse(variable_name) : nil
@template_name_expr = Expression.parse(template_name)
@for = (with_or_for == FOR)
@attributes = {}
markup.scan(TagAttributes) do |key, value|
@@ -43,19 +46,27 @@ module Liquid
context_variable_name = @alias_name || template_name.split('/').last
render_partial_func = ->(var) {
inner_context = context.new_isolated_subcontext
render_partial_func = ->(var, forloop) {
inner_context = context.new_isolated_subcontext
inner_context.template_name = template_name
inner_context.partial = true
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
variable.is_a?(Array) ? variable.each(&render_partial_func) : render_partial_func.call(variable)
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
output
end

View File

@@ -9,9 +9,9 @@ module Liquid
def initialize(tag_name, markup, options)
super
if markup =~ Syntax
@variable_name = Regexp.last_match(1)
@variable_name = Regexp.last_match(1)
@collection_name = Expression.parse(Regexp.last_match(2))
@attributes = {}
@attributes = {}
markup.scan(TagAttributes) do |key, value|
@attributes[key] = Expression.parse(value)
end
@@ -24,11 +24,10 @@ module Liquid
(collection = context.evaluate(@collection_name)) || (return '')
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)
length = collection.length
length = collection.length
cols = context.evaluate(@attributes['cols']).to_i

View File

@@ -16,15 +16,13 @@ module Liquid
#
class Template
attr_accessor :root
attr_reader :resource_limits, :warnings
@@file_system = BlankFileSystem.new
attr_reader :warnings
class TagRegistry
include Enumerable
def initialize
@tags = {}
@tags = {}
@cache = {}
end
@@ -63,103 +61,63 @@ module Liquid
# :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.
# :strict will enforce correct syntax.
attr_writer :error_mode
# 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 :error_mode
Template.error_mode = :lax
attr_accessor :default_exception_renderer
Template.default_exception_renderer = lambda do |exception|
exception
end
def file_system
@@file_system
end
attr_accessor :file_system
Template.file_system = BlankFileSystem.new
def file_system=(obj)
@@file_system = obj
end
attr_accessor :tags
Template.tags = TagRegistry.new
private :tags=
def register_tag(name, klass)
tags[name.to_s] = klass
end
def tags
@tags ||= TagRegistry.new
end
attr_accessor :registers
Template.registers = {}
private :registers=
def add_register(name, klass)
registers[name.to_sym] = klass
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
# to all liquid views. Good for registering the standard library
def register_filter(mod)
Strainer.global_filter(mod)
StrainerFactory.add_global_filter(mod)
end
def default_resource_limits
@default_resource_limits ||= {}
end
attr_accessor :default_resource_limits
Template.default_resource_limits = {}
private :default_resource_limits=
# creates a new <tt>Template</tt> object from liquid source code
# To enable profiling, pass in <tt>profile: true</tt> as an option.
# See Liquid::Profiler for more information
def parse(source, options = {})
template = Template.new
template.parse(source, options)
new.parse(source, options)
end
end
def initialize
@rethrow_errors = false
@resource_limits = ResourceLimits.new(self.class.default_resource_limits)
end
# Parse source code.
# Returns self for easy chaining
def parse(source, options = {})
@options = options
@profiling = options[:profile]
@options = options
@profiling = options[:profile]
@line_numbers = options[:line_numbers] || @profiling
parse_context = options.is_a?(ParseContext) ? options : ParseContext.new(options)
@root = Document.parse(tokenize(source), parse_context)
@warnings = parse_context.warnings
@root = Document.parse(tokenize(source), parse_context)
@warnings = parse_context.warnings
self
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.
#
# if you use the same filters over and over again consider registering them globally
@@ -174,36 +132,17 @@ module Liquid
# * <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
#
def render(*args)
def render(assigns_or_context = nil, options = nil)
return '' if @root.nil?
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
context = coerce_context(assigns_or_context)
output = nil
context_register = context.registers.is_a?(StaticRegisters) ? context.registers.static : context.registers
case args.last
case options
when Hash
options = args.pop
output = options[:output] if options[:output]
options[:registers]&.each do |key, register|
@@ -212,11 +151,11 @@ module Liquid
apply_options_to_context(context, options)
when Module, Array
context.add_filters(args.pop)
context.add_filters(options)
end
Template.registers.each do |key, register|
context_register[key] = register
context_register[key] = register unless context_register.key?(key)
end
# Retrying a render resets resource usage
@@ -230,14 +169,15 @@ module Liquid
end
rescue Liquid::MemoryError => e
context.handle_error(e)
ensure
@errors = context.errors
end
end
def render!(*args)
@rethrow_errors = true
render(*args)
def render!(assigns_or_context = nil, options = nil)
context = coerce_context(assigns_or_context)
# rethrow errors
context.exception_renderer = ->(_e) { raise }
render(context, options)
end
def render_to_output_buffer(context, output)
@@ -246,6 +186,22 @@ module Liquid
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)
Tokenizer.new(source, @line_numbers)
end
@@ -269,10 +225,10 @@ module Liquid
def apply_options_to_context(context, options)
context.add_filters(options[:filters]) if options[:filters]
context.global_filter = options[:global_filter] if options[:global_filter]
context.global_filter = options[:global_filter] if options[:global_filter]
context.exception_renderer = options[:exception_renderer] if options[:exception_renderer]
context.strict_variables = options[:strict_variables] if options[:strict_variables]
context.strict_filters = options[:strict_filters] if options[:strict_filters]
context.strict_variables = options[:strict_variables] if options[:strict_variables]
context.strict_filters = options[:strict_filters] if options[:strict_filters]
end
end
end

View File

@@ -0,0 +1,9 @@
# 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
def initialize(source, line_numbers = false, line_number: nil, for_liquid_tag: false)
@source = source
@line_number = line_number || (line_numbers ? 1 : nil)
@source = source
@line_number = line_number || (line_numbers ? 1 : nil)
@for_liquid_tag = for_liquid_tag
@tokens = tokenize
@tokens = tokenize
end
def shift

View File

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

View File

@@ -12,10 +12,10 @@ module Liquid
# {{ user | link }}
#
class Variable
FilterMarkupRegex = /#{FilterSeparator}\s*(.*)/om
FilterParser = /(?:\s+|#{QuotedFragment}|#{ArgumentSeparator})+/o
FilterArgsRegex = /(?:#{FilterArgumentSeparator}|#{ArgumentSeparator})\s*((?:\w+\s*\:\s*)?#{QuotedFragment})/o
JustTagAttributes = /\A#{TagAttributes}\z/o
FilterMarkupRegex = /#{FilterSeparator}\s*(.*)/om
FilterParser = /(?:\s+|#{QuotedFragment}|#{ArgumentSeparator})+/o
FilterArgsRegex = /(?:#{FilterArgumentSeparator}|#{ArgumentSeparator})\s*((?:\w+\s*\:\s*)?#{QuotedFragment})/o
JustTagAttributes = /\A#{TagAttributes}\z/o
MarkupWithQuotedFragment = /(#{QuotedFragment})(.*)/om
attr_accessor :filters, :name, :line_number
@@ -25,10 +25,10 @@ module Liquid
include ParserSwitching
def initialize(markup, parse_context)
@markup = markup
@name = nil
@markup = markup
@name = nil
@parse_context = parse_context
@line_number = parse_context.line_number
@line_number = parse_context.line_number
parse_with_selected_parser(markup)
end
@@ -45,9 +45,9 @@ module Liquid
@filters = []
return unless markup =~ MarkupWithQuotedFragment
name_markup = Regexp.last_match(1)
name_markup = Regexp.last_match(1)
filter_markup = Regexp.last_match(2)
@name = Expression.parse(name_markup)
@name = Expression.parse(name_markup)
if filter_markup =~ FilterMarkupRegex
filters = Regexp.last_match(1).scan(FilterParser)
filters.each do |f|
@@ -86,9 +86,7 @@ module Liquid
context.invoke(filter_name, output, *filter_args)
end
obj = context.apply_global_filter(obj)
taint_check(context, obj)
obj
context.apply_global_filter(obj)
end
def render_to_output_buffer(context, output)
@@ -115,11 +113,11 @@ module Liquid
private
def parse_filter_expressions(filter_name, unparsed_args)
filter_args = []
filter_args = []
keyword_args = nil
unparsed_args.each do |a|
if (matches = a.match(JustTagAttributes))
keyword_args ||= {}
keyword_args ||= {}
keyword_args[matches[1]] = Expression.parse(matches[2])
else
filter_args << Expression.parse(a)
@@ -142,25 +140,6 @@ module Liquid
parsed_args
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
def children
[@node.name] + @node.filters.flatten

View File

@@ -3,7 +3,7 @@
module Liquid
class VariableLookup
SQUARE_BRACKETED = /\A\[(.*)\]\z/m
COMMAND_METHODS = ['size', 'first', 'last'].freeze
COMMAND_METHODS = ['size', 'first', 'last'].freeze
attr_reader :name, :lookups
@@ -20,7 +20,7 @@ module Liquid
end
@name = name
@lookups = lookups
@lookups = lookups
@command_flags = 0
@lookups.each_index do |i|
@@ -34,7 +34,7 @@ module Liquid
end
def evaluate(context)
name = context.evaluate(@name)
name = context.evaluate(@name)
object = context.find_variable(name)
@lookups.each_index do |i|
@@ -47,7 +47,7 @@ module Liquid
(object.respond_to?(:fetch) && key.is_a?(Integer)))
# 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
# 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.add_development_dependency('rake', '~> 11.3')
s.add_development_dependency('rake', '~> 13.0')
s.add_development_dependency('minitest')
end

View File

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

View File

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

View File

@@ -8,7 +8,7 @@ class Paginate < Liquid::Block
if markup =~ Syntax
@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
else
20

View File

@@ -60,7 +60,7 @@ module ShopFilter
case style
when 'original'
return '/files/shops/random_number/' + url
'/files/shops/random_number/' + url
when 'grande', 'large', 'medium', 'compact', 'small', 'thumb', 'icon'
"/files/shops/random_number/products/#{Regexp.last_match(1)}_#{style}.#{Regexp.last_match(2)}"
else

View File

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

View File

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

View File

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

View File

@@ -49,10 +49,6 @@ class ProductDrop < Liquid::Drop
ContextDrop.new
end
def user_input
(+"foo").taint
end
protected
def callmenot
@@ -114,32 +110,6 @@ class DropsTest < Minitest::Test
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
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.pretty_inspect }}").render!('product' => ProductDrop.new))
@@ -279,7 +249,7 @@ class DropsTest < Minitest::Test
end
def test_invokable_methods
assert_equal(%w(to_liquid catchall user_input context texts).to_set, ProductDrop.invokable_methods)
assert_equal(%w(to_liquid catchall 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 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)

View File

@@ -40,26 +40,29 @@ class ErrorHandlingTest < Minitest::Test
def test_standard_error
template = Liquid::Template.parse(' {{ errors.standard_error }} ')
assert_equal(' Liquid error: standard error ', template.render('errors' => ErrorDrop.new))
context = Liquid::Context.new('errors' => ErrorDrop.new)
assert_equal(' Liquid error: standard error ', template.render(context))
assert_equal(1, template.errors.size)
assert_equal(StandardError, template.errors.first.class)
assert_equal(1, context.errors.size)
assert_equal(StandardError, context.errors.first.class)
end
def test_syntax
template = Liquid::Template.parse(' {{ errors.syntax_error }} ')
assert_equal(' Liquid syntax error: syntax error ', template.render('errors' => ErrorDrop.new))
context = Liquid::Context.new('errors' => ErrorDrop.new)
assert_equal(' Liquid syntax error: syntax error ', template.render(context))
assert_equal(1, template.errors.size)
assert_equal(SyntaxError, template.errors.first.class)
assert_equal(1, context.errors.size)
assert_equal(SyntaxError, context.errors.first.class)
end
def test_argument
template = Liquid::Template.parse(' {{ errors.argument_error }} ')
assert_equal(' Liquid error: argument error ', template.render('errors' => ErrorDrop.new))
context = Liquid::Context.new('errors' => ErrorDrop.new)
assert_equal(' Liquid error: argument error ', template.render(context))
assert_equal(1, template.errors.size)
assert_equal(ArgumentError, template.errors.first.class)
assert_equal(1, context.errors.size)
assert_equal(ArgumentError, context.errors.first.class)
end
def test_missing_endtag_parse_time_error
@@ -78,9 +81,10 @@ class ErrorHandlingTest < Minitest::Test
def test_lax_unrecognized_operator
template = Liquid::Template.parse(' {% if 1 =! 2 %}ok{% endif %} ', error_mode: :lax)
assert_equal(' Liquid error: Unknown operator =! ', template.render)
assert_equal(1, template.errors.size)
assert_equal(Liquid::ArgumentError, template.errors.first.class)
context = Liquid::Context.new('errors' => ErrorDrop.new)
assert_equal(' Liquid error: Unknown operator =! ', template.render(context))
assert_equal(1, context.errors.size)
assert_equal(Liquid::ArgumentError, context.errors.first.class)
end
def test_with_line_numbers_adds_numbers_to_parser_errors
@@ -202,10 +206,11 @@ class ErrorHandlingTest < Minitest::Test
def test_default_exception_renderer_with_internal_error
template = Liquid::Template.parse('This is a runtime error: {{ errors.runtime_error }}', line_numbers: true)
output = template.render('errors' => ErrorDrop.new)
context = Liquid::Context.new('errors' => ErrorDrop.new)
output = template.render(context)
assert_equal('This is a runtime error: Liquid error (line 1): internal', output)
assert_equal([Liquid::InternalError], template.errors.map(&:class))
assert_equal([Liquid::InternalError], context.errors.map(&:class))
end
def test_setting_default_exception_renderer
@@ -217,27 +222,29 @@ class ErrorHandlingTest < Minitest::Test
}
template = Liquid::Template.parse('This is a runtime error: {{ errors.argument_error }}')
output = template.render('errors' => ErrorDrop.new)
context = Liquid::Context.new('errors' => ErrorDrop.new)
output = template.render(context)
assert_equal('This is a runtime error: ', output)
assert_equal([Liquid::ArgumentError], template.errors.map(&:class))
assert_equal([Liquid::ArgumentError], context.errors.map(&:class))
ensure
Liquid::Template.default_exception_renderer = old_exception_renderer if old_exception_renderer
end
def test_exception_renderer_exposing_non_liquid_error
template = Liquid::Template.parse('This is a runtime error: {{ errors.runtime_error }}', line_numbers: true)
template = Liquid::Template.parse('This is a runtime error: {{ errors.runtime_error }}', line_numbers: true)
exceptions = []
handler = ->(e) {
handler = ->(e) {
exceptions << e
e.cause
}
output = template.render({ 'errors' => ErrorDrop.new }, exception_renderer: handler)
context = Liquid::Context.new('errors' => ErrorDrop.new)
output = template.render(context, exception_renderer: handler)
assert_equal('This is a runtime error: runtime error', output)
assert_equal([Liquid::InternalError], exceptions.map(&:class))
assert_equal(exceptions, template.errors)
assert_equal(exceptions, context.errors)
assert_equal('#<RuntimeError: runtime error>', exceptions.first.cause.inspect)
end
@@ -250,14 +257,16 @@ class ErrorHandlingTest < Minitest::Test
def test_included_template_name_with_line_numbers
old_file_system = Liquid::Template.file_system
context = Liquid::Context.new('errors' => ErrorDrop.new)
begin
Liquid::Template.file_system = TestFileSystem.new
template = Liquid::Template.parse("Argument error:\n{% include 'product' %}", line_numbers: true)
page = template.render('errors' => ErrorDrop.new)
page = template.render(context)
ensure
Liquid::Template.file_system = old_file_system
end
assert_equal("Argument error:\nLiquid error (product line 1): argument error", page)
assert_equal("product", template.errors.first.template_name)
assert_equal("product", context.errors.first.template_name)
end
end

View File

@@ -72,10 +72,10 @@ class FiltersTest < Minitest::Test
end
def test_sort
@context['value'] = 3
@context['value'] = 3
@context['numbers'] = [2, 1, 4, 3]
@context['words'] = ['expected', 'as', 'alphabetic']
@context['arrays'] = ['flower', 'are']
@context['words'] = ['expected', 'as', 'alphabetic']
@context['arrays'] = ['flower', 'are']
@context['case_sensitive'] = ['sensitive', 'Expected', 'case']
assert_equal('1 2 3 4', Template.parse("{{numbers | sort | join}}").render(@context))
@@ -86,8 +86,8 @@ class FiltersTest < Minitest::Test
end
def test_sort_natural
@context['words'] = ['case', 'Assert', 'Insensitive']
@context['hashes'] = [{ 'a' => 'A' }, { 'a' => 'b' }, { 'a' => 'C' }]
@context['words'] = ['case', 'Assert', 'Insensitive']
@context['hashes'] = [{ 'a' => 'A' }, { 'a' => 'b' }, { 'a' => 'C' }]
@context['objects'] = [TestObject.new('A'), TestObject.new('b'), TestObject.new('C')]
# Test strings
@@ -101,8 +101,8 @@ class FiltersTest < Minitest::Test
end
def test_compact
@context['words'] = ['a', nil, 'b', nil, 'c']
@context['hashes'] = [{ 'a' => 'A' }, { 'a' => nil }, { 'a' => 'C' }]
@context['words'] = ['a', nil, 'b', nil, 'c']
@context['hashes'] = [{ 'a' => 'A' }, { 'a' => nil }, { 'a' => 'C' }]
@context['objects'] = [TestObject.new('A'), TestObject.new(nil), TestObject.new('C')]
# Test strings
@@ -141,9 +141,9 @@ class FiltersTest < Minitest::Test
def test_filter_with_keyword_arguments
@context['surname'] = 'john'
@context['input'] = 'hello %{first_name}, %{last_name}'
@context['input'] = 'hello %{first_name}, %{last_name}'
@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)
end

View File

@@ -61,63 +61,63 @@ class OutputTest < Minitest::Test
end
def test_variable_piping
text = %( {{ car.gm | make_funny }} )
text = %( {{ car.gm | make_funny }} )
expected = %( LOL )
assert_equal(expected, Template.parse(text).render!(@assigns, filters: [FunnyFilter]))
end
def test_variable_piping_with_input
text = %( {{ car.gm | cite_funny }} )
text = %( {{ car.gm | cite_funny }} )
expected = %( LOL: bad )
assert_equal(expected, Template.parse(text).render!(@assigns, filters: [FunnyFilter]))
end
def test_variable_piping_with_args
text = %! {{ car.gm | add_smiley : ':-(' }} !
text = %! {{ car.gm | add_smiley : ':-(' }} !
expected = %| bad :-( |
assert_equal(expected, Template.parse(text).render!(@assigns, filters: [FunnyFilter]))
end
def test_variable_piping_with_no_args
text = %( {{ car.gm | add_smiley }} )
text = %( {{ car.gm | add_smiley }} )
expected = %| bad :-) |
assert_equal(expected, Template.parse(text).render!(@assigns, filters: [FunnyFilter]))
end
def test_multiple_variable_piping_with_args
text = %! {{ car.gm | add_smiley : ':-(' | add_smiley : ':-('}} !
text = %! {{ car.gm | add_smiley : ':-(' | add_smiley : ':-('}} !
expected = %| bad :-( :-( |
assert_equal(expected, Template.parse(text).render!(@assigns, filters: [FunnyFilter]))
end
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> )
assert_equal(expected, Template.parse(text).render!(@assigns, filters: [FunnyFilter]))
end
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> )
assert_equal(expected, Template.parse(text).render!(@assigns, filters: [FunnyFilter]))
end
def test_multiple_pipings
text = %( {{ best_cars | cite_funny | paragraph }} )
text = %( {{ best_cars | cite_funny | paragraph }} )
expected = %( <p>LOL: bmw</p> )
assert_equal(expected, Template.parse(text).render!(@assigns, filters: [FunnyFilter]))
end
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> )
assert_equal(expected, Template.parse(text).render!(@assigns, filters: [FunnyFilter]))

View File

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

View File

@@ -72,7 +72,7 @@ class ParsingQuirksTest < Minitest::Test
def test_meaningless_parens_lax
with_error_mode(:lax) do
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)
end
end

View File

@@ -159,13 +159,13 @@ class RenderProfilingTest < Minitest::Test
t.render!("collection" => ["one", "two"])
leaf = t.profiler[0].children[0]
assert_operator leaf.self_time, :>, 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
assert_operator(t.profiler[0].total_time, :>, 0)
end
end

View File

@@ -16,28 +16,28 @@ class SecurityTest < Minitest::Test
end
def test_no_instance_eval
text = %( {{ '1+1' | instance_eval }} )
text = %( {{ '1+1' | instance_eval }} )
expected = %( 1+1 )
assert_equal(expected, Template.parse(text).render!(@assigns))
end
def test_no_existing_instance_eval
text = %( {{ '1+1' | __instance_eval__ }} )
text = %( {{ '1+1' | __instance_eval__ }} )
expected = %( 1+1 )
assert_equal(expected, Template.parse(text).render!(@assigns))
end
def test_no_instance_eval_after_mixing_in_new_filter
text = %( {{ '1+1' | instance_eval }} )
text = %( {{ '1+1' | instance_eval }} )
expected = %( 1+1 )
assert_equal(expected, Template.parse(text).render!(@assigns))
end
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 )
assert_equal(expected, Template.parse(text).render!(@assigns, filters: SecurityFilter))
@@ -68,13 +68,13 @@ class SecurityTest < Minitest::Test
def test_max_depth_nested_blocks_does_not_raise_exception
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!)
end
def test_more_than_max_depth_nested_blocks_raises_exception
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
Template.parse(code).render!
end

View File

@@ -207,7 +207,7 @@ class StandardFiltersTest < Minitest::Test
end
def test_sort_when_property_is_sometimes_missing_puts_nils_last
input = [
input = [
{ "price" => 4, "handle" => "alpha" },
{ "handle" => "beta" },
{ "price" => 1, "handle" => "gamma" },
@@ -235,7 +235,7 @@ class StandardFiltersTest < Minitest::Test
end
def test_sort_natural_when_property_is_sometimes_missing_puts_nils_last
input = [
input = [
{ "price" => "4", "handle" => "alpha" },
{ "handle" => "beta" },
{ "price" => "1", "handle" => "gamma" },
@@ -389,7 +389,7 @@ class StandardFiltersTest < Minitest::Test
def test_legacy_map_on_hashes_with_dynamic_key
template = "{% assign key = 'foo' %}{{ thing | map: key | map: 'bar' }}"
hash = { "foo" => { "bar" => 42 } }
hash = { "foo" => { "bar" => 42 } }
assert_template_result("42", template, "thing" => hash)
end
@@ -400,8 +400,8 @@ class StandardFiltersTest < Minitest::Test
end
def test_map_over_proc
drop = TestDrop.new
p = proc { drop }
drop = TestDrop.new
p = proc { drop }
templ = '{{ procs | map: "test" }}'
assert_template_result("testfoo", templ, "procs" => [p])
end
@@ -768,6 +768,49 @@ class StandardFiltersTest < Minitest::Test
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
def test_where_no_target_value
input = [
{ "foo" => false },
@@ -782,7 +825,7 @@ class StandardFiltersTest < Minitest::Test
private
def with_timezone(tz)
old_tz = ENV['TZ']
old_tz = ENV['TZ']
ENV['TZ'] = tz
yield
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
# block
def test_break_with_no_block
assigns = { 'i' => 1 }
markup = '{% break %}'
assigns = { 'i' => 1 }
markup = '{% break %}'
expected = ''
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
# block
def test_continue_with_no_block
assigns = {}
markup = '{% continue %}'
assigns = {}
markup = '{% continue %}'
expected = ''
assert_template_result(expected, markup, assigns)

View File

@@ -106,7 +106,7 @@ HERE
end
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
{% for i in array limit: true offset: 1 %}
{{ i }}
@@ -120,7 +120,7 @@ HERE
end
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
{% for i in array limit: 1 offset: true %}
{{ i }}
@@ -134,8 +134,8 @@ HERE
end
def test_dynamic_variable_limiting
assigns = { 'array' => [1, 2, 3, 4, 5, 6, 7, 8, 9, 0] }
assigns['limit'] = 2
assigns = { 'array' => [1, 2, 3, 4, 5, 6, 7, 8, 9, 0] }
assigns['limit'] = 2
assigns['offset'] = 2
assert_template_result('34', '{%for i in array limit: limit offset: offset %}{{ i }}{%endfor%}', assigns)
@@ -152,8 +152,8 @@ HERE
end
def test_pause_resume
assigns = { 'array' => { 'items' => [1, 2, 3, 4, 5, 6, 7, 8, 9, 0] } }
markup = <<-MKUP
assigns = { 'array' => { 'items' => [1, 2, 3, 4, 5, 6, 7, 8, 9, 0] } }
markup = <<-MKUP
{%for i in array.items limit: 3 %}{{i}}{%endfor%}
next
{%for i in array.items offset:continue limit: 3 %}{{i}}{%endfor%}
@@ -171,8 +171,8 @@ HERE
end
def test_pause_resume_limit
assigns = { 'array' => { 'items' => [1, 2, 3, 4, 5, 6, 7, 8, 9, 0] } }
markup = <<-MKUP
assigns = { 'array' => { 'items' => [1, 2, 3, 4, 5, 6, 7, 8, 9, 0] } }
markup = <<-MKUP
{%for i in array.items limit:3 %}{{i}}{%endfor%}
next
{%for i in array.items offset:continue limit:3 %}{{i}}{%endfor%}
@@ -190,8 +190,8 @@ HERE
end
def test_pause_resume_big_limit
assigns = { 'array' => { 'items' => [1, 2, 3, 4, 5, 6, 7, 8, 9, 0] } }
markup = <<-MKUP
assigns = { 'array' => { 'items' => [1, 2, 3, 4, 5, 6, 7, 8, 9, 0] } }
markup = <<-MKUP
{%for i in array.items limit:3 %}{{i}}{%endfor%}
next
{%for i in array.items offset:continue limit:3 %}{{i}}{%endfor%}
@@ -209,8 +209,8 @@ HERE
end
def test_pause_resume_big_offset
assigns = { 'array' => { 'items' => [1, 2, 3, 4, 5, 6, 7, 8, 9, 0] } }
markup = '{%for i in array.items limit:3 %}{{i}}{%endfor%}
assigns = { 'array' => { 'items' => [1, 2, 3, 4, 5, 6, 7, 8, 9, 0] } }
markup = '{%for i in array.items limit:3 %}{{i}}{%endfor%}
next
{%for i in array.items offset:continue limit:3 %}{{i}}{%endfor%}
next
@@ -226,26 +226,26 @@ HERE
def test_for_with_break
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 = ""
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"
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 = ""
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"
assert_template_result(expected, markup, assigns)
# tests to ensure it only breaks out of the local for loop
# and not all of them.
assigns = { 'array' => [[1, 2], [3, 4], [5, 6]] }
markup = '{% for item in array %}' \
assigns = { 'array' => [[1, 2], [3, 4], [5, 6]] }
markup = '{% for item in array %}' \
'{% for i in item %}' \
'{% if i == 1 %}' \
'{% break %}' \
@@ -257,8 +257,8 @@ HERE
assert_template_result(expected, markup, assigns)
# test break does nothing when unreached
assigns = { 'array' => { 'items' => [1, 2, 3, 4, 5] } }
markup = '{% for i in array.items %}{% if i == 9999 %}{% break %}{% endif %}{{ i }}{% endfor %}'
assigns = { 'array' => { 'items' => [1, 2, 3, 4, 5] } }
markup = '{% for i in array.items %}{% if i == 9999 %}{% break %}{% endif %}{{ i }}{% endfor %}'
expected = '12345'
assert_template_result(expected, markup, assigns)
end
@@ -266,29 +266,29 @@ HERE
def test_for_with_continue
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 = ""
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"
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 = ""
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"
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"
assert_template_result(expected, markup, assigns)
# tests to ensure it only continues the local for loop and not all of them.
assigns = { 'array' => [[1, 2], [3, 4], [5, 6]] }
markup = '{% for item in array %}' \
assigns = { 'array' => [[1, 2], [3, 4], [5, 6]] }
markup = '{% for item in array %}' \
'{% for i in item %}' \
'{% if i == 1 %}' \
'{% continue %}' \
@@ -300,8 +300,8 @@ HERE
assert_template_result(expected, markup, assigns)
# test continue does nothing when unreached
assigns = { 'array' => { 'items' => [1, 2, 3, 4, 5] } }
markup = '{% for i in array.items %}{% if i == 9999 %}{% continue %}{% endif %}{{ i }}{% endfor %}'
assigns = { 'array' => { 'items' => [1, 2, 3, 4, 5] } }
markup = '{% for i in array.items %}{% if i == 9999 %}{% continue %}{% endif %}{{ i }}{% endfor %}'
expected = '12345'
assert_template_result(expected, markup, assigns)
end
@@ -389,8 +389,8 @@ HERE
end
def test_iterate_with_each_when_no_limit_applied
loader = LoaderDrop.new([1, 2, 3, 4, 5])
assigns = { 'items' => loader }
loader = LoaderDrop.new([1, 2, 3, 4, 5])
assigns = { 'items' => loader }
expected = '12345'
template = '{% for item in items %}{{item}}{% endfor %}'
assert_template_result(expected, template, assigns)
@@ -399,8 +399,8 @@ HERE
end
def test_iterate_with_load_slice_when_limit_applied
loader = LoaderDrop.new([1, 2, 3, 4, 5])
assigns = { 'items' => loader }
loader = LoaderDrop.new([1, 2, 3, 4, 5])
assigns = { 'items' => loader }
expected = '1'
template = '{% for item in items limit:1 %}{{item}}{% endfor %}'
assert_template_result(expected, template, assigns)
@@ -409,8 +409,8 @@ HERE
end
def test_iterate_with_load_slice_when_limit_and_offset_applied
loader = LoaderDrop.new([1, 2, 3, 4, 5])
assigns = { 'items' => loader }
loader = LoaderDrop.new([1, 2, 3, 4, 5])
assigns = { 'items' => loader }
expected = '34'
template = '{% for item in items offset:2 limit:2 %}{{item}}{% endfor %}'
assert_template_result(expected, template, assigns)
@@ -419,11 +419,11 @@ HERE
end
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 }
array_assigns = { 'items' => [1, 2, 3, 4, 5] }
expected = '34'
template = '{% for item in items offset:2 limit:2 %}{{item}}{% endfor %}'
array_assigns = { 'items' => [1, 2, 3, 4, 5] }
expected = '34'
template = '{% for item in items offset:2 limit:2 %}{{item}}{% endfor %}'
assert_template_result(expected, template, loader_assigns)
assert_template_result(expected, template, array_assigns)
end

View File

@@ -45,7 +45,7 @@ class IfElseTagTest < Minitest::Test
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"
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)
end

View File

@@ -54,7 +54,7 @@ class CountingFileSystem
attr_reader :count
def read_template_file(_template_path)
@count ||= 0
@count += 1
@count += 1
'from CountingFileSystem'
end
end
@@ -217,8 +217,9 @@ class IncludeTagTest < Minitest::Test
Liquid::Template.file_system = TestFileSystem.new
a = Liquid::Template.parse(' {% include "nested_template" %}')
a.render!
assert_empty(a.errors)
context = Liquid::Context.new
a.render!(context)
assert_empty(context.errors)
end
def test_passing_options_to_included_templates
@@ -257,9 +258,10 @@ class IncludeTagTest < Minitest::Test
def test_including_with_strict_variables
template = Liquid::Template.parse("{% include 'simple' %}", error_mode: :warn)
template.render(nil, strict_variables: true)
context = Liquid::Context.new
template.render(context, strict_variables: true)
assert_equal([], template.errors)
assert_equal([], context.errors)
end
def test_break_through_include

View File

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

View File

@@ -42,32 +42,6 @@ class RenderTagTest < Minitest::Test
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
def test_render_does_not_mutate_parent_scope
Liquid::Template.file_system = StubFileSystem.new('snippet' => '{% assign inner = 1 %}')
assert_template_result('', "{% render 'snippet' %}{{ inner }}")
@@ -205,4 +179,31 @@ class RenderTagTest < Minitest::Test
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

View File

@@ -174,7 +174,7 @@ class StandardTagTest < Minitest::Test
def test_assign_from_case
# 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)
assert_equal("menswear", template.render!("collection" => { 'handle' => 'menswear-jackets' }))
assert_equal("menswear", template.render!("collection" => { 'handle' => 'menswear-t-shirts' }))

View File

@@ -38,12 +38,6 @@ end
class TemplateTest < Minitest::Test
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
str = "false"
100.times do
@@ -54,48 +48,20 @@ class TemplateTest < Minitest::Test
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
def test_custom_assigns_do_not_persist_on_same_template
t = Template.new
assert_equal('from custom assigns', t.parse("{{ foo }}").render!('foo' => 'from custom assigns'))
assert_equal('', t.parse("{{ foo }}").render!)
end
def test_custom_assigns_squash_instance_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
def test_lambda_is_called_once_from_custom_assigns_over_multiple_parses_and_renders
t = Template.new
assigns = { 'number' => -> {
@global ||= 0
@global += 1
@global += 1
} }
assert_equal('1', t.parse("{{number}}").render!(assigns))
assert_equal('1', t.parse("{{number}}").render!(assigns))
@@ -105,112 +71,124 @@ class TemplateTest < Minitest::Test
def test_resource_limits_works_with_custom_length_method
t = Template.parse("{% assign foo = bar %}")
t.resource_limits.render_length_limit = 42
assert_equal("", t.render!("bar" => SomethingWithLength.new))
context = Liquid::Context.new("bar" => SomethingWithLength.new)
context.resource_limits.render_length_limit = 42
assert_equal("", t.render!(context))
end
def test_resource_limits_render_length
t = Template.parse("0123456789")
t.resource_limits.render_length_limit = 5
assert_equal("Liquid error: Memory limits exceeded", t.render)
assert(t.resource_limits.reached?)
context = Liquid::Context.new
context.resource_limits.render_length_limit = 5
assert_equal("Liquid error: Memory limits exceeded", t.render(context))
assert(context.resource_limits.reached?)
t.resource_limits.render_length_limit = 10
context.resource_limits.render_length_limit = 10
assert_equal("0123456789", t.render!)
refute_nil(t.resource_limits.render_length)
refute_nil(context.resource_limits.render_length)
end
def test_resource_limits_render_score
t = Template.parse("{% for a in (1..10) %} {% for a in (1..10) %} foo {% endfor %} {% endfor %}")
t.resource_limits.render_score_limit = 50
assert_equal("Liquid error: Memory limits exceeded", t.render)
assert(t.resource_limits.reached?)
context = Liquid::Context.new
context.resource_limits.render_score_limit = 50
assert_equal("Liquid error: Memory limits exceeded", t.render(context))
assert(context.resource_limits.reached?)
t = Template.parse("{% for a in (1..100) %} foo {% endfor %}")
t.resource_limits.render_score_limit = 50
assert_equal("Liquid error: Memory limits exceeded", t.render)
assert(t.resource_limits.reached?)
context.resource_limits.render_score_limit = 50
assert_equal("Liquid error: Memory limits exceeded", t.render(context))
assert(context.resource_limits.reached?)
t.resource_limits.render_score_limit = 200
assert_equal((" foo " * 100), t.render!)
refute_nil(t.resource_limits.render_score)
context.resource_limits.render_score_limit = 200
assert_equal((" foo " * 100), t.render!(context))
refute_nil(context.resource_limits.render_score)
end
def test_resource_limits_assign_score
t = Template.parse("{% assign foo = 42 %}{% assign bar = 23 %}")
t.resource_limits.assign_score_limit = 1
assert_equal("Liquid error: Memory limits exceeded", t.render)
assert(t.resource_limits.reached?)
context = Liquid::Context.new
context.resource_limits.assign_score_limit = 1
assert_equal("Liquid error: Memory limits exceeded", t.render(context))
assert(context.resource_limits.reached?)
t.resource_limits.assign_score_limit = 2
assert_equal("", t.render!)
refute_nil(t.resource_limits.assign_score)
context.resource_limits.assign_score_limit = 2
assert_equal("", t.render!(context))
refute_nil(context.resource_limits.assign_score)
end
def test_resource_limits_assign_score_counts_bytes_not_characters
t = Template.parse("{% assign foo = 'すごい' %}")
t.render
assert_equal(9, t.resource_limits.assign_score)
context = Liquid::Context.new
t.render(context)
assert_equal(9, context.resource_limits.assign_score)
t = Template.parse("{% capture foo %}すごい{% endcapture %}")
t.render
assert_equal(9, t.resource_limits.assign_score)
t.render(context)
assert_equal(9, context.resource_limits.assign_score)
end
def test_resource_limits_assign_score_nested
t = Template.parse("{% assign foo = 'aaaa' | reverse %}")
t.resource_limits.assign_score_limit = 3
assert_equal("Liquid error: Memory limits exceeded", t.render)
assert(t.resource_limits.reached?)
context = Liquid::Context.new
context.resource_limits.assign_score_limit = 3
assert_equal("Liquid error: Memory limits exceeded", t.render(context))
assert(context.resource_limits.reached?)
t.resource_limits.assign_score_limit = 5
assert_equal("", t.render!)
context.resource_limits.assign_score_limit = 5
assert_equal("", t.render!(context))
end
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.resource_limits.render_score_limit = 50
assert_equal("Liquid error: Memory limits exceeded", t.render)
assert(t.resource_limits.reached?)
context = Liquid::Context.new
context.resource_limits.render_score_limit = 50
assert_equal("Liquid error: Memory limits exceeded", t.render(context))
assert(context.resource_limits.reached?)
end
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.render!
assert(t.resource_limits.assign_score > 0)
assert(t.resource_limits.render_score > 0)
assert(t.resource_limits.render_length > 0)
context = Liquid::Context.new
t.render!(context)
assert(context.resource_limits.assign_score > 0)
assert(context.resource_limits.render_score > 0)
assert(context.resource_limits.render_length > 0)
end
def test_render_length_persists_between_blocks
t = Template.parse("{% if true %}aaaa{% endif %}")
t.resource_limits.render_length_limit = 7
assert_equal("Liquid error: Memory limits exceeded", t.render)
t.resource_limits.render_length_limit = 8
assert_equal("aaaa", t.render)
context = Liquid::Context.new
context.resource_limits.render_length_limit = 7
assert_equal("Liquid error: Memory limits exceeded", t.render(context))
context.resource_limits.render_length_limit = 8
assert_equal("aaaa", t.render(context))
t = Template.parse("{% if true %}aaaa{% endif %}{% if true %}bbb{% endif %}")
t.resource_limits.render_length_limit = 13
assert_equal("Liquid error: Memory limits exceeded", t.render)
t.resource_limits.render_length_limit = 14
assert_equal("aaaabbb", t.render)
context = Liquid::Context.new
context.resource_limits.render_length_limit = 13
assert_equal("Liquid error: Memory limits exceeded", t.render(context))
context.resource_limits.render_length_limit = 14
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.resource_limits.render_length_limit = 5
assert_equal("Liquid error: Memory limits exceeded", t.render)
t.resource_limits.render_length_limit = 11
assert_equal("Liquid error: Memory limits exceeded", t.render)
t.resource_limits.render_length_limit = 12
assert_equal("ababab", t.render)
context = Liquid::Context.new
context.resource_limits.render_length_limit = 5
assert_equal("Liquid error: Memory limits exceeded", t.render(context))
context.resource_limits.render_length_limit = 11
assert_equal("Liquid error: Memory limits exceeded", t.render(context))
context.resource_limits.render_length_limit = 12
assert_equal("ababab", t.render(context))
end
def test_render_length_uses_number_of_bytes_not_characters
t = Template.parse("{% if true %}すごい{% endif %}")
t.resource_limits.render_length_limit = 10
assert_equal("Liquid error: Memory limits exceeded", t.render)
t.resource_limits.render_length_limit = 18
assert_equal("すごい", t.render)
context = Liquid::Context.new
context.resource_limits.render_length_limit = 10
assert_equal("Liquid error: Memory limits exceeded", t.render(context))
context.resource_limits.render_length_limit = 18
assert_equal("すごい", t.render(context))
end
def test_default_resource_limits_unaffected_by_render_with_context
@@ -224,11 +202,12 @@ class TemplateTest < Minitest::Test
def test_can_use_drop_as_context
t = Template.new
t.registers['lulz'] = 'haha'
drop = TemplateContextDrop.new
assert_equal('fizzbuzz', t.parse('{{foo}}').render!(drop))
assert_equal('bar', t.parse('{{bar}}').render!(drop))
assert_equal('haha', t.parse("{{baz}}").render!(drop))
context = Liquid::Context.build(environments: drop, registers: { 'lulz' => 'haha' })
drop.context = context
assert_equal('fizzbuzz', t.parse('{{foo}}').render!(context))
assert_equal('bar', t.parse('{{bar}}').render!(context))
assert_equal('haha', t.parse("{{baz}}").render!(context))
end
def test_render_bang_force_rethrow_errors_on_passed_context
@@ -243,7 +222,7 @@ class TemplateTest < Minitest::Test
def test_exception_renderer_that_returns_string
exception = nil
handler = ->(e) {
handler = ->(e) {
exception = e
'<!-- error -->'
}
@@ -267,38 +246,40 @@ class TemplateTest < Minitest::Test
def test_global_filter_option_on_render
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)
end
def test_global_filter_option_when_native_filters_exist
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)
end
def test_undefined_variables
t = Template.parse("{{x}} {{y}} {{z.a}} {{z.b}} {{z.c.d}}")
result = t.render({ 'x' => 33, 'z' => { 'a' => 32, 'c' => { 'e' => 31 } } }, strict_variables: true)
context = Liquid::Context.new('x' => 33, 'z' => { 'a' => 32, 'c' => { 'e' => 31 } })
result = t.render(context, strict_variables: true)
assert_equal('33 32 ', result)
assert_equal(3, t.errors.count)
assert_instance_of(Liquid::UndefinedVariable, t.errors[0])
assert_equal('Liquid error: undefined variable y', t.errors[0].message)
assert_instance_of(Liquid::UndefinedVariable, t.errors[1])
assert_equal('Liquid error: undefined variable b', t.errors[1].message)
assert_instance_of(Liquid::UndefinedVariable, t.errors[2])
assert_equal('Liquid error: undefined variable d', t.errors[2].message)
assert_equal(3, context.errors.count)
assert_instance_of(Liquid::UndefinedVariable, context.errors[0])
assert_equal('Liquid error: undefined variable y', context.errors[0].message)
assert_instance_of(Liquid::UndefinedVariable, context.errors[1])
assert_equal('Liquid error: undefined variable b', context.errors[1].message)
assert_instance_of(Liquid::UndefinedVariable, context.errors[2])
assert_equal('Liquid error: undefined variable d', context.errors[2].message)
end
def test_nil_value_does_not_raise
Liquid::Template.error_mode = :strict
t = Template.parse("some{{x}}thing")
result = t.render!({ 'x' => nil }, strict_variables: true)
context = Liquid::Context.new('x' => nil)
result = t.render!(context, strict_variables: true)
assert_equal(0, t.errors.count)
assert_equal(0, context.errors.count)
assert_equal('something', result)
end
@@ -313,11 +294,13 @@ class TemplateTest < Minitest::Test
def test_undefined_drop_methods
d = DropWithUndefinedMethod.new
t = Template.new.parse('{{ foo }} {{ woot }}')
result = t.render(d, strict_variables: true)
context = Liquid::Context.new(d)
d.context = context
result = t.render(context, strict_variables: true)
assert_equal('foo ', result)
assert_equal(1, t.errors.count)
assert_instance_of(Liquid::UndefinedDropMethod, t.errors[0])
assert_equal(1, context.errors.count)
assert_instance_of(Liquid::UndefinedDropMethod, context.errors[0])
end
def test_undefined_drop_methods_raise
@@ -336,12 +319,13 @@ class TemplateTest < Minitest::Test
"-#{v}-"
end
end
result = t.render({ 'a' => 123, 'x' => 'foo' }, filters: [filters], strict_filters: true)
context = Liquid::Context.new('a' => 123, 'x' => 'foo')
result = t.render(context, filters: [filters], strict_filters: true)
assert_equal('123 ', result)
assert_equal(1, t.errors.count)
assert_instance_of(Liquid::UndefinedFilter, t.errors[0])
assert_equal('Liquid error: undefined filter somefilter1', t.errors[0].message)
assert_equal(1, context.errors.count)
assert_instance_of(Liquid::UndefinedFilter, context.errors[0])
assert_equal('Liquid error: undefined filter somefilter1', context.errors[0].message)
end
def test_undefined_filters_raise
@@ -361,4 +345,48 @@ class TemplateTest < Minitest::Test
result = t.render('x' => 1, 'y' => 5)
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

View File

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

View File

@@ -51,34 +51,15 @@ class VariableTest < Minitest::Test
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
def test_reuse_parsed_template
template = Template.parse(%({{ greeting }} {{ name }}))
template.assigns['greeting'] = 'Goodbye'
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!)
assert_equal('Goodbye Brian', template.render!('greeting' => 'Goodbye', 'name' => 'Brian'))
end
def test_hash_with_default_proc
template = Template.parse(%(Hello {{ test }}))
assigns = Hash.new { |_h, k| raise "Unknown variable '#{k}'" }
template = Template.parse(%(Hello {{ test }}))
assigns = Hash.new { |_h, k| raise "Unknown variable '#{k}'" }
assigns['test'] = 'Tobi'
assert_equal('Hello Tobi', template.render!(assigns))
assigns.delete('test')

View File

@@ -54,28 +54,39 @@ module Minitest
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
def with_global_filter(*globals)
original_global_strainer = Liquid::Strainer.class_variable_get(:@@global_strainer)
Liquid::Strainer.class_variable_set(:@@global_strainer, Class.new(Liquid::Strainer) do
@filter_methods = Set.new
end)
Liquid::Strainer.class_variable_get(:@@strainer_class_cache).clear
original_global_filters = Liquid::StrainerFactory.instance_variable_get(:@global_filters)
Liquid::StrainerFactory.instance_variable_set(:@global_filters, [])
globals.each do |global|
Liquid::StrainerFactory.add_global_filter(global)
end
Liquid::StrainerFactory.send(:strainer_class_cache).clear
globals.each do |global|
Liquid::Template.register_filter(global)
end
yield
ensure
Liquid::Strainer.class_variable_get(:@@strainer_class_cache).clear
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
Liquid::StrainerFactory.send(:strainer_class_cache).clear
Liquid::StrainerFactory.instance_variable_set(:@global_filters, original_global_filters)
end
def with_error_mode(mode)
@@ -128,7 +139,7 @@ class StubFileSystem
def initialize(values)
@file_read_count = 0
@values = values
@values = values
end
def read_template_file(template_path)
@@ -136,3 +147,16 @@ class StubFileSystem
@values.fetch(template_path)
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

@@ -63,7 +63,7 @@ class BlockUnitTest < Minitest::Test
assert_equal 'hello', template.render
buf = +''
buf = +''
output = template.render({}, output: buf)
assert_equal 'hello', output
assert_equal 'hello', buf
@@ -81,7 +81,7 @@ class BlockUnitTest < Minitest::Test
assert_equal 'foohellobar', template.render
buf = +''
buf = +''
output = template.render({}, output: buf)
assert_equal 'foohellobar', output
assert_equal 'foohellobar', buf

View File

@@ -75,9 +75,9 @@ class ConditionUnitTest < Minitest::Test
end
def test_contains_works_on_arrays
@context = Liquid::Context.new
@context = Liquid::Context.new
@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_true(array_expr, 'contains', 1)
@@ -142,7 +142,7 @@ class ConditionUnitTest < Minitest::Test
end
def test_left_or_right_may_contain_operators
@context = Liquid::Context.new
@context = Liquid::Context.new
@context['one'] = @context['another'] = "gnomeslab-and-or-liquid"
assert_evaluates_true(VariableLookup.new("one"), '==', VariableLookup.new("another"))

View File

@@ -46,7 +46,7 @@ end
class CounterDrop < Liquid::Drop
def count
@count ||= 0
@count += 1
@count += 1
end
end
@@ -55,9 +55,9 @@ class ArrayLike
end
def [](index)
@counts ||= []
@counts ||= []
@counts[index] ||= 0
@counts[index] += 1
@counts[index] += 1
end
def to_liquid
@@ -265,7 +265,7 @@ class ContextUnitTest < Minitest::Test
def test_access_hashes_with_hash_notation
@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('deepsnow', @context['products["tags"][0]'])
@@ -285,8 +285,8 @@ class ContextUnitTest < Minitest::Test
end
def test_access_hashes_with_hash_access_variables
@context['var'] = 'tags'
@context['nested'] = { 'var' => 'tags' }
@context['var'] = 'tags'
@context['nested'] = { 'var' => 'tags' }
@context['products'] = { 'count' => 5, 'tags' => ['deepsnow', 'freestyle'] }
assert_equal('deepsnow', @context['products[var].first'])
@@ -295,7 +295,7 @@ class ContextUnitTest < Minitest::Test
def test_hash_notation_only_for_hash_access
@context['array'] = [1, 2, 3, 4, 5]
@context['hash'] = { 'first' => 'Hello' }
@context['hash'] = { 'first' => 'Hello' }
assert_equal(1, @context['array.first'])
assert_nil(@context['array["first"]'])
@@ -407,7 +407,7 @@ class ContextUnitTest < Minitest::Test
def test_lambda_is_called_once
@context['callcount'] = proc {
@global ||= 0
@global += 1
@global += 1
@global.to_s
}
@@ -421,7 +421,7 @@ class ContextUnitTest < Minitest::Test
def test_nested_lambda_is_called_once
@context['callcount'] = { "lambda" => proc {
@global ||= 0
@global += 1
@global += 1
@global.to_s
} }
@@ -435,7 +435,7 @@ class ContextUnitTest < Minitest::Test
def test_lambda_in_array_is_called_once
@context['callcount'] = [1, 2, proc {
@global ||= 0
@global += 1
@global += 1
@global.to_s
}, 4, 5]
@@ -507,15 +507,15 @@ class ContextUnitTest < Minitest::Test
def test_new_isolated_subcontext_inherits_static_environment
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'])
end
def test_new_isolated_subcontext_inherits_resource_limits
resource_limits = ResourceLimits.new({})
super_context = Context.new({}, {}, {}, false, resource_limits)
subcontext = super_context.new_isolated_subcontext
super_context = Context.new({}, {}, {}, false, resource_limits)
subcontext = super_context.new_isolated_subcontext
assert_equal(resource_limits, subcontext.resource_limits)
end
@@ -532,19 +532,19 @@ class ContextUnitTest < Minitest::Test
}
super_context = Context.new({}, {}, StaticRegisters.new(registers))
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])
end
def test_new_isolated_subcontext_inherits_static_registers
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])
end
def test_new_isolated_subcontext_registers_do_not_pollute_context
super_context = Context.build(registers: { my_register: :my_value })
subcontext = super_context.new_isolated_subcontext
super_context = Context.build(registers: { my_register: :my_value })
subcontext = super_context.new_isolated_subcontext
subcontext.registers[:my_register] = :my_alt_value
assert_equal(:my_value, super_context.registers[:my_register])
end
@@ -558,8 +558,8 @@ class ContextUnitTest < Minitest::Test
super_context = Context.new
super_context.add_filters([my_filter])
subcontext = super_context.new_isolated_subcontext
template = Template.parse('{{ 123 | my_filter }}')
subcontext = super_context.new_isolated_subcontext
template = Template.parse('{{ 123 | my_filter }}')
assert_equal('my filter result', template.render(subcontext))
end

View File

@@ -21,7 +21,7 @@ class PartialCacheUnitTest < Minitest::Test
def test_reads_from_the_file_system_only_once_per_file
file_system = StubFileSystem.new('my_partial' => 'some partial body')
context = Liquid::Context.build(
context = Liquid::Context.build(
registers: { file_system: file_system }
)
@@ -37,7 +37,7 @@ class PartialCacheUnitTest < Minitest::Test
end
def test_cache_state_is_stored_per_context
parse_context = Liquid::ParseContext.new
parse_context = Liquid::ParseContext.new
shared_file_system = StubFileSystem.new(
'my_partial' => 'my shared value'
)
@@ -71,7 +71,7 @@ class PartialCacheUnitTest < Minitest::Test
def test_cache_is_not_broken_when_a_different_parse_context_is_used
file_system = StubFileSystem.new('my_partial' => 'some partial body')
context = Liquid::Context.build(
context = Liquid::Context.build(
registers: { file_system: file_system }
)
@@ -90,4 +90,39 @@ class PartialCacheUnitTest < Minitest::Test
# but measuring file reads is an OK proxy for this.
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

View File

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

View File

@@ -0,0 +1,100 @@
# 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

@@ -0,0 +1,82 @@
# 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

@@ -1,167 +0,0 @@
# 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

@@ -33,7 +33,7 @@ class TagUnitTest < Minitest::Test
assert_equal 'hello', template.render
buf = +''
buf = +''
output = template.render({}, output: buf)
assert_equal 'hello', output
assert_equal 'hello', buf
@@ -51,7 +51,7 @@ class TagUnitTest < Minitest::Test
assert_equal 'foohellobar', template.render
buf = +''
buf = +''
output = template.render({}, output: buf)
assert_equal 'foohellobar', output
assert_equal 'foohellobar', buf

View File

@@ -0,0 +1,12 @@
# 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

@@ -22,7 +22,7 @@ class TemplateUnitTest < Minitest::Test
def test_with_cache_classes_tags_returns_the_same_class
original_cache_setting = Liquid.cache_classes
Liquid.cache_classes = true
Liquid.cache_classes = true
original_klass = Class.new
Object.send(:const_set, :CustomTag, original_klass)
@@ -42,7 +42,7 @@ class TemplateUnitTest < Minitest::Test
def test_without_cache_classes_tags_reloads_the_class
original_cache_setting = Liquid.cache_classes
Liquid.cache_classes = false
Liquid.cache_classes = false
original_klass = Class.new
Object.send(:const_set, :CustomTag, original_klass)
@@ -77,4 +77,11 @@ class TemplateUnitTest < Minitest::Test
ensure
Template.tags.delete('fake')
end
class TemplateSubclass < Liquid::Template
end
def test_template_inheritance
assert_equal("foo", TemplateSubclass.parse("foo").render)
end
end

View File

@@ -34,7 +34,7 @@ class TokenizerTest < Minitest::Test
def tokenize(source)
tokenizer = Liquid::Tokenizer.new(source)
tokens = []
tokens = []
while (t = tokenizer.shift)
tokens << t
end
@@ -42,7 +42,7 @@ class TokenizerTest < Minitest::Test
end
def tokenize_line_numbers(source)
tokenizer = Liquid::Tokenizer.new(source, true)
tokenizer = Liquid::Tokenizer.new(source, true)
line_numbers = []
loop do
line_number = tokenizer.line_number