summaryrefslogtreecommitdiff
path: root/spec/lib/gitlab/template_parser/ast_spec.rb
diff options
context:
space:
mode:
Diffstat (limited to 'spec/lib/gitlab/template_parser/ast_spec.rb')
-rw-r--r--spec/lib/gitlab/template_parser/ast_spec.rb246
1 files changed, 246 insertions, 0 deletions
diff --git a/spec/lib/gitlab/template_parser/ast_spec.rb b/spec/lib/gitlab/template_parser/ast_spec.rb
new file mode 100644
index 00000000000..27361ea8632
--- /dev/null
+++ b/spec/lib/gitlab/template_parser/ast_spec.rb
@@ -0,0 +1,246 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::TemplateParser::AST::Identifier do
+ let(:state) { Gitlab::TemplateParser::EvalState.new }
+
+ describe '#evaluate' do
+ it 'evaluates a selector' do
+ data = { 'number' => 10 }
+
+ expect(described_class.new('number').evaluate(state, data)).to eq(10)
+ end
+
+ it 'returns nil if the key is not set' do
+ expect(described_class.new('number').evaluate(state, {})).to be_nil
+ end
+
+ it 'returns nil if the input is not a Hash' do
+ expect(described_class.new('number').evaluate(state, 45)).to be_nil
+ end
+
+ it 'returns the current data when using the special identifier "it"' do
+ expect(described_class.new('it').evaluate(state, 45)).to eq(45)
+ end
+ end
+end
+
+RSpec.describe Gitlab::TemplateParser::AST::Integer do
+ let(:state) { Gitlab::TemplateParser::EvalState.new }
+
+ describe '#evaluate' do
+ it 'evaluates a selector' do
+ expect(described_class.new(0).evaluate(state, [10])).to eq(10)
+ end
+
+ it 'returns nil if the index is not set' do
+ expect(described_class.new(1).evaluate(state, [10])).to be_nil
+ end
+
+ it 'returns nil if the input is not an Array' do
+ expect(described_class.new(0).evaluate(state, {})).to be_nil
+ end
+ end
+end
+
+RSpec.describe Gitlab::TemplateParser::AST::Selector do
+ let(:state) { Gitlab::TemplateParser::EvalState.new }
+ let(:data) { { 'numbers' => [10] } }
+
+ describe '#evaluate' do
+ it 'evaluates a selector' do
+ ident = Gitlab::TemplateParser::AST::Identifier.new('numbers')
+ int = Gitlab::TemplateParser::AST::Integer.new(0)
+
+ expect(described_class.new([ident, int]).evaluate(state, data)).to eq(10)
+ end
+
+ it 'evaluates a selector that returns nil' do
+ int = Gitlab::TemplateParser::AST::Integer.new(0)
+
+ expect(described_class.new([int]).evaluate(state, data)).to be_nil
+ end
+ end
+end
+
+RSpec.describe Gitlab::TemplateParser::AST::Variable do
+ let(:state) { Gitlab::TemplateParser::EvalState.new }
+ let(:data) { { 'numbers' => [10] } }
+
+ describe '#evaluate' do
+ it 'evaluates a variable' do
+ node = Gitlab::TemplateParser::Parser
+ .new
+ .parse_and_transform('{{numbers.0}}')
+ .nodes[0]
+
+ expect(node.evaluate(state, data)).to eq('10')
+ end
+
+ it 'evaluates an undefined variable' do
+ node =
+ Gitlab::TemplateParser::Parser.new.parse_and_transform('{{foobar}}').nodes[0]
+
+ expect(node.evaluate(state, data)).to eq('')
+ end
+
+ it 'evaluates the special variable "it"' do
+ node =
+ Gitlab::TemplateParser::Parser.new.parse_and_transform('{{it}}').nodes[0]
+
+ expect(node.evaluate(state, data)).to eq(data.to_s)
+ end
+ end
+end
+
+RSpec.describe Gitlab::TemplateParser::AST::Expressions do
+ let(:state) { Gitlab::TemplateParser::EvalState.new }
+
+ describe '#evaluate' do
+ it 'evaluates all expressions' do
+ node = Gitlab::TemplateParser::Parser
+ .new
+ .parse_and_transform('{{number}}foo')
+
+ expect(node.evaluate(state, { 'number' => 10 })).to eq('10foo')
+ end
+ end
+end
+
+RSpec.describe Gitlab::TemplateParser::AST::Text do
+ let(:state) { Gitlab::TemplateParser::EvalState.new }
+
+ describe '#evaluate' do
+ it 'returns the text' do
+ expect(described_class.new('foo').evaluate(state, {})).to eq('foo')
+ end
+ end
+end
+
+RSpec.describe Gitlab::TemplateParser::AST::If do
+ let(:state) { Gitlab::TemplateParser::EvalState.new }
+
+ describe '#evaluate' do
+ it 'evaluates a truthy if expression without an else clause' do
+ node = Gitlab::TemplateParser::Parser
+ .new
+ .parse_and_transform('{% if thing %}foo{% end %}')
+ .nodes[0]
+
+ expect(node.evaluate(state, { 'thing' => true })).to eq('foo')
+ end
+
+ it 'evaluates a falsy if expression without an else clause' do
+ node = Gitlab::TemplateParser::Parser
+ .new
+ .parse_and_transform('{% if thing %}foo{% end %}')
+ .nodes[0]
+
+ expect(node.evaluate(state, { 'thing' => false })).to eq('')
+ end
+
+ it 'evaluates a falsy if expression with an else clause' do
+ node = Gitlab::TemplateParser::Parser
+ .new
+ .parse_and_transform('{% if thing %}foo{% else %}bar{% end %}')
+ .nodes[0]
+
+ expect(node.evaluate(state, { 'thing' => false })).to eq('bar')
+ end
+ end
+
+ describe '#truthy?' do
+ it 'returns true for a non-empty String' do
+ expect(described_class.new.truthy?('foo')).to eq(true)
+ end
+
+ it 'returns true for a non-empty Array' do
+ expect(described_class.new.truthy?([10])).to eq(true)
+ end
+
+ it 'returns true for a Boolean true' do
+ expect(described_class.new.truthy?(true)).to eq(true)
+ end
+
+ it 'returns false for an empty String' do
+ expect(described_class.new.truthy?('')).to eq(false)
+ end
+
+ it 'returns true for an empty Array' do
+ expect(described_class.new.truthy?([])).to eq(false)
+ end
+
+ it 'returns false for a Boolean false' do
+ expect(described_class.new.truthy?(false)).to eq(false)
+ end
+ end
+end
+
+RSpec.describe Gitlab::TemplateParser::AST::Each do
+ let(:state) { Gitlab::TemplateParser::EvalState.new }
+
+ describe '#evaluate' do
+ it 'evaluates the expression' do
+ data = { 'animals' => [{ 'name' => 'Cat' }, { 'name' => 'Dog' }] }
+ node = Gitlab::TemplateParser::Parser
+ .new
+ .parse_and_transform('{% each animals %}{{name}}{% end %}')
+ .nodes[0]
+
+ expect(node.evaluate(state, data)).to eq('CatDog')
+ end
+
+ it 'returns an empty string when the input is not a collection' do
+ data = { 'animals' => 10 }
+ node = Gitlab::TemplateParser::Parser
+ .new
+ .parse_and_transform('{% each animals %}{{name}}{% end %}')
+ .nodes[0]
+
+ expect(node.evaluate(state, data)).to eq('')
+ end
+
+ it 'disallows too many nested loops' do
+ data = {
+ 'foo' => [
+ {
+ 'bar' => [
+ {
+ 'baz' => [
+ {
+ 'quix' => [
+ {
+ 'foo' => [{ 'name' => 'Alice' }]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+
+ template = <<~TPL
+ {% each foo %}
+ {% each bar %}
+ {% each baz %}
+ {% each quix %}
+ {% each foo %}
+ {{name}}
+ {% end %}
+ {% end %}
+ {% end %}
+ {% end %}
+ {% end %}
+ TPL
+
+ node =
+ Gitlab::TemplateParser::Parser.new.parse_and_transform(template).nodes[0]
+
+ expect { node.evaluate(state, data) }
+ .to raise_error(Gitlab::TemplateParser::Error)
+ end
+ end
+end