From ffa747d01a6320ad3a4cd795c0d55c6f38e46fda Mon Sep 17 00:00:00 2001 From: voyagi Date: Thu, 14 May 2026 13:30:58 +0200 Subject: [PATCH 1/2] test(account-details): cover country list ordering Add tests for #1443 that require country options to be sorted by display name and rebuilt without duplicates. --- .../personal-details.component.spec.ts | 80 +++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 src/app/account-details/personal-details.component.spec.ts diff --git a/src/app/account-details/personal-details.component.spec.ts b/src/app/account-details/personal-details.component.spec.ts new file mode 100644 index 000000000..e71826c66 --- /dev/null +++ b/src/app/account-details/personal-details.component.spec.ts @@ -0,0 +1,80 @@ +// --------- BEGIN RUNBOX LICENSE --------- +// Copyright (C) 2016-2019 Runbox Solutions AS (runbox.com). +// +// This file is part of Runbox 7. +// +// Runbox 7 is free software: You can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at your +// option) any later version. +// +// Runbox 7 is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Runbox 7. If not, see . +// ---------- END RUNBOX LICENSE ---------- + +import { HttpClient } from '@angular/common/http'; +import { UntypedFormBuilder } from '@angular/forms'; +import { MatLegacyDialog as MatDialog } from '@angular/material/legacy-dialog'; +import { of } from 'rxjs'; +import { RMM } from '../rmm'; +import { PersonalDetailsComponent } from './personal-details.component'; + +describe('PersonalDetailsComponent', () => { + const accountDetails = { + country: 'NO', + email_alternative_status: 0, + timezone: 'Europe/Oslo', + }; + + function createComponent(): PersonalDetailsComponent { + const http = { + get: (url: string) => of({ + result: url === '/rest/v1/timezones' + ? { timezones: ['Europe/Oslo'] } + : accountDetails, + }), + post: () => of({ result: accountDetails }), + } as unknown as HttpClient; + + const dialog = { + open: () => ({ + afterClosed: () => of(null), + }), + } as unknown as MatDialog; + + const rmm = { + account_security: { + user_password: 'secret', + }, + show_error: () => undefined, + } as unknown as RMM; + + return new PersonalDetailsComponent( + new UntypedFormBuilder(), + http, + dialog, + rmm, + ); + } + + it('sorts countries by display name', () => { + const component = createComponent(); + const countryNames = component.countriesAndTimezones.map((country) => country.name); + + expect(countryNames).toEqual([...countryNames].sort((a, b) => a.localeCompare(b))); + }); + + it('rebuilds the country list instead of appending duplicates', () => { + const component = createComponent(); + const countryCount = component.countriesAndTimezones.length; + + component.loadCountryList(); + + expect(component.countriesAndTimezones.length).toBe(countryCount); + }); +}); From 5f8b9e4ac1678e8cd9e73f66908ae788ad36ad16 Mon Sep 17 00:00:00 2001 From: voyagi Date: Thu, 14 May 2026 13:31:05 +0200 Subject: [PATCH 2/2] fix(account-details): sort countries by display name Populate the country list from countries-and-timezones entries, sort by localized country name, and replace the list on reload instead of appending duplicates. Fixes #1443. --- .../personal-details.component.ts | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/src/app/account-details/personal-details.component.ts b/src/app/account-details/personal-details.component.ts index 75416d191..d6bd1792b 100644 --- a/src/app/account-details/personal-details.component.ts +++ b/src/app/account-details/personal-details.component.ts @@ -96,16 +96,13 @@ export class PersonalDetailsComponent implements OnInit { } loadCountryList() { - for (const country in ct.getAllCountries()) { - if (country) { - const ctObject = { - id: country, - name: ct.getAllCountries()[country].name, - timezones: ct.getAllCountries()[country].timezones, - }; - this.countriesAndTimezones.push(ctObject); - } - } + this.countriesAndTimezones = Object.entries(ct.getAllCountries()) + .map(([id, country]) => ({ + id, + name: country.name, + timezones: country.timezones, + })) + .sort((a, b) => a.name.localeCompare(b.name)); } loadTimezones() {