ama_layout 6.3.0.pre → 6.10.0.pre

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +2 -0
  3. data/ama_layout.gemspec +22 -20
  4. data/app/assets/javascripts/ama_layout/desktop/foundation-custom.js +10 -8
  5. data/app/assets/javascripts/ama_layout/desktop/index.js +1 -0
  6. data/app/assets/javascripts/ama_layout/notifications.coffee +17 -0
  7. data/app/controllers/ama_layout/api/v1/notifications_controller.rb +18 -0
  8. data/app/helpers/ama_layout_path_helper.rb +4 -4
  9. data/app/views/ama_layout/_notification.html.erb +10 -0
  10. data/app/views/ama_layout/_notification_sidebar.html.erb +22 -0
  11. data/app/views/ama_layout/_notifications.html.erb +6 -0
  12. data/app/views/ama_layout/_siteheader.html.erb +8 -3
  13. data/config/routes.rb +9 -0
  14. data/lib/ama_layout.rb +27 -20
  15. data/lib/ama_layout/decorators/navigation_decorator.rb +47 -0
  16. data/lib/ama_layout/decorators/notification_decorator.rb +45 -0
  17. data/lib/ama_layout/navigation.rb +3 -0
  18. data/lib/ama_layout/navigation.yml +58 -6
  19. data/lib/ama_layout/navigation_helper.rb +31 -0
  20. data/lib/ama_layout/notification.rb +89 -0
  21. data/lib/ama_layout/notification_scrubber.rb +13 -0
  22. data/lib/ama_layout/notification_set.rb +140 -0
  23. data/lib/ama_layout/notifications.rb +73 -0
  24. data/lib/ama_layout/notifications/abstract_store.rb +17 -0
  25. data/lib/ama_layout/notifications/redis_store.rb +38 -0
  26. data/lib/ama_layout/version.rb +1 -1
  27. data/spec/ama_layout/controllers/ama_layout/api/v1/notifications_controller_spec.rb +13 -0
  28. data/spec/ama_layout/decorators/navigation_decorator_spec.rb +121 -2
  29. data/spec/ama_layout/decorators/notification_decorator_spec.rb +57 -0
  30. data/spec/ama_layout/navigation_helper_spec.rb +63 -0
  31. data/spec/ama_layout/navigation_spec.rb +13 -43
  32. data/spec/ama_layout/notification_scrubber_spec.rb +10 -0
  33. data/spec/ama_layout/notification_set_spec.rb +281 -0
  34. data/spec/ama_layout/notification_spec.rb +193 -0
  35. data/spec/ama_layout/notifications/abstract_store_spec.rb +23 -0
  36. data/spec/ama_layout/notifications/redis_store_spec.rb +94 -0
  37. data/spec/ama_layout/notifications_spec.rb +109 -0
  38. data/spec/factories/users.rb +35 -0
  39. data/spec/helpers/ama_layout_path_helper_spec.rb +6 -6
  40. data/spec/internal/app/controllers/application_controller.rb +21 -0
  41. data/spec/internal/config/routes.rb +1 -0
  42. data/spec/spec_helper.rb +9 -14
  43. data/spec/support/shared_examples/member_navigation.rb +105 -0
  44. metadata +81 -18
  45. data/styles.scss +0 -0
