spec_views 1.1.1 → 3.1.0

Sign up to get free protection for your applications and to get access to all the features.
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 +66 -0
  9. data/app/models/spec_views/capybara_session_extractor.rb +30 -0
  10. data/app/models/spec_views/directory.rb +149 -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 -174
  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,194 +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|\.ico|\.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
124
- .force_encoding("BINARY")
125
- .gsub(/^\/CreationDate \(.*\)$/, '')
126
- .gsub(/^\/ModDate \(.*\)$/, '')
127
- .gsub(/^\/ID \[<.+><.+>\]$/, '')
128
- end
129
-
130
- def put_write_instructions
131
- puts
132
- puts "\e[33mWarning:\e[0m Writing view fixture to #{champion_path.to_s.gsub(Rails.root.to_s, '')}"
133
- puts 'xdg-open "http://localhost:3100/spec_views/"'
134
- end
135
-
136
- def write_to_path(path, content)
137
- File.open(path.to_s, pdf? ? 'wb' : 'w') do |file|
138
- file.write(content)
139
- end
140
- end
141
-
142
- def pdf?
143
- response.content_type == 'application/pdf'
144
- end
145
- end
146
-
147
- def spec_view
148
- SpecView.new(self, @_spec_view_example, response, $_spec_view_time)
149
- end
150
-
151
7
  module SpecViewExample
152
- def render(description = nil, focus: nil, pending: nil, status: :ok, &block)
153
- context do # rubocop:disable RSpec/MissingExampleGroupArgument
154
- 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)
155
11
  options = {}
156
12
  options[:focus] = focus unless focus.nil?
157
13
  options[:pending] = pending unless pending.nil?
14
+ description = "renders #{description}" if description.is_a?(String)
158
15
  it(description, options) do
159
16
  instance_eval(&block)
160
- spec_view.write_last_run
161
- write = status.in?(
162
- [
163
- response.message.parameterize.underscore.to_sym,
164
- response.status,
165
- ],
166
- )
167
- spec_view.write_challenger if write
168
- spec_view.expect_eq
17
+ expect(response).to match_html_fixture.for_status(status)
169
18
  end
170
19
  end
171
20
  end
172
21
  end
22
+ end
23
+ end
173
24
 
174
- RSpec.configure do |c|
175
- c.extend SpecViewExample, type: :controller
176
- c.before(:suite) do |_example|
177
- $_spec_view_time = Time.zone.now
178
- end
179
- c.before(type: :controller) do |example|
180
- @_spec_view_example = example
181
- end
182
- c.before(:each, type: :controller) do
183
- Timecop.freeze(Time.zone.local(2024, 2, 29, 0o0, 17, 42))
184
- end
185
- c.after(:each, type: :controller) do
186
- Timecop.return
187
- 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
188
44
  end
189
45
  end
190
46
  end
191
47
 
192
- RSpec.configure do |rspec|
193
- 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
194
81
  end