feat: add verification layout themes documentation and new components
- Created `verification-layout-themes.md` to document layout and theme verification results. - Introduced `AlertCard` component for displaying alerts with appropriate icons and metadata. - Added `RouteCloud` component to manage and display route information dynamically. - Implemented tests for QR code generation and decoding to ensure functionality across multiple city and platform links.
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { colorScheme, colorSchemes, departurePage, numericFrame, stopDirection } from '../src/lib/presentation.mjs';
|
||||
import { colorScheme, colorSchemes, lightColorSchemes, departurePage, durationParts, numericFrame, stopDirection } from '../src/lib/presentation.mjs';
|
||||
|
||||
test('all accent schemes remain legible against black and with black route numbers', () => {
|
||||
for (const scheme of colorSchemes) {
|
||||
@@ -11,6 +11,23 @@ test('all accent schemes remain legible against black and with black route numbe
|
||||
}
|
||||
assert.equal(colorScheme('unknown').id, 'lime');
|
||||
});
|
||||
test('light accents have at least 7:1 contrast against white and retain a separate default', () => {
|
||||
for (const scheme of lightColorSchemes) {
|
||||
const rgb = scheme.color.slice(1).match(/../g).map(value => parseInt(value, 16) / 255);
|
||||
const channels = rgb.map(value => value <= .04045 ? value / 12.92 : ((value + .055) / 1.055) ** 2.4);
|
||||
const luminance = .2126 * channels[0] + .7152 * channels[1] + .0722 * channels[2];
|
||||
assert.ok(1.05 / (luminance + .05) >= 7, scheme.id);
|
||||
}
|
||||
assert.equal(colorScheme('lime', 'light').id, 'blue');
|
||||
assert.equal(colorScheme('cyan').id, 'cyan');
|
||||
});
|
||||
test('waiting time crosses hour boundaries without displaying hundreds of minutes', () => {
|
||||
assert.deepEqual(durationParts(59), [{ value: 59, unit: 'мин' }]);
|
||||
assert.deepEqual(durationParts(60), [{ value: 1, unit: 'ч' }]);
|
||||
assert.deepEqual(durationParts(121), [{ value: 2, unit: 'ч' }, { value: 1, unit: 'мин' }]);
|
||||
assert.deepEqual(durationParts(255), [{ value: 4, unit: 'ч' }, { value: 15, unit: 'мин' }]);
|
||||
assert.deepEqual(durationParts(302), [{ value: 5, unit: 'ч' }, { value: 2, unit: 'мин' }]);
|
||||
});
|
||||
test('clock carry rolls changed positions while keeping separators and unchanged digits still', () => {
|
||||
const frame = numericFrame('09:59', '10:00', 959, 1000);
|
||||
assert.equal(frame.direction, 'up');
|
||||
|
||||
23
test/qr.test.mjs
Normal file
23
test/qr.test.mjs
Normal file
@@ -0,0 +1,23 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import jsQR from 'jsqr';
|
||||
import { createPassengerQr, passengerLink } from '../src/lib/passenger.mjs';
|
||||
|
||||
test('rounded QR modules and finders remain decodable for multiple city and platform links', () => {
|
||||
for (const [cityId, stopId] of [[1, 1], [1, 107], [2, 123456]]) {
|
||||
const url = passengerLink('https://pwa.ridebus.by/', cityId, stopId), qr = createPassengerQr(url);
|
||||
const scale = 6, width = qr.size * scale, pixels = new Uint8ClampedArray(width * width * 4).fill(255);
|
||||
for (const rect of qr.rectangles) {
|
||||
for (let y = rect.y * scale; y < (rect.y + rect.height) * scale; y++) for (let x = rect.x * scale; x < (rect.x + rect.width) * scale; x++) {
|
||||
const px = (x + .5) / scale, py = (y + .5) / scale;
|
||||
const cx = Math.max(rect.x + rect.rx, Math.min(rect.x + rect.width - rect.rx, px));
|
||||
const cy = Math.max(rect.y + rect.rx, Math.min(rect.y + rect.height - rect.rx, py));
|
||||
if ((px - cx) ** 2 + (py - cy) ** 2 > rect.rx ** 2) continue;
|
||||
const index = (y * width + x) * 4, value = rect.fill === '#ffffff' ? 255 : 16;
|
||||
pixels[index] = pixels[index + 1] = pixels[index + 2] = value;
|
||||
}
|
||||
}
|
||||
assert.equal(jsQR(pixels, width, width)?.data, url);
|
||||
assert.ok(qr.rectangles.every(rect => rect.x >= 4 && rect.y >= 4 && rect.x + rect.width <= qr.size - 4 && rect.y + rect.height <= qr.size - 4));
|
||||
}
|
||||
});
|
||||
@@ -1,6 +1,6 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { readSnapshot, writeSnapshot, readTimetable, writeTimetable, readWeather, writeWeather } from '../src/lib/storage.ts';
|
||||
import { readSnapshot, writeSnapshot, readTimetable, writeTimetable, readWeather, writeWeather, readSettings, saveSettings } from '../src/lib/storage.ts';
|
||||
|
||||
const data = { cityId: 1, stop: { id: 1, name: 'Швейная фабрика' }, mode: 'live', serviceDate: '2026-10-08', updatedAt: '2026-10-08T12:00:00+03:00', routes: [], alerts: [],
|
||||
departures: [{ id: '1:12:30', scheduledAt: '2026-10-08T12:30:00+03:00' }] };
|
||||
@@ -30,3 +30,14 @@ test('full timetable and weather survive storage fallback and stay isolated by c
|
||||
assert.deepEqual(await readTimetable(1, 1), timetable); assert.deepEqual(await readWeather(1, 1), weather); assert.deepEqual(await readSnapshot(1, 1), data);
|
||||
assert.equal(await readTimetable(1, 2), null); assert.equal(await readWeather(2, 1), null);
|
||||
});
|
||||
test('old theme preferences migrate safely and each theme retains its own accent', () => {
|
||||
const values = new Map([['ridebus-board-settings-v1', JSON.stringify({ colorScheme: 'cyan' })]]);
|
||||
globalThis.localStorage = { getItem: key => values.get(key) || null, setItem: (key, value) => values.set(key, value) };
|
||||
const old = readSettings();
|
||||
assert.equal(old.theme, 'dark'); assert.equal(old.colorScheme, 'cyan'); assert.equal(old.lightColorScheme, 'blue');
|
||||
saveSettings({ ...old, theme: 'light', lightColorScheme: 'crimson' });
|
||||
const saved = readSettings();
|
||||
assert.equal(saved.theme, 'light'); assert.equal(saved.lightColorScheme, 'crimson'); assert.equal(saved.colorScheme, 'cyan');
|
||||
values.set('ridebus-board-settings-v1', JSON.stringify({ theme: 'light', lightColorScheme: 'lime' }));
|
||||
assert.equal(readSettings().lightColorScheme, 'blue');
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user