10404dbb36
同步工作区剩余变更,主要包括: - 营销页面组件与布局持续优化(about/news/services/solutions/team 等) - 详情页四层叙事组件、布局组件、UI 组件调整 - CMS 数据模型、API 路由、权限、工作流、站内通知、媒体管理扩展 - 新增/补充单元测试与 E2E 测试(cms-workflow.spec.ts 等) - ESLint 9 迁移、jest/tsconfig 配置更新、依赖调整 - 新增 ADR、CMS 评估文档、Release Review / Acceptance 报告 - 移除水墨装饰组件与大体积未使用字体文件
204 lines
5.3 KiB
JavaScript
204 lines
5.3 KiB
JavaScript
require('@testing-library/jest-dom');
|
|
|
|
const { TextEncoder, TextDecoder } = require('util');
|
|
global.TextEncoder = TextEncoder;
|
|
global.TextDecoder = TextDecoder;
|
|
|
|
jest.mock('nanoid', () => ({
|
|
nanoid: jest.fn(() => 'test-id-123'),
|
|
}));
|
|
|
|
jest.mock('@/generated/prisma/client', () => ({
|
|
PrismaClient: jest.fn().mockImplementation(() => ({
|
|
contentItem: {
|
|
findMany: jest.fn().mockResolvedValue([]),
|
|
findFirst: jest.fn().mockResolvedValue(null),
|
|
findUnique: jest.fn().mockResolvedValue(null),
|
|
},
|
|
contentModel: {
|
|
findMany: jest.fn().mockResolvedValue([]),
|
|
},
|
|
contentZone: {
|
|
findMany: jest.fn().mockResolvedValue([]),
|
|
findUnique: jest.fn().mockResolvedValue(null),
|
|
},
|
|
})),
|
|
}));
|
|
|
|
jest.mock('@/lib/cms/data-server', () => ({
|
|
getPublishedItems: jest.fn().mockResolvedValue([]),
|
|
getPublishedItemBySlug: jest.fn().mockResolvedValue(null),
|
|
getItemById: jest.fn().mockResolvedValue(null),
|
|
getPageZones: jest.fn().mockResolvedValue([]),
|
|
getZone: jest.fn().mockResolvedValue(null),
|
|
getContentModels: jest.fn().mockResolvedValue([]),
|
|
getCases: jest.fn().mockResolvedValue([]),
|
|
getNews: jest.fn().mockResolvedValue([]),
|
|
getServices: jest.fn().mockResolvedValue([]),
|
|
getProducts: jest.fn().mockResolvedValue([]),
|
|
getSolutions: jest.fn().mockResolvedValue([]),
|
|
getStats: jest.fn().mockResolvedValue([]),
|
|
getHeroBanners: jest.fn().mockResolvedValue([]),
|
|
getCaseBySlug: jest.fn().mockResolvedValue(null),
|
|
getNewsBySlug: jest.fn().mockResolvedValue(null),
|
|
getServiceBySlug: jest.fn().mockResolvedValue(null),
|
|
getProductBySlug: jest.fn().mockResolvedValue(null),
|
|
getSolutionBySlug: jest.fn().mockResolvedValue(null),
|
|
getHomePageZones: jest.fn().mockResolvedValue([]),
|
|
getAllPublishedSlugs: jest.fn().mockResolvedValue([]),
|
|
}));
|
|
|
|
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);
|
|
}
|
|
};
|