b3a7f98e5a
CI / E2E Cloudflare (4/8) (push) Failing after 0s
CI / E2E Cloudflare (8/8) (push) Failing after 1s
CI / Lint (push) Failing after 1s
Auto Extract / Extract (push) Failing after 4s
CI / Version Check (push) Failing after 9s
CI / Integration Tests (push) Failing after 1s
CI / E2E tests (1/8) (push) Failing after 1s
CI / E2E tests (2/8) (push) Failing after 2s
CI / E2E tests (3/8) (push) Failing after 2s
CI / Browser Tests (push) Failing after 1s
CI / E2E tests (5/8) (push) Failing after 1s
CI / E2E Cloudflare (2/8) (push) Failing after 2s
CI / Typecheck (push) Failing after 1s
CI / Changeset Validation (push) Failing after 2s
CI / E2E Cloudflare (5/8) (push) Failing after 1s
CI / E2E Cloudflare (6/8) (push) Failing after 1s
CI / E2E Cloudflare (7/8) (push) Failing after 1s
CodeQL / Analyze (javascript-typescript) (push) Failing after 1s
Format / Format (push) Failing after 0s
CodeQL / Analyze (actions) (push) Failing after 4s
CI / E2E tests (4/8) (push) Failing after 1s
CI / E2E tests (6/8) (push) Failing after 1s
CI / E2E tests (7/8) (push) Failing after 2s
CI / E2E tests (8/8) (push) Failing after 1s
CI / E2E Cloudflare (1/8) (push) Failing after 1s
CI / E2E Cloudflare (3/8) (push) Failing after 2s
Preview Releases / Publish Preview (push) Failing after 0s
zizmor / Run zizmor (push) Failing after 1s
Release / Release (push) Failing after 2s
CI / Smoke Tests (push) Failing after 5m36s
CI / Tests (push) Failing after 6m36s
Release / Sync Templates (push) Has been skipped
CI / E2E Tests (push) Has been cancelled
156 lines
4.6 KiB
TypeScript
156 lines
4.6 KiB
TypeScript
import { afterEach, beforeEach, expect, it } from "vitest";
|
|
|
|
import { IdentifierError } from "../../../src/database/validate.js";
|
|
import {
|
|
loadContentMediaUsageFields,
|
|
MediaUsageFieldDiscoveryError,
|
|
} from "../../../src/media/usage/content-fields.js";
|
|
import { SchemaRegistry } from "../../../src/schema/registry.js";
|
|
import {
|
|
describeEachDialect,
|
|
setupForDialect,
|
|
teardownForDialect,
|
|
type DialectTestContext,
|
|
} from "../../utils/test-db.js";
|
|
|
|
describeEachDialect("content media usage field discovery", (dialect) => {
|
|
let ctx: DialectTestContext;
|
|
let registry: SchemaRegistry;
|
|
|
|
beforeEach(async () => {
|
|
ctx = await setupForDialect(dialect);
|
|
registry = new SchemaRegistry(ctx.db);
|
|
await registry.createCollection({ slug: "posts", label: "Posts" });
|
|
});
|
|
|
|
afterEach(async () => {
|
|
await teardownForDialect(ctx);
|
|
});
|
|
|
|
it("discovers V1 extraction fields and display fields separately", async () => {
|
|
await registry.createField("posts", { slug: "name", label: "Name", type: "string" });
|
|
await registry.createField("posts", { slug: "body", label: "Body", type: "portableText" });
|
|
await registry.createField("posts", { slug: "title", label: "Title", type: "string" });
|
|
await registry.createField("posts", { slug: "raw_data", label: "Raw Data", type: "json" });
|
|
await registry.createField("posts", { slug: "attachment", label: "Attachment", type: "file" });
|
|
await registry.createField("posts", { slug: "hero", label: "Hero", type: "image" });
|
|
await registry.createField("posts", {
|
|
slug: "sections",
|
|
label: "Sections",
|
|
type: "repeater",
|
|
validation: {
|
|
subFields: [
|
|
{ slug: "caption", type: "text", label: "Caption" },
|
|
{ slug: "image", type: "image", label: "Image" },
|
|
],
|
|
},
|
|
});
|
|
|
|
const discovery = await loadContentMediaUsageFields(ctx.db, "posts");
|
|
|
|
expect(discovery.displayFieldSlugs).toEqual(["title", "name"]);
|
|
expect(discovery.extractionFields).toEqual([
|
|
{ slug: "attachment", type: "file" },
|
|
{ slug: "body", type: "portableText" },
|
|
{ slug: "hero", type: "image" },
|
|
{
|
|
slug: "sections",
|
|
type: "repeater",
|
|
validation: { subFields: [{ slug: "image", type: "image" }] },
|
|
},
|
|
]);
|
|
});
|
|
|
|
it("filters unsupported repeater subfields and excludes repeaters without images", async () => {
|
|
await registry.createField("posts", {
|
|
slug: "sections",
|
|
label: "Sections",
|
|
type: "repeater",
|
|
validation: { subFields: [{ slug: "image", type: "image", label: "Image" }] },
|
|
});
|
|
await registry.createField("posts", {
|
|
slug: "downloads",
|
|
label: "Downloads",
|
|
type: "repeater",
|
|
validation: { subFields: [{ slug: "placeholder", type: "image", label: "Placeholder" }] },
|
|
});
|
|
|
|
await ctx.db
|
|
.updateTable("_emdash_fields")
|
|
.set({
|
|
validation: JSON.stringify({
|
|
subFields: [
|
|
{ slug: "download", type: "file", label: "Download" },
|
|
{ slug: "image", type: "image", label: "Image" },
|
|
{ slug: "caption", type: "text", label: "Caption" },
|
|
],
|
|
}),
|
|
})
|
|
.where("slug", "=", "sections")
|
|
.execute();
|
|
await ctx.db
|
|
.updateTable("_emdash_fields")
|
|
.set({
|
|
validation: JSON.stringify({
|
|
subFields: [{ slug: "download", type: "file", label: "Download" }],
|
|
}),
|
|
})
|
|
.where("slug", "=", "downloads")
|
|
.execute();
|
|
|
|
const discovery = await loadContentMediaUsageFields(ctx.db, "posts");
|
|
|
|
expect(discovery.extractionFields).toEqual([
|
|
{
|
|
slug: "sections",
|
|
type: "repeater",
|
|
validation: { subFields: [{ slug: "image", type: "image" }] },
|
|
},
|
|
]);
|
|
});
|
|
|
|
it("fails closed on malformed repeater validation", async () => {
|
|
await registry.createField("posts", {
|
|
slug: "sections",
|
|
label: "Sections",
|
|
type: "repeater",
|
|
validation: { subFields: [{ slug: "image", type: "image", label: "Image" }] },
|
|
});
|
|
await ctx.db
|
|
.updateTable("_emdash_fields")
|
|
.set({ validation: "{" })
|
|
.where("slug", "=", "sections")
|
|
.execute();
|
|
|
|
await expect(loadContentMediaUsageFields(ctx.db, "posts")).rejects.toThrow(
|
|
MediaUsageFieldDiscoveryError,
|
|
);
|
|
});
|
|
|
|
it("rejects supported fields with invalid slugs before they can become column refs", async () => {
|
|
const collection = await registry.getCollection("posts");
|
|
expect(collection).not.toBeNull();
|
|
|
|
await ctx.db
|
|
.insertInto("_emdash_fields")
|
|
.values({
|
|
id: "invalid-media-field",
|
|
collection_id: collection!.id,
|
|
slug: "bad-slug",
|
|
label: "Bad Slug",
|
|
type: "image",
|
|
column_type: "TEXT",
|
|
required: 0,
|
|
unique: 0,
|
|
default_value: null,
|
|
validation: null,
|
|
widget: null,
|
|
options: null,
|
|
sort_order: 0,
|
|
})
|
|
.execute();
|
|
|
|
await expect(loadContentMediaUsageFields(ctx.db, "posts")).rejects.toThrow(IdentifierError);
|
|
});
|
|
});
|