spec_views 1.1.0 → 3.0.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.
Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +111 -5
  3. data/Rakefile +3 -1
  4. data/app/assets/config/spec_views_manifest.js +1 -0
  5. data/app/assets/javascripts/spec_views/diff.js +60 -0
  6. data/app/assets/javascripts/spec_views/jsdiff.js +1055 -0
  7. data/app/controllers/spec_views/views_controller.rb +35 -87
  8. data/app/models/spec_views/base_matcher.rb +65 -0
  9. data/app/models/spec_views/capybara_session_extractor.rb +30 -0
  10. data/app/models/spec_views/directory.rb +140 -0
  11. data/app/models/spec_views/html_matcher.rb +41 -0
  12. data/app/models/spec_views/http_response_extractor.rb +30 -0
  13. data/app/models/spec_views/mail_message_extractor.rb +31 -0
  14. data/app/models/spec_views/pdf_matcher.rb +53 -0
  15. data/app/models/spec_views/view_sanitizer.rb +20 -0
  16. data/app/views/layouts/spec_views.html.erb +261 -0
  17. data/app/views/spec_views/views/_actions.html.erb +11 -0
  18. data/app/views/spec_views/views/_directory_footer.html.erb +14 -0
  19. data/app/views/spec_views/views/compare.html.erb +11 -0
  20. data/app/views/spec_views/views/diff.html.erb +16 -0
  21. data/app/views/spec_views/views/index.html.erb +48 -0
  22. data/app/views/spec_views/views/preview.html.erb +32 -0
  23. data/config/routes.rb +3 -0
  24. data/lib/spec_views/configuration.rb +3 -2
  25. data/lib/spec_views/engine.rb +10 -0
  26. data/lib/spec_views/support.rb +61 -170
  27. data/lib/spec_views/version.rb +3 -1
  28. data/lib/spec_views.rb +3 -1
  29. data/lib/tasks/spec_views_tasks.rake +1 -0
  30. metadata +28 -15
  31. data/app/views/layouts/spec_views.html.haml +0 -200
  32. data/app/views/spec_views/views/compare.html.haml +0 -16
  33. data/app/views/spec_views/views/index.html.haml +0 -29
  34. data/app/views/spec_views/views/preview.html.haml +0 -19
