(frontend) Move doc modal

We can now move a doc to another doc from a search
modal. It will make it easier to move a doc
without having to scroll through the doc grid to
find the destination doc.
We kept most of the logic implemented in the
doc grid dnd.
This commit is contained in:
Anthony LC
2026-02-20 17:22:14 +01:00
parent 217af2e2a8
commit 2718321fbe
14 changed files with 480 additions and 124 deletions

View File

@@ -15,6 +15,7 @@ and this project adheres to
- ✨(frontend) Can print a doc #1832
- ✨(backend) manage reconciliation requests for user accounts #1878
- 👷(CI) add GHCR workflow for forked repo testing #1851
- ✨(frontend) Move doc modal #1886
- ⚡️(backend) remove content from Document serializer when asked #1910
- ✨(backend) allow the duplication of subpages #1893
- ✨(backend) Onboarding docs for new users #1891

View File

@@ -1,10 +1,19 @@
import { expect, test } from '@playwright/test';
import { createDoc, mockedListDocs, toggleHeaderMenu } from './utils-common';
import {
createDoc,
getGridRow,
mockedListDocs,
toggleHeaderMenu,
verifyDocName,
} from './utils-common';
import { createRootSubPage } from './utils-sub-pages';
test.describe('Doc grid dnd', () => {
test('it creates a doc', async ({ page, browserName }) => {
test.describe('Doc grid move', () => {
test('it checks drag and drop functionality', async ({
page,
browserName,
}) => {
await page.goto('/');
const header = page.locator('header').first();
await createDoc(page, 'Draggable doc', browserName, 1);
@@ -29,7 +38,7 @@ test.describe('Doc grid dnd', () => {
await expect(draggableElement).toBeVisible();
await expect(dropZone).toBeVisible();
// Obtenir les positions des éléments
// Get the position of the elements
const draggableBoundingBox = await draggableElement.boundingBox();
const dropZoneBoundingBox = await dropZone.boundingBox();
@@ -46,7 +55,7 @@ test.describe('Doc grid dnd', () => {
);
await page.mouse.down();
// Déplacer vers la zone cible
// Move to the target zone
await page.mouse.move(
dropZoneBoundingBox.x + dropZoneBoundingBox.width / 2,
dropZoneBoundingBox.y + dropZoneBoundingBox.height / 2,
@@ -161,6 +170,55 @@ test.describe('Doc grid dnd', () => {
await page.mouse.up();
});
test('it moves a doc from the doc search modal', async ({
page,
browserName,
}) => {
await page.goto('/');
const [titleDoc1] = await createDoc(page, 'Draggable doc', browserName, 1);
await page.getByRole('button', { name: 'Back to homepage' }).click();
const [titleDoc2] = await createDoc(page, 'Droppable doc', browserName, 1);
await page.getByRole('button', { name: 'Back to homepage' }).click();
const docsGrid = page.getByTestId('docs-grid');
await expect(docsGrid.getByText(titleDoc1)).toBeVisible();
await expect(docsGrid.getByText(titleDoc2)).toBeVisible();
const row = await getGridRow(page, titleDoc1);
await row.getByText(`more_horiz`).click();
await page.getByRole('menuitem', { name: 'Move into a doc' }).click();
await expect(
page.getByRole('dialog').getByRole('heading', { name: 'Move' }),
).toBeVisible();
const input = page.getByRole('combobox', { name: 'Quick search input' });
await input.click();
await input.fill(titleDoc2);
await expect(page.getByRole('option').getByText(titleDoc2)).toBeVisible();
// Select the first result
await page.keyboard.press('Enter');
// The CTA should get the focus
await page.keyboard.press('Tab');
// Validate the move action
await page.keyboard.press('Enter');
await expect(docsGrid.getByText(titleDoc1)).toBeHidden();
await docsGrid
.getByRole('link', { name: `Open document ${titleDoc2}` })
.click();
await verifyDocName(page, titleDoc2);
const docTree = page.getByTestId('doc-tree');
await expect(docTree.getByText(titleDoc1)).toBeVisible();
});
});
test.describe('Doc grid dnd mobile', () => {

View File

@@ -15,6 +15,7 @@ export type QuickSearchAction = {
export type QuickSearchData<T> = {
groupName: string;
groupKey?: string;
elements: T[];
emptyString?: string;
startActions?: QuickSearchAction[];
@@ -30,13 +31,13 @@ export type QuickSearchProps = {
loading?: boolean;
label?: string;
placeholder?: string;
groupKey?: string;
};
export const QuickSearch = ({
onFilter,
inputContent,
inputValue,
loading,
showInput = true,
label,
placeholder,
@@ -72,10 +73,10 @@ export const QuickSearch = ({
tabIndex={-1}
value={selectedValue}
onValueChange={handleValueChange}
disablePointerSelection
>
{showInput && (
<QuickSearchInput
loading={loading}
withSeparator={hasChildrens(children)}
inputValue={inputValue}
onFilter={onFilter}

View File

@@ -28,7 +28,7 @@ export const QuickSearchGroup = <T,>({
{group.startActions?.map((action, index) => {
return (
<QuickSearchItem
key={`${group.groupName}-action-${index}`}
key={`${group.groupKey ?? group.groupName}-start-actions-${index}`}
onSelect={action.onSelect}
>
{action.content}
@@ -38,8 +38,8 @@ export const QuickSearchGroup = <T,>({
{group.elements.map((groupElement, index) => {
return (
<QuickSearchItem
id={`${group.groupName}-element-${index}`}
key={`${group.groupName}-element-${index}`}
id={`${group.groupKey ?? group.groupName}-element-${index}`}
key={`${group.groupKey ?? group.groupName}-element-${index}`}
onSelect={() => {
onSelect?.(groupElement);
}}
@@ -51,7 +51,7 @@ export const QuickSearchGroup = <T,>({
{group.endActions?.map((action, index) => {
return (
<QuickSearchItem
key={`${group.groupName}-action-${index}`}
key={`${group.groupKey ?? group.groupName}-end-actions-${index}`}
onSelect={action.onSelect}
>
{action.content}

View File

@@ -1,6 +1,5 @@
import { Loader } from '@gouvfr-lasuite/cunningham-react';
import { Command } from 'cmdk';
import { ReactNode } from 'react';
import { PropsWithChildren } from 'react';
import { useTranslation } from 'react-i18next';
import { HorizontalSeparator } from '@/components';
@@ -9,19 +8,16 @@ import { useCunninghamTheme } from '@/cunningham';
import { Box } from '../Box';
import { Icon } from '../Icon';
type Props = {
loading?: boolean;
type QuickSearchInputProps = {
inputValue?: string;
onFilter?: (str: string) => void;
placeholder?: string;
children?: ReactNode;
withSeparator?: boolean;
listId?: string;
onUserInteract?: () => void;
isExpanded?: boolean;
};
export const QuickSearchInput = ({
loading,
inputValue,
onFilter,
placeholder,
@@ -30,7 +26,7 @@ export const QuickSearchInput = ({
listId,
onUserInteract,
isExpanded,
}: Props) => {
}: PropsWithChildren<QuickSearchInputProps>) => {
const { t } = useTranslation();
const { spacingsTokens } = useCunninghamTheme();
@@ -52,14 +48,7 @@ export const QuickSearchInput = ({
$gap={spacingsTokens['2xs']}
$padding={{ horizontal: 'base', vertical: 'sm' }}
>
{!loading && (
<Icon iconName="search" $variation="secondary" aria-hidden="true" />
)}
{loading && (
<div>
<Loader size="small" />
</div>
)}
<Command.Input
autoFocus={true}
aria-label={t('Quick search input')}

View File

@@ -22,7 +22,7 @@ export const QuickSearchStyle = createGlobalStyle`
padding: var(--c--globals--spacings--xs);
background: white;
outline: none;
color: var(--c--globals--colors--gray-1000);
color: var(--c--contextuals--content--semantic--neutral--primary);
border-radius: var(--c--globals--spacings--0);
&::placeholder {

View File

@@ -181,6 +181,7 @@ export const SearchPage = ({
setSearch(value);
}}
onKeyDown={handleKeyDown}
autoComplete="off"
/>
</Box>
<Box

View File

@@ -12,15 +12,19 @@ import { DocSearchItem } from './DocSearchItem';
type DocSearchContentProps = {
search: string;
filters: DocSearchFiltersValues;
filterResults?: (doc: Doc) => boolean;
onSelect: (doc: Doc) => void;
onLoadingChange?: (loading: boolean) => void;
renderSearchElement?: (doc: Doc) => React.ReactNode;
};
export const DocSearchContent = ({
search,
filters,
filterResults,
onSelect,
onLoadingChange,
renderSearchElement,
}: DocSearchContentProps) => {
const {
data,
@@ -38,10 +42,15 @@ export const DocSearchContent = ({
const loading = isFetching || isRefetching || isLoading;
const docsData: QuickSearchData<Doc> = useMemo(() => {
const docs = data?.pages.flatMap((page) => page.results) || [];
let docs = data?.pages.flatMap((page) => page.results) || [];
if (filterResults) {
docs = docs.filter(filterResults);
}
return {
groupName: docs.length > 0 ? t('Select a document') : '',
groupKey: 'docs',
elements: search ? docs : [],
emptyString: t('No document found'),
endActions: hasNextPage
@@ -52,7 +61,7 @@ export const DocSearchContent = ({
]
: [],
};
}, [search, data?.pages, fetchNextPage, hasNextPage]);
}, [search, data?.pages, fetchNextPage, hasNextPage, filterResults]);
useEffect(() => {
onLoadingChange?.(loading);
@@ -62,7 +71,9 @@ export const DocSearchContent = ({
<QuickSearchGroup
onSelect={onSelect}
group={docsData}
renderElement={(doc) => <DocSearchItem doc={doc} />}
renderElement={
renderSearchElement ?? ((doc) => <DocSearchItem doc={doc} />)
}
/>
);
};

View File

@@ -1,3 +1,4 @@
export * from './DocSearchContent';
export * from './DocSearchModal';
export * from './DocSearchFilters';
export * from './DocSearchSubPageContent';

View File

@@ -0,0 +1,260 @@
import { Button, Modal, ModalSize } from '@gouvfr-lasuite/cunningham-react';
import { TreeViewMoveModeEnum } from '@gouvfr-lasuite/ui-kit';
import Image from 'next/image';
import { useState } from 'react';
import { Trans, useTranslation } from 'react-i18next';
import { createGlobalStyle, css } from 'styled-components';
import { useDebouncedCallback } from 'use-debounce';
import { Box, ButtonCloseModal, Text } from '@/components';
import { QuickSearch } from '@/components/quick-search';
import { Doc, useMoveDoc, useTrans } from '@/docs/doc-management';
import { DocSearchContent, DocSearchTarget } from '@/docs/doc-search';
import EmptySearchIcon from '@/docs/doc-search/assets/illustration-docs-empty.png';
import { useResponsiveStore } from '@/stores';
import { DocsGridItemDate, DocsGridItemTitle } from './DocsGridItem';
export const DocMoveModalStyle = createGlobalStyle`
.c__modal--full .c__modal__scroller {
height: 100vh;
}
.c__modal__scroller:has(.quick-search-container){
display: flex;
flex-direction: column;
.quick-search-container [cmdk-list] {
overflow-y: auto;
}
.c__modal__title {
padding-inline: var(--c--globals--spacings--md);
padding-block: var(--c--globals--spacings--base);
border-bottom: 1px solid var(--c--contextuals--border--surface--primary);
}
.c__modal__footer {
margin-top: 0rem;
}
.quick-search-input{
padding-inline: var(--c--globals--spacings--md);
}
.c__modal__footer{
border-top: 1px solid var(--c--contextuals--border--surface--primary);
}
.quick-search-container [cmdk-item] {
border-radius: 4px;
}
.quick-search-container [cmdk-item][data-selected='true'] {
background: var(--c--contextuals--background--semantic--contextual--primary);
}
}
`;
type DocMoveModalGlobalProps = {
doc: Doc;
isOpen: boolean;
onClose: () => void;
};
export const DocMoveModal = ({
doc,
isOpen,
onClose,
}: DocMoveModalGlobalProps) => {
const { t } = useTranslation();
const [loading, setLoading] = useState(false);
const [docSelected, setDocSelected] = useState<Doc>();
const { untitledDocument } = useTrans();
const docTitle = doc.title || untitledDocument;
const { mutate: moveDoc } = useMoveDoc(true);
const [search, setSearch] = useState('');
const { isDesktop } = useResponsiveStore();
const handleInputSearch = useDebouncedCallback(setSearch, 700);
const handleSelect = (docSelected: Doc) => {
setDocSelected(docSelected);
};
const handleMoveDoc = () => {
if (!docSelected?.id) {
return;
}
moveDoc({
sourceDocumentId: doc.id,
targetDocumentId: docSelected.id,
position: TreeViewMoveModeEnum.FIRST_CHILD,
});
};
return (
<>
<DocMoveModalStyle />
<Modal
isOpen={isOpen}
onClose={onClose}
closeOnClickOutside
size={isDesktop ? ModalSize.LARGE : ModalSize.FULL}
hideCloseButton
aria-label={t('Move Modal')}
rightActions={
<Box $direction="row-reverse" $padding="md" $gap="small">
<Button
aria-label={t('Move the document to the selected location')}
variant="primary"
fullWidth
onClick={() => {
handleMoveDoc();
}}
disabled={!docSelected}
>
{t('Move here')}
</Button>
<Button
aria-label={t('Cancel the move')}
variant="secondary"
fullWidth
onClick={onClose}
>
{t('Cancel')}
</Button>
</Box>
}
title={
<Box>
<Box
$direction="row"
$justify="space-between"
$align="center"
$width="100%"
>
<Text as="h2" $margin="0" $size="h6" $align="flex-start">
{t('Move')}
</Text>
<ButtonCloseModal
aria-label={t('Close the move modal')}
onClick={() => onClose()}
/>
</Box>
<Box $margin={{ top: 'sm' }}>
<Text
$size="sm"
$variation="secondary"
$display="inline"
$weight="normal"
$textAlign="left"
>
<Trans t={t}>
Choose the new location for <strong>{docTitle}</strong>.
</Trans>
</Text>
</Box>
</Box>
}
>
<Box
aria-label={t('Move modal')}
$direction="column"
$justify="space-between"
className="--docs--doc-move-modal"
onKeyDown={(e) => {
// Close modal on Escape
if (e.key === 'Escape') {
onClose();
return;
}
// Prevent keyboard events from bubbling to parent components (e.g., drag and drop)
e.stopPropagation();
}}
>
<QuickSearch
placeholder={t('Search for a doc')}
loading={loading}
onFilter={handleInputSearch}
>
<Box
$padding={{ horizontal: 'md' }}
$height={isDesktop ? 'min(60vh, 500px)' : 'calc(100vh - 260px)'}
>
{search.length === 0 && (
<Box
$direction="column"
$height="100%"
$align="center"
$justify="center"
>
<Image
width={320}
src={EmptySearchIcon}
alt={t('No active search')}
style={{ maxWidth: '100%', height: 'auto' }}
priority
/>
</Box>
)}
{search && (
<Box>
<DocSearchContent
search={search}
filters={{ target: DocSearchTarget.ALL }}
filterResults={(docResults) =>
docResults.id !== doc.id && docResults.abilities.move
}
onSelect={handleSelect}
onLoadingChange={setLoading}
renderSearchElement={(docSearch) => {
const isSelected = docSelected?.id === docSearch.id;
return (
<Box
className="--docs--doc-move-modal-search-item"
$direction="row"
$align="center"
$justify="space-between"
$width="100%"
$gap="sm"
$padding="3xs"
$css={css`
background-color: ${isSelected
? 'var(--c--contextuals--background--semantic--brand--tertiary)'
: 'transparent'};
border: 1px solid
${isSelected
? 'var(--c--contextuals--border--semantic--brand--tertiary)'
: 'transparent'};
border-radius: var(--c--globals--spacings--3xs);
/* Arrow key navigation highlight */
&[data-selected='true'] {
${!isSelected &&
`
background-color: var(--c--contextuals--background--semantic--contextual--primary);
border-color: transparent;
`}
}
`}
aria-selected={isSelected}
>
<DocsGridItemTitle
doc={docSearch}
withTooltip={false}
/>
<DocsGridItemDate
doc={docSearch}
isDesktop={isDesktop}
isInTrashbin={false}
/>
</Box>
);
}}
/>
</Box>
)}
</Box>
</QuickSearch>
</Box>
</Modal>
</>
);
};

View File

@@ -11,9 +11,12 @@ import {
useCreateFavoriteDoc,
useDeleteFavoriteDoc,
useDuplicateDoc,
useTrans,
} from '@/docs/doc-management';
import { DocShareModal } from '@/docs/doc-share';
import { DocMoveModal } from './DocMoveModal';
interface DocsGridActionsProps {
doc: Doc;
}
@@ -23,6 +26,8 @@ export const DocsGridActions = ({ doc }: DocsGridActionsProps) => {
const deleteModal = useModal();
const shareModal = useModal();
const importModal = useModal();
const { untitledDocument } = useTrans();
const { mutate: duplicateDoc } = useDuplicateDoc();
@@ -56,6 +61,15 @@ export const DocsGridActions = ({ doc }: DocsGridActionsProps) => {
testId: `docs-grid-actions-share-${doc.id}`,
},
{
label: t('Move into a doc'),
icon: 'copy_all',
callback: () => {
importModal.open();
},
testId: `docs-grid-actions-import-${doc.id}`,
show: doc.abilities.move,
},
{
label: t('Duplicate'),
icon: 'content_copy',
@@ -78,7 +92,7 @@ export const DocsGridActions = ({ doc }: DocsGridActionsProps) => {
},
];
const documentTitle = doc.title || t('Untitled document');
const documentTitle = doc.title || untitledDocument;
const menuLabel = t('Open the menu of actions for the document: {{title}}', {
title: documentTitle,
});
@@ -116,6 +130,13 @@ export const DocsGridActions = ({ doc }: DocsGridActionsProps) => {
{shareModal.isOpen && (
<DocShareModal doc={doc} onClose={shareModal.close} />
)}
{importModal.isOpen && (
<DocMoveModal
doc={doc}
onClose={importModal.close}
isOpen={importModal.isOpen}
/>
)}
</>
);
};

View File

@@ -7,7 +7,7 @@ import { css } from 'styled-components';
import { Box, Icon, StyledLink, Text } from '@/components';
import { useConfig } from '@/core';
import { useCunninghamTheme } from '@/cunningham';
import { Doc, LinkReach, SimpleDocItem } from '@/docs/doc-management';
import { Doc, LinkReach, SimpleDocItem, useTrans } from '@/docs/doc-management';
import { useDate } from '@/hooks';
import { useResponsiveStore } from '@/stores';
@@ -26,14 +26,12 @@ export const DocsGridItem = ({ doc, dragMode = false }: DocsGridItemProps) => {
const searchParams = useSearchParams();
const target = searchParams.get('target');
const isInTrashbin = target === 'trashbin';
const { untitledDocument } = useTrans();
const { t } = useTranslation();
const { isDesktop } = useResponsiveStore();
const { flexLeft, flexRight } = useResponsiveDocGrid();
const { spacingsTokens } = useCunninghamTheme();
const isPublic = doc.link_reach === LinkReach.PUBLIC;
const isAuthenticated = doc.link_reach === LinkReach.AUTHENTICATED;
const isShared = isPublic || isAuthenticated;
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === 'Enter' || e.key === ' ') {
@@ -62,7 +60,7 @@ export const DocsGridItem = ({ doc, dragMode = false }: DocsGridItemProps) => {
`}
className="--docs--doc-grid-item"
aria-label={t('Open document: {{title}}', {
title: doc.title || t('Untitled document'),
title: doc.title || untitledDocument,
})}
>
<Box
@@ -82,6 +80,57 @@ export const DocsGridItem = ({ doc, dragMode = false }: DocsGridItemProps) => {
href={`/docs/${doc.id}`}
onKeyDown={handleKeyDown}
>
<DocsGridItemTitle doc={doc} withTooltip={!dragMode} />
</StyledLink>
</Box>
<Box
$flex={flexRight}
$direction="row"
$align="center"
$justify={isDesktop ? 'space-between' : 'flex-end'}
$gap="32px"
role="gridcell"
>
<StyledLink href={`/docs/${doc.id}`} tabIndex={-1}>
<DocsGridItemDate
doc={doc}
isDesktop={isDesktop}
isInTrashbin={isInTrashbin}
/>
</StyledLink>
<Box $direction="row" $align="center" $gap={spacingsTokens.lg}>
{isDesktop && (
<DocsGridItemSharedButton doc={doc} disabled={isInTrashbin} />
)}
{isInTrashbin ? (
<DocsGridTrashbinActions doc={doc} />
) : (
<DocsGridActions doc={doc} />
)}
</Box>
</Box>
</Box>
</>
);
};
export const DocsGridItemTitle = ({
doc,
withTooltip,
}: {
doc: Doc;
withTooltip: boolean;
}) => {
const { t } = useTranslation();
const { isDesktop } = useResponsiveStore();
const { spacingsTokens } = useCunninghamTheme();
const isPublic = doc.link_reach === LinkReach.PUBLIC;
const isAuthenticated = doc.link_reach === LinkReach.AUTHENTICATED;
const isShared = isPublic || isAuthenticated;
return (
<Box
data-testid={`docs-grid-name-${doc.id}`}
$direction="row"
@@ -102,23 +151,7 @@ export const DocsGridItem = ({ doc, dragMode = false }: DocsGridItemProps) => {
: undefined
}
>
{dragMode && (
<>
<Icon
$layer="background"
$theme="neutral"
$variation="primary"
$size="14px"
iconName={isPublic ? 'public' : 'vpn_lock'}
/>
<span className="sr-only">
{isPublic
? t('Accessible to anyone')
: t('Accessible to authenticated users')}
</span>
</>
)}
{!dragMode && (
{withTooltip ? (
<Tooltip
content={
<Text $textAlign="center">
@@ -129,7 +162,24 @@ export const DocsGridItem = ({ doc, dragMode = false }: DocsGridItemProps) => {
}
placement="top"
>
<div>
<Box>
<IconPublic isPublic={isPublic} />
</Box>
</Tooltip>
) : (
<IconPublic isPublic={isPublic} />
)}
</Box>
)}
</Box>
);
};
const IconPublic = ({ isPublic }: { isPublic: boolean }) => {
const { t } = useTranslation();
return (
<>
<Icon
$layer="background"
$theme="neutral"
@@ -142,41 +192,6 @@ export const DocsGridItem = ({ doc, dragMode = false }: DocsGridItemProps) => {
? t('Accessible to anyone')
: t('Accessible to authenticated users')}
</span>
</div>
</Tooltip>
)}
</Box>
)}
</Box>
</StyledLink>
</Box>
<Box
$flex={flexRight}
$direction="row"
$align="center"
$justify={isDesktop ? 'space-between' : 'flex-end'}
$gap="32px"
role="gridcell"
>
<DocsGridItemDate
doc={doc}
isDesktop={isDesktop}
isInTrashbin={isInTrashbin}
/>
<Box $direction="row" $align="center" $gap={spacingsTokens.lg}>
{isDesktop && (
<DocsGridItemSharedButton doc={doc} disabled={isInTrashbin} />
)}
{isInTrashbin ? (
<DocsGridTrashbinActions doc={doc} />
) : (
<DocsGridActions doc={doc} />
)}
</Box>
</Box>
</Box>
</>
);
};
@@ -210,15 +225,8 @@ export const DocsGridItemDate = ({
}
return (
<StyledLink href={`/docs/${doc.id}`} tabIndex={-1}>
<Text
$size="xs"
$layer="background"
$theme="neutral"
$variation="primary"
>
<Text $size="xs" $layer="background" $theme="neutral" $variation="primary">
{dateToDisplay}
</Text>
</StyledLink>
);
};

View File

@@ -6,7 +6,12 @@ import { useTranslation } from 'react-i18next';
import { css } from 'styled-components';
import { DropdownMenu, DropdownMenuOption, Icon } from '@/components';
import { Doc, KEY_LIST_DOC, useRestoreDoc } from '@/docs/doc-management';
import {
Doc,
KEY_LIST_DOC,
useRestoreDoc,
useTrans,
} from '@/docs/doc-management';
import { KEY_LIST_DOC_TRASHBIN } from '../api';
@@ -18,6 +23,7 @@ export const DocsGridTrashbinActions = ({
doc,
}: DocsGridTrashbinActionsProps) => {
const { t } = useTranslation();
const { untitledDocument } = useTrans();
const { toast } = useToastProvider();
const { mutate: restoreDoc, error } = useRestoreDoc({
listInvalidQueries: [KEY_LIST_DOC, KEY_LIST_DOC_TRASHBIN],
@@ -61,7 +67,7 @@ export const DocsGridTrashbinActions = ({
},
];
const documentTitle = doc.title || t('Untitled document');
const documentTitle = doc.title || untitledDocument;
const menuLabel = t('Open the menu of actions for the document: {{title}}', {
title: documentTitle,
});

View File

@@ -18,7 +18,9 @@ describe('DocsGridItemDate', () => {
it('should not render date when not on desktop', () => {
render(
<DocsGridItemDate
doc={{} as Doc}
doc={
{ updated_at: DateTime.now().minus({ minutes: 1 }).toISO() } as Doc
}
isDesktop={false}
isInTrashbin={false}
/>,
@@ -27,7 +29,7 @@ describe('DocsGridItemDate', () => {
},
);
expect(screen.queryByRole('link')).not.toBeInTheDocument();
expect(screen.queryByText('1 minute ago')).not.toBeInTheDocument();
});
[
@@ -66,7 +68,6 @@ describe('DocsGridItemDate', () => {
{ wrapper: AppWrapper },
);
expect(screen.getByRole('link')).toBeInTheDocument();
expect(screen.getByText(rendered)).toBeInTheDocument();
});
});
@@ -87,7 +88,6 @@ describe('DocsGridItemDate', () => {
{ wrapper: AppWrapper },
);
expect(screen.getByRole('link')).toBeInTheDocument();
expect(screen.getByText('il y a 5 jours')).toBeInTheDocument();
await i18next.changeLanguage('en');
@@ -134,7 +134,6 @@ describe('DocsGridItemDate', () => {
{ wrapper: AppWrapper },
);
expect(screen.getByRole('link')).toBeInTheDocument();
await waitFor(
() => {
expect(screen.getByText(rendered)).toBeInTheDocument();