fat_free_crm 0.19.2 → 0.20.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of fat_free_crm might be problematic. Click here for more details.

Files changed (226) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/brakeman-analysis.yml +46 -0
  3. data/.github/workflows/rubocop-analysis.yml +40 -0
  4. data/.github/workflows/ruby.yml +52 -0
  5. data/.rubocop_todo.yml +49 -43
  6. data/.travis.yml +1 -20
  7. data/CHANGELOG.md +13 -0
  8. data/Gemfile +9 -5
  9. data/Gemfile.lock +163 -119
  10. data/README.md +4 -3
  11. data/SECURITY.md +11 -0
  12. data/app/assets/javascripts/application.js.erb +3 -0
  13. data/app/assets/stylesheets/application.css.erb +1 -0
  14. data/app/assets/stylesheets/base.scss +9 -2
  15. data/app/assets/stylesheets/bootstrap-custom.scss +206 -0
  16. data/app/assets/stylesheets/common.scss +87 -67
  17. data/app/assets/stylesheets/header.scss +0 -8
  18. data/app/controllers/admin/field_groups_controller.rb +1 -1
  19. data/app/controllers/admin/fields_controller.rb +1 -1
  20. data/app/controllers/admin/groups_controller.rb +1 -1
  21. data/app/controllers/admin/tags_controller.rb +2 -2
  22. data/app/controllers/admin/users_controller.rb +2 -1
  23. data/app/controllers/application_controller.rb +10 -0
  24. data/app/controllers/comments_controller.rb +1 -1
  25. data/app/controllers/entities/accounts_controller.rb +1 -1
  26. data/app/controllers/entities/campaigns_controller.rb +1 -1
  27. data/app/controllers/home_controller.rb +3 -1
  28. data/app/controllers/lists_controller.rb +1 -1
  29. data/app/controllers/tasks_controller.rb +3 -3
  30. data/app/controllers/users_controller.rb +3 -1
  31. data/app/helpers/accounts_helper.rb +12 -0
  32. data/app/helpers/application_helper.rb +3 -5
  33. data/app/helpers/tasks_helper.rb +2 -2
  34. data/app/models/entities/account.rb +1 -1
  35. data/app/models/entities/account_contact.rb +1 -1
  36. data/app/models/entities/account_opportunity.rb +1 -1
  37. data/app/models/entities/campaign.rb +1 -1
  38. data/app/models/entities/contact.rb +1 -1
  39. data/app/models/entities/lead.rb +1 -1
  40. data/app/models/entities/opportunity.rb +1 -1
  41. data/app/models/fields/custom_field_pair.rb +2 -2
  42. data/app/models/observers/opportunity_observer.rb +1 -1
  43. data/app/models/polymorphic/address.rb +1 -1
  44. data/app/models/polymorphic/comment.rb +1 -1
  45. data/app/models/polymorphic/email.rb +1 -1
  46. data/app/models/polymorphic/task.rb +1 -1
  47. data/app/models/setting.rb +4 -2
  48. data/app/models/users/preference.rb +2 -2
  49. data/app/models/users/user.rb +2 -2
  50. data/app/views/accounts/_edit.html.haml +2 -2
  51. data/app/views/accounts/_new.html.haml +2 -2
  52. data/app/views/accounts/_sidebar_index.html.haml +2 -2
  53. data/app/views/accounts/_sidebar_show.html.haml +19 -19
  54. data/app/views/accounts/create.js.haml +1 -2
  55. data/app/views/accounts/destroy.js.haml +1 -1
  56. data/app/views/accounts/show.html.haml +1 -0
  57. data/app/views/accounts/update.js.haml +2 -3
  58. data/app/views/admin/field_groups/create.js.haml +0 -1
  59. data/app/views/admin/field_groups/destroy.js.haml +0 -1
  60. data/app/views/admin/field_groups/update.js.haml +0 -1
  61. data/app/views/admin/fields/create.js.haml +0 -1
  62. data/app/views/admin/fields/destroy.js.haml +0 -1
  63. data/app/views/admin/fields/update.js.haml +0 -1
  64. data/app/views/admin/groups/create.js.haml +0 -1
  65. data/app/views/admin/groups/destroy.js.haml +0 -1
  66. data/app/views/admin/groups/update.js.haml +0 -1
  67. data/app/views/admin/tags/create.js.haml +0 -1
  68. data/app/views/admin/tags/destroy.js.haml +0 -1
  69. data/app/views/admin/tags/update.js.haml +0 -1
  70. data/app/views/admin/users/create.js.haml +0 -1
  71. data/app/views/admin/users/destroy.js.haml +0 -1
  72. data/app/views/admin/users/update.js.haml +0 -1
  73. data/app/views/campaigns/_edit.html.haml +1 -1
  74. data/app/views/campaigns/_new.html.haml +1 -1
  75. data/app/views/campaigns/_sidebar_index.html.haml +2 -2
  76. data/app/views/campaigns/_sidebar_show.html.haml +68 -67
  77. data/app/views/campaigns/create.js.haml +1 -2
  78. data/app/views/campaigns/destroy.js.haml +1 -1
  79. data/app/views/campaigns/show.html.haml +1 -0
  80. data/app/views/campaigns/update.js.haml +3 -4
  81. data/app/views/comments/_edit.html.haml +1 -1
  82. data/app/views/comments/_new.html.haml +1 -1
  83. data/app/views/comments/update.js.haml +0 -1
  84. data/app/views/contacts/_edit.html.haml +1 -1
  85. data/app/views/contacts/_new.html.haml +1 -1
  86. data/app/views/contacts/_sidebar_show.html.haml +18 -18
  87. data/app/views/contacts/create.js.haml +0 -1
  88. data/app/views/contacts/destroy.js.haml +1 -1
  89. data/app/views/contacts/show.html.haml +1 -0
  90. data/app/views/contacts/update.js.haml +2 -3
  91. data/app/views/entities/_title_bar.html.haml +1 -1
  92. data/app/views/entities/attach.js.haml +2 -2
  93. data/app/views/entities/discard.js.haml +2 -2
  94. data/app/views/home/_opportunity.html.haml +1 -1
  95. data/app/views/home/_task.html.haml +1 -1
  96. data/app/views/home/index.xls.builder +51 -0
  97. data/app/views/layouts/_sidebar.html.haml +2 -2
  98. data/app/views/layouts/_tabbed.html.haml +14 -11
  99. data/app/views/layouts/application.html.haml +2 -2
  100. data/app/views/leads/_edit.html.haml +2 -2
  101. data/app/views/leads/_new.html.haml +2 -2
  102. data/app/views/leads/_sidebar_index.html.haml +2 -2
  103. data/app/views/leads/_sidebar_show.html.haml +30 -30
  104. data/app/views/leads/create.js.haml +2 -3
  105. data/app/views/leads/destroy.js.haml +2 -2
  106. data/app/views/leads/promote.js.haml +3 -4
  107. data/app/views/leads/reject.js.haml +3 -3
  108. data/app/views/leads/show.html.haml +1 -1
  109. data/app/views/leads/update.js.haml +4 -5
  110. data/app/views/lists/create.js.haml +0 -1
  111. data/app/views/opportunities/_edit.html.haml +1 -1
  112. data/app/views/opportunities/_index_brief.html.haml +1 -1
  113. data/app/views/opportunities/_index_long.html.haml +1 -1
  114. data/app/views/opportunities/_new.html.haml +1 -1
  115. data/app/views/opportunities/_sidebar_index.html.haml +2 -2
  116. data/app/views/opportunities/_sidebar_show.html.haml +47 -46
  117. data/app/views/opportunities/create.js.haml +3 -4
  118. data/app/views/opportunities/destroy.js.haml +3 -3
  119. data/app/views/opportunities/show.html.haml +1 -0
  120. data/app/views/opportunities/update.js.haml +5 -6
  121. data/app/views/shared/_inline_styles.html.haml +0 -26
  122. data/app/views/tasks/_assigned.html.haml +1 -1
  123. data/app/views/tasks/_completed.html.haml +1 -1
  124. data/app/views/tasks/_edit.html.haml +1 -1
  125. data/app/views/tasks/_new.html.haml +1 -1
  126. data/app/views/tasks/_pending.html.haml +1 -1
  127. data/app/views/tasks/_related.html.haml +1 -1
  128. data/app/views/tasks/_selector.html.haml +7 -8
  129. data/app/views/tasks/_sidebar_index.html.haml +2 -2
  130. data/app/views/tasks/_title.html.haml +1 -1
  131. data/app/views/tasks/complete.js.haml +1 -1
  132. data/app/views/tasks/create.js.haml +1 -2
  133. data/app/views/tasks/destroy.js.haml +1 -1
  134. data/app/views/tasks/uncomplete.js.haml +1 -2
  135. data/app/views/tasks/update.js.haml +0 -1
  136. data/app/views/users/change_password.js.haml +0 -1
  137. data/app/views/users/update.js.haml +0 -1
  138. data/app/views/users/upload_avatar.js.haml +0 -1
  139. data/app/views/versions/_version_item.html.haml +1 -1
  140. data/bin/setup +6 -6
  141. data/config/application.rb +1 -1
  142. data/config/environment.rb +1 -1
  143. data/config/initializers/content_security_policy.rb +5 -0
  144. data/config/initializers/new_framework_defaults_6_0.rb +46 -0
  145. data/config/initializers/simple_form.rb +135 -55
  146. data/config/initializers/simple_form_bootstrap.rb +433 -0
  147. data/config/locales/fat_free_crm.cs.yml +86 -47
  148. data/config/locales/fat_free_crm.ru.yml +1 -0
  149. data/config/settings.default.yml +0 -41
  150. data/db/migrate/20200806004152_add_pattern_to_fields.rb +7 -0
  151. data/db/migrate/20200806004459_add_html5_to_fields.rb +10 -0
  152. data/db/schema.rb +11 -6
  153. data/fat_free_crm.gemspec +4 -4
  154. data/lib/fat_free_crm/core_ext.rb +1 -1
  155. data/lib/fat_free_crm/gem_ext.rb +0 -1
  156. data/lib/fat_free_crm/tabs.rb +2 -2
  157. data/lib/fat_free_crm/version.rb +2 -2
  158. data/lib/gravatar_image_tag.rb +1 -1
  159. data/lib/tasks/ffcrm/preference_update.rake +19 -0
  160. data/lib/tasks/ffcrm/setup.rake +1 -1
  161. data/lib/templates/erb/scaffold/_form.html.erb +4 -2
  162. data/spec/controllers/comments_controller_spec.rb +6 -6
  163. data/spec/controllers/home_controller_spec.rb +3 -3
  164. data/spec/controllers/users_controller_spec.rb +13 -1
  165. data/spec/factories/user_factories.rb +5 -2
  166. data/spec/features/acceptance_helper.rb +1 -1
  167. data/spec/features/accounts_spec.rb +2 -2
  168. data/spec/features/admin/groups_spec.rb +1 -1
  169. data/spec/features/admin/users_spec.rb +1 -1
  170. data/spec/features/campaigns_spec.rb +2 -2
  171. data/spec/features/contacts_spec.rb +2 -5
  172. data/spec/features/leads_spec.rb +11 -7
  173. data/spec/features/opportunities_spec.rb +4 -4
  174. data/spec/features/support/browser.rb +6 -2
  175. data/spec/features/support/selector_helpers.rb +10 -1
  176. data/spec/features/tasks_spec.rb +6 -6
  177. data/spec/helpers/accounts_helper_spec.rb +57 -0
  178. data/spec/models/entities/opportunity_spec.rb +2 -2
  179. data/spec/models/fields/custom_field_pair_spec.rb +2 -2
  180. data/spec/models/observers/entity_observer_spec.rb +3 -3
  181. data/spec/models/polymorphic/task_spec.rb +11 -11
  182. data/spec/models/polymorphic/version_spec.rb +2 -2
  183. data/spec/models/users/preference_spec.rb +1 -1
  184. data/spec/views/accounts/_edit.haml_spec.rb +1 -1
  185. data/spec/views/accounts/_new.haml_spec.rb +1 -1
  186. data/spec/views/accounts/create.js.haml_spec.rb +0 -1
  187. data/spec/views/accounts/update.js.haml_spec.rb +2 -5
  188. data/spec/views/admin/field_groups/create.js.haml_spec.rb +0 -1
  189. data/spec/views/admin/field_groups/update.js.haml_spec.rb +0 -1
  190. data/spec/views/admin/users/create.js.haml_spec.rb +0 -1
  191. data/spec/views/admin/users/destroy.js.haml_spec.rb +0 -6
  192. data/spec/views/admin/users/update.js.haml_spec.rb +1 -2
  193. data/spec/views/campaigns/_edit.haml_spec.rb +1 -1
  194. data/spec/views/campaigns/_new.haml_spec.rb +1 -1
  195. data/spec/views/campaigns/create.js.haml_spec.rb +0 -1
  196. data/spec/views/campaigns/destroy.js.haml_spec.rb +0 -1
  197. data/spec/views/campaigns/update.js.haml_spec.rb +2 -6
  198. data/spec/views/contacts/_edit.haml_spec.rb +1 -1
  199. data/spec/views/contacts/_new.haml_spec.rb +1 -1
  200. data/spec/views/contacts/create.js.haml_spec.rb +0 -1
  201. data/spec/views/contacts/update.js.haml_spec.rb +3 -7
  202. data/spec/views/home/index.haml_spec.rb +2 -2
  203. data/spec/views/leads/_edit.haml_spec.rb +1 -1
  204. data/spec/views/leads/_new.haml_spec.rb +1 -1
  205. data/spec/views/leads/create.js.haml_spec.rb +0 -3
  206. data/spec/views/leads/destroy.js.haml_spec.rb +0 -2
  207. data/spec/views/leads/promote.js.haml_spec.rb +3 -11
  208. data/spec/views/leads/reject.js.haml_spec.rb +0 -3
  209. data/spec/views/leads/update.js.haml_spec.rb +3 -11
  210. data/spec/views/opportunities/_edit.haml_spec.rb +1 -1
  211. data/spec/views/opportunities/_new.haml_spec.rb +1 -1
  212. data/spec/views/opportunities/create.js.haml_spec.rb +0 -2
  213. data/spec/views/opportunities/destroy.js.haml_spec.rb +0 -3
  214. data/spec/views/opportunities/update.js.haml_spec.rb +3 -11
  215. data/spec/views/tasks/_edit.haml_spec.rb +1 -1
  216. data/spec/views/tasks/complete.js.haml_spec.rb +0 -1
  217. data/spec/views/tasks/create.js.haml_spec.rb +0 -2
  218. data/spec/views/tasks/destroy.js.haml_spec.rb +0 -1
  219. data/spec/views/tasks/uncomplete.js.haml_spec.rb +0 -1
  220. data/spec/views/tasks/update.js.haml_spec.rb +1 -4
  221. data/spec/views/users/change_password.js.haml_spec.rb +1 -2
  222. data/spec/views/users/update.js.haml_spec.rb +1 -2
  223. data/spec/views/users/upload_avatar.js.haml_spec.rb +1 -2
  224. metadata +23 -14
  225. data/config/initializers/new_framework_defaults_5_2.rb +0 -40
  226. data/lib/fat_free_crm/gem_ext/simple_form/action_view_extensions/form_helper.rb +0 -24
