bacchanalytics 0.1.9
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +17 -0
- data/.travis.yml +11 -0
- data/Appraisals +14 -0
- data/CHANGELOG.md +53 -0
- data/Gemfile +3 -0
- data/MIT-LICENSE +20 -0
- data/README.md +94 -0
- data/Rakefile +17 -0
- data/bacchanalytics.gemspec +27 -0
- data/gemfiles/2.3.gemfile +8 -0
- data/gemfiles/2.3.gemfile.lock +31 -0
- data/gemfiles/3.0.gemfile +8 -0
- data/gemfiles/3.0.gemfile.lock +33 -0
- data/gemfiles/3.1.gemfile +8 -0
- data/gemfiles/3.1.gemfile.lock +33 -0
- data/init.rb +1 -0
- data/install.rb +1 -0
- data/lib/bacchanalytics.rb +5 -0
- data/lib/bacchanalytics/adwords_conversion.rb +95 -0
- data/lib/bacchanalytics/analytics.rb +62 -0
- data/lib/bacchanalytics/google_analytics.rb +74 -0
- data/lib/bacchanalytics/version.rb +3 -0
- data/lib/bacchanalytics/website_optimizer.rb +204 -0
- data/test/integration/bacchanalytics_integration_test.rb +64 -0
- data/test/test_helper.rb +8 -0
- data/test/unit/adwords_conversion_test.rb +96 -0
- data/test/unit/bacchanalytics_test.rb +70 -0
- data/test/unit/website_optimizer_test.rb +63 -0
- metadata +190 -0
@@ -0,0 +1,62 @@
|
|
1
|
+
module Bacchanalytics
|
2
|
+
class Analytics
|
3
|
+
include GoogleAnalytics::Base
|
4
|
+
include GoogleAnalytics::TrackingCode
|
5
|
+
|
6
|
+
def initialize(app, options = {})
|
7
|
+
@app = app
|
8
|
+
@web_property_id = options[:web_property_id] || "UA-XXXXX-X"
|
9
|
+
@domain_name = options[:domain_name]
|
10
|
+
@ignored_organic = options[:ignored_organic]
|
11
|
+
@skip_ga_src = options[:skip_ga_src] || false
|
12
|
+
end
|
13
|
+
|
14
|
+
def call(env)
|
15
|
+
status, headers, response = @app.call(env)
|
16
|
+
|
17
|
+
if should_instrument?(headers) && (source = response_source(response))
|
18
|
+
@skip_ga_src = true if env["bacchanlytics.loaded_ga_src"]
|
19
|
+
tracking_code = google_analytics_tracking_code(@web_property_id, @domain_name)
|
20
|
+
|
21
|
+
env["bacchanalytics.loaded_ga_src"] = true
|
22
|
+
new_body = source.sub /<[hH][eE][aA][dD]\s*>/, "<head>\n\n#{tracking_code}"
|
23
|
+
headers["Content-Length"] = new_body.length.to_s
|
24
|
+
Rack::Response.new(new_body, status, headers).finish
|
25
|
+
else
|
26
|
+
[status, headers, response]
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.track_page_view_code(page)
|
31
|
+
"_gaq.push(['_trackPageview', '#{page}'])"
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.track_page_view_script(page)
|
35
|
+
<<-SCRIPT
|
36
|
+
<script type="text/javascript">
|
37
|
+
#{track_page_view_code(page)};
|
38
|
+
</script>
|
39
|
+
SCRIPT
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.track_event(category, action, opt_label = nil, opt_value = nil, options = {})
|
43
|
+
if opt_label.blank? || opt_value.blank?
|
44
|
+
track_event_code = "_gaq.push(['_trackEvent', '#{category}', '#{action}'])"
|
45
|
+
else
|
46
|
+
track_event_code = "_gaq.push(['_trackEvent', '#{category}', '#{action}', '#{opt_label}', '#{opt_value}'])"
|
47
|
+
end
|
48
|
+
|
49
|
+
timeout = options[:timeout] rescue nil
|
50
|
+
if timeout
|
51
|
+
"#{track_event_code};setTimeout('void(0)', #{timeout});"
|
52
|
+
else
|
53
|
+
track_event_code
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
def load_ga_src
|
59
|
+
@skip_ga_src ? "" : super
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
module Bacchanalytics
|
2
|
+
module GoogleAnalytics
|
3
|
+
|
4
|
+
module Base
|
5
|
+
# Return the javascript code used to load google analytics code
|
6
|
+
def load_ga_src
|
7
|
+
<<-SCRIPT
|
8
|
+
(function() {
|
9
|
+
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
|
10
|
+
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
|
11
|
+
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
|
12
|
+
})();
|
13
|
+
SCRIPT
|
14
|
+
end
|
15
|
+
|
16
|
+
# headers["Content-Type"] will be nil if the status of the response is 304 (Not Modified)
|
17
|
+
# From the HTTP Status Code Definitions:
|
18
|
+
# If the client has performed a conditional GET request and access is allowed,
|
19
|
+
# but the document has not been modified, the server SHOULD respond with this status code.
|
20
|
+
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
|
21
|
+
def should_instrument?(headers)
|
22
|
+
!headers["Content-Type"].nil? && (headers["Content-Type"].include? "text/html")
|
23
|
+
end
|
24
|
+
|
25
|
+
def response_source(response)
|
26
|
+
source = nil
|
27
|
+
response.each { |fragment| (source) ? (source << fragment) : (source = fragment) }
|
28
|
+
source
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
module TrackingCode
|
33
|
+
# Construct the new asynchronous version of the Google Analytics code.
|
34
|
+
# http://code.google.com/apis/analytics/docs/tracking/asyncTracking.html
|
35
|
+
def google_analytics_tracking_code(web_property_id, domain_name = nil)
|
36
|
+
<<-SCRIPT
|
37
|
+
<script type="text/javascript">
|
38
|
+
|
39
|
+
var _gaq = _gaq || [];
|
40
|
+
_gaq.push(['_setAccount', '#{web_property_id}']);
|
41
|
+
if ('#{domain_name}' !== ''){
|
42
|
+
_gaq.push(['_setDomainName', '#{domain_name}']);
|
43
|
+
}
|
44
|
+
#{ignored_organic_script}
|
45
|
+
_gaq.push(['_trackPageview']);
|
46
|
+
|
47
|
+
#{load_ga_src}
|
48
|
+
</script>
|
49
|
+
SCRIPT
|
50
|
+
end
|
51
|
+
|
52
|
+
def ignored_organic_script
|
53
|
+
script = ""
|
54
|
+
|
55
|
+
begin
|
56
|
+
if @ignored_organic.is_a?(Array)
|
57
|
+
@ignored_organic.each { |item|
|
58
|
+
script << <<-CODE
|
59
|
+
_gaq.push(['_addIgnoredOrganic', '#{item}']);
|
60
|
+
CODE
|
61
|
+
}
|
62
|
+
elsif @ignored_organic.is_a?(String)
|
63
|
+
script << <<-CODE
|
64
|
+
_gaq.push(['_addIgnoredOrganic', '#{@ignored_organic}']);
|
65
|
+
CODE
|
66
|
+
end
|
67
|
+
rescue
|
68
|
+
end
|
69
|
+
|
70
|
+
script
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,204 @@
|
|
1
|
+
module Bacchanalytics
|
2
|
+
|
3
|
+
module WebsiteOptimizerTrackingCode
|
4
|
+
|
5
|
+
# Construct the Google WebSite Optimizer tracking code.
|
6
|
+
def website_optimizer_tracking_code(page, account_id, ab)
|
7
|
+
if page.blank? || account_id.blank? || !ab_options_valid?(ab)
|
8
|
+
return ""
|
9
|
+
end
|
10
|
+
|
11
|
+
gotc = ""
|
12
|
+
begin
|
13
|
+
goal_keys = [] # Multiple tests and the same goal
|
14
|
+
for key in ab.keys do
|
15
|
+
value = ab[key]
|
16
|
+
next unless value[:locales].nil? || [value[:locales]].flatten.map(&:to_s).include?(I18n.locale.to_s)
|
17
|
+
|
18
|
+
a = value[:a].to_a
|
19
|
+
b = value[:b].to_a
|
20
|
+
g = value[:goal].to_a
|
21
|
+
|
22
|
+
# Check the requested page, to include the A, B or goal tracking code.
|
23
|
+
if a.include?(page)
|
24
|
+
gotc = a_tracking_code(account_id, key)
|
25
|
+
break
|
26
|
+
elsif b.include?(page)
|
27
|
+
gotc = b_tracking_code(account_id, key)
|
28
|
+
break
|
29
|
+
elsif g.include?(page)
|
30
|
+
goal_keys << key
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
if gotc.blank?
|
35
|
+
gotc = goal_tracking_code(account_id, goal_keys)
|
36
|
+
end
|
37
|
+
|
38
|
+
rescue Exception => e
|
39
|
+
logger.debug "#{e.message}"
|
40
|
+
gotc = ""
|
41
|
+
end
|
42
|
+
|
43
|
+
gotc
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
# Returns the A (original page) website optimizer code. Look at the html comments before
|
49
|
+
# the javascript code: use them to pass W3C validations.
|
50
|
+
def a_tracking_code(account_id, track_page_id)
|
51
|
+
result = <<-SCRIPT
|
52
|
+
<!-- Google Website Optimizer Control Script -->
|
53
|
+
<script type="text/javascript">
|
54
|
+
<!--
|
55
|
+
function utmx_section(){}function utmx(){}
|
56
|
+
(function(){var k='#{track_page_id}',d=document,l=d.location,c=d.cookie;function f(n){
|
57
|
+
if(c){var i=c.indexOf(n+'=');if(i>-1){var j=c.indexOf(';',i);return escape(c.substring(i+n.
|
58
|
+
length+1,j<0?c.length:j))}}}var x=f('__utmx'),xx=f('__utmxx'),h=l.hash;
|
59
|
+
d.write('<sc'+'ript src="'+
|
60
|
+
'http'+(l.protocol=='https:'?'s://ssl':'://www')+'.google-analytics.com'
|
61
|
+
+'/siteopt.js?v=1&utmxkey='+k+'&utmx='+(x?x:'')+'&utmxx='+(xx?xx:'')+'&utmxtime='
|
62
|
+
+new Date().valueOf()+(h?'&utmxhash='+escape(h.substr(1)):'')+
|
63
|
+
'" type="text/javascript" charset="utf-8"></sc'+'ript>')})();
|
64
|
+
-->
|
65
|
+
</script>
|
66
|
+
<script type="text/javascript">utmx("url",'A/B');</script>
|
67
|
+
<!-- End of Google Website Optimizer Control Script -->
|
68
|
+
|
69
|
+
<!-- Google Website Optimizer Tracking Script -->
|
70
|
+
<script type="text/javascript">
|
71
|
+
var _gaq = _gaq || [];
|
72
|
+
_gaq.push(['gwo._setAccount', '#{account_id}']);
|
73
|
+
_gaq.push(['gwo._trackPageview', '/#{track_page_id}/test']);
|
74
|
+
#{load_ga_src}
|
75
|
+
</script>
|
76
|
+
<!-- End of Google Website Optimizer Tracking Script -->
|
77
|
+
SCRIPT
|
78
|
+
|
79
|
+
return result
|
80
|
+
end
|
81
|
+
|
82
|
+
# Returns the B (variation page) website optimizer code.
|
83
|
+
def b_tracking_code(account_id, track_page_id)
|
84
|
+
<<-SCRIPT
|
85
|
+
<!-- Google Website Optimizer Tracking Script -->
|
86
|
+
<script type="text/javascript">
|
87
|
+
var _gaq = _gaq || [];
|
88
|
+
_gaq.push(['gwo._setAccount', '#{account_id}']);
|
89
|
+
_gaq.push(['gwo._trackPageview', '/#{track_page_id}/test']);
|
90
|
+
#{load_ga_src}
|
91
|
+
</script>
|
92
|
+
<!-- End of Google Website Optimizer Tracking Script -->
|
93
|
+
SCRIPT
|
94
|
+
end
|
95
|
+
|
96
|
+
# Returns the goal (conversion page) website optimizer code.
|
97
|
+
def goal_tracking_code(account_id, track_page_ids = [])
|
98
|
+
track_page_ids = Array(track_page_ids)
|
99
|
+
|
100
|
+
<<-SCRIPT
|
101
|
+
<!-- Google Website Optimizer Tracking Script -->
|
102
|
+
<script type="text/javascript">
|
103
|
+
var _gaq = _gaq || [];
|
104
|
+
_gaq.push(['gwo._setAccount', '#{account_id}']);
|
105
|
+
#{track_page_ids.map { |id| "_gaq.push(['gwo._trackPageview', '/#{id}/goal']);" }.join("\n")}
|
106
|
+
#{load_ga_src}
|
107
|
+
</script>
|
108
|
+
<!-- End of Google Website Optimizer Tracking Script -->
|
109
|
+
SCRIPT
|
110
|
+
end
|
111
|
+
|
112
|
+
# Are the options of the tests valid?
|
113
|
+
def ab_options_valid?(ab_options = {})
|
114
|
+
return false if ab_options.empty?
|
115
|
+
|
116
|
+
unless ab_options_is_complete?(ab_options)
|
117
|
+
logger.info "**********************************************************************************************************"
|
118
|
+
logger.info "[Bachanalytics] You need to specify the :a, b: and :goal options for your WebSiteOptimizer tests, aborting"
|
119
|
+
logger.info "**********************************************************************************************************"
|
120
|
+
return false
|
121
|
+
end
|
122
|
+
|
123
|
+
unless uniq_goals?(ab_options)
|
124
|
+
logger.info "**************************************************************************************************"
|
125
|
+
logger.info "[Bachanalytics] You can't specify a :goal as part of the your WebSiteOptimizer tests, not applying"
|
126
|
+
logger.info "**************************************************************************************************"
|
127
|
+
return false
|
128
|
+
end
|
129
|
+
|
130
|
+
true
|
131
|
+
end
|
132
|
+
|
133
|
+
# Each A/B test need at least: :a, :b and :goal keys
|
134
|
+
def ab_options_is_complete?(ab_options = {})
|
135
|
+
result = true
|
136
|
+
|
137
|
+
ab_options.values.each do |value|
|
138
|
+
# Has :a, :b and :goal options
|
139
|
+
result = value.keys.size >= 3 && value.key?(:a) && value.key?(:b) && value.key?(:goal)
|
140
|
+
break if result == false
|
141
|
+
end
|
142
|
+
|
143
|
+
result
|
144
|
+
end
|
145
|
+
|
146
|
+
# One goal option must not be an a or b option
|
147
|
+
# A conversion (goal) option can't ba an a or b option (a original or test option)
|
148
|
+
def uniq_goals?(ab_options = {})
|
149
|
+
uniq_goals, test_pages = [], []
|
150
|
+
|
151
|
+
ab_options.values.each do |value|
|
152
|
+
uniq_goals << value[:goal]
|
153
|
+
test_pages << value[:a]
|
154
|
+
test_pages << value[:b]
|
155
|
+
end
|
156
|
+
|
157
|
+
uniq_goals.flatten!.uniq!
|
158
|
+
test_pages.flatten!.uniq!
|
159
|
+
|
160
|
+
(uniq_goals & test_pages).size == 0
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
class WebsiteOptimizer
|
165
|
+
include GoogleAnalytics::Base
|
166
|
+
include WebsiteOptimizerTrackingCode
|
167
|
+
|
168
|
+
def initialize(app, options = {})
|
169
|
+
@app = app
|
170
|
+
@account_id = options[:account_id] || "UA-XXXXX-X"
|
171
|
+
@ab = options[:ab] || {}
|
172
|
+
@skip_ga_src = options[:skip_ga_src] || false
|
173
|
+
end
|
174
|
+
|
175
|
+
def call(env)
|
176
|
+
status, headers, response = @app.call(env)
|
177
|
+
|
178
|
+
if should_instrument?(headers) && (source = response_source(response))
|
179
|
+
page = env['REQUEST_URI']
|
180
|
+
page.gsub!(/\?.*/, '') if page #remove url parameters
|
181
|
+
|
182
|
+
@skip_ga_src = true if env["bacchanalytics.loaded_ga_src"]
|
183
|
+
tracking_code = website_optimizer_tracking_code(page, @account_id, @ab)
|
184
|
+
return [status, headers, response] if tracking_code.to_s == ""
|
185
|
+
|
186
|
+
env["bacchanalytics.loaded_ga_src"] = true
|
187
|
+
new_body = source.sub /<[hH][eE][aA][dD]\s*>/, "<head>\n\n#{tracking_code}"
|
188
|
+
headers["Content-Length"] = new_body.length.to_s
|
189
|
+
Rack::Response.new(new_body, status, headers).finish
|
190
|
+
else
|
191
|
+
[status, headers, response]
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
private
|
196
|
+
def logger
|
197
|
+
defined?(Rails.logger) ? Rails.logger : Logger.new($stderr)
|
198
|
+
end
|
199
|
+
|
200
|
+
def load_ga_src
|
201
|
+
@skip_ga_src ? "" : super
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'bacchanalytics'
|
3
|
+
|
4
|
+
ENV['RACK_ENV'] = 'test'
|
5
|
+
|
6
|
+
class BacchanalyticsIntegrationTest < Test::Unit::TestCase
|
7
|
+
include Rack::Test::Methods
|
8
|
+
include Bacchanalytics::GoogleAnalytics::Base
|
9
|
+
|
10
|
+
def app
|
11
|
+
response = Rack::Response.new(HTML_DOCUMENT)
|
12
|
+
mock_app = lambda do |env|
|
13
|
+
[200, {'Content-Type' => 'text/html'}, response]
|
14
|
+
end
|
15
|
+
|
16
|
+
Rack::Builder.new do
|
17
|
+
use Bacchanalytics::WebsiteOptimizer, {:account_id => 'UA-20891683-1',
|
18
|
+
:ab => {'1924712694' => {:a => ["/"],
|
19
|
+
:b => ["/home"],
|
20
|
+
:goal => ["/welcome"]}}
|
21
|
+
}
|
22
|
+
use Bacchanalytics::Analytics, :web_property_id => "UA-12345-6"
|
23
|
+
run mock_app
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_must_include_google_analytics_src_only_once
|
28
|
+
get "/", {}, {"REQUEST_URI" => "/"}
|
29
|
+
|
30
|
+
times = 0
|
31
|
+
last_response.body.gsub(load_ga_src){|m| times += 1}
|
32
|
+
|
33
|
+
assert_equal 1, times
|
34
|
+
end
|
35
|
+
|
36
|
+
HTML_DOCUMENT = <<-HTML
|
37
|
+
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
|
38
|
+
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
39
|
+
|
40
|
+
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
41
|
+
<head>
|
42
|
+
<meta http-equiv="content-type" content="text/html;charset=UTF-8" />
|
43
|
+
<title>Listing things</title>
|
44
|
+
</head>
|
45
|
+
<body>
|
46
|
+
|
47
|
+
<h1>Listing things</h1>
|
48
|
+
|
49
|
+
<table>
|
50
|
+
<tr>
|
51
|
+
<th>Name</th>
|
52
|
+
</tr>
|
53
|
+
<tr>
|
54
|
+
<td>Thing 1</td>
|
55
|
+
</tr>
|
56
|
+
<tr>
|
57
|
+
<td>Thing 2</td>
|
58
|
+
</tr>
|
59
|
+
</table>
|
60
|
+
|
61
|
+
</body>
|
62
|
+
</html>
|
63
|
+
HTML
|
64
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'bacchanalytics'
|
3
|
+
|
4
|
+
ENV['RACK_ENV'] = 'test'
|
5
|
+
|
6
|
+
class AdwordsConversionTest < Test::Unit::TestCase
|
7
|
+
include Rack::Test::Methods
|
8
|
+
|
9
|
+
WEB_PROPERTY_ID = "UA-12345-6"
|
10
|
+
|
11
|
+
def app
|
12
|
+
response = Rack::Response.new(HTML_DOCUMENT)
|
13
|
+
mock_app = lambda do |env|
|
14
|
+
[200, {'Content-Type' => 'text/html'}, response]
|
15
|
+
end
|
16
|
+
@options ||= [
|
17
|
+
{:id => '1062298921', :label=>'oIiDCJe__QEQqcrF-gM', :language=>'es', :format=>3, :value=>3.5, :description=>'',
|
18
|
+
:pages=>["/welcome", "/en/welcome", "/es/welcome", "/ca/welcome", "/eu/welcome"]
|
19
|
+
}
|
20
|
+
]
|
21
|
+
Bacchanalytics::AdwordsConversion.new(mock_app, @options)
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_must_not_include_any_code_if_no_page
|
25
|
+
get "/", {}, {'REQUEST_URI' => nil}
|
26
|
+
|
27
|
+
assert_equal last_response.body, HTML_DOCUMENT
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_must_not_include_any_code_if_the_page_is_not_a_goal
|
31
|
+
get "/", {}, {'REQUEST_URI' => nil}
|
32
|
+
|
33
|
+
assert_equal last_response.body, HTML_DOCUMENT
|
34
|
+
end
|
35
|
+
|
36
|
+
def test_must_include_conversion_code_if_the_page_is_a_goal
|
37
|
+
get "/welcome", {}, {'REQUEST_URI' => "/welcome"}
|
38
|
+
|
39
|
+
assert last_response.body.include?("Google Code for Conversion Page "), last_response.body
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_must_include_conversion_values_in_the_code
|
43
|
+
get "/welcome", {}, {'REQUEST_URI' => "/welcome"}
|
44
|
+
|
45
|
+
conversion = @options.first
|
46
|
+
assert last_response.body.include?("google_conversion_id = #{conversion[:id]}"), last_response.body
|
47
|
+
assert last_response.body.include?("google_conversion_language = \"#{conversion[:language]}\""), last_response.body
|
48
|
+
assert last_response.body.include?("google_conversion_format = \"#{conversion[:format]}\""), last_response.body
|
49
|
+
assert last_response.body.include?("google_conversion_label = \"#{conversion[:label]}\""), last_response.body
|
50
|
+
assert last_response.body.include?("google_conversion_value = #{conversion[:value]}"), last_response.body
|
51
|
+
end
|
52
|
+
|
53
|
+
def text_must_not_include_any_code_if_not_a_valid_conversion
|
54
|
+
invalid_conversion = {}
|
55
|
+
@options = [invalid_conversion]
|
56
|
+
get "/", {}, {'REQUEST_URI' => "/request_uri"}
|
57
|
+
|
58
|
+
assert_equal last_response.body, HTML_DOCUMENT
|
59
|
+
end
|
60
|
+
|
61
|
+
def test_must_not_include_any_code_if_no_conversion
|
62
|
+
@options = []
|
63
|
+
get "/", {}, {'REQUEST_URI' => "/request_uri"}
|
64
|
+
|
65
|
+
assert_equal last_response.body, HTML_DOCUMENT
|
66
|
+
end
|
67
|
+
|
68
|
+
HTML_DOCUMENT = <<-HTML
|
69
|
+
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
|
70
|
+
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
71
|
+
|
72
|
+
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
73
|
+
<head>
|
74
|
+
<meta http-equiv="content-type" content="text/html;charset=UTF-8" />
|
75
|
+
<title>Listing things</title>
|
76
|
+
</head>
|
77
|
+
<body>
|
78
|
+
|
79
|
+
<h1>Listing things</h1>
|
80
|
+
|
81
|
+
<table>
|
82
|
+
<tr>
|
83
|
+
<th>Name</th>
|
84
|
+
</tr>
|
85
|
+
<tr>
|
86
|
+
<td>Thing 1</td>
|
87
|
+
</tr>
|
88
|
+
<tr>
|
89
|
+
<td>Thing 2</td>
|
90
|
+
</tr>
|
91
|
+
</table>
|
92
|
+
|
93
|
+
</body>
|
94
|
+
</html>
|
95
|
+
HTML
|
96
|
+
end
|