diff --git a/src/web/pages/ImportExport.test.tsx b/src/web/pages/ImportExport.test.tsx index 3623233..c8f0304 100644 --- a/src/web/pages/ImportExport.test.tsx +++ b/src/web/pages/ImportExport.test.tsx @@ -1,6 +1,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { act, create, type ReactTestInstance } from 'react-test-renderer'; import { MemoryRouter } from 'react-router-dom'; +import ModernSelect from '../components/ModernSelect.js'; import ImportExport from './ImportExport.js'; const { apiMock, toastMock } = vi.hoisted(() => ({ @@ -394,6 +395,54 @@ describe('ImportExport', () => { } }); + it('renders webdav export type with ModernSelect instead of a native select', async () => { + let root: ReturnType | null = null; + + try { + await act(async () => { + root = create( + + + , + ); + }); + await flushMicrotasks(); + + const fileUrlInput = root!.root.findAll((node) => ( + node.type === 'input' + && node.props.placeholder === 'https://dav.example.com/backups/metapi.json' + )).at(-1); + const cronInput = root!.root.findAll((node) => ( + node.type === 'input' + && node.props.placeholder === '0 */6 * * *' + )).at(-1); + const selects = root!.root.findAllByType(ModernSelect); + const exportTypeSelect = selects.at(-1); + + expect(fileUrlInput?.props.style).toEqual(expect.objectContaining({ + width: '100%', + padding: '10px 14px', + border: '1px solid var(--color-border)', + borderRadius: 'var(--radius-sm)', + fontSize: 13, + background: 'var(--color-bg)', + color: 'var(--color-text-primary)', + })); + expect(root!.root.findAll((node) => node.type === 'select')).toHaveLength(0); + expect(exportTypeSelect?.props.value).toBe('all'); + expect(exportTypeSelect?.props.options).toEqual([ + { value: 'all', label: '全部' }, + { value: 'accounts', label: '账号与路由' }, + { value: 'preferences', label: '系统设置' }, + ]); + expect(cronInput?.props.style).toEqual(expect.objectContaining({ + fontFamily: 'var(--font-mono)', + })); + } finally { + root?.unmount(); + } + }); + it('disables webdav actions while config has unsaved changes', async () => { let root: ReturnType | null = null; diff --git a/src/web/pages/ImportExport.tsx b/src/web/pages/ImportExport.tsx index 0ef2c85..fccb68b 100644 --- a/src/web/pages/ImportExport.tsx +++ b/src/web/pages/ImportExport.tsx @@ -1,5 +1,6 @@ import React, { useEffect, useMemo, useRef, useState } from 'react'; import { api } from '../api.js'; +import ModernSelect from '../components/ModernSelect.js'; import { useToast } from '../components/Toast.js'; import { tr } from '../i18n.js'; @@ -73,6 +74,40 @@ const DEFAULT_WEBDAV_SNAPSHOT: WebdavConfigSnapshot = { hasPassword: false, }; +const WEBDAV_EXPORT_TYPE_OPTIONS = [ + { value: 'all', label: '全部' }, + { value: 'accounts', label: '账号与路由' }, + { value: 'preferences', label: '系统设置' }, +] as const; + +const formFieldLabelStyle: React.CSSProperties = { + fontSize: 13, + fontWeight: 500, + marginBottom: 8, + color: 'var(--color-text-secondary)', +}; + +const settingsInputStyle: React.CSSProperties = { + width: '100%', + padding: '10px 14px', + border: '1px solid var(--color-border)', + borderRadius: 'var(--radius-sm)', + fontSize: 13, + outline: 'none', + background: 'var(--color-bg)', + color: 'var(--color-text-primary)', + boxSizing: 'border-box', + transition: 'border-color 0.2s', +}; + +const webdavToggleStyle: React.CSSProperties = { + display: 'inline-flex', + alignItems: 'center', + gap: 8, + fontSize: 13, + cursor: 'pointer', +}; + function downloadJsonFile(data: unknown, filename: string) { const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' }); const url = URL.createObjectURL(blob); @@ -672,27 +707,27 @@ export default function ImportExport() { 支持手动推送、手动拉取,以及定时自动导出到 WebDAV。 -
-