mantimon-tcg/.claude/frontend-poc/src/components/profile/DisplayNameEditor.spec.ts
Cal Corum 8685e3e16e Archive frontend POC to .claude/frontend-poc/
Preserves the working F3 Phaser demo implementation before resetting
the main frontend/ directory for a fresh start. The POC demonstrates:
- Vue 3 + Phaser 3 integration
- Real card rendering with images
- Vue-Phaser state sync via gameBridge
- Card interactions and damage counters

To restore: copy .claude/frontend-poc/ back to frontend/ and run npm install
2026-01-31 22:00:51 -06:00

388 lines
11 KiB
TypeScript

import { describe, it, expect } from 'vitest'
import { mount } from '@vue/test-utils'
import DisplayNameEditor from './DisplayNameEditor.vue'
describe('DisplayNameEditor', () => {
describe('view mode', () => {
it('displays the current name', () => {
/**
* Test that the display name is shown in view mode.
*
* By default, the component shows the current name
* with an edit button, not an input field.
*/
const wrapper = mount(DisplayNameEditor, {
props: {
modelValue: 'Test User',
isSaving: false,
},
})
expect(wrapper.text()).toContain('Test User')
})
it('shows edit button', () => {
/**
* Test that an edit button is visible in view mode.
*
* Users need a way to enter edit mode to change their name.
*/
const wrapper = mount(DisplayNameEditor, {
props: {
modelValue: 'Test User',
isSaving: false,
},
})
const editButton = wrapper.find('button')
expect(editButton.exists()).toBe(true)
})
it('enters edit mode when edit button is clicked', async () => {
/**
* Test entering edit mode.
*
* Clicking the edit button should switch from viewing
* to editing the display name.
*/
const wrapper = mount(DisplayNameEditor, {
props: {
modelValue: 'Test User',
isSaving: false,
},
})
await wrapper.find('button').trigger('click')
// Should now show an input field
expect(wrapper.find('input').exists()).toBe(true)
})
})
describe('edit mode', () => {
it('shows input with current value', async () => {
/**
* Test that input is pre-filled with current name.
*
* When entering edit mode, the input should contain
* the current display name for easy editing.
*/
const wrapper = mount(DisplayNameEditor, {
props: {
modelValue: 'Test User',
isSaving: false,
},
})
// Enter edit mode
await wrapper.find('button').trigger('click')
const input = wrapper.find('input')
expect((input.element as HTMLInputElement).value).toBe('Test User')
})
it('shows save and cancel buttons', async () => {
/**
* Test that save and cancel buttons are visible.
*
* Users need clear actions to either save changes
* or cancel and revert to the original value.
*/
const wrapper = mount(DisplayNameEditor, {
props: {
modelValue: 'Test User',
isSaving: false,
},
})
await wrapper.find('button').trigger('click')
const buttons = wrapper.findAll('button')
const buttonTexts = buttons.map(b => b.text())
expect(buttonTexts.some(t => t.includes('Save'))).toBe(true)
expect(buttonTexts.some(t => t.includes('Cancel'))).toBe(true)
})
it('shows character count', async () => {
/**
* Test that character count is displayed.
*
* Users should know how many characters they've used
* relative to the maximum allowed.
*/
const wrapper = mount(DisplayNameEditor, {
props: {
modelValue: 'Test User',
isSaving: false,
},
})
await wrapper.find('button').trigger('click')
expect(wrapper.text()).toContain('/32')
})
it('cancels and reverts on cancel button click', async () => {
/**
* Test cancel functionality.
*
* Clicking cancel should exit edit mode and discard
* any changes made to the input.
*/
const wrapper = mount(DisplayNameEditor, {
props: {
modelValue: 'Test User',
isSaving: false,
},
})
// Enter edit mode
await wrapper.find('button').trigger('click')
// Change the value
await wrapper.find('input').setValue('New Name')
// Click cancel
const cancelButton = wrapper.findAll('button').find(b => b.text().includes('Cancel'))
await cancelButton!.trigger('click')
// Should be back in view mode with original name
expect(wrapper.find('input').exists()).toBe(false)
expect(wrapper.text()).toContain('Test User')
})
it('cancels on Escape key', async () => {
/**
* Test keyboard shortcut for cancel.
*
* Pressing Escape should cancel editing as a
* common keyboard shortcut.
*/
const wrapper = mount(DisplayNameEditor, {
props: {
modelValue: 'Test User',
isSaving: false,
},
})
await wrapper.find('button').trigger('click')
await wrapper.find('input').trigger('keydown', { key: 'Escape' })
expect(wrapper.find('input').exists()).toBe(false)
})
})
describe('validation', () => {
it('shows error for empty name', async () => {
/**
* Test validation of empty display name.
*
* Empty or whitespace-only names should be rejected
* with a clear error message.
*/
const wrapper = mount(DisplayNameEditor, {
props: {
modelValue: 'Test User',
isSaving: false,
},
})
await wrapper.find('button').trigger('click')
await wrapper.find('input').setValue('')
// Click save
const saveButton = wrapper.findAll('button').find(b => b.text().includes('Save'))
await saveButton!.trigger('click')
expect(wrapper.text()).toContain('cannot be empty')
})
it('shows error for name shorter than 2 characters', async () => {
/**
* Test minimum length validation.
*
* Very short names are likely typos and should be rejected.
*/
const wrapper = mount(DisplayNameEditor, {
props: {
modelValue: 'Test User',
isSaving: false,
},
})
await wrapper.find('button').trigger('click')
await wrapper.find('input').setValue('A')
const saveButton = wrapper.findAll('button').find(b => b.text().includes('Save'))
await saveButton!.trigger('click')
expect(wrapper.text()).toContain('at least 2 characters')
})
it('shows error for name longer than 32 characters', async () => {
/**
* Test maximum length validation.
*
* Names that are too long should be rejected to prevent
* UI issues and database constraints.
*/
const wrapper = mount(DisplayNameEditor, {
props: {
modelValue: 'Test User',
isSaving: false,
},
})
await wrapper.find('button').trigger('click')
await wrapper.find('input').setValue('A'.repeat(33))
const saveButton = wrapper.findAll('button').find(b => b.text().includes('Save'))
await saveButton!.trigger('click')
expect(wrapper.text()).toContain('cannot exceed 32')
})
it('does not emit save with validation errors', async () => {
/**
* Test that invalid input does not trigger save.
*
* The component should validate locally before emitting
* the save event to avoid unnecessary API calls.
*/
const wrapper = mount(DisplayNameEditor, {
props: {
modelValue: 'Test User',
isSaving: false,
},
})
await wrapper.find('button').trigger('click')
await wrapper.find('input').setValue('')
const saveButton = wrapper.findAll('button').find(b => b.text().includes('Save'))
await saveButton!.trigger('click')
expect(wrapper.emitted('save')).toBeFalsy()
})
})
describe('saving', () => {
it('emits save event with trimmed value', async () => {
/**
* Test save event emission.
*
* When the user saves, the component should emit the
* trimmed display name for the parent to handle.
*/
const wrapper = mount(DisplayNameEditor, {
props: {
modelValue: 'Test User',
isSaving: false,
},
})
await wrapper.find('button').trigger('click')
await wrapper.find('input').setValue(' New Name ')
const saveButton = wrapper.findAll('button').find(b => b.text().includes('Save'))
await saveButton!.trigger('click')
expect(wrapper.emitted('save')).toBeTruthy()
expect(wrapper.emitted('save')![0]).toEqual(['New Name'])
})
it('saves on Enter key', async () => {
/**
* Test keyboard shortcut for save.
*
* Pressing Enter should save the changes as a
* common keyboard shortcut.
*/
const wrapper = mount(DisplayNameEditor, {
props: {
modelValue: 'Test User',
isSaving: false,
},
})
await wrapper.find('button').trigger('click')
await wrapper.find('input').setValue('New Name')
await wrapper.find('input').trigger('keydown', { key: 'Enter' })
expect(wrapper.emitted('save')).toBeTruthy()
})
it('disables buttons while saving', async () => {
/**
* Test button states during save.
*
* While saving, buttons should be disabled to prevent
* duplicate submissions.
*/
const wrapper = mount(DisplayNameEditor, {
props: {
modelValue: 'Test User',
isSaving: true,
},
})
await wrapper.find('button').trigger('click')
const buttons = wrapper.findAll('button')
buttons.forEach(button => {
if (button.text().includes('Save') || button.text().includes('Cancel')) {
expect(button.attributes('disabled')).toBeDefined()
}
})
})
it('shows "Saving..." text while saving', async () => {
/**
* Test loading state display.
*
* Users should see feedback that their save is in progress.
*/
const wrapper = mount(DisplayNameEditor, {
props: {
modelValue: 'Test User',
isSaving: true,
},
})
await wrapper.find('button').trigger('click')
expect(wrapper.text()).toContain('Saving')
})
it('exits edit mode when modelValue matches saved value', async () => {
/**
* Test automatic exit from edit mode on success.
*
* When the parent updates the modelValue to match the
* saved value, edit mode should close automatically.
*/
const wrapper = mount(DisplayNameEditor, {
props: {
modelValue: 'Test User',
isSaving: false,
},
})
// Enter edit mode and save
await wrapper.find('button').trigger('click')
await wrapper.find('input').setValue('New Name')
const saveButton = wrapper.findAll('button').find(b => b.text().includes('Save'))
await saveButton!.trigger('click')
// Simulate parent updating the value
await wrapper.setProps({ modelValue: 'New Name' })
// Should exit edit mode
expect(wrapper.find('input').exists()).toBe(false)
})
})
})