From 5e58d904fe21e9ffa53d1d2c0c4129aedbc39c7d Mon Sep 17 00:00:00 2001 From: InfinityPacer <160988576+InfinityPacer@users.noreply.github.com> Date: Wed, 12 Aug 2026 07:17:46 +0800 Subject: [PATCH] test(setting): cover directory and rule settings (#668) --- eslint-suppressions.json | 8 +- src/views/setting/AccountSettingDirectory.vue | 7 +- src/views/setting/AccountSettingRule.vue | 6 +- .../__tests__/AccountSettingDirectory.spec.ts | 322 ++++++++++++--- .../__tests__/AccountSettingRule.spec.ts | 373 ++++++++++++++++++ vite.config.ts | 14 + 6 files changed, 665 insertions(+), 65 deletions(-) create mode 100644 src/views/setting/__tests__/AccountSettingRule.spec.ts diff --git a/eslint-suppressions.json b/eslint-suppressions.json index d5b97c3e..7efdf478 100644 --- a/eslint-suppressions.json +++ b/eslint-suppressions.json @@ -841,10 +841,7 @@ }, "src/views/setting/AccountSettingDirectory.vue": { "@typescript-eslint/no-explicit-any": { - "count": 9 - }, - "no-prototype-builtins": { - "count": 1 + "count": 8 }, "sonarjs/super-linear-regex": { "count": 1 @@ -861,9 +858,6 @@ "src/views/setting/AccountSettingRule.vue": { "@typescript-eslint/no-explicit-any": { "count": 14 - }, - "prefer-const": { - "count": 1 } }, "src/views/setting/AccountSettingSearch.vue": { diff --git a/src/views/setting/AccountSettingDirectory.vue b/src/views/setting/AccountSettingDirectory.vue index a41d2edf..d0293067 100644 --- a/src/views/setting/AccountSettingDirectory.vue +++ b/src/views/setting/AccountSettingDirectory.vue @@ -126,7 +126,9 @@ async function loadSystemSettings() { // 将API返回的值赋值给SystemSettings for (const sectionKey of Object.keys(SystemSettings.value) as Array) { Object.keys(SystemSettings.value[sectionKey]).forEach((key: string) => { - if (result.data.hasOwnProperty(key)) (SystemSettings.value[sectionKey] as any)[key] = result.data[key] + if (Object.prototype.hasOwnProperty.call(result.data, key)) { + Reflect.set(SystemSettings.value[sectionKey], key, result.data[key]) + } }) } } @@ -174,6 +176,7 @@ async function saveStorages() { else $toast.error(t('setting.directory.storageSaveFailed')) } catch (error) { console.log(error) + $toast.error(t('setting.directory.storageSaveFailed')) } } @@ -202,6 +205,7 @@ async function saveDirectories() { } else $toast.error(t('setting.directory.directorySaveFailed')) } catch (error) { console.log(error) + $toast.error(t('setting.directory.directorySaveFailed')) } } @@ -296,6 +300,7 @@ async function saveSystemSettings(value: any) { } else $toast.error(t('setting.directory.organizeSaveFailed')) } catch (error) { console.log(error) + $toast.error(t('setting.directory.organizeSaveFailed')) } } diff --git a/src/views/setting/AccountSettingRule.vue b/src/views/setting/AccountSettingRule.vue index f7875129..5e4b4b98 100644 --- a/src/views/setting/AccountSettingRule.vue +++ b/src/views/setting/AccountSettingRule.vue @@ -87,6 +87,7 @@ async function saveCustomRules() { else $toast.error(t('setting.rule.customRuleSaveFailed')) } catch (error) { console.log(error) + $toast.error(t('setting.rule.customRuleSaveFailed')) } } @@ -141,6 +142,7 @@ async function saveFilterRuleGroups() { else $toast.error(t('setting.rule.ruleGroupSaveFailed')) } catch (error) { console.log(error) + $toast.error(t('setting.rule.ruleGroupSaveFailed')) } } @@ -166,8 +168,7 @@ async function shareRules(rules: CustomRule[] | FilterRuleGroup[], type: string) // 复制到剪贴板 try { - let success - success = copyToClipboard(value) + const success = copyToClipboard(value) if (await success) $toast.success( type === 'custom' ? t('setting.rule.customRuleCopySuccess') : t('setting.rule.ruleGroupCopySuccess'), @@ -374,6 +375,7 @@ async function saveTorrentPriority() { else $toast.error('优先规则保存失败!') } catch (error) { console.log(error) + $toast.error('优先规则保存失败!') } } diff --git a/src/views/setting/__tests__/AccountSettingDirectory.spec.ts b/src/views/setting/__tests__/AccountSettingDirectory.spec.ts index b154a713..aa749361 100644 --- a/src/views/setting/__tests__/AccountSettingDirectory.spec.ts +++ b/src/views/setting/__tests__/AccountSettingDirectory.spec.ts @@ -2,52 +2,93 @@ import AccountSettingDirectory from '@/views/setting/AccountSettingDirectory.vue import { fireEvent, screen, waitFor, within } from '@testing-library/vue' import userEvent from '@testing-library/user-event' import { renderWithProviders } from '@tests/support/render' -import { defineComponent } from 'vue' +import { computed, defineComponent } from 'vue' import { beforeEach, describe, expect, it, vi } from 'vitest' const mocks = vi.hoisted(() => ({ apiGet: vi.fn(), apiPost: vi.fn(), + openSharedDialog: vi.fn(), toastError: vi.fn(), toastSuccess: vi.fn(), + useSilentSettingRefresh: vi.fn(), })) vi.mock('@/api', () => ({ - default: { - get: mocks.apiGet, - post: mocks.apiPost, - }, + default: { get: mocks.apiGet, post: mocks.apiPost }, })) vi.mock('vue-toastification', () => ({ - useToast: () => ({ - error: mocks.toastError, - success: mocks.toastSuccess, - }), + useToast: () => ({ error: mocks.toastError, success: mocks.toastSuccess }), })) vi.mock('@/composables/useSilentSettingRefresh', () => ({ - useSilentSettingRefresh: vi.fn(), + useSilentSettingRefresh: mocks.useSilentSettingRefresh, +})) + +vi.mock('@/composables/useSharedDialog', () => ({ + openSharedDialog: mocks.openSharedDialog, })) vi.mock('@/components/cards/DirectoryCard.vue', async () => { const { defineComponent } = await import('vue') - return { default: defineComponent({ name: 'DirectoryCardStub', template: '
' }) } + return { + default: defineComponent({ + name: 'DirectoryCardStub', + props: { directory: { type: Object, required: true } }, + emits: ['close', 'update:modelValue'], + template: ` +
+ {{ directory.name }} + + + +
+ `, + }), + } }) vi.mock('@/components/cards/StorageCard.vue', async () => { const { defineComponent } = await import('vue') - return { default: defineComponent({ name: 'StorageCardStub', template: '
' }) } + return { + default: defineComponent({ + name: 'StorageCardStub', + props: { storage: { type: Object, required: true } }, + emits: ['close', 'done'], + template: ` +
+ {{ storage.name }} + + +
+ `, + }), + } }) vi.mock('vuedraggable', async () => { const { defineComponent, h } = await import('vue') - return { default: defineComponent({ name: 'DraggableStub', - setup(_props, { slots }) { - return () => h('div', slots.default?.()) + props: { modelValue: { type: Array, default: () => [] } }, + emits: ['update:modelValue', 'end'], + setup(props, { emit, slots }) { + const reverse = () => { + emit('update:modelValue', [...props.modelValue].reverse()) + emit('end') + } + return () => { + const items = props.modelValue as Array<{ name?: string }> + return h('div', [ + h('button', { 'aria-label': `reverse-${items[0]?.name ?? 'empty'}`, onClick: reverse }, 'reverse'), + ...items.map(element => slots.item?.({ element })), + ]) + } }, }), } @@ -55,20 +96,60 @@ vi.mock('vuedraggable', async () => { const AceEditorStub = defineComponent({ name: 'VAceEditor', - props: { - value: { type: String, default: '' }, - }, - template: '
', + props: { value: { type: String, default: '' } }, + emits: ['update:value'], + template: '