gherkin 2.2.5-x86-mswin32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (132) hide show
  1. data/.gitattributes +2 -0
  2. data/.gitignore +11 -0
  3. data/.mailmap +2 -0
  4. data/.rspec +1 -0
  5. data/.rvmrc +1 -0
  6. data/Gemfile +5 -0
  7. data/History.txt +306 -0
  8. data/LICENSE +20 -0
  9. data/README.rdoc +59 -0
  10. data/Rakefile +16 -0
  11. data/VERSION +1 -0
  12. data/bin/gherkin +5 -0
  13. data/build_native_gems.sh +8 -0
  14. data/cucumber.yml +3 -0
  15. data/features/escaped_pipes.feature +8 -0
  16. data/features/feature_parser.feature +237 -0
  17. data/features/json_formatter.feature +278 -0
  18. data/features/json_parser.feature +318 -0
  19. data/features/native_lexer.feature +19 -0
  20. data/features/parser_with_native_lexer.feature +205 -0
  21. data/features/pretty_formatter.feature +15 -0
  22. data/features/step_definitions/eyeball_steps.rb +3 -0
  23. data/features/step_definitions/gherkin_steps.rb +29 -0
  24. data/features/step_definitions/json_formatter_steps.rb +28 -0
  25. data/features/step_definitions/json_parser_steps.rb +20 -0
  26. data/features/step_definitions/pretty_formatter_steps.rb +82 -0
  27. data/features/steps_parser.feature +46 -0
  28. data/features/support/env.rb +38 -0
  29. data/gherkin.gemspec +59 -0
  30. data/ikvm/.gitignore +3 -0
  31. data/java/.gitignore +2 -0
  32. data/java/src/main/java/gherkin/lexer/i18n/.gitignore +1 -0
  33. data/java/src/main/resources/gherkin/.gitignore +1 -0
  34. data/lib/.gitignore +4 -0
  35. data/lib/gherkin.rb +2 -0
  36. data/lib/gherkin/c_lexer.rb +17 -0
  37. data/lib/gherkin/cli/main.rb +33 -0
  38. data/lib/gherkin/formatter/argument.rb +28 -0
  39. data/lib/gherkin/formatter/colors.rb +119 -0
  40. data/lib/gherkin/formatter/escaping.rb +15 -0
  41. data/lib/gherkin/formatter/filter_formatter.rb +136 -0
  42. data/lib/gherkin/formatter/json_formatter.rb +72 -0
  43. data/lib/gherkin/formatter/line_filter.rb +26 -0
  44. data/lib/gherkin/formatter/model.rb +231 -0
  45. data/lib/gherkin/formatter/monochrome_format.rb +9 -0
  46. data/lib/gherkin/formatter/pretty_formatter.rb +174 -0
  47. data/lib/gherkin/formatter/regexp_filter.rb +21 -0
  48. data/lib/gherkin/formatter/tag_count_formatter.rb +47 -0
  49. data/lib/gherkin/formatter/tag_filter.rb +19 -0
  50. data/lib/gherkin/i18n.rb +180 -0
  51. data/lib/gherkin/i18n.yml +601 -0
  52. data/lib/gherkin/json_parser.rb +88 -0
  53. data/lib/gherkin/lexer/i18n_lexer.rb +47 -0
  54. data/lib/gherkin/listener/event.rb +45 -0
  55. data/lib/gherkin/listener/formatter_listener.rb +113 -0
  56. data/lib/gherkin/native.rb +7 -0
  57. data/lib/gherkin/native/ikvm.rb +55 -0
  58. data/lib/gherkin/native/java.rb +55 -0
  59. data/lib/gherkin/native/null.rb +9 -0
  60. data/lib/gherkin/parser/meta.txt +5 -0
  61. data/lib/gherkin/parser/parser.rb +164 -0
  62. data/lib/gherkin/parser/root.txt +11 -0
  63. data/lib/gherkin/parser/steps.txt +4 -0
  64. data/lib/gherkin/rb_lexer.rb +8 -0
  65. data/lib/gherkin/rb_lexer/.gitignore +1 -0
  66. data/lib/gherkin/rb_lexer/README.rdoc +8 -0
  67. data/lib/gherkin/rubify.rb +24 -0
  68. data/lib/gherkin/tag_expression.rb +62 -0
  69. data/lib/gherkin/tools.rb +8 -0
  70. data/lib/gherkin/tools/files.rb +34 -0
  71. data/lib/gherkin/tools/reformat.rb +20 -0
  72. data/lib/gherkin/tools/stats.rb +20 -0
  73. data/lib/gherkin/tools/stats_listener.rb +60 -0
  74. data/lib/gherkin/version.rb +3 -0
  75. data/ragel/i18n/.gitignore +1 -0
  76. data/ragel/lexer.c.rl.erb +459 -0
  77. data/ragel/lexer.java.rl.erb +224 -0
  78. data/ragel/lexer.rb.rl.erb +179 -0
  79. data/ragel/lexer_common.rl.erb +50 -0
  80. data/spec/gherkin/c_lexer_spec.rb +21 -0
  81. data/spec/gherkin/fixtures/1.feature +8 -0
  82. data/spec/gherkin/fixtures/comments_in_table.feature +9 -0
  83. data/spec/gherkin/fixtures/complex.feature +45 -0
  84. data/spec/gherkin/fixtures/complex.json +143 -0
  85. data/spec/gherkin/fixtures/complex_for_filtering.feature +60 -0
  86. data/spec/gherkin/fixtures/complex_with_tags.feature +61 -0
  87. data/spec/gherkin/fixtures/dos_line_endings.feature +45 -0
  88. data/spec/gherkin/fixtures/hantu_pisang.feature +35 -0
  89. data/spec/gherkin/fixtures/i18n_fr.feature +14 -0
  90. data/spec/gherkin/fixtures/i18n_no.feature +7 -0
  91. data/spec/gherkin/fixtures/i18n_zh-CN.feature +9 -0
  92. data/spec/gherkin/fixtures/scenario_outline_with_tags.feature +13 -0
  93. data/spec/gherkin/fixtures/scenario_without_steps.feature +5 -0
  94. data/spec/gherkin/fixtures/simple_with_comments.feature +7 -0
  95. data/spec/gherkin/fixtures/simple_with_tags.feature +11 -0
  96. data/spec/gherkin/fixtures/with_bom.feature +3 -0
  97. data/spec/gherkin/formatter/argument_spec.rb +28 -0
  98. data/spec/gherkin/formatter/colors_spec.rb +18 -0
  99. data/spec/gherkin/formatter/filter_formatter_spec.rb +165 -0
  100. data/spec/gherkin/formatter/model_spec.rb +15 -0
  101. data/spec/gherkin/formatter/pretty_formatter_spec.rb +140 -0
  102. data/spec/gherkin/formatter/spaces.feature +9 -0
  103. data/spec/gherkin/formatter/tabs.feature +9 -0
  104. data/spec/gherkin/formatter/tag_count_formatter_spec.rb +30 -0
  105. data/spec/gherkin/i18n_spec.rb +149 -0
  106. data/spec/gherkin/java_lexer_spec.rb +20 -0
  107. data/spec/gherkin/json.rb +5 -0
  108. data/spec/gherkin/json_parser_spec.rb +67 -0
  109. data/spec/gherkin/lexer/i18n_lexer_spec.rb +43 -0
  110. data/spec/gherkin/output_stream_string_io.rb +24 -0
  111. data/spec/gherkin/parser/parser_spec.rb +16 -0
  112. data/spec/gherkin/rb_lexer_spec.rb +19 -0
  113. data/spec/gherkin/sexp_recorder.rb +56 -0
  114. data/spec/gherkin/shared/lexer_group.rb +592 -0
  115. data/spec/gherkin/shared/py_string_group.rb +153 -0
  116. data/spec/gherkin/shared/row_group.rb +120 -0
  117. data/spec/gherkin/shared/tags_group.rb +54 -0
  118. data/spec/gherkin/tag_expression_spec.rb +137 -0
  119. data/spec/spec_helper.rb +68 -0
  120. data/tasks/bench.rake +184 -0
  121. data/tasks/bench/feature_builder.rb +49 -0
  122. data/tasks/bench/generated/.gitignore +1 -0
  123. data/tasks/bench/null_listener.rb +4 -0
  124. data/tasks/compile.rake +102 -0
  125. data/tasks/cucumber.rake +18 -0
  126. data/tasks/gems.rake +42 -0
  127. data/tasks/ikvm.rake +54 -0
  128. data/tasks/ragel_task.rb +70 -0
  129. data/tasks/rdoc.rake +9 -0
  130. data/tasks/release.rake +30 -0
  131. data/tasks/rspec.rake +8 -0
  132. metadata +447 -0
