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,193 @@
|
|
1
|
+
describe AmaLayout::Notification do
|
2
|
+
let(:instance) do
|
3
|
+
described_class.new(
|
4
|
+
header: 'test',
|
5
|
+
content: 'test',
|
6
|
+
active: true,
|
7
|
+
created_at: Time.current
|
8
|
+
)
|
9
|
+
end
|
10
|
+
|
11
|
+
describe '#initialize' do
|
12
|
+
let(:lifespan) { 1.day }
|
13
|
+
|
14
|
+
subject do
|
15
|
+
described_class.new(
|
16
|
+
header: 'test',
|
17
|
+
'content': 'test',
|
18
|
+
'active': true,
|
19
|
+
'created_at': time,
|
20
|
+
'lifespan': lifespan
|
21
|
+
)
|
22
|
+
end
|
23
|
+
|
24
|
+
context 'with a time created_at attribute' do
|
25
|
+
let(:time) { Time.current }
|
26
|
+
|
27
|
+
it 'accepts both symbols and strings as hash keys' do
|
28
|
+
expect(subject.header).to eq('test')
|
29
|
+
expect(subject.active).to be true
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'sets a version by default' do
|
33
|
+
expect(subject.version).to eq(described_class::FORMAT_VERSION)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
context 'with a string created_at attribute' do
|
38
|
+
let(:time) { '1984-01-01' }
|
39
|
+
|
40
|
+
it 'parses the string as a time' do
|
41
|
+
expect(subject.created_at).to eq(Time.zone.parse(time))
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
context 'with an Integer/Fixnum lifespan attribute' do
|
46
|
+
let(:time) { Time.current }
|
47
|
+
let(:lifespan) { 3600 }
|
48
|
+
|
49
|
+
it 'parses the integer to a duration of seconds' do
|
50
|
+
expect(subject.lifespan).to eq(lifespan.seconds)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
context 'with an invalid type attribute' do
|
55
|
+
let(:parameters) do
|
56
|
+
{
|
57
|
+
type: :invalid,
|
58
|
+
header: 'test',
|
59
|
+
content: 'test',
|
60
|
+
created_at: Time.current,
|
61
|
+
active: true
|
62
|
+
}
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'raises ArgumentError' do
|
66
|
+
expect { described_class.new(parameters) }.to raise_error(ArgumentError)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
describe '#<=>' do
|
72
|
+
let(:old) do
|
73
|
+
described_class.new(
|
74
|
+
header: 'test',
|
75
|
+
content: 'test',
|
76
|
+
active: true,
|
77
|
+
created_at: Date.new(1984)
|
78
|
+
)
|
79
|
+
end
|
80
|
+
let(:new) do
|
81
|
+
described_class.new(
|
82
|
+
header: 'test',
|
83
|
+
content: 'test',
|
84
|
+
active: true,
|
85
|
+
created_at: Date.new(2017)
|
86
|
+
)
|
87
|
+
end
|
88
|
+
|
89
|
+
it 'sorts by created_at date' do
|
90
|
+
expect(old <=> new).to eq(-1)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
describe '#active?' do
|
95
|
+
subject do
|
96
|
+
described_class.new(
|
97
|
+
header: 'test',
|
98
|
+
content: 'test',
|
99
|
+
active: active,
|
100
|
+
created_at: Time.current
|
101
|
+
)
|
102
|
+
end
|
103
|
+
|
104
|
+
context 'when active' do
|
105
|
+
let(:active) { true }
|
106
|
+
|
107
|
+
it 'returns true' do
|
108
|
+
expect(subject.active?).to be true
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
context 'when inactive' do
|
113
|
+
let(:active) { false }
|
114
|
+
|
115
|
+
it 'returns false' do
|
116
|
+
expect(subject.active?).to be false
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
describe 'dismissed?' do
|
122
|
+
subject do
|
123
|
+
described_class.new(
|
124
|
+
header: 'test',
|
125
|
+
content: 'test',
|
126
|
+
active: active,
|
127
|
+
created_at: Time.current
|
128
|
+
)
|
129
|
+
end
|
130
|
+
|
131
|
+
context 'when active' do
|
132
|
+
let(:active) { true }
|
133
|
+
|
134
|
+
it 'returns false' do
|
135
|
+
expect(subject.dismissed?).to be false
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
context 'when inactive' do
|
140
|
+
let(:active) { false }
|
141
|
+
|
142
|
+
it 'returns true' do
|
143
|
+
expect(subject.dismissed?).to be true
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
describe '#dismiss!' do
|
149
|
+
it 'returns true' do
|
150
|
+
expect(instance.dismiss!).to be true
|
151
|
+
end
|
152
|
+
|
153
|
+
it 'sets the :active flag to false' do
|
154
|
+
instance.dismiss!
|
155
|
+
expect(instance.active?).to be false
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
describe '#digest' do
|
160
|
+
context 'with the same objects' do
|
161
|
+
let(:other) { instance.dup }
|
162
|
+
|
163
|
+
it 'produces the same digest' do
|
164
|
+
expect(instance.digest).to eq(other.digest)
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
context 'with different objects' do
|
169
|
+
let(:other) do
|
170
|
+
described_class.new(
|
171
|
+
header: 'other',
|
172
|
+
content: 'other',
|
173
|
+
active: true,
|
174
|
+
created_at: Time.current
|
175
|
+
)
|
176
|
+
end
|
177
|
+
|
178
|
+
it 'produces different digests' do
|
179
|
+
expect(instance.digest).to_not eq(other.digest)
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
describe '#to_h' do
|
185
|
+
it 'returns a hash' do
|
186
|
+
expect(instance.to_h).to be_a(Hash)
|
187
|
+
end
|
188
|
+
|
189
|
+
it 'is not empty' do
|
190
|
+
expect(instance.to_h).to_not be_empty
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
describe AmaLayout::Notifications::AbstractStore do
|
2
|
+
context 'when inheriting' do
|
3
|
+
subject { Class.new(described_class).new }
|
4
|
+
|
5
|
+
describe '#get' do
|
6
|
+
it 'raises NotImplementedError' do
|
7
|
+
expect { subject.get('test') }.to raise_error(NotImplementedError)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
describe '#set' do
|
12
|
+
it 'raises NotImplementedError' do
|
13
|
+
expect { subject.set('test', 'test') }.to raise_error(NotImplementedError)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
describe '#delete' do
|
18
|
+
it 'raises NotImplementedError' do
|
19
|
+
expect { subject.delete('test') }.to raise_error(NotImplementedError)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
describe AmaLayout::Notifications::RedisStore do
|
2
|
+
subject do
|
3
|
+
described_class.new(
|
4
|
+
db: 4,
|
5
|
+
namespace: 'test_notifications',
|
6
|
+
host: 'localhost'
|
7
|
+
)
|
8
|
+
end
|
9
|
+
|
10
|
+
around(:each) do |example|
|
11
|
+
subject.clear
|
12
|
+
example.run
|
13
|
+
subject.clear
|
14
|
+
end
|
15
|
+
|
16
|
+
describe '#get' do
|
17
|
+
context 'when a key is not present' do
|
18
|
+
it 'returns nil' do
|
19
|
+
expect(subject.get('missing')).to be nil
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
context 'when a key is present' do
|
24
|
+
before(:each) do
|
25
|
+
subject.set('key', 'value')
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'returns the value' do
|
29
|
+
expect(subject.get('key')).to eq('value')
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
context 'with a default value' do
|
34
|
+
it 'sets a nil key to the default value' do
|
35
|
+
subject.get('missing', default: 'test')
|
36
|
+
expect(subject.get('missing')).to eq('test')
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe '#set' do
|
42
|
+
it 'sets the value for a given key' do
|
43
|
+
subject.set('test', 'value')
|
44
|
+
expect(subject.get('test')).to eq('value')
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'returns true' do
|
48
|
+
expect(subject.set('test', 'value')).to be true
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
describe '#delete' do
|
53
|
+
context 'when a value is deleted' do
|
54
|
+
before(:each) do
|
55
|
+
subject.set('key', 'value')
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'returns true' do
|
59
|
+
expect(subject.delete('key')).to be true
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'deletes the key' do
|
63
|
+
subject.delete('key')
|
64
|
+
expect(subject.get('key')).to be nil
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
context 'when a value is not deleted' do
|
69
|
+
it 'returns false' do
|
70
|
+
expect(subject.delete('key')).to be false
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
describe '#transaction' do
|
76
|
+
it 'does not commit if an exception is raised' do
|
77
|
+
begin
|
78
|
+
subject.transaction do |store|
|
79
|
+
store.set('key', 'value')
|
80
|
+
raise StandardError
|
81
|
+
end
|
82
|
+
rescue StandardError
|
83
|
+
end
|
84
|
+
expect(subject.get('key')).to be nil
|
85
|
+
end
|
86
|
+
|
87
|
+
it 'commits to redis successfully' do
|
88
|
+
subject.transaction do |store|
|
89
|
+
store.set('key', 'value')
|
90
|
+
end
|
91
|
+
expect(subject.get('key')).to eq('value')
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
describe AmaLayout::Notifications 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(:json) do
|
10
|
+
<<-JSON
|
11
|
+
{
|
12
|
+
"02ac263cea5660e9f9020cb46e93772ed7755f2a60c40ad8961d2a15c1f99e6f": {
|
13
|
+
"type": "notice",
|
14
|
+
"header": "test",
|
15
|
+
"content": "test",
|
16
|
+
"created_at": "2017-06-19T06:00:00.000Z",
|
17
|
+
"active": true,
|
18
|
+
"lifespan": 31557600,
|
19
|
+
"version": "1.0.0"
|
20
|
+
}
|
21
|
+
}
|
22
|
+
JSON
|
23
|
+
end
|
24
|
+
|
25
|
+
around(:each) do |example|
|
26
|
+
Timecop.freeze(Time.zone.local(2017, 6, 19)) do
|
27
|
+
store.clear
|
28
|
+
example.run
|
29
|
+
store.clear
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
context 'when including module' do
|
34
|
+
let(:klass) { Class.new.include(described_class) }
|
35
|
+
|
36
|
+
context 'class methods' do
|
37
|
+
before(:each) do
|
38
|
+
klass.class_eval do
|
39
|
+
notification_store AmaLayout::Notifications::RedisStore.new(
|
40
|
+
db: 4,
|
41
|
+
namespace: 'test_notifications',
|
42
|
+
host: 'localhost'
|
43
|
+
)
|
44
|
+
notification_foreign_key :my_id
|
45
|
+
|
46
|
+
def my_id
|
47
|
+
@id ||= SecureRandom.uuid
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
describe '#_notification_foreign_key' do
|
53
|
+
it 'returns the id method as a proc' do
|
54
|
+
expect(klass._notification_foreign_key).to be_a(Proc)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
describe '#_notification_store' do
|
59
|
+
it 'returns the set data store' do
|
60
|
+
expect(klass._notification_store).to be_an(AmaLayout::Notifications::RedisStore)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
context 'instance methods' do
|
66
|
+
context 'with a valid notification store' do
|
67
|
+
subject { klass.new }
|
68
|
+
|
69
|
+
before(:each) do
|
70
|
+
klass.class_eval do
|
71
|
+
notification_store AmaLayout::Notifications::RedisStore.new(
|
72
|
+
db: 4,
|
73
|
+
namespace: 'test_notifications',
|
74
|
+
host: 'localhost'
|
75
|
+
)
|
76
|
+
notification_foreign_key :my_id
|
77
|
+
|
78
|
+
def my_id
|
79
|
+
@id ||= SecureRandom.uuid
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
describe '#notifications' do
|
85
|
+
before(:each) do
|
86
|
+
store.set(subject.my_id, json)
|
87
|
+
end
|
88
|
+
|
89
|
+
it 'fetches notifications from data store' do
|
90
|
+
expect(subject.notifications.size).to eq(1)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
describe '#notifications=' do
|
95
|
+
it 'resets the notifications to nil' do
|
96
|
+
subject.notifications = nil
|
97
|
+
expect(subject.notifications).to be_empty
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
context 'with an undefined notification store' do
|
103
|
+
it 'raises InvalidNotificationStore' do
|
104
|
+
expect { klass.new.notifications }.to raise_error(AmaLayout::Notifications::InvalidNotificationStore)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
FactoryGirl.define do
|
2
|
+
factory :user, class: Class.new(OpenStruct) do
|
3
|
+
in_renewal false
|
4
|
+
member_type 'P'
|
5
|
+
renew_type 'R'
|
6
|
+
status 'A'
|
7
|
+
has_outstanding_balance false
|
8
|
+
member? true
|
9
|
+
|
10
|
+
trait :non_member do
|
11
|
+
member? false
|
12
|
+
end
|
13
|
+
|
14
|
+
trait :with_accr do
|
15
|
+
renew_type 'A'
|
16
|
+
end
|
17
|
+
|
18
|
+
trait :with_mpp do
|
19
|
+
renew_type 'M'
|
20
|
+
end
|
21
|
+
|
22
|
+
trait :in_renewal do
|
23
|
+
in_renewal true
|
24
|
+
end
|
25
|
+
|
26
|
+
trait :in_renewal_late do
|
27
|
+
in_renewal true
|
28
|
+
status 'AL'
|
29
|
+
end
|
30
|
+
|
31
|
+
trait :outstanding_balance do
|
32
|
+
has_outstanding_balance true
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|