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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 60bc5ee9b6f4aed605200f5245ca3924836f287b
4
- data.tar.gz: 58d82c2f85eeba82f4879eee34f822332d6a94de
3
+ metadata.gz: 9e891c8a0cc89e8b248947665837dba154b3ef04
4
+ data.tar.gz: 102b26844c89cbc800331245f904aa9ee06b219c
5
5
  SHA512:
6
- metadata.gz: 3d7a17efaed956c60bcedf7a90f9a093c31203d466e513c2b537b60363cc75ed0edd77ac44eeea6d9de0c6d075bd69a064a5bc4738d00a43e365b5d9c67dcbc9
7
- data.tar.gz: 3038f4dd38797022f8480b70e8ce42b78c508dfcea248ac91566ea2aff65a84737d921cc6e0d5e28cc7a880eabb48548699420bbdede72e0923f8ecae8a2f971
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
- [![Build Status](https://travis-ci.org/danielma/museo.svg?branch=master)](https://travis-ci.org/danielma/museo)
3
+ Museo is a library that provides snapshot testing utilities for Rails (>= 4) views.
4
4
 
5
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/museo`. To experiment with that code, run `bin/console` for an interactive prompt.
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
- TODO: Delete this and the text above, and describe your gem
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
- TODO: Write usage instructions here
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/[USERNAME]/museo.
391
+ Bug reports and pull requests are welcome on GitHub at https://github.com/danielma/museo.
38
392
 
data/exe/museo CHANGED
@@ -4,4 +4,4 @@
4
4
  require "museo"
5
5
  require "museo/cli"
6
6
 
7
- Museo::CLI.new(*ARGV)
7
+ Museo::CLI.start(ARGV)
data/lib/museo.rb CHANGED
@@ -47,7 +47,7 @@ module Museo
47
47
 
48
48
  def initialize
49
49
  @formatter = Museo::Formatter.new
50
- @rspec = false
50
+ @rspec = File.directory?(Museo.rails_root.join("spec"))
51
51
  @generation_disabled = !!ENV["CI"]
52
52
  end
53
53
 
data/lib/museo/cli.rb CHANGED
@@ -1,19 +1,9 @@
1
- module Museo
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
- def list(matcher)
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
- def clear(matcher)
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 RspecIntegration
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
- expect(Snapshot::Rspec.new(self, example.metadata).body).to(
28
- eq(Snapshot.sanitize_response(response.body)),
29
- "Snapshot did not match",
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
@@ -1,3 +1,3 @@
1
1
  module Museo
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0".freeze
3
3
  end
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.1.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: {}