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 @@ -89,7 +89,7 @@ describe('BrainLeftSidebarWave', () => {
render(<BrainLeftSidebarWave wave={baseWave} onHover={onHover} />);
const link = screen.getByRole('link');
await userEvent.click(link);
expect(setActiveWave).toHaveBeenCalledWith('1', { isDirectMessage: false, serialNo: null, divider: null });
expect(setActiveWave).toHaveBeenCalledWith('1', { isDirectMessage: false, divider: null });
});

it('shows drop indicators for non-chat waves', () => {
Expand All @@ -101,7 +101,7 @@ describe('BrainLeftSidebarWave', () => {
it('includes firstUnreadDropSerialNo in href when present', () => {
const waveWithUnread = { ...baseWave, id: '3', firstUnreadDropSerialNo: 42 };
render(<BrainLeftSidebarWave wave={waveWithUnread} onHover={onHover} />);
expect(screen.getByRole('link')).toHaveAttribute('href', '/waves?divider=42&wave=3&serialNo=42');
expect(screen.getByRole('link')).toHaveAttribute('href', '/waves?divider=42&wave=3');
});

it('does not include serialNo in href when firstUnreadDropSerialNo is null', () => {
Expand Down
29 changes: 19 additions & 10 deletions __tests__/components/brain/my-stream/MyStreamWaveChat.test.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { render, screen, act } from "@testing-library/react";
import MyStreamWaveChat from "@/components/brain/my-stream/MyStreamWaveChat";
import { editSlice } from "@/store/editSlice";
import { configureStore } from "@reduxjs/toolkit";
import { act, render, screen } from "@testing-library/react";
import React from "react";
import { Provider } from "react-redux";
import { configureStore } from "@reduxjs/toolkit";
import { editSlice } from "@/store/editSlice";
import MyStreamWaveChat from "@/components/brain/my-stream/MyStreamWaveChat";

const replaceMock = jest.fn();
const searchParamsMock = { get: jest.fn() };
Expand All @@ -23,11 +23,15 @@ jest.mock("@/components/brain/my-stream/layout/LayoutContext", () => ({
useLayout: () => ({ waveViewStyle: { height: "1px" } }),
}));

let capturedProps: any = {};
const capturedPropsHolder = { current: {} as any };
jest.mock("@/components/waves/drops/wave-drops-all", () => ({
__esModule: true,
default: (props: any) => {
capturedProps = props;
capturedPropsHolder.current = props;
return <div data-testid="drops" />;
},
WaveDropsAllWithoutProvider: (props: any) => {
capturedPropsHolder.current = props;
return <div data-testid="drops" />;
},
}));
Expand Down Expand Up @@ -55,21 +59,26 @@ jest.mock("@/hooks/useDeviceInfo", () => ({
default: () => ({ isApp: false }),
}));

jest.mock("@/contexts/wave/UnreadDividerContext", () => ({
UnreadDividerProvider: ({ children }: any) => <>{children}</>,
}));

jest.mock("@/components/waves/gallery", () => ({
WaveGallery: () => <div data-testid="gallery" />,
}));

const wave = { id: "10" } as any;
const wave = { id: "10", metrics: { muted: false } } as any;
const mockOnDropClick = jest.fn();

describe("MyStreamWaveChat", () => {
let store: any;

beforeEach(() => {
capturedProps = {};
capturedPropsHolder.current = {};
replaceMock.mockClear();
searchParamsMock.get.mockReset();
mockIsMemesWave = false;
mockOnDropClick.mockClear();
store = configureStore({
reducer: { edit: editSlice.reducer },
});
Expand All @@ -93,7 +102,7 @@ describe("MyStreamWaveChat", () => {
);
});
expect(replaceMock).toHaveBeenCalled();
expect(capturedProps.initialDrop).toBe(5);
expect(capturedPropsHolder.current.initialDrop).toBe(5);
expect(screen.getByTestId("memes-btn")).toBeInTheDocument();
});

Expand All @@ -110,7 +119,7 @@ describe("MyStreamWaveChat", () => {
);
});
expect(replaceMock).not.toHaveBeenCalled();
expect(capturedProps.initialDrop).toBeNull();
expect(capturedPropsHolder.current.initialDrop).toBeNull();
expect(screen.queryByTestId("memes-btn")).toBeNull();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import DropMobileMenuHandler from '@/components/waves/drops/DropMobileMenuHandle
import { DropSize } from '@/helpers/waves/drop.helpers';

jest.mock('@/hooks/isMobileDevice', () => () => true);
jest.mock('@/hooks/useIsTouchDevice', () => ({ __esModule: true, default: () => true }));

jest.mock('@/components/waves/drops/WaveDropMobileMenu', () => ({
__esModule: true,
Expand Down
1 change: 1 addition & 0 deletions __tests__/components/waves/drops/WaveDrop.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ jest.mock('@/components/waves/drops/WaveDropRatings', () => () => <div data-test
jest.mock('@/components/waves/drops/WaveDropMobileMenu', () => () => <div data-testid="mobile" />);

jest.mock('@/hooks/isMobileDevice');
jest.mock('@/hooks/useIsTouchDevice', () => ({ __esModule: true, default: jest.fn(() => false) }));

jest.mock('next/navigation', () => ({
useRouter: jest.fn(() => ({ push: jest.fn() })),
Expand Down
5 changes: 5 additions & 0 deletions __tests__/components/waves/drops/WaveDropsAll.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,11 @@ jest.mock('@/components/waves/drops/WaveDropsScrollBottomButton', () => ({
}
}));

jest.mock('@/components/waves/drops/WaveDropsScrollToUnreadButton', () => ({
__esModule: true,
WaveDropsScrollToUnreadButton: () => <button data-testid="scroll-unread-btn" />
}));

jest.mock('@/components/waves/drops/WaveDropsEmptyPlaceholder', () => ({
__esModule: true,
default: () => <div data-testid="empty-placeholder" />
Expand Down
82 changes: 66 additions & 16 deletions __tests__/hooks/useDeviceInfo.test.ts
Original file line number Diff line number Diff line change
@@ -1,50 +1,100 @@
import { renderHook } from '@testing-library/react';
import useDeviceInfo from '@/hooks/useDeviceInfo';
import { act, renderHook } from '@testing-library/react';

jest.mock('@/hooks/useCapacitor', () => ({ __esModule: true, default: jest.fn(() => ({ isCapacitor: false })) }));
const capacitorMock = require('@/hooks/useCapacitor').default as jest.Mock;

defineMatchMedia();
let touchStartHandler: EventListener | null = null;

function defineMatchMedia(pointer = false, width = false) {
Object.defineProperty(window, 'matchMedia', {
function defineMatchMedia(pointerFine = true, width = false) {
Object.defineProperty(globalThis, 'matchMedia', {
writable: true,
value: jest.fn((query) => ({
matches: query.includes('pointer') ? pointer : width,
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
})),
value: jest.fn((query: string) => {
if (query === '(pointer: fine)') {
return { matches: pointerFine, addEventListener: jest.fn(), removeEventListener: jest.fn() };
}
if (query === '(max-width: 768px)') {
return { matches: width, addEventListener: jest.fn(), removeEventListener: jest.fn() };
}
return { matches: false, addEventListener: jest.fn(), removeEventListener: jest.fn() };
}),
});
}

describe('useDeviceInfo', () => {
beforeEach(() => {
jest.spyOn(globalThis, 'addEventListener').mockImplementation((event, handler) => {
if (event === 'touchstart') {
touchStartHandler = handler as EventListener;
}
});
jest.spyOn(globalThis, 'removeEventListener');
});

afterEach(() => {
touchStartHandler = null;
jest.restoreAllMocks();
capacitorMock.mockReturnValue({ isCapacitor: false });
});

it('detects classic mobile user agent', () => {
Object.defineProperty(window.navigator, 'userAgent', { value: 'iPhone', configurable: true });
defineMatchMedia(true, false);
Object.defineProperty(globalThis.navigator, 'userAgent', { value: 'iPhone', configurable: true });
Object.defineProperty(globalThis.navigator, 'maxTouchPoints', { value: 5, configurable: true });
defineMatchMedia(false, false);
const { result } = renderHook(() => useDeviceInfo());
expect(result.current.isMobileDevice).toBe(true);
expect(result.current.isAppleMobile).toBe(true);
expect(result.current.hasTouchScreen).toBe(true);
expect(result.current.isApp).toBe(false);
expect(result.current.hasTouchScreen).toBe(true);
});

it('detects capacitor mobile with desktop UA', () => {
capacitorMock.mockReturnValue({ isCapacitor: true });
Object.defineProperty(window.navigator, 'userAgent', { value: 'Macintosh', configurable: true });
defineMatchMedia(true, true);
Object.defineProperty(globalThis.navigator, 'userAgent', { value: 'Macintosh', configurable: true });
Object.defineProperty(globalThis.navigator, 'maxTouchPoints', { value: 5, configurable: true });
defineMatchMedia(false, true);
const { result } = renderHook(() => useDeviceInfo());
expect(result.current.isMobileDevice).toBe(true);
expect(result.current.isApp).toBe(true);
expect(result.current.isAppleMobile).toBe(true);
expect(result.current.hasTouchScreen).toBe(true);
});

it('returns false for desktop without touch', () => {
capacitorMock.mockReturnValue({ isCapacitor: false });
Object.defineProperty(window.navigator, 'userAgent', { value: 'Mozilla/5.0', configurable: true });
defineMatchMedia(false, false);
Object.defineProperty(globalThis.navigator, 'userAgent', { value: 'Mozilla/5.0', configurable: true });
Object.defineProperty(globalThis.navigator, 'maxTouchPoints', { value: 0, configurable: true });
defineMatchMedia(true, false);
const { result } = renderHook(() => useDeviceInfo());
expect(result.current.isMobileDevice).toBe(false);
expect(result.current.hasTouchScreen).toBe(false);
expect(result.current.isAppleMobile).toBe(false);
});

it('hasTouchScreen becomes true on hybrid device after touch even with fine pointer', () => {
capacitorMock.mockReturnValue({ isCapacitor: false });
Object.defineProperty(globalThis.navigator, 'userAgent', { value: 'Mozilla/5.0', configurable: true });
Object.defineProperty(globalThis.navigator, 'maxTouchPoints', { value: 0, configurable: true });
defineMatchMedia(true, false);
const { result } = renderHook(() => useDeviceInfo());

expect(result.current.hasTouchScreen).toBe(false);

act(() => {
if (touchStartHandler) {
touchStartHandler(new Event('touchstart'));
}
});

expect(result.current.hasTouchScreen).toBe(true);
});

it('hasTouchScreen is true when maxTouchPoints > 0 even without touch event', () => {
capacitorMock.mockReturnValue({ isCapacitor: false });
Object.defineProperty(globalThis.navigator, 'userAgent', { value: 'Mozilla/5.0', configurable: true });
Object.defineProperty(globalThis.navigator, 'maxTouchPoints', { value: 5, configurable: true });
defineMatchMedia(true, false);
const { result } = renderHook(() => useDeviceInfo());
expect(result.current.hasTouchScreen).toBe(true);
});
});
Loading