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.
Files changed (71) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.adoc +0 -0
  3. data/LICENSE +21 -0
  4. data/README.adoc +327 -0
  5. data/Rakefile +12 -0
  6. data/data/examples/asciidoc/block_admonition.adoc +27 -0
  7. data/data/examples/asciidoc/block_audio.adoc +13 -0
  8. data/data/examples/asciidoc/block_colist.adoc +46 -0
  9. data/data/examples/asciidoc/block_dlist.adoc +99 -0
  10. data/data/examples/asciidoc/block_example.adoc +21 -0
  11. data/data/examples/asciidoc/block_floating_title.adoc +27 -0
  12. data/data/examples/asciidoc/block_image.adoc +28 -0
  13. data/data/examples/asciidoc/block_listing.adoc +68 -0
  14. data/data/examples/asciidoc/block_literal.adoc +30 -0
  15. data/data/examples/asciidoc/block_olist.adoc +55 -0
  16. data/data/examples/asciidoc/block_open.adoc +40 -0
  17. data/data/examples/asciidoc/block_outline.adoc +60 -0
  18. data/data/examples/asciidoc/block_page_break.adoc +6 -0
  19. data/data/examples/asciidoc/block_paragraph.adoc +17 -0
  20. data/data/examples/asciidoc/block_pass.adoc +5 -0
  21. data/data/examples/asciidoc/block_preamble.adoc +19 -0
  22. data/data/examples/asciidoc/block_quote.adoc +30 -0
  23. data/data/examples/asciidoc/block_sidebar.adoc +22 -0
  24. data/data/examples/asciidoc/block_stem.adoc +28 -0
  25. data/data/examples/asciidoc/block_table.adoc +168 -0
  26. data/data/examples/asciidoc/block_thematic_break.adoc +2 -0
  27. data/data/examples/asciidoc/block_toc.adoc +50 -0
  28. data/data/examples/asciidoc/block_ulist.adoc +43 -0
  29. data/data/examples/asciidoc/block_verse.adoc +37 -0
  30. data/data/examples/asciidoc/block_video.adoc +24 -0
  31. data/data/examples/asciidoc/document.adoc +51 -0
  32. data/data/examples/asciidoc/embedded.adoc +10 -0
  33. data/data/examples/asciidoc/inline_anchor.adoc +27 -0
  34. data/data/examples/asciidoc/inline_break.adoc +8 -0
  35. data/data/examples/asciidoc/inline_button.adoc +3 -0
  36. data/data/examples/asciidoc/inline_callout.adoc +5 -0
  37. data/data/examples/asciidoc/inline_footnote.adoc +9 -0
  38. data/data/examples/asciidoc/inline_image.adoc +44 -0
  39. data/data/examples/asciidoc/inline_kbd.adoc +7 -0
  40. data/data/examples/asciidoc/inline_menu.adoc +11 -0
  41. data/data/examples/asciidoc/inline_quoted.adoc +59 -0
  42. data/data/examples/asciidoc/section.adoc +74 -0
  43. data/doc/img/doctest-diag.odf +0 -0
  44. data/doc/img/doctest-diag.svg +56 -0
  45. data/doc/img/failing-test-term.gif +0 -0
  46. data/lib/asciidoctor-doctest.rb +1 -0
  47. data/lib/asciidoctor/doctest.rb +30 -0
  48. data/lib/asciidoctor/doctest/asciidoc/examples_suite.rb +44 -0
  49. data/lib/asciidoctor/doctest/asciidoc_renderer.rb +103 -0
  50. data/lib/asciidoctor/doctest/base_example.rb +161 -0
  51. data/lib/asciidoctor/doctest/base_examples_suite.rb +188 -0
  52. data/lib/asciidoctor/doctest/core_ext.rb +49 -0
  53. data/lib/asciidoctor/doctest/generator.rb +63 -0
  54. data/lib/asciidoctor/doctest/generator_task.rb +111 -0
  55. data/lib/asciidoctor/doctest/html/example.rb +21 -0
  56. data/lib/asciidoctor/doctest/html/examples_suite.rb +111 -0
  57. data/lib/asciidoctor/doctest/html/html_beautifier.rb +17 -0
  58. data/lib/asciidoctor/doctest/html/normalizer.rb +118 -0
  59. data/lib/asciidoctor/doctest/minitest_diffy.rb +74 -0
  60. data/lib/asciidoctor/doctest/test.rb +120 -0
  61. data/lib/asciidoctor/doctest/version.rb +5 -0
  62. data/spec/asciidoc/examples_suite_spec.rb +99 -0
  63. data/spec/base_example_spec.rb +176 -0
  64. data/spec/core_ext_spec.rb +67 -0
  65. data/spec/html/examples_suite_spec.rb +249 -0
  66. data/spec/html/normalizer_spec.rb +70 -0
  67. data/spec/shared_examples/base_examples_suite.rb +262 -0
  68. data/spec/spec_helper.rb +33 -0
  69. data/spec/support/matchers.rb +7 -0
  70. data/spec/test_spec.rb +164 -0
  71. metadata +360 -0
