spec_views 1.0.1 → 2.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.
- checksums.yaml +4 -4
- data/README.md +101 -16
- data/Rakefile +3 -1
- data/app/assets/config/spec_views_manifest.js +1 -0
- data/app/assets/javascripts/spec_views/diff.js +60 -0
- data/app/assets/javascripts/spec_views/jsdiff.js +1055 -0
- data/app/controllers/spec_views/views_controller.rb +18 -60
- data/app/models/spec_views/base_matcher.rb +64 -0
- data/app/models/spec_views/directory.rb +133 -0
- data/app/models/spec_views/html_matcher.rb +39 -0
- data/app/models/spec_views/http_response_extractor.rb +31 -0
- data/app/models/spec_views/mail_message_extractor.rb +30 -0
- data/app/models/spec_views/pdf_matcher.rb +49 -0
- data/app/views/layouts/spec_views.html.erb +261 -0
- data/app/views/spec_views/views/_actions.html.erb +11 -0
- data/app/views/spec_views/views/_directory_footer.html.erb +14 -0
- data/app/views/spec_views/views/compare.html.erb +11 -0
- data/app/views/spec_views/views/diff.html.erb +16 -0
- data/app/views/spec_views/views/index.html.erb +48 -0
- data/app/views/spec_views/views/preview.html.erb +32 -0
- data/config/routes.rb +3 -0
- data/lib/spec_views/configuration.rb +3 -2
- data/lib/spec_views/engine.rb +10 -0
- data/lib/spec_views/support.rb +56 -166
- data/lib/spec_views/version.rb +3 -1
- data/lib/spec_views.rb +3 -1
- data/lib/tasks/spec_views_tasks.rake +1 -0
- metadata +26 -15
- data/app/views/layouts/spec_views.html.haml +0 -200
- data/app/views/spec_views/views/compare.html.haml +0 -16
- data/app/views/spec_views/views/index.html.haml +0 -29
- 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
|
data/lib/spec_views/engine.rb
CHANGED
@@ -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
|
data/lib/spec_views/support.rb
CHANGED
@@ -1,186 +1,76 @@
|
|
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 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 put_write_instructions
|
123
|
-
puts
|
124
|
-
puts "\e[33mWarning:\e[0m Writing view fixture to #{champion_path.to_s.gsub(Rails.root.to_s, '')}"
|
125
|
-
puts 'xdg-open "http://localhost:3100/spec_views/"'
|
126
|
-
end
|
127
|
-
|
128
|
-
def write_to_path(path, content)
|
129
|
-
File.open(path.to_s, pdf? ? 'wb' : 'w') do |file|
|
130
|
-
file.write(content)
|
131
|
-
end
|
132
|
-
end
|
133
|
-
|
134
|
-
def pdf?
|
135
|
-
response.content_type == 'application/pdf'
|
136
|
-
end
|
137
|
-
end
|
138
|
-
|
139
|
-
def spec_view
|
140
|
-
SpecView.new(self, @_spec_view_example, response, $_spec_view_time)
|
141
|
-
end
|
142
|
-
|
143
7
|
module SpecViewExample
|
144
|
-
def
|
145
|
-
context do
|
146
|
-
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)
|
147
11
|
options = {}
|
148
12
|
options[:focus] = focus unless focus.nil?
|
149
13
|
options[:pending] = pending unless pending.nil?
|
14
|
+
description = "renders #{description}" if description.is_a?(String)
|
150
15
|
it(description, options) do
|
151
16
|
instance_eval(&block)
|
152
|
-
|
153
|
-
write = status.in?(
|
154
|
-
[
|
155
|
-
response.message.parameterize.underscore.to_sym,
|
156
|
-
response.status,
|
157
|
-
],
|
158
|
-
)
|
159
|
-
spec_view.write_challenger if write
|
160
|
-
spec_view.expect_eq
|
17
|
+
expect(response).to match_html_fixture.for_status(status)
|
161
18
|
end
|
162
19
|
end
|
163
20
|
end
|
164
21
|
end
|
22
|
+
end
|
23
|
+
end
|
165
24
|
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
25
|
+
RSpec.configure do |c|
|
26
|
+
c.before(:suite) do |_example|
|
27
|
+
$_spec_view_time = Time.zone.now # rubocop:disable Style/GlobalVars
|
28
|
+
end
|
29
|
+
%i[controller mailer request].each do |type|
|
30
|
+
c.extend SpecViews::Support::SpecViewExample, type: type
|
31
|
+
c.before(type: type) do |example|
|
32
|
+
@_spec_view_example = example
|
33
|
+
end
|
34
|
+
c.before(:each, type: type) do
|
35
|
+
Timecop.freeze(Time.zone.local(2024, 2, 29, 0o0, 17, 42))
|
36
|
+
end
|
37
|
+
c.after(:each, type: type) do
|
38
|
+
Timecop.return
|
180
39
|
end
|
181
40
|
end
|
182
41
|
end
|
183
42
|
|
184
|
-
|
185
|
-
|
43
|
+
|
44
|
+
matchers = [
|
45
|
+
[:match_html_fixture, SpecViews::HtmlMatcher],
|
46
|
+
[:match_pdf_fixture, SpecViews::PdfMatcher]
|
47
|
+
]
|
48
|
+
|
49
|
+
matchers.each do |matcher|
|
50
|
+
RSpec::Matchers.define matcher.first do |_expected|
|
51
|
+
chain :for_status do |status|
|
52
|
+
@status = status
|
53
|
+
end
|
54
|
+
|
55
|
+
match do |actual|
|
56
|
+
example = @matcher_execution_context.instance_variable_get('@_spec_view_example')
|
57
|
+
description = example.full_description
|
58
|
+
type = example.metadata[:type]
|
59
|
+
run_time = $_spec_view_time # rubocop:disable Style/GlobalVars
|
60
|
+
@status ||= :ok
|
61
|
+
@matcher = matcher.second.new(
|
62
|
+
actual,
|
63
|
+
description,
|
64
|
+
expected_status: @status,
|
65
|
+
run_time: run_time,
|
66
|
+
type: type
|
67
|
+
)
|
68
|
+
return @matcher.match?
|
69
|
+
end
|
70
|
+
|
71
|
+
failure_message do |_actual|
|
72
|
+
"#{@matcher.failure_message} " \
|
73
|
+
"Review the challenger: #{Rails.configuration.spec_views.ui_url}"
|
74
|
+
end
|
75
|
+
end
|
186
76
|
end
|
data/lib/spec_views/version.rb
CHANGED