Skip to content

Commit 6b8c7a5

Browse files
authored
fix: highlight ongoing parallel sessions (#50)
This correctly marks ongoing parallel sessions.
1 parent 0e31fe5 commit 6b8c7a5

File tree

10 files changed

+156
-52
lines changed

10 files changed

+156
-52
lines changed

e2e-tests/browserWithFixedTime.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import { BrowserContext, chromium } from '@playwright/test'
22
import path from 'path'
3-
import sinon from 'sinon'
43

5-
export const browserWithFixedTime = async (): Promise<BrowserContext> => {
4+
export const browserWithFixedTime = async (
5+
time?: Date,
6+
): Promise<BrowserContext> => {
67
const browser = await chromium.launch()
78
const context = await browser.newContext({
89
locale: 'no-NO',
@@ -14,11 +15,11 @@ export const browserWithFixedTime = async (): Promise<BrowserContext> => {
1415
})
1516
// Auto-enable sinon right away
1617
// and enforce our "current" date
17-
await context.addInitScript(() => {
18-
const clock = sinon.useFakeTimers()
19-
clock.setSystemTime(new Date('2022-03-11T12:00:00Z'))
20-
;(window as any).__clock = clock
21-
})
18+
await context.addInitScript(`
19+
const clock = sinon.useFakeTimers()
20+
clock.setSystemTime(${(time ?? new Date('2022-03-11T12:00:00Z')).getTime()});
21+
window.__clock = clock
22+
`)
2223

2324
return context
2425
}

e2e-tests/highlight-ongoing-session.spec.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,18 @@ test.describe('Highlight ongoing session', () => {
1212
// There should only be one highlighted row
1313
await expect(page.locator('tr.ongoing')).toHaveCount(1)
1414
})
15+
test('highlight parallel sessions', async () => {
16+
const context = await browserWithFixedTime(new Date('2022-03-11T15:00:00Z'))
17+
const page = await context.newPage()
18+
await page.goto('http://localhost:8080/')
19+
await expect(
20+
page.locator('td:has-text("Session 4") >> xpath=ancestor::tr'),
21+
).toHaveClass('ongoing')
22+
await expect(
23+
page.locator(
24+
'td:has-text("You can have sessions at the same time, too!") >> xpath=ancestor::tr',
25+
),
26+
).toHaveClass('ongoing')
27+
await expect(page.locator('tr.ongoing')).toHaveCount(2)
28+
})
1529
})

src/App.tsx

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,16 +25,6 @@ if (typeof window.matchMedia === 'function') {
2525
defaultTheme = match.matches ? Theme.dark : Theme.light
2626
}
2727

28-
export type Sessions = Record<string, string>
29-
30-
export type Schedule = {
31-
name: string
32-
day: string
33-
tz: string
34-
sessions: Sessions
35-
hidePastSessions: boolean
36-
}
37-
3828
export const App = () => {
3929
let cfg: Schedule = {
4030
name: 'ExampleConf',

src/Editor.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import type { Sessions } from 'app/App'
21
import { AddIcon, DeleteIcon } from 'app/FeatherIcons'
32
import formStyles from 'app/Form.module.css'
43
import { SessionName } from 'app/SessionName'

src/Schedule.tsx

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { ongoingSessions } from 'app/ongoingSessions'
12
import { SessionName } from 'app/SessionName'
23
import tableStyles from 'app/Table.module.css'
34
import {
@@ -99,6 +100,12 @@ export const Schedule = ({
99100
}
100101
}, [])
101102

103+
const ongoing = ongoingSessions({
104+
day: conferenceDate,
105+
sessions,
106+
tz: eventTimezoneName,
107+
})
108+
102109
return (
103110
<table className={tableStyles.Table}>
104111
<thead>
@@ -130,19 +137,10 @@ export const Schedule = ({
130137
parseInt(timeWithTrackA.split('@')[0]) -
131138
parseInt(timeWithTrackB.split('@')[0]),
132139
)
133-
.map((session, i, sessions) => {
140+
.map((session) => {
134141
const timeWithTrack = session[0]
135-
const time = parseInt(timeWithTrack.split('@')[0])
136-
137-
const nextIsOngoing =
138-
sessions[i + 1] !== undefined
139-
? startsInMinutes(
140-
userTime(sessions[i + 1][0] as unknown as number),
141-
) < 0
142-
: false
143-
144-
const isOngoing =
145-
startsInMinutes(userTime(time)) < 0 && !nextIsOngoing
142+
const time = parseInt(timeWithTrack.split('@')[0], 10)
143+
const isOngoing = ongoing[session[0]] !== undefined
146144
const isPast = startsInMinutes(userTime(time)) < 0 && !isOngoing
147145
return { session, isPast, isOngoing }
148146
})

src/ongoingSessions.spec.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { ConfDateWithSessions, ongoingSessions } from 'app/ongoingSessions'
2+
import { format } from 'date-fns'
3+
4+
const makeSchedule = (now: Date): ConfDateWithSessions => ({
5+
day: format(now, 'yyyy-MM-dd'),
6+
tz: 'Europe/Oslo',
7+
sessions: {
8+
900: 'Arrival & Breakfast',
9+
930: 'Opening & Marketplace',
10+
1030: 'Session 1',
11+
1130: 'Coffee Break',
12+
1145: 'Session 2',
13+
1245: 'Lunch Break',
14+
1430: 'Session 3',
15+
1530: 'Coffee Break',
16+
1545: 'Session 4',
17+
'1545@Main hall': `You can have sessions at the same time, too!`,
18+
1645: 'Coffee Break',
19+
1700: 'Closing & Retro',
20+
1730: 'Dinner Break',
21+
1900: 'Evening Activities',
22+
},
23+
})
24+
25+
describe('ongoingSessions()', () => {
26+
it('should mark one ongoing session', () => {
27+
const now = new Date('2022-03-11T13:00:00+01:00')
28+
expect(ongoingSessions(makeSchedule(now), now)).toEqual({
29+
1245: 'Lunch Break',
30+
})
31+
})
32+
33+
it('should mark to parallel ongoing sessions', () => {
34+
const now = new Date('2022-03-11T16:00:00+01:00')
35+
expect(ongoingSessions(makeSchedule(now), now)).toEqual({
36+
1545: 'Session 4',
37+
'1545@Main hall': `You can have sessions at the same time, too!`,
38+
})
39+
})
40+
})

src/ongoingSessions.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { toUTCTime } from 'app/toUTCTime'
2+
3+
export type ConfDate = Pick<Schedule, 'day' | 'tz'>
4+
export type ConfDateWithSessions = Pick<Schedule, 'sessions'> & ConfDate
5+
6+
const timeWithTrackToUTC = (timeWithTrack: string, schedule: ConfDate): Date =>
7+
toUTCTime({
8+
conferenceDate: schedule.day,
9+
eventTimezoneName: schedule.tz,
10+
})(parseInt(timeWithTrack.split('@')[0], 10))
11+
12+
const isBeforeNow = (
13+
[timeWithTrack]: [string, string],
14+
now: Date,
15+
schedule: ConfDate,
16+
): boolean => {
17+
const time = timeWithTrackToUTC(timeWithTrack, schedule)
18+
return time.getTime() < now.getTime()
19+
}
20+
21+
export const ongoingSessions = (
22+
schedule: ConfDateWithSessions,
23+
now = new Date(),
24+
): Sessions => {
25+
return (
26+
Object.entries(schedule.sessions)
27+
.sort(
28+
([timeWithTrackA], [timeWithTrackB]) =>
29+
parseInt(timeWithTrackA.split('@')[0]) -
30+
parseInt(timeWithTrackB.split('@')[0]),
31+
)
32+
// Filter out future sessions
33+
.filter(
34+
([timeWithTrack, session]) =>
35+
timeWithTrackToUTC(timeWithTrack, schedule).getTime() < now.getTime(),
36+
)
37+
// Check of next is ongoing
38+
.filter(([timeWithTrack], i, sessions) => {
39+
const time = timeWithTrackToUTC(timeWithTrack, schedule)
40+
// Find the next session that does not start at the same time
41+
const next = sessions.find(
42+
([nextTimeWithTrack], j) =>
43+
j > i &&
44+
timeWithTrackToUTC(nextTimeWithTrack, schedule).getTime() >
45+
time.getTime(),
46+
)
47+
if (next === undefined) return true // No next session found, so this one must be the last session and therefore it is ongoing
48+
return !isBeforeNow(next, now, schedule)
49+
})
50+
.reduce((ongoing, [k, v]) => ({ ...ongoing, [k]: v }), {})
51+
)
52+
}

src/toUTCTime.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { zonedTimeToUtc } from 'date-fns-tz'
2+
3+
export const toUTCTime =
4+
({
5+
conferenceDate,
6+
eventTimezoneName,
7+
}: {
8+
conferenceDate: string
9+
eventTimezoneName: string
10+
}) =>
11+
(time: number): Date => {
12+
const minutes = time % 100
13+
const hours = (time - minutes) / 100
14+
return zonedTimeToUtc(
15+
`${conferenceDate} ${`${hours}`.padStart(2, '0')}:${`${minutes}`.padStart(
16+
2,
17+
'0',
18+
)}:00.000`,
19+
eventTimezoneName,
20+
)
21+
}

src/types.d.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,13 @@ interface ImportMeta {
2828
PUBLIC_MANIFEST_BACKGROUND_COLOR: string
2929
}
3030
}
31+
32+
type Sessions = Record<string, string>
33+
34+
type Schedule = {
35+
name: string
36+
day: string
37+
tz: string
38+
sessions: Sessions
39+
hidePastSessions: boolean
40+
}

src/useIcalExport.ts

Lines changed: 1 addition & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,7 @@
1-
import type { Schedule } from 'app/App'
21
import { formatSessionName } from 'app/SessionName'
3-
import { zonedTimeToUtc } from 'date-fns-tz'
2+
import { toUTCTime } from 'app/toUTCTime'
43
import { createEvents } from 'ics'
54

6-
export const toUTCTime =
7-
({
8-
conferenceDate,
9-
eventTimezoneName,
10-
}: {
11-
conferenceDate: string
12-
eventTimezoneName: string
13-
}) =>
14-
(time: number): Date => {
15-
const minutes = time % 100
16-
const hours = (time - minutes) / 100
17-
return zonedTimeToUtc(
18-
`${conferenceDate} ${`${hours}`.padStart(2, '0')}:${`${minutes}`.padStart(
19-
2,
20-
'0',
21-
)}:00.000`,
22-
eventTimezoneName,
23-
)
24-
}
25-
265
export const useIcalExport = (schedule: Schedule) => {
276
return (): void => {
287
const utcTime = toUTCTime({

0 commit comments

Comments
 (0)