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
Expand Up @@ -158,4 +158,25 @@ describe("BrainLeftSidebarWave", () => {
);
expect(bellSlashIcons.length).toBe(0);
});

it("hides the pin control when showPin is false", () => {
const wave = {
...baseWave,
id: "7",
};
render(
<BrainLeftSidebarWave wave={wave} onHover={onHover} showPin={false} />
);
expect(screen.queryByTestId("pin")).not.toBeInTheDocument();
});

it("shows the unpin control when showPin is true", () => {
const wave = {
...baseWave,
id: "8",
isPinned: true,
};
render(<BrainLeftSidebarWave wave={wave} onHover={onHover} showPin />);
expect(screen.getByTestId("pin")).toHaveTextContent("true");
});
});
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import BrainLeftSidebarWavePin from '@/components/brain/left-sidebar/waves/BrainLeftSidebarWavePin';
import { MAX_PINNED_WAVES, usePinnedWavesServer } from '@/hooks/usePinnedWavesServer';
import { useMyStream } from '@/contexts/wave/MyStreamContext';
import { useAuth } from '@/components/auth/Auth';
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import BrainLeftSidebarWavePin from "@/components/brain/left-sidebar/waves/BrainLeftSidebarWavePin";
import {
MAX_PINNED_WAVES,
usePinnedWavesServer,
} from "@/hooks/usePinnedWavesServer";
import { useMyStream } from "@/contexts/wave/MyStreamContext";
import { useAuth } from "@/components/auth/Auth";