@@ -7,7 +7,7 @@
7
7
  #------------------------------------------------------------------------------
8
8
  FactoryBot.define do
9
9
  factory :user do
10
- username { FactoryBot.generate(:username) }
10
+ username { FFaker::InternetSE.user_name_variant_short }
11
11
  email { FFaker::Internet.email }
12
12
  first_name { FFaker::Name.first_name }
13
13
  last_name { FFaker::Name.last_name }
@@ -34,8 +34,11 @@ FactoryBot.define do
34
34
  suspended_at { nil }
35
35
  password { "password" }
36
36
  password_confirmation { "password" }
37
- end
38
37
 
38
+ # For unit tests, we dont need to enforce uniqueness
39
+ to_create { |instance| instance.save(validate: false) }
40
+ end
41
+
39
42
  factory :admin do
40
43
  admin { true }
41
44
  end
@@ -8,4 +8,4 @@
8
8
  require 'spec_helper'
9
9
 
10
10
  # Put your acceptance spec helpers inside /spec/features/support
11
- Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
11
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].sort.each { |f| require f }
@@ -31,8 +31,8 @@ feature 'Accounts', '
31
31
  click_link 'Create Account'
32
32
  expect(page).to have_selector('#account_name', visible: true)
33
33
  fill_in 'account_name', with: 'My new account'
