quickpickle
Advanced tools
Comparing version 1.2.2 to 1.2.3
import { Plugin } from 'vite'; | ||
import { BeforeAll, applyBeforeAllHooks, Before, applyBeforeHooks, AfterAll, applyAfterAllHooks, After, applyAfterHooks, BeforeStep, applyBeforeStepHooks, AfterStep, applyAfterStepHooks } from './hooks'; | ||
import { explodeTags, tagsMatch } from './render'; | ||
import { DataTable } from '@cucumber/cucumber'; | ||
@@ -7,2 +8,3 @@ import { DocString } from './models/DocString'; | ||
export { DocString, DataTable }; | ||
export { explodeTags, tagsMatch }; | ||
export { BeforeAll, Before, AfterAll, After, BeforeStep, AfterStep }; | ||
@@ -14,5 +16,5 @@ export { applyBeforeAllHooks, applyBeforeHooks, applyAfterAllHooks, applyAfterHooks, applyBeforeStepHooks, applyAfterStepHooks, }; | ||
export declare const qp: (step: string, state: any, line: number, data?: any) => Promise<any>; | ||
export type QuickPickleConfig<T = { | ||
export type QuickPickleConfigSetting<T = { | ||
[key: string]: any; | ||
}> = { | ||
}> = Partial<{ | ||
todoTags: string | string[]; | ||
@@ -24,8 +26,18 @@ skipTags: string | string[]; | ||
explodeTags: string | string[] | string[][]; | ||
worldConfig: T; | ||
worldConfig: Partial<T>; | ||
}>; | ||
export type QuickPickleConfig<T = { | ||
[key: string]: any; | ||
}> = { | ||
todoTags: string[]; | ||
skipTags: string[]; | ||
failTags: string[]; | ||
concurrentTags: string[]; | ||
sequentialTags: string[]; | ||
explodeTags: string[][]; | ||
worldConfig: Partial<T>; | ||
}; | ||
export declare const defaultConfig: QuickPickleConfig; | ||
export declare function normalizeTags(tags?: string | string[] | undefined): string[]; | ||
export declare function explodeTags(explodeTags: string[][], testTags: string[]): string[][]; | ||
export declare const quickpickle: (conf?: Partial<QuickPickleConfig>) => Plugin; | ||
export declare const quickpickle: (conf?: Partial<QuickPickleConfigSetting>) => Plugin; | ||
export default quickpickle; |
import { ExpressionFactory, ParameterTypeRegistry } from '@cucumber/cucumber-expressions'; | ||
import { isFunction, isString, isObject, concat, intersection, fromPairs, pick, get } from 'lodash-es'; | ||
import { isFunction, isString, isObject, concat, intersection, fromPairs, pick, defaultsDeep, get } from 'lodash-es'; | ||
import parse from '@cucumber/tag-expressions'; | ||
import * as Gherkin from '@cucumber/gherkin'; | ||
import * as Messages from '@cucumber/messages'; | ||
import { DataTable } from '@cucumber/cucumber'; | ||
export { DataTable } from '@cucumber/cucumber'; | ||
import * as Gherkin from '@cucumber/gherkin'; | ||
import * as Messages from '@cucumber/messages'; | ||
@@ -227,11 +227,12 @@ const steps = []; | ||
tags = [...tags, ...child.scenario.tags.map(t => t.name)]; | ||
let todo = (intersection(config.todoTags, tags).length > 0) ? '.todo' : ''; | ||
let skip = (intersection(config.skipTags, tags).length > 0) ? '.skip' : ''; | ||
let fails = (intersection(config.failTags, tags).length > 0) ? '.fails' : ''; | ||
let concurrent = (intersection(config.concurrentTags, tags).length > 0) ? '.concurrent' : ''; | ||
let sequential = (intersection(config.sequentialTags, tags).length > 0) ? '.sequential' : ''; | ||
let todo = tagsMatch(config.todoTags, tags) ? '.todo' : ''; | ||
let skip = tagsMatch(config.skipTags, tags) ? '.skip' : ''; | ||
let fails = tagsMatch(config.failTags, tags) ? '.fails' : ''; | ||
let sequential = tagsMatch(config.sequentialTags, tags) ? '.sequential' : ''; | ||
let concurrent = (!sequential && tagsMatch(config.concurrentTags, tags)) ? '.concurrent' : ''; | ||
let attrs = todo + skip + fails + concurrent + sequential; | ||
// Deal with exploding tags | ||
let taglists = explodeTags(config.explodeTags, tags); | ||
return taglists.map(tags => { | ||
let isExploded = taglists.length > 1 ? true : false; | ||
return taglists.map((tags, explodedIdx) => { | ||
// For Scenario Outlines with examples | ||
@@ -259,3 +260,3 @@ if (child.scenario.examples?.[0]?.tableHeader && child.scenario.examples?.[0]?.tableBody) { | ||
text = replaceParamNames(text, true); | ||
return `${sp} await qp(\`${text}\`, state, ${step.location.line});`; | ||
return `${sp} await qp(\`${text}\`, state, ${step.location.line}${isExploded ? '.' + explodedIdx : ''});`; | ||
}).join('\n')} | ||
@@ -270,3 +271,3 @@ ${sp} await afterScenario(state); | ||
${sp} let state = await ${initFn}(context, '${q(child.scenario.name)}', ['${tags.join("', '") || ''}']); | ||
${renderSteps(child.scenario.steps, config, sp + ' ')} | ||
${renderSteps(child.scenario.steps, config, sp + ' ', isExploded ? `.${explodedIdx}` : '')} | ||
${sp} await afterScenario(state); | ||
@@ -277,3 +278,3 @@ ${sp}}); | ||
} | ||
function renderSteps(steps, config, sp = ' ') { | ||
function renderSteps(steps, config, sp = ' ', explodedText = '') { | ||
return steps.map(step => { | ||
@@ -284,12 +285,87 @@ if (step.dataTable) { | ||
})); | ||
return `${sp}await qp('${q(step.text)}', state, ${step.location.line}, ${data});`; | ||
return `${sp}await qp('${q(step.text)}', state, ${step.location.line}${explodedText}, ${data});`; | ||
} | ||
else if (step.docString) { | ||
let data = JSON.stringify(pick(step.docString, ['content', 'mediaType'])); | ||
return `${sp}await qp('${q(step.text)}', state, ${step.location.line}, ${data});`; | ||
return `${sp}await qp('${q(step.text)}', state, ${step.location.line}${explodedText}, ${data});`; | ||
} | ||
return `${sp}await qp('${q(step.text)}', state, ${step.location.line});`; | ||
return `${sp}await qp('${q(step.text)}', state, ${step.location.line}${explodedText});`; | ||
}).join('\n'); | ||
} | ||
/** | ||
* Escapes quotation marks in a string for the purposes of this rendering function. | ||
* @param t string | ||
* @returns string | ||
*/ | ||
const q = (t) => (t.replace(/'/g, "\\'")); | ||
/** | ||
* Creates a 2d array of all possible combinations of the items in the input array | ||
* @param arr Array | ||
* @returns A 2d array of all possible combinations of the items in the input array | ||
*/ | ||
function explodeArray(arr) { | ||
if (arr.length === 0) | ||
return [[]]; | ||
const [first, ...rest] = arr; | ||
const subCombinations = explodeArray(rest); | ||
return first.flatMap(item => subCombinations.map(subCombo => [item, ...subCombo])); | ||
} | ||
/** | ||
* This function "explodes" any tags in the "explodeTags" setting and returns all possible | ||
* combinations of all the tags. The theory is that it allows you to write one Scenario that | ||
* runs multiple times in different ways; e.g. with and without JS or in different browsers. | ||
* | ||
* To take this case as an example, if the explodeTags are: | ||
* ``` | ||
* [ | ||
* ['nojs', 'js'], | ||
* ['firefox', 'chromium', 'webkit'], | ||
* ] | ||
* ``` | ||
* | ||
* And the testTags are: | ||
* ``` | ||
* ['nojs', 'js', 'snapshot'] | ||
* ``` | ||
* | ||
* Then the function will return: | ||
* ``` | ||
* [ | ||
* ['nojs', 'snapshot'], | ||
* ['js', 'snapshot'], | ||
* ] | ||
* ``` | ||
* | ||
* In that case, the test will be run twice. | ||
* | ||
* @param explodeTags the 2d array of tags that should be exploded | ||
* @param testTags the tags to test against | ||
* @returns a 2d array of all possible combinations of tags | ||
*/ | ||
function explodeTags(explodeTags, testTags) { | ||
if (!explodeTags.length) | ||
return [testTags]; | ||
let tagsToTest = [...testTags]; | ||
// gather a 2d array of items that are shared between tags and each array in explodeTags | ||
// and then remove those items from the tags array | ||
const sharedTags = explodeTags.map(tagList => { | ||
let items = tagList.filter(tag => tagsToTest.includes(tag)); | ||
if (items.length) | ||
items.forEach(item => tagsToTest.splice(tagsToTest.indexOf(item), 1)); | ||
return items; | ||
}); | ||
// then, build a 2d array of all possible combinations of the shared tags | ||
let combined = explodeArray(sharedTags); | ||
// finally, return the list | ||
return combined.length ? combined.map(arr => [...tagsToTest, ...arr]) : [testTags]; | ||
} | ||
/** | ||
* | ||
* @param confTags string[] | ||
* @param testTags string[] | ||
* @returns boolean | ||
*/ | ||
function tagsMatch(confTags, testTags) { | ||
return intersection(confTags.map(t => t.toLowerCase()), testTags.map(t => t.toLowerCase()))?.length ? true : false; | ||
} | ||
@@ -325,2 +401,5 @@ class DocString extends String { | ||
async init() { } | ||
tagsMatch(tags) { | ||
return tagsMatch(tags, this.info.tags); | ||
} | ||
} | ||
@@ -402,26 +481,2 @@ let worldConstructor = QuickPickleWorld; | ||
} | ||
function explodeArray(arr) { | ||
if (arr.length === 0) | ||
return [[]]; | ||
const [first, ...rest] = arr; | ||
const subCombinations = explodeArray(rest); | ||
return first.flatMap(item => subCombinations.map(subCombo => [item, ...subCombo])); | ||
} | ||
function explodeTags(explodeTags, testTags) { | ||
if (!explodeTags.length) | ||
return [testTags]; | ||
let tagsToTest = [...testTags]; | ||
// gather a 3d array of items that are shared between tags and each array in explodeTags | ||
// and then remove those items from the tags array | ||
const sharedTags = explodeTags.map(tagList => { | ||
let items = tagList.filter(tag => tagsToTest.includes(tag)); | ||
if (items.length) | ||
items.forEach(item => tagsToTest.splice(tagsToTest.indexOf(item), 1)); | ||
return items; | ||
}); | ||
// then, build a 3d array of all possible combinations of the remaining tags | ||
let combined = explodeArray(sharedTags); | ||
// finally, return the list | ||
return combined.length ? combined.map(arr => [...tagsToTest, ...arr]) : [testTags]; | ||
} | ||
const quickpickle = (conf = {}) => { | ||
@@ -433,3 +488,3 @@ let config; | ||
configResolved(resolvedConfig) { | ||
config = Object.assign({}, defaultConfig, passedConfig, get(resolvedConfig, 'quickpickle') || {}, get(resolvedConfig, 'test.quickpickle') || {}); | ||
config = defaultsDeep(get(resolvedConfig, 'test.quickpickle') || {}, get(resolvedConfig, 'quickpickle') || {}, passedConfig, defaultConfig); | ||
config.todoTags = normalizeTags(config.todoTags); | ||
@@ -453,3 +508,3 @@ config.skipTags = normalizeTags(config.skipTags); | ||
export { After, AfterAll, AfterStep, Before, BeforeAll, BeforeStep, DocString, Given, QuickPickleWorld, Then, When, applyAfterAllHooks, applyAfterHooks, applyAfterStepHooks, applyBeforeAllHooks, applyBeforeHooks, applyBeforeStepHooks, quickpickle as default, defaultConfig, explodeTags, getWorldConstructor, normalizeTags, qp, quickpickle, setWorldConstructor }; | ||
export { After, AfterAll, AfterStep, Before, BeforeAll, BeforeStep, DocString, Given, QuickPickleWorld, Then, When, applyAfterAllHooks, applyAfterHooks, applyAfterStepHooks, applyBeforeAllHooks, applyBeforeHooks, applyBeforeStepHooks, quickpickle as default, defaultConfig, explodeTags, getWorldConstructor, normalizeTags, qp, quickpickle, setWorldConstructor, tagsMatch }; | ||
//# sourceMappingURL=index.esm.js.map |
@@ -5,1 +5,41 @@ import type { Feature } from "@cucumber/messages"; | ||
export declare function renderFeature(feature: Feature, config: QuickPickleConfig): string; | ||
/** | ||
* This function "explodes" any tags in the "explodeTags" setting and returns all possible | ||
* combinations of all the tags. The theory is that it allows you to write one Scenario that | ||
* runs multiple times in different ways; e.g. with and without JS or in different browsers. | ||
* | ||
* To take this case as an example, if the explodeTags are: | ||
* ``` | ||
* [ | ||
* ['nojs', 'js'], | ||
* ['firefox', 'chromium', 'webkit'], | ||
* ] | ||
* ``` | ||
* | ||
* And the testTags are: | ||
* ``` | ||
* ['nojs', 'js', 'snapshot'] | ||
* ``` | ||
* | ||
* Then the function will return: | ||
* ``` | ||
* [ | ||
* ['nojs', 'snapshot'], | ||
* ['js', 'snapshot'], | ||
* ] | ||
* ``` | ||
* | ||
* In that case, the test will be run twice. | ||
* | ||
* @param explodeTags the 2d array of tags that should be exploded | ||
* @param testTags the tags to test against | ||
* @returns a 2d array of all possible combinations of tags | ||
*/ | ||
export declare function explodeTags(explodeTags: string[][], testTags: string[]): string[][]; | ||
/** | ||
* | ||
* @param confTags string[] | ||
* @param testTags string[] | ||
* @returns boolean | ||
*/ | ||
export declare function tagsMatch(confTags: string[], testTags: string[]): boolean; |
@@ -16,2 +16,3 @@ import type { TestContext } from 'vitest'; | ||
init: () => Promise<void>; | ||
tagsMatch(tags: string[]): boolean; | ||
} | ||
@@ -24,2 +25,3 @@ export declare class QuickPickleWorld implements QuickPickleWorldInterface { | ||
init(): Promise<void>; | ||
tagsMatch(tags: string[]): boolean; | ||
} | ||
@@ -26,0 +28,0 @@ export type WorldConstructor = new (context: TestContext, info?: QuickPickleWorldInterface['info'], worldConfig?: any) => QuickPickleWorldInterface; |
{ | ||
"name": "quickpickle", | ||
"version": "1.2.2", | ||
"version": "1.2.3", | ||
"description": "Plugin for Vitest to run tests written in Gherkin Syntax.", | ||
@@ -5,0 +5,0 @@ "keywords": [ |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
88706
1166