asciidoctor-doctest 1.5.0 → 1.5.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.adoc +32 -0
- data/README.adoc +17 -12
- data/Rakefile +28 -3
- data/features/README +1 -0
- data/features/fixtures/html-slim/Rakefile +15 -0
- data/features/fixtures/html-slim/examples/asciidoc/block_quote.adoc +12 -0
- data/features/fixtures/html-slim/examples/asciidoc/document.adoc +4 -0
- data/features/fixtures/html-slim/examples/asciidoc/inline_quoted.adoc +2 -0
- data/features/fixtures/html-slim/examples/html/block_quote.html +26 -0
- data/features/fixtures/html-slim/examples/html/document.html +7 -0
- data/features/fixtures/html-slim/templates/block_paragraph.html.slim +1 -0
- data/features/fixtures/html-slim/templates/block_quote.html.slim +7 -0
- data/features/fixtures/html-slim/templates/document.html.slim +13 -0
- data/features/fixtures/html-slim/templates/embedded.html.slim +3 -0
- data/features/fixtures/html-slim/templates/inline_quoted.html.slim +8 -0
- data/features/fixtures/html-slim/test/html_test.rb +6 -0
- data/features/fixtures/html-slim/test/test_helper.rb +5 -0
- data/features/generator_html.feature +171 -0
- data/features/step_definitions/doctest_steps.rb +5 -0
- data/features/support/env.rb +21 -0
- data/features/test_html.feature +59 -0
- data/lib/asciidoctor/doctest/asciidoc_renderer.rb +19 -14
- data/lib/asciidoctor/doctest/base_example.rb +11 -5
- data/lib/asciidoctor/doctest/base_examples_suite.rb +1 -1
- data/lib/asciidoctor/doctest/generator.rb +3 -1
- data/lib/asciidoctor/doctest/generator_task.rb +8 -5
- data/lib/asciidoctor/doctest/html/example.rb +1 -1
- data/lib/asciidoctor/doctest/html/examples_suite.rb +2 -2
- data/lib/asciidoctor/doctest/minitest_diffy.rb +1 -1
- data/lib/asciidoctor/doctest/test.rb +4 -1
- data/lib/asciidoctor/doctest/version.rb +1 -1
- data/spec/asciidoc_renderer_spec.rb +98 -0
- data/spec/base_example_spec.rb +4 -1
- data/spec/html/examples_suite_spec.rb +5 -5
- data/spec/minitest_diffy_spec.rb +57 -0
- data/spec/shared_examples/base_examples_suite.rb +0 -20
- data/spec/support/fakefs.rb +19 -0
- metadata +151 -74
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'aruba'
|
2
|
+
require 'aruba/cucumber'
|
3
|
+
require 'asciidoctor/doctest'
|
4
|
+
require 'fileutils'
|
5
|
+
require 'rspec/expectations'
|
6
|
+
|
7
|
+
Dir["#{__dir__}/../step_definitions/**/*.rb"].each { |file| require file }
|
8
|
+
|
9
|
+
PROJECT_DIR = File.expand_path('../../', __dir__)
|
10
|
+
FIXTURES_DIR = File.expand_path('../fixtures', __dir__)
|
11
|
+
TEMP_DIR = File.join(PROJECT_DIR, 'tmp/aruba')
|
12
|
+
|
13
|
+
Before do
|
14
|
+
FileUtils.mkdir_p TEMP_DIR
|
15
|
+
# overwrite Aruba's default temp directory location
|
16
|
+
@dirs = [TEMP_DIR]
|
17
|
+
end
|
18
|
+
|
19
|
+
After do
|
20
|
+
FileUtils.rm_rf(TEMP_DIR) if Dir.exist? TEMP_DIR
|
21
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
Feature: Testing a custom HTML backend
|
2
|
+
|
3
|
+
Background:
|
4
|
+
Given I do have a template-based HTML backend with DocTest
|
5
|
+
|
6
|
+
Scenario: Some examples do not match the expected output
|
7
|
+
When I run `bundle exec rake test`
|
8
|
+
Then the output should contain:
|
9
|
+
"""
|
10
|
+
1) Failure:
|
11
|
+
TestHtml :: block_quote : with_attribution:
|
12
|
+
Failing example..
|
13
|
+
|
14
|
+
<blockquote>A person who never made a mistake
|
15
|
+
<em>never</em>
|
16
|
+
tried anything new.</blockquote>
|
17
|
+
E <span>Albert Einstein</span>
|
18
|
+
A <div class="attribution">— Albert Einstein</div>
|
19
|
+
</div>
|
20
|
+
"""
|
21
|
+
And the output should contain:
|
22
|
+
"""
|
23
|
+
2) Failure:
|
24
|
+
TestHtml :: document : title_with_author:
|
25
|
+
This example should fail..
|
26
|
+
|
27
|
+
<div id="header">
|
28
|
+
<h1>The Dangerous and Thrilling Documentation Chronicles</h1>
|
29
|
+
E <div id="author">Kismet Rainbow Chameleon</div>
|
30
|
+
A <div class="details"><span id="author">Kismet Rainbow Chameleon</span></div>
|
31
|
+
</div>
|
32
|
+
"""
|
33
|
+
And the output should contain:
|
34
|
+
"""
|
35
|
+
5 runs, 3 assertions, 2 failures, 0 errors, 2 skips
|
36
|
+
"""
|
37
|
+
|
38
|
+
Scenario: A necessary template is missing and fallback to the built-in converter is disabled
|
39
|
+
When I remove the file "templates/inline_quoted.html.slim"
|
40
|
+
And I run `bundle exec rake test`
|
41
|
+
Then the output should contain:
|
42
|
+
"""
|
43
|
+
Could not find a custom template to handle template_name: inline_quoted
|
44
|
+
"""
|
45
|
+
And the output should contain:
|
46
|
+
"""
|
47
|
+
1) Failure:
|
48
|
+
TestHtml :: block_quote : with_attribution:
|
49
|
+
Failing example..
|
50
|
+
|
51
|
+
<div class="quoteblock">
|
52
|
+
E <blockquote>A person who never made a mistake
|
53
|
+
E <em>never</em>
|
54
|
+
E tried anything new.</blockquote>
|
55
|
+
E <span>Albert Einstein</span>
|
56
|
+
A <blockquote>A person who never made a mistake --TEMPLATE NOT FOUND-- tried anything new.</blockquote>
|
57
|
+
A <div class="attribution">— Albert Einstein</div>
|
58
|
+
</div>
|
59
|
+
"""
|
@@ -5,7 +5,7 @@ require 'asciidoctor/converter/template'
|
|
5
5
|
module Asciidoctor
|
6
6
|
module DocTest
|
7
7
|
##
|
8
|
-
# This class is basically a wrapper for +Asciidoctor.
|
8
|
+
# This class is basically a wrapper for +Asciidoctor.convert+ that allows to
|
9
9
|
# preset and validate some common parameters.
|
10
10
|
class AsciidocRenderer
|
11
11
|
|
@@ -33,45 +33,50 @@ module Asciidoctor
|
|
33
33
|
# @raise [ArgumentError] if some path from the +template_dirs+ doesn't
|
34
34
|
# exist or is not a directory.
|
35
35
|
#
|
36
|
-
def initialize(backend_name:
|
36
|
+
def initialize(backend_name: '', converter: nil, template_dirs: [],
|
37
37
|
templates_fallback: false)
|
38
38
|
|
39
39
|
@backend_name = backend_name.freeze
|
40
40
|
@converter = converter
|
41
|
-
@converter ||=
|
41
|
+
@converter ||= NoFallbackTemplateConverter unless template_dirs.empty? || templates_fallback
|
42
42
|
|
43
43
|
template_dirs = Array.wrap(template_dirs).freeze
|
44
|
-
|
45
|
-
|
46
|
-
fail ArgumentError, "Templates directory '#{path}' doesn't exist!"
|
44
|
+
template_dirs.each do |path|
|
45
|
+
fail ArgumentError, "Templates directory '#{path}' doesn't exist!" unless Dir.exist? path
|
47
46
|
end
|
48
47
|
@template_dirs = template_dirs unless template_dirs.empty?
|
49
48
|
end
|
50
49
|
|
51
50
|
##
|
52
|
-
#
|
53
|
-
# tested backend.
|
51
|
+
# Converts the given +text+ into AsciiDoc syntax with Asciidoctor using
|
52
|
+
# the tested backend.
|
54
53
|
#
|
55
54
|
# @param text [#to_s] the input text in AsciiDoc syntax.
|
56
55
|
# @param opts [Hash] options to pass to Asciidoctor.
|
57
|
-
# @return [String]
|
56
|
+
# @return [String] converted input.
|
58
57
|
#
|
59
|
-
def
|
60
|
-
|
58
|
+
def convert(text, opts = {})
|
59
|
+
converter_opts = {
|
61
60
|
safe: :safe,
|
62
61
|
backend: backend_name,
|
63
62
|
converter: converter,
|
64
63
|
template_dirs: template_dirs
|
65
64
|
}.merge(opts)
|
66
65
|
|
67
|
-
Asciidoctor.
|
66
|
+
Asciidoctor.convert(text.to_s, converter_opts)
|
68
67
|
end
|
68
|
+
|
69
|
+
# Alias for backward compatibility.
|
70
|
+
alias_method :render, :convert
|
69
71
|
end
|
70
72
|
|
71
73
|
##
|
72
74
|
# @private
|
73
|
-
#
|
74
|
-
|
75
|
+
# TemplateConverter that doesn't fallback to a built-in converter when
|
76
|
+
# no template for a node is found.
|
77
|
+
class NoFallbackTemplateConverter < SimpleDelegator
|
78
|
+
# NOTE: It didn't work with subclass of TemplateConverter instead of
|
79
|
+
# delegator, I have no idea why.
|
75
80
|
|
76
81
|
# Placeholder to be written in a rendered output in place of the node's
|
77
82
|
# content that cannot be rendered due to missing template.
|
@@ -102,7 +102,7 @@ module Asciidoctor
|
|
102
102
|
|
103
103
|
##
|
104
104
|
# @note The default implementation returns content as-is; subclasses
|
105
|
-
#
|
105
|
+
# should override this method.
|
106
106
|
#
|
107
107
|
# @return [String] copy of the content in a form that is suitable for
|
108
108
|
# semantic comparison with another content.
|
@@ -112,15 +112,21 @@ module Asciidoctor
|
|
112
112
|
end
|
113
113
|
|
114
114
|
##
|
115
|
-
# @note
|
116
|
-
# should override this method.
|
115
|
+
# @note (see #content_normalized)
|
117
116
|
#
|
118
|
-
# @return [String] a human-readable (formatted)
|
117
|
+
# @return [String] copy of the content in a human-readable (formatted)
|
118
|
+
# shape for pretty print.
|
119
119
|
#
|
120
|
-
def
|
120
|
+
def content_pretty
|
121
121
|
content.dup
|
122
122
|
end
|
123
123
|
|
124
|
+
##
|
125
|
+
# @return (see #content_pretty)
|
126
|
+
def to_s
|
127
|
+
content_pretty
|
128
|
+
end
|
129
|
+
|
124
130
|
##
|
125
131
|
# @param other the object to compare with.
|
126
132
|
# @return [Boolean] +true+ if +self+ and +other+ equals in attributes
|
@@ -62,7 +62,7 @@ module Asciidoctor
|
|
62
62
|
# @abstract
|
63
63
|
# @param example [BaseExample] the input example to convert.
|
64
64
|
# @param opts [Hash] the options to pass to a new example.
|
65
|
-
# @param renderer [#
|
65
|
+
# @param renderer [#convert]
|
66
66
|
# @return [BaseExample]
|
67
67
|
# :nocov:
|
68
68
|
def convert_example(example, opts, renderer)
|
@@ -17,7 +17,7 @@ module Asciidoctor
|
|
17
17
|
# {BaseExamplesSuite} subclass to read the reference input
|
18
18
|
# examples.
|
19
19
|
#
|
20
|
-
# @param renderer [#
|
20
|
+
# @param renderer [#convert]
|
21
21
|
#
|
22
22
|
# @param pattern [String] glob-like pattern to select examples to
|
23
23
|
# (re)generate (see {BaseExample#name_match?}).
|
@@ -42,6 +42,8 @@ module Asciidoctor
|
|
42
42
|
log["Unknown %s, doesn't exist in input examples!"]
|
43
43
|
else
|
44
44
|
rendered = output_suite.convert_example(input, output.opts, renderer)
|
45
|
+
rendered.desc = output.desc
|
46
|
+
|
45
47
|
if output.empty?
|
46
48
|
log['Generating %s', :magenta]
|
47
49
|
updated << rendered
|
@@ -39,24 +39,27 @@ module Asciidoctor
|
|
39
39
|
# (default: *:*).
|
40
40
|
attr_accessor :pattern
|
41
41
|
|
42
|
-
# @return [Hash] options for Asciidoctor
|
42
|
+
# @return [Hash] options for Asciidoctor converter.
|
43
43
|
# @see AsciidocRenderer#initialize
|
44
|
-
attr_accessor :
|
44
|
+
attr_accessor :converter_opts
|
45
45
|
|
46
46
|
# @return [String] title of the task's description.
|
47
47
|
attr_accessor :title
|
48
48
|
|
49
|
+
# Alias for backward compatibility.
|
50
|
+
alias_method :renderer_opts, :converter_opts
|
51
|
+
|
49
52
|
|
50
53
|
##
|
51
54
|
# @param name [#to_sym] name of the task.
|
52
55
|
# @yield The block to configure this task.
|
53
56
|
def initialize(name)
|
54
57
|
@name = name
|
55
|
-
@examples_path = DocTest.examples_path
|
58
|
+
@examples_path = DocTest.examples_path.dup
|
56
59
|
@force = false
|
57
60
|
@input_suite = nil
|
58
61
|
@output_suite = nil
|
59
|
-
@
|
62
|
+
@converter_opts = {}
|
60
63
|
@pattern = '*:*'
|
61
64
|
@title = "Generate testing examples #{pattern}#{" for #{name}" if name != :generate}."
|
62
65
|
|
@@ -68,7 +71,7 @@ module Asciidoctor
|
|
68
71
|
end
|
69
72
|
|
70
73
|
@input_suite ||= Asciidoc::ExamplesSuite.new(examples_path: @examples_path)
|
71
|
-
@renderer ||= AsciidocRenderer.new(
|
74
|
+
@renderer ||= AsciidocRenderer.new(converter_opts)
|
72
75
|
|
73
76
|
define
|
74
77
|
end
|
@@ -81,7 +81,7 @@ module Asciidoctor::DocTest
|
|
81
81
|
def convert_example(example, opts, renderer)
|
82
82
|
header_footer = !!opts[:header_footer] || example.name.start_with?('document')
|
83
83
|
|
84
|
-
html = renderer.
|
84
|
+
html = renderer.convert(example.content, header_footer: header_footer)
|
85
85
|
html = parse_html(html, !header_footer)
|
86
86
|
|
87
87
|
# When asserting inline examples, ignore paragraph "wrapper".
|
@@ -98,7 +98,7 @@ module Asciidoctor::DocTest
|
|
98
98
|
|
99
99
|
html.normalize!
|
100
100
|
|
101
|
-
create_example example.name, content: html
|
101
|
+
create_example example.name, content: HtmlBeautifier.beautify(html), opts: opts
|
102
102
|
end
|
103
103
|
|
104
104
|
private
|
@@ -14,10 +14,13 @@ module Asciidoctor
|
|
14
14
|
|
15
15
|
##
|
16
16
|
# (see AsciidocRenderer#initialize)
|
17
|
-
def self.
|
17
|
+
def self.converter_opts(**kwargs)
|
18
18
|
@renderer = AsciidocRenderer.new(**kwargs)
|
19
19
|
end
|
20
20
|
|
21
|
+
# Alias for backward compatibility.
|
22
|
+
alias_class_method :renderer_opts, :converter_opts
|
23
|
+
|
21
24
|
##
|
22
25
|
# Generates tests for all the input/output examples.
|
23
26
|
# When some output example is missing, it's reported as skipped test.
|
@@ -0,0 +1,98 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
|
3
|
+
describe DocTest::AsciidocRenderer do
|
4
|
+
|
5
|
+
subject { described_class }
|
6
|
+
|
7
|
+
it { is_expected.to have_method :convert, :render }
|
8
|
+
|
9
|
+
|
10
|
+
describe '#initialize' do
|
11
|
+
|
12
|
+
context 'with defaults' do
|
13
|
+
subject { described_class.new }
|
14
|
+
it { is_expected.to have_attributes backend_name: '', converter: nil, template_dirs: nil }
|
15
|
+
end
|
16
|
+
|
17
|
+
context 'with backend_name' do
|
18
|
+
subject { described_class.new(backend_name: 'html5') }
|
19
|
+
it { is_expected.to have_attributes backend_name: 'html5' }
|
20
|
+
end
|
21
|
+
|
22
|
+
context 'with template_dirs' do
|
23
|
+
include FakeFS::SpecHelpers
|
24
|
+
|
25
|
+
subject { described_class.new(template_dirs: template_dirs) }
|
26
|
+
let(:template_dirs) { ['/tmp/html5'] }
|
27
|
+
|
28
|
+
before { FileUtils.mkpath template_dirs[0] }
|
29
|
+
|
30
|
+
context 'that exists' do
|
31
|
+
it do
|
32
|
+
is_expected.to have_attributes(
|
33
|
+
template_dirs: template_dirs,
|
34
|
+
converter: DocTest::NoFallbackTemplateConverter
|
35
|
+
)
|
36
|
+
end
|
37
|
+
|
38
|
+
context 'and templates_fallback = true' do
|
39
|
+
subject { described_class.new(template_dirs: template_dirs, templates_fallback: true) }
|
40
|
+
it { is_expected.to have_attributes template_dirs: template_dirs, converter: nil }
|
41
|
+
end
|
42
|
+
|
43
|
+
context 'and custom converter' do
|
44
|
+
subject { described_class.new(template_dirs: template_dirs, converter: converter) }
|
45
|
+
let(:converter) { Asciidoctor::Converter::TemplateConverter }
|
46
|
+
|
47
|
+
it { is_expected.to have_attributes template_dirs: template_dirs, converter: converter }
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
context "that doesn't exist" do
|
52
|
+
let(:template_dirs) { ['/tmp/html5', '/tmp/revealjs'] }
|
53
|
+
|
54
|
+
it { expect { subject }.to raise_error ArgumentError }
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
|
61
|
+
describe DocTest::NoFallbackTemplateConverter do
|
62
|
+
|
63
|
+
subject(:delegator) { described_class.new('html5', template_dirs: ['/tmp/html5']) }
|
64
|
+
|
65
|
+
describe '#convert' do
|
66
|
+
|
67
|
+
let(:converter) { delegator.__getobj__ }
|
68
|
+
let(:node) { double('Node', node_name: 'block_foo') }
|
69
|
+
|
70
|
+
before do
|
71
|
+
expect(converter).to receive(:handles?).with('block_foo').and_return(handles)
|
72
|
+
end
|
73
|
+
|
74
|
+
context 'when template is not found' do
|
75
|
+
let(:handles) { false }
|
76
|
+
|
77
|
+
it 'returns a not found marker instead of converted node' do
|
78
|
+
expect(converter).to_not receive(:convert)
|
79
|
+
expect(delegator.convert node).to eq described_class::NOT_FOUND_MARKER
|
80
|
+
end
|
81
|
+
|
82
|
+
it 'prints a warning on stderr' do
|
83
|
+
expect { delegator.convert node }.to output(/Could not find a custom template/i).to_stderr
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
context 'when template is found' do
|
88
|
+
let(:handles) { true }
|
89
|
+
|
90
|
+
it 'delegates to the original #convert and returns result' do
|
91
|
+
expect(converter).to receive(:convert)
|
92
|
+
.with(node, 'block_foo', {}).and_return('allons-y!')
|
93
|
+
|
94
|
+
expect(delegator.convert node).to eq 'allons-y!'
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
data/spec/base_example_spec.rb
CHANGED
@@ -2,7 +2,10 @@ describe DocTest::BaseExample do
|
|
2
2
|
|
3
3
|
subject(:o) { described_class.new ['foo', 'bar'] }
|
4
4
|
|
5
|
-
it
|
5
|
+
it do
|
6
|
+
is_expected.to respond_to :group_name, :local_name, :desc, :opts, :content,
|
7
|
+
:content_normalized, :content_pretty
|
8
|
+
end
|
6
9
|
|
7
10
|
describe '#name' do
|
8
11
|
|
@@ -155,7 +155,7 @@ describe DocTest::HTML::ExamplesSuite do
|
|
155
155
|
let(:input) { create_example 's:dummy', content: '*chunky* bacon' }
|
156
156
|
let(:opts) { {dummy: 'value'} }
|
157
157
|
let(:renderer) { double 'AsciidocRenderer' }
|
158
|
-
let(:
|
158
|
+
let(:converter_opts) { {header_footer: false} }
|
159
159
|
|
160
160
|
subject(:result) { suite.convert_example input, opts, renderer }
|
161
161
|
|
@@ -175,8 +175,8 @@ describe DocTest::HTML::ExamplesSuite do
|
|
175
175
|
end
|
176
176
|
|
177
177
|
before do
|
178
|
-
expect(renderer).to receive(:
|
179
|
-
.with(input.content,
|
178
|
+
expect(renderer).to receive(:convert)
|
179
|
+
.with(input.content, converter_opts).and_return(rendered)
|
180
180
|
end
|
181
181
|
|
182
182
|
it 'returns instance of HTML::Example' do
|
@@ -218,7 +218,7 @@ describe DocTest::HTML::ExamplesSuite do
|
|
218
218
|
|
219
219
|
context 'with example named /^document.*/' do
|
220
220
|
let(:input) { create_example 'document:dummy', content: '*chunky* bacon' }
|
221
|
-
let(:
|
221
|
+
let(:converter_opts) { {header_footer: true} }
|
222
222
|
|
223
223
|
it 'renders content with :header_footer => true' do
|
224
224
|
suite.convert_example input, {}, renderer
|
@@ -230,7 +230,7 @@ describe DocTest::HTML::ExamplesSuite do
|
|
230
230
|
let(:rendered) { '<p><b>chunky</b> bacon</p>' }
|
231
231
|
|
232
232
|
it 'returns content without top-level <p> tags' do
|
233
|
-
expect(result.content).to eq '<b>chunky</b> bacon'
|
233
|
+
expect(result.content.gsub(/\s+/, ' ')).to eq '<b>chunky</b> bacon'
|
234
234
|
end
|
235
235
|
|
236
236
|
it 'does not add implicit include into returned example' do
|