diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..b70343a --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,33 @@ +# Contributing + +See for general advice on contributing to Cucumber. Please also read the [Code of Conduct](https://github.com/cucumber/.github/blob/main/CODE_OF_CONDUCT.md). + +## Polyglot content + +Some of the content in the docs is polyglot - that is, offered in multiple programming languages. We can vary the language-specific content within a single page using some custom React components and MDX. If the page you're editing requires polyglot content, first ensure it has an `.mdx` extension - you can rename from `.md` if needed. + +### Tabs + +You can use the `` and `` components in any MDX page, without adding any imports. Example usage: + +```mdx +Here is some polyglot content: + + + The Java content + The Ruby content + The JavaScript content + +``` + +Under the hood, this is leaning on [Docusaurus's Tabs functionality](https://docusaurus.io/docs/markdown-features/tabs). The language selection will: + +- Sync across different groups of tabs in the same page +- Be remembered across different pages +- Update the URL via the `lang` query parameter, so language-specific URLs are shareable + +### Term + +Sometimes, a bit of specific terminology varies between languages, but we still want to keep the content on one page. For these cases we can use the `` component which supports a few shorthands that are resolved to the correct language-specific terminology. You can see the available terms in [`terms.json`](./src/components/Polyglot/terms.json) + + diff --git a/docs/cucumber/_category_.json b/docs/cucumber/_category_.json new file mode 100644 index 0000000..d1aeec0 --- /dev/null +++ b/docs/cucumber/_category_.json @@ -0,0 +1,4 @@ +{ + "position": 2, + "label": "Cucumber" +} diff --git a/docs/cucumber/index.mdx b/docs/cucumber/index.mdx new file mode 100644 index 0000000..587e4eb --- /dev/null +++ b/docs/cucumber/index.mdx @@ -0,0 +1,5 @@ +import DocCardList from '@theme/DocCardList'; + +# Cucumber + + diff --git a/docs/cucumber/step-definitions.mdx b/docs/cucumber/step-definitions.mdx new file mode 100644 index 0000000..7e90b41 --- /dev/null +++ b/docs/cucumber/step-definitions.mdx @@ -0,0 +1,160 @@ +# Step definitions + +A Step Definition is a stepdef-body with an [expression](#expressions) that links it to one or more [Gherkin steps](/docs/gherkin/reference#steps). +When Cucumber executes a [Gherkin step](/docs/gherkin/reference#steps) in a scenario, it will look for a matching *step definition* to execute. + +To illustrate how this works, look at the following Gherkin Scenario: + +```gherkin +Scenario: Some cukes + Given I have 48 cukes in my belly +``` + +The `I have 48 cukes in my belly` part of the step (the text following the `Given` keyword) will match the following step definition: + + + + +```java +package com.example; +import io.cucumber.java.en.Given; + +public class StepDefinitions { + @Given("I have {int} cukes in my belly") + public void i_have_n_cukes_in_my_belly(int cukes) { + System.out.format("Cukes: %n\n", cukes); + } +} +``` + +Or, using Java8 lambdas: + +```java +package com.example; +import io.cucumber.java8.En; + +public class StepDefinitions implements En { + public StepDefinitions() { + Given("I have {int} cukes in my belly", (Integer cukes) -> { + System.out.format("Cukes: %n\n", cukes); + }); + } +} +``` + + + + +```kotlin +package com.example +import io.cucumber.java8.En + +class StepDefinitions : En { + + init { + Given("I have {int} cukes in my belly") { cukes: Int -> + println("Cukes: $cukes") + } + } + +} +``` + + + + +```scala +package com.example +import io.cucumber.scala.{ScalaDsl, EN} + +class StepDefinitions extends ScalaDsl with EN { + + Given("I have {int} cukes in my belly") { cukes: Int => + println(s"Cukes: $cukes") + } + +} +``` + + + + +```ruby +Given('I have {int} cukes in my belly') do |cukes| + puts "Cukes: #{cukes}" +end +``` + + + + +```javascript +const { Given } = require('cucumber') + +Given('I have {int} cukes in my belly', function (cukes) { + console.log(`Cukes: ${cukes}`) +}); +``` + + + + +## Expressions + +A step definition's *expression* can either be a [Regular Expression](https://en.wikipedia.org/wiki/Regular_expression) +or a [Cucumber Expression](/docs/cucumber/cucumber-expressions). The examples in this section use Cucumber Expressions. +If you prefer to use Regular Expressions, each expression-parameter from the match will be passed as arguments to the step +definition's stepdef-body. + + + + +```java +@Given("I have {int} cukes in my belly") +public void i_have_n_cukes_in_my_belly(int cukes) { +} +``` + + + + +```kotlin +Given("I have {int} cukes in my belly") { cukes: Int -> + println("Cukes: $cukes") +} +``` + + + + +```scala +Given("I have {int} cukes in my belly") { cukes: Int => + println(s"Cukes: $cukes") +} +``` + + + + +```ruby +Given(/I have {int} cukes in my belly/) do |cukes| +end +``` + + + + +```javascript +Given(/I have {int} cukes in my belly/, function (cukes) { +}); +``` + + + + +If the expression-parameter expression is identical to one of the registered +[parameter types](/docs/cucumber/cucumber-expressions#parameter-types)'s `regexp`, +the captured string will be transformed before it is passed to the +step definition's stepdef-body. In the example above, the `cukes` +argument will be an integer, because the built-in `int` parameter type's +`regexp` is `\d+` . diff --git a/docs/gherkin/index.mdx b/docs/gherkin/index.mdx new file mode 100644 index 0000000..3c70efb --- /dev/null +++ b/docs/gherkin/index.mdx @@ -0,0 +1,5 @@ +import DocCardList from '@theme/DocCardList'; + +# Gherkin + + diff --git a/docs/gherkin/languages.mdx b/docs/gherkin/languages.mdx new file mode 100644 index 0000000..5733d5d --- /dev/null +++ b/docs/gherkin/languages.mdx @@ -0,0 +1,16 @@ +--- +sidebar_position: 1 +--- + +import { GherkinLanguages } from "@site/src/components/GherkinLanguages"; + +# Localisation + +In order to allow Gherkin to be written in a number of languages, the keywords have been translated into multiple languages. To improve readability and flow, some languages may have more than one translation for any given keyword. + +You can find all translation of Gherkin [on GitHub](https://github.com/cucumber/gherkin). +This is also the place to add or update translations. + +A list of the currently supported languages and their keywords can be found below. + + diff --git a/docs/gherkin/reference.md b/docs/gherkin/reference.md index 8c4d9d2..b28521b 100644 --- a/docs/gherkin/reference.md +++ b/docs/gherkin/reference.md @@ -1,3 +1,7 @@ +--- +sidebar_position: 0 +--- + # Reference Gherkin uses a set of special [keywords](#keywords) to give structure and meaning to diff --git a/docs/index.mdx b/docs/index.mdx new file mode 100644 index 0000000..915799d --- /dev/null +++ b/docs/index.mdx @@ -0,0 +1,5 @@ +--- +sidebar_position: 0 +--- + +# Introduction diff --git a/docusaurus.config.ts b/docusaurus.config.ts index 3a63551..e446507 100644 --- a/docusaurus.config.ts +++ b/docusaurus.config.ts @@ -64,7 +64,7 @@ export default { }, items: [ { - to: 'https://cucumber.io/docs/installation', + to: '/docs', label: 'Documentation', position: 'left' }, @@ -143,7 +143,7 @@ export default { prism: { theme: lightCodeTheme, darkTheme: darkCodeTheme, - additionalLanguages: ['gherkin'] + additionalLanguages: ['gherkin', 'java', 'ruby', 'scala'] }, }), diff --git a/package-lock.json b/package-lock.json index ffccbcf..736f299 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "name": "website", "version": "0.0.0", "dependencies": { + "@cucumber/gherkin": "^28.0.0", "@docusaurus/core": "3.1.1", "@docusaurus/preset-classic": "3.1.1", "@mdx-js/react": "3.0.1", @@ -2075,6 +2076,37 @@ "node": ">=0.1.90" } }, + "node_modules/@cucumber/gherkin": { + "version": "28.0.0", + "resolved": "https://registry.npmjs.org/@cucumber/gherkin/-/gherkin-28.0.0.tgz", + "integrity": "sha512-Ee6zJQq0OmIUPdW0mSnsCsrWA2PZAELNDPICD2pLfs0Oz7RAPgj80UsD2UCtqyAhw2qAR62aqlktKUlai5zl/A==", + "dependencies": { + "@cucumber/messages": ">=19.1.4 <=24" + } + }, + "node_modules/@cucumber/messages": { + "version": "24.1.0", + "resolved": "https://registry.npmjs.org/@cucumber/messages/-/messages-24.1.0.tgz", + "integrity": "sha512-hxVHiBurORcobhVk80I9+JkaKaNXkW6YwGOEFIh/2aO+apAN+5XJgUUWjng9NwqaQrW1sCFuawLB1AuzmBaNdQ==", + "dependencies": { + "@types/uuid": "9.0.8", + "class-transformer": "0.5.1", + "reflect-metadata": "0.2.1", + "uuid": "9.0.1" + } + }, + "node_modules/@cucumber/messages/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/@discoveryjs/json-ext": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", @@ -3585,6 +3617,11 @@ "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.2.tgz", "integrity": "sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ==" }, + "node_modules/@types/uuid": { + "version": "9.0.8", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.8.tgz", + "integrity": "sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==" + }, "node_modules/@types/ws": { "version": "8.5.10", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.10.tgz", @@ -4569,6 +4606,11 @@ "node": ">=8" } }, + "node_modules/class-transformer": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz", + "integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==" + }, "node_modules/clean-css": { "version": "5.3.3", "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.3.tgz", @@ -12072,6 +12114,12 @@ "node": ">=6.0.0" } }, + "node_modules/reflect-metadata": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.1.tgz", + "integrity": "sha512-i5lLI6iw9AU3Uu4szRNPPEkomnkjRTaVt9hy/bn5g/oSzekBSMeLZblcjP74AW0vBabqERLLIrz+gR8QYR54Tw==", + "deprecated": "This version has a critical bug in fallback handling. Please upgrade to reflect-metadata@0.2.2 or newer." + }, "node_modules/regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", diff --git a/package.json b/package.json index a95a880..2c8c0c3 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "write-heading-ids": "docusaurus write-heading-ids" }, "dependencies": { + "@cucumber/gherkin": "^28.0.0", "@docusaurus/core": "3.1.1", "@docusaurus/preset-classic": "3.1.1", "@mdx-js/react": "3.0.1", diff --git a/src/components/GherkinLanguages/GherkinLanguages.module.scss b/src/components/GherkinLanguages/GherkinLanguages.module.scss new file mode 100644 index 0000000..ff4efa3 --- /dev/null +++ b/src/components/GherkinLanguages/GherkinLanguages.module.scss @@ -0,0 +1,5 @@ +.equivalents { + code { + margin-right: 0.5em; + } +} diff --git a/src/components/GherkinLanguages/GherkinLanguages.tsx b/src/components/GherkinLanguages/GherkinLanguages.tsx new file mode 100644 index 0000000..f01868b --- /dev/null +++ b/src/components/GherkinLanguages/GherkinLanguages.tsx @@ -0,0 +1,58 @@ +import {FC} from "react"; +import {dialects, Dialect} from '@cucumber/gherkin' +import styles from './GherkinLanguages.module.scss' + +type KeywordProps = { + canonical: string + equivalents: readonly string[] +} + +const Keyword: FC = ({canonical, equivalents}) => { + return + {canonical} + {equivalents.map((equiv, index) => { + return {equiv.trim()} + })} + +} + +type KeywordsProps = { + code: keyof typeof dialects + dialect: Dialect +} + + +const Language: FC = ({code, dialect}) => { + return <> +

