Skip to content

Commit

Permalink
Merge pull request apex-enterprise-patterns#7 from OrtooApps/feature/…
Browse files Browse the repository at this point in the history
…filter-and-paging

Feature/filter and paging
  • Loading branch information
rob-baillie-ortoo committed Jan 14, 2022
2 parents 32dff13 + 5d07aed commit 6f868a8
Show file tree
Hide file tree
Showing 11 changed files with 1,189 additions and 25 deletions.
42 changes: 17 additions & 25 deletions TODO.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,43 +4,35 @@ 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
* Move all the Jest LWc tests over to create the component in the before

Look at the use of 'MockDatabase' in fflib
Look at:
SobjectUtils.getSobjectName

* Look at implementing ordering in the datatable example, so you can define the standards more clearly

* Build a filter + results page for Contacts, allowing search on
* Salutation
* Name
* Account Name
* Account Rating


* LWCs
* Standards for:
* Filter and Results Page
* Apex format
* Inner Class for the search parameters
* Offset
* Order By - single column
* Order By Direction

* Edit Forms / Validation
* Edit Forms with Child Records

* Review:
* Self Configured Combobox - not sure it's needed

* Try parent and datatable referencing a DML Service

* Standards
* Labels should be:
* Imported into a static named _LABEL
* Referenced in an object named 'labels'.
* E.g.
labels = {
confirm: CONFIRM_LABEL
}
* so in the html it's {labels.confirm}
* Do not add a target unless you KNOW it is appropriate.
* Once you add it, it cannot be removed
* Always qualify the record page target with the sobject types it's appropriate for

* use standard validation techniques
* Add the property data-validatable
* call reportValidity, binding it to the LWC

* To finalise the core architecture:
* Do we need to have a non all-or-nothing version of commitWork?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,11 +112,81 @@
<shortDescription>The word 'Edit', capitalised.</shortDescription>
<value>Edit</value>
</labels>
<labels>
<fullName>ortoo_core_loading</fullName>
<language>en_US</language>
<protected>false</protected>
<shortDescription>The word 'Loading', capitalised.</shortDescription>
<value>Loading</value>
</labels>
<labels>
<fullName>ortoo_core_error_title</fullName>
<language>en_US</language>
<protected>false</protected>
<shortDescription>The title that is used on generic errors in standard Ortoo LWCs.</shortDescription>
<value>Error</value>
</labels>
<labels>
<fullName>ortoo_core_search_button_label</fullName>
<language>en_US</language>
<protected>false</protected>
<shortDescription>The label that is used on search buttons.</shortDescription>
<value>Apply Filters</value>
</labels>
<labels>
<fullName>ortoo_core_search_button_title</fullName>
<language>en_US</language>
<protected>false</protected>
<shortDescription>The title that is used on search buttons.</shortDescription>
<value>Apply Filters</value>
</labels>
<labels>
<fullName>ortoo_core_first_page</fullName>
<language>en_US</language>
<protected>false</protected>
<shortDescription>The label to use on the 'First Page' button of the pagination controls.</shortDescription>
<value>First Page</value>
</labels>
<labels>
<fullName>ortoo_core_previous_page</fullName>
<language>en_US</language>
<protected>false</protected>
<shortDescription>The label to use on the 'Previous Page' button of the pagination controls.</shortDescription>
<value>Previous</value>
</labels>
<labels>
<fullName>ortoo_core_next_page</fullName>
<language>en_US</language>
<protected>false</protected>
<shortDescription>The label to use on the 'Next Page' button of the pagination controls.</shortDescription>
<value>Next</value>
</labels>
<labels>
<fullName>ortoo_core_last_page</fullName>
<language>en_US</language>
<protected>false</protected>
<shortDescription>The label to use on the 'Last Page' button of the pagination controls.</shortDescription>
<value>Last Page</value>
</labels>
<labels>
<fullName>ortoo_core_total_records</fullName>
<language>en_US</language>
<protected>false</protected>
<shortDescription>The label that is used with a 'Total Records' count.</shortDescription>
<value>Total Records</value>
</labels>
<labels>
<fullName>ortoo_core_page_size</fullName>
<language>en_US</language>
<protected>false</protected>
<shortDescription>The label that is used with a 'Page Size' count.</shortDescription>
<value>Page Size</value>
</labels>
<labels>
<fullName>ortoo_core_page_number_description</fullName>
<language>en_US</language>
<protected>false</protected>
<shortDescription>Describes the page no. of a total. {0} - Current Page, {1} - Total Pages.</shortDescription>
<value>Page {0} of {1}</value>
</labels>
</CustomLabels>
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
import { createElement } from 'lwc';
import FilterAndResults from 'c/filterAndResults';

