asciidoctor-doctest 1.5.2.0 → 2.0.0.beta.1

Sign up to get free protection for your applications and to get access to all the features.
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