museo 0.1.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.
@@ -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: []