diff --git a/e2e/faircalendar.spec.js b/e2e/faircalendar.spec.js
new file mode 100644
index 00000000..a02302a4
--- /dev/null
+++ b/e2e/faircalendar.spec.js
@@ -0,0 +1,59 @@
+// @ts-check
+import { test, expect } from '@playwright/test';
+
+test('auth required', async ({ page }) => {
+ await page.goto('/app/faircalendar');
+ await page.waitForURL(url => url.pathname.startsWith('/login'));
+});
+
+test.describe('authenticated', () => {
+ test.use({ storageState: 'playwright/.auth/johnDoe.json' });
+
+ test('basic content', async ({ page }) => {
+ await page.goto('/app/faircalendar?year=2023&month=10'); // month=0..11
+ await expect(page).toHaveTitle('FairCalendar novembre 2023 - Permacoop');
+
+ const calendar = page.getByTestId('pc-event-calendar');
+ await expect(calendar).toBeVisible();
+
+ const toussaint = calendar.getByText('7h - Jour férié');
+ await expect(toussaint).toBeVisible();
+ });
+
+ test('go to add event', async ({ page }) => {
+ await page.goto('/app/faircalendar?year=2023&month=10');
+
+ const calendar = page.getByTestId('pc-event-calendar');
+
+ await calendar
+ .locator('.ec-body .ec-day')
+ .nth(8)
+ .click();
+
+ await page.waitForURL(
+ '/app/faircalendar/events/add/2023-11-09--2023-11-09'
+ );
+ });
+
+ test('back button behavior', async ({ page }) => {
+ await page.goto('/app/faircalendar?year=2023&month=10');
+
+ const calendar = page.getByTestId('pc-event-calendar');
+
+ // Ensure rendered calendar is present
+ expect(await calendar.locator('.ec').count()).toBe(1);
+
+ await page.goto('/app');
+ await page.goBack();
+
+ // Regression test: ensure calendar is rendered without duplication duplicated, and is interactive
+ expect(await calendar.locator('.ec').count()).toBe(1);
+ await calendar
+ .locator('.ec-body .ec-day')
+ .nth(8)
+ .click();
+ await page.waitForURL(
+ '/app/faircalendar/events/add/2023-11-09--2023-11-09'
+ );
+ });
+});
diff --git a/src/assets/customElements/eventCalendar.js b/src/assets/customElements/eventCalendar.js
index 77cad3b1..5fc1684b 100644
--- a/src/assets/customElements/eventCalendar.js
+++ b/src/assets/customElements/eventCalendar.js
@@ -1,24 +1,53 @@
+// @ts-check
+import * as Turbo from '@hotwired/turbo';
import Calendar from '@event-calendar/core';
import DayGrid from '@event-calendar/day-grid';
import Interaction from '@event-calendar/interaction';
import { format, subDays } from 'date-fns';
export default class extends HTMLElement {
+ /** @type {Calendar} */
+ #ec;
+
+ /** @type{string} */
+ #addUrlTemplate;
+
connectedCallback() {
- this.classList.add('pc-eventcalendar');
+ this.dataset.testid = 'pc-event-calendar';
- const events = JSON.parse(this.dataset.eventsJson);
+ const events = JSON.parse(this.dataset.eventsJson || '{}');
const addUrlTemplate = this.dataset.addUrlTemplate;
+ const date = this.dataset.date || new Date('now').toISOString();
+
+ if (!addUrlTemplate) {
+ throw new Error('data-add-url-template is missing');
+ }
+
+ this.#addUrlTemplate = addUrlTemplate;
+ this.#ec = this.#createCalendar(date, events);
+
+ document.addEventListener('turbo:before-cache', this.#onTurboBeforeCache);
+ }
- const goToEventCreate = (startDate, endDate) => {
- const url = addUrlTemplate
- .replace(':startDate', format(startDate, 'yyyy-MM-dd'))
- .replace(':endDate', format(endDate, 'yyyy-MM-dd'));
+ /**
+ * @param {Date} startDate
+ * @param {Date} endDate
+ */
+ #goToEventCreate = (startDate, endDate) => {
+ const url = this.#addUrlTemplate
+ .replace(':startDate', format(startDate, 'yyyy-MM-dd'))
+ .replace(':endDate', format(endDate, 'yyyy-MM-dd'));
- window.location = url;
- };
+ Turbo.visit(url);
+ };
- const ec = new Calendar({
+ /**
+ * @param {string} date
+ * @param {any[]} events
+ * @returns {Calendar}
+ */
+ #createCalendar = (date, events) => {
+ return new Calendar({
target: this,
props: {
plugins: [DayGrid, Interaction],
@@ -30,30 +59,42 @@ export default class extends HTMLElement {
6, // Saturday
0 // Sunday
],
- date: this.dataset.date,
+ date,
headerToolbar: { start: '', center: '', end: '' },
dayMaxEvents: true,
eventStartEditable: false,
eventDurationEditable: false,
+ // TODO: add testid on days
eventContent: ({ event }) => {
const url = event.extendedProps.url;
if (url) {
- return { html: `${event.title}` };
+ return {
+ html: `${event.title}`
+ };
}
return event.title;
},
- dateClick: info => goToEventCreate(info.date, info.date),
+ dateClick: ({ event, date }) => {
+ this.#goToEventCreate(date, date);
+ },
selectable: true,
selectBackgroundColor: 'var(--background-action-violet)',
- select: info => {
- // By default, range will stay selected if navigating using the
- // back button.
- ec.unselect();
-
- goToEventCreate(info.start, subDays(info.end, 1));
+ select: ({ start, end }) => {
+ this.#goToEventCreate(start, subDays(end, 1));
}
}
}
});
+ };
+
+ #onTurboBeforeCache = () => {
+ this.#ec.destroy();
+ };
+
+ disconnectedCallback() {
+ document.removeEventListener(
+ 'turbo:before-cache',
+ this.#onTurboBeforeCache
+ );
}
}
diff --git a/src/assets/styles/faircalendar.css b/src/assets/styles/faircalendar.css
index 3354cf3b..b0b2d77a 100644
--- a/src/assets/styles/faircalendar.css
+++ b/src/assets/styles/faircalendar.css
@@ -2,7 +2,7 @@
/* EventCalendar customizations */
-.pc-eventcalendar {
+pc-eventcalendar {
display: block;
}
@@ -14,7 +14,7 @@
background-color: var(--event-today);
}
-.pc-eventcalendar .ec-day-grid .ec-uniform .ec-day {
+pc-eventcalendar .ec-day-grid .ec-uniform .ec-day {
/* Fix: by default EventCalendar sets the min-height of day boxes to 0 when dayMaxEvents is true. */
min-height: 5rem;
}
diff --git a/src/templates/pages/faircalendar/index.njk b/src/templates/pages/faircalendar/index.njk
index 16bbbac2..0c46cb7a 100644
--- a/src/templates/pages/faircalendar/index.njk
+++ b/src/templates/pages/faircalendar/index.njk
@@ -32,7 +32,8 @@
data-events-json="{{ fullCalendarEvents|dump }}"
data-add-url-template="{{ path('faircalendar_events_add', { startDate: ':startDate', endDate: ':endDate' }) }}"
data-date="{{ date|htmlDate }}"
- >
+ >
+