mirror of
https://github.com/sky-systems/sky_phone.git
synced 2026-08-29 01:08:59 +00:00
dca0868753
* FIX - stabilize homescreen dragging across scaling Render the home drag preview in an unzoomed viewport portal and normalize FiveM CEF DOM geometry before hit testing and drop settling. Cover Chrome 103, fractional scaling, 4K, ultrawide, and non-16:9 viewport behavior. * FIX - Implement LB guest app layout and lifecycle bridge Ensure custom LB apps are properly sized and visible by applying the required CSS contract on initialization and upon receiving the 'componentsLoaded' signal. * FIX - match uppercase script tags in LB bridge test
175 lines
5.5 KiB
TypeScript
175 lines
5.5 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
|
|
import {
|
|
normalizePhoneViewportRect,
|
|
readPhoneViewportGeometry,
|
|
type PhoneViewportRect,
|
|
} from '@/utils/phoneViewportGeometry'
|
|
|
|
type RectMeasurement = Pick<
|
|
DOMRectReadOnly,
|
|
'height' | 'left' | 'top' | 'width'
|
|
>
|
|
|
|
function measuredRect(
|
|
left: number,
|
|
top: number,
|
|
width: number,
|
|
height: number,
|
|
): RectMeasurement {
|
|
return { height, left, top, width }
|
|
}
|
|
|
|
function expectRectClose(
|
|
actual: PhoneViewportRect,
|
|
expected: PhoneViewportRect,
|
|
): void {
|
|
for (const key of [
|
|
'bottom',
|
|
'height',
|
|
'left',
|
|
'right',
|
|
'top',
|
|
'width',
|
|
] as const) {
|
|
expect(actual[key]).toBeCloseTo(expected[key], 6)
|
|
}
|
|
}
|
|
|
|
function createGeometryFixture(options: {
|
|
canvasOffsetHeight: number
|
|
canvasOffsetWidth: number
|
|
rawCanvasRect: RectMeasurement
|
|
wrapperRect: RectMeasurement
|
|
}): { anchor: Element; element: (rect: RectMeasurement) => Element } {
|
|
const wrapper = {
|
|
getBoundingClientRect: () => options.wrapperRect,
|
|
} as unknown as HTMLElement
|
|
const canvas = {
|
|
closest: (selector: string) =>
|
|
selector === '.phone-resolution-wrapper' ? wrapper : null,
|
|
getBoundingClientRect: () => options.rawCanvasRect,
|
|
offsetHeight: options.canvasOffsetHeight,
|
|
offsetWidth: options.canvasOffsetWidth,
|
|
} as unknown as HTMLElement
|
|
|
|
return {
|
|
anchor: {
|
|
closest: (selector: string) =>
|
|
selector === '.phone-resolution-canvas' ? canvas : null,
|
|
} as unknown as Element,
|
|
element: (rect) =>
|
|
({ getBoundingClientRect: () => rect }) as unknown as Element,
|
|
}
|
|
}
|
|
|
|
describe('phone viewport geometry', () => {
|
|
it('leaves modern Chrome measurements unchanged when the canvas BCR is already rendered', () => {
|
|
const wrapper = measuredRect(1573.09, 126.25, 322.92, 698.832)
|
|
const layer = measuredRect(1589.783336, 177.75, 289.533328, 603.2)
|
|
|
|
expectRectClose(normalizePhoneViewportRect(layer, wrapper, wrapper), {
|
|
bottom: layer.top + layer.height,
|
|
height: layer.height,
|
|
left: layer.left,
|
|
right: layer.left + layer.width,
|
|
top: layer.top,
|
|
width: layer.width,
|
|
})
|
|
})
|
|
|
|
it('calibrates live CEF 103 BCRs back inside the visible wrapper', () => {
|
|
const wrapper = measuredRect(1573.09, 126.25, 322.92, 698.832)
|
|
const rawCanvas = measuredRect(1899.87, 152.5, 389.98, 844)
|
|
const rawLayer = measuredRect(1920.03, 214.25, 349.66, 728.5)
|
|
const corrected = normalizePhoneViewportRect(rawLayer, rawCanvas, wrapper)
|
|
|
|
expect(corrected.left).toBeCloseTo(1589.7833360685163, 6)
|
|
expect(corrected.width).toBeCloseTo(289.5333278629674, 6)
|
|
expect(corrected.right).toBeCloseTo(1879.3166639314836, 6)
|
|
expect(corrected.left).toBeGreaterThan(wrapper.left)
|
|
expect(corrected.right).toBeLessThan(wrapper.left + wrapper.width)
|
|
})
|
|
|
|
it.each([
|
|
['80%', 0.6624],
|
|
['100%', 0.828],
|
|
['120%', 0.9936],
|
|
])(
|
|
'normalizes fractional positions, sizes, and deltas at %s scaling',
|
|
(_label, zoom) => {
|
|
const rawCanvas = measuredRect(1900.125, 212.75, 390, 844)
|
|
const wrapper = measuredRect(1530.5, 250.25, 390 * zoom, 844 * zoom)
|
|
const rawStart = measuredRect(
|
|
rawCanvas.left + 20.125,
|
|
rawCanvas.top + 50.375,
|
|
349.75,
|
|
73.125,
|
|
)
|
|
const rawEnd = measuredRect(
|
|
rawStart.left + 73.25,
|
|
rawStart.top - 41.75,
|
|
rawStart.width,
|
|
rawStart.height,
|
|
)
|
|
const start = normalizePhoneViewportRect(rawStart, rawCanvas, wrapper)
|
|
const end = normalizePhoneViewportRect(rawEnd, rawCanvas, wrapper)
|
|
|
|
expect(start.left).toBeCloseTo(wrapper.left + 20.125 * zoom, 6)
|
|
expect(start.top).toBeCloseTo(wrapper.top + 50.375 * zoom, 6)
|
|
expect(start.width).toBeCloseTo(349.75 * zoom, 6)
|
|
expect(start.height).toBeCloseTo(73.125 * zoom, 6)
|
|
expect(end.left - start.left).toBeCloseTo(73.25 * zoom, 6)
|
|
expect(end.top - start.top).toBeCloseTo(-41.75 * zoom, 6)
|
|
},
|
|
)
|
|
|
|
it('reads visual scale and normalized element rects from a canvas anchor', () => {
|
|
const fixture = createGeometryFixture({
|
|
canvasOffsetHeight: 844,
|
|
canvasOffsetWidth: 390,
|
|
rawCanvasRect: measuredRect(1899.87, 152.5, 389.98, 844),
|
|
wrapperRect: measuredRect(1573.09, 126.25, 322.92, 698.832),
|
|
})
|
|
const geometry = readPhoneViewportGeometry(fixture.anchor)
|
|
const layer = fixture.element(measuredRect(1920.03, 214.25, 349.66, 728.5))
|
|
|
|
expect(geometry).not.toBeNull()
|
|
expect(geometry?.scaleX).toBeCloseTo(0.828, 6)
|
|
expect(geometry?.scaleY).toBeCloseTo(0.828, 6)
|
|
expect(geometry?.rect(layer).left).toBeCloseTo(1589.7833360685163, 6)
|
|
})
|
|
|
|
it('uses finite identity fallbacks for zero geometry and missing anchors', () => {
|
|
const rawCanvas = measuredRect(100, 50, 0, 0)
|
|
const corrected = normalizePhoneViewportRect(
|
|
measuredRect(112.5, 58.25, 40, 20),
|
|
rawCanvas,
|
|
measuredRect(500, 300, 0, 0),
|
|
)
|
|
const fixture = createGeometryFixture({
|
|
canvasOffsetHeight: 0,
|
|
canvasOffsetWidth: 0,
|
|
rawCanvasRect: rawCanvas,
|
|
wrapperRect: measuredRect(500, 300, 0, 0),
|
|
})
|
|
const geometry = readPhoneViewportGeometry(fixture.anchor)
|
|
|
|
expect(corrected).toEqual({
|
|
bottom: 328.25,
|
|
height: 20,
|
|
left: 512.5,
|
|
right: 552.5,
|
|
top: 308.25,
|
|
width: 40,
|
|
})
|
|
expect(Object.values(corrected).every(Number.isFinite)).toBe(true)
|
|
expect(geometry?.scaleX).toBe(1)
|
|
expect(geometry?.scaleY).toBe(1)
|
|
expect(readPhoneViewportGeometry(null)).toBeNull()
|
|
expect(
|
|
readPhoneViewportGeometry({ closest: () => null } as unknown as Element),
|
|
).toBeNull()
|
|
})
|
|
})
|