@salesforce/lds-ads-bridge 1.432.0 → 1.434.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/dist/ads-bridge-perf.js +51 -47
- package/dist/adsBridge.js +35 -9
- package/dist/types/ads-bridge.d.ts +1 -0
- package/package.json +7 -7
- package/src/__tests__/ads-bridge.spec.ts +234 -1
- package/src/__tests__/initial_record.json +50 -0
- package/src/__tests__/task_record_gvp_get.json +233 -0
- package/src/__tests__/task_record_gvp_get2.json +233 -0
- package/src/__tests__/task_record_gvp_save.json +233 -0
- package/src/ads-bridge.ts +39 -10
package/dist/ads-bridge-perf.js
CHANGED
|
@@ -483,7 +483,7 @@ const callbacks$1 = [];
|
|
|
483
483
|
function register(r) {
|
|
484
484
|
callbacks$1.forEach((callback) => callback(r));
|
|
485
485
|
}
|
|
486
|
-
// version: 1.
|
|
486
|
+
// version: 1.434.0-d8deb0d4ea
|
|
487
487
|
|
|
488
488
|
/**
|
|
489
489
|
* Returns true if the value acts like a Promise, i.e. has a "then" function,
|
|
@@ -4459,28 +4459,23 @@ let configurableCreateContentVersion = new Configurable();
|
|
|
4459
4459
|
* Depth to which tracked fields will be added to a request that results from a cache miss.
|
|
4460
4460
|
* A value of 0 inhibits the addition of tracked fields, 1 will add tracked fields that can
|
|
4461
4461
|
* be reached by following 1 relationship from the root record, etc.
|
|
4462
|
-
* @defaultValue '
|
|
4462
|
+
* @defaultValue '1'
|
|
4463
4463
|
*/
|
|
4464
|
-
let trackedFieldDepthOnCacheMiss =
|
|
4464
|
+
let trackedFieldDepthOnCacheMiss = 1;
|
|
4465
4465
|
/**
|
|
4466
4466
|
* Depth to which tracked fields will be added to a request that results from a merge conflict
|
|
4467
4467
|
* A value of 0 inhibits the addition of tracked fields, 1 will add tracked fields that can
|
|
4468
4468
|
* be reached by following 1 relationship from the root record, etc.
|
|
4469
|
-
* @defaultValue '
|
|
4469
|
+
* @defaultValue '1'
|
|
4470
4470
|
*/
|
|
4471
|
-
let trackedFieldDepthOnCacheMergeConflict =
|
|
4471
|
+
let trackedFieldDepthOnCacheMergeConflict = 1;
|
|
4472
4472
|
/**
|
|
4473
4473
|
* Depth to which tracked fields will be added to a request that results from a notify change invocation by the consumer
|
|
4474
4474
|
* A value of 0 inhibits the addition of tracked fields, 1 will add tracked fields that can
|
|
4475
4475
|
* be reached by following 1 relationship from the root record, etc.
|
|
4476
|
-
* @defaultValue '
|
|
4476
|
+
* @defaultValue '1'
|
|
4477
4477
|
*/
|
|
4478
|
-
let trackedFieldDepthOnNotifyChange =
|
|
4479
|
-
/**
|
|
4480
|
-
* Determines if we will only fetch the 'Id' field for the leaf relationship record
|
|
4481
|
-
* @defaultValue 'false', replicates the current behavior and fetches all fields in the store for the leaf relationship record
|
|
4482
|
-
*/
|
|
4483
|
-
let trackedFieldLeafNodeIdAndNameOnly = false;
|
|
4478
|
+
let trackedFieldDepthOnNotifyChange = 1;
|
|
4484
4479
|
/**
|
|
4485
4480
|
* One store enabled Get Object Info adapter
|
|
4486
4481
|
*/
|
|
@@ -4577,12 +4572,6 @@ const configurationForRestAdapters = {
|
|
|
4577
4572
|
getTrackedFieldDepthOnNotifyChange: function () {
|
|
4578
4573
|
return trackedFieldDepthOnNotifyChange;
|
|
4579
4574
|
},
|
|
4580
|
-
setTrackedFieldLeafNodeIdAndNameOnly: function (trackedFieldLeafNodeIdAndNameOnlyParam) {
|
|
4581
|
-
trackedFieldLeafNodeIdAndNameOnly = trackedFieldLeafNodeIdAndNameOnlyParam;
|
|
4582
|
-
},
|
|
4583
|
-
getTrackedFieldLeafNodeIdAndNameOnly: function () {
|
|
4584
|
-
return trackedFieldLeafNodeIdAndNameOnly;
|
|
4585
|
-
},
|
|
4586
4575
|
setRecordRepresentationIngestionOverride: function (ingest) {
|
|
4587
4576
|
recordRepresentationIngestionOverride = ingest;
|
|
4588
4577
|
},
|
|
@@ -6171,6 +6160,9 @@ function buildSelectionFromFields(fields, optionalFields = []) {
|
|
|
6171
6160
|
return createRecordSelection(convertRecordFieldsArrayToTrie(fields, optionalFields));
|
|
6172
6161
|
}
|
|
6173
6162
|
|
|
6163
|
+
// Unused suffixes, but leaving in as comments to avoid eslint errors
|
|
6164
|
+
// const CUSTOM_API_NAME_SUFFIX = '__c';
|
|
6165
|
+
// const DMO_API_NAME_SUFFIX = '__dlm';
|
|
6174
6166
|
const CUSTOM_EXTERNAL_OBJECT_FIELD_SUFFIX = '__x';
|
|
6175
6167
|
const RECORD_REPRESENTATION_ERROR_VERSION = 'RECORD_REPRESENTATION_ERROR_VERSION_1';
|
|
6176
6168
|
const RECORD_REPRESENTATION_ERROR_STORE_METADATA_PARAMS = {
|
|
@@ -6197,7 +6189,7 @@ function addScalarField(current, leafNodeFieldKey) {
|
|
|
6197
6189
|
}
|
|
6198
6190
|
}
|
|
6199
6191
|
function extractTrackedFieldsToTrie(recordId, node, root, config, visitedRecordIds = {}, depth = 0) {
|
|
6200
|
-
var _a
|
|
6192
|
+
var _a;
|
|
6201
6193
|
// Filter Error and null nodes
|
|
6202
6194
|
if (!isGraphNode$1(node)) {
|
|
6203
6195
|
return;
|
|
@@ -6235,13 +6227,11 @@ function extractTrackedFieldsToTrie(recordId, node, root, config, visitedRecordI
|
|
|
6235
6227
|
if (!isGraphNode$1(field)) {
|
|
6236
6228
|
continue;
|
|
6237
6229
|
}
|
|
6238
|
-
const { maxDepth
|
|
6230
|
+
const { maxDepth } = config;
|
|
6239
6231
|
if (field.isScalar('value') === false && !Array.isArray((_a = field.data) === null || _a === void 0 ? void 0 : _a.value)) {
|
|
6240
6232
|
if (depth + 1 > maxDepth) {
|
|
6241
|
-
|
|
6242
|
-
|
|
6243
|
-
addScalarFieldName(current);
|
|
6244
|
-
}
|
|
6233
|
+
addScalarFieldId(current);
|
|
6234
|
+
addScalarFieldName(current);
|
|
6245
6235
|
continue;
|
|
6246
6236
|
}
|
|
6247
6237
|
const spanningLink = field.link('value');
|
|
@@ -6263,19 +6253,10 @@ function extractTrackedFieldsToTrie(recordId, node, root, config, visitedRecordI
|
|
|
6263
6253
|
current.children[key] = next;
|
|
6264
6254
|
}
|
|
6265
6255
|
else {
|
|
6266
|
-
// Skip the field, if its value is null at the max level depth.
|
|
6267
|
-
// Ideally, it should only skip relationship field. However,
|
|
6268
|
-
// on the client, there is not a reliable way to determine the
|
|
6269
|
-
// the field type.
|
|
6270
6256
|
if (depth === maxDepth) {
|
|
6271
|
-
|
|
6272
|
-
|
|
6273
|
-
|
|
6274
|
-
continue;
|
|
6275
|
-
}
|
|
6276
|
-
if (field.scalar('value') === null && !Array.isArray((_b = field.data) === null || _b === void 0 ? void 0 : _b.value)) {
|
|
6277
|
-
continue;
|
|
6278
|
-
}
|
|
6257
|
+
addScalarFieldId(current);
|
|
6258
|
+
addScalarFieldName(current);
|
|
6259
|
+
continue;
|
|
6279
6260
|
}
|
|
6280
6261
|
const state = fieldValueRep.linkData();
|
|
6281
6262
|
if (state !== undefined) {
|
|
@@ -6610,7 +6591,6 @@ function mergeRecordConflict(luvio, incoming, existing, recordConflictMap) {
|
|
|
6610
6591
|
};
|
|
6611
6592
|
const trackedFieldsConfig = {
|
|
6612
6593
|
maxDepth: configurationForRestAdapters.getTrackedFieldDepthOnCacheMergeConflict(),
|
|
6613
|
-
onlyFetchLeafNodeIdAndName: configurationForRestAdapters.getTrackedFieldLeafNodeIdAndNameOnly(),
|
|
6614
6594
|
};
|
|
6615
6595
|
extractTrackedFieldsToTrie(recordKey, incomingNode, incomingTrackedFieldsTrieRoot, trackedFieldsConfig);
|
|
6616
6596
|
extractTrackedFieldsToTrie(recordKey, existingNode, existingTrackedFieldsTrieRoot, trackedFieldsConfig);
|
|
@@ -7048,7 +7028,6 @@ function prepareRequest$7(luvio, config) {
|
|
|
7048
7028
|
const key = keyBuilder$3T(luvio, createResourceParams$15(config));
|
|
7049
7029
|
const allTrackedFields = getTrackedFields(key, luvio.getNode(key), {
|
|
7050
7030
|
maxDepth: configurationForRestAdapters.getTrackedFieldDepthOnCacheMiss(),
|
|
7051
|
-
onlyFetchLeafNodeIdAndName: configurationForRestAdapters.getTrackedFieldLeafNodeIdAndNameOnly(),
|
|
7052
7031
|
}, config.optionalFields);
|
|
7053
7032
|
const optionalFields = fields === undefined ? allTrackedFields : difference(allTrackedFields, fields);
|
|
7054
7033
|
const resourceParams = createResourceParams$15({
|
|
@@ -7141,7 +7120,6 @@ function ingestSuccessChildResourceParams$9(luvio, childResourceParamsArray, chi
|
|
|
7141
7120
|
};
|
|
7142
7121
|
const childTrackedFields = getTrackedFields(childKey, luvio.getNode(childKey), {
|
|
7143
7122
|
maxDepth: configurationForRestAdapters.getTrackedFieldDepthOnCacheMiss(),
|
|
7144
|
-
onlyFetchLeafNodeIdAndName: configurationForRestAdapters.getTrackedFieldLeafNodeIdAndNameOnly(),
|
|
7145
7123
|
}, childResourceParams.queryParams.optionalFields);
|
|
7146
7124
|
const childSnapshot = ingestSuccess$X(luvio, {
|
|
7147
7125
|
recordId: childResourceParams.urlParams.recordId,
|
|
@@ -7900,6 +7878,9 @@ function isSpanningRecord(fieldValue) {
|
|
|
7900
7878
|
function isStoreKeyRecordId(key) {
|
|
7901
7879
|
return key.indexOf(RECORD_ID_PREFIX) > -1 && key.indexOf(RECORD_FIELDS_KEY_JUNCTION) === -1;
|
|
7902
7880
|
}
|
|
7881
|
+
function isStoreKeyRecordField(key) {
|
|
7882
|
+
return key.indexOf(RECORD_ID_PREFIX) > -1 && key.indexOf(RECORD_FIELDS_KEY_JUNCTION) > -1;
|
|
7883
|
+
}
|
|
7903
7884
|
/**
|
|
7904
7885
|
* Returns a shallow copy of a record with its field values if it is a scalar and a reference and a
|
|
7905
7886
|
* a RecordRepresentation with no field if the value if a spanning record.
|
|
@@ -8194,6 +8175,33 @@ class AdsBridge {
|
|
|
8194
8175
|
this.isRecordEmitLocked = false;
|
|
8195
8176
|
}
|
|
8196
8177
|
}
|
|
8178
|
+
filterAndSynthesizeBaseIds(updatedEntries) {
|
|
8179
|
+
// Collect base record IDs that are directly present in the entries.
|
|
8180
|
+
const directBaseIds = new Set();
|
|
8181
|
+
for (let i = 0; i < updatedEntries.length; i++) {
|
|
8182
|
+
if (isStoreKeyRecordId(updatedEntries[i].id)) {
|
|
8183
|
+
directBaseIds.add(updatedEntries[i].id);
|
|
8184
|
+
}
|
|
8185
|
+
}
|
|
8186
|
+
// For field entries whose base record ID has no direct entry, synthesize one.
|
|
8187
|
+
const syntheticBaseIds = new Set();
|
|
8188
|
+
for (let i = 0; i < updatedEntries.length; i++) {
|
|
8189
|
+
if (isStoreKeyRecordField(updatedEntries[i].id)) {
|
|
8190
|
+
const baseId = updatedEntries[i].id.split(RECORD_FIELDS_KEY_JUNCTION)[0];
|
|
8191
|
+
if (!directBaseIds.has(baseId)) {
|
|
8192
|
+
syntheticBaseIds.add(baseId);
|
|
8193
|
+
}
|
|
8194
|
+
}
|
|
8195
|
+
}
|
|
8196
|
+
// Exclude all the store record ids not matching with the record id pattern.
|
|
8197
|
+
// Note: FieldValueRepresentation have the same prefix than RecordRepresentation so we
|
|
8198
|
+
// need to filter them out.
|
|
8199
|
+
const filteredUpdatedEntries = updatedEntries.filter((entry) => isStoreKeyRecordId(entry.id));
|
|
8200
|
+
syntheticBaseIds.forEach((baseId) => {
|
|
8201
|
+
push.call(filteredUpdatedEntries, { id: baseId });
|
|
8202
|
+
});
|
|
8203
|
+
return filteredUpdatedEntries;
|
|
8204
|
+
}
|
|
8197
8205
|
/**
|
|
8198
8206
|
* This method retrieves queries the store with with passed record ids to retrieve their
|
|
8199
8207
|
* associated records and object info. Note that the passed ids are not Salesforce record id
|
|
@@ -8204,14 +8212,10 @@ class AdsBridge {
|
|
|
8204
8212
|
let shouldEmit = false;
|
|
8205
8213
|
const adsRecordMap = {};
|
|
8206
8214
|
const adsObjectMap = {};
|
|
8207
|
-
|
|
8208
|
-
|
|
8209
|
-
|
|
8210
|
-
|
|
8211
|
-
// need to filter them out.
|
|
8212
|
-
if (!isStoreKeyRecordId(storeRecordId)) {
|
|
8213
|
-
continue;
|
|
8214
|
-
}
|
|
8215
|
+
// Context for change: W-21715343
|
|
8216
|
+
const filteredUpdatedEntries = this.filterAndSynthesizeBaseIds(updatedEntries);
|
|
8217
|
+
for (let i = 0; i < filteredUpdatedEntries.length; i++) {
|
|
8218
|
+
const storeRecordId = filteredUpdatedEntries[i].id;
|
|
8215
8219
|
const record = this.recordRepresentationIngestOverride !== undefined
|
|
8216
8220
|
? getShallowRecordDenormalized(luvio, storeRecordId)
|
|
8217
8221
|
: getShallowRecord(luvio, storeRecordId);
|
package/dist/adsBridge.js
CHANGED
|
@@ -58,6 +58,9 @@ function isSpanningRecord(fieldValue) {
|
|
|
58
58
|
function isStoreKeyRecordId(key) {
|
|
59
59
|
return key.indexOf(RECORD_ID_PREFIX) > -1 && key.indexOf(RECORD_FIELDS_KEY_JUNCTION) === -1;
|
|
60
60
|
}
|
|
61
|
+
function isStoreKeyRecordField(key) {
|
|
62
|
+
return key.indexOf(RECORD_ID_PREFIX) > -1 && key.indexOf(RECORD_FIELDS_KEY_JUNCTION) > -1;
|
|
63
|
+
}
|
|
61
64
|
/**
|
|
62
65
|
* Returns a shallow copy of a record with its field values if it is a scalar and a reference and a
|
|
63
66
|
* a RecordRepresentation with no field if the value if a spanning record.
|
|
@@ -356,6 +359,33 @@ class AdsBridge {
|
|
|
356
359
|
this.isRecordEmitLocked = false;
|
|
357
360
|
}
|
|
358
361
|
}
|
|
362
|
+
filterAndSynthesizeBaseIds(updatedEntries) {
|
|
363
|
+
// Collect base record IDs that are directly present in the entries.
|
|
364
|
+
const directBaseIds = new Set();
|
|
365
|
+
for (let i = 0; i < updatedEntries.length; i++) {
|
|
366
|
+
if (isStoreKeyRecordId(updatedEntries[i].id)) {
|
|
367
|
+
directBaseIds.add(updatedEntries[i].id);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
// For field entries whose base record ID has no direct entry, synthesize one.
|
|
371
|
+
const syntheticBaseIds = new Set();
|
|
372
|
+
for (let i = 0; i < updatedEntries.length; i++) {
|
|
373
|
+
if (isStoreKeyRecordField(updatedEntries[i].id)) {
|
|
374
|
+
const baseId = updatedEntries[i].id.split(RECORD_FIELDS_KEY_JUNCTION)[0];
|
|
375
|
+
if (!directBaseIds.has(baseId)) {
|
|
376
|
+
syntheticBaseIds.add(baseId);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
// Exclude all the store record ids not matching with the record id pattern.
|
|
381
|
+
// Note: FieldValueRepresentation have the same prefix than RecordRepresentation so we
|
|
382
|
+
// need to filter them out.
|
|
383
|
+
const filteredUpdatedEntries = updatedEntries.filter((entry) => isStoreKeyRecordId(entry.id));
|
|
384
|
+
syntheticBaseIds.forEach((baseId) => {
|
|
385
|
+
push.call(filteredUpdatedEntries, { id: baseId });
|
|
386
|
+
});
|
|
387
|
+
return filteredUpdatedEntries;
|
|
388
|
+
}
|
|
359
389
|
/**
|
|
360
390
|
* This method retrieves queries the store with with passed record ids to retrieve their
|
|
361
391
|
* associated records and object info. Note that the passed ids are not Salesforce record id
|
|
@@ -367,14 +397,10 @@ class AdsBridge {
|
|
|
367
397
|
let shouldEmit = false;
|
|
368
398
|
const adsRecordMap = {};
|
|
369
399
|
const adsObjectMap = {};
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
// need to filter them out.
|
|
375
|
-
if (!isStoreKeyRecordId(storeRecordId)) {
|
|
376
|
-
continue;
|
|
377
|
-
}
|
|
400
|
+
// Context for change: W-21715343
|
|
401
|
+
const filteredUpdatedEntries = this.filterAndSynthesizeBaseIds(updatedEntries);
|
|
402
|
+
for (let i = 0; i < filteredUpdatedEntries.length; i++) {
|
|
403
|
+
const storeRecordId = filteredUpdatedEntries[i].id;
|
|
378
404
|
const record = this.recordRepresentationIngestOverride !== undefined
|
|
379
405
|
? getShallowRecordDenormalized(luvio, storeRecordId)
|
|
380
406
|
: getShallowRecord(luvio, storeRecordId);
|
|
@@ -435,4 +461,4 @@ function withAdsBridge(callback) {
|
|
|
435
461
|
}
|
|
436
462
|
|
|
437
463
|
export { instrument, withAdsBridge };
|
|
438
|
-
// version: 1.
|
|
464
|
+
// version: 1.434.0-d8deb0d4ea
|
|
@@ -78,6 +78,7 @@ export default class AdsBridge {
|
|
|
78
78
|
* mutations triggered by ADS to be emit back to ADS.
|
|
79
79
|
*/
|
|
80
80
|
private lockLdsRecordEmit;
|
|
81
|
+
private filterAndSynthesizeBaseIds;
|
|
81
82
|
/**
|
|
82
83
|
* This method retrieves queries the store with with passed record ids to retrieve their
|
|
83
84
|
* associated records and object info. Note that the passed ids are not Salesforce record id
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@salesforce/lds-ads-bridge",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.434.0",
|
|
4
4
|
"license": "SEE LICENSE IN LICENSE.txt",
|
|
5
5
|
"description": "Bridge to sync data between LDS and ADS",
|
|
6
6
|
"main": "dist/adsBridge.js",
|
|
@@ -30,9 +30,9 @@
|
|
|
30
30
|
"release:corejar": "yarn build && ../core-build/scripts/core.js --name=lds-ads-bridge"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
|
-
"@salesforce/lds-adapters-uiapi": "^1.
|
|
34
|
-
"@salesforce/lds-runtime-mobile": "^1.
|
|
35
|
-
"@salesforce/lds-uiapi-record-utils-mobile": "^1.
|
|
33
|
+
"@salesforce/lds-adapters-uiapi": "^1.434.0",
|
|
34
|
+
"@salesforce/lds-runtime-mobile": "^1.434.0",
|
|
35
|
+
"@salesforce/lds-uiapi-record-utils-mobile": "^1.434.0"
|
|
36
36
|
},
|
|
37
37
|
"volta": {
|
|
38
38
|
"extends": "../../package.json"
|
|
@@ -41,9 +41,9 @@
|
|
|
41
41
|
{
|
|
42
42
|
"path": "./dist/adsBridge.js",
|
|
43
43
|
"maxSize": {
|
|
44
|
-
"none": "
|
|
45
|
-
"min": "
|
|
46
|
-
"compressed": "4 kB"
|
|
44
|
+
"none": "18.4 kB",
|
|
45
|
+
"min": "6 kB",
|
|
46
|
+
"compressed": "4.3 kB"
|
|
47
47
|
}
|
|
48
48
|
}
|
|
49
49
|
]
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { Luvio, InMemoryStore, Environment } from '@luvio/engine';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
keyBuilderRecord,
|
|
4
|
+
ingestRecord,
|
|
5
|
+
type Registration,
|
|
6
|
+
ingestRecordSuccess,
|
|
7
|
+
} from '@salesforce/lds-adapters-uiapi';
|
|
3
8
|
import { expect } from '@jest/globals';
|
|
4
9
|
|
|
5
10
|
import AdsBridge from '../ads-bridge';
|
|
@@ -417,6 +422,158 @@ describe('AdsBridge', () => {
|
|
|
417
422
|
});
|
|
418
423
|
});
|
|
419
424
|
|
|
425
|
+
// Context for change: W-21715343
|
|
426
|
+
it('correctly emits the updated Case record to ADS when refreshSnapshot is called, and the Case record was updated via spanning record updates on the Task record', async () => {
|
|
427
|
+
const initialLdsState = JSON.parse(JSON.stringify(require('./initial_record.json')));
|
|
428
|
+
const addRecordsGet = JSON.parse(JSON.stringify(require('./task_record_gvp_get.json')));
|
|
429
|
+
const addRecordsSave = JSON.parse(
|
|
430
|
+
JSON.stringify(require('./task_record_gvp_save.json'))
|
|
431
|
+
);
|
|
432
|
+
const addRecordsGet2 = JSON.parse(
|
|
433
|
+
JSON.stringify(require('./task_record_gvp_get2.json'))
|
|
434
|
+
);
|
|
435
|
+
|
|
436
|
+
const { bridge, luvio } = createBridge();
|
|
437
|
+
const caseId: string = initialLdsState.id;
|
|
438
|
+
|
|
439
|
+
const selector = {
|
|
440
|
+
recordId: keyBuilderRecord(luvio, { recordId: caseId }),
|
|
441
|
+
node: { kind: 'Fragment' as const, private: [] as string[] },
|
|
442
|
+
variables: {},
|
|
443
|
+
};
|
|
444
|
+
|
|
445
|
+
// Creating a refreshable snapshot of the Case record as we don't have the wire adapter bindings available to us in jest environment to refresh the snapshot manually.
|
|
446
|
+
const snapshot = luvio.storeLookup(selector, {
|
|
447
|
+
config: { recordId: caseId },
|
|
448
|
+
resolve: async () => {
|
|
449
|
+
const response = await luvio.dispatchResourceRequest({
|
|
450
|
+
baseUri: '',
|
|
451
|
+
basePath: `/ui-api/records/${caseId}`,
|
|
452
|
+
method: 'get',
|
|
453
|
+
body: null,
|
|
454
|
+
queryParams: {},
|
|
455
|
+
urlParams: {},
|
|
456
|
+
headers: {},
|
|
457
|
+
priority: 'normal',
|
|
458
|
+
});
|
|
459
|
+
const config = {
|
|
460
|
+
optionalFields: ['Case.Id'],
|
|
461
|
+
recordId: caseId,
|
|
462
|
+
};
|
|
463
|
+
const trackedFields = ['Case.Status'];
|
|
464
|
+
ingestRecordSuccess(luvio, config, caseId, trackedFields, response as any, 0);
|
|
465
|
+
await luvio.storeBroadcast();
|
|
466
|
+
return luvio.storeLookup(selector);
|
|
467
|
+
},
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
// 1. Prime LDS with the Case fixture (via addRecord → storeIngest + storeBroadcast).
|
|
471
|
+
addRecord(luvio, initialLdsState);
|
|
472
|
+
|
|
473
|
+
const expectedStatus = queryRecord(luvio, { recordId: caseId }).data.fields.Status;
|
|
474
|
+
expect(expectedStatus).toEqual({ value: 'New', displayValue: 'New' });
|
|
475
|
+
|
|
476
|
+
luvio.dispatchResourceRequest = jest.fn();
|
|
477
|
+
|
|
478
|
+
const fn = jest.fn();
|
|
479
|
+
bridge.receiveFromLdsCallback = fn;
|
|
480
|
+
|
|
481
|
+
// 2. Ingest the Task (+ spanning Case__r) via addRecords().
|
|
482
|
+
bridge.addRecords(addRecordsGet); // A RecordGvp.getRecord call is made to get the initial Task record when switching from the Case record to the Task record in console mode.
|
|
483
|
+
bridge.addRecords(addRecordsSave); // Clicking the complete button on the Task record which does a RecordGvp.saveRecord call.
|
|
484
|
+
bridge.addRecords(addRecordsGet2); // A RecordGvp.getRecord call is made to get the updated Task record.
|
|
485
|
+
|
|
486
|
+
expect(luvio.dispatchResourceRequest).toHaveBeenCalledTimes(0);
|
|
487
|
+
|
|
488
|
+
// 3. Synthesize the closed Case record server response which is expected to be received when the refreshSnapshot is called.
|
|
489
|
+
const closedCaseResponse = JSON.parse(JSON.stringify(require('./initial_record.json')));
|
|
490
|
+
closedCaseResponse.fields.Status = { value: 'Closed', displayValue: 'Closed' };
|
|
491
|
+
closedCaseResponse.weakEtag = addRecordsGet2[0].weakEtag;
|
|
492
|
+
closedCaseResponse.systemModstamp = addRecordsGet2[0].systemModstamp;
|
|
493
|
+
closedCaseResponse.lastModifiedDate = addRecordsGet2[0].lastModifiedDate;
|
|
494
|
+
|
|
495
|
+
luvio.dispatchResourceRequest = jest
|
|
496
|
+
.fn()
|
|
497
|
+
.mockResolvedValueOnce({ body: closedCaseResponse })
|
|
498
|
+
.mockImplementation(() => new Promise(() => {}));
|
|
499
|
+
|
|
500
|
+
// 4. Refresh the snapshot to get the updated Case record. This was previously not working because the interactions on the ADS-Bridge had updated the Case record with the latest weakEtag, but NOT the updated Status field value.
|
|
501
|
+
const refreshedSnapshot = await luvio.refreshSnapshot(snapshot);
|
|
502
|
+
expect((refreshedSnapshot.data as any).fields.Status).toEqual(
|
|
503
|
+
expect.objectContaining({ value: 'Closed' })
|
|
504
|
+
);
|
|
505
|
+
|
|
506
|
+
// 5. With the ADS-Bridge changes, the Case record is now emitted when the refreshSnapshot is called.
|
|
507
|
+
expect(fn.mock.calls[0]).toMatchInlineSnapshot(`
|
|
508
|
+
Array [
|
|
509
|
+
Object {
|
|
510
|
+
"500xx000000boDJAAY": Object {
|
|
511
|
+
"Case": Object {
|
|
512
|
+
"isPrimary": true,
|
|
513
|
+
"record": Object {
|
|
514
|
+
"apiName": "Case",
|
|
515
|
+
"childRelationships": Object {},
|
|
516
|
+
"eTag": "",
|
|
517
|
+
"fields": Object {
|
|
518
|
+
"CaseNumber": Object {
|
|
519
|
+
"displayValue": null,
|
|
520
|
+
"value": "00001026",
|
|
521
|
+
},
|
|
522
|
+
"Id": Object {
|
|
523
|
+
"displayValue": null,
|
|
524
|
+
"value": "500xx000000boDJAAY",
|
|
525
|
+
},
|
|
526
|
+
"IsEscalated": Object {
|
|
527
|
+
"displayValue": null,
|
|
528
|
+
"value": false,
|
|
529
|
+
},
|
|
530
|
+
"MasterRecordId": Object {
|
|
531
|
+
"displayValue": null,
|
|
532
|
+
"value": null,
|
|
533
|
+
},
|
|
534
|
+
"Priority": Object {
|
|
535
|
+
"displayValue": "Medium",
|
|
536
|
+
"value": "Medium",
|
|
537
|
+
},
|
|
538
|
+
"RecordTypeId": Object {
|
|
539
|
+
"displayValue": null,
|
|
540
|
+
"value": "012000000000000AAA",
|
|
541
|
+
},
|
|
542
|
+
"Status": Object {
|
|
543
|
+
"displayValue": "Closed",
|
|
544
|
+
"value": "Closed",
|
|
545
|
+
},
|
|
546
|
+
"Subject": Object {
|
|
547
|
+
"displayValue": null,
|
|
548
|
+
"value": "Fixture subject",
|
|
549
|
+
},
|
|
550
|
+
"SystemModstamp": Object {
|
|
551
|
+
"displayValue": null,
|
|
552
|
+
"value": "2026-04-11T00:46:56.000Z",
|
|
553
|
+
},
|
|
554
|
+
},
|
|
555
|
+
"id": "500xx000000boDJAAY",
|
|
556
|
+
"lastModifiedById": "005xx000001X8gHAAS",
|
|
557
|
+
"lastModifiedDate": "2026-04-13T20:54:41.000Z",
|
|
558
|
+
"recordTypeId": "012000000000000AAA",
|
|
559
|
+
"recordTypeInfo": null,
|
|
560
|
+
"systemModstamp": "2026-04-13T20:54:41.000Z",
|
|
561
|
+
"weakEtag": 1776113681000,
|
|
562
|
+
},
|
|
563
|
+
},
|
|
564
|
+
},
|
|
565
|
+
},
|
|
566
|
+
Object {
|
|
567
|
+
"Case": Object {
|
|
568
|
+
"_entityLabel": "Case",
|
|
569
|
+
"_keyPrefix": "500",
|
|
570
|
+
"_nameField": "Name",
|
|
571
|
+
},
|
|
572
|
+
},
|
|
573
|
+
]
|
|
574
|
+
`);
|
|
575
|
+
});
|
|
576
|
+
|
|
420
577
|
describe('displayValue', () => {
|
|
421
578
|
it('does not let null overwrite non-null displayValue', () => {
|
|
422
579
|
const { bridge, luvio } = createBridge();
|
|
@@ -1383,6 +1540,82 @@ describe('AdsBridge', () => {
|
|
|
1383
1540
|
});
|
|
1384
1541
|
});
|
|
1385
1542
|
|
|
1543
|
+
describe('filterAndSynthesizeBaseIds', () => {
|
|
1544
|
+
const BASE_KEY = 'UiApi::RecordRepresentation:001xx000000001AAA';
|
|
1545
|
+
const FIELD_KEY = 'UiApi::RecordRepresentation:001xx000000001AAA__fields__Name';
|
|
1546
|
+
const BASE_KEY_2 = 'UiApi::RecordRepresentation:001xx000000002AAA';
|
|
1547
|
+
const FIELD_KEY_2A = 'UiApi::RecordRepresentation:001xx000000002AAA__fields__Name';
|
|
1548
|
+
const FIELD_KEY_2B = 'UiApi::RecordRepresentation:001xx000000002AAA__fields__Id';
|
|
1549
|
+
const NON_RECORD_KEY = 'SomeOther::Thing:abc123';
|
|
1550
|
+
|
|
1551
|
+
function callFilter(bridge: AdsBridge, entries: { id: string }[]): { id: string }[] {
|
|
1552
|
+
return (bridge as any).filterAndSynthesizeBaseIds(entries);
|
|
1553
|
+
}
|
|
1554
|
+
|
|
1555
|
+
it('returns an empty array when given an empty array', () => {
|
|
1556
|
+
const { bridge } = createBridge();
|
|
1557
|
+
expect(callFilter(bridge, [])).toEqual([]);
|
|
1558
|
+
});
|
|
1559
|
+
|
|
1560
|
+
it('filters out non-record entries', () => {
|
|
1561
|
+
const { bridge } = createBridge();
|
|
1562
|
+
const result = callFilter(bridge, [{ id: NON_RECORD_KEY }]);
|
|
1563
|
+
expect(result).toEqual([]);
|
|
1564
|
+
});
|
|
1565
|
+
|
|
1566
|
+
it('returns base record entries unchanged', () => {
|
|
1567
|
+
const { bridge } = createBridge();
|
|
1568
|
+
const result = callFilter(bridge, [{ id: BASE_KEY }]);
|
|
1569
|
+
expect(result).toEqual([{ id: BASE_KEY }]);
|
|
1570
|
+
});
|
|
1571
|
+
|
|
1572
|
+
it('synthesizes a base record entry for a field entry with no direct base entry', () => {
|
|
1573
|
+
const { bridge } = createBridge();
|
|
1574
|
+
const result = callFilter(bridge, [{ id: FIELD_KEY }]);
|
|
1575
|
+
expect(result).toEqual([{ id: BASE_KEY }]);
|
|
1576
|
+
});
|
|
1577
|
+
|
|
1578
|
+
it('does not synthesize a duplicate base entry when a direct base entry is already present', () => {
|
|
1579
|
+
const { bridge } = createBridge();
|
|
1580
|
+
const result = callFilter(bridge, [{ id: BASE_KEY }, { id: FIELD_KEY }]);
|
|
1581
|
+
expect(result).toHaveLength(1);
|
|
1582
|
+
expect(result).toEqual([{ id: BASE_KEY }]);
|
|
1583
|
+
});
|
|
1584
|
+
|
|
1585
|
+
it('synthesizes exactly one base entry for multiple field entries sharing the same base', () => {
|
|
1586
|
+
const { bridge } = createBridge();
|
|
1587
|
+
const result = callFilter(bridge, [{ id: FIELD_KEY_2A }, { id: FIELD_KEY_2B }]);
|
|
1588
|
+
expect(result).toHaveLength(1);
|
|
1589
|
+
expect(result).toEqual([{ id: BASE_KEY_2 }]);
|
|
1590
|
+
});
|
|
1591
|
+
|
|
1592
|
+
it('returns base entries and synthesizes entries for fields whose base is not directly present', () => {
|
|
1593
|
+
const { bridge } = createBridge();
|
|
1594
|
+
const entries = [
|
|
1595
|
+
{ id: BASE_KEY },
|
|
1596
|
+
{ id: FIELD_KEY },
|
|
1597
|
+
{ id: FIELD_KEY_2A },
|
|
1598
|
+
{ id: FIELD_KEY_2B },
|
|
1599
|
+
];
|
|
1600
|
+
const result = callFilter(bridge, entries);
|
|
1601
|
+
expect(result).toHaveLength(2);
|
|
1602
|
+
expect(result).toContainEqual({ id: BASE_KEY });
|
|
1603
|
+
expect(result).toContainEqual({ id: BASE_KEY_2 });
|
|
1604
|
+
});
|
|
1605
|
+
|
|
1606
|
+
it('filters out non-record entries while still processing valid entries', () => {
|
|
1607
|
+
const { bridge } = createBridge();
|
|
1608
|
+
const result = callFilter(bridge, [
|
|
1609
|
+
{ id: NON_RECORD_KEY },
|
|
1610
|
+
{ id: BASE_KEY },
|
|
1611
|
+
{ id: FIELD_KEY_2A },
|
|
1612
|
+
]);
|
|
1613
|
+
expect(result).toHaveLength(2);
|
|
1614
|
+
expect(result).toContainEqual({ id: BASE_KEY });
|
|
1615
|
+
expect(result).toContainEqual({ id: BASE_KEY_2 });
|
|
1616
|
+
});
|
|
1617
|
+
});
|
|
1618
|
+
|
|
1386
1619
|
describe('isDMOEntity', () => {
|
|
1387
1620
|
it('should return true for DMO record', () => {
|
|
1388
1621
|
const record = createRecord({
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"apiName": "Case",
|
|
3
|
+
"childRelationships": {},
|
|
4
|
+
"eTag": "",
|
|
5
|
+
"fields": {
|
|
6
|
+
"Id": {
|
|
7
|
+
"displayValue": null,
|
|
8
|
+
"value": "500xx000000boDJAAY"
|
|
9
|
+
},
|
|
10
|
+
"RecordTypeId": {
|
|
11
|
+
"displayValue": null,
|
|
12
|
+
"value": "012000000000000AAA"
|
|
13
|
+
},
|
|
14
|
+
"IsEscalated": {
|
|
15
|
+
"displayValue": null,
|
|
16
|
+
"value": false
|
|
17
|
+
},
|
|
18
|
+
"MasterRecordId": {
|
|
19
|
+
"displayValue": null,
|
|
20
|
+
"value": null
|
|
21
|
+
},
|
|
22
|
+
"SystemModstamp": {
|
|
23
|
+
"displayValue": null,
|
|
24
|
+
"value": "2026-04-11T00:46:56.000Z"
|
|
25
|
+
},
|
|
26
|
+
"CaseNumber": {
|
|
27
|
+
"displayValue": null,
|
|
28
|
+
"value": "00001026"
|
|
29
|
+
},
|
|
30
|
+
"Priority": {
|
|
31
|
+
"displayValue": "Medium",
|
|
32
|
+
"value": "Medium"
|
|
33
|
+
},
|
|
34
|
+
"Status": {
|
|
35
|
+
"displayValue": "New",
|
|
36
|
+
"value": "New"
|
|
37
|
+
},
|
|
38
|
+
"Subject": {
|
|
39
|
+
"displayValue": null,
|
|
40
|
+
"value": "Fixture subject"
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
"id": "500xx000000boDJAAY",
|
|
44
|
+
"lastModifiedById": "005xx000001X8gHAAS",
|
|
45
|
+
"lastModifiedDate": "2026-04-13T20:50:18.000Z",
|
|
46
|
+
"recordTypeId": "012000000000000AAA",
|
|
47
|
+
"recordTypeInfo": null,
|
|
48
|
+
"systemModstamp": "2026-04-13T20:50:18.000Z",
|
|
49
|
+
"weakEtag": 1776113418000
|
|
50
|
+
}
|