diff --git a/src/webparts/requestPortal/shared/ui/PeopleField/pickerContext.test.ts b/src/webparts/requestPortal/shared/ui/PeopleField/pickerContext.test.ts new file mode 100644 index 0000000..9d7be02 --- /dev/null +++ b/src/webparts/requestPortal/shared/ui/PeopleField/pickerContext.test.ts @@ -0,0 +1,27 @@ +import { trimPickerQuery } from './pickerContext'; + +const body = (q: unknown): string => JSON.stringify({ queryParams: { QueryString: q, PrincipalType: 1 } }); +const query = (b: unknown): unknown => (JSON.parse(b as string) as { queryParams: { QueryString: unknown } }).queryParams.QueryString; + +describe('trimPickerQuery', () => { + it('убирает пробел в конце и в начале', () => { + expect(query(trimPickerQuery(body('Иван ')))).toBe('Иван'); + expect(query(trimPickerQuery(body(' Иван Петров ')))).toBe('Иван Петров'); + }); + + it('схлопывает двойные пробелы внутри', () => { + expect(query(trimPickerQuery(body('Иван Петров')))).toBe('Иван Петров'); + }); + + it('не трогает остальные поля запроса', () => { + const parsed = JSON.parse(trimPickerQuery(body('a ')) as string) as { queryParams: { PrincipalType: number } }; + expect(parsed.queryParams.PrincipalType).toBe(1); + }); + + it('чужие тела возвращает как есть', () => { + expect(trimPickerQuery('не json QueryString')).toBe('не json QueryString'); + expect(trimPickerQuery(undefined)).toBeUndefined(); + const other = JSON.stringify({ a: 1 }); + expect(trimPickerQuery(other)).toBe(other); + }); +}); diff --git a/src/webparts/requestPortal/shared/ui/PeopleField/pickerContext.ts b/src/webparts/requestPortal/shared/ui/PeopleField/pickerContext.ts index 021a13c..31aceed 100644 --- a/src/webparts/requestPortal/shared/ui/PeopleField/pickerContext.ts +++ b/src/webparts/requestPortal/shared/ui/PeopleField/pickerContext.ts @@ -18,6 +18,43 @@ export function pickedLogin(user: IPickedUser): string { return user.secondaryText || user.loginName || user.id || ''; } +/** + * Убирает пробелы по краям (и схлопывает внутренние) у QueryString в теле запроса + * поиска сотрудника. PeoplePicker из @pnp/spfx-controls-react шлёт введённый + * текст как есть, а SharePoint с «Иван » (пробел в конце, обычное дело при вводе + * «имя фамилия») ничего не находит. Тело не JSON или без QueryString — возвращается как было. + */ +export function trimPickerQuery(body: unknown): unknown { + if (typeof body !== 'string' || body.indexOf('QueryString') < 0) return body; + try { + const parsed = JSON.parse(body) as { queryParams?: { QueryString?: unknown } }; + const query = parsed.queryParams?.QueryString; + if (typeof query !== 'string') return body; + parsed.queryParams!.QueryString = query.trim().replace(/\s+/g, ' '); + return JSON.stringify(parsed); + } catch { + return body; + } +} + +/** + * spHttpClient с подчисткой запроса поиска (см. trimPickerQuery): прокси, чтобы + * остальные методы клиента работали как прежде, подменяется только post. + */ +function withTrimmedSearch(client: WebPartContext['spHttpClient']): WebPartContext['spHttpClient'] { + return new Proxy(client, { + get(target, prop, receiver) { + if (prop !== 'post') return Reflect.get(target, prop, receiver); + return (url: string, config: unknown, options?: { body?: unknown }) => + (target.post as (u: string, c: unknown, o?: unknown) => unknown)( + url, + config, + options ? { ...options, body: trimPickerQuery(options.body) } : options + ); + } + }); +} + /** * Контекст для PeoplePicker из WebPartContext. * as unknown: @pnp/spfx-controls-react тянет свою копию sp-http-msgraph, @@ -27,6 +64,6 @@ export function buildPickerContext(context: WebPartContext): IPeoplePickerContex return { absoluteUrl: context.pageContext.web.absoluteUrl, msGraphClientFactory: context.msGraphClientFactory, - spHttpClient: context.spHttpClient + spHttpClient: withTrimmedSearch(context.spHttpClient) } as unknown as IPeoplePickerContext; }