import { IMyRequest } from './models/IMyRequest'; import { IPage, pageKey, pageSignature, readPage, writePage } from './pageCache'; function request(over: Partial = {}): IMyRequest { return { id: 1, code: '1-2608-1', title: 'Заявка №1', status: 'Черновик', authorEmail: 'ivanov@tnsu.kz', authorName: 'Иванов Иван', created: '2026-08-07T10:00:00Z', requestTypeId: 1, requestTypeName: 'Приём на работу', section: 'Кадры', listId: '/sites/requests/Lists/List1', siteUrl: 'https://tnsukz.sharepoint.com/sites/requests', formKey: 'hire', kind: 'own', ...over, }; } function page(over: Partial = {}): IPage { return { rows: [request()], totalRows: 1, ...over }; } describe('pageKey', () => { it('склеивает раздел и части ключа через разделитель', () => { expect(pageKey('own', [1, 'Черновик'])).toBe('own|1|Черновик'); }); it('раздел всегда первый — ключи разных таблиц не пересекаются', () => { expect(pageKey('own', [1])).not.toBe(pageKey('approvals', [1])); }); it('разные части дают разные ключи', () => { expect(pageKey('own', [1])).not.toBe(pageKey('own', [2])); }); it('раздел без частей — просто раздел', () => { expect(pageKey('search', [])).toBe('search'); }); }); describe('readPage / writePage', () => { it('записанная страница читается по тому же ключу', () => { const key = pageKey('own', ['read-write']); const stored = page(); writePage(key, stored); expect(readPage(key)).toBe(stored); }); it('по незнакомому ключу возвращается undefined', () => { expect(readPage(pageKey('own', ['nothing-here']))).toBeUndefined(); }); it('повторная запись по тому же ключу перезаписывает страницу', () => { const key = pageKey('own', ['overwrite']); writePage(key, page({ totalRows: 1 })); writePage(key, page({ totalRows: 2 })); expect(readPage(key)?.totalRows).toBe(2); }); it('кеш переживает «размонтирование» — хранилище на уровне модуля, а не компонента', () => { const key = pageKey('approvals', ['module-level']); writePage(key, page({ totalRows: 7 })); // Между записью и чтением компонент таблицы мог быть уничтожен целиком — // на модульное хранилище это не влияет (ради этого оно и вынесено из useRef). expect(readPage(key)?.totalRows).toBe(7); }); }); describe('pageSignature', () => { it('одинаковое содержимое даёт одинаковую подпись (перерисовывать нечего)', () => { expect(pageSignature(page())).toBe(pageSignature(page())); }); it('изменение общего числа строк меняет подпись', () => { expect(pageSignature(page({ totalRows: 1 }))).not.toBe(pageSignature(page({ totalRows: 2 }))); }); it('изменение статуса заявки меняет подпись', () => { const before = page({ rows: [request({ status: 'Черновик' })] }); const after = page({ rows: [request({ status: 'На согласовании' })] }); expect(pageSignature(before)).not.toBe(pageSignature(after)); }); it('изменение порядка строк меняет подпись', () => { const a = request({ id: 1 }); const b = request({ id: 2 }); expect(pageSignature(page({ rows: [a, b], totalRows: 2 }))).not.toBe( pageSignature(page({ rows: [b, a], totalRows: 2 })) ); }); it('поля, которых нет в таблице, на подпись не влияют', () => { // kind/section/formKey в колонках не показываются — их изменение не должно // приводить к перерисовке (иначе таблица моргала бы на каждом фоновом обновлении). const before = page({ rows: [request({ section: 'Кадры', kind: 'own' })] }); const after = page({ rows: [request({ section: 'ИТ', kind: 'approval' })] }); expect(pageSignature(before)).toBe(pageSignature(after)); }); it('изменение комментария к решению меняет подпись — он показан колонкой «Причина возврата/отклонения»', () => { const before = page({ rows: [request({ decisionComment: 'Исправьте должность' })] }); const after = page({ rows: [request({ decisionComment: 'Приложите скан' })] }); expect(pageSignature(before)).not.toBe(pageSignature(after)); }); it('появление комментария к решению у строки без него тоже меняет подпись', () => { const before = page({ rows: [request()] }); const after = page({ rows: [request({ decisionComment: 'Исправьте должность' })] }); expect(pageSignature(before)).not.toBe(pageSignature(after)); }); it('появление кода заявки меняет подпись — он показан колонкой «Код»', () => { // Код проставляется вторым запросом, уже после создания элемента (см. // saveRequestItem), поэтому первая выдача приходит без него. Не учитывать // его в подписи — значит оставить в таблице «#1» до перезагрузки страницы. const before = page({ rows: [request({ code: '' })] }); const after = page({ rows: [request({ code: '1-2608-1' })] }); expect(pageSignature(before)).not.toBe(pageSignature(after)); }); it('смена текущего исполнителя меняет подпись — он показан колонкой «Исполнитель»', () => { // Заявка ушла на следующий шаг: статус тот же («На согласовании»), а держатель другой. const before = page({ rows: [request({ currentAssignees: [{ name: 'Петров Пётр', email: 'petrov@tnsu.kz' }] })] }); const after = page({ rows: [request({ currentAssignees: [{ name: 'Сидорова Анна', email: 'sidorova@tnsu.kz' }] })] }); expect(pageSignature(before)).not.toBe(pageSignature(after)); }); it('новый выполнивший меняет подпись — он показан колонкой «Исполнено»', () => { // Один из параллельных исполнителей отметил «Выполнено»: статус заявки тот же («Выполнение»). const before = page({ rows: [request({ executedSteps: [] })] }); const after = page({ rows: [request({ executedSteps: [{ name: 'Петров Пётр', email: 'petrov@tnsu.kz', executedAt: '2026-10-08T05:00:00Z' }] })], }); expect(pageSignature(before)).not.toBe(pageSignature(after)); }); it('пустая страница имеет стабильную подпись', () => { expect(pageSignature({ rows: [], totalRows: 0 })).toBe(pageSignature({ rows: [], totalRows: 0 })); }); });