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
Original file line number Diff line number Diff line change
@@ -1,74 +1,144 @@
import React from 'react';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { DefaultWaveLeaderboardDrop } from '@/components/waves/leaderboard/drops/DefaultWaveLeaderboardDrop';
import React from "react";
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { DefaultWaveLeaderboardDrop } from "@/components/waves/leaderboard/drops/DefaultWaveLeaderboardDrop";

jest.mock('next/navigation', () => ({
jest.mock("next/navigation", () => ({
useRouter: jest.fn(() => ({ push: jest.fn() })),
usePathname: () => '/',
useSearchParams: () => ({ toString: () => '', get: () => null }),
usePathname: () => "/",
useSearchParams: () => ({ toString: () => "", get: () => null }),
}));
jest.mock('@/hooks/drops/useDropInteractionRules', () => ({
jest.mock("@/hooks/drops/useDropInteractionRules", () => ({
useDropInteractionRules: jest.fn(),
}));
jest.mock('@/hooks/useDeviceInfo', () => jest.fn());
jest.mock('@/hooks/isMobileScreen', () => jest.fn());
jest.mock('@/hooks/useLongPressInteraction', () => jest.fn());
jest.mock('@/components/voting', () => ({
jest.mock("@/hooks/useDeviceInfo", () => jest.fn());
jest.mock("@/hooks/isMobileScreen", () => jest.fn());
jest.mock("@/hooks/useLongPressInteraction", () => jest.fn());
jest.mock("@/components/voting", () => ({
VotingModal: (p: any) => <div data-testid="modal">{String(p.isOpen)}</div>,
MobileVotingModal: (p: any) => <div data-testid="mobile">{String(p.isOpen)}</div>,
MobileVotingModal: (p: any) => (
<div data-testid="mobile">{String(p.isOpen)}</div>
),
}));
jest.mock('@/components/voting/VotingModalButton', () => (p: any) => <button data-testid="vote-btn" onClick={p.onClick} />);
jest.mock('@/components/waves/drops/WaveDropActionsOptions', () => ({ __esModule: true, default: () => <div data-testid="options" /> }));
jest.mock('@/components/waves/drops/WaveDropActionsOpen', () => ({ __esModule: true, default: () => <div /> }));
jest.mock('@/components/waves/drops/WaveDropMobileMenuOpen', () => () => <div />);
jest.mock('@/components/waves/drops/WaveDropMobileMenuDelete', () => () => <div />);
jest.mock('@/components/utils/select/dropdown/CommonDropdownItemsMobileWrapper', () => (p: any) => <div>{p.children}</div>);
jest.mock('@/components/waves/leaderboard/drops/header/WaveLeaderboardDropHeader', () => ({ WaveLeaderboardDropHeader: () => <div data-testid="header" /> }));
jest.mock('@/components/waves/leaderboard/content/WaveLeaderboardDropContent', () => ({ WaveLeaderboardDropContent: () => <div data-testid="content" /> }));
jest.mock('@/components/waves/leaderboard/drops/footer/WaveLeaderboardDropFooter', () => ({ WaveLeaderboardDropFooter: () => <div data-testid="footer" /> }));
jest.mock('@/components/waves/leaderboard/drops/header/WaveleaderboardDropRaters', () => ({ WaveLeaderboardDropRaters: () => <div data-testid="raters" /> }));
jest.mock("@/components/voting/VotingModalButton", () => (p: any) => (
<button data-testid="vote-btn" onClick={p.onClick} />
));
jest.mock("@/components/waves/drops/WaveDropActionsOptions", () => ({
__esModule: true,
default: () => <div data-testid="options" />,
}));
jest.mock("@/components/waves/drops/WaveDropActionsOpen", () => ({
__esModule: true,
default: () => <div />,
}));
jest.mock("@/components/waves/drops/WaveDropMobileMenuOpen", () => () => (
<div />
));
jest.mock("@/components/waves/drops/WaveDropMobileMenuDelete", () => () => (
<div />
));
jest.mock(
"@/components/utils/select/dropdown/CommonDropdownItemsMobileWrapper",
() => (p: any) => <div>{p.children}</div>
);
jest.mock(
"@/components/waves/leaderboard/drops/header/WaveLeaderboardDropHeader",
() => ({ WaveLeaderboardDropHeader: () => <div data-testid="header" /> })
);
jest.mock(
"@/components/waves/leaderboard/content/WaveLeaderboardDropContent",
() => ({ WaveLeaderboardDropContent: () => <div data-testid="content" /> })
);
jest.mock(
"@/components/waves/leaderboard/drops/footer/WaveLeaderboardDropFooter",
() => ({ WaveLeaderboardDropFooter: () => <div data-testid="footer" /> })
);
jest.mock(
"@/components/waves/leaderboard/drops/header/WaveleaderboardDropRaters",
() => ({ WaveLeaderboardDropRaters: () => <div data-testid="raters" /> })
);

const useRules = require('@/hooks/drops/useDropInteractionRules').useDropInteractionRules as jest.Mock;
const useDeviceInfo = require('@/hooks/useDeviceInfo') as jest.Mock;
const useIsMobileScreen = require('@/hooks/isMobileScreen') as jest.Mock;
const useLongPressInteraction = require('@/hooks/useLongPressInteraction') as jest.Mock;
const useRules = require("@/hooks/drops/useDropInteractionRules")
.useDropInteractionRules as jest.Mock;
const useDeviceInfo = require("@/hooks/useDeviceInfo") as jest.Mock;
const useIsMobileScreen = require("@/hooks/isMobileScreen") as jest.Mock;
const useLongPressInteraction =
require("@/hooks/useLongPressInteraction") as jest.Mock;

const drop = {
id: 'd1',
const drop = {
id: "d1",
rank: 1,
author: {
handle: 'testuser',
handle: "testuser",
pfp: null,
level: 1,
cic: 0
cic: 0,
},
created_at: new Date().toISOString()
created_at: new Date().toISOString(),
} as any;
const wave = { id: 'w1' } as any;
const wave = { id: "w1" } as any;

Comment thread
simo6529 marked this conversation as resolved.
beforeEach(() => {
useLongPressInteraction.mockReturnValue({ isActive: false, setIsActive: jest.fn(), touchHandlers: {} });
useLongPressInteraction.mockReturnValue({
isActive: false,
setIsActive: jest.fn(),
touchHandlers: {},
});
});

test('opens voting modal when button clicked', async () => {
test("opens voting modal when button clicked", async () => {
const user = userEvent.setup();
useRules.mockReturnValue({ canShowVote: true, canDelete: true });
useDeviceInfo.mockReturnValue({ hasTouchScreen: false });
useIsMobileScreen.mockReturnValue(false);
render(<DefaultWaveLeaderboardDrop drop={drop} wave={wave} onDropClick={jest.fn()} />);
expect(screen.getByTestId('modal')).toHaveTextContent('false');
await user.click(screen.getByTestId('vote-btn'));
expect(screen.getByTestId('modal')).toHaveTextContent('true');
expect(screen.getByTestId('options')).toBeInTheDocument();
render(
<DefaultWaveLeaderboardDrop
drop={drop}
wave={wave}
onDropClick={jest.fn()}
/>
);
expect(screen.getByTestId("modal")).toHaveTextContent("false");
await user.click(screen.getByTestId("vote-btn"));
expect(screen.getByTestId("modal")).toHaveTextContent("true");
expect(screen.getByTestId("options")).toBeInTheDocument();
});

test('uses mobile modal and hides options when cannot delete', () => {
test("uses mobile modal and hides options when cannot delete", () => {
useRules.mockReturnValue({ canShowVote: true, canDelete: false });
useDeviceInfo.mockReturnValue({ hasTouchScreen: false });
useIsMobileScreen.mockReturnValue(true);
useLongPressInteraction.mockReturnValue({ isActive: false, setIsActive: jest.fn(), touchHandlers: {} });
render(<DefaultWaveLeaderboardDrop drop={drop} wave={wave} onDropClick={jest.fn()} />);
expect(screen.getByTestId('mobile')).toHaveTextContent('false');
expect(screen.queryByTestId('options')).toBeNull();
useLongPressInteraction.mockReturnValue({
isActive: false,
setIsActive: jest.fn(),
touchHandlers: {},
});
render(
<DefaultWaveLeaderboardDrop
drop={drop}
wave={wave}
onDropClick={jest.fn()}
/>
);
expect(screen.getByTestId("mobile")).toHaveTextContent("false");
expect(screen.queryByTestId("options")).toBeNull();
});

test("keeps native touch scrolling enabled for long-press handlers", () => {
useRules.mockReturnValue({ canShowVote: true, canDelete: false });
useDeviceInfo.mockReturnValue({ hasTouchScreen: true });
useIsMobileScreen.mockReturnValue(true);

render(
<DefaultWaveLeaderboardDrop
drop={drop}
wave={wave}
onDropClick={jest.fn()}
/>
);

expect(useLongPressInteraction).toHaveBeenCalledWith({
hasTouchScreen: true,
preventDefault: false,
});
});
2 changes: 1 addition & 1 deletion components/brain/BrainMobile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -344,7 +344,7 @@ const BrainMobile: React.FC<Props> = ({ children }) => {
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: -20 }}
transition={{ duration: 0.2, ease: "easeInOut" }}
className="tw-flex-1"
className="tw-min-w-0 tw-flex-1"
>
{viewComponents[activeView]}
</motion.div>
Expand Down
4 changes: 2 additions & 2 deletions components/brain/my-stream/MyStreamWave.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ const MyStreamWave: React.FC<MyStreamWaveProps> = ({ waveId }) => {

return (
<div
className="tailwind-scope tw-relative tw-flex tw-h-full tw-flex-col"
className="tailwind-scope tw-relative tw-flex tw-h-full tw-min-w-0 tw-flex-col"
key={stableWaveKey}
>
{/* Always render tab container (hidden on app inside MyStreamWaveTabs) */}
Expand All @@ -147,7 +147,7 @@ const MyStreamWave: React.FC<MyStreamWaveProps> = ({ waveId }) => {
/>

<div
className="tw-relative tw-flex-grow tw-overflow-hidden"
className="tw-relative tw-min-w-0 tw-flex-grow tw-overflow-hidden"
role="tabpanel"
id={getContentTabPanelId(activeContentTab)}
>
Expand Down
4 changes: 2 additions & 2 deletions components/brain/my-stream/MyStreamWaveLeaderboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ const MyStreamWaveLeaderboard: React.FC<MyStreamWaveLeaderboardProps> = ({
}, []);

const containerClassName = useMemo(() => {
return `tw-w-full tw-flex tw-flex-col tw-rounded-t-xl tw-overflow-y-auto tw-scrollbar-thin tw-scrollbar-thumb-iron-500 tw-scrollbar-track-iron-800 desktop-hover:hover:tw-scrollbar-thumb-iron-300 tw-overflow-x-hidden tw-flex-grow tw-px-2 sm:tw-px-4`;
return `tw-w-full tw-min-w-0 tw-flex tw-flex-col tw-rounded-t-xl tw-overflow-y-auto tw-scrollbar-thin tw-scrollbar-thumb-iron-500 tw-scrollbar-track-iron-800 desktop-hover:hover:tw-scrollbar-thumb-iron-300 tw-overflow-x-hidden tw-flex-grow tw-px-2 sm:tw-px-4`;
}, []);

const [isCreateDropOpen, setIsCreateDropOpen] = useState(false);
Expand Down Expand Up @@ -287,7 +287,7 @@ const MyStreamWaveLeaderboard: React.FC<MyStreamWaveLeaderboardProps> = ({
</div>

{/* Content section */}
<div>
<div className="tw-min-w-0">
<AnimatePresence>
{showToggleableDropInput && (
<motion.div
Expand Down
2 changes: 1 addition & 1 deletion components/home/next-mint-leading/LeadingCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ export const LeadingCard = ({ drop, rank }: LeadingCardProps) => {
<span
className={`tw-text-[11px] tw-font-semibold tw-leading-5 tw-tracking-wide ${rankLabelClass}`}
>
{rank === 1 ? "LEADING" : `CURRENT ${formatOrdinal(rank)} PLACE`}
{rank === 1 ? "LEADING" : `${formatOrdinal(rank)} PLACE`}
</span>
<span className="tw-font-mono tw-text-xs">
<span className="tw-text-white/80">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export const DefaultWaveLeaderboardDrop: React.FC<
// Use the hook for long press interactions
const { isActive, setIsActive, touchHandlers } = useLongPressInteraction({
hasTouchScreen,
preventDefault: false,
});

const getBorderClasses = () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,8 @@ export const WaveLeaderboardGallery: React.FC<WaveLeaderboardGalleryProps> = ({
}

return (
<div className="tw-@container">
<div className="tw-grid tw-gap-x-4 tw-gap-y-8 @lg:tw-grid-cols-2 @3xl:tw-grid-cols-3">
<div className="tw-w-full tw-min-w-0 tw-@container">
<div className="tw-grid tw-w-full tw-min-w-0 tw-gap-x-4 tw-gap-y-8 @lg:tw-grid-cols-2 @3xl:tw-grid-cols-3">
{dropsWithMedia.map((drop) => (
<WaveLeaderboardGalleryItem
key={drop.id}
Expand Down
26 changes: 14 additions & 12 deletions components/waves/leaderboard/gallery/WaveLeaderboardGalleryItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ export const WaveLeaderboardGalleryItem = memo<WaveLeaderboardGalleryItemProps>(
? ""
: "tw-transition-all tw-duration-300 tw-ease-out";
const groupClasses = artFocused ? `tw-group ${transitionClasses}` : "";
const containerClass = `${groupClasses} tw-relative tw-bg-iron-950/50 tw-border tw-border-solid tw-border-iron-800 tw-rounded-lg desktop-hover:hover:tw-border-iron-700 tw-shadow-lg desktop-hover:hover:tw-shadow-xl`;
const containerClass = `${groupClasses} tw-relative tw-w-full tw-min-w-0 tw-bg-iron-950/50 tw-border tw-border-solid tw-border-iron-800 tw-rounded-lg desktop-hover:hover:tw-border-iron-700 tw-shadow-lg desktop-hover:hover:tw-shadow-xl`;

const highlightAnimation =
isHighlighting && !hasTouchScreen ? "tw-animate-gallery-reveal" : "";
Expand Down Expand Up @@ -167,7 +167,7 @@ export const WaveLeaderboardGalleryItem = memo<WaveLeaderboardGalleryItemProps>(
</div>
</button>
<div className="tw-rounded-b-lg tw-border-x-0 tw-border-b-0 tw-border-t tw-border-solid tw-border-iron-800 tw-bg-iron-950/50 tw-p-3">
<div className="tw-mb-3 tw-flex tw-items-start tw-justify-between">
<div className="tw-mb-3 tw-flex tw-min-w-0 tw-items-start tw-justify-between tw-gap-2">
<div className="tw-mr-2 tw-min-w-0 tw-flex-1">
<div className="tw-flex tw-items-center tw-gap-1.5">
<MediaTypeBadge
Expand All @@ -188,7 +188,7 @@ export const WaveLeaderboardGalleryItem = memo<WaveLeaderboardGalleryItemProps>(
<Link
onClick={(e) => e.stopPropagation()}
href={`/${drop.author?.handle}`}
className="tw-mt-0.5 tw-text-xs tw-text-iron-400 tw-no-underline tw-transition-colors tw-duration-150 desktop-hover:hover:tw-text-iron-300 desktop-hover:hover:tw-underline"
className="tw-mt-0.5 tw-block tw-max-w-full tw-truncate tw-text-xs tw-text-iron-400 tw-no-underline tw-transition-colors tw-duration-150 desktop-hover:hover:tw-text-iron-300 desktop-hover:hover:tw-underline"
>
{drop.author?.handle}
</Link>
Expand All @@ -202,12 +202,14 @@ export const WaveLeaderboardGalleryItem = memo<WaveLeaderboardGalleryItemProps>(
/>
)}
</div>
<div className="tw-mb-3 tw-flex tw-items-center tw-justify-between tw-text-xs">
<WaveLeaderboardGalleryItemVotes
drop={drop}
variant={artFocused ? "subtle" : "default"}
/>
<div className="tw-ml-4 tw-flex tw-items-center tw-gap-1 tw-text-iron-500">
<div className="tw-mb-3 tw-flex tw-min-w-0 tw-flex-wrap tw-items-center tw-justify-between tw-gap-y-2 tw-text-xs">
<div className="tw-min-w-0 tw-flex-1">
<WaveLeaderboardGalleryItemVotes
drop={drop}
variant={artFocused ? "subtle" : "default"}
/>
</div>
<div className="tw-ml-auto tw-flex tw-flex-shrink-0 tw-items-center tw-gap-1 tw-text-iron-500">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
Expand All @@ -228,9 +230,9 @@ export const WaveLeaderboardGalleryItem = memo<WaveLeaderboardGalleryItemProps>(
</span>
</div>
</div>
<div className="tw-flex tw-items-center tw-gap-3 tw-border-x-0 tw-border-b-0 tw-border-t tw-border-solid tw-border-iron-800/50 tw-pt-2">
<div className="tw-flex tw-min-w-0 tw-flex-wrap tw-items-center tw-gap-3 tw-border-x-0 tw-border-b-0 tw-border-t tw-border-solid tw-border-iron-800/50 tw-pt-2">
{hasUserVoted && (
<span className="tw-font-mono tw-text-[11px] tw-text-iron-500">
<span className="tw-min-w-0 tw-break-words tw-font-mono tw-text-[11px] tw-text-iron-500">
{WAVE_VOTE_STATS_LABELS.YOUR_VOTES}:{" "}
<span className={voteStyle}>
{isNegativeVote && "-"}
Expand All @@ -240,7 +242,7 @@ export const WaveLeaderboardGalleryItem = memo<WaveLeaderboardGalleryItemProps>(
</span>
)}
{canShowVote && (
<div className="tw-flex tw-flex-1 tw-justify-end">
<div className="tw-ml-auto tw-flex tw-min-w-0 tw-flex-1 tw-justify-end">
<VotingModalButton
drop={drop}
onClick={handleVoteButtonClick}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,16 @@ import type { ExtendedDrop } from "@/helpers/waves/drop.helpers";

interface WaveLeaderboardGalleryItemVotesProps {
readonly drop: ExtendedDrop;
readonly variant?: 'default' | 'subtle' | undefined;
readonly variant?: "default" | "subtle" | undefined;
}

export default function WaveLeaderboardGalleryItemVotes({
drop,
variant = 'default',
variant = "default",
}: WaveLeaderboardGalleryItemVotesProps) {
const current = drop.rating ?? 0;
const isPositive = current >= 0;

const getColorClass = () => {
if (variant === "subtle") {
return "tw-text-iron-200";
Expand All @@ -22,14 +22,16 @@ export default function WaveLeaderboardGalleryItemVotes({
};

return (
<div className="tw-flex tw-items-center tw-gap-2">
<span className={`tw-text-sm tw-font-mono tw-font-bold ${getColorClass()}`}>
<div className="tw-flex tw-min-w-0 tw-flex-wrap tw-items-center tw-gap-2">
<span
className={`tw-font-mono tw-text-sm tw-font-bold ${getColorClass()}`}
>
{formatNumberWithCommas(current)}
</span>
<DropVoteProgressing
current={current}
projected={drop.rating_prediction}
subtle={variant === 'subtle'}
subtle={variant === "subtle"}
/>
</div>
);
Expand Down