refactor: init cypress-cucumber-preprocessor install.

This commit is contained in:
2021-09-02 17:02:45 +02:00
parent 89ec2d42ac
commit 1aa57bbd0a
5000 changed files with 408119 additions and 231 deletions

View File

@@ -0,0 +1,7 @@
const assert = require('assert')
// A better assert.error that allows an exact error message
module.exports = (fn, message) => {
const regexp = new RegExp(message.replace(/[-[\]/{}()*+?.\\^$|]/g, '\\$&'))
assert.throws(fn, regexp)
}

View File

@@ -0,0 +1,69 @@
/* eslint-env mocha */
const assert = require('assert')
const ParameterType = require('../src/parameter_type')
const CombinatorialGeneratedExpressionFactory = require('../src/combinatorial_generated_expression_factory')
describe('CucumberExpressionGenerator', () => {
it('generates multiple expressions', () => {
const parameterTypeCombinations = [
[
new ParameterType(
'color',
/red|blue|yellow/,
null,
s => s,
false,
true
),
new ParameterType(
'csscolor',
/red|blue|yellow/,
null,
s => s,
false,
true
),
],
[
new ParameterType(
'date',
/\d{4}-\d{2}-\d{2}/,
null,
s => s,
false,
true
),
new ParameterType(
'datetime',
/\d{4}-\d{2}-\d{2}/,
null,
s => s,
false,
true
),
new ParameterType(
'timestamp',
/\d{4}-\d{2}-\d{2}/,
null,
s => s,
false,
true
),
],
]
const factory = new CombinatorialGeneratedExpressionFactory(
'I bought a {%s} ball on {%s}',
parameterTypeCombinations
)
const expressions = factory.generateExpressions().map(ge => ge.source)
assert.deepEqual(expressions, [
'I bought a {color} ball on {date}',
'I bought a {color} ball on {datetime}',
'I bought a {color} ball on {timestamp}',
'I bought a {csscolor} ball on {date}',
'I bought a {csscolor} ball on {datetime}',
'I bought a {csscolor} ball on {timestamp}',
])
})
})

View File

@@ -0,0 +1,248 @@
/* 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}'
)
})
})

View File

@@ -0,0 +1,52 @@
/* eslint-env mocha */
const assert = require('assert')
const CucumberExpression = require('../src/cucumber_expression')
const ParameterTypeRegistry = require('../src/parameter_type_registry')
describe('CucumberExpression', () => {
describe('RegExp translation', () => {
it('translates no arguments', () => {
assertRegexp(
'I have 10 cukes in my belly now',
/^I have 10 cukes in my belly now$/
)
})
it('translates alternation', () => {
assertRegexp(
'I had/have a great/nice/charming friend',
/^I (?:had|have) a (?:great|nice|charming) friend$/
)
})
it('translates alternation with non-alpha', () => {
assertRegexp('I said Alpha1/Beta1', /^I said (?:Alpha1|Beta1)$/)
})
it('translates parameters', () => {
assertRegexp(
"I have {float} cukes at {int} o'clock",
/^I have (-?\d*\.\d+) cukes at ((?:-?\d+)|(?:\d+)) o'clock$/
)
})
it('translates parenthesis to non-capturing optional capture group', () => {
assertRegexp(
'I have many big(ish) cukes',
/^I have many big(?:ish)? cukes$/
)
})
it('translates parenthesis with alpha unicode', () => {
assertRegexp('Привет, Мир(ы)!', /^Привет, Мир(?:ы)?!$/)
})
})
})
const assertRegexp = (expression, expectedRegexp) => {
const cucumberExpression = new CucumberExpression(
expression,
new ParameterTypeRegistry()
)
assert.deepEqual(cucumberExpression.regexp, expectedRegexp)
}

View File

