require "spec_helper"
require 'rails'
require 'action_view'
require 'nokogiri'
require 'htmlentities'
describe Liquor do
it "supports {% assign %} and {% declare %}" do
exec(%Q{
{% assign test = 'hello world' %}
{{ test }}
}).strip.should == 'hello world'
expect {
exec(%Q{
{% assign test = 1 %}
{% assign test = 2 %}
{{ test }}
}).strip.should == '2'
}.to_not raise_error
expect {
exec(%Q{
{% declare test = 1 %}
{% declare test = 2 %}
{{ test }}
}).strip.should == '2'
}.to_not raise_error
expect {
exec(%Q{
{% assign true = false %}
})
}.to raise_error(Liquor::NameError, %r|is already occupied by builtin|)
end
it "does not make bound name in {% assign %} available in its expression" do
expect {
exec(%{ {% declare foo = foo %} })
}.to raise_error(Liquor::NameError, %r|is not bound|)
expect {
exec(%{ {% assign foo = foo %} })
}.to raise_error(Liquor::NameError, %r|is not bound|)
end
it "supports {% if %}" do
code = compile(%Q{
{% if x == 1 then: %}
one
{% elsif: x > 1 && x < 10 then: %}
few
{% elsif: x > 10 then: %}
many
{% else: %}
wat
{% end if %}
}, [:x])
code.call(x: 1).strip.should == 'one'
code.call(x: 5).strip.should == 'few'
code.call(x: 11).strip.should == 'many'
code.call(x: -1).strip.should == 'wat'
end
it "supports {% unless %}" do
exec(%Q{
{% unless 1 == 2 then: %}
yes
{% end unless %}
}).strip.should == 'yes'
exec(%Q{
{% unless 1 == 1 then: %}
yes
{% end unless %}
no
}).strip.should == 'no'
end
it "supports {% for %}" do
exec(%Q{
{% for var in: [1, 2, 3] do: %}
{{ var }}
{% end for %}
}).scan(/\d+/).should == %w(1 2 3)
exec(%Q{
{% for var from: 1 to: 5 do: %}
{{ var }}
{% end for %}
}).scan(/\d+/).should == %w(1 2 3 4 5)
end
it "shadows variables of {% for %}" do
exec(%Q{
{% assign var = 1 %}
{% for var from: 1 to: 5 do: %}
{% end for %}
{{ var }}
}).strip.should == '1'
end
it "does not make name bound in {% for %} available in its expression" do
expect {
exec(%{ {% for foo in: foo do: %}{% end for %} })
}.to raise_error(Liquor::NameError, %r|is not bound|)
expect {
exec(%{ {% for foo from: foo to: 1 do: %}{% end for %} })
}.to raise_error(Liquor::NameError, %r|is not bound|)
end
it "does not shadow assignments within scopes" do
exec(%Q{
{% assign var = 1 %}
{% for i from: 1 to: 5 do: %}
{% assign var = i %}
{% end for %}
{{ var }}
}).strip.should == '5'
end
it "maintains forloop special" do
exec(%Q|
{% for x in: ["a", "b", "c"] do: %}
{{ x_loop.length }}
{{ x_loop.index }}
{{ x_loop.rindex }}
{% if x_loop.is_first then: %}first{% end if %}
{% if x_loop.is_last then: %}last{% end if %}
I
{% end for %}
|).scan(/\w+/).should == \
%w(3 0 2 first I 3 1 1 I 3 2 0 last I)
end
it "correctly shadows forloop special" do
exec(%Q|
{% for x in: ["a", "b"] do: %}
{{ x }} {{ x_loop.index }}
{% for x in: ["x", "y", "z"] do: %}
{{ x }} {{ x_loop.index }}
{% end for %}
{{ x_loop.length }}
{% end for %}
|).scan(/\w+/).should == \
%w(a 0 x 0 y 1 z 2 2 b 1 x 0 y 1 z 2 2)
end
it "supports {% capture %}" do
exec(%Q{
{% capture data = %}
{% for var from: 1 to: 3 do: %}{{ var + 1 }}{% end for %}
{% end capture %}
{{ data }}
{{ data }}
}).scan(/\d+/).should == %w'234 234'
end
it "reassigns variables within {% capture %}" do
exec(%Q{
{% assign data = 1 %}
{% capture data = %}2{% end capture %}
{{ data }}
}).strip.should == '2'
end
it "shadows variables within {% capture %}" do
exec(%Q{
{% declare data = 1 %}
{% capture test = %}
{% declare data = 2 %}
{{ data }}
{% end capture %}
{{ data }}
{{ test }}
}).scan(/\d+/).should == %w(1 2)
end
it "supports builtin functions" do
exec(%!{{ "test" | size }}!).should == '4'
exec(%!{{ [1, 2] | size }}!).should == '2'
exec(%!{{ "abc" | upcase }}!).should == 'ABC'
exec(%!{{ "Abc" | downcase }}!).should == 'abc'
exec(%!{{ "abc def" | capitalize }}!).should == 'Abc def'
exec(%!{{ "'" | url_escape }}!).should == '%27'
exec(%!{{ "&" | html_escape }}!).should == '&'
exec(%!{{ "" | html_escape }}!).should == ''
exec(%!{% capture test = %}\na\nb{% end capture %}{{ test | strip_newlines }}!).should == 'ab'
exec(%!{{ [ 1, 2, "a" ] | join }}!).should == '1 2 a'
exec(%!{{ "abc abc" | replace pattern: 'abc' replacement: 'def' }}!).should == 'def def'
exec(%!{{ "abc abc" | replace_first pattern: 'abc' replacement: 'def' }}!).should == 'def abc'
exec(%!{{ "abc abc" | remove pattern: 'abc' }}!).should == ' '
exec(%!{{ "abc abc" | remove_first pattern: 'abc' }}!).should == ' abc'
exec(%!{% capture test = %}a\nb{% end capture %}{{ test | newline_to_br }}!).should == "a
\nb"
exec(%!{{ '12 Jun' | strftime format: '%d' }}!).should == "12"
exec(%!{{ to_number("12") + 10 }}!).should == "22"
exec(%!{% if includes([ 1, 2, 3 ] element: 2) then: %}yes{% end if %}!).should == "yes"
exec(%!{{ index_of([1, 2, 3] element: 2) }}!).should == "1"
exec(%!{{ [ 1, 2, 3 ] | reverse | join }}!).should == "3 2 1"
exec(%!{{ [ 1, null, 3 ] | compact | join }}!).should == "1 3"
# is_even & is_odd
exec(%!{% if is_even(1) then: %}yes{% else: %}no{% end if %}!).should == "no"
exec(%!{% if is_odd(1) then: %}yes{% else: %}no{% end if %}!).should == "yes"
# is_empty
exec(%!{% if is_empty([]) then: %}yes{% end if%}!).should == "yes"
exec(%|{% if !is_empty([1,2,3]) then: %}not empty{% end if %}|).should == "not empty"
exec(%!{% if is_empty(null) then: %}yes{% end if%}!).should == "yes"
exec(%|{% if !is_empty(42) then: %}no{%end if%}|).should == "no"
exec(%|{% if !is_empty(0) then: %}no{%end if%}|).should == "no"
# starts_with
exec(%|{% if starts_with("some string" pattern: "some") then:%}yes{% end if %}|).should == "yes"
exec(%|{% if is_empty("") then: %}yes{%end if%}|).should == "yes"
exec(%|{% if !is_empty("test string") then: %}no{%end if%}|).should == "no"
# html stuff
exec(%|{{ h(html_escape_once('&')) }}|).should == '&'
exec(%|{{ strip_html('test') }}|).should == 'test'
exec(%|{{ decode_html_entities('&>') }}|).should == '&>'
# truncate variants (omission is 11 characters)
exec(%|{{ truncate("This is a test string." length: 10 + 11 omission: "(continued)") }}|).should == "This is a (continued)"
exec(%|{{ truncate_words("This is a test string." words: 4 omission: "(continued)") }}|).should == "This is a test(continued)"
exec(%|{{ html_truncate("
This is a test string. It is very very long to make truncater's job harder." length: 30) }}|).should == "
This is a test string. It is v...
" exec(%|{{ html_truncate_words("This is a test string. It is very very long to make truncater's job harder." words: 9) }}|).should == "
This is a test string. It is very very...
" # tuples exec(%|{% for i in: uniq([1,1,2]) do: %}{{ i }}{% end for %}|).should == '12' exec(%|{{ min([0,1,2]) }}|).should == '0' exec(%|{{ max([0,1,2]) }}|).should == '2' ext = Class.new do include Liquor::External attr_reader :n def initialize(n) @n = n end export :n end exts = [0,1,nil,2].map { |n| ext.new(n) } exec(%|{{ min(exts by: "n").n }}|, exts: exts).should == '0' exec(%|{{ max(exts by: "n").n }}|, exts: exts).should == '2' end it "should not leak iterator binding from {% for %}" do exec(%| {% for artist in: [1,2,3] do: %}{% end for %} {% if false then: %} {% assign artist = 1 %} {% end if %} {{ artist }} {% for artist in: [1,2,3] do: %}{% end for %} {% if false then: %} {% assign artist = 1 %} {% end if %} {{ artist }} |).strip.should == '' end end