-
Notifications
You must be signed in to change notification settings - Fork 1.1k
fix(core): resolve collection url_pattern for content preview links #1745
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| "emdash": patch | ||
| --- | ||
|
|
||
| Fixes preview links 404ing on sites with a custom collection `url_pattern`. The content Preview button now resolves the collection's `url_pattern` (the same route the sitemap and "View published" links use) instead of the hard-coded `/{collection}/{id}`, falling back to `/{collection}/{id}` only when no pattern is configured. An explicit `pathPattern` or `EMDASH_PREVIEW_PATH_PATTERN` still takes precedence. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,133 @@ | ||
| import { Role } from "@emdash-cms/auth"; | ||
| import type { Kysely } from "kysely"; | ||
| import { describe, it, expect, beforeEach, afterEach, vi } from "vitest"; | ||
|
|
||
| import { handleContentCreate, handleContentGet } from "../../../src/api/index.js"; | ||
| import { POST as previewUrl } from "../../../src/astro/routes/api/content/[collection]/[id]/preview-url.js"; | ||
| import type { Database } from "../../../src/database/types.js"; | ||
| import { setI18nConfig } from "../../../src/i18n/config.js"; | ||
| import { _resetAstroI18nCacheForTests } from "../../../src/i18n/resolve.js"; | ||
| import { SchemaRegistry } from "../../../src/schema/registry.js"; | ||
| import { setupTestDatabaseWithCollections, teardownTestDatabase } from "../../utils/test-db.js"; | ||
|
|
||
| /** | ||
| * Regression: the preview-url endpoint used a hard-coded `/{collection}/{id}` | ||
| * default, ignoring the collection's configured `url_pattern`. On any site | ||
| * whose content is served at a custom permalink (e.g. `/blog/{slug}`) the | ||
| * admin "Preview" button produced a link that 404'd. The sitemap and | ||
| * "View published" links already resolve the same `url_pattern`; the preview | ||
| * link must too. See discussion #1525 / PR #1526. | ||
| */ | ||
| describe("preview-url route — respects collection url_pattern", () => { | ||
| let db: Kysely<Database>; | ||
|
|
||
| const call = async (collection: string, id: string, body: Record<string, unknown> = {}) => { | ||
| const request = new Request( | ||
| `http://localhost/_emdash/api/content/${collection}/${id}/preview-url`, | ||
| { | ||
| method: "POST", | ||
| headers: { "Content-Type": "application/json" }, | ||
| body: JSON.stringify(body), | ||
| }, | ||
| ); | ||
| const response = await previewUrl({ | ||
| params: { collection, id }, | ||
| request, | ||
| locals: { | ||
| emdash: { | ||
| db, | ||
| handleContentGet: (c: string, i: string) => handleContentGet(db, c, i), | ||
| }, | ||
| user: { id: "u1", role: Role.ADMIN }, | ||
| }, | ||
| } as unknown as Parameters<typeof previewUrl>[0]); | ||
| return response; | ||
| }; | ||
|
|
||
| beforeEach(async () => { | ||
| db = await setupTestDatabaseWithCollections(); | ||
| }); | ||
|
|
||
| afterEach(async () => { | ||
| vi.unstubAllEnvs(); | ||
| setI18nConfig(null); | ||
| _resetAstroI18nCacheForTests(); | ||
| await teardownTestDatabase(db); | ||
| }); | ||
|
|
||
| it("resolves the configured url_pattern into the preview link", async () => { | ||
| await new SchemaRegistry(db).updateCollection("post", { urlPattern: "/blog/{slug}" }); | ||
| const created = await handleContentCreate(db, "post", { | ||
| data: { title: "Hello World" }, | ||
| }); | ||
| const id = created.data!.item.id; | ||
|
|
||
| const response = await call("post", id); | ||
| expect(response.status).toBe(200); | ||
| const { url } = (await response.json()).data as { url: string }; | ||
|
|
||
| expect(url.startsWith("/blog/hello-world?_preview=")).toBe(true); | ||
| // The generic collection/id fallback must NOT leak through. | ||
| expect(url.startsWith("/post/")).toBe(false); | ||
| }); | ||
|
|
||
| it("falls back to /{collection}/{id} when no url_pattern is configured", async () => { | ||
| const created = await handleContentCreate(db, "post", { | ||
| data: { title: "No Pattern" }, | ||
| }); | ||
| const id = created.data!.item.id; | ||
|
|
||
| const response = await call("post", id); | ||
| expect(response.status).toBe(200); | ||
| const { url } = (await response.json()).data as { url: string }; | ||
|
|
||
| expect(url.startsWith(`/post/${id}?_preview=`)).toBe(true); | ||
| }); | ||
|
|
||
| it("lets an explicit pathPattern override the url_pattern", async () => { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [suggestion] The new tests cover the body-level
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added in e766a63: |
||
| await new SchemaRegistry(db).updateCollection("post", { urlPattern: "/blog/{slug}" }); | ||
| const created = await handleContentCreate(db, "post", { | ||
| data: { title: "Override Me" }, | ||
| }); | ||
| const id = created.data!.item.id; | ||
|
|
||
| const response = await call("post", id, { pathPattern: "/custom/{id}" }); | ||
| expect(response.status).toBe(200); | ||
| const { url } = (await response.json()).data as { url: string }; | ||
|
|
||
| expect(url.startsWith(`/custom/${id}?_preview=`)).toBe(true); | ||
| }); | ||
|
|
||
| it("lets the EMDASH_PREVIEW_PATH_PATTERN env override win over the url_pattern", async () => { | ||
| vi.stubEnv("EMDASH_PREVIEW_PATH_PATTERN", "/env/{id}"); | ||
| await new SchemaRegistry(db).updateCollection("post", { urlPattern: "/blog/{slug}" }); | ||
| const created = await handleContentCreate(db, "post", { | ||
| data: { title: "Env Wins" }, | ||
| }); | ||
| const id = created.data!.item.id; | ||
|
|
||
| const response = await call("post", id); | ||
| expect(response.status).toBe(200); | ||
| const { url } = (await response.json()).data as { url: string }; | ||
|
|
||
| expect(url.startsWith(`/env/${id}?_preview=`)).toBe(true); | ||
| expect(url.startsWith("/blog/")).toBe(false); | ||
| }); | ||
|
|
||
| it("prefixes the locale segment for a non-default-locale entry", async () => { | ||
| setI18nConfig({ defaultLocale: "en", locales: ["en", "de"], prefixDefaultLocale: false }); | ||
| _resetAstroI18nCacheForTests(); | ||
| await new SchemaRegistry(db).updateCollection("post", { urlPattern: "/blog/{slug}" }); | ||
| const created = await handleContentCreate(db, "post", { | ||
| data: { title: "Hallo Welt" }, | ||
| locale: "de", | ||
| }); | ||
| const id = created.data!.item.id; | ||
|
|
||
| const response = await call("post", id); | ||
| expect(response.status).toBe(200); | ||
| const { url } = (await response.json()).data as { url: string }; | ||
|
|
||
| expect(url.startsWith("/de/blog/hallo-welt?_preview=")).toBe(true); | ||
| }); | ||
| }); | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[suggestion] The new
url_patterncode path callslocalizePath, which handles locale prefixes and custompath/codesmappings, but the current tests run with no i18n config so that logic is never exercised. Consider adding a test that sets an i18n config (viasetI18nConfig) and verifies the preview link receives the expected locale prefix for a non-default-locale entry.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added in e766a63: the test sets
setI18nConfig({ defaultLocale: "en", locales: ["en", "de"], prefixDefaultLocale: false })(plus_resetAstroI18nCacheForTests(), same pattern as the sitemap route tests), creates adeentry, and asserts the preview link comes back as/de/blog/hallo-welt?_preview=….