@risleylima/escpos 0.0.13 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +60 -0
- package/README.md +798 -8
- package/docs/COVERAGE_ANALYSIS.md +98 -0
- package/docs/DEPENDENCIES_REVIEW.md +127 -0
- package/docs/JSDOC_REVIEW.md +122 -0
- package/docs/LIBRARY_OVERVIEW.md +383 -0
- package/docs/PRE_PUBLISH_CHECKLIST.md +331 -0
- package/docs/PUBLIC_API_ANALYSIS.md +224 -0
- package/docs/README.md +34 -0
- package/docs/SERIALPORT_V13_MIGRATION_COMPLETE.md +127 -0
- package/docs/TESTS_IMPLEMENTED.md +129 -0
- package/docs/USB_V2_REVIEW.md +148 -0
- package/docs/VERIFICATION_RESULTS.md +172 -0
- package/jest.config.js +16 -0
- package/package.json +12 -7
- package/src/adapter/index.js +37 -0
- package/src/printer/commands.js +6 -4
- package/src/printer/image.js +28 -7
- package/src/printer/index.js +7 -2
- package/src/printer/utils.js +21 -14
- package/src/serial-adapter/index.js +133 -84
- package/src/usb-adapter/index.js +157 -43
- package/tests/README.md +67 -0
- package/tests/integration/printer-flow.test.js +128 -0
- package/tests/unit/adapters/adapter.test.js +49 -0
- package/tests/unit/adapters/serial-adapter.test.js +224 -0
- package/tests/unit/adapters/usb-adapter.test.js +319 -0
- package/tests/unit/image/image.test.js +157 -0
- package/tests/unit/printer/buffer.test.js +60 -0
- package/tests/unit/printer/commands.test.js +109 -0
- package/tests/unit/printer/printer.test.js +405 -0
- package/tests/unit/utils/utils.test.js +96 -0
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const USB = require('../../../src/usb-adapter');
|
|
4
|
+
const usb = require('usb');
|
|
5
|
+
|
|
6
|
+
// Mock usb module (v2 API - uses Promises)
|
|
7
|
+
jest.mock('usb', () => {
|
|
8
|
+
const createMockDevice = () => {
|
|
9
|
+
const mockEndpoint = {
|
|
10
|
+
direction: 'out',
|
|
11
|
+
transfer: jest.fn().mockResolvedValue(undefined)
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const mockInterface = {
|
|
15
|
+
isKernelDriverActive: jest.fn().mockReturnValue(false),
|
|
16
|
+
claim: jest.fn().mockResolvedValue(undefined),
|
|
17
|
+
release: jest.fn().mockResolvedValue(undefined),
|
|
18
|
+
detachKernelDriver: jest.fn().mockResolvedValue(undefined),
|
|
19
|
+
descriptor: {
|
|
20
|
+
bInterfaceClass: 0x07, // PRINTER class
|
|
21
|
+
bInterfaceNumber: 0
|
|
22
|
+
},
|
|
23
|
+
endpoints: [mockEndpoint]
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
// Link endpoint to interface (v2 API structure)
|
|
27
|
+
mockEndpoint.interface = mockInterface;
|
|
28
|
+
|
|
29
|
+
return {
|
|
30
|
+
open: jest.fn().mockResolvedValue(undefined),
|
|
31
|
+
close: jest.fn().mockResolvedValue(undefined),
|
|
32
|
+
interfaces: [mockInterface],
|
|
33
|
+
configDescriptor: {
|
|
34
|
+
interfaces: [
|
|
35
|
+
[
|
|
36
|
+
{
|
|
37
|
+
bInterfaceClass: 0x07 // PRINTER class
|
|
38
|
+
}
|
|
39
|
+
]
|
|
40
|
+
]
|
|
41
|
+
},
|
|
42
|
+
deviceDescriptor: {
|
|
43
|
+
iManufacturer: 1,
|
|
44
|
+
iProduct: 2
|
|
45
|
+
},
|
|
46
|
+
getStringDescriptor: jest.fn().mockResolvedValue('Device String')
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
// Create a single shared mock device instance that persists across tests
|
|
51
|
+
// This ensures the same device is returned every time, maintaining state
|
|
52
|
+
const mockDeviceInstance = createMockDevice();
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
getDeviceList: jest.fn().mockReturnValue([mockDeviceInstance]),
|
|
56
|
+
findByIds: jest.fn().mockReturnValue(mockDeviceInstance),
|
|
57
|
+
on: jest.fn()
|
|
58
|
+
};
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
describe('USB Adapter', () => {
|
|
62
|
+
beforeEach(() => {
|
|
63
|
+
// Don't clear mocks that break the device structure
|
|
64
|
+
// Only clear call history for methods that need it
|
|
65
|
+
const mockDevice = usb.findByIds(1046, 20497);
|
|
66
|
+
if (mockDevice) {
|
|
67
|
+
// Clear call history but preserve mock structure
|
|
68
|
+
if (mockDevice.open && typeof mockDevice.open.mockClear === 'function') {
|
|
69
|
+
mockDevice.open.mockClear();
|
|
70
|
+
}
|
|
71
|
+
if (mockDevice.close && typeof mockDevice.close.mockClear === 'function') {
|
|
72
|
+
mockDevice.close.mockClear();
|
|
73
|
+
}
|
|
74
|
+
if (mockDevice.interfaces && mockDevice.interfaces[0]) {
|
|
75
|
+
if (mockDevice.interfaces[0].claim && typeof mockDevice.interfaces[0].claim.mockClear === 'function') {
|
|
76
|
+
mockDevice.interfaces[0].claim.mockClear();
|
|
77
|
+
}
|
|
78
|
+
if (mockDevice.interfaces[0].release && typeof mockDevice.interfaces[0].release.mockClear === 'function') {
|
|
79
|
+
mockDevice.interfaces[0].release.mockClear();
|
|
80
|
+
}
|
|
81
|
+
if (mockDevice.interfaces[0].endpoints && mockDevice.interfaces[0].endpoints[0]) {
|
|
82
|
+
if (mockDevice.interfaces[0].endpoints[0].transfer && typeof mockDevice.interfaces[0].endpoints[0].transfer.mockClear === 'function') {
|
|
83
|
+
mockDevice.interfaces[0].endpoints[0].transfer.mockClear();
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
// Don't clear findByIds mock as it needs to return the device
|
|
89
|
+
// usb.findByIds.mockClear(); // Commented out to preserve mock return value
|
|
90
|
+
// usb.getDeviceList.mockClear(); // Commented out to preserve mock return value
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
describe('listUSB', () => {
|
|
94
|
+
it('should list USB printer devices', async () => {
|
|
95
|
+
const devices = await USB.listUSB();
|
|
96
|
+
expect(Array.isArray(devices)).toBe(true);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('should filter only printer devices', async () => {
|
|
100
|
+
const devices = await USB.listUSB();
|
|
101
|
+
// Should only return devices with PRINTER interface class
|
|
102
|
+
expect(devices.length).toBeGreaterThanOrEqual(0);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('should get device manufacturer and product strings', async () => {
|
|
106
|
+
const devices = await USB.listUSB();
|
|
107
|
+
if (devices.length > 0) {
|
|
108
|
+
const device = devices[0];
|
|
109
|
+
expect(device.manufacturer).toBeDefined();
|
|
110
|
+
expect(device.product).toBeDefined();
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('should handle devices without descriptors gracefully', async () => {
|
|
115
|
+
// Mock a device that fails to get descriptor
|
|
116
|
+
const mockDeviceWithoutDescriptor = {
|
|
117
|
+
open: jest.fn().mockResolvedValue(undefined),
|
|
118
|
+
close: jest.fn().mockResolvedValue(undefined),
|
|
119
|
+
getStringDescriptor: jest.fn().mockRejectedValue(new Error('Descriptor error')),
|
|
120
|
+
configDescriptor: {
|
|
121
|
+
interfaces: [
|
|
122
|
+
[
|
|
123
|
+
{
|
|
124
|
+
bInterfaceClass: 0x07
|
|
125
|
+
}
|
|
126
|
+
]
|
|
127
|
+
]
|
|
128
|
+
},
|
|
129
|
+
deviceDescriptor: {
|
|
130
|
+
iManufacturer: 1,
|
|
131
|
+
iProduct: 2
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
usb.getDeviceList.mockReturnValueOnce([mockDeviceWithoutDescriptor]);
|
|
136
|
+
|
|
137
|
+
const devices = await USB.listUSB();
|
|
138
|
+
// Should not include devices that fail to get descriptors
|
|
139
|
+
expect(devices.length).toBe(0);
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
describe('connect', () => {
|
|
144
|
+
it('should connect to device by VID/PID', async () => {
|
|
145
|
+
const result = await USB.connect(1046, 20497);
|
|
146
|
+
expect(result).toBe(true);
|
|
147
|
+
expect(usb.findByIds).toHaveBeenCalledWith(1046, 20497);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('should connect to first available device if no VID/PID', async () => {
|
|
151
|
+
const result = await USB.connect();
|
|
152
|
+
expect(result).toBe(true);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('should throw error if device not found', async () => {
|
|
156
|
+
usb.findByIds.mockReturnValueOnce(null);
|
|
157
|
+
usb.getDeviceList.mockReturnValueOnce([]);
|
|
158
|
+
|
|
159
|
+
await expect(USB.connect(9999, 9999)).rejects.toThrow('Cannot find printer!');
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it('should emit connect event', (done) => {
|
|
163
|
+
USB.once('connect', () => {
|
|
164
|
+
done();
|
|
165
|
+
});
|
|
166
|
+
USB.connect(1046, 20497);
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
describe('open', () => {
|
|
171
|
+
beforeEach(async () => {
|
|
172
|
+
await USB.connect(1046, 20497);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it('should open device connection', async () => {
|
|
176
|
+
const result = await USB.open();
|
|
177
|
+
expect(result).toBe(true);
|
|
178
|
+
|
|
179
|
+
// Verify device.open was called
|
|
180
|
+
const mockDevice = usb.findByIds(1046, 20497);
|
|
181
|
+
expect(mockDevice.open).toHaveBeenCalled();
|
|
182
|
+
|
|
183
|
+
// Verify interface was claimed
|
|
184
|
+
if (mockDevice.interfaces && mockDevice.interfaces[0]) {
|
|
185
|
+
expect(mockDevice.interfaces[0].claim).toHaveBeenCalled();
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it('should find output endpoint', async () => {
|
|
190
|
+
await USB.open();
|
|
191
|
+
// Endpoint should be found (mocked)
|
|
192
|
+
expect(USB.write).toBeDefined();
|
|
193
|
+
|
|
194
|
+
// Verify endpoint was found
|
|
195
|
+
const mockDevice = usb.findByIds(1046, 20497);
|
|
196
|
+
if (mockDevice.interfaces && mockDevice.interfaces[0] && mockDevice.interfaces[0].endpoints[0]) {
|
|
197
|
+
expect(mockDevice.interfaces[0].endpoints[0].direction).toBe('out');
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it('should throw error if endpoint not found', async () => {
|
|
202
|
+
const mockDevice = usb.findByIds(1046, 20497);
|
|
203
|
+
// Save original interfaces to restore later
|
|
204
|
+
const originalInterfaces = mockDevice.interfaces;
|
|
205
|
+
|
|
206
|
+
const mockInterfaceWithoutEndpoints = {
|
|
207
|
+
isKernelDriverActive: jest.fn().mockReturnValue(false),
|
|
208
|
+
claim: jest.fn().mockResolvedValue(undefined),
|
|
209
|
+
release: jest.fn().mockResolvedValue(undefined),
|
|
210
|
+
detachKernelDriver: jest.fn().mockResolvedValue(undefined),
|
|
211
|
+
descriptor: {
|
|
212
|
+
bInterfaceClass: 0x07,
|
|
213
|
+
bInterfaceNumber: 0
|
|
214
|
+
},
|
|
215
|
+
endpoints: [] // No endpoints
|
|
216
|
+
};
|
|
217
|
+
mockDevice.interfaces = [mockInterfaceWithoutEndpoints];
|
|
218
|
+
|
|
219
|
+
try {
|
|
220
|
+
await expect(USB.open()).rejects.toThrow('Can not find endpoint from printer');
|
|
221
|
+
} finally {
|
|
222
|
+
// Restore original interfaces to prevent affecting other tests
|
|
223
|
+
mockDevice.interfaces = originalInterfaces;
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
describe('write', () => {
|
|
229
|
+
beforeEach(async () => {
|
|
230
|
+
await USB.connect(1046, 20497);
|
|
231
|
+
await USB.open();
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it('should write data to endpoint', async () => {
|
|
235
|
+
const testData = Buffer.from('test', 'ascii');
|
|
236
|
+
const result = await USB.write(testData);
|
|
237
|
+
expect(result).toBe(true);
|
|
238
|
+
|
|
239
|
+
// Verify that endpoint.transfer was called with correct data
|
|
240
|
+
const mockDevice = usb.findByIds(1046, 20497);
|
|
241
|
+
if (mockDevice.interfaces && mockDevice.interfaces[0] && mockDevice.interfaces[0].endpoints[0]) {
|
|
242
|
+
expect(mockDevice.interfaces[0].endpoints[0].transfer).toHaveBeenCalledWith(testData);
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
describe('close', () => {
|
|
248
|
+
beforeEach(async () => {
|
|
249
|
+
await USB.connect(1046, 20497);
|
|
250
|
+
await USB.open();
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
it('should close device connection', async () => {
|
|
254
|
+
const result = await USB.close();
|
|
255
|
+
expect(result).toBe(true);
|
|
256
|
+
|
|
257
|
+
// Verify that interface release was called
|
|
258
|
+
const mockDevice = usb.findByIds(1046, 20497);
|
|
259
|
+
if (mockDevice.interfaces && mockDevice.interfaces[0]) {
|
|
260
|
+
expect(mockDevice.interfaces[0].release).toHaveBeenCalled();
|
|
261
|
+
}
|
|
262
|
+
expect(mockDevice.close).toHaveBeenCalled();
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
it('should emit close event', async () => {
|
|
266
|
+
let eventReceived = false;
|
|
267
|
+
const promise = new Promise((resolve) => {
|
|
268
|
+
USB.once('close', () => {
|
|
269
|
+
eventReceived = true;
|
|
270
|
+
resolve();
|
|
271
|
+
});
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
expect(USB.listenerCount('close')).toBe(1);
|
|
275
|
+
|
|
276
|
+
await USB.close();
|
|
277
|
+
|
|
278
|
+
await Promise.race([
|
|
279
|
+
promise,
|
|
280
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error('Close event not emitted within timeout')), 1000))
|
|
281
|
+
]);
|
|
282
|
+
|
|
283
|
+
expect(eventReceived).toBe(true);
|
|
284
|
+
});
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
describe('disconnect', () => {
|
|
288
|
+
beforeEach(async () => {
|
|
289
|
+
await USB.connect(1046, 20497);
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
it('should disconnect device', async () => {
|
|
293
|
+
const result = await USB.disconnect();
|
|
294
|
+
expect(result).toBe(true);
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
it('should emit disconnect event', async () => {
|
|
298
|
+
let eventReceived = false;
|
|
299
|
+
const promise = new Promise((resolve) => {
|
|
300
|
+
USB.once('disconnect', () => {
|
|
301
|
+
eventReceived = true;
|
|
302
|
+
resolve();
|
|
303
|
+
});
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
expect(USB.listenerCount('disconnect')).toBe(1);
|
|
307
|
+
|
|
308
|
+
await USB.disconnect();
|
|
309
|
+
|
|
310
|
+
await Promise.race([
|
|
311
|
+
promise,
|
|
312
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error('Disconnect event not emitted within timeout')), 1000))
|
|
313
|
+
]);
|
|
314
|
+
|
|
315
|
+
expect(eventReceived).toBe(true);
|
|
316
|
+
});
|
|
317
|
+
});
|
|
318
|
+
});
|
|
319
|
+
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const Image = require('../../../src/printer/image');
|
|
4
|
+
|
|
5
|
+
// Mock get-pixels
|
|
6
|
+
jest.mock('get-pixels', () => {
|
|
7
|
+
return jest.fn((url, type, callback) => {
|
|
8
|
+
// Simula pixels de uma imagem 4x4 RGB
|
|
9
|
+
const mockPixels = {
|
|
10
|
+
shape: [4, 4, 3], // width, height, channels
|
|
11
|
+
data: new Uint8Array(4 * 4 * 3).fill(0)
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
// Preenche alguns pixels como "pretos" (valores baixos)
|
|
15
|
+
for (let i = 0; i < 8; i++) {
|
|
16
|
+
mockPixels.data[i] = 50; // Pixel escuro
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Preenche outros como "brancos" (valores altos)
|
|
20
|
+
for (let i = 24; i < 48; i++) {
|
|
21
|
+
mockPixels.data[i] = 250; // Pixel claro
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
callback(null, mockPixels);
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
describe('Image', () => {
|
|
29
|
+
describe('load', () => {
|
|
30
|
+
it('should load image from URL', async () => {
|
|
31
|
+
const image = await Image.load('test.png', 'image/png');
|
|
32
|
+
expect(image).toBeInstanceOf(Image);
|
|
33
|
+
expect(image.size.width).toBe(4);
|
|
34
|
+
expect(image.size.height).toBe(4);
|
|
35
|
+
expect(image.size.colors).toBe(3);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('should reject on error', async () => {
|
|
39
|
+
const getPixels = require('get-pixels');
|
|
40
|
+
getPixels.mockImplementationOnce((url, type, callback) => {
|
|
41
|
+
callback(new Error('Load failed'), null);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
await expect(Image.load('invalid.png', 'image/png')).rejects.toThrow('Load failed');
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
describe('Constructor', () => {
|
|
49
|
+
it('should create image from pixels', () => {
|
|
50
|
+
const mockPixels = {
|
|
51
|
+
shape: [2, 2, 3],
|
|
52
|
+
data: new Uint8Array([0, 0, 0, 255, 255, 255, 0, 0, 0, 255, 255, 255])
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const image = new Image(mockPixels);
|
|
56
|
+
expect(image.size.width).toBe(2);
|
|
57
|
+
expect(image.size.height).toBe(2);
|
|
58
|
+
expect(image.size.colors).toBe(3);
|
|
59
|
+
expect(image.data.length).toBe(4);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('should convert pixels to binary data', () => {
|
|
63
|
+
const mockPixels = {
|
|
64
|
+
shape: [2, 2, 4], // width, height, channels (RGBA)
|
|
65
|
+
data: new Uint8Array([
|
|
66
|
+
0, 0, 0, 255, // Pixel 0: preto (RGB=0,0,0)
|
|
67
|
+
255, 255, 255, 255, // Pixel 1: branco (RGB=255,255,255)
|
|
68
|
+
0, 0, 0, 255, // Pixel 2: preto (RGB=0,0,0)
|
|
69
|
+
255, 255, 255, 255 // Pixel 3: branco (RGB=255,255,255)
|
|
70
|
+
])
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const image = new Image(mockPixels);
|
|
74
|
+
expect(image.data[0]).toBe(1); // Preto = 1 (r=0, g=0, b=0, não é branco)
|
|
75
|
+
expect(image.data[1]).toBe(0); // Branco = 0 (r>200, g>200, b>200)
|
|
76
|
+
expect(image.data[2]).toBe(1); // Preto = 1
|
|
77
|
+
expect(image.data[3]).toBe(0); // Branco = 0
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('should handle transparent pixels', () => {
|
|
81
|
+
const mockPixels = {
|
|
82
|
+
shape: [1, 1, 4], // RGBA
|
|
83
|
+
data: new Uint8Array([255, 255, 255, 0]) // Transparente
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const image = new Image(mockPixels);
|
|
87
|
+
expect(image.data[0]).toBe(0); // Transparente = 0
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
describe('toBitmap', () => {
|
|
92
|
+
it('should convert to bitmap format', async () => {
|
|
93
|
+
const image = await Image.load('test.png', 'image/png');
|
|
94
|
+
const bitmap = image.toBitmap(24);
|
|
95
|
+
|
|
96
|
+
expect(bitmap).toHaveProperty('data');
|
|
97
|
+
expect(bitmap).toHaveProperty('density');
|
|
98
|
+
expect(bitmap.density).toBe(24);
|
|
99
|
+
expect(Array.isArray(bitmap.data)).toBe(true);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('should use default density of 24', async () => {
|
|
103
|
+
const image = await Image.load('test.png', 'image/png');
|
|
104
|
+
const bitmap = image.toBitmap();
|
|
105
|
+
|
|
106
|
+
expect(bitmap.density).toBe(24);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('should handle different densities', async () => {
|
|
110
|
+
const image = await Image.load('test.png', 'image/png');
|
|
111
|
+
const bitmap8 = image.toBitmap(8);
|
|
112
|
+
const bitmap24 = image.toBitmap(24);
|
|
113
|
+
|
|
114
|
+
expect(bitmap8.density).toBe(8);
|
|
115
|
+
expect(bitmap24.density).toBe(24);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('should generate correct bitmap structure', async () => {
|
|
119
|
+
const image = await Image.load('test.png', 'image/png');
|
|
120
|
+
const bitmap = image.toBitmap(8);
|
|
121
|
+
|
|
122
|
+
expect(bitmap.data.length).toBeGreaterThan(0);
|
|
123
|
+
bitmap.data.forEach(line => {
|
|
124
|
+
expect(Array.isArray(line)).toBe(true);
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
describe('toRaster', () => {
|
|
130
|
+
it('should convert to raster format', async () => {
|
|
131
|
+
const image = await Image.load('test.png', 'image/png');
|
|
132
|
+
const raster = image.toRaster();
|
|
133
|
+
|
|
134
|
+
expect(raster).toHaveProperty('data');
|
|
135
|
+
expect(raster).toHaveProperty('width');
|
|
136
|
+
expect(raster).toHaveProperty('height');
|
|
137
|
+
expect(Array.isArray(raster.data)).toBe(true);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('should calculate correct raster dimensions', async () => {
|
|
141
|
+
const image = await Image.load('test.png', 'image/png');
|
|
142
|
+
const raster = image.toRaster();
|
|
143
|
+
|
|
144
|
+
// Width should be rounded up to nearest 8-bit boundary
|
|
145
|
+
expect(raster.width).toBeGreaterThan(0);
|
|
146
|
+
expect(raster.height).toBe(image.size.height);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it('should generate correct raster data structure', async () => {
|
|
150
|
+
const image = await Image.load('test.png', 'image/png');
|
|
151
|
+
const raster = image.toRaster();
|
|
152
|
+
|
|
153
|
+
expect(raster.data.length).toBe(raster.width * raster.height);
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const Printer = require('../../../src/printer');
|
|
4
|
+
|
|
5
|
+
describe('SpecBuffer', () => {
|
|
6
|
+
let printer;
|
|
7
|
+
let mockAdapter;
|
|
8
|
+
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
mockAdapter = {
|
|
11
|
+
write: jest.fn().mockResolvedValue(true)
|
|
12
|
+
};
|
|
13
|
+
printer = new Printer(mockAdapter);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
describe('Buffer Operations', () => {
|
|
17
|
+
it('should initialize with empty buffer', () => {
|
|
18
|
+
const buffer = printer.buffer.flush();
|
|
19
|
+
expect(buffer.length).toBe(0);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('should write data to buffer', () => {
|
|
23
|
+
printer.buffer.write('Hello', 'ascii');
|
|
24
|
+
const buffer = printer.buffer.flush();
|
|
25
|
+
expect(buffer.toString('ascii')).toBe('Hello');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('should write hex data to buffer', () => {
|
|
29
|
+
printer.buffer.write('1B40', 'hex');
|
|
30
|
+
const buffer = printer.buffer.flush();
|
|
31
|
+
expect(buffer.toString('hex').toUpperCase()).toBe('1B40');
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('should concatenate multiple writes', () => {
|
|
35
|
+
printer.buffer.write('Hello', 'ascii');
|
|
36
|
+
printer.buffer.write(' World', 'ascii');
|
|
37
|
+
const buffer = printer.buffer.flush();
|
|
38
|
+
expect(buffer.toString('ascii')).toBe('Hello World');
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('should flush and clear buffer', () => {
|
|
42
|
+
printer.buffer.write('Test', 'ascii');
|
|
43
|
+
const buffer1 = printer.buffer.flush();
|
|
44
|
+
const buffer2 = printer.buffer.flush();
|
|
45
|
+
|
|
46
|
+
expect(buffer1.toString('ascii')).toBe('Test');
|
|
47
|
+
expect(buffer2.length).toBe(0);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('should handle Buffer objects', () => {
|
|
51
|
+
const testBuffer = Buffer.from('Test', 'ascii');
|
|
52
|
+
// SpecBuffer.write expects (data, type), but can handle Buffer directly
|
|
53
|
+
// We'll test through printer methods instead
|
|
54
|
+
printer.print('Test');
|
|
55
|
+
const buffer = printer.buffer.flush();
|
|
56
|
+
expect(buffer.toString('ascii')).toBe('Test');
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const commands = require('../../../src/printer/commands');
|
|
4
|
+
|
|
5
|
+
describe('Commands', () => {
|
|
6
|
+
describe('numToHexString', () => {
|
|
7
|
+
it('should convert number to hex string', () => {
|
|
8
|
+
const result = commands.numToHexString(255);
|
|
9
|
+
expect(result).toBe('ff');
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it('should pad odd-length hex strings with leading zero', () => {
|
|
13
|
+
const result = commands.numToHexString(15);
|
|
14
|
+
expect(result).toBe('0f');
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('should handle zero', () => {
|
|
18
|
+
const result = commands.numToHexString(0);
|
|
19
|
+
expect(result).toBe('00');
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('should handle large numbers', () => {
|
|
23
|
+
const result = commands.numToHexString(256);
|
|
24
|
+
expect(result).toBe('0100');
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('should return empty string for NaN input', () => {
|
|
28
|
+
const result = commands.numToHexString('invalid');
|
|
29
|
+
expect(result).toBe('');
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('should return empty string for NaN value', () => {
|
|
33
|
+
const result = commands.numToHexString(NaN);
|
|
34
|
+
expect(result).toBe('');
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('should return empty string for undefined', () => {
|
|
38
|
+
const result = commands.numToHexString(undefined);
|
|
39
|
+
expect(result).toBe('');
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
describe('TXT_CUSTOM_SIZE', () => {
|
|
44
|
+
it('should clamp width to 8 when width > 8', () => {
|
|
45
|
+
const result = commands.TEXT_FORMAT.TXT_CUSTOM_SIZE(10, 2);
|
|
46
|
+
// When width is clamped to 8, the calculation is:
|
|
47
|
+
// widthDec = (8 - 1) * 16 = 112 (0x70)
|
|
48
|
+
// heightDec = (2 - 1) = 1 (0x01)
|
|
49
|
+
// sizeDec = 112 + 1 = 113 (0x71)
|
|
50
|
+
expect(result.toString('hex').toUpperCase()).toContain('1D2171');
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('should clamp width to 1 when width < 1', () => {
|
|
54
|
+
const result = commands.TEXT_FORMAT.TXT_CUSTOM_SIZE(0, 2);
|
|
55
|
+
// When width is clamped to 1, the calculation is:
|
|
56
|
+
// widthDec = (1 - 1) * 16 = 0 (0x00)
|
|
57
|
+
// heightDec = (2 - 1) = 1 (0x01)
|
|
58
|
+
// sizeDec = 0 + 1 = 1 (0x01)
|
|
59
|
+
expect(result.toString('hex').toUpperCase()).toContain('1D2101');
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('should clamp height to 8 when height > 8', () => {
|
|
63
|
+
const result = commands.TEXT_FORMAT.TXT_CUSTOM_SIZE(2, 10);
|
|
64
|
+
// When height is clamped to 8, the calculation is:
|
|
65
|
+
// widthDec = (2 - 1) * 16 = 16 (0x10)
|
|
66
|
+
// heightDec = (8 - 1) = 7 (0x07)
|
|
67
|
+
// sizeDec = 16 + 7 = 23 (0x17)
|
|
68
|
+
expect(result.toString('hex').toUpperCase()).toContain('1D2117');
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('should clamp height to 1 when height < 1', () => {
|
|
72
|
+
const result = commands.TEXT_FORMAT.TXT_CUSTOM_SIZE(2, 0);
|
|
73
|
+
// When height is clamped to 1, the calculation is:
|
|
74
|
+
// widthDec = (2 - 1) * 16 = 16 (0x10)
|
|
75
|
+
// heightDec = (1 - 1) = 0 (0x00)
|
|
76
|
+
// sizeDec = 16 + 0 = 16 (0x10)
|
|
77
|
+
expect(result.toString('hex').toUpperCase()).toContain('1D2110');
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('should clamp both width and height when both are out of range', () => {
|
|
81
|
+
const result = commands.TEXT_FORMAT.TXT_CUSTOM_SIZE(10, 0);
|
|
82
|
+
// When both are clamped: width=8, height=1
|
|
83
|
+
// widthDec = (8 - 1) * 16 = 112 (0x70)
|
|
84
|
+
// heightDec = (1 - 1) = 0 (0x00)
|
|
85
|
+
// sizeDec = 112 + 0 = 112 (0x70)
|
|
86
|
+
expect(result.toString('hex').toUpperCase()).toContain('1D2170');
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('should handle valid range values correctly', () => {
|
|
90
|
+
const result = commands.TEXT_FORMAT.TXT_CUSTOM_SIZE(4, 3);
|
|
91
|
+
// widthDec = (4 - 1) * 16 = 48 (0x30)
|
|
92
|
+
// heightDec = (3 - 1) = 2 (0x02)
|
|
93
|
+
// sizeDec = 48 + 2 = 50 (0x32)
|
|
94
|
+
expect(result.toString('hex').toUpperCase()).toContain('1D2132');
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('should handle boundary values (1 and 8)', () => {
|
|
98
|
+
const result1 = commands.TEXT_FORMAT.TXT_CUSTOM_SIZE(1, 1);
|
|
99
|
+
expect(result1.toString('hex').toUpperCase()).toContain('1D2100');
|
|
100
|
+
|
|
101
|
+
const result2 = commands.TEXT_FORMAT.TXT_CUSTOM_SIZE(8, 8);
|
|
102
|
+
// widthDec = (8 - 1) * 16 = 112 (0x70)
|
|
103
|
+
// heightDec = (8 - 1) = 7 (0x07)
|
|
104
|
+
// sizeDec = 112 + 7 = 119 (0x77)
|
|
105
|
+
expect(result2.toString('hex').toUpperCase()).toContain('1D2177');
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
|