asciidoctor-doctest 1.5.2.0 → 2.0.0.beta.1

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 (43) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +1 -1
  3. data/README.adoc +48 -68
  4. data/features/fixtures/html-slim/Rakefile +5 -11
  5. data/features/generator_html.feature +6 -6
  6. data/features/test_html.feature +70 -28
  7. data/lib/asciidoctor/doctest.rb +11 -14
  8. data/lib/asciidoctor/doctest/asciidoc_converter.rb +85 -0
  9. data/lib/asciidoctor/doctest/{base_example.rb → example.rb} +6 -27
  10. data/lib/asciidoctor/doctest/factory.rb +36 -0
  11. data/lib/asciidoctor/doctest/generator.rb +30 -23
  12. data/lib/asciidoctor/doctest/html/converter.rb +64 -0
  13. data/lib/asciidoctor/doctest/{html/normalizer.rb → html_normalizer.rb} +4 -4
  14. data/lib/asciidoctor/doctest/io.rb +14 -0
  15. data/lib/asciidoctor/doctest/{asciidoc/examples_suite.rb → io/asciidoc.rb} +4 -8
  16. data/lib/asciidoctor/doctest/{base_examples_suite.rb → io/base.rb} +28 -46
  17. data/lib/asciidoctor/doctest/io/xml.rb +69 -0
  18. data/lib/asciidoctor/doctest/no_fallback_template_converter.rb +42 -0
  19. data/lib/asciidoctor/doctest/rake_tasks.rb +229 -0
  20. data/lib/asciidoctor/doctest/test_reporter.rb +110 -0
  21. data/lib/asciidoctor/doctest/tester.rb +134 -0
  22. data/lib/asciidoctor/doctest/version.rb +1 -1
  23. data/spec/asciidoc_converter_spec.rb +64 -0
  24. data/spec/{base_example_spec.rb → example_spec.rb} +4 -5
  25. data/spec/factory_spec.rb +46 -0
  26. data/spec/html/converter_spec.rb +95 -0
  27. data/spec/{html/normalizer_spec.rb → html_normalizer_spec.rb} +1 -1
  28. data/spec/{asciidoc/examples_suite_spec.rb → io/asciidoc_spec.rb} +3 -8
  29. data/spec/{html/examples_suite_spec.rb → io/xml_spec.rb} +3 -106
  30. data/spec/no_fallback_template_converter_spec.rb +38 -0
  31. data/spec/shared_examples/{base_examples_suite.rb → base_examples.rb} +25 -28
  32. data/spec/spec_helper.rb +4 -0
  33. data/spec/tester_spec.rb +153 -0
  34. metadata +52 -59
  35. data/features/fixtures/html-slim/test/html_test.rb +0 -6
  36. data/features/fixtures/html-slim/test/test_helper.rb +0 -5
  37. data/lib/asciidoctor/doctest/asciidoc_renderer.rb +0 -111
  38. data/lib/asciidoctor/doctest/generator_task.rb +0 -115
  39. data/lib/asciidoctor/doctest/html/example.rb +0 -21
  40. data/lib/asciidoctor/doctest/html/examples_suite.rb +0 -118
  41. data/lib/asciidoctor/doctest/test.rb +0 -125
  42. data/spec/asciidoc_renderer_spec.rb +0 -103
  43. data/spec/test_spec.rb +0 -164
