diff --git a/packages/sanity/src/_singletons/core/releases/BundlesMetadataContext.ts b/packages/sanity/src/_singletons/core/releases/BundlesMetadataContext.ts new file mode 100644 index 00000000000..09c2f0fc4c2 --- /dev/null +++ b/packages/sanity/src/_singletons/core/releases/BundlesMetadataContext.ts @@ -0,0 +1,18 @@ +import {createContext} from 'react' + +import type {MetadataWrapper} from '../../../core/store/bundles/createBundlesMetadataAggregator' + +/** + * @internal + */ +export interface BundlesMetadataContextValue { + state: MetadataWrapper + addBundleSlugsToListener: (slugs: string[]) => void + removeBundleSlugsFromListener: (slugs: string[]) => void +} + +/** + * @internal + * @hidden + */ +export const BundlesMetadataContext = createContext(null) diff --git a/packages/sanity/src/_singletons/index.ts b/packages/sanity/src/_singletons/index.ts index ca77d8f8c1d..3c10545eef7 100644 --- a/packages/sanity/src/_singletons/index.ts +++ b/packages/sanity/src/_singletons/index.ts @@ -66,3 +66,4 @@ export * from './context/VirtualizerScrollInstanceContext' export * from './context/WorkspaceContext' export * from './context/WorkspacesContext' export * from './context/ZIndexContext' +export * from './core/releases/BundlesMetadataContext' diff --git a/packages/sanity/src/core/releases/components/BundleMenuButton/BundleMenuButton.tsx b/packages/sanity/src/core/releases/components/BundleMenuButton/BundleMenuButton.tsx index 67dacb6ba27..4be88630e87 100644 --- a/packages/sanity/src/core/releases/components/BundleMenuButton/BundleMenuButton.tsx +++ b/packages/sanity/src/core/releases/components/BundleMenuButton/BundleMenuButton.tsx @@ -9,9 +9,10 @@ import {useBundleOperations} from '../../../store/bundles/useBundleOperations' type Props = { bundle?: BundleDocument + documentCount: number } -export const BundleMenuButton = ({bundle}: Props) => { +export const BundleMenuButton = ({bundle, documentCount}: Props) => { const {deleteBundle, updateBundle} = useBundleOperations() const router = useRouter() const isBundleArchived = !!bundle?.archivedAt @@ -56,8 +57,7 @@ export const BundleMenuButton = ({bundle}: Props) => { setIsPerformingOperation(false) } - // TODO: Replace this with the count once it's available. Wait for @jordanl17 change on that. - const count = 2 + const bundleHasDocuments = !!documentCount return ( <> @@ -92,8 +92,10 @@ export const BundleMenuButton = ({bundle}: Props) => { {showDiscardDialog && ( setShowDiscardDialog(false)} + // remove body padding if no documents in release + padding={bundleHasDocuments} footer={{ confirmButton: { tone: 'default', @@ -103,9 +105,11 @@ export const BundleMenuButton = ({bundle}: Props) => { }, }} > - - This will also delete {count} document version{count > 1 ? 's' : ''}. - + {bundleHasDocuments && ( + + This will also delete {documentCount} document version{documentCount > 1 ? 's' : ''}. + + )} )} diff --git a/packages/sanity/src/core/releases/components/BundleMenuButton/__tests__/BundleMenuButton.test.tsx b/packages/sanity/src/core/releases/components/BundleMenuButton/__tests__/BundleMenuButton.test.tsx index e0a05c6bcd8..0deebe1a9ea 100644 --- a/packages/sanity/src/core/releases/components/BundleMenuButton/__tests__/BundleMenuButton.test.tsx +++ b/packages/sanity/src/core/releases/components/BundleMenuButton/__tests__/BundleMenuButton.test.tsx @@ -1,4 +1,4 @@ -import {describe, expect, jest, test} from '@jest/globals' +import {beforeEach, describe, expect, jest, test} from '@jest/globals' import {fireEvent, render, screen} from '@testing-library/react' import {act} from 'react' import {useRouter} from 'sanity/router' @@ -21,14 +21,18 @@ jest.mock('sanity/router', () => ({ useRouter: jest.fn().mockReturnValue({state: {}, navigate: jest.fn()}), })) -const renderTest = async (bundle: BundleDocument) => { +const renderTest = async (bundle: BundleDocument, documentCount: number = 2) => { const wrapper = await createTestProvider({ resources: [releasesUsEnglishLocaleBundle], }) - return render(, {wrapper}) + return render(, {wrapper}) } describe('BundleMenuButton', () => { + beforeEach(() => { + jest.clearAllMocks() + }) + test('will archive an unarchived bundle', async () => { const activeBundle: BundleDocument = { _id: 'activeBundle', @@ -40,6 +44,8 @@ describe('BundleMenuButton', () => { _createdAt: new Date().toISOString(), _updatedAt: new Date().toISOString(), _rev: '', + hue: 'gray', + icon: 'cube', } await renderTest(activeBundle) @@ -67,6 +73,8 @@ describe('BundleMenuButton', () => { _createdAt: new Date().toISOString(), _updatedAt: new Date().toISOString(), _rev: '', + hue: 'gray', + icon: 'cube', } await renderTest(archivedBundle) @@ -82,7 +90,7 @@ describe('BundleMenuButton', () => { }) }) - test('will delete a bundle', async () => { + test('will delete a bundle with documents', async () => { const activeBundle: BundleDocument = { _id: 'activeBundle', _type: 'bundle', @@ -93,6 +101,8 @@ describe('BundleMenuButton', () => { _createdAt: new Date().toISOString(), _updatedAt: new Date().toISOString(), _rev: '', + hue: 'gray', + icon: 'cube', } await renderTest(activeBundle) @@ -102,6 +112,8 @@ describe('BundleMenuButton', () => { fireEvent.click(screen.getByText('Delete')) }) expect(useBundleOperations().deleteBundle).not.toHaveBeenCalled() + // TODO: remove not exact once i18n used for strings + screen.getByText('This will also delete 2 document versions', {exact: false}) await act(() => { fireEvent.click(screen.getByText('Confirm')) @@ -110,4 +122,37 @@ describe('BundleMenuButton', () => { expect(useBundleOperations().deleteBundle).toHaveBeenCalledWith(activeBundle) expect(useRouter().navigate).not.toHaveBeenCalled() }) + + test('will delete a bundle with no documents', async () => { + const activeEmptyBundle: BundleDocument = { + _id: 'activeEmptyBundle', + _type: 'bundle', + archivedAt: new Date().toISOString(), + title: 'activeEmptyBundle', + name: 'activeEmptyBundle', + authorId: 'author', + _createdAt: new Date().toISOString(), + _updatedAt: new Date().toISOString(), + _rev: '', + hue: 'gray', + icon: 'cube', + } + await renderTest(activeEmptyBundle, 0) + + fireEvent.click(screen.getByLabelText('Release menu')) + + await act(() => { + fireEvent.click(screen.getByText('Delete')) + }) + expect(useBundleOperations().deleteBundle).not.toHaveBeenCalled() + // confirm dialog body is hidden when no documents in bundle + expect(screen.queryByTestId('confirm-delete-body')).toBeNull() + + await act(() => { + fireEvent.click(screen.getByText('Confirm')) + }) + + expect(useBundleOperations().deleteBundle).toHaveBeenCalledWith(activeEmptyBundle) + expect(useRouter().navigate).not.toHaveBeenCalled() + }) }) diff --git a/packages/sanity/src/core/releases/components/BundlesTable/BundleHeader.tsx b/packages/sanity/src/core/releases/components/ReleasesTable/ReleaseHeader.tsx similarity index 59% rename from packages/sanity/src/core/releases/components/BundlesTable/BundleHeader.tsx rename to packages/sanity/src/core/releases/components/ReleasesTable/ReleaseHeader.tsx index b4c01941bb2..f2189840dee 100644 --- a/packages/sanity/src/core/releases/components/BundlesTable/BundleHeader.tsx +++ b/packages/sanity/src/core/releases/components/ReleasesTable/ReleaseHeader.tsx @@ -1,16 +1,16 @@ import {SearchIcon} from '@sanity/icons' -import {Button, Card, Flex, Stack, TextInput} from '@sanity/ui' +import {Box, Button, Card, Flex, Stack, Text, TextInput} from '@sanity/ui' /** * @internal */ -export interface BundleHeaderProps { +export interface ReleaseHeaderProps { searchDisabled?: boolean searchTerm?: string setSearchTerm: (value?: string) => void } -export function BundleHeader({searchDisabled, searchTerm, setSearchTerm}: BundleHeaderProps) { +export function ReleaseHeader({searchDisabled, searchTerm, setSearchTerm}: ReleaseHeaderProps) { return ( @@ -29,6 +29,22 @@ export function BundleHeader({searchDisabled, searchTerm, setSearchTerm}: Bundle clearButton={!!searchTerm} /> + {/* Number of documents */} + + + + Documents + + + + {/* Created */} + +