34
- select 'Affiliate', from: 'account_category'
35
- select 'Myself', from: 'account_assigned_to'
34
+ select2 'Affiliate', from: 'Category:'
35
+ select2 'Myself', from: 'Assigned to:'
36
36
  click_link 'Contact Information'
37
37
  fill_in 'account_phone', with: '+1 2345 6789'
38
38
  fill_in 'account_website', with: 'http://www.example.com'
@@ -23,7 +23,7 @@ feature 'Groups tab', '
23
23
  click_link 'create a new group'
24
24
  expect(page).to have_selector('#group_name', visible: true)
25
25
  fill_in 'group_name', with: 'The Enterprise Bridge'
26
- select 'Mr Spock', from: 'group_user_ids'
26
+ select2 'Mr Spock', from: 'Users'
27
27
  click_button 'Create Group'
28
28
  expect(page).to have_content('The Enterprise Bridge')
29
29
  expect(page).to have_content('members: Mr Spock')
@@ -29,7 +29,7 @@ feature 'Users tab', '
29
29
  fill_in 'user_password_confirmation', with: 'password'
30
30
  fill_in 'user_title', with: 'Chief'
31
31
  fill_in 'user_company', with: 'Weather Inc.'
32
- select 'Superheroes', from: 'user_group_ids'
32
+ select2 'Superheroes', from: 'Groups:'
33
33
 
