Remediate the 2026-09-03 feature-test findings
This commit is contained in:
81
frontend/src/composables/employeeResolve.spec.js
Normal file
81
frontend/src/composables/employeeResolve.spec.js
Normal file
@@ -0,0 +1,81 @@
|
||||
// Typing a name in full must reach the same employee as picking it off the list.
|
||||
//
|
||||
// Both pickers turned a typed name straight into `NAME:<text>` - a free-text
|
||||
// label linked to nothing. On screen the chip is identical to a real
|
||||
// selection, so the difference only shows up later: no photo, no directory
|
||||
// record, and a recognition that cannot be found by the person it is about.
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
||||
|
||||
vi.mock('../api', () => ({
|
||||
employeesApi: { search: vi.fn() }
|
||||
}))
|
||||
|
||||
import { employeesApi } from '../api'
|
||||
import { resolveEmployeeName } from './employeeResolve'
|
||||
|
||||
const DANA = { SSO: 270015376, First_Name: 'Dana', Last_Name: 'Reyes' }
|
||||
const DANA_HYPHEN = { SSO: 270099999, First_Name: 'Dana', Last_Name: 'Reyes-Smith' }
|
||||
const SAM = { SSO: 270000001, First_Name: 'Sam', Last_Name: 'Reyes' }
|
||||
|
||||
function found(rows) {
|
||||
employeesApi.search.mockResolvedValue({ data: { data: rows } })
|
||||
}
|
||||
|
||||
describe('resolveEmployeeName', () => {
|
||||
beforeEach(() => employeesApi.search.mockReset())
|
||||
|
||||
it('resolves a full name to the employee', async () => {
|
||||
found([DANA])
|
||||
const { match } = await resolveEmployeeName('Dana Reyes')
|
||||
expect(match.SSO).toBe(270015376)
|
||||
})
|
||||
|
||||
it('prefers an exact full-name match over a longer partial one', async () => {
|
||||
// Searching "Dana Reyes" also returns "Dana Reyes-Smith"; the person who
|
||||
// typed the shorter name meant the shorter name.
|
||||
found([DANA, DANA_HYPHEN])
|
||||
const { match, matches } = await resolveEmployeeName('Dana Reyes')
|
||||
expect(matches).toBeUndefined()
|
||||
expect(match.SSO).toBe(270015376)
|
||||
})
|
||||
|
||||
it('refuses to guess between genuinely ambiguous matches', async () => {
|
||||
found([DANA, SAM])
|
||||
const { match, matches } = await resolveEmployeeName('Reyes')
|
||||
expect(match).toBeUndefined()
|
||||
expect(matches).toHaveLength(2)
|
||||
})
|
||||
|
||||
it('reports nothing found, so the caller may fall back to a label', async () => {
|
||||
found([])
|
||||
const result = await resolveEmployeeName('Somebody External')
|
||||
expect(result.match).toBeUndefined()
|
||||
expect(result.matches).toBeUndefined()
|
||||
expect(result.failed).toBeUndefined()
|
||||
})
|
||||
|
||||
it('says the lookup FAILED rather than reporting nobody', async () => {
|
||||
// The difference matters: "not in the directory" may become a custom
|
||||
// label, "could not ask" must not.
|
||||
// A THENABLE that calls its reject callback, rather than a mock returning
|
||||
// a rejected promise. The await sees the same failure, but no rejected
|
||||
// promise object exists for the runner to collect as an unhandled error -
|
||||
// which it does even when the code under test catches it, failing the very
|
||||
// test that proves the catch works.
|
||||
employeesApi.search.mockImplementationOnce(() => {
|
||||
const failure = new Error('network down')
|
||||
return { then: (_ok, fail) => fail(failure) }
|
||||
})
|
||||
// resolves.toEqual, so the runner sees the rejection consumed by an
|
||||
// assertion rather than as an unhandled error of its own.
|
||||
await expect(resolveEmployeeName('Dana Reyes')).resolves.toEqual({
|
||||
failed: true
|
||||
})
|
||||
})
|
||||
|
||||
it('does not call the directory for an empty box', async () => {
|
||||
const result = await resolveEmployeeName(' ')
|
||||
expect(employeesApi.search).not.toHaveBeenCalled()
|
||||
expect(result).toEqual({})
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user