Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 | 14x 46x 46x 46x 14x 14x 14x 11x 14x 2x 1x 11x 11x 46x 46x 16x 11x 11x 46x 46x 46x 46x 46x 5x 5x 5x 5x 5x 5x 5x | import { vi } from "vitest";
/**
* jsdom ships no canvas implementation, so `getContext("2d")` returns null and
* every drawing path silently no-ops. These doubles give the React tests a
* context that records what was asked of it, which is what makes assertions
* like "drawn exactly once per change" possible at all.
*/
export class RecordingPath2D {
constructor(readonly d: string) {}
}
export interface CanvasRecorder {
/** The `d` string of every path filled, in order. */
fills: string[];
/** Every `canvas.width` assignment — each one clears the backing store. */
resizes: number[];
/** Undo the doubles. */
restore(): void;
}
/**
* Install a fake 2D context on `HTMLCanvasElement` plus a recording `Path2D`.
* Returns the recorder; call `restore()` to undo.
*/
export function installCanvasDouble(): CanvasRecorder {
const recorder = { fills: [] as string[], resizes: [] as number[] };
const contexts = new WeakMap<HTMLCanvasElement, CanvasRenderingContext2D>();
const getContext = vi
.spyOn(HTMLCanvasElement.prototype, "getContext")
.mockImplementation(function (this: HTMLCanvasElement, kind: string) {
Iif (kind !== "2d") return null;
const existing = contexts.get(this);
if (existing) return existing;
const context = {
canvas: this,
fillStyle: "",
setTransform: () => {},
clearRect: () => {},
fillRect: () => {},
fill: (path: RecordingPath2D) => {
recorder.fills.push(path.d);
},
save: () => {},
restore: () => {},
beginPath: () => {},
arc: () => {},
clip: () => {},
drawImage: () => {
recorder.fills.push("image");
},
createLinearGradient: () => ({ addColorStop: () => {} }),
createRadialGradient: () => ({ addColorStop: () => {} }),
} as unknown as CanvasRenderingContext2D;
contexts.set(this, context);
return context;
} as never);
// jsdom implements width/height as reflected attributes; wrapping them lets
// the tests see the resize that would wipe a real canvas.
const descriptor = Object.getOwnPropertyDescriptor(HTMLCanvasElement.prototype, "width")!;
Object.defineProperty(HTMLCanvasElement.prototype, "width", {
configurable: true,
get(this: HTMLCanvasElement) {
return descriptor.get!.call(this);
},
set(this: HTMLCanvasElement, value: number) {
recorder.resizes.push(value);
descriptor.set!.call(this, value);
},
});
vi.stubGlobal("Path2D", RecordingPath2D);
return {
fills: recorder.fills,
resizes: recorder.resizes,
restore() {
getContext.mockRestore();
Object.defineProperty(HTMLCanvasElement.prototype, "width", descriptor);
vi.unstubAllGlobals();
},
};
}
/** An `Image` double that resolves (or fails) on the next microtask. */
export function installImageDouble(shouldFail = false): void {
class FakeImage {
onload: (() => void) | null = null;
onerror: (() => void) | null = null;
crossOrigin: string | null = null;
#src = "";
set src(value: string) {
this.#src = value;
queueMicrotask(() => (shouldFail ? this.onerror?.() : this.onload?.()));
}
get src(): string {
return this.#src;
}
}
vi.stubGlobal("Image", FakeImage);
}
|