34
34
  click_button 'Create User'
35
35
  expect(find('#users')).to have_content('Captain Thunder')
@@ -31,7 +31,7 @@ feature 'Campaigns', '
31
31
  click_link 'Create Campaign'
32
32
  expect(page).to have_selector('#campaign_name', visible: true)
33
33
  fill_in 'campaign_name', with: 'Cool Campaign'
34
- select 'On Hold', from: 'campaign_status'
34
+ select2 'On Hold', from: 'Status:'
35
35
  click_link 'Comment'
36
36
  fill_in 'comment_body', with: 'This campaign is very important'
37
37
  click_button 'Create Campaign'
@@ -66,7 +66,7 @@ feature 'Campaigns', '
66
66
  click_link 'My Cool Campaign'
67
67
  click_link 'Edit'
68
68
  fill_in 'campaign_name', with: 'My Even Cooler Campaign'
69
- select 'Started', from: 'campaign_status'
69
+ select2 'Started', from: 'Status:'
70
70
  click_button 'Save Campaign'
71
71
  expect(page).to have_content('My Even Cooler Campaign')
72
72
  expect(page).to have_content('My Even Cooler Campaign')
@@ -77,7 +77,8 @@ feature 'Contacts', '
77
77
  fill_in 'contact_last_name', with: 'Subject'
78
78
  fill_in 'contact_email', with: "test.subject@example.com"
79
79
  click_button 'Save Contact'
80
- expect(summary_element).to have_content('Test Subject')
80
+ sleep(3) # TODO: A better ajax wait, or redirect on save
81
+ expect(find('#edit_contact_title')).to have_content('Test Subject')
81
82
 
82
83
  click_link 'Dashboard'
83
84
  expect(activities_element).to have_content("Bill Murray updated contact Test Subject")
@@ -115,10 +116,6 @@ feature 'Contacts', '
115
116
  find('#main')
116
117
  end
117
118
 
118
- def summary_element
119
- find('#summary')
120
- end
121
-
122
119
  def menu_element
123
120
  find('#menu')
124
121
  end
@@ -36,13 +36,16 @@ feature 'Leads', '
36
36
  fill_in 'lead_email', with: 'mr_lead@example.com'
37
37
  fill_in 'lead_phone', with: '+44 1234 567890'
38
38
  click_link 'Status'
39
- select 'Myself', from: 'lead_assigned_to'
40
- select 'Contacted', from: 'lead_status'
41
- select 'Campaign', from: 'lead_source'
39
+ select2 'Myself', from: 'Assigned to:'
40
+ select2 'Contacted', from: 'Status:'
41
+ select2 'Campaign', from: 'Source:'
42
42
  click_link 'Comment'
43
43
  fill_in 'comment_body', with: 'This is an important lead.'
44
- click_link 'Status'
45
- select 'Contacted', from: 'lead_status'
44
+
45
+ # TODO: Refactor to a page object
46
+ # This panel is already open, so clicking status again closed the div.
47
+ # click_link 'Status'
48
+ select2 'Contacted', from: 'Status:'
46
49
  click_button 'Create Lead'
47
50
  expect(leads_element).to have_content('Mr Lead')
48
51
 
@@ -79,9 +82,10 @@ feature 'Leads', '
79
82
  fill_in 'lead_first_name', with: 'Mrs'
80
83
  fill_in 'lead_phone', with: '+44 0987 654321'
81
84
  click_link('Status')
82
- select 'Rejected', from: 'lead_status'
85
+ select2 'Rejected', from: 'Status:'
83
86
  click_button 'Save Lead'
84
- expect(summary_element).to have_content('Mrs Lead')
87
+ sleep(3) # TODO: A better AJAX observing call, or just a redirect on save.
88
+ expect(find('#title')).to have_content('Mrs Lead')
85
89
 
86
90
  click_link "Dashboard"
87
91
  expect(activities_element).to have_content("Bill Murray updated lead Mrs Lead")
@@ -38,7 +38,7 @@ feature 'Opportunities', '
38
38
  sleep(1)
39
39
  find('li', text: 'Example Account').click
40
40
  expect(page).to have_content('Example Account')
41
- select 'Prospecting', from: 'opportunity_stage'
41
+ select2 'Prospecting', from: 'Stage'
42
42
  click_link 'Comment'
43
43
  fill_in 'comment_body', with: 'This is a very important opportunity.'
44
44
  click_button 'Create Opportunity'
@@ -67,7 +67,7 @@ feature 'Opportunities', '
67
67
  scenario "remembers the comment field when the creation was unsuccessful", js: true do
68
68
  visit opportunities_page
69
69
  click_link 'Create Opportunity'
70
- select 'Prospecting', from: 'opportunity_stage'
70
+ select2 'Prospecting', from: 'Stage:'
71
71
 
72
72
  click_link 'Comment'
