activity_notification 0.0.8 → 0.0.9
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/Gemfile.lock +11 -1
- data/README.md +63 -28
- data/activity_notification.gemspec +4 -2
- data/app/controllers/activity_notification/notifications_controller.rb +1 -1
- data/app/controllers/activity_notification/notifications_with_devise_controller.rb +10 -10
- data/app/views/activity_notification/notifications/default/_index.html.erb +9 -4
- data/lib/activity_notification.rb +7 -6
- data/lib/activity_notification/apis/notification_api.rb +14 -15
- data/lib/activity_notification/common.rb +15 -7
- data/lib/activity_notification/config.rb +2 -0
- data/lib/activity_notification/helpers/view_helpers.rb +5 -4
- data/lib/activity_notification/mailers/helpers.rb +9 -9
- data/lib/activity_notification/models.rb +16 -0
- data/lib/activity_notification/models/{notifiable.rb → concerns/notifiable.rb} +15 -10
- data/lib/activity_notification/models/{notifier.rb → concerns/notifier.rb} +6 -0
- data/lib/activity_notification/models/{target.rb → concerns/target.rb} +34 -17
- data/lib/activity_notification/renderable.rb +2 -1
- data/lib/activity_notification/roles/acts_as_notifiable.rb +38 -23
- data/lib/activity_notification/roles/acts_as_notifier.rb +11 -0
- data/lib/activity_notification/roles/acts_as_target.rb +9 -18
- data/lib/activity_notification/version.rb +1 -1
- data/lib/generators/activity_notification/{migration → active_record}/migration_generator.rb +5 -4
- data/lib/generators/activity_notification/controllers_generator.rb +1 -1
- data/lib/generators/activity_notification/install_generator.rb +3 -6
- data/lib/generators/activity_notification/{notification → models}/notification_generator.rb +5 -4
- data/lib/generators/activity_notification/views_generator.rb +20 -22
- data/lib/generators/templates/active_record/migration.rb +1 -1
- data/lib/generators/templates/activity_notification.rb +13 -3
- data/{config → lib/generators/templates}/locales/en.yml +0 -0
- data/lib/generators/templates/notification/notification.rb +4 -1
- data/spec/concerns/{notification_api_spec.rb → apis/notification_api_spec.rb} +169 -45
- data/spec/concerns/common_spec.rb +150 -0
- data/spec/concerns/models/notifiable_spec.rb +435 -0
- data/spec/concerns/models/notifier_spec.rb +23 -0
- data/spec/concerns/models/target_spec.rb +579 -0
- data/spec/concerns/renderable_spec.rb +110 -0
- data/spec/controllers/notifications_controller_shared_examples.rb +457 -0
- data/spec/controllers/notifications_controller_spec.rb +12 -0
- data/spec/controllers/notifications_with_devise_controller_spec.rb +81 -0
- data/spec/factories/admins.rb +5 -0
- data/spec/factories/articles.rb +1 -1
- data/spec/factories/comments.rb +1 -1
- data/spec/factories/dummy/dummy_notifiable.rb +4 -0
- data/spec/factories/dummy/dummy_notifier.rb +4 -0
- data/spec/factories/dummy/dummy_target.rb +4 -0
- data/spec/factories/notifications.rb +1 -1
- data/spec/factories/users.rb +7 -1
- data/spec/generators/active_record/migration_generator_spec.rb +41 -0
- data/spec/generators/controllers_generator_spec.rb +62 -0
- data/spec/generators/install_generator_spec.rb +43 -0
- data/spec/generators/models/notification_generator_spec.rb +41 -0
- data/spec/generators/views_generator_spec.rb +111 -0
- data/spec/helpers/polymorphic_helpers_spec.rb +89 -0
- data/spec/helpers/view_helpers_spec.rb +258 -0
- data/spec/mailers/mailer_spec.rb +98 -0
- data/spec/models/dummy/dummy_notifiable_spec.rb +6 -0
- data/spec/models/dummy/dummy_notifier_spec.rb +6 -0
- data/spec/models/dummy/dummy_target_spec.rb +6 -0
- data/spec/models/notification_spec.rb +5 -4
- data/spec/rails_app/app/assets/javascripts/application.js +2 -0
- data/spec/rails_app/app/controllers/articles_controller.rb +62 -0
- data/spec/rails_app/app/controllers/comments_controller.rb +34 -0
- data/spec/rails_app/app/models/admin.rb +8 -0
- data/spec/rails_app/app/models/article.rb +6 -6
- data/spec/rails_app/app/models/comment.rb +2 -2
- data/spec/rails_app/app/models/dummy/dummy_base.rb +2 -0
- data/spec/rails_app/app/models/dummy/dummy_notifiable.rb +4 -0
- data/spec/rails_app/app/models/dummy/dummy_notifier.rb +4 -0
- data/spec/rails_app/app/models/dummy/dummy_target.rb +4 -0
- data/spec/rails_app/app/models/user.rb +5 -5
- data/spec/rails_app/app/views/activity_notification/notifications/default/custom/_path_test.html.erb +1 -0
- data/spec/rails_app/app/views/activity_notification/notifications/default/custom/_test.html.erb +1 -0
- data/spec/rails_app/app/views/activity_notification/notifications/users/_custom_index.html.erb +1 -0
- data/spec/rails_app/app/views/activity_notification/notifications/users/custom/_test.html.erb +1 -0
- data/spec/rails_app/app/views/articles/_form.html.erb +20 -0
- data/spec/rails_app/app/views/articles/edit.html.erb +6 -0
- data/spec/rails_app/app/views/articles/index.html.erb +67 -0
- data/spec/rails_app/app/views/articles/new.html.erb +5 -0
- data/spec/rails_app/app/views/articles/show.html.erb +38 -0
- data/spec/rails_app/app/views/layouts/_header.html.erb +8 -0
- data/spec/rails_app/app/views/layouts/application.html.erb +3 -4
- data/spec/rails_app/config/initializers/activity_notification.rb +13 -3
- data/spec/rails_app/config/initializers/devise.rb +274 -274
- data/spec/rails_app/config/locales/activity_notification.en.yml +20 -0
- data/spec/rails_app/config/locales/devise.en.yml +62 -0
- data/spec/rails_app/config/routes.rb +6 -2
- data/spec/rails_app/db/migrate/20160715050433_create_test_tables.rb +6 -2
- data/spec/rails_app/db/schema.rb +8 -0
- data/spec/rails_app/db/seeds.rb +43 -0
- data/spec/roles/acts_as_notifiable_spec.rb +32 -0
- data/spec/roles/acts_as_notifier_spec.rb +17 -0
- data/spec/roles/acts_as_target_spec.rb +40 -0
- data/spec/spec_helper.rb +18 -14
- metadata +136 -12
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'controllers/notifications_controller_shared_examples'
|
2
|
+
|
3
|
+
describe ActivityNotification::NotificationsController, type: :controller do
|
4
|
+
let(:test_target) { create(:user) }
|
5
|
+
let(:target_type) { :users }
|
6
|
+
let(:typed_target_param) { :user_id }
|
7
|
+
let(:extra_params) { {} }
|
8
|
+
let(:valid_session) {}
|
9
|
+
|
10
|
+
it_behaves_like :notification_controller
|
11
|
+
|
12
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'controllers/notifications_controller_shared_examples'
|
2
|
+
|
3
|
+
describe ActivityNotification::NotificationsWithDeviseController, type: :controller do
|
4
|
+
let(:test_user) { create(:confirmed_user) }
|
5
|
+
let(:unauthenticated_user) { create(:confirmed_user) }
|
6
|
+
let(:test_target) { create(:admin, user: test_user) }
|
7
|
+
let(:target_type) { :admins }
|
8
|
+
let(:typed_target_param) { :admin_id }
|
9
|
+
let(:extra_params) { { devise_type: :users } }
|
10
|
+
let(:valid_session) {}
|
11
|
+
|
12
|
+
context "signed in with devise as authenticated user" do
|
13
|
+
before do
|
14
|
+
sign_in test_user
|
15
|
+
end
|
16
|
+
|
17
|
+
it_behaves_like :notification_controller
|
18
|
+
end
|
19
|
+
|
20
|
+
context "signed in with devise as unauthenticated user" do
|
21
|
+
let(:target_params) { { target_type: target_type, devise_type: :users } }
|
22
|
+
|
23
|
+
describe "GET #index" do
|
24
|
+
before do
|
25
|
+
sign_in unauthenticated_user
|
26
|
+
get :index, target_params.merge({ typed_target_param => test_target }), valid_session
|
27
|
+
end
|
28
|
+
|
29
|
+
it "returns 403 as http status code" do
|
30
|
+
expect(response.status).to eq(403)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
context "unsigned in with devise" do
|
36
|
+
let(:target_params) { { target_type: target_type, devise_type: :users } }
|
37
|
+
|
38
|
+
describe "GET #index" do
|
39
|
+
before do
|
40
|
+
get :index, target_params.merge({ typed_target_param => test_target }), valid_session
|
41
|
+
end
|
42
|
+
|
43
|
+
it "returns 302 as http status code" do
|
44
|
+
expect(response.status).to eq(302)
|
45
|
+
end
|
46
|
+
|
47
|
+
it "redirects to sign_in path" do
|
48
|
+
expect(response).to redirect_to new_user_session_path
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
context "without devise_type parameter" do
|
54
|
+
let(:target_params) { { target_type: target_type } }
|
55
|
+
|
56
|
+
describe "GET #index" do
|
57
|
+
before do
|
58
|
+
get :index, target_params.merge({ typed_target_param => test_target }), valid_session
|
59
|
+
end
|
60
|
+
|
61
|
+
it "returns 400 as http status code" do
|
62
|
+
expect(response.status).to eq(400)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
context "with wrong devise_type parameter" do
|
68
|
+
let(:target_params) { { target_type: target_type, devise_type: :dummy_targets } }
|
69
|
+
|
70
|
+
describe "GET #index" do
|
71
|
+
before do
|
72
|
+
get :index, target_params.merge({ typed_target_param => test_target }), valid_session
|
73
|
+
end
|
74
|
+
|
75
|
+
it "returns 403 as http status code" do
|
76
|
+
expect(response.status).to eq(403)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
data/spec/factories/articles.rb
CHANGED
data/spec/factories/comments.rb
CHANGED
data/spec/factories/users.rb
CHANGED
@@ -1,5 +1,11 @@
|
|
1
1
|
FactoryGirl.define do
|
2
2
|
factory :user do
|
3
|
-
email [
|
3
|
+
email Array.new(10){[*"A".."Z", *"0".."9"].sample}.join + '@example.com'
|
4
|
+
password "password"
|
5
|
+
password_confirmation "password"
|
6
|
+
end
|
7
|
+
|
8
|
+
factory :confirmed_user, parent: :user do
|
9
|
+
after(:build) { |user| user.skip_confirmation! }
|
4
10
|
end
|
5
11
|
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'generators/activity_notification/active_record/migration_generator'
|
2
|
+
|
3
|
+
describe ActivityNotification::Generators::MigrationGenerator, type: :generator do
|
4
|
+
|
5
|
+
# setup_default_destination
|
6
|
+
destination File.expand_path("../../../../tmp", __FILE__)
|
7
|
+
before { prepare_destination }
|
8
|
+
|
9
|
+
it 'runs generating migration tasks' do
|
10
|
+
gen = generator
|
11
|
+
expect(gen).to receive :create_migrations
|
12
|
+
gen.invoke_all
|
13
|
+
end
|
14
|
+
|
15
|
+
describe 'the generated files' do
|
16
|
+
context 'without name argument' do
|
17
|
+
before do
|
18
|
+
run_generator
|
19
|
+
end
|
20
|
+
|
21
|
+
describe 'CreateNotifications migration file' do
|
22
|
+
subject { file(Dir["tmp/db/migrate/*_create_notifications.rb"].first.gsub!('tmp/', '')) }
|
23
|
+
it { is_expected.to exist }
|
24
|
+
it { is_expected.to contain(/class CreateNotifications < ActiveRecord::Migration/) }
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
context 'with CreateCustomNotifications as name argument' do
|
29
|
+
before do
|
30
|
+
run_generator %w(CreateCustomNotifications)
|
31
|
+
end
|
32
|
+
|
33
|
+
describe 'CreateCustomNotifications migration file' do
|
34
|
+
subject { file(Dir["tmp/db/migrate/*_create_custom_notifications.rb"].first.gsub!('tmp/', '')) }
|
35
|
+
it { is_expected.to exist }
|
36
|
+
it { is_expected.to contain(/class CreateCustomNotifications < ActiveRecord::Migration/) }
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'generators/activity_notification/controllers_generator'
|
2
|
+
|
3
|
+
describe ActivityNotification::Generators::ControllersGenerator, type: :generator do
|
4
|
+
|
5
|
+
# setup_default_destination
|
6
|
+
destination File.expand_path("../../../tmp", __FILE__)
|
7
|
+
before { prepare_destination }
|
8
|
+
|
9
|
+
it 'runs generating controllers tasks' do
|
10
|
+
gen = generator %w(users)
|
11
|
+
expect(gen).to receive :create_controllers
|
12
|
+
expect(gen).to receive(:readme).and_return(true)
|
13
|
+
gen.invoke_all
|
14
|
+
end
|
15
|
+
|
16
|
+
describe 'the generated files' do
|
17
|
+
context 'without target argument' do
|
18
|
+
it 'raises Thor::RequiredArgumentMissingError' do
|
19
|
+
expect { run_generator }
|
20
|
+
.to raise_error(Thor::RequiredArgumentMissingError)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
context 'with users as target' do
|
25
|
+
context 'with target controllers as default' do
|
26
|
+
before do
|
27
|
+
run_generator %w(users)
|
28
|
+
end
|
29
|
+
|
30
|
+
describe 'the notifications_controller' do
|
31
|
+
subject { file('app/controllers/users/notifications_controller.rb') }
|
32
|
+
it { is_expected.to exist }
|
33
|
+
it { is_expected.to contain(/class Users::NotificationsController < ActivityNotification::NotificationsController/) }
|
34
|
+
end
|
35
|
+
|
36
|
+
describe 'the notifications_with_devise_controller' do
|
37
|
+
subject { file('app/controllers/users/notifications_with_devise_controller.rb') }
|
38
|
+
it { is_expected.to exist }
|
39
|
+
it { is_expected.to contain(/class Users::NotificationsWithDeviseController < ActivityNotification::NotificationsWithDeviseController/) }
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
context 'with a controllers option as notifications' do
|
44
|
+
before do
|
45
|
+
run_generator %w(users --controllers notifications)
|
46
|
+
end
|
47
|
+
|
48
|
+
describe 'the notifications_controller' do
|
49
|
+
subject { file('app/controllers/users/notifications_controller.rb') }
|
50
|
+
it { is_expected.to exist }
|
51
|
+
it { is_expected.to contain(/class Users::NotificationsController < ActivityNotification::NotificationsController/) }
|
52
|
+
end
|
53
|
+
|
54
|
+
describe 'the notifications_with_devise_controller' do
|
55
|
+
subject { file('app/controllers/users/notifications_with_devise_controller.rb') }
|
56
|
+
it { is_expected.not_to exist }
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'generators/activity_notification/install_generator'
|
2
|
+
|
3
|
+
describe ActivityNotification::Generators::InstallGenerator, type: :generator do
|
4
|
+
|
5
|
+
# setup_default_destination
|
6
|
+
destination File.expand_path("../../../tmp", __FILE__)
|
7
|
+
before { prepare_destination }
|
8
|
+
|
9
|
+
it 'runs both the initializer and locale tasks' do
|
10
|
+
gen = generator
|
11
|
+
expect(gen).to receive :copy_initializer
|
12
|
+
expect(gen).to receive :copy_locale
|
13
|
+
expect(gen).to receive(:readme).and_return(true)
|
14
|
+
gen.invoke_all
|
15
|
+
end
|
16
|
+
|
17
|
+
describe 'the generated files' do
|
18
|
+
context 'with active_record orm as default' do
|
19
|
+
before do
|
20
|
+
run_generator
|
21
|
+
end
|
22
|
+
|
23
|
+
describe 'the initializer' do
|
24
|
+
subject { file('config/initializers/activity_notification.rb') }
|
25
|
+
it { is_expected.to exist }
|
26
|
+
it { is_expected.to contain(/ActivityNotification.configure do |config|/) }
|
27
|
+
end
|
28
|
+
|
29
|
+
describe 'the locale file' do
|
30
|
+
subject { file('config/locales/activity_notification.en.yml') }
|
31
|
+
it { is_expected.to exist }
|
32
|
+
it { is_expected.to contain(/en:\n.+notification:\n.+default:/) }
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
context 'with orm option as not :active_record' do
|
37
|
+
it 'raises MissingORMError' do
|
38
|
+
expect { run_generator %w(--orm dummy) }
|
39
|
+
.to raise_error(TypeError)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'generators/activity_notification/models/notification_generator'
|
2
|
+
|
3
|
+
describe ActivityNotification::Generators::NotificationGenerator, type: :generator do
|
4
|
+
|
5
|
+
# setup_default_destination
|
6
|
+
destination File.expand_path("../../../../tmp", __FILE__)
|
7
|
+
before { prepare_destination }
|
8
|
+
|
9
|
+
it 'runs generating model tasks' do
|
10
|
+
gen = generator
|
11
|
+
expect(gen).to receive :create_models
|
12
|
+
gen.invoke_all
|
13
|
+
end
|
14
|
+
|
15
|
+
describe 'the generated files' do
|
16
|
+
context 'without name argument' do
|
17
|
+
before do
|
18
|
+
run_generator
|
19
|
+
end
|
20
|
+
|
21
|
+
describe 'app/models/notification.rb' do
|
22
|
+
subject { file('app/models/notification.rb') }
|
23
|
+
it { is_expected.to exist }
|
24
|
+
it { is_expected.to contain(/class Notification < ActivityNotification::Notification/) }
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
context 'with CustomNotification as name argument' do
|
29
|
+
before do
|
30
|
+
run_generator %w(CustomNotification)
|
31
|
+
end
|
32
|
+
|
33
|
+
describe 'app/models/notification.rb' do
|
34
|
+
subject { file('app/models/custom_notification.rb') }
|
35
|
+
it { is_expected.to exist }
|
36
|
+
it { is_expected.to contain(/class CustomNotification < ActivityNotification::Notification/) }
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
require 'generators/activity_notification/views_generator'
|
2
|
+
|
3
|
+
describe ActivityNotification::Generators::ViewsGenerator, type: :generator do
|
4
|
+
|
5
|
+
# setup_default_destination
|
6
|
+
destination File.expand_path("../../../tmp", __FILE__)
|
7
|
+
before { prepare_destination }
|
8
|
+
|
9
|
+
it 'runs generating views tasks' do
|
10
|
+
gen = generator
|
11
|
+
expect(gen).to receive :copy_views
|
12
|
+
gen.invoke_all
|
13
|
+
end
|
14
|
+
|
15
|
+
describe 'the generated files' do
|
16
|
+
context 'without target argument' do
|
17
|
+
context 'with target views as default' do
|
18
|
+
before do
|
19
|
+
run_generator
|
20
|
+
end
|
21
|
+
|
22
|
+
describe 'the notification views' do
|
23
|
+
describe 'default/_default.html.erb' do
|
24
|
+
subject { file('app/views/activity_notification/notifications/default/_default.html.erb') }
|
25
|
+
it { is_expected.to exist }
|
26
|
+
end
|
27
|
+
|
28
|
+
describe 'default/_index.html.erb' do
|
29
|
+
subject { file('app/views/activity_notification/notifications/default/_index.html.erb') }
|
30
|
+
it { is_expected.to exist }
|
31
|
+
end
|
32
|
+
|
33
|
+
describe 'default/destroy.js.erb' do
|
34
|
+
subject { file('app/views/activity_notification/notifications/default/destroy.js.erb') }
|
35
|
+
it { is_expected.to exist }
|
36
|
+
end
|
37
|
+
|
38
|
+
describe 'default/index.html.erb' do
|
39
|
+
subject { file('app/views/activity_notification/notifications/default/index.html.erb') }
|
40
|
+
it { is_expected.to exist }
|
41
|
+
end
|
42
|
+
|
43
|
+
describe 'default/open_all.js.erb' do
|
44
|
+
subject { file('app/views/activity_notification/notifications/default/open_all.js.erb') }
|
45
|
+
it { is_expected.to exist }
|
46
|
+
end
|
47
|
+
|
48
|
+
describe 'default/open.js.erb' do
|
49
|
+
subject { file('app/views/activity_notification/notifications/default/open.js.erb') }
|
50
|
+
it { is_expected.to exist }
|
51
|
+
end
|
52
|
+
|
53
|
+
describe 'default/show.html.erb' do
|
54
|
+
subject { file('app/views/activity_notification/notifications/default/show.html.erb') }
|
55
|
+
it { is_expected.to exist }
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
describe 'the mailer views' do
|
60
|
+
describe 'default/default.html.erb' do
|
61
|
+
subject { file('app/views/activity_notification/mailer/default/default.html.erb') }
|
62
|
+
it { is_expected.to exist }
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
context 'with a views option as notifications' do
|
68
|
+
before do
|
69
|
+
run_generator %w(--views notifications)
|
70
|
+
end
|
71
|
+
|
72
|
+
describe 'the notification views' do
|
73
|
+
describe 'default/index.html.erb' do
|
74
|
+
subject { file('app/views/activity_notification/notifications/default/index.html.erb') }
|
75
|
+
it { is_expected.to exist }
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
describe 'the mailer views' do
|
80
|
+
describe 'default/default.html.erb' do
|
81
|
+
subject { file('app/views/activity_notification/mailer/default/default.html.erb') }
|
82
|
+
it { is_expected.not_to exist }
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
context 'with users as target' do
|
89
|
+
context 'with target views as default' do
|
90
|
+
before do
|
91
|
+
run_generator %w(users)
|
92
|
+
end
|
93
|
+
|
94
|
+
describe 'the notification views' do
|
95
|
+
describe 'users/index.html.erb' do
|
96
|
+
subject { file('app/views/activity_notification/notifications/users/index.html.erb') }
|
97
|
+
it { is_expected.to exist }
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
describe 'the mailer views' do
|
102
|
+
describe 'users/default.html.erb' do
|
103
|
+
subject { file('app/views/activity_notification/mailer/users/default.html.erb') }
|
104
|
+
it { is_expected.to exist }
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
end
|
111
|
+
end
|