Skip to content

Commit b70f549

Browse files
Matus Kasakclaude
andcommitted
Open DiscoJuice popup reliably (poll until bound) instead of fixed delay
The fixed 250ms click raced against async AAI script loading: when it fired before DiscoJuice bound, the popup never opened (CI showed div.discojuice staying display:none). Poll (bounded, outside the Angular zone so it never stalls SSR / whenStable) until DiscoJuice has created its popup markup, then click. Cancel the timer on destroy. Also make the e2e loginViaForm wait for the popup to be visible before closing it, instead of asserting mere existence. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 32f8e17 commit b70f549

2 files changed

Lines changed: 49 additions & 9 deletions

File tree

cypress/support/commands.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -101,9 +101,9 @@ Cypress.Commands.add('login', login);
101101
*/
102102
function loginViaForm(email: string, password: string): void {
103103
// The CLARIN/LINDAT DiscoJuice federated-login popup auto-opens on /login and covers the local
104-
// password form, so close it first (if present) before filling in the local-account credentials.
105-
cy.wait(500);
106-
cy.get('.discojuice_close').should('exist').click();
104+
// password form. It opens only once the AAI scripts have loaded and DiscoJuice has bound, so wait
105+
// until it is actually visible, then close it before filling in the local-account credentials.
106+
cy.get('.discojuice_close', { timeout: 20000 }).should('be.visible').click();
107107

108108
// Enter email
109109
cy.get('[data-test="email"]').should('be.visible').type(email);

src/app/shared/log-in/methods/password/log-in-password.component.ts

Lines changed: 46 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import {
55
import {
66
Component,
77
Inject,
8+
NgZone,
9+
OnDestroy,
810
OnInit,
911
PLATFORM_ID,
1012
} from '@angular/core';
@@ -79,7 +81,12 @@ export const SHOW_DISCOJUICE_POPUP_CACHE_NAME = 'SHOW_DISCOJUICE_POPUP';
7981
TranslateModule,
8082
],
8183
})
82-
export class LogInPasswordComponent implements OnInit {
84+
export class LogInPasswordComponent implements OnInit, OnDestroy {
85+
86+
/**
87+
* Handle of the pending DiscoJuice popup-open timer so it can be cancelled on destroy.
88+
*/
89+
private discoJuiceTimer: ReturnType<typeof setTimeout> = null;
8390

8491
/**
8592
* The authentication method data.
@@ -143,6 +150,7 @@ export class LogInPasswordComponent implements OnInit {
143150
protected authorizationService: AuthorizationDataService,
144151
@Inject(PLATFORM_ID) protected platformId: object,
145152
protected storage: CookieService,
153+
protected zone: NgZone,
146154
) {
147155
this.authMethod = injectedAuthMethodModel;
148156
}
@@ -254,14 +262,46 @@ export class LogInPasswordComponent implements OnInit {
254262
}
255263

256264
/**
257-
* Show DiscoJuice login modal using javascript functions. The timeout must be set because of angular component
258-
* lifecycle. Discojuice won't be showed up without timeout.
265+
* Trigger the DiscoJuice popup by programmatically clicking the sign-on link that the AAI script
266+
* binds DiscoJuice to (rendered in the CLARIN top navbar).
267+
*
268+
* The AAI/DiscoJuice scripts are loaded asynchronously (and in parallel) by the navbar component,
269+
* so on a cold load DiscoJuice may not have bound its click handler by the time this component
270+
* initialises. Clicking too early is a silent no-op and the popup never opens. We therefore poll
271+
* (bounded) until DiscoJuice has created its popup markup (`div.discojuice`, built when it binds
272+
* to the sign-on link) and only then click, which reliably opens the popup regardless of how long
273+
* the scripts take to load.
274+
*
275+
* The polling runs OUTSIDE the Angular zone so it never keeps the application unstable (which
276+
* would stall SSR rendering and `fixture.whenStable()` in tests). A programmatic `click()` also
277+
* bypasses the sign-on link's `pointer-events: none` guard (that guard only blocks real pointer
278+
* input), so the popup opens even while the link is still visually disabled for the mouse.
259279
* @private
260280
*/
261281
private popUpDiscoJuiceLogin() {
262-
setTimeout(() => {
263-
document?.getElementById('clarin-signon-discojuice')?.click();
264-
}, 250);
282+
const maxAttempts = 40; // ~10s at 250ms intervals — well beyond a normal script load
283+
let attempts = 0;
284+
this.zone.runOutsideAngular(() => {
285+
const tryOpen = () => {
286+
const signOnLink = document?.getElementById('clarin-signon-discojuice');
287+
// `div.discojuice` is created (hidden) when DiscoJuice binds to the sign-on link, so its
288+
// presence means the click handler is wired and a click will actually open the popup.
289+
if (signOnLink && document?.querySelector('div.discojuice')) {
290+
signOnLink.click();
291+
return;
292+
}
293+
if (++attempts < maxAttempts) {
294+
this.discoJuiceTimer = setTimeout(tryOpen, 250);
295+
}
296+
};
297+
this.discoJuiceTimer = setTimeout(tryOpen, 250);
298+
});
299+
}
300+
301+
ngOnDestroy(): void {
302+
if (this.discoJuiceTimer) {
303+
clearTimeout(this.discoJuiceTimer);
304+
}
265305
}
266306

267307
/**

0 commit comments

Comments
 (0)