Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/components/editor/callout-plugin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ export function CalloutPlugin(): null {

if (!emojiSpan) {
emojiSpan = document.createElement("span");
emojiSpan.className = "callout-emoji select-none text-base shrink-0";
emojiSpan.className = "callout-emoji select-none text-lg shrink-0";
emojiSpan.contentEditable = "false";
dom.insertBefore(emojiSpan, dom.firstChild);
}
Expand Down
64 changes: 64 additions & 0 deletions src/components/editor/design-spec-compliance.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { describe, it, expect } from "vitest";
import { readFileSync } from "fs";
import { resolve } from "path";

/**
* Regression tests for issue #64: UI does not match design spec after PR #59.
*
* These tests verify that editor plugin source files comply with the design
* spec constraints. They catch violations at the source level since the
* plugins manipulate DOM directly and aren't easily unit-testable.
*/

function readSource(relativePath: string): string {
return readFileSync(resolve(__dirname, relativePath), "utf-8");
}

describe("callout-plugin design spec compliance", () => {
const source = readSource("./callout-plugin.tsx");

it("callout emoji does not use text-base (not in typography scale)", () => {
// The typography scale does not include text-base. Emoji should use
// text-lg or text-sm per the design spec.
const emojiClassMatch = source.match(
/callout-emoji[^"]*text-base[^"]*/
);
expect(emojiClassMatch).toBeNull();
});

it("callout emoji uses an allowed typography size", () => {
const emojiClassLine = source.match(
/callout-emoji[^"]*?(text-(?:sm|lg|xl|2xl|3xl))/
);
expect(emojiClassLine).not.toBeNull();
});
});

describe("draggable-block-plugin design spec compliance", () => {
const source = readSource("./draggable-block-plugin.tsx");

it("drag handle does not use transition classes (hover states must be instant)", () => {
// Design spec: "Hover states: no transition (instant)."
// The drag handle className must not include transition-opacity or duration-*.
const menuClassMatch = source.match(
/DRAGGABLE_BLOCK_MENU_CLASSNAME[^`]*transition-opacity/
);
expect(menuClassMatch).toBeNull();
});

it("dragging element applies scale(1.02) per drag-and-drop spec", () => {
// Design spec: "Dragging: element at 50% opacity, slight scale (scale-[1.02]), shadow-lg."
expect(source).toContain('style.transform = "scale(1.02)"');
});

it("dragging element applies shadow-lg equivalent per drag-and-drop spec", () => {
// The shadow-lg value from Tailwind's theme
expect(source).toContain("style.boxShadow");
expect(source).toContain("0 10px 15px -3px");
});

it("drag end resets transform and boxShadow", () => {
expect(source).toContain('style.transform = ""');
expect(source).toContain('style.boxShadow = ""');
});
});
10 changes: 9 additions & 1 deletion src/components/editor/draggable-block-plugin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,9 @@ export function DraggableBlockPlugin({
// Set drag image
const dragImage = target.cloneNode(true) as HTMLElement;
dragImage.style.opacity = "0.5";
dragImage.style.transform = "scale(1.02)";
dragImage.style.boxShadow =
"0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1)";
dragImage.style.position = "absolute";
dragImage.style.top = "-1000px";
document.body.appendChild(dragImage);
Expand All @@ -191,6 +194,9 @@ export function DraggableBlockPlugin({

draggingBlockElemRef.current = target;
target.style.opacity = "0.5";
target.style.transform = "scale(1.02)";
target.style.boxShadow =
"0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1)";
},
[editor]
);
Expand All @@ -199,6 +205,8 @@ export function DraggableBlockPlugin({
isDraggingRef.current = false;
if (draggingBlockElemRef.current) {
draggingBlockElemRef.current.style.opacity = "1";
draggingBlockElemRef.current.style.transform = "";
draggingBlockElemRef.current.style.boxShadow = "";
draggingBlockElemRef.current = null;
}
if (dropIndicatorRef.current) {
Expand Down Expand Up @@ -292,7 +300,7 @@ export function DraggableBlockPlugin({
{/* Drag handle */}
<div
ref={menuRef}
className={`${DRAGGABLE_BLOCK_MENU_CLASSNAME} absolute z-10 cursor-grab opacity-0 transition-opacity duration-100 active:cursor-grabbing`}
className={`${DRAGGABLE_BLOCK_MENU_CLASSNAME} absolute z-10 cursor-grab opacity-0 active:cursor-grabbing`}
draggable
onDragStart={handleDragStart}
onDragEnd={handleDragEnd}
Expand Down
Loading