Files
sky_phone/frontend/src/utils/gameView.test.ts
T
Leon.Schmidt 387c27cc7f ENH - refine camera capture controls
Move Photo and Video selection onto Sky UI, separate the bottom controls, and expose the camera movement lock with localized labels. Support smooth wheel zoom down to 0.5x inside the phone crop and extend browser lifecycle coverage and camera contracts.
2026-08-15 22:19:28 +02:00

152 lines
4.5 KiB
TypeScript

import { describe, expect, it, vi } from 'vitest'
import { createGameView, gameViewGeometry } from '@/utils/gameView'
describe('gameViewGeometry', () => {
it('center-crops a widescreen game view for 3:4 portrait output', () => {
const geometry = gameViewGeometry(1920, 1080, 540, 720)
expect(Array.from(geometry.textureCoordinates)).toEqual([
expect.closeTo(0.29, 2),
0,
expect.closeTo(0.71, 2),
0,
expect.closeTo(0.29, 2),
1,
expect.closeTo(0.71, 2),
1,
])
})
it('keeps the full game view for 16:9 landscape output', () => {
const geometry = gameViewGeometry(1920, 1080, 720, 405)
expect(Array.from(geometry.textureCoordinates)).toEqual([
0, 0, 1, 0, 0, 1, 1, 1,
])
})
it('keeps the full texture when both aspect ratios match', () => {
const geometry = gameViewGeometry(1600, 900, 800, 450)
expect(Array.from(geometry.textureCoordinates)).toEqual([
0, 0, 1, 0, 0, 1, 1, 1,
])
})
it('widens the phone crop at 0.5x while higher zoom levels crop around the center', () => {
const wideGeometry = gameViewGeometry(1920, 1080, 540, 720, 0.5)
expect(Array.from(wideGeometry.textureCoordinates)).toEqual([
expect.closeTo(0.08, 2),
0,
expect.closeTo(0.92, 2),
0,
expect.closeTo(0.08, 2),
1,
expect.closeTo(0.92, 2),
1,
])
expect(Array.from(wideGeometry.positions)).toEqual([
-1, -1, 1, -1, -1, 1, 1, 1,
])
const zoomedGeometry = gameViewGeometry(1920, 1080, 540, 720, 2)
expect(Array.from(zoomedGeometry.textureCoordinates)).toEqual([
expect.closeTo(0.39, 2),
0.25,
expect.closeTo(0.61, 2),
0.25,
expect.closeTo(0.39, 2),
0.75,
expect.closeTo(0.61, 2),
0.75,
])
})
})
describe('createGameView', () => {
it('recreates graphics resources and resumes after context restoration', () => {
const gl = {
ARRAY_BUFFER: 1,
CLAMP_TO_EDGE: 2,
COLOR_BUFFER_BIT: 4,
COMPILE_STATUS: 5,
DYNAMIC_DRAW: 6,
FLOAT: 7,
FRAGMENT_SHADER: 8,
LINK_STATUS: 9,
MIRRORED_REPEAT: 10,
NEAREST: 11,
REPEAT: 12,
RGBA: 13,
STATIC_DRAW: 14,
TEXTURE_2D: 15,
TEXTURE_MAG_FILTER: 16,
TEXTURE_MIN_FILTER: 17,
TEXTURE_WRAP_S: 18,
TEXTURE_WRAP_T: 19,
TRIANGLE_STRIP: 20,
UNSIGNED_BYTE: 21,
VERTEX_SHADER: 22,
attachShader: vi.fn(),
bindBuffer: vi.fn(),
bindTexture: vi.fn(),
bufferData: vi.fn(),
clear: vi.fn(),
clearColor: vi.fn(),
compileShader: vi.fn(),
createBuffer: vi.fn(() => ({})),
createProgram: vi.fn(() => ({})),
createShader: vi.fn(() => ({})),
createTexture: vi.fn(() => ({})),
deleteBuffer: vi.fn(),
deleteProgram: vi.fn(),
deleteShader: vi.fn(),
deleteTexture: vi.fn(),
drawArrays: vi.fn(),
enableVertexAttribArray: vi.fn(),
finish: vi.fn(),
getAttribLocation: vi.fn((_program, name: string) =>
name === 'a_position' ? 0 : 1,
),
getExtension: vi.fn(() => ({ loseContext: vi.fn() })),
getProgramInfoLog: vi.fn(() => ''),
getProgramParameter: vi.fn(() => true),
getShaderInfoLog: vi.fn(() => ''),
getShaderParameter: vi.fn(() => true),
getUniformLocation: vi.fn(() => ({})),
linkProgram: vi.fn(),
shaderSource: vi.fn(),
texImage2D: vi.fn(),
texParameterf: vi.fn(),
uniform1i: vi.fn(),
useProgram: vi.fn(),
vertexAttribPointer: vi.fn(),
viewport: vi.fn(),
}
const canvas = Object.assign(new EventTarget(), {
getContext: () => gl,
height: 0,
width: 0,
}) as unknown as HTMLCanvasElement
const restored = vi.fn()
vi.spyOn(console, 'error').mockImplementation(() => undefined)
vi.spyOn(console, 'info').mockImplementation(() => undefined)
const view = createGameView(canvas, { onContextRestored: restored })
view.resize(540, 720, 1920, 1080, 2)
const lost = new Event('webglcontextlost', { cancelable: true })
canvas.dispatchEvent(lost)
expect(lost.defaultPrevented).toBe(true)
expect(view.isLost()).toBe(true)
canvas.dispatchEvent(new Event('webglcontextrestored'))
expect(view.isLost()).toBe(false)
expect(restored).toHaveBeenCalledOnce()
expect(gl.createProgram).toHaveBeenCalledTimes(2)
expect(canvas.width).toBe(540)
expect(canvas.height).toBe(720)
view.render()
expect(gl.drawArrays).toHaveBeenCalledOnce()
view.dispose()
})
})