重构 UI 编辑器状态存储类型
Project CI / Repository checks (pull_request) Successful in 2m52s
Project CI / Frontend tests (pull_request) Successful in 3m20s
Project CI / Backend tests (pull_request) Successful in 3m59s
Project CI / Native shell tests (pull_request) Successful in 14m50s

- 将 `UiDesignStateStore` 重命名为 `IUiDesignStateStore`,统一命名规范。
- 替换所有引用,移除 `mockUiDesignStateStore`,改用 `uiDesignStateStore`。
- 更新相关测试和技术文档以匹配重命名与实现更改。
This commit is contained in:
2026-08-20 16:00:09 +08:00
parent 9778ac1660
commit 086ebd0248
5 changed files with 26 additions and 25 deletions
@@ -14,7 +14,7 @@ export type UiDesignStateSaveResult =
}
| { status: 'conflict'; current: UiDesignStateSnapshot };
export type UiDesignStateStore = {
export type IUiDesignStateStore = {
load(assetId: string): Promise<UiDesignStateSnapshot>;
save(
assetId: string,
@@ -26,7 +26,7 @@ export type UiDesignStateStore = {
export function createTauriUiDesignStateStore(
projectPath: string,
expectedProjectId: string,
): UiDesignStateStore {
): IUiDesignStateStore {
return {
load(assetId) {
return invoke<UiDesignStateSnapshot>('load_ui_design_state', {
@@ -47,17 +47,17 @@ export function createTauriUiDesignStateStore(
};
}
const mockSnapshots = new Map<string, UiDesignStateSnapshot>();
const snapshots = new Map<string, UiDesignStateSnapshot>();
export const mockUiDesignStateStore: UiDesignStateStore = {
export const uiDesignStateStore: IUiDesignStateStore = {
async load(assetId) {
const snapshot = mockSnapshots.get(assetId);
const snapshot = snapshots.get(assetId);
return snapshot
? structuredClone(snapshot)
: { revision: 0, state: structuredClone(EMPTY_UI_EDITOR_STATE) };
},
async save(assetId, expectedRevision, state) {
const current = mockSnapshots.get(assetId) ?? {
const current = snapshots.get(assetId) ?? {
revision: 0,
state: structuredClone(EMPTY_UI_EDITOR_STATE),
};
@@ -68,7 +68,7 @@ export const mockUiDesignStateStore: UiDesignStateStore = {
revision: current.revision + 1,
state: structuredClone(state),
};
mockSnapshots.set(assetId, next);
snapshots.set(assetId, next);
return {
status: 'saved',
state: structuredClone(next.state),
@@ -4,8 +4,8 @@ import { useMemo, useState } from 'react';
import { ThemedModal } from '../../components/modal/ThemedModal';
import {
createTauriUiDesignStateStore,
mockUiDesignStateStore,
type UiDesignStateStore,
type IUiDesignStateStore,
uiDesignStateStore,
} from '../../features/ui-editor/uiDesignStateStore';
import { BindingOverview } from './components/BindingOverview';
import { EditorDialogs } from './components/EditorDialogs';
@@ -35,14 +35,14 @@ export default function UiEditorPage({
projectId?: string;
resourceLabel?: string;
onBack?: () => void;
stateStore?: UiDesignStateStore;
stateStore?: IUiDesignStateStore;
}) {
const resolvedStore = useMemo(
() =>
stateStore ??
(resourceId && projectId
? createTauriUiDesignStateStore(projectPath, projectId)
: mockUiDesignStateStore),
: uiDesignStateStore),
[projectId, projectPath, resourceId, stateStore],
);
const session = useUiEditorSession(projectPath, resourceId, resolvedStore);
@@ -29,8 +29,8 @@ import type { UIDesignImageRole } from '../../features/ui-editor/types/UIDesignI
import type { UIDesignSuggestionTreeNode } from '../../features/ui-editor/types/UIDesignSuggestionTreeNode';
import type { UiNodeMoveRequest } from '../../features/ui-editor/types/UiNodeMoveRequest';
import {
mockUiDesignStateStore,
type UiDesignStateStore,
type IUiDesignStateStore,
uiDesignStateStore,
} from '../../features/ui-editor/uiDesignStateStore';
import { applyUiDesignSuggestions } from '../../features/ui-editor/uiDesignSuggestions';
import { useUiEditorFontFaces } from '../../features/ui-editor/useUiEditorFontFaces';
@@ -170,7 +170,7 @@ export type PendingWorkflowStepChange = {
export function useUiEditorSession(
projectPath: string,
resourceId?: string,
stateStore: UiDesignStateStore = mockUiDesignStateStore,
stateStore: IUiDesignStateStore = uiDesignStateStore,
) {
const editor = useUiEditorState(EMPTY_UI_EDITOR_STATE);
const replaceEditorState = editor.replaceState;
@@ -6,26 +6,26 @@ import { invoke } from '@tauri-apps/api/core';
import {
createTauriUiDesignStateStore,
mockUiDesignStateStore,
uiDesignStateStore,
} from '../src/features/ui-editor/uiDesignStateStore';
describe('UI 设计 State mock store', () => {
it('按资源保留快照并拒绝过期 revision', async () => {
const first = await mockUiDesignStateStore.load('ui-1');
const first = await uiDesignStateStore.load('ui-1');
first.state.ui_trees.push({} as never);
await expect(
mockUiDesignStateStore.save('ui-1', first.revision, first.state),
uiDesignStateStore.save('ui-1', first.revision, first.state),
).resolves.toMatchObject({
status: 'saved',
revision: 1,
});
await expect(mockUiDesignStateStore.load('ui-1')).resolves.toMatchObject({
await expect(uiDesignStateStore.load('ui-1')).resolves.toMatchObject({
revision: 1,
state: { ui_trees: [{}] },
});
await expect(
mockUiDesignStateStore.save('ui-1', first.revision, first.state),
uiDesignStateStore.save('ui-1', first.revision, first.state),
).resolves.toMatchObject({ status: 'conflict', current: { revision: 1 } });
});
@@ -28,8 +28,8 @@ import { invoke } from '@tauri-apps/api/core';
import type { Node as UiNode } from '../src/features/ui-editor/types/Node';
import type { State } from '../src/features/ui-editor/types/State';
import type {
IUiDesignStateStore,
UiDesignStateSnapshot,
UiDesignStateStore,
} from '../src/features/ui-editor/uiDesignStateStore';
import UiEditorPage from '../src/view/ui-editor';
import { useUiEditorSession } from '../src/view/ui-editor/useUiEditorPage';
@@ -78,6 +78,7 @@ function node(id: string, children: UiNode[] = []): UiNode {
source: 'System',
},
components: [],
children_display_mode: undefined,
children,
};
}
@@ -133,7 +134,7 @@ function stateWithPages(pageIds: string[]): State {
}
async function renderLoadedSession(state: State) {
const stateStore: UiDesignStateStore = {
const stateStore: IUiDesignStateStore = {
load: vi.fn().mockResolvedValue({ revision: 0, state }),
save: vi.fn(),
};
@@ -158,7 +159,7 @@ describe('UiEditorPage', () => {
configurable: true,
value: { core: { invoke } },
});
const stateStore: UiDesignStateStore = {
const stateStore: IUiDesignStateStore = {
load: vi.fn().mockResolvedValue({
revision: 0,
state: stateWithPages(['page']),
@@ -216,7 +217,7 @@ describe('UiEditorPage', () => {
it('does not cancel a preview scope before state loading creates one', () => {
vi.mocked(invoke).mockClear();
const stateStore: UiDesignStateStore = {
const stateStore: IUiDesignStateStore = {
load: vi.fn(() => new Promise(() => undefined)),
save: vi.fn(),
};
@@ -533,7 +534,7 @@ describe('UiEditorPage', () => {
});
it('shows a page-level save failure and allows a retry', async () => {
const stateStore: UiDesignStateStore = {
const stateStore: IUiDesignStateStore = {
load: vi.fn().mockResolvedValue(structuredClone(EMPTY_SNAPSHOT)),
save: vi.fn().mockRejectedValue(new Error('临时存储不可用')),
};
@@ -563,7 +564,7 @@ describe('UiEditorPage', () => {
});
it('keeps the dedicated message for a save conflict', async () => {
const stateStore: UiDesignStateStore = {
const stateStore: IUiDesignStateStore = {
load: vi.fn().mockResolvedValue(structuredClone(EMPTY_SNAPSHOT)),
save: vi.fn().mockResolvedValue({
status: 'conflict',