diff --git a/TODO.txt b/TODO.txt index 92f1e14e79d..bfabbd1bbcc 100644 --- a/TODO.txt +++ b/TODO.txt @@ -4,6 +4,13 @@ Licenses that are needed with the source code and binary: * Amoss - https://github.com/bobalicious/amoss/blob/main/LICENSE * SObject Fabricator - https://github.com/bobalicious/SObjectFabricator/blob/master/LICENSE +* Docs at the top level about the ortoo Id + +Remove need for data-name - reference the id info + +Add library for getting LWC elements by their generated Ortoo Id + Add docs for the retrieval of elements + Look at the use of 'MockDatabase' in fflib Look at: SobjectUtils.getSobjectName @@ -19,8 +26,6 @@ Look at: * Try parent and datatable referencing a DML Service * Standards - * data-name to identify elements in a test - * e.g. [data-name="cancel"] * Labels should be: * Imported into a static named _LABEL * Referenced in an object named 'labels'. diff --git a/framework/default/ortoo-core/default/lwc/confirmationDialog/__tests__/confirmationDialog.test.js b/framework/default/ortoo-core/default/lwc/confirmationDialog/__tests__/confirmationDialog.test.js index 7e2dc8fb7f7..1bf01488460 100644 --- a/framework/default/ortoo-core/default/lwc/confirmationDialog/__tests__/confirmationDialog.test.js +++ b/framework/default/ortoo-core/default/lwc/confirmationDialog/__tests__/confirmationDialog.test.js @@ -45,10 +45,10 @@ describe('c-confirmation-dialog', () => { const expectedElement = element.shadowRoot.querySelector( 'c-modal div[slot="footer"]' ); expect( expectedElement ).not.toBe( null ); - expect( expectedElement.querySelector( '[data-name="confirm"]' ).title ).not.toBe( null ); - expect( expectedElement.querySelector( '[data-name="confirm"]' ).label ).not.toBe( null ); - expect( expectedElement.querySelector( '[data-name="cancel"]' ).title ).not.toBe( null ); - expect( expectedElement.querySelector( '[data-name="cancel"]' ).label ).not.toBe( null ); + expect( expectedElement.querySelector( '[data-ortoo-elem-id="confirmation-confirm"]' ).title ).not.toBe( null ); + expect( expectedElement.querySelector( '[data-ortoo-elem-id="confirmation-confirm"]' ).label ).not.toBe( null ); + expect( expectedElement.querySelector( '[data-ortoo-elem-id="confirmation-cancel"]' ).title ).not.toBe( null ); + expect( expectedElement.querySelector( '[data-ortoo-elem-id="confirmation-cancel"]' ).label ).not.toBe( null ); }); it('When set to visible, contains a div containing cancel and confirm buttons with the specified labels, directing them to the modal footer slot', () => { @@ -64,10 +64,10 @@ describe('c-confirmation-dialog', () => { const expectedElement = element.shadowRoot.querySelector( 'c-modal div[slot="footer"]' ); expect( expectedElement ).not.toBe( null ); - expect( expectedElement.querySelector( '[data-name="confirm"]' ).title ).toBe( 'Confirm thing' ); - expect( expectedElement.querySelector( '[data-name="confirm"]' ).label ).toBe( 'Confirm thing' ); - expect( expectedElement.querySelector( '[data-name="cancel"]' ).title ).toBe( 'Cancel thing' ); - expect( expectedElement.querySelector( '[data-name="cancel"]' ).label ).toBe( 'Cancel thing' ); + expect( expectedElement.querySelector( '[data-ortoo-elem-id="confirmation-confirm"]' ).title ).toBe( 'Confirm thing' ); + expect( expectedElement.querySelector( '[data-ortoo-elem-id="confirmation-confirm"]' ).label ).toBe( 'Confirm thing' ); + expect( expectedElement.querySelector( '[data-ortoo-elem-id="confirmation-cancel"]' ).title ).toBe( 'Cancel thing' ); + expect( expectedElement.querySelector( '[data-ortoo-elem-id="confirmation-cancel"]' ).label ).toBe( 'Cancel thing' ); }); it('When set to visible and passed a valid type, contains a div containing cancel and confirm buttons with the specified labels, directing them to the modal footer slot', () => { @@ -81,8 +81,8 @@ describe('c-confirmation-dialog', () => { const expectedElement = element.shadowRoot.querySelector( 'c-modal div[slot="footer"]' ); expect( expectedElement ).not.toBe( null ); - expect( expectedElement.querySelector( '[data-name="confirm"]' ).label ).not.toBe( null ); - expect( expectedElement.querySelector( '[data-name="cancel"]' ).label ).not.toBe( null ); + expect( expectedElement.querySelector( '[data-ortoo-elem-id="confirmation-confirm"]' ).label ).not.toBe( null ); + expect( expectedElement.querySelector( '[data-ortoo-elem-id="confirmation-cancel"]' ).label ).not.toBe( null ); }); it('When set to an invalid type, will throw an error', () => { @@ -113,7 +113,7 @@ describe('c-confirmation-dialog', () => { const cancelHandler = jest.fn(); element.addEventListener( 'cancel', cancelHandler ) ; - element.shadowRoot.querySelector( '[data-name="confirm"]' ).click(); + element.shadowRoot.querySelector( '[data-ortoo-elem-id="confirmation-confirm"]' ).click(); expect( confirmHandler ).toHaveBeenCalled(); expect( confirmHandler.mock.calls[0][0].detail ).toBe( CONFIRM_MESSAGE ); @@ -142,7 +142,7 @@ describe('c-confirmation-dialog', () => { const cancelHandler = jest.fn(); element.addEventListener( 'cancel', cancelHandler ) ; - element.shadowRoot.querySelector( '[data-name="cancel"]' ).click(); + element.shadowRoot.querySelector( '[data-ortoo-elem-id="confirmation-cancel"]' ).click(); expect( cancelHandler ).toHaveBeenCalled(); expect( cancelHandler.mock.calls[0][0].detail ).toBe( CANCEL_MESSAGE ); diff --git a/framework/default/ortoo-core/default/lwc/confirmationDialog/confirmationDialog.html b/framework/default/ortoo-core/default/lwc/confirmationDialog/confirmationDialog.html index 82b302d2ca7..f015c91338f 100644 --- a/framework/default/ortoo-core/default/lwc/confirmationDialog/confirmationDialog.html +++ b/framework/default/ortoo-core/default/lwc/confirmationDialog/confirmationDialog.html @@ -2,6 +2,7 @@
@@ -13,24 +14,25 @@
- - - - + + + + +
\ No newline at end of file diff --git a/framework/default/ortoo-core/default/lwc/confirmationDialog/confirmationDialog.js b/framework/default/ortoo-core/default/lwc/confirmationDialog/confirmationDialog.js index d7007326da3..8bd4f37dbb7 100644 --- a/framework/default/ortoo-core/default/lwc/confirmationDialog/confirmationDialog.js +++ b/framework/default/ortoo-core/default/lwc/confirmationDialog/confirmationDialog.js @@ -1,4 +1,5 @@ import { LightningElement, api } from 'lwc'; +import configureElementIdGenerator from 'c/elementIdGenerator'; import CONFIRM_LABEL from '@salesforce/label/c.ortoo_core_confirm'; import CANCEL_LABEL from '@salesforce/label/c.ortoo_core_cancel'; @@ -70,7 +71,16 @@ export default class ConfirmationDialog extends LightningElement { @api visible; + @api ortooElemIdPrefix = 'confirmation'; + + ortooIdConfiguration = { + modalId: '', + cancelId: 'cancel', + confirmId: 'confirm', + } + connectedCallback() { + configureElementIdGenerator( this ); this.confirmLabel = this.confirmLabel ? this.confirmLabel : buttonLabels[ this.type ].confirm; this.cancelLabel = this.cancelLabel ? this.cancelLabel : buttonLabels[ this.type ].cancel; } diff --git a/framework/default/ortoo-core/default/lwc/elementFinder/__tests__/elementFinder.test.js b/framework/default/ortoo-core/default/lwc/elementFinder/__tests__/elementFinder.test.js new file mode 100644 index 00000000000..4db42853b60 --- /dev/null +++ b/framework/default/ortoo-core/default/lwc/elementFinder/__tests__/elementFinder.test.js @@ -0,0 +1,33 @@ +import configureFindElement from 'c/elementFinder'; + +describe( 'configureFindElement', () => { + it( 'will add a "findElement" function against the passed object', () => { + + const objectToRunAgainst = {}; + + configureFindElement( objectToRunAgainst ); + + expect( objectToRunAgainst.findElement ).toBeDefined(); + }); +}); + +describe( 'findElement', () => { + it( 'will ask the bound object for the element with the specified data-ortoo-elem-id', () => { + + const mockQuerySelector = jest.fn(); + + const objectToRunAgainst = { + template: { + querySelector: mockQuerySelector + } + }; + mockQuerySelector.mockReturnValueOnce( 'the element' ); + + configureFindElement( objectToRunAgainst ); + + const response = objectToRunAgainst.findElement( 'test-element' ); + + expect( response ).toBe( 'the element' ); + expect( mockQuerySelector ).toBeCalledWith( '[data-ortoo-elem-id="test-element"]' ); + }); +}); \ No newline at end of file diff --git a/framework/default/ortoo-core/default/lwc/elementFinder/elementFinder.js b/framework/default/ortoo-core/default/lwc/elementFinder/elementFinder.js new file mode 100644 index 00000000000..eb1f3d8eab7 --- /dev/null +++ b/framework/default/ortoo-core/default/lwc/elementFinder/elementFinder.js @@ -0,0 +1,18 @@ +/** + * Will bind a function that will allow the bound LWC to 'findElement' passing an Ortoo Element Id. + * + * Should be bound in the connectedCallback of the LWC. E.g. + * + * connectedCallback() { + * configureFindElement( this ); + * } + * + * @param bindTo The LWC to bind this function to + */ +const configureFindElement = bindTo => { + bindTo.findElement = function( ortooElemId ) { + return this.template.querySelector( `[data-ortoo-elem-id="${ortooElemId}"]` ); + }.bind( bindTo ); +}; + +export default configureFindElement; \ No newline at end of file diff --git a/framework/default/ortoo-core/default/lwc/elementFinder/elementFinder.js-meta.xml b/framework/default/ortoo-core/default/lwc/elementFinder/elementFinder.js-meta.xml new file mode 100644 index 00000000000..884004a97fa --- /dev/null +++ b/framework/default/ortoo-core/default/lwc/elementFinder/elementFinder.js-meta.xml @@ -0,0 +1,5 @@ + + + 52.0 + false + \ No newline at end of file diff --git a/framework/default/ortoo-core/default/lwc/elementIdGenerator/__tests__/elementIdGenerator.test.js b/framework/default/ortoo-core/default/lwc/elementIdGenerator/__tests__/elementIdGenerator.test.js new file mode 100644 index 00000000000..96ac16533a2 --- /dev/null +++ b/framework/default/ortoo-core/default/lwc/elementIdGenerator/__tests__/elementIdGenerator.test.js @@ -0,0 +1,93 @@ +import configureElementIdGenerator from 'c/elementIdGenerator'; + +describe('configureElementIdGenerator', () => { + afterEach(() => { + }); + + it( 'will add properties to the passed object based on the ortooIdConfiguration property names with values built from the combination of the ortooIdConfiguration property values and the ortooElemIdPrefix', () => { + + const objectToRunAgainst = { + ortooElemIdPrefix: 'theprefix', + ortooIdConfiguration: { + field1: 'thefield1', + field2: 'thefield2' + } + }; + + configureElementIdGenerator( objectToRunAgainst ); + + expect( objectToRunAgainst.field1 ).toBe( 'theprefix-thefield1' ); + expect( objectToRunAgainst.field2 ).toBe( 'theprefix-thefield2' ); + }); + + it( 'will skip the value if it is not specified', () => { + + const objectToRunAgainst = { + ortooElemIdPrefix: 'theprefix', + ortooIdConfiguration: { + field1: '', + field2: 'thefield2' + } + }; + + configureElementIdGenerator( objectToRunAgainst ); + + expect( objectToRunAgainst.field1 ).toBe( 'theprefix' ); + expect( objectToRunAgainst.field2 ).toBe( 'theprefix-thefield2' ); + }); + + it( 'will throw an error if given an object with no ortooElemIdPrefix property', () => { + + const objectToRunAgainst = { + ortooIdConfiguration: { + field1: '', + field2: 'thefield2' + } + }; + + expect( () => configureElementIdGenerator( objectToRunAgainst ) ).toThrow(); + }); + it( 'will throw an error if given an object with an empty ortooElemIdPrefix property', () => { + + const objectToRunAgainst = { + ortooElemIdPrefix: '', + ortooIdConfiguration: { + field1: '', + field2: 'thefield2' + } + }; + + expect( () => configureElementIdGenerator( objectToRunAgainst ) ).toThrow(); + }); + it( 'will throw an error if given an object with a null ortooElemIdPrefix property', () => { + + const objectToRunAgainst = { + ortooElemIdPrefix: null, + ortooIdConfiguration: { + field1: '', + field2: 'thefield2' + } + }; + + expect( () => configureElementIdGenerator( objectToRunAgainst ) ).toThrow(); + }); + + it( 'will throw an error if given an object with no ortooIdConfiguration property', () => { + + const objectToRunAgainst = { + ortooElemIdPrefix: 'theprefix' + }; + + expect( () => configureElementIdGenerator( objectToRunAgainst ) ).toThrow(); + }); + + it( 'will not throw an error if given an object with an empty object for the ortooIdConfiguration property', () => { + + const objectToRunAgainst = { + ortooElemIdPrefix: 'theprefix', + ortooIdConfiguration: {} + }; + + expect( () => configureElementIdGenerator( objectToRunAgainst ) ).not.toThrow(); + }); +}); \ No newline at end of file diff --git a/framework/default/ortoo-core/default/lwc/elementIdGenerator/elementIdGenerator.js b/framework/default/ortoo-core/default/lwc/elementIdGenerator/elementIdGenerator.js new file mode 100644 index 00000000000..ab4aec4dceb --- /dev/null +++ b/framework/default/ortoo-core/default/lwc/elementIdGenerator/elementIdGenerator.js @@ -0,0 +1,25 @@ +const generateId = function( suffix ) { + return suffix ? this.ortooElemIdPrefix + '-' + suffix : this.ortooElemIdPrefix;; +} + +const configureElementIdGenerator = function( bindTo ) { + + if ( ! bindTo.ortooIdConfiguration ) { + throw 'configureGenerator called against an object with no ortooIdConfiguration member variable - this should contain the mapping of properties to their suffixes: ' + bindTo; + } + + if ( ! bindTo.ortooElemIdPrefix ) { + throw new 'configureGenerator called against an object with no ortooElemIdPrefix member variable - this should contain the Id prefix' + bindTo; + } + + for ( const idName in bindTo.ortooIdConfiguration ) { + + Object.defineProperty( bindTo, idName, { + get: function() { + return generateId.call( bindTo, bindTo.ortooIdConfiguration[ idName ] ); + } + }); + } +} + +export default configureElementIdGenerator; \ No newline at end of file diff --git a/framework/default/ortoo-core/default/lwc/elementIdGenerator/elementIdGenerator.js-meta.xml b/framework/default/ortoo-core/default/lwc/elementIdGenerator/elementIdGenerator.js-meta.xml new file mode 100644 index 00000000000..884004a97fa --- /dev/null +++ b/framework/default/ortoo-core/default/lwc/elementIdGenerator/elementIdGenerator.js-meta.xml @@ -0,0 +1,5 @@ + + + 52.0 + false + \ No newline at end of file diff --git a/framework/default/ortoo-core/default/lwc/errorRenderer/__tests__/errorRenderer.test.js b/framework/default/ortoo-core/default/lwc/errorRenderer/__tests__/errorRenderer.test.js index 73c62b38b27..3a3ec6b4cb0 100644 --- a/framework/default/ortoo-core/default/lwc/errorRenderer/__tests__/errorRenderer.test.js +++ b/framework/default/ortoo-core/default/lwc/errorRenderer/__tests__/errorRenderer.test.js @@ -1,6 +1,6 @@ import displayError from 'c/errorRenderer'; -let showToastEvent = function ShowToastEvent( details ) { +const showToastEvent = function ShowToastEvent( details ) { this.detail = details }; @@ -8,7 +8,7 @@ jest.mock('lightning/platformShowToastEvent', () => ({ ShowToastEvent: showToastEvent })); -describe('c-error-renderer', () => { +describe('displayError', () => { afterEach(() => { }); @@ -16,24 +16,52 @@ describe('c-error-renderer', () => { console.error = jest.fn(); - let objectToRunAgainst = { + const objectToRunAgainst = { dispatchEvent: jest.fn() }; - let error = 'An error string'; + const error = 'An error string'; displayError.call( objectToRunAgainst, error ); expect( objectToRunAgainst.dispatchEvent ).toBeCalled(); - let dispatchedEvent = objectToRunAgainst.dispatchEvent.mock.calls[0][0]; + const dispatchedEvent = objectToRunAgainst.dispatchEvent.mock.calls[0][0]; expect( dispatchedEvent.detail.title ).toBe( 'c.ortoo_core_error_title' ); expect( dispatchedEvent.detail.message ).toBe( error ); expect( dispatchedEvent.detail.variant ).toBe( 'error' ); expect( console.error ).toHaveBeenCalledTimes( 1 ); - let reportedError = console.error.mock.calls[0][0]; + const reportedError = console.error.mock.calls[0][0]; + + expect( reportedError ).toBe( error ); + }); + + it( 'When the passed javascript error is a string, will raise a toast with the message as the error message', () => { + + console.error = jest.fn(); + + const objectToRunAgainst = { + dispatchEvent: jest.fn() + }; + + const error = { + message: 'javascript error format' + }; + + displayError.call( objectToRunAgainst, error ); + + expect( objectToRunAgainst.dispatchEvent ).toBeCalled(); + + const dispatchedEvent = objectToRunAgainst.dispatchEvent.mock.calls[0][0]; + + expect( dispatchedEvent.detail.title ).toBe( 'c.ortoo_core_error_title' ); + expect( dispatchedEvent.detail.message ).toBe( 'javascript error format' ); + expect( dispatchedEvent.detail.variant ).toBe( 'error' ); + + expect( console.error ).toHaveBeenCalledTimes( 1 ); + const reportedError = console.error.mock.calls[0][0]; expect( reportedError ).toBe( error ); }); @@ -42,11 +70,11 @@ describe('c-error-renderer', () => { console.error = jest.fn(); - let objectToRunAgainst = { + const objectToRunAgainst = { dispatchEvent: jest.fn() }; - let error = { + const error = { body: { message: 'An error message in the body' } @@ -56,14 +84,14 @@ describe('c-error-renderer', () => { expect( objectToRunAgainst.dispatchEvent ).toBeCalled(); - let dispatchedEvent = objectToRunAgainst.dispatchEvent.mock.calls[0][0]; + const dispatchedEvent = objectToRunAgainst.dispatchEvent.mock.calls[0][0]; expect( dispatchedEvent.detail.title ).toBe( 'c.ortoo_core_error_title' ); expect( dispatchedEvent.detail.message ).toBe( 'An error message in the body' ); expect( dispatchedEvent.detail.variant ).toBe( 'error' ); expect( console.error ).toHaveBeenCalledTimes( 1 ); - let reportedError = console.error.mock.calls[0][0]; + const reportedError = console.error.mock.calls[0][0]; expect( reportedError ).toBe( error ); }); diff --git a/framework/default/ortoo-core/default/lwc/errorRenderer/errorRenderer.js b/framework/default/ortoo-core/default/lwc/errorRenderer/errorRenderer.js index b3720e88680..43ed94e408c 100644 --- a/framework/default/ortoo-core/default/lwc/errorRenderer/errorRenderer.js +++ b/framework/default/ortoo-core/default/lwc/errorRenderer/errorRenderer.js @@ -1,5 +1,4 @@ import { ShowToastEvent } from 'lightning/platformShowToastEvent'; - import ERROR_TITLE from '@salesforce/label/c.ortoo_core_error_title'; /** @@ -7,11 +6,19 @@ import ERROR_TITLE from '@salesforce/label/c.ortoo_core_error_title'; */ const displayError = function( error ) { - let title = ERROR_TITLE; + let title = ERROR_TITLE; // should be a label + + // By default we assume we have a string for the error let message = error; console.error( error ); + // Javascript Errors will have message set + if ( error.message ) { + message = error.message; + } + + // Apex Exceptions will have body.message set if ( error.body ) { message = error.body.message; } diff --git a/framework/default/ortoo-core/default/lwc/formValidator/__tests__/formValidator.test.js b/framework/default/ortoo-core/default/lwc/formValidator/__tests__/formValidator.test.js index deee97d110e..53ba3e6d961 100644 --- a/framework/default/ortoo-core/default/lwc/formValidator/__tests__/formValidator.test.js +++ b/framework/default/ortoo-core/default/lwc/formValidator/__tests__/formValidator.test.js @@ -1,6 +1,6 @@ import reportValidity from 'c/formValidator'; -let showToastEvent = function ShowToastEvent( details ) { +const showToastEvent = function ShowToastEvent( details ) { this.detail = details }; @@ -8,16 +8,16 @@ jest.mock('lightning/platformShowToastEvent', () => ({ ShowToastEvent: showToastEvent })); -describe('c-form-validator', () => { +describe('reportValidity', () => { afterEach(() => { }); it( 'When called against an object, will ask it for all the data-validateable elements and then call reportValidity against each one. If all are true, returns true', () => { - let mockQuerySelector = jest.fn(); - let mockDispatchEvent = jest.fn(); + const mockQuerySelector = jest.fn(); + const mockDispatchEvent = jest.fn(); - let objectToRunAgainst = { + const objectToRunAgainst = { dispatchEvent: mockDispatchEvent, template: { querySelectorAll: mockQuerySelector @@ -31,7 +31,7 @@ describe('c-form-validator', () => { ] ); - let response = reportValidity.call( objectToRunAgainst ); + const response = reportValidity.call( objectToRunAgainst ); expect( response ).toBe( true ); @@ -43,10 +43,10 @@ describe('c-form-validator', () => { it( 'When an element reportValidity returns false, will return false and raise a toast', () => { - let mockQuerySelector = jest.fn(); - let mockDispatchEvent = jest.fn(); + const mockQuerySelector = jest.fn(); + const mockDispatchEvent = jest.fn(); - let objectToRunAgainst = { + const objectToRunAgainst = { dispatchEvent: mockDispatchEvent, template: { querySelectorAll: mockQuerySelector @@ -60,13 +60,13 @@ describe('c-form-validator', () => { ] ); - let response = reportValidity.call( objectToRunAgainst ); + const response = reportValidity.call( objectToRunAgainst ); expect( response ).toBe( false ); expect( mockDispatchEvent ).toHaveBeenCalledTimes( 1 ); - let dispatchedEvent = mockDispatchEvent.mock.calls[0][0]; + const dispatchedEvent = mockDispatchEvent.mock.calls[0][0]; expect( dispatchedEvent.detail.title ).toBe( 'c.ortoo_core_error_title' ); expect( dispatchedEvent.detail.message ).toBe( 'c.ortoo_core_validation_errors_occurred' ); @@ -76,17 +76,17 @@ describe('c-form-validator', () => { it( 'When an element reportValidity returns false, will still call against all the others', () => { // this ensures that every object is checked - otherwise not all errors will be reported properly - let mockQuerySelector = jest.fn(); - let mockDispatchEvent = jest.fn(); + const mockQuerySelector = jest.fn(); + const mockDispatchEvent = jest.fn(); - let objectToRunAgainst = { + const objectToRunAgainst = { dispatchEvent: mockDispatchEvent, template: { querySelectorAll: mockQuerySelector } }; - let mockSelectorReturn = [ + const mockSelectorReturn = [ { reportValidity: jest.fn().mockReturnValueOnce( true ) }, { reportValidity: jest.fn().mockReturnValueOnce( false ) }, { reportValidity: jest.fn().mockReturnValueOnce( false ) }, @@ -96,9 +96,92 @@ describe('c-form-validator', () => { mockQuerySelector.mockReturnValueOnce( mockSelectorReturn ); - let response = reportValidity.call( objectToRunAgainst ); + const response = reportValidity.call( objectToRunAgainst ); expect( response ).toBe( false ); mockSelectorReturn.forEach( thisElement => expect( thisElement.reportValidity ).toHaveBeenCalledTimes( 1 ) ); }); + + it( 'When an element reportValidity returns false, but options say to not show a toast, will not show a toast', () => { + + const mockQuerySelector = jest.fn(); + const mockDispatchEvent = jest.fn(); + + const objectToRunAgainst = { + dispatchEvent: mockDispatchEvent, + template: { + querySelectorAll: mockQuerySelector + } + }; + + mockQuerySelector.mockReturnValueOnce( + [ + { reportValidity: () => true }, + { reportValidity: () => false } + ] + ); + + reportValidity.call( objectToRunAgainst, { showToast: false } ); + expect( mockDispatchEvent ).toHaveBeenCalledTimes( 0 ); + + }); + + it( 'When an element reportValidity returns false, and options say to show a toast, will show a toast', () => { + + const mockQuerySelector = jest.fn(); + const mockDispatchEvent = jest.fn(); + + const objectToRunAgainst = { + dispatchEvent: mockDispatchEvent, + template: { + querySelectorAll: mockQuerySelector + } + }; + + mockQuerySelector.mockReturnValueOnce( + [ + { reportValidity: () => true }, + { reportValidity: () => false } + ] + ); + + reportValidity.call( objectToRunAgainst, { showToast: true } ); + expect( mockDispatchEvent ).toHaveBeenCalledTimes( 1 ); + + }); + it( 'When an element reportValidity returns false, and given alternative error title and message, will show a toast with the specified text', () => { + + const mockQuerySelector = jest.fn(); + const mockDispatchEvent = jest.fn(); + + const objectToRunAgainst = { + dispatchEvent: mockDispatchEvent, + template: { + querySelectorAll: mockQuerySelector + } + }; + + mockQuerySelector.mockReturnValueOnce( + [ + { reportValidity: () => true }, + { reportValidity: () => false } + ] + ); + + const customTitle = 'custom title'; + const customMessage = 'custom message'; + const customVariant = 'warning'; + + const response = reportValidity.call( objectToRunAgainst, { validationErrorTitle: customTitle, validationErrorMessage: customMessage, toastVariant: customVariant } ); + + expect( response ).toBe( false ); + + expect( mockDispatchEvent ).toHaveBeenCalledTimes( 1 ); + + const dispatchedEvent = mockDispatchEvent.mock.calls[0][0]; + + expect( dispatchedEvent.detail.title ).toBe( customTitle ); + expect( dispatchedEvent.detail.message ).toBe( customMessage ); + expect( dispatchedEvent.detail.variant ).toBe( customVariant ); + }); }); \ No newline at end of file diff --git a/framework/default/ortoo-core/default/lwc/formValidator/formValidator.html b/framework/default/ortoo-core/default/lwc/formValidator/formValidator.html deleted file mode 100644 index 27e0f69560f..00000000000 --- a/framework/default/ortoo-core/default/lwc/formValidator/formValidator.html +++ /dev/null @@ -1,3 +0,0 @@ - \ No newline at end of file diff --git a/framework/default/ortoo-core/default/lwc/formValidator/formValidator.js b/framework/default/ortoo-core/default/lwc/formValidator/formValidator.js index bcc3924b91e..02ff3ad1df8 100644 --- a/framework/default/ortoo-core/default/lwc/formValidator/formValidator.js +++ b/framework/default/ortoo-core/default/lwc/formValidator/formValidator.js @@ -13,10 +13,22 @@ import VALIDATION_MESSAGE from '@salesforce/label/c.ortoo_core_validation_errors * * @returns Boolean States if the bound LWC is regarded as valid. */ -const reportValidity = function() { +const reportValidity = function( options ) { + + !options && ( options = {} ); + + !options.hasOwnProperty( 'showToast' ) && ( options.showToast = true ); + !options.hasOwnProperty( 'toastVariant' ) && ( options.toastVariant = 'error' ); + + !options.validationErrorTitle && ( options.validationErrorTitle = ERROR_TITLE ); + !options.validationErrorMessage && ( options.validationErrorMessage = VALIDATION_MESSAGE ); const validateableElements = this.template.querySelectorAll( '[data-validateable]' ); + if ( !validateableElements ) { + return true; + } + let hasValidationError = false; validateableElements.forEach( thisElement => { if ( ! thisElement.reportValidity() ) { @@ -24,11 +36,11 @@ const reportValidity = function() { } }); - if ( hasValidationError ) { + if ( hasValidationError && options.showToast ) { const toastEvent = new ShowToastEvent({ - title: ERROR_TITLE, - message: VALIDATION_MESSAGE, - variant: 'error', + title: options.validationErrorTitle, + message: options.validationErrorMessage, + variant: options.toastVariant, }); this.dispatchEvent( toastEvent ); } diff --git a/framework/default/ortoo-core/default/lwc/modal/__tests__/modal.test.js b/framework/default/ortoo-core/default/lwc/modal/__tests__/modal.test.js index 888d00c5c98..ce6e25cd9b2 100644 --- a/framework/default/ortoo-core/default/lwc/modal/__tests__/modal.test.js +++ b/framework/default/ortoo-core/default/lwc/modal/__tests__/modal.test.js @@ -117,12 +117,26 @@ describe('c-modal', () => { return Promise.resolve() .then( () => { const clickEvent = new CustomEvent( 'click', {} ); - return element.shadowRoot.querySelector( 'button[data-name="cancel-cross"]' ).dispatchEvent( clickEvent ); + return element.shadowRoot.querySelector( '[data-ortoo-elem-id="modal-closecross"]' ).dispatchEvent( clickEvent ); }) .then( () => { expect( cancelHandler ).toHaveBeenCalled(); }); }); + it( 'Will use the passed prefix to define the element ids', () => { + const element = createElement('c-modal', { + is: Modal + }); + element.visible = true; + element.ortooElemIdPrefix = 'definedmodel' + + document.body.appendChild( element ); + + return Promise.resolve() + .then( () => { + expect( element.shadowRoot.querySelector( '[data-ortoo-elem-id="definedmodel-closecross"]' ) ).not.toBe( null ); + }) + }); }); \ No newline at end of file diff --git a/framework/default/ortoo-core/default/lwc/modal/modal.html b/framework/default/ortoo-core/default/lwc/modal/modal.html index a2b3f208f6a..650722d99be 100644 --- a/framework/default/ortoo-core/default/lwc/modal/modal.html +++ b/framework/default/ortoo-core/default/lwc/modal/modal.html @@ -13,10 +13,10 @@