asciidoctor-doctest 1.5.0

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 (71) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.adoc +0 -0
  3. data/LICENSE +21 -0
  4. data/README.adoc +327 -0
  5. data/Rakefile +12 -0
  6. data/data/examples/asciidoc/block_admonition.adoc +27 -0
  7. data/data/examples/asciidoc/block_audio.adoc +13 -0
  8. data/data/examples/asciidoc/block_colist.adoc +46 -0
  9. data/data/examples/asciidoc/block_dlist.adoc +99 -0
  10. data/data/examples/asciidoc/block_example.adoc +21 -0
  11. data/data/examples/asciidoc/block_floating_title.adoc +27 -0
  12. data/data/examples/asciidoc/block_image.adoc +28 -0
  13. data/data/examples/asciidoc/block_listing.adoc +68 -0
  14. data/data/examples/asciidoc/block_literal.adoc +30 -0
  15. data/data/examples/asciidoc/block_olist.adoc +55 -0
  16. data/data/examples/asciidoc/block_open.adoc +40 -0
  17. data/data/examples/asciidoc/block_outline.adoc +60 -0
  18. data/data/examples/asciidoc/block_page_break.adoc +6 -0
  19. data/data/examples/asciidoc/block_paragraph.adoc +17 -0
  20. data/data/examples/asciidoc/block_pass.adoc +5 -0
  21. data/data/examples/asciidoc/block_preamble.adoc +19 -0
  22. data/data/examples/asciidoc/block_quote.adoc +30 -0
  23. data/data/examples/asciidoc/block_sidebar.adoc +22 -0
  24. data/data/examples/asciidoc/block_stem.adoc +28 -0
  25. data/data/examples/asciidoc/block_table.adoc +168 -0
  26. data/data/examples/asciidoc/block_thematic_break.adoc +2 -0
  27. data/data/examples/asciidoc/block_toc.adoc +50 -0
  28. data/data/examples/asciidoc/block_ulist.adoc +43 -0
  29. data/data/examples/asciidoc/block_verse.adoc +37 -0
  30. data/data/examples/asciidoc/block_video.adoc +24 -0
  31. data/data/examples/asciidoc/document.adoc +51 -0
  32. data/data/examples/asciidoc/embedded.adoc +10 -0
  33. data/data/examples/asciidoc/inline_anchor.adoc +27 -0
  34. data/data/examples/asciidoc/inline_break.adoc +8 -0
  35. data/data/examples/asciidoc/inline_button.adoc +3 -0
  36. data/data/examples/asciidoc/inline_callout.adoc +5 -0
  37. data/data/examples/asciidoc/inline_footnote.adoc +9 -0
  38. data/data/examples/asciidoc/inline_image.adoc +44 -0
  39. data/data/examples/asciidoc/inline_kbd.adoc +7 -0
  40. data/data/examples/asciidoc/inline_menu.adoc +11 -0
  41. data/data/examples/asciidoc/inline_quoted.adoc +59 -0
  42. data/data/examples/asciidoc/section.adoc +74 -0
  43. data/doc/img/doctest-diag.odf +0 -0
  44. data/doc/img/doctest-diag.svg +56 -0
  45. data/doc/img/failing-test-term.gif +0 -0
  46. data/lib/asciidoctor-doctest.rb +1 -0
  47. data/lib/asciidoctor/doctest.rb +30 -0
  48. data/lib/asciidoctor/doctest/asciidoc/examples_suite.rb +44 -0
  49. data/lib/asciidoctor/doctest/asciidoc_renderer.rb +103 -0
  50. data/lib/asciidoctor/doctest/base_example.rb +161 -0
  51. data/lib/asciidoctor/doctest/base_examples_suite.rb +188 -0
  52. data/lib/asciidoctor/doctest/core_ext.rb +49 -0
  53. data/lib/asciidoctor/doctest/generator.rb +63 -0
  54. data/lib/asciidoctor/doctest/generator_task.rb +111 -0
  55. data/lib/asciidoctor/doctest/html/example.rb +21 -0
  56. data/lib/asciidoctor/doctest/html/examples_suite.rb +111 -0
  57. data/lib/asciidoctor/doctest/html/html_beautifier.rb +17 -0
  58. data/lib/asciidoctor/doctest/html/normalizer.rb +118 -0
  59. data/lib/asciidoctor/doctest/minitest_diffy.rb +74 -0
  60. data/lib/asciidoctor/doctest/test.rb +120 -0
  61. data/lib/asciidoctor/doctest/version.rb +5 -0
  62. data/spec/asciidoc/examples_suite_spec.rb +99 -0
  63. data/spec/base_example_spec.rb +176 -0
  64. data/spec/core_ext_spec.rb +67 -0
  65. data/spec/html/examples_suite_spec.rb +249 -0
  66. data/spec/html/normalizer_spec.rb +70 -0
  67. data/spec/shared_examples/base_examples_suite.rb +262 -0
  68. data/spec/spec_helper.rb +33 -0
  69. data/spec/support/matchers.rb +7 -0
  70. data/spec/test_spec.rb +164 -0
  71. metadata +360 -0
