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
4 changes: 1 addition & 3 deletions src/components/ResultList/Item/Item.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@ export interface IItemProps {
highlights?: Record<string, string[]>;
extraInfo?: string;
linkNewTab?: boolean;
defaultCitation: string;
}

export const Item = (props: IItemProps): ReactElement => {
Expand All @@ -67,7 +66,6 @@ export const Item = (props: IItemProps): ReactElement => {
highlights,
extraInfo,
linkNewTab = false,
defaultCitation = '',
} = props;
const { bibcode, pubdate, title = ['Untitled'], author = [], author_count, pub } = doc;
const encodedCanonicalID = bibcode ? encodeURIComponent(bibcode) : '';
Expand Down Expand Up @@ -152,7 +150,7 @@ export const Item = (props: IItemProps): ReactElement => {
<Text as={MathJax} dangerouslySetInnerHTML={{ __html: unwrapStringValue(title) }} />
</SimpleLink>
<Flex alignItems="start" ml={1}>
{!isClient || hideActions ? null : <ItemResourceDropdowns doc={doc} defaultCitation={defaultCitation} />}
{!isClient || hideActions ? null : <ItemResourceDropdowns doc={doc} />}
</Flex>
</Flex>
<Flex direction="column">
Expand Down
98 changes: 98 additions & 0 deletions src/components/ResultList/Item/ItemResourceDropdowns.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import { render, waitFor } from '@/test-utils';
import { afterEach, describe, expect, test, vi } from 'vitest';
import { ItemResourceDropdowns } from './ItemResourceDropdowns';
import { IDocsEntity } from '@/api/search/types';

const mocks = vi.hoisted(() => ({
useRouter: vi.fn(() => ({
query: {},
asPath: '/',
push: vi.fn(),
events: { on: vi.fn(), off: vi.fn() },
})),
useSettings: vi.fn(() => ({
settings: { defaultCitationFormat: 'agu' },
})),
useGetExportCitation: vi.fn(() => ({ data: undefined as { export: string } | undefined })),
}));

vi.mock('next/router', () => ({ useRouter: mocks.useRouter }));
vi.mock('@/lib/useSettings', () => ({ useSettings: mocks.useSettings }));
vi.mock('@/api/export/export', () => ({
useGetExportCitation: mocks.useGetExportCitation,
}));

const makeDoc = (overrides?: Partial<IDocsEntity>): IDocsEntity =>
({
bibcode: '2020ApJ...123..456A',
title: ['Test Paper'],
author: ['Author, A.'],
pubdate: '2020-01-00',
citation_count: 0,
reference_count: 0,
esources: [],
property: [],
...overrides,
} as unknown as IDocsEntity);

describe('ItemResourceDropdowns', () => {
afterEach(() => {
mocks.useGetExportCitation.mockClear();
mocks.useGetExportCitation.mockReturnValue({ data: undefined });
});

test('does not fetch citation on mount', () => {
render(<ItemResourceDropdowns doc={makeDoc()} />);

// The hook should be called with enabled: false on initial render
// because isShareOpen starts as false
expect(mocks.useGetExportCitation).toHaveBeenCalledWith(
expect.objectContaining({
format: 'agu',
bibcode: ['2020ApJ...123..456A'],
}),
expect.objectContaining({ enabled: false }),
);
});

test('fetches citation when share menu is opened', async () => {
const { user, getByLabelText } = render(<ItemResourceDropdowns doc={makeDoc()} />);

mocks.useGetExportCitation.mockClear();

const shareButton = getByLabelText('share options');
await user.click(shareButton);

// After opening the share menu, the hook should be called
// with enabled: true
await waitFor(() => {
expect(mocks.useGetExportCitation).toHaveBeenCalledWith(
expect.objectContaining({
format: 'agu',
bibcode: ['2020ApJ...123..456A'],
}),
expect.objectContaining({ enabled: true }),
);
});
});

test('uses citation data from hook in CopyMenuItem', async () => {
mocks.useGetExportCitation.mockReturnValue({
data: { export: 'Author, A. (2020). Test Paper.' },
});

const { getByLabelText, user, getByText } = render(<ItemResourceDropdowns doc={makeDoc()} />);

const shareButton = getByLabelText('share options');
await user.click(shareButton);

// The Copy Citation menu item should be visible
expect(getByText('Copy Citation')).toBeInTheDocument();
});

test('renders share options button', () => {
const { getByLabelText } = render(<ItemResourceDropdowns doc={makeDoc()} />);

expect(getByLabelText('share options')).toBeInTheDocument();
});
});
29 changes: 20 additions & 9 deletions src/components/ResultList/Item/ItemResourceDropdowns.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
MenuList,
Tooltip,
useClipboard,
useDisclosure,
useToast,
} from '@chakra-ui/react';
import { LockIcon, UnlockIcon } from '@chakra-ui/icons';
Expand All @@ -18,9 +19,12 @@ import { MouseEventHandler, ReactElement, useEffect } from 'react';
import { isBrowser } from '@/utils/common/guards';
import { IDocsEntity } from '@/api/search/types';
import { CopyMenuItem } from '@/components/CopyButton';
import { useGetExportCitation } from '@/api/export/export';
import { useSettings } from '@/lib/useSettings';

export interface IItemResourceDropdownsProps {
doc: IDocsEntity;
/** @deprecated No longer used — citation is fetched lazily. */
defaultCitation?: string;
}

Expand All @@ -30,9 +34,21 @@ export interface IItem {
path?: string;
}

export const ItemResourceDropdowns = ({ doc, defaultCitation }: IItemResourceDropdownsProps): ReactElement => {
export const ItemResourceDropdowns = ({ doc }: IItemResourceDropdownsProps): ReactElement => {
const router = useRouter();
const toast = useToast();
const { isOpen: isShareOpen, onOpen: onShareOpen, onClose: onShareClose } = useDisclosure();
const { settings } = useSettings();

const { data: citationData } = useGetExportCitation(
{
format: settings.defaultCitationFormat,
bibcode: [doc.bibcode],
},
{ enabled: isShareOpen && !!doc.bibcode },
);

const citation = citationData?.export ?? '';

const { hasCopied, onCopy, setValue, value } = useClipboard('');

Expand Down Expand Up @@ -136,7 +152,7 @@ export const ItemResourceDropdowns = ({ doc, defaultCitation }: IItemResourceDro
};

const handleCitationCopied = () => {
if (!!defaultCitation) {
if (!!citation) {
toast({ status: 'info', title: 'Copied to Clipboard' });
} else {
toast({ status: 'error', title: 'There was a problem fetching citation. Try reloading the page.' });
Expand Down Expand Up @@ -217,7 +233,7 @@ export const ItemResourceDropdowns = ({ doc, defaultCitation }: IItemResourceDro
</Tooltip>
{/* share menu */}
<Tooltip label="Share options" shouldWrapChildren>
<Menu variant="compact">
<Menu variant="compact" isOpen={isShareOpen} onOpen={onShareOpen} onClose={onShareClose}>
<MenuButton
as={IconButton}
aria-label="share options"
Expand All @@ -227,12 +243,7 @@ export const ItemResourceDropdowns = ({ doc, defaultCitation }: IItemResourceDro
/>
<MenuList>
<MenuItem onClick={handleCopyAbstractUrl}>Copy URL</MenuItem>
<CopyMenuItem
text={defaultCitation ?? ''}
onCopyComplete={handleCitationCopied}
label="Copy Citation"
asHtml
/>
<CopyMenuItem text={citation} onCopyComplete={handleCitationCopied} label="Copy Citation" asHtml />
</MenuList>
</Menu>
</Tooltip>
Expand Down
35 changes: 4 additions & 31 deletions src/components/ResultList/SimpleResultList.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
import { Alert, AlertIcon, Box, Flex, Text, VisuallyHidden } from '@chakra-ui/react';
import { useIsClient } from '@/lib/useIsClient';
import PT from 'prop-types';
import { HTMLAttributes, ReactElement, useMemo } from 'react';
import { HTMLAttributes, ReactElement } from 'react';
import { ErrorBoundary, FallbackProps } from 'react-error-boundary';
import { Item, IItemProps } from './Item';
import { useHighlights } from './useHighlights';
import { IDocsEntity } from '@/api/search/types';
import { useGetExportCitation } from '@/api/export/export';
import { useSettings } from '@/lib/useSettings';
import { handleBoundaryError } from '@/lib/errorHandler';

export interface ISimpleResultListProps extends HTMLAttributes<HTMLDivElement> {
Expand Down Expand Up @@ -45,7 +43,9 @@ const ItemErrorFallback = ({ bibcode }: { bibcode: string } & FallbackProps) =>
*/
const SafeItem = (props: IItemProps) => (
<ErrorBoundary
onError={(error, errorInfo) => handleBoundaryError(error, errorInfo, { component: 'Item', bibcode: props.doc.bibcode })}
onError={(error, errorInfo) =>
handleBoundaryError(error, errorInfo, { component: 'Item', bibcode: props.doc.bibcode })
}
fallbackRender={(fallbackProps) => <ItemErrorFallback {...fallbackProps} bibcode={props.doc.bibcode} />}
>
<Item {...props} />
Expand All @@ -68,32 +68,6 @@ export const SimpleResultList = (props: ISimpleResultListProps): ReactElement =>

const { highlights, showHighlights, isFetchingHighlights } = useHighlights();

const { settings } = useSettings();
const { defaultCitationFormat } = settings;

const bibcodes = docs.map((d) => d.bibcode).sort();

const { data: citationData } = useGetExportCitation(
{
format: defaultCitationFormat,
bibcode: bibcodes,
sort: ['bibcode asc'],
outputformat: 2,
},
{ enabled: !!settings?.defaultCitationFormat },
);

// a map from bibcode to citation
const defaultCitations = useMemo(() => {
const citationSet = new Map<string, string>();
if (!!citationData) {
citationData.docs.map((doc) => {
citationSet.set(doc.bibcode, doc.reference);
});
}
return citationSet;
}, [citationData]);

return (
<Flex
as="section"
Expand All @@ -117,7 +91,6 @@ export const SimpleResultList = (props: ISimpleResultListProps): ReactElement =>
highlights={highlights?.[index] ?? {}}
isFetchingHighlights={allowHighlight && isFetchingHighlights}
useNormCite={useNormCite}
defaultCitation={defaultCitations?.get(doc.bibcode)}
/>
))}
</Flex>
Expand Down
4 changes: 2 additions & 2 deletions src/components/SimpleLink/SimpleLink.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,6 @@ export const SimpleLink = forwardRef(
href={href}
prefetch={prefetch}
passHref={passHref}
onTouchStart={onTouchStart}
onMouseEnter={onMouseEnter}
onNavigate={onNavigate}
locale={locale}
shallow={shallow}
Expand All @@ -50,6 +48,8 @@ export const SimpleLink = forwardRef(
<chakra.a
ref={ref}
onClick={onClick}
onMouseEnter={onMouseEnter}
onTouchStart={onTouchStart}
target={newTab || isExternal ? '_blank' : undefined}
rel={isExternal ? 'noopener' : undefined}
{...rest}
Expand Down
Loading