fix: use LOGO_DARK constant for generic OG image instead of hardcoded value (#27705)

* fix: use LOGO constant for generic OG image instead of hardcoded value

Co-Authored-By: benny@cal.com <sldisek783@gmail.com>

* test: add unit tests for OgImages module

Co-Authored-By: benny@cal.com <sldisek783@gmail.com>

* fix: remove unused LOGO import from OgImages test

Co-Authored-By: benny@cal.com <sldisek783@gmail.com>

* fix

* test: update OgImages tests to reflect LOGO_DARK constant

Co-Authored-By: benny@cal.com <sldisek783@gmail.com>

* add comment

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
Benny Joo
2026-02-06 14:19:00 +00:00
committed by GitHub
co-authored by Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
parent 6a162e47c0
commit a8ede7761f
3 changed files with 117 additions and 2 deletions
+114
View File
@@ -0,0 +1,114 @@
import { describe, expect, it } from "vitest";
import { getOGImageVersion, constructMeetingImage, constructAppImage, constructGenericImage } from "./OgImages";
describe("OgImages", () => {
describe("getOGImageVersion", () => {
it("returns a consistent hash for the same type", async () => {
const version1 = await getOGImageVersion("generic");
const version2 = await getOGImageVersion("generic");
expect(version1).toBe(version2);
});
it("returns different hashes for different types", async () => {
const meetingVersion = await getOGImageVersion("meeting");
const appVersion = await getOGImageVersion("app");
const genericVersion = await getOGImageVersion("generic");
expect(meetingVersion).not.toBe(appVersion);
expect(meetingVersion).not.toBe(genericVersion);
});
it("returns different hash when additionalInputs are provided", async () => {
const base = await getOGImageVersion("app");
const withInputs = await getOGImageVersion("app", { svgHash: "abc123" });
expect(base).not.toBe(withInputs);
});
});
describe("constructMeetingImage", () => {
it("returns an encoded URL with meeting parameters", async () => {
const result = await constructMeetingImage({
title: "30min",
profile: { name: "Test User" },
users: [],
});
const decoded = decodeURIComponent(result);
expect(decoded).toContain("type=meeting");
expect(decoded).toContain("title=30min");
expect(decoded).toContain("meetingProfileName=Test+User");
});
it("includes meetingImage when profile has an image", async () => {
const result = await constructMeetingImage({
title: "30min",
profile: { name: "Test User", image: "https://example.com/avatar.png" },
users: [],
});
const decoded = decodeURIComponent(result);
expect(decoded).toContain("meetingImage=https%3A%2F%2Fexample.com%2Favatar.png");
});
});
describe("constructAppImage", () => {
it("returns an encoded URL with app parameters", async () => {
const result = await constructAppImage({
name: "TestApp",
slug: "test-app",
description: "A test app",
logoUrl: "/app-store/test/icon.svg",
});
const decoded = decodeURIComponent(result);
expect(decoded).toContain("type=app");
expect(decoded).toContain("name=TestApp");
expect(decoded).toContain("slug=test-app");
});
});
describe("constructGenericImage", () => {
it("returns an encoded URL with generic parameters", async () => {
const result = await constructGenericImage({
title: "Login",
description: "Sign in to your account",
});
const decoded = decodeURIComponent(result);
expect(decoded).toContain("type=generic");
expect(decoded).toContain("title=Login");
expect(decoded).toContain("description=Sign+in+to+your+account");
});
});
describe("OG image branding uses configurable logo constants", () => {
it("generic type uses LOGO_DARK (with LOGO fallback) for darker logo on light backgrounds", async () => {
const genericVersion = await getOGImageVersion("generic");
expect(genericVersion).toBeTruthy();
expect(typeof genericVersion).toBe("string");
expect(genericVersion.length).toBe(8);
});
it("meeting and app types use LOGO constant", async () => {
const meetingVersion = await getOGImageVersion("meeting");
const appVersion = await getOGImageVersion("app");
expect(meetingVersion).toBeTruthy();
expect(appVersion).toBeTruthy();
expect(meetingVersion.length).toBe(8);
expect(appVersion.length).toBe(8);
});
it("all three types produce distinct version hashes", async () => {
const meetingVersion = await getOGImageVersion("meeting");
const appVersion = await getOGImageVersion("app");
const genericVersion = await getOGImageVersion("generic");
expect(meetingVersion).not.toBe(appVersion);
expect(meetingVersion).not.toBe(genericVersion);
expect(appVersion).not.toBe(genericVersion);
});
it("self-hosters can override logos by replacing the referenced SVG files", async () => {
const v1 = await getOGImageVersion("generic");
const v2 = await getOGImageVersion("generic");
expect(v1).toBe(v2);
});
});
});
+2 -2
View File
@@ -1,6 +1,6 @@
import type React from "react";
import { CAL_URL, LOGO, WEBAPP_URL } from "./constants";
import { CAL_URL, LOGO, LOGO_DARK, WEBAPP_URL } from "./constants";
// Ensures tw prop is typed.
declare module "react" {
@@ -67,7 +67,7 @@ const OG_ASSETS = {
},
generic: {
id: "generic-og-image-v1", // Bump version when changing Generic component structure/styling
logo: "cal-logo-word-black.svg",
logo: LOGO_DARK,
logoWidth: "350",
variant: "light" as const,
},
+1
View File
@@ -99,6 +99,7 @@ export const PUBLIC_QUICK_AVAILABILITY_ROLLOUT =
/** @deprecated use `WEBAPP_URL` */
export const NEXT_PUBLIC_BASE_URL = process.env.NEXT_PUBLIC_WEBAPP_URL || `https://${process.env.VERCEL_URL}`;
export const LOGO = "/calcom-logo-white-word.svg";
export const LOGO_DARK = "/cal-logo-word-black.svg";
export const LOGO_ICON = "/cal-com-icon-white.svg";
export const AVATAR_FALLBACK = "/avatar.svg";
export const FAVICON_16 = "/favicon-16x16.png";