@@ -123,7 +123,9 @@ async function gotoMyMeetings(page: Page): Promise<{ viewerLfid: string }> {
123123 const viewer = { name : 'E2E Viewer' , username : viewerLfid , email : 'viewer-e2e@example.com' } ;
124124
125125 await page . route ( '**/api/user/meetings*' , ( route ) => fulfillJson ( route , [ upcomingMeeting ( 'link-up-1' , 'Clickable Upcoming' , viewer , { organizer : true } ) ] ) ) ;
126- await page . route ( '**/api/user/past-meetings*' , ( route ) => fulfillJson ( route , [ pastMeeting ( 'link-past-1' , 'Clickable Past' , viewer , { organizer : true } ) ] ) ) ;
126+ await page . route ( '**/api/user/past-meetings*' , ( route ) =>
127+ fulfillJson ( route , [ pastMeeting ( 'link-past-1' , 'Clickable Past' , viewer , { organizer : true , project_slug : 'e2e-project' } ) ] )
128+ ) ;
127129
128130 await page . goto ( MEETINGS_URL , { waitUntil : 'domcontentloaded' } ) ;
129131 skipWhenAuthMissing ( page ) ;
@@ -168,7 +170,7 @@ test.describe('Meeting card — clickable title and date/time chip', () => {
168170 await chipPopup . close ( ) ;
169171 } ) ;
170172
171- test ( 'past card: title and date chip navigate in-app (routerLink), not a new tab ' , async ( { page } ) => {
173+ test ( 'past card: organizer (Manage role) title and date chip navigate in-app to the admin details page ' , async ( { page } ) => {
172174 await gotoMyMeetings ( page ) ;
173175 await pastTab ( page ) . click ( ) ;
174176
@@ -178,20 +180,66 @@ test.describe('Meeting card — clickable title and date/time chip', () => {
178180 const title = pastCard . getByTestId ( 'meeting-title' ) ;
179181 const chip = pastCard . getByTestId ( 'meeting-datetime' ) ;
180182
183+ // Fixture is seeded with organizer: true and is_foundation: false — Manage-role viewers
184+ // route to the admin details page, not the public join page (see #2251). The fixture's
185+ // project_slug must also survive onto the link so projectQueryParamGuard (which fails open
186+ // and skips seeding project/foundation context when `?project=` is absent) has what it needs.
181187 await expect ( title ) . toHaveJSProperty ( 'tagName' , 'A' ) ;
182- await expect ( title ) . toHaveAttribute ( 'href' , '/meetings/link-past-1' ) ;
188+ await expect ( title ) . toHaveAttribute ( 'href' , '/project/ meetings/link-past-1/details?project=e2e-project ' ) ;
183189 await expect ( title ) . not . toHaveAttribute ( 'target' , '_blank' ) ;
184190
185191 await expect ( chip ) . toHaveJSProperty ( 'tagName' , 'A' ) ;
186- await expect ( chip ) . toHaveAttribute ( 'href' , '/meetings/link-past-1' ) ;
192+ await expect ( chip ) . toHaveAttribute ( 'href' , '/project/ meetings/link-past-1/details?project=e2e-project ' ) ;
187193 await expect ( chip ) . not . toHaveAttribute ( 'target' , '_blank' ) ;
188194
189195 // Activate the title anchor — same-tab SPA nav, so only one of the two anchors is clicked here
190196 // (clicking both would require navigating back and re-deriving the past-tab state in between).
191197 // Chip's routerLink is already proven equivalent via the identical href assertion above.
192198 await title . click ( ) ;
193199 await page . waitForURL ( ( url ) => ! / ^ \/ m e e t i n g s \/ ? $ / . test ( url . pathname ) ) ;
194- expect ( page . url ( ) ) . toContain ( '/meetings/link-past-1' ) ;
200+ expect ( page . url ( ) ) . toContain ( '/project/meetings/link-past-1/details' ) ;
201+ expect ( page . url ( ) ) . toContain ( 'project=e2e-project' ) ;
202+ } ) ;
203+
204+ test ( 'past card: non-organizer (View role) title and date chip navigate to the public join/summary page' , async ( { page } ) => {
205+ await seedMeLensCookie ( page ) ;
206+ await stubMeLensContext ( page ) ;
207+
208+ await page . goto ( '/' , { waitUntil : 'domcontentloaded' } ) ;
209+ skipWhenAuthMissing ( page ) ;
210+
211+ const viewerLfid = await readViewerLfid ( page ) ;
212+ if ( ! viewerLfid ) {
213+ test . skip ( true , 'Could not resolve the signed-in LFID from the SSR auth state' ) ;
214+ return ;
215+ }
216+
217+ const viewer = { name : 'E2E Viewer' , username : viewerLfid , email : 'viewer-e2e@example.com' } ;
218+ const otherCreator = { name : 'Other Organizer' , username : 'other-organizer-e2e' , email : 'other-organizer-e2e@example.com' } ;
219+
220+ await page . route ( '**/api/user/meetings*' , ( route ) => fulfillJson ( route , [ ] ) ) ;
221+ await page . route ( '**/api/user/past-meetings*' , ( route ) =>
222+ fulfillJson ( route , [ pastMeeting ( 'link-past-view-1' , 'Clickable Past — View Role' , otherCreator , { organizer : false } ) ] )
223+ ) ;
224+
225+ await page . goto ( MEETINGS_URL , { waitUntil : 'domcontentloaded' } ) ;
226+ skipWhenAuthMissing ( page ) ;
227+ await expect ( page ) . not . toHaveURL ( / a u t h 0 \. c o m / ) ;
228+ if ( ! page . url ( ) . includes ( '/meetings' ) ) {
229+ test . skip ( true , 'Me lens is not available for this user — /meetings redirected away' ) ;
230+ return ;
231+ }
232+
233+ await pastTab ( page ) . click ( ) ;
234+
235+ const pastCard = card ( page , 'link-past-view-1' ) ;
236+ await expect ( pastCard ) . toBeVisible ( ) ;
237+
238+ const title = pastCard . getByTestId ( 'meeting-title' ) ;
239+ const chip = pastCard . getByTestId ( 'meeting-datetime' ) ;
240+
241+ await expect ( title ) . toHaveAttribute ( 'href' , '/meetings/link-past-view-1' ) ;
242+ await expect ( chip ) . toHaveAttribute ( 'href' , '/meetings/link-past-view-1' ) ;
195243 } ) ;
196244
197245 test ( 'clicking inner card actions does not trigger a details navigation or open a new tab' , async ( { page, context } ) => {
0 commit comments