snapshot_inspector 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +227 -0
  4. data/Rakefile +8 -0
  5. data/app/assets/config/snapshot_inspector/manifest.js +2 -0
  6. data/app/assets/javascripts/snapshot_inspector/application.js +1 -0
  7. data/app/assets/stylesheets/snapshot_inspector/application.css +33 -0
  8. data/app/assets/stylesheets/snapshot_inspector/snapshots/mail.css +48 -0
  9. data/app/assets/stylesheets/snapshot_inspector/snapshots/not_found.css +15 -0
  10. data/app/assets/stylesheets/snapshot_inspector/snapshots/response.css +9 -0
  11. data/app/assets/stylesheets/snapshot_inspector/snapshots.css +73 -0
  12. data/app/controllers/snapshot_inspector/application_controller.rb +14 -0
  13. data/app/controllers/snapshot_inspector/snapshots/mail_controller.rb +57 -0
  14. data/app/controllers/snapshot_inspector/snapshots/response_controller.rb +15 -0
  15. data/app/controllers/snapshot_inspector/snapshots_controller.rb +7 -0
  16. data/app/helpers/snapshot_inspector/application_helper.rb +15 -0
  17. data/app/helpers/snapshot_inspector/snapshots_helper.rb +37 -0
  18. data/app/mailers/snapshot_inspector/application_mailer.rb +6 -0
  19. data/app/models/snapshot_inspector/snapshot/context.rb +49 -0
  20. data/app/models/snapshot_inspector/snapshot/mail_type.rb +35 -0
  21. data/app/models/snapshot_inspector/snapshot/response_type.rb +19 -0
  22. data/app/models/snapshot_inspector/snapshot/rspec_context.rb +52 -0
  23. data/app/models/snapshot_inspector/snapshot/test_unit_context.rb +44 -0
  24. data/app/models/snapshot_inspector/snapshot/type.rb +52 -0
  25. data/app/models/snapshot_inspector/snapshot.rb +86 -0
  26. data/app/views/layouts/snapshot_inspector/application.html.erb +18 -0
  27. data/app/views/snapshot_inspector/snapshots/index.html.erb +29 -0
  28. data/app/views/snapshot_inspector/snapshots/mail/show.html.erb +107 -0
  29. data/app/views/snapshot_inspector/snapshots/not_found.html.erb +8 -0
  30. data/app/views/snapshot_inspector/snapshots/response/raw.html.erb +1 -0
  31. data/app/views/snapshot_inspector/snapshots/response/show.html.erb +1 -0
  32. data/config/importmap.rb +12 -0
  33. data/config/routes.rb +9 -0
  34. data/lib/minitest/snapshot_inspector_plugin.rb +28 -0
  35. data/lib/snapshot_inspector/engine.rb +67 -0
  36. data/lib/snapshot_inspector/storage.rb +60 -0
  37. data/lib/snapshot_inspector/test/action_mailer_headers.rb +18 -0
  38. data/lib/snapshot_inspector/test/rspec_helpers.rb +45 -0
  39. data/lib/snapshot_inspector/test/test_unit_helpers.rb +48 -0
  40. data/lib/snapshot_inspector/version.rb +3 -0
  41. data/lib/snapshot_inspector.rb +42 -0
  42. data/lib/tasks/tmp.rake +10 -0
  43. metadata +159 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 571c9cf5b244dabba8c02e82927bef51de92ee803425cdaba8d959f6c326680d
