mirror of
https://github.com/go-gitea/gitea.git
synced 2026-04-13 09:35:13 +02:00
The stopwatch navbar icon and popup were only rendered by the server when a stopwatch was already active at page load. If a tab was opened before the stopwatch started, `initStopwatch()` found no `.active-stopwatch` element in the DOM, returned early, and never registered a SharedWorker listener. As a result the WebSocket push from the stopwatch notifier had nowhere to land and the icon never appeared. Fix by always rendering both the icon anchor and the popup skeleton in the navbar (hidden with `tw-hidden` when no stopwatch is active). `initStopwatch()` can now set up the SharedWorker in every tab, and `updateStopwatchData` can call `showElem`/`hideElem` as stopwatch state changes arrive in real time. Also add `onShow` to `createTippy` so the popup content is re-cloned from the (JS-updated) original each time the tooltip opens, keeping it current even when the stopwatch was started after page load. Add a new e2e test (`stopwatch appears via real-time push`) that verifies the icon appears after `apiStartStopwatch` is called with the page already loaded.
106 lines
4.1 KiB
TypeScript
106 lines
4.1 KiB
TypeScript
import {test, expect} from '@playwright/test';
|
|
import {loginUser, baseUrl, apiUserHeaders, apiCreateUser, apiDeleteUser, apiCreateRepo, apiCreateIssue, apiStartStopwatch} from './utils.ts';
|
|
|
|
// These tests rely on a short EVENT_SOURCE_UPDATE_TIME in the e2e server config.
|
|
test.describe('events', () => {
|
|
test('notification count', async ({page, request}) => {
|
|
const id = `ev-notif-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
|
|
const owner = `${id}-owner`;
|
|
const commenter = `${id}-commenter`;
|
|
const repoName = id;
|
|
|
|
await Promise.all([apiCreateUser(request, owner), apiCreateUser(request, commenter)]);
|
|
|
|
// Create repo and login in parallel — repo is needed for the issue, login for the event stream
|
|
await Promise.all([
|
|
apiCreateRepo(request, {name: repoName, headers: apiUserHeaders(owner)}),
|
|
loginUser(page, owner),
|
|
]);
|
|
const badge = page.locator('a.not-mobile .notification_count');
|
|
await expect(badge).toBeHidden();
|
|
|
|
// Create issue as another user — this generates a notification delivered via server push
|
|
await apiCreateIssue(request, owner, repoName, {title: 'events notification test', headers: apiUserHeaders(commenter)});
|
|
|
|
// Wait for the notification badge to appear via server event
|
|
await expect(badge).toBeVisible({timeout: 15000});
|
|
|
|
// Cleanup
|
|
await Promise.all([apiDeleteUser(request, commenter), apiDeleteUser(request, owner)]);
|
|
});
|
|
|
|
test('stopwatch visible at page load', async ({page, request}) => {
|
|
const name = `ev-sw-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
|
|
const headers = apiUserHeaders(name);
|
|
|
|
await apiCreateUser(request, name);
|
|
|
|
// Create repo, issue, and start stopwatch before login
|
|
await apiCreateRepo(request, {name, headers});
|
|
await apiCreateIssue(request, name, name, {title: 'events stopwatch test', headers});
|
|
await apiStartStopwatch(request, name, name, 1, {headers});
|
|
|
|
// Login — page renders with the active stopwatch element
|
|
await loginUser(page, name);
|
|
|
|
// Verify stopwatch is visible and links to the correct issue
|
|
const stopwatch = page.locator('.active-stopwatch.not-mobile');
|
|
await expect(stopwatch).toBeVisible();
|
|
|
|
// Cleanup
|
|
await apiDeleteUser(request, name);
|
|
});
|
|
|
|
test('stopwatch appears via real-time push', async ({page, request}) => {
|
|
const name = `ev-sw-push-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
|
|
const headers = apiUserHeaders(name);
|
|
|
|
await apiCreateUser(request, name);
|
|
await apiCreateRepo(request, {name, headers});
|
|
await apiCreateIssue(request, name, name, {title: 'events stopwatch push test', headers});
|
|
|
|
// Login before starting stopwatch — page loads without active stopwatch
|
|
await loginUser(page, name);
|
|
|
|
const stopwatch = page.locator('.active-stopwatch.not-mobile');
|
|
await expect(stopwatch).toBeHidden();
|
|
|
|
// Start stopwatch after page is loaded — icon should appear via WebSocket push
|
|
await apiStartStopwatch(request, name, name, 1, {headers});
|
|
await expect(stopwatch).toBeVisible({timeout: 15000});
|
|
|
|
// Cleanup
|
|
await apiDeleteUser(request, name);
|
|
});
|
|
|
|
test('logout propagation', async ({browser, request}) => {
|
|
const name = `ev-logout-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
|
|
|
|
await apiCreateUser(request, name);
|
|
|
|
// Use a single context so both pages share the same session and SharedWorker
|
|
const context = await browser.newContext({baseURL: baseUrl()});
|
|
const page1 = await context.newPage();
|
|
const page2 = await context.newPage();
|
|
|
|
await loginUser(page1, name);
|
|
|
|
// Navigate page2 so it connects to the shared event stream
|
|
await page2.goto('/');
|
|
|
|
// Verify page2 is logged in
|
|
await expect(page2.getByRole('link', {name: 'Sign In'})).toBeHidden();
|
|
|
|
// Logout from page1 — this sends a logout event to all tabs
|
|
await page1.goto('/user/logout');
|
|
|
|
// page2 should be redirected via the logout event
|
|
await expect(page2.getByRole('link', {name: 'Sign In'})).toBeVisible();
|
|
|
|
await context.close();
|
|
|
|
// Cleanup
|
|
await apiDeleteUser(request, name);
|
|
});
|
|
});
|