Compare commits

...
Author SHA1 Message Date
Burke Libbey de603f07af Import Array and Hash tests, and make == blank match behaviour with AS
When ActiveSupport isn't loaded, `[] == blank` is false, but when it is
loaded, it (correctly?) matches the behaviour of `[] == empty`.

I think it's fair to say the intended behaviour is for == blank to match
the behaviour of when AS is loaded, so let's just special-case this.

We also import a bunch of Array and Hash tests here.
2024-06-12 12:12:59 -04:00
Samuel DoironandGitHub 0b9318222b Merge pull request #1735 from Shopify/marco-condition-to-liquid-value
Call to_liquid_value when short circuiting conditions
2023-07-19 15:05:00 -03:00
Marco Rudilosso 21d6197533 Call to_liquid_value when short circuiting conditions 2023-07-19 15:22:12 +00:00
Marco Concetto RudilossoandGitHub 5e92b3a89a Merge pull request #1731 from Shopify/allow-recursive-liquid-tags
Allow liquid tag inside liquid tag
2023-07-10 13:25:13 +01:00
Marco Concetto Rudilosso 7f2cf1fe67 Recursively parse for liquid tag 2023-07-07 11:20:34 +01:00
Marco Concetto Rudilosso 10e0fb795e Allow liquid tag inside liquid tag 2023-07-06 10:45:12 +01:00
Michelle VinciandGitHub 546dd9bc06 Merge pull request #1730 from Shopify/capitalize-desc-fix
[Liquid reference]: Update description of `capitalize`
2023-07-05 08:52:26 -07:00
Michelle Vinci 9a77e3e923 update desc 2023-07-05 08:44:06 -07:00
Michael GoandGitHub c44d1d9193 Merge pull request #1727 from Shopify/partial-cache-with-error-mode
include parse mode to partial cache key
2023-06-27 15:06:58 -03:00
Michael Go dd7bbf26bc include error mode to partial cache key 2023-06-23 18:48:07 -03:00
Adam KlingbaumandGitHub cca24a2226 Merge pull request #1726 from Shopify/klingbaum/ensure_sum_filter_calls_#to_liquid
Ensure `sum` filter calls `to_liquid` on evaluated property value
2023-06-22 15:47:36 -04:00
Adam Klingbaum 98ce25cb40 Ensure sum filter calls to_liquid 2023-06-22 16:25:11 +00:00
Adam KlingbaumandGitHub 77293d4524 Merge pull request #1722 from Shopify/klingbaum/sum-filter
Add `sum` filter to standard filters
2023-06-19 15:15:49 -04:00
Adam Klingbaum af66bc8a5f Add sum filter to standard filters 2023-06-19 19:11:05 +00:00
Guillaume MaletteandGitHub 42e5c52336 Merge pull request #1717 from Shopify/gm/fix-raw-trimmode-at-end-of-block
[trim] Fix trimming at end of raw block when specified in the opening tag
2023-05-19 14:08:02 -04:00
Guillaume Malette 649cca1349 [trim] Fix trimming at end of raw block when specified in the opening tag 2023-05-19 11:41:45 -04:00
Ian Ker-SeymerandGitHub 81ed65f2a1 Merge pull request #1688 from Shopify/fix-readme-docs-link
Fix broken documentantion link in README
2023-05-11 18:48:09 -04:00
Jeffrey LoppertandGitHub 6ca06c22b8 Merge pull request #1712 from Shopify/jloppert/update-readme-docs-link
Update Liquid Documentation from Shopify URL
2023-05-10 16:20:40 -04:00
Jeffrey LoppertandGitHub 80bc7ffdf2 Update Liquid Documentation from Shopify URL
Current url https://shopify.dev/docs/docs/api/liquid 404s. Updated to https://shopify.dev/docs/api/liquid
2023-05-10 14:47:16 -04:00
Michael GoandGitHub 48cb643c02 Merge pull request #1695 from Shopify/syntax-error-with-filename
render template name with Liquid Syntax errors
2023-03-02 17:08:27 -04:00
Michael Go 1d97389fb0 render template name with Liquid Syntax errors 2023-03-02 16:50:55 -04:00
Michael GoandGitHub 3ff4170cb0 Merge pull request #1692 from Shopify/error-message-with-filepath
set context's template_name with template.name in render and include tag
2023-02-28 10:57:24 -04:00
Michael Go 24dceef552 set context's template_name with template's name 2023-02-28 10:55:38 -04:00
Michael Go 428c66ffac add name attribute to Template for more flexibility to set context's template name 2023-02-27 17:11:52 -04:00
Michael Go 0fe4a5d144 render error message with actual template path 2023-02-27 11:01:21 -04:00
Dylan Thacker-SmithandGitHub e650dc4195 Revert "Instrument usage of bug with iteration of String with offset or 0 limit (#1667)" (#1677)
This reverts commit c743936a78.
2023-02-22 12:48:18 -05:00
Robertas Godelis a75517e2c7 Fix broken documentantion link in README 2023-02-15 18:27:36 +02:00
Guilherme CarreiroandGitHub 9ab688eada Update rubocop-shopify (2.7.0 -> 2.12.0) (#1687) 2023-02-15 08:40:59 +01:00
Erin MorrisseyandGitHub abef59d129 Merge pull request #1685 from Shopify/update-liquid-paths
Update link paths in docs markup
2023-02-14 10:37:15 -08:00
Erin Morrissey 940c3a4207 update link paths in docs markup 2023-02-14 10:34:15 -08:00
Guilherme CarreiroandGitHub 84a0289ebc Fix CI (as it installs RuboCop 1.45.1 and fails) (#1686) 2023-02-14 12:24:38 +01:00
Michael GoandGitHub 4599e5459f Merge pull request #1684 from Shopify/fix-variable-lookup-parse-timeout
fix variable lookup parse timing out with missing closing bracket
2023-02-02 10:39:46 -04:00
Dylan Thacker-SmithandGitHub 59c445f0e1 Avoid a couple of duplicate assertions (#1675) 2023-02-02 09:31:01 -05:00
Michael Go bd9c3802c8 add variable parser timeout unit tests 2023-02-01 21:15:35 -04:00
Michael Go 2b40850e4a fix variable lookup parse timing out with missing closing bracket 2023-02-01 19:52:30 -04:00
Marco Concetto RudilossoandGitHub 22ded5f304 Merge pull request #1683 from Shopify/marco/raw-tag-whitespace-control
Allow raw tag to be used with whitespace control
2023-02-01 16:51:46 +00:00
Marco Concetto Rudilosso ddc32b7bd8 actually test whitespace stripping 2023-02-01 15:52:34 +00:00
Marco Concetto Rudilosso 74e505f6fa Allow raw tag to be used with whitespace control 2023-02-01 13:49:51 +00:00
Michael GoandGitHub 6a888d4564 Merge pull request #1680 from Shopify/recursively-parse-brackets
recursively parse brackets on variable lookup
2023-01-31 16:29:54 -04:00
Michael Go dd257b3d66 add an unit test for double nest variable lookup 2023-01-30 20:36:14 -04:00
Michael Go 1aaf6ed019 recursively parse brackets on variable lookup 2023-01-30 20:19:35 -04:00
Michael GoandGitHub daf93a83c2 Merge pull request #1676 from Shopify/tablerow-nil-params
raise invalid integer argument error from tablerow
2023-01-18 11:08:04 -04:00
Michael Go e889a9da0b use to_i to parse parameters of tablerow tag 2023-01-17 17:48:16 -04:00
Michael Go 0f11c97623 raise invalid integer argument error from tablerow 2023-01-16 18:53:53 -04:00
Dylan Thacker-SmithandGitHub e804f36681 Merge pull request #1674 from Shopify/better-test-to-liquid-value
Improve test coverage of the to_liquid_value feature.
2023-01-16 10:44:41 -05:00
Dylan Thacker-Smith 619ed3fcd7 Add some additional to_liquid_value assertions 2023-01-13 15:46:12 -05:00
Dylan Thacker-Smith cdb5cb06b2 Remove == method from drops from testing to_liquid_value
Since they could cause tests to pass without to_liquid_value being called
on the left side of the equality comparison.
2023-01-13 15:37:27 -05:00
Jean byroot BoussierandGitHub bc153159e6 Merge pull request #1673 from Shopify/initialize-context
Initialize context to nil on the Drop class
2023-01-12 22:19:28 +01:00
Jemma Issroff 128b4e35be Initialize context to nil on the Drop class
We believe this will reduce megamorphic exits in YJIT. There are
currently 241 separate shapes generated with edge_name "@context" in
SFR. Initializing it to nil will significantly reduce this number.
2023-01-12 15:35:22 -05:00
Dylan Thacker-SmithandGitHub c743936a78 Instrument usage of bug with iteration of String with offset or 0 limit (#1667) 2023-01-11 14:05:50 -05:00
Dylan Thacker-SmithandGitHub bf711a0521 Provide another assertion for to_liquid_value unless tag test (#1672)
Since the original one would pass even if to_liquid_value isn't called on
the BooleanDrop object.
2023-01-11 14:05:27 -05:00
Dylan Thacker-SmithandGitHub e8731f27d9 Merge pull request #1671 from ashmaroli/use-builtin-bundle-cache
Use cache built into `ruby/setup-ruby` action
2023-01-11 11:40:11 -05:00
Ashwin Maroli 6a44c1ec77 Do not set ENV["BUNDLE_PATH"] 2023-01-11 22:06:47 +05:30
Ashwin Maroli 1beb87b446 Bump latest Ruby version in CI matrix 2023-01-11 20:58:17 +05:30
Ashwin Maroli b839deb3a8 Use cache built into ruby/setup-ruby action 2023-01-11 20:50:44 +05:30
liamgriffinandGitHub 0b826120c0 Merge pull request #1665 from Shopify/update-description-divided_by-filter
[Liquid reference docs] Adding more content to divided_by filter
2023-01-09 14:12:23 +00:00
liamgriffin 936f803a4e Removed trailing space 2023-01-06 11:36:20 +00:00
liamgriffin 5cd8a83fa6 Adding more content to divided_by filter
Adding more content to divided_by filter, by detailing the behaviour of how different results are generated by different types of divisors.
2023-01-06 11:19:13 +00:00
Dylan Thacker-SmithandGitHub c2c6cb2b15 Allow commas to separate for tag attributes (#1658)
For consistency with tags like the `render` tag, where we actually
prefer to use commas to separate attributes
2022-11-29 09:00:55 -05:00
63 changed files with 1586 additions and 452 deletions
+10 -14
View File
@@ -1,5 +1,10 @@
name: Liquid
on: [push, pull_request]
env:
BUNDLE_JOBS: 4
BUNDLE_RETRY: 3
jobs:
test:
runs-on: ubuntu-latest
@@ -7,22 +12,18 @@ jobs:
matrix:
entry:
- { ruby: 2.7, allowed-failure: false } # minimum supported
- { ruby: 3.1, allowed-failure: false } # latest
- { ruby: 3.2, allowed-failure: false } # latest
- { ruby: ruby-head, allowed-failure: true }
name: test (${{ matrix.entry.ruby }})
name: Test Ruby ${{ matrix.entry.ruby }}
steps:
- uses: actions/checkout@v3
- uses: ruby/setup-ruby@v1
with:
ruby-version: ${{ matrix.entry.ruby }}
- uses: actions/cache@v1
with:
path: vendor/bundle
key: ${{ runner.os }}-gems-${{ hashFiles('Gemfile') }}
restore-keys: ${{ runner.os }}-gems-
- run: bundle install --jobs=3 --retry=3 --path=vendor/bundle
bundler-cache: true
- run: bundle exec rake
continue-on-error: ${{ matrix.entry.allowed-failure }}
memory_profile:
runs-on: ubuntu-latest
steps:
@@ -30,10 +31,5 @@ jobs:
- uses: ruby/setup-ruby@v1
with:
ruby-version: 2.7
- uses: actions/cache@v1
with:
path: vendor/bundle
key: ${{ runner.os }}-gems-${{ hashFiles('Gemfile') }}
restore-keys: ${{ runner.os }}-gems-
- run: bundle install --jobs=3 --retry=3 --path=vendor/bundle
bundler-cache: true
- run: bundle exec rake memory_profile:run
+8 -3
View File
@@ -12,6 +12,7 @@ Performance:
AllCops:
TargetRubyVersion: 2.7
NewCops: disable
SuggestExtensions: false
Exclude:
- 'vendor/bundle/**/*'
@@ -19,6 +20,10 @@ Naming/MethodName:
Exclude:
- 'example/server/liquid_servlet.rb'
# Backport https://github.com/Shopify/ruby-style-guide/pull/258
Layout/BeginEndAlignment:
Enabled: true
Style/ClassMethodsDefinitions:
Enabled: false
# liquid filter calls were being mistaken to be calls on arrays
Style/ConcatArrayLiterals:
Exclude:
- 'test/integration/standard_filter_test.rb'
+2 -1
View File
@@ -18,7 +18,8 @@ group :benchmark, :test do
end
group :test do
gem 'rubocop-shopify', '~> 2.7.0', require: false
gem 'rubocop', '~> 1.44.0'
gem 'rubocop-shopify', '~> 2.12.0', require: false
gem 'rubocop-performance', require: false
platform :mri, :truffleruby do
+2 -2
View File
@@ -5,7 +5,7 @@
* [Contributing guidelines](CONTRIBUTING.md)
* [Version history](History.md)
* [Liquid documentation from Shopify](https://shopify.dev/api/liquid)
* [Liquid documentation from Shopify](https://shopify.dev/docs/api/liquid)
* [Liquid Wiki at GitHub](https://github.com/Shopify/liquid/wiki)
* [Website](http://liquidmarkup.org/)
@@ -111,4 +111,4 @@ template.render!({ 'x' => 1}, { strict_variables: true })
To help track usages of a feature or code path in production, we have released opt-in usage tracking. To enable this, we provide an empty `Liquid:: Usage.increment` method which you can customize to your needs. The feature is well suited to https://github.com/Shopify/statsd-instrument. However, the choice of implementation is up to you.
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.
+9 -5
View File
@@ -30,14 +30,18 @@ class Servlet < LiquidServlet
private
def products_list
[{ 'name' => 'Arbor Draft', 'price' => 39900, 'description' => 'the *arbor draft* is a excellent product' },
{ 'name' => 'Arbor Element', 'price' => 40000, 'description' => 'the *arbor element* rocks for freestyling' },
{ 'name' => 'Arbor Diamond', 'price' => 59900, 'description' => 'the *arbor diamond* is a made up product because im obsessed with arbor and have no creativity' }]
[
{ 'name' => 'Arbor Draft', 'price' => 39900, 'description' => 'the *arbor draft* is a excellent product' },
{ 'name' => 'Arbor Element', 'price' => 40000, 'description' => 'the *arbor element* rocks for freestyling' },
{ 'name' => 'Arbor Diamond', 'price' => 59900, 'description' => 'the *arbor diamond* is a made up product because im obsessed with arbor and have no creativity' }
]
end
def more_products_list
[{ 'name' => 'Arbor Catalyst', 'price' => 39900, 'description' => 'the *arbor catalyst* is an advanced drop-through for freestyle and flatground performance and versatility' },
{ 'name' => 'Arbor Fish', 'price' => 40000, 'description' => 'the *arbor fish* is a compact pin that features an extended wheelbase and time-honored teardrop shape' }]
[
{ 'name' => 'Arbor Catalyst', 'price' => 39900, 'description' => 'the *arbor catalyst* is an advanced drop-through for freestyle and flatground performance and versatility' },
{ 'name' => 'Arbor Fish', 'price' => 40000, 'description' => 'the *arbor fish* is a compact pin that features an extended wheelbase and time-honored teardrop shape' }
]
end
def description
+1 -1
View File
@@ -41,7 +41,7 @@ module Liquid
AnyStartingTag = /#{TagStart}|#{VariableStart}/o
PartialTemplateParser = /#{TagStart}.*?#{TagEnd}|#{VariableStart}.*?#{VariableIncompleteEnd}/om
TemplateParser = /(#{PartialTemplateParser}|#{AnyStartingTag})/om
VariableParser = /\[[^\]]+\]|#{VariableSegment}+\??/o
VariableParser = /\[(?>[^\[\]]+|\g<0>)*\]|#{VariableSegment}+\??/o
RAISE_EXCEPTION_LAMBDA = ->(_e) { raise }
+8 -4
View File
@@ -36,13 +36,17 @@ module Liquid
# @api private
def self.raise_unknown_tag(tag, block_name, block_delimiter, parse_context)
if tag == 'else'
raise SyntaxError, parse_context.locale.t("errors.syntax.unexpected_else",
block_name: block_name)
raise SyntaxError, parse_context.locale.t(
"errors.syntax.unexpected_else",
block_name: block_name,
)
elsif tag.start_with?('end')
raise SyntaxError, parse_context.locale.t("errors.syntax.invalid_delimiter",
raise SyntaxError, parse_context.locale.t(
"errors.syntax.invalid_delimiter",
tag: tag,
block_name: block_name,
block_delimiter: block_delimiter)
block_delimiter: block_delimiter,
)
else
raise SyntaxError, parse_context.locale.t("errors.syntax.unknown_tag", tag: tag)
end
+6
View File
@@ -45,6 +45,12 @@ module Liquid
end
tag_name = Regexp.last_match(1)
markup = Regexp.last_match(2)
if tag_name == 'liquid'
parse_context.line_number -= 1
next parse_liquid_tag(markup, parse_context)
end
unless (tag = registered_tags[tag_name])
# end parsing if we reach an unknown tag and let the caller decide
# determine how to proceed
+21 -4
View File
@@ -69,9 +69,9 @@ module Liquid
case condition.child_relation
when :or
break if result
break if Liquid::Utils.to_liquid_value(result)
when :and
break unless result
break unless Liquid::Utils.to_liquid_value(result)
else
break
end
@@ -112,6 +112,8 @@ module Liquid
if left.is_a?(MethodLiteral)
if right.respond_to?(left.method_name)
return right.send(left.method_name)
elsif (res = fallback_simulation_of_active_support(left.method_name, right))
return res
else
return nil
end
@@ -120,6 +122,8 @@ module Liquid
if right.is_a?(MethodLiteral)
if left.respond_to?(right.method_name)
return left.send(right.method_name)
elsif (res = fallback_simulation_of_active_support(right.method_name, left))
return res
else
return nil
end
@@ -128,6 +132,17 @@ module Liquid
left == right
end
# ActiveSupport creates #blank? as an alias for #empty? on Hash and Array.
# Without this simulation, [] == blank behaves differently when AS is loaded vs. not.
def fallback_simulation_of_active_support(method_name, obj)
return nil unless method_name == :blank?
case obj
when Array, Hash
obj.empty?
end # else nil
end
def interpret_condition(left, right, op, context)
# If the operator is empty this means that the decision statement is just
# a single variable. We can just poll this variable from the context and
@@ -159,8 +174,10 @@ module Liquid
class ParseTreeVisitor < Liquid::ParseTreeVisitor
def children
[
@node.left, @node.right,
@node.child_condition, @node.attachment
@node.left,
@node.right,
@node.child_condition,
@node.attachment
].compact
end
end
+1 -1
View File
@@ -144,7 +144,7 @@ module Liquid
self.class.build(
resource_limits: resource_limits,
static_environments: static_environments,
registers: Registers.new(registers)
registers: Registers.new(registers),
).tap do |subcontext|
subcontext.base_scope_depth = base_scope_depth + 1
subcontext.exception_renderer = exception_renderer
+4
View File
@@ -25,6 +25,10 @@ module Liquid
class Drop
attr_writer :context
def initialize
@context = nil
end
# Catch all for the method
def liquid_method_missing(method)
return nil unless @context&.strict_variables
+4 -1
View File
@@ -3,7 +3,10 @@
module Liquid
class Expression
LITERALS = {
nil => nil, 'nil' => nil, 'null' => nil, '' => nil,
nil => nil,
'nil' => nil,
'null' => nil,
'' => nil,
'true' => true,
'false' => false,
'blank' => '',
+1 -1
View File
@@ -5,7 +5,7 @@ module Liquid
# @liquid_type object
# @liquid_name forloop
# @liquid_summary
# Information about a parent [`for` loop](/api/liquid/tags/for).
# Information about a parent [`for` loop](/docs/api/liquid/tags/for).
class ForloopDrop < Drop
def initialize(name, length, parentloop)
@name = name
+12 -3
View File
@@ -4,7 +4,8 @@ module Liquid
class PartialCache
def self.load(template_name, context:, parse_context:)
cached_partials = context.registers[:cached_partials]
cached = cached_partials[template_name]
cache_key = "#{template_name}:#{parse_context.error_mode}"
cached = cached_partials[cache_key]
return cached if cached
file_system = context.registers[:file_system]
@@ -15,8 +16,16 @@ module Liquid
template_factory = context.registers[:template_factory]
template = template_factory.for(template_name)
partial = template.parse(source, parse_context)
cached_partials[template_name] = partial
begin
partial = template.parse(source, parse_context)
rescue Liquid::Error => e
e.template_name = template&.name || template_name
raise e
end
partial.name ||= template_name
cached_partials[cache_key] = partial
ensure
parse_context.partial = false
end
+35 -7
View File
@@ -25,7 +25,7 @@ module Liquid
STRIP_HTML_BLOCKS = Regexp.union(
%r{<script.*?</script>}m,
/<!--.*?-->/m,
%r{<style.*?</style>}m
%r{<style.*?</style>}m,
)
STRIP_HTML_TAGS = /<.*?>/m
@@ -69,7 +69,7 @@ module Liquid
# @liquid_type filter
# @liquid_category string
# @liquid_summary
# Capitalizes the first word in a string.
# Capitalizes the first word in a string and downcases the remaining characters.
# @liquid_syntax string | capitalize
# @liquid_return [string]
def capitalize(input)
@@ -615,7 +615,7 @@ module Liquid
# @liquid_description
# > Note:
# > The `concat` filter won't filter out duplicates. If you want to remove duplicates, then you need to use the
# > [`uniq` filter](/api/liquid/filters/uniq).
# > [`uniq` filter](/docs/api/liquid/filters/uniq).
# @liquid_syntax array | concat: array
# @liquid_return [array[untyped]]
def concat(input, array)
@@ -757,7 +757,7 @@ module Liquid
# @liquid_type filter
# @liquid_category math
# @liquid_summary
# Divides a number by a given number.
# Divides a number by a given number. The `divided_by` filter produces a result of the same type as the divisor. This means if you divide by an integer, the result will be an integer, and if you divide by a float, the result will be a float.
# @liquid_syntax number | divided_by: number
# @liquid_return [number]
def divided_by(input, operand)
@@ -857,9 +857,9 @@ module Liquid
# @liquid_summary
# Sets a default value for any variable whose value is one of the following:
#
# - [`empty`](/api/liquid/basics#empty)
# - [`false`](/api/liquid/basics#truthy-and-falsy)
# - [`nil`](/api/liquid/basics#nil)
# - [`empty`](/docs/api/liquid/basics#empty)
# - [`false`](/docs/api/liquid/basics#truthy-and-falsy)
# - [`nil`](/docs/api/liquid/basics#nil)
# @liquid_syntax variable | default: variable
# @liquid_return [untyped]
# @liquid_optional_param allow_false [boolean] Whether to use false values instead of the default.
@@ -869,6 +869,34 @@ module Liquid
false_check || (input.respond_to?(:empty?) && input.empty?) ? default_value : input
end
# @liquid_public_docs
# @liquid_type filter
# @liquid_category array
# @liquid_summary
# Returns the sum of all elements in an array.
# @liquid_syntax array | sum
# @liquid_return [number]
def sum(input, property = nil)
ary = InputIterator.new(input, context)
return 0 if ary.empty?
values_for_sum = ary.map do |item|
if property.nil?
item
elsif item.respond_to?(:[])
item[property]
else
0
end
rescue TypeError
raise_property_error(property)
end
InputIterator.new(values_for_sum, context).sum do |item|
Utils.to_number(item)
end
end
private
attr_reader :context
+1 -1
View File
@@ -5,7 +5,7 @@ module Liquid
# @liquid_type object
# @liquid_name tablerowloop
# @liquid_summary
# Information about a parent [`tablerow` loop](/api/liquid/tags/tablerow).
# Information about a parent [`tablerow` loop](/docs/api/liquid/tags/tablerow).
class TablerowloopDrop < Drop
def initialize(length, cols)
@length = length
+1 -1
View File
@@ -8,7 +8,7 @@ module Liquid
# @liquid_summary
# Creates a new variable.
# @liquid_description
# You can create variables of any [basic type](/api/liquid/basics#types), [object](/api/liquid/objects), or object property.
# You can create variables of any [basic type](/docs/api/liquid/basics#types), [object](/docs/api/liquid/objects), or object property.
# @liquid_syntax
# {% assign variable_name = value %}
# @liquid_syntax_keyword variable_name The name of the variable being created.
+1 -1
View File
@@ -15,7 +15,7 @@ module Liquid
# @liquid_category iteration
# @liquid_name break
# @liquid_summary
# Stops a [`for` loop](/api/liquid/tags/for) from iterating.
# Stops a [`for` loop](/docs/api/liquid/tags/for) from iterating.
# @liquid_syntax
# {% break %}
class Break < Tag
+1 -1
View File
@@ -77,7 +77,7 @@ module Liquid
end
result = Liquid::Utils.to_liquid_value(
block.evaluate(context)
block.evaluate(context),
)
if result
+1 -1
View File
@@ -6,7 +6,7 @@ module Liquid
# @liquid_category iteration
# @liquid_name continue
# @liquid_summary
# Causes a [`for` loop](/api/liquid/tags/for) to skip to the next iteration.
# Causes a [`for` loop](/docs/api/liquid/tags/for) to skip to the next iteration.
# @liquid_syntax
# {% continue %}
class Continue < Tag
+1 -1
View File
@@ -6,7 +6,7 @@ module Liquid
# @liquid_category iteration
# @liquid_name cycle
# @liquid_summary
# Loops through a group of strings and outputs them one at a time for each iteration of a [`for` loop](/api/liquid/tags/for).
# Loops through a group of strings and outputs them one at a time for each iteration of a [`for` loop](/docs/api/liquid/tags/for).
# @liquid_description
# The `cycle` tag must be used inside a `for` loop.
#
+2 -2
View File
@@ -12,8 +12,8 @@ module Liquid
# or [section](/themes/architecture/sections) file that they're created in. However, the variable is shared across
# [snippets](/themes/architecture#snippets) included in the file.
#
# Similarly, variables that are created with `decrement` are independent from those created with [`assign`](/api/liquid/tags/assign)
# and [`capture`](/api/liquid/tags/capture). However, `decrement` and [`increment`](/api/liquid/tags/increment) share
# Similarly, variables that are created with `decrement` are independent from those created with [`assign`](/docs/api/liquid/tags/assign)
# and [`capture`](/docs/api/liquid/tags/capture). However, `decrement` and [`increment`](/docs/api/liquid/tags/increment) share
# variables.
# @liquid_syntax
# {% decrement variable_name %}
+2 -2
View File
@@ -9,10 +9,10 @@ module Liquid
# Outputs an expression.
# @liquid_description
# Using the `echo` tag is the same as wrapping an expression in curly brackets (`{{` and `}}`). However, unlike the curly
# bracket method, you can use the `echo` tag inside [`liquid` tags](/api/liquid/tags/liquid).
# bracket method, you can use the `echo` tag inside [`liquid` tags](/docs/api/liquid/tags/liquid).
#
# > Tip:
# > You can use [filters](/api/liquid/filters) on expressions inside `echo` tags.
# > You can use [filters](/docs/api/liquid/filters) on expressions inside `echo` tags.
# @liquid_syntax
# {% liquid
# echo expression
+5 -4
View File
@@ -9,10 +9,10 @@ module Liquid
# Renders an expression for every item in an array.
# @liquid_description
# You can do a maximum of 50 iterations with a `for` loop. If you need to iterate over more than 50 items, then use the
# [`paginate` tag](/api/liquid/tags/paginate) to split the items over multiple pages.
# [`paginate` tag](/docs/api/liquid/tags/paginate) to split the items over multiple pages.
#
# > Tip:
# > Every `for` loop has an associated [`forloop` object](/api/liquid/objects/forloop) with information about the loop.
# > Every `for` loop has an associated [`forloop` object](/docs/api/liquid/objects/forloop) with information about the loop.
# @liquid_syntax
# {% for variable in array %}
# expression
@@ -98,11 +98,12 @@ module Liquid
@name = "#{@variable_name}-#{collection_name}"
@reversed = p.id?('reversed')
while p.look(:id) && p.look(:colon, 1)
while p.look(:comma) || p.look(:id)
p.consume?(:comma)
unless (attribute = p.id?('limit') || p.id?('offset'))
raise SyntaxError, options[:locale].t("errors.syntax.for_invalid_attribute")
end
p.consume
p.consume(:colon)
set_attribute(attribute, p.expression)
end
p.consume(:end_of_string)
+1 -1
View File
@@ -53,7 +53,7 @@ module Liquid
def render_to_output_buffer(context, output)
@blocks.each do |block|
result = Liquid::Utils.to_liquid_value(
block.evaluate(context)
block.evaluate(context),
)
if result
+7 -5
View File
@@ -8,7 +8,7 @@ module Liquid
# @liquid_summary
# Renders a [snippet](/themes/architecture#snippets).
# @liquid_description
# Inside the snippet, you can access and alter variables that are [created](/api/liquid/tags/variable-tags) outside of the
# Inside the snippet, you can access and alter variables that are [created](/docs/api/liquid/tags/variable-tags) outside of the
# snippet.
# @liquid_syntax
# {% include 'filename' %}
@@ -16,7 +16,7 @@ module Liquid
# @liquid_deprecated
# Deprecated because the way that variables are handled reduces performance and makes code harder to both read and maintain.
#
# The `include` tag has been replaced by [`render`](/api/liquid/tags/render).
# The `include` tag has been replaced by [`render`](/docs/api/liquid/tags/render).
class Include < Tag
prepend Tag::Disableable
@@ -57,7 +57,7 @@ module Liquid
partial = PartialCache.load(
template_name,
context: context,
parse_context: parse_context
parse_context: parse_context,
)
context_variable_name = @alias_name || template_name.split('/').last
@@ -70,9 +70,11 @@ module Liquid
old_template_name = context.template_name
old_partial = context.partial
begin
context.template_name = template_name
context.partial = true
context.template_name = partial.name
context.partial = true
context.stack do
@attributes.each do |key, value|
context[key] = context.evaluate(value)
+2 -2
View File
@@ -12,8 +12,8 @@ module Liquid
# or [section](/themes/architecture/sections) file that they're created in. However, the variable is shared across
# [snippets](/themes/architecture#snippets) included in the file.
#
# Similarly, variables that are created with `increment` are independent from those created with [`assign`](/api/liquid/tags/assign)
# and [`capture`](/api/liquid/tags/capture). However, `increment` and [`decrement`](/api/liquid/tags/decrement) share
# Similarly, variables that are created with `increment` are independent from those created with [`assign`](/docs/api/liquid/tags/assign)
# and [`capture`](/docs/api/liquid/tags/capture). However, `increment` and [`decrement`](/docs/api/liquid/tags/decrement) share
# variables.
# @liquid_syntax
# {% increment variable_name %}
+2 -1
View File
@@ -14,7 +14,7 @@ module Liquid
# @liquid_syntax_keyword expression The expression to be output without being rendered.
class Raw < Block
Syntax = /\A\s*\z/
FullTokenPossiblyInvalid = /\A(.*)#{TagStart}\s*(\w+)\s*(.*)?#{TagEnd}\z/om
FullTokenPossiblyInvalid = /\A(.*)#{TagStart}#{WhitespaceControl}?\s*(\w+)\s*(.*)?#{WhitespaceControl}?#{TagEnd}\z/om
def initialize(tag_name, markup, parse_context)
super
@@ -26,6 +26,7 @@ module Liquid
@body = +''
while (token = tokens.shift)
if token =~ FullTokenPossiblyInvalid && block_delimiter == Regexp.last_match(2)
parse_context.trim_whitespace = (token[-3] == WhitespaceControl)
@body << Regexp.last_match(1) if Regexp.last_match(1) != ""
return
end
+7 -7
View File
@@ -8,19 +8,19 @@ module Liquid
# @liquid_summary
# Renders a [snippet](/themes/architecture#snippets) or [app block](/themes/architecture/sections/section-schema#render-app-blocks).
# @liquid_description
# Inside snippets and app blocks, you can't directly access variables that are [created](/api/liquid/tags/variable-tags) outside
# of the snippet or app block. However, you can [specify variables as parameters](/api/liquid/tags/render#render-passing-variables-to-a-snippet)
# Inside snippets and app blocks, you can't directly access variables that are [created](/docs/api/liquid/tags/variable-tags) outside
# of the snippet or app block. However, you can [specify variables as parameters](/docs/api/liquid/tags/render#render-passing-variables-to-a-snippet)
# to pass outside variables to snippets.
#
# While you can't directly access created variables, you can access global objects, as well as any objects that are
# directly accessible outside the snippet or app block. For example, a snippet or app block inside the [product template](/themes/architecture/templates/product)
# can access the [`product` object](/api/liquid/objects/product), and a snippet or app block inside a [section](/themes/architecture/sections)
# can access the [`section` object](/api/liquid/objects/section).
# can access the [`product` object](/docs/api/liquid/objects/product), and a snippet or app block inside a [section](/themes/architecture/sections)
# can access the [`section` object](/docs/api/liquid/objects/section).
#
# Outside a snippet or app block, you can't access variables created inside the snippet or app block.
#
# > Note:
# > When you render a snippet using the `render` tag, you can't use the [`include` tag](/api/liquid/tags/include)
# > When you render a snippet using the `render` tag, you can't use the [`include` tag](/docs/api/liquid/tags/include)
# > inside the snippet.
# @liquid_syntax
# {% render 'filename' %}
@@ -69,14 +69,14 @@ module Liquid
partial = PartialCache.load(
template_name,
context: context,
parse_context: parse_context
parse_context: parse_context,
)
context_variable_name = @alias_name || template_name.split('/').last
render_partial_func = ->(var, forloop) {
inner_context = context.new_isolated_subcontext
inner_context.template_name = template_name
inner_context.template_name = partial.name
inner_context.partial = true
inner_context['forloop'] = forloop if forloop
+12 -4
View File
@@ -11,7 +11,7 @@ module Liquid
# The `tablerow` tag must be wrapped in HTML `<table>` and `</table>` tags.
#
# > Tip:
# > Every `tablerow` loop has an associated [`tablerowloop` object](/api/liquid/objects/tablerowloop) with information about the loop.
# > Every `tablerow` loop has an associated [`tablerowloop` object](/docs/api/liquid/objects/tablerowloop) with information about the loop.
# @liquid_syntax
# {% tablerow variable in array %}
# expression
@@ -45,13 +45,13 @@ module Liquid
def render_to_output_buffer(context, output)
(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
from = @attributes.key?('offset') ? to_integer(context.evaluate(@attributes['offset'])) : 0
to = @attributes.key?('limit') ? from + to_integer(context.evaluate(@attributes['limit'])) : nil
collection = Utils.slice_collection(collection, from, to)
length = collection.length
cols = @attributes.key?('cols') ? context.evaluate(@attributes['cols']).to_i : length
cols = @attributes.key?('cols') ? to_integer(context.evaluate(@attributes['cols'])) : length
output << "<tr class=\"row1\">\n"
context.stack do
@@ -82,6 +82,14 @@ module Liquid
super + @node.attributes.values + [@node.collection_name]
end
end
private
def to_integer(value)
value.to_i
rescue NoMethodError
raise Liquid::ArgumentError, "invalid integer"
end
end
Template.register_tag('tablerow', TableRow)
+3 -3
View File
@@ -11,7 +11,7 @@ module Liquid
# Renders an expression unless a specific condition is `true`.
# @liquid_description
# > Tip:
# > Similar to the [`if` tag](/api/liquid/tags/if), you can use `elsif` to add more conditions to an `unless` tag.
# > Similar to the [`if` tag](/docs/api/liquid/tags/if), you can use `elsif` to add more conditions to an `unless` tag.
# @liquid_syntax
# {% unless condition %}
# expression
@@ -23,7 +23,7 @@ module Liquid
# First condition is interpreted backwards ( if not )
first_block = @blocks.first
result = Liquid::Utils.to_liquid_value(
first_block.evaluate(context)
first_block.evaluate(context),
)
unless result
@@ -33,7 +33,7 @@ module Liquid
# After the first condition unless works just like if
@blocks[1..-1].each do |block|
result = Liquid::Utils.to_liquid_value(
block.evaluate(context)
block.evaluate(context),
)
if result
+3 -1
View File
@@ -15,7 +15,7 @@ module Liquid
# template.render('user_name' => 'bob')
#
class Template
attr_accessor :root
attr_accessor :root, :name
attr_reader :resource_limits, :warnings
class TagRegistry
@@ -189,6 +189,8 @@ module Liquid
@profiler = context.profiler = Liquid::Profiler.new
end
context.template_name ||= name
begin
# render the nodelist.
@root.render_to_output_buffer(context, output || +'')
-5
View File
@@ -49,8 +49,3 @@ module Database
end
end
end
if __FILE__ == $PROGRAM_NAME
p(Database.tables['collections']['frontpage'].keys)
# p Database.tables['blog']['articles']
end
+2 -2
View File
@@ -45,8 +45,8 @@ class Paginate < Liquid::Block
pagination['items'] = collection_size
pagination['pages'] = page_count - 1
pagination['previous'] = link('&laquo; Previous', current_page - 1) unless 1 >= current_page
pagination['next'] = link('Next &raquo;', current_page + 1) unless page_count <= current_page + 1
pagination['previous'] = link('&laquo; Previous', current_page - 1) if 1 < current_page
pagination['next'] = link('Next &raquo;', current_page + 1) if page_count > current_page + 1
pagination['parts'] = []
hellip_break = false
+103
View File
@@ -0,0 +1,103 @@
# frozen_string_literal: true
require 'test_helper'
class ArrayTest < Minitest::Test
include Liquid
def test_array_size
assert_template_result("0", "{{a.size}}", { 'a' => [] })
assert_template_result("2", "{{a.size}}", { 'a' => [1, 2] })
end
def test_array_first
assert_template_result("", "{{a.first}}", { 'a' => [] })
assert_template_result("1", "{{a.first}}", { 'a' => [1, 2] })
end
def test_array_last
assert_template_result("", "{{a.last}}", { 'a' => [] })
assert_template_result("2", "{{a.last}}", { 'a' => [1, 2] })
end
def test_array_index
assert_template_result("", "{{a[1]}}", { 'a' => [] })
assert_template_result("2", "{{a[1]}}", { 'a' => [1, 2] })
end
def test_negative_array_index
assert_template_result("3", "{{a[-1]}}", { 'a' => [1, 2, 3] })
assert_template_result("1", "{{a[-3]}}", { 'a' => [1, 2, 3] })
assert_template_result("", "{{a[-4]}}", { 'a' => [1, 2, 3] })
end
def test_array_to_s
arr = ["a", ["b", 1], "c"]
assert_template_result("ab1c", "{{a}}", { 'a' => arr })
end
def test_auto_methods
a1 = []
a2 = [1, 2, 3, "abc"]
assert_template_result("0,nonblank", "{{a.size}},{%if a['size'] == blank%}blank{%else%}nonblank{%endif%}", { 'a' => a1 })
assert_template_result("4,nonblank", "{{a.size}},{%if a['size'] == blank%}blank{%else%}nonblank{%endif%}", { 'a' => a2 })
assert_template_result(",", "{{a.first}},{{a['first']}}", { 'a' => a1 })
assert_template_result("1,", "{{a.first}},{{a['first']}}", { 'a' => a2 })
assert_template_result(",", "{{a.last}},{{a['last']}}", { 'a' => a1 })
assert_template_result("abc,", "{{a.last}},{{a['last']}}", { 'a' => a2 })
end
def test_array_equality
a0 = []
a1 = [1]
a2 = [1, 2]
a3 = [1]
arrays = [a0, a1, a2, a3]
arrays.each_with_index do |a, i|
arrays.each_with_index do |b, j|
prefix = "(#{i},#{j})"
assert_template_result(
prefix + (a == b ? "y" : "n"),
"#{prefix}{% if a == b %}y{%else%}n{%endif%}",
{ "a" => a, "b" => b },
)
end
end
end
def test_contains
a0 = [1, "a", ["b", "c"], true]
a1 = ["b", "c"]
a2 = ["B", "C"]
tpl = "{%if a contains b%}y{%else%}n{%endif%}"
assert_template_result("y", tpl, { "a" => a0, "b" => 1 })
assert_template_result("n", tpl, { "a" => a0, "b" => 2 })
assert_template_result("y", tpl, { "a" => a0, "b" => a1 })
assert_template_result("n", tpl, { "a" => a0, "b" => a2 })
end
def test_empty
assert_template_result("y", "{%if a == empty%}y{%else%}n{%endif%}", { 'a' => [] })
assert_template_result("n", "{%if a == empty%}y{%else%}n{%endif%}", { 'a' => [1, 2] })
end
def test_blank
assert_template_result("y", "{%if a == blank%}y{%else%}n{%endif%}", { 'a' => [] })
assert_template_result("n", "{%if a == blank%}y{%else%}n{%endif%}", { 'a' => [1, 2] })
end
def test_iter
a0 = [1, "a"]
assert_template_result("1a", "{%for i in a%}{{i}}{%endfor%}", { 'a' => a0 })
end
def test_output
a0 = []
a1 = [1, "a", ["b", "c"], true]
assert_template_result("", "{{a}}", { 'a' => a0 })
assert_template_result("1abctrue", "{{a}}", { 'a' => a1 })
end
end
+17 -8
View File
@@ -14,19 +14,25 @@ class AssignTest < Minitest::Test
end
def test_assigned_variable
assert_template_result('.foo.',
assert_template_result(
'.foo.',
'{% assign foo = values %}.{{ foo[0] }}.',
{ 'values' => %w(foo bar baz) })
{ 'values' => %w(foo bar baz) },
)
assert_template_result('.bar.',
assert_template_result(
'.bar.',
'{% assign foo = values %}.{{ foo[1] }}.',
{ 'values' => %w(foo bar baz) })
{ 'values' => %w(foo bar baz) },
)
end
def test_assign_with_filter
assert_template_result('.bar.',
assert_template_result(
'.bar.',
'{% assign foo = values | split: "," %}.{{ foo[1] }}.',
{ 'values' => "foo,bar,baz" })
{ 'values' => "foo,bar,baz" },
)
end
def test_assign_syntax_error
@@ -34,8 +40,11 @@ class AssignTest < Minitest::Test
end
def test_assign_uses_error_mode
assert_match_syntax_error("Expected dotdot but found pipe in ",
"{% assign foo = ('X' | downcase) %}", error_mode: :strict)
assert_match_syntax_error(
"Expected dotdot but found pipe in ",
"{% assign foo = ('X' | downcase) %}",
error_mode: :strict,
)
assert_template_result("", "{% assign foo = ('X' | downcase) %}", error_mode: :lax)
end
+19 -8
View File
@@ -57,9 +57,11 @@ class BlankTest < Minitest::Test
def test_nested_blocks_are_blank_but_only_if_all_children_are
assert_template_result("", wrap(wrap(" ")))
assert_template_result("\n but this is not " * (N + 1),
assert_template_result(
"\n but this is not " * (N + 1),
wrap('{% if true %} {% comment %} this is blank {% endcomment %} {% endif %}
{% if true %} but this is not {% endif %}'))
{% if true %} but this is not {% endif %}'),
)
end
def test_assigns_are_blank
@@ -89,12 +91,21 @@ class BlankTest < Minitest::Test
end
def test_include_is_blank
assert_template_result("foobar" * (N + 1), wrap("{% include 'foobar' %}"),
partials: { 'foobar' => 'foobar' })
assert_template_result(" foobar " * (N + 1), wrap("{% include ' foobar ' %}"),
partials: { ' foobar ' => ' foobar ' })
assert_template_result(" " * (N + 1), wrap(" {% include ' ' %} "),
partials: { ' ' => ' ' })
assert_template_result(
"foobar" * (N + 1),
wrap("{% include 'foobar' %}"),
partials: { 'foobar' => 'foobar' },
)
assert_template_result(
" foobar " * (N + 1),
wrap("{% include ' foobar ' %}"),
partials: { ' foobar ' => ' foobar ' },
)
assert_template_result(
" " * (N + 1),
wrap(" {% include ' ' %} "),
partials: { ' ' => ' ' },
)
end
def test_case_is_blank
+36 -22
View File
@@ -121,14 +121,23 @@ class ContextTest < Minitest::Test
end
def test_length_query
assert_template_result("true", "{% if numbers.size == 4 %}true{% endif %}",
{ "numbers" => [1, 2, 3, 4] })
assert_template_result(
"true",
"{% if numbers.size == 4 %}true{% endif %}",
{ "numbers" => [1, 2, 3, 4] },
)
assert_template_result("true", "{% if numbers.size == 4 %}true{% endif %}",
{ "numbers" => { 1 => 1, 2 => 2, 3 => 3, 4 => 4 } })
assert_template_result(
"true",
"{% if numbers.size == 4 %}true{% endif %}",
{ "numbers" => { 1 => 1, 2 => 2, 3 => 3, 4 => 4 } },
)
assert_template_result("true", "{% if numbers.size == 1000 %}true{% endif %}",
{ "numbers" => { 1 => 1, 2 => 2, 3 => 3, 4 => 4, 'size' => 1000 } })
assert_template_result(
"true",
"{% if numbers.size == 1000 %}true{% endif %}",
{ "numbers" => { 1 => 1, 2 => 2, 3 => 3, 4 => 4, 'size' => 1000 } },
)
end
def test_hyphenated_variable
@@ -228,12 +237,14 @@ class ContextTest < Minitest::Test
end
def test_hash_to_array_transition
assigns = { 'colors' => {
'Blue' => ['003366', '336699', '6699CC', '99CCFF'],
'Green' => ['003300', '336633', '669966', '99CC99'],
'Yellow' => ['CC9900', 'FFCC00', 'FFFF99', 'FFFFCC'],
'Red' => ['660000', '993333', 'CC6666', 'FF9999'],
} }
assigns = {
'colors' => {
'Blue' => ['003366', '336699', '6699CC', '99CCFF'],
'Green' => ['003300', '336633', '669966', '99CC99'],
'Yellow' => ['CC9900', 'FFCC00', 'FFFF99', 'FFFFCC'],
'Red' => ['660000', '993333', 'CC6666', 'FF9999'],
},
}
assert_template_result("003366", "{{ colors.Blue[0] }}", assigns)
assert_template_result("FF9999", "{{ colors.Red[3] }}", assigns)
@@ -262,7 +273,7 @@ class ContextTest < Minitest::Test
assigns = { 'product' => { 'variants' => [{ 'title' => 'draft151cm' }, { 'title' => 'element151cm' }] } }
assert_template_result("draft151cm", '{{ product["variants"][0]["title"] }}', assigns)
assert_template_result("element151cm", '{{ product["variants"][1]["title"] }}', assigns)
assert_template_result("draft151cm", '{{ product["variants"][0]["title"] }}', assigns)
assert_template_result("draft151cm", '{{ product["variants"].first["title"] }}', assigns)
assert_template_result("element151cm", '{{ product["variants"].last["title"] }}', assigns)
end
@@ -410,10 +421,12 @@ class ContextTest < Minitest::Test
def test_nested_lambda_is_called_once
@global = 0
@context['callcount'] = { "lambda" => proc {
@global += 1
@global.to_s
} }
@context['callcount'] = {
"lambda" => proc {
@global += 1
@global.to_s
},
}
assert_equal('1', @context['callcount.lambda'])
assert_equal('1', @context['callcount.lambda'])
@@ -423,10 +436,11 @@ class ContextTest < Minitest::Test
def test_lambda_in_array_is_called_once
@global = 0
@context['callcount'] = [1, 2, proc {
@global += 1
@global.to_s
}, 4, 5]
p = proc {
@global += 1
@global.to_s
}
@context['callcount'] = [1, 2, p, 4, 5]
assert_equal('1', @context['callcount[2]'])
assert_equal('1', @context['callcount[2]'])
@@ -473,7 +487,7 @@ class ContextTest < Minitest::Test
def test_static_environments_are_read_with_lower_priority_than_environments
context = Context.build(
static_environments: { 'shadowed' => 'static', 'unshadowed' => 'static' },
environments: { 'shadowed' => 'dynamic' }
environments: { 'shadowed' => 'dynamic' },
)
assert_equal('dynamic', context['shadowed'])
+89 -6
View File
@@ -105,7 +105,8 @@ class ErrorHandlingTest < Minitest::Test
end
def test_parsing_warn_with_line_numbers_adds_numbers_to_lexer_errors
template = Liquid::Template.parse('
template = Liquid::Template.parse(
'
foobar
{% if 1 =! 2 %}ok{% endif %}
@@ -113,15 +114,19 @@ class ErrorHandlingTest < Minitest::Test
bla
',
error_mode: :warn,
line_numbers: true)
line_numbers: true,
)
assert_equal(['Liquid syntax error (line 4): Unexpected character = in "1 =! 2"'],
template.warnings.map(&:message))
assert_equal(
['Liquid syntax error (line 4): Unexpected character = in "1 =! 2"'],
template.warnings.map(&:message),
)
end
def test_parsing_strict_with_line_numbers_adds_numbers_to_lexer_errors
err = assert_raises(SyntaxError) do
Liquid::Template.parse('
Liquid::Template.parse(
'
foobar
{% if 1 =! 2 %}ok{% endif %}
@@ -129,7 +134,8 @@ class ErrorHandlingTest < Minitest::Test
bla
',
error_mode: :strict,
line_numbers: true)
line_numbers: true,
)
end
assert_equal('Liquid syntax error (line 4): Unexpected character = in "1 =! 2"', err.message)
@@ -257,4 +263,81 @@ class ErrorHandlingTest < Minitest::Test
output = Liquid::Template.parse("{% assign x = 0 %}{% if 1 < '2' %}{% assign x = 3 %}{% endif %}{{ x }}").render
assert_equal("0", output)
end
def test_syntax_error_is_raised_with_template_name
file_system = StubFileSystem.new("snippet" => "1\n2\n{{ 1")
context = Liquid::Context.build(
registers: { file_system: file_system },
)
template = Template.parse(
'{% render "snippet" %}',
line_numbers: true,
)
template.name = "template/index"
assert_equal(
"Liquid syntax error (snippet line 3): Variable '{{' was not properly terminated with regexp: /\\}\\}/",
template.render(context),
)
end
def test_syntax_error_is_raised_with_template_name_from_template_factory
file_system = StubFileSystem.new("snippet" => "1\n2\n{{ 1")
context = Liquid::Context.build(
registers: {
file_system: file_system,
template_factory: StubTemplateFactory.new,
},
)
template = Template.parse(
'{% render "snippet" %}',
line_numbers: true,
)
template.name = "template/index"
assert_equal(
"Liquid syntax error (some/path/snippet line 3): Variable '{{' was not properly terminated with regexp: /\\}\\}/",
template.render(context),
)
end
def test_error_is_raised_during_parse_with_template_name
depth = Liquid::Block::MAX_DEPTH + 1
code = "{% if true %}" * depth + "rendered" + "{% endif %}" * depth
template = Template.parse("{% render 'snippet' %}", line_numbers: true)
context = Liquid::Context.build(
registers: {
file_system: StubFileSystem.new("snippet" => code),
template_factory: StubTemplateFactory.new,
},
)
assert_equal("Liquid error (some/path/snippet line 1): Nesting too deep", template.render(context))
end
def test_internal_error_is_raised_with_template_name
template = Template.new
template.parse(
"{% render 'snippet' %}",
line_numbers: true,
)
template.name = "template/index"
context = Liquid::Context.build(
registers: {
file_system: StubFileSystem.new({}),
},
)
assert_equal(
"Liquid error (template/index line 1): internal",
template.render(context),
)
end
end
+2 -2
View File
@@ -32,11 +32,11 @@ class ExpressionTest < Minitest::Test
assert_match_syntax_error(
"Liquid syntax error (line 1): Invalid expression type 'false' in range expression",
"{{ (false..true) }}"
"{{ (false..true) }}",
)
assert_match_syntax_error(
"Liquid syntax error (line 1): Invalid expression type '(1..2)' in range expression",
"{{ ((1..2)..3) }}"
"{{ ((1..2)..3) }}",
)
end
+1 -1
View File
@@ -17,7 +17,7 @@ class FilterKwargTest < Minitest::Test
with_global_filter(KwargFilter) do
assert_equal(
"data-src='src' data-widths='100, 200'",
Template.parse("{{ 'img' | html_tag: data-src: 'src', data-widths: '100, 200' }}").render(nil, nil)
Template.parse("{{ 'img' | html_tag: data-src: 'src', data-widths: '100, 200' }}").render(nil, nil),
)
end
end
+35 -14
View File
@@ -68,22 +68,34 @@ class FiltersTest < Minitest::Test
def test_sort
assert_template_result("1 2 3 4", "{{numbers | sort | join}}", { "numbers" => [2, 1, 4, 3] })
assert_template_result("alphabetic as expected", "{{words | sort | join}}",
{ "words" => ['expected', 'as', 'alphabetic'] })
assert_template_result(
"alphabetic as expected",
"{{words | sort | join}}",
{ "words" => ['expected', 'as', 'alphabetic'] },
)
assert_template_result("3", "{{value | sort}}", { "value" => 3 })
assert_template_result('are flower', "{{arrays | sort | join}}", { 'arrays' => ['flower', 'are'] })
assert_template_result("Expected case sensitive", "{{case_sensitive | sort | join}}",
{ "case_sensitive" => ["sensitive", "Expected", "case"] })
assert_template_result(
"Expected case sensitive",
"{{case_sensitive | sort | join}}",
{ "case_sensitive" => ["sensitive", "Expected", "case"] },
)
end
def test_sort_natural
# Test strings
assert_template_result("Assert case Insensitive", "{{words | sort_natural | join}}",
{ "words" => ["case", "Assert", "Insensitive"] })
assert_template_result(
"Assert case Insensitive",
"{{words | sort_natural | join}}",
{ "words" => ["case", "Assert", "Insensitive"] },
)
# Test hashes
assert_template_result("A b C", "{{hashes | sort_natural: 'a' | map: 'a' | join}}",
{ "hashes" => [{ "a" => "A" }, { "a" => "b" }, { "a" => "C" }] })
assert_template_result(
"A b C",
"{{hashes | sort_natural: 'a' | map: 'a' | join}}",
{ "hashes" => [{ "a" => "A" }, { "a" => "b" }, { "a" => "C" }] },
)
# Test objects
@context['objects'] = [TestObject.new('A'), TestObject.new('b'), TestObject.new('C')]
@@ -92,12 +104,18 @@ class FiltersTest < Minitest::Test
def test_compact
# Test strings
assert_template_result("a b c", "{{words | compact | join}}",
{ "words" => ['a', nil, 'b', nil, 'c'] })
assert_template_result(
"a b c",
"{{words | compact | join}}",
{ "words" => ['a', nil, 'b', nil, 'c'] },
)
# Test hashes
assert_template_result("A C", "{{hashes | compact: 'a' | map: 'a' | join}}",
{ "hashes" => [{ "a" => "A" }, { "a" => nil }, { "a" => "C" }] })
assert_template_result(
"A C",
"{{hashes | compact: 'a' | map: 'a' | join}}",
{ "hashes" => [{ "a" => "A" }, { "a" => nil }, { "a" => "C" }] },
)
# Test objects
@context['objects'] = [TestObject.new('A'), TestObject.new(nil), TestObject.new('C')]
@@ -109,8 +127,11 @@ class FiltersTest < Minitest::Test
end
def test_strip_html_ignore_comments_with_html
assert_template_result("bla blub", "{{ var | strip_html }}",
{ "var" => "<!-- split and some <ul> tag --><b>bla blub</a>" })
assert_template_result(
"bla blub",
"{{ var | strip_html }}",
{ "var" => "<!-- split and some <ul> tag --><b>bla blub</a>" },
)
end
def test_capitalize
+223
View File
@@ -0,0 +1,223 @@
# frozen_string_literal: true
require "test_helper"
require "json"
class HashTest < Minitest::Test
include Liquid
class HashSubclass < Hash
def []=(key, value)
super(fmt_key(key), value)
end
def [](key)
super(fmt_key(key))
end
def key?(key)
super(fmt_key(key))
end
def include?(key)
super(fmt_key(key))
end
def to_s
super.upcase
end
private
def fmt_key(key)
return key unless key.is_a?(String)
key.upcase
end
end
def test_size
assert_template_result("0", "{{h.size}}", { "h" => {} })
assert_template_result("1", "{{h.size}}", { "h" => { "a" => 1 } })
end
def test_first
assert_template_result("", "{{h.first}}", { "h" => {} })
assert_template_result("a1", "{{h.first}}", { "h" => { "a" => 1 } })
end
def test_last
assert_template_result("", "{{h.last}}", { "h" => { "a" => 1 } })
end
def test_index
assert_template_result("", "{{h['a']}}", { "h" => {} })
assert_template_result("1", "{{h['a']}}", { "h" => { "a" => 1 } })
end
def test_to_s
h = { "a" => 1, "b" => [1, { "c" => 2 }] }
assert_template_result("{\"a\"=>1, \"b\"=>[1, {\"c\"=>2}]}", "{{h}}", { "h" => h })
end
def test_auto_methods
h1 = { "a" => 1 }
h2 = { "last" => 10, "first" => 11, "size" => 12 }
assert_template_result("1,nonblank", "{{h.size}},{%if h['size'] == blank%}blank{%else%}nonblank{%endif%}", { "h" => h1 })
assert_template_result("12,nonblank", "{{h.size}},{%if h['size'] == blank%}blank{%else%}nonblank{%endif%}", { "h" => h2 })
assert_template_result("a1,", "{{h.first}},{{h['first']}}", { "h" => h1 })
assert_template_result("11,11", "{{h.first}},{{h['first']}}", { "h" => h2 })
assert_template_result(",", "{{h.last}},{{h['last']}}", { "h" => h1 })
assert_template_result("10,10", "{{h.last}},{{h['last']}}", { "h" => h2 })
end
def test_equality
h0 = {}
h1 = { "a" => 1 }
h2 = { "a" => 1, "b" => 2 }
h3 = { "a" => 1 }
hashes = [h0, h1, h2, h3]
hashes.each_with_index do |a, i|
hashes.each_with_index do |b, j|
prefix = "(#{i},#{j})"
assert_template_result(
prefix + (a == b ? "y" : "n"),
"#{prefix}{% if a == b %}y{%else%}n{%endif%}",
{ "a" => a, "b" => b },
)
end
end
end
def test_contains
h = { "a" => 1, "b" => { "c" => "d" }, "3" => ["f", 2] }
tpl = "{%if h contains b%}y{%else%}n{%endif%}"
assert_template_result("y", tpl, { "h" => h, "b" => "a" })
# assert_template_result("y", tpl, h, { "h" => { "b" => "a".html_safe } })
assert_template_result("y", tpl, { "h" => h, "b" => "a".b })
assert_template_result("n", tpl, { "h" => h, "b" => "A" })
# assert_template_result("n", tpl, h, { "h" => { "b" => "A".html_safe } })
assert_template_result("n", tpl, { "h" => h, "b" => "A".b })
assert_template_result("n", tpl, { "h" => h, "b" => 1 })
assert_template_result("n", tpl, { "h" => h, "b" => "1" })
assert_template_result("n", tpl, { "h" => h, "b" => 3 })
assert_template_result("y", tpl, { "h" => h, "b" => "3" })
end
def test_empty
assert_template_result("y", "{%if h == empty%}y{%else%}n{%endif%}", { "h" => {} })
assert_template_result("n", "{%if h == empty%}y{%else%}n{%endif%}", { "h" => { "a" => 1 } })
end
def test_blank
assert_template_result("y", "{%if h == blank%}y{%else%}n{%endif%}", { "h" => {} })
assert_template_result("n", "{%if h == blank%}y{%else%}n{%endif%}", { "h" => { "a" => 1 } })
end
def test_iter
h0 = { "a" => 1, "b" => { "c" => "d" }, "3" => ["f", 2] }
assert_template_result("a1b{\"c\"=>\"d\"}3f2", "{%for i in h%}{{i}}{%endfor%}", { "h" => h0 })
end
def test_output
h0 = {}
h1 = { "a" => 1, "b" => { "c" => "d" }, "3" => ["f", 2] }
assert_template_result("{}", "{{h}}", { "h" => h0 })
assert_template_result("{\"a\"=>1, \"b\"=>{\"c\"=>\"d\"}, \"3\"=>[\"f\", 2]}", "{{h}}", { "h" => h1 })
end
def test_integer_key
h = { 2 => "huh" }
assert_template_result("huh", "{{h[2]}}", { "h" => h })
assert_template_result("huh", "{{h[b]}}", { "h" => h, "b" => 2 })
assert_template_result("", "{{h[b]}}", { "h" => h, "b" => "2" })
end
def test_nil_key
assert_hash_roundtrip(nil, nil)
refute_hash_roundtrip(nil, "nil")
refute_hash_roundtrip(nil, "null")
end
def test_bool_key
assert_hash_roundtrip(true, true)
refute_hash_roundtrip(true, false)
refute_hash_roundtrip(true, "true")
refute_hash_roundtrip(true, nil)
end
def test_int_key
assert_hash_roundtrip(1, 1)
refute_hash_roundtrip(1, 2)
refute_hash_roundtrip(1, "1")
refute_hash_roundtrip(1, 1.0000000000001)
end
def test_string_key
assert_hash_roundtrip("word", "word")
# assert_hash_roundtrip("word", "word".html_safe)
assert_hash_roundtrip("word", "word".b)
refute_hash_roundtrip("word", "word ")
refute_hash_roundtrip("word", nil)
refute_hash_roundtrip("word", true)
end
def test_weird_keys
assert_hash_roundtrip([1, 2], [1, 2])
refute_hash_roundtrip([1, 2], [1, 3])
assert_hash_roundtrip({ "a" => 1 }, { "a" => 1 })
refute_hash_roundtrip({ "a" => 1 }, { "a" => 2 })
o = Struct.new(:to_liquid).new(Object.new)
# failed_hash_roundtrip(o, o)
refute_hash_roundtrip(o, 1)
refute_hash_roundtrip(o, nil)
assert_hash_roundtrip("\xff".b, "\xff".b)
assert_hash_roundtrip(1..2, 1..2)
refute_hash_roundtrip(1..2, 1...2)
assert_hash_roundtrip(
111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111,
111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111,
)
# f64 hashing isn't perfect but we can make some basic assertions.
assert_hash_roundtrip(1.1234, 1.1234)
refute_hash_roundtrip(1.1234, 1.1)
end
def test_hash_html_key
# I mean this is probably not ideal but this is the way liquid works.
assert_template_result("{\"a<script>b\"=>\"y\"}", "{{h}}", { "h" => { "a<script>b" => "y" } })
end
def test_hash_subclass
hs = HashSubclass.new
hs["A"] = "b"
assert_template_result("b", "{{h['a']}}", { "h" => hs })
assert_template_result("b", "{{h['A']}}", { "h" => hs })
assert_template_result("", "{{h['b']}}", { "h" => hs })
assert_template_result("yes", "{% if h contains 'a' %}yes{%else%}no{%endif%}", { "h" => hs })
assert_template_result('{"A"=>"B"}', "{{h}}", { "h" => hs })
end
private
def failed_hash_roundtrip(key, test)
assert_template_result(
"Liquid error (templates/index line 1): internal",
"{{h[b]}}",
{ "h" => { key => "y" }, "b" => test },
)
end
def assert_hash_roundtrip(key, test)
assert_template_result("y", "{{h[b]}}", { "h" => { key => "y" }, "b" => test })
end
def refute_hash_roundtrip(key, test)
assert_template_result("", "{{h[b]}}", { "h" => { key => "y" }, "b" => test })
end
end
+79 -7
View File
@@ -188,7 +188,7 @@ class StandardFiltersTest < Minitest::Test
def test_base64_url_safe_encode
assert_equal(
'YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXogQUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVogMTIzNDU2Nzg5MCAhQCMkJV4mKigpLT1fKy8_Ljo7W117fVx8',
@filters.base64_url_safe_encode('abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ 1234567890 !@#$%^&*()-=_+/?.:;[]{}\|')
@filters.base64_url_safe_encode('abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ 1234567890 !@#$%^&*()-=_+/?.:;[]{}\|'),
)
assert_equal('', @filters.base64_url_safe_encode(nil))
end
@@ -196,7 +196,7 @@ class StandardFiltersTest < Minitest::Test
def test_base64_url_safe_decode
assert_equal(
'abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ 1234567890 !@#$%^&*()-=_+/?.:;[]{}\|',
@filters.base64_url_safe_decode('YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXogQUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVogMTIzNDU2Nzg5MCAhQCMkJV4mKigpLT1fKy8_Ljo7W117fVx8')
@filters.base64_url_safe_decode('YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXogQUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVogMTIzNDU2Nzg5MCAhQCMkJV4mKigpLT1fKy8_Ljo7W117fVx8'),
)
exception = assert_raises(Liquid::ArgumentError) do
@filters.base64_url_safe_decode("invalidbase64")
@@ -230,7 +230,7 @@ class StandardFiltersTest < Minitest::Test
assert_equal('one two three', @filters.truncatewords('one two three'))
assert_equal(
'Two small (13&#8221; x 5.5&#8221; x 10&#8221; high) baskets fit inside one large basket (13&#8221;...',
@filters.truncatewords('Two small (13&#8221; x 5.5&#8221; x 10&#8221; high) baskets fit inside one large basket (13&#8221; x 16&#8221; x 10.5&#8221; high) with cover.', 15)
@filters.truncatewords('Two small (13&#8221; x 5.5&#8221; x 10&#8221; high) baskets fit inside one large basket (13&#8221; x 16&#8221; x 10.5&#8221; high) with cover.', 15),
)
assert_equal("测试测试测试测试", @filters.truncatewords('测试测试测试测试', 5))
assert_equal('one two1', @filters.truncatewords("one two three", 2, 1))
@@ -433,8 +433,11 @@ class StandardFiltersTest < Minitest::Test
def test_map
assert_equal([1, 2, 3, 4], @filters.map([{ "a" => 1 }, { "a" => 2 }, { "a" => 3 }, { "a" => 4 }], 'a'))
assert_template_result('abc', "{{ ary | map:'foo' | map:'bar' }}",
{ 'ary' => [{ 'foo' => { 'bar' => 'a' } }, { 'foo' => { 'bar' => 'b' } }, { 'foo' => { 'bar' => 'c' } }] })
assert_template_result(
'abc',
"{{ ary | map:'foo' | map:'bar' }}",
{ 'ary' => [{ 'foo' => { 'bar' => 'a' } }, { 'foo' => { 'bar' => 'b' } }, { 'foo' => { 'bar' => 'c' } }] },
)
end
def test_map_doesnt_call_arbitrary_stuff
@@ -458,8 +461,11 @@ class StandardFiltersTest < Minitest::Test
end
def test_map_on_hashes
assert_template_result("4217", '{{ thing | map: "foo" | map: "bar" }}',
{ "thing" => { "foo" => [{ "bar" => 42 }, { "bar" => 17 }] } })
assert_template_result(
"4217",
'{{ thing | map: "foo" | map: "bar" }}',
{ "thing" => { "foo" => [{ "bar" => 42 }, { "bar" => 17 }] } },
)
end
def test_legacy_map_on_hashes_with_dynamic_key
@@ -922,6 +928,72 @@ class StandardFiltersTest < Minitest::Test
assert_equal([{ "foo" => true }, { "foo" => "for sure" }], @filters.where(input, "foo"))
end
def test_sum_with_all_numbers
input = [1, 2]
assert_equal(3, @filters.sum(input))
assert_raises(Liquid::ArgumentError, "cannot select the property 'quantity'") do
@filters.sum(input, "quantity")
end
end
def test_sum_with_numeric_strings
input = [1, 2, "3", "4"]
assert_equal(10, @filters.sum(input))
assert_raises(Liquid::ArgumentError, "cannot select the property 'quantity'") do
@filters.sum(input, "quantity")
end
end
def test_sum_with_nested_arrays
input = [1, [2, [3, 4]]]
assert_equal(10, @filters.sum(input))
assert_raises(Liquid::ArgumentError, "cannot select the property 'quantity'") do
@filters.sum(input, "quantity")
end
end
def test_sum_with_indexable_map_values
input = [{ "quantity" => 1 }, { "quantity" => 2, "weight" => 3 }, { "weight" => 4 }]
assert_equal(0, @filters.sum(input))
assert_equal(3, @filters.sum(input, "quantity"))
assert_equal(7, @filters.sum(input, "weight"))
assert_equal(0, @filters.sum(input, "subtotal"))
end
def test_sum_with_indexable_non_map_values
input = [1, [2], "foo", { "quantity" => 3 }]
assert_equal(3, @filters.sum(input))
assert_raises(Liquid::ArgumentError, "cannot select the property 'quantity'") do
@filters.sum(input, "quantity")
end
end
def test_sum_with_unindexable_values
input = [1, true, nil, { "quantity" => 2 }]
assert_equal(1, @filters.sum(input))
assert_raises(Liquid::ArgumentError, "cannot select the property 'quantity'") do
@filters.sum(input, "quantity")
end
end
def test_sum_without_property_calls_to_liquid
t = TestThing.new
Liquid::Template.parse('{{ foo | sum }}').render("foo" => [t])
assert(t.foo > 0)
end
def test_sum_with_property_calls_to_liquid_on_property_values
t = TestThing.new
Liquid::Template.parse('{{ foo | sum: "quantity" }}').render("foo" => [{ "quantity" => t }])
assert(t.foo > 0)
end
private
def with_timezone(tz)
+29 -14
View File
@@ -73,9 +73,11 @@ HERE
def test_for_helpers
assigns = { 'array' => [1, 2, 3] }
assert_template_result(' 1/3 2/3 3/3 ',
assert_template_result(
' 1/3 2/3 3/3 ',
'{%for item in array%} {{forloop.index}}/{{forloop.length}} {%endfor%}',
assigns)
assigns,
)
assert_template_result(' 1 2 3 ', '{%for item in array%} {{forloop.index}} {%endfor%}', assigns)
assert_template_result(' 0 1 2 ', '{%for item in array%} {{forloop.index0}} {%endfor%}', assigns)
assert_template_result(' 2 1 0 ', '{%for item in array%} {{forloop.rindex0}} {%endfor%}', assigns)
@@ -86,9 +88,11 @@ HERE
def test_for_and_if
assigns = { 'array' => [1, 2, 3] }
assert_template_result('+--',
assert_template_result(
'+--',
'{%for item in array%}{% if forloop.first %}+{% else %}-{% endif %}{%endfor%}',
assigns)
assigns,
)
end
def test_for_else
@@ -103,6 +107,7 @@ HERE
assert_template_result('1234', '{%for i in array limit:4 %}{{ i }}{%endfor%}', assigns)
assert_template_result('3456', '{%for i in array limit:4 offset:2 %}{{ i }}{%endfor%}', assigns)
assert_template_result('3456', '{%for i in array limit: 4 offset: 2 %}{{ i }}{%endfor%}', assigns)
assert_template_result('3456', '{%for i in array, limit: 4, offset: 2 %}{{ i }}{%endfor%}', assigns)
end
def test_limiting_with_invalid_limit
@@ -324,15 +329,20 @@ HERE
# ruby 1.9.3 no longer supports .each on String though we mimic
# the functionality for backwards compatibility
assert_template_result('test string',
assert_template_result(
'test string',
'{%for val in string%}{{val}}{%endfor%}',
{ 'string' => "test string" })
{ 'string' => "test string" },
)
assert_template_result('test string',
assert_template_result(
'test string',
'{%for val in string limit:1%}{{val}}{%endfor%}',
{ 'string' => "test string" })
{ 'string' => "test string" },
)
assert_template_result('val-string-1-1-0-1-0-true-true-test string',
assert_template_result(
'val-string-1-1-0-1-0-true-true-test string',
'{%for val in string%}' \
'{{forloop.name}}-' \
'{{forloop.index}}-' \
@@ -343,23 +353,28 @@ HERE
'{{forloop.first}}-' \
'{{forloop.last}}-' \
'{{val}}{%endfor%}',
{ 'string' => "test string" })
{ 'string' => "test string" },
)
end
def test_for_parentloop_references_parent_loop
assert_template_result('1.1 1.2 1.3 2.1 2.2 2.3 ',
assert_template_result(
'1.1 1.2 1.3 2.1 2.2 2.3 ',
'{% for inner in outer %}{% for k in inner %}' \
'{{ forloop.parentloop.index }}.{{ forloop.index }} ' \
'{% endfor %}{% endfor %}',
{ 'outer' => [[1, 1, 1], [1, 1, 1]] })
{ 'outer' => [[1, 1, 1], [1, 1, 1]] },
)
end
def test_for_parentloop_nil_when_not_present
assert_template_result('.1 .2 ',
assert_template_result(
'.1 .2 ',
'{% for inner in outer %}' \
'{{ forloop.parentloop.index }}.{{ forloop.index }} ' \
'{% endfor %}',
{ 'outer' => [[1, 1, 1], [1, 1, 1]] })
{ 'outer' => [[1, 1, 1], [1, 1, 1]] },
)
end
def test_inner_for_over_empty_input
+17 -9
View File
@@ -7,8 +7,10 @@ class IfElseTagTest < Minitest::Test
def test_if
assert_template_result(' ', ' {% if false %} this text should not go into the output {% endif %} ')
assert_template_result(' this text should go into the output ',
' {% if true %} this text should go into the output {% endif %} ')
assert_template_result(
' this text should go into the output ',
' {% if true %} this text should go into the output {% endif %} ',
)
assert_template_result(' you rock ?', '{% if false %} you suck {% endif %} {% if true %} you rock {% endif %}?')
end
@@ -51,18 +53,22 @@ class IfElseTagTest < Minitest::Test
def test_comparison_of_expressions_starting_with_and_or_or
assigns = { 'order' => { 'items_count' => 0 }, 'android' => { 'name' => 'Roy' } }
assert_template_result("YES",
assert_template_result(
"YES",
"{% if android.name == 'Roy' %}YES{% endif %}",
assigns)
assert_template_result("YES",
assigns,
)
assert_template_result(
"YES",
"{% if order.items_count == 0 %}YES{% endif %}",
assigns)
assigns,
)
end
def test_if_and
assert_template_result(' YES ', '{% if true and true %} YES {% endif %}')
assert_template_result('', '{% if false and true %} YES {% endif %}')
assert_template_result('', '{% if false and true %} YES {% endif %}')
assert_template_result('', '{% if true and false %} YES {% endif %}')
end
def test_hash_miss_generates_false
@@ -155,8 +161,10 @@ class IfElseTagTest < Minitest::Test
original_op = Condition.operators['contains']
Condition.operators['contains'] = :[]
assert_template_result('yes',
%({% if 'gnomeslab-and-or-liquid' contains 'gnomeslab-and-or-liquid' %}yes{% endif %}))
assert_template_result(
'yes',
%({% if 'gnomeslab-and-or-liquid' contains 'gnomeslab-and-or-liquid' %}yes{% endif %}),
)
ensure
Condition.operators['contains'] = original_op
end
+154 -59
View File
@@ -58,65 +58,88 @@ class IncludeTagTest < Minitest::Test
end
def test_include_tag_looks_for_file_system_in_registers_first
assert_equal('from OtherFileSystem',
Template.parse("{% include 'pick_a_source' %}").render!({}, registers: { file_system: OtherFileSystem.new }))
assert_equal(
'from OtherFileSystem',
Template.parse("{% include 'pick_a_source' %}").render!({}, registers: { file_system: OtherFileSystem.new }),
)
end
def test_include_tag_with
assert_template_result("Product: Draft 151cm ",
assert_template_result(
"Product: Draft 151cm ",
"{% include 'product' with products[0] %}",
{ "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }] },
partials: { "product" => "Product: {{ product.title }} " })
partials: { "product" => "Product: {{ product.title }} " },
)
end
def test_include_tag_with_alias
assert_template_result("Product: Draft 151cm ",
assert_template_result(
"Product: Draft 151cm ",
"{% include 'product_alias' with products[0] as product %}",
{ "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }] },
partials: { "product_alias" => "Product: {{ product.title }} " })
partials: { "product_alias" => "Product: {{ product.title }} " },
)
end
def test_include_tag_for_alias
assert_template_result("Product: Draft 151cm Product: Element 155cm ",
assert_template_result(
"Product: Draft 151cm Product: Element 155cm ",
"{% include 'product_alias' for products as product %}",
{ "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }] },
partials: { "product_alias" => "Product: {{ product.title }} " })
partials: { "product_alias" => "Product: {{ product.title }} " },
)
end
def test_include_tag_with_default_name
assert_template_result("Product: Draft 151cm ",
"{% include 'product' %}", { "product" => { 'title' => 'Draft 151cm' } },
partials: { "product" => "Product: {{ product.title }} " })
assert_template_result(
"Product: Draft 151cm ",
"{% include 'product' %}",
{ "product" => { 'title' => 'Draft 151cm' } },
partials: { "product" => "Product: {{ product.title }} " },
)
end
def test_include_tag_for
assert_template_result("Product: Draft 151cm Product: Element 155cm ",
assert_template_result(
"Product: Draft 151cm Product: Element 155cm ",
"{% include 'product' for products %}",
{ "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }] },
partials: { "product" => "Product: {{ product.title }} " })
partials: { "product" => "Product: {{ product.title }} " },
)
end
def test_include_tag_with_local_variables
assert_template_result("Locale: test123 ", "{% include 'locale_variables' echo1: 'test123' %}",
partials: { "locale_variables" => "Locale: {{echo1}} {{echo2}}" })
assert_template_result(
"Locale: test123 ",
"{% include 'locale_variables' echo1: 'test123' %}",
partials: { "locale_variables" => "Locale: {{echo1}} {{echo2}}" },
)
end
def test_include_tag_with_multiple_local_variables
assert_template_result("Locale: test123 test321",
assert_template_result(
"Locale: test123 test321",
"{% include 'locale_variables' echo1: 'test123', echo2: 'test321' %}",
partials: { "locale_variables" => "Locale: {{echo1}} {{echo2}}" })
partials: { "locale_variables" => "Locale: {{echo1}} {{echo2}}" },
)
end
def test_include_tag_with_multiple_local_variables_from_context
assert_template_result("Locale: test123 test321",
assert_template_result(
"Locale: test123 test321",
"{% include 'locale_variables' echo1: echo1, echo2: more_echos.echo2 %}",
{ 'echo1' => 'test123', 'more_echos' => { "echo2" => 'test321' } },
partials: { "locale_variables" => "Locale: {{echo1}} {{echo2}}" })
partials: { "locale_variables" => "Locale: {{echo1}} {{echo2}}" },
)
end
def test_included_templates_assigns_variables
assert_template_result("bar", "{% include 'assignments' %}{{ foo }}",
partials: { 'assignments' => "{% assign foo = 'bar' %}" })
assert_template_result(
"bar",
"{% include 'assignments' %}{{ foo }}",
partials: { 'assignments' => "{% assign foo = 'bar' %}" },
)
end
def test_nested_include_tag
@@ -137,13 +160,19 @@ class IncludeTagTest < Minitest::Test
"details" => "details",
}
assert_template_result("Product: Draft 151cm details ",
"{% include 'nested_product_template' with product %}", { "product" => { "title" => 'Draft 151cm' } },
partials: partials)
assert_template_result(
"Product: Draft 151cm details ",
"{% include 'nested_product_template' with product %}",
{ "product" => { "title" => 'Draft 151cm' } },
partials: partials,
)
assert_template_result("Product: Draft 151cm details Product: Element 155cm details ",
"{% include 'nested_product_template' for products %}", { "products" => [{ "title" => 'Draft 151cm' }, { "title" => 'Element 155cm' }] },
partials: partials)
assert_template_result(
"Product: Draft 151cm details Product: Element 155cm details ",
"{% include 'nested_product_template' for products %}",
{ "products" => [{ "title" => 'Draft 151cm' }, { "title" => 'Element 155cm' }] },
partials: partials,
)
end
def test_recursively_included_template_does_not_produce_endless_loop
@@ -161,46 +190,68 @@ class IncludeTagTest < Minitest::Test
end
def test_dynamically_choosen_template
assert_template_result("Test123", "{% include template %}", { "template" => 'Test123' },
partials: { "Test123" => "Test123" })
assert_template_result(
"Test123",
"{% include template %}",
{ "template" => 'Test123' },
partials: { "Test123" => "Test123" },
)
assert_template_result("Test321", "{% include template %}", { "template" => 'Test321' },
partials: { "Test321" => "Test321" })
assert_template_result(
"Test321",
"{% include template %}",
{ "template" => 'Test321' },
partials: { "Test321" => "Test321" },
)
assert_template_result("Product: Draft 151cm ", "{% include template for product %}",
assert_template_result(
"Product: Draft 151cm ",
"{% include template for product %}",
{ "template" => 'product', 'product' => { 'title' => 'Draft 151cm' } },
partials: { "product" => "Product: {{ product.title }} " })
partials: { "product" => "Product: {{ product.title }} " },
)
end
def test_include_tag_caches_second_read_of_same_partial
file_system = CountingFileSystem.new
assert_equal('from CountingFileSystemfrom CountingFileSystem',
Template.parse("{% include 'pick_a_source' %}{% include 'pick_a_source' %}").render!({}, registers: { file_system: file_system }))
assert_equal(
'from CountingFileSystemfrom CountingFileSystem',
Template.parse("{% include 'pick_a_source' %}{% include 'pick_a_source' %}").render!({}, registers: { file_system: file_system }),
)
assert_equal(1, file_system.count)
end
def test_include_tag_doesnt_cache_partials_across_renders
file_system = CountingFileSystem.new
assert_equal('from CountingFileSystem',
Template.parse("{% include 'pick_a_source' %}").render!({}, registers: { file_system: file_system }))
assert_equal(
'from CountingFileSystem',
Template.parse("{% include 'pick_a_source' %}").render!({}, registers: { file_system: file_system }),
)
assert_equal(1, file_system.count)
assert_equal('from CountingFileSystem',
Template.parse("{% include 'pick_a_source' %}").render!({}, registers: { file_system: file_system }))
assert_equal(
'from CountingFileSystem',
Template.parse("{% include 'pick_a_source' %}").render!({}, registers: { file_system: file_system }),
)
assert_equal(2, file_system.count)
end
def test_include_tag_within_if_statement
assert_template_result("foo_if_true", "{% if true %}{% include 'foo_if_true' %}{% endif %}",
partials: { "foo_if_true" => "foo_if_true" })
assert_template_result(
"foo_if_true",
"{% if true %}{% include 'foo_if_true' %}{% endif %}",
partials: { "foo_if_true" => "foo_if_true" },
)
end
def test_custom_include_tag
original_tag = Liquid::Template.tags['include']
Liquid::Template.tags['include'] = CustomInclude
begin
assert_equal("custom_foo",
Template.parse("{% include 'custom_foo' %}").render!)
assert_equal(
"custom_foo",
Template.parse("{% include 'custom_foo' %}").render!,
)
ensure
Liquid::Template.tags['include'] = original_tag
end
@@ -210,8 +261,10 @@ class IncludeTagTest < Minitest::Test
original_tag = Liquid::Template.tags['include']
Liquid::Template.tags['include'] = CustomInclude
begin
assert_equal("custom_foo_if_true",
Template.parse("{% if true %}{% include 'custom_foo_if_true' %}{% endif %}").render!)
assert_equal(
"custom_foo_if_true",
Template.parse("{% if true %}{% include 'custom_foo_if_true' %}{% endif %}").render!,
)
ensure
Liquid::Template.tags['include'] = original_tag
end
@@ -242,31 +295,49 @@ class IncludeTagTest < Minitest::Test
end
def test_render_raise_argument_error_when_template_is_undefined
assert_template_result("Liquid error (line 1): Argument error in tag 'include' - Illegal template name",
"{% include undefined_variable %}", render_errors: true)
assert_template_result(
"Liquid error (line 1): Argument error in tag 'include' - Illegal template name",
"{% include undefined_variable %}",
render_errors: true,
)
assert_template_result("Liquid error (line 1): Argument error in tag 'include' - Illegal template name",
"{% include nil %}", render_errors: true)
assert_template_result(
"Liquid error (line 1): Argument error in tag 'include' - Illegal template name",
"{% include nil %}",
render_errors: true,
)
end
def test_render_raise_argument_error_when_template_is_not_a_string
assert_template_result("Liquid error (line 1): Argument error in tag 'include' - Illegal template name",
"{% include 123 %}", render_errors: true)
assert_template_result(
"Liquid error (line 1): Argument error in tag 'include' - Illegal template name",
"{% include 123 %}",
render_errors: true,
)
end
def test_including_via_variable_value
assert_template_result("from TestFileSystem", "{% assign page = 'pick_a_source' %}{% include page %}",
partials: { "pick_a_source" => "from TestFileSystem" })
assert_template_result(
"from TestFileSystem",
"{% assign page = 'pick_a_source' %}{% include page %}",
partials: { "pick_a_source" => "from TestFileSystem" },
)
partials = { "product" => "Product: {{ product.title }} " }
assert_template_result("Product: Draft 151cm ", "{% assign page = 'product' %}{% include page %}",
assert_template_result(
"Product: Draft 151cm ",
"{% assign page = 'product' %}{% include page %}",
{ "product" => { 'title' => 'Draft 151cm' } },
partials: partials)
partials: partials,
)
assert_template_result("Product: Draft 151cm ", "{% assign page = 'product' %}{% include page for foo %}",
assert_template_result(
"Product: Draft 151cm ",
"{% assign page = 'product' %}{% include page for foo %}",
{ "foo" => { 'title' => 'Draft 151cm' } },
partials: partials)
partials: partials,
)
end
def test_including_with_strict_variables
@@ -279,7 +350,31 @@ class IncludeTagTest < Minitest::Test
def test_break_through_include
assert_template_result("1", "{% for i in (1..3) %}{{ i }}{% break %}{{ i }}{% endfor %}")
assert_template_result("1", "{% for i in (1..3) %}{{ i }}{% include 'break' %}{{ i }}{% endfor %}",
partials: { 'break' => "{% break %}" })
assert_template_result(
"1",
"{% for i in (1..3) %}{{ i }}{% include 'break' %}{{ i }}{% endfor %}",
partials: { 'break' => "{% break %}" },
)
end
def test_render_tag_renders_error_with_template_name
assert_template_result(
'Liquid error (foo line 1): standard error',
"{% include 'foo' with errors %}",
{ 'errors' => ErrorDrop.new },
partials: { 'foo' => '{{ foo.standard_error }}' },
render_errors: true,
)
end
def test_render_tag_renders_error_with_template_name_from_template_factory
assert_template_result(
'Liquid error (some/path/foo line 1): standard error',
"{% include 'foo' with errors %}",
{ 'errors' => ErrorDrop.new },
partials: { 'foo' => '{{ foo.standard_error }}' },
template_factory: StubTemplateFactory.new,
render_errors: true,
)
end
end # IncludeTagTest
+8 -4
View File
@@ -8,19 +8,23 @@ class IncrementTagTest < Minitest::Test
def test_inc
assert_template_result('0 1', '{%increment port %} {{ port }}')
assert_template_result(' 0 1 2', '{{port}} {%increment port %} {%increment port%} {{port}}')
assert_template_result('0 0 1 2 1',
assert_template_result(
'0 0 1 2 1',
'{%increment port %} {%increment starboard%} ' \
'{%increment port %} {%increment port%} ' \
'{%increment starboard %}')
'{%increment starboard %}',
)
end
def test_dec
assert_template_result('-1 -1', '{%decrement port %} {{ port }}', { 'port' => 10 })
assert_template_result(' -1 -2 -2', '{{port}} {%decrement port %} {%decrement port%} {{port}}')
assert_template_result('0 1 2 0 3 1 1 3',
assert_template_result(
'0 1 2 0 3 1 1 3',
'{%increment starboard %} {%increment starboard%} {%increment starboard%} ' \
'{%increment port %} {%increment starboard%} ' \
'{%increment port %} {%decrement port%} ' \
'{%decrement starboard %}')
'{%decrement starboard %}',
)
end
end
+33
View File
@@ -113,4 +113,37 @@ class LiquidTagTest < Minitest::Test
{% raw %}{% liquid echo 'test' %}{% endraw %}
LIQUID
end
def test_nested_liquid_tags
assert_template_result('good', <<~LIQUID)
{%- liquid
liquid
if true
echo "good"
endif
-%}
LIQUID
end
def test_nested_liquid_tags_on_same_line
assert_template_result('good', <<~LIQUID)
{%- liquid liquid liquid echo "good" -%}
LIQUID
end
def test_nested_liquid_liquid_is_not_skipped_if_used_in_non_tag_position
assert_template_result('liquid', <<~LIQUID, { 'liquid' => 'liquid' })
{%- liquid liquid liquid echo liquid -%}
LIQUID
end
def test_next_liquid_with_unclosed_if_tag
assert_match_syntax_error("Liquid syntax error (line 2): 'if' tag was never closed", <<~LIQUID)
{%- liquid
liquid if true
echo "good"
endif
-%}
LIQUID
end
end
+7 -3
View File
@@ -6,12 +6,16 @@ class RawTagTest < Minitest::Test
include Liquid
def test_tag_in_raw
assert_template_result('{% comment %} test {% endcomment %}',
'{% raw %}{% comment %} test {% endcomment %}{% endraw %}')
assert_template_result(
'{% comment %} test {% endcomment %}',
'{% raw %}{% comment %} test {% endcomment %}{% endraw %}',
)
end
def test_output_in_raw
assert_template_result('{{ test }}', '{% raw %}{{ test }}{% endraw %}')
assert_template_result('>{{ test }}<', '> {%- raw -%}{{ test }}{%- endraw -%} <')
assert_template_result("> inner <", "> {%- raw -%} inner {%- endraw %} <")
assert_template_result("> inner <", "> {%- raw -%} inner {%- endraw -%} <")
end
def test_open_tag_in_raw
+127 -48
View File
@@ -6,52 +6,79 @@ class RenderTagTest < Minitest::Test
include Liquid
def test_render_with_no_arguments
assert_template_result('rendered content', '{% render "source" %}',
partials: { 'source' => 'rendered content' })
assert_template_result(
'rendered content',
'{% render "source" %}',
partials: { 'source' => 'rendered content' },
)
end
def test_render_tag_looks_for_file_system_in_registers_first
assert_template_result('from register file system', '{% render "pick_a_source" %}',
partials: { 'pick_a_source' => 'from register file system' })
assert_template_result(
'from register file system',
'{% render "pick_a_source" %}',
partials: { 'pick_a_source' => 'from register file system' },
)
end
def test_render_passes_named_arguments_into_inner_scope
assert_template_result('My Product', '{% render "product", inner_product: outer_product %}',
assert_template_result(
'My Product',
'{% render "product", inner_product: outer_product %}',
{ 'outer_product' => { 'title' => 'My Product' } },
partials: { 'product' => '{{ inner_product.title }}' })
partials: { 'product' => '{{ inner_product.title }}' },
)
end
def test_render_accepts_literals_as_arguments
assert_template_result('123', '{% render "snippet", price: 123 %}',
partials: { 'snippet' => '{{ price }}' })
assert_template_result(
'123',
'{% render "snippet", price: 123 %}',
partials: { 'snippet' => '{{ price }}' },
)
end
def test_render_accepts_multiple_named_arguments
assert_template_result('1 2', '{% render "snippet", one: 1, two: 2 %}',
partials: { 'snippet' => '{{ one }} {{ two }}' })
assert_template_result(
'1 2',
'{% render "snippet", one: 1, two: 2 %}',
partials: { 'snippet' => '{{ one }} {{ two }}' },
)
end
def test_render_does_not_inherit_parent_scope_variables
assert_template_result('', '{% assign outer_variable = "should not be visible" %}{% render "snippet" %}',
partials: { 'snippet' => '{{ outer_variable }}' })
assert_template_result(
'',
'{% assign outer_variable = "should not be visible" %}{% render "snippet" %}',
partials: { 'snippet' => '{{ outer_variable }}' },
)
end
def test_render_does_not_inherit_variable_with_same_name_as_snippet
assert_template_result('', "{% assign snippet = 'should not be visible' %}{% render 'snippet' %}",
partials: { 'snippet' => '{{ snippet }}' })
assert_template_result(
'',
"{% assign snippet = 'should not be visible' %}{% render 'snippet' %}",
partials: { 'snippet' => '{{ snippet }}' },
)
end
def test_render_does_not_mutate_parent_scope
assert_template_result('', "{% render 'snippet' %}{{ inner }}",
partials: { 'snippet' => '{% assign inner = 1 %}' })
assert_template_result(
'',
"{% render 'snippet' %}{{ inner }}",
partials: { 'snippet' => '{% assign inner = 1 %}' },
)
end
def test_nested_render_tag
assert_template_result('one two', "{% render 'one' %}",
assert_template_result(
'one two',
"{% render 'one' %}",
partials: {
'one' => "one {% render 'two' %}",
'two' => 'two',
})
},
)
end
def test_recursively_rendered_template_does_not_produce_endless_loop
@@ -77,27 +104,36 @@ class RenderTagTest < Minitest::Test
def test_include_tag_caches_second_read_of_same_partial
file_system = StubFileSystem.new('snippet' => 'echo')
assert_equal('echoecho',
assert_equal(
'echoecho',
Template.parse('{% render "snippet" %}{% render "snippet" %}')
.render!({}, registers: { file_system: file_system }))
.render!({}, registers: { file_system: file_system }),
)
assert_equal(1, file_system.file_read_count)
end
def test_render_tag_doesnt_cache_partials_across_renders
file_system = StubFileSystem.new('snippet' => 'my message')
assert_equal('my message',
Template.parse('{% include "snippet" %}').render!({}, registers: { file_system: file_system }))
assert_equal(
'my message',
Template.parse('{% include "snippet" %}').render!({}, registers: { file_system: file_system }),
)
assert_equal(1, file_system.file_read_count)
assert_equal('my message',
Template.parse('{% include "snippet" %}').render!({}, registers: { file_system: file_system }))
assert_equal(
'my message',
Template.parse('{% include "snippet" %}').render!({}, registers: { file_system: file_system }),
)
assert_equal(2, file_system.file_read_count)
end
def test_render_tag_within_if_statement
assert_template_result('my message', '{% if true %}{% render "snippet" %}{% endif %}',
partials: { 'snippet' => 'my message' })
assert_template_result(
'my message',
'{% if true %}{% render "snippet" %}{% endif %}',
partials: { 'snippet' => 'my message' },
)
end
def test_break_through_render
@@ -107,13 +143,19 @@ class RenderTagTest < Minitest::Test
end
def test_increment_is_isolated_between_renders
assert_template_result('010', '{% increment %}{% increment %}{% render "incr" %}',
partials: { 'incr' => '{% increment %}' })
assert_template_result(
'010',
'{% increment %}{% increment %}{% render "incr" %}',
partials: { 'incr' => '{% increment %}' },
)
end
def test_decrement_is_isolated_between_renders
assert_template_result('-1-2-1', '{% decrement %}{% decrement %}{% render "decr" %}',
partials: { 'decr' => '{% decrement %}' })
assert_template_result(
'-1-2-1',
'{% decrement %}{% decrement %}{% render "decr" %}',
partials: { 'decr' => '{% decrement %}' },
)
end
def test_includes_will_not_render_inside_render_tag
@@ -124,7 +166,7 @@ class RenderTagTest < Minitest::Test
partials: {
'foo' => 'bar',
'test_include' => '{% include "foo" %}',
}
},
)
end
@@ -138,72 +180,109 @@ class RenderTagTest < Minitest::Test
'nested_render_with_sibling_include' => '{% render "test_include" %}{% include "foo" %}',
'test_include' => '{% include "foo" %}',
},
render_errors: true
render_errors: true,
)
end
def test_render_tag_with
assert_template_result("Product: Draft 151cm ",
assert_template_result(
"Product: Draft 151cm ",
"{% render 'product' with products[0] %}",
{ "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }] },
partials: {
'product' => "Product: {{ product.title }} ",
'product_alias' => "Product: {{ product.title }} ",
})
},
)
end
def test_render_tag_with_alias
assert_template_result("Product: Draft 151cm ",
assert_template_result(
"Product: Draft 151cm ",
"{% render 'product_alias' with products[0] as product %}",
{ "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }] },
partials: {
'product' => "Product: {{ product.title }} ",
'product_alias' => "Product: {{ product.title }} ",
})
},
)
end
def test_render_tag_for_alias
assert_template_result("Product: Draft 151cm Product: Element 155cm ",
assert_template_result(
"Product: Draft 151cm Product: Element 155cm ",
"{% render 'product_alias' for products as product %}",
{ "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }] },
partials: {
'product' => "Product: {{ product.title }} ",
'product_alias' => "Product: {{ product.title }} ",
})
},
)
end
def test_render_tag_for
assert_template_result("Product: Draft 151cm Product: Element 155cm ",
assert_template_result(
"Product: Draft 151cm Product: Element 155cm ",
"{% render 'product' for products %}",
{ "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }] },
partials: {
'product' => "Product: {{ product.title }} ",
'product_alias' => "Product: {{ product.title }} ",
})
},
)
end
def test_render_tag_forloop
assert_template_result("Product: Draft 151cm first index:1 Product: Element 155cm last index:2 ",
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' }] },
partials: {
'product' => "Product: {{ product.title }} {% if forloop.first %}first{% endif %} {% if forloop.last %}last{% endif %} index:{{ forloop.index }} ",
})
},
)
end
def test_render_tag_for_drop
assert_template_result("123",
"{% render 'loop' for loop as value %}", { "loop" => TestEnumerable.new },
assert_template_result(
"123",
"{% render 'loop' for loop as value %}",
{ "loop" => TestEnumerable.new },
partials: {
'loop' => "{{ value.foo }}",
})
},
)
end
def test_render_tag_with_drop
assert_template_result("TestEnumerable",
"{% render 'loop' with loop as value %}", { "loop" => TestEnumerable.new },
assert_template_result(
"TestEnumerable",
"{% render 'loop' with loop as value %}",
{ "loop" => TestEnumerable.new },
partials: {
'loop' => "{{ value }}",
})
},
)
end
def test_render_tag_renders_error_with_template_name
assert_template_result(
'Liquid error (foo line 1): standard error',
"{% render 'foo' with errors %}",
{ 'errors' => ErrorDrop.new },
partials: { 'foo' => '{{ foo.standard_error }}' },
render_errors: true,
)
end
def test_render_tag_renders_error_with_template_name_from_template_factory
assert_template_result(
'Liquid error (some/path/foo line 1): standard error',
"{% render 'foo' with errors %}",
{ 'errors' => ErrorDrop.new },
partials: { 'foo' => '{{ foo.standard_error }}' },
template_factory: StubTemplateFactory.new,
render_errors: true,
)
end
end
+105 -52
View File
@@ -6,8 +6,10 @@ class StandardTagTest < Minitest::Test
include Liquid
def test_no_transform
assert_template_result('this text should come out of the template without change...',
'this text should come out of the template without change...')
assert_template_result(
'this text should come out of the template without change...',
'this text should come out of the template without change...',
)
assert_template_result('blah', 'blah')
assert_template_result('<blah>', '<blah>')
@@ -20,8 +22,10 @@ class StandardTagTest < Minitest::Test
end
def test_has_a_block_which_does_nothing
assert_template_result(%(the comment block should be removed .. right?),
%(the comment block should be removed {%comment%} be gone.. {%endcomment%} .. right?))
assert_template_result(
%(the comment block should be removed .. right?),
%(the comment block should be removed {%comment%} be gone.. {%endcomment%} .. right?),
)
assert_template_result('', '{%comment%}{%endcomment%}')
assert_template_result('', '{%comment%}{% endcomment %}')
@@ -64,61 +68,81 @@ class StandardTagTest < Minitest::Test
def test_capture
assigns = { 'var' => 'content' }
assert_template_result('content foo content foo ',
assert_template_result(
'content foo content foo ',
'{{ var2 }}{% capture var2 %}{{ var }} foo {% endcapture %}{{ var2 }}{{ var2 }}',
assigns)
assigns,
)
end
def test_capture_detects_bad_syntax
assert_raises(SyntaxError) do
assert_template_result('content foo content foo ',
assert_template_result(
'content foo content foo ',
'{{ var2 }}{% capture %}{{ var }} foo {% endcapture %}{{ var2 }}{{ var2 }}',
{ 'var' => 'content' })
{ 'var' => 'content' },
)
end
end
def test_case
assigns = { 'condition' => 2 }
assert_template_result(' its 2 ',
assert_template_result(
' its 2 ',
'{% case condition %}{% when 1 %} its 1 {% when 2 %} its 2 {% endcase %}',
assigns)
assigns,
)
assigns = { 'condition' => 1 }
assert_template_result(' its 1 ',
assert_template_result(
' its 1 ',
'{% case condition %}{% when 1 %} its 1 {% when 2 %} its 2 {% endcase %}',
assigns)
assigns,
)
assigns = { 'condition' => 3 }
assert_template_result('',
assert_template_result(
'',
'{% case condition %}{% when 1 %} its 1 {% when 2 %} its 2 {% endcase %}',
assigns)
assigns,
)
assigns = { 'condition' => "string here" }
assert_template_result(' hit ',
assert_template_result(
' hit ',
'{% case condition %}{% when "string here" %} hit {% endcase %}',
assigns)
assigns,
)
assigns = { 'condition' => "bad string here" }
assert_template_result('',
assert_template_result(
'',
'{% case condition %}{% when "string here" %} hit {% endcase %}',\
assigns)
assigns,
)
end
def test_case_with_else
assigns = { 'condition' => 5 }
assert_template_result(' hit ',
assert_template_result(
' hit ',
'{% case condition %}{% when 5 %} hit {% else %} else {% endcase %}',
assigns)
assigns,
)
assigns = { 'condition' => 6 }
assert_template_result(' else ',
assert_template_result(
' else ',
'{% case condition %}{% when 5 %} hit {% else %} else {% endcase %}',
assigns)
assigns,
)
assigns = { 'condition' => 6 }
assert_template_result(' else ',
assert_template_result(
' else ',
'{% case condition %} {% when 5 %} hit {% else %} else {% endcase %}',
assigns)
assigns,
)
end
def test_case_on_size
@@ -131,47 +155,67 @@ class StandardTagTest < Minitest::Test
end
def test_case_on_size_with_else
assert_template_result('else',
assert_template_result(
'else',
'{% case a.size %}{% when 1 %}1{% when 2 %}2{% else %}else{% endcase %}',
{ 'a' => [] })
{ 'a' => [] },
)
assert_template_result('1',
assert_template_result(
'1',
'{% case a.size %}{% when 1 %}1{% when 2 %}2{% else %}else{% endcase %}',
{ 'a' => [1] })
{ 'a' => [1] },
)
assert_template_result('2',
assert_template_result(
'2',
'{% case a.size %}{% when 1 %}1{% when 2 %}2{% else %}else{% endcase %}',
{ 'a' => [1, 1] })
{ 'a' => [1, 1] },
)
assert_template_result('else',
assert_template_result(
'else',
'{% case a.size %}{% when 1 %}1{% when 2 %}2{% else %}else{% endcase %}',
{ 'a' => [1, 1, 1] })
{ 'a' => [1, 1, 1] },
)
assert_template_result('else',
assert_template_result(
'else',
'{% case a.size %}{% when 1 %}1{% when 2 %}2{% else %}else{% endcase %}',
{ 'a' => [1, 1, 1, 1] })
{ 'a' => [1, 1, 1, 1] },
)
assert_template_result('else',
assert_template_result(
'else',
'{% case a.size %}{% when 1 %}1{% when 2 %}2{% else %}else{% endcase %}',
{ 'a' => [1, 1, 1, 1, 1] })
{ 'a' => [1, 1, 1, 1, 1] },
)
end
def test_case_on_length_with_else
assert_template_result('else',
assert_template_result(
'else',
'{% case a.empty? %}{% when true %}true{% when false %}false{% else %}else{% endcase %}',
{})
{},
)
assert_template_result('false',
assert_template_result(
'false',
'{% case false %}{% when true %}true{% when false %}false{% else %}else{% endcase %}',
{})
{},
)
assert_template_result('true',
assert_template_result(
'true',
'{% case true %}{% when true %}true{% when false %}false{% else %}else{% endcase %}',
{})
{},
)
assert_template_result('else',
assert_template_result(
'else',
'{% case NULL %}{% when true %}true{% when false %}false{% else %}else{% endcase %}',
{})
{},
)
end
def test_assign_from_case
@@ -253,24 +297,33 @@ class StandardTagTest < Minitest::Test
assert_template_result('one two one', '{%cycle "one", "two"%} {%cycle "one", "two"%} {%cycle "one", "two"%}')
assert_template_result('text-align: left text-align: right',
'{%cycle "text-align: left", "text-align: right" %} {%cycle "text-align: left", "text-align: right"%}')
assert_template_result(
'text-align: left text-align: right',
'{%cycle "text-align: left", "text-align: right" %} {%cycle "text-align: left", "text-align: right"%}',
)
end
def test_multiple_cycles
assert_template_result('1 2 1 1 2 3 1',
'{%cycle 1,2%} {%cycle 1,2%} {%cycle 1,2%} {%cycle 1,2,3%} {%cycle 1,2,3%} {%cycle 1,2,3%} {%cycle 1,2,3%}')
assert_template_result(
'1 2 1 1 2 3 1',
'{%cycle 1,2%} {%cycle 1,2%} {%cycle 1,2%} {%cycle 1,2,3%} {%cycle 1,2,3%} {%cycle 1,2,3%} {%cycle 1,2,3%}',
)
end
def test_multiple_named_cycles
assert_template_result('one one two two one one',
'{%cycle 1: "one", "two" %} {%cycle 2: "one", "two" %} {%cycle 1: "one", "two" %} {%cycle 2: "one", "two" %} {%cycle 1: "one", "two" %} {%cycle 2: "one", "two" %}')
assert_template_result(
'one one two two one one',
'{%cycle 1: "one", "two" %} {%cycle 2: "one", "two" %} {%cycle 1: "one", "two" %} {%cycle 2: "one", "two" %} {%cycle 1: "one", "two" %} {%cycle 2: "one", "two" %}',
)
end
def test_multiple_named_cycles_with_names_from_context
assigns = { "var1" => 1, "var2" => 2 }
assert_template_result('one one two two one one',
'{%cycle var1: "one", "two" %} {%cycle var2: "one", "two" %} {%cycle var1: "one", "two" %} {%cycle var2: "one", "two" %} {%cycle var1: "one", "two" %} {%cycle var2: "one", "two" %}', assigns)
assert_template_result(
'one one two two one one',
'{%cycle var1: "one", "two" %} {%cycle var2: "one", "two" %} {%cycle var1: "one", "two" %} {%cycle var2: "one", "two" %} {%cycle var1: "one", "two" %} {%cycle var2: "one", "two" %}',
assigns,
)
end
def test_size_of_array
+98 -22
View File
@@ -18,52 +18,70 @@ class TableRowTest < Minitest::Test
end
def test_table_row
assert_template_result("<tr class=\"row1\">\n<td class=\"col1\"> 1 </td><td class=\"col2\"> 2 </td><td class=\"col3\"> 3 </td></tr>\n<tr class=\"row2\"><td class=\"col1\"> 4 </td><td class=\"col2\"> 5 </td><td class=\"col3\"> 6 </td></tr>\n",
assert_template_result(
"<tr class=\"row1\">\n<td class=\"col1\"> 1 </td><td class=\"col2\"> 2 </td><td class=\"col3\"> 3 </td></tr>\n<tr class=\"row2\"><td class=\"col1\"> 4 </td><td class=\"col2\"> 5 </td><td class=\"col3\"> 6 </td></tr>\n",
'{% tablerow n in numbers cols:3%} {{n}} {% endtablerow %}',
{ 'numbers' => [1, 2, 3, 4, 5, 6] })
{ 'numbers' => [1, 2, 3, 4, 5, 6] },
)
assert_template_result("<tr class=\"row1\">\n</tr>\n",
assert_template_result(
"<tr class=\"row1\">\n</tr>\n",
'{% tablerow n in numbers cols:3%} {{n}} {% endtablerow %}',
{ 'numbers' => [] })
{ 'numbers' => [] },
)
end
def test_table_row_with_different_cols
assert_template_result("<tr class=\"row1\">\n<td class=\"col1\"> 1 </td><td class=\"col2\"> 2 </td><td class=\"col3\"> 3 </td><td class=\"col4\"> 4 </td><td class=\"col5\"> 5 </td></tr>\n<tr class=\"row2\"><td class=\"col1\"> 6 </td></tr>\n",
assert_template_result(
"<tr class=\"row1\">\n<td class=\"col1\"> 1 </td><td class=\"col2\"> 2 </td><td class=\"col3\"> 3 </td><td class=\"col4\"> 4 </td><td class=\"col5\"> 5 </td></tr>\n<tr class=\"row2\"><td class=\"col1\"> 6 </td></tr>\n",
'{% tablerow n in numbers cols:5%} {{n}} {% endtablerow %}',
{ 'numbers' => [1, 2, 3, 4, 5, 6] })
{ 'numbers' => [1, 2, 3, 4, 5, 6] },
)
end
def test_table_col_counter
assert_template_result("<tr class=\"row1\">\n<td class=\"col1\">1</td><td class=\"col2\">2</td></tr>\n<tr class=\"row2\"><td class=\"col1\">1</td><td class=\"col2\">2</td></tr>\n<tr class=\"row3\"><td class=\"col1\">1</td><td class=\"col2\">2</td></tr>\n",
assert_template_result(
"<tr class=\"row1\">\n<td class=\"col1\">1</td><td class=\"col2\">2</td></tr>\n<tr class=\"row2\"><td class=\"col1\">1</td><td class=\"col2\">2</td></tr>\n<tr class=\"row3\"><td class=\"col1\">1</td><td class=\"col2\">2</td></tr>\n",
'{% tablerow n in numbers cols:2%}{{tablerowloop.col}}{% endtablerow %}',
{ 'numbers' => [1, 2, 3, 4, 5, 6] })
{ 'numbers' => [1, 2, 3, 4, 5, 6] },
)
end
def test_quoted_fragment
assert_template_result("<tr class=\"row1\">\n<td class=\"col1\"> 1 </td><td class=\"col2\"> 2 </td><td class=\"col3\"> 3 </td></tr>\n<tr class=\"row2\"><td class=\"col1\"> 4 </td><td class=\"col2\"> 5 </td><td class=\"col3\"> 6 </td></tr>\n",
assert_template_result(
"<tr class=\"row1\">\n<td class=\"col1\"> 1 </td><td class=\"col2\"> 2 </td><td class=\"col3\"> 3 </td></tr>\n<tr class=\"row2\"><td class=\"col1\"> 4 </td><td class=\"col2\"> 5 </td><td class=\"col3\"> 6 </td></tr>\n",
"{% tablerow n in collections.frontpage cols:3%} {{n}} {% endtablerow %}",
{ 'collections' => { 'frontpage' => [1, 2, 3, 4, 5, 6] } })
assert_template_result("<tr class=\"row1\">\n<td class=\"col1\"> 1 </td><td class=\"col2\"> 2 </td><td class=\"col3\"> 3 </td></tr>\n<tr class=\"row2\"><td class=\"col1\"> 4 </td><td class=\"col2\"> 5 </td><td class=\"col3\"> 6 </td></tr>\n",
{ 'collections' => { 'frontpage' => [1, 2, 3, 4, 5, 6] } },
)
assert_template_result(
"<tr class=\"row1\">\n<td class=\"col1\"> 1 </td><td class=\"col2\"> 2 </td><td class=\"col3\"> 3 </td></tr>\n<tr class=\"row2\"><td class=\"col1\"> 4 </td><td class=\"col2\"> 5 </td><td class=\"col3\"> 6 </td></tr>\n",
"{% tablerow n in collections['frontpage'] cols:3%} {{n}} {% endtablerow %}",
{ 'collections' => { 'frontpage' => [1, 2, 3, 4, 5, 6] } })
{ 'collections' => { 'frontpage' => [1, 2, 3, 4, 5, 6] } },
)
end
def test_enumerable_drop
assert_template_result("<tr class=\"row1\">\n<td class=\"col1\"> 1 </td><td class=\"col2\"> 2 </td><td class=\"col3\"> 3 </td></tr>\n<tr class=\"row2\"><td class=\"col1\"> 4 </td><td class=\"col2\"> 5 </td><td class=\"col3\"> 6 </td></tr>\n",
assert_template_result(
"<tr class=\"row1\">\n<td class=\"col1\"> 1 </td><td class=\"col2\"> 2 </td><td class=\"col3\"> 3 </td></tr>\n<tr class=\"row2\"><td class=\"col1\"> 4 </td><td class=\"col2\"> 5 </td><td class=\"col3\"> 6 </td></tr>\n",
'{% tablerow n in numbers cols:3%} {{n}} {% endtablerow %}',
{ 'numbers' => ArrayDrop.new([1, 2, 3, 4, 5, 6]) })
{ 'numbers' => ArrayDrop.new([1, 2, 3, 4, 5, 6]) },
)
end
def test_offset_and_limit
assert_template_result("<tr class=\"row1\">\n<td class=\"col1\"> 1 </td><td class=\"col2\"> 2 </td><td class=\"col3\"> 3 </td></tr>\n<tr class=\"row2\"><td class=\"col1\"> 4 </td><td class=\"col2\"> 5 </td><td class=\"col3\"> 6 </td></tr>\n",
assert_template_result(
"<tr class=\"row1\">\n<td class=\"col1\"> 1 </td><td class=\"col2\"> 2 </td><td class=\"col3\"> 3 </td></tr>\n<tr class=\"row2\"><td class=\"col1\"> 4 </td><td class=\"col2\"> 5 </td><td class=\"col3\"> 6 </td></tr>\n",
'{% tablerow n in numbers cols:3 offset:1 limit:6%} {{n}} {% endtablerow %}',
{ 'numbers' => [0, 1, 2, 3, 4, 5, 6, 7] })
{ 'numbers' => [0, 1, 2, 3, 4, 5, 6, 7] },
)
end
def test_blank_string_not_iterable
assert_template_result("<tr class=\"row1\">\n</tr>\n",
assert_template_result(
"<tr class=\"row1\">\n</tr>\n",
"{% tablerow char in characters cols:3 %}I WILL NOT BE OUTPUT{% endtablerow %}",
{ 'characters' => '' })
{ 'characters' => '' },
)
end
def test_cols_nil_constant_same_as_evaluated_nil_expression
@@ -72,12 +90,50 @@ class TableRowTest < Minitest::Test
"<td class=\"col2\">false</td>" \
"</tr>\n"
assert_template_result(expect,
"{% tablerow i in (1..2) cols:nil %}{{ tablerowloop.col_last }}{% endtablerow %}")
assert_template_result(
expect,
"{% tablerow i in (1..2) cols:nil %}{{ tablerowloop.col_last }}{% endtablerow %}",
)
assert_template_result(expect,
assert_template_result(
expect,
"{% tablerow i in (1..2) cols:var %}{{ tablerowloop.col_last }}{% endtablerow %}",
{ "var" => nil })
{ "var" => nil },
)
end
def test_nil_limit_is_treated_as_zero
expect = "<tr class=\"row1\">\n" \
"</tr>\n"
assert_template_result(
expect,
"{% tablerow i in (1..2) limit:nil %}{{ i }}{% endtablerow %}",
)
assert_template_result(
expect,
"{% tablerow i in (1..2) limit:var %}{{ i }}{% endtablerow %}",
{ "var" => nil },
)
end
def test_nil_offset_is_treated_as_zero
expect = "<tr class=\"row1\">\n" \
"<td class=\"col1\">1:false</td>" \
"<td class=\"col2\">2:true</td>" \
"</tr>\n"
assert_template_result(
expect,
"{% tablerow i in (1..2) offset:nil %}{{ i }}:{{ tablerowloop.col_last }}{% endtablerow %}",
)
assert_template_result(
expect,
"{% tablerow i in (1..2) offset:var %}{{ i }}:{{ tablerowloop.col_last }}{% endtablerow %}",
{ "var" => nil },
)
end
def test_tablerow_loop_drop_attributes
@@ -131,4 +187,24 @@ class TableRowTest < Minitest::Test
assert_template_result(expected_output, template)
end
def test_table_row_renders_correct_error_message_for_invalid_parameters
assert_template_result(
"Liquid error (line 1): invalid integer",
'{% tablerow n in (1...10) limit:true %} {{n}} {% endtablerow %}',
render_errors: true,
)
assert_template_result(
"Liquid error (line 1): invalid integer",
'{% tablerow n in (1...10) offset:true %} {{n}} {% endtablerow %}',
render_errors: true,
)
assert_template_result(
"Liquid error (line 1): invalid integer",
'{% tablerow n in (1...10) cols:true %} {{n}} {% endtablerow %}',
render_errors: true,
)
end
end
@@ -7,8 +7,10 @@ class UnlessElseTagTest < Minitest::Test
def test_unless
assert_template_result(' ', ' {% unless true %} this text should not go into the output {% endunless %} ')
assert_template_result(' this text should go into the output ',
' {% unless false %} this text should go into the output {% endunless %} ')
assert_template_result(
' this text should go into the output ',
' {% unless false %} this text should go into the output {% endunless %} ',
)
assert_template_result(' you rock ?', '{% unless true %} you suck {% endunless %} {% unless false %} you rock {% endunless %}?')
end
+6 -4
View File
@@ -93,10 +93,12 @@ class TemplateTest < Minitest::Test
def test_lambda_is_called_once_from_custom_assigns_over_multiple_parses_and_renders
t = Template.new
assigns = { 'number' => -> {
@global ||= 0
@global += 1
} }
assigns = {
'number' => -> {
@global ||= 0
@global += 1
},
}
assert_equal('1', t.parse("{{number}}").render!(assigns))
assert_equal('1', t.parse("{{number}}").render!(assigns))
assert_equal('1', t.render!(assigns))
+75
View File
@@ -1,6 +1,7 @@
# frozen_string_literal: true
require 'test_helper'
require 'timeout'
class VariableTest < Minitest::Test
include Liquid
@@ -24,17 +25,23 @@ class VariableTest < Minitest::Test
def test_if_tag_calls_to_liquid_value
assert_template_result('one', '{% if foo == 1 %}one{% endif %}', { 'foo' => IntegerDrop.new('1') })
assert_template_result('one', '{% if foo == eqv %}one{% endif %}', { 'foo' => IntegerDrop.new(1), 'eqv' => IntegerDrop.new(1) })
assert_template_result('one', '{% if 0 < foo %}one{% endif %}', { 'foo' => IntegerDrop.new('1') })
assert_template_result('one', '{% if foo > 0 %}one{% endif %}', { 'foo' => IntegerDrop.new('1') })
assert_template_result('one', '{% if b > a %}one{% endif %}', { 'b' => IntegerDrop.new(1), 'a' => IntegerDrop.new(0) })
assert_template_result('true', '{% if foo == true %}true{% endif %}', { 'foo' => BooleanDrop.new(true) })
assert_template_result('true', '{% if foo %}true{% endif %}', { 'foo' => BooleanDrop.new(true) })
assert_template_result('', '{% if foo %}true{% endif %}', { 'foo' => BooleanDrop.new(false) })
assert_template_result('', '{% if foo == true %}True{% endif %}', { 'foo' => BooleanDrop.new(false) })
assert_template_result('', '{% if foo and true %}SHOULD NOT HAPPEN{% endif %}', { 'foo' => BooleanDrop.new(false) })
assert_template_result('one', '{% if a contains x %}one{% endif %}', { 'a' => [1], 'x' => IntegerDrop.new(1) })
end
def test_unless_tag_calls_to_liquid_value
assert_template_result('', '{% unless foo %}true{% endunless %}', { 'foo' => BooleanDrop.new(true) })
assert_template_result('true', '{% unless foo %}true{% endunless %}', { 'foo' => BooleanDrop.new(false) })
end
def test_case_tag_calls_to_liquid_value
@@ -130,4 +137,72 @@ class VariableTest < Minitest::Test
def test_raw_value_variable
assert_template_result('bar', '{{ [key] }}', { 'key' => 'foo', 'foo' => 'bar' })
end
def test_dynamic_find_var_with_drop
assert_template_result(
'bar',
'{{ [list[settings.zero]] }}',
{
'list' => ['foo'],
'settings' => SettingsDrop.new("zero" => 0),
'foo' => 'bar',
},
)
assert_template_result(
'foo',
'{{ [list[settings.zero]["foo"]] }}',
{
'list' => [{ 'foo' => 'bar' }],
'settings' => SettingsDrop.new("zero" => 0),
'bar' => 'foo',
},
)
end
def test_double_nested_variable_lookup
assert_template_result(
'bar',
'{{ list[list[settings.zero]]["foo"] }}',
{
'list' => [1, { 'foo' => 'bar' }],
'settings' => SettingsDrop.new("zero" => 0),
'bar' => 'foo',
},
)
end
def test_variable_lookup_should_not_hang_with_invalid_syntax
Timeout.timeout(1) do
assert_template_result(
'bar',
"{{['foo'}}",
{
'foo' => 'bar',
},
error_mode: :lax,
)
end
very_long_key = "1234567890" * 100
template_list = [
"{{['#{very_long_key}']}}", # valid
"{{['#{very_long_key}'}}", # missing closing bracket
"{{[['#{very_long_key}']}}", # extra open bracket
]
template_list.each do |template|
Timeout.timeout(1) do
assert_template_result(
'bar',
template,
{
very_long_key => 'bar',
},
error_mode: :lax,
)
end
end
end
end
+18 -12
View File
@@ -39,11 +39,12 @@ module Minitest
def assert_template_result(
expected, template, assigns = {},
message: nil, partials: nil, error_mode: nil, render_errors: false
message: nil, partials: nil, error_mode: nil, render_errors: false,
template_factory: nil
)
template = Liquid::Template.parse(template, line_numbers: true, error_mode: error_mode&.to_sym)
file_system = StubFileSystem.new(partials || {})
registers = Liquid::Registers.new(file_system: file_system)
registers = Liquid::Registers.new(file_system: file_system, template_factory: template_factory)
context = Liquid::Context.build(static_environments: assigns, rethrow_errors: !render_errors, registers: registers)
output = template.render(context)
assert_equal(expected, output, message)
@@ -125,16 +126,23 @@ class ThingWithToLiquid
end
end
class SettingsDrop < Liquid::Drop
def initialize(settings)
super()
@settings = settings
end
def liquid_method_missing(key)
@settings[key]
end
end
class IntegerDrop < Liquid::Drop
def initialize(value)
super()
@value = value.to_i
end
def ==(other)
@value == other
end
def to_s
@value.to_s
end
@@ -150,10 +158,6 @@ class BooleanDrop < Liquid::Drop
@value = value
end
def ==(other)
@value == other
end
def to_liquid_value
@value
end
@@ -206,8 +210,10 @@ class StubTemplateFactory
@count = 0
end
def for(_template_name)
def for(template_name)
@count += 1
Liquid::Template.new
template = Liquid::Template.new
template.name = "some/path/" + template_name
template
end
end
+4 -2
View File
@@ -35,8 +35,10 @@ class BlockUnitTest < Minitest::Test
def test_variable_many_embedded_fragments
template = Liquid::Template.parse(" {{funk}} {{so}} {{brother}} ")
assert_equal(7, template.root.nodelist.size)
assert_equal([String, Variable, String, Variable, String, Variable, String],
block_types(template.root.nodelist))
assert_equal(
[String, Variable, String, Variable, String, Variable, String],
block_types(template.root.nodelist),
)
end
def test_with_block
+8 -4
View File
@@ -164,13 +164,17 @@ class ConditionUnitTest < Minitest::Test
private
def assert_evaluates_true(left, op, right)
assert(Condition.new(left, op, right).evaluate(@context),
"Evaluated false: #{left} #{op} #{right}")
assert(
Condition.new(left, op, right).evaluate(@context),
"Evaluated false: #{left} #{op} #{right}",
)
end
def assert_evaluates_false(left, op, right)
assert(!Condition.new(left, op, right).evaluate(@context),
"Evaluated true: #{left} #{op} #{right}")
assert(
!Condition.new(left, op, right).evaluate(@context),
"Evaluated true: #{left} #{op} #{right}",
)
end
def assert_evaluates_argument_error(left, op, right)
+36 -36
View File
@@ -8,245 +8,245 @@ class ParseTreeVisitorTest < Minitest::Test
def test_variable
assert_equal(
["test"],
visit(%({{ test }}))
visit(%({{ test }})),
)
end
def test_varible_with_filter
assert_equal(
["test", "infilter"],
visit(%({{ test | split: infilter }}))
visit(%({{ test | split: infilter }})),
)
end
def test_dynamic_variable
assert_equal(
["test", "inlookup"],
visit(%({{ test[inlookup] }}))
visit(%({{ test[inlookup] }})),
)
end
def test_echo
assert_equal(
["test"],
visit(%({% echo test %}))
visit(%({% echo test %})),
)
end
def test_if_condition
assert_equal(
["test"],
visit(%({% if test %}{% endif %}))
visit(%({% if test %}{% endif %})),
)
end
def test_complex_if_condition
assert_equal(
["test"],
visit(%({% if 1 == 1 and 2 == test %}{% endif %}))
visit(%({% if 1 == 1 and 2 == test %}{% endif %})),
)
end
def test_if_body
assert_equal(
["test"],
visit(%({% if 1 == 1 %}{{ test }}{% endif %}))
visit(%({% if 1 == 1 %}{{ test }}{% endif %})),
)
end
def test_unless_condition
assert_equal(
["test"],
visit(%({% unless test %}{% endunless %}))
visit(%({% unless test %}{% endunless %})),
)
end
def test_complex_unless_condition
assert_equal(
["test"],
visit(%({% unless 1 == 1 and 2 == test %}{% endunless %}))
visit(%({% unless 1 == 1 and 2 == test %}{% endunless %})),
)
end
def test_unless_body
assert_equal(
["test"],
visit(%({% unless 1 == 1 %}{{ test }}{% endunless %}))
visit(%({% unless 1 == 1 %}{{ test }}{% endunless %})),
)
end
def test_elsif_condition
assert_equal(
["test"],
visit(%({% if 1 == 1 %}{% elsif test %}{% endif %}))
visit(%({% if 1 == 1 %}{% elsif test %}{% endif %})),
)
end
def test_complex_elsif_condition
assert_equal(
["test"],
visit(%({% if 1 == 1 %}{% elsif 1 == 1 and 2 == test %}{% endif %}))
visit(%({% if 1 == 1 %}{% elsif 1 == 1 and 2 == test %}{% endif %})),
)
end
def test_elsif_body
assert_equal(
["test"],
visit(%({% if 1 == 1 %}{% elsif 2 == 2 %}{{ test }}{% endif %}))
visit(%({% if 1 == 1 %}{% elsif 2 == 2 %}{{ test }}{% endif %})),
)
end
def test_else_body
assert_equal(
["test"],
visit(%({% if 1 == 1 %}{% else %}{{ test }}{% endif %}))
visit(%({% if 1 == 1 %}{% else %}{{ test }}{% endif %})),
)
end
def test_case_left
assert_equal(
["test"],
visit(%({% case test %}{% endcase %}))
visit(%({% case test %}{% endcase %})),
)
end
def test_case_condition
assert_equal(
["test"],
visit(%({% case 1 %}{% when test %}{% endcase %}))
visit(%({% case 1 %}{% when test %}{% endcase %})),
)
end
def test_case_when_body
assert_equal(
["test"],
visit(%({% case 1 %}{% when 2 %}{{ test }}{% endcase %}))
visit(%({% case 1 %}{% when 2 %}{{ test }}{% endcase %})),
)
end
def test_case_else_body
assert_equal(
["test"],
visit(%({% case 1 %}{% else %}{{ test }}{% endcase %}))
visit(%({% case 1 %}{% else %}{{ test }}{% endcase %})),
)
end
def test_for_in
assert_equal(
["test"],
visit(%({% for x in test %}{% endfor %}))
visit(%({% for x in test %}{% endfor %})),
)
end
def test_for_limit
assert_equal(
["test"],
visit(%({% for x in (1..5) limit: test %}{% endfor %}))
visit(%({% for x in (1..5) limit: test %}{% endfor %})),
)
end
def test_for_offset
assert_equal(
["test"],
visit(%({% for x in (1..5) offset: test %}{% endfor %}))
visit(%({% for x in (1..5) offset: test %}{% endfor %})),
)
end
def test_for_body
assert_equal(
["test"],
visit(%({% for x in (1..5) %}{{ test }}{% endfor %}))
visit(%({% for x in (1..5) %}{{ test }}{% endfor %})),
)
end
def test_for_range
assert_equal(
["test"],
visit(%({% for x in (1..test) %}{% endfor %}))
visit(%({% for x in (1..test) %}{% endfor %})),
)
end
def test_tablerow_in
assert_equal(
["test"],
visit(%({% tablerow x in test %}{% endtablerow %}))
visit(%({% tablerow x in test %}{% endtablerow %})),
)
end
def test_tablerow_limit
assert_equal(
["test"],
visit(%({% tablerow x in (1..5) limit: test %}{% endtablerow %}))
visit(%({% tablerow x in (1..5) limit: test %}{% endtablerow %})),
)
end
def test_tablerow_offset
assert_equal(
["test"],
visit(%({% tablerow x in (1..5) offset: test %}{% endtablerow %}))
visit(%({% tablerow x in (1..5) offset: test %}{% endtablerow %})),
)
end
def test_tablerow_body
assert_equal(
["test"],
visit(%({% tablerow x in (1..5) %}{{ test }}{% endtablerow %}))
visit(%({% tablerow x in (1..5) %}{{ test }}{% endtablerow %})),
)
end
def test_cycle
assert_equal(
["test"],
visit(%({% cycle test %}))
visit(%({% cycle test %})),
)
end
def test_assign
assert_equal(
["test"],
visit(%({% assign x = test %}))
visit(%({% assign x = test %})),
)
end
def test_capture
assert_equal(
["test"],
visit(%({% capture x %}{{ test }}{% endcapture %}))
visit(%({% capture x %}{{ test }}{% endcapture %})),
)
end
def test_include
assert_equal(
["test"],
visit(%({% include test %}))
visit(%({% include test %})),
)
end
def test_include_with
assert_equal(
["test"],
visit(%({% include "hai" with test %}))
visit(%({% include "hai" with test %})),
)
end
def test_include_for
assert_equal(
["test"],
visit(%({% include "hai" for test %}))
visit(%({% include "hai" for test %})),
)
end
def test_render_with
assert_equal(
["test"],
visit(%({% render "hai" with test %}))
visit(%({% render "hai" with test %})),
)
end
def test_render_for
assert_equal(
["test"],
visit(%({% render "hai" for test %}))
visit(%({% render "hai" for test %})),
)
end
@@ -257,7 +257,7 @@ class ParseTreeVisitorTest < Minitest::Test
["test", []],
["xs", []],
]]],
traversal(%({% for x in xs offset: test %}{{ other }}{% endfor %})).visit
traversal(%({% for x in xs offset: test %}{{ other }}{% endfor %})).visit,
)
end
+61 -20
View File
@@ -7,13 +7,13 @@ class PartialCacheUnitTest < Minitest::Test
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
parse_context: Liquid::ParseContext.new,
)
assert_equal('my partial body', partial.render)
@@ -22,14 +22,14 @@ 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(
registers: { file_system: file_system }
registers: { file_system: file_system },
)
2.times do
Liquid::PartialCache.load(
'my_partial',
context: context,
parse_context: Liquid::ParseContext.new
parse_context: Liquid::ParseContext.new,
)
end
@@ -39,31 +39,31 @@ class PartialCacheUnitTest < Minitest::Test
def test_cache_state_is_stored_per_context
parse_context = Liquid::ParseContext.new
shared_file_system = StubFileSystem.new(
'my_partial' => 'my shared value'
'my_partial' => 'my shared value',
)
context_one = Liquid::Context.build(
registers: {
file_system: shared_file_system,
}
},
)
context_two = Liquid::Context.build(
registers: {
file_system: shared_file_system,
}
},
)
2.times do
Liquid::PartialCache.load(
'my_partial',
context: context_one,
parse_context: parse_context
parse_context: parse_context,
)
end
Liquid::PartialCache.load(
'my_partial',
context: context_two,
parse_context: parse_context
parse_context: parse_context,
)
assert_equal(2, shared_file_system.file_read_count)
@@ -72,18 +72,18 @@ 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(
registers: { file_system: file_system }
registers: { file_system: file_system },
)
Liquid::PartialCache.load(
'my_partial',
context: context,
parse_context: Liquid::ParseContext.new(my_key: 'value one')
parse_context: Liquid::ParseContext.new(my_key: 'value one'),
)
Liquid::PartialCache.load(
'my_partial',
context: context,
parse_context: Liquid::ParseContext.new(my_key: 'value two')
parse_context: Liquid::ParseContext.new(my_key: 'value two'),
)
# Technically what we care about is that the file was parsed twice,
@@ -95,13 +95,13 @@ class PartialCacheUnitTest < Minitest::Test
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
parse_context: Liquid::ParseContext.new,
)
assert_equal('my partial body', partial.render)
@@ -113,13 +113,13 @@ class PartialCacheUnitTest < Minitest::Test
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
parse_context: Liquid::ParseContext.new,
)
assert_equal('my partial body', partial.render)
@@ -129,12 +129,12 @@ class PartialCacheUnitTest < Minitest::Test
def test_cache_state_is_shared_for_subcontexts
parse_context = Liquid::ParseContext.new
shared_file_system = StubFileSystem.new(
'my_partial' => 'my shared value'
'my_partial' => 'my shared value',
)
context = Liquid::Context.build(
registers: Liquid::Registers.new(
file_system: shared_file_system,
)
),
)
subcontext = context.new_isolated_subcontext
@@ -144,16 +144,57 @@ class PartialCacheUnitTest < Minitest::Test
Liquid::PartialCache.load(
'my_partial',
context: context,
parse_context: parse_context
parse_context: parse_context,
)
Liquid::PartialCache.load(
'my_partial',
context: subcontext,
parse_context: parse_context
parse_context: parse_context,
)
end
assert_equal(1, shared_file_system.file_read_count)
end
def test_uses_template_name_from_template_factory
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('some/path/my_partial', partial.name)
end
def test_includes_error_mode_into_template_cache
template_factory = StubTemplateFactory.new
context = Liquid::Context.build(
registers: {
file_system: StubFileSystem.new('my_partial' => 'my partial body'),
template_factory: template_factory,
},
)
[:lax, :warn, :strict].each do |error_mode|
Liquid::PartialCache.load(
'my_partial',
context: context,
parse_context: Liquid::ParseContext.new(error_mode: error_mode),
)
end
assert_equal(
["my_partial:lax", "my_partial:warn", "my_partial:strict"],
context.registers[:cached_partials].keys,
)
end
end
+13
View File
@@ -1,6 +1,7 @@
# frozen_string_literal: true
require 'test_helper'
require 'timeout'
class RegexpUnitTest < Minitest::Test
include Liquid
@@ -37,10 +38,22 @@ class RegexpUnitTest < Minitest::Test
def test_variable_parser
assert_equal(['var'], 'var'.scan(VariableParser))
assert_equal(['[var]'], '[var]'.scan(VariableParser))
assert_equal(['var', 'method'], 'var.method'.scan(VariableParser))
assert_equal(['var', '[method]'], 'var[method]'.scan(VariableParser))
assert_equal(['var', '[method]', '[0]'], 'var[method][0]'.scan(VariableParser))
assert_equal(['var', '["method"]', '[0]'], 'var["method"][0]'.scan(VariableParser))
assert_equal(['var', '[method]', '[0]', 'method'], 'var[method][0].method'.scan(VariableParser))
end
def test_variable_parser_with_large_input
Timeout.timeout(1) { assert_equal(['[var]'], '[var]'.scan(VariableParser)) }
very_long_string = "foo" * 1000
# valid dynamic lookup
Timeout.timeout(1) { assert_equal(["[#{very_long_string}]"], "[#{very_long_string}]".scan(VariableParser)) }
# invalid dynamic lookup with missing closing bracket
Timeout.timeout(1) { assert_equal([very_long_string], "[#{very_long_string}".scan(VariableParser)) }
end
end # RegexpTest
+1 -1
View File
@@ -50,7 +50,7 @@ class StrainerFactoryUnitTest < Minitest::Test
assert_match(
/\ALiquid error: wrong number of arguments \((1 for 0|given 1, expected 0)\)\z/,
exception.message
exception.message,
)
source = AccessScopeFilters.instance_method(:public_filter).source_location
assert_equal(source.map(&:to_s), exception.backtrace[0].split(':')[0..1])