@@ -0,0 +1,270 @@
/* eslint-env mocha */
const assert = require('assert')
const {
CucumberExpression,
ParameterTypeRegistry,
ParameterType,
} = require('../src/index')
describe('CucumberExpression', () => {
it('documents match arguments', () => {
const parameterTypeRegistry = new ParameterTypeRegistry()
/// [capture-match-arguments]
const expr = 'I have {int} cuke(s)'
const expression = new CucumberExpression(expr, parameterTypeRegistry)
const args = expression.match('I have 7 cukes')
assert.equal(7, args[0].getValue(null))
/// [capture-match-arguments]
})
it('matches word', () => {
assert.deepEqual(match('three {word} mice', 'three blind mice'), ['blind'])
})
it('matches double quoted string', () => {
assert.deepEqual(match('three {string} mice', 'three "blind" mice'), [
'blind',
])
})
it('matches multiple double quoted strings', () => {
assert.deepEqual(
match(
'three {string} and {string} mice',
'three "blind" and "crippled" mice'
),
['blind', 'crippled']
)
})
it('matches single quoted string', () => {
assert.deepEqual(match('three {string} mice', "three 'blind' mice"), [
'blind',
])
})
it('matches multiple single quoted strings', () => {
assert.deepEqual(
match(
'three {string} and {string} mice',
"three 'blind' and 'crippled' mice"
),
['blind', 'crippled']
)
})
it('does not match misquoted string', () => {
assert.deepEqual(match('three {string} mice', 'three "blind\' mice'), null)
})
it('matches single quoted string with double quotes', () => {
assert.deepEqual(match('three {string} mice', 'three \'"blind"\' mice'), [
'"blind"',
])
})
it('matches double quoted string with single quotes', () => {
assert.deepEqual(match('three {string} mice', 'three "\'blind\'" mice'), [
"'blind'",
])
})
it('matches double quoted string with escaped double quote', () => {
assert.deepEqual(match('three {string} mice', 'three "bl\\"nd" mice'), [
'bl"nd',
])
})
it('matches single quoted string with escaped single quote', () => {
assert.deepEqual(match('three {string} mice', "three 'bl\\'nd' mice"), [
"bl'nd",
])
})
it('matches single quoted string with escaped single quote', () => {
assert.deepEqual(match('three {string} mice', "three 'bl\\'nd' mice"), [
"bl'nd",
])
})
it('matches escaped parenthesis', () => {
assert.deepEqual(
match(
'three \\(exceptionally) {string} mice',
'three (exceptionally) "blind" mice'
),
['blind']
)
})
it('matches escaped slash', () => {
assert.deepEqual(match('12\\/2020', '12/2020'), [])
})
it('matches int', () => {
assert.deepEqual(match('{int}', '22'), [22])
})
it("doesn't match float as int", () => {
assert.deepEqual(match('{int}', '1.22'), null)
})
it('matches float', () => {
assert.deepEqual(match('{float}', '0.22'), [0.22])
assert.deepEqual(match('{float}', '.22'), [0.22])
})
it('matches anonymous', () => {
assert.deepEqual(match('{}', '0.22'), ['0.22'])
})
it('throws unknown parameter type', () => {
try {
match('{unknown}', 'something')
assert.fail()
} catch (expected) {
assert.equal(expected.message, 'Undefined parameter type {unknown}')
}
})
it('does not allow optional parameter types', () => {
try {
match('({int})', '3')
assert.fail()
} catch (expected) {
assert.equal(
expected.message,
'Parameter types cannot be optional: ({int})'
)
}
})
it('allows escaped optional parameter types', () => {
assert.deepEqual(match('\\({int})', '(3)'), [3])
})
it('does not allow text/parameter type alternation', () => {
try {
match('x/{int}', '3')
assert.fail()
} catch (expected) {
assert.equal(
expected.message,
'Parameter types cannot be alternative: x/{int}'
)
}
})
it('does not allow parameter type/text alternation', () => {
try {
match('{int}/x', '3')
assert.fail()
} catch (expected) {
assert.equal(
expected.message,
'Parameter types cannot be alternative: {int}/x'
)
}
})
for (const c of '[]()$.|?*+'.split('')) {
it(`does not allow parameter type with ${c}`, () => {
try {
match(`{${c}string}`, 'something')
assert.fail()
} catch (expected) {
assert.equal(
expected.message,
`Illegal character '${c}' in parameter name {${c}string}`
)
}
})
}
it('exposes source', () => {
const expr = 'I have {int} cuke(s)'
assert.equal(
new CucumberExpression(expr, new ParameterTypeRegistry()).source,
expr
)
})
// JavaScript-specific
it('delegates transform to custom object', () => {
const parameterTypeRegistry = new ParameterTypeRegistry()
parameterTypeRegistry.defineParameterType(
new ParameterType(
'widget',
/\w+/,
null,
function(s) {
return this.createWidget(s)
},
false,
true
)
)
const expression = new CucumberExpression(
'I have a {widget}',
parameterTypeRegistry
)
const world = {
createWidget(s) {
return `widget:${s}`
},
}
const args = expression.match(`I have a bolt`)
assert.equal(args[0].getValue(world), 'widget:bolt')
})
describe('escapes special characters', () => {
;['\\', '[', ']', '^', '$', '.', '|', '?', '*', '+'].forEach(character => {
it(`escapes ${character}`, () => {
const expr = `I have {int} cuke(s) and ${character}`
const expression = new CucumberExpression(
expr,
new ParameterTypeRegistry()
)
const arg1 = expression.match(`I have 800 cukes and ${character}`)[0]
assert.equal(arg1.getValue(null), 800)
})
})
it(`escapes .`, () => {
const expr = `I have {int} cuke(s) and .`
const expression = new CucumberExpression(
expr,
new ParameterTypeRegistry()
)
assert.equal(expression.match(`I have 800 cukes and 3`), null)
const arg1 = expression.match(`I have 800 cukes and .`)[0]
assert.equal(arg1.getValue(null), 800)
})
it(`escapes |`, () => {
const expr = `I have {int} cuke(s) and a|b`
const expression = new CucumberExpression(
expr,
new ParameterTypeRegistry()
)
assert.equal(expression.match(`I have 800 cukes and a`), null)
assert.equal(expression.match(`I have 800 cukes and b`), null)
const arg1 = expression.match(`I have 800 cukes and a|b`)[0]
assert.equal(arg1.getValue(null), 800)
})
})
})
const match = (expression, text) => {
const cucumberExpression = new CucumberExpression(
expression,
new ParameterTypeRegistry()
)
const args = cucumberExpression.match(text)
if (!args) return null
return args.map(arg => arg.getValue(null))
}

