@@ -106,7 +106,8 @@ const Fusion = (featureFileToLoad, optionsToPassToJestCucumber) => {
106106 feature . scenarioOutlines ,
107107 beforeEach ,
108108 afterEach ,
109- testFn
109+ testFn ,
110+ true
110111 ) ;
111112 } ) ;
112113} ;
@@ -115,31 +116,17 @@ const matchJestTestSuiteWithCucumberFeature = (
115116 featureScenariosOrOutline ,
116117 beforeEachFn ,
117118 afterEachFn ,
118- testFn
119+ testFn ,
120+ isOutline
119121) => {
120122 featureScenariosOrOutline . forEach ( ( currentScenarioOrOutline ) => {
121123 if ( stepsDefinition . before ) beforeEachFn ( stepsDefinition . before ) ;
122124
123125 matchJestTestWithCucumberScenario (
124126 currentScenarioOrOutline . title ,
125-
126- // when scenario outline table contains examples then jest-cucumber.loadFeature
127- // calculates scenario parameters and place them into currentScenarioOrOutline.scenarios[0].steps
128- // at the same time currentScenarioOrOutline.steps contains pure steps without
129- // example substutions for example if the scenario outline looks like:
130- // Scenario Outline: test scenario
131- // Given Step sentence
132- // | field |
133- // | <example> |
134- // Examples:
135- // | example |
136- // | myValue |
137- // then currentScenarioOrOutline.steps will contain {field: '<example>'}
138- // and at the same time currentScenarioOrOutline.scenarios[0].steps will contain {field: 'myValue'}
139- currentScenarioOrOutline . scenarios != null
140- ? currentScenarioOrOutline . scenarios [ 0 ] . steps
141- : currentScenarioOrOutline . steps ,
142- testFn
127+ currentScenarioOrOutline . steps ,
128+ testFn ,
129+ isOutline
143130 ) ;
144131
145132 if ( stepsDefinition . after ) afterEachFn ( stepsDefinition . after ) ;
@@ -149,20 +136,26 @@ const matchJestTestSuiteWithCucumberFeature = (
149136const matchJestTestWithCucumberScenario = (
150137 currentScenarioTitle ,
151138 currentScenarioSteps ,
152- testFn
139+ testFn ,
140+ isOutline
153141) => {
154142 testFn ( currentScenarioTitle , ( { given, when, then, and, but } ) => {
155143 currentScenarioSteps . forEach ( ( currentStep ) => {
156144 matchJestDefinitionWithCucumberStep (
157145 { given, when, then, and, but } ,
158- currentStep
146+ currentStep ,
147+ isOutline
159148 ) ;
160149 } ) ;
161150 } ) ;
162151} ;
163152
164- const matchJestDefinitionWithCucumberStep = ( verbFunction , currentStep ) => {
165- const foundMatchingStep = findMatchingStep ( currentStep ) ;
153+ const matchJestDefinitionWithCucumberStep = (
154+ verbFunction ,
155+ currentStep ,
156+ isOutline
157+ ) => {
158+ const foundMatchingStep = findMatchingStep ( currentStep , isOutline ) ;
166159 if ( ! foundMatchingStep ) return ;
167160
168161 // this will be the "given", "when", "then"...functions
@@ -172,14 +165,15 @@ const matchJestDefinitionWithCucumberStep = (verbFunction, currentStep) => {
172165 ) ;
173166} ;
174167
175- const findMatchingStep = ( currentStep ) => {
168+ const findMatchingStep = ( currentStep , isOutline ) => {
176169 const scenarioType = currentStep . keyword ;
177170 const scenarioSentence = currentStep . stepText ;
178171 const foundStep = Object . keys ( stepsDefinition [ scenarioType ] ) . find (
179172 ( currentStepDefinitionFunction ) => {
180173 return isFunctionForScenario (
181174 scenarioSentence ,
182- stepsDefinition [ scenarioType ] [ currentStepDefinitionFunction ]
175+ stepsDefinition [ scenarioType ] [ currentStepDefinitionFunction ] ,
176+ isOutline
183177 ) ;
184178 }
185179 ) ;
@@ -193,10 +187,142 @@ const findMatchingStep = (currentStep) => {
193187 ) ;
194188} ;
195189
196- const isFunctionForScenario = ( scenarioSentence , stepDefinitionFunction ) =>
197- stepDefinitionFunction . stepRegExp
198- ? scenarioSentence . match ( stepDefinitionFunction . stepRegExp )
199- : scenarioSentence === stepDefinitionFunction . stepExpression ;
190+ const isFunctionForScenario = (
191+ scenarioSentence ,
192+ stepDefinitionFunction ,
193+ isOutline
194+ ) => {
195+ if ( stepDefinitionFunction . stepRegExp ) {
196+ if ( isOutline && / < [ \w ] * > / . test ( scenarioSentence ) ) {
197+ return isPotentialStepFunctionForScenario (
198+ scenarioSentence ,
199+ stepDefinitionFunction . stepRegExp
200+ ) ;
201+ } else return scenarioSentence . match ( stepDefinitionFunction . stepRegExp ) ;
202+ }
203+
204+ return scenarioSentence === stepDefinitionFunction . stepExpression ;
205+ } ;
206+
207+ const isPotentialStepFunctionForScenario = (
208+ scenarioDefinition ,
209+ regStepFunc
210+ ) => {
211+ //so this one is tricky, to ensure we only find the
212+ // step definition corresponding to actual steps function in the case of outlined gherkin
213+ // we have to "disable" the outlining (since it can replace regular expression
214+ // and then ensure that all "non-outlined" part do respect the regular expression of
215+ // of the step function
216+ // FIRST, we clean the string version of the step definition that has outline variable
217+ const cleanedStepFunc = regStepFunc . source
218+ . replace ( / ^ \^ / , "" )
219+ // .replace( /\\\(/g, '(' )
220+ // .replace( /\\\)/g, ')')
221+ // .replace( /\\\^/g, '^')
222+ // .replace( /\\\$/g, '$')
223+ . replace ( / \$ $ / , "" ) ;
224+ // .replace( /\([.\\]+[sSdDwWbB*][*?+]?\)|\(\[.*\](?:[+?*]{1}|\{\d\})\)/g, '' )
225+
226+ let currentScenarioPart ;
227+ let currentStepFuncLeft = cleanedStepFunc ;
228+ let currentScenarioDefLeft = scenarioDefinition ;
229+
230+ //we step through each of the scenario outline variables
231+ // from there, we will try to detect any regexp present in the
232+ // step definition, so that we can ensure to find the right match
233+ while (
234+ ( currentScenarioPart = / < [ \w ] * > / gi. exec ( currentScenarioDefLeft ) ) != null
235+ ) {
236+ let fixedPart = currentScenarioPart . input . substring (
237+ 0 ,
238+ currentScenarioPart . index
239+ ) ;
240+ let idxCutScenarioPart =
241+ currentScenarioPart . index + currentScenarioPart [ 0 ] . length ;
242+
243+ const regEscapedStepFunc = / \( [ a - z A - Z 0 ! | , : ? * + . ^ = $ { } > < \\ \- ] + \) / g. exec (
244+ currentStepFuncLeft
245+ . replace ( / \\ \( / g, "(" )
246+ . replace ( / \\ \) / g, ")" )
247+ . replace ( / \\ \^ / g, "^" )
248+ . replace ( / \\ \$ / g, "$" )
249+ ) ;
250+ const regStepFuncLeft = / \( [ a - z A - Z 0 ! | , : ? * + . ^ = $ { } > < \\ \- ] + \) / g. exec (
251+ currentStepFuncLeft
252+ ) ;
253+
254+ if (
255+ regStepFuncLeft &&
256+ regEscapedStepFunc . index == currentScenarioPart . index
257+ ) {
258+ //if we have a regex inside our step function definition
259+ // and that regex is at the same position than our Outlined variable
260+ // we just need to check that the sentence match,
261+ // so we can "evaluate" the step function and remove the regex in it
262+ currentStepFuncLeft =
263+ regEscapedStepFunc . input . substring ( 0 , regEscapedStepFunc . index ) +
264+ currentStepFuncLeft . substring (
265+ regStepFuncLeft . index + regStepFuncLeft [ 0 ] . length
266+ ) ;
267+ } else if (
268+ regStepFuncLeft &&
269+ regStepFuncLeft . index < currentScenarioPart . index
270+ ) {
271+ //if we have a regex inside our step function definition
272+ // but that regex is not at the same position than our outlined variable
273+ // we need to evaluate the regex against the scenario part
274+ const strRegexToEvaluate = regStepFuncLeft . input . substring (
275+ 0 ,
276+ regStepFuncLeft . index + regStepFuncLeft [ 0 ] . length
277+ ) ;
278+ const regexToEvaluate = new RegExp ( strRegexToEvaluate ) ;
279+ const regIntermediatePart = regexToEvaluate . exec (
280+ currentScenarioPart . input
281+ ) ;
282+ if ( regIntermediatePart ) {
283+ fixedPart = regStepFuncLeft . input . substring (
284+ 0 ,
285+ regStepFuncLeft . index + regStepFuncLeft [ 0 ] . length
286+ ) ;
287+ idxCutScenarioPart = regIntermediatePart [ 0 ] . length ;
288+ }
289+ }
290+
291+ const partIndex = currentStepFuncLeft . indexOf ( fixedPart ) ;
292+ if ( partIndex !== - 1 ) {
293+ currentStepFuncLeft = currentStepFuncLeft . substring (
294+ partIndex + fixedPart . length
295+ ) ;
296+ currentScenarioDefLeft =
297+ currentScenarioDefLeft . substring ( idxCutScenarioPart ) ;
298+ } else {
299+ return false ;
300+ }
301+ }
302+
303+ return (
304+ ( currentScenarioDefLeft === "" && currentStepFuncLeft === "" ) ||
305+ evaluateStepFuncEndVsScenarioEnd (
306+ currentStepFuncLeft ,
307+ currentScenarioDefLeft
308+ )
309+ ) ;
310+ } ;
311+
312+ const evaluateStepFuncEndVsScenarioEnd = (
313+ stepFunctionDef ,
314+ scenarioDefinition
315+ ) => {
316+ if (
317+ / \( .* ( \? \: ) ? [ . \\ ] * [ s S d D w W b B * ] [ * ? + ] ? .* \) | \( \[ .* \] (?: [ + ? * ] { 1 } | \{ \d \} ) \) / g. test (
318+ stepFunctionDef
319+ )
320+ ) {
321+ return new RegExp ( stepFunctionDef ) . test ( scenarioDefinition ) ;
322+ }
323+
324+ return stepFunctionDef . endsWith ( scenarioDefinition ) ;
325+ } ;
200326
201327const injectVariable = (
202328 scenarioType ,
0 commit comments