mirror of
https://github.com/NoFxAiOS/nofx.git
synced 2026-07-03 02:50:59 +08:00
feat: add whether to enable self registration toggle (#760)
* refactor(frontend): extract RegistrationDisabled as reusable component - Create RegistrationDisabled component with i18n support - Add registrationClosed and registrationClosedMessage translations - Replace inline JSX in App.tsx with new component - Improve code maintainability and reusability - Add hover effect to back button for better UX * fix(frontend): add registration toggle to LoginModal component - Add useSystemConfig hook to LoginModal - Conditionally render registration button based on registration_enabled config - Ensures consistency with HeaderBar and LoginPage registration controls - Completes registration toggle feature implementation across all entry points * feat(frontend): add registration toggle UI support - Add registration disabled page in App.tsx when registration is closed - Hide registration link in LoginPage when registration is disabled - Add registration_enabled field to SystemConfig interface - Frontend conditionally shows/hides registration UI based on backend config * feat: add registration toggle feature Add system-level registration enable/disable control: - Add registration_enabled config to system_config table (default: true) - Add registration check in handleRegister API endpoint - Expose registration_enabled status in /api/config endpoint - Frontend can use this config to conditionally show/hide registration UI This allows administrators to control user registration without code changes. * fix(frontend): add registration toggle to HeaderBar and RegisterPage - Add useSystemConfig hook and registrationEnabled check to HeaderBar - Conditionally show/hide signup buttons in both desktop and mobile views - Add registration check to RegisterPage to show RegistrationDisabled component - This completes the registration toggle feature across all UI components * test(frontend): add comprehensive unit tests for registration toggle feature - Add RegistrationDisabled component tests (rendering, navigation, styling) - Add registrationToggle logic tests (config handling, edge cases, multi-location consistency) - Configure Vitest with jsdom environment for React component testing - All 80 tests passing (9 new tests for RegistrationDisabled + 21 for toggle logic)
This commit is contained in:
committed by
tangmengqiu
parent
b282045b66
commit
ced6c3d9de
@@ -4,6 +4,7 @@ import { motion } from 'framer-motion'
|
||||
import { Menu, X, ChevronDown } from 'lucide-react'
|
||||
import { t, type Language } from '../i18n/translations'
|
||||
import { Container } from './Container'
|
||||
import { useSystemConfig } from '../hooks/useSystemConfig'
|
||||
|
||||
interface HeaderBarProps {
|
||||
onLoginClick?: () => void
|
||||
@@ -33,6 +34,8 @@ export default function HeaderBar({
|
||||
const [userDropdownOpen, setUserDropdownOpen] = useState(false)
|
||||
const dropdownRef = useRef<HTMLDivElement>(null)
|
||||
const userDropdownRef = useRef<HTMLDivElement>(null)
|
||||
const { config: systemConfig } = useSystemConfig()
|
||||
const registrationEnabled = systemConfig?.registration_enabled !== false
|
||||
|
||||
// Close dropdown when clicking outside
|
||||
useEffect(() => {
|
||||
@@ -464,16 +467,18 @@ export default function HeaderBar({
|
||||
>
|
||||
{t('signIn', language)}
|
||||
</a>
|
||||
<a
|
||||
href="/register"
|
||||
className="px-4 py-2 rounded font-semibold text-sm transition-colors hover:opacity-90"
|
||||
style={{
|
||||
background: 'var(--brand-yellow)',
|
||||
color: 'var(--brand-black)',
|
||||
}}
|
||||
>
|
||||
{t('signUp', language)}
|
||||
</a>
|
||||
{registrationEnabled && (
|
||||
<a
|
||||
href="/register"
|
||||
className="px-4 py-2 rounded font-semibold text-sm transition-colors hover:opacity-90"
|
||||
style={{
|
||||
background: 'var(--brand-yellow)',
|
||||
color: 'var(--brand-black)',
|
||||
}}
|
||||
>
|
||||
{t('signUp', language)}
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
@@ -901,17 +906,19 @@ export default function HeaderBar({
|
||||
>
|
||||
{t('signIn', language)}
|
||||
</a>
|
||||
<a
|
||||
href="/register"
|
||||
className="block w-full px-4 py-2 rounded font-semibold text-sm text-center transition-colors"
|
||||
style={{
|
||||
background: 'var(--brand-yellow)',
|
||||
color: 'var(--brand-black)',
|
||||
}}
|
||||
onClick={() => setMobileMenuOpen(false)}
|
||||
>
|
||||
{t('signUp', language)}
|
||||
</a>
|
||||
{registrationEnabled && (
|
||||
<a
|
||||
href="/register"
|
||||
className="block w-full px-4 py-2 rounded font-semibold text-sm text-center transition-colors"
|
||||
style={{
|
||||
background: 'var(--brand-yellow)',
|
||||
color: 'var(--brand-black)',
|
||||
}}
|
||||
onClick={() => setMobileMenuOpen(false)}
|
||||
>
|
||||
{t('signUp', language)}
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -6,6 +6,7 @@ import { t } from '../i18n/translations'
|
||||
import { Eye, EyeOff } from 'lucide-react'
|
||||
import { Input } from './ui/input'
|
||||
import { toast } from 'sonner'
|
||||
import { useSystemConfig } from '../hooks/useSystemConfig'
|
||||
|
||||
export function LoginPage() {
|
||||
const { language } = useLanguage()
|
||||
@@ -21,6 +22,8 @@ export function LoginPage() {
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [adminPassword, setAdminPassword] = useState('')
|
||||
const adminMode = false
|
||||
const { config: systemConfig } = useSystemConfig()
|
||||
const registrationEnabled = systemConfig?.registration_enabled !== false
|
||||
|
||||
const handleAdminLogin = async (e: React.FormEvent) => {
|
||||
e.preventDefault()
|
||||
@@ -313,7 +316,7 @@ export function LoginPage() {
|
||||
</div>
|
||||
|
||||
{/* Register Link */}
|
||||
{!adminMode && (
|
||||
{!adminMode && registrationEnabled && (
|
||||
<div className="text-center mt-6">
|
||||
<p className="text-sm" style={{ color: 'var(--text-secondary)' }}>
|
||||
还没有账户?{' '}
|
||||
|
||||
@@ -9,6 +9,7 @@ import { copyWithToast } from '../lib/clipboard'
|
||||
import { Eye, EyeOff } from 'lucide-react'
|
||||
import { Input } from './ui/input'
|
||||
import PasswordChecklist from 'react-password-checklist'
|
||||
import { RegistrationDisabled } from './RegistrationDisabled'
|
||||
|
||||
export function RegisterPage() {
|
||||
const { language } = useLanguage()
|
||||
@@ -22,6 +23,7 @@ export function RegisterPage() {
|
||||
const [confirmPassword, setConfirmPassword] = useState('')
|
||||
const [betaCode, setBetaCode] = useState('')
|
||||
const [betaMode, setBetaMode] = useState(false)
|
||||
const [registrationEnabled, setRegistrationEnabled] = useState(true)
|
||||
const [otpCode, setOtpCode] = useState('')
|
||||
const [userID, setUserID] = useState('')
|
||||
const [otpSecret, setOtpSecret] = useState('')
|
||||
@@ -33,16 +35,22 @@ export function RegisterPage() {
|
||||
const [showConfirmPassword, setShowConfirmPassword] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
// 获取系统配置,检查是否开启内测模式
|
||||
// 获取系统配置,检查是否开启内测模式和注册功能
|
||||
getSystemConfig()
|
||||
.then((config) => {
|
||||
setBetaMode(config.beta_mode || false)
|
||||
setRegistrationEnabled(config.registration_enabled !== false)
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error('Failed to fetch system config:', err)
|
||||
})
|
||||
}, [])
|
||||
|
||||
// 如果注册功能被禁用,显示注册已关闭页面
|
||||
if (!registrationEnabled) {
|
||||
return <RegistrationDisabled />
|
||||
}
|
||||
|
||||
const handleRegister = async (e: React.FormEvent) => {
|
||||
e.preventDefault()
|
||||
setError('')
|
||||
|
||||
103
web/src/components/RegistrationDisabled.test.tsx
Normal file
103
web/src/components/RegistrationDisabled.test.tsx
Normal file
@@ -0,0 +1,103 @@
|
||||
import { describe, it, expect, vi } from 'vitest'
|
||||
import { render, screen, fireEvent } from '@testing-library/react'
|
||||
import { RegistrationDisabled } from './RegistrationDisabled'
|
||||
import { LanguageProvider } from '../contexts/LanguageContext'
|
||||
|
||||
// Mock useLanguage hook
|
||||
vi.mock('../contexts/LanguageContext', async () => {
|
||||
const actual = await vi.importActual('../contexts/LanguageContext')
|
||||
return {
|
||||
...actual,
|
||||
useLanguage: () => ({ language: 'en' }),
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* RegistrationDisabled Component Tests
|
||||
*
|
||||
* Tests the component that displays when registration is disabled
|
||||
* This is part of the registration toggle feature
|
||||
*/
|
||||
describe('RegistrationDisabled Component', () => {
|
||||
const renderComponent = () => {
|
||||
return render(
|
||||
<LanguageProvider>
|
||||
<RegistrationDisabled />
|
||||
</LanguageProvider>
|
||||
)
|
||||
}
|
||||
|
||||
describe('Rendering', () => {
|
||||
it('should render the component without errors', () => {
|
||||
const { container } = renderComponent()
|
||||
expect(container).toBeTruthy()
|
||||
})
|
||||
|
||||
it('should display the NoFx logo', () => {
|
||||
renderComponent()
|
||||
const logo = screen.getByAltText('NoFx Logo')
|
||||
expect(logo).toBeTruthy()
|
||||
expect(logo.getAttribute('src')).toBe('/icons/nofx.svg')
|
||||
})
|
||||
|
||||
it('should display registration closed heading', () => {
|
||||
renderComponent()
|
||||
const heading = screen.getByText('Registration Closed')
|
||||
expect(heading).toBeTruthy()
|
||||
})
|
||||
|
||||
it('should display registration closed message', () => {
|
||||
renderComponent()
|
||||
const message = screen.getByText(/User registration is currently disabled/i)
|
||||
expect(message).toBeTruthy()
|
||||
})
|
||||
|
||||
it('should display back to login button', () => {
|
||||
renderComponent()
|
||||
const button = screen.getByRole('button', { name: /back to login/i })
|
||||
expect(button).toBeTruthy()
|
||||
})
|
||||
})
|
||||
|
||||
describe('Navigation', () => {
|
||||
it('should navigate to login page when button is clicked', () => {
|
||||
const pushStateSpy = vi.spyOn(window.history, 'pushState')
|
||||
const dispatchEventSpy = vi.spyOn(window, 'dispatchEvent')
|
||||
|
||||
renderComponent()
|
||||
const button = screen.getByRole('button', { name: /back to login/i })
|
||||
|
||||
fireEvent.click(button)
|
||||
|
||||
expect(pushStateSpy).toHaveBeenCalledWith({}, '', '/login')
|
||||
expect(dispatchEventSpy).toHaveBeenCalled()
|
||||
|
||||
pushStateSpy.mockRestore()
|
||||
dispatchEventSpy.mockRestore()
|
||||
})
|
||||
})
|
||||
|
||||
describe('Styling', () => {
|
||||
it('should have correct background color', () => {
|
||||
const { container } = renderComponent()
|
||||
const mainDiv = container.firstChild as HTMLElement
|
||||
// Browser converts hex to rgb
|
||||
expect(mainDiv.style.background).toMatch(/rgb\(11,\s*14,\s*17\)|#0B0E11/i)
|
||||
})
|
||||
|
||||
it('should have correct text color', () => {
|
||||
const { container } = renderComponent()
|
||||
const mainDiv = container.firstChild as HTMLElement
|
||||
// Browser converts hex to rgb
|
||||
expect(mainDiv.style.color).toMatch(/rgb\(234,\s*236,\s*239\)|#EAECEF/i)
|
||||
})
|
||||
|
||||
it('should have centered layout', () => {
|
||||
const { container } = renderComponent()
|
||||
const mainDiv = container.firstChild as HTMLElement
|
||||
expect(mainDiv.className).toContain('flex')
|
||||
expect(mainDiv.className).toContain('items-center')
|
||||
expect(mainDiv.className).toContain('justify-center')
|
||||
})
|
||||
})
|
||||
})
|
||||
39
web/src/components/RegistrationDisabled.tsx
Normal file
39
web/src/components/RegistrationDisabled.tsx
Normal file
@@ -0,0 +1,39 @@
|
||||
import { useLanguage } from '../contexts/LanguageContext'
|
||||
import { t } from '../i18n/translations'
|
||||
|
||||
export function RegistrationDisabled() {
|
||||
const { language } = useLanguage()
|
||||
|
||||
const handleBackToLogin = () => {
|
||||
window.history.pushState({}, '', '/login')
|
||||
window.dispatchEvent(new PopStateEvent('popstate'))
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className="min-h-screen flex items-center justify-center"
|
||||
style={{ background: '#0B0E11', color: '#EAECEF' }}
|
||||
>
|
||||
<div className="text-center max-w-md px-6">
|
||||
<img
|
||||
src="/icons/nofx.svg"
|
||||
alt="NoFx Logo"
|
||||
className="w-16 h-16 mx-auto mb-4"
|
||||
/>
|
||||
<h1 className="text-2xl font-semibold mb-3">
|
||||
{t('registrationClosed', language)}
|
||||
</h1>
|
||||
<p className="text-sm text-gray-400">
|
||||
{t('registrationClosedMessage', language)}
|
||||
</p>
|
||||
<button
|
||||
className="mt-6 px-4 py-2 rounded text-sm font-semibold transition-colors hover:opacity-90"
|
||||
style={{ background: '#F0B90B', color: '#000' }}
|
||||
onClick={handleBackToLogin}
|
||||
>
|
||||
{t('backToLogin', language)}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import { motion } from 'framer-motion'
|
||||
import { X } from 'lucide-react'
|
||||
import { t, Language } from '../../i18n/translations'
|
||||
import { useSystemConfig } from '../../hooks/useSystemConfig'
|
||||
|
||||
interface LoginModalProps {
|
||||
onClose: () => void
|
||||
@@ -8,6 +9,9 @@ interface LoginModalProps {
|
||||
}
|
||||
|
||||
export default function LoginModal({ onClose, language }: LoginModalProps) {
|
||||
const { config: systemConfig } = useSystemConfig()
|
||||
const registrationEnabled = systemConfig?.registration_enabled !== false
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
className="fixed inset-0 z-50 flex items-center justify-center p-4"
|
||||
@@ -66,23 +70,25 @@ export default function LoginModal({ onClose, language }: LoginModalProps) {
|
||||
>
|
||||
{t('signIn', language)}
|
||||
</motion.button>
|
||||
<motion.button
|
||||
onClick={() => {
|
||||
window.history.pushState({}, '', '/register')
|
||||
window.dispatchEvent(new PopStateEvent('popstate'))
|
||||
onClose()
|
||||
}}
|
||||
className="block w-full px-6 py-3 rounded-lg font-semibold text-center"
|
||||
style={{
|
||||
background: 'var(--brand-dark-gray)',
|
||||
color: 'var(--brand-light-gray)',
|
||||
border: '1px solid rgba(240, 185, 11, 0.2)',
|
||||
}}
|
||||
whileHover={{ scale: 1.05, borderColor: 'var(--brand-yellow)' }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
>
|
||||
{t('registerNewAccount', language)}
|
||||
</motion.button>
|
||||
{registrationEnabled && (
|
||||
<motion.button
|
||||
onClick={() => {
|
||||
window.history.pushState({}, '', '/register')
|
||||
window.dispatchEvent(new PopStateEvent('popstate'))
|
||||
onClose()
|
||||
}}
|
||||
className="block w-full px-6 py-3 rounded-lg font-semibold text-center"
|
||||
style={{
|
||||
background: 'var(--brand-dark-gray)',
|
||||
color: 'var(--brand-light-gray)',
|
||||
border: '1px solid rgba(240, 185, 11, 0.2)',
|
||||
}}
|
||||
whileHover={{ scale: 1.05, borderColor: 'var(--brand-yellow)' }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
>
|
||||
{t('registerNewAccount', language)}
|
||||
</motion.button>
|
||||
)}
|
||||
</div>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
|
||||
@@ -496,6 +496,9 @@ export const translations = {
|
||||
exitLogin: 'Sign Out',
|
||||
signIn: 'Sign In',
|
||||
signUp: 'Sign Up',
|
||||
registrationClosed: 'Registration Closed',
|
||||
registrationClosedMessage:
|
||||
'User registration is currently disabled. Please contact the administrator for access.',
|
||||
|
||||
// Hero Section
|
||||
githubStarsInDays: '2.5K+ GitHub Stars in 3 days',
|
||||
@@ -1305,6 +1308,8 @@ export const translations = {
|
||||
exitLogin: '退出登录',
|
||||
signIn: '登录',
|
||||
signUp: '注册',
|
||||
registrationClosed: '注册已关闭',
|
||||
registrationClosedMessage: '平台当前不开放新用户注册,如需访问请联系管理员获取账号。',
|
||||
|
||||
// Hero Section
|
||||
githubStarsInDays: '3 天内 2.5K+ GitHub Stars',
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
export interface SystemConfig {
|
||||
beta_mode: boolean
|
||||
registration_enabled?: boolean
|
||||
}
|
||||
|
||||
let configPromise: Promise<SystemConfig> | null = null
|
||||
|
||||
204
web/src/lib/registrationToggle.test.ts
Normal file
204
web/src/lib/registrationToggle.test.ts
Normal file
@@ -0,0 +1,204 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
|
||||
/**
|
||||
* Registration Toggle Feature Tests
|
||||
*
|
||||
* Tests the logic for determining whether registration is enabled
|
||||
* This validates the registration_enabled configuration behavior
|
||||
*/
|
||||
describe('Registration Toggle Logic', () => {
|
||||
describe('registration_enabled configuration', () => {
|
||||
it('should default to true when registration_enabled is undefined', () => {
|
||||
const config = {}
|
||||
const registrationEnabled = (config as any).registration_enabled !== false
|
||||
|
||||
expect(registrationEnabled).toBe(true)
|
||||
})
|
||||
|
||||
it('should be true when registration_enabled is explicitly true', () => {
|
||||
const config = { registration_enabled: true }
|
||||
const registrationEnabled = config.registration_enabled !== false
|
||||
|
||||
expect(registrationEnabled).toBe(true)
|
||||
})
|
||||
|
||||
it('should be false when registration_enabled is explicitly false', () => {
|
||||
const config = { registration_enabled: false }
|
||||
const registrationEnabled = config.registration_enabled !== false
|
||||
|
||||
expect(registrationEnabled).toBe(false)
|
||||
})
|
||||
|
||||
it('should default to true when registration_enabled is null', () => {
|
||||
const config = { registration_enabled: null }
|
||||
const registrationEnabled = (config.registration_enabled as any) !== false
|
||||
|
||||
expect(registrationEnabled).toBe(true)
|
||||
})
|
||||
|
||||
it('should handle missing config gracefully', () => {
|
||||
const config = null
|
||||
const registrationEnabled = config?.registration_enabled !== false
|
||||
|
||||
expect(registrationEnabled).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('UI component visibility logic', () => {
|
||||
it('should show signup button when registration is enabled', () => {
|
||||
const registrationEnabled = true
|
||||
const shouldShowSignup = registrationEnabled
|
||||
|
||||
expect(shouldShowSignup).toBe(true)
|
||||
})
|
||||
|
||||
it('should hide signup button when registration is disabled', () => {
|
||||
const registrationEnabled = false
|
||||
const shouldShowSignup = registrationEnabled
|
||||
|
||||
expect(shouldShowSignup).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('conditional rendering patterns', () => {
|
||||
it('should render signup link with registrationEnabled && pattern', () => {
|
||||
const registrationEnabled = true
|
||||
const signupElement = registrationEnabled && 'SignUpButton'
|
||||
|
||||
expect(signupElement).toBe('SignUpButton')
|
||||
})
|
||||
|
||||
it('should not render signup link when disabled', () => {
|
||||
const registrationEnabled = false
|
||||
const signupElement = registrationEnabled && 'SignUpButton'
|
||||
|
||||
expect(signupElement).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('SystemConfig interface compliance', () => {
|
||||
interface SystemConfig {
|
||||
beta_mode: boolean
|
||||
registration_enabled?: boolean
|
||||
}
|
||||
|
||||
it('should have optional registration_enabled field', () => {
|
||||
const config1: SystemConfig = {
|
||||
beta_mode: false,
|
||||
}
|
||||
|
||||
const config2: SystemConfig = {
|
||||
beta_mode: false,
|
||||
registration_enabled: true,
|
||||
}
|
||||
|
||||
expect(config1.beta_mode).toBe(false)
|
||||
expect(config2.registration_enabled).toBe(true)
|
||||
})
|
||||
|
||||
it('should handle both beta_mode and registration_enabled', () => {
|
||||
const config: SystemConfig = {
|
||||
beta_mode: true,
|
||||
registration_enabled: false,
|
||||
}
|
||||
|
||||
expect(config.beta_mode).toBe(true)
|
||||
expect(config.registration_enabled).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('edge cases', () => {
|
||||
it('should treat empty string as truthy (not false)', () => {
|
||||
const config = { registration_enabled: '' as any }
|
||||
const registrationEnabled = config.registration_enabled !== false
|
||||
|
||||
expect(registrationEnabled).toBe(true)
|
||||
})
|
||||
|
||||
it('should treat 0 as truthy (not false)', () => {
|
||||
const config = { registration_enabled: 0 as any }
|
||||
const registrationEnabled = config.registration_enabled !== false
|
||||
|
||||
expect(registrationEnabled).toBe(true)
|
||||
})
|
||||
|
||||
it('should treat "false" string as truthy (not false)', () => {
|
||||
const config = { registration_enabled: 'false' as any }
|
||||
const registrationEnabled = config.registration_enabled !== false
|
||||
|
||||
expect(registrationEnabled).toBe(true)
|
||||
})
|
||||
|
||||
it('should only treat boolean false as disabled', () => {
|
||||
const testCases = [
|
||||
{ value: false, expected: false },
|
||||
{ value: true, expected: true },
|
||||
{ value: null, expected: true },
|
||||
{ value: undefined, expected: true },
|
||||
{ value: 0, expected: true },
|
||||
{ value: '', expected: true },
|
||||
{ value: 'false', expected: true },
|
||||
{ value: [], expected: true },
|
||||
{ value: {}, expected: true },
|
||||
]
|
||||
|
||||
testCases.forEach(({ value, expected }) => {
|
||||
const config = { registration_enabled: value as any }
|
||||
const registrationEnabled = config.registration_enabled !== false
|
||||
expect(registrationEnabled).toBe(expected)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('backend API response handling', () => {
|
||||
it('should parse backend response with registration_enabled', () => {
|
||||
const apiResponse = {
|
||||
beta_mode: false,
|
||||
default_coins: ['BTCUSDT'],
|
||||
btc_eth_leverage: 5,
|
||||
altcoin_leverage: 5,
|
||||
registration_enabled: true,
|
||||
}
|
||||
|
||||
expect(apiResponse.registration_enabled).toBe(true)
|
||||
})
|
||||
|
||||
it('should handle backend response without registration_enabled', () => {
|
||||
const apiResponse = {
|
||||
beta_mode: false,
|
||||
default_coins: ['BTCUSDT'],
|
||||
btc_eth_leverage: 5,
|
||||
altcoin_leverage: 5,
|
||||
}
|
||||
|
||||
const registrationEnabled =
|
||||
(apiResponse as any).registration_enabled !== false
|
||||
|
||||
expect(registrationEnabled).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('multi-location consistency', () => {
|
||||
const systemConfig = { registration_enabled: false }
|
||||
|
||||
it('should have consistent behavior across LoginPage', () => {
|
||||
const registrationEnabled = systemConfig?.registration_enabled !== false
|
||||
expect(registrationEnabled).toBe(false)
|
||||
})
|
||||
|
||||
it('should have consistent behavior across RegisterPage', () => {
|
||||
const registrationEnabled = systemConfig?.registration_enabled !== false
|
||||
expect(registrationEnabled).toBe(false)
|
||||
})
|
||||
|
||||
it('should have consistent behavior across HeaderBar', () => {
|
||||
const registrationEnabled = systemConfig?.registration_enabled !== false
|
||||
expect(registrationEnabled).toBe(false)
|
||||
})
|
||||
|
||||
it('should have consistent behavior across LoginModal', () => {
|
||||
const registrationEnabled = systemConfig?.registration_enabled !== false
|
||||
expect(registrationEnabled).toBe(false)
|
||||
})
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user