Emdash source with visual editor image upload fix

Fixes:
1. media.ts: wrap placeholder generation in try-catch
2. toolbar.ts: check r.ok, display error message in popover
This commit is contained in:
2026-05-03 10:44:54 +07:00
parent 78f81bebb6
commit 2d1be52177
2352 changed files with 662964 additions and 0 deletions

View File

@@ -0,0 +1,161 @@
import { describe, it, expect } from "vitest";
import { getPreviewUrl, buildPreviewUrl } from "../../../src/preview/urls.js";
// Regex patterns for URL validation
const RELATIVE_PREVIEW_URL_REGEX = /^\/posts\/hello-world\?_preview=/;
const ABSOLUTE_PREVIEW_URL_REGEX = /^https:\/\/example\.com\/posts\/hello-world\?_preview=/;
const BLOG_PREVIEW_URL_REGEX = /^\/blog\/hello-world\?_preview=/;
const CONTENT_PREVIEW_URL_REGEX = /^\/content\/posts\/view\/hello-world\?_preview=/;
const BASE64URL_INVALID_CHARS_REGEX = /[+/=]/;
describe("preview URLs", () => {
const testSecret = "test-secret-key-for-preview-tokens";
describe("getPreviewUrl", () => {
it("generates relative URL by default", async () => {
const url = await getPreviewUrl({
collection: "posts",
id: "hello-world",
secret: testSecret,
});
// Should start with path
expect(url).toMatch(RELATIVE_PREVIEW_URL_REGEX);
// Should have a token
const urlObj = new URL(url, "http://example.com");
const token = urlObj.searchParams.get("_preview");
expect(token).toBeTruthy();
expect(token!.split(".").length).toBe(2);
});
it("generates absolute URL with baseUrl", async () => {
const url = await getPreviewUrl({
collection: "posts",
id: "hello-world",
secret: testSecret,
baseUrl: "https://example.com",
});
expect(url).toMatch(ABSOLUTE_PREVIEW_URL_REGEX);
});
it("respects custom path pattern", async () => {
const url = await getPreviewUrl({
collection: "posts",
id: "hello-world",
secret: testSecret,
pathPattern: "/blog/{id}",
});
expect(url).toMatch(BLOG_PREVIEW_URL_REGEX);
});
it("supports complex path patterns", async () => {
const url = await getPreviewUrl({
collection: "posts",
id: "hello-world",
secret: testSecret,
pathPattern: "/content/{collection}/view/{id}",
});
expect(url).toMatch(CONTENT_PREVIEW_URL_REGEX);
});
it("generates URL-safe tokens", async () => {
const url = await getPreviewUrl({
collection: "posts",
id: "test-id",
secret: testSecret,
});
// Token should not contain URL-unsafe characters
const urlObj = new URL(url, "http://example.com");
const token = urlObj.searchParams.get("_preview");
expect(token).not.toMatch(BASE64URL_INVALID_CHARS_REGEX);
});
it("substitutes {locale} placeholder", async () => {
const url = await getPreviewUrl({
collection: "posts",
id: "hello-world",
secret: testSecret,
pathPattern: "/{locale}/{id}",
locale: "pt-br",
});
expect(url).toMatch(/^\/pt-br\/hello-world\?_preview=/);
});
it("collapses empty {locale} segment", async () => {
const url = await getPreviewUrl({
collection: "posts",
id: "hello-world",
secret: testSecret,
pathPattern: "/{locale}/{id}",
locale: "",
});
// Empty locale should not leave a leading double slash.
expect(url).toMatch(/^\/hello-world\?_preview=/);
});
it("respects expiresIn option", async () => {
const shortUrl = await getPreviewUrl({
collection: "posts",
id: "test",
secret: testSecret,
expiresIn: "30m",
});
const longUrl = await getPreviewUrl({
collection: "posts",
id: "test",
secret: testSecret,
expiresIn: "7d",
});
// Both should be valid but different tokens
expect(shortUrl).not.toBe(longUrl);
});
});
describe("buildPreviewUrl", () => {
it("builds URL from existing token", () => {
const token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.test-signature";
const url = buildPreviewUrl({
path: "/posts/hello-world",
token,
});
expect(url).toBe(`/posts/hello-world?_preview=${token}`);
});
it("builds absolute URL with baseUrl", () => {
const token = "test-token";
const url = buildPreviewUrl({
path: "/posts/hello-world",
token,
baseUrl: "https://example.com",
});
expect(url).toBe(`https://example.com/posts/hello-world?_preview=${token}`);
});
it("preserves existing query params in path", () => {
const token = "test-token";
// Note: buildPreviewUrl doesn't preserve existing params, it starts fresh
// This is intentional - the path should be clean
const url = buildPreviewUrl({
path: "/posts/hello-world",
token,
});
expect(url).toContain("_preview=test-token");
});
});
});