@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,128 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const Printer = require('../../src/printer');
|
|
4
|
+
|
|
5
|
+
describe('Printer Integration', () => {
|
|
6
|
+
let printer;
|
|
7
|
+
let mockAdapter;
|
|
8
|
+
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
mockAdapter = {
|
|
11
|
+
write: jest.fn().mockResolvedValue(true),
|
|
12
|
+
close: jest.fn().mockResolvedValue(true)
|
|
13
|
+
};
|
|
14
|
+
printer = new Printer(mockAdapter);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
describe('Complete Print Flow', () => {
|
|
18
|
+
it('should print a complete receipt', async () => {
|
|
19
|
+
printer
|
|
20
|
+
.hardware('init')
|
|
21
|
+
.beep(1, 1)
|
|
22
|
+
.encode(860)
|
|
23
|
+
.size(2, 2)
|
|
24
|
+
.align('ct')
|
|
25
|
+
.textln('TEST RECEIPT')
|
|
26
|
+
.size(1, 1)
|
|
27
|
+
.align('lt')
|
|
28
|
+
.textln('Item 1: R$ 10,00')
|
|
29
|
+
.textln('Item 2: R$ 20,00')
|
|
30
|
+
.align('rt')
|
|
31
|
+
.textln('Total: R$ 30,00')
|
|
32
|
+
.align('lt')
|
|
33
|
+
.drawLine()
|
|
34
|
+
.cut(true);
|
|
35
|
+
|
|
36
|
+
await printer.flush();
|
|
37
|
+
|
|
38
|
+
expect(mockAdapter.write).toHaveBeenCalled();
|
|
39
|
+
const writtenBuffer = mockAdapter.write.mock.calls[0][0];
|
|
40
|
+
expect(writtenBuffer.length).toBeGreaterThan(0);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('should print receipt with barcode', async () => {
|
|
44
|
+
printer
|
|
45
|
+
.hardware('init')
|
|
46
|
+
.align('ct')
|
|
47
|
+
.textln('PRODUCT')
|
|
48
|
+
.barcode('123456789012', 'EAN13', {
|
|
49
|
+
width: 2,
|
|
50
|
+
height: 50,
|
|
51
|
+
position: 'BLW'
|
|
52
|
+
})
|
|
53
|
+
.cut(true);
|
|
54
|
+
|
|
55
|
+
await printer.flush();
|
|
56
|
+
|
|
57
|
+
expect(mockAdapter.write).toHaveBeenCalled();
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('should handle multiple prints in sequence', async () => {
|
|
61
|
+
// First print
|
|
62
|
+
printer
|
|
63
|
+
.hardware('init')
|
|
64
|
+
.textln('Print 1')
|
|
65
|
+
.cut(true);
|
|
66
|
+
await printer.flush();
|
|
67
|
+
|
|
68
|
+
// Second print
|
|
69
|
+
printer
|
|
70
|
+
.hardware('init')
|
|
71
|
+
.textln('Print 2')
|
|
72
|
+
.cut(true);
|
|
73
|
+
await printer.flush();
|
|
74
|
+
|
|
75
|
+
expect(mockAdapter.write).toHaveBeenCalledTimes(2);
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
describe('Complex Formatting', () => {
|
|
80
|
+
it('should handle mixed formatting', async () => {
|
|
81
|
+
printer
|
|
82
|
+
.align('ct')
|
|
83
|
+
.size(2, 2)
|
|
84
|
+
.style('B')
|
|
85
|
+
.textln('BOLD TITLE')
|
|
86
|
+
.style('NORMAL')
|
|
87
|
+
.size(1, 1)
|
|
88
|
+
.align('lt')
|
|
89
|
+
.textln('Normal text')
|
|
90
|
+
.style('U')
|
|
91
|
+
.textln('Underlined text')
|
|
92
|
+
.style('NORMAL')
|
|
93
|
+
.cut(true);
|
|
94
|
+
|
|
95
|
+
await printer.flush();
|
|
96
|
+
expect(mockAdapter.write).toHaveBeenCalled();
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('should handle encoding changes', async () => {
|
|
100
|
+
printer
|
|
101
|
+
.encode('UTF-8')
|
|
102
|
+
.textln('UTF-8 Text')
|
|
103
|
+
.encode('GB18030')
|
|
104
|
+
.textln('GB18030 Text')
|
|
105
|
+
.cut(true);
|
|
106
|
+
|
|
107
|
+
await printer.flush();
|
|
108
|
+
expect(mockAdapter.write).toHaveBeenCalled();
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
describe('Error Handling', () => {
|
|
113
|
+
it('should handle adapter write errors', async () => {
|
|
114
|
+
mockAdapter.write.mockRejectedValueOnce(new Error('Write failed'));
|
|
115
|
+
|
|
116
|
+
printer.text('Test');
|
|
117
|
+
await expect(printer.flush()).rejects.toThrow('Write failed');
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('should handle adapter close errors gracefully', async () => {
|
|
121
|
+
mockAdapter.close.mockRejectedValueOnce(new Error('Close failed'));
|
|
122
|
+
|
|
123
|
+
printer.text('Test');
|
|
124
|
+
await expect(printer.close()).rejects.toThrow('Close failed');
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const Adapter = require('../../../src/adapter');
|
|
4
|
+
|
|
5
|
+
describe('Adapter', () => {
|
|
6
|
+
let adapter;
|
|
7
|
+
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
adapter = new Adapter();
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it('should be an instance of EventEmitter', () => {
|
|
13
|
+
const EventEmitter = require('events');
|
|
14
|
+
expect(adapter).toBeInstanceOf(EventEmitter);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('should throw NotImplementedException for connect', () => {
|
|
18
|
+
expect(() => adapter.connect()).toThrow(Error);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('should throw NotImplementedException for open', () => {
|
|
22
|
+
expect(() => adapter.open()).toThrow(Error);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('should throw NotImplementedException for write', () => {
|
|
26
|
+
expect(() => adapter.write()).toThrow(Error);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('should throw NotImplementedException for close', () => {
|
|
30
|
+
expect(() => adapter.close()).toThrow(Error);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('should throw NotImplementedException for read', () => {
|
|
34
|
+
expect(() => adapter.read()).toThrow(Error);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('should copy properties from provided adapter', () => {
|
|
38
|
+
const mockAdapter = {
|
|
39
|
+
connect: jest.fn(),
|
|
40
|
+
write: jest.fn(),
|
|
41
|
+
customProp: 'test'
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const adapter = new Adapter(mockAdapter);
|
|
45
|
+
expect(adapter.customProp).toBe('test');
|
|
46
|
+
expect(adapter.connect).toBe(mockAdapter.connect);
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const Serial = require('../../../src/serial-adapter');
|
|
4
|
+
const { SerialPort } = require('serialport');
|
|
5
|
+
|
|
6
|
+
// Mock serialport (v13 API - uses Promises)
|
|
7
|
+
jest.mock('serialport', () => {
|
|
8
|
+
const mockPort = {
|
|
9
|
+
isOpen: false,
|
|
10
|
+
open: jest.fn().mockResolvedValue(undefined),
|
|
11
|
+
close: jest.fn().mockResolvedValue(undefined),
|
|
12
|
+
write: jest.fn().mockResolvedValue(undefined),
|
|
13
|
+
flush: jest.fn().mockResolvedValue(undefined),
|
|
14
|
+
drain: jest.fn().mockResolvedValue(undefined),
|
|
15
|
+
on: jest.fn(),
|
|
16
|
+
removeListener: jest.fn()
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
// Update isOpen when open/close are called
|
|
20
|
+
const originalOpen = mockPort.open;
|
|
21
|
+
mockPort.open = jest.fn().mockImplementation(async () => {
|
|
22
|
+
mockPort.isOpen = true;
|
|
23
|
+
return originalOpen();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const originalClose = mockPort.close;
|
|
27
|
+
mockPort.close = jest.fn().mockImplementation(async () => {
|
|
28
|
+
mockPort.isOpen = false;
|
|
29
|
+
return originalClose();
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const SerialPortConstructor = jest.fn().mockImplementation((options) => {
|
|
33
|
+
return mockPort;
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// Attach list as static method to SerialPort constructor
|
|
37
|
+
SerialPortConstructor.list = jest.fn().mockResolvedValue([
|
|
38
|
+
{ path: '/dev/ttyUSB0' },
|
|
39
|
+
{ path: '/dev/ttyUSB1' }
|
|
40
|
+
]);
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
SerialPort: SerialPortConstructor
|
|
44
|
+
};
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
describe('Serial Adapter', () => {
|
|
48
|
+
beforeEach(() => {
|
|
49
|
+
jest.clearAllMocks();
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
describe('connect', () => {
|
|
53
|
+
it('should connect to serial port', async () => {
|
|
54
|
+
const result = await Serial.connect('/dev/ttyUSB0');
|
|
55
|
+
expect(result).toBe(true);
|
|
56
|
+
expect(SerialPort).toHaveBeenCalled();
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('should verify port exists', async () => {
|
|
60
|
+
await Serial.connect('/dev/ttyUSB0');
|
|
61
|
+
expect(SerialPort.list).toHaveBeenCalled();
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('should throw error if port does not exist', async () => {
|
|
65
|
+
await expect(Serial.connect('/dev/invalid')).rejects.toThrow('The specified port does not exist!');
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('should emit connect event', (done) => {
|
|
69
|
+
Serial.once('connect', () => {
|
|
70
|
+
done();
|
|
71
|
+
});
|
|
72
|
+
Serial.connect('/dev/ttyUSB0');
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
describe('open', () => {
|
|
77
|
+
beforeEach(async () => {
|
|
78
|
+
await Serial.connect('/dev/ttyUSB0');
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('should open port if closed', async () => {
|
|
82
|
+
const result = await Serial.open();
|
|
83
|
+
expect(result).toBe(true);
|
|
84
|
+
|
|
85
|
+
// Verify port.open was called
|
|
86
|
+
const mockPort = SerialPort.mock.results[0].value;
|
|
87
|
+
expect(mockPort.open).toHaveBeenCalled();
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('should return true if already open', async () => {
|
|
91
|
+
// Get the mock port and set it as already open
|
|
92
|
+
const mockPort = SerialPort.mock.results[0].value;
|
|
93
|
+
const previousCallCount = mockPort.open.mock.calls.length;
|
|
94
|
+
mockPort.isOpen = true;
|
|
95
|
+
|
|
96
|
+
const result = await Serial.open();
|
|
97
|
+
expect(result).toBe(true);
|
|
98
|
+
|
|
99
|
+
// Verify port.open was NOT called again (call count should be the same)
|
|
100
|
+
// Since isOpen is true, the code should return early without calling open()
|
|
101
|
+
expect(mockPort.open.mock.calls.length).toBe(previousCallCount);
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
describe('write', () => {
|
|
106
|
+
beforeEach(async () => {
|
|
107
|
+
await Serial.connect('/dev/ttyUSB0');
|
|
108
|
+
await Serial.open();
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('should write data to port', async () => {
|
|
112
|
+
const testData = Buffer.from('test', 'ascii');
|
|
113
|
+
const result = await Serial.write(testData);
|
|
114
|
+
expect(result).toBe(true);
|
|
115
|
+
|
|
116
|
+
// Verify port.write was called with correct data
|
|
117
|
+
const mockPort = SerialPort.mock.results[0].value;
|
|
118
|
+
expect(mockPort.write).toHaveBeenCalledWith(testData);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('should drain port after write', async () => {
|
|
122
|
+
const testData = Buffer.from('test', 'ascii');
|
|
123
|
+
await Serial.write(testData);
|
|
124
|
+
|
|
125
|
+
const mockPort = SerialPort.mock.results[0].value;
|
|
126
|
+
expect(mockPort.write).toHaveBeenCalled();
|
|
127
|
+
expect(mockPort.drain).toHaveBeenCalled();
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
describe('close', () => {
|
|
132
|
+
beforeEach(async () => {
|
|
133
|
+
await Serial.connect('/dev/ttyUSB0');
|
|
134
|
+
await Serial.open();
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('should close port', async () => {
|
|
138
|
+
const result = await Serial.close();
|
|
139
|
+
expect(result).toBe(true);
|
|
140
|
+
|
|
141
|
+
// Verify close sequence was called
|
|
142
|
+
const mockPort = SerialPort.mock.results[0].value;
|
|
143
|
+
expect(mockPort.flush).toHaveBeenCalled();
|
|
144
|
+
expect(mockPort.drain).toHaveBeenCalled();
|
|
145
|
+
expect(mockPort.close).toHaveBeenCalled();
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('should flush before closing', async () => {
|
|
149
|
+
await Serial.close();
|
|
150
|
+
|
|
151
|
+
const mockPort = SerialPort.mock.results[0].value;
|
|
152
|
+
expect(mockPort.flush).toHaveBeenCalled();
|
|
153
|
+
expect(mockPort.drain).toHaveBeenCalled();
|
|
154
|
+
expect(mockPort.close).toHaveBeenCalled();
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it('should use default timeout of 50ms', async () => {
|
|
158
|
+
await Serial.close();
|
|
159
|
+
// Should complete without error
|
|
160
|
+
expect(true).toBe(true);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it('should use custom timeout', async () => {
|
|
164
|
+
await Serial.close(100);
|
|
165
|
+
// Should complete without error
|
|
166
|
+
expect(true).toBe(true);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it('should emit close event', async () => {
|
|
170
|
+
let eventReceived = false;
|
|
171
|
+
const promise = new Promise((resolve) => {
|
|
172
|
+
Serial.once('close', () => {
|
|
173
|
+
eventReceived = true;
|
|
174
|
+
resolve();
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
expect(Serial.listenerCount('close')).toBe(1);
|
|
179
|
+
|
|
180
|
+
await Serial.close();
|
|
181
|
+
|
|
182
|
+
await Promise.race([
|
|
183
|
+
promise,
|
|
184
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error('Close event not emitted within timeout')), 1000))
|
|
185
|
+
]);
|
|
186
|
+
|
|
187
|
+
expect(eventReceived).toBe(true);
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
describe('disconnect', () => {
|
|
192
|
+
beforeEach(async () => {
|
|
193
|
+
await Serial.connect('/dev/ttyUSB0');
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it('should disconnect (calls close)', async () => {
|
|
197
|
+
const result = await Serial.disconnect();
|
|
198
|
+
expect(result).toBe(true);
|
|
199
|
+
});
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
describe('read', () => {
|
|
203
|
+
beforeEach(async () => {
|
|
204
|
+
await Serial.connect('/dev/ttyUSB0');
|
|
205
|
+
await Serial.open();
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it('should read data from port', async () => {
|
|
209
|
+
const mockPort = SerialPort.mock.results[0].value;
|
|
210
|
+
const testData = Buffer.from('response', 'ascii');
|
|
211
|
+
|
|
212
|
+
// Mock data event
|
|
213
|
+
mockPort.on.mockImplementation((event, handler) => {
|
|
214
|
+
if (event === 'data') {
|
|
215
|
+
setTimeout(() => handler(testData), 10);
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
const data = await Serial.read();
|
|
220
|
+
expect(Buffer.isBuffer(data)).toBe(true);
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
|