feat: add new-tab links and removable media previews (1.7.3)
CMS quality / php (8.2) (push) Canceled after 0s
CMS quality / php (8.4) (push) Canceled after 0s
CMS quality / browser (push) Canceled after 0s
CMS quality / mysql (push) Canceled after 0s

This commit is contained in:
kantarci committed 2026-09-14 11:48:44 +02:00
1 parent b8991ddd5c
commit 6d21c452bd
28 files changed
+496 -46

No files matched your search

+65
View File
@@ -2569,6 +2569,69 @@ final class CmsTest extends TestCase
$this->partials->delete($this->partials->findByKind('header')['id']);
}
public function testNewTabLinksPersistForBlocksAndNestedMenus(): void
{
$document = DemoContent::starter('Links');
$document['schemaVersion'] = 20;
$types = [
'button' => ['text' => 'Öffnen'],
'image' => ['src' => '/bild.png', 'width' => 120, 'height' => 80, 'alt' => 'Bild', 'decorative' => false, 'click' => 'link'],
'iconcard' => ['title' => 'Karte', 'text' => '', 'icon' => 'star'],
'cta' => ['title' => 'Aufruf', 'text' => '', 'buttonText' => 'Öffnen'],
'icon' => ['icon' => 'star', 'size' => 24, 'decorative' => false, 'label' => 'Öffnen'],
];
foreach ($types as $type => $props) {
$document['blocks'][] = ['id' => 'new-'.$type, 'type' => $type, 'props' => [...$props, 'href' => '/kontakt', 'newTab' => true]];
}
$revision = $this->pages->saveDraft('page-1', $document, 0);
$this->pages->publish('page-1', $revision, 1);
$this->menus->save('header', 'Links', [['label' => 'Eltern', 'type' => 'url', 'url' => '/', 'newTab' => true, 'children' => [['label' => 'Kind', 'type' => 'url', 'url' => '/kontakt', 'newTab' => true]]]], 0);
$crawler = new \Symfony\Component\DomCrawler\Crawler($this->request('/')->getContent());
foreach (['#new-button', '#new-image a', '#new-iconcard a', '#new-cta a', '#new-icon'] as $selector) {
self::assertSame('_blank', $crawler->filter($selector)->attr('target'));
self::assertSame('noopener', $crawler->filter($selector)->attr('rel'));
}
self::assertCount(2, $crawler->filter('.site-header .menu-list a[target="_blank"][rel="noopener"]'));
$document['blocks'][array_key_last($document['blocks'])]['props']['newTab'] = 'yes';
$this->expectException(InvalidDocument::class);
(new DocumentValidator())->validate($document);
}
public function testNewTabAndRemovedBackgroundRequireSchemaTwenty(): void
{
foreach ([['type' => 'button', 'props' => ['text' => 'Link', 'href' => '/', 'newTab' => true]], ['type' => 'section', 'style' => ['tablet' => ['backgroundImage' => 'none']], 'children' => []]] as $block) {
$document = DemoContent::starter('Schema');
$document['schemaVersion'] = 19;
$document['blocks'][] = ['id' => 'new-option', ...$block];
try {
(new DocumentValidator())->validate($document);
self::fail('An explicit schema upgrade is required.');
} catch (InvalidDocument) {
$document['schemaVersion'] = 20;
(new DocumentValidator())->validate($document);
self::assertSame(20, $document['schemaVersion']);
}
}
}
public function testRemovingAnInheritedBackgroundAlsoRemovesItOnNarrowerDevices(): void
{
$id = str_repeat('a', 32);
$this->db->insert('media', ['id' => $id, 'title' => 'Klein', 'alt' => '', 'variants' => json_encode([['src' => '/media/'.$id.'/192', 'width' => 192, 'height' => 64, 'bytes' => 1]]), 'archived' => 0, 'version' => 1, 'created_at' => gmdate('Y-m-d H:i:s')]);
$compiler = new StyleCompiler(new MediaLibrary($this->db, $this->path.'-media'));
$document = ['blocks' => [['id' => 'background', 'style' => ['desktop' => ['backgroundImage' => $id], 'tablet' => ['backgroundImage' => 'none']]]]];
$css = $compiler->compileDocument($document);
self::assertStringContainsString('#background{background-image:url("/media/'.$id.'/192")}', $css);
self::assertStringContainsString('@media (max-width:1024px){#background{background-image:none}}', $css);
self::assertStringContainsString('@media (max-width:767px){#background{background-image:none}}', $css);
$document['blocks'][0]['style']['desktop'] += ['gradientFrom' => '#000000', 'gradientTo' => '#ffffff', 'gradientAngle' => 90];
$gradientCss = $compiler->compileDocument($document);
self::assertStringContainsString('@media (max-width:1024px){#background{background-image:linear-gradient(90deg,#000000,#ffffff)}}', $gradientCss);
self::assertStringContainsString('@media (max-width:767px){#background{background-image:linear-gradient(90deg,#000000,#ffffff)}}', $gradientCss);
unset($document['blocks'][0]['style']['tablet']);
self::assertStringNotContainsString('background-image:none', $compiler->compileDocument($document));
}
/**
* @return iterable<string, array{string, string}>
*/
@@ -2585,6 +2648,8 @@ final class CmsTest extends TestCase
yield 'removes an iframe' => ['<iframe src="https://evil.example"></iframe>ok', 'ok'];
yield 'closes unbalanced markup' => ['<strong>unge<em>schlossen', '<strong>unge<em>schlossen</em></strong>'];
yield 'keeps a local link' => ['<a href="/kontakt">hier</a>', '<a href="/kontakt">hier</a>'];
yield 'new local tab has no opener' => ['<a href="/kontakt" target="_blank" rel="opener">hier</a>', '<a href="/kontakt" target="_blank" rel="noopener">hier</a>'];
yield 'rejects named frames' => ['<a href="/kontakt" target="_top">hier</a>', '<a href="/kontakt">hier</a>'];
yield 'marks an external link' => ['<a href="https://example.com">dort</a>', '<a href="https://example.com" rel="noopener">dort</a>'];
yield 'escapes plain text and keeps the line break' => ["Eins <3\nZwei", 'Eins &lt;3<br>Zwei'];
}