chromium/chrome/test/data/webui/side_panel/read_anything/app_style_updater_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 {AppStyleUpdater, BrowserProxy} from 'chrome-untrusted://read-anything-side-panel.top-chrome/read_anything.js';
import type {AppElement} from 'chrome-untrusted://read-anything-side-panel.top-chrome/read_anything.js';
import {assertEquals, assertNotEquals, assertStringContains} from 'chrome-untrusted://webui-test/chai_assert.js';

import {suppressInnocuousErrors} from './common.js';
import {FakeReadingMode} from './fake_reading_mode.js';
import {TestColorUpdaterBrowserProxy} from './test_color_updater_browser_proxy.js';

suite('AppStyleUpdater', () => {
  let app: AppElement;
  let updater: AppStyleUpdater;

  function computeStyle(style: string) {
    return window.getComputedStyle(app.$.container).getPropertyValue(style);
  }

  function setAppFontSize(size: number) {
    app.style.fontSize = size + 'px';
  }

  function updateStyles(styles: {[attribute: string]: string}) {
    for (const [key, val] of Object.entries(styles)) {
      app.style.setProperty(key, val);
    }
  }

  setup(() => {
    suppressInnocuousErrors();
    document.body.innerHTML = window.trustedTypes!.emptyHTML;
    BrowserProxy.setInstance(new TestColorUpdaterBrowserProxy());
    const readingMode = new FakeReadingMode();
    chrome.readingMode = readingMode as unknown as typeof chrome.readingMode;

    app = document.createElement('read-anything-app');
    document.body.appendChild(app);
    updater = new AppStyleUpdater(app);
  });

  test('line spacing depends on font size', () => {
    setAppFontSize(10);
    chrome.readingMode.lineSpacing = 10;
    updater.setLineSpacing();
    assertEquals('100px', computeStyle('line-height'));

    setAppFontSize(12);
    chrome.readingMode.lineSpacing = 16;
    updater.setLineSpacing();
    assertEquals('192px', computeStyle('line-height'));
  });

  test('letter spacing depends on font size', () => {
    setAppFontSize(10);
    chrome.readingMode.letterSpacing = 10;
    updater.setLetterSpacing();
    assertEquals('100px', computeStyle('letter-spacing'));

    setAppFontSize(12);
    chrome.readingMode.letterSpacing = 16;
    updater.setLetterSpacing();
    assertEquals('192px', computeStyle('letter-spacing'));
  });

  test('font size scales', () => {
    setAppFontSize(10);
    chrome.readingMode.fontSize = 1;
    updater.setFontSize();
    assertEquals('10px', computeStyle('font-size'));

    chrome.readingMode.fontSize = 2.5;
    updater.setFontSize();
    assertEquals('25px', computeStyle('font-size'));

    chrome.readingMode.fontSize = 0.5;
    updater.setFontSize();
    assertEquals('5px', computeStyle('font-size'));
  });

  test('font name', () => {
    chrome.readingMode.fontName = 'Poppins';
    updater.setFont();
    assertStringContains(
        computeStyle('font-family'), chrome.readingMode.fontName);

    chrome.readingMode.fontName = 'Lexend Deca';
    updater.setFont();
    assertStringContains(
        computeStyle('font-family'), chrome.readingMode.fontName);
  });

  test('current highlight', () => {
    const expectedYellowColor = 'yellow';
    const expectedDarkColor = 'black';
    updateStyles({
      '--color-read-anything-current-read-aloud-highlight-yellow':
          expectedYellowColor,
      '--color-read-anything-current-read-aloud-highlight-dark':
          expectedDarkColor,
    });
    chrome.readingMode.turnedHighlightOn();
    chrome.readingMode.colorTheme = chrome.readingMode.yellowTheme;
    updater.setHighlight();
    assertEquals(
        expectedYellowColor, computeStyle('--current-highlight-bg-color'));

    chrome.readingMode.colorTheme = chrome.readingMode.darkTheme;
    updater.setHighlight();
    assertEquals(
        expectedDarkColor, computeStyle('--current-highlight-bg-color'));

    chrome.readingMode.turnedHighlightOff();
    chrome.readingMode.colorTheme = chrome.readingMode.lightTheme;
    updater.setHighlight();
    assertEquals('transparent', computeStyle('--current-highlight-bg-color'));
  });

  test('overflow toolbar changes style based on input', () => {
    updater.overflowToolbar(true);
    const scrollOverflow = computeStyle('--app-overflow-x');
    const scrollMinWidth = computeStyle('--container-min-width');

    updater.overflowToolbar(false);
    const noScrollOverflow = computeStyle('--app-overflow-x');
    const noScrollMinWidth = computeStyle('--container-min-width');

    assertNotEquals(scrollOverflow, noScrollOverflow);
    assertNotEquals(scrollMinWidth, noScrollMinWidth);
  });

  test('overflow toolbar without scrolling is same as resetting', () => {
    updater.overflowToolbar(false);
    const noScrollOverflow = computeStyle('--app-overflow-x');
    const noScrollMinWidth = computeStyle('--container-min-width');

    updater.resetToolbar();
    const resetOverflow = computeStyle('--app-overflow-x');
    const resetMinWidth = computeStyle('--container-min-width');

    assertEquals(resetOverflow, noScrollOverflow);
    assertEquals(resetMinWidth, noScrollMinWidth);
  });

  test('color theme', () => {
    // Make each expected color distinct so we can verify each color is changed
    // with each update.
    const expectedDefaultBackground = 'rgb(0, 0, 255)';
    const expectedYellowBackground = 'rgb(0, 255, 0)';
    const expectedDarkBackground = 'rgb(0, 255, 255)';
    const expectedDefaultForeground = 'rgb(255, 0, 0)';
    const expectedYellowForeground = 'rgb(255, 0, 255)';
    const expectedDarkForeground = 'rgb(255, 255, 0)';
    const expectedDefaultSelectionBackground = 'rgb(255, 255, 255)';
    const expectedYellowCurrentHighlight = 'rgb(0, 0, 0)';
    const expectedDarkCurrentHighlight = 'rgb(5, 5, 100)';
    const expectedDefaultPreviousHighlight = 'rgb(5, 100, 5)';
    const expectedYellowPreviousHighlight = 'rgb(5, 100, 100)';
    const expectedDarkPreviousHighlight = 'rgb(100, 5, 5)';
    const expectedDefaultEmptyHeading = 'rgb(100, 5, 100)';
    const expectedDefaultEmptyBody = 'rgb(100, 100, 100)';
    const expectedYellowEmptyBody = 'rgb(6, 6, 37)';
    const expectedDarkEmptyBody = 'rgb(6, 37, 6)';
    const expectedDefaultLink = 'rgb(6, 37, 37)';
    const expectedYellowLink = 'rgb(37, 6, 6)';
    const expectedDarkLink = 'rgb(37, 6, 37)';
    const expectedDefaultLinkVisited = 'rgb(37, 37, 6)';
    const expectedYellowLinkVisited = 'rgb(37, 37, 37)';
    const expectedDarkLinkVisited = 'rgb(14, 14, 28)';
    updateStyles({
      '--color-sys-base-container-elevated': expectedDefaultBackground,
      '--color-read-anything-background-yellow': expectedYellowBackground,
      '--color-read-anything-background-dark': expectedDarkBackground,
      '--color-sys-on-surface': expectedDefaultForeground,
      '--color-read-anything-foreground': expectedDefaultEmptyHeading,
      '--color-read-anything-foreground-yellow': expectedYellowForeground,
      '--color-read-anything-foreground-dark': expectedDarkForeground,
      '--color-text-selection-background': expectedDefaultSelectionBackground,
      '--color-read-anything-current-read-aloud-highlight-yellow':
          expectedYellowCurrentHighlight,
      '--color-read-anything-current-read-aloud-highlight-dark':
          expectedDarkCurrentHighlight,
      '--color-sys-on-surface-subtle': expectedDefaultPreviousHighlight,
      '--color-read-anything-previous-read-aloud-highlight-yellow':
          expectedYellowPreviousHighlight,
      '--color-read-anything-previous-read-aloud-highlight-dark':
          expectedDarkPreviousHighlight,
      '--color-side-panel-card-secondary-foreground': expectedDefaultEmptyBody,
      '--google-grey-700': expectedYellowEmptyBody,
      '--google-grey-500': expectedDarkEmptyBody,
      '--color-read-anything-link-default': expectedDefaultLink,
      '--color-read-anything-link-default-yellow': expectedYellowLink,
      '--color-read-anything-link-default-dark': expectedDarkLink,
      '--color-read-anything-link-visited': expectedDefaultLinkVisited,
      '--color-read-anything-link-visited-yellow': expectedYellowLinkVisited,
      '--color-read-anything-link-visited-dark': expectedDarkLinkVisited,
    });
    chrome.readingMode.turnedHighlightOn();

    // Verify default theme colors.
    chrome.readingMode.colorTheme = chrome.readingMode.defaultTheme;
    updater.setTheme();
    assertStringContains(computeStyle('background'), expectedDefaultBackground);
    assertStringContains(computeStyle('color'), expectedDefaultForeground);
    assertEquals(
        expectedDefaultSelectionBackground,
        computeStyle('--current-highlight-bg-color'));
    assertEquals(
        expectedDefaultPreviousHighlight,
        computeStyle('--previous-highlight-color'));
    assertEquals(
        expectedDefaultEmptyHeading,
        computeStyle('--sp-empty-state-heading-color'));
    assertEquals(
        expectedDefaultEmptyBody, computeStyle('--sp-empty-state-body-color'));
    assertEquals(expectedDefaultLink, computeStyle('--link-color'));
    assertEquals(
        expectedDefaultLinkVisited, computeStyle('--visited-link-color'));

    // Verify yellow theme colors.
    chrome.readingMode.colorTheme = chrome.readingMode.yellowTheme;
    updater.setTheme();
    assertStringContains(computeStyle('background'), expectedYellowBackground);
    assertStringContains(computeStyle('color'), expectedYellowForeground);
    assertEquals(
        expectedYellowCurrentHighlight,
        computeStyle('--current-highlight-bg-color'));
    assertEquals(
        expectedYellowPreviousHighlight,
        computeStyle('--previous-highlight-color'));
    assertEquals(
        expectedYellowForeground,
        computeStyle('--sp-empty-state-heading-color'));
    assertEquals(
        expectedYellowEmptyBody, computeStyle('--sp-empty-state-body-color'));
    assertEquals(expectedYellowLink, computeStyle('--link-color'));
    assertEquals(
        expectedYellowLinkVisited, computeStyle('--visited-link-color'));

    // Verify dark theme colors.
    chrome.readingMode.colorTheme = chrome.readingMode.darkTheme;
    updater.setTheme();
    assertStringContains(computeStyle('background'), expectedDarkBackground);
    assertStringContains(computeStyle('color'), expectedDarkForeground);
    assertEquals(
        expectedDarkCurrentHighlight,
        computeStyle('--current-highlight-bg-color'));
    assertEquals(
        expectedDarkPreviousHighlight,
        computeStyle('--previous-highlight-color'));
    assertEquals(
        expectedDarkForeground, computeStyle('--sp-empty-state-heading-color'));
    assertEquals(
        expectedDarkEmptyBody, computeStyle('--sp-empty-state-body-color'));
    assertEquals(expectedDarkLink, computeStyle('--link-color'));
    assertEquals(expectedDarkLinkVisited, computeStyle('--visited-link-color'));
  });

  test('setAllTextStyles updates all text styles', () => {
    setAppFontSize(10);
    chrome.readingMode.fontSize = 2;
    chrome.readingMode.lineSpacing = 4;
    chrome.readingMode.letterSpacing = 3;
    chrome.readingMode.fontName = 'Andika';
    chrome.readingMode.colorTheme = chrome.readingMode.blueTheme;
    chrome.readingMode.turnedHighlightOn();
    const expectedBlueBackground = 'rgb(1, 2, 3)';
    const expectedBlueForeground = 'rgb(4, 5, 6)';
    const expectedBlueCurrentHighlight = 'rgb(7, 8, 9)';
    const expectedBluePreviousHighlight = 'rgb(10, 11, 12)';
    const expectedBlueEmptyBody = 'rgb(13, 14, 15)';
    const expectedBlueLink = 'rgb(16, 17, 18)';
    const expectedBlueLinkVisited = 'rgb(19, 20, 21)';
    updateStyles({
      '--color-read-anything-background-blue': expectedBlueBackground,
      '--color-read-anything-foreground-blue': expectedBlueForeground,
      '--color-read-anything-current-read-aloud-highlight-blue':
          expectedBlueCurrentHighlight,
      '--color-read-anything-previous-read-aloud-highlight-blue':
          expectedBluePreviousHighlight,
      '--google-grey-700': expectedBlueEmptyBody,
      '--color-read-anything-link-default-blue': expectedBlueLink,
      '--color-read-anything-link-visited-blue': expectedBlueLinkVisited,
    });

    updater.setAllTextStyles();

    assertEquals('20px', computeStyle('font-size'));
    assertEquals('80px', computeStyle('line-height'));
    assertEquals('60px', computeStyle('letter-spacing'));
    assertStringContains(
        computeStyle('font-family'), chrome.readingMode.fontName);
    assertStringContains(computeStyle('background'), expectedBlueBackground);
    assertStringContains(computeStyle('color'), expectedBlueForeground);
    assertEquals(
        expectedBlueCurrentHighlight,
        computeStyle('--current-highlight-bg-color'));
    assertEquals(
        expectedBluePreviousHighlight,
        computeStyle('--previous-highlight-color'));
    assertEquals(
        expectedBlueForeground, computeStyle('--sp-empty-state-heading-color'));
    assertEquals(
        expectedBlueEmptyBody, computeStyle('--sp-empty-state-body-color'));
    assertEquals(expectedBlueLink, computeStyle('--link-color'));
    assertEquals(expectedBlueLinkVisited, computeStyle('--visited-link-color'));
  });
});