mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-11-13 07:40:59 +00:00
style: format the six files the formatting check names
They are outside scripts/format-baseline.json, so new code there must be formatted; Prettier only, no change in behaviour.
This commit is contained in:
@@ -103,7 +103,10 @@ describe('localizedParams', () => {
|
||||
});
|
||||
|
||||
it('FE-NOTIF-MODEL-009: anything that is not a bare date passes through, and no params stay none', () => {
|
||||
expect(localizedParams({ trip: '2026-11-05 at noon', n: '3' }, 'de', NOW)).toEqual({ trip: '2026-11-05 at noon', n: '3' });
|
||||
expect(localizedParams({ trip: '2026-11-05 at noon', n: '3' }, 'de', NOW)).toEqual({
|
||||
trip: '2026-11-05 at noon',
|
||||
n: '3',
|
||||
});
|
||||
expect(localizedParams(undefined, 'de', NOW)).toEqual({});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -137,12 +137,19 @@ function FeedCard({ entry, color, active, onTap, showMood, showWeather, t }: Fee
|
||||
) : (
|
||||
<MapPin size={11} strokeWidth={2.2} className="flex-none" />
|
||||
)}
|
||||
<span dir="auto" className="truncate font-geist text-[length:calc(11px*var(--fs-scale-caption,1))] font-semibold">
|
||||
<span
|
||||
dir="auto"
|
||||
className="truncate font-geist text-[length:calc(11px*var(--fs-scale-caption,1))] font-semibold"
|
||||
>
|
||||
{place}
|
||||
</span>
|
||||
</span>
|
||||
)}
|
||||
{story && <span dir="auto" className="line-clamp-3 text-caption leading-[1.45] text-m-ink opacity-80">{story}</span>}
|
||||
{story && (
|
||||
<span dir="auto" className="line-clamp-3 text-caption leading-[1.45] text-m-ink opacity-80">
|
||||
{story}
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
</button>
|
||||
);
|
||||
|
||||
@@ -44,7 +44,10 @@ export function CardTitleButton({
|
||||
onClick={onOpen}
|
||||
className="flex min-w-0 flex-1 flex-wrap items-center gap-x-[7px] gap-y-1 text-start"
|
||||
>
|
||||
<span dir="auto" className="line-clamp-2 min-w-0 flex-[1_1_auto] break-words text-[0.78125rem] font-bold text-m-ink wide:basis-full">
|
||||
<span
|
||||
dir="auto"
|
||||
className="line-clamp-2 min-w-0 flex-[1_1_auto] break-words text-[0.78125rem] font-bold text-m-ink wide:basis-full"
|
||||
>
|
||||
{res.title}
|
||||
</span>
|
||||
<span className="inline-flex flex-none items-center gap-1 rounded-full border border-[color:var(--m-rowbr)] bg-m-card px-2 py-[2px] font-geist text-[0.5625rem] font-bold uppercase tracking-[.06em] text-m-muted">
|
||||
@@ -116,7 +119,9 @@ export function CardWhenAndCode({
|
||||
<Field label={t('reservations.date')} className="flex-[1.4]">
|
||||
{dayDate ? (
|
||||
<span className="flex min-w-0 items-center justify-center gap-1.5">
|
||||
<span dir="auto" className="min-w-0 truncate">{dayValue}</span>
|
||||
<span dir="auto" className="min-w-0 truncate">
|
||||
{dayValue}
|
||||
</span>
|
||||
<span className="flex-none font-medium text-m-faint">{dayDate}</span>
|
||||
</span>
|
||||
) : (
|
||||
@@ -178,7 +183,9 @@ export function CardFiles({ files, card, t }: { files: ReturnType<typeof filesFo
|
||||
className="flex items-center gap-[6px] rounded-[10px] border border-[color:var(--m-rowbr)] bg-m-card px-[10px] py-[7px]"
|
||||
>
|
||||
<FileText size={12} strokeWidth={2} className="flex-none text-m-muted" />
|
||||
<span dir="auto" className="truncate font-geist text-[0.65625rem] font-semibold text-m-muted">{f.original_name}</span>
|
||||
<span dir="auto" className="truncate font-geist text-[0.65625rem] font-semibold text-m-muted">
|
||||
{f.original_name}
|
||||
</span>
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -15,9 +15,9 @@
|
||||
* Zero specificity, so an explicit text-center or text-end on the element wins.
|
||||
*/
|
||||
:where(
|
||||
:root[dir='rtl'] [dir='auto']:dir(ltr):is(.truncate, [class*='line-clamp-'], input, textarea),
|
||||
:root[dir='ltr'] [dir='auto']:dir(rtl):is(.truncate, [class*='line-clamp-'], input, textarea)
|
||||
) {
|
||||
:root[dir='rtl'] [dir='auto']:dir(ltr):is(.truncate, [class*='line-clamp-'], input, textarea),
|
||||
:root[dir='ltr'] [dir='auto']:dir(rtl):is(.truncate, [class*='line-clamp-'], input, textarea)
|
||||
) {
|
||||
text-align: end;
|
||||
}
|
||||
|
||||
@@ -32,7 +32,8 @@
|
||||
* own property, so it composes with a rotation the icon carries (an open
|
||||
* disclosure pointing down) instead of replacing it.
|
||||
*/
|
||||
:root[dir='rtl'] .m-root
|
||||
:root[dir='rtl']
|
||||
.m-root
|
||||
:is(
|
||||
.lucide-chevron-left,
|
||||
.lucide-chevron-right,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { readFileSync } from 'node:fs'
|
||||
import { resolve } from 'node:path'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { resolve } from 'node:path';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
// FE-RTL-CSS-001 to FE-RTL-CSS-004
|
||||
//
|
||||
@@ -8,43 +8,55 @@ import { describe, expect, it } from 'vitest'
|
||||
// text that lines up with the UI in its own direction, arrows that turn round
|
||||
// in the touch shell, and Arabic's caption labels. jsdom applies no
|
||||
// stylesheet, so these read the real file.
|
||||
const css = readFileSync(resolve(process.cwd(), 'src/styles/rtl.css'), 'utf8').replace(/\/\*[\s\S]*?\*\//g, '')
|
||||
const css = readFileSync(resolve(process.cwd(), 'src/styles/rtl.css'), 'utf8').replace(/\/\*[\s\S]*?\*\//g, '');
|
||||
|
||||
/** The declarations of the rule whose selector text contains every given part. */
|
||||
function ruleBody(...parts: string[]): string {
|
||||
for (const chunk of css.split('}')) {
|
||||
const open = chunk.indexOf('{')
|
||||
if (open < 0) continue
|
||||
const head = chunk.slice(0, open)
|
||||
if (parts.every(part => head.includes(part))) return chunk.slice(open + 1)
|
||||
const open = chunk.indexOf('{');
|
||||
if (open < 0) continue;
|
||||
const head = chunk.slice(0, open);
|
||||
if (parts.every((part) => head.includes(part))) return chunk.slice(open + 1);
|
||||
}
|
||||
return ''
|
||||
return '';
|
||||
}
|
||||
|
||||
describe('rtl.css', () => {
|
||||
it('FE-RTL-CSS-001: the stylesheet is loaded with the app', () => {
|
||||
const main = readFileSync(resolve(process.cwd(), 'src/main.tsx'), 'utf8')
|
||||
expect(main).toMatch(/import '\.\/styles\/rtl\.css'/)
|
||||
})
|
||||
const main = readFileSync(resolve(process.cwd(), 'src/main.tsx'), 'utf8');
|
||||
expect(main).toMatch(/import '\.\/styles\/rtl\.css'/);
|
||||
});
|
||||
|
||||
it('FE-RTL-CSS-002: cut, clamped or typed user text in its own direction sits on the UI start edge', () => {
|
||||
const body = ruleBody(":root[dir='rtl'] [dir='auto']:dir(ltr)", '.truncate', "[class*='line-clamp-']", 'input', 'textarea')
|
||||
expect(body).toMatch(/text-align:\s*end/)
|
||||
const body = ruleBody(
|
||||
":root[dir='rtl'] [dir='auto']:dir(ltr)",
|
||||
'.truncate',
|
||||
"[class*='line-clamp-']",
|
||||
'input',
|
||||
'textarea'
|
||||
);
|
||||
expect(body).toMatch(/text-align:\s*end/);
|
||||
// The left-to-right UI gets the mirror case: Arabic text in an English row.
|
||||
expect(css).toContain(":root[dir='ltr'] [dir='auto']:dir(rtl)")
|
||||
expect(css).toContain(":root[dir='ltr'] [dir='auto']:dir(rtl)");
|
||||
// Zero specificity: an explicit alignment class on the element still wins.
|
||||
expect(css).toMatch(/:where\(\s*:root\[dir='rtl'\]/)
|
||||
})
|
||||
expect(css).toMatch(/:where\(\s*:root\[dir='rtl'\]/);
|
||||
});
|
||||
|
||||
it('FE-RTL-CSS-003: in the touch shell every back, forward and flow arrow turns round in a right-to-left language', () => {
|
||||
const body = ruleBody(":root[dir='rtl'] .m-root", '.lucide-chevron-left', '.lucide-chevron-right', '.lucide-arrow-left', '.lucide-arrow-right')
|
||||
expect(body).toMatch(/scale:\s*-1 1/)
|
||||
const body = ruleBody(
|
||||
":root[dir='rtl'] .m-root",
|
||||
'.lucide-chevron-left',
|
||||
'.lucide-chevron-right',
|
||||
'.lucide-arrow-left',
|
||||
'.lucide-arrow-right'
|
||||
);
|
||||
expect(body).toMatch(/scale:\s*-1 1/);
|
||||
// An icon that names its own treatment keeps it.
|
||||
expect(css).toContain(":not([class*='rtl:'])")
|
||||
})
|
||||
expect(css).toContain(":not([class*='rtl:'])");
|
||||
});
|
||||
|
||||
it('FE-RTL-CSS-004: Arabic and Hebrew caption labels grow, drop the tracking and take the muted ink', () => {
|
||||
expect(ruleBody(':lang(ar)', ':lang(he)', '.m-root .uppercase')).toMatch(/zoom:\s*1\.2[\s\S]*letter-spacing:\s*0/)
|
||||
expect(ruleBody('.uppercase.text-m-faint')).toMatch(/color:\s*var\(--m-muted\)/)
|
||||
})
|
||||
})
|
||||
expect(ruleBody(':lang(ar)', ':lang(he)', '.m-root .uppercase')).toMatch(/zoom:\s*1\.2[\s\S]*letter-spacing:\s*0/);
|
||||
expect(ruleBody('.uppercase.text-m-faint')).toMatch(/color:\s*var\(--m-muted\)/);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { readFileSync } from 'node:fs'
|
||||
import { resolve } from 'node:path'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { resolve } from 'node:path';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { directionOf, pickLocale, translator } from '../../../native-shell/i18n'
|
||||
import { directionOf, pickLocale, translator } from '../../../native-shell/i18n';
|
||||
|
||||
// FE-NATIVE-SHELL-001 to FE-NATIVE-SHELL-006
|
||||
//
|
||||
@@ -12,83 +12,85 @@ import { directionOf, pickLocale, translator } from '../../../native-shell/i18n'
|
||||
|
||||
/** Every rule of a stylesheet as its selector list, its declarations and the media query around it, comments dropped. */
|
||||
function rules(css: string): { selectors: string[]; body: string; media: string }[] {
|
||||
const out: { selectors: string[]; body: string; media: string }[] = []
|
||||
const stack: string[] = []
|
||||
let buf = ''
|
||||
const out: { selectors: string[]; body: string; media: string }[] = [];
|
||||
const stack: string[] = [];
|
||||
let buf = '';
|
||||
for (const ch of css.replace(/\/\*[\s\S]*?\*\//g, '')) {
|
||||
if (ch === '{') {
|
||||
stack.push(buf.trim())
|
||||
buf = ''
|
||||
stack.push(buf.trim());
|
||||
buf = '';
|
||||
} else if (ch === '}') {
|
||||
const head = stack.pop() ?? ''
|
||||
const head = stack.pop() ?? '';
|
||||
if (!head.startsWith('@')) {
|
||||
const media = stack.filter(h => h.startsWith('@media')).join(' ')
|
||||
out.push({ selectors: head.split(',').map(sel => sel.trim()), body: buf, media })
|
||||
const media = stack.filter((h) => h.startsWith('@media')).join(' ');
|
||||
out.push({ selectors: head.split(',').map((sel) => sel.trim()), body: buf, media });
|
||||
}
|
||||
buf = ''
|
||||
buf = '';
|
||||
} else {
|
||||
buf += ch
|
||||
buf += ch;
|
||||
}
|
||||
}
|
||||
return out
|
||||
return out;
|
||||
}
|
||||
|
||||
const css = readFileSync(resolve(process.cwd(), 'native-shell/shell.css'), 'utf8')
|
||||
const all = rules(css)
|
||||
const css = readFileSync(resolve(process.cwd(), 'native-shell/shell.css'), 'utf8');
|
||||
const all = rules(css);
|
||||
const find = (selector: string, media?: RegExp) =>
|
||||
all.filter(r => r.selectors.includes(selector) && (!media || media.test(r.media)))
|
||||
all.filter((r) => r.selectors.includes(selector) && (!media || media.test(r.media)));
|
||||
|
||||
describe('native shell', () => {
|
||||
it('FE-NATIVE-SHELL-001: Arabic and Hebrew start right to left, every other language left to right', () => {
|
||||
expect(pickLocale(['ar-SA'])).toBe('ar')
|
||||
expect(pickLocale(['he-IL'])).toBe('he')
|
||||
expect(directionOf('ar')).toBe('rtl')
|
||||
expect(directionOf('he')).toBe('rtl')
|
||||
expect(directionOf('ru')).toBe('ltr')
|
||||
expect(directionOf('de')).toBe('ltr')
|
||||
})
|
||||
expect(pickLocale(['ar-SA'])).toBe('ar');
|
||||
expect(pickLocale(['he-IL'])).toBe('he');
|
||||
expect(directionOf('ar')).toBe('rtl');
|
||||
expect(directionOf('he')).toBe('rtl');
|
||||
expect(directionOf('ru')).toBe('ltr');
|
||||
expect(directionOf('de')).toBe('ltr');
|
||||
});
|
||||
|
||||
it('FE-NATIVE-SHELL-002: the first screens speak without dashes as punctuation', () => {
|
||||
for (const code of ['en', 'de']) {
|
||||
const t = translator(code)
|
||||
const t = translator(code);
|
||||
for (const key of ['native.welcome.sub', 'native.cloud.sub', 'native.cloud.f3']) {
|
||||
expect(t(key)).not.toMatch(/[\u2013\u2014]| - /)
|
||||
expect(t(key)).not.toMatch(/[\u2013\u2014]| - /);
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
it('FE-NATIVE-SHELL-003: Russian keeps TREK\'s faces through Cyrillic stand-ins under the same family names', () => {
|
||||
const faces = css.match(/@font-face\s*\{[^}]*\}/g) ?? []
|
||||
const cyrillic = faces.filter(f => /unicode-range:\s*U\+0301, U\+0400-045F/.test(f))
|
||||
expect(cyrillic.filter(f => /font-family:\s*'Poppins'/.test(f))).toHaveLength(3)
|
||||
expect(cyrillic.filter(f => /font-family:\s*'MuseoModerno'/.test(f))).toHaveLength(1)
|
||||
it("FE-NATIVE-SHELL-003: Russian keeps TREK's faces through Cyrillic stand-ins under the same family names", () => {
|
||||
const faces = css.match(/@font-face\s*\{[^}]*\}/g) ?? [];
|
||||
const cyrillic = faces.filter((f) => /unicode-range:\s*U\+0301, U\+0400-045F/.test(f));
|
||||
expect(cyrillic.filter((f) => /font-family:\s*'Poppins'/.test(f))).toHaveLength(3);
|
||||
expect(cyrillic.filter((f) => /font-family:\s*'MuseoModerno'/.test(f))).toHaveLength(1);
|
||||
for (const face of cyrillic) {
|
||||
const file = /url\('\.\/fonts\/([^']+)'\)/.exec(face)?.[1]
|
||||
expect(file).toBeTruthy()
|
||||
expect(() => readFileSync(resolve(process.cwd(), 'native-shell/fonts', file as string))).not.toThrow()
|
||||
const file = /url\('\.\/fonts\/([^']+)'\)/.exec(face)?.[1];
|
||||
expect(file).toBeTruthy();
|
||||
expect(() => readFileSync(resolve(process.cwd(), 'native-shell/fonts', file as string))).not.toThrow();
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
it('FE-NATIVE-SHELL-004: a short screen on its side puts the two halves of a screen side by side', () => {
|
||||
const compact = /orientation: landscape\) and \(max-height: 500px\)/
|
||||
expect(find('.screen:has(> .col)', compact)[0]?.body).toMatch(/grid-template-columns:\s*minmax\(0, 1fr\) minmax\(0, 1fr\)/)
|
||||
expect(find('.col-about', compact)[0]?.body).toMatch(/position:\s*sticky/)
|
||||
const compact = /orientation: landscape\) and \(max-height: 500px\)/;
|
||||
expect(find('.screen:has(> .col)', compact)[0]?.body).toMatch(
|
||||
/grid-template-columns:\s*minmax\(0, 1fr\) minmax\(0, 1fr\)/
|
||||
);
|
||||
expect(find('.col-about', compact)[0]?.body).toMatch(/position:\s*sticky/);
|
||||
// Everywhere else the halves are no boxes of their own: one column, as before.
|
||||
expect(find('.col')[0]?.body).toMatch(/display:\s*contents/)
|
||||
})
|
||||
expect(find('.col')[0]?.body).toMatch(/display:\s*contents/);
|
||||
});
|
||||
|
||||
it('FE-NATIVE-SHELL-005: a screen taller than the window scrolls instead of overflowing both edges', () => {
|
||||
expect(find('.screen')[0]?.body).toMatch(/margin-block:\s*auto/)
|
||||
const shell = find('.shell')[0]?.body ?? ''
|
||||
expect(shell).toMatch(/overflow-x:\s*clip/)
|
||||
expect(shell).not.toMatch(/justify-content:\s*center/)
|
||||
})
|
||||
expect(find('.screen')[0]?.body).toMatch(/margin-block:\s*auto/);
|
||||
const shell = find('.shell')[0]?.body ?? '';
|
||||
expect(shell).toMatch(/overflow-x:\s*clip/);
|
||||
expect(shell).not.toMatch(/justify-content:\s*center/);
|
||||
});
|
||||
|
||||
it('FE-NATIVE-SHELL-006: in Arabic the chevrons sit at the far edge and point along the reading direction, the price stands apart', () => {
|
||||
expect(find('.btn .chev')[0]?.body).toMatch(/margin-inline-start:\s*auto/)
|
||||
expect(find(":root[dir='rtl'] .chev")[0]?.body).toMatch(/transform:\s*scaleX\(-1\)/)
|
||||
expect(find('.plan-price small')[0]?.body).toMatch(/margin-inline-start:\s*7px/)
|
||||
expect(find('.btn-row')[0]?.body).toMatch(/text-align:\s*start/)
|
||||
expect(css).not.toMatch(/margin-left|margin-right|padding-left|padding-right|text-align:\s*left/)
|
||||
})
|
||||
})
|
||||
expect(find('.btn .chev')[0]?.body).toMatch(/margin-inline-start:\s*auto/);
|
||||
expect(find(":root[dir='rtl'] .chev")[0]?.body).toMatch(/transform:\s*scaleX\(-1\)/);
|
||||
expect(find('.plan-price small')[0]?.body).toMatch(/margin-inline-start:\s*7px/);
|
||||
expect(find('.btn-row')[0]?.body).toMatch(/text-align:\s*start/);
|
||||
expect(css).not.toMatch(/margin-left|margin-right|padding-left|padding-right|text-align:\s*left/);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user