museo 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,49 @@
1
+ module Museo
2
+ class Formatter
3
+ def initialize(options = {})
4
+ @indent = options.delete(:indent) || " ".freeze
5
+ end
6
+
7
+ def format(object, indent_level = 0)
8
+ case object
9
+ when Hash
10
+ format_hash(object, indent_level)
11
+ when Array
12
+ format_array(object, indent_level)
13
+ else
14
+ format_value(object)
15
+ end
16
+ end
17
+
18
+ private
19
+
20
+ def format_hash(hash, indent_level)
21
+ [
22
+ "{",
23
+ hash.map do |key, value|
24
+ "#{@indent * (indent_level + 1)}#{format(key)} => #{format(value, indent_level + 1)},"
25
+ end,
26
+ "#{@indent * indent_level}}",
27
+ ].join("\n")
28
+ end
29
+
30
+ def format_array(array, indent_level)
31
+ [
32
+ "[",
33
+ array.map do |value|
34
+ "#{@indent * (indent_level + 1)}#{format(value, indent_level + 1)},"
35
+ end,
36
+ "#{@indent * indent_level}]",
37
+ ].join("\n")
38
+ end
39
+
40
+ def format_value(value)
41
+ case value
42
+ when Symbol
43
+ value.inspect
44
+ else
45
+ value.to_s
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,33 @@
1
+ module Museo
2
+ module MinitestIntegration
3
+ include TestIntegration
4
+
5
+ def self.included(base)
6
+ base.extend(ClassMethods)
7
+ end
8
+
9
+ module ClassMethods
10
+ def snapshot(description)
11
+ test "snapshot: #{description}" do
12
+ begin
13
+ _museo_setup
14
+
15
+ instance_eval(&Proc.new)
16
+
17
+ assert_snapshot
18
+ ensure
19
+ _museo_teardown
20
+ end
21
+ end
22
+ end
23
+ end
24
+
25
+ def assert_snapshot
26
+ assert_equal(
27
+ Snapshot::Minitest.new(self).body,
28
+ Snapshot.sanitize_response(response.body),
29
+ "Snapshot did not match",
30
+ )
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,33 @@
1
+ module Museo
2
+ module RspecIntegration
3
+ include TestIntegration
4
+
5
+ def self.included(base)
6
+ base.render_views
7
+ base.extend(ClassMethods)
8
+ end
9
+
10
+ module ClassMethods
11
+ def snapshot(description)
12
+ it "matches snapshot: #{description}" do |example|
13
+ begin
14
+ _museo_setup
15
+
16
+ instance_exec(example, &Proc.new)
17
+
18
+ expect_matching_snapshot(example)
19
+ ensure
20
+ _museo_teardown
21
+ end
22
+ end
23
+ end
24
+ end
25
+
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
+ )
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,13 @@
1
+ module Museo
2
+ class Snapshot
3
+ class Minitest < Snapshot
4
+ def initialize(test_case)
5
+ super(
6
+ klass: test_case.class,
7
+ test_name: test_case.name,
8
+ response: test_case.response,
9
+ )
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ module Museo
2
+ class Snapshot
3
+ class Rspec < Snapshot
4
+ def initialize(example, metadata)
5
+ super(
6
+ klass: metadata[:described_class],
7
+ test_name: metadata[:description],
8
+ response: example.response,
9
+ )
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,63 @@
1
+ require "fileutils"
2
+ require_relative "snapshot/rspec"
3
+ require_relative "snapshot/minitest"
4
+
5
+ module Museo
6
+ class Snapshot
7
+ class GenerationDisabledError < StandardError; end
8
+
9
+ class << self
10
+ def sanitize_response(body)
11
+ body.gsub(/:0x[a-fA-F0-9]{4,}/, ":0xXXXXXX")
12
+ end
13
+ end
14
+
15
+ def initialize(klass:, test_name:, response:)
16
+ @class_name = klass.to_s
17
+ @test_name = test_name
18
+ @response = response
19
+
20
+ generate unless exists?
21
+ end
22
+
23
+ def body
24
+ exists? ? File.read(path) : nil
25
+ end
26
+
27
+ private
28
+
29
+ def exists?
30
+ File.exist?(path)
31
+ end
32
+
33
+ def folder
34
+ Museo.pathname(@class_name)
35
+ end
36
+
37
+ def file_name
38
+ "#{Museo.clean_name(@test_name)}.snapshot"
39
+ end
40
+
41
+ def path
42
+ Museo.rails_root.join(folder, file_name)
43
+ end
44
+
45
+ def sanitized_response
46
+ self.class.sanitize_response(@response.body.to_s)
47
+ end
48
+
49
+ def generate
50
+ if Museo.configuration.generation_disabled
51
+ fail GenerationDisabledError,
52
+ "Can't generate snapshots in a CI environment. " \
53
+ "Please generate snapshots locally first"
54
+ end
55
+
56
+ FileUtils.mkdir_p(folder)
57
+
58
+ File.open(path, "wb") { |f| f.print sanitized_response }
59
+
60
+ puts "Updated snapshot for #{file_name.inspect}"
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,51 @@
1
+ module Museo
2
+ module TestIntegration
3
+ def _museo_setup
4
+ _add_snapshot_helper_methods
5
+ _redefine_render_with_snapshot_layout
6
+ end
7
+
8
+ def _museo_teardown
9
+ _remove_snapshot_helper_methods
10
+ end
11
+
12
+ def _add_snapshot_helper_methods
13
+ @controller.class.helper do
14
+ Museo.configuration.stubbed_methods.each do |method_name, callable|
15
+ alias_method("_original_#{method_name}", method_name) if method_defined?(method_name)
16
+
17
+ define_method(method_name) do |*args, &block|
18
+ output = callable.respond_to?(:call) ? instance_exec(*args, block, &callable) : callable
19
+
20
+ [
21
+ "<!--",
22
+ "Stubbed method call: #{method_name}",
23
+ Museo.configuration.formatter.format(output),
24
+ "-->",
25
+ "",
26
+ ].join("\n").html_safe
27
+ end
28
+ end
29
+ end
30
+ end
31
+
32
+ def _remove_snapshot_helper_methods
33
+ @controller.class.helper do
34
+ Museo.configuration.stubbed_methods.each do |method_name, _callable|
35
+ remove_method method_name
36
+
37
+ if method_defined?("_original_#{method_name}")
38
+ alias_method method_name, "_original_#{method_name}"
39
+ end
40
+ end
41
+ end
42
+ end
43
+
44
+ def _redefine_render_with_snapshot_layout
45
+ @controller.define_singleton_method(:render) do |*args|
46
+ options = args.length > 1 ? args.last : {}
47
+ super(*args[1..-2], options.merge(layout: "museo/snapshot"))
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,3 @@
1
+ module Museo
2
+ VERSION = "0.1.0"
3
+ end
data/lib/museo.rb ADDED
@@ -0,0 +1,68 @@
1
+ require "rails"
2
+ require "museo/version"
3
+ require "museo/formatter"
4
+ require "museo/snapshot"
5
+ require "museo/test_integration"
6
+ require "museo/minitest_integration"
7
+ require "museo/rspec_integration"
8
+ require "museo/engine"
9
+
10
+ module Museo
11
+ class << self
12
+ def configure
13
+ yield(configuration)
14
+ end
15
+
16
+ def configuration
17
+ @configuration ||= Configuration.new
18
+ end
19
+
20
+ def rails_root
21
+ @rails_root ||= Rails::Application.find_root(Dir.pwd)
22
+ end
23
+
24
+ def clean_name(name)
25
+ if name
26
+ name.gsub("::", "/").gsub(/[^0-9a-z\/]+/i, "_")
27
+ else
28
+ ""
29
+ end
30
+ end
31
+
32
+ def pathname(class_name = "")
33
+ test_directory = Museo.configuration.rspec ? "spec" : "test"
34
+
35
+ Museo.rails_root.join(test_directory, "snapshots", clean_name(class_name))
36
+ end
37
+
38
+ def clear_configuration!
39
+ @configuration = Configuration.new
40
+ end
41
+ end
42
+
43
+ class Configuration
44
+ attr_accessor :formatter
45
+ attr_accessor :rspec
46
+ attr_accessor :generation_disabled
47
+
48
+ def initialize
49
+ @formatter = Museo::Formatter.new
50
+ @rspec = false
51
+ @generation_disabled = !!ENV["CI"]
52
+ end
53
+
54
+ def stubbed_methods
55
+ @stubbed_methods ||= {}
56
+ end
57
+
58
+ def stub(name, value = nil)
59
+ value = block_given? ? Proc.new : value
60
+
61
+ stubbed_methods[name] = value
62
+ end
63
+
64
+ def clear_stubs!
65
+ @stubbed_methods = {}
66
+ end
67
+ end
68
+ end
@@ -0,0 +1 @@
1
+ # rake tasks
data/museo.gemspec ADDED
@@ -0,0 +1,31 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "museo/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "museo"
8
+ spec.version = Museo::VERSION
9
+ spec.authors = ["Daniel Ma"]
10
+ spec.email = ["drailskid@yahoo.com"]
11
+
12
+ spec.summary = %(Snapshot testing for Rails views)
13
+ spec.description = %(Provide snapshot testing utilities for Rails views)
14
+ spec.homepage = "https://github.com/danielma/museo"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
17
+ spec.bindir = "exe"
18
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency "rails", ">= 4", "< 5.1"
22
+ spec.add_development_dependency "appraisal"
23
+ spec.add_development_dependency "rubocop", "~> 0.42"
24
+ spec.add_development_dependency "rspec"
25
+ spec.add_development_dependency "rspec-rails"
26
+ spec.add_development_dependency "guard"
27
+ spec.add_development_dependency "guard-rspec"
28
+ spec.add_development_dependency "guard-minitest"
29
+ spec.add_development_dependency "pry"
30
+ spec.add_development_dependency "awesome_print"
31
+ end
metadata ADDED
@@ -0,0 +1,221 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: museo
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Daniel Ma
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2016-12-16 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: '4'
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: '5.1'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ version: '4'
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: '5.1'
33
+ - !ruby/object:Gem::Dependency
34
+ name: appraisal
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ type: :development
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ - !ruby/object:Gem::Dependency
48
+ name: rubocop
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '0.42'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '0.42'
61
+ - !ruby/object:Gem::Dependency
62
+ name: rspec
63
+ requirement: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
68
+ type: :development
69
+ prerelease: false
70
+ version_requirements: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ - !ruby/object:Gem::Dependency
76
+ name: rspec-rails
77
+ requirement: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
82
+ type: :development
83
+ prerelease: false
84
+ version_requirements: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
89
+ - !ruby/object:Gem::Dependency
90
+ name: guard
91
+ requirement: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ version: '0'
96
+ type: :development
97
+ prerelease: false
98
+ version_requirements: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ version: '0'
103
+ - !ruby/object:Gem::Dependency
104
+ name: guard-rspec
105
+ requirement: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - ">="
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ type: :development
111
+ prerelease: false
112
+ version_requirements: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ version: '0'
117
+ - !ruby/object:Gem::Dependency
118
+ name: guard-minitest
119
+ requirement: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - ">="
122
+ - !ruby/object:Gem::Version
123
+ version: '0'
124
+ type: :development
125
+ prerelease: false
126
+ version_requirements: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - ">="
129
+ - !ruby/object:Gem::Version
130
+ version: '0'
131
+ - !ruby/object:Gem::Dependency
132
+ name: pry
133
+ requirement: !ruby/object:Gem::Requirement
134
+ requirements:
135
+ - - ">="
136
+ - !ruby/object:Gem::Version
137
+ version: '0'
138
+ type: :development
139
+ prerelease: false
140
+ version_requirements: !ruby/object:Gem::Requirement
141
+ requirements:
142
+ - - ">="
143
+ - !ruby/object:Gem::Version
144
+ version: '0'
145
+ - !ruby/object:Gem::Dependency
146
+ name: awesome_print
147
+ requirement: !ruby/object:Gem::Requirement
148
+ requirements:
149
+ - - ">="
150
+ - !ruby/object:Gem::Version
151
+ version: '0'
152
+ type: :development
153
+ prerelease: false
154
+ version_requirements: !ruby/object:Gem::Requirement
155
+ requirements:
156
+ - - ">="
157
+ - !ruby/object:Gem::Version
158
+ version: '0'
159
+ description: Provide snapshot testing utilities for Rails views
160
+ email:
161
+ - drailskid@yahoo.com
162
+ executables:
163
+ - museo
164
+ extensions: []
165
+ extra_rdoc_files: []
166
+ files:
167
+ - ".gitignore"
168
+ - ".rspec"
169
+ - ".rubocop.yml"
170
+ - ".ruby-version"
171
+ - ".travis.yml"
172
+ - Appraisals
173
+ - Gemfile
174
+ - Guardfile
175
+ - README.md
176
+ - Rakefile
177
+ - app/views/layouts/museo/snapshot.html.erb
178
+ - bin/console
179
+ - bin/setup
180
+ - exe/museo
181
+ - gemfiles/rails_4.gemfile
182
+ - gemfiles/rails_4.gemfile.lock
183
+ - gemfiles/rails_5.gemfile
184
+ - gemfiles/rails_5.gemfile.lock
185
+ - lib/museo.rb
186
+ - lib/museo/cli.rb
187
+ - lib/museo/engine.rb
188
+ - lib/museo/formatter.rb
189
+ - lib/museo/minitest_integration.rb
190
+ - lib/museo/rspec_integration.rb
191
+ - lib/museo/snapshot.rb
192
+ - lib/museo/snapshot/minitest.rb
193
+ - lib/museo/snapshot/rspec.rb
194
+ - lib/museo/test_integration.rb
195
+ - lib/museo/version.rb
196
+ - lib/tasks/museo_tasks.rake
197
+ - museo.gemspec
198
+ homepage: https://github.com/danielma/museo
199
+ licenses: []
200
+ metadata: {}
201
+ post_install_message:
202
+ rdoc_options: []
203
+ require_paths:
204
+ - lib
205
+ required_ruby_version: !ruby/object:Gem::Requirement
206
+ requirements:
207
+ - - ">="
208
+ - !ruby/object:Gem::Version
209
+ version: '0'
210
+ required_rubygems_version: !ruby/object:Gem::Requirement
211
+ requirements:
212
+ - - ">="
213
+ - !ruby/object:Gem::Version
214
+ version: '0'
215
+ requirements: []
216
+ rubyforge_project:
217
+ rubygems_version: 2.4.5.1
218
+ signing_key:
219
+ specification_version: 4
220
+ summary: Snapshot testing for Rails views
221
+ test_files: []