From c09e0eb4e749d1952db963aa04b01d018303cbae Mon Sep 17 00:00:00 2001 From: Misaka Date: Sat, 21 Mar 2026 19:45:19 +0800 Subject: [PATCH] test: cover renderer state helpers --- .../src/hooks/useSharedProductionIds.ts | 20 ++++- .../src/hooks/useUpdateDialogState.ts | 6 +- tests/unit/use-shared-production-ids.test.ts | 40 +++++++++ tests/unit/use-update-dialog-state.test.ts | 81 +++++++++++++++++++ 4 files changed, 141 insertions(+), 6 deletions(-) create mode 100644 tests/unit/use-shared-production-ids.test.ts create mode 100644 tests/unit/use-update-dialog-state.test.ts diff --git a/src/renderer/src/hooks/useSharedProductionIds.ts b/src/renderer/src/hooks/useSharedProductionIds.ts index b33968f..737b85a 100644 --- a/src/renderer/src/hooks/useSharedProductionIds.ts +++ b/src/renderer/src/hooks/useSharedProductionIds.ts @@ -1,21 +1,33 @@ import { useEffect } from 'react' -function parseProductionIds(input: string): string[] { +interface ValidationSyncApi { + clearSharedProductionIds: () => Promise + setSharedProductionIds: (ids: string[]) => Promise +} + +export function parseProductionIds(input: string): string[] { return input .split('\n') .map((line) => line.trim()) .filter((line) => line.length > 0) } -async function syncSharedProductionIds(input: string): Promise { +export async function syncSharedProductionIdsWithApi( + input: string, + validationApi: ValidationSyncApi +): Promise { const productionIds = parseProductionIds(input) if (productionIds.length === 0) { - await window.electron.validation.clearSharedProductionIds() + await validationApi.clearSharedProductionIds() return } - await window.electron.validation.setSharedProductionIds(productionIds) + await validationApi.setSharedProductionIds(productionIds) +} + +async function syncSharedProductionIds(input: string): Promise { + await syncSharedProductionIdsWithApi(input, window.electron.validation) } export function useSharedProductionIds(orderNumbers: string, debounceMs = 300) { diff --git a/src/renderer/src/hooks/useUpdateDialogState.ts b/src/renderer/src/hooks/useUpdateDialogState.ts index 431ad50..6e16bd6 100644 --- a/src/renderer/src/hooks/useUpdateDialogState.ts +++ b/src/renderer/src/hooks/useUpdateDialogState.ts @@ -1,11 +1,13 @@ import { startTransition, useEffect, useRef, useState } from 'react' import type { UpdateDialogCatalog, UpdateRelease } from '../../../main/types/update.types' -function releaseKey(release: UpdateRelease): string { +export function releaseKey(release: UpdateRelease): string { return `${release.channel}:${release.version}` } -function getInitialSelectedRelease(catalog: UpdateDialogCatalog | null): UpdateRelease | undefined { +export function getInitialSelectedRelease( + catalog: UpdateDialogCatalog | null +): UpdateRelease | undefined { if (!catalog || catalog.mode === 'disabled') { return undefined } diff --git a/tests/unit/use-shared-production-ids.test.ts b/tests/unit/use-shared-production-ids.test.ts new file mode 100644 index 0000000..2058784 --- /dev/null +++ b/tests/unit/use-shared-production-ids.test.ts @@ -0,0 +1,40 @@ +import { describe, expect, it, vi } from 'vitest' +import { + parseProductionIds, + syncSharedProductionIdsWithApi +} from '../../src/renderer/src/hooks/useSharedProductionIds' + +describe('useSharedProductionIds helpers', () => { + it('parses trimmed non-empty production ids', () => { + expect(parseProductionIds(' 22A1 \n\nSC70202602120085\n ')).toEqual([ + '22A1', + 'SC70202602120085' + ]) + }) + + it('clears shared ids when parsed input is empty', async () => { + const clearSharedProductionIds = vi.fn(async () => undefined) + const setSharedProductionIds = vi.fn(async () => undefined) + + await syncSharedProductionIdsWithApi(' \n\t', { + clearSharedProductionIds, + setSharedProductionIds + }) + + expect(clearSharedProductionIds).toHaveBeenCalledTimes(1) + expect(setSharedProductionIds).not.toHaveBeenCalled() + }) + + it('writes parsed ids when input contains values', async () => { + const clearSharedProductionIds = vi.fn(async () => undefined) + const setSharedProductionIds = vi.fn(async () => undefined) + + await syncSharedProductionIdsWithApi('22A1\nSC70202602120085', { + clearSharedProductionIds, + setSharedProductionIds + }) + + expect(clearSharedProductionIds).not.toHaveBeenCalled() + expect(setSharedProductionIds).toHaveBeenCalledWith(['22A1', 'SC70202602120085']) + }) +}) diff --git a/tests/unit/use-update-dialog-state.test.ts b/tests/unit/use-update-dialog-state.test.ts new file mode 100644 index 0000000..e2a6057 --- /dev/null +++ b/tests/unit/use-update-dialog-state.test.ts @@ -0,0 +1,81 @@ +import { describe, expect, it } from 'vitest' +import type { UpdateDialogCatalog, UpdateRelease } from '../../src/main/types/update.types' +import { + getInitialSelectedRelease, + releaseKey +} from '../../src/renderer/src/hooks/useUpdateDialogState' + +const stableRelease: UpdateRelease = { + version: '1.4.1', + channel: 'stable', + artifactKey: 'stable-141.exe', + sha256: 'stable-hash', + size: 100, + publishedAt: '2026-03-21T10:00:00.000Z', + changelogKey: 'stable-141.md', + notesSummary: 'stable summary' +} + +const previewRelease: UpdateRelease = { + version: '1.5.0-preview.1', + channel: 'preview', + artifactKey: 'preview-150.exe', + sha256: 'preview-hash', + size: 100, + publishedAt: '2026-03-22T10:00:00.000Z', + changelogKey: 'preview-150.md', + notesSummary: 'preview summary' +} + +describe('useUpdateDialogState helpers', () => { + it('builds stable release keys', () => { + expect(releaseKey(stableRelease)).toBe('stable:1.4.1') + }) + + it('returns undefined for disabled catalog', () => { + const catalog: UpdateDialogCatalog = { mode: 'disabled' } + expect(getInitialSelectedRelease(catalog)).toBeUndefined() + }) + + it('prefers recommended user release', () => { + const catalog: UpdateDialogCatalog = { + mode: 'user', + recommendedRelease: stableRelease + } + + expect(getInitialSelectedRelease(catalog)).toEqual(stableRelease) + }) + + it('prefers recommended admin release before channel fallbacks', () => { + const catalog: UpdateDialogCatalog = { + mode: 'admin', + recommendedRelease: previewRelease, + channels: { + stable: [stableRelease], + preview: [previewRelease] + } + } + + expect(getInitialSelectedRelease(catalog)).toEqual(previewRelease) + }) + + it('falls back to stable then preview when admin recommendation is absent', () => { + const stableCatalog: UpdateDialogCatalog = { + mode: 'admin', + channels: { + stable: [stableRelease], + preview: [previewRelease] + } + } + const previewCatalog: UpdateDialogCatalog = { + mode: 'admin', + channels: { + stable: [], + preview: [previewRelease] + } + } + + expect(getInitialSelectedRelease(stableCatalog)).toEqual(stableRelease) + expect(getInitialSelectedRelease(previewCatalog)).toEqual(previewRelease) + }) +})