diff --git a/packages/x-scheduler-headless/src/event-calendar-selectors/eventCalendarEventSelectors.test.ts b/packages/x-scheduler-headless/src/event-calendar-selectors/eventCalendarEventSelectors.test.ts new file mode 100644 index 0000000000000..43269ff5c836e --- /dev/null +++ b/packages/x-scheduler-headless/src/event-calendar-selectors/eventCalendarEventSelectors.test.ts @@ -0,0 +1,180 @@ +import { EventBuilder, getEventCalendarStateFromParameters } from 'test/utils/scheduler'; +import { eventCalendarEventSelectors } from './eventCalendarEventSelectors'; + +const defaultEvent = EventBuilder.new().id('event-1').build(); +const readOnlyEvent = EventBuilder.new().id('event-1').readOnly(true).build(); + +describe('eventCalendarEventSelectors', () => { + describe('isDraggable', () => { + it('should return false when areEventsDraggable is not defined', () => { + const state = getEventCalendarStateFromParameters({ + events: [defaultEvent], + areEventsDraggable: undefined, + }); + expect(eventCalendarEventSelectors.isDraggable(state, 'event-1')).to.equal(false); + }); + + it('should return false when areEventsDraggable is false', () => { + const state = getEventCalendarStateFromParameters({ + events: [defaultEvent], + areEventsDraggable: false, + }); + expect(eventCalendarEventSelectors.isDraggable(state, 'event-1')).to.equal(false); + }); + + it('should return false when the event is read-only', () => { + const state = getEventCalendarStateFromParameters({ + events: [readOnlyEvent], + areEventsDraggable: true, + }); + expect(eventCalendarEventSelectors.isDraggable(state, 'event-1')).to.equal(false); + }); + + it('should return false when the calendar is read-only', () => { + const state = getEventCalendarStateFromParameters({ + events: [defaultEvent], + areEventsDraggable: true, + readOnly: true, + }); + expect(eventCalendarEventSelectors.isDraggable(state, 'event-1')).to.equal(false); + }); + + it('should return false when the event start property is read-only', () => { + const state = getEventCalendarStateFromParameters({ + events: [defaultEvent], + areEventsDraggable: true, + eventModelStructure: { + start: { getter: (event) => event.start }, + }, + }); + expect(eventCalendarEventSelectors.isDraggable(state, 'event-1')).to.equal(false); + }); + + it('should return false when the event end property is read-only', () => { + const state = getEventCalendarStateFromParameters({ + events: [defaultEvent], + areEventsDraggable: true, + eventModelStructure: { + end: { getter: (event) => event.end }, + }, + }); + expect(eventCalendarEventSelectors.isDraggable(state, 'event-1')).to.equal(false); + }); + + it('should return true when areEventsDraggable is true and the event is not read-only', () => { + const state = getEventCalendarStateFromParameters({ + events: [defaultEvent], + areEventsDraggable: true, + }); + expect(eventCalendarEventSelectors.isDraggable(state, 'event-1')).to.equal(true); + }); + }); + + describe('isResizable', () => { + it('should return false when areEventsResizable is not defined', () => { + const state = getEventCalendarStateFromParameters({ + events: [defaultEvent], + view: 'week', + areEventsResizable: undefined, + }); + expect(eventCalendarEventSelectors.isResizable(state, 'event-1', 'day-grid')).to.equal(false); + }); + + it('should return false when areEventsResizable is false', () => { + const state = getEventCalendarStateFromParameters({ + events: [defaultEvent], + view: 'week', + areEventsResizable: false, + }); + expect(eventCalendarEventSelectors.isResizable(state, 'event-1', 'day-grid')).to.equal(false); + }); + + it('should return false when the event is read-only', () => { + const state = getEventCalendarStateFromParameters({ + events: [readOnlyEvent], + view: 'week', + areEventsResizable: true, + }); + expect(eventCalendarEventSelectors.isResizable(state, 'event-1', 'day-grid')).to.equal(false); + }); + + it('should return false when the calendar is read-only', () => { + const state = getEventCalendarStateFromParameters({ + events: [defaultEvent], + view: 'week', + areEventsResizable: true, + readOnly: true, + }); + expect(eventCalendarEventSelectors.isResizable(state, 'event-1', 'day-grid')).to.equal(false); + }); + + it('should return false when the event start property is read-only', () => { + const state = getEventCalendarStateFromParameters({ + events: [defaultEvent], + view: 'week', + areEventsResizable: true, + eventModelStructure: { + start: { getter: (event) => event.start }, + }, + }); + expect(eventCalendarEventSelectors.isResizable(state, 'event-1', 'day-grid')).to.equal(false); + }); + + it('should return false when the event end property is read-only', () => { + const state = getEventCalendarStateFromParameters({ + events: [defaultEvent], + view: 'week', + areEventsResizable: true, + eventModelStructure: { + end: { getter: (event) => event.end }, + }, + }); + expect(eventCalendarEventSelectors.isResizable(state, 'event-1', 'day-grid')).to.equal(false); + }); + + it('should return true when areEventsResizable is true and the event is not read-only', () => { + const state = getEventCalendarStateFromParameters({ + events: [defaultEvent], + view: 'week', + areEventsResizable: true, + }); + expect(eventCalendarEventSelectors.isResizable(state, 'event-1', 'day-grid')).to.equal(true); + }); + + it('should return false in day view on day-grid surface', () => { + const state = getEventCalendarStateFromParameters({ + events: [defaultEvent], + view: 'day', + areEventsResizable: true, + }); + expect(eventCalendarEventSelectors.isResizable(state, 'event-1', 'day-grid')).to.equal(false); + }); + + it('should return true in day view on time-grid surface', () => { + const state = getEventCalendarStateFromParameters({ + events: [defaultEvent], + view: 'day', + areEventsResizable: true, + }); + expect(eventCalendarEventSelectors.isResizable(state, 'event-1', 'time-grid')).to.equal(true); + }); + + it('should return false in month view on day-grid surface for single-day events', () => { + const state = getEventCalendarStateFromParameters({ + events: [defaultEvent], + view: 'month', + areEventsResizable: true, + }); + expect(eventCalendarEventSelectors.isResizable(state, 'event-1', 'day-grid')).to.equal(false); + }); + + it('should return true in month view on day-grid surface for multi-day events', () => { + const state = getEventCalendarStateFromParameters({ + events: [{ ...defaultEvent, allDay: true }], + view: 'month', + areEventsResizable: true, + }); + expect(eventCalendarEventSelectors.isResizable(state, 'event-1', 'day-grid')).to.equal(true); + }); + }); +}); diff --git a/packages/x-scheduler-headless/src/event-calendar-selectors/eventCalendarEventSelectors.ts b/packages/x-scheduler-headless/src/event-calendar-selectors/eventCalendarEventSelectors.ts index 723d0a893d245..65c1dd0a467a1 100644 --- a/packages/x-scheduler-headless/src/event-calendar-selectors/eventCalendarEventSelectors.ts +++ b/packages/x-scheduler-headless/src/event-calendar-selectors/eventCalendarEventSelectors.ts @@ -26,7 +26,7 @@ export const eventCalendarEventSelectors = { ), isResizable: createSelector( (state: State, eventId: CalendarEventId, surfaceType: EventSurfaceType) => { - if (schedulerEventSelectors.isReadOnly(state, eventId) || !state.areEventsDraggable) { + if (schedulerEventSelectors.isReadOnly(state, eventId) || !state.areEventsResizable) { return false; } diff --git a/packages/x-scheduler-headless/src/event-calendar-selectors/eventCalendarSelectors.test.ts b/packages/x-scheduler-headless/src/event-calendar-selectors/eventCalendarOccurrencePlaceholderSelectors.test.ts similarity index 81% rename from packages/x-scheduler-headless/src/event-calendar-selectors/eventCalendarSelectors.test.ts rename to packages/x-scheduler-headless/src/event-calendar-selectors/eventCalendarOccurrencePlaceholderSelectors.test.ts index 858d4378d1262..6cdfc618de9a5 100644 --- a/packages/x-scheduler-headless/src/event-calendar-selectors/eventCalendarSelectors.test.ts +++ b/packages/x-scheduler-headless/src/event-calendar-selectors/eventCalendarOccurrencePlaceholderSelectors.test.ts @@ -1,42 +1,29 @@ -import { adapter } from 'test/utils/scheduler'; +import { adapter, DEFAULT_EVENT_CALENDAR_STATE } from 'test/utils/scheduler'; import { eventCalendarOccurrencePlaceholderSelectors } from './eventCalendarOccurrencePlaceholderSelectors'; -import { EventCalendarState as State } from '../use-event-calendar/EventCalendarStore.types'; +import { EventCalendarState } from '../use-event-calendar'; -describe('EventCalendarStore.selectors', () => { - const baseState = (overrides: Partial = {}) => - ({ - adapter, - occurrencePlaceholder: null, - ...overrides, - }) as State; - - describe('isEventDraggable', () => { - // TODO - }); - - describe('isEventResizable', () => { - // TODO - }); - describe('occurrencePlaceholderToRenderInDayCell', () => { +describe('eventCalendarOccurrencePlaceholderSelectors', () => { + describe('placeholderInDayCell', () => { // TODO }); - describe('occurrencePlaceholderToRenderInTimeRange', () => { + describe('placeholderInTimeRange', () => { // TODO }); - describe('isCreatingNewEventInDayGridCell', () => { + describe('isCreatingInDayCell', () => { const day = adapter.date('2024-01-15'); it('should return false when there is no placeholder', () => { - const state = baseState(); + const state = DEFAULT_EVENT_CALENDAR_STATE; expect(eventCalendarOccurrencePlaceholderSelectors.isCreatingInDayCell(state, day)).to.equal( false, ); }); it('should return false when surfaceType is not "day-grid"', () => { - const state = baseState({ + const state: EventCalendarState = { + ...DEFAULT_EVENT_CALENDAR_STATE, occurrencePlaceholder: { type: 'creation', surfaceType: 'time-grid', @@ -44,14 +31,15 @@ describe('EventCalendarStore.selectors', () => { end: adapter.endOfDay(day), resourceId: null, }, - }); + }; expect(eventCalendarOccurrencePlaceholderSelectors.isCreatingInDayCell(state, day)).to.equal( false, ); }); it('should return false when the placeholder type is not "creation"', () => { - const state = baseState({ + const state: EventCalendarState = { + ...DEFAULT_EVENT_CALENDAR_STATE, occurrencePlaceholder: { type: 'internal-drag', eventId: 'event-id', @@ -68,14 +56,15 @@ describe('EventCalendarStore.selectors', () => { end: adapter.endOfDay(day), }, }, - }); + }; expect(eventCalendarOccurrencePlaceholderSelectors.isCreatingInDayCell(state, day)).to.equal( false, ); }); it('should return true when creating on the same day', () => { - const state = baseState({ + const state: EventCalendarState = { + ...DEFAULT_EVENT_CALENDAR_STATE, occurrencePlaceholder: { type: 'creation', surfaceType: 'day-grid', @@ -83,7 +72,7 @@ describe('EventCalendarStore.selectors', () => { end: adapter.endOfDay(day), resourceId: null, }, - }); + }; expect(eventCalendarOccurrencePlaceholderSelectors.isCreatingInDayCell(state, day)).to.equal( true, ); @@ -91,7 +80,8 @@ describe('EventCalendarStore.selectors', () => { it('should return false when day does not match placeholder.start day', () => { const otherDay = adapter.addDays(day, 1); - const state = baseState({ + const state: EventCalendarState = { + ...DEFAULT_EVENT_CALENDAR_STATE, occurrencePlaceholder: { type: 'creation', surfaceType: 'day-grid', @@ -99,27 +89,28 @@ describe('EventCalendarStore.selectors', () => { end: adapter.endOfDay(otherDay), resourceId: null, }, - }); + }; expect(eventCalendarOccurrencePlaceholderSelectors.isCreatingInDayCell(state, day)).to.equal( false, ); }); }); - describe('isCreatingNewEventInTimeRange', () => { + describe('isCreatingInTimeRange', () => { const day = adapter.date('2024-01-15'); const dayStart = adapter.startOfDay(day); const dayEnd = adapter.endOfDay(day); it('should return false when there is no placeholder', () => { - const state = baseState(); + const state = DEFAULT_EVENT_CALENDAR_STATE; expect( eventCalendarOccurrencePlaceholderSelectors.isCreatingInTimeRange(state, dayStart, dayEnd), ).to.equal(false); }); it('should return false when surfaceType is not "time-grid"', () => { - const state = baseState({ + const state: EventCalendarState = { + ...DEFAULT_EVENT_CALENDAR_STATE, occurrencePlaceholder: { type: 'creation', surfaceType: 'day-grid', @@ -127,14 +118,15 @@ describe('EventCalendarStore.selectors', () => { end: adapter.setHours(dayStart, 11), resourceId: null, }, - }); + }; expect( eventCalendarOccurrencePlaceholderSelectors.isCreatingInTimeRange(state, dayStart, dayEnd), ).to.equal(false); }); it('should return false when the type is not "creating"', () => { - const state = baseState({ + const state: EventCalendarState = { + ...DEFAULT_EVENT_CALENDAR_STATE, occurrencePlaceholder: { type: 'internal-drag', eventId: 'event-id', @@ -151,7 +143,7 @@ describe('EventCalendarStore.selectors', () => { end: adapter.endOfDay(day), }, }, - }); + }; expect( eventCalendarOccurrencePlaceholderSelectors.isCreatingInTimeRange(state, dayStart, dayEnd), ).to.equal(false); @@ -159,7 +151,8 @@ describe('EventCalendarStore.selectors', () => { it('should return false when placeholder.start is not the same day as dayStart', () => { const nextDay = adapter.addDays(day, 1); - const state = baseState({ + const state: EventCalendarState = { + ...DEFAULT_EVENT_CALENDAR_STATE, occurrencePlaceholder: { type: 'creation', surfaceType: 'time-grid', @@ -167,14 +160,15 @@ describe('EventCalendarStore.selectors', () => { end: adapter.setHours(adapter.startOfDay(nextDay), 10), resourceId: null, }, - }); + }; expect( eventCalendarOccurrencePlaceholderSelectors.isCreatingInTimeRange(state, dayStart, dayEnd), ).to.equal(false); }); it('should return true when placeholder overlaps [dayStart, dayEnd] strictly (start < dayEnd && end > dayStart)', () => { - const state = baseState({ + const state: EventCalendarState = { + ...DEFAULT_EVENT_CALENDAR_STATE, occurrencePlaceholder: { type: 'creation', surfaceType: 'time-grid', @@ -182,14 +176,15 @@ describe('EventCalendarStore.selectors', () => { end: adapter.setHours(dayStart, 11), // > dayStart resourceId: null, }, - }); + }; expect( eventCalendarOccurrencePlaceholderSelectors.isCreatingInTimeRange(state, dayStart, dayEnd), ).to.equal(true); }); it('should return false when start == dayEnd', () => { - const state = baseState({ + const state: EventCalendarState = { + ...DEFAULT_EVENT_CALENDAR_STATE, occurrencePlaceholder: { type: 'creation', surfaceType: 'time-grid', @@ -197,14 +192,15 @@ describe('EventCalendarStore.selectors', () => { end: adapter.addMinutes(dayEnd, 30), resourceId: null, }, - }); + }; expect( eventCalendarOccurrencePlaceholderSelectors.isCreatingInTimeRange(state, dayStart, dayEnd), ).to.equal(false); }); it('should return false when end == dayStart', () => { - const state = baseState({ + const state: EventCalendarState = { + ...DEFAULT_EVENT_CALENDAR_STATE, occurrencePlaceholder: { type: 'creation', surfaceType: 'time-grid', @@ -212,7 +208,7 @@ describe('EventCalendarStore.selectors', () => { end: dayStart, // end > dayStart is false resourceId: null, }, - }); + }; expect( eventCalendarOccurrencePlaceholderSelectors.isCreatingInTimeRange(state, dayStart, dayEnd), ).to.equal(false); diff --git a/test/utils/scheduler/index.ts b/test/utils/scheduler/index.ts index 10616f4baafdc..3c74a754a419a 100644 --- a/test/utils/scheduler/index.ts +++ b/test/utils/scheduler/index.ts @@ -4,4 +4,5 @@ export * from './adapters'; export * from './StateWatcher'; export * from './SchedulerStoreRunner'; export * from './StoreSpy'; +export * from './state'; export * from './event-builder'; diff --git a/test/utils/scheduler/state.ts b/test/utils/scheduler/state.ts new file mode 100644 index 0000000000000..e0013647557c5 --- /dev/null +++ b/test/utils/scheduler/state.ts @@ -0,0 +1,17 @@ +import { + EventCalendarParameters, + EventCalendarStore, +} from '@mui/x-scheduler-headless/use-event-calendar'; +import { adapter } from './adapters'; + +export const DEFAULT_EVENT_CALENDAR_STATE = new EventCalendarStore({ events: [] }, adapter).state; + +export const getEventCalendarStateFromParameters = < + TEvent extends object, + TResource extends object, +>( + parameters: EventCalendarParameters, +) => { + const state = new EventCalendarStore(parameters, adapter).state; + return state; +};