Skip to content

Commit

Permalink
Added initial versions of base LWCs, including a confirmation dialog …
Browse files Browse the repository at this point in the history
…and a formatted modal
  • Loading branch information
rob-baillie-ortoo committed Dec 13, 2021
1 parent 78dabd3 commit fad409d
Show file tree
Hide file tree
Showing 25 changed files with 21,458 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import { createElement } from 'lwc';
import ConfirmationDialog from 'c/confirmationDialog';

describe('c-confirmation-dialog', () => {
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);
}
});

it('When set to visible, and with a title contains a div containing the title, directing it to the title slot', () => {
const element = createElement('c-confirmation-dialog', {
is: ConfirmationDialog
});
element.title = 'The title';
element.visible = true;
document.body.appendChild(element);

const expectedElement = element.shadowRoot.querySelector( 'c-formatted-modal div[slot="title"]' );
expect( expectedElement.innerHTML ).toBe( 'The title' );
});

it('When set to visible, contains a div containing a message slot, directing it to the modal contents slot', () => {
const element = createElement('c-confirmation-dialog', {
is: ConfirmationDialog
});
element.message = 'The message';
element.visible = true;
document.body.appendChild(element);

const expectedElement = element.shadowRoot.querySelector( 'c-formatted-modal div[slot="contents"]' );
expect( expectedElement ).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', () => {
const element = createElement('c-confirmation-dialog', {
is: ConfirmationDialog
});
element.confirmLabel = 'Confirm';
element.cancelLabel = 'Cancel';
element.visible = true;
document.body.appendChild(element);

const expectedElement = element.shadowRoot.querySelector( 'c-formatted-modal div[slot="footer"]' );
expect( expectedElement ).not.toBe( null );

expect( expectedElement.querySelector( '[title="Confirm"]' ) ).not.toBe( null );
expect( expectedElement.querySelector( '[title="Cancel"]' ) ).not.toBe( null );
});

it('Clicking the confirm button will issue an event containing the confirm message', () => {

const CONFIRM_MESSAGE = 'The confirm message';
const CANCEL_MESSAGE = 'The cancel message';

const element = createElement('c-confirmation-dialog', {
is: ConfirmationDialog
});

element.confirmLabel = 'Confirm';
element.confirmMessage = CONFIRM_MESSAGE;
element.cancelLabel = 'Cancel';
element.cancelMessage = CANCEL_MESSAGE;

element.visible = true;
document.body.appendChild(element);

const confirmHandler = jest.fn();
element.addEventListener( 'confirm', confirmHandler ) ;

const cancelHandler = jest.fn();
element.addEventListener( 'cancel', cancelHandler ) ;

element.shadowRoot.querySelector( '[title="Confirm"]' ).click();

expect( confirmHandler ).toHaveBeenCalled();
expect( confirmHandler.mock.calls[0][0].detail ).toBe( CONFIRM_MESSAGE );

expect( cancelHandler ).not.toHaveBeenCalled();
});

