249 lines
7.3 KiB
JavaScript
249 lines
7.3 KiB
JavaScript
/* eslint-env mocha */
|
|
const assert = require('assert')
|
|
const CucumberExpressionGenerator = require('../src/cucumber_expression_generator')
|
|
const CucumberExpression = require('../src/cucumber_expression')
|
|
const ParameterType = require('../src/parameter_type')
|
|
const ParameterTypeRegistry = require('../src/parameter_type_registry')
|
|
|
|
class Currency {}
|
|
|
|
describe('CucumberExpressionGenerator', () => {
|
|
let parameterTypeRegistry, generator
|
|
|
|
function assertExpression(expectedExpression, expectedArgumentNames, text) {
|
|
const generatedExpression = generator.generateExpressions(text)[0]
|
|
assert.deepEqual(generatedExpression.parameterNames, expectedArgumentNames)
|
|
assert.equal(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.equal(match.length, expectedArgumentNames.length)
|
|
}
|
|
|
|
beforeEach(() => {
|
|
parameterTypeRegistry = new ParameterTypeRegistry()
|
|
generator = new CucumberExpressionGenerator(parameterTypeRegistry)
|
|
})
|
|
|
|
it('documents expression generation', () => {
|
|
const parameterRegistry = new ParameterTypeRegistry()
|
|
/// [generate-expression]
|
|
const generator = new CucumberExpressionGenerator(parameterRegistry)
|
|
const undefinedStepText = 'I have 2 cucumbers and 1.5 tomato'
|
|
const generatedExpression = generator.generateExpressions(
|
|
undefinedStepText
|
|
)[0]
|
|
assert.equal(
|
|
generatedExpression.source,
|
|
'I have {int} cucumbers and {float} tomato'
|
|
)
|
|
assert.equal(generatedExpression.parameterNames[0], 'int')
|
|
assert.equal(generatedExpression.parameterTypes[1].name, 'float')
|
|
/// [generate-expression]
|
|
})
|
|
|
|
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',
|
|
/cd/,
|
|
Currency,
|
|
s => new Currency(s),
|
|
true,
|
|
false
|
|
)
|
|
)
|
|
parameterTypeRegistry.defineParameterType(
|
|
new ParameterType('date', /bc/, Date, s => new Date(s), true, false)
|
|
)
|
|
|
|
assertExpression('a{date}defg', ['date'], 'abcdefg')
|
|
})
|
|
|
|
// 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.deepEqual(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.deepEqual(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',
|
|
/(1st hotel)?/,
|
|
null,
|
|
s => s,
|
|
true,
|
|
false
|
|
)
|
|
)
|
|
|
|
const expression = generator.generateExpressions(
|
|
'I reach Stage4: 1st flight-1st 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.equal(
|
|
expression.source,
|
|
'I reach Stage{int}: {optional-flight}{int}st hotel'
|
|
)
|
|
})
|
|
|
|
it('generates at most 256 expressions', () => {
|
|
for (let i = 0; i < 4; i++) {
|
|
parameterTypeRegistry.defineParameterType(
|
|
new ParameterType('my-type-' + i, /[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 simple step')
|
|
assert.equal(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.equal(expressions.length, 2)
|
|
assert.equal(
|
|
expressions[0].source,
|
|
'{exactly-one} {zero-or-more} {zero-or-more}'
|
|
)
|
|
assert.equal(
|
|
expressions[1].source,
|
|
'{zero-or-more} {zero-or-more} {zero-or-more}'
|
|
)
|
|
})
|
|
})
|