jest.mock('@salesforce/label/c.ortoo_core_loading', () => { return { default: "Loading" } }, { virtual: true } );

const CARD_SELECTOR = '[data-ortoo-elem-id="filterandresults-card"]';
const SEARCH_BUTTON_SELECTOR = '[data-ortoo-elem-id="filterandresults-search"]';
const TOP_PAGE_NAVIGATION_SELECTOR = '[data-ortoo-elem-id="filterandresults-pageselectortop"]';
const BOTTOM_PAGE_NAVIGATION_SELECTOR = '[data-ortoo-elem-id="filterandresults-pageselectorbottom"]';

describe( 'c-filter-and-results', () => {

beforeEach( () => {
const element = createElement('c-filter-and-result', {
is: FilterAndResults
});

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);
}
});

it( 'when rendered, includes slots for the info, form, action buttons and the data', () => {

const element = document.body.querySelector( 'c-filter-and-result' );

return Promise.resolve()
.then( () => {
expect( element.shadowRoot.querySelector( 'slot[name="info"]' ) ).not.toBeNull();
expect( element.shadowRoot.querySelector( 'slot[name="form"]' ) ).not.toBeNull();
expect( element.shadowRoot.querySelector( 'slot[name="action-buttons"]' ) ).not.toBeNull();
expect( element.shadowRoot.querySelector( 'slot[name="data"]' ) ).not.toBeNull();
})
});

it( 'when loaded is set to true, will show a loading spinner', () => {

const element = document.body.querySelector( 'c-filter-and-result' );

element.loading = true;

return Promise.resolve()
.then( () => {
expect( element.shadowRoot.querySelector( 'lightning-spinner' ) ).not.toBeNull();
})
});

it( 'when loaded is set to false, will not show a loading spinner', () => {

const element = document.body.querySelector( 'c-filter-and-result' );

element.loading = false;

return Promise.resolve()
.then( () => {
expect( element.shadowRoot.querySelector( 'lightning-spinner' ) ).toBeNull();
})
});

it( 'when showPaginationControls flag is set to true will show pagination controls above and below the data', () => {

const element = document.body.querySelector( 'c-filter-and-result' );

element.showPaginationControls = true;

return Promise.resolve()
.then( () => {
expect( element.shadowRoot.querySelector( TOP_PAGE_NAVIGATION_SELECTOR ) ).not.toBeNull();
expect( element.shadowRoot.querySelector( BOTTOM_PAGE_NAVIGATION_SELECTOR ) ).not.toBeNull();
})
});

