# frozen_string_literal: true require 'cucumber/cucumber_expressions/cucumber_expression_generator' require 'cucumber/cucumber_expressions/cucumber_expression' require 'cucumber/cucumber_expressions/parameter_type' require 'cucumber/cucumber_expressions/parameter_type_registry' module Cucumber module CucumberExpressions describe CucumberExpressionGenerator do class Currency end before do @parameter_type_registry = ParameterTypeRegistry.new @generator = CucumberExpressionGenerator.new(@parameter_type_registry) end it 'documents expression generation' do parameter_registry = ParameterTypeRegistry.new ### [generate-expression] generator = CucumberExpressionGenerator.new(parameter_registry) undefined_step_text = 'I have 2 cucumbers and 1.5 tomato' generated_expression = generator.generate_expressions(undefined_step_text)[0] expect(generated_expression.source).to eq('I have {int} cucumbers and {float} tomato') expect(generated_expression.parameter_types[1].type).to eq(Float) ### [generate-expression] end it 'generates expression for no args' do assert_expression('hello', [], 'hello') end it 'generates expression with escaped left parenthesis' do assert_expression('\\(iii)', [], '(iii)') end it 'generates expression with escaped left curly brace' do assert_expression('\\{iii}', [], '{iii}') end it 'generates expression with escaped slashes' do assert_expression('The {int}\\/{int}\\/{int} hey', ['int', 'int2', 'int3'], 'The 1814/05/17 hey') end it 'generates expression for int float arg' do assert_expression('I have {int} cukes and {float} euro', ['int', 'float'], 'I have 2 cukes and 1.5 euro') end it 'generates expression for strings' do assert_expression('I like {string} and {string}', ['string', 'string2'], 'I like "bangers" and \'mash\'') end it 'generates expression with % sign' do assert_expression('I am {int}% foobar', ['int'], 'I am 20% foobar') end it 'generates expression for just int' do assert_expression('{int}', ['int'], '99999') end it 'numbers only second argument when builtin type is not reserved keyword' do assert_expression('I have {int} cukes and {int} euro', ['int', 'int2'], 'I have 2 cukes and 5 euro') end it 'numbers only second argument when type is not reserved keyword' do @parameter_type_registry.define_parameter_type( ParameterType.new( 'currency', '[A-Z]{3}', Currency, ->(s) { Currency.new(s)}, true, true ) ) assert_expression('I have a {currency} account and a {currency} account', ['currency', 'currency2'], 'I have a EUR account and a GBP account') end it 'exposes parameters in a generated expression' do expression = @generator.generate_expressions('I have 2 cukes and 1.5 euro')[0] types = expression.parameter_types.map(&:type) expect(types).to eq([Integer, Float]) end it 'matches parameter types with optional capture groups' do @parameter_type_registry.define_parameter_type( ParameterType.new( 'optional-flight', /(1st flight)?/, String, ->(s) { s}, true, false ) ) @parameter_type_registry.define_parameter_type( ParameterType.new( 'optional-hotel', /(1 hotel)?/, String, ->(s) { s}, true, false ) ) expression = @generator.generate_expressions('I reach Stage 4: 1st flight -1 hotel')[0] # While you would expect this to be `I reach Stage {int}: {optional-flight} -{optional-hotel}` # the `-1` causes {int} to match just before {optional-hotel}. expect(expression.source).to eq('I reach Stage {int}: {optional-flight} {int} hotel') end it 'generates at most 256 expressions' do for i in 0..3 @parameter_type_registry.define_parameter_type( ParameterType.new( "my-type-#{i}", /([a-z] )*?[a-z]/, String, ->(s) { s}, true, false ) ) end # This would otherwise generate 4^11=4194300 expressions and consume just shy of 1.5GB. expressions = @generator.generate_expressions('a s i m p l e s t e p') expect(expressions.length).to eq(256) end it 'prefers expression with longest non empty match' do @parameter_type_registry.define_parameter_type( ParameterType.new( 'zero-or-more', /[a-z]*/, String, ->(s) { s}, true, false ) ) @parameter_type_registry.define_parameter_type( ParameterType.new( 'exactly-one', /[a-z]/, String, ->(s) { s}, true, false ) ) expressions = @generator.generate_expressions('a simple step') expect(expressions.length).to eq(2) expect(expressions[0].source).to eq('{exactly-one} {zero-or-more} {zero-or-more}') expect(expressions[1].source).to eq('{zero-or-more} {zero-or-more} {zero-or-more}') end context 'does not suggest parameter when match is' do before do @parameter_type_registry.define_parameter_type( ParameterType.new( 'direction', /(up|down)/, String, ->(s) { s}, true, false ) ) end it 'at the beginning of a word' do expect(@generator.generate_expressions('When I download a picture')[0].source).not_to eq('When I {direction}load a picture') expect(@generator.generate_expressions('When I download a picture')[0].source).to eq('When I download a picture') end it 'inside a word' do expect(@generator.generate_expressions('When I watch the muppet show')[0].source).not_to eq('When I watch the m{direction}pet show') expect(@generator.generate_expressions('When I watch the muppet show')[0].source).to eq('When I watch the muppet show') end it 'at the end of a word' do expect(@generator.generate_expressions('When I create a group')[0].source).not_to eq('When I create a gro{direction}') expect(@generator.generate_expressions('When I create a group')[0].source).to eq('When I create a group') end end context 'does suggest parameter when match is' do before do @parameter_type_registry.define_parameter_type( ParameterType.new( 'direction', /(up|down)/, String, ->(s) { s}, true, false ) ) end it 'a full word' do expect(@generator.generate_expressions('When I go down the road')[0].source).to eq('When I go {direction} the road') expect(@generator.generate_expressions('When I walk up the hill')[0].source).to eq('When I walk {direction} the hill') expect(@generator.generate_expressions('up the hill, the road goes down')[0].source).to eq('{direction} the hill, the road goes {direction}') end it 'wrapped around punctuation characters' do expect(@generator.generate_expressions('When direction is:down')[0].source).to eq('When direction is:{direction}') expect(@generator.generate_expressions('Then direction is down.')[0].source).to eq('Then direction is {direction}.') end end def assert_expression(expected_expression, expected_argument_names, text) generated_expression = @generator.generate_expressions(text)[0] expect(generated_expression.parameter_names).to eq(expected_argument_names) expect(generated_expression.source).to eq(expected_expression) cucumber_expression = CucumberExpression.new(generated_expression.source, @parameter_type_registry) match = cucumber_expression.match(text) raise "Expected text '#{text}' to match generated expression '#{generated_expression.source}'" if match.nil? expect(match.length).to eq(expected_argument_names.length) end end end end