message_train 0.7.5 → 1.0.0
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 +5 -5
- data/.gitattributes +1 -0
- data/.github/workflows/ci.yml +86 -0
- data/.gitignore +64 -0
- data/.rubocop.yml +9 -0
- data/.rubocop_todo.yml +854 -2
- data/.ruby-version +1 -1
- data/.simplecov +2 -4
- data/CHANGELOG.md +128 -0
- data/CLAUDE.md +5 -0
- data/Gemfile +30 -32
- data/LICENSE.txt +1 -1
- data/README.md +106 -35
- data/Rakefile +4 -18
- data/app/assets/stylesheets/message_train.scss +405 -67
- data/app/controllers/concerns/message_train_support.rb +2 -2
- data/app/controllers/message_train/boxes_controller.rb +4 -2
- data/app/controllers/message_train/conversations_controller.rb +1 -0
- data/app/helpers/message_train/application_helper.rb +68 -0
- data/app/helpers/message_train/attachments_helper.rb +7 -4
- data/app/helpers/message_train/conversations_helper.rb +16 -16
- data/app/helpers/message_train/messages_helper.rb +23 -10
- data/app/javascript/controllers/message_train/bulk_actions_controller.js +36 -0
- data/app/javascript/controllers/message_train/lightbox_controller.js +25 -0
- data/app/javascript/controllers/message_train/nested_form_controller.js +36 -0
- data/app/javascript/controllers/message_train/recipient_autocomplete_controller.js +31 -0
- data/app/mailers/message_train/previews/receipt_mailer_preview.rb +7 -5
- data/app/models/message_train/attachment.rb +54 -42
- data/app/models/message_train/box.rb +2 -2
- data/app/models/message_train/conversation.rb +28 -2
- data/app/models/message_train/ignore.rb +2 -2
- data/app/models/message_train/message.rb +10 -4
- data/app/models/message_train/receipt.rb +27 -4
- data/app/models/message_train/unsubscribe.rb +1 -1
- data/app/views/application/404.html.haml +1 -3
- data/app/views/kaminari/_first_page.html.haml +1 -1
- data/app/views/kaminari/_last_page.html.haml +1 -1
- data/app/views/kaminari/_next_page.html.haml +1 -1
- data/app/views/kaminari/_page.html.haml +3 -3
- data/app/views/kaminari/_paginator.html.haml +12 -11
- data/app/views/kaminari/_prev_page.html.haml +1 -1
- data/app/views/message_train/application/_attachment_fields.html.haml +10 -7
- data/app/views/message_train/application/_attachment_link.html.haml +2 -2
- data/app/views/message_train/application/_widget.html.haml +2 -4
- data/app/views/message_train/boxes/_widget.html.haml +5 -5
- data/app/views/message_train/boxes/show.html.haml +28 -45
- data/app/views/message_train/collectives/_widget.html.haml +4 -4
- data/app/views/message_train/conversations/_conversation.html.haml +11 -11
- data/app/views/message_train/conversations/_deleted_toggle.html.haml +1 -1
- data/app/views/message_train/conversations/_ignored_toggle.html.haml +2 -2
- data/app/views/message_train/conversations/_read_toggle.html.haml +2 -2
- data/app/views/message_train/conversations/_toggle.html.haml +6 -4
- data/app/views/message_train/conversations/_trashed_toggle.html.haml +2 -2
- data/app/views/message_train/conversations/show.html.haml +8 -6
- data/app/views/message_train/messages/_deleted_toggle.html.haml +1 -1
- data/app/views/message_train/messages/_form.html.haml +22 -30
- data/app/views/message_train/messages/_message.html.haml +27 -45
- data/app/views/message_train/messages/_read_toggle.html.haml +2 -2
- data/app/views/message_train/messages/_toggle.html.haml +5 -1
- data/app/views/message_train/messages/_trashed_toggle.html.haml +2 -2
- data/app/views/message_train/unsubscribes/index.html.haml +12 -15
- data/badges/coverage.json +1 -0
- data/config/importmap.rb +2 -0
- data/config/locales/en.yml +6 -3
- data/db/migrate/20150721145319_create_message_train_conversations.rb +1 -1
- data/db/migrate/20150721160322_create_message_train_messages.rb +1 -1
- data/db/migrate/20150721161144_create_message_train_attachments.rb +5 -2
- data/db/migrate/20150721161940_create_message_train_receipts.rb +1 -1
- data/db/migrate/20150721163838_create_message_train_ignores.rb +1 -1
- data/db/migrate/20150901183458_add_received_through_to_message_train_receipts.rb +1 -1
- data/db/migrate/20151004184347_add_unique_index_to_receipts.rb +1 -1
- data/db/migrate/20151124000820_create_message_train_unsubscribes.rb +1 -1
- data/db/migrate/20260827095938_remove_paperclip_columns_from_message_train_attachments.rb +8 -0
- data/db/migrate/20260827171549_remove_body_from_message_train_messages.rb +5 -0
- data/db/migrate/20260827180000_add_marking_indexes.rb +9 -0
- data/db/migrate/20260827200000_add_body_back_to_message_train_messages.rb +5 -0
- data/docs/screenshots/inbox-desktop.png +0 -0
- data/docs/screenshots/inbox-mobile.png +0 -0
- data/lib/generators/message_train/install/install_generator.rb +1 -1
- data/lib/message_train/engine.rb +11 -0
- data/lib/message_train/instance_methods.rb +13 -7
- data/lib/message_train/localization.rb +4 -3
- data/lib/message_train/version.rb +1 -1
- data/lib/message_train.rb +0 -2
- data/message_train.gemspec +57 -389
- data/spec/controllers/message_train/boxes_controller_spec.rb +13 -8
- data/spec/controllers/message_train/concerns_spec.rb +2 -2
- data/spec/controllers/message_train/conversations_controller_spec.rb +36 -7
- data/spec/controllers/message_train/messages_controller_spec.rb +4 -4
- data/spec/dummy/app/assets/stylesheets/application.scss +141 -12
- data/spec/dummy/app/controllers/application_controller.rb +1 -1
- data/spec/dummy/app/javascript/application.js +4 -0
- data/spec/dummy/app/javascript/controllers/application.js +9 -0
- data/spec/dummy/app/javascript/controllers/index.js +4 -0
- data/spec/dummy/app/models/role.rb +1 -1
- data/spec/dummy/app/views/active_storage/blobs/_blob.html.erb +14 -0
- data/spec/dummy/app/views/layouts/_top_navigation.html.haml +18 -7
- data/spec/dummy/app/views/layouts/application.html.haml +22 -26
- data/spec/dummy/bin/importmap +4 -0
- data/spec/dummy/config/application.rb +7 -20
- data/spec/dummy/config/environments/development.rb +17 -21
- data/spec/dummy/config/environments/production.rb +11 -63
- data/spec/dummy/config/environments/test.rb +21 -16
- data/spec/dummy/config/importmap.rb +7 -0
- data/spec/dummy/config/initializers/assets.rb +1 -6
- data/spec/dummy/config/routes.rb +1 -1
- data/spec/dummy/config/storage.yml +7 -0
- data/spec/dummy/db/migrate/20150721140013_devise_create_users.rb +1 -1
- data/spec/dummy/db/migrate/20150721141009_rolify_create_roles.rb +1 -1
- data/spec/dummy/db/migrate/20150721141128_create_groups.rb +1 -1
- data/spec/dummy/db/migrate/20150721150307_add_display_name_column_to_users.rb +1 -1
- data/spec/dummy/db/migrate/20160207190402_create_message_train_conversations.message_train.rb +1 -1
- data/spec/dummy/db/migrate/20160207190403_create_message_train_messages.message_train.rb +1 -1
- data/spec/dummy/db/migrate/20160207190404_create_message_train_attachments.message_train.rb +5 -2
- data/spec/dummy/db/migrate/20160207190405_create_message_train_receipts.message_train.rb +1 -1
- data/spec/dummy/db/migrate/20160207190406_create_message_train_ignores.message_train.rb +1 -1
- data/spec/dummy/db/migrate/20160207190407_add_received_through_to_message_train_receipts.message_train.rb +1 -1
- data/spec/dummy/db/migrate/20160207190408_add_unique_index_to_receipts.message_train.rb +1 -1
- data/spec/dummy/db/migrate/20160207190409_create_message_train_unsubscribes.message_train.rb +1 -1
- data/spec/dummy/db/migrate/20260827095939_remove_paperclip_columns_from_message_train_attachments.message_train.rb +9 -0
- data/spec/dummy/db/migrate/20260827161141_create_active_storage_tables.active_storage.rb +57 -0
- data/spec/dummy/db/migrate/20260827171550_remove_body_from_message_train_messages.message_train.rb +6 -0
- data/spec/dummy/db/migrate/20260827225116_add_marking_indexes.message_train.rb +10 -0
- data/spec/dummy/db/migrate/20260828034830_add_body_back_to_message_train_messages.message_train.rb +6 -0
- data/spec/dummy/db/migrate/20260828040000_drop_action_text_tables.rb +5 -0
- data/spec/dummy/db/schema.rb +106 -72
- data/spec/dummy/db/seeds/conversations.seeds.rb +22 -22
- data/spec/dummy/db/seeds/development/conversations.seeds.rb +2 -2
- data/spec/dummy/db/seeds/groups.seeds.rb +4 -4
- data/spec/dummy/db/seeds/test/attachments.seeds.rb +4 -4
- data/spec/dummy/db/seeds/test/conversations.seeds.rb +2 -2
- data/spec/dummy/db/seeds/users.seeds.rb +8 -8
- data/spec/dummy/vendor/javascript/.keep +0 -0
- data/spec/factories/attachment.rb +5 -6
- data/spec/factories/group.rb +2 -2
- data/spec/factories/message.rb +7 -7
- data/spec/factories/user.rb +3 -3
- data/spec/features/boxes_spec.rb +38 -34
- data/spec/features/conversations_spec.rb +13 -6
- data/spec/features/messages_spec.rb +20 -20
- data/spec/features/unsubscribes_spec.rb +9 -9
- data/spec/helpers/message_train/application_helper_spec.rb +16 -18
- data/spec/helpers/message_train/attachment_helper_spec.rb +8 -18
- data/spec/helpers/message_train/boxes_helper_spec.rb +6 -6
- data/spec/helpers/message_train/collectives_helper_spec.rb +10 -10
- data/spec/helpers/message_train/conversations_helper_spec.rb +43 -85
- data/spec/helpers/message_train/messages_helper_spec.rb +41 -61
- data/spec/models/message_train/attachment_spec.rb +26 -36
- data/spec/models/message_train/box_spec.rb +2 -2
- data/spec/models/message_train/conversation_spec.rb +15 -0
- data/spec/models/message_train/message_spec.rb +5 -1
- data/spec/models/message_train/receipt_spec.rb +1 -1
- data/spec/models/message_train/unsubscribe_spec.rb +1 -1
- data/spec/rails_helper.rb +14 -13
- data/spec/support/controller_behaviors.rb +13 -13
- data/spec/support/factory_bot.rb +3 -0
- data/spec/support/feature_behaviors.rb +14 -14
- data/spec/support/loaded_site/attachments.rb +6 -2
- data/spec/support/utilities.rb +49 -20
- metadata +260 -189
- data/.travis.yml +0 -12
- data/VERSION +0 -1
- data/app/assets/javascripts/ckeditor/config.js +0 -49
- data/app/assets/javascripts/message_train.js +0 -149
- data/spec/dummy/app/assets/javascripts/application.js +0 -17
- data/spec/dummy/app/assets/stylesheets/bootstrap-everything.scss +0 -54
- data/spec/dummy/config/initializers/bootstrap_leather.rb +0 -7
- data/spec/dummy/config/initializers/paperclip.rb +0 -5
- data/spec/dummy/config/secrets.yml +0 -22
- data/spec/dummy/db/test.sqlite3 +0 -0
- data/spec/support/factory_girl.rb +0 -3
|
@@ -8,7 +8,7 @@ module MessageTrain
|
|
|
8
8
|
# Relationships
|
|
9
9
|
it { should belong_to :message }
|
|
10
10
|
it { should belong_to :recipient }
|
|
11
|
-
it { should belong_to
|
|
11
|
+
it { should belong_to(:received_through).optional }
|
|
12
12
|
# Validations
|
|
13
13
|
it { should validate_presence_of :message }
|
|
14
14
|
it { should validate_presence_of :recipient }
|
data/spec/rails_helper.rb
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
require '
|
|
2
|
-
Coveralls.wear!
|
|
1
|
+
require 'simplecov'
|
|
3
2
|
|
|
4
3
|
# This file is copied to spec/ when you run 'rails generate rspec:install'
|
|
5
4
|
ENV['RAILS_ENV'] ||= 'test'
|
|
@@ -12,11 +11,9 @@ require 'spec_helper'
|
|
|
12
11
|
require 'rspec/rails'
|
|
13
12
|
# Add additional requires below this line. Rails is not loaded until this point!
|
|
14
13
|
require 'shoulda/matchers'
|
|
15
|
-
require 'factory_girl_rails'
|
|
16
|
-
require 'paperclip/matchers'
|
|
17
14
|
require 'capybara/rspec'
|
|
18
|
-
require 'capybara/
|
|
19
|
-
require 'database_cleaner'
|
|
15
|
+
require 'capybara/cuprite'
|
|
16
|
+
require 'database_cleaner/active_record'
|
|
20
17
|
require 'rake'
|
|
21
18
|
|
|
22
19
|
# Requires supporting ruby files with custom matchers and macros, etc, in
|
|
@@ -42,7 +39,10 @@ Dir[MessageTrain::Engine.root.join('spec/support/**/*.rb')]
|
|
|
42
39
|
ActiveRecord::Migrator.migrations_paths = 'spec/dummy/db/migrate'
|
|
43
40
|
ActiveRecord::Migration.maintain_test_schema!
|
|
44
41
|
|
|
45
|
-
Capybara.
|
|
42
|
+
Capybara.register_driver(:cuprite) do |app|
|
|
43
|
+
Capybara::Cuprite::Driver.new(app, window_size: [1200, 800], process_timeout: 15)
|
|
44
|
+
end
|
|
45
|
+
Capybara.javascript_driver = :cuprite
|
|
46
46
|
|
|
47
47
|
Shoulda::Matchers.configure do |config|
|
|
48
48
|
config.integrate do |with|
|
|
@@ -57,6 +57,12 @@ RSpec.configure do |config|
|
|
|
57
57
|
config.before(:suite) do
|
|
58
58
|
DatabaseCleaner.strategy = :transaction
|
|
59
59
|
DatabaseCleaner.clean_with(:truncation)
|
|
60
|
+
# Seedbank resolves seed files relative to Rake's process cwd
|
|
61
|
+
# (Rake.application.original_dir), not Rails.root, and memoizes that on
|
|
62
|
+
# first access. Since specs run from the gem root, not spec/dummy, this
|
|
63
|
+
# must be set before Dummy::Application.load_tasks triggers Seedbank's
|
|
64
|
+
# own task generation, or it silently finds no seed files at all.
|
|
65
|
+
Seedbank.application_root = MessageTrain::Engine.root.join('spec/dummy')
|
|
60
66
|
Dummy::Application.load_tasks
|
|
61
67
|
Rake::Task['db:seed'].invoke # loading seeds
|
|
62
68
|
end
|
|
@@ -82,9 +88,6 @@ RSpec.configure do |config|
|
|
|
82
88
|
# https://relishapp.com/rspec/rspec-rails/docs
|
|
83
89
|
config.infer_spec_type_from_file_location!
|
|
84
90
|
|
|
85
|
-
config.expect_with(:rspec) { |c| c.syntax = [:should, :expect] }
|
|
86
|
-
config.mock_with(:rspec) { |c| c.syntax = [:should, :expect] }
|
|
87
|
-
|
|
88
91
|
config.before(:each) { @routes = MessageTrain::Engine.routes }
|
|
89
92
|
config.include MessageTrain::Engine.routes.url_helpers
|
|
90
93
|
|
|
@@ -94,9 +97,7 @@ RSpec.configure do |config|
|
|
|
94
97
|
config.include Devise::Test::ControllerHelpers, type: :routing
|
|
95
98
|
config.include RSpecHtmlMatchers
|
|
96
99
|
|
|
97
|
-
# config.include(Shoulda::Matchers::ActiveRecord, type: :model)
|
|
98
|
-
config.include Paperclip::Shoulda::Matchers
|
|
99
100
|
config.after(:suite) do
|
|
100
|
-
FileUtils.rm_rf(Dir["#{Rails.root}/
|
|
101
|
+
FileUtils.rm_rf(Dir["#{Rails.root}/tmp/storage/*"])
|
|
101
102
|
end
|
|
102
103
|
end
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
shared_examples_for 'a successful page' do |options = {}|
|
|
2
2
|
context 'responds successfully' do
|
|
3
3
|
subject { response }
|
|
4
|
-
it {
|
|
4
|
+
it { is_expected.to be_successful }
|
|
5
5
|
end
|
|
6
6
|
if options[:which_renders].present?
|
|
7
7
|
it_behaves_like 'a page rendering a template', options[:which_renders]
|
|
@@ -10,25 +10,25 @@ end
|
|
|
10
10
|
shared_examples_for 'a page rendering a template' do |template|
|
|
11
11
|
context "renders the #{template} template" do
|
|
12
12
|
subject { response }
|
|
13
|
-
it {
|
|
13
|
+
it { is_expected.to render_template(template) }
|
|
14
14
|
end
|
|
15
15
|
end
|
|
16
16
|
shared_examples_for 'an error response' do |http_status|
|
|
17
17
|
context "issues error response code #{http_status}" do
|
|
18
18
|
subject { response }
|
|
19
|
-
it {
|
|
19
|
+
it { is_expected.to have_http_status(http_status) }
|
|
20
20
|
end
|
|
21
21
|
end
|
|
22
22
|
shared_examples_for 'a redirect to' do |path|
|
|
23
23
|
context "redirects to #{path}" do
|
|
24
24
|
subject { response }
|
|
25
|
-
it {
|
|
25
|
+
it { is_expected.to redirect_to path_to_url(path) }
|
|
26
26
|
end
|
|
27
27
|
end
|
|
28
28
|
shared_examples_for 'a redirect matching' do |path_expression|
|
|
29
29
|
context "redirects matching #{path_expression}" do
|
|
30
30
|
subject { response.location }
|
|
31
|
-
it {
|
|
31
|
+
it { is_expected.to match path_expression }
|
|
32
32
|
end
|
|
33
33
|
end
|
|
34
34
|
shared_examples_for 'a redirect to sign in' do
|
|
@@ -39,7 +39,7 @@ shared_examples_for 'a redirect with a message' do |path, options|
|
|
|
39
39
|
options.each do |key, value|
|
|
40
40
|
context "sets #{key} message" do
|
|
41
41
|
subject { flash[key] }
|
|
42
|
-
it {
|
|
42
|
+
it { is_expected.to eq value }
|
|
43
43
|
end
|
|
44
44
|
end
|
|
45
45
|
end
|
|
@@ -47,7 +47,7 @@ shared_examples_for 'an error response with message' do |message|
|
|
|
47
47
|
it_behaves_like 'an error response', :forbidden
|
|
48
48
|
context 'sets failure message' do
|
|
49
49
|
subject { flash[:error] }
|
|
50
|
-
it {
|
|
50
|
+
it { is_expected.to eq message }
|
|
51
51
|
end
|
|
52
52
|
end
|
|
53
53
|
shared_examples_for 'a 404 Not Found error' do
|
|
@@ -59,7 +59,7 @@ shared_examples_for 'a page with a message' do |options|
|
|
|
59
59
|
options.each do |key, value|
|
|
60
60
|
context "sets #{key} message" do
|
|
61
61
|
subject { flash[key] }
|
|
62
|
-
it {
|
|
62
|
+
it { is_expected.to eq value }
|
|
63
63
|
end
|
|
64
64
|
end
|
|
65
65
|
end
|
|
@@ -68,7 +68,7 @@ shared_examples_for 'a page with a message matching' do |options|
|
|
|
68
68
|
options.each do |key, value|
|
|
69
69
|
context "sets #{key} message" do
|
|
70
70
|
subject { flash[key] }
|
|
71
|
-
it {
|
|
71
|
+
it { is_expected.to match value }
|
|
72
72
|
end
|
|
73
73
|
end
|
|
74
74
|
end
|
|
@@ -94,26 +94,26 @@ shared_examples_for 'a redirect with error' do |path, message|
|
|
|
94
94
|
it_behaves_like 'a redirect to', path
|
|
95
95
|
context 'sets error' do
|
|
96
96
|
subject { flash[:error] }
|
|
97
|
-
it {
|
|
97
|
+
it { is_expected.to eq message }
|
|
98
98
|
end
|
|
99
99
|
end
|
|
100
100
|
shared_examples_for 'a redirect with notice' do |path, message|
|
|
101
101
|
it_behaves_like 'a redirect to', path
|
|
102
102
|
context 'sets notice' do
|
|
103
103
|
subject { flash[:notice] }
|
|
104
|
-
it {
|
|
104
|
+
it { is_expected.to eq message }
|
|
105
105
|
end
|
|
106
106
|
end
|
|
107
107
|
shared_examples_for 'a redirect with alert' do |path, message|
|
|
108
108
|
it_behaves_like 'a redirect to', path
|
|
109
109
|
context 'sets alert' do
|
|
110
110
|
subject { flash[:alert] }
|
|
111
|
-
it {
|
|
111
|
+
it { is_expected.to eq message }
|
|
112
112
|
end
|
|
113
113
|
end
|
|
114
114
|
shared_examples_for 'a response without error' do
|
|
115
115
|
context 'sets no errors' do
|
|
116
116
|
subject { flash[:error] }
|
|
117
|
-
it {
|
|
117
|
+
it { is_expected.to be_nil }
|
|
118
118
|
end
|
|
119
119
|
end
|
|
@@ -10,8 +10,8 @@ shared_examples_for 'an authenticated section' do |path|
|
|
|
10
10
|
)
|
|
11
11
|
context 'displays a login form' do
|
|
12
12
|
subject { page }
|
|
13
|
-
it {
|
|
14
|
-
it {
|
|
13
|
+
it { is_expected.to have_selector 'form.new_user input#user_email' }
|
|
14
|
+
it { is_expected.to have_selector 'form.new_user input#user_password' }
|
|
15
15
|
end
|
|
16
16
|
end
|
|
17
17
|
end
|
|
@@ -21,14 +21,14 @@ shared_examples_for 'a bootstrap page' do |options = {}|
|
|
|
21
21
|
context 'displays a page with bootstrap elements' do
|
|
22
22
|
subject { page }
|
|
23
23
|
if options[:title].present?
|
|
24
|
-
it {
|
|
25
|
-
it {
|
|
24
|
+
it { is_expected.to have_title html_escape(options[:title]) }
|
|
25
|
+
it { is_expected.to have_xpath '//h2', text: options[:title] }
|
|
26
26
|
end
|
|
27
27
|
it do
|
|
28
|
-
|
|
28
|
+
expect(page).to(
|
|
29
29
|
have_selector(
|
|
30
|
-
'.
|
|
31
|
-
text:
|
|
30
|
+
'.top-navigation .site-brand',
|
|
31
|
+
text: MessageTrain.configuration.site_name
|
|
32
32
|
)
|
|
33
33
|
)
|
|
34
34
|
end
|
|
@@ -40,21 +40,21 @@ shared_examples_for(
|
|
|
40
40
|
) do |options|
|
|
41
41
|
if options[:text]
|
|
42
42
|
it do
|
|
43
|
-
|
|
43
|
+
expect(page).to have_selector 'details.message-train-dropdown-nav-item summary', text: options[:text]
|
|
44
44
|
end
|
|
45
45
|
else
|
|
46
|
-
it {
|
|
46
|
+
it { is_expected.to have_selector 'details.message-train-dropdown-nav-item summary' }
|
|
47
47
|
end
|
|
48
48
|
end
|
|
49
49
|
|
|
50
50
|
shared_examples_for 'a bootstrap page with an alert' do |type, text|
|
|
51
51
|
subject { page }
|
|
52
|
-
it {
|
|
52
|
+
it { is_expected.to have_selector "#alert_area .alert.#{type}", text: text }
|
|
53
53
|
end
|
|
54
54
|
|
|
55
55
|
shared_examples_for 'a bootstrap page without an alert' do |type|
|
|
56
56
|
subject { page }
|
|
57
|
-
it {
|
|
57
|
+
it { is_expected.not_to have_selector "#alert_area .alert.#{type}" }
|
|
58
58
|
end
|
|
59
59
|
|
|
60
60
|
shared_examples_for(
|
|
@@ -69,7 +69,7 @@ shared_examples_for(
|
|
|
69
69
|
context "displays a list of ##{collection_css_id} in .#{member_css_class}" do
|
|
70
70
|
subject { page }
|
|
71
71
|
it do
|
|
72
|
-
|
|
72
|
+
expect(page).to(
|
|
73
73
|
have_css(
|
|
74
74
|
"##{collection_css_id} .#{member_css_class}",
|
|
75
75
|
minimum: options[:minimum]
|
|
@@ -86,7 +86,7 @@ shared_examples_for(
|
|
|
86
86
|
it_behaves_like 'a bootstrap page', title: title
|
|
87
87
|
context "displays an item at ##{options[:css_id]}" do
|
|
88
88
|
subject { page }
|
|
89
|
-
it {
|
|
89
|
+
it { is_expected.to have_css("##{options[:css_id]}") }
|
|
90
90
|
end
|
|
91
91
|
end
|
|
92
92
|
|
|
@@ -100,7 +100,7 @@ shared_examples_for 'a bootstrap form with errors' do |error_fields|
|
|
|
100
100
|
)
|
|
101
101
|
context 'shows which field has errors' do
|
|
102
102
|
subject { page }
|
|
103
|
-
it {
|
|
103
|
+
it { is_expected.to have_css('.field_with_errors', text: field.to_s.humanize) }
|
|
104
104
|
end
|
|
105
105
|
end
|
|
106
106
|
end
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
shared_context 'attachments' do
|
|
2
2
|
let(:image_attachment) do
|
|
3
|
-
MessageTrain::Attachment
|
|
3
|
+
MessageTrain::Attachment
|
|
4
|
+
.joins(:attachment_blob)
|
|
5
|
+
.find_by(active_storage_blobs: { filename: 'image-sample.jpg' })
|
|
4
6
|
end
|
|
5
7
|
let(:pdf_attachment) do
|
|
6
|
-
MessageTrain::Attachment
|
|
8
|
+
MessageTrain::Attachment
|
|
9
|
+
.joins(:attachment_blob)
|
|
10
|
+
.find_by(active_storage_blobs: { filename: 'pdf-sample.pdf' })
|
|
7
11
|
end
|
|
8
12
|
end
|
data/spec/support/utilities.rb
CHANGED
|
@@ -9,33 +9,62 @@ def show_page
|
|
|
9
9
|
end
|
|
10
10
|
|
|
11
11
|
def submit_via_button(button_name)
|
|
12
|
-
|
|
13
|
-
page.execute_script("$('##{button_id}').click();")
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
# Used to fill ckeditor fields
|
|
17
|
-
# @param [String] locator label text for the textarea or textarea id
|
|
18
|
-
def fill_in_ckeditor(locator, options)
|
|
19
|
-
locator = find_field_by_label(locator)
|
|
20
|
-
# Fill the editor content
|
|
21
|
-
page.execute_script <<-SCRIPT
|
|
22
|
-
var ckeditor = CKEDITOR.instances.#{locator}
|
|
23
|
-
ckeditor.setData('#{ActionController::Base.helpers.j options[:with]}')
|
|
24
|
-
ckeditor.focus()
|
|
25
|
-
ckeditor.updateElement()
|
|
26
|
-
SCRIPT
|
|
12
|
+
click_button button_name
|
|
27
13
|
end
|
|
28
14
|
|
|
29
15
|
def fill_in_autocomplete(field, options = {})
|
|
30
16
|
field = find_field_by_label(field)
|
|
31
17
|
fill_in field, with: options[:with]
|
|
32
18
|
|
|
33
|
-
|
|
34
|
-
|
|
19
|
+
# The Stimulus controller debounces the search and populates a <datalist>
|
|
20
|
+
# with the matching participant slugs; resolve to the first suggestion
|
|
21
|
+
# rather than submitting the raw partial query as the recipient. Some
|
|
22
|
+
# callers intentionally use a query with no real match (e.g. to leave
|
|
23
|
+
# recipients unresolved for a draft), so don't wait/fail if none appear.
|
|
24
|
+
list_id = find_field(field)[:list]
|
|
25
|
+
return unless page.has_css?("##{list_id} option", visible: :all, wait: 1)
|
|
26
|
+
|
|
27
|
+
suggestion = find("##{list_id} option", visible: :all)
|
|
28
|
+
fill_in field, with: suggestion[:value]
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Cuprite drives the app on a real server thread with its own DB
|
|
32
|
+
# connection, so any marking it does through the browser commits outside
|
|
33
|
+
# the spec's transaction and would otherwise leak into later examples
|
|
34
|
+
# that reuse the same fixture conversation. Undo it the same way: on a
|
|
35
|
+
# separate thread, so this write also commits immediately instead of
|
|
36
|
+
# being rolled back with the rest of this example's (this thread's)
|
|
37
|
+
# transaction. SQLite only allows one writer at a time, and the RSpec
|
|
38
|
+
# thread's still-open transaction can be holding that lock at any given
|
|
39
|
+
# moment, so retry a few times rather than racing it.
|
|
40
|
+
def on_separate_connection(retries: 15)
|
|
41
|
+
Thread.new do
|
|
42
|
+
ActiveRecord::Base.connection_pool.with_connection do
|
|
43
|
+
begin
|
|
44
|
+
yield
|
|
45
|
+
rescue ActiveRecord::StatementInvalid => e
|
|
46
|
+
raise unless e.cause.is_a?(SQLite3::BusyException) && retries.positive?
|
|
47
|
+
|
|
48
|
+
sleep 0.3
|
|
49
|
+
retries -= 1
|
|
50
|
+
retry
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end.join
|
|
54
|
+
end
|
|
35
55
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
56
|
+
def undo_leaked_mark(mark_to_set, conversation: unread_conversation)
|
|
57
|
+
on_separate_connection do
|
|
58
|
+
box = first_user.box(:in)
|
|
59
|
+
# Ignore/unignore isn't a Receipt "marked_*" column like the other
|
|
60
|
+
# marks - it's tracked on its own model, routed through here rather
|
|
61
|
+
# than Box#mark (matching how BoxesController#destroy dispatches it).
|
|
62
|
+
if %i[ignore unignore].include?(mark_to_set)
|
|
63
|
+
MessageTrain::Ignore.send(mark_to_set, conversation, box)
|
|
64
|
+
else
|
|
65
|
+
box.mark(mark_to_set, conversations: conversation)
|
|
66
|
+
end
|
|
67
|
+
end
|
|
39
68
|
end
|
|
40
69
|
|
|
41
70
|
def find_field_by_label(locator)
|