Files
MeshChatX/tests/frontend/StickerView.test.js

198 lines
6.1 KiB
JavaScript

import { mount, flushPromises } from "@vue/test-utils";
import { describe, it, expect, vi, beforeAll, afterEach } from "vitest";
import StickerView from "@/components/stickers/StickerView.vue";
const origIntersectionObserver = globalThis.IntersectionObserver;
beforeAll(() => {
const ctx = {
fillStyle: "",
strokeStyle: "",
fillRect: vi.fn(),
clearRect: vi.fn(),
save: vi.fn(),
restore: vi.fn(),
translate: vi.fn(),
scale: vi.fn(),
rotate: vi.fn(),
beginPath: vi.fn(),
closePath: vi.fn(),
moveTo: vi.fn(),
lineTo: vi.fn(),
arc: vi.fn(),
fill: vi.fn(),
stroke: vi.fn(),
setTransform: vi.fn(),
drawImage: vi.fn(),
measureText: vi.fn(() => ({ width: 0 })),
createLinearGradient: vi.fn(() => ({ addColorStop: vi.fn() })),
createRadialGradient: vi.fn(() => ({ addColorStop: vi.fn() })),
};
HTMLCanvasElement.prototype.getContext = vi.fn(() => ctx);
});
describe("StickerView.vue", () => {
it("renders img for static sticker", () => {
const w = mount(StickerView, {
props: {
src: "https://example.invalid/sticker.png",
imageType: "png",
alt: "x",
},
});
expect(w.find("img").exists()).toBe(true);
expect(w.find("video").exists()).toBe(false);
w.unmount();
});
it("renders video for webm", () => {
const w = mount(StickerView, {
props: {
src: "https://example.invalid/s.webm",
imageType: "webm",
},
});
expect(w.find("video").exists()).toBe(true);
w.unmount();
});
it("TGS does not fetch until intersection reports in view", async () => {
let ioCallback;
class MockIntersectionObserver {
constructor(cb) {
ioCallback = cb;
}
observe = vi.fn();
disconnect = vi.fn();
}
globalThis.IntersectionObserver = MockIntersectionObserver;
const baseFetch = globalThis.fetch;
const fetchSpy = vi.spyOn(globalThis, "fetch").mockImplementation(async (input, init) => {
const reqUrl = typeof input === "string" ? input : (input?.url ?? "");
if (reqUrl.includes("example.invalid/a.tgs")) {
return {
arrayBuffer: () => Promise.resolve(new ArrayBuffer(8)),
};
}
return baseFetch(input, init);
});
const tgsCallCount = () =>
fetchSpy.mock.calls.filter((c) => {
const u = typeof c[0] === "string" ? c[0] : (c[0]?.url ?? "");
return String(u).includes("example.invalid/a.tgs");
}).length;
const w = mount(StickerView, {
props: {
src: "https://example.invalid/a.tgs",
imageType: "tgs",
},
attachTo: document.body,
});
await flushPromises();
expect(tgsCallCount()).toBe(0);
const root = w.vm.$refs.stickerRoot;
expect(root).toBeTruthy();
ioCallback([{ isIntersecting: true, target: root }]);
await w.vm.$nextTick();
await w.vm.$nextTick();
expect(w.vm.inView).toBe(true);
expect(w.vm.$refs.lottieMount).toBeTruthy();
await vi.waitFor(() => {
expect(tgsCallCount()).toBeGreaterThan(0);
});
fetchSpy.mockRestore();
w.unmount();
});
it("WebM calls play when in view and pause when out", async () => {
let ioCallback;
class MockIntersectionObserver {
constructor(cb) {
ioCallback = cb;
}
observe = vi.fn();
disconnect = vi.fn();
}
globalThis.IntersectionObserver = MockIntersectionObserver;
const playSpy = vi.spyOn(HTMLVideoElement.prototype, "play").mockResolvedValue(undefined);
const pauseSpy = vi.spyOn(HTMLVideoElement.prototype, "pause").mockImplementation(() => {});
const w = mount(StickerView, {
props: {
src: "https://example.invalid/s.webm",
imageType: "webm",
},
attachTo: document.body,
});
await flushPromises();
const root = w.vm.$refs.stickerRoot;
ioCallback([{ isIntersecting: true, target: root }]);
await flushPromises();
expect(playSpy).toHaveBeenCalled();
ioCallback([{ isIntersecting: false, target: root }]);
await flushPromises();
expect(pauseSpy).toHaveBeenCalled();
playSpy.mockRestore();
pauseSpy.mockRestore();
w.unmount();
});
it("mountLottie fetches and decodes gzip TGS payload", async () => {
const { gzipSync } = await import("node:zlib");
const payload = JSON.stringify({
v: "5.5.7",
fr: 30,
ip: 0,
op: 30,
w: 512,
h: 512,
nm: "t",
ddd: 0,
assets: [],
layers: [],
});
const gz = gzipSync(Buffer.from(payload, "utf8"));
const ab = gz.buffer.slice(gz.byteOffset, gz.byteOffset + gz.byteLength);
const fetchMock = vi.fn(() =>
Promise.resolve({
arrayBuffer: () => Promise.resolve(ab),
})
);
vi.stubGlobal("fetch", fetchMock);
const w = mount(StickerView, {
props: {
src: "https://example.invalid/a.tgs",
imageType: "tgs",
},
attachTo: document.body,
});
await w.vm.$nextTick();
expect(w.vm.$refs.lottieMount).toBeTruthy();
w.vm.inView = true;
await w.vm.mountLottie();
await flushPromises();
expect(fetchMock).toHaveBeenCalled();
expect(w.vm.lottieAnim).toBeTruthy();
w.unmount();
vi.unstubAllGlobals();
});
afterEach(() => {
globalThis.IntersectionObserver = origIntersectionObserver;
vi.restoreAllMocks();
});
});