feat(effects): polish 8 effects and add 7 new engines with parallax
Build, Test & Push Frontend / quality-check (push) Successful in 32s
Build, Test & Push Frontend / docker (push) Successful in 26s

Synthwave, fireflies, snow, sakura, nebula: CSS/SVG. Matrix and
constellation: canvas. Existing engines gain depth, color, glow, and
parallax (mouse and device tilt, opt-in on iOS)
This commit is contained in:
lobo committed 2026-10-03 17:31:19 +02:00
1 parent d7810fbf94
commit ed5df8c40d
21 files changed
+1510 -211

No files matched your search

+48 -4
View File
@@ -1,6 +1,6 @@
// @vitest-environment jsdom
import {afterEach, describe, expect, it, vi} from 'vitest';
import {cleanup, render, screen} from '@testing-library/react';
import {cleanup, fireEvent, render, screen, waitFor} from '@testing-library/react';
import AppearanceCard from './AppearanceCard.jsx';
const preferences = vi.hoisted(() => ({value: null}));
@@ -8,15 +8,19 @@ vi.mock('react-i18next', () => import('../test/i18n-mock.js'));
vi.mock('../context/PreferencesContext.jsx', () => ({usePreferences: () => preferences.value}));
vi.mock('../hooks/useSaveAppearance.js', () => ({default: () => vi.fn()}));
const show = (equipped) => {
const show = (equipped, reduceMotion = false) => {
preferences.value = {
appearance: {accent: 'violet', corners: 'default', wideLayout: false, reduceMotion: false, showBanner: true},
appearance: {accent: 'violet', corners: 'default', wideLayout: false, reduceMotion, showBanner: true},
equipped,
};
render(<AppearanceCard/>);
};
afterEach(cleanup);
afterEach(() => {
cleanup();
localStorage.clear();
delete window.DeviceOrientationEvent;
});
describe('AppearanceCard', () => {
it('tells that the skin sets colors and corners only while a skin is worn', () => {
@@ -28,4 +32,44 @@ describe('AppearanceCard', () => {
show({});
expect(screen.queryByText('SETTINGS.SKIN_OVERRIDES')).toBeNull();
});
describe('tilt effects', () => {
const stubPermission = (answer) => {
window.DeviceOrientationEvent = {requestPermission: vi.fn().mockResolvedValue(answer)};
return window.DeviceOrientationEvent.requestPermission;
};
it('has no tilt switch where the browser needs no permission', () => {
show({});
expect(document.querySelector('#tilt')).toBeNull();
});
it('asks for permission on the tap, then shows the switch on and stores it', async () => {
const request = stubPermission('granted');
show({});
fireEvent.click(document.querySelector('#tilt'));
expect(request).toHaveBeenCalledTimes(1);
await waitFor(() => expect(document.querySelector('#tilt').getAttribute('aria-checked')).toBe('true'));
expect(localStorage.getItem('tbp_tilt')).toBe('true');
});
it('stays off and tells why when the permission is refused', async () => {
stubPermission('denied');
show({});
fireEvent.click(document.querySelector('#tilt'));
await waitFor(() => expect(screen.getByText('SETTINGS.TILT_DENIED')).toBeTruthy());
expect(document.querySelector('#tilt').getAttribute('aria-checked')).toBe('false');
});
it('is disabled while animations are reduced', () => {
stubPermission('granted');
show({}, true);
expect(document.querySelector('#tilt').disabled).toBe(true);
});
});
});