diff --git a/.agents/skills/oft-spec-driven-development/SKILL.md b/.agents/skills/oft-spec-driven-development/SKILL.md index 0c91b8c..d20f37c 100644 --- a/.agents/skills/oft-spec-driven-development/SKILL.md +++ b/.agents/skills/oft-spec-driven-development/SKILL.md @@ -232,6 +232,12 @@ If the issue text is unavailable, ask the user for the issue content or a short - Preserve existing OFT IDs unless the semantics changed. - When semantics change, revise the item thoughtfully instead of making arbitrary ID churn. - Keep requirements user-facing and design technical; do not duplicate the same text in both places. +- For source and test coverage tags, use only the covering artifact type on the + left side (for example, `[impl->dsn~name~1]`); let OpenFastTrace generate the + tag item's name. Do not use `>>` forwarding to connect implementation tags + to tests. Instead, make the relevant design item need both `impl` and + `itest`, and have separate implementation and test coverage tags cover that + design item. - Prefer precise task items over broad epics in changesets. - Keep comments in code minimal and let naming carry intent, consistent with the quality requirements. - Avoid adding dependencies unless a documented design decision approves them. diff --git a/README.md b/README.md index fc38b75..45ecd6b 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,19 @@ for portable editor behavior. The first supported request is `workspace/symbol`; it currently returns no symbols until workspace indexing is implemented. +### OFT templates + +In Markdown and reStructuredText documents, type an `oft-` prefix and select +one of the OpenFastTrace snippets for an architecture, business or technical +constraint, design, feature, quality goal, quality scenario, requirement, or +scenario item. The scenario snippet includes a +Given-When-Then skeleton and a `Covers:` placeholder. + +To insert a snippet, type `oft-`, press `Ctrl+Space` to open the completion +list (or wait for it to appear), then select the desired OpenFastTrace snippet. + +Snippets are based on the [OFT IntelliJ plugin](https://github.com/itsallcode/openfasttrace-intellij-plugin/blob/main/src/main/resources/liveTemplates/OpenFastTrace.xml). + ## Requirements The extension requires Java 21 or later. It uses `openfasttrace.java.home` when @@ -34,10 +47,6 @@ is needed. ## Extension Settings -Include if your extension adds any VS Code settings through the `contributes.configuration` extension point. - -For example: - This extension contributes `openfasttrace.java.home`, an absolute path to a Java 21 or later runtime. Configure it when VS Code cannot find a suitable runtime through `JAVA_HOME` or `PATH`. diff --git a/build.gradle b/build.gradle index 55f6a42..bb564b5 100644 --- a/build.gradle +++ b/build.gradle @@ -21,10 +21,10 @@ sonar { } requirementTracing { - inputDirectories = files('doc') + inputDirectories = files('doc', 'extension/src', 'server/src') reportVerbosity = 'FAILURE_DETAILS' filteredTags = ['mvp'] - filterAcceptsItemsWithoutTag = false + filterAcceptsItemsWithoutTag = true } def isNonStable = { String version -> diff --git a/doc/changesets/03-mvp-oft-template-snippets.md b/doc/changesets/03-mvp-oft-template-snippets.md new file mode 100644 index 0000000..5d994ea --- /dev/null +++ b/doc/changesets/03-mvp-oft-template-snippets.md @@ -0,0 +1,49 @@ +# 03 MVP OFT Template Snippets + +## Goal + +Provide a small, immediately useful authoring slice by offering editable OFT +starter templates in the supported specification-document languages. + +## Scope + +In scope: + +* Contribute feature, requirement, scenario, design, architecture, and + constraint snippets for Markdown and reStructuredText. +* Add the template requirement chain to the MVP trace. +* Verify snippet availability and the scenario skeleton in a VS Code + integration test. + +Out of scope: + +* Parsing, indexing, highlighting, ID completion, and trace execution. + +## Design References + +* [System Requirements](../system_requirements.md) +* [LSP-first Architecture](../design/architecture.md) +* [Quality Requirements](../design/quality_requirements.md) + +## Task List + +### Requirements And Design + +- [x] Mark the feature, template requirement, and template-insertion scenario as MVP. +- [x] Split template snippets into their own single-scenario design item. +- [x] Add traced implementation and integration-test items for the snippet + contribution, covered by TypeScript source and integration-test tags. + +### Implementation + +- [x] Add language-native Markdown and reStructuredText snippet assets. +- [x] Register the assets in the extension manifest. + +### Verification + +- [x] Run the extension compile, lint, unit, and integration tests. +- [x] Run the full Gradle build and the MVP requirements trace. + +### Update User Documentation + +- [x] Document the templates and their invocation in README.md. diff --git a/doc/design/architecture.md b/doc/design/architecture.md index 77c6cf0..f0a4bea 100644 --- a/doc/design/architecture.md +++ b/doc/design/architecture.md @@ -84,17 +84,31 @@ Covers: ### Editor Presentation `dsn~editor-presentation~1` -The VS Code adapter contributes snippets for OFT templates, a Markdown -TextMate injection grammar for immediate declaration coloring, and maps LSP -semantic tokens to OFT coverage-tag styling. It preserves the host language of -every document. +The VS Code adapter contributes a Markdown TextMate injection grammar for +immediate declaration coloring and maps LSP semantic tokens to OFT coverage-tag +styling. It preserves the host language of every document. + +Tags: backlog Covers: - `scn~recognize-and-highlight-oft-content~1` -- `scn~author-an-oft-link~1` Needs: impl +### OFT Template Snippets +`dsn~oft-template-snippets~1` + +The VS Code adapter contributes language-native Markdown and reStructuredText +snippet files for the common OFT artifact templates. The templates are static, +editable starter text and do not require language-server parsing or indexing. + +Tags: mvp + +Covers: +- `scn~insert-oft-template~1` + +Needs: impl, itest + ### Trace Command and Plain-Text Report `dsn~trace-command-and-plain-text-report~1` @@ -103,6 +117,8 @@ request with workspace-relative inputs and filters. The server returns a typed trace report containing status, messages, and source locations. The adapter renders it in an Output channel and opens locations from report entries. +Tags: backlog + Covers: - `scn~trace-the-mvp-scope~1` @@ -116,6 +132,8 @@ settings. Profiles contain scope, additional paths, artifact-type filters, tag filters, and output mode. Commands and a Quick Pick select profiles; optional VS Code Tasks are an adapter, not the persistence model. +Tags: backlog + Covers: - `scn~configure-a-repeatable-filtered-trace~1` @@ -128,6 +146,8 @@ The server exposes a normalized trace-result tree through a custom LSP command. The VS Code adapter maps it to `TestController` items grouped by file, item, and trace link; each node retains its source `Location` and defect detail. +Tags: backlog + Covers: - `scn~inspect-structured-results~1` @@ -140,6 +160,8 @@ The VS Code adapter registers a command that opens the canonical upstream user guide using the host browser service. The guide itself is not bundled or duplicated. +Tags: backlog + Covers: - `scn~read-the-oft-guide~1` diff --git a/doc/system_requirements.md b/doc/system_requirements.md index b6a6709..5a85906 100644 --- a/doc/system_requirements.md +++ b/doc/system_requirements.md @@ -26,6 +26,8 @@ only that scope. The product recognizes OFT declarations in Markdown, reStructuredText, and coverage tags in supported workspace files. +Tags: backlog + Needs: req ### Connect the Language Server @@ -44,6 +46,8 @@ Needs: req The product finds OFT declarations and follows coverage links throughout the workspace. +Tags: backlog + Needs: req ### Author OFT Items and Links @@ -52,6 +56,8 @@ Needs: req The product provides item templates and context-aware completion of declared OFT IDs. +Tags: mvp + Needs: req ### Run OFT Traces @@ -60,6 +66,8 @@ Needs: req The product runs OpenFastTrace for the workspace and presents a navigable report. +Tags: backlog + Needs: req ### Configure Repeatable Traces @@ -68,6 +76,8 @@ Needs: req The product stores named trace profiles with inputs, filters, and output-mode selection. +Tags: backlog + Needs: req ### Inspect Structured Trace Results @@ -76,6 +86,8 @@ Needs: req The product presents trace outcomes as a hierarchical result view with source navigation. +Tags: backlog + Needs: req ### Access OFT Documentation @@ -83,6 +95,8 @@ Needs: req The product opens the upstream OpenFastTrace user guide from the IDE. +Tags: backlog + Needs: req ## User Requirements @@ -93,6 +107,8 @@ Needs: req The editor highlights OFT declarations in `.md`, `.markdown`, and `.rst` documents without changing the documents' ordinary language mode. +Tags: backlog + Covers: - `feat~recognize-oft-content~1` @@ -117,6 +133,8 @@ Needs: scn The editor highlights OFT coverage tags in source, configuration, and markup files supported by the OpenFastTrace tag importer. +Tags: backlog + Covers: - `feat~recognize-oft-content~1` @@ -128,6 +146,8 @@ Needs: scn Users can find workspace declarations by full or partial OFT item ID and open the matching declaration. +Tags: backlog + Covers: - `feat~discover-and-navigate-oft-items~1` @@ -140,6 +160,8 @@ Users can go from a `Covers:` ID or either side of a coverage tag to the corresponding declaration. Missing name and revision on a coverage-tag left side are resolved from its right-side target. +Tags: backlog + Covers: - `feat~discover-and-navigate-oft-items~1` @@ -151,6 +173,8 @@ Needs: scn Users can find `Covers:` entries and source-side coverage tags that cover an OFT declaration. +Tags: backlog + Covers: - `feat~discover-and-navigate-oft-items~1` @@ -163,6 +187,8 @@ Users can insert bundled templates for common OFT artifacts, including `feat`, `req`, `scn`, `dsn`, architecture, and constraint items. The scenario template includes a Given-When-Then skeleton and a covered-item placeholder. +Tags: mvp + Covers: - `feat~author-oft-items-and-links~1` @@ -174,6 +200,8 @@ Needs: scn While editing a `Covers:` entry, users receive workspace declaration IDs ranked by full-ID prefix, name prefix, name substring, and artifact-type prefix. +Tags: backlog + Covers: - `feat~author-oft-items-and-links~1` @@ -185,6 +213,8 @@ Needs: scn After an artifact type and `->` in a likely coverage tag, users receive the same ranked declaration completion for the tag target. +Tags: backlog + Covers: - `feat~author-oft-items-and-links~1` @@ -197,6 +227,8 @@ Users can run an OFT trace using the workspace folder as the default input and read a plain-text result in the IDE. Reported IDs and defects navigate to their known source locations. +Tags: backlog + Covers: - `feat~run-oft-traces~1` @@ -208,6 +240,8 @@ Needs: scn Users can restrict a trace to selected workspace-relative paths, including source and test roots where the client can identify them. +Tags: backlog + Covers: - `feat~configure-repeatable-traces~1` @@ -219,6 +253,8 @@ Needs: scn Users can filter a trace by artifact types and OFT tags. A tag filter of `mvp` selects only MVP-tagged product artifacts. +Tags: backlog + Covers: - `feat~configure-repeatable-traces~1` @@ -230,6 +266,8 @@ Needs: scn Users can save and invoke named trace profiles with scope, paths, filters, and output mode. +Tags: backlog + Covers: - `feat~configure-repeatable-traces~1` @@ -241,6 +279,8 @@ Needs: scn Users can inspect pass/fail trace results grouped by source file, item, and incoming or outgoing link, including defect explanations and source navigation. +Tags: backlog + Covers: - `feat~inspect-structured-trace-results~1` @@ -252,6 +292,8 @@ Needs: scn Users can invoke an IDE command that opens the upstream OpenFastTrace user guide. +Tags: backlog + Covers: - `feat~access-oft-documentation~1` @@ -267,6 +309,8 @@ Needs: scn **Then** the extension highlights declarations or coverage tags in the relevant range while preserving the file's normal language support. +Tags: backlog + Covers: - `req~highlight-oft-declarations~1` - `req~highlight-coverage-tags~1` @@ -283,6 +327,8 @@ Needs: dsn Tags: mvp +Tags: backlog + Covers: - `req~start-language-server~1` @@ -295,6 +341,8 @@ Needs: dsn **When** the user searches, follows the reference, or requests implementations **Then** the IDE presents the declaration or all covering locations. +Tags: backlog + Covers: - `req~search-workspace-oft-items~1` - `req~navigate-oft-links~1` @@ -302,16 +350,31 @@ Covers: Needs: dsn +### Insert an OFT Template +`scn~insert-oft-template~1` + +**Given** a user is editing a supported specification document +**When** the user inserts an OFT template +**Then** the editor inserts the selected editable template, including the +scenario Given-When-Then skeleton and Covers placeholder where applicable. + +Tags: mvp + +Covers: +- `req~insert-oft-templates~1` + +Needs: dsn + ### Author an OFT Link -`scn~author-an-oft-link~1` +`scn~author-an-oft-link~2` **Given** a user is editing a supported document or coverage tag -**When** the user inserts a template or requests completion at a valid OFT -reference position -**Then** the editor inserts the template or offers ranked workspace IDs. +**When** the user requests completion at a valid OFT reference position +**Then** the editor offers ranked workspace IDs. + +Tags: backlog Covers: -- `req~insert-oft-templates~1` - `req~complete-covers-references~1` - `req~complete-coverage-tag-targets~1` @@ -326,6 +389,8 @@ filter **Then** the server traces only that tagged scope and the extension presents a navigable plain-text report. +Tags: backlog + Covers: - `req~trace-the-workspace~1` @@ -339,6 +404,8 @@ Needs: dsn tags, and output mode **Then** the trace uses that profile and retains it for later invocation. +Tags: backlog + Covers: - `req~select-trace-inputs~1` - `req~filter-trace-results~1` @@ -354,6 +421,8 @@ Needs: dsn **Then** the user can inspect result hierarchy, status, explanation, and source locations. +Tags: backlog + Covers: - `req~review-trace-result-tree~1` @@ -367,6 +436,8 @@ Needs: dsn **Then** the upstream guide opens in the configured external or integrated browser surface. +Tags: backlog + Covers: - `req~open-oft-user-guide~1` diff --git a/extension/package.json b/extension/package.json index d92d99e..ccc4d32 100644 --- a/extension/package.json +++ b/extension/package.json @@ -8,13 +8,24 @@ "vscode": "^1.120.0" }, "categories": [ - "Other" + "Other", + "Snippets" ], "activationEvents": [ "onStartupFinished" ], "main": "./out/extension.js", "contributes": { + "snippets": [ + { + "language": "markdown", + "path": "./snippets/openfasttrace-markdown.code-snippets" + }, + { + "language": "restructuredtext", + "path": "./snippets/openfasttrace-restructuredtext.code-snippets" + } + ], "configuration": { "title": "OpenFastTrace", "properties": { diff --git a/extension/snippets/openfasttrace-markdown.code-snippets b/extension/snippets/openfasttrace-markdown.code-snippets new file mode 100644 index 0000000..47ec377 --- /dev/null +++ b/extension/snippets/openfasttrace-markdown.code-snippets @@ -0,0 +1,47 @@ +{ + "OpenFastTrace: Architecture": { + "prefix": ["oft-architecture"], + "body": ["${1|###,####,#####,######,##|} ${2:Title}", "`arch~${2/(.*)/${1:/kebabcase}/}~1`", "", "${3:Description}", "", "Comment:", "", "${4:Comment}", "", "Covers:", "", "* `${5:covered-item}`", "", "Needs: dsn", "", "$0"], + "description": "OFT Architectural Requirement" + }, + "OpenFastTrace: Business Constraint": { + "prefix": ["oft-business-constraint"], + "body": ["${1|##,###,####,#####,######|} ${2:Title}", "`bconstr~${2/(.*)/${1:/kebabcase}/}~1`", "", "${3:Description}", "", "Rationale:", "", "${4:Rationale}", "", "Needs: req", "", "$0"], + "description": "OFT Business Constraint" + }, + "OpenFastTrace: Constraint": { + "prefix": ["oft-constraint"], + "body": ["${1|###,####,#####,######,##|} ${2:Title}", "`constr~${2/(.*)/${1:/kebabcase}/}~1`", "", "${3:Description}", "", "Rationale:", "", "${4:Rationale}", "", "Needs: dsn", "", "$0"], + "description": "OFT Technical Constraint" + }, + "OpenFastTrace: Design": { + "prefix": ["oft-design"], + "body": ["${1|###,####,#####,######,##|} ${2:Title}", "`dsn~${2/(.*)/${1:/kebabcase}/}~1`", "", "${3:Description}", "", "Covers:", "", "* `${4:covered-item}`", "", "Needs: impl, ${5|utest,itest,stest|}", "", "$0"], + "description": "OFT Design" + }, + "OpenFastTrace: Feature": { + "prefix": ["oft-feature"], + "body": ["${1|##,###,####,#####,######|} ${2:Title}", "`feat~${2/(.*)/${1:/kebabcase}/}~1`", "", "${3:Description}", "", "Rationale:", "", "${4:Rationale}", "", "Needs: req", "", "$0"], + "description": "OFT Feature" + }, + "OpenFastTrace: Quality Goal": { + "prefix": ["oft-quality-goal"], + "body": ["${1|##,###,####,#####,######|} ${2:Title}", "`qg~${2/(.*)/${1:/kebabcase}/}~1`", "", "${3:Description}", "", "Rationale:", "", "${4:Rationale}", "", "Needs: qs", "", "$0"], + "description": "OFT Quality Goal" + }, + "OpenFastTrace: Quality Scenario": { + "prefix": ["oft-quality-scenario"], + "body": ["${1|###,####,#####,######,##|} ${2:Title}", "`qs~${2/(.*)/${1:/kebabcase}/}~1`", "", "${3:Description}", "", "Covers:", "", "* `${4:covered-item}`", "", "Needs: dsn, ${5|utest,itest,stest|}", "", "$0"], + "description": "OFT Quality Scenario" + }, + "OpenFastTrace: Requirement": { + "prefix": ["oft-requirement"], + "body": ["${1|###,####,#####,######,##|} ${2:Title}", "`req~${2/(.*)/${1:/kebabcase}/}~1`", "", "${3:Description}", "", "Covers:", "", "* [`${4:covered-item}`](#${5:markdown-link})", "", "Needs: req", "", "$0"], + "description": "OFT High-Level Requirement" + }, + "OpenFastTrace: Scenario": { + "prefix": ["oft-scenario"], + "body": ["${1|###,####,#####,######,##|} ${2:Title}", "`scn~${2/(.*)/${1:/kebabcase}/}~1`", "", "**Given** ${3:the initial context}", "**When** ${4:an action occurs}", "**Then** ${5:the expected outcome occurs}", "", "Covers:", "- `${6:covered-item}`", "", "Needs: dsn", "", "$0"], + "description": "OFT Scenario" + } +} diff --git a/extension/snippets/openfasttrace-restructuredtext.code-snippets b/extension/snippets/openfasttrace-restructuredtext.code-snippets new file mode 100644 index 0000000..38b99bb --- /dev/null +++ b/extension/snippets/openfasttrace-restructuredtext.code-snippets @@ -0,0 +1,47 @@ +{ + "OpenFastTrace: Architecture": { + "prefix": ["oft-architecture"], + "body": ["${1:Title}", "${1/./=/g}", "", "``arch~${1/(.*)/${1:/kebabcase}/}~1``", "", "${2:Description}", "", "Comment:", "", "${3:Comment}", "", "Covers:", "", "* ``${4:covered-item}``", "", "Needs: dsn", "", "$0"], + "description": "OFT Architectural Requirement" + }, + "OpenFastTrace: Business Constraint": { + "prefix": ["oft-business-constraint"], + "body": ["${1:Title}", "${1/./-/g}", "", "``bconstr~${1/(.*)/${1:/kebabcase}/}~1``", "", "${2:Description}", "", "Rationale:", "", "${3:Rationale}", "", "Needs: req", "", "$0"], + "description": "OFT Business Constraint" + }, + "OpenFastTrace: Constraint": { + "prefix": ["oft-constraint"], + "body": ["${1:Title}", "${1/./=/g}", "", "``constr~${1/(.*)/${1:/kebabcase}/}~1``", "", "${2:Description}", "", "Rationale:", "", "${3:Rationale}", "", "Needs: dsn", "", "$0"], + "description": "OFT Technical Constraint" + }, + "OpenFastTrace: Design": { + "prefix": ["oft-design"], + "body": ["${1:Title}", "${1/./=/g}", "", "``dsn~${1/(.*)/${1:/kebabcase}/}~1``", "", "${2:Description}", "", "Covers:", "", "* ``${3:covered-item}``", "", "Needs: impl, ${4|utest,itest,stest|}", "", "$0"], + "description": "OFT Design" + }, + "OpenFastTrace: Feature": { + "prefix": ["oft-feature"], + "body": ["${1:Title}", "${1/./-/g}", "", "``feat~${1/(.*)/${1:/kebabcase}/}~1``", "", "${2:Description}", "", "Rationale:", "", "${3:Rationale}", "", "Needs: req", "", "$0"], + "description": "OFT Feature" + }, + "OpenFastTrace: Quality Goal": { + "prefix": ["oft-quality-goal"], + "body": ["${1:Title}", "${1/./-/g}", "", "``qg~${1/(.*)/${1:/kebabcase}/}~1``", "", "${2:Description}", "", "Rationale:", "", "${3:Rationale}", "", "Needs: qs", "", "$0"], + "description": "OFT Quality Goal" + }, + "OpenFastTrace: Quality Scenario": { + "prefix": ["oft-quality-scenario"], + "body": ["${1:Title}", "${1/./=/g}", "", "``qs~${1/(.*)/${1:/kebabcase}/}~1``", "", "${2:Description}", "", "Covers:", "", "* ``${3:covered-item}``", "", "Needs: dsn, ${4|utest,itest,stest|}", "", "$0"], + "description": "OFT Quality Scenario" + }, + "OpenFastTrace: Requirement": { + "prefix": ["oft-requirement"], + "body": ["${1:Title}", "${1/./=/g}", "", "``req~${1/(.*)/${1:/kebabcase}/}~1``", "", "${2:Description}", "", "Covers:", "", "* ``${3:covered-item}``", "", "Needs: req", "", "$0"], + "description": "OFT High-Level Requirement" + }, + "OpenFastTrace: Scenario": { + "prefix": ["oft-scenario"], + "body": ["${1:Title}", "${1/./=/g}", "", "``scn~${1/(.*)/${1:/kebabcase}/}~1``", "", "**Given** ${2:the initial context}", "**When** ${3:an action occurs}", "**Then** ${4:the expected outcome occurs}", "", "Covers:", "", "* ``${5:covered-item}``", "", "Needs: dsn", "", "$0"], + "description": "OFT Scenario" + } +} diff --git a/extension/src/extension.ts b/extension/src/extension.ts index d4a5843..d021d95 100644 --- a/extension/src/extension.ts +++ b/extension/src/extension.ts @@ -3,6 +3,7 @@ import * as vscode from 'vscode'; import { LanguageClient, LanguageClientOptions, ServerOptions } from 'vscode-languageclient/node'; import { resolveJavaExecutable } from './javaRuntime'; +// [impl->dsn~oft-template-snippets~1] let client: LanguageClient | undefined; export async function activate(context: vscode.ExtensionContext): Promise<{ getLanguageClient: typeof getLanguageClient }> { diff --git a/extension/src/test/integration/extension.test.ts b/extension/src/test/integration/extension.test.ts index 0e789de..f7e1d8d 100644 --- a/extension/src/test/integration/extension.test.ts +++ b/extension/src/test/integration/extension.test.ts @@ -1,12 +1,40 @@ import * as assert from 'assert'; import * as vscode from 'vscode'; +// [itest->dsn~oft-template-snippets~1] interface OpenFastTraceExports { getLanguageClient(): { sendRequest(method: string, parameters: { query: string }): Thenable; } | undefined; } +const commonTemplateLabels = [ + 'OpenFastTrace: Feature', + 'OpenFastTrace: Requirement', + 'OpenFastTrace: Scenario', + 'OpenFastTrace: Design', + 'OpenFastTrace: Architecture', + 'OpenFastTrace: Constraint', +]; + +const templateLabels = [ + ...commonTemplateLabels, + 'OpenFastTrace: Business Constraint', + 'OpenFastTrace: Quality Goal', + 'OpenFastTrace: Quality Scenario', +]; + +const supportedDocumentLanguages = ['markdown', 'restructuredtext']; + +interface SnippetDefinition { + body: string[]; +} + +interface SnippetContribution { + language: string; + path: string; +} + suite('OpenFastTrace extension', () => { test('starts the language server and returns an empty workspace symbol result', async () => { const extension = vscode.extensions.getExtension('itsallcode.openfasttrace'); @@ -19,4 +47,41 @@ suite('OpenFastTrace extension', () => { const symbols = await client.sendRequest('workspace/symbol', { query: '' }); assert.deepStrictEqual(symbols, []); }); + + for (const language of supportedDocumentLanguages) { + for (const templateLabel of templateLabels) { + test(`offers ${templateLabel} in ${language} documents`, async () => { + const snippets = await snippetsFor(language); + const snippet = snippets[templateLabel]; + + assert.ok(snippet, `${language} must offer ${templateLabel}`); + if (templateLabel === 'OpenFastTrace: Scenario') { + const scenario = snippet.body.join('\n'); + assert.match(scenario, /\*\*Given\*\*/); + assert.match(scenario, /\*\*When\*\*/); + assert.match(scenario, /\*\*Then\*\*/); + assert.match(scenario, /Covers:/); + } + if (language === 'markdown') { + assert.match(snippet.body.join('\n'), /\$\{1\|/); + assert.match(snippet.body.join('\n'), /\$\{2\/\(\.\*\)\/\$\{1:\/kebabcase\}\/\}/); + } + if (language === 'restructuredtext') { + assert.match(snippet.body.join('\n'), /\$\{1\/\.\/.*\/g\}/); + assert.match(snippet.body.join('\n'), /\$\{1\/\(\.\*\)\/\$\{1:\/kebabcase\}\/\}/); + } + }); + } + } }); + +async function snippetsFor(language: string): Promise> { + const extension = vscode.extensions.getExtension('itsallcode.openfasttrace'); + assert.ok(extension, 'OpenFastTrace extension must be available to the test host'); + const contributions = extension.packageJSON.contributes.snippets as SnippetContribution[]; + const contribution = contributions.find(candidate => candidate.language === language); + + assert.ok(contribution, `OpenFastTrace must contribute snippets for ${language}`); + const contents = await vscode.workspace.fs.readFile(vscode.Uri.joinPath(extension.extensionUri, contribution.path)); + return JSON.parse(new TextDecoder().decode(contents)) as Record; +}