Merge branch 'main' into lexical

* main:
  Don't choke when rendering commands where the card was deleted
  Update tests
  Rework system events
  test: add missing assertions
  dep: bump active_record-tenanted to fix URI support
  Remove hyphen from "Un-do"
  Prevent default when handling keyboard shortcuts
  dep: update AR::Tenanted to support database URIs
  Detect platform and label the correct hotkey shortcut
  Things got too tight
  Don't require TLS for Beamer at the moment
  dep: update dependencies

# Conflicts:
#	Gemfile
#	Gemfile.lock
#	app/assets/stylesheets/comments.css
This commit is contained in:
Jorge Manrubia
2025-05-29 12:44:14 +02:00
19 changed files with 140 additions and 111 deletions
+1
View File
@@ -29,6 +29,7 @@ gem "rouge"
gem "jbuilder" gem "jbuilder"
gem "actiontext-lexical", github: "basecamp/actiontext-lexical" gem "actiontext-lexical", github: "basecamp/actiontext-lexical"
gem "image_processing", "~> 1.14" gem "image_processing", "~> 1.14"
gem "platform_agent"
# Telemetry and logging # Telemetry and logging
gem "sentry-ruby" gem "sentry-ruby"
+24 -40
View File
@@ -1,13 +1,6 @@
GIT
remote: https://github.com/basecamp/actiontext-lexical.git
revision: 19038b86b8617062adbb930990edaacc1f643e3d
specs:
actiontext-lexical (0.1.0)
rails (>= 8.0.2)
GIT GIT
remote: https://github.com/basecamp/active_record-tenanted remote: https://github.com/basecamp/active_record-tenanted
revision: 173048c2af29e9a723811e552bc59e3154e82e2a revision: 523947a0f0f49f66a8a62eb93e9ba1a345b3076b
specs: specs:
active_record-tenanted (0.1.0) active_record-tenanted (0.1.0)
activerecord (>= 8.1.alpha) activerecord (>= 8.1.alpha)
@@ -25,21 +18,20 @@ GIT
GIT GIT
remote: https://github.com/crmne/ruby_llm.git remote: https://github.com/crmne/ruby_llm.git
revision: 87a86f70cb64e23d5b76994edf8080618f07e071 revision: 8412d51e869f1ee8e5229831a9a0bb5e9b8f3847
specs: specs:
ruby_llm (1.3.0rc1) ruby_llm (1.2.0)
base64 base64
event_stream_parser (~> 1) event_stream_parser (~> 1)
faraday (>= 1.10.0) faraday (~> 2)
faraday-multipart (>= 1) faraday-multipart (~> 1)
faraday-net_http (>= 1) faraday-net_http (~> 3)
faraday-retry (>= 1) faraday-retry (~> 2)
marcel (~> 1.0)
zeitwerk (~> 2) zeitwerk (~> 2)
GIT GIT
remote: https://github.com/rails/rails.git remote: https://github.com/rails/rails.git
revision: bc7f77b32d7f3f26db654cae506c26a959852506 revision: c1d7d56e831acf0aff5f3e27a6b384f543f358d0
branch: main branch: main
specs: specs:
actioncable (8.1.0.alpha) actioncable (8.1.0.alpha)
@@ -73,6 +65,7 @@ GIT
rails-html-sanitizer (~> 1.6) rails-html-sanitizer (~> 1.6)
useragent (~> 0.16) useragent (~> 0.16)
actiontext (8.1.0.alpha) actiontext (8.1.0.alpha)
action_text-trix (~> 2.1.15)
actionpack (= 8.1.0.alpha) actionpack (= 8.1.0.alpha)
activerecord (= 8.1.0.alpha) activerecord (= 8.1.0.alpha)
activestorage (= 8.1.0.alpha) activestorage (= 8.1.0.alpha)
@@ -139,6 +132,8 @@ GIT
GEM GEM
remote: https://rubygems.org/ remote: https://rubygems.org/
specs: specs:
action_text-trix (2.1.15)
railties
addressable (2.8.7) addressable (2.8.7)
public_suffix (>= 2.0.2, < 7.0) public_suffix (>= 2.0.2, < 7.0)
ast (2.4.3) ast (2.4.3)
@@ -147,7 +142,7 @@ GEM
bcrypt_pbkdf (1.1.1) bcrypt_pbkdf (1.1.1)
benchmark (0.4.0) benchmark (0.4.0)
bigdecimal (3.1.9) bigdecimal (3.1.9)
bootsnap (1.18.4) bootsnap (1.18.6)
msgpack (~> 1.2) msgpack (~> 1.2)
brakeman (7.0.2) brakeman (7.0.2)
racc racc
@@ -193,10 +188,6 @@ GEM
net-http (>= 0.5.0) net-http (>= 0.5.0)
faraday-retry (2.3.1) faraday-retry (2.3.1)
faraday (~> 2.0) faraday (~> 2.0)
ffi (1.17.2)
ffi (1.17.2-arm64-darwin)
ffi (1.17.2-x86_64-darwin)
ffi (1.17.2-x86_64-linux-gnu)
fugit (1.11.1) fugit (1.11.1)
et-orbi (~> 1, >= 1.2.11) et-orbi (~> 1, >= 1.2.11)
raabro (~> 1.4) raabro (~> 1.4)
@@ -205,12 +196,9 @@ GEM
addressable (>= 2.5.0) addressable (>= 2.5.0)
globalid (1.2.1) globalid (1.2.1)
activesupport (>= 6.1) activesupport (>= 6.1)
hashdiff (1.2.0) hashdiff (1.1.2)
i18n (1.14.7) i18n (1.14.7)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
image_processing (1.14.0)
mini_magick (>= 4.9.5, < 6)
ruby-vips (>= 2.0.17, < 3)
importmap-rails (2.1.0) importmap-rails (2.1.0)
actionpack (>= 6.0.0) actionpack (>= 6.0.0)
activesupport (>= 6.0.0) activesupport (>= 6.0.0)
@@ -224,7 +212,7 @@ GEM
actionview (>= 5.0.0) actionview (>= 5.0.0)
activesupport (>= 5.0.0) activesupport (>= 5.0.0)
json (2.11.3) json (2.11.3)
kamal (2.6.0) kamal (2.6.1)
activesupport (>= 7.0) activesupport (>= 7.0)
base64 (~> 0.2) base64 (~> 0.2)
bcrypt_pbkdf (~> 1.0) bcrypt_pbkdf (~> 1.0)
@@ -248,9 +236,6 @@ GEM
net-smtp net-smtp
marcel (1.0.4) marcel (1.0.4)
matrix (0.4.2) matrix (0.4.2)
mini_magick (5.2.0)
benchmark
logger
mini_mime (1.1.5) mini_mime (1.1.5)
mini_portile2 (2.8.9) mini_portile2 (2.8.9)
minitest (5.25.5) minitest (5.25.5)
@@ -258,7 +243,7 @@ GEM
multipart-post (2.4.1) multipart-post (2.4.1)
net-http (0.6.0) net-http (0.6.0)
uri uri
net-imap (0.5.7) net-imap (0.5.8)
date date
net-protocol net-protocol
net-pop (0.1.2) net-pop (0.1.2)
@@ -287,6 +272,9 @@ GEM
parser (3.3.8.0) parser (3.3.8.0)
ast (~> 2.4.1) ast (~> 2.4.1)
racc racc
platform_agent (1.0.1)
activesupport (>= 5.2.0)
useragent (~> 0.16.3)
pp (0.6.2) pp (0.6.2)
prettyprint prettyprint
prettyprint (0.2.0) prettyprint (0.2.0)
@@ -366,24 +354,21 @@ GEM
rubocop-performance (>= 1.24) rubocop-performance (>= 1.24)
rubocop-rails (>= 2.30) rubocop-rails (>= 2.30)
ruby-progressbar (1.13.0) ruby-progressbar (1.13.0)
ruby-vips (2.2.3)
ffi (~> 1.12)
logger
rubyzip (2.4.1) rubyzip (2.4.1)
securerandom (0.4.1) securerandom (0.4.1)
selenium-webdriver (4.31.0) selenium-webdriver (4.32.0)
base64 (~> 0.2) base64 (~> 0.2)
logger (~> 1.4) logger (~> 1.4)
rexml (~> 3.2, >= 3.2.5) rexml (~> 3.2, >= 3.2.5)
rubyzip (>= 1.2.2, < 3.0) rubyzip (>= 1.2.2, < 3.0)
websocket (~> 1.0) websocket (~> 1.0)
sentry-rails (5.23.0) sentry-rails (5.24.0)
railties (>= 5.0) railties (>= 5.0)
sentry-ruby (~> 5.23.0) sentry-ruby (~> 5.24.0)
sentry-ruby (5.23.0) sentry-ruby (5.24.0)
bigdecimal bigdecimal
concurrent-ruby (~> 1.0, >= 1.0.2) concurrent-ruby (~> 1.0, >= 1.0.2)
solid_cable (3.0.7) solid_cable (3.0.8)
actioncable (>= 7.2) actioncable (>= 7.2)
activejob (>= 7.2) activejob (>= 7.2)
activerecord (>= 7.2) activerecord (>= 7.2)
@@ -463,7 +448,6 @@ PLATFORMS
x86_64-mingw32 x86_64-mingw32
DEPENDENCIES DEPENDENCIES
actiontext-lexical!
active_record-tenanted! active_record-tenanted!
bcrypt (~> 3.1.7) bcrypt (~> 3.1.7)
bootsnap bootsnap
@@ -472,10 +456,10 @@ DEPENDENCIES
capybara capybara
debug debug
geared_pagination (~> 1.2) geared_pagination (~> 1.2)
image_processing (~> 1.14)
importmap-rails importmap-rails
jbuilder jbuilder
kamal kamal
platform_agent
propshaft propshaft
puma (>= 5.0) puma (>= 5.0)
rails! rails!
+29 -3
View File
@@ -37,8 +37,10 @@
} }
.comment__author { .comment__author {
margin-inline-end: calc(var(--comment-padding-inline) * -0.5); .comment--system & strong {
display: none;
}
.btn { .btn {
font-weight: inherit; font-weight: inherit;
} }
@@ -51,9 +53,17 @@
.comment--mine_ & { .comment--mine_ & {
margin-inline: calc(var(--comment-padding-inline) * -0.75); margin-inline: calc(var(--comment-padding-inline) * -0.75);
} }
.comment--system & {
display: none;
}
} }
.comment__body { .comment__body {
.comment--system & {
text-align: center;
}
* { * {
&:first-child { &:first-child {
margin-block-start: 0; margin-block-start: 0;
@@ -99,8 +109,13 @@
} }
} }
.comment__event { .comment--system {
--comment-padding-block: calc(var(--block-space) * 0.66);
line-height: 1.2;
max-inline-size: var(--comment-max); max-inline-size: var(--comment-max);
text-align: center;
&::before { &::before {
/* Make up space for lack of avatar */ /* Make up space for lack of avatar */
@@ -108,5 +123,16 @@
display: flex; display: flex;
inline-size: calc(var(--comment-padding-inline) * 0.75); inline-size: calc(var(--comment-padding-inline) * 0.75);
} }
.comment__author {
a {
margin: 0 auto;
}
}
.comment__body {
margin-block-start: calc(var(--block-space-half) * -0.33);
text-align: center !important;
}
} }
} }
+1 -1
View File
@@ -7,7 +7,7 @@
font-family: var(--font-mono); font-family: var(--font-mono);
font-size: var(--text-normal); font-size: var(--text-normal);
inset: auto 0 0 0; inset: auto 0 0 0;
padding: var(--block-space) calc(var(--tray-size) + var(--inline-space-double)); padding: var(--block-space) calc(var(--tray-size) + calc(var(--inline-space) * 3));
position: fixed; position: fixed;
z-index: var(--z-terminal); z-index: var(--z-terminal);
+1 -1
View File
@@ -1,5 +1,5 @@
class ApplicationController < ActionController::Base class ApplicationController < ActionController::Base
include Authentication, CurrentTimezone include Authentication, CurrentTimezone, SetPlatform
stale_when_importmap_changes stale_when_importmap_changes
allow_browser versions: :modern allow_browser versions: :modern
+12
View File
@@ -0,0 +1,12 @@
module SetPlatform
extend ActiveSupport::Concern
included do
helper_method :platform
end
private
def platform
@platform ||= ApplicationPlatform.new(request.user_agent)
end
end
+19
View File
@@ -0,0 +1,19 @@
module HotkeysHelper
def hotkey_label(hotkey)
hotkey.split(",").first if hotkey
case hotkey
when Array
hotkey.map { |key|
if key == "ctrl" && platform.mac?
""
elsif key == "enter"
platform.mac? ? "return" : "enter"
else
key
end
}.join("+")
else
hotkey.split(",").first if hotkey
end
end
end
@@ -3,6 +3,7 @@ import { Controller } from "@hotwired/stimulus"
export default class extends Controller { export default class extends Controller {
click(event) { click(event) {
if (this.#isClickable && !this.#shouldIgnore(event)) { if (this.#isClickable && !this.#shouldIgnore(event)) {
event.preventDefault()
this.element.click() this.element.click()
} }
} }
+29
View File
@@ -0,0 +1,29 @@
class ApplicationPlatform < PlatformAgent
def ios?
match? /iPhone|iPad/
end
def android?
match? /Android/
end
def mac?
match? /Macintosh/
end
def chrome?
user_agent.browser.match? /Chrome/
end
def safari?
user_agent.browser.match? /Safari/
end
def mobile?
ios? || android?
end
def desktop?
!mobile?
end
end
+5 -29
View File
@@ -8,46 +8,22 @@ class Card::Eventable::SystemCommenter
def comment def comment
return unless comment_body.present? return unless comment_body.present?
if comment = find_replaceable_system_comment card.comments.create! creator: User.system, body: comment_body, created_at: event.created_at
comment.update! body: comment_body
else
card.comments.create! creator: User.system, body: comment_body, created_at: event.created_at
end
end end
private private
RELATED_EVENTS = [
%w[ card_assigned card_unassigned ]
]
def comment_body def comment_body
case event.action case event.action
when "card_assigned" when "card_assigned"
"Assigned to #{event.assignees.pluck(:name).to_sentence}." "#{event.creator.name} assigned this to #{event.assignees.pluck(:name).to_sentence}."
when "card_unassigned" when "card_unassigned"
"Unassigned from #{event.assignees.pluck(:name).to_sentence}." "#{event.creator.name} unassigned from #{event.assignees.pluck(:name).to_sentence}."
when "card_staged" when "card_staged"
"#{event.creator.name} moved this to '#{event.stage_name}'." "#{event.creator.name} moved this to '#{event.stage_name}'."
when "card_closed" when "card_closed"
"Closed by #{ event.creator.name }" "Closed as “#{ card.closure.reason } by #{ event.creator.name }"
when "card_title_changed" when "card_title_changed"
"#{event.creator.name} changed title from '#{event.particulars.dig('particulars', 'old_title')}' to '#{event.particulars.dig('particulars', 'new_title')}'." "#{event.creator.name} changed the title from '#{event.particulars.dig('particulars', 'old_title')}' to '#{event.particulars.dig('particulars', 'new_title')}'."
end end
end end
def find_replaceable_system_comment
previous_comment, previous_event = card.comments.last, card.events[-2]
if previous_comment&.creator&.system? && replaceable_event?(previous_event, event)
previous_comment
end
end
def replaceable_event?(candidate_event, replacement_event)
candidate_event && replacement_event &&
(candidate_event.action == replacement_event.action || related_events?(candidate_event, replacement_event))
end
def related_events?(candidate_event, replacement_event)
RELATED_EVENTS.find { |related_actions| related_actions.include?(candidate_event.action) && related_actions.include?(replacement_event.action) }
end
end end
+2 -2
View File
@@ -4,7 +4,7 @@ class Command::GoToCard < Command
validates_presence_of :card validates_presence_of :card
def title def title
"Visit card '#{card.title}'" "Visit card '#{card&.title}'"
end end
def execute def execute
@@ -13,6 +13,6 @@ class Command::GoToCard < Command
private private
def card def card
user.accessible_cards.find(card_id) user.accessible_cards.find_by_id(card_id)
end end
end end
+1 -1
View File
@@ -1,6 +1,6 @@
<% cache comment do %> <% cache comment do %>
<%= turbo_frame_tag comment do %> <%= turbo_frame_tag comment do %>
<div class="comment flex align-start full-width" data-creator-id="<%= comment.creator_id %>" data-created-by-current-user-target="creation"> <div class="comment flex align-start full-width <%= "comment--system" if comment.creator.system? %>" data-creator-id="<%= comment.creator_id %>" data-created-by-current-user-target="creation">
<figure class="comment__avatar flex-item-no-shrink" aria-hidden="true"> <figure class="comment__avatar flex-item-no-shrink" aria-hidden="true">
<%= avatar_tag comment.creator, hidden_for_screen_reader: true %> <%= avatar_tag comment.creator, hidden_for_screen_reader: true %>
</figure> </figure>
+1 -1
View File
@@ -3,7 +3,7 @@
<div class="flex gap-half margin-block-start-double margin-block-end"> <div class="flex gap-half margin-block-start-double margin-block-end">
<span class="card-perma__closure-message">Completed by <%= card.closed_by.name %> on <%= local_datetime_tag(card.closed_at, style: :shortdate) %>.</span> <span class="card-perma__closure-message">Completed by <%= card.closed_by.name %> on <%= local_datetime_tag(card.closed_at, style: :shortdate) %>.</span>
<%= button_to card_closure_path(card), method: :delete, class: "btn btn--plain borderless fill-transparent" do %> <%= button_to card_closure_path(card), method: :delete, class: "btn btn--plain borderless fill-transparent" do %>
<span class="pad-inline-end">Un-do</span> <span class="pad-inline-end">Undo</span>
<% end %> <% end %>
</div> </div>
<% else %> <% else %>
+2 -2
View File
@@ -7,7 +7,7 @@
<div class="card-filter--collection flex-inline flex-column align-center center position-relative" data-controller="dialog" data-action="click@document->dialog#closeOnClickOutside"> <div class="card-filter--collection flex-inline flex-column align-center center position-relative" data-controller="dialog" data-action="click@document->dialog#closeOnClickOutside">
<%= tag.button class: "txt-align-center input input--select borderless txt-medium", <%= tag.button class: "txt-align-center input input--select borderless txt-medium",
data: { data: {
action: "click->dialog#open:stop keydown.meta+j@document->hotkey#click", action: "click->dialog#open:stop keydown.meta+j@document->hotkey#click keydown.ctrl+j@document->hotkey#click",
controller: "hotkey" } do %> controller: "hotkey" } do %>
<h1 class="txt-large overflow-ellipsis margin-none"> <h1 class="txt-large overflow-ellipsis margin-none">
<span><%= filter_title filter %></span> <span><%= filter_title filter %></span>
@@ -38,7 +38,7 @@
<%= render "cards/index/collections_filter", filter: filter %> <%= render "cards/index/collections_filter", filter: filter %>
</menu> </menu>
<p class="txt-small txt-nowrap txt-align-center margin-block-half full-width"> <p class="txt-small txt-nowrap txt-align-center margin-block-half full-width">
Press <kbd class="kbd">⌘j</kbd> anytime to open this, <kbd class="kbd">esc</kbd> to close. Press <kbd class="kbd"><%= hotkey_label(["ctrl", "J"]) -%></kbd> anytime to open this, <kbd class="kbd">esc</kbd> to close.
</p> </p>
<% end %> <% end %>
</div> </div>
+2 -3
View File
@@ -4,7 +4,7 @@
data: { data: {
controller: "form", controller: "form",
terminal_target: "form", terminal_target: "form",
action: "turbo:submit-start->terminal#commandSubmitted turbo:submit-end->terminal#focus keydown.meta+k@document->terminal#focus keydown.enter->terminal#executeCommand keydown.esc->terminal#hideMenus turbo:submit-end->terminal#handleCommandResponse" action: "turbo:submit-start->terminal#commandSubmitted turbo:submit-end->terminal#focus keydown.meta+k@document->terminal#focus:prevent keydown.ctrl+k@document->terminal#focus:prevent keydown.enter->terminal#executeCommand keydown.esc->terminal#hideMenus turbo:submit-end->terminal#handleCommandResponse"
} do %> } do %>
<label class="terminal__label txt-nowrap" for="fizzy_do">Fizzy do &gt;</label> <label class="terminal__label txt-nowrap" for="fizzy_do">Fizzy do &gt;</label>
@@ -17,9 +17,8 @@
terminal_target: "input", terminal_target: "input",
action: "keydown.up->toggle-class#add:prevent keydown.up->navigable-list#selectCurrentOrLast terminal#hideError" action: "keydown.up->toggle-class#add:prevent keydown.up->navigable-list#selectCurrentOrLast terminal#hideError"
}, },
placeholder: "Press ⌘+K to search or type commands…", placeholder: "Press #{ hotkey_label(["ctrl", "K"]) } to search or type commands…",
spellcheck: "false" %> spellcheck: "false" %>
<%= hidden_field_tag "confirmed", nil, data: { terminal_target: "confirmation" } %> <%= hidden_field_tag "confirmed", nil, data: { terminal_target: "confirmation" } %>
<% end %> <% end %>
+2 -2
View File
@@ -2,7 +2,7 @@
data-controller="dialog" data-action="keydown.esc->dialog#close click@document->dialog#closeOnClickOutside"> data-controller="dialog" data-action="keydown.esc->dialog#close click@document->dialog#closeOnClickOutside">
<%= tag.button class:"events__filter-btn input input--select flex-inline center min-width max-width txt-small", <%= tag.button class:"events__filter-btn input input--select flex-inline center min-width max-width txt-small",
data: { data: {
action: "click->dialog#open:stop keydown.meta+j@document->hotkey#click", action: "click->dialog#open:stop keydown.meta+j@document->hotkey#click keydown.ctrl+j@document->hotkey#click",
controller: "hotkey" } do %> controller: "hotkey" } do %>
<span class="overflow-ellipsis center"> <span class="overflow-ellipsis center">
<%= filter_selected_collections_label(filter) %> <%= filter_selected_collections_label(filter) %>
@@ -35,7 +35,7 @@
<% end %> <% end %>
<p class="txt-small txt-nowrap txt-align-center margin-block-half full-width"> <p class="txt-small txt-nowrap txt-align-center margin-block-half full-width">
Press <kbd class="kbd">⌘j</kbd> anytime to open this, <kbd class="kbd">esc</kbd> to close. Press <kbd class="kbd"><%= hotkey_label(["ctrl", "K"]) -%></kbd> anytime to open this, <kbd class="kbd">esc</kbd> to close.
</p> </p>
<% end %> <% end %>
</div> </div>
+1 -1
View File
@@ -33,7 +33,7 @@ accessories:
beamer-primary: beamer-primary:
<<: *beamer-accessory <<: *beamer-accessory
service: beamer-primary service: beamer-primary
cmd: beamer primary --remove-after=1h --dir=/storage cmd: beamer primary --remove-after=1h --dir=/storage --disable-tls
port: 5000:80 port: 5000:80
roles: roles:
- web - web
@@ -7,7 +7,7 @@ class Card::Eventable::SystemCommenterTest < ActiveSupport::TestCase
end end
test "card_assigned" do test "card_assigned" do
assert_system_comment "Assigned to Kevin" do assert_system_comment "David assigned this to Kevin." do
@card.toggle_assignment users(:kevin) @card.toggle_assignment users(:kevin)
end end
end end
@@ -16,7 +16,7 @@ class Card::Eventable::SystemCommenterTest < ActiveSupport::TestCase
@card.toggle_assignment users(:kevin) @card.toggle_assignment users(:kevin)
@card.comments.destroy_all # To skip deduplication logic @card.comments.destroy_all # To skip deduplication logic
assert_system_comment "Unassigned from Kevin" do assert_system_comment "David unassigned from Kevin" do
@card.toggle_assignment users(:kevin) @card.toggle_assignment users(:kevin)
end end
end end
@@ -28,35 +28,17 @@ class Card::Eventable::SystemCommenterTest < ActiveSupport::TestCase
end end
test "card_closed" do test "card_closed" do
assert_system_comment "Closed by David" do assert_system_comment "Closed as “Done” by David" do
@card.close @card.close
end end
end end
test "card_title_changed" do test "card_title_changed" do
assert_system_comment "David changed title from 'The text is too small' to 'Make text larger'" do assert_system_comment "David changed the title from 'The text is too small' to 'Make text larger'." do
@card.update! title: "Make text larger" @card.update! title: "Make text larger"
end end
end end
test "don't duplicate comments for the same kind of events" do
@card.change_stage_to workflow_stages(:qa_in_progress)
assert_no_difference -> { @card.comments.count } do
@card.reload.change_stage_to workflow_stages(:qa_on_hold)
assert_match /David moved this to 'On hold'/i, @card.comments.last.body.to_plain_text
end
end
test "don't duplicate comments for related events" do
@card.toggle_assignment users(:kevin)
assert_no_difference -> { @card.comments.count } do
@card.reload.toggle_assignment users(:kevin)
assert_match /Unassigned from Kevin/i, @card.comments.last.body.to_plain_text
end
end
test "don't notify on system comments" do test "don't notify on system comments" do
@card.watch_by(users(:david)) @card.watch_by(users(:david))
+3 -3
View File
@@ -32,7 +32,7 @@ class Command::CloseTest < ActionDispatch::IntegrationTest
execute_command "/close", context_url: collection_cards_url(@card.collection) execute_command "/close", context_url: collection_cards_url(@card.collection)
cards.each { it.reload.closed? } assert cards.map(&:reload).all?(&:closed?)
end end
test "undo close" do test "undo close" do
@@ -42,10 +42,10 @@ class Command::CloseTest < ActionDispatch::IntegrationTest
command = parse_command "/close", context_url: collection_cards_url(@card.collection) command = parse_command "/close", context_url: collection_cards_url(@card.collection)
command.execute command.execute
cards.each { it.reload.closed? } assert cards.map(&:reload).all?(&:closed?)
command.undo command.undo
cards.each { it.reload.open? } assert cards.map(&:reload).all?(&:open?)
end end
end end