From 26a029d407be480d791972afb5975cf62c9360a6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 02:47:55 +0200 Subject: Adding upstream version 124.0.1. Signed-off-by: Daniel Baumann --- devtools/client/shared/test/browser_spectrum.js | 518 ++++++++++++++++++++++++ 1 file changed, 518 insertions(+) create mode 100644 devtools/client/shared/test/browser_spectrum.js (limited to 'devtools/client/shared/test/browser_spectrum.js') diff --git a/devtools/client/shared/test/browser_spectrum.js b/devtools/client/shared/test/browser_spectrum.js new file mode 100644 index 0000000000..6189b1f0af --- /dev/null +++ b/devtools/client/shared/test/browser_spectrum.js @@ -0,0 +1,518 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Tests that the spectrum color picker works correctly + +const Spectrum = require("resource://devtools/client/shared/widgets/Spectrum.js"); +const { + accessibility: { + SCORES: { FAIL, AAA, AA }, + }, +} = require("resource://devtools/shared/constants.js"); + +loader.lazyRequireGetter( + this, + "cssColors", + "resource://devtools/shared/css/color-db.js", + true +); + +const TEST_URI = CHROME_URL_ROOT + "doc_spectrum.html"; +const REGULAR_TEXT_PROPS = { + "font-size": { value: "11px" }, + "font-weight": { value: "bold" }, + opacity: { value: "1" }, +}; +const SINGLE_BG_COLOR = { + value: cssColors.white, +}; +const ZERO_ALPHA_COLOR = [0, 255, 255, 0]; + +add_task(async function () { + const { host, doc } = await createHost("bottom", TEST_URI); + + const container = doc.getElementById("spectrum-container"); + await testCreateAndDestroyShouldAppendAndRemoveElements(container); + await testPassingAColorAtInitShouldSetThatColor(container); + await testSettingAndGettingANewColor(container); + await testChangingColorShouldEmitEvents(container, doc); + await testSettingColorShoudUpdateTheUI(container); + await testChangingColorShouldUpdateColorPreview(container); + await testNotSettingTextPropsShouldNotShowContrastSection(container); + await testSettingTextPropsAndColorShouldUpdateContrastValue(container); + await testOnlySelectingLargeTextWithNonZeroAlphaShouldShowIndicator( + container + ); + await testSettingMultiColoredBackgroundShouldShowContrastRange(container); + + host.destroy(); +}); + +/** + * Helper method for extracting the rgba overlay value of the color preview's background + * image style. + * + * @param {String} linearGradientStr + * The linear gradient CSS string. + * @return {String} Returns the rgba string for the color overlay. + */ +function extractRgbaOverlayString(linearGradientStr) { + const start = linearGradientStr.indexOf("("); + const end = linearGradientStr.indexOf(")"); + + return linearGradientStr.substring(start + 1, end + 1); +} + +function testColorPreviewDisplay( + spectrum, + expectedRgbCssString, + expectedBorderColor +) { + const { colorPreview } = spectrum; + const colorPreviewStyle = window.getComputedStyle(colorPreview); + expectedBorderColor = + expectedBorderColor === "transparent" + ? "rgba(0, 0, 0, 0)" + : expectedBorderColor; + + spectrum.updateUI(); + + // Extract the first rgba value from the linear gradient + const linearGradientStr = + colorPreviewStyle.getPropertyValue("background-image"); + const colorPreviewValue = extractRgbaOverlayString(linearGradientStr); + + is( + colorPreviewValue, + expectedRgbCssString, + `Color preview should be ${expectedRgbCssString}` + ); + + info("Test if color preview has a border or not."); + // Since border-color is a shorthand CSS property, using getComputedStyle will return + // an empty string. Instead, use one of the border sides to find the border-color value + // since they will all be the same. + const borderColorTop = colorPreviewStyle.getPropertyValue("border-top-color"); + is( + borderColorTop, + expectedBorderColor, + "Color preview border color is correct." + ); +} + +async function testCreateAndDestroyShouldAppendAndRemoveElements(container) { + ok(container, "We have the root node to append spectrum to"); + is(container.childElementCount, 0, "Root node is empty"); + + const s = await createSpectrum(container, cssColors.white); + Assert.greater( + container.childElementCount, + 0, + "Spectrum has appended elements" + ); + + s.destroy(); + is(container.childElementCount, 0, "Destroying spectrum removed all nodes"); +} + +async function testPassingAColorAtInitShouldSetThatColor(container) { + const initRgba = cssColors.white; + + const s = await createSpectrum(container, initRgba); + + const setRgba = s.rgb; + + is(initRgba[0], setRgba[0], "Spectrum initialized with the right color"); + is(initRgba[1], setRgba[1], "Spectrum initialized with the right color"); + is(initRgba[2], setRgba[2], "Spectrum initialized with the right color"); + is(initRgba[3], setRgba[3], "Spectrum initialized with the right color"); + + s.destroy(); +} + +async function testSettingAndGettingANewColor(container) { + const s = await createSpectrum(container, cssColors.black); + + const colorToSet = cssColors.white; + s.rgb = colorToSet; + const newColor = s.rgb; + + is(colorToSet[0], newColor[0], "Spectrum set with the right color"); + is(colorToSet[1], newColor[1], "Spectrum set with the right color"); + is(colorToSet[2], newColor[2], "Spectrum set with the right color"); + is(colorToSet[3], newColor[3], "Spectrum set with the right color"); + + s.destroy(); +} + +async function testChangingColorShouldEmitEventsHelper( + spectrum, + moveFn, + expectedColor +) { + const onChanged = spectrum.once("changed", (rgba, color) => { + is(rgba[0], expectedColor[0], "New color is correct"); + is(rgba[1], expectedColor[1], "New color is correct"); + is(rgba[2], expectedColor[2], "New color is correct"); + is(rgba[3], expectedColor[3], "New color is correct"); + is(`rgba(${rgba.join(", ")})`, color, "RGBA and css color correspond"); + }); + + moveFn(); + await onChanged; + ok(true, "Changed event was emitted on color change"); +} + +async function testChangingColorShouldEmitEvents(container, doc) { + const s = await createSpectrum(container, cssColors.white); + + const sendUpKey = () => EventUtils.sendKey("Up"); + const sendDownKey = () => EventUtils.sendKey("Down"); + const sendLeftKey = () => EventUtils.sendKey("Left"); + const sendRightKey = () => EventUtils.sendKey("Right"); + + info( + "Test that simulating a mouse drag move event emits color changed event" + ); + const draggerMoveFn = () => + s.onDraggerMove(s.dragger.offsetWidth / 2, s.dragger.offsetHeight / 2); + testChangingColorShouldEmitEventsHelper(s, draggerMoveFn, [128, 64, 64, 1]); + + info( + "Test that moving the dragger with arrow keys emits color changed event." + ); + // Focus on the spectrum dragger when spectrum is shown + s.dragger.focus(); + is( + doc.activeElement.className, + "spectrum-color spectrum-box", + "Spectrum dragger has successfully received focus." + ); + testChangingColorShouldEmitEventsHelper(s, sendDownKey, [125, 62, 62, 1]); + testChangingColorShouldEmitEventsHelper(s, sendLeftKey, [125, 63, 63, 1]); + testChangingColorShouldEmitEventsHelper(s, sendUpKey, [128, 64, 64, 1]); + testChangingColorShouldEmitEventsHelper(s, sendRightKey, [128, 63, 63, 1]); + + info( + "Test that moving the hue slider with arrow keys emits color changed event." + ); + // Tab twice to focus on hue slider + EventUtils.sendKey("Tab"); + is( + doc.activeElement.className, + "devtools-button", + "Eyedropper has focus now." + ); + EventUtils.sendKey("Tab"); + is( + doc.activeElement.className, + "spectrum-hue-input", + "Hue slider has successfully received focus." + ); + testChangingColorShouldEmitEventsHelper(s, sendRightKey, [128, 66, 63, 1]); + testChangingColorShouldEmitEventsHelper(s, sendLeftKey, [128, 63, 63, 1]); + + info( + "Test that moving the hue slider with arrow keys emits color changed event." + ); + // Tab to focus on alpha slider + EventUtils.sendKey("Tab"); + is( + doc.activeElement.className, + "spectrum-alpha-input", + "Alpha slider has successfully received focus." + ); + testChangingColorShouldEmitEventsHelper(s, sendLeftKey, [128, 63, 63, 0.99]); + testChangingColorShouldEmitEventsHelper(s, sendRightKey, [128, 63, 63, 1]); + + s.destroy(); +} + +function setSpectrumProps(spectrum, props, updateUI = true) { + for (const prop in props) { + spectrum[prop] = props[prop]; + + // Setting textProps implies contrast should be enabled for spectrum + if (prop === "textProps") { + spectrum.contrastEnabled = true; + } + } + + if (updateUI) { + spectrum.updateUI(); + } +} + +function testAriaAttributesOnSpectrumElements( + spectrum, + colorName, + rgbString, + alpha +) { + for (const slider of [spectrum.dragger, spectrum.hueSlider]) { + is( + slider.getAttribute("aria-describedby"), + "spectrum-dragger", + "Slider contains the correct describedby text." + ); + is( + slider.getAttribute("aria-valuetext"), + rgbString, + "Slider contains the correct valuetext text." + ); + } + + is( + spectrum.colorPreview.title, + colorName, + "Spectrum element contains the correct title text." + ); +} + +async function testSettingColorShoudUpdateTheUI(container) { + const s = await createSpectrum(container, cssColors.white); + const dragHelperOriginalPos = [ + s.dragHelper.style.top, + s.dragHelper.style.left, + ]; + const alphaSliderOriginalVal = s.alphaSlider.value; + let hueSliderOriginalVal = s.hueSlider.value; + + setSpectrumProps(s, { rgb: [50, 240, 234, 0.2] }); + + Assert.notEqual( + s.alphaSlider.value, + alphaSliderOriginalVal, + "Alpha helper has moved" + ); + Assert.notStrictEqual( + s.dragHelper.style.top, + dragHelperOriginalPos[0], + "Drag helper has moved" + ); + Assert.notStrictEqual( + s.dragHelper.style.left, + dragHelperOriginalPos[1], + "Drag helper has moved" + ); + Assert.notStrictEqual( + s.hueSlider.value, + hueSliderOriginalVal, + "Hue helper has moved" + ); + testAriaAttributesOnSpectrumElements( + s, + "Closest to: aqua", + "rgba(50, 240, 234, 0.2)", + 0.2 + ); + + hueSliderOriginalVal = s.hueSlider.value; + + setSpectrumProps(s, { rgb: ZERO_ALPHA_COLOR }); + is(s.alphaSlider.value, "0", "Alpha range UI has been updated again"); + Assert.notStrictEqual( + hueSliderOriginalVal, + s.hueSlider.value, + "Hue slider should have move again" + ); + testAriaAttributesOnSpectrumElements(s, "aqua", "rgba(0, 255, 255, 0)", 0); + + s.destroy(); +} + +async function testChangingColorShouldUpdateColorPreview(container) { + const s = await createSpectrum(container, [0, 0, 1, 1]); + + info("Test that color preview is black."); + testColorPreviewDisplay(s, "rgb(0, 0, 1)", "transparent"); + + info("Test that color preview is blue."); + s.rgb = [0, 0, 255, 1]; + testColorPreviewDisplay(s, "rgb(0, 0, 255)", "transparent"); + + info("Test that color preview is red."); + s.rgb = [255, 0, 0, 1]; + testColorPreviewDisplay(s, "rgb(255, 0, 0)", "transparent"); + + info("Test that color preview is white and also has a light grey border."); + s.rgb = cssColors.white; + testColorPreviewDisplay(s, "rgb(255, 255, 255)", "rgb(204, 204, 204)"); + + s.destroy(); +} + +async function testNotSettingTextPropsShouldNotShowContrastSection(container) { + const s = await createSpectrum(container, cssColors.white); + + setSpectrumProps(s, { rgb: cssColors.black }); + ok( + !s.spectrumContrast.classList.contains("visible"), + "Contrast section is not shown." + ); + + s.destroy(); +} + +function testSpectrumContrast( + spectrum, + contrastValueEl, + rgb, + expectedValue, + expectedBadgeClass = "", + expectLargeTextIndicator = false +) { + setSpectrumProps(spectrum, { rgb }); + + is( + contrastValueEl.textContent, + expectedValue, + "Contrast value has the correct text." + ); + is( + contrastValueEl.className, + `accessibility-contrast-value${ + expectedBadgeClass ? " " + expectedBadgeClass : "" + }`, + `Contrast value contains ${expectedBadgeClass || "base"} class.` + ); + is( + spectrum.contrastLabel.childNodes.length === 3, + expectLargeTextIndicator, + `Large text indicator is ${expectLargeTextIndicator ? "" : "not"} shown.` + ); +} + +async function testSettingTextPropsAndColorShouldUpdateContrastValue( + container +) { + const s = await createSpectrum(container, cssColors.white); + + ok( + !s.spectrumContrast.classList.contains("visible"), + "Contrast value is not available yet." + ); + + info( + "Test that contrast ratio is calculated on setting 'textProps' and 'rgb'." + ); + setSpectrumProps( + s, + { textProps: REGULAR_TEXT_PROPS, backgroundColorData: SINGLE_BG_COLOR }, + false + ); + testSpectrumContrast(s, s.contrastValue, [50, 240, 234, 0.8], "1.35", FAIL); + + info("Test that contrast ratio is updated when color is changed."); + testSpectrumContrast(s, s.contrastValue, cssColors.black, "21.00", AAA); + + info("Test that contrast ratio cannot be calculated with zero alpha."); + testSpectrumContrast( + s, + s.contrastValue, + ZERO_ALPHA_COLOR, + "Unable to calculate" + ); + + s.destroy(); +} + +async function testOnlySelectingLargeTextWithNonZeroAlphaShouldShowIndicator( + container +) { + let s = await createSpectrum(container, cssColors.white); + + Assert.notStrictEqual( + s.contrastLabel.childNodes.length, + 3, + "Large text indicator is initially hidden." + ); + + info( + "Test that selecting large text with non-zero alpha shows large text indicator." + ); + setSpectrumProps( + s, + { + textProps: { + "font-size": { value: "24px" }, + "font-weight": { value: "normal" }, + opacity: { value: "1" }, + }, + backgroundColorData: SINGLE_BG_COLOR, + }, + false + ); + testSpectrumContrast(s, s.contrastValue, cssColors.black, "21.00", AAA, true); + + info( + "Test that selecting large text with zero alpha hides large text indicator." + ); + testSpectrumContrast( + s, + s.contrastValue, + ZERO_ALPHA_COLOR, + "Unable to calculate" + ); + + // Spectrum should be closed and opened again to reflect changes in text size + s.destroy(); + s = await createSpectrum(container, cssColors.white); + + info("Test that selecting regular text does not show large text indicator."); + setSpectrumProps( + s, + { textProps: REGULAR_TEXT_PROPS, backgroundColorData: SINGLE_BG_COLOR }, + false + ); + testSpectrumContrast(s, s.contrastValue, cssColors.black, "21.00", AAA); + + s.destroy(); +} + +async function testSettingMultiColoredBackgroundShouldShowContrastRange( + container +) { + const s = await createSpectrum(container, cssColors.white); + + info( + "Test setting text with non-zero alpha and multi-colored bg shows contrast range and empty single contrast." + ); + setSpectrumProps( + s, + { + textProps: REGULAR_TEXT_PROPS, + backgroundColorData: { + min: cssColors.yellow, + max: cssColors.green, + }, + }, + false + ); + testSpectrumContrast(s, s.contrastValueMin, cssColors.white, "1.07", FAIL); + testSpectrumContrast(s, s.contrastValueMax, cssColors.white, "5.14", AA); + testSpectrumContrast(s, s.contrastValue, cssColors.white, ""); + ok( + s.spectrumContrast.classList.contains("range"), + "Contrast section contains range class." + ); + + info("Test setting text with zero alpha shows error in contrast min span."); + testSpectrumContrast( + s, + s.contrastValueMin, + ZERO_ALPHA_COLOR, + "Unable to calculate" + ); + + s.destroy(); +} + +async function createSpectrum(...spectrumConstructorParams) { + const s = new Spectrum(...spectrumConstructorParams); + await waitFor(() => s.dragger.offsetHeight > 0); + s.show(); + return s; +} -- cgit v1.2.3