Files
privacy.sexy/tests/e2e/modal-layout-shifts.cy.ts
undergroundwires 86fde6d7dc Fix button inconsistencies and macOS layout shifts
This commit fixes layout shifts experienced in macOS Safari when
hovering over top menu items. Instead of making text bold — which was
causing layout shifts — the hover effect now changes the text color.
This ensures a consistent UI across different browsers and platforms.

Additionally, this commit fixes the styling of the privacy button
located in the bottom right corner. Previously styled as an `<a>`
element, it is now correctly represented as a `<button>`.

Furthermore, the commit enhances HTML conformity and accessibility by
correctly using `<button>` and `<a>` tags instead of relying on click
interactions on `<span>` elements.

This commit introduces `FlatButton` Vue component and a new
`flat-button` mixin. These centralize button usage and link styles,
aligning the hover/touch reactions of buttons across the application,
thereby creating a more consistent user interface.
2023-12-29 17:26:40 +01:00

88 lines
3.0 KiB
TypeScript

import { formatAssertionMessage } from '@tests/shared/FormatAssertionMessage';
import { ViewportTestScenarios } from './support/scenarios/viewport-test-scenarios';
describe('Modal interaction and layout stability', () => {
ViewportTestScenarios.forEach(({ // some shifts are observed only on extra small or large screens
name, width, height,
}) => {
it(name, () => {
cy.viewport(width, height);
cy.visit('/');
let metricsBeforeModal: ViewportMetrics | undefined;
captureViewportMetrics((metrics) => {
metricsBeforeModal = metrics;
});
cy
.contains('button', 'Privacy')
.click();
cy
.get('.modal-content')
.should('be.visible');
captureViewportMetrics((metrics) => {
const metricsAfterModal = metrics;
expect(metricsBeforeModal).to.deep.equal(metricsAfterModal, formatAssertionMessage([
`Expected (initial metrics before modal): ${JSON.stringify(metricsBeforeModal)}`,
`Actual (metrics after modal is opened): ${JSON.stringify(metricsAfterModal)}`,
]));
});
});
});
});
interface ViewportMetrics {
readonly x: number;
readonly y: number;
/*
Excluding height and width from the metrics to ensure test accuracy.
Height and width measurements can lead to false negatives due to layout shifts caused by
delayed loading of fonts and icons.
*/
}
function captureViewportMetrics(callback: (metrics: ViewportMetrics) => void): void {
cy.window().then((win) => {
cy.get('body')
.then((body) => {
const position = getElementViewportMetrics(body[0], win);
cy.log(`Captured metrics: ${JSON.stringify(position)}`);
callback(position);
});
});
}
function getElementViewportMetrics(element: HTMLElement, win: Window): ViewportMetrics {
const elementXRelativeToViewport = getElementXRelativeToViewport(element, win);
const elementYRelativeToViewport = getElementYRelativeToViewport(element, win);
return {
x: elementXRelativeToViewport,
y: elementYRelativeToViewport,
};
}
function getElementYRelativeToViewport(element: HTMLElement, win: Window): number {
const relativeTop = element.getBoundingClientRect().top;
const { position, top } = win.getComputedStyle(element);
const topValue = position === 'static' ? 0 : parseInt(top, 10);
if (Number.isNaN(topValue)) {
throw new Error(`Could not calculate Y position value from 'top': ${top}`);
}
const viewportRelativeY = relativeTop - topValue + win.scrollY;
return viewportRelativeY;
}
function getElementXRelativeToViewport(element: HTMLElement, win: Window): number {
const relativeLeft = element.getBoundingClientRect().left;
const { position, left } = win.getComputedStyle(element);
const leftValue = position === 'static' ? 0 : parseInt(left, 10);
if (Number.isNaN(leftValue)) {
throw new Error(`Could not calculate X position value from 'left': ${left}`);
}
const viewportRelativeX = relativeLeft - leftValue + win.scrollX;
return viewportRelativeX;
}