uploadDir = sys_get_temp_dir() . '/urlshort-test-' . bin2hex(random_bytes(4)); mkdir($this->uploadDir, 0775, true); } protected function tearDown(): void { foreach (glob($this->uploadDir . '/*') ?: [] as $file) { unlink($file); } if (is_dir($this->uploadDir)) { rmdir($this->uploadDir); } } private function renderer(): QrRenderer { return new QrRenderer(new LogoStore($this->uploadDir)); } public function testPngIsGeneratedInRequestedSize(): void { $png = $this->renderer()->png('https://kurz.test/messe2026', new QrOptions(size: 512, margin: 4)); self::assertStringStartsWith("\x89PNG", $png); $info = getimagesizefromstring($png); self::assertIsArray($info); self::assertSame(512, $info[0]); self::assertSame(512, $info[1]); } /** * @dataProvider sizeProvider */ public function testAllOfferedSizesWork(int $size): void { $info = getimagesizefromstring( $this->renderer()->png('https://kurz.test/abc', new QrOptions(size: $size)), ); self::assertIsArray($info); self::assertSame($size, $info[0]); } /** * @return list */ public static function sizeProvider(): array { return array_map(static fn (int $size): array => [$size], QrOptions::SIZES); } public function testSvgIsVectorAndCarriesTheColors(): void { $svg = $this->renderer()->svg( 'https://kurz.test/messe2026', new QrOptions(foreground: '#0b4f9e', background: '#fff7ed', size: 1024), ); self::assertStringContainsString('renderer()->png($data, new QrOptions(size: 512, margin: 0)); $withMargin = $this->renderer()->png($data, new QrOptions(size: 512, margin: 10)); // Beide behalten die Zielgroesse ... self::assertSame(512, (getimagesizefromstring($withoutMargin) ?: [0])[0]); self::assertSame(512, (getimagesizefromstring($withMargin) ?: [0])[0]); // ... unterscheiden sich aber im Bildinhalt. self::assertNotSame($withoutMargin, $withMargin); } public function testLogoForcesErrorCorrectionLevelH(): void { $logo = $this->uploadDir . '/logo.png'; $this->writePngLogo($logo); $options = (new QrOptions(errorCorrection: 'L'))->withLogoPath('logo.png'); self::assertSame('H', $options->errorCorrection); $png = $this->renderer()->png('https://kurz.test/mitlogo', $options); self::assertStringStartsWith("\x89PNG", $png); } public function testSvgLogoIsEmbeddedInSvgOutput(): void { file_put_contents( $this->uploadDir . '/logo.svg', '' . '', ); $svg = $this->renderer()->svg( 'https://kurz.test/mitlogo', (new QrOptions())->withLogoPath('logo.svg'), ); self::assertStringContainsString('renderer()->png($data, new QrOptions(foreground: '#000000')), $this->renderer()->png($data, new QrOptions(foreground: '#0b4f9e')), ); } public function testOptionsAreClampedToValidRanges(): void { $options = new QrOptions( foreground: 'unsinn', size: 999, margin: 99, errorCorrection: 'X', logoScale: 500, ); self::assertSame('#000000', $options->foreground); self::assertSame(512, $options->size); self::assertSame(QrOptions::MAX_MARGIN, $options->margin); self::assertSame('M', $options->errorCorrection); self::assertSame(QrOptions::MAX_LOGO_SCALE, $options->logoScale); } private function writePngLogo(string $path): void { $image = imagecreatetruecolor(120, 120); self::assertNotFalse($image); imagefill($image, 0, 0, (int) imagecolorallocate($image, 255, 0, 0)); imagepng($image, $path); } }