mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-20 14:30:41 +00:00
* fix: Ensure logout clears both Firebase auth and API key When logging out via the avatar dropdown, the logout function was only clearing Firebase authentication but not the stored API key. This could leave users partially authenticated with their API key still active. Updated CurrentUserPopover to use handleSignOut from useCurrentUser composable, which properly handles both authentication methods: - Clears API key if logged in with API key - Signs out Firebase if logged in with Firebase This ensures complete logout regardless of authentication method. Fixes #5261 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * test: Update CurrentUserPopover tests to match new logout implementation Updated test mocks to include handleSignOut from useCurrentUser composable and adjusted test expectations to verify handleSignOut is called instead of the direct logout method. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
207 lines
5.6 KiB
TypeScript
207 lines
5.6 KiB
TypeScript
import { VueWrapper, mount } from '@vue/test-utils'
|
|
import Button from 'primevue/button'
|
|
import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
import { h } from 'vue'
|
|
import { createI18n } from 'vue-i18n'
|
|
|
|
import enMessages from '@/locales/en/main.json'
|
|
|
|
import CurrentUserPopover from './CurrentUserPopover.vue'
|
|
|
|
// Mock all firebase modules
|
|
vi.mock('firebase/app', () => ({
|
|
initializeApp: vi.fn(),
|
|
getApp: vi.fn()
|
|
}))
|
|
|
|
vi.mock('firebase/auth', () => ({
|
|
getAuth: vi.fn(),
|
|
setPersistence: vi.fn(),
|
|
browserLocalPersistence: {},
|
|
onAuthStateChanged: vi.fn(),
|
|
signInWithEmailAndPassword: vi.fn(),
|
|
signOut: vi.fn()
|
|
}))
|
|
|
|
// Mock pinia
|
|
vi.mock('pinia')
|
|
|
|
// Mock showSettingsDialog and showTopUpCreditsDialog
|
|
const mockShowSettingsDialog = vi.fn()
|
|
const mockShowTopUpCreditsDialog = vi.fn()
|
|
|
|
// Mock window.open
|
|
const originalWindowOpen = window.open
|
|
beforeEach(() => {
|
|
window.open = vi.fn()
|
|
})
|
|
|
|
afterAll(() => {
|
|
window.open = originalWindowOpen
|
|
})
|
|
|
|
// Mock the useCurrentUser composable
|
|
const mockHandleSignOut = vi.fn()
|
|
vi.mock('@/composables/auth/useCurrentUser', () => ({
|
|
useCurrentUser: vi.fn(() => ({
|
|
userPhotoUrl: 'https://example.com/avatar.jpg',
|
|
userDisplayName: 'Test User',
|
|
userEmail: 'test@example.com',
|
|
handleSignOut: mockHandleSignOut
|
|
}))
|
|
}))
|
|
|
|
// Mock the useFirebaseAuthActions composable
|
|
const mockLogout = vi.fn()
|
|
vi.mock('@/composables/auth/useFirebaseAuthActions', () => ({
|
|
useFirebaseAuthActions: vi.fn(() => ({
|
|
fetchBalance: vi.fn().mockResolvedValue(undefined),
|
|
logout: mockLogout
|
|
}))
|
|
}))
|
|
|
|
// Mock the dialog service
|
|
vi.mock('@/services/dialogService', () => ({
|
|
useDialogService: vi.fn(() => ({
|
|
showSettingsDialog: mockShowSettingsDialog,
|
|
showTopUpCreditsDialog: mockShowTopUpCreditsDialog
|
|
}))
|
|
}))
|
|
|
|
// Mock UserAvatar component
|
|
vi.mock('@/components/common/UserAvatar.vue', () => ({
|
|
default: {
|
|
name: 'UserAvatarMock',
|
|
render() {
|
|
return h('div', 'Avatar')
|
|
}
|
|
}
|
|
}))
|
|
|
|
// Mock UserCredit component
|
|
vi.mock('@/components/common/UserCredit.vue', () => ({
|
|
default: {
|
|
name: 'UserCreditMock',
|
|
render() {
|
|
return h('div', 'Credit: 100')
|
|
}
|
|
}
|
|
}))
|
|
|
|
describe('CurrentUserPopover', () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks()
|
|
})
|
|
|
|
const mountComponent = (): VueWrapper => {
|
|
const i18n = createI18n({
|
|
legacy: false,
|
|
locale: 'en',
|
|
messages: { en: enMessages }
|
|
})
|
|
|
|
return mount(CurrentUserPopover, {
|
|
global: {
|
|
plugins: [i18n],
|
|
stubs: {
|
|
Divider: true
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
it('renders user information correctly', () => {
|
|
const wrapper = mountComponent()
|
|
|
|
expect(wrapper.text()).toContain('Test User')
|
|
expect(wrapper.text()).toContain('test@example.com')
|
|
})
|
|
|
|
it('renders logout button with correct props', () => {
|
|
const wrapper = mountComponent()
|
|
|
|
// Find all buttons and get the logout button (second one)
|
|
const buttons = wrapper.findAllComponents(Button)
|
|
const logoutButton = buttons[1]
|
|
|
|
// Check that logout button has correct props
|
|
expect(logoutButton.props('label')).toBe('Log Out')
|
|
expect(logoutButton.props('icon')).toBe('pi pi-sign-out')
|
|
})
|
|
|
|
it('opens user settings and emits close event when settings button is clicked', async () => {
|
|
const wrapper = mountComponent()
|
|
|
|
// Find all buttons and get the settings button (first one)
|
|
const buttons = wrapper.findAllComponents(Button)
|
|
const settingsButton = buttons[0]
|
|
|
|
// Click the settings button
|
|
await settingsButton.trigger('click')
|
|
|
|
// Verify showSettingsDialog was called with 'user'
|
|
expect(mockShowSettingsDialog).toHaveBeenCalledWith('user')
|
|
|
|
// Verify close event was emitted
|
|
expect(wrapper.emitted('close')).toBeTruthy()
|
|
expect(wrapper.emitted('close')!.length).toBe(1)
|
|
})
|
|
|
|
it('calls logout function and emits close event when logout button is clicked', async () => {
|
|
const wrapper = mountComponent()
|
|
|
|
// Find all buttons and get the logout button (second one)
|
|
const buttons = wrapper.findAllComponents(Button)
|
|
const logoutButton = buttons[1]
|
|
|
|
// Click the logout button
|
|
await logoutButton.trigger('click')
|
|
|
|
// Verify handleSignOut was called
|
|
expect(mockHandleSignOut).toHaveBeenCalled()
|
|
|
|
// Verify close event was emitted
|
|
expect(wrapper.emitted('close')).toBeTruthy()
|
|
expect(wrapper.emitted('close')!.length).toBe(1)
|
|
})
|
|
|
|
it('opens API pricing docs and emits close event when API pricing button is clicked', async () => {
|
|
const wrapper = mountComponent()
|
|
|
|
// Find all buttons and get the API pricing button (third one now)
|
|
const buttons = wrapper.findAllComponents(Button)
|
|
const apiPricingButton = buttons[2]
|
|
|
|
// Click the API pricing button
|
|
await apiPricingButton.trigger('click')
|
|
|
|
// Verify window.open was called with the correct URL
|
|
expect(window.open).toHaveBeenCalledWith(
|
|
'https://docs.comfy.org/tutorials/api-nodes/pricing',
|
|
'_blank'
|
|
)
|
|
|
|
// Verify close event was emitted
|
|
expect(wrapper.emitted('close')).toBeTruthy()
|
|
expect(wrapper.emitted('close')!.length).toBe(1)
|
|
})
|
|
|
|
it('opens top-up dialog and emits close event when top-up button is clicked', async () => {
|
|
const wrapper = mountComponent()
|
|
|
|
// Find all buttons and get the top-up button (last one)
|
|
const buttons = wrapper.findAllComponents(Button)
|
|
const topUpButton = buttons[buttons.length - 1]
|
|
|
|
// Click the top-up button
|
|
await topUpButton.trigger('click')
|
|
|
|
// Verify showTopUpCreditsDialog was called
|
|
expect(mockShowTopUpCreditsDialog).toHaveBeenCalled()
|
|
|
|
// Verify close event was emitted
|
|
expect(wrapper.emitted('close')).toBeTruthy()
|
|
expect(wrapper.emitted('close')!.length).toBe(1)
|
|
})
|
|
})
|