Spaces:
Sleeping
Sleeping
| import { describe, it, expect, beforeEach } from 'vitest'; | |
| import * as THREE from 'three'; | |
| import { stickToAxes, isUiTouchTarget } from '../src/ui/TouchControls.js'; | |
| import { Navigation } from '../src/engine/Navigation.js'; | |
| describe('stickToAxes', () => { | |
| it('maps stick-up to forward (−z) and right to +x', () => { | |
| const up = stickToAxes(0, -48, 48); | |
| expect(up.z).toBeCloseTo(1, 5); | |
| expect(Math.abs(up.x)).toBeLessThan(0.01); | |
| const right = stickToAxes(48, 0, 48); | |
| expect(right.x).toBeCloseTo(1, 5); | |
| expect(Math.abs(right.z)).toBeLessThan(0.01); | |
| }); | |
| it('clamps magnitude to unit circle', () => { | |
| const a = stickToAxes(100, 100, 48); | |
| expect(Math.hypot(a.x, a.z)).toBeCloseTo(1, 5); | |
| }); | |
| }); | |
| describe('isUiTouchTarget', () => { | |
| it('treats dock/hud ancestors as UI', () => { | |
| const dock = { id: 'left-dock', tagName: 'DIV', closest: (sel) => (sel.includes('#left-dock') ? dock : null) }; | |
| const child = { tagName: 'BUTTON', closest: (sel) => dock.closest(sel) }; | |
| expect(isUiTouchTarget(child)).toBe(true); | |
| }); | |
| it('treats Visualization sheet as UI', () => { | |
| const sheet = { | |
| id: 'visualization-controls-container', | |
| tagName: 'DIV', | |
| closest: (sel) => (sel.includes('#visualization-controls-container') ? sheet : null), | |
| }; | |
| const child = { tagName: 'BUTTON', closest: (sel) => sheet.closest(sel) }; | |
| expect(isUiTouchTarget(child)).toBe(true); | |
| }); | |
| it('treats field-info tip as UI', () => { | |
| const tip = { | |
| className: 'field-info-tip', | |
| tagName: 'DIV', | |
| closest: (sel) => (sel.includes('.field-info-tip') ? tip : null), | |
| }; | |
| expect(isUiTouchTarget(tip)).toBe(true); | |
| }); | |
| it('treats boot/Galaxy progress overlay as UI', () => { | |
| const overlay = { | |
| id: 'boot-progress', | |
| tagName: 'DIV', | |
| closest: (sel) => (sel.includes('#boot-progress') ? overlay : null), | |
| }; | |
| expect(isUiTouchTarget(overlay)).toBe(true); | |
| }); | |
| it('allows bare canvas', () => { | |
| const canvas = { tagName: 'CANVAS', closest: () => null }; | |
| expect(isUiTouchTarget(canvas)).toBe(false); | |
| }); | |
| }); | |
| describe('Navigation touch API', () => { | |
| let nav; | |
| let camera; | |
| beforeEach(() => { | |
| camera = new THREE.PerspectiveCamera(60, 1, 0.1, 2000); | |
| nav = new Navigation(camera, null); | |
| }); | |
| it('setMoveAxes and setVertical feed update without breaking desktop keys', () => { | |
| nav.setMoveAxes(0, -1); | |
| nav.setVertical(1); | |
| expect(nav.touchAxes.z).toBeCloseTo(-1); | |
| expect(nav.touchVertical).toBe(1); | |
| const before = camera.position.clone(); | |
| nav.update(0.016); | |
| expect(camera.position.distanceTo(before)).toBeGreaterThan(0); | |
| nav.setMoveAxes(0, 0); | |
| nav.setVertical(0); | |
| expect(nav.touchAxes.x).toBe(0); | |
| expect(nav.touchVertical).toBe(0); | |
| }); | |
| it('applyLookDelta updates euler pitch/yaw clamps', () => { | |
| nav.applyLookDelta(100, 0); | |
| expect(nav.euler.y).toBeLessThan(0); | |
| nav.applyLookDelta(0, 10000); | |
| expect(nav.euler.x).toBeGreaterThanOrEqual(-Math.PI / 2 + 0.05); | |
| expect(nav.euler.x).toBeLessThanOrEqual(Math.PI / 2 - 0.05); | |
| }); | |
| }); | |