fix(account): ein vor dem Umbenennen gestartetes Lesen meldet den alten Namen nicht mehr
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
f46a69df68
commit
7c55912ebe
2 files changed
+67
-3
No files matched your search
@@ -512,6 +512,62 @@ describe('loadMyUsername', () => {
|
|||||||
mocks.getRow.mockRejectedValue(offline);
|
mocks.getRow.mockRejectedValue(offline);
|
||||||
await expect(profile.loadMyUsername()).rejects.toBe(offline);
|
await expect(profile.loadMyUsername()).rejects.toBe(offline);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('eine Antwort, die vor dem Umbenennen abgeschickt wurde, meldet den alten Namen nicht noch einmal: der neuere Name gilt', async () => {
|
||||||
|
mocks.userId = 'u1';
|
||||||
|
let answer!: (row: unknown) => void;
|
||||||
|
mocks.getRow.mockReturnValue(new Promise((resolve) => (answer = resolve)));
|
||||||
|
const listener = vi.fn();
|
||||||
|
profile.onUsernameChange(listener);
|
||||||
|
const loading = profile.loadMyUsername(); // das Lesen läuft (Stand vor dem Umbenennen)
|
||||||
|
mocks.apiPost.mockResolvedValue({ username: 'Neu' });
|
||||||
|
await profile.saveUsername('Neu'); // währenddessen umbenannt
|
||||||
|
answer({ $id: 'u1', username: 'Alt' }); // die Antwort ist älter als das Umbenennen
|
||||||
|
await expect(loading).resolves.toBe('Neu');
|
||||||
|
expect(profile.myUsername()).toBe('Neu');
|
||||||
|
expect(listener).toHaveBeenCalledExactlyOnceWith('u1', 'Neu'); // „Alt“ wurde nie gemeldet
|
||||||
|
expect([...(await profile.usernamesFor(['u1']))]).toEqual([['u1', 'Neu']]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('dasselbe gilt für eine Antwort „kein Profil“, die vor dem Speichern abgeschickt wurde (Registrierung)', async () => {
|
||||||
|
mocks.userId = 'u1';
|
||||||
|
let answer!: (error: unknown) => void;
|
||||||
|
mocks.getRow.mockReturnValue(new Promise((_, reject) => (answer = reject)));
|
||||||
|
const listener = vi.fn();
|
||||||
|
profile.onUsernameChange(listener);
|
||||||
|
const loading = profile.loadMyUsername();
|
||||||
|
mocks.apiPost.mockResolvedValue({ username: 'Kenso' });
|
||||||
|
await profile.saveUsername('Kenso');
|
||||||
|
answer(notFound());
|
||||||
|
await expect(loading).resolves.toBe('Kenso');
|
||||||
|
expect(listener).toHaveBeenCalledExactlyOnceWith('u1', 'Kenso');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('ein Lesen, das erst nach dem Umbenennen beginnt, gilt wieder (der Name kann auf einem anderen Gerät geändert worden sein)', async () => {
|
||||||
|
mocks.userId = 'u1';
|
||||||
|
mocks.apiPost.mockResolvedValue({ username: 'Neu' });
|
||||||
|
await profile.saveUsername('Neu');
|
||||||
|
mocks.getRow.mockResolvedValue({ $id: 'u1', username: 'Anderswo' });
|
||||||
|
const listener = vi.fn();
|
||||||
|
profile.onUsernameChange(listener);
|
||||||
|
await expect(profile.loadMyUsername()).resolves.toBe('Anderswo');
|
||||||
|
expect(profile.myUsername()).toBe('Anderswo');
|
||||||
|
expect(listener).toHaveBeenCalledExactlyOnceWith('u1', 'Anderswo');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('das Umbenennen eines anderen Kontos macht ein laufendes Lesen nicht hinfällig', async () => {
|
||||||
|
mocks.userId = 'u1';
|
||||||
|
let answer!: (row: unknown) => void;
|
||||||
|
mocks.getRow.mockReturnValue(new Promise((resolve) => (answer = resolve)));
|
||||||
|
const loading = profile.loadMyUsername();
|
||||||
|
mocks.userId = 'u2'; // der Aufruf gehört einem anderen Konto
|
||||||
|
mocks.apiPost.mockResolvedValue({ username: 'Mia' });
|
||||||
|
await profile.saveUsername('Mia');
|
||||||
|
mocks.userId = 'u1';
|
||||||
|
answer({ $id: 'u1', username: 'Kenso' });
|
||||||
|
await expect(loading).resolves.toBe('Kenso');
|
||||||
|
expect(profile.myUsername()).toBe('Kenso');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Merker „in dieser Sitzung schon gefragt“', () => {
|
describe('Merker „in dieser Sitzung schon gefragt“', () => {
|
||||||
|
|||||||
@@ -79,6 +79,9 @@ export function validateUsername(name: string): string | null {
|
|||||||
|
|
||||||
/** Konto-ID → Benutzername, nur gefundene (ein Konto ohne Profil kann sich später einen Namen wählen, also wird es wieder gefragt). */
|
/** Konto-ID → Benutzername, nur gefundene (ein Konto ohne Profil kann sich später einen Namen wählen, also wird es wieder gefragt). */
|
||||||
const names = new Map<string, string>();
|
const names = new Map<string, string>();
|
||||||
|
/** Zählt die Namen, die diese Seite gespeichert hat; `savedAt` merkt je Konto den Stand beim letzten Speichern (siehe `loadMyUsername`). */
|
||||||
|
let saves = 0;
|
||||||
|
const savedAt = new Map<string, number>();
|
||||||
const listeners = new Set<(userId: string, username: string) => void>();
|
const listeners = new Set<(userId: string, username: string) => void>();
|
||||||
|
|
||||||
/** Merkt den Namen eines Kontos, dessen Stand diese Seite gerade selbst erfahren hat (Speichern, Lesen des eigenen Profils), und meldet ihn. */
|
/** Merkt den Namen eines Kontos, dessen Stand diese Seite gerade selbst erfahren hat (Speichern, Lesen des eigenen Profils), und meldet ihn. */
|
||||||
@@ -188,9 +191,11 @@ export async function isUsernameTaken(name: string): Promise<boolean> {
|
|||||||
export async function loadMyUsername(): Promise<string | null> {
|
export async function loadMyUsername(): Promise<string | null> {
|
||||||
const userId = currentUserId();
|
const userId = currentUserId();
|
||||||
if (!userId) return null;
|
if (!userId) return null;
|
||||||
|
const started = saves;
|
||||||
const name = await getUsername(userId);
|
const name = await getUsername(userId);
|
||||||
// Kein Profil: war es inzwischen gespeichert (Registrierung: die Abfrage war schneller als das Speichern), gilt der gemerkte Name.
|
// Wurde der Name währenddessen gespeichert (Umbenennen, Registrierung: die Abfrage war schneller als das Speichern), ist die Antwort
|
||||||
if (name === null) return names.get(userId) ?? null;
|
// älter als der gemerkte Name: er gilt, und der alte wird nicht noch einmal gemeldet. Kein Profil heißt sonst ebenfalls „der gemerkte“.
|
||||||
|
if (name === null || (savedAt.get(userId) ?? 0) > started) return names.get(userId) ?? null;
|
||||||
announce(userId, name);
|
announce(userId, name);
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
@@ -208,7 +213,10 @@ export async function saveUsername(name: string): Promise<string> {
|
|||||||
const saved = res.username;
|
const saved = res.username;
|
||||||
// Eine Antwort ohne Namen ist keine Antwort des Dienstes (Proxy, Fehlseite): der Ausgang ist offen.
|
// Eine Antwort ohne Namen ist keine Antwort des Dienstes (Proxy, Fehlseite): der Ausgang ist offen.
|
||||||
if (typeof saved !== 'string' || saved === '') throw unavailableError();
|
if (typeof saved !== 'string' || saved === '') throw unavailableError();
|
||||||
if (userId) announce(userId, saved);
|
if (userId) {
|
||||||
|
savedAt.set(userId, ++saves);
|
||||||
|
announce(userId, saved);
|
||||||
|
}
|
||||||
return saved;
|
return saved;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user