From 30d146e8b57169053587b6ee2e7f4880df20dccd Mon Sep 17 00:00:00 2001 From: wucm667 Date: Sat, 25 Jul 2026 04:27:19 +0800 Subject: [PATCH] fix(frontend): adapt affiliate copy controls for mobile --- frontend/src/views/user/AffiliateView.vue | 12 +- .../user/__tests__/AffiliateView.spec.ts | 116 ++++++++++++++++++ 2 files changed, 122 insertions(+), 6 deletions(-) create mode 100644 frontend/src/views/user/__tests__/AffiliateView.spec.ts diff --git a/frontend/src/views/user/AffiliateView.vue b/frontend/src/views/user/AffiliateView.vue index 66417d23dd..fdf0a35d66 100644 --- a/frontend/src/views/user/AffiliateView.vue +++ b/frontend/src/views/user/AffiliateView.vue @@ -51,9 +51,9 @@

{{ t('affiliate.yourCode') }}

-
- {{ detail.aff_code }} - @@ -62,9 +62,9 @@

{{ t('affiliate.inviteLink') }}

-
- {{ inviteLink }} - diff --git a/frontend/src/views/user/__tests__/AffiliateView.spec.ts b/frontend/src/views/user/__tests__/AffiliateView.spec.ts new file mode 100644 index 0000000000..e3eb0d7841 --- /dev/null +++ b/frontend/src/views/user/__tests__/AffiliateView.spec.ts @@ -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() + 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: '
' }, + 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', + ) + }) +})