100 lines
3.8 KiB
TypeScript
100 lines
3.8 KiB
TypeScript
import { describe, it, expect } from 'vitest'
|
|
import { parseState, clampBounds, MIN_WIDTH, MIN_HEIGHT, type Rect } from './windowState'
|
|
|
|
/** 1920×1040 工作区(常见全高清屏去掉任务栏) */
|
|
const work: Rect = { x: 0, y: 0, width: 1920, height: 1040 }
|
|
|
|
describe('parseState', () => {
|
|
it('空输入回退空状态', () => {
|
|
expect(parseState(null)).toEqual({})
|
|
expect(parseState(undefined)).toEqual({})
|
|
expect(parseState('')).toEqual({})
|
|
})
|
|
|
|
it('非法 JSON 回退空状态', () => {
|
|
expect(parseState('{broken json!!')).toEqual({})
|
|
})
|
|
|
|
it('合法 bounds 与 lastDir 被保留', () => {
|
|
const s = parseState(
|
|
JSON.stringify({ bounds: { x: 10, y: 20, width: 1000, height: 700 }, lastDir: 'C:/docs' })
|
|
)
|
|
expect(s.bounds).toEqual({ x: 10, y: 20, width: 1000, height: 700 })
|
|
expect(s.lastDir).toBe('C:/docs')
|
|
})
|
|
|
|
it('bounds 字段类型异常或缺失时被忽略', () => {
|
|
expect(parseState(JSON.stringify({ bounds: { x: 'a', y: 2, width: 1000, height: 700 } })).bounds).toBeUndefined()
|
|
expect(parseState(JSON.stringify({ bounds: { x: 1, y: 2, width: 1000 } })).bounds).toBeUndefined()
|
|
expect(parseState(JSON.stringify({ bounds: null })).bounds).toBeUndefined()
|
|
})
|
|
|
|
it('bounds 含 NaN/Infinity 等非有限数值时被忽略', () => {
|
|
const s = parseState(
|
|
'{"bounds":{"x":NaN,"y":2,"width":1000,"height":700}}'
|
|
)
|
|
expect(s.bounds).toBeUndefined()
|
|
})
|
|
|
|
it('lastDir 非字符串或空串时被忽略', () => {
|
|
expect(parseState(JSON.stringify({ lastDir: 42 })).lastDir).toBeUndefined()
|
|
expect(parseState(JSON.stringify({ lastDir: '' })).lastDir).toBeUndefined()
|
|
})
|
|
})
|
|
|
|
describe('clampBounds', () => {
|
|
it('工作区内合法 bounds 原样保留', () => {
|
|
const b = { x: 100, y: 50, width: 1280, height: 820 }
|
|
expect(clampBounds(b, work)).toEqual(b)
|
|
})
|
|
|
|
it('小于最小尺寸时钳制到最小值', () => {
|
|
const c = clampBounds({ x: 100, y: 100, width: 400, height: 300 }, work)
|
|
expect(c.width).toBe(MIN_WIDTH)
|
|
expect(c.height).toBe(MIN_HEIGHT)
|
|
})
|
|
|
|
it('超出工作区尺寸时封顶到工作区大小', () => {
|
|
const c = clampBounds({ x: 0, y: 0, width: 4000, height: 3000 }, work)
|
|
expect(c.width).toBe(work.width)
|
|
expect(c.height).toBe(work.height)
|
|
})
|
|
|
|
it('窗口移出屏幕左侧时拉回(右边缘至少 60px 进入工作区)', () => {
|
|
const c = clampBounds({ x: -2000, y: 100, width: 1000, height: 700 }, work)
|
|
expect(c.x).toBe(work.x - 1000 + 60)
|
|
})
|
|
|
|
it('窗口移出屏幕右侧时拉回(左边缘至少 60px 进入工作区)', () => {
|
|
const c = clampBounds({ x: 5000, y: 100, width: 1000, height: 700 }, work)
|
|
expect(c.x).toBe(work.x + work.width - 60)
|
|
})
|
|
|
|
it('窗口移出屏幕下方时拉回', () => {
|
|
const c = clampBounds({ x: 100, y: 5000, width: 1000, height: 700 }, work)
|
|
expect(c.y).toBe(work.y + work.height - 60)
|
|
})
|
|
|
|
it('带偏移的工作区(多显示器副屏)同样钳制', () => {
|
|
const side: Rect = { x: 1920, y: 200, width: 1080, height: 900 }
|
|
const c = clampBounds({ x: 0, y: 0, width: 1000, height: 700 }, side)
|
|
// 左边缘至少 60px 进入副屏
|
|
expect(c.x).toBe(side.x - 1000 + 60)
|
|
})
|
|
|
|
it('非整数尺寸与坐标被取整', () => {
|
|
const c = clampBounds({ x: 10.6, y: 20.2, width: 1000.7, height: 700.1 }, work)
|
|
expect(Number.isInteger(c.x)).toBe(true)
|
|
expect(Number.isInteger(c.y)).toBe(true)
|
|
expect(Number.isInteger(c.width)).toBe(true)
|
|
expect(Number.isInteger(c.height)).toBe(true)
|
|
})
|
|
|
|
it('工作区小于最小尺寸时尺寸仍保持最小值', () => {
|
|
const tiny: Rect = { x: 0, y: 0, width: 800, height: 500 }
|
|
const c = clampBounds({ x: 0, y: 0, width: 1000, height: 700 }, tiny)
|
|
expect(c.width).toBe(MIN_WIDTH)
|
|
expect(c.height).toBe(MIN_HEIGHT)
|
|
})
|
|
})
|