it( 'when showPaginationControls flag is set to true will configure the pagination controls with record information', () => {

const NUMBER_OF_RECORDS = 100;
const WINDOW_SIZE = 20;
const OFFSET = 40;

const element = document.body.querySelector( 'c-filter-and-result' );

element.showPaginationControls = true;
element.numberOfRecords = NUMBER_OF_RECORDS;
element.recordsWindowSize = WINDOW_SIZE;
element.offset = OFFSET;

return Promise.resolve()
.then( () => {
const topNavigator = element.shadowRoot.querySelector( TOP_PAGE_NAVIGATION_SELECTOR );
const bottomNavigator = element.shadowRoot.querySelector( BOTTOM_PAGE_NAVIGATION_SELECTOR );

expect( topNavigator.numberOfRecords ).toBe( NUMBER_OF_RECORDS );
expect( bottomNavigator.numberOfRecords ).toBe( NUMBER_OF_RECORDS );
expect( topNavigator.recordsPerPage ).toBe( WINDOW_SIZE );
expect( bottomNavigator.recordsPerPage ).toBe( WINDOW_SIZE );
expect( topNavigator.offset ).toBe( OFFSET );
expect( bottomNavigator.offset ).toBe( OFFSET );
})
});

it( 'when showPaginationControls flag is set to false will not show pagination controls', () => {

const element = document.body.querySelector( 'c-filter-and-result' );

element.showPaginationControls = false;

return Promise.resolve()
.then( () => {
expect( element.shadowRoot.querySelector( TOP_PAGE_NAVIGATION_SELECTOR ) ).toBeNull();
expect( element.shadowRoot.querySelector( BOTTOM_PAGE_NAVIGATION_SELECTOR ) ).toBeNull();
})
});

it( 'when the title is set, will set the card title to the passed value', () => {

const element = document.body.querySelector( 'c-filter-and-result' );

element.title = 'The title';

return Promise.resolve()
.then( () => {
expect( element.shadowRoot.querySelector( CARD_SELECTOR ).title ).toBe( 'The title' );
})
});

it( 'when the the search button is clicked, will issue a search event', () => {

const element = document.body.querySelector( 'c-filter-and-result' );

const eventHandler = jest.fn();
element.addEventListener( 'search', eventHandler );

return Promise.resolve()
.then( () => {
element.shadowRoot.querySelector( SEARCH_BUTTON_SELECTOR ).click();
})
.then( () => {
expect( eventHandler ).toHaveBeenCalledTimes( 1 );
})
});

it( 'when the top pagination control issues a navigation event, will forward the detail as a new navigation event', () => {

const element = document.body.querySelector( 'c-filter-and-result' );
element.showPaginationControls = true;

const eventHandler = jest.fn();
element.addEventListener( 'navigate', eventHandler );

const eventPayload = {
detail: 'the original detail'
}

return Promise.resolve()
.then( () => {
const navigateEvent = new CustomEvent( 'navigate', eventPayload );
element.shadowRoot.querySelector( TOP_PAGE_NAVIGATION_SELECTOR ).dispatchEvent( navigateEvent );
})
.then( () => {
expect( eventHandler ).toHaveBeenCalledTimes( 1 );
const receivedEventPayload = eventHandler.mock.calls[0][0];

expect( receivedEventPayload.detail ).toEqual( eventPayload.detail );
expect( receivedEventPayload ).not.toBe( eventPayload );
})
});
it( 'when the bottom pagination control issues a navigation event, will forward the detail as a new navigation event', () => {

const element = document.body.querySelector( 'c-filter-and-result' );
element.showPaginationControls = true;

const eventHandler = jest.fn();
element.addEventListener( 'navigate', eventHandler );

const eventPayload = {
detail: 'the original detail'
}

return Promise.resolve()
.then( () => {
const navigateEvent = new CustomEvent( 'navigate', eventPayload );
element.shadowRoot.querySelector( BOTTOM_PAGE_NAVIGATION_SELECTOR ).dispatchEvent( navigateEvent );
})
.then( () => {
expect( eventHandler ).toHaveBeenCalledTimes( 1 );
const receivedEventPayload = eventHandler.mock.calls[0][0];

expect( receivedEventPayload.detail ).toEqual( eventPayload.detail );
expect( receivedEventPayload ).not.toBe( eventPayload );
})
});
});
Loading

0 comments on commit 6f868a8

Please sign in to comment.