{dialect.name}/{dialect.native} ({code})

+ + + + + + + + + + + + + + + + + + + + +
KeywordEquivalent(s)
+ +} + +export const GherkinLanguages: FC = () => { + return <> + {Object.entries(dialects).map(([key, dialect]) => { + return + })} + +} diff --git a/src/components/GherkinLanguages/index.ts b/src/components/GherkinLanguages/index.ts new file mode 100644 index 0000000..176897f --- /dev/null +++ b/src/components/GherkinLanguages/index.ts @@ -0,0 +1 @@ +export * from './GherkinLanguages' diff --git a/src/components/Polyglot/Tabs.tsx b/src/components/Polyglot/Tabs.tsx new file mode 100644 index 0000000..1080a5d --- /dev/null +++ b/src/components/Polyglot/Tabs.tsx @@ -0,0 +1,19 @@ +import {FC, ReactElement, ReactNode} from "react"; +import DocusaurusTabs from "@theme/Tabs"; +import DocusaurusTabItem from "@theme/TabItem"; +import {LANGUAGES} from "./constants"; + +type TabProps = { value: keyof typeof LANGUAGES, children: ReactNode }; + +export const Tabs: FC<{children: ReactElement[]}> = ({children}) => { + return + {children.map(child => { + return {child.props.children} + })} + +} + +export const Tab: FC = () => { + // no-op component impl - see remapping in above + throw ' must be used inside of ' +} diff --git a/src/components/Polyglot/Term.tsx b/src/components/Polyglot/Term.tsx new file mode 100644 index 0000000..210a508 --- /dev/null +++ b/src/components/Polyglot/Term.tsx @@ -0,0 +1,21 @@ +import {FC} from "react"; +import terms from './terms.json' +import {useHistory} from "@docusaurus/router"; +import BrowserOnly from '@docusaurus/BrowserOnly'; +import {DEFAULT_LANGUAGE} from "./constants"; + +type TermProps = { children: keyof typeof terms }; + +const InternalTerm: FC = ({children}) => { + const history = useHistory() + const lang = new URLSearchParams(history.location.search).get('lang') || localStorage.getItem('docusaurus.tab.lang') || DEFAULT_LANGUAGE + return {terms[children][lang]} +} + +export const Term: FC = ({children}) => { + return {terms[children][DEFAULT_LANGUAGE]}}> + {() => { + return {children} + }} + +} diff --git a/src/components/Polyglot/constants.ts b/src/components/Polyglot/constants.ts new file mode 100644 index 0000000..c0ff2f3 --- /dev/null +++ b/src/components/Polyglot/constants.ts @@ -0,0 +1,9 @@ +export const DEFAULT_LANGUAGE = 'java' + +export const LANGUAGES = { + "java": "Java", + "kotlin": "Kotlin", + "scala": "Scala", + "ruby": "Ruby", + "javascript": "JavaScript" +} diff --git a/src/components/Polyglot/index.ts b/src/components/Polyglot/index.ts new file mode 100644 index 0000000..54c0f1b --- /dev/null +++ b/src/components/Polyglot/index.ts @@ -0,0 +1,2 @@ +export * from './Tabs' +export * from './Term' diff --git a/src/components/Polyglot/terms.json b/src/components/Polyglot/terms.json new file mode 100644 index 0000000..45d9de3 --- /dev/null +++ b/src/components/Polyglot/terms.json @@ -0,0 +1,16 @@ +{ + "stepdef-body": { + "java": "method", + "kotlin": "function", + "scala": "function", + "javascript": "function", + "ruby": "block" + }, + "expression-parameter": { + "java": "capture group", + "kotlin": "capture group", + "scala": "capture group", + "javascript": "output parameter", + "ruby": "output parameter" + } +} diff --git a/src/theme/MDXComponents.js b/src/theme/MDXComponents.js new file mode 100644 index 0000000..b6b971d --- /dev/null +++ b/src/theme/MDXComponents.js @@ -0,0 +1,12 @@ +// Import the original mapper +import MDXComponents from '@theme-original/MDXComponents'; +import {Tab, Tabs, Term} from "@site/src/components/Polyglot"; + +export default { + // Re-use the default mapping + ...MDXComponents, + // Add our custom components + Tab, + Tabs, + Term +};