summaryrefslogtreecommitdiffstats
path: root/devtools/client/shared/test/browser_spectrum.js
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/client/shared/test/browser_spectrum.js')
-rw-r--r--devtools/client/shared/test/browser_spectrum.js518
1 files changed, 518 insertions, 0 deletions
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;
+}