monet 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 4ba54b53a3cb965bdcc834da924e37f3df244e76
4
+ data.tar.gz: ca5a32475a8411d8dcc61e750ca6e534582a38a2
5
+ SHA512:
6
+ metadata.gz: e2debbc40dc2411f580a40ee1dd8e8bf6691ca342779ac773a8ca48d059edd35d442a89cbb3cffb28c402f88f0413ff99b7ad16c6313cac9b1607c078545ffd9
7
+ data.tar.gz: 0308e17eeac8c8e00d60a1e752b74711186f1faa16ec0bc729082b14a8d0f3e1d8dc45132fec54b46441f7eb972a66bfcdc5fc1de0ce56ad323d246fcadfa8e7
data/.gitignore ADDED
@@ -0,0 +1,19 @@
1
+ *.gem
2
+ .gems
3
+
4
+ *.rbc
5
+ .bundle
6
+ .config
7
+ .yardoc
8
+ Gemfile.lock
9
+ InstalledFiles
10
+ _yardoc
11
+ coverage
12
+ doc/
13
+ lib/bundler/man
14
+ pkg
15
+ rdoc
16
+ spec/reports
17
+ test/tmp
18
+ test/version_tmp
19
+ tmp
data/.ruby-gemset ADDED
@@ -0,0 +1 @@
1
+ monet
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.0
data/.travis.yml ADDED
@@ -0,0 +1,6 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.0
4
+ - 1.9.3
5
+ - rbx
6
+
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in monet.gemspec
4
+ gemspec
5
+
6
+ group :test do
7
+ gem 'pry'
8
+ gem 'webmock'
9
+ gem 'vcr'
10
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Luke van der Hoeven
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,68 @@
1
+ # Monet
2
+ [![Build Status](https://travis-ci.org/plukevdh/monet.png?branch=master)](https://travis-ci.org/plukevdh/monet)
3
+ [![Code Climate](https://codeclimate.com/github/plukevdh/monet.png)](https://codeclimate.com/github/plukevdh/monet)
4
+
5
+ Monet is a libary built for making testing interfaces and design easy. We all have interfaces that we've added a new button, changed some CSS, or added new javascript interaction to and had the page layout explode unexpectedly. Monet is meant to make tracking those changes and ensuring consistent automatably easy.
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ gem 'monet'
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install monet
20
+
21
+ ## Usage
22
+
23
+ The basic gem requires a config file that is somehow called on the app startup. This config primarily exists to give the gem a list of paths it needs to collect and either baseline or compare to previous baselines. This config might look something like this:
24
+
25
+ ```ruby
26
+ Monet.config do |config|
27
+ config.driver = :poltergeist
28
+ config.dimensions = [1440,900]
29
+
30
+ config.map do |map|
31
+ map.add 'home/index'
32
+ map.add 'home/show'
33
+ end
34
+
35
+ # alternatively...
36
+
37
+ config.map :spider
38
+ end
39
+ ```
40
+
41
+ ## Todo
42
+ - Site spidering
43
+ - Baseline caching
44
+ - Browser/driver config
45
+ - Parallelize PNG diffing
46
+ - Dashboard
47
+ - Rails integration
48
+ - Sinatra/Rack integration
49
+ - Web UI
50
+
51
+ ## Contributing
52
+
53
+ 1. Fork it
54
+ 2. Branch it (`git checkout -b my-new-feature`)
55
+ 3. Commit it (`git commit -am 'Add some feature'`)
56
+ 4. Push it (`git push origin my-new-feature`)
57
+ 5. Pull Request it!
58
+
59
+ Willing to consider commit bit priviledges to anyone who expresses extreme interest in the project.
60
+
61
+ ## Credits
62
+
63
+ Big shout to __Jeff Kreeftmeijer__ for his blog post on [ChunkyPNG](http://jeffkreeftmeijer.com/2011/comparing-images-and-creating-image-diffs/). The Monet::Compare::CompareStrategy code is basically what he wrote in the post.
64
+
65
+ ## Alternatives
66
+
67
+ - https://github.com/intridea/green_onion : Mostly the same thing I did here, but I didn't know about it starting out. I like what they did, but I had a different take, so I decided to pursue my version to the end.
68
+ - https://github.com/BBC-News/wraith : Similar idea again, but more limited in features, also doesn't allow for baselining, more of a multi-site/URL compare.
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rspec/core/rake_task'
3
+ desc "Runs the site and grabs baselines"
4
+
5
+ task :baseline do
6
+
7
+ end
8
+
9
+ RSpec::Core::RakeTask.new(:test)
10
+ task :default => :test
@@ -0,0 +1,37 @@
1
+ require "capybara"
2
+ require 'capybara/poltergeist'
3
+ require "capybara/dsl"
4
+
5
+ require 'monet/config'
6
+
7
+ module Monet
8
+ class Capture
9
+ include Capybara::DSL
10
+
11
+ def initialize(config={})
12
+ @config = (config.is_a? Monet::Config) ? config : Monet::Config.new(config)
13
+
14
+ # TODO: make configurable
15
+ Capybara.default_driver = @config.driver
16
+ end
17
+
18
+ def capture(path)
19
+ visit normalize_path(path)
20
+ page.driver.render(image_name_from_path(path), full: true)
21
+ end
22
+
23
+ private
24
+ def capture_path
25
+ @config.capture_dir
26
+ end
27
+
28
+ def normalize_path(path)
29
+ "http://#{path}" unless path.start_with?("https?")
30
+ end
31
+
32
+ def image_name_from_path(path)
33
+ name = path.gsub(/https?:\/\//, '').gsub('.', '_')
34
+ "#{capture_path}/#{name}-#{Time.now.to_i}.png"
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,72 @@
1
+ require 'spidr'
2
+
3
+ module Monet
4
+ class CaptureMap
5
+ extend Forwardable
6
+
7
+ class PathCollection
8
+ attr_reader :paths, :root_url
9
+
10
+ def initialize(root_url)
11
+ @root_url = root_url
12
+ @paths = []
13
+ end
14
+
15
+ def add(path)
16
+ @paths << normalized_path(path)
17
+ end
18
+
19
+ def normalized_path(path)
20
+ path.chomp "/"
21
+ end
22
+ end
23
+
24
+ class PathSpider < PathCollection
25
+ SKIP_EXT = %w(js css png jpg mp4 txt zip ico ogv ogg pdf gz)
26
+ SKIP_PATHS = [/\?.*/]
27
+
28
+ def paths
29
+ @paths = normalize Spidr.site(@root_url, ignore_links: ignores)
30
+ end
31
+
32
+ def ignores
33
+ SKIP_EXT.map {|x| Regexp.new x }.concat SKIP_PATHS
34
+ end
35
+
36
+ private
37
+ def normalize(spider_results)
38
+ spider_results.history.map &:to_s
39
+ end
40
+ end
41
+
42
+ InvalidURL = Class.new(StandardError)
43
+
44
+ attr_reader :type
45
+
46
+ def initialize(root_url, type=:explicit, &block)
47
+ @type = type
48
+ @path_helper = type_mapper.new parse_uri(root_url)
49
+
50
+ yield(@path_helper) if block_given?
51
+ end
52
+
53
+ def_delegators :@path_helper, :paths, :add, :root_url
54
+
55
+ def type_mapper
56
+ case @type
57
+ when :explicit
58
+ PathCollection
59
+ when :spider
60
+ PathSpider
61
+ end
62
+ end
63
+
64
+ private
65
+ def parse_uri(path)
66
+ uri = URI.parse path
67
+ raise InvalidURL, "#{path} is not a valid url" if uri.class == URI::Generic
68
+
69
+ uri.to_s
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,58 @@
1
+ require 'oily_png'
2
+ require 'monet/diff_strategy'
3
+
4
+ module Monet
5
+ class Compare
6
+ extend Forwardable
7
+
8
+ class Changeset
9
+ def initialize(base_image, pixel_array)
10
+ @base_image = base_image
11
+ @changed_pixels = pixel_array
12
+ end
13
+
14
+ def modified?
15
+ pixels_changed > 0
16
+ end
17
+
18
+ def pixels_changed
19
+ @changed_pixels.count
20
+ end
21
+
22
+ def percentage_changed
23
+ ((pixels_changed.to_f / @base_image.area.to_f) * 100).round(2)
24
+ end
25
+ end
26
+
27
+ def initialize(strategy=ColorBlend)
28
+ @strategy_class = strategy
29
+ end
30
+
31
+ def compare(base_image, new_image)
32
+ base_png = ChunkyPNG::Image.from_file(base_image)
33
+ new_png = ChunkyPNG::Image.from_file(new_image)
34
+
35
+ diff_stats = []
36
+
37
+ # TODO: make configurable
38
+ diff_strategy = @strategy_class.new(base_png, new_png)
39
+
40
+ base_png.height.times do |y|
41
+ base_png.row(y).each_with_index do |pixel, x|
42
+ diff_strategy.calculate_for_pixel(pixel, x, y)
43
+ end
44
+ end
45
+
46
+ changeset = Changeset.new(base_png, diff_strategy.score)
47
+ diff_strategy.save(diff_filename(base_image)) if changeset.modified?
48
+
49
+ changeset
50
+ end
51
+
52
+ private
53
+
54
+ def diff_filename(base_filename)
55
+ base_filename[0..-5] << "-diff.png"
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,45 @@
1
+ require 'monet/capture_map'
2
+
3
+ module Monet
4
+ class Config
5
+ MissingBaseURL = Class.new(Exception)
6
+
7
+ DEFAULT_OPTIONS = {
8
+ driver: :poltergeist,
9
+ dimensions: [1440],
10
+ map: nil,
11
+ base_url: nil,
12
+ capture_dir: "./captures"
13
+ }
14
+
15
+ attr_accessor *DEFAULT_OPTIONS.keys
16
+
17
+ def initialize(opts={})
18
+ DEFAULT_OPTIONS.each do |opt, default|
19
+ send "#{opt}=", opts[opt] || default
20
+ end
21
+ end
22
+
23
+ def self.config(&block)
24
+ cfg = new
25
+ block.call cfg
26
+ cfg
27
+ end
28
+
29
+ def base_url
30
+ raise MissingBaseURL, "Please set the base_url in the config" unless @base_url
31
+ @base_url
32
+ end
33
+
34
+ def capture_dir=(path)
35
+ @capture_dir = File.expand_path(path)
36
+ end
37
+
38
+ def map(type=:explicit, &block)
39
+ @map ||= CaptureMap.new(base_url, type)
40
+
41
+ block.call(@map) if block_given? && type == :explicit
42
+ @map
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,70 @@
1
+ module Monet
2
+ class DiffStrategy
3
+ include ChunkyPNG::Color
4
+
5
+ attr_reader :score
6
+
7
+ def initialize(base_image, diff_image)
8
+ @base_image = base_image
9
+ @diff_image = diff_image
10
+ @score = []
11
+
12
+ raise Errors::DifferentDimensions unless dimensions_match?
13
+ end
14
+
15
+ def calculate_for_pixel(pixel, x, y)
16
+ @score << [x,y] unless pixel == @diff_image[x,y]
17
+ end
18
+
19
+ def save(filename)
20
+ @output.save(filename)
21
+ end
22
+
23
+ private
24
+ def dimensions_match?
25
+ @base_image.width == @diff_image.width &&
26
+ @base_image.height == @diff_image.height
27
+ end
28
+
29
+ private
30
+ def for_color(color, *params)
31
+ send color, *params
32
+ end
33
+ end
34
+
35
+ class Grayscale < DiffStrategy
36
+ def initialize(base_image, diff_image)
37
+ super
38
+ @output = ChunkyPNG::Image.new(base_image.width, base_image.height, WHITE)
39
+ end
40
+
41
+ def calculate_for_pixel(pixel, x, y)
42
+ return if pixel == @diff_image[x,y]
43
+
44
+ rgb_colors = %w(r g b).map do |color|
45
+ for_color(color, @diff_image[x,y]) - for_color(color, pixel)
46
+ end
47
+
48
+ score = Math.sqrt(rgb_colors.reduce(0) {|memo, diff| memo += (diff ** 2) } ) / Math.sqrt(MAX ** 2 * 3)
49
+
50
+ @output[x,y] = grayscale(MAX - (score * MAX).round)
51
+ super
52
+ end
53
+ end
54
+
55
+ class ColorBlend < DiffStrategy
56
+ def initialize(base_image, diff_image)
57
+ super
58
+ @output = ChunkyPNG::Image.new(base_image.width, diff_image.width, BLACK)
59
+ end
60
+
61
+ def calculate_for_pixel(pixel, x, y)
62
+ rgb_colors = %w(r g b).map do |color|
63
+ for_color(color, pixel) + for_color(color, @diff_image[x,y]) - 2 * [for_color(color, pixel), for_color(color, @diff_image[x,y])].min
64
+ end
65
+
66
+ @output[x,y] = rgb(*rgb_colors)
67
+ super
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,8 @@
1
+ module Monet::Errors
2
+ class DifferentDimensions < StandardError
3
+ def message
4
+ "Images are different dimensions. Cannot compare accurately."
5
+ end
6
+ end
7
+
8
+ end
@@ -0,0 +1,3 @@
1
+ module Monet
2
+ VERSION = "0.1.0"
3
+ end
data/lib/monet.rb ADDED
@@ -0,0 +1,9 @@
1
+ require "monet/version"
2
+ require "monet/errors"
3
+ require "monet/capture_map"
4
+ require "monet/capture"
5
+ require "monet/compare"
6
+
7
+ module Monet
8
+
9
+ end
data/monet.gemspec ADDED
@@ -0,0 +1,42 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'monet/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "monet"
8
+ gem.version = Monet::VERSION
9
+ gem.authors = ["Luke van der Hoeven"]
10
+ gem.email = ["hungerandthirst@gmail.com"]
11
+ gem.description = %q{Monet is a web UI change comparer.}
12
+ gem.summary = %q{
13
+ Monet captures your web pages, sets up a baseline
14
+ and then ensures that future changes to either backend
15
+ or front end code leaves your UI intact. No more wondering
16
+ if CSS changes blow your UI up. Simply capture your page,
17
+ make changes, run tests and compare the diff!
18
+ }
19
+ gem.homepage = "http://plukevdh.github.com/monet"
20
+
21
+ gem.files = `git ls-files`.split($/)
22
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
23
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
24
+ gem.require_paths = ["lib"]
25
+
26
+ gem.add_dependency('rake')
27
+ gem.add_dependency('poltergeist')
28
+ gem.add_dependency('capybara')
29
+ # gem.add_dependency('chunky_png')
30
+ gem.add_dependency('oily_png')
31
+ gem.add_dependency('spidr')
32
+
33
+ if RUBY_ENGINE == 'rbx'
34
+ gem.add_dependency('rubysl')
35
+ gem.add_dependency('racc')
36
+ gem.add_dependency('json')
37
+ end
38
+
39
+ gem.add_development_dependency('rspec-given')
40
+ gem.add_development_dependency('rspec')
41
+ gem.add_development_dependency('timecop')
42
+ end
@@ -0,0 +1,116 @@
1
+ require 'spec_helper'
2
+ require 'monet/capture_map'
3
+
4
+ describe Monet::CaptureMap::PathCollection do
5
+ context "base helper" do
6
+ Given(:helper) { Monet::CaptureMap::PathCollection.new("http://google.com") }
7
+ Then { helper.paths.should == [] }
8
+ end
9
+
10
+ context "add items" do
11
+ Given(:helper) { Monet::CaptureMap::PathCollection.new("http://google.com") }
12
+ When { helper.add('path') }
13
+ Then { helper.paths.should == ['path'] }
14
+ end
15
+ end
16
+
17
+ describe Monet::CaptureMap::PathSpider do
18
+ Given(:spider) { Monet::CaptureMap::PathSpider.new("http://spider.io/") }
19
+
20
+ context "ignores" do
21
+ When(:ignores) { spider.ignores }
22
+ Then { ignores.all? {|x| x.is_a? Regexp}.should be_true }
23
+ end
24
+ end
25
+
26
+ describe Monet::CaptureMap do
27
+ context "no arguments" do
28
+ When(:result) { Monet::CaptureMap.new }
29
+ Then { result.should have_failed(ArgumentError) }
30
+ end
31
+
32
+ context "requires full url" do
33
+ When(:result) { Monet::CaptureMap.new("google.com") }
34
+ Then { result.should have_failed(Monet::CaptureMap::InvalidURL, /google.com is not a valid url/) }
35
+ end
36
+
37
+ context "requires valid url" do
38
+ When(:result) { Monet::CaptureMap.new("google") }
39
+ Then { result.should have_failed(Monet::CaptureMap::InvalidURL, /google is not a valid url/) }
40
+ end
41
+
42
+ context "with name" do
43
+ Given(:map) { Monet::CaptureMap.new("http://google.com") }
44
+ Then { map.root_url.should == "http://google.com" }
45
+ And { map.paths.should == [] }
46
+ end
47
+
48
+ context "with paths" do
49
+ Given(:map) {
50
+ Monet::CaptureMap.new("http://google.com") do |map|
51
+ map.add 'home/'
52
+ map.add 'test/new'
53
+ end
54
+ }
55
+
56
+ context "add paths" do
57
+ Then { map.root_url.should == "http://google.com" }
58
+ And { map.paths.should == ['home', 'test/new'] }
59
+ end
60
+ end
61
+
62
+ context "spider mapper", vcr: { cassette_name: "spider", record: :new_episodes } do
63
+ Given(:map) { Monet::CaptureMap.new("http://www.spider.io", :spider) }
64
+ When(:paths) { map.paths }
65
+ Then { paths.should == [
66
+ "http://www.spider.io",
67
+ "http://www.spider.io/anti-malware/",
68
+ "http://www.spider.io/viewability/",
69
+ "http://www.spider.io/press/",
70
+ "http://www.spider.io/team/",
71
+ "http://www.spider.io/blog/",
72
+ "http://www.spider.io/",
73
+ "http://www.spider.io/blog/2013/03/chameleon-botnet/",
74
+ "http://www.spider.io/blog/2013/12/cyber-criminals-defraud-display-advertisers-with-tdss/",
75
+ "http://www.spider.io/zeus",
76
+ "http://www.spider.io/blog/2013/11/how-to-defraud-display-advertisers-with-zeus/",
77
+ "http://www.spider.io/blog/2013/05/a-botnet-primer-for-display-advertisers/",
78
+ "http://www.spider.io/blog/2013/09/display-advertisers-funding-cybercriminals-since-2011/",
79
+ "http://www.spider.io/blog/2013/09/securing-the-legitimacy-of-display-ad-inventory/",
80
+ "http://www.spider.io/blog/2013/04/display-advertising-fraud-is-a-sell-side-problem/",
81
+ "http://www.spider.io/blog/2012/12/internet-explorer-data-leakage/",
82
+ "http://www.spider.io/blog/2013/08/sambreel-is-still-injecting-ads-video-advertisers-beware/",
83
+ "http://www.spider.io/blog/page/2/",
84
+ "http://www.spider.io/blog/2011/10/the-problem-with-client-side-analytics/",
85
+ "http://www.spider.io/blog/2012/12/responsible-disclosure/",
86
+ "http://www.spider.io/blog/2013/05/spider-io-granted-mrc-accreditation-for-viewable-impression-measurement/",
87
+ "http://www.spider.io/blog/2013/04/at-least-two-percent-of-monitored-display-ad-exchange-inventory-is-hidden/",
88
+ "http://www.spider.io/blog/2013/03/who-is-behind-the-chameleon-botnet/",
89
+ "http://www.spider.io/blog/page/3/",
90
+ "http://www.spider.io/blog/2013/02/which-display-ad-exchange-sells-the-highest-quality-inventory/",
91
+ "http://www.spider.io/blog/2012/12/there-are-two-ways-to-measure-ad-viewability-there-is-only-one-right-way/",
92
+ "http://www.spider.io/blog/page/4/",
93
+ "http://www.spider.io/blog/2012/12/review-of-iab-safeframe-1-0/",
94
+ "http://www.spider.io/blog/2012/10/qa-about-ad-viewability/",
95
+ "http://www.spider.io/blog/2012/10/the-first-technology-to-measure-the-viewability-of-iframed-ads-across-all-major-browsers-press-release/",
96
+ "http://www.spider.io/vSta98h",
97
+ "http://www.spider.io/blog/2012/07/join-us-for-a-tipple-at-spider-towers/",
98
+ "http://www.spider.io/blog/2012/07/startups-acquiring-startups-for-equity/",
99
+ "http://www.spider.io/blog/page/5/",
100
+ "http://www.spider.io/visibility-demo-screencast/",
101
+ "http://www.spider.io/blog/2012/07/whats-in-an-ip-address/",
102
+ "http://www.spider.io/blog/2011/12/physical-hack-day/",
103
+ "http://www.spider.io/blog/2011/11/our-first-hack-day/",
104
+ "http://www.spider.io/careers/",
105
+ "http://www.spider.io/blog/2011/10/extreme-architecting/",
106
+ "http://www.spider.io/blog/2011/09/how-to-catch-a-bot/",
107
+ "http://www.spider.io/blog/page/6/",
108
+ "http://www.spider.io/blog/2011/10/testing-javascript-with-mturk/",
109
+ "http://www.spider.io/blog/2011/10/demonstration-screencast-verifying-that-display-ads-are-visible-from-within-iframes/",
110
+ "http://www.spider.io/blog/2011/10/calling-out-to-researchersacademics/",
111
+ "http://www.spider.io/blog/page/7/"
112
+ ]}
113
+ And { paths.any? {|p| p.end_with? "css" }.should be_false }
114
+ end
115
+ end
116
+
@@ -0,0 +1,47 @@
1
+ require 'spec_helper'
2
+ require 'monet/capture'
3
+
4
+ describe Monet::Capture do
5
+ Given(:path) { File.expand_path './spec/tmp/output' }
6
+ Given(:capture_agent) { Monet::Capture.new(capture_dir: path) }
7
+
8
+ before do
9
+ Timecop.freeze
10
+ end
11
+
12
+ after do
13
+ Timecop.return
14
+
15
+ Dir.glob("#{path}/*.png").each do |file|
16
+ File.delete(file)
17
+ end
18
+ end
19
+
20
+ context "can pass config" do
21
+ context "as a hash" do
22
+ Given(:capture_agent) { Monet::Capture.new(capture_dir: path) }
23
+ When(:config) { capture_agent.instance_variable_get :@config }
24
+ Then { config.should be_a(Monet::Config) }
25
+ Then { config.capture_dir.should == path }
26
+ end
27
+
28
+ context "as a Monet::Config" do
29
+ Given(:config) { Monet::Config.new(capture_dir: path) }
30
+ Given(:capture_agent) { Monet::Capture.new(config) }
31
+ When(:final) { capture_agent.instance_variable_get :@config }
32
+ Then { final.should be_a(Monet::Config) }
33
+ Then { final.capture_dir.should == path }
34
+ end
35
+ end
36
+
37
+ context "converts name properly" do
38
+ When { capture_agent.capture('https://google.com') }
39
+ Then { File.exist?("#{path}/google_com-#{Time.now.to_i}.png").should be_true }
40
+ end
41
+
42
+ context "prepends default protocol if missing" do
43
+ When { capture_agent.capture('www.facebook.com') }
44
+ Then { File.exist?("#{path}/www_facebook_com-#{Time.now.to_i}.png").should be_true }
45
+ end
46
+
47
+ end