/** * The MIT License (MIT) * Copyright (c) 2014-2015 Yahoo! Inc. * Modifications copyright (c) Microsoft Corporation * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ // @ts-check import { test as it, expect } from './playwright-test-fixtures'; import fs from 'fs'; import path from 'path'; import BlinkDiff from '../../packages/playwright-test/lib/third_party/blink-diff'; import PNGImage from '../../packages/playwright-test/lib/third_party/png-js'; /** * @param {string} type * @returns {PNGImage} */ function generateImage(type) { let image; switch (type) { case 'small-1': image = PNGImage.createImage(2, 2); image.setAt(0, 0, { red: 10, green: 20, blue: 30, alpha: 40 }); image.setAt(0, 1, { red: 50, green: 60, blue: 70, alpha: 80 }); image.setAt(1, 0, { red: 90, green: 100, blue: 110, alpha: 120 }); image.setAt(1, 1, { red: 130, green: 140, blue: 150, alpha: 160 }); break; case 'small-2': image = PNGImage.createImage(2, 2); image.setAt(0, 0, { red: 210, green: 220, blue: 230, alpha: 240 }); image.setAt(0, 1, { red: 10, green: 20, blue: 30, alpha: 40 }); image.setAt(1, 0, { red: 50, green: 60, blue: 70, alpha: 80 }); image.setAt(1, 1, { red: 15, green: 25, blue: 35, alpha: 45 }); break; case 'small-3': image = PNGImage.createImage(2, 2); break; case 'medium-1': image = PNGImage.createImage(3, 3); image.setAt(0, 0, { red: 130, green: 140, blue: 150, alpha: 160 }); image.setAt(0, 1, { red: 170, green: 180, blue: 190, alpha: 200 }); image.setAt(0, 2, { red: 210, green: 220, blue: 230, alpha: 240 }); image.setAt(1, 0, { red: 15, green: 25, blue: 35, alpha: 45 }); image.setAt(1, 1, { red: 55, green: 65, blue: 75, alpha: 85 }); image.setAt(1, 2, { red: 95, green: 105, blue: 115, alpha: 125 }); image.setAt(2, 0, { red: 10, green: 20, blue: 30, alpha: 40 }); image.setAt(2, 1, { red: 50, green: 60, blue: 70, alpha: 80 }); image.setAt(2, 2, { red: 90, green: 100, blue: 110, alpha: 120 }); break; case 'medium-2': image = PNGImage.createImage(3, 3); image.setAt(0, 0, { red: 95, green: 15, blue: 165, alpha: 26 }); image.setAt(0, 1, { red: 15, green: 225, blue: 135, alpha: 144 }); image.setAt(0, 2, { red: 170, green: 80, blue: 210, alpha: 2 }); image.setAt(1, 0, { red: 50, green: 66, blue: 23, alpha: 188 }); image.setAt(1, 1, { red: 110, green: 120, blue: 63, alpha: 147 }); image.setAt(1, 2, { red: 30, green: 110, blue: 10, alpha: 61 }); image.setAt(2, 0, { red: 190, green: 130, blue: 180, alpha: 29 }); image.setAt(2, 1, { red: 10, green: 120, blue: 31, alpha: 143 }); image.setAt(2, 2, { red: 155, green: 165, blue: 15, alpha: 185 }); break; case 'slim-1': image = PNGImage.createImage(1, 3); image.setAt(0, 0, { red: 15, green: 225, blue: 135, alpha: 144 }); image.setAt(0, 1, { red: 170, green: 80, blue: 210, alpha: 2 }); image.setAt(0, 2, { red: 50, green: 66, blue: 23, alpha: 188 }); break; case 'slim-2': image = PNGImage.createImage(3, 1); image.setAt(0, 0, { red: 15, green: 225, blue: 135, alpha: 144 }); image.setAt(1, 0, { red: 170, green: 80, blue: 210, alpha: 2 }); image.setAt(2, 0, { red: 50, green: 66, blue: 23, alpha: 188 }); break; } return /** @type {PNGImage} */ (image); } /** * @param {Buffer} buf1 * @param {Buffer} buf2 * @returns boolean */ function compareBuffer(buf1, buf2) { if (buf1.length !== buf2.length) return false; for (let i = 0, len = buf1.length; i < len; i++) { if (buf1[i] !== buf2[i]) return false; } return true; } it.describe('Blink-Diff', () => { it.describe('Default values', () => { /** @type {BlinkDiff} */ let instance; it.beforeEach(() => { instance = new BlinkDiff({ imageA: 'image-a' as any, imageAPath: 'path to image-a', imageB: 'image-b' as any, imageBPath: 'path to image-b', composition: false }); }); it('should have the right values for imageA', () => { expect(instance._imageA).toBe('image-a'); }); it('should have the right values for imageAPath', () => { expect(instance._imageAPath).toBe('path to image-a'); }); it('should have the right values for imageB', () => { expect(instance._imageB).toBe('image-b'); }); it('should have the right values for imageBPath', () => { expect(instance._imageBPath).toBe('path to image-b'); }); it('should not have a value for imageOutputPath', () => { expect(instance._imageOutputPath).toBeUndefined(); }); it('should not have a value for thresholdType', () => { expect(instance._thresholdType).toBe('pixel'); }); it('should not have a value for threshold', () => { expect(instance._threshold).toBe(500); }); it('should not have a value for delta', () => { expect(instance._delta).toBe(20); }); it('should not have a value for outputMaskRed', () => { expect(instance._outputMaskRed).toBe(255); }); it('should not have a value for outputMaskGreen', () => { expect(instance._outputMaskGreen).toBe(0); }); it('should not have a value for outputMaskBlue', () => { expect(instance._outputMaskBlue).toBe(0); }); it('should not have a value for outputMaskAlpha', () => { expect(instance._outputMaskAlpha).toBe(255); }); it('should not have a value for outputMaskOpacity', () => { expect(instance._outputMaskOpacity).toBe(0.7); }); it('should not have a value for outputBackgroundRed', () => { expect(instance._outputBackgroundRed).toBe(0); }); it('should not have a value for outputBackgroundGreen', () => { expect(instance._outputBackgroundGreen).toBe(0); }); it('should not have a value for outputBackgroundBlue', () => { expect(instance._outputBackgroundBlue).toBe(0); }); it('should not have a value for outputBackgroundAlpha', () => { expect(instance._outputBackgroundAlpha).toBeUndefined(); }); it('should not have a value for outputBackgroundOpacity', () => { expect(instance._outputBackgroundOpacity).toBe(0.6); }); it('should not have a value for copyImageAToOutput', () => { expect(instance._copyImageAToOutput).toBeTruthy(); }); it('should not have a value for copyImageBToOutput', () => { expect(instance._copyImageBToOutput).toBeFalsy(); }); it('should not have a value for filter', () => { expect(instance._filter).toEqual([]); }); it('should not have a value for debug', () => { expect(instance._debug).toBeFalsy(); }); it.describe('Special cases', () => { /** @type {BlinkDiff} */ let instance; it.beforeEach(() => { instance = new BlinkDiff({ imageA: 'image-a' as any, imageB: 'image-b' as any }); }); it('should have the images', () => { expect(instance._imageA).toBe('image-a'); expect(instance._imageB).toBe('image-b'); }); }); }); it.describe('Methods', () => { /** @type {BlinkDiff} */ let instance; it.beforeEach(() => { instance = new BlinkDiff({ imageA: 'image-a' as any, imageAPath: 'path to image-a', imageB: 'image-b' as any, imageBPath: 'path to image-b' }); }); it.describe('hasPassed', () => { it('should pass when identical', () => { expect(instance.hasPassed(BlinkDiff.RESULT_IDENTICAL)).toBeTruthy(); }); it('should pass when similar', () => { expect(instance.hasPassed(BlinkDiff.RESULT_SIMILAR)).toBeTruthy(); }); it('should not pass when unknown', () => { expect(instance.hasPassed(BlinkDiff.RESULT_UNKNOWN)).toBeFalsy(); }); it('should not pass when different', () => { expect(instance.hasPassed(BlinkDiff.RESULT_DIFFERENT)).toBeFalsy(); }); }); it.describe('_colorDelta', () => { it('should calculate the delta', () => { const color1 = { c1: 23, c2: 87, c3: 89, c4: 234 }, color2 = { c1: 84, c2: 92, c3: 50, c4: 21 }; expect(instance._colorDelta(color1, color2)).toBeGreaterThanOrEqual(225.02); expect(instance._colorDelta(color1, color2)).toBeLessThanOrEqual(225.03); }); }); it.describe('_loadImage', () => { /** @type {PNGImage} */ let image; it.beforeEach(() => { image = generateImage('medium-2'); }); it.describe('from Image', () => { it('should use already loaded image', () => { const result = instance._loadImageSync('pathToFile', image); expect(result).toBeInstanceOf(PNGImage); expect(result).toBe(image); }); }); it.describe('from Path', () => { it('should load image when only path given', async () => { const image = await instance._loadImageSync(path.join(__dirname, 'assets', 'test.png')); const compare = compareBuffer(image.getImage().data, image.getImage().data); expect(compare).toBeTruthy(); }); }); it.describe('from Buffer', () => { /** @type {Buffer} */ let buffer; it.beforeEach(() => { buffer = fs.readFileSync(path.join(__dirname, 'assets', 'test.png')); }); it('should load image from buffer if given', async () => { const image = await instance._loadImageSync('pathToFile', buffer); const compare = compareBuffer(image.getImage().data, image.getImage().data); expect(compare).toBeTruthy(); }); }); }); it.describe('_copyImage', () => { it('should copy the image', () => { const image1 = generateImage('small-1'); const image2 = generateImage('small-2'); instance._copyImage(image1, image2); expect(image1.getAt(0, 0)).toBe(image2.getAt(0, 0)); expect(image1.getAt(0, 1)).toBe(image2.getAt(0, 1)); expect(image1.getAt(1, 0)).toBe(image2.getAt(1, 0)); expect(image1.getAt(1, 1)).toBe(image2.getAt(1, 1)); }); }); it.describe('_correctDimensions', () => { it.describe('Negative Values', () => { it('should correct negative x values', () => { const rect = { x: -10, y: 23, width: 42, height: 57 }; instance._correctDimensions(300, 200, rect); expect(rect.x).toBe(0); expect(rect.y).toBe(23); expect(rect.width).toBe(42); expect(rect.height).toBe(57); }); it('should correct negative y values', () => { const rect = { x: 10, y: -23, width: 42, height: 57 }; instance._correctDimensions(300, 200, rect); expect(rect.x).toBe(10); expect(rect.y).toBe(0); expect(rect.width).toBe(42); expect(rect.height).toBe(57); }); it('should correct negative width values', () => { const rect = { x: 10, y: 23, width: -42, height: 57 }; instance._correctDimensions(300, 200, rect); expect(rect.x).toBe(10); expect(rect.y).toBe(23); expect(rect.width).toBe(0); expect(rect.height).toBe(57); }); it('should correct negative height values', () => { const rect = { x: 10, y: 23, width: 42, height: -57 }; instance._correctDimensions(300, 200, rect); expect(rect.x).toBe(10); expect(rect.y).toBe(23); expect(rect.width).toBe(42); expect(rect.height).toBe(0); }); it('should correct all negative values', () => { const rect = { x: -10, y: -23, width: -42, height: -57 }; instance._correctDimensions(300, 200, rect); expect(rect.x).toBe(0); expect(rect.y).toBe(0); expect(rect.width).toBe(0); expect(rect.height).toBe(0); }); }); it.describe('Dimensions', () => { it('should correct too big x values', () => { const rect = { x: 1000, y: 23, width: 42, height: 57 }; instance._correctDimensions(300, 200, rect); expect(rect.x).toBe(299); expect(rect.y).toBe(23); expect(rect.width).toBe(1); expect(rect.height).toBe(57); }); it('should correct too big y values', () => { const rect = { x: 10, y: 2300, width: 42, height: 57 }; instance._correctDimensions(300, 200, rect); expect(rect.x).toBe(10); expect(rect.y).toBe(199); expect(rect.width).toBe(42); expect(rect.height).toBe(1); }); it('should correct too big width values', () => { const rect = { x: 11, y: 23, width: 4200, height: 57 }; instance._correctDimensions(300, 200, rect); expect(rect.x).toBe(11); expect(rect.y).toBe(23); expect(rect.width).toBe(289); expect(rect.height).toBe(57); }); it('should correct too big height values', () => { const rect = { x: 11, y: 23, width: 42, height: 5700 }; instance._correctDimensions(300, 200, rect); expect(rect.x).toBe(11); expect(rect.y).toBe(23); expect(rect.width).toBe(42); expect(rect.height).toBe(177); }); it('should correct too big width and height values', () => { const rect = { x: 11, y: 23, width: 420, height: 570 }; instance._correctDimensions(300, 200, rect); expect(rect.x).toBe(11); expect(rect.y).toBe(23); expect(rect.width).toBe(289); expect(rect.height).toBe(177); }); }); it.describe('Border Dimensions', () => { it('should correct too big x values', () => { const rect = { x: 300, y: 23, width: 42, height: 57 }; instance._correctDimensions(300, 200, rect); expect(rect.x).toBe(299); expect(rect.y).toBe(23); expect(rect.width).toBe(1); expect(rect.height).toBe(57); }); it('should correct too big y values', () => { const rect = { x: 10, y: 200, width: 42, height: 57 }; instance._correctDimensions(300, 200, rect); expect(rect.x).toBe(10); expect(rect.y).toBe(199); expect(rect.width).toBe(42); expect(rect.height).toBe(1); }); it('should correct too big width values', () => { const rect = { x: 11, y: 23, width: 289, height: 57 }; instance._correctDimensions(300, 200, rect); expect(rect.x).toBe(11); expect(rect.y).toBe(23); expect(rect.width).toBe(289); expect(rect.height).toBe(57); }); it('should correct too big height values', () => { const rect = { x: 11, y: 23, width: 42, height: 177 }; instance._correctDimensions(300, 200, rect); expect(rect.x).toBe(11); expect(rect.y).toBe(23); expect(rect.width).toBe(42); expect(rect.height).toBe(177); }); }); }); it.describe('_crop', () => { /** @type {PNGImage} */ let croppedImage; /** @type {PNGImage} */ let expectedImage; it.beforeEach(() => { croppedImage = generateImage('medium-1'); expectedImage = generateImage('medium-1'); }); it('should crop image', () => { instance._crop('Medium-1', croppedImage, { x: 1, y: 2, width: 2, height: 1 }); expect(croppedImage.getWidth()).toBe(2); expect(croppedImage.getHeight()).toBe(1); expect(croppedImage.getAt(0, 0)).toBe(expectedImage.getAt(1, 2)); expect(croppedImage.getAt(1, 0)).toBe(expectedImage.getAt(2, 2)); }); }); it.describe('_clip', () => { it('should clip the image small and medium', () => { const image1 = generateImage('small-1'), image2 = generateImage('medium-2'); instance._clip(image1, image2); expect(image1.getWidth()).toBe(image2.getWidth()); expect(image1.getHeight()).toBe(image2.getHeight()); }); it('should clip the image medium and small', () => { const image1 = generateImage('medium-1'), image2 = generateImage('small-2'); instance._clip(image1, image2); expect(image1.getWidth()).toBe(image2.getWidth()); expect(image1.getHeight()).toBe(image2.getHeight()); }); it('should clip the image slim-1 and medium', () => { const image1 = generateImage('slim-1'), image2 = generateImage('medium-1'); instance._clip(image1, image2); expect(image1.getWidth()).toBe(image2.getWidth()); expect(image1.getHeight()).toBe(image2.getHeight()); }); it('should clip the image slim-2 and medium', () => { const image1 = generateImage('slim-2'), image2 = generateImage('medium-1'); instance._clip(image1, image2); expect(image1.getWidth()).toBe(image2.getWidth()); expect(image1.getHeight()).toBe(image2.getHeight()); }); it('should clip the image small and small', () => { const image1 = generateImage('small-2'), image2 = generateImage('small-1'); instance._clip(image1, image2); expect(image1.getWidth()).toBe(image2.getWidth()); expect(image1.getHeight()).toBe(image2.getHeight()); }); }); it.describe('isAboveThreshold', () => { it.describe('Pixel threshold', () => { it.beforeEach(() => { instance._thresholdType = BlinkDiff.THRESHOLD_PIXEL; instance._threshold = 50; }); it('should be below threshold', () => { expect(instance.isAboveThreshold(49)).toBeFalsy(); }); it('should be above threshold on border', () => { expect(instance.isAboveThreshold(50)).toBeTruthy(); }); it('should be above threshold', () => { expect(instance.isAboveThreshold(51)).toBeTruthy(); }); }); it.describe('Percent threshold', () => { it.beforeEach(() => { instance._thresholdType = BlinkDiff.THRESHOLD_PERCENT; instance._threshold = 0.1; }); it('should be below threshold', () => { expect(instance.isAboveThreshold(9, 100)).toBeFalsy(); }); it('should be above threshold on border', () => { expect(instance.isAboveThreshold(10, 100)).toBeTruthy(); }); it('should be above threshold', () => { expect(instance.isAboveThreshold(11, 100)).toBeTruthy(); }); }); }); it.describe('Comparison', () => { /** @type {PNGImage} */ let image1; /** @type {PNGImage} */ let image2; /** @type {PNGImage} */ let image3; /** @type {PNGImage} */ let image4; /** @type {{ red: number, green: number, blue: number, alpha: number }} */ let maskColor; /** @type {{ red: number, green: number, blue: number, alpha: number }} */ let shiftColor; /** @type {{ red: number, green: number, blue: number, alpha: number }} */ let backgroundMaskColor; it.beforeEach(() => { image1 = generateImage('small-1'); image2 = generateImage('small-2'); image3 = generateImage('small-3'); image4 = generateImage('small-1'); maskColor = { red: 123, green: 124, blue: 125, alpha: 126 }; shiftColor = { red: 200, green: 100, blue: 0, alpha: 113 }; backgroundMaskColor = { red: 31, green: 33, blue: 35, alpha: 37 }; }); it.describe('_pixelCompare', () => { it('should have no differences with a zero dimension', () => { const deltaThreshold = 10, width = 0, height = 0, hShift = 0, vShift = 0; const result = instance._pixelCompare(image1, image2, image3, deltaThreshold, width, height, maskColor, shiftColor, backgroundMaskColor, hShift, vShift); expect(result).toBe(0); }); it('should have all differences', () => { const deltaThreshold = 10, width = 2, height = 2, hShift = 0, vShift = 0; const result = instance._pixelCompare(image1, image2, image3, deltaThreshold, width, height, maskColor, shiftColor, backgroundMaskColor, hShift, vShift); expect(result).toBe(4); }); it('should have some differences', () => { const deltaThreshold = 100, width = 2, height = 2, hShift = 0, vShift = 0; const result = instance._pixelCompare(image1, image2, image3, deltaThreshold, width, height, maskColor, shiftColor, backgroundMaskColor, hShift, vShift); expect(result).toBe(2); }); }); it.describe('_compare', () => { it.beforeEach(() => { instance._thresholdType = BlinkDiff.THRESHOLD_PIXEL; instance._threshold = 3; }); it('should be different', () => { const deltaThreshold = 10, hShift = 0, vShift = 0; const result = instance._compare(image1, image2, image3, deltaThreshold, maskColor, shiftColor, backgroundMaskColor, hShift, vShift); expect(result).toEqual({ code: BlinkDiff.RESULT_DIFFERENT, differences: 4, dimension: 4, width: 2, height: 2 }); }); it('should be similar', () => { const deltaThreshold = 100, hShift = 0, vShift = 0; const result = instance._compare(image1, image2, image3, deltaThreshold, maskColor, shiftColor, backgroundMaskColor, hShift, vShift); expect(result).toEqual({ code: BlinkDiff.RESULT_SIMILAR, differences: 2, dimension: 4, width: 2, height: 2 }); }); it('should be identical', () => { const deltaThreshold = 10, hShift = 0, vShift = 0; const result = instance._compare(image1, image4, image3, deltaThreshold, maskColor, shiftColor, backgroundMaskColor, hShift, vShift); expect(result).toEqual({ code: BlinkDiff.RESULT_IDENTICAL, differences: 0, dimension: 4, width: 2, height: 2 }); }); }); }); it.describe('Run', () => { it.beforeEach(() => { instance._imageA = generateImage('small-1'); instance._imageB = generateImage('medium-1'); instance._thresholdType = BlinkDiff.THRESHOLD_PIXEL; instance._threshold = 3; instance._composition = false; }); it('should crop image-a', async () => { instance._cropImageA = { width: 1, height: 2 }; console.log('A'); const result = instance.runSync(); expect(result.dimension).toBe(2); }); it('should crop image-b', async () => { instance._cropImageB = { width: 1, height: 1 }; const result = instance.runSync(); expect(result.dimension).toBe(1); }); it('should clip image-b', async () => { const result = instance.runSync(); expect(result.dimension).toBe(4); }); it('should crop and clip images', async () => { instance._cropImageA = { width: 1, height: 2 }; instance._cropImageB = { width: 1, height: 1 }; const result = instance.runSync(); expect(result.dimension).toBe(1); }); it('should write output file', async ({}, testInfo) => { instance._imageOutputPath = testInfo.outputPath('tmp.png'); instance.runSync(); if (!fs.existsSync(testInfo.outputPath('tmp.png'))) throw new Error('Could not write file.'); }); it('should compare image-a to image-b', async () => { const result = instance.runSync(); expect(result.code).toBe(BlinkDiff.RESULT_DIFFERENT); }); it('should be black', async () => { instance._delta = 1000; instance._copyImageAToOutput = false; instance._copyImageBToOutput = false; instance._outputBackgroundRed = 0; instance._outputBackgroundGreen = 0; instance._outputBackgroundBlue = 0; instance._outputBackgroundAlpha = 0; instance._outputBackgroundOpacity = undefined; instance.runSync(); expect(instance._imageOutput.getAt(0, 0)).toBe(0); }); it('should copy image-a to output by default', async () => { instance._delta = 1000; instance._outputBackgroundRed = undefined; instance._outputBackgroundGreen = undefined; instance._outputBackgroundBlue = undefined; instance._outputBackgroundAlpha = undefined; instance._outputBackgroundOpacity = undefined; instance.runSync(); expect(instance._imageOutput.getAt(0, 0)).toBe(instance._imageA.getAt(0, 0)); }); it('should copy image-a to output', async () => { instance._delta = 1000; instance._copyImageAToOutput = true; instance._copyImageBToOutput = false; instance._outputBackgroundRed = undefined; instance._outputBackgroundGreen = undefined; instance._outputBackgroundBlue = undefined; instance._outputBackgroundAlpha = undefined; instance._outputBackgroundOpacity = undefined; instance.runSync(); expect(instance._imageOutput.getAt(0, 0)).toBe(instance._imageA.getAt(0, 0)); }); it('should copy image-b to output', async () => { instance._delta = 1000; instance._copyImageAToOutput = false; instance._copyImageBToOutput = true; instance._outputBackgroundRed = undefined; instance._outputBackgroundGreen = undefined; instance._outputBackgroundBlue = undefined; instance._outputBackgroundAlpha = undefined; instance._outputBackgroundOpacity = undefined; instance.runSync(); expect(instance._imageOutput.getAt(0, 0)).toBe(instance._imageB.getAt(0, 0)); }); }); it.describe('Color-Conversion', () => { it('should convert RGB to XYZ', () => { const color = /** @type {any} */ (instance._convertRgbToXyz({ c1: 92 / 255, c2: 255 / 255, c3: 162 / 255, c4: 1 })); expect(color.c1).toBeCloseTo(0.6144431682352941, 0.0001); expect(color.c2).toBeCloseTo(0.8834245847058824, 0.0001); expect(color.c3).toBeCloseTo(0.6390158682352941, 0.0001); expect(color.c4).toBeCloseTo(1, 0.0001); }); it('should convert Xyz to CIELab', () => { const color = /** @type {any} */ (instance._convertXyzToCieLab({ c1: 0.6144431682352941, c2: 0.8834245847058824, c3: 0.6390158682352941, c4: 1 })); expect(color.c1).toBeCloseTo(95.30495102757038, 0.0001); expect(color.c2).toBeCloseTo(-54.68933740774734, 0.0001); expect(color.c3).toBeCloseTo(19.63870174748623, 0.0001); expect(color.c4).toBeCloseTo(1, 0.0001); }); }); }); });