snapshot_inspector 0.1.1

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 (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