Merge branch 'worktree-agent-afa50210f14592c5f'
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Process;
|
||||
|
||||
/**
|
||||
* PHP cannot open a woff2 without the Brotli extension, so the axes are read by Node:
|
||||
* bin/check-font.mjs (fontkit) prints them as JSON. Without Node the test is skipped, the
|
||||
* way `material:scheme` reports the same missing binary instead of guessing.
|
||||
*/
|
||||
it('keeps the variable axes the typescale depends on in the packaged font', function () {
|
||||
$node = config('livewire-material.node', 'node');
|
||||
|
||||
if (Process::run([$node, '--version'])->failed()) {
|
||||
$this->markTestSkipped("Node ({$node}) could not run; `npm run check:font` reads the woff2's axes.");
|
||||
}
|
||||
|
||||
$result = Process::run([$node, __DIR__.'/../../bin/check-font.mjs']);
|
||||
|
||||
expect($result->successful())->toBeTrue($result->errorOutput());
|
||||
|
||||
$font = json_decode($result->output(), true, flags: JSON_THROW_ON_ERROR);
|
||||
|
||||
expect($font['axes'])->toHaveKeys(['wght', 'ROND'])
|
||||
// font.css declares `font-weight: 400 700`; every typescale weight is inside it.
|
||||
->and($font['axes']['wght'])->toMatchArray(['min' => 400, 'max' => 700])
|
||||
// The axis every `type-emphasized-*` utility sets to 100; a re-subset must keep it.
|
||||
->and($font['axes']['ROND'])->toMatchArray(['min' => 0, 'max' => 100]);
|
||||
});
|
||||
|
||||
it('fails loudly when the font has no variable axes at all', function () {
|
||||
$node = config('livewire-material.node', 'node');
|
||||
|
||||
if (Process::run([$node, '--version'])->failed()) {
|
||||
$this->markTestSkipped("Node ({$node}) could not run; `npm run check:font` reads the woff2's axes.");
|
||||
}
|
||||
|
||||
$result = Process::run([$node, __DIR__.'/../../bin/check-font.mjs', __DIR__.'/../Fixtures/no-such-font.woff2']);
|
||||
|
||||
expect($result->failed())->toBeTrue()
|
||||
->and($result->errorOutput())->toContain('no-such-font.woff2');
|
||||
});
|
||||
@@ -20,6 +20,34 @@ function declarations(string $css, string $selector): array
|
||||
return array_combine($matches[1], $matches[2]);
|
||||
}
|
||||
|
||||
/**
|
||||
* M3's 15 type styles, in the order type.css declares them.
|
||||
*
|
||||
* @return list<string>
|
||||
*/
|
||||
function typeStyles(): array
|
||||
{
|
||||
return [
|
||||
'display-lg', 'display-md', 'display-sm',
|
||||
'headline-lg', 'headline-md', 'headline-sm',
|
||||
'title-lg', 'title-md', 'title-sm',
|
||||
'body-lg', 'body-md', 'body-sm',
|
||||
'label-lg', 'label-md', 'label-sm',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* The body of every `@utility type-…` block of type.css, keyed by utility name.
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
function typeUtilities(): array
|
||||
{
|
||||
preg_match_all('/@utility (type-[\w-]+) \{\n(.*?)\n\}/s', File::get(packageCss('tokens/type.css')), $matches, PREG_SET_ORDER);
|
||||
|
||||
return array_combine(array_column($matches, 1), array_column($matches, 2));
|
||||
}
|
||||
|
||||
it('ships a default scheme that declares every role in both themes', function () {
|
||||
$scheme = json_decode(File::get(packageCss('tokens/scheme.json')), true);
|
||||
$css = File::get(packageCss('tokens/scheme.css'));
|
||||
@@ -85,15 +113,93 @@ it('reads every safe-area inset through a variable that can replace it', functio
|
||||
->and(File::get(packageCss('components/app-bar.css')))->toContain('padding-top: var(--material-safe-top, env(safe-area-inset-top));');
|
||||
});
|
||||
|
||||
it('makes every motion token instant under reduced motion', function () {
|
||||
it('makes every motion token instant under reduced motion, in both schemes', function () {
|
||||
$motion = File::get(packageCss('tokens/motion.css'));
|
||||
$reduced = Str::of($motion)->after('prefers-reduced-motion: reduce')->toString();
|
||||
|
||||
preg_match_all('/(--md-sys-motion-[\w-]*duration[\w-]*):/', Str::before($motion, '@media'), $durations);
|
||||
|
||||
expect($durations[1])->not->toBeEmpty();
|
||||
expect($durations[1])->not->toBeEmpty()
|
||||
// The Standard scheme sets the same variables on a selector of its own, so :root alone
|
||||
// would not reach it: the block has to name both.
|
||||
->and($reduced)->toMatch('/:root,\s*\[data-motion="standard"\] \{/');
|
||||
|
||||
foreach ($durations[1] as $duration) {
|
||||
expect($reduced)->toContain("{$duration}: 0ms;");
|
||||
}
|
||||
});
|
||||
|
||||
it('resets the roundness axis on every regular type style, so emphasis stays one element', function () {
|
||||
$utilities = typeUtilities();
|
||||
|
||||
expect(array_keys($utilities))->toHaveCount(30);
|
||||
|
||||
foreach (typeStyles() as $style) {
|
||||
expect($utilities)->toHaveKeys(["type-{$style}", "type-emphasized-{$style}"])
|
||||
// font-variation-settings inherits: without the reset, body copy inside an
|
||||
// emphasized heading would stay fully rounded.
|
||||
->and($utilities["type-{$style}"])->toContain('font-variation-settings: normal;')
|
||||
->and($utilities["type-emphasized-{$style}"])->toContain('font-variation-settings: "ROND" 100;');
|
||||
}
|
||||
});
|
||||
|
||||
it('gives every emphasized type style its own tracking, as Compose does', function () {
|
||||
$tokens = declarations(File::get(packageCss('tokens/type.css')), ':root');
|
||||
$utilities = typeUtilities();
|
||||
|
||||
foreach (typeStyles() as $style) {
|
||||
expect($tokens)->toHaveKey("--md-sys-typescale-emphasized-{$style}-tracking")
|
||||
->and($utilities["type-{$style}"])->toContain("letter-spacing: var(--md-sys-typescale-{$style}-tracking);")
|
||||
->and($utilities["type-emphasized-{$style}"])->toContain("letter-spacing: var(--md-sys-typescale-emphasized-{$style}-tracking);");
|
||||
}
|
||||
|
||||
// TypeScaleTokens.kt as sp / 16, for the five styles where emphasized parts from baseline.
|
||||
expect($tokens)->toMatchArray([
|
||||
'--md-sys-typescale-display-lg-tracking' => '-0.0125rem',
|
||||
'--md-sys-typescale-emphasized-display-lg-tracking' => '0rem',
|
||||
'--md-sys-typescale-title-md-tracking' => '0.0125rem',
|
||||
'--md-sys-typescale-emphasized-title-md-tracking' => '0.009375rem',
|
||||
'--md-sys-typescale-body-lg-tracking' => '0.03125rem',
|
||||
'--md-sys-typescale-emphasized-body-lg-tracking' => '0.009375rem',
|
||||
'--md-sys-typescale-body-md-tracking' => '0.0125rem',
|
||||
'--md-sys-typescale-emphasized-body-md-tracking' => '0.015625rem',
|
||||
'--md-sys-typescale-label-md-tracking' => '0.03125rem',
|
||||
'--md-sys-typescale-emphasized-label-md-tracking' => '0.03125rem',
|
||||
]);
|
||||
});
|
||||
|
||||
it('reads every spring over the duration M3 publishes for the web', function () {
|
||||
$expressive = declarations(File::get(packageCss('tokens/motion.css')), ':root');
|
||||
|
||||
// docs/reference/m3/styles.md § Motion, "Web curve equivalents for springs".
|
||||
$durations = [
|
||||
'spatial-fast' => '350ms', 'spatial-default' => '500ms', 'spatial-slow' => '650ms',
|
||||
'effects-fast' => '150ms', 'effects-default' => '200ms', 'effects-slow' => '300ms',
|
||||
];
|
||||
|
||||
foreach ($durations as $spring => $duration) {
|
||||
expect($expressive["--md-sys-motion-{$spring}-duration"])->toBe($duration)
|
||||
// 49 sampled points, starting at rest and landing exactly on the target.
|
||||
->and($expressive["--md-sys-motion-{$spring}"])->toStartWith('linear(0, ')->toEndWith(', 1)')
|
||||
->and(explode(', ', $expressive["--md-sys-motion-{$spring}"]))->toHaveCount(49);
|
||||
}
|
||||
});
|
||||
|
||||
it('swaps only the spatial springs for the Standard motion scheme', function () {
|
||||
$motion = File::get(packageCss('tokens/motion.css'));
|
||||
$expressive = declarations($motion, ':root');
|
||||
$standard = declarations($motion, '[data-motion="standard"] {');
|
||||
|
||||
// Both schemes share the effects springs, so the block carries the three spatial pairs only.
|
||||
expect(array_keys($standard))->toBe([
|
||||
'--md-sys-motion-spatial-fast', '--md-sys-motion-spatial-fast-duration',
|
||||
'--md-sys-motion-spatial-default', '--md-sys-motion-spatial-default-duration',
|
||||
'--md-sys-motion-spatial-slow', '--md-sys-motion-spatial-slow-duration',
|
||||
]);
|
||||
|
||||
foreach (['fast' => '350ms', 'default' => '500ms', 'slow' => '750ms'] as $speed => $duration) {
|
||||
expect($standard["--md-sys-motion-spatial-{$speed}-duration"])->toBe($duration)
|
||||
->and($standard["--md-sys-motion-spatial-{$speed}"])->toStartWith('linear(0, ')
|
||||
->not->toBe($expressive["--md-sys-motion-spatial-{$speed}"]);
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user