@@ -0,0 +1,57 @@
1
+ describe AmaLayout::NotificationDecorator do
2
+ let(:notification) do
3
+ AmaLayout::Notification.new(
4
+ header: 'test',
5
+ content: 'content',
6
+ type: :warning,
7
+ created_at: Date.yesterday.beginning_of_day,
8
+ active: true
9
+ )
10
+ end
11
+ subject { described_class.new(notification) }
12
+
13
+ describe '#created_at' do
14
+ around(:each) do |example|
15
+ Timecop.freeze(Time.zone.local(2017, 8)) do
16
+ example.run
17
+ end
18
+ end
19
+
20
+ it 'returns the time elapsed in english words' do
21
+ expect(subject.created_at).to eq('1 day ago')
22
+ end
23
+ end
24
+
25
+ describe '#icon' do
26
+ it 'returns a div' do
27
+ expect(subject.icon).to include('<div')
28
+ end
29
+
30
+ it 'contains the proper icon class' do
31
+ expect(subject.icon).to include('fa-exclamation')
32
+ end
33
+
34
+ it 'contains the proper colour class' do
35
+ expect(subject.icon).to include('right-sidebar__content-icon--orange')
36
+ end
37
+ end
38
+
39
+ describe '#active_class' do
40
+ context 'when active' do
41
+ it 'returns the proper class' do
42
+ expect(subject.active_class).to_not include('inactive')
43
+ expect(subject.active_class).to include('active')
44
+ end
45
+ end
46
+
47
+ context 'when inactive' do
48
+ before(:each) do
49
+ notification.dismiss!
50
+ end
51
+
52
+ it 'returns the proper class' do
53
+ expect(subject.active_class).to include('inactive')
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,63 @@
1
+ describe AmaLayout::NavigationHelper do
2
+ subject { FactoryGirl.create(:user) }
3
+
4
+ describe '#navigation' do
5
+ before(:each) do
6
+ subject.class.include(AmaLayout::NavigationHelper).new
7
+ end
8
+
9
+ context 'non-member' do
10
+ subject { FactoryGirl.create(:user, :non_member) }
11
+
12
+ it 'shows non-member sidebar menu' do
13
+ expect(subject.navigation).to eq 'non-member'
14
+ end
15
+ end
16
+
17
+ context 'member' do
18
+ it 'shows member sidebar menu' do
19
+ expect(subject.navigation).to eq 'member'
20
+ end
21
+ end
22
+
23
+ context 'member with accr' do
24
+ subject { FactoryGirl.create(:user, :with_accr) }
25
+
26
+ it 'shows member sidebar menu' do
27
+ expect(subject.navigation).to eq 'member'
28
+ end
29
+ end
30
+
31
+ context 'member with mpp' do
32
+ subject { FactoryGirl.create(:user, :with_mpp) }
33
+
34
+ it 'shows member sidebar menu' do
35
+ expect(subject.navigation).to eq 'member'
36
+ end
37
+ end
38
+
39
+ context 'member in-renewal' do
40
+ subject { FactoryGirl.create(:user, :in_renewal) }
41
+
42
+ it 'shows in-renewal sidebar menu' do
43
+ expect(subject.navigation).to eq 'member-in-renewal'
44
+ end
45
+ end
46
+
47
+ context 'member in-renewal late' do
48
+ subject { FactoryGirl.create(:user, :in_renewal_late) }
49
+
50
+ it 'shows in-renewal-late sidebar menu' do
51
+ expect(subject.navigation).to eq 'member-in-renewal-late'
52
+ end
53
+ end
54
+
55
+ context 'member with outstanding balance' do
56
+ subject { FactoryGirl.create(:user, :outstanding_balance) }
57
+
58
+ it 'shows member-with-outstanding-balance sidebar menu' do
59
+ expect(subject.navigation).to eq 'member-with-outstanding-balance'
60
+ end
61
+ end
62
+ end
63
+ end
@@ -66,37 +66,7 @@ describe AmaLayout::Navigation do
66
66
 
67
67
  context "member" do
68
68
  context "subnavs" do
69
- context "automotive" do
70
- let(:automotive_subnav) { subject.items[2].sub_nav }
71
-
72
- it "returns the subnav items" do
73
- expect(automotive_subnav[0].text).to eq "Roadside Assistance Overview"
74
- expect(automotive_subnav[0].link).to eq "#{automotive_site}/"
75
- end
76
- end
77
-
78
- context "driver education" do
79
- let(:driver_education_subnav) { subject.items[3].sub_nav }
80
-
81
- it "return the subnav items" do
82
- expect(driver_education_subnav[0].text).to eq "Driver Education Overview"
83
- expect(driver_education_subnav[0].link).to eq "#{driveredonline_site}/"
84
-
85
- expect(driver_education_subnav[1].text).to eq "New Driver Online Program"
86
- expect(driver_education_subnav[1].link).to eq "#{driveredonline_site}/dashboard"
87
- end
88
- end
89
-
90
- context "registries" do
91
- let(:registries_subnav) { subject.items[5].sub_nav }
92
-
93
- it "returns the subnav items" do
94
- expect(registries_subnav[0].text).to eq "Registries Overview"
95
- expect(registries_subnav[0].link).to eq "#{registries_site}/"
96
- expect(registries_subnav[1].text).to eq "Vehicle Registration Auto-Renew"
97
- expect(registries_subnav[1].link).to eq "#{registries_site}/order/registrations/new"
98
- end
99
- end
69
+ include_examples "member_navigation"
100
70
  end
