156 lines
5.8 KiB
JavaScript
156 lines
5.8 KiB
JavaScript
import { readFileSync } from 'node:fs';
|
|
import { createServer } from 'vite';
|
|
|
|
const server = await createServer({
|
|
logLevel: 'error',
|
|
server: { middlewareMode: true, hmr: false },
|
|
appType: 'custom'
|
|
});
|
|
|
|
function assert(condition, message) {
|
|
if (!condition) {
|
|
throw new Error(message);
|
|
}
|
|
}
|
|
|
|
function createStorage() {
|
|
const values = new Map();
|
|
return {
|
|
values,
|
|
storage: {
|
|
getItem: (key) => values.get(key) ?? null,
|
|
setItem: (key, value) => values.set(key, value)
|
|
}
|
|
};
|
|
}
|
|
|
|
const fullMotionPreference = () => ({ matches: false });
|
|
const reducedMotionPreference = () => ({ matches: true });
|
|
|
|
try {
|
|
const motion = await server.ssrLoadModule('/src/game/settings/visualMotion.ts');
|
|
|
|
assert(
|
|
motion.preferredVisualMotionMode(fullMotionPreference) === 'full',
|
|
'The OS default must use full motion when reduced motion is not requested.'
|
|
);
|
|
assert(
|
|
motion.preferredVisualMotionMode(reducedMotionPreference) === 'reduced',
|
|
'The OS reduced-motion preference must become the default mode.'
|
|
);
|
|
assert(
|
|
motion.preferredVisualMotionMode(() => { throw new Error('unavailable'); }) === 'full',
|
|
'An unavailable media query must safely fall back to full motion.'
|
|
);
|
|
|
|
const { values, storage } = createStorage();
|
|
assert(
|
|
motion.loadVisualMotionMode(storage, fullMotionPreference) === 'full',
|
|
'A fresh full-motion environment must load full mode.'
|
|
);
|
|
assert(
|
|
motion.loadVisualMotionMode(storage, reducedMotionPreference) === 'reduced',
|
|
'A fresh reduced-motion environment must load reduced mode.'
|
|
);
|
|
|
|
for (const mode of motion.visualMotionModes) {
|
|
motion.saveVisualMotionMode(mode, storage);
|
|
assert(
|
|
motion.loadVisualMotionMode(storage, reducedMotionPreference) === mode,
|
|
`Stored mode ${mode} must override the OS default.`
|
|
);
|
|
}
|
|
|
|
values.set(motion.visualMotionStorageKey, 'invalid');
|
|
assert(
|
|
motion.loadVisualMotionMode(storage, fullMotionPreference) === 'full' &&
|
|
motion.loadVisualMotionMode(storage, reducedMotionPreference) === 'reduced',
|
|
'Invalid stored values must normalize to the current OS default.'
|
|
);
|
|
|
|
assert(
|
|
motion.normalizeVisualMotionMode('full', 'reduced') === 'full' &&
|
|
motion.normalizeVisualMotionMode('reduced', 'full') === 'reduced' &&
|
|
motion.normalizeVisualMotionMode('invalid', 'reduced') === 'reduced',
|
|
'Visual motion normalization must preserve valid values and replace invalid values.'
|
|
);
|
|
assert(
|
|
motion.nextVisualMotionMode('full') === 'reduced' &&
|
|
motion.nextVisualMotionMode('reduced') === 'full',
|
|
'The two visual motion modes must cycle in display order.'
|
|
);
|
|
assert(
|
|
motion.visualMotionModeLabel('full') === '보통' &&
|
|
motion.visualMotionModeLabel('reduced') === '줄임',
|
|
'Visual motion labels must match the title settings copy.'
|
|
);
|
|
|
|
motion.saveVisualMotionMode('reduced', storage);
|
|
assert(
|
|
motion.isVisualMotionReduced(storage, fullMotionPreference),
|
|
'The shared reduced-motion predicate must honor the stored reduced mode.'
|
|
);
|
|
motion.saveVisualMotionMode('full', storage);
|
|
assert(
|
|
!motion.isVisualMotionReduced(storage, reducedMotionPreference),
|
|
'The shared reduced-motion predicate must let a stored full mode override the OS preference.'
|
|
);
|
|
|
|
const throwingStorage = {
|
|
getItem: () => { throw new Error('blocked'); },
|
|
setItem: () => { throw new Error('blocked'); }
|
|
};
|
|
assert(
|
|
motion.loadVisualMotionMode(throwingStorage, reducedMotionPreference) === 'reduced',
|
|
'Storage read failures must fall back to the OS preference.'
|
|
);
|
|
motion.saveVisualMotionMode('full', throwingStorage);
|
|
|
|
const titleSource = readFileSync('src/game/scenes/TitleScene.ts', 'utf8');
|
|
assert(
|
|
titleSource.includes("from '../settings/visualMotion'") &&
|
|
titleSource.includes('화면 움직임:') &&
|
|
titleSource.includes('nextVisualMotionMode(loadVisualMotionMode())') &&
|
|
titleSource.includes('ui(468)'),
|
|
'The title settings panel must expose the persisted motion toggle in its expanded layout.'
|
|
);
|
|
|
|
const battleSource = readFileSync('src/game/scenes/BattleScene.ts', 'utf8');
|
|
assert(
|
|
battleSource.includes("import { isVisualMotionReduced } from '../settings/visualMotion';"),
|
|
'BattleScene must use the shared visual motion preference.'
|
|
);
|
|
assert(
|
|
!battleSource.includes("matchMedia('(prefers-reduced-motion: reduce)')"),
|
|
'BattleScene must not keep direct reduced-motion media queries.'
|
|
);
|
|
assert(
|
|
battleSource.includes('if (!reducedMotion && profile.haze)') &&
|
|
battleSource.includes('if (!reducedMotion && profile.particles)'),
|
|
'Reduced mode must disable moving battle haze and particles without suppressing tint creation.'
|
|
);
|
|
assert(
|
|
(battleSource.match(/this\.shakeCamera\(/g) ?? []).length === 3 &&
|
|
(battleSource.match(/this\.cameras\.main\.shake\(/g) ?? []).length === 1 &&
|
|
battleSource.includes('if (isVisualMotionReduced())'),
|
|
'Every battle camera shake must pass through the shared reduced-motion guard.'
|
|
);
|
|
|
|
const storySource = readFileSync('src/game/scenes/StoryScene.ts', 'utf8');
|
|
assert(
|
|
storySource.includes("import { isVisualMotionReduced } from '../settings/visualMotion';") &&
|
|
storySource.includes('this.visualMotionReduced = isVisualMotionReduced();'),
|
|
'StoryScene must load the shared visual motion preference when the scene starts.'
|
|
);
|
|
assert(
|
|
storySource.includes('const particleCount = this.visualMotionReduced ? 0') &&
|
|
storySource.includes('? Math.min(1, profile.haze.count)') &&
|
|
storySource.includes('if (this.visualMotionReduced) {\n return;\n }'),
|
|
'Reduced story motion must stop background panning and particles while retaining one static haze layer.'
|
|
);
|
|
|
|
console.info('Verified visual motion defaults, persistence, title controls, and story/battle motion guards.');
|
|
} finally {
|
|
await server.close();
|
|
}
|