diff --git a/src/components/EffortPanel/__tests__/effortPanelState.test.ts b/src/components/EffortPanel/__tests__/effortPanelState.test.ts new file mode 100644 index 000000000..8760cd4e3 --- /dev/null +++ b/src/components/EffortPanel/__tests__/effortPanelState.test.ts @@ -0,0 +1,101 @@ +import { describe, expect, test } from 'bun:test' +import { + END_POSITION, + HOME_POSITION, + PANEL_POSITIONS, + type PanelPosition, + getInitialCursor, + isUltracode, + moveLeft, + moveRight, +} from '../effortPanelState.js' + +describe('effortPanelState', () => { + test('PANEL_POSITIONS 顺序为 low → ultracode', () => { + expect(PANEL_POSITIONS).toEqual([ + 'low', + 'medium', + 'high', + 'xhigh', + 'max', + 'ultracode', + ]) + }) + + test('moveLeft 在 low 处保持 low', () => { + expect(moveLeft('low')).toBe('low') + }) + + test('moveLeft 正常左移', () => { + expect(moveLeft('high')).toBe('medium') + expect(moveLeft('ultracode')).toBe('max') + }) + + test('moveRight 在 ultracode 处保持 ultracode', () => { + expect(moveRight('ultracode')).toBe('ultracode') + }) + + test('moveRight 正常右移', () => { + expect(moveRight('medium')).toBe('high') + expect(moveRight('max')).toBe('ultracode') + }) + + test('HOME_POSITION 等于 low', () => { + expect(HOME_POSITION).toBe('low') + }) + + test('END_POSITION 等于 ultracode', () => { + expect(END_POSITION).toBe('ultracode') + }) + + test('isUltracode 守卫', () => { + expect(isUltracode('ultracode')).toBe(true) + expect(isUltracode('max')).toBe(false) + }) + + test('getInitialCursor:env override 为合法档位时返回 env 值', () => { + expect( + getInitialCursor({ + envOverride: 'high', + appStateEffort: 'medium', + displayed: 'high', + }), + ).toBe('high') + }) + + test('getInitialCursor:env 为 null(unset)时用 displayed', () => { + expect( + getInitialCursor({ + envOverride: null, + appStateEffort: undefined, + displayed: 'medium', + }), + ).toBe('medium') + }) + + test('getInitialCursor:env undefined 时用 displayed', () => { + expect( + getInitialCursor({ + envOverride: undefined, + appStateEffort: 'high', + displayed: 'high', + }), + ).toBe('high') + }) + + test('getInitialCursor:env 是数值(ant-only)时落回 displayed', () => { + // 数值不是合法 PanelPosition,回退 + expect( + getInitialCursor({ + envOverride: 75, + appStateEffort: 'medium', + displayed: 'medium', + }), + ).toBe('medium') + }) + + test('PanelPosition 类型编译期检查(隐式)', () => { + const p: PanelPosition = 'xhigh' + expect(p).toBe('xhigh') + }) +}) diff --git a/src/components/EffortPanel/effortPanelState.ts b/src/components/EffortPanel/effortPanelState.ts new file mode 100644 index 000000000..858a89438 --- /dev/null +++ b/src/components/EffortPanel/effortPanelState.ts @@ -0,0 +1,91 @@ +import type { EffortValue } from '../../utils/effort.js' + +/** + * 光标在面板上的位置。仅面板内部使用,不进入 AppState / settings / API。 + * 'ultracode' 不是 EffortLevel;它在本面板里仅作视觉占位与文案引导。 + */ +export type PanelPosition = + | 'low' + | 'medium' + | 'high' + | 'xhigh' + | 'max' + | 'ultracode' + +export const PANEL_POSITIONS: readonly PanelPosition[] = [ + 'low', + 'medium', + 'high', + 'xhigh', + 'max', + 'ultracode', +] as const + +export const HOME_POSITION: PanelPosition = 'low' +export const END_POSITION: PanelPosition = 'ultracode' + +/** + * 判断一个值是否可作为面板光标位置(不含 ultracode,因 ultracode 仅由面板内部产生)。 + */ +function isNonUltracodePosition( + value: unknown, +): value is Exclude { + return ( + typeof value === 'string' && + value !== 'ultracode' && + (PANEL_POSITIONS as readonly string[]).includes(value) + ) +} + +/** + * 把 EffortValue 归一化为面板可用的光标位置。 + * - null / undefined / 数值(ant-only)/ ultracode → undefined(让上层用 displayed) + * - 合法 string 档位 → 返回该档位 + */ +function normalizeToPanelPosition( + value: EffortValue | null | undefined, +): PanelPosition | undefined { + if (value === null || value === undefined) return undefined + if (typeof value === 'number') return undefined + if (isNonUltracodePosition(value)) { + return value + } + return undefined +} + +export function moveLeft(cursor: PanelPosition): PanelPosition { + const idx = PANEL_POSITIONS.indexOf(cursor) + if (idx <= 0) return PANEL_POSITIONS[0] + return PANEL_POSITIONS[idx - 1] +} + +export function moveRight(cursor: PanelPosition): PanelPosition { + const idx = PANEL_POSITIONS.indexOf(cursor) + if (idx === -1 || idx >= PANEL_POSITIONS.length - 1) { + return PANEL_POSITIONS[PANEL_POSITIONS.length - 1] + } + return PANEL_POSITIONS[idx + 1] +} + +export function isUltracode(cursor: PanelPosition): boolean { + return cursor === 'ultracode' +} + +/** + * 决定面板挂载时的初始光标位置。 + * 优先级:env override(若是合法档位)> displayed level + * + * @param envOverride getEffortEnvOverride() 的返回值:EffortValue | null | undefined + * @param appStateEffort AppState.effortValue + * @param displayed getDisplayedEffortLevel(model, appStateEffort) —— 必传,避免此处再依赖 model + */ +export function getInitialCursor(args: { + envOverride: EffortValue | null | undefined + appStateEffort: EffortValue | undefined + displayed: PanelPosition +}): PanelPosition { + const fromEnv = normalizeToPanelPosition(args.envOverride) + if (fromEnv !== undefined) return fromEnv + // displayed 已经是 EffortLevel(不含 ultracode),合法 + return args.displayed +}