'use strict'; /** * Set up a test for color properties that does not expect exact equality for * numeric values within the color. This is necessary for color-mix and * relative color syntax, which perform float arithmetic on color channels. * * @param {number} epsilon Epsilon for comparison of numeric values. */ function set_up_fuzzy_color_test(epsilon) { if (!epsilon) { epsilon = 0.01; } // The function function fuzzy_compare_colors(input, expected) { const colorElementDividers = /( |\(|,)/; // Return the string stripped of numbers. function getNonNumbers(color) { return color.replace(/[0-9\.]/g, ''); } // Return an array of all numbers in the color. function getNumbers(color) { const result = []; color.split(colorElementDividers).forEach(element => { const numberElement = parseFloat(element); if (!isNaN(numberElement)) { result.push(numberElement); } }); return result; } try { assert_array_approx_equals(getNumbers(input), getNumbers(expected), epsilon, "Numeric parameters are approximately equal."); // Assert that the text of the two colors are equal. i.e. colorSpace, colorFunction and format. assert_equals(getNonNumbers(input), getNonNumbers(expected), "Color format is correct."); } catch (error) { throw `Colors do not match.\nActual: ${input}\nExpected: ${expected}.\n${error}` } } return fuzzy_compare_colors; } /** * Test the computed value of a color with some tolerance for numeric parameters. * * @param {string} specified A specified value for the color. * @param {string} computed The expected computed color. If omitted, defaults * to the default test_computed_value test, as * fuzziness is unnecessary. * @param {object} epsilon Epsilon for comparison of numeric values. */ function fuzzy_test_computed_color(specified, computed, epsilon) { if (!computed) { test_computed_value("color", specified); return; } test_computed_value("color", specified, computed, undefined /* titleExtra */, {comparisonFunction: set_up_fuzzy_color_test(epsilon)}); } /** * Test the parsed value of a color. * * @param {string} specified A specified value for the property. * @param {string} parsed The expected parsed color. If omitted, defaults * to the default test_valid_value test, as * fuzziness is unnecessary. * @param {object} epsilon Epsilon for comparison of numeric values. */ function fuzzy_test_valid_color(specified, parsed, epsilon) { if (!parsed) { test_valid_value("color", specified); return; } test_valid_value("color", specified, parsed, {comparisonFunction: set_up_fuzzy_color_test(epsilon)}); } /** * Fuzzy color matcher for oklab color with optional transparency. * @param {string} actual Observed color * @param {string} expected What the color should be * @param {string} message Error message to facilitate diagnostics */ function assert_oklab_color(actual, expected, message) { const paramMatch = '(\\-?\\d*\\.?\\d*)'; const optAlphaMatch = '( \\/ (\\d*\\.?\\d*))?'; const pattern = `oklab\\(${paramMatch} ${paramMatch} ${paramMatch}${optAlphaMatch}\\)`; const oklabRegex = new RegExp(pattern); let matches = expected.match(oklabRegex); assert_true(!!matches, `Expected value ${expected} not recognized as an oklab color`); const p0 = parseFloat(matches[1]); const p1 = parseFloat(matches[2]); const p2 = parseFloat(matches[3]); const alpha = (matches[5] !== undefined) ? parseFloat(matches[5]) : undefined; matches = actual.match(oklabRegex); assert_true(!!matches, `Actual value ${actual} not recognized as an oklab color`); const tolerance = 0.01; let colorMatch = Math.abs(parseFloat(matches[1]) - p0) <= tolerance && Math.abs(parseFloat(matches[2]) - p1) <= tolerance && Math.abs(parseFloat(matches[3]) - p2) <= tolerance; if (colorMatch) { if (alpha !== undefined) { colorMatch = matches[5] != undefined && Math.abs(parseFloat(matches[5]) - alpha) <= tolerance; } else { colorMatch = matches[5] == undefined; } } assert_true( colorMatch, `expected: ${expected} actual ${actual} -- ${message}`); }