fix(portal): review display, hover overlay, and drag ghost for complex field types
- Extract formatFieldValue helper for shared use between review step
and confirmation page — one source of truth for TAG_PICKER,
AVAILABILITY_PICKER, and SECTION_PRIORITY display, so the raw-ID
and [object Object] leaks from two parallel stringifiers can't
regress on either side.
- TAG_PICKER: lookup via field.available_tags (server-inlined).
- AVAILABILITY_PICKER: lookup via usePublicFormTimeSlots, strip
seconds. "Laden…" while the cache warms.
- SECTION_PRIORITY: defensive shape-guard prevents [object Object]
leaks, sorted priority-prefixed rendering ("1. Bar, 2. Hospitality").
- Subtle primary-tinted hover (4% primary, primary border) replacing
the near-black Vuetify default overlay on unranked section cards.
- Explicit ghost-class / drag-class / chosen-class on vuedraggable
with solid drag-clone + elevation shadow and a 30%-opacity silhouette
at the origin, so mid-drag text no longer overlaps.
- 17 new formatFieldValue unit assertions + 2 new FieldSectionPriority
assertions locking in the draggable classes and the disabled-card
toggle at max.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -30,8 +30,8 @@ vi.mock('vuetify', () => ({
|
||||
vi.mock('vuedraggable', () => ({
|
||||
default: {
|
||||
name: 'draggable',
|
||||
props: ['modelValue'],
|
||||
template: '<div class="draggable-stub"><template v-for="(el, i) in modelValue" :key="i"><slot name="item" :element="el" :index="i"/></template></div>',
|
||||
props: ['modelValue', 'ghostClass', 'dragClass', 'chosenClass', 'itemKey', 'handle', 'animation'],
|
||||
template: '<div class="draggable-stub" :data-ghost-class="ghostClass" :data-drag-class="dragClass" :data-chosen-class="chosenClass"><template v-for="(el, i) in modelValue" :key="i"><slot name="item" :element="el" :index="i"/></template></div>',
|
||||
},
|
||||
}))
|
||||
|
||||
@@ -234,4 +234,32 @@ describe('FieldSectionPriority', () => {
|
||||
|
||||
expect(w.text()).toContain('1 / 5')
|
||||
})
|
||||
|
||||
it('wires ghost-class / drag-class / chosen-class through to <draggable>', () => {
|
||||
state.data.value = [section({ id: 'a' })]
|
||||
const w = mountPicker({ field: field(), modelValue: [] })
|
||||
|
||||
const d = w.find('.draggable-stub')
|
||||
expect(d.attributes('data-ghost-class')).toBe('section-priority-ghost')
|
||||
expect(d.attributes('data-drag-class')).toBe('section-priority-drag')
|
||||
expect(d.attributes('data-chosen-class')).toBe('section-priority-chosen')
|
||||
})
|
||||
|
||||
it('toggles the disabled class on unranked cards when max is reached', () => {
|
||||
state.data.value = [section({ id: 'a' }), section({ id: 'b' })]
|
||||
|
||||
// Not at cap — unranked cards must not carry the disabled marker.
|
||||
const notFull = mountPicker({
|
||||
field: field({ validation_rules: { max_priorities: 3 } }),
|
||||
modelValue: [{ section_id: 'a', priority: 1 }],
|
||||
})
|
||||
expect(notFull.html()).not.toContain('section-priority-unranked-disabled')
|
||||
|
||||
// Hit the cap — remaining unranked cards switch to the disabled class.
|
||||
const full = mountPicker({
|
||||
field: field({ validation_rules: { max_priorities: 1 } }),
|
||||
modelValue: [{ section_id: 'a', priority: 1 }],
|
||||
})
|
||||
expect(full.html()).toContain('section-priority-unranked-disabled')
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user