diff --git a/web/src/components/creator/WorksGallery.tsx b/web/src/components/creator/WorksGallery.tsx index 205f70a..6685803 100644 --- a/web/src/components/creator/WorksGallery.tsx +++ b/web/src/components/creator/WorksGallery.tsx @@ -111,7 +111,7 @@ interface CollectionProgress { } interface WorksGalleryProps { - /** 不传则不过滤来源(如竞品分析页跨全部竞品);监控/自有账号详情传具体账号 ID。 */ + /** 不传则不限定账号;仍按 sourceType 限定来源类型。监控/自有账号详情传具体账号 ID。 */ sourceId?: string; sourceType?: 'owned' | 'competitor'; emptyHint?: string; diff --git a/web/src/pages/creator/competitors/index.tsx b/web/src/pages/creator/competitors/index.tsx index ffb19ef..2f6a58f 100644 --- a/web/src/pages/creator/competitors/index.tsx +++ b/web/src/pages/creator/competitors/index.tsx @@ -69,7 +69,7 @@ export default function Page() { /> - + ); } diff --git a/web/tests/competitor-analysis-source.test.cjs b/web/tests/competitor-analysis-source.test.cjs new file mode 100644 index 0000000..fbcd78a --- /dev/null +++ b/web/tests/competitor-analysis-source.test.cjs @@ -0,0 +1,87 @@ +const test = require('node:test'); +const assert = require('node:assert/strict'); +const fs = require('node:fs'); +const path = require('node:path'); +const Module = require('node:module'); +const ts = require('typescript'); + +const pagePath = path.join(__dirname, '../src/pages/creator/competitors/index.tsx'); + +function createPage() { + let filters = {}; + const gallery = 'WorksGallery'; + const element = (type, props) => ({ type, props }); + const mocks = { + react: { + useMemo: (factory) => factory(), + useState: () => [filters, (update) => { filters = update(filters); }], + }, + 'react/jsx-runtime': { jsx: element, jsxs: element }, + antd: { + Card: 'Card', DatePicker: 'DatePicker', Flex: 'Flex', InputNumber: 'InputNumber', + Select: 'Select', Space: 'Space', Typography: { Text: 'Text' }, + }, + '@/components/creator/WorksGallery': { __esModule: true, default: gallery }, + }; + const compiled = ts.transpileModule(fs.readFileSync(pagePath, 'utf8'), { + fileName: pagePath, + compilerOptions: { + module: ts.ModuleKind.CommonJS, jsx: ts.JsxEmit.ReactJSX, esModuleInterop: true, + inlineSourceMap: true, inlineSources: true, + }, + }).outputText; + const loaded = new Module(pagePath, module); + loaded.filename = pagePath; + loaded.paths = Module._nodeModulePaths(path.dirname(pagePath)); + loaded.require = (name) => Object.hasOwn(mocks, name) ? mocks[name] : module.require(name); + loaded._compile(compiled, pagePath); + + function descendants(node) { + if (!node || typeof node !== 'object') return []; + return [node, ...[node.props?.children].flat(Infinity).flatMap(descendants)]; + } + return () => { + const nodes = descendants(loaded.exports.default()); + return { + gallery: nodes.find((node) => node.type === gallery).props, + controls: nodes.filter((node) => node.props.onChange), + }; + }; +} + +function assertCompetitorOnly(props) { + assert.equal(props.sourceType, 'competitor', '竞品分析必须排除自有账号作品'); + assert.equal(props.sourceId, undefined, '竞品分析应包含所有监测账号,而非限定某个账号'); +} + +test('竞品分析首次加载只查询监测账号作品', () => { + const render = createPage(); + assertCompetitorOnly(render().gallery); +}); + +test('调整及清空各项筛选后仍只查询监测账号作品', () => { + const render = createPage(); + const date = { format: (pattern) => { + assert.equal(pattern, 'YYYY-MM-DD'); + return '2026-05-18'; + } }; + const cases = [ + ['最低点赞', 'min_likes', 12, 12], + ['最低评论', 'min_comments', 3, 3], + ['最低转发', 'min_shares', 5, 5], + ['全部状态', 'published_at_status', 'verified', 'verified'], + ['发布开始日期', 'published_after', date, '2026-05-18T00:00:00Z'], + ['发布结束日期', 'published_before', date, '2026-05-18T23:59:59Z'], + ]; + for (const [placeholder, key, value, expected] of cases) { + render().controls.find((control) => control.props.placeholder === placeholder).props.onChange(value); + assertCompetitorOnly(render().gallery); + assert.equal(render().gallery.extraFilters[key], expected); + } + for (const [placeholder, key] of cases) { + const clearValue = key === 'published_at_status' ? undefined : null; + render().controls.find((control) => control.props.placeholder === placeholder).props.onChange(clearValue); + assertCompetitorOnly(render().gallery); + assert.equal(render().gallery.extraFilters[key], undefined); + } +});