73
73
  fill_in 'comment_body', with: 'This is a very important opportunity.'
@@ -85,8 +85,8 @@ feature 'Opportunities', '
85
85
  click_link 'A Cool Opportunity'
86
86
  click_link 'Edit'
87
87
  fill_in 'opportunity_name', with: 'An Even Cooler Opportunity'
88
- select 'Other Example Account', from: 'account_id'
89
- select 'Analysis', from: 'opportunity_stage'
88
+ select2 'Other Example Account', from: 'Account (create new or select existing):'
89
+ select2 'Analysis', from: 'Stage:'
90
90
  click_button 'Save Opportunity'
91
91
  expect(page).to have_content('An Even Cooler Opportunity')
92
92
  click_link "Dashboard"
@@ -15,10 +15,14 @@ if ENV['BROWSER'] == 'chrome'
15
15
  Capybara::Selenium::Driver.new(app, browser: :chrome, desired_capabilities: capabilities)
16
16
  end
17
17
  else
18
+ # For local testing in an environment with a display or remote X server configured
19
+ # such as WSL2, use NO_HEADLESS=1 bundle exec rspec spec/features
20
+ #
21
+ # For modern firefox, use MARIONETTE=1 bundle exec rspec spec/features
18
22
  Capybara.register_driver :selenium do |app|
19
23
  options = Selenium::WebDriver::Firefox::Options.new
20
- options.args << '--headless'
21
- capabilities = Selenium::WebDriver::Remote::Capabilities.firefox(marionette: false)
24
+ options.args << '--headless' unless ENV['NO_HEADLESS'].present?
25
+ capabilities = Selenium::WebDriver::Remote::Capabilities.firefox(marionette: ENV['MARIONETTE'].present?)
22
26
  Capybara::Selenium::Driver.new(app, browser: :firefox, options: options, desired_capabilities: capabilities)
23
27
  end
24
28
  end
@@ -7,7 +7,7 @@
7
7
  #------------------------------------------------------------------------------
8
8
  module SelectorHelpers
9
9
  def click_filter_tab(filter_name)
10
- tab = find(:xpath, "//div[@class='filters']//td[contains(text(), '#{filter_name}')]")
10
+ tab = find(:xpath, "//div[@class='filters']//a[contains(text(), '#{filter_name}')]")
11
11
  tab.click
12
12
  end
13
13
 
@@ -22,6 +22,15 @@ module SelectorHelpers
22
22
  page.execute_script "$('#task_#{task_id} a')[1].click()"
23
23
  end
24
24
  end
25
+
26
+ # See github.com/goodwill/capybara-select2
27
+ def select2(value, options = {})
28
+ select2_container = find("div.label", text: options[:from]).find(:xpath, '..').find('.select2-container')
29
+
30
+ select2_container.find(".select2-selection").click
31
+ drop_container = ".select2-dropdown"
32
+ find(:xpath, "//body").find("#{drop_container} li.select2-results__option", text: value).click
33
+ end
25
34
  end
26
35
 
27
36
  RSpec.configuration.include SelectorHelpers, type: :feature
@@ -33,9 +33,9 @@ feature 'Tasks', '
33
33
  click_link 'Create Task'
34
34
  expect(page).to have_selector('#task_name', visible: true)
35
35
  fill_in 'task_name', with: 'Task I Need To Do'
36
- select 'Tomorrow', from: 'task_bucket'
37
- select 'Myself', from: 'task_assigned_to'
38
- select 'Call', from: 'task_category'
36
+ select2 'Tomorrow', from: 'Due:'
37
+ select2 'Myself', from: 'Assign to:'
38
+ select2 'Call', from: 'Category:'
39
39
  click_button 'Create Task'
40
40
  expect(page).to have_content('Task I Need To Do')
41
41
 
@@ -51,9 +51,9 @@ feature 'Tasks', '
51
51
  click_link 'Create Task'
52
52
  expect(page).to have_selector('#task_name', visible: true)
53
53
  fill_in 'task_name', with: 'Task For Someone Else'
54
- select 'Tomorrow', from: 'task_bucket'
55
- select 'Another User', from: 'task_assigned_to'
56
- select 'Call', from: 'task_category'
54
+ select2 'Tomorrow', from: 'Due:'
55
+ select2 'Another User', from: 'Assign to:'
56
+ select2 'Call', from: 'Category:'
57
57
  click_button 'Create Task'
58
58
  expect(page).to have_content('The task has been created and assigned to Another User')
59
59
 
@@ -8,9 +8,66 @@
8
8
  require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
9
9
 
10
10
  describe AccountsHelper do
11
+ let(:assignee) { create(:user) }
11
12
  # Delete this example and add some real ones or delete this file
12
13
  it "should be included in the object returned by #helper" do
13
14
  included_modules = (class << helper; self; end).send :included_modules
14
15
  expect(included_modules).to include(AccountsHelper)
15
16
  end
