Files
request-portal/src/webparts/requestPortal/shared/routing.test.ts
T
Тимур Городецкий 11eb127764
Tests / frontend-test (push) Successful in 4m45s
feat: доработал заявку перемещение тмц
2026-09-10 11:19:16 +05:00

623 lines
29 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import {
IRoute,
navigate,
navigateToRequestLink,
newRequestLinkUrl,
parseRoute,
persistOwnStatus,
persistOwnTab,
persistPage,
persistQuery,
requestLinkUrl,
routeUrl,
storedOwnStatus,
storedOwnTab,
storedPageFor,
storedQueryFor,
} from './routing';
// buildQuery/navigate читают и пишут window.location через history API, а
// подвкладка/фильтр «Моих заявок» — ещё и localStorage (см. persistOwnTab/
// persistOwnStatus) — сбрасываем оба перед каждым тестом, чтобы они не
// зависели от порядка запуска.
beforeEach(() => {
window.history.replaceState({}, '', '/');
window.localStorage.clear();
});
describe('parseRoute', () => {
it('пустой адрес — подача заявки в разделе «Новая заявка»', () => {
// page всегда определён (1 — значение по умолчанию), в адрес не пишется.
expect(parseRoute('')).toEqual({ mode: 'submit', nav: 'new', page: 1, query: '' });
});
it('rpnav задаёт раздел бокового меню', () => {
expect(parseRoute('?rpnav=approvals').nav).toBe('approvals');
expect(parseRoute('?rpnav=execution').nav).toBe('execution');
expect(parseRoute('?rpnav=rework').nav).toBe('rework');
expect(parseRoute('?rpnav=own').nav).toBe('own');
expect(parseRoute('?rpnav=search').nav).toBe('search');
});
it('незнакомый rpnav откатывается на раздел по умолчанию', () => {
expect(parseRoute('?rpnav=bogus').nav).toBe('new');
});
it('полный набор параметров открывает заявку на согласование', () => {
expect(parseRoute('?rpmode=approve&rptype=hire&rpid=42')).toEqual({
mode: 'approve',
nav: 'new',
formKey: 'hire',
itemId: 42,
page: 1,
query: '',
});
});
it('раздел меню сохраняется, когда поверх него открыта заявка', () => {
expect(parseRoute('?rpmode=approve&rptype=hire&rpid=42&rpnav=own').nav).toBe('own');
});
it('режим редактирования собственной заявки разбирается так же, как согласование', () => {
expect(parseRoute('?rpmode=edit&rptype=hire&rpid=7')).toEqual({
mode: 'edit',
nav: 'new',
formKey: 'hire',
itemId: 7,
page: 1,
query: '',
});
});
it('без rpid заявка не открывается — остаётся обычная подача', () => {
expect(parseRoute('?rpmode=approve&rptype=hire').mode).toBe('submit');
});
it('нечисловой rpid не открывает заявку', () => {
expect(parseRoute('?rpmode=approve&rptype=hire&rpid=abc').mode).toBe('submit');
});
it('rpid=0 не считается заявкой (в SharePoint нумерация с единицы)', () => {
expect(parseRoute('?rpmode=approve&rptype=hire&rpid=0').mode).toBe('submit');
});
it('rptype + rpsource — создание производной заявки с предзаполнением', () => {
expect(parseRoute('?rptype=workplace&rpsource=15')).toEqual({
mode: 'create',
nav: 'new',
formKey: 'workplace',
sourceId: 15,
});
});
it('создание производной заявки всегда открывается в разделе «Новая заявка»', () => {
expect(parseRoute('?rptype=workplace&rpsource=15&rpnav=own').nav).toBe('new');
});
it('открытая заявка имеет приоритет над предзаполнением', () => {
expect(parseRoute('?rpmode=approve&rptype=hire&rpid=42&rpsource=15').mode).toBe('approve');
});
it('rpmode=create + rptype без rpsource — пустая новая форма без предзаполнения', () => {
expect(parseRoute('?rpmode=create&rptype=hire')).toEqual({
mode: 'create',
nav: 'new',
formKey: 'hire',
});
});
it('rptype БЕЗ rpmode — тоже пустая новая форма (buildQuery для create его не пишет)', () => {
expect(parseRoute('?rptype=hire')).toEqual({
mode: 'create',
nav: 'new',
formKey: 'hire',
});
});
it('rpmode=create без rptype не открывает пустую форму — остаётся обычная подача', () => {
expect(parseRoute('?rpmode=create').mode).toBe('submit');
});
it('внешняя ссылка ТМЦ: rptype + positionId + nomenclatureId + storeName прокидываются в create', () => {
expect(parseRoute('?rptype=wh-tmc-transfer&positionId=00d31059-1f52-11ec-a738-3cecef292546&nomenclatureId=abc-123&storeName=Склад%20№1')).toEqual({
mode: 'create',
nav: 'new',
formKey: 'wh-tmc-transfer',
positionId: '00d31059-1f52-11ec-a738-3cecef292546',
nomenclatureId: 'abc-123',
storeName: 'Склад №1',
});
});
it('positionId/nomenclatureId без rptype игнорируются — обычная подача', () => {
// Внешние параметры имеют смысл только при открытии формы (rptype); сами по
// себе не должны влиять на маршрут.
expect(parseRoute('?positionId=x&nomenclatureId=y').mode).toBe('submit');
});
it('rpmode=create + rptype + rpsource — предзаполнение важнее пустой формы', () => {
expect(parseRoute('?rpmode=create&rptype=hire&rpsource=15')).toEqual({
mode: 'create',
nav: 'new',
formKey: 'hire',
sourceId: 15,
});
});
});
describe('newRequestLinkUrl', () => {
it('строит абсолютную ссылку на пустую форму без rpsource', () => {
const url = newRequestLinkUrl('hire');
expect(url).toBe(`${window.location.origin}${window.location.pathname}?rpmode=create&rptype=hire`);
});
it('ссылка на пустую форму разбирается обратно в тот же маршрут', () => {
const url = newRequestLinkUrl('hire');
const route = parseRoute(new URL(url).search);
expect(route).toEqual({ mode: 'create', nav: 'new', formKey: 'hire' });
});
});
describe('подвкладки и фильтр «Моих заявок»', () => {
it('без rptab — подвкладка по умолчанию', () => {
expect(parseRoute('?rpnav=own').ownTab).toBe('created');
});
it('rptab задаёт подвкладку', () => {
expect(parseRoute('?rpnav=own&rptab=approved').ownTab).toBe('approved');
expect(parseRoute('?rpnav=own&rptab=rejected').ownTab).toBe('rejected');
expect(parseRoute('?rpnav=own&rptab=executed').ownTab).toBe('executed');
});
it('незнакомый rptab откатывается на подвкладку по умолчанию', () => {
expect(parseRoute('?rpnav=own&rptab=bogus').ownTab).toBe('created');
});
it('вне «Моих заявок» подвкладка не читается — чужой rptab туда не протекает', () => {
expect(parseRoute('?rpnav=approvals&rptab=approved').ownTab).toBeUndefined();
expect(parseRoute('?rpnav=search&rptab=approved').ownTab).toBeUndefined();
});
it('фильтр по статусу действует только на вкладке «Инициированные»', () => {
expect(parseRoute('?rpnav=own&rpstatus=Черновик').ownStatus).toBe('Черновик');
// У вкладок с решениями отбор задаёт сама вкладка — фильтр там бессмыслен.
expect(parseRoute('?rpnav=own&rptab=approved&rpstatus=Черновик').ownStatus).toBeUndefined();
expect(parseRoute('?rpnav=approvals&rpstatus=Черновик').ownStatus).toBeUndefined();
});
it('значения по умолчанию не засоряют адрес', () => {
expect(routeUrl({ mode: 'submit', nav: 'own', ownTab: 'created' })).not.toContain('rptab');
expect(routeUrl({ mode: 'submit', nav: 'own' })).not.toContain('rptab');
});
it('непустая подвкладка и фильтр попадают в адрес', () => {
expect(routeUrl({ mode: 'submit', nav: 'own', ownTab: 'rejected' })).toContain('rptab=rejected');
expect(routeUrl({ mode: 'submit', nav: 'own', ownTab: 'created', ownStatus: 'Черновик' })).toContain('rpstatus=');
});
it('фильтр не пишется в адрес вместе с подвкладкой решений', () => {
const url = routeUrl({ mode: 'submit', nav: 'own', ownTab: 'approved', ownStatus: 'Черновик' });
expect(url).toContain('rptab=approved');
expect(url).not.toContain('rpstatus');
});
it('уход из «Моих заявок» убирает подвкладку и фильтр из адреса', () => {
navigate({ mode: 'submit', nav: 'own', ownTab: 'rejected' });
navigate({ mode: 'submit', nav: 'approvals' });
expect(window.location.search).not.toContain('rptab');
expect(window.location.search).not.toContain('rpstatus');
});
});
describe('страница таблицы (rppage + persistPage)', () => {
it('без rppage — первая страница', () => {
expect(parseRoute('?rpnav=own').page).toBe(1);
});
it('rppage задаёт страницу', () => {
expect(parseRoute('?rpnav=own&rppage=3').page).toBe(3);
});
it('мусор в rppage трактуется как первая страница, а не роняет разбор', () => {
expect(parseRoute('?rpnav=own&rppage=0').page).toBe(1);
expect(parseRoute('?rpnav=own&rppage=-2').page).toBe(1);
expect(parseRoute('?rpnav=own&rppage=abc').page).toBe(1);
expect(parseRoute('?rpnav=own&rppage=1.5').page).toBe(1);
});
it('первая страница не засоряет адрес, непервая — попадает', () => {
expect(routeUrl({ mode: 'submit', nav: 'own', page: 1 })).not.toContain('rppage');
expect(routeUrl({ mode: 'submit', nav: 'own' })).not.toContain('rppage');
expect(routeUrl({ mode: 'submit', nav: 'own', page: 4 })).toContain('rppage=4');
});
it('round-trip страницы через navigate', () => {
navigate({ mode: 'submit', nav: 'approvals', page: 2 });
expect(parseRoute(window.location.search).page).toBe(2);
});
it('без rppage подставляется запомненная для ЭТОЙ таблицы страница', () => {
persistPage({ mode: 'submit', nav: 'approvals' }, 5);
expect(parseRoute('?rpnav=approvals').page).toBe(5);
});
it('страница запоминается отдельно по таблицам и не переносится между разделами', () => {
persistPage({ mode: 'submit', nav: 'approvals' }, 5);
// В другом разделе своя нумерация — чужая страница туда попасть не должна,
// иначе можно приземлиться на несуществующую (у него всего одна страница).
expect(parseRoute('?rpnav=execution').page).toBe(1);
});
it('подвкладки «Моих заявок» — тоже разные таблицы, у каждой своя страница', () => {
persistPage({ mode: 'submit', nav: 'own', ownTab: 'created' }, 3);
persistPage({ mode: 'submit', nav: 'own', ownTab: 'approved' }, 7);
expect(parseRoute('?rpnav=own&rptab=created').page).toBe(3);
expect(parseRoute('?rpnav=own&rptab=approved').page).toBe(7);
});
it('явная страница в адресе имеет приоритет над запомненной', () => {
persistPage({ mode: 'submit', nav: 'approvals' }, 5);
expect(parseRoute('?rpnav=approvals&rppage=2').page).toBe(2);
});
it('возврат на первую страницу стирает запомненную', () => {
persistPage({ mode: 'submit', nav: 'approvals' }, 5);
expect(parseRoute('?rpnav=approvals').page).toBe(5);
persistPage({ mode: 'submit', nav: 'approvals' }, 1);
expect(parseRoute('?rpnav=approvals').page).toBe(1);
});
it('страница переживает открытие заявки поверх списка', () => {
navigate({ mode: 'submit', nav: 'own', ownTab: 'created', page: 3 });
navigate({ mode: 'approve', nav: 'own', ownTab: 'created', formKey: 'hire', itemId: 42, page: 3 });
expect(parseRoute(window.location.search).page).toBe(3);
});
// Ради этого storedPageFor и заведён: при переходе по меню маршрут СТРОИТСЯ,
// и если не подставить запомненную страницу явно, она останется только в
// состоянии — в адресе будет первая, а на экране открытая (см. goto в
// RequestPortal.tsx).
it('storedPageFor отдаёт запомненную страницу нужной таблицы', () => {
persistPage({ mode: 'submit', nav: 'approvals' }, 4);
expect(storedPageFor('approvals')).toBe(4);
expect(storedPageFor('execution')).toBe(1);
});
it('storedPageFor различает подвкладки «Моих заявок»', () => {
persistPage({ mode: 'submit', nav: 'own', ownTab: 'rejected' }, 6);
expect(storedPageFor('own', 'rejected')).toBe(6);
expect(storedPageFor('own', 'created')).toBe(1);
});
it('storedPageFor без подвкладки берёт запомненную — ту, что и откроется', () => {
persistOwnTab('executed');
persistPage({ mode: 'submit', nav: 'own', ownTab: 'executed' }, 2);
expect(storedPageFor('own')).toBe(2);
});
it('переход по меню кладёт запомненную страницу в адрес', () => {
persistPage({ mode: 'submit', nav: 'approvals' }, 3);
navigate({ mode: 'submit', nav: 'approvals', page: storedPageFor('approvals') });
expect(window.location.search).toContain('rppage=3');
});
it('localStorage недоступен — страница просто первая, без падения', () => {
const original = window.localStorage.getItem;
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- имитация приватного режима, где чтение localStorage бросает
(window.localStorage as any).getItem = (): never => {
throw new Error('SecurityError');
};
try {
expect(() => parseRoute('?rpnav=own')).not.toThrow();
expect(parseRoute('?rpnav=own').page).toBe(1);
} finally {
window.localStorage.getItem = original;
}
});
});
describe('поиск (rpq + persistQuery)', () => {
it('без rpq — пустой запрос', () => {
expect(parseRoute('?rpnav=own').query).toBe('');
});
it('rpq задаёт запрос, в т.ч. с пробелами и кириллицей', () => {
expect(parseRoute('?rpnav=own&rpq=%D0%B0%D0%B1%D0%B2%20%D0%B3').query).toBe('абв г');
});
it('пустой запрос не засоряет адрес, непустой — попадает', () => {
expect(routeUrl({ mode: 'submit', nav: 'own', query: '' })).not.toContain('rpq');
expect(routeUrl({ mode: 'submit', nav: 'own' })).not.toContain('rpq');
expect(routeUrl({ mode: 'submit', nav: 'own', query: 'бухгалтер' })).toContain('rpq=');
});
it('round-trip запроса через navigate', () => {
navigate({ mode: 'submit', nav: 'search', query: 'Горбенко' });
expect(parseRoute(window.location.search).query).toBe('Горбенко');
});
it('без rpq подставляется запомненный для ЭТОЙ таблицы запрос', () => {
persistQuery({ mode: 'submit', nav: 'search' }, 'бухгалтер');
expect(parseRoute('?rpnav=search').query).toBe('бухгалтер');
});
it('запрос запоминается отдельно по таблицам и не переносится между разделами', () => {
persistQuery({ mode: 'submit', nav: 'search' }, 'бухгалтер');
expect(parseRoute('?rpnav=approvals').query).toBe('');
});
it('подвкладки «Моих заявок» — тоже разные таблицы, у каждой свой запрос', () => {
persistQuery({ mode: 'submit', nav: 'own', ownTab: 'created' }, 'приём');
persistQuery({ mode: 'submit', nav: 'own', ownTab: 'approved' }, 'место');
expect(parseRoute('?rpnav=own&rptab=created').query).toBe('приём');
expect(parseRoute('?rpnav=own&rptab=approved').query).toBe('место');
});
it('очистка запроса стирает запомненный', () => {
persistQuery({ mode: 'submit', nav: 'search' }, 'бухгалтер');
expect(parseRoute('?rpnav=search').query).toBe('бухгалтер');
persistQuery({ mode: 'submit', nav: 'search' }, '');
expect(parseRoute('?rpnav=search').query).toBe('');
});
it('storedQueryFor отдаёт запомненный запрос нужной таблицы', () => {
persistQuery({ mode: 'submit', nav: 'approvals' }, 'заявка');
expect(storedQueryFor('approvals')).toBe('заявка');
expect(storedQueryFor('execution')).toBe('');
});
it('переход по меню кладёт восстановленный запрос в адрес', () => {
persistQuery({ mode: 'submit', nav: 'approvals' }, 'заявка');
navigate({ mode: 'submit', nav: 'approvals', query: storedQueryFor('approvals') || undefined });
expect(parseRoute(window.location.search).query).toBe('заявка');
});
it('replace не добавляет запись в историю — набор текста не засоряет «назад»', () => {
navigate({ mode: 'submit', nav: 'search' });
const before = window.history.length;
navigate({ mode: 'submit', nav: 'search', query: 'бух' }, { replace: true });
navigate({ mode: 'submit', nav: 'search', query: 'бухг' }, { replace: true });
expect(window.history.length).toBe(before);
expect(parseRoute(window.location.search).query).toBe('бухг');
});
it('localStorage недоступен — запрос просто пустой, без падения', () => {
const original = window.localStorage.getItem;
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- имитация приватного режима, где чтение localStorage бросает
(window.localStorage as any).getItem = (): never => {
throw new Error('SecurityError');
};
try {
expect(() => parseRoute('?rpnav=own')).not.toThrow();
expect(parseRoute('?rpnav=own').query).toBe('');
} finally {
window.localStorage.getItem = original;
}
});
});
describe('persistOwnTab/persistOwnStatus — запоминание подвкладки/фильтра между визитами', () => {
it('без явного rptab в адресе подставляется запомненная подвкладка', () => {
persistOwnTab('rejected');
expect(parseRoute('?rpnav=own').ownTab).toBe('rejected');
});
it('без явного rpstatus подставляется запомненный фильтр (только на «Инициированных»)', () => {
persistOwnStatus('Черновик');
expect(parseRoute('?rpnav=own').ownStatus).toBe('Черновик');
// На вкладках с решениями запомненный фильтр ни при чём — там его нет по смыслу.
persistOwnTab('approved');
expect(parseRoute('?rpnav=own').ownStatus).toBeUndefined();
});
it('явный параметр в адресе имеет приоритет над запомненным значением', () => {
persistOwnTab('rejected');
expect(parseRoute('?rpnav=own&rptab=executed').ownTab).toBe('executed');
});
it('очистка фильтра persistOwnStatus убирает его и из последующих parseRoute', () => {
persistOwnStatus('Черновик');
expect(parseRoute('?rpnav=own').ownStatus).toBe('Черновик');
persistOwnStatus(undefined);
expect(parseRoute('?rpnav=own').ownStatus).toBeUndefined();
});
it('запомненное значение не протекает за пределы раздела «Мои заявки»', () => {
persistOwnTab('rejected');
expect(parseRoute('?rpnav=approvals').ownTab).toBeUndefined();
});
// Как и со страницей: при переходе маршрут СТРОИТСЯ, и без явной подстановки
// восстановленный фильтр остался бы только в состоянии — в адресе его бы не
// было (см. goto/gotoOwnTab в RequestPortal.tsx).
it('storedOwnStatus отдаёт фильтр только для вкладки «Инициированные»', () => {
persistOwnStatus('Отклонено');
expect(storedOwnStatus('created')).toBe('Отклонено');
expect(storedOwnStatus('approved')).toBeUndefined();
expect(storedOwnStatus('rejected')).toBeUndefined();
});
it('storedOwnTab отдаёт запомненную подвкладку', () => {
persistOwnTab('executed');
expect(storedOwnTab()).toBe('executed');
});
it('переход в «Мои заявки» кладёт восстановленный фильтр в адрес', () => {
persistOwnStatus('Согласовано');
const ownTab = storedOwnTab();
navigate({ mode: 'submit', nav: 'own', ownTab, ownStatus: storedOwnStatus(ownTab) });
expect(window.location.search).toContain('rpstatus=');
expect(parseRoute(window.location.search).ownStatus).toBe('Согласовано');
});
it('переход на подвкладку решений фильтр в адрес не тащит', () => {
persistOwnStatus('Согласовано');
navigate({ mode: 'submit', nav: 'own', ownTab: 'approved', ownStatus: storedOwnStatus('approved') });
expect(window.location.search).toContain('rptab=approved');
expect(window.location.search).not.toContain('rpstatus');
});
// Ровно тот баг, который это всё затевалось починить: подвкладка «Инициированные»
// с выбранным фильтром → уход на другую подвкладку → возврат — фильтр должен
// остаться прежним, а не сброситься на «Все статусы» (см. RequestPortal.tsx/
// gotoOwnTab — persistOwnTab НЕ трогает сохранённый статус).
it('переключение на другую подвкладку и обратно не стирает запомненный фильтр', () => {
persistOwnStatus('На согласовании');
expect(parseRoute('?rpnav=own').ownStatus).toBe('На согласовании');
persistOwnTab('approved');
persistOwnTab('created');
expect(parseRoute('?rpnav=own').ownStatus).toBe('На согласовании');
});
it('localStorage недоступен (например, приватный режим) — не роняет parseRoute', () => {
const original = window.localStorage.getItem;
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- имитация приватного режима, где чтение localStorage бросает
(window.localStorage as any).getItem = (): never => {
throw new Error('SecurityError');
};
try {
expect(() => parseRoute('?rpnav=own')).not.toThrow();
expect(parseRoute('?rpnav=own').ownTab).toBe('created');
} finally {
window.localStorage.getItem = original;
}
});
it('round-trip для подвкладки решений', () => {
navigate({ mode: 'submit', nav: 'own', ownTab: 'executed' });
const route = parseRoute(window.location.search);
expect(route.nav).toBe('own');
expect(route.ownTab).toBe('executed');
});
});
describe('routeUrl', () => {
it('раздел по умолчанию не засоряет адрес параметром', () => {
expect(routeUrl({ mode: 'submit', nav: 'new' })).toBe('/');
});
it('непустой раздел попадает в адрес', () => {
expect(routeUrl({ mode: 'submit', nav: 'own' })).toContain('rpnav=own');
});
it('сохраняет посторонние параметры страницы (их ставит SharePoint)', () => {
window.history.replaceState({}, '', '/page.aspx?env=WebViewList');
const url = routeUrl({ mode: 'submit', nav: 'own' });
expect(url).toContain('env=WebViewList');
expect(url).toContain('rpnav=own');
});
it('заявка без formKey/itemId не добавляет параметров открытия', () => {
const url = routeUrl({ mode: 'approve', nav: 'new' } as IRoute);
expect(url).not.toContain('rpmode');
});
});
describe('navigate', () => {
it('после перехода маршрут читается обратно тем же parseRoute (round-trip)', () => {
navigate({ mode: 'approve', nav: 'own', formKey: 'hire', itemId: 42 });
expect(parseRoute(window.location.search)).toEqual({
mode: 'approve',
nav: 'own',
formKey: 'hire',
itemId: 42,
// Подвкладка «Моих заявок» всегда определена внутри раздела — в адресе её
// нет только потому, что это значение по умолчанию.
ownTab: 'created',
ownStatus: undefined,
page: 1,
query: '',
});
});
it('подвкладка и фильтр «Моих заявок» переживают открытие и закрытие заявки', () => {
navigate({ mode: 'submit', nav: 'own', ownTab: 'created', ownStatus: 'Черновик' });
navigate({ mode: 'approve', nav: 'own', formKey: 'hire', itemId: 42, ownTab: 'created', ownStatus: 'Черновик' });
const opened = parseRoute(window.location.search);
expect(opened.ownStatus).toBe('Черновик');
navigate({ mode: 'submit', nav: 'own', ownTab: opened.ownTab, ownStatus: opened.ownStatus });
expect(parseRoute(window.location.search).ownStatus).toBe('Черновик');
});
it('round-trip для создания производной заявки', () => {
navigate({ mode: 'create', nav: 'new', formKey: 'workplace', sourceId: 15 });
expect(parseRoute(window.location.search)).toEqual({
mode: 'create',
nav: 'new',
formKey: 'workplace',
sourceId: 15,
});
});
it('возврат к подаче заявки убирает параметры открытой заявки из адреса', () => {
navigate({ mode: 'approve', nav: 'new', formKey: 'hire', itemId: 42 });
navigate({ mode: 'submit', nav: 'new' });
expect(window.location.search).toBe('');
});
it('добавляет запись в историю (pushState, а не replaceState)', () => {
const before = window.history.length;
navigate({ mode: 'submit', nav: 'own' });
expect(window.history.length).toBe(before + 1);
});
it('оповещает подписчиков своим событием (pushState сам popstate не порождает)', () => {
const seen: string[] = [];
const onChange = (): void => {
seen.push(window.location.search);
};
window.addEventListener('rp-route-change', onChange);
navigate({ mode: 'submit', nav: 'approvals' });
window.removeEventListener('rp-route-change', onChange);
expect(seen).toHaveLength(1);
expect(seen[0]).toContain('rpnav=approvals');
});
});
describe('requestLinkUrl / navigateToRequestLink', () => {
it('ссылка абсолютна и не тащит посторонние параметры текущей страницы', () => {
window.history.replaceState({}, '', '/?foo=bar&rpnav=own');
expect(requestLinkUrl('clearance-form', 7, '/sites/requests/Lists/List10')).toBe(
'http://localhost/?rpmode=approve&rptype=clearance-form&rpid=7&rplist=%2Fsites%2Frequests%2FLists%2FList10'
);
});
it('ссылка на заявку этого же портала открывается навигацией, без перезагрузки', () => {
window.history.replaceState({}, '', '/?rpnav=own');
expect(navigateToRequestLink(requestLinkUrl('clearance-form', 7))).toBe(true);
const route = parseRoute();
expect(route.mode).toBe('approve');
expect(route.formKey).toBe('clearance-form');
expect(route.itemId).toBe(7);
// Раздел под панелью заявки остаётся тем же, что был открыт.
expect(route.nav).toBe('own');
});
it('чужой сайт, другая страница и ссылка не на заявку — клик отдаётся браузеру', () => {
expect(navigateToRequestLink('https://example.com/p?rpmode=approve&rptype=a&rpid=1')).toBe(false);
expect(navigateToRequestLink('/OtherPage.aspx?rpmode=approve&rptype=a&rpid=1')).toBe(false);
expect(navigateToRequestLink('http://localhost/?rpnav=own')).toBe(false);
});
it('мусор вместо адреса не роняет обработчик клика', () => {
expect(navigateToRequestLink('не ссылка')).toBe(false);
});
});