diff --git a/apps/roam/src/components/settings/DiscourseRelationConfigPanel.tsx b/apps/roam/src/components/settings/DiscourseRelationConfigPanel.tsx index b78dff219..968bf4f7d 100644 --- a/apps/roam/src/components/settings/DiscourseRelationConfigPanel.tsx +++ b/apps/roam/src/components/settings/DiscourseRelationConfigPanel.tsx @@ -44,7 +44,9 @@ import { render as renderToast } from "roamjs-components/components/Toast"; import getPageTitleByPageUid from "roamjs-components/queries/getPageTitleByPageUid"; import updateBlock from "roamjs-components/writes/updateBlock"; import getTextByBlockUid from "roamjs-components/queries/getTextByBlockUid"; -import getDiscourseNodes from "~/utils/getDiscourseNodes"; +import getDiscourseNodes, { + getRelationEndpointNodeTypes, +} from "~/utils/getDiscourseNodes"; import { isRelationComplete } from "~/utils/isRelationComplete"; import { getConditionLabels } from "~/utils/conditionToDatalog"; import { formatHexColor } from "./DiscourseNodeCanvasSettings"; @@ -81,6 +83,7 @@ const edgeDisplayByUid = (uid: string) => export const RelationEditPanel = ({ editingRelationInfo, nodes, + configuredNodeTypes, back, translatorKeys, previewUid, @@ -88,6 +91,7 @@ export const RelationEditPanel = ({ editingRelationInfo: TreeNode; back: () => void; nodes: Record; + configuredNodeTypes: string[]; translatorKeys: string[]; previewUid: string; }) => { @@ -748,7 +752,7 @@ export const RelationEditPanel = ({ ); } }} - items={Object.keys(nodes)} + items={configuredNodeTypes} transformItem={transformItem} /> @@ -765,7 +769,7 @@ export const RelationEditPanel = ({ ).data("node", nodes[e]?.label); } }} - items={Object.keys(nodes)} + items={configuredNodeTypes} transformItem={transformItem} /> @@ -991,16 +995,18 @@ const DiscourseRelationConfigPanel = ({ })), [], ); - const nodes = useMemo(() => { + const { nodes, configuredNodeTypes } = useMemo(() => { + const discourseNodes = getDiscourseNodes(); const nodes = Object.fromEntries( - getDiscourseNodes().map((n) => { + discourseNodes.map((n) => { const color = formatHexColor(n.canvasSettings.color); return [n.type, { label: n.text, format: n.format, color }]; }), ); // TypeError: Iterator value * is not an entry object nodes["*"] = { label: "Any", format: ".+", color: "#000" }; - return nodes; + const configuredNodeTypes = getRelationEndpointNodeTypes(discourseNodes); + return { nodes, configuredNodeTypes }; }, []); const previewUid = useSubTree({ parentUid, key: "preview" }).uid; const [translatorKeys, setTranslatorKeys] = useState(getConditionLabels); @@ -1148,6 +1154,7 @@ const DiscourseRelationConfigPanel = ({
{ + (globalThis as { window: unknown }).window = { + roamAlphaAPI: { + util: { + generateUID: () => "generated-uid", + }, + }, + }; +}); + +const mocks = vi.hoisted(() => ({ + isNewSettingsStoreEnabled: vi.fn(), + getAllDiscourseNodes: vi.fn(), +})); + +vi.mock("~/components/settings/utils/accessors", () => ({ + isNewSettingsStoreEnabled: mocks.isNewSettingsStoreEnabled, + getAllDiscourseNodes: mocks.getAllDiscourseNodes, +})); + +import getDiscourseNodes, { + getRelationEndpointNodeTypes, +} from "~/utils/getDiscourseNodes"; + +const makeUserNode = ({ + text, + type, +}: { + text: string; + type: string; +}): DiscourseNode => ({ + text, + type, + shortcut: "", + specification: [], + backedBy: "user", + canvasSettings: {}, + format: "{content}", +}); + +describe("getRelationEndpointNodeTypes", () => { + beforeEach(() => { + vi.clearAllMocks(); + mocks.isNewSettingsStoreEnabled.mockReturnValue(true); + }); + + it("excludes default Page and Block nodes while keeping user-configured types", () => { + mocks.getAllDiscourseNodes.mockReturnValue([ + makeUserNode({ text: "Claim", type: "CLM" }), + makeUserNode({ text: "Evidence", type: "EVD" }), + ]); + + const discourseNodes = getDiscourseNodes(); + expect(discourseNodes.map((n) => n.type)).toEqual( + expect.arrayContaining(["page-node", "blck-node"]), + ); + + expect(getRelationEndpointNodeTypes(discourseNodes)).toEqual([ + "CLM", + "EVD", + ]); + }); + + it("keeps a user-configured node that shares a default node's name", () => { + mocks.getAllDiscourseNodes.mockReturnValue([ + makeUserNode({ text: "Page", type: "user-page-node" }), + ]); + + expect(getRelationEndpointNodeTypes(getDiscourseNodes())).toEqual([ + "user-page-node", + ]); + }); +}); diff --git a/apps/roam/src/utils/getDiscourseNodes.ts b/apps/roam/src/utils/getDiscourseNodes.ts index 9098737d6..dd973ae3a 100644 --- a/apps/roam/src/utils/getDiscourseNodes.ts +++ b/apps/roam/src/utils/getDiscourseNodes.ts @@ -14,6 +14,9 @@ export const excludeDefaultNodes = (node: DiscourseNode) => { return node.backedBy !== "default"; }; +export const getRelationEndpointNodeTypes = (nodes: DiscourseNode[]) => + nodes.filter(excludeDefaultNodes).map((n) => n.type); + // TODO - only text and type should be required export type DiscourseNode = { text: string;