Compare commits

..
Author SHA1 Message Date
dependabot[bot]andGitHub e5a314be3c Bump rexml from 3.2.6 to 3.2.8 in the bundler group across 1 directory
Bumps the bundler group with 1 update in the / directory: [rexml](https://github.com/ruby/rexml).


Updates `rexml` from 3.2.6 to 3.2.8
- [Release notes](https://github.com/ruby/rexml/releases)
- [Changelog](https://github.com/ruby/rexml/blob/master/NEWS.md)
- [Commits](https://github.com/ruby/rexml/compare/v3.2.6...v3.2.8)

---
updated-dependencies:
- dependency-name: rexml
  dependency-type: indirect
  dependency-group: bundler
...

Signed-off-by: dependabot[bot] <[email protected]>
2024-05-16 22:26:03 +00:00
Peter ZhuandGitHub 77bc56a1c2 Merge pull request #1792 from Shopify/centralize_ruby_version
Centralize Ruby Version to `.ruby-version`
2024-04-11 11:12:08 -04:00
Jenny Shen 88d013c8da Use latest compatible Bundler of each Ruby version run 2024-04-05 10:33:27 -04:00
Jenny Shen 36c7fc8e07 Remove ruby version definition in CI/CD
it will be read from .ruby-version
2024-04-05 10:33:06 -04:00
Jenny Shen 2b4810006b Remove TargetRubyVersion in RuboCop config
reads from required_ruby_version in RuboCop 1.61+
2024-04-05 10:23:39 -04:00
Jenny Shen fc4f19471e Update rubocop to 1.61.0 2024-04-05 10:23:37 -04:00
Jenny Shen 8596bb2e38 Commit a .ruby-version 2024-04-05 10:23:05 -04:00
Ian Ker-SeymerandGitHub 6bf18775e7 Merge pull request #1791 from Shopify/v5.5.0
Bump to v5.5.0
2024-03-21 15:50:59 -04:00
Ian Ker-Seymer 56a0b7c42b Bump to v5.5.0 2024-03-21 15:48:54 -04:00
Ian Ker-SeymerandGitHub dba733084e Merge pull request #1760 from mtasaka/nil-nil-comparison-fix
change: make nil_safe_casecmp judge compatible for nil-nil comparison
2024-03-21 15:38:13 -04:00
Michael GoandGitHub 4a4fe3c72a Merge pull request #1781 from Shopify/contextualize-before-to-liquid
update variable's context before invoking its to_liquid
2024-02-12 16:58:54 -04:00
Michael Go 4f35b0bc66 update liquid-c 2024-02-12 16:49:52 -04:00
Michael Go a5e5fab82a update variable's context before invoking its to_liquid 2024-02-12 16:48:20 -04:00
Ian Ker-SeymerandGitHub 02ecaab9d1 Merge pull request #1783 from Shopify/contains-encoding
Fallback to binary comparison when `contains` RHS is  UTF8 encoded
2024-01-31 11:14:01 -05:00
Ian Ker-Seymer 1b2b62964e Allow for binary comparison of incompatible strings 2024-01-30 19:03:05 -05:00
Michael GoandGitHub 9b38a15282 Merge pull request #1776 from Shopify/refactor/invalid-encoding-error
add new TemplateEncodingError
2024-01-12 12:38:34 -04:00
Michael Go 7b25b770af add new TemplateEncodingError 2024-01-12 12:17:20 -04:00
Michael GoandGitHub cf76c0bbec Merge pull request #1775 from Shopify/allow-nil-template-source
allow non-string template source
2024-01-12 10:48:30 -04:00
Michael Go 6a0fe3f7e3 convert template source to string to ensure encoding validity 2024-01-10 16:50:31 -04:00
Michael Go 730ad3684a allow nil template source 2024-01-10 16:32:08 -04:00
Michael GoandGitHub 3ac7e470e6 Merge pull request #1774 from Shopify/check-utf8-validity
check template UTF8 validity before parsing
2024-01-10 15:27:49 -04:00
Michael Go 369a6c55e3 check template UTF8 validity before parsing 2024-01-10 18:58:15 +00:00
Michael GoandGitHub f5ed5404b5 Merge pull request #1773 from Shopify/liquid-tag-whitespace-control-with-comment
don't reset Liquid tag's whitespace control from comment tag
2024-01-05 10:54:32 -04:00
Michael Go a3c837687e don't reset Liquid tag's whitespace control from comment tag 2024-01-05 10:21:33 -04:00
Michael GoandGitHub 96a036372d Merge pull request #1770 from Shopify/revert-invalid-comment-body
don't allow invalid syntax inside comment tag
2024-01-02 11:07:05 -04:00
Mamoru TASAKA 4924822c88 change: make nil_safe_casecmp judge compatible for nil-nil comparison
Ruby returns 0 (not nil) for nil <=> nil, i.e. nil and nil are judged
as equal for comparison, and so returns nil_safe_compare .
ref: https://github.com/Shopify/liquid/pull/1476

To make the behavior of nil_safe_casecmp consistent with
nil_safe_compare , change nil_safe_casecmp so that comparison between
nil <=> nil return 0 (equal).

Also change testsuite to reflect this change.

Fixes #1759 .
2023-12-15 15:09:57 +09:00
Michael Go fc9c338682 refactor: rename comment tag unit test 2023-12-14 17:08:26 -04:00
Michael Go 3c5ad7db61 don't allow invalid syntax inside comment tag 2023-12-14 17:01:03 -04:00
Michael GoandGitHub c658bf970a Merge pull request #1769 from Shopify/comment-tag-with-extra-string
fix parsing comment tag with extra string
2023-12-13 14:16:44 -04:00
Michael Go c618ac1c9f fix parsing comment tag with extra string 2023-12-12 16:23:37 -04:00
Ian Ker-SeymerandGitHub 0f0d5d889f Merge pull request #1750 from IevaGraz/fix/custom_tag_rendering
Fix for custom tag rendering
2023-12-10 11:28:50 -05:00
Michael GoandGitHub 11a1f8e673 Merge pull request #1764 from Shopify/fix-comment-tag-whitespace-control
implement whitespace control to comment tag
2023-12-06 14:30:16 -05:00
Michael Go 24d461a9e3 implement whitespace control to comment tag 2023-12-01 16:04:03 -04:00
Michael GoandGitHub cbb422e5d3 Merge pull request #1763 from Shopify/fix-comment-tag-delim-parsing
fix parsing comment tag delimiter
2023-12-01 15:35:32 -04:00
Michael Go bf0f79f36c fix parsing comment tag delimiter 2023-11-30 13:24:54 -04:00
Michael GoandGitHub cf2787791e Merge pull request #1755 from Shopify/non-parsing-comment
Don't parse nodes inside a comment tag
2023-11-29 14:10:20 -04:00
Michael Go 6a5ebb0e85 fix parsing nested comment tag with extra strings 2023-11-11 12:19:44 -04:00
Michael Go 6dafc19b6d fix parsing comment tag delimiter with extra strings 2023-11-10 15:42:54 -04:00
Michael Go 41f65173b0 fix parsing comment block body inside a liquid tag 2023-11-09 16:27:23 -04:00
Michael Go e180535784 allow incomplete tags inside a comment tag 2023-11-08 16:50:41 -04:00
Michael Go a681e73aec refactor comment tag unit test to use test helper 2023-11-08 16:45:11 -04:00
Michael Go 2abf52d546 add a quirky comment tag unit test 2023-11-08 11:23:00 -04:00
eada2b65a2 clean up comment tag body parsing
Co-authored-by: Peter Zhu <[email protected]>
2023-11-08 11:13:13 -04:00
Michael GoandAlex Coco dbf0aa8cd5 don't parse nodes inside a comment tag
Co-authored-by: Alex Coco <[email protected]>
2023-11-07 18:38:13 -04:00
Ieva Grazuleviciute 407a8e5b0f Fix for custom tag rendering 2023-10-20 11:42:54 +03:00
Ateş GöralandGitHub e3dcc75ab5 Merge pull request #1748 from Shopify/atesgoral/update-ci-status-badge
Update CI status badge
2023-10-16 11:23:04 -04:00
Ates Goral ceb7a4237f Update CI status badge 2023-10-16 11:17:24 -04:00
Guillaume MaletteandGitHub 7b60b7fef5 Merge pull request #1746 from Shopify/gm/base64-encoding
[base64] Respect string encoding of input in base64_decode filters
2023-09-29 11:13:18 -04:00
Guillaume Malette 33e1a8ffbc fix style 2023-09-29 09:50:42 -04:00
Guillaume Malette cc47fa8f03 [base64] Respect string encoding of input in base64_decode filters 2023-09-29 09:39:59 -04:00
Adam KlingbaumandGitHub b1b9b9f691 Merge pull request #1739 from Shopify/klingbaum/fix-sum-filter-float-output
Fix `BigDecimal` output in `sum` filter
2023-09-27 13:34:27 -04:00
Adam Klingbaum 75e7725f57 Streamline new tests for sum filter 2023-08-08 16:15:48 +00:00
James PriorandAdam Klingbaum de6d15a73e Test sum floats from properties. 2023-08-08 16:10:04 +00:00
James PriorandAdam Klingbaum 2c5d2be193 Don't render sum filter results in scientific notation. 2023-08-08 16:10:03 +00:00
Samuel DoironandGitHub 0b9318222b Merge pull request #1735 from Shopify/marco-condition-to-liquid-value
Call to_liquid_value when short circuiting conditions
2023-07-19 15:05:00 -03:00
Marco Rudilosso 21d6197533 Call to_liquid_value when short circuiting conditions 2023-07-19 15:22:12 +00:00
Marco Concetto RudilossoandGitHub 5e92b3a89a Merge pull request #1731 from Shopify/allow-recursive-liquid-tags
Allow liquid tag inside liquid tag
2023-07-10 13:25:13 +01:00
Marco Concetto Rudilosso 7f2cf1fe67 Recursively parse for liquid tag 2023-07-07 11:20:34 +01:00
Marco Concetto Rudilosso 10e0fb795e Allow liquid tag inside liquid tag 2023-07-06 10:45:12 +01:00
Michelle VinciandGitHub 546dd9bc06 Merge pull request #1730 from Shopify/capitalize-desc-fix
[Liquid reference]: Update description of `capitalize`
2023-07-05 08:52:26 -07:00
Michelle Vinci 9a77e3e923 update desc 2023-07-05 08:44:06 -07:00
Michael GoandGitHub c44d1d9193 Merge pull request #1727 from Shopify/partial-cache-with-error-mode
include parse mode to partial cache key
2023-06-27 15:06:58 -03:00
Michael Go dd7bbf26bc include error mode to partial cache key 2023-06-23 18:48:07 -03:00
Adam KlingbaumandGitHub cca24a2226 Merge pull request #1726 from Shopify/klingbaum/ensure_sum_filter_calls_#to_liquid
Ensure `sum` filter calls `to_liquid` on evaluated property value
2023-06-22 15:47:36 -04:00
Adam Klingbaum 98ce25cb40 Ensure sum filter calls to_liquid 2023-06-22 16:25:11 +00:00
Adam KlingbaumandGitHub 77293d4524 Merge pull request #1722 from Shopify/klingbaum/sum-filter
Add `sum` filter to standard filters
2023-06-19 15:15:49 -04:00
Adam Klingbaum af66bc8a5f Add sum filter to standard filters 2023-06-19 19:11:05 +00:00
Guillaume MaletteandGitHub 42e5c52336 Merge pull request #1717 from Shopify/gm/fix-raw-trimmode-at-end-of-block
[trim] Fix trimming at end of raw block when specified in the opening tag
2023-05-19 14:08:02 -04:00
Guillaume Malette 649cca1349 [trim] Fix trimming at end of raw block when specified in the opening tag 2023-05-19 11:41:45 -04:00
Ian Ker-SeymerandGitHub 81ed65f2a1 Merge pull request #1688 from Shopify/fix-readme-docs-link
Fix broken documentantion link in README
2023-05-11 18:48:09 -04:00
Jeffrey LoppertandGitHub 6ca06c22b8 Merge pull request #1712 from Shopify/jloppert/update-readme-docs-link
Update Liquid Documentation from Shopify URL
2023-05-10 16:20:40 -04:00
Jeffrey LoppertandGitHub 80bc7ffdf2 Update Liquid Documentation from Shopify URL
Current url https://shopify.dev/docs/docs/api/liquid 404s. Updated to https://shopify.dev/docs/api/liquid
2023-05-10 14:47:16 -04:00
Michael GoandGitHub 48cb643c02 Merge pull request #1695 from Shopify/syntax-error-with-filename
render template name with Liquid Syntax errors
2023-03-02 17:08:27 -04:00
Michael Go 1d97389fb0 render template name with Liquid Syntax errors 2023-03-02 16:50:55 -04:00
Michael GoandGitHub 3ff4170cb0 Merge pull request #1692 from Shopify/error-message-with-filepath
set context's template_name with template.name in render and include tag
2023-02-28 10:57:24 -04:00
Michael Go 24dceef552 set context's template_name with template's name 2023-02-28 10:55:38 -04:00
Michael Go 428c66ffac add name attribute to Template for more flexibility to set context's template name 2023-02-27 17:11:52 -04:00
Michael Go 0fe4a5d144 render error message with actual template path 2023-02-27 11:01:21 -04:00
Dylan Thacker-SmithandGitHub e650dc4195 Revert "Instrument usage of bug with iteration of String with offset or 0 limit (#1667)" (#1677)
This reverts commit c743936a78.
2023-02-22 12:48:18 -05:00
Robertas Godelis a75517e2c7 Fix broken documentantion link in README 2023-02-15 18:27:36 +02:00
Guilherme CarreiroandGitHub 9ab688eada Update rubocop-shopify (2.7.0 -> 2.12.0) (#1687) 2023-02-15 08:40:59 +01:00
Erin MorrisseyandGitHub abef59d129 Merge pull request #1685 from Shopify/update-liquid-paths
Update link paths in docs markup
2023-02-14 10:37:15 -08:00
Erin Morrissey 940c3a4207 update link paths in docs markup 2023-02-14 10:34:15 -08:00
Guilherme CarreiroandGitHub 84a0289ebc Fix CI (as it installs RuboCop 1.45.1 and fails) (#1686) 2023-02-14 12:24:38 +01:00
Michael GoandGitHub 4599e5459f Merge pull request #1684 from Shopify/fix-variable-lookup-parse-timeout
fix variable lookup parse timing out with missing closing bracket
2023-02-02 10:39:46 -04:00
Dylan Thacker-SmithandGitHub 59c445f0e1 Avoid a couple of duplicate assertions (#1675) 2023-02-02 09:31:01 -05:00
Michael Go bd9c3802c8 add variable parser timeout unit tests 2023-02-01 21:15:35 -04:00
Michael Go 2b40850e4a fix variable lookup parse timing out with missing closing bracket 2023-02-01 19:52:30 -04:00
Marco Concetto RudilossoandGitHub 22ded5f304 Merge pull request #1683 from Shopify/marco/raw-tag-whitespace-control
Allow raw tag to be used with whitespace control
2023-02-01 16:51:46 +00:00
Marco Concetto Rudilosso ddc32b7bd8 actually test whitespace stripping 2023-02-01 15:52:34 +00:00
Marco Concetto Rudilosso 74e505f6fa Allow raw tag to be used with whitespace control 2023-02-01 13:49:51 +00:00
Michael GoandGitHub 6a888d4564 Merge pull request #1680 from Shopify/recursively-parse-brackets
recursively parse brackets on variable lookup
2023-01-31 16:29:54 -04:00
Michael Go dd257b3d66 add an unit test for double nest variable lookup 2023-01-30 20:36:14 -04:00
Michael Go 1aaf6ed019 recursively parse brackets on variable lookup 2023-01-30 20:19:35 -04:00
Michael GoandGitHub daf93a83c2 Merge pull request #1676 from Shopify/tablerow-nil-params
raise invalid integer argument error from tablerow
2023-01-18 11:08:04 -04:00
Michael Go e889a9da0b use to_i to parse parameters of tablerow tag 2023-01-17 17:48:16 -04:00
Michael Go 0f11c97623 raise invalid integer argument error from tablerow 2023-01-16 18:53:53 -04:00
72 changed files with 1755 additions and 461 deletions
+3 -2
View File
@@ -12,7 +12,8 @@ jobs:
matrix:
entry:
- { ruby: 2.7, allowed-failure: false } # minimum supported
- { ruby: 3.2, allowed-failure: false } # latest
- { ruby: 3.2, allowed-failure: false }
- { ruby: 3.3, allowed-failure: false } # latest
- { ruby: ruby-head, allowed-failure: true }
name: Test Ruby ${{ matrix.entry.ruby }}
steps:
@@ -21,6 +22,7 @@ jobs:
with:
ruby-version: ${{ matrix.entry.ruby }}
bundler-cache: true
bundler: latest
- run: bundle exec rake
continue-on-error: ${{ matrix.entry.allowed-failure }}
@@ -30,6 +32,5 @@ jobs:
- uses: actions/checkout@v3
- uses: ruby/setup-ruby@v1
with:
ruby-version: 2.7
bundler-cache: true
- run: bundle exec rake memory_profile:run
-2
View File
@@ -4,7 +4,5 @@
pkg
*.rbc
.rvmrc
.ruby-version
Gemfile.lock
.bundle
.byebug_history
+8 -4
View File
@@ -10,8 +10,8 @@ Performance:
Enabled: true
AllCops:
TargetRubyVersion: 2.7
NewCops: disable
SuggestExtensions: false
Exclude:
- 'vendor/bundle/**/*'
@@ -19,6 +19,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'
+1
View File
@@ -0,0 +1 @@
3.3.0
+3 -2
View File
@@ -18,10 +18,11 @@ group :benchmark, :test do
end
group :test do
gem 'rubocop-shopify', '~> 2.7.0', require: false
gem 'rubocop', '~> 1.61.0'
gem 'rubocop-shopify', '~> 2.12.0', require: false
gem 'rubocop-performance', require: false
platform :mri, :truffleruby do
gem 'liquid-c', github: 'Shopify/liquid-c', ref: 'master'
gem 'liquid-c', github: 'Shopify/liquid-c', ref: 'main'
end
end
+75
View File
@@ -0,0 +1,75 @@
GIT
remote: https://github.com/Shopify/liquid-c.git
revision: 5a786af7284df55e013ea20551c4b688d02e8326
ref: main
specs:
liquid-c (4.2.0)
liquid (>= 5.0.1)
PATH
remote: .
specs:
liquid (5.5.0)
GEM
remote: https://rubygems.org/
specs:
ast (2.4.2)
benchmark-ips (2.13.0)
json (2.7.2)
language_server-protocol (3.17.0.3)
memory_profiler (1.0.1)
minitest (5.22.3)
parallel (1.24.0)
parser (3.3.0.5)
ast (~> 2.4.1)
racc
racc (1.7.3)
rainbow (3.1.1)
rake (13.2.1)
regexp_parser (2.9.0)
rexml (3.2.8)
strscan (>= 3.0.9)
rubocop (1.61.0)
json (~> 2.3)
language_server-protocol (>= 3.17.0)
parallel (~> 1.10)
parser (>= 3.3.0.2)
rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 1.8, < 3.0)
rexml (>= 3.2.5, < 4.0)
rubocop-ast (>= 1.30.0, < 2.0)
ruby-progressbar (~> 1.7)
unicode-display_width (>= 2.4.0, < 3.0)
rubocop-ast (1.31.2)
parser (>= 3.3.0.4)
rubocop-performance (1.19.1)
rubocop (>= 1.7.0, < 2.0)
rubocop-ast (>= 0.4.0)
rubocop-shopify (2.12.0)
rubocop (~> 1.44)
ruby-progressbar (1.13.0)
stackprof (0.2.26)
strscan (3.1.0)
terminal-table (3.0.2)
unicode-display_width (>= 1.1.1, < 3)
unicode-display_width (2.5.0)
PLATFORMS
ruby
DEPENDENCIES
benchmark-ips
liquid!
liquid-c!
memory_profiler
minitest
rake (~> 13.0)
rubocop (~> 1.61.0)
rubocop-performance
rubocop-shopify (~> 2.12.0)
stackprof
terminal-table
BUNDLED WITH
2.5.7
+4
View File
@@ -1,5 +1,9 @@
# Liquid Change Log
## 5.5.0 2024-03-21
Please reference the GitHub release for more information.
## 5.4.0 2022-07-29
### Breaking Changes
+2 -2
View File
@@ -1,11 +1,11 @@
[![Build Status](https://api.travis-ci.org/Shopify/liquid.svg?branch=master)](http://travis-ci.org/Shopify/liquid)
[![Build status](https://github.com/Shopify/liquid/actions/workflows/liquid.yml/badge.svg)](https://github.com/Shopify/liquid/actions/workflows/liquid.yml)
[![Inline docs](http://inch-ci.org/github/Shopify/liquid.svg?branch=master)](http://inch-ci.org/github/Shopify/liquid)
# Liquid template engine
* [Contributing guidelines](CONTRIBUTING.md)
* [Version history](History.md)
* [Liquid documentation from Shopify](https://shopify.dev/api/liquid)
* [Liquid documentation from Shopify](https://shopify.dev/docs/api/liquid)
* [Liquid Wiki at GitHub](https://github.com/Shopify/liquid/wiki)
* [Website](http://liquidmarkup.org/)
+9 -5
View File
@@ -30,14 +30,18 @@ class Servlet < LiquidServlet
private
def products_list
[{ 'name' => 'Arbor Draft', 'price' => 39900, 'description' => 'the *arbor draft* is a excellent product' },
{ 'name' => 'Arbor Element', 'price' => 40000, 'description' => 'the *arbor element* rocks for freestyling' },
{ 'name' => 'Arbor Diamond', 'price' => 59900, 'description' => 'the *arbor diamond* is a made up product because im obsessed with arbor and have no creativity' }]
[
{ 'name' => 'Arbor Draft', 'price' => 39900, 'description' => 'the *arbor draft* is a excellent product' },
{ 'name' => 'Arbor Element', 'price' => 40000, 'description' => 'the *arbor element* rocks for freestyling' },
{ 'name' => 'Arbor Diamond', 'price' => 59900, 'description' => 'the *arbor diamond* is a made up product because im obsessed with arbor and have no creativity' }
]
end
def more_products_list
[{ 'name' => 'Arbor Catalyst', 'price' => 39900, 'description' => 'the *arbor catalyst* is an advanced drop-through for freestyle and flatground performance and versatility' },
{ 'name' => 'Arbor Fish', 'price' => 40000, 'description' => 'the *arbor fish* is a compact pin that features an extended wheelbase and time-honored teardrop shape' }]
[
{ 'name' => 'Arbor Catalyst', 'price' => 39900, 'description' => 'the *arbor catalyst* is an advanced drop-through for freestyle and flatground performance and versatility' },
{ 'name' => 'Arbor Fish', 'price' => 40000, 'description' => 'the *arbor fish* is a compact pin that features an extended wheelbase and time-honored teardrop shape' }
]
end
def description
+1 -1
View File
@@ -41,7 +41,7 @@ module Liquid
AnyStartingTag = /#{TagStart}|#{VariableStart}/o
PartialTemplateParser = /#{TagStart}.*?#{TagEnd}|#{VariableStart}.*?#{VariableIncompleteEnd}/om
TemplateParser = /(#{PartialTemplateParser}|#{AnyStartingTag})/om
VariableParser = /\[[^\]]+\]|#{VariableSegment}+\??/o
VariableParser = /\[(?>[^\[\]]+|\g<0>)*\]|#{VariableSegment}+\??/o
RAISE_EXCEPTION_LAMBDA = ->(_e) { raise }
+8 -4
View File
@@ -36,13 +36,17 @@ module Liquid
# @api private
def self.raise_unknown_tag(tag, block_name, block_delimiter, parse_context)
if tag == 'else'
raise SyntaxError, parse_context.locale.t("errors.syntax.unexpected_else",
block_name: block_name)
raise SyntaxError, parse_context.locale.t(
"errors.syntax.unexpected_else",
block_name: block_name,
)
elsif tag.start_with?('end')
raise SyntaxError, parse_context.locale.t("errors.syntax.invalid_delimiter",
raise SyntaxError, parse_context.locale.t(
"errors.syntax.invalid_delimiter",
tag: tag,
block_name: block_name,
block_delimiter: block_delimiter)
block_delimiter: block_delimiter,
)
else
raise SyntaxError, parse_context.locale.t("errors.syntax.unknown_tag", tag: tag)
end
+7
View File
@@ -6,6 +6,7 @@ module Liquid
class BlockBody
LiquidTagToken = /\A\s*(#{TagName})\s*(.*?)\z/o
FullToken = /\A#{TagStart}#{WhitespaceControl}?(\s*)(#{TagName})(\s*)(.*?)#{WhitespaceControl}?#{TagEnd}\z/om
FullTokenPossiblyInvalid = /\A(.*)#{TagStart}#{WhitespaceControl}?\s*(\w+)\s*(.*)?#{WhitespaceControl}?#{TagEnd}\z/om
ContentOfVariable = /\A#{VariableStart}#{WhitespaceControl}?(.*?)#{WhitespaceControl}?#{VariableEnd}\z/om
WhitespaceOrNothing = /\A\s*\z/
TAGSTART = "{%"
@@ -45,6 +46,12 @@ module Liquid
end
tag_name = Regexp.last_match(1)
markup = Regexp.last_match(2)
if tag_name == 'liquid'
parse_context.line_number -= 1
next parse_liquid_tag(markup, parse_context)
end
unless (tag = registered_tags[tag_name])
# end parsing if we reach an unknown tag and let the caller decide
# determine how to proceed
+9 -4
View File
@@ -24,6 +24,9 @@ module Liquid
else
false
end
rescue Encoding::CompatibilityError
# "✅".b.include?("✅") raises Encoding::CompatibilityError despite being materially equal
left.b.include?(right.b)
end,
}
@@ -69,9 +72,9 @@ module Liquid
case condition.child_relation
when :or
break if result
break if Liquid::Utils.to_liquid_value(result)
when :and
break unless result
break unless Liquid::Utils.to_liquid_value(result)
else
break
end
@@ -159,8 +162,10 @@ module Liquid
class ParseTreeVisitor < Liquid::ParseTreeVisitor
def children
[
@node.left, @node.right,
@node.child_condition, @node.attachment
@node.left,
@node.right,
@node.child_condition,
@node.attachment
].compact
end
end
+8 -4
View File
@@ -27,7 +27,7 @@ module Liquid
@environments.flatten!
@static_environments = [static_environments].flatten(1).freeze
@scopes = [(outer_scope || {})]
@scopes = [outer_scope || {}]
@registers = registers.is_a?(Registers) ? registers : Registers.new(registers)
@errors = []
@partial = false
@@ -144,7 +144,7 @@ module Liquid
self.class.build(
resource_limits: resource_limits,
static_environments: static_environments,
registers: Registers.new(registers)
registers: Registers.new(registers),
).tap do |subcontext|
subcontext.base_scope_depth = base_scope_depth + 1
subcontext.exception_renderer = exception_renderer
@@ -197,10 +197,14 @@ module Liquid
try_variable_find_in_environments(key, raise_on_not_found: raise_on_not_found)
end
variable = variable.to_liquid
# update variable's context before invoking #to_liquid
variable.context = self if variable.respond_to?(:context=)
variable
liquid_variable = variable.to_liquid
liquid_variable.context = self if variable != liquid_variable && liquid_variable.respond_to?(:context=)
liquid_variable
end
def lookup_and_evaluate(obj, key, raise_on_not_found: true)
+16 -15
View File
@@ -40,19 +40,20 @@ module Liquid
end
end
ArgumentError = Class.new(Error)
ContextError = Class.new(Error)
FileSystemError = Class.new(Error)
StandardError = Class.new(Error)
SyntaxError = Class.new(Error)
StackLevelError = Class.new(Error)
MemoryError = Class.new(Error)
ZeroDivisionError = Class.new(Error)
FloatDomainError = Class.new(Error)
UndefinedVariable = Class.new(Error)
UndefinedDropMethod = Class.new(Error)
UndefinedFilter = Class.new(Error)
MethodOverrideError = Class.new(Error)
DisabledError = Class.new(Error)
InternalError = Class.new(Error)
ArgumentError = Class.new(Error)
ContextError = Class.new(Error)
FileSystemError = Class.new(Error)
StandardError = Class.new(Error)
SyntaxError = Class.new(Error)
StackLevelError = Class.new(Error)
MemoryError = Class.new(Error)
ZeroDivisionError = Class.new(Error)
FloatDomainError = Class.new(Error)
UndefinedVariable = Class.new(Error)
UndefinedDropMethod = Class.new(Error)
UndefinedFilter = Class.new(Error)
MethodOverrideError = Class.new(Error)
DisabledError = Class.new(Error)
InternalError = Class.new(Error)
TemplateEncodingError = Class.new(Error)
end
+4 -1
View File
@@ -3,7 +3,10 @@
module Liquid
class Expression
LITERALS = {
nil => nil, 'nil' => nil, 'null' => nil, '' => nil,
nil => nil,
'nil' => nil,
'null' => nil,
'' => nil,
'true' => true,
'false' => false,
'blank' => '',
+1 -1
View File
@@ -5,7 +5,7 @@ module Liquid
# @liquid_type object
# @liquid_name forloop
# @liquid_summary
# Information about a parent [`for` loop](/api/liquid/tags/for).
# Information about a parent [`for` loop](/docs/api/liquid/tags/for).
class ForloopDrop < Drop
def initialize(name, length, parentloop)
@name = name
+1
View File
@@ -15,6 +15,7 @@
include: "Error in tag 'include' - Valid syntax: include '[template]' (with|for) [object|collection]"
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}"
invalid_template_encoding: "Invalid template encoding"
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"
+12 -3
View File
@@ -4,7 +4,8 @@ module Liquid
class PartialCache
def self.load(template_name, context:, parse_context:)
cached_partials = context.registers[:cached_partials]
cached = cached_partials[template_name]
cache_key = "#{template_name}:#{parse_context.error_mode}"
cached = cached_partials[cache_key]
return cached if cached
file_system = context.registers[:file_system]
@@ -15,8 +16,16 @@ module Liquid
template_factory = context.registers[:template_factory]
template = template_factory.for(template_name)
partial = template.parse(source, parse_context)
cached_partials[template_name] = partial
begin
partial = template.parse(source, parse_context)
rescue Liquid::Error => e
e.template_name = template&.name || template_name
raise e
end
partial.name ||= template_name
cached_partials[cache_key] = partial
ensure
parse_context.partial = false
end
+55 -8
View File
@@ -25,10 +25,23 @@ module Liquid
STRIP_HTML_BLOCKS = Regexp.union(
%r{<script.*?</script>}m,
/<!--.*?-->/m,
%r{<style.*?</style>}m
%r{<style.*?</style>}m,
)
STRIP_HTML_TAGS = /<.*?>/m
class << self
def try_coerce_encoding(input, encoding:)
original_encoding = input.encoding
if input.encoding != encoding
input.force_encoding(encoding)
unless input.valid_encoding?
input.force_encoding(original_encoding)
end
end
input
end
end
# @liquid_public_docs
# @liquid_type filter
# @liquid_category array
@@ -69,7 +82,7 @@ module Liquid
# @liquid_type filter
# @liquid_category string
# @liquid_summary
# Capitalizes the first word in a string.
# Capitalizes the first word in a string and downcases the remaining characters.
# @liquid_syntax string | capitalize
# @liquid_return [string]
def capitalize(input)
@@ -150,7 +163,8 @@ module Liquid
# @liquid_syntax string | base64_decode
# @liquid_return [string]
def base64_decode(input)
Base64.strict_decode64(input.to_s)
input = input.to_s
StandardFilters.try_coerce_encoding(Base64.strict_decode64(input), encoding: input.encoding)
rescue ::ArgumentError
raise Liquid::ArgumentError, "invalid base64 provided to base64_decode"
end
@@ -174,7 +188,8 @@ module Liquid
# @liquid_syntax string | base64_url_safe_decode
# @liquid_return [string]
def base64_url_safe_decode(input)
Base64.urlsafe_decode64(input.to_s)
input = input.to_s
StandardFilters.try_coerce_encoding(Base64.urlsafe_decode64(input), encoding: input.encoding)
rescue ::ArgumentError
raise Liquid::ArgumentError, "invalid base64 provided to base64_url_safe_decode"
end
@@ -615,7 +630,7 @@ module Liquid
# @liquid_description
# > Note:
# > The `concat` filter won't filter out duplicates. If you want to remove duplicates, then you need to use the
# > [`uniq` filter](/api/liquid/filters/uniq).
# > [`uniq` filter](/docs/api/liquid/filters/uniq).
# @liquid_syntax array | concat: array
# @liquid_return [array[untyped]]
def concat(input, array)
@@ -857,9 +872,9 @@ module Liquid
# @liquid_summary
# Sets a default value for any variable whose value is one of the following:
#
# - [`empty`](/api/liquid/basics#empty)
# - [`false`](/api/liquid/basics#truthy-and-falsy)
# - [`nil`](/api/liquid/basics#nil)
# - [`empty`](/docs/api/liquid/basics#empty)
# - [`false`](/docs/api/liquid/basics#truthy-and-falsy)
# - [`nil`](/docs/api/liquid/basics#nil)
# @liquid_syntax variable | default: variable
# @liquid_return [untyped]
# @liquid_optional_param allow_false [boolean] Whether to use false values instead of the default.
@@ -869,6 +884,36 @@ module Liquid
false_check || (input.respond_to?(:empty?) && input.empty?) ? default_value : input
end
# @liquid_public_docs
# @liquid_type filter
# @liquid_category array
# @liquid_summary
# Returns the sum of all elements in an array.
# @liquid_syntax array | sum
# @liquid_return [number]
def sum(input, property = nil)
ary = InputIterator.new(input, context)
return 0 if ary.empty?
values_for_sum = ary.map do |item|
if property.nil?
item
elsif item.respond_to?(:[])
item[property]
else
0
end
rescue TypeError
raise_property_error(property)
end
result = InputIterator.new(values_for_sum, context).sum do |item|
Utils.to_number(item)
end
result.is_a?(BigDecimal) ? result.to_f : result
end
private
attr_reader :context
@@ -899,6 +944,8 @@ module Liquid
def nil_safe_casecmp(a, b)
if !a.nil? && !b.nil?
a.to_s.casecmp(b.to_s)
elsif a.nil? && b.nil?
0
else
a.nil? ? 1 : -1
end
+1 -1
View File
@@ -5,7 +5,7 @@ module Liquid
# @liquid_type object
# @liquid_name tablerowloop
# @liquid_summary
# Information about a parent [`tablerow` loop](/api/liquid/tags/tablerow).
# Information about a parent [`tablerow` loop](/docs/api/liquid/tags/tablerow).
class TablerowloopDrop < Drop
def initialize(length, cols)
@length = length
+2 -1
View File
@@ -54,7 +54,8 @@ module Liquid
# of the `render_to_output_buffer` method will become the default and the `render`
# method will be removed.
def render_to_output_buffer(context, output)
output << render(context)
render_result = render(context)
output << render_result if render_result
output
end
+1 -1
View File
@@ -8,7 +8,7 @@ module Liquid
# @liquid_summary
# Creates a new variable.
# @liquid_description
# You can create variables of any [basic type](/api/liquid/basics#types), [object](/api/liquid/objects), or object property.
# You can create variables of any [basic type](/docs/api/liquid/basics#types), [object](/docs/api/liquid/objects), or object property.
# @liquid_syntax
# {% assign variable_name = value %}
# @liquid_syntax_keyword variable_name The name of the variable being created.
+1 -1
View File
@@ -15,7 +15,7 @@ module Liquid
# @liquid_category iteration
# @liquid_name break
# @liquid_summary
# Stops a [`for` loop](/api/liquid/tags/for) from iterating.
# Stops a [`for` loop](/docs/api/liquid/tags/for) from iterating.
# @liquid_syntax
# {% break %}
class Break < Tag
+1 -1
View File
@@ -77,7 +77,7 @@ module Liquid
end
result = Liquid::Utils.to_liquid_value(
block.evaluate(context)
block.evaluate(context),
)
if result
+59
View File
@@ -25,6 +25,65 @@ module Liquid
def blank?
true
end
private
def parse_body(body, tokenizer)
if parse_context.depth >= MAX_DEPTH
raise StackLevelError, "Nesting too deep"
end
parse_context.depth += 1
comment_tag_depth = 1
begin
# Consume tokens without creating child nodes.
# The children tag doesn't require to be a valid Liquid except the comment and raw tag.
# The child comment and raw tag must be closed.
while (token = tokenizer.send(:shift))
tag_name = if tokenizer.for_liquid_tag
next if token.empty? || token.match?(BlockBody::WhitespaceOrNothing)
tag_name_match = BlockBody::LiquidTagToken.match(token)
next if tag_name_match.nil?
tag_name_match[1]
else
token =~ BlockBody::FullToken
Regexp.last_match(2)
end
case tag_name
when "raw"
parse_raw_tag_body(tokenizer)
when "comment"
comment_tag_depth += 1
when "endcomment"
comment_tag_depth -= 1
end
if comment_tag_depth.zero?
parse_context.trim_whitespace = (token[-3] == WhitespaceControl) unless tokenizer.for_liquid_tag
return false
end
end
raise_tag_never_closed(block_name)
ensure
parse_context.depth -= 1
end
false
end
def parse_raw_tag_body(tokenizer)
while (token = tokenizer.send(:shift))
return if token =~ BlockBody::FullTokenPossiblyInvalid && "endraw" == Regexp.last_match(2)
end
raise_tag_never_closed("raw")
end
end
Template.register_tag('comment', Comment)
+1 -1
View File
@@ -6,7 +6,7 @@ module Liquid
# @liquid_category iteration
# @liquid_name continue
# @liquid_summary
# Causes a [`for` loop](/api/liquid/tags/for) to skip to the next iteration.
# Causes a [`for` loop](/docs/api/liquid/tags/for) to skip to the next iteration.
# @liquid_syntax
# {% continue %}
class Continue < Tag
+1 -1
View File
@@ -6,7 +6,7 @@ module Liquid
# @liquid_category iteration
# @liquid_name cycle
# @liquid_summary
# Loops through a group of strings and outputs them one at a time for each iteration of a [`for` loop](/api/liquid/tags/for).
# Loops through a group of strings and outputs them one at a time for each iteration of a [`for` loop](/docs/api/liquid/tags/for).
# @liquid_description
# The `cycle` tag must be used inside a `for` loop.
#
+2 -2
View File
@@ -12,8 +12,8 @@ module Liquid
# or [section](/themes/architecture/sections) file that they're created in. However, the variable is shared across
# [snippets](/themes/architecture#snippets) included in the file.
#
# Similarly, variables that are created with `decrement` are independent from those created with [`assign`](/api/liquid/tags/assign)
# and [`capture`](/api/liquid/tags/capture). However, `decrement` and [`increment`](/api/liquid/tags/increment) share
# Similarly, variables that are created with `decrement` are independent from those created with [`assign`](/docs/api/liquid/tags/assign)
# and [`capture`](/docs/api/liquid/tags/capture). However, `decrement` and [`increment`](/docs/api/liquid/tags/increment) share
# variables.
# @liquid_syntax
# {% decrement variable_name %}
+2 -2
View File
@@ -9,10 +9,10 @@ module Liquid
# Outputs an expression.
# @liquid_description
# Using the `echo` tag is the same as wrapping an expression in curly brackets (`{{` and `}}`). However, unlike the curly
# bracket method, you can use the `echo` tag inside [`liquid` tags](/api/liquid/tags/liquid).
# bracket method, you can use the `echo` tag inside [`liquid` tags](/docs/api/liquid/tags/liquid).
#
# > Tip:
# > You can use [filters](/api/liquid/filters) on expressions inside `echo` tags.
# > You can use [filters](/docs/api/liquid/filters) on expressions inside `echo` tags.
# @liquid_syntax
# {% liquid
# echo expression
+2 -2
View File
@@ -9,10 +9,10 @@ module Liquid
# Renders an expression for every item in an array.
# @liquid_description
# You can do a maximum of 50 iterations with a `for` loop. If you need to iterate over more than 50 items, then use the
# [`paginate` tag](/api/liquid/tags/paginate) to split the items over multiple pages.
# [`paginate` tag](/docs/api/liquid/tags/paginate) to split the items over multiple pages.
#
# > Tip:
# > Every `for` loop has an associated [`forloop` object](/api/liquid/objects/forloop) with information about the loop.
# > Every `for` loop has an associated [`forloop` object](/docs/api/liquid/objects/forloop) with information about the loop.
# @liquid_syntax
# {% for variable in array %}
# expression
+2 -2
View File
@@ -53,7 +53,7 @@ module Liquid
def render_to_output_buffer(context, output)
@blocks.each do |block|
result = Liquid::Utils.to_liquid_value(
block.evaluate(context)
block.evaluate(context),
)
if result
@@ -111,7 +111,7 @@ module Liquid
def parse_binary_comparisons(p)
condition = parse_comparison(p)
first_condition = condition
while (op = (p.id?('and') || p.id?('or')))
while (op = p.id?('and') || p.id?('or'))
child_condition = parse_comparison(p)
condition.send(op, child_condition)
condition = child_condition
+7 -5
View File
@@ -8,7 +8,7 @@ module Liquid
# @liquid_summary
# Renders a [snippet](/themes/architecture#snippets).
# @liquid_description
# Inside the snippet, you can access and alter variables that are [created](/api/liquid/tags/variable-tags) outside of the
# Inside the snippet, you can access and alter variables that are [created](/docs/api/liquid/tags/variable-tags) outside of the
# snippet.
# @liquid_syntax
# {% include 'filename' %}
@@ -16,7 +16,7 @@ module Liquid
# @liquid_deprecated
# Deprecated because the way that variables are handled reduces performance and makes code harder to both read and maintain.
#
# The `include` tag has been replaced by [`render`](/api/liquid/tags/render).
# The `include` tag has been replaced by [`render`](/docs/api/liquid/tags/render).
class Include < Tag
prepend Tag::Disableable
@@ -57,7 +57,7 @@ module Liquid
partial = PartialCache.load(
template_name,
context: context,
parse_context: parse_context
parse_context: parse_context,
)
context_variable_name = @alias_name || template_name.split('/').last
@@ -70,9 +70,11 @@ module Liquid
old_template_name = context.template_name
old_partial = context.partial
begin
context.template_name = template_name
context.partial = true
context.template_name = partial.name
context.partial = true
context.stack do
@attributes.each do |key, value|
context[key] = context.evaluate(value)
+2 -2
View File
@@ -12,8 +12,8 @@ module Liquid
# or [section](/themes/architecture/sections) file that they're created in. However, the variable is shared across
# [snippets](/themes/architecture#snippets) included in the file.
#
# Similarly, variables that are created with `increment` are independent from those created with [`assign`](/api/liquid/tags/assign)
# and [`capture`](/api/liquid/tags/capture). However, `increment` and [`decrement`](/api/liquid/tags/decrement) share
# Similarly, variables that are created with `increment` are independent from those created with [`assign`](/docs/api/liquid/tags/assign)
# and [`capture`](/docs/api/liquid/tags/capture). However, `increment` and [`decrement`](/docs/api/liquid/tags/decrement) share
# variables.
# @liquid_syntax
# {% increment variable_name %}
+2 -2
View File
@@ -14,7 +14,6 @@ module Liquid
# @liquid_syntax_keyword expression The expression to be output without being rendered.
class Raw < Block
Syntax = /\A\s*\z/
FullTokenPossiblyInvalid = /\A(.*)#{TagStart}\s*(\w+)\s*(.*)?#{TagEnd}\z/om
def initialize(tag_name, markup, parse_context)
super
@@ -25,7 +24,8 @@ module Liquid
def parse(tokens)
@body = +''
while (token = tokens.shift)
if token =~ FullTokenPossiblyInvalid && block_delimiter == Regexp.last_match(2)
if token =~ BlockBody::FullTokenPossiblyInvalid && block_delimiter == Regexp.last_match(2)
parse_context.trim_whitespace = (token[-3] == WhitespaceControl)
@body << Regexp.last_match(1) if Regexp.last_match(1) != ""
return
end
+7 -7
View File
@@ -8,19 +8,19 @@ module Liquid
# @liquid_summary
# Renders a [snippet](/themes/architecture#snippets) or [app block](/themes/architecture/sections/section-schema#render-app-blocks).
# @liquid_description
# Inside snippets and app blocks, you can't directly access variables that are [created](/api/liquid/tags/variable-tags) outside
# of the snippet or app block. However, you can [specify variables as parameters](/api/liquid/tags/render#render-passing-variables-to-a-snippet)
# Inside snippets and app blocks, you can't directly access variables that are [created](/docs/api/liquid/tags/variable-tags) outside
# of the snippet or app block. However, you can [specify variables as parameters](/docs/api/liquid/tags/render#render-passing-variables-to-a-snippet)
# to pass outside variables to snippets.
#
# While you can't directly access created variables, you can access global objects, as well as any objects that are
# directly accessible outside the snippet or app block. For example, a snippet or app block inside the [product template](/themes/architecture/templates/product)
# can access the [`product` object](/api/liquid/objects/product), and a snippet or app block inside a [section](/themes/architecture/sections)
# can access the [`section` object](/api/liquid/objects/section).
# can access the [`product` object](/docs/api/liquid/objects/product), and a snippet or app block inside a [section](/themes/architecture/sections)
# can access the [`section` object](/docs/api/liquid/objects/section).
#
# Outside a snippet or app block, you can't access variables created inside the snippet or app block.
#
# > Note:
# > When you render a snippet using the `render` tag, you can't use the [`include` tag](/api/liquid/tags/include)
# > When you render a snippet using the `render` tag, you can't use the [`include` tag](/docs/api/liquid/tags/include)
# > inside the snippet.
# @liquid_syntax
# {% render 'filename' %}
@@ -69,14 +69,14 @@ module Liquid
partial = PartialCache.load(
template_name,
context: context,
parse_context: parse_context
parse_context: parse_context,
)
context_variable_name = @alias_name || template_name.split('/').last
render_partial_func = ->(var, forloop) {
inner_context = context.new_isolated_subcontext
inner_context.template_name = template_name
inner_context.template_name = partial.name
inner_context.partial = true
inner_context['forloop'] = forloop if forloop
+12 -4
View File
@@ -11,7 +11,7 @@ module Liquid
# The `tablerow` tag must be wrapped in HTML `<table>` and `</table>` tags.
#
# > Tip:
# > Every `tablerow` loop has an associated [`tablerowloop` object](/api/liquid/objects/tablerowloop) with information about the loop.
# > Every `tablerow` loop has an associated [`tablerowloop` object](/docs/api/liquid/objects/tablerowloop) with information about the loop.
# @liquid_syntax
# {% tablerow variable in array %}
# expression
@@ -45,13 +45,13 @@ module Liquid
def render_to_output_buffer(context, output)
(collection = context.evaluate(@collection_name)) || (return '')
from = @attributes.key?('offset') ? context.evaluate(@attributes['offset']).to_i : 0
to = @attributes.key?('limit') ? from + context.evaluate(@attributes['limit']).to_i : nil
from = @attributes.key?('offset') ? to_integer(context.evaluate(@attributes['offset'])) : 0
to = @attributes.key?('limit') ? from + to_integer(context.evaluate(@attributes['limit'])) : nil
collection = Utils.slice_collection(collection, from, to)
length = collection.length
cols = @attributes.key?('cols') ? context.evaluate(@attributes['cols']).to_i : length
cols = @attributes.key?('cols') ? to_integer(context.evaluate(@attributes['cols'])) : length
output << "<tr class=\"row1\">\n"
context.stack do
@@ -82,6 +82,14 @@ module Liquid
super + @node.attributes.values + [@node.collection_name]
end
end
private
def to_integer(value)
value.to_i
rescue NoMethodError
raise Liquid::ArgumentError, "invalid integer"
end
end
Template.register_tag('tablerow', TableRow)
+3 -3
View File
@@ -11,7 +11,7 @@ module Liquid
# Renders an expression unless a specific condition is `true`.
# @liquid_description
# > Tip:
# > Similar to the [`if` tag](/api/liquid/tags/if), you can use `elsif` to add more conditions to an `unless` tag.
# > Similar to the [`if` tag](/docs/api/liquid/tags/if), you can use `elsif` to add more conditions to an `unless` tag.
# @liquid_syntax
# {% unless condition %}
# expression
@@ -23,7 +23,7 @@ module Liquid
# First condition is interpreted backwards ( if not )
first_block = @blocks.first
result = Liquid::Utils.to_liquid_value(
first_block.evaluate(context)
first_block.evaluate(context),
)
unless result
@@ -33,7 +33,7 @@ module Liquid
# After the first condition unless works just like if
@blocks[1..-1].each do |block|
result = Liquid::Utils.to_liquid_value(
block.evaluate(context)
block.evaluate(context),
)
if result
+9 -1
View File
@@ -15,7 +15,7 @@ module Liquid
# template.render('user_name' => 'bob')
#
class Template
attr_accessor :root
attr_accessor :root, :name
attr_reader :resource_limits, :warnings
class TagRegistry
@@ -107,6 +107,12 @@ module Liquid
# Returns self for easy chaining
def parse(source, options = {})
parse_context = configure_options(options)
source = source.to_s.to_str
unless source.valid_encoding?
raise TemplateEncodingError, parse_context.locale.t("errors.syntax.invalid_template_encoding")
end
tokenizer = parse_context.new_tokenizer(source, start_line_number: @line_numbers && 1)
@root = Document.parse(tokenizer, parse_context)
self
@@ -189,6 +195,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 || +'')
+1 -1
View File
@@ -5,7 +5,7 @@ module Liquid
attr_reader :line_number, :for_liquid_tag
def initialize(source, line_numbers = false, line_number: nil, for_liquid_tag: false)
@source = source.to_s.to_str
@source = source
@line_number = line_number || (line_numbers ? 1 : nil)
@for_liquid_tag = for_liquid_tag
@offset = 0
+1 -1
View File
@@ -2,5 +2,5 @@
# frozen_string_literal: true
module Liquid
VERSION = "5.4.0"
VERSION = "5.5.0"
end
-5
View File
@@ -49,8 +49,3 @@ module Database
end
end
end
if __FILE__ == $PROGRAM_NAME
p(Database.tables['collections']['frontpage'].keys)
# p Database.tables['blog']['articles']
end
+2 -2
View File
@@ -45,8 +45,8 @@ class Paginate < Liquid::Block
pagination['items'] = collection_size
pagination['pages'] = page_count - 1
pagination['previous'] = link('&laquo; Previous', current_page - 1) unless 1 >= current_page
pagination['next'] = link('Next &raquo;', current_page + 1) unless page_count <= current_page + 1
pagination['previous'] = link('&laquo; Previous', current_page - 1) if 1 < current_page
pagination['next'] = link('Next &raquo;', current_page + 1) if page_count > current_page + 1
pagination['parts'] = []
hellip_break = false
+17 -8
View File
@@ -14,19 +14,25 @@ class AssignTest < Minitest::Test
end
def test_assigned_variable
assert_template_result('.foo.',
assert_template_result(
'.foo.',
'{% assign foo = values %}.{{ foo[0] }}.',
{ 'values' => %w(foo bar baz) })
{ 'values' => %w(foo bar baz) },
)
assert_template_result('.bar.',
assert_template_result(
'.bar.',
'{% assign foo = values %}.{{ foo[1] }}.',
{ 'values' => %w(foo bar baz) })
{ 'values' => %w(foo bar baz) },
)
end
def test_assign_with_filter
assert_template_result('.bar.',
assert_template_result(
'.bar.',
'{% assign foo = values | split: "," %}.{{ foo[1] }}.',
{ 'values' => "foo,bar,baz" })
{ 'values' => "foo,bar,baz" },
)
end
def test_assign_syntax_error
@@ -34,8 +40,11 @@ class AssignTest < Minitest::Test
end
def test_assign_uses_error_mode
assert_match_syntax_error("Expected dotdot but found pipe in ",
"{% assign foo = ('X' | downcase) %}", error_mode: :strict)
assert_match_syntax_error(
"Expected dotdot but found pipe in ",
"{% assign foo = ('X' | downcase) %}",
error_mode: :strict,
)
assert_template_result("", "{% assign foo = ('X' | downcase) %}", error_mode: :lax)
end
+19 -8
View File
@@ -57,9 +57,11 @@ class BlankTest < Minitest::Test
def test_nested_blocks_are_blank_but_only_if_all_children_are
assert_template_result("", wrap(wrap(" ")))
assert_template_result("\n but this is not " * (N + 1),
assert_template_result(
"\n but this is not " * (N + 1),
wrap('{% if true %} {% comment %} this is blank {% endcomment %} {% endif %}
{% if true %} but this is not {% endif %}'))
{% if true %} but this is not {% endif %}'),
)
end
def test_assigns_are_blank
@@ -89,12 +91,21 @@ class BlankTest < Minitest::Test
end
def test_include_is_blank
assert_template_result("foobar" * (N + 1), wrap("{% include 'foobar' %}"),
partials: { 'foobar' => 'foobar' })
assert_template_result(" foobar " * (N + 1), wrap("{% include ' foobar ' %}"),
partials: { ' foobar ' => ' foobar ' })
assert_template_result(" " * (N + 1), wrap(" {% include ' ' %} "),
partials: { ' ' => ' ' })
assert_template_result(
"foobar" * (N + 1),
wrap("{% include 'foobar' %}"),
partials: { 'foobar' => 'foobar' },
)
assert_template_result(
" foobar " * (N + 1),
wrap("{% include ' foobar ' %}"),
partials: { ' foobar ' => ' foobar ' },
)
assert_template_result(
" " * (N + 1),
wrap(" {% include ' ' %} "),
partials: { ' ' => ' ' },
)
end
def test_case_is_blank
+73 -22
View File
@@ -36,6 +36,24 @@ class Category
end
end
class ProductsDrop < Liquid::Drop
def initialize(products)
@products = products
end
def size
@products.size
end
def to_liquid
if @context["forloop"]
@products.first(@context["forloop"].length)
else
@products
end
end
end
class CategoryDrop < Liquid::Drop
attr_accessor :category, :context
@@ -121,14 +139,23 @@ class ContextTest < Minitest::Test
end
def test_length_query
assert_template_result("true", "{% if numbers.size == 4 %}true{% endif %}",
{ "numbers" => [1, 2, 3, 4] })
assert_template_result(
"true",
"{% if numbers.size == 4 %}true{% endif %}",
{ "numbers" => [1, 2, 3, 4] },
)
assert_template_result("true", "{% if numbers.size == 4 %}true{% endif %}",
{ "numbers" => { 1 => 1, 2 => 2, 3 => 3, 4 => 4 } })
assert_template_result(
"true",
"{% if numbers.size == 4 %}true{% endif %}",
{ "numbers" => { 1 => 1, 2 => 2, 3 => 3, 4 => 4 } },
)
assert_template_result("true", "{% if numbers.size == 1000 %}true{% endif %}",
{ "numbers" => { 1 => 1, 2 => 2, 3 => 3, 4 => 4, 'size' => 1000 } })
assert_template_result(
"true",
"{% if numbers.size == 1000 %}true{% endif %}",
{ "numbers" => { 1 => 1, 2 => 2, 3 => 3, 4 => 4, 'size' => 1000 } },
)
end
def test_hyphenated_variable
@@ -228,12 +255,14 @@ class ContextTest < Minitest::Test
end
def test_hash_to_array_transition
assigns = { 'colors' => {
'Blue' => ['003366', '336699', '6699CC', '99CCFF'],
'Green' => ['003300', '336633', '669966', '99CC99'],
'Yellow' => ['CC9900', 'FFCC00', 'FFFF99', 'FFFFCC'],
'Red' => ['660000', '993333', 'CC6666', 'FF9999'],
} }
assigns = {
'colors' => {
'Blue' => ['003366', '336699', '6699CC', '99CCFF'],
'Green' => ['003300', '336633', '669966', '99CC99'],
'Yellow' => ['CC9900', 'FFCC00', 'FFFF99', 'FFFFCC'],
'Red' => ['660000', '993333', 'CC6666', 'FF9999'],
},
}
assert_template_result("003366", "{{ colors.Blue[0] }}", assigns)
assert_template_result("FF9999", "{{ colors.Red[3] }}", assigns)
@@ -262,7 +291,7 @@ class ContextTest < Minitest::Test
assigns = { 'product' => { 'variants' => [{ 'title' => 'draft151cm' }, { 'title' => 'element151cm' }] } }
assert_template_result("draft151cm", '{{ product["variants"][0]["title"] }}', assigns)
assert_template_result("element151cm", '{{ product["variants"][1]["title"] }}', assigns)
assert_template_result("draft151cm", '{{ product["variants"][0]["title"] }}', assigns)
assert_template_result("draft151cm", '{{ product["variants"].first["title"] }}', assigns)
assert_template_result("element151cm", '{{ product["variants"].last["title"] }}', assigns)
end
@@ -410,10 +439,12 @@ class ContextTest < Minitest::Test
def test_nested_lambda_is_called_once
@global = 0
@context['callcount'] = { "lambda" => proc {
@global += 1
@global.to_s
} }
@context['callcount'] = {
"lambda" => proc {
@global += 1
@global.to_s
},
}
assert_equal('1', @context['callcount.lambda'])
assert_equal('1', @context['callcount.lambda'])
@@ -423,10 +454,11 @@ class ContextTest < Minitest::Test
def test_lambda_in_array_is_called_once
@global = 0
@context['callcount'] = [1, 2, proc {
@global += 1
@global.to_s
}, 4, 5]
p = proc {
@global += 1
@global.to_s
}
@context['callcount'] = [1, 2, p, 4, 5]
assert_equal('1', @context['callcount[2]'])
assert_equal('1', @context['callcount[2]'])
@@ -473,7 +505,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'])
@@ -621,6 +653,25 @@ class ContextTest < Minitest::Test
assert_equal(:my_value, c.registers[:my_register])
end
def test_variable_to_liquid_returns_contextual_drop
context = {
"products" => ProductsDrop.new(["A", "B", "C", "D", "E"]),
}
template = Liquid::Template.parse(<<~LIQUID)
{%- for i in (1..3) -%}
for_loop_products_count: {{ products | size }}
{% endfor %}
unscoped_products_count: {{ products | size }}
LIQUID
result = template.render(context)
assert_includes(result, "for_loop_products_count: 3")
assert_includes(result, "unscoped_products_count: 5")
end
private
def assert_no_object_allocations
+89 -6
View File
@@ -105,7 +105,8 @@ class ErrorHandlingTest < Minitest::Test
end
def test_parsing_warn_with_line_numbers_adds_numbers_to_lexer_errors
template = Liquid::Template.parse('
template = Liquid::Template.parse(
'
foobar
{% if 1 =! 2 %}ok{% endif %}
@@ -113,15 +114,19 @@ class ErrorHandlingTest < Minitest::Test
bla
',
error_mode: :warn,
line_numbers: true)
line_numbers: true,
)
assert_equal(['Liquid syntax error (line 4): Unexpected character = in "1 =! 2"'],
template.warnings.map(&:message))
assert_equal(
['Liquid syntax error (line 4): Unexpected character = in "1 =! 2"'],
template.warnings.map(&:message),
)
end
def test_parsing_strict_with_line_numbers_adds_numbers_to_lexer_errors
err = assert_raises(SyntaxError) do
Liquid::Template.parse('
Liquid::Template.parse(
'
foobar
{% if 1 =! 2 %}ok{% endif %}
@@ -129,7 +134,8 @@ class ErrorHandlingTest < Minitest::Test
bla
',
error_mode: :strict,
line_numbers: true)
line_numbers: true,
)
end
assert_equal('Liquid syntax error (line 4): Unexpected character = in "1 =! 2"', err.message)
@@ -257,4 +263,81 @@ class ErrorHandlingTest < Minitest::Test
output = Liquid::Template.parse("{% assign x = 0 %}{% if 1 < '2' %}{% assign x = 3 %}{% endif %}{{ x }}").render
assert_equal("0", output)
end
def test_syntax_error_is_raised_with_template_name
file_system = StubFileSystem.new("snippet" => "1\n2\n{{ 1")
context = Liquid::Context.build(
registers: { file_system: file_system },
)
template = Template.parse(
'{% render "snippet" %}',
line_numbers: true,
)
template.name = "template/index"
assert_equal(
"Liquid syntax error (snippet line 3): Variable '{{' was not properly terminated with regexp: /\\}\\}/",
template.render(context),
)
end
def test_syntax_error_is_raised_with_template_name_from_template_factory
file_system = StubFileSystem.new("snippet" => "1\n2\n{{ 1")
context = Liquid::Context.build(
registers: {
file_system: file_system,
template_factory: StubTemplateFactory.new,
},
)
template = Template.parse(
'{% render "snippet" %}',
line_numbers: true,
)
template.name = "template/index"
assert_equal(
"Liquid syntax error (some/path/snippet line 3): Variable '{{' was not properly terminated with regexp: /\\}\\}/",
template.render(context),
)
end
def test_error_is_raised_during_parse_with_template_name
depth = Liquid::Block::MAX_DEPTH + 1
code = "{% if true %}" * depth + "rendered" + "{% endif %}" * depth
template = Template.parse("{% render 'snippet' %}", line_numbers: true)
context = Liquid::Context.build(
registers: {
file_system: StubFileSystem.new("snippet" => code),
template_factory: StubTemplateFactory.new,
},
)
assert_equal("Liquid error (some/path/snippet line 1): Nesting too deep", template.render(context))
end
def test_internal_error_is_raised_with_template_name
template = Template.new
template.parse(
"{% render 'snippet' %}",
line_numbers: true,
)
template.name = "template/index"
context = Liquid::Context.build(
registers: {
file_system: StubFileSystem.new({}),
},
)
assert_equal(
"Liquid error (template/index line 1): internal",
template.render(context),
)
end
end
+2 -2
View File
@@ -32,11 +32,11 @@ class ExpressionTest < Minitest::Test
assert_match_syntax_error(
"Liquid syntax error (line 1): Invalid expression type 'false' in range expression",
"{{ (false..true) }}"
"{{ (false..true) }}",
)
assert_match_syntax_error(
"Liquid syntax error (line 1): Invalid expression type '(1..2)' in range expression",
"{{ ((1..2)..3) }}"
"{{ ((1..2)..3) }}",
)
end
+1 -1
View File
@@ -17,7 +17,7 @@ class FilterKwargTest < Minitest::Test
with_global_filter(KwargFilter) do
assert_equal(
"data-src='src' data-widths='100, 200'",
Template.parse("{{ 'img' | html_tag: data-src: 'src', data-widths: '100, 200' }}").render(nil, nil)
Template.parse("{{ 'img' | html_tag: data-src: 'src', data-widths: '100, 200' }}").render(nil, nil),
)
end
end
+35 -14
View File
@@ -68,22 +68,34 @@ class FiltersTest < Minitest::Test
def test_sort
assert_template_result("1 2 3 4", "{{numbers | sort | join}}", { "numbers" => [2, 1, 4, 3] })
assert_template_result("alphabetic as expected", "{{words | sort | join}}",
{ "words" => ['expected', 'as', 'alphabetic'] })
assert_template_result(
"alphabetic as expected",
"{{words | sort | join}}",
{ "words" => ['expected', 'as', 'alphabetic'] },
)
assert_template_result("3", "{{value | sort}}", { "value" => 3 })
assert_template_result('are flower', "{{arrays | sort | join}}", { 'arrays' => ['flower', 'are'] })
assert_template_result("Expected case sensitive", "{{case_sensitive | sort | join}}",
{ "case_sensitive" => ["sensitive", "Expected", "case"] })
assert_template_result(
"Expected case sensitive",
"{{case_sensitive | sort | join}}",
{ "case_sensitive" => ["sensitive", "Expected", "case"] },
)
end
def test_sort_natural
# Test strings
assert_template_result("Assert case Insensitive", "{{words | sort_natural | join}}",
{ "words" => ["case", "Assert", "Insensitive"] })
assert_template_result(
"Assert case Insensitive",
"{{words | sort_natural | join}}",
{ "words" => ["case", "Assert", "Insensitive"] },
)
# Test hashes
assert_template_result("A b C", "{{hashes | sort_natural: 'a' | map: 'a' | join}}",
{ "hashes" => [{ "a" => "A" }, { "a" => "b" }, { "a" => "C" }] })
assert_template_result(
"A b C",
"{{hashes | sort_natural: 'a' | map: 'a' | join}}",
{ "hashes" => [{ "a" => "A" }, { "a" => "b" }, { "a" => "C" }] },
)
# Test objects
@context['objects'] = [TestObject.new('A'), TestObject.new('b'), TestObject.new('C')]
@@ -92,12 +104,18 @@ class FiltersTest < Minitest::Test
def test_compact
# Test strings
assert_template_result("a b c", "{{words | compact | join}}",
{ "words" => ['a', nil, 'b', nil, 'c'] })
assert_template_result(
"a b c",
"{{words | compact | join}}",
{ "words" => ['a', nil, 'b', nil, 'c'] },
)
# Test hashes
assert_template_result("A C", "{{hashes | compact: 'a' | map: 'a' | join}}",
{ "hashes" => [{ "a" => "A" }, { "a" => nil }, { "a" => "C" }] })
assert_template_result(
"A C",
"{{hashes | compact: 'a' | map: 'a' | join}}",
{ "hashes" => [{ "a" => "A" }, { "a" => nil }, { "a" => "C" }] },
)
# Test objects
@context['objects'] = [TestObject.new('A'), TestObject.new(nil), TestObject.new('C')]
@@ -109,8 +127,11 @@ class FiltersTest < Minitest::Test
end
def test_strip_html_ignore_comments_with_html
assert_template_result("bla blub", "{{ var | strip_html }}",
{ "var" => "<!-- split and some <ul> tag --><b>bla blub</a>" })
assert_template_result(
"bla blub",
"{{ var | strip_html }}",
{ "var" => "<!-- split and some <ul> tag --><b>bla blub</a>" },
)
end
def test_capitalize
+138 -9
View File
@@ -176,7 +176,17 @@ class StandardFiltersTest < Minitest::Test
end
def test_base64_decode
assert_equal('one two three', @filters.base64_decode('b25lIHR3byB0aHJlZQ=='))
decoded = @filters.base64_decode('b25lIHR3byB0aHJlZQ==')
assert_equal('one two three', decoded)
assert_equal(Encoding::UTF_8, decoded.encoding)
decoded = @filters.base64_decode('4pyF')
assert_equal('✅', decoded)
assert_equal(Encoding::UTF_8, decoded.encoding)
decoded = @filters.base64_decode("/w==")
assert_equal(Encoding::ASCII_8BIT, decoded.encoding)
assert_equal((+"\xFF").force_encoding(Encoding::ASCII_8BIT), decoded)
exception = assert_raises(Liquid::ArgumentError) do
@filters.base64_decode("invalidbase64")
@@ -188,16 +198,27 @@ class StandardFiltersTest < Minitest::Test
def test_base64_url_safe_encode
assert_equal(
'YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXogQUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVogMTIzNDU2Nzg5MCAhQCMkJV4mKigpLT1fKy8_Ljo7W117fVx8',
@filters.base64_url_safe_encode('abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ 1234567890 !@#$%^&*()-=_+/?.:;[]{}\|')
@filters.base64_url_safe_encode('abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ 1234567890 !@#$%^&*()-=_+/?.:;[]{}\|'),
)
assert_equal('', @filters.base64_url_safe_encode(nil))
end
def test_base64_url_safe_decode
decoded = @filters.base64_url_safe_decode('YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXogQUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVogMTIzNDU2Nzg5MCAhQCMkJV4mKigpLT1fKy8_Ljo7W117fVx8')
assert_equal(
'abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ 1234567890 !@#$%^&*()-=_+/?.:;[]{}\|',
@filters.base64_url_safe_decode('YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXogQUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVogMTIzNDU2Nzg5MCAhQCMkJV4mKigpLT1fKy8_Ljo7W117fVx8')
decoded,
)
assert_equal(Encoding::UTF_8, decoded.encoding)
decoded = @filters.base64_url_safe_decode('4pyF')
assert_equal('✅', decoded)
assert_equal(Encoding::UTF_8, decoded.encoding)
decoded = @filters.base64_url_safe_decode("_w==")
assert_equal(Encoding::ASCII_8BIT, decoded.encoding)
assert_equal((+"\xFF").force_encoding(Encoding::ASCII_8BIT), decoded)
exception = assert_raises(Liquid::ArgumentError) do
@filters.base64_url_safe_decode("invalidbase64")
end
@@ -230,7 +251,7 @@ class StandardFiltersTest < Minitest::Test
assert_equal('one two three', @filters.truncatewords('one two three'))
assert_equal(
'Two small (13&#8221; x 5.5&#8221; x 10&#8221; high) baskets fit inside one large basket (13&#8221;...',
@filters.truncatewords('Two small (13&#8221; x 5.5&#8221; x 10&#8221; high) baskets fit inside one large basket (13&#8221; x 16&#8221; x 10.5&#8221; high) with cover.', 15)
@filters.truncatewords('Two small (13&#8221; x 5.5&#8221; x 10&#8221; high) baskets fit inside one large basket (13&#8221; x 16&#8221; x 10.5&#8221; high) with cover.', 15),
)
assert_equal("测试测试测试测试", @filters.truncatewords('测试测试测试测试', 5))
assert_equal('one two1', @filters.truncatewords("one two three", 2, 1))
@@ -310,8 +331,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_natural(input, "price"))
end
@@ -433,8 +454,11 @@ class StandardFiltersTest < Minitest::Test
def test_map
assert_equal([1, 2, 3, 4], @filters.map([{ "a" => 1 }, { "a" => 2 }, { "a" => 3 }, { "a" => 4 }], 'a'))
assert_template_result('abc', "{{ ary | map:'foo' | map:'bar' }}",
{ 'ary' => [{ 'foo' => { 'bar' => 'a' } }, { 'foo' => { 'bar' => 'b' } }, { 'foo' => { 'bar' => 'c' } }] })
assert_template_result(
'abc',
"{{ ary | map:'foo' | map:'bar' }}",
{ 'ary' => [{ 'foo' => { 'bar' => 'a' } }, { 'foo' => { 'bar' => 'b' } }, { 'foo' => { 'bar' => 'c' } }] },
)
end
def test_map_doesnt_call_arbitrary_stuff
@@ -458,8 +482,11 @@ class StandardFiltersTest < Minitest::Test
end
def test_map_on_hashes
assert_template_result("4217", '{{ thing | map: "foo" | map: "bar" }}',
{ "thing" => { "foo" => [{ "bar" => 42 }, { "bar" => 17 }] } })
assert_template_result(
"4217",
'{{ thing | map: "foo" | map: "bar" }}',
{ "thing" => { "foo" => [{ "bar" => 42 }, { "bar" => 17 }] } },
)
end
def test_legacy_map_on_hashes_with_dynamic_key
@@ -922,6 +949,108 @@ 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
def test_sum_of_floats
input = [0.1, 0.2, 0.3]
assert_equal(0.6, @filters.sum(input))
assert_template_result("0.6", "{{ input | sum }}", { "input" => input })
end
def test_sum_of_negative_floats
input = [0.1, 0.2, -0.3]
assert_equal(0.0, @filters.sum(input))
assert_template_result("0.0", "{{ input | sum }}", { "input" => input })
end
def test_sum_with_float_strings
input = [0.1, "0.2", "0.3"]
assert_equal(0.6, @filters.sum(input))
assert_template_result("0.6", "{{ input | sum }}", { "input" => input })
end
def test_sum_resulting_in_negative_float
input = [0.1, -0.2, -0.3]
assert_equal(-0.4, @filters.sum(input))
assert_template_result("-0.4", "{{ input | sum }}", { "input" => input })
end
def test_sum_with_floats_and_indexable_map_values
input = [{ "quantity" => 1 }, { "quantity" => 0.2, "weight" => -0.3 }, { "weight" => 0.4 }]
assert_equal(0.0, @filters.sum(input))
assert_equal(1.2, @filters.sum(input, "quantity"))
assert_equal(0.1, @filters.sum(input, "weight"))
assert_equal(0.0, @filters.sum(input, "subtotal"))
assert_template_result("0", "{{ input | sum }}", { "input" => input })
assert_template_result("1.2", "{{ input | sum: 'quantity' }}", { "input" => input })
assert_template_result("0.1", "{{ input | sum: 'weight' }}", { "input" => input })
assert_template_result("0", "{{ input | sum: 'subtotal' }}", { "input" => input })
end
private
def with_timezone(tz)
+28 -14
View File
@@ -73,9 +73,11 @@ HERE
def test_for_helpers
assigns = { 'array' => [1, 2, 3] }
assert_template_result(' 1/3 2/3 3/3 ',
assert_template_result(
' 1/3 2/3 3/3 ',
'{%for item in array%} {{forloop.index}}/{{forloop.length}} {%endfor%}',
assigns)
assigns,
)
assert_template_result(' 1 2 3 ', '{%for item in array%} {{forloop.index}} {%endfor%}', assigns)
assert_template_result(' 0 1 2 ', '{%for item in array%} {{forloop.index0}} {%endfor%}', assigns)
assert_template_result(' 2 1 0 ', '{%for item in array%} {{forloop.rindex0}} {%endfor%}', assigns)
@@ -86,9 +88,11 @@ HERE
def test_for_and_if
assigns = { 'array' => [1, 2, 3] }
assert_template_result('+--',
assert_template_result(
'+--',
'{%for item in array%}{% if forloop.first %}+{% else %}-{% endif %}{%endfor%}',
assigns)
assigns,
)
end
def test_for_else
@@ -325,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}}-' \
@@ -344,23 +353,28 @@ HERE
'{{forloop.first}}-' \
'{{forloop.last}}-' \
'{{val}}{%endfor%}',
{ 'string' => "test string" })
{ 'string' => "test string" },
)
end
def test_for_parentloop_references_parent_loop
assert_template_result('1.1 1.2 1.3 2.1 2.2 2.3 ',
assert_template_result(
'1.1 1.2 1.3 2.1 2.2 2.3 ',
'{% for inner in outer %}{% for k in inner %}' \
'{{ forloop.parentloop.index }}.{{ forloop.index }} ' \
'{% endfor %}{% endfor %}',
{ 'outer' => [[1, 1, 1], [1, 1, 1]] })
{ 'outer' => [[1, 1, 1], [1, 1, 1]] },
)
end
def test_for_parentloop_nil_when_not_present
assert_template_result('.1 .2 ',
assert_template_result(
'.1 .2 ',
'{% for inner in outer %}' \
'{{ forloop.parentloop.index }}.{{ forloop.index }} ' \
'{% endfor %}',
{ 'outer' => [[1, 1, 1], [1, 1, 1]] })
{ 'outer' => [[1, 1, 1], [1, 1, 1]] },
)
end
def test_inner_for_over_empty_input
+17 -9
View File
@@ -7,8 +7,10 @@ class IfElseTagTest < Minitest::Test
def test_if
assert_template_result(' ', ' {% if false %} this text should not go into the output {% endif %} ')
assert_template_result(' this text should go into the output ',
' {% if true %} this text should go into the output {% endif %} ')
assert_template_result(
' this text should go into the output ',
' {% if true %} this text should go into the output {% endif %} ',
)
assert_template_result(' you rock ?', '{% if false %} you suck {% endif %} {% if true %} you rock {% endif %}?')
end
@@ -51,18 +53,22 @@ class IfElseTagTest < Minitest::Test
def test_comparison_of_expressions_starting_with_and_or_or
assigns = { 'order' => { 'items_count' => 0 }, 'android' => { 'name' => 'Roy' } }
assert_template_result("YES",
assert_template_result(
"YES",
"{% if android.name == 'Roy' %}YES{% endif %}",
assigns)
assert_template_result("YES",
assigns,
)
assert_template_result(
"YES",
"{% if order.items_count == 0 %}YES{% endif %}",
assigns)
assigns,
)
end
def test_if_and
assert_template_result(' YES ', '{% if true and true %} YES {% endif %}')
assert_template_result('', '{% if false and true %} YES {% endif %}')
assert_template_result('', '{% if false and true %} YES {% endif %}')
assert_template_result('', '{% if true and false %} YES {% endif %}')
end
def test_hash_miss_generates_false
@@ -155,8 +161,10 @@ class IfElseTagTest < Minitest::Test
original_op = Condition.operators['contains']
Condition.operators['contains'] = :[]
assert_template_result('yes',
%({% if 'gnomeslab-and-or-liquid' contains 'gnomeslab-and-or-liquid' %}yes{% endif %}))
assert_template_result(
'yes',
%({% if 'gnomeslab-and-or-liquid' contains 'gnomeslab-and-or-liquid' %}yes{% endif %}),
)
ensure
Condition.operators['contains'] = original_op
end
+154 -59
View File
@@ -58,65 +58,88 @@ class IncludeTagTest < Minitest::Test
end
def test_include_tag_looks_for_file_system_in_registers_first
assert_equal('from OtherFileSystem',
Template.parse("{% include 'pick_a_source' %}").render!({}, registers: { file_system: OtherFileSystem.new }))
assert_equal(
'from OtherFileSystem',
Template.parse("{% include 'pick_a_source' %}").render!({}, registers: { file_system: OtherFileSystem.new }),
)
end
def test_include_tag_with
assert_template_result("Product: Draft 151cm ",
assert_template_result(
"Product: Draft 151cm ",
"{% include 'product' with products[0] %}",
{ "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }] },
partials: { "product" => "Product: {{ product.title }} " })
partials: { "product" => "Product: {{ product.title }} " },
)
end
def test_include_tag_with_alias
assert_template_result("Product: Draft 151cm ",
assert_template_result(
"Product: Draft 151cm ",
"{% include 'product_alias' with products[0] as product %}",
{ "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }] },
partials: { "product_alias" => "Product: {{ product.title }} " })
partials: { "product_alias" => "Product: {{ product.title }} " },
)
end
def test_include_tag_for_alias
assert_template_result("Product: Draft 151cm Product: Element 155cm ",
assert_template_result(
"Product: Draft 151cm Product: Element 155cm ",
"{% include 'product_alias' for products as product %}",
{ "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }] },
partials: { "product_alias" => "Product: {{ product.title }} " })
partials: { "product_alias" => "Product: {{ product.title }} " },
)
end
def test_include_tag_with_default_name
assert_template_result("Product: Draft 151cm ",
"{% include 'product' %}", { "product" => { 'title' => 'Draft 151cm' } },
partials: { "product" => "Product: {{ product.title }} " })
assert_template_result(
"Product: Draft 151cm ",
"{% include 'product' %}",
{ "product" => { 'title' => 'Draft 151cm' } },
partials: { "product" => "Product: {{ product.title }} " },
)
end
def test_include_tag_for
assert_template_result("Product: Draft 151cm Product: Element 155cm ",
assert_template_result(
"Product: Draft 151cm Product: Element 155cm ",
"{% include 'product' for products %}",
{ "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }] },
partials: { "product" => "Product: {{ product.title }} " })
partials: { "product" => "Product: {{ product.title }} " },
)
end
def test_include_tag_with_local_variables
assert_template_result("Locale: test123 ", "{% include 'locale_variables' echo1: 'test123' %}",
partials: { "locale_variables" => "Locale: {{echo1}} {{echo2}}" })
assert_template_result(
"Locale: test123 ",
"{% include 'locale_variables' echo1: 'test123' %}",
partials: { "locale_variables" => "Locale: {{echo1}} {{echo2}}" },
)
end
def test_include_tag_with_multiple_local_variables
assert_template_result("Locale: test123 test321",
assert_template_result(
"Locale: test123 test321",
"{% include 'locale_variables' echo1: 'test123', echo2: 'test321' %}",
partials: { "locale_variables" => "Locale: {{echo1}} {{echo2}}" })
partials: { "locale_variables" => "Locale: {{echo1}} {{echo2}}" },
)
end
def test_include_tag_with_multiple_local_variables_from_context
assert_template_result("Locale: test123 test321",
assert_template_result(
"Locale: test123 test321",
"{% include 'locale_variables' echo1: echo1, echo2: more_echos.echo2 %}",
{ 'echo1' => 'test123', 'more_echos' => { "echo2" => 'test321' } },
partials: { "locale_variables" => "Locale: {{echo1}} {{echo2}}" })
partials: { "locale_variables" => "Locale: {{echo1}} {{echo2}}" },
)
end
def test_included_templates_assigns_variables
assert_template_result("bar", "{% include 'assignments' %}{{ foo }}",
partials: { 'assignments' => "{% assign foo = 'bar' %}" })
assert_template_result(
"bar",
"{% include 'assignments' %}{{ foo }}",
partials: { 'assignments' => "{% assign foo = 'bar' %}" },
)
end
def test_nested_include_tag
@@ -137,13 +160,19 @@ class IncludeTagTest < Minitest::Test
"details" => "details",
}
assert_template_result("Product: Draft 151cm details ",
"{% include 'nested_product_template' with product %}", { "product" => { "title" => 'Draft 151cm' } },
partials: partials)
assert_template_result(
"Product: Draft 151cm details ",
"{% include 'nested_product_template' with product %}",
{ "product" => { "title" => 'Draft 151cm' } },
partials: partials,
)
assert_template_result("Product: Draft 151cm details Product: Element 155cm details ",
"{% include 'nested_product_template' for products %}", { "products" => [{ "title" => 'Draft 151cm' }, { "title" => 'Element 155cm' }] },
partials: partials)
assert_template_result(
"Product: Draft 151cm details Product: Element 155cm details ",
"{% include 'nested_product_template' for products %}",
{ "products" => [{ "title" => 'Draft 151cm' }, { "title" => 'Element 155cm' }] },
partials: partials,
)
end
def test_recursively_included_template_does_not_produce_endless_loop
@@ -161,46 +190,68 @@ class IncludeTagTest < Minitest::Test
end
def test_dynamically_choosen_template
assert_template_result("Test123", "{% include template %}", { "template" => 'Test123' },
partials: { "Test123" => "Test123" })
assert_template_result(
"Test123",
"{% include template %}",
{ "template" => 'Test123' },
partials: { "Test123" => "Test123" },
)
assert_template_result("Test321", "{% include template %}", { "template" => 'Test321' },
partials: { "Test321" => "Test321" })
assert_template_result(
"Test321",
"{% include template %}",
{ "template" => 'Test321' },
partials: { "Test321" => "Test321" },
)
assert_template_result("Product: Draft 151cm ", "{% include template for product %}",
assert_template_result(
"Product: Draft 151cm ",
"{% include template for product %}",
{ "template" => 'product', 'product' => { 'title' => 'Draft 151cm' } },
partials: { "product" => "Product: {{ product.title }} " })
partials: { "product" => "Product: {{ product.title }} " },
)
end
def test_include_tag_caches_second_read_of_same_partial
file_system = CountingFileSystem.new
assert_equal('from CountingFileSystemfrom CountingFileSystem',
Template.parse("{% include 'pick_a_source' %}{% include 'pick_a_source' %}").render!({}, registers: { file_system: file_system }))
assert_equal(
'from CountingFileSystemfrom CountingFileSystem',
Template.parse("{% include 'pick_a_source' %}{% include 'pick_a_source' %}").render!({}, registers: { file_system: file_system }),
)
assert_equal(1, file_system.count)
end
def test_include_tag_doesnt_cache_partials_across_renders
file_system = CountingFileSystem.new
assert_equal('from CountingFileSystem',
Template.parse("{% include 'pick_a_source' %}").render!({}, registers: { file_system: file_system }))
assert_equal(
'from CountingFileSystem',
Template.parse("{% include 'pick_a_source' %}").render!({}, registers: { file_system: file_system }),
)
assert_equal(1, file_system.count)
assert_equal('from CountingFileSystem',
Template.parse("{% include 'pick_a_source' %}").render!({}, registers: { file_system: file_system }))
assert_equal(
'from CountingFileSystem',
Template.parse("{% include 'pick_a_source' %}").render!({}, registers: { file_system: file_system }),
)
assert_equal(2, file_system.count)
end
def test_include_tag_within_if_statement
assert_template_result("foo_if_true", "{% if true %}{% include 'foo_if_true' %}{% endif %}",
partials: { "foo_if_true" => "foo_if_true" })
assert_template_result(
"foo_if_true",
"{% if true %}{% include 'foo_if_true' %}{% endif %}",
partials: { "foo_if_true" => "foo_if_true" },
)
end
def test_custom_include_tag
original_tag = Liquid::Template.tags['include']
Liquid::Template.tags['include'] = CustomInclude
begin
assert_equal("custom_foo",
Template.parse("{% include 'custom_foo' %}").render!)
assert_equal(
"custom_foo",
Template.parse("{% include 'custom_foo' %}").render!,
)
ensure
Liquid::Template.tags['include'] = original_tag
end
@@ -210,8 +261,10 @@ class IncludeTagTest < Minitest::Test
original_tag = Liquid::Template.tags['include']
Liquid::Template.tags['include'] = CustomInclude
begin
assert_equal("custom_foo_if_true",
Template.parse("{% if true %}{% include 'custom_foo_if_true' %}{% endif %}").render!)
assert_equal(
"custom_foo_if_true",
Template.parse("{% if true %}{% include 'custom_foo_if_true' %}{% endif %}").render!,
)
ensure
Liquid::Template.tags['include'] = original_tag
end
@@ -242,31 +295,49 @@ class IncludeTagTest < Minitest::Test
end
def test_render_raise_argument_error_when_template_is_undefined
assert_template_result("Liquid error (line 1): Argument error in tag 'include' - Illegal template name",
"{% include undefined_variable %}", render_errors: true)
assert_template_result(
"Liquid error (line 1): Argument error in tag 'include' - Illegal template name",
"{% include undefined_variable %}",
render_errors: true,
)
assert_template_result("Liquid error (line 1): Argument error in tag 'include' - Illegal template name",
"{% include nil %}", render_errors: true)
assert_template_result(
"Liquid error (line 1): Argument error in tag 'include' - Illegal template name",
"{% include nil %}",
render_errors: true,
)
end
def test_render_raise_argument_error_when_template_is_not_a_string
assert_template_result("Liquid error (line 1): Argument error in tag 'include' - Illegal template name",
"{% include 123 %}", render_errors: true)
assert_template_result(
"Liquid error (line 1): Argument error in tag 'include' - Illegal template name",
"{% include 123 %}",
render_errors: true,
)
end
def test_including_via_variable_value
assert_template_result("from TestFileSystem", "{% assign page = 'pick_a_source' %}{% include page %}",
partials: { "pick_a_source" => "from TestFileSystem" })
assert_template_result(
"from TestFileSystem",
"{% assign page = 'pick_a_source' %}{% include page %}",
partials: { "pick_a_source" => "from TestFileSystem" },
)
partials = { "product" => "Product: {{ product.title }} " }
assert_template_result("Product: Draft 151cm ", "{% assign page = 'product' %}{% include page %}",
assert_template_result(
"Product: Draft 151cm ",
"{% assign page = 'product' %}{% include page %}",
{ "product" => { 'title' => 'Draft 151cm' } },
partials: partials)
partials: partials,
)
assert_template_result("Product: Draft 151cm ", "{% assign page = 'product' %}{% include page for foo %}",
assert_template_result(
"Product: Draft 151cm ",
"{% assign page = 'product' %}{% include page for foo %}",
{ "foo" => { 'title' => 'Draft 151cm' } },
partials: partials)
partials: partials,
)
end
def test_including_with_strict_variables
@@ -279,7 +350,31 @@ class IncludeTagTest < Minitest::Test
def test_break_through_include
assert_template_result("1", "{% for i in (1..3) %}{{ i }}{% break %}{{ i }}{% endfor %}")
assert_template_result("1", "{% for i in (1..3) %}{{ i }}{% include 'break' %}{{ i }}{% endfor %}",
partials: { 'break' => "{% break %}" })
assert_template_result(
"1",
"{% for i in (1..3) %}{{ i }}{% include 'break' %}{{ i }}{% endfor %}",
partials: { 'break' => "{% break %}" },
)
end
def test_render_tag_renders_error_with_template_name
assert_template_result(
'Liquid error (foo line 1): standard error',
"{% include 'foo' with errors %}",
{ 'errors' => ErrorDrop.new },
partials: { 'foo' => '{{ foo.standard_error }}' },
render_errors: true,
)
end
def test_render_tag_renders_error_with_template_name_from_template_factory
assert_template_result(
'Liquid error (some/path/foo line 1): standard error',
"{% include 'foo' with errors %}",
{ 'errors' => ErrorDrop.new },
partials: { 'foo' => '{{ foo.standard_error }}' },
template_factory: StubTemplateFactory.new,
render_errors: true,
)
end
end # IncludeTagTest
+8 -4
View File
@@ -8,19 +8,23 @@ class IncrementTagTest < Minitest::Test
def test_inc
assert_template_result('0 1', '{%increment port %} {{ port }}')
assert_template_result(' 0 1 2', '{{port}} {%increment port %} {%increment port%} {{port}}')
assert_template_result('0 0 1 2 1',
assert_template_result(
'0 0 1 2 1',
'{%increment port %} {%increment starboard%} ' \
'{%increment port %} {%increment port%} ' \
'{%increment starboard %}')
'{%increment starboard %}',
)
end
def test_dec
assert_template_result('-1 -1', '{%decrement port %} {{ port }}', { 'port' => 10 })
assert_template_result(' -1 -2 -2', '{{port}} {%decrement port %} {%decrement port%} {{port}}')
assert_template_result('0 1 2 0 3 1 1 3',
assert_template_result(
'0 1 2 0 3 1 1 3',
'{%increment starboard %} {%increment starboard%} {%increment starboard%} ' \
'{%increment port %} {%increment starboard%} ' \
'{%increment port %} {%decrement port%} ' \
'{%decrement starboard %}')
'{%decrement starboard %}',
)
end
end
+33
View File
@@ -113,4 +113,37 @@ class LiquidTagTest < Minitest::Test
{% raw %}{% liquid echo 'test' %}{% endraw %}
LIQUID
end
def test_nested_liquid_tags
assert_template_result('good', <<~LIQUID)
{%- liquid
liquid
if true
echo "good"
endif
-%}
LIQUID
end
def test_nested_liquid_tags_on_same_line
assert_template_result('good', <<~LIQUID)
{%- liquid liquid liquid echo "good" -%}
LIQUID
end
def test_nested_liquid_liquid_is_not_skipped_if_used_in_non_tag_position
assert_template_result('liquid', <<~LIQUID, { 'liquid' => 'liquid' })
{%- liquid liquid liquid echo liquid -%}
LIQUID
end
def test_next_liquid_with_unclosed_if_tag
assert_match_syntax_error("Liquid syntax error (line 2): 'if' tag was never closed", <<~LIQUID)
{%- liquid
liquid if true
echo "good"
endif
-%}
LIQUID
end
end
+7 -3
View File
@@ -6,12 +6,16 @@ class RawTagTest < Minitest::Test
include Liquid
def test_tag_in_raw
assert_template_result('{% comment %} test {% endcomment %}',
'{% raw %}{% comment %} test {% endcomment %}{% endraw %}')
assert_template_result(
'{% comment %} test {% endcomment %}',
'{% raw %}{% comment %} test {% endcomment %}{% endraw %}',
)
end
def test_output_in_raw
assert_template_result('{{ test }}', '{% raw %}{{ test }}{% endraw %}')
assert_template_result('>{{ test }}<', '> {%- raw -%}{{ test }}{%- endraw -%} <')
assert_template_result("> inner <", "> {%- raw -%} inner {%- endraw %} <")
assert_template_result("> inner <", "> {%- raw -%} inner {%- endraw -%} <")
end
def test_open_tag_in_raw
+127 -48
View File
@@ -6,52 +6,79 @@ class RenderTagTest < Minitest::Test
include Liquid
def test_render_with_no_arguments
assert_template_result('rendered content', '{% render "source" %}',
partials: { 'source' => 'rendered content' })
assert_template_result(
'rendered content',
'{% render "source" %}',
partials: { 'source' => 'rendered content' },
)
end
def test_render_tag_looks_for_file_system_in_registers_first
assert_template_result('from register file system', '{% render "pick_a_source" %}',
partials: { 'pick_a_source' => 'from register file system' })
assert_template_result(
'from register file system',
'{% render "pick_a_source" %}',
partials: { 'pick_a_source' => 'from register file system' },
)
end
def test_render_passes_named_arguments_into_inner_scope
assert_template_result('My Product', '{% render "product", inner_product: outer_product %}',
assert_template_result(
'My Product',
'{% render "product", inner_product: outer_product %}',
{ 'outer_product' => { 'title' => 'My Product' } },
partials: { 'product' => '{{ inner_product.title }}' })
partials: { 'product' => '{{ inner_product.title }}' },
)
end
def test_render_accepts_literals_as_arguments
assert_template_result('123', '{% render "snippet", price: 123 %}',
partials: { 'snippet' => '{{ price }}' })
assert_template_result(
'123',
'{% render "snippet", price: 123 %}',
partials: { 'snippet' => '{{ price }}' },
)
end
def test_render_accepts_multiple_named_arguments
assert_template_result('1 2', '{% render "snippet", one: 1, two: 2 %}',
partials: { 'snippet' => '{{ one }} {{ two }}' })
assert_template_result(
'1 2',
'{% render "snippet", one: 1, two: 2 %}',
partials: { 'snippet' => '{{ one }} {{ two }}' },
)
end
def test_render_does_not_inherit_parent_scope_variables
assert_template_result('', '{% assign outer_variable = "should not be visible" %}{% render "snippet" %}',
partials: { 'snippet' => '{{ outer_variable }}' })
assert_template_result(
'',
'{% assign outer_variable = "should not be visible" %}{% render "snippet" %}',
partials: { 'snippet' => '{{ outer_variable }}' },
)
end
def test_render_does_not_inherit_variable_with_same_name_as_snippet
assert_template_result('', "{% assign snippet = 'should not be visible' %}{% render 'snippet' %}",
partials: { 'snippet' => '{{ snippet }}' })
assert_template_result(
'',
"{% assign snippet = 'should not be visible' %}{% render 'snippet' %}",
partials: { 'snippet' => '{{ snippet }}' },
)
end
def test_render_does_not_mutate_parent_scope
assert_template_result('', "{% render 'snippet' %}{{ inner }}",
partials: { 'snippet' => '{% assign inner = 1 %}' })
assert_template_result(
'',
"{% render 'snippet' %}{{ inner }}",
partials: { 'snippet' => '{% assign inner = 1 %}' },
)
end
def test_nested_render_tag
assert_template_result('one two', "{% render 'one' %}",
assert_template_result(
'one two',
"{% render 'one' %}",
partials: {
'one' => "one {% render 'two' %}",
'two' => 'two',
})
},
)
end
def test_recursively_rendered_template_does_not_produce_endless_loop
@@ -77,27 +104,36 @@ class RenderTagTest < Minitest::Test
def test_include_tag_caches_second_read_of_same_partial
file_system = StubFileSystem.new('snippet' => 'echo')
assert_equal('echoecho',
assert_equal(
'echoecho',
Template.parse('{% render "snippet" %}{% render "snippet" %}')
.render!({}, registers: { file_system: file_system }))
.render!({}, registers: { file_system: file_system }),
)
assert_equal(1, file_system.file_read_count)
end
def test_render_tag_doesnt_cache_partials_across_renders
file_system = StubFileSystem.new('snippet' => 'my message')
assert_equal('my message',
Template.parse('{% include "snippet" %}').render!({}, registers: { file_system: file_system }))
assert_equal(
'my message',
Template.parse('{% include "snippet" %}').render!({}, registers: { file_system: file_system }),
)
assert_equal(1, file_system.file_read_count)
assert_equal('my message',
Template.parse('{% include "snippet" %}').render!({}, registers: { file_system: file_system }))
assert_equal(
'my message',
Template.parse('{% include "snippet" %}').render!({}, registers: { file_system: file_system }),
)
assert_equal(2, file_system.file_read_count)
end
def test_render_tag_within_if_statement
assert_template_result('my message', '{% if true %}{% render "snippet" %}{% endif %}',
partials: { 'snippet' => 'my message' })
assert_template_result(
'my message',
'{% if true %}{% render "snippet" %}{% endif %}',
partials: { 'snippet' => 'my message' },
)
end
def test_break_through_render
@@ -107,13 +143,19 @@ class RenderTagTest < Minitest::Test
end
def test_increment_is_isolated_between_renders
assert_template_result('010', '{% increment %}{% increment %}{% render "incr" %}',
partials: { 'incr' => '{% increment %}' })
assert_template_result(
'010',
'{% increment %}{% increment %}{% render "incr" %}',
partials: { 'incr' => '{% increment %}' },
)
end
def test_decrement_is_isolated_between_renders
assert_template_result('-1-2-1', '{% decrement %}{% decrement %}{% render "decr" %}',
partials: { 'decr' => '{% decrement %}' })
assert_template_result(
'-1-2-1',
'{% decrement %}{% decrement %}{% render "decr" %}',
partials: { 'decr' => '{% decrement %}' },
)
end
def test_includes_will_not_render_inside_render_tag
@@ -124,7 +166,7 @@ class RenderTagTest < Minitest::Test
partials: {
'foo' => 'bar',
'test_include' => '{% include "foo" %}',
}
},
)
end
@@ -138,72 +180,109 @@ class RenderTagTest < Minitest::Test
'nested_render_with_sibling_include' => '{% render "test_include" %}{% include "foo" %}',
'test_include' => '{% include "foo" %}',
},
render_errors: true
render_errors: true,
)
end
def test_render_tag_with
assert_template_result("Product: Draft 151cm ",
assert_template_result(
"Product: Draft 151cm ",
"{% render 'product' with products[0] %}",
{ "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }] },
partials: {
'product' => "Product: {{ product.title }} ",
'product_alias' => "Product: {{ product.title }} ",
})
},
)
end
def test_render_tag_with_alias
assert_template_result("Product: Draft 151cm ",
assert_template_result(
"Product: Draft 151cm ",
"{% render 'product_alias' with products[0] as product %}",
{ "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }] },
partials: {
'product' => "Product: {{ product.title }} ",
'product_alias' => "Product: {{ product.title }} ",
})
},
)
end
def test_render_tag_for_alias
assert_template_result("Product: Draft 151cm Product: Element 155cm ",
assert_template_result(
"Product: Draft 151cm Product: Element 155cm ",
"{% render 'product_alias' for products as product %}",
{ "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }] },
partials: {
'product' => "Product: {{ product.title }} ",
'product_alias' => "Product: {{ product.title }} ",
})
},
)
end
def test_render_tag_for
assert_template_result("Product: Draft 151cm Product: Element 155cm ",
assert_template_result(
"Product: Draft 151cm Product: Element 155cm ",
"{% render 'product' for products %}",
{ "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }] },
partials: {
'product' => "Product: {{ product.title }} ",
'product_alias' => "Product: {{ product.title }} ",
})
},
)
end
def test_render_tag_forloop
assert_template_result("Product: Draft 151cm first index:1 Product: Element 155cm last index:2 ",
assert_template_result(
"Product: Draft 151cm first index:1 Product: Element 155cm last index:2 ",
"{% render 'product' for products %}",
{ "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }] },
partials: {
'product' => "Product: {{ product.title }} {% if forloop.first %}first{% endif %} {% if forloop.last %}last{% endif %} index:{{ forloop.index }} ",
})
},
)
end
def test_render_tag_for_drop
assert_template_result("123",
"{% render 'loop' for loop as value %}", { "loop" => TestEnumerable.new },
assert_template_result(
"123",
"{% render 'loop' for loop as value %}",
{ "loop" => TestEnumerable.new },
partials: {
'loop' => "{{ value.foo }}",
})
},
)
end
def test_render_tag_with_drop
assert_template_result("TestEnumerable",
"{% render 'loop' with loop as value %}", { "loop" => TestEnumerable.new },
assert_template_result(
"TestEnumerable",
"{% render 'loop' with loop as value %}",
{ "loop" => TestEnumerable.new },
partials: {
'loop' => "{{ value }}",
})
},
)
end
def test_render_tag_renders_error_with_template_name
assert_template_result(
'Liquid error (foo line 1): standard error',
"{% render 'foo' with errors %}",
{ 'errors' => ErrorDrop.new },
partials: { 'foo' => '{{ foo.standard_error }}' },
render_errors: true,
)
end
def test_render_tag_renders_error_with_template_name_from_template_factory
assert_template_result(
'Liquid error (some/path/foo line 1): standard error',
"{% render 'foo' with errors %}",
{ 'errors' => ErrorDrop.new },
partials: { 'foo' => '{{ foo.standard_error }}' },
template_factory: StubTemplateFactory.new,
render_errors: true,
)
end
end
+105 -52
View File
@@ -6,8 +6,10 @@ class StandardTagTest < Minitest::Test
include Liquid
def test_no_transform
assert_template_result('this text should come out of the template without change...',
'this text should come out of the template without change...')
assert_template_result(
'this text should come out of the template without change...',
'this text should come out of the template without change...',
)
assert_template_result('blah', 'blah')
assert_template_result('<blah>', '<blah>')
@@ -20,8 +22,10 @@ class StandardTagTest < Minitest::Test
end
def test_has_a_block_which_does_nothing
assert_template_result(%(the comment block should be removed .. right?),
%(the comment block should be removed {%comment%} be gone.. {%endcomment%} .. right?))
assert_template_result(
%(the comment block should be removed .. right?),
%(the comment block should be removed {%comment%} be gone.. {%endcomment%} .. right?),
)
assert_template_result('', '{%comment%}{%endcomment%}')
assert_template_result('', '{%comment%}{% endcomment %}')
@@ -64,61 +68,81 @@ class StandardTagTest < Minitest::Test
def test_capture
assigns = { 'var' => 'content' }
assert_template_result('content foo content foo ',
assert_template_result(
'content foo content foo ',
'{{ var2 }}{% capture var2 %}{{ var }} foo {% endcapture %}{{ var2 }}{{ var2 }}',
assigns)
assigns,
)
end
def test_capture_detects_bad_syntax
assert_raises(SyntaxError) do
assert_template_result('content foo content foo ',
assert_template_result(
'content foo content foo ',
'{{ var2 }}{% capture %}{{ var }} foo {% endcapture %}{{ var2 }}{{ var2 }}',
{ 'var' => 'content' })
{ 'var' => 'content' },
)
end
end
def test_case
assigns = { 'condition' => 2 }
assert_template_result(' its 2 ',
assert_template_result(
' its 2 ',
'{% case condition %}{% when 1 %} its 1 {% when 2 %} its 2 {% endcase %}',
assigns)
assigns,
)
assigns = { 'condition' => 1 }
assert_template_result(' its 1 ',
assert_template_result(
' its 1 ',
'{% case condition %}{% when 1 %} its 1 {% when 2 %} its 2 {% endcase %}',
assigns)
assigns,
)
assigns = { 'condition' => 3 }
assert_template_result('',
assert_template_result(
'',
'{% case condition %}{% when 1 %} its 1 {% when 2 %} its 2 {% endcase %}',
assigns)
assigns,
)
assigns = { 'condition' => "string here" }
assert_template_result(' hit ',
assert_template_result(
' hit ',
'{% case condition %}{% when "string here" %} hit {% endcase %}',
assigns)
assigns,
)
assigns = { 'condition' => "bad string here" }
assert_template_result('',
assert_template_result(
'',
'{% case condition %}{% when "string here" %} hit {% endcase %}',\
assigns)
assigns,
)
end
def test_case_with_else
assigns = { 'condition' => 5 }
assert_template_result(' hit ',
assert_template_result(
' hit ',
'{% case condition %}{% when 5 %} hit {% else %} else {% endcase %}',
assigns)
assigns,
)
assigns = { 'condition' => 6 }
assert_template_result(' else ',
assert_template_result(
' else ',
'{% case condition %}{% when 5 %} hit {% else %} else {% endcase %}',
assigns)
assigns,
)
assigns = { 'condition' => 6 }
assert_template_result(' else ',
assert_template_result(
' else ',
'{% case condition %} {% when 5 %} hit {% else %} else {% endcase %}',
assigns)
assigns,
)
end
def test_case_on_size
@@ -131,47 +155,67 @@ class StandardTagTest < Minitest::Test
end
def test_case_on_size_with_else
assert_template_result('else',
assert_template_result(
'else',
'{% case a.size %}{% when 1 %}1{% when 2 %}2{% else %}else{% endcase %}',
{ 'a' => [] })
{ 'a' => [] },
)
assert_template_result('1',
assert_template_result(
'1',
'{% case a.size %}{% when 1 %}1{% when 2 %}2{% else %}else{% endcase %}',
{ 'a' => [1] })
{ 'a' => [1] },
)
assert_template_result('2',
assert_template_result(
'2',
'{% case a.size %}{% when 1 %}1{% when 2 %}2{% else %}else{% endcase %}',
{ 'a' => [1, 1] })
{ 'a' => [1, 1] },
)
assert_template_result('else',
assert_template_result(
'else',
'{% case a.size %}{% when 1 %}1{% when 2 %}2{% else %}else{% endcase %}',
{ 'a' => [1, 1, 1] })
{ 'a' => [1, 1, 1] },
)
assert_template_result('else',
assert_template_result(
'else',
'{% case a.size %}{% when 1 %}1{% when 2 %}2{% else %}else{% endcase %}',
{ 'a' => [1, 1, 1, 1] })
{ 'a' => [1, 1, 1, 1] },
)
assert_template_result('else',
assert_template_result(
'else',
'{% case a.size %}{% when 1 %}1{% when 2 %}2{% else %}else{% endcase %}',
{ 'a' => [1, 1, 1, 1, 1] })
{ 'a' => [1, 1, 1, 1, 1] },
)
end
def test_case_on_length_with_else
assert_template_result('else',
assert_template_result(
'else',
'{% case a.empty? %}{% when true %}true{% when false %}false{% else %}else{% endcase %}',
{})
{},
)
assert_template_result('false',
assert_template_result(
'false',
'{% case false %}{% when true %}true{% when false %}false{% else %}else{% endcase %}',
{})
{},
)
assert_template_result('true',
assert_template_result(
'true',
'{% case true %}{% when true %}true{% when false %}false{% else %}else{% endcase %}',
{})
{},
)
assert_template_result('else',
assert_template_result(
'else',
'{% case NULL %}{% when true %}true{% when false %}false{% else %}else{% endcase %}',
{})
{},
)
end
def test_assign_from_case
@@ -253,24 +297,33 @@ class StandardTagTest < Minitest::Test
assert_template_result('one two one', '{%cycle "one", "two"%} {%cycle "one", "two"%} {%cycle "one", "two"%}')
assert_template_result('text-align: left text-align: right',
'{%cycle "text-align: left", "text-align: right" %} {%cycle "text-align: left", "text-align: right"%}')
assert_template_result(
'text-align: left text-align: right',
'{%cycle "text-align: left", "text-align: right" %} {%cycle "text-align: left", "text-align: right"%}',
)
end
def test_multiple_cycles
assert_template_result('1 2 1 1 2 3 1',
'{%cycle 1,2%} {%cycle 1,2%} {%cycle 1,2%} {%cycle 1,2,3%} {%cycle 1,2,3%} {%cycle 1,2,3%} {%cycle 1,2,3%}')
assert_template_result(
'1 2 1 1 2 3 1',
'{%cycle 1,2%} {%cycle 1,2%} {%cycle 1,2%} {%cycle 1,2,3%} {%cycle 1,2,3%} {%cycle 1,2,3%} {%cycle 1,2,3%}',
)
end
def test_multiple_named_cycles
assert_template_result('one one two two one one',
'{%cycle 1: "one", "two" %} {%cycle 2: "one", "two" %} {%cycle 1: "one", "two" %} {%cycle 2: "one", "two" %} {%cycle 1: "one", "two" %} {%cycle 2: "one", "two" %}')
assert_template_result(
'one one two two one one',
'{%cycle 1: "one", "two" %} {%cycle 2: "one", "two" %} {%cycle 1: "one", "two" %} {%cycle 2: "one", "two" %} {%cycle 1: "one", "two" %} {%cycle 2: "one", "two" %}',
)
end
def test_multiple_named_cycles_with_names_from_context
assigns = { "var1" => 1, "var2" => 2 }
assert_template_result('one one two two one one',
'{%cycle var1: "one", "two" %} {%cycle var2: "one", "two" %} {%cycle var1: "one", "two" %} {%cycle var2: "one", "two" %} {%cycle var1: "one", "two" %} {%cycle var2: "one", "two" %}', assigns)
assert_template_result(
'one one two two one one',
'{%cycle var1: "one", "two" %} {%cycle var2: "one", "two" %} {%cycle var1: "one", "two" %} {%cycle var2: "one", "two" %} {%cycle var1: "one", "two" %} {%cycle var2: "one", "two" %}',
assigns,
)
end
def test_size_of_array
+98 -22
View File
@@ -18,52 +18,70 @@ class TableRowTest < Minitest::Test
end
def test_table_row
assert_template_result("<tr class=\"row1\">\n<td class=\"col1\"> 1 </td><td class=\"col2\"> 2 </td><td class=\"col3\"> 3 </td></tr>\n<tr class=\"row2\"><td class=\"col1\"> 4 </td><td class=\"col2\"> 5 </td><td class=\"col3\"> 6 </td></tr>\n",
assert_template_result(
"<tr class=\"row1\">\n<td class=\"col1\"> 1 </td><td class=\"col2\"> 2 </td><td class=\"col3\"> 3 </td></tr>\n<tr class=\"row2\"><td class=\"col1\"> 4 </td><td class=\"col2\"> 5 </td><td class=\"col3\"> 6 </td></tr>\n",
'{% tablerow n in numbers cols:3%} {{n}} {% endtablerow %}',
{ 'numbers' => [1, 2, 3, 4, 5, 6] })
{ 'numbers' => [1, 2, 3, 4, 5, 6] },
)
assert_template_result("<tr class=\"row1\">\n</tr>\n",
assert_template_result(
"<tr class=\"row1\">\n</tr>\n",
'{% tablerow n in numbers cols:3%} {{n}} {% endtablerow %}',
{ 'numbers' => [] })
{ 'numbers' => [] },
)
end
def test_table_row_with_different_cols
assert_template_result("<tr class=\"row1\">\n<td class=\"col1\"> 1 </td><td class=\"col2\"> 2 </td><td class=\"col3\"> 3 </td><td class=\"col4\"> 4 </td><td class=\"col5\"> 5 </td></tr>\n<tr class=\"row2\"><td class=\"col1\"> 6 </td></tr>\n",
assert_template_result(
"<tr class=\"row1\">\n<td class=\"col1\"> 1 </td><td class=\"col2\"> 2 </td><td class=\"col3\"> 3 </td><td class=\"col4\"> 4 </td><td class=\"col5\"> 5 </td></tr>\n<tr class=\"row2\"><td class=\"col1\"> 6 </td></tr>\n",
'{% tablerow n in numbers cols:5%} {{n}} {% endtablerow %}',
{ 'numbers' => [1, 2, 3, 4, 5, 6] })
{ 'numbers' => [1, 2, 3, 4, 5, 6] },
)
end
def test_table_col_counter
assert_template_result("<tr class=\"row1\">\n<td class=\"col1\">1</td><td class=\"col2\">2</td></tr>\n<tr class=\"row2\"><td class=\"col1\">1</td><td class=\"col2\">2</td></tr>\n<tr class=\"row3\"><td class=\"col1\">1</td><td class=\"col2\">2</td></tr>\n",
assert_template_result(
"<tr class=\"row1\">\n<td class=\"col1\">1</td><td class=\"col2\">2</td></tr>\n<tr class=\"row2\"><td class=\"col1\">1</td><td class=\"col2\">2</td></tr>\n<tr class=\"row3\"><td class=\"col1\">1</td><td class=\"col2\">2</td></tr>\n",
'{% tablerow n in numbers cols:2%}{{tablerowloop.col}}{% endtablerow %}',
{ 'numbers' => [1, 2, 3, 4, 5, 6] })
{ 'numbers' => [1, 2, 3, 4, 5, 6] },
)
end
def test_quoted_fragment
assert_template_result("<tr class=\"row1\">\n<td class=\"col1\"> 1 </td><td class=\"col2\"> 2 </td><td class=\"col3\"> 3 </td></tr>\n<tr class=\"row2\"><td class=\"col1\"> 4 </td><td class=\"col2\"> 5 </td><td class=\"col3\"> 6 </td></tr>\n",
assert_template_result(
"<tr class=\"row1\">\n<td class=\"col1\"> 1 </td><td class=\"col2\"> 2 </td><td class=\"col3\"> 3 </td></tr>\n<tr class=\"row2\"><td class=\"col1\"> 4 </td><td class=\"col2\"> 5 </td><td class=\"col3\"> 6 </td></tr>\n",
"{% tablerow n in collections.frontpage cols:3%} {{n}} {% endtablerow %}",
{ 'collections' => { 'frontpage' => [1, 2, 3, 4, 5, 6] } })
assert_template_result("<tr class=\"row1\">\n<td class=\"col1\"> 1 </td><td class=\"col2\"> 2 </td><td class=\"col3\"> 3 </td></tr>\n<tr class=\"row2\"><td class=\"col1\"> 4 </td><td class=\"col2\"> 5 </td><td class=\"col3\"> 6 </td></tr>\n",
{ 'collections' => { 'frontpage' => [1, 2, 3, 4, 5, 6] } },
)
assert_template_result(
"<tr class=\"row1\">\n<td class=\"col1\"> 1 </td><td class=\"col2\"> 2 </td><td class=\"col3\"> 3 </td></tr>\n<tr class=\"row2\"><td class=\"col1\"> 4 </td><td class=\"col2\"> 5 </td><td class=\"col3\"> 6 </td></tr>\n",
"{% tablerow n in collections['frontpage'] cols:3%} {{n}} {% endtablerow %}",
{ 'collections' => { 'frontpage' => [1, 2, 3, 4, 5, 6] } })
{ 'collections' => { 'frontpage' => [1, 2, 3, 4, 5, 6] } },
)
end
def test_enumerable_drop
assert_template_result("<tr class=\"row1\">\n<td class=\"col1\"> 1 </td><td class=\"col2\"> 2 </td><td class=\"col3\"> 3 </td></tr>\n<tr class=\"row2\"><td class=\"col1\"> 4 </td><td class=\"col2\"> 5 </td><td class=\"col3\"> 6 </td></tr>\n",
assert_template_result(
"<tr class=\"row1\">\n<td class=\"col1\"> 1 </td><td class=\"col2\"> 2 </td><td class=\"col3\"> 3 </td></tr>\n<tr class=\"row2\"><td class=\"col1\"> 4 </td><td class=\"col2\"> 5 </td><td class=\"col3\"> 6 </td></tr>\n",
'{% tablerow n in numbers cols:3%} {{n}} {% endtablerow %}',
{ 'numbers' => ArrayDrop.new([1, 2, 3, 4, 5, 6]) })
{ 'numbers' => ArrayDrop.new([1, 2, 3, 4, 5, 6]) },
)
end
def test_offset_and_limit
assert_template_result("<tr class=\"row1\">\n<td class=\"col1\"> 1 </td><td class=\"col2\"> 2 </td><td class=\"col3\"> 3 </td></tr>\n<tr class=\"row2\"><td class=\"col1\"> 4 </td><td class=\"col2\"> 5 </td><td class=\"col3\"> 6 </td></tr>\n",
assert_template_result(
"<tr class=\"row1\">\n<td class=\"col1\"> 1 </td><td class=\"col2\"> 2 </td><td class=\"col3\"> 3 </td></tr>\n<tr class=\"row2\"><td class=\"col1\"> 4 </td><td class=\"col2\"> 5 </td><td class=\"col3\"> 6 </td></tr>\n",
'{% tablerow n in numbers cols:3 offset:1 limit:6%} {{n}} {% endtablerow %}',
{ 'numbers' => [0, 1, 2, 3, 4, 5, 6, 7] })
{ 'numbers' => [0, 1, 2, 3, 4, 5, 6, 7] },
)
end
def test_blank_string_not_iterable
assert_template_result("<tr class=\"row1\">\n</tr>\n",
assert_template_result(
"<tr class=\"row1\">\n</tr>\n",
"{% tablerow char in characters cols:3 %}I WILL NOT BE OUTPUT{% endtablerow %}",
{ 'characters' => '' })
{ 'characters' => '' },
)
end
def test_cols_nil_constant_same_as_evaluated_nil_expression
@@ -72,12 +90,50 @@ class TableRowTest < Minitest::Test
"<td class=\"col2\">false</td>" \
"</tr>\n"
assert_template_result(expect,
"{% tablerow i in (1..2) cols:nil %}{{ tablerowloop.col_last }}{% endtablerow %}")
assert_template_result(
expect,
"{% tablerow i in (1..2) cols:nil %}{{ tablerowloop.col_last }}{% endtablerow %}",
)
assert_template_result(expect,
assert_template_result(
expect,
"{% tablerow i in (1..2) cols:var %}{{ tablerowloop.col_last }}{% endtablerow %}",
{ "var" => nil })
{ "var" => nil },
)
end
def test_nil_limit_is_treated_as_zero
expect = "<tr class=\"row1\">\n" \
"</tr>\n"
assert_template_result(
expect,
"{% tablerow i in (1..2) limit:nil %}{{ i }}{% endtablerow %}",
)
assert_template_result(
expect,
"{% tablerow i in (1..2) limit:var %}{{ i }}{% endtablerow %}",
{ "var" => nil },
)
end
def test_nil_offset_is_treated_as_zero
expect = "<tr class=\"row1\">\n" \
"<td class=\"col1\">1:false</td>" \
"<td class=\"col2\">2:true</td>" \
"</tr>\n"
assert_template_result(
expect,
"{% tablerow i in (1..2) offset:nil %}{{ i }}:{{ tablerowloop.col_last }}{% endtablerow %}",
)
assert_template_result(
expect,
"{% tablerow i in (1..2) offset:var %}{{ i }}:{{ tablerowloop.col_last }}{% endtablerow %}",
{ "var" => nil },
)
end
def test_tablerow_loop_drop_attributes
@@ -131,4 +187,24 @@ class TableRowTest < Minitest::Test
assert_template_result(expected_output, template)
end
def test_table_row_renders_correct_error_message_for_invalid_parameters
assert_template_result(
"Liquid error (line 1): invalid integer",
'{% tablerow n in (1...10) limit:true %} {{n}} {% endtablerow %}',
render_errors: true,
)
assert_template_result(
"Liquid error (line 1): invalid integer",
'{% tablerow n in (1...10) offset:true %} {{n}} {% endtablerow %}',
render_errors: true,
)
assert_template_result(
"Liquid error (line 1): invalid integer",
'{% tablerow n in (1...10) cols:true %} {{n}} {% endtablerow %}',
render_errors: true,
)
end
end
@@ -7,8 +7,10 @@ class UnlessElseTagTest < Minitest::Test
def test_unless
assert_template_result(' ', ' {% unless true %} this text should not go into the output {% endunless %} ')
assert_template_result(' this text should go into the output ',
' {% unless false %} this text should go into the output {% endunless %} ')
assert_template_result(
' this text should go into the output ',
' {% unless false %} this text should go into the output {% endunless %} ',
)
assert_template_result(' you rock ?', '{% unless true %} you suck {% endunless %} {% unless false %} you rock {% endunless %}?')
end
+24 -4
View File
@@ -93,10 +93,12 @@ class TemplateTest < Minitest::Test
def test_lambda_is_called_once_from_custom_assigns_over_multiple_parses_and_renders
t = Template.new
assigns = { 'number' => -> {
@global ||= 0
@global += 1
} }
assigns = {
'number' => -> {
@global ||= 0
@global += 1
},
}
assert_equal('1', t.parse("{{number}}").render!(assigns))
assert_equal('1', t.parse("{{number}}").render!(assigns))
assert_equal('1', t.render!(assigns))
@@ -335,4 +337,22 @@ class TemplateTest < Minitest::Test
assert_equal("x=2", output)
assert_instance_of(String, output)
end
def test_raises_error_with_invalid_utf8
e = assert_raises(TemplateEncodingError) do
Template.parse(<<~LIQUID)
{% comment %}
\xC0
{% endcomment %}
LIQUID
end
assert_equal('Liquid error: Invalid template encoding', e.message)
end
def test_allows_non_string_values_as_source
assert_equal('', Template.parse(nil).render)
assert_equal('1', Template.parse(1).render)
assert_equal('true', Template.parse(true).render)
end
end
+70
View File
@@ -1,6 +1,7 @@
# frozen_string_literal: true
require 'test_helper'
require 'timeout'
class VariableTest < Minitest::Test
include Liquid
@@ -33,6 +34,7 @@ class VariableTest < Minitest::Test
assert_template_result('', '{% if foo %}true{% endif %}', { 'foo' => BooleanDrop.new(false) })
assert_template_result('', '{% if foo == true %}True{% endif %}', { 'foo' => BooleanDrop.new(false) })
assert_template_result('', '{% if foo and true %}SHOULD NOT HAPPEN{% endif %}', { 'foo' => BooleanDrop.new(false) })
assert_template_result('one', '{% if a contains x %}one{% endif %}', { 'a' => [1], 'x' => IntegerDrop.new(1) })
end
@@ -135,4 +137,72 @@ class VariableTest < Minitest::Test
def test_raw_value_variable
assert_template_result('bar', '{{ [key] }}', { 'key' => 'foo', 'foo' => 'bar' })
end
def test_dynamic_find_var_with_drop
assert_template_result(
'bar',
'{{ [list[settings.zero]] }}',
{
'list' => ['foo'],
'settings' => SettingsDrop.new("zero" => 0),
'foo' => 'bar',
},
)
assert_template_result(
'foo',
'{{ [list[settings.zero]["foo"]] }}',
{
'list' => [{ 'foo' => 'bar' }],
'settings' => SettingsDrop.new("zero" => 0),
'bar' => 'foo',
},
)
end
def test_double_nested_variable_lookup
assert_template_result(
'bar',
'{{ list[list[settings.zero]]["foo"] }}',
{
'list' => [1, { 'foo' => 'bar' }],
'settings' => SettingsDrop.new("zero" => 0),
'bar' => 'foo',
},
)
end
def test_variable_lookup_should_not_hang_with_invalid_syntax
Timeout.timeout(1) do
assert_template_result(
'bar',
"{{['foo'}}",
{
'foo' => 'bar',
},
error_mode: :lax,
)
end
very_long_key = "1234567890" * 100
template_list = [
"{{['#{very_long_key}']}}", # valid
"{{['#{very_long_key}'}}", # missing closing bracket
"{{[['#{very_long_key}']}}", # extra open bracket
]
template_list.each do |template|
Timeout.timeout(1) do
assert_template_result(
'bar',
template,
{
very_long_key => 'bar',
},
error_mode: :lax,
)
end
end
end
end
+18 -4
View File
@@ -39,11 +39,12 @@ module Minitest
def assert_template_result(
expected, template, assigns = {},
message: nil, partials: nil, error_mode: nil, render_errors: false
message: nil, partials: nil, error_mode: nil, render_errors: false,
template_factory: nil
)
template = Liquid::Template.parse(template, line_numbers: true, error_mode: error_mode&.to_sym)
file_system = StubFileSystem.new(partials || {})
registers = Liquid::Registers.new(file_system: file_system)
registers = Liquid::Registers.new(file_system: file_system, template_factory: template_factory)
context = Liquid::Context.build(static_environments: assigns, rethrow_errors: !render_errors, registers: registers)
output = template.render(context)
assert_equal(expected, output, message)
@@ -125,6 +126,17 @@ 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()
@@ -198,8 +210,10 @@ class StubTemplateFactory
@count = 0
end
def for(_template_name)
def for(template_name)
@count += 1
Liquid::Template.new
template = Liquid::Template.new
template.name = "some/path/" + template_name
template
end
end
+4 -2
View File
@@ -35,8 +35,10 @@ class BlockUnitTest < Minitest::Test
def test_variable_many_embedded_fragments
template = Liquid::Template.parse(" {{funk}} {{so}} {{brother}} ")
assert_equal(7, template.root.nodelist.size)
assert_equal([String, Variable, String, Variable, String, Variable, String],
block_types(template.root.nodelist))
assert_equal(
[String, Variable, String, Variable, String, Variable, String],
block_types(template.root.nodelist),
)
end
def test_with_block
+13 -4
View File
@@ -55,6 +55,11 @@ class ConditionUnitTest < Minitest::Test
assert_evaluates_false('bob', 'contains', '---')
end
def test_contains_binary_encoding_compatibility_with_utf8
assert_evaluates_true('🙈'.b, 'contains', '🙈')
assert_evaluates_true('🙈', 'contains', '🙈'.b)
end
def test_invalid_comparation_operator
assert_evaluates_argument_error(1, '~~', 0)
end
@@ -164,13 +169,17 @@ class ConditionUnitTest < Minitest::Test
private
def assert_evaluates_true(left, op, right)
assert(Condition.new(left, op, right).evaluate(@context),
"Evaluated false: #{left} #{op} #{right}")
assert(
Condition.new(left, op, right).evaluate(@context),
"Evaluated false: #{left.inspect} #{op} #{right.inspect}",
)
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.inspect} #{op} #{right.inspect}",
)
end
def assert_evaluates_argument_error(left, op, right)
+36 -36
View File
@@ -8,245 +8,245 @@ class ParseTreeVisitorTest < Minitest::Test
def test_variable
assert_equal(
["test"],
visit(%({{ test }}))
visit(%({{ test }})),
)
end
def test_varible_with_filter
assert_equal(
["test", "infilter"],
visit(%({{ test | split: infilter }}))
visit(%({{ test | split: infilter }})),
)
end
def test_dynamic_variable
assert_equal(
["test", "inlookup"],
visit(%({{ test[inlookup] }}))
visit(%({{ test[inlookup] }})),
)
end
def test_echo
assert_equal(
["test"],
visit(%({% echo test %}))
visit(%({% echo test %})),
)
end
def test_if_condition
assert_equal(
["test"],
visit(%({% if test %}{% endif %}))
visit(%({% if test %}{% endif %})),
)
end
def test_complex_if_condition
assert_equal(
["test"],
visit(%({% if 1 == 1 and 2 == test %}{% endif %}))
visit(%({% if 1 == 1 and 2 == test %}{% endif %})),
)
end
def test_if_body
assert_equal(
["test"],
visit(%({% if 1 == 1 %}{{ test }}{% endif %}))
visit(%({% if 1 == 1 %}{{ test }}{% endif %})),
)
end
def test_unless_condition
assert_equal(
["test"],
visit(%({% unless test %}{% endunless %}))
visit(%({% unless test %}{% endunless %})),
)
end
def test_complex_unless_condition
assert_equal(
["test"],
visit(%({% unless 1 == 1 and 2 == test %}{% endunless %}))
visit(%({% unless 1 == 1 and 2 == test %}{% endunless %})),
)
end
def test_unless_body
assert_equal(
["test"],
visit(%({% unless 1 == 1 %}{{ test }}{% endunless %}))
visit(%({% unless 1 == 1 %}{{ test }}{% endunless %})),
)
end
def test_elsif_condition
assert_equal(
["test"],
visit(%({% if 1 == 1 %}{% elsif test %}{% endif %}))
visit(%({% if 1 == 1 %}{% elsif test %}{% endif %})),
)
end
def test_complex_elsif_condition
assert_equal(
["test"],
visit(%({% if 1 == 1 %}{% elsif 1 == 1 and 2 == test %}{% endif %}))
visit(%({% if 1 == 1 %}{% elsif 1 == 1 and 2 == test %}{% endif %})),
)
end
def test_elsif_body
assert_equal(
["test"],
visit(%({% if 1 == 1 %}{% elsif 2 == 2 %}{{ test }}{% endif %}))
visit(%({% if 1 == 1 %}{% elsif 2 == 2 %}{{ test }}{% endif %})),
)
end
def test_else_body
assert_equal(
["test"],
visit(%({% if 1 == 1 %}{% else %}{{ test }}{% endif %}))
visit(%({% if 1 == 1 %}{% else %}{{ test }}{% endif %})),
)
end
def test_case_left
assert_equal(
["test"],
visit(%({% case test %}{% endcase %}))
visit(%({% case test %}{% endcase %})),
)
end
def test_case_condition
assert_equal(
["test"],
visit(%({% case 1 %}{% when test %}{% endcase %}))
visit(%({% case 1 %}{% when test %}{% endcase %})),
)
end
def test_case_when_body
assert_equal(
["test"],
visit(%({% case 1 %}{% when 2 %}{{ test }}{% endcase %}))
visit(%({% case 1 %}{% when 2 %}{{ test }}{% endcase %})),
)
end
def test_case_else_body
assert_equal(
["test"],
visit(%({% case 1 %}{% else %}{{ test }}{% endcase %}))
visit(%({% case 1 %}{% else %}{{ test }}{% endcase %})),
)
end
def test_for_in
assert_equal(
["test"],
visit(%({% for x in test %}{% endfor %}))
visit(%({% for x in test %}{% endfor %})),
)
end
def test_for_limit
assert_equal(
["test"],
visit(%({% for x in (1..5) limit: test %}{% endfor %}))
visit(%({% for x in (1..5) limit: test %}{% endfor %})),
)
end
def test_for_offset
assert_equal(
["test"],
visit(%({% for x in (1..5) offset: test %}{% endfor %}))
visit(%({% for x in (1..5) offset: test %}{% endfor %})),
)
end
def test_for_body
assert_equal(
["test"],
visit(%({% for x in (1..5) %}{{ test }}{% endfor %}))
visit(%({% for x in (1..5) %}{{ test }}{% endfor %})),
)
end
def test_for_range
assert_equal(
["test"],
visit(%({% for x in (1..test) %}{% endfor %}))
visit(%({% for x in (1..test) %}{% endfor %})),
)
end
def test_tablerow_in
assert_equal(
["test"],
visit(%({% tablerow x in test %}{% endtablerow %}))
visit(%({% tablerow x in test %}{% endtablerow %})),
)
end
def test_tablerow_limit
assert_equal(
["test"],
visit(%({% tablerow x in (1..5) limit: test %}{% endtablerow %}))
visit(%({% tablerow x in (1..5) limit: test %}{% endtablerow %})),
)
end
def test_tablerow_offset
assert_equal(
["test"],
visit(%({% tablerow x in (1..5) offset: test %}{% endtablerow %}))
visit(%({% tablerow x in (1..5) offset: test %}{% endtablerow %})),
)
end
def test_tablerow_body
assert_equal(
["test"],
visit(%({% tablerow x in (1..5) %}{{ test }}{% endtablerow %}))
visit(%({% tablerow x in (1..5) %}{{ test }}{% endtablerow %})),
)
end
def test_cycle
assert_equal(
["test"],
visit(%({% cycle test %}))
visit(%({% cycle test %})),
)
end
def test_assign
assert_equal(
["test"],
visit(%({% assign x = test %}))
visit(%({% assign x = test %})),
)
end
def test_capture
assert_equal(
["test"],
visit(%({% capture x %}{{ test }}{% endcapture %}))
visit(%({% capture x %}{{ test }}{% endcapture %})),
)
end
def test_include
assert_equal(
["test"],
visit(%({% include test %}))
visit(%({% include test %})),
)
end
def test_include_with
assert_equal(
["test"],
visit(%({% include "hai" with test %}))
visit(%({% include "hai" with test %})),
)
end
def test_include_for
assert_equal(
["test"],
visit(%({% include "hai" for test %}))
visit(%({% include "hai" for test %})),
)
end
def test_render_with
assert_equal(
["test"],
visit(%({% render "hai" with test %}))
visit(%({% render "hai" with test %})),
)
end
def test_render_for
assert_equal(
["test"],
visit(%({% render "hai" for test %}))
visit(%({% render "hai" for test %})),
)
end
@@ -257,7 +257,7 @@ class ParseTreeVisitorTest < Minitest::Test
["test", []],
["xs", []],
]]],
traversal(%({% for x in xs offset: test %}{{ other }}{% endfor %})).visit
traversal(%({% for x in xs offset: test %}{{ other }}{% endfor %})).visit,
)
end
+61 -20
View File
@@ -7,13 +7,13 @@ class PartialCacheUnitTest < Minitest::Test
context = Liquid::Context.build(
registers: {
file_system: StubFileSystem.new('my_partial' => 'my partial body'),
}
},
)
partial = Liquid::PartialCache.load(
'my_partial',
context: context,
parse_context: Liquid::ParseContext.new
parse_context: Liquid::ParseContext.new,
)
assert_equal('my partial body', partial.render)
@@ -22,14 +22,14 @@ class PartialCacheUnitTest < Minitest::Test
def test_reads_from_the_file_system_only_once_per_file
file_system = StubFileSystem.new('my_partial' => 'some partial body')
context = Liquid::Context.build(
registers: { file_system: file_system }
registers: { file_system: file_system },
)
2.times do
Liquid::PartialCache.load(
'my_partial',
context: context,
parse_context: Liquid::ParseContext.new
parse_context: Liquid::ParseContext.new,
)
end
@@ -39,31 +39,31 @@ class PartialCacheUnitTest < Minitest::Test
def test_cache_state_is_stored_per_context
parse_context = Liquid::ParseContext.new
shared_file_system = StubFileSystem.new(
'my_partial' => 'my shared value'
'my_partial' => 'my shared value',
)
context_one = Liquid::Context.build(
registers: {
file_system: shared_file_system,
}
},
)
context_two = Liquid::Context.build(
registers: {
file_system: shared_file_system,
}
},
)
2.times do
Liquid::PartialCache.load(
'my_partial',
context: context_one,
parse_context: parse_context
parse_context: parse_context,
)
end
Liquid::PartialCache.load(
'my_partial',
context: context_two,
parse_context: parse_context
parse_context: parse_context,
)
assert_equal(2, shared_file_system.file_read_count)
@@ -72,18 +72,18 @@ class PartialCacheUnitTest < Minitest::Test
def test_cache_is_not_broken_when_a_different_parse_context_is_used
file_system = StubFileSystem.new('my_partial' => 'some partial body')
context = Liquid::Context.build(
registers: { file_system: file_system }
registers: { file_system: file_system },
)
Liquid::PartialCache.load(
'my_partial',
context: context,
parse_context: Liquid::ParseContext.new(my_key: 'value one')
parse_context: Liquid::ParseContext.new(my_key: 'value one'),
)
Liquid::PartialCache.load(
'my_partial',
context: context,
parse_context: Liquid::ParseContext.new(my_key: 'value two')
parse_context: Liquid::ParseContext.new(my_key: 'value two'),
)
# Technically what we care about is that the file was parsed twice,
@@ -95,13 +95,13 @@ class PartialCacheUnitTest < Minitest::Test
context = Liquid::Context.build(
registers: {
file_system: StubFileSystem.new('my_partial' => 'my partial body'),
}
},
)
partial = Liquid::PartialCache.load(
'my_partial',
context: context,
parse_context: Liquid::ParseContext.new
parse_context: Liquid::ParseContext.new,
)
assert_equal('my partial body', partial.render)
@@ -113,13 +113,13 @@ class PartialCacheUnitTest < Minitest::Test
registers: {
file_system: StubFileSystem.new('my_partial' => 'my partial body'),
template_factory: template_factory,
}
},
)
partial = Liquid::PartialCache.load(
'my_partial',
context: context,
parse_context: Liquid::ParseContext.new
parse_context: Liquid::ParseContext.new,
)
assert_equal('my partial body', partial.render)
@@ -129,12 +129,12 @@ class PartialCacheUnitTest < Minitest::Test
def test_cache_state_is_shared_for_subcontexts
parse_context = Liquid::ParseContext.new
shared_file_system = StubFileSystem.new(
'my_partial' => 'my shared value'
'my_partial' => 'my shared value',
)
context = Liquid::Context.build(
registers: Liquid::Registers.new(
file_system: shared_file_system,
)
),
)
subcontext = context.new_isolated_subcontext
@@ -144,16 +144,57 @@ class PartialCacheUnitTest < Minitest::Test
Liquid::PartialCache.load(
'my_partial',
context: context,
parse_context: parse_context
parse_context: parse_context,
)
Liquid::PartialCache.load(
'my_partial',
context: subcontext,
parse_context: parse_context
parse_context: parse_context,
)
end
assert_equal(1, shared_file_system.file_read_count)
end
def test_uses_template_name_from_template_factory
template_factory = StubTemplateFactory.new
context = Liquid::Context.build(
registers: {
file_system: StubFileSystem.new('my_partial' => 'my partial body'),
template_factory: template_factory,
},
)
partial = Liquid::PartialCache.load(
'my_partial',
context: context,
parse_context: Liquid::ParseContext.new,
)
assert_equal('some/path/my_partial', partial.name)
end
def test_includes_error_mode_into_template_cache
template_factory = StubTemplateFactory.new
context = Liquid::Context.build(
registers: {
file_system: StubFileSystem.new('my_partial' => 'my partial body'),
template_factory: template_factory,
},
)
[:lax, :warn, :strict].each do |error_mode|
Liquid::PartialCache.load(
'my_partial',
context: context,
parse_context: Liquid::ParseContext.new(error_mode: error_mode),
)
end
assert_equal(
["my_partial:lax", "my_partial:warn", "my_partial:strict"],
context.registers[:cached_partials].keys,
)
end
end
+13
View File
@@ -1,6 +1,7 @@
# frozen_string_literal: true
require 'test_helper'
require 'timeout'
class RegexpUnitTest < Minitest::Test
include Liquid
@@ -37,10 +38,22 @@ class RegexpUnitTest < Minitest::Test
def test_variable_parser
assert_equal(['var'], 'var'.scan(VariableParser))
assert_equal(['[var]'], '[var]'.scan(VariableParser))
assert_equal(['var', 'method'], 'var.method'.scan(VariableParser))
assert_equal(['var', '[method]'], 'var[method]'.scan(VariableParser))
assert_equal(['var', '[method]', '[0]'], 'var[method][0]'.scan(VariableParser))
assert_equal(['var', '["method"]', '[0]'], 'var["method"][0]'.scan(VariableParser))
assert_equal(['var', '[method]', '[0]', 'method'], 'var[method][0].method'.scan(VariableParser))
end
def test_variable_parser_with_large_input
Timeout.timeout(1) { assert_equal(['[var]'], '[var]'.scan(VariableParser)) }
very_long_string = "foo" * 1000
# valid dynamic lookup
Timeout.timeout(1) { assert_equal(["[#{very_long_string}]"], "[#{very_long_string}]".scan(VariableParser)) }
# invalid dynamic lookup with missing closing bracket
Timeout.timeout(1) { assert_equal([very_long_string], "[#{very_long_string}".scan(VariableParser)) }
end
end # RegexpTest
+1 -1
View File
@@ -50,7 +50,7 @@ class StrainerFactoryUnitTest < Minitest::Test
assert_match(
/\ALiquid error: wrong number of arguments \((1 for 0|given 1, expected 0)\)\z/,
exception.message
exception.message,
)
source = AccessScopeFilters.instance_method(:public_filter).source_location
assert_equal(source.map(&:to_s), exception.backtrace[0].split(':')[0..1])
+9
View File
@@ -20,4 +20,13 @@ class TagUnitTest < Minitest::Test
tag = Tag.parse("some_tag", "", Tokenizer.new(""), ParseContext.new)
assert_equal('some_tag', tag.tag_name)
end
class CustomTag < Liquid::Tag
def render(_context); end
end
def test_tag_render_to_output_buffer_nil_value
custom_tag = CustomTag.parse("some_tag", "", Tokenizer.new(""), ParseContext.new)
assert_equal('some string', custom_tag.render_to_output_buffer(Context.new, "some string"))
end
end
+202
View File
@@ -0,0 +1,202 @@
# frozen_string_literal: true
require 'test_helper'
class CommentTagUnitTest < Minitest::Test
def test_comment_inside_liquid_tag
assert_template_result("", <<~LIQUID.chomp)
{% liquid
if 1 != 1
comment
else
echo 123
endcomment
endif
%}
LIQUID
end
def test_does_not_parse_nodes_inside_a_comment
assert_template_result("", <<~LIQUID.chomp)
{% comment %}
{% if true %}
{% if ... %}
{%- for ? -%}
{% while true %}
{%
unless if
%}
{% endcase %}
{% endcomment %}
LIQUID
end
def test_allows_unclosed_tags
assert_template_result('', <<~LIQUID.chomp)
{% comment %}
{% if true %}
{% endcomment %}
LIQUID
end
def test_open_tags_in_comment
assert_template_result('', <<~LIQUID.chomp)
{% comment %}
{% assign a = 123 {% comment %}
{% endcomment %}
LIQUID
assert_raises(Liquid::SyntaxError) do
assert_template_result("", <<~LIQUID.chomp)
{% comment %}
{% assign foo = "1"
{% endcomment %}
LIQUID
end
assert_raises(Liquid::SyntaxError) do
assert_template_result("", <<~LIQUID.chomp)
{% comment %}
{% comment %}
{% invalid
{% endcomment %}
{% endcomment %}
LIQUID
end
assert_raises(Liquid::SyntaxError) do
assert_template_result("", <<~LIQUID.chomp)
{% comment %}
{% {{ {%- endcomment %}
LIQUID
end
end
def test_child_comment_tags_need_to_be_closed
assert_template_result("", <<~LIQUID.chomp)
{% comment %}
{% comment %}
{% comment %}{% endcomment %}
{% endcomment %}
{% endcomment %}
LIQUID
assert_raises(Liquid::SyntaxError) do
assert_template_result("", <<~LIQUID.chomp)
{% comment %}
{% comment %}
{% comment %}
{% endcomment %}
{% endcomment %}
LIQUID
end
end
def test_child_raw_tags_need_to_be_closed
assert_template_result("", <<~LIQUID.chomp)
{% comment %}
{% raw %}
{% endcomment %}
{% endraw %}
{% endcomment %}
LIQUID
assert_raises(Liquid::SyntaxError) do
Liquid::Template.parse(<<~LIQUID.chomp)
{% comment %}
{% raw %}
{% endcomment %}
{% endcomment %}
LIQUID
end
end
def test_error_line_number_is_correct
template = Liquid::Template.parse(<<~LIQUID.chomp, line_numbers: true)
{% comment %}
{% if true %}
{% endcomment %}
{{ errors.standard_error }}
LIQUID
output = template.render('errors' => ErrorDrop.new)
expected = <<~TEXT.chomp
Liquid error (line 4): standard error
TEXT
assert_equal(expected, output)
end
def test_comment_tag_delimiter_with_extra_strings
assert_template_result(
'',
<<~LIQUID.chomp,
{% comment %}
{% comment %}
{% endcomment
{% if true %}
{% endif %}
{% endcomment %}
LIQUID
)
end
def test_nested_comment_tag_with_extra_strings
assert_template_result(
'',
<<~LIQUID.chomp,
{% comment %}
{% comment
{% assign foo = 1 %}
{% endcomment
{% assign foo = 1 %}
{% endcomment %}
LIQUID
)
end
def test_ignores_delimiter_with_extra_strings
assert_template_result(
'',
<<~LIQUID.chomp,
{% if true %}
{% comment %}
{% commentXXXXX %}wut{% endcommentXXXXX %}
{% endcomment %}
{% endif %}
LIQUID
)
end
def test_delimiter_can_have_extra_strings
assert_template_result('', "{% comment %}123{% endcomment xyz %}")
assert_template_result('', "{% comment %}123{% endcomment\txyz %}")
assert_template_result('', "{% comment %}123{% endcomment\nxyz %}")
assert_template_result('', "{% comment %}123{% endcomment\n xyz endcomment %}")
assert_template_result('', "{%comment}{% assign a = 1 %}{%endcomment}{% endif %}")
end
def test_with_whitespace_control
assert_template_result("Hello!", " {%- comment -%}123{%- endcomment -%}Hello!")
assert_template_result("Hello!", "{%- comment -%}123{%- endcomment -%} Hello!")
assert_template_result("Hello!", " {%- comment -%}123{%- endcomment -%} Hello!")
assert_template_result("Hello!", <<~LIQUID.chomp)
{%- comment %}Whitespace control!{% endcomment -%}
Hello!
LIQUID
end
def test_dont_override_liquid_tag_whitespace_control
assert_template_result("Hello!World!", <<~LIQUID.chomp)
Hello!
{%- liquid
comment
this is inside a liquid tag
endcomment
-%}
World!
LIQUID
end
end