mirror of
https://github.com/Shopify/liquid.git
synced 2026-09-12 23:40:45 -07:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dd7bbf26bc | ||
|
|
cca24a2226 | ||
|
|
98ce25cb40 | ||
|
|
77293d4524 | ||
|
|
af66bc8a5f | ||
|
|
42e5c52336 | ||
|
|
649cca1349 | ||
|
|
81ed65f2a1 | ||
|
|
6ca06c22b8 | ||
|
|
80bc7ffdf2 | ||
|
|
48cb643c02 | ||
|
|
1d97389fb0 | ||
|
|
3ff4170cb0 | ||
|
|
24dceef552 | ||
|
|
428c66ffac | ||
|
|
0fe4a5d144 | ||
|
|
e650dc4195 | ||
|
|
a75517e2c7 | ||
|
|
9ab688eada | ||
|
|
abef59d129 | ||
|
|
940c3a4207 | ||
|
|
84a0289ebc | ||
|
|
4599e5459f | ||
|
|
59c445f0e1 | ||
|
|
bd9c3802c8 | ||
|
|
2b40850e4a | ||
|
|
22ded5f304 | ||
|
|
ddc32b7bd8 | ||
|
|
74e505f6fa | ||
|
|
6a888d4564 | ||
|
|
dd257b3d66 | ||
|
|
1aaf6ed019 | ||
|
|
daf93a83c2 | ||
|
|
e889a9da0b | ||
|
|
0f11c97623 | ||
|
|
e804f36681 | ||
|
|
619ed3fcd7 | ||
|
|
cdb5cb06b2 | ||
|
|
bc153159e6 | ||
|
|
128b4e35be | ||
|
|
c743936a78 | ||
|
|
bf711a0521 | ||
|
|
e8731f27d9 | ||
|
|
6a44c1ec77 | ||
|
|
1beb87b446 | ||
|
|
b839deb3a8 | ||
|
|
0b826120c0 | ||
|
|
936f803a4e | ||
|
|
5cd8a83fa6 | ||
|
|
c2c6cb2b15 | ||
|
|
9ccbf64571 | ||
|
|
8b1b9f649a | ||
|
|
474315c6bb | ||
|
|
667664bb22 | ||
|
|
1cdd1f0834 | ||
|
|
1cd5ec54f0 | ||
|
|
29732f4305 | ||
|
|
abed47547c | ||
|
|
ace3fe15ac | ||
|
|
db8e85ab31 | ||
|
|
f484b868d0 | ||
|
|
3e8994c258 | ||
|
|
7ccac29688 | ||
|
|
96b5325f87 | ||
|
|
b8f05267a9 | ||
|
|
6ce4ec1011 | ||
|
|
a39422feac | ||
|
|
2c2f5826d5 | ||
|
|
c99c93255d | ||
|
|
c0c191cabd | ||
|
|
6765d93938 | ||
|
|
4f17abfb4a | ||
|
|
eff2a63204 | ||
|
|
fbab19ac8c | ||
|
|
b14bf94c98 | ||
|
|
951be6c1f5 | ||
|
|
c8f3cfa8fc | ||
|
|
f21d1c5d9d | ||
|
|
456be2f75e | ||
|
|
ff1c35b986 | ||
|
|
cab08cfe57 | ||
|
|
bb7027138e | ||
|
|
34512df8e9 | ||
|
|
bca01e8944 | ||
|
|
d3647e280d | ||
|
|
c0f565ce7b | ||
|
|
753015d8fc | ||
|
|
f4e32d2214 | ||
|
|
8adbbfeaa6 | ||
|
|
4648f0fa64 | ||
|
|
3a64b3741f | ||
|
|
2c51a1922a | ||
|
|
57fdc1b5fb | ||
|
|
308dfc3cb6 | ||
|
|
2515f3be09 | ||
|
|
433ed0fff2 | ||
|
|
0787660603 | ||
|
|
93c252fe5a | ||
|
|
df13389940 | ||
|
|
ca2d850eea | ||
|
|
3a736da222 | ||
|
|
eb89f22d93 | ||
|
|
98e146ebf7 | ||
|
|
7611463f02 | ||
|
|
f1846d63a3 | ||
|
|
af3f8612bf | ||
|
|
6f8722a6d3 | ||
|
|
81f44e36be | ||
|
|
c9ec8f4635 | ||
|
|
3fb467f069 | ||
|
|
992e15a173 | ||
|
|
0bb6539dce | ||
|
|
1fdc577246 | ||
|
|
74245cd396 | ||
|
|
86605016e1 | ||
|
|
6981305736 | ||
|
|
6d3c5ef3d3 | ||
|
|
150ddf4c3b | ||
|
|
a3e9088a0e | ||
|
|
b05393884d | ||
|
|
ef9db08642 | ||
|
|
5dd8c84b47 | ||
|
|
1af12c74cc | ||
|
|
72bbbda022 | ||
|
|
489e3ca7bf | ||
|
|
65542f9e4f | ||
|
|
d497bfffe9 | ||
|
|
ebafb0a3fe | ||
|
|
4ec9db3f99 | ||
|
|
95eb5d6036 | ||
|
|
7ba40d48c0 | ||
|
|
9020dbcd41 | ||
|
|
73f7467258 | ||
|
|
f3aa5fbd7c | ||
|
|
eb70bb9b87 | ||
|
|
4d8e55dbc4 | ||
|
|
f697093e94 | ||
|
|
7594bed88a | ||
|
|
f64471eb4e | ||
|
|
d4c24f3ce2 | ||
|
|
3bbb7aa7ba | ||
|
|
102bac2e33 | ||
|
|
e69f729f76 | ||
|
|
4ec0b85d80 | ||
|
|
f77c766262 | ||
|
|
7bc14ae2be | ||
|
|
41c19929ad | ||
|
|
05d768c6ab | ||
|
|
54414dfd83 | ||
|
|
23a8438fa6 | ||
|
|
21f3337dec | ||
|
|
1f0a0ad55c | ||
|
|
36dce29776 | ||
|
|
8b68630a11 | ||
|
|
8882338aa1 | ||
|
|
6c2c621712 | ||
|
|
1cae1e497f | ||
|
|
ed7dae50aa | ||
|
|
7e99432bd1 | ||
|
|
6c187b8470 | ||
|
|
6e07f73f68 | ||
|
|
f64af57b7b | ||
|
|
c60c3c7802 | ||
|
|
11625b1bc9 | ||
|
|
ec6fb4d5fa | ||
|
|
fad58ef436 | ||
|
|
22568080b1 | ||
|
|
dd7ed00ec4 | ||
|
|
1667c1180e | ||
|
|
7357dcf185 | ||
|
|
68c3827ef2 | ||
|
|
4af38bc549 | ||
|
|
df241abf70 | ||
|
|
10f8337209 | ||
|
|
5ed0410a8b | ||
|
|
0f5220c391 | ||
|
|
7a23f46fab | ||
|
|
3f7edf00b9 | ||
|
|
b4a2a79e26 | ||
|
|
1d2bee1f60 | ||
|
|
01e6eec97a | ||
|
|
fbdab19358 | ||
|
|
ce85ac5d3d | ||
|
|
c0ffee16a3 | ||
|
|
a7eb33fa39 | ||
|
|
1a85e98793 | ||
|
|
c588337aac | ||
|
|
97f7922457 | ||
|
|
0d83e64cfe | ||
|
|
0d5e01ae98 | ||
|
|
15eaa49e48 | ||
|
|
91c54c579d | ||
|
|
1310c4978d | ||
|
|
3de1db3c3a | ||
|
|
03522caaf8 | ||
|
|
7acea2a9c9 | ||
|
|
d8ef698539 | ||
|
|
ebdfdb80e5 | ||
|
|
95e9fa5010 | ||
|
|
0e14d539a3 | ||
|
|
db106ae058 | ||
|
|
db3999a008 | ||
|
|
10e2aa8d5b | ||
|
|
7c4114671b | ||
|
|
b01de9d325 | ||
|
|
a5369c26a8 | ||
|
|
a03de8f9ea | ||
|
|
e86fe27259 | ||
|
|
c8906d05b9 | ||
|
|
50d1a2ffc9 | ||
|
|
f686c5dec7 | ||
|
|
aa8ce87b96 | ||
|
|
698f5e0d96 | ||
|
|
996bfe0c82 | ||
|
|
be81c9ae5a | ||
|
|
edd4d70aee | ||
|
|
ac66dbbafe | ||
|
|
017c1b5e83 | ||
|
|
250555c9a8 | ||
|
|
e361a4d53c |
@@ -1,2 +0,0 @@
|
||||
enabled:
|
||||
- cla
|
||||
@@ -0,0 +1,22 @@
|
||||
name: Contributor License Agreement (CLA)
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [opened, synchronize]
|
||||
issue_comment:
|
||||
types: [created]
|
||||
|
||||
jobs:
|
||||
cla:
|
||||
runs-on: ubuntu-latest
|
||||
if: |
|
||||
(github.event.issue.pull_request
|
||||
&& !github.event.issue.pull_request.merged_at
|
||||
&& contains(github.event.comment.body, 'signed')
|
||||
)
|
||||
|| (github.event.pull_request && !github.event.pull_request.merged)
|
||||
steps:
|
||||
- uses: Shopify/shopify-cla-action@v1
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
cla-token: ${{ secrets.CLA_TOKEN }}
|
||||
@@ -1,39 +1,35 @@
|
||||
name: Liquid
|
||||
on: [push, pull_request]
|
||||
|
||||
env:
|
||||
BUNDLE_JOBS: 4
|
||||
BUNDLE_RETRY: 3
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
entry:
|
||||
- { ruby: 2.5, allowed-failure: false } # minimum supported
|
||||
- { ruby: 3.0, allowed-failure: false } # latest
|
||||
- { ruby: 2.7, allowed-failure: false } # minimum supported
|
||||
- { 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@v2
|
||||
- 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:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- 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
|
||||
|
||||
+9
-4
@@ -10,8 +10,9 @@ Performance:
|
||||
Enabled: true
|
||||
|
||||
AllCops:
|
||||
TargetRubyVersion: 2.5
|
||||
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'
|
||||
|
||||
+137
-14
@@ -1,27 +1,74 @@
|
||||
# This configuration was generated by
|
||||
# `rubocop --auto-gen-config`
|
||||
# on 2020-12-11 18:53:41 UTC using RuboCop version 1.6.1.
|
||||
# on 2022-05-18 19:25:47 UTC using RuboCop version 1.29.1.
|
||||
# The point is for the user to remove these configuration records
|
||||
# one by one as the offenses are removed from the code base.
|
||||
# Note that changes in the inspected code, or installation of new
|
||||
# versions of RuboCop, may require this file to be generated again.
|
||||
|
||||
# Offense count: 2
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: EnforcedStyle.
|
||||
# SupportedStyles: runtime_error, standard_error
|
||||
Lint/InheritException:
|
||||
# Offense count: 1
|
||||
# This cop supports safe auto-correction (--auto-correct).
|
||||
# Configuration parameters: TreatCommentsAsGroupSeparators, ConsiderPunctuation, Include.
|
||||
# Include: **/*.gemspec
|
||||
Gemspec/OrderedDependencies:
|
||||
Exclude:
|
||||
- 'lib/liquid/interrupts.rb'
|
||||
- 'liquid.gemspec'
|
||||
|
||||
# Offense count: 113
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: AutoCorrect, AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns.
|
||||
# URISchemes: http, https
|
||||
Layout/LineLength:
|
||||
Max: 260
|
||||
# Offense count: 6
|
||||
# This cop supports safe auto-correction (--auto-correct).
|
||||
Layout/ClosingHeredocIndentation:
|
||||
Exclude:
|
||||
- 'test/integration/tags/for_tag_test.rb'
|
||||
|
||||
# Offense count: 34
|
||||
# This cop supports safe auto-correction (--auto-correct).
|
||||
Layout/EmptyLineAfterGuardClause:
|
||||
Exclude:
|
||||
- 'lib/liquid/block.rb'
|
||||
- 'lib/liquid/block_body.rb'
|
||||
- 'lib/liquid/context.rb'
|
||||
- 'lib/liquid/drop.rb'
|
||||
- 'lib/liquid/lexer.rb'
|
||||
- 'lib/liquid/parser.rb'
|
||||
- 'lib/liquid/profiler/hooks.rb'
|
||||
- 'lib/liquid/standardfilters.rb'
|
||||
- 'lib/liquid/tags/for.rb'
|
||||
- 'lib/liquid/tags/if.rb'
|
||||
- 'lib/liquid/utils.rb'
|
||||
- 'lib/liquid/variable.rb'
|
||||
- 'lib/liquid/variable_lookup.rb'
|
||||
- 'performance/shopify/money_filter.rb'
|
||||
- 'performance/shopify/paginate.rb'
|
||||
|
||||
# Offense count: 8
|
||||
# This cop supports safe auto-correction (--auto-correct).
|
||||
# Configuration parameters: AllowAliasSyntax, AllowedMethods.
|
||||
# AllowedMethods: alias_method, public, protected, private
|
||||
Layout/EmptyLinesAroundAttributeAccessor:
|
||||
Exclude:
|
||||
- 'lib/liquid/template.rb'
|
||||
- 'test/integration/filter_test.rb'
|
||||
- 'test/integration/tags/include_tag_test.rb'
|
||||
- 'test/unit/strainer_template_unit_test.rb'
|
||||
|
||||
# Offense count: 17
|
||||
# This cop supports safe auto-correction (--auto-correct).
|
||||
# Configuration parameters: EnforcedStyle, IndentationWidth.
|
||||
# SupportedStyles: aligned, indented
|
||||
Layout/LineEndStringConcatenationIndentation:
|
||||
Exclude:
|
||||
- 'test/integration/tags/for_tag_test.rb'
|
||||
- 'test/integration/tags/increment_tag_test.rb'
|
||||
|
||||
# Offense count: 1
|
||||
# This cop supports safe auto-correction (--auto-correct).
|
||||
# Configuration parameters: EnforcedStyle, IndentationWidth.
|
||||
# SupportedStyles: aligned, indented
|
||||
Layout/MultilineOperationIndentation:
|
||||
Exclude:
|
||||
- 'lib/liquid/expression.rb'
|
||||
|
||||
# Offense count: 9
|
||||
Lint/MissingSuper:
|
||||
Exclude:
|
||||
- 'lib/liquid/forloop_drop.rb'
|
||||
@@ -33,7 +80,7 @@ Lint/MissingSuper:
|
||||
- 'test/integration/tags/for_tag_test.rb'
|
||||
- 'test/integration/tags/table_row_test.rb'
|
||||
|
||||
# Offense count: 43
|
||||
# Offense count: 44
|
||||
Naming/ConstantName:
|
||||
Exclude:
|
||||
- 'lib/liquid.rb'
|
||||
@@ -51,7 +98,83 @@ Naming/ConstantName:
|
||||
- 'performance/shopify/paginate.rb'
|
||||
- 'test/integration/tags/include_tag_test.rb'
|
||||
|
||||
# Offense count: 9
|
||||
# Configuration parameters: CheckIdentifiers, CheckConstants, CheckVariables, CheckStrings, CheckSymbols, CheckComments, CheckFilepaths, FlaggedTerms.
|
||||
Naming/InclusiveLanguage:
|
||||
Exclude:
|
||||
- 'lib/liquid/drop.rb'
|
||||
- 'lib/liquid/parse_context.rb'
|
||||
- 'test/integration/drop_test.rb'
|
||||
- 'test/integration/tags/if_else_tag_test.rb'
|
||||
|
||||
# Offense count: 2
|
||||
Style/ClassVars:
|
||||
Exclude:
|
||||
- 'lib/liquid/condition.rb'
|
||||
|
||||
# Offense count: 3
|
||||
# This cop supports safe auto-correction (--auto-correct).
|
||||
Style/ExplicitBlockArgument:
|
||||
Exclude:
|
||||
- 'test/integration/context_test.rb'
|
||||
- 'test/integration/tag/disableable_test.rb'
|
||||
- 'test/integration/tags/for_tag_test.rb'
|
||||
|
||||
# Offense count: 2982
|
||||
# This cop supports safe auto-correction (--auto-correct).
|
||||
# Configuration parameters: EnforcedStyle, ConsistentQuotesInMultiline.
|
||||
# SupportedStyles: single_quotes, double_quotes
|
||||
Style/StringLiterals:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 20
|
||||
# This cop supports safe auto-correction (--auto-correct).
|
||||
# Configuration parameters: EnforcedStyle.
|
||||
# SupportedStyles: single_quotes, double_quotes
|
||||
Style/StringLiteralsInInterpolation:
|
||||
Exclude:
|
||||
- 'lib/liquid/condition.rb'
|
||||
- 'lib/liquid/strainer_template.rb'
|
||||
- 'lib/liquid/tag/disableable.rb'
|
||||
- 'performance/shopify/shop_filter.rb'
|
||||
- 'performance/shopify/tag_filter.rb'
|
||||
|
||||
# Offense count: 6
|
||||
# This cop supports safe auto-correction (--auto-correct).
|
||||
# Configuration parameters: EnforcedStyleForMultiline.
|
||||
# SupportedStylesForMultiline: comma, consistent_comma, no_comma
|
||||
Style/TrailingCommaInArrayLiteral:
|
||||
Exclude:
|
||||
- 'example/server/example_servlet.rb'
|
||||
- 'lib/liquid/condition.rb'
|
||||
- 'test/integration/context_test.rb'
|
||||
- 'test/integration/standard_filter_test.rb'
|
||||
- 'test/unit/parse_tree_visitor_test.rb'
|
||||
|
||||
# Offense count: 1
|
||||
# This cop supports safe auto-correction (--auto-correct).
|
||||
# Configuration parameters: EnforcedStyleForMultiline.
|
||||
# SupportedStylesForMultiline: comma, consistent_comma, no_comma
|
||||
Style/TrailingCommaInHashLiteral:
|
||||
Exclude:
|
||||
- 'lib/liquid/expression.rb'
|
||||
|
||||
# Offense count: 19
|
||||
# This cop supports safe auto-correction (--auto-correct).
|
||||
# Configuration parameters: EnforcedStyle, MinSize, WordRegex.
|
||||
# SupportedStyles: percent, brackets
|
||||
Style/WordArray:
|
||||
Exclude:
|
||||
- 'lib/liquid/tags/if.rb'
|
||||
- 'liquid.gemspec'
|
||||
- 'test/integration/assign_test.rb'
|
||||
- 'test/integration/context_test.rb'
|
||||
- 'test/integration/drop_test.rb'
|
||||
- 'test/integration/standard_filter_test.rb'
|
||||
|
||||
# Offense count: 117
|
||||
# This cop supports safe auto-correction (--auto-correct).
|
||||
# Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, AllowedPatterns, IgnoredPatterns.
|
||||
# URISchemes: http, https
|
||||
Layout/LineLength:
|
||||
Max: 260
|
||||
|
||||
@@ -18,8 +18,8 @@ group :benchmark, :test do
|
||||
end
|
||||
|
||||
group :test do
|
||||
gem 'rubocop', '~> 1.4', require: false
|
||||
gem 'rubocop-shopify', '~> 1.0.7', require: false
|
||||
gem 'rubocop', '~> 1.44.0'
|
||||
gem 'rubocop-shopify', '~> 2.12.0', require: false
|
||||
gem 'rubocop-performance', require: false
|
||||
|
||||
platform :mri, :truffleruby do
|
||||
|
||||
+42
-1
@@ -1,9 +1,50 @@
|
||||
# Liquid Change Log
|
||||
|
||||
## 5.0.2 (unreleased)
|
||||
## 5.4.0 2022-07-29
|
||||
|
||||
### Breaking Changes
|
||||
* Drop support for end-of-life Ruby versions (2.5 and 2.6) (#1578) [Andy Waite]
|
||||
|
||||
### Features
|
||||
* Allow `#` to be used as an inline comment tag (#1498) [CP Clermont]
|
||||
|
||||
### Fixes
|
||||
* `PartialCache` now shares snippet cache with subcontexts by default (#1553) [Chris AtLee]
|
||||
* Hash registers no longer leak into subcontexts as static registers (#1564) [Chris AtLee]
|
||||
* Fix `ParseTreeVisitor` for `with` variable expressions in `Render` tag (#1596) [CP Clermont]
|
||||
|
||||
### Changed
|
||||
* Liquid::Context#registers now always returns a Liquid::Registers object, though supports the most used Hash functions for compatibility (#1553)
|
||||
|
||||
## 5.3.0 2022-03-22
|
||||
|
||||
### Fixes
|
||||
* StandardFilter: Fix missing @context on iterations (#1525) [Thierry Joyal]
|
||||
* Fix warning about block and default value in `static_registers.rb` (#1531) [Peter Zhu]
|
||||
|
||||
### Deprecation
|
||||
* Condition#evaluate to require mandatory context argument in Liquid 6.0.0 (#1527) [Thierry Joyal]
|
||||
|
||||
## 5.2.0 2022-03-01
|
||||
|
||||
### Features
|
||||
* Add `remove_last`, and `replace_last` filters (#1422) [Anders Hagbard]
|
||||
* Eagerly cache global filters (#1524) [Jean Boussier]
|
||||
|
||||
### Fixes
|
||||
* Fix some internal errors in filters from invalid input (#1476) [Dylan Thacker-Smith]
|
||||
* Allow dash in filter kwarg name for consistency with Liquid::C (#1518) [CP Clermont]
|
||||
|
||||
## 5.1.0 / 2021-09-09
|
||||
|
||||
### Features
|
||||
* Add `base64_encode`, `base64_decode`, `base64_url_safe_encode`, and `base64_url_safe_decode` filters (#1450) [Daniel Insley]
|
||||
* Introduce `to_liquid_value` in `Liquid::Drop` (#1441) [Michael Go]
|
||||
|
||||
### Fixes
|
||||
* Fix support for using a String subclass for the liquid source (#1421) [Dylan Thacker-Smith]
|
||||
* Add `ParseTreeVisitor` to `RangeLookup` (#1470) [CP Clermont]
|
||||
* Translate `RangeError` to `Liquid::Error` for `truncatewords` with large int (#1431) [Dylan Thacker-Smith]
|
||||
|
||||
## 5.0.1 / 2021-03-24
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
* [Contributing guidelines](CONTRIBUTING.md)
|
||||
* [Version history](History.md)
|
||||
* [Liquid documentation from Shopify](http://docs.shopify.com/themes/liquid-basics)
|
||||
* [Liquid documentation from Shopify](https://shopify.dev/docs/api/liquid)
|
||||
* [Liquid Wiki at GitHub](https://github.com/Shopify/liquid/wiki)
|
||||
* [Website](http://liquidmarkup.org/)
|
||||
|
||||
@@ -56,20 +56,20 @@ For standard use you can just pass it the content of a file and call render with
|
||||
|
||||
Setting the error mode of Liquid lets you specify how strictly you want your templates to be interpreted.
|
||||
Normally the parser is very lax and will accept almost anything without error. Unfortunately this can make
|
||||
it very hard to debug and can lead to unexpected behaviour.
|
||||
it very hard to debug and can lead to unexpected behaviour.
|
||||
|
||||
Liquid also comes with a stricter parser that can be used when editing templates to give better error messages
|
||||
when templates are invalid. You can enable this new parser like this:
|
||||
|
||||
```ruby
|
||||
Liquid::Template.error_mode = :strict # Raises a SyntaxError when invalid syntax is used
|
||||
Liquid::Template.error_mode = :warn # Adds errors to template.errors but continues as normal
|
||||
Liquid::Template.error_mode = :warn # Adds strict errors to template.errors but continues as normal
|
||||
Liquid::Template.error_mode = :lax # The default mode, accepts almost anything.
|
||||
```
|
||||
|
||||
If you want to set the error mode only on specific templates you can pass `:error_mode` as an option to `parse`:
|
||||
```ruby
|
||||
Liquid::Template.parse(source, :error_mode => :strict)
|
||||
Liquid::Template.parse(source, error_mode: :strict)
|
||||
```
|
||||
This is useful for doing things like enabling strict mode only in the theme editor.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
+5
-5
@@ -29,6 +29,7 @@ module Liquid
|
||||
WhitespaceControl = '-'
|
||||
TagStart = /\{\%/
|
||||
TagEnd = /\%\}/
|
||||
TagName = /#|\w+/
|
||||
VariableSignature = /\(?[\w\-\.\[\]]\)?/
|
||||
VariableSegment = /[\w\-]/
|
||||
VariableStart = /\{\{/
|
||||
@@ -36,11 +37,11 @@ module Liquid
|
||||
VariableIncompleteEnd = /\}\}?/
|
||||
QuotedString = /"[^"]*"|'[^']*'/
|
||||
QuotedFragment = /#{QuotedString}|(?:[^\s,\|'"]|#{QuotedString})+/o
|
||||
TagAttributes = /(\w+)\s*\:\s*(#{QuotedFragment})/o
|
||||
TagAttributes = /(\w[\w-]*)\s*\:\s*(#{QuotedFragment})/o
|
||||
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 }
|
||||
|
||||
@@ -59,8 +60,8 @@ require 'liquid/forloop_drop'
|
||||
require 'liquid/extensions'
|
||||
require 'liquid/errors'
|
||||
require 'liquid/interrupts'
|
||||
require 'liquid/strainer_factory'
|
||||
require 'liquid/strainer_template'
|
||||
require 'liquid/strainer_factory'
|
||||
require 'liquid/expression'
|
||||
require 'liquid/context'
|
||||
require 'liquid/parser_switching'
|
||||
@@ -83,8 +84,7 @@ require 'liquid/tokenizer'
|
||||
require 'liquid/parse_context'
|
||||
require 'liquid/partial_cache'
|
||||
require 'liquid/usage'
|
||||
require 'liquid/register'
|
||||
require 'liquid/static_registers'
|
||||
require 'liquid/registers'
|
||||
require 'liquid/template_factory'
|
||||
|
||||
# Load all the tags of the standard library
|
||||
|
||||
+8
-4
@@ -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
|
||||
|
||||
@@ -4,8 +4,8 @@ require 'English'
|
||||
|
||||
module Liquid
|
||||
class BlockBody
|
||||
LiquidTagToken = /\A\s*(\w+)\s*(.*?)\z/o
|
||||
FullToken = /\A#{TagStart}#{WhitespaceControl}?(\s*)(\w+)(\s*)(.*?)#{WhitespaceControl}?#{TagEnd}\z/om
|
||||
LiquidTagToken = /\A\s*(#{TagName})\s*(.*?)\z/o
|
||||
FullToken = /\A#{TagStart}#{WhitespaceControl}?(\s*)(#{TagName})(\s*)(.*?)#{WhitespaceControl}?#{TagEnd}\z/om
|
||||
ContentOfVariable = /\A#{VariableStart}#{WhitespaceControl}?(.*?)#{WhitespaceControl}?#{VariableEnd}\z/om
|
||||
WhitespaceOrNothing = /\A\s*\z/
|
||||
TAGSTART = "{%"
|
||||
@@ -37,7 +37,7 @@ module Liquid
|
||||
|
||||
private def parse_for_liquid_tag(tokenizer, parse_context)
|
||||
while (token = tokenizer.shift)
|
||||
unless token.empty? || token =~ WhitespaceOrNothing
|
||||
unless token.empty? || token.match?(WhitespaceOrNothing)
|
||||
unless token =~ LiquidTagToken
|
||||
# line isn't empty but didn't match tag syntax, yield and let the
|
||||
# caller raise a syntax error
|
||||
@@ -109,14 +109,22 @@ module Liquid
|
||||
end
|
||||
end
|
||||
|
||||
private def parse_for_document(tokenizer, parse_context)
|
||||
private def handle_invalid_tag_token(token, parse_context)
|
||||
if token.end_with?('%}')
|
||||
yield token, token
|
||||
else
|
||||
BlockBody.raise_missing_tag_terminator(token, parse_context)
|
||||
end
|
||||
end
|
||||
|
||||
private def parse_for_document(tokenizer, parse_context, &block)
|
||||
while (token = tokenizer.shift)
|
||||
next if token.empty?
|
||||
case
|
||||
when token.start_with?(TAGSTART)
|
||||
whitespace_handler(token, parse_context)
|
||||
unless token =~ FullToken
|
||||
BlockBody.raise_missing_tag_terminator(token, parse_context)
|
||||
return handle_invalid_tag_token(token, parse_context, &block)
|
||||
end
|
||||
tag_name = Regexp.last_match(2)
|
||||
markup = Regexp.last_match(4)
|
||||
@@ -150,7 +158,7 @@ module Liquid
|
||||
end
|
||||
parse_context.trim_whitespace = false
|
||||
@nodelist << token
|
||||
@blank &&= !!(token =~ WhitespaceOrNothing)
|
||||
@blank &&= token.match?(WhitespaceOrNothing)
|
||||
end
|
||||
parse_context.line_number = tokenizer.line_number
|
||||
end
|
||||
@@ -231,8 +239,8 @@ module Liquid
|
||||
end
|
||||
|
||||
def create_variable(token, parse_context)
|
||||
token.scan(ContentOfVariable) do |content|
|
||||
markup = content.first
|
||||
if token =~ ContentOfVariable
|
||||
markup = Regexp.last_match(1)
|
||||
return Variable.new(markup, parse_context)
|
||||
end
|
||||
BlockBody.raise_missing_variable_terminator(token, parse_context)
|
||||
|
||||
+14
-6
@@ -8,7 +8,7 @@ module Liquid
|
||||
# c = Condition.new(1, '==', 1)
|
||||
# c.evaluate #=> true
|
||||
#
|
||||
class Condition #:nodoc:
|
||||
class Condition # :nodoc:
|
||||
@@operators = {
|
||||
'==' => ->(cond, left, right) { cond.send(:equal_variables, left, right) },
|
||||
'!=' => ->(cond, left, right) { !cond.send(:equal_variables, left, right) },
|
||||
@@ -61,7 +61,7 @@ module Liquid
|
||||
@child_condition = nil
|
||||
end
|
||||
|
||||
def evaluate(context = Context.new)
|
||||
def evaluate(context = deprecated_default_context)
|
||||
condition = self
|
||||
result = nil
|
||||
loop do
|
||||
@@ -134,8 +134,8 @@ module Liquid
|
||||
# return this as the result.
|
||||
return context.evaluate(left) if op.nil?
|
||||
|
||||
left = context.evaluate(left)
|
||||
right = context.evaluate(right)
|
||||
left = Liquid::Utils.to_liquid_value(context.evaluate(left))
|
||||
right = Liquid::Utils.to_liquid_value(context.evaluate(right))
|
||||
|
||||
operation = self.class.operators[op] || raise(Liquid::ArgumentError, "Unknown operator #{op}")
|
||||
|
||||
@@ -150,11 +150,19 @@ module Liquid
|
||||
end
|
||||
end
|
||||
|
||||
def deprecated_default_context
|
||||
warn("DEPRECATION WARNING: Condition#evaluate without a context argument is deprecated" \
|
||||
" and will be removed from Liquid 6.0.0.")
|
||||
Context.new
|
||||
end
|
||||
|
||||
class ParseTreeVisitor < Liquid::ParseTreeVisitor
|
||||
def children
|
||||
[
|
||||
@node.left, @node.right,
|
||||
@node.child_condition, @node.attachment
|
||||
@node.left,
|
||||
@node.right,
|
||||
@node.child_condition,
|
||||
@node.attachment
|
||||
].compact
|
||||
end
|
||||
end
|
||||
|
||||
@@ -26,9 +26,9 @@ module Liquid
|
||||
@environments = [environments]
|
||||
@environments.flatten!
|
||||
|
||||
@static_environments = [static_environments].flat_map(&:freeze).freeze
|
||||
@static_environments = [static_environments].flatten(1).freeze
|
||||
@scopes = [(outer_scope || {})]
|
||||
@registers = registers
|
||||
@registers = registers.is_a?(Registers) ? registers : Registers.new(registers)
|
||||
@errors = []
|
||||
@partial = false
|
||||
@strict_variables = false
|
||||
@@ -39,6 +39,10 @@ module Liquid
|
||||
@global_filter = nil
|
||||
@disabled_tags = {}
|
||||
|
||||
@registers.static[:cached_partials] ||= {}
|
||||
@registers.static[:file_system] ||= Liquid::Template.file_system
|
||||
@registers.static[:template_factory] ||= Liquid::TemplateFactory.new
|
||||
|
||||
self.exception_renderer = Template.default_exception_renderer
|
||||
if rethrow_errors
|
||||
self.exception_renderer = Liquid::RAISE_EXCEPTION_LAMBDA
|
||||
@@ -124,7 +128,7 @@ module Liquid
|
||||
# context['var'] = 'hi'
|
||||
# end
|
||||
#
|
||||
# context['var] #=> nil
|
||||
# context['var'] #=> nil
|
||||
def stack(new_scope = {})
|
||||
push(new_scope)
|
||||
yield
|
||||
@@ -140,7 +144,7 @@ module Liquid
|
||||
self.class.build(
|
||||
resource_limits: resource_limits,
|
||||
static_environments: static_environments,
|
||||
registers: StaticRegisters.new(registers)
|
||||
registers: Registers.new(registers),
|
||||
).tap do |subcontext|
|
||||
subcontext.base_scope_depth = base_scope_depth + 1
|
||||
subcontext.exception_renderer = exception_renderer
|
||||
|
||||
@@ -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
|
||||
|
||||
+15
-11
@@ -3,28 +3,33 @@
|
||||
module Liquid
|
||||
class Expression
|
||||
LITERALS = {
|
||||
nil => nil, 'nil' => nil, 'null' => nil, '' => nil,
|
||||
nil => nil,
|
||||
'nil' => nil,
|
||||
'null' => nil,
|
||||
'' => nil,
|
||||
'true' => true,
|
||||
'false' => false,
|
||||
'blank' => '',
|
||||
'empty' => ''
|
||||
}.freeze
|
||||
|
||||
SINGLE_QUOTED_STRING = /\A\s*'(.*)'\s*\z/m
|
||||
DOUBLE_QUOTED_STRING = /\A\s*"(.*)"\s*\z/m
|
||||
INTEGERS_REGEX = /\A\s*(-?\d+)\s*\z/
|
||||
FLOATS_REGEX = /\A\s*(-?\d[\d\.]+)\s*\z/
|
||||
INTEGERS_REGEX = /\A(-?\d+)\z/
|
||||
FLOATS_REGEX = /\A(-?\d[\d\.]+)\z/
|
||||
|
||||
# Use an atomic group (?>...) to avoid pathological backtracing from
|
||||
# malicious input as described in https://github.com/Shopify/liquid/issues/1357
|
||||
RANGES_REGEX = /\A\s*\(\s*(?>(\S+)\s*\.\.)\s*(\S+)\s*\)\s*\z/
|
||||
RANGES_REGEX = /\A\(\s*(?>(\S+)\s*\.\.)\s*(\S+)\s*\)\z/
|
||||
|
||||
def self.parse(markup)
|
||||
return nil unless markup
|
||||
|
||||
markup = markup.strip
|
||||
if (markup.start_with?('"') && markup.end_with?('"')) ||
|
||||
(markup.start_with?("'") && markup.end_with?("'"))
|
||||
return markup[1..-2]
|
||||
end
|
||||
|
||||
case markup
|
||||
when nil
|
||||
nil
|
||||
when SINGLE_QUOTED_STRING, DOUBLE_QUOTED_STRING
|
||||
Regexp.last_match(1)
|
||||
when INTEGERS_REGEX
|
||||
Regexp.last_match(1).to_i
|
||||
when RANGES_REGEX
|
||||
@@ -32,7 +37,6 @@ module Liquid
|
||||
when FLOATS_REGEX
|
||||
Regexp.last_match(1).to_f
|
||||
else
|
||||
markup = markup.strip
|
||||
if LITERALS.key?(markup)
|
||||
LITERALS[markup]
|
||||
else
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Liquid
|
||||
# @liquid_public_docs
|
||||
# @liquid_type object
|
||||
# @liquid_name forloop
|
||||
# @liquid_summary
|
||||
# Information about a parent [`for` loop](/docs/api/liquid/tags/for).
|
||||
class ForloopDrop < Drop
|
||||
def initialize(name, length, parentloop)
|
||||
@name = name
|
||||
@@ -9,33 +14,68 @@ module Liquid
|
||||
@index = 0
|
||||
end
|
||||
|
||||
attr_reader :length, :parentloop
|
||||
# @liquid_public_docs
|
||||
# @liquid_name length
|
||||
# @liquid_summary
|
||||
# The total number of iterations in the loop.
|
||||
# @liquid_return [number]
|
||||
attr_reader :length
|
||||
|
||||
def name
|
||||
Usage.increment('forloop_drop_name')
|
||||
@name
|
||||
end
|
||||
# @liquid_public_docs
|
||||
# @liquid_name parentloop
|
||||
# @liquid_summary
|
||||
# The parent `forloop` object.
|
||||
# @liquid_description
|
||||
# If the current `for` loop isn't nested inside another `for` loop, then `nil` is returned.
|
||||
# @liquid_return [forloop]
|
||||
attr_reader :parentloop
|
||||
|
||||
attr_reader :name
|
||||
|
||||
# @liquid_public_docs
|
||||
# @liquid_summary
|
||||
# The 1-based index of the current iteration.
|
||||
# @liquid_return [number]
|
||||
def index
|
||||
@index + 1
|
||||
end
|
||||
|
||||
# @liquid_public_docs
|
||||
# @liquid_summary
|
||||
# The 0-based index of the current iteration.
|
||||
# @liquid_return [number]
|
||||
def index0
|
||||
@index
|
||||
end
|
||||
|
||||
# @liquid_public_docs
|
||||
# @liquid_summary
|
||||
# The 1-based index of the current iteration, in reverse order.
|
||||
# @liquid_return [number]
|
||||
def rindex
|
||||
@length - @index
|
||||
end
|
||||
|
||||
# @liquid_public_docs
|
||||
# @liquid_summary
|
||||
# The 0-based index of the current iteration, in reverse order.
|
||||
# @liquid_return [number]
|
||||
def rindex0
|
||||
@length - @index - 1
|
||||
end
|
||||
|
||||
# @liquid_public_docs
|
||||
# @liquid_summary
|
||||
# Returns `true` if the current iteration is the first. Returns `false` if not.
|
||||
# @liquid_return [boolean]
|
||||
def first
|
||||
@index == 0
|
||||
end
|
||||
|
||||
# @liquid_public_docs
|
||||
# @liquid_summary
|
||||
# Returns `true` if the current iteration is the last. Returns `false` if not.
|
||||
# @liquid_return [boolean]
|
||||
def last
|
||||
@index == @length - 1
|
||||
end
|
||||
|
||||
+2
-3
@@ -18,6 +18,7 @@ module Liquid
|
||||
IDENTIFIER = /[a-zA-Z_][\w-]*\??/
|
||||
SINGLE_STRING_LITERAL = /'[^\']*'/
|
||||
DOUBLE_STRING_LITERAL = /"[^\"]*"/
|
||||
STRING_LITERAL = Regexp.union(SINGLE_STRING_LITERAL, DOUBLE_STRING_LITERAL)
|
||||
NUMBER_LITERAL = /-?\d+(\.\d+)?/
|
||||
DOTDOT = /\.\./
|
||||
COMPARISON_OPERATOR = /==|!=|<>|<=?|>=?|contains(?=\s)/
|
||||
@@ -35,9 +36,7 @@ module Liquid
|
||||
break if @ss.eos?
|
||||
tok = if (t = @ss.scan(COMPARISON_OPERATOR))
|
||||
[:comparison, t]
|
||||
elsif (t = @ss.scan(SINGLE_STRING_LITERAL))
|
||||
[:string, t]
|
||||
elsif (t = @ss.scan(DOUBLE_STRING_LITERAL))
|
||||
elsif (t = @ss.scan(STRING_LITERAL))
|
||||
[:string, t]
|
||||
elsif (t = @ss.scan(NUMBER_LITERAL))
|
||||
[:number, t]
|
||||
|
||||
@@ -13,15 +13,16 @@
|
||||
for_invalid_attribute: "Invalid attribute in for loop. Valid attributes are limit and offset"
|
||||
if: "Syntax Error in tag 'if' - Valid syntax: if [expression]"
|
||||
include: "Error in tag 'include' - Valid syntax: include '[template]' (with|for) [object|collection]"
|
||||
unknown_tag: "Unknown tag '%{tag}'"
|
||||
inline_comment_invalid: "Syntax error in tag '#' - Each line of comments must be prefixed by the '#' character"
|
||||
invalid_delimiter: "'%{tag}' is not a valid delimiter for %{block_name} tags. use %{block_delimiter}"
|
||||
render: "Syntax error in tag 'render' - Template name must be a quoted string"
|
||||
table_row: "Syntax Error in 'table_row loop' - Valid syntax: table_row [item] in [collection] cols=3"
|
||||
tag_never_closed: "'%{block_name}' tag was never closed"
|
||||
tag_termination: "Tag '%{token}' was not properly terminated with regexp: %{tag_end}"
|
||||
unexpected_else: "%{block_name} tag does not expect 'else' tag"
|
||||
unexpected_outer_tag: "Unexpected outer '%{tag}' tag"
|
||||
tag_termination: "Tag '%{token}' was not properly terminated with regexp: %{tag_end}"
|
||||
unknown_tag: "Unknown tag '%{tag}'"
|
||||
variable_termination: "Variable '%{token}' was not properly terminated with regexp: %{tag_end}"
|
||||
tag_never_closed: "'%{block_name}' tag was never closed"
|
||||
table_row: "Syntax Error in 'table_row loop' - Valid syntax: table_row [item] in [collection] cols=3"
|
||||
render: "Syntax error in tag 'render' - Template name must be a quoted string"
|
||||
argument:
|
||||
include: "Argument error in tag 'include' - Illegal template name"
|
||||
disabled:
|
||||
|
||||
@@ -3,20 +3,29 @@
|
||||
module Liquid
|
||||
class PartialCache
|
||||
def self.load(template_name, context:, parse_context:)
|
||||
cached_partials = (context.registers[:cached_partials] ||= {})
|
||||
cached = cached_partials[template_name]
|
||||
cached_partials = context.registers[:cached_partials]
|
||||
cache_key = "#{template_name}:#{parse_context.error_mode}"
|
||||
cached = cached_partials[cache_key]
|
||||
return cached if cached
|
||||
|
||||
file_system = (context.registers[:file_system] ||= Liquid::Template.file_system)
|
||||
file_system = context.registers[:file_system]
|
||||
source = file_system.read_template_file(template_name)
|
||||
|
||||
parse_context.partial = true
|
||||
|
||||
template_factory = (context.registers[:template_factory] ||= Liquid::TemplateFactory.new)
|
||||
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
|
||||
|
||||
@@ -8,10 +8,22 @@ module Liquid
|
||||
if start_obj.respond_to?(:evaluate) || end_obj.respond_to?(:evaluate)
|
||||
new(start_obj, end_obj)
|
||||
else
|
||||
start_obj.to_i..end_obj.to_i
|
||||
begin
|
||||
start_obj.to_i..end_obj.to_i
|
||||
rescue NoMethodError
|
||||
invalid_expr = start_markup unless start_obj.respond_to?(:to_i)
|
||||
invalid_expr ||= end_markup unless end_obj.respond_to?(:to_i)
|
||||
if invalid_expr
|
||||
raise Liquid::SyntaxError, "Invalid expression type '#{invalid_expr}' in range expression"
|
||||
end
|
||||
|
||||
raise
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
attr_reader :start_obj, :end_obj
|
||||
|
||||
def initialize(start_obj, end_obj)
|
||||
@start_obj = start_obj
|
||||
@end_obj = end_obj
|
||||
@@ -35,5 +47,11 @@ module Liquid
|
||||
Utils.to_integer(input)
|
||||
end
|
||||
end
|
||||
|
||||
class ParseTreeVisitor < Liquid::ParseTreeVisitor
|
||||
def children
|
||||
[@node.start_obj, @node.end_obj]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Liquid
|
||||
class Register
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,51 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Liquid
|
||||
class Registers
|
||||
attr_reader :static
|
||||
|
||||
def initialize(registers = {})
|
||||
@static = registers.is_a?(Registers) ? registers.static : registers
|
||||
@changes = {}
|
||||
end
|
||||
|
||||
def []=(key, value)
|
||||
@changes[key] = value
|
||||
end
|
||||
|
||||
def [](key)
|
||||
if @changes.key?(key)
|
||||
@changes[key]
|
||||
else
|
||||
@static[key]
|
||||
end
|
||||
end
|
||||
|
||||
def delete(key)
|
||||
@changes.delete(key)
|
||||
end
|
||||
|
||||
UNDEFINED = Object.new
|
||||
|
||||
def fetch(key, default = UNDEFINED, &block)
|
||||
if @changes.key?(key)
|
||||
@changes.fetch(key)
|
||||
elsif default != UNDEFINED
|
||||
if block_given?
|
||||
@static.fetch(key, &block)
|
||||
else
|
||||
@static.fetch(key, default)
|
||||
end
|
||||
else
|
||||
@static.fetch(key, &block)
|
||||
end
|
||||
end
|
||||
|
||||
def key?(key)
|
||||
@changes.key?(key) || @static.key?(key)
|
||||
end
|
||||
end
|
||||
|
||||
# Alias for backwards compatibility
|
||||
StaticRegisters = Registers
|
||||
end
|
||||
+536
-83
@@ -1,11 +1,19 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'cgi'
|
||||
require 'base64'
|
||||
require 'bigdecimal'
|
||||
|
||||
module Liquid
|
||||
module StandardFilters
|
||||
MAX_INT = (1 << 31) - 1
|
||||
MAX_I32 = (1 << 31) - 1
|
||||
private_constant :MAX_I32
|
||||
|
||||
MIN_I64 = -(1 << 63)
|
||||
MAX_I64 = (1 << 63) - 1
|
||||
I64_RANGE = MIN_I64..MAX_I64
|
||||
private_constant :MIN_I64, :MAX_I64, :I64_RANGE
|
||||
|
||||
HTML_ESCAPE = {
|
||||
'&' => '&',
|
||||
'>' => '>',
|
||||
@@ -17,43 +25,103 @@ module Liquid
|
||||
STRIP_HTML_BLOCKS = Regexp.union(
|
||||
%r{<script.*?</script>}m,
|
||||
/<!--.*?-->/m,
|
||||
%r{<style.*?</style>}m
|
||||
%r{<style.*?</style>}m,
|
||||
)
|
||||
STRIP_HTML_TAGS = /<.*?>/m
|
||||
|
||||
# Return the size of an array or of an string
|
||||
# @liquid_public_docs
|
||||
# @liquid_type filter
|
||||
# @liquid_category array
|
||||
# @liquid_summary
|
||||
# Returns the size of a string or array.
|
||||
# @liquid_description
|
||||
# The size of a string is the number of characters that the string includes. The size of an array is the number of items
|
||||
# in the array.
|
||||
# @liquid_syntax variable | size
|
||||
# @liquid_return [number]
|
||||
def size(input)
|
||||
input.respond_to?(:size) ? input.size : 0
|
||||
end
|
||||
|
||||
# convert an input string to DOWNCASE
|
||||
# @liquid_public_docs
|
||||
# @liquid_type filter
|
||||
# @liquid_category string
|
||||
# @liquid_summary
|
||||
# Converts a string to all lowercase characters.
|
||||
# @liquid_syntax string | downcase
|
||||
# @liquid_return [string]
|
||||
def downcase(input)
|
||||
input.to_s.downcase
|
||||
end
|
||||
|
||||
# convert an input string to UPCASE
|
||||
# @liquid_public_docs
|
||||
# @liquid_type filter
|
||||
# @liquid_category string
|
||||
# @liquid_summary
|
||||
# Converts a string to all uppercase characters.
|
||||
# @liquid_syntax string | upcase
|
||||
# @liquid_return [string]
|
||||
def upcase(input)
|
||||
input.to_s.upcase
|
||||
end
|
||||
|
||||
# capitalize words in the input centence
|
||||
# @liquid_public_docs
|
||||
# @liquid_type filter
|
||||
# @liquid_category string
|
||||
# @liquid_summary
|
||||
# Capitalizes the first word in a string.
|
||||
# @liquid_syntax string | capitalize
|
||||
# @liquid_return [string]
|
||||
def capitalize(input)
|
||||
input.to_s.capitalize
|
||||
end
|
||||
|
||||
# @liquid_public_docs
|
||||
# @liquid_type filter
|
||||
# @liquid_category string
|
||||
# @liquid_summary
|
||||
# Escapes special characters in HTML, such as `<>`, `'`, and `&`, and converts characters into escape sequences. The filter doesn't effect characters within the string that don’t have a corresponding escape sequence.".
|
||||
# @liquid_syntax string | escape
|
||||
# @liquid_return [string]
|
||||
def escape(input)
|
||||
CGI.escapeHTML(input.to_s) unless input.nil?
|
||||
end
|
||||
alias_method :h, :escape
|
||||
|
||||
# @liquid_public_docs
|
||||
# @liquid_type filter
|
||||
# @liquid_category string
|
||||
# @liquid_summary
|
||||
# Escapes a string without changing characters that have already been escaped.
|
||||
# @liquid_syntax string | escape_once
|
||||
# @liquid_return [string]
|
||||
def escape_once(input)
|
||||
input.to_s.gsub(HTML_ESCAPE_ONCE_REGEXP, HTML_ESCAPE)
|
||||
end
|
||||
|
||||
# @liquid_public_docs
|
||||
# @liquid_type filter
|
||||
# @liquid_category string
|
||||
# @liquid_summary
|
||||
# Converts any URL-unsafe characters in a string to the
|
||||
# [percent-encoded](https://developer.mozilla.org/en-US/docs/Glossary/percent-encoding) equivalent.
|
||||
# @liquid_description
|
||||
# > Note:
|
||||
# > Spaces are converted to a `+` character, instead of a percent-encoded character.
|
||||
# @liquid_syntax string | url_encode
|
||||
# @liquid_return [string]
|
||||
def url_encode(input)
|
||||
CGI.escape(input.to_s) unless input.nil?
|
||||
end
|
||||
|
||||
# @liquid_public_docs
|
||||
# @liquid_type filter
|
||||
# @liquid_category string
|
||||
# @liquid_summary
|
||||
# Decodes any [percent-encoded](https://developer.mozilla.org/en-US/docs/Glossary/percent-encoding) characters
|
||||
# in a string.
|
||||
# @liquid_syntax string | url_decode
|
||||
# @liquid_return [string]
|
||||
def url_decode(input)
|
||||
return if input.nil?
|
||||
|
||||
@@ -63,18 +131,94 @@ module Liquid
|
||||
result
|
||||
end
|
||||
|
||||
# @liquid_public_docs
|
||||
# @liquid_type filter
|
||||
# @liquid_category string
|
||||
# @liquid_summary
|
||||
# Encodes a string to [Base64 format](https://developer.mozilla.org/en-US/docs/Glossary/Base64).
|
||||
# @liquid_syntax string | base64_encode
|
||||
# @liquid_return [string]
|
||||
def base64_encode(input)
|
||||
Base64.strict_encode64(input.to_s)
|
||||
end
|
||||
|
||||
# @liquid_public_docs
|
||||
# @liquid_type filter
|
||||
# @liquid_category string
|
||||
# @liquid_summary
|
||||
# Decodes a string in [Base64 format](https://developer.mozilla.org/en-US/docs/Glossary/Base64).
|
||||
# @liquid_syntax string | base64_decode
|
||||
# @liquid_return [string]
|
||||
def base64_decode(input)
|
||||
Base64.strict_decode64(input.to_s)
|
||||
rescue ::ArgumentError
|
||||
raise Liquid::ArgumentError, "invalid base64 provided to base64_decode"
|
||||
end
|
||||
|
||||
# @liquid_public_docs
|
||||
# @liquid_type filter
|
||||
# @liquid_category string
|
||||
# @liquid_summary
|
||||
# Encodes a string to URL-safe [Base64 format](https://developer.mozilla.org/en-US/docs/Glossary/Base64).
|
||||
# @liquid_syntax string | base64_url_safe_encode
|
||||
# @liquid_return [string]
|
||||
def base64_url_safe_encode(input)
|
||||
Base64.urlsafe_encode64(input.to_s)
|
||||
end
|
||||
|
||||
# @liquid_public_docs
|
||||
# @liquid_type filter
|
||||
# @liquid_category string
|
||||
# @liquid_summary
|
||||
# Decodes a string in URL-safe [Base64 format](https://developer.mozilla.org/en-US/docs/Glossary/Base64).
|
||||
# @liquid_syntax string | base64_url_safe_decode
|
||||
# @liquid_return [string]
|
||||
def base64_url_safe_decode(input)
|
||||
Base64.urlsafe_decode64(input.to_s)
|
||||
rescue ::ArgumentError
|
||||
raise Liquid::ArgumentError, "invalid base64 provided to base64_url_safe_decode"
|
||||
end
|
||||
|
||||
# @liquid_public_docs
|
||||
# @liquid_type filter
|
||||
# @liquid_category string
|
||||
# @liquid_summary
|
||||
# Returns a substring or series of array items, starting at a given 0-based index.
|
||||
# @liquid_description
|
||||
# By default, the substring has a length of one character, and the array series has one array item. However, you can
|
||||
# provide a second parameter to specify the number of characters or array items.
|
||||
# @liquid_syntax string | slice
|
||||
# @liquid_return [string]
|
||||
def slice(input, offset, length = nil)
|
||||
offset = Utils.to_integer(offset)
|
||||
length = length ? Utils.to_integer(length) : 1
|
||||
|
||||
if input.is_a?(Array)
|
||||
input.slice(offset, length) || []
|
||||
else
|
||||
input.to_s.slice(offset, length) || ''
|
||||
begin
|
||||
if input.is_a?(Array)
|
||||
input.slice(offset, length) || []
|
||||
else
|
||||
input.to_s.slice(offset, length) || ''
|
||||
end
|
||||
rescue RangeError
|
||||
if I64_RANGE.cover?(length) && I64_RANGE.cover?(offset)
|
||||
raise # unexpected error
|
||||
end
|
||||
offset = offset.clamp(I64_RANGE)
|
||||
length = length.clamp(I64_RANGE)
|
||||
retry
|
||||
end
|
||||
end
|
||||
|
||||
# Truncate a string down to x characters
|
||||
# @liquid_public_docs
|
||||
# @liquid_type filter
|
||||
# @liquid_category string
|
||||
# @liquid_summary
|
||||
# Truncates a string down to a given number of characters.
|
||||
# @liquid_description
|
||||
# If the specified number of characters is less than the length of the string, then an ellipsis (`...`) is appended to
|
||||
# the truncated string. The ellipsis is included in the character count of the truncated string.
|
||||
# @liquid_syntax string | truncate: number
|
||||
# @liquid_return [string]
|
||||
def truncate(input, length = 50, truncate_string = "...")
|
||||
return if input.nil?
|
||||
input_str = input.to_s
|
||||
@@ -88,6 +232,20 @@ module Liquid
|
||||
input_str.length > length ? input_str[0...l].concat(truncate_string_str) : input_str
|
||||
end
|
||||
|
||||
# @liquid_public_docs
|
||||
# @liquid_type filter
|
||||
# @liquid_category string
|
||||
# @liquid_summary
|
||||
# Truncates a string down to a given number of words.
|
||||
# @liquid_description
|
||||
# If the specified number of words is less than the number of words in the string, then an ellipsis (`...`) is appended to
|
||||
# the truncated string.
|
||||
#
|
||||
# > Caution:
|
||||
# > HTML tags are treated as words, so you should strip any HTML from truncated content. If you don't strip HTML, then
|
||||
# > closing HTML tags can be removed, which can result in unexpected behavior.
|
||||
# @liquid_syntax string | truncatewords: number
|
||||
# @liquid_return [string]
|
||||
def truncatewords(input, words = 15, truncate_string = "...")
|
||||
return if input.nil?
|
||||
input = input.to_s
|
||||
@@ -97,9 +255,9 @@ module Liquid
|
||||
wordlist = begin
|
||||
input.split(" ", words + 1)
|
||||
rescue RangeError
|
||||
raise if words + 1 < MAX_INT
|
||||
# e.g. integer #{words} too big to convert to `int'
|
||||
raise Liquid::ArgumentError, "integer #{words} too big for truncatewords"
|
||||
# integer too big for String#split, but we can semantically assume no truncation is needed
|
||||
return input if words + 1 > MAX_I32
|
||||
raise # unexpected error
|
||||
end
|
||||
return input if wordlist.length <= words
|
||||
|
||||
@@ -107,27 +265,57 @@ module Liquid
|
||||
wordlist.join(" ").concat(truncate_string.to_s)
|
||||
end
|
||||
|
||||
# Split input string into an array of substrings separated by given pattern.
|
||||
#
|
||||
# Example:
|
||||
# <div class="summary">{{ post | split '//' | first }}</div>
|
||||
#
|
||||
# @liquid_public_docs
|
||||
# @liquid_type filter
|
||||
# @liquid_category string
|
||||
# @liquid_summary
|
||||
# Splits a string into an array of substrings based on a given separator.
|
||||
# @liquid_syntax string | split: string
|
||||
# @liquid_return [array[string]]
|
||||
def split(input, pattern)
|
||||
input.to_s.split(pattern.to_s)
|
||||
end
|
||||
|
||||
# @liquid_public_docs
|
||||
# @liquid_type filter
|
||||
# @liquid_category string
|
||||
# @liquid_summary
|
||||
# Strips all whitespace from the left and right of a string.
|
||||
# @liquid_syntax string | strip
|
||||
# @liquid_return [string]
|
||||
def strip(input)
|
||||
input.to_s.strip
|
||||
end
|
||||
|
||||
# @liquid_public_docs
|
||||
# @liquid_type filter
|
||||
# @liquid_category string
|
||||
# @liquid_summary
|
||||
# Strips all whitespace from the left of a string.
|
||||
# @liquid_syntax string | lstrip
|
||||
# @liquid_return [string]
|
||||
def lstrip(input)
|
||||
input.to_s.lstrip
|
||||
end
|
||||
|
||||
# @liquid_public_docs
|
||||
# @liquid_type filter
|
||||
# @liquid_category string
|
||||
# @liquid_summary
|
||||
# Strips all whitespace from the right of a string.
|
||||
# @liquid_syntax string | rstrip
|
||||
# @liquid_return [string]
|
||||
def rstrip(input)
|
||||
input.to_s.rstrip
|
||||
end
|
||||
|
||||
# @liquid_public_docs
|
||||
# @liquid_type filter
|
||||
# @liquid_category string
|
||||
# @liquid_summary
|
||||
# Strips all HTML tags from a string.
|
||||
# @liquid_syntax string | strip_html
|
||||
# @liquid_return [string]
|
||||
def strip_html(input)
|
||||
empty = ''
|
||||
result = input.to_s.gsub(STRIP_HTML_BLOCKS, empty)
|
||||
@@ -135,18 +323,35 @@ module Liquid
|
||||
result
|
||||
end
|
||||
|
||||
# Remove all newlines from the string
|
||||
# @liquid_public_docs
|
||||
# @liquid_type filter
|
||||
# @liquid_category string
|
||||
# @liquid_summary
|
||||
# Strips all newline characters (line breaks) from a string.
|
||||
# @liquid_syntax string | strip_newlines
|
||||
# @liquid_return [string]
|
||||
def strip_newlines(input)
|
||||
input.to_s.gsub(/\r?\n/, '')
|
||||
end
|
||||
|
||||
# Join elements of the array with certain character between them
|
||||
# @liquid_public_docs
|
||||
# @liquid_type filter
|
||||
# @liquid_category array
|
||||
# @liquid_summary
|
||||
# Combines all of the items in an array into a single string, separated by a space.
|
||||
# @liquid_syntax array | join
|
||||
# @liquid_return [string]
|
||||
def join(input, glue = ' ')
|
||||
InputIterator.new(input, context).join(glue)
|
||||
end
|
||||
|
||||
# Sort elements of the array
|
||||
# provide optional property with which to sort an array of hashes or drops
|
||||
# @liquid_public_docs
|
||||
# @liquid_type filter
|
||||
# @liquid_category array
|
||||
# @liquid_summary
|
||||
# Sorts the items in an array in case-sensitive alphabetical, or numerical, order.
|
||||
# @liquid_syntax array | sort
|
||||
# @liquid_return [array[untyped]]
|
||||
def sort(input, property = nil)
|
||||
ary = InputIterator.new(input, context)
|
||||
|
||||
@@ -165,8 +370,17 @@ module Liquid
|
||||
end
|
||||
end
|
||||
|
||||
# Sort elements of an array ignoring case if strings
|
||||
# provide optional property with which to sort an array of hashes or drops
|
||||
# @liquid_public_docs
|
||||
# @liquid_type filter
|
||||
# @liquid_category array
|
||||
# @liquid_summary
|
||||
# Sorts the items in an array in case-insensitive alphabetical order.
|
||||
# @liquid_description
|
||||
# > Caution:
|
||||
# > You shouldn't use the `sort_natural` filter to sort numerical values. When comparing items an array, each item is converted to a
|
||||
# > string, so sorting on numerical values can lead to unexpected results.
|
||||
# @liquid_syntax array | sort_natural
|
||||
# @liquid_return [array[untyped]]
|
||||
def sort_natural(input, property = nil)
|
||||
ary = InputIterator.new(input, context)
|
||||
|
||||
@@ -185,30 +399,48 @@ module Liquid
|
||||
end
|
||||
end
|
||||
|
||||
# Filter the elements of an array to those with a certain property value.
|
||||
# By default the target is any truthy value.
|
||||
# @liquid_public_docs
|
||||
# @liquid_type filter
|
||||
# @liquid_category array
|
||||
# @liquid_summary
|
||||
# Filters an array to include only items with a specific property value.
|
||||
# @liquid_description
|
||||
# This requires you to provide both the property name and the associated value.
|
||||
# @liquid_syntax array | where: string, string
|
||||
# @liquid_return [array[untyped]]
|
||||
def where(input, property, target_value = nil)
|
||||
ary = InputIterator.new(input, context)
|
||||
|
||||
if ary.empty?
|
||||
[]
|
||||
elsif ary.first.respond_to?(:[]) && target_value.nil?
|
||||
begin
|
||||
ary.select { |item| item[property] }
|
||||
elsif target_value.nil?
|
||||
ary.select do |item|
|
||||
item[property]
|
||||
rescue TypeError
|
||||
raise_property_error(property)
|
||||
rescue NoMethodError
|
||||
return nil unless item.respond_to?(:[])
|
||||
raise
|
||||
end
|
||||
elsif ary.first.respond_to?(:[])
|
||||
begin
|
||||
ary.select { |item| item[property] == target_value }
|
||||
else
|
||||
ary.select do |item|
|
||||
item[property] == target_value
|
||||
rescue TypeError
|
||||
raise_property_error(property)
|
||||
rescue NoMethodError
|
||||
return nil unless item.respond_to?(:[])
|
||||
raise
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Remove duplicate elements from an array
|
||||
# provide optional property with which to determine uniqueness
|
||||
# @liquid_public_docs
|
||||
# @liquid_type filter
|
||||
# @liquid_category array
|
||||
# @liquid_summary
|
||||
# Removes any duplicate items in an array.
|
||||
# @liquid_syntax array | uniq
|
||||
# @liquid_return [array[untyped]]
|
||||
def uniq(input, property = nil)
|
||||
ary = InputIterator.new(input, context)
|
||||
|
||||
@@ -216,22 +448,37 @@ module Liquid
|
||||
ary.uniq
|
||||
elsif ary.empty? # The next two cases assume a non-empty array.
|
||||
[]
|
||||
elsif ary.first.respond_to?(:[])
|
||||
begin
|
||||
ary.uniq { |a| a[property] }
|
||||
else
|
||||
ary.uniq do |item|
|
||||
item[property]
|
||||
rescue TypeError
|
||||
raise_property_error(property)
|
||||
rescue NoMethodError
|
||||
return nil unless item.respond_to?(:[])
|
||||
raise
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Reverse the elements of an array
|
||||
# @liquid_public_docs
|
||||
# @liquid_type filter
|
||||
# @liquid_category array
|
||||
# @liquid_summary
|
||||
# Reverses the order of the items in an array.
|
||||
# @liquid_syntax array | reverse
|
||||
# @liquid_return [array[untyped]]
|
||||
def reverse(input)
|
||||
ary = InputIterator.new(input, context)
|
||||
ary.reverse
|
||||
end
|
||||
|
||||
# map/collect on a given property
|
||||
# @liquid_public_docs
|
||||
# @liquid_type filter
|
||||
# @liquid_category array
|
||||
# @liquid_summary
|
||||
# Creates an array of values from a specific property of the items in an array.
|
||||
# @liquid_syntax array | map: string
|
||||
# @liquid_return [array[untyped]]
|
||||
def map(input, property)
|
||||
InputIterator.new(input, context).map do |e|
|
||||
e = e.call if e.is_a?(Proc)
|
||||
@@ -247,8 +494,13 @@ module Liquid
|
||||
raise_property_error(property)
|
||||
end
|
||||
|
||||
# Remove nils within an array
|
||||
# provide optional property with which to check for nil
|
||||
# @liquid_public_docs
|
||||
# @liquid_type filter
|
||||
# @liquid_category array
|
||||
# @liquid_summary
|
||||
# Removes any `nil` items from an array.
|
||||
# @liquid_syntax array | compact
|
||||
# @liquid_return [array[untyped]]
|
||||
def compact(input, property = nil)
|
||||
ary = InputIterator.new(input, context)
|
||||
|
||||
@@ -256,40 +508,116 @@ module Liquid
|
||||
ary.compact
|
||||
elsif ary.empty? # The next two cases assume a non-empty array.
|
||||
[]
|
||||
elsif ary.first.respond_to?(:[])
|
||||
begin
|
||||
ary.reject { |a| a[property].nil? }
|
||||
else
|
||||
ary.reject do |item|
|
||||
item[property].nil?
|
||||
rescue TypeError
|
||||
raise_property_error(property)
|
||||
rescue NoMethodError
|
||||
return nil unless item.respond_to?(:[])
|
||||
raise
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Replace occurrences of a string with another
|
||||
# @liquid_public_docs
|
||||
# @liquid_type filter
|
||||
# @liquid_category string
|
||||
# @liquid_summary
|
||||
# Replaces any instance of a substring inside a string with a given string.
|
||||
# @liquid_syntax string | replace: string, string
|
||||
# @liquid_return [string]
|
||||
def replace(input, string, replacement = '')
|
||||
input.to_s.gsub(string.to_s, replacement.to_s)
|
||||
end
|
||||
|
||||
# Replace the first occurrences of a string with another
|
||||
# @liquid_public_docs
|
||||
# @liquid_type filter
|
||||
# @liquid_category string
|
||||
# @liquid_summary
|
||||
# Replaces the first instance of a substring inside a string with a given string.
|
||||
# @liquid_syntax string | replace_first: string, string
|
||||
# @liquid_return [string]
|
||||
def replace_first(input, string, replacement = '')
|
||||
input.to_s.sub(string.to_s, replacement.to_s)
|
||||
end
|
||||
|
||||
# remove a substring
|
||||
# @liquid_public_docs
|
||||
# @liquid_type filter
|
||||
# @liquid_category string
|
||||
# @liquid_summary
|
||||
# Replaces the last instance of a substring inside a string with a given string.
|
||||
# @liquid_syntax string | replace_last: string, string
|
||||
# @liquid_return [string]
|
||||
def replace_last(input, string, replacement)
|
||||
input = input.to_s
|
||||
string = string.to_s
|
||||
replacement = replacement.to_s
|
||||
|
||||
start_index = input.rindex(string)
|
||||
|
||||
return input unless start_index
|
||||
|
||||
output = input.dup
|
||||
output[start_index, string.length] = replacement
|
||||
output
|
||||
end
|
||||
|
||||
# @liquid_public_docs
|
||||
# @liquid_type filter
|
||||
# @liquid_category string
|
||||
# @liquid_summary
|
||||
# Removes any instance of a substring inside a string.
|
||||
# @liquid_syntax string | remove: string
|
||||
# @liquid_return [string]
|
||||
def remove(input, string)
|
||||
input.to_s.gsub(string.to_s, '')
|
||||
replace(input, string, '')
|
||||
end
|
||||
|
||||
# remove the first occurrences of a substring
|
||||
# @liquid_public_docs
|
||||
# @liquid_type filter
|
||||
# @liquid_category string
|
||||
# @liquid_summary
|
||||
# Removes the first instance of a substring inside a string.
|
||||
# @liquid_syntax string | remove_first: string
|
||||
# @liquid_return [string]
|
||||
def remove_first(input, string)
|
||||
input.to_s.sub(string.to_s, '')
|
||||
replace_first(input, string, '')
|
||||
end
|
||||
|
||||
# add one string to another
|
||||
# @liquid_public_docs
|
||||
# @liquid_type filter
|
||||
# @liquid_category string
|
||||
# @liquid_summary
|
||||
# Removes the last instance of a substring inside a string.
|
||||
# @liquid_syntax string | remove_last: string
|
||||
# @liquid_return [string]
|
||||
def remove_last(input, string)
|
||||
replace_last(input, string, '')
|
||||
end
|
||||
|
||||
# @liquid_public_docs
|
||||
# @liquid_type filter
|
||||
# @liquid_category string
|
||||
# @liquid_summary
|
||||
# Adds a given string to the end of a string.
|
||||
# @liquid_syntax string | append: string
|
||||
# @liquid_return [string]
|
||||
def append(input, string)
|
||||
input.to_s + string.to_s
|
||||
end
|
||||
|
||||
# @liquid_public_docs
|
||||
# @liquid_type filter
|
||||
# @liquid_category array
|
||||
# @liquid_summary
|
||||
# Concatenates (combines) two arrays.
|
||||
# @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](/docs/api/liquid/filters/uniq).
|
||||
# @liquid_syntax array | concat: array
|
||||
# @liquid_return [array[untyped]]
|
||||
def concat(input, array)
|
||||
unless array.respond_to?(:to_ary)
|
||||
raise ArgumentError, "concat filter requires an array argument"
|
||||
@@ -297,12 +625,24 @@ module Liquid
|
||||
InputIterator.new(input, context).concat(array)
|
||||
end
|
||||
|
||||
# prepend a string to another
|
||||
# @liquid_public_docs
|
||||
# @liquid_type filter
|
||||
# @liquid_category string
|
||||
# @liquid_summary
|
||||
# Adds a given string to the beginning of a string.
|
||||
# @liquid_syntax string | prepend: string
|
||||
# @liquid_return [string]
|
||||
def prepend(input, string)
|
||||
string.to_s + input.to_s
|
||||
end
|
||||
|
||||
# Add <br /> tags in front of all newlines in input string
|
||||
# @liquid_public_docs
|
||||
# @liquid_type filter
|
||||
# @liquid_category string
|
||||
# @liquid_summary
|
||||
# Converts newlines (`\n`) in a string to HTML line breaks (`<br>`).
|
||||
# @liquid_syntax string | newline_to_br
|
||||
# @liquid_return [string]
|
||||
def newline_to_br(input)
|
||||
input.to_s.gsub(/\r?\n/, "<br />\n")
|
||||
end
|
||||
@@ -346,58 +686,106 @@ module Liquid
|
||||
date.strftime(format.to_s)
|
||||
end
|
||||
|
||||
# Get the first element of the passed in array
|
||||
#
|
||||
# Example:
|
||||
# {{ product.images | first | to_img }}
|
||||
#
|
||||
# @liquid_public_docs
|
||||
# @liquid_type filter
|
||||
# @liquid_category array
|
||||
# @liquid_summary
|
||||
# Returns the first item in an array.
|
||||
# @liquid_syntax array | first
|
||||
# @liquid_return [untyped]
|
||||
def first(array)
|
||||
array.first if array.respond_to?(:first)
|
||||
end
|
||||
|
||||
# Get the last element of the passed in array
|
||||
#
|
||||
# Example:
|
||||
# {{ product.images | last | to_img }}
|
||||
#
|
||||
# @liquid_public_docs
|
||||
# @liquid_type filter
|
||||
# @liquid_category array
|
||||
# @liquid_summary
|
||||
# Returns the last item in an array.
|
||||
# @liquid_syntax array | last
|
||||
# @liquid_return [untyped]
|
||||
def last(array)
|
||||
array.last if array.respond_to?(:last)
|
||||
end
|
||||
|
||||
# absolute value
|
||||
# @liquid_public_docs
|
||||
# @liquid_type filter
|
||||
# @liquid_category math
|
||||
# @liquid_summary
|
||||
# Returns the absolute value of a number.
|
||||
# @liquid_syntax number | abs
|
||||
# @liquid_return [number]
|
||||
def abs(input)
|
||||
result = Utils.to_number(input).abs
|
||||
result.is_a?(BigDecimal) ? result.to_f : result
|
||||
end
|
||||
|
||||
# addition
|
||||
# @liquid_public_docs
|
||||
# @liquid_type filter
|
||||
# @liquid_category math
|
||||
# @liquid_summary
|
||||
# Adds two numbers.
|
||||
# @liquid_syntax number | plus: number
|
||||
# @liquid_return [number]
|
||||
def plus(input, operand)
|
||||
apply_operation(input, operand, :+)
|
||||
end
|
||||
|
||||
# subtraction
|
||||
# @liquid_public_docs
|
||||
# @liquid_type filter
|
||||
# @liquid_category math
|
||||
# @liquid_summary
|
||||
# Subtracts a given number from another number.
|
||||
# @liquid_syntax number | minus: number
|
||||
# @liquid_return [number]
|
||||
def minus(input, operand)
|
||||
apply_operation(input, operand, :-)
|
||||
end
|
||||
|
||||
# multiplication
|
||||
# @liquid_public_docs
|
||||
# @liquid_type filter
|
||||
# @liquid_category math
|
||||
# @liquid_summary
|
||||
# Multiplies a number by a given number.
|
||||
# @liquid_syntax number | times: number
|
||||
# @liquid_return [number]
|
||||
def times(input, operand)
|
||||
apply_operation(input, operand, :*)
|
||||
end
|
||||
|
||||
# division
|
||||
# @liquid_public_docs
|
||||
# @liquid_type filter
|
||||
# @liquid_category math
|
||||
# @liquid_summary
|
||||
# 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)
|
||||
apply_operation(input, operand, :/)
|
||||
rescue ::ZeroDivisionError => e
|
||||
raise Liquid::ZeroDivisionError, e.message
|
||||
end
|
||||
|
||||
# @liquid_public_docs
|
||||
# @liquid_type filter
|
||||
# @liquid_category math
|
||||
# @liquid_summary
|
||||
# Returns the remainder of dividing a number by a given number.
|
||||
# @liquid_syntax number | modulo: number
|
||||
# @liquid_return [number]
|
||||
def modulo(input, operand)
|
||||
apply_operation(input, operand, :%)
|
||||
rescue ::ZeroDivisionError => e
|
||||
raise Liquid::ZeroDivisionError, e.message
|
||||
end
|
||||
|
||||
# @liquid_public_docs
|
||||
# @liquid_type filter
|
||||
# @liquid_category math
|
||||
# @liquid_summary
|
||||
# Rounds a number to the nearest integer.
|
||||
# @liquid_syntax number | round
|
||||
# @liquid_return [number]
|
||||
def round(input, n = 0)
|
||||
result = Utils.to_number(input).round(Utils.to_number(n))
|
||||
result = result.to_f if result.is_a?(BigDecimal)
|
||||
@@ -407,18 +795,39 @@ module Liquid
|
||||
raise Liquid::FloatDomainError, e.message
|
||||
end
|
||||
|
||||
# @liquid_public_docs
|
||||
# @liquid_type filter
|
||||
# @liquid_category math
|
||||
# @liquid_summary
|
||||
# Rounds a number up to the nearest integer.
|
||||
# @liquid_syntax number | ceil
|
||||
# @liquid_return [number]
|
||||
def ceil(input)
|
||||
Utils.to_number(input).ceil.to_i
|
||||
rescue ::FloatDomainError => e
|
||||
raise Liquid::FloatDomainError, e.message
|
||||
end
|
||||
|
||||
# @liquid_public_docs
|
||||
# @liquid_type filter
|
||||
# @liquid_category math
|
||||
# @liquid_summary
|
||||
# Rounds a number down to the nearest integer.
|
||||
# @liquid_syntax number | floor
|
||||
# @liquid_return [number]
|
||||
def floor(input)
|
||||
Utils.to_number(input).floor.to_i
|
||||
rescue ::FloatDomainError => e
|
||||
raise Liquid::FloatDomainError, e.message
|
||||
end
|
||||
|
||||
# @liquid_public_docs
|
||||
# @liquid_type filter
|
||||
# @liquid_category math
|
||||
# @liquid_summary
|
||||
# Limits a number to a minimum value.
|
||||
# @liquid_syntax number | at_least
|
||||
# @liquid_return [number]
|
||||
def at_least(input, n)
|
||||
min_value = Utils.to_number(n)
|
||||
|
||||
@@ -427,6 +836,13 @@ module Liquid
|
||||
result.is_a?(BigDecimal) ? result.to_f : result
|
||||
end
|
||||
|
||||
# @liquid_public_docs
|
||||
# @liquid_type filter
|
||||
# @liquid_category math
|
||||
# @liquid_summary
|
||||
# Limits a number to a maximum value.
|
||||
# @liquid_syntax number | at_most
|
||||
# @liquid_return [number]
|
||||
def at_most(input, n)
|
||||
max_value = Utils.to_number(n)
|
||||
|
||||
@@ -435,22 +851,52 @@ module Liquid
|
||||
result.is_a?(BigDecimal) ? result.to_f : result
|
||||
end
|
||||
|
||||
# Set a default value when the input is nil, false or empty
|
||||
#
|
||||
# Example:
|
||||
# {{ product.title | default: "No Title" }}
|
||||
#
|
||||
# Use `allow_false` when an input should only be tested against nil or empty and not false.
|
||||
#
|
||||
# Example:
|
||||
# {{ product.title | default: "No Title", allow_false: true }}
|
||||
# @liquid_public_docs
|
||||
# @liquid_type filter
|
||||
# @liquid_category default
|
||||
# @liquid_summary
|
||||
# Sets a default value for any variable whose value is one of the following:
|
||||
#
|
||||
# - [`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.
|
||||
def default(input, default_value = '', options = {})
|
||||
options = {} unless options.is_a?(Hash)
|
||||
false_check = options['allow_false'] ? input.nil? : !input
|
||||
false_check = options['allow_false'] ? input.nil? : !Liquid::Utils.to_liquid_value(input)
|
||||
false_check || (input.respond_to?(:empty?) && input.empty?) ? default_value : input
|
||||
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
|
||||
@@ -465,10 +911,16 @@ module Liquid
|
||||
end
|
||||
|
||||
def nil_safe_compare(a, b)
|
||||
if !a.nil? && !b.nil?
|
||||
a <=> b
|
||||
result = a <=> b
|
||||
|
||||
if result
|
||||
result
|
||||
elsif a.nil?
|
||||
1
|
||||
elsif b.nil?
|
||||
-1
|
||||
else
|
||||
a.nil? ? 1 : -1
|
||||
raise Liquid::ArgumentError, "cannot sort values of incompatible types"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -523,8 +975,9 @@ module Liquid
|
||||
|
||||
def each
|
||||
@input.each do |e|
|
||||
e = e.respond_to?(:to_liquid) ? e.to_liquid : e
|
||||
e.context = @context if e.respond_to?(:context=)
|
||||
yield(e.respond_to?(:to_liquid) ? e.to_liquid : e)
|
||||
yield(e)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Liquid
|
||||
class StaticRegisters
|
||||
attr_reader :static
|
||||
|
||||
def initialize(registers = {})
|
||||
@static = registers.is_a?(StaticRegisters) ? registers.static : registers
|
||||
@registers = {}
|
||||
end
|
||||
|
||||
def []=(key, value)
|
||||
@registers[key] = value
|
||||
end
|
||||
|
||||
def [](key)
|
||||
if @registers.key?(key)
|
||||
@registers[key]
|
||||
else
|
||||
@static[key]
|
||||
end
|
||||
end
|
||||
|
||||
def delete(key)
|
||||
@registers.delete(key)
|
||||
end
|
||||
|
||||
UNDEFINED = Object.new
|
||||
|
||||
def fetch(key, default = UNDEFINED, &block)
|
||||
if @registers.key?(key)
|
||||
@registers.fetch(key)
|
||||
elsif default != UNDEFINED
|
||||
@static.fetch(key, default, &block)
|
||||
else
|
||||
@static.fetch(key, &block)
|
||||
end
|
||||
end
|
||||
|
||||
def key?(key)
|
||||
@registers.key?(key) || @static.key?(key)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -7,25 +7,30 @@ module Liquid
|
||||
|
||||
def add_global_filter(filter)
|
||||
strainer_class_cache.clear
|
||||
global_filters << filter
|
||||
GlobalCache.add_filter(filter)
|
||||
end
|
||||
|
||||
def create(context, filters = [])
|
||||
strainer_from_cache(filters).new(context)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def global_filters
|
||||
@global_filters ||= []
|
||||
def global_filter_names
|
||||
GlobalCache.filter_method_names
|
||||
end
|
||||
|
||||
GlobalCache = Class.new(StrainerTemplate)
|
||||
|
||||
private
|
||||
|
||||
def strainer_from_cache(filters)
|
||||
strainer_class_cache[filters] ||= begin
|
||||
klass = Class.new(StrainerTemplate)
|
||||
global_filters.each { |f| klass.add_filter(f) }
|
||||
filters.each { |f| klass.add_filter(f) }
|
||||
klass
|
||||
if filters.empty?
|
||||
GlobalCache
|
||||
else
|
||||
strainer_class_cache[filters] ||= begin
|
||||
klass = Class.new(GlobalCache)
|
||||
filters.each { |f| klass.add_filter(f) }
|
||||
klass
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -31,6 +31,15 @@ module Liquid
|
||||
filter_methods.include?(method.to_s)
|
||||
end
|
||||
|
||||
def inherited(subclass)
|
||||
super
|
||||
subclass.instance_variable_set(:@filter_methods, @filter_methods.dup)
|
||||
end
|
||||
|
||||
def filter_method_names
|
||||
filter_methods.map(&:to_s).to_a
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def filter_methods
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Liquid
|
||||
# @liquid_public_docs
|
||||
# @liquid_type object
|
||||
# @liquid_name tablerowloop
|
||||
# @liquid_summary
|
||||
# Information about a parent [`tablerow` loop](/docs/api/liquid/tags/tablerow).
|
||||
class TablerowloopDrop < Drop
|
||||
def initialize(length, cols)
|
||||
@length = length
|
||||
@@ -10,40 +15,92 @@ module Liquid
|
||||
@index = 0
|
||||
end
|
||||
|
||||
attr_reader :length, :col, :row
|
||||
# @liquid_public_docs
|
||||
# @liquid_summary
|
||||
# The total number of iterations in the loop.
|
||||
# @liquid_return [number]
|
||||
attr_reader :length
|
||||
|
||||
# @liquid_public_docs
|
||||
# @liquid_summary
|
||||
# The 1-based index of the current column.
|
||||
# @liquid_return [number]
|
||||
attr_reader :col
|
||||
|
||||
# @liquid_public_docs
|
||||
# @liquid_summary
|
||||
# The 1-based index of current row.
|
||||
# @liquid_return [number]
|
||||
attr_reader :row
|
||||
|
||||
# @liquid_public_docs
|
||||
# @liquid_summary
|
||||
# The 1-based index of the current iteration.
|
||||
# @liquid_return [number]
|
||||
def index
|
||||
@index + 1
|
||||
end
|
||||
|
||||
# @liquid_public_docs
|
||||
# @liquid_summary
|
||||
# The 0-based index of the current iteration.
|
||||
# @liquid_return [number]
|
||||
def index0
|
||||
@index
|
||||
end
|
||||
|
||||
# @liquid_public_docs
|
||||
# @liquid_summary
|
||||
# The 0-based index of the current column.
|
||||
# @liquid_return [number]
|
||||
def col0
|
||||
@col - 1
|
||||
end
|
||||
|
||||
# @liquid_public_docs
|
||||
# @liquid_summary
|
||||
# The 1-based index of the current iteration, in reverse order.
|
||||
# @liquid_return [number]
|
||||
def rindex
|
||||
@length - @index
|
||||
end
|
||||
|
||||
# @liquid_public_docs
|
||||
# @liquid_summary
|
||||
# The 0-based index of the current iteration, in reverse order.
|
||||
# @liquid_return [number]
|
||||
def rindex0
|
||||
@length - @index - 1
|
||||
end
|
||||
|
||||
# @liquid_public_docs
|
||||
# @liquid_summary
|
||||
# Returns `true` if the current iteration is the first. Returns `false` if not.
|
||||
# @liquid_return [boolean]
|
||||
def first
|
||||
@index == 0
|
||||
end
|
||||
|
||||
# @liquid_public_docs
|
||||
# @liquid_summary
|
||||
# Returns `true` if the current iteration is the last. Returns `false` if not.
|
||||
# @liquid_return [boolean]
|
||||
def last
|
||||
@index == @length - 1
|
||||
end
|
||||
|
||||
# @liquid_public_docs
|
||||
# @liquid_summary
|
||||
# Returns `true` if the current column is the first in the row. Returns `false` if not.
|
||||
# @liquid_return [boolean]
|
||||
def col_first
|
||||
@col == 1
|
||||
end
|
||||
|
||||
# @liquid_public_docs
|
||||
# @liquid_summary
|
||||
# Returns `true` if the current column is the last in the row. Returns `false` if not.
|
||||
# @liquid_return [boolean]
|
||||
def col_last
|
||||
@col == @cols
|
||||
end
|
||||
|
||||
+8
-2
@@ -14,12 +14,18 @@ module Liquid
|
||||
end
|
||||
|
||||
def disable_tags(*tag_names)
|
||||
@disabled_tags ||= []
|
||||
@disabled_tags.concat(tag_names)
|
||||
tag_names += disabled_tags
|
||||
define_singleton_method(:disabled_tags) { tag_names }
|
||||
prepend(Disabler)
|
||||
end
|
||||
|
||||
private :new
|
||||
|
||||
protected
|
||||
|
||||
def disabled_tags
|
||||
[]
|
||||
end
|
||||
end
|
||||
|
||||
def initialize(tag_name, markup, parse_context)
|
||||
|
||||
@@ -3,14 +3,6 @@
|
||||
module Liquid
|
||||
class Tag
|
||||
module Disabler
|
||||
module ClassMethods
|
||||
attr_reader :disabled_tags
|
||||
end
|
||||
|
||||
def self.prepended(base)
|
||||
base.extend(ClassMethods)
|
||||
end
|
||||
|
||||
def render_to_output_buffer(context, output)
|
||||
context.with_disabled_tags(self.class.disabled_tags) do
|
||||
super
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Liquid
|
||||
# Assign sets a variable in your template.
|
||||
#
|
||||
# {% assign foo = 'monkey' %}
|
||||
#
|
||||
# You can then use the variable later in the page.
|
||||
#
|
||||
# {{ foo }}
|
||||
#
|
||||
# @liquid_public_docs
|
||||
# @liquid_type tag
|
||||
# @liquid_category variable
|
||||
# @liquid_name assign
|
||||
# @liquid_summary
|
||||
# Creates a new variable.
|
||||
# @liquid_description
|
||||
# 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.
|
||||
# @liquid_syntax_keyword value The value you want to assign to the variable.
|
||||
class Assign < Tag
|
||||
Syntax = /(#{VariableSignature}+)\s*=\s*(.*)\s*/om
|
||||
|
||||
|
||||
@@ -10,6 +10,14 @@ module Liquid
|
||||
# {% endif %}
|
||||
# {% endfor %}
|
||||
#
|
||||
# @liquid_public_docs
|
||||
# @liquid_type tag
|
||||
# @liquid_category iteration
|
||||
# @liquid_name break
|
||||
# @liquid_summary
|
||||
# Stops a [`for` loop](/docs/api/liquid/tags/for) from iterating.
|
||||
# @liquid_syntax
|
||||
# {% break %}
|
||||
class Break < Tag
|
||||
INTERRUPT = BreakInterrupt.new.freeze
|
||||
|
||||
|
||||
+13
-10
@@ -1,17 +1,20 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Liquid
|
||||
# Capture stores the result of a block into a variable without rendering it inplace.
|
||||
#
|
||||
# {% capture heading %}
|
||||
# Monkeys!
|
||||
# @liquid_public_docs
|
||||
# @liquid_type tag
|
||||
# @liquid_category variable
|
||||
# @liquid_name capture
|
||||
# @liquid_summary
|
||||
# Creates a new variable with a string value.
|
||||
# @liquid_description
|
||||
# You can create complex strings with Liquid logic and variables.
|
||||
# @liquid_syntax
|
||||
# {% capture variable %}
|
||||
# value
|
||||
# {% endcapture %}
|
||||
# ...
|
||||
# <h1>{{ heading }}</h1>
|
||||
#
|
||||
# Capture is useful for saving content for use later in your template, such as
|
||||
# in a sidebar or footer.
|
||||
#
|
||||
# @liquid_syntax_keyword variable The name of the variable being created.
|
||||
# @liquid_syntax_keyword value The value you want to assign to the variable.
|
||||
class Capture < Block
|
||||
Syntax = /(#{VariableSignature}+)/o
|
||||
|
||||
|
||||
+29
-1
@@ -1,6 +1,27 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Liquid
|
||||
# @liquid_public_docs
|
||||
# @liquid_type tag
|
||||
# @liquid_category conditional
|
||||
# @liquid_name case
|
||||
# @liquid_summary
|
||||
# Renders a specific expression depending on the value of a specific variable.
|
||||
# @liquid_syntax
|
||||
# {% case variable %}
|
||||
# {% when first_value %}
|
||||
# first_expression
|
||||
# {% when second_value %}
|
||||
# second_expression
|
||||
# {% else %}
|
||||
# third_expression
|
||||
# {% endcase %}
|
||||
# @liquid_syntax_keyword variable The name of the variable you want to base your case statement on.
|
||||
# @liquid_syntax_keyword first_value A specific value to check for.
|
||||
# @liquid_syntax_keyword second_value A specific value to check for.
|
||||
# @liquid_syntax_keyword first_expression An expression to be rendered when the variable's value matches `first_value`.
|
||||
# @liquid_syntax_keyword second_expression An expression to be rendered when the variable's value matches `second_value`.
|
||||
# @liquid_syntax_keyword third_expression An expression to be rendered when the variable's value has no match.
|
||||
class Case < Block
|
||||
Syntax = /(#{QuotedFragment})/o
|
||||
WhenSyntax = /(#{QuotedFragment})(?:(?:\s+or\s+|\s*\,\s*)(#{QuotedFragment}.*))?/om
|
||||
@@ -52,7 +73,14 @@ module Liquid
|
||||
@blocks.each do |block|
|
||||
if block.else?
|
||||
block.attachment.render_to_output_buffer(context, output) if execute_else_block
|
||||
elsif block.evaluate(context)
|
||||
next
|
||||
end
|
||||
|
||||
result = Liquid::Utils.to_liquid_value(
|
||||
block.evaluate(context),
|
||||
)
|
||||
|
||||
if result
|
||||
execute_else_block = false
|
||||
block.attachment.render_to_output_buffer(context, output)
|
||||
end
|
||||
|
||||
@@ -1,6 +1,19 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Liquid
|
||||
# @liquid_public_docs
|
||||
# @liquid_type tag
|
||||
# @liquid_category syntax
|
||||
# @liquid_name comment
|
||||
# @liquid_summary
|
||||
# Prevents an expression from being rendered or output.
|
||||
# @liquid_description
|
||||
# Any text inside `comment` tags won't be output, and any Liquid code will be parsed, but not executed.
|
||||
# @liquid_syntax
|
||||
# {% comment %}
|
||||
# content
|
||||
# {% endcomment %}
|
||||
# @liquid_syntax_keyword content The content of the comment.
|
||||
class Comment < Block
|
||||
def render_to_output_buffer(_context, output)
|
||||
output
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Liquid
|
||||
# Continue tag to be used to break out of a for loop.
|
||||
#
|
||||
# == Basic Usage:
|
||||
# {% for item in collection %}
|
||||
# {% if item.condition %}
|
||||
# {% continue %}
|
||||
# {% endif %}
|
||||
# {% endfor %}
|
||||
#
|
||||
# @liquid_public_docs
|
||||
# @liquid_type tag
|
||||
# @liquid_category iteration
|
||||
# @liquid_name continue
|
||||
# @liquid_summary
|
||||
# Causes a [`for` loop](/docs/api/liquid/tags/for) to skip to the next iteration.
|
||||
# @liquid_syntax
|
||||
# {% continue %}
|
||||
class Continue < Tag
|
||||
INTERRUPT = ContinueInterrupt.new.freeze
|
||||
|
||||
|
||||
+12
-11
@@ -1,18 +1,19 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Liquid
|
||||
# Cycle is usually used within a loop to alternate between values, like colors or DOM classes.
|
||||
#
|
||||
# {% for item in items %}
|
||||
# <div class="{% cycle 'red', 'green', 'blue' %}"> {{ item }} </div>
|
||||
# {% end %}
|
||||
#
|
||||
# <div class="red"> Item one </div>
|
||||
# <div class="green"> Item two </div>
|
||||
# <div class="blue"> Item three </div>
|
||||
# <div class="red"> Item four </div>
|
||||
# <div class="green"> Item five</div>
|
||||
# @liquid_public_docs
|
||||
# @liquid_type tag
|
||||
# @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](/docs/api/liquid/tags/for).
|
||||
# @liquid_description
|
||||
# The `cycle` tag must be used inside a `for` loop.
|
||||
#
|
||||
# > Tip:
|
||||
# > Use the `cycle` tag to output text in a predictable pattern. For example, to apply odd/even classes to rows in a table.
|
||||
# @liquid_syntax
|
||||
# {% cycle string, string, ... %}
|
||||
class Cycle < Tag
|
||||
SimpleSyntax = /\A#{QuotedFragment}+/o
|
||||
NamedSyntax = /\A(#{QuotedFragment})\s*\:\s*(.*)/om
|
||||
|
||||
@@ -1,34 +1,36 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Liquid
|
||||
# decrement is used in a place where one needs to insert a counter
|
||||
# into a template, and needs the counter to survive across
|
||||
# multiple instantiations of the template.
|
||||
# NOTE: decrement is a pre-decrement, --i,
|
||||
# while increment is post: i++.
|
||||
#
|
||||
# (To achieve the survival, the application must keep the context)
|
||||
#
|
||||
# if the variable does not exist, it is created with value 0.
|
||||
|
||||
# Hello: {% decrement variable %}
|
||||
#
|
||||
# gives you:
|
||||
#
|
||||
# Hello: -1
|
||||
# Hello: -2
|
||||
# Hello: -3
|
||||
# @liquid_public_docs
|
||||
# @liquid_type tag
|
||||
# @liquid_category variable
|
||||
# @liquid_name decrement
|
||||
# @liquid_summary
|
||||
# Creates a new variable, with a default value of -1, that's decreased by 1 with each subsequent call.
|
||||
# @liquid_description
|
||||
# Variables that are declared with `decrement` are unique to the [layout](/themes/architecture/layouts), [template](/themes/architecture/templates),
|
||||
# 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`](/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 %}
|
||||
# @liquid_syntax_keyword variable_name The name of the variable being decremented.
|
||||
class Decrement < Tag
|
||||
attr_reader :variable_name
|
||||
|
||||
def initialize(tag_name, markup, options)
|
||||
super
|
||||
@variable = markup.strip
|
||||
@variable_name = markup.strip
|
||||
end
|
||||
|
||||
def render_to_output_buffer(context, output)
|
||||
value = context.environments.first[@variable] ||= 0
|
||||
counter_environment = context.environments.first
|
||||
value = counter_environment[@variable_name] || 0
|
||||
value -= 1
|
||||
context.environments.first[@variable] = value
|
||||
counter_environment[@variable_name] = value
|
||||
output << value.to_s
|
||||
output
|
||||
end
|
||||
|
||||
+16
-9
@@ -1,16 +1,23 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Liquid
|
||||
# Echo outputs an expression
|
||||
#
|
||||
# {% echo monkey %}
|
||||
# {% echo user.name %}
|
||||
#
|
||||
# This is identical to variable output syntax, like {{ foo }}, but works
|
||||
# inside {% liquid %} tags. The full syntax is supported, including filters:
|
||||
#
|
||||
# {% echo user | link %}
|
||||
# @liquid_public_docs
|
||||
# @liquid_type tag
|
||||
# @liquid_category syntax
|
||||
# @liquid_name echo
|
||||
# @liquid_summary
|
||||
# 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](/docs/api/liquid/tags/liquid).
|
||||
#
|
||||
# > Tip:
|
||||
# > You can use [filters](/docs/api/liquid/filters) on expressions inside `echo` tags.
|
||||
# @liquid_syntax
|
||||
# {% liquid
|
||||
# echo expression
|
||||
# %}
|
||||
# @liquid_syntax_keyword expression The expression to be output.
|
||||
class Echo < Tag
|
||||
attr_reader :variable
|
||||
|
||||
|
||||
+25
-46
@@ -1,50 +1,29 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Liquid
|
||||
# "For" iterates over an array or collection.
|
||||
# Several useful variables are available to you within the loop.
|
||||
#
|
||||
# == Basic usage:
|
||||
# {% for item in collection %}
|
||||
# {{ forloop.index }}: {{ item.name }}
|
||||
# {% endfor %}
|
||||
#
|
||||
# == Advanced usage:
|
||||
# {% for item in collection %}
|
||||
# <div {% if forloop.first %}class="first"{% endif %}>
|
||||
# Item {{ forloop.index }}: {{ item.name }}
|
||||
# </div>
|
||||
# {% else %}
|
||||
# There is nothing in the collection.
|
||||
# {% endfor %}
|
||||
#
|
||||
# You can also define a limit and offset much like SQL. Remember
|
||||
# that offset starts at 0 for the first item.
|
||||
#
|
||||
# {% for item in collection limit:5 offset:10 %}
|
||||
# {{ item.name }}
|
||||
# {% end %}
|
||||
#
|
||||
# To reverse the for loop simply use {% for item in collection reversed %} (note that the flag's spelling is different to the filter `reverse`)
|
||||
#
|
||||
# == Available variables:
|
||||
#
|
||||
# forloop.name:: 'item-collection'
|
||||
# forloop.length:: Length of the loop
|
||||
# forloop.index:: The current item's position in the collection;
|
||||
# forloop.index starts at 1.
|
||||
# This is helpful for non-programmers who start believe
|
||||
# the first item in an array is 1, not 0.
|
||||
# forloop.index0:: The current item's position in the collection
|
||||
# where the first item is 0
|
||||
# forloop.rindex:: Number of items remaining in the loop
|
||||
# (length - index) where 1 is the last item.
|
||||
# forloop.rindex0:: Number of items remaining in the loop
|
||||
# where 0 is the last item.
|
||||
# forloop.first:: Returns true if the item is the first item.
|
||||
# forloop.last:: Returns true if the item is the last item.
|
||||
# forloop.parentloop:: Provides access to the parent loop, if present.
|
||||
# @liquid_public_docs
|
||||
# @liquid_type tag
|
||||
# @liquid_category iteration
|
||||
# @liquid_name for
|
||||
# @liquid_summary
|
||||
# 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](/docs/api/liquid/tags/paginate) to split the items over multiple pages.
|
||||
#
|
||||
# > Tip:
|
||||
# > 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
|
||||
# {% endfor %}
|
||||
# @liquid_syntax_keyword variable The current item in the array.
|
||||
# @liquid_syntax_keyword array The array to iterate over.
|
||||
# @liquid_syntax_keyword expression The expression to render for each iteration.
|
||||
# @liquid_optional_param limit [number] The number of iterations to perform.
|
||||
# @liquid_optional_param offset [number] The 1-based index to start iterating at.
|
||||
# @liquid_optional_param range [untyped] A custom numeric range to iterate over.
|
||||
# @liquid_optional_param reversed [untyped] Iterate in reverse order.
|
||||
class For < Block
|
||||
Syntax = /\A(#{VariableSegment}+)\s+in\s+(#{QuotedFragment}+)\s*(reversed)?/o
|
||||
|
||||
@@ -119,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)
|
||||
@@ -198,7 +178,6 @@ module Liquid
|
||||
case key
|
||||
when 'offset'
|
||||
@from = if expr == 'continue'
|
||||
Usage.increment('for_offset_continue')
|
||||
:continue
|
||||
else
|
||||
parse_expression(expr)
|
||||
|
||||
+16
-10
@@ -1,16 +1,18 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Liquid
|
||||
# If is the conditional block
|
||||
#
|
||||
# {% if user.admin %}
|
||||
# Admin user!
|
||||
# {% else %}
|
||||
# Not admin user
|
||||
# @liquid_public_docs
|
||||
# @liquid_type tag
|
||||
# @liquid_category conditional
|
||||
# @liquid_name if
|
||||
# @liquid_summary
|
||||
# Renders an expression if a specific condition is `true`.
|
||||
# @liquid_syntax
|
||||
# {% if condition %}
|
||||
# expression
|
||||
# {% endif %}
|
||||
#
|
||||
# There are {% if count < 5 %} less {% else %} more {% endif %} items than you need.
|
||||
#
|
||||
# @liquid_syntax_keyword condition The condition to evaluate.
|
||||
# @liquid_syntax_keyword expression The expression to render if the condition is met.
|
||||
class If < Block
|
||||
Syntax = /(#{QuotedFragment})\s*([=!<>a-z_]+)?\s*(#{QuotedFragment})?/o
|
||||
ExpressionsAndOperators = /(?:\b(?:\s?and\s?|\s?or\s?)\b|(?:\s*(?!\b(?:\s?and\s?|\s?or\s?)\b)(?:#{QuotedFragment}|\S+)\s*)+)/o
|
||||
@@ -50,7 +52,11 @@ module Liquid
|
||||
|
||||
def render_to_output_buffer(context, output)
|
||||
@blocks.each do |block|
|
||||
if block.evaluate(context)
|
||||
result = Liquid::Utils.to_liquid_value(
|
||||
block.evaluate(context),
|
||||
)
|
||||
|
||||
if result
|
||||
return block.attachment.render_to_output_buffer(context, output)
|
||||
end
|
||||
end
|
||||
|
||||
+21
-17
@@ -1,20 +1,22 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Liquid
|
||||
# Include allows templates to relate with other templates
|
||||
#
|
||||
# Simply include another template:
|
||||
#
|
||||
# {% include 'product' %}
|
||||
#
|
||||
# Include a template with a local variable:
|
||||
#
|
||||
# {% include 'product' with products[0] %}
|
||||
#
|
||||
# Include a template for a collection:
|
||||
#
|
||||
# {% include 'product' for products %}
|
||||
# @liquid_public_docs
|
||||
# @liquid_type tag
|
||||
# @liquid_category theme
|
||||
# @liquid_name include
|
||||
# @liquid_summary
|
||||
# Renders a [snippet](/themes/architecture#snippets).
|
||||
# @liquid_description
|
||||
# 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' %}
|
||||
# @liquid_syntax_keyword filename The name of the snippet to render, without the `.liquid` extension.
|
||||
# @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`](/docs/api/liquid/tags/render).
|
||||
class Include < Tag
|
||||
prepend Tag::Disableable
|
||||
|
||||
@@ -50,12 +52,12 @@ module Liquid
|
||||
|
||||
def render_to_output_buffer(context, output)
|
||||
template_name = context.evaluate(@template_name_expr)
|
||||
raise ArgumentError, options[:locale].t("errors.argument.include") unless template_name
|
||||
raise ArgumentError, options[:locale].t("errors.argument.include") unless template_name.is_a?(String)
|
||||
|
||||
partial = PartialCache.load(
|
||||
template_name,
|
||||
context: context,
|
||||
parse_context: parse_context
|
||||
parse_context: parse_context,
|
||||
)
|
||||
|
||||
context_variable_name = @alias_name || template_name.split('/').last
|
||||
@@ -68,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)
|
||||
|
||||
@@ -1,30 +1,35 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Liquid
|
||||
# increment is used in a place where one needs to insert a counter
|
||||
# into a template, and needs the counter to survive across
|
||||
# multiple instantiations of the template.
|
||||
# (To achieve the survival, the application must keep the context)
|
||||
#
|
||||
# if the variable does not exist, it is created with value 0.
|
||||
#
|
||||
# Hello: {% increment variable %}
|
||||
#
|
||||
# gives you:
|
||||
#
|
||||
# Hello: 0
|
||||
# Hello: 1
|
||||
# Hello: 2
|
||||
# @liquid_public_docs
|
||||
# @liquid_type tag
|
||||
# @liquid_category variable
|
||||
# @liquid_name increment
|
||||
# @liquid_summary
|
||||
# Creates a new variable, with a default value of 0, that's increased by 1 with each subsequent call.
|
||||
# @liquid_description
|
||||
# Variables that are declared with `increment` are unique to the [layout](/themes/architecture/layouts), [template](/themes/architecture/templates),
|
||||
# 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`](/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 %}
|
||||
# @liquid_syntax_keyword variable_name The name of the variable being incremented.
|
||||
class Increment < Tag
|
||||
attr_reader :variable_name
|
||||
|
||||
def initialize(tag_name, markup, options)
|
||||
super
|
||||
@variable = markup.strip
|
||||
@variable_name = markup.strip
|
||||
end
|
||||
|
||||
def render_to_output_buffer(context, output)
|
||||
value = context.environments.first[@variable] ||= 0
|
||||
context.environments.first[@variable] = value + 1
|
||||
counter_environment = context.environments.first
|
||||
value = counter_environment[@variable_name] || 0
|
||||
counter_environment[@variable_name] = value + 1
|
||||
|
||||
output << value.to_s
|
||||
output
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Liquid
|
||||
class InlineComment < Tag
|
||||
def initialize(tag_name, markup, options)
|
||||
super
|
||||
|
||||
# Semantically, a comment should only ignore everything after it on the line.
|
||||
# Currently, this implementation doesn't support mixing a comment with another tag
|
||||
# but we need to reserve future support for this and prevent the introduction
|
||||
# of inline comments from being backward incompatible change.
|
||||
#
|
||||
# As such, we're forcing users to put a # symbol on every line otherwise this
|
||||
# tag will throw an error.
|
||||
if markup.match?(/\n\s*[^#\s]/)
|
||||
raise SyntaxError, options[:locale].t("errors.syntax.inline_comment_invalid")
|
||||
end
|
||||
end
|
||||
|
||||
def render_to_output_buffer(_context, output)
|
||||
output
|
||||
end
|
||||
|
||||
def blank?
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
Template.register_tag('#', InlineComment)
|
||||
end
|
||||
+13
-1
@@ -1,9 +1,20 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Liquid
|
||||
# @liquid_public_docs
|
||||
# @liquid_type tag
|
||||
# @liquid_category syntax
|
||||
# @liquid_name raw
|
||||
# @liquid_summary
|
||||
# Outputs any Liquid code as text instead of rendering it.
|
||||
# @liquid_syntax
|
||||
# {% raw %}
|
||||
# expression
|
||||
# {% endraw %}
|
||||
# @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
|
||||
@@ -15,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
|
||||
|
||||
@@ -1,13 +1,37 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Liquid
|
||||
# @liquid_public_docs
|
||||
# @liquid_type tag
|
||||
# @liquid_category theme
|
||||
# @liquid_name render
|
||||
# @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](/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](/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](/docs/api/liquid/tags/include)
|
||||
# > inside the snippet.
|
||||
# @liquid_syntax
|
||||
# {% render 'filename' %}
|
||||
# @liquid_syntax_keyword filename The name of the snippet to render, without the `.liquid` extension.
|
||||
class Render < Tag
|
||||
FOR = 'for'
|
||||
SYNTAX = /(#{QuotedString}+)(\s+(with|#{FOR})\s+(#{QuotedFragment}+))?(\s+(?:as)\s+(#{VariableSegment}+))?/o
|
||||
|
||||
disable_tags "include"
|
||||
|
||||
attr_reader :template_name_expr, :attributes
|
||||
attr_reader :template_name_expr, :variable_name_expr, :attributes, :alias_name
|
||||
|
||||
def initialize(tag_name, markup, options)
|
||||
super
|
||||
@@ -21,7 +45,7 @@ module Liquid
|
||||
@alias_name = Regexp.last_match(6)
|
||||
@variable_name_expr = variable_name ? parse_expression(variable_name) : nil
|
||||
@template_name_expr = parse_expression(template_name)
|
||||
@for = (with_or_for == FOR)
|
||||
@is_for_loop = (with_or_for == FOR)
|
||||
|
||||
@attributes = {}
|
||||
markup.scan(TagAttributes) do |key, value|
|
||||
@@ -29,26 +53,30 @@ module Liquid
|
||||
end
|
||||
end
|
||||
|
||||
def for_loop?
|
||||
@is_for_loop
|
||||
end
|
||||
|
||||
def render_to_output_buffer(context, output)
|
||||
render_tag(context, output)
|
||||
end
|
||||
|
||||
def render_tag(context, output)
|
||||
# Though we evaluate this here we will only ever parse it as a string literal.
|
||||
template_name = context.evaluate(@template_name_expr)
|
||||
raise ArgumentError, options[:locale].t("errors.argument.include") unless template_name
|
||||
# The expression should be a String literal, which parses to a String object
|
||||
template_name = @template_name_expr
|
||||
raise ::ArgumentError unless template_name.is_a?(String)
|
||||
|
||||
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
|
||||
|
||||
@@ -61,7 +89,7 @@ module Liquid
|
||||
}
|
||||
|
||||
variable = @variable_name_expr ? context.evaluate(@variable_name_expr) : nil
|
||||
if @for && variable.respond_to?(:each) && variable.respond_to?(:count)
|
||||
if @is_for_loop && variable.respond_to?(:each) && variable.respond_to?(:count)
|
||||
forloop = Liquid::ForloopDrop.new(template_name, variable.count, nil)
|
||||
variable.each { |var| render_partial_func.call(var, forloop) }
|
||||
else
|
||||
@@ -75,6 +103,7 @@ module Liquid
|
||||
def children
|
||||
[
|
||||
@node.template_name_expr,
|
||||
@node.variable_name_expr,
|
||||
] + @node.attributes.values
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,6 +1,28 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Liquid
|
||||
# @liquid_public_docs
|
||||
# @liquid_type tag
|
||||
# @liquid_category iteration
|
||||
# @liquid_name tablerow
|
||||
# @liquid_summary
|
||||
# Generates HTML table rows for every item in an array.
|
||||
# @liquid_description
|
||||
# The `tablerow` tag must be wrapped in HTML `<table>` and `</table>` tags.
|
||||
#
|
||||
# > Tip:
|
||||
# > 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
|
||||
# {% endtablerow %}
|
||||
# @liquid_syntax_keyword variable The current item in the array.
|
||||
# @liquid_syntax_keyword array The array to iterate over.
|
||||
# @liquid_syntax_keyword expression The expression to render.
|
||||
# @liquid_optional_param cols [number] The number of columns that the table should have.
|
||||
# @liquid_optional_param limit [number] The number of iterations to perform.
|
||||
# @liquid_optional_param offset [number] The 1-based index to start iterating at.
|
||||
# @liquid_optional_param range [untyped] A custom numeric range to iterate over.
|
||||
class TableRow < Block
|
||||
Syntax = /(\w+)\s+in\s+(#{QuotedFragment}+)/o
|
||||
|
||||
@@ -23,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 = context.evaluate(@attributes['cols']).to_i
|
||||
cols = @attributes.key?('cols') ? to_integer(context.evaluate(@attributes['cols'])) : length
|
||||
|
||||
output << "<tr class=\"row1\">\n"
|
||||
context.stack do
|
||||
@@ -60,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,21 +3,40 @@
|
||||
require_relative 'if'
|
||||
|
||||
module Liquid
|
||||
# Unless is a conditional just like 'if' but works on the inverse logic.
|
||||
#
|
||||
# {% unless x < 0 %} x is greater than zero {% endunless %}
|
||||
#
|
||||
# @liquid_public_docs
|
||||
# @liquid_type tag
|
||||
# @liquid_category conditional
|
||||
# @liquid_name unless
|
||||
# @liquid_summary
|
||||
# Renders an expression unless a specific condition is `true`.
|
||||
# @liquid_description
|
||||
# > Tip:
|
||||
# > 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
|
||||
# {% endunless %}
|
||||
# @liquid_syntax_keyword condition The condition to evaluate.
|
||||
# @liquid_syntax_keyword expression The expression to render unless the condition is met.
|
||||
class Unless < If
|
||||
def render_to_output_buffer(context, output)
|
||||
# First condition is interpreted backwards ( if not )
|
||||
first_block = @blocks.first
|
||||
unless first_block.evaluate(context)
|
||||
result = Liquid::Utils.to_liquid_value(
|
||||
first_block.evaluate(context),
|
||||
)
|
||||
|
||||
unless result
|
||||
return first_block.attachment.render_to_output_buffer(context, output)
|
||||
end
|
||||
|
||||
# After the first condition unless works just like if
|
||||
@blocks[1..-1].each do |block|
|
||||
if block.evaluate(context)
|
||||
result = Liquid::Utils.to_liquid_value(
|
||||
block.evaluate(context),
|
||||
)
|
||||
|
||||
if result
|
||||
return block.attachment.render_to_output_buffer(context, output)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -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
|
||||
@@ -167,15 +167,14 @@ module Liquid
|
||||
|
||||
output = nil
|
||||
|
||||
context_register = context.registers.is_a?(StaticRegisters) ? context.registers.static : context.registers
|
||||
|
||||
case args.last
|
||||
when Hash
|
||||
options = args.pop
|
||||
output = options[:output] if options[:output]
|
||||
static_registers = context.registers.static
|
||||
|
||||
options[:registers]&.each do |key, register|
|
||||
context_register[key] = register
|
||||
static_registers[key] = register
|
||||
end
|
||||
|
||||
apply_options_to_context(context, options)
|
||||
@@ -190,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 || +'')
|
||||
|
||||
@@ -8,11 +8,15 @@ module Liquid
|
||||
@source = source.to_s.to_str
|
||||
@line_number = line_number || (line_numbers ? 1 : nil)
|
||||
@for_liquid_tag = for_liquid_tag
|
||||
@offset = 0
|
||||
@tokens = tokenize
|
||||
end
|
||||
|
||||
def shift
|
||||
(token = @tokens.shift) || return
|
||||
token = @tokens[@offset]
|
||||
return nil unless token
|
||||
|
||||
@offset += 1
|
||||
|
||||
if @line_number
|
||||
@line_number += @for_liquid_tag ? 1 : token.count("\n")
|
||||
@@ -31,7 +35,9 @@ module Liquid
|
||||
tokens = @source.split(TemplateParser)
|
||||
|
||||
# removes the rogue empty element at the beginning of the array
|
||||
tokens.shift if tokens[0]&.empty?
|
||||
if tokens[0]&.empty?
|
||||
@offset += 1
|
||||
end
|
||||
|
||||
tokens
|
||||
end
|
||||
|
||||
@@ -81,5 +81,13 @@ module Liquid
|
||||
rescue ::ArgumentError
|
||||
nil
|
||||
end
|
||||
|
||||
def self.to_liquid_value(obj)
|
||||
# Enable "obj" to represent itself as a primitive value like integer, string, or boolean
|
||||
return obj.to_liquid_value if obj.respond_to?(:to_liquid_value)
|
||||
|
||||
# Otherwise return the object itself
|
||||
obj
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -47,7 +47,7 @@ module Liquid
|
||||
|
||||
name_markup = Regexp.last_match(1)
|
||||
filter_markup = Regexp.last_match(2)
|
||||
@name = Expression.parse(name_markup)
|
||||
@name = parse_context.parse_expression(name_markup)
|
||||
if filter_markup =~ FilterMarkupRegex
|
||||
filters = Regexp.last_match(1).scan(FilterParser)
|
||||
filters.each do |f|
|
||||
@@ -65,7 +65,7 @@ module Liquid
|
||||
|
||||
return if p.look(:end_of_string)
|
||||
|
||||
@name = Expression.parse(p.expression)
|
||||
@name = parse_context.parse_expression(p.expression)
|
||||
while p.consume?(:pipe)
|
||||
filtername = p.consume(:id)
|
||||
filterargs = p.consume?(:colon) ? parse_filterargs(p) : []
|
||||
@@ -122,9 +122,9 @@ module Liquid
|
||||
unparsed_args.each do |a|
|
||||
if (matches = a.match(JustTagAttributes))
|
||||
keyword_args ||= {}
|
||||
keyword_args[matches[1]] = Expression.parse(matches[2])
|
||||
keyword_args[matches[1]] = parse_context.parse_expression(matches[2])
|
||||
else
|
||||
filter_args << Expression.parse(a)
|
||||
filter_args << parse_context.parse_expression(a)
|
||||
end
|
||||
end
|
||||
result = [filter_name, filter_args]
|
||||
|
||||
@@ -2,8 +2,7 @@
|
||||
|
||||
module Liquid
|
||||
class VariableLookup
|
||||
SQUARE_BRACKETED = /\A\[(.*)\]\z/m
|
||||
COMMAND_METHODS = ['size', 'first', 'last'].freeze
|
||||
COMMAND_METHODS = ['size', 'first', 'last'].freeze
|
||||
|
||||
attr_reader :name, :lookups
|
||||
|
||||
@@ -15,8 +14,8 @@ module Liquid
|
||||
lookups = markup.scan(VariableParser)
|
||||
|
||||
name = lookups.shift
|
||||
if name =~ SQUARE_BRACKETED
|
||||
name = Expression.parse(Regexp.last_match(1))
|
||||
if name&.start_with?('[') && name&.end_with?(']')
|
||||
name = Expression.parse(name[1..-2])
|
||||
end
|
||||
@name = name
|
||||
|
||||
@@ -25,14 +24,18 @@ module Liquid
|
||||
|
||||
@lookups.each_index do |i|
|
||||
lookup = lookups[i]
|
||||
if lookup =~ SQUARE_BRACKETED
|
||||
lookups[i] = Expression.parse(Regexp.last_match(1))
|
||||
if lookup&.start_with?('[') && lookup&.end_with?(']')
|
||||
lookups[i] = Expression.parse(lookup[1..-2])
|
||||
elsif COMMAND_METHODS.include?(lookup)
|
||||
@command_flags |= 1 << i
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def lookup_command?(lookup_index)
|
||||
@command_flags & (1 << lookup_index) != 0
|
||||
end
|
||||
|
||||
def evaluate(context)
|
||||
name = context.evaluate(@name)
|
||||
object = context.find_variable(name)
|
||||
@@ -40,6 +43,9 @@ module Liquid
|
||||
@lookups.each_index do |i|
|
||||
key = context.evaluate(@lookups[i])
|
||||
|
||||
# Cast "key" to its liquid value to enable it to act as a primitive value
|
||||
key = Liquid::Utils.to_liquid_value(key)
|
||||
|
||||
# If object is a hash- or array-like object we look for the
|
||||
# presence of the key and if its available we return it
|
||||
if object.respond_to?(:[]) &&
|
||||
@@ -53,7 +59,7 @@ module Liquid
|
||||
# Some special cases. If the part wasn't in square brackets and
|
||||
# no key with the same name was found we interpret following calls
|
||||
# as commands and call them on the current object
|
||||
elsif @command_flags & (1 << i) != 0 && object.respond_to?(key)
|
||||
elsif lookup_command?(i) && object.respond_to?(key)
|
||||
object = object.send(key).to_liquid
|
||||
|
||||
# No key was present with the desired value and it wasn't one of the directly supported
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Liquid
|
||||
VERSION = "5.0.2.alpha"
|
||||
VERSION = "5.4.0"
|
||||
end
|
||||
|
||||
+2
-3
@@ -17,13 +17,12 @@ Gem::Specification.new do |s|
|
||||
s.license = "MIT"
|
||||
# s.description = "A secure, non-evaling end user template engine with aesthetic markup."
|
||||
|
||||
s.required_ruby_version = ">= 2.5.0"
|
||||
s.required_ruby_version = ">= 2.7.0"
|
||||
s.required_rubygems_version = ">= 1.3.7"
|
||||
|
||||
s.metadata['allowed_push_host'] = 'https://rubygems.org'
|
||||
|
||||
s.test_files = Dir.glob("{test}/**/*")
|
||||
s.files = Dir.glob("{lib}/**/*") + %w(LICENSE README.md)
|
||||
s.files = Dir.glob("{lib}/**/*") + %w(LICENSE README.md)
|
||||
|
||||
s.extra_rdoc_files = ["History.md", "README.md"]
|
||||
|
||||
|
||||
@@ -3,11 +3,19 @@
|
||||
require 'yaml'
|
||||
|
||||
module Database
|
||||
DATABASE_FILE_PATH = "#{__dir__}/vision.database.yml"
|
||||
|
||||
# Load the standard vision toolkit database and re-arrage it to be simply exportable
|
||||
# to liquid as assigns. All this is based on Shopify
|
||||
def self.tables
|
||||
@tables ||= begin
|
||||
db = YAML.load_file("#{__dir__}/vision.database.yml")
|
||||
db =
|
||||
if YAML.respond_to?(:unsafe_load_file) # Only Psych 4+ can use unsafe_load_file
|
||||
# unsafe_load_file is needed for YAML references
|
||||
YAML.unsafe_load_file(DATABASE_FILE_PATH)
|
||||
else
|
||||
YAML.load_file(DATABASE_FILE_PATH)
|
||||
end
|
||||
|
||||
# From vision source
|
||||
db['products'].each do |product|
|
||||
@@ -41,8 +49,3 @@ module Database
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if __FILE__ == $PROGRAM_NAME
|
||||
p(Database.tables['collections']['frontpage'].keys)
|
||||
# p Database.tables['blog']['articles']
|
||||
end
|
||||
|
||||
@@ -45,8 +45,8 @@ class Paginate < Liquid::Block
|
||||
|
||||
pagination['items'] = collection_size
|
||||
pagination['pages'] = page_count - 1
|
||||
pagination['previous'] = link('« Previous', current_page - 1) unless 1 >= current_page
|
||||
pagination['next'] = link('Next »', current_page + 1) unless page_count <= current_page + 1
|
||||
pagination['previous'] = link('« Previous', current_page - 1) if 1 < current_page
|
||||
pagination['next'] = link('Next »', current_page + 1) if page_count > current_page + 1
|
||||
pagination['parts'] = []
|
||||
|
||||
hellip_break = false
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
# This profiler run simulates Shopify.
|
||||
# We are looking in the tests directory for liquid files and render them within the designated layout file.
|
||||
# We will also export a substantial database to liquid which the templates can render values of.
|
||||
# All this is to make the benchmark as non syntetic as possible. All templates and tests are lifted from
|
||||
# All this is to make the benchmark as non synthetic as possible. All templates and tests are lifted from
|
||||
# direct real-world usage and the profiler measures code that looks very similar to the way it looks in
|
||||
# Shopify which is likely the biggest user of liquid in the world which something to the tune of several
|
||||
# million Template#render calls a day.
|
||||
|
||||
@@ -6,51 +6,51 @@ class AssignTest < Minitest::Test
|
||||
include Liquid
|
||||
|
||||
def test_assign_with_hyphen_in_variable_name
|
||||
template_source = <<-END_TEMPLATE
|
||||
{% assign this-thing = 'Print this-thing' %}
|
||||
{{ this-thing }}
|
||||
template_source = <<~END_TEMPLATE
|
||||
{% assign this-thing = 'Print this-thing' -%}
|
||||
{{ this-thing -}}
|
||||
END_TEMPLATE
|
||||
template = Template.parse(template_source)
|
||||
rendered = template.render!
|
||||
assert_equal("Print this-thing", rendered.strip)
|
||||
assert_template_result("Print this-thing", template_source)
|
||||
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
|
||||
assert_match_syntax_error(/assign/,
|
||||
'{% assign foo not values %}.',
|
||||
'values' => "foo,bar,baz")
|
||||
assert_match_syntax_error(/assign/, '{% assign foo not values %}.')
|
||||
end
|
||||
|
||||
def test_assign_uses_error_mode
|
||||
with_error_mode(:strict) do
|
||||
assert_raises(SyntaxError) do
|
||||
Template.parse("{% assign foo = ('X' | downcase) %}")
|
||||
end
|
||||
end
|
||||
with_error_mode(:lax) do
|
||||
assert(Template.parse("{% assign foo = ('X' | downcase) %}"))
|
||||
end
|
||||
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
|
||||
|
||||
def test_expression_with_whitespace_in_square_brackets
|
||||
source = "{% assign r = a[ 'b' ] %}{{ r }}"
|
||||
assert_template_result('result', source, 'a' => { 'b' => 'result' })
|
||||
assert_template_result('result', source, { 'a' => { 'b' => 'result' } })
|
||||
end
|
||||
|
||||
def test_assign_score_exceeding_resource_limit
|
||||
|
||||
@@ -9,12 +9,6 @@ class FoobarTag < Liquid::Tag
|
||||
end
|
||||
end
|
||||
|
||||
class BlankTestFileSystem
|
||||
def read_template_file(template_path)
|
||||
template_path
|
||||
end
|
||||
end
|
||||
|
||||
class BlankTest < Minitest::Test
|
||||
include Liquid
|
||||
N = 10
|
||||
@@ -33,7 +27,7 @@ class BlankTest < Minitest::Test
|
||||
|
||||
def test_new_tags_are_not_blank_by_default
|
||||
with_custom_tag('foobar', FoobarTag) do
|
||||
assert_template_result(" " * N, wrap_in_for("{% foobar %}"))
|
||||
assert_equal(" " * N, Liquid::Template.parse(wrap_in_for("{% foobar %}")).render!)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -63,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
|
||||
@@ -95,10 +91,21 @@ class BlankTest < Minitest::Test
|
||||
end
|
||||
|
||||
def test_include_is_blank
|
||||
Liquid::Template.file_system = BlankTestFileSystem.new
|
||||
assert_template_result("foobar" * (N + 1), wrap("{% include 'foobar' %}"))
|
||||
assert_template_result(" foobar " * (N + 1), wrap("{% include ' foobar ' %}"))
|
||||
assert_template_result(" " * (N + 1), wrap(" {% include ' ' %} "))
|
||||
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
|
||||
|
||||
@@ -6,10 +6,8 @@ class BlockTest < Minitest::Test
|
||||
include Liquid
|
||||
|
||||
def test_unexpected_end_tag
|
||||
exc = assert_raises(SyntaxError) do
|
||||
Template.parse("{% if true %}{% endunless %}")
|
||||
end
|
||||
assert_equal(exc.message, "Liquid syntax error: 'endunless' is not a valid delimiter for if tags. use endif")
|
||||
source = '{% if true %}{% endunless %}'
|
||||
assert_match_syntax_error("Liquid syntax error (line 1): 'endunless' is not a valid delimiter for if tags. use endif", source)
|
||||
end
|
||||
|
||||
def test_with_custom_tag
|
||||
|
||||
@@ -10,44 +10,38 @@ class CaptureTest < Minitest::Test
|
||||
end
|
||||
|
||||
def test_capture_with_hyphen_in_variable_name
|
||||
template_source = <<-END_TEMPLATE
|
||||
{% capture this-thing %}Print this-thing{% endcapture %}
|
||||
{{ this-thing }}
|
||||
template_source = <<~END_TEMPLATE
|
||||
{% capture this-thing %}Print this-thing{% endcapture -%}
|
||||
{{ this-thing -}}
|
||||
END_TEMPLATE
|
||||
template = Template.parse(template_source)
|
||||
rendered = template.render!
|
||||
assert_equal("Print this-thing", rendered.strip)
|
||||
assert_template_result("Print this-thing", template_source)
|
||||
end
|
||||
|
||||
def test_capture_to_variable_from_outer_scope_if_existing
|
||||
template_source = <<-END_TEMPLATE
|
||||
{% assign var = '' %}
|
||||
{% if true %}
|
||||
{% capture var %}first-block-string{% endcapture %}
|
||||
{% endif %}
|
||||
{% if true %}
|
||||
{% capture var %}test-string{% endcapture %}
|
||||
{% endif %}
|
||||
{{var}}
|
||||
template_source = <<~END_TEMPLATE
|
||||
{% assign var = '' -%}
|
||||
{% if true -%}
|
||||
{% capture var %}first-block-string{% endcapture -%}
|
||||
{% endif -%}
|
||||
{% if true -%}
|
||||
{% capture var %}test-string{% endcapture -%}
|
||||
{% endif -%}
|
||||
{{var-}}
|
||||
END_TEMPLATE
|
||||
template = Template.parse(template_source)
|
||||
rendered = template.render!
|
||||
assert_equal("test-string", rendered.gsub(/\s/, ''))
|
||||
assert_template_result("test-string", template_source)
|
||||
end
|
||||
|
||||
def test_assigning_from_capture
|
||||
template_source = <<-END_TEMPLATE
|
||||
{% assign first = '' %}
|
||||
{% assign second = '' %}
|
||||
{% for number in (1..3) %}
|
||||
{% capture first %}{{number}}{% endcapture %}
|
||||
{% assign second = first %}
|
||||
{% endfor %}
|
||||
{{ first }}-{{ second }}
|
||||
template_source = <<~END_TEMPLATE
|
||||
{% assign first = '' -%}
|
||||
{% assign second = '' -%}
|
||||
{% for number in (1..3) -%}
|
||||
{% capture first %}{{number}}{% endcapture -%}
|
||||
{% assign second = first -%}
|
||||
{% endfor -%}
|
||||
{{ first }}-{{ second -}}
|
||||
END_TEMPLATE
|
||||
template = Template.parse(template_source)
|
||||
rendered = template.render!
|
||||
assert_equal("3-3", rendered.gsub(/\s/, ''))
|
||||
assert_template_result("3-3", template_source)
|
||||
end
|
||||
|
||||
def test_increment_assign_score_by_bytes_not_characters
|
||||
|
||||
+122
-107
@@ -24,7 +24,7 @@ class ContextSensitiveDrop < Liquid::Drop
|
||||
end
|
||||
end
|
||||
|
||||
class Category < Liquid::Drop
|
||||
class Category
|
||||
attr_accessor :name
|
||||
|
||||
def initialize(name)
|
||||
@@ -36,8 +36,9 @@ class Category < Liquid::Drop
|
||||
end
|
||||
end
|
||||
|
||||
class CategoryDrop
|
||||
class CategoryDrop < Liquid::Drop
|
||||
attr_accessor :category, :context
|
||||
|
||||
def initialize(category)
|
||||
@category = category
|
||||
end
|
||||
@@ -101,7 +102,7 @@ class ContextTest < Minitest::Test
|
||||
end
|
||||
|
||||
def test_variables_not_existing
|
||||
assert_nil(@context['does_not_exist'])
|
||||
assert_template_result("true", "{% if does_not_exist == nil %}true{% endif %}")
|
||||
end
|
||||
|
||||
def test_scoping
|
||||
@@ -120,22 +121,27 @@ class ContextTest < Minitest::Test
|
||||
end
|
||||
|
||||
def test_length_query
|
||||
@context['numbers'] = [1, 2, 3, 4]
|
||||
assert_template_result(
|
||||
"true",
|
||||
"{% if numbers.size == 4 %}true{% endif %}",
|
||||
{ "numbers" => [1, 2, 3, 4] },
|
||||
)
|
||||
|
||||
assert_equal(4, @context['numbers.size'])
|
||||
assert_template_result(
|
||||
"true",
|
||||
"{% if numbers.size == 4 %}true{% endif %}",
|
||||
{ "numbers" => { 1 => 1, 2 => 2, 3 => 3, 4 => 4 } },
|
||||
)
|
||||
|
||||
@context['numbers'] = { 1 => 1, 2 => 2, 3 => 3, 4 => 4 }
|
||||
|
||||
assert_equal(4, @context['numbers.size'])
|
||||
|
||||
@context['numbers'] = { 1 => 1, 2 => 2, 3 => 3, 4 => 4, 'size' => 1000 }
|
||||
|
||||
assert_equal(1000, @context['numbers.size'])
|
||||
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
|
||||
@context['oh-my'] = 'godz'
|
||||
assert_equal('godz', @context['oh-my'])
|
||||
assert_template_result("godz", "{{ oh-my }}", { "oh-my" => 'godz' })
|
||||
end
|
||||
|
||||
def test_add_filter
|
||||
@@ -187,24 +193,24 @@ class ContextTest < Minitest::Test
|
||||
end
|
||||
|
||||
def test_hierachical_data
|
||||
@context['hash'] = { "name" => 'tobi' }
|
||||
assert_equal('tobi', @context['hash.name'])
|
||||
assert_equal('tobi', @context['hash["name"]'])
|
||||
assigns = { 'hash' => { "name" => 'tobi' } }
|
||||
assert_template_result("tobi", "{{ hash.name }}", assigns)
|
||||
assert_template_result("tobi", '{{ hash["name"] }}', assigns)
|
||||
end
|
||||
|
||||
def test_keywords
|
||||
assert_equal(true, @context['true'])
|
||||
assert_equal(false, @context['false'])
|
||||
assert_template_result("pass", "{% if true == expect %}pass{% endif %}", { "expect" => true })
|
||||
assert_template_result("pass", "{% if false == expect %}pass{% endif %}", { "expect" => false })
|
||||
end
|
||||
|
||||
def test_digits
|
||||
assert_equal(100, @context['100'])
|
||||
assert_equal(100.00, @context['100.00'])
|
||||
assert_template_result("pass", "{% if 100 == expect %}pass{% endif %}", { "expect" => 100 })
|
||||
assert_template_result("pass", "{% if 100.00 == expect %}pass{% endif %}", { "expect" => 100.00 })
|
||||
end
|
||||
|
||||
def test_strings
|
||||
assert_equal("hello!", @context['"hello!"'])
|
||||
assert_equal("hello!", @context["'hello!'"])
|
||||
assert_template_result("hello!", '{{ "hello!" }}')
|
||||
assert_template_result("hello!", "{{ 'hello!' }}")
|
||||
end
|
||||
|
||||
def test_merge
|
||||
@@ -216,99 +222,92 @@ class ContextTest < Minitest::Test
|
||||
end
|
||||
|
||||
def test_array_notation
|
||||
@context['test'] = [1, 2, 3, 4, 5]
|
||||
|
||||
assert_equal(1, @context['test[0]'])
|
||||
assert_equal(2, @context['test[1]'])
|
||||
assert_equal(3, @context['test[2]'])
|
||||
assert_equal(4, @context['test[3]'])
|
||||
assert_equal(5, @context['test[4]'])
|
||||
assigns = { "test" => ["a", "b"] }
|
||||
assert_template_result("a", "{{ test[0] }}", assigns)
|
||||
assert_template_result("b", "{{ test[1] }}", assigns)
|
||||
assert_template_result("pass", "{% if test[2] == nil %}pass{% endif %}", assigns)
|
||||
end
|
||||
|
||||
def test_recoursive_array_notation
|
||||
@context['test'] = { 'test' => [1, 2, 3, 4, 5] }
|
||||
assigns = { "test" => { 'test' => [1, 2, 3, 4, 5] } }
|
||||
assert_template_result("1", "{{ test.test[0] }}", assigns)
|
||||
|
||||
assert_equal(1, @context['test.test[0]'])
|
||||
|
||||
@context['test'] = [{ 'test' => 'worked' }]
|
||||
|
||||
assert_equal('worked', @context['test[0].test'])
|
||||
assigns = { "test" => [{ 'test' => 'worked' }] }
|
||||
assert_template_result("worked", "{{ test[0].test }}", assigns)
|
||||
end
|
||||
|
||||
def test_hash_to_array_transition
|
||||
@context['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_equal('003366', @context['colors.Blue[0]'])
|
||||
assert_equal('FF9999', @context['colors.Red[3]'])
|
||||
assert_template_result("003366", "{{ colors.Blue[0] }}", assigns)
|
||||
assert_template_result("FF9999", "{{ colors.Red[3] }}", assigns)
|
||||
end
|
||||
|
||||
def test_try_first
|
||||
@context['test'] = [1, 2, 3, 4, 5]
|
||||
assigns = { 'test' => [1, 2, 3, 4, 5] }
|
||||
assert_template_result("1", "{{ test.first }}", assigns)
|
||||
assert_template_result("pass", "{% if test.last == 5 %}pass{% endif %}", assigns)
|
||||
|
||||
assert_equal(1, @context['test.first'])
|
||||
assert_equal(5, @context['test.last'])
|
||||
assigns = { "test" => { "test" => [1, 2, 3, 4, 5] } }
|
||||
assert_template_result("1", "{{ test.test.first }}", assigns)
|
||||
assert_template_result("5", "{{ test.test.last }}", assigns)
|
||||
|
||||
@context['test'] = { 'test' => [1, 2, 3, 4, 5] }
|
||||
|
||||
assert_equal(1, @context['test.test.first'])
|
||||
assert_equal(5, @context['test.test.last'])
|
||||
|
||||
@context['test'] = [1]
|
||||
assert_equal(1, @context['test.first'])
|
||||
assert_equal(1, @context['test.last'])
|
||||
assigns = { "test" => [1] }
|
||||
assert_template_result("1", "{{ test.first }}", assigns)
|
||||
assert_template_result("1", "{{ test.last }}", assigns)
|
||||
end
|
||||
|
||||
def test_access_hashes_with_hash_notation
|
||||
@context['products'] = { 'count' => 5, 'tags' => ['deepsnow', 'freestyle'] }
|
||||
@context['product'] = { 'variants' => [{ 'title' => 'draft151cm' }, { 'title' => 'element151cm' }] }
|
||||
assigns = { 'products' => { 'count' => 5, 'tags' => ['deepsnow', 'freestyle'] } }
|
||||
assert_template_result("5", '{{ products["count"] }}', assigns)
|
||||
assert_template_result("deepsnow", '{{ products["tags"][0] }}', assigns)
|
||||
assert_template_result("deepsnow", '{{ products["tags"].first }}', assigns)
|
||||
|
||||
assert_equal(5, @context['products["count"]'])
|
||||
assert_equal('deepsnow', @context['products["tags"][0]'])
|
||||
assert_equal('deepsnow', @context['products["tags"].first'])
|
||||
assert_equal('draft151cm', @context['product["variants"][0]["title"]'])
|
||||
assert_equal('element151cm', @context['product["variants"][1]["title"]'])
|
||||
assert_equal('draft151cm', @context['product["variants"][0]["title"]'])
|
||||
assert_equal('element151cm', @context['product["variants"].last["title"]'])
|
||||
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"].first["title"] }}', assigns)
|
||||
assert_template_result("element151cm", '{{ product["variants"].last["title"] }}', assigns)
|
||||
end
|
||||
|
||||
def test_access_variable_with_hash_notation
|
||||
@context['foo'] = 'baz'
|
||||
@context['bar'] = 'foo'
|
||||
|
||||
assert_equal('baz', @context['["foo"]'])
|
||||
assert_equal('baz', @context['[bar]'])
|
||||
assert_template_result('baz', '{{ ["foo"] }}', { "foo" => "baz" })
|
||||
assert_template_result('baz', '{{ [bar] }}', { 'foo' => 'baz', 'bar' => 'foo' })
|
||||
end
|
||||
|
||||
def test_access_hashes_with_hash_access_variables
|
||||
@context['var'] = 'tags'
|
||||
@context['nested'] = { 'var' => 'tags' }
|
||||
@context['products'] = { 'count' => 5, 'tags' => ['deepsnow', 'freestyle'] }
|
||||
assigns = {
|
||||
'var' => 'tags',
|
||||
'nested' => { 'var' => 'tags' },
|
||||
'products' => { 'count' => 5, 'tags' => ['deepsnow', 'freestyle'] },
|
||||
}
|
||||
|
||||
assert_equal('deepsnow', @context['products[var].first'])
|
||||
assert_equal('freestyle', @context['products[nested.var].last'])
|
||||
assert_template_result('deepsnow', '{{ products[var].first }}', assigns)
|
||||
assert_template_result('freestyle', '{{ products[nested.var].last }}', assigns)
|
||||
end
|
||||
|
||||
def test_hash_notation_only_for_hash_access
|
||||
@context['array'] = [1, 2, 3, 4, 5]
|
||||
@context['hash'] = { 'first' => 'Hello' }
|
||||
assigns = { "array" => [1, 2, 3, 4, 5] }
|
||||
assert_template_result("1", "{{ array.first }}", assigns)
|
||||
assert_template_result("pass", '{% if array["first"] == nil %}pass{% endif %}', assigns)
|
||||
|
||||
assert_equal(1, @context['array.first'])
|
||||
assert_nil(@context['array["first"]'])
|
||||
assert_equal('Hello', @context['hash["first"]'])
|
||||
assert_template_result("Hello", '{{ hash["first"] }}', { "hash" => { "first" => "Hello" } })
|
||||
end
|
||||
|
||||
def test_first_can_appear_in_middle_of_callchain
|
||||
@context['product'] = { 'variants' => [{ 'title' => 'draft151cm' }, { 'title' => 'element151cm' }] }
|
||||
assigns = { "product" => { 'variants' => [{ 'title' => 'draft151cm' }, { 'title' => 'element151cm' }] } }
|
||||
|
||||
assert_equal('draft151cm', @context['product.variants[0].title'])
|
||||
assert_equal('element151cm', @context['product.variants[1].title'])
|
||||
assert_equal('draft151cm', @context['product.variants.first.title'])
|
||||
assert_equal('element151cm', @context['product.variants.last.title'])
|
||||
assert_template_result('draft151cm', '{{ product.variants[0].title }}', assigns)
|
||||
assert_template_result('element151cm', '{{ product.variants[1].title }}', assigns)
|
||||
assert_template_result('draft151cm', '{{ product.variants.first.title }}', assigns)
|
||||
assert_template_result('element151cm', '{{ product.variants.last.title }}', assigns)
|
||||
end
|
||||
|
||||
def test_cents
|
||||
@@ -350,10 +349,12 @@ class ContextTest < Minitest::Test
|
||||
end
|
||||
|
||||
def test_ranges
|
||||
@context.merge("test" => '5')
|
||||
assert_equal((1..5), @context['(1..5)'])
|
||||
assert_equal((1..5), @context['(1..test)'])
|
||||
assert_equal((5..5), @context['(test..test)'])
|
||||
assert_template_result("1..5", '{{ (1..5) }}')
|
||||
assert_template_result("pass", '{% if (1..5) == expect %}pass{% endif %}', { "expect" => (1..5) })
|
||||
|
||||
assigns = { "test" => '5' }
|
||||
assert_template_result("1..5", "{{ (1..test) }}", assigns)
|
||||
assert_template_result("5..5", "{{ (test..test) }}", assigns)
|
||||
end
|
||||
|
||||
def test_cents_through_drop_nestedly
|
||||
@@ -405,45 +406,45 @@ class ContextTest < Minitest::Test
|
||||
end
|
||||
|
||||
def test_lambda_is_called_once
|
||||
@global = 0
|
||||
|
||||
@context['callcount'] = proc {
|
||||
@global ||= 0
|
||||
@global += 1
|
||||
@global += 1
|
||||
@global.to_s
|
||||
}
|
||||
|
||||
assert_equal('1', @context['callcount'])
|
||||
assert_equal('1', @context['callcount'])
|
||||
assert_equal('1', @context['callcount'])
|
||||
|
||||
@global = nil
|
||||
end
|
||||
|
||||
def test_nested_lambda_is_called_once
|
||||
@context['callcount'] = { "lambda" => proc {
|
||||
@global ||= 0
|
||||
@global += 1
|
||||
@global.to_s
|
||||
} }
|
||||
@global = 0
|
||||
|
||||
@context['callcount'] = {
|
||||
"lambda" => proc {
|
||||
@global += 1
|
||||
@global.to_s
|
||||
},
|
||||
}
|
||||
|
||||
assert_equal('1', @context['callcount.lambda'])
|
||||
assert_equal('1', @context['callcount.lambda'])
|
||||
assert_equal('1', @context['callcount.lambda'])
|
||||
|
||||
@global = nil
|
||||
end
|
||||
|
||||
def test_lambda_in_array_is_called_once
|
||||
@context['callcount'] = [1, 2, proc {
|
||||
@global ||= 0
|
||||
@global += 1
|
||||
@global.to_s
|
||||
}, 4, 5]
|
||||
@global = 0
|
||||
|
||||
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]'])
|
||||
assert_equal('1', @context['callcount[2]'])
|
||||
|
||||
@global = nil
|
||||
end
|
||||
|
||||
def test_access_to_context_from_proc
|
||||
@@ -486,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'])
|
||||
@@ -531,7 +532,7 @@ class ContextTest < Minitest::Test
|
||||
registers = {
|
||||
my_register: :my_value,
|
||||
}
|
||||
super_context = Context.new({}, {}, StaticRegisters.new(registers))
|
||||
super_context = Context.new({}, {}, Registers.new(registers))
|
||||
super_context.registers[:my_register] = :my_alt_value
|
||||
subcontext = super_context.new_isolated_subcontext
|
||||
assert_equal(:my_value, subcontext.registers[:my_register])
|
||||
@@ -620,6 +621,20 @@ class ContextTest < Minitest::Test
|
||||
end
|
||||
end
|
||||
|
||||
def test_context_always_uses_static_registers
|
||||
registers = {
|
||||
my_register: :my_value,
|
||||
}
|
||||
c = Context.new({}, {}, registers)
|
||||
assert_instance_of(Registers, c.registers)
|
||||
assert_equal(:my_value, c.registers[:my_register])
|
||||
|
||||
r = Registers.new(registers)
|
||||
c = Context.new({}, {}, r)
|
||||
assert_instance_of(Registers, c.registers)
|
||||
assert_equal(:my_value, c.registers[:my_register])
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def assert_no_object_allocations
|
||||
|
||||
@@ -6,16 +6,12 @@ class DocumentTest < Minitest::Test
|
||||
include Liquid
|
||||
|
||||
def test_unexpected_outer_tag
|
||||
exc = assert_raises(SyntaxError) do
|
||||
Template.parse("{% else %}")
|
||||
end
|
||||
assert_equal(exc.message, "Liquid syntax error: Unexpected outer 'else' tag")
|
||||
source = "{% else %}"
|
||||
assert_match_syntax_error("Liquid syntax error (line 1): Unexpected outer 'else' tag", source)
|
||||
end
|
||||
|
||||
def test_unknown_tag
|
||||
exc = assert_raises(SyntaxError) do
|
||||
Template.parse("{% foo %}")
|
||||
end
|
||||
assert_equal(exc.message, "Liquid syntax error: Unknown tag 'foo'")
|
||||
source = "{% foo %}"
|
||||
assert_match_syntax_error("Liquid syntax error (line 1): Unknown tag 'foo'", source)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -63,9 +63,7 @@ class ErrorHandlingTest < Minitest::Test
|
||||
end
|
||||
|
||||
def test_missing_endtag_parse_time_error
|
||||
assert_raises(Liquid::SyntaxError) do
|
||||
Liquid::Template.parse(' {% for a in b %} ... ')
|
||||
end
|
||||
assert_match_syntax_error(/: 'for' tag was never closed\z/, ' {% for a in b %} ... ')
|
||||
end
|
||||
|
||||
def test_unrecognized_operator
|
||||
@@ -84,37 +82,31 @@ class ErrorHandlingTest < Minitest::Test
|
||||
end
|
||||
|
||||
def test_with_line_numbers_adds_numbers_to_parser_errors
|
||||
err = assert_raises(SyntaxError) do
|
||||
Liquid::Template.parse('
|
||||
foobar
|
||||
source = <<~LIQUID
|
||||
foobar
|
||||
|
||||
{% "cat" | foobar %}
|
||||
{% "cat" | foobar %}
|
||||
|
||||
bla
|
||||
',
|
||||
line_numbers: true)
|
||||
end
|
||||
|
||||
assert_match(/Liquid syntax error \(line 4\)/, err.message)
|
||||
bla
|
||||
LIQUID
|
||||
assert_match_syntax_error(/Liquid syntax error \(line 3\)/, source)
|
||||
end
|
||||
|
||||
def test_with_line_numbers_adds_numbers_to_parser_errors_with_whitespace_trim
|
||||
err = assert_raises(SyntaxError) do
|
||||
Liquid::Template.parse('
|
||||
foobar
|
||||
source = <<~LIQUID
|
||||
foobar
|
||||
|
||||
{%- "cat" | foobar -%}
|
||||
{%- "cat" | foobar -%}
|
||||
|
||||
bla
|
||||
',
|
||||
line_numbers: true)
|
||||
end
|
||||
bla
|
||||
LIQUID
|
||||
|
||||
assert_match(/Liquid syntax error \(line 4\)/, err.message)
|
||||
assert_match_syntax_error(/Liquid syntax error \(line 3\)/, source)
|
||||
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 %}
|
||||
@@ -122,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 %}
|
||||
@@ -138,27 +134,25 @@ 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)
|
||||
end
|
||||
|
||||
def test_syntax_errors_in_nested_blocks_have_correct_line_number
|
||||
err = assert_raises(SyntaxError) do
|
||||
Liquid::Template.parse('
|
||||
foobar
|
||||
source = <<~LIQUID
|
||||
foobar
|
||||
|
||||
{% if 1 != 2 %}
|
||||
{% foo %}
|
||||
{% endif %}
|
||||
{% if 1 != 2 %}
|
||||
{% foo %}
|
||||
{% endif %}
|
||||
|
||||
bla
|
||||
',
|
||||
line_numbers: true)
|
||||
end
|
||||
bla
|
||||
LIQUID
|
||||
|
||||
assert_equal("Liquid syntax error (line 5): Unknown tag 'foo'", err.message)
|
||||
assert_match_syntax_error("Liquid syntax error (line 4): Unknown tag 'foo'", source)
|
||||
end
|
||||
|
||||
def test_strict_error_messages
|
||||
@@ -269,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
|
||||
|
||||
@@ -4,43 +4,46 @@ require 'test_helper'
|
||||
|
||||
class ExpressionTest < Minitest::Test
|
||||
def test_keyword_literals
|
||||
assert_equal(true, parse_and_eval("true"))
|
||||
assert_equal(true, parse_and_eval(" true "))
|
||||
assert_template_result("true", "{{ true }}")
|
||||
assert_expression_result(true, "true")
|
||||
end
|
||||
|
||||
def test_string
|
||||
assert_equal("single quoted", parse_and_eval("'single quoted'"))
|
||||
assert_equal("double quoted", parse_and_eval('"double quoted"'))
|
||||
assert_equal("spaced", parse_and_eval(" 'spaced' "))
|
||||
assert_equal("spaced2", parse_and_eval(' "spaced2" '))
|
||||
assert_template_result("single quoted", "{{'single quoted'}}")
|
||||
assert_template_result("double quoted", '{{"double quoted"}}')
|
||||
assert_template_result("spaced", "{{ 'spaced' }}")
|
||||
assert_template_result("spaced2", "{{ 'spaced2' }}")
|
||||
end
|
||||
|
||||
def test_int
|
||||
assert_equal(123, parse_and_eval("123"))
|
||||
assert_equal(456, parse_and_eval(" 456 "))
|
||||
assert_equal(12, parse_and_eval("012"))
|
||||
assert_template_result("456", "{{ 456 }}")
|
||||
assert_expression_result(123, "123")
|
||||
assert_expression_result(12, "012")
|
||||
end
|
||||
|
||||
def test_float
|
||||
assert_equal(1.5, parse_and_eval("1.5"))
|
||||
assert_equal(2.5, parse_and_eval(" 2.5 "))
|
||||
assert_template_result("2.5", "{{ 2.5 }}")
|
||||
assert_expression_result(1.5, "1.5")
|
||||
end
|
||||
|
||||
def test_range
|
||||
assert_equal(1..2, parse_and_eval("(1..2)"))
|
||||
assert_equal(3..4, parse_and_eval(" ( 3 .. 4 ) "))
|
||||
assert_template_result("3..4", "{{ ( 3 .. 4 ) }}")
|
||||
assert_expression_result(1..2, "(1..2)")
|
||||
|
||||
assert_match_syntax_error(
|
||||
"Liquid syntax error (line 1): Invalid expression type 'false' in range expression",
|
||||
"{{ (false..true) }}",
|
||||
)
|
||||
assert_match_syntax_error(
|
||||
"Liquid syntax error (line 1): Invalid expression type '(1..2)' in range expression",
|
||||
"{{ ((1..2)..3) }}",
|
||||
)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def parse_and_eval(markup, **assigns)
|
||||
if Liquid::Template.error_mode == :strict
|
||||
p = Liquid::Parser.new(markup)
|
||||
markup = p.expression
|
||||
p.consume(:end_of_string)
|
||||
end
|
||||
expression = Liquid::Expression.parse(markup)
|
||||
context = Liquid::Context.new(assigns)
|
||||
context.evaluate(expression)
|
||||
def assert_expression_result(expect, markup, **assigns)
|
||||
liquid = "{% if expect == #{markup} %}pass{% else %}got {{ #{markup} }}{% endif %}"
|
||||
assert_template_result("pass", liquid, { "expect" => expect, **assigns })
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'test_helper'
|
||||
|
||||
class FilterKwargTest < Minitest::Test
|
||||
module KwargFilter
|
||||
def html_tag(_tag, attributes)
|
||||
attributes
|
||||
.map { |key, value| "#{key}='#{value}'" }
|
||||
.join(' ')
|
||||
end
|
||||
end
|
||||
|
||||
include Liquid
|
||||
|
||||
def test_can_parse_data_kwargs
|
||||
with_global_filter(KwargFilter) do
|
||||
assert_equal(
|
||||
"data-src='src' data-widths='100, 200'",
|
||||
Template.parse("{{ 'img' | html_tag: data-src: 'src', data-widths: '100, 200' }}").render(nil, nil),
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -59,84 +59,87 @@ class FiltersTest < Minitest::Test
|
||||
end
|
||||
|
||||
def test_size
|
||||
@context['var'] = 'abcd'
|
||||
@context.add_filters(MoneyFilter)
|
||||
|
||||
assert_equal('4', Template.parse("{{var | size}}").render(@context))
|
||||
assert_template_result("4", "{{var | size}}", { "var" => 'abcd' })
|
||||
end
|
||||
|
||||
def test_join
|
||||
@context['var'] = [1, 2, 3, 4]
|
||||
|
||||
assert_equal("1 2 3 4", Template.parse("{{var | join}}").render(@context))
|
||||
assert_template_result("1 2 3 4", "{{var | join}}", { "var" => [1, 2, 3, 4] })
|
||||
end
|
||||
|
||||
def test_sort
|
||||
@context['value'] = 3
|
||||
@context['numbers'] = [2, 1, 4, 3]
|
||||
@context['words'] = ['expected', 'as', 'alphabetic']
|
||||
@context['arrays'] = ['flower', 'are']
|
||||
@context['case_sensitive'] = ['sensitive', 'Expected', 'case']
|
||||
|
||||
assert_equal('1 2 3 4', Template.parse("{{numbers | sort | join}}").render(@context))
|
||||
assert_equal('alphabetic as expected', Template.parse("{{words | sort | join}}").render(@context))
|
||||
assert_equal('3', Template.parse("{{value | sort}}").render(@context))
|
||||
assert_equal('are flower', Template.parse("{{arrays | sort | join}}").render(@context))
|
||||
assert_equal('Expected case sensitive', Template.parse("{{case_sensitive | sort | join}}").render(@context))
|
||||
assert_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("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"] },
|
||||
)
|
||||
end
|
||||
|
||||
def test_sort_natural
|
||||
@context['words'] = ['case', 'Assert', 'Insensitive']
|
||||
@context['hashes'] = [{ 'a' => 'A' }, { 'a' => 'b' }, { 'a' => 'C' }]
|
||||
@context['objects'] = [TestObject.new('A'), TestObject.new('b'), TestObject.new('C')]
|
||||
|
||||
# Test strings
|
||||
assert_equal('Assert case Insensitive', Template.parse("{{words | sort_natural | join}}").render(@context))
|
||||
assert_template_result(
|
||||
"Assert case Insensitive",
|
||||
"{{words | sort_natural | join}}",
|
||||
{ "words" => ["case", "Assert", "Insensitive"] },
|
||||
)
|
||||
|
||||
# Test hashes
|
||||
assert_equal('A b C', Template.parse("{{hashes | sort_natural: 'a' | map: 'a' | join}}").render(@context))
|
||||
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')]
|
||||
assert_equal('A b C', Template.parse("{{objects | sort_natural: 'a' | map: 'a' | join}}").render(@context))
|
||||
end
|
||||
|
||||
def test_compact
|
||||
@context['words'] = ['a', nil, 'b', nil, 'c']
|
||||
@context['hashes'] = [{ 'a' => 'A' }, { 'a' => nil }, { 'a' => 'C' }]
|
||||
@context['objects'] = [TestObject.new('A'), TestObject.new(nil), TestObject.new('C')]
|
||||
|
||||
# Test strings
|
||||
assert_equal('a b c', Template.parse("{{words | compact | join}}").render(@context))
|
||||
assert_template_result(
|
||||
"a b c",
|
||||
"{{words | compact | join}}",
|
||||
{ "words" => ['a', nil, 'b', nil, 'c'] },
|
||||
)
|
||||
|
||||
# Test hashes
|
||||
assert_equal('A C', Template.parse("{{hashes | compact: 'a' | map: 'a' | join}}").render(@context))
|
||||
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')]
|
||||
assert_equal('A C', Template.parse("{{objects | compact: 'a' | map: 'a' | join}}").render(@context))
|
||||
end
|
||||
|
||||
def test_strip_html
|
||||
@context['var'] = "<b>bla blub</a>"
|
||||
|
||||
assert_equal("bla blub", Template.parse("{{ var | strip_html }}").render(@context))
|
||||
assert_template_result("bla blub", "{{ var | strip_html }}", { "var" => "<b>bla blub</a>" })
|
||||
end
|
||||
|
||||
def test_strip_html_ignore_comments_with_html
|
||||
@context['var'] = "<!-- split and some <ul> tag --><b>bla blub</a>"
|
||||
|
||||
assert_equal("bla blub", Template.parse("{{ var | strip_html }}").render(@context))
|
||||
assert_template_result(
|
||||
"bla blub",
|
||||
"{{ var | strip_html }}",
|
||||
{ "var" => "<!-- split and some <ul> tag --><b>bla blub</a>" },
|
||||
)
|
||||
end
|
||||
|
||||
def test_capitalize
|
||||
@context['var'] = "blub"
|
||||
|
||||
assert_equal("Blub", Template.parse("{{ var | capitalize }}").render(@context))
|
||||
assert_template_result("Blub", "{{ var | capitalize }}", { "var" => "blub" })
|
||||
end
|
||||
|
||||
def test_nonexistent_filter_is_ignored
|
||||
@context['var'] = 1000
|
||||
|
||||
assert_equal('1000', Template.parse("{{ var | xyzzy }}").render(@context))
|
||||
assert_template_result("1000", "{{ var | xyzzy }}", { "var" => 1000 })
|
||||
end
|
||||
|
||||
def test_filter_with_keyword_arguments
|
||||
|
||||
@@ -33,31 +33,25 @@ class OutputTest < Minitest::Test
|
||||
|
||||
def setup
|
||||
@assigns = {
|
||||
'best_cars' => 'bmw',
|
||||
'car' => { 'bmw' => 'good', 'gm' => 'bad' },
|
||||
}
|
||||
end
|
||||
|
||||
def test_variable
|
||||
text = %( {{best_cars}} )
|
||||
|
||||
expected = %( bmw )
|
||||
assert_equal(expected, Template.parse(text).render!(@assigns))
|
||||
assert_template_result(" bmw ", " {{best_cars}} ", { "best_cars" => "bmw" })
|
||||
end
|
||||
|
||||
def test_variable_traversing_with_two_brackets
|
||||
text = %({{ site.data.menu[include.menu][include.locale] }})
|
||||
assert_equal("it works!", Template.parse(text).render!(
|
||||
source = "{{ site.data.menu[include.menu][include.locale] }}"
|
||||
assert_template_result("it works!", source, {
|
||||
"site" => { "data" => { "menu" => { "foo" => { "bar" => "it works!" } } } },
|
||||
"include" => { "menu" => "foo", "locale" => "bar" }
|
||||
))
|
||||
"include" => { "menu" => "foo", "locale" => "bar" },
|
||||
})
|
||||
end
|
||||
|
||||
def test_variable_traversing
|
||||
text = %( {{car.bmw}} {{car.gm}} {{car.bmw}} )
|
||||
|
||||
expected = %( good bad good )
|
||||
assert_equal(expected, Template.parse(text).render!(@assigns))
|
||||
source = " {{car.bmw}} {{car.gm}} {{car.bmw}} "
|
||||
assert_template_result(" good bad good ", source, @assigns)
|
||||
end
|
||||
|
||||
def test_variable_piping
|
||||
@@ -110,10 +104,11 @@ class OutputTest < Minitest::Test
|
||||
end
|
||||
|
||||
def test_multiple_pipings
|
||||
assigns = { 'best_cars' => 'bmw' }
|
||||
text = %( {{ best_cars | cite_funny | paragraph }} )
|
||||
expected = %( <p>LOL: bmw</p> )
|
||||
|
||||
assert_equal(expected, Template.parse(text).render!(@assigns, filters: [FunnyFilter]))
|
||||
assert_equal(expected, Template.parse(text).render!(assigns, filters: [FunnyFilter]))
|
||||
end
|
||||
|
||||
def test_link_to
|
||||
|
||||
@@ -129,6 +129,6 @@ class ParsingQuirksTest < Minitest::Test
|
||||
end
|
||||
|
||||
def test_contains_in_id
|
||||
assert_template_result(' YES ', '{% if containsallshipments == true %} YES {% endif %}', 'containsallshipments' => true)
|
||||
assert_template_result(' YES ', '{% if containsallshipments == true %} YES {% endif %}', { 'containsallshipments' => true })
|
||||
end
|
||||
end # ParsingQuirksTest
|
||||
|
||||
@@ -3,6 +3,27 @@
|
||||
require 'test_helper'
|
||||
|
||||
class ProfilerTest < Minitest::Test
|
||||
class TestDrop < Liquid::Drop
|
||||
def initialize(value)
|
||||
super()
|
||||
@value = value
|
||||
end
|
||||
|
||||
def to_s
|
||||
artificial_execution_time
|
||||
|
||||
@value
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Monotonic clock precision fluctuate based on the operating system
|
||||
# By introducing a small sleep we ensure ourselves to register a non zero unit of time
|
||||
def artificial_execution_time
|
||||
sleep(Process.clock_getres(Process::CLOCK_MONOTONIC))
|
||||
end
|
||||
end
|
||||
|
||||
include Liquid
|
||||
|
||||
class ProfilingFileSystem
|
||||
@@ -198,16 +219,22 @@ class ProfilerTest < Minitest::Test
|
||||
|
||||
def test_profiling_supports_self_time
|
||||
t = Template.parse("{% for item in collection %} {{ item }} {% endfor %}", profile: true)
|
||||
t.render!("collection" => ["one", "two"])
|
||||
leaf = t.profiler[0].children[0]
|
||||
collection = [
|
||||
TestDrop.new("one"),
|
||||
TestDrop.new("two"),
|
||||
]
|
||||
output = t.render!("collection" => collection)
|
||||
assert_equal(" one two ", output)
|
||||
|
||||
assert_operator(leaf.self_time, :>, 0)
|
||||
leaf = t.profiler[0].children[0]
|
||||
assert_operator(leaf.self_time, :>, 0.0)
|
||||
end
|
||||
|
||||
def test_profiling_supports_total_time
|
||||
t = Template.parse("{% if true %} {% increment test %} {{ test }} {% endif %}", profile: true)
|
||||
t.render!
|
||||
t = Template.parse("{% if true %} {{ test }} {% endif %}", profile: true)
|
||||
output = t.render!("test" => TestDrop.new("one"))
|
||||
assert_equal(" one ", output)
|
||||
|
||||
assert_operator(t.profiler[0].total_time, :>, 0)
|
||||
assert_operator(t.profiler[0].total_time, :>, 0.0)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -3,10 +3,6 @@
|
||||
|
||||
require 'test_helper'
|
||||
|
||||
class Filters
|
||||
include Liquid::StandardFilters
|
||||
end
|
||||
|
||||
class TestThing
|
||||
attr_reader :foo
|
||||
|
||||
@@ -29,8 +25,24 @@ class TestThing
|
||||
end
|
||||
|
||||
class TestDrop < Liquid::Drop
|
||||
def test
|
||||
"testfoo"
|
||||
def initialize(value:)
|
||||
@value = value
|
||||
end
|
||||
|
||||
attr_reader :value
|
||||
|
||||
def registers
|
||||
{ @value => @context.registers[@value] }
|
||||
end
|
||||
end
|
||||
|
||||
class TestModel
|
||||
def initialize(value:)
|
||||
@value = value
|
||||
end
|
||||
|
||||
def to_liquid
|
||||
TestDrop.new(value: @value)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -53,10 +65,13 @@ class NumberLikeThing < Liquid::Drop
|
||||
end
|
||||
|
||||
class StandardFiltersTest < Minitest::Test
|
||||
Filters = Class.new(Liquid::StrainerTemplate)
|
||||
Filters.add_filter(Liquid::StandardFilters)
|
||||
|
||||
include Liquid
|
||||
|
||||
def setup
|
||||
@filters = Filters.new
|
||||
@filters = Filters.new(Context.new)
|
||||
end
|
||||
|
||||
def test_size
|
||||
@@ -94,6 +109,10 @@ class StandardFiltersTest < Minitest::Test
|
||||
assert_raises(Liquid::ArgumentError) do
|
||||
@filters.slice('foobar', 0, "")
|
||||
end
|
||||
assert_equal("", @filters.slice("foobar", 0, -(1 << 64)))
|
||||
assert_equal("foobar", @filters.slice("foobar", 0, 1 << 63))
|
||||
assert_equal("", @filters.slice("foobar", 1 << 63, 6))
|
||||
assert_equal("", @filters.slice("foobar", -(1 << 63), 6))
|
||||
end
|
||||
|
||||
def test_slice_on_arrays
|
||||
@@ -108,6 +127,10 @@ class StandardFiltersTest < Minitest::Test
|
||||
assert_equal(%w(r), @filters.slice(input, -1))
|
||||
assert_equal(%w(), @filters.slice(input, 100, 10))
|
||||
assert_equal(%w(), @filters.slice(input, -100, 10))
|
||||
assert_equal([], @filters.slice(input, 0, -(1 << 64)))
|
||||
assert_equal(input, @filters.slice(input, 0, 1 << 63))
|
||||
assert_equal([], @filters.slice(input, 1 << 63, 6))
|
||||
assert_equal([], @filters.slice(input, -(1 << 63), 6))
|
||||
end
|
||||
|
||||
def test_truncate
|
||||
@@ -117,6 +140,8 @@ class StandardFiltersTest < Minitest::Test
|
||||
assert_equal('1234567890', @filters.truncate('1234567890'))
|
||||
assert_equal("测试...", @filters.truncate("测试测试测试测试", 5))
|
||||
assert_equal('12341', @filters.truncate("1234567890", 5, 1))
|
||||
assert_equal("foobar", @filters.truncate("foobar", 1 << 63))
|
||||
assert_equal("...", @filters.truncate("foobar", -(1 << 63)))
|
||||
end
|
||||
|
||||
def test_split
|
||||
@@ -145,6 +170,40 @@ class StandardFiltersTest < Minitest::Test
|
||||
assert_equal('<strong>Hulk</strong>', @filters.escape_once('<strong>Hulk</strong>'))
|
||||
end
|
||||
|
||||
def test_base64_encode
|
||||
assert_equal('b25lIHR3byB0aHJlZQ==', @filters.base64_encode('one two three'))
|
||||
assert_equal('', @filters.base64_encode(nil))
|
||||
end
|
||||
|
||||
def test_base64_decode
|
||||
assert_equal('one two three', @filters.base64_decode('b25lIHR3byB0aHJlZQ=='))
|
||||
|
||||
exception = assert_raises(Liquid::ArgumentError) do
|
||||
@filters.base64_decode("invalidbase64")
|
||||
end
|
||||
|
||||
assert_equal('Liquid error: invalid base64 provided to base64_decode', exception.message)
|
||||
end
|
||||
|
||||
def test_base64_url_safe_encode
|
||||
assert_equal(
|
||||
'YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXogQUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVogMTIzNDU2Nzg5MCAhQCMkJV4mKigpLT1fKy8_Ljo7W117fVx8',
|
||||
@filters.base64_url_safe_encode('abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ 1234567890 !@#$%^&*()-=_+/?.:;[]{}\|'),
|
||||
)
|
||||
assert_equal('', @filters.base64_url_safe_encode(nil))
|
||||
end
|
||||
|
||||
def test_base64_url_safe_decode
|
||||
assert_equal(
|
||||
'abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ 1234567890 !@#$%^&*()-=_+/?.:;[]{}\|',
|
||||
@filters.base64_url_safe_decode('YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXogQUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVogMTIzNDU2Nzg5MCAhQCMkJV4mKigpLT1fKy8_Ljo7W117fVx8'),
|
||||
)
|
||||
exception = assert_raises(Liquid::ArgumentError) do
|
||||
@filters.base64_url_safe_decode("invalidbase64")
|
||||
end
|
||||
assert_equal('Liquid error: invalid base64 provided to base64_url_safe_decode', exception.message)
|
||||
end
|
||||
|
||||
def test_url_encode
|
||||
assert_equal('foo%2B1%40example.com', @filters.url_encode('[email protected]'))
|
||||
assert_equal('1', @filters.url_encode(1))
|
||||
@@ -171,17 +230,15 @@ class StandardFiltersTest < Minitest::Test
|
||||
assert_equal('one two three', @filters.truncatewords('one two three'))
|
||||
assert_equal(
|
||||
'Two small (13” x 5.5” x 10” high) baskets fit inside one large basket (13”...',
|
||||
@filters.truncatewords('Two small (13” x 5.5” x 10” high) baskets fit inside one large basket (13” x 16” x 10.5” high) with cover.', 15)
|
||||
@filters.truncatewords('Two small (13” x 5.5” x 10” high) baskets fit inside one large basket (13” x 16” x 10.5” high) with cover.', 15),
|
||||
)
|
||||
assert_equal("测试测试测试测试", @filters.truncatewords('测试测试测试测试', 5))
|
||||
assert_equal('one two1', @filters.truncatewords("one two three", 2, 1))
|
||||
assert_equal('one two three...', @filters.truncatewords("one two\tthree\nfour", 3))
|
||||
assert_equal('one two...', @filters.truncatewords("one two three four", 2))
|
||||
assert_equal('one...', @filters.truncatewords("one two three four", 0))
|
||||
exception = assert_raises(Liquid::ArgumentError) do
|
||||
@filters.truncatewords("one two three four", 1 << 31)
|
||||
end
|
||||
assert_equal("Liquid error: integer #{1 << 31} too big for truncatewords", exception.message)
|
||||
assert_equal('one two three four', @filters.truncatewords("one two three four", 1 << 31))
|
||||
assert_equal('one...', @filters.truncatewords("one two three four", -(1 << 32)))
|
||||
end
|
||||
|
||||
def test_strip_html
|
||||
@@ -225,8 +282,8 @@ class StandardFiltersTest < Minitest::Test
|
||||
{ "price" => 1, "handle" => "gamma" },
|
||||
{ "price" => 2, "handle" => "epsilon" },
|
||||
{ "price" => 4, "handle" => "alpha" },
|
||||
{ "handle" => "delta" },
|
||||
{ "handle" => "beta" },
|
||||
{ "handle" => "delta" },
|
||||
]
|
||||
assert_equal(expectation, @filters.sort(input, "price"))
|
||||
end
|
||||
@@ -329,8 +386,9 @@ class StandardFiltersTest < Minitest::Test
|
||||
assert_equal(["foo"], @filters.uniq("foo"))
|
||||
assert_equal([1, 3, 2, 4], @filters.uniq([1, 1, 3, 2, 3, 1, 4, 3, 2, 1]))
|
||||
assert_equal([{ "a" => 1 }, { "a" => 3 }, { "a" => 2 }], @filters.uniq([{ "a" => 1 }, { "a" => 3 }, { "a" => 1 }, { "a" => 2 }], "a"))
|
||||
testdrop = TestDrop.new
|
||||
assert_equal([testdrop], @filters.uniq([testdrop, TestDrop.new], 'test'))
|
||||
test_drop = TestDrop.new(value: "test")
|
||||
test_drop_alternate = TestDrop.new(value: "test")
|
||||
assert_equal([test_drop], @filters.uniq([test_drop, test_drop_alternate], 'value'))
|
||||
end
|
||||
|
||||
def test_uniq_empty_array
|
||||
@@ -375,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
|
||||
@@ -386,18 +447,31 @@ class StandardFiltersTest < Minitest::Test
|
||||
|
||||
def test_map_calls_to_liquid
|
||||
t = TestThing.new
|
||||
assert_template_result("woot: 1", '{{ foo | map: "whatever" }}', "foo" => [t])
|
||||
assert_template_result("woot: 1", '{{ foo | map: "whatever" }}', { "foo" => [t] })
|
||||
end
|
||||
|
||||
def test_map_calls_context=
|
||||
model = TestModel.new(value: :test)
|
||||
|
||||
template = Template.parse('{{ foo | map: "registers" }}')
|
||||
template.registers[:test] = 1234
|
||||
template.assigns['foo'] = [model]
|
||||
|
||||
assert_template_result("{:test=>1234}", template.render!)
|
||||
end
|
||||
|
||||
def test_map_on_hashes
|
||||
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
|
||||
template = "{% assign key = 'foo' %}{{ thing | map: key | map: 'bar' }}"
|
||||
hash = { "foo" => { "bar" => 42 } }
|
||||
assert_template_result("42", template, "thing" => hash)
|
||||
assert_template_result("42", template, { "thing" => hash })
|
||||
end
|
||||
|
||||
def test_sort_calls_to_liquid
|
||||
@@ -407,10 +481,10 @@ class StandardFiltersTest < Minitest::Test
|
||||
end
|
||||
|
||||
def test_map_over_proc
|
||||
drop = TestDrop.new
|
||||
drop = TestDrop.new(value: "testfoo")
|
||||
p = proc { drop }
|
||||
templ = '{{ procs | map: "test" }}'
|
||||
assert_template_result("testfoo", templ, "procs" => [p])
|
||||
output = Liquid::Template.parse('{{ procs | map: "value" }}').render!({ "procs" => [p] })
|
||||
assert_equal("testfoo", output)
|
||||
end
|
||||
|
||||
def test_map_over_drops_returning_procs
|
||||
@@ -422,12 +496,13 @@ class StandardFiltersTest < Minitest::Test
|
||||
"proc" => -> { "bar" },
|
||||
},
|
||||
]
|
||||
templ = '{{ drops | map: "proc" }}'
|
||||
assert_template_result("foobar", templ, "drops" => drops)
|
||||
output = Liquid::Template.parse('{{ drops | map: "proc" }}').render!({ "drops" => drops })
|
||||
assert_equal("foobar", output)
|
||||
end
|
||||
|
||||
def test_map_works_on_enumerables
|
||||
assert_template_result("123", '{{ foo | map: "foo" }}', "foo" => TestEnumerable.new)
|
||||
output = Liquid::Template.parse('{{ foo | map: "foo" }}').render!({ "foo" => TestEnumerable.new })
|
||||
assert_equal("123", output)
|
||||
end
|
||||
|
||||
def test_map_returns_empty_on_2d_input_array
|
||||
@@ -454,16 +529,16 @@ class StandardFiltersTest < Minitest::Test
|
||||
end
|
||||
|
||||
def test_sort_works_on_enumerables
|
||||
assert_template_result("213", '{{ foo | sort: "bar" | map: "foo" }}', "foo" => TestEnumerable.new)
|
||||
assert_template_result("213", '{{ foo | sort: "bar" | map: "foo" }}', { "foo" => TestEnumerable.new })
|
||||
end
|
||||
|
||||
def test_first_and_last_call_to_liquid
|
||||
assert_template_result('foobar', '{{ foo | first }}', 'foo' => [ThingWithToLiquid.new])
|
||||
assert_template_result('foobar', '{{ foo | last }}', 'foo' => [ThingWithToLiquid.new])
|
||||
assert_template_result('foobar', '{{ foo | first }}', { 'foo' => [ThingWithToLiquid.new] })
|
||||
assert_template_result('foobar', '{{ foo | last }}', { 'foo' => [ThingWithToLiquid.new] })
|
||||
end
|
||||
|
||||
def test_truncate_calls_to_liquid
|
||||
assert_template_result("wo...", '{{ foo | truncate: 5 }}', "foo" => TestThing.new)
|
||||
assert_template_result("wo...", '{{ foo | truncate: 5 }}', { "foo" => TestThing.new })
|
||||
end
|
||||
|
||||
def test_date
|
||||
@@ -505,19 +580,31 @@ class StandardFiltersTest < Minitest::Test
|
||||
end
|
||||
|
||||
def test_replace
|
||||
assert_equal('2 2 2 2', @filters.replace('1 1 1 1', '1', 2))
|
||||
assert_equal('b b b b', @filters.replace('a a a a', 'a', 'b'))
|
||||
assert_equal('2 2 2 2', @filters.replace('1 1 1 1', 1, 2))
|
||||
assert_equal('2 1 1 1', @filters.replace_first('1 1 1 1', '1', 2))
|
||||
assert_equal('1 1 1 1', @filters.replace('1 1 1 1', 2, 3))
|
||||
assert_template_result('2 2 2 2', "{{ '1 1 1 1' | replace: '1', 2 }}")
|
||||
|
||||
assert_equal('b a a a', @filters.replace_first('a a a a', 'a', 'b'))
|
||||
assert_equal('2 1 1 1', @filters.replace_first('1 1 1 1', 1, 2))
|
||||
assert_equal('1 1 1 1', @filters.replace_first('1 1 1 1', 2, 3))
|
||||
assert_template_result('2 1 1 1', "{{ '1 1 1 1' | replace_first: '1', 2 }}")
|
||||
|
||||
assert_equal('a a a b', @filters.replace_last('a a a a', 'a', 'b'))
|
||||
assert_equal('1 1 1 2', @filters.replace_last('1 1 1 1', 1, 2))
|
||||
assert_equal('1 1 1 1', @filters.replace_last('1 1 1 1', 2, 3))
|
||||
assert_template_result('1 1 1 2', "{{ '1 1 1 1' | replace_last: '1', 2 }}")
|
||||
end
|
||||
|
||||
def test_remove
|
||||
assert_equal(' ', @filters.remove("a a a a", 'a'))
|
||||
assert_equal(' ', @filters.remove("1 1 1 1", 1))
|
||||
assert_equal('a a a', @filters.remove_first("a a a a", 'a '))
|
||||
assert_equal(' 1 1 1', @filters.remove_first("1 1 1 1", 1))
|
||||
assert_template_result('a a a', "{{ 'a a a a' | remove_first: 'a ' }}")
|
||||
assert_template_result(' ', "{{ '1 1 1 1' | remove: 1 }}")
|
||||
|
||||
assert_equal('b a a', @filters.remove_first("a b a a", 'a '))
|
||||
assert_template_result(' 1 1 1', "{{ '1 1 1 1' | remove_first: 1 }}")
|
||||
|
||||
assert_equal('a a b', @filters.remove_last("a a b a", ' a'))
|
||||
assert_template_result('1 1 1 ', "{{ '1 1 1 1' | remove_last: 1 }}")
|
||||
end
|
||||
|
||||
def test_pipes_in_string_arguments
|
||||
@@ -525,42 +612,42 @@ class StandardFiltersTest < Minitest::Test
|
||||
end
|
||||
|
||||
def test_strip
|
||||
assert_template_result('ab c', "{{ source | strip }}", 'source' => " ab c ")
|
||||
assert_template_result('ab c', "{{ source | strip }}", 'source' => " \tab c \n \t")
|
||||
assert_template_result('ab c', "{{ source | strip }}", { 'source' => " ab c " })
|
||||
assert_template_result('ab c', "{{ source | strip }}", { 'source' => " \tab c \n \t" })
|
||||
end
|
||||
|
||||
def test_lstrip
|
||||
assert_template_result('ab c ', "{{ source | lstrip }}", 'source' => " ab c ")
|
||||
assert_template_result("ab c \n \t", "{{ source | lstrip }}", 'source' => " \tab c \n \t")
|
||||
assert_template_result('ab c ', "{{ source | lstrip }}", { 'source' => " ab c " })
|
||||
assert_template_result("ab c \n \t", "{{ source | lstrip }}", { 'source' => " \tab c \n \t" })
|
||||
end
|
||||
|
||||
def test_rstrip
|
||||
assert_template_result(" ab c", "{{ source | rstrip }}", 'source' => " ab c ")
|
||||
assert_template_result(" \tab c", "{{ source | rstrip }}", 'source' => " \tab c \n \t")
|
||||
assert_template_result(" ab c", "{{ source | rstrip }}", { 'source' => " ab c " })
|
||||
assert_template_result(" \tab c", "{{ source | rstrip }}", { 'source' => " \tab c \n \t" })
|
||||
end
|
||||
|
||||
def test_strip_newlines
|
||||
assert_template_result('abc', "{{ source | strip_newlines }}", 'source' => "a\nb\nc")
|
||||
assert_template_result('abc', "{{ source | strip_newlines }}", 'source' => "a\r\nb\nc")
|
||||
assert_template_result('abc', "{{ source | strip_newlines }}", { 'source' => "a\nb\nc" })
|
||||
assert_template_result('abc', "{{ source | strip_newlines }}", { 'source' => "a\r\nb\nc" })
|
||||
end
|
||||
|
||||
def test_newlines_to_br
|
||||
assert_template_result("a<br />\nb<br />\nc", "{{ source | newline_to_br }}", 'source' => "a\nb\nc")
|
||||
assert_template_result("a<br />\nb<br />\nc", "{{ source | newline_to_br }}", 'source' => "a\r\nb\nc")
|
||||
assert_template_result("a<br />\nb<br />\nc", "{{ source | newline_to_br }}", { 'source' => "a\nb\nc" })
|
||||
assert_template_result("a<br />\nb<br />\nc", "{{ source | newline_to_br }}", { 'source' => "a\r\nb\nc" })
|
||||
end
|
||||
|
||||
def test_plus
|
||||
assert_template_result("2", "{{ 1 | plus:1 }}")
|
||||
assert_template_result("2.0", "{{ '1' | plus:'1.0' }}")
|
||||
|
||||
assert_template_result("5", "{{ price | plus:'2' }}", 'price' => NumberLikeThing.new(3))
|
||||
assert_template_result("5", "{{ price | plus:'2' }}", { 'price' => NumberLikeThing.new(3) })
|
||||
end
|
||||
|
||||
def test_minus
|
||||
assert_template_result("4", "{{ input | minus:operand }}", 'input' => 5, 'operand' => 1)
|
||||
assert_template_result("4", "{{ input | minus:operand }}", { 'input' => 5, 'operand' => 1 })
|
||||
assert_template_result("2.3", "{{ '4.3' | minus:'2' }}")
|
||||
|
||||
assert_template_result("5", "{{ price | minus:'2' }}", 'price' => NumberLikeThing.new(7))
|
||||
assert_template_result("5", "{{ price | minus:'2' }}", { 'price' => NumberLikeThing.new(7) })
|
||||
end
|
||||
|
||||
def test_abs
|
||||
@@ -583,7 +670,7 @@ class StandardFiltersTest < Minitest::Test
|
||||
assert_template_result("7.25", "{{ 0.0725 | times:100 }}")
|
||||
assert_template_result("-7.25", '{{ "-0.0725" | times:100 }}')
|
||||
assert_template_result("7.25", '{{ "-0.0725" | times: -100 }}')
|
||||
assert_template_result("4", "{{ price | times:2 }}", 'price' => NumberLikeThing.new(2))
|
||||
assert_template_result("4", "{{ price | times:2 }}", { 'price' => NumberLikeThing.new(2) })
|
||||
end
|
||||
|
||||
def test_divided_by
|
||||
@@ -598,7 +685,7 @@ class StandardFiltersTest < Minitest::Test
|
||||
assert_template_result("4", "{{ 1 | modulo: 0 }}")
|
||||
end
|
||||
|
||||
assert_template_result("5", "{{ price | divided_by:2 }}", 'price' => NumberLikeThing.new(10))
|
||||
assert_template_result("5", "{{ price | divided_by:2 }}", { 'price' => NumberLikeThing.new(10) })
|
||||
end
|
||||
|
||||
def test_modulo
|
||||
@@ -607,39 +694,39 @@ class StandardFiltersTest < Minitest::Test
|
||||
assert_template_result("4", "{{ 1 | modulo: 0 }}")
|
||||
end
|
||||
|
||||
assert_template_result("1", "{{ price | modulo:2 }}", 'price' => NumberLikeThing.new(3))
|
||||
assert_template_result("1", "{{ price | modulo:2 }}", { 'price' => NumberLikeThing.new(3) })
|
||||
end
|
||||
|
||||
def test_round
|
||||
assert_template_result("5", "{{ input | round }}", 'input' => 4.6)
|
||||
assert_template_result("5", "{{ input | round }}", { 'input' => 4.6 })
|
||||
assert_template_result("4", "{{ '4.3' | round }}")
|
||||
assert_template_result("4.56", "{{ input | round: 2 }}", 'input' => 4.5612)
|
||||
assert_template_result("4.56", "{{ input | round: 2 }}", { 'input' => 4.5612 })
|
||||
assert_raises(Liquid::FloatDomainError) do
|
||||
assert_template_result("4", "{{ 1.0 | divided_by: 0.0 | round }}")
|
||||
end
|
||||
|
||||
assert_template_result("5", "{{ price | round }}", 'price' => NumberLikeThing.new(4.6))
|
||||
assert_template_result("4", "{{ price | round }}", 'price' => NumberLikeThing.new(4.3))
|
||||
assert_template_result("5", "{{ price | round }}", { 'price' => NumberLikeThing.new(4.6) })
|
||||
assert_template_result("4", "{{ price | round }}", { 'price' => NumberLikeThing.new(4.3) })
|
||||
end
|
||||
|
||||
def test_ceil
|
||||
assert_template_result("5", "{{ input | ceil }}", 'input' => 4.6)
|
||||
assert_template_result("5", "{{ input | ceil }}", { 'input' => 4.6 })
|
||||
assert_template_result("5", "{{ '4.3' | ceil }}")
|
||||
assert_raises(Liquid::FloatDomainError) do
|
||||
assert_template_result("4", "{{ 1.0 | divided_by: 0.0 | ceil }}")
|
||||
end
|
||||
|
||||
assert_template_result("5", "{{ price | ceil }}", 'price' => NumberLikeThing.new(4.6))
|
||||
assert_template_result("5", "{{ price | ceil }}", { 'price' => NumberLikeThing.new(4.6) })
|
||||
end
|
||||
|
||||
def test_floor
|
||||
assert_template_result("4", "{{ input | floor }}", 'input' => 4.6)
|
||||
assert_template_result("4", "{{ input | floor }}", { 'input' => 4.6 })
|
||||
assert_template_result("4", "{{ '4.3' | floor }}")
|
||||
assert_raises(Liquid::FloatDomainError) do
|
||||
assert_template_result("4", "{{ 1.0 | divided_by: 0.0 | floor }}")
|
||||
end
|
||||
|
||||
assert_template_result("5", "{{ price | floor }}", 'price' => NumberLikeThing.new(5.4))
|
||||
assert_template_result("5", "{{ price | floor }}", { 'price' => NumberLikeThing.new(5.4) })
|
||||
end
|
||||
|
||||
def test_at_most
|
||||
@@ -648,9 +735,9 @@ class StandardFiltersTest < Minitest::Test
|
||||
assert_template_result("5", "{{ 5 | at_most:6 }}")
|
||||
|
||||
assert_template_result("4.5", "{{ 4.5 | at_most:5 }}")
|
||||
assert_template_result("5", "{{ width | at_most:5 }}", 'width' => NumberLikeThing.new(6))
|
||||
assert_template_result("4", "{{ width | at_most:5 }}", 'width' => NumberLikeThing.new(4))
|
||||
assert_template_result("4", "{{ 5 | at_most: width }}", 'width' => NumberLikeThing.new(4))
|
||||
assert_template_result("5", "{{ width | at_most:5 }}", { 'width' => NumberLikeThing.new(6) })
|
||||
assert_template_result("4", "{{ width | at_most:5 }}", { 'width' => NumberLikeThing.new(4) })
|
||||
assert_template_result("4", "{{ 5 | at_most: width }}", { 'width' => NumberLikeThing.new(4) })
|
||||
end
|
||||
|
||||
def test_at_least
|
||||
@@ -659,9 +746,9 @@ class StandardFiltersTest < Minitest::Test
|
||||
assert_template_result("6", "{{ 5 | at_least:6 }}")
|
||||
|
||||
assert_template_result("5", "{{ 4.5 | at_least:5 }}")
|
||||
assert_template_result("6", "{{ width | at_least:5 }}", 'width' => NumberLikeThing.new(6))
|
||||
assert_template_result("5", "{{ width | at_least:5 }}", 'width' => NumberLikeThing.new(4))
|
||||
assert_template_result("6", "{{ 5 | at_least: width }}", 'width' => NumberLikeThing.new(6))
|
||||
assert_template_result("6", "{{ width | at_least:5 }}", { 'width' => NumberLikeThing.new(6) })
|
||||
assert_template_result("5", "{{ width | at_least:5 }}", { 'width' => NumberLikeThing.new(4) })
|
||||
assert_template_result("6", "{{ 5 | at_least: width }}", { 'width' => NumberLikeThing.new(6) })
|
||||
end
|
||||
|
||||
def test_append
|
||||
@@ -694,6 +781,8 @@ class StandardFiltersTest < Minitest::Test
|
||||
assert_equal("bar", @filters.default([], "bar"))
|
||||
assert_equal("bar", @filters.default({}, "bar"))
|
||||
assert_template_result('bar', "{{ false | default: 'bar' }}")
|
||||
assert_template_result('bar', "{{ drop | default: 'bar' }}", { 'drop' => BooleanDrop.new(false) })
|
||||
assert_template_result('Yay', "{{ drop | default: 'bar' }}", { 'drop' => BooleanDrop.new(true) })
|
||||
end
|
||||
|
||||
def test_default_handle_false
|
||||
@@ -704,6 +793,8 @@ class StandardFiltersTest < Minitest::Test
|
||||
assert_equal("bar", @filters.default([], "bar", "allow_false" => true))
|
||||
assert_equal("bar", @filters.default({}, "bar", "allow_false" => true))
|
||||
assert_template_result('false', "{{ false | default: 'bar', allow_false: true }}")
|
||||
assert_template_result('Nay', "{{ drop | default: 'bar', allow_false: true }}", { 'drop' => BooleanDrop.new(false) })
|
||||
assert_template_result('Yay', "{{ drop | default: 'bar', allow_false: true }}", { 'drop' => BooleanDrop.new(true) })
|
||||
end
|
||||
|
||||
def test_cannot_access_private_methods
|
||||
@@ -732,6 +823,18 @@ class StandardFiltersTest < Minitest::Test
|
||||
assert_equal(expectation, @filters.where(input, "ok"))
|
||||
end
|
||||
|
||||
def test_where_string_keys
|
||||
input = [
|
||||
"alpha", "beta", "gamma", "delta"
|
||||
]
|
||||
|
||||
expectation = [
|
||||
"beta",
|
||||
]
|
||||
|
||||
assert_equal(expectation, @filters.where(input, "be"))
|
||||
end
|
||||
|
||||
def test_where_no_key_set
|
||||
input = [
|
||||
{ "handle" => "alpha", "ok" => true },
|
||||
@@ -777,7 +880,7 @@ class StandardFiltersTest < Minitest::Test
|
||||
end
|
||||
|
||||
def test_all_filters_never_raise_non_liquid_exception
|
||||
test_drop = TestDrop.new
|
||||
test_drop = TestDrop.new(value: "test")
|
||||
test_drop.context = Context.new
|
||||
test_enum = TestEnumerable.new
|
||||
test_enum.context = Context.new
|
||||
@@ -802,19 +905,14 @@ class StandardFiltersTest < Minitest::Test
|
||||
{ 1 => "bar" },
|
||||
["foo", 123, nil, true, false, Drop, ["foo"], { foo: "bar" }],
|
||||
]
|
||||
test_types.each do |first|
|
||||
test_types.each do |other|
|
||||
(@filters.methods - Object.methods).each do |method|
|
||||
arg_count = @filters.method(method).arity
|
||||
arg_count *= -1 if arg_count < 0
|
||||
inputs = [first]
|
||||
inputs << ([other] * (arg_count - 1)) if arg_count > 1
|
||||
begin
|
||||
@filters.send(method, *inputs)
|
||||
rescue Liquid::ArgumentError, Liquid::ZeroDivisionError
|
||||
nil
|
||||
end
|
||||
end
|
||||
StandardFilters.public_instance_methods(false).each do |method|
|
||||
arg_count = @filters.method(method).arity
|
||||
arg_count *= -1 if arg_count < 0
|
||||
|
||||
test_types.repeated_permutation(arg_count) do |args|
|
||||
@filters.send(method, *args)
|
||||
rescue Liquid::Error
|
||||
nil
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -830,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)
|
||||
|
||||
@@ -9,8 +9,8 @@ class BreakTagTest < Minitest::Test
|
||||
# block
|
||||
def test_break_with_no_block
|
||||
assigns = { 'i' => 1 }
|
||||
markup = '{% break %}'
|
||||
expected = ''
|
||||
markup = 'before{% break %}after'
|
||||
expected = 'before'
|
||||
|
||||
assert_template_result(expected, markup, assigns)
|
||||
end
|
||||
|
||||
@@ -6,7 +6,7 @@ class EchoTest < Minitest::Test
|
||||
include Liquid
|
||||
|
||||
def test_echo_outputs_its_input
|
||||
assert_template_result('BAR', <<~LIQUID, 'variable-name' => 'bar')
|
||||
assert_template_result('BAR', <<~LIQUID, { 'variable-name' => 'bar' })
|
||||
{%- echo variable-name | upcase -%}
|
||||
LIQUID
|
||||
end
|
||||
|
||||
@@ -12,10 +12,10 @@ class ForTagTest < Minitest::Test
|
||||
include Liquid
|
||||
|
||||
def test_for
|
||||
assert_template_result(' yo yo yo yo ', '{%for item in array%} yo {%endfor%}', 'array' => [1, 2, 3, 4])
|
||||
assert_template_result('yoyo', '{%for item in array%}yo{%endfor%}', 'array' => [1, 2])
|
||||
assert_template_result(' yo ', '{%for item in array%} yo {%endfor%}', 'array' => [1])
|
||||
assert_template_result('', '{%for item in array%}{%endfor%}', 'array' => [1, 2])
|
||||
assert_template_result(' yo yo yo yo ', '{%for item in array%} yo {%endfor%}', { 'array' => [1, 2, 3, 4] })
|
||||
assert_template_result('yoyo', '{%for item in array%}yo{%endfor%}', { 'array' => [1, 2] })
|
||||
assert_template_result(' yo ', '{%for item in array%} yo {%endfor%}', { 'array' => [1] })
|
||||
assert_template_result('', '{%for item in array%}{%endfor%}', { 'array' => [1, 2] })
|
||||
expected = <<HERE
|
||||
|
||||
yo
|
||||
@@ -30,7 +30,7 @@ HERE
|
||||
yo
|
||||
{%endfor%}
|
||||
HERE
|
||||
assert_template_result(expected, template, 'array' => [1, 2, 3])
|
||||
assert_template_result(expected, template, { 'array' => [1, 2, 3] })
|
||||
end
|
||||
|
||||
def test_for_reversed
|
||||
@@ -45,37 +45,39 @@ HERE
|
||||
Template.parse('{% for i in (a..2) %}{% endfor %}').render!("a" => [1, 2])
|
||||
end
|
||||
|
||||
assert_template_result(' 0 1 2 3 ', '{% for item in (a..3) %} {{item}} {% endfor %}', "a" => "invalid integer")
|
||||
assert_template_result(' 0 1 2 3 ', '{% for item in (a..3) %} {{item}} {% endfor %}', { "a" => "invalid integer" })
|
||||
end
|
||||
|
||||
def test_for_with_variable_range
|
||||
assert_template_result(' 1 2 3 ', '{%for item in (1..foobar) %} {{item}} {%endfor%}', "foobar" => 3)
|
||||
assert_template_result(' 1 2 3 ', '{%for item in (1..foobar) %} {{item}} {%endfor%}', { "foobar" => 3 })
|
||||
end
|
||||
|
||||
def test_for_with_hash_value_range
|
||||
foobar = { "value" => 3 }
|
||||
assert_template_result(' 1 2 3 ', '{%for item in (1..foobar.value) %} {{item}} {%endfor%}', "foobar" => foobar)
|
||||
assert_template_result(' 1 2 3 ', '{%for item in (1..foobar.value) %} {{item}} {%endfor%}', { "foobar" => foobar })
|
||||
end
|
||||
|
||||
def test_for_with_drop_value_range
|
||||
foobar = ThingWithValue.new
|
||||
assert_template_result(' 1 2 3 ', '{%for item in (1..foobar.value) %} {{item}} {%endfor%}', "foobar" => foobar)
|
||||
assert_template_result(' 1 2 3 ', '{%for item in (1..foobar.value) %} {{item}} {%endfor%}', { "foobar" => foobar })
|
||||
end
|
||||
|
||||
def test_for_with_variable
|
||||
assert_template_result(' 1 2 3 ', '{%for item in array%} {{item}} {%endfor%}', 'array' => [1, 2, 3])
|
||||
assert_template_result('123', '{%for item in array%}{{item}}{%endfor%}', 'array' => [1, 2, 3])
|
||||
assert_template_result('123', '{% for item in array %}{{item}}{% endfor %}', 'array' => [1, 2, 3])
|
||||
assert_template_result('abcd', '{%for item in array%}{{item}}{%endfor%}', 'array' => ['a', 'b', 'c', 'd'])
|
||||
assert_template_result('a b c', '{%for item in array%}{{item}}{%endfor%}', 'array' => ['a', ' ', 'b', ' ', 'c'])
|
||||
assert_template_result('abc', '{%for item in array%}{{item}}{%endfor%}', 'array' => ['a', '', 'b', '', 'c'])
|
||||
assert_template_result(' 1 2 3 ', '{%for item in array%} {{item}} {%endfor%}', { 'array' => [1, 2, 3] })
|
||||
assert_template_result('123', '{%for item in array%}{{item}}{%endfor%}', { 'array' => [1, 2, 3] })
|
||||
assert_template_result('123', '{% for item in array %}{{item}}{% endfor %}', { 'array' => [1, 2, 3] })
|
||||
assert_template_result('abcd', '{%for item in array%}{{item}}{%endfor%}', { 'array' => ['a', 'b', 'c', 'd'] })
|
||||
assert_template_result('a b c', '{%for item in array%}{{item}}{%endfor%}', { 'array' => ['a', ' ', 'b', ' ', 'c'] })
|
||||
assert_template_result('abc', '{%for item in array%}{{item}}{%endfor%}', { 'array' => ['a', '', 'b', '', 'c'] })
|
||||
end
|
||||
|
||||
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,15 +88,17 @@ 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
|
||||
assert_template_result('+++', '{%for item in array%}+{%else%}-{%endfor%}', 'array' => [1, 2, 3])
|
||||
assert_template_result('-', '{%for item in array%}+{%else%}-{%endfor%}', 'array' => [])
|
||||
assert_template_result('-', '{%for item in array%}+{%else%}-{%endfor%}', 'array' => nil)
|
||||
assert_template_result('+++', '{%for item in array%}+{%else%}-{%endfor%}', { 'array' => [1, 2, 3] })
|
||||
assert_template_result('-', '{%for item in array%}+{%else%}-{%endfor%}', { 'array' => [] })
|
||||
assert_template_result('-', '{%for item in array%}+{%else%}-{%endfor%}', { 'array' => nil })
|
||||
end
|
||||
|
||||
def test_limiting
|
||||
@@ -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
|
||||
@@ -263,6 +268,19 @@ HERE
|
||||
assert_template_result(expected, markup, assigns)
|
||||
end
|
||||
|
||||
def test_for_with_break_after_nested_loop
|
||||
source = <<~LIQUID.chomp
|
||||
{% for i in (1..2) -%}
|
||||
{% for j in (1..2) -%}
|
||||
{{ i }}-{{ j }},
|
||||
{%- endfor -%}
|
||||
{% break -%}
|
||||
{% endfor -%}
|
||||
after
|
||||
LIQUID
|
||||
assert_template_result("1-1,1-2,after", source)
|
||||
end
|
||||
|
||||
def test_for_with_continue
|
||||
assigns = { 'array' => { 'items' => [1, 2, 3, 4, 5] } }
|
||||
|
||||
@@ -311,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}}-' \
|
||||
@@ -330,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
|
||||
@@ -354,7 +382,7 @@ HERE
|
||||
end
|
||||
|
||||
def test_blank_string_not_iterable
|
||||
assert_template_result('', "{% for char in characters %}I WILL NOT BE OUTPUT{% endfor %}", 'characters' => '')
|
||||
assert_template_result('', "{% for char in characters %}I WILL NOT BE OUTPUT{% endfor %}", { 'characters' => '' })
|
||||
end
|
||||
|
||||
def test_bad_variable_naming_in_for_loop
|
||||
@@ -437,30 +465,4 @@ HERE
|
||||
|
||||
assert(context.registers[:for_stack].empty?)
|
||||
end
|
||||
|
||||
def test_instrument_for_offset_continue
|
||||
assert_usage_increment('for_offset_continue') do
|
||||
Template.parse('{% for item in items offset:continue %}{{item}}{% endfor %}')
|
||||
end
|
||||
|
||||
assert_usage_increment('for_offset_continue', times: 0) do
|
||||
Template.parse('{% for item in items offset:2 %}{{item}}{% endfor %}')
|
||||
end
|
||||
end
|
||||
|
||||
def test_instrument_forloop_drop_name
|
||||
assigns = { 'items' => [1, 2, 3, 4, 5] }
|
||||
|
||||
assert_usage_increment('forloop_drop_name', times: 5) do
|
||||
Template.parse('{% for item in items %}{{forloop.name}}{% endfor %}').render!(assigns)
|
||||
end
|
||||
|
||||
assert_usage_increment('forloop_drop_name', times: 0) do
|
||||
Template.parse('{% for item in items %}{{forloop.index}}{% endfor %}').render!(assigns)
|
||||
end
|
||||
|
||||
assert_usage_increment('forloop_drop_name', times: 0) do
|
||||
Template.parse('{% for item in items %}{{item}}{% endfor %}').render!(assigns)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -24,23 +26,23 @@ class IfElseTagTest < Minitest::Test
|
||||
end
|
||||
|
||||
def test_if_boolean
|
||||
assert_template_result(' YES ', '{% if var %} YES {% endif %}', 'var' => true)
|
||||
assert_template_result(' YES ', '{% if var %} YES {% endif %}', { 'var' => true })
|
||||
end
|
||||
|
||||
def test_if_or
|
||||
assert_template_result(' YES ', '{% if a or b %} YES {% endif %}', 'a' => true, 'b' => true)
|
||||
assert_template_result(' YES ', '{% if a or b %} YES {% endif %}', 'a' => true, 'b' => false)
|
||||
assert_template_result(' YES ', '{% if a or b %} YES {% endif %}', 'a' => false, 'b' => true)
|
||||
assert_template_result('', '{% if a or b %} YES {% endif %}', 'a' => false, 'b' => false)
|
||||
assert_template_result(' YES ', '{% if a or b %} YES {% endif %}', { 'a' => true, 'b' => true })
|
||||
assert_template_result(' YES ', '{% if a or b %} YES {% endif %}', { 'a' => true, 'b' => false })
|
||||
assert_template_result(' YES ', '{% if a or b %} YES {% endif %}', { 'a' => false, 'b' => true })
|
||||
assert_template_result('', '{% if a or b %} YES {% endif %}', { 'a' => false, 'b' => false })
|
||||
|
||||
assert_template_result(' YES ', '{% if a or b or c %} YES {% endif %}', 'a' => false, 'b' => false, 'c' => true)
|
||||
assert_template_result('', '{% if a or b or c %} YES {% endif %}', 'a' => false, 'b' => false, 'c' => false)
|
||||
assert_template_result(' YES ', '{% if a or b or c %} YES {% endif %}', { 'a' => false, 'b' => false, 'c' => true })
|
||||
assert_template_result('', '{% if a or b or c %} YES {% endif %}', { 'a' => false, 'b' => false, 'c' => false })
|
||||
end
|
||||
|
||||
def test_if_or_with_operators
|
||||
assert_template_result(' YES ', '{% if a == true or b == true %} YES {% endif %}', 'a' => true, 'b' => true)
|
||||
assert_template_result(' YES ', '{% if a == true or b == false %} YES {% endif %}', 'a' => true, 'b' => true)
|
||||
assert_template_result('', '{% if a == false or b == false %} YES {% endif %}', 'a' => true, 'b' => true)
|
||||
assert_template_result(' YES ', '{% if a == true or b == true %} YES {% endif %}', { 'a' => true, 'b' => true })
|
||||
assert_template_result(' YES ', '{% if a == true or b == false %} YES {% endif %}', { 'a' => true, 'b' => true })
|
||||
assert_template_result('', '{% if a == false or b == false %} YES {% endif %}', { 'a' => true, 'b' => true })
|
||||
end
|
||||
|
||||
def test_comparison_of_strings_containing_and_or_or
|
||||
@@ -51,55 +53,59 @@ 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
|
||||
assert_template_result('', '{% if foo.bar %} NO {% endif %}', 'foo' => {})
|
||||
assert_template_result('', '{% if foo.bar %} NO {% endif %}', { 'foo' => {} })
|
||||
end
|
||||
|
||||
def test_if_from_variable
|
||||
assert_template_result('', '{% if var %} NO {% endif %}', 'var' => false)
|
||||
assert_template_result('', '{% if var %} NO {% endif %}', 'var' => nil)
|
||||
assert_template_result('', '{% if foo.bar %} NO {% endif %}', 'foo' => { 'bar' => false })
|
||||
assert_template_result('', '{% if foo.bar %} NO {% endif %}', 'foo' => {})
|
||||
assert_template_result('', '{% if foo.bar %} NO {% endif %}', 'foo' => nil)
|
||||
assert_template_result('', '{% if foo.bar %} NO {% endif %}', 'foo' => true)
|
||||
assert_template_result('', '{% if var %} NO {% endif %}', { 'var' => false })
|
||||
assert_template_result('', '{% if var %} NO {% endif %}', { 'var' => nil })
|
||||
assert_template_result('', '{% if foo.bar %} NO {% endif %}', { 'foo' => { 'bar' => false } })
|
||||
assert_template_result('', '{% if foo.bar %} NO {% endif %}', { 'foo' => {} })
|
||||
assert_template_result('', '{% if foo.bar %} NO {% endif %}', { 'foo' => nil })
|
||||
assert_template_result('', '{% if foo.bar %} NO {% endif %}', { 'foo' => true })
|
||||
|
||||
assert_template_result(' YES ', '{% if var %} YES {% endif %}', 'var' => "text")
|
||||
assert_template_result(' YES ', '{% if var %} YES {% endif %}', 'var' => true)
|
||||
assert_template_result(' YES ', '{% if var %} YES {% endif %}', 'var' => 1)
|
||||
assert_template_result(' YES ', '{% if var %} YES {% endif %}', 'var' => {})
|
||||
assert_template_result(' YES ', '{% if var %} YES {% endif %}', 'var' => [])
|
||||
assert_template_result(' YES ', '{% if var %} YES {% endif %}', { 'var' => "text" })
|
||||
assert_template_result(' YES ', '{% if var %} YES {% endif %}', { 'var' => true })
|
||||
assert_template_result(' YES ', '{% if var %} YES {% endif %}', { 'var' => 1 })
|
||||
assert_template_result(' YES ', '{% if var %} YES {% endif %}', { 'var' => {} })
|
||||
assert_template_result(' YES ', '{% if var %} YES {% endif %}', { 'var' => [] })
|
||||
assert_template_result(' YES ', '{% if "foo" %} YES {% endif %}')
|
||||
assert_template_result(' YES ', '{% if foo.bar %} YES {% endif %}', 'foo' => { 'bar' => true })
|
||||
assert_template_result(' YES ', '{% if foo.bar %} YES {% endif %}', 'foo' => { 'bar' => "text" })
|
||||
assert_template_result(' YES ', '{% if foo.bar %} YES {% endif %}', 'foo' => { 'bar' => 1 })
|
||||
assert_template_result(' YES ', '{% if foo.bar %} YES {% endif %}', 'foo' => { 'bar' => {} })
|
||||
assert_template_result(' YES ', '{% if foo.bar %} YES {% endif %}', 'foo' => { 'bar' => [] })
|
||||
assert_template_result(' YES ', '{% if foo.bar %} YES {% endif %}', { 'foo' => { 'bar' => true } })
|
||||
assert_template_result(' YES ', '{% if foo.bar %} YES {% endif %}', { 'foo' => { 'bar' => "text" } })
|
||||
assert_template_result(' YES ', '{% if foo.bar %} YES {% endif %}', { 'foo' => { 'bar' => 1 } })
|
||||
assert_template_result(' YES ', '{% if foo.bar %} YES {% endif %}', { 'foo' => { 'bar' => {} } })
|
||||
assert_template_result(' YES ', '{% if foo.bar %} YES {% endif %}', { 'foo' => { 'bar' => [] } })
|
||||
|
||||
assert_template_result(' YES ', '{% if var %} NO {% else %} YES {% endif %}', 'var' => false)
|
||||
assert_template_result(' YES ', '{% if var %} NO {% else %} YES {% endif %}', 'var' => nil)
|
||||
assert_template_result(' YES ', '{% if var %} YES {% else %} NO {% endif %}', 'var' => true)
|
||||
assert_template_result(' YES ', '{% if "foo" %} YES {% else %} NO {% endif %}', 'var' => "text")
|
||||
assert_template_result(' YES ', '{% if var %} NO {% else %} YES {% endif %}', { 'var' => false })
|
||||
assert_template_result(' YES ', '{% if var %} NO {% else %} YES {% endif %}', { 'var' => nil })
|
||||
assert_template_result(' YES ', '{% if var %} YES {% else %} NO {% endif %}', { 'var' => true })
|
||||
assert_template_result(' YES ', '{% if "foo" %} YES {% else %} NO {% endif %}', { 'var' => "text" })
|
||||
|
||||
assert_template_result(' YES ', '{% if foo.bar %} NO {% else %} YES {% endif %}', 'foo' => { 'bar' => false })
|
||||
assert_template_result(' YES ', '{% if foo.bar %} YES {% else %} NO {% endif %}', 'foo' => { 'bar' => true })
|
||||
assert_template_result(' YES ', '{% if foo.bar %} YES {% else %} NO {% endif %}', 'foo' => { 'bar' => "text" })
|
||||
assert_template_result(' YES ', '{% if foo.bar %} NO {% else %} YES {% endif %}', 'foo' => { 'notbar' => true })
|
||||
assert_template_result(' YES ', '{% if foo.bar %} NO {% else %} YES {% endif %}', 'foo' => {})
|
||||
assert_template_result(' YES ', '{% if foo.bar %} NO {% else %} YES {% endif %}', 'notfoo' => { 'bar' => true })
|
||||
assert_template_result(' YES ', '{% if foo.bar %} NO {% else %} YES {% endif %}', { 'foo' => { 'bar' => false } })
|
||||
assert_template_result(' YES ', '{% if foo.bar %} YES {% else %} NO {% endif %}', { 'foo' => { 'bar' => true } })
|
||||
assert_template_result(' YES ', '{% if foo.bar %} YES {% else %} NO {% endif %}', { 'foo' => { 'bar' => "text" } })
|
||||
assert_template_result(' YES ', '{% if foo.bar %} NO {% else %} YES {% endif %}', { 'foo' => { 'notbar' => true } })
|
||||
assert_template_result(' YES ', '{% if foo.bar %} NO {% else %} YES {% endif %}', { 'foo' => {} })
|
||||
assert_template_result(' YES ', '{% if foo.bar %} NO {% else %} YES {% endif %}', { 'notfoo' => { 'bar' => true } })
|
||||
end
|
||||
|
||||
def test_nested_if
|
||||
@@ -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
|
||||
@@ -184,7 +192,7 @@ class IfElseTagTest < Minitest::Test
|
||||
tests.each do |vals, expected|
|
||||
a, b, c = vals
|
||||
assigns = { 'a' => a, 'b' => b, 'c' => c }
|
||||
assert_template_result(expected.to_s, tpl, assigns, assigns.to_s)
|
||||
assert_template_result(expected.to_s, tpl, assigns, message: assigns.to_s)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -3,44 +3,13 @@
|
||||
require 'test_helper'
|
||||
|
||||
class TestFileSystem
|
||||
PARTIALS = {
|
||||
"nested_template" => "{% include 'header' %} {% include 'body' %} {% include 'footer' %}",
|
||||
"body" => "body {% include 'body_detail' %}",
|
||||
}
|
||||
|
||||
def read_template_file(template_path)
|
||||
case template_path
|
||||
when "product"
|
||||
"Product: {{ product.title }} "
|
||||
|
||||
when "product_alias"
|
||||
"Product: {{ product.title }} "
|
||||
|
||||
when "locale_variables"
|
||||
"Locale: {{echo1}} {{echo2}}"
|
||||
|
||||
when "variant"
|
||||
"Variant: {{ variant.title }}"
|
||||
|
||||
when "nested_template"
|
||||
"{% include 'header' %} {% include 'body' %} {% include 'footer' %}"
|
||||
|
||||
when "body"
|
||||
"body {% include 'body_detail' %}"
|
||||
|
||||
when "nested_product_template"
|
||||
"Product: {{ nested_product_template.title }} {%include 'details'%} "
|
||||
|
||||
when "recursively_nested_template"
|
||||
"-{% include 'recursively_nested_template' %}"
|
||||
|
||||
when "pick_a_source"
|
||||
"from TestFileSystem"
|
||||
|
||||
when 'assignments'
|
||||
"{% assign foo = 'bar' %}"
|
||||
|
||||
when 'break'
|
||||
"{% break %}"
|
||||
|
||||
else
|
||||
template_path
|
||||
end
|
||||
PARTIALS[template_path] || template_path
|
||||
end
|
||||
end
|
||||
|
||||
@@ -81,70 +50,129 @@ class IncludeTagTest < Minitest::Test
|
||||
include Liquid
|
||||
|
||||
def setup
|
||||
Liquid::Template.file_system = TestFileSystem.new
|
||||
@default_file_system = Liquid::Template.file_system
|
||||
end
|
||||
|
||||
def teardown
|
||||
Liquid::Template.file_system = @default_file_system
|
||||
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 ",
|
||||
"{% include 'product' with products[0] %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }])
|
||||
assert_template_result(
|
||||
"Product: Draft 151cm ",
|
||||
"{% include 'product' with products[0] %}",
|
||||
{ "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }] },
|
||||
partials: { "product" => "Product: {{ product.title }} " },
|
||||
)
|
||||
end
|
||||
|
||||
def test_include_tag_with_alias
|
||||
assert_template_result("Product: Draft 151cm ",
|
||||
"{% include 'product_alias' with products[0] as product %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }])
|
||||
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 }} " },
|
||||
)
|
||||
end
|
||||
|
||||
def test_include_tag_for_alias
|
||||
assert_template_result("Product: Draft 151cm Product: Element 155cm ",
|
||||
"{% include 'product_alias' for products as product %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }])
|
||||
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 }} " },
|
||||
)
|
||||
end
|
||||
|
||||
def test_include_tag_with_default_name
|
||||
assert_template_result("Product: Draft 151cm ",
|
||||
"{% include 'product' %}", "product" => { 'title' => 'Draft 151cm' })
|
||||
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 ",
|
||||
"{% include 'product' for products %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => '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 }} " },
|
||||
)
|
||||
end
|
||||
|
||||
def test_include_tag_with_local_variables
|
||||
assert_template_result("Locale: test123 ", "{% include 'locale_variables' echo1: 'test123' %}")
|
||||
assert_template_result(
|
||||
"Locale: test123 ",
|
||||
"{% include 'locale_variables' echo1: 'test123' %}",
|
||||
partials: { "locale_variables" => "Locale: {{echo1}} {{echo2}}" },
|
||||
)
|
||||
end
|
||||
|
||||
def test_include_tag_with_multiple_local_variables
|
||||
assert_template_result("Locale: test123 test321",
|
||||
"{% include 'locale_variables' echo1: 'test123', echo2: 'test321' %}")
|
||||
assert_template_result(
|
||||
"Locale: test123 test321",
|
||||
"{% include 'locale_variables' echo1: 'test123', echo2: 'test321' %}",
|
||||
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' })
|
||||
{ 'echo1' => 'test123', 'more_echos' => { "echo2" => 'test321' } },
|
||||
partials: { "locale_variables" => "Locale: {{echo1}} {{echo2}}" },
|
||||
)
|
||||
end
|
||||
|
||||
def test_included_templates_assigns_variables
|
||||
assert_template_result("bar", "{% include 'assignments' %}{{ foo }}")
|
||||
assert_template_result(
|
||||
"bar",
|
||||
"{% include 'assignments' %}{{ foo }}",
|
||||
partials: { 'assignments' => "{% assign foo = 'bar' %}" },
|
||||
)
|
||||
end
|
||||
|
||||
def test_nested_include_tag
|
||||
assert_template_result("body body_detail", "{% include 'body' %}")
|
||||
partials = { "body" => "body {% include 'body_detail' %}", "body_detail" => "body_detail" }
|
||||
assert_template_result("body body_detail", "{% include 'body' %}", partials: partials)
|
||||
|
||||
assert_template_result("header body body_detail footer", "{% include 'nested_template' %}")
|
||||
partials = partials.merge({
|
||||
"nested_template" => "{% include 'header' %} {% include 'body' %} {% include 'footer' %}",
|
||||
"header" => "header",
|
||||
"footer" => "footer",
|
||||
})
|
||||
assert_template_result("header body body_detail footer", "{% include 'nested_template' %}", partials: partials)
|
||||
end
|
||||
|
||||
def test_nested_include_with_variable
|
||||
assert_template_result("Product: Draft 151cm details ",
|
||||
"{% include 'nested_product_template' with product %}", "product" => { "title" => 'Draft 151cm' })
|
||||
partials = {
|
||||
"nested_product_template" => "Product: {{ nested_product_template.title }} {%include 'details'%} ",
|
||||
"details" => "details",
|
||||
}
|
||||
|
||||
assert_template_result("Product: Draft 151cm details Product: Element 155cm details ",
|
||||
"{% include 'nested_product_template' for products %}", "products" => [{ "title" => 'Draft 151cm' }, { "title" => 'Element 155cm' }])
|
||||
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,
|
||||
)
|
||||
end
|
||||
|
||||
def test_recursively_included_template_does_not_produce_endless_loop
|
||||
@@ -162,41 +190,68 @@ class IncludeTagTest < Minitest::Test
|
||||
end
|
||||
|
||||
def test_dynamically_choosen_template
|
||||
assert_template_result("Test123", "{% include template %}", "template" => 'Test123')
|
||||
assert_template_result("Test321", "{% include template %}", "template" => 'Test321')
|
||||
assert_template_result(
|
||||
"Test123",
|
||||
"{% include template %}",
|
||||
{ "template" => 'Test123' },
|
||||
partials: { "Test123" => "Test123" },
|
||||
)
|
||||
|
||||
assert_template_result("Product: Draft 151cm ", "{% include template for product %}",
|
||||
"template" => 'product', 'product' => { 'title' => 'Draft 151cm' })
|
||||
assert_template_result(
|
||||
"Test321",
|
||||
"{% include template %}",
|
||||
{ "template" => 'Test321' },
|
||||
partials: { "Test321" => "Test321" },
|
||||
)
|
||||
|
||||
assert_template_result(
|
||||
"Product: Draft 151cm ",
|
||||
"{% include template for product %}",
|
||||
{ "template" => 'product', 'product' => { 'title' => 'Draft 151cm' } },
|
||||
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 %}")
|
||||
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
|
||||
@@ -206,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
|
||||
@@ -222,6 +279,7 @@ class IncludeTagTest < Minitest::Test
|
||||
end
|
||||
|
||||
def test_passing_options_to_included_templates
|
||||
Liquid::Template.file_system = TestFileSystem.new
|
||||
assert_raises(Liquid::SyntaxError) do
|
||||
Template.parse("{% include template %}", error_mode: :strict).render!("template" => '{{ "X" || downcase }}')
|
||||
end
|
||||
@@ -237,25 +295,53 @@ class IncludeTagTest < Minitest::Test
|
||||
end
|
||||
|
||||
def test_render_raise_argument_error_when_template_is_undefined
|
||||
assert_raises(Liquid::ArgumentError) do
|
||||
template = Liquid::Template.parse('{% include undefined_variable %}')
|
||||
template.render!
|
||||
end
|
||||
assert_raises(Liquid::ArgumentError) do
|
||||
template = Liquid::Template.parse('{% include nil %}')
|
||||
template.render!
|
||||
end
|
||||
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,
|
||||
)
|
||||
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,
|
||||
)
|
||||
end
|
||||
|
||||
def test_including_via_variable_value
|
||||
assert_template_result("from TestFileSystem", "{% assign page = 'pick_a_source' %}{% include page %}")
|
||||
assert_template_result(
|
||||
"from TestFileSystem",
|
||||
"{% assign page = 'pick_a_source' %}{% include page %}",
|
||||
partials: { "pick_a_source" => "from TestFileSystem" },
|
||||
)
|
||||
|
||||
assert_template_result("Product: Draft 151cm ", "{% assign page = 'product' %}{% include page %}", "product" => { 'title' => 'Draft 151cm' })
|
||||
partials = { "product" => "Product: {{ product.title }} " }
|
||||
|
||||
assert_template_result("Product: Draft 151cm ", "{% assign page = 'product' %}{% include page for foo %}", "foo" => { 'title' => 'Draft 151cm' })
|
||||
assert_template_result(
|
||||
"Product: Draft 151cm ",
|
||||
"{% assign page = 'product' %}{% include page %}",
|
||||
{ "product" => { 'title' => 'Draft 151cm' } },
|
||||
partials: partials,
|
||||
)
|
||||
|
||||
assert_template_result(
|
||||
"Product: Draft 151cm ",
|
||||
"{% assign page = 'product' %}{% include page for foo %}",
|
||||
{ "foo" => { 'title' => 'Draft 151cm' } },
|
||||
partials: partials,
|
||||
)
|
||||
end
|
||||
|
||||
def test_including_with_strict_variables
|
||||
Liquid::Template.file_system = StubFileSystem.new({ "simple" => "simple" })
|
||||
template = Liquid::Template.parse("{% include 'simple' %}", error_mode: :warn)
|
||||
template.render(nil, strict_variables: true)
|
||||
|
||||
@@ -264,6 +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 %}")
|
||||
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
|
||||
|
||||
@@ -6,20 +6,25 @@ class IncrementTagTest < Minitest::Test
|
||||
include Liquid
|
||||
|
||||
def test_inc
|
||||
assert_template_result('0', '{%increment port %}', {})
|
||||
assert_template_result('0 1', '{%increment port %} {%increment port%}', {})
|
||||
assert_template_result('0 0 1 2 1',
|
||||
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',
|
||||
'{%increment port %} {%increment starboard%} ' \
|
||||
'{%increment port %} {%increment port%} ' \
|
||||
'{%increment starboard %}', {})
|
||||
'{%increment starboard %}',
|
||||
)
|
||||
end
|
||||
|
||||
def test_dec
|
||||
assert_template_result('9', '{%decrement port %}', 'port' => 10)
|
||||
assert_template_result('-1 -2', '{%decrement port %} {%decrement port%}', {})
|
||||
assert_template_result('1 5 2 2 5',
|
||||
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',
|
||||
'{%increment starboard %} {%increment starboard%} {%increment starboard%} ' \
|
||||
'{%increment port %} {%increment starboard%} ' \
|
||||
'{%increment port %} {%decrement port%} ' \
|
||||
'{%decrement starboard %}', 'port' => 1, 'starboard' => 5)
|
||||
'{%decrement starboard %}',
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'test_helper'
|
||||
|
||||
class InlineCommentTest < Minitest::Test
|
||||
include Liquid
|
||||
|
||||
def test_inline_comment_returns_nothing
|
||||
assert_template_result('', '{%- # this is an inline comment -%}')
|
||||
assert_template_result('', '{%-# this is an inline comment -%}')
|
||||
assert_template_result('', '{% # this is an inline comment %}')
|
||||
assert_template_result('', '{%# this is an inline comment %}')
|
||||
end
|
||||
|
||||
def test_inline_comment_does_not_require_a_space_after_the_pound_sign
|
||||
assert_template_result('', '{%#this is an inline comment%}')
|
||||
end
|
||||
|
||||
def test_liquid_inline_comment_returns_nothing
|
||||
assert_template_result('Hey there, how are you doing today?', <<~LIQUID)
|
||||
{%- liquid
|
||||
# This is how you'd write a block comment in a liquid tag.
|
||||
# It looks a lot like what you'd have in ruby.
|
||||
|
||||
# You can use it as inline documentation in your
|
||||
# liquid blocks to explain why you're doing something.
|
||||
echo "Hey there, "
|
||||
|
||||
# It won't affect the output.
|
||||
echo "how are you doing today?"
|
||||
-%}
|
||||
LIQUID
|
||||
end
|
||||
|
||||
def test_inline_comment_can_be_written_on_multiple_lines
|
||||
assert_template_result('', <<~LIQUID)
|
||||
{%-
|
||||
# That kind of block comment is also allowed.
|
||||
# It would only be a stylistic difference.
|
||||
|
||||
# Much like JavaScript's /* */ comments and their
|
||||
# leading * on new lines.
|
||||
-%}
|
||||
LIQUID
|
||||
end
|
||||
|
||||
def test_inline_comment_multiple_pound_signs
|
||||
assert_template_result('', <<~LIQUID)
|
||||
{%- liquid
|
||||
######################################
|
||||
# We support comments like this too. #
|
||||
######################################
|
||||
-%}
|
||||
LIQUID
|
||||
end
|
||||
|
||||
def test_inline_comments_require_the_pound_sign_on_every_new_line
|
||||
assert_match_syntax_error("Each line of comments must be prefixed by the '#' character", <<~LIQUID)
|
||||
{%-
|
||||
# some comment
|
||||
echo 'hello world'
|
||||
-%}
|
||||
LIQUID
|
||||
end
|
||||
|
||||
def test_inline_comment_does_not_support_nested_tags
|
||||
assert_template_result(' -%}', "{%- # {% echo 'hello world' %} -%}")
|
||||
end
|
||||
end
|
||||
@@ -6,13 +6,13 @@ class LiquidTagTest < Minitest::Test
|
||||
include Liquid
|
||||
|
||||
def test_liquid_tag
|
||||
assert_template_result('1 2 3', <<~LIQUID, 'array' => [1, 2, 3])
|
||||
assert_template_result('1 2 3', <<~LIQUID, { 'array' => [1, 2, 3] })
|
||||
{%- liquid
|
||||
echo array | join: " "
|
||||
-%}
|
||||
LIQUID
|
||||
|
||||
assert_template_result('1 2 3', <<~LIQUID, 'array' => [1, 2, 3])
|
||||
assert_template_result('1 2 3', <<~LIQUID, { 'array' => [1, 2, 3] })
|
||||
{%- liquid
|
||||
for value in array
|
||||
echo value
|
||||
@@ -23,7 +23,7 @@ class LiquidTagTest < Minitest::Test
|
||||
-%}
|
||||
LIQUID
|
||||
|
||||
assert_template_result('4 8 12 6', <<~LIQUID, 'array' => [1, 2, 3])
|
||||
assert_template_result('4 8 12 6', <<~LIQUID, { 'array' => [1, 2, 3] })
|
||||
{%- liquid
|
||||
for value in array
|
||||
assign double_value = value | times: 2
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -6,53 +6,79 @@ class RenderTagTest < Minitest::Test
|
||||
include Liquid
|
||||
|
||||
def test_render_with_no_arguments
|
||||
Liquid::Template.file_system = StubFileSystem.new('source' => 'rendered content')
|
||||
assert_template_result('rendered content', '{% render "source" %}')
|
||||
assert_template_result(
|
||||
'rendered content',
|
||||
'{% render "source" %}',
|
||||
partials: { 'source' => 'rendered content' },
|
||||
)
|
||||
end
|
||||
|
||||
def test_render_tag_looks_for_file_system_in_registers_first
|
||||
file_system = StubFileSystem.new('pick_a_source' => 'from register file system')
|
||||
assert_equal('from register file system',
|
||||
Template.parse('{% render "pick_a_source" %}').render!({}, registers: { file_system: 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
|
||||
Liquid::Template.file_system = StubFileSystem.new('product' => '{{ inner_product.title }}')
|
||||
assert_template_result('My Product', '{% render "product", inner_product: outer_product %}',
|
||||
'outer_product' => { 'title' => 'My Product' })
|
||||
assert_template_result(
|
||||
'My Product',
|
||||
'{% render "product", inner_product: outer_product %}',
|
||||
{ 'outer_product' => { 'title' => 'My Product' } },
|
||||
partials: { 'product' => '{{ inner_product.title }}' },
|
||||
)
|
||||
end
|
||||
|
||||
def test_render_accepts_literals_as_arguments
|
||||
Liquid::Template.file_system = StubFileSystem.new('snippet' => '{{ price }}')
|
||||
assert_template_result('123', '{% render "snippet", price: 123 %}')
|
||||
assert_template_result(
|
||||
'123',
|
||||
'{% render "snippet", price: 123 %}',
|
||||
partials: { 'snippet' => '{{ price }}' },
|
||||
)
|
||||
end
|
||||
|
||||
def test_render_accepts_multiple_named_arguments
|
||||
Liquid::Template.file_system = StubFileSystem.new('snippet' => '{{ one }} {{ two }}')
|
||||
assert_template_result('1 2', '{% render "snippet", one: 1, two: 2 %}')
|
||||
assert_template_result(
|
||||
'1 2',
|
||||
'{% render "snippet", one: 1, two: 2 %}',
|
||||
partials: { 'snippet' => '{{ one }} {{ two }}' },
|
||||
)
|
||||
end
|
||||
|
||||
def test_render_does_not_inherit_parent_scope_variables
|
||||
Liquid::Template.file_system = StubFileSystem.new('snippet' => '{{ outer_variable }}')
|
||||
assert_template_result('', '{% assign outer_variable = "should not be visible" %}{% render "snippet" %}')
|
||||
assert_template_result(
|
||||
'',
|
||||
'{% assign outer_variable = "should not be visible" %}{% render "snippet" %}',
|
||||
partials: { 'snippet' => '{{ outer_variable }}' },
|
||||
)
|
||||
end
|
||||
|
||||
def test_render_does_not_inherit_variable_with_same_name_as_snippet
|
||||
Liquid::Template.file_system = StubFileSystem.new('snippet' => '{{ snippet }}')
|
||||
assert_template_result('', "{% assign snippet = 'should not be visible' %}{% render 'snippet' %}")
|
||||
assert_template_result(
|
||||
'',
|
||||
"{% assign snippet = 'should not be visible' %}{% render 'snippet' %}",
|
||||
partials: { 'snippet' => '{{ snippet }}' },
|
||||
)
|
||||
end
|
||||
|
||||
def test_render_does_not_mutate_parent_scope
|
||||
Liquid::Template.file_system = StubFileSystem.new('snippet' => '{% assign inner = 1 %}')
|
||||
assert_template_result('', "{% render 'snippet' %}{{ inner }}")
|
||||
assert_template_result(
|
||||
'',
|
||||
"{% render 'snippet' %}{{ inner }}",
|
||||
partials: { 'snippet' => '{% assign inner = 1 %}' },
|
||||
)
|
||||
end
|
||||
|
||||
def test_nested_render_tag
|
||||
Liquid::Template.file_system = StubFileSystem.new(
|
||||
'one' => "one {% render 'two' %}",
|
||||
'two' => 'two'
|
||||
assert_template_result(
|
||||
'one two',
|
||||
"{% render 'one' %}",
|
||||
partials: {
|
||||
'one' => "one {% render 'two' %}",
|
||||
'two' => 'two',
|
||||
},
|
||||
)
|
||||
assert_template_result('one two', "{% render 'one' %}")
|
||||
end
|
||||
|
||||
def test_recursively_rendered_template_does_not_produce_endless_loop
|
||||
@@ -73,141 +99,190 @@ class RenderTagTest < Minitest::Test
|
||||
end
|
||||
|
||||
def test_dynamically_choosen_templates_are_not_allowed
|
||||
Liquid::Template.file_system = StubFileSystem.new('snippet' => 'should not be rendered')
|
||||
|
||||
assert_raises(Liquid::SyntaxError) do
|
||||
Liquid::Template.parse("{% assign name = 'snippet' %}{% render name %}")
|
||||
end
|
||||
assert_syntax_error("{% assign name = 'snippet' %}{% render name %}")
|
||||
end
|
||||
|
||||
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
|
||||
Liquid::Template.file_system = StubFileSystem.new('snippet' => 'my message')
|
||||
assert_template_result('my message', '{% if true %}{% render "snippet" %}{% endif %}')
|
||||
assert_template_result(
|
||||
'my message',
|
||||
'{% if true %}{% render "snippet" %}{% endif %}',
|
||||
partials: { 'snippet' => 'my message' },
|
||||
)
|
||||
end
|
||||
|
||||
def test_break_through_render
|
||||
Liquid::Template.file_system = StubFileSystem.new('break' => '{% break %}')
|
||||
assert_template_result('1', '{% for i in (1..3) %}{{ i }}{% break %}{{ i }}{% endfor %}')
|
||||
assert_template_result('112233', '{% for i in (1..3) %}{{ i }}{% render "break" %}{{ i }}{% endfor %}')
|
||||
options = { partials: { 'break' => '{% break %}' } }
|
||||
assert_template_result('1', '{% for i in (1..3) %}{{ i }}{% break %}{{ i }}{% endfor %}', **options)
|
||||
assert_template_result('112233', '{% for i in (1..3) %}{{ i }}{% render "break" %}{{ i }}{% endfor %}', **options)
|
||||
end
|
||||
|
||||
def test_increment_is_isolated_between_renders
|
||||
Liquid::Template.file_system = StubFileSystem.new('incr' => '{% increment %}')
|
||||
assert_template_result('010', '{% increment %}{% increment %}{% render "incr" %}')
|
||||
assert_template_result(
|
||||
'010',
|
||||
'{% increment %}{% increment %}{% render "incr" %}',
|
||||
partials: { 'incr' => '{% increment %}' },
|
||||
)
|
||||
end
|
||||
|
||||
def test_decrement_is_isolated_between_renders
|
||||
Liquid::Template.file_system = StubFileSystem.new('decr' => '{% decrement %}')
|
||||
assert_template_result('-1-2-1', '{% decrement %}{% decrement %}{% render "decr" %}')
|
||||
assert_template_result(
|
||||
'-1-2-1',
|
||||
'{% decrement %}{% decrement %}{% render "decr" %}',
|
||||
partials: { 'decr' => '{% decrement %}' },
|
||||
)
|
||||
end
|
||||
|
||||
def test_includes_will_not_render_inside_render_tag
|
||||
Liquid::Template.file_system = StubFileSystem.new(
|
||||
'foo' => 'bar',
|
||||
'test_include' => '{% include "foo" %}'
|
||||
assert_template_result(
|
||||
'Liquid error (test_include line 1): include usage is not allowed in this context',
|
||||
'{% render "test_include" %}',
|
||||
render_errors: true,
|
||||
partials: {
|
||||
'foo' => 'bar',
|
||||
'test_include' => '{% include "foo" %}',
|
||||
},
|
||||
)
|
||||
|
||||
exc = assert_raises(Liquid::DisabledError) do
|
||||
Liquid::Template.parse('{% render "test_include" %}').render!
|
||||
end
|
||||
assert_equal('Liquid error: include usage is not allowed in this context', exc.message)
|
||||
end
|
||||
|
||||
def test_includes_will_not_render_inside_nested_sibling_tags
|
||||
Liquid::Template.file_system = StubFileSystem.new(
|
||||
'foo' => 'bar',
|
||||
'nested_render_with_sibling_include' => '{% render "test_include" %}{% include "foo" %}',
|
||||
'test_include' => '{% include "foo" %}'
|
||||
assert_template_result(
|
||||
"Liquid error (test_include line 1): include usage is not allowed in this context" \
|
||||
"Liquid error (nested_render_with_sibling_include line 1): include usage is not allowed in this context",
|
||||
'{% render "nested_render_with_sibling_include" %}',
|
||||
partials: {
|
||||
'foo' => 'bar',
|
||||
'nested_render_with_sibling_include' => '{% render "test_include" %}{% include "foo" %}',
|
||||
'test_include' => '{% include "foo" %}',
|
||||
},
|
||||
render_errors: true,
|
||||
)
|
||||
|
||||
output = Liquid::Template.parse('{% render "nested_render_with_sibling_include" %}').render
|
||||
assert_equal('Liquid error: include usage is not allowed in this contextLiquid error: include usage is not allowed in this context', output)
|
||||
end
|
||||
|
||||
def test_render_tag_with
|
||||
Liquid::Template.file_system = StubFileSystem.new(
|
||||
'product' => "Product: {{ product.title }} ",
|
||||
'product_alias' => "Product: {{ product.title }} ",
|
||||
assert_template_result(
|
||||
"Product: Draft 151cm ",
|
||||
"{% render 'product' with products[0] %}",
|
||||
{ "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }] },
|
||||
partials: {
|
||||
'product' => "Product: {{ product.title }} ",
|
||||
'product_alias' => "Product: {{ product.title }} ",
|
||||
},
|
||||
)
|
||||
|
||||
assert_template_result("Product: Draft 151cm ",
|
||||
"{% render 'product' with products[0] %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }])
|
||||
end
|
||||
|
||||
def test_render_tag_with_alias
|
||||
Liquid::Template.file_system = StubFileSystem.new(
|
||||
'product' => "Product: {{ product.title }} ",
|
||||
'product_alias' => "Product: {{ product.title }} ",
|
||||
assert_template_result(
|
||||
"Product: Draft 151cm ",
|
||||
"{% render 'product_alias' with products[0] as product %}",
|
||||
{ "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }] },
|
||||
partials: {
|
||||
'product' => "Product: {{ product.title }} ",
|
||||
'product_alias' => "Product: {{ product.title }} ",
|
||||
},
|
||||
)
|
||||
|
||||
assert_template_result("Product: Draft 151cm ",
|
||||
"{% render 'product_alias' with products[0] as product %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }])
|
||||
end
|
||||
|
||||
def test_render_tag_for_alias
|
||||
Liquid::Template.file_system = StubFileSystem.new(
|
||||
'product' => "Product: {{ product.title }} ",
|
||||
'product_alias' => "Product: {{ product.title }} ",
|
||||
assert_template_result(
|
||||
"Product: Draft 151cm Product: Element 155cm ",
|
||||
"{% render 'product_alias' for products as product %}",
|
||||
{ "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }] },
|
||||
partials: {
|
||||
'product' => "Product: {{ product.title }} ",
|
||||
'product_alias' => "Product: {{ product.title }} ",
|
||||
},
|
||||
)
|
||||
|
||||
assert_template_result("Product: Draft 151cm Product: Element 155cm ",
|
||||
"{% render 'product_alias' for products as product %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }])
|
||||
end
|
||||
|
||||
def test_render_tag_for
|
||||
Liquid::Template.file_system = StubFileSystem.new(
|
||||
'product' => "Product: {{ product.title }} ",
|
||||
'product_alias' => "Product: {{ product.title }} ",
|
||||
assert_template_result(
|
||||
"Product: Draft 151cm Product: Element 155cm ",
|
||||
"{% render 'product' for products %}",
|
||||
{ "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }] },
|
||||
partials: {
|
||||
'product' => "Product: {{ product.title }} ",
|
||||
'product_alias' => "Product: {{ product.title }} ",
|
||||
},
|
||||
)
|
||||
|
||||
assert_template_result("Product: Draft 151cm Product: Element 155cm ",
|
||||
"{% render 'product' for products %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }])
|
||||
end
|
||||
|
||||
def test_render_tag_forloop
|
||||
Liquid::Template.file_system = StubFileSystem.new(
|
||||
'product' => "Product: {{ product.title }} {% if forloop.first %}first{% endif %} {% if forloop.last %}last{% endif %} index:{{ forloop.index }} ",
|
||||
assert_template_result(
|
||||
"Product: Draft 151cm first index:1 Product: Element 155cm last index:2 ",
|
||||
"{% render 'product' for products %}",
|
||||
{ "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }] },
|
||||
partials: {
|
||||
'product' => "Product: {{ product.title }} {% if forloop.first %}first{% endif %} {% if forloop.last %}last{% endif %} index:{{ forloop.index }} ",
|
||||
},
|
||||
)
|
||||
|
||||
assert_template_result("Product: Draft 151cm first index:1 Product: Element 155cm last index:2 ",
|
||||
"{% render 'product' for products %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }])
|
||||
end
|
||||
|
||||
def test_render_tag_for_drop
|
||||
Liquid::Template.file_system = StubFileSystem.new(
|
||||
'loop' => "{{ value.foo }}",
|
||||
assert_template_result(
|
||||
"123",
|
||||
"{% render 'loop' for loop as value %}",
|
||||
{ "loop" => TestEnumerable.new },
|
||||
partials: {
|
||||
'loop' => "{{ value.foo }}",
|
||||
},
|
||||
)
|
||||
|
||||
assert_template_result("123",
|
||||
"{% render 'loop' for loop as value %}", "loop" => TestEnumerable.new)
|
||||
end
|
||||
|
||||
def test_render_tag_with_drop
|
||||
Liquid::Template.file_system = StubFileSystem.new(
|
||||
'loop' => "{{ value }}",
|
||||
assert_template_result(
|
||||
"TestEnumerable",
|
||||
"{% render 'loop' with loop as value %}",
|
||||
{ "loop" => TestEnumerable.new },
|
||||
partials: {
|
||||
'loop' => "{{ value }}",
|
||||
},
|
||||
)
|
||||
end
|
||||
|
||||
assert_template_result("TestEnumerable",
|
||||
"{% render 'loop' with loop as value %}", "loop" => TestEnumerable.new)
|
||||
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
|
||||
|
||||
@@ -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 %}')
|
||||
@@ -36,6 +40,8 @@ class StandardTagTest < Minitest::Test
|
||||
assert_template_result('', '{%comment%}{% endif %}{%endcomment%}')
|
||||
assert_template_result('', '{% comment %}{% endwhatever %}{% endcomment %}')
|
||||
assert_template_result('', '{% comment %}{% raw %} {{%%%%}} }} { {% endcomment %} {% comment {% endraw %} {% endcomment %}')
|
||||
assert_template_result('', '{% comment %}{% " %}{% endcomment %}')
|
||||
assert_template_result('', '{% comment %}{%%}{% endcomment %}')
|
||||
|
||||
assert_template_result('foobar', 'foo{%comment%}comment{%endcomment%}bar')
|
||||
assert_template_result('foobar', 'foo{% comment %}comment{% endcomment %}bar')
|
||||
@@ -62,160 +68,199 @@ 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
|
||||
assert_template_result('', '{% case a.size %}{% when 1 %}1{% when 2 %}2{% endcase %}', 'a' => [])
|
||||
assert_template_result('1', '{% case a.size %}{% when 1 %}1{% when 2 %}2{% endcase %}', 'a' => [1])
|
||||
assert_template_result('2', '{% case a.size %}{% when 1 %}1{% when 2 %}2{% endcase %}', 'a' => [1, 1])
|
||||
assert_template_result('', '{% case a.size %}{% when 1 %}1{% when 2 %}2{% endcase %}', 'a' => [1, 1, 1])
|
||||
assert_template_result('', '{% case a.size %}{% when 1 %}1{% when 2 %}2{% endcase %}', 'a' => [1, 1, 1, 1])
|
||||
assert_template_result('', '{% case a.size %}{% when 1 %}1{% when 2 %}2{% endcase %}', 'a' => [1, 1, 1, 1, 1])
|
||||
assert_template_result('', '{% case a.size %}{% when 1 %}1{% when 2 %}2{% endcase %}', { 'a' => [] })
|
||||
assert_template_result('1', '{% case a.size %}{% when 1 %}1{% when 2 %}2{% endcase %}', { 'a' => [1] })
|
||||
assert_template_result('2', '{% case a.size %}{% when 1 %}1{% when 2 %}2{% endcase %}', { 'a' => [1, 1] })
|
||||
assert_template_result('', '{% case a.size %}{% when 1 %}1{% when 2 %}2{% endcase %}', { 'a' => [1, 1, 1] })
|
||||
assert_template_result('', '{% case a.size %}{% when 1 %}1{% when 2 %}2{% endcase %}', { 'a' => [1, 1, 1, 1] })
|
||||
assert_template_result('', '{% case a.size %}{% when 1 %}1{% when 2 %}2{% endcase %}', { 'a' => [1, 1, 1, 1, 1] })
|
||||
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
|
||||
# Example from the shopify forums
|
||||
code = "{% case collection.handle %}{% when 'menswear-jackets' %}{% assign ptitle = 'menswear' %}{% when 'menswear-t-shirts' %}{% assign ptitle = 'menswear' %}{% else %}{% assign ptitle = 'womenswear' %}{% endcase %}{{ ptitle }}"
|
||||
template = Liquid::Template.parse(code)
|
||||
assert_equal("menswear", template.render!("collection" => { 'handle' => 'menswear-jackets' }))
|
||||
assert_equal("menswear", template.render!("collection" => { 'handle' => 'menswear-t-shirts' }))
|
||||
assert_equal("womenswear", template.render!("collection" => { 'handle' => 'x' }))
|
||||
assert_equal("womenswear", template.render!("collection" => { 'handle' => 'y' }))
|
||||
assert_equal("womenswear", template.render!("collection" => { 'handle' => 'z' }))
|
||||
code = "{% case collection.handle %}{% when 'menswear-jackets' %}{% assign ptitle = 'menswear' %}{% when 'menswear-t-shirts' %}{% assign ptitle = 'menswear' %}{% else %}{% assign ptitle = 'womenswear' %}{% endcase %}{{ ptitle }}"
|
||||
assert_template_result("menswear", code, { "collection" => { 'handle' => 'menswear-jackets' } })
|
||||
assert_template_result("menswear", code, { "collection" => { 'handle' => 'menswear-t-shirts' } })
|
||||
assert_template_result("womenswear", code, { "collection" => { 'handle' => 'x' } })
|
||||
assert_template_result("womenswear", code, { "collection" => { 'handle' => 'y' } })
|
||||
assert_template_result("womenswear", code, { "collection" => { 'handle' => 'z' } })
|
||||
end
|
||||
|
||||
def test_case_when_or
|
||||
code = '{% case condition %}{% when 1 or 2 or 3 %} its 1 or 2 or 3 {% when 4 %} its 4 {% endcase %}'
|
||||
assert_template_result(' its 1 or 2 or 3 ', code, 'condition' => 1)
|
||||
assert_template_result(' its 1 or 2 or 3 ', code, 'condition' => 2)
|
||||
assert_template_result(' its 1 or 2 or 3 ', code, 'condition' => 3)
|
||||
assert_template_result(' its 4 ', code, 'condition' => 4)
|
||||
assert_template_result('', code, 'condition' => 5)
|
||||
assert_template_result(' its 1 or 2 or 3 ', code, { 'condition' => 1 })
|
||||
assert_template_result(' its 1 or 2 or 3 ', code, { 'condition' => 2 })
|
||||
assert_template_result(' its 1 or 2 or 3 ', code, { 'condition' => 3 })
|
||||
assert_template_result(' its 4 ', code, { 'condition' => 4 })
|
||||
assert_template_result('', code, { 'condition' => 5 })
|
||||
|
||||
code = '{% case condition %}{% when 1 or "string" or null %} its 1 or 2 or 3 {% when 4 %} its 4 {% endcase %}'
|
||||
assert_template_result(' its 1 or 2 or 3 ', code, 'condition' => 1)
|
||||
assert_template_result(' its 1 or 2 or 3 ', code, 'condition' => 'string')
|
||||
assert_template_result(' its 1 or 2 or 3 ', code, 'condition' => nil)
|
||||
assert_template_result('', code, 'condition' => 'something else')
|
||||
assert_template_result(' its 1 or 2 or 3 ', code, { 'condition' => 1 })
|
||||
assert_template_result(' its 1 or 2 or 3 ', code, { 'condition' => 'string' })
|
||||
assert_template_result(' its 1 or 2 or 3 ', code, { 'condition' => nil })
|
||||
assert_template_result('', code, { 'condition' => 'something else' })
|
||||
end
|
||||
|
||||
def test_case_when_comma
|
||||
code = '{% case condition %}{% when 1, 2, 3 %} its 1 or 2 or 3 {% when 4 %} its 4 {% endcase %}'
|
||||
assert_template_result(' its 1 or 2 or 3 ', code, 'condition' => 1)
|
||||
assert_template_result(' its 1 or 2 or 3 ', code, 'condition' => 2)
|
||||
assert_template_result(' its 1 or 2 or 3 ', code, 'condition' => 3)
|
||||
assert_template_result(' its 4 ', code, 'condition' => 4)
|
||||
assert_template_result('', code, 'condition' => 5)
|
||||
assert_template_result(' its 1 or 2 or 3 ', code, { 'condition' => 1 })
|
||||
assert_template_result(' its 1 or 2 or 3 ', code, { 'condition' => 2 })
|
||||
assert_template_result(' its 1 or 2 or 3 ', code, { 'condition' => 3 })
|
||||
assert_template_result(' its 4 ', code, { 'condition' => 4 })
|
||||
assert_template_result('', code, { 'condition' => 5 })
|
||||
|
||||
code = '{% case condition %}{% when 1, "string", null %} its 1 or 2 or 3 {% when 4 %} its 4 {% endcase %}'
|
||||
assert_template_result(' its 1 or 2 or 3 ', code, 'condition' => 1)
|
||||
assert_template_result(' its 1 or 2 or 3 ', code, 'condition' => 'string')
|
||||
assert_template_result(' its 1 or 2 or 3 ', code, 'condition' => nil)
|
||||
assert_template_result('', code, 'condition' => 'something else')
|
||||
assert_template_result(' its 1 or 2 or 3 ', code, { 'condition' => 1 })
|
||||
assert_template_result(' its 1 or 2 or 3 ', code, { 'condition' => 'string' })
|
||||
assert_template_result(' its 1 or 2 or 3 ', code, { 'condition' => nil })
|
||||
assert_template_result('', code, { 'condition' => 'something else' })
|
||||
end
|
||||
|
||||
def test_case_when_comma_and_blank_body
|
||||
code = '{% case condition %}{% when 1, 2 %} {% assign r = "result" %} {% endcase %}{{ r }}'
|
||||
assert_template_result('result', code, 'condition' => 2)
|
||||
assert_template_result('result', code, { 'condition' => 2 })
|
||||
end
|
||||
|
||||
def test_assign
|
||||
@@ -252,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
|
||||
|
||||
@@ -60,22 +60,22 @@ class StatementsTest < Minitest::Test
|
||||
|
||||
def test_var_strings_equal
|
||||
text = ' {% if var == "hello there!" %} true {% else %} false {% endif %} '
|
||||
assert_template_result(' true ', text, 'var' => 'hello there!')
|
||||
assert_template_result(' true ', text, { 'var' => 'hello there!' })
|
||||
end
|
||||
|
||||
def test_var_strings_are_not_equal
|
||||
text = ' {% if "hello there!" == var %} true {% else %} false {% endif %} '
|
||||
assert_template_result(' true ', text, 'var' => 'hello there!')
|
||||
assert_template_result(' true ', text, { 'var' => 'hello there!' })
|
||||
end
|
||||
|
||||
def test_var_and_long_string_are_equal
|
||||
text = " {% if var == 'hello there!' %} true {% else %} false {% endif %} "
|
||||
assert_template_result(' true ', text, 'var' => 'hello there!')
|
||||
assert_template_result(' true ', text, { 'var' => 'hello there!' })
|
||||
end
|
||||
|
||||
def test_var_and_long_string_are_equal_backwards
|
||||
text = " {% if 'hello there!' == var %} true {% else %} false {% endif %} "
|
||||
assert_template_result(' true ', text, 'var' => 'hello there!')
|
||||
assert_template_result(' true ', text, { 'var' => 'hello there!' })
|
||||
end
|
||||
|
||||
# def test_is_nil
|
||||
@@ -87,27 +87,27 @@ class StatementsTest < Minitest::Test
|
||||
|
||||
def test_is_collection_empty
|
||||
text = ' {% if array == empty %} true {% else %} false {% endif %} '
|
||||
assert_template_result(' true ', text, 'array' => [])
|
||||
assert_template_result(' true ', text, { 'array' => [] })
|
||||
end
|
||||
|
||||
def test_is_not_collection_empty
|
||||
text = ' {% if array == empty %} true {% else %} false {% endif %} '
|
||||
assert_template_result(' false ', text, 'array' => [1, 2, 3])
|
||||
assert_template_result(' false ', text, { 'array' => [1, 2, 3] })
|
||||
end
|
||||
|
||||
def test_nil
|
||||
text = ' {% if var == nil %} true {% else %} false {% endif %} '
|
||||
assert_template_result(' true ', text, 'var' => nil)
|
||||
assert_template_result(' true ', text, { 'var' => nil })
|
||||
|
||||
text = ' {% if var == null %} true {% else %} false {% endif %} '
|
||||
assert_template_result(' true ', text, 'var' => nil)
|
||||
assert_template_result(' true ', text, { 'var' => nil })
|
||||
end
|
||||
|
||||
def test_not_nil
|
||||
text = ' {% if var != nil %} true {% else %} false {% endif %} '
|
||||
assert_template_result(' true ', text, 'var' => 1)
|
||||
assert_template_result(' true ', text, { 'var' => 1 })
|
||||
|
||||
text = ' {% if var != null %} true {% else %} false {% endif %} '
|
||||
assert_template_result(' true ', text, 'var' => 1)
|
||||
assert_template_result(' true ', text, { 'var' => 1 })
|
||||
end
|
||||
end # StatementsTest
|
||||
|
||||
@@ -18,49 +18,193 @@ 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", "{% tablerow char in characters cols:3 %}I WILL NOT BE OUTPUT{% endtablerow %}", 'characters' => '')
|
||||
assert_template_result(
|
||||
"<tr class=\"row1\">\n</tr>\n",
|
||||
"{% tablerow char in characters cols:3 %}I WILL NOT BE OUTPUT{% endtablerow %}",
|
||||
{ 'characters' => '' },
|
||||
)
|
||||
end
|
||||
|
||||
def test_cols_nil_constant_same_as_evaluated_nil_expression
|
||||
expect = "<tr class=\"row1\">\n" \
|
||||
"<td class=\"col1\">false</td>" \
|
||||
"<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:var %}{{ tablerowloop.col_last }}{% endtablerow %}",
|
||||
{ "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
|
||||
template = <<~LIQUID.chomp
|
||||
{% tablerow i in (1...2) %}
|
||||
col: {{ tablerowloop.col }}
|
||||
col0: {{ tablerowloop.col0 }}
|
||||
col_first: {{ tablerowloop.col_first }}
|
||||
col_last: {{ tablerowloop.col_last }}
|
||||
first: {{ tablerowloop.first }}
|
||||
index: {{ tablerowloop.index }}
|
||||
index0: {{ tablerowloop.index0 }}
|
||||
last: {{ tablerowloop.last }}
|
||||
length: {{ tablerowloop.length }}
|
||||
rindex: {{ tablerowloop.rindex }}
|
||||
rindex0: {{ tablerowloop.rindex0 }}
|
||||
row: {{ tablerowloop.row }}
|
||||
{% endtablerow %}
|
||||
LIQUID
|
||||
|
||||
expected_output = <<~OUTPUT
|
||||
<tr class="row1">
|
||||
<td class="col1">
|
||||
col: 1
|
||||
col0: 0
|
||||
col_first: true
|
||||
col_last: false
|
||||
first: true
|
||||
index: 1
|
||||
index0: 0
|
||||
last: false
|
||||
length: 2
|
||||
rindex: 2
|
||||
rindex0: 1
|
||||
row: 1
|
||||
</td><td class="col2">
|
||||
col: 2
|
||||
col0: 1
|
||||
col_first: false
|
||||
col_last: true
|
||||
first: false
|
||||
index: 2
|
||||
index0: 1
|
||||
last: true
|
||||
length: 2
|
||||
rindex: 1
|
||||
rindex0: 0
|
||||
row: 1
|
||||
</td></tr>
|
||||
OUTPUT
|
||||
|
||||
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
|
||||
|
||||
@@ -19,10 +21,10 @@ class UnlessElseTagTest < Minitest::Test
|
||||
end
|
||||
|
||||
def test_unless_in_loop
|
||||
assert_template_result('23', '{% for i in choices %}{% unless i %}{{ forloop.index }}{% endunless %}{% endfor %}', 'choices' => [1, nil, false])
|
||||
assert_template_result('23', '{% for i in choices %}{% unless i %}{{ forloop.index }}{% endunless %}{% endfor %}', { 'choices' => [1, nil, false] })
|
||||
end
|
||||
|
||||
def test_unless_else_in_loop
|
||||
assert_template_result(' TRUE 2 3 ', '{% for i in choices %}{% unless i %} {{ forloop.index }} {% else %} TRUE {% endunless %}{% endfor %}', 'choices' => [1, nil, false])
|
||||
assert_template_result(' TRUE 2 3 ', '{% for i in choices %}{% unless i %} {{ forloop.index }} {% else %} TRUE {% endunless %}{% endfor %}', { 'choices' => [1, nil, false] })
|
||||
end
|
||||
end # UnlessElseTest
|
||||
|
||||
@@ -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))
|
||||
@@ -315,13 +317,11 @@ class TemplateTest < Minitest::Test
|
||||
end
|
||||
|
||||
def test_using_range_literal_works_as_expected
|
||||
t = Template.parse("{% assign foo = (x..y) %}{{ foo }}")
|
||||
result = t.render('x' => 1, 'y' => 5)
|
||||
assert_equal('1..5', result)
|
||||
source = "{% assign foo = (x..y) %}{{ foo }}"
|
||||
assert_template_result("1..5", source, { "x" => 1, "y" => 5 })
|
||||
|
||||
t = Template.parse("{% assign nums = (x..y) %}{% for num in nums %}{{ num }}{% endfor %}")
|
||||
result = t.render('x' => 1, 'y' => 5)
|
||||
assert_equal('12345', result)
|
||||
source = "{% assign nums = (x..y) %}{% for num in nums %}{{ num }}{% endfor %}"
|
||||
assert_template_result("12345", source, { "x" => 1, "y" => 5 })
|
||||
end
|
||||
|
||||
def test_source_string_subclass
|
||||
|
||||
@@ -530,14 +530,9 @@ class TrimModeTest < Minitest::Test
|
||||
end
|
||||
|
||||
def test_pre_trim_blank_preceding_text
|
||||
template = Liquid::Template.parse("\n{%- raw %}{% endraw %}")
|
||||
assert_equal("", template.render)
|
||||
|
||||
template = Liquid::Template.parse("\n{%- if true %}{% endif %}")
|
||||
assert_equal("", template.render)
|
||||
|
||||
template = Liquid::Template.parse("{{ 'B' }} \n{%- if true %}C{% endif %}")
|
||||
assert_equal("BC", template.render)
|
||||
assert_template_result("", "\n{%- raw %}{% endraw %}")
|
||||
assert_template_result("", "\n{%- if true %}{% endif %}")
|
||||
assert_template_result("BC", "{{ 'B' }} \n{%- if true %}C{% endif %}")
|
||||
end
|
||||
|
||||
def test_bug_compatible_pre_trim
|
||||
|
||||
@@ -1,59 +1,87 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'test_helper'
|
||||
require 'timeout'
|
||||
|
||||
class VariableTest < Minitest::Test
|
||||
include Liquid
|
||||
|
||||
def test_simple_variable
|
||||
template = Template.parse(%({{test}}))
|
||||
assert_equal('worked', template.render!('test' => 'worked'))
|
||||
assert_equal('worked wonderfully', template.render!('test' => 'worked wonderfully'))
|
||||
assert_template_result('worked', "{{test}}", { 'test' => 'worked' })
|
||||
assert_template_result('worked wonderfully', "{{test}}", { 'test' => 'worked wonderfully' })
|
||||
end
|
||||
|
||||
def test_variable_render_calls_to_liquid
|
||||
assert_template_result('foobar', '{{ foo }}', 'foo' => ThingWithToLiquid.new)
|
||||
assert_template_result('foobar', '{{ foo }}', { 'foo' => ThingWithToLiquid.new })
|
||||
end
|
||||
|
||||
def test_variable_lookup_calls_to_liquid_value
|
||||
assert_template_result('1', '{{ foo }}', { 'foo' => IntegerDrop.new('1') })
|
||||
assert_template_result('2', '{{ list[foo] }}', { 'foo' => IntegerDrop.new('1'), 'list' => [1, 2, 3] })
|
||||
assert_template_result('one', '{{ list[foo] }}', { 'foo' => IntegerDrop.new('1'), 'list' => { 1 => 'one' } })
|
||||
assert_template_result('Yay', '{{ foo }}', { 'foo' => BooleanDrop.new(true) })
|
||||
assert_template_result('YAY', '{{ foo | upcase }}', { 'foo' => BooleanDrop.new(true) })
|
||||
end
|
||||
|
||||
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('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
|
||||
assert_template_result('One', '{% case foo %}{% when 1 %}One{% endcase %}', { 'foo' => IntegerDrop.new('1') })
|
||||
end
|
||||
|
||||
def test_simple_with_whitespaces
|
||||
template = Template.parse(%( {{ test }} ))
|
||||
assert_equal(' worked ', template.render!('test' => 'worked'))
|
||||
assert_equal(' worked wonderfully ', template.render!('test' => 'worked wonderfully'))
|
||||
assert_template_result(" worked ", " {{ test }} ", { "test" => "worked" })
|
||||
assert_template_result(" worked wonderfully ", " {{ test }} ", { "test" => "worked wonderfully" })
|
||||
end
|
||||
|
||||
def test_expression_with_whitespace_in_square_brackets
|
||||
assert_template_result('result', "{{ a[ 'b' ] }}", 'a' => { 'b' => 'result' })
|
||||
assert_template_result('result', "{{ a[ [ 'b' ] ] }}", 'b' => 'c', 'a' => { 'c' => 'result' })
|
||||
assert_template_result('result', "{{ a[ 'b' ] }}", { 'a' => { 'b' => 'result' } })
|
||||
assert_template_result('result', "{{ a[ [ 'b' ] ] }}", { 'b' => 'c', 'a' => { 'c' => 'result' } })
|
||||
end
|
||||
|
||||
def test_ignore_unknown
|
||||
template = Template.parse(%({{ test }}))
|
||||
assert_equal('', template.render!)
|
||||
assert_template_result("", "{{ test }}")
|
||||
end
|
||||
|
||||
def test_using_blank_as_variable_name
|
||||
template = Template.parse("{% assign foo = blank %}{{ foo }}")
|
||||
assert_equal('', template.render!)
|
||||
assert_template_result("", "{% assign foo = blank %}{{ foo }}")
|
||||
end
|
||||
|
||||
def test_using_empty_as_variable_name
|
||||
template = Template.parse("{% assign foo = empty %}{{ foo }}")
|
||||
assert_equal('', template.render!)
|
||||
assert_template_result("", "{% assign foo = empty %}{{ foo }}")
|
||||
end
|
||||
|
||||
def test_hash_scoping
|
||||
assert_template_result('worked', "{{ test.test }}", 'test' => { 'test' => 'worked' })
|
||||
assert_template_result('worked', "{{ test . test }}", 'test' => { 'test' => 'worked' })
|
||||
assert_template_result('worked', "{{ test.test }}", { 'test' => { 'test' => 'worked' } })
|
||||
assert_template_result('worked', "{{ test . test }}", { 'test' => { 'test' => 'worked' } })
|
||||
end
|
||||
|
||||
def test_false_renders_as_false
|
||||
assert_equal('false', Template.parse("{{ foo }}").render!('foo' => false))
|
||||
assert_equal('false', Template.parse("{{ false }}").render!)
|
||||
assert_template_result("false", "{{ foo }}", { 'foo' => false })
|
||||
assert_template_result("false", "{{ false }}")
|
||||
end
|
||||
|
||||
def test_nil_renders_as_empty_string
|
||||
assert_equal('', Template.parse("{{ nil }}").render!)
|
||||
assert_equal('cat', Template.parse("{{ nil | append: 'cat' }}").render!)
|
||||
assert_template_result("", "{{ nil }}")
|
||||
assert_template_result("cat", "{{ nil | append: 'cat' }}")
|
||||
end
|
||||
|
||||
def test_preset_assigns
|
||||
@@ -94,14 +122,86 @@ class VariableTest < Minitest::Test
|
||||
end
|
||||
|
||||
def test_multiline_variable
|
||||
assert_equal('worked', Template.parse("{{\ntest\n}}").render!('test' => 'worked'))
|
||||
assert_template_result("worked", "{{\ntest\n}}", { "test" => "worked" })
|
||||
end
|
||||
|
||||
def test_render_symbol
|
||||
assert_template_result('bar', '{{ foo }}', 'foo' => :bar)
|
||||
assert_template_result('bar', '{{ foo }}', { 'foo' => :bar })
|
||||
end
|
||||
|
||||
def test_dynamic_find_var
|
||||
assert_template_result('bar', '{{ [key] }}', 'key' => 'foo', 'foo' => 'bar')
|
||||
assert_template_result('bar', '{{ [key] }}', { 'key' => 'foo', 'foo' => 'bar' })
|
||||
end
|
||||
|
||||
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
|
||||
|
||||
+72
-22
@@ -37,23 +37,30 @@ module Minitest
|
||||
module Assertions
|
||||
include Liquid
|
||||
|
||||
def assert_template_result(expected, template, assigns = {}, message = nil)
|
||||
assert_equal(expected, Template.parse(template, line_numbers: true).render!(assigns), message)
|
||||
def assert_template_result(
|
||||
expected, template, assigns = {},
|
||||
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, 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)
|
||||
end
|
||||
|
||||
def assert_template_result_matches(expected, template, assigns = {}, message = nil)
|
||||
return assert_template_result(expected, template, assigns, message) unless expected.is_a?(Regexp)
|
||||
|
||||
assert_match(expected, Template.parse(template, line_numbers: true).render!(assigns), message)
|
||||
end
|
||||
|
||||
def assert_match_syntax_error(match, template, assigns = {})
|
||||
def assert_match_syntax_error(match, template, error_mode: nil)
|
||||
exception = assert_raises(Liquid::SyntaxError) do
|
||||
Template.parse(template, line_numbers: true).render(assigns)
|
||||
Template.parse(template, line_numbers: true, error_mode: error_mode&.to_sym).render
|
||||
end
|
||||
assert_match(match, exception.message)
|
||||
end
|
||||
|
||||
def assert_syntax_error(template, error_mode: nil)
|
||||
assert_match_syntax_error("", template, error_mode: error_mode)
|
||||
end
|
||||
|
||||
def assert_usage_increment(name, times: 1)
|
||||
old_method = Liquid::Usage.method(:increment)
|
||||
calls = 0
|
||||
@@ -72,21 +79,21 @@ module Minitest
|
||||
end
|
||||
|
||||
def with_global_filter(*globals)
|
||||
original_global_filters = Liquid::StrainerFactory.instance_variable_get(:@global_filters)
|
||||
Liquid::StrainerFactory.instance_variable_set(:@global_filters, [])
|
||||
globals.each do |global|
|
||||
Liquid::StrainerFactory.add_global_filter(global)
|
||||
end
|
||||
|
||||
Liquid::StrainerFactory.send(:strainer_class_cache).clear
|
||||
original_global_cache = Liquid::StrainerFactory::GlobalCache
|
||||
Liquid::StrainerFactory.send(:remove_const, :GlobalCache)
|
||||
Liquid::StrainerFactory.const_set(:GlobalCache, Class.new(Liquid::StrainerTemplate))
|
||||
|
||||
globals.each do |global|
|
||||
Liquid::Template.register_filter(global)
|
||||
end
|
||||
yield
|
||||
ensure
|
||||
Liquid::StrainerFactory.send(:strainer_class_cache).clear
|
||||
Liquid::StrainerFactory.instance_variable_set(:@global_filters, original_global_filters)
|
||||
begin
|
||||
yield
|
||||
ensure
|
||||
Liquid::StrainerFactory.send(:remove_const, :GlobalCache)
|
||||
Liquid::StrainerFactory.const_set(:GlobalCache, original_global_cache)
|
||||
Liquid::StrainerFactory.send(:strainer_class_cache).clear
|
||||
end
|
||||
end
|
||||
|
||||
def with_error_mode(mode)
|
||||
@@ -119,6 +126,47 @@ 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 to_s
|
||||
@value.to_s
|
||||
end
|
||||
|
||||
def to_liquid_value
|
||||
@value
|
||||
end
|
||||
end
|
||||
|
||||
class BooleanDrop < Liquid::Drop
|
||||
def initialize(value)
|
||||
super()
|
||||
@value = value
|
||||
end
|
||||
|
||||
def to_liquid_value
|
||||
@value
|
||||
end
|
||||
|
||||
def to_s
|
||||
@value ? "Yay" : "Nay"
|
||||
end
|
||||
end
|
||||
|
||||
class ErrorDrop < Liquid::Drop
|
||||
def standard_error
|
||||
raise Liquid::StandardError, 'standard error'
|
||||
@@ -162,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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -10,8 +10,8 @@ class ConditionUnitTest < Minitest::Test
|
||||
end
|
||||
|
||||
def test_basic_condition
|
||||
assert_equal(false, Condition.new(1, '==', 2).evaluate)
|
||||
assert_equal(true, Condition.new(1, '==', 1).evaluate)
|
||||
assert_equal(false, Condition.new(1, '==', 2).evaluate(Context.new))
|
||||
assert_equal(true, Condition.new(1, '==', 1).evaluate(Context.new))
|
||||
end
|
||||
|
||||
def test_default_operators_evalute_true
|
||||
@@ -67,11 +67,11 @@ class ConditionUnitTest < Minitest::Test
|
||||
end
|
||||
|
||||
def test_hash_compare_backwards_compatibility
|
||||
assert_nil(Condition.new({}, '>', 2).evaluate)
|
||||
assert_nil(Condition.new(2, '>', {}).evaluate)
|
||||
assert_equal(false, Condition.new({}, '==', 2).evaluate)
|
||||
assert_equal(true, Condition.new({ 'a' => 1 }, '==', 'a' => 1).evaluate)
|
||||
assert_equal(true, Condition.new({ 'a' => 2 }, 'contains', 'a').evaluate)
|
||||
assert_nil(Condition.new({}, '>', 2).evaluate(Context.new))
|
||||
assert_nil(Condition.new(2, '>', {}).evaluate(Context.new))
|
||||
assert_equal(false, Condition.new({}, '==', 2).evaluate(Context.new))
|
||||
assert_equal(true, Condition.new({ 'a' => 1 }, '==', 'a' => 1).evaluate(Context.new))
|
||||
assert_equal(true, Condition.new({ 'a' => 2 }, 'contains', 'a').evaluate(Context.new))
|
||||
end
|
||||
|
||||
def test_contains_works_on_arrays
|
||||
@@ -106,30 +106,29 @@ class ConditionUnitTest < Minitest::Test
|
||||
|
||||
def test_or_condition
|
||||
condition = Condition.new(1, '==', 2)
|
||||
|
||||
assert_equal(false, condition.evaluate)
|
||||
assert_equal(false, condition.evaluate(Context.new))
|
||||
|
||||
condition.or(Condition.new(2, '==', 1))
|
||||
|
||||
assert_equal(false, condition.evaluate)
|
||||
assert_equal(false, condition.evaluate(Context.new))
|
||||
|
||||
condition.or(Condition.new(1, '==', 1))
|
||||
|
||||
assert_equal(true, condition.evaluate)
|
||||
assert_equal(true, condition.evaluate(Context.new))
|
||||
end
|
||||
|
||||
def test_and_condition
|
||||
condition = Condition.new(1, '==', 1)
|
||||
|
||||
assert_equal(true, condition.evaluate)
|
||||
assert_equal(true, condition.evaluate(Context.new))
|
||||
|
||||
condition.and(Condition.new(2, '==', 2))
|
||||
|
||||
assert_equal(true, condition.evaluate)
|
||||
assert_equal(true, condition.evaluate(Context.new))
|
||||
|
||||
condition.and(Condition.new(2, '==', 1))
|
||||
|
||||
assert_equal(false, condition.evaluate)
|
||||
assert_equal(false, condition.evaluate(Context.new))
|
||||
end
|
||||
|
||||
def test_should_allow_custom_proc_operator
|
||||
@@ -148,16 +147,34 @@ class ConditionUnitTest < Minitest::Test
|
||||
assert_evaluates_true(VariableLookup.new("one"), '==', VariableLookup.new("another"))
|
||||
end
|
||||
|
||||
def test_default_context_is_deprecated
|
||||
if Gem::Version.new(Liquid::VERSION) >= Gem::Version.new('6.0.0')
|
||||
flunk("Condition#evaluate without a context argument is to be removed")
|
||||
end
|
||||
|
||||
_out, err = capture_io do
|
||||
assert_equal(true, Condition.new(1, '==', 1).evaluate)
|
||||
end
|
||||
|
||||
expected = "DEPRECATION WARNING: Condition#evaluate without a context argument is deprecated" \
|
||||
" and will be removed from Liquid 6.0.0."
|
||||
assert_includes(err.lines.map(&:strip), expected)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
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)
|
||||
|
||||
@@ -8,224 +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 %})),
|
||||
)
|
||||
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 %})),
|
||||
)
|
||||
end
|
||||
|
||||
def test_render_for
|
||||
assert_equal(
|
||||
["test"],
|
||||
visit(%({% render "hai" for test %})),
|
||||
)
|
||||
end
|
||||
|
||||
@@ -236,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
|
||||
|
||||
|
||||
@@ -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,16 +113,88 @@ 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)
|
||||
assert_equal(1, template_factory.count)
|
||||
end
|
||||
|
||||
def test_cache_state_is_shared_for_subcontexts
|
||||
parse_context = Liquid::ParseContext.new
|
||||
shared_file_system = StubFileSystem.new(
|
||||
'my_partial' => 'my shared value',
|
||||
)
|
||||
context = Liquid::Context.build(
|
||||
registers: Liquid::Registers.new(
|
||||
file_system: shared_file_system,
|
||||
),
|
||||
)
|
||||
subcontext = context.new_isolated_subcontext
|
||||
|
||||
assert_equal(subcontext.registers[:cached_partials].object_id, context.registers[:cached_partials].object_id)
|
||||
|
||||
2.times do
|
||||
Liquid::PartialCache.load(
|
||||
'my_partial',
|
||||
context: context,
|
||||
parse_context: parse_context,
|
||||
)
|
||||
|
||||
Liquid::PartialCache.load(
|
||||
'my_partial',
|
||||
context: subcontext,
|
||||
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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
|
||||
require 'test_helper'
|
||||
|
||||
class StaticRegistersUnitTest < Minitest::Test
|
||||
class RegistersUnitTest < Minitest::Test
|
||||
include Liquid
|
||||
|
||||
def test_set
|
||||
static_register = StaticRegisters.new(a: 1, b: 2)
|
||||
static_register = Registers.new(a: 1, b: 2)
|
||||
static_register[:b] = 22
|
||||
static_register[:c] = 33
|
||||
|
||||
@@ -16,13 +16,13 @@ class StaticRegistersUnitTest < Minitest::Test
|
||||
end
|
||||
|
||||
def test_get_missing_key
|
||||
static_register = StaticRegisters.new
|
||||
static_register = Registers.new
|
||||
|
||||
assert_nil(static_register[:missing])
|
||||
end
|
||||
|
||||
def test_delete
|
||||
static_register = StaticRegisters.new(a: 1, b: 2)
|
||||
static_register = Registers.new(a: 1, b: 2)
|
||||
static_register[:b] = 22
|
||||
static_register[:c] = 33
|
||||
|
||||
@@ -37,7 +37,7 @@ class StaticRegistersUnitTest < Minitest::Test
|
||||
end
|
||||
|
||||
def test_fetch
|
||||
static_register = StaticRegisters.new(a: 1, b: 2)
|
||||
static_register = Registers.new(a: 1, b: 2)
|
||||
static_register[:b] = 22
|
||||
static_register[:c] = 33
|
||||
|
||||
@@ -61,7 +61,7 @@ class StaticRegistersUnitTest < Minitest::Test
|
||||
end
|
||||
|
||||
def test_key
|
||||
static_register = StaticRegisters.new(a: 1, b: 2)
|
||||
static_register = Registers.new(a: 1, b: 2)
|
||||
static_register[:b] = 22
|
||||
static_register[:c] = 33
|
||||
|
||||
@@ -72,7 +72,7 @@ class StaticRegistersUnitTest < Minitest::Test
|
||||
end
|
||||
|
||||
def test_static_register_can_be_frozen
|
||||
static_register = StaticRegisters.new(a: 1)
|
||||
static_register = Registers.new(a: 1)
|
||||
|
||||
static_register.static.freeze
|
||||
|
||||
@@ -94,14 +94,14 @@ class StaticRegistersUnitTest < Minitest::Test
|
||||
end
|
||||
|
||||
def test_new_static_retains_static
|
||||
static_register = StaticRegisters.new(a: 1, b: 2)
|
||||
static_register = Registers.new(a: 1, b: 2)
|
||||
static_register[:b] = 22
|
||||
static_register[:c] = 33
|
||||
|
||||
new_static_register = StaticRegisters.new(static_register)
|
||||
new_static_register = Registers.new(static_register)
|
||||
new_static_register[:b] = 222
|
||||
|
||||
newest_static_register = StaticRegisters.new(new_static_register)
|
||||
newest_static_register = Registers.new(new_static_register)
|
||||
newest_static_register[:c] = 333
|
||||
|
||||
assert_equal(1, static_register[:a])
|
||||
@@ -118,11 +118,11 @@ class StaticRegistersUnitTest < Minitest::Test
|
||||
end
|
||||
|
||||
def test_multiple_instances_are_unique
|
||||
static_register_1 = StaticRegisters.new(a: 1, b: 2)
|
||||
static_register_1 = Registers.new(a: 1, b: 2)
|
||||
static_register_1[:b] = 22
|
||||
static_register_1[:c] = 33
|
||||
|
||||
static_register_2 = StaticRegisters.new(a: 10, b: 20)
|
||||
static_register_2 = Registers.new(a: 10, b: 20)
|
||||
static_register_2[:b] = 220
|
||||
static_register_2[:c] = 330
|
||||
|
||||
@@ -138,11 +138,11 @@ class StaticRegistersUnitTest < Minitest::Test
|
||||
end
|
||||
|
||||
def test_initialization_reused_static_same_memory_object
|
||||
static_register_1 = StaticRegisters.new(a: 1, b: 2)
|
||||
static_register_1 = Registers.new(a: 1, b: 2)
|
||||
static_register_1[:b] = 22
|
||||
static_register_1[:c] = 33
|
||||
|
||||
static_register_2 = StaticRegisters.new(static_register_1)
|
||||
static_register_2 = Registers.new(static_register_1)
|
||||
|
||||
assert_equal(1, static_register_2[:a])
|
||||
assert_equal(2, static_register_2[:b])
|
||||
@@ -50,9 +50,10 @@ class StrainerFactoryUnitTest < Minitest::Test
|
||||
|
||||
assert_match(
|
||||
/\ALiquid error: wrong number of arguments \((1 for 0|given 1, expected 0)\)\z/,
|
||||
exception.message
|
||||
exception.message,
|
||||
)
|
||||
assert_equal(exception.backtrace[0].split(':')[0], __FILE__)
|
||||
source = AccessScopeFilters.instance_method(:public_filter).source_location
|
||||
assert_equal(source.map(&:to_s), exception.backtrace[0].split(':')[0..1])
|
||||
end
|
||||
|
||||
def test_strainer_only_invokes_public_filter_methods
|
||||
|
||||
@@ -57,8 +57,8 @@ class StrainerTemplateUnitTest < Minitest::Test
|
||||
end
|
||||
|
||||
def test_add_filter_does_not_raise_when_module_overrides_previously_registered_method
|
||||
strainer = Context.new.strainer
|
||||
with_global_filter do
|
||||
strainer = Context.new.strainer
|
||||
strainer.class.add_filter(PublicMethodOverrideFilter)
|
||||
assert(strainer.class.send(:filter_methods).include?('public_filter'))
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user