UNPKG

node-quirc

Version:

quirc for nodejs (QR decoder)

413 lines (383 loc) 17.6 kB
'use strict'; const fs = require("fs"); const path = require("path"); const util = require("util"); const jpeg = require("jpeg-js"); const chai = require("chai"); const expect = chai.expect; const quirc = require("../index.js"); // QR-code versions. const qr_versions = Array(40).fill(0).map((_, i) => i + 1); // QR-code ECC levels. const qr_ecc_levels = { ECC_LEVEL_M: "M", ECC_LEVEL_L: "L", ECC_LEVEL_H: "H", ECC_LEVEL_Q: "Q", }; // QR-code encoding modes. const qr_enc_modes = { MODE_NUMERIC: "NUMERIC", MODE_ALNUM: "ALNUM", MODE_BYTE: "BYTE", MODE_KANJI: "KANJI", }; const qr_eci = { ECI_ISO_8859_1: "ISO_8859_1", ECI_IBM437: "IBM437", ECI_ISO_8859_2: "ISO_8859_2", ECI_ISO_8859_3: "ISO_8859_3", ECI_ISO_8859_4: "ISO_8859_4", ECI_ISO_8859_5: "ISO_8859_5", ECI_ISO_8859_6: "ISO_8859_6", ECI_ISO_8859_7: "ISO_8859_7", ECI_ISO_8859_8: "ISO_8859_8", ECI_ISO_8859_9: "ISO_8859_9", ECI_WINDOWS_874: "WINDOWS_874", ECI_ISO_8859_13: "ISO_8859_13", ECI_ISO_8859_15: "ISO_8859_15", ECI_SHIFT_JIS: "SHIFT_JIS", ECI_UTF_8: "UTF_8", }; const extensions = ["png", "jpeg"]; /* helpers for test data files */ function test_data_path(local_path) { return path.join(__dirname, "data", local_path); } function read_test_data(local_path) { return fs.readFileSync(test_data_path(local_path)); } describe("constants", function () { describe("QR-code versions", function () { it("should set VERSION_MIN to 1", function () { expect(quirc.constants.VERSION_MIN).to.exist.and.to.eql(1); }); it("should set VERSION_MAX to 40", function () { expect(quirc.constants.VERSION_MAX).to.exist.and.to.eql(40); }); }); describe("QR-code ECC levels", function () { for (const [key, value] of Object.entries(qr_ecc_levels)) { it(`should set ${key} to ${value}`, function () { expect(quirc.constants[key]).to.exist.and.to.eql(value); }); } }); describe("QR-code encoding modes", function () { for (const [key, value] of Object.entries(qr_enc_modes)) { it(`should set ${key} to ${value}`, function () { expect(quirc.constants[key]).to.exist.and.to.eql(value); }); } }); describe("QR-code ECI", function () { for (const [key, value] of Object.entries(qr_eci)) { it(`should set ${key} to ${value}`, function () { expect(quirc.constants[key]).to.exist.and.to.eql(value); }); } }); }); describe("decode()", function () { describe("arguments", function () { it("should return a rejected Promise when no arguments are given", function (done) { const p = quirc.decode() expect(p).to.be.a("Promise"); p.catch((e) => { expect(e.message).to.eql("img must be a Buffer or ImageData"); done(); }); }); it("should return a Promise when only one argument is given", function () { const p = quirc.decode(Buffer.from("data")); expect(p).to.be.a("Promise"); p.catch((e) => { /* ignored */ }); }); it("should throw when img is not a Buffer", function () { expect(function () { quirc.decode("a string", function dummy() { }); }).to.throw(TypeError, "img must be a Buffer or ImageData"); }); it("should throw when callback is not a function", function () { expect(function () { quirc.decode(Buffer.from(""), "not a function"); }).to.throw(TypeError, "callback must be a function"); }); }); context("when the buffer data is empty", function () { it("should yield an Error", function (done) { quirc.decode(Buffer.from(""), function (err, codes) { expect(err).to.exist.and.to.be.an("error"); expect(err.message).to.eql("failed to load image"); return done(); }); }); }); context("when the buffer data is not an image", function () { it("should yield an Error", function (done) { quirc.decode(Buffer.from("Hello World"), function (err, codes) { expect(err).to.exist.and.to.be.an("error"); expect(err.message).to.eql("failed to load image"); return done(); }); }); }); extensions.forEach(function (ext) { context(`${ext}`, function () { context("when the image file has no QR Code", function () { let empty_image; before(function () { empty_image = read_test_data(`1x1.${ext}`); }); it("should not yield an Error", function (done) { quirc.decode(empty_image, function (err, codes) { expect(err).to.not.exist; return done(); }); }); it("should not yield a result", function (done) { quirc.decode(empty_image, function (err, codes) { expect(codes).to.be.an('array').and.to.have.length(0); return done(); }); }); }); context("when the image file has multiple QR Code", function () { let hello_plus_world; before(function () { hello_plus_world = read_test_data(`Hello+World.${ext}`); }); it("should not yield an Error", function (done) { quirc.decode(hello_plus_world, function (err, codes) { expect(err).to.not.exist; return done(); }); }); it("should yield two results", function (done) { quirc.decode(hello_plus_world, function (err, codes) { expect(codes).to.be.an('array').and.to.have.length(2); return done(); }); }); it("should yield the first QR Code", function (done) { quirc.decode(hello_plus_world, function (err, codes) { expect(codes[0].err).to.not.exist; expect(codes[0].version).to.eql(1); expect(codes[0].ecc_level).to.eql("H"); expect(codes[0].mask).to.eql(1); expect(codes[0].mode).to.eql("BYTE"); expect(codes[1].eci).to.eql("UTF_8"); expect(codes[0].data).to.be.an.instanceof(Buffer); expect(codes[0].data.toString()).to.eql("Hello"); return done(); }); }); it("should yield the second QR Code", function (done) { quirc.decode(hello_plus_world, function (err, codes) { expect(codes[1].err).to.not.exist; expect(codes[1].version).to.eql(1); expect(codes[1].ecc_level).to.eql("H"); expect(codes[1].mask).to.eql(3); expect(codes[1].mode).to.eql("BYTE"); expect(codes[1].eci).to.eql("UTF_8"); expect(codes[1].data).to.be.an.instanceof(Buffer); expect(codes[1].data.toString()).to.eql("World"); return done(); }); }); }); context("when the QR code is flipped", function () { let image_with_flipped_qr_code; before(function () { image_with_flipped_qr_code = read_test_data(`flipped.${ext}`); }); it("should not yield an Error", function (done) { quirc.decode(image_with_flipped_qr_code, function (err, codes) { expect(err).to.not.exist; return done(); }); }); it("should yield two results", function (done) { quirc.decode(image_with_flipped_qr_code, function (err, codes) { expect(codes).to.be.an('array').and.to.have.length(2); return done(); }); }); it("should yield the first QR Code", function (done) { quirc.decode(image_with_flipped_qr_code, function (err, codes) { expect(codes[0].err).to.not.exist; expect(codes[0].version).to.eql(3); expect(codes[0].ecc_level).to.eql("L"); expect(codes[0].mask).to.eql(3); expect(codes[0].mode).to.eql("BYTE"); expect(codes[0].eci).to.eql("UTF_8"); expect(codes[0].data).to.be.an.instanceof(Buffer); expect(codes[0].data.toString()).to.eql("The quick brown fox jumps over the lazy dog"); return done(); }); }); it("should yield the second QR Code", function (done) { quirc.decode(image_with_flipped_qr_code, function (err, codes) { expect(codes[1].err).to.not.exist; expect(codes[1].version).to.eql(3); expect(codes[1].ecc_level).to.eql("L"); expect(codes[1].mask).to.eql(3); expect(codes[1].mode).to.eql("BYTE"); expect(codes[1].eci).to.eql("UTF_8"); expect(codes[1].data).to.be.an.instanceof(Buffer); expect(codes[1].data.toString()).to.eql("The quick brown fox jumps over the lazy dog"); return done(); }); }); }); /* * This context() ensure that quirc was compiled with QUIRC_MAX_REGIONS > 256. * * see https://github.com/dlbeer/quirc/issues/2 for the test image file and * https://github.com/dlbeer/quirc/pull/9 for the rational. */ context("when the image file is big", function () { let big_image_with_two_qrcodes; before(function () { big_image_with_two_qrcodes = read_test_data(`big_image_with_two_qrcodes.${ext}`); }); it("should not yield an Error", function (done) { quirc.decode(big_image_with_two_qrcodes, function (err, codes) { expect(err).to.not.exist; return done(); }); }); it("should yield two results", function (done) { quirc.decode(big_image_with_two_qrcodes, function (err, codes) { expect(codes).to.be.an('array').and.to.have.length(2); return done(); }); }); it("should yield the first QR Code", function (done) { quirc.decode(big_image_with_two_qrcodes, function (err, codes) { expect(codes[0].err).to.not.exist; expect(codes[0].version).to.eql(4); expect(codes[0].ecc_level).to.eql("M"); expect(codes[0].mask).to.eql(2); expect(codes[0].mode).to.eql("BYTE"); expect(codes[0].data).to.be.an.instanceof(Buffer); expect(codes[0].data.toString()).to.eql("from javascript"); return done(); }); }); it("should yield the second QR Code", function (done) { quirc.decode(big_image_with_two_qrcodes, function (err, codes) { expect(codes[1].err).to.not.exist; expect(codes[1].version).to.eql(4); expect(codes[1].ecc_level).to.eql("M"); expect(codes[1].mask).to.eql(2); expect(codes[1].mode).to.eql("BYTE"); expect(codes[1].data).to.be.an.instanceof(Buffer); expect(codes[1].data.toString()).to.eql("here comes qr!"); return done(); }); }); }); }); }); context("using generated file", function () { const mode_to_data = { NUMERIC: "42", ALNUM: "AC-42", BYTE: "aA1234", KANJI: [0x93, 0x5f, 0xe4, 0xaa], // 点茗 in Shift-JIS }; function test_filename(version, ecc_level, mode) { const fmt = "version=%s,level=%s,mode=%s.png"; // pad version with a leading 0 if needed to "simulate" printf's // %02d format. const version_str = ("0" + version).slice(-2); return util.format(fmt, version_str, ecc_level, mode); } for (const version of qr_versions) { for (const [_, ecc_level] of Object.entries(qr_ecc_levels)) { for (const [_, mode] of Object.entries(qr_enc_modes)) { const fname = test_filename(version, ecc_level, mode); context(fname, function () { // relative path for test_data_path() and // read_test_data() const rpath = "generated/" + fname; // use accessSync(), because async it() calls // won't register as expected. let found = false; try { // XXX: TOCTOU but meh fs.accessSync(test_data_path(rpath), fs.constants.R_OK); found = true; } catch (e) { found = false; } if (!found) { it.skip(rpath + " not found, skipped"); } else { it("should yield the QR Code", function (done) { var image = read_test_data(rpath); quirc.decode(image, function (err, codes) { expect(err).to.not.exist; expect(codes).to.be.an('array').and.to.have.length(1); expect(codes[0].version).to.eql(version); expect(codes[0].ecc_level).to.eql(ecc_level); expect(codes[0].mode).to.eql(mode); expect(codes[0].data).to.be.an.instanceof(Buffer); expect(codes[0].data).to.eql(Buffer.from(mode_to_data[mode])); return done(); }); }); } }); } } } }); context("raw image data", function () { it("should read QR codes from raw image data", function (done) { const big_image_with_two_qrcodes = jpeg.decode( read_test_data("big_image_with_two_qrcodes.jpeg") ); quirc.decode(big_image_with_two_qrcodes, function (err, codes) { expect(codes).to.be.an("array").and.to.have.length(2); expect(codes[0].err).to.not.exist; expect(codes[0].version).to.eql(4); expect(codes[0].ecc_level).to.eql("M"); expect(codes[0].mask).to.eql(2); expect(codes[0].mode).to.eql("BYTE"); expect(codes[0].data).to.be.an.instanceof(Buffer); expect(codes[0].data.toString()).to.eql("from javascript"); expect(codes[1].err).to.not.exist; expect(codes[1].version).to.eql(4); expect(codes[1].ecc_level).to.eql("M"); expect(codes[1].mask).to.eql(2); expect(codes[1].mode).to.eql("BYTE"); expect(codes[1].data).to.be.an.instanceof(Buffer); expect(codes[1].data.toString()).to.eql("here comes qr!"); done(); }); }); }); context("regressions", function () { // https://github.com/dlbeer/quirc/pull/87 context("dark image", function () { let img; before(function () { img = read_test_data("black.png") }); it("should not yield an Error", function (done) { quirc.decode(img, function (err, codes) { expect(err).to.not.exist; return done(); }); }); it("should not yield a result", function (done) { quirc.decode(img, function (err, codes) { expect(codes).to.be.an('array').and.to.have.length(0); return done(); }); }); }); }); });