diff --git a/apps/client/src/common/utils/__tests__/urlPresets.test.ts b/apps/client/src/common/utils/__tests__/urlPresets.test.ts index 56904962e..45635e6c9 100644 --- a/apps/client/src/common/utils/__tests__/urlPresets.test.ts +++ b/apps/client/src/common/utils/__tests__/urlPresets.test.ts @@ -1,8 +1,8 @@ import { resolvePath } from 'react-router-dom'; -import { generateUrlFromPreset, getRouteFromPreset, validateUrlPresetPath } from '../urlPresets'; +import { generatePathFromPreset, getRouteFromPreset, validateUrlPresetPath } from '../urlPresets'; -describe('A preset fails if incorrect', () => { +describe('validateUrlPresetPaths()', () => { test.each([ // no empty '', @@ -17,87 +17,56 @@ describe('A preset fails if incorrect', () => { // no editor 'editor', 'editor?test', - ])('validateUrlPresetPath(%s) should return false', (t) => { + ])('flags known edge cases: %s', (t) => { expect(validateUrlPresetPath(t).isValid).toBeFalsy(); }); }); -describe('generateUrlFromPreset and getRouteFromPreset function', () => { - test('generate the expected url from an alias', () => { - const testData = [ - { - enabled: true, - alias: 'demopage', - pathAndParams: '/timer?user=guest', - }, - ]; +describe('getRouteFromPreset()', () => { + const presets = [ + { + enabled: true, + alias: 'demopage', + pathAndParams: '/timer?user=guest', + }, + ]; - const expected = [ - { - url: '/timer?user=guest&alias=demopage', - }, - ]; - - expect(generateUrlFromPreset(testData[0])).toStrictEqual(expected[0].url); + it('checks if the current location matches an enabled preset', () => { + // we make the current location be the alias + const location = resolvePath('demopage'); + expect(getRouteFromPreset(location, presets)).toStrictEqual('timer?user=guest&alias=demopage'); }); - test('generate the url to redirect to when the current URL is just the alias', () => { - const presets = [ - { - enabled: true, - alias: 'demopage', - pathAndParams: '/timer?user=guest', - }, - ]; - // let current location be the alias - const location = resolvePath(presets[0].alias); - const expected = [ - { - url: '/timer?user=guest&alias=demopage', - }, - ]; - - // @ts-expect-error -- using a Path as a Location - expect(getRouteFromPreset(location, presets, null)).toStrictEqual(expected[0].url); + it('returns null if the current location is the exact match of an unwrapped alias', () => { + // we make the current location be the alias + const location = resolvePath('/timer?user=guest&alias=demopage'); + expect(getRouteFromPreset(location, presets)).toEqual(null); }); - test('generate the url to redirect to when the current URL the same url but with a change of params', () => { - const presets = [ - { - enabled: true, - alias: 'demopage', - pathAndParams: '/timer?user=guest', - }, - ]; - // let current location be the actual url with alias attached to it - const location = resolvePath(presets[0].pathAndParams); - const urlSearchParams = new URLSearchParams(location.search); - urlSearchParams.append('alias', presets[0].alias); // - // update current alias with extra param - presets[0].pathAndParams += '&eventId=674'; - const expected = [ - { - url: '/timer?user=guest&eventId=674&alias=demopage', - }, - ]; - - // @ts-expect-error -- using a Path as a Location - expect(getRouteFromPreset(location, presets, urlSearchParams)).toStrictEqual(expected[0].url); + it('returns a new destination if the current location is an out-of-date unwrapped alias', () => { + // we make the current location be the alias + const location = resolvePath('/timer?user=admin&alias=demopage'); + expect(getRouteFromPreset(location, presets)).toEqual('timer?user=guest&alias=demopage'); }); - test('generate no url to redirect to when the current URL the same url', () => { - const presets = [ - { - enabled: true, - alias: 'demopage', - pathAndParams: '/timer?user=guest', - }, - ]; - // let current location be the actual url with alias attached to it - const location = resolvePath(presets[0].pathAndParams); - const urlSearchParams = new URLSearchParams(location.search); - urlSearchParams.append('alias', presets[0].alias); - // @ts-expect-error -- using a Path as a Location - expect(getRouteFromPreset(location, presets, urlSearchParams)).toBeNull(); + it('checks if the current location contains an unwrapped preset', () => { + // we make the current location be the alias + const location = resolvePath('/timer?user=guest&alias=demopage'); + expect(getRouteFromPreset(location, presets)).toEqual(null); + }); + + it('ignores a location that has no presets', () => { + // we make the current location be the alias + const location = resolvePath('/unknown'); + expect(getRouteFromPreset(location, presets)).toEqual(null); + }); +}); + +describe('generatePathFromPreset()', () => { + test.each([ + ['timer?user=guest', 'demopage', 'timer?user=guest&alias=demopage'], + ['timer?user=admin', 'demopage', 'timer?user=admin&alias=demopage'], + ])('generates a path from a preset: %s', (path, alias, expected) => { + expect(generatePathFromPreset(path, alias)).toEqual(expected); }); }); diff --git a/apps/client/src/common/utils/urlPresets.ts b/apps/client/src/common/utils/urlPresets.ts index 098f0391a..c4ef93c3b 100644 --- a/apps/client/src/common/utils/urlPresets.ts +++ b/apps/client/src/common/utils/urlPresets.ts @@ -1,13 +1,11 @@ -import isEqual from 'react-fast-compare'; -import { Location, resolvePath } from 'react-router-dom'; +import { Path, resolvePath } from 'react-router-dom'; import { URLPreset } from 'ontime-types'; /** * Validates a preset against defined parameters - * @param {string} preset - * @returns {{message: string, isValid: boolean}} + * Used in the context of form validation */ -export const validateUrlPresetPath = (preset: string): { message: string; isValid: boolean } => { +export function validateUrlPresetPath(preset: string): { message: string; isValid: boolean } { if (preset === '' || preset == null) { return { isValid: false, message: 'Path cannot be empty' }; } @@ -26,7 +24,7 @@ export const validateUrlPresetPath = (preset: string): { message: string; isVali } return { isValid: true, message: 'ok' }; -}; +} /** * Utility removes trailing slash from a string @@ -36,48 +34,52 @@ function removeTrailingSlash(text: string): string { } /** - * Gets the URL to send a preset to - * @param location - * @param data - * @param searchParams + * Checks whether the current location corresponds to a preset and returns the new path if necessary */ -export const getRouteFromPreset = (location: Location, data: URLPreset[], searchParams: URLSearchParams) => { +export function getRouteFromPreset(location: Path, urlPresets: URLPreset[]) { + // current url is the pathname without the leading slash const currentURL = location.pathname.substring(1); - // we need to check if the whole url here is an alias, so we can redirect - const foundPreset = data.find((preset) => preset.alias === removeTrailingSlash(currentURL) && preset.enabled); + // we need to check if the whole url is an alias + const foundPreset = urlPresets.find((preset) => preset.alias === removeTrailingSlash(currentURL) && preset.enabled); if (foundPreset) { - return generateUrlFromPreset(foundPreset); + // if so, we can redirect to the preset path + return generatePathFromPreset(foundPreset.pathAndParams, foundPreset.alias); } + // if the current url is not an alias, we check if the alias is in the search parameters + const searchParams = new URLSearchParams(location.search); + const presetOnPage = searchParams.get('alias'); - for (const d of data) { - if (presetOnPage) { - // if the alias fits the preset on this page, but the URL is different, we redirect user to the new URL - // if we have the same alias and its enabled and its not empty - if (d.alias !== '' && d.enabled && d.alias === presetOnPage) { - const newPath = resolvePath(d.pathAndParams); - const urlParams = new URLSearchParams(newPath.search); - urlParams.set('alias', d.alias); - // we confirm either the url parameters does not match or the url path doesnt - if (!isEqual(urlParams, searchParams) || newPath.pathname !== location.pathname) { - // we then redirect to the alias route, since the view listening to this alias has an outdated URL - return `${newPath.pathname}?${urlParams}`; - } + if (!presetOnPage) { + return null; + } + + for (const preset of urlPresets) { + // if the page has a known enabled alias, we check if we need to redirect + if (preset.alias === presetOnPage && preset.enabled) { + const newPath = generatePathFromPreset(preset.pathAndParams, preset.alias); + const currentPath = `${location.pathname}${location.search}`.substring(1); + if (currentPath !== newPath) { + // if current path is out of date + // return new path so we can redirect + return newPath; } } } return null; -}; +} /** - * Generate URL from an preset - * @param presetData + * Handles generating a path and search parameters from a preset */ -export const generateUrlFromPreset = (presetData: URLPreset) => { - const newPresetPath = resolvePath(presetData.pathAndParams); - const urlParams = new URLSearchParams(newPresetPath.search); - urlParams.set('alias', presetData.alias); +export function generatePathFromPreset(pathAndParams: string, alias: string): string { + const path = resolvePath(pathAndParams); + const searchParams = new URLSearchParams(path.search); - return `${newPresetPath.pathname}?${urlParams}`; -}; + // save the alias so we have a reference to it being a preset and can update if necessary + searchParams.set('alias', alias); + + // return path concatenated without the leading slash + return `${path.pathname}?${searchParams}`.substring(1); +} diff --git a/apps/client/src/features/PresetWrapper.tsx b/apps/client/src/features/PresetWrapper.tsx index e46ad8af3..d5beeab09 100644 --- a/apps/client/src/features/PresetWrapper.tsx +++ b/apps/client/src/features/PresetWrapper.tsx @@ -1,6 +1,6 @@ /* eslint-disable react/display-name */ import { ComponentType, useEffect } from 'react'; -import { useLocation, useNavigate, useSearchParams } from 'react-router-dom'; +import { useLocation, useNavigate } from 'react-router-dom'; import useUrlPresets from '../common/hooks-query/useUrlPresets'; import { getRouteFromPreset } from '../common/utils/urlPresets'; @@ -8,19 +8,19 @@ import { getRouteFromPreset } from '../common/utils/urlPresets'; const withPreset =
(Component: ComponentType
) => { return (props: Partial
) => {
const { data } = useUrlPresets();
- const [searchParams] = useSearchParams();
const navigate = useNavigate();
const location = useLocation();
// navigate if is alias route
useEffect(() => {
if (!data) return;
- const url = getRouteFromPreset(location, data, searchParams);
- // navigate to this route if its not empty
- if (url) {
- navigate(url);
+ const destination = getRouteFromPreset(location, data);
+
+ // navigate to this destination if its not null
+ if (destination) {
+ navigate(destination);
}
- }, [data, searchParams, navigate, location]);
+ }, [data, navigate, location]);
return