Suivez votre progression
Accueil Trailhead
Accueil Trailhead

Dig Into the E-Bikes Sample App Components

In this step, you take a look at some highlights from the sample app code.

Product Explorer Code Highlights

The Product Explorer page contains the most complex set of components in the E-Bikes sample app. There are three Lightning web components that are exposed to the Lightning App Builder: productFilter, productTileList, and productCard.

Product explorer screenshot annotated with component and event names

The components use the Lightning Message Service (LMS) to communicate with each other. Each component sends or receives messages that are triggered when the user interacts with the components.

  • When product filters change, productFilter publishes a ProductsFiltered message that updates the product list accordingly.
  • When a product from the list is selected, productTileList publishes a ProductSelected message that updates the product card with the product detail information.

The Lightning Message Service (LMS) allows sibling components to communicate with each other using messages. It also allows to communicate across Visualforce, Aura, and Lightning web components on a page (not demonstrated in E-Bikes).

Product Filter Component

The productFilter component imports getPicklistValues from the UI API to retrieve picklist values for the category, material, and level product fields.

@wire(getPicklistValues, {
  recordTypeId: '012000000000000AAA',
  fieldApiName: CATEGORY_FIELD
})
categories;

This approach is particularly interesting because this component doesn’t rely on Apex to retrieve record data. The UI stays up to date with no code change even if the picklist values change later on (for example, if E-Bikes releases a new product category).

Whenever the value of product filters changes, productFilter publishes a ProductsFiltered message with the current filter values.

publish(this.messageContext, PRODUCTS_FILTERED_MESSAGE, {
  filters: this.filters
});

This message is handled by the productTileList component.

Product Tile List Component

The productTileList component subscribes to the ProductsFiltered message as soon as it is added to the DOM. It uses a handleFilterChange function to keep track of the product filters.

connectedCallback() {
  this.productFilterSubscription = subscribe(
    this.messageContext,
    PRODUCTS_FILTERED_MESSAGE,
    (message) => this.handleFilterChange(message)
  );
}

productTileList retrieves a list of products using the applied filters and a page number in a getProducts wire adapter.

import getProducts from '@salesforce/apex/ProductController.getProducts';
export default class ProductTileList extends LightningElement {
  @wire(getProducts, {
    filters: '$filters',
    pageNumber: '$pageNumber'
  })
  products;
}

The wire adapter is imported from the getProducts method of the ProductController Apex class.

public with sharing class ProductController {
  static Integer PAGE_SIZE = 9;
  @AuraEnabled(Cacheable=true)
  public static PagedResult getProducts(
    Filters filters,
    Integer pageNumber) {
    // Builds and run query then,
    // returns product records in a PagedResult object
  }
}

This method runs a SOQL query that returns a filtered list of product records wrapped into a PagedResult object. The results are paginated in batches of nine products as specified in the PAGE_SIZE constant that’s used in the query (not shown in the code snippet).

Paginating is useful to limit the number of items returned. This best practice ensures that performance remains stable as the E-Bikes product catalog grows.

As an exercise, change the value of the default page size:

  1. Edit the force-app/main/default/classes/ProductController.cls file.
  2. Change the value of PAGE_SIZE to 6.
  3. Save the file.
  4. Deploy your changes to your org with this command:
sfdx force:source:deploy -m ApexClass:ProductController
  1. Refresh the Product Explorer page and notice the updated product list page size.

Product Tile List Tests

Let’s look at component tests before leaving you to further exploration. We’ll look at Jest tests for the productTileList component. You can find them in the productTileList.test.js file.

Because tests run in isolation from the platform, you need to mock all platform dependencies. This includes the Lightning Message Service and both the MessageContext and getProducts wire adapters.

A LMS mock is provided out of the box by the Lightning Web Components testing library (sfdx-lwc-jest) but we've defined a custom mock implementation to gain additional testing capabilities. The implementation of this custom LMS mock is located here.

We specify a Jest module mapper configuration in the jest.config.js file to dynamically replace certain modules imports by our mocks.

const { jestConfig } = require('@salesforce/sfdx-lwc-jest/config');
module.exports = {
  ...jestConfig,
  moduleNameMapper: {
    '^@salesforce/apex$': '/force-app/test/jest-mocks/apex',
    '^lightning/navigation$': '/force-app/test/jest-mocks/lightning/navigation',
    '^lightning/messageService$': '/force-app/test/jest-mocks/lightning/messageService'
  }
};

In our productTileList test, we also mock the MessageContext and getProducts wire adapters with the following code:

// Realistic data with multiple records
const mockGetProducts = require('./data/getProducts.json');
// An empty list of records to verify the component does something reasonable
// when there is no data to display
const mockGetProductsNoRecords = require('./data/getProductsNoRecords.json');
// Register the Apex wire adapter. Some tests verify that provisioned values trigger desired behavior.
const getProductsAdapter = registerApexTestWireAdapter(getProducts);
// Register as a standard wire adapter because the component under test requires this adapter.
// We don't exercise this wire adapter in the tests.
registerTestWireAdapter(MessageContext);

Note how this uses simple JSON files from the data folder to replace lists of products normally returned by the server. We save those lists in two constants: mockGetProducts and mockGetProductsNoRecords.

Let’s now look at a simple test called “displays one c-product-tile per record”. This test checks that the number of product tiles matches the number of records returned from the mock adapter.

it('displays one c-product-tile per record', () => {
  const recordCount = mockGetProducts.records.length;
  const element = createElement('c-product-tile-list', {
      is: ProductTileList
  });
  document.body.appendChild(element);
  getProductsAdapter.emit(mockGetProducts);
  return Promise.resolve().then(() => {
      const productTiles = element.shadowRoot.querySelectorAll(
          'c-product-tile'
      );
      expect(productTiles).toHaveLength(recordCount);
  });
});

It starts by creating the product list component (c-product-tile) and attaching it to the DOM. It then sends mock data by passing the mockGetProducts constant to the getProductsAdapter test adapter. Finally, it counts the number of c-product-tile DOM elements and makes sure that it matches the number of mock records from mockGetProducts.

What’s Next?

While this is the end of your guided tour of the E-Bikes sample app, there’s more to explore. Feel free to take a look at the other components and their tests, and don’t forget to check out the Community.

Check out the other sample apps on https://trailhead.salesforce.com/sample-gallery. Explore their source code and learn from them with the other dedicated Quick Start projects.