diff --git a/package.json b/package.json index bddd6175..84aabcfb 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "scripts": { "dev": "vite", "build": "vite build", + "test": "node --test", "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0", "preview": "vite preview" }, diff --git a/src/components/EditorHeader/ControlPanel.jsx b/src/components/EditorHeader/ControlPanel.jsx index cc1968d7..effed40a 100644 --- a/src/components/EditorHeader/ControlPanel.jsx +++ b/src/components/EditorHeader/ControlPanel.jsx @@ -47,8 +47,6 @@ import { } from "../../data/constants"; import jsPDF from "jspdf"; import { useHotkeys } from "react-hotkeys-hook"; -import { Validator } from "jsonschema"; -import { areaSchema, noteSchema, tableSchema } from "../../data/schemas"; import { db } from "../../data/db"; import { useLayout, @@ -67,6 +65,7 @@ import { } from "../../hooks"; import { enterFullscreen, exitFullscreen } from "../../utils/fullscreen"; import { dataURItoBlob } from "../../utils/utils"; +import { classifyClipboardPayload } from "../../utils/clipboard"; import { IconAddArea, IconAddNote, IconAddTable } from "../../icons"; import LayoutDropdown from "./LayoutDropdown"; import Sidesheet from "./SideSheet/Sidesheet"; @@ -710,29 +709,35 @@ export default function ControlPanel({ } catch (error) { return; } - const v = new Validator(); - console.log(obj); - if (v.validate(obj, tableSchema).valid) { + + const clipboardEntity = classifyClipboardPayload(obj); + if (!clipboardEntity) { + return; + } + + const { payload } = clipboardEntity; + + if (clipboardEntity.type === "table") { addTable({ table: { - ...obj, - x: obj.x + 20, - y: obj.y + 20, + ...payload, + x: payload.x + 20, + y: payload.y + 20, id: nanoid(), }, }); - } else if (v.validate(obj, areaSchema).valid) { + } else if (clipboardEntity.type === "area") { addArea({ - ...obj, - x: obj.x + 20, - y: obj.y + 20, + ...payload, + x: payload.x + 20, + y: payload.y + 20, id: areas.length, }); - } else if (v.validate(obj, noteSchema)) { + } else if (clipboardEntity.type === "note") { addNote({ - ...obj, - x: obj.x + 20, - y: obj.y + 20, + ...payload, + x: payload.x + 20, + y: payload.y + 20, id: notes.length, }); } diff --git a/src/utils/clipboard.js b/src/utils/clipboard.js new file mode 100644 index 00000000..21f0bbcf --- /dev/null +++ b/src/utils/clipboard.js @@ -0,0 +1,25 @@ +import { Validator } from "jsonschema"; +import { tableSchema, areaSchema, noteSchema } from "../data/schemas"; + +const validator = new Validator(); + +const schemaMap = [ + { type: "table", schema: tableSchema }, + { type: "area", schema: areaSchema }, + { type: "note", schema: noteSchema }, +]; + +export function classifyClipboardPayload(payload) { + if (!payload || typeof payload !== "object") { + return null; + } + + for (const { type, schema } of schemaMap) { + if (validator.validate(payload, schema).valid) { + return { type, payload }; + } + } + + return null; +} + diff --git a/tests/clipboard.test.js b/tests/clipboard.test.js new file mode 100644 index 00000000..b9761b94 --- /dev/null +++ b/tests/clipboard.test.js @@ -0,0 +1,67 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { classifyClipboardPayload } from "../src/utils/clipboard.js"; + +const baseField = { + id: "fld_1", + name: "id", + type: "INTEGER", + default: "", + check: "", + primary: true, + unique: true, + notNull: true, + increment: true, + comment: "", +}; + +test("classifies a valid table payload", () => { + const payload = { + id: "tbl_1", + name: "users", + x: 0, + y: 0, + fields: [baseField], + comment: "", + indices: [], + color: "#000000", + }; + + const result = classifyClipboardPayload(payload); + assert.ok(result); + assert.equal(result.type, "table"); + assert.equal(result.payload, payload); +}); + +test("classifies a valid note payload", () => { + const payload = { + id: 0, + x: 10, + y: 10, + title: "Note", + content: "", + color: "#ffffff", + height: 80, + }; + + const result = classifyClipboardPayload(payload); + assert.ok(result); + assert.equal(result.type, "note"); +}); + +test("rejects invalid note payloads", () => { + const payload = { + id: 0, + title: "Broken note", + }; + + const result = classifyClipboardPayload(payload); + assert.equal(result, null); +}); + +test("returns null for non-object values", () => { + assert.equal(classifyClipboardPayload(null), null); + assert.equal(classifyClipboardPayload("string"), null); + assert.equal(classifyClipboardPayload(42), null); +}); +