it('Clicking the cancel button will issue an event containing the cancel message', () => {

const CONFIRM_MESSAGE = 'The confirm message';
const CANCEL_MESSAGE = 'The cancel message';

const element = createElement('c-confirmation-dialog', {
is: ConfirmationDialog
});

element.confirmLabel = 'Confirm';
element.confirmMessage = CONFIRM_MESSAGE;
element.cancelLabel = 'Cancel';
element.cancelMessage = CANCEL_MESSAGE;

element.visible = true;
document.body.appendChild(element);

const confirmHandler = jest.fn();
element.addEventListener( 'confirm', confirmHandler ) ;

const cancelHandler = jest.fn();
element.addEventListener( 'cancel', cancelHandler ) ;

element.shadowRoot.querySelector( '[title="Cancel"]' ).click();

expect( cancelHandler ).toHaveBeenCalled();
expect( cancelHandler.mock.calls[0][0].detail ).toBe( CANCEL_MESSAGE );

expect( confirmHandler ).not.toHaveBeenCalled();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<template>
<c-formatted-modal visible={visible}>
<div slot="title">
{title}
</div>

<div slot="contents">
<slot name="message"></slot>
</div>

<div slot="footer">
<lightning-button-group>
<lightning-button
variant="neutral"
name="cancel"
label={cancelLabel}
title={cancelLabel}
onclick={handleCancel}
></lightning-button>
<lightning-button
variant="brand"
name="confirm"
label={confirmLabel}
title={confirmLabel}
onclick={handleConfirm}
></lightning-button>
</lightning-button-group>
</div>
</c-formatted-modal>
</template>
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { LightningElement, api } from 'lwc';

export default class ConfirmationDialog extends LightningElement {

@api title;
@api name;
// TODO: default labels
// TODO: consider standard variations - No / *Yes ; Cancel / *Confirm ; Cancel / *Save
@api confirmLabel;
@api cancelLabel;

// The message to send back to the parent component when the confirmation button is clicked
@api confirmMessage;

// The message to send back to the parent component when the cancel button is clicked
@api cancelMessage;

@api visible;

handleCancel( event ) {
this.dispatchEvent( new CustomEvent( 'cancel', { detail: this.cancelMessage } ) );
}

handleConfirm( event ) {
this.dispatchEvent( new CustomEvent( 'confirm', { detail: this.confirmMessage } ) );
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>52.0</apiVersion>
<description>Provides a modal dialog box that will request confirmation or cancellation. Both events can have a 'message' provided to them by the consumer, allowing the dialog to state what has been confirmed or cancelled.</description>
<masterLabel>Confirmation Dialog</masterLabel>
<isExposed>false</isExposed>
</LightningComponentBundle>
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import { createElement } from 'lwc';
import FormattedModal from 'c/formattedModal';

describe('c-formatted-modal', () => {
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);
}
});
it('Will use a c-modal to render the modal', () => {
const element = createElement('c-formatted-modal', {
is: FormattedModal
});
document.body.appendChild(element);
element.visible = true;

return Promise.resolve()
.then( () => {
const modal = element.shadowRoot.querySelector( 'c-modal' );
expect( modal ).not.toBe( null );
});
});

it('Will create a contents slot in the c-modal', () => {
const element = createElement('c-formatted-modal', {
is: FormattedModal
});
document.body.appendChild(element);
element.visible = true;

return Promise.resolve() // we need this because we set the element to visible *after* adding it to the dom.
// this means we need to wait for a render cycle to complete.
.then( () => {
const modalContentsSlot = element.shadowRoot.querySelector( 'c-modal [slot="contents"]' );
expect( modalContentsSlot ).not.toBe( null );
});
});

it('Will populate the contents of the c-modal with slots that are exposed externally', () => {
const element = createElement('c-formatted-modal', {
is: FormattedModal
});
document.body.appendChild(element);
element.visible = true;

return Promise.resolve()
.then( () => {
const titleSlot = element.shadowRoot.querySelector( 'c-modal [slot="contents"] slot[name="title"]' )
const contentsSlot = element.shadowRoot.querySelector( 'c-modal [slot="contents"] slot[name="contents"]' )
const footerSlot = element.shadowRoot.querySelector( 'c-modal [slot="contents"] slot[name="footer"]' )
expect( titleSlot ).not.toBe( null );
expect( contentsSlot ).not.toBe( null );
expect( footerSlot ).not.toBe( null );
});
});

it('When the modal is set to be not visible, will not render the slots', () => {
const element = createElement('c-formatted-modal', {
is: FormattedModal
});
document.body.appendChild(element);
element.visible = false;

return Promise.resolve()
.then( () => {
const titleSlot = element.shadowRoot.querySelector( 'c-modal [slot="contents"] slot[name="title"]' )
const contentsSlot = element.shadowRoot.querySelector( 'c-modal [slot="contents"] slot[name="contents"]' )
const footerSlot = element.shadowRoot.querySelector( 'c-modal [slot="contents"] slot[name="footer"]' )
expect( titleSlot ).toBe( null );
expect( contentsSlot ).toBe( null );
expect( footerSlot ).toBe( null );
});
});

it('will default the modal to be non visible', () => {
const element = createElement('c-formatted-modal', {
is: FormattedModal
});
document.body.appendChild(element);

return Promise.resolve()
.then( () => {
const titleSlot = element.shadowRoot.querySelector( 'c-modal [slot="contents"] slot[name="title"]' )
const contentsSlot = element.shadowRoot.querySelector( 'c-modal [slot="contents"] slot[name="contents"]' )
const footerSlot = element.shadowRoot.querySelector( 'c-modal [slot="contents"] slot[name="footer"]' )
expect( titleSlot ).toBe( null );
expect( contentsSlot ).toBe( null );
expect( footerSlot ).toBe( null );
});
});

it('will forward the cancel event on the c-modal, if one it issued', () => {
const element = createElement('c-formatted-modal', {
is: FormattedModal
});
element.visible = true;
document.body.appendChild(element);

let cancelHandler = jest.fn();
element.addEventListener( 'cancel', cancelHandler );

return Promise.resolve()
.then( () => {
const modal = element.shadowRoot.querySelector( 'c-modal' );
modal.dispatchEvent( new CustomEvent( 'cancel' ) );
})
.then( () => {
expect( cancelHandler ).toHaveBeenCalled();
})
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<template>
<c-modal
visible={visible}
oncancel={handleCancel}
>
<div slot="contents">
<header class="slds-modal__header">
<h2 id="modal-heading-01" class="slds-text-heading_medium slds-hyphenate">
<slot name="title"></slot>
</h2>
</header>
<div class="slds-modal__content slds-p-around_medium" id="modal-content-id-1">
<slot name="contents"></slot>
</div>
<footer class="slds-modal__footer">
<slot name="footer"></slot>
</footer>
</div>
</c-modal>
</template>
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { LightningElement, api } from 'lwc';

export default class FormattedModal extends LightningElement {
@api visible;

handleCancel() {
this.dispatchCancel();
}

dispatchCancel() {
const event = new CustomEvent( 'cancel' );
this.dispatchEvent( event );
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>52.0</apiVersion>
<description>A modal that follows the standard Lightning Card rendering convention of providing title, content and footer slots.</description>
<masterLabel>Formatted Modal</masterLabel>
<isExposed>false</isExposed>
</LightningComponentBundle>
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
const DISPLAY_DENSITY = Object.freeze({
COMFY : 'comfy',
COMPACT : 'compact',
DEFAULT : 'comfy',
});

const getKeyForDisplayDensity = displayDensity => {
for ( let key in DISPLAY_DENSITY ) {
if ( DISPLAY_DENSITY[ key ] == displayDensity ) {
return key;
}
}
}

const LABEL_PROPERTIES = Object.freeze({
COMFY : {
VARIANT : 'standard',
CLASSES : 'slds-form-element slds-form-element_stacked'
},
COMPACT : {
VARIANT : 'label-inline',
CLASSES : 'slds-form-element slds-form-element_horizontal'
},
HIDDEN : {
VARIANT : 'label-hidden',
CLASSES : ''
},
});

const CONSTANTS = Object.freeze({
DISPLAY_DENSITY : DISPLAY_DENSITY,
LABEL_PROPERTIES : LABEL_PROPERTIES,
getLabelVariant : displayDensity => LABEL_PROPERTIES[ getKeyForDisplayDensity( displayDensity ) ].VARIANT,
getLabelClasses : displayDensity => LABEL_PROPERTIES[ getKeyForDisplayDensity( displayDensity ) ].CLASSES
});

export default CONSTANTS;
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>52.0</apiVersion>
<description>A service modal that provides constants relating to layouts (e.g. display density and classes for labels) as well as functions for returning the correct constants in certain circumstances.</description>
<masterLabel>Layout Constants</masterLabel>
<isExposed>false</isExposed>
</LightningComponentBundle>
Loading

0 comments on commit fad409d

Please sign in to comment.