gaq 0.2.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.
Files changed (70) hide show
  1. data/.gitignore +18 -0
  2. data/Gemfile +15 -0
  3. data/Guardfile +23 -0
  4. data/LICENSE.txt +22 -0
  5. data/README.md +102 -0
  6. data/Rakefile +1 -0
  7. data/gaq.gemspec +22 -0
  8. data/lib/gaq.rb +3 -0
  9. data/lib/gaq/class_cache.rb +32 -0
  10. data/lib/gaq/command_language.rb +145 -0
  11. data/lib/gaq/configuration.rb +159 -0
  12. data/lib/gaq/controller_facade.rb +15 -0
  13. data/lib/gaq/controller_handle.rb +93 -0
  14. data/lib/gaq/dsl_target.rb +51 -0
  15. data/lib/gaq/dsl_target_factory.rb +64 -0
  16. data/lib/gaq/flash_commands_adapter.rb +48 -0
  17. data/lib/gaq/interprets_config.rb +13 -0
  18. data/lib/gaq/railtie.rb +40 -0
  19. data/lib/gaq/snippet_renderer.rb +44 -0
  20. data/lib/gaq/version.rb +3 -0
  21. data/spec-dummy/.rspec +1 -0
  22. data/spec-dummy/README.rdoc +261 -0
  23. data/spec-dummy/Rakefile +7 -0
  24. data/spec-dummy/app/assets/images/rails.png +0 -0
  25. data/spec-dummy/app/assets/javascripts/application.js +13 -0
  26. data/spec-dummy/app/assets/stylesheets/application.css +13 -0
  27. data/spec-dummy/app/controllers/application_controller.rb +3 -0
  28. data/spec-dummy/app/controllers/integration_spec_controller.rb +22 -0
  29. data/spec-dummy/app/helpers/application_helper.rb +2 -0
  30. data/spec-dummy/app/views/integration_spec/view.erb +0 -0
  31. data/spec-dummy/app/views/layouts/application.html.erb +16 -0
  32. data/spec-dummy/config.ru +4 -0
  33. data/spec-dummy/config/application.rb +64 -0
  34. data/spec-dummy/config/boot.rb +6 -0
  35. data/spec-dummy/config/environment.rb +5 -0
  36. data/spec-dummy/config/environments/development.rb +26 -0
  37. data/spec-dummy/config/environments/production.rb +51 -0
  38. data/spec-dummy/config/environments/test_dynamic.rb +39 -0
  39. data/spec-dummy/config/environments/test_static.rb +38 -0
  40. data/spec-dummy/config/initializers/backtrace_silencers.rb +7 -0
  41. data/spec-dummy/config/initializers/inflections.rb +15 -0
  42. data/spec-dummy/config/initializers/mime_types.rb +5 -0
  43. data/spec-dummy/config/initializers/secret_token.rb +7 -0
  44. data/spec-dummy/config/initializers/session_store.rb +8 -0
  45. data/spec-dummy/config/initializers/wrap_parameters.rb +10 -0
  46. data/spec-dummy/config/locales/en.yml +5 -0
  47. data/spec-dummy/config/routes.rb +8 -0
  48. data/spec-dummy/db/seeds.rb +7 -0
  49. data/spec-dummy/public/404.html +26 -0
  50. data/spec-dummy/public/422.html +26 -0
  51. data/spec-dummy/public/500.html +25 -0
  52. data/spec-dummy/public/favicon.ico +0 -0
  53. data/spec-dummy/public/robots.txt +5 -0
  54. data/spec-dummy/script/rails +6 -0
  55. data/spec-dummy/spec/common_spec_methods.rb +88 -0
  56. data/spec-dummy/spec/features/dynamic_config_spec.rb +21 -0
  57. data/spec-dummy/spec/features/next_request_spec.rb +27 -0
  58. data/spec-dummy/spec/features/presence_spec.rb +64 -0
  59. data/spec-dummy/spec/spec_helper.rb +41 -0
  60. data/spec/lib/gaq/class_cache_spec.rb +62 -0
  61. data/spec/lib/gaq/command_language_spec.rb +267 -0
  62. data/spec/lib/gaq/configuration_spec.rb +233 -0
  63. data/spec/lib/gaq/controller_facade_spec.rb +29 -0
  64. data/spec/lib/gaq/controller_handle_spec.rb +510 -0
  65. data/spec/lib/gaq/dsl_target_factory_spec.rb +163 -0
  66. data/spec/lib/gaq/dsl_target_spec.rb +87 -0
  67. data/spec/lib/gaq/flash_commands_adapter_spec.rb +116 -0
  68. data/spec/lib/gaq/interprets_config_spec.rb +37 -0
  69. data/spec/lib/gaq/snippet_renderer_spec.rb +60 -0
  70. metadata +159 -0