View File

@@ -0,0 +1,258 @@
/* eslint-env mocha */
'use strict'
const assert = require('assert')
const assertThrows = require('./assert_throws')
const CucumberExpression = require('../src/cucumber_expression')
const RegularExpression = require('../src/regular_expression')
const ParameterTypeRegistry = require('../src/parameter_type_registry')
const ParameterType = require('../src/parameter_type')
require('@babel/polyfill')
class Color {
/// [color-constructor]
constructor(name) {
this.name = name
}
/// [color-constructor]
}
class CssColor {
constructor(name) {
this.name = name
}
}
describe('Custom parameter type', () => {
let parameterTypeRegistry
beforeEach(() => {
parameterTypeRegistry = new ParameterTypeRegistry()
/* eslint-disable prettier/prettier */
/// [add-color-parameter-type]
parameterTypeRegistry.defineParameterType(
new ParameterType(
'color', // name
/red|blue|yellow/, // regexp
Color, // type
s => new Color(s), // transformer
false, // useForSnippets
true // preferForRegexpMatch
)
)
/// [add-color-parameter-type]
/* eslint-enable prettier/prettier */
})
describe('CucumberExpression', () => {
it('throws exception for illegal character in parameter name', () => {
assertThrows(
() => new ParameterType('[string]', /.*/, String, s => s, false, true),
"Illegal character '[' in parameter name {[string]}"
)
})
it('matches parameters with custom parameter type', () => {
const expression = new CucumberExpression(
'I have a {color} ball',
parameterTypeRegistry
)
const value = expression.match('I have a red ball')[0].getValue(null)
assert.equal(value.name, 'red')
})
it('matches parameters with multiple capture groups', () => {
class Coordinate {
constructor(x, y, z) {
this.x = x
this.y = y
this.z = z
}
}
parameterTypeRegistry.defineParameterType(
new ParameterType(
'coordinate',
/(\d+),\s*(\d+),\s*(\d+)/,
Coordinate,
(x, y, z) => new Coordinate(parseInt(x), parseInt(y), parseInt(z)),
true,
true
)
)
const expression = new CucumberExpression(
'A {int} thick line from {coordinate} to {coordinate}',
parameterTypeRegistry
)
const args = expression.match('A 5 thick line from 10,20,30 to 40,50,60')
const thick = args[0].getValue(null)
assert.equal(thick, 5)
const from = args[1].getValue(null)
assert.equal(from.x, 10)
assert.equal(from.y, 20)
assert.equal(from.z, 30)
const to = args[2].getValue(null)
assert.equal(to.x, 40)
assert.equal(to.y, 50)
assert.equal(to.z, 60)
})
it('matches parameters with custom parameter type using optional capture group', () => {
parameterTypeRegistry = new ParameterTypeRegistry()
parameterTypeRegistry.defineParameterType(
new ParameterType(
'color',
[/red|blue|yellow/, /(?:dark|light) (?:red|blue|yellow)/],
Color,
s => new Color(s),
false,
true
)
)
const expression = new CucumberExpression(
'I have a {color} ball',
parameterTypeRegistry
)
const value = expression.match('I have a dark red ball')[0].getValue(null)
assert.equal(value.name, 'dark red')
})
it('defers transformation until queried from argument', () => {
parameterTypeRegistry.defineParameterType(
new ParameterType(
'throwing',
/bad/,
null,
s => {
throw new Error(`Can't transform [${s}]`)
},
false,
true
)
)
const expression = new CucumberExpression(
'I have a {throwing} parameter',
parameterTypeRegistry
)
const args = expression.match('I have a bad parameter')
assertThrows(() => args[0].getValue(null), "Can't transform [bad]")
})
describe('conflicting parameter type', () => {
it('is detected for type name', () => {
assertThrows(
() =>
parameterTypeRegistry.defineParameterType(
new ParameterType(
'color',
/.*/,
CssColor,
s => new CssColor(s),
false,
true
)
),
'There is already a parameter type with name color'
)
})
it('is not detected for type', () => {
parameterTypeRegistry.defineParameterType(
new ParameterType(
'whatever',
/.*/,
Color,
s => new Color(s),
false,
false
)
)
})
it('is not detected for regexp', () => {
parameterTypeRegistry.defineParameterType(
new ParameterType(
'css-color',
/red|blue|yellow/,
CssColor,
s => new CssColor(s),
true,
false
)
)
assert.equal(
new CucumberExpression(
'I have a {css-color} ball',
parameterTypeRegistry
)
.match('I have a blue ball')[0]
.getValue(null).constructor,
CssColor
)
assert.equal(
new CucumberExpression(
'I have a {css-color} ball',
parameterTypeRegistry
)
.match('I have a blue ball')[0]
.getValue(null).name,
'blue'
)
assert.equal(
new CucumberExpression('I have a {color} ball', parameterTypeRegistry)
.match('I have a blue ball')[0]
.getValue(null).constructor,
Color
)
assert.equal(
new CucumberExpression('I have a {color} ball', parameterTypeRegistry)
.match('I have a blue ball')[0]
.getValue(null).name,
'blue'
)
})
})
// JavaScript-specific
it('creates arguments using async transform', async () => {
parameterTypeRegistry = new ParameterTypeRegistry()
/// [add-async-parameter-type]
parameterTypeRegistry.defineParameterType(
new ParameterType(
'asyncColor',
/red|blue|yellow/,
Color,
async s => new Color(s),
false,
true
)
)
/// [add-async-parameter-type]
const expression = new CucumberExpression(
'I have a {asyncColor} ball',
parameterTypeRegistry
)
const args = await expression.match('I have a red ball')
const value = await args[0].getValue(null)
assert.equal(value.name, 'red')
})
})
describe('RegularExpression', () => {
it('matches arguments with custom parameter type', () => {
const expression = new RegularExpression(
/I have a (red|blue|yellow) ball/,
parameterTypeRegistry
)
const value = expression.match('I have a red ball')[0].getValue(null)
assert.equal(value.constructor, Color)
assert.equal(value.name, 'red')
})
})
})

