Explorar o código

:bath: test overhaul

smiley hai 1 mes
pai
achega
c99f5d4022

+ 6 - 12
phpstan-baseline.neon

@@ -102,6 +102,12 @@ parameters:
 			count: 1
 			path: examples/intervention-image.php
 
+		-
+			rawMessage: 'Function parseHexValue() return type has no value type specified in iterable type array.'
+			identifier: missingType.iterableValue
+			count: 1
+			path: examples/qrcode-interactive.php
+
 		-
 			rawMessage: 'Method RandomDotsSVGOutput::collectModules() should return array<int, mixed> but returns array<int|string, list<string>>.'
 			identifier: return.type
@@ -234,24 +240,12 @@ parameters:
 			count: 1
 			path: src/QRCode.php
 
-		-
-			rawMessage: 'Method chillerlan\QRCodeTest\Common\MaskPatternTest::assertMask() has parameter $expected with no value type specified in iterable type array.'
-			identifier: missingType.iterableValue
-			count: 1
-			path: tests/Common/MaskPatternTest.php
-
 		-
 			rawMessage: 'Method chillerlan\QRCodeTest\Common\MaskPatternTest::maskPatternProvider() return type has no value type specified in iterable type array.'
 			identifier: missingType.iterableValue
 			count: 1
 			path: tests/Common/MaskPatternTest.php
 
-		-
-			rawMessage: 'Method chillerlan\QRCodeTest\Common\MaskPatternTest::testMask() has parameter $expected with no value type specified in iterable type array.'
-			identifier: missingType.iterableValue
-			count: 1
-			path: tests/Common/MaskPatternTest.php
-
 		-
 			rawMessage: 'Parameter #1 $string of static method chillerlan\QRCode\Data\QRDataModeInterface::validateString() expects string, string|false given.'
 			identifier: argument.type

+ 10 - 17
tests/Common/BitBufferTest.php

@@ -13,7 +13,7 @@ namespace chillerlan\QRCodeTest\Common;
 
 use chillerlan\QRCode\QRCodeException;
 use chillerlan\QRCode\Common\{BitBuffer, Mode};
