diff --git a/hugo-apps/src/app-space/AppSpace.vue b/hugo-apps/src/app-space/AppSpace.vue index 63ae089e9..e894b0f04 100644 --- a/hugo-apps/src/app-space/AppSpace.vue +++ b/hugo-apps/src/app-space/AppSpace.vue @@ -138,7 +138,12 @@ onMounted(async () => { activeTheme.value = theme } - const eidParam = params.get('eventId') + // Read `eventId` case-insensitively: users land on links like + // /app-space/?eventid=... (lowercase) and URLSearchParams.get is case-sensitive. + let eidParam: string | null = null + for (const [k, v] of params) { + if (k.toLowerCase() === 'eventid') { eidParam = v; break } + } if (eidParam) eventId.value = Number(eidParam) const data = await loadData() diff --git a/hugo-apps/src/app-space/__tests__/AppSpace.eventid-case.test.ts b/hugo-apps/src/app-space/__tests__/AppSpace.eventid-case.test.ts new file mode 100644 index 000000000..f6084b800 --- /dev/null +++ b/hugo-apps/src/app-space/__tests__/AppSpace.eventid-case.test.ts @@ -0,0 +1,54 @@ +// @vitest-environment happy-dom +import { describe, it, expect, vi, beforeEach } from 'vitest' +import { mount, flushPromises } from '@vue/test-utils' + +// Regression: the `eventId` URL query param must be read case-insensitively. +// Users were hitting /app-space/?eventid=... (lowercase) and getting the +// default event because URLSearchParams.get('eventId') is case-sensitive. + +vi.mock('../useRealtimeProgress', async () => { + const { ref } = await import('vue') + return { useRealtimeProgress: () => ({ lastCompletion: ref(null), connected: ref(false) }) } +}) + +import AppSpace from '../AppSpace.vue' + +const eventBody = { + eventId: 10000015, + eventName: 'Case Insensitive Event', + eventDescription: '
ok
', + eventType: 'DEVTOBERFEST', + authenticated: false, + type: 'COMPLEX', + paths: [{ + id: 1, title: 'Track A', description: '', items: [ + { imsId: 1, title: 'Tut 1', type: 'TUTORIAL', status: '', progress: 0, experience: '', timeToComplete: 0, url: '/tutorials/x.html', description: '' } + ] + }] +} + +describe('AppSpace eventId query param is case-insensitive', () => { + beforeEach(() => { + vi.restoreAllMocks() + }) + + for (const key of ['eventId', 'eventid', 'EVENTID', 'EventId']) { + it(`reads the event id from ?${key}=10000015`, async () => { + window.history.replaceState({}, '', `/app-space/?${key}=10000015`) + const fetchSpy = vi.fn(async (input: unknown) => { + const url = String(input) + if (url.includes('getAppSpaceProgress')) return { ok: true, status: 200, json: async () => eventBody } + return { ok: false, status: 404, json: async () => ({}) } + }) + vi.stubGlobal('fetch', fetchSpy as unknown as typeof fetch) + + mount(AppSpace) + await flushPromises() + + // The backend call must carry the id read from the URL, not the default 0. + const urls = fetchSpy.mock.calls.map((c) => String(c[0])) + expect(urls.some((u) => u.includes('eventLegacyId=10000015'))).toBe(true) + expect(urls.some((u) => u.includes('eventLegacyId=0'))).toBe(false) + }) + } +})