17
+ describe '#display_pipeline' do
18
+ context 'when the pipeline is zero' do
19
+ it 'returns "N/A"' do
20
+ expect(display_value(0)).to eq('N/A')
21
+ end
22
+ end
23
+ context 'when the pipeline is has 300' do
24
+ it 'returns "$300"' do
25
+ expect(display_value(300)).to eq('$300')
26
+ end
27
+ end
28
+ end
29
+ describe '#display_won' do
30
+ context 'when the won is zero' do
31
+ it 'returns "N/A"' do
32
+ expect(display_value(0)).to eq('N/A')
33
+ end
34
+ end
35
+ context 'when the won is has 300' do
36
+ it 'returns "$300"' do
37
+ expect(display_value(300)).to eq('$300')
38
+ end
39
+ end
40
+ end
41
+ describe '#display_lost' do
42
+ context 'when the lost is zero' do
43
+ it 'returns "N/A"' do
44
+ expect(display_value(0)).to eq('N/A')
45
+ end
46
+ end
47
+ context 'when the lost is has 300' do
48
+ it 'returns "$300"' do
49
+ expect(display_value(300)).to eq('$300')
50
+ end
51
+ end
52
+ end
53
+ describe '#display_assigned' do
54
+ context 'when the name is under 16 chars' do
55
+ it 'returns the name' do
56
+ @account = create(:account)
57
+ allow(@account).to receive(:assignee).and_return(assignee)
58
+ allow(assignee).to receive(:full_name).and_return('Abe Lincoln')
59
+ allow(@account).to receive(:assigned_to).and_return(1)
60
+ expect(display_assigned(@account)).to eq('Abe Lincoln')
61
+ end
62
+ end
63
+ context 'when the name is over 16 chars' do
64
+ it 'returns the truncated name' do
65
+ @account = create(:account)
66
+ allow(@account).to receive(:assignee).and_return(assignee)
67
+ allow(assignee).to receive(:full_name).and_return('Richard Milhouse Nixon')
68
+ allow(@account).to receive(:assigned_to).and_return(1)
69
+ expect(display_assigned(@account)).to eq('Richard Milho...')
70
+ end
71
+ end
72
+ end
16
73
  end
@@ -113,14 +113,14 @@ describe Opportunity do
113
113
 
114
114
  it "should set the probability to 0% if opportunity has been lost" do
115
115
  opportunity = create(:opportunity, stage: "prospecting", probability: 25)
116
- opportunity.update_attributes(stage: 'lost')
116
+ opportunity.update(stage: 'lost')
117
117
  opportunity.reload
118
118
  expect(opportunity.probability).to eq(0)
119
119
  end
120
120
 
121
121
  it "should set the probablility to 100% if opportunity has been won" do
122
122
  opportunity = create(:opportunity, stage: "prospecting", probability: 65)
123
- opportunity.update_attributes(stage: 'won')
123
+ opportunity.update(stage: 'won')
124
124
  opportunity.reload
125
125
  expect(opportunity.probability).to eq(100)
126
126
  end
@@ -45,9 +45,9 @@ describe CustomFieldPair do
45
45
 
46
46
  it "should update the pair" do
47
47
  foo1 = double(required: true, disabled: 'false')
48
- expect(foo1).to receive(:update_attributes).with(@field.merge(@pair1))
48
+ expect(foo1).to receive(:update).with(@field.merge(@pair1))
49
49
  foo2 = double
50
- expect(foo2).to receive(:update_attributes).with(@field.merge(@pair2).merge('required' => true, 'disabled' => 'false'))
50
+ expect(foo2).to receive(:update).with(@field.merge(@pair2).merge('required' => true, 'disabled' => 'false'))
51
51
  expect(foo1).to receive(:paired_with).and_return(foo2)
52
52
  expect(CustomFieldPair).to receive(:find).with('3').and_return(foo1)
53
53
 
@@ -52,7 +52,7 @@ describe EntityObserver do
52
52
 
53
53
  it "notifies the new owner if the entity is re-assigned" do
54
54
  expect(UserMailer).to receive(:assigned_entity_notification).with(entity, assigner).and_return(mail)
55
- entity.update_attributes(assignee: assignee)
55
+ entity.update(assignee: assignee)
56
56
  end
57
57
 
58
58
  it "does not notify the owner if the entity is not re-assigned" do
@@ -62,12 +62,12 @@ describe EntityObserver do
62
62
 
63
63
  it "does not notify anyone if the entity becomes unassigned" do
64
64
  expect(UserMailer).not_to receive(:assigned_entity_notification)
65
- entity.update_attributes(assignee: nil)
65
+ entity.update(assignee: nil)
66
66
  end
67
67
 
68
68
  it "does not notify me if I re-assign an entity to myself" do
69
69
  expect(UserMailer).not_to receive(:assigned_entity_notification)
70
- entity.update_attributes(assignee: assigner)
70
+ entity.update(assignee: assigner)
71
71
  end
72
72
  end
73
73
  end
@@ -63,14 +63,14 @@ describe Task do
63
63
  describe "Task/Update" do
64
64
  it "should update task name" do
65
65
  task = create(:task, name: "Hello")
66
- task.update_attributes(name: "World")
66
+ task.update(name: "World")
67
67
  expect(task.errors).to be_empty
68
68
  expect(task.name).to eq("World")
69
69
  end
70
70
 
71
71
  it "should update task category" do
72
72
  task = create(:task, category: "call")
73
- task.update_attributes(category: "email")
73
+ task.update(category: "email")
74
74
  expect(task.errors).to be_empty
75
75
  expect(task.category).to eq("email")
76
76
  end
@@ -79,7 +79,7 @@ describe Task do
79
79
  him = create(:user)
80
80
  her = create(:user)
81
81
  task = create(:task, assigned_to: him.id)
82
- task.update_attributes(assigned_to: her.id)
82
+ task.update(assigned_to: her.id)
83
83
  expect(task.errors).to be_empty
84
84
  expect(task.assigned_to).to eq(her.id)
85
85
  expect(task.assignee).to eq(her)
@@ -88,7 +88,7 @@ describe Task do
88
88
  it "should reassign the task from another person to myself" do
89
89
  him = create(:user)
90
90
  task = create(:task, assigned_to: him.id)
91
- task.update_attributes(assigned_to: "")
91
+ task.update(assigned_to: "")
92
92
  expect(task.errors).to be_empty
93
93
  expect(task.assigned_to).to eq(nil)
94
94
  expect(task.assignee).to eq(nil)
@@ -101,7 +101,7 @@ describe Task do
101
101
 
102
102
  it "should update due date based on selected bucket within #{offset ? 'different' : 'current'} timezone" do
