diff options
Diffstat (limited to 'spec/lib/gitlab/template_parser/ast_spec.rb')
-rw-r--r-- | spec/lib/gitlab/template_parser/ast_spec.rb | 246 |
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 |