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.
Files changed (49) hide show
  1. package/__mocks__/ScanditDataCaptureCore.ts +94 -0
  2. package/dist/dts/defaults/LabelCaptureDefaults.d.ts +2 -0
  3. package/dist/dts/defaults/index.d.ts +1 -1
  4. package/dist/dts/generated/LabelProxy.d.ts +30 -0
  5. package/dist/dts/generated/LabelProxyAdapter.d.ts +256 -0
  6. package/dist/dts/generated/index.d.ts +6 -0
  7. package/dist/dts/labelcapture/AdaptiveRecognitionResult.d.ts +4 -0
  8. package/dist/dts/labelcapture/AdaptiveRecognitionResultType.d.ts +3 -0
  9. package/dist/dts/labelcapture/CustomBarcode.d.ts +5 -0
  10. package/dist/dts/labelcapture/CustomText.d.ts +4 -0
  11. package/dist/dts/labelcapture/ExpiryDateText.d.ts +4 -0
  12. package/dist/dts/labelcapture/ImeiOneBarcode.d.ts +5 -0
  13. package/dist/dts/labelcapture/ImeiTwoBarcode.d.ts +5 -0
  14. package/dist/dts/labelcapture/LabelCaptureAdaptiveRecognitionSettings.d.ts +11 -0
  15. package/dist/dts/labelcapture/LabelCaptureListener.d.ts +1 -2
  16. package/dist/dts/labelcapture/LabelCaptureSession.d.ts +0 -1
  17. package/dist/dts/labelcapture/LabelCaptureValidationFlowListener.d.ts +1 -0
  18. package/dist/dts/labelcapture/LabelCaptureValidationFlowSettings.d.ts +22 -0
  19. package/dist/dts/labelcapture/PackingDateText.d.ts +4 -0
  20. package/dist/dts/labelcapture/PartNumberBarcode.d.ts +5 -0
  21. package/dist/dts/labelcapture/ReceiptScanningLineItem.d.ts +13 -0
  22. package/dist/dts/labelcapture/ReceiptScanningResult.d.ts +27 -0
  23. package/dist/dts/labelcapture/SerialNumberBarcode.d.ts +5 -0
  24. package/dist/dts/labelcapture/TotalPriceText.d.ts +4 -0
  25. package/dist/dts/labelcapture/UnitPriceText.d.ts +4 -0
  26. package/dist/dts/labelcapture/WeightText.d.ts +4 -0
  27. package/dist/dts/labelcapture/controller/LabelCaptureAdaptiveRecognitionOverlayController.d.ts +29 -0
  28. package/dist/dts/labelcapture/controller/LabelCaptureAdvancedOverlayController.d.ts +7 -47
  29. package/dist/dts/labelcapture/controller/LabelCaptureBasicOverlayController.d.ts +7 -25
  30. package/dist/dts/labelcapture/controller/LabelCaptureController.d.ts +7 -25
  31. package/dist/dts/labelcapture/controller/LabelCaptureValidationFlowOverlayController.d.ts +13 -15
  32. package/dist/dts/labelcapture/index.d.ts +8 -0
  33. package/dist/dts/labelcapture/private/PrivateLabelCaptureSession.d.ts +1 -6
  34. package/dist/dts/labelcapture/view/LabelCaptureAdaptiveRecognitionListener.d.ts +5 -0
  35. package/dist/dts/labelcapture/view/LabelCaptureAdaptiveRecognitionOverlay.d.ts +20 -0
  36. package/dist/dts/labelcapture/view/LabelCaptureAdvancedOverlay.d.ts +1 -0
  37. package/dist/dts/labelcapture/view/LabelCaptureBasicOverlay.d.ts +2 -1
  38. package/dist/dts/labelcapture/view/LabelCaptureValidationFlowOverlay.d.ts +1 -0
  39. package/dist/dts/proxy-types.d.ts +1 -1
  40. package/dist/index.js +1152 -54
  41. package/dist/index.js.map +1 -1
  42. package/jest.config.js +38 -0
  43. package/package.json +3 -3
  44. package/test/AdaptiveRecognitionResultType.test.ts +19 -0
  45. package/test/LabelCaptureAdaptiveRecognitionSettings.test.ts +115 -0
  46. package/test/LabelCaptureValidationFlowOverlayController.test.ts +427 -0
  47. package/test/LabelCaptureValidationFlowSettings.test.ts +120 -0
  48. package/test/ReceiptScanningLineItem.test.ts +99 -0
  49. 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
+ });