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
@@ -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