@@ -0,0 +1,134 @@
1
+ require 'asciidoctor/doctest/minitest_diffy'
2
+ require 'asciidoctor/doctest/test_reporter'
3
+ require 'corefines'
4
+ require 'minitest'
5
+
6
+ using Corefines::Object::presence
7
+
8
+ module Asciidoctor
9
+ module DocTest
10
+ class Tester
11
+
12
+ ##
13
+ # @return [Minitest::Reporter] an instance of minitest's +Reporter+
14
+ # to report test results.
15
+ attr_reader :reporter
16
+
17
+ ##
18
+ # @param input_suite [IO::Base] an instance of {IO::Base} subclass to
19
+ # read the reference input examples from.
20
+ #
21
+ # @param output_suite [IO::Base] an instance of {IO::Base} subclass to
22
+ # read the output examples from (i.e. an expected output).
23
+ #
24
+ # @param converter [#call] a callable that accepts a string content of
25
+ # an input example and a hash with options for the converter, and
26
+ # returns the converted content.
27
+ #
28
+ # @param reporter [Minitest::Reporter, nil] an instance of minitest's
29
+ # +Reporter+ to report test results. When omitted or +nil+, then
30
+ # {TestReporter} is used.
31
+ #
32
+ def initialize(input_suite, output_suite, converter, reporter = nil)
33
+ @input_suite = input_suite
34
+ @output_suite = output_suite
35
+ @converter = converter
36
+ @reporter = reporter || TestReporter.new
37
+ end
38
+
39
+ ##
40
+ # Runs tests for all the input/output examples which name matches
41
+ # the _pattern_. When some output example is missing, it's reported as
42
+ # a skipped test.
43
+ #
44
+ # @param pattern [String] glob-like pattern to select examples to test
45
+ # (see {Example#name_match?}).
46
+ #
47
+ def run_tests(pattern: '*:*')
48
+ @reporter.start
49
+
50
+ @input_suite.pair_with(@output_suite).each do |input, output|
51
+ next if input.empty? || !input.name_match?(pattern)
52
+ test_example input, output
53
+ end
54
+
55
+ @reporter.report
56
+ @reporter.passed?
57
+ end
58
+
59
+ ##
60
+ # Tests if the given reference input is matching the expected output
61
+ # after conversion through the tested backend.
62
+ #
63
+ # @param input_exmpl [Example] the reference input example.
64
+ # @param output_exmpl [Example] the expected output example.
65
+ #
66
+ def test_example(input_exmpl, output_exmpl)
67
+ test_with_minitest input_exmpl.name do |test|
68
+ if output_exmpl.empty?
69
+ test.skip 'No expected output found'
70
+ else
71
+ actual, expected = @converter.convert_examples(input_exmpl, output_exmpl)
72
+ msg = output_exmpl.desc.presence || input_exmpl.desc
73
+
74
+ test.assert_equal expected, actual, msg
75
+ end
76
+ end
77
+ end
78
+
79
+ protected
80
+
81
+ ##
82
+ # Runs the given block with Minitest as a single test.
83
+ #
84
+ # @param [String, Symbol] test name.
85
+ # @yield [Minitest::Test] Gives the test context to the block.
86
+ #
87
+ def test_with_minitest(name, &block)
88
+ MinitestSingleTest.test! @reporter, name, name, block
89
+ end
90
+
91
+
92
+ # @private
93
+ class MinitestSingleTest < Minitest::Test
94
+ include MinitestDiffy
95
+
96
+ # @note Overrides method from +Minitest::Test+.
97
+ attr_reader :location
98
+
99
+ def self.test!(reporter, name, location, callable)
100
+ new(reporter, name, location, callable).failures
101
+ end
102
+
103
+ private
104
+
105
+ def initialize(reporter, name, location, callable)
106
+ super name
107
+ @reporter = reporter
108
+ @location = location
109
+ @callable = callable
110
+ run
111
+ @reporter.record(self)
112
+ end
113
+
114
+ # @note Overrides method from +Minitest::Test+.
115
+ def run
116
+ with_info_handler do
117
+ time_it do
118
+ capture_exceptions do
119
+ @callable.call(self)
120
+ end
121
+ end
122
+ end
123
+
124
+ self # per contract
125
+ end
126
+
127
+ # @note Overrides method from +Minitest::Assertions+.
128
+ def mu_pp(example)
129
+ example.to_s
130
+ end
131
+ end
132
+ end
133
+ end
134
+ end
@@ -1,5 +1,5 @@
1
1
  module Asciidoctor
2
2
  module DocTest
3
- VERSION = '1.5.2.0'
3
+ VERSION = '2.0.0.beta.1'
4
4
  end
5
5
  end
