ama_layout 6.3.0.pre → 6.10.0.pre

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 (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