101
71
  end
102
72
 
@@ -121,18 +91,18 @@ describe AmaLayout::Navigation do
121
91
 
122
92
  context "member-in-renewal" do
123
93
  context "subnavs" do
124
- context "driver education" do
125
- before(:each) do
126
- subject.user = OpenStruct.new navigation: "member-in-renewal"
127
- end
128
- let(:driver_education_subnav) { subject.items[2].sub_nav }
94
+ before(:each) do
95
+ subject.user = OpenStruct.new navigation: "member-in-renewal"
96
+ end
129
97
 
130
- it "return the subnav items" do
131
- expect(driver_education_subnav[0].text).to eq "Driver Education Overview"
132
- expect(driver_education_subnav[0].link).to eq "#{driveredonline_site}/"
98
+ include_examples "member_navigation"
133
99
 
134
- expect(driver_education_subnav[1].text).to eq "New Driver Online Program"
135
- expect(driver_education_subnav[1].link).to eq "#{driveredonline_site}/dashboard"
100
+ context "membership" do
101
+ let(:membership_subnav) { subject.items[1].sub_nav }
102
+
103
+ it "has the correct subnav items" do
104
+ expect(membership_subnav[1].text).to eq "Renew Membership"
105
+ expect(membership_subnav[1].link).to eq "#{membership_site}/renews/new"
136
106
  end
137
107
  end
138
108
  end
@@ -148,7 +118,7 @@ describe AmaLayout::Navigation do
148
118
  expect(subject.items[0].text).to eq "Account Dashboard"
149
119
  expect(subject.items[0].link).to eq "#{gatekeeper_site}/"
150
120
  expect(subject.items[1].text).to eq "Renew"
151
- expect(subject.items[1].link).to eq "#{youraccount_site}/renew"
121
+ expect(subject.items[1].link).to eq "#{membership_site}/renews/new"
152
122
  end
153
123
  end
154
124
 
@@ -177,7 +147,7 @@ describe AmaLayout::Navigation do
177
147
  expect(subject.items[0].text).to eq "Account Dashboard"
178
148
  expect(subject.items[0].link).to eq "#{gatekeeper_site}/"
179
149
  expect(subject.items[1].text).to eq "Pay Outstanding Balance"
180
- expect(subject.items[1].link).to eq "#{youraccount_site}/renew"
150
+ expect(subject.items[1].link).to eq "#{membership_site}/renews/new"
181
151
  end
182
152
  end
183
153
 
