gaq 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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