From a5d0c625d5cc0ac8b011eab0dac434a3b6dcd72b Mon Sep 17 00:00:00 2001 From: Deon Sanchez <69873175+deon-sanchez@users.noreply.github.com> Date: Wed, 17 Dec 2025 09:49:59 -0700 Subject: [PATCH 1/2] added min size for sticky note --- .secrets.baseline | 8 ++++---- src/frontend/src/CustomNodes/NoteNode/index.tsx | 14 +++++++------- src/frontend/src/constants/constants.ts | 5 +++-- .../FlowPage/components/PageComponent/index.tsx | 7 +++++-- 4 files changed, 19 insertions(+), 15 deletions(-) diff --git a/.secrets.baseline b/.secrets.baseline index a292fc06f327..9ab98099a69e 100644 --- a/.secrets.baseline +++ b/.secrets.baseline @@ -1403,7 +1403,7 @@ "filename": "src/frontend/src/constants/constants.ts", "hashed_secret": "19a2fbd0dd38b4097f419c962342ef5e109eab07", "is_verified": false, - "line_number": 737, + "line_number": 751, "is_secret": false }, { @@ -1411,7 +1411,7 @@ "filename": "src/frontend/src/constants/constants.ts", "hashed_secret": "3806954324550e26ef5de85d007f1746825a073c", "is_verified": false, - "line_number": 738, + "line_number": 752, "is_secret": false }, { @@ -1419,7 +1419,7 @@ "filename": "src/frontend/src/constants/constants.ts", "hashed_secret": "c04f8fbf55c9096907a982750b1c6b0e4c1dd658", "is_verified": false, - "line_number": 913, + "line_number": 926, "is_secret": false } ], @@ -1528,5 +1528,5 @@ } ] }, - "generated_at": "2025-12-03T21:24:04Z" + "generated_at": "2025-12-17T16:48:17Z" } diff --git a/src/frontend/src/CustomNodes/NoteNode/index.tsx b/src/frontend/src/CustomNodes/NoteNode/index.tsx index 7ad16c792d6c..c3414d5638a8 100644 --- a/src/frontend/src/CustomNodes/NoteNode/index.tsx +++ b/src/frontend/src/CustomNodes/NoteNode/index.tsx @@ -3,6 +3,7 @@ import { debounce } from "lodash"; import { useMemo, useRef, useState } from "react"; import { COLOR_OPTIONS, + DEFAULT_NOTE_SIZE, NOTE_NODE_MIN_HEIGHT, NOTE_NODE_MIN_WIDTH, } from "@/constants/constants"; @@ -14,7 +15,6 @@ import NodeDescription from "../GenericNode/components/NodeDescription"; import NoteToolbarComponent from "./NoteToolbarComponent"; const CHAR_LIMIT = 2500; -const DEFAULT_NOTE_SIZE = 324; const TRANSPARENT_COLOR = "#00000000"; /** @@ -103,8 +103,8 @@ function NoteNode({ () => currentFlow?.data?.nodes.find((node) => node.id === data.id), [currentFlow, data.id], ); - const nodeWidth = nodeData?.measured?.width ?? DEFAULT_NOTE_SIZE; - const nodeHeight = nodeData?.measured?.height ?? DEFAULT_NOTE_SIZE; + const nodeWidth = nodeData?.width ?? DEFAULT_NOTE_SIZE; + const nodeHeight = nodeData?.height ?? DEFAULT_NOTE_SIZE; // Debounced resize handler to avoid excessive state updates during drag const debouncedResize = useMemo( @@ -146,8 +146,8 @@ function NoteNode({ return ( <> debouncedResize(width, height)} isVisible={selected} lineClassName="!border !border-muted-foreground" @@ -162,8 +162,8 @@ function NoteNode({ ref={nodeRef} data-testid="note_node" style={{ - minWidth: nodeWidth, - minHeight: nodeHeight, + width: nodeWidth, + height: nodeHeight, backgroundColor: resolvedBgColor, }} className={cn( diff --git a/src/frontend/src/constants/constants.ts b/src/frontend/src/constants/constants.ts index bad8532c6975..d2ee7f8375bb 100644 --- a/src/frontend/src/constants/constants.ts +++ b/src/frontend/src/constants/constants.ts @@ -882,8 +882,9 @@ export const DRAG_EVENTS_CUSTOM_TYPESS = { "text/plain": "text/plain", }; -export const NOTE_NODE_MIN_WIDTH = 324; -export const NOTE_NODE_MIN_HEIGHT = 324; +export const NOTE_NODE_MIN_WIDTH = 260; +export const NOTE_NODE_MIN_HEIGHT = 100; +export const DEFAULT_NOTE_SIZE = 324; export const COLOR_OPTIONS = { amber: "hsl(var(--note-amber))", diff --git a/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx b/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx index 60afa87a7bfe..bc678414f8df 100644 --- a/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx +++ b/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx @@ -24,6 +24,7 @@ import NoteNode from "@/CustomNodes/NoteNode"; import FlowToolbar from "@/components/core/flowToolbarComponent"; import { COLOR_OPTIONS, + DEFAULT_NOTE_SIZE, NOTE_NODE_MIN_HEIGHT, NOTE_NODE_MIN_WIDTH, } from "@/constants/constants"; @@ -161,8 +162,8 @@ export default function Page({ const addComponent = useAddComponent(); const zoomLevel = reactFlowInstance?.getZoom(); - const shadowBoxWidth = NOTE_NODE_MIN_WIDTH * (zoomLevel || 1); - const shadowBoxHeight = NOTE_NODE_MIN_HEIGHT * (zoomLevel || 1); + const shadowBoxWidth = DEFAULT_NOTE_SIZE * (zoomLevel || 1); + const shadowBoxHeight = DEFAULT_NOTE_SIZE * (zoomLevel || 1); const shadowBoxBackgroundColor = COLOR_OPTIONS[Object.keys(COLOR_OPTIONS)[0]]; const handleGroupNode = useCallback(() => { @@ -656,6 +657,8 @@ export default function Page({ id: newId, type: "noteNode", position: position || { x: 0, y: 0 }, + width: DEFAULT_NOTE_SIZE, + height: DEFAULT_NOTE_SIZE, data: { ...data, id: newId, From c97202d31ed7d11cd1ab1d0a87d9c857e4b3d5a1 Mon Sep 17 00:00:00 2001 From: Deon Sanchez <69873175+deon-sanchez@users.noreply.github.com> Date: Wed, 17 Dec 2025 10:22:40 -0700 Subject: [PATCH 2/2] tests created --- .../__tests__/note-node-shrink.test.tsx | 221 ++++++++++++++++++ 1 file changed, 221 insertions(+) create mode 100644 src/frontend/src/CustomNodes/NoteNode/__tests__/note-node-shrink.test.tsx diff --git a/src/frontend/src/CustomNodes/NoteNode/__tests__/note-node-shrink.test.tsx b/src/frontend/src/CustomNodes/NoteNode/__tests__/note-node-shrink.test.tsx new file mode 100644 index 000000000000..e1d5d0ad30f0 --- /dev/null +++ b/src/frontend/src/CustomNodes/NoteNode/__tests__/note-node-shrink.test.tsx @@ -0,0 +1,221 @@ +/** + * Unit tests for NoteNode shrink/resize behavior + * Validates that sticky notes can shrink to the correct minimum size + */ + +import { render, screen } from "@testing-library/react"; +import { + DEFAULT_NOTE_SIZE, + NOTE_NODE_MIN_HEIGHT, + NOTE_NODE_MIN_WIDTH, +} from "@/constants/constants"; +import type { NoteDataType } from "@/types/flow"; + +// Mock dependencies +const mockSetNode = jest.fn(); +const mockCurrentFlow = { + data: { + nodes: [] as Array<{ id: string; width?: number; height?: number }>, + }, +}; + +jest.mock("@/stores/flowStore", () => ({ + __esModule: true, + default: (selector: (state: any) => any) => + selector({ + currentFlow: mockCurrentFlow, + setNode: mockSetNode, + }), +})); + +jest.mock("@xyflow/react", () => ({ + NodeResizer: ({ + minWidth, + minHeight, + onResize, + isVisible, + }: { + minWidth: number; + minHeight: number; + onResize: (event: any, params: { width: number; height: number }) => void; + isVisible?: boolean; + }) => ( +
+ ), +})); + +jest.mock("@/shared/hooks/use-alternate", () => ({ + useAlternate: (initial: boolean) => [initial, jest.fn()], +})); + +jest.mock("../NoteToolbarComponent", () => ({ + __esModule: true, + default: () =>
, +})); + +jest.mock("../../GenericNode/components/NodeDescription", () => ({ + __esModule: true, + default: () =>
, +})); + +jest.mock("@/utils/utils", () => ({ + cn: (...classes: any[]) => classes.filter(Boolean).join(" "), +})); + +// Import component after mocks are set up +import NoteNode from "../index"; + +describe("NoteNode Shrink Behavior", () => { + const createMockData = ( + id: string = "test-note", + backgroundColor: string = "amber", + ): NoteDataType => + ({ + id, + type: "noteNode", + node: { + description: "Test note content", + template: { backgroundColor }, + }, + }) as NoteDataType; + + beforeEach(() => { + jest.clearAllMocks(); + mockCurrentFlow.data.nodes = []; + }); + + describe("Minimum Size Constraints", () => { + it("should configure NodeResizer with correct minimum width", () => { + const data = createMockData(); + render(); + + const resizer = screen.getByTestId("node-resizer"); + expect(Number(resizer.dataset.minWidth)).toBe(NOTE_NODE_MIN_WIDTH); + expect(NOTE_NODE_MIN_WIDTH).toBe(260); + }); + + it("should configure NodeResizer with correct minimum height", () => { + const data = createMockData(); + render(); + + const resizer = screen.getByTestId("node-resizer"); + expect(Number(resizer.dataset.minHeight)).toBe(NOTE_NODE_MIN_HEIGHT); + expect(NOTE_NODE_MIN_HEIGHT).toBe(100); + }); + + it("should show resizer only when selected", () => { + const data = createMockData(); + + // When selected + const { rerender } = render(); + let resizer = screen.getByTestId("node-resizer"); + expect(resizer.dataset.isVisible).toBe("true"); + + // When not selected + rerender(); + resizer = screen.getByTestId("node-resizer"); + expect(resizer.dataset.isVisible).toBe("false"); + }); + }); + + describe("Default Size Behavior", () => { + it("should use DEFAULT_NOTE_SIZE when no dimensions are stored", () => { + const data = createMockData("note-1"); + mockCurrentFlow.data.nodes = []; + + render(); + + const noteNode = screen.getByTestId("note_node"); + expect(noteNode.style.width).toBe(`${DEFAULT_NOTE_SIZE}px`); + expect(noteNode.style.height).toBe(`${DEFAULT_NOTE_SIZE}px`); + expect(DEFAULT_NOTE_SIZE).toBe(324); + }); + + it("should use stored dimensions from flow state", () => { + const data = createMockData("note-1"); + const customWidth = 400; + const customHeight = 300; + + mockCurrentFlow.data.nodes = [ + { id: "note-1", width: customWidth, height: customHeight }, + ]; + + render(); + + const noteNode = screen.getByTestId("note_node"); + expect(noteNode.style.width).toBe(`${customWidth}px`); + expect(noteNode.style.height).toBe(`${customHeight}px`); + }); + }); + + describe("Shrink to Minimum Size", () => { + it("should allow shrinking to minimum dimensions", () => { + const data = createMockData("note-1"); + + // Simulate a note that has been shrunk to minimum size + mockCurrentFlow.data.nodes = [ + { + id: "note-1", + width: NOTE_NODE_MIN_WIDTH, + height: NOTE_NODE_MIN_HEIGHT, + }, + ]; + + render(); + + const noteNode = screen.getByTestId("note_node"); + expect(noteNode.style.width).toBe(`${NOTE_NODE_MIN_WIDTH}px`); + expect(noteNode.style.height).toBe(`${NOTE_NODE_MIN_HEIGHT}px`); + }); + + it("should render correctly at minimum width", () => { + const data = createMockData("note-1"); + mockCurrentFlow.data.nodes = [ + { id: "note-1", width: NOTE_NODE_MIN_WIDTH, height: DEFAULT_NOTE_SIZE }, + ]; + + render(); + + const noteNode = screen.getByTestId("note_node"); + expect(noteNode.style.width).toBe(`${NOTE_NODE_MIN_WIDTH}px`); + }); + + it("should render correctly at minimum height", () => { + const data = createMockData("note-1"); + mockCurrentFlow.data.nodes = [ + { + id: "note-1", + width: DEFAULT_NOTE_SIZE, + height: NOTE_NODE_MIN_HEIGHT, + }, + ]; + + render(); + + const noteNode = screen.getByTestId("note_node"); + expect(noteNode.style.height).toBe(`${NOTE_NODE_MIN_HEIGHT}px`); + }); + }); + + describe("Size Constraints Validation", () => { + it("should have minimum width less than default size", () => { + expect(NOTE_NODE_MIN_WIDTH).toBeLessThan(DEFAULT_NOTE_SIZE); + }); + + it("should have minimum height less than default size", () => { + expect(NOTE_NODE_MIN_HEIGHT).toBeLessThan(DEFAULT_NOTE_SIZE); + }); + + it("should have reasonable minimum dimensions for usability", () => { + // Minimum width should be at least 200px for readability + expect(NOTE_NODE_MIN_WIDTH).toBeGreaterThanOrEqual(200); + // Minimum height should be at least 80px for content + expect(NOTE_NODE_MIN_HEIGHT).toBeGreaterThanOrEqual(80); + }); + }); +});