sanity-plugin-media 4.1.1 → 4.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +56 -4
- package/dist/index.d.mts +131 -57
- package/dist/index.d.ts +131 -57
- package/dist/index.js +259 -98
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +259 -98
- package/dist/index.mjs.map +1 -1
- package/package.json +9 -2
- package/src/__tests__/fixtures/createEpicTestStore.ts +27 -0
- package/src/__tests__/fixtures/listenMock.ts +9 -0
- package/src/__tests__/fixtures/mockSanityClient.ts +84 -0
- package/src/__tests__/fixtures/renderWithProviders.tsx +54 -0
- package/src/__tests__/fixtures/rootState.ts +27 -0
- package/src/__tests__/fixtures/withinDialog.ts +28 -0
- package/src/components/Browser/Browser.test.tsx +44 -0
- package/src/components/CardAsset/CardAsset.test.tsx +322 -0
- package/src/components/DialogAssetEdit/Details.tsx +123 -44
- package/src/components/DialogAssetEdit/DialogAssetEdit.test.tsx +215 -0
- package/src/components/DialogAssetEdit/index.tsx +138 -30
- package/src/components/DialogTagCreate/DialogTagCreate.test.tsx +120 -0
- package/src/components/DialogTagEdit/DialogTagEdit.test.tsx +164 -0
- package/src/components/FormBuilderTool/FormBuilderTool.test.tsx +62 -0
- package/src/components/UploadDropzone/UploadDropzone.test.tsx +39 -0
- package/src/contexts/ToolOptionsContext.tsx +6 -3
- package/src/formSchema/index.test.ts +55 -0
- package/src/formSchema/index.ts +28 -12
- package/src/hooks/useVersionedClient.ts +1 -1
- package/src/modules/assets/deleteAndUpdateEpics.test.ts +86 -0
- package/src/modules/assets/fetchEpic.test.ts +72 -0
- package/src/modules/assets/reducer.test.ts +90 -0
- package/src/modules/assets/tagsAndListenerEpics.test.ts +205 -0
- package/src/modules/dialog/epics.test.ts +167 -0
- package/src/modules/dialog/reducer.test.ts +184 -0
- package/src/modules/notifications/epics.test.ts +373 -0
- package/src/modules/notifications/index.ts +24 -4
- package/src/modules/notifications/reducer.test.ts +53 -0
- package/src/modules/search/index.test.ts +35 -0
- package/src/modules/selectors.test.ts +20 -0
- package/src/modules/tags/epics.test.ts +95 -0
- package/src/modules/tags/index.test.ts +41 -0
- package/src/modules/uploads/epics.test.ts +108 -0
- package/src/modules/uploads/index.test.ts +58 -0
- package/src/operators/checkTagName.test.ts +28 -0
- package/src/types/index.ts +20 -7
- package/src/utils/blocksToText.test.ts +42 -0
- package/src/utils/constructFilter.test.ts +119 -0
- package/src/utils/generatePreviewBlobUrl.test.ts +69 -0
- package/src/utils/getAssetResolution.test.ts +12 -0
- package/src/utils/getDocumentAssetIds.test.ts +49 -0
- package/src/utils/getSchemeColor.test.ts +11 -0
- package/src/utils/getTagSelectOptions.test.ts +43 -0
- package/src/utils/getUniqueDocuments.test.ts +25 -0
- package/src/utils/imageDprUrl.test.ts +45 -0
- package/src/utils/isSupportedAssetType.test.ts +15 -0
- package/src/utils/sanitizeFormData.test.ts +58 -0
- package/src/utils/typeGuards.test.ts +17 -0
- package/src/utils/uploadSanityAsset.test.ts +28 -0
- package/src/utils/withMaxConcurrency.test.ts +42 -0
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// @vitest-environment node
|
|
2
|
+
|
|
3
|
+
import {describe, expect, it} from 'vitest'
|
|
4
|
+
import type {AssetType, ImageAsset} from '../../types'
|
|
5
|
+
import assetsReducer, {assetsActions, initialState, type AssetsReducerState} from './index'
|
|
6
|
+
|
|
7
|
+
const minimalImage = {
|
|
8
|
+
_id: 'img-1',
|
|
9
|
+
_type: 'sanity.imageAsset',
|
|
10
|
+
_createdAt: '2020-01-01',
|
|
11
|
+
_updatedAt: '2020-01-01',
|
|
12
|
+
_rev: 'r1',
|
|
13
|
+
originalFilename: 'a.png',
|
|
14
|
+
size: 1,
|
|
15
|
+
mimeType: 'image/png',
|
|
16
|
+
url: 'https://example.com/a.png'
|
|
17
|
+
} as ImageAsset
|
|
18
|
+
|
|
19
|
+
describe('assets slice', () => {
|
|
20
|
+
function stateWithOneAsset(): AssetsReducerState {
|
|
21
|
+
return {
|
|
22
|
+
...initialState,
|
|
23
|
+
assetTypes: ['image'] as AssetType[],
|
|
24
|
+
allIds: ['img-1'],
|
|
25
|
+
byIds: {
|
|
26
|
+
'img-1': {
|
|
27
|
+
_type: 'asset',
|
|
28
|
+
asset: minimalImage,
|
|
29
|
+
picked: false,
|
|
30
|
+
updating: false
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
it('pick toggles picked flag', () => {
|
|
37
|
+
let state = stateWithOneAsset()
|
|
38
|
+
state = assetsReducer(state, assetsActions.pick({assetId: 'img-1', picked: true}))
|
|
39
|
+
expect(state.byIds['img-1'].picked).toBe(true)
|
|
40
|
+
expect(state.lastPicked).toBe('img-1')
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
it('pickClear clears selection', () => {
|
|
44
|
+
let state = stateWithOneAsset()
|
|
45
|
+
state = assetsReducer(state, assetsActions.pick({assetId: 'img-1', picked: true}))
|
|
46
|
+
state = assetsReducer(state, assetsActions.pickClear())
|
|
47
|
+
expect(state.byIds['img-1'].picked).toBe(false)
|
|
48
|
+
expect(state.lastPicked).toBeUndefined()
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
it('fetchComplete merges assets', () => {
|
|
52
|
+
let state = assetsReducer({...initialState, assetTypes: ['image'] as AssetType[]}, {
|
|
53
|
+
type: '@@INIT'
|
|
54
|
+
} as never)
|
|
55
|
+
state = assetsReducer(state, assetsActions.fetchComplete({assets: [minimalImage]}))
|
|
56
|
+
expect(state.allIds).toContain('img-1')
|
|
57
|
+
expect(state.fetching).toBe(false)
|
|
58
|
+
expect(state.fetchCount).toBe(1)
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
it('orderSet resets page index', () => {
|
|
62
|
+
let state: AssetsReducerState = {
|
|
63
|
+
...initialState,
|
|
64
|
+
assetTypes: ['image'] as AssetType[],
|
|
65
|
+
pageIndex: 3
|
|
66
|
+
}
|
|
67
|
+
state = assetsReducer(
|
|
68
|
+
state,
|
|
69
|
+
assetsActions.orderSet({
|
|
70
|
+
order: {field: 'size', direction: 'asc'}
|
|
71
|
+
})
|
|
72
|
+
)
|
|
73
|
+
expect(state.pageIndex).toBe(0)
|
|
74
|
+
expect(state.order.field).toBe('size')
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
it('listenerDeleteQueueComplete removes assets', () => {
|
|
78
|
+
let state = stateWithOneAsset()
|
|
79
|
+
state = assetsReducer(state, assetsActions.listenerDeleteQueueComplete({assetIds: ['img-1']}))
|
|
80
|
+
expect(state.allIds).toHaveLength(0)
|
|
81
|
+
expect(state.byIds['img-1']).toBeUndefined()
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
it('listenerUpdateQueueComplete updates asset document', () => {
|
|
85
|
+
let state = stateWithOneAsset()
|
|
86
|
+
const updated = {...minimalImage, title: 'New'}
|
|
87
|
+
state = assetsReducer(state, assetsActions.listenerUpdateQueueComplete({assets: [updated]}))
|
|
88
|
+
expect(state.byIds['img-1'].asset.title).toBe('New')
|
|
89
|
+
})
|
|
90
|
+
})
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
// @vitest-environment node
|
|
2
|
+
|
|
3
|
+
import {describe, expect, it, vi, beforeEach, afterEach} from 'vitest'
|
|
4
|
+
import {
|
|
5
|
+
assetsActions,
|
|
6
|
+
assetsListenerCreateQueueEpic,
|
|
7
|
+
assetsListenerDeleteQueueEpic,
|
|
8
|
+
assetsListenerUpdateQueueEpic,
|
|
9
|
+
assetsTagsAddEpic,
|
|
10
|
+
assetsTagsRemoveEpic
|
|
11
|
+
} from './index'
|
|
12
|
+
import {ASSETS_ACTIONS} from './actions'
|
|
13
|
+
import {createEpicTestStore} from '../../__tests__/fixtures/createEpicTestStore'
|
|
14
|
+
import {
|
|
15
|
+
createMockSanityClient,
|
|
16
|
+
mockTransactionCommit
|
|
17
|
+
} from '../../__tests__/fixtures/mockSanityClient'
|
|
18
|
+
import {initialState as assetsInitialState} from './index'
|
|
19
|
+
import type {ImageAsset, Tag} from '../../types'
|
|
20
|
+
|
|
21
|
+
const sampleAsset = {
|
|
22
|
+
_id: 'a1',
|
|
23
|
+
_type: 'sanity.imageAsset',
|
|
24
|
+
_createdAt: '',
|
|
25
|
+
_updatedAt: '',
|
|
26
|
+
_rev: 'r1',
|
|
27
|
+
originalFilename: 'x.png',
|
|
28
|
+
size: 1,
|
|
29
|
+
mimeType: 'image/png',
|
|
30
|
+
url: ''
|
|
31
|
+
} as ImageAsset
|
|
32
|
+
|
|
33
|
+
const sampleTag: Tag = {
|
|
34
|
+
_id: 't1',
|
|
35
|
+
_type: 'media.tag',
|
|
36
|
+
_createdAt: '',
|
|
37
|
+
_updatedAt: '',
|
|
38
|
+
_rev: 'tr',
|
|
39
|
+
name: {_type: 'slug', current: 'tag-a'}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
describe('assetsTagsAddEpic', () => {
|
|
43
|
+
it('runs transaction.commit when adding tag to picked assets', async () => {
|
|
44
|
+
const tx = mockTransactionCommit(undefined)
|
|
45
|
+
const client = createMockSanityClient({
|
|
46
|
+
transaction: vi.fn(() => tx)
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
const assetItem = {
|
|
50
|
+
_type: 'asset' as const,
|
|
51
|
+
asset: sampleAsset,
|
|
52
|
+
picked: true,
|
|
53
|
+
updating: false
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const store = createEpicTestStore(assetsTagsAddEpic, client, {
|
|
57
|
+
assets: {
|
|
58
|
+
...assetsInitialState,
|
|
59
|
+
assetTypes: ['image'],
|
|
60
|
+
allIds: ['a1'],
|
|
61
|
+
byIds: {a1: assetItem}
|
|
62
|
+
},
|
|
63
|
+
tags: {
|
|
64
|
+
allIds: ['t1'],
|
|
65
|
+
byIds: {
|
|
66
|
+
t1: {_type: 'tag', tag: sampleTag, picked: false, updating: false}
|
|
67
|
+
},
|
|
68
|
+
creating: false,
|
|
69
|
+
fetchCount: -1,
|
|
70
|
+
fetching: false,
|
|
71
|
+
panelVisible: true
|
|
72
|
+
}
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
store.dispatch(
|
|
76
|
+
ASSETS_ACTIONS.tagsAddRequest({
|
|
77
|
+
assets: [assetItem],
|
|
78
|
+
tag: sampleTag
|
|
79
|
+
})
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
await vi.waitFor(() => {
|
|
83
|
+
expect(tx.commit).toHaveBeenCalled()
|
|
84
|
+
})
|
|
85
|
+
})
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
describe('assetsTagsRemoveEpic', () => {
|
|
89
|
+
it('runs transaction.commit for tag removal', async () => {
|
|
90
|
+
const tx = mockTransactionCommit(undefined)
|
|
91
|
+
const client = createMockSanityClient({
|
|
92
|
+
transaction: vi.fn(() => tx)
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
const assetItem = {
|
|
96
|
+
_type: 'asset' as const,
|
|
97
|
+
asset: sampleAsset,
|
|
98
|
+
picked: true,
|
|
99
|
+
updating: false
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const store = createEpicTestStore(assetsTagsRemoveEpic, client, {
|
|
103
|
+
assets: {
|
|
104
|
+
...assetsInitialState,
|
|
105
|
+
assetTypes: ['image'],
|
|
106
|
+
allIds: ['a1'],
|
|
107
|
+
byIds: {a1: assetItem}
|
|
108
|
+
},
|
|
109
|
+
tags: {
|
|
110
|
+
allIds: ['t1'],
|
|
111
|
+
byIds: {
|
|
112
|
+
t1: {_type: 'tag', tag: sampleTag, picked: false, updating: false}
|
|
113
|
+
},
|
|
114
|
+
creating: false,
|
|
115
|
+
fetchCount: -1,
|
|
116
|
+
fetching: false,
|
|
117
|
+
panelVisible: true
|
|
118
|
+
}
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
store.dispatch(
|
|
122
|
+
ASSETS_ACTIONS.tagsRemoveRequest({
|
|
123
|
+
assets: [assetItem],
|
|
124
|
+
tag: sampleTag
|
|
125
|
+
})
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
await vi.waitFor(() => {
|
|
129
|
+
expect(tx.commit).toHaveBeenCalled()
|
|
130
|
+
})
|
|
131
|
+
})
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
describe('assets listener queue epics', () => {
|
|
135
|
+
beforeEach(() => {
|
|
136
|
+
vi.useFakeTimers()
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
afterEach(() => {
|
|
140
|
+
vi.useRealTimers()
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
it('assetsListenerCreateQueueEpic batches creates after buffer window', async () => {
|
|
144
|
+
const store = createEpicTestStore(assetsListenerCreateQueueEpic, createMockSanityClient(), {
|
|
145
|
+
assets: {
|
|
146
|
+
...assetsInitialState,
|
|
147
|
+
assetTypes: ['image'],
|
|
148
|
+
allIds: ['a1'],
|
|
149
|
+
byIds: {
|
|
150
|
+
a1: {_type: 'asset', asset: sampleAsset, picked: false, updating: false}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
const updated = {...sampleAsset, title: 'L'}
|
|
156
|
+
store.dispatch(assetsActions.listenerCreateQueue({asset: updated}))
|
|
157
|
+
|
|
158
|
+
await vi.advanceTimersByTimeAsync(2000)
|
|
159
|
+
|
|
160
|
+
await vi.waitFor(() => {
|
|
161
|
+
expect(store.getState().assets.byIds.a1.asset.title).toBe('L')
|
|
162
|
+
})
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
it('assetsListenerDeleteQueueEpic batches deletes', async () => {
|
|
166
|
+
const store = createEpicTestStore(assetsListenerDeleteQueueEpic, createMockSanityClient(), {
|
|
167
|
+
assets: {
|
|
168
|
+
...assetsInitialState,
|
|
169
|
+
assetTypes: ['image'],
|
|
170
|
+
allIds: ['a1'],
|
|
171
|
+
byIds: {
|
|
172
|
+
a1: {_type: 'asset', asset: sampleAsset, picked: false, updating: false}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
store.dispatch(assetsActions.listenerDeleteQueue({assetId: 'a1'}))
|
|
178
|
+
await vi.advanceTimersByTimeAsync(2000)
|
|
179
|
+
|
|
180
|
+
await vi.waitFor(() => {
|
|
181
|
+
expect(store.getState().assets.byIds.a1).toBeUndefined()
|
|
182
|
+
})
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
it('assetsListenerUpdateQueueEpic batches updates', async () => {
|
|
186
|
+
const store = createEpicTestStore(assetsListenerUpdateQueueEpic, createMockSanityClient(), {
|
|
187
|
+
assets: {
|
|
188
|
+
...assetsInitialState,
|
|
189
|
+
assetTypes: ['image'],
|
|
190
|
+
allIds: ['a1'],
|
|
191
|
+
byIds: {
|
|
192
|
+
a1: {_type: 'asset', asset: sampleAsset, picked: false, updating: false}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
})
|
|
196
|
+
|
|
197
|
+
const updated = {...sampleAsset, title: 'Buffered'}
|
|
198
|
+
store.dispatch(assetsActions.listenerUpdateQueue({asset: updated}))
|
|
199
|
+
await vi.advanceTimersByTimeAsync(2000)
|
|
200
|
+
|
|
201
|
+
await vi.waitFor(() => {
|
|
202
|
+
expect(store.getState().assets.byIds.a1.asset.title).toBe('Buffered')
|
|
203
|
+
})
|
|
204
|
+
})
|
|
205
|
+
})
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
// @vitest-environment node
|
|
2
|
+
|
|
3
|
+
import {describe, expect, it, vi} from 'vitest'
|
|
4
|
+
import {dialogClearOnAssetUpdateEpic, dialogTagCreateEpic, dialogTagDeleteEpic} from './index'
|
|
5
|
+
import {assetsActions, initialState as assetsInitialState} from '../assets'
|
|
6
|
+
import {tagsActions} from '../tags'
|
|
7
|
+
import {createEpicTestStore} from '../../__tests__/fixtures/createEpicTestStore'
|
|
8
|
+
import {createMockSanityClient} from '../../__tests__/fixtures/mockSanityClient'
|
|
9
|
+
import type {ImageAsset, Tag} from '../../types'
|
|
10
|
+
|
|
11
|
+
const sampleAsset = {
|
|
12
|
+
_id: 'a1',
|
|
13
|
+
_type: 'sanity.imageAsset',
|
|
14
|
+
_createdAt: '',
|
|
15
|
+
_updatedAt: '',
|
|
16
|
+
_rev: 'r1',
|
|
17
|
+
originalFilename: 'x.png',
|
|
18
|
+
size: 1,
|
|
19
|
+
mimeType: 'image/png',
|
|
20
|
+
url: 'https://example.com/x.png'
|
|
21
|
+
} as ImageAsset
|
|
22
|
+
|
|
23
|
+
const sampleTag: Tag = {
|
|
24
|
+
_id: 't1',
|
|
25
|
+
_type: 'media.tag',
|
|
26
|
+
_createdAt: '',
|
|
27
|
+
_updatedAt: '',
|
|
28
|
+
_rev: 'tr',
|
|
29
|
+
name: {_type: 'slug', current: 'alpha'}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const tagWithId = (id: string): Tag => ({
|
|
33
|
+
...sampleTag,
|
|
34
|
+
_id: id
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
describe('dialogClearOnAssetUpdateEpic', () => {
|
|
38
|
+
it('removes the dialog when assets.updateComplete carries closeDialogId', async () => {
|
|
39
|
+
const store = createEpicTestStore(dialogClearOnAssetUpdateEpic, createMockSanityClient({}), {
|
|
40
|
+
assets: {
|
|
41
|
+
...assetsInitialState,
|
|
42
|
+
assetTypes: ['image'],
|
|
43
|
+
allIds: ['a1'],
|
|
44
|
+
byIds: {
|
|
45
|
+
a1: {_type: 'asset', asset: sampleAsset, picked: false, updating: true}
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
dialog: {
|
|
49
|
+
items: [{id: 'a1', type: 'assetEdit', assetId: 'a1'}]
|
|
50
|
+
}
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
store.dispatch(assetsActions.updateComplete({asset: sampleAsset, closeDialogId: 'a1'}))
|
|
54
|
+
|
|
55
|
+
await vi.waitFor(() => {
|
|
56
|
+
expect(store.getState().dialog.items).toHaveLength(0)
|
|
57
|
+
})
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
it('removes the dialog when tags.updateComplete carries closeDialogId', async () => {
|
|
61
|
+
const store = createEpicTestStore(dialogClearOnAssetUpdateEpic, createMockSanityClient({}), {
|
|
62
|
+
tags: {
|
|
63
|
+
allIds: ['t1'],
|
|
64
|
+
byIds: {
|
|
65
|
+
t1: {_type: 'tag', tag: sampleTag, picked: false, updating: true}
|
|
66
|
+
},
|
|
67
|
+
creating: false,
|
|
68
|
+
fetchCount: -1,
|
|
69
|
+
fetching: false,
|
|
70
|
+
panelVisible: true
|
|
71
|
+
},
|
|
72
|
+
dialog: {
|
|
73
|
+
items: [{id: 't1', type: 'tagEdit', tagId: 't1'}]
|
|
74
|
+
}
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
store.dispatch(tagsActions.updateComplete({tag: sampleTag, closeDialogId: 't1'}))
|
|
78
|
+
|
|
79
|
+
await vi.waitFor(() => {
|
|
80
|
+
expect(store.getState().dialog.items).toHaveLength(0)
|
|
81
|
+
})
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
it('does not emit remove when closeDialogId is absent', async () => {
|
|
85
|
+
const store = createEpicTestStore(dialogClearOnAssetUpdateEpic, createMockSanityClient({}), {
|
|
86
|
+
assets: {
|
|
87
|
+
...assetsInitialState,
|
|
88
|
+
assetTypes: ['image'],
|
|
89
|
+
allIds: ['a1'],
|
|
90
|
+
byIds: {
|
|
91
|
+
a1: {_type: 'asset', asset: sampleAsset, picked: false, updating: true}
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
dialog: {
|
|
95
|
+
items: [{id: 'a1', type: 'assetEdit', assetId: 'a1'}]
|
|
96
|
+
}
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
store.dispatch(assetsActions.updateComplete({asset: sampleAsset}))
|
|
100
|
+
|
|
101
|
+
expect(store.getState().dialog.items).toHaveLength(1)
|
|
102
|
+
})
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
describe('dialogTagCreateEpic', () => {
|
|
106
|
+
it('dispatches inlineTagCreate when createComplete includes assetId', async () => {
|
|
107
|
+
const store = createEpicTestStore(dialogTagCreateEpic, createMockSanityClient({}), {
|
|
108
|
+
dialog: {
|
|
109
|
+
items: [{id: 'a1', type: 'assetEdit', assetId: 'a1'}]
|
|
110
|
+
}
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
store.dispatch(tagsActions.createComplete({assetId: 'a1', tag: sampleTag}))
|
|
114
|
+
|
|
115
|
+
await vi.waitFor(() => {
|
|
116
|
+
const item = store.getState().dialog.items[0]
|
|
117
|
+
expect(item?.type).toBe('assetEdit')
|
|
118
|
+
expect('lastCreatedTag' in item && item.lastCreatedTag).toEqual({
|
|
119
|
+
label: 'alpha',
|
|
120
|
+
value: 't1'
|
|
121
|
+
})
|
|
122
|
+
})
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
it('removes tag create dialog when createComplete has no assetId', async () => {
|
|
126
|
+
const store = createEpicTestStore(dialogTagCreateEpic, createMockSanityClient({}), {
|
|
127
|
+
dialog: {
|
|
128
|
+
items: [{id: 'tagCreate', type: 'tagCreate'}]
|
|
129
|
+
}
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
store.dispatch(tagsActions.createComplete({tag: sampleTag}))
|
|
133
|
+
|
|
134
|
+
await vi.waitFor(() => {
|
|
135
|
+
expect(store.getState().dialog.items).toHaveLength(0)
|
|
136
|
+
})
|
|
137
|
+
})
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
describe('dialogTagDeleteEpic', () => {
|
|
141
|
+
it('dispatches inlineTagRemove with listener tag ids', async () => {
|
|
142
|
+
const store = createEpicTestStore(dialogTagDeleteEpic, createMockSanityClient({}), {
|
|
143
|
+
tags: {
|
|
144
|
+
allIds: ['t9'],
|
|
145
|
+
byIds: {
|
|
146
|
+
t9: {_type: 'tag', tag: tagWithId('t9'), picked: false, updating: false}
|
|
147
|
+
},
|
|
148
|
+
creating: false,
|
|
149
|
+
fetchCount: -1,
|
|
150
|
+
fetching: false,
|
|
151
|
+
panelVisible: true
|
|
152
|
+
},
|
|
153
|
+
dialog: {
|
|
154
|
+
items: [{id: 'a1', type: 'assetEdit', assetId: 'a1'}]
|
|
155
|
+
}
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
store.dispatch(tagsActions.listenerDeleteQueueComplete({tagIds: ['t9']}))
|
|
159
|
+
|
|
160
|
+
await vi.waitFor(() => {
|
|
161
|
+
expect(store.getState().dialog.items[0]).toMatchObject({
|
|
162
|
+
type: 'assetEdit',
|
|
163
|
+
lastRemovedTagIds: ['t9']
|
|
164
|
+
})
|
|
165
|
+
})
|
|
166
|
+
})
|
|
167
|
+
})
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
// @vitest-environment node
|
|
2
|
+
|
|
3
|
+
import {describe, expect, it} from 'vitest'
|
|
4
|
+
import {DIALOG_ACTIONS} from './actions'
|
|
5
|
+
import dialogReducer, {dialogActions} from './index'
|
|
6
|
+
import {assetsActions} from '../assets'
|
|
7
|
+
import {ASSETS_ACTIONS} from '../assets/actions'
|
|
8
|
+
import {tagsActions} from '../tags'
|
|
9
|
+
import type {AssetItem, ImageAsset, Tag} from '../../types'
|
|
10
|
+
import {createTestRootState} from '../../__tests__/fixtures/rootState'
|
|
11
|
+
|
|
12
|
+
const sampleAsset = {
|
|
13
|
+
_id: 'a1',
|
|
14
|
+
_type: 'sanity.imageAsset',
|
|
15
|
+
_createdAt: '',
|
|
16
|
+
_updatedAt: '',
|
|
17
|
+
_rev: 'r1',
|
|
18
|
+
originalFilename: 'x.png',
|
|
19
|
+
size: 1,
|
|
20
|
+
mimeType: 'image/png',
|
|
21
|
+
url: 'https://example.com/x.png'
|
|
22
|
+
} as ImageAsset
|
|
23
|
+
|
|
24
|
+
const sampleTag: Tag = {
|
|
25
|
+
_id: 't1',
|
|
26
|
+
_type: 'media.tag',
|
|
27
|
+
_createdAt: '',
|
|
28
|
+
_updatedAt: '',
|
|
29
|
+
_rev: 'tr',
|
|
30
|
+
name: {_type: 'slug', current: 'alpha'}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const assetItem = (asset: ImageAsset = sampleAsset): AssetItem => ({
|
|
34
|
+
_type: 'asset',
|
|
35
|
+
asset,
|
|
36
|
+
picked: false,
|
|
37
|
+
updating: false
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
function dialogState() {
|
|
41
|
+
return createTestRootState().dialog
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
describe('dialog slice reducers', () => {
|
|
45
|
+
it('clear removes all items', () => {
|
|
46
|
+
const state = dialogReducer(
|
|
47
|
+
{...dialogState(), items: [{id: 'x', type: 'tags'}]},
|
|
48
|
+
dialogActions.clear()
|
|
49
|
+
)
|
|
50
|
+
expect(state.items).toEqual([])
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
it('remove filters out the dialog with the given id', () => {
|
|
54
|
+
const state = dialogReducer(
|
|
55
|
+
{
|
|
56
|
+
...dialogState(),
|
|
57
|
+
items: [
|
|
58
|
+
{id: 'a', type: 'tags'},
|
|
59
|
+
{id: 'b', type: 'searchFacets'}
|
|
60
|
+
]
|
|
61
|
+
},
|
|
62
|
+
dialogActions.remove({id: 'a'})
|
|
63
|
+
)
|
|
64
|
+
expect(state.items).toEqual([{id: 'b', type: 'searchFacets'}])
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
it('showAssetEdit appends an asset edit dialog', () => {
|
|
68
|
+
const state = dialogReducer(dialogState(), dialogActions.showAssetEdit({assetId: 'a1'}))
|
|
69
|
+
expect(state.items).toEqual([{assetId: 'a1', id: 'a1', type: 'assetEdit'}])
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
it('showSearchFacets and showTags append the expected dialogs', () => {
|
|
73
|
+
let state = dialogReducer(dialogState(), dialogActions.showSearchFacets())
|
|
74
|
+
state = dialogReducer(state, dialogActions.showTags())
|
|
75
|
+
expect(state.items).toEqual([
|
|
76
|
+
{id: 'searchFacets', type: 'searchFacets'},
|
|
77
|
+
{id: 'tags', type: 'tags'}
|
|
78
|
+
])
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
it('inlineTagCreate sets lastCreatedTag on matching assetEdit items', () => {
|
|
82
|
+
const state = dialogReducer(
|
|
83
|
+
{
|
|
84
|
+
...dialogState(),
|
|
85
|
+
items: [
|
|
86
|
+
{id: 'a1', type: 'assetEdit', assetId: 'a1'},
|
|
87
|
+
{id: 'a2', type: 'assetEdit', assetId: 'a2'}
|
|
88
|
+
]
|
|
89
|
+
},
|
|
90
|
+
dialogActions.inlineTagCreate({assetId: 'a1', tag: sampleTag})
|
|
91
|
+
)
|
|
92
|
+
const a1 = state.items.find(i => i.type === 'assetEdit' && i.assetId === 'a1')
|
|
93
|
+
const a2 = state.items.find(i => i.type === 'assetEdit' && i.assetId === 'a2')
|
|
94
|
+
expect(a1 && 'lastCreatedTag' in a1 && a1.lastCreatedTag).toEqual({
|
|
95
|
+
label: 'alpha',
|
|
96
|
+
value: 't1'
|
|
97
|
+
})
|
|
98
|
+
expect(a2).toBeDefined()
|
|
99
|
+
expect(Object.prototype.hasOwnProperty.call(a2, 'lastCreatedTag')).toBe(false)
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
it('inlineTagRemove sets lastRemovedTagIds on all assetEdit items', () => {
|
|
103
|
+
const state = dialogReducer(
|
|
104
|
+
{
|
|
105
|
+
...dialogState(),
|
|
106
|
+
items: [
|
|
107
|
+
{id: 'a1', type: 'assetEdit', assetId: 'a1'},
|
|
108
|
+
{id: 'tags', type: 'tags'}
|
|
109
|
+
]
|
|
110
|
+
},
|
|
111
|
+
dialogActions.inlineTagRemove({tagIds: ['x', 'y']})
|
|
112
|
+
)
|
|
113
|
+
const edit = state.items.find(i => i.type === 'assetEdit')
|
|
114
|
+
expect(edit && 'lastRemovedTagIds' in edit && edit.lastRemovedTagIds).toEqual(['x', 'y'])
|
|
115
|
+
const tagsPanel = state.items.find(i => i.type === 'tags')
|
|
116
|
+
expect(tagsPanel && 'lastRemovedTagIds' in tagsPanel).toBeFalsy()
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
it('showConfirmDeleteAssets pushes a confirm dialog wired to assets deleteRequest', () => {
|
|
120
|
+
const item = assetItem()
|
|
121
|
+
const state = dialogReducer(
|
|
122
|
+
dialogState(),
|
|
123
|
+
dialogActions.showConfirmDeleteAssets({assets: [item], closeDialogId: 'a1'})
|
|
124
|
+
)
|
|
125
|
+
const confirm = state.items[0]
|
|
126
|
+
expect(confirm?.type).toBe('confirm')
|
|
127
|
+
expect(confirm && 'title' in confirm && confirm.title).toBe('Permanently delete 1 asset?')
|
|
128
|
+
const cb = confirm && 'confirmCallbackAction' in confirm ? confirm.confirmCallbackAction : null
|
|
129
|
+
expect(cb).toEqual(assetsActions.deleteRequest({assets: [sampleAsset]}))
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
it('showConfirmDeleteTag pushes a confirm dialog wired to tags deleteRequest', () => {
|
|
133
|
+
const state = dialogReducer(
|
|
134
|
+
dialogState(),
|
|
135
|
+
dialogActions.showConfirmDeleteTag({closeDialogId: 't1', tag: sampleTag})
|
|
136
|
+
)
|
|
137
|
+
const confirm = state.items[0]
|
|
138
|
+
expect(confirm?.type).toBe('confirm')
|
|
139
|
+
expect(confirm && 'title' in confirm && confirm.title).toMatch(/permanently delete/i)
|
|
140
|
+
const cb = confirm && 'confirmCallbackAction' in confirm ? confirm.confirmCallbackAction : null
|
|
141
|
+
expect(cb).toEqual(tagsActions.deleteRequest({tag: sampleTag}))
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
it('showConfirmAssetsTagAdd uses plural copy for multiple assets', () => {
|
|
145
|
+
const a2 = {...sampleAsset, _id: 'a2', originalFilename: 'y.png'} as ImageAsset
|
|
146
|
+
const state = dialogReducer(
|
|
147
|
+
dialogState(),
|
|
148
|
+
dialogActions.showConfirmAssetsTagAdd({
|
|
149
|
+
assetsPicked: [assetItem(), assetItem(a2)],
|
|
150
|
+
tag: sampleTag
|
|
151
|
+
})
|
|
152
|
+
)
|
|
153
|
+
const confirm = state.items[0]
|
|
154
|
+
expect(confirm && 'title' in confirm && confirm.title).toContain('2 assets')
|
|
155
|
+
const cb = confirm && 'confirmCallbackAction' in confirm ? confirm.confirmCallbackAction : null
|
|
156
|
+
expect(cb).toEqual(
|
|
157
|
+
ASSETS_ACTIONS.tagsAddRequest({assets: [assetItem(), assetItem(a2)], tag: sampleTag})
|
|
158
|
+
)
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
it('showConfirmAssetsTagRemove pushes removal confirm with tagsRemoveRequest', () => {
|
|
162
|
+
const state = dialogReducer(
|
|
163
|
+
dialogState(),
|
|
164
|
+
dialogActions.showConfirmAssetsTagRemove({
|
|
165
|
+
assetsPicked: [assetItem()],
|
|
166
|
+
tag: sampleTag
|
|
167
|
+
})
|
|
168
|
+
)
|
|
169
|
+
const confirm = state.items[0]
|
|
170
|
+
expect(confirm && 'tone' in confirm && confirm.tone).toBe('critical')
|
|
171
|
+
const cb = confirm && 'confirmCallbackAction' in confirm ? confirm.confirmCallbackAction : null
|
|
172
|
+
expect(cb).toEqual(ASSETS_ACTIONS.tagsRemoveRequest({assets: [assetItem()], tag: sampleTag}))
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
it('DIALOG_ACTIONS.showTagCreate appends tag create dialog', () => {
|
|
176
|
+
const state = dialogReducer(dialogState(), DIALOG_ACTIONS.showTagCreate())
|
|
177
|
+
expect(state.items).toEqual([{id: 'tagCreate', type: 'tagCreate'}])
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
it('DIALOG_ACTIONS.showTagEdit appends tag edit dialog with tag id', () => {
|
|
181
|
+
const state = dialogReducer(dialogState(), DIALOG_ACTIONS.showTagEdit({tagId: 't9'}))
|
|
182
|
+
expect(state.items).toEqual([{id: 't9', tagId: 't9', type: 'tagEdit'}])
|
|
183
|
+
})
|
|
184
|
+
})
|