fix(account): Abmelden nimmt den Merker „Benutzername schon gefragt“ weg
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
7c55912ebe
commit
53bcecf3cb
2 files changed
+80
-2
No files matched your search
@@ -12,6 +12,8 @@ const mocks = vi.hoisted(() => {
|
||||
/** Der angemeldete Nutzer, wie `currentUser`/`currentUserId` ihn nennen. */
|
||||
user: null as TestUser | null,
|
||||
authListeners: new Set<(user: TestUser | null) => void>(),
|
||||
/** Die Hörer von `onLogout`: sie hören nur das Abmelden durch den Nutzer, nicht jede Änderung des Nutzers. */
|
||||
logoutListeners: new Set<() => void>(),
|
||||
loadMyUsername: vi.fn(),
|
||||
usernameDialog: vi.fn(),
|
||||
pages: { editor: page(), cards: page(), community: page(), reset: page(), home: page() },
|
||||
@@ -28,6 +30,10 @@ vi.mock('./account/auth', () => ({
|
||||
mocks.authListeners.add(fn);
|
||||
return () => mocks.authListeners.delete(fn);
|
||||
},
|
||||
onLogout: (fn: () => void) => {
|
||||
mocks.logoutListeners.add(fn);
|
||||
return () => mocks.logoutListeners.delete(fn);
|
||||
},
|
||||
}));
|
||||
vi.mock('./account/login-dialog', () => ({ requireLogin: vi.fn() }));
|
||||
vi.mock('./account/username-dialog', () => ({ usernameDialog: mocks.usernameDialog }));
|
||||
@@ -75,6 +81,7 @@ beforeEach(() => {
|
||||
mocks.getCurrentUser.mockReset().mockResolvedValue(null); // Gast: der Anmeldestatus ist „abgemeldet“
|
||||
mocks.user = null;
|
||||
mocks.authListeners.clear();
|
||||
mocks.logoutListeners.clear();
|
||||
mocks.loadMyUsername.mockReset().mockResolvedValue(null); // das Konto hat keinen Namen
|
||||
mocks.usernameDialog.mockReset().mockResolvedValue(null); // „Später“
|
||||
sessionStorage.clear();
|
||||
@@ -251,6 +258,12 @@ describe('Benutzername abfragen (Spec § 5)', () => {
|
||||
mocks.user = user;
|
||||
for (const fn of [...mocks.authListeners]) fn(user);
|
||||
};
|
||||
/** Der Nutzer meldet sich ab: wie in auth.ts erst der Nutzer weg (onAuthChange), dann die Abmelde-Hörer (onLogout). */
|
||||
const userLogsOut = () => {
|
||||
mocks.user = null;
|
||||
for (const fn of [...mocks.authListeners]) fn(null);
|
||||
for (const fn of [...mocks.logoutListeners]) fn();
|
||||
};
|
||||
const settle = () => new Promise((resolve) => setTimeout(resolve, 10));
|
||||
|
||||
it('Start mit Sitzung, Konto ohne Namen: der Dialog „prompt“ erscheint genau einmal; der Merker liegt in sessionStorage', async () => {
|
||||
@@ -313,6 +326,64 @@ describe('Benutzername abfragen (Spec § 5)', () => {
|
||||
expect(mocks.usernameDialog).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('Abmelden nimmt den Merker weg: ein zweites Konto ohne Namen im selben Tab wird gefragt', async () => {
|
||||
const mia: TestUser = { $id: 'u2', email: 'mia@example.com' };
|
||||
sessionAtStart();
|
||||
await start('/');
|
||||
await vi.waitFor(() => expect(mocks.usernameDialog).toHaveBeenCalledTimes(1));
|
||||
expect(sessionStorage.getItem(PROMPTED_KEY)).toBe('1');
|
||||
|
||||
userLogsOut();
|
||||
expect(sessionStorage.getItem(PROMPTED_KEY)).toBeNull();
|
||||
|
||||
userSignsIn(mia);
|
||||
await vi.waitFor(() => expect(mocks.usernameDialog).toHaveBeenCalledTimes(2));
|
||||
expect(sessionStorage.getItem(PROMPTED_KEY)).toBe('1'); // und für dieses Konto gilt es wieder
|
||||
userSignsIn(mia);
|
||||
await settle();
|
||||
expect(mocks.usernameDialog).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('auch dasselbe Konto wird nach dem Abmelden und erneuten Anmelden (weiter ohne Namen) wieder gefragt', async () => {
|
||||
sessionAtStart();
|
||||
await start('/');
|
||||
await vi.waitFor(() => expect(mocks.usernameDialog).toHaveBeenCalledTimes(1));
|
||||
userLogsOut();
|
||||
userSignsIn(kenso);
|
||||
await vi.waitFor(() => expect(mocks.usernameDialog).toHaveBeenCalledTimes(2));
|
||||
});
|
||||
|
||||
it('nur das Abmelden räumt den Merker weg, nicht jede Änderung des Nutzers (das Anmelden beim Registrieren räumt eine alte Sitzung weg)', async () => {
|
||||
sessionAtStart();
|
||||
await start('/');
|
||||
await vi.waitFor(() => expect(mocks.usernameDialog).toHaveBeenCalledTimes(1));
|
||||
mocks.user = null;
|
||||
for (const fn of [...mocks.authListeners]) fn(null); // abgelaufene Sitzung: kein onLogout
|
||||
expect(sessionStorage.getItem(PROMPTED_KEY)).toBe('1');
|
||||
});
|
||||
|
||||
it('Abmelden ohne gesetzten Merker ist harmlos', async () => {
|
||||
mocks.loadMyUsername.mockResolvedValue('Kenso');
|
||||
sessionAtStart();
|
||||
await start('/');
|
||||
await vi.waitFor(() => expect(mocks.loadMyUsername).toHaveBeenCalledTimes(1));
|
||||
expect(() => userLogsOut()).not.toThrow();
|
||||
expect(sessionStorage.getItem(PROMPTED_KEY)).toBeNull();
|
||||
});
|
||||
|
||||
it('ist sessionStorage gesperrt, wirft das Abmelden nichts', async () => {
|
||||
const denied = () => {
|
||||
throw new DOMException('blockiert', 'SecurityError');
|
||||
};
|
||||
vi.stubGlobal('sessionStorage', { getItem: denied, setItem: denied, removeItem: denied });
|
||||
sessionAtStart();
|
||||
await start('/');
|
||||
await vi.waitFor(() => expect(mocks.usernameDialog).toHaveBeenCalledTimes(1));
|
||||
expect(() => userLogsOut()).not.toThrow();
|
||||
userSignsIn(); // der Merker im Speicher ist mit weg: gefragt wird wieder
|
||||
await vi.waitFor(() => expect(mocks.usernameDialog).toHaveBeenCalledTimes(2));
|
||||
});
|
||||
|
||||
it('Anmeldung, Konto mit Namen: kein Dialog', async () => {
|
||||
mocks.loadMyUsername.mockResolvedValue('Kenso');
|
||||
await start('/');
|
||||
@@ -353,6 +424,7 @@ describe('Benutzername abfragen (Spec § 5)', () => {
|
||||
await vi.waitFor(() => expect(mocks.usernameDialog).toHaveBeenCalledTimes(1));
|
||||
|
||||
mocks.authListeners.clear(); // die erste Seite ist weg
|
||||
mocks.logoutListeners.clear();
|
||||
mocks.loadMyUsername.mockClear();
|
||||
await start('/');
|
||||
await vi.waitFor(() => expect(mocks.loadMyUsername).toHaveBeenCalledTimes(1));
|
||||
@@ -367,6 +439,7 @@ describe('Benutzername abfragen (Spec § 5)', () => {
|
||||
|
||||
sessionStorage.clear(); // neuer Tab = neue sessionStorage
|
||||
mocks.authListeners.clear();
|
||||
mocks.logoutListeners.clear();
|
||||
await start('/');
|
||||
await vi.waitFor(() => expect(mocks.usernameDialog).toHaveBeenCalledTimes(2));
|
||||
});
|
||||
|
||||
+7
-2
@@ -2,8 +2,8 @@ import '@fontsource-variable/nunito-sans';
|
||||
import './styles.css';
|
||||
import { mountAccountMenu, type AccountMenu } from './account/account-menu';
|
||||
import { client } from './account/appwrite';
|
||||
import { currentUserId, getCurrentUser, onAuthChange } from './account/auth';
|
||||
import { loadMyUsername, markUsernamePrompted, usernamePrompted } from './account/profile';
|
||||
import { currentUserId, getCurrentUser, onAuthChange, onLogout } from './account/auth';
|
||||
import { clearUsernamePrompted, loadMyUsername, markUsernamePrompted, usernamePrompted } from './account/profile';
|
||||
import { usernameDialog } from './account/username-dialog';
|
||||
import { DONATE_URL } from './config';
|
||||
import { currentLang, onLangChange, setLang } from './i18n/i18n';
|
||||
@@ -94,6 +94,11 @@ onAuthChange((user) => {
|
||||
if (user) void checkUsername(user.$id);
|
||||
});
|
||||
|
||||
// Der Merker gilt je Anmeldung: wer sich abmeldet, nimmt ihn mit, und ein weiteres Konto ohne Namen im selben Tab wird gefragt. Nur beim
|
||||
// Abmelden, nicht bei jeder Änderung des Nutzers: das Anmelden beim Registrieren räumt auch eine alte Sitzung weg (onAuthChange(null)), und
|
||||
// die Registrierung hat den Merker da schon gesetzt (login-dialog.ts).
|
||||
onLogout(clearUsernamePrompted);
|
||||
|
||||
// Der Anmeldestatus wird einmal beim Start geholt; schlägt das fehl (außer 401, das getCurrentUser selbst als „abgemeldet“ meldet),
|
||||
// gilt der Nutzer als abgemeldet.
|
||||
if (accountSlot) {
|
||||
|
||||
Reference in new issue
Block a user