fix: show optional affiliate code on registration

This commit is contained in:
wucm667
2026-07-26 02:47:35 +08:00
parent 2730c1c43b
commit 0875143d98
2 changed files with 136 additions and 1 deletions
+24 -1
View File
@@ -134,6 +134,27 @@
</transition>
</div>
<!-- Affiliate Invitation Code Input (Optional) -->
<div v-else-if="affiliateEnabled" data-testid="affiliate-invitation-field">
<label for="affiliate_code" class="input-label">
{{ t('auth.invitationCodeLabel') }}
<span class="ml-1 text-xs font-normal text-gray-400 dark:text-dark-500">({{ t('common.optional') }})</span>
</label>
<div class="relative">
<div class="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3.5">
<Icon name="key" size="md" class="text-gray-400 dark:text-dark-500" />
</div>
<input
id="affiliate_code"
v-model="formData.aff_code"
type="text"
:disabled="registrationActionDisabled"
class="input pl-11"
:placeholder="t('auth.invitationCodePlaceholder')"
/>
</div>
</div>
<!-- Promo Code Input (Optional) -->
<div v-if="promoCodeEnabled">
<label for="promo_code" class="input-label">
@@ -183,7 +204,7 @@
</div>
<!-- Turnstile Widget -->
<div v-if="turnstileEnabled && turnstileSiteKey">
<div v-if="turnstileEnabled && turnstileSiteKey" data-testid="registration-turnstile">
<TurnstileWidget
ref="turnstileRef"
:site-key="turnstileSiteKey"
@@ -351,6 +372,7 @@ const registrationEnabled = ref<boolean>(true)
const emailVerifyEnabled = ref<boolean>(false)
const promoCodeEnabled = ref<boolean>(true)
const invitationCodeEnabled = ref<boolean>(false)
const affiliateEnabled = ref<boolean>(false)
const turnstileEnabled = ref<boolean>(false)
const turnstileSiteKey = ref<string>('')
const siteName = ref<string>('Sub2API')
@@ -459,6 +481,7 @@ onMounted(async () => {
emailVerifyEnabled.value = settings.email_verify_enabled
promoCodeEnabled.value = settings.promo_code_enabled
invitationCodeEnabled.value = settings.invitation_code_enabled
affiliateEnabled.value = settings.affiliate_enabled
turnstileEnabled.value = settings.turnstile_enabled
turnstileSiteKey.value = settings.turnstile_site_key || ''
siteName.value = settings.site_name || 'Sub2API'
@@ -0,0 +1,112 @@
import { flushPromises, mount } from '@vue/test-utils'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import RegisterView from '@/views/auth/RegisterView.vue'
const { getPublicSettingsMock } = vi.hoisted(() => ({
getPublicSettingsMock: vi.fn()
}))
const publicSettings = {
registration_enabled: true,
email_verify_enabled: false,
promo_code_enabled: false,
invitation_code_enabled: false,
affiliate_enabled: true,
turnstile_enabled: true,
turnstile_site_key: 'site-key',
site_name: 'Sub2API',
registration_email_suffix_whitelist: [],
linuxdo_oauth_enabled: false,
wechat_oauth_enabled: false,
oidc_oauth_enabled: false,
github_oauth_enabled: false,
google_oauth_enabled: false
}
vi.mock('vue-router', () => ({
useRouter: () => ({ push: vi.fn() }),
useRoute: () => ({ query: {} })
}))
vi.mock('vue-i18n', () => ({
createI18n: () => ({
global: {
t: (key: string) => key
}
}),
useI18n: () => ({
t: (key: string) => key,
locale: { value: 'en' }
})
}))
vi.mock('@/stores', () => ({
useAuthStore: () => ({ register: vi.fn() }),
useAppStore: () => ({
showError: vi.fn(),
showSuccess: vi.fn(),
showWarning: vi.fn()
})
}))
vi.mock('@/api/auth', async () => {
const actual = await vi.importActual<typeof import('@/api/auth')>('@/api/auth')
return {
...actual,
getPublicSettings: (...args: unknown[]) => getPublicSettingsMock(...args)
}
})
function mountRegister() {
return mount(RegisterView, {
global: {
stubs: {
AuthLayout: { template: '<div><slot /><slot name="footer" /></div>' },
Icon: true,
TurnstileWidget: { template: '<div data-testid="turnstile-widget" />' },
LoginAgreementPrompt: true,
EmailOAuthButtons: true,
LinuxDoOAuthSection: true,
WechatOAuthSection: true,
OidcOAuthSection: true,
RouterLink: true,
transition: false
}
}
})
}
describe('RegisterView invitation layout', () => {
beforeEach(() => {
getPublicSettingsMock.mockReset()
getPublicSettingsMock.mockResolvedValue(publicSettings)
})
it('keeps the optional affiliate invitation field before Turnstile', async () => {
const wrapper = mountRegister()
await flushPromises()
const invitationField = wrapper.get('[data-testid="affiliate-invitation-field"]')
const turnstile = wrapper.get('[data-testid="registration-turnstile"]')
expect(invitationField.get('input').attributes('id')).toBe('affiliate_code')
expect(invitationField.text()).toContain('common.optional')
expect(
invitationField.element.compareDocumentPosition(turnstile.element) &
Node.DOCUMENT_POSITION_FOLLOWING
).toBeTruthy()
})
it('uses the mandatory invitation field without duplicating the affiliate field', async () => {
getPublicSettingsMock.mockResolvedValueOnce({
...publicSettings,
invitation_code_enabled: true
})
const wrapper = mountRegister()
await flushPromises()
expect(wrapper.find('[data-testid="affiliate-invitation-field"]').exists()).toBe(false)
expect(wrapper.get('#invitation_code').exists()).toBe(true)
})
})