UNPKG

sanity-plugin-media

Version:

This version of `sanity-plugin-media` is for Sanity Studio V3.

374 lines (348 loc) 11.2 kB
// @vitest-environment node import {describe, expect, it, vi} from 'vitest' import { notificationsAssetsDeleteCompleteEpic, notificationsAssetsDeleteErrorEpic, notificationsAssetsTagsAddCompleteEpic, notificationsAssetsTagsRemoveCompleteEpic, notificationsAssetsUpdateCompleteEpic, notificationsAssetsUploadCompleteEpic, notificationsGenericErrorEpic, notificationsTagCreateCompleteEpic, notificationsTagDeleteCompleteEpic, notificationsTagUpdateCompleteEpic } from './index' import {assetsActions, initialState as assetsInitialState} from '../assets' import {ASSETS_ACTIONS} from '../assets/actions' import {tagsActions} from '../tags' import {uploadsActions} from '../uploads' import {createEpicTestStore} from '../../__tests__/fixtures/createEpicTestStore' import {createMockSanityClient} from '../../__tests__/fixtures/mockSanityClient' import type {AssetItem, AssetType, ImageAsset, Tag} from '../../types' const sampleAsset = { _id: 'a1', _type: 'sanity.imageAsset', _createdAt: '', _updatedAt: '', _rev: 'r1', originalFilename: 'x.png', size: 1, mimeType: 'image/png', url: 'https://example.com/x.png' } as ImageAsset const sampleAsset2 = { ...sampleAsset, _id: 'a2' } as ImageAsset const sampleTag: Tag = { _id: 't1', _type: 'media.tag', _createdAt: '', _updatedAt: '', _rev: 'tr', name: {_type: 'slug', current: 'alpha'} } const assetItem = (asset: ImageAsset): AssetItem => ({ _type: 'asset', asset, picked: false, updating: false }) function assetsWithRows(rows: Record<string, AssetItem>) { return { ...assetsInitialState, assetTypes: ['image'] as AssetType[], allIds: Object.keys(rows), byIds: rows } } describe('notificationsAssetsDeleteCompleteEpic', () => { it('adds info notification with pluralized asset count', async () => { const store = createEpicTestStore( notificationsAssetsDeleteCompleteEpic, createMockSanityClient({}), {} ) store.dispatch(assetsActions.deleteComplete({assetIds: ['x', 'y']})) await vi.waitFor(() => { expect(store.getState().notifications.items).toEqual([ {asset: undefined, status: 'info', title: '2 assets deleted'} ]) }) }) }) describe('notificationsAssetsDeleteErrorEpic', () => { it('adds error notification with count', async () => { const store = createEpicTestStore( notificationsAssetsDeleteErrorEpic, createMockSanityClient({}), { assets: assetsWithRows({ a1: {...assetItem(sampleAsset), updating: true} }) } ) store.dispatch(assetsActions.deleteError({assetIds: ['a1'], error: {} as any})) await vi.waitFor(() => { const [n] = store.getState().notifications.items expect(n.status).toBe('error') expect(n.title).toBe( 'Unable to delete 1 asset. Please review any asset errors and try again.' ) }) }) }) describe('notificationsAssetsUploadCompleteEpic', () => { it('adds info notification from upload check results count', async () => { const store = createEpicTestStore( notificationsAssetsUploadCompleteEpic, createMockSanityClient({}), {} ) store.dispatch( uploadsActions.checkComplete({ results: {h1: 'id1', h2: null} }) ) await vi.waitFor(() => { expect(store.getState().notifications.items).toEqual([ {asset: undefined, status: 'info', title: 'Uploaded 2 assets'} ]) }) }) }) const tagsWithTagUpdating = { allIds: ['t1'], byIds: { t1: {_type: 'tag' as const, tag: sampleTag, picked: false, updating: true} }, creating: false, fetchCount: -1, fetching: false, panelVisible: true } describe('notificationsAssetsTagsAddCompleteEpic', () => { it('adds info notification with asset count', async () => { const store = createEpicTestStore( notificationsAssetsTagsAddCompleteEpic, createMockSanityClient({}), { assets: assetsWithRows({ a1: {...assetItem(sampleAsset), updating: true}, a2: {...assetItem(sampleAsset2), updating: true} }), tags: tagsWithTagUpdating } ) store.dispatch( ASSETS_ACTIONS.tagsAddComplete({ assets: [assetItem(sampleAsset), assetItem(sampleAsset2)], tag: sampleTag }) ) await vi.waitFor(() => { expect(store.getState().notifications.items).toEqual([ {asset: undefined, status: 'info', title: 'Tag added to 2 assets'} ]) }) }) }) describe('notificationsAssetsTagsRemoveCompleteEpic', () => { it('adds info notification with asset count', async () => { const store = createEpicTestStore( notificationsAssetsTagsRemoveCompleteEpic, createMockSanityClient({}), { assets: assetsWithRows({ a1: {...assetItem(sampleAsset), updating: true} }), tags: tagsWithTagUpdating } ) store.dispatch( ASSETS_ACTIONS.tagsRemoveComplete({ assets: [assetItem(sampleAsset)], tag: sampleTag }) ) await vi.waitFor(() => { expect(store.getState().notifications.items).toEqual([ {asset: undefined, status: 'info', title: 'Tag removed from 1 asset'} ]) }) }) }) describe('notificationsAssetsUpdateCompleteEpic', () => { it('batches multiple updateComplete actions into one notification after buffer window', async () => { vi.useFakeTimers() const store = createEpicTestStore( notificationsAssetsUpdateCompleteEpic, createMockSanityClient({}), { assets: assetsWithRows({ a1: {...assetItem(sampleAsset), updating: true}, a2: {...assetItem(sampleAsset2), updating: true} }) } ) store.dispatch(assetsActions.updateComplete({asset: sampleAsset})) store.dispatch(assetsActions.updateComplete({asset: sampleAsset2})) await vi.advanceTimersByTimeAsync(2000) expect(store.getState().notifications.items).toEqual([ {asset: undefined, status: 'info', title: '2 assets updated'} ]) vi.useRealTimers() }) it('emits separate notifications when updates fall in different buffer windows', async () => { vi.useFakeTimers() const store = createEpicTestStore( notificationsAssetsUpdateCompleteEpic, createMockSanityClient({}), { assets: assetsWithRows({ a1: {...assetItem(sampleAsset), updating: true}, a2: {...assetItem(sampleAsset2), updating: true} }) } ) store.dispatch(assetsActions.updateComplete({asset: sampleAsset})) await vi.advanceTimersByTimeAsync(2000) store.dispatch(assetsActions.updateComplete({asset: sampleAsset2})) await vi.advanceTimersByTimeAsync(2000) expect(store.getState().notifications.items).toEqual([ {asset: undefined, status: 'info', title: '1 asset updated'}, {asset: undefined, status: 'info', title: '1 asset updated'} ]) vi.useRealTimers() }) }) describe('notificationsGenericErrorEpic', () => { it('maps assets.updateError to error notification title', async () => { const store = createEpicTestStore(notificationsGenericErrorEpic, createMockSanityClient({}), { assets: assetsWithRows({ a1: {...assetItem(sampleAsset), updating: true} }) }) store.dispatch( assetsActions.updateError({ asset: sampleAsset, error: {message: 'patch failed', statusCode: 500} }) ) await vi.waitFor(() => { expect(store.getState().notifications.items).toEqual([ {asset: undefined, status: 'error', title: 'An error occurred: patch failed'} ]) }) }) it('maps assets.fetchError (bare HttpError payload) to error notification title', async () => { const store = createEpicTestStore(notificationsGenericErrorEpic, createMockSanityClient({}), { assets: { ...assetsInitialState, assetTypes: ['image'] as AssetType[], fetching: true } }) store.dispatch( assetsActions.fetchError({ message: 'fetch failed', statusCode: 503 }) ) await vi.waitFor(() => { expect(store.getState().notifications.items[0].title).toBe('An error occurred: fetch failed') }) }) it('maps tags.createError to error notification title', async () => { const store = createEpicTestStore(notificationsGenericErrorEpic, createMockSanityClient({}), { tags: { creating: true, creatingError: undefined, allIds: [], byIds: {}, fetchCount: -1, fetching: false, panelVisible: true } as any }) store.dispatch( tagsActions.createError({ name: 'n', error: {message: 'tag create', statusCode: 400} }) ) await vi.waitFor(() => { expect(store.getState().notifications.items[0].title).toBe('An error occurred: tag create') }) }) it('maps uploads.uploadError to error notification title', async () => { const store = createEpicTestStore(notificationsGenericErrorEpic, createMockSanityClient({}), { uploads: {allIds: ['h'], byIds: {h: {} as any}} }) store.dispatch( uploadsActions.uploadError({ hash: 'h', error: {message: 'upload bad', statusCode: 413} }) ) await vi.waitFor(() => { expect(store.getState().notifications.items[0].title).toBe('An error occurred: upload bad') }) }) }) describe('notificationsTagCreateCompleteEpic', () => { it('adds tag created notification', async () => { const store = createEpicTestStore( notificationsTagCreateCompleteEpic, createMockSanityClient({}), {} ) store.dispatch(tagsActions.createComplete({tag: sampleTag})) await vi.waitFor(() => { expect(store.getState().notifications.items).toEqual([ {asset: undefined, status: 'info', title: 'Tag created'} ]) }) }) }) describe('notificationsTagDeleteCompleteEpic', () => { it('adds tag deleted notification', async () => { const store = createEpicTestStore( notificationsTagDeleteCompleteEpic, createMockSanityClient({}), {} ) store.dispatch(tagsActions.deleteComplete({tagId: 't1'})) await vi.waitFor(() => { expect(store.getState().notifications.items).toEqual([ {asset: undefined, status: 'info', title: 'Tag deleted'} ]) }) }) }) describe('notificationsTagUpdateCompleteEpic', () => { it('adds tag updated notification', async () => { const store = createEpicTestStore( notificationsTagUpdateCompleteEpic, createMockSanityClient({}), { tags: { allIds: ['t1'], byIds: { t1: {_type: 'tag', tag: sampleTag, picked: false, updating: true} }, creating: false, fetchCount: -1, fetching: false, panelVisible: true } } ) store.dispatch(tagsActions.updateComplete({tag: sampleTag})) await vi.waitFor(() => { expect(store.getState().notifications.items).toEqual([ {asset: undefined, status: 'info', title: 'Tag updated'} ]) }) }) })