fix(settings): show the installed version without the GitHub release check (#1879)

The settings facade filled its version signal only inside the GitHub
releases callback, so offline or under an api.github.com rate limit the
installed version was missing from the sidebar's About item and the About
page. The version is known locally from DataService, so the signal starts
with it. The release check also gets an error handler: SettingsService
already logs the failure, and without it the error resurfaced uncaught.

The sidebar layout E2E no longer needs to stub the releases request; a new
E2E aborts it and expects the version in both places.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
4grayandClaude Opus 5.5 authored and GitHub committed 2026-10-10 22:49:22 +02:00
1 parent 91e277e9ba
commit 039238b7bc
4 files changed
+72 -12

No files matched your search

+8
View File
@@ -0,0 +1,8 @@
---
type: fix
area: settings
---
Settings always show the installed version, also offline or when GitHub
cannot be reached; before, it was missing from the sidebar and the About page
until the update check answered.
+23 -8
View File
@@ -232,14 +232,6 @@ test.describe('Settings', () => {
test('@settings @web About keeps its label and clears the window bottom', async ({
page,
}) => {
// The sidebar shows the version only once the GitHub releases check
// answers; CI runners are often rate-limited there, so answer locally
// with an older release (no update badge replaces the version).
await page.route('**/repos/4gray/iptvnator/releases*', (route) =>
route.fulfill({
json: [{ created_at: '2020-01-01T00:00:00Z', name: '0.0.1' }],
})
);
await openSettings(page);
const about = page.locator(
'.settings-nav__footer [data-test-id="settings-section-about"]'
@@ -279,6 +271,29 @@ test.describe('Settings', () => {
).toBeGreaterThanOrEqual(12);
});
test('@settings @web The installed version shows while GitHub is unreachable', async ({
page,
}) => {
// Offline, or rate-limited by api.github.com: the release check never
// answers, yet the installed version is known locally.
await page.route('**/repos/4gray/iptvnator/releases*', (route) =>
route.abort()
);
await openSettings(page);
const version = /\d+\.\d+\.\d+/;
await expect(
page.locator(
'[data-test-id="settings-section-about"] [data-test-id="settings-nav-version"]'
)
).toHaveText(version);
await openSettingsSection(page, 'about');
await expect(page.locator('[data-test-id="app-version"]')).toHaveText(
version
);
});
test('@settings @web Esc leaves settings like the header Back', async ({
page,
}) => {
@@ -7,7 +7,7 @@ import {
} from '@iptvnator/shared/interfaces';
import { TranslateModule, TranslateService } from '@ngx-translate/core';
import { MockProvider } from 'ng-mocks';
import { of } from 'rxjs';
import { NEVER, of, throwError } from 'rxjs';
import { ElectronServiceStub } from '../services/electron.service.stub';
import { SettingsService } from '../services/settings.service';
import { AppUpdateReleaseNotesDialogComponent } from './app-update-release-notes-dialog.component';
@@ -216,6 +216,34 @@ describe('SettingsAppUpdateFacade', () => {
);
});
describe('Installed version without the release check', () => {
it('shows the installed version before GitHub answers', () => {
(
TestBed.inject(SettingsService).getAppVersion as jest.Mock
).mockReturnValue(NEVER);
facade.checkAppVersion();
expect(facade.version()).toBe('1.0.0');
expect(facade.updateMessage()).toBe('');
});
it('keeps the installed version when GitHub cannot be reached', async () => {
(
TestBed.inject(SettingsService).getAppVersion as jest.Mock
).mockReturnValue(
throwError(() => new Error('rate limit exceeded'))
);
facade.checkAppVersion();
// An unhandled observable error is rethrown on a timer.
await flush();
expect(facade.version()).toBe('1.0.0');
expect(facade.updateMessage()).toBe('');
});
});
describe('Version check', () => {
const latestVersion = '1.0.0';
const currentVersion = '0.1.0';
@@ -34,8 +34,12 @@ export class SettingsAppUpdateFacade {
/** Latest updater status, polled once and then pushed by the backend */
readonly status = signal<ElectronBridgeAppUpdateStatus | null>(null);
/** Current version of the app */
readonly version = signal('');
/**
* Installed version of the app. Known locally from the start, so it does
* not wait on (or vanish with) the GitHub releases check, which fails
* offline and when api.github.com rate-limits the client.
*/
readonly version = signal(this.dataService.getAppVersion());
/** Outcome of the last version check, kept apart from its wording. */
private readonly versionCheck = signal<{
@@ -148,7 +152,12 @@ export class SettingsAppUpdateFacade {
this.settingsService
.getAppVersion()
.pipe(take(1))
.subscribe((version) => this.showVersionInformation(version));
.subscribe({
next: (version) => this.showVersionInformation(version),
// SettingsService already logs the failure; without an
// answer there is simply no update note to show.
error: () => undefined,
});
}
/**