chromium/chrome/test/data/webui/cr_elements/store_client_test.ts

// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import {makeStoreClientMixin} from 'chrome://resources/cr_elements/store_client/store_client.js';
import type {Action} from 'chrome://resources/js/store.js';
import {Store} from 'chrome://resources/js/store.js';
import {html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
import {assertEquals, assertFalse, assertTrue} from 'chrome://webui-test/chai_assert.js';

interface TestState {
  value: number;
}

interface TestActions extends Action {
  name: 'increment'|'decrement';
}

function reducer({value}: TestState, {name}: TestActions): TestState {
  switch (name) {
    case 'increment':
      return {value: value + 1};
    case 'decrement':
      return {value: value - 1};
  }
}

let store: Store<TestState, TestActions>|null = null;

const TestStoreClientMixin = makeStoreClientMixin(() => {
  return store || (store = new Store({value: 0}, reducer));
});

interface StoreClientTestElement {
  $: {
    value: HTMLElement,
    neverTwo: HTMLElement,
  };
}

class StoreClientTestElement extends TestStoreClientMixin
(PolymerElement) {
  static get is() {
    return 'store-client-test-element';
  }

  static get properties() {
    return {
      value_: Number,
      neverTwo_: Number,
    };
  }

  static get template() {
    return html`<div>
      <span id="value">[[value_]]</span>
      <span id="neverTwo">[[neverTwo_]]</span>
    </div>`;
  }

  override connectedCallback() {
    super.connectedCallback();
    this.watch<number>('value_', state => state.value);
    this.watch<number>(
        'neverTwo_', state => state.value === 2 ? undefined : state.value);
    this.updateFromStore();
  }
}

customElements.define(StoreClientTestElement.is, StoreClientTestElement);

declare global {
  interface HTMLElementTagNameMap {
    'store-client-test-element': StoreClientTestElement;
  }
}

suite('StoreClient', function() {
  let storeClientTestElement: StoreClientTestElement;

  setup(function() {
    document.body.innerHTML = window.trustedTypes!.emptyHTML;
    storeClientTestElement =
        document.createElement('store-client-test-element');
    storeClientTestElement.getStore().init({value: 0});
    document.body.appendChild(storeClientTestElement);
  });

  teardown(function() {
    store = null;
  });

  test('displays updated values from store', function() {
    assertEquals(
        '0', storeClientTestElement.$.value.textContent,
        'initial value is displayed');

    storeClientTestElement.dispatch({name: 'increment'});

    assertEquals(
        '1', storeClientTestElement.$.value.textContent,
        'new value is displayed');

    storeClientTestElement.dispatch({name: 'decrement'});
    storeClientTestElement.dispatch({name: 'decrement'});

    assertEquals(
        '-1', storeClientTestElement.$.value.textContent,
        'new value is displayed again');
  });

  test('does not update property if getter returns undefined', function() {
    storeClientTestElement.dispatch({name: 'increment'});
    storeClientTestElement.dispatch({name: 'increment'});
    assertEquals(
        '2', storeClientTestElement.$.value.textContent, '2 is displayed');
    assertEquals(
        '1', storeClientTestElement.$.neverTwo.textContent,
        'old value is displayed because local property was not updated to 2');
  });

  test('observes store while active', function() {
    const store = storeClientTestElement.getStore();
    assertTrue(
        store.hasObserver(storeClientTestElement),
        'element is observing store');
    storeClientTestElement.remove();
    assertFalse(
        store.hasObserver(storeClientTestElement),
        'element is not observing store after remove');
  });
});