Files
vending-machine-game/tests/content.test.js
T
HamedandClaude Opus 5.5 d9ae8a45da feat: add landing-style start screen with four start modes
Phase 3 of turning the game into the company landing page.

- Replace the Iran-map briefing with a brand start screen over the
  orbiting 3D factory: logo, tagline and four ways in.
- Real world: safety notice, then GPS; if location is denied or
  missing, a friendly dialog offers offline play instead.
- Offline: start at Park Mellat beside the family car with the route
  to the factory, where the first purchase mission is.
- About Us: start in the factory yard. Products: start at the machine
  samples with the showroom open.
- Contact, address with map links, FAQ, careers, gallery and social
  links open in a sheet (bottom sheet on phones) from company config.
- Restart replays the chosen mode. Layout scrolls on short screens and
  respects safe areas.
- Add tests for the start screen, map links and theme.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 08:39:22 +03:30

75 lines
2.9 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import test from 'node:test';
import assert from 'node:assert/strict';
import { readFileSync, readdirSync, statSync } from 'node:fs';
import { join, relative } from 'node:path';
import { COMPANY } from '../src/config/company.js';
import { strings } from '../src/i18n/fa.js';
const SRC = new URL('../src/', import.meta.url).pathname;
// Persian text may only live in the strings file, the company config, map data and the list of
// machine sites (place names and addresses are map content, not interface text).
const ALLOWED = ['i18n/', 'config/', 'data/', 'constants/locations.js'];
const PERSIAN = /[؀-ۿ]/;
// Persian digits used for number formatting, not text.
const DIGITS_ONLY = /^[۰-۹]$/;
/**
* Lists source files below a folder.
* @param {string} dir - Folder to walk.
* @returns {string[]} File paths.
*/
function sourceFiles(dir) {
return readdirSync(dir).flatMap((name) => {
const path = join(dir, name);
if (statSync(path).isDirectory()) return sourceFiles(path);
return /\.(js|jsx)$/.test(name) && !/\.test\./.test(name) ? [path] : [];
});
}
/**
* WCAG contrast ratio between two #rrggbb colours.
* @param {string} a - First colour.
* @param {string} b - Second colour.
* @returns {number} Ratio from 1 to 21.
*/
function contrast(a, b) {
const luminance = (hex) => {
const [r, g, bl] = [1, 3, 5]
.map((i) => parseInt(hex.slice(i, i + 2), 16) / 255)
.map((c) => (c <= 0.03928 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4));
return 0.2126 * r + 0.7152 * g + 0.0722 * bl;
};
const [high, low] = [luminance(a), luminance(b)].sort((x, y) => y - x);
return (high + 0.05) / (low + 0.05);
}
test('Interface text lives in the strings file, not in components or game code', () => {
const offenders = sourceFiles(SRC)
.map((path) => relative(SRC, path))
.filter((path) => !ALLOWED.some((allowed) => path.startsWith(allowed)))
.flatMap((path) =>
readFileSync(join(SRC, path), 'utf8')
.split('\n')
.map((line, i) => [line, i + 1])
.filter(([line]) => [...line].some((ch) => PERSIAN.test(ch) && !DIGITS_ONLY.test(ch)))
.map(([, n]) => `${path}:${n}`),
);
assert.deepEqual(offenders, []);
});
test('Brand palette keeps readable contrast', () => {
const c = COMPANY.colors;
assert.equal(c.primary, '#00a693');
assert.ok(contrast(c.primary, c.surface) >= 4.5, 'primary accents on dark surfaces');
assert.ok(contrast(c.onPrimary, c.primary) >= 4.5, 'text on primary buttons');
assert.ok(contrast(c.text, c.surface) >= 7, 'body text');
assert.ok(contrast(c.textMuted, c.surface) >= 4.5, 'secondary text');
});
test('Strings that take values build complete sentences', () => {
assert.equal(strings.prompts.getIn('بی‌ام‌و'), 'F · سوار بی‌ام‌و شو');
assert.equal(strings.units.hetoman('۵۰۰'), '۵۰۰ هتومان');
assert.ok(strings.landing.logoAlt.includes(COMPANY.shortName));
assert.equal(strings.ranks.length, 6);
});