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.
- 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
|