mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-07 12:57:57 +08:00
feat(home): add compact home page preset to avoid abuse classification
- Add compact_home_enabled setting to provide a minimal landing page - Preserves custom home_content priority over compact mode - Renders only site identity, navigation, and login/dashboard link - Avoids marketing copy that triggers anti-fraud systems - Includes focused backend/frontend tests and i18n support Fixes #5065
This commit is contained in:
@@ -233,6 +233,7 @@ func (h *SettingHandler) GetSettings(c *gin.Context) {
|
||||
ContactInfo: settings.ContactInfo,
|
||||
DocURL: settings.DocURL,
|
||||
HomeContent: settings.HomeContent,
|
||||
CompactHomeEnabled: settings.CompactHomeEnabled,
|
||||
HideCcsImportButton: settings.HideCcsImportButton,
|
||||
PurchaseSubscriptionEnabled: settings.PurchaseSubscriptionEnabled,
|
||||
PurchaseSubscriptionURL: settings.PurchaseSubscriptionURL,
|
||||
|
||||
@@ -299,6 +299,9 @@ func diffSettings(before *service.SystemSettings, after *service.SystemSettings,
|
||||
if before.HomeContent != after.HomeContent {
|
||||
changed = append(changed, "home_content")
|
||||
}
|
||||
if before.CompactHomeEnabled != after.CompactHomeEnabled {
|
||||
changed = append(changed, "compact_home_enabled")
|
||||
}
|
||||
if before.HideCcsImportButton != after.HideCcsImportButton {
|
||||
changed = append(changed, "hide_ccs_import_button")
|
||||
}
|
||||
|
||||
@@ -64,6 +64,18 @@ func TestDiffSettings_NoChangeWhenEqual(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestDiffSettings_DetectsCompactHomeChange(t *testing.T) {
|
||||
changed := diffSettings(
|
||||
&service.SystemSettings{},
|
||||
&service.SystemSettings{CompactHomeEnabled: true},
|
||||
nil,
|
||||
nil,
|
||||
UpdateSettingsRequest{},
|
||||
)
|
||||
|
||||
require.Contains(t, changed, service.SettingKeyCompactHomeEnabled)
|
||||
}
|
||||
|
||||
func TestEqualNullableFloat(t *testing.T) {
|
||||
five := 5.0
|
||||
five2 := 5.0
|
||||
|
||||
@@ -142,6 +142,7 @@ type UpdateSettingsRequest struct {
|
||||
ContactInfo string `json:"contact_info"`
|
||||
DocURL string `json:"doc_url"`
|
||||
HomeContent string `json:"home_content"`
|
||||
CompactHomeEnabled bool `json:"compact_home_enabled"`
|
||||
HideCcsImportButton bool `json:"hide_ccs_import_button"`
|
||||
PurchaseSubscriptionEnabled *bool `json:"purchase_subscription_enabled"`
|
||||
PurchaseSubscriptionURL *string `json:"purchase_subscription_url"`
|
||||
@@ -1430,6 +1431,7 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) {
|
||||
ContactInfo: req.ContactInfo,
|
||||
DocURL: req.DocURL,
|
||||
HomeContent: req.HomeContent,
|
||||
CompactHomeEnabled: req.CompactHomeEnabled,
|
||||
HideCcsImportButton: req.HideCcsImportButton,
|
||||
PurchaseSubscriptionEnabled: purchaseEnabled,
|
||||
PurchaseSubscriptionURL: purchaseURL,
|
||||
@@ -1982,6 +1984,7 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) {
|
||||
ContactInfo: updatedSettings.ContactInfo,
|
||||
DocURL: updatedSettings.DocURL,
|
||||
HomeContent: updatedSettings.HomeContent,
|
||||
CompactHomeEnabled: updatedSettings.CompactHomeEnabled,
|
||||
HideCcsImportButton: updatedSettings.HideCcsImportButton,
|
||||
PurchaseSubscriptionEnabled: updatedSettings.PurchaseSubscriptionEnabled,
|
||||
PurchaseSubscriptionURL: updatedSettings.PurchaseSubscriptionURL,
|
||||
|
||||
@@ -142,6 +142,7 @@ type SystemSettings struct {
|
||||
ContactInfo string `json:"contact_info"`
|
||||
DocURL string `json:"doc_url"`
|
||||
HomeContent string `json:"home_content"`
|
||||
CompactHomeEnabled bool `json:"compact_home_enabled"`
|
||||
HideCcsImportButton bool `json:"hide_ccs_import_button"`
|
||||
PurchaseSubscriptionEnabled bool `json:"purchase_subscription_enabled"`
|
||||
PurchaseSubscriptionURL string `json:"purchase_subscription_url"`
|
||||
@@ -344,6 +345,7 @@ type PublicSettings struct {
|
||||
ContactInfo string `json:"contact_info"`
|
||||
DocURL string `json:"doc_url"`
|
||||
HomeContent string `json:"home_content"`
|
||||
CompactHomeEnabled bool `json:"compact_home_enabled"`
|
||||
HideCcsImportButton bool `json:"hide_ccs_import_button"`
|
||||
PurchaseSubscriptionEnabled bool `json:"purchase_subscription_enabled"`
|
||||
PurchaseSubscriptionURL string `json:"purchase_subscription_url"`
|
||||
|
||||
@@ -67,6 +67,7 @@ func (h *SettingHandler) GetPublicSettings(c *gin.Context) {
|
||||
ContactInfo: settings.ContactInfo,
|
||||
DocURL: settings.DocURL,
|
||||
HomeContent: settings.HomeContent,
|
||||
CompactHomeEnabled: settings.CompactHomeEnabled,
|
||||
HideCcsImportButton: settings.HideCcsImportButton,
|
||||
PurchaseSubscriptionEnabled: settings.PurchaseSubscriptionEnabled,
|
||||
PurchaseSubscriptionURL: settings.PurchaseSubscriptionURL,
|
||||
|
||||
@@ -271,6 +271,7 @@ const (
|
||||
SettingKeyContactInfo = "contact_info" // 客服联系方式
|
||||
SettingKeyDocURL = "doc_url" // 文档链接
|
||||
SettingKeyHomeContent = "home_content" // 首页内容(支持 Markdown/HTML,或 URL 作为 iframe src)
|
||||
SettingKeyCompactHomeEnabled = "compact_home_enabled" // 是否启用内置简洁首页
|
||||
SettingKeyHideCcsImportButton = "hide_ccs_import_button" // 是否隐藏 API Keys 页面的导入 CCS 按钮
|
||||
SettingKeyPurchaseSubscriptionEnabled = "purchase_subscription_enabled" // 是否展示"购买订阅"页面入口
|
||||
SettingKeyPurchaseSubscriptionURL = "purchase_subscription_url" // "购买订阅"页面 URL(作为 iframe src)
|
||||
|
||||
@@ -330,6 +330,7 @@ func (s *SettingService) parseSettings(settings map[string]string) *SystemSettin
|
||||
ContactInfo: settings[SettingKeyContactInfo],
|
||||
DocURL: settings[SettingKeyDocURL],
|
||||
HomeContent: settings[SettingKeyHomeContent],
|
||||
CompactHomeEnabled: settings[SettingKeyCompactHomeEnabled] == "true",
|
||||
HideCcsImportButton: settings[SettingKeyHideCcsImportButton] == "true",
|
||||
PurchaseSubscriptionEnabled: settings[SettingKeyPurchaseSubscriptionEnabled] == "true",
|
||||
PurchaseSubscriptionURL: strings.TrimSpace(settings[SettingKeyPurchaseSubscriptionURL]),
|
||||
|
||||
@@ -179,6 +179,7 @@ func (s *SettingService) GetPublicSettings(ctx context.Context) (*PublicSettings
|
||||
SettingKeyContactInfo,
|
||||
SettingKeyDocURL,
|
||||
SettingKeyHomeContent,
|
||||
SettingKeyCompactHomeEnabled,
|
||||
SettingKeyHideCcsImportButton,
|
||||
SettingKeyPurchaseSubscriptionEnabled,
|
||||
SettingKeyPurchaseSubscriptionURL,
|
||||
@@ -307,6 +308,7 @@ func (s *SettingService) GetPublicSettings(ctx context.Context) (*PublicSettings
|
||||
ContactInfo: settings[SettingKeyContactInfo],
|
||||
DocURL: settings[SettingKeyDocURL],
|
||||
HomeContent: settings[SettingKeyHomeContent],
|
||||
CompactHomeEnabled: settings[SettingKeyCompactHomeEnabled] == "true",
|
||||
HideCcsImportButton: settings[SettingKeyHideCcsImportButton] == "true",
|
||||
PurchaseSubscriptionEnabled: settings[SettingKeyPurchaseSubscriptionEnabled] == "true",
|
||||
PurchaseSubscriptionURL: strings.TrimSpace(settings[SettingKeyPurchaseSubscriptionURL]),
|
||||
@@ -495,6 +497,7 @@ type PublicSettingsInjectionPayload struct {
|
||||
ContactInfo string `json:"contact_info"`
|
||||
DocURL string `json:"doc_url"`
|
||||
HomeContent string `json:"home_content"`
|
||||
CompactHomeEnabled bool `json:"compact_home_enabled"`
|
||||
HideCcsImportButton bool `json:"hide_ccs_import_button"`
|
||||
PurchaseSubscriptionEnabled bool `json:"purchase_subscription_enabled"`
|
||||
PurchaseSubscriptionURL string `json:"purchase_subscription_url"`
|
||||
@@ -567,6 +570,7 @@ func (s *SettingService) GetPublicSettingsForInjection(ctx context.Context) (any
|
||||
ContactInfo: settings.ContactInfo,
|
||||
DocURL: settings.DocURL,
|
||||
HomeContent: settings.HomeContent,
|
||||
CompactHomeEnabled: settings.CompactHomeEnabled,
|
||||
HideCcsImportButton: settings.HideCcsImportButton,
|
||||
PurchaseSubscriptionEnabled: settings.PurchaseSubscriptionEnabled,
|
||||
PurchaseSubscriptionURL: settings.PurchaseSubscriptionURL,
|
||||
|
||||
@@ -78,6 +78,25 @@ func TestSettingService_GetPublicSettings_ExposesTablePreferences(t *testing.T)
|
||||
require.Equal(t, []int{20, 50, 100}, settings.TablePageSizeOptions)
|
||||
}
|
||||
|
||||
func TestSettingService_GetPublicSettings_ExposesCompactHomeEnabled(t *testing.T) {
|
||||
repo := &settingPublicRepoStub{
|
||||
values: map[string]string{
|
||||
SettingKeyCompactHomeEnabled: "true",
|
||||
},
|
||||
}
|
||||
svc := NewSettingService(repo, &config.Config{})
|
||||
|
||||
settings, err := svc.GetPublicSettings(context.Background())
|
||||
|
||||
require.NoError(t, err)
|
||||
require.True(t, settings.CompactHomeEnabled)
|
||||
|
||||
missingSettings, err := NewSettingService(&settingPublicRepoStub{values: map[string]string{}}, &config.Config{}).
|
||||
GetPublicSettings(context.Background())
|
||||
require.NoError(t, err)
|
||||
require.False(t, missingSettings.CompactHomeEnabled)
|
||||
}
|
||||
|
||||
func TestSettingService_GetPublicSettings_ExposesForceEmailOnThirdPartySignup(t *testing.T) {
|
||||
repo := &settingPublicRepoStub{
|
||||
values: map[string]string{
|
||||
|
||||
@@ -228,6 +228,16 @@ func (s *defaultSubGroupReaderStub) GetByID(ctx context.Context, id int64) (*Gro
|
||||
return nil, ErrGroupNotFound
|
||||
}
|
||||
|
||||
func TestSettingService_UpdateSettings_PersistsCompactHomeEnabled(t *testing.T) {
|
||||
repo := &settingUpdateRepoStub{}
|
||||
svc := NewSettingService(repo, &config.Config{})
|
||||
|
||||
err := svc.UpdateSettings(context.Background(), &SystemSettings{CompactHomeEnabled: true})
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "true", repo.updates[SettingKeyCompactHomeEnabled])
|
||||
}
|
||||
|
||||
func TestSettingService_UpdateSettings_DefaultSubscriptions_ValidGroup(t *testing.T) {
|
||||
repo := &settingUpdateRepoStub{}
|
||||
groupReader := &defaultSubGroupReaderStub{
|
||||
|
||||
@@ -320,6 +320,7 @@ func (s *SettingService) buildSystemSettingsUpdates(ctx context.Context, setting
|
||||
updates[SettingKeyContactInfo] = settings.ContactInfo
|
||||
updates[SettingKeyDocURL] = settings.DocURL
|
||||
updates[SettingKeyHomeContent] = settings.HomeContent
|
||||
updates[SettingKeyCompactHomeEnabled] = strconv.FormatBool(settings.CompactHomeEnabled)
|
||||
updates[SettingKeyHideCcsImportButton] = strconv.FormatBool(settings.HideCcsImportButton)
|
||||
updates[SettingKeyPurchaseSubscriptionEnabled] = strconv.FormatBool(settings.PurchaseSubscriptionEnabled)
|
||||
updates[SettingKeyPurchaseSubscriptionURL] = strings.TrimSpace(settings.PurchaseSubscriptionURL)
|
||||
|
||||
@@ -139,6 +139,7 @@ type SystemSettings struct {
|
||||
ContactInfo string
|
||||
DocURL string
|
||||
HomeContent string
|
||||
CompactHomeEnabled bool
|
||||
HideCcsImportButton bool
|
||||
PurchaseSubscriptionEnabled bool
|
||||
PurchaseSubscriptionURL string
|
||||
@@ -305,6 +306,7 @@ type PublicSettings struct {
|
||||
ContactInfo string
|
||||
DocURL string
|
||||
HomeContent string
|
||||
CompactHomeEnabled bool
|
||||
HideCcsImportButton bool
|
||||
|
||||
PurchaseSubscriptionEnabled bool
|
||||
|
||||
@@ -440,6 +440,7 @@ export interface SystemSettings {
|
||||
contact_info: string;
|
||||
doc_url: string;
|
||||
home_content: string;
|
||||
compact_home_enabled: boolean;
|
||||
hide_ccs_import_button: boolean;
|
||||
table_default_page_size: number;
|
||||
table_page_size_options: number[];
|
||||
@@ -752,6 +753,7 @@ export interface UpdateSettingsRequest {
|
||||
contact_info?: string;
|
||||
doc_url?: string;
|
||||
home_content?: string;
|
||||
compact_home_enabled?: boolean;
|
||||
hide_ccs_import_button?: boolean;
|
||||
table_default_page_size?: number;
|
||||
table_page_size_options?: number[];
|
||||
|
||||
@@ -73,6 +73,7 @@ function buildPublicSettings(overrides: Partial<WeChatPublicSettings> = {}): WeC
|
||||
contact_info: '',
|
||||
doc_url: '',
|
||||
home_content: '',
|
||||
compact_home_enabled: false,
|
||||
hide_ccs_import_button: false,
|
||||
payment_enabled: false,
|
||||
table_default_page_size: 20,
|
||||
|
||||
@@ -250,6 +250,7 @@ describe('ProfileIdentityBindingsSection', () => {
|
||||
contact_info: '',
|
||||
doc_url: '',
|
||||
home_content: '',
|
||||
compact_home_enabled: false,
|
||||
hide_ccs_import_button: false,
|
||||
payment_enabled: false,
|
||||
table_default_page_size: 20,
|
||||
|
||||
@@ -548,6 +548,8 @@ export default {
|
||||
homeContentPlaceholder: 'Enter custom content for the home page. Supports Markdown & HTML. If a URL is entered, it will be displayed as an iframe.',
|
||||
homeContentHint: 'Customize the home page content. Supports Markdown/HTML. If you enter a URL (starting with http:// or https://), it will be used as an iframe src to embed an external page. When set, the default status information will no longer be displayed.',
|
||||
homeContentIframeWarning: '⚠️ iframe mode note: Some websites have X-Frame-Options or CSP security policies that prevent embedding in iframes. If the page appears blank or shows an error, please verify the target website allows embedding, or consider using HTML mode to build your own content.',
|
||||
compactHome: 'Compact Home Page',
|
||||
compactHomeHint: 'Show a restrained site identity page when no custom home page content is set.',
|
||||
hideCcsImportButton: 'Hide CCS Import Button',
|
||||
hideCcsImportButtonHint: 'When enabled, the "Import to CCS" button will be hidden on the API Keys page'
|
||||
},
|
||||
|
||||
@@ -543,6 +543,8 @@ export default {
|
||||
'自定义首页内容,支持 Markdown/HTML。如果输入的是链接(以 http:// 或 https:// 开头),则会使用该链接作为 iframe 的 src 属性,这允许你设置任意网页作为首页。设置后首页的状态信息将不再显示。',
|
||||
homeContentIframeWarning:
|
||||
'⚠️ iframe 模式提示:部分网站设置了 X-Frame-Options 或 CSP 安全策略,禁止被嵌入到 iframe 中。如果页面显示空白或报错,请确认目标网站允许被嵌入,或考虑使用 HTML 模式自行构建页面内容。',
|
||||
compactHome: '简洁首页',
|
||||
compactHomeHint: '未设置自定义首页内容时,展示简洁的站点信息页面。',
|
||||
hideCcsImportButton: '隐藏 CCS 导入按钮',
|
||||
hideCcsImportButtonHint: '启用后将在 API Keys 页面隐藏"导入 CCS"按钮'
|
||||
},
|
||||
|
||||
@@ -33,6 +33,7 @@ function createPublicSettings(overrides: Partial<PublicSettings> = {}): PublicSe
|
||||
contact_info: '',
|
||||
doc_url: '',
|
||||
home_content: '',
|
||||
compact_home_enabled: false,
|
||||
hide_ccs_import_button: false,
|
||||
payment_enabled: false,
|
||||
risk_control_enabled: false,
|
||||
@@ -452,6 +453,7 @@ describe('useAppStore', () => {
|
||||
contact_info: '',
|
||||
doc_url: '',
|
||||
home_content: '',
|
||||
compact_home_enabled: false,
|
||||
hide_ccs_import_button: false,
|
||||
purchase_subscription_enabled: false,
|
||||
purchase_subscription_url: '',
|
||||
|
||||
@@ -342,6 +342,7 @@ export const useAppStore = defineStore('app', () => {
|
||||
contact_info: contactInfo.value,
|
||||
doc_url: docUrl.value,
|
||||
home_content: '',
|
||||
compact_home_enabled: false,
|
||||
hide_ccs_import_button: false,
|
||||
payment_enabled: false,
|
||||
table_default_page_size: 20,
|
||||
|
||||
@@ -210,6 +210,7 @@ export interface PublicSettings {
|
||||
contact_info: string
|
||||
doc_url: string
|
||||
home_content: string
|
||||
compact_home_enabled: boolean
|
||||
hide_ccs_import_button: boolean
|
||||
payment_enabled: boolean
|
||||
risk_control_enabled: boolean
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<!-- Custom Home Content: Full Page Mode -->
|
||||
<div v-if="homeContent" class="min-h-screen">
|
||||
<div v-if="hasHomeContent" class="min-h-screen">
|
||||
<!-- iframe mode -->
|
||||
<iframe
|
||||
v-if="isHomeContentUrl"
|
||||
@@ -12,6 +12,75 @@
|
||||
<div v-else v-html="homeContent"></div>
|
||||
</div>
|
||||
|
||||
<!-- Compact Home Page -->
|
||||
<div
|
||||
v-else-if="compactHomeEnabled"
|
||||
data-testid="compact-home"
|
||||
class="flex min-h-screen flex-col bg-gray-50 text-gray-900 dark:bg-dark-950 dark:text-white"
|
||||
>
|
||||
<header class="border-b border-gray-200 px-4 py-4 sm:px-6 dark:border-dark-800">
|
||||
<nav class="mx-auto flex max-w-5xl flex-wrap items-center justify-between gap-3 sm:gap-4">
|
||||
<div class="flex min-w-0 flex-1 items-center gap-3">
|
||||
<img
|
||||
:src="siteLogo || '/logo.svg'"
|
||||
alt="Logo"
|
||||
class="h-9 w-9 shrink-0 rounded-lg object-contain"
|
||||
/>
|
||||
<span class="min-w-0 truncate text-base font-semibold">{{ siteName }}</span>
|
||||
</div>
|
||||
<div class="flex max-w-full shrink-0 flex-wrap items-center justify-end gap-2">
|
||||
<LocaleSwitcher />
|
||||
<a
|
||||
v-if="docUrl"
|
||||
:href="docUrl"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="flex h-10 w-10 shrink-0 items-center justify-center rounded-lg text-gray-500 hover:bg-gray-100 dark:text-dark-400 dark:hover:bg-dark-800"
|
||||
:title="t('home.viewDocs')"
|
||||
>
|
||||
<Icon name="book" size="md" />
|
||||
</a>
|
||||
<button
|
||||
class="flex h-10 w-10 shrink-0 items-center justify-center rounded-lg text-gray-500 hover:bg-gray-100 dark:text-dark-400 dark:hover:bg-dark-800"
|
||||
:title="isDark ? t('home.switchToLight') : t('home.switchToDark')"
|
||||
@click="toggleTheme"
|
||||
>
|
||||
<Icon v-if="isDark" name="sun" size="md" />
|
||||
<Icon v-else name="moon" size="md" />
|
||||
</button>
|
||||
<router-link
|
||||
:to="isAuthenticated ? dashboardPath : '/login'"
|
||||
class="inline-flex min-h-10 shrink-0 items-center justify-center rounded-lg bg-gray-900 px-4 py-2 text-sm font-medium text-white hover:bg-gray-800 dark:bg-white dark:text-gray-900 dark:hover:bg-gray-200"
|
||||
>
|
||||
{{ isAuthenticated ? t('home.dashboard') : t('home.login') }}
|
||||
</router-link>
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main class="flex min-w-0 flex-1 items-center justify-center px-4 py-16 sm:px-6">
|
||||
<div class="min-w-0 max-w-2xl text-center">
|
||||
<img
|
||||
:src="siteLogo || '/logo.svg'"
|
||||
alt="Logo"
|
||||
class="mx-auto mb-6 h-20 w-20 rounded-2xl object-contain"
|
||||
/>
|
||||
<h1 class="[overflow-wrap:anywhere] text-3xl font-bold md:text-4xl">{{ siteName }}</h1>
|
||||
<p class="mt-4 whitespace-pre-wrap [overflow-wrap:anywhere] text-base text-gray-600 dark:text-dark-300">{{ siteSubtitle }}</p>
|
||||
<router-link
|
||||
:to="isAuthenticated ? dashboardPath : '/login'"
|
||||
class="mt-8 inline-flex min-h-10 items-center justify-center rounded-lg bg-primary-600 px-5 py-2.5 text-sm font-medium text-white hover:bg-primary-700"
|
||||
>
|
||||
{{ isAuthenticated ? t('home.goToDashboard') : t('home.login') }}
|
||||
</router-link>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer class="min-w-0 border-t border-gray-200 px-4 py-5 text-center text-sm text-gray-500 [overflow-wrap:anywhere] sm:px-6 dark:border-dark-800 dark:text-dark-400">
|
||||
© {{ currentYear }} {{ siteName }}
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<!-- Default Home Page -->
|
||||
<div
|
||||
v-else
|
||||
@@ -423,6 +492,8 @@ const siteLogo = computed(() => sanitizeUrl(appStore.cachedPublicSettings?.site_
|
||||
const siteSubtitle = computed(() => appStore.cachedPublicSettings?.site_subtitle || 'AI API Gateway Platform')
|
||||
const docUrl = computed(() => sanitizeUrl(appStore.cachedPublicSettings?.doc_url || appStore.docUrl || ''))
|
||||
const homeContent = computed(() => appStore.cachedPublicSettings?.home_content || '')
|
||||
const hasHomeContent = computed(() => homeContent.value.trim().length > 0)
|
||||
const compactHomeEnabled = computed(() => appStore.cachedPublicSettings?.compact_home_enabled === true)
|
||||
|
||||
// Check if homeContent is a URL (for iframe display)
|
||||
const isHomeContentUrl = computed(() => {
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { mount, RouterLinkStub } from '@vue/test-utils'
|
||||
|
||||
import HomeView from '../HomeView.vue'
|
||||
|
||||
const { appStore, authStore } = vi.hoisted(() => ({
|
||||
appStore: {
|
||||
cachedPublicSettings: {} as Record<string, unknown>,
|
||||
siteName: 'Fallback site',
|
||||
siteLogo: '',
|
||||
docUrl: '',
|
||||
publicSettingsLoaded: true,
|
||||
fetchPublicSettings: vi.fn(),
|
||||
},
|
||||
authStore: {
|
||||
isAuthenticated: false,
|
||||
isAdmin: false,
|
||||
user: null as { email?: string } | null,
|
||||
checkAuth: vi.fn(),
|
||||
},
|
||||
}))
|
||||
|
||||
vi.mock('@/stores', () => ({
|
||||
useAppStore: () => appStore,
|
||||
useAuthStore: () => authStore,
|
||||
}))
|
||||
|
||||
vi.mock('vue-i18n', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import('vue-i18n')>()
|
||||
return {
|
||||
...actual,
|
||||
useI18n: () => ({ t: (key: string) => key }),
|
||||
}
|
||||
})
|
||||
|
||||
function mountHome(settings: Record<string, unknown> = {}) {
|
||||
appStore.cachedPublicSettings = {
|
||||
site_name: 'Test site',
|
||||
site_subtitle: 'Test subtitle',
|
||||
...settings,
|
||||
}
|
||||
|
||||
return mount(HomeView, {
|
||||
global: {
|
||||
stubs: {
|
||||
RouterLink: RouterLinkStub,
|
||||
LocaleSwitcher: { template: '<div data-testid="locale-switcher" />' },
|
||||
Icon: { template: '<span data-testid="icon" />' },
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
function compactDestination(wrapper: ReturnType<typeof mountHome>) {
|
||||
return wrapper.get('[data-testid="compact-home"]').findComponent(RouterLinkStub).props('to')
|
||||
}
|
||||
|
||||
describe('HomeView compact mode', () => {
|
||||
beforeEach(() => {
|
||||
authStore.isAuthenticated = false
|
||||
authStore.isAdmin = false
|
||||
authStore.user = null
|
||||
authStore.checkAuth.mockClear()
|
||||
appStore.fetchPublicSettings.mockClear()
|
||||
localStorage.clear()
|
||||
vi.spyOn(window, 'matchMedia').mockReturnValue({ matches: false } as MediaQueryList)
|
||||
})
|
||||
|
||||
it('renders custom HTML ahead of compact mode', () => {
|
||||
const wrapper = mountHome({
|
||||
compact_home_enabled: true,
|
||||
home_content: '<section id="custom-home">Custom home</section>',
|
||||
})
|
||||
|
||||
expect(wrapper.get('#custom-home').text()).toBe('Custom home')
|
||||
expect(wrapper.find('[data-testid="compact-home"]').exists()).toBe(false)
|
||||
})
|
||||
|
||||
it('renders custom URL content ahead of compact mode', () => {
|
||||
const wrapper = mountHome({
|
||||
compact_home_enabled: true,
|
||||
home_content: ' https://example.com/home ',
|
||||
})
|
||||
|
||||
expect(wrapper.get('iframe').attributes('src')).toBe('https://example.com/home')
|
||||
expect(wrapper.find('[data-testid="compact-home"]').exists()).toBe(false)
|
||||
})
|
||||
|
||||
it('treats whitespace-only custom content as empty and selects compact mode', () => {
|
||||
const wrapper = mountHome({ compact_home_enabled: true, home_content: ' \n\t ' })
|
||||
|
||||
expect(wrapper.get('[data-testid="compact-home"]').text()).toContain('Test site')
|
||||
})
|
||||
|
||||
it.each([undefined, false])('selects the default home when compact mode is %s', (enabled) => {
|
||||
const settings = enabled === undefined ? {} : { compact_home_enabled: enabled }
|
||||
const wrapper = mountHome(settings)
|
||||
|
||||
expect(wrapper.find('[data-testid="compact-home"]').exists()).toBe(false)
|
||||
expect(wrapper.find('.terminal-container').exists()).toBe(true)
|
||||
})
|
||||
|
||||
it('links unauthenticated visitors to login', () => {
|
||||
expect(compactDestination(mountHome({ compact_home_enabled: true }))).toBe('/login')
|
||||
})
|
||||
|
||||
it('links authenticated users to their dashboard', () => {
|
||||
authStore.isAuthenticated = true
|
||||
|
||||
expect(compactDestination(mountHome({ compact_home_enabled: true }))).toBe('/dashboard')
|
||||
})
|
||||
|
||||
it('links administrators to the admin dashboard', () => {
|
||||
authStore.isAuthenticated = true
|
||||
authStore.isAdmin = true
|
||||
|
||||
const wrapper = mountHome({ compact_home_enabled: true })
|
||||
expect(compactDestination(wrapper)).toBe('/admin/dashboard')
|
||||
expect(authStore.checkAuth).toHaveBeenCalledOnce()
|
||||
expect(appStore.fetchPublicSettings).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
@@ -5974,6 +5974,19 @@
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Compact Home Page -->
|
||||
<div class="flex items-center justify-between border-t border-gray-100 pt-4 dark:border-dark-700">
|
||||
<div>
|
||||
<label class="font-medium text-gray-900 dark:text-white">{{
|
||||
t("admin.settings.site.compactHome")
|
||||
}}</label>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">
|
||||
{{ t("admin.settings.site.compactHomeHint") }}
|
||||
</p>
|
||||
</div>
|
||||
<Toggle v-model="form.compact_home_enabled" data-testid="compact-home-toggle" />
|
||||
</div>
|
||||
|
||||
<!-- Hide CCS Import Button -->
|
||||
<div
|
||||
class="flex items-center justify-between border-t border-gray-100 pt-4 dark:border-dark-700"
|
||||
@@ -8834,6 +8847,7 @@ const form = reactive<SettingsForm>({
|
||||
contact_info: "",
|
||||
doc_url: "",
|
||||
home_content: "",
|
||||
compact_home_enabled: false,
|
||||
backend_mode_enabled: false,
|
||||
hide_ccs_import_button: false,
|
||||
payment_enabled: false,
|
||||
@@ -10358,6 +10372,7 @@ async function saveSettings() {
|
||||
contact_info: form.contact_info,
|
||||
doc_url: form.doc_url,
|
||||
home_content: form.home_content,
|
||||
compact_home_enabled: form.compact_home_enabled,
|
||||
backend_mode_enabled: form.backend_mode_enabled,
|
||||
hide_ccs_import_button: form.hide_ccs_import_button,
|
||||
table_default_page_size: form.table_default_page_size,
|
||||
|
||||
@@ -376,6 +376,7 @@ const baseSettingsResponse = {
|
||||
contact_info: "",
|
||||
doc_url: "",
|
||||
home_content: "",
|
||||
compact_home_enabled: false,
|
||||
hide_ccs_import_button: false,
|
||||
table_default_page_size: 20,
|
||||
table_page_size_options: [10, 20, 50, 100],
|
||||
@@ -678,6 +679,22 @@ describe("admin SettingsView payment visible method controls", () => {
|
||||
adminSettingsFetch.mockResolvedValue(undefined);
|
||||
});
|
||||
|
||||
it("submits the compact home page toggle", async () => {
|
||||
const wrapper = mountView();
|
||||
await flushPromises();
|
||||
|
||||
const toggle = wrapper.get('[data-testid="compact-home-toggle"]');
|
||||
expect((toggle.element as HTMLInputElement).checked).toBe(false);
|
||||
|
||||
await toggle.setValue(true);
|
||||
await wrapper.find("form").trigger("submit.prevent");
|
||||
await flushPromises();
|
||||
|
||||
expect(updateSettings).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ compact_home_enabled: true }),
|
||||
);
|
||||
});
|
||||
|
||||
it("renders panel rate limit card and saves settings", async () => {
|
||||
getPanelRateLimitSettings.mockClear();
|
||||
updatePanelRateLimitSettings.mockClear();
|
||||
|
||||
Reference in New Issue
Block a user