@@ -0,0 +1,74 @@
1
+ require 'colorize'
2
+ require 'diffy'
3
+
4
+ module Asciidoctor
5
+ module DocTest
6
+ ##
7
+ # Module to be included into +Minitest::Test+ to use Diffy for diff.
8
+ module MinitestDiffy
9
+
10
+ # @private
11
+ def self.included(base)
12
+ base.make_my_diffs_pretty!
13
+ end
14
+
15
+ ##
16
+ # Returns diff between +exp+ and +act+ (if needed) using Diffy.
17
+ #
18
+ # @note Overrides method from +Minitest::Assertions+.
19
+ def diff(exp, act)
20
+ expected = mu_pp_for_diff(exp)
21
+ actual = mu_pp_for_diff(act)
22
+
23
+ if need_diff? expected, actual
24
+ ::Diffy::Diff.new(expected, actual, context: 3).to_s
25
+ else
26
+ "Expected: #{mu_pp(exp)}\n Actual: #{mu_pp(act)}"
27
+ end
28
+ end
29
+
30
+ ##
31
+ # Returns +true+ if diff should be printed (using Diffy) for the given
32
+ # content, +false+ otherwise.
33
+ #
34
+ # @param expected [String]
35
+ # @param actual [String]
36
+ #
37
+ def need_diff?(expected, actual)
38
+ expected.include?("\n") ||
39
+ actual.include?("\n") ||
40
+ expected.size > 30 ||
41
+ actual.size > 30 ||
42
+ expected == actual
43
+ end
44
+ end
45
+ end
46
+ end
47
+
48
+ module Diffy
49
+ module Format
50
+
51
+ ##
52
+ # ANSI color output suitable for terminal, customized for minitest.
53
+ def minitest
54
+ padding = ' ' * 2
55
+ ary = map do |line|
56
+ case line
57
+ when /^(---|\+\+\+|\\\\)/
58
+ # ignore
59
+ when /^\\\s*No newline at end of file/
60
+ # ignore
61
+ when /^\+/
62
+ line.chomp.sub(/^\+/, 'A' + padding).red
63
+ when /^-/
64
+ line.chomp.sub(/^\-/, 'E' + padding).green
65
+ else
66
+ padding + line.chomp
67
+ end
68
+ end
69
+ "\n" + ary.join("\n")
70
+ end
71
+ end
72
+ end
73
+
74
+ Diffy::Diff.default_format = :minitest
@@ -0,0 +1,120 @@
1
+ require 'active_support/core_ext/object/blank'
2
+ require 'asciidoctor/doctest/asciidoc_renderer'
3
+ require 'asciidoctor/doctest/core_ext'
4
+ require 'asciidoctor/doctest/minitest_diffy'
5
+ require 'asciidoctor/doctest/asciidoc/examples_suite'
6
+ require 'minitest'
7
+
8
+ module Asciidoctor
9
+ module DocTest
10
+ ##
11
+ # Test class for Asciidoctor backends.
12
+ class Test < Minitest::Test
13
+ include MinitestDiffy
14
+
15
+ ##
16
+ # (see AsciidocRenderer#initialize)
17
+ def self.renderer_opts(**kwargs)
18
+ @renderer = AsciidocRenderer.new(**kwargs)
19
+ end
20
+
21
+ ##
22
+ # Generates tests for all the input/output examples.
23
+ # When some output example is missing, it's reported as skipped test.
24
+ #
25
+ # @param output_suite [BaseExamplesSuite, Class] the examples suite class
26
+ # (or its instance) to read the output examples from (i.e. an
27
+ # expected output).
28
+ # @param input_suite [BaseExamplesSuite, Class] the examples suite class
29
+ # (or its instance) to read the reference input examples from.
30
+ #
31
+ # If class is given, then it's instantiated with zero arguments.
32
+ #
33
+ def self.generate_tests!(output_suite, input_suite = Asciidoc::ExamplesSuite)
34
+ instance = ->(o) { o.is_a?(Class) ? o.new : o }
35
+ @output_suite = instance[output_suite]
36
+ @input_suite = instance[input_suite]
37
+
38
+ @input_suite.pair_with(@output_suite).each do |input, output|
39
+ next if input.empty?
40
+
41
+ define_test input.name do
42
+ if output.empty?
43
+ skip 'No expected output found'
44
+ else
45
+ test_example output, input
46
+ end
47
+ end
48
+ end
49
+ end
50
+
51
+ ##
52
+ # Defines a new test method.
53
+ #
54
+ # @param name [String] name of the test (method).
55
+ # @param block [Proc] the test method's body.
56
+ #
57
+ def self.define_test(name, &block)
58
+ (@test_methods ||= []) << name
59
+ define_method name, block
60
+ end
61
+
62
+ ##
63
+ # @!method self.test
64
+ # @see .define_test
65
+ alias_class_method :test, :define_test
66
+
67
+ ##
68
+ # @private
69
+ # @note Overrides method from +Minitest::Test+.
70
+ # @return [Array] names of the test methods to run.
71
+ def self.runnable_methods
72
+ (@test_methods || []) + super - ['test_example']
73
+ end
74
+
75
+ ##
76
+ # Tests if the given reference input is matching the expected output
77
+ # after conversion through the tested backend.
78
+ #
79
+ # @param output_exmpl [BaseExample] the expected output example.
80
+ # @param input_exmpl [BaseExample] the reference input example.
81
+ # @raise [Minitest::Assertion] if the assertion fails.
82
+ #
83
+ def test_example(output_exmpl, input_exmpl)
84
+ converted_exmpl = output_suite.convert_example(input_exmpl, output_exmpl.opts, renderer)
85
+ msg = output_exmpl.desc.presence || input_exmpl.desc
86
+
87
+ assert_equal output_exmpl, converted_exmpl, msg
88
+ end
89
+
90
+ ##
91
+ # @private
92
+ # @note Overrides method from +Minitest::Test+.
93
+ # @return [String] name of this test that will be printed in a report.
94
+ def location
95
+ prefix = self.class.name.split('::').last
96
+ name = self.name.sub(':', ' : ')
97
+ "#{prefix} :: #{name}"
98
+ end
99
+
100
+ ##
101
+ # @private
102
+ # Returns a human-readable (formatted) version of the asserted object.
103
+ #
104
+ # @note Overrides method from +Minitest::Assertions+.
105
+ #
106
+ # @param example [#to_s]
107
+ # @return [String]
108
+ #
109
+ def mu_pp(example)
110
+ example.to_s
111
+ end
112
+
113
+ [:input_suite, :output_suite, :renderer].each do |name|
114
+ define_method name do
115
+ self.class.instance_variable_get(:"@#{name}")
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,5 @@
1
+ module Asciidoctor
2
+ module DocTest
3
+ VERSION = '1.5.0'
4
+ end
5
+ end
@@ -0,0 +1,99 @@
1
+ require 'active_support/core_ext/string/strip'
2
+ require 'forwardable'
3
+
4
+ describe DocTest::Asciidoc::ExamplesSuite do
5
+ extend Forwardable
6
+
7
+ it_should_behave_like DocTest::BaseExamplesSuite
8
+
9
+ def_delegator :suite, :create_example
10
+
11
+ subject(:suite) { described_class.new }
12
+
13
+
14
+ describe '#initialize' do
15
+
16
+ it 'uses ".adoc" as default file_ext' do
17
+ expect(suite.file_ext).to eq '.adoc'
18
+ end
19
+ end
20
+
21
+
22
+ describe '#parse' do
23
+
24
+ context 'one example' do
25
+
26
+ shared_examples :example do
27
+ subject(:parsed) { suite.parse input, 's' }
28
+
29
+ it { is_expected.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
+ end
35
+
36
+ context 'with name only' do
37
+ let(:input) { "// .basic\n" }
38
+ let(:output) { create_example 's:basic' }
39
+
40
+ include_examples :example
41
+ end
42
+
43
+ context 'with multiline content' do
44
+ let :content do
45
+ <<-EOF.strip_heredoc
46
+ Paragraphs don't require
47
+ any special markup.
48
+
49
+ To begin a new one, separate it by blank line.
50
+ EOF
51
+ end
52
+
53
+ let(:input) { "// .multiline\n#{content}" }
54
+ let(:output) { create_example 's:multiline', content: content.chomp }
55
+
56
+ include_examples :example
57
+ end
58
+
59
+ context 'with description' do
60
+ let :input do
61
+ <<-EOF.strip_heredoc
62
+ // .strong
63
+ // This is a description,
64
+ // see?
65
+ *allons-y!*
66
+ EOF
67
+ end
68
+
69
+ let :output do
70
+ create_example 's:strong', content: '*allons-y!*',
71
+ desc: "This is a description,\nsee?"
72
+ end
73
+
74
+ include_examples :example
75
+ end
76
+ end
77
+
78
+ context 'multiple examples' do
79
+ let :input do
80
+ <<-EOF.strip_heredoc
81
+ // .basic
82
+ http://asciidoctor.org
83
+
84
+ // .xref
85
+ Refer to <<section-a>>.
86
+ EOF
87
+ end
88
+
89
+ subject(:parsed) { suite.parse input, 's' }
90
+
91
+ it { is_expected.to have(2).items }
92
+
93
+ it 'returns an array with parsed Example objects' do
94
+ expect(parsed[0]).to eql create_example('s:basic', content: 'http://asciidoctor.org')
95
+ expect(parsed[1]).to eql create_example('s:xref', content: 'Refer to <<section-a>>.')
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,176 @@
1
+ describe DocTest::BaseExample do
2
+
3
+ subject(:o) { described_class.new ['foo', 'bar'] }
4
+
5
+ it { is_expected.to respond_to :group_name, :local_name, :content, :desc, :opts }
6
+
7
+ describe '#name' do
8
+
9
+ it 'returns #{group_name}:#{local_name}' do
10
+ o.group_name = 'block_olist'
11
+ o.local_name = 'with-start'
12
+
13
+ expect(o.name).to eq "#{o.group_name}:#{o.local_name}"
14
+ end
15
+ end
16
+
17
+ describe '#name=' do
18
+
19
+ shared_examples :example do
20
+ it 'sets group_name and local_name' do
21
+ is_expected.to have_attributes group_name: 'section', local_name: 'basic'
22
+ end
23
+ end
24
+
25
+ context 'with String' do
26
+ before { o.name = 'section:basic' }
27
+ include_examples :example
28
+ end
29
+
30
+ context 'with Array' do
31
+ before { o.name = ['section', 'basic'] }
32
+ include_examples :example
33
+ end
34
+ end
35
+
36
+ describe '#name_match?' do
37
+ name = 'block_ulist:with-title'
38
+
39
+ context "when name is e.g. #{name}" do
40
+ subject(:o) { described_class.new(name) }
41
+
42
+ [ '*', '*:*', 'block_ulist:*', '*:with-title', 'block_*:*',
43
+ 'block_ulist:with-*', 'block_ulist:*title'
44
+ ].each do |pattern|
45
+ it "returns true for #{pattern}" do
46
+ expect(o.name_match?(pattern)).to be_truthy
47
+ end
48
+ end
49
+
50
+ [ 'block_foo:with-title', 'block_ulist:foo', 'foo:*' '*:foo', 'foo'
51
+ ].each do |pattern|
52
+ it "returns false for #{pattern}" do
53
+ expect(o.name_match?(pattern)).to be_falsy
54
+ end
55
+ end
56
+ end
57
+ end
58
+
59
+ describe '#empty?' do
60
+ subject { o.empty? }
61
+
62
+ context 'when content is nil' do
63
+ before { o.content = nil }
64
+ it { is_expected.to be_truthy }
65
+ end
66
+
67
+ context 'when content is blank' do
68
+ before { o.content = ' ' }
69
+ it { is_expected.to be_truthy }
70
+ end
71
+
72
+ context 'when content is not blank' do
73
+ before { o.content = 'allons-y!' }
74
+ it { is_expected.to be_falsy }
75
+ end
76
+ end
77
+
78
+ describe '#[]' do
79
+
80
+ context 'when option exists' do
81
+ it 'returns the option value' do
82
+ o.opts[:foo] = 'bar'
83
+ expect(o['foo']).to eq 'bar'
84
+ end
85
+ end
86
+
87
+ context 'when option does not exist' do
88
+ it 'returns nil' do
89
+ expect(o[:nothing]).to be_nil
90
+ end
91
+ end
92
+ end
93
+
94
+ describe '#[]=' do
95
+ subject { o.opts }
96
+
97
+ context 'with boolean value' do
98
+ [true, false].each do |value|
99
+ it "associates the option with #{value}" do
100
+ o['foo'] = value
101
+ is_expected.to eq(foo: value)
102
+ end
103
+ end
104
+ end
105
+
106
+ context 'with Array value' do
107
+ it 'associates the option with the value' do
108
+ o['foo'] = ['a', 'b']
109
+ is_expected.to eq(foo: ['a', 'b'])
110
+ end
111
+ end
112
+
113
+ context 'with String value' do
114
+
115
+ context 'when option is not defined' do
116
+ it 'associates the option with the value wrapped in an array' do
117
+ o['key'] = 'foo'
118
+ is_expected.to eq(key: ['foo'])
119
+ end
120
+ end
121
+
122
+ context 'when option is already defined' do
123
+ before { o.opts[:key] = ['foo'] }
124
+
125
+ it 'adds the value to array associated with the option' do
126
+ o[:key] = 'bar'
127
+ is_expected.to eq(key: ['foo', 'bar'])
128
+ end
129
+ end
130
+ end
131
+
132
+ context 'with nil value' do
133
+ before { o.opts[:key] = ['foo'] }
134
+
135
+ it 'deletes the option' do
136
+ o[:key] = nil
137
+ is_expected.to be_empty
138
+ end
139
+ end
140
+ end
141
+
142
+ describe '#==' do
143
+
144
+ let(:first) { described_class.new('a:b', content: 'allons-y!') }
145
+ let(:second) { described_class.new('a:b', content: 'allons-y!') }
146
+
147
+ it 'returns true for different instances with the same name and content' do
148
+ expect(first).to eq second
149
+ end
150
+
151
+ it 'returns false for instances with different name' do
152
+ second.name = 'a:x'
153
+ expect(first).to_not eq second
154
+ end
155
+
156
+ it 'returns false for instances with different content_normalized' do
157
+ expect(second).to receive(:content_normalized).and_return('ALLONS-Y!')
158
+ expect(first).to_not eq second
159
+ end
160
+ end
161
+
162
+ describe '#dup' do
163
+ it 'returns deep copy' do
164
+ origo = described_class.new('a:b', content: 'allons-y!', desc: 'who?', opts: {key: ['value']})
165
+ copy = origo.dup
166
+
167
+ expect(origo).to eql copy
168
+ expect(origo).to_not equal copy
169
+
170
+ origo.instance_values.values.zip(copy.instance_values.values).each do |val1, val2|
171
+ expect(val1).to_not equal val2 unless val1.nil? && val2.nil?
172
+ end
173
+ expect(origo.opts[:key].first).to_not equal copy.opts[:key].first
174
+ end
175
+ end
176
+ end