neverland 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ script: bundle exec rspec spec
5
+ before_script:
6
+ - "export DISPLAY=:99.0"
7
+ - "sh -e /etc/init.d/xvfb start"
@@ -0,0 +1,39 @@
1
+ # Neverland
2
+
3
+ [![Build Status](https://secure.travis-ci.org/mhoran/neverland.png)](http://travis-ci.org/mhoran/neverland)
4
+
5
+ Neverland takes the pain out of testing HTML5 geolocation within your Rails
6
+ app. For more on HTML5 geolocation, check out [the
7
+ spec](http://dev.w3.org/geo/api/spec-source.html).
8
+
9
+ ## Getting Started
10
+
11
+ To install, add the following to your Gemfile:
12
+
13
+ ```ruby
14
+ gem 'neverland'
15
+ ```
16
+
17
+ In your test environment initializer, add the Neverland middleware:
18
+
19
+ ```ruby
20
+ Dummy::Application.configure do
21
+ # ...
22
+
23
+ config.middleware.use Neverland::Middleware
24
+ end
25
+ ```
26
+
27
+ By default, Neverland will mock the result of
28
+ navigator.geolocation.getCurrentLocation to latitude 42.31283, longitude
29
+ -71.114287. This can be overridden by sending the parameters
30
+ `neverland[:latitude]` and `neverland[:longitude]`. Error states can also be
31
+ triggered by setting `neverland[:error_code]`.
32
+
33
+ ## Caveats
34
+
35
+ * The implementation naievely overrides the browser's geolocation
36
+ implementation by inserting a JavaScript tag into the response).
37
+
38
+ * The middleware will treat the response body as HTML. If you're using XHTML,
39
+ this could cause issues.
@@ -1,6 +1,8 @@
1
1
  require "neverland/version"
2
2
  require 'neverland/middleware'
3
3
  require 'neverland/engine'
4
+ require 'neverland/parameter_extractor'
5
+ require 'neverland/script_injector'
4
6
 
5
7
  module Neverland
6
8
  end
@@ -7,34 +7,11 @@ module Neverland
7
7
  end
8
8
 
9
9
  def call(env)
10
- cookies = Rack::Request.new(env).cookies
11
- latitude = cookies['mock_latitude']
12
- longitude = cookies['mock_longitude']
10
+ params = ParameterExtractor.extract(env)
13
11
 
14
12
  status, headers, response = @app.call(env)
15
13
 
16
- case response
17
- when ActionDispatch::Response
18
- document = Nokogiri::HTML(response.body)
19
- head = document.at('head')
20
-
21
- script = Nokogiri::XML::Node.new('script', document)
22
- script['type'] = 'text/javascript'
23
- script['src'] = '/javascripts/neverland.js'
24
- head << script
25
-
26
- if latitude.present? && longitude.present?
27
- script = Nokogiri::XML::Node.new('script', document)
28
- script['type'] = 'text/javascript'
29
- script.inner_html = <<-SCRIPT
30
- Neverland.setLatitude(#{latitude})
31
- Neverland.setLongitude(#{longitude})
32
- SCRIPT
33
- head << script
34
- end
35
-
36
- response.body = document.to_s
37
- end
14
+ response = ScriptInjector.inject(response, params)
38
15
 
39
16
  [status, headers, response]
40
17
  end
@@ -0,0 +1,47 @@
1
+ module Neverland
2
+ class ParameterExtractor
3
+ def self.extract(env)
4
+ new(env).extract
5
+ end
6
+
7
+ def initialize(env)
8
+ @request = Rack::Request.new(env)
9
+ end
10
+
11
+ def extract
12
+ Parameters.new(latitude, longitude, error_code)
13
+ end
14
+
15
+ private
16
+
17
+ def params
18
+ @request.params
19
+ end
20
+
21
+ def neverland_params
22
+ params['neverland'] || {}
23
+ end
24
+
25
+ def latitude
26
+ neverland_params['latitude']
27
+ end
28
+
29
+ def longitude
30
+ neverland_params['longitude']
31
+ end
32
+
33
+ def error_code
34
+ neverland_params['error_code']
35
+ end
36
+ end
37
+
38
+ class Parameters
39
+ attr_reader :latitude, :longitude, :error_code
40
+
41
+ def initialize(latitude = nil, longitude = nil, error_code = nil)
42
+ @latitude = latitude
43
+ @longitude = longitude
44
+ @error_code = error_code
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,69 @@
1
+ module Neverland
2
+ class ScriptInjector
3
+ def self.inject(response, params)
4
+ new(response).inject(params)
5
+ end
6
+
7
+ def initialize(response)
8
+ case response
9
+ when ActionDispatch::Response
10
+ @injector = ActionDispatchScriptInjector.new(response)
11
+ else
12
+ @injector = NullScriptInjector.new(response)
13
+ end
14
+ end
15
+
16
+ def inject(params)
17
+ @injector.inject(params)
18
+ end
19
+ end
20
+
21
+ class ActionDispatchScriptInjector
22
+ def initialize(response)
23
+ @response = response
24
+ @document = Nokogiri::HTML(@response.body)
25
+ @head = @document.at('head')
26
+ end
27
+
28
+ def inject(params)
29
+ if @head
30
+ inject_script(:src => '/javascripts/neverland.js')
31
+
32
+ if params.error_code
33
+ inject_script(:body => <<SCRIPT)
34
+ Neverland.setError(#{params.error_code})
35
+ SCRIPT
36
+ elsif params.latitude && params.longitude
37
+ inject_script(:body => <<SCRIPT)
38
+ Neverland.setLatitude(#{params.latitude})
39
+ Neverland.setLongitude(#{params.longitude})
40
+ SCRIPT
41
+ end
42
+
43
+ @response.body = @document.to_s
44
+ end
45
+
46
+ @response
47
+ end
48
+
49
+ private
50
+
51
+ def inject_script(opts = {})
52
+ script = Nokogiri::XML::Node.new('script', @document)
53
+ script['type'] = 'text/javascript'
54
+ script['src'] = opts[:src] if opts[:src]
55
+ script.inner_html = opts[:body] if opts[:body]
56
+ @head << script
57
+ end
58
+ end
59
+
60
+ class NullScriptInjector
61
+ def initialize(response)
62
+ @response = response
63
+ end
64
+
65
+ def inject(_)
66
+ @response
67
+ end
68
+ end
69
+ end
@@ -1,3 +1,3 @@
1
1
  module Neverland
2
- VERSION = "0.0.2"
2
+ VERSION = "0.0.3"
3
3
  end
@@ -1,24 +1,50 @@
1
1
  // http://dev.w3.org/geo/api/spec-source.html
2
2
 
3
3
  navigator.geolocation.getCurrentPosition = function(success, error) {
4
- success({coords: Neverland.getCoords(), timestamp: Date.now()});
4
+ Neverland.getCoords(function(position) {
5
+ success(position)
6
+ }, function(positionError) {
7
+ error(positionError)
8
+ })
5
9
  }
6
10
 
7
- var Neverland = new function() {
8
- var _latitude = 42.31283;
9
- var _longitude = -71.114287;
11
+ var Neverland = (function() {
12
+ var _latitude = 42.31283,
13
+ _longitude = -71.114287,
14
+ _positionError
15
+
16
+ var PositionError = function(code) {
17
+ this.PERMISSION_DENIED = 1
18
+ this.POSITION_UNAVAILABLE = 2
19
+ this.TIMEOUT = 3
20
+ this.code = code
21
+ this.message = null
22
+ Object.freeze(this)
23
+ }
24
+
25
+ var Coordinates = function() {
26
+ this.latitude = _latitude,
27
+ this.longitude = _longitude,
28
+ this.altitude = null,
29
+ this.accuracy = 33,
30
+ this.altitudeAccuracy = null,
31
+ this.heading = null,
32
+ this.speed = null
33
+ Object.freeze(this)
34
+ }
35
+
36
+ var Position = function() {
37
+ this.coords = new Coordinates()
38
+ this.timestamp = Date.now()
39
+ Object.freeze(this)
40
+ }
10
41
 
11
42
  return {
12
- getCoords: function () {
13
- return {
14
- latitude: _latitude,
15
- longitude: _longitude,
16
- altitude: null,
17
- accuracy: 33,
18
- altitudeAccuracy: null,
19
- heading: null,
20
- speed: null
21
- };
43
+ getCoords: function (success, error) {
44
+ if (_positionError)
45
+ error(_positionError)
46
+ else
47
+ success(new Position())
22
48
  },
23
49
 
24
50
  setLatitude: function(latitude) {
@@ -27,6 +53,10 @@ var Neverland = new function() {
27
53
 
28
54
  setLongitude: function(longitude) {
29
55
  _longitude = longitude;
56
+ },
57
+
58
+ setError: function(code) {
59
+ _positionError = new PositionError(code);
30
60
  }
31
61
  }
32
- }
62
+ })()
@@ -0,0 +1,3 @@
1
+ class DecapitatedController < ApplicationController
2
+ layout 'decapitated'
3
+ end
@@ -0,0 +1,8 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <body>
4
+
5
+ <%= yield %>
6
+
7
+ </body>
8
+ </html>
@@ -1,9 +1,19 @@
1
- <div>Latitude: <span id="latitude"></span></div>
2
- <div>Longitude: <span id="longitude"><span></div>
1
+ <div id="success">
2
+ Latitude: <span id="latitude"></span><br>
3
+ Longitude: <span id="longitude"></span>
4
+ </div>
5
+
6
+ <div id="error"></div>
3
7
 
4
8
  <%= javascript_tag do %>
5
- navigator.geolocation.getCurrentPosition(function(success) {
6
- $('#latitude').text(success.coords.latitude);
7
- $('#longitude').text(success.coords.longitude);
9
+ navigator.geolocation.getCurrentPosition(function(position) {
10
+ $('#latitude').text(position.coords.latitude)
11
+ $('#longitude').text(position.coords.longitude)
12
+ }, function(positionError) {
13
+ switch(positionError.code) {
14
+ case positionError.PERMISSION_DENIED:
15
+ $('#error').text('Permission Denied')
16
+ $('#success').hide()
17
+ }
8
18
  });
9
19
  <% end %>
@@ -1,3 +1,4 @@
1
1
  Dummy::Application.routes.draw do
2
2
  root :to => 'test#index'
3
+ match '/decapitated' => 'decapitated#index'
3
4
  end
@@ -0,0 +1,25 @@
1
+ require 'spec_helper'
2
+
3
+ module Neverland
4
+ describe ParameterExtractor do
5
+ describe '.extract' do
6
+ let(:latitude) { '42.31283' }
7
+ let(:longitude) { '-71.114287' }
8
+ let(:error_code) { '3' }
9
+ let(:params) { { :neverland => { :latitude => latitude, :longitude => longitude, :error_code => error_code } } }
10
+ let(:env) { Rack::MockRequest.env_for('/bananas', :params => params) }
11
+
12
+ it 'should return the provided latitude' do
13
+ ParameterExtractor.extract(env).latitude.should == latitude
14
+ end
15
+
16
+ it 'should return the provided longitude' do
17
+ ParameterExtractor.extract(env).longitude.should == longitude
18
+ end
19
+
20
+ it 'should return the provided error code' do
21
+ ParameterExtractor.extract(env).error_code.should == error_code
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,27 @@
1
+ require 'spec_helper'
2
+
3
+ module Neverland
4
+ describe ScriptInjector do
5
+ describe '.inject' do
6
+ context 'with an ActionDispatch::Response' do
7
+ let(:parameters) { Parameters.new }
8
+ let(:response) { ActionDispatch::Response.new(200, {}, ['<html><head></head></html>']) }
9
+
10
+ it 'should insert the mock JavaScript into the head' do
11
+ injected_response = ScriptInjector.inject(response, parameters)
12
+ Nokogiri::HTML(injected_response.body).css('head script[src="/javascripts/neverland.js"]').should_not be_empty
13
+ end
14
+ end
15
+
16
+ context 'with a Rack::Response' do
17
+ let(:parameters) { Parameters.new }
18
+ let(:response) { Rack::Response.new(['<html><head></head></html>'], 200, {}) }
19
+
20
+ it 'should not insert the mock JavaScript into the head' do
21
+ injected_response = ScriptInjector.inject(response, parameters)
22
+ Nokogiri::HTML(injected_response.body.first).css('head script[src="/javascripts/neverland.js"]').should be_empty
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -1,6 +1,24 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe 'neverland' do
4
+ it "should not insert the mock JavaScript on an unsuccessful response" do
5
+ lambda { visit '/nonexistent' }.should raise_error(ActionController::RoutingError)
6
+
7
+ page.should_not have_selector('head script[src="/javascripts/neverland.js"]')
8
+ end
9
+
10
+ it "should not raise an error if the response has no head" do
11
+ lambda { visit '/decapitated' }.should_not raise_error
12
+
13
+ page.should_not have_selector('head script[src="/javascripts/neverland.js"]')
14
+ end
15
+
16
+ it 'should not insert the mock JavaScript if the response has no head' do
17
+ visit '/decapitated'
18
+
19
+ page.should_not have_selector('head script[src="/javascripts/neverland.js"]')
20
+ end
21
+
4
22
  it 'should insert the mock JavaScript on a successful response' do
5
23
  visit '/'
6
24
 
@@ -16,23 +34,17 @@ describe 'neverland' do
16
34
  end
17
35
 
18
36
  it 'should mock the provided coordinates on a successful response', :js => true do
19
- visit '/'
20
- cookie 'mock_latitude', '37.2630556'
21
- cookie 'mock_longitude', '-115.7930198'
22
-
23
- visit '/'
37
+ latitude = '37.2630556'
38
+ longitude = '-115.7930198'
39
+ visit root_path(:neverland => {:latitude => latitude, :longitude => longitude})
24
40
 
25
- find('#latitude').should have_content('37.2630556')
26
- find('#longitude').should have_content('-115.7930198')
41
+ find('#latitude').should have_content(latitude)
42
+ find('#longitude').should have_content(longitude)
27
43
  end
28
44
 
29
- it "should not mock on an error response" do
30
- lambda { visit '/nonexistent' }.should raise_error(ActionController::RoutingError)
45
+ it 'should display Permission Denied when the location request is denied', :js => true do
46
+ visit root_path(:neverland => {:error_code => 1})
31
47
 
32
- page.should_not have_selector('head script[src="/javascripts/neverland.js"]')
48
+ find('#error').should have_content('Permission Denied')
33
49
  end
34
50
  end
35
-
36
- def cookie(name, value)
37
- Capybara.current_session.driver.browser.manage.add_cookie(:name => name, :value => value)
38
- end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: neverland
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.3
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-03-24 00:00:00.000000000 Z
12
+ date: 2012-03-26 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: nokogiri
16
- requirement: &16437000 !ruby/object:Gem::Requirement
16
+ requirement: &9117980 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: '0'
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *16437000
24
+ version_requirements: *9117980
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: rspec-rails
27
- requirement: &16436500 !ruby/object:Gem::Requirement
27
+ requirement: &9117340 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ! '>='
@@ -32,10 +32,10 @@ dependencies:
32
32
  version: '0'
33
33
  type: :development
34
34
  prerelease: false
35
- version_requirements: *16436500
35
+ version_requirements: *9117340
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: tzinfo
38
- requirement: &16435920 !ruby/object:Gem::Requirement
38
+ requirement: &9116640 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ! '>='
@@ -43,10 +43,10 @@ dependencies:
43
43
  version: '0'
44
44
  type: :development
45
45
  prerelease: false
46
- version_requirements: *16435920
46
+ version_requirements: *9116640
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: capybara
49
- requirement: &16435360 !ruby/object:Gem::Requirement
49
+ requirement: &9116000 !ruby/object:Gem::Requirement
50
50
  none: false
51
51
  requirements:
52
52
  - - ! '>='
@@ -54,10 +54,10 @@ dependencies:
54
54
  version: '0'
55
55
  type: :development
56
56
  prerelease: false
57
- version_requirements: *16435360
57
+ version_requirements: *9116000
58
58
  - !ruby/object:Gem::Dependency
59
59
  name: jquery-rails
60
- requirement: &16434720 !ruby/object:Gem::Requirement
60
+ requirement: &9115500 !ruby/object:Gem::Requirement
61
61
  none: false
62
62
  requirements:
63
63
  - - ! '>='
@@ -65,7 +65,7 @@ dependencies:
65
65
  version: '0'
66
66
  type: :development
67
67
  prerelease: false
68
- version_requirements: *16434720
68
+ version_requirements: *9115500
69
69
  description: Ever need to test geolocation in your Rails app? This gem makes it easy.
70
70
  email:
71
71
  - matthew.horan@livestream.com
@@ -75,12 +75,16 @@ extra_rdoc_files: []
75
75
  files:
76
76
  - .gitignore
77
77
  - .rspec
78
+ - .travis.yml
78
79
  - Gemfile
79
80
  - MIT-LICENSE
81
+ - README.md
80
82
  - Rakefile
81
83
  - lib/neverland.rb
82
84
  - lib/neverland/engine.rb
83
85
  - lib/neverland/middleware.rb
86
+ - lib/neverland/parameter_extractor.rb
87
+ - lib/neverland/script_injector.rb
84
88
  - lib/neverland/version.rb
85
89
  - neverland.gemspec
86
90
  - public/javascripts/neverland.js
@@ -89,9 +93,12 @@ files:
89
93
  - spec/dummy/app/assets/javascripts/application.js
90
94
  - spec/dummy/app/assets/stylesheets/application.css
91
95
  - spec/dummy/app/controllers/application_controller.rb
96
+ - spec/dummy/app/controllers/decapitated_controller.rb
92
97
  - spec/dummy/app/controllers/test_controller.rb
93
98
  - spec/dummy/app/helpers/application_helper.rb
99
+ - spec/dummy/app/views/decapitated/index.html.erb
94
100
  - spec/dummy/app/views/layouts/application.html.erb
101
+ - spec/dummy/app/views/layouts/decapitated.html.erb
95
102
  - spec/dummy/app/views/test/index.html.erb
96
103
  - spec/dummy/config.ru
97
104
  - spec/dummy/config/application.rb
@@ -106,6 +113,8 @@ files:
106
113
  - spec/dummy/public/500.html
107
114
  - spec/dummy/public/favicon.ico
108
115
  - spec/dummy/script/rails
116
+ - spec/neverland/parameter_extractor_spec.rb
117
+ - spec/neverland/script_injector_spec.rb
109
118
  - spec/requests/neverland_spec.rb
110
119
  - spec/spec_helper.rb
111
120
  homepage: ''