Skip to content

Commit 1476c21

Browse files
authored
Merge pull request #2990 from basecamp/card-description-turbo-refresh
Morph card title and description on broadcast refreshes
2 parents d3a3998 + d37480d commit 1476c21

6 files changed

Lines changed: 107 additions & 10 deletions

File tree

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { Controller } from "@hotwired/stimulus"
2+
3+
// Marks the enclosing turbo-frame permanent while connected, so that a
4+
// broadcasted page refresh can't morph away an edit in progress. The attribute
5+
// is never in the server-rendered markup, so display mode stays morphable and
6+
// page replacements never transplant the frame (https://github.com/basecamp/lexxy/issues/263).
7+
export default class extends Controller {
8+
connect() {
9+
this.frame = this.element.closest("turbo-frame")
10+
this.frame?.setAttribute("data-turbo-permanent", "")
11+
}
12+
13+
disconnect() {
14+
this.frame?.removeAttribute("data-turbo-permanent")
15+
}
16+
}

app/views/cards/container/_content.html.erb

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
<% if card.published? %>
2-
<div data-turbo-permanent>
32
<%= turbo_frame_tag card, :edit do %>
43
<%# When canceling an edit (with the ESC key), restore the button area to show "Edit" instead of "Save changes". %>
54
<%= turbo_stream.replace dom_id(card, :card_closure_toggle) do %>
@@ -8,7 +7,6 @@
87

98
<%= render "cards/container/content_display", card: card %>
109
<% end %>
11-
</div>
1210
<% else %>
1311
<%= form_with model: card, id: "card_form", data: { controller: "autoresize auto-save" } do |form| %>
1412
<h1 class="card__title">

app/views/cards/edit.html.erb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
<% end %>
88

99
<%= form_with model: @card, id: dom_id(@card, :edit_form),
10-
data: { controller: "autoresize form local-save", local_save_key_value: "card-#{@card.id}", action: "turbo:submit-end->local-save#submit" } do |form| %>
10+
data: { controller: "autoresize form local-save morph-guard", local_save_key_value: "card-#{@card.id}", action: "turbo:submit-end->local-save#submit" } do |form| %>
1111
<h1 class="card__title flex align-start gap-half">
1212
<%= form.label :title, class: "flex flex-column align-center autoresize__wrapper", data: { autoresize_target: "wrapper", autoresize_clone_value: "" } do %>
1313
<%= form.text_area :title, class: "card-field__title autoresize__textarea input input--textarea full-width borderless txt-align-start hide-focus-ring hide-scrollbar",

test/application_system_test_case.rb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,10 @@ def sign_in_as(user)
3434
visit session_transfer_url(user.identity.transfer_id, script_name: nil)
3535
assert_current_path root_path
3636
end
37+
38+
def fill_in_lexxy(selector = "lexxy-editor", with:)
39+
editor_element = find(selector)
40+
editor_element.set with
41+
page.execute_script("arguments[0].value = '#{with}'", editor_element)
42+
end
3743
end

test/system/card_refresh_test.rb

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
require "application_system_test_case"
2+
3+
class CardRefreshTest < ApplicationSystemTestCase
4+
include ActionView::RecordIdentifier
5+
6+
setup do
7+
sign_in_as(users(:david))
8+
Current.user = users(:kevin)
9+
@card = cards(:layout)
10+
end
11+
12+
test "a broadcast refresh shows card changes made elsewhere" do
13+
visit card_url(@card)
14+
assert_selector "h1", text: @card.title
15+
16+
update_card_elsewhere
17+
18+
assert_selector "h1", text: "Retitled elsewhere", wait: 5
19+
assert_text "Description updated elsewhere"
20+
end
21+
22+
test "a broadcast refresh preserves an edit in progress" do
23+
visit card_url(@card)
24+
click_on @card.title
25+
26+
within_edit_frame do
27+
fill_in_lexxy with: "Draft description in progress"
28+
end
29+
30+
update_card_elsewhere
31+
32+
# The title change's system comment appearing proves the refresh reached the page.
33+
assert_text "changed the title", wait: 5
34+
within_edit_frame do
35+
assert_selector "lexxy-editor", text: "Draft description in progress"
36+
end
37+
assert_no_text "Description updated elsewhere"
38+
end
39+
40+
test "canceling an edit reveals card changes made elsewhere during the edit" do
41+
visit card_url(@card)
42+
click_on @card.title
43+
within_edit_frame { assert_selector "form" }
44+
45+
update_card_elsewhere
46+
assert_text "changed the title", wait: 5
47+
48+
send_keys :escape
49+
50+
assert_selector "h1", text: "Retitled elsewhere"
51+
assert_text "Description updated elsewhere"
52+
end
53+
54+
test "a broadcast refresh shows card changes after an edit is canceled" do
55+
visit card_url(@card)
56+
click_on @card.title
57+
within_edit_frame { assert_selector "form" }
58+
59+
send_keys :escape
60+
assert_selector "a.card__title-link", text: @card.title
61+
62+
update_card_elsewhere
63+
64+
assert_selector "h1", text: "Retitled elsewhere", wait: 5
65+
assert_text "Description updated elsewhere"
66+
end
67+
68+
private
69+
def update_card_elsewhere
70+
wait_for_cable_subscriptions
71+
@card.update!(title: "Retitled elsewhere", description: "Description updated elsewhere")
72+
perform_enqueued_jobs only: Turbo::Streams::BroadcastStreamJob
73+
end
74+
75+
# A broadcast sent before the page's subscriptions are confirmed is lost.
76+
def wait_for_cable_subscriptions
77+
assert_selector "turbo-cable-stream-source[connected]", visible: :all
78+
assert_no_selector "turbo-cable-stream-source:not([connected])", visible: :all
79+
end
80+
81+
def within_edit_frame(&block)
82+
within "##{dom_id(@card, :edit)}", &block
83+
end
84+
end

test/system/smoke_test.rb

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -93,11 +93,4 @@ class SmokeTest < ApplicationSystemTestCase
9393
column_el.find(".cards__expander-count", text: cards_count + 1)
9494
assert_equal("Triage", card.reload.column.name)
9595
end
96-
97-
private
98-
def fill_in_lexxy(selector = "lexxy-editor", with:)
99-
editor_element = find(selector)
100-
editor_element.set with
101-
page.execute_script("arguments[0].value = '#{with}'", editor_element)
102-
end
10396
end

0 commit comments

Comments
 (0)