Skip to content
Merged

wip #2242

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 .mcp.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"mcpServers": {
"next-devtools": {
"command": "npx",
"command": "/opt/homebrew/bin/npx",
"args": ["-y", "next-devtools-mcp@latest"]
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,25 +1,78 @@
import { render, screen, fireEvent } from '@testing-library/react';
import React from 'react';
import CommonDropdownItemsMobileWrapper from '@/components/utils/select/dropdown/CommonDropdownItemsMobileWrapper';
import { fireEvent, render, screen } from "@testing-library/react";
import React from "react";
import CommonDropdownItemsMobileWrapper from "@/components/utils/select/dropdown/CommonDropdownItemsMobileWrapper";

jest.mock("@headlessui/react", () => {
const Dialog = ({ children, className }: any) => (
<div data-testid="dialog" className={className}>
{children}
</div>
);
const DialogPanel = ({ children, className }: any) => (
<div data-testid="dialog-panel" className={className}>
{children}
</div>
);
const DialogTitle = ({ children, className }: any) => (
<div className={className}>{children}</div>
);
const Transition = ({ show, children }: any) =>
show ? <div data-testid="root">{children}</div> : null;
const TransitionChild = ({ children }: any) => <div>{children}</div>;

jest.mock('@headlessui/react', () => {
const Comp = (p: any) => <div {...p}>{p.children}</div>;
return {
Dialog: Object.assign(Comp, { Panel: Comp, Title: Comp }),
Transition: { Root: ({ show, children }: any) => (show ? <div data-testid="root">{children}</div> : null), Child: Comp },
Dialog,
DialogPanel,
DialogTitle,
Transition,
TransitionChild,
};
});

describe('CommonDropdownItemsMobileWrapper', () => {
it('renders when open and closes on button click', () => {
describe("CommonDropdownItemsMobileWrapper", () => {
it("uses the default z-index class when none is provided", () => {
const setOpen = jest.fn();

render(
<CommonDropdownItemsMobileWrapper isOpen={true} setOpen={setOpen}>
<li>child</li>
</CommonDropdownItemsMobileWrapper>
);

expect(screen.getByTestId("dialog")).toHaveClass("tw-z-[1000]");
});

it("applies a custom z-index class when provided", () => {
const setOpen = jest.fn();

render(
<CommonDropdownItemsMobileWrapper isOpen={true} setOpen={setOpen} label="test">
<CommonDropdownItemsMobileWrapper
isOpen={true}
setOpen={setOpen}
zIndexClassName="tw-z-[1020]"
>
<li>child</li>
</CommonDropdownItemsMobileWrapper>
);
expect(screen.getByText('test')).toBeInTheDocument();
fireEvent.click(screen.getByLabelText('Close panel'));

expect(screen.getByTestId("dialog")).toHaveClass("tw-z-[1020]");
});

it("renders when open and closes on button click", () => {
const setOpen = jest.fn();

render(
<CommonDropdownItemsMobileWrapper
isOpen={true}
setOpen={setOpen}
label="test"
>
<li>child</li>
</CommonDropdownItemsMobileWrapper>
);

expect(screen.getByText("test")).toBeInTheDocument();
fireEvent.click(screen.getByLabelText("Close panel"));
expect(setOpen).toHaveBeenCalledWith(false);
});
});
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
import React from "react";
import { render, screen, fireEvent, waitFor } from "@testing-library/react";
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
import WaveDropActionsAddReaction from "@/components/waves/drops/WaveDropActionsAddReaction";
import type { ExtendedDrop } from "@/helpers/waves/drop.helpers";
import { DropSize } from "@/helpers/waves/drop.helpers";
import { ApiDropType } from "@/generated/models/ApiDropType";

const applyOptimisticDropUpdateMock = jest.fn(() => ({ rollback: jest.fn() }));
const setToastMock = jest.fn();
const mobileWrapperDialogMock = jest.fn(
({ isOpen, children, zIndexClassName }: any) =>
isOpen ? (
<div data-testid="mobile-dialog" data-z-index={zIndexClassName}>
{children}
</div>
) : null
);

jest.mock("@/contexts/wave/MyStreamContext", () => ({
useMyStream: jest.fn(() => ({
Expand Down Expand Up @@ -39,7 +47,11 @@ jest.mock("@/services/api/common-api", () => ({
commonApiPost: jest.fn(() => Promise.resolve({})),
}));

// Mock emoji-mart/react Picker and emoji-mart/data
jest.mock("@/components/mobile-wrapper-dialog/MobileWrapperDialog", () => ({
__esModule: true,
default: (props: any) => mobileWrapperDialogMock(props),
}));

jest.mock("@emoji-mart/react", () => ({
__esModule: true,
default: ({ onEmojiSelect }: any) => (
Expand All @@ -54,7 +66,6 @@ jest.mock("@emoji-mart/data", () => ({
default: {},
}));

// Mock useEmoji
jest.mock("@/contexts/EmojiContext", () => ({
useEmoji: jest.fn(() => ({
emojiMap: [],
Expand All @@ -66,7 +77,6 @@ jest.mock("@/contexts/EmojiContext", () => ({
})),
}));

// Mock drop object
const baseDrop = {
id: "12345",
wave: { id: "wave-1" },
Expand All @@ -92,6 +102,7 @@ const tempDrop = {
stableKey: "temp-001",
stableHash: "hash-temp-001",
} as ExtendedDrop;

describe("WaveDropActionsAddReaction", () => {
beforeEach(() => {
jest.clearAllMocks();
Expand Down Expand Up @@ -124,7 +135,6 @@ describe("WaveDropActionsAddReaction", () => {
fireEvent.click(button);
expect(await screen.findByTestId("mock-picker")).toBeInTheDocument();

// Simulate Escape key
fireEvent.keyDown(document, { key: "Escape" });
await waitFor(() => {
expect(screen.queryByTestId("mock-picker")).not.toBeInTheDocument();
Expand All @@ -138,7 +148,6 @@ describe("WaveDropActionsAddReaction", () => {
fireEvent.click(button);
expect(await screen.findByTestId("mock-picker")).toBeInTheDocument();

// Simulate outside click
fireEvent.mouseDown(document.body);
await waitFor(() => {
expect(screen.queryByTestId("mock-picker")).not.toBeInTheDocument();
Expand Down Expand Up @@ -171,4 +180,21 @@ describe("WaveDropActionsAddReaction", () => {
fireEvent.click(button);
expect(await screen.findByTestId("mock-picker")).toBeInTheDocument();
});

it("forwards custom dialog z-index to the mobile wrapper", async () => {
render(
<WaveDropActionsAddReaction
drop={mockDrop}
isMobile={true}
dialogZIndexClassName="tw-z-[1030]"
/>
);

fireEvent.click(screen.getByRole("button", { name: /add reaction/i }));

expect(await screen.findByTestId("mobile-dialog")).toHaveAttribute(
"data-z-index",
"tw-z-[1030]"
);
});
});
128 changes: 118 additions & 10 deletions __tests__/components/waves/drops/WaveDropMobileMenu.test.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,26 @@
import { AuthContext } from "@/components/auth/Auth";
import WaveDropMobileMenu from "@/components/waves/drops/WaveDropMobileMenu";
import { WaveDropLayerProvider } from "@/components/waves/drops/WaveDropLayerContext";
import { ApiDropType } from "@/generated/models/ApiDropType";
import { useDropInteractionRules } from "@/hooks/drops/useDropInteractionRules";
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";

const mockIsMemesWave = jest.fn();
const writeText = jest.fn().mockResolvedValue(undefined);
const addReactionMock = jest.fn((props: any) => (
<div
data-testid="add-reaction"
data-dialog-z-index={props.dialogZIndexClassName}
/>
));
const mobileWrapperMock = jest.fn((props: any) =>
props.isOpen ? (
<div data-testid="wrapper" data-z-index={props.zIndexClassName}>
{props.children}
</div>
) : null
);

jest.mock("@/hooks/drops/useDropInteractionRules", () => ({
useDropInteractionRules: jest.fn(),
Expand All @@ -30,16 +44,19 @@ jest.mock("@/components/waves/drops/WaveDropActionsMarkUnread", () => () => (
jest.mock("@/components/waves/drops/WaveDropActionsRate", () => () => (
<div data-testid="clap" />
));
jest.mock("@/components/waves/drops/WaveDropActionsAddReaction", () => () => (
<div data-testid="add-reaction" />
));
jest.mock("@/components/waves/drops/WaveDropActionsAddReaction", () => ({
__esModule: true,
default: (props: any) => addReactionMock(props),
}));
jest.mock("@/components/waves/drops/WaveDropActionsQuickReact", () => () => (
<div data-testid="quick-react" />
));
jest.mock(
"@/components/utils/select/dropdown/CommonDropdownItemsMobileWrapper",
() => (props: any) =>
props.isOpen ? <div data-testid="wrapper">{props.children}</div> : null
() => ({
__esModule: true,
default: (props: any) => mobileWrapperMock(props),
})
);

jest.mock("@/contexts/SeizeSettingsContext", () => ({
Expand Down Expand Up @@ -70,6 +87,8 @@ const mockedUseDropInteractionRules = jest.mocked(useDropInteractionRules);

beforeEach(() => {
writeText.mockClear();
addReactionMock.mockClear();
mobileWrapperMock.mockClear();
mockIsMemesWave.mockReturnValue(false);
mockedUseDropInteractionRules.mockReturnValue({
canShowVote: true,
Expand Down Expand Up @@ -107,7 +126,6 @@ test("copies serial jump links for non-memes drops", async () => {
longPressTriggered={false}
setOpen={jest.fn()}
onReply={jest.fn()}
onQuote={jest.fn()}
onAddReaction={jest.fn()}
/>
</AuthContext.Provider>
Expand Down Expand Up @@ -142,7 +160,6 @@ test("copies canonical drop links for memes submissions", async () => {
longPressTriggered={false}
setOpen={jest.fn()}
onReply={jest.fn()}
onQuote={jest.fn()}
onAddReaction={jest.fn()}
/>
</AuthContext.Provider>
Expand Down Expand Up @@ -177,7 +194,6 @@ test("hides follow and clap when author and memes wave", () => {
longPressTriggered={false}
setOpen={jest.fn()}
onReply={jest.fn()}
onQuote={jest.fn()}
onAddReaction={jest.fn()}
/>
</AuthContext.Provider>
Expand Down Expand Up @@ -223,7 +239,6 @@ test("shows pinned-drop action in the mobile menu for admins", () => {
longPressTriggered={false}
setOpen={jest.fn()}
onReply={jest.fn()}
onQuote={jest.fn()}
onAddReaction={jest.fn()}
/>
</AuthContext.Provider>
Expand Down Expand Up @@ -269,7 +284,6 @@ test("does not show pinned-drop action in the mobile menu for non-admins", () =>
longPressTriggered={false}
setOpen={jest.fn()}
onReply={jest.fn()}
onQuote={jest.fn()}
onAddReaction={jest.fn()}
/>
</AuthContext.Provider>
Expand Down Expand Up @@ -358,3 +372,97 @@ test("shows only copy link in the mobile menu for guests", () => {
expect(screen.queryByTestId("set-pinned-drop")).toBeNull();
expect(screen.queryByTestId("delete")).toBeNull();
});

test("uses single-drop layer overrides when provided by context", () => {
const drop = {
id: "1",
serial_no: 1,
wave: { id: "w" },
drop_type: ApiDropType.Chat,
author: { handle: "alice" },
} as any;

render(
<WaveDropLayerProvider
value={{
mobileMenuZIndexClassName: "tw-z-[1020]",
mobileDialogZIndexClassName: "tw-z-[1030]",
}}
>
<AuthContext.Provider
value={
{
connectedProfile: { handle: "alice" },
activeProfileProxy: null,
} as any
}
>
<WaveDropMobileMenu
drop={drop}
isOpen
showReplyAndQuote
longPressTriggered={false}
setOpen={jest.fn()}
onReply={jest.fn()}
onAddReaction={jest.fn()}
/>
</AuthContext.Provider>
</WaveDropLayerProvider>
);

expect(screen.getByTestId("wrapper")).toHaveAttribute(
"data-z-index",
"tw-z-[1020]"
);
expect(screen.getByTestId("add-reaction")).toHaveAttribute(
"data-dialog-z-index",
"tw-z-[1030]"
);
});

test("preserves default layer values when context overrides are undefined", () => {
const drop = {
id: "1",
serial_no: 1,
wave: { id: "w" },
drop_type: ApiDropType.Chat,
author: { handle: "alice" },
} as any;

render(
<WaveDropLayerProvider
value={{
mobileMenuZIndexClassName: undefined,
mobileDialogZIndexClassName: "tw-z-[1030]",
}}
>
<AuthContext.Provider
value={
{
connectedProfile: { handle: "alice" },
activeProfileProxy: null,
} as any
}
>
<WaveDropMobileMenu
drop={drop}
isOpen
showReplyAndQuote
longPressTriggered={false}
setOpen={jest.fn()}
onReply={jest.fn()}
onAddReaction={jest.fn()}
/>
</AuthContext.Provider>
</WaveDropLayerProvider>
);

expect(screen.getByTestId("wrapper")).toHaveAttribute(
"data-z-index",
"tw-z-[1000]"
);
expect(screen.getByTestId("add-reaction")).toHaveAttribute(
"data-dialog-z-index",
"tw-z-[1030]"
);
});
Loading
Loading