103
103
  task = create(:task, due_at: Time.now.midnight.tomorrow, bucket: "due_tomorrow")
104
- task.update_attributes(bucket: "due_this_week")
104
+ task.update(bucket: "due_this_week")
105
105
  expect(task.errors).to be_empty
106
106
  expect(task.bucket).to eq("due_this_week")
107
107
  expect(task.due_at.change(usec: 0)).to eq(Time.zone.now.end_of_week.change(usec: 0))
@@ -109,7 +109,7 @@ describe Task do
109
109
 
110
110
  it "should update due date if specific calendar date selected within #{offset ? 'different' : 'current'} timezone" do
111
111
  task = create(:task, due_at: Time.now.midnight.tomorrow, bucket: "due_tomorrow")
112
- task.update_attributes(bucket: "specific_time", calendar: "2020-03-20")
112
+ task.update(bucket: "specific_time", calendar: "2020-03-20")
113
113
  expect(task.errors).to be_empty
114
114
  expect(task.bucket).to eq("specific_time")
115
115
  expect(task.due_at.to_i).to eq(Time.parse("2020-03-20").to_i)
@@ -120,7 +120,7 @@ describe Task do
120
120
  describe "Task/Complete" do
121
121
  it "should complete a task that is overdue" do
122
122
  task = create(:task, due_at: 2.days.ago, bucket: "overdue")
123
- task.update_attributes(completed_at: Time.now, completed_by: task.user.id)
123
+ task.update(completed_at: Time.now, completed_by: task.user.id)
124
124
 
125
125
  expect(task.errors).to be_empty
126
126
  expect(task.completed_at).not_to eq(nil)
@@ -129,7 +129,7 @@ describe Task do
129
129
 
130
130
  it "should complete a task due sometime in the future" do
131
131
  task = create(:task, due_at: Time.now.midnight.tomorrow, bucket: "due_tomorrow")
132
- task.update_attributes(completed_at: Time.now, completed_by: task.user.id)
132
+ task.update(completed_at: Time.now, completed_by: task.user.id)
133
133
 
134
134
  expect(task.errors).to be_empty
135
135
  expect(task.completed_at).not_to eq(nil)
@@ -139,7 +139,7 @@ describe Task do
139
139
  it "should complete a task that is due on specific date in the future" do
140
140
  task = create(:task, calendar: "10/10/2022 12:00 AM", bucket: "specific_time")
141
141
  task.calendar = nil # Calendar is not saved in the database; we need it only to set the :due_at.
142
- task.update_attributes(completed_at: Time.now, completed_by: task.user.id)
142
+ task.update(completed_at: Time.now, completed_by: task.user.id)
143
143
  expect(task.errors).to be_empty
144
144
  expect(task.completed_at).not_to eq(nil)
145
145
  expect(task.completor).to eq(task.user)
@@ -148,7 +148,7 @@ describe Task do
148
148
  it "should complete a task that is due on specific date in the past" do
149
149
  task = create(:task, calendar: "10/10/1992 12:00 AM", bucket: "specific_time")
150
150
  task.calendar = nil # Calendar is not saved in the database; we need it only to set the :due_at.
151
- task.update_attributes(completed_at: Time.now, completed_by: task.user.id)
151
+ task.update(completed_at: Time.now, completed_by: task.user.id)
152
152
  expect(task.errors).to be_empty
153
153
  expect(task.completed_at).not_to eq(nil)
154
154
  expect(task.completor).to eq(task.user)
@@ -158,7 +158,7 @@ describe Task do
158
158
  due_at = Time.now - 42.days
159
159
  task = create(:task, due_at: due_at, bucket: "specific_time",
160
160
  calendar: due_at.strftime('%Y-%m-%d %H:%M'))
161
- task.update_attributes(completed_at: Time.now, completed_by: task.user.id, calendar: '')
161
+ task.update(completed_at: Time.now, completed_by: task.user.id, calendar: '')
162
162
 
163
163
  expect(task.completed?).to eq(true)
164
164
  expect(task.due_at).to eq(due_at.utc.strftime('%Y-%m-%d %H:%M'))
@@ -82,9 +82,9 @@ describe Version, versioning: true do
82
82
 
83
83
  it "should add a version when updating existing #{item}" do
84
84
  if @item.respond_to?(:full_name)
85
- @item.update_attributes(first_name: "Billy", last_name: "Bones")
85
+ @item.update(first_name: "Billy", last_name: "Bones")
86
86
  else
87
- @item.update_attributes(name: "Billy Bones")
87
+ @item.update(name: "Billy Bones")
88
88
  end
89
89
  @version = Version.where(@conditions.merge(event: 'update')).first
90
90
 
@@ -22,7 +22,7 @@ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
22
22
  describe Preference do
23
23
  before(:each) do
24
24
  @user = create(:user)
25
- @magoody = Base64.encode64(Marshal.dump("magoody"))
25
+ @magoody = Base64.encode64("magoody".to_json)
26
26
  end
27
27
 
28
28
  it "should create a new instance given valid attributes" do
@@ -22,7 +22,7 @@ describe "/accounts/_edit" do
22
22
  expect(view).to render_template(partial: "_contact_info")
23
23
  expect(view).to render_template(partial: "_permissions")
24
24
 
25
- expect(rendered).to have_tag("form[class=edit_account]") do |form|
25
+ expect(rendered).to have_tag('form[class="simple_form edit_account"]') do |form|
26
26
  expect(form).to have_tag "input[type=hidden][id=account_user_id][value='#{@account.user_id}']"
27
27
  end
28
28
  end
@@ -23,7 +23,7 @@ describe "/accounts/_new" do
23
23
  expect(view).to render_template(partial: "_contact_info")
24
24
  expect(view).to render_template(partial: "_permissions")
