require('@testing-library/jest-dom'); jest.mock('next-auth', () => { return { __esModule: true, default: jest.fn(() => ({ handlers: { authOptions: { providers: [], callbacks: {}, pages: {}, session: {}, }, }, signIn: jest.fn(), signOut: jest.fn(), auth: jest.fn(), })), getServerSession: jest.fn(), }; }); jest.mock('next-auth/providers/credentials', () => jest.fn(() => ({ name: '邮箱密码', credentials: { email: { label: '邮箱', type: 'email' }, password: { label: '密码', type: 'password' }, }, authorize: jest.fn(), })) ); jest.mock('nanoid', () => ({ nanoid: jest.fn(() => 'test-id-123'), })); jest.mock('next/dynamic', () => ({ __esModule: true, default: (importFn, options) => { const MockComponent = (props) => null; MockComponent.displayName = 'DynamicComponent'; MockComponent.preload = () => Promise.resolve(); return MockComponent; }, })); jest.mock('next/server', () => ({ NextRequest: class MockNextRequest { constructor(input, init = {}) { this.url = typeof input === 'string' ? input : input.url; this.method = init.method || 'GET'; this.headers = new Headers(init.headers); this.body = init.body; } async json() { return typeof this.body === 'string' ? JSON.parse(this.body) : this.body; } }, NextResponse: { json: (body, init = {}) => ({ status: init.status || 200, json: async () => body, }), }, })); global.console = { ...console, error: jest.fn(), warn: jest.fn(), log: jest.fn(), }; class MockIntersectionObserver { constructor(callback, options = {}) { this.callback = callback; this.options = options; this.elements = new Set(); this.observationEntries = []; } observe(element) { this.elements.add(element); const entry = { isIntersecting: true, target: element, boundingClientRect: element.getBoundingClientRect ? element.getBoundingClientRect() : {}, intersectionRatio: 1, intersectionRect: {}, rootBounds: {}, time: Date.now(), }; this.observationEntries.push(entry); this.callback(this.observationEntries, this); } unobserve(element) { this.elements.delete(element); this.observationEntries = this.observationEntries.filter( entry => entry.target !== element ); } disconnect() { this.elements.clear(); this.observationEntries = []; } takeRecords() { return this.observationEntries; } } global.IntersectionObserver = MockIntersectionObserver; global.IntersectionObserverEntry = class IntersectionObserverEntry { constructor() { this.isIntersecting = true; this.target = {}; this.boundingClientRect = {}; this.intersectionRatio = 1; this.intersectionRect = {}; this.rootBounds = {}; this.time = Date.now(); } }; Object.defineProperty(window, 'matchMedia', { writable: true, value: jest.fn().mockImplementation(query => ({ matches: false, media: query, onchange: null, addListener: jest.fn(), removeListener: jest.fn(), addEventListener: jest.fn(), removeEventListener: jest.fn(), dispatchEvent: jest.fn(), })), }); global.Request = class Request { constructor(input, init = {}) { this.url = typeof input === 'string' ? input : input.url; this.method = init.method || 'GET'; this.headers = new Headers(init.headers); this.body = init.body; } async json() { return typeof this.body === 'string' ? JSON.parse(this.body) : this.body; } }; global.Headers = class Headers { constructor(init = {}) { this.headers = {}; if (init) { Object.entries(init).forEach(([key, value]) => { this.headers[key.toLowerCase()] = value; }); } } get(name) { return this.headers[name.toLowerCase()]; } set(name, value) { this.headers[name.toLowerCase()] = value; } }; global.Response = class Response { constructor(body, init = {}) { this.body = body; this.status = init.status || 200; this.statusText = init.statusText || 'OK'; this.headers = new Headers(init.headers); } async json() { return typeof this.body === 'string' ? JSON.parse(this.body) : this.body; } async text() { return String(this.body); } };