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.
- checksums.yaml +4 -4
- data/.travis.yml +2 -0
- data/ama_layout.gemspec +22 -20
- data/app/assets/javascripts/ama_layout/desktop/foundation-custom.js +10 -8
- data/app/assets/javascripts/ama_layout/desktop/index.js +1 -0
- data/app/assets/javascripts/ama_layout/notifications.coffee +17 -0
- data/app/controllers/ama_layout/api/v1/notifications_controller.rb +18 -0
- data/app/helpers/ama_layout_path_helper.rb +4 -4
- data/app/views/ama_layout/_notification.html.erb +10 -0
- data/app/views/ama_layout/_notification_sidebar.html.erb +22 -0
- data/app/views/ama_layout/_notifications.html.erb +6 -0
- data/app/views/ama_layout/_siteheader.html.erb +8 -3
- data/config/routes.rb +9 -0
- data/lib/ama_layout.rb +27 -20
- data/lib/ama_layout/decorators/navigation_decorator.rb +47 -0
- data/lib/ama_layout/decorators/notification_decorator.rb +45 -0
- data/lib/ama_layout/navigation.rb +3 -0
- data/lib/ama_layout/navigation.yml +58 -6
- data/lib/ama_layout/navigation_helper.rb +31 -0
- data/lib/ama_layout/notification.rb +89 -0
- data/lib/ama_layout/notification_scrubber.rb +13 -0
- data/lib/ama_layout/notification_set.rb +140 -0
- data/lib/ama_layout/notifications.rb +73 -0
- data/lib/ama_layout/notifications/abstract_store.rb +17 -0
- data/lib/ama_layout/notifications/redis_store.rb +38 -0
- data/lib/ama_layout/version.rb +1 -1
- data/spec/ama_layout/controllers/ama_layout/api/v1/notifications_controller_spec.rb +13 -0
- data/spec/ama_layout/decorators/navigation_decorator_spec.rb +121 -2
- data/spec/ama_layout/decorators/notification_decorator_spec.rb +57 -0
- data/spec/ama_layout/navigation_helper_spec.rb +63 -0
- data/spec/ama_layout/navigation_spec.rb +13 -43
- data/spec/ama_layout/notification_scrubber_spec.rb +10 -0
- data/spec/ama_layout/notification_set_spec.rb +281 -0
- data/spec/ama_layout/notification_spec.rb +193 -0
- data/spec/ama_layout/notifications/abstract_store_spec.rb +23 -0
- data/spec/ama_layout/notifications/redis_store_spec.rb +94 -0
- data/spec/ama_layout/notifications_spec.rb +109 -0
- data/spec/factories/users.rb +35 -0
- data/spec/helpers/ama_layout_path_helper_spec.rb +6 -6
- data/spec/internal/app/controllers/application_controller.rb +21 -0
- data/spec/internal/config/routes.rb +1 -0
- data/spec/spec_helper.rb +9 -14
- data/spec/support/shared_examples/member_navigation.rb +105 -0
- metadata +81 -18
- 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
|
-
|
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
|
-
|
125
|
-
|
126
|
-
|
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
|
-
|
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
|
-
|
135
|
-
|
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 "#{
|
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 "#{
|
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
|