@@ -0,0 +1,67 @@
1
+ describe Enumerable do
2
+
3
+ describe '#map_send' do
4
+
5
+ it 'sends a message to each element and collects the result' do
6
+ expect([1, 2, 3].map_send(:+, 3)).to eq [4, 5, 6]
7
+ end
8
+ end
9
+ end
10
+
11
+ describe String do
12
+
13
+ describe '#concat' do
14
+
15
+ context 'without separator' do
16
+ subject { 'foo' }
17
+
18
+ it 'appends the given string to self' do
19
+ subject.concat 'bar'
20
+ is_expected.to eq 'foobar'
21
+ end
22
+ end
23
+
24
+ context 'with separator' do
25
+
26
+ context 'when self is empty' do
27
+ subject { '' }
28
+
29
+ it 'appends the given string to self' do
30
+ subject.concat 'bar', "\n"
31
+ is_expected.to eq 'bar'
32
+ end
33
+ end
34
+
35
+ context 'when self is not empty' do
36
+ subject { 'foo' }
37
+
38
+ it 'appends the given separator and string to self' do
39
+ subject.concat 'bar', "\n"
40
+ is_expected.to eq "foo\nbar"
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+
47
+
48
+ describe Module do
49
+
50
+ describe '#alias_class_method' do
51
+
52
+ subject(:klass) do
53
+ Class.new do
54
+ def self.salute
55
+ 'Meow!'
56
+ end
57
+ end
58
+ end
59
+
60
+ it 'defines new class method that calls the old class method' do
61
+ klass.alias_class_method :say_hello!, :salute
62
+
63
+ expect(klass).to respond_to :say_hello!
64
+ expect(klass.say_hello!).to eq klass.salute
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,249 @@
1
+ require 'active_support/core_ext/string/strip'
2
+
3
+ describe DocTest::HTML::ExamplesSuite do
4
+ extend Forwardable
5
+
6
+ it_should_behave_like DocTest::BaseExamplesSuite
7
+
8
+ def_delegator :suite, :create_example
9
+
10
+ subject(:suite) { described_class.new }
11
+
12
+
13
+ describe '#initialize' do
14
+
15
+ it 'uses ".html" as default file_ext' do
16
+ expect(suite.file_ext).to eq '.html'
17
+ end
18
+ end
19
+
20
+
21
+ describe 'parsing/serialization:' do
22
+
23
+ context 'one example' do
24
+
25
+ shared_examples :example do
26
+ let(:parsed) { suite.parse input, 's' }
27
+ let(:serialized) { suite.serialize output }
28
+
29
+ it { (expect parsed).to have(1).items }
30
+
31
+ it 'returns an array with parsed example object' do
32
+ (expect parsed.first).to eql output
33
+ end
34
+
35
+ it 'returns a serialized example as string' do
36
+ (expect serialized).to eql input
37
+ end
38
+ end
39
+
40
+ context 'with name only' do
41
+ let(:input) { "<!-- .basic -->\n" }
42
+ let(:output) { create_example 's:basic' }
43
+
44
+ include_examples :example
45
+ end
46
+
47
+ context 'with multiline content' do
48
+ let :content do
49
+ <<-EOF.strip_heredoc
50
+ <p>Paragraphs don't require
51
+ any special markup.</p>
52
+
53
+ <p>To begin a new one, separate it by blank line.</p>
54
+ EOF
55
+ end
56
+
57
+ let(:input) { "<!-- .multiline -->\n#{content}" }
58
+ let(:output) { create_example 's:multiline', content: content.chomp }
59
+
60
+ include_examples :example
61
+ end
62
+
63
+ context 'with description' do
64
+ let :input do
65
+ <<-EOF.strip_heredoc
66
+ <!-- .strong
67
+ This is a description,
68
+ see?
69
+ -->
70
+ <strong>allons-y!</strong>
71
+ EOF
72
+ end
73
+
74
+ let :output do
75
+ create_example 's:strong', content: '<strong>allons-y!</strong>',
76
+ desc: "This is a description,\nsee?"
77
+ end
78
+ include_examples :example
79
+ end
80
+
81
+ context 'with options' do
82
+ let :input do
83
+ <<-EOF.strip_heredoc
84
+ <!-- .basic
85
+ :exclude: .//code
86
+ :exclude: .//section
87
+ :include: ./p/node()
88
+ -->
89
+ <p>dummy</p>
90
+ EOF
91
+ end
92
+
93
+ let :output do
94
+ create_example 's:basic', content: '<p>dummy</p>', opts: {
95
+ exclude: ['.//code', './/section'],
96
+ include: ['./p/node()']
97
+ }
98
+ end
99
+ include_examples :example
100
+ end
101
+
102
+ context 'with boolean option' do
103
+ let(:input) { "<!-- .basic\n:header_footer:\n-->\n" }
104
+ let(:output) { create_example 's:basic', opts: { header_footer: true } }
105
+
106
+ include_examples :example
107
+ end
108
+
109
+ context 'with description and options' do
110
+ let :input do
111
+ <<-EOF.strip_heredoc
112
+ <!-- .basic
113
+ This is a description.
114
+ :exclude: .//code
115
+ :header_footer:
116
+ -->
117
+ EOF
118
+ end
119
+
120
+ let :output do
121
+ create_example 's:basic', desc: 'This is a description.', opts: {
122
+ exclude: ['.//code'],
123
+ header_footer: true
124
+ }
125
+ end
126
+ include_examples :example
127
+ end
128
+ end
129
+
130
+ context 'multiple examples' do
131
+ let :input do
132
+ <<-EOF.strip_heredoc
133
+ <!-- .basic -->
134
+ http://asciidoctor.org
135
+
136
+ <!-- .xref -->
137
+ Refer to <<section-a>>.
138
+ EOF
139
+ end
140
+
141
+ subject(:parsed) { suite.parse input, 's' }
142
+
143
+ it { is_expected.to have(2).items }
144
+
145
+ it 'returns an array with parsed Example objects' do
146
+ expect(parsed[0]).to eql create_example('s:basic', content: 'http://asciidoctor.org')
147
+ expect(parsed[1]).to eql create_example('s:xref', content: 'Refer to <<section-a>>.')
148
+ end
149
+ end
150
+ end
151
+
152
+
153
+ describe '#convert_example' do
154
+
155
+ let(:input) { create_example 's:dummy', content: '*chunky* bacon' }
156
+ let(:opts) { {dummy: 'value'} }
157
+ let(:renderer) { double 'AsciidocRenderer' }
158
+ let(:renderer_opts) { {header_footer: false} }
159
+
160
+ subject(:result) { suite.convert_example input, opts, renderer }
161
+
162
+ let :rendered do
163
+ <<-EOF
164
+ <section>
165
+ <h1>Title</h1>
166
+ <div>
167
+ <p><b>Chunky</b> bacon</p>
168
+ </div>
169
+ <code>meh</code>
170
+ </section>
171
+ <div>
172
+ <p>why?</p>
173
+ </div>
174
+ EOF
175
+ end
176
+
177
+ before do
178
+ expect(renderer).to receive(:render)
179
+ .with(input.content, renderer_opts).and_return(rendered)
180
+ end
181
+
182
+ it 'returns instance of HTML::Example' do
183
+ is_expected.to be_instance_of DocTest::HTML::Example
184
+ end
185
+
186
+ it 'returns Example with the same name as input_example' do
187
+ expect(result.name).to eq input.name
188
+ end
189
+
190
+ it 'returns Example with the given opts' do
191
+ expect(result.opts).to eq opts
192
+ end
193
+
194
+ context 'with :exclude option' do
195
+ let(:opts) { {exclude: ['.//p', './/code']} }
196
+
197
+ it 'returns content without HTML (sub)elements specified by XPath' do
198
+ expect(result.content.gsub(/\s*/, '')).to eq \
199
+ '<section><h1>Title</h1><div></div></section><div></div>'
200
+ end
201
+ end
202
+
203
+ context 'with :include option' do
204
+ let(:opts) { {include: ['.//p']} }
205
+
206
+ it 'returns content with only HTML (sub)elements specified by XPath' do
207
+ expect(result.content.gsub(/\s*/, '')).to eq '<p><b>Chunky</b>bacon</p><p>why?</p>'
208
+ end
209
+ end
210
+
211
+ context 'with :header_footer option' do
212
+ let(:opts) { {header_footer: true} }
213
+
214
+ it 'renders content with :header_footer => true' do
215
+ suite.convert_example input, {}, renderer
216
+ end
217
+ end
218
+
219
+ context 'with example named /^document.*/' do
220
+ let(:input) { create_example 'document:dummy', content: '*chunky* bacon' }
221
+ let(:renderer_opts) { {header_footer: true} }
222
+
223
+ it 'renders content with :header_footer => true' do
224
+ suite.convert_example input, {}, renderer
225
+ end
226
+ end
227
+
228
+ context 'with example named /inline_.*/' do
229
+ let(:input) { create_example 'inline_quoted:dummy', content: '*chunky* bacon' }
230
+ let(:rendered) { '<p><b>chunky</b> bacon</p>' }
231
+
232
+ it 'returns content without top-level <p> tags' do
233
+ expect(result.content).to eq '<b>chunky</b> bacon'
234
+ end
235
+
236
+ it 'does not add implicit include into returned example' do
237
+ expect(result.opts).to_not include :include
238
+ end
239
+
240
+ context 'with :include option' do
241
+ let(:opts) { {include: ['.//b']} }
242
+
243
+ it 'preferes the include option' do
244
+ expect(result.content).to eq '<b>chunky</b>'
245
+ end
246
+ end
247
+ end
248
+ end
249
+ end
@@ -0,0 +1,70 @@
1
+ describe DocTest::HTML::Normalizer do
2
+
3
+ [Nokogiri::HTML::Document, Nokogiri::HTML::DocumentFragment].each do |klass|
4
+ it "HtmlNormalizer should be included in #{klass}" do
5
+ expect(klass).to have_method :normalize!
6
+ end
7
+ end
8
+
9
+ describe 'normalize!' do
10
+
11
+ it 'sorts attributes by name' do
12
+ output = normalize '<img src="tux.png" width="60" height="100" alt="Tux!">'
13
+ expect(output).to eq '<img alt="Tux!" height="100" src="tux.png" width="60">'
14
+ end
15
+
16
+ it 'removes all blank text nodes' do
17
+ output = normalize " <section>\n <p>Lorem ipsum</p>\n\t</section>\n\n"
18
+ expect(output).to eq '<section><p>Lorem ipsum</p></section>'
19
+ end
20
+
21
+ context 'in "style" attribute' do
22
+
23
+ it 'sorts CSS declarations by name' do
24
+ output = normalize %(<div style="width: 100%; color: 'red'; font-style: bold"></div>)
25
+ expect(output).to eq %(<div style="color: 'red'; font-style: bold; width: 100%;"></div>)
26
+ end
27
+ end
28
+
29
+ context 'in text node' do
30
+
31
+ it 'strips nonsignificant leading and trailing whitespaces' do
32
+ output = normalize "<p> Lorem<b> ipsum</b> dolor\n<br> sit <i>amet</i></p>"
33
+ expect(output).to eq '<p>Lorem<b> ipsum</b> dolor<br>sit <i>amet</i></p>'
34
+ end
35
+
36
+ it 'strips nonsignificant repeated whitespaces' do
37
+ output = normalize "<p>Lorem ipsum\t\tdolor</p>"
38
+ expect(output).to eq "<p>Lorem ipsum\tdolor</p>"
39
+ end
40
+
41
+ it 'replaces newlines with spaces' do
42
+ output = normalize "<p>Lorem\nipsum\n\ndolor</p>"
43
+ expect(output).to eq '<p>Lorem ipsum dolor</p>'
44
+ end
45
+ end
46
+
47
+ context 'in preformatted node or descendant' do
48
+
49
+ it 'does not strip leading and trailing whitespaces' do
50
+ input = "<pre> Lorem<b> ipsum</b> dolor\n<br> sit amet</pre>"
51
+ expect(normalize(input)).to eq input
52
+ end
53
+
54
+ it 'does not strip repeated whitespaces' do
55
+ input = "<pre>Lorem ipsum\t\tdolor\n<code>sit amet</code></pre>"
56
+ output = normalize input
57
+ expect(output).to eq input
58
+ end
59
+
60
+ it 'does not replace newlines with spaces' do
61
+ input = "<pre>Lorem\n<code>\nipsum</code>\n\ndolor</pre>"
62
+ expect(normalize(input)).to eq input
63
+ end
64
+ end
65
+ end
66
+
67
+ def normalize(input)
68
+ Nokogiri::HTML.fragment(input).normalize!.to_s
69
+ end
70
+ end
@@ -0,0 +1,262 @@
1
+ require 'active_support/core_ext/array/access'
2
+ require 'fileutils'
3
+ require 'forwardable'
4
+ require 'fakefs/spec_helpers'
5
+
6
+ module FakeFS
7
+ # XXX remove after merging of https://github.com/defunkt/fakefs/pull/270
8
+ module FileTest
9
+
10
+ def readable?(file_name)
11
+ File.readable?(file_name)
12
+ end
13
+ module_function :readable?
14
+ end
15
+
16
+ # XXX remove after merging of https://github.com/defunkt/fakefs/pull/269
17
+ class Pathname
18
+ def read(*args)
19
+ File.read(@path, *args)
20
+ end
21
+ end
22
+ end
23
+
24
+
25
+ shared_examples DocTest::BaseExamplesSuite do
26
+ extend Forwardable
27
+
28
+ def_delegator :suite, :create_example
29
+
30
+ subject(:suite) { described_class.new(file_ext: '.adoc', examples_path: ex_path) }
31
+ let(:ex_path) { ['/tmp/alpha', '/tmp/beta'] }
32
+
33
+
34
+ describe '#parse' do
35
+ context 'empty file' do
36
+ subject { suite.parse '', 'block_ulist' }
37
+
38
+ it { is_expected.to be_empty }
39
+ end
40
+ end
41
+
42
+
43
+ describe '#read_examples' do
44
+ include FakeFS::SpecHelpers
45
+
46
+ subject(:result) { suite.read_examples group_name }
47
+
48
+ let(:group_name) { 'section' }
49
+
50
+ before do
51
+ ex_path.each { |p| FileUtils.mkpath p }
52
+ create_and_write_group ex_path.first, 'noise', '.adoc', 'foo', 'bar'
53
+
54
+ allow(suite).to receive(:parse) do |input, group_name|
55
+ path, file_name, *example_names = input.split("\n")
56
+ expect(group_name).to eq file_name.split('.').first
57
+
58
+ example_names.map do |name|
59
+ # this content is just filling to identify the example in test
60
+ create_example [group_name, name], content: "#{path}/#{file_name}:#{name}"
61
+ end
62
+ end
63
+ end
64
+
65
+ context "when the group's file is not found" do
66
+ it { is_expected.to be_empty }
67
+ end
68
+
69
+ context "when the group's file has a wrong file extension" do
70
+ before do
71
+ create_and_write_group ex_path.first, group_name, '.html', 'level1', 'level2'
72
+ end
73
+
74
+ it { is_expected.to be_empty }
75
+ end
76
+
77
+ context 'when single group file is found' do
78
+ let! :examples do
79
+ create_and_write_group ex_path.second, group_name, '.adoc', 'level1', 'level2'
80
+ end
81
+
82
+ it 'returns parsed examples' do
83
+ is_expected.to eq examples
84
+ end
85
+ end
86
+
87
+ context 'when multiple group files are found and contains example with same name' do
88
+ let! :examples do
89
+ first = create_and_write_group ex_path.first, group_name, '.adoc', 'level1', 'level2'
90
+ second = create_and_write_group ex_path.second, group_name, '.adoc', 'level2', 'level3'
91
+ [*first, second[1]]
92
+ end
93
+
94
+ it 'returns parsed examples without duplicates (first wins)' do
95
+ is_expected.to eq examples
96
+ end
97
+ end
98
+ end
99
+
100
+
101
+ describe '#write_examples' do
102
+ include FakeFS::SpecHelpers
103
+
104
+ let :examples do
105
+ (1..2).map { |i| create_example "section:level#{i}", content: 'yada' }
106
+ end
107
+
108
+ before { ex_path.each { |p| FileUtils.mkpath p } }
109
+
110
+ it 'writes serialized examples to file named after the group with file extension' do
111
+ expect(suite).to receive :serialize do |exmpls|
112
+ exmpls.map(&:name).join("\n")
113
+ end
114
+ suite.write_examples examples
115
+
116
+ file = File.read "#{ex_path.first}/section.adoc"
117
+ expect(file).to eq examples.map(&:name).join("\n")
118
+ end
119
+ end
120
+
121
+
122
+ describe '#file_names' do
123
+ include FakeFS::SpecHelpers
124
+
125
+ subject(:result) { suite.group_names }
126
+
127
+ before { ex_path.each { |p| FileUtils.mkpath p } }
128
+
129
+ context 'when no file is found' do
130
+ it { is_expected.to be_empty }
131
+ end
132
+
133
+ it 'returns names of files with matching file extension only' do
134
+ %w[block_image.html block_ulist.adoc].each do |name|
135
+ File.write "#{ex_path.first}/#{name}", 'yada'
136
+ end
137
+ is_expected.to contain_exactly 'block_ulist'
138
+ end
139
+
140
+ it 'returns names sorted and deduplicated' do
141
+ (names = %w[z j d c k d]).each_with_index do |name, i|
142
+ File.write "#{ex_path[i % 2]}/#{name}.adoc", 'yada'
143
+ end
144
+
145
+ is_expected.to eq names.uniq.sort
146
+ end
147
+
148
+ it 'ignores directories and files in subdirectories' do
149
+ Dir.mkdir "#{ex_path.first}/invalid.adoc"
150
+ File.write "#{ex_path.first}/invalid.adoc/wat.adoc", 'yada'
151
+
152
+ is_expected.to be_empty
153
+ end
154
+ end
155
+
156
+
157
+ describe '#update_examples' do
158
+
159
+ let :current do
160
+ %w[gr0:ex0 gr0:ex1 gr1:ex0 gr1:ex1].map do |name|
161
+ create_example name, content: name.reverse
162
+ end
163
+ end
164
+ let :updated do
165
+ [ (create_example 'gr0:ex0', content: 'allons-y!'),
166
+ (create_example 'gr1:ex1', content: 'allons-y!') ]
167
+ end
168
+
169
+ before do
170
+ expect(suite).to receive(:read_examples).exactly(2).times do |group_name|
171
+ current.select { |e| e.group_name == group_name }
172
+ end
173
+ end
174
+
175
+ it 'merges current and updated examples and writes them' do
176
+ is_expected.to receive(:write_examples).with [updated[0], current[1]]
177
+ is_expected.to receive(:write_examples).with [current[2], updated[1]]
178
+
179
+ suite.update_examples updated
180
+ end
181
+ end
182
+
183
+
184
+ describe '#pair_with' do
185
+
186
+ subject(:result) { ours_suite.pair_with(theirs_suite).to_a }
187
+ let(:result_names) { result.map(&:first).map(&:name) }
188
+
189
+ let(:ours_suite) { described_class.new(file_ext: '.xyz') }
190
+ let(:theirs_suite) { DocTest::Asciidoc::ExamplesSuite.new(file_ext: '.adoc') }
191
+
192
+ def ours_exmpl(suffix, group = 0)
193
+ ours_suite.create_example "gr#{group}:ex#{suffix}", content: 'ours!'
194
+ end
195
+
196
+ def theirs_exmpl(suffix, group = 0)
197
+ theirs_suite.create_example "gr#{group}:ex#{suffix}", content: 'theirs!'
198
+ end
199
+
200
+ before do
201
+ expect(ours_suite).to receive(:group_names)
202
+ .and_return(['gr0', 'gr1'])
203
+ expect(theirs_suite).to receive(:read_examples)
204
+ .with(/gr[0-1]/).exactly(:twice).and_return(*theirs)
205
+ expect(ours_suite).to receive(:read_examples)
206
+ .with(/gr[0-1]/).exactly(:twice).and_return(*ours)
207
+ end
208
+
209
+ context do
210
+ let :ours do
211
+ [ [ ours_exmpl(0, 0), ours_exmpl(1, 0) ], [ ours_exmpl(0, 1), ours_exmpl(1, 1) ] ]
212
+ end
213
+ let :theirs do
214
+ [ [ theirs_exmpl(1, 0), theirs_exmpl(0, 0) ], [ theirs_exmpl(0, 1), theirs_exmpl(1, 1) ] ]
215
+ end
216
+
217
+ it 'returns pairs of ours/theirs examples in ours order' do
218
+ expect(result_names).to eq %w[gr0:ex0 gr0:ex1 gr1:ex0 gr1:ex1]
219
+ expect(result).to eq ours.flatten(1).zip theirs.flatten(1).sort_by(&:name)
220
+ end
221
+ end
222
+
223
+ context 'when some example is missing' do
224
+ let(:ours) { [(0..2).map { |i| ours_exmpl i }, []] }
225
+ let(:theirs) { [[1, 0, 2].map { |i| theirs_exmpl i }, []] }
226
+
227
+ context 'in theirs suite' do
228
+ let(:theirs) { [ [theirs_exmpl(2), theirs_exmpl(0)], [] ] }
229
+
230
+ it 'returns pairs in ours order' do
231
+ expect(result_names).to eq %w[gr0:ex0 gr0:ex1 gr0:ex2]
232
+ end
233
+
234
+ it 'replaces the missing example with empty one with the name' do
235
+ expect(result.second.last).to eq theirs_suite.create_example 'gr0:ex1'
236
+ end
237
+ end
238
+
239
+ context 'in ours suite' do
240
+ let(:ours) { [ [ours_exmpl(1), ours_exmpl(2)], [] ] }
241
+
242
+ it 'returns pairs in ours order with the missing example at the end' do
243
+ expect(result_names).to eq %w[gr0:ex1 gr0:ex2 gr0:ex0]
244
+ end
245
+
246
+ it 'replaces the missing example with empty one with the name' do
247
+ expect(result.last.first).to eq ours_suite.create_example 'gr0:ex0'
248
+ end
249
+ end
250
+ end
251
+ end
252
+
253
+
254
+ def create_and_write_group(path, group_name, file_ext, *examples)
255
+ content = [path, group_name + file_ext, *examples].join("\n")
256
+ File.write File.join(path, group_name + file_ext), content
257
+
258
+ examples.map do |name|
259
+ create_example [group_name, name], content: "#{path}/#{group_name}#{file_ext}:#{name}"
260
+ end
261
+ end
262
+ end