cronofy-elements 1.46.1 → 1.48.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (25) hide show
  1. package/build/CronofyElements.v1.48.2.js +2 -0
  2. package/build/{CronofyElements.v1.46.1.js.LICENSE.txt → CronofyElements.v1.48.2.js.LICENSE.txt} +0 -0
  3. package/build/npm/CronofyElements.js +2 -2
  4. package/demo/availability-viewer.ejs +6 -0
  5. package/demo/date-time-picker.ejs +4 -25
  6. package/package.json +1 -1
  7. package/src/js/components/AvailabilityRules/Wrapper.js +14 -5
  8. package/src/js/components/AvailabilityRules/utils/tz-utils.js +17 -0
  9. package/src/js/components/AvailabilityViewer/AvailabilityViewer.js +4 -0
  10. package/src/js/components/DateTimePicker/Confirm.js +37 -1
  11. package/src/js/components/DateTimePicker/DateTimePicker.js +1 -0
  12. package/src/js/components/DateTimePicker/SequencedSlotButton.js +37 -11
  13. package/src/js/components/DateTimePicker/Wrapper.js +1 -1
  14. package/src/js/components/DateTimePicker/contexts/status-reducer.js +2 -2
  15. package/src/js/components/DateTimePicker/scss/_components.confirm.scss +10 -0
  16. package/src/js/components/DateTimePicker/scss/_components.slotslist.scss +10 -0
  17. package/src/js/components/DateTimePicker/utils/slots.js +15 -2
  18. package/src/js/helpers/connections.js +1 -15
  19. package/src/js/helpers/init.DateTimePicker.js +44 -2
  20. package/src/js/helpers/mocks.js +97 -0
  21. package/tests/DateTimePicker/SequencedSlotButton.test.js +28 -1
  22. package/tests/DateTimePicker/contexts/status-reducer.test.js +5 -3
  23. package/tests/DateTimePicker/dummy-data.js +186 -1
  24. package/tests/DateTimePicker/utils.test.js +24 -3
  25. package/build/CronofyElements.v1.46.1.js +0 -2
@@ -115,6 +115,12 @@
115
115
  // { start: offsetTime(1, "09:00"), end: offsetTime(90, "17:30") }
116
116
  // ];
117
117
 
