diff --git a/framework/default/ortoo-core/default/lwc/uriUtils/__tests__/uriUtils.test.js b/framework/default/ortoo-core/default/lwc/uriUtils/__tests__/uriUtils.test.js index e3d146d5205..85743179dd1 100644 --- a/framework/default/ortoo-core/default/lwc/uriUtils/__tests__/uriUtils.test.js +++ b/framework/default/ortoo-core/default/lwc/uriUtils/__tests__/uriUtils.test.js @@ -137,6 +137,41 @@ describe( 'getUriFragmentAsObject', () => { const got = UriUtils.getUriFragmentAsObject(); + expect( got ).toEqual( expected ); + }) + it( 'will decode %20 as space in property names', () => { + + location.hash = 'property%201=%22value%22'; + + const expected = {}; + expected[ 'property 1' ] = 'value'; + const got = UriUtils.getUriFragmentAsObject(); + + expect( got ).toEqual( expected ); + }) + + it( 'will skip strings with no equals in them', () => { + + location.hash = 'property1=%22value1%22&ignorethisbit&property2=%22value2%22'; + + const expected = { + property1: 'value1', + property2: 'value2' + }; + const got = UriUtils.getUriFragmentAsObject(); + + expect( got ).toEqual( expected ); + }) + it( 'will skip values that do not decode properly', () => { + + location.hash = 'property1=%22value1%22&invalid=%22incompleteJson&property2=%22value2%22'; + + const expected = { + property1: 'value1', + property2: 'value2' + }; + const got = UriUtils.getUriFragmentAsObject(); + expect( got ).toEqual( expected ); }) }); @@ -177,19 +212,83 @@ describe( 'setUriFragmentObject / getUriFragmentAsObject', () => { }); }); -describe( 'registerUriFragmentListener', () => { +describe( 'addFragmentToUri', () => { - beforeAll(() => { - window.addEventListener = jest.fn(); - }) + it( 'when given a uri with no fragment and an object, will encode the object and add it as the fragment', () => { + + const uri = 'theBaseUri'; + const fragmentObject = { param: 'value' }; + + const got = UriUtils.addFragmentToUri( uri, fragmentObject ); + + expect( got ).toBe( 'theBaseUri#param=%22value%22' ); + }); + + it( 'when given a uri with a fragment and an object, will encode the object and add to the end of the fragment', () => { + + const uri = 'theBaseUri#originalfragment'; + const fragmentObject = { param: 'value' }; + + const got = UriUtils.addFragmentToUri( uri, fragmentObject ); + + expect( got ).toBe( 'theBaseUri#originalfragment¶m=%22value%22' ); + }); + + it( 'when given a uri without a fragment and an empty object, will return the uri untouched', () => { + + const uri = 'theBaseUri'; + const fragmentObject = {}; + + const got = UriUtils.addFragmentToUri( uri, fragmentObject ); + + expect( got ).toBe( 'theBaseUri' ); + }); + it( 'when given a uri without a fragment and undefined, will return the uri untouched', () => { + + const uri = 'theBaseUri'; + + const got = UriUtils.addFragmentToUri( uri ); + + expect( got ).toBe( 'theBaseUri' ); + }); + + it( 'when given a uri without a fragment and an empty object, will return the uri untouched', () => { + + const uri = 'theBaseUri'; + const fragmentObject = {}; + + const got = UriUtils.addFragmentToUri( uri, fragmentObject ); + + expect( got ).toBe( 'theBaseUri' ); + }); + + it( 'when given a uri with a fragment and an empty object, will return the uri untouched', () => { + + const uri = 'theBaseUri#originalfragment'; + const fragmentObject = {}; + + const got = UriUtils.addFragmentToUri( uri, fragmentObject ); + + expect( got ).toBe( 'theBaseUri#originalfragment' ); + }); + + it( 'when given a uri and a string fragment, will return the uri with the string fragment added', () => { + + const uri = 'theBaseUri'; + const fragment = 'astring'; + + const got = UriUtils.addFragmentToUri( uri, fragment ); + + expect( got ).toBe( 'theBaseUri#astring' ); + }); - it( 'will register the given function as an event listener for hashchanges on the window', () => { + it( 'when given a uri with a fragment and a string fragment, will return the uri with the string fragment added', () => { - const handler = jest.fn(); + const uri = 'theBaseUri#existingFragment'; + const fragment = 'astring'; - const got = UriUtils.registerUriFragmentListener( handler ); + const got = UriUtils.addFragmentToUri( uri, fragment ); - expect( window.addEventListener ).toHaveBeenCalledTimes( 1 ); - expect( window.addEventListener ).toHaveBeenCalledWith( 'hashchange', handler ); + expect( got ).toBe( 'theBaseUri#existingFragment&astring' ); }); }); \ No newline at end of file diff --git a/framework/default/ortoo-core/default/lwc/uriUtils/uriUtils.js b/framework/default/ortoo-core/default/lwc/uriUtils/uriUtils.js index cf3d4dacb44..0c229972427 100644 --- a/framework/default/ortoo-core/default/lwc/uriUtils/uriUtils.js +++ b/framework/default/ortoo-core/default/lwc/uriUtils/uriUtils.js @@ -10,48 +10,26 @@ * Warning: Whilst it will encode Date and DateTime properties it may not do what you expect (due to timezones). * It is advised that you explicitly format the dates prior to using them in these functions. */ -const isObject = potentialObject => typeof potentialObject === 'object' && !Array.isArray( potentialObject ); - -const flatten = objectToFlatten => { - - const result = {}; - - for ( const propertyName in objectToFlatten ) { - if ( isObject( objectToFlatten[ propertyName ] ) ) { - const flattenedChild = flatten( objectToFlatten[ propertyName ] ); - for ( const childPropertyName in flattenedChild ) { - - result[ propertyName + '.' + childPropertyName ] = flattenedChild[ childPropertyName ]; - } - } else { - result[ propertyName ] = objectToFlatten[ propertyName ]; - } - } - return result; -}; - -const expand = ( objectToExpand, objectToAssignTo ) => { - - ! objectToAssignTo && ( objectToAssignTo = {} ); - - for ( const propertyName in objectToExpand ) { - - if ( propertyName.includes( '.' ) ) { - const parentPropertyName = propertyName.substring( 0, propertyName.indexOf( '.' ) ); - const childPropertyName = propertyName.substring( propertyName.indexOf( '.' ) + 1 ); +const addFragmentToUri = ( uri, object ) => { + const fragment = buildUriFragment( object ); + return fragment + ? ( uri.includes( '#' ) ) + ? uri + '&' + fragment + : uri + '#' + fragment + : uri; +} - const child = []; - child[ childPropertyName ] = objectToExpand[ propertyName ]; - !objectToAssignTo[ parentPropertyName ] && ( objectToAssignTo[ parentPropertyName ] = {} ); +const setUriFragmentObject = object => { + location.hash = buildUriFragment( object ); +} - expand( child, objectToAssignTo[ parentPropertyName ] ); +const getUriFragmentAsObject = () => { + return interpretUriFragment( location.hash ); +} - } else { - objectToAssignTo[ propertyName ] = objectToExpand[ propertyName ]; - } - } - return objectToAssignTo; +const registerUriFragmentListener = handler => { + window.addEventListener( 'hashchange', handler ); } const buildUriFragment = objectToHash => { @@ -89,35 +67,68 @@ const interpretUriFragment = hashToInterpret => { hashToInterpret .split( '&' ) .forEach( thisParameterPairString => { - const [rawParameterName,rawParameterValue] = thisParameterPairString.split( '=' ); - - let parameterValue = ''; - try { - parameterValue = JSON.parse( decodeURIComponent( rawParameterValue ) ); - } catch ( e ) { - console.error( 'Invalid parameter value in URI fragment' ); + if ( thisParameterPairString.includes( '=' ) ) { + const [rawParameterName,rawParameterValue] = thisParameterPairString.split( '=' ); + + let parameterValue = ''; + try { + parameterValue = JSON.parse( decodeURIComponent( rawParameterValue ) ); + flatObject[ decodeURIComponent( rawParameterName ) ] = parameterValue; + } catch ( e ) { + console.error( 'Invalid parameter value in URI fragment' ); + } } - flatObject[ decodeURIComponent( rawParameterName ) ] = parameterValue; }); return expand( flatObject ); } -const setUriFragmentObject = object => { - location.hash = buildUriFragment( object ); -} +const flatten = objectToFlatten => { -const getUriFragmentAsObject = () => { - return interpretUriFragment( location.hash ); -} + const result = {}; -const registerUriFragmentListener = handler => { - window.addEventListener( 'hashchange', handler ); + for ( const propertyName in objectToFlatten ) { + if ( isObject( objectToFlatten[ propertyName ] ) ) { + const flattenedChild = flatten( objectToFlatten[ propertyName ] ); + for ( const childPropertyName in flattenedChild ) { + + result[ propertyName + '.' + childPropertyName ] = flattenedChild[ childPropertyName ]; + } + } else { + result[ propertyName ] = objectToFlatten[ propertyName ]; + } + } + return result; +}; + +const expand = ( objectToExpand, objectToAssignTo ) => { + + ! objectToAssignTo && ( objectToAssignTo = {} ); + + for ( const propertyName in objectToExpand ) { + + if ( propertyName.includes( '.' ) ) { + + const parentPropertyName = propertyName.substring( 0, propertyName.indexOf( '.' ) ); + const childPropertyName = propertyName.substring( propertyName.indexOf( '.' ) + 1 ); + + const child = []; + child[ childPropertyName ] = objectToExpand[ propertyName ]; + !objectToAssignTo[ parentPropertyName ] && ( objectToAssignTo[ parentPropertyName ] = {} ); + + expand( child, objectToAssignTo[ parentPropertyName ] ); + + } else { + objectToAssignTo[ propertyName ] = objectToExpand[ propertyName ]; + } + } + return objectToAssignTo; } +const isObject = potentialObject => typeof potentialObject === 'object' && !Array.isArray( potentialObject ); + export default { - buildUriFragment : buildUriFragment, - interpretUriFragment : interpretUriFragment, + addFragmentToUri : addFragmentToUri, setUriFragmentObject : setUriFragmentObject, getUriFragmentAsObject : getUriFragmentAsObject, registerUriFragmentListener : registerUriFragmentListener,