import { shallowMount, createLocalVue } from '@vue/test-utils' import ElementUI from 'element-ui' import News from '@/views/companyNews/index.vue' import { newsCategories, saveCategorySettings, listNews, companies, latestBatch, startBatch, reviewNews, showNews, saveNews } from '@/api/companyNews' jest.mock('@/api/companyNews', () => ({ newsCategories: jest.fn(), saveCategorySettings: jest.fn(), listNews: jest.fn(), companies: jest.fn(), showNews: jest.fn(), saveNews: jest.fn(), reviewNews: jest.fn(), newsLogs: jest.fn(), startBatch: jest.fn(), latestBatch: jest.fn(), listBatches: jest.fn(), listTasks: jest.fn() })) const localVue = createLocalVue() localVue.use(ElementUI) const flush = async() => { await Promise.resolve(); await Promise.resolve(); await Promise.resolve() } let wrapper const create = () => shallowMount(News, { localVue, stubs: ['lx-header', 'my-tinymce'], mocks: { $message: { success: jest.fn(), warning: jest.fn() }, $confirm: jest.fn(() => Promise.resolve()), $prompt: jest.fn(() => Promise.resolve({ value: '不相关' })) } }) describe('Enterprise news management', () => { beforeEach(() => { jest.useFakeTimers() newsCategories.mockResolvedValue({ categories: [{ code: '12014', name: '产品信息', count: 317 }], retained_categories: ['12014'] }) saveCategorySettings.mockResolvedValue({}) listNews.mockResolvedValue({ data: [], total: 0 }) companies.mockResolvedValue({ data: [], total: 0, current_page: 1, last_page: 1 }) latestBatch.mockResolvedValue({ batch: null }) }) afterEach(() => { if (wrapper) wrapper.destroy() jest.clearAllMocks(); jest.clearAllTimers(); jest.useRealTimers() }) it('defaults to retained and submits multiple categories and retention settings', async() => { wrapper = create(); await flush() expect(listNews).toHaveBeenCalledWith(expect.objectContaining({ review_status: 'retained' })) wrapper.vm.query.category_codes = ['12014', '00000'] wrapper.vm.search(); await flush() expect(listNews).toHaveBeenLastCalledWith(expect.objectContaining({ category_codes: ['12014', '00000'] })) await wrapper.vm.openSettings() expect(wrapper.vm.retainedCategories).toEqual(['12014']) wrapper.setData({ retainedCategories: [] }) await wrapper.vm.saveSettings() expect(saveCategorySettings).toHaveBeenCalledWith({ retained_categories: [] }) wrapper.vm.reset() expect(wrapper.vm.query.category_codes).toEqual([]) expect(wrapper.vm.query.review_status).toBe('retained') }) it('restores running progress and prevents starting a duplicate batch', async() => { latestBatch.mockResolvedValue({ batch: { id: 3, status: 'running', total: 100, processed: 40, success: 38, failed: 2, inserted: 8, progress: 40 }}) wrapper = create(); await flush() expect(wrapper.vm.running).toBe(true) expect(wrapper.text()).toContain('100') await wrapper.vm.start() expect(startBatch).not.toHaveBeenCalled() expect(wrapper.find('el-progress-stub').attributes('percentage')).toBe('40') }) it('sends company and inclusive publication date filters', async() => { wrapper = create(); await flush() wrapper.vm.query.company_id = 41 wrapper.vm.dates = ['2026-10-01', '2026-10-09'] wrapper.vm.search(); await flush() expect(listNews).toHaveBeenLastCalledWith(expect.objectContaining({ company_id: 41, date_from: '2026-10-01', date_to: '2026-10-09', page: 1 })) }) it('starts all-eligible-company batch without leaking list filters', async() => { wrapper = create(); await flush() wrapper.vm.query.company_id = 41 startBatch.mockResolvedValue({ id: 9, total: 100, status: 'running', progress: 0 }) await wrapper.vm.start() expect(startBatch).toHaveBeenCalledWith({}) expect(wrapper.vm.batch.id).toBe(9) }) it('retains news without ever sending is_visible and preserves version checks', async() => { wrapper = create(); await flush() reviewNews.mockResolvedValue({}) await wrapper.vm.review({ id: 8, version: 3 }, 'retained') expect(reviewNews).toHaveBeenCalledWith({ id: 8, version: 3, review_status: 'retained', review_note: '' }) }) it('edits only editorial fields and blocks unsafe source URLs', async() => { wrapper = create(); await flush() showNews.mockResolvedValue({ id: 8, version: 3, title: '标题', source_content: '接口原始', content_html: '

人工

', raw_payload: {}}) await wrapper.vm.edit({ id: 8 }) saveNews.mockResolvedValue({ id: 8, version: 4, title: '新标题', content_html: '

人工

' }) wrapper.vm.$set(wrapper.vm.form, 'title', '新标题') await wrapper.vm.save() expect(saveNews).toHaveBeenCalledWith({ id: 8, version: 3, title: '新标题', summary: undefined, content_html: '

人工

' }) expect(wrapper.vm.safeUrl('javascript:alert(1)')).toBe(false) expect(wrapper.vm.safeUrl('https://example.com/news')).toBe(true) }) it('appends companies on dropdown scroll and prevents duplicate page loads', async() => { companies.mockResolvedValueOnce({ data: [{ id: 1, company_name: '候选企业' }], total: 2, current_page: 1, last_page: 2 }) wrapper = create(); await flush() let resolvePage companies.mockImplementationOnce(() => new Promise(resolve => { resolvePage = resolve })) wrapper.vm.onCompanyScroll({ target: { scrollHeight: 500, scrollTop: 300, clientHeight: 200 }}) wrapper.vm.onCompanyScroll({ target: { scrollHeight: 500, scrollTop: 300, clientHeight: 200 }}) expect(companies).toHaveBeenCalledTimes(2) expect(companies).toHaveBeenLastCalledWith({ keyword: '', page: 2 }) resolvePage({ data: [{ id: 2, company_name: '已占额企业' }], total: 2, current_page: 2, last_page: 2 }) await flush() expect(wrapper.vm.companyOptions.map(c => c.id)).toEqual([1, 2]) expect(wrapper.vm.companyHasMore).toBe(false) }) it('discards stale company responses after changing search keyword', async() => { wrapper = create(); await flush() let resolveOld companies.mockImplementationOnce(() => new Promise(resolve => { resolveOld = resolve })) const old = wrapper.vm.searchCompanies('旧') companies.mockResolvedValueOnce({ data: [{ id: 9, company_name: '新企业' }], total: 1, current_page: 1, last_page: 1 }) await wrapper.vm.searchCompanies('新') resolveOld({ data: [{ id: 8, company_name: '旧企业' }], total: 1, current_page: 1, last_page: 1 }) await old expect(wrapper.vm.companyOptions.map(c => c.id)).toEqual([9]) expect(wrapper.vm.companyKeyword).toBe('新') }) it('shows missing configuration and prevents creating doomed batches', async() => { latestBatch.mockResolvedValue({ batch: null, configuration_error: '缺少 YUANHE_BASE_URL' }) wrapper = create(); await flush() expect(wrapper.vm.configurationError).toBe('缺少 YUANHE_BASE_URL') await wrapper.vm.start() expect(startBatch).not.toHaveBeenCalled() expect(wrapper.findAll('el-alert-stub').at(0).attributes('title')).toBe('缺少 YUANHE_BASE_URL') }) })