// Mock ResizeObserver
global.ResizeObserver = jest.fn().mockImplementation(() => ({
Expand All @@ -13,66 +16,82 @@ global.ResizeObserver = jest.fn().mockImplementation(() => ({
}));

// Mock react-tooltip
jest.mock('react-tooltip', () => ({
jest.mock("react-tooltip", () => ({
Tooltip: ({ children, id }: any) => (
<div data-testid={`tooltip-${id}`} role="tooltip">
{children}
</div>
),
}));
jest.mock('@fortawesome/react-fontawesome', () => ({ FontAwesomeIcon: () => <svg data-testid="icon" /> }));
jest.mock('@/contexts/wave/MyStreamContext');
jest.mock('@/hooks/usePinnedWavesServer');
jest.mock('@/components/auth/Auth');
jest.mock("@fortawesome/react-fontawesome", () => ({
FontAwesomeIcon: () => <svg data-testid="icon" />,
}));
jest.mock("@/contexts/wave/MyStreamContext");
jest.mock("@/hooks/usePinnedWavesServer");
jest.mock("@/components/auth/Auth");

const addPinnedWave = jest.fn();
const removePinnedWave = jest.fn();
const setToast = jest.fn();
const mockedUseMyStream = useMyStream as jest.Mock;
const mockedUsePinnedWavesServer = usePinnedWavesServer as jest.Mock;
const mockedUseAuth = useAuth as jest.Mock;

function setup(isPinned = false, storedPinned: string[] = []) {
mockedUseMyStream.mockReturnValue({ waves: { addPinnedWave, removePinnedWave } });
mockedUsePinnedWavesServer.mockReturnValue({
function setup(
isPinned = false,
storedPinned: string[] = [],
canPinWave = (waveId: string) => isPinned || !storedPinned.includes(waveId)
) {
mockedUseMyStream.mockReturnValue({
waves: { addPinnedWave, removePinnedWave },
});
mockedUsePinnedWavesServer.mockReturnValue({
pinnedIds: storedPinned,
isOperationInProgress: jest.fn().mockReturnValue(false)
isOperationInProgress: jest.fn().mockReturnValue(false),
canPinWave: jest.fn().mockImplementation(canPinWave),
});
mockedUseAuth.mockReturnValue({
setToast: jest.fn()
setToast,
});
localStorage.setItem('pinnedWave', JSON.stringify(storedPinned));
localStorage.setItem("pinnedWave", JSON.stringify(storedPinned));
return render(<BrainLeftSidebarWavePin waveId="1" isPinned={isPinned} />);
}

describe('BrainLeftSidebarWavePin', () => {
describe("BrainLeftSidebarWavePin", () => {
beforeEach(() => {
jest.clearAllMocks();
localStorage.clear();
});

it('unpins wave when already pinned', async () => {
it("unpins wave when already pinned", async () => {
const user = userEvent.setup();
setup(true, ['1']);
await user.click(screen.getByRole('button', { name: /unpin wave/i }));
expect(removePinnedWave).toHaveBeenCalledWith('1');
setup(true, ["1"]);
await user.click(screen.getByRole("button", { name: /unpin wave/i }));
expect(removePinnedWave).toHaveBeenCalledWith("1");
expect(addPinnedWave).not.toHaveBeenCalled();
});

it('pins wave when under max limit', async () => {
it("pins wave when under max limit", async () => {
const user = userEvent.setup();
setup(false, []);
await user.click(screen.getByRole('button', { name: /pin wave/i }));
expect(addPinnedWave).toHaveBeenCalledWith('1');
await user.click(screen.getByRole("button", { name: /pin wave/i }));
expect(addPinnedWave).toHaveBeenCalledWith("1");
expect(removePinnedWave).not.toHaveBeenCalled();
});

it('shows tooltip and does not pin when max limit reached', async () => {
it("shows tooltip and does not pin when max limit reached", async () => {
const user = userEvent.setup();
const maxList = Array(MAX_PINNED_WAVES).fill('x');
setup(false, maxList);
await user.click(screen.getByRole('button', { name: /pin wave/i }));
const maxList = Array(MAX_PINNED_WAVES).fill("x");
setup(false, maxList, () => false);
await user.click(screen.getByRole("button", { name: /pin wave/i }));
expect(addPinnedWave).not.toHaveBeenCalled();
const tooltip = screen.getByTestId('tooltip-wave-pin-1');
expect(tooltip).toHaveTextContent(`Max ${MAX_PINNED_WAVES} pinned waves. Unpin another wave first.`);
expect(setToast).toHaveBeenCalledWith({
type: "error",
message: `Maximum ${MAX_PINNED_WAVES} pinned waves allowed`,
});
const tooltip = screen.getByTestId("tooltip-wave-pin-1");
expect(tooltip).toHaveTextContent(
`Max ${MAX_PINNED_WAVES} pinned waves. Unpin another wave first.`
);
});
});
Original file line number Diff line number Diff line change
@@ -1,75 +1,138 @@
import React from 'react';
import { render, screen } from '@testing-library/react';
import type { UnifiedWavesListWavesHandle } from '@/components/brain/left-sidebar/waves/UnifiedWavesListWaves';
import UnifiedWavesListWaves from '@/components/brain/left-sidebar/waves/UnifiedWavesListWaves';
import { useShowFollowingWaves } from '@/hooks/useShowFollowingWaves';
import { useAuth } from '@/components/auth/Auth';
import { useVirtualizedWaves } from '@/hooks/useVirtualizedWaves';
import { createMockMinimalWave } from '@/__tests__/utils/mockFactories';
import React from "react";
import { render, screen } from "@testing-library/react";
import type { UnifiedWavesListWavesHandle } from "@/components/brain/left-sidebar/waves/UnifiedWavesListWaves";
import UnifiedWavesListWaves from "@/components/brain/left-sidebar/waves/UnifiedWavesListWaves";
import { useShowFollowingWaves } from "@/hooks/useShowFollowingWaves";
import { useAuth } from "@/components/auth/Auth";
import { useVirtualizedWaves } from "@/hooks/useVirtualizedWaves";
import { useSeizeSettingsOptional } from "@/contexts/SeizeSettingsContext";
import { createMockMinimalWave } from "@/__tests__/utils/mockFactories";

jest.mock('@/components/utils/switch/CommonSwitch', () => (props: any) => <div data-testid="switch">{props.label}-{String(props.isOn)}</div>);
jest.mock('@/components/brain/left-sidebar/waves/BrainLeftSidebarWave', () => (props: any) => <div data-testid={`wave-${props.wave.id}`} data-pin={String(props.showPin)} />);
jest.mock('@/components/brain/left-sidebar/waves/SectionHeader', () => (props: any) => <div data-testid={`header-${props.label}`}>{props.label}{props.rightContent}</div>);
jest.mock("@/components/utils/switch/CommonSwitch", () => (props: any) => (
<div data-testid="switch">
{props.label}-{String(props.isOn)}
</div>
));
jest.mock(
"@/components/brain/left-sidebar/waves/BrainLeftSidebarWave",
() => (props: any) => (
<div
data-testid={`wave-${props.wave.id}`}
data-pin={String(props.showPin)}
/>
)
);
jest.mock(
"@/components/brain/left-sidebar/waves/SectionHeader",
() => (props: any) => (
<div data-testid={`header-${props.label}`}>
{props.label}
{props.rightContent}
</div>
)
);

jest.mock('@/hooks/useShowFollowingWaves');
jest.mock('@/components/auth/Auth');
jest.mock('@/hooks/useVirtualizedWaves');
jest.mock("@/hooks/useShowFollowingWaves");
jest.mock("@/components/auth/Auth");
jest.mock("@/hooks/useVirtualizedWaves");
jest.mock("@/contexts/SeizeSettingsContext", () => ({
useSeizeSettingsOptional: jest.fn(),
}));

const mockUseShowFollowingWaves = useShowFollowingWaves as jest.Mock;
const mockUseAuth = useAuth as jest.Mock;
const mockUseVirtualizedWaves = useVirtualizedWaves as jest.Mock;
const mockUseSeizeSettingsOptional = useSeizeSettingsOptional as jest.Mock;

const scrollRef = { current: document.createElement('div') } as React.RefObject<HTMLDivElement>;
const container = document.createElement('div');
const sentinel = document.createElement('div');
const scrollRef = {
current: document.createElement("div"),
} as React.RefObject<HTMLDivElement>;
const container = document.createElement("div");
const sentinel = document.createElement("div");

const baseWaves = [
createMockMinimalWave({ id: 'p1', isPinned: true }),
createMockMinimalWave({ id: 'r1', isPinned: false })
createMockMinimalWave({ id: "a1" }),
createMockMinimalWave({ id: "p1", isPinned: true }),
createMockMinimalWave({ id: "r1", isPinned: false }),
];

beforeEach(() => {
jest.clearAllMocks();
mockUseShowFollowingWaves.mockReturnValue([false, jest.fn()]);
mockUseAuth.mockReturnValue({ connectedProfile: { handle: 'alice' }, activeProfileProxy: null });
mockUseAuth.mockReturnValue({
connectedProfile: { handle: "alice" },
activeProfileProxy: null,
});
mockUseSeizeSettingsOptional.mockReturnValue({
isAnnouncementsWave: (waveId: string) => waveId === "a1",
});
mockUseVirtualizedWaves.mockReturnValue({
containerRef: { current: container },
sentinelRef: { current: sentinel },
virtualItems: [
{ index: 0, start: 0, size: 62 },
{ index: 1, start: 62, size: 40 }
{ index: 1, start: 62, size: 40 },
],
totalHeight: 102
totalHeight: 102,
});
});

it('renders structure even when no waves', () => {
it("renders structure even when no waves", () => {
const { container } = render(
<UnifiedWavesListWaves waves={[]} onHover={jest.fn()} scrollContainerRef={scrollRef} />
<UnifiedWavesListWaves
waves={[]}
onHover={jest.fn()}
scrollContainerRef={scrollRef}
/>
);
expect(container.firstChild).not.toBeNull();
expect(screen.getByTestId('header-All Waves')).toBeInTheDocument();
expect(screen.getByTestId('switch')).toBeInTheDocument();
expect(screen.getByTestId("header-All Waves")).toBeInTheDocument();
expect(screen.getByTestId("switch")).toBeInTheDocument();
});

it('renders pinned and regular waves with headers and switch', () => {
it("renders pinned and regular waves with headers and switch", () => {
const ref = React.createRef<UnifiedWavesListWavesHandle>();
render(
<UnifiedWavesListWaves waves={baseWaves} onHover={jest.fn()} scrollContainerRef={scrollRef} ref={ref} />
<UnifiedWavesListWaves
waves={baseWaves}
onHover={jest.fn()}
scrollContainerRef={scrollRef}
ref={ref}
/>
);
// Component only renders "All Waves" header, pinned waves don't get their own header
expect(screen.getByTestId('header-All Waves')).toBeInTheDocument();
expect(screen.getByTestId('switch')).toBeInTheDocument();
// Verify pinned waves section exists by aria-label
expect(screen.getByLabelText('Pinned waves')).toBeInTheDocument();
expect(screen.getByTestId('wave-p1')).toHaveAttribute('data-pin', 'true');
expect(screen.getByTestId('wave-r1')).toHaveAttribute('data-pin', 'true');
expect(screen.getByTestId("header-All Waves")).toBeInTheDocument();
expect(screen.getByTestId("switch")).toBeInTheDocument();
expect(screen.getByLabelText("Announcement waves")).toBeInTheDocument();
expect(screen.getByLabelText("Pinned waves")).toBeInTheDocument();
expect(screen.getByTestId("wave-a1")).toHaveAttribute("data-pin", "false");
expect(screen.getByTestId("wave-p1")).toHaveAttribute("data-pin", "true");
expect(screen.getByTestId("wave-r1")).toHaveAttribute("data-pin", "true");
expect(ref.current?.containerRef.current).toBe(container);
expect(ref.current?.sentinelRef.current).toBeInstanceOf(HTMLElement);
});

it('respects hide options and does not render toggle when not connected', () => {
mockUseAuth.mockReturnValue({ connectedProfile: null, activeProfileProxy: null });
it("passes pin controls through for pinned announcement waves", () => {
render(
<UnifiedWavesListWaves
waves={[
createMockMinimalWave({
id: "a1",
isPinned: true,
}),
]}
onHover={jest.fn()}
scrollContainerRef={scrollRef}
/>
);

expect(screen.getByTestId("wave-a1")).toHaveAttribute("data-pin", "true");
});

it("respects hide options and does not render toggle when not connected", () => {
mockUseAuth.mockReturnValue({
connectedProfile: null,
activeProfileProxy: null,
});
render(
<UnifiedWavesListWaves
waves={baseWaves}
Expand All @@ -80,9 +143,10 @@ it('respects hide options and does not render toggle when not connected', () =>
hideToggle
/>
);
expect(screen.queryByTestId('header-Pinned')).toBeNull();
expect(screen.queryByTestId('header-All Waves')).toBeNull();
expect(screen.queryByTestId('switch')).toBeNull();
expect(screen.queryByTestId('wave-p1')).toBeNull();
expect(screen.getByTestId('wave-r1')).toHaveAttribute('data-pin', 'false');
expect(screen.queryByTestId("header-Pinned")).toBeNull();
expect(screen.queryByTestId("header-All Waves")).toBeNull();
expect(screen.queryByTestId("switch")).toBeNull();
expect(screen.getByTestId("wave-a1")).toHaveAttribute("data-pin", "false");
expect(screen.queryByTestId("wave-p1")).toBeNull();
expect(screen.getByTestId("wave-r1")).toHaveAttribute("data-pin", "false");
});
Loading
Loading