temporary = sys_get_temp_dir().'/livewire-material-mail-'.Str::random(8); File::ensureDirectoryExists($this->temporary); File::put($this->temporary.'/mail-theme-probe.blade.php', <<<'BLADE' # Your export is ready The report has **finished**. Download Files expire after seven days. | File | Size | |:-----|-----:| | orders.csv | 1.2 MB | BLADE); File::put($this->temporary.'/mail-footer-probe.blade.php', <<<'BLADE' Hello. Sent by the probe · [Unsubscribe](https://example.com/unsubscribe) BLADE); View::addLocation($this->temporary); config(['mail.markdown.theme' => 'livewire-material::mail.theme']); }); afterEach(function () { File::deleteDirectory($this->temporary); }); /** * The inline style of the first element carrying the class. */ function inlineStyle(string $html, string $class): string { preg_match('/<[^>]+class="[^"]*\b'.preg_quote($class, '/').'\b[^"]*"[^>]*style="([^"]*)"/', $html, $match); return $match[1] ?? ''; } /** * Put the package's mail components on `mail.markdown.paths`, as the provider does when * `livewire-material.mail.components` is on at boot, and make the renderer again. */ function withPackageMailComponents(): void { config(['livewire-material.mail.components' => true]); $provider = app()->getProvider(LivewireMaterialServiceProvider::class); (fn () => $this->registerMailComponents())->call($provider); app()->forgetInstance(Markdown::class); } it('stays on the standard contrast level, which is the one a mail client can show', function () { File::put($this->temporary.'/material-scheme.json', json_encode([ 'light' => ['primary' => '#123456', 'on-primary' => '#fefefe', 'surface-container' => '#eeeeee', 'surface-container-lowest' => '#fdfdfd'], 'dark' => ['primary' => '#abcdef'], 'contrast' => [ 'standard' => 0, 'high' => ['light' => ['primary' => '#000000', 'on-primary' => '#ffffff'], 'dark' => ['primary' => '#ffffff']], ], ])); config(['livewire-material.scheme' => $this->temporary.'/material-scheme.json']); expect(inlineStyle((new ThemedProbeMail)->render(), 'button-primary')) ->toContain('background-color: #123456') ->not->toContain('background-color: #000000'); }); it('inlines the application\'s scheme onto the mail', function () { File::put($this->temporary.'/material-scheme.json', json_encode([ 'light' => ['primary' => '#123456', 'on-primary' => '#fefefe', 'surface-container' => '#eeeeee', 'surface-container-lowest' => '#fdfdfd'], 'dark' => ['primary' => '#abcdef'], ])); config(['livewire-material.scheme' => $this->temporary.'/material-scheme.json']); $html = (new ThemedProbeMail)->render(); expect(inlineStyle($html, 'button-primary')) ->toContain('background-color: #123456') ->toContain('border-top: 16px solid #123456') ->toContain('color: #fefefe') ->toContain('border-radius: 9999px') ->and(inlineStyle($html, 'inner-body'))->toContain('background-color: #fdfdfd') ->and(inlineStyle($html, 'wrapper'))->toContain('background-color: #eeeeee') ->and(inlineStyle($html, 'panel-content'))->toContain('background-color: #eeeeee') ->and($html) ->toMatch('/

toMatch('/

toMatch('/]*style="[^"]*font-weight: 500/') ->toMatch('/not->toContain('#abcdef') ->not->toContain('var(--') ->not->toContain('color-mix'); }); it('inlines the active colour profile onto the mail', function () { File::put($this->temporary.'/material-scheme.json', json_encode([ 'light' => ['primary' => '#4f46e5'], 'dark' => ['primary' => '#aaaaff'], 'default' => 'indigo', 'profiles' => [ 'indigo' => ['label' => 'Indigo', 'light' => ['primary' => '#4f46e5'], 'dark' => ['primary' => '#aaaaff']], 'rose' => ['label' => 'Rose', 'light' => ['primary' => '#c2185b'], 'dark' => ['primary' => '#ffb0c8']], ], ])); config(['livewire-material.scheme' => $this->temporary.'/material-scheme.json']); Scheme::resolveProfileUsing(fn (): string => 'rose'); try { expect(inlineStyle((new ThemedProbeMail)->render(), 'button-primary'))->toContain('background-color: #c2185b'); } finally { Scheme::resolveProfileUsing(null); } }); it('takes the theme from a mailable too', function () { config(['mail.markdown.theme' => 'default']); $mail = new ThemedProbeMail; $mail->theme = 'livewire-material::mail.theme'; expect(inlineStyle($mail->render(), 'button-primary'))->toContain('border-radius: 9999px'); }); it('falls back to the package\'s default scheme', function (?string $contents) { $path = $this->temporary.'/material-scheme.json'; if ($contents !== null) { File::put($path, $contents); } config(['livewire-material.scheme' => $path]); $default = json_decode(File::get(__DIR__.'/../../resources/css/tokens/scheme.json'), true); expect(inlineStyle((new ThemedProbeMail)->render(), 'button-primary')) ->toContain("background-color: {$default['light']['primary']}"); })->with([ 'no scheme file' => [null], 'not JSON' => ['{ nope'], 'not a colour' => [json_encode(['light' => ['primary' => 'red; background: url(x)']])], ]); it('styles every button colour Laravel and M3 name', function () { $css = view('livewire-material::mail.theme')->render(); foreach (['primary', 'secondary', 'tertiary', 'error', 'success', 'warning', 'info', 'blue', 'green', 'red'] as $color) { expect($css)->toContain(".button-{$color} {"); } expect($css)->not->toContain('@media'); }); it('leaves the application\'s mail components alone unless asked', function () { expect(config('mail.markdown.paths'))->not->toContain(LivewireMaterialServiceProvider::mailComponentPath()); withPackageMailComponents(); withPackageMailComponents(); expect(config('mail.markdown.paths'))->toBe([ resource_path('views/vendor/mail'), LivewireMaterialServiceProvider::mailComponentPath(), ]); }); it('renders the package header: the app name, never Laravel\'s logo', function () { config(['app.name' => 'Laravel']); $framework = (new ThemedProbeMail)->render(); withPackageMailComponents(); $html = (new ThemedProbeMail)->render(); expect($framework)->toContain('notification-logo') ->and($html)->not->toContain('notification-logo') ->toMatch('/]*>\s*]*>\s*Laravel\s*<\/a>/'); }); it('renders a configured logo in the header, sized by attributes', function () { config(['app.name' => 'Probe', 'livewire-material.mail.logo' => ['src' => 'https://example.com/logo.png', 'width' => 160, 'height' => 40]]); withPackageMailComponents(); expect((new ThemedProbeMail)->render()) ->toMatch('/ 'Probe']); withPackageMailComponents(); $markdown = app(Markdown::class); expect((string) $markdown->render('mail-footer-probe')) ->toContain('Sent by the probe') ->toContain('href="https://example.com/unsubscribe"') ->not->toContain('All rights reserved.') ->and((string) $markdown->renderText('mail-footer-probe')) ->toContain('Sent by the probe') ->not->toContain('All rights reserved.') ->and((string) $markdown->render('mail-theme-probe')) ->toContain('© '.date('Y').' Probe. All rights reserved.'); });