mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-09-09 01:36:45 +08:00
fix(classification): preserve condition groups when toggling rules
This commit is contained in:
@@ -63,9 +63,10 @@ function cloneCondition(node: ClassificationConditionNode): ClassificationCondit
|
||||
}
|
||||
|
||||
const group = node as ClassificationConditionGroup
|
||||
if (group.all !== undefined) return { all: group.all?.map(cloneCondition) ?? group.all }
|
||||
if (group.any !== undefined) return { any: group.any?.map(cloneCondition) ?? group.any }
|
||||
if (group.not !== undefined) return { not: group.not ? cloneCondition(group.not) : group.not }
|
||||
// API 序列化会为未选中的分支保留 null;只能复制真正有值的分支,避免覆盖 any/not。
|
||||
if (group.all !== undefined && group.all !== null) return { all: group.all.map(cloneCondition) }
|
||||
if (group.any !== undefined && group.any !== null) return { any: group.any.map(cloneCondition) }
|
||||
if (group.not !== undefined && group.not !== null) return { not: cloneCondition(group.not) }
|
||||
return {}
|
||||
}
|
||||
|
||||
|
||||
@@ -210,6 +210,25 @@ describe('ClassificationRuleEditor', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('切换规则开关时保留条件组的有效分支', async () => {
|
||||
const user = userEvent.setup()
|
||||
const condition = {
|
||||
all: null,
|
||||
any: [{ field: 'media.genre_names', operator: 'contains_any', value: ['动画'] }],
|
||||
not: null,
|
||||
} as ClassificationRule['when']
|
||||
const editor = await renderEditor([createRule({ when: condition })])
|
||||
|
||||
await user.click(screen.getByRole('checkbox', { name: '启用规则 电影规则' }))
|
||||
|
||||
expect(editor.latestRules()[0]).toEqual(
|
||||
expect.objectContaining({
|
||||
enabled: false,
|
||||
when: { any: [{ field: 'media.genre_names', operator: 'contains_any', value: ['动画'] }] },
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('按媒体类型过滤分类目标,并在目标失效时自动清空', async () => {
|
||||
const user = userEvent.setup()
|
||||
const editor = await renderEditor([createRule()])
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts" setup>
|
||||
import api from '@/api'
|
||||
import api, { getApiErrorMessage } from '@/api'
|
||||
import type { TransferDirectoryConf } from '@/api/types'
|
||||
import type {
|
||||
ClassificationCategory,
|
||||
@@ -361,7 +361,7 @@ async function validateCurrentDraft(): Promise<void> {
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
validatedDraftSnapshot.value = null
|
||||
toast.error(t('setting.classification.validationRequestFailed'))
|
||||
toast.error(getApiErrorMessage(error) || t('setting.classification.validationRequestFailed'))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
apiGet: vi.fn(),
|
||||
apiErrorMessage: vi.fn(),
|
||||
analyzeImpact: vi.fn(),
|
||||
initialize: vi.fn(),
|
||||
loadHistory: vi.fn(),
|
||||
@@ -29,6 +30,7 @@ const mocks = vi.hoisted(() => ({
|
||||
|
||||
vi.mock('@/api', () => ({
|
||||
default: { get: mocks.apiGet },
|
||||
getApiErrorMessage: mocks.apiErrorMessage,
|
||||
}))
|
||||
|
||||
vi.mock('@/composables/useMediaClassification', () => ({
|
||||
@@ -243,6 +245,7 @@ describe('AccountSettingClassification', () => {
|
||||
},
|
||||
],
|
||||
})
|
||||
mocks.apiErrorMessage.mockReset().mockReturnValue(undefined)
|
||||
mocks.analyzeImpact.mockReset()
|
||||
mocks.initialize.mockReset().mockResolvedValue(undefined)
|
||||
mocks.loadHistory.mockReset().mockResolvedValue(undefined)
|
||||
@@ -448,6 +451,18 @@ describe('AccountSettingClassification', () => {
|
||||
expect(mocks.toastInfo).toHaveBeenCalledWith('已恢复当前活动策略')
|
||||
})
|
||||
|
||||
it('surfaces the server reason when validation rejects a changed rule draft', async () => {
|
||||
const user = userEvent.setup()
|
||||
mocks.validateDraft.mockRejectedValueOnce(new Error('validation rejected'))
|
||||
mocks.apiErrorMessage.mockReturnValueOnce('请求参数不正确')
|
||||
await renderWithProviders(AccountSettingClassification)
|
||||
await openWorkspace('验证发布')
|
||||
|
||||
await user.click(screen.getByRole('button', { name: '校验草稿' }))
|
||||
|
||||
await waitFor(() => expect(mocks.toastError).toHaveBeenCalledWith('请求参数不正确'))
|
||||
})
|
||||
|
||||
it('updates source fallbacks through stable category IDs', async () => {
|
||||
const user = userEvent.setup()
|
||||
await renderWithProviders(AccountSettingClassification)
|
||||
|
||||
Reference in New Issue
Block a user