rspec-documentation 0.0.2 → 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +1 -0
- data/Gemfile.lock +3 -3
- data/Makefile +9 -0
- data/README.md +17 -21
- data/exe/rspec-documentation +5 -1
- data/lib/rspec/documentation/version.rb +1 -1
- data/lib/rspec/documentation.rb +1 -51
- data/lib/rspec_documentation/configuration.rb +17 -4
- data/lib/rspec_documentation/document.rb +17 -5
- data/lib/rspec_documentation/documentation.rb +85 -0
- data/lib/rspec_documentation/formatters/ansi.rb +44 -0
- data/lib/rspec_documentation/formatters/html.rb +31 -0
- data/lib/rspec_documentation/formatters/json.rb +32 -0
- data/lib/rspec_documentation/formatters/ruby.rb +31 -0
- data/lib/rspec_documentation/formatters/yaml.rb +36 -0
- data/lib/rspec_documentation/formatters.rb +20 -0
- data/lib/rspec_documentation/html_element.rb +21 -18
- data/lib/rspec_documentation/javascript_bundle.rb +17 -0
- data/lib/rspec_documentation/page_collection.rb +24 -9
- data/lib/rspec_documentation/page_tree.rb +16 -8
- data/lib/rspec_documentation/page_tree_element.rb +31 -9
- data/lib/rspec_documentation/parsed_document.rb +5 -3
- data/lib/rspec_documentation/project_initialization.rb +52 -0
- data/lib/rspec_documentation/rspec/failure.rb +20 -5
- data/lib/rspec_documentation/rspec.rb +0 -2
- data/lib/rspec_documentation/spec.rb +58 -12
- data/lib/rspec_documentation/stylesheet_bundle.rb +17 -0
- data/lib/rspec_documentation/summary.rb +87 -0
- data/lib/rspec_documentation/util.rb +12 -0
- data/lib/rspec_documentation.rb +16 -3
- data/lib/templates/000-Introduction.md.erb +35 -0
- data/lib/templates/010-Usage.md.erb +3 -0
- data/lib/templates/500-License.md.erb +3 -0
- data/lib/templates/bootstrap.js.erb +7 -0
- data/lib/templates/footer.html.erb +5 -0
- data/lib/templates/header.html.erb +16 -3
- data/lib/templates/layout.css.erb +381 -2
- data/lib/templates/layout.html.erb +13 -82
- data/lib/templates/layout.js.erb +67 -0
- data/lib/templates/modal_spec.html.erb +51 -0
- data/lib/templates/moon.svg.erb +1 -0
- data/lib/templates/script_tags.html.erb +1 -0
- data/lib/templates/stylesheet_links.html.erb +1 -0
- data/lib/templates/sun.svg.erb +1 -0
- data/lib/templates/tabbed_spec.html.erb +12 -49
- data/lib/templates/themes/bootstrap.min.css +11 -0
- data/lib/templates/themes/cerulean.css +5 -6
- data/lib/templates/themes/cosmo.css +5 -6
- data/lib/templates/themes/cyborg.css +5 -6
- data/lib/templates/themes/darkly.css +5 -6
- data/lib/templates/themes/flatly.css +5 -6
- data/lib/templates/themes/journal.css +5 -6
- data/lib/templates/themes/litera.css +5 -6
- data/lib/templates/themes/lumen.css +5 -6
- data/lib/templates/themes/lux.css +5 -6
- data/lib/templates/themes/materia.css +5 -6
- data/lib/templates/themes/minty.css +5 -6
- data/lib/templates/themes/morph.css +5 -6
- data/lib/templates/themes/pulse.css +5 -6
- data/lib/templates/themes/quartz.css +5 -6
- data/lib/templates/themes/sandstone.css +5 -6
- data/lib/templates/themes/simplex.css +5 -6
- data/lib/templates/themes/sketchy.css +5 -6
- data/lib/templates/themes/slate.css +5 -6
- data/lib/templates/themes/solar.css +5 -6
- data/lib/templates/themes/spacelab.css +5 -6
- data/lib/templates/themes/superhero.css +5 -6
- data/lib/templates/themes/united.css +5 -6
- data/lib/templates/themes/vapor.css +5 -6
- data/lib/templates/themes/yeti.css +5 -6
- data/lib/templates/themes/zephyr.css +5 -6
- data/rspec-documentation/pages/000-Introduction/000-Quickstart.md +17 -0
- data/rspec-documentation/pages/000-Introduction.md +14 -30
- data/rspec-documentation/pages/010-File System/000-Ordering.md +1 -1
- data/rspec-documentation/pages/010-File System/010-Documentation Bundle.md +9 -0
- data/rspec-documentation/pages/010-File System.md +1 -1
- data/rspec-documentation/pages/020-Running Tests.md +41 -0
- data/rspec-documentation/pages/030-Examples/010-Basic.md +51 -0
- data/rspec-documentation/pages/030-Examples/020-HTML.md +45 -0
- data/rspec-documentation/pages/030-Examples/030-ANSI.md +33 -0
- data/rspec-documentation/pages/030-Examples/040-JSON.md +39 -0
- data/rspec-documentation/pages/030-Examples/050-YAML.md +40 -0
- data/rspec-documentation/pages/030-Examples.md +7 -0
- data/rspec-documentation/pages/040-Spec Helper.md +11 -0
- data/rspec-documentation/pages/050-Linking.md +20 -0
- data/rspec-documentation/pages/060-Configuration/010-Context.md +26 -0
- data/rspec-documentation/pages/060-Configuration/020-Build Paths.md +33 -0
- data/rspec-documentation/pages/060-Configuration/030-Attribution.md +23 -0
- data/rspec-documentation/pages/060-Configuration.md +13 -0
- data/rspec-documentation/pages/070-Publishing.md +13 -0
- data/rspec-documentation/pages/500-License.md +11 -0
- data/rspec-documentation/spec_helper.rb +8 -0
- data/rspec-documentation.gemspec +2 -1
- metadata +46 -12
- data/lib/rspec_documentation/ansi_html.rb +0 -28
- data/lib/rspec_documentation/context.rb +0 -44
- data/lib/rspec_documentation/rspec/example_group.rb +0 -26
- data/lib/rspec_documentation/rspec/reporter.rb +0 -45
- data/rspec-documentation/pages/010-File System/010-Standalone Directories.md +0 -17
- data/rspec-documentation/pages/010-File System/020-Standalone Directory/Example Page.md +0 -3
- data/rspec-documentation/pages/020-Running Specs.md +0 -11
@@ -4,19 +4,24 @@ module RSpecDocumentation
|
|
4
4
|
# Builds content for a collection of page paths, collates failures from embedded examples.
|
5
5
|
# Writes the final structure to disk.
|
6
6
|
class PageCollection
|
7
|
-
attr_reader :failures
|
7
|
+
attr_reader :failures, :page_paths
|
8
8
|
|
9
9
|
def initialize(page_paths:)
|
10
|
-
@page_paths = page_paths
|
10
|
+
@page_paths = page_paths.sort
|
11
11
|
@buffer = {}
|
12
12
|
@failures = []
|
13
13
|
end
|
14
14
|
|
15
15
|
def generate
|
16
|
-
page_paths.
|
17
|
-
document = Document.new(document: path.read, page_tree: page_tree)
|
16
|
+
page_paths.zip(documents).each do |path, document|
|
18
17
|
buffer[bundle_path_for(path)] = document.render
|
19
|
-
|
18
|
+
failures.concat(document.failures)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def documents
|
23
|
+
@documents ||= page_paths.map do |path|
|
24
|
+
Document.new(document: path.read, path: path, page_tree: page_tree(path: path))
|
20
25
|
end
|
21
26
|
end
|
22
27
|
|
@@ -24,18 +29,28 @@ module RSpecDocumentation
|
|
24
29
|
Util.bundle_dir.rmtree if Util.bundle_dir.directory?
|
25
30
|
Util.bundle_dir.mkpath
|
26
31
|
|
27
|
-
|
32
|
+
buffer.each do |path, content|
|
28
33
|
path.dirname.mkpath
|
29
34
|
Util.bundle_path(path).write(content)
|
30
35
|
end
|
36
|
+
write_index unless buffer.empty?
|
37
|
+
end
|
38
|
+
|
39
|
+
def examples_count
|
40
|
+
documents.map(&:specs).flatten.size
|
31
41
|
end
|
32
42
|
|
33
43
|
private
|
34
44
|
|
35
|
-
attr_reader :
|
45
|
+
attr_reader :buffer
|
46
|
+
|
47
|
+
def write_index
|
48
|
+
_path, content = buffer.first
|
49
|
+
Util.bundle_index_path.write(content)
|
50
|
+
end
|
36
51
|
|
37
|
-
def page_tree
|
38
|
-
PageTree.new(page_paths: page_paths)
|
52
|
+
def page_tree(path:)
|
53
|
+
PageTree.new(page_paths: page_paths, current_path: path)
|
39
54
|
end
|
40
55
|
|
41
56
|
def bundle_path_for(path)
|
@@ -3,8 +3,9 @@
|
|
3
3
|
module RSpecDocumentation
|
4
4
|
# A hierarchical structure of all pages in the documentation tree. Used for rendering a navigation section.
|
5
5
|
class PageTree
|
6
|
-
def initialize(page_paths:)
|
6
|
+
def initialize(page_paths:, current_path:)
|
7
7
|
@page_paths = page_paths
|
8
|
+
@current_path = current_path
|
8
9
|
@structure = {}
|
9
10
|
@nodes = []
|
10
11
|
end
|
@@ -19,7 +20,7 @@ module RSpecDocumentation
|
|
19
20
|
|
20
21
|
private
|
21
22
|
|
22
|
-
attr_reader :page_paths, :structure, :nodes
|
23
|
+
attr_reader :page_paths, :structure, :nodes, :current_path
|
23
24
|
|
24
25
|
def tree
|
25
26
|
@tree ||= begin
|
@@ -29,17 +30,24 @@ module RSpecDocumentation
|
|
29
30
|
end
|
30
31
|
|
31
32
|
def build_nodes(root:, path:)
|
32
|
-
root[:children].sort.
|
33
|
+
root[:children].sort.each do |child|
|
33
34
|
node = page_tree_node(path: path, child: child)
|
34
35
|
next nil if node.nil?
|
35
36
|
|
36
|
-
|
37
|
-
nodes.push(
|
38
|
-
|
39
|
-
|
37
|
+
li_open, *li_body, li_close = node
|
38
|
+
nodes.push(li_open)
|
39
|
+
nodes.concat(li_body)
|
40
|
+
push_children(root: root, path: path, child: child)
|
41
|
+
nodes.push(li_close)
|
40
42
|
end
|
41
43
|
end
|
42
44
|
|
45
|
+
def push_children(root:, path:, child:)
|
46
|
+
nodes.push('<ol>')
|
47
|
+
build_nodes(root: root[child], path: path.join(child)) unless root[child].nil?
|
48
|
+
nodes.last == '<ol>' ? nodes.pop : nodes.push('</ol>')
|
49
|
+
end
|
50
|
+
|
43
51
|
def build_tree(branch: structure, depth: 0)
|
44
52
|
normalized_paths.each do |path|
|
45
53
|
first, second, *rest = path_segments(path: path, depth: depth)
|
@@ -67,7 +75,7 @@ module RSpecDocumentation
|
|
67
75
|
end
|
68
76
|
|
69
77
|
def page_tree_node(path:, child:)
|
70
|
-
PageTreeElement.new(path: path, child: child).node
|
78
|
+
PageTreeElement.new(path: path, child: child, current_path: current_path).node
|
71
79
|
end
|
72
80
|
end
|
73
81
|
end
|
@@ -5,25 +5,26 @@ module RSpecDocumentation
|
|
5
5
|
# document (or just a text entry if no document exists, i.e. if it is a directory without an
|
6
6
|
# index file).
|
7
7
|
class PageTreeElement
|
8
|
-
def initialize(path:, child:)
|
8
|
+
def initialize(path:, child:, current_path:)
|
9
9
|
@path = path
|
10
10
|
@child = child
|
11
|
+
@current_path = current_path
|
11
12
|
@nodes = []
|
13
|
+
raise MissingFileError, "Missing file: #{entry_path.relative_path_from(Util.base_dir)}" unless entry?
|
12
14
|
end
|
13
15
|
|
14
16
|
def node
|
15
17
|
return nil if entry_and_directory?
|
16
18
|
|
17
|
-
nodes.push(
|
18
|
-
nodes.push(link)
|
19
|
-
nodes.push(bullet) unless entry?
|
19
|
+
nodes.push(li_open)
|
20
|
+
nodes.push(link)
|
20
21
|
nodes.push('</li>')
|
21
22
|
nodes
|
22
23
|
end
|
23
24
|
|
24
25
|
private
|
25
26
|
|
26
|
-
attr_reader :path, :child, :nodes
|
27
|
+
attr_reader :path, :child, :current_path, :nodes
|
27
28
|
|
28
29
|
def entry_and_directory?
|
29
30
|
return false unless path.join(child).sub_ext('.md').file?
|
@@ -33,22 +34,43 @@ module RSpecDocumentation
|
|
33
34
|
true
|
34
35
|
end
|
35
36
|
|
37
|
+
def entry_path
|
38
|
+
path.join(child).sub_ext('.md')
|
39
|
+
end
|
40
|
+
|
36
41
|
def entry?
|
37
|
-
|
42
|
+
entry_path.file?
|
43
|
+
end
|
44
|
+
|
45
|
+
def li_open
|
46
|
+
"<li id='#{Util.path_id(path.join(child))}' #{active_class} data-list-item-id='##{path_id}' " \
|
47
|
+
"data-parent-id='##{parent_path_id}'>"
|
38
48
|
end
|
39
49
|
|
40
50
|
def link
|
41
|
-
"<a href='#{href}'>#{title}</a>"
|
51
|
+
"<a #{active_class} href='#{href}'>#{title}</a>"
|
52
|
+
end
|
53
|
+
|
54
|
+
def path_id
|
55
|
+
@path_id ||= Util.path_id(path.join(child))
|
56
|
+
end
|
57
|
+
|
58
|
+
def parent_path_id
|
59
|
+
@parent_path_id ||= Util.path_id(path)
|
42
60
|
end
|
43
61
|
|
44
|
-
def
|
45
|
-
"
|
62
|
+
def active_class
|
63
|
+
active? ? 'class="active"' : nil
|
46
64
|
end
|
47
65
|
|
48
66
|
def href
|
49
67
|
Util.href(path.join(child))
|
50
68
|
end
|
51
69
|
|
70
|
+
def active?
|
71
|
+
Util.href(current_path) == href
|
72
|
+
end
|
73
|
+
|
52
74
|
def title
|
53
75
|
Util.label(child)
|
54
76
|
end
|
@@ -5,8 +5,9 @@ module RSpecDocumentation
|
|
5
5
|
class ParsedDocument
|
6
6
|
attr_reader :failures
|
7
7
|
|
8
|
-
def initialize(document)
|
9
|
-
@document = Kramdown::Document.new(document, input: 'GFM')
|
8
|
+
def initialize(document, path:)
|
9
|
+
@document = Kramdown::Document.new(document, input: 'GFM', syntax_highlighter: 'rouge')
|
10
|
+
@path = path
|
10
11
|
@failures = []
|
11
12
|
end
|
12
13
|
|
@@ -29,7 +30,7 @@ module RSpecDocumentation
|
|
29
30
|
|
30
31
|
private
|
31
32
|
|
32
|
-
attr_reader :document
|
33
|
+
attr_reader :document, :path
|
33
34
|
|
34
35
|
def recursive_specs(element: document.root)
|
35
36
|
element.children.each.with_index.map do |child, index|
|
@@ -53,6 +54,7 @@ module RSpecDocumentation
|
|
53
54
|
format: element.options[:lang].partition(':').last,
|
54
55
|
parent: parent,
|
55
56
|
index: index,
|
57
|
+
path: path,
|
56
58
|
location: element.options[:location]
|
57
59
|
)
|
58
60
|
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RSpecDocumentation
|
4
|
+
# Initializes a new project if `rspec-documentation/` directory is empty.
|
5
|
+
class ProjectInitialization
|
6
|
+
include Paintbrush
|
7
|
+
|
8
|
+
def flush
|
9
|
+
print_welcome
|
10
|
+
create_base_dir
|
11
|
+
create_sample_files
|
12
|
+
print_initialization_complete
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def print_welcome
|
18
|
+
warn(paintbrush { blue "\nWelcome to #{cyan 'RSpec Documentation'}. A new project is being initialized.\n" })
|
19
|
+
warn(paintbrush { blue "If you want undo at any point, simply delete #{cyan 'rspec-documentation/'}.\n" })
|
20
|
+
end
|
21
|
+
|
22
|
+
def create_base_dir
|
23
|
+
Util.base_dir.mkpath
|
24
|
+
print_created(Util.base_dir)
|
25
|
+
end
|
26
|
+
|
27
|
+
def create_sample_files
|
28
|
+
sample_files.each do |sample_file|
|
29
|
+
Util.base_dir.join(sample_file).sub_ext('.md').write(RSpecDocumentation.template(sample_file, :md).result)
|
30
|
+
print_created(Util.base_dir.join(sample_file).sub_ext('.md'))
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def print_created(path)
|
35
|
+
warn(paintbrush { " #{green_b 'create'} #{cyan path.relative_path_from(pwd)}" })
|
36
|
+
end
|
37
|
+
|
38
|
+
def print_initialization_complete
|
39
|
+
warn(paintbrush do
|
40
|
+
blue "\nYour documentation project has been #{green 'initialized'} wlth smoe example pages."
|
41
|
+
end)
|
42
|
+
end
|
43
|
+
|
44
|
+
def pwd
|
45
|
+
Pathname.new(Dir.pwd)
|
46
|
+
end
|
47
|
+
|
48
|
+
def sample_files
|
49
|
+
%w[000-Introduction 010-Usage 500-License]
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -4,34 +4,49 @@ module RSpecDocumentation
|
|
4
4
|
module RSpec
|
5
5
|
# Stores information about a failed RSpec example. Thin wrapper around `RSpec::Failure`.
|
6
6
|
class Failure
|
7
|
+
include Paintbrush
|
8
|
+
|
9
|
+
attr_reader :spec
|
10
|
+
|
7
11
|
def initialize(cause:, spec:)
|
8
12
|
@cause = cause
|
9
13
|
@spec = spec
|
10
14
|
end
|
11
15
|
|
12
16
|
def message
|
13
|
-
"\n#{formatted_header}\n\n#{formatted_source}\n#{formatted_cause}\n\n"
|
17
|
+
"\n#{formatted_header}\n\n#{formatted_source}\n\n#{formatted_cause}\n\n"
|
14
18
|
end
|
15
19
|
|
16
20
|
private
|
17
21
|
|
18
|
-
attr_reader :cause
|
22
|
+
attr_reader :cause
|
19
23
|
|
20
24
|
def formatted_header
|
21
|
-
|
25
|
+
paintbrush { cyan indented("# #{path}:#{spec.location}") }
|
26
|
+
end
|
27
|
+
|
28
|
+
def path
|
29
|
+
spec.path.relative_path_from(Pathname.new(Dir.pwd))
|
22
30
|
end
|
23
31
|
|
24
32
|
def formatted_source
|
25
|
-
|
33
|
+
paintbrush { white indented(spec.source) }
|
26
34
|
end
|
27
35
|
|
28
36
|
def formatted_cause
|
29
|
-
|
37
|
+
paintbrush { red indented(without_anonymous_group_text(cause.message)) }
|
30
38
|
end
|
31
39
|
|
32
40
|
def indented(text)
|
33
41
|
text.split("\n").map { |line| " #{line}" }.join("\n")
|
34
42
|
end
|
43
|
+
|
44
|
+
# If an error occurs outside of a test, RSpec will provide an error to the Reporter
|
45
|
+
# referring to an anonymous group due to the way specs are evaluated. This does not help
|
46
|
+
# with debugging so we remove it. TODO: Find a better way ?
|
47
|
+
def without_anonymous_group_text(text)
|
48
|
+
text&.gsub(/ for #<RSpec::ExampleGroups::Anonymous.*$/, '')
|
49
|
+
end
|
35
50
|
end
|
36
51
|
end
|
37
52
|
end
|
@@ -3,20 +3,30 @@
|
|
3
3
|
module RSpecDocumentation
|
4
4
|
# Executes specs from a Markdown code block and provides the outcome in the appropriate format.
|
5
5
|
class Spec
|
6
|
-
attr_reader :parent, :location, :index, :format
|
6
|
+
attr_reader :parent, :location, :index, :format, :path
|
7
7
|
|
8
|
-
|
8
|
+
@durations = []
|
9
|
+
|
10
|
+
class << self
|
11
|
+
attr_accessor :subjects, :durations
|
12
|
+
end
|
13
|
+
|
14
|
+
def initialize(spec:, format:, parent:, location:, path:, index:) # rubocop:disable Metrics/ParameterLists
|
9
15
|
@spec = spec
|
10
16
|
@format = format.empty? ? :plaintext : format.to_sym
|
11
17
|
@parent = parent
|
12
18
|
@location = location
|
19
|
+
@path = path
|
13
20
|
@index = index
|
14
21
|
@failures = []
|
15
|
-
@reporter = RSpec::Reporter.new
|
16
22
|
end
|
17
23
|
|
18
|
-
def
|
19
|
-
|
24
|
+
def subject
|
25
|
+
raise Error, "Code block did not define an example (e.g. with `it`).\n#{spec}" if examples.empty?
|
26
|
+
raise Error, "Code block did not define a subject:\n#{spec}" if subjects.empty?
|
27
|
+
raise Error, "Cannot define more than one example per code block:\n#{spec}" if subjects.size > 1
|
28
|
+
|
29
|
+
subjects.last
|
20
30
|
end
|
21
31
|
|
22
32
|
def source
|
@@ -27,15 +37,33 @@ module RSpecDocumentation
|
|
27
37
|
failures.first
|
28
38
|
end
|
29
39
|
|
40
|
+
def reporter
|
41
|
+
@reporter ||= ::RSpec::Core::Reporter.new(::RSpec::Core::Configuration.new)
|
42
|
+
end
|
43
|
+
|
30
44
|
def run
|
45
|
+
self.class.subjects = []
|
31
46
|
RSpec.with_failure_notifier(failure_notifier) do
|
32
|
-
example_group.run(reporter)
|
47
|
+
succeeded = example_group.run(reporter)
|
48
|
+
durations << run_time if run_time
|
49
|
+
next succeeded if succeeded
|
50
|
+
|
51
|
+
notify_failure(reported_failure)
|
52
|
+
succeeded
|
33
53
|
end
|
34
54
|
end
|
35
55
|
|
36
56
|
private
|
37
57
|
|
38
|
-
attr_reader :spec, :failures
|
58
|
+
attr_reader :spec, :failures
|
59
|
+
|
60
|
+
def subjects
|
61
|
+
self.class.subjects
|
62
|
+
end
|
63
|
+
|
64
|
+
def durations
|
65
|
+
self.class.durations
|
66
|
+
end
|
39
67
|
|
40
68
|
def examples
|
41
69
|
@examples ||= example_group.examples
|
@@ -45,18 +73,36 @@ module RSpecDocumentation
|
|
45
73
|
# rubocop:disable Style/DocumentDynamicEvalDefinition, Security/Eval
|
46
74
|
@example_group ||= binding.eval(
|
47
75
|
<<-SPEC, __FILE__, __LINE__.to_i
|
48
|
-
RSpec::ExampleGroup.describe do
|
49
|
-
|
76
|
+
::RSpec::Core::ExampleGroup.describe do
|
77
|
+
after { RSpecDocumentation::Spec.subjects << subject }
|
78
|
+
include_context '__rspec_documentation' do
|
79
|
+
#{spec}
|
80
|
+
end
|
50
81
|
end
|
51
82
|
SPEC
|
52
83
|
)
|
53
84
|
# rubocop:enable Style/DocumentDynamicEvalDefinition, Security/Eval
|
54
85
|
end
|
55
86
|
|
87
|
+
def run_time
|
88
|
+
return nil unless reporter&.examples&.first
|
89
|
+
|
90
|
+
@run_time ||= reporter.examples.first.execution_result.run_time
|
91
|
+
end
|
92
|
+
|
93
|
+
def notify_failure(failure)
|
94
|
+
failures << RSpec::Failure.new(cause: failure, spec: self)
|
95
|
+
end
|
96
|
+
|
97
|
+
def reported_failure
|
98
|
+
exception = reporter.failed_examples.first.exception
|
99
|
+
return exception unless exception.is_a?(::RSpec::Core::MultipleExceptionError)
|
100
|
+
|
101
|
+
exception.all_exceptions.first
|
102
|
+
end
|
103
|
+
|
56
104
|
def failure_notifier
|
57
|
-
proc
|
58
|
-
failures << RSpec::Failure.new(cause: failure, spec: self)
|
59
|
-
end
|
105
|
+
proc { |failure| notify_failure(failure) }
|
60
106
|
end
|
61
107
|
end
|
62
108
|
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RSpecDocumentation
|
4
|
+
# Compiles CSS assets into a single file.
|
5
|
+
class StylesheetBundle
|
6
|
+
def flush
|
7
|
+
Util.bundle_dir.join('assets').mkpath
|
8
|
+
stylesheet_bundle_path.write(RSpecDocumentation.template(:layout, :css).result(binding))
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def stylesheet_bundle_path
|
14
|
+
Util.bundle_dir.join('assets/bundle.css')
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RSpecDocumentation
|
4
|
+
# Provides a summary of the test run including details of any failures.
|
5
|
+
class Summary
|
6
|
+
include Paintbrush
|
7
|
+
|
8
|
+
def initialize(page_collection:, pwd:, started_at:)
|
9
|
+
@page_collection = page_collection
|
10
|
+
@pwd = pwd
|
11
|
+
@duration = Time.now.utc - started_at
|
12
|
+
end
|
13
|
+
|
14
|
+
def flush
|
15
|
+
if page_collection.failures.empty?
|
16
|
+
print_success_summary
|
17
|
+
else
|
18
|
+
print_failure_summary
|
19
|
+
end
|
20
|
+
|
21
|
+
print_duration_summary
|
22
|
+
|
23
|
+
nil
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
attr_reader :page_collection, :pwd, :duration
|
29
|
+
|
30
|
+
def print_success_summary
|
31
|
+
warn(successful_examples_summary)
|
32
|
+
warn(paintbrush { blue("\n Created #{green(page_collection.page_paths.size)} pages.\n") })
|
33
|
+
warn(paintbrush { white(" View your documentation here: #{blue(Util.bundle_index_path)}\n") })
|
34
|
+
end
|
35
|
+
|
36
|
+
def print_failure_summary
|
37
|
+
page_collection.failures.each do |failure|
|
38
|
+
warn(failure.message)
|
39
|
+
end
|
40
|
+
|
41
|
+
warn(failure_summary)
|
42
|
+
warn("\nFailed examples:\n\n")
|
43
|
+
warn(failed_examples_summary)
|
44
|
+
$stderr.puts
|
45
|
+
end
|
46
|
+
|
47
|
+
def print_duration_summary
|
48
|
+
warn(duration_summary)
|
49
|
+
end
|
50
|
+
|
51
|
+
def failure_summary
|
52
|
+
examples = pluralize(page_collection.examples_count, 'example')
|
53
|
+
failures = pluralize(page_collection.failures.size, 'failure')
|
54
|
+
paintbrush do
|
55
|
+
red "#{page_collection.examples_count} #{examples}, #{page_collection.failures.size} #{failures}."
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def successful_examples_summary
|
60
|
+
examples = pluralize(page_collection.examples_count, 'example')
|
61
|
+
failures = pluralize(page_collection.failures.size, 'failure')
|
62
|
+
paintbrush do
|
63
|
+
green "\n #{cyan page_collection.examples_count} #{examples}, " \
|
64
|
+
"#{cyan page_collection.failures.size} #{failures}."
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def pluralize(count, noun)
|
69
|
+
count.zero? || count > 1 ? "#{noun}s" : noun
|
70
|
+
end
|
71
|
+
|
72
|
+
def failed_examples_summary
|
73
|
+
page_collection.failures.map(&:spec).map do |spec|
|
74
|
+
paintbrush { red " #{spec.path.relative_path_from(pwd)}:#{spec.location}" }
|
75
|
+
end.join("\n")
|
76
|
+
end
|
77
|
+
|
78
|
+
def duration_summary
|
79
|
+
formatted_spec_duration = format('%.2g', RSpecDocumentation::Spec.durations.sum)
|
80
|
+
formatted_total_duration = format('%.2g', duration)
|
81
|
+
paintbrush do
|
82
|
+
cyan " Total build time: #{white formatted_total_duration} seconds, " \
|
83
|
+
"examples executed in #{white formatted_spec_duration} seconds.\n"
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -10,6 +10,10 @@ module RSpecDocumentation
|
|
10
10
|
bundle_dir.join(*relative_path.split.map { |segment| normalized_filename(segment) }).sub_ext('.html')
|
11
11
|
end
|
12
12
|
|
13
|
+
def self.bundle_index_path
|
14
|
+
bundle_dir.join('index.html')
|
15
|
+
end
|
16
|
+
|
13
17
|
def self.base_dir
|
14
18
|
root_dir.join('rspec-documentation', 'pages')
|
15
19
|
end
|
@@ -28,6 +32,10 @@ module RSpecDocumentation
|
|
28
32
|
Pathname.new(path).basename.sub_ext('').sub(ORDERING_PREFIX_REGEXP, '')
|
29
33
|
end
|
30
34
|
|
35
|
+
def self.path_id(path)
|
36
|
+
"path-id-#{Digest::SHA256.hexdigest(bundle_path(path).relative_path_from(bundle_dir).to_s)}"
|
37
|
+
end
|
38
|
+
|
31
39
|
def self.href(path)
|
32
40
|
relative_path = Pathname.new(path).relative_path_from(base_dir)
|
33
41
|
url_root.join(*relative_path.split.map { |segment| normalized_filename(segment) }).sub_ext('.html')
|
@@ -39,6 +47,10 @@ module RSpecDocumentation
|
|
39
47
|
Pathname.new(ENV.fetch('RSPEC_DOCUMENTATION_URL_ROOT'))
|
40
48
|
end
|
41
49
|
|
50
|
+
def self.assets_root
|
51
|
+
url_root.join('assets')
|
52
|
+
end
|
53
|
+
|
42
54
|
def self.normalized_filename(path)
|
43
55
|
path.to_s.gsub(' ', '-').downcase.sub(ORDERING_PREFIX_REGEXP, '')
|
44
56
|
end
|
data/lib/rspec_documentation.rb
CHANGED
@@ -12,17 +12,26 @@ require 'rake'
|
|
12
12
|
|
13
13
|
require 'pathname'
|
14
14
|
require 'securerandom'
|
15
|
+
require 'digest'
|
16
|
+
require 'json'
|
17
|
+
require 'yaml'
|
18
|
+
require 'date'
|
19
|
+
require 'time'
|
15
20
|
|
16
21
|
require_relative 'rspec_documentation/rspec'
|
17
22
|
require_relative 'rspec_documentation/util'
|
18
|
-
require_relative 'rspec_documentation/
|
23
|
+
require_relative 'rspec_documentation/summary'
|
24
|
+
require_relative 'rspec_documentation/project_initialization'
|
19
25
|
require_relative 'rspec_documentation/configuration'
|
26
|
+
require_relative 'rspec_documentation/stylesheet_bundle'
|
27
|
+
require_relative 'rspec_documentation/javascript_bundle'
|
20
28
|
require_relative 'rspec_documentation/document'
|
21
29
|
require_relative 'rspec_documentation/parsed_document'
|
22
30
|
require_relative 'rspec_documentation/html_element'
|
23
|
-
require_relative 'rspec_documentation/
|
31
|
+
require_relative 'rspec_documentation/formatters'
|
24
32
|
require_relative 'rspec_documentation/spec'
|
25
33
|
require_relative 'rspec_documentation/markdown_renderer'
|
34
|
+
require_relative 'rspec_documentation/documentation'
|
26
35
|
require_relative 'rspec_documentation/page_collection'
|
27
36
|
require_relative 'rspec_documentation/page_tree'
|
28
37
|
require_relative 'rspec_documentation/page_tree_element'
|
@@ -30,13 +39,17 @@ require_relative 'rspec_documentation/page_tree_element'
|
|
30
39
|
# Internal module used by RSpec::Documentation to run examples and write output into an HTML document.
|
31
40
|
module RSpecDocumentation
|
32
41
|
class Error < StandardError; end
|
42
|
+
class MissingFileError < Error; end
|
33
43
|
|
34
44
|
def self.root
|
35
45
|
Pathname.new(File.dirname(__dir__))
|
36
46
|
end
|
37
47
|
|
38
48
|
def self.template(name, format = :html)
|
39
|
-
ERB.new(
|
49
|
+
ERB.new(
|
50
|
+
root.join('lib/templates', "#{name}.#{format}.erb").read,
|
51
|
+
eoutvar: "@eout#{SecureRandom.hex(16)}"
|
52
|
+
)
|
40
53
|
end
|
41
54
|
|
42
55
|
def self.theme(name)
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# Introduction
|
2
|
+
|
3
|
+
Welcome to _RSpec Documentation_! This is your first documentation page.
|
4
|
+
|
5
|
+
Here's an example spec:
|
6
|
+
|
7
|
+
```rspec
|
8
|
+
subject { true }
|
9
|
+
|
10
|
+
it { is_expected.to be true }
|
11
|
+
```
|
12
|
+
|
13
|
+
And here's another:
|
14
|
+
|
15
|
+
```rspec:html
|
16
|
+
subject { '<h1>Title</h1><button class="btn btn-primary">Button</button>' }
|
17
|
+
|
18
|
+
it { is_expected.to include 'Button' }
|
19
|
+
```
|
20
|
+
|
21
|
+
And here's one more:
|
22
|
+
|
23
|
+
```rspec:ansi
|
24
|
+
subject { "\e[35mSome purple text\e[0m" }
|
25
|
+
|
26
|
+
it { is_expected.to include 'purple text' }
|
27
|
+
```
|
28
|
+
|
29
|
+
See the [documentation](https://docs.bob.frl/rspec-documentation) for more information on adding more content to your documentation.
|
30
|
+
|
31
|
+
Run the `rspec-documentation` command any time you want to generate your documentation bundle:
|
32
|
+
|
33
|
+
```console
|
34
|
+
$ rspec-documentation
|
35
|
+
```
|