Skip to content

Commit a3caa0a

Browse files
authored
DEV: refactor prosemirror_editor_spec monolithic test file into 11 files (#36621)
1 parent 045a952 commit a3caa0a

13 files changed

+2132
-2336
lines changed
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# frozen_string_literal: true
2+
3+
RSpec.shared_context "with prosemirror editor" do
4+
fab!(:current_user) do
5+
Fabricate(
6+
:user,
7+
refresh_auto_groups: true,
8+
composition_mode: UserOption.composition_mode_types[:rich],
9+
)
10+
end
11+
12+
fab!(:tag)
13+
fab!(:category_with_emoji) do
14+
Fabricate(:category, slug: "cat", emoji: "cat", style_type: "emoji")
15+
end
16+
fab!(:category_with_icon) { Fabricate(:category, icon: "bell", style_type: "icon") }
17+
fab!(:category_without_icon, :category)
18+
19+
let(:cdp) { PageObjects::CDP.new }
20+
let(:composer) { PageObjects::Components::Composer.new }
21+
let(:rich) { composer.rich_editor }
22+
23+
before { sign_in(current_user) }
24+
25+
def open_composer
26+
page.visit "/new-topic"
27+
expect(composer).to be_opened
28+
composer.focus
29+
end
30+
31+
def paste_and_click_image
32+
# This helper can only be used reliably to paste a single image when no other images are present.
33+
expect(rich).to have_no_css(".composer-image-node img")
34+
35+
cdp.allow_clipboard
36+
cdp.copy_test_image
37+
cdp.paste
38+
39+
expect(rich).to have_css(".composer-image-node img", count: 1)
40+
expect(rich).to have_no_css(".composer-image-node img[src='/images/transparent.png']")
41+
expect(rich).to have_no_css(".composer-image-node img[data-placeholder='true']")
42+
43+
rich.find(".composer-image-node img").click
44+
45+
expect(rich).to have_css(".composer-image-node .fk-d-menu", count: 2)
46+
end
47+
end
Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
# frozen_string_literal: true
2+
3+
describe "Composer - ProseMirror - Autocomplete", type: :system do
4+
include_context "with prosemirror editor"
5+
6+
it "triggers an autocomplete on mention" do
7+
open_composer
8+
composer.type_content("@#{current_user.username}")
9+
10+
expect(composer).to have_mention_autocomplete
11+
end
12+
13+
it "triggers an autocomplete on hashtag" do
14+
open_composer
15+
composer.type_content("##{tag.name}")
16+
17+
expect(composer).to have_hashtag_autocomplete
18+
end
19+
20+
it "triggers an autocomplete on emoji" do
21+
open_composer
22+
composer.type_content(":smile")
23+
24+
expect(composer).to have_emoji_autocomplete
25+
end
26+
27+
it "strips partially written emoji when using 'more' emoji modal" do
28+
open_composer
29+
30+
composer.type_content("Why :repeat_single")
31+
32+
expect(composer).to have_emoji_autocomplete
33+
34+
# "more" emoji picker
35+
composer.send_keys(:down, :enter)
36+
find("img[data-emoji='repeat_single_button']").click
37+
composer.toggle_rich_editor
38+
39+
expect(composer).to have_value("Why :repeat_single_button: ")
40+
end
41+
42+
context "with composer messages" do
43+
fab!(:category)
44+
45+
it "shows a popup" do
46+
open_composer
47+
composer.type_content("Maybe @staff can help?")
48+
49+
expect(composer).to have_popup_content(
50+
I18n.t("js.composer.cannot_see_group_mention.not_mentionable", group: "staff"),
51+
)
52+
end
53+
end
54+
55+
describe "mentions" do
56+
fab!(:topic) { Fabricate(:topic, category: category_with_emoji) }
57+
fab!(:post) { Fabricate(:post, topic: topic) }
58+
fab!(:mixed_case_user) { Fabricate(:user, username: "TestUser_123") }
59+
fab!(:mixed_case_group) do
60+
Fabricate(:group, name: "TestGroup_ABC", mentionable_level: Group::ALIAS_LEVELS[:everyone])
61+
end
62+
63+
before do
64+
Draft.set(
65+
current_user,
66+
topic.draft_key,
67+
0,
68+
{ reply: "hey @#{current_user.username} and @unknown - how are you?" }.to_json,
69+
)
70+
end
71+
72+
it "validates manually typed mentions" do
73+
open_composer
74+
75+
composer.type_content("Hey @#{current_user.username} ")
76+
77+
expect(rich).to have_css("a.mention", text: current_user.username)
78+
79+
composer.type_content("and @invalid_user - how are you?")
80+
81+
expect(rich).to have_no_css("a.mention", text: "@invalid_user")
82+
83+
composer.toggle_rich_editor
84+
85+
expect(composer).to have_value(
86+
"Hey @#{current_user.username} and @invalid_user - how are you?",
87+
)
88+
end
89+
90+
it "validates mentions in drafts" do
91+
page.visit("/t/#{topic.id}")
92+
93+
expect(composer).to be_opened
94+
95+
expect(rich).to have_css("a.mention", text: current_user.username)
96+
expect(rich).to have_no_css("a.mention", text: "@unknown")
97+
end
98+
99+
it "validates mentions case-insensitively" do
100+
open_composer
101+
102+
composer.type_content("Hey @testuser_123 and @TESTUSER_123 ")
103+
104+
expect(rich).to have_css("a.mention", text: "testuser_123")
105+
expect(rich).to have_css("a.mention", text: "TESTUSER_123")
106+
107+
composer.type_content("and @InvalidUser ")
108+
109+
expect(rich).to have_no_css("a.mention", text: "@InvalidUser")
110+
end
111+
112+
it "validates group mentions case-insensitively" do
113+
open_composer
114+
115+
composer.type_content("Hey @testgroup_abc and @TESTGROUP_ABC ")
116+
117+
expect(rich).to have_css("a.mention", text: "testgroup_abc")
118+
expect(rich).to have_css("a.mention", text: "TESTGROUP_ABC")
119+
120+
composer.type_content("and @InvalidGroup ")
121+
122+
expect(rich).to have_no_css("a.mention", text: "@InvalidGroup")
123+
end
124+
125+
context "with unicode usernames" do
126+
fab!(:category)
127+
128+
before do
129+
SiteSetting.external_system_avatars_enabled = true
130+
SiteSetting.external_system_avatars_url =
131+
"/letter_avatar_proxy/v4/letter/{first_letter}/{color}/{size}.png"
132+
SiteSetting.unicode_usernames = true
133+
end
134+
135+
it "renders unicode mentions as nodes" do
136+
unicode_user = Fabricate(:unicode_user)
137+
138+
open_composer
139+
140+
composer.type_content("Hey @#{unicode_user.username} - how are you?")
141+
142+
expect(rich).to have_css("a.mention", text: unicode_user.username)
143+
144+
composer.toggle_rich_editor
145+
146+
expect(composer).to have_value("Hey @#{unicode_user.username} - how are you?")
147+
end
148+
end
149+
end
150+
151+
describe "hashtags" do
152+
it "correctly renders category with emoji hashtags after selecting from autocomplete" do
153+
open_composer
154+
155+
composer.type_content("here is the ##{category_with_emoji.slug[0..1]}")
156+
expect(composer).to have_hashtag_autocomplete
157+
158+
# the xpath here is to get the parent element, which is the actual hashtag-autocomplete__option
159+
find(".hashtag-color--category-#{category_with_emoji.id}").find(:xpath, "..").click
160+
expect(rich).to have_css(
161+
".hashtag-cooked .hashtag-category-emoji.hashtag-color--category-#{category_with_emoji.id} img.emoji[title='cat']",
162+
)
163+
end
164+
165+
it "correctly renders category with icon hashtags after selecting from autocomplete" do
166+
open_composer
167+
168+
composer.type_content("here is the ##{category_with_icon.slug[0..1]}")
169+
expect(composer).to have_hashtag_autocomplete
170+
171+
find(".hashtag-color--category-#{category_with_icon.id}").find(:xpath, "..").click
172+
expect(rich).to have_css(
173+
".hashtag-cooked .hashtag-category-icon.hashtag-color--category-#{category_with_icon.id} svg.d-icon.d-icon-bell",
174+
)
175+
expect(rich).to have_css(".hashtag-cooked svg use[href='#bell']")
176+
end
177+
178+
it "correctly renders category with square hashtags after selecting from autocomplete" do
179+
open_composer
180+
181+
composer.type_content("here is the ##{category_without_icon.slug[0..1]}")
182+
expect(composer).to have_hashtag_autocomplete
183+
184+
find(".hashtag-color--category-#{category_without_icon.id}").find(:xpath, "..").click
185+
expect(rich).to have_css(
186+
".hashtag-cooked .hashtag-category-square.hashtag-color--category-#{category_without_icon.id}",
187+
)
188+
end
189+
190+
it "correctly renders tag hashtags after selecting from autocomplete" do
191+
open_composer
192+
193+
composer.type_content("##{tag.name[0..2]}")
194+
expect(composer).to have_hashtag_autocomplete
195+
196+
find(".hashtag-color--tag-#{tag.id}").find(:xpath, "..").click
197+
expect(rich).to have_css(".hashtag-cooked .d-icon.d-icon-tag.hashtag-color--tag-#{tag.id}")
198+
end
199+
end
200+
end

0 commit comments

Comments
 (0)