fix(admin): name records by translatable (mltext) fields

The record heading, breadcrumb crumb and document title took the first
plain text field only, so records named by an mltext field fell back to
the generic update message. A shared recordDisplayName helper now takes
the first text or ML field and reads its default locale, then the first
non-empty one.
This commit is contained in:
Jakub Zych
2026-10-07 19:05:47 +02:00
parent 7554241e9d
commit a714bb4629
6 changed files with 105 additions and 13 deletions

View File

@@ -611,6 +611,14 @@ describe('password and preset on the form (UI-SPEC S7, D-19, D-27 G7)', () => {
expect(editor(wrapper, 'slug').element.value).toBe('ada-lovelace')
})
it('titles a record by the default locale of an mltext first field, and publishes it as the crumb', async () => {
const record = clone(rosterRecordFixture)
record.data.name = { en: 'Ada Lovelace', pl: 'Ada Lovelace PL' }
const { wrapper } = await mountApp('/acme/roster/people/1', people({ [`GET ${PEOPLE}/schema/form`]: mlSchema('mltext', 'text'), [`GET ${PERSON}`]: { body: record } }))
expect(wrapper.find('h1').text()).toBe('Ada Lovelace')
expect(recordCrumb.value).toBe('Ada Lovelace')
})
it('drives a text slug from the active locale of an mltext name, until edited', async () => {
const { wrapper } = await mountApp('/acme/roster/people/create', people({ [`GET ${PEOPLE}/schema/form`]: mlSchema('mltext', 'text') }))
await editor(wrapper, 'name').setValue('Hello World')

View File

@@ -4,6 +4,7 @@
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
import { enableAutoUnmount, flushPromises, type VueWrapper } from '@vue/test-utils'
import { setBundle } from '../../src/app/i18n'
import { recordCrumb } from '../../src/state/useBreadcrumbs'
import { toasts } from '../../src/state/useToasts'
import PreviewView from '../../src/views/PreviewView.vue'
import { clone, langFixture, rosterFormSchemaFixture, rosterRecordFixture } from '../fixtures/typed'
@@ -121,6 +122,23 @@ describe('preview view (UI-SPEC S3, D-11)', () => {
expect(editor.findAll('[role="radiogroup"]').every((group) => group.attributes('aria-disabled') === 'true')).toBe(true)
})
it('heads the record by the default locale of an mltext first field, and publishes it as the crumb', async () => {
const { wrapper } = await mountApp(
PREVIEW,
routes({
[`GET ${LIST}/schema/form`]: schemaWith((schema) => {
schema.meta.enabledLocales = ['en', 'pl']
schema.fields.find((field) => field.name === 'name')!.type = 'mltext'
}),
[`GET ${RECORD}`]: recordWith((record) => {
record.data.name = { en: 'Ada Lovelace', pl: 'Ada Lovelace PL' }
}),
}),
)
expect(wrapper.find('h1').text()).toBe('Ada Lovelace')
expect(recordCrumb.value).toBe('Ada Lovelace')
})
it('shows the muted dash for an empty value and the label of a relation, never its id', async () => {
const { wrapper } = await mountApp(PREVIEW, routes({ [`GET ${RECORD}`]: recordWith((record) => (record.data.joined_ip = null)) }))
expect(wrapper.find('[data-preview-field="joined_ip"] [data-empty]').text()).toBe('—')

View File

@@ -11,6 +11,7 @@ import {
panelDomId,
presetUpdates,
presetValue,
recordDisplayName,
snapshot,
tabDomId,
tabOf,
@@ -271,3 +272,33 @@ describe('ML create seed and adopt merge', () => {
expect(mergeMLValue({ en: 'Hello', pl: 'Witaj' }, ['en', 'pl'])).toEqual({ en: 'Hello', pl: 'Witaj' })
})
})
describe('recordDisplayName', () => {
const field = (name: string, type: string): FormField => ({ ...formField('name'), name, type })
const text = field('name', 'text')
const title = field('title', 'mltext')
const locales = ['en', 'pl']
it('reads a plain text value, trimmed', () => {
expect(recordDisplayName([text], { name: ' Anna ' }, locales)).toBe('Anna')
expect(recordDisplayName([text], { name: 42 }, locales)).toBe('42')
})
it('reads the default locale of an ML value', () => {
expect(recordDisplayName([title], { title: { en: 'Hello', pl: 'Cześć' } }, locales)).toBe('Hello')
})
it('falls back to the first non-empty locale when the default is empty', () => {
expect(recordDisplayName([title], { title: { en: ' ', pl: 'Cześć' } }, locales)).toBe('Cześć')
})
it('is empty when every locale is empty', () => {
expect(recordDisplayName([title], { title: { en: '', pl: '' } }, locales)).toBe('')
expect(recordDisplayName([title], {}, locales)).toBe('')
})
it('skips fields that are not text-like, in favour of a later text field', () => {
const fields = [field('notify', 'checkbox'), field('tags', 'relation'), title, text]
expect(recordDisplayName(fields, { notify: true, tags: [1], title: { en: 'Post', pl: '' }, name: 'Anna' }, locales)).toBe('Post')
})
})