chromium/chrome/test/data/webui/chromeos/settings/controls/v2/settings_toggle_v2_test.ts

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

import 'chrome://os-settings/os_settings.js';

import {CrToggleElement, SettingsToggleV2Element} from 'chrome://os-settings/os_settings.js';
import {strictQuery} from 'chrome://resources/ash/common/typescript_utils/strict_query.js';
import {assertEquals, assertFalse, assertNotEquals, assertNotReached, assertThrows, assertTrue} from 'chrome://webui-test/chai_assert.js';
import {flushTasks} from 'chrome://webui-test/polymer_test_util.js';
import {eventToPromise, isVisible} from 'chrome://webui-test/test_util.js';

import {clearBody} from '../../utils.js';

/** @fileoverview Suite of tests for settings-toggle-v2. */
suite(SettingsToggleV2Element.is, () => {
  let toggleElement: SettingsToggleV2Element;
  let internalToggleElement: CrToggleElement;
  let fakeTogglePref: chrome.settingsPrivate.PrefObject;

  async function init() {
    clearBody();
    toggleElement = document.createElement('settings-toggle-v2');
    document.body.appendChild(toggleElement);

    await flushTasks();

    internalToggleElement =
        strictQuery('cr-toggle', toggleElement.shadowRoot, CrToggleElement);
    assertTrue(isVisible(internalToggleElement));
  }

  async function initWithPref(
      prefValue: boolean = false, isInverted: boolean = false) {
    await init();

    toggleElement.inverted = isInverted;

    /**
     * Pref value used in tests, should reflect the 'checked' attribute.
     * Create a new pref for each test() to prevent order (state)
     * dependencies between tests.
     */
    fakeTogglePref = {
      key: 'test',
      type: chrome.settingsPrivate.PrefType.BOOLEAN,
      value: prefValue,
    };

    toggleElement.pref = {...fakeTogglePref};
    await flushTasks();
  }

  suite('when disabled', () => {
    setup(async () => {
      await init();
      toggleElement.disabled = true;
    });

    test('disabled property is reflected to attribute', () => {
      assertTrue(toggleElement.hasAttribute('disabled'));

      toggleElement.disabled = false;
      assertFalse(toggleElement.hasAttribute('disabled'));
    });

    test('internal cr-toggle is disabled', () => {
      assertTrue(internalToggleElement.disabled);
    });

    test('clicking does not change the toggle state', () => {
      assertFalse(toggleElement.checked);
      assertFalse(internalToggleElement.checked);

      internalToggleElement.click();
      assertFalse(toggleElement.checked);
      assertFalse(internalToggleElement.checked);
    });
  });

  test(
      'triggers a change event when the value of toggle changes.', async () => {
        await init();
        const checkedChangeEventPromise = eventToPromise('change', window);

        internalToggleElement.click();

        const event = await checkedChangeEventPromise;
        assertEquals(toggleElement.checked, event.detail);
        // Event should not pass the shadow DOM boundary.
        assertFalse(event.composed);
      });

  test('the internal cr-toggle control value changes on click', async () => {
    await init();

    assertFalse(toggleElement.checked);

    internalToggleElement.click();
    assertTrue(toggleElement.checked);
    assertTrue(internalToggleElement.checked);

    internalToggleElement.click();
    assertFalse(toggleElement.checked);
    assertFalse(internalToggleElement.checked);
  });

  test('should focus the internal toggle', async () => {
    await init();

    assertNotEquals(
        internalToggleElement, toggleElement.shadowRoot!.activeElement);
    toggleElement.focus();
    assertEquals(
        internalToggleElement, toggleElement.shadowRoot!.activeElement);
  });

  suite('with pref object', () => {
    setup(async () => {
      await initWithPref();
    });

    async function setEnforcedPref(): Promise<void> {
      toggleElement.pref = {
        ...fakeTogglePref,
        enforcement: chrome.settingsPrivate.Enforcement.ENFORCED,
        controlledBy: chrome.settingsPrivate.ControlledBy.DEVICE_POLICY,
      };

      await flushTasks();
    }

    suite('Pref type validation', () => {
      [{
        prefType: chrome.settingsPrivate.PrefType.STRING,
        testValue: 'foo',
        isValid: false,
      },
       {
         prefType: chrome.settingsPrivate.PrefType.NUMBER,
         testValue: 1,
         isValid: false,
       },
       {
         prefType: chrome.settingsPrivate.PrefType.DICTIONARY,
         testValue: {},
         isValid: false,
       },
       {
         prefType: chrome.settingsPrivate.PrefType.BOOLEAN,
         testValue: true,
         isValid: true,
       },
       {
         prefType: chrome.settingsPrivate.PrefType.LIST,
         testValue: [],
         isValid: false,
       },
       {
         prefType: chrome.settingsPrivate.PrefType.URL,
         testValue: 'bar',
         isValid: false,
       },
      ].forEach(({prefType, testValue, isValid}) => {
        test(
            `${prefType} pref type is ${isValid ? 'valid' : 'invalid'}`, () => {
              function validatePref() {
                toggleElement.pref = {
                  key: 'settings.sample',
                  type: prefType,
                  value: testValue,
                };
                toggleElement.validatePref();
              }

              if (isValid) {
                validatePref();
              } else {
                assertThrows(validatePref);
              }
            });
      });
    });

    test('control value reflects the pref value', async () => {
      await initWithPref(true);

      assertTrue(toggleElement.pref!.value);
      assertTrue(toggleElement.checked);

      assertTrue(internalToggleElement.checked);
    });

    test('checked property reflects the pref value when pref changes', () => {
      assertFalse(toggleElement.pref!.value);
      assertFalse(toggleElement.checked);

      toggleElement.set('pref.value', true);
      assertTrue(toggleElement.pref!.value);
      assertTrue(toggleElement.checked);
    });

    test('checked property changes on click', () => {
      assertFalse(toggleElement.checked);

      internalToggleElement.click();
      assertTrue(toggleElement.checked);

      internalToggleElement.click();
      assertFalse(toggleElement.checked);
    });

    test(
        'resetToPrefValue changes the checked property to the pref value',
        () => {
          assertFalse(toggleElement.checked);
          assertFalse(toggleElement.pref!.value);

          toggleElement.checked = true;
          assertTrue(toggleElement.checked);
          assertFalse(toggleElement.pref!.value);

          toggleElement.resetToPrefValue();
          assertFalse(toggleElement.checked);
          assertFalse(toggleElement.pref!.value);
        });

    test('toggling dispatches pref change event', async () => {
      const prefChangeEventPromise =
          eventToPromise('user-action-setting-pref-change', window);

      internalToggleElement.click();
      assertTrue(toggleElement.checked);

      const event = await prefChangeEventPromise;
      assertEquals(fakeTogglePref.key, event.detail.prefKey);
      assertEquals(toggleElement.checked, event.detail.value);
    });

    test('Toggling does not update the pref value directly', async () => {
      const initialPrefValue = toggleElement.pref!.value;

      const prefChangeEventPromise =
          eventToPromise('user-action-setting-pref-change', window);
      internalToggleElement.click();
      assertTrue(toggleElement.checked);
      await prefChangeEventPromise;

      // Local pref object should be treated as immutable data and should not be
      // updated directly.
      assertEquals(initialPrefValue, toggleElement.pref!.value);
    });

    test(
        'Pref value updates the checked value when the toggle is disabled',
        () => {
          toggleElement.disabled = true;
          toggleElement.set('pref.value', true);
          assertTrue(toggleElement.pref!.value);
          assertTrue(toggleElement.checked);
        });

    test('toggle is disabled when policy is enforced by pref', async () => {
      await setEnforcedPref();

      assertTrue(toggleElement.isPrefEnforced);
      assertTrue(toggleElement.disabled);
    });

    suite('policy indicator', () => {
      test('is not visible if there is no enforced pref', () => {
        const policyIndicator =
            toggleElement.shadowRoot!.querySelector('cr-policy-pref-indicator');
        assertFalse(isVisible(policyIndicator));
      });

      test('is visible when policy is enforced by pref', async () => {
        await setEnforcedPref();

        const policyIndicator =
            toggleElement.shadowRoot!.querySelector('cr-policy-pref-indicator');
        assertTrue(!!policyIndicator);
        assertTrue(isVisible(policyIndicator));

        // policy indicator is still visible even when the toggle is manually
        // changed to enabled.
        toggleElement.disabled = false;
        assertTrue(!!policyIndicator);
      });
    });

    suite('with noSetPref', () => {
      setup(() => {
        toggleElement.noSetPref = true;
      });

      test('toggling does not dispatch a pref change event', async () => {
        const eventPromise =
            eventToPromise('user-action-setting-pref-change', window)
                .then(() => {
                  assertNotReached();
                });

        const successPromise = new Promise((resolve) => {
          setTimeout(() => resolve('success'), 500);
        });

        assertFalse(toggleElement.checked);
        assertFalse(toggleElement.pref!.value);
        internalToggleElement.click();
        assertTrue(toggleElement.checked);

        // eventPromise should never resolve, else it will fail this test.
        const value = await Promise.race([eventPromise, successPromise]);
        assertEquals('success', value);
        assertFalse(toggleElement.pref!.value);
      });

      test(
          'commitPrefChange() updates the pref value to the checked value',
          async () => {
            assertFalse(toggleElement.checked);
            assertFalse(toggleElement.pref!.value);

            internalToggleElement.click();
            assertTrue(toggleElement.checked);
            assertFalse(toggleElement.pref!.value);

            const prefChangeEventPromise =
                eventToPromise('user-action-setting-pref-change', window);
            toggleElement.commitPrefChange();
            assertTrue(toggleElement.checked);

            const event = await prefChangeEventPromise;
            assertEquals(fakeTogglePref.key, event.detail.prefKey);
            assertEquals(toggleElement.checked, event.detail.value);
          });

      test(
          'resetToPrefValue() changes the checked property to the pref value',
          () => {
            assertFalse(toggleElement.checked);
            assertFalse(toggleElement.pref!.value);

            toggleElement.checked = true;
            assertTrue(toggleElement.checked);
            assertFalse(toggleElement.pref!.value);

            assertTrue(internalToggleElement.checked);

            toggleElement.resetToPrefValue();
            assertFalse(toggleElement.checked);
            assertFalse(toggleElement.pref!.value);
          });
    });

    suite('with inverted enabled', () => {
      setup(async () => {
        await initWithPref(/*prefValue=*/ false, /*isInverted=*/ true);
      });

      test('toggle value is the opposite of the pref', () => {
        assertFalse(toggleElement.pref!.value);
        assertTrue(toggleElement.checked);
      });

      test(
          'clicking on the toggle changes the pref value to the opposite of the toggle',
          async () => {
            const prefChangeEventPromise =
                eventToPromise('user-action-setting-pref-change', window);

            internalToggleElement.click();
            await flushTasks();
            assertFalse(toggleElement.checked);

            const event = await prefChangeEventPromise;
            assertEquals(fakeTogglePref.key, event.detail.prefKey);
            assertEquals(!toggleElement.checked, event.detail.value);
          });

      test(
          'checked value reflects the opposite of pref value when pref changes',
          () => {
            assertFalse(toggleElement.pref!.value);
            assertTrue(toggleElement.checked);

            toggleElement.set('pref.value', true);
            assertTrue(toggleElement.pref!.value);
            assertFalse(toggleElement.checked);
          });
    });
  });

  suite('without pref object', () => {
    setup(async () => {
      await init();

      // there is no pref set.
      assertEquals(toggleElement.pref, undefined);
    });

    test('value changes on click', () => {
      assertFalse(toggleElement.checked);

      internalToggleElement.click();
      assertTrue(toggleElement.checked);

      internalToggleElement.click();
      assertFalse(toggleElement.checked);
    });

    test('control value changes on toggle element change', () => {
      assertFalse(toggleElement.checked);

      toggleElement.checked = true;
      assertTrue(toggleElement.checked);
      assertTrue(internalToggleElement.checked);

      toggleElement.checked = false;
      assertFalse(toggleElement.checked);
      assertFalse(internalToggleElement.checked);
    });

    test('commitPrefChange has no effect when there is no pref', () => {
      assertFalse(toggleElement.checked);

      assertThrows(() => {
        toggleElement.commitPrefChange();
      }, 'updatePrefValueFromUserAction() requires pref to be defined.');

      assertFalse(toggleElement.checked);
    });

    test('resetToPrefValue has no effect when there is no pref', () => {
      assertFalse(toggleElement.checked);

      assertThrows(() => {
        toggleElement.resetToPrefValue();
      }, 'resetToPrefValue() requires pref to be defined.');

      assertFalse(toggleElement.checked);
    });
  });
});