View File

@@ -0,0 +1,30 @@
/* eslint-env mocha */
const fs = require('fs')
const assert = require('assert')
const CucumberExpression = require('../src/cucumber_expression')
const RegularExpression = require('../src/regular_expression')
const ParameterTypeRegistry = require('../src/parameter_type_registry')
describe('examples.txt', () => {
const match = (expression_text, text) => {
const m = /\/(.*)\//.exec(expression_text)
const expression = m
? new RegularExpression(new RegExp(m[1]), new ParameterTypeRegistry())
: new CucumberExpression(expression_text, new ParameterTypeRegistry())
const args = expression.match(text)
if (!args) return null
return args.map(arg => arg.getValue(null))
}
const examples = fs.readFileSync('examples.txt', 'utf-8')
const chunks = examples.split(/^---/m)
for (const chunk of chunks) {
const [expressionText, text, expectedArgs] = chunk.trim().split(/\n/m)
it(`Works with: ${expressionText}`, () => {
assert.deepEqual(
JSON.stringify(match(expressionText, text)),
expectedArgs
)
})
}
})

1
node_modules/cucumber-expressions/test/mocha.opts generated vendored Normal file
View File

@@ -0,0 +1 @@
--recursive

View File

@@ -0,0 +1,99 @@
/* eslint-env mocha */
const assert = require('assert')
const ParameterTypeRegistry = require('../src/parameter_type_registry')
const ParameterType = require('../src/parameter_type')
class Name {}
class Person {}
class Place {}
const CAPITALISED_WORD = /[A-Z]+\w+/
describe('ParameterTypeRegistry', () => {
let registry
beforeEach(() => {
registry = new ParameterTypeRegistry()
})
it('does not allow more than one preferential parameter type for each regexp', () => {
registry.defineParameterType(
new ParameterType(
'name',
CAPITALISED_WORD,
Name,
s => new Name(s),
true,
true
)
)
registry.defineParameterType(
new ParameterType(
'person',
CAPITALISED_WORD,
Person,
s => new Person(s),
true,
false
)
)
try {
registry.defineParameterType(
new ParameterType(
'place',
CAPITALISED_WORD,
Place,
s => new Place(s),
true,
true
)
)
throw new Error('Should have failed')
} catch (err) {
assert.equal(
err.message,
`There can only be one preferential parameter type per regexp. The regexp ${CAPITALISED_WORD} is used for two preferential parameter types, {name} and {place}`
)
}
})
it('looks up preferential parameter type by regexp', () => {
const name = new ParameterType(
'name',
/[A-Z]+\w+/,
null,
s => new Name(s),
true,
false
)
const person = new ParameterType(
'person',
/[A-Z]+\w+/,
null,
s => new Person(s),
true,
true
)
const place = new ParameterType(
'place',
/[A-Z]+\w+/,
null,
s => new Place(s),
true,
false
)
registry.defineParameterType(name)
registry.defineParameterType(person)
registry.defineParameterType(place)
assert.equal(
registry.lookupByRegexp(
'[A-Z]+\\w+',
/([A-Z]+\w+) and ([A-Z]+\w+)/,
'Lisa and Bob'
),
person
)
})
})