@@ -0,0 +1,64 @@
1
+ require 'fileutils'
2
+
3
+ describe DocTest::AsciidocConverter do
4
+
5
+ subject { described_class }
6
+
7
+ it { is_expected.to have_method :convert, :call }
8
+
9
+
10
+ describe '#initialize' do
11
+
12
+ context 'with defaults' do
13
+ subject { described_class.new.opts }
14
+ it { is_expected.to include safe: :safe }
15
+ end
16
+
17
+ context 'with backend_name' do
18
+ subject { described_class.new(backend_name: 'html5').opts }
19
+ it { is_expected.to include backend: 'html5' }
20
+
21
+ context 'empty string' do
22
+ subject { described_class.new(backend_name: '').opts }
23
+ it { is_expected.to_not include :backend }
24
+ end
25
+ end
26
+
27
+ context 'with template_dirs' do
28
+ include FakeFS::SpecHelpers
29
+
30
+ subject { described_class.new(template_dirs: template_dirs).opts }
31
+ let(:template_dirs) { ['/tmp/html5'] }
32
+
33
+ before { FileUtils.mkpath template_dirs[0] }
34
+
35
+ context 'that exists' do
36
+ it do
37
+ is_expected.to include(
38
+ template_dirs: template_dirs,
39
+ converter: DocTest::NoFallbackTemplateConverter
40
+ )
41
+ end
42
+
43
+ context 'and templates_fallback is true' do
44
+ subject { described_class.new(template_dirs: template_dirs, templates_fallback: true).opts }
45
+ it { is_expected.to include template_dirs: template_dirs }
46
+ it { is_expected.to_not include :converter }
47
+ end
48
+
49
+ context 'and custom converter' do
50
+ subject { described_class.new(template_dirs: template_dirs, converter: converter).opts }
51
+ let(:converter) { Asciidoctor::Converter::TemplateConverter }
52
+
53
+ it { is_expected.to include template_dirs: template_dirs, converter: converter }
54
+ end
55
+ end
56
+
57
+ context "that doesn't exist" do
58
+ let(:template_dirs) { ['/tmp/html5', '/tmp/revealjs'] }
59
+
60
+ it { expect { subject }.to raise_error ArgumentError }
61
+ end
62
+ end
63
+ end
64
+ end
@@ -1,10 +1,9 @@
1
- describe DocTest::BaseExample do
1
+ describe DocTest::Example do
2
2
 
3
3
  subject(:o) { described_class.new ['foo', 'bar'] }
4
4
 
5
5
  it do
6
- is_expected.to respond_to :group_name, :local_name, :desc, :opts, :content,
7
- :content_normalized, :content_pretty
6
+ is_expected.to respond_to :group_name, :local_name, :desc, :opts, :content
8
7
  end
9
8
 
10
9
  describe '#name' do
@@ -156,8 +155,8 @@ describe DocTest::BaseExample do
156
155
  expect(first).to_not eq second
157
156
  end
158
157
 
159
- it 'returns false for instances with different content_normalized' do
160
- expect(second).to receive(:content_normalized).and_return('ALLONS-Y!')
158
+ it 'returns false for instances with different content' do
159
+ expect(second).to receive(:content).and_return('ALLONS-Y!')
161
160
  expect(first).to_not eq second
162
161
  end
163
162
  end
