museo 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +360 -6
- data/exe/museo +1 -1
- data/lib/museo.rb +1 -1
- data/lib/museo/cli.rb +7 -16
- data/lib/museo/rspec_integration.rb +5 -5
- data/lib/museo/version.rb +1 -1
- data/museo.gemspec +1 -0
- data/screenshots/initial-state.png +0 -0
- metadata +16 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9e891c8a0cc89e8b248947665837dba154b3ef04
|
4
|
+
data.tar.gz: 102b26844c89cbc800331245f904aa9ee06b219c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4d1a3ce1ba8a7fc7d5080d2ab438e6765dc698d6ae66b15d5b2afc811b30344f00af0145b42fefc409ae9a3652ece0c7e17e862d4a747ca3f283f1f19d4974fd
|
7
|
+
data.tar.gz: c653d3c0d92891ec6c0ddc2ba241167e9a85e87facbc8fe1c1d9932ab994e52c25dad7b91011834cde457f13f7ee06213205cba73f0e30633a0ce5c205085e8a
|
data/README.md
CHANGED
@@ -1,10 +1,10 @@
|
|
1
|
-
# Museo
|
1
|
+
# Museo [![Build Status](https://travis-ci.org/danielma/museo.svg?branch=master)](https://travis-ci.org/danielma/museo)
|
2
2
|
|
3
|
-
|
3
|
+
Museo is a library that provides snapshot testing utilities for Rails (>= 4) views.
|
4
4
|
|
5
|
-
|
5
|
+
Snapshot testing is a form of regression testing where the output of your views (given a set of state) is written to a file and compared against the historical version during testing. Any time there is a change in the rendered output of your views, Museo will show you the diff and cause your tests to fail. If the changes you see are what you expected, you can remove the previous version of the snapshot and make the new version the canonical output.
|
6
6
|
|
7
|
-
|
7
|
+
It supports Minitest and RSpec (2, 3).
|
8
8
|
|
9
9
|
## Installation
|
10
10
|
|
@@ -24,7 +24,361 @@ Or install it yourself as:
|
|
24
24
|
|
25
25
|
## Usage
|
26
26
|
|
27
|
-
|
27
|
+
### Configuration
|
28
|
+
|
29
|
+
`config/initializers/museo.rb`
|
30
|
+
|
31
|
+
```ruby
|
32
|
+
Museo.configure do |config|
|
33
|
+
# Configure Museo to use RSpec (automatically set to true if spec directory exists)
|
34
|
+
config.rspec = true
|
35
|
+
|
36
|
+
# Stub the `render` method inside views
|
37
|
+
config.stub(:render) do |options = {}, block = nil|
|
38
|
+
options[:content] = capture(&block) if block
|
39
|
+
options
|
40
|
+
end
|
41
|
+
end
|
42
|
+
```
|
43
|
+
|
44
|
+
### CLI
|
45
|
+
|
46
|
+
The `museo` CLI has 2 commands: `list` and `clear`
|
47
|
+
|
48
|
+
```bash
|
49
|
+
$ museo help
|
50
|
+
Commands:
|
51
|
+
museo clear [MATCHER] # Clear snapshots that match MATCHER
|
52
|
+
museo help [COMMAND] # Describe available commands or one specific command
|
53
|
+
museo list [MATCHER] # List snapshots that match MATCHER
|
54
|
+
```
|
55
|
+
|
56
|
+
## Getting Started
|
57
|
+
|
58
|
+
Museo makes a custom testing command available to your controller tests to save and compare snapshots.
|
59
|
+
|
60
|
+
These examples will be written with Minitest, but there are examples below for how to work with RSpec as well.
|
61
|
+
|
62
|
+
Let's start with a simple setup and write some snapshot tests.
|
63
|
+
|
64
|
+
`app/controllers/products_controller.rb`
|
65
|
+
|
66
|
+
```ruby
|
67
|
+
class ProductsController < ApplicationController
|
68
|
+
def index
|
69
|
+
@products = Product.all
|
70
|
+
end
|
71
|
+
end
|
72
|
+
```
|
73
|
+
|
74
|
+
`app/views/products/index.html.erb`
|
75
|
+
|
76
|
+
```erb
|
77
|
+
<h1 class="page-title">Products</h1>
|
78
|
+
|
79
|
+
<%= render partial: "product", collection: @products %>
|
80
|
+
```
|
81
|
+
|
82
|
+
`app/views/products/_product.html.erb`
|
83
|
+
|
84
|
+
```erb
|
85
|
+
<div class="product">
|
86
|
+
<strong><%= product.name %></strong>
|
87
|
+
<% if product.discounted? %>
|
88
|
+
<div>
|
89
|
+
Price: <strike><%= number_to_currency product.price %></strike>
|
90
|
+
<%= number_to_currency product.discounted_price %>
|
91
|
+
</div>
|
92
|
+
<div><%= product.discount_percentage %><sup>%</sup> off!</div>
|
93
|
+
<% else %>
|
94
|
+
<div>
|
95
|
+
Price: <%= number_to_currency product.price %>
|
96
|
+
</div>
|
97
|
+
<% end %>
|
98
|
+
</div>
|
99
|
+
```
|
100
|
+
|
101
|
+
It looks like this
|
102
|
+
|
103
|
+
![Initial State](screenshots/initial-state.png)
|
104
|
+
|
105
|
+
Now, let's do some snapshot tests
|
106
|
+
|
107
|
+
First, include `Museo::RSpecIntegration` and write your first snapshot
|
108
|
+
|
109
|
+
```ruby
|
110
|
+
class ProductsController < ActionController::TestCase
|
111
|
+
include Museo::MinitestIntegration
|
112
|
+
|
113
|
+
describe "GET #index" do
|
114
|
+
snapshot "with no params" do
|
115
|
+
get :index
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
```
|
120
|
+
|
121
|
+
Then run the tests
|
122
|
+
|
123
|
+
```bash
|
124
|
+
$ bundle exec rspec
|
125
|
+
Run options: include {:focus=>true}
|
126
|
+
|
127
|
+
All examples were filtered out; ignoring {:focus=>true}
|
128
|
+
|
129
|
+
Randomized with seed 36544
|
130
|
+
|
131
|
+
ProductsController
|
132
|
+
GET #index
|
133
|
+
Updated snapshot for "matches_snapshot_with_no_params.snapshot"
|
134
|
+
matches snapshot: with no params
|
135
|
+
|
136
|
+
Top 1 slowest examples (0.04054 seconds, 98.4% of total time):
|
137
|
+
ProductsController GET #index matches snapshot: with no params
|
138
|
+
0.04054 seconds
|
139
|
+
|
140
|
+
Finished in 0.04118 seconds (files took 1.41 seconds to load)
|
141
|
+
1 example, 0 failures
|
142
|
+
|
143
|
+
Randomized with seed 36544
|
144
|
+
```
|
145
|
+
|
146
|
+
Notice where it says `Updated snapshot for "matches_snapshot_with_no_params.snapshot". This means there was no previous snapshot file and Museo generated a new one.
|
147
|
+
|
148
|
+
Museo uses a special layout file that includes the contents of every `content_for` block, so your snapshots will not break for layout changes, but will break for any change of a `content_for` block.
|
149
|
+
|
150
|
+
```bash
|
151
|
+
$ tree spec/snapshots
|
152
|
+
spec/snapshots
|
153
|
+
└── ProductsController
|
154
|
+
└── matches_snapshot_with_no_params.snapshot
|
155
|
+
|
156
|
+
1 directory, 1 file
|
157
|
+
```
|
158
|
+
|
159
|
+
Now we have our first snapshot! Let's make some changes to our template and see what happens.
|
160
|
+
|
161
|
+
`app/views/products/index.html.erb`
|
162
|
+
|
163
|
+
```erb
|
164
|
+
<h1 class="title">Products</h1>
|
165
|
+
|
166
|
+
<hr />
|
167
|
+
|
168
|
+
<%= render partial: "product", collection: @products %>
|
169
|
+
```
|
170
|
+
|
171
|
+
```bash
|
172
|
+
$ bundle exec rspec
|
173
|
+
Run options: include {:focus=>true}
|
174
|
+
|
175
|
+
All examples were filtered out; ignoring {:focus=>true}
|
176
|
+
|
177
|
+
Randomized with seed 60210
|
178
|
+
|
179
|
+
ProductsController
|
180
|
+
GET #index
|
181
|
+
matches snapshot: with no params (FAILED - 1)
|
182
|
+
|
183
|
+
Failures:
|
184
|
+
|
185
|
+
1) ProductsController GET #index matches snapshot: with no params
|
186
|
+
Failure/Error: DEFAULT_FAILURE_NOTIFIER = lambda { |failure, _opts| raise failure }
|
187
|
+
|
188
|
+
Snapshot did not match
|
189
|
+
Diff:
|
190
|
+
@@ -1,6 +1,8 @@
|
191
|
+
<!-- content_for layout -->
|
192
|
+
|
193
|
+
-<h1 class="page-title">Products</h1>
|
194
|
+
+<h1 class="title">Products</h1>
|
195
|
+
+
|
196
|
+
+<hr />
|
197
|
+
|
198
|
+
<div class="product">
|
199
|
+
<strong>Westworld Season 1 Blu-Ray</strong>
|
200
|
+
```
|
201
|
+
|
202
|
+
Now we have an error letting us know that our snapshot didn't match.
|
203
|
+
|
204
|
+
If the diff is correct, we can clear out the old snapshot and regenerate it using the CLI tool
|
205
|
+
|
206
|
+
```bash
|
207
|
+
$ bundle exec museo clear ProductsController
|
208
|
+
Directory: /Users/danielma/Code/test/museo-example/test/snapshots/ProductsController
|
209
|
+
|
210
|
+
matches_snapshot_with_no_params.snapshot
|
211
|
+
Removing snapshots
|
212
|
+
```
|
213
|
+
|
214
|
+
Now that the snapshots are cleared, run the tests again to regenerate the snapshots.
|
215
|
+
|
216
|
+
```bash
|
217
|
+
$ bundle exec rspec
|
218
|
+
Run options: include {:focus=>true}
|
219
|
+
|
220
|
+
All examples were filtered out; ignoring {:focus=>true}
|
221
|
+
|
222
|
+
Randomized with seed 6462
|
223
|
+
|
224
|
+
ProductsController
|
225
|
+
GET #index
|
226
|
+
Updated snapshot for "matches_snapshot_with_no_params.snapshot"
|
227
|
+
matches snapshot: with no params
|
228
|
+
```
|
229
|
+
|
230
|
+
Include your snapshots in your version control repo for accurate history
|
231
|
+
|
232
|
+
### How can Museo help?
|
233
|
+
|
234
|
+
Let's say you decide to change some logic in your `_product.html.erb` partial. Museo can help you make sure the output is exactly what you expect.
|
235
|
+
|
236
|
+
In our `_product.html.erb`, we want to move the block for a non-discounted product above the block for a discounted product. We'll need a logic change.
|
237
|
+
|
238
|
+
`app/views/products/_product.html.erb`
|
239
|
+
|
240
|
+
```erb
|
241
|
+
<div class="product">
|
242
|
+
<strong><%= product.name %></strong>
|
243
|
+
<% if product.discounted? %>
|
244
|
+
<div>
|
245
|
+
Price: <%= number_to_currency product.price %>
|
246
|
+
</div>
|
247
|
+
<% else %>
|
248
|
+
<div>
|
249
|
+
Price: <strike><%= number_to_currency product.price %></strike>
|
250
|
+
<%= number_to_currency product.discounted_price %>
|
251
|
+
</div>
|
252
|
+
<div><%= product.discount_percentage %><sup>%</sup> off!</div>
|
253
|
+
<% end %>
|
254
|
+
</div>
|
255
|
+
|
256
|
+
```
|
257
|
+
|
258
|
+
```bash
|
259
|
+
$ bundle exec rspec
|
260
|
+
```
|
261
|
+
|
262
|
+
This command should include this failure:
|
263
|
+
|
264
|
+
```
|
265
|
+
Failures:
|
266
|
+
|
267
|
+
1) ProductsController GET #index matches snapshot: with no params
|
268
|
+
Failure/Error: DEFAULT_FAILURE_NOTIFIER = lambda { |failure, _opts| raise failure }
|
269
|
+
|
270
|
+
Snapshot did not match
|
271
|
+
Diff:
|
272
|
+
|
273
|
+
|
274
|
+
|
275
|
+
@@ -7,16 +7,16 @@
|
276
|
+
<div class="product">
|
277
|
+
<strong>Westworld Season 1 Blu-Ray</strong>
|
278
|
+
<div>
|
279
|
+
- Price: <strike>$40.00</strike>
|
280
|
+
- $36.00
|
281
|
+
+ Price: $40.00
|
282
|
+
</div>
|
283
|
+
- <div>10<sup>%</sup> off!</div>
|
284
|
+
</div>
|
285
|
+
<div class="product">
|
286
|
+
<strong>1 Week in Samurai World</strong>
|
287
|
+
<div>
|
288
|
+
- Price: $280,000.00
|
289
|
+
+ Price: <strike>$280,000.00</strike>
|
290
|
+
+ $280,000.00
|
291
|
+
</div>
|
292
|
+
+ <div><sup>%</sup> off!</div>
|
293
|
+
</div>
|
294
|
+
```
|
295
|
+
|
296
|
+
Hmmm, we didn't expect the snapshot to break in this case. We only wanted to change the way the file was written. Looks like the logic in our most recent change is broken.
|
297
|
+
|
298
|
+
We can fix it by changing `<% if product.discounted? %>` to `<% unless product.discounted? %>`
|
299
|
+
|
300
|
+
`app/views/products/_product.html.erb`
|
301
|
+
|
302
|
+
```erb
|
303
|
+
<div class="product">
|
304
|
+
<strong><%= product.name %></strong>
|
305
|
+
<% unless product.discounted? %>
|
306
|
+
<div>
|
307
|
+
Price: <%= number_to_currency product.price %>
|
308
|
+
</div>
|
309
|
+
<% else %>
|
310
|
+
<div>
|
311
|
+
Price: <strike><%= number_to_currency product.price %></strike>
|
312
|
+
<%= number_to_currency product.discounted_price %>
|
313
|
+
</div>
|
314
|
+
<div><%= product.discount_percentage %><sup>%</sup> off!</div>
|
315
|
+
<% end %>
|
316
|
+
</div>
|
317
|
+
```
|
318
|
+
|
319
|
+
And now, `bundle exec rspec` passes!
|
320
|
+
|
321
|
+
We have successfully made a change in our template without breaking the expected output.
|
322
|
+
|
323
|
+
### Advanced Usage
|
324
|
+
|
325
|
+
#### Stubbing methods in your views
|
326
|
+
|
327
|
+
In some cases, you may not desire to include the output from certain helpers in your snapshots. For example, if you are using the `react_rails` gem, you may not want to include the output from the `react_component` helper.
|
328
|
+
|
329
|
+
We can stub this method in the Museo configuration
|
330
|
+
|
331
|
+
`config/initializers/museo.rb`
|
332
|
+
|
333
|
+
```ruby
|
334
|
+
Museo.configure do |config|
|
335
|
+
config.stub(:react_component) do |name, *_|
|
336
|
+
name
|
337
|
+
end
|
338
|
+
end
|
339
|
+
```
|
340
|
+
|
341
|
+
And use the helper in `views/products/index.html.erb`
|
342
|
+
|
343
|
+
```erb
|
344
|
+
<h1 class="title">Products</h1>
|
345
|
+
|
346
|
+
<hr />
|
347
|
+
|
348
|
+
<%= render partial: "product", collection: @products %>
|
349
|
+
|
350
|
+
<%= react_component "Components.Products", products: @products %>
|
351
|
+
```
|
352
|
+
|
353
|
+
Now, when we run `bundle exec rspec`, our new snapshots will no longer include the contents of the helper, but only the arguments we returned from our stub block.
|
354
|
+
|
355
|
+
```
|
356
|
+
Failures:
|
357
|
+
|
358
|
+
1) ProductsController GET #index matches snapshot: with no params
|
359
|
+
Failure/Error: DEFAULT_FAILURE_NOTIFIER = lambda { |failure, _opts| raise failure }
|
360
|
+
|
361
|
+
Snapshot did not match
|
362
|
+
Diff:
|
363
|
+
@@ -20,6 +20,12 @@
|
364
|
+
</div>
|
365
|
+
|
366
|
+
|
367
|
+
+<!--
|
368
|
+
+Stubbed method call: react_component
|
369
|
+
+Components.Products
|
370
|
+
+-->
|
371
|
+
+
|
372
|
+
+
|
373
|
+
|
374
|
+
<!-- end layout -->
|
375
|
+
```
|
376
|
+
|
377
|
+
This allows us to snapshot views without the output from noisy helpers (eg `render partial:` etc)
|
378
|
+
|
379
|
+
## Prior Art
|
380
|
+
|
381
|
+
This library was inspired by [Jest Snapshot Testing](https://facebook.github.io/jest/blog/2016/07/27/jest-14.html). It wouldn't be here without the Jest team's hard work.
|
28
382
|
|
29
383
|
## Development
|
30
384
|
|
@@ -34,5 +388,5 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
|
|
34
388
|
|
35
389
|
## Contributing
|
36
390
|
|
37
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/
|
391
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/danielma/museo.
|
38
392
|
|
data/exe/museo
CHANGED
data/lib/museo.rb
CHANGED
data/lib/museo/cli.rb
CHANGED
@@ -1,19 +1,9 @@
|
|
1
|
-
|
2
|
-
class CLI
|
3
|
-
def initialize(command = nil, *argv)
|
4
|
-
case command.to_s.strip.downcase
|
5
|
-
when "clear"
|
6
|
-
clear(argv.first)
|
7
|
-
when "list"
|
8
|
-
list(argv.first)
|
9
|
-
when ""
|
10
|
-
puts "Please add a command"
|
11
|
-
else
|
12
|
-
puts "Don't know how to do command: #{command}"
|
13
|
-
end
|
14
|
-
end
|
1
|
+
require "thor"
|
15
2
|
|
16
|
-
|
3
|
+
module Museo
|
4
|
+
class CLI < Thor
|
5
|
+
desc "list [MATCHER]", "List snapshots that match MATCHER"
|
6
|
+
def list(matcher = nil)
|
17
7
|
directory = find_directory(matcher)
|
18
8
|
|
19
9
|
if File.directory?(directory)
|
@@ -24,7 +14,8 @@ module Museo
|
|
24
14
|
end
|
25
15
|
end
|
26
16
|
|
27
|
-
|
17
|
+
desc "clear [MATCHER]", "Clear snapshots that match MATCHER"
|
18
|
+
def clear(matcher = nil)
|
28
19
|
list(matcher)
|
29
20
|
directory_to_clear = find_directory(matcher)
|
30
21
|
|
@@ -1,5 +1,5 @@
|
|
1
1
|
module Museo
|
2
|
-
module
|
2
|
+
module RSpecIntegration
|
3
3
|
include TestIntegration
|
4
4
|
|
5
5
|
def self.included(base)
|
@@ -24,10 +24,10 @@ module Museo
|
|
24
24
|
end
|
25
25
|
|
26
26
|
def expect_matching_snapshot(example)
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
)
|
27
|
+
snapshot = Snapshot::Rspec.new(self, example.metadata)
|
28
|
+
response_body = Snapshot.sanitize_response(response.body)
|
29
|
+
|
30
|
+
expect(response_body).to eq(snapshot.body), "Snapshot did not match"
|
31
31
|
end
|
32
32
|
end
|
33
33
|
end
|
data/lib/museo/version.rb
CHANGED
data/museo.gemspec
CHANGED
@@ -19,6 +19,7 @@ Gem::Specification.new do |spec|
|
|
19
19
|
spec.require_paths = ["lib"]
|
20
20
|
|
21
21
|
spec.add_dependency "rails", ">= 4", "< 5.1"
|
22
|
+
spec.add_dependency "thor"
|
22
23
|
spec.add_development_dependency "appraisal"
|
23
24
|
spec.add_development_dependency "rubocop", "~> 0.42"
|
24
25
|
spec.add_development_dependency "rspec"
|
Binary file
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: museo
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Daniel Ma
|
@@ -30,6 +30,20 @@ dependencies:
|
|
30
30
|
- - "<"
|
31
31
|
- !ruby/object:Gem::Version
|
32
32
|
version: '5.1'
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: thor
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - ">="
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '0'
|
40
|
+
type: :runtime
|
41
|
+
prerelease: false
|
42
|
+
version_requirements: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '0'
|
33
47
|
- !ruby/object:Gem::Dependency
|
34
48
|
name: appraisal
|
35
49
|
requirement: !ruby/object:Gem::Requirement
|
@@ -195,6 +209,7 @@ files:
|
|
195
209
|
- lib/museo/version.rb
|
196
210
|
- lib/tasks/museo_tasks.rake
|
197
211
|
- museo.gemspec
|
212
|
+
- screenshots/initial-state.png
|
198
213
|
homepage: https://github.com/danielma/museo
|
199
214
|
licenses: []
|
200
215
|
metadata: {}
|