View File

@@ -0,0 +1,20 @@
/* eslint-env mocha */
const assertThrows = require('./assert_throws')
const ParameterType = require('../src/parameter_type')
describe('ParameterType', () => {
it('does not allow ignore flag on regexp', () => {
assertThrows(
() =>
new ParameterType(
'case-insensitive',
/[a-z]+/i,
String,
s => s,
true,
true
),
"ParameterType Regexps can't use flag 'i'"
)
})
})

View File

@@ -0,0 +1,106 @@
/* eslint-env mocha */
const assert = require('assert')
const RegularExpression = require('../src/regular_expression')
const ParameterTypeRegistry = require('../src/parameter_type_registry')
describe('RegularExpression', () => {
it('documents match arguments', () => {
const parameterRegistry = new ParameterTypeRegistry()
/// [capture-match-arguments]
const expr = /I have (\d+) cukes? in my (\w+) now/
const expression = new RegularExpression(expr, parameterRegistry)
const args = expression.match('I have 7 cukes in my belly now')
assert.equal(7, args[0].getValue(null))
assert.equal('belly', args[1].getValue(null))
/// [capture-match-arguments]
})
it('does no transform by default', () => {
assert.deepEqual(match(/(\d\d)/, '22')[0], '22')
})
it('does not transform anonymous', () => {
assert.deepEqual(match(/(.*)/, '22')[0], '22')
})
it('transforms negative int', () => {
assert.deepEqual(match(/(-?\d+)/, '-22')[0], -22)
})
it('transforms positive int', () => {
assert.deepEqual(match(/(\d+)/, '22')[0], 22)
})
it('transforms float without integer part', () => {
assert.deepEqual(match(/(-?\d*\.?\d+)/, '.22')[0], 0.22)
})
it('transforms float with sign', () => {
assert.deepEqual(match(/(-?\d*\.?\d+)/, '-1.22')[0], -1.22)
})
it('returns null when there is no match', () => {
assert.equal(match(/hello/, 'world'), null)
})
it('matches nested capture group without match', () => {
assert.deepEqual(match(/^a user( named "([^"]*)")?$/, 'a user'), [null])
})
it('matches nested capture group with match', () => {
assert.deepEqual(
match(/^a user( named "([^"]*)")?$/, 'a user named "Charlie"'),
['Charlie']
)
})
it('matches capture group nested in optional one', () => {
const regexp = /^a (pre-commercial transaction |pre buyer fee model )?purchase(?: for \$(\d+))?$/
assert.deepEqual(match(regexp, 'a purchase'), [null, null])
assert.deepEqual(match(regexp, 'a purchase for $33'), [null, 33])
assert.deepEqual(match(regexp, 'a pre buyer fee model purchase'), [
'pre buyer fee model ',
null,
])
})
it('ignores non capturing groups', () => {
assert.deepEqual(
match(
/(\S+) ?(can|cannot)? (?:delete|cancel) the (\d+)(?:st|nd|rd|th) (attachment|slide) ?(?:upload)?/,
'I can cancel the 1st slide upload'
),
['I', 'can', 1, 'slide']
)
})
it('works with escaped parenthesis', () => {
assert.deepEqual(match(/Across the line\(s\)/, 'Across the line(s)'), [])
})
it('exposes regexp and source', () => {
const regexp = /I have (\d+) cukes? in my (.+) now/
let expression = new RegularExpression(regexp, new ParameterTypeRegistry())
assert.deepEqual(expression.regexp, regexp)
assert.deepEqual(expression.source, regexp.source)
})
it('does not take consider parenthesis in character class as group', function() {
const expression = new RegularExpression(
/^drawings: ([A-Z_, ()]+)$/,
new ParameterTypeRegistry()
)
const args = expression.match('drawings: ONE, TWO(ABC)')
assert.equal(args[0].getValue(), 'ONE, TWO(ABC)')
})
})
const match = (regexp, text) => {
const parameterRegistry = new ParameterTypeRegistry()
const regularExpression = new RegularExpression(regexp, parameterRegistry)
const args = regularExpression.match(text)
if (!args) return null
return args.map(arg => arg.getValue(null))
}

View File

@@ -0,0 +1,100 @@
/* eslint-env mocha */
const assert = require('assert')
const TreeRegexp = require('../src/tree_regexp')
describe('TreeRegexp', () => {
it('exposes group source', () => {
const tr = new TreeRegexp(/(a(?:b)?)(c)/)
assert.deepEqual(tr.groupBuilder.children.map(gb => gb.source), [
'a(?:b)?',
'c',
])
})
it('builds tree', () => {
const tr = new TreeRegexp(/(a(?:b)?)(c)/)
const group = tr.match('ac')
assert.equal(group.value, 'ac')
assert.equal(group.children[0].value, 'a')
assert.deepEqual(group.children[0].children, [])
assert.equal(group.children[1].value, 'c')
})
it('ignores non-capturing groups', () => {
const tr = new TreeRegexp(/(a(?:b)?)(c)/)
const group = tr.match('ac')
assert.equal(group.value, 'ac')
assert.equal(group.children[0].value, 'a')
assert.deepEqual(group.children[0].children, [])
assert.equal(group.children[1].value, 'c')
})
it('matches optional group', () => {
const tr = new TreeRegexp(/^Something( with an optional argument)?/)
const group = tr.match('Something')
assert.equal(group.children[0].value, null)
})
it('matches nested groups', () => {
const tr = new TreeRegexp(
/^A (\d+) thick line from ((\d+),\s*(\d+),\s*(\d+)) to ((\d+),\s*(\d+),\s*(\d+))/
)
const group = tr.match('A 5 thick line from 10,20,30 to 40,50,60')
assert.equal(group.children[0].value, '5')
assert.equal(group.children[1].value, '10,20,30')
assert.equal(group.children[1].children[0].value, '10')
assert.equal(group.children[1].children[1].value, '20')
assert.equal(group.children[1].children[2].value, '30')
assert.equal(group.children[2].value, '40,50,60')
assert.equal(group.children[2].children[0].value, '40')
assert.equal(group.children[2].children[1].value, '50')
assert.equal(group.children[2].children[2].value, '60')
})
it('detects multiple non capturing groups', () => {
const tr = new TreeRegexp(/(?:a)(:b)(\?c)(d)/)
const group = tr.match('a:b?cd')
assert.equal(group.children.length, 3)
})
it('works with escaped backslash', () => {
const tr = new TreeRegexp(/foo\\(bar|baz)/)
const group = tr.match('foo\\bar')
assert.equal(group.children.length, 1)
})
it('works with escaped slash', () => {
const tr = new TreeRegexp(/^I go to '\/(.+)'$/)
const group = tr.match("I go to '/hello'")
assert.equal(group.children.length, 1)
})
it('works with digit and word', () => {
const tr = new TreeRegexp(/^(\d) (\w+)$/)
const group = tr.match('2 you')
assert.equal(group.children.length, 2)
})
it('captures non capturing groups with capturing groups inside', () => {
const tr = new TreeRegexp('the stdout(?: from "(.*?)")?')
const group = tr.match('the stdout')
assert.equal(group.value, 'the stdout')
assert.equal(group.children[0].value, null)
assert.equal(group.children.length, 1)
})
it('works with flags', () => {
const tr = new TreeRegexp(/HELLO/i)
const group = tr.match('hello')
assert.equal(group.value, 'hello')
})
it('does not consider parenthesis in character class as group', () => {
const tr = new TreeRegexp(/^drawings: ([A-Z, ()]+)$/)
const group = tr.match('drawings: ONE(TWO)')
assert.equal(group.value, 'drawings: ONE(TWO)')
assert.equal(group.children.length, 1)
assert.equal(group.children[0].value, 'ONE(TWO)')
})
})