rspec-documentation 0.0.2 → 0.0.3
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.
- 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 +13 -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 +18 -4
- 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
|
+
```
|