mirror of
https://git.quad4.io/RNS-Things/MeshChatX.git
synced 2026-04-25 17:32:09 +00:00
198 lines
6.1 KiB
JavaScript
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();
|
|
});
|
|
});
|