ECITest.php 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. <?php
  2. /**
  3. * ECITest.php
  4. *
  5. * @created 12.03.2023
  6. * @author smiley <smiley@chillerlan.net>
  7. * @copyright 2023 smiley
  8. * @license MIT
  9. */
  10. declare(strict_types=1);
  11. namespace chillerlan\QRCodeTest\Data;
  12. use chillerlan\QRCode\QROptions;
  13. use chillerlan\QRCode\Common\{BitBuffer, ECICharset, Mode};
  14. use chillerlan\QRCode\Data\{Byte, ECI, Hanzi, QRCodeDataException, QRData};
  15. use PHPUnit\Framework\TestCase;
  16. use PHPUnit\Framework\Attributes\{DataProvider, Test};
  17. /**
  18. * Tests the ECI class
  19. */
  20. final class ECITest extends TestCase{
  21. private QRData $QRData;
  22. private int $testCharset = ECICharset::GB18030;
  23. private const testData = '无可奈何燃花作香';
  24. protected function setUp():void{
  25. $this->QRData = new QRData(new QROptions);
  26. }
  27. /**
  28. * @phpstan-return array{0: ECI, 1: Byte}
  29. */
  30. private function getDataSegments():array{
  31. return [
  32. new ECI($this->testCharset),
  33. /** @phan-suppress-next-line PhanParamSuspiciousOrder */
  34. new Byte(mb_convert_encoding(self::testData, ECICharset::MB_ENCODINGS[$this->testCharset], mb_internal_encoding())),
  35. ];
  36. }
  37. /**
  38. * returns versions within the version breakpoints 1-9, 10-26 and 27-40
  39. *
  40. * @phpstan-return array<string, array{0: int}>
  41. */
  42. public static function versionBreakpointProvider():array{
  43. return ['1-9' => [7], '10-26' => [15], '27-40' => [30]];
  44. }
  45. #[Test]
  46. #[DataProvider('versionBreakpointProvider')]
  47. public function decodeSegment(int $version):void{
  48. $options = new QROptions;
  49. $options->version = $version;
  50. /** @var \chillerlan\QRCode\Data\QRDataModeInterface[] $segments */
  51. $segments = $this->getDataSegments();
  52. // invoke a QRData instance and write data
  53. $this->QRData = new QRData($options, $segments);
  54. // get the filled bitbuffer
  55. $bitBuffer = $this->QRData->getBitBuffer();
  56. // read the first 4 bits
  57. $this::assertSame($segments[0]::DATAMODE, $bitBuffer->read(4));
  58. // decode the data
  59. $this::assertSame(self::testData, (new ECI)->decodeSegment($bitBuffer, $options->version));
  60. }
  61. #[Test]
  62. public function invalidDataException():void{
  63. $this->expectException(QRCodeDataException::class);
  64. $this->expectExceptionMessage('invalid encoding id:');
  65. new ECI(-1);
  66. }
  67. /**
  68. * since the ECI class only accepts integer values,
  69. * we'll use this test to check for the upper end of the accepted input range
  70. */
  71. #[Test]
  72. public function invalidDataOnEmptyException():void{
  73. $this->expectException(QRCodeDataException::class);
  74. $this->expectExceptionMessage('invalid encoding id:');
  75. new ECI(1000000);
  76. }
  77. /**
  78. * @phpstan-return array<int, array{0: int, 1: int}>
  79. */
  80. public static function eciCharsetIdProvider():array{
  81. return [
  82. [ 0, 8],
  83. [ 127, 8],
  84. [ 128, 16],
  85. [ 16383, 16],
  86. [ 16384, 24],
  87. [999999, 24],
  88. ];
  89. }
  90. #[Test]
  91. #[DataProvider('eciCharsetIdProvider')]
  92. public function readWrite(int $id, int $lengthInBits):void{
  93. $bitBuffer = new BitBuffer;
  94. $eci = (new ECI($id))->write($bitBuffer, 1);
  95. $this::assertSame($lengthInBits, $eci->getLengthInBits());
  96. $this::assertSame(Mode::ECI, $bitBuffer->read(4));
  97. $this::assertSame($id, $eci->parseValue($bitBuffer)->getID());
  98. }
  99. /**
  100. * Tests if and exception is thrown when the ECI segment is followed by a mode that is not 8-bit byte
  101. */
  102. #[Test]
  103. public function decodeECISegmentFollowedByInvalidModeException():void{
  104. $this->expectException(QRCodeDataException::class);
  105. $this->expectExceptionMessage('ECI designator followed by invalid mode:');
  106. $options = new QROptions;
  107. $options->version = 5;
  108. /** @var \chillerlan\QRCode\Data\QRDataModeInterface[] $segments */
  109. $segments = $this->getDataSegments();
  110. // follow the ECI segment by a non-8bit-byte segment
  111. $segments[1] = new Hanzi(self::testData);
  112. $bitBuffer = (new QRData($options, $segments))->getBitBuffer();
  113. // verify the ECI mode indicator
  114. $this::assertSame(Mode::ECI, $bitBuffer->read(4));
  115. // throw
  116. (new ECI)->decodeSegment($bitBuffer, $options->version);
  117. }
  118. /**
  119. * @phpstan-return array<string, array{0: int, 1: string}>
  120. */
  121. public static function unknownEncodingDataProvider():array{
  122. return [
  123. 'CP437' => [0, "\x41\x42\x43"],
  124. 'ISO_IEC_8859_1_GLI' => [1, "\x41\x42\x43"],
  125. ];
  126. }
  127. /**
  128. * Tests detection of an unknown character set
  129. */
  130. #[Test]
  131. #[DataProvider('unknownEncodingDataProvider')]
  132. public function convertUnknownEncoding(int $id, string $data):void{
  133. $options = new QROptions;
  134. $options->version = 5;
  135. $segments = [new ECI($id), new Byte($data)];
  136. $bitBuffer = (new QRData($options, $segments))->getBitBuffer();
  137. $this::assertSame(Mode::ECI, $bitBuffer->read(4));
  138. $this::assertSame($data,(new ECI)->decodeSegment($bitBuffer, $options->version));
  139. }
  140. }