rspec-documentation 0.0.1 → 0.0.2

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 (67) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +1 -0
  3. data/.rubocop.yml +2 -0
  4. data/Gemfile +3 -0
  5. data/Gemfile.lock +25 -2
  6. data/README.md +1 -1
  7. data/Rakefile +1 -0
  8. data/exe/rspec-documentation +5 -0
  9. data/lib/rspec/documentation/version.rb +2 -2
  10. data/lib/rspec/documentation.rb +65 -2
  11. data/lib/rspec_documentation/ansi_html.rb +28 -0
  12. data/lib/rspec_documentation/configuration.rb +15 -0
  13. data/lib/rspec_documentation/context.rb +44 -0
  14. data/lib/rspec_documentation/document.rb +61 -0
  15. data/lib/rspec_documentation/html_element.rb +55 -0
  16. data/lib/rspec_documentation/markdown_renderer.rb +7 -0
  17. data/lib/rspec_documentation/page_collection.rb +49 -0
  18. data/lib/rspec_documentation/page_tree.rb +73 -0
  19. data/lib/rspec_documentation/page_tree_element.rb +56 -0
  20. data/lib/rspec_documentation/parsed_document.rb +70 -0
  21. data/lib/rspec_documentation/rspec/example_group.rb +26 -0
  22. data/lib/rspec_documentation/rspec/failure.rb +37 -0
  23. data/lib/rspec_documentation/rspec/reporter.rb +45 -0
  24. data/lib/rspec_documentation/rspec.rb +14 -0
  25. data/lib/rspec_documentation/spec.rb +62 -0
  26. data/lib/rspec_documentation/util.rb +46 -0
  27. data/lib/rspec_documentation.rb +58 -0
  28. data/lib/tasks/rspec/documentation/generate.rake +10 -0
  29. data/lib/templates/footer.html.erb +1 -0
  30. data/lib/templates/header.html.erb +7 -0
  31. data/lib/templates/layout.css.erb +39 -0
  32. data/lib/templates/layout.html.erb +100 -0
  33. data/lib/templates/tabbed_spec.html.erb +119 -0
  34. data/lib/templates/themes/cerulean.css +12 -0
  35. data/lib/templates/themes/cosmo.css +12 -0
  36. data/lib/templates/themes/cyborg.css +12 -0
  37. data/lib/templates/themes/darkly.css +12 -0
  38. data/lib/templates/themes/flatly.css +12 -0
  39. data/lib/templates/themes/journal.css +12 -0
  40. data/lib/templates/themes/litera.css +12 -0
  41. data/lib/templates/themes/lumen.css +12 -0
  42. data/lib/templates/themes/lux.css +12 -0
  43. data/lib/templates/themes/materia.css +12 -0
  44. data/lib/templates/themes/minty.css +12 -0
  45. data/lib/templates/themes/morph.css +12 -0
  46. data/lib/templates/themes/pulse.css +12 -0
  47. data/lib/templates/themes/quartz.css +12 -0
  48. data/lib/templates/themes/sandstone.css +12 -0
  49. data/lib/templates/themes/simplex.css +12 -0
  50. data/lib/templates/themes/sketchy.css +12 -0
  51. data/lib/templates/themes/slate.css +12 -0
  52. data/lib/templates/themes/solar.css +12 -0
  53. data/lib/templates/themes/spacelab.css +12 -0
  54. data/lib/templates/themes/superhero.css +12 -0
  55. data/lib/templates/themes/united.css +12 -0
  56. data/lib/templates/themes/vapor.css +12 -0
  57. data/lib/templates/themes/yeti.css +12 -0
  58. data/lib/templates/themes/zephyr.css +12 -0
  59. data/rspec-documentation/pages/000-Introduction.md +39 -0
  60. data/rspec-documentation/pages/010-File System/000-Ordering.md +14 -0
  61. data/rspec-documentation/pages/010-File System/010-Standalone Directories.md +17 -0
  62. data/rspec-documentation/pages/010-File System/020-Standalone Directory/Example Page.md +3 -0
  63. data/rspec-documentation/pages/010-File System.md +26 -0
  64. data/rspec-documentation/pages/020-Running Specs.md +11 -0
  65. data/rspec-documentation.gemspec +9 -1
  66. data/sig/rspec/documentation.rbs +1 -1
  67. metadata +158 -4
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RSpecDocumentation
4
+ module RSpec
5
+ # Replicates behaviour of a regular RSpec example group but also defines `it_documents` as an
6
+ # alias for `it`.
7
+ class ExampleGroup < ::RSpec::Core::ExampleGroup
8
+ def self.it_documents(described_object, &block)
9
+ raise Error, 'Must pass an example object to `it_documents`' if described_object.nil?
10
+
11
+ metadata = { described_object: described_object }
12
+ ::RSpec::Core::Example.new(self, 'documentation', metadata, block)
13
+ end
14
+
15
+ class << self
16
+ def method_missing(...)
17
+ RSpecDocumentation.context.public_send(...)
18
+ end
19
+
20
+ def respond_to_missing?(*args)
21
+ RSpecDocumentation.context.respond_to_missing?(*args)
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RSpecDocumentation
4
+ module RSpec
5
+ # Stores information about a failed RSpec example. Thin wrapper around `RSpec::Failure`.
6
+ class Failure
7
+ def initialize(cause:, spec:)
8
+ @cause = cause
9
+ @spec = spec
10
+ end
11
+
12
+ def message
13
+ "\n#{formatted_header}\n\n#{formatted_source}\n#{formatted_cause}\n\n"
14
+ end
15
+
16
+ private
17
+
18
+ attr_reader :cause, :spec
19
+
20
+ def formatted_header
21
+ "\e[1;37m#{indented('Failure in example')}:\e[0m"
22
+ end
23
+
24
+ def formatted_source
25
+ "\e[1;34m#{indented(spec.source)}\e[0m"
26
+ end
27
+
28
+ def formatted_cause
29
+ "\e[31m#{indented(cause.message)}\e[0m"
30
+ end
31
+
32
+ def indented(text)
33
+ text.split("\n").map { |line| " #{line}" }.join("\n")
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RSpecDocumentation
4
+ module RSpec
5
+ # RSpec reporter, compliant with RSpec::Core::Reporter. Stores outcomes of specs.
6
+ class Reporter
7
+ attr_reader :passed_examples, :failed_examples
8
+
9
+ def initialize
10
+ @passed_examples = []
11
+ @failed_examples = []
12
+ end
13
+
14
+ def described_object
15
+ raise Error, 'Code block did not contain an example.' if passed_examples.empty? && failed_examples.empty?
16
+
17
+ passed_examples&.first&.metadata&.fetch(:described_object)
18
+ end
19
+
20
+ def report(*_); end
21
+
22
+ def finish(*_); end
23
+
24
+ def example_passed(example)
25
+ raise Error, 'Cannot define more than one example per code block.' if passed_examples.size >= 1
26
+
27
+ passed_examples << example
28
+ end
29
+
30
+ def example_group_started(*_); end
31
+
32
+ def example_group_finished(*_); end
33
+
34
+ def example_started(*_); end
35
+
36
+ def example_finished(*_); end
37
+
38
+ def example_failed(example)
39
+ failed_examples << example
40
+ end
41
+
42
+ def fail_fast_limit_met?(*_); end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'rspec/example_group'
4
+ require_relative 'rspec/reporter'
5
+ require_relative 'rspec/failure'
6
+
7
+ module RSpecDocumentation
8
+ # Provides various classes that wrap or interface RSpec internals.
9
+ module RSpec
10
+ def self.with_failure_notifier(callable, &block)
11
+ ::RSpec::Support.with_failure_notifier(callable, &block)
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RSpecDocumentation
4
+ # Executes specs from a Markdown code block and provides the outcome in the appropriate format.
5
+ class Spec
6
+ attr_reader :parent, :location, :index, :format
7
+
8
+ def initialize(spec:, format:, parent:, location:, index:)
9
+ @spec = spec
10
+ @format = format.empty? ? :plaintext : format.to_sym
11
+ @parent = parent
12
+ @location = location
13
+ @index = index
14
+ @failures = []
15
+ @reporter = RSpec::Reporter.new
16
+ end
17
+
18
+ def described_object
19
+ @described_object ||= reporter.described_object
20
+ end
21
+
22
+ def source
23
+ spec
24
+ end
25
+
26
+ def failure
27
+ failures.first
28
+ end
29
+
30
+ def run
31
+ RSpec.with_failure_notifier(failure_notifier) do
32
+ example_group.run(reporter)
33
+ end
34
+ end
35
+
36
+ private
37
+
38
+ attr_reader :spec, :failures, :reporter
39
+
40
+ def examples
41
+ @examples ||= example_group.examples
42
+ end
43
+
44
+ def example_group
45
+ # rubocop:disable Style/DocumentDynamicEvalDefinition, Security/Eval
46
+ @example_group ||= binding.eval(
47
+ <<-SPEC, __FILE__, __LINE__.to_i
48
+ RSpec::ExampleGroup.describe do
49
+ #{spec}
50
+ end
51
+ SPEC
52
+ )
53
+ # rubocop:enable Style/DocumentDynamicEvalDefinition, Security/Eval
54
+ end
55
+
56
+ def failure_notifier
57
+ proc do |failure|
58
+ failures << RSpec::Failure.new(cause: failure, spec: self)
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RSpecDocumentation
4
+ # Misc utility classes for functionality shared between components, helpers for path normalization.
5
+ class Util
6
+ ORDERING_PREFIX_REGEXP = /^[0-9]+-/.freeze
7
+
8
+ def self.bundle_path(path)
9
+ relative_path = Pathname.new(path).relative_path_from(base_dir)
10
+ bundle_dir.join(*relative_path.split.map { |segment| normalized_filename(segment) }).sub_ext('.html')
11
+ end
12
+
13
+ def self.base_dir
14
+ root_dir.join('rspec-documentation', 'pages')
15
+ end
16
+
17
+ def self.bundle_dir
18
+ return root_dir.join('rspec-documentation', 'bundle') unless ENV.key?('RSPEC_DOCUMENTATION_BUNDLE_PATH')
19
+
20
+ Pathname.new(ENV.fetch('RSPEC_DOCUMENTATION_BUNDLE_PATH'))
21
+ end
22
+
23
+ def self.root_dir
24
+ Pathname.new(Dir.pwd)
25
+ end
26
+
27
+ def self.label(path)
28
+ Pathname.new(path).basename.sub_ext('').sub(ORDERING_PREFIX_REGEXP, '')
29
+ end
30
+
31
+ def self.href(path)
32
+ relative_path = Pathname.new(path).relative_path_from(base_dir)
33
+ url_root.join(*relative_path.split.map { |segment| normalized_filename(segment) }).sub_ext('.html')
34
+ end
35
+
36
+ def self.url_root
37
+ return bundle_dir unless ENV.key?('RSPEC_DOCUMENTATION_URL_ROOT')
38
+
39
+ Pathname.new(ENV.fetch('RSPEC_DOCUMENTATION_URL_ROOT'))
40
+ end
41
+
42
+ def self.normalized_filename(path)
43
+ path.to_s.gsub(' ', '-').downcase.sub(ORDERING_PREFIX_REGEXP, '')
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'htmlbeautifier'
4
+ require 'kramdown'
5
+ require 'kramdown-parser-gfm'
6
+ require 'paintbrush'
7
+ require 'redcarpet'
8
+ require 'rouge'
9
+ require 'rouge/plugins/redcarpet'
10
+ require 'rspec'
11
+ require 'rake'
12
+
13
+ require 'pathname'
14
+ require 'securerandom'
15
+
16
+ require_relative 'rspec_documentation/rspec'
17
+ require_relative 'rspec_documentation/util'
18
+ require_relative 'rspec_documentation/context'
19
+ require_relative 'rspec_documentation/configuration'
20
+ require_relative 'rspec_documentation/document'
21
+ require_relative 'rspec_documentation/parsed_document'
22
+ require_relative 'rspec_documentation/html_element'
23
+ require_relative 'rspec_documentation/ansi_html'
24
+ require_relative 'rspec_documentation/spec'
25
+ require_relative 'rspec_documentation/markdown_renderer'
26
+ require_relative 'rspec_documentation/page_collection'
27
+ require_relative 'rspec_documentation/page_tree'
28
+ require_relative 'rspec_documentation/page_tree_element'
29
+
30
+ # Internal module used by RSpec::Documentation to run examples and write output into an HTML document.
31
+ module RSpecDocumentation
32
+ class Error < StandardError; end
33
+
34
+ def self.root
35
+ Pathname.new(File.dirname(__dir__))
36
+ end
37
+
38
+ def self.template(name, format = :html)
39
+ ERB.new(root.join('lib/templates', "#{name}.#{format}.erb").read)
40
+ end
41
+
42
+ def self.theme(name)
43
+ ERB.new(root.join('lib/templates/themes', "#{name}.css").read).result(binding)
44
+ end
45
+
46
+ def self.context
47
+ yield configuration.context if block_given?
48
+ configuration.context
49
+ end
50
+
51
+ def self.configure
52
+ yield configuration
53
+ end
54
+
55
+ def self.configuration
56
+ @configuration ||= RSpecDocumentation::Configuration.new
57
+ end
58
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ namespace :rspec do
4
+ namespace :documentation do
5
+ desc 'Generates documentation from Markdown located in rpsec-documentation/pages'
6
+ task :generate do
7
+ RSpec::Documentation.generate_documentation
8
+ end
9
+ end
10
+ end
@@ -0,0 +1 @@
1
+ <hr/>
@@ -0,0 +1,7 @@
1
+ <div class="header p-3 m-3 w-90 text-end">
2
+ <h1 class="title"><%= title %></h1>
3
+ <span class="separator border-end pb-2 ms-2 me-2"></span>
4
+ <span class="version"><%= version %></span>
5
+ </div>
6
+
7
+ <hr/>
@@ -0,0 +1,39 @@
1
+ .code {
2
+ font-family: monospace;
3
+ max-height: 30rem;
4
+ overflow-y: auto;
5
+ }
6
+
7
+ h1.title {
8
+ display: inline;
9
+ }
10
+
11
+ .header .separator {
12
+ display: inline-block;
13
+ height: 2.5rem;
14
+ }
15
+
16
+ .version {
17
+ color: #bbb;
18
+ font-size: 2rem;
19
+ }
20
+
21
+ .ansi-html {
22
+ display: inline-block;
23
+ background-color: #2e2e2e;
24
+ border: 5px solid #2e2e2e;
25
+ border-radius: 10px;
26
+ }
27
+
28
+ .ansi-html .ansi-color-0 { color: #555555; }
29
+ .ansi-html .ansi-color-1 { color: #ff0000; }
30
+ .ansi-html .ansi-color-2 { color: #47ff47; }
31
+ .ansi-html .ansi-color-3 { color: #e9e947; }
32
+ .ansi-html .ansi-color-4 { color: #49a0dd; }
33
+ .ansi-html .ansi-color-5 { color: #8d7eeb; }
34
+ .ansi-html .ansi-color-6 { color: #2eecff; }
35
+ .ansi-html .ansi-color-7 { color: #ffffff; }
36
+ .ansi-html .ansi-color-9 { color: #606060; }
37
+ .ansi-html .ansi-color-reset { color: #dddddd; }
38
+
39
+ <%= Rouge::Themes::Github.render %>
@@ -0,0 +1,100 @@
1
+ <html>
2
+ <head>
3
+ <style>
4
+
5
+ /* cerulean, cosmo, cyborg, darkly, flatly, journal, litera, lumen, lux, materia, minty, morph,
6
+ pulse, quartz, sandstone, simplex, sketchy, slate, solar, spacelab, superhero, united, vapor,
7
+ yeti, zephyr */
8
+ <%= RSpecDocumentation.theme(:materia) %>
9
+
10
+ /* Base16, BlackWhiteTheme, Colorful, Github, Gruvbox, IgorPro, Magritte, Molokai, Monokai,
11
+ MonokaiSublime, Pastie, ThankfulEyes, Tulip */
12
+ <%= Rouge::Themes::Molokai.render %>
13
+
14
+ /* https://stackoverflow.com/a/61587938 */
15
+ .consistent-height .tab-content {
16
+ display: flex;
17
+ }
18
+
19
+ .consistent-height .tab-content > .tab-pane {
20
+ display: block; /* undo "display: none;" */
21
+ visibility: hidden;
22
+ margin-right: -100%;
23
+ width: 100%;
24
+ }
25
+
26
+ .consistent-height .tab-content > .active {
27
+ visibility: visible;
28
+ }
29
+
30
+
31
+ .code {
32
+ font-family: monospace;
33
+ max-height: 30rem;
34
+ overflow-y: auto;
35
+ }
36
+
37
+ h1.title {
38
+ display: inline;
39
+ }
40
+
41
+ .header .separator {
42
+ display: inline-block;
43
+ height: 2.5rem;
44
+ }
45
+
46
+ .version {
47
+ font-size: 2rem;
48
+ }
49
+
50
+ .highlight {
51
+ padding: 1rem;
52
+ }
53
+
54
+ .ansi-html {
55
+ display: inline-block;
56
+ background-color: #2e2e2e;
57
+ border: 5px solid #2e2e2e;
58
+ border-radius: 10px;
59
+ }
60
+
61
+ .ansi-html .ansi-color-0 { color: var(--bs-dark); }
62
+ .ansi-html .ansi-color-1 { color: var(--bs-red); }
63
+ .ansi-html .ansi-color-2 { color: var(--bs-green); }
64
+ .ansi-html .ansi-color-3 { color: var(--bs-yellow) }
65
+ .ansi-html .ansi-color-4 { color: var(--bs-blue); }
66
+ .ansi-html .ansi-color-5 { color: var(--bs-purple); }
67
+ .ansi-html .ansi-color-6 { color: var(--bs-cyan); }
68
+ .ansi-html .ansi-color-7 { color: var(--bs-white); }
69
+ .ansi-html .ansi-color-9 { color: var(--bs-light); }
70
+ .ansi-html .ansi-color-reset { color: var(--bs-light);; }
71
+
72
+ </style>
73
+ </head>
74
+
75
+ <body>
76
+ <div class="container m-3 p-3">
77
+ <div class="bg-light sticky-top">
78
+ <%= header %>
79
+ </div>
80
+
81
+ <div class="row">
82
+ <div class="col-auto page-tree fs-6 mt-1 ms-3 me-3">
83
+ <% page_tree.elements.each do |element| %>
84
+ <%= element %>
85
+ <% end %>
86
+ </div>
87
+
88
+ <div class="col">
89
+ <%= html %>
90
+ </div>
91
+ </div>
92
+ <%= footer %>
93
+ </div>
94
+
95
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.2.3/js/bootstrap.bundle.min.js"
96
+ integrity="sha512-i9cEfJwUwViEPFKdC1enz4ZRGBj8YQo6QByFTF92YXHi7waCqyexvRD75S5NVTsSiTv7rKWqG9Y5eFxmRsOn0A=="
97
+ crossorigin="anonymous"
98
+ referrerpolicy="no-referrer"></script>
99
+ </body>
100
+ </html>
@@ -0,0 +1,119 @@
1
+ <div class="modal fade" id="modal-<%= element_id %>-side-by-side" data-bs-backdrop="static" data-bs-keyboard="false" tabindex="-1" aria-labelledby="staticBackdropLabel" aria-hidden="true">
2
+ <div class="modal-dialog modal-xl">
3
+ <div class="modal-content">
4
+ <div class="modal-header">
5
+ <h5 class="modal-title" id="modal-<%= element_id %>-side-by-side-label">Side-by-side view</h5>
6
+ <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
7
+ </div>
8
+ <div class="modal-body">
9
+ <div class="container p-3">
10
+ <div class="row border">
11
+ <div class="col border">
12
+ <div class="p-3 mb-5 code highlight">
13
+ <%= code_source.gsub("\n", '<br/>').gsub(' ', '&nbsp;&nbsp;') %>
14
+ </div>
15
+ </div>
16
+
17
+ <% unless html_source.nil? %>
18
+ <div class="col border">
19
+ <div class="p-3 mb-5 code highlight">
20
+ <%= html_source.gsub("\n", '<br/>').gsub(' ', '&nbsp;&nbsp;') %>
21
+ </div>
22
+ </div>
23
+ <% end %>
24
+
25
+ <div class="col border">
26
+ <div class="p-3 mb-5 <%= html_source.nil? ? 'code highlight' : nil %>">
27
+ <%= rendered_result %>
28
+ </div>
29
+ </div>
30
+ </div>
31
+ </div>
32
+ </div>
33
+ <div class="modal-footer">
34
+ <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
35
+ </div>
36
+ </div>
37
+ </div>
38
+ </div>
39
+
40
+ <div class="consistent-height">
41
+
42
+ <ul class="nav nav-tabs" id="html-tabs-<%= element_id %>" role="tablist">
43
+
44
+ <li class="nav-item" role="presentation">
45
+ <button class="nav-link active"
46
+ id="code-source-<%= element_id %>-tab"
47
+ data-bs-toggle="tab"
48
+ data-bs-target="#code-source-<%= element_id %>"
49
+ type="button"
50
+ role="tab"
51
+ aria-controls="code-source"
52
+ aria-selected="true">Code</button>
53
+ </li>
54
+
55
+ <% unless html_source.nil? %>
56
+ <li class="nav-item" role="presentation">
57
+ <button class="nav-link"
58
+ id="html-source-<%= element_id %>-tab"
59
+ data-bs-toggle="tab"
60
+ data-bs-target="#html-source-<%= element_id %>"
61
+ type="button"
62
+ role="tab"
63
+ aria-controls="html-source"
64
+ aria-selected="true">HTML</button>
65
+ </li>
66
+ <% end %>
67
+
68
+ <li class="nav-item" role="presentation">
69
+ <button class="nav-link"
70
+ id="rendered-<%= element_id %>-tab"
71
+ data-bs-toggle="tab"
72
+ data-bs-target="#rendered-<%= element_id %>"
73
+ type="button"
74
+ role="tab"
75
+ aria-controls="rendered"
76
+ aria-selected="false"><%= html_source.nil? ? 'Output' : 'Rendered HTML' %></button>
77
+ </li>
78
+
79
+ <li class="nav-item bs-auto ms-auto mb-2">
80
+ <button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#modal-<%= element_id %>-side-by-side">
81
+ View Side-by-side
82
+ </button>
83
+ </li>
84
+
85
+ </ul>
86
+
87
+ <div class="tab-content" id="html-tab-content-<%= element_id %>">
88
+
89
+ <div class="tab-pane fade show active"
90
+ id="code-source-<%= element_id %>"
91
+ role="tabpanel"
92
+ aria-labelledby="code-source-<%= element_id %>-tab">
93
+ <div class="p-3 mb-5 code highlight">
94
+ <%= code_source.gsub("\n", '<br/>').gsub(' ', '&nbsp;&nbsp;') %>
95
+ </div>
96
+ </div>
97
+
98
+ <% unless html_source.nil? %>
99
+ <div class="tab-pane fade"
100
+ id="html-source-<%= element_id %>"
101
+ role="tabpanel"
102
+ aria-labelledby="html-source-<%= element_id %>-tab">
103
+ <div class="p-3 mb-5 code highlight">
104
+ <%= html_source.gsub("\n", '<br/>').gsub(' ', '&nbsp;&nbsp;') %>
105
+ </div>
106
+ </div>
107
+ <% end %>
108
+
109
+ <div class="tab-pane fade"
110
+ id="rendered-<%= element_id %>"
111
+ role="tabpanel"
112
+ aria-labelledby="rendered-<%= element_id %>-tab">
113
+ <div class="p-3 mb-5 <%= html_source.nil? ? 'code highlight' : nil %>">
114
+ <%= rendered_result %>
115
+ </div>
116
+ </div>
117
+ </div>
118
+ </div>
119
+