asciidoctor-doctest 1.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.adoc +0 -0
- data/LICENSE +21 -0
- data/README.adoc +327 -0
- data/Rakefile +12 -0
- data/data/examples/asciidoc/block_admonition.adoc +27 -0
- data/data/examples/asciidoc/block_audio.adoc +13 -0
- data/data/examples/asciidoc/block_colist.adoc +46 -0
- data/data/examples/asciidoc/block_dlist.adoc +99 -0
- data/data/examples/asciidoc/block_example.adoc +21 -0
- data/data/examples/asciidoc/block_floating_title.adoc +27 -0
- data/data/examples/asciidoc/block_image.adoc +28 -0
- data/data/examples/asciidoc/block_listing.adoc +68 -0
- data/data/examples/asciidoc/block_literal.adoc +30 -0
- data/data/examples/asciidoc/block_olist.adoc +55 -0
- data/data/examples/asciidoc/block_open.adoc +40 -0
- data/data/examples/asciidoc/block_outline.adoc +60 -0
- data/data/examples/asciidoc/block_page_break.adoc +6 -0
- data/data/examples/asciidoc/block_paragraph.adoc +17 -0
- data/data/examples/asciidoc/block_pass.adoc +5 -0
- data/data/examples/asciidoc/block_preamble.adoc +19 -0
- data/data/examples/asciidoc/block_quote.adoc +30 -0
- data/data/examples/asciidoc/block_sidebar.adoc +22 -0
- data/data/examples/asciidoc/block_stem.adoc +28 -0
- data/data/examples/asciidoc/block_table.adoc +168 -0
- data/data/examples/asciidoc/block_thematic_break.adoc +2 -0
- data/data/examples/asciidoc/block_toc.adoc +50 -0
- data/data/examples/asciidoc/block_ulist.adoc +43 -0
- data/data/examples/asciidoc/block_verse.adoc +37 -0
- data/data/examples/asciidoc/block_video.adoc +24 -0
- data/data/examples/asciidoc/document.adoc +51 -0
- data/data/examples/asciidoc/embedded.adoc +10 -0
- data/data/examples/asciidoc/inline_anchor.adoc +27 -0
- data/data/examples/asciidoc/inline_break.adoc +8 -0
- data/data/examples/asciidoc/inline_button.adoc +3 -0
- data/data/examples/asciidoc/inline_callout.adoc +5 -0
- data/data/examples/asciidoc/inline_footnote.adoc +9 -0
- data/data/examples/asciidoc/inline_image.adoc +44 -0
- data/data/examples/asciidoc/inline_kbd.adoc +7 -0
- data/data/examples/asciidoc/inline_menu.adoc +11 -0
- data/data/examples/asciidoc/inline_quoted.adoc +59 -0
- data/data/examples/asciidoc/section.adoc +74 -0
- data/doc/img/doctest-diag.odf +0 -0
- data/doc/img/doctest-diag.svg +56 -0
- data/doc/img/failing-test-term.gif +0 -0
- data/lib/asciidoctor-doctest.rb +1 -0
- data/lib/asciidoctor/doctest.rb +30 -0
- data/lib/asciidoctor/doctest/asciidoc/examples_suite.rb +44 -0
- data/lib/asciidoctor/doctest/asciidoc_renderer.rb +103 -0
- data/lib/asciidoctor/doctest/base_example.rb +161 -0
- data/lib/asciidoctor/doctest/base_examples_suite.rb +188 -0
- data/lib/asciidoctor/doctest/core_ext.rb +49 -0
- data/lib/asciidoctor/doctest/generator.rb +63 -0
- data/lib/asciidoctor/doctest/generator_task.rb +111 -0
- data/lib/asciidoctor/doctest/html/example.rb +21 -0
- data/lib/asciidoctor/doctest/html/examples_suite.rb +111 -0
- data/lib/asciidoctor/doctest/html/html_beautifier.rb +17 -0
- data/lib/asciidoctor/doctest/html/normalizer.rb +118 -0
- data/lib/asciidoctor/doctest/minitest_diffy.rb +74 -0
- data/lib/asciidoctor/doctest/test.rb +120 -0
- data/lib/asciidoctor/doctest/version.rb +5 -0
- data/spec/asciidoc/examples_suite_spec.rb +99 -0
- data/spec/base_example_spec.rb +176 -0
- data/spec/core_ext_spec.rb +67 -0
- data/spec/html/examples_suite_spec.rb +249 -0
- data/spec/html/normalizer_spec.rb +70 -0
- data/spec/shared_examples/base_examples_suite.rb +262 -0
- data/spec/spec_helper.rb +33 -0
- data/spec/support/matchers.rb +7 -0
- data/spec/test_spec.rb +164 -0
- 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
|