diff --git a/.changes/settings-version-offline.md b/.changes/settings-version-offline.md new file mode 100644 index 000000000..e9e24512f --- /dev/null +++ b/.changes/settings-version-offline.md @@ -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. diff --git a/apps/web-e2e/src/settings.e2e.ts b/apps/web-e2e/src/settings.e2e.ts index f0b01388a..165735abc 100644 --- a/apps/web-e2e/src/settings.e2e.ts +++ b/apps/web-e2e/src/settings.e2e.ts @@ -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, }) => { diff --git a/apps/web/src/app/settings/settings-app-update.facade.spec.ts b/apps/web/src/app/settings/settings-app-update.facade.spec.ts index c874bcdf2..588fecf1d 100644 --- a/apps/web/src/app/settings/settings-app-update.facade.spec.ts +++ b/apps/web/src/app/settings/settings-app-update.facade.spec.ts @@ -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'; diff --git a/apps/web/src/app/settings/settings-app-update.facade.ts b/apps/web/src/app/settings/settings-app-update.facade.ts index 463c4aa0a..3af5927d0 100644 --- a/apps/web/src/app/settings/settings-app-update.facade.ts +++ b/apps/web/src/app/settings/settings-app-update.facade.ts @@ -34,8 +34,12 @@ export class SettingsAppUpdateFacade { /** Latest updater status, polled once and then pushed by the backend */ readonly status = signal(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, + }); } /**