在Windows系统上构建

This commit is contained in:
cheney
2026-07-09 20:03:35 +08:00
parent 22ae3ac755
commit 0652323b85
11 changed files with 642 additions and 19 deletions
+16
View File
@@ -0,0 +1,16 @@
// 生成测试用二维码图片(PNG),便于用「从图片扫描」功能验证。
// 用法:node tests/fixtures/generate-qr.mjs [网址] [输出路径]
import QRCode from "qrcode";
import { fileURLToPath } from "node:url";
import { dirname, resolve } from "node:path";
const __dirname = dirname(fileURLToPath(import.meta.url));
const url = process.argv[2] || "https://fapiao.example.com/open?id=20240701";
const out = resolve(__dirname, process.argv[3] || "sample-qr.png");
await QRCode.toFile(out, url, {
errorCorrectionLevel: "M",
margin: 4,
scale: 8,
});
console.log(`已生成二维码图片: ${out}\n内容: ${url}`);
+71
View File
@@ -0,0 +1,71 @@
import { describe, it, expect } from "vitest";
import QRCode from "qrcode";
import {
extractUrl,
decodeQrFromImageData,
} from "../src/qrscan.js";
/**
* 用 qrcode 库把文本渲染成 RGBA 像素数据(放大每个模块以贴近真实拍摄清晰度)。
* @param {string} text 待编码文本。
* @param {number} scale 每个二维码模块放大的像素倍数。
* @param {number} quiet 静区(模块数)。
* @returns {{data:Uint8ClampedArray,width:number,height:number}} 画布像素数据。
*/
function renderQrImageData(text, scale = 8, quiet = 4) {
const qr = QRCode.create(text, { errorCorrectionLevel: "M" });
const size = qr.modules.size;
const modules = qr.modules.data;
const dim = (size + quiet * 2) * scale;
const data = new Uint8ClampedArray(dim * dim * 4);
// 先整体填白(含静区)。
data.fill(255);
for (let row = 0; row < size; row += 1) {
for (let col = 0; col < size; col += 1) {
if (!modules[row * size + col]) continue; // 0 = 浅色,跳过
const x0 = (col + quiet) * scale;
const y0 = (row + quiet) * scale;
for (let dy = 0; dy < scale; dy += 1) {
for (let dx = 0; dx < scale; dx += 1) {
const idx = ((y0 + dy) * dim + (x0 + dx)) * 4;
data[idx] = 0;
data[idx + 1] = 0;
data[idx + 2] = 0;
data[idx + 3] = 255;
}
}
}
}
return { data, width: dim, height: dim };
}
describe("qrscan", () => {
// 4-1 网址校验:接受 http/https,拒绝其它内容。
it("4-1 extractUrl 仅接受 http/https 网址", () => {
expect(extractUrl("https://example.com/inv?id=1")).toBe(
"https://example.com/inv?id=1"
);
expect(extractUrl(" http://a.b/c ")).toBe("http://a.b/c");
expect(extractUrl("ftp://x")).toBe("");
expect(extractUrl("hello")).toBe("");
expect(extractUrl(null)).toBe("");
});
// 4-2 从生成的清晰二维码像素中解码,验证扫描核心逻辑正确。
it("4-2 decodeQrFromImageData 能识别清晰二维码", () => {
const url = "https://fapiao.example.com/open?id=20240701";
const imageData = renderQrImageData(url);
expect(decodeQrFromImageData(imageData)).toBe(url);
});
// 4-3 无法识别时返回 null,而不是抛错。
it("4-3 无二维码时返回 null", () => {
const blank = {
data: new Uint8ClampedArray(64 * 64 * 4).fill(255),
width: 64,
height: 64,
};
expect(decodeQrFromImageData(blank)).toBeNull();
expect(decodeQrFromImageData(null)).toBeNull();
});
});