UNPKG

cucumber-expressions

Version:

Cucumber Expressions - a simpler alternative to Regular Expressions

332 lines (290 loc) 10.3 kB
import assert from 'assert' import CucumberExpressionGenerator from '../src/CucumberExpressionGenerator' import CucumberExpression from '../src/CucumberExpression' import ParameterType from '../src/ParameterType' import ParameterTypeRegistry from '../src/ParameterTypeRegistry' class Currency { constructor(public readonly s: string) {} } describe('CucumberExpressionGenerator', () => { let parameterTypeRegistry: ParameterTypeRegistry let generator: CucumberExpressionGenerator function assertExpression( expectedExpression: string, expectedArgumentNames: string[], text: string ) { const generatedExpression = generator.generateExpressions(text)[0] assert.deepStrictEqual( generatedExpression.parameterNames, expectedArgumentNames ) assert.strictEqual(generatedExpression.source, expectedExpression) const cucumberExpression = new CucumberExpression( generatedExpression.source, parameterTypeRegistry ) const match = cucumberExpression.match(text) if (match === null) { assert.fail( `Expected text '${text}' to match generated expression '${generatedExpression.source}'` ) } assert.strictEqual(match.length, expectedArgumentNames.length) } beforeEach(() => { parameterTypeRegistry = new ParameterTypeRegistry() generator = new CucumberExpressionGenerator(parameterTypeRegistry) }) it('documents expression generation', () => { parameterTypeRegistry = new ParameterTypeRegistry() generator = new CucumberExpressionGenerator(parameterTypeRegistry) const undefinedStepText = 'I have 2 cucumbers and 1.5 tomato' const generatedExpression = generator.generateExpressions( undefinedStepText )[0] assert.strictEqual( generatedExpression.source, 'I have {int} cucumbers and {float} tomato' ) assert.strictEqual(generatedExpression.parameterNames[0], 'int') assert.strictEqual(generatedExpression.parameterTypes[1].name, 'float') }) it('generates expression for no args', () => { assertExpression('hello', [], 'hello') }) it('generates expression with escaped left parenthesis', () => { assertExpression('\\(iii)', [], '(iii)') }) it('generates expression with escaped left curly brace', () => { assertExpression('\\{iii}', [], '{iii}') }) it('generates expression with escaped slashes', () => { assertExpression( 'The {int}\\/{int}\\/{int} hey', ['int', 'int2', 'int3'], 'The 1814/05/17 hey' ) }) it('generates expression for int float arg', () => { assertExpression( 'I have {int} cukes and {float} euro', ['int', 'float'], 'I have 2 cukes and 1.5 euro' ) }) it('generates expression for strings', () => { assertExpression( 'I like {string} and {string}', ['string', 'string2'], 'I like "bangers" and \'mash\'' ) }) it('generates expression with % sign', () => { assertExpression('I am {int}%% foobar', ['int'], 'I am 20%% foobar') }) it('generates expression for just int', () => { assertExpression('{int}', ['int'], '99999') }) it('numbers only second argument when builtin type is not reserved keyword', () => { assertExpression( 'I have {float} cukes and {float} euro', ['float', 'float2'], 'I have 2.5 cukes and 1.5 euro' ) }) it('generates expression for custom type', () => { parameterTypeRegistry.defineParameterType( new ParameterType( 'currency', /[A-Z]{3}/, Currency, s => new Currency(s), true, false ) ) assertExpression( 'I have a {currency} account', ['currency'], 'I have a EUR account' ) }) it('prefers leftmost match when there is overlap', () => { parameterTypeRegistry.defineParameterType( new ParameterType<Currency>( 'currency', /c d/, Currency, s => new Currency(s), true, false ) ) parameterTypeRegistry.defineParameterType( new ParameterType('date', /b c/, Date, s => new Date(s), true, false) ) assertExpression('a {date} d e f g', ['date'], 'a b c d e f g') }) // TODO: prefers widest match it('generates all combinations of expressions when several parameter types match', () => { parameterTypeRegistry.defineParameterType( new ParameterType( 'currency', /x/, null, s => new Currency(s), true, false ) ) parameterTypeRegistry.defineParameterType( new ParameterType('date', /x/, null, s => new Date(s), true, false) ) const generatedExpressions = generator.generateExpressions( 'I have x and x and another x' ) const expressions = generatedExpressions.map(e => e.source) assert.deepStrictEqual(expressions, [ 'I have {currency} and {currency} and another {currency}', 'I have {currency} and {currency} and another {date}', 'I have {currency} and {date} and another {currency}', 'I have {currency} and {date} and another {date}', 'I have {date} and {currency} and another {currency}', 'I have {date} and {currency} and another {date}', 'I have {date} and {date} and another {currency}', 'I have {date} and {date} and another {date}', ]) }) it('exposes parameter type names in generated expression', () => { const expression = generator.generateExpressions( 'I have 2 cukes and 1.5 euro' )[0] const typeNames = expression.parameterTypes.map(parameter => parameter.name) assert.deepStrictEqual(typeNames, ['int', 'float']) }) it('matches parameter types with optional capture groups', () => { parameterTypeRegistry.defineParameterType( new ParameterType( 'optional-flight', /(1st flight)?/, null, s => s, true, false ) ) parameterTypeRegistry.defineParameterType( new ParameterType( 'optional-hotel', /(1 hotel)?/, null, s => s, true, false ) ) const expression = generator.generateExpressions( '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}. assert.strictEqual( expression.source, 'I reach Stage {int}: {optional-flight} {int} hotel' ) }) it('generates at most 256 expressions', () => { for (let i = 0; i < 4; i++) { parameterTypeRegistry.defineParameterType( new ParameterType( 'my-type-' + i, /([a-z] )*?[a-z]/, null, s => s, true, false ) ) } // This would otherwise generate 4^11=419430 expressions and consume just shy of 1.5GB. const expressions = generator.generateExpressions('a s i m p l e s t e p') assert.strictEqual(expressions.length, 256) }) it('prefers expression with longest non empty match', () => { parameterTypeRegistry.defineParameterType( new ParameterType('zero-or-more', /[a-z]*/, null, s => s, true, false) ) parameterTypeRegistry.defineParameterType( new ParameterType('exactly-one', /[a-z]/, null, s => s, true, false) ) const expressions = generator.generateExpressions('a simple step') assert.strictEqual(expressions.length, 2) assert.strictEqual( expressions[0].source, '{exactly-one} {zero-or-more} {zero-or-more}' ) assert.strictEqual( expressions[1].source, '{zero-or-more} {zero-or-more} {zero-or-more}' ) }) it('does not suggest parameter included at the beginning of a word', () => { parameterTypeRegistry.defineParameterType( new ParameterType('direction', /(up|down)/, null, s => s, true, false) ) const expressions = generator.generateExpressions('I download a picture') assert.strictEqual(expressions.length, 1) assert.notEqual(expressions[0].source, 'I {direction}load a picture') assert.strictEqual(expressions[0].source, 'I download a picture') }) it('does not suggest parameter included inside a word', () => { parameterTypeRegistry.defineParameterType( new ParameterType('direction', /(up|down)/, null, s => s, true, false) ) const expressions = generator.generateExpressions('I watch the muppet show') assert.strictEqual(expressions.length, 1) assert.notEqual(expressions[0].source, 'I watch the m{direction}pet show') assert.strictEqual(expressions[0].source, 'I watch the muppet show') }) it('does not suggest parameter at the end of a word', () => { parameterTypeRegistry.defineParameterType( new ParameterType('direction', /(up|down)/, null, s => s, true, false) ) const expressions = generator.generateExpressions('I create a group') assert.strictEqual(expressions.length, 1) assert.notEqual(expressions[0].source, 'I create a gro{direction}') assert.strictEqual(expressions[0].source, 'I create a group') }) it('does suggest parameter that are a full word', () => { parameterTypeRegistry.defineParameterType( new ParameterType('direction', /(up|down)/, null, s => s, true, false) ) assert.strictEqual( generator.generateExpressions('When I go down the road')[0].source, 'When I go {direction} the road' ) assert.strictEqual( generator.generateExpressions('When I walk up the hill')[0].source, 'When I walk {direction} the hill' ) assert.strictEqual( generator.generateExpressions('up the hill, the road goes down')[0] .source, '{direction} the hill, the road goes {direction}' ) }) it('does not consider punctuation as being part of a word', () => { parameterTypeRegistry.defineParameterType( new ParameterType('direction', /(up|down)/, null, s => s, true, false) ) assert.strictEqual( generator.generateExpressions('direction is:down')[0].source, 'direction is:{direction}' ) assert.strictEqual( generator.generateExpressions('direction is down.')[0].source, 'direction is {direction}.' ) }) })