mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-07 12:57:57 +08:00
fix(frontend): adapt affiliate copy controls for mobile
This commit is contained in:
@@ -51,9 +51,9 @@
|
||||
<div class="mt-5 grid gap-4 md:grid-cols-2">
|
||||
<div class="space-y-2">
|
||||
<p class="text-sm font-medium text-gray-700 dark:text-gray-300">{{ t('affiliate.yourCode') }}</p>
|
||||
<div class="flex items-center gap-2 rounded-xl border border-gray-200 bg-gray-50 px-3 py-2 dark:border-dark-700 dark:bg-dark-900">
|
||||
<code class="flex-1 truncate text-sm font-semibold text-gray-900 dark:text-white">{{ detail.aff_code }}</code>
|
||||
<button class="btn btn-secondary btn-sm" @click="copyCode">
|
||||
<div class="flex flex-col items-stretch gap-2 rounded-xl border border-gray-200 bg-gray-50 px-3 py-2 dark:border-dark-700 dark:bg-dark-900 sm:flex-row sm:items-center">
|
||||
<code class="min-w-0 break-all text-sm font-semibold text-gray-900 dark:text-white sm:flex-1 sm:truncate">{{ detail.aff_code }}</code>
|
||||
<button class="btn btn-secondary btn-sm w-full sm:w-auto sm:shrink-0" @click="copyCode">
|
||||
<Icon name="copy" size="sm" />
|
||||
<span>{{ t('affiliate.copyCode') }}</span>
|
||||
</button>
|
||||
@@ -62,9 +62,9 @@
|
||||
|
||||
<div class="space-y-2">
|
||||
<p class="text-sm font-medium text-gray-700 dark:text-gray-300">{{ t('affiliate.inviteLink') }}</p>
|
||||
<div class="flex items-center gap-2 rounded-xl border border-gray-200 bg-gray-50 px-3 py-2 dark:border-dark-700 dark:bg-dark-900">
|
||||
<code class="flex-1 truncate text-sm text-gray-700 dark:text-gray-300">{{ inviteLink }}</code>
|
||||
<button class="btn btn-secondary btn-sm" @click="copyInviteLink">
|
||||
<div class="flex flex-col items-stretch gap-2 rounded-xl border border-gray-200 bg-gray-50 px-3 py-2 dark:border-dark-700 dark:bg-dark-900 sm:flex-row sm:items-center">
|
||||
<code class="min-w-0 break-all text-sm text-gray-700 dark:text-gray-300 sm:flex-1 sm:truncate">{{ inviteLink }}</code>
|
||||
<button class="btn btn-secondary btn-sm w-full sm:w-auto sm:shrink-0" @click="copyInviteLink">
|
||||
<Icon name="copy" size="sm" />
|
||||
<span>{{ t('affiliate.copyLink') }}</span>
|
||||
</button>
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
import { flushPromises, mount } from '@vue/test-utils'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import AffiliateView from '../AffiliateView.vue'
|
||||
|
||||
const { copyToClipboard, getAffiliateDetail } = vi.hoisted(() => ({
|
||||
copyToClipboard: vi.fn(),
|
||||
getAffiliateDetail: vi.fn(),
|
||||
}))
|
||||
|
||||
vi.mock('@/api/user', () => ({
|
||||
default: {
|
||||
getAffiliateDetail,
|
||||
transferAffiliateQuota: vi.fn(),
|
||||
},
|
||||
}))
|
||||
|
||||
vi.mock('@/stores/app', () => ({
|
||||
useAppStore: () => ({
|
||||
showError: vi.fn(),
|
||||
showSuccess: vi.fn(),
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('@/stores/auth', () => ({
|
||||
useAuthStore: () => ({
|
||||
refreshUser: vi.fn(),
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('@/composables/useClipboard', () => ({
|
||||
useClipboard: () => ({ copyToClipboard }),
|
||||
}))
|
||||
|
||||
vi.mock('vue-i18n', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import('vue-i18n')>()
|
||||
return {
|
||||
...actual,
|
||||
useI18n: () => ({
|
||||
t: (key: string) => key,
|
||||
}),
|
||||
}
|
||||
})
|
||||
|
||||
describe('AffiliateView', () => {
|
||||
const affiliateCode = 'affiliate-code-that-is-long-enough-to-overflow-a-mobile-viewport'
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
copyToClipboard.mockResolvedValue(true)
|
||||
getAffiliateDetail.mockResolvedValue({
|
||||
user_id: 1,
|
||||
aff_code: affiliateCode,
|
||||
inviter_id: null,
|
||||
aff_count: 0,
|
||||
aff_quota: 0,
|
||||
aff_frozen_quota: 0,
|
||||
aff_history_quota: 0,
|
||||
effective_rebate_rate_percent: 10,
|
||||
invitees: [],
|
||||
})
|
||||
})
|
||||
|
||||
it('stacks long values and copy controls on mobile while retaining desktop rows', async () => {
|
||||
const wrapper = mount(AffiliateView, {
|
||||
global: {
|
||||
stubs: {
|
||||
AppLayout: { template: '<main><slot /></main>' },
|
||||
Icon: true,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
await flushPromises()
|
||||
|
||||
const values = wrapper.findAll('code')
|
||||
expect(values).toHaveLength(2)
|
||||
for (const value of values) {
|
||||
expect(value.classes()).toEqual(expect.arrayContaining([
|
||||
'min-w-0',
|
||||
'break-all',
|
||||
'sm:flex-1',
|
||||
'sm:truncate',
|
||||
]))
|
||||
expect(Array.from(value.element.parentElement?.classList ?? [])).toEqual(expect.arrayContaining([
|
||||
'flex-col',
|
||||
'items-stretch',
|
||||
'sm:flex-row',
|
||||
'sm:items-center',
|
||||
]))
|
||||
}
|
||||
|
||||
const copyButtons = wrapper.findAll('button').filter((button) =>
|
||||
['affiliate.copyCode', 'affiliate.copyLink'].includes(button.text()),
|
||||
)
|
||||
expect(copyButtons).toHaveLength(2)
|
||||
for (const button of copyButtons) {
|
||||
expect(button.classes()).toEqual(expect.arrayContaining([
|
||||
'w-full',
|
||||
'sm:w-auto',
|
||||
'sm:shrink-0',
|
||||
]))
|
||||
}
|
||||
|
||||
await copyButtons[0].trigger('click')
|
||||
await copyButtons[1].trigger('click')
|
||||
await flushPromises()
|
||||
|
||||
expect(copyToClipboard).toHaveBeenNthCalledWith(1, affiliateCode, 'affiliate.codeCopied')
|
||||
expect(copyToClipboard).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
`${window.location.origin}/register?aff=${encodeURIComponent(affiliateCode)}`,
|
||||
'affiliate.linkCopied',
|
||||
)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user