Files
outline/server/commands/documentCreator.test.ts
Copilot 1c52a3e7da Fix custom emoji UUID escaping in API document creation (#11594)
* Initial plan

* Add custom emoji UUID parsing support

Co-authored-by: tommoor <380914+tommoor@users.noreply.github.com>

* Fix custom emoji UUID parsing - working solution

Co-authored-by: tommoor <380914+tommoor@users.noreply.github.com>

* Add JSDoc and improve code quality based on review

Co-authored-by: tommoor <380914+tommoor@users.noreply.github.com>

* Add JSON content verification to custom emoji test

Co-authored-by: tommoor <380914+tommoor@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: tommoor <380914+tommoor@users.noreply.github.com>
2026-02-27 19:32:06 -05:00

356 lines
10 KiB
TypeScript

import { ProsemirrorHelper } from "@server/models/helpers/ProsemirrorHelper";
import {
buildUser,
buildCollection,
buildDocument,
buildFileOperation,
buildTemplate,
} from "@server/test/factories";
import { withAPIContext } from "@server/test/support";
import documentCreator from "./documentCreator";
describe("documentCreator", () => {
describe("content vs text priority", () => {
it("should prioritize content over text when both are provided", async () => {
const user = await buildUser();
const collection = await buildCollection({
userId: user.id,
teamId: user.teamId,
});
const testText = "This is plain text";
const testContent = ProsemirrorHelper.toProsemirror(
"This is rich content"
).toJSON();
const document = await withAPIContext(user, (ctx) =>
documentCreator(ctx, {
title: "Test Document",
text: testText,
content: testContent,
collectionId: collection.id,
})
);
expect(document.content).toEqual(testContent);
expect(document.text).toContain("This is rich content");
expect(document.text).not.toContain("This is plain text");
});
it("should use text when content is not provided", async () => {
const user = await buildUser();
const collection = await buildCollection({
userId: user.id,
teamId: user.teamId,
});
const testText = "This is plain text";
const document = await withAPIContext(user, (ctx) =>
documentCreator(ctx, {
title: "Test Document",
text: testText,
collectionId: collection.id,
})
);
expect(document.text).toContain("This is plain text");
});
it("should create empty document when neither content nor text is provided", async () => {
const user = await buildUser();
const collection = await buildCollection({
userId: user.id,
teamId: user.teamId,
});
const document = await withAPIContext(user, (ctx) =>
documentCreator(ctx, {
title: "Empty Document",
collectionId: collection.id,
})
);
expect(document.text).toBe("");
expect(document.title).toBe("Empty Document");
});
});
describe("basic document creation", () => {
it("should create a basic document", async () => {
const user = await buildUser();
const collection = await buildCollection({
userId: user.id,
teamId: user.teamId,
});
const document = await withAPIContext(user, (ctx) =>
documentCreator(ctx, {
title: "Test Document",
text: "This is a test document",
collectionId: collection.id,
})
);
expect(document.title).toBe("Test Document");
expect(document.text).toContain("This is a test document");
expect(document.collectionId).toBe(collection.id);
expect(document.createdById).toBe(user.id);
expect(document.lastModifiedById).toBe(user.id);
expect(document.teamId).toBe(user.teamId);
});
it("should create document with custom properties", async () => {
const user = await buildUser();
const collection = await buildCollection({
userId: user.id,
teamId: user.teamId,
});
const document = await withAPIContext(user, (ctx) =>
documentCreator(ctx, {
title: "Custom Document",
text: "Custom content",
icon: "📄",
color: "#FF0000",
fullWidth: true,
collectionId: collection.id,
})
);
expect(document.icon).toBe("📄");
expect(document.color).toBe("#FF0000");
expect(document.fullWidth).toBe(true);
});
it("should create document as draft when publish is false", async () => {
const user = await buildUser();
const collection = await buildCollection({
userId: user.id,
teamId: user.teamId,
});
const document = await withAPIContext(user, (ctx) =>
documentCreator(ctx, {
title: "Draft Document",
text: "Draft content",
collectionId: collection.id,
publish: false,
})
);
expect(document.publishedAt).toBeNull();
});
it("should publish document when publish is true", async () => {
const user = await buildUser();
const collection = await buildCollection({
userId: user.id,
teamId: user.teamId,
});
const document = await withAPIContext(user, (ctx) =>
documentCreator(ctx, {
title: "Published Document",
text: "Published content",
collectionId: collection.id,
publish: true,
})
);
expect(document.publishedAt).toBeInstanceOf(Date);
});
it("should throw error when trying to publish without collection", async () => {
const user = await buildUser();
await expect(
withAPIContext(user, (ctx) =>
documentCreator(ctx, {
title: "Invalid Document",
text: "Content",
publish: true,
})
)
).rejects.toThrow("Collection ID is required to publish");
});
});
describe("template document handling", () => {
it("should create document from template", async () => {
const user = await buildUser();
const collection = await buildCollection({
userId: user.id,
teamId: user.teamId,
});
const template = await buildTemplate({
title: "Template Document",
text: "Template content",
icon: "📋",
color: "#00FF00",
fullWidth: true,
userId: user.id,
teamId: user.teamId,
collectionId: collection.id,
});
const document = await withAPIContext(user, (ctx) =>
documentCreator(ctx, {
title: "From Template",
template,
collectionId: collection.id,
})
);
expect(document.title).toBe("From Template");
expect(document.icon).toBe("📋");
expect(document.color).toBe("#00FF00");
expect(document.fullWidth).toBe(true);
});
it("should use template title when no title provided", async () => {
const user = await buildUser();
const collection = await buildCollection({
userId: user.id,
teamId: user.teamId,
});
const template = await buildTemplate({
title: "Template Title",
text: "Template content",
userId: user.id,
teamId: user.teamId,
collectionId: collection.id,
});
const document = await withAPIContext(user, (ctx) =>
documentCreator(ctx, {
template,
collectionId: collection.id,
})
);
expect(document.title).toBe("Template Title");
});
it("should throw error when state is provided with template", async () => {
const user = await buildUser();
const collection = await buildCollection({
userId: user.id,
teamId: user.teamId,
});
const template = await buildTemplate({
title: "Template Document",
text: "Template content",
userId: user.id,
teamId: user.teamId,
collectionId: collection.id,
});
await expect(
withAPIContext(user, (ctx) =>
documentCreator(ctx, {
template,
state: Buffer.from("some state"),
collectionId: collection.id,
})
)
).rejects.toThrow(
"State cannot be set when creating a document from a template"
);
});
});
describe("parent document handling", () => {
it("should create child document", async () => {
const user = await buildUser();
const collection = await buildCollection({
userId: user.id,
teamId: user.teamId,
});
const parentDocument = await buildDocument({
title: "Parent Document",
text: "Parent content",
userId: user.id,
teamId: user.teamId,
collectionId: collection.id,
});
const document = await withAPIContext(user, (ctx) =>
documentCreator(ctx, {
title: "Child Document",
text: "Child content",
parentDocumentId: parentDocument.id,
collectionId: collection.id,
})
);
expect(document.parentDocumentId).toBe(parentDocument.id);
});
});
describe("import handling", () => {
it("should handle import metadata", async () => {
const user = await buildUser();
const collection = await buildCollection({
userId: user.id,
teamId: user.teamId,
});
const fileOperation = await buildFileOperation({
teamId: user.teamId,
});
const sourceMetadata = { fileName: "test" };
const document = await withAPIContext(user, (ctx) =>
documentCreator(ctx, {
title: "fileOperation Document",
text: "fileOperation content",
importId: fileOperation.id,
sourceMetadata,
collectionId: collection.id,
})
);
expect(document.importId).toBe(fileOperation.id);
expect(document.sourceMetadata).toEqual(sourceMetadata);
});
});
describe("custom emoji handling", () => {
it("should not escape custom emoji UUIDs in text", async () => {
const user = await buildUser();
const collection = await buildCollection({
userId: user.id,
teamId: user.teamId,
});
const customEmojiId = "550e8400-e29b-41d4-a716-446655440000";
const text = `Check this :${customEmojiId}: custom emoji`;
const document = await withAPIContext(user, (ctx) =>
documentCreator(ctx, {
title: "Custom Emoji Test",
text,
collectionId: collection.id,
})
);
// The custom emoji should be preserved in the text without escaping
expect(document.text).toContain(`:${customEmojiId}:`);
expect(document.text).not.toContain(`\\:${customEmojiId}:`);
expect(document.text).not.toContain(`\\:${customEmojiId}\\:`);
// The JSON content should include an emoji node with the UUID
expect(document.content).toBeDefined();
const contentStr = JSON.stringify(document.content);
expect(contentStr).toContain('"type":"emoji"');
expect(contentStr).toContain(`"data-name":"${customEmojiId}"`);
});
});
});