import { mount } from '@vue/test-utils'; import { existsSync, readFileSync } from 'node:fs'; import { resolve } from 'node:path'; import { afterEach, describe, expect, it, vi } from 'vitest'; import { ref } from 'vue'; import FeatureCards from '../app/components/FeatureCards.vue'; import StepProcess from '../app/components/StepProcess.vue'; const readComponent = (name: string) => readFileSync(resolve(process.cwd(), `app/components/${name}.vue`), 'utf8'); describe('homepage navigation and carousel behavior', () => { afterEach(() => { vi.useRealTimers(); vi.unstubAllGlobals(); }); it('uses current-page login navigation for every homepage CTA', () => { const components = ['AppHeader', 'HeroBanner', 'StepProcess', 'CtaBanner']; for (const component of components) { const source = readComponent(component); expect(source).not.toMatch(/target="_blank"[^>]*>\s*(登录|立即开始|免费试用|立即免费使用)/u); expect(source).toContain("window.location.href = 'https://inkpod.vip/user/login'"); } }); it('advances the four-step content automatically', async () => { vi.useFakeTimers(); vi.stubGlobal('ref', ref); const wrapper = mount(StepProcess); expect(wrapper.get('[data-testid="step-image-0"]').classes()).toContain('opacity-100'); await vi.advanceTimersByTimeAsync(2000); expect(wrapper.get('[data-testid="step-image-1"]').classes()).toContain('opacity-100'); wrapper.unmount(); }); it('fills the active step progress line before advancing', () => { const source = readComponent('StepProcess'); expect(source).toContain('step-progress'); expect(source).toContain('animation: fill-step-progress 2s linear forwards'); expect(source).toContain('@keyframes fill-step-progress'); expect(source).toContain('to { width: 100%; }'); expect(source).toContain('class="step-title text-xl lg:text-4xl"'); expect(source).not.toContain('step-title text-xl font-bold'); }); it('advances the feature content every two seconds', async () => { vi.useFakeTimers(); vi.stubGlobal('ref', ref); const wrapper = mount(FeatureCards); expect(wrapper.get('[data-testid="feature-image-0"]').classes()).toContain('opacity-100'); await vi.advanceTimersByTimeAsync(2000); expect(wrapper.get('[data-testid="feature-image-1"]').classes()).toContain('opacity-100'); wrapper.unmount(); }); it('keeps feature items aligned with the image and uses restrained titles', () => { const source = readComponent('FeatureCards'); expect(source).toContain('feature-list'); expect(source).toContain('feature-item-title text-xl lg:text-3xl'); expect(source).toContain('grid-template-rows: repeat(4, minmax(0, 1fr))'); }); it('runs the customer-case track as an automatic marquee', () => { const source = readComponent('CustomerCases'); expect(source).toContain('animation: case-marquee 30s'); expect(source).toContain('@media (prefers-reduced-motion: reduce)'); }); it('keeps the hero visual as a capped image instead of a background', () => { const source = readComponent('HeroBanner'); expect(source).toMatch(/]+src="\/主视觉\.png"/u); expect(source).not.toMatch(/background(?:-image)?:\s*[^;]*主视觉\.png/u); expect(source).toContain('hero-visual'); }); it('styles the hero emphasis and separates the three selling points', () => { const source = readComponent('HeroBanner'); expect(source).toContain('hero-copy'); expect(source).toContain('hero-gradient-text'); expect(source).toContain('linear-gradient(90deg, #111827 0%, #111827 28%, #ff6800 100%)'); expect(source).toContain('.hero-copy { align-items: flex-start; }'); expect(source).toContain('hero-subtitle font-thin'); expect(source).toContain('selling-point-separator'); expect(source).toContain('aria-hidden="true">|'); }); it('loads the supplied PingFang SC font weights globally', () => { const css = readFileSync(resolve(process.cwd(), 'app/assets/css/tailwind.css'), 'utf8'); expect(css).toContain("font-family: 'PingFang SC'"); expect(css).toContain("url('/fonts/pingfang-sc-regular.ttf')"); expect(css).toContain("url('/fonts/pingfang-sc-thin.ttf')"); expect(css).toContain("url('/fonts/pingfang-sc-medium.ttf')"); expect(css).toContain("url('/fonts/pingfang-sc-semibold.ttf')"); expect(existsSync(resolve(process.cwd(), 'public/fonts/pingfang-sc-regular.ttf'))).toBe(true); expect(existsSync(resolve(process.cwd(), 'public/fonts/pingfang-sc-thin.ttf'))).toBe(true); expect(existsSync(resolve(process.cwd(), 'public/fonts/pingfang-sc-medium.ttf'))).toBe(true); expect(existsSync(resolve(process.cwd(), 'public/fonts/pingfang-sc-semibold.ttf'))).toBe(true); }); it('uses the theme color for trust statistics', () => { const source = readComponent('TrustSection'); expect(source).toContain('trust-stat-value text-primary'); expect(source).toContain('trust-illustration'); expect(source).toContain('margin-bottom: -40px'); }); it('temporarily hides recommendation and solution navigation', () => { const source = readComponent('AppHeader'); expect(source).toContain('v-if="SHOW_EXTENDED_NAV"'); }); it('uses a compact desktop navigation font size', () => { const source = readComponent('AppHeader'); expect(source).toContain('desktop-nav'); expect(source).toContain('.desktop-nav { font-size: 18px; }'); }); it('aligns country flags and keeps the more-products action at the bottom', () => { const source = readComponent('PodProducts'); expect(source).toContain('country-button'); expect(source).toContain('country-flag'); expect(source).toContain('more-products'); expect(source).toContain('more-products-link text-primary'); expect(source).not.toContain('fa-arrow-right'); expect(source).toContain('margin-top: auto'); }); });