diff --git a/app/i18n/messages/en.json b/app/i18n/messages/en.json
index ab8d77a07..0d625501c 100644
--- a/app/i18n/messages/en.json
+++ b/app/i18n/messages/en.json
@@ -243,6 +243,7 @@
"NotFoundPage.searchPageLink": "search page",
"NotFoundPage.title": "404 Page not found",
"Notifications.cannotReserveDuringMaintenance": "Cannot make reservations during maintenance.",
+ "Notifications.continousFreeDaysError": "You can only select a continuous period of available days",
"Notifications.errorMessage": "Something went wrong. Please try again in a moment.",
"Notifications.loginErrorMessage": "Failed to login. Please try again in a moment.",
"Notifications.userFetchErrorMessage": "Failed to retrieve user info. Please try again in a moment.",
@@ -266,6 +267,7 @@
"Overnight.legend.ownSelection": "Own selection",
"Overnight.legend.reserved": "Reserved",
"Overnight.nextMonth": "Next month",
+ "Overnight.overMaxAlert": "The selected time is over the maximum duration",
"Overnight.prevMonth": "Previous month",
"Partners.kaupunginkirjatoImageAlt": "Helsinki City Library",
"Partners.nuorisoasiainkeskusImageAlt": "City of Helsinki – Youth Department",
diff --git a/app/i18n/messages/fi.json b/app/i18n/messages/fi.json
index 0b0e70420..f9d561657 100644
--- a/app/i18n/messages/fi.json
+++ b/app/i18n/messages/fi.json
@@ -243,6 +243,7 @@
"NotFoundPage.searchPageLink": "hakusivulta",
"NotFoundPage.title": "404 Sivua ei löydy",
"Notifications.cannotReserveDuringMaintenance": "Huoltotyön aikana ei voi tehdä varauksia.",
+ "Notifications.continousFreeDaysError": "Voit valita vain yhtenäisen jakson vapaita päiviä",
"Notifications.errorMessage": "Jotain meni vikaan. Yritä hetken päästä uudelleen.",
"Notifications.loginErrorMessage": "Kirjautuminen epäonnistui. Yritä hetken päästä uudelleen.",
"Notifications.userFetchErrorMessage": "Käyttäjätietojen hakeminen epäonnistui. Yritä hetken päästä uudelleen.",
@@ -266,6 +267,7 @@
"Overnight.legend.ownSelection": "Oma valinta",
"Overnight.legend.reserved": "Varattu",
"Overnight.nextMonth": "Seuraava kuukausi",
+ "Overnight.overMaxAlert": "Valittu aika ylittää maksimipituuden",
"Overnight.prevMonth": "Edellinen kuukausi",
"Partners.kaupunginkirjatoImageAlt": "Helsingin kaupunginkirjasto",
"Partners.nuorisoasiainkeskusImageAlt": "Helsingin kaupunki - nuorisoasiainkeskus",
diff --git a/app/i18n/messages/sv.json b/app/i18n/messages/sv.json
index 67a4736ed..9cad9fb73 100644
--- a/app/i18n/messages/sv.json
+++ b/app/i18n/messages/sv.json
@@ -245,6 +245,7 @@
"NotFoundPage.searchPageLink": "söksidan",
"NotFoundPage.title": "404 Webbplatsen hittades inte",
"Notifications.cannotReserveDuringMaintenance": "Det går inte att göra reservationer under underhåll.",
+ "Notifications.continousFreeDaysError": "Du kan bara välja en sammanhängande period av lediga dagar",
"Notifications.errorMessage": "Ett fel uppstod. Försök på nytt om en liten stund.",
"Notifications.loginErrorMessage": "Inloggning misslyckades. Försök igen om en liten stund.",
"Notifications.userFetchErrorMessage": "Användarinformations hämtning misslyckades. Försök igen om en liten stund.",
@@ -268,6 +269,7 @@
"Overnight.legend.ownSelection": "Egen val",
"Overnight.legend.reserved": "Reserverad",
"Overnight.nextMonth": "Nästa månad",
+ "Overnight.overMaxAlert": "Den valda tiden överskrider maximal varaktighet",
"Overnight.prevMonth": "Föregående månad",
"Partners.kaupunginkirjatoImageAlt": "Helsingfors stadsbibliotek",
"Partners.nuorisoasiainkeskusImageAlt": "Helsingfors stad - ungdomscentralen",
diff --git a/app/shared/overnight-calendar/OvernightCalendar.js b/app/shared/overnight-calendar/OvernightCalendar.js
index a6b5998dd..1ad281fdf 100644
--- a/app/shared/overnight-calendar/OvernightCalendar.js
+++ b/app/shared/overnight-calendar/OvernightCalendar.js
@@ -20,7 +20,9 @@ import {
handleDisableDays,
handleFormattingSelected,
isDurationBelowMin,
+ isDurationOverMax,
isReservingAllowed,
+ isSelectionContinous,
nextDayBookedModifier,
nextDayClosedModifier,
prevDayBookedModifier,
@@ -78,14 +80,23 @@ function OvernightCalendar({
const selectedDuration = getSelectedDuration(
startDate, endDate, overnightStartTime, overnightEndTime);
const isDurBelowMin = isDurationBelowMin(selectedDuration, minPeriod);
- const minDurationText = getPrettifiedPeriodUnits(minPeriod, t('common.unit.time.day.short'));
if (isDurBelowMin) {
+ const minDurationText = getPrettifiedPeriodUnits(minPeriod, t('common.unit.time.day.short'));
actions.addNotification({
message: `${t('Overnight.belowMinAlert')} (${minDurationText})`,
type: 'info',
timeOut: 10000,
});
}
+ const isDurOverMax = isDurationOverMax(selectedDuration, maxPeriod);
+ if (isDurOverMax) {
+ const maxDurationText = getPrettifiedPeriodUnits(maxPeriod, t('common.unit.time.day.short'));
+ actions.addNotification({
+ message: `${t('Overnight.overMaxAlert')} (${maxDurationText})`,
+ type: 'info',
+ timeOut: 10000,
+ });
+ }
}
}, [startDate, endDate]);
@@ -107,20 +118,16 @@ function OvernightCalendar({
});
const validateAndSelect = (day, { booked, nextBooked, nextClosed }) => {
- const isNextBlocked = !startDate && (nextBooked || nextClosed);
+ const isNextBlocked = (!startDate || (startDate && endDate)) && (nextBooked || nextClosed);
const isDateDisabled = handleDisableDays({
day,
now,
reservable,
reservableAfter,
reservableBefore,
- startDate,
openingHours,
reservations: filteredReservations,
- maxPeriod,
minPeriod,
- overnightEndTime,
- overnightStartTime,
hasAdminBypass: isUnitManagerOrHigher,
});
@@ -135,6 +142,16 @@ function OvernightCalendar({
return;
}
+ if ((startDate && !endDate)
+ && !isSelectionContinous(startDate, day, filteredReservations, openingHours)) {
+ actions.addNotification({
+ message: t('Notifications.continousFreeDaysError'),
+ type: 'info',
+ timeOut: 10000,
+ });
+ return;
+ }
+
if (!isDateDisabled && !booked && !isNextBlocked) {
handleDateSelect({
value: day,
@@ -184,6 +201,9 @@ function OvernightCalendar({
const isDurBelowMin = isUnitAdminOrHigher ? false
: isDurationBelowMin(selectedDuration, minPeriod);
+ const isDurOverMax = isUnitAdminOrHigher ? false
+ : isDurationOverMax(selectedDuration, maxPeriod);
+
return (
closedDaysModifier(day, openingHours),
- booked: (day) => (
- startDate ? null : reservationsModifier(day, filteredReservations)),
- nextBooked: (day) => (
- startDate ? null : nextDayBookedModifier(day, filteredReservations)),
- nextBookedStartSelected: (day) => (
- startDate ? nextDayBookedModifier(day, filteredReservations) : null),
+ booked: (day) => reservationsModifier(day, filteredReservations),
+ nextBooked: (day) => nextDayBookedModifier(day, filteredReservations),
nextClosed: (day) => nextDayClosedModifier(day, openingHours),
- prevBooked: (day) => (
- startDate ? null : prevDayBookedModifier(day, filteredReservations)),
+ prevBooked: (day) => prevDayBookedModifier(day, filteredReservations),
prevClosed: (day) => prevDayClosedModifier(day, openingHours),
}}
onDayClick={validateAndSelect}
@@ -243,6 +254,8 @@ function OvernightCalendar({
endDatetime={getOvernightDatetime(endDate, overnightEndTime, t)}
handleSelectDatetimes={handleSelectDatetimes}
isDurationBelowMin={isDurBelowMin}
+ isDurationOverMax={isDurOverMax}
+ maxDuration={maxPeriod || ''}
minDuration={minPeriod}
selected={selected}
startDatetime={getOvernightDatetime(startDate, overnightStartTime, t)}
@@ -254,6 +267,8 @@ function OvernightCalendar({
duration={selectedDuration}
endDatetime={getOvernightDatetime(endDate, overnightEndTime, t)}
isDurationBelowMin={isDurBelowMin}
+ isDurationOverMax={isDurOverMax}
+ maxDuration={maxPeriod || ''}
minDuration={minPeriod}
onCancel={onEditCancel}
onConfirm={handleSelectDatetimes}
diff --git a/app/shared/overnight-calendar/OvernightEditSummary.js b/app/shared/overnight-calendar/OvernightEditSummary.js
index af3f4340f..e19adbd5c 100644
--- a/app/shared/overnight-calendar/OvernightEditSummary.js
+++ b/app/shared/overnight-calendar/OvernightEditSummary.js
@@ -8,12 +8,15 @@ import { getPrettifiedPeriodUnits } from '../../utils/timeUtils';
function OvernightEditSummary({
startDatetime, endDatetime, selected, onCancel, onConfirm, t,
- duration, minDuration, isDurationBelowMin, datesSameAsInitial
+ duration, minDuration, isDurationBelowMin, datesSameAsInitial,
+ maxDuration, isDurationOverMax
}) {
const timeRange = startDatetime && endDatetime ? `${startDatetime} - ${endDatetime}` : `${selected[0]} - ${selected[1]}`;
const durationText = getPrettifiedPeriodUnits(duration, t('common.unit.time.day.short'));
const minDurationText = getPrettifiedPeriodUnits(minDuration, t('common.unit.time.day.short'));
+ const maxDurationText = getPrettifiedPeriodUnits(maxDuration, t('common.unit.time.day.short'));
const hasMinDurationError = !datesSameAsInitial && isDurationBelowMin;
+ const hasMaxDurationError = !datesSameAsInitial && isDurationOverMax;
const validRange = startDatetime && endDatetime;
return (
@@ -32,6 +35,9 @@ function OvernightEditSummary({
{hasMinDurationError && (
{`${t('Overnight.belowMinAlert')} (${minDurationText})`}
)}
+ {hasMaxDurationError && (
+ {`${t('Overnight.overMaxAlert')} (${maxDurationText})`}
+ )}
{t('TimeSlots.reserveButton')}
@@ -55,6 +59,8 @@ OvernightSummary.propTypes = {
duration: PropTypes.object.isRequired,
isDurationBelowMin: PropTypes.bool.isRequired,
minDuration: PropTypes.string.isRequired,
+ isDurationOverMax: PropTypes.bool.isRequired,
+ maxDuration: PropTypes.string.isRequired,
};
export default injectT(OvernightSummary);
diff --git a/app/shared/overnight-calendar/_overnight-calendar.scss b/app/shared/overnight-calendar/_overnight-calendar.scss
index 2a9f8459b..017bbfdd1 100644
--- a/app/shared/overnight-calendar/_overnight-calendar.scss
+++ b/app/shared/overnight-calendar/_overnight-calendar.scss
@@ -141,11 +141,11 @@
border: 2px solid $black;
}
- &--start:not(.DayPicker-Day--disabled) {
- background: linear-gradient($half-day-angle, $medium-gray 49%, $highlighted-color 50%);
+ &--start {
+ background: linear-gradient($half-day-angle, $available-color 49%, $highlighted-color 50%);
&.DayPicker-Day--prevBooked {
- background: linear-gradient($half-day-angle, $medium-gray 49%, $highlighted-color 50%);
+ background: linear-gradient($half-day-angle, $busy-color 49%, $highlighted-color 50%);
}
&.DayPicker-Day--prevClosed {
@@ -153,13 +153,9 @@
}
}
- &--end:not(.DayPicker-Day--disabled) {
+ &--end {
background: linear-gradient($half-day-angle, $highlighted-color 49%, $available-color 50%);
- &.DayPicker-Day--nextBookedStartSelected {
- background: linear-gradient($half-day-angle, $highlighted-color 49%, $medium-gray 50%);
- }
-
&.DayPicker-Day--nextClosed {
background: linear-gradient($half-day-angle, $highlighted-color 49%, $medium-gray 50%);
}
@@ -167,21 +163,29 @@
&--nextBooked:not(.DayPicker-Day--disabled) {
background: linear-gradient($half-day-angle, $available-color 49%, $booked-color 50%);
- }
- &--nextBookedStartSelected:not(.DayPicker-Day--disabled) {
- background: linear-gradient($half-day-angle, $available-color 49%, $medium-gray 50%);
+ &.DayPicker-Day--selected {
+ background: linear-gradient($half-day-angle, $highlighted-color 49%, $booked-color 50%);
+ }
}
&--prevBooked:not(.DayPicker-Day--disabled) {
background: linear-gradient($half-day-angle, $booked-color 49%, $available-color 50%);
+
+ &.DayPicker-Day--selected {
+ background: linear-gradient($half-day-angle, $booked-color 49%, $highlighted-color 50%);
+ }
}
&--nextClosed:not(.DayPicker-Day--disabled) {
background: linear-gradient($half-day-angle, $available-color 49%, $medium-gray 50%);
+
+ &.DayPicker-Day--selected {
+ background: linear-gradient($half-day-angle, $highlighted-color 49%, $medium-gray 50%);
+ }
}
- &--prevClosed:not(.DayPicker-Day--disabled) {
+ &--prevClosed:not(.DayPicker-Day--disabled):not(.DayPicker-Day--start) {
background: linear-gradient($half-day-angle, $medium-gray 49%, $available-color 50%);
}
diff --git a/app/shared/overnight-calendar/overnightUtils.js b/app/shared/overnight-calendar/overnightUtils.js
index f7edef214..2a6f015ae 100644
--- a/app/shared/overnight-calendar/overnightUtils.js
+++ b/app/shared/overnight-calendar/overnightUtils.js
@@ -28,10 +28,13 @@ export function handleDateSelect({
} else if (startTimedValue.getTime() === startDate.getTime()) {
setStartDate(null);
setEndDate(null);
+ } else if (startTimedValue.getTime() < startDate.getTime()) {
+ setStartDate(startTimedValue);
+ setEndDate(null);
} else if (!endDate) {
setEndDate(endTimedValue);
- } else if (endTimedValue.getTime() === endDate.getTime()) {
- setStartDate(null);
+ } else {
+ setStartDate(startTimedValue);
setEndDate(null);
}
}
@@ -44,30 +47,24 @@ export function handleDateSelect({
* @param {boolean} params.reservable
* @param {string} params.reservableAfter datetime
* @param {string} params.reservableBefore datetime
- * @param {Date} params.startDate
* @param {Object[]} params.openingHours
* @param {Object[]} params.reservations
- * @param {string} params.maxPeriod
- * @param {string} params.overnightEndTime
- * @param {string} params.overnightStartTime
* @param {boolean} params.hasAdminBypass
* @returns {boolean} is day disabled
*/
export function handleDisableDays({
- day, now, reservable, reservableAfter, reservableBefore, startDate,
- openingHours, reservations, maxPeriod, overnightEndTime,
- overnightStartTime, hasAdminBypass
+ day, now, reservable, reservableAfter, reservableBefore,
+ openingHours, reservations, hasAdminBypass
}) {
const isAfterToday = now.isAfter(day, 'day');
const beforeDate = reservableAfter || moment();
const isBeforeDate = moment(day).isBefore(beforeDate, 'day');
const afterDate = reservableBefore || moment().add(1, 'year');
const isAfterDate = moment(day).isAfter(afterDate, 'day');
- const isBeforeStartDate = startDate && moment(day).isBefore(startDate, 'day');
if (!hasAdminBypass && !reservable) {
return true;
}
- if (isAfterToday || isBeforeDate || isAfterDate || isBeforeStartDate) {
+ if (isAfterToday || isBeforeDate || isAfterDate) {
return true;
}
if (reservationsModifier(day, reservations)) {
@@ -82,18 +79,6 @@ export function handleDisableDays({
}
}
- if (startDate) {
- if (!hasAdminBypass && maxPeriod && isOverMaxPeriod(
- startDate, day, maxPeriod, overnightEndTime, overnightStartTime)) {
- return true;
- }
-
- const firstBlockedDay = getFirstBlockedDay(startDate, reservations, closedDays);
- if (firstBlockedDay && moment(day).isSameOrAfter(firstBlockedDay, 'day')) {
- return true;
- }
- }
-
return false;
}
@@ -134,12 +119,12 @@ export function getClosedDays(openingHours) {
*/
export function reservationsModifier(day, reservations) {
if (day && reservations) {
+ const dayMoment = moment(day);
for (let index = 0; index < reservations.length; index += 1) {
const reservation = reservations[index];
- const dayMoment = moment(day);
const beginMoment = moment(reservation.begin);
const endMoment = moment(reservation.end);
- if (dayMoment.isBetween(beginMoment, endMoment, 'day', '[]')) {
+ if (dayMoment.isBetween(beginMoment, endMoment, 'day', '()')) {
return true;
}
}
@@ -157,7 +142,7 @@ export function reservationsModifier(day, reservations) {
export function nextDayBookedModifier(day, reservations) {
if (day && reservations) {
const firstBooked = findFirstClosestReservation(day, reservations);
- if (firstBooked && moment(day).add(1, 'day').isSame(firstBooked.begin, 'day')) {
+ if (firstBooked && moment(day).isSame(firstBooked.begin, 'day')) {
return true;
}
}
@@ -174,7 +159,7 @@ export function nextDayBookedModifier(day, reservations) {
export function prevDayBookedModifier(day, reservations) {
if (day && reservations) {
const firstBooked = findPrevFirstClosestReservation(day, reservations);
- if (firstBooked && moment(day).subtract(1, 'day').isSame(firstBooked.end, 'day')) {
+ if (firstBooked && moment(day).isSame(firstBooked.end, 'day')) {
return true;
}
}
@@ -267,7 +252,7 @@ export function findPrevFirstClosedDay(fromDate, closedDays) {
export function findFirstClosestReservation(fromDate, reservations) {
const fromMoment = moment(fromDate);
const futureReservations = reservations.filter(
- reservation => moment(reservation.begin).isAfter(fromMoment));
+ reservation => moment(reservation.begin).isSameOrAfter(fromMoment, 'day'));
const sortedReservations = [...futureReservations].sort(
(a, b) => moment(a.begin).diff(fromMoment) - moment(b.begin).diff(fromMoment));
return sortedReservations.length > 0 ? sortedReservations[0] : null;
@@ -281,9 +266,9 @@ export function findFirstClosestReservation(fromDate, reservations) {
*/
export function findPrevFirstClosestReservation(fromDate, reservations) {
const fromMoment = moment(fromDate);
- const futureReservations = reservations.filter(
- reservation => moment(reservation.begin).isBefore(fromMoment));
- const sortedReservations = [...futureReservations].sort(
+ const pastReservations = reservations.filter(
+ reservation => moment(reservation.end).isSameOrBefore(fromMoment, 'day'));
+ const sortedReservations = [...pastReservations].sort(
(a, b) => moment(a.begin).diff(fromMoment) - moment(b.begin).diff(fromMoment));
return sortedReservations.length > 0 ? sortedReservations[sortedReservations.length - 1] : null;
}
@@ -509,6 +494,19 @@ export function isDurationBelowMin(duration, minPeriod) {
return duration < moment.duration(minPeriod);
}
+/**
+ * Returns true if duration is over maxPeriod
+ * @param {Object} duration moment
+ * @param {string} maxPeriod
+ * @returns {boolean} true if duration is over maxPeriod
+ */
+export function isDurationOverMax(duration, maxPeriod) {
+ if (!maxPeriod) {
+ return false;
+ }
+ return duration > moment.duration(maxPeriod);
+}
+
/**
* Returns true if dates are same as initial dates
* @param {Date} startDate
@@ -523,3 +521,43 @@ export function areDatesSameAsInitialDates(startDate, endDate, initialStart, ini
}
return moment(startDate).isSame(initialStart) && moment(endDate).isSame(initialEnd);
}
+
+/**
+ * Returns true if selection is continous i.e. does not contain disabled days
+ * @param {Date} startDate
+ * @param {Date} endDate
+ * @param {Object[]} reservations
+ * @param {Object[]} openingHours
+ * @returns {boolean} true if selection is continous
+ */
+export function isSelectionContinous(startDate, endDate, reservations, openingHours) {
+ const dates = createDateArray(startDate, endDate);
+
+ for (let index = 0; index < dates.length; index += 1) {
+ const date = dates[index];
+ if (reservationsModifier(date, reservations) || closedDaysModifier(date, openingHours)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+
+/**
+ * Creates array of dates between start and end Date
+ * @param {Date} startDate
+ * @param {Date} endDate
+ * @returns {Date[]} array of dates between start and end Date
+ */
+export function createDateArray(startDate, endDate) {
+ const start = new Date(startDate);
+ const end = new Date(endDate);
+ const dateArray = [];
+
+ while (start <= end) {
+ dateArray.push(new Date(start));
+ start.setDate(start.getDate() + 1);
+ }
+
+ return dateArray;
+}
diff --git a/app/shared/overnight-calendar/tests/OvernightCalendar.spec.js b/app/shared/overnight-calendar/tests/OvernightCalendar.spec.js
index 468684aa6..ec93f60a2 100644
--- a/app/shared/overnight-calendar/tests/OvernightCalendar.spec.js
+++ b/app/shared/overnight-calendar/tests/OvernightCalendar.spec.js
@@ -18,6 +18,8 @@ describe('app/shared/overnight-calendar/OvernightCalendar', () => {
overnightReservations: true,
overnightEndTime: '09:00:00',
overnightStartTime: '11:00:00',
+ minPeriod: '1 00:00:00',
+ maxPeriod: '10 00:00:00'
});
const defaultProps = {
currentLanguage: 'fi',
@@ -96,6 +98,8 @@ describe('app/shared/overnight-calendar/OvernightCalendar', () => {
expect(summary.prop('handleSelectDatetimes')).toBeDefined();
expect(summary.prop('isDurationBelowMin')).toBe(false);
expect(summary.prop('minDuration')).toBe(defaultProps.resource.minPeriod);
+ expect(summary.prop('isDurationOverMax')).toBe(false);
+ expect(summary.prop('maxDuration')).toBe(defaultProps.resource.maxPeriod);
expect(summary.prop('selected')).toBe(defaultProps.selected);
expect(summary.prop('startDatetime')).toBe('');
});
@@ -132,8 +136,10 @@ describe('app/shared/overnight-calendar/OvernightCalendar', () => {
expect(editSummary.prop('datesSameAsInitial')).toBe(true);
expect(editSummary.prop('duration')).toBeDefined();
expect(editSummary.prop('endDatetime')).toBe('19.2.2024 TimeSlots.selectedTime');
- expect(editSummary.prop('isDurationBelowMin')).toBe(false);
+ expect(editSummary.prop('isDurationBelowMin')).toBe(true);
expect(editSummary.prop('minDuration')).toBe(defaultProps.resource.minPeriod);
+ expect(editSummary.prop('isDurationOverMax')).toBe(false);
+ expect(editSummary.prop('maxDuration')).toBe(defaultProps.resource.maxPeriod);
expect(editSummary.prop('onCancel')).toBe(defaultProps.onEditCancel);
expect(editSummary.prop('onConfirm')).toBeDefined();
expect(editSummary.prop('selected')).toBe(selected);
diff --git a/app/shared/overnight-calendar/tests/OvernightEditSummary.spec.js b/app/shared/overnight-calendar/tests/OvernightEditSummary.spec.js
index 4d97b8326..2e49f1706 100644
--- a/app/shared/overnight-calendar/tests/OvernightEditSummary.spec.js
+++ b/app/shared/overnight-calendar/tests/OvernightEditSummary.spec.js
@@ -17,6 +17,8 @@ describe('app/shared/overnight-calendar/OvernightEditSummary', () => {
startDatetime: '23.2.2024 11:00',
isDurationBelowMin: false,
minDuration: '1 00:00:00',
+ maxDuration: '10 00:00:00',
+ isDurationOverMax: false,
datesSameAsInitial: true,
onCancel: () => {},
onConfirm: () => {},
@@ -76,6 +78,22 @@ describe('app/shared/overnight-calendar/OvernightEditSummary', () => {
});
});
+ describe('when there is max duration error', () => {
+ test('error text', () => {
+ const paragraph = getWrapper({ datesSameAsInitial: false, isDurationOverMax: true }).find('p.overnight-error');
+ const maxDurationText = getPrettifiedPeriodUnits(defaultProps.maxDuration, 'common.unit.time.day.short');
+ expect(paragraph).toHaveLength(1);
+ expect(paragraph.text()).toEqual(`Overnight.overMaxAlert (${maxDurationText})`);
+ });
+ });
+
+ describe('when there isnt max duration error', () => {
+ test('doesnt render error text', () => {
+ const paragraph = getWrapper({ datesSameAsInitial: false, isDurationOverMax: false }).find('p.overnight-error');
+ expect(paragraph).toHaveLength(0);
+ });
+ });
+
test('reservation controls wrapping div', () => {
const div = getWrapper().find('div.app-ReservationTime__controls');
expect(div).toHaveLength(1);
diff --git a/app/shared/overnight-calendar/tests/OvernightSummary.spec.js b/app/shared/overnight-calendar/tests/OvernightSummary.spec.js
index bba7bb550..e4f39489b 100644
--- a/app/shared/overnight-calendar/tests/OvernightSummary.spec.js
+++ b/app/shared/overnight-calendar/tests/OvernightSummary.spec.js
@@ -17,6 +17,8 @@ describe('app/shared/overnight-calendar/OvernightSummary', () => {
startDatetime: '23.2.2024 11:00',
isDurationBelowMin: false,
minDuration: '1 00:00:00',
+ maxDuration: '10 00:00:00',
+ isDurationOverMax: false,
handleSelectDatetimes: () => {},
};
@@ -84,6 +86,22 @@ describe('app/shared/overnight-calendar/OvernightSummary', () => {
});
});
+ describe('when there is max duration error', () => {
+ test('error text', () => {
+ const paragraph = getWrapper({ datesSameAsInitial: false, isDurationOverMax: true }).find('p.overnight-error');
+ const maxDurationText = getPrettifiedPeriodUnits(defaultProps.maxDuration, 'common.unit.time.day.short');
+ expect(paragraph).toHaveLength(1);
+ expect(paragraph.text()).toEqual(`Overnight.overMaxAlert (${maxDurationText})`);
+ });
+ });
+
+ describe('when there isnt max duration error', () => {
+ test('doesnt render error text', () => {
+ const paragraph = getWrapper({ datesSameAsInitial: false, isDurationOverMax: false }).find('p.overnight-error');
+ expect(paragraph).toHaveLength(0);
+ });
+ });
+
test('reserve button', () => {
const button = getWrapper().find(Button);
expect(button.prop('bsStyle')).toBe('primary');
diff --git a/app/shared/overnight-calendar/tests/overnightUtils.spec.js b/app/shared/overnight-calendar/tests/overnightUtils.spec.js
index e07b2a076..d047b1aab 100644
--- a/app/shared/overnight-calendar/tests/overnightUtils.spec.js
+++ b/app/shared/overnight-calendar/tests/overnightUtils.spec.js
@@ -3,6 +3,7 @@ import moment from 'moment';
import {
areDatesSameAsInitialDates,
closedDaysModifier,
+ createDateArray,
filterSelectedReservation,
findFirstClosedDay,
findFirstClosestReservation,
@@ -20,8 +21,10 @@ import {
handleDisableDays,
handleFormattingSelected,
isDurationBelowMin,
+ isDurationOverMax,
isOverMaxPeriod,
isReservingAllowed,
+ isSelectionContinous,
nextDayBookedModifier,
nextDayClosedModifier,
prevDayBookedModifier,
@@ -34,7 +37,7 @@ import Resource from '../../../utils/fixtures/Resource';
describe('app/shared/overnight-calendar/overnightUtils', () => {
describe('handleDateSelect', () => {
- test('returns undefined when value if falsy', () => {
+ test('returns undefined when value is falsy', () => {
expect(handleDateSelect({ value: null })).toBeUndefined();
});
@@ -64,6 +67,22 @@ describe('app/shared/overnight-calendar/overnightUtils', () => {
expect(setEndDate).toHaveBeenCalledWith(null);
});
+ test('sets startDate and endDate correctly if given value is before startDate', () => {
+ const setStartDate = jest.fn();
+ const setEndDate = jest.fn();
+ const overnightStartTime = '11:00:00';
+ const startDate = new Date('2024-02-21');
+ const value = new Date('2024-02-11');
+ const expectedStart = setDatesTime(value, overnightStartTime).toDate();
+ handleDateSelect({
+ value, startDate, setStartDate, setEndDate, overnightStartTime
+ });
+ expect(setStartDate).toHaveBeenCalled();
+ expect(setStartDate).toHaveBeenCalledWith(expectedStart);
+ expect(setEndDate).toHaveBeenCalled();
+ expect(setEndDate).toHaveBeenCalledWith(null);
+ });
+
test('sets endDate if startDate already set, value is not same as start and endDate not set', () => {
const setStartDate = jest.fn();
const setEndDate = jest.fn();
@@ -84,14 +103,15 @@ describe('app/shared/overnight-calendar/overnightUtils', () => {
expect(setEndDate).toHaveBeenCalledWith(expectedCall);
});
- test('sets startDate and endDate to null if start and end are selected and end is same as value', () => {
+ test('sets start to given value when both start and end were already selected', () => {
const setStartDate = jest.fn();
const setEndDate = jest.fn();
const overnightStartTime = '11:00:00';
const overnightEndTime = '13:00:00';
- const val = new Date();
+ const val = new Date('2024-02-22');
const start = new Date('2024-02-21');
- const end = setDatesTime(val, overnightEndTime).toDate();
+ const end = new Date('2024-02-25');
+ const expectedCall = setDatesTime(val, overnightStartTime).toDate();
handleDateSelect({
value: val,
startDate: start,
@@ -102,7 +122,7 @@ describe('app/shared/overnight-calendar/overnightUtils', () => {
overnightEndTime
});
expect(setStartDate).toHaveBeenCalled();
- expect(setStartDate).toHaveBeenCalledWith(null);
+ expect(setStartDate).toHaveBeenCalledWith(expectedCall);
expect(setEndDate).toHaveBeenCalled();
expect(setEndDate).toHaveBeenCalledWith(null);
});
@@ -114,7 +134,6 @@ describe('app/shared/overnight-calendar/overnightUtils', () => {
const reservable = true;
const reservableAfter = '2024-04-19T00:00:00+03:00';
const reservableBefore = '2024-04-29T00:00:00+03:00';
- const startDate = null;
const openingHours = [
{ date: '2024-04-19', closes: null, opens: null },
{ date: '2024-04-20', closes: '2024-04-20T20:00:00+03:00', opens: '2024-04-20T06:00:00+03:00' },
@@ -130,11 +149,9 @@ describe('app/shared/overnight-calendar/overnightUtils', () => {
{ date: '2024-04-30', closes: null, opens: null },
];
const reservations = [
- Reservation.build({ begin: '2024-04-23T13:00:00+03:00', end: '2024-04-24T09:00:00+03:00' })
+ Reservation.build({ begin: '2024-04-22T13:00:00+03:00', end: '2024-04-24T09:00:00+03:00' })
];
- const maxPeriod = '3 00:00:00';
- const overnightEndTime = '09:00:00';
- const overnightStartTime = '13:00:00';
+
const hasAdminBypass = false;
const params = {
now,
@@ -142,12 +159,8 @@ describe('app/shared/overnight-calendar/overnightUtils', () => {
reservable,
reservableAfter,
reservableBefore,
- startDate,
openingHours,
reservations,
- maxPeriod,
- overnightEndTime,
- overnightStartTime,
hasAdminBypass
};
@@ -190,16 +203,6 @@ describe('app/shared/overnight-calendar/overnightUtils', () => {
)).toBe(true);
});
- test('day is before selected start date', () => {
- expect(handleDisableDays(
- {
- ...params,
- day: moment('2024-04-24').toDate(),
- startDate: moment('2024-04-26').toDate(),
- }
- )).toBe(true);
- });
-
test('day has reservation', () => {
expect(handleDisableDays(
{
@@ -217,27 +220,6 @@ describe('app/shared/overnight-calendar/overnightUtils', () => {
}
)).toBe(true);
});
-
- test('day is over max reservation time', () => {
- expect(handleDisableDays(
- {
- ...params,
- day: moment('2024-04-29').toDate(),
- startDate: moment('2024-04-25').toDate(),
- }
- )).toBe(true);
- });
-
- test('day is after a reservation or closed day when startDate is selected', () => {
- expect(handleDisableDays(
- {
- ...params,
- maxPeriod: '10 00:00:00',
- day: moment('2024-04-25').toDate(),
- startDate: moment('2024-04-22').toDate(),
- }
- )).toBe(true);
- });
});
});
@@ -276,7 +258,7 @@ describe('app/shared/overnight-calendar/overnightUtils', () => {
describe('reservationsModifier', () => {
const reservations = [
- Reservation.build({ begin: '2024-04-23T13:00:00+03:00', end: '2024-04-24T09:00:00+03:00' })
+ Reservation.build({ begin: '2024-04-22T13:00:00+03:00', end: '2024-04-25T09:00:00+03:00' })
];
test('returns true when day has reservation', () => {
expect(reservationsModifier(
@@ -286,10 +268,14 @@ describe('app/shared/overnight-calendar/overnightUtils', () => {
});
test('returns false when day has no reservation', () => {
+ expect(reservationsModifier(
+ moment('2024-04-21').toDate(), reservations)).toBe(false);
expect(reservationsModifier(
moment('2024-04-22').toDate(), reservations)).toBe(false);
expect(reservationsModifier(
moment('2024-04-25').toDate(), reservations)).toBe(false);
+ expect(reservationsModifier(
+ moment('2024-04-26').toDate(), reservations)).toBe(false);
});
});
@@ -299,7 +285,7 @@ describe('app/shared/overnight-calendar/overnightUtils', () => {
];
test('returns true when next day has reservation', () => {
expect(nextDayBookedModifier(
- moment('2024-04-22').toDate(), reservations)).toBe(true);
+ moment('2024-04-23').toDate(), reservations)).toBe(true);
});
test('returns false when next day has no reservation', () => {
@@ -316,7 +302,7 @@ describe('app/shared/overnight-calendar/overnightUtils', () => {
];
test('returns true when previous day has reservation', () => {
expect(prevDayBookedModifier(
- moment('2024-04-25').toDate(), reservations)).toBe(true);
+ moment('2024-04-24').toDate(), reservations)).toBe(true);
});
test('returns false when previous day has no reservation', () => {
@@ -715,6 +701,16 @@ describe('app/shared/overnight-calendar/overnightUtils', () => {
});
});
+ describe('isDurationOverMax', () => {
+ test('returns correct result', () => {
+ expect(isDurationOverMax(moment.duration(2, 'days'), '')).toBe(false);
+ expect(isDurationOverMax(moment.duration(2, 'days'), '10:00:00')).toBe(true);
+ expect(isDurationOverMax(moment.duration(2, 'days'), '3 10:00:00')).toBe(false);
+ expect(isDurationOverMax(moment.duration(3, 'days'), '3 10:00:00')).toBe(false);
+ expect(isDurationOverMax(moment.duration(4, 'days'), '3 10:00:00')).toBe(true);
+ });
+ });
+
describe('areDatesSameAsInitialDates', () => {
const startDate = moment('2024-04-23').toDate();
const endDate = moment('2024-04-26').toDate();
@@ -747,4 +743,66 @@ describe('app/shared/overnight-calendar/overnightUtils', () => {
.toBe(false);
});
});
+
+ describe('isSelectionContinous', () => {
+ const reservations = [
+ Reservation.build({
+ begin: '2024-04-27T13:00:00+03:00',
+ end: '2024-04-29T09:00:00+03:00'
+ })
+ ];
+ const openingHours = [
+ { date: '2024-04-19', closes: null, opens: null },
+ { date: '2024-04-20', closes: '2024-04-20T20:00:00+03:00', opens: '2024-04-20T06:00:00+03:00' },
+ { date: '2024-04-21', closes: '2024-04-21T20:00:00+03:00', opens: '2024-04-21T06:00:00+03:00' },
+ { date: '2024-04-22', closes: '2024-04-22T20:00:00+03:00', opens: '2024-04-22T06:00:00+03:00' },
+ { date: '2024-04-23', closes: '2024-04-23T20:00:00+03:00', opens: '2024-04-23T06:00:00+03:00' },
+ { date: '2024-04-24', closes: '2024-04-24T20:00:00+03:00', opens: '2024-04-24T06:00:00+03:00' },
+ { date: '2024-04-25', closes: '2024-04-25T20:00:00+03:00', opens: '2024-04-25T06:00:00+03:00' },
+ { date: '2024-04-26', closes: '2024-04-26T20:00:00+03:00', opens: '2024-04-26T06:00:00+03:00' },
+ { date: '2024-04-27', closes: '2024-04-27T20:00:00+03:00', opens: '2024-04-27T06:00:00+03:00' },
+ { date: '2024-04-28', closes: '2024-04-28T20:00:00+03:00', opens: '2024-04-28T06:00:00+03:00' },
+ { date: '2024-04-29', closes: '2024-04-29T20:00:00+03:00', opens: '2024-04-29T06:00:00+03:00' },
+ { date: '2024-04-30', closes: null, opens: null },
+ ];
+ test('returns true when no reservations or closed days in selection', () => {
+ const startDate = moment('2024-04-23').toDate();
+ const endDate = moment('2024-04-27').toDate();
+ expect(isSelectionContinous(startDate, endDate, [], openingHours))
+ .toBe(true);
+ expect(isSelectionContinous(startDate, endDate, reservations, openingHours))
+ .toBe(true);
+ });
+ test('returns false when reservations or closed days in selection', () => {
+ const startDate1 = moment('2024-04-23').toDate();
+ const endDate1 = moment('2024-04-29').toDate();
+ const startDate2 = moment('2024-04-25').toDate();
+ const endDate2 = moment('2024-04-30').toDate();
+ const startDate3 = moment('2024-04-19').toDate();
+ const endDate3 = moment('2024-04-20').toDate();
+ expect(isSelectionContinous(startDate1, endDate1, reservations, openingHours))
+ .toBe(false);
+ expect(isSelectionContinous(startDate2, endDate2, reservations, openingHours))
+ .toBe(false);
+ expect(isSelectionContinous(startDate3, endDate3, reservations, openingHours))
+ .toBe(false);
+ });
+ });
+
+ describe('createDateArray', () => {
+ test('returns correct array', () => {
+ const startDate = moment('2024-04-23').toDate();
+ const endDate = moment('2024-04-27').toDate();
+ expect(createDateArray(startDate, endDate))
+ .toStrictEqual([
+ moment('2024-04-23').toDate(),
+ moment('2024-04-24').toDate(),
+ moment('2024-04-25').toDate(),
+ moment('2024-04-26').toDate(),
+ moment('2024-04-27').toDate()
+ ]);
+ expect(createDateArray(moment('2024-04-23').toDate(), moment('2024-04-23').toDate()))
+ .toStrictEqual([moment('2024-04-23').toDate()]);
+ });
+ });
});