Skip to content

Commit

Permalink
Add tests
Browse files Browse the repository at this point in the history
  • Loading branch information
Shane32 committed May 19, 2024
1 parent b8ba8c9 commit 4ca3ec9
Show file tree
Hide file tree
Showing 4 changed files with 113 additions and 10 deletions.
4 changes: 2 additions & 2 deletions QRCoder/PayloadGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public static class PayloadGenerator
public abstract class Payload
{
public virtual int Version { get { return -1; } }
public virtual QRCodeGenerator.ECCLevel? EccLevel { get { return null; } } // null is default
public virtual QRCodeGenerator.ECCLevel EccLevel { get { return QRCodeGenerator.ECCLevel.Default; } } // null is default
public virtual QRCodeGenerator.EciMode EciMode { get { return QRCodeGenerator.EciMode.Default; } }
public abstract override string ToString();
}
Expand Down Expand Up @@ -2380,7 +2380,7 @@ public class SlovenianUpnQr : Payload
private string _recipientSiReference = "";

public override int Version { get { return 15; } }
public override QRCodeGenerator.ECCLevel? EccLevel { get { return QRCodeGenerator.ECCLevel.M; } }
public override QRCodeGenerator.ECCLevel EccLevel { get { return QRCodeGenerator.ECCLevel.M; } }
public override QRCodeGenerator.EciMode EciMode { get { return QRCodeGenerator.EciMode.Iso8859_2; } }

private string LimitLength(string value, int maxLength)
Expand Down
14 changes: 10 additions & 4 deletions QRCoder/QRCodeGenerator.ECCLevel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,29 +8,35 @@ public partial class QRCodeGenerator
/// </summary>
public enum ECCLevel
{
/// <summary>
/// Default error correction level, which will select Level M (Medium) unless otherwise specified by the payload.
/// Level M allows approximately 15% of data to be recovered, offering a balance between data capacity and error recovery.
/// </summary>
Default = -1,

/// <summary>
/// Level L: Low error correction (approximately 7% of data can be recovered).
/// This level allows the highest data density.
/// </summary>
L,
L = 0,

/// <summary>
/// Level M: Medium error correction (approximately 15% of data can be recovered).
/// Offers a balance between data capacity and error recovery.
/// </summary>
M,
M = 1,

/// <summary>
/// Level Q: Quartile error correction (approximately 25% of data can be recovered).
/// More robust error correction at the cost of reduced data capacity.
/// </summary>
Q,
Q = 2,

/// <summary>
/// Level H: High error correction (approximately 30% of data can be recovered).
/// Provides the highest level of error recovery, ideal for environments with high risk of data loss.
/// </summary>
H
H = 3
}
}
}
32 changes: 28 additions & 4 deletions QRCoder/QRCodeGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ public QRCodeData CreateQrCode(byte[] binaryData, ECCLevel eccLevel)
/// <returns>Returns the raw QR code data which can be used for rendering.</returns>
public static QRCodeData GenerateQrCode(PayloadGenerator.Payload payload)
{
return GenerateQrCode(payload.ToString(), payload.EccLevel ?? ECCLevel.M, false, false, payload.EciMode, payload.Version);
return GenerateQrCode(payload.ToString(), payload.EccLevel, false, false, payload.EciMode, payload.Version);
}

/// <summary>
Expand All @@ -105,8 +105,10 @@ public static QRCodeData GenerateQrCode(PayloadGenerator.Payload payload)
/// <returns>Returns the raw QR code data which can be used for rendering.</returns>
public static QRCodeData GenerateQrCode(PayloadGenerator.Payload payload, ECCLevel eccLevel)
{
if (payload.EccLevel.HasValue && eccLevel != payload.EccLevel.Value)
throw new ArgumentException($"The provided payload requires a ECC level of {eccLevel}.", nameof(eccLevel));
if (eccLevel == ECCLevel.Default)
eccLevel = payload.EccLevel;
else if (payload.EccLevel != ECCLevel.Default && eccLevel != payload.EccLevel)
throw new ArgumentOutOfRangeException(nameof(eccLevel), $"The provided payload requires a ECC level of {eccLevel}.");
return GenerateQrCode(payload.ToString(), eccLevel, false, false, payload.EciMode, payload.Version);
}