@@ -0,0 +1,261 @@
1
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
2
+ <html>
3
+ <head>
4
+ <meta content="text/html; charset=UTF-8" http-equiv="Content-Type"/>
5
+ <title>Spec Views</title>
6
+ <meta charset="utf-8"/>
7
+ <meta content="width=device-width, initial-scale=1, shrink-to-fit=no" name="viewport"/>
8
+ <meta content="no-cache" name="turbolinks-cache-control"/>
9
+ <%= csrf_meta_tags %>
10
+ <%= csp_meta_tag %>
11
+ <style>
12
+ :root {
13
+ --dark-bg-light: #2A2D2E;
14
+ --dark-bg-lighter: #37373D;
15
+ --dark-text: #DADADA;
16
+
17
+ --accept-bg: #5fa285;
18
+ --accept-text: var(--dark-text);
19
+
20
+ --reject-bg: #d27575;
21
+ --reject-text: var(--dark-text);
22
+
23
+ --highlight: #0E639C;
24
+ --challenger: var(--highlight);
25
+ --outdated: var(--reject-bg);
26
+ }
27
+
28
+ * {
29
+ box-sizing: border-box;
30
+ margin: 0;
31
+ padding: 0;
32
+ }
33
+
34
+ body {
35
+ font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";
36
+ background: #252526;
37
+ color: #FFF;
38
+ margin: 0;
39
+ padding: 0;
40
+ }
41
+
42
+ .iframes {
43
+ position: relative;
44
+ z-index: 5;
45
+ display: flex;
46
+ justify-content: space-between;
47
+ align-items: stretch;
48
+ height: calc(100vh - 50px);
49
+ overflow: auto;
50
+ }
51
+
52
+ .iframes > * {
53
+ width: calc(50% - 5px);
54
+ height: 100%;
55
+ display: flex;
56
+ flex-direction: column;
57
+ justify-content: stretch;
58
+ }
59
+
60
+ .w-100 {
61
+ width: 100%;
62
+ }
63
+
64
+ iframe {
65
+ border: 0;
66
+ margin: 0;
67
+ padding: 0;
68
+ width: 100%;
69
+ flex-grow: 1;
70
+ }
71
+
72
+ h2 {
73
+ margin: 0;
74
+ padding: 4px 0;
75
+ text-align: center;
76
+ font-weight: 300;
77
+ text-transform: uppercase;
78
+ font-size: 12px;
79
+ line-height: 1;
80
+ }
81
+
82
+ .footer {
83
+ position: relative;
84
+ z-index: 10;
85
+ display: flex;
86
+ justify-content: space-between;
87
+ height: 50px;
88
+ box-shadow: 0px -1px 4px rgba(0, 0, 0, 0.65);
89
+ }
90
+
91
+ .info {
92
+ padding: 0 0 0 10px;
93
+ font-size: 14px;
94
+ display: flex;
95
+ align-items: center;
96
+ }
97
+ .info > div {
98
+ padding: 0.5rem 0.2rem;
99
+ }
100
+
101
+ .actions {
102
+ display: flex;
103
+ justify-content: flex-end;
104
+ padding: 2px 2rem 0 0;
105
+ }
106
+ .actions > * {
107
+ margin-left: 2rem;
108
+ }
109
+
110
+ form.button_to {
111
+ display: flex;
112
+ }
113
+
114
+ input[type=submit], .btn {
115
+ display: block;
116
+ padding: 0.5rem 0.5rem 0.4rem;
117
+ border: 0 solid var(--dark-bg-lighter);
118
+ border-width: 0 0 8px 0;
119
+ color: var(--dark-text);
120
+ border-radius: 0;
121
+ background: var(--dark-bg-light);
122
+ cursor: pointer;
123
+ font-size: 0.9rem;
124
+ line-height: 1.75;
125
+ min-width: 5rem;
126
+ text-align: center;
127
+ }
128
+ input[type=submit]:hover, .btn:hover {
129
+ background: var(--dark-bg-lighter);
130
+ }
131
+ input[type=submit].accept, .btn.accept {
132
+ border-color: var(--accept-bg);
133
+ color: var(--accept-text);
134
+ }
135
+ input[type=submit].reject, .btn.reject {
136
+ border-color: var(--reject-bg);
137
+ color: var(--reject-text);
138
+ }
139
+ input[type=submit].btn, .btn.btn {
140
+ border-color: var(--highlight);
141
+ }
142
+ input[type=submit].disabled, .btn.disabled {
143
+ opacity: 0.5;
144
+ cursor: default;
145
+ }
146
+ input[type=submit].disabled:hover, .btn.disabled:hover {
147
+ background: var(--dark-bg-light);
148
+ }
149
+
150
+ a {
151
+ color: var(--dark-text);
152
+ text-decoration: none;
153
+ }
154
+
155
+ .directories {
156
+ display: flex;
157
+ flex-direction: column;
158
+ align-items: center;
159
+ margin: 5rem auto 0;
160
+ list-style-type: none;
161
+ padding: 0;
162
+ padding-bottom: 2rem;
163
+ }
164
+ .directories > div {
165
+ margin-top: 1rem;
166
+ width: 58rem;
167
+ }
168
+ .directories > a {
169
+ display: flex;
170
+ flex: 1 0 auto;
171
+ align-items: stretch;
172
+ line-height: 1.5;
173
+ }
174
+ .directories > a:hover, .directories > a:focus {
175
+ background-color: var(--dark-bg-light);
176
+ }
177
+ .directories > a:active {
178
+ background-color: var(--dark-bg-lighter);
179
+ }
180
+ .directories > a.challenger > div:first-child {
181
+ border-color: var(--challenger);
182
+ text-align: center;
183
+ }
184
+ .directories > a.outdated > div:first-child {
185
+ border-color: var(--outdated);
186
+ text-align: center;
187
+ }
188
+ .directories > a > div {
189
+ padding: 0.5rem;
190
+ }
191
+ .directories > a > :first-child {
192
+ width: 8rem;
193
+ border: 0 solid transparent;
194
+ border-left-width: 1rem;
195
+ font-size: 0.75rem;
196
+ display: flex;
197
+ align-items: center;
198
+ }
199
+ .directories > a > :last-child {
200
+ display: flex;
201
+ flex: 1 0 auto;
202
+ width: calc(100vw - 8rem - 1rem);
203
+ align-items: center;
204
+ }
205
+ @media screen and (min-width: 1300px) {
206
+ .directories > a > :last-child {
207
+ width: 75vw;
208
+ }
209
+ }
210
+ @media screen and (min-width: 2500px) {
211
+ .directories > a > :last-child {
212
+ width: 65vw;
213
+ }
214
+ }
215
+ .directories > a > :last-child > :nth-child(1) {
216
+ flex-shrink: 0;
217
+ overflow: hidden;
218
+ text-overflow: ellipsis;
219
+ width: 15rem;
220
+ text-align: right;
221
+ }
222
+ .directories > a > :last-child > :nth-child(2) {
223
+ flex-shrink: 0;
224
+ padding: 0 1rem;
225
+ text-align: center;
226
+ width: 4rem;
227
+ }
228
+
229
+ #diff-settings {
230
+ box-shadow: 0px 1px 4px rgba(0, 0, 0, 0.65);
231
+ margin: 0;
232
+ padding: 1rem;
233
+ }
234
+
235
+ #diff-settings span, #diff-settings label {
236
+ margin-right: 1rem;
237
+ }
238
+ #diff-settings span {
239
+ margin-right: 1rem;
240
+ }
241
+
242
+ #diff-challenger, #diff-champion {
243
+ display: none;
244
+ }
245
+
246
+ #diff-result {
247
+ margin-top: 1rem;
248
+ padding: 0 1rem;
249
+ }
250
+ #diff-result ins {
251
+ color: #9dd795;
252
+ }
253
+ #diff-result del {
254
+ color: #e39ca2;
255
+ }
256
+ </style>
257
+ </head>
258
+ <body>
259
+ <%= yield %>
260
+ </body>
261
+ </html>
@@ -0,0 +1,11 @@
1
+ <div class="actions">
2
+ <%= link_to 'Index', url_for(action: :index), class: 'index btn' %>
3
+ <% unless action_name == 'compare' %>
4
+ <%= link_to 'Compare', url_for(action: :compare), class: 'compare btn' %>
5
+ <% end %>
6
+ <% if action_name != 'diff' && diffable %>
7
+ <%= link_to 'Diff', url_for(action: :diff), class: 'diff btn' %>
8
+ <% end %>
9
+ <%= button_to 'Reject', url_for(action: :reject), class: 'reject' %>
10
+ <%= button_to 'Accept', url_for(action: :accept), class: 'accept' %>
11
+ </div>
@@ -0,0 +1,14 @@
1
+ <div class="footer">
2
+ <div class="info">
3
+ <div>
4
+ <%= directory.controller_name %>
5
+ </div>
6
+ <div>
7
+ <%= directory.method %>
8
+ </div>
9
+ <div>
10
+ <%= directory.description_tail %>
11
+ </div>
12
+ </div>
13
+ <%= render 'actions', diffable: !directory.binary? %>
14
+ </div>
@@ -0,0 +1,11 @@
1
+ <div class="iframes">
2
+ <div>
3
+ <h2>Champion</h2>
4
+ <iframe src="<%= url_for(action: :show) %>"></iframe>
5
+ </div>
6
+ <div>
7
+ <h2>Challenger</h2>
8
+ <iframe src="<%= url_for(action: :show, view: :challenger) %>"></iframe>
9
+ </div>
10
+ </div>
11
+ <%= render 'directory_footer', directory: @directory %>
@@ -0,0 +1,16 @@
1
+ <div class="iframes">
2
+ <div class="w-100">
3
+ <div id="diff-settings">
4
+ <span>Diff</span>
5
+ <label><input type="radio" name="diff_type" value="diffChars" checked> Characters</label>
6
+ <label><input type="radio" name="diff_type" value="diffWords"> Words</label>
7
+ <label><input type="radio" name="diff_type" value="diffLines"> Lines</label>
8
+ </div>
9
+ <div id="diff-champion"><%= @champion %></div>
10
+ <div id="diff-challenger"><%= @challenger %></div>
11
+ <pre id="diff-result"></pre>
12
+ </div>
13
+ </div>
14
+ <%= render 'directory_footer', directory: @directory %>
15
+
16
+ <%= javascript_include_tag 'spec_views/diff' %>
@@ -0,0 +1,48 @@
1
+ <div class="iframes">
2
+ <div class="w-100">
3
+ <div class="directories">
4
+ <% @directories.each do |dir| %>
5
+ <% body = capture do %>
6
+ <div>
7
+ <div>
8
+ <%= dir.controller_name %>
9
+ </div>
10
+ <div>
11
+ <%= dir.method %>
12
+ </div>
13
+ <div>
14
+ <%= dir.description_tail %>
15
+ </div>
16
+ </div>
17
+ <% end %>
18
+ <% if dir.challenger? %>
19
+ <%= link_to({ action: :compare, id: dir }, class: 'challenger') do %>
20
+ <div>CHALLENGER</div>
21
+ <%= body %>
22
+ <% end %>
23
+ <% elsif dir.last_run < @latest_run %>
24
+ <%= link_to({ action: :preview, id: dir }, class: 'outdated') do %>
25
+ <div>OUTDATED</div>
26
+ <%= body %>
27
+ <% end %>
28
+ <% else %>
29
+ <%= link_to(action: :preview, id: dir) do %>
30
+ <div></div>
31
+ <%= body %>
32
+ <% end %>
33
+ <% end %>
34
+ <% end %>
35
+ </div>
36
+ </div>
37
+ </div>
38
+ <div class="footer">
39
+ <div class="info"></div>
40
+ <div class="actions">
41
+ <% if @directories.any?{ |dir| dir.last_run < @latest_run } %>
42
+ <%= button_to 'Remove Outdated', url_for(action: :destroy_outdated), method: :delete, class: 'reject' %>
43
+ <% end %>
44
+ <% if @directories.any?(&:challenger?) %>
45
+ <%= button_to 'Accept All', url_for(action: :accept_all), method: :post, class: 'accept' %>
46
+ <% end %>
47
+ </div>
48
+ </div>
@@ -0,0 +1,32 @@
1
+ <div class="iframes">
2
+ <div class="w-100">
3
+ <h2>Champion</h2>
4
+ <iframe src="<%= url_for(action: :show) %>"></iframe>
5
+ </div>
6
+ </div>
7
+ <div class="footer">
8
+ <div class="info">
9
+ <div>
10
+ <%= @directory.controller_name %>
11
+ </div>
12
+ <div>
13
+ <%= @directory.method %>
14
+ </div>
15
+ <div>
16
+ <%= @directory.description_tail %>
17
+ </div>
18
+ </div>
19
+ <div class="actions">
20
+ <%= link_to 'Index', url_for(action: :index), class: 'index btn' %>
21
+ <% if @previous %>
22
+ <%= link_to 'Previous', url_for(action: :preview, id: @previous), class: 'previous btn', disabled: true %>
23
+ <% else %>
24
+ <span class="previous btn disabled">Previous</span>
25
+ <% end %>
26
+ <% if @next %>
27
+ <%= link_to 'Next', url_for(action: :preview, id: @next), class: 'next btn' %>
28
+ <% else %>
29
+ <span class="previous btn disabled">Next</span>
30
+ <% end %>
31
+ </div>
32
+ </div>
data/config/routes.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  Rails.application.routes.draw do
2
4
  namespace :spec_views do
