0
0
Fork 0
mirror of https://github.com/discourse/discourse.git synced 2026-08-09 21:45:25 +08:00
discourse/plugins/discourse-calendar/spec/system/upcoming_events_spec.rb
Régis Hanol f91d7a9976
FIX: Cook event location and description as inline markdown (#41994)
Previously, a markdown link in an event's location rendered correctly in
email notifications but showed as raw `[text](url)` markup on the event
card. Every surface (card, email, topic excerpt) re-implemented its own
rendering of the same fields, and `extract_events` HTML-escaped the
stored location one level further on every edit.

This change cooks `location` and `description` once, server side, with a
restricted inline pipeline (links and emoji only, so onebox embeds
cannot return), and every surface renders through that shared cook — the
card via new `location_html`/`description_html` serializer attributes.
It stops escaping the stored location, with a migration healing existing
rows, and moves the composer's `[event]` parsing onto core's
`parseBBCodeTag` so quoted attribute values containing `]` survive
round-trips.

Ref - t/188447
2026-07-28 18:59:23 +02:00

707 lines
23 KiB
Ruby
Vendored
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# frozen_string_literal: true
describe "Upcoming Events" do
fab!(:admin)
fab!(:user)
fab!(:category)
let(:composer) { PageObjects::Components::Composer.new }
let(:topic_page) { PageObjects::Pages::Topic.new }
let(:upcoming_events) { PageObjects::Pages::DiscourseCalendar::UpcomingEvents.new }
before do
SiteSetting.calendar_enabled = true
SiteSetting.discourse_post_event_enabled = true
SiteSetting.calendar_upcoming_events_default_view = "month"
sign_in(admin)
end
describe "basic functionality" do
it "displays events in the calendar", time: Time.utc(2025, 9, 10, 12, 0) do
post =
create_post(
user: admin,
category: Fabricate(:category),
title: "A great event to join",
raw: "[event start=\"2025-09-11 08:05\" end=\"2025-09-11 10:05\"]\n[/event]",
)
upcoming_events.visit
expect(upcoming_events).to have_content_in_calendar(post.topic.title)
end
end
describe "popup invitees on recurring events" do
fab!(:going_recurring_user, :user)
fab!(:going_once_user, :user)
let(:post_event_page) { PageObjects::Pages::DiscourseCalendar::PostEvent.new }
it "filters non-recurring goings out of future occurrences",
time: Time.utc(2026, 5, 12, 12, 0) do
set_subfolder "/discuss"
post =
create_post(
user: admin,
category:,
title: "Weekly stand-up event",
raw: "[event status='public' start='2026-05-14 14:00' recurrence='every_week']\n[/event]",
)
event = DiscoursePostEvent::Event.find(post.id)
DiscoursePostEvent::Invitee.create_attendance!(
going_recurring_user.id,
event.id,
:going,
recurring: true,
)
DiscoursePostEvent::Invitee.create_attendance!(going_once_user.id, event.id, :going)
upcoming_events.visit("/discuss")
upcoming_events.click_event_on("2026-05-14")
expect(post_event_page).to have_going_count(2)
expect(post_event_page).to have_invitee_avatar(going_recurring_user.username)
expect(post_event_page).to have_invitee_avatar(going_once_user.username)
expect(post_event_page).to have_title_link_href(post.relative_url)
post_event_page.close_popup
upcoming_events.click_event_on("2026-05-21")
expect(post_event_page).to have_going_count(1)
expect(post_event_page).to have_invitee_avatar(going_recurring_user.username)
expect(post_event_page).to have_no_invitee_avatar(going_once_user.username)
end
end
describe "recurring events on the My events calendar" do
it "shows a single occurrence when the user RSVPed to only the next event",
time: Time.utc(2026, 6, 11, 12, 0) do
post =
create_post(
user: admin,
category:,
title: "Daily team sync",
raw:
"[event status='public' start='2026-06-11 18:00' end='2026-06-11 18:30' recurrence='every_day']\n[/event]",
)
event = DiscoursePostEvent::Event.find(post.id)
DiscoursePostEvent::Invitee.create_attendance!(admin.id, event.id, :going, recurring: false)
visit("/upcoming-events/mine/month/2026/6/1")
expect(upcoming_events).to have_calendar
expect(upcoming_events).to have_event(post.topic.title)
expect(upcoming_events).to have_event_count(1)
end
end
describe "event description in popup" do
let(:post_event_page) { PageObjects::Pages::DiscourseCalendar::PostEvent.new }
it "shows clamped description with expand toggle", time: Time.utc(2025, 9, 10, 12, 0) do
long_description =
"Join us for a <b>great</b> event :tada: with **bold text** and more details! " * 10
create_post(
user: admin,
category: Fabricate(:category),
title: "Event with long description",
raw:
"[event start=\"2025-09-11 08:05\" end=\"2025-09-11 10:05\"]\n#{long_description}\n[/event]",
)
upcoming_events.visit
upcoming_events.open_year_view
find("a", text: "Event with long description").click
expect(post_event_page).to have_description_clamped
expect(post_event_page).to have_description("Join us for a great event")
expect(post_event_page).to have_description("with bold text and more details!")
expect(post_event_page).to have_description_toggle
post_event_page.click_description_toggle
expect(post_event_page).to have_description_expanded
end
it "shows description without toggle for short text", time: Time.utc(2025, 9, 10, 12, 0) do
create_post(
user: admin,
category: Fabricate(:category),
title: "Event with short description",
raw: "[event start=\"2025-09-11 08:05\" end=\"2025-09-11 10:05\"]\nBrief sync.\n[/event]",
)
upcoming_events.visit
upcoming_events.open_year_view
find("a", text: "Event with short description").click
expect(post_event_page).to have_description("Brief sync.")
expect(post_event_page).to have_no_description_toggle
end
end
describe "event display and formatting" do
before { admin.user_option.update!(timezone: "America/New_York") }
describe "non-recurring events" do
describe "with local time enabled" do
it "displays event time in event timezone with (Local time) suffix",
timezone: "Australia/Brisbane",
time: Time.utc(2025, 9, 10, 12, 0) do
create_post(
user: admin,
category: Fabricate(:category),
title: "Local time event",
raw:
"[event showLocalTime=true timezone=CET start=\"2025-09-11 08:05\" end=\"2025-09-11 10:05\"]\n[/event]",
)
upcoming_events.visit
upcoming_events.open_year_view
expect(upcoming_events).to have_event_with_time(
"Local time event (Local time)",
"8:05am - 10:05am",
)
find("a", text: "Local time event (Local time)").click
expect(page).to have_css(
".event__section.event-dates",
text: "Thu, Sep 11 8:05 AM (CET) → 10:05 AM (CET)",
)
end
end
describe "without local time" do
it "displays event time converted to user timezone",
timezone: "Australia/Brisbane",
time: Time.utc(2025, 9, 10, 12, 0) do
create_post(
user: admin,
category: Fabricate(:category),
title: "Event with CET time",
raw:
"[event timezone=CET start=\"2025-09-11 19:00\" end=\"2025-09-11 21:00\"]\n[/event]",
)
upcoming_events.visit
upcoming_events.open_year_view
expect(upcoming_events).to have_event_with_time("Event with CET time", "1:00pm - 3:00pm")
find("a", text: "Event with CET time").click
expect(page).to have_css(
".event__section.event-dates",
text: "Tomorrow 1:00 PM → 3:00 PM",
)
end
end
describe "with same timezone as user" do
it "displays event time normally when event timezone matches user timezone",
time: Time.utc(2025, 9, 10, 12, 0) do
create_post(
user: admin,
category: Fabricate(:category),
title: "Same timezone event",
raw:
"[event showLocalTime=true timezone=\"America/New_York\" start=\"2025-09-12 08:05\" end=\"2025-09-12 09:05\"]\n[/event]",
)
upcoming_events.visit
upcoming_events.open_year_view
expect(upcoming_events).to have_event_with_time("Same timezone event", "8:05am - 9:05am")
find("a", text: "Same timezone event").click
expect(page).to have_css(
".event__section.event-dates",
text: "Fri, Sep 12 8:05 AM → 9:05 AM",
)
end
end
end
describe "recurring events" do
describe "with local time enabled" do
it "displays multiple occurrences with correct local time",
timezone: "Australia/Brisbane",
time: Time.utc(2025, 9, 10, 12, 0) do
create_post(
user: admin,
category: Fabricate(:category),
title: "Recurring local event",
raw:
"[event recurrence=every_week showLocalTime=true timezone=CET start=\"2025-09-11 08:05\" end=\"2025-09-11 10:05\"]\n[/event]",
)
upcoming_events.visit
upcoming_events.open_year_view
expect(page).to have_css(
"tr.fc-list-event:nth-child(2) .fc-list-event-time",
text: "8:05am - 10:05am",
)
expect(page).to have_css(
"tr.fc-list-event:nth-child(4) .fc-list-event-time",
text: "8:05am - 10:05am",
)
find("tr.fc-list-event:nth-child(2) .fc-list-event-title a").click
expect(page).to have_css(
".event__section.event-dates",
text: "Thu, Sep 11 8:05 AM (CET) → 10:05 AM (CET)",
)
page.send_keys(:escape)
find("tr.fc-list-event:nth-child(4) .fc-list-event-title a").click
expect(page).to have_css(
".event__section.event-dates",
text: "Thu, Sep 18 8:05 AM (CET) → 10:05 AM (CET)",
)
end
end
describe "without local time" do
it "displays multiple occurrences converted to user timezone",
timezone: "Australia/Brisbane",
time: Time.utc(2025, 9, 10, 12, 0) do
create_post(
user: admin,
category: Fabricate(:category),
title: "Recurring CET event",
raw:
"[event recurrence=every_week timezone=CET start=\"2025-09-11 19:00\" end=\"2025-09-11 20:00\"]\n[/event]",
)
upcoming_events.visit
upcoming_events.open_year_view
expect(page).to have_css(
"tr.fc-list-event:nth-child(2) .fc-list-event-time",
text: "1:00pm - 2:00pm",
)
expect(page).to have_css(
"tr.fc-list-event:nth-child(4) .fc-list-event-time",
text: "1:00pm - 2:00pm",
)
find(
"tr.fc-list-event:nth-child(2) .fc-list-event-title a",
text: "Recurring CET event",
).click
expect(page).to have_css(
".event__section.event-dates",
text: "Tomorrow 1:00 PM → 2:00 PM (New York)",
)
page.send_keys(:escape)
find(
"tr.fc-list-event:nth-child(4) .fc-list-event-title a",
text: "Recurring CET event",
).click
expect(page).to have_css(
".event__section.event-dates",
text: "Thu, Sep 18 1:00 PM → 2:00 PM",
)
end
end
describe "with same timezone as user" do
it "displays multiple occurrences without timezone conversion issues",
timezone: "Australia/Brisbane",
time: Time.utc(2025, 9, 10, 12, 0) do
create_post(
user: admin,
category: Fabricate(:category),
title: "Recurring same TZ event",
raw:
"[event recurrence=every_week showLocalTime=true timezone=\"America/New_York\" start=\"2025-09-12 08:05\" end=\"2025-09-12 10:05\"]\n[/event]",
)
upcoming_events.visit
upcoming_events.open_year_view
expect(page).to have_css(
"tr.fc-list-event:nth-child(2) .fc-list-event-time",
text: "8:05am - 10:05am",
)
expect(page).to have_css(
"tr.fc-list-event:nth-child(4) .fc-list-event-time",
text: "8:05am - 10:05am",
)
find("tr.fc-list-event:nth-child(2) .fc-list-event-title a").click
expect(page).to have_css(
".event__section.event-dates",
text: "Fri, Sep 12 8:05 AM → 10:05 AM (New York)",
)
page.send_keys(:escape)
find("tr.fc-list-event:nth-child(4) .fc-list-event-title a").click
expect(page).to have_css(
".event__section.event-dates",
text: "Fri, Sep 19 8:05 AM → 10:05 AM",
)
end
end
end
describe "recurring events" do
it "displays recurring events until the specified end date",
time: Time.utc(2025, 6, 2, 19, 00) do
post =
create_post(
user: admin,
category: Fabricate(:category),
title: "A recurring post event",
raw:
"[event recurrenceUntil=\"#{30.days.from_now.iso8601}\" timezone=\"Australia\/Brisbane\" recurrence=\"every_week\" status=\"public\" start=\"2025-06-11 08:05\"]\n[/event]",
)
upcoming_events.visit
upcoming_events.expect_event_count(4)
upcoming_events.expect_event_at_position(post.topic.title, row: 3, col: 2)
upcoming_events.expect_event_at_position(post.topic.title, row: 4, col: 2)
upcoming_events.expect_event_at_position(post.topic.title, row: 5, col: 2)
upcoming_events.expect_event_at_position(post.topic.title, row: 6, col: 2)
end
end
end
describe "event filtering" do
it "loads the correct range of dates", time: Time.utc(2025, 9, 1, 12, 0) do
create_post(
user: admin,
category: Fabricate(:category),
title: "going to the zoo",
raw:
"[event start=\"2025-09-07 18:30\" status=\"public\" timezone=\"Europe/Prague\" end=\"2025-09-07 21:00\"]\n[/event]",
)
visit("/upcoming-events/week/2025/9/1")
upcoming_events.expect_event_visible("zoo")
end
it "shows only events the user is attending when filtered",
time: Time.utc(2025, 6, 2, 19, 00) do
attending_event =
create_post(
user: admin,
category: Fabricate(:category),
title: "Attending post event",
raw: "[event status=\"public\" start=\"2025-06-11 08:05\"]\n[/event]",
)
create_post(
user: admin,
category: Fabricate(:category),
title: "Non attending post event",
raw: "[event start=\"2025-06-12 08:05\"]\n[/event]",
)
DiscoursePostEvent::Event.find(attending_event.id).create_invitees(
[{ user_id: admin.id, status: 0 }],
)
upcoming_events.visit
upcoming_events.expect_event_visible("Attending post event")
upcoming_events.expect_event_visible("Non attending post event")
upcoming_events.open_mine_events
upcoming_events.expect_event_visible("Attending post event")
upcoming_events.expect_event_not_visible("Non attending post event")
end
end
describe "calendar navigation and views" do
describe "navigation buttons" do
describe "today button" do
it "navigates to current date", time: Time.utc(2025, 6, 2, 19, 00) do
visit("/upcoming-events/month/2025/8/1")
upcoming_events.today
upcoming_events.expect_to_be_on_path("/upcoming-events/month/2025/6/1")
end
context "in different timezone", timezone: "Europe/London" do
it "navigates to current date in day view", time: Time.utc(2025, 6, 2, 19, 00) do
visit("/upcoming-events/day/2025/8/1")
upcoming_events.today
upcoming_events.expect_to_be_on_path("/upcoming-events/day/2025/6/2")
end
end
end
describe "next button" do
it "navigates to next month" do
visit("/upcoming-events/month/2025/8/1")
upcoming_events.next
expect(page).to have_content("September 2025")
upcoming_events.expect_to_be_on_path("/upcoming-events/month/2025/9/1")
end
it "navigates to next week" do
visit("/upcoming-events/week/2025/8/4")
upcoming_events.next
expect(page).to have_content("Aug 11 17, 2025")
upcoming_events.expect_to_be_on_path("/upcoming-events/week/2025/8/11")
end
context "in different timezone", timezone: "Europe/London" do
it "navigates to next day" do
visit("/upcoming-events/day/2025/8/4")
upcoming_events.next
expect(page).to have_content("August 5, 2025")
upcoming_events.expect_to_be_on_path("/upcoming-events/day/2025/8/5")
end
it "navigates to next week" do
visit("/upcoming-events/week/2025/8/4")
upcoming_events.next
expect(page).to have_content("Aug 11 17, 2025")
upcoming_events.expect_to_be_on_path("/upcoming-events/week/2025/8/11")
end
end
end
describe "prev button" do
it "navigates to previous day" do
visit("/upcoming-events/day/2025/8/1")
upcoming_events.prev
expect(page).to have_content("July 31, 2025")
upcoming_events.expect_to_be_on_path("/upcoming-events/day/2025/7/31")
end
it "navigates to previous month" do
visit("/upcoming-events/month/2025/8/1")
upcoming_events.prev
expect(page).to have_content("July 2025")
upcoming_events.expect_to_be_on_path("/upcoming-events/month/2025/7/1")
end
it "navigates to previous week" do
visit("/upcoming-events/week/2025/8/4")
upcoming_events.prev
expect(page).to have_content("Jul 28 Aug 3, 2025")
upcoming_events.expect_to_be_on_path("/upcoming-events/week/2025/7/28")
end
context "in different timezone", timezone: "Europe/London" do
it "navigates to previous day" do
visit("/upcoming-events/day/2025/8/1")
upcoming_events.prev
expect(page).to have_content("July 31, 2025")
upcoming_events.expect_to_be_on_path("/upcoming-events/day/2025/7/31")
end
it "navigates to previous month" do
visit("/upcoming-events/month/2025/8/1")
upcoming_events.prev
expect(page).to have_content("July 2025")
upcoming_events.expect_to_be_on_path("/upcoming-events/month/2025/7/1")
end
it "navigates to previous week" do
visit("/upcoming-events/week/2025/8/4")
upcoming_events.prev
expect(page).to have_content("Jul 28 Aug 3, 2025")
upcoming_events.expect_to_be_on_path("/upcoming-events/week/2025/7/28")
end
end
end
describe "event duration display" do
context "with recurring event" do
it "displays longer events with appropriate visual height in time grid view",
time: Time.utc(2025, 6, 2, 19, 00) do
create_post(
user: admin,
category: Fabricate(:category),
title: "This is a short meeting",
raw:
"[event recurrence=\"every_week\" start=\"2025-06-03 10:00\" end=\"2025-06-03 11:00\"]\n[/event]",
)
create_post(
user: admin,
category: Fabricate(:category),
title: "This is a long workshop",
raw:
"[event recurrence=\"every_week\" start=\"2025-06-03 14:00\" end=\"2025-06-03 17:00\"]\n[/event]",
)
upcoming_events.visit
visit("/upcoming-events/week/2025/6/2")
expect(upcoming_events).to have_event_height("This is a short meeting", 47)
expect(upcoming_events).to have_event_height("This is a long workshop", 143)
end
end
context "with non recurring event" do
it "displays longer events with appropriate visual height in time grid view",
time: Time.utc(2025, 6, 2, 19, 00) do
create_post(
user: admin,
category: Fabricate(:category),
title: "This is a short meeting",
raw: "[event start=\"2025-06-03 10:00\" end=\"2025-06-03 11:00\"]\n[/event]",
)
create_post(
user: admin,
category: Fabricate(:category),
title: "This is a long workshop",
raw: "[event start=\"2025-06-03 14:00\" end=\"2025-06-03 17:00\"]\n[/event]",
)
upcoming_events.visit
visit("/upcoming-events/week/2025/6/2")
expect(upcoming_events).to have_event_height("This is a short meeting", 47)
expect(upcoming_events).to have_event_height("This is a long workshop", 143)
end
end
end
end
describe "configurable view" do
before { SiteSetting.calendar_upcoming_events_default_view = "day" }
it "default view is controlled by calendar_upcoming_events_default_view setting",
time: Time.utc(2025, 9, 15) do
upcoming_events.visit
upcoming_events.expect_content("September 15, 2025")
upcoming_events.expect_to_be_on_path("/upcoming-events/day/2025/9/15")
end
end
describe "view switching" do
it "switching from month to week view keeps the same day" do
visit("/upcoming-events/month/2025/9/16")
upcoming_events.open_week_view
upcoming_events.expect_content("Sep 15 21, 2025")
upcoming_events.expect_to_be_on_path("/upcoming-events/week/2025/9/15")
end
end
end
describe "with calendar_event_display setting", time: Time.utc(2025, 6, 2, 19, 00) do
before do
create_post(
user: admin,
category: Fabricate(:category),
title: "This is a short meeting",
raw:
"[event recurrence=\"every_week\" start=\"2025-06-03 10:00\" end=\"2025-06-03 11:00\"]\n[/event]",
)
end
context "with block" do
before { SiteSetting.calendar_event_display = "block" }
it "renders block" do
visit("/upcoming-events/month/2025/9/16")
expect(page).to have_selector(".fc-daygrid-block-event")
end
end
context "with auto" do
before { SiteSetting.calendar_event_display = "auto" }
it "renders dot" do
visit("/upcoming-events/month/2025/9/16")
expect(page).to have_selector(".fc-daygrid-dot-event")
end
end
end
context "with tag color", time: Time.utc(2025, 6, 2, 19, 00) do
before do
SiteSetting.map_events_to_color = [
{ type: "tag", color: "rgb(231, 76, 60)", slug: "awesome-tag" },
].to_json
create_post(
user: admin,
category: Fabricate(:category),
topic: Fabricate(:topic, tags: [Fabricate(:tag, name: "awesome-tag")]),
title: "This is a short meeting",
raw: "[event start=\"2025-06-03 10:00\" end=\"2025-06-03 11:00\"]\n[/event]",
)
end
it "display the event with the correct color" do
visit("/upcoming-events/month/2025/6/16")
expect(get_rgb_color(find(".fc-daygrid-event-dot"), "borderColor")).to eq("rgb(231, 76, 60)")
end
end
context "with category color", time: Time.utc(2025, 6, 2, 19, 00) do
before do
SiteSetting.map_events_to_color = [
{ type: "category", color: "rgb(231, 76, 60)", slug: "awesome-category" },
].to_json
create_post(
user: admin,
category: Fabricate(:category, slug: "awesome-category"),
title: "This is a short meeting",
raw: "[event start=\"2025-06-03 10:00\" end=\"2025-06-03 11:00\"]\n[/event]",
)
end
it "display the event with the correct color" do
visit("/upcoming-events/month/2025/6/16")
expect(get_rgb_color(find(".fc-daygrid-event-dot"), "borderColor")).to eq("rgb(231, 76, 60)")
end
end
end