Expand All @@ -123,6 +125,7 @@ public static QRCodeData GenerateQrCode(PayloadGenerator.Payload payload, ECCLev
/// <returns>Returns the raw QR code data which can be used for rendering.</returns>
public static QRCodeData GenerateQrCode(string plainText, ECCLevel eccLevel, bool forceUtf8 = false, bool utf8BOM = false, EciMode eciMode = EciMode.Default, int requestedVersion = -1)
{
eccLevel = ValidateECCLevel(eccLevel);
EncodingMode encoding = GetEncodingFromPlaintext(plainText, forceUtf8);
var codedText = PlainTextToBinary(plainText, encoding, eciMode, utf8BOM, forceUtf8);
var dataInputLength = GetDataLength(encoding, plainText, codedText, forceUtf8);
Expand Down Expand Up @@ -167,7 +170,6 @@ public static QRCodeData GenerateQrCode(string plainText, ECCLevel eccLevel, boo
return GenerateQrCode(completeBitArray, eccLevel, version);
}


/// <summary>
/// Calculates the QR code data which than can be used in one of the rendering classes to generate a graphical representation.
/// </summary>
Expand All @@ -177,6 +179,7 @@ public static QRCodeData GenerateQrCode(string plainText, ECCLevel eccLevel, boo
/// <returns>Returns the raw QR code data which can be used for rendering.</returns>
public static QRCodeData GenerateQrCode(byte[] binaryData, ECCLevel eccLevel)
{
eccLevel = ValidateECCLevel(eccLevel);
int version = GetVersion(binaryData.Length, EncodingMode.Byte, eccLevel);

int countIndicatorLen = GetCountIndicatorLength(version, EncodingMode.Byte);
Expand All @@ -189,6 +192,27 @@ public static QRCodeData GenerateQrCode(byte[] binaryData, ECCLevel eccLevel)
return GenerateQrCode(bitArray, eccLevel, version);
}

/// <summary>
/// Validates the specified error correction level.
/// Returns the provided level if it is valid, or the level M if the provided level is Default.
/// Throws an exception if an invalid level is provided.
/// </summary>
private static ECCLevel ValidateECCLevel(ECCLevel eccLevel)
{
switch (eccLevel)
{
case ECCLevel.L:
case ECCLevel.M:
case ECCLevel.Q:
case ECCLevel.H:
return eccLevel;
case ECCLevel.Default:
return ECCLevel.M;
default:
throw new ArgumentOutOfRangeException(nameof(eccLevel), eccLevel, "Invalid error correction level.");
}
}

private static readonly BitArray _repeatingPattern = new BitArray(
new[] { true, true, true, false, true, true, false, false, false, false, false, true, false, false, false, true });

Expand Down
73 changes: 73 additions & 0 deletions QRCoderTests/QRGeneratorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using System.Linq;
using System.Collections;
using System.Text;
using System;

namespace QRCoderTests
{
Expand Down Expand Up @@ -226,6 +227,78 @@ bool IsValidISO(string input)
}
}
}

[Fact]
[Category("QRGenerator/EccLevel")]
public void ecc_level_from_payload_works()
{
var stringValue = "this is a test";

// set up baselines
var expectedL = Encode(QRCodeGenerator.GenerateQrCode(stringValue, QRCodeGenerator.ECCLevel.L));
var expectedM = Encode(QRCodeGenerator.GenerateQrCode(stringValue, QRCodeGenerator.ECCLevel.M));
var expectedQ = Encode(QRCodeGenerator.GenerateQrCode(stringValue, QRCodeGenerator.ECCLevel.Q));
var expectedH = Encode(QRCodeGenerator.GenerateQrCode(stringValue, QRCodeGenerator.ECCLevel.H));

// ensure that the baselines are different from each other
expectedL.ShouldNotBe(expectedM);
expectedL.ShouldNotBe(expectedQ);
expectedL.ShouldNotBe(expectedH);
expectedM.ShouldNotBe(expectedQ);
expectedM.ShouldNotBe(expectedH);
expectedQ.ShouldNotBe(expectedH);

// validate that any ECC level can be used when the payload specifies a default ECC level
var payloadDefault = new SamplePayload(stringValue, QRCodeGenerator.ECCLevel.Default);
Encode(QRCodeGenerator.GenerateQrCode(payloadDefault)).ShouldBe(expectedM);
Encode(QRCodeGenerator.GenerateQrCode(payloadDefault, QRCodeGenerator.ECCLevel.Default)).ShouldBe(expectedM);
Encode(QRCodeGenerator.GenerateQrCode(payloadDefault, QRCodeGenerator.ECCLevel.L)).ShouldBe(expectedL);
Encode(QRCodeGenerator.GenerateQrCode(payloadDefault, QRCodeGenerator.ECCLevel.M)).ShouldBe(expectedM);
Encode(QRCodeGenerator.GenerateQrCode(payloadDefault, QRCodeGenerator.ECCLevel.Q)).ShouldBe(expectedQ);
Encode(QRCodeGenerator.GenerateQrCode(payloadDefault, QRCodeGenerator.ECCLevel.H)).ShouldBe(expectedH);

// validate that the ECC level specified in the payload is used when default is specified,
// or checks that the selected ECC level matches the payload ECC level, throwing an exception otherwise
Verify(QRCodeGenerator.ECCLevel.L, expectedL);
Verify(QRCodeGenerator.ECCLevel.M, expectedM);
Verify(QRCodeGenerator.ECCLevel.Q, expectedQ);
Verify(QRCodeGenerator.ECCLevel.H, expectedH);


void Verify(QRCodeGenerator.ECCLevel eccLevel, string expected)
{
var payload = new SamplePayload(stringValue, eccLevel);
Encode(QRCodeGenerator.GenerateQrCode(payload)).ShouldBe(expected);
foreach (var ecc in Enum.GetValues(typeof(QRCodeGenerator.ECCLevel)).Cast<QRCodeGenerator.ECCLevel>())
{
if (ecc == eccLevel || ecc == QRCodeGenerator.ECCLevel.Default)
Encode(QRCodeGenerator.GenerateQrCode(payload, ecc)).ShouldBe(expected);
else
Should.Throw<ArgumentOutOfRangeException>(() => Encode(QRCodeGenerator.GenerateQrCode(payload, ecc)));
}
}

string Encode(QRCodeData qrData)
{
return string.Join("", qrData.ModuleMatrix.Select(x => x.ToBitString()).ToArray());
}
}

private class SamplePayload : PayloadGenerator.Payload
{
private string _data;
private QRCodeGenerator.ECCLevel _eccLevel;

public SamplePayload(string data, QRCodeGenerator.ECCLevel eccLevel)
{
_data = data;
_eccLevel = eccLevel;
}

public override QRCodeGenerator.ECCLevel EccLevel => _eccLevel;

public override string ToString() => _data;
}
}

public static class ExtensionMethods
Expand Down

0 comments on commit 4ca3ec9

Please sign in to comment.