3
5
  root to: redirect('spec_views/views')
@@ -9,6 +11,7 @@ Rails.application.routes.draw do
9
11
  end
10
12
  member do
11
13
  get :compare
14
+ get :diff
12
15
  get :preview
13
16
  post :accept
14
17
  post :reject
@@ -4,8 +4,9 @@ module SpecViews
4
4
  class Configuration < ActiveSupport::InheritableOptions
5
5
  def self.default
6
6
  new(
7
- directory: 'spec/fixtures/views'
7
+ directory: 'spec/fixtures/views',
8
+ ui_url: 'http://localhost:3000/spec_views'
8
9
  )
9
10
  end
10
11
  end
11
- end
12
+ end
@@ -1,7 +1,17 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_views/configuration'
2
4
 
3
5
  module SpecViews
4
6
  class Engine < ::Rails::Engine
5
7
  config.spec_views = Configuration.default
8
+
9
+ initializer 'spec_views.assets.precompile' do |app|
10
+ app.config.assets.precompile += %w[spec_views/diff.js]
11
+ end
12
+
13
+ config.generators do |g|
14
+ g.test_framework :rspec
15
+ end
6
16
  end
7
17
  end
@@ -1,190 +1,81 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'timecop'
4
+
3
5
  module SpecViews
4
6
  module Support
