From 019d932d35583a2915aef609be581dbcad3a22c9 Mon Sep 17 00:00:00 2001 From: smiley Date: Sun, 24 Dec 2017 07:54:47 +0100 Subject: [PATCH] :sparkles: more tests & cleanup --- src/Output/QRImage.php | 141 +++++++++++++++++++------- src/Output/QRMarkup.php | 30 +++--- src/Output/QROutputAbstract.php | 36 ++----- src/Output/QRString.php | 35 ++++--- src/QRCode.php | 2 - tests/Output/QRImageTest.php | 52 ++++++++++ tests/Output/QRMarkupTest.php | 64 ++++++++++++ tests/Output/QROutputTestAbstract.php | 67 ++++++++++++ tests/Output/QRStringTest.php | 42 ++++++++ tests/QRCodeTest.php | 24 ++--- tests/Traits/QRAuthenticatorTest.php | 31 ++++++ 11 files changed, 417 insertions(+), 107 deletions(-) create mode 100644 tests/Output/QRImageTest.php create mode 100644 tests/Output/QRMarkupTest.php create mode 100644 tests/Output/QROutputTestAbstract.php create mode 100644 tests/Output/QRStringTest.php create mode 100644 tests/Traits/QRAuthenticatorTest.php diff --git a/src/Output/QRImage.php b/src/Output/QRImage.php index c4de43939..fa831f683 100644 --- a/src/Output/QRImage.php +++ b/src/Output/QRImage.php @@ -12,14 +12,18 @@ namespace chillerlan\QRCode\Output; -use chillerlan\QRCode\Data\QRMatrix; -use chillerlan\QRCode\QRCode; +use chillerlan\QRCode\{QRCode, Data\QRMatrix}; /** * Converts the matrix into images, raw or base64 output */ class QRImage extends QROutputAbstract{ + const transparencyTypes = [ + QRCode::OUTPUT_IMAGE_PNG, + QRCode::OUTPUT_IMAGE_GIF, + ]; + protected $moduleValues = [ // light QRMatrix::M_DATA => [255, 255, 255], @@ -42,40 +46,51 @@ class QRImage extends QROutputAbstract{ QRMatrix::M_TEST << 8 => [0, 0, 0], ]; + /** + * @see imagecreatetruecolor() + * @var resource + */ + protected $image; + + /** + * @var int + */ + protected $scale; + + /** + * @var int + */ + protected $length; + + /** + * @see imagecolorallocate() + * @var int + */ + protected $background; + /** * @return string + * @throws \chillerlan\QRCode\Output\QRCodeOutputException */ public function dump():string{ - $scale = $this->options->scale; - $length = $this->moduleCount * $scale; - $image = imagecreatetruecolor($length, $length); - $background = imagecolorallocate($image, ...$this->options->imageTransparencyBG); - $moduleValues = is_array($this->options->moduleValues[QRMatrix::M_DATA]) + + if($this->options->cachefile !== null && !is_writable(dirname($this->options->cachefile))){ + throw new QRCodeOutputException('Could not write data to cache file: '.$this->options->cachefile); + } + + $this->setImage(); + + $moduleValues = is_array($this->options->moduleValues[$this->matrix::M_DATA]) ? $this->options->moduleValues // @codeCoverageIgnore : $this->moduleValues; - if((bool)$this->options->imageTransparent && in_array($this->options->outputType, [QRCode::OUTPUT_IMAGE_PNG, QRCode::OUTPUT_IMAGE_GIF,], true)){ - imagecolortransparent($image, $background); - } - - imagefilledrectangle($image, 0, 0, $length, $length, $background); - foreach($this->matrix->matrix() as $y => $row){ foreach($row as $x => $pixel){ - $color = imagecolorallocate($image, ...$moduleValues[$pixel]); - - imagefilledrectangle($image, $x * $scale, $y * $scale, ($x + 1) * $scale - 1, ($y + 1) * $scale - 1, $color); + $this->setPixel($x, $y, imagecolorallocate($this->image, ...$moduleValues[$pixel])); } } - ob_start(); - - call_user_func_array([$this, $this->options->outputType ?? QRCode::OUTPUT_IMAGE_PNG], [&$image]); - - $imageData = ob_get_contents(); - imagedestroy($image); - - ob_end_clean(); + $imageData = $this->dumpImage(); if((bool)$this->options->imageBase64){ $imageData = 'data:image/'.$this->options->outputType.';base64,'.base64_encode($imageData); @@ -85,34 +100,90 @@ class QRImage extends QROutputAbstract{ } /** - * @param $image + * @return void */ - protected function png(&$image){ + protected function setImage(){ + $this->scale = $this->options->scale; + $this->length = $this->moduleCount * $this->scale; + $this->image = imagecreatetruecolor($this->length, $this->length); + $this->background = imagecolorallocate($this->image, ...$this->options->imageTransparencyBG); + + if((bool)$this->options->imageTransparent && in_array($this->options->outputType, $this::transparencyTypes, true)){ + imagecolortransparent($this->image, $this->background); + } + + imagefilledrectangle($this->image, 0, 0, $this->length, $this->length, $this->background); + } + + /** + * @param $x + * @param $y + * @param $color + * @return void + */ + protected function setPixel($x, $y, $color){ + imagefilledrectangle( + $this->image, + $x * $this->scale, + $y * $this->scale, + ($x + 1) * $this->scale - 1, + ($y + 1) * $this->scale - 1, + $color + ); + } + + /** + * @return string + * @throws \chillerlan\QRCode\Output\QRCodeOutputException + */ + protected function dumpImage():string { + ob_start(); + + try{ + call_user_func([$this, $this->options->outputType ?? QRCode::OUTPUT_IMAGE_PNG]); + } + // not going to cover edge cases + // @codeCoverageIgnoreStart + catch(\Exception $e){ + throw new QRCodeOutputException($e->getMessage()); + } + // @codeCoverageIgnoreEnd + + $imageData = ob_get_contents(); + imagedestroy($this->image); + + ob_end_clean(); + + return $imageData; + } + + /** + * @return void + */ + protected function png(){ imagepng( - $image, + $this->image, $this->options->cachefile, in_array($this->options->pngCompression, range(-1, 9), true) ? $this->options->pngCompression : -1 ); - } /** * Jiff - like... JitHub! - * - * @param $image + * @return void */ - protected function gif(&$image){ - imagegif($image, $this->options->cachefile); + protected function gif(){ + imagegif($this->image, $this->options->cachefile); } /** - * @param $image + * @return void */ - protected function jpg(&$image){ + protected function jpg(){ imagejpeg( - $image, + $this->image, $this->options->cachefile, in_array($this->options->jpegQuality, range(0, 100), true) ? $this->options->jpegQuality diff --git a/src/Output/QRMarkup.php b/src/Output/QRMarkup.php index d32944b33..7b191310a 100644 --- a/src/Output/QRMarkup.php +++ b/src/Output/QRMarkup.php @@ -23,13 +23,20 @@ class QRMarkup extends QROutputAbstract{ * @return string */ public function dump(){ - switch($this->options->outputType){ - case QRCode::OUTPUT_MARKUP_HTML: - return $this->toHTML(); - case QRCode::OUTPUT_MARKUP_SVG : - default: - return $this->toSVG(); + + if($this->options->cachefile !== null && !is_writable(dirname($this->options->cachefile))){ + throw new QRCodeOutputException('Could not write data to cache file: '.$this->options->cachefile); } + + $data = $this->options->outputType === QRCode::OUTPUT_MARKUP_HTML + ? $this->toHTML() + : $this->toSVG(); + + if($this->options->cachefile !== null){ + $this->saveToFile($data); + } + + return $data; } /** @@ -45,14 +52,11 @@ class QRMarkup extends QROutputAbstract{ $html .= ''; } - $html .= ''; - $html .= $this->options->eol; + $html .= ''.$this->options->eol; } if($this->options->cachefile){ - $html = ''.$this->options->eol.$html.''; - - return $this->saveToFile($html); + return ''.$this->options->eol.$html.''; } return $html; @@ -117,9 +121,7 @@ class QRMarkup extends QROutputAbstract{ // if saving to file, append the correct headers if($this->options->cachefile){ - $svg = ''.$this->options->eol.$svg; - - return $this->saveToFile($svg); + return ''.$this->options->eol.$svg; } return $svg; diff --git a/src/Output/QROutputAbstract.php b/src/Output/QROutputAbstract.php index fd7bfd35a..498b4bac6 100644 --- a/src/Output/QROutputAbstract.php +++ b/src/Output/QROutputAbstract.php @@ -12,9 +12,7 @@ namespace chillerlan\QRCode\Output; -use chillerlan\QRCode\{ - Data\QRMatrix, QROptions -}; +use chillerlan\QRCode\{Data\QRMatrix, QROptions}; /** * @@ -41,36 +39,22 @@ abstract class QROutputAbstract implements QROutputInterface{ * * @param \chillerlan\QRCode\QROptions $options * @param \chillerlan\QRCode\Data\QRMatrix $matrix - * - * @throws \chillerlan\QRCode\Output\QRCodeOutputException */ public function __construct(QROptions $options, QRMatrix $matrix){ - $this->options = $options; - - $this->moduleCount = $matrix->size(); - - if($this->moduleCount < 21){ // minimum QR modules @todo: quet zone - throw new QRCodeOutputException('Invalid matrix!'); - } - - $this->matrix = $matrix; + $this->options = $options; + $this->matrix = $matrix; + $this->moduleCount = $this->matrix->size(); } /** - * @param string $data + * @see file_put_contents() * - * @return bool - * @throws \chillerlan\QRCode\Output\QRCodeOutputException + * @param string $data + + * @return bool|int */ - protected function saveToFile(string $data):bool { - - try{ - return (bool)file_put_contents($this->options->cachefile, $data); - } - catch(\Exception $e){ - throw new QRCodeOutputException('Could not write data to cache file: '.$e->getMessage()); - } - + protected function saveToFile(string $data) { + return file_put_contents($this->options->cachefile, $data); } } diff --git a/src/Output/QRString.php b/src/Output/QRString.php index 65a6e3474..8cb367a24 100644 --- a/src/Output/QRString.php +++ b/src/Output/QRString.php @@ -24,39 +24,48 @@ class QRString extends QROutputAbstract{ */ public function dump():string{ - switch($this->options->outputType){ - case QRCode::OUTPUT_STRING_TEXT: - return $this->toString(); - case QRCode::OUTPUT_STRING_JSON: - default: - return json_encode($this->matrix->matrix()); + $data = $this->options->outputType === QRCode::OUTPUT_STRING_JSON + ? json_encode($this->matrix->matrix()) + : $this->toString(); + + if($this->options->cachefile !== null){ + + if(!is_writable(dirname($this->options->cachefile))){ + throw new QRCodeOutputException('Could not write data to cache file: '.$this->options->cachefile); + } + + $this->saveToFile($data); } + return $data; } /** * @return string */ protected function toString():string{ - $str = ''; + $str = []; foreach($this->matrix->matrix() as $row){ + $r = []; + foreach($row as $col){ $col = $this->options->moduleValues[$col]; - + // fallback if(is_bool($col) || !is_string($col)){ - $col = $col ? $this->options->textDark : $this->options->textLight; + $col = $col + ? $this->options->textDark + : $this->options->textLight; } - - $str .= $col; + $r[] = $col; } - $str .= $this->options->eol; + $str[] = implode('', $r); } - return $str; + return implode($this->options->eol, $str); } } diff --git a/src/QRCode.php b/src/QRCode.php index bdd631c6f..5e0b22aff 100644 --- a/src/QRCode.php +++ b/src/QRCode.php @@ -321,8 +321,6 @@ class QRCode{ /** * a dummy * - * @codeCoverageIgnore - * * @param $data * * @return bool diff --git a/tests/Output/QRImageTest.php b/tests/Output/QRImageTest.php new file mode 100644 index 000000000..1ef0a9f48 --- /dev/null +++ b/tests/Output/QRImageTest.php @@ -0,0 +1,52 @@ + + * @copyright 2017 Smiley + * @license MIT + */ + +namespace chillerlan\QRCodeTest\Output; + +use chillerlan\QRCode\Output\QRImage; +use chillerlan\QRCode\QRCode; + +class QRImageTest extends QROutputTestAbstract{ + + protected $FQCN = QRImage::class; + + public function types(){ + return [ + [QRCode::OUTPUT_IMAGE_PNG], + [QRCode::OUTPUT_IMAGE_GIF], + [QRCode::OUTPUT_IMAGE_JPG], + ]; + } + + /** + * @dataProvider types + * @param $type + */ + public function testImageOutput($type){ + $this->options->outputType = $type; + $this->options->cachefile = $this::cachefile.$type; + $this->setOutputInterface(); + $this->outputInterface->dump(); + + $this->options->cachefile = null; + $this->options->imageBase64 = false; + $this->setOutputInterface(); + $img = $this->outputInterface->dump(); + + if($type === QRCode::OUTPUT_IMAGE_JPG){ // jpeg encoding may cause different results + $this->markAsRisky(); + } + + $this->assertSame($img, file_get_contents($this::cachefile.$type)); + } + +} diff --git a/tests/Output/QRMarkupTest.php b/tests/Output/QRMarkupTest.php new file mode 100644 index 000000000..caa646679 --- /dev/null +++ b/tests/Output/QRMarkupTest.php @@ -0,0 +1,64 @@ + + * @copyright 2017 Smiley + * @license MIT + */ + +namespace chillerlan\QRCodeTest\Output; + +use chillerlan\QRCode\Output\QRMarkup; +use chillerlan\QRCode\QRCode; + +class QRMarkupTest extends QROutputTestAbstract{ + + protected $FQCN = QRMarkup::class; + + public function types(){ + return [ + [QRCode::OUTPUT_MARKUP_HTML], + [QRCode::OUTPUT_MARKUP_SVG], + ]; + } + + /** + * @dataProvider types + * @param $type + */ + public function testMarkupOutputFile($type){ + $this->options->outputType = $type; + $this->options->cachefile = $this::cachefile.$type; + $this->setOutputInterface(); + $data = $this->outputInterface->dump(); + + $this->assertSame($data, file_get_contents($this->options->cachefile)); + } + + /** + * @dataProvider types + * @param $type + */ + public function testMarkupOutput($type){ + $this->options->outputType = $type; + $this->setOutputInterface(); + + $expected = explode($this->options->eol, file_get_contents($this::cachefile.$type)); + // cut off the doctype & head + array_shift($expected); + + if($type === QRCode::OUTPUT_MARKUP_HTML){ + // cut off the tag + array_pop($expected); + } + + $expected = implode($this->options->eol, $expected); + + $this->assertSame(trim($expected), trim($this->outputInterface->dump())); + } + +} diff --git a/tests/Output/QROutputTestAbstract.php b/tests/Output/QROutputTestAbstract.php new file mode 100644 index 000000000..5311d35e2 --- /dev/null +++ b/tests/Output/QROutputTestAbstract.php @@ -0,0 +1,67 @@ + + * @copyright 2017 Smiley + * @license MIT + */ + +namespace chillerlan\QRCodeTest\Output; + +use chillerlan\QRCode\Data\Byte; +use chillerlan\QRCode\Output\QROutputInterface; +use chillerlan\QRCode\QROptions; +use chillerlan\QRCodeTest\QRTestAbstract; + +/** + */ +abstract class QROutputTestAbstract extends QRTestAbstract{ + + const cachefile = __DIR__.'/output_test.'; + + /** + * @var \chillerlan\QRCode\Output\QROutputInterface + */ + protected $outputInterface; + + /** + * @var \chillerlan\QRCode\QROptions + */ + protected $options; + + /** + * @var \chillerlan\QRCode\Data\QRMatrix + */ + protected $matrix; + + protected function setUp(){ + parent::setUp(); + + $this->options = new QROptions; + $this->setOutputInterface(); + } + + protected function setOutputInterface(){ + $this->outputInterface = $this->reflection->newInstanceArgs([$this->options, (new Byte($this->options, 'testdata'))->initMatrix(0)]); + return $this->outputInterface; + } + + public function testInstance(){ + $this->assertInstanceOf(QROutputInterface::class, $this->outputInterface); + } + + /** + * @expectedException \chillerlan\QRCode\Output\QRCodeOutputException + * @expectedExceptionMessage Could not write data to cache file: /foo + */ + public function testSaveException(){ + $this->options->cachefile = '/foo'; + $this->setOutputInterface(); + $this->outputInterface->dump(); + } + +} diff --git a/tests/Output/QRStringTest.php b/tests/Output/QRStringTest.php new file mode 100644 index 000000000..f023b8308 --- /dev/null +++ b/tests/Output/QRStringTest.php @@ -0,0 +1,42 @@ + + * @copyright 2017 Smiley + * @license MIT + */ + +namespace chillerlan\QRCodeTest\Output; + +use chillerlan\QRCode\Output\QRString; +use chillerlan\QRCode\QRCode; + +class QRStringTest extends QROutputTestAbstract{ + + protected $FQCN = QRString::class; + + public function types(){ + return [ + [QRCode::OUTPUT_STRING_JSON], + [QRCode::OUTPUT_STRING_TEXT], + ]; + } + + /** + * @dataProvider types + * @param $type + */ + public function testStringOutput($type){ + $this->options->outputType = $type; + $this->options->cachefile = $this::cachefile.$type; + $this->setOutputInterface(); + $data = $this->outputInterface->dump(); + + $this->assertSame($data, file_get_contents($this->options->cachefile)); + } + +} diff --git a/tests/QRCodeTest.php b/tests/QRCodeTest.php index 48cec5575..b1b2c06ef 100644 --- a/tests/QRCodeTest.php +++ b/tests/QRCodeTest.php @@ -30,21 +30,6 @@ class QRCodeTest extends QRTestAbstract{ $this->qrcode = $this->reflection->newInstance(); } - /* - public function optionsDataProvider(){ - return [ - [QROptions::class], -# [], - ]; - } - - public function testInstance($options){ - $q = $this->reflection->newInstanceArgs([new $options]); - $this->assertInstanceOf($this->FQCN, $q); -# print_r($q->render('test')); - } - */ - public function testIsNumber(){ $this->assertTrue($this->qrcode->isNumber('0123456789')); $this->assertFalse($this->qrcode->isNumber('ABC')); @@ -100,7 +85,12 @@ class QRCodeTest extends QRTestAbstract{ $this->qrcode->setOptions(new QROptions(['outputType' => 'foo']))->render('test'); } - - + /** + * @expectedException \chillerlan\QRCode\Data\QRCodeDataException + * @expectedExceptionMessage QRCode::getMatrix() No data given. + */ + public function testGetMatrixException(){ + $this->qrcode->getMatrix(''); + } } diff --git a/tests/Traits/QRAuthenticatorTest.php b/tests/Traits/QRAuthenticatorTest.php new file mode 100644 index 000000000..04e725f20 --- /dev/null +++ b/tests/Traits/QRAuthenticatorTest.php @@ -0,0 +1,31 @@ + + * @copyright 2017 Smiley + * @license MIT + */ + +namespace chillerlan\QRCodeTest\Traits; + +use chillerlan\QRCode\Traits\QRAuthenticator; +use chillerlan\QRCodeTest\QRTestAbstract; + +class QRAuthenticatorTest extends QRTestAbstract{ + use QRAuthenticator; + + protected function setUp(){} + + public function testGetURI(){ + $this->authenticatorSecret = 'SECRETTEST234567'; + + $expected = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAOEAAADhCAIAAACx0UUtAAAABnRSTlMA/wD/AP83WBt9AAAFh0lEQVR4nO3dy47rNhBF0XSQ///lm0lGGjAg+NC2e61pW5KTe0C4QFXx58+fP39B2N9vfwH4HzJKnYxSJ6PUySh1MkqdjFIno9TJKHUySp2MUiej1MkodTJKnYxSJ6PUySh1MkqdjFL3z8rFPz8/u77H2KPpauW5U7daee64Uexxq6musvG1U3d+619winWUOhmlTkapk1Hqlmqmh43jJKZKmalbrRQrK8/daKpImvrvvfYvOMU6Sp2MUiej1MkodTtrpoeNuzJTD5oqC6YKjnO3Gpu6dqUoXHnuuQGM1lHqZJQ6GaVORqk7WDNds7E6Gd/5YWN1Mr7zQ+R1u2uso9TJKHUySp2MUvcNNdOUc2+ybdxJ2tje9AWso9TJKHUySp2MUnewZrr24/2thp5z8yPOtXOd6986xzpKnYxSJ6PUySh1O2uma2+FrRRJ1/46dq0XakrzvT7rKHUySp2MUiej1P1E9hKmnGv3WdmjGj/orW/1BW/uWUepk1HqZJQ6GaVuqWba+HLatbLg2qC8jTaeRzW+87kxFs5n4pvJKHUySp2MUrezZrp28uxKofPw1vG4Y28Vo9dGDk6xjlIno9TJKHUySt29uXlTP883jnWYuvP4w2PnTrzdOE6iufk3Zh2lTkapk1HqZJS6yqzxlXlu5ybjXXvNb8rGUmblW117NdE6Sp2MUiej1MkodUs108Yf/g9vzb6bcu70pnPj+869P3mOdZQ6GaVORqmTUeqWaqZzo6mvlUHn3pF7fPjcrI0VzY20B+sodTJKnYxSJ6PU7Zw1fm0E+DnX2ozGt3r4xB6slQc9WEepk1HqZJQ6GaXu4Ny8qWsfrk3ze2tAeKRIeuvaKdZR6mSUOhmlTkap2zkDYmUWw+PajZXNW81PK3MCp3xii9IU6yh1MkqdjFIno9TtnAGxUhZc26Pa2Bu08ayjc3O7z9WI1zbwrKPUySh1MkqdjFK3cwbExo2lsY1HPUWcOydp5bkrQy6cz8QvIqPUySh1MkrdvfOZru0znZv2du1Fvre81fs1Zh2lTkapk1HqZJS6e+czXZv2tnHLKtJ0NXXt1F+nvFVCWUepk1HqZJQ6GaXu4AyIh3On+2ystz7x5Kfxc69NE9fPxO8lo9TJKHUySt3Bd/NW6o+VomHjHLmHje/mrbRzTXnr1Cj9TPwiMkqdjFIno9R9//lMY2/N6zu3VzS2sdnL+UzwHxmlTkapk1HqDs4an9oNethYNJzbdjp357Frdc+5ncIp1lHqZJQ6GaVORqk7uM907XDVt1qjVh608Wuc64VaoZ+JX0RGqZNR6mSUup0108O5bphzfUUfMZj8XEU1vnbs3MuW1lHqZJQ6GaVORqm7188U2f/Y+Nxzw7bP9RWNvbVTOGYdpU5GqZNR6mSUuoNz86Z+cV+rqM6Nd3vrhbqNhc659qYV1lHqZJQ6GaVORqlb2md6y7UX6sbXbmznOjdzb+pBU7cas8/ELyKj1MkodTJK3dI+07VJcY8f4M3Z2xu/xtSHx/9zxne+Np9QPxPfTEapk1HqZJS6ne/mvVVDjK3srEQ2lqZcOzbp2qh16yh1MkqdjFIno9RV+pk2VjZThc74w1ObNBtNVVQb+8aarKPUySh1MkqdjFJ3sGa65tpZq5HBgOemp48f9NYUQesodTJKnYxSJ6PUfWTNtPGNsoe3zuFdaUIaf42VD187JWvMOkqdjFIno9TJKHUHa6a3ji9a0RxqvvKq3rkHTf11hXWUOhmlTkapk1HqdtZMb/XKbNzgWfnwNVMbPG/VeRvDYB2lTkapk1HqZJS6jzyfiV/FOkqdjFIno9TJKHUySp2MUiej1MkodTJKnYxSJ6PUySh1MkqdjFIno9TJKHUySp2MUiej1P0LqTXB8AO7a6AAAAAASUVORK5CYII='; + + $this->assertSame($expected, $this->getURIQRCode('testlabel', 'example.com')); + } + +}