diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index a721d7fb..36a14494 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -37,6 +37,7 @@ jobs:
env:
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
+ VITE_KLIPY_API_KEY: ${{ secrets.VITE_KLIPY_API_KEY }}
run: npm run tauri build
- name: Stage Windows release assets
diff --git a/Client/tauri-client/.gitignore b/Client/tauri-client/.gitignore
index 1ed2749a..13cbd141 100644
--- a/Client/tauri-client/.gitignore
+++ b/Client/tauri-client/.gitignore
@@ -7,4 +7,5 @@ src-tauri/gen/
coverage/
playwright-report/
test-results/
-~/
\ No newline at end of file
+~/
+.env
\ No newline at end of file
diff --git a/Client/tauri-client/src/assets/KLIPY Light with logo.svg b/Client/tauri-client/src/assets/KLIPY Light with logo.svg
new file mode 100644
index 00000000..756df803
--- /dev/null
+++ b/Client/tauri-client/src/assets/KLIPY Light with logo.svg
@@ -0,0 +1,76 @@
+
+
+
diff --git a/Client/tauri-client/src/components/GifPicker.ts b/Client/tauri-client/src/components/GifPicker.ts
index 5538b6c3..f73445e7 100644
--- a/Client/tauri-client/src/components/GifPicker.ts
+++ b/Client/tauri-client/src/components/GifPicker.ts
@@ -1,9 +1,9 @@
-// GifPicker — searchable GIF selector powered by Tenor API.
+// GifPicker — searchable GIF selector powered by Klipy API.
// Uses @lib/dom helpers exclusively. Never sets innerHTML with user content.
import { createElement, setText, clearChildren } from "@lib/dom";
-import { searchGifs, getTrendingGifs } from "@lib/tenor";
-import type { TenorGif } from "@lib/tenor";
+import { searchGifs, getTrendingGifs } from "@lib/gifProvider";
+import type { GifResult } from "@lib/gifProvider";
// ---------------------------------------------------------------------------
// Types
@@ -43,13 +43,13 @@ export function createGifPicker(options: GifPickerOptions): {
const searchInput = createElement("input", {
class: "gp-search",
type: "text",
- placeholder: "Search Tenor",
+ placeholder: "Search Klipy",
});
header.appendChild(searchInput);
// Attribution
const attribution = createElement("div", { class: "gp-attribution" });
- setText(attribution, "Powered by Tenor");
+ setText(attribution, "Powered by Klipy");
header.appendChild(attribution);
root.appendChild(header);
@@ -68,7 +68,7 @@ export function createGifPicker(options: GifPickerOptions): {
// ── Rendering ──
- function renderGifs(gifs: readonly TenorGif[]): void {
+ function renderGifs(gifs: readonly GifResult[]): void {
clearChildren(gridArea);
if (gifs.length === 0) {
diff --git a/Client/tauri-client/src/components/message-list/media.ts b/Client/tauri-client/src/components/message-list/media.ts
index 4ced9824..5b6f774a 100644
--- a/Client/tauri-client/src/components/message-list/media.ts
+++ b/Client/tauri-client/src/components/message-list/media.ts
@@ -5,6 +5,7 @@
import { createElement, setText, appendChildren } from "@lib/dom";
import { createIcon } from "@lib/icons";
+import klipyWatermark from "../../assets/KLIPY Light with logo.svg";
import { createLogger } from "@lib/logger";
import { observeMedia } from "@lib/media-visibility";
import { loadPref } from "@components/settings/helpers";
@@ -34,6 +35,16 @@ function cacheImageHeight(url: string, h: number): void {
imageHeightCache.set(url, h);
}
+/** Check if a URL originates from the Klipy CDN. */
+function isKlipyUrl(url: string): boolean {
+ try {
+ const { hostname } = new URL(url);
+ return hostname === "klipy.com" || hostname.endsWith(".klipy.com");
+ } catch {
+ return false;
+ }
+}
+
/** Check if a URL points to an animated GIF. */
function isGifUrl(url: string): boolean {
try {
@@ -289,6 +300,17 @@ export function renderInlineImage(url: string): HTMLDivElement {
});
wrap.appendChild(img);
+
+ if (isKlipyUrl(url)) {
+ const watermark = createElement("img", {
+ class: "klipy-watermark",
+ src: klipyWatermark,
+ alt: "",
+ "aria-hidden": "true",
+ });
+ wrap.appendChild(watermark);
+ }
+
return wrap;
}
diff --git a/Client/tauri-client/src/lib/gifProvider.ts b/Client/tauri-client/src/lib/gifProvider.ts
new file mode 100644
index 00000000..82f54a28
--- /dev/null
+++ b/Client/tauri-client/src/lib/gifProvider.ts
@@ -0,0 +1,115 @@
+// Klipy GIF API client — provides GIF search and trending.
+// Drop-in replacement for Tenor (EOL June 30, 2026).
+// Register at partner.klipy.com to get a production API key.
+// Override via VITE_KLIPY_API_KEY at build time.
+const KLIPY_API_KEY = import.meta.env.VITE_KLIPY_API_KEY ?? "";
+const GIF_API_BASE = "https://api.klipy.com/v2";
+const DEFAULT_LIMIT = 20;
+
+// ---------------------------------------------------------------------------
+// Types
+// ---------------------------------------------------------------------------
+
+export interface GifResult {
+ readonly id: string;
+ readonly title: string;
+ /** tinygif URL for preview thumbnails */
+ readonly url: string;
+ /** Full-size gif URL for sending */
+ readonly fullUrl: string;
+}
+
+interface GifMediaFormat {
+ readonly url: string;
+}
+
+interface GifApiResult {
+ readonly id: string;
+ readonly title: string;
+ readonly media_formats: {
+ readonly tinygif?: GifMediaFormat;
+ readonly gif?: GifMediaFormat;
+ };
+}
+
+interface GifSearchResponse {
+ readonly results: readonly GifApiResult[];
+}
+
+// ---------------------------------------------------------------------------
+// Helpers
+// ---------------------------------------------------------------------------
+
+/** Validate that a URL originates from a trusted Klipy CDN domain. */
+function isAllowedGifUrl(url: string): boolean {
+ try {
+ const parsed = new URL(url);
+ return (
+ parsed.protocol === "https:" &&
+ (parsed.hostname === "klipy.com" || parsed.hostname.endsWith(".klipy.com"))
+ );
+ } catch {
+ return false;
+ }
+}
+
+function parseResults(data: GifSearchResponse): readonly GifResult[] {
+ return data.results
+ .filter((r) => {
+ const tinyUrl = r.media_formats.tinygif?.url ?? "";
+ const gifUrl = r.media_formats.gif?.url ?? "";
+ return tinyUrl && gifUrl && isAllowedGifUrl(tinyUrl) && isAllowedGifUrl(gifUrl);
+ })
+ .map((r) => ({
+ id: r.id,
+ title: r.title,
+ url: r.media_formats.tinygif!.url,
+ fullUrl: r.media_formats.gif!.url,
+ }));
+}
+
+// ---------------------------------------------------------------------------
+// Public API
+// ---------------------------------------------------------------------------
+
+/**
+ * Search Klipy for GIFs matching the given query.
+ */
+export async function searchGifs(
+ query: string,
+ limit: number = DEFAULT_LIMIT,
+): Promise {
+ const params = new URLSearchParams({
+ q: query,
+ key: KLIPY_API_KEY,
+ limit: String(limit),
+ media_filter: "gif,tinygif",
+ });
+
+ const res = await fetch(`${GIF_API_BASE}/search?${params.toString()}`);
+ if (!res.ok) {
+ throw new Error(`GIF search failed: ${res.status} ${res.statusText}`);
+ }
+ const data: GifSearchResponse = await res.json();
+ return parseResults(data);
+}
+
+/**
+ * Fetch currently trending GIFs from Klipy.
+ */
+export async function getTrendingGifs(
+ limit: number = DEFAULT_LIMIT,
+): Promise {
+ const params = new URLSearchParams({
+ key: KLIPY_API_KEY,
+ limit: String(limit),
+ media_filter: "gif,tinygif",
+ });
+
+ const res = await fetch(`${GIF_API_BASE}/featured?${params.toString()}`);
+ if (!res.ok) {
+ throw new Error(`GIF trending failed: ${res.status} ${res.statusText}`);
+ }
+ const data: GifSearchResponse = await res.json();
+ return parseResults(data);
+}
diff --git a/Client/tauri-client/src/lib/tenor.ts b/Client/tauri-client/src/lib/tenor.ts
deleted file mode 100644
index 3b6b453c..00000000
--- a/Client/tauri-client/src/lib/tenor.ts
+++ /dev/null
@@ -1,122 +0,0 @@
-// Tenor API v2 client — provides GIF search and trending.
-// Uses the anonymous test key for development.
-
-// Tenor API key — defaults to Google's public anonymous test key from
-// https://developers.google.com/tenor/guides/quickstart
-// This key is intentionally public (Google's demo key, not a secret).
-// Override via VITE_TENOR_API_KEY at build time for production use.
-const TENOR_API_KEY =
- // codeql[js/hardcoded-credentials] -- Google's public anonymous demo key, not a secret
- import.meta.env.VITE_TENOR_API_KEY ?? "AIzaSyAyimkuYQYF_FXVALexPuGQctUWRURdCYQ";
-const TENOR_BASE = "https://tenor.googleapis.com/v2";
-const DEFAULT_LIMIT = 20;
-
-// ---------------------------------------------------------------------------
-// Types
-// ---------------------------------------------------------------------------
-
-export interface TenorGif {
- readonly id: string;
- readonly title: string;
- /** tinygif URL for preview thumbnails */
- readonly url: string;
- /** Full-size gif URL for sending */
- readonly fullUrl: string;
-}
-
-interface TenorMediaFormat {
- readonly url: string;
-}
-
-interface TenorResult {
- readonly id: string;
- readonly title: string;
- readonly media_formats: {
- readonly tinygif?: TenorMediaFormat;
- readonly gif?: TenorMediaFormat;
- };
-}
-
-interface TenorResponse {
- readonly results: readonly TenorResult[];
-}
-
-// ---------------------------------------------------------------------------
-// Helpers
-// ---------------------------------------------------------------------------
-
-/** Trusted Tenor CDN origins for URL validation. */
-const TENOR_ALLOWED_ORIGINS = new Set([
- "https://media.tenor.com",
- "https://c.tenor.com",
- "https://media1.tenor.com",
-]);
-
-/** Validate that a URL originates from a trusted Tenor domain. */
-function isTenorUrl(url: string): boolean {
- try {
- const parsed = new URL(url);
- return TENOR_ALLOWED_ORIGINS.has(parsed.origin) && parsed.protocol === "https:";
- } catch {
- return false;
- }
-}
-
-function parseResults(data: TenorResponse): readonly TenorGif[] {
- return data.results
- .filter((r) => {
- const tinyUrl = r.media_formats.tinygif?.url ?? "";
- const gifUrl = r.media_formats.gif?.url ?? "";
- return tinyUrl && gifUrl && isTenorUrl(tinyUrl) && isTenorUrl(gifUrl);
- })
- .map((r) => ({
- id: r.id,
- title: r.title,
- url: r.media_formats.tinygif!.url,
- fullUrl: r.media_formats.gif!.url,
- }));
-}
-
-// ---------------------------------------------------------------------------
-// Public API
-// ---------------------------------------------------------------------------
-
-/**
- * Search Tenor for GIFs matching the given query.
- */
-export async function searchGifs(
- query: string,
- limit: number = DEFAULT_LIMIT,
-): Promise {
- const params = new URLSearchParams({
- q: query,
- key: TENOR_API_KEY,
- limit: String(limit),
- media_filter: "gif,tinygif",
- });
-
- const res = await fetch(`${TENOR_BASE}/search?${params.toString()}`);
- if (!res.ok) {
- throw new Error(`Tenor search failed: ${res.status} ${res.statusText}`);
- }
- const data: TenorResponse = await res.json();
- return parseResults(data);
-}
-
-/**
- * Fetch currently trending GIFs from Tenor.
- */
-export async function getTrendingGifs(limit: number = DEFAULT_LIMIT): Promise {
- const params = new URLSearchParams({
- key: TENOR_API_KEY,
- limit: String(limit),
- media_filter: "gif,tinygif",
- });
-
- const res = await fetch(`${TENOR_BASE}/featured?${params.toString()}`);
- if (!res.ok) {
- throw new Error(`Tenor trending failed: ${res.status} ${res.statusText}`);
- }
- const data: TenorResponse = await res.json();
- return parseResults(data);
-}
diff --git a/Client/tauri-client/src/styles/app.css b/Client/tauri-client/src/styles/app.css
index d42fda92..f35e55f0 100644
--- a/Client/tauri-client/src/styles/app.css
+++ b/Client/tauri-client/src/styles/app.css
@@ -642,7 +642,7 @@
/* Image attachment */
.msg-image {
- margin-top: 4px; max-width: 550px; border-radius: var(--radius-md);
+ position: relative; margin-top: 4px; max-width: 550px; border-radius: var(--radius-md);
overflow: hidden; cursor: pointer; contain: layout style;
}
.msg-image img {
@@ -1106,10 +1106,15 @@
gap: 6px;
}
.gp-item {
- cursor: pointer; border-radius: var(--radius-sm);
+ position: relative; cursor: pointer; border-radius: var(--radius-sm);
overflow: hidden; transition: transform .1s;
}
.gp-item:hover { transform: scale(1.03); }
+.klipy-watermark {
+ position: absolute; bottom: 6px; left: 6px;
+ height: 18px; width: auto; pointer-events: none;
+ opacity: 0.9;
+}
.gp-img {
width: 100%; display: block; object-fit: cover;
border-radius: var(--radius-sm);
diff --git a/Client/tauri-client/tests/unit/gif-picker.test.ts b/Client/tauri-client/tests/unit/gif-picker.test.ts
index 1c24349d..0fc16fa3 100644
--- a/Client/tauri-client/tests/unit/gif-picker.test.ts
+++ b/Client/tauri-client/tests/unit/gif-picker.test.ts
@@ -1,36 +1,36 @@
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import { createGifPicker } from "@components/GifPicker";
import type { GifPickerOptions } from "@components/GifPicker";
-import type { TenorGif } from "@lib/tenor";
+import type { GifResult } from "@lib/gifProvider";
// ---------------------------------------------------------------------------
// Module mock — must be hoisted before imports in vitest
// ---------------------------------------------------------------------------
-vi.mock("@lib/tenor", () => ({
+vi.mock("@lib/gifProvider", () => ({
searchGifs: vi.fn(),
getTrendingGifs: vi.fn(),
}));
// Import the mocks so tests can control their return values
-import { searchGifs, getTrendingGifs } from "@lib/tenor";
+import { searchGifs, getTrendingGifs } from "@lib/gifProvider";
// ---------------------------------------------------------------------------
// Fixtures
// ---------------------------------------------------------------------------
-function makeGif(id: string): TenorGif {
+function makeGif(id: string): GifResult {
return {
id,
title: `GIF ${id}`,
- url: `https://media.tenor.com/preview/${id}.gif`,
- fullUrl: `https://media.tenor.com/full/${id}.gif`,
+ url: `https://media.klipy.com/preview/${id}.gif`,
+ fullUrl: `https://media.klipy.com/full/${id}.gif`,
};
}
-const TRENDING_GIFS: readonly TenorGif[] = [makeGif("t1"), makeGif("t2"), makeGif("t3")];
+const TRENDING_GIFS: readonly GifResult[] = [makeGif("t1"), makeGif("t2"), makeGif("t3")];
-const SEARCH_GIFS: readonly TenorGif[] = [makeGif("s1"), makeGif("s2")];
+const SEARCH_GIFS: readonly GifResult[] = [makeGif("s1"), makeGif("s2")];
// ---------------------------------------------------------------------------
// Helpers
@@ -90,7 +90,7 @@ describe("GifPicker", () => {
const { picker } = makePicker();
const attribution = picker.element.querySelector(".gp-attribution");
expect(attribution).not.toBeNull();
- expect(attribution!.textContent).toBe("Powered by Tenor");
+ expect(attribution!.textContent).toBe("Powered by Klipy");
picker.destroy();
});
});
@@ -103,7 +103,7 @@ describe("GifPicker", () => {
const input = picker.element.querySelector(".gp-search") as HTMLInputElement;
expect(input).not.toBeNull();
expect(input.tagName).toBe("INPUT");
- expect(input.placeholder).toBe("Search Tenor");
+ expect(input.placeholder).toBe("Search Klipy");
picker.destroy();
});
@@ -265,11 +265,11 @@ describe("GifPicker", () => {
});
it("img alt falls back to 'GIF' when title is empty", async () => {
- const gifNoTitle: TenorGif = {
+ const gifNoTitle: GifResult = {
id: "no-title",
title: "",
- url: "https://media.tenor.com/preview/no-title.gif",
- fullUrl: "https://media.tenor.com/full/no-title.gif",
+ url: "https://media.klipy.com/preview/no-title.gif",
+ fullUrl: "https://media.klipy.com/full/no-title.gif",
};
vi.mocked(getTrendingGifs).mockResolvedValue([gifNoTitle]);
diff --git a/Client/tauri-client/tests/unit/tenor.test.ts b/Client/tauri-client/tests/unit/gif-provider.test.ts
similarity index 80%
rename from Client/tauri-client/tests/unit/tenor.test.ts
rename to Client/tauri-client/tests/unit/gif-provider.test.ts
index 0891b4a3..67b398ef 100644
--- a/Client/tauri-client/tests/unit/tenor.test.ts
+++ b/Client/tauri-client/tests/unit/gif-provider.test.ts
@@ -1,8 +1,8 @@
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
-import { searchGifs, getTrendingGifs } from "../../src/lib/tenor";
+import { searchGifs, getTrendingGifs } from "../../src/lib/gifProvider";
// ---------------------------------------------------------------------------
-// Fetch mock — global fetch used by tenor.ts (no plugin wrapper)
+// Fetch mock — global fetch used by gifProvider.ts (no plugin wrapper)
// ---------------------------------------------------------------------------
const mockFetch = vi.fn();
@@ -20,7 +20,7 @@ afterEach(() => {
// Helpers
// ---------------------------------------------------------------------------
-function tenorResult(
+function gifResult(
id: string,
overrides: {
tinygif?: string | null;
@@ -29,8 +29,8 @@ function tenorResult(
} = {},
) {
const {
- tinygif = `https://media.tenor.com/${id}_tiny.gif`,
- gif = `https://media.tenor.com/${id}.gif`,
+ tinygif = `https://media.klipy.com/${id}_tiny.gif`,
+ gif = `https://media.klipy.com/${id}.gif`,
title = `Title ${id}`,
} = overrides;
@@ -75,10 +75,10 @@ function capturedUrl(): string {
describe("searchGifs", () => {
describe("URL construction", () => {
- it("calls the Tenor search endpoint", async () => {
+ it("calls the Klipy search endpoint", async () => {
mockFetch.mockResolvedValue(okResponse([]));
await searchGifs("cats");
- expect(capturedUrl()).toMatch(/^https:\/\/tenor\.googleapis\.com\/v2\/search/);
+ expect(capturedUrl()).toMatch(/^https:\/\/api\.klipy\.com\/v2\/search/);
});
it("includes the query param q", async () => {
@@ -90,7 +90,7 @@ describe("searchGifs", () => {
it("includes the API key param", async () => {
mockFetch.mockResolvedValue(okResponse([]));
await searchGifs("dogs");
- expect(capturedParams().get("key")).toBeTruthy();
+ expect(capturedParams().has("key")).toBe(true);
});
it("includes media_filter param", async () => {
@@ -127,20 +127,20 @@ describe("searchGifs", () => {
});
it("maps id, title, url (tinygif), and fullUrl (gif) correctly", async () => {
- mockFetch.mockResolvedValue(okResponse([tenorResult("abc123")]));
+ mockFetch.mockResolvedValue(okResponse([gifResult("abc123")]));
const gifs = await searchGifs("cats");
expect(gifs).toHaveLength(1);
expect(gifs[0]).toEqual({
id: "abc123",
title: "Title abc123",
- url: "https://media.tenor.com/abc123_tiny.gif",
- fullUrl: "https://media.tenor.com/abc123.gif",
+ url: "https://media.klipy.com/abc123_tiny.gif",
+ fullUrl: "https://media.klipy.com/abc123.gif",
});
});
it("maps multiple results in order", async () => {
mockFetch.mockResolvedValue(
- okResponse([tenorResult("a"), tenorResult("b"), tenorResult("c")]),
+ okResponse([gifResult("a"), gifResult("b"), gifResult("c")]),
);
const gifs = await searchGifs("cats");
expect(gifs.map((g) => g.id)).toEqual(["a", "b", "c"]);
@@ -148,7 +148,7 @@ describe("searchGifs", () => {
it("filters out results with no tinygif format", async () => {
mockFetch.mockResolvedValue(
- okResponse([tenorResult("keep"), tenorResult("drop", { tinygif: null })]),
+ okResponse([gifResult("keep"), gifResult("drop", { tinygif: null })]),
);
const gifs = await searchGifs("cats");
expect(gifs).toHaveLength(1);
@@ -157,7 +157,7 @@ describe("searchGifs", () => {
it("filters out results with no gif format", async () => {
mockFetch.mockResolvedValue(
- okResponse([tenorResult("keep"), tenorResult("drop", { gif: null })]),
+ okResponse([gifResult("keep"), gifResult("drop", { gif: null })]),
);
const gifs = await searchGifs("cats");
expect(gifs).toHaveLength(1);
@@ -166,7 +166,7 @@ describe("searchGifs", () => {
it("filters out results missing both formats", async () => {
mockFetch.mockResolvedValue(
- okResponse([tenorResult("drop", { tinygif: null, gif: null }), tenorResult("keep")]),
+ okResponse([gifResult("drop", { tinygif: null, gif: null }), gifResult("keep")]),
);
const gifs = await searchGifs("cats");
expect(gifs).toHaveLength(1);
@@ -175,11 +175,29 @@ describe("searchGifs", () => {
it("returns an empty array when all results lack required formats", async () => {
mockFetch.mockResolvedValue(
- okResponse([tenorResult("x", { tinygif: null }), tenorResult("y", { gif: null })]),
+ okResponse([gifResult("x", { tinygif: null }), gifResult("y", { gif: null })]),
);
const gifs = await searchGifs("cats");
expect(gifs).toEqual([]);
});
+
+ it("filters out results with non-Klipy CDN URLs", async () => {
+ mockFetch.mockResolvedValue(
+ okResponse([
+ gifResult("drop", {
+ tinygif: "https://media.tenor.com/drop_tiny.gif",
+ gif: "https://media.tenor.com/drop.gif",
+ }),
+ gifResult("keep", {
+ tinygif: "https://static.klipy.com/keep_tiny.gif",
+ gif: "https://static.klipy.com/keep.gif",
+ }),
+ ]),
+ );
+ const gifs = await searchGifs("cats");
+ expect(gifs).toHaveLength(1);
+ expect(gifs[0]?.id).toBe("keep");
+ });
});
describe("HTTP error handling", () => {
@@ -211,10 +229,10 @@ describe("searchGifs", () => {
describe("getTrendingGifs", () => {
describe("URL construction", () => {
- it("calls the Tenor featured endpoint", async () => {
+ it("calls the Klipy featured endpoint", async () => {
mockFetch.mockResolvedValue(okResponse([]));
await getTrendingGifs();
- expect(capturedUrl()).toMatch(/^https:\/\/tenor\.googleapis\.com\/v2\/featured/);
+ expect(capturedUrl()).toMatch(/^https:\/\/api\.klipy\.com\/v2\/featured/);
});
it("does not include a q param", async () => {
@@ -226,7 +244,7 @@ describe("getTrendingGifs", () => {
it("includes the API key param", async () => {
mockFetch.mockResolvedValue(okResponse([]));
await getTrendingGifs();
- expect(capturedParams().get("key")).toBeTruthy();
+ expect(capturedParams().has("key")).toBe(true);
});
it("includes media_filter param", async () => {
@@ -256,19 +274,19 @@ describe("getTrendingGifs", () => {
});
it("maps fields correctly", async () => {
- mockFetch.mockResolvedValue(okResponse([tenorResult("trend1")]));
+ mockFetch.mockResolvedValue(okResponse([gifResult("trend1")]));
const gifs = await getTrendingGifs();
expect(gifs[0]).toEqual({
id: "trend1",
title: "Title trend1",
- url: "https://media.tenor.com/trend1_tiny.gif",
- fullUrl: "https://media.tenor.com/trend1.gif",
+ url: "https://media.klipy.com/trend1_tiny.gif",
+ fullUrl: "https://media.klipy.com/trend1.gif",
});
});
it("filters out results with missing tinygif", async () => {
mockFetch.mockResolvedValue(
- okResponse([tenorResult("keep"), tenorResult("drop", { tinygif: null })]),
+ okResponse([gifResult("keep"), gifResult("drop", { tinygif: null })]),
);
const gifs = await getTrendingGifs();
expect(gifs.map((g) => g.id)).toEqual(["keep"]);
@@ -276,7 +294,7 @@ describe("getTrendingGifs", () => {
it("filters out results with missing gif", async () => {
mockFetch.mockResolvedValue(
- okResponse([tenorResult("keep"), tenorResult("drop", { gif: null })]),
+ okResponse([gifResult("keep"), gifResult("drop", { gif: null })]),
);
const gifs = await getTrendingGifs();
expect(gifs.map((g) => g.id)).toEqual(["keep"]);
diff --git a/Client/tauri-client/tests/unit/message-input.test.ts b/Client/tauri-client/tests/unit/message-input.test.ts
index db91e22a..6a4c6be0 100644
--- a/Client/tauri-client/tests/unit/message-input.test.ts
+++ b/Client/tauri-client/tests/unit/message-input.test.ts
@@ -746,9 +746,9 @@ describe("MessageInput", () => {
gifBtn.click();
expect(lastGifPickerOptions).not.toBeNull();
- lastGifPickerOptions!.onSelect("https://tenor.com/example.gif");
+ lastGifPickerOptions!.onSelect("https://media.klipy.com/example.gif");
- expect(opts.onSend).toHaveBeenCalledWith("https://tenor.com/example.gif", null, []);
+ expect(opts.onSend).toHaveBeenCalledWith("https://media.klipy.com/example.gif", null, []);
comp.destroy?.();
});