Skip to content

Commit 33020c9

Browse files
j8kinbruno-morel
authored andcommitted
roll back changes made by previous PR
For unknown reason not able to fix via resolving merge conflict. Raise a new one.
1 parent d504140 commit 33020c9

File tree

7 files changed

+383
-174
lines changed

7 files changed

+383
-174
lines changed

package-lock.json

Lines changed: 97 additions & 135 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
"dependencies": {
2525
"callsites": "~3.1.0",
2626
"jest-cli": "^27.0.3",
27-
"jest-cucumber": "^3.0.1"
27+
"jest-cucumber": "^3.0.2"
2828
},
2929
"devDependencies": {
3030
"codecov": "~4.0.0-0",

src/index.js

Lines changed: 156 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -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 = (
149136
const 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-zA-Z0!|,:?*+.^=${}><\\\-]+\)/g.exec(
244+
currentStepFuncLeft
245+
.replace(/\\\(/g, "(")
246+
.replace(/\\\)/g, ")")
247+
.replace(/\\\^/g, "^")
248+
.replace(/\\\$/g, "$")
249+
);
250+
const regStepFuncLeft = /\([a-zA-Z0!|,:?*+.^=${}><\\\-]+\)/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+
/\(.*(\?\:)?[.\\]*[sSdDwWbB*][*?+]?.*\)|\(\[.*\](?:[+?*]{1}|\{\d\})\)/g.test(
318+
stepFunctionDef
319+
)
320+
) {
321+
return new RegExp(stepFunctionDef).test(scenarioDefinition);
322+
}
323+
324+
return stepFunctionDef.endsWith(scenarioDefinition);
325+
};
200326

201327
const injectVariable = (
202328
scenarioType,
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
Feature: Scenario Outline verification
2+
3+
Scenario Outline: Simple Scenario Outline. Buy item: <Item>
4+
This scenario use Before fusion hook (see scenario-outline2.steps.js)
5+
This hook is remove all items from list of onlineSales if number of items >=2.
6+
This scenario also demonstrate case dependancy in scenario outline:
7+
if you are using some global variable it is not clear between scenarious.
8+
9+
Given I have <nItems> items for sale
10+
When I bought "<Item>"
11+
Then I have <aItems> items for sale
12+
13+
Examples:
14+
| nItems | Item | aItems |
15+
| 0 | Mist written by Stephen King | 1 |
16+
| 1 | Metallica. ReLoad. | 2 |
17+
18+
Scenario Outline: Complex Scenario Outline. <nItems>
19+
Given I have <nItems> items for sale
20+
When I bought the following items:
21+
| Item |
22+
| Mist written by Stephen King |
23+
| Metallica. ReLoad. |
24+
25+
Then I have <aItems> items for sale
26+
27+
Examples:
28+
| nItems | aItems |
29+
| 0 | 2 |
30+
31+
Scenario: Complex Scenario
32+
This scenario is necessary to make sure that related steps are working without examples.
33+
34+
Given I have 0 items for sale
35+
36+
When I bought the following items:
37+
| Item |
38+
| Mist written by Stephen King |
39+
| Metallica. ReLoad. |
40+
| Sabaton. Great War |
41+
42+
Then I have 3 items for sale
43+
44+
Then I want to sell 2 items if they in list
45+
| Item |
46+
| Sabaton. Great War |
47+
| Cucumber for dummies |
48+
49+
Scenario Outline: Using examples in sentance and in table
50+
51+
Given I have 0 items for sale
52+
53+
When I bought the following items:
54+
| Item |
55+
| Mist written by Stephen King |
56+
| Metallica. ReLoad. |
57+
| Sabaton. Great War |
58+
59+
Then I have 3 items for sale
60+
61+
Then I want to sell <nSale> items if they in list
62+
| Item |
63+
| Sabaton. Great War |
64+
| <SaleItemName> |
65+
66+
Then I have <NItems> items for sale
67+
68+
Examples:
69+
| nSale | SaleItemName | NItems |
70+
| 2 | Cucumber for dummies | 2 |
71+
| 3 | Mist written by Stephen King | 1 |
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
const { Given, When, Then, Fusion, Before } = require("../../../../src");
2+
3+
const { OnlineSales } = require("../../../src/online-sales");
4+
5+
const onlineSales = new OnlineSales();
6+
7+
Before(()=>{
8+
if (onlineSales.nItems() >= 2) {
9+
onlineSales.listedItems = [];
10+
}
11+
})
12+
13+
Given(/^I have (\d+) items for sale$/, (nItems) => {
14+
expect(Number(nItems)).toBe(onlineSales.nItems());
15+
});
16+
17+
When(/^I bought "(.+)"/, (item) => {
18+
onlineSales.buyItem(item);
19+
});
20+
21+
When(/^I bought the following items:$/, (table) => {
22+
if (typeof table === "string") return;
23+
24+
table.forEach((row) => {
25+
onlineSales.buyItem(row.Item)
26+
});
27+
});
28+
29+
Then(/^I have (\d+) items for sale$/, (nItems) => {
30+
expect(Number(nItems)).toBe(onlineSales.nItems());
31+
});
32+
33+
Then(/^I want to sell (\d+) items if they in list$/, (nItems, table) => {
34+
if (typeof nItems !== "string") return;
35+
if (typeof table === "string") return;
36+
let iNumber = Number(nItems);
37+
38+
table.forEach((row) => {
39+
if (iNumber > 0 && onlineSales.listedItems.includes(row.Item)) {
40+
onlineSales.sellItem(row.Item);
41+
iNumber--;
42+
}
43+
});
44+
});
45+
46+
Fusion("../scenario-outline2.feature");

test/specs/features/using-dynamic-values.feature

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,12 @@ Feature: Getting rich writing software
2323

2424
# Use test step with parameter in step RegExp and paramter in step Table
2525
Then my account#<accN> should be:
26-
| field | value |
27-
| name | Cash |
28-
| balance | <balance> |
29-
| type | Account |
26+
| field | value |
27+
| name | <Type> |
28+
| balance | <balance> |
29+
| type | Account |
3030

31-
Examples:
32-
| NAccounts | accN | balance | Description |
33-
| 2 | 1 | 10000 | First Scenario |
34-
| 2 | 2 | 1234 | Second Scenario |
31+
Examples:
32+
| NAccounts | accN | Type | balance | Description |
33+
| 2 | 1 | Cash | 10000 | First Scenario |
34+
| 2 | 2 | Deposit | 1234 | Second Scenario |

0 commit comments

Comments
 (0)