diff --git a/templates/base/head_navbar.tmpl b/templates/base/head_navbar.tmpl index 447f78565e..5b8593ec43 100644 --- a/templates/base/head_navbar.tmpl +++ b/templates/base/head_navbar.tmpl @@ -152,31 +152,29 @@ {{$activeStopwatch := and .PageGlobalData (call .PageGlobalData.GetActiveStopwatch)}} - {{if $activeStopwatch}} -
-
- - {{svg "octicon-issue-opened" 16}} - {{$activeStopwatch.RepoSlug}}#{{$activeStopwatch.IssueIndex}} - -
-
- -
-
- -
-
+ {{template "base/head_banner"}} diff --git a/templates/base/head_navbar_icons.tmpl b/templates/base/head_navbar_icons.tmpl index 89b02389fc..e0c997b88a 100644 --- a/templates/base/head_navbar_icons.tmpl +++ b/templates/base/head_navbar_icons.tmpl @@ -3,14 +3,12 @@ {{if and $data $data.IsSigned}}{{/* data may not exist, for example: rendering 503 page before the PageGlobalData middleware */}} {{- $activeStopwatch := call $data.GetActiveStopwatch -}} {{- $notificationUnreadCount := call $data.GetNotificationUnreadCount -}} - {{if $activeStopwatch}} - +
{{svg "octicon-stopwatch"}}
- {{end}}
{{svg "octicon-bell"}} diff --git a/tests/e2e/events.test.ts b/tests/e2e/events.test.ts index 61f1a3c881..bab3430945 100644 --- a/tests/e2e/events.test.ts +++ b/tests/e2e/events.test.ts @@ -29,7 +29,7 @@ test.describe('events', () => { await Promise.all([apiDeleteUser(request, commenter), apiDeleteUser(request, owner)]); }); - test('stopwatch', async ({page, request}) => { + 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); @@ -51,6 +51,28 @@ test.describe('events', () => { 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)}`; diff --git a/web_src/js/features/stopwatch.ts b/web_src/js/features/stopwatch.ts index 6fa8fbbdf3..75f6290735 100644 --- a/web_src/js/features/stopwatch.ts +++ b/web_src/js/features/stopwatch.ts @@ -34,6 +34,11 @@ export function initStopwatch() { interactive: true, hideOnClick: true, theme: 'default', + onShow(instance) { + // Re-clone so the tooltip always reflects the latest stopwatch state, + // even when the icon became visible via a real-time WebSocket push. + instance.setContent(stopwatchPopup.cloneNode(true) as Element); + }, }); }