118
+ // Query period with at least one missing week in the middle
119
+ // availablePeriods = [
120
+ // { start: offsetTime(1, "09:00"), end: offsetTime(7, "17:30") },
121
+ // { start: offsetTime(25, "09:00"), end: offsetTime(50, "17:30") }
122
+ // ];
123
+
118
124
  if (display.one) {
119
125
 
120
126
  const originalQuery = {
@@ -49,6 +49,7 @@
49
49
  sequence: [
50
50
  {
51
51
  sequence_id: "test",
52
+ sequence_title: "Test Event One",
52
53
  ordinal: 1,
53
54
  participants: [
54
55
  {
@@ -66,6 +67,7 @@
66
67
  },
67
68
  {
68
69
  sequence_id: "test-1",
70
+ sequence_title: "Test Event Two",
69
71
  ordinal: 2,
70
72
  participants: [
71
73
  {
@@ -86,31 +88,7 @@
86
88
  minimum: { minutes: 15 }
87
89
  }
88
90
  }
89
- },
90
- {
91
- sequence_id: "test-2",
92
- ordinal: 3,
93
- participants: [
94
- {
95
- required: "all",
96
- members: [
97
- {
98
- sub: "<%= sub %>",
99
- // managed_availability: true,
100
- // availability_rule_ids: ["weekly_work_hours"]
101
- }
102
- ]
103
- }
104
- ],
105
- required_duration: { minutes: 30 },
106
- start_interval: { minutes: 10 },
107
- buffer: {
108
- before: {
109
- minimum: { minutes: 15 }
110
- }
111
- }
112
- },
113
-
91
+ },
114
92
  ],
115
93
  query_periods: [
116
94
  { start: slotTimes(01,"08:00"), end: slotTimes(31,"17:00") }
@@ -151,6 +129,7 @@
151
129
  },
152
130
  config: {
153
131
  logs: 'info',
132
+ //slot_button_mode: 'detailed' //summary | detailed
154
133
  //selected_date: "2022-05-15"
155
134
  //mode: 'no_confirm'
156
135
  // week_start_day: "tuesday"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cronofy-elements",
3
- "version": "1.46.1",
3
+ "version": "1.48.2",
4
4
  "description": "Fast track scheduling with Cronofy's embeddable UI Elements",
5
5
  "main": "build/npm/CronofyElements.js",
6
6
  "scripts": {
@@ -14,7 +14,7 @@ import {
14
14
  parseAccountOptions,
15
15
  buildRuleTemplate,
16
16
  } from "../../helpers/utils.AvailabilityRules";
17
- import { getInitialSelectedTzid } from "./utils/tz-utils";
17
+ import { getInitialSelectedTzid, addTzidToTzList } from "./utils/tz-utils";
18
18
  import { parseStyleOptions, classBuilder } from "../../helpers/theming";
19
19
  import { globals } from "../../styles/utils";
20
20
  import { useI18n } from "../../contexts/i18n-context";
@@ -198,10 +198,19 @@ const Wrapper = ({ options }) => {
198
198
  tz.list,
199
199
  rulesResponse.availability_rule.tzid
200
200
  );
201
- setTz({
202
- list: tz.list,
203
- selectedTzid: selectedTzid,
204
- });
201
+ if (selectedTzid) {
202
+ setTz({
203
+ list: tz.list,
204
+ selectedTzid: selectedTzid,
205
+ });
206
+ } else {
207
+ //If we get here the tzid from the availbility rule is not in the tz.list so we need to add it.
208
+ const result = addTzidToTzList(tz.list, rulesResponse.availability_rule.tzid);
209
+ setTz({
210
+ list: result.tzList,
211
+ selectedTzid: result.selectedTzid,
212
+ });
213
+ }
205
214
 
206
215
  setSlots(slots => {
207
216
  const hydratedSlots = checkSlotAvailability(
@@ -42,3 +42,20 @@ export const getInitialSelectedTzid = (tzList, tzid) => {
42
42
  const result = tzList.find(tz => tzid === tz.tzid);
43
43
  return result;
44
44
  };
45
+
46
+ export const addTzidToTzList = (tzList, tzid) => {
47
+ const isValidTimeZone = !!moment.tz.zone(tzid);
48
+ if (isValidTimeZone) {
49
+ const newTzList = [...tzList];
50
+ const item = createTzObject(tzid);
51
+ newTzList.push(item);
52
+ newTzList.sort((tzA, tzB) => tzA.offsetMins - tzB.offsetMins);
53
+
54
+ return {
55
+ tzList: newTzList,
56
+ selectedTzid: item,
57
+ };
58
+ } else {
59
+ throw `There was an error setting the selected tzid as ${tzid}.`;
60
+ }
61
+ };
@@ -336,6 +336,10 @@ const AvailabilityViewer = ({ options, error, eventCallback }) => {
336
336
  // If we get here, the query_periods are not within the visible window.
337
337
  // Empty queries will have triggered a `log.warn` earlier.
338
338
  log.info("The provided query_periods are not within the visible window");
339
+ // Passing an empty array to `handleNormalAvailability()`
340
+ // to trigger the page being updated when there is an empty week
341
+ // in the middle of a query period.
342
+ handleNormalAvailability([]);
339
343
  }
340
344
  if (status.slotSelection === "unrestricted" && emptyQuery) {
341
345
  setRawData([]);
@@ -1,4 +1,4 @@
1
- import React from "react";
1
+ import React, { useState } from "react";
2
2
  import moment from "moment-timezone";
3
3
 
4
4
  import { useI18n } from "../../contexts/i18n-context";
@@ -12,6 +12,12 @@ const Confirm = ({ confirmButtonRef }) => {
12
12
  const theme = useTheme();
13
13
  const [tz] = useTz();
14
14
 
15
+ const [sequenceTitlePresent, setSequenceTitlePresent] = useState(
16
+ () =>
17
+ status.selected.sequence &&
18
+ status.selected.sequence.every(obj => Object.keys(obj).includes("sequence_title"))
19
+ );
20
+
15
21
  const handleCancel = () => {
16
22
  dispatchStatus({ type: "CANCEL_SLOT_SELECTION" });
17
23
  };
@@ -52,6 +58,36 @@ const Confirm = ({ confirmButtonRef }) => {
52
58
  )}{" "}
53
59
  {`(${moment.tz(tz.selectedTzid.tzid).format("z")})`}
54
60
  </p>
61
+ {status.selected.sequence && sequenceTitlePresent && (
62
+ <dl className={theme.classBuilder("confirm-details--sequence")}>
63
+ {status.selected.sequence.map((slot, k) => (
64
+ <div key={k}>
65
+ <dt
66
+ className={theme.classBuilder(
67
+ "confirm-details--sequence-title"
68
+ )}
69
+ >
70
+ {slot.sequence_title}
71
+ </dt>
72
+ <dd
73
+ className={theme.classBuilder("confirm-details--sequence-time")}
74
+ >
75
+ {i18n.customFormatedTimeZone(
76
+ moment(slot.start, "YYYY-MM-DDTHH:mm:00Z"),
77
+ tz.selectedTzid.tzid,
78
+ "LT"
79
+ )}
80
+ {" - "}
81
+ {i18n.customFormatedTimeZone(
82
+ moment(slot.end, "YYYY-MM-DDTHH:mm:00Z"),
83
+ tz.selectedTzid.tzid,
84
+ "LT"
85
+ )}
86
+ </dd>
87
+ </div>
88
+ ))}
89
+ </dl>
90
+ )}
55
91
  </div>
56
92
  <button
57
93
  className={theme.classBuilder("confirm-button")}
@@ -78,6 +78,7 @@ const DateTimePicker = ({ options }) => {
78
78
  availableDays: [],
79
79
  sequenced_availability: options.query.sequence ? true : false,
80
80
  slots: {},
81
+ slotButtonMode: options.config.slotButtonMode,
81
82
  slotFetchCount: 0,
82
83
  slotInjectionPoint: undefined,
83
84
  tzid: options.tzid,
@@ -41,29 +41,55 @@ const SequencedSlotButton = ({ slot }) => {
41
41
  }, [status.focusedSlot]);
42
42
 
43
43
  let classStub = "time-slot";
44
+ if (status.slotButtonMode === "detailed") classStub = classStub + " time-slot--detailed";
44
45
  if (status.selected.start === start) classStub = classStub + " time-slot--selected";
45
46
 
46
- return (
47
- <button
48
- className={theme.classBuilder(classStub)}
49
- onClick={() => handleSlotSelection(slot)}
50
- ref={slotButtonRef}
51
- >
52
- <span className={theme.classBuilder("visually-hidden")}>
53
- {i18n.t("select_time_slot")}
54
- </span>
47
+ const detailedSlot = slot.map((s, i) => (
48
+ <li key={i} className={theme.classBuilder("detailed-slot-list--item")}>
55
49
  {i18n.customFormatedTimeZone(
56
- moment(start, "YYYY-MM-DDTHH:mm:00Z"),
50
+ moment(s.start, "YYYY-MM-DDTHH:mm:00Z"),
57
51
  tz.selectedTzid.tzid,
58
52
  "LT"
59
53
  )}
60
54
  {" - "}
61
55
  {i18n.customFormatedTimeZone(
62
- moment(end, "YYYY-MM-DDTHH:mm:00Z"),
56
+ moment(s.end, "YYYY-MM-DDTHH:mm:00Z"),
63
57
  tz.selectedTzid.tzid,
64
58
  "LT"
65
59
  )}{" "}
66
60
  {`(${moment(start, "YYYY-MM-DDTHH:mm:00Z").tz(tz.selectedTzid.tzid).format("z")})`}
61
+ </li>
62
+ ));
63
+
64
+ return (
65
+ <button
66
+ className={theme.classBuilder(classStub)}
67
+ onClick={() => handleSlotSelection(slot)}
68
+ ref={slotButtonRef}
69
+ >
70
+ <span className={theme.classBuilder("visually-hidden")}>
71
+ {i18n.t("select_time_slot")}
72
+ </span>
73
+ {status.slotButtonMode === "detailed" ? (
74
+ <ul className={theme.classBuilder("detailed-slot-list")}>{detailedSlot}</ul>
75
+ ) : (
76
+ <>
77
+ {i18n.customFormatedTimeZone(
78
+ moment(start, "YYYY-MM-DDTHH:mm:00Z"),
79
+ tz.selectedTzid.tzid,
80
+ "LT"
81
+ )}
82
+ {" - "}
83
+ {i18n.customFormatedTimeZone(
84
+ moment(end, "YYYY-MM-DDTHH:mm:00Z"),
85
+ tz.selectedTzid.tzid,
86
+ "LT"
87
+ )}{" "}
88
+ {`(${moment(start, "YYYY-MM-DDTHH:mm:00Z")
89
+ .tz(tz.selectedTzid.tzid)
90
+ .format("z")})`}
91
+ </>
92
+ )}
67
93
  </button>
68
94
  );
69
95
  };
@@ -186,7 +186,7 @@ const Wrapper = () => {
186
186
 
187
187
  // For allowing only this to be called once slots have been fetched
188
188
  // Since we know the first month that is being fetched will be for the initial selected day
189
- if (status.selectedDay && fetchCount >= 1) {
189
+ if (status.selectedDay && fetchCount >= 1 && !status.selected) {
190
190
  const currentMonth = moment(status.selectedDay, "YYYY-MM-DD").format("YYYY-MM");
191
191
  const month = status.months.filter(el => {
192
192
  return el.month === currentMonth;
@@ -110,7 +110,7 @@ export const statusReducer = (state, action) => {
110
110
 
111
111
  // Add action slots to state slots
112
112
  const slotsObject = state.sequenced_availability
113
- ? addSequencedSlotsToObject(state.slots, action.slots)
113
+ ? addSequencedSlotsToObject(state.slots, action.slots, state.query.sequence)
114
114
  : addSlotsToObject(state.slots, action.slots);
115
115
 
116
116
  // since we already know the available days for state slots
@@ -118,7 +118,7 @@ export const statusReducer = (state, action) => {
118
118
  // this way we don't have to loop through the entirety of the state slotsObject
119
119
  // every time new slots are rendered
120
120
  const actionSlotsObject = state.sequenced_availability
121
- ? addSequencedSlotsToObject({}, action.slots)
121
+ ? addSequencedSlotsToObject({}, action.slots, [])
122
122
  : addSlotsToObject({}, action.slots);
123
123
 
124
124
  const availableDays = uniqueItems([
@@ -10,6 +10,16 @@
10
10
  font-size: 1.5em;
11
11
  }
12
12
 
13
+ .DTP__confirm-details--sequence-title {
14
+ display: inline-block;
15
+ font-weight: bold;
16
+ }
17
+
18
+ .DTP__confirm-details--sequence-time {
19
+ display: inline-block;
20
+ margin-left: 0.5rem;
21
+ }
22
+
13
23
  .DTP__confirm-button {
14
24
  @extend .DTP__button;
15
25
  background-color: var(--buttonConfirm);
@@ -25,6 +25,16 @@
25
25
  border-radius: 0.4em;
26
26
  }
27
27
 
28
+ .DTP__time-slot--detailed {
29
+ height: unset;
30
+ }
31
+
32
+ .DTP__detailed-slot-list,
33
+ .DTP__detailed-slot-list--item {
34
+ @extend %unset;
35
+ list-style: none;
36
+ }
37
+
28
38
  .DTP__time-slot--selected {
29
39
  @extend .DTP__button;
30
40
  background-color: var(--buttonActive);
@@ -196,13 +196,26 @@ export const addSlotsToObject = (slotsObject, newSlotsArray) => {
196
196
  return slotsObject;
197
197
  };
198
198
 
199
- export const addSequencedSlotsToObject = (slotsObject, newSlotsArray) => {
199
+ export const addSequenceTitle = (slot, sequence) => {
200
+ if (sequence.length > 0) {
201
+ const title = sequence.find(s => {
202
+ return s.sequence_id === slot.sequence_id;
203
+ }).sequence_title;
204
+ return title;
205
+ }
206
+ };
207
+
208
+ export const addSequencedSlotsToObject = (slotsObject, newSlotsArray, sequence) => {
200
209
  newSlotsArray.forEach(slot => {
210
+ const formattedSlot = slot.sequence.map(s => {
211
+ const title = addSequenceTitle(s, sequence);
212
+ return title ? { ...s, sequence_title: title } : s;
213
+ });
201
214
  const startArray = slot.sequence.map(a => a.start);
202
215
  const start = startArray.reduce((prev, current) => {
203
216
  return prev < current ? prev : current;
204
217
  });
205
- slotsObject[start] = slot.sequence;
218
+ slotsObject[start] = formattedSlot;
206
219
  });
207
220
  return slotsObject;
208
221
  };
@@ -276,21 +276,7 @@ export const getSequencedAvailability = (
276
276
  tzid = "Etc/UTC",
277
277
  mock = false
278
278
  ) => {
279
- if (
280
- (mock && params.response_format === "slots") ||
281
- (mock && typeof params.start_interval === "undefined")
282
- ) {
283
- return mocks.availabilitySlots;
284
- }
285
- if (mock && params.response_format === "overlapping_slots") {
286
- return mocks.availabilityOverlappingSlots(
287
- params.start_interval.minutes,
288
- params.required_duration.minutes,
289
- params.query_periods,
290
- tzid
291
- );
292
- }
293
- if (mock) return mocks.availability;
279
+ if (mock) return mocks.sequencedAvailabilitySlots;
294
280
 
295
281
  return fetch(`${api_domain}/v1/sequenced_availability?et=${token}`, {
296
282
  method: "POST",
@@ -10,7 +10,7 @@ import {
10
10
  validateLocaleModifiers,
11
11
  } from "./init";
12
12
  import { logConstructor } from "./logging";
13
- import { queryForDateTimePicker } from "./mocks";
13
+ import { queryForDateTimePicker, sequencedQueryForDateTimePicker } from "./mocks";
14
14
 
15
15
  import {
16
16
  getMonthsFromQuery,
@@ -75,8 +75,10 @@ export const parseDateTimePickerOptions = (options = {}) => {
75
75
  const isSequencedAvailabilityQuery = options.availability_query.sequence ? true : false;
76
76
 
77
77
  let query;
78
- if (options.demo) {
78
+ if (options.demo && !isSequencedAvailabilityQuery) {
79
79
  query = queryForDateTimePicker;
80
+ } else if (options.demo && isSequencedAvailabilityQuery) {
81
+ query = sequencedQueryForDateTimePicker;
80
82
  } else if (isBookableEventsQuery) {
81
83
  query = options.availability_query;
82
84
  query = parseWithOverlappingSlots(query);
@@ -88,6 +90,45 @@ export const parseDateTimePickerOptions = (options = {}) => {
88
90
  query = parseWithOverlappingSlots(query);
89
91
  }
90
92
 
93
+ if (isSequencedAvailabilityQuery) {
94
+ const isOneTitlePresent = query.sequence.some(obj =>
95
+ Object.keys(obj).includes("sequence_title")
96
+ );
97
+
98
+ if (isOneTitlePresent) {
99
+ const isEveryTitlePresent = query.sequence.every(obj =>
100
+ Object.keys(obj).includes("sequence_title")
101
+ );
102
+
103
+ if (!isEveryTitlePresent) {
104
+ log.error("There is a missing `sequence_title` from at least one sequence event.");
105
+ return false;
106
+ }
107
+ }
108
+ }
109
+
110
+ if (typeof config.slot_button_mode !== "undefined" && !isSequencedAvailabilityQuery) {
111
+ log.warn(
112
+ `\`config.slot_button_mode\`. can only be used with a sequenced availability query. Setting this option on a regular availability query will have no effect.`
113
+ );
114
+ }
115
+
116
+ const slotButtonMode =
117
+ typeof config.slot_button_mode === "undefined" ? "summary" : config.slot_button_mode;
118
+
119
+ const validSlotButtonModes = ["summary", "detailed"];
120
+ const validSlotButtonMode = validSlotButtonModes.includes(slotButtonMode);
121
+
122
+ if (isSequencedAvailabilityQuery && !validSlotButtonMode) {
123
+ log.error(
124
+ `Please provide a valid \`config.slot_button_mode\`. \`${slotButtonMode}\` is not a supported value.`,
125
+ {
126
+ docsSlug: "date-time-picker/#config.slot_button_mode",
127
+ }
128
+ );
129
+ return false;
130
+ }
131
+
91
132
  const tzid = parseTimezone(options.tzid, "date-time-picker", log);
92
133
 
93
134
  const tzList = typeof config.tz_list === "undefined" ? false : config.tz_list;
@@ -170,6 +211,7 @@ export const parseDateTimePickerOptions = (options = {}) => {
170
211
  startDate,
171
212
  endDate,
172
213
  tzList,
214
+ slotButtonMode,
173
215
  },
174
216
  translations,
175
217
  log,
@@ -562,6 +562,67 @@ export const availabilitySlots = new Promise(function (resolve, reject) {
562
562
  });
563
563
  });
564
564
 
565
+ export const sequencedAvailabilitySlots = new Promise(function (resolve, reject) {
566
+ const createMockSequenceSlot = (day, startOne, endOne, startTwo, endTwo) => ({
567
+ sequence: [
568
+ {
569
+ sequence_id: "demo-one",
570
+ start: offsetTime(day, startOne),
571
+ end: offsetTime(day, endOne),
572
+ participants: [
573
+ {
574
+ sub: "acc_5b97a52a5c92eb0cc0400ffe",
575
+ },
576
+ ],
577
+ },
578
+ {
579
+ sequence_id: "demo-two",
580
+ start: offsetTime(day, startTwo),
581
+ end: offsetTime(day, endTwo),
582
+ participants: [
583
+ {
584
+ sub: "acc_5b97a52a5c92eb0cc0400ffe",
585
+ },
586
+ ],
587
+ },
588
+ ],
589
+ });
590
+
591
+ resolve({
592
+ available_slots: [
593
+ createMockSequenceSlot(1, "09:00", "09:30", "09:30", "10:00"),
594
+ createMockSequenceSlot(1, "10:00", "10:30", "10:30", "11:00"),
595
+ createMockSequenceSlot(1, "11:00", "11:30", "11:30", "12:00"),
596
+ createMockSequenceSlot(1, "12:00", "12:30", "12:30", "13:00"),
597
+ createMockSequenceSlot(1, "14:00", "14:30", "14:30", "15:00"),
598
+ createMockSequenceSlot(2, "14:30", "15:00", "15:00", "15:30"),
599
+ createMockSequenceSlot(2, "16:30", "17:00", "17:00", "17:30"),
600
+ createMockSequenceSlot(3, "08:00", "08:30", "08:30", "09:00"),
601
+ createMockSequenceSlot(3, "09:00", "09:30", "09:30", "10:00"),
602
+ createMockSequenceSlot(3, "10:00", "10:30", "10:30", "11:00"),
603
+ createMockSequenceSlot(3, "11:00", "11:30", "11:30", "12:00"),
604
+ createMockSequenceSlot(3, "12:00", "12:30", "12:30", "13:00"),
605
+ createMockSequenceSlot(3, "14:00", "14:30", "14:30", "15:00"),
606
+ createMockSequenceSlot(4, "09:00", "09:30", "09:30", "10:00"),
607
+ createMockSequenceSlot(4, "10:00", "10:30", "10:30", "11:00"),
608
+ createMockSequenceSlot(4, "12:00", "12:30", "12:30", "13:00"),
609
+ createMockSequenceSlot(4, "16:00", "16:30", "16:30", "17:00"),
610
+ createMockSequenceSlot(5, "09:00", "09:30", "09:30", "10:00"),
611
+ createMockSequenceSlot(5, "10:00", "10:30", "10:30", "11:00"),
612
+ createMockSequenceSlot(5, "11:00", "11:30", "11:30", "12:00"),
613
+ createMockSequenceSlot(5, "12:00", "12:30", "12:30", "13:00"),
614
+ createMockSequenceSlot(5, "13:30", "14:00", "14:00", "14:30"),
615
+ createMockSequenceSlot(8, "09:00", "09:30", "09:30", "10:00"),
616
+ createMockSequenceSlot(8, "10:00", "10:30", "10:30", "11:00"),
617
+ createMockSequenceSlot(8, "11:00", "11:30", "11:30", "12:00"),
618
+ createMockSequenceSlot(8, "14:00", "14:30", "14:30", "15:00"),
619
+ createMockSequenceSlot(9, "09:00", "09:30", "09:30", "10:00"),
620
+ createMockSequenceSlot(9, "10:00", "10:30", "10:30", "11:00"),
621
+ createMockSequenceSlot(9, "11:00", "11:30", "11:30", "12:00"),
622
+ ],
623
+ });
624
+ });
625
+
565
626
  export const availabilityOverlappingSlots = (
566
627
  interval = 30,
567
628
  duration = 60,
@@ -772,3 +833,39 @@ export const queryForDateTimePicker = {
772
833
  query_periods: [{ start: offsetTime(1, "09:00"), end: offsetTime(9, "11:30") }],
773
834
  required_duration: { minutes: 30 },
774
835
  };
836
+
837
+ export const sequencedQueryForDateTimePicker = {
838
+ sequence: [
839
+ {
840
+ sequence_id: "demo-one",
841
+ sequence_title: "Demo Event One",
842
+ ordinal: 1,
843
+ participants: [
844
+ {
845
+ members: [
846
+ {
847
+ sub: "acc_5b97a52a5c92eb0cc0400ffe",
848
+ },
849
+ ],
850
+ },
851
+ ],
852
+ required_duration: { minutes: 30 },
853
+ },
854
+ {
855
+ sequence_id: "demo-two",
856
+ sequence_title: "Demo Event Two",
857
+ ordinal: 2,
858
+ participants: [
859
+ {
860
+ members: [
861
+ {
862
+ sub: "acc_5b97a52a5c92eb0cc0400ffe",
863
+ },
864
+ ],
865
+ },
866
+ ],
867
+ required_duration: { minutes: 30 },
868
+ },
869
+ ],
870
+ query_periods: [{ start: offsetTime(1, "09:00"), end: offsetTime(9, "11:30") }],
871
+ };
@@ -1,5 +1,5 @@
1
1
  import React from "react";
2
- import { render, fireEvent } from "@testing-library/react";
2
+ import { render, fireEvent, within } from "@testing-library/react";
3
3
  import "@testing-library/jest-dom";
4
4
 
5
5
  import SequencedSlotButton from "../../src/js/components/DateTimePicker/SequencedSlotButton";
@@ -67,6 +67,33 @@ describe("SequencedSlotButton", () => {
67
67
  expect(button.innerHTML).toEqual(expect.stringContaining("10:30 AM - 12:00 PM (BST)"));
68
68
  });
69
69
 
70
+ it("displays a detailed view when slot button mode is set to detailed", () => {
71
+ const status = {
72
+ slotButtonMode: "detailed",
73
+ };
74
+ const { container } = render(<SequencedSlotButton slot={slot} />, {
75
+ wrapper: e => wrapper({ ...e, status }),
76
+ });
77
+
78
+ const button = container.querySelector(".DTP__time-slot");
79
+ const detailedList = container.querySelector(".DTP__detailed-slot-list");
80
+
81
+ expect(button).toBeInTheDocument();
82
+ expect(detailedList).toBeInTheDocument();
83
+
84
+ const { getAllByRole } = within(detailedList);
85
+ const items = getAllByRole("listitem");
86
+ expect(items.length).toBe(2);
87
+
88
+ const listText = items.map(item => item.textContent);
89
+ expect(listText).toMatchInlineSnapshot(`
90
+ Array [
91
+ "10:30 AM - 11:00 AM (BST)",
92
+ "11:30 AM - 12:00 PM (BST)",
93
+ ]
94
+ `);
95
+ });
96
+
70
97
  it("displays slot button with time in GMT", () => {
71
98
  const slot = [
72
99
  { sequence_id: "test-1", start: "2022-11-05T09:30:00Z", end: "2022-11-05T10:00:00Z" },
@@ -9,8 +9,9 @@ import {
9
9
  testSequencedSlotsArray,
10
10
  testDaySlots,
11
11
  testDaySequencedSlots,
12
- testSequencedSlotsAObject,
12
+ testSequencedSlotsObject,
13
13
  testQuery,
14
+ testSequencedQuery,
14
15
  } from "../dummy-data";
15
16
 
16
17
  import {
@@ -584,6 +585,7 @@ describe("Date Time Picker status reducer", () => {
584
585
  it("sets sequenced slots", () => {
585
586
  const oldState = {
586
587
  ...startingState,
588
+ query: testSequencedQuery,
587
589
  slots: {},
588
590
  availableDays: [],
589
591
  slotFetchCount: 0,
@@ -649,7 +651,7 @@ describe("Date Time Picker status reducer", () => {
649
651
  } = result;
650
652
 
651
653
  // These values have been updated
652
- expect(slots).toStrictEqual(testSequencedSlotsAObject);
654
+ expect(slots).toStrictEqual(testSequencedSlotsObject);
653
655
  expect(availableDays).toStrictEqual([
654
656
  "2021-09-29",
655
657
  "2021-09-30",
@@ -748,7 +750,7 @@ describe("Date Time Picker status reducer", () => {
748
750
  it("SELECT_DAY for sequenced slots", async () => {
749
751
  const oldState = {
750
752
  ...startingState,
751
- slots: testSequencedSlotsAObject,
753
+ slots: testSequencedSlotsObject,
752
754
  sequenced_availability: true,
753
755
  };
754
756
  const date = "2021-09-29";