@@ -0,0 +1,46 @@
1
+ require 'ostruct'
2
+
3
+ describe DocTest::Factory do
4
+
5
+ subject(:factory) { Module.new { extend DocTest::Factory } }
6
+
7
+ let(:default_opts) { {} }
8
+
9
+ before do
10
+ factory.register(:foo, OpenStruct, default_opts)
11
+ factory.register(:bar, String, 'illegal')
12
+ factory.register(:baz, Integer)
13
+ end
14
+
15
+ describe '.create' do
16
+ it "returns instance of registered class" do
17
+ expect( factory.create(:foo) ).to eq OpenStruct.new
18
+ end
19
+
20
+ context "with opts" do
21
+ it "initializes class with opts" do
22
+ expect( factory.create(:foo, a: 42) ).to eq OpenStruct.new(a: 42)
23
+ end
24
+
25
+ context "when class with default_opts" do
26
+ let(:default_opts) { {a: 1, b: 2} }
27
+
28
+ it "initializes class with opts merged with default_opts" do
29
+ expect( factory.create(:foo, b: 6) ).to eq OpenStruct.new(a: 1, b: 6)
30
+ end
31
+ end
32
+ end
33
+
34
+ context "when class with default_opts" do
35
+ let(:default_opts) { {a: 1, b: 2} }
36
+
37
+ it "initializes class with default_opts" do
38
+ expect( factory.create(:foo) ).to eq OpenStruct.new(default_opts)
39
+ end
40
+ end
41
+
42
+ context "with unregistered name" do
43
+ it { expect { factory.create(:unknown) }.to raise_error(ArgumentError) }
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,95 @@
1
+ require 'forwardable'
2
+
3
+ module DocTest
4
+ describe HTML::Converter do
5
+ extend Forwardable
6
+
7
+ def_delegator :converter, :convert_examples
8
+
9
+ subject(:converter) { described_class.new }
10
+
11
+ describe '#convert_examples' do
12
+
13
+ let(:input) { Example.new 's:dummy', content: '*chunky* bacon' }
14
+ let(:output) { Example.new 's:dummy', content: '<b>chunky</b> bacon', opts: opts }
15
+ let(:opts) { {dummy: 'value'} }
16
+ let(:converter_opts) { {header_footer: false} }
17
+
18
+ subject(:result) { convert_examples input, output }
19
+
20
+ let :rendered do
21
+ <<-EOF
22
+ <section>
23
+ <h1>Title</h1>
24
+ <div>
25
+ <p><b>Chunky</b> bacon</p>
26
+ </div>
27
+ <code>meh</code>
28
+ </section>
29
+ <div>
30
+ <p>why?</p>
31
+ </div>
32
+ EOF
33
+ end
34
+
35
+ before do
36
+ expect(converter).to receive(:convert)
37
+ .with(input.content, converter_opts).and_return(rendered)
38
+ end
39
+
40
+ it 'returns array of converted input content and output content'
41
+
42
+ context 'with :exclude option' do
43
+ let(:opts) { {exclude: ['.//p', './/code']} }
44
+
45
+ it 'returns content without HTML (sub)elements specified by XPath' do
46
+ expect(result.first.gsub(/\s*/, '')).to eq \
47
+ '<section><h1>Title</h1><div></div></section><div></div>'
48
+ end
49
+ end
50
+
51
+ context 'with :include option' do
52
+ let(:opts) { {include: ['.//p']} }
53
+
54
+ it 'returns content with only HTML (sub)elements specified by XPath' do
55
+ expect(result.first.gsub(/\s*/, '')).to eq '<p><b>Chunky</b>bacon</p><p>why?</p>'
56
+ end
57
+ end
58
+
59
+ context 'with :header_footer option' do
60
+ let(:opts) { {header_footer: true} }
61
+ let(:converter_opts) { {header_footer: true} }
62
+
63
+ it 'renders content with :header_footer => true' do
64
+ convert_examples input, output
65
+ end
66
+ end
67
+
68
+ context 'with example named /^document.*/' do
69
+ let(:input) { Example.new 'document:dummy', content: '*chunky* bacon' }
70
+ let(:converter_opts) { {header_footer: true} }
71
+
72
+ it 'renders content with :header_footer => true' do
73
+ convert_examples input, output
74
+ end
75
+ end
76
+
77
+ context 'with example named /inline_.*/' do
78
+ let(:input) { Example.new 'inline_quoted:dummy', content: '*chunky* bacon' }
79
+ let(:rendered) { '<p><b>chunky</b> bacon</p>' }
80
+
81
+ it 'returns content without top-level <p> tags' do
82
+ expect(result.first).to eq '<b>chunky</b> bacon'
83
+ end
84
+
85
+ context 'with :include option' do
86
+ let(:opts) { {include: ['.//b']} }
87
+
88
+ it 'preferes the include option' do
89
+ expect(result.first).to eq '<b>chunky</b>'
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
@@ -1,4 +1,4 @@
1
- describe DocTest::HTML::Normalizer do
1
+ describe DocTest::HtmlNormalizer do
2
2
 
3
3
  [Nokogiri::HTML::Document, Nokogiri::HTML::DocumentFragment].each do |klass|
4
4
  it "HtmlNormalizer should be included in #{klass}" do
@@ -1,15 +1,10 @@
1
- require 'forwardable'
2
-
3
1
  using Corefines::String::unindent
4
2
 
5
- describe DocTest::Asciidoc::ExamplesSuite do
6
- extend Forwardable
7
-
8
- it_should_behave_like DocTest::BaseExamplesSuite
3
+ describe DocTest::IO::Asciidoc do
9
4
 
10
- def_delegator :suite, :create_example
5
+ it_should_behave_like DocTest::IO::Base
11
6
 