@@ -0,0 +1,10 @@
1
+ describe AmaLayout::NotificationScrubber do
2
+ describe '#initialize' do
3
+ let(:sanitized) { Loofah.fragment(string).scrub!(subject).to_s }
4
+ let(:string) { '<script>alert("haxxed");</script><a href="#" invalid="test">test</a>waffles' }
5
+
6
+ it 'scrubs HTML tags from a string' do
7
+ expect(sanitized).to eq('alert("haxxed");<a href="#">test</a>waffles')
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,281 @@
1
+ describe AmaLayout::NotificationSet do
2
+ let(:store) do
3
+ AmaLayout::Notifications::RedisStore.new(
4
+ db: 4,
5
+ namespace: 'test_notifications',
6
+ host: 'localhost'
7
+ )
8
+ end
9
+ let(:key) { 1 }
10
+ let(:duration) { AmaLayout::Notification::DEFAULT_LIFESPAN }
11
+ let(:store_key) { key.to_s }
12
+ let(:digest) { base_notification.digest }
13
+ let(:base_notification) do
14
+ AmaLayout::Notification.new(
15
+ type: :notice,
16
+ header: 'test',
17
+ content: 'test',
18
+ lifespan: duration,
19
+ version: '1.0.0',
20
+ created_at: Time.zone.local(2017, 06, 19),
21
+ active: true
22
+ )
23
+ end
24
+ let(:json) do
25
+ <<-JSON
26
+ {
27
+ "#{digest}": {
28
+ "type": "notice",
29
+ "header": "test",
30
+ "content": "test",
31
+ "created_at": "2017-06-19T06:00:00.000Z",
32
+ "active": true,
33
+ "lifespan": #{duration},
34
+ "version": "1.0.0"
35
+ }
36
+ }
37
+ JSON
38
+ end
39
+ let(:stale_json) do
40
+ <<-JSON
41
+ {
42
+ "d3c2bc71904100674325791b371db7446097f956ea76a304e787abd5f2588665": {
43
+ "type": "notice",
44
+ "header": "stale",
45
+ "content": "stale",
46
+ "created_at": "2012-06-19T06:00:00.000Z",
47
+ "active": true,
48
+ "lifespan": #{duration},
49
+ "version": "1.0.0"
50
+ }
51
+ }
52
+ JSON
53
+ end
54
+
55
+ subject { described_class.new(store, key) }
56
+
57
+ around(:each) do |example|
58
+ Timecop.freeze(Time.zone.local(2017, 6, 19)) do
59
+ store.clear
60
+ example.run
61
+ store.clear
62
+ end
63
+ end
64
+
65
+ describe '#initialize' do
66
+ context 'with valid JSON in data store' do
67
+ before(:each) do
68
+ store.set(store_key, notification)
69
+ end
70
+
71
+ context 'without stale notifications in the data store' do
72
+ let(:notification) { json }
73
+
74
+ it 'fetches the notifications' do
75
+ expect(subject.size).to eq(1)
76
+ end
77
+ end
78
+
79
+ context 'with stale notifications in the data store' do
80
+ let(:notification) { stale_json }
81
+
82
+ it 'returns an empty set' do
83
+ expect(subject).to be_empty
84
+ end
85
+
86
+ it 'cleans out stale notifications from the data store' do
87
+ subject
88
+ expect(store.get(store_key)).to eq('{}')
89
+ end
90
+ end
91
+ end
92
+
93
+ context 'with invalid JSON in data store' do
94
+ before(:each) do
95
+ store.set(store_key, '{"invalid_json":')
96
+ end
97
+
98
+ it 'logs to Rails logger' do
99
+ expect(Rails.logger).to receive(:error).with(instance_of(String))
100
+ subject
101
+ end
102
+
103
+ it 'deletes the key in data store' do
104
+ subject
105
+ expect(store.get(store_key)).to be nil
106
+ end
107
+
108
+ it 'returns an empty set' do
109
+ expect(subject).to be_empty
110
+ end
111
+
112
+ it 'sets the base attribute to a hash' do
113
+ expect(subject.base).to be_a(Hash)
114
+ end
115
+ end
116
+
117
+ context 'with no entry in data store' do
118
+ it 'returns an empty set' do
119
+ expect(subject).to be_empty
120
+ end
121
+ end
122
+ end
123
+
124
+ describe '#create' do
125
+ it 'returns the NotificationSet instance' do
126
+ expect(subject.create(header: 'test', content: 'test')).to be_a(described_class)
127
+ end
128
+
129
+ it 'creates a new active notification' do
130
+ subject.create(header: 'test', content: 'test')
131
+ expect(subject.size).to eq(1)
132
+ end
133
+
134
+ it 'saves a notification in data store' do
135
+ subject.create(header: 'test', content: 'test')
136
+ expect(store.get(store_key)).to be_a(String)
137
+ end
138
+
139
+ context 'when the same notification exists but is dismissed' do
140
+ before(:each) do
141
+ store.set(store_key, json)
142
+ subject.first.dismiss!
143
+ subject.save
144
+ subject.create(header: 'test', content: 'test')
145
+ end
146
+
147
+ it 'does not overwrite the notification' do
148
+ expect(subject.active).to be_empty
149
+ end
150
+
151
+ it 'still has the dismissed notification in the data store' do
152
+ data = JSON.parse(store.get(store_key))
153
+ notification = data.values.first
154
+ expect(data.values.first['active']).to be false
155
+ end
156
+ end
157
+ end
158
+
159
+ describe '#delete' do
160
+ before(:each) do
161
+ subject.create(base_notification.to_h)
162
+ end
163
+
164
+ context 'with an array as an argument' do
165
+ it 'deletes the notification from the data store' do
166
+ data = subject.delete([digest])
167
+ expect(data).to be_a(described_class)
168
+ expect(data).to be_empty
169
+ expect(store.get(store_key)).to eq('{}')
170
+ end
171
+
172
+ it 'returns falsey if nothing is deleted' do
173
+ expect(subject.delete(['missing'])).to be_falsey
174
+ end
175
+ end
176
+
177
+ context 'with string arguments' do
178
+ it 'deletes the notification from the data store' do
179
+ data = subject.delete(digest)
180
+ expect(data).to be_a(described_class)
181
+ expect(data).to be_empty
182
+ expect(store.get(store_key)).to eq('{}')
183
+ end
184
+
185
+ it 'returns falsey if nothing is deleted' do
186
+ expect(subject.delete('missing')).to be_falsey
187
+ end
188
+ end
189
+ end
190
+
191
+ describe '#destroy' do
192
+ context 'when data is removed' do
193
+ before(:each) do
194
+ subject.create(header: 'test', content: 'test', lifespan: 1.day)
195
+ end
196
+
197
+ it 'returns a NotificationSet instance' do
198
+ expect(subject.destroy!).to be_a(described_class)
199
+ end
200
+
201
+ it 'removes the notifications from the data store' do
202
+ subject.destroy!
203
+ expect(store.get(store_key)).to be nil
204
+ end
205
+
206
+ it 'returns an empty set' do
207
+ expect(subject.destroy!).to be_empty
208
+ end
209
+ end
210
+
211
+ context 'when data is not removed' do
212
+ it 'returns false' do
213
+ expect(subject.destroy!).to be false
214
+ end
215
+ end
216
+ end
217
+
218
+ describe '#find' do
219
+ context 'when id is not preset' do
220
+ it 'returns nil' do
221
+ expect(subject.find('invalid')).to be nil
222
+ end
223
+ end
224
+
225
+ context 'when id is present' do
226
+ let(:id) { subject.last.id }
227
+
228
+ before(:each) do
229
+ subject.create(header: 'test', content: 'test')
230
+ end
231
+
232
+ it 'returns the notification' do
233
+ expect(subject.find(id)).to eq(subject.last)
234
+ end
235
+ end
236
+ end
237
+
238
+ describe '#save' do
239
+ before(:each) do
240
+ subject.create(header: 'test', content: 'test')
241
+ end
242
+
243
+ it 'saves the notifications' do
244
+ expect(subject.last.active?).to be true
245
+ subject.last.dismiss!
246
+ subject.save
247
+ expect(subject.active).to be_empty
248
+ end
249
+
250
+ it 'returns the NotificationSet instance' do
251
+ expect(subject.save).to be_a(described_class)
252
+ end
253
+ end
254
+
255
+ describe '#inspect' do
256
+ it 'returns a stringified instance' do
257
+ expect(subject.inspect).to eq('<AmaLayout::NotificationSet>: []')
258
+ end
259
+ end
260
+
261
+ context 'scoping' do
262
+ before(:each) do
263
+ subject.create(header: 'test', content: 'test')
264
+ subject.create(header: 'inactive', content: 'inactive')
265
+ subject.last.dismiss!
266
+ subject.save
267
+ end
268
+
269
+ describe '#all' do
270
+ it 'returns both active and inactive notifications' do
271
+ expect(subject.all.size).to eq(2)
272
+ end
273
+ end
274
+
275
+ describe '#active' do
276
+ it 'returns only active notifications' do
277
+ expect(subject.active.size).to eq(1)
278
+ end
279
+ end
280
+ end
281
+ end