5
- class SpecView
6
- attr_reader :body, :context, :example, :spec_name, :directory_path, :last_run_path,
7
- :time, :response
8
-
9
- class ReadError < StandardError
10
- def initialize(msg = 'My default message')
11
- super
12
- end
13
- end
14
-
15
- def initialize(context, example, response, time)
16
- @context = context
17
- @response = response
18
- @example = example
19
- @spec_name = example.full_description.strip
20
- .gsub(/[^0-9A-Za-z.\-]/, '_').gsub('__', '_')
21
- @spec_name = "#{@spec_name}__pdf" if pdf?
22
- @directory_path = Rails.root.join(Rails.configuration.spec_views.directory, spec_name)
23
- @last_run_path = directory_path.join('last_run.txt')
24
- @body = response.body
25
- @time = time
26
- end
27
-
28
- def write
29
- FileUtils.mkdir_p(directory_path)
30
- write_to_path(champion_path, sanitized_body)
31
- put_write_instructions
32
- end
33
-
34
- def write_challenger
35
- return unless changed?
36
-
37
- FileUtils.mkdir_p(directory_path)
38
- write_to_path(challenger_path, sanitized_body)
39
- end
40
-
41
- def delete_challenger
42
- FileUtils.rm_f(challenger_path) unless changed?
43
- end
44
-
45
- def write_last_run
46
- FileUtils.mkdir_p(directory_path)
47
- write_to_path(last_run_path, time)
48
- end
49
-
50
- def expect_eq
51
- pdf? ? expect_eq_pdf : expect_eq_text
52
- delete_challenger
53
- end
54
-
55
- def expect_eq_text
56
- context.expect(sanitized_body).to context.eq(read)
57
- end
58
-
59
- def expect_eq_pdf
60
- champion_md5 = nil
61
- begin
62
- champion_md5 = Digest::MD5.file(champion_path).hexdigest
63
- rescue Errno::ENOENT # rubocop:disable Lint/SuppressedException
64
- end
65
- current_md5 = Digest::MD5.hexdigest(sanitized_body)
66
- context.expect(current_md5).to context.eq(champion_md5), 'MD5s of PDF do not match'
67
- end
68
-
69
- def read
70
- File.read(champion_path)
71
- rescue Errno::ENOENT
72
- raise ReadError, "Cannot find view fixture #{champion_path.to_s.gsub(Rails.root.to_s, '')}\n" \
73
- "Create the file by adding the follwing to your spec:\n" \
74
- 'spec_view.write'
75
- end
76
-
77
- def changed?
78
- return pdf_changed? if pdf?
79
-
80
- begin
81
- champion = read
82
- rescue ReadError
83
- champion = nil
84
- end
85
- sanitized_body != champion
86
- end
87
-
88
- def pdf_changed?
89
- champion_md5 = Digest::MD5.file(champion_path).hexdigest
90
- current_md5 = Digest::MD5.hexdigest(sanitized_body)
91
- champion_md5 != current_md5
92
- rescue Errno::ENOENT
93
- true
94
- end
95
-
96
- private
97
-
98
- def champion_path
99
- pdf? ? directory_path.join('view.pdf') : directory_path.join('view.html')
100
- end
101
-
102
- def challenger_path
103
- pdf? ? directory_path.join('challenger.pdf') : directory_path.join('challenger.html')
104
- end
105
-
106
- def sanitized_body
107
- return remove_headers_from_pdf(body) if pdf?
108
-
109
- remove_pack_digests_from_body(
110
- remove_digests_from_body(body),
111
- )
112
- end
113
-
114
- def remove_digests_from_body(body)
115
- body.gsub(/(-[a-z0-9]{64})(\.css|\.js|\.png|\.jpg|\.jpeg|\.svg|\.gif)/, '\2')
116
- end
117
-
118
- def remove_pack_digests_from_body(body)
119
- body.gsub(%r{(packs.*/js/[a-z0-9_]+)(-[a-z0-9]{20})(\.js)}, '\1\3')
120
- end
121
-
122
- def remove_headers_from_pdf(pdf)
123
- pdf.gsub(/^\/CreationDate.*$/, '')
124
- end
125
-
126
- def put_write_instructions
127
- puts
128
- puts "\e[33mWarning:\e[0m Writing view fixture to #{champion_path.to_s.gsub(Rails.root.to_s, '')}"
129
- puts 'xdg-open "http://localhost:3100/spec_views/"'
130
- end
131
-
132
- def write_to_path(path, content)
133
- File.open(path.to_s, pdf? ? 'wb' : 'w') do |file|
134
- file.write(content)
135
- end
136
- end
137
-
138
- def pdf?
139
- response.content_type == 'application/pdf'
140
- end
141
- end
142
-
143
- def spec_view
144
- SpecView.new(self, @_spec_view_example, response, $_spec_view_time)
145
- end
146
-
147
7
  module SpecViewExample