-use PHPUnit\Framework\Attributes\DataProvider;
+use PHPUnit\Framework\Attributes\{Test, TestWith};
 use PHPUnit\Framework\TestCase;
 
 /**
@@ -27,28 +27,21 @@ final class BitBufferTest extends TestCase{
 		$this->bitBuffer = new BitBuffer;
 	}
 
-	/**
-	 * @phpstan-return array<string, array{0: int, 1: int}>
-	 */
-	public static function bitProvider():array{
-		return [
-			'number'   => [Mode::NUMBER, 16],
-			'alphanum' => [Mode::ALPHANUM, 32],
-			'byte'     => [Mode::BYTE, 64],
-			'kanji'    => [Mode::KANJI, 128],
-			'hanzi'    => [Mode::HANZI, 208],
-		];
-	}
-
-	#[DataProvider('bitProvider')]
-	public function testPut(int $data, int $expected):void{
+	#[Test]
+	#[TestWith([Mode::NUMBER, 16], 'number')]
+	#[TestWith([Mode::ALPHANUM, 32], 'alphanum')]
+	#[TestWith([Mode::BYTE, 64], 'byte')]
+	#[TestWith([Mode::KANJI, 128], 'kanji')]
+	#[TestWith([Mode::HANZI, 208], 'hanzi')]
+	public function put(int $data, int $expected):void{
 		$this->bitBuffer->put($data, 4);
 
 		$this::assertSame($expected, $this->bitBuffer->getBuffer()[0]);
 		$this::assertSame(4, $this->bitBuffer->getLength());
 	}
 
-	public function testReadException():void{
+	#[Test]
+	public function readException():void{
 		$this->expectException(QRCodeException::class);
 		$this->expectExceptionMessage('invalid $numBits');
 

+ 7 - 11
tests/Common/ECICharsetTest.php

@@ -13,20 +13,15 @@ namespace chillerlan\QRCodeTest\Common;
 
 use chillerlan\QRCode\QRCodeException;
 use chillerlan\QRCode\Common\ECICharset;
-use PHPUnit\Framework\Attributes\DataProvider;
+use PHPUnit\Framework\Attributes\{DataProvider, Test, TestWith};
 use PHPUnit\Framework\TestCase;
 
 final class ECICharsetTest extends TestCase{
 
-	/**
-	 * @phpstan-return array<int, array{0: int}>
-	 */
-	public static function invalidIdProvider():array{
-		return [[-1], [1000000]];
-	}
-
-	#[DataProvider('invalidIdProvider')]
-	public function testInvalidDataException(int $id):void{
+	#[Test]
+	#[TestWith([-1])]
+	#[TestWith([1000000])]
+	public function invalidDataException(int $id):void{
 		$this->expectException(QRCodeException::class);
 		$this->expectExceptionMessage('invalid charset id:');
 
@@ -46,8 +41,9 @@ final class ECICharsetTest extends TestCase{
 		return $params;
 	}
 
+	#[Test]
 	#[DataProvider('encodingProvider')]
-	public function testGetName(int $id, string|null $name = null):void{
+	public function getName(int $id, string|null $name = null):void{
 		$eciCharset = new ECICharset($id);
 
 		$this::assertSame($id, $eciCharset->getID());

+ 12 - 5
tests/Common/EccLevelTest.php

@@ -13,6 +13,7 @@ namespace chillerlan\QRCodeTest\Common;
 
 use chillerlan\QRCode\QRCodeException;
 use chillerlan\QRCode\Common\{EccLevel, MaskPattern};
+use PHPUnit\Framework\Attributes\Test;
 use PHPUnit\Framework\TestCase;
 
 /**
@@ -20,37 +21,43 @@ use PHPUnit\Framework\TestCase;
  */
 final class EccLevelTest extends TestCase{
 
-	public function testConstructInvalidEccException():void{
+	#[Test]
+	public function constructInvalidEccException():void{
 		$this->expectException(QRCodeException::class);
 		$this->expectExceptionMessage('invalid ECC level');
 
 		new EccLevel(69);
 	}
 
-	public function testToString():void{
+	#[Test]
+	public function eccToString():void{
 		$ecc = new EccLevel(EccLevel::L);
 
 		$this::assertSame('L', (string)$ecc);
 	}
 
-	public function testGetLevel():void{
+	#[Test]
+	public function getLevel():void{
 		$ecc = new EccLevel(EccLevel::L);
 
 		$this::assertSame(EccLevel::L, $ecc->getLevel());
 	}
 
-	public function testGetOrdinal():void{
+	#[Test]
+	public function getOrdinal():void{
 		$ecc = new EccLevel(EccLevel::L);
 
 		$this::assertSame(0, $ecc->getOrdinal());
 	}
 
-	public function testGetformatPattern():void{
+	#[Test]
+	public function getformatPattern():void{
 		$ecc = new EccLevel(EccLevel::Q);
 
 		$this::assertSame(0b010010010110100, $ecc->getformatPattern(new MaskPattern(4)));
 	}
 
+	#[Test]
 	public function getMaxBits():void{
 		$ecc = new EccLevel(EccLevel::Q);
 

+ 13 - 7
tests/Common/MaskPatternTest.php

@@ -16,7 +16,7 @@ namespace chillerlan\QRCodeTest\Common;
 
 use chillerlan\QRCode\QRCodeException;
 use chillerlan\QRCode\Common\MaskPattern;
-use PHPUnit\Framework\Attributes\DataProvider;
+use PHPUnit\Framework\Attributes\{DataProvider, Test};
 use PHPUnit\Framework\TestCase;
 use Closure;
 
@@ -100,8 +100,9 @@ final class MaskPatternTest extends TestCase{
 	 *
 	 * @param int[][] $expected
 	 */
+	#[Test]
 	#[DataProvider('maskPatternProvider')]
-	public function testMask(int $pattern, array $expected):void{
+	public function mask(int $pattern, array $expected):void{
 		$maskPattern = new MaskPattern($pattern);
 
 		$this::assertTrue($this->assertMask($maskPattern->getMask(), $expected));
@@ -126,14 +127,16 @@ final class MaskPatternTest extends TestCase{
 	/**
 	 * Tests if an exception is thrown on an incorrect mask pattern
 	 */
-	public function testInvalidMaskPatternException():void{
+	#[Test]
+	public function invalidMaskPatternException():void{
 		$this->expectException(QRCodeException::class);
 		$this->expectExceptionMessage('invalid mask pattern');
 
 		new MaskPattern(42);
 	}
 
-	public function testPenaltyRule1():void{
+	#[Test]
+	public function penaltyRule1():void{
 		// horizontal
 		$this::assertSame(0, MaskPattern::testRule1([[false, false, false, false]], 1, 4));
 		$this::assertSame(3, MaskPattern::testRule1([[false, false, false, false, false, true]], 1, 6));
@@ -144,14 +147,16 @@ final class MaskPatternTest extends TestCase{
 		$this::assertSame(4, MaskPattern::testRule1([[false], [false], [false], [false], [false], [false]], 6, 1));
 	}
 
-	public function testPenaltyRule2():void{
+	#[Test]
+	public function penaltyRule2():void{
 		$this::assertSame(0, MaskPattern::testRule2([[false]], 1, 1));
 		$this::assertSame(0, MaskPattern::testRule2([[false, false], [false, true]], 2, 2));
 		$this::assertSame(3, MaskPattern::testRule2([[false, false], [false, false]], 2, 2));
 		$this::assertSame(12, MaskPattern::testRule2([[false, false, false], [false, false, false], [false, false, false]], 3, 3)); // phpcs:ignore
 	}
 
-	public function testPenaltyRule3():void{
+	#[Test]
+	public function penaltyRule3():void{
 		// horizontal
 		$this::assertSame(40, MaskPattern::testRule3([[false, false, false, false, true, false, true, true, true, false, true]], 1, 11)); // phpcs:ignore
 		$this::assertSame(40, MaskPattern::testRule3([[true, false, true, true, true, false, true, false, false, false, false]], 1, 11)); // phpcs:ignore
@@ -162,7 +167,8 @@ final class MaskPatternTest extends TestCase{
 		$this::assertSame(0, MaskPattern::testRule3([[true], [false], [true], [true], [true], [false], [true]], 7, 1));
 	}
 
-	public function testPenaltyRule4():void{
+	#[Test]
+	public function penaltyRule4():void{
 		$this::assertSame(100, MaskPattern::testRule4([[false]], 1, 1));
 		$this::assertSame(0, MaskPattern::testRule4([[false, true]], 1, 2));
 		$this::assertSame(30, MaskPattern::testRule4([[false, true, true, true, true, false]], 1, 6));

+ 14 - 19
tests/Common/ModeTest.php

@@ -13,7 +13,7 @@ namespace chillerlan\QRCodeTest\Common;
 
 use chillerlan\QRCode\QRCodeException;
 use chillerlan\QRCode\Common\Mode;
-use PHPUnit\Framework\Attributes\DataProvider;
+use PHPUnit\Framework\Attributes\{Test, TestWith};
 use PHPUnit\Framework\TestCase;
 
 /**
@@ -22,34 +22,29 @@ use PHPUnit\Framework\TestCase;
 final class ModeTest extends TestCase{
 
 	/**
-	 * version breakpoints for numeric mode
-	 *
-	 * @phpstan-return array<int, array{0: int, 1: int}>
+	 * Tests the version breakpoints for numeric mode
 	 */
-	public static function versionProvider():array{
-		return [
-			[ 1, 10],
-			[ 9, 10],
-			[10, 12],
-			[26, 12],
-			[27, 14],
-			[40, 14],
-		];
-	}
-
-	#[DataProvider('versionProvider')]
-	public function testGetLengthBitsForVersionBreakpoints(int $version, int $expected):void{
+	#[Test]
+	#[TestWith([ 1, 10], '10 low')]
+	#[TestWith([ 9, 10], '10 high')]
+	#[TestWith([10, 12], '12 low')]
+	#[TestWith([26, 12], '12 high')]
+	#[TestWith([27, 14], '14 low')]
+	#[TestWith([40, 14], '14 high')]
+	public function getLengthBitsForVersionBreakpoints(int $version, int $expected):void{
 		$this::assertSame($expected, Mode::getLengthBitsForVersion(Mode::NUMBER, $version));
 	}
 
-	public function testGetLengthBitsForVersionInvalidModeException():void{
+	#[Test]
+	public function getLengthBitsForVersionInvalidModeException():void{
 		$this->expectException(QRCodeException::class);
 		$this->expectExceptionMessage('invalid mode given');
 
 		Mode::getLengthBitsForVersion(42, 69);
 	}
 
-	public function testGetLengthBitsForVersionInvalidVersionException():void{
+	#[Test]
+	public function getLengthBitsForVersionInvalidVersionException():void{
 		$this->expectException(QRCodeException::class);
 		$this->expectExceptionMessage('invalid version number');
 

+ 17 - 8
tests/Common/VersionTest.php

@@ -13,6 +13,7 @@ namespace chillerlan\QRCodeTest\Common;
 
 use chillerlan\QRCode\QRCodeException;
 use chillerlan\QRCode\Common\{EccLevel, Version};
+use PHPUnit\Framework\Attributes\Test;
 use PHPUnit\Framework\TestCase;
 
 /**
@@ -26,37 +27,45 @@ final class VersionTest extends TestCase{
 		$this->version = new Version(7);
 	}
 
-	public function testToString():void{
+	#[Test]
+	public function versionToString():void{
 		$this::assertSame('7', (string)$this->version);
 	}
 
-	public function testGetVersionNumber():void{
+	#[Test]
+	public function getVersionNumber():void{
 		$this::assertSame(7, $this->version->getVersionNumber());
 	}
 
-	public function testGetDimension():void{
+	#[Test]
+	public function getDimension():void{
 		$this::assertSame(45, $this->version->getDimension());
 	}
 
-	public function testGetVersionPattern():void{
+	#[Test]
+	public function getVersionPattern():void{
 		$this::assertSame(0b000111110010010100, $this->version->getVersionPattern());
 		// no pattern for version < 7
 		$this::assertNull((new Version(6))->getVersionPattern());
 	}
 
-	public function testGetAlignmentPattern():void{
+	#[Test]
+	public function getAlignmentPattern():void{
 		$this::assertSame([6, 22, 38], $this->version->getAlignmentPattern());
 	}
 
-	public function testGetRSBlocks():void{
+	#[Test]
+	public function getRSBlocks():void{
 		$this::assertSame([18, [[2, 14], [4, 15]]], $this->version->getRSBlocks(new EccLevel(EccLevel::Q)));
 	}
 
-	public function testGetTotalCodewords():void{
+	#[Test]
+	public function getTotalCodewords():void{
 		$this::assertSame(196, $this->version->getTotalCodewords());
 	}
 
-	public function testConstructInvalidVersion():void{
+	#[Test]
+	public function constructInvalidVersion():void{
 		$this->expectException(QRCodeException::class);
 		$this->expectExceptionMessage('invalid version given');
 

+ 1 - 2
tests/Data/AlphaNumTest.php

@@ -11,8 +11,7 @@ declare(strict_types=1);
 
 namespace chillerlan\QRCodeTest\Data;
 
-use chillerlan\QRCode\Data\AlphaNum;
-use chillerlan\QRCode\Data\QRDataModeInterface;
+use chillerlan\QRCode\Data\{AlphaNum, QRDataModeInterface};
 
 /**
  * Tests the AlphaNum class

+ 6 - 4
tests/Data/ByteTest.php

@@ -11,8 +11,8 @@ declare(strict_types=1);
 
 namespace chillerlan\QRCodeTest\Data;
 
-use chillerlan\QRCode\Data\Byte;
-use chillerlan\QRCode\Data\QRDataModeInterface;
+use chillerlan\QRCode\Data\{Byte, QRDataModeInterface};
+use PHPUnit\Framework\Attributes\Test;
 
 /**
  * Tests the Byte class
@@ -39,11 +39,13 @@ final class ByteTest extends DataInterfaceTestAbstract{
 		];
 	}
 
-	public function testInvalidDataException():void{
+	#[Test]
+	public function binaryStringInvalid():void{
 		$this::markTestSkipped('N/A (binary mode)');
 	}
 
-	public function testBinaryStringInvalid():void{
+	#[Test]
+	public function invalidDataException():void{
 		$this::markTestSkipped('N/A (binary mode)');
 	}
 

+ 24 - 14
tests/Data/DataInterfaceTestAbstract.php

@@ -11,14 +11,14 @@ declare(strict_types=1);
 
 namespace chillerlan\QRCodeTest\Data;
 
+use chillerlan\QRCode\QROptions;
 use chillerlan\QRCode\Common\{BitBuffer, EccLevel, MaskPattern, Mode, Version};
 use chillerlan\QRCode\Data\{QRCodeDataException, QRData, QRDataModeInterface, QRMatrix};
-use chillerlan\QRCode\QROptions;
 use chillerlan\QRCodeTest\Traits\QRMaxLengthTrait;
-use Exception, Generator;
-use PHPUnit\Framework\Attributes\DataProvider;
-use PHPUnit\Framework\ExpectationFailedException;
 use PHPUnit\Framework\TestCase;
+use PHPUnit\Framework\Attributes\{DataProvider, Test};
+use PHPUnit\Framework\ExpectationFailedException;
+use Exception, Generator;
 use function array_map, hex2bin, mb_strlen, mb_substr, sprintf, str_repeat, strlen, substr;
 
 /**
@@ -49,8 +49,9 @@ abstract class DataInterfaceTestAbstract extends TestCase{
 	/**
 	 * Tests initializing the data matrix
 	 */
+	#[Test]
 	#[DataProvider('maskPatternProvider')]
-	public function testInitMatrix(int $pattern):void{
+	public function initMatrix(int $pattern):void{
 		$maskPattern = new MaskPattern($pattern);
 
 		$this->QRData->setData([$this->dataMode]);
@@ -69,8 +70,9 @@ abstract class DataInterfaceTestAbstract extends TestCase{
 	/**
 	 * Tests if a string is properly validated for the respective data mode
 	 */
+	#[Test]
 	#[DataProvider('stringValidateProvider')]
-	public function testValidateString(string $string, bool $expected):void{
+	public function validateString(string $string, bool $expected):void{
 		$this::assertSame($expected, $this->dataMode::validateString($string));
 
 		// back out on potentially invalid strings
@@ -97,7 +99,8 @@ abstract class DataInterfaceTestAbstract extends TestCase{
 	 *
 	 * @see https://github.com/chillerlan/php-qrcode/issues/182
 	 */
-	public function testBinaryStringInvalid():void{
+	#[Test]
+	public function binaryStringInvalid():void{
 		$this::assertFalse($this->dataMode::validateString(hex2bin('01015989f47dff8e852122117e04c90b9f15defc1c36477b1fe1')));
 	}
 
@@ -113,8 +116,9 @@ abstract class DataInterfaceTestAbstract extends TestCase{
 	/**
 	 * Tests decoding a data segment from a given BitBuffer
 	 */
+	#[Test]
 	#[DataProvider('versionBreakpointProvider')]
-	public function testDecodeSegment(int $version):void{
+	public function decodeSegment(int $version):void{
 		$options          = new QROptions;
 		$options->version = $version;
 
@@ -170,8 +174,9 @@ abstract class DataInterfaceTestAbstract extends TestCase{
 
 	}
 
+	#[Test]
 	#[DataProvider('maxLengthProvider')]
-	public function testMaxLength(Version $version, EccLevel $eccLevel, string $str):void{
+	public function maxLength(Version $version, EccLevel $eccLevel, string $str):void{
 		$options           = new QROptions;
 		$options->version  = $version->getVersionNumber();
 		$options->eccLevel = $eccLevel->getLevel();
@@ -188,8 +193,9 @@ abstract class DataInterfaceTestAbstract extends TestCase{
 	/**
 	 * Tests getting the minimum QR version for the given data
 	 */
+	#[Test]
 	#[DataProvider('maxLengthProvider')]
-	public function testGetMinimumVersion(Version $version, EccLevel $eccLevel, string $str):void{
+	public function getMinimumVersion(Version $version, EccLevel $eccLevel, string $str):void{
 		$options           = new QROptions;
 		$options->version  = Version::AUTO;
 		$options->eccLevel = $eccLevel->getLevel();
@@ -218,8 +224,9 @@ abstract class DataInterfaceTestAbstract extends TestCase{
 	/**
 	 * Tests if an exception is thrown on data overflow
 	 */
+	#[Test]
 	#[DataProvider('maxLengthProvider')]
-	public function testMaxLengthOverflowException(Version $version, EccLevel $eccLevel, string $str, string $str1):void{
+	public function maxLengthOverflowException(Version $version, EccLevel $eccLevel, string $str, string $str1):void{
 		$this->expectException(QRCodeDataException::class);
 		$this->expectExceptionMessage('code length overflow');
 
@@ -234,7 +241,8 @@ abstract class DataInterfaceTestAbstract extends TestCase{
 	/**
 	 * Tests if an exception is thrown when the data exceeds the maximum version while auto-detecting
 	 */
-	public function testGetMinimumVersionException():void{
+	#[Test]
+	public function getMinimumVersionException():void{
 		$this->expectException(QRCodeDataException::class);
 		$this->expectExceptionMessage('data exceeds');
 
@@ -244,7 +252,8 @@ abstract class DataInterfaceTestAbstract extends TestCase{
 	/**
 	 * Tests if an exception is thrown when an invalid character is encountered
 	 */
-	public function testInvalidDataException():void{
+	#[Test]
+	public function invalidDataException():void{
 		$this->expectException(QRCodeDataException::class);
 		$this->expectExceptionMessage('invalid data');
 
@@ -254,7 +263,8 @@ abstract class DataInterfaceTestAbstract extends TestCase{
 	/**
 	 * Tests if an exception is thrown if the given string is empty
 	 */
-	public function testInvalidDataOnEmptyException():void{
+	#[Test]
+	public function invalidDataOnEmptyException():void{
 		$this->expectException(QRCodeDataException::class);
 		$this->expectExceptionMessage('invalid data');
 

+ 14 - 14
tests/Data/ECITest.php

@@ -13,9 +13,9 @@ namespace chillerlan\QRCodeTest\Data;
 
 use chillerlan\QRCode\QROptions;
 use chillerlan\QRCode\Common\{BitBuffer, ECICharset, Mode};
-use chillerlan\QRCode\Data\{Byte, ECI, Hanzi, QRCodeDataException, QRData, QRDataModeInterface};
-use PHPUnit\Framework\Attributes\DataProvider;
+use chillerlan\QRCode\Data\{Byte, ECI, Hanzi, QRCodeDataException, QRData};
 use PHPUnit\Framework\TestCase;
+use PHPUnit\Framework\Attributes\{DataProvider, Test};
 
 /**
  * Tests the ECI class
@@ -42,12 +42,6 @@ final class ECITest extends TestCase{
 		];
 	}
 
-	public function testDataModeInstance():void{
-		$datamode = new ECI($this->testCharset);
-
-		$this::assertInstanceOf(QRDataModeInterface::class, $datamode);
-	}
-
 	/**
 	 * returns versions within the version breakpoints 1-9, 10-26 and 27-40
 	 *
@@ -57,8 +51,9 @@ final class ECITest extends TestCase{
 		return ['1-9' => [7], '10-26' => [15], '27-40' => [30]];
 	}
 
+	#[Test]
 	#[DataProvider('versionBreakpointProvider')]
-	public function testDecodeSegment(int $version):void{
+	public function decodeSegment(int $version):void{
 		$options = new QROptions;
 		$options->version = $version;
 
@@ -75,7 +70,8 @@ final class ECITest extends TestCase{
 		$this::assertSame(self::testData, ECI::decodeSegment($bitBuffer, $options->version));
 	}
 
-	public function testInvalidDataException():void{
+	#[Test]
+	public function invalidDataException():void{
 		$this->expectException(QRCodeDataException::class);
 		$this->expectExceptionMessage('invalid encoding id:');
 
@@ -86,7 +82,8 @@ final class ECITest extends TestCase{
 	 * since the ECI class only accepts integer values,
 	 * we'll use this test to check for the upper end of the accepted input range
 	 */
-	public function testInvalidDataOnEmptyException():void{
+	#[Test]
+	public function invalidDataOnEmptyException():void{
 		$this->expectException(QRCodeDataException::class);
 		$this->expectExceptionMessage('invalid encoding id:');
 
@@ -107,8 +104,9 @@ final class ECITest extends TestCase{
 		];
 	}
 
+	#[Test]
 	#[DataProvider('eciCharsetIdProvider')]
-	public function testReadWrite(int $id, int $lengthInBits):void{
+	public function readWrite(int $id, int $lengthInBits):void{
 		$bitBuffer = new BitBuffer;
 		$eci       = (new ECI($id))->write($bitBuffer, 1);
 
@@ -120,7 +118,8 @@ final class ECITest extends TestCase{
 	/**
 	 * Tests if and exception is thrown when the ECI segment is followed by a mode that is not 8-bit byte
 	 */
-	public function testDecodeECISegmentFollowedByInvalidModeException():void{
+	#[Test]
+	public function decodeECISegmentFollowedByInvalidModeException():void{
 		$this->expectException(QRCodeDataException::class);
 		$this->expectExceptionMessage('ECI designator followed by invalid mode:');
 
@@ -151,8 +150,9 @@ final class ECITest extends TestCase{
 	/**
 	 * Tests detection of an unknown character set
 	 */
+	#[Test]
 	#[DataProvider('unknownEncodingDataProvider')]
-	public function testConvertUnknownEncoding(int $id, string $data):void{
+	public function convertUnknownEncoding(int $id, string $data):void{
 		$options          = new QROptions;
 		$options->version = 5;
 

+ 4 - 5
tests/Data/HanziTest.php

@@ -11,10 +11,8 @@ declare(strict_types=1);
 
 namespace chillerlan\QRCodeTest\Data;
 
-use chillerlan\QRCode\Data\Hanzi;
-use chillerlan\QRCode\Data\QRDataModeInterface;
-use PHPUnit\Framework\Attributes\DataProvider;
-use PHPUnit\Framework\Attributes\Group;
+use chillerlan\QRCode\Data\{Hanzi, QRDataModeInterface};
+use PHPUnit\Framework\Attributes\{DataProvider, Group, Test};
 use Generator, Throwable;
 use function bin2hex, chr, defined, sprintf;
 
@@ -70,9 +68,10 @@ final class HanziTest extends DataInterfaceTestAbstract{
 
 	}
 
+	#[Test]
 	#[Group('slow')]
 	#[DataProvider('hanziProvider')]
-	public function testValidateGB2312(string $chr):void{
+	public function validateGB2312(string $chr):void{
 		// we may run into several issues due to encoding detection failures
 		try{
 			$this::assertTrue(Hanzi::validateString($chr));

+ 4 - 5
tests/Data/KanjiTest.php

@@ -11,11 +11,9 @@ declare(strict_types=1);
 
 namespace chillerlan\QRCodeTest\Data;
 
-use chillerlan\QRCode\Data\Kanji;
-use chillerlan\QRCode\Data\QRDataModeInterface;
+use chillerlan\QRCode\Data\{Kanji, QRDataModeInterface};
+use PHPUnit\Framework\Attributes\{DataProvider, Group, Test};
 use Generator, Throwable;
-use PHPUnit\Framework\Attributes\DataProvider;
-use PHPUnit\Framework\Attributes\Group;
 use function bin2hex, chr, defined, sprintf;
 
 /**
@@ -97,9 +95,10 @@ final class KanjiTest extends DataInterfaceTestAbstract{
 
 	}
 
+	#[Test]
 	#[Group('slow')]
 	#[DataProvider('kanjiProvider')]
-	public function testValidateSJIS(string $chr):void{
+	public function validateSJIS(string $chr):void{
 		// we may run into several issues due to encoding detection failures
 		try{
 			$this::assertTrue(Kanji::validateString($chr));

+ 1 - 2
tests/Data/NumberTest.php

@@ -11,8 +11,7 @@ declare(strict_types=1);
 
 namespace chillerlan\QRCodeTest\Data;
 
-use chillerlan\QRCode\Data\Number;
-use chillerlan\QRCode\Data\QRDataModeInterface;
+use chillerlan\QRCode\Data\{Number, QRDataModeInterface};
 
 /**
  * Tests the Number class

+ 9 - 12
tests/Data/QRDataTest.php

@@ -11,14 +11,11 @@ declare(strict_types=1);
 
 namespace chillerlan\QRCodeTest\Data;
 
-use chillerlan\QRCode\Common\BitBuffer;
-use chillerlan\QRCode\Common\EccLevel;
-use chillerlan\QRCode\Common\MaskPattern;
-use chillerlan\QRCode\Data\Byte;
-use chillerlan\QRCode\Data\QRData;
+use PHPUnit\Framework\Attributes\Test;
+use chillerlan\QRCode\{QRCode, QROptions};
+use chillerlan\QRCode\Common\{BitBuffer, EccLevel, MaskPattern};
+use chillerlan\QRCode\Data\{Byte, QRData};
 use chillerlan\QRCode\Output\QRGdImagePNG;
-use chillerlan\QRCode\QRCode;
-use chillerlan\QRCode\QROptions;
 use chillerlan\QRCodeTest\Traits\QRMatrixDebugTrait;
 use PHPUnit\Framework\TestCase;
 
@@ -28,7 +25,8 @@ final class QRDataTest extends TestCase{
 	/**
 	 * tests setting the BitBuffer object directly
 	 */
-	public function testSetBitBuffer():void{
+	#[Test]
+	public function setBitBuffer():void{
 		$rawBytes = [
 			67, 22, 135, 71, 71, 7, 51, 162, 242, 247, 119, 119, 114, 231, 150, 247,
 			87, 71, 86, 38, 82, 230, 54, 246, 210, 247, 118, 23, 70, 54, 131, 247,
@@ -60,17 +58,16 @@ final class QRDataTest extends TestCase{
 
 		$this->debugMatrix($matrix);
 
-		$this::assertSame($decodeResult->data, 'https://www.youtube.com/watch?v=DLzxrzFCyOs&t=43s');
+		$this::assertSame('https://www.youtube.com/watch?v=DLzxrzFCyOs&t=43s', $decodeResult->data);
 	}
 
-	public function testEstimateTotalBitLength():void{
+	#[Test]
+	public function estimateTotalBitLength():void{
 
 		$options = new QROptions([
 			'versionMin'          => 10,
 			'quietzoneSize'       => 2,
 			'eccLevel'            => EccLevel::H,
-#			'outputType'          => QROutputInterface::CUSTOM,
-#			'outputInterface'     => PmaQrCodeSVG::class,
 			'outputBase64'        => false,
 			'cssClass'            => 'pma-2fa-qrcode',
 			'drawCircularModules' => true,

+ 49 - 25
tests/Data/QRMatrixTest.php

@@ -16,7 +16,7 @@ use chillerlan\QRCode\Common\{EccLevel, MaskPattern, Version};
 use chillerlan\QRCode\Data\{QRCodeDataException, QRMatrix};
 use chillerlan\QRCodeTest\Traits\QRMatrixDebugTrait;
 use PHPUnit\Framework\TestCase;
-use PHPUnit\Framework\Attributes\DataProvider;
+use PHPUnit\Framework\Attributes\{DataProvider, Test};
 use Generator;
 
 /**
@@ -41,28 +41,32 @@ final class QRMatrixTest extends TestCase{
 	/**
 	 * Tests if size() returns the actual matrix size/count
 	 */
-	public function testGetSize():void{
+	#[Test]
+	public function getSize():void{
 		$this::assertCount($this->matrix->getSize(), $this->matrix->getBooleanMatrix());
 	}
 
 	/**
 	 * Tests if version() returns the current (given) version
 	 */
-	public function testGetVersion():void{
+	#[Test]
+	public function getVersion():void{
 		$this::assertSame($this::version, $this->matrix->getVersion()->getVersionNumber());
 	}
 
 	/**
 	 * Tests if eccLevel() returns the current (given) ECC level
 	 */
-	public function testGetECC():void{
+	#[Test]
+	public function getECC():void{
 		$this::assertSame(EccLevel::L, $this->matrix->getEccLevel()->getLevel());
 	}
 
 	/**
 	 * Tests if maskPattern() returns the current (or default) mask pattern
 	 */
-	public function testGetMaskPattern():void{
+	#[Test]
+	public function getMaskPattern():void{
 		// set via matrix evaluation
 		$matrix = (new QRCode)->addByteSegment('testdata')->getQRMatrix();
 
@@ -73,7 +77,8 @@ final class QRMatrixTest extends TestCase{
 	/**
 	 * Tests the set(), get() and check() methods
 	 */
-	public function testGetSetCheck():void{
+	#[Test]
+	public function getSetCheck():void{
 		$this->matrix->set(10, 10, true, QRMatrix::M_LOGO);
 		$this::assertSame(QRMatrix::M_LOGO_DARK, $this->matrix->get(10, 10));
 		$this::assertTrue($this->matrix->check(10, 10));
@@ -108,8 +113,9 @@ final class QRMatrixTest extends TestCase{
 	/**
 	 * Tests setting the dark module and verifies its position
 	 */
+	#[Test]
 	#[DataProvider('matrixProvider')]
-	public function testSetDarkModule(QRMatrix $matrix):void{
+	public function setDarkModule(QRMatrix $matrix):void{
 		$matrix->setDarkModule();
 
 		$this->dm($matrix);
@@ -120,8 +126,9 @@ final class QRMatrixTest extends TestCase{
 	/**
 	 * Tests setting the finder patterns and verifies their positions
 	 */
+	#[Test]
 	#[DataProvider('matrixProvider')]
-	public function testSetFinderPattern(QRMatrix $matrix):void{
+	public function setFinderPattern(QRMatrix $matrix):void{
 		$matrix->setFinderPattern();
 
 		$this->dm($matrix);
@@ -134,8 +141,9 @@ final class QRMatrixTest extends TestCase{
 	/**
 	 * Tests the separator patterns and verifies their positions
 	 */
+	#[Test]
 	#[DataProvider('matrixProvider')]
-	public function testSetSeparators(QRMatrix $matrix):void{
+	public function setSeparators(QRMatrix $matrix):void{
 		$matrix->setSeparators();
 
 		$this->dm($matrix);
@@ -149,8 +157,9 @@ final class QRMatrixTest extends TestCase{
 	/**
 	 * Tests the alignment patterns and verifies their positions - version 1 (no pattern) skipped
 	 */
+	#[Test]
 	#[DataProvider('matrixProvider')]
-	public function testSetAlignmentPattern(QRMatrix $matrix):void{
+	public function setAlignmentPattern(QRMatrix $matrix):void{
 		$version = $matrix->getVersion();
 
 		if($version->getVersionNumber() === 1){
@@ -180,8 +189,9 @@ final class QRMatrixTest extends TestCase{
 	/**
 	 * Tests the timing patterns and verifies their positions
 	 */
+	#[Test]
 	#[DataProvider('matrixProvider')]
-	public function testSetTimingPattern(QRMatrix $matrix):void{
+	public function setTimingPattern(QRMatrix $matrix):void{
 
 		$matrix
 			->setFinderPattern()
@@ -208,8 +218,9 @@ final class QRMatrixTest extends TestCase{
 	/**
 	 * Tests the version patterns and verifies their positions - version < 7 skipped
 	 */
+	#[Test]
 	#[DataProvider('matrixProvider')]
-	public function testSetVersionNumber(QRMatrix $matrix):void{
+	public function setVersionNumber(QRMatrix $matrix):void{
 
 		if($matrix->getVersion()->getVersionNumber() < 7){
 			$this::markTestSkipped('N/A (Version < 7)');
@@ -228,8 +239,9 @@ final class QRMatrixTest extends TestCase{
 	/**
 	 * Tests the format patterns and verifies their positions
 	 */
+	#[Test]
 	#[DataProvider('matrixProvider')]
-	public function testSetFormatInfo(QRMatrix $matrix):void{
+	public function setFormatInfo(QRMatrix $matrix):void{
 		$matrix->setFormatInfo(new MaskPattern(MaskPattern::PATTERN_000));
 
 		$this->dm($matrix);
@@ -243,8 +255,9 @@ final class QRMatrixTest extends TestCase{
 	/**
 	 * Tests the quiet zone pattern and verifies its position
 	 */
+	#[Test]
 	#[DataProvider('matrixProvider')]
-	public function testSetQuietZone(QRMatrix $matrix):void{
+	public function setQuietZone(QRMatrix $matrix):void{
 		$size          = $matrix->getSize();
 		$quietZoneSize = 5;
 
@@ -274,7 +287,8 @@ final class QRMatrixTest extends TestCase{
 	/**
 	 * Tests if an exception is thrown in an attempt to create the quiet zone before data was written
 	 */
-	public function testSetQuietZoneException():void{
+	#[Test]
+	public function setQuietZoneException():void{
 		$this->expectException(QRCodeDataException::class);
 		$this->expectExceptionMessage('use only after writing data');
 
@@ -284,8 +298,9 @@ final class QRMatrixTest extends TestCase{
 	/**
 	 * Tests rotating the matrix by 90 degrees CW
 	 */
+	#[Test]
 	#[DataProvider('matrixProvider')]
-	public function testRotate90(QRMatrix $matrix):void{
+	public function rotate90(QRMatrix $matrix):void{
 		$matrix->initFunctionalPatterns();
 
 		// matrix size
@@ -320,7 +335,8 @@ final class QRMatrixTest extends TestCase{
 	/**
 	 * Tests if the logo space is drawn square if one of the dimensions is omitted
 	 */
-	public function testSetLogoSpaceOmitDimension():void{
+	#[Test]
+	public function setLogoSpaceOmitDimension():void{
 		$o = new QROptions;
 		$o->version         = 2;
 		$o->eccLevel        = EccLevel::H;
@@ -342,7 +358,8 @@ final class QRMatrixTest extends TestCase{
 	/**
 	 * Tests the auto orientation of the logo space
 	 */
-	public function testSetLogoSpaceOrientation():void{
+	#[Test]
+	public function setLogoSpaceOrientation():void{
 		$o = new QROptions;
 		$o->version      = 10;
 		$o->eccLevel     = EccLevel::H;
@@ -366,7 +383,8 @@ final class QRMatrixTest extends TestCase{
 	/**
 	 * Tests the manual positioning of the logo space
 	 */
-	public function testSetLogoSpacePosition():void{
+	#[Test]
+	public function setLogoSpacePosition():void{
 		$o = new QROptions;
 		$o->version       = 10;
 		$o->eccLevel      = EccLevel::H;
@@ -394,7 +412,8 @@ final class QRMatrixTest extends TestCase{
 	/**
 	 * Tests whether an exception is thrown when an ECC level other than "H" is set when attempting to add logo space
 	 */
-	public function testSetLogoSpaceInvalidEccException():void{
+	#[Test]
+	public function setLogoSpaceInvalidEccException():void{
 		$this->expectException(QRCodeDataException::class);
 		$this->expectExceptionMessage('ECC level "H" required to add logo space');
 
@@ -404,7 +423,8 @@ final class QRMatrixTest extends TestCase{
 	/**
 	 * Tests whether an exception is thrown when width or height exceed the matrix size
 	 */
-	public function testSetLogoSpaceExceedsException():void{
+	#[Test]
+	public function setLogoSpaceExceedsException():void{
 		$this->expectException(QRCodeDataException::class);
 		$this->expectExceptionMessage('logo dimensions exceed matrix size');
 
@@ -418,7 +438,8 @@ final class QRMatrixTest extends TestCase{
 	/**
 	 * Tests whether an exception is thrown when the logo space size exceeds the maximum ECC capacity
 	 */
-	public function testSetLogoSpaceMaxSizeException():void{
+	#[Test]
+	public function setLogoSpaceMaxSizeException():void{
 		$this->expectException(QRCodeDataException::class);
 		$this->expectExceptionMessage('logo space exceeds the maximum error correction capacity');
 
@@ -432,7 +453,8 @@ final class QRMatrixTest extends TestCase{
 	/**
 	 * Tests flipping the value of a module
 	 */
-	public function testFlip():void{
+	#[Test]
+	public function flip():void{
 		$this->matrix->set(20, 20, true, QRMatrix::M_LOGO);
 
 		// cover checkType()
@@ -452,7 +474,8 @@ final class QRMatrixTest extends TestCase{
 	/**
 	 * Tests checking whether the M_TYPE of a module is not one of an array of M_TYPES
 	 */
-	public function testCheckTypeIn():void{
+	#[Test]
+	public function checkTypeIn():void{
 		$this->matrix->set(10, 10, true, QRMatrix::M_QUIETZONE);
 
 		$this::assertFalse($this->matrix->checkTypeIn(10, 10, [QRMatrix::M_DATA, QRMatrix::M_FINDER]));
@@ -462,7 +485,8 @@ final class QRMatrixTest extends TestCase{
 	/**
 	 * Tests checking the adjacent modules
 	 */
-	public function testCheckNeighbours():void{
+	#[Test]
+	public function checkNeighbours():void{
 
 		$this->matrix
 			->setFinderPattern()

+ 6 - 1
tests/Output/QREpsTest.php

@@ -15,7 +15,11 @@ use chillerlan\QRCode\QROptions;
 use chillerlan\QRCode\Data\QRMatrix;
 use chillerlan\QRCode\Output\{QREps, QROutputInterface};
 use chillerlan\Settings\SettingsContainerInterface;
+use PHPUnit\Framework\Attributes\Test;
 
+/**
+ * Tests the QREps output class
+ */
 class QREpsTest extends QROutputTestAbstract{
 
 	public static function moduleValueProvider():array{
@@ -37,7 +41,8 @@ class QREpsTest extends QROutputTestAbstract{
 		return new QREps($options, $matrix);
 	}
 
-	public function testSetModuleValues():void{
+	#[Test]
+	public function setModuleValues():void{
 
 		$this->options->moduleValues = [
 			// data

+ 4 - 12
tests/Output/QRFpdfTest.php

@@ -16,24 +16,15 @@ use chillerlan\QRCode\Data\QRMatrix;
 use chillerlan\QRCode\Output\{QRFpdf, QROutputInterface};
 use chillerlan\QRCodeTest\Traits\RGBArrayModuleValueProviderTrait;
 use chillerlan\Settings\SettingsContainerInterface;
+use PHPUnit\Framework\Attributes\Test;
 use FPDF;
-use function class_exists;
 
 /**
- * Tests the QRFpdf output module
+ * Tests the QRFpdf output class
  */
 final class QRFpdfTest extends QROutputTestAbstract{
 	use RGBArrayModuleValueProviderTrait;
 
-	protected function setUp():void{
-
-		if(!class_exists(FPDF::class)){
-			$this::markTestSkipped('FPDF not available');
-		}
-
-		parent::setUp();
-	}
-
 	protected function getOutputInterface(
 		SettingsContainerInterface|QROptions $options,
 		QRMatrix                             $matrix,
@@ -41,7 +32,8 @@ final class QRFpdfTest extends QROutputTestAbstract{
 		return new QRFpdf($options, $matrix);
 	}
 
-	public function testSetModuleValues():void{
+	#[Test]
+	public function setModuleValues():void{
 
 		$this->options->moduleValues = [
 			// data

+ 3 - 10
tests/Output/QRGdImageAVIFTest.php

@@ -15,19 +15,12 @@ use chillerlan\QRCode\QROptions;
 use chillerlan\QRCode\Data\QRMatrix;
 use chillerlan\QRCode\Output\{QRGdImageAVIF, QROutputInterface};
 use chillerlan\Settings\SettingsContainerInterface;
-use function function_exists;
+use PHPUnit\Framework\Attributes\{RequiresFunction, RequiresPhpExtension};
 
+#[RequiresPhpExtension('gd')]
+#[RequiresFunction('imageavif')]
 final class QRGdImageAVIFTest extends QRGdImageTestAbstract{
 
-	protected function setUp():void{
-		parent::setUp();
-
-		if(!function_exists('imageavif')){
-			$this::markTestSkipped('no AVIF support');
-		}
-
-	}
-
 	protected function getOutputInterface(
 		SettingsContainerInterface|QROptions $options,
 		QRMatrix                             $matrix,

+ 2 - 0
tests/Output/QRGdImageBMPTest.php

@@ -15,7 +15,9 @@ use chillerlan\QRCode\QROptions;
 use chillerlan\QRCode\Data\QRMatrix;
 use chillerlan\QRCode\Output\{QRGdImageBMP, QROutputInterface};
 use chillerlan\Settings\SettingsContainerInterface;
+use PHPUnit\Framework\Attributes\RequiresPhpExtension;
 
+#[RequiresPhpExtension('gd')]
 final class QRGdImageBMPTest extends QRGdImageTestAbstract{
 
 	protected function getOutputInterface(

+ 2 - 0
tests/Output/QRGdImageGIFTest.php

@@ -15,7 +15,9 @@ use chillerlan\QRCode\QROptions;
 use chillerlan\QRCode\Data\QRMatrix;
 use chillerlan\QRCode\Output\{QRGdImageGIF, QROutputInterface};
 use chillerlan\Settings\SettingsContainerInterface;
+use PHPUnit\Framework\Attributes\RequiresPhpExtension;
 
+#[RequiresPhpExtension('gd')]
 final class QRGdImageGIFTest extends QRGdImageTestAbstract{
 
 	protected function getOutputInterface(

+ 2 - 0
tests/Output/QRGdImageJPGTest.php

@@ -15,7 +15,9 @@ use chillerlan\QRCode\QROptions;
 use chillerlan\QRCode\Data\QRMatrix;
 use chillerlan\QRCode\Output\{QRGdImageJPEG, QROutputInterface};
 use chillerlan\Settings\SettingsContainerInterface;
+use PHPUnit\Framework\Attributes\RequiresPhpExtension;
 
+#[RequiresPhpExtension('gd')]
 final class QRGdImageJPGTest extends QRGdImageTestAbstract{
 
 	protected function getOutputInterface(

+ 2 - 0
tests/Output/QRGdImagePNGTest.php

@@ -15,7 +15,9 @@ use chillerlan\QRCode\QROptions;
 use chillerlan\QRCode\Data\QRMatrix;
 use chillerlan\QRCode\Output\{QRGdImagePNG, QROutputInterface};
 use chillerlan\Settings\SettingsContainerInterface;
+use PHPUnit\Framework\Attributes\RequiresPhpExtension;
 
+#[RequiresPhpExtension('gd')]
 final class QRGdImagePNGTest extends QRGdImageTestAbstract{
 
 	protected function getOutputInterface(

+ 9 - 14
tests/Output/QRGdImageTestAbstract.php

@@ -15,25 +15,18 @@ namespace chillerlan\QRCodeTest\Output;
 
 use chillerlan\QRCode\Data\QRMatrix;
 use chillerlan\QRCodeTest\Traits\RGBArrayModuleValueProviderTrait;
+use PHPUnit\Framework\Attributes\{RequiresPhpExtension, Test};
 use GdImage;
-use function extension_loaded;
 
 /**
- * Tests the QRGdImage output module
+ * Tests the QRGdImage output classes
  */
+#[RequiresPhpExtension('gd')]
 abstract class QRGdImageTestAbstract extends QROutputTestAbstract{
 	use RGBArrayModuleValueProviderTrait;
 
-	protected function setUp():void{
-
-		if(!extension_loaded('gd')){
-			$this::markTestSkipped('ext-gd not loaded');
-		}
-
-		parent::setUp();
-	}
-
-	public function testSetModuleValues():void{
+	#[Test]
+	public function setModuleValues():void{
 
 		$this->options->moduleValues = [
 			// data
@@ -48,14 +41,16 @@ abstract class QRGdImageTestAbstract extends QROutputTestAbstract{
 		$this::assertTrue(true); // tricking the code coverage
 	}
 
-	public function testOutputGetResource():void{
+	#[Test]
+	public function outputGetResource():void{
 		$this->options->returnResource = true;
 		$this->outputInterface         = $this->getOutputInterface($this->options, $this->matrix);
 
 		$this::assertInstanceOf(GdImage::class, $this->outputInterface->dump());
 	}
 
-	public function testBase64MimeType():void{
+	#[Test]
+	public function base64MimeType():void{
 		$this->options->outputBase64 = true;
 		$this->outputInterface       = $this->getOutputInterface($this->options, $this->matrix);
 

+ 2 - 0
tests/Output/QRGdImageWEBPTest.php

@@ -15,7 +15,9 @@ use chillerlan\QRCode\QROptions;
 use chillerlan\QRCode\Data\QRMatrix;
 use chillerlan\QRCode\Output\{QRGdImageWEBP, QROutputInterface};
 use chillerlan\Settings\SettingsContainerInterface;
+use PHPUnit\Framework\Attributes\RequiresPhpExtension;
 
+#[RequiresPhpExtension('gd')]
 final class QRGdImageWEBPTest extends QRGdImageTestAbstract{
 
 	protected function getOutputInterface(

+ 7 - 13
tests/Output/QRImagickTest.php

@@ -17,23 +17,15 @@ use chillerlan\QRCode\QROptions;
 use chillerlan\QRCode\Data\QRMatrix;
 use chillerlan\QRCode\Output\{QRImagick, QROutputInterface};
 use chillerlan\Settings\SettingsContainerInterface;
+use PHPUnit\Framework\Attributes\{RequiresPhpExtension, Test};
 use Imagick;
-use function extension_loaded;
 
 /**
- * Tests the QRImagick output module
+ * Tests the QRImagick output class
  */
+#[RequiresPhpExtension('imagick')]
 final class QRImagickTest extends QROutputTestAbstract{
 
-	protected function setUp():void{
-
-		if(!extension_loaded('imagick')){
-			$this::markTestSkipped('ext-imagick not loaded');
-		}
-
-		parent::setUp();
-	}
-
 	protected function getOutputInterface(
 		SettingsContainerInterface|QROptions $options,
 		QRMatrix                             $matrix,
@@ -67,7 +59,8 @@ final class QRImagickTest extends QROutputTestAbstract{
 		];
 	}
 
-	public function testSetModuleValues():void{
+	#[Test]
+	public function setModuleValues():void{
 
 		$this->options->moduleValues = [
 			// data
@@ -82,7 +75,8 @@ final class QRImagickTest extends QROutputTestAbstract{
 		$this::assertTrue(true); // tricking the code coverage
 	}
 
-	public function testOutputGetResource():void{
+	#[Test]
+	public function outputGetResource():void{
 		$this->options->returnResource = true;
 		$this->outputInterface         = $this->getOutputInterface($this->options, $this->matrix);
 

+ 9 - 16
tests/Output/QRInterventionImageTest.php

@@ -11,30 +11,21 @@ declare(strict_types=1);
 
 namespace chillerlan\QRCodeTest\Output;
 
-use chillerlan\QRCode\Data\QRMatrix;
-use chillerlan\QRCode\Output\QRInterventionImage;
-use chillerlan\QRCode\Output\QROutputInterface;
 use chillerlan\QRCode\QROptions;
+use chillerlan\QRCode\Data\QRMatrix;
+use chillerlan\QRCode\Output\{QRInterventionImage, QROutputInterface};
 use chillerlan\QRCodeTest\Traits\CssColorModuleValueProviderTrait;
 use chillerlan\Settings\SettingsContainerInterface;
 use Intervention\Image\Interfaces\ImageInterface;
-use function extension_loaded;
+use PHPUnit\Framework\Attributes\{RequiresPhpExtension, Test};
 
 /**
- * Tests the QRInterventionImage output module
+ * Tests the QRInterventionImage output class
  */
+#[RequiresPhpExtension('gd')]
 class QRInterventionImageTest extends QROutputTestAbstract{
 	use CssColorModuleValueProviderTrait;
 
-	protected function setUp():void{
-
-		if(!extension_loaded('gd')){
-			$this::markTestSkipped('ext-gd not loaded');
-		}
-
-		parent::setUp();
-	}
-
 	protected function getOutputInterface(
 		SettingsContainerInterface|QROptions $options,
 		QRMatrix                             $matrix,
@@ -42,7 +33,8 @@ class QRInterventionImageTest extends QROutputTestAbstract{
 		return new QRInterventionImage($options, $matrix);
 	}
 
-	public function testSetModuleValues():void{
+	#[Test]
+	public function setModuleValues():void{
 
 		$this->options->moduleValues = [
 			// data
@@ -57,7 +49,8 @@ class QRInterventionImageTest extends QROutputTestAbstract{
 		$this::assertTrue(true); // tricking the code coverage
 	}
 
-	public function testOutputGetResource():void{
+	#[Test]
+	public function outputGetResource():void{
 		$this->options->returnResource = true;
 		$this->outputInterface         = $this->getOutputInterface($this->options, $this->matrix);
 

+ 5 - 2
tests/Output/QRMarkupTestAbstract.php

@@ -13,14 +13,16 @@ namespace chillerlan\QRCodeTest\Output;
 
 use chillerlan\QRCode\Data\QRMatrix;
 use chillerlan\QRCodeTest\Traits\CssColorModuleValueProviderTrait;
+use PHPUnit\Framework\Attributes\Test;
 
 /**
- * Tests the QRMarkup output module
+ * Tests the QRMarkup output classes
  */
 abstract class QRMarkupTestAbstract extends QROutputTestAbstract{
 	use CssColorModuleValueProviderTrait;
 
-	public function testSetModuleValues():void{
+	#[Test]
+	public function setModuleValues():void{
 		$this->options->outputBase64     = false;
 		$this->options->drawLightModules = true;
 		$this->options->moduleValues     = [
@@ -31,6 +33,7 @@ abstract class QRMarkupTestAbstract extends QROutputTestAbstract{
 
 		$this->outputInterface = $this->getOutputInterface($this->options, $this->matrix);
 		$data = $this->outputInterface->dump();
+
 		$this::assertStringContainsString('#4A6000', $data);
 		$this::assertStringContainsString('#ECF9BE', $data);
 	}

+ 10 - 6
tests/Output/QROutputTestAbstract.php

@@ -11,13 +11,13 @@ declare(strict_types=1);
 
 namespace chillerlan\QRCodeTest\Output;
 
-use chillerlan\QRCodeTest\Traits\BuildDirTrait;
 use chillerlan\QRCode\{QRCode, QROptions};
 use chillerlan\QRCode\Data\QRMatrix;
 use chillerlan\QRCode\Output\{QRCodeOutputException, QROutputInterface};
+use chillerlan\QRCodeTest\Traits\BuildDirTrait;
 use chillerlan\Settings\SettingsContainerInterface;
-use PHPUnit\Framework\Attributes\DataProvider;
 use PHPUnit\Framework\TestCase;
+use PHPUnit\Framework\Attributes\{DataProvider, Test};
 use ReflectionObject;
 
 /**
@@ -52,7 +52,8 @@ abstract class QROutputTestAbstract extends TestCase{
 	/**
 	 * Tests if an exception is thrown when trying to write a cache file to an invalid destination
 	 */
-	public function testSaveException():void{
+	#[Test]
+	public function saveException():void{
 		$this->expectException(QRCodeOutputException::class);
 		$this->expectExceptionMessage('Cannot write data to cache file: /foo/bar.test');
 
@@ -64,8 +65,9 @@ abstract class QROutputTestAbstract extends TestCase{
 	 */
 	abstract public static function moduleValueProvider():array;
 
+	#[Test]
 	#[DataProvider('moduleValueProvider')]
-	public function testValidateModuleValues(mixed $value, bool $expected):void{
+	public function validateModuleValues(mixed $value, bool $expected):void{
 		$this::assertSame($expected, $this->outputInterface::moduleValueIsValid($value));
 	}
 
@@ -76,12 +78,14 @@ abstract class QROutputTestAbstract extends TestCase{
 	/**
 	 * covers the module values settings
 	 */
-	abstract public function testSetModuleValues():void;
+	#[Test]
+	abstract public function setModuleValues():void;
 
 	/**
 	 * coverage of the built-in output modules
 	 */
-	public function testRenderToCacheFile():void{
+	#[Test]
+	public function renderToCacheFile():void{
 		$this->options->outputBase64 = false;
 		$this->outputInterface       = $this->getOutputInterface($this->options, $this->matrix);
 		// create the cache file

+ 6 - 1
tests/Output/QRStringJSONTest.php

@@ -16,7 +16,11 @@ use chillerlan\QRCode\Data\QRMatrix;
 use chillerlan\QRCode\Output\{QROutputInterface, QRStringJSON};
 use chillerlan\QRCodeTest\Traits\CssColorModuleValueProviderTrait;
 use chillerlan\Settings\SettingsContainerInterface;
+use PHPUnit\Framework\Attributes\Test;
 
+/**
+ * Tests the QRStringJSON output class
+ */
 final class QRStringJSONTest extends QROutputTestAbstract{
 	use CssColorModuleValueProviderTrait;
 
@@ -27,7 +31,8 @@ final class QRStringJSONTest extends QROutputTestAbstract{
 		return new QRStringJSON($options, $matrix);
 	}
 
-	public function testSetModuleValues():void{
+	#[Test]
+	public function setModuleValues():void{
 
 		$this->options->moduleValues = [
 			// data

+ 6 - 1
tests/Output/QRStringTextTest.php

@@ -15,7 +15,11 @@ use chillerlan\QRCode\QROptions;
 use chillerlan\QRCode\Data\QRMatrix;
 use chillerlan\QRCode\Output\{QROutputInterface, QRStringText};
 use chillerlan\Settings\SettingsContainerInterface;
+use PHPUnit\Framework\Attributes\Test;
 
+/**
+ * Tests the QRStringText output class
+ */
 final class QRStringTextTest extends QROutputTestAbstract{
 
 	protected function getOutputInterface(
@@ -37,7 +41,8 @@ final class QRStringTextTest extends QROutputTestAbstract{
 		];
 	}
 
-	public function testSetModuleValues():void{
+	#[Test]
+	public function setModuleValues():void{
 
 		$this->options->moduleValues = [
 			// data

+ 2 - 0
tests/QRCodeReaderGDTest.php

@@ -14,10 +14,12 @@ namespace chillerlan\QRCodeTest;
 use chillerlan\QRCode\Common\{GDLuminanceSource, LuminanceSourceInterface};
 use chillerlan\QRCode\QROptions;
 use chillerlan\Settings\SettingsContainerInterface;
+use PHPUnit\Framework\Attributes\RequiresPhpExtension;
 
 /**
  * Tests the GD based reader
  */
+#[RequiresPhpExtension('gd')]
 final class QRCodeReaderGDTest extends QRCodeReaderTestAbstract{
 
 	protected function getLuminanceSourceFromFile(

+ 4 - 8
tests/QRCodeReaderImagickTest.php

@@ -15,21 +15,16 @@ use chillerlan\QRCode\QROptions;
 use chillerlan\QRCode\Common\{IMagickLuminanceSource, LuminanceSourceInterface};
 use chillerlan\QRCode\Decoder\Decoder;
 use chillerlan\Settings\SettingsContainerInterface;
-use PHPUnit\Framework\Attributes\DataProvider;
-use function extension_loaded;
+use PHPUnit\Framework\Attributes\{DataProvider, RequiresPhpExtension, Test};
 use const PHP_OS_FAMILY;
 
 /**
  * Tests the Imagick based reader
  */
+#[RequiresPhpExtension('imagick')]
 final class QRCodeReaderImagickTest extends QRCodeReaderTestAbstract{
 
 	protected function setUp():void{
-
-		if(!extension_loaded('imagick')){
-			$this::markTestSkipped('imagick not installed');
-		}
-
 		parent::setUp();
 
 		$this->options->readerUseImagickIfAvailable = true;
@@ -55,8 +50,9 @@ final class QRCodeReaderImagickTest extends QRCodeReaderTestAbstract{
 		];
 	}
 
+	#[Test]
 	#[DataProvider('vectorQRCodeProvider')]
-	public function testReadVectorFormats(string $img, string $expected):void{
+	public function readVectorFormats(string $img, string $expected):void{
 
 		if(PHP_OS_FAMILY === 'Linux'){
 			$this::markTestSkipped('avoid imagick conversion errors (ha ha)');

+ 12 - 9
tests/QRCodeReaderTestAbstract.php

@@ -20,7 +20,7 @@ use chillerlan\QRCode\Decoder\Decoder;
 use chillerlan\QRCode\Output\QRGdImagePNG;
 use chillerlan\QRCodeTest\Traits\{QRMatrixDebugTrait, QRMaxLengthTrait};
 use chillerlan\Settings\SettingsContainerInterface;
-use PHPUnit\Framework\Attributes\{DataProvider, Group};
+use PHPUnit\Framework\Attributes\{DataProvider, Group, Test};
 use PHPUnit\Framework\TestCase;
 use Exception, Generator, RuntimeException;
 use function array_map, defined, realpath, sprintf, str_repeat, substr;
@@ -49,6 +49,11 @@ abstract class QRCodeReaderTestAbstract extends TestCase{
 		$this->options->readerUseImagickIfAvailable = false;
 	}
 
+	abstract protected function getLuminanceSourceFromFile(
+		string                               $file,
+		SettingsContainerInterface|QROptions $options,
+	):LuminanceSourceInterface;
+
 	/**
 	 * @phpstan-return array<string, array{0: string, 1: string, 2: bool}>
 	 */
@@ -79,14 +84,10 @@ abstract class QRCodeReaderTestAbstract extends TestCase{
 		];
 	}
 
-	abstract protected function getLuminanceSourceFromFile(
-		string                               $file,
-		SettingsContainerInterface|QROptions $options,
-	):LuminanceSourceInterface;
-
+	#[Test]
 	#[Group('slow')]
 	#[DataProvider('qrCodeProvider')]
-	public function testReader(string $img, string $expected, bool $grayscale):void{
+	public function reader(string $img, string $expected, bool $grayscale):void{
 
 		if($grayscale){
 			$this->options->readerGrayscale        = true;
@@ -107,7 +108,8 @@ abstract class QRCodeReaderTestAbstract extends TestCase{
 		$this::assertSame($expected, (string)$result);
 	}
 
-	public function testReaderMultiMode():void{
+	#[Test]
+	public function readerMultiMode():void{
 		$this->options->outputInterface = QRGdImagePNG::class;
 		$this->options->outputBase64    = false;
 
@@ -154,9 +156,10 @@ abstract class QRCodeReaderTestAbstract extends TestCase{
 
 	}
 
+	#[Test]
 	#[Group('slow')]
 	#[DataProvider('dataTestProvider')]
-	public function testReadData(Version $version, EccLevel $ecc, string $expected):void{
+	public function readData(Version $version, EccLevel $ecc, string $expected):void{
 		$this->options->outputInterface  = QRGdImagePNG::class;
 		$this->options->imageTransparent = false;
 		$this->options->eccLevel         = $ecc->getLevel();

+ 39 - 6
tests/QRCodeTest.php

@@ -11,9 +11,11 @@ declare(strict_types=1);
 
 namespace chillerlan\QRCodeTest;
 
-use chillerlan\QRCode\{QROptions, QRCode};
-use chillerlan\QRCode\Output\QRCodeOutputException;
+use chillerlan\QRCode\{QRCode, QRCodeException, QROptions};
+use chillerlan\QRCode\Common\ECICharset;
+use chillerlan\QRCode\Output\{QRCodeOutputException, QRGdImagePNG};
 use chillerlan\QRCodeTest\Traits\BuildDirTrait;
+use PHPUnit\Framework\Attributes\Test;
 use PHPUnit\Framework\TestCase;
 use stdClass;
 
@@ -41,7 +43,8 @@ final class QRCodeTest extends TestCase{
 	/**
 	 * tests if an exception is thrown if the given output class does not exist
 	 */
-	public function testInitCustomOutputInterfaceNotExistsException():void{
+	#[Test]
+	public function initCustomOutputInterfaceNotExistsException():void{
 		$this->expectException(QRCodeOutputException::class);
 		$this->expectExceptionMessage('invalid output class');
 
@@ -53,7 +56,8 @@ final class QRCodeTest extends TestCase{
 	/**
 	 * tests if an exception is thrown if the given output class does not implement QROutputInterface
 	 */
-	public function testInitCustomOutputInterfaceNotImplementsException():void{
+	#[Test]
+	public function initCustomOutputInterfaceNotImplementsException():void{
 		$this->expectException(QRCodeOutputException::class);
 		$this->expectExceptionMessage('output class does not implement QROutputInterface');
 
@@ -65,7 +69,8 @@ final class QRCodeTest extends TestCase{
 	/**
 	 * Tests if an exception is thrown when trying to write a cache file to an invalid destination
 	 */
-	public function testSaveException():void{
+	#[Test]
+	public function saveException():void{
 		$this->expectException(QRCodeOutputException::class);
 		$this->expectExceptionMessage('Cannot write data to cache file: /foo/bar.test');
 
@@ -77,7 +82,8 @@ final class QRCodeTest extends TestCase{
 	/**
 	 * Tests if a cache file is properly saved in the given path
 	 */
-	public function testRenderToCacheFile():void{
+	#[Test]
+	public function renderToCacheFile():void{
 		$fileSubPath = $this::buildDir.'/test.cache.svg';
 
 		$this->options->cachefile    = $this->getBuildPath($fileSubPath);
@@ -88,4 +94,31 @@ final class QRCodeTest extends TestCase{
 		$this::assertSame($data, $this->getBuildFileContent($fileSubPath));
 	}
 
+	/**
+	 * Tests adding and decoding an ECI sequence
+	 */
+	#[Test]
+	public function addEciSegment():void{
+		$expected = '无可奈何燃花作香';
+
+		$qrCode = (new QRCode([
+			'outputBase64'    => false,
+			'outputInterface' => QRGdImagePNG::class,
+		]));
+
+		$qrCode->addEciSegment(ECICharset::GB18030, $expected);
+
+		$result = $qrCode->readFromBlob($qrCode->render());
+
+		$this::assertSame($expected, $result->data);
+	}
+
+	#[Test]
+	public function addEciSegmentInvalidCharsetException():void{
+		$this->expectException(QRCodeException::class);
+		$this->expectExceptionMessage('unable to add ECI segment');
+
+		(new QRCode)->addEciSegment(666, 'nope');
+	}
+
 }

+ 33 - 72
tests/QROptionsTest.php

@@ -15,7 +15,7 @@ namespace chillerlan\QRCodeTest;
 
 use chillerlan\QRCode\{QRCodeException, QROptions};
 use chillerlan\QRCode\Common\{EccLevel, Version};
-use PHPUnit\Framework\Attributes\DataProvider;
+use PHPUnit\Framework\Attributes\{Test, TestWith};
 use PHPUnit\Framework\TestCase;
 
 /**
@@ -23,45 +23,29 @@ use PHPUnit\Framework\TestCase;
  */
 final class QROptionsTest extends TestCase{
 
-	/**
-	 * @return int[][]
-	 */
-	public static function VersionProvider():array{
-		return [
-			'values > 40 should be clamped to 40'        => [42, 40],
-			'values < 1 should be clamped to 1'          => [-42, 1],
-			'values in between shold not be touched'     => [21, 21],
-			'value -1 should be treated as is (default)' => [Version::AUTO, -1],
-		];
-	}
-
 	/**
 	 * Tests the $version clamping
 	 */
-	#[DataProvider('VersionProvider')]
-	public function testVersionClamp(int $version, int $expected):void{
+	#[Test]
+	#[TestWith([42, 40], 'values > 40 should be clamped to 40')]
+	#[TestWith([-42, 1], 'values < 1 should be clamped to 1')]
+	#[TestWith([21, 21], 'values in between should not be touched')]
+	#[TestWith([Version::AUTO, -1], 'value -1 should be treated as is (default)')]
+	public function versionClamp(int $version, int $expected):void{
 		$o = new QROptions(['version' => $version]);
 
 		$this::assertSame($expected, $o->version);
 	}
 
-	/**
-	 * @return int[][]
-	 */
-	public static function VersionMinMaxProvider():array{
-		return [
-			'normal clamp'         => [5, 10, 5, 10],
-			'exceeding values'     => [-42, 42, 1, 40],
-			'min > max'            => [10, 5, 5, 10],
-			'min > max, exceeding' => [42, -42, 1, 40],
-		];
-	}
-
 	/**
 	 * Tests the $versionMin/$versionMax clamping
 	 */
-	#[DataProvider('VersionMinMaxProvider')]
-	public function testVersionMinMaxClamp(int $versionMin, int $versionMax, int $expectedMin, int $expectedMax):void{
+	#[Test]
+	#[TestWith([5, 10, 5, 10], 'normal clamp')]
+	#[TestWith([-42, 42, 1, 40], 'exceeding values')]
+	#[TestWith([10, 5, 5, 10], 'min > max' )]
+	#[TestWith([42, -42, 1, 40], 'min > max, exceeding')]
+	public function versionMinMaxClamp(int $versionMin, int $versionMax, int $expectedMin, int $expectedMax):void{
 		$o = new QROptions(['versionMin' => $versionMin, 'versionMax' => $versionMax]);
 
 		$this::assertSame($expectedMin, $o->versionMin);
@@ -73,7 +57,8 @@ final class QROptionsTest extends TestCase{
 	 *
 	 * @phan-suppress PhanTypeMismatchPropertyProbablyReal
 	 */
-	public function testSetEccLevel():void{
+	#[Test]
+	public function setEccLevel():void{
 		$o = new QROptions(['eccLevel' => EccLevel::H]);
 
 		$this::assertSame(EccLevel::H, $o->eccLevel);
@@ -86,7 +71,8 @@ final class QROptionsTest extends TestCase{
 	/**
 	 * Tests if an exception is thrown when attempting to set an invalid ECC level integer
 	 */
-	public function testSetEccLevelFromIntException():void{
+	#[Test]
+	public function setEccLevelFromIntException():void{
 		$this->expectException(QRCodeException::class);
 		$this->expectExceptionMessage('Invalid ECC level: "42"');
 
@@ -96,42 +82,24 @@ final class QROptionsTest extends TestCase{
 	/**
 	 * Tests if an exception is thrown when attempting to set an invalid ECC level string
 	 */
-	public function testSetEccLevelFromStringException():void{
+	#[Test]
+	public function setEccLevelFromStringException():void{
 		$this->expectException(QRCodeException::class);
 		$this->expectExceptionMessage('Invalid ECC level: "FOO"');
 
 		new QROptions(['eccLevel' => 'foo']);
 	}
 
-	/**
-	 * @return int[][][]
-	 */
-	public static function RGBProvider():array{
-		return [
-			'exceeding values' => [[-1, 0, 999], [0, 0 ,255]],
-			'too few values'   => [[1, 2], [255, 255, 255]],
-			'too many values'  => [[1, 2, 3, 4, 5], [1, 2, 3]],
-		];
-	}
-
-	/**
-	 * @return int[][]
-	 */
-	public static function logoSpaceValueProvider():array{
-		return [
-			'negative' => [ -1,   0],
-			'zero'     => [  0,   0],
-			'normal'   => [ 69,  69],
-			'max'      => [177, 177],
-			'exceed'   => [178, 177],
-		];
-	}
-
 	/**
 	 * Tests the clamping (between 0 and 177) of the logo space values
 	 */
-	#[DataProvider('logoSpaceValueProvider')]
-	public function testClampLogoSpaceValue(int $value, int $expected):void{
+	#[Test]
+	#[TestWith([ -1,   0], 'negative')]
+	#[TestWith([  0,   0], 'zero')]
+	#[TestWith([ 69,  69], 'normal')]
+	#[TestWith([177, 177], 'max')]
+	#[TestWith([178, 177], 'exceed')]
+	public function clampLogoSpaceValue(int $value, int $expected):void{
 		$o = new QROptions;
 
 		foreach(['logoSpaceWidth', 'logoSpaceHeight', 'logoSpaceStartX', 'logoSpaceStartY'] as $prop){
@@ -144,7 +112,8 @@ final class QROptionsTest extends TestCase{
 	/**
 	 * Tests if the optional logo space start values are nullable
 	 */
-	public function testLogoSpaceStartNullable():void{
+	#[Test]
+	public function logoSpaceStartNullable():void{
 		$o = new QROptions([
 			'logoSpaceStartX' => 42,
 			'logoSpaceStartY' => 69,
@@ -160,22 +129,14 @@ final class QROptionsTest extends TestCase{
 		$this::assertNull($o->logoSpaceStartY);
 	}
 
-	/**
-	 * @return float[][]
-	 */
-	public static function circleRadiusProvider():array{
-		return [
-			[0.0, 0.1],
-			[0.5, 0.5],
-			[1.5, 0.75],
-		];
-	}
-
 	/**
 	 * Tests clamping of the circle radius
 	 */
-	#[DataProvider('circleRadiusProvider')]
-	public function testClampCircleRadius(float $value, float $expected):void{
+	#[Test]
+	#[TestWith([0.0, 0.1], 'min')]
+	#[TestWith([0.5, 0.5], 'no clamp')]
+	#[TestWith([1.5, 0.75], 'max')]
+	public function clampCircleRadius(float $value, float $expected):void{
 		$o = new QROptions(['circleRadius' => $value]);
 
 		$this::assertSame($expected, $o->circleRadius);