tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

browser_spectrum.js (14889B)


      1 /* Any copyright is dedicated to the Public Domain.
      2   http://creativecommons.org/publicdomain/zero/1.0/ */
      3 
      4 "use strict";
      5 
      6 // Tests that the spectrum color picker works correctly
      7 
      8 const Spectrum = require("resource://devtools/client/shared/widgets/Spectrum.js");
      9 const {
     10  accessibility: {
     11    SCORES: { FAIL, AAA, AA },
     12  },
     13 } = require("resource://devtools/shared/constants.js");
     14 
     15 loader.lazyRequireGetter(
     16  this,
     17  "cssColors",
     18  "resource://devtools/shared/css/color-db.js",
     19  true
     20 );
     21 
     22 const TEST_URI = CHROME_URL_ROOT + "doc_spectrum.html";
     23 const REGULAR_TEXT_PROPS = {
     24  "font-size": { value: "11px" },
     25  "font-weight": { value: "bold" },
     26  opacity: { value: "1" },
     27 };
     28 const SINGLE_BG_COLOR = {
     29  value: cssColors.white,
     30 };
     31 const ZERO_ALPHA_COLOR = [0, 255, 255, 0];
     32 
     33 add_task(async function () {
     34  const { host, doc } = await createHost("bottom", TEST_URI);
     35 
     36  const container = doc.getElementById("spectrum-container");
     37  await testCreateAndDestroyShouldAppendAndRemoveElements(container);
     38  await testPassingAColorAtInitShouldSetThatColor(container);
     39  await testSettingAndGettingANewColor(container);
     40  await testChangingColorShouldEmitEvents(container, doc);
     41  await testSettingColorShoudUpdateTheUI(container);
     42  await testChangingColorShouldUpdateColorPreview(container);
     43  await testNotSettingTextPropsShouldNotShowContrastSection(container);
     44  await testSettingTextPropsAndColorShouldUpdateContrastValue(container);
     45  await testOnlySelectingLargeTextWithNonZeroAlphaShouldShowIndicator(
     46    container
     47  );
     48  await testSettingMultiColoredBackgroundShouldShowContrastRange(container);
     49 
     50  host.destroy();
     51 });
     52 
     53 /**
     54 * Helper method for extracting the rgba overlay value of the color preview's background
     55 * image style.
     56 *
     57 * @param   {string} linearGradientStr
     58 *          The linear gradient CSS string.
     59 * @return  {string} Returns the rgba string for the color overlay.
     60 */
     61 function extractRgbaOverlayString(linearGradientStr) {
     62  const start = linearGradientStr.indexOf("(");
     63  const end = linearGradientStr.indexOf(")");
     64 
     65  return linearGradientStr.substring(start + 1, end + 1);
     66 }
     67 
     68 function testColorPreviewDisplay(
     69  spectrum,
     70  expectedRgbCssString,
     71  expectedBorderColor
     72 ) {
     73  const { colorPreview } = spectrum;
     74  const colorPreviewStyle = window.getComputedStyle(colorPreview);
     75  expectedBorderColor =
     76    expectedBorderColor === "transparent"
     77      ? "rgba(0, 0, 0, 0)"
     78      : expectedBorderColor;
     79 
     80  spectrum.updateUI();
     81 
     82  // Extract the first rgba value from the linear gradient
     83  const linearGradientStr =
     84    colorPreviewStyle.getPropertyValue("background-image");
     85  const colorPreviewValue = extractRgbaOverlayString(linearGradientStr);
     86 
     87  is(
     88    colorPreviewValue,
     89    expectedRgbCssString,
     90    `Color preview should be ${expectedRgbCssString}`
     91  );
     92 
     93  info("Test if color preview has a border or not.");
     94  // Since border-color is a shorthand CSS property, using getComputedStyle will return
     95  // an empty string. Instead, use one of the border sides to find the border-color value
     96  // since they will all be the same.
     97  const borderColorTop = colorPreviewStyle.getPropertyValue("border-top-color");
     98  is(
     99    borderColorTop,
    100    expectedBorderColor,
    101    "Color preview border color is correct."
    102  );
    103 }
    104 
    105 async function testCreateAndDestroyShouldAppendAndRemoveElements(container) {
    106  ok(container, "We have the root node to append spectrum to");
    107  is(container.childElementCount, 0, "Root node is empty");
    108 
    109  const s = await createSpectrum(container, cssColors.white);
    110  Assert.greater(
    111    container.childElementCount,
    112    0,
    113    "Spectrum has appended elements"
    114  );
    115 
    116  s.destroy();
    117  is(container.childElementCount, 0, "Destroying spectrum removed all nodes");
    118 }
    119 
    120 async function testPassingAColorAtInitShouldSetThatColor(container) {
    121  const initRgba = cssColors.white;
    122 
    123  const s = await createSpectrum(container, initRgba);
    124 
    125  const setRgba = s.rgb;
    126 
    127  is(initRgba[0], setRgba[0], "Spectrum initialized with the right color");
    128  is(initRgba[1], setRgba[1], "Spectrum initialized with the right color");
    129  is(initRgba[2], setRgba[2], "Spectrum initialized with the right color");
    130  is(initRgba[3], setRgba[3], "Spectrum initialized with the right color");
    131 
    132  s.destroy();
    133 }
    134 
    135 async function testSettingAndGettingANewColor(container) {
    136  const s = await createSpectrum(container, cssColors.black);
    137 
    138  const colorToSet = cssColors.white;
    139  s.rgb = colorToSet;
    140  const newColor = s.rgb;
    141 
    142  is(colorToSet[0], newColor[0], "Spectrum set with the right color");
    143  is(colorToSet[1], newColor[1], "Spectrum set with the right color");
    144  is(colorToSet[2], newColor[2], "Spectrum set with the right color");
    145  is(colorToSet[3], newColor[3], "Spectrum set with the right color");
    146 
    147  s.destroy();
    148 }
    149 
    150 async function testChangingColorShouldEmitEventsHelper(
    151  spectrum,
    152  moveFn,
    153  expectedColor
    154 ) {
    155  const onChanged = spectrum.once("changed", (rgba, color) => {
    156    is(rgba[0], expectedColor[0], "New color is correct");
    157    is(rgba[1], expectedColor[1], "New color is correct");
    158    is(rgba[2], expectedColor[2], "New color is correct");
    159    is(rgba[3], expectedColor[3], "New color is correct");
    160    is(`rgba(${rgba.join(", ")})`, color, "RGBA and css color correspond");
    161  });
    162 
    163  moveFn();
    164  await onChanged;
    165  ok(true, "Changed event was emitted on color change");
    166 }
    167 
    168 async function testChangingColorShouldEmitEvents(container, doc) {
    169  const s = await createSpectrum(container, cssColors.white);
    170 
    171  const sendUpKey = () => EventUtils.sendKey("Up");
    172  const sendDownKey = () => EventUtils.sendKey("Down");
    173  const sendLeftKey = () => EventUtils.sendKey("Left");
    174  const sendRightKey = () => EventUtils.sendKey("Right");
    175 
    176  info(
    177    "Test that simulating a mouse drag move event emits color changed event"
    178  );
    179  const draggerMoveFn = () =>
    180    s.onDraggerMove(s.dragger.offsetWidth / 2, s.dragger.offsetHeight / 2);
    181  testChangingColorShouldEmitEventsHelper(s, draggerMoveFn, [128, 64, 64, 1]);
    182 
    183  info(
    184    "Test that moving the dragger with arrow keys emits color changed event."
    185  );
    186  // Focus on the spectrum dragger when spectrum is shown
    187  s.dragger.focus();
    188  is(
    189    doc.activeElement.className,
    190    "spectrum-color spectrum-box",
    191    "Spectrum dragger has successfully received focus."
    192  );
    193  testChangingColorShouldEmitEventsHelper(s, sendDownKey, [125, 62, 62, 1]);
    194  testChangingColorShouldEmitEventsHelper(s, sendLeftKey, [125, 63, 63, 1]);
    195  testChangingColorShouldEmitEventsHelper(s, sendUpKey, [127, 64, 64, 1]);
    196  testChangingColorShouldEmitEventsHelper(s, sendRightKey, [128, 63, 63, 1]);
    197 
    198  info(
    199    "Test that moving the hue slider with arrow keys emits color changed event."
    200  );
    201  // Tab twice to focus on hue slider
    202  EventUtils.sendKey("Tab");
    203  is(
    204    doc.activeElement.className,
    205    "devtools-button",
    206    "Eyedropper has focus now."
    207  );
    208  EventUtils.sendKey("Tab");
    209  is(
    210    doc.activeElement.className,
    211    "spectrum-hue-input",
    212    "Hue slider has successfully received focus."
    213  );
    214  testChangingColorShouldEmitEventsHelper(s, sendRightKey, [128, 66, 63, 1]);
    215  testChangingColorShouldEmitEventsHelper(s, sendLeftKey, [128, 63, 63, 1]);
    216 
    217  info(
    218    "Test that moving the hue slider with arrow keys emits color changed event."
    219  );
    220  // Tab to focus on alpha slider
    221  EventUtils.sendKey("Tab");
    222  is(
    223    doc.activeElement.className,
    224    "spectrum-alpha-input",
    225    "Alpha slider has successfully received focus."
    226  );
    227  testChangingColorShouldEmitEventsHelper(s, sendLeftKey, [128, 63, 63, 0.99]);
    228  testChangingColorShouldEmitEventsHelper(s, sendRightKey, [128, 63, 63, 1]);
    229 
    230  s.destroy();
    231 }
    232 
    233 function setSpectrumProps(spectrum, props, updateUI = true) {
    234  for (const prop in props) {
    235    spectrum[prop] = props[prop];
    236 
    237    // Setting textProps implies contrast should be enabled for spectrum
    238    if (prop === "textProps") {
    239      spectrum.contrastEnabled = true;
    240    }
    241  }
    242 
    243  if (updateUI) {
    244    spectrum.updateUI();
    245  }
    246 }
    247 
    248 function testAriaAttributesOnSpectrumElements(spectrum, colorName, rgbString) {
    249  for (const slider of [spectrum.dragger, spectrum.hueSlider]) {
    250    is(
    251      slider.getAttribute("aria-describedby"),
    252      "spectrum-dragger",
    253      "Slider contains the correct describedby text."
    254    );
    255    is(
    256      slider.getAttribute("aria-valuetext"),
    257      rgbString,
    258      "Slider contains the correct valuetext text."
    259    );
    260  }
    261 
    262  is(
    263    spectrum.colorPreview.title,
    264    colorName,
    265    "Spectrum element contains the correct title text."
    266  );
    267 }
    268 
    269 async function testSettingColorShoudUpdateTheUI(container) {
    270  const s = await createSpectrum(container, cssColors.white);
    271  const dragHelperOriginalPos = [
    272    s.dragHelper.style.top,
    273    s.dragHelper.style.left,
    274  ];
    275  const alphaSliderOriginalVal = s.alphaSlider.value;
    276  let hueSliderOriginalVal = s.hueSlider.value;
    277 
    278  setSpectrumProps(s, { rgb: [50, 240, 0, 0.2] });
    279 
    280  Assert.notEqual(
    281    s.alphaSlider.value,
    282    alphaSliderOriginalVal,
    283    "Alpha helper has moved"
    284  );
    285  Assert.notStrictEqual(
    286    s.dragHelper.style.top,
    287    dragHelperOriginalPos[0],
    288    "Drag helper has moved"
    289  );
    290  Assert.notStrictEqual(
    291    s.dragHelper.style.left,
    292    dragHelperOriginalPos[1],
    293    "Drag helper has moved"
    294  );
    295  Assert.notStrictEqual(
    296    s.hueSlider.value,
    297    hueSliderOriginalVal,
    298    "Hue helper has moved"
    299  );
    300  testAriaAttributesOnSpectrumElements(
    301    s,
    302    "Closest to: lime",
    303    "rgba(50, 240, 0, 0.2)",
    304    0.2
    305  );
    306 
    307  hueSliderOriginalVal = s.hueSlider.value;
    308 
    309  setSpectrumProps(s, { rgb: [0, 255, 0, 0] });
    310  is(s.alphaSlider.value, "0", "Alpha range UI has been updated again");
    311  Assert.notStrictEqual(
    312    hueSliderOriginalVal,
    313    s.hueSlider.value,
    314    "Hue slider should have move again"
    315  );
    316  testAriaAttributesOnSpectrumElements(s, "lime", "rgba(0, 255, 0, 0)", 0);
    317 
    318  s.destroy();
    319 }
    320 
    321 async function testChangingColorShouldUpdateColorPreview(container) {
    322  const s = await createSpectrum(container, [0, 0, 1, 1]);
    323 
    324  info("Test that color preview is black.");
    325  testColorPreviewDisplay(s, "rgb(0, 0, 1)", "transparent");
    326 
    327  info("Test that color preview is blue.");
    328  s.rgb = [0, 0, 255, 1];
    329  testColorPreviewDisplay(s, "rgb(0, 0, 255)", "transparent");
    330 
    331  info("Test that color preview is red.");
    332  s.rgb = [255, 0, 0, 1];
    333  testColorPreviewDisplay(s, "rgb(255, 0, 0)", "transparent");
    334 
    335  info("Test that color preview is white and also has a light grey border.");
    336  s.rgb = cssColors.white;
    337  testColorPreviewDisplay(s, "rgb(255, 255, 255)", "rgb(204, 204, 204)");
    338 
    339  s.destroy();
    340 }
    341 
    342 async function testNotSettingTextPropsShouldNotShowContrastSection(container) {
    343  const s = await createSpectrum(container, cssColors.white);
    344 
    345  setSpectrumProps(s, { rgb: cssColors.black });
    346  ok(
    347    !s.spectrumContrast.classList.contains("visible"),
    348    "Contrast section is not shown."
    349  );
    350 
    351  s.destroy();
    352 }
    353 
    354 function testSpectrumContrast(
    355  spectrum,
    356  contrastValueEl,
    357  rgb,
    358  expectedValue,
    359  expectedBadgeClass = "",
    360  expectLargeTextIndicator = false
    361 ) {
    362  setSpectrumProps(spectrum, { rgb });
    363 
    364  is(
    365    contrastValueEl.textContent,
    366    expectedValue,
    367    "Contrast value has the correct text."
    368  );
    369  is(
    370    contrastValueEl.className,
    371    `accessibility-contrast-value${
    372      expectedBadgeClass ? " " + expectedBadgeClass : ""
    373    }`,
    374    `Contrast value contains ${expectedBadgeClass || "base"} class.`
    375  );
    376  is(
    377    spectrum.contrastLabel.childNodes.length === 3,
    378    expectLargeTextIndicator,
    379    `Large text indicator is ${expectLargeTextIndicator ? "" : "not"} shown.`
    380  );
    381 }
    382 
    383 async function testSettingTextPropsAndColorShouldUpdateContrastValue(
    384  container
    385 ) {
    386  const s = await createSpectrum(container, cssColors.white);
    387 
    388  ok(
    389    !s.spectrumContrast.classList.contains("visible"),
    390    "Contrast value is not available yet."
    391  );
    392 
    393  info(
    394    "Test that contrast ratio is calculated on setting 'textProps' and 'rgb'."
    395  );
    396  setSpectrumProps(
    397    s,
    398    { textProps: REGULAR_TEXT_PROPS, backgroundColorData: SINGLE_BG_COLOR },
    399    false
    400  );
    401  testSpectrumContrast(s, s.contrastValue, [50, 240, 234, 0.8], "1.35", FAIL);
    402 
    403  info("Test that contrast ratio is updated when color is changed.");
    404  testSpectrumContrast(s, s.contrastValue, cssColors.black, "21.00", AAA);
    405 
    406  info("Test that contrast ratio cannot be calculated with zero alpha.");
    407  testSpectrumContrast(
    408    s,
    409    s.contrastValue,
    410    ZERO_ALPHA_COLOR,
    411    "Unable to calculate"
    412  );
    413 
    414  s.destroy();
    415 }
    416 
    417 async function testOnlySelectingLargeTextWithNonZeroAlphaShouldShowIndicator(
    418  container
    419 ) {
    420  let s = await createSpectrum(container, cssColors.white);
    421 
    422  Assert.notStrictEqual(
    423    s.contrastLabel.childNodes.length,
    424    3,
    425    "Large text indicator is initially hidden."
    426  );
    427 
    428  info(
    429    "Test that selecting large text with non-zero alpha shows large text indicator."
    430  );
    431  setSpectrumProps(
    432    s,
    433    {
    434      textProps: {
    435        "font-size": { value: "24px" },
    436        "font-weight": { value: "normal" },
    437        opacity: { value: "1" },
    438      },
    439      backgroundColorData: SINGLE_BG_COLOR,
    440    },
    441    false
    442  );
    443  testSpectrumContrast(s, s.contrastValue, cssColors.black, "21.00", AAA, true);
    444 
    445  info(
    446    "Test that selecting large text with zero alpha hides large text indicator."
    447  );
    448  testSpectrumContrast(
    449    s,
    450    s.contrastValue,
    451    ZERO_ALPHA_COLOR,
    452    "Unable to calculate"
    453  );
    454 
    455  // Spectrum should be closed and opened again to reflect changes in text size
    456  s.destroy();
    457  s = await createSpectrum(container, cssColors.white);
    458 
    459  info("Test that selecting regular text does not show large text indicator.");
    460  setSpectrumProps(
    461    s,
    462    { textProps: REGULAR_TEXT_PROPS, backgroundColorData: SINGLE_BG_COLOR },
    463    false
    464  );
    465  testSpectrumContrast(s, s.contrastValue, cssColors.black, "21.00", AAA);
    466 
    467  s.destroy();
    468 }
    469 
    470 async function testSettingMultiColoredBackgroundShouldShowContrastRange(
    471  container
    472 ) {
    473  const s = await createSpectrum(container, cssColors.white);
    474 
    475  info(
    476    "Test setting text with non-zero alpha and multi-colored bg shows contrast range and empty single contrast."
    477  );
    478  setSpectrumProps(
    479    s,
    480    {
    481      textProps: REGULAR_TEXT_PROPS,
    482      backgroundColorData: {
    483        min: cssColors.yellow,
    484        max: cssColors.green,
    485      },
    486    },
    487    false
    488  );
    489  testSpectrumContrast(s, s.contrastValueMin, cssColors.white, "1.07", FAIL);
    490  testSpectrumContrast(s, s.contrastValueMax, cssColors.white, "5.14", AA);
    491  testSpectrumContrast(s, s.contrastValue, cssColors.white, "");
    492  ok(
    493    s.spectrumContrast.classList.contains("range"),
    494    "Contrast section contains range class."
    495  );
    496 
    497  info("Test setting text with zero alpha shows error in contrast min span.");
    498  testSpectrumContrast(
    499    s,
    500    s.contrastValueMin,
    501    ZERO_ALPHA_COLOR,
    502    "Unable to calculate"
    503  );
    504 
    505  s.destroy();
    506 }
    507 
    508 async function createSpectrum(...spectrumConstructorParams) {
    509  const s = new Spectrum(...spectrumConstructorParams);
    510  await waitFor(() => s.dragger.offsetHeight > 0);
    511  s.show();
    512  return s;
    513 }