From 7394a2591cceb69188d6c5210c72ca45a1ddbd71 Mon Sep 17 00:00:00 2001 From: unanmed <1319491857@qq.com> Date: Mon, 14 Sep 2026 16:51:59 +0800 Subject: [PATCH] test(06-08): cover common direction utils and MapLocIndexer (stage 1) - add utils.test.ts covering getFaceMovement/degradeFace/nextFaceDirection/fromDirectionString for all directions - add indexer.test.ts covering MapLocIndexer coordinate/index round-trip and setWidth stride --- .../data-common/src/common/indexer.test.ts | 57 +++++ .../data-common/src/common/utils.test.ts | 203 ++++++++++++++++++ 2 files changed, 260 insertions(+) create mode 100644 packages-user/data-common/src/common/indexer.test.ts create mode 100644 packages-user/data-common/src/common/utils.test.ts diff --git a/packages-user/data-common/src/common/indexer.test.ts b/packages-user/data-common/src/common/indexer.test.ts new file mode 100644 index 0000000..cfc0514 --- /dev/null +++ b/packages-user/data-common/src/common/indexer.test.ts @@ -0,0 +1,57 @@ +// 测试 L0 MapLocIndexer 的坐标与索引互转及地图宽度切换 +import { describe, expect, it } from 'vitest'; +import { MapLocIndexer } from './indexer'; + +/** 创建索引器并完成地图宽度设置 */ +function createIndexer(width: number): MapLocIndexer { + const indexer = new MapLocIndexer(); + indexer.setWidth(width); + return indexer; +} + +describe('MapLocIndexer', () => { + // 验证坐标形式与定位符形式对同一位置返回一致的索引 + it('agrees between the coordinate and locator forms', () => { + const indexer = createIndexer(5); + + expect(indexer.locToIndex(0, 0)).toBe(0); + expect(indexer.locToIndex(2, 3)).toBe(17); + expect(indexer.locaterToIndex({ x: 2, y: 3 })).toBe(17); + expect(indexer.locaterToIndex({ x: 2, y: 3 })).toBe( + indexer.locToIndex(2, 3) + ); + }); + + // 验证范围内索引可经 indexToLocator 与 locToIndex 无损往返 + it('round-trips every in-range index back to its locator', () => { + const indexer = createIndexer(5); + + for (let index = 0; index < 20; index++) { + const locator = indexer.indexToLocator(index); + expect(indexer.locToIndex(locator.x, locator.y)).toBe(index); + expect(indexer.locaterToIndex(locator)).toBe(index); + } + }); + + // 验证扁平索引按宽度拆分为列与行(含行末与次行首) + it('splits a flat index into its column and row', () => { + const indexer = createIndexer(4); + + expect(indexer.indexToLocator(0)).toEqual({ x: 0, y: 0 }); + expect(indexer.indexToLocator(3)).toEqual({ x: 3, y: 0 }); + expect(indexer.indexToLocator(4)).toEqual({ x: 0, y: 1 }); + expect(indexer.indexToLocator(9)).toEqual({ x: 1, y: 2 }); + }); + + // 验证 setWidth 改变行步长后,同一坐标映射到新索引 + it('changes the row stride after setWidth', () => { + const indexer = createIndexer(4); + + expect(indexer.locToIndex(1, 2)).toBe(9); + + indexer.setWidth(10); + + expect(indexer.locToIndex(1, 2)).toBe(21); + expect(indexer.indexToLocator(21)).toEqual({ x: 1, y: 2 }); + }); +}); diff --git a/packages-user/data-common/src/common/utils.test.ts b/packages-user/data-common/src/common/utils.test.ts new file mode 100644 index 0000000..cbce799 --- /dev/null +++ b/packages-user/data-common/src/common/utils.test.ts @@ -0,0 +1,203 @@ +// 测试 L0 common 朝向工具纯函数:坐标偏移、降级、旋转与字符串解析 +import { describe, expect, it } from 'vitest'; +import { FaceDirection } from './types'; +import { + degradeFace, + fromDirectionString, + getFaceMovement, + nextFaceDirection +} from './utils'; + +/** 全部朝向枚举值,用于校验方向表的完整覆盖 */ +const ALL_DIRECTIONS: readonly FaceDirection[] = [ + FaceDirection.Unknown, + FaceDirection.Left, + FaceDirection.Up, + FaceDirection.Right, + FaceDirection.Down, + FaceDirection.LeftUp, + FaceDirection.RightUp, + FaceDirection.LeftDown, + FaceDirection.RightDown +]; + +/** 每个朝向对应的单步坐标偏移量 */ +const FACE_MOVEMENTS: readonly [FaceDirection, Loc][] = [ + [FaceDirection.Unknown, { x: 0, y: 0 }], + [FaceDirection.Left, { x: -1, y: 0 }], + [FaceDirection.Up, { x: 0, y: -1 }], + [FaceDirection.Right, { x: 1, y: 0 }], + [FaceDirection.Down, { x: 0, y: 1 }], + [FaceDirection.LeftUp, { x: -1, y: -1 }], + [FaceDirection.RightUp, { x: 1, y: -1 }], + [FaceDirection.LeftDown, { x: -1, y: 1 }], + [FaceDirection.RightDown, { x: 1, y: 1 }] +]; + +describe('getFaceMovement', () => { + // 验证全部朝向(含未知)都返回唯一且正确的坐标偏移量 + it('returns the coordinate offset for every face direction', () => { + for (const [dir, movement] of FACE_MOVEMENTS) { + expect(getFaceMovement(dir)).toEqual(movement); + } + expect(FACE_MOVEMENTS.map(item => item[0]).sort()).toEqual( + [...ALL_DIRECTIONS].sort() + ); + }); +}); + +describe('degradeFace', () => { + // 验证对角朝向降级为对应的左/右,正交朝向原样返回 + it('degrades diagonals into their orthogonal direction', () => { + expect(degradeFace(FaceDirection.LeftUp)).toBe(FaceDirection.Left); + expect(degradeFace(FaceDirection.LeftDown)).toBe(FaceDirection.Left); + expect(degradeFace(FaceDirection.RightUp)).toBe(FaceDirection.Right); + expect(degradeFace(FaceDirection.RightDown)).toBe(FaceDirection.Right); + expect(degradeFace(FaceDirection.Left)).toBe(FaceDirection.Left); + expect(degradeFace(FaceDirection.Right)).toBe(FaceDirection.Right); + expect(degradeFace(FaceDirection.Up)).toBe(FaceDirection.Up); + expect(degradeFace(FaceDirection.Down)).toBe(FaceDirection.Down); + }); + + // 验证未知朝向默认返回未知,且可经 unknown 参数自定义返回值 + it('maps unknown to the configurable fallback', () => { + expect(degradeFace(FaceDirection.Unknown)).toBe(FaceDirection.Unknown); + expect(degradeFace(FaceDirection.Unknown, FaceDirection.Down)).toBe( + FaceDirection.Down + ); + }); +}); + +describe('nextFaceDirection', () => { + // 验证四方向顺时针旋转顺序为上→右→下→左 + it('rotates four directions clockwise', () => { + expect(nextFaceDirection(FaceDirection.Up)).toBe(FaceDirection.Right); + expect(nextFaceDirection(FaceDirection.Right)).toBe(FaceDirection.Down); + expect(nextFaceDirection(FaceDirection.Down)).toBe(FaceDirection.Left); + expect(nextFaceDirection(FaceDirection.Left)).toBe(FaceDirection.Up); + }); + + // 验证四方向逆时针旋转顺序为上→左→下→右 + it('rotates four directions anticlockwise', () => { + expect(nextFaceDirection(FaceDirection.Up, true)).toBe( + FaceDirection.Left + ); + expect(nextFaceDirection(FaceDirection.Left, true)).toBe( + FaceDirection.Down + ); + expect(nextFaceDirection(FaceDirection.Down, true)).toBe( + FaceDirection.Right + ); + expect(nextFaceDirection(FaceDirection.Right, true)).toBe( + FaceDirection.Up + ); + }); + + // 验证四方向模式下对角朝向同样按九十度旋转 + it('rotates diagonal directions in four-way mode', () => { + expect(nextFaceDirection(FaceDirection.LeftUp)).toBe( + FaceDirection.RightUp + ); + expect(nextFaceDirection(FaceDirection.RightUp)).toBe( + FaceDirection.RightDown + ); + expect(nextFaceDirection(FaceDirection.RightDown)).toBe( + FaceDirection.LeftDown + ); + expect(nextFaceDirection(FaceDirection.LeftDown)).toBe( + FaceDirection.LeftUp + ); + }); + + // 验证八方向顺时针按四十五度依次推进一整圈 + it('rotates eight directions clockwise', () => { + expect(nextFaceDirection(FaceDirection.Up, false, true)).toBe( + FaceDirection.RightUp + ); + expect(nextFaceDirection(FaceDirection.RightUp, false, true)).toBe( + FaceDirection.Right + ); + expect(nextFaceDirection(FaceDirection.Right, false, true)).toBe( + FaceDirection.RightDown + ); + expect(nextFaceDirection(FaceDirection.RightDown, false, true)).toBe( + FaceDirection.Down + ); + expect(nextFaceDirection(FaceDirection.Down, false, true)).toBe( + FaceDirection.LeftDown + ); + expect(nextFaceDirection(FaceDirection.LeftDown, false, true)).toBe( + FaceDirection.Left + ); + expect(nextFaceDirection(FaceDirection.Left, false, true)).toBe( + FaceDirection.LeftUp + ); + expect(nextFaceDirection(FaceDirection.LeftUp, false, true)).toBe( + FaceDirection.Up + ); + }); + + // 验证八方向逆时针按四十五度依次回退一整圈 + it('rotates eight directions anticlockwise', () => { + expect(nextFaceDirection(FaceDirection.Up, true, true)).toBe( + FaceDirection.LeftUp + ); + expect(nextFaceDirection(FaceDirection.LeftUp, true, true)).toBe( + FaceDirection.Left + ); + expect(nextFaceDirection(FaceDirection.Left, true, true)).toBe( + FaceDirection.LeftDown + ); + expect(nextFaceDirection(FaceDirection.LeftDown, true, true)).toBe( + FaceDirection.Down + ); + expect(nextFaceDirection(FaceDirection.Down, true, true)).toBe( + FaceDirection.RightDown + ); + expect(nextFaceDirection(FaceDirection.RightDown, true, true)).toBe( + FaceDirection.Right + ); + expect(nextFaceDirection(FaceDirection.Right, true, true)).toBe( + FaceDirection.RightUp + ); + expect(nextFaceDirection(FaceDirection.RightUp, true, true)).toBe( + FaceDirection.Up + ); + }); + + // 验证未知朝向在所有旋转模式下都原样透传 + it('passes unknown through every rotation mode', () => { + expect(nextFaceDirection(FaceDirection.Unknown)).toBe( + FaceDirection.Unknown + ); + expect(nextFaceDirection(FaceDirection.Unknown, true)).toBe( + FaceDirection.Unknown + ); + expect(nextFaceDirection(FaceDirection.Unknown, false, true)).toBe( + FaceDirection.Unknown + ); + expect(nextFaceDirection(FaceDirection.Unknown, true, true)).toBe( + FaceDirection.Unknown + ); + }); +}); + +describe('fromDirectionString', () => { + // 验证每个合法朝向字符串都解析为对应枚举值 + it('parses every direction string', () => { + expect(fromDirectionString('left')).toBe(FaceDirection.Left); + expect(fromDirectionString('right')).toBe(FaceDirection.Right); + expect(fromDirectionString('up')).toBe(FaceDirection.Up); + expect(fromDirectionString('down')).toBe(FaceDirection.Down); + expect(fromDirectionString('leftup')).toBe(FaceDirection.LeftUp); + expect(fromDirectionString('rightup')).toBe(FaceDirection.RightUp); + expect(fromDirectionString('leftdown')).toBe(FaceDirection.LeftDown); + expect(fromDirectionString('rightdown')).toBe(FaceDirection.RightDown); + }); + + // 验证无法识别的字符串回退为未知朝向 + it('falls back to unknown for an unrecognized string', () => { + const invalid = 'sideways' as Dir2; + expect(fromDirectionString(invalid)).toBe(FaceDirection.Unknown); + }); +});