@@ -0,0 +1,163 @@
1
+ require 'gaq/dsl_target_factory'
2
+
3
+ require 'gaq/class_cache'
4
+
5
+ module Gaq
6
+ describe DslTargetFactory do
7
+ let(:flash_commands_adapter) { Object.new }
8
+ let(:immediate_commands) { Object.new }
9
+ let(:new_command_proc) { Object.new }
10
+ let(:variables) { Object.new }
11
+ let(:tracker_names) { ["tracker name", "foo"] }
12
+
13
+ let(:class_cache) do
14
+ ClassCache.new
15
+ end
16
+
17
+ let(:target_base_class) do
18
+ Class.new do
19
+ attr_reader :target_factory, :factory_token, :new_command_proc, :commands, :tracker_name
20
+
21
+ def initialize(target_factory, factory_token, new_command_proc, commands, tracker_name)
22
+ @target_factory, @factory_token, @new_command_proc, @commands, @tracker_name =
23
+ target_factory, factory_token, new_command_proc, commands, tracker_name
24
+ end
25
+
26
+ class << self
27
+ def builds_variable_commands_module(&block)
28
+ @block = block
29
+ end
30
+
31
+ def variable_commands_module(*args)
32
+ @block.call(*args)
33
+ end
34
+ end
35
+
36
+ #default implementation for examples not concerned about variables
37
+ self.builds_variable_commands_module { Module.new }
38
+ end
39
+ end
40
+
41
+ subject do
42
+ result = described_class.new(class_cache, flash_commands_adapter, immediate_commands, new_command_proc, variables, tracker_names)
43
+ result.target_base_class = target_base_class
44
+ result
45
+ end
46
+
47
+ describe "targets" do
48
+ shared_context "common initalization of target" do
49
+ it "passes itself as target factory" do
50
+ target.target_factory.should be subject
51
+ end
52
+
53
+ it "passes the new_command_proc through" do
54
+ target.new_command_proc.should be new_command_proc
55
+ end
56
+ end
57
+
58
+ shared_context immediate_commands: true do
59
+ it "passes immediate_commands as commands" do
60
+ target.commands.should be immediate_commands
61
+ end
62
+ end
63
+
64
+ shared_context flash_commands: true do
65
+ it "passes flash_commands_adapter as commands" do
66
+ target.commands.should be flash_commands_adapter
67
+ end
68
+ end
69
+
70
+ shared_context default_tracker: true do
71
+ it "passes nil as the tracker name" do
72
+ target.tracker_name.should be_nil
73
+ end
74
+ end
75
+
76
+ shared_context custom_tracker: true do
77
+ it "passes nil as the tracker name" do
78
+ target.tracker_name.should be == "tracker name"
79
+ end
80
+ end
81
+
82
+ describe "#root_target", immediate_commands: true, default_tracker: true do
83
+ let(:target) { subject.root_target }
84
+
85
+ include_context "common initalization of target"
86
+ end
87
+
88
+ let(:root_token) do
89
+ subject.root_target.factory_token
90
+ end
91
+
92
+ describe "#target_with_tracker_name (unregistered name)" do
93
+ it "complains about the unknown name" do
94
+ expect {
95
+ subject.target_with_tracker_name("bogus", root_token)
96
+ }.to raise_exception
97
+ end
98
+ end
99
+
100
+ describe "#target_with_tracker_name" do
101
+ it "tolerates both strings and symbols as tracker names" do
102
+ subject.target_with_tracker_name("tracker name", root_token).tracker_name.should be ==
103
+ subject.target_with_tracker_name(:"tracker name", root_token).tracker_name
104
+ end
105
+ end
106
+
107
+ describe "#target_with_tracker_name (token is root token)", immediate_commands: true, custom_tracker: true do
108
+ let(:target) { subject.target_with_tracker_name("tracker name", root_token) }
109
+
110
+ include_context "common initalization of target"
111
+ end
112
+
113
+ describe "#target_for_next_request (token is root token)", flash_commands: true, default_tracker: true do
114
+ let(:target) { subject.target_for_next_request(root_token) }
115
+
116
+ include_context "common initalization of target"
117
+ end
118
+
119
+ describe "#target_with_tracker_name (token is from #target_for_next_request)", flash_commands: true, custom_tracker: true do
120
+ let(:target) do
121
+ token = subject.target_for_next_request(root_token).factory_token
122
+ subject.target_with_tracker_name("tracker name", token)
123
+ end
124
+
125
+ include_context "common initalization of target"
126
+ end
127
+
128
+ describe "#target_with_tracker_name (token is from #target_with_tracker_name)", custom_tracker: true, immediate_commands: true do
129
+ let(:target) do
130
+ token = subject.target_with_tracker_name("foo", root_token).factory_token
131
+ subject.target_with_tracker_name("tracker name", token)
132
+ end
133
+
134
+ include_context "common initalization of target"
135
+ end
136
+
137
+ describe "#target_for_next_request (token is from #target_with_tracker_name)", custom_tracker: true, flash_commands: true do
138
+ let(:target) do
139
+ token = subject.target_with_tracker_name("tracker name", root_token).factory_token
140
+ subject.target_for_next_request(token)
141
+ end
142
+
143
+ include_context "common initalization of target"
144
+ end
145
+ end
146
+
147
+ describe "variables" do
148
+ let(:variables_module) { Module.new }
149
+
150
+ before do
151
+ target_base_class.builds_variable_commands_module do |*args|
152
+ args.should have(1).argument
153
+ args.first.should be variables
154
+ variables_module
155
+ end
156
+ end
157
+
158
+ it "includes the variables module into the target's class" do
159
+ subject.root_target.should be_a_kind_of(variables_module)
160
+ end
161
+ end
162
+ end
163
+ end
@@ -0,0 +1,87 @@
1
+ require 'gaq/dsl_target'
2
+
3
+ require 'gaq/configuration'
4
+ require 'gaq/command_language'
5
+
6
+ module Gaq
7
+ describe DslTarget do
8
+ let(:target_factory) { double "target factory" }
9
+ let(:factory_token) { Object.new }
10
+ let(:new_command_proc) { double "new_command_proc" }
11
+ let(:commands) { double "commands" }
12
+ let(:tracker_name) { "custom tracker name" }
13
+
14
+ shared_context with_instance_subject: true do
15
+ subject do
16
+ described_class.new(target_factory, factory_token, new_command_proc, commands, tracker_name)
17
+ end
18
+ end
19
+
20
+ def command_expecting_tracker_name
21
+ result = double
22
+ result.should_receive(:tracker_name=).with(tracker_name)
23
+ commands.should_receive(:<<).with(result).once
24
+ result
25
+ end
26
+
27
+ describe "#tracker", with_instance_subject: true do
28
+ it "delegates to target factory, passing token" do
29
+ tracker_name = "tracker name"
30
+
31
+ target_factory.should_receive(:target_with_tracker_name)
32
+ .with(tracker_name, factory_token)
33
+ .and_return(5)
34
+
35
+ subject.tracker(tracker_name).should be 5
36
+ end
37
+ end
38
+
39
+ describe "#next_request", with_instance_subject: true do
40
+ it "delegates to target factory, passing token" do
41
+ target_factory.should_receive(:target_for_next_request)
42
+ .with(factory_token)
43
+ .and_return(5)
44
+
45
+ subject.next_request.should be 5
46
+ end
47
+ end
48
+
49
+ describe "#track_event", with_instance_subject: true do
50
+ it "creates and pushes a new command" do
51
+ command = command_expecting_tracker_name
52
+
53
+ new_command_proc.should_receive(:call)
54
+ .with(:track_event, "3", "5")
55
+ .and_return(command)
56
+
57
+ subject.track_event("3", "5")
58
+ end
59
+ end
60
+
61
+ describe ".variable_commands_module" do
62
+ let(:variable) do
63
+ result = Configuration::Variable.new(4, "myvar", 2)
64
+ result.slot.should be == 4
65
+ result.name.should be == "myvar"
66
+ result.scope.should be == 2
67
+ result
68
+ end
69
+
70
+ subject do
71
+ cls = Class.new(described_class)
72
+ cls.send :include, described_class.variable_commands_module([variable])
73
+ cls.new(target_factory, factory_token, new_command_proc, commands, tracker_name)
74
+ end
75
+
76
+ it "creates a variable setter that pushes a set_custom_var command" do
77
+ command = command_expecting_tracker_name
78
+
79
+ new_command_proc.should_receive(:call)
80
+ .with(:set_custom_var, 4, "myvar", "value", 2)
81
+ .and_return(command)
82
+
83
+ subject.myvar = "value"
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,116 @@
1
+ require 'gaq/flash_commands_adapter'
2
+
3
+ module Gaq
4
+ describe FlashCommandsAdapter do
5
+ let(:flash) { {} }
6
+
7
+ let(:language) do
8
+ result = double "language"
9
+ if example.metadata[:null_conversion]
10
+ result.stub(:commands_to_flash_items) { |commands| commands }
11
+ result.stub(:commands_from_flash_items) { |commands| commands }
12
+ end
13
+ result
14
+ end
15
+ let(:controller_facade) do
16
+ result = double "controller facade"
17
+ result.stub(:flash) { flash }
18
+ result
19
+ end
20
+
21
+ subject do
22
+ described_class.new(language, controller_facade)
23
+ end
24
+
25
+ def commands_as_seen_in_flash
26
+ flash[described_class::FLASH_KEY].drop(1)
27
+ end
28
+
29
+ def version_as_seen_in_flash
30
+ flash[described_class::FLASH_KEY].first
31
+ end
32
+
33
+ def version_as_coded
34
+ described_class::FLASH_FORMAT_VERSION
35
+ end
36
+
37
+ describe "#<<" do
38
+ it "saves to flash with pushed command", null_conversion: true do
39
+ subject << 5
40
+ commands_as_seen_in_flash.should be == [5]
41
+ end
42
+
43
+ it "saves to flash with pushed commands", null_conversion: true do
44
+ subject << 5
45
+ subject << 17
46
+ commands_as_seen_in_flash.should be == [5, 17]
47
+ end
48
+
49
+ it "converts items using language.commands_to_flash_items" do
50
+ language.should_receive(:commands_to_flash_items).with([5]).and_return([:bogus])
51
+ language.should_receive(:commands_to_flash_items).with([5, 17]).and_return([:result_array])
52
+
53
+ subject << 5
54
+ subject << 17
55
+
56
+ commands_as_seen_in_flash.should be == [:result_array]
57
+ end
58
+
59
+ it "saves to flash including current format version", null_conversion: true do
60
+ subject << 5
61
+
62
+ version_as_seen_in_flash.should be == version_as_coded
63
+ end
64
+ end
65
+
66
+ describe "#commands_from_flash" do
67
+ context "with empty flash" do
68
+ it "returns something empty", null_conversion: true do
69
+ subject.commands_from_flash.should be_empty
70
+ end
71
+ end
72
+
73
+ context "with current data in flash" do
74
+ before do
75
+ flash[described_class::FLASH_KEY] = [
76
+ described_class::FLASH_FORMAT_VERSION,
77
+ 2,
78
+ 3
79
+ ]
80
+ end
81
+
82
+ it "passes flash items through language.commands_from_flash_items and returns result" do
83
+ language.should_receive(:commands_from_flash_items)
84
+ .with([2, 3]).and_return([:conversion, :result])
85
+ subject.commands_from_flash.should be == [:conversion, :result]
86
+ end
87
+ end
88
+
89
+ context "with stale data in flash" do
90
+ before do
91
+ flash[described_class::FLASH_KEY] = [
92
+ described_class::FLASH_FORMAT_VERSION - 1,
93
+ 2,
94
+ 3
95
+ ]
96
+ end
97
+
98
+ it "returns something empty", null_conversion: true do
99
+ subject.commands_from_flash.should be_empty
100
+ end
101
+ end
102
+
103
+ context "with ancient data in flash" do
104
+ before do
105
+ flash[described_class::FLASH_KEY] = [
106
+ ["_trackEvent", "foo", "bar", "baz"]
107
+ ]
108
+ end
109
+
110
+ it "returns something empty", null_conversion: true do
111
+ subject.commands_from_flash.should be_empty
112
+ end
113
+ end
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,37 @@
1
+ require 'gaq/interprets_config'
2
+ require 'gaq/controller_facade'
3
+
4
+ require 'active_support/string_inquirer'
5
+
6
+ module Gaq
7
+ describe InterpretsConfig do
8
+ subject do
9
+ Class.new do
10
+ include InterpretsConfig
11
+
12
+ public :interpret_config #make it accessible to tests
13
+ end.new
14
+ end
15
+
16
+ let(:controller) { Object.new }
17
+
18
+ let(:controller_facade) do
19
+ ControllerFacade.new(controller)
20
+ end
21
+
22
+ it "passes strings and numbers right through" do
23
+ subject.interpret_config(3, controller_facade).should be 3
24
+ subject.interpret_config("foo", controller_facade).should be == "foo"
25
+ end
26
+
27
+ it "uses controller_facade.evaluate_config_lambda for result when value is callable" do
28
+ value = double
29
+ value.stub(:call) do |arg|
30
+ arg.should be controller
31
+ :foo
32
+ end
33
+
34
+ subject.interpret_config(value, controller_facade).should be :foo
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,60 @@
1
+ require 'gaq/snippet_renderer'
2
+
3
+ module Gaq
4
+ describe SnippetRenderer do
5
+ let(:rendering_context) do
6
+ double("rendering context").tap do |context|
7
+ # no assertion?? XXX
8
+ context.stub(:javascript_tag) { |content| content }
9
+ end
10
+ end
11
+
12
+ let(:config) do
13
+ double "config"
14
+ end
15
+
16
+ let(:rails_env) do
17
+ double "rails env"
18
+ end
19
+
20
+ subject do
21
+ described_class.new(rendering_context, config, rails_env)
22
+ end
23
+
24
+ describe "#render" do
25
+ def pushed_arrays(rendered)
26
+ rendered = rendered.lines.drop_while { |line| !/^\s*_gaq.push/.match(line) }
27
+ rendered = rendered.take_while { |line| !line.empty? }
28
+
29
+ rendered = rendered.join('')[/\A\_gaq.push\((.*)\);\Z/m, 1]
30
+ rendered = rendered.split(/,\n\s*/)
31
+ rendered.map { |string| JSON.parse(string) }
32
+ end
33
+
34
+ it "renders commands segments" do
35
+ config.stub(:render_ga_js?) { false }
36
+
37
+ commands_as_segments = [
38
+ ["foo", "bar"],
39
+ ["baz", true, 3]
40
+ ]
41
+ rendered = subject.render(commands_as_segments)
42
+
43
+ pushed_arrays(rendered).should be == commands_as_segments
44
+ end
45
+
46
+ describe "snippet rendering" do
47
+ it "renders the snippet when config.render_ga_js? returns true" do
48
+ config.stub(:render_ga_js?) { true }
49
+ subject.render([]).should include('google-analytics.com')
50
+ end
51
+
52
+ it "does not render the snippet when config.render_ga_js? returns false" do
53
+ config.stub(:render_ga_js?) { false }
54
+ subject.render([]).should_not include('google-analytics.com')
55
+ end
56
+ end
57
+ end
58
+
59
+ end
60
+ end