spec_views 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +35 -0
- data/Rakefile +32 -0
- data/app/assets/config/spec_views_manifest.js +0 -0
- data/app/controllers/spec_views/views_controller.rb +171 -0
- data/app/views/layouts/spec_views.html.haml +198 -0
- data/app/views/spec_views/views/compare.html.haml +16 -0
- data/app/views/spec_views/views/index.html.haml +28 -0
- data/app/views/spec_views/views/preview.html.haml +19 -0
- data/config/routes.rb +18 -0
- data/lib/spec_views/configuration.rb +11 -0
- data/lib/spec_views/engine.rb +17 -0
- data/lib/spec_views/support.rb +182 -0
- data/lib/spec_views/version.rb +3 -0
- data/lib/spec_views.rb +5 -0
- data/lib/tasks/spec_views_tasks.rake +4 -0
- metadata +101 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 82f7ca371b973c3523a11dd3cacb3b76001d3fe2f2d8b78b93992bdabdafafea
|
4
|
+
data.tar.gz: 5285d6511b852478642cb6402f6b58a62271642822a18acd25113cd2b39bc068
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 3cc275120f3d2f06ca86158ac2a950edacce9a829dbacd7205edd556b4f52aaaa4b9b4ed24366918449fb1a239af7da86786ed43c2cb1b27c3371066ce23f40b
|
7
|
+
data.tar.gz: 1513f33981acb6b65b397c3faa2760eba807a5fd4f0a7a033599684471fd5e17a73c927d97361072fb548f142cdd31605b4ef4fb4d789428a20bbc9b541b8d02
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2022 Sebastian Gaul
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
# SpecViews
|
2
|
+
Render views from controller specs for comparision
|
3
|
+
|
4
|
+
## Usage
|
5
|
+
Replace selected RSpec `it` methods with `render`:
|
6
|
+
|
7
|
+
```ruby
|
8
|
+
RSpec.describe HomeController, type: :controller do
|
9
|
+
describe 'GET #show' do
|
10
|
+
render 'homepage' do
|
11
|
+
get :show
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
```
|
16
|
+
|
17
|
+
## Installation
|
18
|
+
Add this line to your application's Gemfile:
|
19
|
+
|
20
|
+
```ruby
|
21
|
+
gem 'spec_views'
|
22
|
+
```
|
23
|
+
|
24
|
+
And then execute:
|
25
|
+
```bash
|
26
|
+
$ bundle
|
27
|
+
```
|
28
|
+
|
29
|
+
Or install it yourself as:
|
30
|
+
```bash
|
31
|
+
$ gem install spec_views
|
32
|
+
```
|
33
|
+
|
34
|
+
## License
|
35
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
begin
|
2
|
+
require 'bundler/setup'
|
3
|
+
rescue LoadError
|
4
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
5
|
+
end
|
6
|
+
|
7
|
+
require 'rdoc/task'
|
8
|
+
|
9
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
10
|
+
rdoc.rdoc_dir = 'rdoc'
|
11
|
+
rdoc.title = 'SpecViews'
|
12
|
+
rdoc.options << '--line-numbers'
|
13
|
+
rdoc.rdoc_files.include('README.md')
|
14
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
15
|
+
end
|
16
|
+
|
17
|
+
APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__)
|
18
|
+
load 'rails/tasks/engine.rake'
|
19
|
+
|
20
|
+
load 'rails/tasks/statistics.rake'
|
21
|
+
|
22
|
+
require 'bundler/gem_tasks'
|
23
|
+
|
24
|
+
require 'rake/testtask'
|
25
|
+
|
26
|
+
Rake::TestTask.new(:test) do |t|
|
27
|
+
t.libs << 'test'
|
28
|
+
t.pattern = 'test/**/*_test.rb'
|
29
|
+
t.verbose = false
|
30
|
+
end
|
31
|
+
|
32
|
+
task default: :test
|
File without changes
|
@@ -0,0 +1,171 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SpecViews
|
4
|
+
class ViewsController < ApplicationController
|
5
|
+
skip_authorization_check
|
6
|
+
layout 'spec_views'
|
7
|
+
|
8
|
+
def index
|
9
|
+
@directories = directories
|
10
|
+
@latest_run = directories.map(&:last_run).max
|
11
|
+
return unless params[:challenger] == 'next'
|
12
|
+
|
13
|
+
first = @directories.detect(&:challenger?)
|
14
|
+
if first
|
15
|
+
redirect_to(action: :compare, id: first)
|
16
|
+
else
|
17
|
+
redirect_to(action: :index, challenger: nil)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def show
|
22
|
+
view = filename('view')
|
23
|
+
view = filename('challenger') if params[:view] == 'challenger'
|
24
|
+
if pdf?
|
25
|
+
send_data(
|
26
|
+
get_view(view),
|
27
|
+
filename: 'a.pdf',
|
28
|
+
type: 'application/pdf', disposition: 'inline'
|
29
|
+
)
|
30
|
+
else
|
31
|
+
render html: get_view(view)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def compare
|
36
|
+
@directory = directory
|
37
|
+
end
|
38
|
+
|
39
|
+
def preview
|
40
|
+
@directory = directory
|
41
|
+
@next = directories[directories.index(@directory) + 1]
|
42
|
+
index = directories.index(@directory)
|
43
|
+
@previous = directories[index - 1] if index > 0
|
44
|
+
end
|
45
|
+
|
46
|
+
def accept
|
47
|
+
accept_directory(params[:id])
|
48
|
+
redirect_to action: :index, challenger: :next
|
49
|
+
end
|
50
|
+
|
51
|
+
def accept_all
|
52
|
+
directories.each do |dir|
|
53
|
+
accept_directory(dir) if dir.challenger?
|
54
|
+
end
|
55
|
+
redirect_to action: :index
|
56
|
+
end
|
57
|
+
|
58
|
+
def reject
|
59
|
+
challenger = file_path(filename('challenger'))
|
60
|
+
FileUtils.remove_file(challenger)
|
61
|
+
redirect_to action: :index, challenger: :next
|
62
|
+
end
|
63
|
+
|
64
|
+
def destroy_outdated
|
65
|
+
@directories = directories
|
66
|
+
@latest_run = directories.map(&:last_run).max
|
67
|
+
@directories.each do |dir|
|
68
|
+
dir.delete! if dir.last_run < @latest_run
|
69
|
+
end
|
70
|
+
redirect_to action: :index
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
class Directory
|
76
|
+
attr_reader :path
|
77
|
+
|
78
|
+
def initialize(path)
|
79
|
+
@path = path
|
80
|
+
end
|
81
|
+
|
82
|
+
def basename
|
83
|
+
path.basename
|
84
|
+
end
|
85
|
+
|
86
|
+
def to_param
|
87
|
+
basename.to_s
|
88
|
+
end
|
89
|
+
|
90
|
+
def name
|
91
|
+
basename
|
92
|
+
end
|
93
|
+
|
94
|
+
def challenger?
|
95
|
+
File.file?(path.join('challenger.html')) || File.file?(path.join('challenger.pdf'))
|
96
|
+
end
|
97
|
+
|
98
|
+
def controller_name
|
99
|
+
splitted_name.first.gsub(/Controller(_.*)$/, 'Controller').gsub('_', '::')
|
100
|
+
end
|
101
|
+
|
102
|
+
def method
|
103
|
+
splitted_name.second
|
104
|
+
end
|
105
|
+
|
106
|
+
def description
|
107
|
+
splitted_name.third.humanize
|
108
|
+
end
|
109
|
+
|
110
|
+
def last_run
|
111
|
+
@last_run ||= begin
|
112
|
+
Time.zone.parse(File.read(path.join('last_run.txt')))
|
113
|
+
rescue Errno::ENOENT
|
114
|
+
Time.zone.at(0)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def delete!
|
119
|
+
FileUtils.remove_dir(path)
|
120
|
+
end
|
121
|
+
|
122
|
+
private
|
123
|
+
|
124
|
+
def splitted_name
|
125
|
+
@splitted_name ||= name.to_s.split(/_(DELETE|GET|PATCH|POST|PUT)_/)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def directories
|
130
|
+
@directories ||= Pathname.new(directory_path).children.select(&:directory?).map do |path|
|
131
|
+
Directory.new(path)
|
132
|
+
end.sort_by(&:basename)
|
133
|
+
end
|
134
|
+
|
135
|
+
def directory_path
|
136
|
+
Rails.root.join(Rails.configuration.spec_views.directory)
|
137
|
+
end
|
138
|
+
|
139
|
+
def directory
|
140
|
+
directories.detect { |dir| dir.to_param == params[:id] }
|
141
|
+
end
|
142
|
+
|
143
|
+
def get_view(filename = nil)
|
144
|
+
filename ||= pdf? ? 'view.pdf' : 'view.html'
|
145
|
+
File.read(file_path(filename)).html_safe # rubocop:disable Rails/OutputSafety
|
146
|
+
rescue Errno::ENOENT
|
147
|
+
''
|
148
|
+
end
|
149
|
+
|
150
|
+
def file_path(filename, id: nil)
|
151
|
+
id ||= params[:id]
|
152
|
+
id = id.to_param if id.respond_to?(:to_param)
|
153
|
+
directory_path.join(id, filename)
|
154
|
+
end
|
155
|
+
|
156
|
+
def filename(base = 'view')
|
157
|
+
pdf? ? "#{base}.pdf" : "#{base}.html"
|
158
|
+
end
|
159
|
+
|
160
|
+
def pdf?
|
161
|
+
params[:id] && params[:id].end_with?('__pdf')
|
162
|
+
end
|
163
|
+
|
164
|
+
def accept_directory(id)
|
165
|
+
champion = file_path(filename('view'), id: id)
|
166
|
+
challenger = file_path(filename('challenger'), id: id)
|
167
|
+
FileUtils.copy_file(challenger, champion)
|
168
|
+
FileUtils.remove_file(challenger)
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
@@ -0,0 +1,198 @@
|
|
1
|
+
!!!
|
2
|
+
%html
|
3
|
+
%head
|
4
|
+
%meta{content: "text/html; charset=UTF-8", "http-equiv" => "Content-Type"}/
|
5
|
+
%title Spec Views
|
6
|
+
%meta{charset: "utf-8"}/
|
7
|
+
%meta{content: "width=device-width, initial-scale=1, shrink-to-fit=no", name: "viewport"}/
|
8
|
+
%meta{name: "turbolinks-cache-control", content: "no-cache"}/
|
9
|
+
= csrf_meta_tags
|
10
|
+
= csp_meta_tag
|
11
|
+
:sass
|
12
|
+
$dark-bg: #252526
|
13
|
+
$dark-bg-light: #2A2D2E
|
14
|
+
$dark-bg-lighter: #37373D
|
15
|
+
$dark-text: #DADADA
|
16
|
+
|
17
|
+
$accept-bg: #5fa285
|
18
|
+
$accept-text: $dark-text
|
19
|
+
|
20
|
+
$reject-bg: #d27575
|
21
|
+
$reject-text: $dark-text
|
22
|
+
|
23
|
+
$highlight: #0E639C
|
24
|
+
$challenger: $highlight
|
25
|
+
$outdated: $reject-bg
|
26
|
+
|
27
|
+
*
|
28
|
+
box-sizing: border-box
|
29
|
+
margin: 0
|
30
|
+
padding: 0
|
31
|
+
|
32
|
+
body
|
33
|
+
font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol"
|
34
|
+
background: #252526
|
35
|
+
color: #FFF
|
36
|
+
margin: 0
|
37
|
+
padding: 0
|
38
|
+
|
39
|
+
.iframes
|
40
|
+
position: relative
|
41
|
+
z-index: 5
|
42
|
+
display: flex
|
43
|
+
justify-content: space-between
|
44
|
+
align-items: stretch
|
45
|
+
height: calc(100vh - 50px)
|
46
|
+
overflow: auto
|
47
|
+
|
48
|
+
> *
|
49
|
+
width: calc(50% - 5px)
|
50
|
+
height: 100%
|
51
|
+
display: flex
|
52
|
+
flex-direction: column
|
53
|
+
justify-content: stretch
|
54
|
+
|
55
|
+
&.w-100
|
56
|
+
width: calc(100% - 5px)
|
57
|
+
|
58
|
+
iframe
|
59
|
+
border: 0
|
60
|
+
margin: 0
|
61
|
+
padding: 0
|
62
|
+
width: 100%
|
63
|
+
flex-grow: 1
|
64
|
+
|
65
|
+
h2
|
66
|
+
margin: 0
|
67
|
+
padding: 4px 0
|
68
|
+
text-align: center
|
69
|
+
font-weight: 300
|
70
|
+
text-transform: uppercase
|
71
|
+
font-size: 12px
|
72
|
+
line-height: 1
|
73
|
+
|
74
|
+
.footer
|
75
|
+
position: relative
|
76
|
+
z-index: 10
|
77
|
+
display: flex
|
78
|
+
justify-content: space-between
|
79
|
+
height: 50px
|
80
|
+
box-shadow: 0px -1px 4px rgba(0, 0, 0, 0.65)
|
81
|
+
|
82
|
+
.info
|
83
|
+
padding: 0 0 0 10px
|
84
|
+
font-size: 14px
|
85
|
+
display: flex
|
86
|
+
align-items: center
|
87
|
+
|
88
|
+
> div
|
89
|
+
padding: .5rem .2rem
|
90
|
+
|
91
|
+
.actions
|
92
|
+
display: flex
|
93
|
+
justify-content: flex-end
|
94
|
+
padding: 2px 2rem 0 0
|
95
|
+
|
96
|
+
> *
|
97
|
+
margin-left: 2rem
|
98
|
+
|
99
|
+
form.button_to
|
100
|
+
display: flex
|
101
|
+
|
102
|
+
input[type=submit], .btn
|
103
|
+
display: block
|
104
|
+
padding: .5rem .5rem .4rem
|
105
|
+
border: 0 solid $dark-bg-lighter
|
106
|
+
border-width: 0 0 8px 0
|
107
|
+
color: $dark-text
|
108
|
+
border-radius: 0
|
109
|
+
background: $dark-bg-light
|
110
|
+
cursor: pointer
|
111
|
+
font-size: .9rem
|
112
|
+
line-height: 1.75
|
113
|
+
min-width: 5rem
|
114
|
+
text-align: center
|
115
|
+
|
116
|
+
&:hover
|
117
|
+
background: $dark-bg-lighter
|
118
|
+
|
119
|
+
&.accept
|
120
|
+
border-color: $accept-bg
|
121
|
+
color: $accept-text
|
122
|
+
&.reject
|
123
|
+
border-color: $reject-bg
|
124
|
+
color: $reject-text
|
125
|
+
&.btn
|
126
|
+
border-color: $highlight
|
127
|
+
&.disabled
|
128
|
+
opacity: 0.5
|
129
|
+
cursor: default
|
130
|
+
&:hover
|
131
|
+
background: $dark-bg-light
|
132
|
+
|
133
|
+
a
|
134
|
+
color: $dark-text
|
135
|
+
text-decoration: none
|
136
|
+
|
137
|
+
.directories
|
138
|
+
display: flex
|
139
|
+
flex-direction: column
|
140
|
+
align-items: center
|
141
|
+
margin: 5rem auto 0
|
142
|
+
list-style-type: none
|
143
|
+
padding: 0
|
144
|
+
|
145
|
+
> div
|
146
|
+
margin-top: 1rem
|
147
|
+
width: 58rem
|
148
|
+
> a
|
149
|
+
display: flex
|
150
|
+
flex: 1 0 auto
|
151
|
+
align-items: stretch
|
152
|
+
|
153
|
+
&:hover, &:focus
|
154
|
+
background-color: $dark-bg-light
|
155
|
+
&:active
|
156
|
+
background-color: $dark-bg-lighter
|
157
|
+
|
158
|
+
&.challenger
|
159
|
+
> div:first-child
|
160
|
+
border-color: $challenger
|
161
|
+
text-align: center
|
162
|
+
&.outdated
|
163
|
+
> div:first-child
|
164
|
+
border-color: $outdated
|
165
|
+
text-align: center
|
166
|
+
|
167
|
+
> div
|
168
|
+
padding: .5rem
|
169
|
+
|
170
|
+
> :first-child
|
171
|
+
width: 8rem
|
172
|
+
border: 0 solid transparent
|
173
|
+
border-left-width: 1rem
|
174
|
+
font-size: 0.75rem
|
175
|
+
display: flex
|
176
|
+
align-items: center
|
177
|
+
|
178
|
+
> :last-child
|
179
|
+
display: flex
|
180
|
+
flex: 1 0 auto
|
181
|
+
width: 50rem
|
182
|
+
align-items: center
|
183
|
+
|
184
|
+
> :nth-child(1)
|
185
|
+
flex-shrink: 0
|
186
|
+
overflow: hidden
|
187
|
+
text-overflow: ellipsis
|
188
|
+
width: 15rem
|
189
|
+
text-align: right
|
190
|
+
> :nth-child(2)
|
191
|
+
flex-shrink: 0
|
192
|
+
padding: 0 1rem
|
193
|
+
text-align: center
|
194
|
+
width: 4rem
|
195
|
+
> :nth-child(3)
|
196
|
+
|
197
|
+
%body
|
198
|
+
= yield
|
@@ -0,0 +1,16 @@
|
|
1
|
+
.iframes
|
2
|
+
%div
|
3
|
+
%h2 Champion
|
4
|
+
%iframe{ src: url_for(action: :show) }
|
5
|
+
%div
|
6
|
+
%h2 Challenger
|
7
|
+
%iframe{ src: url_for(action: :show, view: :challenger) }
|
8
|
+
.footer
|
9
|
+
.info
|
10
|
+
%div= @directory.controller_name
|
11
|
+
%div= @directory.method
|
12
|
+
%div= @directory.description
|
13
|
+
.actions
|
14
|
+
= link_to 'Index', url_for(action: :index), class: 'index btn'
|
15
|
+
= button_to 'Reject', url_for(action: :reject), class: 'reject'
|
16
|
+
= button_to 'Accept', url_for(action: :accept), class: 'accept'
|
@@ -0,0 +1,28 @@
|
|
1
|
+
.iframes
|
2
|
+
.directories.w-100
|
3
|
+
- @directories.each do |dir|
|
4
|
+
- body = capture do
|
5
|
+
%div
|
6
|
+
%div= dir.controller_name
|
7
|
+
%div= dir.method
|
8
|
+
%div= dir.description
|
9
|
+
- if dir.challenger?
|
10
|
+
= link_to({ action: :compare, id: dir }, class: 'challenger') do
|
11
|
+
%div CHALLENGER
|
12
|
+
= body
|
13
|
+
- elsif dir.last_run < @latest_run
|
14
|
+
= link_to({ action: :preview, id: dir }, class: 'outdated') do
|
15
|
+
%div OUTDATED
|
16
|
+
= body
|
17
|
+
- else
|
18
|
+
= link_to(action: :preview, id: dir) do
|
19
|
+
%div
|
20
|
+
= body
|
21
|
+
|
22
|
+
.footer
|
23
|
+
.info
|
24
|
+
.actions
|
25
|
+
- if @directories.any?{ |dir| dir.last_run < @latest_run }
|
26
|
+
= button_to 'Remove Outdated', url_for(action: :destroy_outdated), method: :delete, class: 'reject'
|
27
|
+
- if @directories.any?(&:challenger?)
|
28
|
+
= button_to 'Accept All', url_for(action: :accept_all), method: :post, class: 'accept'
|
@@ -0,0 +1,19 @@
|
|
1
|
+
.iframes
|
2
|
+
%div.w-100
|
3
|
+
%h2 Champion
|
4
|
+
%iframe{ src: url_for(action: :show) }
|
5
|
+
.footer
|
6
|
+
.info
|
7
|
+
%div= @directory.controller_name
|
8
|
+
%div= @directory.method
|
9
|
+
%div= @directory.description
|
10
|
+
.actions
|
11
|
+
= link_to 'Index', url_for(action: :index), class: 'index btn'
|
12
|
+
- if @previous
|
13
|
+
= link_to 'Previous', url_for(action: :preview, id: @previous), class: 'previous btn', disabled: true
|
14
|
+
- else
|
15
|
+
%span.previous.btn.disabled Previous
|
16
|
+
- if @next
|
17
|
+
= link_to 'Next', url_for(action: :preview, id: @next), class: 'next btn'
|
18
|
+
- else
|
19
|
+
%span.previous.btn.disabled Next
|
data/config/routes.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
Rails.application.routes.draw do
|
2
|
+
namespace :spec_views do
|
3
|
+
root to: redirect('spec_views/views')
|
4
|
+
|
5
|
+
resources :views, only: %i[index show] do
|
6
|
+
collection do
|
7
|
+
delete :destroy_outdated
|
8
|
+
post :accept_all
|
9
|
+
end
|
10
|
+
member do
|
11
|
+
get :compare
|
12
|
+
get :preview
|
13
|
+
post :accept
|
14
|
+
post :reject
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'spec_views/configuration'
|
2
|
+
|
3
|
+
module SpecViews
|
4
|
+
class Engine < ::Rails::Engine
|
5
|
+
config.spec_views = Configuration.default
|
6
|
+
|
7
|
+
initializer 'configure.rspec' do |app|
|
8
|
+
if Rails.env.test? && Object.const_defined?('RSpec')
|
9
|
+
require 'spec_views/support'
|
10
|
+
|
11
|
+
RSpec.configure do |config|
|
12
|
+
config.include Support
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,182 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SpecViews
|
4
|
+
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
|
+
module SpecViewExample
|
144
|
+
def render(description = nil, focus: nil, pending: nil, status: :ok, &block)
|
145
|
+
context do # rubocop:disable RSpec/MissingExampleGroupArgument
|
146
|
+
render_views
|
147
|
+
options = {}
|
148
|
+
options[:focus] = focus unless focus.nil?
|
149
|
+
options[:pending] = pending unless pending.nil?
|
150
|
+
it(description, options) do
|
151
|
+
instance_eval(&block)
|
152
|
+
spec_view.write_last_run
|
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
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
RSpec.configure do |c|
|
167
|
+
c.extend SpecViewExample, type: :controller
|
168
|
+
c.before(:suite) do |_example|
|
169
|
+
$_spec_view_time = Time.zone.now
|
170
|
+
end
|
171
|
+
c.before(type: :controller) do |example|
|
172
|
+
@_spec_view_example = example
|
173
|
+
end
|
174
|
+
c.before(:each, type: :controller) do
|
175
|
+
Timecop.freeze(Time.zone.local(2024, 2, 29, 0o0, 17, 42))
|
176
|
+
end
|
177
|
+
c.after(:each, type: :controller) do
|
178
|
+
Timecop.return
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
data/lib/spec_views.rb
ADDED
metadata
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: spec_views
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Sebastian Gaul
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2022-01-14 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rails
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 5.2.6
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 5.2.6
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rspec-rails
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: haml-rails
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
description: Render views from controller specs for comparision
|
56
|
+
email:
|
57
|
+
- sebastian.gaul@lichtbit.com
|
58
|
+
executables: []
|
59
|
+
extensions: []
|
60
|
+
extra_rdoc_files: []
|
61
|
+
files:
|
62
|
+
- MIT-LICENSE
|
63
|
+
- README.md
|
64
|
+
- Rakefile
|
65
|
+
- app/assets/config/spec_views_manifest.js
|
66
|
+
- app/controllers/spec_views/views_controller.rb
|
67
|
+
- app/views/layouts/spec_views.html.haml
|
68
|
+
- app/views/spec_views/views/compare.html.haml
|
69
|
+
- app/views/spec_views/views/index.html.haml
|
70
|
+
- app/views/spec_views/views/preview.html.haml
|
71
|
+
- config/routes.rb
|
72
|
+
- lib/spec_views.rb
|
73
|
+
- lib/spec_views/configuration.rb
|
74
|
+
- lib/spec_views/engine.rb
|
75
|
+
- lib/spec_views/support.rb
|
76
|
+
- lib/spec_views/version.rb
|
77
|
+
- lib/tasks/spec_views_tasks.rake
|
78
|
+
homepage: https://lichtbit.com
|
79
|
+
licenses:
|
80
|
+
- MIT
|
81
|
+
metadata: {}
|
82
|
+
post_install_message:
|
83
|
+
rdoc_options: []
|
84
|
+
require_paths:
|
85
|
+
- lib
|
86
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
87
|
+
requirements:
|
88
|
+
- - ">="
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: '0'
|
91
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
92
|
+
requirements:
|
93
|
+
- - ">="
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '0'
|
96
|
+
requirements: []
|
97
|
+
rubygems_version: 3.1.6
|
98
|
+
signing_key:
|
99
|
+
specification_version: 4
|
100
|
+
summary: Render views from controller specs for comparision
|
101
|
+
test_files: []
|