scandit-datacapture-frameworks-label 8.1.1 → 8.2.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/__mocks__/ScanditDataCaptureCore.ts +94 -0
- package/dist/dts/defaults/LabelCaptureDefaults.d.ts +2 -0
- package/dist/dts/defaults/index.d.ts +1 -1
- package/dist/dts/generated/LabelProxy.d.ts +30 -0
- package/dist/dts/generated/LabelProxyAdapter.d.ts +256 -0
- package/dist/dts/generated/index.d.ts +6 -0
- package/dist/dts/labelcapture/AdaptiveRecognitionResult.d.ts +4 -0
- package/dist/dts/labelcapture/AdaptiveRecognitionResultType.d.ts +3 -0
- package/dist/dts/labelcapture/CustomBarcode.d.ts +5 -0
- package/dist/dts/labelcapture/CustomText.d.ts +4 -0
- package/dist/dts/labelcapture/ExpiryDateText.d.ts +4 -0
- package/dist/dts/labelcapture/ImeiOneBarcode.d.ts +5 -0
- package/dist/dts/labelcapture/ImeiTwoBarcode.d.ts +5 -0
- package/dist/dts/labelcapture/LabelCaptureAdaptiveRecognitionSettings.d.ts +11 -0
- package/dist/dts/labelcapture/LabelCaptureListener.d.ts +1 -2
- package/dist/dts/labelcapture/LabelCaptureSession.d.ts +0 -1
- package/dist/dts/labelcapture/LabelCaptureValidationFlowListener.d.ts +1 -0
- package/dist/dts/labelcapture/LabelCaptureValidationFlowSettings.d.ts +22 -0
- package/dist/dts/labelcapture/PackingDateText.d.ts +4 -0
- package/dist/dts/labelcapture/PartNumberBarcode.d.ts +5 -0
- package/dist/dts/labelcapture/ReceiptScanningLineItem.d.ts +13 -0
- package/dist/dts/labelcapture/ReceiptScanningResult.d.ts +27 -0
- package/dist/dts/labelcapture/SerialNumberBarcode.d.ts +5 -0
- package/dist/dts/labelcapture/TotalPriceText.d.ts +4 -0
- package/dist/dts/labelcapture/UnitPriceText.d.ts +4 -0
- package/dist/dts/labelcapture/WeightText.d.ts +4 -0
- package/dist/dts/labelcapture/controller/LabelCaptureAdaptiveRecognitionOverlayController.d.ts +29 -0
- package/dist/dts/labelcapture/controller/LabelCaptureAdvancedOverlayController.d.ts +7 -47
- package/dist/dts/labelcapture/controller/LabelCaptureBasicOverlayController.d.ts +7 -25
- package/dist/dts/labelcapture/controller/LabelCaptureController.d.ts +7 -25
- package/dist/dts/labelcapture/controller/LabelCaptureValidationFlowOverlayController.d.ts +13 -15
- package/dist/dts/labelcapture/index.d.ts +8 -0
- package/dist/dts/labelcapture/private/PrivateLabelCaptureSession.d.ts +1 -6
- package/dist/dts/labelcapture/view/LabelCaptureAdaptiveRecognitionListener.d.ts +5 -0
- package/dist/dts/labelcapture/view/LabelCaptureAdaptiveRecognitionOverlay.d.ts +20 -0
- package/dist/dts/labelcapture/view/LabelCaptureAdvancedOverlay.d.ts +1 -0
- package/dist/dts/labelcapture/view/LabelCaptureBasicOverlay.d.ts +2 -1
- package/dist/dts/labelcapture/view/LabelCaptureValidationFlowOverlay.d.ts +1 -0
- package/dist/dts/proxy-types.d.ts +1 -1
- package/dist/index.js +1152 -54
- package/dist/index.js.map +1 -1
- package/jest.config.js +38 -0
- package/package.json +3 -3
- package/test/AdaptiveRecognitionResultType.test.ts +19 -0
- package/test/LabelCaptureAdaptiveRecognitionSettings.test.ts +115 -0
- package/test/LabelCaptureValidationFlowOverlayController.test.ts +427 -0
- package/test/LabelCaptureValidationFlowSettings.test.ts +120 -0
- package/test/ReceiptScanningLineItem.test.ts +99 -0
- package/test/ReceiptScanningResult.test.ts +247 -0
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { describe, it, expect } from '@jest/globals';
|
|
2
|
+
import { LabelCaptureAdaptiveRecognitionSettings } from '../src/labelcapture/LabelCaptureAdaptiveRecognitionSettings';
|
|
3
|
+
import { AdaptiveRecognitionResultType } from '../src/labelcapture/AdaptiveRecognitionResultType';
|
|
4
|
+
|
|
5
|
+
describe('LabelCaptureAdaptiveRecognitionSettings', () => {
|
|
6
|
+
describe('constructor', () => {
|
|
7
|
+
it('should create settings with resultType', () => {
|
|
8
|
+
const settings = new LabelCaptureAdaptiveRecognitionSettings(AdaptiveRecognitionResultType.Receipt);
|
|
9
|
+
|
|
10
|
+
expect(settings.resultType).toBe(AdaptiveRecognitionResultType.Receipt);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it('should have default empty processingHintText', () => {
|
|
14
|
+
const settings = new LabelCaptureAdaptiveRecognitionSettings(AdaptiveRecognitionResultType.Receipt);
|
|
15
|
+
|
|
16
|
+
expect(settings.processingHintText).toBe('');
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
describe('resultType', () => {
|
|
21
|
+
it('getter returns the correct value', () => {
|
|
22
|
+
const settings = new LabelCaptureAdaptiveRecognitionSettings(AdaptiveRecognitionResultType.Receipt);
|
|
23
|
+
|
|
24
|
+
expect(settings.resultType).toBe(AdaptiveRecognitionResultType.Receipt);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('setter updates the value', () => {
|
|
28
|
+
const settings = new LabelCaptureAdaptiveRecognitionSettings(AdaptiveRecognitionResultType.Receipt);
|
|
29
|
+
|
|
30
|
+
// Currently only Receipt is available, but testing setter works
|
|
31
|
+
settings.resultType = AdaptiveRecognitionResultType.Receipt;
|
|
32
|
+
|
|
33
|
+
expect(settings.resultType).toBe(AdaptiveRecognitionResultType.Receipt);
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
describe('processingHintText', () => {
|
|
38
|
+
it('getter returns the correct value', () => {
|
|
39
|
+
const settings = new LabelCaptureAdaptiveRecognitionSettings(AdaptiveRecognitionResultType.Receipt);
|
|
40
|
+
settings.processingHintText = 'Processing...';
|
|
41
|
+
|
|
42
|
+
expect(settings.processingHintText).toBe('Processing...');
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('setter updates the value', () => {
|
|
46
|
+
const settings = new LabelCaptureAdaptiveRecognitionSettings(AdaptiveRecognitionResultType.Receipt);
|
|
47
|
+
|
|
48
|
+
settings.processingHintText = 'Please wait...';
|
|
49
|
+
|
|
50
|
+
expect(settings.processingHintText).toBe('Please wait...');
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('can be set to empty string', () => {
|
|
54
|
+
const settings = new LabelCaptureAdaptiveRecognitionSettings(AdaptiveRecognitionResultType.Receipt);
|
|
55
|
+
settings.processingHintText = 'Some text';
|
|
56
|
+
settings.processingHintText = '';
|
|
57
|
+
|
|
58
|
+
expect(settings.processingHintText).toBe('');
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('can contain special characters', () => {
|
|
62
|
+
const settings = new LabelCaptureAdaptiveRecognitionSettings(AdaptiveRecognitionResultType.Receipt);
|
|
63
|
+
|
|
64
|
+
settings.processingHintText = 'Processing... 处理中 🔄';
|
|
65
|
+
|
|
66
|
+
expect(settings.processingHintText).toBe('Processing... 处理中 🔄');
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('can contain newlines', () => {
|
|
70
|
+
const settings = new LabelCaptureAdaptiveRecognitionSettings(AdaptiveRecognitionResultType.Receipt);
|
|
71
|
+
|
|
72
|
+
settings.processingHintText = 'Line 1\nLine 2';
|
|
73
|
+
|
|
74
|
+
expect(settings.processingHintText).toBe('Line 1\nLine 2');
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
describe('serialization', () => {
|
|
79
|
+
it('should have nameForSerialization decorators applied', () => {
|
|
80
|
+
const settings = new LabelCaptureAdaptiveRecognitionSettings(AdaptiveRecognitionResultType.Receipt);
|
|
81
|
+
settings.processingHintText = 'Test hint';
|
|
82
|
+
|
|
83
|
+
// The class should be serializable via DefaultSerializeable
|
|
84
|
+
// Testing that the instance has the expected structure
|
|
85
|
+
expect(settings.resultType).toBeDefined();
|
|
86
|
+
expect(settings.processingHintText).toBeDefined();
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
describe('edge cases', () => {
|
|
91
|
+
it('should handle very long processingHintText', () => {
|
|
92
|
+
const settings = new LabelCaptureAdaptiveRecognitionSettings(AdaptiveRecognitionResultType.Receipt);
|
|
93
|
+
const longText = 'A'.repeat(10000);
|
|
94
|
+
|
|
95
|
+
settings.processingHintText = longText;
|
|
96
|
+
|
|
97
|
+
expect(settings.processingHintText).toBe(longText);
|
|
98
|
+
expect(settings.processingHintText.length).toBe(10000);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('should handle multiple updates to processingHintText', () => {
|
|
102
|
+
const settings = new LabelCaptureAdaptiveRecognitionSettings(AdaptiveRecognitionResultType.Receipt);
|
|
103
|
+
|
|
104
|
+
settings.processingHintText = 'First';
|
|
105
|
+
expect(settings.processingHintText).toBe('First');
|
|
106
|
+
|
|
107
|
+
settings.processingHintText = 'Second';
|
|
108
|
+
expect(settings.processingHintText).toBe('Second');
|
|
109
|
+
|
|
110
|
+
settings.processingHintText = 'Third';
|
|
111
|
+
expect(settings.processingHintText).toBe('Third');
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
|
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
import { describe, it, expect, jest, beforeEach, afterEach, beforeAll } from '@jest/globals';
|
|
2
|
+
import { EventEmitter, EventPayload, FactoryMaker } from 'scandit-datacapture-frameworks-core';
|
|
3
|
+
import { LabelCaptureValidationFlowOverlayController, LabelCaptureValidationFlowListenerEvents } from '../src/labelcapture/controller/LabelCaptureValidationFlowOverlayController';
|
|
4
|
+
import { LabelCaptureValidationFlowOverlay } from '../src/labelcapture/view/LabelCaptureValidationFlowOverlay';
|
|
5
|
+
import { LabelCaptureValidationFlowListener } from '../src/labelcapture/LabelCaptureValidationFlowListener';
|
|
6
|
+
import { LabelField } from '../src/labelcapture/LabelField';
|
|
7
|
+
import { LabelCapture } from '../src/labelcapture/LabelCapture';
|
|
8
|
+
import { LabelCaptureSettings } from '../src/labelcapture/LabelCaptureSettings';
|
|
9
|
+
import { LabelCaptureValidationFlowSettings } from '../src/labelcapture/LabelCaptureValidationFlowSettings';
|
|
10
|
+
import { LabelCaptureDefaults } from '../src/defaults';
|
|
11
|
+
|
|
12
|
+
// Mock defaults for tests
|
|
13
|
+
const mockDefaults: LabelCaptureDefaults = {
|
|
14
|
+
LabelCapture: {
|
|
15
|
+
RecommendedCameraSettings: {} as any,
|
|
16
|
+
LabelCaptureBasicOverlay: {
|
|
17
|
+
DefaultPredictedFieldBrush: {} as any,
|
|
18
|
+
DefaultCapturedFieldBrush: {} as any,
|
|
19
|
+
DefaultLabelBrush: {} as any,
|
|
20
|
+
},
|
|
21
|
+
LabelCaptureValidationFlowOverlay: {
|
|
22
|
+
Settings: {
|
|
23
|
+
missingFieldsHintText: 'Missing fields',
|
|
24
|
+
standbyHintText: 'Point camera at label',
|
|
25
|
+
validationHintText: 'Validating...',
|
|
26
|
+
validationErrorText: 'Validation error',
|
|
27
|
+
requiredFieldErrorText: 'Required field',
|
|
28
|
+
manualInputButtonText: 'Enter manually',
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
Feedback: {
|
|
32
|
+
success: {} as any,
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
// Create a mock proxy with a real EventEmitter for event testing
|
|
38
|
+
const createMockLabelProxy = () => {
|
|
39
|
+
const eventEmitter = new EventEmitter();
|
|
40
|
+
return {
|
|
41
|
+
subscribeForEvents: jest.fn((events: string[]) => {}),
|
|
42
|
+
unsubscribeFromEvents: jest.fn((events: string[]) => {}),
|
|
43
|
+
dispose: jest.fn(),
|
|
44
|
+
eventEmitter: eventEmitter,
|
|
45
|
+
};
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const createMockLabelProxyAdapter = () => {
|
|
49
|
+
return {
|
|
50
|
+
registerListenerForValidationFlowEvents: jest.fn(() => Promise.resolve()),
|
|
51
|
+
unregisterListenerForValidationFlowEvents: jest.fn(() => Promise.resolve()),
|
|
52
|
+
updateLabelCaptureValidationFlowOverlay: jest.fn(() => Promise.resolve()),
|
|
53
|
+
};
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
// Mock LabelField.fromJSON
|
|
57
|
+
const createMockLabelField = (name: string, text: string | null, isRequired: boolean): LabelField => {
|
|
58
|
+
return {
|
|
59
|
+
name,
|
|
60
|
+
text,
|
|
61
|
+
isRequired,
|
|
62
|
+
} as LabelField;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
describe('LabelCaptureValidationFlowOverlayController', () => {
|
|
66
|
+
let mockOverlay: LabelCaptureValidationFlowOverlay;
|
|
67
|
+
let mockLabelCapture: LabelCapture;
|
|
68
|
+
let mockProxy: ReturnType<typeof createMockLabelProxy>;
|
|
69
|
+
let mockAdapter: ReturnType<typeof createMockLabelProxyAdapter>;
|
|
70
|
+
let controller: LabelCaptureValidationFlowOverlayController;
|
|
71
|
+
let mockListener: LabelCaptureValidationFlowListener;
|
|
72
|
+
let eventEmitterOnSpy: jest.SpiedFunction<typeof EventEmitter.prototype.on>;
|
|
73
|
+
let eventEmitterOffSpy: jest.SpiedFunction<typeof EventEmitter.prototype.off>;
|
|
74
|
+
|
|
75
|
+
beforeAll(() => {
|
|
76
|
+
FactoryMaker.bindInstanceIfNotExists('LabelCaptureDefaults', mockDefaults);
|
|
77
|
+
// Register the proxy builder - each test gets a fresh proxy
|
|
78
|
+
FactoryMaker.bindLazyInstance('LabelCaptureValidationFlowOverlayProxy', () => createMockLabelProxy());
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
beforeEach(() => {
|
|
82
|
+
jest.clearAllMocks();
|
|
83
|
+
|
|
84
|
+
// Create mock LabelCapture
|
|
85
|
+
const settings = LabelCaptureSettings.settingsFromLabelDefinitions([], {});
|
|
86
|
+
mockLabelCapture = new LabelCapture(settings);
|
|
87
|
+
|
|
88
|
+
// Create mock overlay
|
|
89
|
+
mockOverlay = new LabelCaptureValidationFlowOverlay(mockLabelCapture);
|
|
90
|
+
|
|
91
|
+
// Create mock adapter
|
|
92
|
+
mockAdapter = createMockLabelProxyAdapter();
|
|
93
|
+
|
|
94
|
+
// Create controller - proxy will be created by FactoryMaker
|
|
95
|
+
controller = new LabelCaptureValidationFlowOverlayController(mockOverlay);
|
|
96
|
+
// Get the proxy that was created by FactoryMaker
|
|
97
|
+
mockProxy = (controller as any)._proxy;
|
|
98
|
+
// Spy on EventEmitter methods after proxy is created
|
|
99
|
+
eventEmitterOnSpy = jest.spyOn(mockProxy.eventEmitter, 'on');
|
|
100
|
+
eventEmitterOffSpy = jest.spyOn(mockProxy.eventEmitter, 'off');
|
|
101
|
+
// Override the adapter with our mock
|
|
102
|
+
(controller as any).adapter = mockAdapter;
|
|
103
|
+
|
|
104
|
+
// Create mock listener
|
|
105
|
+
mockListener = {
|
|
106
|
+
didCaptureLabelWithFields: jest.fn(),
|
|
107
|
+
didSubmitManualInputForField: jest.fn(),
|
|
108
|
+
};
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
afterEach(() => {
|
|
112
|
+
jest.clearAllMocks();
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
describe('subscribeLabelCaptureValidationFlowListener', () => {
|
|
116
|
+
it('should register listener and subscribe to events when listener is set', async () => {
|
|
117
|
+
// given
|
|
118
|
+
mockOverlay.listener = mockListener;
|
|
119
|
+
|
|
120
|
+
// when
|
|
121
|
+
await controller.subscribeLabelCaptureValidationFlowListener();
|
|
122
|
+
|
|
123
|
+
// then
|
|
124
|
+
expect(mockProxy.subscribeForEvents).toHaveBeenCalledWith(
|
|
125
|
+
Object.values(LabelCaptureValidationFlowListenerEvents)
|
|
126
|
+
);
|
|
127
|
+
expect(eventEmitterOnSpy).toHaveBeenCalledWith(
|
|
128
|
+
LabelCaptureValidationFlowListenerEvents.didCaptureLabelWithFields,
|
|
129
|
+
expect.any(Function)
|
|
130
|
+
);
|
|
131
|
+
expect(eventEmitterOnSpy).toHaveBeenCalledWith(
|
|
132
|
+
LabelCaptureValidationFlowListenerEvents.didSubmitManualInputForField,
|
|
133
|
+
expect.any(Function)
|
|
134
|
+
);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('should not subscribe multiple times if already subscribed', async () => {
|
|
138
|
+
// given
|
|
139
|
+
mockOverlay.listener = mockListener;
|
|
140
|
+
await controller.subscribeLabelCaptureValidationFlowListener();
|
|
141
|
+
const initialCallCount = eventEmitterOnSpy.mock.calls.length;
|
|
142
|
+
|
|
143
|
+
// when
|
|
144
|
+
await controller.subscribeLabelCaptureValidationFlowListener();
|
|
145
|
+
|
|
146
|
+
// then
|
|
147
|
+
expect(eventEmitterOnSpy.mock.calls.length).toBe(initialCallCount);
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
describe('unsubscribeLabelCaptureValidationFlowListener', () => {
|
|
152
|
+
it('should unregister listener and unsubscribe from events', async () => {
|
|
153
|
+
// given
|
|
154
|
+
mockOverlay.listener = mockListener;
|
|
155
|
+
await controller.subscribeLabelCaptureValidationFlowListener();
|
|
156
|
+
|
|
157
|
+
// when
|
|
158
|
+
await controller.unsubscribeLabelCaptureValidationFlowListener();
|
|
159
|
+
|
|
160
|
+
// then
|
|
161
|
+
expect(eventEmitterOffSpy).toHaveBeenCalledWith(
|
|
162
|
+
LabelCaptureValidationFlowListenerEvents.didCaptureLabelWithFields,
|
|
163
|
+
expect.any(Function)
|
|
164
|
+
);
|
|
165
|
+
expect(eventEmitterOffSpy).toHaveBeenCalledWith(
|
|
166
|
+
LabelCaptureValidationFlowListenerEvents.didSubmitManualInputForField,
|
|
167
|
+
expect.any(Function)
|
|
168
|
+
);
|
|
169
|
+
expect(mockProxy.unsubscribeFromEvents).toHaveBeenCalledWith(
|
|
170
|
+
Object.values(LabelCaptureValidationFlowListenerEvents)
|
|
171
|
+
);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it('should not unsubscribe if not subscribed', async () => {
|
|
175
|
+
// when
|
|
176
|
+
await controller.unsubscribeLabelCaptureValidationFlowListener();
|
|
177
|
+
|
|
178
|
+
// then
|
|
179
|
+
expect(eventEmitterOffSpy).not.toHaveBeenCalled();
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
describe('didSubmitManualInputForField event handling', () => {
|
|
184
|
+
it('should notify listener when didSubmitManualInputForField event is received', async () => {
|
|
185
|
+
// given
|
|
186
|
+
mockOverlay.listener = mockListener;
|
|
187
|
+
await controller.subscribeLabelCaptureValidationFlowListener();
|
|
188
|
+
|
|
189
|
+
const fieldData = {
|
|
190
|
+
name: 'Barcode',
|
|
191
|
+
text: '1234567890123',
|
|
192
|
+
isRequired: true,
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
const eventPayload: EventPayload = {
|
|
196
|
+
name: LabelCaptureValidationFlowListenerEvents.didSubmitManualInputForField,
|
|
197
|
+
data: JSON.stringify({
|
|
198
|
+
fields: [JSON.stringify(fieldData)],
|
|
199
|
+
oldValue: null,
|
|
200
|
+
newValue: '1234567890123',
|
|
201
|
+
}),
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
// Mock LabelField.fromJSON
|
|
205
|
+
const mockField = createMockLabelField(fieldData.name, fieldData.text, fieldData.isRequired);
|
|
206
|
+
jest.spyOn(LabelField as any, 'fromJSON').mockReturnValue(mockField);
|
|
207
|
+
|
|
208
|
+
// when - emit event directly on the EventEmitter
|
|
209
|
+
mockProxy.eventEmitter.emit(
|
|
210
|
+
LabelCaptureValidationFlowListenerEvents.didSubmitManualInputForField,
|
|
211
|
+
eventPayload
|
|
212
|
+
);
|
|
213
|
+
|
|
214
|
+
// then
|
|
215
|
+
expect(mockListener.didSubmitManualInputForField).toHaveBeenCalledTimes(1);
|
|
216
|
+
expect(mockListener.didSubmitManualInputForField).toHaveBeenCalledWith(mockField, null, '1234567890123');
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it('should handle field with null text', async () => {
|
|
220
|
+
// given
|
|
221
|
+
mockOverlay.listener = mockListener;
|
|
222
|
+
await controller.subscribeLabelCaptureValidationFlowListener();
|
|
223
|
+
|
|
224
|
+
const fieldData = {
|
|
225
|
+
name: 'value:weight',
|
|
226
|
+
text: null,
|
|
227
|
+
isRequired: true,
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
const eventPayload: EventPayload = {
|
|
231
|
+
name: LabelCaptureValidationFlowListenerEvents.didSubmitManualInputForField,
|
|
232
|
+
data: JSON.stringify({
|
|
233
|
+
fields: [JSON.stringify(fieldData)],
|
|
234
|
+
oldValue: 'oldWeight',
|
|
235
|
+
newValue: 'newWeight',
|
|
236
|
+
}),
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
const mockField = createMockLabelField(fieldData.name, fieldData.text, fieldData.isRequired);
|
|
240
|
+
jest.spyOn(LabelField as any, 'fromJSON').mockReturnValue(mockField);
|
|
241
|
+
|
|
242
|
+
// when - emit event directly on the EventEmitter
|
|
243
|
+
mockProxy.eventEmitter.emit(
|
|
244
|
+
LabelCaptureValidationFlowListenerEvents.didSubmitManualInputForField,
|
|
245
|
+
eventPayload
|
|
246
|
+
);
|
|
247
|
+
|
|
248
|
+
// then
|
|
249
|
+
expect(mockListener.didSubmitManualInputForField).toHaveBeenCalledWith(
|
|
250
|
+
expect.objectContaining({
|
|
251
|
+
name: 'value:weight',
|
|
252
|
+
text: null,
|
|
253
|
+
isRequired: true,
|
|
254
|
+
}),
|
|
255
|
+
'oldWeight',
|
|
256
|
+
'newWeight'
|
|
257
|
+
);
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
it('should not notify listener if listener is null', async () => {
|
|
261
|
+
// given
|
|
262
|
+
mockOverlay.listener = mockListener;
|
|
263
|
+
await controller.subscribeLabelCaptureValidationFlowListener();
|
|
264
|
+
// Remove listener after subscribing
|
|
265
|
+
mockOverlay.listener = null;
|
|
266
|
+
|
|
267
|
+
const eventPayload: EventPayload = {
|
|
268
|
+
name: LabelCaptureValidationFlowListenerEvents.didSubmitManualInputForField,
|
|
269
|
+
data: JSON.stringify({
|
|
270
|
+
fields: [JSON.stringify({ name: 'Barcode', text: '123', isRequired: true })],
|
|
271
|
+
oldValue: null,
|
|
272
|
+
newValue: '123',
|
|
273
|
+
}),
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
// when - emit event directly on the EventEmitter
|
|
277
|
+
mockProxy.eventEmitter.emit(
|
|
278
|
+
LabelCaptureValidationFlowListenerEvents.didSubmitManualInputForField,
|
|
279
|
+
eventPayload
|
|
280
|
+
);
|
|
281
|
+
|
|
282
|
+
// then
|
|
283
|
+
expect(mockListener.didSubmitManualInputForField).not.toHaveBeenCalled();
|
|
284
|
+
});
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
describe('didCaptureLabelWithFields event handling', () => {
|
|
288
|
+
it('should notify listener when didCaptureLabelWithFields event is received', async () => {
|
|
289
|
+
// given
|
|
290
|
+
mockOverlay.listener = mockListener;
|
|
291
|
+
await controller.subscribeLabelCaptureValidationFlowListener();
|
|
292
|
+
|
|
293
|
+
const fieldsData = [
|
|
294
|
+
{ name: 'Barcode', text: '1234567890123', isRequired: true },
|
|
295
|
+
{ name: 'value:unit_price', text: '5.99', isRequired: false },
|
|
296
|
+
];
|
|
297
|
+
|
|
298
|
+
const eventPayload: EventPayload = {
|
|
299
|
+
name: LabelCaptureValidationFlowListenerEvents.didCaptureLabelWithFields,
|
|
300
|
+
data: JSON.stringify({
|
|
301
|
+
fields: fieldsData.map((f) => JSON.stringify(f)),
|
|
302
|
+
}),
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
jest.spyOn(LabelField as any, 'fromJSON').mockImplementation((json: any) => {
|
|
306
|
+
// json is already parsed by the controller before calling fromJSON
|
|
307
|
+
return createMockLabelField(json.name, json.text, json.isRequired);
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
// when - emit event directly on the EventEmitter
|
|
311
|
+
mockProxy.eventEmitter.emit(
|
|
312
|
+
LabelCaptureValidationFlowListenerEvents.didCaptureLabelWithFields,
|
|
313
|
+
eventPayload
|
|
314
|
+
);
|
|
315
|
+
|
|
316
|
+
// then
|
|
317
|
+
expect(mockListener.didCaptureLabelWithFields).toHaveBeenCalledTimes(1);
|
|
318
|
+
expect(mockListener.didCaptureLabelWithFields).toHaveBeenCalledWith(
|
|
319
|
+
expect.arrayContaining([
|
|
320
|
+
expect.objectContaining({ name: 'Barcode' }),
|
|
321
|
+
expect.objectContaining({ name: 'value:unit_price' }),
|
|
322
|
+
])
|
|
323
|
+
);
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
it('should handle empty fields array', async () => {
|
|
327
|
+
// given
|
|
328
|
+
mockOverlay.listener = mockListener;
|
|
329
|
+
await controller.subscribeLabelCaptureValidationFlowListener();
|
|
330
|
+
|
|
331
|
+
const eventPayload: EventPayload = {
|
|
332
|
+
name: LabelCaptureValidationFlowListenerEvents.didCaptureLabelWithFields,
|
|
333
|
+
data: JSON.stringify({
|
|
334
|
+
fields: [],
|
|
335
|
+
}),
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
jest.spyOn(LabelField as any, 'fromJSON').mockReturnValue(null);
|
|
339
|
+
|
|
340
|
+
// when - emit event directly on the EventEmitter
|
|
341
|
+
mockProxy.eventEmitter.emit(
|
|
342
|
+
LabelCaptureValidationFlowListenerEvents.didCaptureLabelWithFields,
|
|
343
|
+
eventPayload
|
|
344
|
+
);
|
|
345
|
+
|
|
346
|
+
// then
|
|
347
|
+
expect(mockListener.didCaptureLabelWithFields).toHaveBeenCalledWith([]);
|
|
348
|
+
});
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
describe('updateValidationFlowOverlay', () => {
|
|
352
|
+
it('should call adapter to update overlay', async () => {
|
|
353
|
+
// given
|
|
354
|
+
const settings = LabelCaptureValidationFlowSettings.create();
|
|
355
|
+
settings.validationHintText = 'Enter value manually';
|
|
356
|
+
await mockOverlay.applySettings(settings);
|
|
357
|
+
|
|
358
|
+
// when
|
|
359
|
+
await controller.updateValidationFlowOverlay();
|
|
360
|
+
|
|
361
|
+
// then
|
|
362
|
+
expect(mockAdapter.updateLabelCaptureValidationFlowOverlay).toHaveBeenCalled();
|
|
363
|
+
const callArgs = (mockAdapter.updateLabelCaptureValidationFlowOverlay as jest.Mock).mock.calls[0][0] as { dataCaptureViewId: number; overlayJson: string };
|
|
364
|
+
expect(callArgs).toHaveProperty('dataCaptureViewId');
|
|
365
|
+
expect(callArgs).toHaveProperty('overlayJson');
|
|
366
|
+
expect(JSON.parse(callArgs.overlayJson)).toHaveProperty('hasListener');
|
|
367
|
+
});
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
describe('dispose', () => {
|
|
371
|
+
it('should unsubscribe and dispose proxy', async () => {
|
|
372
|
+
// given
|
|
373
|
+
mockOverlay.listener = mockListener;
|
|
374
|
+
await controller.subscribeLabelCaptureValidationFlowListener();
|
|
375
|
+
|
|
376
|
+
// when
|
|
377
|
+
controller.dispose();
|
|
378
|
+
|
|
379
|
+
// then
|
|
380
|
+
expect(mockProxy.dispose).toHaveBeenCalled();
|
|
381
|
+
});
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
describe('initialize', () => {
|
|
385
|
+
it('should subscribe listener if overlay has listener on construction', async () => {
|
|
386
|
+
// given
|
|
387
|
+
mockOverlay.listener = mockListener;
|
|
388
|
+
const newMockAdapter = createMockLabelProxyAdapter();
|
|
389
|
+
|
|
390
|
+
// when - create a new controller and inject our mock adapter
|
|
391
|
+
const newController = new LabelCaptureValidationFlowOverlayController(mockOverlay);
|
|
392
|
+
(newController as any).adapter = newMockAdapter;
|
|
393
|
+
|
|
394
|
+
// Wait for async initialize
|
|
395
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
396
|
+
|
|
397
|
+
// then - verify that subscribe was called (via eventEmitter.on)
|
|
398
|
+
const newProxy = (newController as any)._proxy;
|
|
399
|
+
// Since the controller auto-subscribes on init when listener exists,
|
|
400
|
+
// we can verify by calling subscribe again and checking it's already subscribed
|
|
401
|
+
const onSpy = jest.spyOn(newProxy.eventEmitter, 'on');
|
|
402
|
+
await newController.subscribeLabelCaptureValidationFlowListener();
|
|
403
|
+
// Should not add more listeners since already subscribed
|
|
404
|
+
expect(onSpy).not.toHaveBeenCalled();
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
it('should not subscribe if overlay has no listener on construction', async () => {
|
|
408
|
+
// given
|
|
409
|
+
mockOverlay.listener = null;
|
|
410
|
+
const newMockAdapter = createMockLabelProxyAdapter();
|
|
411
|
+
|
|
412
|
+
// when
|
|
413
|
+
const newController = new LabelCaptureValidationFlowOverlayController(mockOverlay);
|
|
414
|
+
(newController as any).adapter = newMockAdapter;
|
|
415
|
+
const newProxy = (newController as any)._proxy;
|
|
416
|
+
const onSpy = jest.spyOn(newProxy.eventEmitter, 'on');
|
|
417
|
+
|
|
418
|
+
// Wait for async initialize
|
|
419
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
420
|
+
|
|
421
|
+
// then - should not have subscribed since no listener
|
|
422
|
+
// When we subscribe now, it should actually add listeners
|
|
423
|
+
await newController.subscribeLabelCaptureValidationFlowListener();
|
|
424
|
+
expect(onSpy).toHaveBeenCalled();
|
|
425
|
+
});
|
|
426
|
+
});
|
|
427
|
+
});
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { describe, it, expect, beforeAll } from '@jest/globals';
|
|
2
|
+
import { FactoryMaker } from 'scandit-datacapture-frameworks-core';
|
|
3
|
+
import { LabelCaptureValidationFlowSettings } from '../src/labelcapture/LabelCaptureValidationFlowSettings';
|
|
4
|
+
import { LabelCaptureDefaults } from '../src/defaults';
|
|
5
|
+
|
|
6
|
+
// Mock defaults for tests
|
|
7
|
+
const mockDefaults: LabelCaptureDefaults = {
|
|
8
|
+
LabelCapture: {
|
|
9
|
+
RecommendedCameraSettings: {} as any,
|
|
10
|
+
LabelCaptureBasicOverlay: {
|
|
11
|
+
DefaultPredictedFieldBrush: {} as any,
|
|
12
|
+
DefaultCapturedFieldBrush: {} as any,
|
|
13
|
+
DefaultLabelBrush: {} as any,
|
|
14
|
+
},
|
|
15
|
+
LabelCaptureValidationFlowOverlay: {
|
|
16
|
+
Settings: {
|
|
17
|
+
missingFieldsHintText: 'Missing fields',
|
|
18
|
+
standbyHintText: 'Point camera at label',
|
|
19
|
+
validationHintText: 'Validating...',
|
|
20
|
+
validationErrorText: 'Validation error',
|
|
21
|
+
requiredFieldErrorText: 'Required field',
|
|
22
|
+
manualInputButtonText: 'Enter manually',
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
Feedback: {
|
|
26
|
+
success: {} as any,
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
describe('LabelCaptureValidationFlowSettings', () => {
|
|
32
|
+
beforeAll(() => {
|
|
33
|
+
FactoryMaker.bindInstanceIfNotExists('LabelCaptureDefaults', mockDefaults);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
describe('setPlaceholderTextForLabelDefinition', () => {
|
|
37
|
+
it('should set placeholder for a field name', () => {
|
|
38
|
+
const settings = LabelCaptureValidationFlowSettings.create();
|
|
39
|
+
|
|
40
|
+
settings.setPlaceholderTextForLabelDefinition('Barcode', 'Scan barcode');
|
|
41
|
+
|
|
42
|
+
expect(settings.getPlaceholderTextForLabelDefinition('Barcode')).toBe('Scan barcode');
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('should allow setting multiple field placeholders', () => {
|
|
46
|
+
const settings = LabelCaptureValidationFlowSettings.create();
|
|
47
|
+
|
|
48
|
+
settings.setPlaceholderTextForLabelDefinition('Barcode', 'Scan barcode');
|
|
49
|
+
settings.setPlaceholderTextForLabelDefinition('value:unit_price', 'Enter price');
|
|
50
|
+
settings.setPlaceholderTextForLabelDefinition('value:weight', 'Enter weight');
|
|
51
|
+
|
|
52
|
+
expect(settings.getPlaceholderTextForLabelDefinition('Barcode')).toBe('Scan barcode');
|
|
53
|
+
expect(settings.getPlaceholderTextForLabelDefinition('value:unit_price')).toBe('Enter price');
|
|
54
|
+
expect(settings.getPlaceholderTextForLabelDefinition('value:weight')).toBe('Enter weight');
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('should remove placeholder when set to null', () => {
|
|
58
|
+
const settings = LabelCaptureValidationFlowSettings.create();
|
|
59
|
+
|
|
60
|
+
settings.setPlaceholderTextForLabelDefinition('Barcode', 'Scan barcode');
|
|
61
|
+
expect(settings.getPlaceholderTextForLabelDefinition('Barcode')).toBe('Scan barcode');
|
|
62
|
+
|
|
63
|
+
settings.setPlaceholderTextForLabelDefinition('Barcode', null);
|
|
64
|
+
expect(settings.getPlaceholderTextForLabelDefinition('Barcode')).toBeNull();
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('should override existing placeholder', () => {
|
|
68
|
+
const settings = LabelCaptureValidationFlowSettings.create();
|
|
69
|
+
|
|
70
|
+
settings.setPlaceholderTextForLabelDefinition('Barcode', 'First placeholder');
|
|
71
|
+
settings.setPlaceholderTextForLabelDefinition('Barcode', 'Second placeholder');
|
|
72
|
+
|
|
73
|
+
expect(settings.getPlaceholderTextForLabelDefinition('Barcode')).toBe('Second placeholder');
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
describe('getPlaceholderTextForLabelDefinition', () => {
|
|
78
|
+
it('should return null for non-existent field', () => {
|
|
79
|
+
const settings = LabelCaptureValidationFlowSettings.create();
|
|
80
|
+
|
|
81
|
+
expect(settings.getPlaceholderTextForLabelDefinition('NonExistent')).toBeNull();
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
describe('serialization', () => {
|
|
86
|
+
it('should not include labelDefinitionsPlaceholders when empty', () => {
|
|
87
|
+
const settings = LabelCaptureValidationFlowSettings.create();
|
|
88
|
+
|
|
89
|
+
const json = settings.toJSON() as Record<string, unknown>;
|
|
90
|
+
|
|
91
|
+
expect(json['labelDefinitionsPlaceholders']).toBeUndefined();
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('should include labelDefinitionsPlaceholders when set', () => {
|
|
95
|
+
const settings = LabelCaptureValidationFlowSettings.create();
|
|
96
|
+
settings.setPlaceholderTextForLabelDefinition('Barcode', 'Scan barcode');
|
|
97
|
+
settings.setPlaceholderTextForLabelDefinition('value:unit_price', 'Enter price');
|
|
98
|
+
|
|
99
|
+
const json = settings.toJSON() as Record<string, unknown>;
|
|
100
|
+
|
|
101
|
+
expect(json['labelDefinitionsPlaceholders']).toBeDefined();
|
|
102
|
+
const placeholders = json['labelDefinitionsPlaceholders'] as Record<string, string | null>;
|
|
103
|
+
expect(placeholders['Barcode']).toBe('Scan barcode');
|
|
104
|
+
expect(placeholders['value:unit_price']).toBe('Enter price');
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('should not include removed placeholders in serialization', () => {
|
|
108
|
+
const settings = LabelCaptureValidationFlowSettings.create();
|
|
109
|
+
settings.setPlaceholderTextForLabelDefinition('Barcode', 'Scan barcode');
|
|
110
|
+
settings.setPlaceholderTextForLabelDefinition('value:weight', 'Enter weight');
|
|
111
|
+
settings.setPlaceholderTextForLabelDefinition('Barcode', null);
|
|
112
|
+
|
|
113
|
+
const json = settings.toJSON() as Record<string, unknown>;
|
|
114
|
+
|
|
115
|
+
const placeholders = json['labelDefinitionsPlaceholders'] as Record<string, string | null>;
|
|
116
|
+
expect(placeholders['Barcode']).toBeUndefined();
|
|
117
|
+
expect(placeholders['value:weight']).toBe('Enter weight');
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
});
|