@@ -0,0 +1,153 @@
1
+ # encoding: utf-8
2
+ require 'spec_helper'
3
+
4
+ module Gherkin
5
+ module Lexer
6
+ shared_examples_for "a Gherkin lexer lexing py_strings" do
7
+ def scan(gherkin)
8
+ @lexer.scan(gherkin)
9
+ end
10
+
11
+ def ps(content)
12
+ '"""%s"""' % ("\n" + content + "\n")
13
+ end
14
+
15
+ it "should provide the amount of indentation of the triple quotes to the listener" do
16
+ str = <<EOS
17
+ Feature: some feature
18
+ Scenario: some scenario
19
+ Given foo
20
+ """
21
+ Hello
22
+ Goodbye
23
+ """
24
+ Then bar
25
+ EOS
26
+ @listener.should_receive(:py_string).with(" Hello\nGoodbye", 4)
27
+ scan(str)
28
+ end
29
+
30
+ it "should parse a simple py_string" do
31
+ @listener.should_receive(:py_string).with("I am a py_string", 1)
32
+ scan ps("I am a py_string")
33
+ end
34
+
35
+ it "should parse an empty py_string" do
36
+ @listener.should_receive(:py_string).with("", 4)
37
+ scan("Feature: Hi\nScenario: Hi\nGiven a step\n\"\"\"\n\"\"\"")
38
+ end
39
+
40
+ it "should treat a string containing only newlines as only newlines" do
41
+ py_string = <<EOS
42
+ """
43
+
44
+
45
+
46
+ """
47
+ EOS
48
+ @listener.should_receive(:py_string).with("\n\n", 1)
49
+ scan(py_string)
50
+ end
51
+
52
+ it "should parse content separated by two newlines" do
53
+ scan ps("A\n\nB")
54
+ @listener.to_sexp.should == [
55
+ [:py_string, "A\n\nB", 1],
56
+ [:eof]
57
+ ]
58
+ end
59
+
60
+ it "should parse a multiline string" do
61
+ @listener.should_receive(:py_string).with("A\nB\nC\nD", 1)
62
+ scan ps("A\nB\nC\nD")
63
+ end
64
+
65
+ it "should ignore unescaped quotes inside the string delimeters" do
66
+ @listener.should_receive(:py_string).with("What does \"this\" mean?", 1)
67
+ scan ps('What does "this" mean?')
68
+ end
69
+
70
+ it "should preserve whitespace within the triple quotes" do
71
+ str = <<EOS
72
+ """
73
+ Line one
74
+ Line two
75
+ """
76
+ EOS
77
+ @listener.should_receive(:py_string).with(" Line one\nLine two", 1)
78
+ scan(str)
79
+ end
80
+
81
+ it "should preserve tabs within the content" do
82
+ @listener.should_receive(:py_string).with("I have\tsome tabs\nInside\t\tthe content", 1)
83
+ scan ps("I have\tsome tabs\nInside\t\tthe content")
84
+ end
85
+
86
+ it "should handle complex py_strings" do
87
+ py_string = <<EOS
88
+ # Feature comment
89
+ @one
90
+ Feature: Sample
91
+
92
+ @two @three
93
+ Scenario: Missing
94
+ Given missing
95
+
96
+ 1 scenario (1 passed)
97
+ 1 step (1 passed)
98
+
99
+ EOS
100
+
101
+ @listener.should_receive(:py_string).with(py_string, 1)
102
+ scan ps(py_string)
103
+ end
104
+
105
+ it "should allow whitespace after the closing py_string delimiter" do
106
+ str = <<EOS
107
+ """
108
+ Line one
109
+ """
110
+ EOS
111
+ @listener.should_receive(:py_string).with(" Line one", 1)
112
+ scan(str)
113
+ end
114
+
115
+ it "should preserve the last newline(s) at the end of a py_string" do
116
+ str = <<EOS
117
+ """
118
+ PyString text
119
+
120
+
121
+ """
122
+ EOS
123
+ @listener.should_receive(:py_string).with("PyString text\n\n",1)
124
+ scan(str)
125
+ end
126
+
127
+ it "should preserve CRLFs within py_strings" do
128
+ @listener.should_receive(:py_string).with("Line one\r\nLine two\r\n", 1)
129
+ scan("\"\"\"\r\nLine one\r\nLine two\r\n\r\n\"\"\"")
130
+ end
131
+
132
+ it "should unescape escaped triple quotes" do
133
+ str = <<EOS
134
+ """
135
+ \\"\\"\\"
136
+ """
137
+ EOS
138
+ @listener.should_receive(:py_string).with('"""', 1)
139
+ scan(str)
140
+ end
141
+
142
+ it "should not unescape escaped single quotes" do
143
+ str = <<EOS
144
+ """
145
+ \\" \\"\\"
146
+ """
147
+ EOS
148
+ @listener.should_receive(:py_string).with('\" \"\"', 1)
149
+ scan(str)
150
+ end
151
+ end
152
+ end
153
+ end
@@ -0,0 +1,120 @@
1
+ # encoding: utf-8
2
+ require 'spec_helper'
3
+
4
+ module Gherkin
5
+ module Lexer
6
+ shared_examples_for "a Gherkin lexer lexing rows" do
7
+ def scan(gherkin)
8
+ @lexer.scan(gherkin)
9
+ end
10
+
11
+ rows = {
12
+ "|a|b|\n" => %w{a b},
13
+ "|a|b|c|\n" => %w{a b c},
14
+ }
15
+
16
+ rows.each do |text, expected|
17
+ it "should parse #{text}" do
18
+ @listener.should_receive(:row).with(r(expected), 1)
19
+ scan(text.dup)
20
+ end
21
+ end
22
+
23
+ it "should parse a row with many cells" do
24
+ @listener.should_receive(:row).with(r(%w{a b c d e f g h i j k l m n o p}), 1)
25
+ scan("|a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|\n")
26
+ end
27
+
28
+ it "should parse multicharacter cell content" do
29
+ @listener.should_receive(:row).with(r(%w{foo bar}), 1)
30
+ scan("| foo | bar |\n")
31
+ end
32
+
33
+ it "should escape backslashed pipes" do
34
+ @listener.should_receive(:row).with(r(['|', 'the', '\a', '\\', '|\\|']), 1)
35
+ scan('| \| | the | \a | \\ | \|\\\| |' + "\n")
36
+ end
37
+
38
+ it "should parse cells with spaces within the content" do
39
+ @listener.should_receive(:row).with(r(["Dill pickle", "Valencia orange"]), 1)
40
+ scan("| Dill pickle | Valencia orange |\n")
41
+ end
42
+
43
+ it "should allow utf-8" do
44
+ scan(" | ůﻚ | 2 | \n")
45
+ @listener.to_sexp.should == [
46
+ [:row, ["ůﻚ", "2"], 1],
47
+ [:eof]
48
+ ]
49
+ end
50
+
51
+ it "should allow utf-8 using should_receive" do
52
+ @listener.should_receive(:row).with(r(['繁體中文 而且','並且','繁體中文 而且','並且']), 1)
53
+ scan("| 繁體中文 而且|並且| 繁體中文 而且|並且|\n")
54
+ end
55
+
56
+ it "should parse a 2x2 table" do
57
+ @listener.should_receive(:row).with(r(%w{1 2}), 1)
58
+ @listener.should_receive(:row).with(r(%w{3 4}), 2)
59
+ scan("| 1 | 2 |\n| 3 | 4 |\n")
60
+ end
61
+
62
+ it "should parse a 2x2 table with empty cells" do
63
+ @listener.should_receive(:row).with(r(['1', '']), 1)
64
+ @listener.should_receive(:row).with(r(['', '4']), 2)
65
+ scan("| 1 | |\n|| 4 |\n")
66
+ end
67
+
68
+ it "should parse a row with empty cells" do
69
+ @listener.should_receive(:row).with(r(['1', '']), 1).twice
70
+ scan("| 1 | |\n")
71
+ scan("|1||\n")
72
+ end
73
+
74
+ it "should parse a 1x2 table that does not end in a newline" do
75
+ @listener.should_receive(:row).with(r(%w{1 2}), 1)
76
+ scan("| 1 | 2 |")
77
+ end
78
+
79
+ it "should parse a row without spaces and with a newline" do
80
+ @listener.should_receive(:row).with(r(%w{1 2}), 1)
81
+ scan("|1|2|\n")
82
+ end
83
+
84
+ it "should parse a row with whitespace after the rows" do
85
+ @listener.should_receive(:row).with(r(%w{1 2}), 1)
86
+ scan("| 1 | 2 | \n ")
87
+ end
88
+
89
+ it "should parse a row with lots of whitespace" do
90
+ @listener.should_receive(:row).with(r(["abc", "123"]), 1)
91
+ scan(" \t| \t abc\t| \t123\t \t\t| \t\t \t \t\n ")
92
+ end
93
+
94
+ it "should parse a table with a commented-out row" do
95
+ @listener.should_receive(:row).with(r(["abc"]), 1)
96
+ @listener.should_receive(:comment).with("#|123|", 2)
97
+ @listener.should_receive(:row).with(r(["def"]), 3)
98
+ scan("|abc|\n#|123|\n|def|\n")
99
+ end
100
+
101
+ it "should raise LexingError for rows that aren't closed" do
102
+ lambda {
103
+ scan("|| oh hello \n")
104
+ }.should raise_error(/Lexing error on line 1: '\|\| oh hello/)
105
+ end
106
+
107
+ it "should raise LexingError for rows that are followed by a comment" do
108
+ lambda {
109
+ scan("|hi| # oh hello \n")
110
+ }.should raise_error(/Lexing error on line 1: '\|hi\| # oh hello/)
111
+ end
112
+
113
+ it "should raise LexingError for rows that aren't closed" do
114
+ lambda {
115
+ scan("|| oh hello \n |Shoudn't Get|Here|")
116
+ }.should raise_error(/Lexing error on line 1: '\|\| oh hello/)
117
+ end
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,54 @@
1
+ # encoding: utf-8
2
+ require 'spec_helper'
3
+
4
+ module Gherkin
5
+ module Lexer
6
+ shared_examples_for "a Gherkin lexer lexing tags" do
7
+ def scan(gherkin)
8
+ @lexer.scan(gherkin)
9
+ end
10
+
11
+ it "should lex a single tag" do
12
+ @listener.should_receive(:tag).with("@dog", 1)
13
+ scan("@dog\n")
14
+ end
15
+
16
+ it "should lex multiple tags" do
17
+ @listener.should_receive(:tag).twice
18
+ scan("@dog @cat\n")
19
+ end
20
+
21
+ it "should lex UTF-8 tags" do
22
+ @listener.should_receive(:tag).with("@シナリオテンプレート", 1)
23
+ scan("@シナリオテンプレート\n")
24
+ end
25
+
26
+ it "should lex mixed tags" do
27
+ @listener.should_receive(:tag).with("@wip", 1).ordered
28
+ @listener.should_receive(:tag).with("@Значения", 1).ordered
29
+ scan("@wip @Значения\n")
30
+ end
31
+
32
+ it "should lex wacky identifiers" do
33
+ @listener.should_receive(:tag).exactly(4).times
34
+ scan("@BJ-x98.77 @BJ-z12.33 @O_o" "@#not_a_comment\n")
35
+ end
36
+
37
+ # TODO: Ask on ML for opinions about this one
38
+ it "should lex tags without spaces between them?" do
39
+ @listener.should_receive(:tag).twice
40
+ scan("@one@two\n")
41
+ end
42
+
43
+ it "should not lex tags beginning with two @@ signs" do
44
+ @listener.should_not_receive(:tag)
45
+ lambda { scan("@@test\n") }.should raise_error(/Lexing error on line 1/)
46
+ end
47
+
48
+ it "should not lex a lone @ sign" do
49
+ @listener.should_not_receive(:tag)
50
+ lambda { scan("@\n") }.should raise_error(/Lexing error on line 1/)
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,137 @@
1
+ require 'spec_helper'
2
+ require 'gherkin/tag_expression'
3
+
4
+ module Gherkin
5
+ describe TagExpression do
6
+ context "no tags" do
7
+ before(:each) do
8
+ @e = Gherkin::TagExpression.new([])
9
+ end
10
+
11
+ it "should match @foo" do
12
+ @e.eval(['@foo']).should == true
13
+ end
14
+
15
+ it "should match empty tags" do
16
+ @e.eval([]).should == true
17
+ end
18
+ end
19
+
20
+ context "@foo" do
21
+ before(:each) do
22
+ @e = Gherkin::TagExpression.new(['@foo'])
23
+ end
24
+
25
+ it "should match @foo" do
26
+ @e.eval(['@foo']).should == true
27
+ end
28
+
29
+ it "should not match @bar" do
30
+ @e.eval(['@bar']).should == false
31
+ end
32
+
33
+ it "should not match no tags" do
34
+ @e.eval([]).should == false
35
+ end
36
+ end
37
+
38
+ context "!@foo" do
39
+ before(:each) do
40
+ @e = Gherkin::TagExpression.new(['~@foo'])
41
+ end
42
+
43
+ it "should match @bar" do
44
+ @e.eval(['@bar']).should == true
45
+ end
46
+
47
+ it "should not match @foo" do
48
+ @e.eval(['@foo']).should == false
49
+ end
50
+ end
51
+
52
+ context "@foo || @bar" do
53
+ before(:each) do
54
+ @e = Gherkin::TagExpression.new(['@foo,@bar'])
55
+ end
56
+
57
+ it "should match @foo" do
58
+ @e.eval(['@foo']).should == true
59
+ end
60
+
61
+ it "should match @bar" do
62
+ @e.eval(['@bar']).should == true
63
+ end
64
+
65
+ it "should not match @zap" do
66
+ @e.eval(['@zap']).should == false
67
+ end
68
+ end
69
+
70
+ context "(@foo || @bar) && !@zap" do
71
+ before(:each) do
72
+ @e = Gherkin::TagExpression.new(['@foo,@bar', '~@zap'])
73
+ end
74
+
75
+ it "should match @foo" do
76
+ @e.eval(['@foo']).should == true
77
+ end
78
+
79
+ it "should not match @foo @zap" do
80
+ @e.eval(['@foo', '@zap']).should == false
81
+ end
82
+ end
83
+
84
+ context "(@foo:3 || !@bar:4) && @zap:5" do
85
+ before(:each) do
86
+ @e = Gherkin::TagExpression.new(['@foo:3,~@bar','@zap:5'])
87
+ end
88
+
89
+ it "should count tags for positive tags" do
90
+ rubify_hash(@e.limits).should == {'@foo' => 3, '@zap' => 5}
91
+ end
92
+
93
+ it "should match @foo @zap" do
94
+ @e.eval(['@foo', '@zap']).should == true
95
+ end
96
+ end
97
+
98
+ context "Parsing '@foo:3,~@bar', '@zap:5'" do
99
+ before(:each) do
100
+ @e = Gherkin::TagExpression.new([' @foo:3 , ~@bar ', ' @zap:5 '])
101
+ end
102
+
103
+ unless defined?(JRUBY_VERSION)
104
+ it "should split and trim (ruby implementation detail)" do
105
+ @e.__send__(:ruby_expression).should == "(!vars['@bar']||vars['@foo'])&&(vars['@zap'])"
106
+ end
107
+ end
108
+
109
+ it "should have limits" do
110
+ rubify_hash(@e.limits).should == {"@zap"=>5, "@foo"=>3}
111
+ end
112
+ end
113
+
114
+ context "Tag limits" do
115
+ it "should be counted for negative tags" do
116
+ @e = Gherkin::TagExpression.new(['~@todo:3'])
117
+ rubify_hash(@e.limits).should == {"@todo"=>3}
118
+ end
119
+
120
+ it "should be counted for positive tags" do
121
+ @e = Gherkin::TagExpression.new(['@todo:3'])
122
+ rubify_hash(@e.limits).should == {"@todo"=>3}
123
+ end
124
+
125
+ it "should raise an error for inconsistent limits" do
126
+ lambda do
127
+ @e = Gherkin::TagExpression.new(['@todo:3', '~@todo:4'])
128
+ end.should raise_error(/Inconsistent tag limits for @todo: 3 and 4/)
129
+ end
130
+
131
+ it "should allow duplicate consistent limits" do
132
+ @e = Gherkin::TagExpression.new(['@todo:3', '~@todo:3'])
133
+ rubify_hash(@e.limits).should == {"@todo"=>3}
134
+ end
135
+ end
136
+ end
137
+ end