// 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:` - 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({}) }) })