mirror of
https://hubproxy.babadafafafafa.cn/https://github.com/mihomo-party-org/clash-party.git
synced 2026-09-20 08:03:39 +08:00
fix: preserve numeric override IDs before JSON cloning (#2142)
This commit is contained in:
82
src/main/config/override.test.tsx
Normal file
82
src/main/config/override.test.tsx
Normal file
@@ -0,0 +1,82 @@
|
||||
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'fs'
|
||||
import { tmpdir } from 'os'
|
||||
import { join } from 'path'
|
||||
import React from 'react'
|
||||
import { renderToString } from 'react-dom/server'
|
||||
import { SortableContext } from '@dnd-kit/sortable'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import {
|
||||
getOverride,
|
||||
getOverrideConfig,
|
||||
getOverrideItem,
|
||||
setOverrideConfig,
|
||||
updateOverrideItem
|
||||
} from './override'
|
||||
|
||||
let testDir = ''
|
||||
|
||||
vi.mock('../utils/dirs', () => ({
|
||||
overrideConfigPath: () => join(testDir, 'override.yaml'),
|
||||
overridePath: (id: string, ext: string) => join(testDir, `${id}.${ext}`)
|
||||
}))
|
||||
vi.mock('../utils/chromeRequest', () => ({ get: vi.fn() }))
|
||||
vi.mock('./controledMihomo', () => ({ getControledMihomoConfig: vi.fn() }))
|
||||
|
||||
beforeEach(() => {
|
||||
testDir = mkdtempSync(join(tmpdir(), 'clash-party-override-test-'))
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
rmSync(testDir, { recursive: true, force: true })
|
||||
})
|
||||
|
||||
function writeConfig(id: string): void {
|
||||
writeFileSync(
|
||||
join(testDir, 'override.yaml'),
|
||||
`items:\n - id: ${id}\n name: Existing override\n type: local\n ext: js\n updated: 0\n`
|
||||
)
|
||||
}
|
||||
|
||||
describe('legacy numeric override IDs', () => {
|
||||
it.each([
|
||||
['.inf', 'Infinity'],
|
||||
['-.inf', '-Infinity'],
|
||||
['.nan', 'NaN'],
|
||||
['123456', '123456'],
|
||||
['19e86409178', 'Infinity'],
|
||||
['"19e86409178"', '19e86409178']
|
||||
])('keeps %s usable after JSON transport', async (yamlId, expectedId) => {
|
||||
writeConfig(yamlId)
|
||||
const config = await getOverrideConfig(true)
|
||||
// SortableContext probes object IDs with `in`; null would throw here.
|
||||
const ids = JSON.parse(JSON.stringify(config)).items.map((item: IOverrideItem) => item.id)
|
||||
expect(() =>
|
||||
renderToString(<SortableContext items={ids}>{null}</SortableContext>)
|
||||
).not.toThrow()
|
||||
expect(config.items[0].id).toBe(expectedId)
|
||||
expect((await getOverrideItem(expectedId))?.name).toBe('Existing override')
|
||||
})
|
||||
|
||||
it('matches and persists the same ID during a queued update', async () => {
|
||||
writeConfig('.inf')
|
||||
const { items } = await getOverrideConfig(true)
|
||||
await updateOverrideItem({ ...items[0], global: true })
|
||||
const saved = await getOverrideConfig(true)
|
||||
expect(saved.items).toEqual([{ ...items[0], global: true }])
|
||||
expect(readFileSync(join(testDir, 'override.yaml'), 'utf8')).toContain('id: Infinity')
|
||||
})
|
||||
|
||||
it('preserves access to the existing override file', async () => {
|
||||
writeConfig('.inf')
|
||||
writeFileSync(join(testDir, 'Infinity.js'), 'function main(config) { return config }')
|
||||
const { items } = await getOverrideConfig(true)
|
||||
expect(await getOverride(items[0].id, items[0].ext)).toContain('function main')
|
||||
})
|
||||
|
||||
it('normalizes IDs before the setter JSON clone', async () => {
|
||||
writeConfig('.inf')
|
||||
const { items } = await getOverrideConfig(true)
|
||||
await setOverrideConfig({ items: [{ ...items[0], id: Infinity as unknown as string }] })
|
||||
expect((await getOverrideConfig(true)).items[0].id).toBe('Infinity')
|
||||
})
|
||||
})
|
||||
@@ -14,6 +14,17 @@ let overrideConfigVersion = 0
|
||||
// 与 profile.yaml 共用(见 runtimeConfigQueue.ts)
|
||||
const overrideConfigWriteQueue = runtimeConfigWriteQueue
|
||||
|
||||
// Legacy YAML can contain numeric IDs (including .inf). Convert them before
|
||||
// JSON cloning turns non-finite numbers into null, breaking the sortable UI.
|
||||
function normalizeOverrideIds(config: IOverrideConfig): IOverrideConfig {
|
||||
if (Array.isArray(config.items)) {
|
||||
config.items = config.items.map((item) =>
|
||||
item && typeof item.id === 'number' ? { ...item, id: String(item.id) } : item
|
||||
)
|
||||
}
|
||||
return config
|
||||
}
|
||||
|
||||
export async function getOverrideConfig(force = false): Promise<IOverrideConfig> {
|
||||
if (force || !overrideConfig) {
|
||||
const seen = overrideConfigVersion
|
||||
@@ -24,12 +35,12 @@ export async function getOverrideConfig(force = false): Promise<IOverrideConfig>
|
||||
}
|
||||
if (typeof overrideConfig !== 'object') overrideConfig = { items: [] }
|
||||
if (!Array.isArray(overrideConfig.items)) overrideConfig.items = []
|
||||
return JSON.parse(JSON.stringify(overrideConfig)) as IOverrideConfig
|
||||
return JSON.parse(JSON.stringify(normalizeOverrideIds(overrideConfig))) as IOverrideConfig
|
||||
}
|
||||
|
||||
export async function setOverrideConfig(config: IOverrideConfig): Promise<void> {
|
||||
await overrideConfigWriteQueue.run(async () => {
|
||||
const nextConfig = JSON.parse(JSON.stringify(config)) as IOverrideConfig
|
||||
const nextConfig = JSON.parse(JSON.stringify(normalizeOverrideIds(config))) as IOverrideConfig
|
||||
await atomicWriteFile(overrideConfigPath(), stringify(nextConfig), { encoding: 'utf8' })
|
||||
overrideConfig = nextConfig
|
||||
overrideConfigVersion++
|
||||
@@ -47,7 +58,9 @@ export async function updateOverrideConfig(
|
||||
throw new Error('Override config is invalid')
|
||||
}
|
||||
if (!Array.isArray(currentConfig.items)) currentConfig.items = []
|
||||
const nextConfig = updater(JSON.parse(JSON.stringify(currentConfig)) as IOverrideConfig)
|
||||
const nextConfig = updater(
|
||||
JSON.parse(JSON.stringify(normalizeOverrideIds(currentConfig))) as IOverrideConfig
|
||||
)
|
||||
await atomicWriteFile(overrideConfigPath(), stringify(nextConfig), { encoding: 'utf8' })
|
||||
overrideConfig = nextConfig
|
||||
overrideConfigVersion++
|
||||
|
||||
@@ -2,7 +2,7 @@ import { defineConfig } from 'vitest/config'
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
include: ['src/**/*.test.ts', 'scripts/**/*.test.ts'],
|
||||
include: ['src/**/*.test.{ts,tsx}', 'scripts/**/*.test.ts'],
|
||||
environment: 'node'
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user