diff --git a/javascript/Makefile b/javascript/Makefile index 1403bb31f..a6df5f96a 100644 --- a/javascript/Makefile +++ b/javascript/Makefile @@ -5,6 +5,7 @@ GHERKIN_PARSER = src/Parser.ts GHERKIN_RAZOR = gherkin-javascript.razor SOURCE_FILES = $(shell find . -name "*.js" | grep -v $(GHERKIN_PARSER)) +# HELP - is this correnct now? GHERKIN = npx gherkin-javascript GOOD_FEATURE_FILES = $(shell find ../testdata/good -name "*.feature" -o -name "*.feature.md") diff --git a/javascript/src/GherkinInMarkdownTokenMatcher.ts b/javascript/src/GherkinInMarkdownTokenMatcher.ts index 742645a0f..5b7356411 100644 --- a/javascript/src/GherkinInMarkdownTokenMatcher.ts +++ b/javascript/src/GherkinInMarkdownTokenMatcher.ts @@ -130,7 +130,13 @@ export default class GherkinInMarkdownTokenMatcher implements ITokenMatcher { + const line = new GherkinLine('# hello', location.line) + const token = new Token(line, location) + assert(tm.match_FeatureLine(token)) + assert.strictEqual(token.matchedType, TokenType.FeatureLine) + assert.strictEqual(token.matchedKeyword, undefined) + assert.strictEqual(token.matchedText, '# hello') + }) + it('matches bullet Step', () => { const line = new GherkinLine(' * Given I have 3 cukes', location.line) const token = new Token(line, location) assert(tm.match_StepLine(token)) assert.strictEqual(token.matchedType, TokenType.StepLine) assert.strictEqual(token.matchedKeyword, 'Given ') + assert.strictEqual(token.matchedKeywordType, 'Context') assert.strictEqual(token.matchedText, 'I have 3 cukes') assert.strictEqual(token.location.column, 6) }) - + it('matches plus Step', () => { const line = new GherkinLine(' + Given I have 3 cukes', location.line) const token = new Token(line, location) assert(tm.match_StepLine(token)) assert.strictEqual(token.matchedType, TokenType.StepLine) assert.strictEqual(token.matchedKeyword, 'Given ') + assert.strictEqual(token.matchedKeywordType, 'Context') assert.strictEqual(token.matchedText, 'I have 3 cukes') assert.strictEqual(token.location.column, 6) }) - + it('matches hyphen Step', () => { const line = new GherkinLine(' - Given I have 3 cukes', location.line) const token = new Token(line, location) assert(tm.match_StepLine(token)) assert.strictEqual(token.matchedType, TokenType.StepLine) assert.strictEqual(token.matchedKeyword, 'Given ') + assert.strictEqual(token.matchedKeywordType, 'Context') assert.strictEqual(token.matchedText, 'I have 3 cukes') assert.strictEqual(token.location.column, 6) }) + + it('matches a when Step', () => { + const line = new GherkinLine(' - When I do something', location.line) + const token = new Token(line, location) + assert(tm.match_StepLine(token)) + assert.strictEqual(token.matchedType, TokenType.StepLine) + assert.strictEqual(token.matchedKeyword, 'When ') + assert.strictEqual(token.matchedKeywordType, 'Action') + assert.strictEqual(token.matchedText, 'I do something') + assert.strictEqual(token.location.column, 6) + }) it('matches arbitrary text as Other', () => { const line = new GherkinLine('Whatever', location.line) @@ -186,4 +209,21 @@ describe('GherkinInMarkdownTokenMatcher', function () { ] assert.deepStrictEqual(t.matchedItems, expectedItems) }) + + it('matches arbitrary text as Empty after the FeatureLine has already been matched', () => { + // White Box testing - implementation detail... + // Given the FeatureLine has already been matched + const tFeatureLine = new Token(new GherkinLine('# something arbitrary', location.line), location); + assert(tm.match_FeatureLine(tFeatureLine)) + + + const t = new Token(new GherkinLine('arbitrary text', location.line), location); + // (tm as any).matchedFeatureLine = true + assert(tm.match_Empty(t)) + assert.strictEqual(t.matchedType, TokenType.Empty) + const expectedItems: Item[] =undefined + assert.deepStrictEqual(t.matchedItems, expectedItems) + assert.strictEqual(t.matchedKeyword, undefined) + assert.strictEqual(t.matchedText, undefined) + } ) }) diff --git a/javascript/test/ParserTest.ts b/javascript/test/ParserTest.ts index 90635320f..8e82a44b4 100644 --- a/javascript/test/ParserTest.ts +++ b/javascript/test/ParserTest.ts @@ -6,16 +6,17 @@ import GherkinClassicTokenMatcher from '../src/GherkinClassicTokenMatcher' import AstNode from '../src/AstNode' import generateMessages from '../src/generateMessages' import GherkinInMarkdownTokenMatcher from '../src/GherkinInMarkdownTokenMatcher' +import { StepKeywordType } from '@cucumber/messages' describe('Parser', function () { describe('with Gherkin Classic', () => { let parser: Parser beforeEach( () => - (parser = new Parser( - new AstBuilder(messages.IdGenerator.incrementing()), - new GherkinClassicTokenMatcher() - )) + (parser = new Parser( + new AstBuilder(messages.IdGenerator.incrementing()), + new GherkinClassicTokenMatcher() + )) ) it('parses a simple feature', function () { @@ -93,20 +94,20 @@ describe('Parser', function () { try { parser.parse( '# a comment\n' + - 'Feature: Foo\n' + - ' Scenario: Bar\n' + - ' Given x\n' + - ' ```\n' + - ' unclosed docstring\n' + 'Feature: Foo\n' + + ' Scenario: Bar\n' + + ' Given x\n' + + ' ```\n' + + ' unclosed docstring\n' ) } catch (expected) { ast = parser.parse( 'Feature: Foo\n' + - ' Scenario: Bar\n' + - ' Given x\n' + - ' """\n' + - ' closed docstring\n' + - ' """' + ' Scenario: Bar\n' + + ' Given x\n' + + ' """\n' + + ' closed docstring\n' + + ' """' ) } @@ -155,11 +156,11 @@ describe('Parser', function () { it('interpolates data tables', function () { const envelopes = generateMessages( 'Feature: Foo\n' + - ' Scenario Outline: Parenthesis\n' + - ' Given the thing and has \n' + - ' Examples:\n' + - ' | is (not) triggered | value |\n' + - ' | is triggered | foo |\n ', + ' Scenario Outline: Parenthesis\n' + + ' Given the thing and has \n' + + ' Examples:\n' + + ' | is (not) triggered | value |\n' + + ' | is triggered | foo |\n ', '', messages.SourceMediaType.TEXT_X_CUCUMBER_GHERKIN_PLAIN, { includePickles: true, newId: messages.IdGenerator.incrementing() } @@ -194,10 +195,10 @@ describe('Parser', function () { let parser: Parser beforeEach( () => - (parser = new Parser( - new AstBuilder(messages.IdGenerator.incrementing()), - new GherkinInMarkdownTokenMatcher() - )) + (parser = new Parser( + new AstBuilder(messages.IdGenerator.incrementing()), + new GherkinInMarkdownTokenMatcher() + )) ) it('does not parse a feature description', function () { @@ -308,5 +309,128 @@ description assert.strictEqual(pickle.steps[0].argument.docString.content, '```what') }) + + it("parses Markdown data tables with headers", () => { + const markdown = `## Feature: DataTables + +### Scenario: minimalistic + +* Given a simple data table + | foo | bar | + | --- | --- | + | boz | boo | +` + const ast = parser.parse(markdown) + const gherkinDocument: messages.GherkinDocument = { + "comments": [ + { + "location": { + "column": 3, + "line": 7 + }, + "text": undefined + } + ], + "feature": { + "children": [ + { + "scenario": { + "description": "", + "examples": [], + "id": "3", + "keyword": "Scenario", + "location": { + "column": 5, + "line": 3 + }, + "name": "minimalistic", + "steps": [ + { + "dataTable": { + "location": { + "column": 3, + "line": 6 + }, + "rows": [ + { + "cells": [ + { + "location": { + "column": 5, + "line": 6 + }, + "value": "foo" + }, + { + "location": { + "column": 11, + "line": 6 + }, + "value": "bar" + } + ], + "id": "0", + "location": { + "column": 3, + "line": 6 + } + }, + { + "cells": [ + { + "location": { + "column": 5, + "line": 8 + }, + "value": "boz" + }, + { + "location": { + "column": 11, + "line": 8 + }, + "value": "boo" + } + ], + "id": "1", + "location": { + "column": 3, + "line": 8 + } + } + ] + }, + "id": "2", + docString: undefined, + "keyword": "Given ", + "keywordType": StepKeywordType.CONTEXT, + "location": { + "column": 3, + "line": 5 + }, + "text": "a simple data table" + } + ], + "tags": [] + } + } + ], + "description": "", + "keyword": "Feature", + "language": "en", + "location": { + "column": 4, + "line": 1 + }, + "name": "DataTables", + "tags": [] + } + } + assert.deepStrictEqual(ast, gherkinDocument) + }) + }) + + }) + diff --git a/python/Makefile b/python/Makefile index 76db3bf94..2e2316f88 100644 --- a/python/Makefile +++ b/python/Makefile @@ -8,8 +8,8 @@ SOURCE_FILES = $(shell find . -name "*.py" | grep -v $(GHERKIN_PARSER)) GHERKIN = bin/gherkin GHERKIN_GENERATE_TOKENS = bin/gherkin-generate-tokens -GOOD_FEATURE_FILES = $(shell find ../testdata/good -name "*.feature") -BAD_FEATURE_FILES = $(shell find ../testdata/bad -name "*.feature") +GOOD_FEATURE_FILES = $(shell find ../testdata/good -name "*.feature" -o -name "*.feature.md") +BAD_FEATURE_FILES = $(shell find ../testdata/bad -name "*.feature" -o -name "*.feature.md") TOKENS = $(patsubst ../testdata/%,acceptance/testdata/%.tokens,$(GOOD_FEATURE_FILES)) ASTS = $(patsubst ../testdata/%,acceptance/testdata/%.ast.ndjson,$(GOOD_FEATURE_FILES)) diff --git a/python/bin/gherkin_generate_tokens.py b/python/bin/gherkin_generate_tokens.py index f512273f8..82e5c2250 100644 --- a/python/bin/gherkin_generate_tokens.py +++ b/python/bin/gherkin_generate_tokens.py @@ -7,12 +7,18 @@ from gherkin.token_scanner import TokenScanner from gherkin.token_formatter_builder import TokenFormatterBuilder from gherkin.parser import Parser +from gherkin.token_matcher_markdown import GherkinInMarkdownTokenMatcher files = sys.argv[1:] if sys.version_info < (3, 0) and os.name != 'nt': # for Python2 unless on Windows native UTF8Writer = codecs.getwriter('utf8') sys.stdout = UTF8Writer(sys.stdout) + parser = Parser(TokenFormatterBuilder()) for file in files: scanner = TokenScanner(file) - print(parser.parse(scanner)) + + if(file.endswith('.md')): + print(parser.parse(scanner, GherkinInMarkdownTokenMatcher()) ) + else: + print(parser.parse(scanner)) \ No newline at end of file diff --git a/python/gherkin/stream/gherkin_events.py b/python/gherkin/stream/gherkin_events.py index 23dcb15d1..9a6102c52 100644 --- a/python/gherkin/stream/gherkin_events.py +++ b/python/gherkin/stream/gherkin_events.py @@ -3,6 +3,7 @@ from gherkin.pickles.compiler import Compiler from gherkin.errors import ParserError, CompositeParserException from gherkin.stream.id_generator import IdGenerator +from gherkin.token_matcher_markdown import GherkinInMarkdownTokenMatcher def create_errors(errors, uri): for error in errors: @@ -28,7 +29,10 @@ def enum(self, source_event): source = source_event['source']['data'] try: - gherkin_document = self.parser.parse(source) + matcher=None + if(uri.endswith('.md')): + matcher=GherkinInMarkdownTokenMatcher() + gherkin_document = self.parser.parse(source, matcher) gherkin_document['uri'] = uri if (self.options.print_source): diff --git a/python/gherkin/stream/source_events.py b/python/gherkin/stream/source_events.py index 946b6bc62..c5c5e1aa5 100644 --- a/python/gherkin/stream/source_events.py +++ b/python/gherkin/stream/source_events.py @@ -5,11 +5,18 @@ def source_event(path): 'source': { 'uri': path, 'data': io.open(path, 'r', encoding='utf8', newline='').read(), - 'mediaType': 'text/x.cucumber.gherkin+plain' + 'mediaType': _media_type(path) } } return event + +def _media_type(path): + if(path.endswith(".feature")): + return 'text/x.cucumber.gherkin+plain' + if(path.endswith(".feature.md")): + return 'text/x.cucumber.gherkin+markdown' + class SourceEvents: def __init__(self, paths): self.paths = paths diff --git a/python/gherkin/token_matcher_markdown.py b/python/gherkin/token_matcher_markdown.py index 317200180..9197080c2 100644 --- a/python/gherkin/token_matcher_markdown.py +++ b/python/gherkin/token_matcher_markdown.py @@ -25,6 +25,7 @@ def match_FeatureLine(self, token): if(self.matched_feature_line): self._set_token_matched(token,None) + return False # We first try to match "# Feature: blah" result = self._match_title_line(KEYWORD_PREFIX_HEADER, self.dialect.feature_keywords, ':', token, 'FeatureLine') @@ -34,6 +35,7 @@ def match_FeatureLine(self, token): if not result: self._set_token_matched(token,'FeatureLine',token.line.get_line_text()) + result=True self.matched_feature_line=result return result @@ -79,11 +81,13 @@ def match_StepLine(self, token): return self._match_title_line(KEYWORD_PREFIX_BULLET, nonStarStepKeywords, '', token, 'StepLine') def match_Comment(self, token): + result = False if(token.line.startswith('|')): table_cells = token.line.table_cells if(self._is_gfm_table_separator(table_cells)): - return True - return self._set_token_matched(token,None,False) + result= True + self._set_token_matched(token, "Comment") + return result def match_Empty(self, token): @@ -180,14 +184,28 @@ def _match_title_line(self, prefix, keywords, keywordSuffix, token, token_type): match = re.search(u'{}({}){}(.*)'.format(prefix, keywords_or_list, keywordSuffix), token.line.get_line_text()) indent = token.line.indent result = False - + matchedKeywordType=None if(match): matchedKeyword = match.group(2) indent += len(match.group(1)) - self._set_token_matched(token, token_type, match.group(3).strip(), matchedKeyword, indent=indent) + + # only set the keyword type if this is a step keyword + if( matchedKeyword in self.keyword_types ): + matchedKeywordType = self.keyword_types[matchedKeyword][0] + + self._set_token_matched(token, token_type, match.group(3).strip(), matchedKeyword, keyword_type=matchedKeywordType, indent=indent) return True return False + def _set_token_matched2(self, token, matched, indent=None, ): + token.matched_gherkin_dialect = self.dialect_name + if indent is not None: + token.matched_indent = indent + else: + token.matched_indent = token.line.indent if token.line else 0 + token.location['column'] = token.matched_indent + 1 + return matched + def _set_token_matched(self, token, matched_type, text=None, keyword=None, keyword_type=None, indent=None, items=None): if items is None: diff --git a/python/test/gherkin_in_markdown_token_matcher_test.py b/python/test/gherkin_in_markdown_token_matcher_test.py index 82a28215b..309f1d45a 100644 --- a/python/test/gherkin_in_markdown_token_matcher_test.py +++ b/python/test/gherkin_in_markdown_token_matcher_test.py @@ -6,7 +6,16 @@ from gherkin.gherkin_line import GherkinLine location = { 'line': 1, 'column': 1 } -def test_it_matches_FeatureLine(): +def test_it_matches_FeatureLineH1(): + tm = GherkinInMarkdownTokenMatcher('en') + line = GherkinLine('''# Feature: hello''',location['line']) + token = Token(gherkin_line=line, location=location) + assert tm.match_FeatureLine(token) + assert token.matched_type == 'FeatureLine' + assert token.matched_keyword == 'Feature' + assert token.matched_text == 'hello' + +def test_it_matches_FeatureLineH2(): tm = GherkinInMarkdownTokenMatcher('en') line = GherkinLine('''## Feature: hello''',location['line']) token = Token(gherkin_line=line, location=location) @@ -24,6 +33,15 @@ def test_it_matches_FeatureLine_in_French(): assert token.matched_keyword == u'Fonctionnalité' assert token.matched_text == 'hello' +def test_it_matches_FeatureLine_without_the_Feature_keyword(): + tm = GherkinInMarkdownTokenMatcher('en') + line = GherkinLine('''# hello''',location['line']) + token = Token(gherkin_line=line, location=location) + assert tm.match_FeatureLine(token) + assert token.matched_type == 'FeatureLine' + assert token.matched_keyword == None + assert token.matched_text == '# hello' + def test_it_matches_bullet_Step(): tm = GherkinInMarkdownTokenMatcher('en') line = GherkinLine(''' * Given I have 3 cukes''',location['line']) @@ -41,6 +59,7 @@ def test_it_matches_plus_Step(): assert tm.match_StepLine(token) assert token.matched_type == 'StepLine' assert token.matched_keyword == 'Given ' + assert token.matched_keyword_type == 'Context' assert token.matched_text == 'I have 3 cukes' assert token.location['column'] == 6 @@ -51,9 +70,22 @@ def test_it_matches_hyphen_Step(): assert tm.match_StepLine(token) assert token.matched_type == 'StepLine' assert token.matched_keyword == 'Given ' + assert token.matched_keyword_type == 'Context' assert token.matched_text == 'I have 3 cukes' assert token.location['column'] == 6 +def test_it_matches_a_when_Step(): + tm = GherkinInMarkdownTokenMatcher('en') + line = GherkinLine(''' - When I do something''',location['line']) + token = Token(gherkin_line=line, location=location) + assert tm.match_StepLine(token) + assert token.matched_type == 'StepLine' + assert token.matched_keyword == 'When ' + assert token.matched_keyword_type == 'Action' + assert token.matched_text == 'I do something' + assert token.location['column'] == 6 + + def test_it_matches_arbitrary_text_as_Other(): tm = GherkinInMarkdownTokenMatcher('en') line = GherkinLine('''Whatever''',location['line']) @@ -151,11 +183,13 @@ def test_it_matches_table_separator_row_as_comment(): l1 = GherkinLine(' | h1 | h2 |',location['line']) t1 = Token(l1,location) assert tm.match_TableRow(t1) + assert t1.location['column'] == 3 l2 = GherkinLine(' | --- | --- |',location['line']) t2 = Token(l2,location) assert not tm.match_TableRow(t2) assert tm.match_Comment(t2) + assert t2.location['column'] == 3 def test_it_matches_indented_tags(): tm = GherkinInMarkdownTokenMatcher('en') @@ -229,4 +263,32 @@ def test_it_matches_ExamplesLine(): assert tm.match_ExamplesLine(token) assert token.matched_type == 'ExamplesLine' assert token.matched_keyword == 'Examples' - assert token.matched_text == '' \ No newline at end of file + assert token.matched_text == '' + +def test_it_matches_Empty(): + tm = GherkinInMarkdownTokenMatcher('en') + line = GherkinLine('''''',location['line']) + token = Token(gherkin_line=line, location=location) + assert tm.match_Empty(token) + assert token.matched_type == 'Empty' + assert token.matched_keyword == None + assert token.matched_text == None + +def test_it_matches_arbitrary_text_as_Empty_after_the_FeatureLine_has_already_been_matched(): + # White Box testing - implementation detail... + # Given the FeatureLine has already been matched + tm = GherkinInMarkdownTokenMatcher('en') + + line = GherkinLine('''# something arbitrary''',location['line']) + token = Token(gherkin_line=line, location=location) + assert(tm.match_FeatureLine(token)) + + line = GherkinLine('''arbitrary text''',location['line']) + token=Token(gherkin_line=line, location=location) + + assert(tm.match_Empty(token)) + assert token.matched_type == 'Empty' + assert token.matched_items == [] + assert token.matched_keyword == None + assert token.matched_text == None + pass \ No newline at end of file diff --git a/python/test/gherkin_test.py b/python/test/gherkin_test.py index 826f72c07..71edf9f2e 100644 --- a/python/test/gherkin_test.py +++ b/python/test/gherkin_test.py @@ -5,6 +5,8 @@ from gherkin.errors import ParserError import pytest +from gherkin.token_matcher_markdown import GherkinInMarkdownTokenMatcher + def test_parser(): parser = Parser() @@ -89,3 +91,202 @@ def test_change_the_default_language(): } assert expected == feature_file + +def test_parsing_markdown_does_not_parse_a_feature_description(): + parser = Parser() + matcher = GherkinInMarkdownTokenMatcher() + + feature_file = """# Feature: hello +This is the +description +""" + ast = parser.parse(TokenScanner(feature_file), matcher) + expected = { + 'feature': { + 'tags': [], + 'description': '', + 'location': { 'line': 1, 'column': 3 }, + 'language': 'en', + 'keyword': 'Feature', + 'name': 'hello', + 'children': [], + }, + 'comments': [], + } + assert ast == expected + +def test_parsing_markdown_parses_a_feature_without_a_hash_Feature_header(): + parser = Parser() + matcher = GherkinInMarkdownTokenMatcher() + feature_file = """# Hello +This is the +description + +## Scenario: hello ++ Given a step + +## Some other header +""" + ast = parser.parse(TokenScanner(feature_file), matcher) + expected = { + 'feature': { + 'tags': [], + 'location': { + 'line': 1, + 'column': 1, + }, + 'language': 'en', + 'name': '# Hello', + 'description': '', + 'children': [ + { + 'scenario': { + 'id': '1', + 'tags': [], + 'location': { + 'line': 5, + 'column': 4, + }, + 'keyword': 'Scenario', + 'name': 'hello', + 'description': '', + 'steps': [ + { + 'id': '0', + 'location': { + 'line': 6, + 'column': 3, + }, + 'keyword': 'Given ', + 'keywordType': "Context", + 'text': 'a step', + }, + ], + 'examples': [], + }, + }, + ], + }, + 'comments': [], + } + assert ast == expected + +def test_it_parses_markdown_data_tables_with_headers(): + parser = Parser() + matcher = GherkinInMarkdownTokenMatcher() + markdown = """## Feature: DataTables + +### Scenario: minimalistic + +* Given a simple data table + | foo | bar | + | --- | --- | + | boz | boo | +""" + ast = parser.parse(TokenScanner(markdown), matcher) + expected = { + "comments": [ + { + "location": { + "column": 3, + "line": 7 + }, + "text": None + } + ], + "feature": { + "children": [ + { + "scenario": { + "description": "", + "examples": [], + "id": "3", + "keyword": "Scenario", + "location": { + "column": 5, + "line": 3 + }, + "name": "minimalistic", + "steps": [ + { + "dataTable": { + "location": { + "column": 3, + "line": 6 + }, + "rows": [ + { + "cells": [ + { + "location": { + "column": 5, + "line": 6 + }, + "value": "foo" + }, + { + "location": { + "column": 11, + "line": 6 + }, + "value": "bar" + } + ], + "id": "0", + "location": { + "column": 3, + "line": 6 + } + }, + { + "cells": [ + { + "location": { + "column": 5, + "line": 8 + }, + "value": "boz" + }, + { + "location": { + "column": 11, + "line": 8 + }, + "value": "boo" + } + ], + "id": "1", + "location": { + "column": 3, + "line": 8 + } + } + ] + }, + "id": "2", + # "docString": None, + "keyword": "Given ", + "keywordType": "Context", + "location": { + "column": 3, + "line": 5 + }, + "text": "a simple data table" + } + ], + "tags": [] + } + } + ], + "description": "", + "keyword": "Feature", + "language": "en", + "location": { + "column": 4, + "line": 1 + }, + "name": "DataTables", + "tags": [] + } + } + assert ast == expected diff --git a/python/test/source_events_test.py b/python/test/source_events_test.py new file mode 100644 index 000000000..c0d883897 --- /dev/null +++ b/python/test/source_events_test.py @@ -0,0 +1,11 @@ +from gherkin.stream.source_events import source_event + +from os import path + +def test_should_resolve_classic_feature(): + actual = source_event(path.join(path.dirname(__file__),"..","..","testdata","good","minimal.feature")) + assert actual['source']['mediaType'] == 'text/x.cucumber.gherkin+plain' + +def test_should_resolve_markdown_feature(): + actual = source_event(path.join(path.dirname(__file__),"..","..","testdata","good","minimal.feature.md")) + assert actual['source']['mediaType'] == 'text/x.cucumber.gherkin+markdown' diff --git a/testdata/good/datatables.feature.md.ast.ndjson b/testdata/good/datatables.feature.md.ast.ndjson index ff3747d20..a5409d1f0 100644 --- a/testdata/good/datatables.feature.md.ast.ndjson +++ b/testdata/good/datatables.feature.md.ast.ndjson @@ -1 +1 @@ -{"gherkinDocument":{"comments":[],"feature":{"children":[{"scenario":{"description":"","examples":[],"id":"3","keyword":"Scenario","location":{"column":5,"line":3},"name":"minimalistic","steps":[{"dataTable":{"location":{"column":3,"line":6},"rows":[{"cells":[{"location":{"column":5,"line":6},"value":"foo"},{"location":{"column":11,"line":6},"value":"bar"}],"id":"0","location":{"column":3,"line":6}},{"cells":[{"location":{"column":5,"line":8},"value":"boz"},{"location":{"column":11,"line":8},"value":"boo"}],"id":"1","location":{"column":3,"line":8}}]},"id":"2","keyword":"Given ","keywordType":"Context","location":{"column":3,"line":5},"text":"a simple data table"}],"tags":[]}}],"description":"","keyword":"Feature","language":"en","location":{"column":4,"line":1},"name":"DataTables","tags":[]},"uri":"../testdata/good/datatables.feature.md"}} +{"gherkinDocument":{"comments":[{"location":{"column":3,"line":7},"text":null}],"feature":{"children":[{"scenario":{"description":"","examples":[],"id":"3","keyword":"Scenario","location":{"column":5,"line":3},"name":"minimalistic","steps":[{"dataTable":{"location":{"column":3,"line":6},"rows":[{"cells":[{"location":{"column":5,"line":6},"value":"foo"},{"location":{"column":11,"line":6},"value":"bar"}],"id":"0","location":{"column":3,"line":6}},{"cells":[{"location":{"column":5,"line":8},"value":"boz"},{"location":{"column":11,"line":8},"value":"boo"}],"id":"1","location":{"column":3,"line":8}}]},"id":"2","keyword":"Given ","keywordType":"Context","location":{"column":3,"line":5},"text":"a simple data table"}],"tags":[]}}],"description":"","keyword":"Feature","language":"en","location":{"column":4,"line":1},"name":"DataTables","tags":[]},"uri":"../testdata/good/datatables.feature.md"}} diff --git a/testdata/good/datatables.feature.md.tokens b/testdata/good/datatables.feature.md.tokens new file mode 100644 index 000000000..0ecbe9558 --- /dev/null +++ b/testdata/good/datatables.feature.md.tokens @@ -0,0 +1,9 @@ +(1:4)FeatureLine:()Feature/DataTables/ +(2:1)Empty:// +(3:5)ScenarioLine:()Scenario/minimalistic/ +(4:1)Empty:// +(5:3)StepLine:(Context)Given /a simple data table/ +(6:3)TableRow:()|//5:foo,11:bar +(7:3)Comment:// +(8:3)TableRow:()|//5:boz,11:boo +EOF diff --git a/testdata/good/docstrings.feature.md.tokens b/testdata/good/docstrings.feature.md.tokens new file mode 100644 index 000000000..41bce1621 --- /dev/null +++ b/testdata/good/docstrings.feature.md.tokens @@ -0,0 +1,9 @@ +(1:4)FeatureLine:()Feature/DocString variations/ +(2:1)Empty:// +(3:5)ScenarioLine:()Scenario/minimalistic/ +(4:1)Empty:// +(5:3)StepLine:(Conjunction)And /a DocString with an implicitly escaped separator inside/ +(6:1)DocStringSeparator:()````// +(7:1)Other:/```/ +(8:1)DocStringSeparator:()````// +EOF diff --git a/testdata/good/minimal.feature.md.tokens b/testdata/good/minimal.feature.md.tokens new file mode 100644 index 000000000..4d34320ee --- /dev/null +++ b/testdata/good/minimal.feature.md.tokens @@ -0,0 +1,6 @@ +(1:3)FeatureLine:()Feature/Minimal/ +(2:1)Empty:// +(3:4)ScenarioLine:()Scenario/minimalistic/ +(4:1)Empty:// +(5:6)StepLine:(Context)Given /the minimalism/ +EOF diff --git a/testdata/good/misc.feature.md.tokens b/testdata/good/misc.feature.md.tokens new file mode 100644 index 000000000..f34c19738 --- /dev/null +++ b/testdata/good/misc.feature.md.tokens @@ -0,0 +1,26 @@ +(1:1)FeatureLine:/Markdown document without "# Feature:" header/ +(2:1)Empty:// +(3:1)Empty:// +(4:1)Empty:// +(5:1)Empty:// +(6:1)Empty:// +(7:1)Empty:// +(8:1)Empty:// +(9:1)Empty:// +(10:1)Empty:// +(11:1)Empty:// +(12:3)ScenarioLine:()Scenario/Something about math/ +(13:3)StepLine:(Context)Given /step one/ +(14:3)StepLine:(Action)When /step two/ +(15:3)StepLine:(Outcome)Then /step three/ +(16:1)Empty:// +(17:3)ScenarioLine:()Scenario/Something about gravity/ +(18:4)StepLine:(Context)Given /step one/ +(19:4)StepLine:(Action)When /step two/ +(20:4)StepLine:(Outcome)Then /step three/ +(21:1)Empty:// +(22:1)Empty:// +(23:1)Empty:// +(24:1)Empty:// +(25:1)Empty:// +EOF diff --git a/testdata/good/tags.feature.md.ast.ndjson b/testdata/good/tags.feature.md.ast.ndjson index 26df9f842..75837dc9e 100644 --- a/testdata/good/tags.feature.md.ast.ndjson +++ b/testdata/good/tags.feature.md.ast.ndjson @@ -1 +1 @@ -{"gherkinDocument":{"comments":[],"feature":{"children":[{"scenario":{"description":"","examples":[],"id":"4","keyword":"Scenario","location":{"column":4,"line":7},"name":"minimalistic","steps":[{"id":"0","keyword":"Given ","keywordType":"Context","location":{"column":3,"line":8},"text":"the minimalism"}],"tags":[{"id":"1","location":{"column":2,"line":5},"name":"@scenario_tag1"},{"id":"2","location":{"column":19,"line":5},"name":"@scenario_tag2"},{"id":"3","location":{"column":4,"line":6},"name":"@scenario_tag3"}]}},{"scenario":{"description":"","examples":[{"description":"","id":"11","keyword":"Examples","location":{"column":5,"line":17},"name":"","tableBody":[{"cells":[{"location":{"column":5,"line":20},"value":"minimalism"}],"id":"7","location":{"column":3,"line":20}}],"tableHeader":{"cells":[{"location":{"column":5,"line":18},"value":"what"}],"id":"6","location":{"column":3,"line":18}},"tags":[{"id":"8","location":{"column":2,"line":15},"name":"@ex_tag1"},{"id":"9","location":{"column":13,"line":15},"name":"@ex_tag2"},{"id":"10","location":{"column":4,"line":16},"name":"@ex_tag3"}]},{"description":"","id":"17","keyword":"Examples","location":{"column":5,"line":24},"name":"","tableBody":[{"cells":[{"location":{"column":5,"line":27},"value":"more minimalism"}],"id":"13","location":{"column":3,"line":27}}],"tableHeader":{"cells":[{"location":{"column":5,"line":25},"value":"what"}],"id":"12","location":{"column":3,"line":25}},"tags":[{"id":"14","location":{"column":2,"line":22},"name":"@ex_tag4"},{"id":"15","location":{"column":13,"line":22},"name":"@ex_tag5"},{"id":"16","location":{"column":4,"line":23},"name":"@ex_tag6"}]}],"id":"21","keyword":"Scenario Outline","location":{"column":4,"line":12},"name":"minimalistic outline","steps":[{"id":"5","keyword":"Given ","keywordType":"Context","location":{"column":3,"line":13},"text":"the "}],"tags":[{"id":"18","location":{"column":2,"line":10},"name":"@so_tag1"},{"id":"19","location":{"column":14,"line":10},"name":"@so_tag2"},{"id":"20","location":{"column":4,"line":11},"name":"@so_tag3"}]}},{"scenario":{"description":"","examples":[],"id":"23","keyword":"Scenario","location":{"column":4,"line":30},"name":"comments","steps":[],"tags":[{"id":"22","location":{"column":2,"line":29},"name":"@comment_tag1"}]}},{"scenario":{"description":"","examples":[],"id":"25","keyword":"Scenario","location":{"column":4,"line":34},"name":"hash in tags","steps":[],"tags":[{"id":"24","location":{"column":2,"line":33},"name":"@comment_tag#2"}]}},{"rule":{"children":[{"scenario":{"description":"","examples":[],"id":"28","keyword":"Scenario","location":{"column":5,"line":41},"name":"joined tags","steps":[],"tags":[{"id":"26","location":{"column":2,"line":40},"name":"@joined_tag3"},{"id":"27","location":{"column":16,"line":40},"name":"@joined_tag4"}]}}],"description":"","id":"30","keyword":"Rule","location":{"column":4,"line":38},"name":"","tags":[{"id":"29","location":{"column":2,"line":37},"name":"@rule_tag"}]}}],"description":"","keyword":"Feature","language":"en","location":{"column":3,"line":3},"name":"Minimal Scenario Outline","tags":[{"id":"31","location":{"column":2,"line":1},"name":"@feature_tag1"},{"id":"32","location":{"column":18,"line":1},"name":"@feature_tag2"},{"id":"33","location":{"column":4,"line":2},"name":"@feature_tag3"}]},"uri":"../testdata/good/tags.feature.md"}} +{"gherkinDocument":{"comments":[{"location":{"column":3,"line":19},"text":null},{"location":{"column":3,"line":26},"text":null}],"feature":{"children":[{"scenario":{"description":"","examples":[],"id":"4","keyword":"Scenario","location":{"column":4,"line":7},"name":"minimalistic","steps":[{"id":"0","keyword":"Given ","keywordType":"Context","location":{"column":3,"line":8},"text":"the minimalism"}],"tags":[{"id":"1","location":{"column":2,"line":5},"name":"@scenario_tag1"},{"id":"2","location":{"column":19,"line":5},"name":"@scenario_tag2"},{"id":"3","location":{"column":4,"line":6},"name":"@scenario_tag3"}]}},{"scenario":{"description":"","examples":[{"description":"","id":"11","keyword":"Examples","location":{"column":5,"line":17},"name":"","tableBody":[{"cells":[{"location":{"column":5,"line":20},"value":"minimalism"}],"id":"7","location":{"column":3,"line":20}}],"tableHeader":{"cells":[{"location":{"column":5,"line":18},"value":"what"}],"id":"6","location":{"column":3,"line":18}},"tags":[{"id":"8","location":{"column":2,"line":15},"name":"@ex_tag1"},{"id":"9","location":{"column":13,"line":15},"name":"@ex_tag2"},{"id":"10","location":{"column":4,"line":16},"name":"@ex_tag3"}]},{"description":"","id":"17","keyword":"Examples","location":{"column":5,"line":24},"name":"","tableBody":[{"cells":[{"location":{"column":5,"line":27},"value":"more minimalism"}],"id":"13","location":{"column":3,"line":27}}],"tableHeader":{"cells":[{"location":{"column":5,"line":25},"value":"what"}],"id":"12","location":{"column":3,"line":25}},"tags":[{"id":"14","location":{"column":2,"line":22},"name":"@ex_tag4"},{"id":"15","location":{"column":13,"line":22},"name":"@ex_tag5"},{"id":"16","location":{"column":4,"line":23},"name":"@ex_tag6"}]}],"id":"21","keyword":"Scenario Outline","location":{"column":4,"line":12},"name":"minimalistic outline","steps":[{"id":"5","keyword":"Given ","keywordType":"Context","location":{"column":3,"line":13},"text":"the "}],"tags":[{"id":"18","location":{"column":2,"line":10},"name":"@so_tag1"},{"id":"19","location":{"column":14,"line":10},"name":"@so_tag2"},{"id":"20","location":{"column":4,"line":11},"name":"@so_tag3"}]}},{"scenario":{"description":"","examples":[],"id":"23","keyword":"Scenario","location":{"column":4,"line":30},"name":"comments","steps":[],"tags":[{"id":"22","location":{"column":2,"line":29},"name":"@comment_tag1"}]}},{"scenario":{"description":"","examples":[],"id":"25","keyword":"Scenario","location":{"column":4,"line":34},"name":"hash in tags","steps":[],"tags":[{"id":"24","location":{"column":2,"line":33},"name":"@comment_tag#2"}]}},{"rule":{"children":[{"scenario":{"description":"","examples":[],"id":"28","keyword":"Scenario","location":{"column":5,"line":41},"name":"joined tags","steps":[],"tags":[{"id":"26","location":{"column":2,"line":40},"name":"@joined_tag3"},{"id":"27","location":{"column":16,"line":40},"name":"@joined_tag4"}]}}],"description":"","id":"30","keyword":"Rule","location":{"column":4,"line":38},"name":"","tags":[{"id":"29","location":{"column":2,"line":37},"name":"@rule_tag"}]}}],"description":"","keyword":"Feature","language":"en","location":{"column":3,"line":3},"name":"Minimal Scenario Outline","tags":[{"id":"31","location":{"column":2,"line":1},"name":"@feature_tag1"},{"id":"32","location":{"column":18,"line":1},"name":"@feature_tag2"},{"id":"33","location":{"column":4,"line":2},"name":"@feature_tag3"}]},"uri":"../testdata/good/tags.feature.md"}} diff --git a/testdata/good/tags.feature.md.tokens b/testdata/good/tags.feature.md.tokens new file mode 100644 index 000000000..60bd55c4b --- /dev/null +++ b/testdata/good/tags.feature.md.tokens @@ -0,0 +1,43 @@ +(1:1)TagLine://2:@feature_tag1,18:@feature_tag2 +(2:3)TagLine://4:@feature_tag3 +(3:3)FeatureLine:()Feature/Minimal Scenario Outline/ +(4:1)Empty:// +(5:1)TagLine://2:@scenario_tag1,19:@scenario_tag2 +(6:3)TagLine://4:@scenario_tag3 +(7:4)ScenarioLine:()Scenario/minimalistic/ +(8:3)StepLine:(Context)Given /the minimalism/ +(9:1)Empty:// +(10:1)TagLine://2:@so_tag1,14:@so_tag2 +(11:3)TagLine://4:@so_tag3 +(12:4)ScenarioLine:()Scenario Outline/minimalistic outline/ +(13:3)StepLine:(Context)Given /the / +(14:1)Empty:// +(15:1)TagLine://2:@ex_tag1,13:@ex_tag2 +(16:3)TagLine://4:@ex_tag3 +(17:5)ExamplesLine:()Examples// +(18:3)TableRow:()|//5:what +(19:3)Comment:// +(20:3)TableRow:()|//5:minimalism +(21:1)Empty:// +(22:1)TagLine://2:@ex_tag4,13:@ex_tag5 +(23:3)TagLine://4:@ex_tag6 +(24:5)ExamplesLine:()Examples// +(25:3)TableRow:()|//5:what +(26:3)Comment:// +(27:3)TableRow:()|//5:more minimalism +(28:1)Empty:// +(29:1)TagLine://2:@comment_tag1 +(30:4)ScenarioLine:()Scenario/comments/ +(31:1)Empty:// +(32:1)Empty:// +(33:1)TagLine://2:@comment_tag#2 +(34:4)ScenarioLine:()Scenario/hash in tags/ +(35:1)Empty:// +(36:1)Empty:// +(37:1)TagLine://2:@rule_tag +(38:4)RuleLine:()Rule// +(39:1)Empty:// +(40:1)TagLine://2:@joined_tag3,16:@joined_tag4 +(41:5)ScenarioLine:()Scenario/joined tags/ +(42:1)Empty:// +EOF