-
Notifications
You must be signed in to change notification settings - Fork 3.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Lightning record picker multi value (#893)
* feat: creating record-picker-multi-value scenario + using record-picker component * feat: adding pill-container to displayed selected records * feat: handling pill-container itemremove event * feat: adding logic to filters fetched records based on the one that already has been selected * feat: creating test for record-picker-multi-value * test: should clear the input when a selection is made * feat: adding test for records filtering * refacto: clean-up tests and comments * refacto: add state * refacto: simplify code (remove state) * chore: add comments + error and view source panel * feat: adding lightning-card to the recordPickerMultiValue cmp * feat: adding recordPickerMultiValue cmp to recordPicker flexipage * fix: display icon in pill items * fix: Reset recordId to ensure the wire can be called a second time the same recordId * change to gql wire * fix gql * chore: fix typo * pr: adjust view source footer --------- Co-authored-by: skempf <skempf@salesforce.com>
- Loading branch information
1 parent
33b6697
commit 4f57051
Showing
7 changed files
with
432 additions
and
11 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
30 changes: 30 additions & 0 deletions
30
force-app/main/default/lwc/recordPickerMultiValue/__tests__/data/graphqlContactResult.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
{ | ||
"uiapi": { | ||
"query": { | ||
"Contact": { | ||
"edges": [ | ||
{ | ||
"node": { | ||
"Id": "005xx000016QpSqAAK", | ||
"Name": { | ||
"value": "Amy Taylor" | ||
}, | ||
"Title": { | ||
"value": "VP of Engineering" | ||
}, | ||
"Phone": { | ||
"value": "4152568563" | ||
}, | ||
"Email": { | ||
"value": "amy@demo.net" | ||
}, | ||
"Picture__c": { | ||
"value": "https://s3-us-west-2.amazonaws.com/dev-or-devrl-s3-bucket/sample-apps/people/amy_taylor.jpg" | ||
} | ||
} | ||
} | ||
] | ||
} | ||
} | ||
} | ||
} |
202 changes: 202 additions & 0 deletions
202
force-app/main/default/lwc/recordPickerMultiValue/__tests__/recordPickerMultiValue.test.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,202 @@ | ||
import { createElement } from 'lwc'; | ||
import { graphql } from 'lightning/uiGraphQLApi'; | ||
import RecordPickerMultiValue from 'c/recordPickerMultiValue'; | ||
|
||
// Mock realistic data | ||
const mockGraphQL = require('./data/graphqlContactResult.json'); | ||
|
||
// Helper function to wait until the microtask queue is empty. This is needed for promise | ||
// timing when calling imperative Apex. | ||
async function flushPromises() { | ||
return Promise.resolve(); | ||
} | ||
|
||
describe('recordPickerMultiValue', () => { | ||
let element; | ||
beforeEach(() => { | ||
// Create component | ||
element = createElement('c-record-picker-multi-value', { | ||
is: RecordPickerMultiValue | ||
}); | ||
element.objectApiName = 'Contact'; | ||
element.label = 'Contact'; | ||
document.body.appendChild(element); | ||
}); | ||
|
||
afterEach(() => { | ||
// The jsdom instance is shared across test cases in a single file so reset the DOM | ||
while (document.body.firstChild) { | ||
document.body.removeChild(document.body.firstChild); | ||
} | ||
// Prevent data saved on mocks from leaking between tests | ||
jest.clearAllMocks(); | ||
}); | ||
|
||
it('is accessible', async () => { | ||
expect(element).toBeAccessible(); | ||
}); | ||
|
||
it('should display the selected record in the pill container', async () => { | ||
// Set selected record | ||
const recordPickerElement = element.shadowRoot.querySelector( | ||
'lightning-record-picker' | ||
); | ||
recordPickerElement.value = '005xx000001X83aAAC'; | ||
recordPickerElement.dispatchEvent( | ||
new CustomEvent('change', { | ||
detail: { recordId: '005xx000001X83aAAC' } | ||
}) | ||
); | ||
await flushPromises(); | ||
|
||
// Emit data from @wire | ||
graphql.emit(mockGraphQL); | ||
await flushPromises(); | ||
|
||
const pillContainer = element.shadowRoot.querySelector( | ||
'lightning-pill-container' | ||
); | ||
|
||
expect(pillContainer.items).toEqual([ | ||
{ | ||
name: '005xx000016QpSqAAK', | ||
label: 'Amy Taylor', | ||
iconName: 'standard:contact', | ||
type: 'icon' | ||
} | ||
]); | ||
}); | ||
|
||
it('should clear the input when a selection is made', async () => { | ||
const recordPickerElement = element.shadowRoot.querySelector( | ||
'lightning-record-picker' | ||
); | ||
|
||
// Spy on recordPickerElement.clearSelection() | ||
const clearSelection = jest.spyOn( | ||
recordPickerElement, | ||
'clearSelection' | ||
); | ||
|
||
// Simulate a record selection in the record picker | ||
recordPickerElement.value = '005xx000001X83aAAC'; | ||
recordPickerElement.dispatchEvent( | ||
new CustomEvent('change', { | ||
detail: { recordId: '005xx000001X83aAAC' } | ||
}) | ||
); | ||
await flushPromises(); | ||
|
||
// Emit data from @wire | ||
graphql.emit(mockGraphQL); | ||
await flushPromises(); | ||
|
||
expect(clearSelection).toHaveBeenCalled(); | ||
}); | ||
|
||
it('should filter out a record from the suggestions when it has already been selected', async () => { | ||
// Set selected record | ||
const recordPickerElement = element.shadowRoot.querySelector( | ||
'lightning-record-picker' | ||
); | ||
recordPickerElement.value = '005xx000016QpSqAAK'; | ||
recordPickerElement.dispatchEvent( | ||
new CustomEvent('change', { | ||
detail: { recordId: '005xx000016QpSqAAK' } | ||
}) | ||
); | ||
await flushPromises(); | ||
|
||
// Emit data from @wire | ||
graphql.emit(mockGraphQL); | ||
await flushPromises(); | ||
|
||
expect(recordPickerElement.filter.criteria).toEqual( | ||
expect.arrayContaining([ | ||
{ | ||
fieldPath: 'Id', | ||
operator: 'nin', | ||
value: ['005xx000016QpSqAAK'] | ||
} | ||
]) | ||
); | ||
}); | ||
|
||
it('should remove the corresponding pill when selected record is removed', async () => { | ||
// Set selected record | ||
const recordPickerElement = element.shadowRoot.querySelector( | ||
'lightning-record-picker' | ||
); | ||
recordPickerElement.value = '005xx000001X83aAAC'; | ||
recordPickerElement.dispatchEvent( | ||
new CustomEvent('change', { | ||
detail: { recordId: '005xx000001X83aAAC' } | ||
}) | ||
); | ||
// Emit data from @wire | ||
graphql.emit(mockGraphQL); | ||
await flushPromises(); | ||
|
||
// Simulate a selection removal | ||
const pillContainer = element.shadowRoot.querySelector( | ||
'lightning-pill-container' | ||
); | ||
pillContainer.dispatchEvent( | ||
new CustomEvent('itemremove', { | ||
detail: { | ||
item: { | ||
name: '005xx000016QpSqAAK', | ||
label: 'Amy Taylor', | ||
iconName: 'standard:contact' | ||
} | ||
} | ||
}) | ||
); | ||
await flushPromises(); | ||
|
||
// The pill item has been removed | ||
expect(pillContainer.items).toEqual([]); | ||
}); | ||
|
||
it('should remove a record from filter when it has been removed from selection', async () => { | ||
// Set selected record | ||
const recordPickerElement = element.shadowRoot.querySelector( | ||
'lightning-record-picker' | ||
); | ||
recordPickerElement.value = '005xx000001X83aAAC'; | ||
recordPickerElement.dispatchEvent( | ||
new CustomEvent('change', { | ||
detail: { recordId: '005xx000001X83aAAC' } | ||
}) | ||
); | ||
await flushPromises(); | ||
|
||
// Emit data from @wire | ||
graphql.emit(mockGraphQL); | ||
await flushPromises(); | ||
|
||
// Simulate a selection removal | ||
const pillContainer = element.shadowRoot.querySelector( | ||
'lightning-pill-container' | ||
); | ||
pillContainer.dispatchEvent( | ||
new CustomEvent('itemremove', { | ||
detail: { | ||
item: { | ||
name: '005xx000016QpSqAAK', | ||
label: 'Amy Taylor', | ||
iconName: 'standard:contact' | ||
} | ||
} | ||
}) | ||
); | ||
await flushPromises(); | ||
|
||
// no more filter with the selected record id | ||
expect(recordPickerElement.filter.criteria).toEqual( | ||
expect.arrayContaining([ | ||
{ fieldPath: 'Id', operator: 'nin', value: [] } | ||
]) | ||
); | ||
}); | ||
}); |
32 changes: 32 additions & 0 deletions
32
force-app/main/default/lwc/recordPickerMultiValue/recordPickerMultiValue.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
<template> | ||
<lightning-card | ||
title="RecordPickerMultiValue" | ||
class="slds-card__body_inner" | ||
icon-name="standard:search" | ||
> | ||
<div> | ||
<lightning-record-picker | ||
lwc:ref="recordPicker" | ||
label="Contacts" | ||
placeholder="Search Contacts..." | ||
object-api-name="Contact" | ||
filter={recordPickerFilter} | ||
onchange={handleRecordPickerChange} | ||
></lightning-record-picker> | ||
</div> | ||
|
||
<lightning-pill-container | ||
items={pillItems} | ||
onitemremove={handlePillRemove} | ||
></lightning-pill-container> | ||
<template lwc:if={wireError}> | ||
<c-error-panel errors={wireError}></c-error-panel> | ||
</template> | ||
|
||
<c-view-source source="lwc/recordPickerMultiValue" slot="footer"> | ||
As of Winter '24, `lightning-record-picker` only supports a single | ||
selection. This sample component shows how you can turn | ||
`lightning-record-picker` into a multi-selection record picker. | ||
</c-view-source> | ||
</lightning-card> | ||
</template> |
Oops, something went wrong.