snapshot_ui-rails 0.5.0
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 +7 -0
- data/CHANGELOG.md +14 -0
- data/LICENSE.txt +21 -0
- data/README.md +178 -0
- data/lib/snapshot_ui/rails/mail.rb +77 -0
- data/lib/snapshot_ui/rails/mail_snapshot.rb +65 -0
- data/lib/snapshot_ui/rails/railtie.rb +88 -0
- data/lib/snapshot_ui/rails/routes.rb +21 -0
- data/lib/snapshot_ui/rails/test/helpers.rb +74 -0
- data/lib/snapshot_ui/rails/version.rb +9 -0
- data/lib/snapshot_ui/rails/web/mail_renderer.rb +115 -0
- data/lib/snapshot_ui/rails/web/views/snapshots/mail/show.html.erb +56 -0
- data/lib/snapshot_ui/rails.rb +66 -0
- data/lib/snapshot_ui-rails.rb +4 -0
- data/snapshot_ui-rails.gemspec +29 -0
- metadata +92 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 0cf0d4ed677d5728c73352d9f2413512ca1ff519e10f7c8dd8915633c343ded6
|
|
4
|
+
data.tar.gz: dbbf6856befbbd129fb2ca9b684a755770c1d01ec4f5c81ea08b39f40a94f212
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: a41d4cb2b9e40cfe0f7300aaff7e74c44fa0affa0509eebbcce0bfad09cfb73dabcfdf46087192cd3383962860d4c20b645b89bb7bdb5e4f3fb1854243c34141
|
|
7
|
+
data.tar.gz: 80119bbeb6bc9bfe99e98d47a7688e86ccb6595ace6e2d151dfd0eab4f8e5cab35f38de399d73020053aa2c0261977a7c866d0d627e50658fdfb22c7369a49ab
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
## [Unreleased]
|
|
2
|
+
|
|
3
|
+
## [0.5.0] - 2026-10-26
|
|
4
|
+
|
|
5
|
+
- Initial release. Rails integration for Snapshot UI:
|
|
6
|
+
- Snapshot UI is configured automatically from the Rails application (root, tmp directory, mount path and host);
|
|
7
|
+
with the defaults no initializer is required.
|
|
8
|
+
- The URL shown after a test run is derived from `config.snapshot_ui.mount_path` and `config.snapshot_ui.host`
|
|
9
|
+
(the host defaults from the app's `default_url_options`, then `PORT`, then `http://localhost:3000`).
|
|
10
|
+
- `mount_snapshot_ui` mounts the web interface in `config/routes.rb`; the default path is `/rails/ui_snapshots`.
|
|
11
|
+
- `take_snapshot` is available in `ActionDispatch::IntegrationTest` without an explicit `include`.
|
|
12
|
+
- `take_snapshot` also captures emails (`ActionMailer::MessageDelivery` or `Mail::Message`) in mailer and
|
|
13
|
+
integration tests, so mailers can be reviewed in the browser instead of maintaining Action Mailer previews.
|
|
14
|
+
Both the HTML and plain-text parts are captured, with attachments and an `.eml` download.
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Tomaz Zlender
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# Snapshot UI for Rails
|
|
2
|
+
|
|
3
|
+
Rails integration for [Snapshot UI](https://github.com/tomazzlender/snapshot_ui). Take snapshots of responses in your
|
|
4
|
+
integration tests and view them in a browser — with no configuration.
|
|
5
|
+
|
|
6
|
+
This gem wires Snapshot UI into a Rails application: it fills in Snapshot UI's configuration from what Rails already
|
|
7
|
+
knows, adds a router helper to mount the web interface, and makes `take_snapshot` available in your integration tests.
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
```ruby
|
|
12
|
+
# Gemfile
|
|
13
|
+
group :development, :test do
|
|
14
|
+
gem "snapshot_ui-rails"
|
|
15
|
+
end
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
```sh
|
|
19
|
+
bundle install
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
The `snapshot_ui` gem is pulled in automatically.
|
|
23
|
+
|
|
24
|
+
## Usage
|
|
25
|
+
|
|
26
|
+
With the defaults, the whole setup is one line in your routes — **no initializer, no configuration**.
|
|
27
|
+
|
|
28
|
+
### 1. Mount the web interface
|
|
29
|
+
|
|
30
|
+
Snapshot UI is a development tool: it displays artifacts captured during test runs and has nothing to show in
|
|
31
|
+
production. Mount it only where you need it, the way you would Sidekiq's or Flipper's dashboards:
|
|
32
|
+
|
|
33
|
+
```ruby
|
|
34
|
+
# config/routes.rb
|
|
35
|
+
Rails.application.routes.draw do
|
|
36
|
+
mount_snapshot_ui if Rails.env.development?
|
|
37
|
+
# ...
|
|
38
|
+
end
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
This mounts the UI at `/rails/ui_snapshots` — alongside Rails' own development tools such as `/rails/info` and
|
|
42
|
+
`/rails/mailers`. Every snapshot you take shows up there, grouped by the test that produced it:
|
|
43
|
+
|
|
44
|
+

|
|
45
|
+
|
|
46
|
+
### 2. Take snapshots in your integration tests
|
|
47
|
+
|
|
48
|
+
`take_snapshot` is available in every `ActionDispatch::IntegrationTest` — no `include` required:
|
|
49
|
+
|
|
50
|
+
```ruby
|
|
51
|
+
require "test_helper"
|
|
52
|
+
|
|
53
|
+
class PlotsTest < ActionDispatch::IntegrationTest
|
|
54
|
+
test "shows a plot" do
|
|
55
|
+
get "/plots/A3"
|
|
56
|
+
|
|
57
|
+
take_snapshot(response)
|
|
58
|
+
|
|
59
|
+
assert_response :success
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Run the test with snapshots enabled and the response is captured and rendered exactly as it came back — here, a plot
|
|
65
|
+
page from a community-garden site:
|
|
66
|
+
|
|
67
|
+

|
|
68
|
+
|
|
69
|
+
`take_snapshot` accepts an object that responds to `#body` (such as the integration test's `response`) or a plain
|
|
70
|
+
`String`, and two options:
|
|
71
|
+
|
|
72
|
+
```ruby
|
|
73
|
+
take_snapshot(response, title: "Plot detail", slug: "plot-A3")
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
* `title` — the name shown in the list (defaults to a name derived from the test).
|
|
77
|
+
* `slug` — a custom, stable URL for the snapshot (`/rails/ui_snapshots/plot-A3`); must be unique.
|
|
78
|
+
|
|
79
|
+
### 3. Snapshotting Action Mailer emails
|
|
80
|
+
|
|
81
|
+
`take_snapshot` also accepts an email, so you can review mailers in the browser instead of maintaining Action Mailer
|
|
82
|
+
previews:
|
|
83
|
+
|
|
84
|
+
```ruby
|
|
85
|
+
class NotifierMailerTest < ActionMailer::TestCase
|
|
86
|
+
test "welcome email" do
|
|
87
|
+
email = NotifierMailer.welcome(user)
|
|
88
|
+
take_snapshot(email)
|
|
89
|
+
|
|
90
|
+
assert_emails 0 # your usual assertions are unaffected
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
One call captures the whole email, and the snapshot renders with the same chrome as any Rails view — the subject,
|
|
96
|
+
mailer/action and address headers, attachments, and the rendered email itself:
|
|
97
|
+
|
|
98
|
+

|
|
99
|
+
|
|
100
|
+
Because the whole message is captured, **both the HTML and the plain-text versions are available** from the one call —
|
|
101
|
+
a switcher appears whenever the email has both, so there is nothing to keep in sync:
|
|
102
|
+
|
|
103
|
+

|
|
104
|
+
|
|
105
|
+
The snapshot also offers each attachment as a download and a **Download .eml** of the raw message, and the full header
|
|
106
|
+
set is one click away under “All headers”.
|
|
107
|
+
|
|
108
|
+
`take_snapshot` accepts an `ActionMailer::MessageDelivery` (what a mailer action returns) or a `Mail::Message`, and
|
|
109
|
+
the same `title:` and `slug:` options as responses. It is available in `ActionMailer::TestCase` and in integration
|
|
110
|
+
tests with no explicit `include`.
|
|
111
|
+
|
|
112
|
+
### 4. Run the tests and view the snapshots
|
|
113
|
+
|
|
114
|
+
Snapshots are only taken when you ask for them, via the `--take-snapshots` flag or `TAKE_SNAPSHOTS=1`:
|
|
115
|
+
|
|
116
|
+
```sh
|
|
117
|
+
bin/rails test test/integration --take-snapshots
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
At the end of the run the URL to review the snapshots is printed. Start your app (`bin/rails server`) and open
|
|
121
|
+
<http://localhost:3000/rails/ui_snapshots>. The page refreshes automatically as you take new snapshots.
|
|
122
|
+
|
|
123
|
+
You'll usually want the snapshots directory ignored by git:
|
|
124
|
+
|
|
125
|
+
```
|
|
126
|
+
# .gitignore
|
|
127
|
+
/tmp/snapshot_ui/
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## Configuration
|
|
131
|
+
|
|
132
|
+
**None is required.** Everything is inferred from the Rails application:
|
|
133
|
+
|
|
134
|
+
| Setting | Default |
|
|
135
|
+
| ------------------------ | ------------------------------------------------------------------------------- |
|
|
136
|
+
| `project_root_directory` | `Rails.root` |
|
|
137
|
+
| `storage_directory` | `Rails.root/tmp/snapshot_ui` |
|
|
138
|
+
| `mount_path` | `/rails/ui_snapshots` |
|
|
139
|
+
| `host` | the app's `default_url_options`, else `PORT`, else `http://localhost:3000` |
|
|
140
|
+
|
|
141
|
+
The mount path and host are only used to build the "ready for review" URL printed after a test run and the link on the
|
|
142
|
+
UI's empty state; the interface itself works wherever it is mounted.
|
|
143
|
+
|
|
144
|
+
To override, add an initializer — it runs after this gem's defaults, so your values win:
|
|
145
|
+
|
|
146
|
+
```ruby
|
|
147
|
+
# config/initializers/snapshot_ui.rb
|
|
148
|
+
Rails.application.configure do
|
|
149
|
+
# Mount somewhere other than /rails/ui_snapshots (also change the argument to mount_snapshot_ui):
|
|
150
|
+
config.snapshot_ui.mount_path = "/admin/snapshots"
|
|
151
|
+
|
|
152
|
+
# Only needed if your development server isn't on http://localhost:3000:
|
|
153
|
+
config.snapshot_ui.host = "http://localhost:4000"
|
|
154
|
+
end
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
```ruby
|
|
158
|
+
# config/routes.rb — pass the same path when it isn't the default
|
|
159
|
+
mount_snapshot_ui at: "/admin/snapshots" if Rails.env.development?
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
If you already set `config.action_controller.default_url_options`, the host is taken from there and you don't need to
|
|
163
|
+
set `config.snapshot_ui.host` at all.
|
|
164
|
+
|
|
165
|
+
## How it works
|
|
166
|
+
|
|
167
|
+
The gem is a thin adapter over `snapshot_ui`. A Railtie:
|
|
168
|
+
|
|
169
|
+
* fills in `SnapshotUI.configuration` from `Rails.root`, the mount path and the resolved host;
|
|
170
|
+
* requires the `mount_snapshot_ui` router helper, which mounts the same `SnapshotUI::Web` Rack app the core gem ships;
|
|
171
|
+
* includes `SnapshotUI::Test::MinitestHelpers` into `ActionDispatch::IntegrationTest` via `ActiveSupport.on_load`.
|
|
172
|
+
|
|
173
|
+
For anything not specific to Rails, see the
|
|
174
|
+
[Snapshot UI README](https://github.com/tomazzlender/snapshot_ui/tree/main/gems/snapshot_ui).
|
|
175
|
+
|
|
176
|
+
## License
|
|
177
|
+
|
|
178
|
+
MIT.
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "mail"
|
|
4
|
+
|
|
5
|
+
module SnapshotUI
|
|
6
|
+
module Rails
|
|
7
|
+
# Reads a stored "mail" snapshot back into a usable form: the reconstructed
|
|
8
|
+
# Mail::Message plus the parts and metadata the mail view needs.
|
|
9
|
+
class Mail
|
|
10
|
+
HTML = "text/html"
|
|
11
|
+
TEXT = "text/plain"
|
|
12
|
+
|
|
13
|
+
def initialize(snapshot)
|
|
14
|
+
@type_data = snapshot.type_data
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def message
|
|
18
|
+
@message ||= begin
|
|
19
|
+
message = ::Mail::Message.new(@type_data[:message].to_s)
|
|
20
|
+
message.bcc = @type_data[:bcc] if @type_data[:bcc]
|
|
21
|
+
message
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def mailer
|
|
26
|
+
@type_data[:mailer]
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def action
|
|
30
|
+
@type_data[:action]
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def html?
|
|
34
|
+
!part_for(HTML).nil?
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def text?
|
|
38
|
+
!part_for(TEXT).nil?
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Both alternatives were captured — offer a switcher.
|
|
42
|
+
def multipart_alternatives?
|
|
43
|
+
html? && text?
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def default_mime_type
|
|
47
|
+
if html?
|
|
48
|
+
HTML
|
|
49
|
+
else
|
|
50
|
+
(text? ? TEXT : message.mime_type)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# The decoded body for a MIME type, falling back to the message body for a
|
|
55
|
+
# single-part email whose type matches.
|
|
56
|
+
def part_body(mime_type)
|
|
57
|
+
if (part = part_for(mime_type))
|
|
58
|
+
part.respond_to?(:decoded) ? part.decoded : part.to_s
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def attachments
|
|
63
|
+
message.attachments || []
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def eml
|
|
67
|
+
message.to_s
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
private
|
|
71
|
+
|
|
72
|
+
def part_for(mime_type)
|
|
73
|
+
message.find_first_mime_type(mime_type) || ((message.mime_type == mime_type) ? message : nil)
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "snapshot_ui/snapshot"
|
|
4
|
+
|
|
5
|
+
module SnapshotUI
|
|
6
|
+
module Rails
|
|
7
|
+
# Captures an ActionMailer / Mail message as a snapshot.
|
|
8
|
+
#
|
|
9
|
+
# The whole email is serialized to its RFC-822 form (which keeps every MIME
|
|
10
|
+
# part — both the HTML and the plain-text alternatives, and any attachments),
|
|
11
|
+
# so a single stored string round-trips to both views. BCC is stored
|
|
12
|
+
# separately because it is not part of the serialized message.
|
|
13
|
+
module MailSnapshot
|
|
14
|
+
TYPE = "mail"
|
|
15
|
+
|
|
16
|
+
module_function
|
|
17
|
+
|
|
18
|
+
def persist(mail, context:)
|
|
19
|
+
message = to_mail_message(mail)
|
|
20
|
+
|
|
21
|
+
SnapshotUI::Snapshot.persist(
|
|
22
|
+
snapshotee: nil,
|
|
23
|
+
context: context,
|
|
24
|
+
type: TYPE,
|
|
25
|
+
type_data: {
|
|
26
|
+
message: message.to_s,
|
|
27
|
+
bcc: Array(message.bcc),
|
|
28
|
+
mailer: mailer_name(mail),
|
|
29
|
+
action: action_name(mail)
|
|
30
|
+
}
|
|
31
|
+
)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# ActionMailer hands tests a MessageDelivery; #message is the underlying
|
|
35
|
+
# Mail::Message. A bare Mail::Message is accepted too.
|
|
36
|
+
def to_mail_message(mail)
|
|
37
|
+
mail.respond_to?(:message) ? mail.message : mail
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# The mailer class name. ActionMailer::MessageDelivery stores it as
|
|
41
|
+
# @mailer_class; #mailer_class is unreliable across Rails versions (on some
|
|
42
|
+
# it returns MessageDelivery itself), so prefer the ivar.
|
|
43
|
+
def mailer_name(mail)
|
|
44
|
+
mailer_class =
|
|
45
|
+
if mail.instance_variable_defined?(:@mailer_class)
|
|
46
|
+
mail.instance_variable_get(:@mailer_class)
|
|
47
|
+
elsif mail.respond_to?(:mailer_class)
|
|
48
|
+
mail.mailer_class
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
(mailer_class || mail.class).name
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# ActionMailer::MessageDelivery stores the action as @action (a Symbol);
|
|
55
|
+
# some versions/objects expose #action_name. Fall back gracefully.
|
|
56
|
+
def action_name(mail)
|
|
57
|
+
if mail.respond_to?(:action_name)
|
|
58
|
+
mail.action_name
|
|
59
|
+
elsif mail.instance_variable_defined?(:@action)
|
|
60
|
+
mail.instance_variable_get(:@action)&.to_s
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails/railtie"
|
|
4
|
+
require "snapshot_ui"
|
|
5
|
+
|
|
6
|
+
module SnapshotUI
|
|
7
|
+
module Rails
|
|
8
|
+
# Wires Snapshot UI into a Rails application:
|
|
9
|
+
#
|
|
10
|
+
# * fills in the configuration Snapshot UI needs from what Rails already
|
|
11
|
+
# knows (its root, its tmp directory and the mount path), so with the
|
|
12
|
+
# defaults no initializer is required;
|
|
13
|
+
# * adds +mount_snapshot_ui+ to the router;
|
|
14
|
+
# * makes +take_snapshot+ available in integration tests.
|
|
15
|
+
#
|
|
16
|
+
# To override anything, set it under +config.snapshot_ui+ in
|
|
17
|
+
# +config/initializers/snapshot_ui.rb+ (or an environment file):
|
|
18
|
+
#
|
|
19
|
+
# Rails.application.configure do
|
|
20
|
+
# config.snapshot_ui.mount_path = "/admin/snapshots"
|
|
21
|
+
# config.snapshot_ui.host = "http://localhost:4000"
|
|
22
|
+
# end
|
|
23
|
+
class Railtie < ::Rails::Railtie
|
|
24
|
+
config.snapshot_ui = ActiveSupport::OrderedOptions.new
|
|
25
|
+
# The path the UI is mounted at.
|
|
26
|
+
config.snapshot_ui.mount_path = nil
|
|
27
|
+
# The scheme://host[:port] the UI is reachable at, for the "ready for
|
|
28
|
+
# review" message. Defaults to the application's declared host, else
|
|
29
|
+
# http://localhost:3000. See SnapshotUI::Rails.default_host.
|
|
30
|
+
config.snapshot_ui.host = nil
|
|
31
|
+
# A full URL that overrides both of the above for the printed message.
|
|
32
|
+
# Rarely needed; prefer mount_path + host.
|
|
33
|
+
config.snapshot_ui.web_url = nil
|
|
34
|
+
|
|
35
|
+
# The mount path is known immediately, so the router helper and the
|
|
36
|
+
# storage paths can use it.
|
|
37
|
+
initializer "snapshot_ui.configure" do |app|
|
|
38
|
+
SnapshotUI::Rails.mount_path = app.config.snapshot_ui.mount_path if app.config.snapshot_ui.mount_path
|
|
39
|
+
|
|
40
|
+
SnapshotUI.configure do |config|
|
|
41
|
+
config.project_root_directory ||= ::Rails.root.to_s
|
|
42
|
+
config.storage_directory ||= ::Rails.root.join("tmp", "snapshot_ui").to_s
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# The web URL is resolved after initialization so that a host declared in
|
|
47
|
+
# a user initializer (config.action_controller.default_url_options, or
|
|
48
|
+
# config.snapshot_ui.host) has already been set.
|
|
49
|
+
config.after_initialize do |app|
|
|
50
|
+
options = app.config.snapshot_ui
|
|
51
|
+
|
|
52
|
+
SnapshotUI.configuration.web_url =
|
|
53
|
+
options.web_url ||
|
|
54
|
+
SnapshotUI::Rails.web_url(host: options.host || SnapshotUI::Rails.default_host(app))
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# The +mount_snapshot_ui+ router helper.
|
|
58
|
+
initializer "snapshot_ui.routes" do
|
|
59
|
+
require_relative "routes"
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Teach the web interface to render "mail" snapshots.
|
|
63
|
+
initializer "snapshot_ui.mail_renderer" do
|
|
64
|
+
require "snapshot_ui/web"
|
|
65
|
+
require_relative "mail_snapshot"
|
|
66
|
+
require_relative "web/mail_renderer"
|
|
67
|
+
SnapshotUI::Web.register_renderer(
|
|
68
|
+
SnapshotUI::Rails::MailSnapshot::TYPE,
|
|
69
|
+
SnapshotUI::Rails::Web::MailRenderer
|
|
70
|
+
)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# +take_snapshot+ in tests, without an explicit include. One helper covers
|
|
74
|
+
# both integration tests (responses) and mailer tests (emails).
|
|
75
|
+
initializer "snapshot_ui.test_helpers" do
|
|
76
|
+
ActiveSupport.on_load(:action_dispatch_integration_test) do
|
|
77
|
+
require_relative "test/helpers"
|
|
78
|
+
include SnapshotUI::Rails::Test::Helpers
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
ActiveSupport.on_load(:action_mailer_test_case) do
|
|
82
|
+
require_relative "test/helpers"
|
|
83
|
+
include SnapshotUI::Rails::Test::Helpers
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "snapshot_ui/web"
|
|
4
|
+
|
|
5
|
+
module ActionDispatch
|
|
6
|
+
module Routing
|
|
7
|
+
class Mapper
|
|
8
|
+
# Mounts the Snapshot UI web interface.
|
|
9
|
+
#
|
|
10
|
+
# # config/routes.rb
|
|
11
|
+
# mount_snapshot_ui
|
|
12
|
+
# mount_snapshot_ui at: "/admin/snapshots"
|
|
13
|
+
#
|
|
14
|
+
# With no argument it mounts at +config.snapshot_ui.mount_path+
|
|
15
|
+
# (default +/rails/ui_snapshots+).
|
|
16
|
+
def mount_snapshot_ui(at: SnapshotUI::Rails.mount_path)
|
|
17
|
+
mount SnapshotUI::Web, at: at, as: "snapshot_ui"
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "snapshot_ui"
|
|
4
|
+
require_relative "../mail_snapshot"
|
|
5
|
+
|
|
6
|
+
module SnapshotUI
|
|
7
|
+
module Rails
|
|
8
|
+
module Test
|
|
9
|
+
# A single +take_snapshot+ for Rails tests that dispatches by what it is
|
|
10
|
+
# given: an email (ActionMailer::MessageDelivery or Mail::Message) is
|
|
11
|
+
# captured as a mail snapshot; anything else (a response, or a String of
|
|
12
|
+
# HTML) is captured as a response snapshot.
|
|
13
|
+
#
|
|
14
|
+
# Included into both ActionDispatch::IntegrationTest and
|
|
15
|
+
# ActionMailer::TestCase by the railtie, so one method covers both.
|
|
16
|
+
module Helpers
|
|
17
|
+
def take_snapshot(snapshotee, title: nil, slug: nil)
|
|
18
|
+
return unless SnapshotUI.snapshot_taking_enabled?
|
|
19
|
+
|
|
20
|
+
type, type_data = classify(snapshotee)
|
|
21
|
+
SnapshotUI.exit_if_not_configured!
|
|
22
|
+
increment_take_snapshot_counter_scoped_by_test
|
|
23
|
+
|
|
24
|
+
context = {
|
|
25
|
+
test_framework: "minitest",
|
|
26
|
+
method_name: name,
|
|
27
|
+
source_location: build_source_location(caller_locations(1..1).first),
|
|
28
|
+
test_case_name: self.class.to_s,
|
|
29
|
+
take_snapshot_index: _take_snapshot_counter - 1,
|
|
30
|
+
metadata: {title: title, slug: slug}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if type == SnapshotUI::Rails::MailSnapshot::TYPE
|
|
34
|
+
SnapshotUI::Rails::MailSnapshot.persist(snapshotee, context: context)
|
|
35
|
+
else
|
|
36
|
+
SnapshotUI::Snapshot.persist(snapshotee: snapshotee, context: context, type: type, type_data: type_data)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
# Returns [type, type_data]. type_data is nil for responses (the core
|
|
43
|
+
# extracts it from the snapshotee).
|
|
44
|
+
def classify(snapshotee)
|
|
45
|
+
if mail_like?(snapshotee)
|
|
46
|
+
[SnapshotUI::Rails::MailSnapshot::TYPE, nil]
|
|
47
|
+
elsif snapshotee.respond_to?(:body) || snapshotee.is_a?(String)
|
|
48
|
+
[SnapshotUI::Snapshot::DEFAULT_TYPE, nil]
|
|
49
|
+
else
|
|
50
|
+
raise ArgumentError,
|
|
51
|
+
"#take_snapshot accepts a response (something responding to #body), a String of HTML, " \
|
|
52
|
+
"or an email (ActionMailer::MessageDelivery / Mail::Message). You provided `#{snapshotee.class}`."
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def mail_like?(object)
|
|
57
|
+
(defined?(ActionMailer::MessageDelivery) && object.is_a?(ActionMailer::MessageDelivery)) ||
|
|
58
|
+
(defined?(::Mail::Message) && object.is_a?(::Mail::Message))
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
attr_reader :_take_snapshot_counter
|
|
62
|
+
|
|
63
|
+
def increment_take_snapshot_counter_scoped_by_test
|
|
64
|
+
@_take_snapshot_counter ||= 0
|
|
65
|
+
@_take_snapshot_counter += 1
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def build_source_location(caller_location)
|
|
69
|
+
[caller_location.path, caller_location.lineno]
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "base64"
|
|
4
|
+
require "rack/utils"
|
|
5
|
+
require_relative "../mail"
|
|
6
|
+
|
|
7
|
+
module SnapshotUI
|
|
8
|
+
module Rails
|
|
9
|
+
module Web
|
|
10
|
+
# Renders "mail" snapshots for SnapshotUI::Web. Registered by the railtie.
|
|
11
|
+
#
|
|
12
|
+
# #show renders the email chrome (headers, an HTML/plain-text switcher when
|
|
13
|
+
# both parts exist, attachments, an .eml download) with the chosen part
|
|
14
|
+
# shown in an iframe. #raw serves a single part with its content type, or
|
|
15
|
+
# the .eml file.
|
|
16
|
+
module MailRenderer
|
|
17
|
+
VIEW = "snapshots/mail/show"
|
|
18
|
+
|
|
19
|
+
module_function
|
|
20
|
+
|
|
21
|
+
def show(snapshot, view)
|
|
22
|
+
mail = SnapshotUI::Rails::Mail.new(snapshot)
|
|
23
|
+
part = requested_part(view) || mail.default_mime_type
|
|
24
|
+
|
|
25
|
+
presenter = Presenter.new(snapshot: snapshot, mail: mail, part: part, view: view)
|
|
26
|
+
view.render_body_in_layout(presenter.render_body, status: 200)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def raw(snapshot, view)
|
|
30
|
+
mail = SnapshotUI::Rails::Mail.new(snapshot)
|
|
31
|
+
|
|
32
|
+
if view.request.params["eml"]
|
|
33
|
+
filename = [mail.mailer, mail.action].compact.join("#")
|
|
34
|
+
filename = "email" if filename.empty?
|
|
35
|
+
return [200,
|
|
36
|
+
{"content-type" => "message/rfc822",
|
|
37
|
+
"content-disposition" => %(attachment; filename="#{filename}.eml")},
|
|
38
|
+
[mail.eml]]
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
mime_type = view.request.params["part"] || mail.default_mime_type
|
|
42
|
+
body = mail.part_body(mime_type)
|
|
43
|
+
|
|
44
|
+
if body
|
|
45
|
+
[200, {"content-type" => "#{mime_type}; charset=utf-8"}, [body]]
|
|
46
|
+
else
|
|
47
|
+
[404, {"content-type" => "text/plain; charset=utf-8"},
|
|
48
|
+
["Email part `#{mime_type}` not found in this snapshot."]]
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def requested_part(view)
|
|
53
|
+
mime = view.request.params["part"]
|
|
54
|
+
mime if [SnapshotUI::Rails::Mail::HTML, SnapshotUI::Rails::Mail::TEXT].include?(mime)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Passed as the render context so the template can call these helpers and
|
|
58
|
+
# still reach the core view's URL/asset helpers via method_missing.
|
|
59
|
+
class Presenter
|
|
60
|
+
H = Rack::Utils
|
|
61
|
+
|
|
62
|
+
def initialize(snapshot:, mail:, part:, view:)
|
|
63
|
+
@snapshot = snapshot
|
|
64
|
+
@mail = mail
|
|
65
|
+
@part = part
|
|
66
|
+
@view = view
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
attr_reader :snapshot, :mail, :part, :view
|
|
70
|
+
|
|
71
|
+
# Renders this gem's mail template to a <body> string. The template
|
|
72
|
+
# lives under this file's directory (web/views/...).
|
|
73
|
+
def render_body
|
|
74
|
+
template = File.expand_path("views/#{MailRenderer::VIEW}.html.erb", __dir__)
|
|
75
|
+
ERB.new(File.read(template), trim_mode: "-").result(binding)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def h(text)
|
|
79
|
+
H.escape_html(text.to_s)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def raw_part_url(mime_type)
|
|
83
|
+
base = view.raw_snapshot_path(snapshot.slug)
|
|
84
|
+
"#{base}?#{H.build_query("part" => mime_type)}"
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def eml_url
|
|
88
|
+
"#{view.raw_snapshot_path(snapshot.slug)}?#{H.build_query("eml" => "1")}"
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def show_url(mime_type)
|
|
92
|
+
"#{view.snapshot_path(snapshot.slug)}?#{H.build_query("part" => mime_type)}"
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def attachment_data_uri(attachment)
|
|
96
|
+
"data:application/octet-stream;base64,#{Base64.strict_encode64(attachment.decoded)}"
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def attachment_filename(attachment)
|
|
100
|
+
attachment.respond_to?(:original_filename) ? attachment.original_filename : attachment.filename
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Let the template reach the core Application helpers (root_path, etc.).
|
|
104
|
+
def method_missing(name, *args, &block)
|
|
105
|
+
view.respond_to?(name, true) ? view.send(name, *args, &block) : super
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def respond_to_missing?(name, include_private = false)
|
|
109
|
+
view.respond_to?(name, include_private) || super
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
<body id="snapshots_mail" <%= refresh_controller %>>
|
|
2
|
+
<div class="mail">
|
|
3
|
+
<header class="mail_header">
|
|
4
|
+
<a class="back" href="<%= root_path %>">← All snapshots</a>
|
|
5
|
+
<h1><%= h(mail.message.subject) %></h1>
|
|
6
|
+
<% if mail.mailer || mail.action %>
|
|
7
|
+
<p class="mailer"><%= h([mail.mailer, mail.action].compact.join("#")) %></p>
|
|
8
|
+
<% end %>
|
|
9
|
+
|
|
10
|
+
<dl class="fields">
|
|
11
|
+
<dt>From</dt><dd><%= h(mail.message.header["from"]) %></dd>
|
|
12
|
+
<dt>To</dt><dd><%= h(mail.message.header["to"]) %></dd>
|
|
13
|
+
<% if mail.message.cc %><dt>Cc</dt><dd><%= h(mail.message.header["cc"]) %></dd><% end %>
|
|
14
|
+
<% if mail.message.bcc && !Array(mail.message.bcc).empty? %><dt>Bcc</dt><dd><%= h(Array(mail.message.bcc).join(", ")) %></dd><% end %>
|
|
15
|
+
<% if mail.message.reply_to %><dt>Reply-To</dt><dd><%= h(mail.message.header["reply-to"]) %></dd><% end %>
|
|
16
|
+
</dl>
|
|
17
|
+
|
|
18
|
+
<div class="mail_toolbar">
|
|
19
|
+
<% if mail.multipart_alternatives? %>
|
|
20
|
+
<div class="parts">
|
|
21
|
+
<a class="part_tab <%= "is_active" if part == SnapshotUI::Rails::Mail::HTML %>" href="<%= show_url(SnapshotUI::Rails::Mail::HTML) %>">HTML</a>
|
|
22
|
+
<a class="part_tab <%= "is_active" if part == SnapshotUI::Rails::Mail::TEXT %>" href="<%= show_url(SnapshotUI::Rails::Mail::TEXT) %>">Plain text</a>
|
|
23
|
+
</div>
|
|
24
|
+
<% else %>
|
|
25
|
+
<span class="part_single"><%= part == SnapshotUI::Rails::Mail::HTML ? "HTML email" : "Plain-text email" %></span>
|
|
26
|
+
<% end %>
|
|
27
|
+
|
|
28
|
+
<a class="eml" href="<%= eml_url %>">Download .eml</a>
|
|
29
|
+
</div>
|
|
30
|
+
|
|
31
|
+
<% unless mail.attachments.empty? %>
|
|
32
|
+
<div class="attachments">
|
|
33
|
+
<span class="attachments_label">Attachments:</span>
|
|
34
|
+
<% mail.attachments.each do |attachment| %>
|
|
35
|
+
<a class="attachment" href="<%= attachment_data_uri(attachment) %>" download="<%= h(attachment_filename(attachment)) %>"><%= h(attachment_filename(attachment)) %></a>
|
|
36
|
+
<% end %>
|
|
37
|
+
</div>
|
|
38
|
+
<% end %>
|
|
39
|
+
|
|
40
|
+
<details class="all_headers">
|
|
41
|
+
<summary>All headers</summary>
|
|
42
|
+
<table>
|
|
43
|
+
<% mail.message.header_fields.each do |field| %>
|
|
44
|
+
<tr><td class="name"><%= h(field.name) %></td><td><%= h(field.value) %></td></tr>
|
|
45
|
+
<% end %>
|
|
46
|
+
</table>
|
|
47
|
+
</details>
|
|
48
|
+
</header>
|
|
49
|
+
|
|
50
|
+
<% if mail.part_body(part) %>
|
|
51
|
+
<iframe id="raw" class="mail_body <%= "is_text" if part == SnapshotUI::Rails::Mail::TEXT %>" src="<%= raw_part_url(part) %>"></iframe>
|
|
52
|
+
<% else %>
|
|
53
|
+
<p class="empty">This email has no <%= part == SnapshotUI::Rails::Mail::HTML ? "HTML" : "plain-text" %> part.</p>
|
|
54
|
+
<% end %>
|
|
55
|
+
</div>
|
|
56
|
+
</body>
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "snapshot_ui"
|
|
4
|
+
require_relative "rails/version"
|
|
5
|
+
|
|
6
|
+
module SnapshotUI
|
|
7
|
+
# Rails integration for Snapshot UI. Requiring this file (which Bundler does
|
|
8
|
+
# automatically for the +snapshot_ui-rails+ gem) loads the railtie that
|
|
9
|
+
# configures Snapshot UI from the Rails application and adds the router and
|
|
10
|
+
# test helpers.
|
|
11
|
+
#
|
|
12
|
+
# With the defaults no configuration is needed. To override, set values under
|
|
13
|
+
# +config.snapshot_ui+ (see the README).
|
|
14
|
+
module Rails
|
|
15
|
+
DEFAULT_MOUNT_PATH = "/rails/ui_snapshots"
|
|
16
|
+
DEFAULT_HOST = "http://localhost:3000"
|
|
17
|
+
|
|
18
|
+
class << self
|
|
19
|
+
# The path the UI is mounted at. Set by the railtie from
|
|
20
|
+
# +config.snapshot_ui.mount_path+; used to build the default web URL and
|
|
21
|
+
# by +mount_snapshot_ui+.
|
|
22
|
+
attr_writer :mount_path
|
|
23
|
+
|
|
24
|
+
def mount_path
|
|
25
|
+
@mount_path || DEFAULT_MOUNT_PATH
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Builds the URL the "ready for review" message and the empty-state link
|
|
29
|
+
# point at, from a host (scheme://host:port) and the mount path.
|
|
30
|
+
def web_url(host:, mount_path: self.mount_path)
|
|
31
|
+
"#{host.to_s.chomp("/")}#{mount_path}"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# The host (scheme://host[:port]) to use when the application hasn't been
|
|
35
|
+
# told one explicitly. Rails does not know the address its development
|
|
36
|
+
# server will bind to, so this is a best effort: the host declared in
|
|
37
|
+
# +config.action_controller.default_url_options+ if present, then the PORT
|
|
38
|
+
# environment variable, then the conventional http://localhost:3000.
|
|
39
|
+
def default_host(app)
|
|
40
|
+
options = default_url_options(app)
|
|
41
|
+
return DEFAULT_HOST if options.empty? && !ENV.key?("PORT")
|
|
42
|
+
|
|
43
|
+
scheme = url_scheme(app, options)
|
|
44
|
+
host = options[:host] || "localhost"
|
|
45
|
+
port = options[:port] || ENV["PORT"] || 3000
|
|
46
|
+
|
|
47
|
+
"#{scheme}://#{host}:#{port}"
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
private
|
|
51
|
+
|
|
52
|
+
def default_url_options(app)
|
|
53
|
+
app.config.action_controller.default_url_options || {}
|
|
54
|
+
rescue
|
|
55
|
+
{}
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def url_scheme(app, options)
|
|
59
|
+
scheme = options[:protocol] || (app.config.force_ssl ? "https" : "http")
|
|
60
|
+
scheme.to_s.sub(%r{://\z}, "")
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
require_relative "rails/railtie"
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../snapshot_ui/lib/snapshot_ui/version"
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |spec|
|
|
6
|
+
spec.name = "snapshot_ui-rails"
|
|
7
|
+
spec.version = SnapshotUI::VERSION
|
|
8
|
+
spec.authors = ["Tomaz Zlender"]
|
|
9
|
+
spec.email = ["tomaz@zlender.se"]
|
|
10
|
+
|
|
11
|
+
spec.summary = "Rails integration for Snapshot UI — take snapshots of responses in tests and view them in a browser."
|
|
12
|
+
spec.homepage = "https://github.com/tomazzlender/snapshot_ui"
|
|
13
|
+
spec.license = "MIT"
|
|
14
|
+
spec.required_ruby_version = ">= 3.3.0"
|
|
15
|
+
|
|
16
|
+
spec.metadata["allowed_push_host"] = "https://rubygems.org"
|
|
17
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
|
18
|
+
spec.metadata["source_code_uri"] = "#{spec.homepage}/tree/main/gems/snapshot_ui-rails"
|
|
19
|
+
spec.metadata["changelog_uri"] = "#{spec.homepage}/blob/main/gems/snapshot_ui-rails/CHANGELOG.md"
|
|
20
|
+
|
|
21
|
+
gem_root = File.expand_path(__dir__)
|
|
22
|
+
spec.files = Dir.chdir(gem_root) do
|
|
23
|
+
Dir.glob(%w[lib/**/* snapshot_ui-rails.gemspec README.md CHANGELOG.md LICENSE.txt]).select { |path| File.file?(path) }
|
|
24
|
+
end
|
|
25
|
+
spec.require_paths = ["lib"]
|
|
26
|
+
|
|
27
|
+
spec.add_dependency "snapshot_ui", SnapshotUI::VERSION
|
|
28
|
+
spec.add_dependency "railties", ">= 7.1", "< 9"
|
|
29
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: snapshot_ui-rails
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.5.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Tomaz Zlender
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: snapshot_ui
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - '='
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: 0.5.0
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - '='
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: 0.5.0
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: railties
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - ">="
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '7.1'
|
|
33
|
+
- - "<"
|
|
34
|
+
- !ruby/object:Gem::Version
|
|
35
|
+
version: '9'
|
|
36
|
+
type: :runtime
|
|
37
|
+
prerelease: false
|
|
38
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
39
|
+
requirements:
|
|
40
|
+
- - ">="
|
|
41
|
+
- !ruby/object:Gem::Version
|
|
42
|
+
version: '7.1'
|
|
43
|
+
- - "<"
|
|
44
|
+
- !ruby/object:Gem::Version
|
|
45
|
+
version: '9'
|
|
46
|
+
email:
|
|
47
|
+
- tomaz@zlender.se
|
|
48
|
+
executables: []
|
|
49
|
+
extensions: []
|
|
50
|
+
extra_rdoc_files: []
|
|
51
|
+
files:
|
|
52
|
+
- CHANGELOG.md
|
|
53
|
+
- LICENSE.txt
|
|
54
|
+
- README.md
|
|
55
|
+
- lib/snapshot_ui-rails.rb
|
|
56
|
+
- lib/snapshot_ui/rails.rb
|
|
57
|
+
- lib/snapshot_ui/rails/mail.rb
|
|
58
|
+
- lib/snapshot_ui/rails/mail_snapshot.rb
|
|
59
|
+
- lib/snapshot_ui/rails/railtie.rb
|
|
60
|
+
- lib/snapshot_ui/rails/routes.rb
|
|
61
|
+
- lib/snapshot_ui/rails/test/helpers.rb
|
|
62
|
+
- lib/snapshot_ui/rails/version.rb
|
|
63
|
+
- lib/snapshot_ui/rails/web/mail_renderer.rb
|
|
64
|
+
- lib/snapshot_ui/rails/web/views/snapshots/mail/show.html.erb
|
|
65
|
+
- snapshot_ui-rails.gemspec
|
|
66
|
+
homepage: https://github.com/tomazzlender/snapshot_ui
|
|
67
|
+
licenses:
|
|
68
|
+
- MIT
|
|
69
|
+
metadata:
|
|
70
|
+
allowed_push_host: https://rubygems.org
|
|
71
|
+
homepage_uri: https://github.com/tomazzlender/snapshot_ui
|
|
72
|
+
source_code_uri: https://github.com/tomazzlender/snapshot_ui/tree/main/gems/snapshot_ui-rails
|
|
73
|
+
changelog_uri: https://github.com/tomazzlender/snapshot_ui/blob/main/gems/snapshot_ui-rails/CHANGELOG.md
|
|
74
|
+
rdoc_options: []
|
|
75
|
+
require_paths:
|
|
76
|
+
- lib
|
|
77
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - ">="
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: 3.3.0
|
|
82
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
83
|
+
requirements:
|
|
84
|
+
- - ">="
|
|
85
|
+
- !ruby/object:Gem::Version
|
|
86
|
+
version: '0'
|
|
87
|
+
requirements: []
|
|
88
|
+
rubygems_version: 3.6.9
|
|
89
|
+
specification_version: 4
|
|
90
|
+
summary: Rails integration for Snapshot UI — take snapshots of responses in tests
|
|
91
|
+
and view them in a browser.
|
|
92
|
+
test_files: []
|