Files
amare/tests/Feature/PublicSite/MotionMarkupTest.php

176 lines
7.6 KiB
PHP

<?php
declare(strict_types=1);
namespace Tests\Feature\PublicSite;
use App\Models\PortfolioCase;
use App\Models\Service;
use App\Models\SiteSetting;
use App\Models\Testimonial;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class MotionMarkupTest extends TestCase
{
use RefreshDatabase;
public function test_home_exposes_page_open_beats_reveal_groups_and_chapter_index_links(): void
{
$settings = SiteSetting::instance();
$settings->update([
'hero_title' => 'Celebrações com propósito',
'default_og_image_path' => 'media/hero.jpg',
'default_og_image_alt' => 'Capa editorial',
]);
Service::factory()->published()->featured()->create(['title' => 'Casamentos']);
PortfolioCase::factory()->published()->create([
'title' => 'Caso A',
'is_featured' => true,
]);
Testimonial::factory()->published()->create(['author_name' => 'Ana']);
$response = $this->get(route('home'));
$response
->assertOk()
->assertSee('data-motion="page-open"', false)
->assertSee('data-motion-beat="seal"', false)
->assertSee('data-motion-beat="title"', false)
->assertSee('data-motion-beat="media"', false)
->assertSee('data-motion-beat="cta"', false)
->assertSee('data-reveal-group', false)
->assertSee('data-reveal-from="up"', false)
->assertSee('data-chapter-index', false)
->assertSee('data-chapter-progress', false)
->assertSee('href="#hero-heading"', false)
->assertSee('href="#manifesto-heading"', false)
->assertSee('href="#services-heading"', false)
->assertSee('href="#portfolio-heading"', false)
->assertSee('href="#method-heading"', false)
->assertSee('href="#testimonials-heading"', false)
->assertSee('href="#positioning-heading"', false)
->assertSee('href="#final-cta-heading"', false)
->assertSee('data-chapter="hero"', false)
->assertSee('data-chapter="manifesto"', false)
->assertSee('data-reveal', false);
}
public function test_chapter_index_omits_empty_cms_sections(): void
{
SiteSetting::instance();
$response = $this->get(route('home'));
$response
->assertOk()
->assertSee('data-chapter-index', false)
->assertSee('href="#hero-heading"', false)
->assertSee('href="#manifesto-heading"', false)
->assertSee('href="#method-heading"', false)
->assertSee('href="#positioning-heading"', false)
->assertSee('href="#final-cta-heading"', false)
->assertDontSee('href="#services-heading"', false)
->assertDontSee('href="#portfolio-heading"', false)
->assertDontSee('href="#testimonials-heading"', false)
->assertDontSee('id="services-heading"', false)
->assertDontSee('id="portfolio-heading"', false)
->assertDontSee('id="testimonials-heading"', false);
}
public function test_motion_tokens_and_runtime_exist_with_reduced_motion_guards(): void
{
$tokens = (string) file_get_contents(resource_path('css/tokens.css'));
$appCss = (string) file_get_contents(resource_path('css/app.css'));
$appJs = (string) file_get_contents(resource_path('js/app.js'));
$this->assertStringContainsString('--amare-motion-enter: 500ms', $tokens);
$this->assertStringContainsString('--amare-motion-feedback: 150ms', $tokens);
$this->assertStringContainsString('--amare-motion-stagger: 90ms', $tokens);
$this->assertStringContainsString('--amare-motion-stagger-cap: 270ms', $tokens);
$this->assertStringContainsString('--amare-motion-distance-y: 12px', $tokens);
$this->assertStringContainsString('--amare-motion-distance-x: 24px', $tokens);
$this->assertStringContainsString('--amare-motion-distance-x-mobile: 16px', $tokens);
$this->assertStringContainsString('--amare-ease-arrival:', $tokens);
$this->assertStringContainsString('--amare-motion-enter: 0.01ms', $tokens);
$this->assertStringContainsString('[data-motion="page-open"]', $appCss);
$this->assertStringContainsString('[data-motion-beat]', $appCss);
$this->assertStringContainsString('[data-reveal-group]', $appCss);
$this->assertStringContainsString('[data-reveal-from="left"]', $appCss);
$this->assertStringContainsString('[data-reveal-from="right"]', $appCss);
$this->assertStringContainsString('[data-reveal]', $appCss);
$this->assertStringContainsString('[data-chapter-index]', $appCss);
$this->assertStringContainsString('prefers-reduced-motion: no-preference', $appCss);
// Entrance motion must not fade text opacity — mid-fade fails WCAG contrast (axe).
$this->assertDoesNotMatchRegularExpression(
'/\[data-motion-beat\][^{]*\{[^}]*opacity:\s*0/s',
$appCss,
);
$this->assertDoesNotMatchRegularExpression(
'/\[data-motion="page-open"\]:not\(\.is-active\)[^{]*\{[^}]*opacity:\s*0/s',
$appCss,
);
$this->assertDoesNotMatchRegularExpression(
'/\[data-reveal\]:not\(\.is-revealed\)[^{]*\{[^}]*opacity:\s*0/s',
$appCss,
);
$this->assertFileExists(resource_path('js/motion.js'));
$this->assertStringContainsString("import './motion.js'", $appJs);
$motionJs = (string) file_get_contents(resource_path('js/motion.js'));
$this->assertStringContainsString('prefers-reduced-motion', $motionJs);
$this->assertStringContainsString("querySelectorAll('[data-reveal-group]')", $motionJs);
$this->assertStringContainsString('Math.min(index, 3)', $motionJs);
$this->assertStringContainsString('observer.unobserve(entry.target)', $motionJs);
$this->assertStringContainsString("typeof IntersectionObserver !== 'function'", $motionJs);
$this->assertStringContainsString('requestAnimationFrame', $motionJs);
$this->assertStringContainsString('framePending', $motionJs);
}
public function test_all_visual_public_pages_expose_shared_opening_and_reveal_hooks(): void
{
SiteSetting::instance();
$case = PortfolioCase::factory()->published()->create([
'slug' => 'casamento-jardim',
'title' => 'Casamento Jardim',
]);
$responses = [
$this->get(route('home')),
$this->get(route('services.index')),
$this->get(route('portfolio.index')),
$this->get(route('portfolio.show', $case->slug)),
$this->get(route('about')),
$this->get(route('contact')),
$this->get(route('privacy')),
];
foreach ($responses as $response) {
$response
->assertOk()
->assertSee('data-motion="page-open"', false)
->assertSee('data-motion-beat', false)
->assertSee('data-reveal-group', false)
->assertSee('data-reveal', false);
}
}
public function test_technical_endpoints_do_not_expose_motion_contract(): void
{
SiteSetting::instance();
$this->get(route('sitemap'))
->assertOk()
->assertDontSee('data-motion=', false)
->assertDontSee('data-reveal', false);
$this->get(route('robots'))
->assertOk()
->assertDontSee('data-motion=', false)
->assertDontSee('data-reveal', false);
}
}