rspec-documentation 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
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
+