25
25
 
26
- expect(rendered).to have_tag("form[class=new_account]")
26
+ expect(rendered).to have_tag('form[class="simple_form new_account"]')
27
27
  end
28
28
 
29
29
  it "should render background info field if settings require so" do
@@ -47,7 +47,6 @@ describe "/accounts/create" do
47
47
  render
48
48
 
49
49
  expect(rendered).to include("#create_account")
50
- expect(rendered).to include(%/$('#create_account').effect("shake"/)
51
50
  end
52
51
  end
53
52
  end
@@ -34,7 +34,6 @@ describe "/accounts/update" do
34
34
  render
35
35
  expect(rendered).to include("$('#sidebar').html")
36
36
  expect(rendered).to have_text("Recent Items")
37
- expect(rendered).to include("$('#summary').effect('shake'")
38
37
  end
39
38
  end
40
39
 
@@ -70,11 +69,10 @@ describe "/accounts/update" do
70
69
  controller.request.env["HTTP_REFERER"] = "http://localhost/accounts/123"
71
70
  end
72
71
 
73
- it "should redraw the [edit_account] form and shake it" do
72
+ it "should redraw the [edit_account] form" do
74
73
  render
75
74
 
76
75
  expect(rendered).to include("#edit_account")
77
- expect(rendered).to include(%/$('#edit_account').effect("shake"/)
78
76
  expect(rendered).to include('focus()')
79
77
  end
80
78
  end
@@ -84,11 +82,10 @@ describe "/accounts/update" do
84
82
  controller.request.env["HTTP_REFERER"] = "http://localhost/accounts"
85
83
  end
86
84
 
87
- it "should redraw the [edit_account] form and shake it" do
85
+ it "should redraw the [edit_account] form" do
88
86
  render
89
87
 
90
88
  expect(rendered).to include("account_#{@account.id}")
91
- expect(rendered).to include(%/$('#account_#{@account.id}').effect("shake"/)
92
89
  expect(rendered).to include('focus()')
93
90
  end
94
91
  end
@@ -26,6 +26,5 @@ describe "admin/field_groups/create" do
26
26
  allow(field_group).to receive(:valid?).and_return(false)
27
27
  render
28
28
  expect(view).to render_template("admin/field_groups/create")
29
- expect(rendered).to have_text("effect(\"shake\", { duration:250, distance: 6 });")
30
29
  end
31
30
  end
@@ -25,6 +25,5 @@ describe "admin/field_groups/update" do
25
25
  it "renders javascript for invalid field group" do
26
26
  allow(field_group.errors).to receive(:empty?).and_return(false)
27
27
  render
28
- expect(rendered).to have_text("$('##{dom_id(field_group, :edit)}').effect('shake', { distance:5 }, 250);")
29
28
  end
30
29
  end
@@ -36,7 +36,6 @@ describe "admin/users/create" do
36
36
  render
37
37
 
38
38
  expect(rendered).to include('Please specify username')
39
- expect(rendered).to include(%/$('#create_user').effect("shake"/)
40
39
  end
41
40
  end
42
41
  end
@@ -37,12 +37,6 @@ describe "admin/users/destroy" do
37
37
  expect(rendered).to include(%/crm.flick('#{dom_id(@user, :confirm)}', 'remove');/)
38
38
  end
39
39
 
40
- it "should shake user partial" do
41
- render
42
-
43
- expect(rendered).to include(%/$('#user_#{@user.id}').effect('shake'/)
44
- end
45
-
46
40
  it "should show flash message" do
47
41
  render
48
42
 
@@ -27,11 +27,10 @@ describe "admin/users/update" do
27
27
  @user.errors.add(:name)
28
28
  end
29
29
 
30
- it "redraws [Edit User] form and shakes it" do
30
+ it "redraws [Edit User] form" do
31
31
  render
32
32
 
33
33
  expect(rendered).to include("user_#{@user.id}")
34
- expect(rendered).to include(%/$('#user_#{@user.id}').effect("shake"/)
35
34
  expect(rendered).to include(%/$('#user_username').focus()/)
36
35
  end
37
36
  end
@@ -23,7 +23,7 @@ describe "/campaigns/_edit" do
23
23
  expect(view).to render_template(partial: "campaigns/_objectives")
24
24
  expect(view).to render_template(partial: "_permissions")
25
25
 
26
- expect(view).to have_tag("form[class=edit_campaign]") do
26
+ expect(view).to have_tag('form[class="simple_form edit_campaign"]') do
27
27
  with_tag "input[type=hidden][id=campaign_user_id][value='#{@campaign.user_id}']"
28
28
  end
29
29
  end
@@ -22,7 +22,7 @@ describe "/campaigns/_new" do
22
22
  expect(view).to render_template(partial: "campaigns/_objectives")
23
23
  expect(view).to render_template(partial: "_permissions")
24
24
 
25
- expect(rendered).to have_tag("form[class=new_campaign]")
25
+ expect(rendered).to have_tag('form[class="simple_form new_campaign"]')
26
26
  end
27
27
 
28
28
  it "should render background info field if settings require so" do
@@ -44,7 +44,6 @@ describe "/campaigns/create" do
44
44
  render
45
45
 
46
46
  expect(rendered).to include("$('#create_campaign').html")
47
- expect(rendered).to include(%/$('#create_campaign').effect("shake"/)
48
47
  end
49
48
  end
50
49
  end
@@ -23,7 +23,6 @@ describe "/campaigns/destroy" do
23
23
  it "should update Campaigns sidebar" do
24
24
  expect(rendered).to include("#sidebar")
25
25
  expect(rendered).to have_text("Recent Items")
26
- expect(rendered).to include(%/$('#filters').effect('shake'/)
27
26
  end
28
27
 
29
28
  it "should update pagination" do