148
- def render(description = nil, focus: nil, pending: nil, status: :ok, &block)
149
- context do # rubocop:disable RSpec/MissingExampleGroupArgument
150
- render_views
8
+ def it_renders(description = nil, focus: nil, pending: nil, status: :ok, &block)
9
+ context do
10
+ render_views if respond_to?(:render_views)
151
11
  options = {}
152
12
  options[:focus] = focus unless focus.nil?
153
13
  options[:pending] = pending unless pending.nil?
14
+ description = "renders #{description}" if description.is_a?(String)
154
15
  it(description, options) do
155
16
  instance_eval(&block)
156
- spec_view.write_last_run
157
- write = status.in?(
158
- [
159
- response.message.parameterize.underscore.to_sym,
160
- response.status,
161
- ],
162
- )
163
- spec_view.write_challenger if write
164
- spec_view.expect_eq
17
+ expect(response).to match_html_fixture.for_status(status)
165
18
  end
166
19
  end
167
20
  end
168
21
  end
22
+ end
23
+ end
169
24
 
170
- RSpec.configure do |c|
171
- c.extend SpecViewExample, type: :controller
172
- c.before(:suite) do |_example|
173
- $_spec_view_time = Time.zone.now
174
- end
175
- c.before(type: :controller) do |example|
176
- @_spec_view_example = example
177
- end
178
- c.before(:each, type: :controller) do
179
- Timecop.freeze(Time.zone.local(2024, 2, 29, 0o0, 17, 42))
180
- end
181
- c.after(:each, type: :controller) do
182
- Timecop.return
183
- end
25
+ RSpec.shared_context 'SpecViews ViewSanitizer' do
26
+ let(:view_sanitizer) { SpecViews::ViewSanitizer.new }
27
+ end
28
+
29
+ RSpec.configure do |c|
30
+ c.before(:suite) do |_example|
31
+ $_spec_view_time = Time.zone.now # rubocop:disable Style/GlobalVars
32
+ end
33
+ c.include_context 'SpecViews ViewSanitizer'
34
+ %i[controller feature mailer request].each do |type|
35
+ c.extend SpecViews::Support::SpecViewExample, type: type
36
+ c.before(type: type) do |example|
37
+ @_spec_view_example = example
38
+ end
39
+ c.before(:each, type: type) do
40
+ Timecop.freeze(Time.zone.local(2024, 2, 29, 0o0, 17, 42))
41
+ end
42
+ c.after(:each, type: type) do
43
+ Timecop.return
184
44
  end
