memos/web/tests/memo-editor-relations.test.ts

132 lines
5 KiB
TypeScript

import { create } from "@bufbuild/protobuf";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { memoService } from "@/components/MemoEditor/services/memoService";
import { createInitialState, type EditorState } from "@/components/MemoEditor/state";
import {
type Memo,
MemoRelation_MemoSchema,
MemoRelation_Type,
MemoRelationSchema,
MemoSchema,
} from "@/types/proto/api/v1/memo_service_pb";
const clients = vi.hoisted(() => ({
createMemo: vi.fn(),
createMemoComment: vi.fn(),
getMemo: vi.fn(),
updateMemo: vi.fn(),
}));
vi.mock("@/connect", () => ({
attachmentServiceClient: {
createAttachment: vi.fn(),
},
memoServiceClient: {
createMemo: clients.createMemo,
createMemoComment: clients.createMemoComment,
getMemo: clients.getMemo,
updateMemo: clients.updateMemo,
},
}));
const createRelation = (type: MemoRelation_Type, memoName: string, relatedMemoName: string) =>
create(MemoRelationSchema, {
type,
memo: create(MemoRelation_MemoSchema, { name: memoName }),
relatedMemo: create(MemoRelation_MemoSchema, { name: relatedMemoName }),
});
const createReference = (relatedMemoName: string) =>
create(MemoRelationSchema, {
type: MemoRelation_Type.REFERENCE,
relatedMemo: create(MemoRelation_MemoSchema, { name: relatedMemoName }),
});
const createEditorState = (memo: Memo): EditorState => ({
...createInitialState(),
...memoService.fromMemo(memo),
});
describe("memo editor relation updates", () => {
beforeEach(() => {
clients.createMemo.mockReset().mockImplementation(async ({ memo }: { memo: Memo }) => ({ ...memo, name: "memos/created" }));
clients.createMemoComment
.mockReset()
.mockImplementation(async ({ comment }: { comment: Memo }) => ({ ...comment, name: "memos/comment" }));
clients.getMemo.mockReset();
clients.updateMemo.mockReset();
clients.updateMemo.mockImplementation(async ({ memo }: { memo: Memo }) => memo);
});
it("assigns a new top-level memo to the selected Space", async () => {
const state = createInitialState();
state.content = "Roadmap";
await memoService.save(state, { space: "spaces/product" });
expect(clients.createMemo).toHaveBeenCalledWith({
memo: expect.objectContaining({ content: "Roadmap", space: "spaces/product" }),
});
});
it("does not inherit the selected Space when creating a comment", async () => {
const state = createInitialState();
state.content = "Reply";
await memoService.save(state, { parentMemoName: "memos/parent", space: "spaces/product" });
expect(clients.createMemoComment).toHaveBeenCalledOnce();
const request = clients.createMemoComment.mock.calls[0][0];
expect(request.name).toBe("memos/parent");
expect(request.comment.content).toBe("Reply");
expect(request.comment.space).toBeUndefined();
});
it("sends only mutable references when editing a comment", async () => {
const commentName = "memos/comment";
const parentRelation = createRelation(MemoRelation_Type.COMMENT, commentName, "memos/parent");
const existingReference = createRelation(MemoRelation_Type.REFERENCE, commentName, "memos/existing-reference");
const incomingReference = createRelation(MemoRelation_Type.REFERENCE, "memos/referencing-comment", commentName);
const comment = create(MemoSchema, {
name: commentName,
content: "Comment",
relations: [parentRelation, existingReference, incomingReference],
});
const addedReference = createReference("memos/added-reference");
const state = createEditorState(comment);
state.metadata.relations = [...state.metadata.relations, addedReference];
clients.getMemo.mockResolvedValue(comment);
await memoService.save(state, { memoName: commentName });
expect(clients.updateMemo).toHaveBeenCalledOnce();
const request = clients.updateMemo.mock.calls[0][0];
expect(request.updateMask.paths).toContain("relations");
expect(request.memo.relations).toEqual([existingReference, addedReference]);
expect(request.memo.relations.every((relation: { type: MemoRelation_Type }) => relation.type === MemoRelation_Type.REFERENCE)).toBe(
true,
);
});
it("ignores a new incoming comment when comparing a parent memo's relations", async () => {
const parentName = "memos/parent";
const outgoingReference = createRelation(MemoRelation_Type.REFERENCE, parentName, "memos/reference");
const editorMemo = create(MemoSchema, {
name: parentName,
content: "Parent",
relations: [outgoingReference],
});
const freshMemo = create(MemoSchema, {
name: parentName,
content: editorMemo.content,
relations: [outgoingReference, createRelation(MemoRelation_Type.COMMENT, "memos/new-comment", parentName)],
});
const state = createEditorState(editorMemo);
clients.getMemo.mockResolvedValue(freshMemo);
const result = await memoService.save(state, { memoName: parentName });
expect(result).toEqual({ memoName: parentName, hasChanges: false });
expect(clients.updateMemo).not.toHaveBeenCalled();
});
});