refactor(profile): split avatar and bindings cards

This commit is contained in:
IanShaw027
2026-04-21 17:56:15 +08:00
parent ee3f158f4e
commit 7309c02f0b
10 changed files with 801 additions and 749 deletions

View File

@@ -22,7 +22,11 @@
/>
</div>
<ProfileInfoCard
<ProfileInfoCard :user="user" />
<ProfileAvatarCard :user="user" />
<ProfileAccountBindingsCard
:user="user"
:linuxdo-enabled="linuxdoOAuthEnabled"
:oidc-enabled="oidcOAuthEnabled"
@@ -73,6 +77,8 @@ import { Icon } from '@/components/icons'
import StatCard from '@/components/common/StatCard.vue'
import AppLayout from '@/components/layout/AppLayout.vue'
import ProfileBalanceNotifyCard from '@/components/user/profile/ProfileBalanceNotifyCard.vue'
import ProfileAccountBindingsCard from '@/components/user/profile/ProfileAccountBindingsCard.vue'
import ProfileAvatarCard from '@/components/user/profile/ProfileAvatarCard.vue'
import ProfileEditForm from '@/components/user/profile/ProfileEditForm.vue'
import ProfileInfoCard from '@/components/user/profile/ProfileInfoCard.vue'
import ProfilePasswordForm from '@/components/user/profile/ProfilePasswordForm.vue'

View File

@@ -0,0 +1,101 @@
import { flushPromises, mount } from '@vue/test-utils'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import ProfileView from '@/views/user/ProfileView.vue'
const {
fetchPublicSettingsMock,
refreshUserMock,
authState
} = vi.hoisted(() => ({
fetchPublicSettingsMock: vi.fn(),
refreshUserMock: vi.fn(),
authState: {
user: null as Record<string, unknown> | null,
refreshUser: vi.fn()
}
}))
vi.mock('@/stores/auth', () => ({
useAuthStore: () => authState
}))
vi.mock('@/stores/app', () => ({
useAppStore: () => ({
fetchPublicSettings: fetchPublicSettingsMock
})
}))
vi.mock('@/utils/format', () => ({
formatDate: () => 'April 2026'
}))
vi.mock('vue-i18n', async (importOriginal) => {
const actual = await importOriginal<typeof import('vue-i18n')>()
return {
...actual,
useI18n: () => ({
t: (key: string) => key
})
}
})
describe('ProfileView', () => {
beforeEach(() => {
refreshUserMock.mockReset()
fetchPublicSettingsMock.mockReset()
refreshUserMock.mockResolvedValue(undefined)
authState.refreshUser = refreshUserMock
authState.user = {
id: 1,
username: 'alice',
email: 'alice@example.com',
role: 'user',
balance: 10,
concurrency: 2,
status: 'active',
allowed_groups: null,
balance_notify_enabled: true,
balance_notify_threshold: null,
balance_notify_extra_emails: [],
created_at: '2026-04-20T00:00:00Z',
updated_at: '2026-04-20T00:00:00Z'
}
fetchPublicSettingsMock.mockResolvedValue({
contact_info: '',
balance_low_notify_enabled: false,
balance_low_notify_threshold: 0,
linuxdo_oauth_enabled: true,
wechat_oauth_enabled: true,
wechat_oauth_open_enabled: true,
wechat_oauth_mp_enabled: false,
oidc_oauth_enabled: true,
oidc_oauth_provider_name: 'OIDC'
})
})
it('renders info, avatar, and account binding cards as separate sections', async () => {
const wrapper = mount(ProfileView, {
global: {
stubs: {
AppLayout: { template: '<div><slot /></div>' },
StatCard: { template: '<div class="stat-card" />' },
ProfileInfoCard: { template: '<div data-testid="profile-info-card" />' },
ProfileAvatarCard: { template: '<div data-testid="profile-avatar-card" />' },
ProfileAccountBindingsCard: { template: '<div data-testid="profile-account-bindings-card" />' },
ProfileEditForm: true,
ProfileBalanceNotifyCard: true,
ProfilePasswordForm: true,
ProfileTotpCard: true,
Icon: true
}
}
})
await flushPromises()
const html = wrapper.html()
expect(html.indexOf('profile-info-card')).toBeGreaterThan(-1)
expect(html.indexOf('profile-avatar-card')).toBeGreaterThan(html.indexOf('profile-info-card'))
expect(html.indexOf('profile-account-bindings-card')).toBeGreaterThan(html.indexOf('profile-avatar-card'))
})
})