feat(SPA): media library indications for custom medial field type usage
This commit is contained in:
1 parent
3e7fc9a4a5
commit
0fc3511e2b
28 files changed
+2060
-131
No files matched your search
@@ -59,15 +59,15 @@ describe('formatType', () => {
|
||||
|
||||
describe('usedInLabel and accessibleName', () => {
|
||||
it('pluralises the article count', () => {
|
||||
expect(usedInLabel(1)).toBe('used in 1 article')
|
||||
expect(usedInLabel(2)).toBe('used in 2 articles')
|
||||
expect(usedInLabel(1)).toBe('used in 1 place')
|
||||
expect(usedInLabel(2)).toBe('used in 2 places')
|
||||
expect(usedInLabel(0)).toBe('unused')
|
||||
})
|
||||
|
||||
it('builds the card name from filename, size and usage', () => {
|
||||
expect(accessibleName(base)).toBe('north-wall.jpg, 3.9 MB, used in 2 articles')
|
||||
expect(accessibleName(base)).toBe('north-wall.jpg, 3.9 MB, used in 2 places')
|
||||
expect(accessibleName({ ...base, used_in: 1 })).toBe(
|
||||
'north-wall.jpg, 3.9 MB, used in 1 article',
|
||||
'north-wall.jpg, 3.9 MB, used in 1 place',
|
||||
)
|
||||
expect(accessibleName({ ...base, used_in: 0 })).toBe('north-wall.jpg, 3.9 MB, unused')
|
||||
})
|
||||
|
||||
@@ -0,0 +1,200 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import type { MediaUsageItem } from '@/composables/useMedia'
|
||||
import { deleteCopy, tooltipLines, pluralEntries, splitUsage, usageEntryTitle } from '../mediaUsage'
|
||||
import { pluralPlaces } from '../mediaFormat'
|
||||
|
||||
function article(id: number, published = true): MediaUsageItem {
|
||||
return { type: 'article', id, title: `Article ${id}`, published, fields: [] }
|
||||
}
|
||||
|
||||
function entry(id: number, firstField?: { type: string; value: unknown }): MediaUsageItem {
|
||||
return {
|
||||
type: 'entry',
|
||||
id,
|
||||
fields: ['thumbnail'],
|
||||
content_type_key: 'events',
|
||||
content_type_name: 'Events',
|
||||
first_field: firstField ? { key: 'title', ...firstField } : undefined,
|
||||
}
|
||||
}
|
||||
|
||||
function entries(count: number) {
|
||||
return Array.from({ length: count }, (_, index) =>
|
||||
entry(index + 1, { type: 'text', value: `Entry title ${index + 1}` }),
|
||||
)
|
||||
}
|
||||
|
||||
describe('splitUsage', () => {
|
||||
it('counts articles and entries and ignores authors and sites', () => {
|
||||
const split = splitUsage([
|
||||
article(1),
|
||||
article(2, false),
|
||||
{ type: 'author', id: 3, fields: [] },
|
||||
{ type: 'site', id: 4, fields: [] },
|
||||
...entries(2),
|
||||
])
|
||||
|
||||
expect(split.articles.map((row) => row.id)).toEqual([1, 2])
|
||||
expect(split.entryCount).toBe(2)
|
||||
expect(split.entries).toHaveLength(2)
|
||||
expect(split.more).toBe(0)
|
||||
})
|
||||
|
||||
it.each([
|
||||
[1, 1, 0],
|
||||
[3, 3, 0],
|
||||
[4, 3, 1],
|
||||
[5, 3, 2],
|
||||
[120, 3, 117],
|
||||
])('with %i entries keeps %i rows and %i more', (count, rows, more) => {
|
||||
const split = splitUsage(entries(count))
|
||||
|
||||
expect(split.entryCount).toBe(count)
|
||||
expect(split.entries).toHaveLength(rows)
|
||||
expect(split.more).toBe(more)
|
||||
})
|
||||
|
||||
it('keeps the order of the API', () => {
|
||||
const split = splitUsage([entry(9), entry(2), entry(5), entry(1)])
|
||||
|
||||
expect(split.entries.map((row) => row.id)).toEqual([9, 2, 5])
|
||||
})
|
||||
|
||||
it('gives zeros for an empty usage', () => {
|
||||
expect(splitUsage([])).toMatchObject({ articles: [], entryCount: 0, more: 0 })
|
||||
})
|
||||
|
||||
it('reads a row without a type as an article', () => {
|
||||
expect(splitUsage([{ id: 7, title: 'Old', published: true }]).articles).toHaveLength(1)
|
||||
})
|
||||
|
||||
it('carries the content type name, key and the derived title of an entry', () => {
|
||||
const [row] = splitUsage([
|
||||
entry(42, { type: 'text', value: 'Lime plaster workshop' }),
|
||||
]).entries
|
||||
|
||||
expect(row).toEqual({
|
||||
id: 42,
|
||||
key: 'events',
|
||||
typeName: 'Events',
|
||||
title: 'Lime plaster workshop',
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('usageEntryTitle', () => {
|
||||
it.each([
|
||||
['text', 'Lime plaster workshop', 'Lime plaster workshop'],
|
||||
['text', '', 'Entry 42'],
|
||||
['text', null, 'Entry 42'],
|
||||
['richtext', '## Heritage walk\n\nMeet at 9', 'Heritage walk'],
|
||||
['richtext', '', 'Entry 42'],
|
||||
['number', 1500, '1,500'],
|
||||
['number', 0, '0'],
|
||||
['boolean', true, 'Yes'],
|
||||
['boolean', false, 'No'],
|
||||
['select', 'Workshop', 'Workshop'],
|
||||
['media', 7, 'Entry 42'],
|
||||
['media', null, 'Entry 42'],
|
||||
['mystery', 'Odd value', 'Odd value'],
|
||||
])('%s %j reads %s', (type, value, title) => {
|
||||
expect(usageEntryTitle(entry(42, { type, value }))).toBe(title)
|
||||
})
|
||||
|
||||
it('formats a date as the entry list does', () => {
|
||||
expect(usageEntryTitle(entry(42, { type: 'date', value: '2026-11-21' }))).toBe(
|
||||
'21 Nov 2026',
|
||||
)
|
||||
})
|
||||
|
||||
it('falls back to the id when there is no first field', () => {
|
||||
expect(usageEntryTitle(entry(42))).toBe('Entry 42')
|
||||
})
|
||||
})
|
||||
|
||||
describe('wording', () => {
|
||||
it('pluralises entries and places', () => {
|
||||
expect(pluralEntries(1)).toBe('1 entry')
|
||||
expect(pluralEntries(5)).toBe('5 entries')
|
||||
expect(pluralPlaces(1)).toBe('1 place')
|
||||
expect(pluralPlaces(7)).toBe('7 places')
|
||||
})
|
||||
})
|
||||
|
||||
describe('tooltipLines', () => {
|
||||
it.each([
|
||||
[[...entries(3)], ['Used in entries: 3']],
|
||||
[
|
||||
[article(1), article(2), ...entries(5)],
|
||||
['Used in articles: 2', 'Used in entries: 5'],
|
||||
],
|
||||
[[article(1), article(2)], ['Used in articles: 2']],
|
||||
[[{ type: 'author', id: 1, fields: [] }], []],
|
||||
])('reads %#', (items, lines) => {
|
||||
expect(tooltipLines(items as MediaUsageItem[])).toEqual(lines)
|
||||
})
|
||||
})
|
||||
|
||||
describe('deleteCopy', () => {
|
||||
const NOTICE = 'This image is no longer in the library'
|
||||
|
||||
it.each([
|
||||
[
|
||||
[...Array(2).keys()].map((id) => article(id + 1)).concat(entries(5)),
|
||||
"Delete an image that's in use?",
|
||||
`"lime-plaster.jpg" is used in 5 entries and 2 articles. Those entry fields will show "${NOTICE}", and the articles will show a broken image. This can't be undone.`,
|
||||
],
|
||||
[
|
||||
[article(1), entry(1)],
|
||||
"Delete an image that's in use?",
|
||||
`"lime-plaster.jpg" is used in 1 entry and 1 article. Those entry fields will show "${NOTICE}", and the articles will show a broken image. This can't be undone.`,
|
||||
],
|
||||
[
|
||||
[article(1), article(2)],
|
||||
"Delete an image that's in use?",
|
||||
`"lime-plaster.jpg" is used in 2 articles. If you delete it, they will show a broken image. This can't be undone.`,
|
||||
],
|
||||
[
|
||||
entries(3),
|
||||
"Delete an image that's in use?",
|
||||
`"lime-plaster.jpg" is used in 3 entries. Those entry fields will show "${NOTICE}". This can't be undone.`,
|
||||
],
|
||||
])('case %#', (items, title, description) => {
|
||||
const copy = deleteCopy('lime-plaster.jpg', items.length, {
|
||||
status: 'ready',
|
||||
items: items as MediaUsageItem[],
|
||||
})
|
||||
|
||||
expect(copy.title).toBe(title)
|
||||
expect(copy.description).toBe(description)
|
||||
expect(copy.split).not.toBeNull()
|
||||
})
|
||||
|
||||
it('is the unused dialog at used_in 0', () => {
|
||||
const copy = deleteCopy('lime-plaster.jpg', 0, null)
|
||||
|
||||
expect(copy.title).toBe('Delete this image?')
|
||||
expect(copy.description).toBe(
|
||||
`"lime-plaster.jpg" will be deleted from your library. No article uses it. This can't be undone.`,
|
||||
)
|
||||
expect(copy.split).toBeNull()
|
||||
})
|
||||
|
||||
it.each([
|
||||
['loading', { status: 'loading' } as const],
|
||||
['failed', { status: 'failed', failure: 'failed' } as const],
|
||||
['absent', null],
|
||||
[
|
||||
'authors only',
|
||||
{ status: 'ready', items: [{ type: 'author', id: 1, fields: [] }] } as const,
|
||||
],
|
||||
])('uses the generic copy when the usage is %s', (_name, state) => {
|
||||
const copy = deleteCopy('lime-plaster.jpg', 2, state as never)
|
||||
|
||||
expect(copy.title).toBe("Delete an image that's in use?")
|
||||
expect(copy.description).toBe(
|
||||
`"lime-plaster.jpg" is in use. Articles will show a broken image and entry fields will show "${NOTICE}". This can't be undone.`,
|
||||
)
|
||||
expect(copy.split).toBeNull()
|
||||
})
|
||||
})
|
||||
Reference in new issue
Block a user