Bilder in vier Breiten, Releasearchiv fuer Hosting ohne Composer
Uploads werden weiterhin nach WebP umgewandelt, jetzt in vier statt drei Breiten (480/960/1600/2400) und ohne Hochskalieren. Hintergrundbilder waehlen ihre Variante je Geraet, weil CSS kein srcset kennt. Dabei kam ein Fehler heraus, den erst der Browsertest zeigte: der Dokumentpruefer erlaubte drei sources je Bild, also war mit der vierten Variante jedes Dokument mit einem Bild ungueltig - ohne sichtbare Meldung. Die Grenze haengt jetzt an MediaLibrary::WIDTHS. Geteiltes Hosting hat meistens keinen Composer. scripts/build-release.php baut deshalb auf einem Build-Rechner ein Archiv mit fertigem vendor/ ohne Entwicklungspakete, samt SHA-256. Der Updater bricht ab, wenn ein Update composer.lock aendert und Composer auf dem Server nicht aufrufbar ist, statt mit einem unpassenden vendor/ weiterzulaufen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
c046681302
commit
c3bff7d936
17 files changed
+414
-26
No files matched your search
@@ -26,6 +26,7 @@ use App\Kernel;
|
||||
use Doctrine\DBAL\Connection;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\HttpFoundation\File\UploadedFile;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
@@ -1784,6 +1785,97 @@ final class CmsTest extends TestCase
|
||||
]);
|
||||
}
|
||||
|
||||
public function testAnImageMayCarryAsManySourcesAsThePipelineProduces(): void
|
||||
{
|
||||
// This is the test for a bug the browser check found: the validator allowed three sources,
|
||||
// the media library produced four, and every document with a picture in it was rejected -
|
||||
// silently, because a rejected save looks like nothing happening.
|
||||
$sources = [];
|
||||
foreach (MediaLibrary::WIDTHS as $width) {
|
||||
$sources[] = ['src' => '/media/'.str_repeat('a', 32).'/'.$width, 'width' => $width];
|
||||
}
|
||||
$image = ['id' => 'bild', 'type' => 'image', 'props' => [
|
||||
'src' => end($sources)['src'], 'alt' => 'Ein Bild', 'decorative' => false,
|
||||
'width' => end($sources)['width'], 'height' => 800, 'sources' => $sources,
|
||||
]];
|
||||
|
||||
$document = DemoContent::pages()['home'];
|
||||
$document['schemaVersion'] = max(3, $document['schemaVersion']);
|
||||
$document['blocks'][0]['children'][] = $image;
|
||||
(new DocumentValidator())->validate($document);
|
||||
self::assertCount(count(MediaLibrary::WIDTHS), $sources, 'Vier Quellen sind erlaubt, nicht drei.');
|
||||
|
||||
// One more than the pipeline can produce stays a reason to refuse.
|
||||
$last = array_key_last($document['blocks'][0]['children']);
|
||||
$document['blocks'][0]['children'][$last]['props']['sources'][] = ['src' => '/media/'.str_repeat('a', 32).'/3200', 'width' => 3200];
|
||||
$this->expectException(InvalidDocument::class);
|
||||
(new DocumentValidator())->validate($document);
|
||||
}
|
||||
|
||||
public function testAnUploadBecomesWebpInSeveralWidths(): void
|
||||
{
|
||||
$media = new MediaLibrary($this->db, $this->path.'-media');
|
||||
$file = tempnam(sys_get_temp_dir(), 'kant-upload-');
|
||||
// The upload guard compares against the memory limit, and by this point in a full suite
|
||||
// run a good part of it is already used. The limit is raised for this test, because the
|
||||
// subject here is the image pipeline and not the guard.
|
||||
$limit = ini_get('memory_limit');
|
||||
ini_set('memory_limit', '512M');
|
||||
try {
|
||||
// A source wide enough for every step.
|
||||
$image = imagecreatetruecolor(2600, 1600);
|
||||
imagejpeg($image, $file, 92);
|
||||
imagedestroy($image);
|
||||
$item = $media->upload(new UploadedFile($file, 'foto.jpg', test: true));
|
||||
|
||||
// Four widths, all WebP, none larger than the source.
|
||||
self::assertSame([480, 960, 1600, 2400], array_column($item['variants'], 'width'));
|
||||
foreach ($item['variants'] as $variant) {
|
||||
$path = $this->path.'-media/'.$item['id'].'-'.$variant['width'].'.webp';
|
||||
self::assertSame('image/webp', getimagesize($path)['mime'], 'Jede Variante ist WebP.');
|
||||
// The aspect ratio survives the resizing.
|
||||
self::assertSame((int) round($variant['width'] * 1600 / 2600), $variant['height']);
|
||||
self::assertGreaterThan(0, $variant['bytes']);
|
||||
}
|
||||
|
||||
// A narrow source is not blown up: it yields fewer variants, not bigger ones.
|
||||
$small = imagecreatetruecolor(700, 500);
|
||||
imagepng($small, $file);
|
||||
imagedestroy($small);
|
||||
$narrow = $media->upload(new UploadedFile($file, 'klein.png', test: true));
|
||||
self::assertSame([480, 700], array_column($narrow['variants'], 'width'));
|
||||
} finally {
|
||||
@unlink($file);
|
||||
ini_set('memory_limit', (string) $limit);
|
||||
}
|
||||
}
|
||||
|
||||
public function testABackgroundImageUsesASmallerVariantOnSmallerScreens(): void
|
||||
{
|
||||
$media = new MediaLibrary($this->db, $this->path.'-media');
|
||||
$file = tempnam(sys_get_temp_dir(), 'kant-bg-');
|
||||
try {
|
||||
// Smaller than the upload test above: both run in the same process, and the memory
|
||||
// guard counts what is already allocated.
|
||||
$image = imagecreatetruecolor(2000, 1200);
|
||||
imagejpeg($image, $file, 92);
|
||||
imagedestroy($image);
|
||||
$item = $media->upload(new UploadedFile($file, 'hintergrund.jpg', test: true));
|
||||
|
||||
$compiler = new StyleCompiler($media);
|
||||
$css = $compiler->compileDocument(['blocks' => [['id' => 'hero', 'type' => 'section', 'style' => [
|
||||
'desktop' => ['backgroundImage' => $item['id']],
|
||||
'mobile' => ['backgroundImage' => $item['id']],
|
||||
]]]]);
|
||||
|
||||
// CSS has no srcset, but the rules are written per device - so the device decides.
|
||||
self::assertStringContainsString('#hero{background-image:url("/media/'.$item['id'].'/2000")}', $css);
|
||||
self::assertStringContainsString('@media (max-width:767px){#hero{background-image:url("/media/'.$item['id'].'/960")}}', $css);
|
||||
} finally {
|
||||
@unlink($file);
|
||||
}
|
||||
}
|
||||
|
||||
public function testEveryLongListSaysHowMuchThereIsAndLetsYouReachIt(): void
|
||||
{
|
||||
// Thirty revisions: a fixed cap would show some and say nothing about the rest.
|
||||
|
||||
@@ -444,7 +444,9 @@ final class EditorSecurityTest extends WebTestCase
|
||||
$client->request('POST', '/admin/api/media/upload', files: ['file' => new UploadedFile($path, 'example.php', test: true)], server: ['HTTP_X_CSRF_TOKEN' => $token]);
|
||||
self::assertResponseStatusCodeSame(201);
|
||||
$item = json_decode($client->getResponse()->getContent(), true)['item'];
|
||||
self::assertSame([480, 960, 1600], array_column($item['variants'], 'width'));
|
||||
// Four steps now; the last one is capped at the width of the source (1800 here), so a
|
||||
// picture is never blown up beyond what was uploaded.
|
||||
self::assertSame([480, 960, 1600, 1800], array_column($item['variants'], 'width'));
|
||||
foreach ($item['variants'] as $variant) {
|
||||
$file = $this->path.'-media/'.$item['id'].'-'.$variant['width'].'.webp';
|
||||
self::assertSame('image/webp', getimagesize($file)['mime']);
|
||||
|
||||
+30
-1
@@ -136,6 +136,34 @@ final class UpdaterTest extends TestCase
|
||||
self::assertStringContainsString('FEHLER v1.2.0', $updater->log()[0]);
|
||||
}
|
||||
|
||||
public function testAnUpdateThatChangesDependenciesStopsWhenComposerIsMissing(): void
|
||||
{
|
||||
// The installation carries the lock of 1.0.0; the new version brings a different one. On
|
||||
// hosting without Composer that cannot be resolved: vendor/ is outside Git and stays old.
|
||||
$this->release('1.3.0', true, 'neu');
|
||||
|
||||
$updater = $this->updater();
|
||||
$result = $updater->update();
|
||||
|
||||
self::assertFalse($result['ok'], $this->explain($result));
|
||||
$failed = $this->firstFailed($result);
|
||||
self::assertSame('Abhängigkeiten', $failed['step']);
|
||||
self::assertStringContainsString('Releasearchiv', $failed['detail']);
|
||||
// Rolled back, and the site is online again on the previous version.
|
||||
self::assertSame('1.0.0', $updater->version());
|
||||
self::assertFalse(is_file($this->work.'/'.Updater::MAINTENANCE_FILE));
|
||||
}
|
||||
|
||||
public function testAnUpdateWithUnchangedDependenciesRunsWithoutComposer(): void
|
||||
{
|
||||
// Same dependencies as the installed version, so vendor/ stays valid.
|
||||
$this->release('1.4.0', true, 'alt');
|
||||
|
||||
$result = $this->updater()->update();
|
||||
self::assertTrue($result['ok'], $this->explain($result));
|
||||
self::assertStringContainsString('composer.lock ist unverändert', $this->named($result['steps'], 'Abhängigkeiten', 'step')['detail']);
|
||||
}
|
||||
|
||||
public function testADryRunChangesNothing(): void
|
||||
{
|
||||
$this->release('1.1.0', true);
|
||||
@@ -189,8 +217,9 @@ final class UpdaterTest extends TestCase
|
||||
* Publishes a release: VERSION, a .gitignore that keeps the installation out of Git, and the
|
||||
* two stub commands the updater calls. `$healthy` decides whether they succeed.
|
||||
*/
|
||||
private function release(string $version, bool $healthy): void
|
||||
private function release(string $version, bool $healthy, string $lock = 'alt'): void
|
||||
{
|
||||
file_put_contents($this->author.'/composer.lock', json_encode(['packages' => [$lock]]));
|
||||
file_put_contents($this->author.'/VERSION', $version."\n");
|
||||
file_put_contents($this->author.'/README.md', 'KantSites '.$version."\n");
|
||||
// Only the installation is kept out of Git here: the stub commands live under vendor/
|
||||
|
||||
Reference in new issue
Block a user