@shopify/react-native-skia
Version:
High-performance React Native Graphics using Skia
130 lines (120 loc) • 3.87 kB
text/typescript
import path from "path";
import fs from "fs";
import { PNG } from "pngjs";
import pixelmatch from "pixelmatch";
import { diff } from "jest-diff";
import type { SkSurface, SkImage } from "../skia/types";
export const E2E = process.env.E2E === "true";
export const CI = process.env.CI === "true";
export const itFailsE2e = E2E ? it.failing : it;
export const itRunsE2eOnly = E2E ? it : it.skip;
export const itRunsNodeOnly = E2E ? it.skip : it;
export const itRunsCIAndNodeOnly = CI || !E2E ? it : it.skip;
export const docPath = (relPath: string) =>
path.resolve(process.cwd(), `../../apps/docs/static/img/${relPath}`);
export const processResult = (
surface: SkSurface,
relPath: string,
overwrite = false
) => {
surface.flush();
const image = surface.makeImageSnapshot();
surface.getCanvas().clear(Float32Array.of(0, 0, 0, 0));
const result = checkImage(image, relPath, { overwrite });
image.dispose();
return result;
};
interface CheckImageOptions {
maxPixelDiff?: number;
threshold?: number;
overwrite?: boolean;
mute?: boolean;
shouldFail?: boolean;
}
// On Github Action, the image decoding is slightly different
// all tests that show the oslo.jpg have small differences but look ok
const defaultCheckImageOptions = {
maxPixelDiff: 200,
threshold: 0.1,
overwrite: false,
mute: false,
shouldFail: false,
};
export const checkImage = (
image: SkImage,
relPath: string,
opts?: CheckImageOptions
) => {
const options = { ...defaultCheckImageOptions, ...opts };
const { overwrite, threshold, mute, maxPixelDiff, shouldFail } = options;
const png = image.encodeToBytes();
const p = path.resolve(__dirname, relPath);
if (fs.existsSync(p) && !overwrite) {
const ref = fs.readFileSync(p);
const baseline = PNG.sync.read(ref);
const toTest = PNG.sync.read(Buffer.from(image.encodeToBytes()));
const diffImage = new PNG({
width: baseline.width,
height: baseline.height,
});
if (baseline.width !== toTest.width || baseline.height !== toTest.height) {
throw new Error(
`Image sizes don't match: ${baseline.width}x${baseline.height} vs ${toTest.width}x${toTest.height}`
);
}
const diffPixelsCount = pixelmatch(
baseline.data,
toTest.data,
diffImage.data,
baseline.width,
baseline.height,
{ threshold }
);
if (!mute) {
if (diffPixelsCount > maxPixelDiff && !shouldFail) {
console.log(`${p} didn't match`);
fs.writeFileSync(`${p}.test.png`, PNG.sync.write(toTest));
fs.writeFileSync(`${p}-diff-test.png`, PNG.sync.write(diffImage));
}
if (shouldFail) {
expect(diffPixelsCount).not.toBeLessThanOrEqual(maxPixelDiff);
} else {
expect(diffPixelsCount).toBeLessThanOrEqual(maxPixelDiff);
}
}
return diffPixelsCount;
} else {
fs.writeFileSync(p, png);
}
return 0;
};
expect.extend({
toBeApproximatelyEqual(_received, _argument, tolerance = 0.1) {
const received =
Array.isArray(_received) || _received instanceof Float32Array
? _received
: [_received];
const argument =
Array.isArray(_argument) || _received instanceof Float32Array
? _argument
: [_argument];
if (received.length !== argument.length) {
return { pass: false, message: () => "Arrays have different lengths" };
}
for (let i = 0; i < received.length; i++) {
if (
isNaN(argument[i]) ||
isNaN(received[i]) ||
Math.abs(received[i] - argument[i]) > tolerance
) {
const diffString = diff(received, argument);
return {
pass: false,
message: () => `Element at index ${i} differ more than ${tolerance}:
${diffString}`,
};
}
}
return { pass: true, message: () => "Arrays are approximately equal" };
},
});