should be displayed
59 |
60 | Examples:
61 | | language | ready_message |
62 | | en | Ready |
63 | | fr | Pret |
64 | `)
65 |
66 | const onChange = value => {
67 | console.log(value)
68 | setValue(value)
69 | }
70 |
71 | const themeOptions = [
72 | { label: 'jira', value: 'jira' },
73 | { label: 'cucumber', value: 'cucumber' }
74 | ]
75 | const modeOptions = [
76 | { label: 'gherkin i18n', value: 'gherkin_i18n' },
77 | { label: 'gherkin background i18n', value: 'gherkin_background_i18n' },
78 | { label: 'gherkin scenario i18n', value: 'gherkin_scenario_i18n' }
79 | ]
80 |
81 | const ToolbarContent = (
82 |
83 | Theme
84 |
103 | )
104 |
105 | return (
106 |
107 | setLanguage(option.value)}
113 | theme={theme}
114 | mode={mode}
115 | autoCompleteFunction={autoCompleteFunction}
116 | toolbarContent={ToolbarContent}
117 | autoFocus
118 | activateLinter
119 | showGutter
120 | setOptions={{
121 | showLineNumbers: true
122 | }}
123 | />
124 |
125 | )
126 | }
127 |
128 | const root = document.createElement('div')
129 | document.body.appendChild(root)
130 |
131 | ReactDOM.createRoot(root).render()
132 |
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | preset: 'ts-jest/presets/js-with-ts',
3 | setupFilesAfterEnv: ['./jest.setup.ts'],
4 | testEnvironment: 'jsdom',
5 | transformIgnorePatterns: ['node_modules/(?!escape-string-regexp|uuid/)']
6 | }
7 |
--------------------------------------------------------------------------------
/jest.setup.ts:
--------------------------------------------------------------------------------
1 | import '@testing-library/jest-dom'
2 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@smartbear/react-gherkin-editor",
3 | "version": "2.4.14",
4 | "description": "A Gherkin language editor for React.",
5 | "homepage": "https://github.com/SmartBear/react-gherkin-editor",
6 | "repository": {
7 | "type": "git",
8 | "url": "https://github.com/SmartBear/react-gherkin-editor"
9 | },
10 | "main": "./dist/index.js",
11 | "types": "./dist/index.d.ts",
12 | "author": "SmartBear Software",
13 | "license": "MIT",
14 | "files": [
15 | "dist"
16 | ],
17 | "scripts": {
18 | "start": "webpack serve",
19 | "pretty": "prettier --write .",
20 | "test": "jest",
21 | "lint": "concurrently yarn:eslint yarn:type-check",
22 | "eslint": "eslint --ext js,jsx,ts,tsx .",
23 | "type-check": "tsc --noEmit",
24 | "build": "tsc --project tsconfig.build.json",
25 | "release": "node scripts/release.js",
26 | "prepack": "rm -rf dist && yarn build",
27 | "postpack": "rm -rf dist",
28 | "postversion": "git push --follow-tags"
29 | },
30 | "devDependencies": {
31 | "@testing-library/jest-dom": "^6.6.3",
32 | "@testing-library/react": "^14.3.0",
33 | "@types/jest": "^29.5.14",
34 | "@types/react": "^18.2.79",
35 | "@typescript-eslint/parser": "^5.61.0",
36 | "buffer": "^6.0.3",
37 | "concurrently": "^8.2.2",
38 | "eslint": "^8.57.1",
39 | "eslint-config-standard": "^17.1.0",
40 | "eslint-plugin-import": "^2.31.0",
41 | "eslint-plugin-jest": "^27.9.0",
42 | "eslint-plugin-n": "^15.7.0",
43 | "eslint-plugin-promise": "^6.6.0",
44 | "eslint-plugin-react": "^7.37.5",
45 | "html-webpack-plugin": "^5.6.3",
46 | "jest": "^29.7.0",
47 | "jest-environment-jsdom": "^29.7.0",
48 | "prettier": "^3.5.3",
49 | "process": "^0.11.10",
50 | "prop-types": "^15.8.1",
51 | "react": "^18.2.0",
52 | "react-dom": "^18.2.0",
53 | "react-is": "^18.3.1",
54 | "react-select-event": "^5.5.1",
55 | "semver": "^7.7.2",
56 | "stream-browserify": "^3.0.0",
57 | "styled-components": "^5.3.11",
58 | "ts-jest": "^29.2.6",
59 | "ts-loader": "^9.5.2",
60 | "typescript": "^5.5.4",
61 | "webpack": "^5.99.9",
62 | "webpack-cli": "^5.1.4",
63 | "webpack-dev-server": "^4.15.1"
64 | },
65 | "dependencies": {
66 | "@cucumber/gherkin": "^26.2.0",
67 | "ace-builds": "^1.42.0",
68 | "calculate-size": "^1.1.1",
69 | "escape-string-regexp": "^5.0.0",
70 | "lodash": "^4.17.21",
71 | "re-resizable": "^6.11.2",
72 | "react-ace": "^10.1.0",
73 | "react-select": "^5.10.1"
74 | },
75 | "peerDependencies": {
76 | "react": ">=16.8.0 <19.0.0",
77 | "react-dom": ">=16.8.0 <19.0.0",
78 | "styled-components": ">=3.0.0 <6.0.0"
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/scripts/release.js:
--------------------------------------------------------------------------------
1 | const semver = require('semver')
2 | const fs = require('fs')
3 | const { execSync } = require('child_process')
4 |
5 | const releaseTypes = ['major', 'minor', 'patch']
6 | const changelogFile = 'CHANGELOG.md'
7 |
8 | const exec = command => {
9 | try {
10 | command()
11 | } catch (error) {
12 | console.log(`Error: ${error.message}`)
13 | process.exitCode = 1
14 | }
15 | }
16 |
17 | const release = () => {
18 | const releaseType = computeReleaseType()
19 |
20 | const oldVersion = process.env.npm_package_version
21 | const newVersion = semver.inc(oldVersion, releaseType)
22 |
23 | console.log(`Preparing ${releaseType} release from v${oldVersion} to v${newVersion}...`)
24 |
25 | console.log('Updating changelog...')
26 |
27 | updateChangelog(newVersion)
28 |
29 | console.log(`Creating a new version using "yarn version --${releaseType}"...`)
30 |
31 | createVersion(releaseType)
32 |
33 | console.log('Release done! GitHub will publish it.')
34 | }
35 |
36 | const computeReleaseType = () => {
37 | const args = process.argv.slice(2)
38 |
39 | const requestedReleaseTypes = releaseTypes.filter(releaseType => args.includes(`--${releaseType}`))
40 |
41 | if (requestedReleaseTypes.length === 0) {
42 | throw new Error('No release type specified. Please specify a new version using --major, --minor or --patch.')
43 | }
44 |
45 | if (requestedReleaseTypes.length > 1) {
46 | throw new Error('Multiple release types specified. Please specify a new version using --major, --minor or --patch.')
47 | }
48 |
49 | return requestedReleaseTypes[0]
50 | }
51 |
52 | const updateChangelog = newVersion => {
53 | const changelog = fs.readFileSync(changelogFile, 'utf8')
54 | const newChangelog = changelog.replace('## [Unreleased]', `## [Unreleased]\n\n## [${newVersion}]`)
55 |
56 | fs.writeFileSync(changelogFile, newChangelog, 'utf8')
57 | execSync(`git add ${changelogFile}`)
58 | }
59 |
60 | const createVersion = releaseType => {
61 | const command = `yarn version --${releaseType}`
62 |
63 | execSync(command, { stdio: 'inherit' })
64 | }
65 |
66 | exec(release)
67 |
--------------------------------------------------------------------------------
/src/components/GherkinEditor/index.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect, useRef, useImperativeHandle } from 'react'
2 | import PropTypes from 'prop-types'
3 | import AceEditor from 'react-ace'
4 | import { require as acequire } from 'ace-builds'
5 | import { Resizable } from 're-resizable'
6 | import KeywordCompleter from '../../modules/keyword-completer'
7 | import StepCompleter from '../../modules/step-completer'
8 | import {
9 | setGherkinDialect as setDialect,
10 | getGherkinDialect as getDialect
11 | } from '../../modules/dialects/gherkin_i18n'
12 | import {
13 | setGherkinDialect as setBackgroundDialect,
14 | getGherkinDialect as getBackgroundDialect
15 | } from '../../modules/dialects/gherkin_background_i18n'
16 | import {
17 | setGherkinDialect as setScenarioDialect,
18 | getGherkinDialect as getScenarioDialect
19 | } from '../../modules/dialects/gherkin_scenario_i18n'
20 | import GherkinAnnotator from '../../modules/gherkin-annotator'
21 | import Toolbar from '../Toolbar'
22 | import { EditorWrapper } from './styled'
23 |
24 | import 'ace-builds/src-noconflict/ext-language_tools'
25 |
26 | import '../../themes/jira'
27 | import '../../themes/cucumber'
28 |
29 | import '../../modules/mode/gherkin_i18n'
30 | import '../../modules/mode/gherkin_background_i18n'
31 | import '../../modules/mode/gherkin_scenario_i18n'
32 |
33 | const setGherkinDialectFunctions = {
34 | gherkin_i18n: setDialect,
35 | gherkin_background_i18n: setBackgroundDialect,
36 | gherkin_scenario_i18n: setScenarioDialect
37 | }
38 |
39 | const getGherkinDialectFunctions = {
40 | gherkin_i18n: getDialect,
41 | gherkin_background_i18n: getBackgroundDialect,
42 | gherkin_scenario_i18n: getScenarioDialect
43 | }
44 |
45 | const defaultOptions = {
46 | fontFamily: [
47 | "'SFMono-Medium'",
48 | "'SF Mono'",
49 | "'Segoe UI Mono'",
50 | "'Roboto Mono'",
51 | "'Ubuntu Mono'",
52 | 'Menlo',
53 | 'Consolas',
54 | 'Courier',
55 | 'monospace'
56 | ].join(', '),
57 | enableBasicAutocompletion: true,
58 | enableLiveAutocompletion: true,
59 | showLineNumbers: false,
60 | displayIndentGuides: false,
61 | tabSize: 2
62 | }
63 |
64 | let gherkinAnnotator = null
65 |
66 | const GherkinEditor = React.forwardRef((props, ref) => {
67 | const [currentLanguage, setCurrentLanguage] = useState(props.language)
68 | const [height, setHeight] = useState(props.initialHeight)
69 |
70 | const aceEditorRef = useRef()
71 |
72 | const {
73 | activateLinter,
74 | autoCompleteFunction,
75 | autoFocus,
76 | hideToolbar,
77 | initialValue,
78 | language,
79 | mode,
80 | onLanguageChange,
81 | onParse,
82 | onSubmit,
83 | readOnly,
84 | setOptions,
85 | showGutter,
86 | theme,
87 | toolbarContent,
88 | uniqueId
89 | } = props
90 |
91 | const setGherkinDialect = setGherkinDialectFunctions[mode] || setDialect
92 | const getGherkinDialect = getGherkinDialectFunctions[mode] || getDialect
93 | const isLinterActivated = activateLinter && showGutter
94 |
95 | useEffect(() => {
96 | if (autoFocus) {
97 | aceEditorRef.current.editor.focus()
98 | }
99 | }, [autoFocus])
100 |
101 | useEffect(() => {
102 | const keywordCompleter = new KeywordCompleter(getGherkinDialect)
103 | const stepCompleter = new StepCompleter(
104 | autoCompleteFunction,
105 | getGherkinDialect
106 | )
107 | const langTools = acequire('ace/ext/language_tools')
108 |
109 | langTools.setCompleters([keywordCompleter, stepCompleter])
110 | }, [autoCompleteFunction, getGherkinDialect])
111 |
112 | useEffect(() => {
113 | setCurrentLanguage(language)
114 | }, [language])
115 |
116 | useEffect(() => {
117 | setGherkinDialect(currentLanguage)
118 |
119 | aceEditorRef.current.editor.session.setMode({
120 | path: `ace/mode/${mode}`,
121 | v: Date.now()
122 | })
123 | }, [setGherkinDialect, currentLanguage, mode])
124 |
125 | useEffect(() => {
126 | if (!isLinterActivated) {
127 | gherkinAnnotator = null
128 | return
129 | }
130 |
131 | const session = aceEditorRef.current.editor.getSession()
132 |
133 | if (!gherkinAnnotator) {
134 | gherkinAnnotator = new GherkinAnnotator(session, onParse)
135 | } else {
136 | gherkinAnnotator.setSession(session)
137 | }
138 | }, [isLinterActivated])
139 |
140 | useEffect(() => {
141 | if (gherkinAnnotator) {
142 | gherkinAnnotator.setLanguage(currentLanguage)
143 | gherkinAnnotator.setMode(mode)
144 | gherkinAnnotator.annotate(initialValue)
145 | }
146 | }, [currentLanguage, mode, initialValue])
147 |
148 | useImperativeHandle(ref, () => ({
149 | editor: aceEditorRef.current.editor
150 | }))
151 |
152 | if (activateLinter && !showGutter) {
153 | console.warn('activateLinter requires showGutter to be true')
154 | }
155 |
156 | const onResizeStop = (_event, _direction, _refToElement, delta) => {
157 | setHeight(height + delta.height)
158 | }
159 |
160 | const languageChangeHandler = option => {
161 | setCurrentLanguage(option.value)
162 | onLanguageChange(option)
163 | }
164 |
165 | const onChangeHandler = (newValue, ...args) => {
166 | if (gherkinAnnotator) {
167 | gherkinAnnotator.annotate(newValue)
168 | }
169 |
170 | return props.onChange(newValue, ...args)
171 | }
172 |
173 | const options = { ...defaultOptions, ...setOptions }
174 |
175 | return (
176 |
177 | {!hideToolbar && (
178 |
184 | )}
185 |
199 | onSubmit(editor.getValue())
214 | }
215 | ]}
216 | />
217 |
218 |
219 | )
220 | })
221 |
222 | GherkinEditor.propTypes = {
223 | initialValue: PropTypes.string,
224 | language: PropTypes.string,
225 | hideToolbar: PropTypes.bool,
226 | readOnly: PropTypes.bool,
227 | uniqueId: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
228 | toolbarContent: PropTypes.node,
229 | onChange: PropTypes.func,
230 | onSubmit: PropTypes.func,
231 | autoCompleteFunction: PropTypes.func,
232 | onLanguageChange: PropTypes.func,
233 | autoFocus: PropTypes.bool,
234 | initialHeight: PropTypes.number,
235 | theme: PropTypes.string,
236 | mode: PropTypes.oneOf([
237 | 'gherkin_i18n',
238 | 'gherkin_background_i18n',
239 | 'gherkin_scenario_i18n'
240 | ]),
241 | fontSize: PropTypes.number,
242 | width: PropTypes.string,
243 | showPrintMargin: PropTypes.bool,
244 | showGutter: PropTypes.bool,
245 | highlightActiveLine: PropTypes.bool,
246 | activateLinter: PropTypes.bool,
247 | setOptions: PropTypes.object
248 | }
249 |
250 | GherkinEditor.defaultProps = {
251 | initialValue: '',
252 | language: 'en',
253 | hideToolbar: false,
254 | readOnly: false,
255 | uniqueId: Math.random().toString(36).substr(2, 9),
256 | onChange: () => {},
257 | onSubmit: () => {},
258 | autoCompleteFunction: () => Promise.resolve([]),
259 | onLanguageChange: () => {},
260 | autoFocus: false,
261 | initialHeight: 500,
262 | theme: 'jira',
263 | mode: 'gherkin_i18n',
264 | fontSize: 14,
265 | width: '100%',
266 | showPrintMargin: false,
267 | showGutter: false,
268 | highlightActiveLine: false,
269 | activateLinter: false,
270 | setOptions: {}
271 | }
272 |
273 | export default GherkinEditor
274 |
--------------------------------------------------------------------------------
/src/components/GherkinEditor/styled.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components'
2 |
3 | export const EditorWrapper = styled.div`
4 | border-width: 1px;
5 | border-style: solid;
6 | border-color: rgb(223, 225, 230);
7 | border-radius: 3px;
8 | `
9 |
--------------------------------------------------------------------------------
/src/components/Toolbar/index.tsx:
--------------------------------------------------------------------------------
1 | import gherkinLanguages from '../../lib/gherkin-languages'
2 | import _find from 'lodash/find'
3 | import React from 'react'
4 | import Select from 'react-select'
5 |
6 | import { LanguageDropdownContainer, ToolbarContainer } from './styled'
7 |
8 | interface ToolbarProps {
9 | content?: React.ReactNode
10 | language?: string
11 | readOnly?: boolean
12 | onLanguageChange?(option: object): void
13 | }
14 |
15 | const availableLanguages = Object.entries(gherkinLanguages).map(
16 | ([key, language]) => ({
17 | value: key,
18 | label: (language as any).native
19 | })
20 | )
21 |
22 | const languageSelectStyles = {
23 | container: styles => ({ ...styles, 'z-index': 5 })
24 | }
25 |
26 | const Toolbar = ({
27 | content,
28 | language = 'en',
29 | readOnly = false,
30 | onLanguageChange = () => {}
31 | }: ToolbarProps) => {
32 | const gherkinLanguage = _find(availableLanguages, { value: language })
33 |
34 | return (
35 |
36 |
37 |
45 |
46 | {content}
47 |
48 | )
49 | }
50 |
51 | export default Toolbar
52 |
--------------------------------------------------------------------------------
/src/components/Toolbar/styled.ts:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components'
2 |
3 | export const ToolbarContainer = styled.div`
4 | display: flex;
5 | flex-direction: row;
6 | flex-wrap: wrap;
7 | align-items: center;
8 | justify-content: space-between;
9 | padding: 3px;
10 | background-color: rgb(235, 236, 240);
11 | `
12 |
13 | export const LanguageDropdownContainer = styled.div`
14 | min-width: 150px;
15 | `
16 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | import GherkinEditor from './components/GherkinEditor'
2 | import GherkinLinter from './lib/gherkin-linter'
3 |
4 | export { GherkinLinter }
5 |
6 | export default GherkinEditor
7 |
--------------------------------------------------------------------------------
/src/lib/gherkin-languages/index.ts:
--------------------------------------------------------------------------------
1 | import Languages from '@cucumber/gherkin/dist/src/gherkin-languages.json'
2 |
3 | export type LanguageIdentifier = keyof typeof Languages
4 |
5 | export default Languages
6 |
--------------------------------------------------------------------------------
/src/lib/gherkin-linter/index.ts:
--------------------------------------------------------------------------------
1 | import { generateMessages } from '@cucumber/gherkin'
2 | import * as m from '@cucumber/messages'
3 | import gherkinLanguages from '../gherkin-languages'
4 |
5 | export type OnParseCallback = (messages: Readonly) => void
6 |
7 | export default class GherkinLinter {
8 | private options: object
9 | private offset: number
10 | private isSubset: boolean
11 | private subsetType: string
12 | private language: string
13 | private featureKeyword: string
14 | private lastParsedGherkin: string
15 | private lintingErrors: object[]
16 |
17 | constructor(private onParse?: OnParseCallback) {
18 | this.options = {
19 | includeGherkinDocument: true,
20 | newId: () => Math.random().toString()
21 | }
22 |
23 | this.offset = 0
24 | this.isSubset = false
25 | this.subsetType = ''
26 |
27 | this.language = 'en'
28 | this.featureKeyword = 'Feature'
29 |
30 | this.lastParsedGherkin = ''
31 | this.lintingErrors = []
32 | }
33 |
34 | setLanguage(language) {
35 | language ||= 'en'
36 |
37 | if (this.language === language) {
38 | return this
39 | }
40 |
41 | if (!gherkinLanguages[language]) {
42 | return this
43 | }
44 |
45 | this.language = language
46 | this.featureKeyword = gherkinLanguages[this.language].feature[0]
47 |
48 | this.lastParsedGherkin = ''
49 |
50 | return this
51 | }
52 |
53 | setSubsetType(type) {
54 | if (type === this.subsetType) {
55 | return this
56 | }
57 |
58 | if (type === 'scenario' || type === 'background') {
59 | this.subsetType = type
60 | this.isSubset = true
61 | } else {
62 | this.subsetType = ''
63 | this.isSubset = false
64 | }
65 |
66 | this.lastParsedGherkin = ''
67 |
68 | return this
69 | }
70 |
71 | parse(gherkin) {
72 | if (gherkin === this.lastParsedGherkin) {
73 | return this
74 | }
75 |
76 | this.parseGherkin(gherkin)
77 | this.lastParsedGherkin = gherkin
78 |
79 | return this
80 | }
81 |
82 | getLintingErrors() {
83 | return this.lintingErrors
84 | }
85 |
86 | private parseGherkin(gherkin) {
87 | const messages = generateMessages(
88 | this.getContentToLint(gherkin),
89 | 'feature.feature',
90 | m.SourceMediaType.TEXT_X_CUCUMBER_GHERKIN_PLAIN,
91 | { includeSource: !!this.onParse, ...this.options }
92 | )
93 |
94 | this.lintingErrors = messages
95 | .filter(message => message.parseError)
96 | .map(message => ({
97 | line: message.parseError.source.location.line - this.offset,
98 | row: message.parseError.source.location.line - 1 - this.offset,
99 | character: message.parseError.source.location.column,
100 | column: message.parseError.source.location.column - 1,
101 | text: this.removeLineNumber(message.parseError.message),
102 | type: 'warning'
103 | }))
104 |
105 | this.onParse && this.onParse(messages)
106 | }
107 |
108 | private getContentToLint(gherkin) {
109 | let featurePrefix = ''
110 |
111 | this.offset = 0
112 |
113 | if (this.language !== 'en') {
114 | this.offset += 1
115 | featurePrefix = `# language: ${this.language}\n`
116 | }
117 |
118 | if (this.isSubset) {
119 | const subsetKeyword = gherkinLanguages[this.language][this.subsetType][0]
120 |
121 | featurePrefix = `${featurePrefix}${this.featureKeyword}:\n${subsetKeyword}:\n`
122 | this.offset += 2
123 | }
124 |
125 | return `${featurePrefix}${gherkin}`
126 | }
127 |
128 | private removeLineNumber(errorMessage) {
129 | return errorMessage.split(' ').slice(1).join(' ')
130 | }
131 | }
132 |
--------------------------------------------------------------------------------
/src/modules/dialects/gherkin_background_i18n.js:
--------------------------------------------------------------------------------
1 | import gherkinLanguages from '../../lib/gherkin-languages'
2 |
3 | let GherkinDialect = {}
4 |
5 | export const setGherkinDialect = language => {
6 | const dialect = gherkinLanguages[language]
7 | const trimWhiteSpace = string => string.trim()
8 | GherkinDialect = {
9 | name: dialect.name,
10 | native_name: dialect.native,
11 | labels: [],
12 | keywords: [
13 | ...new Set([
14 | ...dialect.given.map(trimWhiteSpace),
15 | ...dialect.when.map(trimWhiteSpace),
16 | ...dialect.then.map(trimWhiteSpace),
17 | ...dialect.and.map(trimWhiteSpace),
18 | ...dialect.but.map(trimWhiteSpace)
19 | ])
20 | ]
21 | }
22 | }
23 |
24 | setGherkinDialect('en')
25 |
26 | export const getGherkinDialect = () => GherkinDialect
27 |
--------------------------------------------------------------------------------
/src/modules/dialects/gherkin_i18n.js:
--------------------------------------------------------------------------------
1 | import gherkinLanguages from '../../lib/gherkin-languages'
2 |
3 | let GherkinDialect = {}
4 |
5 | export const setGherkinDialect = language => {
6 | const dialect = gherkinLanguages[language]
7 | const trimWhiteSpace = string => string.trim()
8 | GherkinDialect = {
9 | name: dialect.name,
10 | native_name: dialect.native,
11 | labels: [
12 | ...new Set([
13 | ...dialect.feature.map(trimWhiteSpace),
14 | ...dialect.background.map(trimWhiteSpace),
15 | ...dialect.scenario.map(trimWhiteSpace),
16 | ...dialect.scenarioOutline.map(trimWhiteSpace),
17 | ...dialect.examples.map(trimWhiteSpace)
18 | ])
19 | ],
20 | keywords: [
21 | ...new Set([
22 | ...dialect.given.map(trimWhiteSpace),
23 | ...dialect.when.map(trimWhiteSpace),
24 | ...dialect.then.map(trimWhiteSpace),
25 | ...dialect.and.map(trimWhiteSpace),
26 | ...dialect.but.map(trimWhiteSpace)
27 | ])
28 | ]
29 | }
30 | }
31 |
32 | setGherkinDialect('en')
33 |
34 | export const getGherkinDialect = () => GherkinDialect
35 |
--------------------------------------------------------------------------------
/src/modules/dialects/gherkin_scenario_i18n.js:
--------------------------------------------------------------------------------
1 | import gherkinLanguages from '../../lib/gherkin-languages'
2 |
3 | let GherkinDialect = {}
4 |
5 | export const setGherkinDialect = language => {
6 | const dialect = gherkinLanguages[language]
7 | const trimWhiteSpace = string => string.trim()
8 | GherkinDialect = {
9 | name: dialect.name,
10 | native_name: dialect.native,
11 | labels: [...new Set([...dialect.examples.map(trimWhiteSpace)])],
12 | keywords: [
13 | ...new Set([
14 | ...dialect.given.map(trimWhiteSpace),
15 | ...dialect.when.map(trimWhiteSpace),
16 | ...dialect.then.map(trimWhiteSpace),
17 | ...dialect.and.map(trimWhiteSpace),
18 | ...dialect.but.map(trimWhiteSpace)
19 | ])
20 | ]
21 | }
22 | }
23 |
24 | setGherkinDialect('en')
25 |
26 | export const getGherkinDialect = () => GherkinDialect
27 |
--------------------------------------------------------------------------------
/src/modules/gherkin-annotator/__mocks__/index.ts:
--------------------------------------------------------------------------------
1 | const setSession = jest.fn()
2 | const setLanguage = jest.fn()
3 | const setMode = jest.fn()
4 | const annotate = jest.fn()
5 |
6 | const GherkinAnnotator = jest.fn().mockImplementation(() => {
7 | return {
8 | setSession,
9 | setLanguage,
10 | setMode,
11 | annotate
12 | }
13 | })
14 |
15 | export default GherkinAnnotator
16 |
17 | export { setSession, setLanguage, setMode, annotate }
18 |
--------------------------------------------------------------------------------
/src/modules/gherkin-annotator/index.ts:
--------------------------------------------------------------------------------
1 | import debounce from 'lodash/debounce'
2 | import GherkinLinter, { OnParseCallback } from '../../lib/gherkin-linter'
3 | import { LanguageIdentifier } from '../../lib/gherkin-languages'
4 |
5 | export default class GherkinAnnotator {
6 | private linter:GherkinLinter
7 | public language: LanguageIdentifier = 'en'
8 | public mode: '' | 'scenario' | 'background' = ''
9 |
10 | constructor(public session, private onParse?: OnParseCallback) {
11 | this.linter = new GherkinLinter(onParse)
12 | }
13 |
14 | setSession(session) {
15 | this.session = session
16 | }
17 |
18 | setLanguage(language) {
19 | this.language = language
20 | }
21 |
22 | setMode(mode: 'gherkin_background_i18n' | 'gherkin_scenario_i18n' | '') {
23 | switch (mode) {
24 | case 'gherkin_background_i18n':
25 | this.mode = 'background'
26 | break
27 | case 'gherkin_scenario_i18n':
28 | this.mode = 'scenario'
29 | break
30 | default:
31 | this.mode = ''
32 | }
33 | }
34 |
35 | annotate(value) {
36 | this.debouncedAnnotate(value)
37 | }
38 |
39 | debouncedAnnotate = debounce(value => {
40 | this.annotateNow(value)
41 | }, 250)
42 |
43 | async annotateNow(value) {
44 | const errors = await this.lint(value)
45 |
46 | if (!Array.isArray(errors)) {
47 | return
48 | }
49 |
50 | if (errors.length > 0) {
51 | this.session.setAnnotations(errors)
52 | } else {
53 | this.session.clearAnnotations()
54 | }
55 | }
56 |
57 | async lint(value) {
58 | return this.linter
59 | .setLanguage(this.language)
60 | .setSubsetType(this.mode)
61 | .parse(value)
62 | .getLintingErrors()
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/modules/keyword-completer/index.js:
--------------------------------------------------------------------------------
1 | class KeywordCompleter {
2 | constructor(getGherkinDialect) {
3 | this.getGherkinDialect = getGherkinDialect
4 | }
5 |
6 | getCompletions = async (_editor, session, position, _prefix, callback) => {
7 | const lineTokens = session.getLine(position.row).trim().split(' ')
8 |
9 | if (lineTokens.length === 1) {
10 | const keywords = [
11 | ...this.getGherkinDialect().labels,
12 | ...this.getGherkinDialect().keywords
13 | ]
14 | const completions = keywords.map((keyword, index) => ({
15 | caption: keyword,
16 | value: keyword,
17 | score: index,
18 | meta: 'Keyword'
19 | }))
20 |
21 | callback(null, completions)
22 | }
23 | }
24 | }
25 |
26 | export default KeywordCompleter
27 |
--------------------------------------------------------------------------------
/src/modules/mode/gherkin_background_i18n.js:
--------------------------------------------------------------------------------
1 | /* istanbul ignore file */
2 | import { getGherkinDialect } from '../dialects/gherkin_background_i18n'
3 | import escapeStringRegexp from 'escape-string-regexp'
4 | import { define } from 'ace-builds'
5 |
6 | define('ace/mode/gherkin_background_highlight_rules', [
7 | 'require',
8 | 'exports',
9 | 'module',
10 | 'ace/lib/oop',
11 | 'ace/mode/text_highlight_rules'
12 | ], function (acequire, exports, _module) {
13 | const oop = acequire('../lib/oop')
14 | const TextHighlightRules = acequire('./text_highlight_rules')
15 | .TextHighlightRules
16 | const stringEscape =
17 | '\\\\(x[0-9A-Fa-f]{2}|[0-7]{3}|[\\\\abfnrtv\'"]|U[0-9A-Fa-f]{8}|u[0-9A-Fa-f]{4})'
18 |
19 | const GherkinHighlightRules = function () {
20 | const keywords = getGherkinDialect().keywords
21 |
22 | this.$rules = {
23 | start: [
24 | {
25 | token: 'constant.numeric',
26 | regex: '(?:(?:[1-9]\\d*)|(?:0))'
27 | },
28 | {
29 | token: 'comment',
30 | regex: '#.*$'
31 | },
32 | {
33 | token: 'keyword',
34 | regex: '(?:' + keywords.map(escapeStringRegexp).join('|') + ')\\b'
35 | },
36 | {
37 | token: 'string', // multi line """ string start
38 | regex: '"{3}',
39 | next: 'qqstring3'
40 | },
41 | {
42 | token: 'string', // " string
43 | regex: '"',
44 | next: 'qqstring'
45 | },
46 | {
47 | token: 'text',
48 | regex: '^\\s*(?=@[\\w])',
49 | next: [
50 | {
51 | token: 'text',
52 | regex: '\\s+'
53 | },
54 | {
55 | token: 'variable.parameter',
56 | regex: '@[\\w]+'
57 | },
58 | {
59 | token: 'empty',
60 | regex: '',
61 | next: 'start'
62 | }
63 | ]
64 | },
65 | {
66 | token: 'argument',
67 | regex: '<[^>]+>'
68 | },
69 | {
70 | token: 'comment',
71 | regex: '\\|(?=.)',
72 | next: 'table-item'
73 | },
74 | {
75 | token: 'comment',
76 | regex: '\\|$',
77 | next: 'start'
78 | }
79 | ],
80 | qqstring3: [
81 | {
82 | token: 'constant.language.escape',
83 | regex: stringEscape
84 | },
85 | {
86 | token: 'string', // multi line """ string end
87 | regex: '"{3}',
88 | next: 'start'
89 | },
90 | {
91 | defaultToken: 'string'
92 | }
93 | ],
94 | qqstring: [
95 | {
96 | token: 'constant.language.escape',
97 | regex: stringEscape
98 | },
99 | {
100 | token: 'string',
101 | regex: '\\\\$',
102 | next: 'qqstring'
103 | },
104 | {
105 | token: 'string',
106 | regex: '"|$',
107 | next: 'start'
108 | },
109 | {
110 | defaultToken: 'string'
111 | }
112 | ],
113 | 'table-item': [
114 | {
115 | token: 'comment',
116 | regex: /$/,
117 | next: 'start'
118 | },
119 | {
120 | token: 'comment',
121 | regex: /\|/
122 | },
123 | {
124 | token: 'string',
125 | regex: /\\./
126 | },
127 | {
128 | defaultToken: 'string'
129 | }
130 | ]
131 | }
132 | this.normalizeRules()
133 | }
134 |
135 | oop.inherits(GherkinHighlightRules, TextHighlightRules)
136 |
137 | exports.GherkinHighlightRules = GherkinHighlightRules
138 | })
139 |
140 | define('ace/mode/gherkin_background_i18n', [
141 | 'require',
142 | 'exports',
143 | 'module',
144 | 'ace/lib/oop',
145 | 'ace/mode/text',
146 | 'ace/mode/gherkin_background_highlight_rules'
147 | ], function (acequire, exports, _module) {
148 | const oop = acequire('../lib/oop')
149 | const TextMode = acequire('./text').Mode
150 | const GherkinHighlightRules = acequire('./gherkin_background_highlight_rules')
151 | .GherkinHighlightRules
152 |
153 | const Mode = function () {
154 | this.HighlightRules = GherkinHighlightRules
155 | this.$behaviour = this.$defaultBehaviour
156 | }
157 | oop.inherits(Mode, TextMode)
158 | ;(function () {
159 | this.lineCommentStart = '#'
160 | this.$id = 'ace/mode/gherkin_background_i18n'
161 |
162 | this.getNextLineIndent = function (state, line, _tab) {
163 | const labels = getGherkinDialect().labels
164 | const keywords = getGherkinDialect().keywords
165 |
166 | let indent = this.$getIndent(line)
167 | const space2 = ' '
168 |
169 | const tokenizedLine = this.getTokenizer().getLineTokens(line, state)
170 | const tokens = tokenizedLine.tokens
171 |
172 | if (line.match('[ ]*\\|')) {
173 | indent += '| '
174 | }
175 |
176 | if (tokens.length && tokens[tokens.length - 1].type === 'comment') {
177 | return indent
178 | }
179 |
180 | if (state === 'start') {
181 | if (line.match(labels.map(escapeStringRegexp).join(':|') + ':')) {
182 | indent += space2
183 | } else if (
184 | line.match(
185 | '(' +
186 | keywords.map(escapeStringRegexp).join('|') +
187 | ').+(:)$|Examples:'
188 | )
189 | ) {
190 | indent += space2
191 | } else if (line.match('\\*.+')) {
192 | indent += '* '
193 | }
194 | }
195 |
196 | return indent
197 | }
198 | }.call(Mode.prototype))
199 |
200 | exports.Mode = Mode
201 | })
202 |
--------------------------------------------------------------------------------
/src/modules/mode/gherkin_i18n.js:
--------------------------------------------------------------------------------
1 | /* istanbul ignore file */
2 | import { getGherkinDialect } from '../dialects/gherkin_i18n'
3 | import escapeStringRegexp from 'escape-string-regexp'
4 | import { define } from 'ace-builds'
5 |
6 | define('ace/mode/gherkin_highlight_rules', [
7 | 'require',
8 | 'exports',
9 | 'module',
10 | 'ace/lib/oop',
11 | 'ace/mode/text_highlight_rules'
12 | ], function (acequire, exports, module) {
13 | const oop = acequire('../lib/oop')
14 | const TextHighlightRules = acequire('./text_highlight_rules')
15 | .TextHighlightRules
16 | const stringEscape =
17 | '\\\\(x[0-9A-Fa-f]{2}|[0-7]{3}|[\\\\abfnrtv\'"]|U[0-9A-Fa-f]{8}|u[0-9A-Fa-f]{4})'
18 |
19 | const GherkinHighlightRules = function () {
20 | const labels = getGherkinDialect().labels
21 | const keywords = getGherkinDialect().keywords
22 |
23 | this.$rules = {
24 | start: [
25 | {
26 | token: 'constant.numeric',
27 | regex: '(?:(?:[1-9]\\d*)|(?:0))'
28 | },
29 | {
30 | token: 'comment',
31 | regex: '#.*$'
32 | },
33 | {
34 | token: 'keyword',
35 | regex:
36 | '(?:' +
37 | labels.map(escapeStringRegexp).join('|') +
38 | '):|(?:' +
39 | keywords.map(escapeStringRegexp).join('|') +
40 | ')\\b'
41 | },
42 | {
43 | token: 'string', // multi line """ string start
44 | regex: '"{3}',
45 | next: 'qqstring3'
46 | },
47 | {
48 | token: 'string', // " string
49 | regex: '"',
50 | next: 'qqstring'
51 | },
52 | {
53 | token: 'text',
54 | regex: '^\\s*(?=@[\\w])',
55 | next: [
56 | {
57 | token: 'text',
58 | regex: '\\s+'
59 | },
60 | {
61 | token: 'variable.parameter',
62 | regex: '@[\\w]+'
63 | },
64 | {
65 | token: 'empty',
66 | regex: '',
67 | next: 'start'
68 | }
69 | ]
70 | },
71 | {
72 | token: 'argument',
73 | regex: '<[^>]+>'
74 | },
75 | {
76 | token: 'comment',
77 | regex: '\\|(?=.)',
78 | next: 'table-item'
79 | },
80 | {
81 | token: 'comment',
82 | regex: '\\|$',
83 | next: 'start'
84 | }
85 | ],
86 | qqstring3: [
87 | {
88 | token: 'constant.language.escape',
89 | regex: stringEscape
90 | },
91 | {
92 | token: 'string', // multi line """ string end
93 | regex: '"{3}',
94 | next: 'start'
95 | },
96 | {
97 | defaultToken: 'string'
98 | }
99 | ],
100 | qqstring: [
101 | {
102 | token: 'constant.language.escape',
103 | regex: stringEscape
104 | },
105 | {
106 | token: 'string',
107 | regex: '\\\\$',
108 | next: 'qqstring'
109 | },
110 | {
111 | token: 'string',
112 | regex: '"|$',
113 | next: 'start'
114 | },
115 | {
116 | defaultToken: 'string'
117 | }
118 | ],
119 | 'table-item': [
120 | {
121 | token: 'comment',
122 | regex: /$/,
123 | next: 'start'
124 | },
125 | {
126 | token: 'comment',
127 | regex: /\|/
128 | },
129 | {
130 | token: 'string',
131 | regex: /\\./
132 | },
133 | {
134 | defaultToken: 'string'
135 | }
136 | ]
137 | }
138 | this.normalizeRules()
139 | }
140 |
141 | oop.inherits(GherkinHighlightRules, TextHighlightRules)
142 |
143 | exports.GherkinHighlightRules = GherkinHighlightRules
144 | })
145 |
146 | define('ace/mode/gherkin_i18n', [
147 | 'require',
148 | 'exports',
149 | 'module',
150 | 'ace/lib/oop',
151 | 'ace/mode/text',
152 | 'ace/mode/gherkin_highlight_rules'
153 | ], function (acequire, exports, module) {
154 | const oop = acequire('../lib/oop')
155 | const TextMode = acequire('./text').Mode
156 | const GherkinHighlightRules = acequire('./gherkin_highlight_rules')
157 | .GherkinHighlightRules
158 |
159 | const Mode = function () {
160 | this.HighlightRules = GherkinHighlightRules
161 | this.$behaviour = this.$defaultBehaviour
162 | }
163 | oop.inherits(Mode, TextMode)
164 | ;(function () {
165 | this.lineCommentStart = '#'
166 | this.$id = 'ace/mode/gherkin_i18n'
167 |
168 | this.getNextLineIndent = function (state, line, tab) {
169 | const labels = getGherkinDialect().labels
170 | const keywords = getGherkinDialect().keywords
171 |
172 | let indent = this.$getIndent(line)
173 | const space2 = ' '
174 |
175 | const tokenizedLine = this.getTokenizer().getLineTokens(line, state)
176 | const tokens = tokenizedLine.tokens
177 |
178 | if (line.match('[ ]*\\|')) {
179 | indent += '| '
180 | }
181 |
182 | if (tokens.length && tokens[tokens.length - 1].type === 'comment') {
183 | return indent
184 | }
185 |
186 | if (state === 'start') {
187 | if (line.match(labels.map(escapeStringRegexp).join(':|') + ':')) {
188 | indent += space2
189 | } else if (
190 | line.match(
191 | '(' +
192 | keywords.map(escapeStringRegexp).join('|') +
193 | ').+(:)$|Examples:'
194 | )
195 | ) {
196 | indent += space2
197 | } else if (line.match('\\*.+')) {
198 | indent += '* '
199 | }
200 | }
201 |
202 | return indent
203 | }
204 | }.call(Mode.prototype))
205 |
206 | exports.Mode = Mode
207 | })
208 |
--------------------------------------------------------------------------------
/src/modules/mode/gherkin_scenario_i18n.js:
--------------------------------------------------------------------------------
1 | /* istanbul ignore file */
2 | import { getGherkinDialect } from '../dialects/gherkin_scenario_i18n'
3 | import escapeStringRegexp from 'escape-string-regexp'
4 | import { define } from 'ace-builds'
5 |
6 | define('ace/mode/gherkin_scenario_highlight_rules', [
7 | 'require',
8 | 'exports',
9 | 'module',
10 | 'ace/lib/oop',
11 | 'ace/mode/text_highlight_rules'
12 | ], function (acequire, exports, module) {
13 | const oop = acequire('../lib/oop')
14 | const TextHighlightRules = acequire('./text_highlight_rules')
15 | .TextHighlightRules
16 | const stringEscape =
17 | '\\\\(x[0-9A-Fa-f]{2}|[0-7]{3}|[\\\\abfnrtv\'"]|U[0-9A-Fa-f]{8}|u[0-9A-Fa-f]{4})'
18 |
19 | const GherkinHighlightRules = function () {
20 | const labels = getGherkinDialect().labels
21 | const keywords = getGherkinDialect().keywords
22 |
23 | this.$rules = {
24 | start: [
25 | {
26 | token: 'constant.numeric',
27 | regex: '(?:(?:[1-9]\\d*)|(?:0))'
28 | },
29 | {
30 | token: 'comment',
31 | regex: '#.*$'
32 | },
33 | {
34 | token: 'keyword',
35 | regex:
36 | '(?:' +
37 | labels.map(escapeStringRegexp).join('|') +
38 | '):|(?:' +
39 | keywords.map(escapeStringRegexp).join('|') +
40 | ')\\b'
41 | },
42 | {
43 | token: 'string', // multi line """ string start
44 | regex: '"{3}',
45 | next: 'qqstring3'
46 | },
47 | {
48 | token: 'string', // " string
49 | regex: '"',
50 | next: 'qqstring'
51 | },
52 | {
53 | token: 'text',
54 | regex: '^\\s*(?=@[\\w])',
55 | next: [
56 | {
57 | token: 'text',
58 | regex: '\\s+'
59 | },
60 | {
61 | token: 'variable.parameter',
62 | regex: '@[\\w]+'
63 | },
64 | {
65 | token: 'empty',
66 | regex: '',
67 | next: 'start'
68 | }
69 | ]
70 | },
71 | {
72 | token: 'argument',
73 | regex: '<[^>]+>'
74 | },
75 | {
76 | token: 'comment',
77 | regex: '\\|(?=.)',
78 | next: 'table-item'
79 | },
80 | {
81 | token: 'comment',
82 | regex: '\\|$',
83 | next: 'start'
84 | }
85 | ],
86 | qqstring3: [
87 | {
88 | token: 'constant.language.escape',
89 | regex: stringEscape
90 | },
91 | {
92 | token: 'string', // multi line """ string end
93 | regex: '"{3}',
94 | next: 'start'
95 | },
96 | {
97 | defaultToken: 'string'
98 | }
99 | ],
100 | qqstring: [
101 | {
102 | token: 'constant.language.escape',
103 | regex: stringEscape
104 | },
105 | {
106 | token: 'string',
107 | regex: '\\\\$',
108 | next: 'qqstring'
109 | },
110 | {
111 | token: 'string',
112 | regex: '"|$',
113 | next: 'start'
114 | },
115 | {
116 | defaultToken: 'string'
117 | }
118 | ],
119 | 'table-item': [
120 | {
121 | token: 'comment',
122 | regex: /$/,
123 | next: 'start'
124 | },
125 | {
126 | token: 'comment',
127 | regex: /\|/
128 | },
129 | {
130 | token: 'string',
131 | regex: /\\./
132 | },
133 | {
134 | defaultToken: 'string'
135 | }
136 | ]
137 | }
138 | this.normalizeRules()
139 | }
140 |
141 | oop.inherits(GherkinHighlightRules, TextHighlightRules)
142 |
143 | exports.GherkinHighlightRules = GherkinHighlightRules
144 | })
145 |
146 | define('ace/mode/gherkin_scenario_i18n', [
147 | 'require',
148 | 'exports',
149 | 'module',
150 | 'ace/lib/oop',
151 | 'ace/mode/text',
152 | 'ace/mode/gherkin_scenario_highlight_rules'
153 | ], function (acequire, exports, module) {
154 | const oop = acequire('../lib/oop')
155 | const TextMode = acequire('./text').Mode
156 | const GherkinHighlightRules = acequire('./gherkin_scenario_highlight_rules')
157 | .GherkinHighlightRules
158 |
159 | const Mode = function () {
160 | this.HighlightRules = GherkinHighlightRules
161 | this.$behaviour = this.$defaultBehaviour
162 | }
163 | oop.inherits(Mode, TextMode)
164 | ;(function () {
165 | this.lineCommentStart = '#'
166 | this.$id = 'ace/mode/gherkin_scenario_i18n'
167 |
168 | this.getNextLineIndent = function (state, line, tab) {
169 | const labels = getGherkinDialect().labels
170 | const keywords = getGherkinDialect().keywords
171 |
172 | let indent = this.$getIndent(line)
173 | const space2 = ' '
174 |
175 | const tokenizedLine = this.getTokenizer().getLineTokens(line, state)
176 | const tokens = tokenizedLine.tokens
177 |
178 | if (line.match('[ ]*\\|')) {
179 | indent += '| '
180 | }
181 |
182 | if (tokens.length && tokens[tokens.length - 1].type === 'comment') {
183 | return indent
184 | }
185 |
186 | if (state === 'start') {
187 | if (line.match(labels.map(escapeStringRegexp).join(':|') + ':')) {
188 | indent += space2
189 | } else if (
190 | line.match(
191 | '(' +
192 | keywords.map(escapeStringRegexp).join('|') +
193 | ').+(:)$|Examples:'
194 | )
195 | ) {
196 | indent += space2
197 | } else if (line.match('\\*.+')) {
198 | indent += '* '
199 | }
200 | }
201 |
202 | return indent
203 | }
204 | }.call(Mode.prototype))
205 |
206 | exports.Mode = Mode
207 | })
208 |
--------------------------------------------------------------------------------
/src/modules/step-completer/index.js:
--------------------------------------------------------------------------------
1 | import _isEmpty from 'lodash/isEmpty'
2 | import _map from 'lodash/map'
3 | import _orderBy from 'lodash/orderBy'
4 | import calculateSize from 'calculate-size'
5 |
6 | class StepCompleter {
7 | constructor(autoCompleteFunction, getGherkinDialect) {
8 | this.autoCompleteFunction = autoCompleteFunction
9 | this.getGherkinDialect = getGherkinDialect
10 | }
11 |
12 | getCompletions = async (editor, session, position, _prefix, callback) => {
13 | const lineTokens = session.getLine(position.row).trim().split(' ')
14 |
15 | if (
16 | lineTokens.length > 1 &&
17 | this.getGherkinDialect().keywords.includes(lineTokens[0])
18 | ) {
19 | const keyword = lineTokens.shift()
20 | const text = lineTokens.join(' ')
21 | try {
22 | const completions = await this.autoCompleteFunction(keyword, text)
23 | callback(null, completions)
24 | this._resizePopup(editor, completions)
25 | } catch (error) {
26 | callback(null, [])
27 | throw error
28 | }
29 | }
30 | }
31 |
32 | _resizePopup = (editor, completions) => {
33 | if (_isEmpty(completions)) {
34 | return
35 | }
36 |
37 | const strings = _map(completions, 'caption')
38 | const longestString = _orderBy(strings, 'length', 'desc').shift()
39 | const width = this._calculateVisualLength(editor, longestString)
40 |
41 | editor.completer.popup.container.style.width = `${width + 50}px`
42 | }
43 |
44 | _calculateVisualLength = (editor, string) => {
45 | const { fontFamily, fontSize } = editor.getOptions()
46 | const { width } = calculateSize(string, {
47 | font: fontFamily,
48 | fontSize
49 | })
50 |
51 | return width
52 | }
53 | }
54 |
55 | export default StepCompleter
56 |
--------------------------------------------------------------------------------
/src/themes/cucumber.js:
--------------------------------------------------------------------------------
1 | import { define } from 'ace-builds'
2 | /* istanbul ignore file */
3 | define('ace/theme/cucumber', [
4 | 'require',
5 | 'exports',
6 | 'module',
7 | 'ace/lib/dom'
8 | ], function (acequire, exports) {
9 | exports.isDark = false
10 | exports.cssClass = 'ace-cucumber'
11 | exports.cssText = `
12 | .ace-cucumber .ace_gutter {
13 | background: #f6f6f6;
14 | color: #4D4D4C;
15 | }
16 | .ace-cucumber .ace_print-margin {
17 | width: 1px;
18 | background: #f6f6f6;
19 | }
20 | .ace-cucumber {
21 | background-color: #FFFFFF;
22 | color: #4D4D4C;
23 | }
24 | .ace-cucumber .ace_cursor {
25 | color: #AEAFAD;
26 | }
27 | .ace-cucumber .ace_marker-layer .ace_selection {
28 | background: #D6D6D6;
29 | }
30 | .ace-cucumber.ace_multiselect .ace_selection.ace_start {
31 | box-shadow: 0 0 3px 0px #FFFFFF;
32 | }
33 | .ace-cucumber .ace_marker-layer .ace_step {
34 | background: rgb(255, 255, 0);
35 | }
36 | .ace-cucumber .ace_marker-layer .ace_bracket {
37 | margin: -1px 0 0 -1px;
38 | border: 1px solid #D1D1D1;
39 | }
40 | .ace-cucumber .ace_marker-layer .ace_active-line {
41 | background: #EFEFEF;
42 | }
43 | .ace-cucumber .ace_gutter-active-line {
44 | background-color : #dcdcdc;
45 | }
46 | .ace-cucumber .ace_marker-layer .ace_selected-word {
47 | border: 1px solid #D6D6D6;
48 | }
49 | .ace-cucumber .ace_invisible {
50 | color: #D1D1D1;
51 | }
52 | .ace-cucumber .ace_keyword,
53 | .ace-cucumber .ace_meta,
54 | .ace-cucumber .ace_storage,
55 | .ace-cucumber .ace_storage.ace_type,
56 | .ace-cucumber .ace_support.ace_type {
57 | color: rgb(25, 144, 184);
58 | font-weight: bold;
59 | }
60 | .ace-cucumber .ace_keyword.ace_operator {
61 | color: #3E999F;
62 | }
63 | .ace-cucumber .ace_constant.ace_character,
64 | .ace-cucumber .ace_constant.ace_language,
65 | .ace-cucumber .ace_constant.ace_numeric,
66 | .ace-cucumber .ace_keyword.ace_other.ace_unit,
67 | .ace-cucumber .ace_support.ace_constant,
68 | .ace-cucumber .ace_variable.ace_parameter {
69 | color: #F5871F;
70 | }
71 | .ace-cucumber .ace_constant.ace_other {
72 | color: #666969;
73 | }
74 | .ace-cucumber .ace_invalid {
75 | color: #FFFFFF;
76 | background-color: #C82829;
77 | }
78 | .ace-cucumber .ace_invalid.ace_deprecated {
79 | color: #FFFFFF;
80 | background-color: #8959A8;
81 | }
82 | .ace-cucumber .ace_fold {
83 | background-color: #4271AE;
84 | border-color: #4D4D4C;
85 | }
86 | .ace-cucumber .ace_entity.ace_name.ace_function,
87 | .ace-cucumber .ace_support.ace_function,
88 | .ace-cucumber .ace_variable {
89 | color: #4271AE;
90 | }
91 | .ace-cucumber .ace_support.ace_class,
92 | .ace-cucumber .ace_support.ace_type {
93 | color: #C99E00;
94 | }
95 | .ace-cucumber .ace_heading,
96 | .ace-cucumber .ace_markup.ace_heading,
97 | .ace-cucumber .ace_string {
98 | color: rgb(47, 156, 10);
99 | }
100 | .ace-cucumber .ace_entity.ace_name.ace_tag,
101 | .ace-cucumber .ace_entity.ace_other.ace_attribute-name,
102 | .ace-cucumber .ace_meta.ace_tag,
103 | .ace-cucumber .ace_string.ace_regexp,
104 | .ace-cucumber .ace_variable {
105 | color: #C82829;
106 | }
107 |
108 | .ace-cucumber .ace_argument {
109 | color: rgb(166, 127, 89);
110 | }
111 | .ace-cucumber .ace_comment {
112 | color: rgb(125, 139, 153);
113 | }
114 | .ace-cucumber .ace_indent-guide {
115 | background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAE0lEQVQImWP4////f4bdu3f/BwAlfgctduB85QAAAABJRU5ErkJggg==) right repeat-y;
116 | }
117 | .ace-cucumber .ace_tooltip {
118 | max-width: 400px;
119 | white-space: pre-wrap;
120 | overflow-wrap: break-word;
121 | word-wrap: break-word;
122 | }`
123 |
124 | const dom = acequire('../lib/dom')
125 | dom.importCssString(exports.cssText, exports.cssClass)
126 | })
127 |
--------------------------------------------------------------------------------
/src/themes/jira.js:
--------------------------------------------------------------------------------
1 | import { define } from 'ace-builds'
2 |
3 | /* istanbul ignore file */
4 | define('ace/theme/jira', [
5 | 'require',
6 | 'exports',
7 | 'module',
8 | 'ace/lib/dom'
9 | ], function (acequire, exports) {
10 | 'use strict'
11 |
12 | exports.isDark = false
13 | exports.cssClass = 'ace-jira'
14 | exports.cssText = `ace_editor {
15 | margin: auto;
16 | width: 100%;
17 | }
18 | .ace-jira .ace_gutter {
19 | background: rgb(235, 236, 240);
20 | color: rgb(137, 147, 164);
21 | }
22 |
23 | .ace-jira .ace_print-margin {
24 | width: 1px;
25 | background: #e8e8e8;
26 | }
27 |
28 | .ace-jira .ace_fold {
29 | background-color: #6B72E6;
30 | }
31 |
32 | .ace-jira {
33 | background: #FFFFFF;
34 | color: #172B4D;
35 | }
36 |
37 | .ace-jira .ace_cursor {
38 | color: black;
39 | }
40 |
41 | .ace-jira .ace_invisible {
42 | color: rgb(191, 191, 191);
43 | }
44 |
45 | .ace-jira .ace_storage, .ace-jira .ace_keyword {
46 | font-weight: bolder;
47 | }
48 |
49 | .ace-jira .ace_constant {
50 | color: rgb(197, 6, 11);
51 | }
52 |
53 | .ace-jira .ace_constant.ace_buildin {
54 | color: rgb(88, 72, 246);
55 | }
56 |
57 | .ace-jira .ace_constant.ace_language {
58 | color: rgb(88, 92, 246);
59 | }
60 |
61 | .ace-jira .ace_constant.ace_library {
62 | color: rgb(6, 150, 14);
63 | }
64 |
65 | .ace-jira .ace_invalid {
66 | background-color: rgba(255, 0, 0, 0.1);
67 | color: red;
68 | }
69 |
70 | .ace-jira .ace_support.ace_function {
71 | color: rgb(60, 76, 114);
72 | }
73 |
74 | .ace-jira .ace_support.ace_constant {
75 | color: rgb(6, 150, 14);
76 | }
77 |
78 | .ace-jira .ace_support.ace_type,
79 | .ace-jira .ace_support.ace_class {
80 | color: rgb(109, 121, 222);
81 | }
82 |
83 | .ace-jira .ace_keyword.ace_operator {
84 | color: rgb(104, 118, 135);
85 | }
86 |
87 | .ace-jira .ace_string {
88 | color: #36B37E;
89 | }
90 |
91 | .ace-jira .ace_comment {
92 | color: rgb(80, 95, 121);
93 | }
94 |
95 | .ace-jira .ace_argument {
96 | color: rgb(101, 84, 192);
97 | }
98 |
99 | .ace-jira .ace_comment.ace_doc {
100 | color: rgb(0, 102, 255);
101 | }
102 |
103 | .ace-jira .ace_comment.ace_doc.ace_tag {
104 | color: rgb(128, 159, 191);
105 | }
106 |
107 | .ace-jira .ace_constant.ace_numeric {
108 | color: rgb(0, 0, 205);
109 | }
110 |
111 | .ace-jira .ace_variable {
112 | color: rgb(49, 132, 149);
113 | }
114 |
115 | .ace-jira .ace_xml-pe {
116 | color: rgb(104, 104, 91);
117 | }
118 |
119 | .ace-jira .ace_entity.ace_name.ace_function {
120 | color: #0000A2;
121 | }
122 |
123 | .ace-jira .ace_heading {
124 | color: rgb(12, 7, 255);
125 | }
126 |
127 | .ace-jira .ace_list {
128 | color: rgb(185, 6, 144);
129 | }
130 |
131 | .ace-jira .ace_meta.ace_tag {
132 | color: rgb(0, 22, 142);
133 | }
134 |
135 | .ace-jira .ace_string.ace_regex {
136 | color: rgb(255, 0, 0);
137 | }
138 |
139 | .ace-jira .ace_marker-layer .ace_selection {
140 | background: rgb(181, 213, 255);
141 | }
142 |
143 | .ace-jira.ace_multiselect .ace_selection.ace_start {
144 | box-shadow: 0 0 3px 0px white;
145 | }
146 |
147 | .ace-jira .ace_marker-layer .ace_step {
148 | background: rgb(252, 255, 0);
149 | }
150 |
151 | .ace-jira .ace_marker-layer .ace_stack {
152 | background: rgb(164, 229, 101);
153 | }
154 |
155 | .ace-jira .ace_marker-layer .ace_bracket {
156 | margin: -1px 0 0 -1px;
157 | border: 1px solid rgb(192, 192, 192);
158 | }
159 |
160 | .ace-jira .ace_marker-layer .ace_active-line {
161 | background: rgba(0, 0, 0, 0.07);
162 | }
163 |
164 | .ace-jira .ace_gutter-active-line {
165 | background-color: #dcdcdc;
166 | }
167 |
168 | .ace-jira .ace_marker-layer .ace_selected-word {
169 | background: rgb(250, 250, 255);
170 | border: 1px solid rgb(200, 200, 250);
171 | }
172 |
173 | .ace-jira .ace_indent-guide {
174 | background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAE0lEQVQImWP4////f4bLly//BwAmVgd1/w11/gAAAABJRU5ErkJggg==") right repeat-y;
175 | }
176 |
177 | .ace-jira .ace_tooltip {
178 | max-width: 400px;
179 | white-space: pre-wrap;
180 | overflow-wrap: break-word;
181 | word-wrap: break-word;
182 | }`
183 |
184 | const dom = acequire('../lib/dom')
185 | dom.importCssString(exports.cssText, exports.cssClass)
186 | })
187 |
--------------------------------------------------------------------------------
/test/components/GherkinEditor/index.spec.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { render } from '@testing-library/react'
3 | import GherkinAnnotator, {
4 | annotate,
5 | setLanguage,
6 | setMode,
7 | setSession
8 | } from '../../../src/modules/gherkin-annotator'
9 | import GherkinEditor from '../../../src/components/GherkinEditor'
10 |
11 | jest.mock('../../../src/modules/gherkin-annotator')
12 |
13 | beforeEach(() => {
14 | GherkinAnnotator.mockClear()
15 | annotate.mockClear()
16 | setSession.mockClear()
17 | setLanguage.mockClear()
18 | setMode.mockClear()
19 | })
20 |
21 | describe('GherkinEditor', () => {
22 | it('renders a Gherkin editor', () => {
23 | const gherkinEditor = render()
24 |
25 | expect(
26 | gherkinEditor.container.querySelector('.ace_editor')
27 | ).toBeInTheDocument()
28 | })
29 |
30 | describe('when hideToolbar is false', () => {
31 | it('renders a toolbar with a language selector', () => {
32 | const gherkinEditor = render()
33 |
34 | const toolbar = gherkinEditor.queryByTestId('editor-toolbar')
35 |
36 | expect(toolbar).toBeInTheDocument()
37 | expect(toolbar).toHaveTextContent('English')
38 | })
39 | })
40 |
41 | describe('when hideToolbar is true', () => {
42 | it('does not render a toolbar', () => {
43 | const gherkinEditor = render()
44 |
45 | expect(
46 | gherkinEditor.queryByTestId('editor-toolbar')
47 | ).not.toBeInTheDocument()
48 | })
49 | })
50 |
51 | describe('when no setOptions value is provided', () => {
52 | it('sets options with default values', () => {
53 | const ref = React.createRef()
54 | render()
55 |
56 | const editor = ref.current.editor
57 |
58 | expect(editor.getOption('fontFamily')).toEqual(
59 | "'SFMono-Medium', 'SF Mono', 'Segoe UI Mono', 'Roboto Mono', 'Ubuntu Mono', Menlo, Consolas, Courier, monospace"
60 | )
61 | expect(editor.getOption('enableBasicAutocompletion')).toBe(true)
62 | expect(editor.getOption('enableLiveAutocompletion')).toBe(true)
63 | expect(editor.getOption('showLineNumbers')).toBe(false)
64 | expect(editor.getOption('displayIndentGuides')).toBe(false)
65 | expect(editor.getOption('tabSize')).toBe(2)
66 | })
67 | })
68 |
69 | describe('when a setOptions value is provided', () => {
70 | it('merges options with the default options', () => {
71 | const setOptions = {
72 | showLineNumbers: true,
73 | tabSize: 4,
74 | firstLineNumber: 5
75 | }
76 |
77 | const ref = React.createRef()
78 | render()
79 |
80 | const editor = ref.current.editor
81 |
82 | expect(editor.getOption('fontFamily')).toEqual(
83 | "'SFMono-Medium', 'SF Mono', 'Segoe UI Mono', 'Roboto Mono', 'Ubuntu Mono', Menlo, Consolas, Courier, monospace"
84 | )
85 | expect(editor.getOption('enableBasicAutocompletion')).toBe(true)
86 | expect(editor.getOption('enableLiveAutocompletion')).toBe(true)
87 | expect(editor.getOption('showLineNumbers')).toBe(true)
88 | expect(editor.getOption('displayIndentGuides')).toBe(false)
89 | expect(editor.getOption('tabSize')).toBe(4)
90 | expect(editor.getOption('firstLineNumber')).toBe(5)
91 | })
92 | })
93 |
94 | describe('when content is changed', () => {
95 | it('calls the onChange callback', () => {
96 | const onChange = jest.fn()
97 |
98 | const ref = React.createRef()
99 | render(
100 |
105 | )
106 |
107 | const editor = ref.current.editor
108 |
109 | editor.setValue('Then no scenario')
110 |
111 | expect(onChange).toHaveBeenCalledWith(
112 | 'Then no scenario',
113 | expect.anything()
114 | )
115 | })
116 | })
117 |
118 | describe('linting', () => {
119 | describe('when linter is activated', () => {
120 | it('lints the initial value', () => {
121 | render(
122 |
127 | )
128 |
129 | expect(annotate).toHaveBeenCalledWith('Given a scenario')
130 | })
131 |
132 | it('lints with new value when it has changed', () => {
133 | const ref = React.createRef()
134 |
135 | render(
136 |
142 | )
143 |
144 | const editor = ref.current.editor
145 |
146 | editor.setValue('Then no scenario')
147 |
148 | expect(annotate).toHaveBeenCalledWith('Then no scenario')
149 | })
150 |
151 | it('lints after language has changed', () => {
152 | const { rerender } = render(
153 |
159 | )
160 |
161 | annotate.mockClear()
162 | setLanguage.mockClear()
163 |
164 | rerender(
165 |
171 | )
172 |
173 | expect(setLanguage).toHaveBeenCalledWith('fr')
174 | expect(annotate).toHaveBeenCalled()
175 | })
176 |
177 | it('lints after mode has changed', () => {
178 | const { rerender } = render(
179 |
184 | )
185 |
186 | annotate.mockClear()
187 | setMode.mockClear()
188 |
189 | rerender(
190 |
196 | )
197 |
198 | expect(setMode).toHaveBeenCalledWith('gherkin_scenario_i18n')
199 | expect(annotate).toHaveBeenCalled()
200 | })
201 |
202 | it('delegates onParse to the annotator', () => {
203 | const onParse = jest.fn()
204 | render(
205 |
210 | )
211 |
212 | render(
213 |
219 | )
220 | expect(GherkinAnnotator).toHaveBeenCalledTimes(1)
221 | expect(GherkinAnnotator).toHaveBeenCalledWith(expect.anything(), onParse)
222 | })
223 | })
224 |
225 | describe('when linter status changes', () => {
226 | it('resets the annotator session', () => {
227 | const { rerender } = render()
228 |
229 | rerender()
230 | rerender()
231 |
232 | expect(GherkinAnnotator).toHaveBeenCalledTimes(1)
233 | expect(setSession).toHaveBeenCalledTimes(1)
234 | })
235 | })
236 |
237 | describe('when linter is deactivated', () => {
238 | it('does not instanciate linter', () => {
239 | render(
240 |
245 | )
246 |
247 | expect(GherkinAnnotator).not.toHaveBeenCalled()
248 | })
249 |
250 | it('does not lint when value has changed', () => {
251 | const ref = React.createRef()
252 |
253 | render(
254 |
260 | )
261 |
262 | const editor = ref.current.editor
263 |
264 | editor.setValue('Then no scenario')
265 |
266 | expect(GherkinAnnotator).not.toHaveBeenCalled()
267 | })
268 | })
269 |
270 | describe('when gutter is deactivated', () => {
271 | it('deactivates linting and warns the users', () => {
272 | const { warn } = console
273 |
274 | console.warn = jest.fn()
275 |
276 | render(
277 |
282 | )
283 |
284 | expect(GherkinAnnotator).not.toHaveBeenCalled()
285 | expect(console.warn).toHaveBeenCalledWith(
286 | 'activateLinter requires showGutter to be true'
287 | )
288 |
289 | console.warn = warn
290 | })
291 | })
292 | })
293 | })
294 |
--------------------------------------------------------------------------------
/test/components/Toolbar/index.spec.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { render } from '@testing-library/react'
3 | import SelectEvent from 'react-select-event'
4 | import Toolbar from '../../../src/components/Toolbar'
5 |
6 | describe('Toolbar', () => {
7 | describe('when no language is set', () => {
8 | it('renders a language selector with English as the default language', () => {
9 | const toolbar = render()
10 |
11 | expect(
12 | toolbar.container.querySelector(
13 | '.gherkin-editor-language-select__single-value'
14 | )
15 | ).toHaveTextContent('English')
16 | })
17 | })
18 |
19 | describe('when a language is set', () => {
20 | it('renders a language selector with the default language', () => {
21 | const toolbar = render()
22 |
23 | expect(
24 | toolbar.container.querySelector(
25 | '.gherkin-editor-language-select__single-value'
26 | )
27 | ).toHaveTextContent('français')
28 | })
29 | })
30 |
31 | describe('when the toolbar is not read only', () => {
32 | it('renders the language selector as enabled', () => {
33 | const toolbar = render()
34 |
35 | expect(toolbar.container.querySelector('.gherkin-editor-language-select__input')).toBeEnabled()
36 | })
37 |
38 | it('calls the onLanguageChange callback when changing the language', async () => {
39 | const onLanguageChange = jest.fn()
40 |
41 | const toolbar = render()
42 | const input = toolbar.container.querySelector('.gherkin-editor-language-select__input') as HTMLElement
43 |
44 | SelectEvent.openMenu(input)
45 | await SelectEvent.select(input, 'français')
46 |
47 | expect(onLanguageChange).toHaveBeenCalled()
48 | })
49 | })
50 |
51 | describe('when the toolbar is read only', () => {
52 | it('renders the language selector as disabled', () => {
53 | const toolbar = render()
54 |
55 | expect(toolbar.container.querySelector('.gherkin-editor-language-select__input')).toBeDisabled()
56 | })
57 | })
58 | })
59 |
--------------------------------------------------------------------------------
/test/lib/gherkin-linter/index.spec.js:
--------------------------------------------------------------------------------
1 | import GherkinLinter from '../../../src/lib/gherkin-linter'
2 |
3 | const fullGherkinContent = `Feature:
4 | Background:
5 | Background description
6 |
7 | Given some step
8 | invalid gherkin here!
9 | Examples:
10 | `
11 |
12 | const fullGherkinContentInFrench = `Fonctionnalité:
13 | Contexte:
14 | Description du contexte
15 |
16 | Quand some step
17 | invalid gherkin here!
18 | Exemples:
19 | `
20 |
21 | const subsetBackgroundGherkinContent = `Background description
22 |
23 | Given some step
24 | invalid gherkin here!
25 | Examples:
26 | `
27 |
28 | const subsetScenarioGherkinContentInFrench = `Scénario description
29 |
30 | Quand some step
31 | invalid gherkin here!
32 | Exemples:
33 | `
34 |
35 | describe('GherkinLinter', () => {
36 | const gherkinLinter = new GherkinLinter()
37 |
38 | describe('.parse(gherkin)', () => {
39 | describe('with valid gherkin', () => {
40 | const gherkin = 'Feature:\nScenario:\n'
41 |
42 | it('allows method chaining', () => {
43 | expect(gherkinLinter.parse(gherkin)).toEqual(gherkinLinter)
44 | })
45 | })
46 |
47 | describe('with invalid gherkin', () => {
48 | const gherkin = '_Feature:\n_Scenario:\n'
49 |
50 | it('allows method chaining', () => {
51 | expect(gherkinLinter.parse(gherkin)).toEqual(gherkinLinter)
52 | })
53 |
54 | it('does not raise error', () => {
55 | expect(() => gherkinLinter.parse(gherkin)).not.toThrow()
56 | })
57 | })
58 |
59 | describe('with same gherkin twice', () => {
60 | const gherkin = 'Feature:\nScenario:\n'
61 |
62 | beforeEach(() => {
63 | gherkinLinter.parse(gherkin)
64 | })
65 |
66 | it('allows method chaining', () => {
67 | expect(gherkinLinter.parse(gherkin)).toEqual(gherkinLinter)
68 | })
69 |
70 | it('parses the gherkin only once', () => {
71 | const parseSpy = jest.spyOn(gherkinLinter, 'parseGherkin')
72 |
73 | gherkinLinter.parse(gherkin)
74 |
75 | expect(parseSpy).not.toHaveBeenCalled()
76 | })
77 | })
78 | })
79 |
80 | describe('.setLanguage(language)', () => {
81 | describe('with no language', () => {
82 | beforeEach(() => {
83 | gherkinLinter.setLanguage('fr')
84 | })
85 |
86 | it('allows method chaining', () => {
87 | expect(gherkinLinter.setLanguage()).toEqual(gherkinLinter)
88 | })
89 | it('set to english per default', () => {
90 | expect(gherkinLinter.setLanguage().language).toEqual('en')
91 | })
92 | it('set the feature keyword to Feature', () => {
93 | expect(gherkinLinter.setLanguage().featureKeyword).toEqual('Feature')
94 | })
95 |
96 | it('reset the lastParsedGherkin member', () => {
97 | gherkinLinter.parse('Feature: Scenario:')
98 | expect(gherkinLinter.setLanguage().lastParsedGherkin).toEqual('')
99 | })
100 | })
101 |
102 | describe('with same language as the current one', () => {
103 | const language = 'fr'
104 |
105 | beforeEach(() => {
106 | gherkinLinter.setLanguage(language)
107 | })
108 |
109 | it('allows method chaining', () => {
110 | expect(gherkinLinter.setLanguage(language)).toEqual(gherkinLinter)
111 | })
112 |
113 | it('does not reset the lastParsedGherkin member', () => {
114 | gherkinLinter.parse('Feature: Scenario:')
115 | expect(gherkinLinter.setLanguage(language).lastParsedGherkin).toEqual(
116 | 'Feature: Scenario:'
117 | )
118 | })
119 | })
120 |
121 | describe('with compatible language', () => {
122 | const language = 'fr'
123 |
124 | beforeEach(() => {
125 | gherkinLinter.setLanguage()
126 | })
127 |
128 | it('allows method chaining', () => {
129 | expect(gherkinLinter.setLanguage(language)).toEqual(gherkinLinter)
130 | })
131 | it('set the language properly', () => {
132 | expect(gherkinLinter.setLanguage(language).language).toEqual('fr')
133 | })
134 | it('set the feature keyword properly', () => {
135 | expect(gherkinLinter.setLanguage(language).featureKeyword).toEqual(
136 | 'Fonctionnalité'
137 | )
138 | })
139 |
140 | it('reset the lastParsedGherkin member', () => {
141 | gherkinLinter.parse('Feature: Scenario:')
142 | expect(gherkinLinter.setLanguage(language).lastParsedGherkin).toEqual(
143 | ''
144 | )
145 | })
146 | })
147 |
148 | describe('with incompatible language', () => {
149 | const language = 'nope'
150 |
151 | beforeEach(() => {
152 | gherkinLinter.setLanguage('fr')
153 | })
154 |
155 | it('allows method chaining', () => {
156 | expect(gherkinLinter.setLanguage(language)).toEqual(gherkinLinter)
157 | })
158 | it('keeps the language unchanged', () => {
159 | expect(gherkinLinter.setLanguage(language).language).toEqual('fr')
160 | })
161 | it('keeps the feature keyword unchanged', () => {
162 | expect(gherkinLinter.setLanguage(language).featureKeyword).toEqual(
163 | 'Fonctionnalité'
164 | )
165 | })
166 |
167 | it('does not reset the lastParsedGherkin member', () => {
168 | gherkinLinter.parse('Feature: Scenario:')
169 | expect(gherkinLinter.setLanguage(language).lastParsedGherkin).toEqual(
170 | 'Feature: Scenario:'
171 | )
172 | })
173 | })
174 | })
175 |
176 | describe('.setSubsetType(type)', () => {
177 | describe('when subset type is scenario or background', () => {
178 | describe('when subset type is scenario', () => {
179 | const type = 'scenario'
180 |
181 | beforeEach(() => {
182 | gherkinLinter.setSubsetType()
183 | })
184 |
185 | it('set isSubset to true', () => {
186 | expect(gherkinLinter.setSubsetType(type).isSubset).toBe(true)
187 | })
188 |
189 | it('set subsetType to scenario', () => {
190 | expect(gherkinLinter.setSubsetType(type).subsetType).toEqual(
191 | 'scenario'
192 | )
193 | })
194 |
195 | it('allows chaining methods', () => {
196 | expect(gherkinLinter.setSubsetType(type)).toEqual(gherkinLinter)
197 | })
198 |
199 | it('reset lastParsedGherkin', () => {
200 | gherkinLinter.parse('Feature:')
201 | expect(gherkinLinter.setSubsetType(type).lastParsedGherkin).toEqual(
202 | ''
203 | )
204 | })
205 | })
206 |
207 | describe('when subset type is background', () => {
208 | const type = 'background'
209 |
210 | beforeEach(() => {
211 | gherkinLinter.setSubsetType()
212 | })
213 |
214 | it('set isSubset to true', () => {
215 | expect(gherkinLinter.setSubsetType(type).isSubset).toBe(true)
216 | })
217 |
218 | it('set subsetType to scenario', () => {
219 | expect(gherkinLinter.setSubsetType(type).subsetType).toEqual(
220 | 'background'
221 | )
222 | })
223 |
224 | it('allows chaining methods', () => {
225 | expect(gherkinLinter.setSubsetType(type)).toEqual(gherkinLinter)
226 | })
227 |
228 | it('reset lastParsedGherkin', () => {
229 | gherkinLinter.parse('Feature:')
230 | expect(gherkinLinter.setSubsetType(type).lastParsedGherkin).toEqual(
231 | ''
232 | )
233 | })
234 | })
235 | })
236 |
237 | describe('when subset type is not scenario or background', () => {
238 | const type = 'foo'
239 |
240 | beforeEach(() => {
241 | gherkinLinter.setSubsetType('scenario')
242 | })
243 |
244 | it('set isSubset to false', () => {
245 | expect(gherkinLinter.setSubsetType(type).isSubset).toBe(false)
246 | })
247 |
248 | it('clean subsetType', () => {
249 | expect(gherkinLinter.setSubsetType(type).subsetType).toEqual('')
250 | })
251 |
252 | it('allows chaining methods', () => {
253 | expect(gherkinLinter.setSubsetType(type)).toEqual(gherkinLinter)
254 | })
255 |
256 | it('reset lastParsedGherkin', () => {
257 | gherkinLinter.parse('Feature:')
258 | expect(gherkinLinter.setSubsetType(type).lastParsedGherkin).toEqual('')
259 | })
260 | })
261 |
262 | describe('when subset is not specified', () => {
263 | beforeEach(() => {
264 | gherkinLinter.setSubsetType('scenario')
265 | })
266 |
267 | it('set isSubset to false', () => {
268 | expect(gherkinLinter.setSubsetType().isSubset).toBe(false)
269 | })
270 |
271 | it('clean subsetType', () => {
272 | expect(gherkinLinter.setSubsetType().subsetType).toEqual('')
273 | })
274 |
275 | it('allows chaining methods', () => {
276 | expect(gherkinLinter.setSubsetType()).toEqual(gherkinLinter)
277 | })
278 |
279 | it('reset lastParsedGherkin', () => {
280 | gherkinLinter.parse('Feature:')
281 | expect(gherkinLinter.setSubsetType().lastParsedGherkin).toEqual('')
282 | })
283 | })
284 |
285 | describe('when subset is the same as it was before', () => {
286 | it('does not reset lastParsedGherkin', () => {
287 | gherkinLinter.setSubsetType('scenario')
288 | gherkinLinter.parse('Feature:')
289 | expect(
290 | gherkinLinter.setSubsetType('scenario').lastParsedGherkin
291 | ).toEqual('Feature:')
292 | })
293 | })
294 | })
295 |
296 | describe('.getLintingErrors()', () => {
297 | describe('when gherkin content is valid', () => {
298 | it('returns an empty array', () => {
299 | expect(
300 | gherkinLinter.parse('Feature:').getLintingErrors().length
301 | ).toEqual(0)
302 | })
303 | })
304 |
305 | describe('when gherkin content has errors', () => {
306 | describe('with a full feature in plain english', () => {
307 | it('returns an array with the errors', () => {
308 | gherkinLinter.setLanguage().setSubsetType().parse(fullGherkinContent)
309 | expect(gherkinLinter.getLintingErrors()).toEqual([
310 | {
311 | character: 3,
312 | column: 2,
313 | line: 6,
314 | row: 5,
315 | text:
316 | "expected: #EOF, #TableRow, #DocStringSeparator, #StepLine, #TagLine, #ScenarioLine, #RuleLine, #Comment, #Empty, got 'invalid gherkin here!'",
317 | type: 'warning'
318 | },
319 | {
320 | character: 3,
321 | column: 2,
322 | line: 7,
323 | row: 6,
324 | text:
325 | "expected: #EOF, #TableRow, #DocStringSeparator, #StepLine, #TagLine, #ScenarioLine, #RuleLine, #Comment, #Empty, got 'Examples:'",
326 | type: 'warning'
327 | }
328 | ])
329 | })
330 | })
331 |
332 | describe('with a full feature in another valid language', () => {
333 | it('returns an array with the errors with appropriate line numbers', () => {
334 | gherkinLinter
335 | .setLanguage('fr')
336 | .setSubsetType()
337 | .parse(fullGherkinContentInFrench)
338 | expect(gherkinLinter.getLintingErrors()).toEqual([
339 | {
340 | character: 3,
341 | column: 2,
342 | line: 6,
343 | row: 5,
344 | text:
345 | "expected: #EOF, #TableRow, #DocStringSeparator, #StepLine, #TagLine, #ScenarioLine, #RuleLine, #Comment, #Empty, got 'invalid gherkin here!'",
346 | type: 'warning'
347 | },
348 | {
349 | character: 3,
350 | column: 2,
351 | line: 7,
352 | row: 6,
353 | text:
354 | "expected: #EOF, #TableRow, #DocStringSeparator, #StepLine, #TagLine, #ScenarioLine, #RuleLine, #Comment, #Empty, got 'Exemples:'",
355 | type: 'warning'
356 | }
357 | ])
358 | })
359 | })
360 |
361 | describe('with a subset in plain english', () => {
362 | it('returns an array with the errors with appropriate line numbers', () => {
363 | gherkinLinter
364 | .setLanguage()
365 | .setSubsetType('background')
366 | .parse(subsetBackgroundGherkinContent)
367 | expect(gherkinLinter.getLintingErrors()).toEqual([
368 | {
369 | character: 1,
370 | column: 0,
371 | line: 4,
372 | row: 3,
373 | text:
374 | "expected: #EOF, #TableRow, #DocStringSeparator, #StepLine, #TagLine, #ScenarioLine, #RuleLine, #Comment, #Empty, got 'invalid gherkin here!'",
375 | type: 'warning'
376 | },
377 | {
378 | character: 1,
379 | column: 0,
380 | line: 5,
381 | row: 4,
382 | text:
383 | "expected: #EOF, #TableRow, #DocStringSeparator, #StepLine, #TagLine, #ScenarioLine, #RuleLine, #Comment, #Empty, got 'Examples:'",
384 | type: 'warning'
385 | }
386 | ])
387 | })
388 | })
389 |
390 | describe('with a subset in another valid language', () => {
391 | it('returns an array with the errors with appropriate line numbers', () => {
392 | gherkinLinter
393 | .setLanguage('fr')
394 | .setSubsetType('scenario')
395 | .parse(subsetScenarioGherkinContentInFrench)
396 | expect(gherkinLinter.getLintingErrors()).toEqual([
397 | {
398 | character: 1,
399 | column: 0,
400 | line: 4,
401 | row: 3,
402 | text:
403 | "expected: #EOF, #TableRow, #DocStringSeparator, #StepLine, #TagLine, #ExamplesLine, #ScenarioLine, #RuleLine, #Comment, #Empty, got 'invalid gherkin here!'",
404 | type: 'warning'
405 | }
406 | ])
407 | })
408 | })
409 | })
410 | })
411 |
412 | describe('after parsing', () => {
413 | it('calls back with the cucumber message envelopes', () => {
414 | let messages
415 | const gherkinLinter = new GherkinLinter(msgs => (messages = msgs))
416 | const source = 'Feature: our feature\n'
417 | gherkinLinter.parse(source)
418 | expect(messages.length).toBeGreaterThan(0)
419 | expect(messages[0].source.data).toEqual(source)
420 | expect(messages[1].gherkinDocument.feature.name).toEqual('our feature')
421 | })
422 | })
423 | })
424 |
--------------------------------------------------------------------------------
/test/modules/gherkin-annotator/index.spec.ts:
--------------------------------------------------------------------------------
1 | import GherkinAnnotator from '../../../src/modules/gherkin-annotator'
2 |
3 | jest.unmock('../../../src/modules/gherkin-annotator')
4 |
5 | describe('GherkinAnnotator', () => {
6 | const session = { setAnnotations: jest.fn(), clearAnnotations: jest.fn() }
7 | const gherkinAnnotator = new GherkinAnnotator(session)
8 |
9 | describe('initial state', () => {
10 | it('initialize language to english per default', () => {
11 | expect(gherkinAnnotator.language).toEqual('en')
12 | })
13 |
14 | it('initialize mode to default', () => {
15 | expect(gherkinAnnotator.mode).toEqual('')
16 | })
17 | })
18 |
19 | it('delegates onParse to the linter', async() => {
20 | const onParse = jest.fn()
21 |
22 | const gherkinAnnotator = new GherkinAnnotator(session, onParse)
23 |
24 | gherkinAnnotator.setLanguage('en')
25 | await gherkinAnnotator.annotateNow('Feature: feature')
26 |
27 | expect(onParse).toHaveBeenCalledTimes(1)
28 | })
29 |
30 | describe('.setSession(session)', () => {
31 | afterEach(() => {
32 | gherkinAnnotator.setSession(session)
33 | })
34 |
35 | it('set the session properly', () => {
36 | const newSession = { foo: 'bar' }
37 |
38 | gherkinAnnotator.setSession(newSession)
39 | expect(gherkinAnnotator.session).toEqual(newSession)
40 | })
41 | })
42 |
43 | describe('.setLanguage(language)', () => {
44 | it('set the language properly', () => {
45 | gherkinAnnotator.setLanguage('fr')
46 | expect(gherkinAnnotator.language).toEqual('fr')
47 | })
48 | })
49 |
50 | describe('.setMode(mode)', () => {
51 | describe('when mode is gherkin_background_i18n', () => {
52 | it('set the mode to background', () => {
53 | gherkinAnnotator.setMode('gherkin_background_i18n')
54 | expect(gherkinAnnotator.mode).toEqual('background')
55 | })
56 | })
57 |
58 | describe('when mode is gherkin_scenario_i18n', () => {
59 | it('set the mode to scenario', () => {
60 | gherkinAnnotator.setMode('gherkin_scenario_i18n')
61 | expect(gherkinAnnotator.mode).toEqual('scenario')
62 | })
63 | })
64 |
65 | describe('when mode is not recognized', () => {
66 | it('reset the mode', () => {
67 | // @ts-ignore
68 | gherkinAnnotator.setMode('gherkin_foo_i18n')
69 | expect(gherkinAnnotator.mode).toEqual('')
70 | })
71 | })
72 | })
73 |
74 | describe('.annotateNow(someGherkin)', () => {
75 | beforeEach(() => {
76 | session.setAnnotations.mockClear()
77 | session.clearAnnotations.mockClear()
78 | })
79 |
80 | describe('with invalid gherkin', () => {
81 | const gherkin = 'invalid!'
82 |
83 | it('set the annotations to the session with errors', async () => {
84 | await gherkinAnnotator.annotateNow(gherkin)
85 | expect(session.clearAnnotations).not.toHaveBeenCalled()
86 | expect(session.setAnnotations).toHaveBeenCalledWith(expect.any(Array))
87 | })
88 | })
89 |
90 | describe('without linting errors', () => {
91 | const gherkin = 'Feature:'
92 |
93 | it('clear the annotations of the session', async () => {
94 | gherkinAnnotator.setLanguage('en')
95 | await gherkinAnnotator.annotateNow(gherkin)
96 | expect(session.setAnnotations).not.toHaveBeenCalled()
97 | expect(session.clearAnnotations).toHaveBeenCalledTimes(1)
98 | })
99 | })
100 | })
101 | })
102 |
--------------------------------------------------------------------------------
/test/modules/keyword-completer/index.spec.js:
--------------------------------------------------------------------------------
1 | import { getGherkinDialect } from '../../../src/modules/dialects/gherkin_i18n'
2 | import { getGherkinDialect as getBackgroundDialect } from '../../../src/modules/dialects/gherkin_background_i18n'
3 | import { getGherkinDialect as getScenarioDialect } from '../../../src/modules/dialects/gherkin_scenario_i18n'
4 | import KeywordCompleter from '../../../src/modules/keyword-completer'
5 |
6 | describe('KeywordCompleter class', () => {
7 | describe('Gherkin i18n', () => {
8 | describe('getCompletions', () => {
9 | it('calls the callback with the completions', async () => {
10 | const sessionMock = {
11 | getLine: jest.fn().mockReturnValue('')
12 | }
13 | const callBackMock = jest.fn()
14 | const keywordCompleter = new KeywordCompleter(getGherkinDialect)
15 | await keywordCompleter.getCompletions(
16 | null,
17 | sessionMock,
18 | { row: 1 },
19 | null,
20 | callBackMock
21 | )
22 | expect(callBackMock).toHaveBeenCalledWith(null, [
23 | {
24 | caption: 'Feature',
25 | meta: 'Keyword',
26 | score: 0,
27 | value: 'Feature'
28 | },
29 | {
30 | caption: 'Business Need',
31 | meta: 'Keyword',
32 | score: 1,
33 | value: 'Business Need'
34 | },
35 | {
36 | caption: 'Ability',
37 | meta: 'Keyword',
38 | score: 2,
39 | value: 'Ability'
40 | },
41 | {
42 | caption: 'Background',
43 | meta: 'Keyword',
44 | score: 3,
45 | value: 'Background'
46 | },
47 | {
48 | caption: 'Example',
49 | meta: 'Keyword',
50 | score: 4,
51 | value: 'Example'
52 | },
53 | {
54 | caption: 'Scenario',
55 | meta: 'Keyword',
56 | score: 5,
57 | value: 'Scenario'
58 | },
59 | {
60 | caption: 'Scenario Outline',
61 | meta: 'Keyword',
62 | score: 6,
63 | value: 'Scenario Outline'
64 | },
65 | {
66 | caption: 'Scenario Template',
67 | meta: 'Keyword',
68 | score: 7,
69 | value: 'Scenario Template'
70 | },
71 | {
72 | caption: 'Examples',
73 | meta: 'Keyword',
74 | score: 8,
75 | value: 'Examples'
76 | },
77 | {
78 | caption: 'Scenarios',
79 | meta: 'Keyword',
80 | score: 9,
81 | value: 'Scenarios'
82 | },
83 | {
84 | caption: '*',
85 | meta: 'Keyword',
86 | score: 10,
87 | value: '*'
88 | },
89 | {
90 | caption: 'Given',
91 | meta: 'Keyword',
92 | score: 11,
93 | value: 'Given'
94 | },
95 | {
96 | caption: 'When',
97 | meta: 'Keyword',
98 | score: 12,
99 | value: 'When'
100 | },
101 | {
102 | caption: 'Then',
103 | meta: 'Keyword',
104 | score: 13,
105 | value: 'Then'
106 | },
107 | {
108 | caption: 'And',
109 | meta: 'Keyword',
110 | score: 14,
111 | value: 'And'
112 | },
113 | {
114 | caption: 'But',
115 | meta: 'Keyword',
116 | score: 15,
117 | value: 'But'
118 | }
119 | ])
120 | })
121 | })
122 | })
123 |
124 | describe('Gherkin background i18n', () => {
125 | describe('getCompletions', () => {
126 | it('calls the callback with the completions', async () => {
127 | const sessionMock = {
128 | getLine: jest.fn().mockReturnValue('')
129 | }
130 | const callBackMock = jest.fn()
131 | const keywordCompleter = new KeywordCompleter(getBackgroundDialect)
132 | await keywordCompleter.getCompletions(
133 | null,
134 | sessionMock,
135 | { row: 1 },
136 | null,
137 | callBackMock
138 | )
139 | expect(callBackMock).toHaveBeenCalledWith(null, [
140 | {
141 | caption: '*',
142 | meta: 'Keyword',
143 | score: 0,
144 | value: '*'
145 | },
146 | {
147 | caption: 'Given',
148 | meta: 'Keyword',
149 | score: 1,
150 | value: 'Given'
151 | },
152 | {
153 | caption: 'When',
154 | meta: 'Keyword',
155 | score: 2,
156 | value: 'When'
157 | },
158 | {
159 | caption: 'Then',
160 | meta: 'Keyword',
161 | score: 3,
162 | value: 'Then'
163 | },
164 | {
165 | caption: 'And',
166 | meta: 'Keyword',
167 | score: 4,
168 | value: 'And'
169 | },
170 | {
171 | caption: 'But',
172 | meta: 'Keyword',
173 | score: 5,
174 | value: 'But'
175 | }
176 | ])
177 | })
178 | })
179 | })
180 |
181 | describe('Gherkin scenario i18n', () => {
182 | describe('getCompletions', () => {
183 | it('calls the callback with the completions', async () => {
184 | const sessionMock = {
185 | getLine: jest.fn().mockReturnValue('')
186 | }
187 | const callBackMock = jest.fn()
188 | const keywordCompleter = new KeywordCompleter(getScenarioDialect)
189 | await keywordCompleter.getCompletions(
190 | null,
191 | sessionMock,
192 | { row: 1 },
193 | null,
194 | callBackMock
195 | )
196 | expect(callBackMock).toHaveBeenCalledWith(null, [
197 | {
198 | caption: 'Examples',
199 | meta: 'Keyword',
200 | score: 0,
201 | value: 'Examples'
202 | },
203 | {
204 | caption: 'Scenarios',
205 | meta: 'Keyword',
206 | score: 1,
207 | value: 'Scenarios'
208 | },
209 | {
210 | caption: '*',
211 | meta: 'Keyword',
212 | score: 2,
213 | value: '*'
214 | },
215 | {
216 | caption: 'Given',
217 | meta: 'Keyword',
218 | score: 3,
219 | value: 'Given'
220 | },
221 | {
222 | caption: 'When',
223 | meta: 'Keyword',
224 | score: 4,
225 | value: 'When'
226 | },
227 | {
228 | caption: 'Then',
229 | meta: 'Keyword',
230 | score: 5,
231 | value: 'Then'
232 | },
233 | {
234 | caption: 'And',
235 | meta: 'Keyword',
236 | score: 6,
237 | value: 'And'
238 | },
239 | {
240 | caption: 'But',
241 | meta: 'Keyword',
242 | score: 7,
243 | value: 'But'
244 | }
245 | ])
246 | })
247 | })
248 | })
249 | })
250 |
--------------------------------------------------------------------------------
/test/modules/step-completer/index.spec.js:
--------------------------------------------------------------------------------
1 | import { getGherkinDialect } from '../../../src/modules/dialects/gherkin_i18n'
2 | import StepCompleter from '../../../src/modules/step-completer'
3 |
4 | describe('StepCompleter class', () => {
5 | const steps = [
6 | {
7 | name: 'I start the coffee machine using language "lang"',
8 | value: '',
9 | score: 100
10 | },
11 | {
12 | name: 'I shutdown the coffee machine',
13 | value: '',
14 | score: 10
15 | }
16 | ]
17 |
18 | const editorMock = {
19 | getOptions: jest
20 | .fn()
21 | .mockReturnValue({ fontFamily: 'Arial', fontSize: 14 }),
22 | completer: { popup: { container: { style: { width: 0 } } } }
23 | }
24 |
25 | describe('getCompletions', () => {
26 | it('calls the callback with the completions', async () => {
27 | const autoCompleteFunctionMock = jest.fn(() => Promise.resolve(steps))
28 | const sessionMock = {
29 | getLine: jest.fn().mockReturnValue('And I')
30 | }
31 | const callBackMock = jest.fn()
32 | const stepCompleter = new StepCompleter(
33 | autoCompleteFunctionMock,
34 | getGherkinDialect
35 | )
36 | await stepCompleter.getCompletions(
37 | editorMock,
38 | sessionMock,
39 | { row: 1 },
40 | null,
41 | callBackMock
42 | )
43 | expect(autoCompleteFunctionMock).toHaveBeenCalledWith('And', 'I')
44 | expect(callBackMock).toHaveBeenCalledWith(null, steps)
45 | })
46 |
47 | it('does not call autoCompleteFunction when word does not start with a gherkin keyword', async () => {
48 | const autoCompleteFunctionMock = jest.fn()
49 | const callBackMock = jest.fn()
50 | const sessionMock = {
51 | getLine: jest.fn().mockReturnValue('I start')
52 | }
53 | const stepCompleter = new StepCompleter(
54 | autoCompleteFunctionMock,
55 | getGherkinDialect
56 | )
57 | await stepCompleter.getCompletions(
58 | editorMock,
59 | sessionMock,
60 | { row: 1 },
61 | null,
62 | callBackMock
63 | )
64 | expect(autoCompleteFunctionMock).not.toHaveBeenCalled()
65 | expect(callBackMock).not.toHaveBeenCalled()
66 | })
67 |
68 | it('resizes the completions popup', async () => {
69 | const autoCompleteFunctionMock = jest.fn()
70 | const callBackMock = jest.fn()
71 | const sessionMock = {
72 | getLine: jest.fn().mockReturnValue('I start')
73 | }
74 | const stepCompleter = new StepCompleter(
75 | autoCompleteFunctionMock,
76 | getGherkinDialect
77 | )
78 | await stepCompleter.getCompletions(
79 | editorMock,
80 | sessionMock,
81 | { row: 1 },
82 | null,
83 | callBackMock
84 | )
85 | expect(editorMock.completer.popup.container.style.width).toEqual('50px')
86 | })
87 | })
88 | })
89 |
--------------------------------------------------------------------------------
/tsconfig.build.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "exclude": [
4 | "test/**/*",
5 | "jest.setup.ts"
6 | ]
7 | }
8 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "declaration": true,
4 | "target": "es5",
5 | "lib": [
6 | "es6",
7 | "dom"
8 | ],
9 | "sourceMap": true,
10 | "allowJs": true,
11 | "jsx": "react",
12 | "resolveJsonModule": true,
13 | "module": "commonjs",
14 | "esModuleInterop": true,
15 | "noImplicitAny": false,
16 | "moduleResolution": "node",
17 | "outDir": "dist",
18 | "downlevelIteration": true,
19 | "skipLibCheck": true
20 | },
21 | "include": [
22 | "src/**/*",
23 | "./jest.setup.ts",
24 | "test/**/*"
25 | ],
26 | "compileOnSave": false
27 | }
28 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const TerserPlugin = require('terser-webpack-plugin')
3 | const HtmlWebpackPlugin = require('html-webpack-plugin')
4 | const { DefinePlugin, ProvidePlugin, SourceMapDevToolPlugin } = require('webpack')
5 |
6 | module.exports = {
7 | mode: 'development',
8 | devtool: false,
9 | context: path.resolve(__dirname, 'dev'),
10 | entry: './index.js',
11 | devServer: {
12 | host: 'localhost',
13 | port: 5000,
14 | devMiddleware: {
15 | publicPath: '/',
16 | stats: {
17 | preset: 'minimal',
18 | colors: true
19 | }
20 | },
21 | client: {
22 | logging: 'none'
23 | }
24 | },
25 | module: {
26 | rules: [
27 | {
28 | test: /\.(js|jsx|ts|tsx)$/,
29 | exclude: /node_modules/,
30 | use: {
31 | loader: 'ts-loader'
32 | }
33 | }
34 | ]
35 | },
36 | resolve: {
37 | extensions: ['.js', '.jsx', '.ts', '.tsx'],
38 | modules: [path.resolve(__dirname, 'src'), 'node_modules'],
39 | fallback: {
40 | stream: require.resolve('stream-browserify')
41 | }
42 | },
43 | optimization: {
44 | minimizer: [
45 | new TerserPlugin({
46 | terserOptions: {
47 | format: {
48 | comments: false
49 | }
50 | },
51 | extractComments: false
52 | })
53 | ]
54 | },
55 | performance: {
56 | hints: false
57 | },
58 | plugins: [
59 | new HtmlWebpackPlugin({
60 | title: 'React Gherkin Editor'
61 | }),
62 | new DefinePlugin({
63 | __REACT_DEVTOOLS_GLOBAL_HOOK__: '({ isDisabled: true })'
64 | }),
65 | new ProvidePlugin({
66 | process: 'process/browser'
67 | }),
68 | new ProvidePlugin({
69 | Buffer: ['buffer', 'Buffer']
70 | }),
71 | new SourceMapDevToolPlugin({
72 | exclude: /node_modules/
73 | })
74 | ]
75 | }
76 |
--------------------------------------------------------------------------------