| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154 |
- const {test} = require('node:test');
- const assert = require('node:assert/strict');
- const fs = require('node:fs');
- const vm = require('node:vm');
- const path = require('node:path');
- function load(write) {
- const source = fs.readFileSync(path.join(__dirname, 'BluetoothPrinter.js'), 'utf8')
- .replace(/export function /g, 'function ')
- .replace('export default BluetoothPrinter;', 'globalThis.Printer = BluetoothPrinter; globalThis.encode = typeof encodeMonoRaster === "function" ? encodeMonoRaster : undefined;');
- const context = {ArrayBuffer, Uint8Array, Date, Promise, setTimeout, clearTimeout,
- console: {log(){}, warn(){}, error(){}},
- uni: {getSystemInfoSync: () => ({platform:'android'}), writeBLECharacteristicValue: write}};
- vm.runInNewContext(source, context);
- return context;
- }
- function printer(write) {
- const context = load(write);
- const p = new context.Printer({writeTimeoutMs:30});
- Object.assign(p, {isConnected:true, deviceId:'device', serviceId:'service', characteristicId:'characteristic'});
- return p;
- }
- // 独立解码GS v 0;将白色窄光栅补回完整宽度,验证高度和全部黑色像素不变。
- function decode(buffer, width) {
- const bytes = new Uint8Array(buffer), rows = [];
- for (let i=0; i<bytes.length;) {
- assert.deepEqual(Array.from(bytes.slice(i,i+4)), [29,118,48,0]);
- const stride=bytes[i+4]+bytes[i+5]*256, height=bytes[i+6]+bytes[i+7]*256;
- assert.ok(stride>0 && stride<=width/8 && height>0);
- i+=8;
- assert.ok(i+stride*height<=bytes.length);
- for(let y=0;y<height;y++) {
- const row = new Uint8Array(width/8);
- row.set(bytes.slice(i,i+stride)); rows.push(...row); i+=stride;
- }
- }
- return new Uint8Array(rows);
- }
- test('混合空白与非空行压缩后逐像素和高度一致', () => {
- const {encode} = load();
- const data = new Uint8Array(48*720);
- for(let y=110;y<320;y++) for(let x=0;x<48;x++) data[y*48+x]=(x*37+y)%256;
- const encoded = encode(data,384,720);
- assert.deepEqual(decode(encoded,384),data);
- assert.ok(encoded.byteLength < data.byteLength*0.5);
- });
- test('黑白交替、全白、全黑、短空白和2048行边界不改变数据', () => {
- const {encode} = load();
- for (const height of [1,7,8,255,2048,2050]) for(const mode of ['white','black','alternating']) {
- const data=new Uint8Array(48*height);
- for(let y=0;y<height;y++) if(mode==='black'||mode==='alternating'&&y%2) data.fill(255,y*48,(y+1)*48);
- const encoded=encode(data,384,height);
- assert.deepEqual(decode(encoded,384),data);
- assert.ok(encoded.byteLength<=data.byteLength+8*Math.ceil(height/2048));
- }
- });
- test('拒绝PNG或长度/尺寸不正确的点阵,关闭优化仍可正常还原', () => {
- const {encode} = load();
- assert.throws(()=>encode(new Uint8Array(7),384,2));
- assert.throws(()=>encode(new Uint8Array(48),383,1));
- assert.throws(()=>encode(new Uint8Array(0),384,0));
- const data=new Uint8Array(48*100);
- const result=encode(data,384,100,{compactBlankRows:false});
- assert.equal(result.byteLength,4808);
- assert.deepEqual(decode(result,384),data);
- });
- test('默认20字节且原始数据顺序不变',async()=>{
- const chunks=[];
- const p=printer(o=>{chunks.push(new Uint8Array(o.value));o.success({errMsg:'ok'});});
- const data=Uint8Array.from({length:43},(_,i)=>i);
- await p.sendPrintData(data.buffer);
- assert.deepEqual(chunks.map(c=>c.length),[20,20,3]);
- assert.deepEqual(Uint8Array.from(chunks.flatMap(c=>Array.from(c))),data);
- });
- test('已验证的连接传输参数降低分包数,且限制作用于当前设备',async()=>{
- const chunks=[];
- const p=printer(o=>{chunks.push(o.value.byteLength);o.success({errMsg:'ok'});});
- assert.throws(()=>p.setTransportProfile({deviceId:'other',maxWriteBytes:180,writeDelayMs:0}));
- assert.throws(()=>p.setTransportProfile({deviceId:'device',maxWriteBytes:0}));
- p.setTransportProfile({deviceId:'device',maxWriteBytes:180,writeDelayMs:0});
- await p.sendPrintData(new ArrayBuffer(361));
- assert.deepEqual(chunks,[180,180,1]);
- });
- test('整次发送互斥,进度节流且最终进度必达',async()=>{
- let first;
- const p=printer(o=>{if(!first)first=o;else o.success({errMsg:'ok'});});
- p.setTransportProfile({deviceId:'device',maxWriteBytes:20,writeDelayMs:0});
- const progress=[];
- const pending=p.sendPrintData(new ArrayBuffer(2000),(a,b)=>progress.push([a,b]));
- await assert.rejects(p.sendPrintData(new ArrayBuffer(20)),/进行中/);
- first.success({errMsg:'ok'});
- await pending;
- assert.ok(progress.length<10);
- assert.deepEqual(progress.at(-1),[2000,2000]);
- });
- test('写入失败不重试,禁止后续任务接在可能残缺的光栅后',async()=>{
- let writes=0;
- const p=printer(o=>{writes++;o.fail({errCode:10008,errMsg:'fail'});});
- await assert.rejects(p.sendPrintData(new ArrayBuffer(40)));
- await assert.rejects(p.sendPrintData(new ArrayBuffer(40)),/中断/);
- assert.equal(writes,1);
- });
- test('写入无回调会超时,不重发数据',async()=>{
- let writes=0;
- const p=printer(()=>writes++);
- await assert.rejects(p.sendPrintData(new ArrayBuffer(40)),/超时/);
- assert.equal(writes,1);
- });
- test('UI进度回调异常不重发或中断数据',async()=>{
- let bytes=0;
- const p=printer(o=>{bytes+=o.value.byteLength;o.success({errMsg:'ok'});});
- await p.sendPrintData(new ArrayBuffer(21),()=>{throw new Error('UI failure');});
- assert.equal(bytes,21);
- });
- module.exports={load,decode};
- test('错误后的人工恢复与传输参数清理',async()=>{
- let fail=true;
- const p=printer(o=>fail?o.fail({errCode:10007}):o.success({errMsg:'ok'}));
- p.setTransportProfile({deviceId:'device',maxWriteBytes:180,writeDelayMs:0});
- await assert.rejects(p.sendPrintData(new ArrayBuffer(8)));
- fail=false;
- p.confirmPrinterBufferCleared();
- await p.sendPrintData(new ArrayBuffer(8));
- // 断开时禁止把已测连接的包长沿用到另一台打印机。
- p.isConnected=false;
- p.disconnect();
- assert.equal(p.transportProfile,null);
- });
- test('超时后的迟到成功回调不会继续发送余下数据',async()=>{
- let late, writes=0;
- const p=printer(o=>{late=o;writes++;});
- await assert.rejects(p.sendPrintData(new ArrayBuffer(41)),/超时/);
- late.success({errMsg:'ok'});
- await Promise.resolve();
- assert.equal(writes,1);
- await assert.rejects(p.sendPrintData(new ArrayBuffer(1)),/中断/);
- });
- // 可选地传入由真实PNG解码得到的点阵样张;没有样张时仍运行全部独立单元测试。
- if (process.env.INVOICE_RASTER_SAMPLES) {
- const samples=JSON.parse(fs.readFileSync(process.env.INVOICE_RASTER_SAMPLES,'utf8'));
- for(const sample of samples) test(`实际发票样张像素还原:${sample.name}`,()=>{
- const data=new Uint8Array(Buffer.from(sample.raster,'base64'));
- const {encode}=load();
- const before=encode(data,sample.width,sample.height,{compactBlankRows:false});
- const after=encode(data,sample.width,sample.height);
- assert.deepEqual(decode(after,sample.width),data);
- assert.ok(after.byteLength<=before.byteLength);
- console.log(JSON.stringify({name:sample.name,width:sample.width,height:sample.height,
- pngBytes:sample.pngBytes,before:before.byteLength,after:after.byteLength,
- savedPercent:Math.round((1-after.byteLength/before.byteLength)*1000)/10}));
- });
- }
|