fix(02): show owner name / Family in event popover footer
Backend: - expand.ts: add ownerName: string | null to CalendarOccurrence interface and expandOccurrences() signature; thread it onto every emitted occurrence. - events.ts: SELECT users.displayName as ownerName in the join; pass it to expandOccurrences(). Frontend: - client.ts: add ownerName: string | null to CalendarOccurrence. - EventDetailPopover.tsx: render isShared ? 'Family' : (ownerName ?? calendarName) in the footer instead of calendarName. Tests: - expand.test.ts: pass ownerName to all expandOccurrences() calls; assert ownerName is carried onto occurrences in the DST test. - events.test.ts: add ownerName to mock rows; assert ownerName present on occurrences; add ownerName assertion to timed-recurring test. - EventDetailPopover.test.tsx: add ownerName to fixtures; split "calendar name in footer" into three targeted tests covering personal-with-owner, shared→Family, and null-owner fallback.
This commit is contained in:
@@ -46,6 +46,7 @@ describe('expandOccurrences', () => {
|
||||
1, // calendarId
|
||||
'My Calendar', // calendarName
|
||||
1, // ownerUserId
|
||||
'Alice', // ownerName
|
||||
'#4A90D9', // color
|
||||
false, // isShared
|
||||
)
|
||||
@@ -58,6 +59,8 @@ describe('expandOccurrences', () => {
|
||||
// after DST: '...T10:00:00-04:00[America/New_York]'. Both include the IANA bracket.
|
||||
for (const occ of occurrences) {
|
||||
expect(occ.allDay).toBe(false)
|
||||
// ownerName must be threaded through to every occurrence
|
||||
expect(occ.ownerName).toBe('Alice')
|
||||
// start must be IANA-annotated: '2026-03-01T10:00:00-05:00[America/New_York]'
|
||||
expect(occ.start).toMatch(/T10:00:00/)
|
||||
// Must include IANA bracket — offset-only strings fail Temporal.ZonedDateTime.from()
|
||||
@@ -96,6 +99,7 @@ describe('expandOccurrences', () => {
|
||||
1,
|
||||
'My Calendar',
|
||||
1,
|
||||
null, // ownerName
|
||||
'#4A90D9',
|
||||
false,
|
||||
)
|
||||
@@ -127,6 +131,7 @@ describe('expandOccurrences', () => {
|
||||
1,
|
||||
'My Calendar',
|
||||
1,
|
||||
null, // ownerName
|
||||
'#4A90D9',
|
||||
false,
|
||||
)
|
||||
@@ -156,6 +161,7 @@ describe('expandOccurrences', () => {
|
||||
1,
|
||||
'My Calendar',
|
||||
1,
|
||||
null, // ownerName
|
||||
'#4A90D9',
|
||||
false,
|
||||
)
|
||||
@@ -196,6 +202,7 @@ describe('expandOccurrences', () => {
|
||||
1,
|
||||
'My Calendar',
|
||||
1,
|
||||
null, // ownerName
|
||||
'#4A90D9',
|
||||
false,
|
||||
)
|
||||
|
||||
@@ -70,19 +70,22 @@ describe('GET /api/events', () => {
|
||||
expect(res.status).toBe(400)
|
||||
})
|
||||
|
||||
it('returns occurrences with color and isShared fields for valid window', async () => {
|
||||
it('returns occurrences with color, isShared, and ownerName fields for valid window', async () => {
|
||||
const { app } = await import('../../src/index.js')
|
||||
const res = await app.request('/api/events?start=2026-06-01&end=2026-07-01')
|
||||
expect(res.status).toBe(200)
|
||||
const body = await res.json() as { occurrences: Array<{ color: string; isShared: boolean }> }
|
||||
const body = await res.json() as { occurrences: Array<{ color: string; isShared: boolean; ownerName: string | null }> }
|
||||
expect(body).toHaveProperty('occurrences')
|
||||
expect(Array.isArray(body.occurrences)).toBe(true)
|
||||
// Each occurrence must carry color + isShared (may be empty array if DB is mocked empty)
|
||||
// Each occurrence must carry color + isShared + ownerName (may be empty array if DB is mocked empty)
|
||||
for (const occ of body.occurrences) {
|
||||
expect(occ).toHaveProperty('color')
|
||||
expect(typeof occ.color).toBe('string')
|
||||
expect(occ).toHaveProperty('isShared')
|
||||
expect(typeof occ.isShared).toBe('boolean')
|
||||
expect(occ).toHaveProperty('ownerName')
|
||||
// ownerName is string | null — both are valid
|
||||
expect(occ.ownerName === null || typeof occ.ownerName === 'string').toBe(true)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -100,6 +103,7 @@ describe('GET /api/events', () => {
|
||||
isShared: false,
|
||||
userId: 1,
|
||||
userColor: '#4A90D9',
|
||||
ownerName: 'Alice',
|
||||
},
|
||||
]
|
||||
|
||||
@@ -107,7 +111,7 @@ describe('GET /api/events', () => {
|
||||
const res = await app.request('/api/events?start=2026-06-01&end=2026-07-01')
|
||||
expect(res.status).toBe(200)
|
||||
|
||||
const body = await res.json() as { occurrences: Array<{ uid: string; start: string; allDay: boolean }> }
|
||||
const body = await res.json() as { occurrences: Array<{ uid: string; start: string; allDay: boolean; ownerName: string | null }> }
|
||||
expect(body.occurrences.length).toBeGreaterThan(0)
|
||||
|
||||
// All returned occurrences must be inside [2026-06-01, 2026-07-01)
|
||||
@@ -118,6 +122,8 @@ describe('GET /api/events', () => {
|
||||
const startDate = occ.start.slice(0, 10) // 'YYYY-MM-DD'
|
||||
expect(startDate >= '2026-06-01').toBe(true)
|
||||
expect(startDate < '2026-07-01').toBe(true)
|
||||
// ownerName from the mock row must be threaded through
|
||||
expect(occ.ownerName).toBe('Alice')
|
||||
}
|
||||
})
|
||||
|
||||
@@ -135,6 +141,7 @@ describe('GET /api/events', () => {
|
||||
isShared: false,
|
||||
userId: 1,
|
||||
userColor: '#50C878',
|
||||
ownerName: 'Bob',
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user