12
- subject(:suite) { described_class.new }
7
+ subject(:suite) { described_class.new(file_ext: '.adoc') }
13
8
 
14
9
 
15
10
  describe '#initialize' do
@@ -1,15 +1,10 @@
1
- require 'forwardable'
2
-
3
1
  using Corefines::String::unindent
4
2
 
5
- describe DocTest::HTML::ExamplesSuite do
6
- extend Forwardable
7
-
8
- it_should_behave_like DocTest::BaseExamplesSuite
3
+ describe DocTest::IO::XML do
9
4
 
10
- def_delegator :suite, :create_example
5
+ it_should_behave_like DocTest::IO::Base
11
6
 
12
- subject(:suite) { described_class.new }
7
+ subject(:suite) { described_class.new(file_ext: '.html') }
13
8
 
14
9
 
15
10
  describe '#initialize' do
@@ -143,102 +138,4 @@ describe DocTest::HTML::ExamplesSuite do
143
138
  end
144
139
  end
145
140
  end
146
-
147
-
148
- describe '#convert_example' do
149
-
150
- let(:input) { create_example 's:dummy', content: '*chunky* bacon' }
151
- let(:opts) { {dummy: 'value'} }
152
- let(:renderer) { double 'AsciidocRenderer' }
153
- let(:converter_opts) { {header_footer: false} }
154
-
155
- subject(:result) { suite.convert_example input, opts, renderer }
156
-
157
- let :rendered do
158
- <<-EOF
159
- <section>
160
- <h1>Title</h1>
161
- <div>
162
- <p><b>Chunky</b> bacon</p>
163
- </div>
164
- <code>meh</code>
165
- </section>
166
- <div>
167
- <p>why?</p>
168
- </div>
169
- EOF
170
- end
171
-
172
- before do
173
- expect(renderer).to receive(:convert)
174
- .with(input.content, converter_opts).and_return(rendered)
175
- end
176
-
177
- it 'returns instance of HTML::Example' do
178
- is_expected.to be_instance_of DocTest::HTML::Example
179
- end
180
-
181
- it 'returns Example with the same name as input_example' do
182
- expect(result.name).to eq input.name
183
- end
184
-
185
- it 'returns Example with the given opts' do
186
- expect(result.opts).to eq opts
187
- end
188
-
189
- context 'with :exclude option' do
190
- let(:opts) { {exclude: ['.//p', './/code']} }
191
-
192
- it 'returns content without HTML (sub)elements specified by XPath' do
193
- expect(result.content.gsub(/\s*/, '')).to eq \
194
- '<section><h1>Title</h1><div></div></section><div></div>'
195
- end
196
- end
197
-
198
- context 'with :include option' do
199
- let(:opts) { {include: ['.//p']} }
200
-
201
- it 'returns content with only HTML (sub)elements specified by XPath' do
202
- expect(result.content.gsub(/\s*/, '')).to eq '<p><b>Chunky</b>bacon</p><p>why?</p>'
203
- end
204
- end
205
-
206
- context 'with :header_footer option' do
207
- let(:opts) { {header_footer: true} }
208
-
209
- it 'renders content with :header_footer => true' do
210
- suite.convert_example input, {}, renderer
211
- end
212
- end
213
-
214
- context 'with example named /^document.*/' do
215
- let(:input) { create_example 'document:dummy', content: '*chunky* bacon' }
216
- let(:converter_opts) { {header_footer: true} }
217
-
218
- it 'renders content with :header_footer => true' do
219
- suite.convert_example input, {}, renderer
220
- end
221
- end
222
-
223
- context 'with example named /inline_.*/' do
224
- let(:input) { create_example 'inline_quoted:dummy', content: '*chunky* bacon' }
225
- let(:rendered) { '<p><b>chunky</b> bacon</p>' }
226
-
227
- it 'returns content without top-level <p> tags' do
228
- expect(result.content).to eq '<b>chunky</b> bacon'
229
- end
230
-
231
- it 'does not add implicit include into returned example' do
232
- expect(result.opts).to_not include :include
233
- end
234
-
235
- context 'with :include option' do
236
- let(:opts) { {include: ['.//b']} }
237
-
238
- it 'preferes the include option' do
239
- expect(result.content).to eq '<b>chunky</b>'
240
- end
241
- end
242
- end
243
- end
244
141
  end