time_accounting_spec.rb 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265
  1. # Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
  2. require 'rails_helper'
  3. RSpec.describe 'Ticket zoom > Time Accounting', authenticated_as: :authenticate, type: :system do
  4. let(:ticket) { create(:ticket, group: Group.find_by(name: 'Users')) }
  5. let(:article_body) { Faker::Hacker.unique.say_something_smart }
  6. let(:time_unit) { Faker::Number.unique.decimal(l_digits: 1, r_digits: 1) }
  7. let(:time_accounting_unit) { nil }
  8. let(:time_accounting_unit_custom) { nil }
  9. let(:time_accounting_types) { false }
  10. let(:active_type) { create(:ticket_time_accounting_type) }
  11. let(:inactive_type) { create(:ticket_time_accounting_type, active: false) }
  12. let(:create_new_article) { true }
  13. def authenticate
  14. Setting.set('time_accounting', true)
  15. Setting.set('time_accounting_types', time_accounting_types)
  16. Setting.set('time_accounting_unit', time_accounting_unit) if time_accounting_unit.present?
  17. Setting.set('time_accounting_unit_custom', time_accounting_unit_custom) if time_accounting_unit == 'custom'
  18. active_type && inactive_type
  19. true
  20. end
  21. before do
  22. visit "#ticket/zoom/#{ticket.id}"
  23. if create_new_article
  24. find(:richtext).send_keys article_body
  25. click_on 'Update'
  26. accounted_time_modal_hook if defined?(accounted_time_modal_hook)
  27. end
  28. end
  29. describe 'time units' do
  30. shared_examples 'accounting time' do |time_accounting_unit = '', display_unit = nil|
  31. let(:time_accounting_unit) { time_accounting_unit }
  32. let(:time_accounting_unit_custom) { display_unit }
  33. it 'accounts time with no unit', if: !display_unit do
  34. in_modal do
  35. expect(page).to have_text('Time Accounting')
  36. expect(page).to have_no_css('.time-accounting-display-unit')
  37. fill_in 'time_unit', with: time_unit
  38. click_on 'Account Time'
  39. end
  40. expect(find('.accounted-time-value-row:nth-of-type(1)')).to have_text("Total\n#{time_unit}", exact: true)
  41. end
  42. it "accounts time in #{display_unit}", if: display_unit do
  43. in_modal do
  44. expect(page).to have_text('Time Accounting')
  45. expect(page).to have_text(display_unit)
  46. fill_in 'time_unit', with: time_unit
  47. click_on 'Account Time'
  48. end
  49. expect(find('.accounted-time-value-row:nth-of-type(1)')).to have_text("Total\n#{time_unit}\n#{display_unit}", exact: true)
  50. end
  51. end
  52. context 'without a unit' do
  53. it_behaves_like 'accounting time'
  54. end
  55. context 'with a pre-defined unit' do
  56. it_behaves_like 'accounting time', 'minute', 'minute(s)'
  57. end
  58. context 'with a custom unit' do
  59. it_behaves_like 'accounting time', 'custom', 'person day(s)'
  60. end
  61. end
  62. describe 'time input' do
  63. it 'handles user hiding the time accounting modal' do
  64. in_modal do
  65. # click on background to close modal
  66. execute_script 'document.elementFromPoint(300, 100).click();'
  67. end
  68. # try to submit again
  69. click_on 'Update'
  70. in_modal do
  71. fill_in 'time_unit', with: '123'
  72. click_on 'Account Time'
  73. end
  74. expect(find('.accounted-time-value-row:nth-of-type(1)')).to have_text("Total\n123.0", exact: true)
  75. end
  76. it 'allows to input time with a comma and saves with a dot instead' do
  77. in_modal do
  78. fill_in 'time_unit', with: '4,6'
  79. click_on 'Account Time'
  80. end
  81. expect(find('.accounted-time-value-row:nth-of-type(1)')).to have_text("Total\n4.6", exact: true)
  82. end
  83. it 'allows to input time with a trailing space' do
  84. in_modal do
  85. fill_in 'time_unit', with: '4 '
  86. click_on 'Account Time'
  87. end
  88. expect(find('.accounted-time-value-row:nth-of-type(1)')).to have_text("Total\n4.0", exact: true)
  89. end
  90. it 'does not allow to input time with letters' do
  91. in_modal do
  92. fill_in 'time_unit', with: '4abc'
  93. click_on 'Account Time'
  94. expect(page).to have_css('.input.has-error [name=time_unit]')
  95. end
  96. end
  97. end
  98. describe 'activity type' do
  99. context 'when time_accounting_types is disabled' do
  100. it 'does not show types dropdown' do
  101. in_modal do
  102. expect(page).to have_no_text %r{Activity Type}i
  103. end
  104. end
  105. end
  106. context 'when time_accounting_types is enabled' do
  107. let(:time_accounting_types) { true }
  108. it 'shows types dropdown' do
  109. in_modal do
  110. expect(page).to have_select 'Activity Type', options: ['-', active_type.name]
  111. end
  112. end
  113. context 'when more than three types are used', authenticated_as: :authenticate do
  114. let(:create_new_article) { false }
  115. let(:types) { create_list(:ticket_time_accounting_type, 4) }
  116. def authenticate
  117. Setting.set('time_accounting', true)
  118. Setting.set('time_accounting_types', time_accounting_types)
  119. Setting.set('time_accounting_unit', time_accounting_unit) if time_accounting_unit.present?
  120. Setting.set('time_accounting_unit_custom', time_accounting_unit_custom) if time_accounting_unit == 'custom'
  121. 3.times do
  122. create(:ticket_time_accounting, ticket: ticket, time_unit: 25, type: types[0])
  123. create(:ticket_time_accounting, ticket: ticket, time_unit: 50, type: types[1])
  124. create(:ticket_time_accounting, ticket: ticket, time_unit: 75, type: types[2])
  125. end
  126. create(:ticket_time_accounting, ticket: ticket, time_unit: 50, type: types[3])
  127. true
  128. end
  129. it 'shows a table for the top three that is sorted correctly by default and can show all entries', :aggregate_failures do
  130. expect(page).to have_css('.accounted-time-value-row:nth-of-type(1)', text: "Total\n500.0")
  131. expect(page).to have_css('.accounted-time-value-row:nth-of-type(2)', text: "#{types[2].name}\n225.0")
  132. expect(page).to have_css('.accounted-time-value-row:nth-of-type(3)', text: "#{types[1].name}\n150.0")
  133. expect(page).to have_css('.accounted-time-value-row:nth-of-type(4)', text: "#{types[0].name}\n75.0")
  134. expect(page).to have_css('.accounted-time-value-container .js-showMoreEntries')
  135. click('.accounted-time-value-container .js-showMoreEntries')
  136. expect(page).to have_css('.accounted-time-value-row:nth-of-type(5)', text: "#{types[3].name}\n50.0")
  137. end
  138. end
  139. context 'when new time is accounted or article got deleted', authenticated_as: :authenticate do
  140. let(:types) { create_list(:ticket_time_accounting_type, 4) }
  141. let(:accounted_time_modal_hook) do
  142. in_modal do
  143. fill_in 'time_unit', with: '123'
  144. select types[0].name, from: 'Activity Type'
  145. click_on 'Account Time'
  146. end
  147. end
  148. def authenticate
  149. Setting.set('time_accounting', true)
  150. Setting.set('time_accounting_types', time_accounting_types)
  151. types
  152. true
  153. end
  154. it 'updates the table correctly' do
  155. expect(page).to have_css('.accounted-time-value-row:nth-of-type(1)', text: "Total\n123.0")
  156. expect(page).to have_css('.accounted-time-value-row:nth-of-type(2)', text: "#{types[0].name}\n123.0")
  157. within :active_ticket_article, ticket.reload.articles.last do
  158. click '.js-ArticleAction[data-type=delete]'
  159. end
  160. in_modal do
  161. click '.js-submit'
  162. end
  163. expect(page).to have_css('.accounted-time-value-row:nth-of-type(1)', text: "Total\n0.0")
  164. expect(page).to have_no_css('.accounted-time-value-row:nth-of-type(2)', text: "#{types[0].name}\n123.0")
  165. end
  166. end
  167. end
  168. end
  169. describe "Time accounting: don't list activity types if they are not used #4806", authenticated_as: :authenticate do
  170. let(:create_new_article) { false }
  171. context 'with types' do
  172. let(:type) { create(:ticket_time_accounting_type) }
  173. def authenticate
  174. Setting.set('time_accounting', true)
  175. Setting.set('time_accounting_types', true)
  176. type
  177. create(:ticket_time_accounting, ticket: ticket, time_unit: 25)
  178. true
  179. end
  180. it 'does not show the None category if there are no accountings on categories' do
  181. within '.accounted-time-value-container' do
  182. expect(page).to have_no_text('None')
  183. end
  184. end
  185. end
  186. context 'with types disabled' do
  187. let(:type) { create(:ticket_time_accounting_type) }
  188. def authenticate
  189. Setting.set('time_accounting', true)
  190. Setting.set('time_accounting_types', false)
  191. create(:ticket_time_accounting, ticket: ticket, time_unit: 25, type: type)
  192. true
  193. end
  194. it 'does not show the Type category if types are disabled' do
  195. within '.accounted-time-value-container' do
  196. expect(page).to have_no_text(type.name)
  197. end
  198. end
  199. end
  200. end
  201. end