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
@@ -1,6 +0,0 @@
1
- require 'test_helper'
2
-
3
- class TestHtml < DocTest::Test
4
- converter_opts template_dirs: 'templates'
5
- generate_tests! DocTest::HTML::ExamplesSuite
6
- end
@@ -1,5 +0,0 @@
1
- require 'asciidoctor/doctest'
2
- require 'minitest/autorun'
3
- require 'tilt'
4
-
5
- DocTest.examples_path = ['examples/html', 'examples/asciidoc']
@@ -1,111 +0,0 @@
1
- require 'asciidoctor'
2
- require 'asciidoctor/converter/template'
3
- require 'corefines'
4
- require 'delegate'
5
-
6
- using Corefines::Object[:blank?, :presence]
7
-
8
- module Asciidoctor
9
- module DocTest
10
- ##
11
- # This class is basically a wrapper for +Asciidoctor.convert+ that allows to
12
- # preset and validate some common parameters.
13
- class AsciidocRenderer
14
-
15
- attr_reader :backend_name, :converter, :template_dirs
16
-
17
- ##
18
- # @param backend_name [#to_s, nil] the name of the tested backend.
19
- #
20
- # @param converter [Class, Asciidoctor::Converter::Base, nil]
21
- # the backend's converter class (or its instance). If not
22
- # specified, the default converter for the specified backend will
23
- # be used.
24
- #
25
- # @param template_dirs [Array<String>, String] path of the directory (or
26
- # multiple directories) where to look for the backend's templates.
27
- # Relative paths are referenced from the working directory.
28
- #
29
- # @param templates_fallback [Boolean] allow to fall back to using an
30
- # appropriate built-in converter when there is no required
31
- # template in the tested backend?
32
- # This is actually a default behaviour in Asciidoctor, but since
33
- # it's inappropriate for testing of custom backends, it's disabled
34
- # by default.
35
- #
36
- # @raise [ArgumentError] if some path from the +template_dirs+ doesn't
37
- # exist or is not a directory.
38
- #
39
- def initialize(backend_name: nil, converter: nil, template_dirs: [],
40
- templates_fallback: false)
41
-
42
- @backend_name = backend_name.to_s.freeze.presence
43
- @converter = converter
44
- @converter ||= NoFallbackTemplateConverter unless template_dirs.empty? || templates_fallback
45
-
46
- template_dirs = Array(template_dirs).freeze
47
- template_dirs.each do |path|
48
- fail ArgumentError, "Templates directory '#{path}' doesn't exist!" unless Dir.exist? path
49
- end
50
- @template_dirs = template_dirs unless template_dirs.empty?
51
- end
52
-
53
- ##
54
- # Converts the given +text+ into AsciiDoc syntax with Asciidoctor using
55
- # the tested backend.
56
- #
57
- # @param text [#to_s] the input text in AsciiDoc syntax.
58
- # @param opts [Hash] options to pass to Asciidoctor.
59
- # @return [String] converted input.
60
- #
61
- def convert(text, opts = {})
62
- converter_opts = {
63
- safe: :safe,
64
- backend: backend_name,
65
- converter: converter,
66
- template_dirs: template_dirs
67
- }.merge(opts)
68
-
69
- Asciidoctor.convert(text.to_s, converter_opts)
70
- end
71
-
72
- # Alias for backward compatibility.
73
- alias_method :render, :convert
74
- end
75
-
76
- ##
77
- # @private
78
- # TemplateConverter that doesn't fallback to a built-in converter when
79
- # no template for a node is found.
80
- class NoFallbackTemplateConverter < SimpleDelegator
81
- # NOTE: It didn't work with subclass of TemplateConverter instead of
82
- # delegator, I have no idea why.
83
-
84
- # Placeholder to be written in a rendered output in place of the node's
85
- # content that cannot be rendered due to missing template.
86
- NOT_FOUND_MARKER = '--TEMPLATE NOT FOUND--'
87
-
88
- def initialize(backend, opts = {})
89
- super Asciidoctor::Converter::TemplateConverter.new(backend, opts[:template_dirs], opts)
90
- end
91
-
92
- ##
93
- # Delegates to the template converter and returns results, or prints
94
- # warning and returns {NOT_FOUND_MARKER} if there is no template to
95
- # handle the specified +template_name+.
96
- def convert(node, template_name = nil, opts = {})
97
- template_name ||= node.node_name
98
-
99
- if handles? template_name
100
- super
101
- else
102
- warn "Could not find a custom template to handle template_name: #{template_name}"
103
- NOT_FOUND_MARKER
104
- end
105
- end
106
-
107
- # Alias for backward compatibility.
108
- alias_method :convert_with_options, :convert
109
- end
110
- end
111
- end
@@ -1,115 +0,0 @@
1
- require 'asciidoctor/doctest/generator'
2
- require 'corefines'
3
- require 'rake/tasklib'
4
-
5
- using Corefines::String::unindent
6
-
7
- module Asciidoctor
8
- module DocTest
9
- ##
10
- # Rake task for generating output examples.
11
- # @see Generator
12
- class GeneratorTask < Rake::TaskLib
13
-
14
- # List of values representing +true+.
15
- TRUE_VALUES = %w[yes y true]
16
-
17
- # This attribute is used only for the default {#input_suite}.
18
- # @return (see DocTest.examples_path)
19
- attr_accessor :examples_path
20
-
21
- # @return [Boolean] whether to rewrite an already existing testing
22
- # example. May be overriden with +FORCE+ variable on the command line
23
- # (default: false).
24
- attr_accessor :force
25
-
26
- # @return [BaseExamplesSuite] an instance of {BaseExamplesSuite} subclass
27
- # to read the reference input examples
28
- # (default: +Asciidoc::ExamplesSuite.new(examples_path: examples_path)+).
29
- attr_accessor :input_suite
30
-
31
- # @return [BaseExamplesSuite] an instance of {BaseExamplesSuite} subclass
32
- # to read and generate the output examples.
33
- attr_accessor :output_suite
34
-
35
- # @return [#to_sym] name of the task.
36
- attr_accessor :name
37
-
38
- # @return [String] glob pattern to select examples to (re)generate.
39
- # May be overriden with +PATTERN+ variable on the command line
40
- # (default: *:*).
41
- attr_accessor :pattern
42
-
43
- # @return [Hash] options for Asciidoctor converter.
44
- # @see AsciidocRenderer#initialize
45
- attr_accessor :converter_opts
46
-
47
- # @return [String] title of the task's description.
48
- attr_accessor :title
49
-
50
- # Alias for backward compatibility.
51
- alias_method :renderer_opts, :converter_opts
52
-
53
-
54
- ##
55
- # @param name [#to_sym] name of the task.
56
- # @yield The block to configure this task.
57
- def initialize(name)
58
- @name = name
59
- @examples_path = DocTest.examples_path.dup
60
- @force = false
61
- @input_suite = nil
62
- @output_suite = nil
63
- @converter_opts = {}
64
- @pattern = '*:*'
65
- @title = "Generate testing examples #{pattern}#{" for #{name}" if name != :generate}."
66
-
67
- yield self
68
-
69
- fail 'The output_suite is not provided!' unless @output_suite
70
- if @output_suite.examples_path.first == DocTest::BUILTIN_EXAMPLES_PATH
71
- fail "The examples_path in output suite is invalid: #{@output_suite.examples_path}"
72
- end
73
-
74
- @input_suite ||= Asciidoc::ExamplesSuite.new(examples_path: @examples_path)
75
- @renderer ||= AsciidocRenderer.new(converter_opts)
76
-
77
- define
78
- end
79
-
80
- def pattern
81
- ENV['PATTERN'] || @pattern
82
- end
83
-
84
- def force?
85
- return TRUE_VALUES.include?(ENV['FORCE'].downcase) if ENV.key? 'FORCE'
86
- !!force
87
- end
88
-
89
- private
90
-
91
- def define
92
- desc description
93
-
94
- task name.to_sym do
95
- puts title
96
- Generator.generate! output_suite, input_suite, @renderer,
97
- pattern: pattern, rewrite: force?
98
- end
99
- self
100
- end
101
-
102
- def description
103
- <<-EOS.unindent
104
- #{title}
105
-
106
- Options (environment variables):
107
- PATTERN glob pattern to select examples to (re)generate. [default: #{@pattern}]
108
- E.g. *:*, block_toc:basic, block*:*, *list:with*, ...
109
- FORCE overwrite existing examples (yes/no)? [default: #{@force ? 'yes' : 'no'}]
110
-
111
- EOS
112
- end
113
- end
114
- end
115
- end
@@ -1,21 +0,0 @@
1
- require 'asciidoctor/doctest/base_example'
2
- require 'asciidoctor/doctest/html/normalizer'
3
- require 'htmlbeautifier'
4
- require 'nokogiri'
5
-
6
- module Asciidoctor::DocTest
7
- module HTML
8
- ##
9
- # Subclass of {BaseExample} for HTML-based backends.
10
- class Example < BaseExample
11
-
12
- def content_normalized
13
- Nokogiri::HTML.fragment(content).normalize!.to_s
14
- end
15
-
16
- def content_pretty
17
- HtmlBeautifier.beautify content_normalized
18
- end
19
- end
20
- end
21
- end
@@ -1,118 +0,0 @@
1
- require 'asciidoctor/doctest/base_examples_suite'
2
- require 'asciidoctor/doctest/html/example'
3
- require 'asciidoctor/doctest/html/normalizer'
4
- require 'corefines'
5
- require 'nokogiri'
6
-
7
- using Corefines::Object[:blank?, :presence, :then]
8
- using Corefines::String::concat!
9
-
10
- module Asciidoctor::DocTest
11
- module HTML
12
- ##
13
- # Subclass of {BaseExamplesSuite} for HTML-based backends.
14
- #
15
- # @example Format of the example's header
16
- # <!-- .example-name
17
- # Any text that is not the example's name or an option is considered
18
- # as a description.
19
- # :option_1: value 1
20
- # :option_2: value 1
21
- # :option_2: value 2
22
- # :boolean_option:
23
- # -->
24
- # <p>The example's content in <strong>HTML</strong>.</p>
25
- #
26
- # <div class="note">The trailing new line (below this) will be removed.</div>
27
- #
28
- class ExamplesSuite < BaseExamplesSuite
29
-
30
- def initialize(file_ext: '.html', paragraph_xpath: './p/node()', **kwargs)
31
- super file_ext: file_ext, **kwargs
32
- @paragraph_xpath = paragraph_xpath
33
- end
34
-
35
- def parse(input, group_name)
36
- examples = []
37
- current = create_example(nil)
38
- in_comment = false
39
-
40
- input.each_line do |line|
41
- line.chomp!
42
- if line =~ /^<!--\s*\.([^ \n]+)/
43
- name = $1
44
- current.content.chomp!
45
- examples << (current = create_example([group_name, name]))
46
- in_comment = true
47
- elsif in_comment
48
- if line =~ /^\s*:([^:]+):(.*)/
49
- current[$1.to_sym] = $2.blank? ? true : $2.strip
50
- else
51
- desc = line.rstrip.chomp('-->').strip
52
- (current.desc ||= '').concat!(desc, "\n") unless desc.empty?
53
- end
54
- else
55
- current.content.concat!(line, "\n")
56
- end
57
- in_comment &= !line.end_with?('-->')
58
- end
59
-
60
- examples
61
- end
62
-
63
- def serialize(examples)
64
- Array(examples).map { |exmpl|
65
- header = [
66
- ".#{exmpl.local_name}",
67
- exmpl.desc.presence,
68
- *format_options(exmpl.opts)
69
- ].compact
70
-
71
- header_str = header.one? ? (header.first + ' ') : (header.join("\n") + "\n")
72
- [ "<!-- #{header_str}-->", exmpl.content.presence ].compact.join("\n") + "\n"
73
- }.join("\n")
74
- end
75
-
76
- def create_example(*args)
77
- Example.new(*args)
78
- end
79
-
80
- def convert_example(example, opts, renderer)
81
- # The header & footer are excluded by default; always enable for document examples.
82
- header_footer = !!opts[:header_footer] || example.name.start_with?('document')
83
-
84
- # When asserting inline examples, defaults to ignore paragraph "wrapper".
85
- includes = opts[:include] || (@paragraph_xpath if example.name.start_with? 'inline_')
86
-
87
- renderer.convert(example.content, header_footer: header_footer)
88
- .then { |s| parse_html s, !header_footer }
89
- .then { |h| find_nodes h, includes }
90
- .then { |h| remove_nodes h, opts[:exclude] }
91
- .then { |h| h.normalize! }
92
- .then { |h| HtmlBeautifier.beautify h }
93
- .then { |h| create_example example.name, content: h, opts: opts }
94
- end
95
-
96
- protected
97
-
98
- def find_nodes(html, xpaths)
99
- Array(xpaths).reduce(html) do |htm, xpath|
100
- # XPath returns NodeSet, but we need DocumentFragment, so convert it again.
101
- parse_html htm.xpath(xpath).to_html
102
- end
103
- end
104
-
105
- def remove_nodes(html, xpaths)
106
- return html unless xpaths
107
-
108
- Array(xpaths).each_with_object(html.clone) do |xpath, htm|
109
- htm.xpath(xpath).remove
110
- end
111
- end
112
-
113
- def parse_html(str, fragment = true)
114
- fragment ? ::Nokogiri::HTML.fragment(str) : ::Nokogiri::HTML.parse(str)
115
- end
116
- end
117
- end
118
- end
@@ -1,125 +0,0 @@
1
- require 'asciidoctor/doctest/asciidoc_renderer'
2
- require 'asciidoctor/doctest/minitest_diffy'
3
- require 'asciidoctor/doctest/asciidoc/examples_suite'
4
- require 'corefines'
5
- require 'minitest'
6
-
7
- using Corefines::Object[:blank?, :presence]
8
- using Corefines::Module::alias_class_method
9
-
10
- module Asciidoctor
11
- module DocTest
12
- ##
13
- # Test class for Asciidoctor backends.
14
- class Test < Minitest::Test
15
- include MinitestDiffy
16
-
17
- ##
18
- # (see AsciidocRenderer#initialize)
19
- def self.converter_opts(**kwargs)
20
- @renderer = AsciidocRenderer.new(**kwargs)
21
- end
22
-
23
- # Alias for backward compatibility.
24
- alias_class_method :renderer_opts, :converter_opts
25
-
26
- ##
27
- # Generates tests for all the input/output examples.
28
- # When some output example is missing, it's reported as skipped test.
29
- #
30
- # @param output_suite [BaseExamplesSuite, Class] the examples suite class
31
- # (or its instance) to read the output examples from (i.e. an
32
- # expected output).
33
- # @param input_suite [BaseExamplesSuite, Class] the examples suite class
34
- # (or its instance) to read the reference input examples from.
35
- #
36
- # If class is given, then it's instantiated with zero arguments.
37
- #
38
- def self.generate_tests!(output_suite, input_suite = Asciidoc::ExamplesSuite)
39
- instance = ->(o) { o.is_a?(Class) ? o.new : o }
40
- @output_suite = instance[output_suite]
41
- @input_suite = instance[input_suite]
42
-
43
- @input_suite.pair_with(@output_suite).each do |input, output|
44
- next if input.empty?
45
-
46
- define_test input.name do
47
- if output.empty?
48
- skip 'No expected output found'
49
- else
50
- test_example output, input
51
- end
52
- end
53
- end
54
- end
55
-
56
- ##
57
- # Defines a new test method.
58
- #
59
- # @param name [String] name of the test (method).
60
- # @param block [Proc] the test method's body.
61
- #
62
- def self.define_test(name, &block)
63
- (@test_methods ||= []) << name
64
- define_method name, block
65
- end
66
-
67
- ##
68
- # @!method self.test
69
- # @see .define_test
70
- alias_class_method :test, :define_test
71
-
72
- ##
73
- # @private
74
- # @note Overrides method from +Minitest::Test+.
75
- # @return [Array] names of the test methods to run.
76
- def self.runnable_methods
77
- (@test_methods || []) + super - ['test_example']
78
- end
79
-
80
- ##
81
- # Tests if the given reference input is matching the expected output
82
- # after conversion through the tested backend.
83
- #
84
- # @param output_exmpl [BaseExample] the expected output example.
85
- # @param input_exmpl [BaseExample] the reference input example.
86
- # @raise [Minitest::Assertion] if the assertion fails.
87
- #
88
- def test_example(output_exmpl, input_exmpl)
89
- converted_exmpl = output_suite.convert_example(input_exmpl, output_exmpl.opts, renderer)
90
- msg = output_exmpl.desc.presence || input_exmpl.desc
91
-
92
- assert_equal output_exmpl, converted_exmpl, msg
93
- end
94
-
95
- ##
96
- # @private
97
- # @note Overrides method from +Minitest::Test+.
98
- # @return [String] name of this test that will be printed in a report.
99
- def location
100
- prefix = self.class.name.split('::').last
101
- name = self.name.sub(':', ' : ')
102
- "#{prefix} :: #{name}"
103
- end
104
-
105
- ##
106
- # @private
107
- # Returns a human-readable (formatted) version of the asserted object.
108
- #
109
- # @note Overrides method from +Minitest::Assertions+.
110
- #
111
- # @param example [#to_s]
112
- # @return [String]
113
- #
114
- def mu_pp(example)
115
- example.to_s
116
- end
117
-
118
- [:input_suite, :output_suite, :renderer].each do |name|
119
- define_method name do
120
- self.class.instance_variable_get(:"@#{name}")
121
- end
122
- end
123
- end
124
- end
125
- end