@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
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
const { SerialPort } = require('serialport');
|
|
3
|
-
const EventEmitter = require('events');
|
|
4
3
|
const Adapter = require('../adapter');
|
|
5
4
|
|
|
6
5
|
const debug = require('debug')('escpos:serial-adapter');
|
|
7
6
|
|
|
8
7
|
const scope = {
|
|
9
8
|
port: null,
|
|
9
|
+
/**
|
|
10
|
+
* Verify that a serial port exists
|
|
11
|
+
* @private
|
|
12
|
+
* @async
|
|
13
|
+
* @param {String} port - Serial port path to verify
|
|
14
|
+
* @returns {Promise<String>} Verified port path
|
|
15
|
+
* @throws {Error} If port does not exist
|
|
16
|
+
*/
|
|
10
17
|
verifyPort: async (port) => {
|
|
11
18
|
let ports = await SerialPort.list();
|
|
12
19
|
if (!ports.find((i) => i.path === port)) {
|
|
@@ -16,110 +23,151 @@ const scope = {
|
|
|
16
23
|
}
|
|
17
24
|
}
|
|
18
25
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
scope.port.removeListener('close', clearPort);
|
|
40
|
-
scope.port = null;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
scope.port.on('close', clearPort);
|
|
44
|
-
|
|
45
|
-
debug('Device Connected and Open!');
|
|
46
|
-
Serial.emit('connect', scope.port);
|
|
47
|
-
|
|
48
|
-
resolve(true);
|
|
49
|
-
});
|
|
50
|
-
}
|
|
51
|
-
});
|
|
26
|
+
// Create Adapter instance first, so it's the same object used internally and exported
|
|
27
|
+
const Serial = new Adapter();
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Connect to a serial port printer
|
|
31
|
+
* @async
|
|
32
|
+
* @param {String} port - Serial port path (e.g., '/dev/ttyUSB0' or 'COM3')
|
|
33
|
+
* @param {Object} [options] - Serial port options (baudRate, dataBits, etc.)
|
|
34
|
+
* @returns {Promise<Boolean>} True if connection successful
|
|
35
|
+
* @throws {Error} If port does not exist or cannot be opened
|
|
36
|
+
* @fires Serial#connect
|
|
37
|
+
* @fires Serial#close (if reconnecting)
|
|
38
|
+
*/
|
|
39
|
+
Serial.connect = async (port, options) => {
|
|
40
|
+
// Close existing connection if any
|
|
41
|
+
if (scope.port) {
|
|
42
|
+
try {
|
|
43
|
+
await scope.port.close();
|
|
44
|
+
} catch (e) {
|
|
45
|
+
debug('Error closing existing port: ', e);
|
|
52
46
|
}
|
|
47
|
+
Serial.emit('close');
|
|
48
|
+
}
|
|
53
49
|
|
|
54
|
-
|
|
50
|
+
// Verify port exists
|
|
51
|
+
const portVerified = await scope.verifyPort(port);
|
|
52
|
+
|
|
53
|
+
// Create SerialPort instance (v13: no callback, autoOpen: false)
|
|
54
|
+
scope.port = new SerialPort(Object.assign(options || {}, {
|
|
55
|
+
path: portVerified,
|
|
56
|
+
autoOpen: false
|
|
57
|
+
}));
|
|
58
|
+
|
|
59
|
+
// Handle errors via events
|
|
60
|
+
scope.port.on('error', (err) => {
|
|
61
|
+
debug('Error on Serial Port: ', err);
|
|
62
|
+
});
|
|
55
63
|
|
|
64
|
+
// Handle close event
|
|
65
|
+
let clearPort = () => {
|
|
66
|
+
Serial.emit('disconnect', scope.port);
|
|
67
|
+
scope.port.removeListener('close', clearPort);
|
|
68
|
+
scope.port = null;
|
|
69
|
+
};
|
|
70
|
+
scope.port.on('close', clearPort);
|
|
71
|
+
|
|
72
|
+
// Open the port manually (v13: returns Promise)
|
|
73
|
+
try {
|
|
74
|
+
await scope.port.open();
|
|
75
|
+
debug('Device Connected and Open!');
|
|
76
|
+
Serial.emit('connect', scope.port);
|
|
77
|
+
return true;
|
|
78
|
+
} catch (err) {
|
|
79
|
+
debug('Error Opening the Selected Port: ', err);
|
|
56
80
|
if (scope.port) {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
})
|
|
60
|
-
|
|
61
|
-
|
|
81
|
+
try {
|
|
82
|
+
await scope.port.close();
|
|
83
|
+
} catch (closeErr) {
|
|
84
|
+
debug('Error closing port after open failure: ', closeErr);
|
|
85
|
+
}
|
|
62
86
|
}
|
|
63
|
-
|
|
87
|
+
throw err;
|
|
88
|
+
}
|
|
64
89
|
}
|
|
65
90
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
debug('Device
|
|
77
|
-
|
|
91
|
+
/**
|
|
92
|
+
* Open the serial port if it's closed
|
|
93
|
+
* @async
|
|
94
|
+
* @returns {Promise<Boolean>} True if port is open (or was already open)
|
|
95
|
+
* @throws {Error} If port cannot be opened
|
|
96
|
+
*/
|
|
97
|
+
Serial.open = async () => {
|
|
98
|
+
if (!scope.port.isOpen) {
|
|
99
|
+
try {
|
|
100
|
+
await scope.port.open();
|
|
101
|
+
debug('Device Opened!');
|
|
102
|
+
return scope.port.isOpen;
|
|
103
|
+
} catch (err) {
|
|
104
|
+
throw err;
|
|
78
105
|
}
|
|
79
|
-
}
|
|
106
|
+
} else {
|
|
107
|
+
debug('Device is already Opened!');
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
80
110
|
};
|
|
81
111
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
112
|
+
/**
|
|
113
|
+
* Write data to the serial port
|
|
114
|
+
* @async
|
|
115
|
+
* @param {Buffer} data - Data buffer to send to printer
|
|
116
|
+
* @returns {Promise<Boolean>} True if write successful
|
|
117
|
+
* @throws {Error} If write fails
|
|
118
|
+
*/
|
|
119
|
+
Serial.write = async (data) => {
|
|
120
|
+
try {
|
|
121
|
+
await scope.port.write(data);
|
|
122
|
+
await scope.port.drain();
|
|
123
|
+
return true;
|
|
124
|
+
} catch (e) {
|
|
125
|
+
throw e;
|
|
126
|
+
}
|
|
92
127
|
};
|
|
93
128
|
|
|
94
|
-
|
|
129
|
+
/**
|
|
130
|
+
* Close the serial port connection
|
|
131
|
+
* @async
|
|
132
|
+
* @param {Number} [timeout] - Timeout in milliseconds before closing (default: 50ms)
|
|
133
|
+
* @returns {Promise<Boolean>} True if port closed successfully
|
|
134
|
+
* @fires Serial#close
|
|
135
|
+
*/
|
|
136
|
+
Serial.close = async (timeout) => {
|
|
95
137
|
let time = Number(timeout);
|
|
96
138
|
if (Number.isNaN(time)) {
|
|
97
|
-
time = 50
|
|
139
|
+
time = 50;
|
|
98
140
|
}
|
|
99
141
|
|
|
100
|
-
|
|
101
|
-
scope.port.flush(
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
});
|
|
115
|
-
});
|
|
116
|
-
|
|
142
|
+
try {
|
|
143
|
+
await scope.port.flush();
|
|
144
|
+
await new Promise(resolve => setTimeout(resolve, time));
|
|
145
|
+
await scope.port.drain();
|
|
146
|
+
await scope.port.close();
|
|
147
|
+
// Emit event synchronously - this ensures listeners are called immediately
|
|
148
|
+
Serial.emit('close');
|
|
149
|
+
return true;
|
|
150
|
+
} catch (e) {
|
|
151
|
+
debug('Error while closing device: ', e);
|
|
152
|
+
// Emit event synchronously even on error - this ensures listeners are called immediately
|
|
153
|
+
Serial.emit('close');
|
|
154
|
+
return true; // Still resolve to allow cleanup
|
|
155
|
+
}
|
|
117
156
|
}
|
|
118
157
|
|
|
158
|
+
/**
|
|
159
|
+
* Disconnect from the serial port (calls close internally)
|
|
160
|
+
* @param {Number} [timeout] - Timeout in milliseconds before closing (default: 50ms)
|
|
161
|
+
* @returns {Promise<Boolean>} True if disconnection successful
|
|
162
|
+
*/
|
|
119
163
|
Serial.disconnect = (timeout) => {
|
|
120
164
|
return Serial.close(timeout);
|
|
121
165
|
}
|
|
122
166
|
|
|
167
|
+
/**
|
|
168
|
+
* Read data from the serial port
|
|
169
|
+
* @returns {Promise<Buffer>} Data received from the port
|
|
170
|
+
*/
|
|
123
171
|
Serial.read = () => {
|
|
124
172
|
return new Promise((resolve, reject) => {
|
|
125
173
|
let dataHandler = (data) => {
|
|
@@ -130,4 +178,5 @@ Serial.read = () => {
|
|
|
130
178
|
});
|
|
131
179
|
};
|
|
132
180
|
|
|
133
|
-
|
|
181
|
+
// Serial is already an Adapter instance, so export it directly
|
|
182
|
+
module.exports = Serial;
|
package/src/usb-adapter/index.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
'use strict'
|
|
2
|
-
const { EventEmitter } = require('stream');
|
|
3
2
|
const Adapter = require('../adapter');
|
|
4
3
|
const usb = require('usb');
|
|
5
4
|
const os = require('os');
|
|
@@ -23,16 +22,28 @@ const IFACE_CLASS = {
|
|
|
23
22
|
HUB: 0x09
|
|
24
23
|
};
|
|
25
24
|
|
|
26
|
-
|
|
25
|
+
// Create Adapter instance first, so it's the same object used internally and exported
|
|
26
|
+
const USB = new Adapter();
|
|
27
27
|
|
|
28
|
+
/**
|
|
29
|
+
* List all available USB printer devices
|
|
30
|
+
* @async
|
|
31
|
+
* @returns {Promise<Array>} Array of USB printer devices with manufacturer and product information
|
|
32
|
+
*/
|
|
28
33
|
USB.listUSB = async () => {
|
|
29
34
|
const devices = usb.getDeviceList().filter((device) => {
|
|
30
35
|
try {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
}
|
|
36
|
+
// In v2, we need to check configDescriptor for interface class
|
|
37
|
+
const configDescriptor = device.configDescriptor;
|
|
38
|
+
if (!configDescriptor || !configDescriptor.interfaces) {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
// configDescriptor.interfaces is an array of arrays (alternate settings)
|
|
42
|
+
return configDescriptor.interfaces.some((ifaceArray) => {
|
|
43
|
+
return ifaceArray.some((iface) => {
|
|
44
|
+
return iface.bInterfaceClass === IFACE_CLASS.PRINTER;
|
|
45
|
+
});
|
|
46
|
+
});
|
|
36
47
|
} catch (e) {
|
|
37
48
|
debug('Error while get device info: ', e);
|
|
38
49
|
return false;
|
|
@@ -41,21 +52,30 @@ USB.listUSB = async () => {
|
|
|
41
52
|
|
|
42
53
|
let retorno = [];
|
|
43
54
|
|
|
44
|
-
|
|
55
|
+
/**
|
|
56
|
+
* Get string descriptor from USB device
|
|
57
|
+
* @private
|
|
58
|
+
* @async
|
|
59
|
+
* @param {Object} device - USB device object
|
|
60
|
+
* @param {Number} type - Descriptor type index
|
|
61
|
+
* @returns {Promise<String|Boolean>} Descriptor string or false on error
|
|
62
|
+
*/
|
|
63
|
+
const getDescriptor = async (device, type) => {
|
|
45
64
|
try {
|
|
46
|
-
device.open();
|
|
47
|
-
device.getStringDescriptor(type
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
}
|
|
51
|
-
device.close();
|
|
52
|
-
resolve(data);
|
|
53
|
-
});
|
|
65
|
+
await device.open();
|
|
66
|
+
const data = await device.getStringDescriptor(type);
|
|
67
|
+
await device.close();
|
|
68
|
+
return data;
|
|
54
69
|
} catch (e) {
|
|
55
|
-
debug(
|
|
56
|
-
|
|
70
|
+
debug('Error while read device description: ', e);
|
|
71
|
+
try {
|
|
72
|
+
await device.close();
|
|
73
|
+
} catch (closeErr) {
|
|
74
|
+
// Ignore close errors
|
|
75
|
+
}
|
|
76
|
+
return false;
|
|
57
77
|
}
|
|
58
|
-
}
|
|
78
|
+
};
|
|
59
79
|
|
|
60
80
|
for (let device of devices) {
|
|
61
81
|
device.manufacturer = await getDescriptor(device, device.deviceDescriptor.iManufacturer);
|
|
@@ -68,6 +88,15 @@ USB.listUSB = async () => {
|
|
|
68
88
|
return retorno;
|
|
69
89
|
};
|
|
70
90
|
|
|
91
|
+
/**
|
|
92
|
+
* Connect to a USB printer device
|
|
93
|
+
* @async
|
|
94
|
+
* @param {Number} [vid] - Vendor ID (optional, if not provided, uses first available printer)
|
|
95
|
+
* @param {Number} [pid] - Product ID (optional, if not provided, uses first available printer)
|
|
96
|
+
* @returns {Promise<Boolean>} True if connection successful
|
|
97
|
+
* @throws {Error} If printer cannot be found
|
|
98
|
+
* @fires USB#connect
|
|
99
|
+
*/
|
|
71
100
|
USB.connect = async (vid, pid) => {
|
|
72
101
|
scope.device = null;
|
|
73
102
|
scope.endpoint = null;
|
|
@@ -96,27 +125,52 @@ USB.connect = async (vid, pid) => {
|
|
|
96
125
|
return true;
|
|
97
126
|
};
|
|
98
127
|
|
|
128
|
+
/**
|
|
129
|
+
* Open the USB device and claim the printer interface
|
|
130
|
+
* @async
|
|
131
|
+
* @returns {Promise<Boolean>} True if device opened successfully
|
|
132
|
+
* @throws {Error} If interfaces cannot be accessed or endpoint not found
|
|
133
|
+
* @fires USB#connect
|
|
134
|
+
*/
|
|
99
135
|
USB.open = async () => {
|
|
100
|
-
scope.device.open();
|
|
101
|
-
|
|
136
|
+
await scope.device.open();
|
|
137
|
+
|
|
138
|
+
// In v2, device.interfaces is a direct array of Interface objects
|
|
139
|
+
// We need to iterate through all interfaces to find the printer interface
|
|
140
|
+
const interfaces = scope.device.interfaces;
|
|
141
|
+
if (!interfaces || interfaces.length === 0) {
|
|
142
|
+
throw new Error('Cannot access device interfaces');
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
for (let interfaceObj of interfaces) {
|
|
102
146
|
if (scope.endpoint) {
|
|
103
147
|
break;
|
|
104
148
|
}
|
|
105
149
|
|
|
150
|
+
// Check if this interface is a printer interface
|
|
151
|
+
const descriptor = interfaceObj.descriptor;
|
|
152
|
+
if (descriptor && descriptor.bInterfaceClass !== IFACE_CLASS.PRINTER) {
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Claim interface (required on all platforms)
|
|
106
157
|
if ("win32" !== os.platform()) {
|
|
107
|
-
if
|
|
158
|
+
// On Linux/macOS, detach kernel driver first if active
|
|
159
|
+
if (interfaceObj.isKernelDriverActive()) {
|
|
108
160
|
try {
|
|
109
|
-
|
|
161
|
+
await interfaceObj.detachKernelDriver();
|
|
110
162
|
} catch (e) {
|
|
111
|
-
throw new Error(
|
|
163
|
+
throw new Error(`[ERROR] Could not detach kernel driver: ${e.message}`);
|
|
112
164
|
}
|
|
113
165
|
}
|
|
114
|
-
iface.claim(); // must be called before using any endpoints of this interface.
|
|
115
166
|
}
|
|
116
|
-
|
|
167
|
+
// Claim interface (required on all platforms before using endpoints)
|
|
168
|
+
await interfaceObj.claim();
|
|
169
|
+
|
|
170
|
+
for (let endpoint of interfaceObj.endpoints) {
|
|
117
171
|
if (scope.endpoint) {
|
|
118
172
|
break;
|
|
119
|
-
} else if (endpoint.direction
|
|
173
|
+
} else if (endpoint.direction === 'out') {
|
|
120
174
|
scope.endpoint = endpoint;
|
|
121
175
|
USB.emit('connect', scope.device);
|
|
122
176
|
debug('Device Opened!');
|
|
@@ -130,40 +184,100 @@ USB.open = async () => {
|
|
|
130
184
|
return true;
|
|
131
185
|
};
|
|
132
186
|
|
|
187
|
+
/**
|
|
188
|
+
* Close the USB device connection and release interfaces
|
|
189
|
+
* @async
|
|
190
|
+
* @returns {Promise<Boolean>} True if device closed successfully
|
|
191
|
+
* @fires USB#close
|
|
192
|
+
*/
|
|
133
193
|
USB.close = async () => {
|
|
194
|
+
const device = scope.device; // Save device reference before cleanup
|
|
195
|
+
|
|
134
196
|
if (scope.device) {
|
|
135
|
-
|
|
197
|
+
try {
|
|
198
|
+
// Release interfaces before closing
|
|
199
|
+
// Only release the interface we actually claimed
|
|
200
|
+
if (scope.endpoint && scope.endpoint.interface) {
|
|
201
|
+
const interfaceObj = scope.endpoint.interface;
|
|
202
|
+
try {
|
|
203
|
+
// Check if interface is still valid and was claimed
|
|
204
|
+
if (interfaceObj && typeof interfaceObj.release === 'function') {
|
|
205
|
+
await interfaceObj.release();
|
|
206
|
+
}
|
|
207
|
+
} catch (e) {
|
|
208
|
+
debug('Error releasing interface: ', e);
|
|
209
|
+
}
|
|
210
|
+
} else {
|
|
211
|
+
// Fallback: try to release all interfaces
|
|
212
|
+
const interfaces = scope.device.interfaces;
|
|
213
|
+
if (interfaces && interfaces.length > 0) {
|
|
214
|
+
for (let interfaceObj of interfaces) {
|
|
215
|
+
try {
|
|
216
|
+
// Only release if we claimed it (kernel driver was detached)
|
|
217
|
+
if (interfaceObj && typeof interfaceObj.release === 'function' && !interfaceObj.isKernelDriverActive()) {
|
|
218
|
+
await interfaceObj.release();
|
|
219
|
+
}
|
|
220
|
+
} catch (e) {
|
|
221
|
+
debug('Error releasing interface: ', e);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
await scope.device.close();
|
|
227
|
+
} catch (e) {
|
|
228
|
+
debug('Error closing device: ', e);
|
|
229
|
+
}
|
|
136
230
|
}
|
|
137
231
|
|
|
138
|
-
|
|
139
|
-
debug('Device Closed!');
|
|
232
|
+
// Clear endpoint before emitting event
|
|
140
233
|
scope.endpoint = null;
|
|
234
|
+
|
|
235
|
+
// Emit event synchronously - this ensures listeners are called immediately
|
|
236
|
+
USB.emit('close', device);
|
|
237
|
+
debug('Device Closed!');
|
|
141
238
|
|
|
142
239
|
return true;
|
|
143
240
|
}
|
|
144
241
|
|
|
242
|
+
/**
|
|
243
|
+
* Disconnect from the USB device (calls close internally)
|
|
244
|
+
* @async
|
|
245
|
+
* @returns {Promise<Boolean>} True if disconnection successful
|
|
246
|
+
* @fires USB#disconnect
|
|
247
|
+
*/
|
|
145
248
|
USB.disconnect = async () => {
|
|
249
|
+
const device = scope.device; // Save device reference before cleanup
|
|
250
|
+
|
|
146
251
|
if (scope.device) {
|
|
147
252
|
await USB.close().catch(e => { debug(e); return true });
|
|
148
253
|
}
|
|
149
|
-
|
|
150
|
-
|
|
254
|
+
|
|
255
|
+
// Clear scope before emitting event
|
|
151
256
|
scope.endpoint = null;
|
|
152
257
|
scope.device = null;
|
|
258
|
+
|
|
259
|
+
// Emit event synchronously - this ensures listeners are called immediately
|
|
260
|
+
USB.emit('disconnect', device);
|
|
261
|
+
debug('Device Disconnected!');
|
|
153
262
|
|
|
154
263
|
return true;
|
|
155
264
|
}
|
|
156
265
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
266
|
+
/**
|
|
267
|
+
* Write data to the USB printer
|
|
268
|
+
* @async
|
|
269
|
+
* @param {Buffer} data - Data buffer to send to printer
|
|
270
|
+
* @returns {Promise<Boolean>} True if write successful
|
|
271
|
+
* @throws {Error} If write fails
|
|
272
|
+
*/
|
|
273
|
+
USB.write = async (data) => {
|
|
274
|
+
try {
|
|
275
|
+
await scope.endpoint.transfer(data);
|
|
276
|
+
return true;
|
|
277
|
+
} catch (e) {
|
|
278
|
+
throw e;
|
|
279
|
+
}
|
|
167
280
|
}
|
|
168
281
|
|
|
169
|
-
|
|
282
|
+
// USB is already an Adapter instance, so export it directly
|
|
283
|
+
module.exports = USB;
|
package/tests/README.md
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# Tests - EscPos
|
|
2
|
+
|
|
3
|
+
This directory contains the test suite for the EscPos library.
|
|
4
|
+
|
|
5
|
+
## Structure
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
tests/
|
|
9
|
+
├── unit/ # Isolated unit tests
|
|
10
|
+
│ ├── printer/ # Printer class tests
|
|
11
|
+
│ ├── adapters/ # Adapter tests (USB/Serial)
|
|
12
|
+
│ ├── image/ # Image processing tests
|
|
13
|
+
│ └── utils/ # Utility functions tests
|
|
14
|
+
└── integration/ # Integration tests
|
|
15
|
+
└── printer-flow/ # Complete flow tests
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Running Tests
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
# Run all tests
|
|
22
|
+
npm test
|
|
23
|
+
|
|
24
|
+
# Run in watch mode
|
|
25
|
+
npm run test:watch
|
|
26
|
+
|
|
27
|
+
# Run with coverage
|
|
28
|
+
npm run test:coverage
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Coverage
|
|
32
|
+
|
|
33
|
+
The tests cover:
|
|
34
|
+
- ✅ Buffer operations
|
|
35
|
+
- ✅ Text formatting
|
|
36
|
+
- ✅ ESC/POS commands
|
|
37
|
+
- ✅ Image processing
|
|
38
|
+
- ✅ Barcodes
|
|
39
|
+
- ✅ Adapters (with mocks)
|
|
40
|
+
- ✅ Complete print flows
|
|
41
|
+
|
|
42
|
+
## Mocks
|
|
43
|
+
|
|
44
|
+
The tests use mocks for:
|
|
45
|
+
- **USB Adapter**: Mock of `usb` module
|
|
46
|
+
- **Serial Adapter**: Mock of `serialport` module
|
|
47
|
+
- **Image**: Mock of `get-pixels` module
|
|
48
|
+
|
|
49
|
+
This allows running tests without requiring real hardware.
|
|
50
|
+
|
|
51
|
+
## Adding New Tests
|
|
52
|
+
|
|
53
|
+
1. Create the test file in `tests/unit/` or `tests/integration/`
|
|
54
|
+
2. Use the `*.test.js` convention
|
|
55
|
+
3. Follow Jest's describe/it pattern
|
|
56
|
+
4. Use mocks for external dependencies
|
|
57
|
+
|
|
58
|
+
## Example
|
|
59
|
+
|
|
60
|
+
```javascript
|
|
61
|
+
describe('MyFeature', () => {
|
|
62
|
+
it('should do something', () => {
|
|
63
|
+
// test here
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
```
|
|
67
|
+
|