4
+ data.tar.gz: 568238234e8cfb20cc5cfd2662fb81c303b35a74f6baeb44dfbea4bfe5af6144
5
+ SHA512:
6
+ metadata.gz: 0feb058a79241aff473b9111e6c1ee85c6e1e1806c08614f592f1714ca6eba05040db9b5a93fff255d8217b5a3e399803ca082047abe1b1aaeacb65506a805bc
7
+ data.tar.gz: 497dff7826caf2b15c86fc904401e27d77c65a85af85bb0a55720ab0ea4929fc3cfbef11d4a30f2e064afb647888939cdc56c03c4424dcef0a00bd906715944f
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2023 Tomaz Zlender
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,227 @@
1
+ # Snapshot Inspector
2
+
3
+ Take snapshots of responses and mail messages while testing, and inspect them in a browser. A Ruby on Rails engine. Works with the default Ruby on Rails testing framework and RSpec.
4
+
5
+ > **NOTICE**
6
+ >
7
+ > The library is actively used during the development of apps running in production, however, treat it as beta software.
8
+ >
9
+ > So far it's been tested on the latest version of Ruby on Rails (7.0.x) and Ruby (> 3.1.x).
10
+
11
+ A sneak peek into what the library is about:
12
+
13
+ ![A list of snapshots](doc/snapshots_index.png)
14
+
15
+ ## Rationale
16
+
17
+ Imagine that I'm working on a website for a local urban gardening community. It's a server side rendered
18
+ app, using sprinkles of JavaScript on top.
19
+
20
+ While writing view related tests, like integration or mailer tests, I wish to see how a
21
+ response body (or mail message body) looks like in a browser for a given test setup.
22
+
23
+ It's quite easy to do that in some cases. Let's say I'm writing an integration test for a form for signing
24
+ up gardeners to the garden's waiting list.
25
+ I can simply start the development server, go to a URL of the page that displays the form
26
+ and I can see and inspect the rendered HTML that exactly reflects the response body I'm dealing
27
+ with in the integration test.
28
+
29
+ Now imagine I'm writing an integration test for a success page that is displayed after a new gardener fills
30
+ in the form and submits it. Or imagine, that I'm writing a test for a membership details page of
31
+ an expired membership that is visible only to a specific registered gardener.
32
+
33
+ In such cases, I need to put much more effort into recreating the state in a browser, while I already put effort
34
+ into creating the test setup in the first place.
35
+
36
+ In JavaScript heavy applications it's natural to use system tests (or similar) for such purposes. It's possible
37
+ to [take screenshots](https://api.rubyonrails.org/classes/ActionDispatch/SystemTesting/TestHelpers/ScreenshotHelper.html#method-i-take_screenshot),
38
+ and even save the HTML of the page being screenshotted for later inspection.
39
+
40
+ However, in the world of server rendered applications with sprinkles of JavaScript on top, the integration tests
41
+ (where a system under test deals with controller/model/view) can cover a lot of the testing needs that
42
+ in JavaScript heavy context can only be fulfilled with system tests (javascript/controller/model/view
43
+ running via a browser).
44
+
45
+ It would be convenient to use integration tests for such cases since they are much faster to run. However,
46
+ writing assertions against HTML can become quickly cumbersome because of the reasons explained above.
47
+
48
+ Alternatively, I could use a debugger to inspect the state of a response body through a shell,
49
+ but that makes me feel like peeking through a peephole compared to inspecting HTML with a Web Inspector.
50
+
51
+ Enter **Snapshot Inspector**.
52
+
53
+ All that is keeping me away from using Web Inspector for such purposes is one drop of a line
54
+
55
+ ```ruby
56
+ take_snapshot response
57
+ ```
58
+
59
+ after a request is made in a test and a response object has been populated.
60
+
61
+ Take a look at an example of a rendered response snapshot of a thank you page in a browser:
62
+
63
+ ![Response snapshot example: thank you page](doc/response_snapshot_example_thank_you_page.png)
64
+
65
+ ### Exploring a new codebase
66
+
67
+ Imagine you are new to a codebase (or it's been months since you've looked at it) and you
68
+ are investigating a reported UI bug. You know to which route
69
+ the bug is related to, but since you haven't worked in that area of the codebase, it's not clear how
70
+ to recreate the state of the application in a browser to replicate the bug. Maybe you don't even know
71
+ what the UI related to the route looks like since you haven't managed to click around
72
+ through all the permutations of the state of the whole application.
73
+
74
+ With Snapshot Inspector, you can simply drop a bunch of `take_snapshot response` in all the tests
75
+ of the related controller, and you'll get a pretty good picture of how the UI looks like.
76
+ That might be all you need to do to replicate the bug. Or you can create a new test that
77
+ will replicate the state of the UI the bug reporter was in, and generate snapshots as you explore.
78
+
79
+ ### A case for taking snapshots of mail messages
80
+
81
+ There is an established practice of using [Action Mailer Previews](https://guides.rubyonrails.org/action_mailer_basics.html#previewing-emails).
82
+
83
+ Imagine that I'm working on a mailer that I wish to send to a gardener that has signed up for the
84
+ waiting list.
85
+
86
+ Without Snapshot Inspector I would write the mailer test and the mailer preview. I would need to
87
+ do the same setup of the mailer state twice to keep the parity between the two.
88
+
89
+ With Snapshot Inspector, I can simply drop one line of code into the mailer test and avoid writing
90
+ the mailer preview altogether.
91
+
92
+ ```ruby
93
+ take_snapshot mail
94
+ ```
95
+
96
+ An example of a rendered mail message snapshot of a thank you email in a browser:
97
+
98
+ ![A mail message snapshot](doc/mail_snapshot_example_1.png)
99
+
100
+ The duplicated effort between mailer previews and tests is gone. I can look and inspect all
101
+ interesting variations of a mailer that I'm already testing anyhow, without putting any effort into
102
+ creating previews.
103
+
104
+ ### Summary
105
+
106
+ Taking snapshots is to integration and mailer tests what taking screenshots is to system tests.
107
+
108
+ ### The added bonus: UI library
109
+
110
+ While not the primary goal of the Snapshot Inspector, a side effect of taking snapshots is a library
111
+ of the UI surface area of the application.
112
+
113
+ One of the benefits of automated tests with well written test descriptions is to document the
114
+ behavior of a system under test. Since the output of a system under test in the case of integration
115
+ and mailer tests is a renderable UI, if we take snapshots of all tested responses and emails, we
116
+ consequently generate a browsable UI library of the whole application.
117
+
118
+ Of course, that only applies to applications that are heavy on the server side rendering and use
119
+ a tiny bit of JavaScript on top. In the world of [Hotwire](https://hotwired.dev), I imagine there can
120
+ be quite a few applications like that.
121
+
122
+ ## Installation
123
+
124
+ Add the gem to your application's Gemfile under `:development` and `:test` groups. Snapshots are taken in the test environment and inspected in the development environment.
125
+
126
+ ```ruby
127
+ group [:development, :test] do
128
+ gem "snapshot_inspector"
129
+ end
130
+ ```
131
+
132
+ Then execute:
133
+ ```bash
134
+ bundle install
135
+ ```
136
+
137
+ ## Usage
138
+
139
+ Take snapshots by placing a helper method `take_snapshot` in tests that deal with instances of `ActionDispatch::TestResponse` or `ActionMailer::MessageDelivery`.
140
+ For example, in controller, integration, or mailer tests.
141
+
142
+ For the best experience, take snapshots before assertions. That way it is possible to inspect them as part of the investigation of why an assertion failed.
143
+
144
+ An example from an integration test:
145
+
146
+ ```ruby
147
+ test "should get index" do
148
+ get root_path
149
+
150
+ take_snapshot response # <-- takes a snapshot of the response (an instance of ActionDispatch::TestResponse)
151
+
152
+ assert_response :success
153
+ end
154
+ ```
155
+
156
+ An example of a mailer test:
157
+
158
+ ```ruby
159
+ test "welcome mail" do
160
+ mail = NotifierMailer.welcome
161
+
162
+ take_snapshot mail # <-- takes a snapshot of the mail (an instance of ActionMailer::MessageDelivery)
163
+
164
+ assert_equal "Welcome!", mail.subject
165
+ end
166
+ ```
167
+
168
+ When the tests are run, to avoid the performance overhead, the snapshot taking is skipped by default.
169
+ To enable them, run the tests with a flag `--take-snapshots`. The flag works with the default testing framework only.
170
+
171
+ ```bash
172
+ bin/rails test --take-snapshots
173
+ TAKE_SNAPSHOTS=1 bin/rails test
174
+ ```
175
+
176
+ If you are using RSpec, use the environment variable `TAKE_SNAPSHOTS`. The variable also works with the default testing framework.
177
+
178
+ ```bash
179
+ TAKE_SNAPSHOTS=1 bin/rspec
180
+ ```
181
+
182
+ Then start your local server and visit http://localhost:300/rails/snapshots.
183
+
184
+ ### Live Reloading
185
+
186
+ If you wish for the snapshots in a browser to live reload whenever a test run is completed,
187
+ Snapshot Inspector works out of the box with [hotwire-livereload](https://github.com/kirillplatonov/hotwire-livereload).
188
+ Besides the general installation instructions, add the following lines into `development.rb`.
189
+
190
+ ```ruby
191
+ config.hotwire_livereload.listen_paths << SnapshotInspector::Storage.snapshots_directory
192
+ config.hotwire_livereload.force_reload_paths << SnapshotInspector::Storage.snapshots_directory
193
+ ```
194
+
195
+ ## How it works
196
+
197
+ - By default, snapshot taking is skipped to preserve the performance of a test suit in CI and similar environments.
198
+ - Snapshots are stored in `tmp/snapshot_inspector` folder.
199
+ - For every test run, existing snapshots are cleared and new snapshots are generated.
200
+ - The testing framework (Rails' default or RSpec) is autodetected.
201
+ - By default, when a snapshot is rendered all JavaScript related tags (`<script>` and `<link>`) are removed
202
+ from the HTML. That way we can pretty closely simulate how a page looks like with JavaScript turned off
203
+ without going into the trouble of turning it off in a browser, and still taking advantage of live reloading.
204
+
205
+ ## Unresolved Challenges
206
+
207
+ Imagine that you are building a page with form. You wish to test drive the development while at the
208
+ same time use the Snapshot Inspector to see how the page looks after every change.
209
+
210
+ It takes more time to make a change in the view, run the test (and generate a snapshot),
211
+ and reload the browser than to run a development server and use live reloading directly.
212
+ Even with live reloading turned on and using things like [guard](https://github.com/guard/guard).
213
+
214
+ It would be nice to find a way to speed up the tests. Perhaps there is a way to run them
215
+ interactively from a console and to run something similar to `reload!` after every change? Please reach out
216
+ via GitHub Issues if you have suggestions.
217
+
218
+ ## Contributing
219
+
220
+ - Fork the repo
221
+ - Create your feature branch (git checkout -b my-new-feature)
222
+ - Commit your changes (git commit -am 'Add some feature')
223
+ - Push to the branch (git push origin my-new-feature)
224
+ - Create a new Pull Request
225
+
226
+ ## License
227
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ require "bundler/setup"
2
+
3
+ APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__)
4
+ load "rails/tasks/engine.rake"
5
+
6
+ load "rails/tasks/statistics.rake"
7
+
8
+ require "bundler/gem_tasks"
@@ -0,0 +1,2 @@
1
+ //= link_directory ../../stylesheets/snapshot_inspector .css
2
+ //= link_tree ../../javascripts/snapshot_inspector .js
@@ -0,0 +1 @@
1
+ import "controllers"
@@ -0,0 +1,33 @@
1
+ /*
2
+ * This is a manifest file that'll be compiled into application.css, which will include all the files
3
+ * listed below.
4
+ *
5
+ * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6
+ * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
7
+ *
8
+ * You're free to add application-wide styles to this file and they'll appear at the bottom of the
9
+ * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
10
+ * files in this directory. Styles in this file should be added after the last require_* statement.
11
+ * It is generally better to create a new file per style scope.
12
+ *
13
+ *= require_tree .
14
+ *= require_self
15
+ */
16
+
17
+ html {
18
+ height: 100%;
19
+ }
20
+
21
+ body {
22
+ background-color: #fff;
23
+ color: #333;
24
+ margin: 0;
25
+ padding: 0;
26
+ height: 100%;
27
+ }
28
+
29
+ body, p, ol, ul, td {
30
+ font-family: helvetica, verdana, arial, sans-serif;
31
+ font-size: 20px;
32
+ line-height: 1.7em;
33
+ }
@@ -0,0 +1,48 @@
1
+ body#mail_show {
2
+ display: flex;
3
+ flex-flow: column;
4
+ height: 100%;
5
+ }
6
+
7
+ body#mail_show header {
8
+ width: 100%;
9
+ padding: 10px 0 0 0;
10
+ margin: 0;
11
+ background: white;
12
+ font: 12px "Lucida Grande", sans-serif;
13
+ border-bottom: 1px solid #dedede;
14
+ overflow: hidden;
15
+ }
16
+
17
+ body#mail_show dl {
18
+ margin: 0 0 10px 0;
19
+ padding: 0;
20
+ }
21
+
22
+ body#mail_show dt {
23
+ width: 80px;
24
+ padding: 1px;
25
+ float: left;
26
+ clear: left;
27
+ text-align: right;
28
+ color: #7f7f7f;
29
+ }
30
+
31
+ body#mail_show dd {
32
+ margin-left: 90px; /* 80px + 10px */
33
+ padding: 1px;
34
+ }
35
+
36
+ body#mail_show dd:empty:before {
37
+ content: "\00a0"; // &nbsp;
38
+ }
39
+
40
+ body#mail_show td {
41
+ font: 12px "Lucida Grande", sans-serif;
42
+ }
43
+
44
+ body#mail_show iframe {
45
+ border: 0;
46
+ width: 100%;
47
+ flex: 1 1 auto;
48
+ }
@@ -0,0 +1,15 @@
1
+ main#not_found {
2
+ padding: 100px;
3
+ }
4
+
5
+ main#not_found h1 {
6
+ margin: 0;
7
+ font-size: 30px;
8
+ font-weight: normal;
9
+ color: #666666;
10
+ }
11
+
12
+ main#not_found p {
13
+ margin: 18px 0 0;
14
+ color: #666666;
15
+ }
@@ -0,0 +1,9 @@
1
+ body#response_show {
2
+ overflow: hidden;
3
+ }
4
+
5
+ body#response_show iframe#body {
6
+ border: 0;
7
+ width: 100%;
8
+ height: 100%;
9
+ }
@@ -0,0 +1,73 @@
1
+ body#snapshots_index main {
2
+ padding: 100px;
3
+ }
4
+
5
+ body#snapshots_index h1 {
6
+ margin: 0;
7
+ font-size: 30px;
8
+ }
9
+
10
+ body#snapshots_index h2 {
11
+ margin: 30px 0 0;
12
+ font-size: 24px;
13
+ }
14
+
15
+ body#snapshots_index p {
16
+ margin: 18px 0 0;
17
+ color: #666666;
18
+ }
19
+
20
+ body#snapshots_index ul {
21
+ margin: 3px 0 0;
22
+ }
23
+
24
+ body#snapshots_index h1, h2, h3, h4, h5, h6 {
25
+ font-weight: normal;
26
+ color: #666666;
27
+ }
28
+
29
+ body#snapshots_index pre {
30
+ background-color: #eee;
31
+ padding: 10px;
32
+ font-size: 11px;
33
+ white-space: pre-wrap;
34
+ }
35
+
36
+ body#snapshots_index a { color: #000; text-decoration: none; }
37
+ body#snapshots_index a:hover { text-decoration: underline; }
38
+
39
+ body#snapshots_index form.enable_javascript {
40
+ margin-top: 20px;
41
+ }
42
+
43
+ body#snapshots_index form.enable_javascript input {
44
+ transform: scale(1.4);
45
+ cursor: pointer;
46
+ }
47
+
48
+ body#snapshots_index form.enable_javascript label {
49
+ cursor: pointer;
50
+ user-select: none;
51
+ font-size: 19px;
52
+ display: flex;
53
+ align-items: center;
54
+ }
55
+
56
+ body#snapshots_index form.enable_javascript label span {
57
+ margin: 0 0 0 10px;
58
+ color: #666666;
59
+ }
60
+
61
+ @media (prefers-color-scheme: dark) {
62
+ body#snapshots_index {
63
+ background-color: #222;
64
+ color: #ececec;
65
+ }
66
+
67
+ body#snapshots_index pre {
68
+ background-color: #333;
69
+ }
70
+
71
+ body#snapshots_index a { color: #fff; }
72
+ body#snapshots_index a:hover { color: #fff; text-decoration: underline; }
73
+ }
@@ -0,0 +1,14 @@
1
+ module SnapshotInspector
2
+ class ApplicationController < ActionController::Base
3
+ helper SnapshotInspector::Engine.helpers
4
+
5
+ content_security_policy(false)
6
+
7
+ private
8
+
9
+ def snapshot_not_found(error)
10
+ @error = error
11
+ render "snapshot_inspector/snapshots/not_found", status: 404
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,57 @@
1
+ module SnapshotInspector
2
+ class Snapshots::MailController < ApplicationController
3
+ helper_method :part_query
4
+
5
+ rescue_from Snapshot::NotFound, with: :snapshot_not_found
6
+
7
+ def show
8
+ @snapshot = Snapshot.find(params[:slug])
9
+ @email = @snapshot.message
10
+
11
+ if params[:format] == "eml"
12
+ send_data @email.to_s, filename: "#{@snapshot.mailer_name}##{@snapshot.action_name}.eml"
13
+ else
14
+ @part = find_preferred_part(request.format, Mime[:html], Mime[:text])
15
+ render :show, formats: [:html]
16
+ end
17
+ end
18
+
19
+ def raw
20
+ @snapshot = Snapshot.find(params[:slug])
21
+ @email = @snapshot.message
22
+ part_type = Mime::Type.lookup(params[:part] || "text/html")
23
+
24
+ if (part = find_part(part_type))
25
+ response.content_type = part_type
26
+ render plain: part.respond_to?(:decoded) ? part.decoded : part
27
+ else
28
+ raise AbstractController::ActionNotFound, "Email part `#{part_type}` not found in a snapshot #{@snapshot.context.test_case_name}##{@snapshot.context.method_name}"
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ def find_preferred_part(*formats)
35
+ formats.each do |format|
36
+ if (part = @email.find_first_mime_type(format))
37
+ return part
38
+ end
39
+ end
40
+ if formats.any? { |f| @email.mime_type == f }
41
+ @email
42
+ end
43
+ end
44
+
45
+ def find_part(format)
46
+ if (part = @email.find_first_mime_type(format))
47
+ part
48
+ elsif @email.mime_type == format
49
+ @email
50
+ end
51
+ end
52
+
53
+ def part_query(mime_type)
54
+ request.query_parameters.merge(part: mime_type).to_query
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,15 @@
1
+ module SnapshotInspector
2
+ class Snapshots::ResponseController < ApplicationController
3
+ rescue_from Snapshot::NotFound, with: :snapshot_not_found
4
+
5
+ layout false, only: [:raw]
6
+
7
+ def show
8
+ @snapshot = Snapshot.find(params[:slug])
9
+ end
10
+
11
+ def raw
12
+ @snapshot = Snapshot.find(params[:slug])
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,7 @@
1
+ module SnapshotInspector
2
+ class SnapshotsController < ApplicationController
3
+ def index
4
+ @grouped_by_test_class = Snapshot.grouped_by_test_case
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,15 @@
1
+ module SnapshotInspector
2
+ module ApplicationHelper
3
+ def snapshot_inspector_importmap_tags(entry_point = "application", shim: true)
4
+ safe_join(
5
+ [
6
+ javascript_inline_importmap_tag(SnapshotInspector.configuration.importmap.to_json(resolver: self)),
7
+ javascript_importmap_module_preload_tags(SnapshotInspector.configuration.importmap),
8
+ (javascript_importmap_shim_nonce_configuration_tag if shim),
9
+ (javascript_importmap_shim_tag if shim),
10
+ javascript_import_module_tag(entry_point)
11
+ ].compact, "\n"
12
+ )
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,37 @@
1
+ module SnapshotInspector
2
+ module SnapshotsHelper
3
+ def prepare_for_render(body, enable_javascript:)
4
+ prepared =
5
+ if enable_javascript == "true"
6
+ body
7
+ else
8
+ remove_traces_of_javascript(body)
9
+ end
10
+
11
+ prepared.html_safe
12
+ end
13
+
14
+ def remove_traces_of_javascript(html)
15
+ doc = Nokogiri.HTML(html)
16
+
17
+ doc.css("script").each do |element|
18
+ element.replace("")
19
+ end
20
+
21
+ doc.css('link[href$=".js"]').each do |element|
22
+ element.replace("")
23
+ end
24
+
25
+ doc.to_html
26
+ end
27
+
28
+ def self.snapshot_path(snapshot, enable_javascript:)
29
+ case snapshot.type
30
+ when "mail"
31
+ SnapshotInspector::Engine.routes.url_helpers.mail_snapshot_path(slug: snapshot.slug)
32
+ when "response"
33
+ SnapshotInspector::Engine.routes.url_helpers.response_snapshot_path(slug: snapshot.slug, enable_javascript: enable_javascript)
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,6 @@
1
+ module SnapshotInspector
2
+ class ApplicationMailer < ActionMailer::Base
3
+ default from: "from@example.com"
4
+ layout "mailer"
5
+ end
6
+ end
@@ -0,0 +1,49 @@
1
+ module SnapshotInspector
2
+ class Snapshot
3
+ class Context
4
+ class_attribute :registry, default: {}, instance_writer: false, instance_predicate: false
5
+
6
+ def self.test_framework(name)
7
+ registry[name] = self
8
+ end
9
+
10
+ def self.extract(context)
11
+ record = registry[context[:test_framework]].new
12
+ record.extract(context)
13
+ record
14
+ end
15
+
16
+ def self.from_hash(hash)
17
+ record = registry[hash[:test_framework].to_sym].new
18
+ record.from_hash(hash)
19
+ record
20
+ end
21
+
22
+ # @private
23
+ def extract(_context)
24
+ raise "Implement in a child class."
25
+ end
26
+
27
+ # @private
28
+ def from_hash(_hash)
29
+ raise "Implement in a child class."
30
+ end
31
+
32
+ def to_slug
33
+ raise "Implement in a child class."
34
+ end
35
+
36
+ def name
37
+ raise "Implement in a child class."
38
+ end
39
+
40
+ def test_group
41
+ raise "Implement in a child class."
42
+ end
43
+
44
+ def order_index
45
+ raise "Implement in a child class."
46
+ end
47
+ end
48
+ end
49
+ end