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
20 changes: 14 additions & 6 deletions __tests__/components/waves/outcome/WaveManualOutcome.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,27 @@ import { WaveManualOutcome } from '@/components/waves/outcome/WaveManualOutcome'

const outcome = {
description: 'desc',
distribution: [
{ amount: 1, description: 'A' },
{ amount: 2, description: 'B' },
{ amount: 3, description: 'C' },
{ amount: 4, description: 'D' },
} as any;

const distribution = {
items: [
{ index: 1, amount: 0, description: 'A' },
{ index: 2, amount: 150, description: 'B' },
{ index: 3, amount: 250, description: 'C' },
{ index: 4, amount: 350, description: 'D' },
],
totalCount: 4,
hasNextPage: false,
isFetchingNextPage: false,
fetchNextPage: jest.fn(),
} as any;

describe('WaveManualOutcome', () => {
it('expands and shows all winners', async () => {
const user = userEvent.setup();
render(<WaveManualOutcome outcome={outcome} />);
render(<WaveManualOutcome outcome={outcome} distribution={distribution} />);
await user.click(screen.getByRole('button'));
expect(screen.getByText('-')).toBeInTheDocument();
expect(screen.getByText('View more')).toBeInTheDocument();
await user.click(screen.getByText('View more'));
expect(screen.queryByText('View more')).toBeNull();
Expand Down
34 changes: 31 additions & 3 deletions __tests__/components/waves/outcome/WaveOutcome.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,22 +14,50 @@ jest.mock("@/components/waves/outcome/WaveManualOutcome", () => ({
WaveManualOutcome: (props: any) => <div data-testid="manual" />,
}));

jest.mock("@/hooks/waves/useWaveOutcomeDistributionQuery", () => ({
useWaveOutcomeDistributionQuery: jest.fn().mockReturnValue({
items: [],
totalCount: 0,
hasNextPage: false,
isFetchingNextPage: false,
fetchNextPage: jest.fn(),
isLoading: false,
isError: false,
errorMessage: undefined,
}),
}));

import { WaveOutcome } from "@/components/waves/outcome/WaveOutcome";
import { ApiWaveOutcomeCredit } from "@/generated/models/ApiWaveOutcomeCredit";

describe("WaveOutcome", () => {
it("renders rep outcome", () => {
render(<WaveOutcome outcome={{ credit: ApiWaveOutcomeCredit.Rep } as any} />);
render(
<WaveOutcome
waveId="wave-1"
outcome={{ credit: ApiWaveOutcomeCredit.Rep, index: 0 } as any}
/>
);
expect(screen.getByTestId("rep")).toBeInTheDocument();
});

it("renders nic outcome", () => {
render(<WaveOutcome outcome={{ credit: ApiWaveOutcomeCredit.Cic } as any} />);
render(
<WaveOutcome
waveId="wave-2"
outcome={{ credit: ApiWaveOutcomeCredit.Cic, index: 1 } as any}
/>
);
expect(screen.getByTestId("nic")).toBeInTheDocument();
});

it("renders manual outcome", () => {
render(<WaveOutcome outcome={{ credit: "OTHER" } as any} />);
render(
<WaveOutcome
waveId="wave-3"
outcome={{ credit: "OTHER", index: 2 } as any}
/>
);
expect(screen.getByTestId("manual")).toBeInTheDocument();
});
});
32 changes: 30 additions & 2 deletions __tests__/components/waves/outcome/WaveRepOutcome.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,25 @@ jest.mock('@fortawesome/react-fontawesome', () => ({ FontAwesomeIcon: () => <spa

describe('WaveRepOutcome', () => {
const outcome = {
distribution: [{ amount: 10 }, { amount: 20 }, { amount: 30 }, { amount: 40 }],
amount: 100,
rep_category: 'rep',
} as any;

const distribution = {
items: [
{ index: 1, amount: 10 },
{ index: 2, amount: 20 },
{ index: 3, amount: 30 },
{ index: 4, amount: 40 },
],
totalCount: 4,
hasNextPage: false,
isFetchingNextPage: false,
fetchNextPage: jest.fn(),
} as any;

it('expands list and shows more items', () => {
render(<WaveRepOutcome outcome={outcome} />);
render(<WaveRepOutcome outcome={outcome} distribution={distribution} />);
expect(screen.queryByText('10 Rep')).toBeNull();

fireEvent.click(screen.getByRole('button'));
Expand All @@ -30,4 +42,20 @@ describe('WaveRepOutcome', () => {
fireEvent.click(screen.getByText(/View more/i));
expect(screen.getByText('40 Rep')).toBeInTheDocument();
});

it('shows loading state when fetching next page', () => {
const loadingDistribution = {
...distribution,
hasNextPage: true,
isFetchingNextPage: true,
};
render(<WaveRepOutcome outcome={outcome} distribution={loadingDistribution} />);

fireEvent.click(screen.getByRole('button')); // Expand accordion

const viewMoreBtn = screen.getByRole('button', { name: /loading\.\.\./i });
expect(viewMoreBtn).toBeInTheDocument();
expect(viewMoreBtn).toBeDisabled();
expect(screen.getByText(/1 more/i)).toBeInTheDocument();
});
});
93 changes: 93 additions & 0 deletions __tests__/hooks/waves/useWaveOutcomeDistributionQuery.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { renderHook } from '@testing-library/react';
import { useWaveOutcomeDistributionQuery } from '@/hooks/waves/useWaveOutcomeDistributionQuery';
import { useInfiniteQuery } from '@tanstack/react-query';
import { commonApiFetch } from '@/services/api/common-api';
import { QueryKey } from '@/components/react-query-wrapper/ReactQueryWrapper';

jest.mock('@tanstack/react-query');
jest.mock('@/services/api/common-api');

const useInfiniteQueryMock = useInfiniteQuery as jest.Mock;
const fetchMock = commonApiFetch as jest.Mock;

describe('useWaveOutcomeDistributionQuery', () => {
beforeEach(() => {
jest.clearAllMocks();
useInfiniteQueryMock.mockReturnValue({
data: { pages: [], pageParams: [] },
isError: false,
error: null,
refetch: jest.fn(),
isFetching: false,
isFetchingNextPage: false,
hasNextPage: false,
fetchNextPage: jest.fn(),
});
});

it('should be disabled when outcomeIndex is null', () => {
renderHook(() =>
useWaveOutcomeDistributionQuery({
waveId: 'wave1',
outcomeIndex: null,
})
);

expect(useInfiniteQueryMock).toHaveBeenCalledWith(
expect.objectContaining({
queryKey: [
QueryKey.WAVE_OUTCOME_DISTRIBUTION,
'wave1',
'',
100,
'ASC',
],
enabled: false,
})
);
});

it('should be enabled when outcomeIndex is 0', () => {
renderHook(() =>
useWaveOutcomeDistributionQuery({
waveId: 'wave1',
outcomeIndex: 0,
})
);

expect(useInfiniteQueryMock).toHaveBeenCalledWith(
expect.objectContaining({
queryKey: [
QueryKey.WAVE_OUTCOME_DISTRIBUTION,
'wave1',
'0',
100,
'ASC',
],
enabled: true,
})
);
});

it('should be enabled when outcomeIndex is "1"', () => {
renderHook(() =>
useWaveOutcomeDistributionQuery({
waveId: 'wave1',
outcomeIndex: '1',
})
);

expect(useInfiniteQueryMock).toHaveBeenCalledWith(
expect.objectContaining({
queryKey: [
QueryKey.WAVE_OUTCOME_DISTRIBUTION,
'wave1',
'1',
100,
'ASC',
],
enabled: true,
})
);
});
});
78 changes: 63 additions & 15 deletions components/brain/my-stream/MyStreamWaveOutcome.tsx
Original file line number Diff line number Diff line change
@@ -1,33 +1,81 @@
"use client"
"use client";

import React, { useMemo } from "react";
import { FC, useMemo, useRef } from "react";
import { ApiWave } from "@/generated/models/ApiWave";
import { WaveOutcome } from "@/components/waves/outcome/WaveOutcome";
import { useLayout } from "./layout/LayoutContext";
import SpinnerLoader from "@/components/common/SpinnerLoader";
import { useInfiniteScroll } from "@/hooks/useInfiniteScroll";
import { useWaveOutcomesQuery } from "@/hooks/waves/useWaveOutcomesQuery";

interface MyStreamWaveOutcomeProps {
readonly wave: ApiWave;
}

// Removed in favor of useWaveViewHeight hook

const MyStreamWaveOutcome: React.FC<MyStreamWaveOutcomeProps> = ({ wave }) => {
const MyStreamWaveOutcome: FC<MyStreamWaveOutcomeProps> = ({ wave }) => {
// Get the pre-calculated style from LayoutContext
const { outcomeViewStyle } = useLayout();

const scrollContainerRef = useRef<HTMLDivElement>(null);
const sentinelRef = useRef<HTMLDivElement>(null);

const {
outcomes,
fetchNextPage,
hasNextPage,
isFetching,
isFetchingNextPage,
isLoading,
errorMessage,
} = useWaveOutcomesQuery({ waveId: wave.id });


const containerClassName = useMemo(() => {
return `tw-pt-4 tw-pb-4 tw-w-full tw-flex tw-flex-col tw-overflow-y-auto no-scrollbar lg:tw-scrollbar-thin tw-scrollbar-thumb-iron-500 tw-scrollbar-track-iron-800 desktop-hover:hover:tw-scrollbar-thumb-iron-300 tw-flex-grow lg:tw-pr-2`;
}, []);

useInfiniteScroll(
hasNextPage,
isFetchingNextPage,
fetchNextPage,
scrollContainerRef,
sentinelRef,
"200px"
);

const hasOutcomes = outcomes.length > 0;
const isInitialLoading = isLoading && !hasOutcomes;
return (
<div className={containerClassName} style={outcomeViewStyle}>
<div className="tw-px-2 sm:tw-px-4 tw-space-y-4">
{wave.outcomes.map((outcome, index) => (
<WaveOutcome
key={`${outcome.credit}-${outcome.type}-${index}`}
outcome={outcome}
/>
))}
</div>
<div
className={containerClassName}
style={outcomeViewStyle}
ref={scrollContainerRef}
>
{isInitialLoading && <SpinnerLoader text="Loading outcomes..." />}
{!isInitialLoading && errorMessage && (
<div className="tw-px-4 tw-text-sm tw-text-red-400">
{errorMessage}
</div>
)}
{!isInitialLoading && !errorMessage && !hasOutcomes && !isFetching && (
<div className="tw-px-4 tw-text-sm tw-text-iron-500">
No outcomes to show.
</div>
)}
{hasOutcomes && (
<div className="tw-px-2 sm:tw-px-4 tw-space-y-4">
{outcomes.map((outcome, index) => (
<WaveOutcome
waveId={wave.id}
key={`${outcome.index ?? index}-${outcome.type}`}
outcome={outcome}
/>
))}
<div ref={sentinelRef} style={{ height: "1px" }} />
{isFetchingNextPage && (
<SpinnerLoader text="Loading more outcomes..." />
)}
</div>
)}
</div>
);
};
Expand Down
8 changes: 8 additions & 0 deletions components/react-query-wrapper/ReactQueryWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,8 @@ export enum QueryKey {
WAVE_FOLLOWERS = "WAVE_FOLLOWERS",
FEED_ITEMS = "FEED_ITEMS",
WAVE_DECISIONS = "WAVE_DECISIONS",
WAVE_OUTCOMES = "WAVE_OUTCOMES",
WAVE_OUTCOME_DISTRIBUTION = "WAVE_OUTCOME_DISTRIBUTION",
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

interface InitProfileRatersParamsAndData {
Expand Down Expand Up @@ -1139,6 +1141,12 @@ export default function ReactQueryWrapper({
queryClient.invalidateQueries({
queryKey: [QueryKey.WAVE],
});
queryClient.invalidateQueries({
queryKey: [QueryKey.WAVE_OUTCOMES],
});
queryClient.invalidateQueries({
queryKey: [QueryKey.WAVE_OUTCOME_DISTRIBUTION],
});
};

const invalidateDrops = () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@ import { ExtendedDrop } from "@/helpers/waves/drop.helpers";
import { cicToType } from "@/helpers/Helpers";

import Link from "next/link";
import Image from "next/image";
import UserCICAndLevel, {
UserCICAndLevelSize,
} from "@/components/user/utils/UserCICAndLevel";
import WinnerDropBadge from "@/components/waves/drops/winner/WinnerDropBadge";
import WaveDropTime from "@/components/waves/drops/time/WaveDropTime";
import UserProfileTooltipWrapper from "@/components/utils/tooltip/UserProfileTooltipWrapper";
import { resolveIpfsUrlSync } from "@/components/ipfs/IPFSContext";

interface WaveLeaderboardDropAuthorProps {
readonly drop: ExtendedDrop;
Expand All @@ -29,10 +31,12 @@ export const WaveLeaderboardDropAuthor: React.FC<
<div className="tw-rounded-lg tw-h-full tw-w-full">
<div className="tw-h-full tw-w-full tw-max-w-full tw-rounded-lg tw-overflow-hidden tw-bg-iron-900 tw-ring-1 tw-ring-white/10">
<div className="tw-h-full tw-text-center tw-flex tw-items-center tw-justify-center tw-rounded-lg tw-overflow-hidden">
<img
src={drop.author.pfp}
<Image
src={resolveIpfsUrlSync(drop.author.pfp)}
alt="Profile picture"
className="tw-bg-transparent tw-max-w-full tw-max-h-full tw-h-auto tw-w-auto tw-mx-auto tw-object-contain"
width={44}
height={44}
className="tw-rounded-lg tw-object-contain"
/>
</div>
</div>
Expand Down
Loading