|
| 1 | +import { adapter, EventBuilder } from 'test/utils/scheduler'; |
| 2 | +import { sortEventOccurrences } from './sortEventOccurrences'; |
| 3 | + |
| 4 | +describe('sortEventOccurrences', () => { |
| 5 | + describe('basic sorting', () => { |
| 6 | + it('should return empty array when given empty array', () => { |
| 7 | + expect(sortEventOccurrences([], adapter)).toEqual([]); |
| 8 | + }); |
| 9 | + |
| 10 | + it('should return single occurrence unchanged', () => { |
| 11 | + const occurrence = EventBuilder.new().singleDay('2024-01-15T10:00:00').toOccurrence(); |
| 12 | + expect(sortEventOccurrences([occurrence], adapter)).toEqual([occurrence]); |
| 13 | + }); |
| 14 | + |
| 15 | + it('should sort occurrences by start date (earliest first)', () => { |
| 16 | + const first = EventBuilder.new().id('first').singleDay('2024-01-15T08:00:00').toOccurrence(); |
| 17 | + const second = EventBuilder.new() |
| 18 | + .id('second') |
| 19 | + .singleDay('2024-01-15T10:00:00') |
| 20 | + .toOccurrence(); |
| 21 | + const third = EventBuilder.new().id('third').singleDay('2024-01-15T14:00:00').toOccurrence(); |
| 22 | + |
| 23 | + expect(sortEventOccurrences([third, first, second], adapter)).to.deep.equal([ |
| 24 | + first, |
| 25 | + second, |
| 26 | + third, |
| 27 | + ]); |
| 28 | + }); |
| 29 | + }); |
| 30 | + |
| 31 | + describe('same start date tiebreaker', () => { |
| 32 | + it('should sort by end date (later end first) when start dates are equal', () => { |
| 33 | + const short = EventBuilder.new() |
| 34 | + .id('short') |
| 35 | + .singleDay('2024-01-15T10:00:00', 30) |
| 36 | + .toOccurrence(); |
| 37 | + const long = EventBuilder.new() |
| 38 | + .id('long') |
| 39 | + .singleDay('2024-01-15T10:00:00', 120) |
| 40 | + .toOccurrence(); |
| 41 | + |
| 42 | + expect(sortEventOccurrences([short, long], adapter)).to.deep.equal([long, short]); |
| 43 | + }); |
| 44 | + |
| 45 | + it('should handle multiple events with same start but different ends', () => { |
| 46 | + const short = EventBuilder.new() |
| 47 | + .id('short') |
| 48 | + .singleDay('2024-01-15T10:00:00', 30) |
| 49 | + .toOccurrence(); |
| 50 | + const medium = EventBuilder.new() |
| 51 | + .id('medium') |
| 52 | + .singleDay('2024-01-15T10:00:00') |
| 53 | + .toOccurrence(); |
| 54 | + const long = EventBuilder.new() |
| 55 | + .id('long') |
| 56 | + .singleDay('2024-01-15T10:00:00', 120) |
| 57 | + .toOccurrence(); |
| 58 | + |
| 59 | + expect(sortEventOccurrences([short, medium, long], adapter)).to.deep.equal([ |
| 60 | + long, |
| 61 | + medium, |
| 62 | + short, |
| 63 | + ]); |
| 64 | + }); |
| 65 | + }); |
| 66 | + |
| 67 | + describe('all-day events', () => { |
| 68 | + it('should sort all-day events by start of day', () => { |
| 69 | + const day1 = EventBuilder.new() |
| 70 | + .id('day1') |
| 71 | + .span('2024-01-15T00:00:00', '2024-01-15T23:59:59') |
| 72 | + .allDay(true) |
| 73 | + .toOccurrence(); |
| 74 | + const day2 = EventBuilder.new() |
| 75 | + .id('day2') |
| 76 | + .span('2024-01-16T00:00:00', '2024-01-16T23:59:59') |
| 77 | + .allDay(true) |
| 78 | + .toOccurrence(); |
| 79 | + |
| 80 | + expect(sortEventOccurrences([day2, day1], adapter)).to.deep.equal([day1, day2]); |
| 81 | + }); |
| 82 | + |
| 83 | + it('should sort all-day events with same start by end date (longer first)', () => { |
| 84 | + const singleDay = EventBuilder.new() |
| 85 | + .id('single') |
| 86 | + .span('2024-01-15T00:00:00', '2024-01-15T23:59:59') |
| 87 | + .allDay(true) |
| 88 | + .toOccurrence(); |
| 89 | + const multiDay = EventBuilder.new() |
| 90 | + .id('multi') |
| 91 | + .span('2024-01-15T00:00:00', '2024-01-17T23:59:59') |
| 92 | + .allDay(true) |
| 93 | + .toOccurrence(); |
| 94 | + |
| 95 | + expect(sortEventOccurrences([singleDay, multiDay], adapter)).to.deep.equal([ |
| 96 | + multiDay, |
| 97 | + singleDay, |
| 98 | + ]); |
| 99 | + }); |
| 100 | + }); |
| 101 | + |
| 102 | + describe('mixed timed and all-day events', () => { |
| 103 | + it('should correctly sort mixed all-day and timed events on the same day', () => { |
| 104 | + const allDay = EventBuilder.new() |
| 105 | + .id('allDay') |
| 106 | + .span('2024-01-15T00:00:00', '2024-01-15T23:59:59') |
| 107 | + .allDay(true) |
| 108 | + .toOccurrence(); |
| 109 | + const timed = EventBuilder.new().id('timed').singleDay('2024-01-15T10:00:00').toOccurrence(); |
| 110 | + |
| 111 | + // All-day event starts at midnight (start of day), so it should come first |
| 112 | + expect(sortEventOccurrences([timed, allDay], adapter)).to.deep.equal([allDay, timed]); |
| 113 | + }); |
| 114 | + |
| 115 | + it('should sort all-day event after timed event that starts earlier in the day', () => { |
| 116 | + const allDay = EventBuilder.new() |
| 117 | + .id('allDay') |
| 118 | + .span('2024-01-16T00:00:00', '2024-01-16T23:59:59') |
| 119 | + .allDay(true) |
| 120 | + .toOccurrence(); |
| 121 | + const timedEarlier = EventBuilder.new() |
| 122 | + .id('timed') |
| 123 | + .singleDay('2024-01-15T10:00:00') |
| 124 | + .toOccurrence(); |
| 125 | + |
| 126 | + expect(sortEventOccurrences([allDay, timedEarlier], adapter)).to.deep.equal([ |
| 127 | + timedEarlier, |
| 128 | + allDay, |
| 129 | + ]); |
| 130 | + }); |
| 131 | + }); |
| 132 | + |
| 133 | + describe('edge cases', () => { |
| 134 | + it('should maintain stable sort for identical occurrences', () => { |
| 135 | + // Two events with exactly the same start and end |
| 136 | + const event1 = EventBuilder.new() |
| 137 | + .id('event1') |
| 138 | + .singleDay('2024-01-15T10:00:00') |
| 139 | + .toOccurrence(); |
| 140 | + const event2 = EventBuilder.new() |
| 141 | + .id('event2') |
| 142 | + .singleDay('2024-01-15T10:00:00') |
| 143 | + .toOccurrence(); |
| 144 | + |
| 145 | + // Should maintain original order when equal |
| 146 | + expect(sortEventOccurrences([event1, event2], adapter)).to.deep.equal([event1, event2]); |
| 147 | + }); |
| 148 | + |
| 149 | + it('should handle events spanning multiple days', () => { |
| 150 | + const multiDay = EventBuilder.new() |
| 151 | + .id('multiDay') |
| 152 | + .span('2024-01-15T10:00:00', '2024-01-17T14:00:00') |
| 153 | + .toOccurrence(); |
| 154 | + const singleDay = EventBuilder.new() |
| 155 | + .id('singleDay') |
| 156 | + .singleDay('2024-01-15T12:00:00') |
| 157 | + .toOccurrence(); |
| 158 | + |
| 159 | + // multiDay starts earlier (10:00), so it should come first |
| 160 | + expect(sortEventOccurrences([singleDay, multiDay], adapter)).to.deep.equal([ |
| 161 | + multiDay, |
| 162 | + singleDay, |
| 163 | + ]); |
| 164 | + }); |
| 165 | + |
| 166 | + it('should handle events on different days', () => { |
| 167 | + const monday = EventBuilder.new() |
| 168 | + .id('monday') |
| 169 | + .singleDay('2024-01-15T10:00:00') |
| 170 | + .toOccurrence(); |
| 171 | + const wednesday = EventBuilder.new() |
| 172 | + .id('wednesday') |
| 173 | + .singleDay('2024-01-17T10:00:00') |
| 174 | + .toOccurrence(); |
| 175 | + const tuesday = EventBuilder.new() |
| 176 | + .id('tuesday') |
| 177 | + .singleDay('2024-01-16T10:00:00') |
| 178 | + .toOccurrence(); |
| 179 | + |
| 180 | + expect(sortEventOccurrences([wednesday, monday, tuesday], adapter)).to.deep.equal([ |
| 181 | + monday, |
| 182 | + tuesday, |
| 183 | + wednesday, |
| 184 | + ]); |
| 185 | + }); |
| 186 | + |
| 187 | + it('should not mutate the original array', () => { |
| 188 | + const early = EventBuilder.new().id('early').singleDay('2024-01-15T08:00:00').toOccurrence(); |
| 189 | + const late = EventBuilder.new().id('late').singleDay('2024-01-15T14:00:00').toOccurrence(); |
| 190 | + |
| 191 | + const original = [late, early]; |
| 192 | + |
| 193 | + expect(sortEventOccurrences(original, adapter)).to.deep.equal([early, late]); |
| 194 | + expect(original).to.deep.equal([late, early]); |
| 195 | + }); |
| 196 | + }); |
| 197 | +}); |
0 commit comments