185
45
  end
186
46
  end
187
47
 
188
- RSpec.configure do |rspec|
189
- rspec.include SpecViews::Support, type: :controller
48
+ matchers = [
49
+ [:match_html_fixture, SpecViews::HtmlMatcher],
50
+ [:match_pdf_fixture, SpecViews::PdfMatcher]
51
+ ]
52
+
53
+ matchers.each do |matcher|
54
+ RSpec::Matchers.define matcher.first do |_expected|
55
+ chain :for_status do |status|
56
+ @status = status
57
+ end
58
+
59
+ match do |actual|
60
+ example = @matcher_execution_context.instance_variable_get('@_spec_view_example')
61
+ description = example.full_description
62
+ type = example.metadata[:type]
63
+ run_time = $_spec_view_time # rubocop:disable Style/GlobalVars
64
+ @status ||= :ok
65
+ @matcher = matcher.second.new(
66
+ actual,
67
+ description,
68
+ expected_status: @status,
69
+ run_time: run_time,
70
+ sanitizer: view_sanitizer,
71
+ type: type
72
+ )
73
+ return @matcher.match?
74
+ end
75
+
76
+ failure_message do |_actual|
77
+ "#{@matcher.failure_message} " \
78
+ "Review the challenger: #{Rails.configuration.spec_views.ui_url}"
79
+ end
80
+ end
190
81
  end