@wordpress/url
Version:
WordPress URL utilities.
1,252 lines (1,076 loc) • 39.4 kB
JavaScript
/**
* Internal dependencies
*/
import {
addQueryArgs,
buildQueryString,
cleanForSlug,
filterURLForDisplay,
getAuthority,
getFilename,
getFragment,
getPath,
getProtocol,
getQueryArg,
getQueryArgs,
getQueryString,
hasQueryArg,
isEmail,
isURL,
isPhoneNumber,
isValidAuthority,
isValidFragment,
isValidPath,
isValidProtocol,
isValidQueryString,
normalizePath,
prependHTTP,
prependHTTPS,
removeQueryArgs,
safeDecodeURI,
} from '../';
import wptData from './fixtures/wpt-data';
describe( 'isURL', () => {
it.each( wptData.map( ( { input, failure } ) => [ input, !! failure ] ) )(
'%s',
( input, isFailure ) => {
expect( isURL( input ) ).toBe( ! isFailure );
}
);
} );
describe( 'isEmail', () => {
it.each( [
'simple@wordpress.org',
'very.common@wordpress.org',
'disposable.style.email.with+symbol@wordpress.org',
'other.email-with-hyphen@wordpress.org',
'fully-qualified-domain@wordpress.org',
'user.name+tag+sorting@wordpress.org',
'x@wordpress.org',
'wordpress-indeed@strange-wordpress.org',
'wordpress@s.wordpress',
'1234567890123456789012345678901234567890123456789012345678901234+x@wordpress.org',
] )(
'returns true when given things that look like an email: %s',
( email ) => {
expect( isEmail( email ) ).toBe( true );
}
);
it.each( [
'Abc.wordpress.org',
'A@b@c@wordpress.org',
'a"b(c)d,e:f;g<h>i[jk]l@wordpress.org',
'just"not"right@wordpress.org',
'this is"notallowed@wordpress.org',
'this still"not\\allowed@wordpress.org',
] )(
"returns false when given things that don't look like an email: %s",
( email ) => {
expect( isEmail( email ) ).toBe( false );
}
);
} );
describe( 'isPhoneNumber', () => {
it.each( [
'+1 (555) 123-4567',
'(555) 123-4567',
'555-123-4567',
'5551234567',
'+91 987 654 3210',
'123-456-7890',
'(123) 456-7890',
'123 456 7890',
'123.456.7890',
'+1 123 456 7890',
'1234567890',
'+44 791 112 3456',
'(123) 4567',
'+1 (123) 45678901',
'12-34-56',
'123456789012345',
'+12 3456789012345',
'tel:+1-123-456-7890',
] )(
'returns true when given things that look like a phone number: %s',
( phoneNumber ) => {
expect( isPhoneNumber( phoneNumber ) ).toBe( true );
}
);
it.each( [
'not a phone number',
'123',
'1234',
'12345',
'+91 123',
'abc-def-ghij',
'a123456789b',
'12-34-5',
'tel:911',
'tel:12345',
] )(
"returns false when given things that don't look like a phone number: %s",
( phoneNumber ) => {
expect( isPhoneNumber( phoneNumber ) ).toBe( false );
}
);
} );
describe( 'getProtocol', () => {
it( 'returns the protocol part of a URL', () => {
expect(
getProtocol(
'http://user:password@www.test-this.com:1020/test-path/file.extension#anchor?query=params&more'
)
).toBe( 'http:' );
expect(
getProtocol(
'https://user:password@www.test-this.com:1020/test-path/file.extension#anchor?query=params&more'
)
).toBe( 'https:' );
expect( getProtocol( 'https://wordpress.org#test' ) ).toBe( 'https:' );
expect( getProtocol( 'https://wordpress.org/' ) ).toBe( 'https:' );
expect( getProtocol( 'https://wordpress.org?test' ) ).toBe( 'https:' );
expect( getProtocol( 'https://localhost:8080' ) ).toBe( 'https:' );
expect( getProtocol( 'tel:1234' ) ).toBe( 'tel:' );
expect( getProtocol( 'blob:data' ) ).toBe( 'blob:' );
expect( getProtocol( 'file:///folder/file.txt' ) ).toBe( 'file:' );
} );
it( 'returns undefined when the provided value does not contain a URL protocol', () => {
expect( getProtocol( '' ) ).toBeUndefined();
expect( getProtocol( 'https' ) ).toBeUndefined();
expect( getProtocol( 'example.com' ) ).toBeUndefined();
expect( getProtocol( ' https:// ' ) ).toBeUndefined();
} );
} );
describe( 'isValidProtocol', () => {
it( 'returns true if the protocol is valid', () => {
expect( isValidProtocol( 'tel:' ) ).toBe( true );
expect( isValidProtocol( 'http:' ) ).toBe( true );
expect( isValidProtocol( 'https:' ) ).toBe( true );
expect( isValidProtocol( 'file:' ) ).toBe( true );
expect( isValidProtocol( 'test.protocol:' ) ).toBe( true );
expect( isValidProtocol( 'test-protocol:' ) ).toBe( true );
expect( isValidProtocol( 'test+protocol:' ) ).toBe( true );
expect( isValidProtocol( 'test+protocol123:' ) ).toBe( true );
} );
it( 'returns false if the protocol is invalid', () => {
expect( isValidProtocol() ).toBe( false );
expect( isValidProtocol( '' ) ).toBe( false );
expect( isValidProtocol( ' http: ' ) ).toBe( false );
expect( isValidProtocol( 'http :' ) ).toBe( false );
expect( isValidProtocol( 'http: //' ) ).toBe( false );
expect( isValidProtocol( 'test protocol:' ) ).toBe( false );
expect( isValidProtocol( 'test#protocol:' ) ).toBe( false );
expect( isValidProtocol( 'test?protocol:' ) ).toBe( false );
expect( isValidProtocol( '123test+protocol:' ) ).toBe( false );
} );
} );
describe( 'getAuthority', () => {
it( 'returns the authority part of a URL', () => {
expect(
getAuthority(
'https://user:password@www.test-this.com:1020/test-path/file.extension#anchor?query=params&more'
)
).toBe( 'user:password@www.test-this.com:1020' );
expect(
getAuthority(
'http://user:password@www.test-this.com:1020/test-path/file.extension#anchor?query=params&more'
)
).toBe( 'user:password@www.test-this.com:1020' );
expect( getAuthority( 'https://wordpress.org#test' ) ).toBe(
'wordpress.org'
);
expect( getAuthority( 'https://wordpress.org/' ) ).toBe(
'wordpress.org'
);
expect( getAuthority( 'https://wordpress.org?test' ) ).toBe(
'wordpress.org'
);
expect( getAuthority( 'https://localhost:8080' ) ).toBe(
'localhost:8080'
);
} );
it( 'returns undefined when the provided value does not contain a URL authority', () => {
expect( getAuthority( '' ) ).toBeUndefined();
expect( getAuthority( 'https://' ) ).toBeUndefined();
expect( getAuthority( 'https:///' ) ).toBeUndefined();
expect( getAuthority( 'https://#' ) ).toBeUndefined();
expect( getAuthority( 'https://?' ) ).toBeUndefined();
expect( getAuthority( 'example.com' ) ).toBeUndefined();
expect( getAuthority( 'https://#?hello' ) ).toBeUndefined();
} );
} );
describe( 'isValidAuthority', () => {
it( 'returns true if the authority is valid', () => {
expect(
isValidAuthority( 'user:password@www.test-this.com:1020' )
).toBe( true );
expect( isValidAuthority( 'wordpress.org' ) ).toBe( true );
expect( isValidAuthority( 'localhost' ) ).toBe( true );
expect( isValidAuthority( 'localhost:8080' ) ).toBe( true );
expect( isValidAuthority( 'www.the-best-website.co.uk' ) ).toBe( true );
expect( isValidAuthority( 'WWW.VERYLOUD.COM' ) ).toBe( true );
} );
it( 'returns false if the authority is invalid', () => {
expect( isValidAuthority() ).toBe( false );
expect( isValidAuthority( '' ) ).toBe( false );
expect( isValidAuthority( 'inv alid.website.com' ) ).toBe( false );
expect( isValidAuthority( 'test#.com' ) ).toBe( false );
expect( isValidAuthority( 'test?.com' ) ).toBe( false );
} );
} );
describe( 'getPath', () => {
it( 'returns the path part of a URL', () => {
expect(
getPath(
'https://user:password@www.test-this.com:1020/test-path/file.extension#anchor?query=params&more'
)
).toBe( 'test-path/file.extension' );
expect(
getPath(
'http://user:password@www.test-this.com:1020/test-path/file.extension#anchor?query=params&more'
)
).toBe( 'test-path/file.extension' );
expect( getPath( 'https://wordpress.org/test-path#anchor' ) ).toBe(
'test-path'
);
expect( getPath( 'https://wordpress.org/test-path?query' ) ).toBe(
'test-path'
);
expect(
getPath(
'https://www.google.com/search?source=hp&ei=tP7kW8-_FoK89QORoa2QBQ&q=test+url&oq=test+url&gs_l=psy-ab.3..0l10'
)
).toBe( 'search' );
expect( getPath( 'https://wordpress.org/this%20is%20a%20test' ) ).toBe(
'this%20is%20a%20test'
);
expect(
getPath( 'https://wordpress.org/this%20is%20a%20test?query' )
).toBe( 'this%20is%20a%20test' );
} );
it( 'returns undefined when the provided value does not contain a URL path', () => {
expect( getPath() ).toBeUndefined();
expect( getPath( '' ) ).toBeUndefined();
expect( getPath( 'https://wordpress.org#test' ) ).toBeUndefined();
expect( getPath( 'https://wordpress.org/' ) ).toBeUndefined();
expect( getPath( 'https://wordpress.org?test' ) ).toBeUndefined();
expect( getPath( 'https://localhost:8080' ) ).toBeUndefined();
expect( getPath( 'https://' ) ).toBeUndefined();
expect( getPath( 'https:///test' ) ).toBeUndefined();
expect( getPath( 'https://#' ) ).toBeUndefined();
expect( getPath( 'https://?' ) ).toBeUndefined();
expect( getPath( 'example.com' ) ).toBeUndefined();
expect( getPath( 'https://#?hello' ) ).toBeUndefined();
expect( getPath( 'https' ) ).toBeUndefined();
} );
} );
describe( 'isValidPath', () => {
it( 'returns true if the path is valid', () => {
expect( isValidPath( 'test-path/file.extension' ) ).toBe( true );
expect( isValidPath( '/absolute/path' ) ).toBe( true );
expect( isValidPath( 'relative/path' ) ).toBe( true );
expect( isValidPath( 'slightly/longer/path/' ) ).toBe( true );
expect( isValidPath( 'path/with/percent%20encoding' ) ).toBe( true );
expect( isValidPath( '/' ) ).toBe( true );
} );
it( 'returns false if the path is invalid', () => {
expect( isValidPath() ).toBe( false );
expect( isValidPath( '' ) ).toBe( false );
expect( isValidPath( 'path /with/spaces' ) ).toBe( false );
expect( isValidPath( 'path/with/number/symbol#' ) ).toBe( false );
expect( isValidPath( 'path/with/question/mark?' ) ).toBe( false );
expect( isValidPath( ' path/with/padding ' ) ).toBe( false );
} );
} );
describe( 'getFilename', () => {
it.each( [
[ 'https://wordpress.org/image.jpg', 'image.jpg' ],
[ 'https://wordpress.org/image.jpg?query=test', 'image.jpg' ],
[ 'https://wordpress.org/image.jpg#anchor', 'image.jpg' ],
[ 'http://localhost:8080/a/path/to/an/image.jpg', 'image.jpg' ],
[ '/path/to/an/image.jpg', 'image.jpg' ],
[ 'path/to/an/image.jpg', 'image.jpg' ],
[ '/image.jpg', 'image.jpg' ],
[ 'https://wordpress.org/file.pdf', 'file.pdf' ],
[ 'https://wordpress.org/image.webp?query=test', 'image.webp' ],
[ 'https://wordpress.org/video.mov#anchor', 'video.mov' ],
[ 'http://localhost:8080/a/path/to/audio.mp3', 'audio.mp3' ],
] )( 'returns the filename part of the URL: %s', ( url, filename ) => {
expect( getFilename( url ) ).toBe( filename );
} );
it( 'returns undefined when the provided value does not contain a filename', () => {
expect( getFilename( 'http://localhost:8080/' ) ).toBe( undefined );
expect( getFilename( 'http://localhost:8080/a/path/' ) ).toBe(
undefined
);
expect( getFilename( 'http://localhost:8080/?query=test' ) ).toBe(
undefined
);
expect( getFilename( 'http://localhost:8080/#anchor' ) ).toBe(
undefined
);
expect( getFilename( 'a/path/' ) ).toBe( undefined );
expect( getFilename( '/' ) ).toBe( undefined );
expect( getFilename( undefined ) ).toBe( undefined );
expect( getFilename( null ) ).toBe( undefined );
} );
} );
describe( 'getQueryString', () => {
it( 'returns the query string of a URL', () => {
expect(
getQueryString(
'http://user:password@www.test-this.com:1020/test-path/file.extension?query=params&more#anchor'
)
).toBe( 'query=params&more' );
expect(
getQueryString( 'https://wordpress.org/test-path?query' )
).toBe( 'query' );
expect(
getQueryString(
'https://www.google.com/search?source=hp&ei=tP7kW8-_FoK89QORoa2QBQ&q=test+url&oq=test+url&gs_l=psy-ab.3..0l10'
)
).toBe(
'source=hp&ei=tP7kW8-_FoK89QORoa2QBQ&q=test+url&oq=test+url&gs_l=psy-ab.3..0l10'
);
expect(
getQueryString( 'https://wordpress.org/this%20is%20a%20test?query' )
).toBe( 'query' );
expect(
getQueryString(
'https://wordpress.org/test?query=something%20with%20spaces'
)
).toBe( 'query=something%20with%20spaces' );
expect(
getQueryString(
'https://andalouses.example/beach?foo[]=bar&foo[]=baz'
)
).toBe( 'foo[]=bar&foo[]=baz' );
expect(
getQueryString( 'https://example.com?foo[]=bar&foo[]=baz' )
).toBe( 'foo[]=bar&foo[]=baz' );
expect(
getQueryString( 'https://example.com?foo=bar&foo=baz?test' )
).toBe( 'foo=bar&foo=baz?test' );
} );
it( 'returns the query string of a path', () => {
expect( getQueryString( '/wp-json/wp/v2/posts?type=page' ) ).toBe(
'type=page'
);
expect( getQueryString( '/wp-json/wp/v2/posts' ) ).toBeUndefined();
} );
it( 'returns undefined when the provided does not contain a url query string', () => {
expect( getQueryString( '' ) ).toBeUndefined();
expect(
getQueryString(
'https://user:password@www.test-this.com:1020/test-path/file.extension#anchor?query=params&more'
)
).toBeUndefined();
expect(
getQueryString( 'https://wordpress.org/test-path#anchor' )
).toBeUndefined();
expect(
getQueryString( 'https://wordpress.org/this%20is%20a%20test' )
).toBeUndefined();
expect(
getQueryString( 'https://wordpress.org#test' )
).toBeUndefined();
expect( getQueryString( 'https://wordpress.org/' ) ).toBeUndefined();
expect( getQueryString( 'https://localhost:8080' ) ).toBeUndefined();
expect( getQueryString( 'invalid' ) ).toBeUndefined();
expect(
getQueryString( 'https://example.com/empty?' )
).toBeUndefined();
} );
} );
describe( 'buildQueryString', () => {
it( 'builds simple strings', () => {
const data = {
foo: 'bar',
baz: 'boom',
cow: 'milk',
php: 'hypertext processor',
};
expect( buildQueryString( data ) ).toBe(
'foo=bar&baz=boom&cow=milk&php=hypertext%20processor'
);
} );
it( 'builds complex data', () => {
const data = {
user: {
name: 'Bob Smith',
age: 47,
sex: 'M',
dob: '5/12/1956',
},
pastimes: [ 'golf', 'opera', 'poker', 'rap' ],
children: {
bobby: { age: 12, sex: 'M' },
sally: { age: 8, sex: 'F' },
},
};
expect( buildQueryString( data ) ).toBe(
'user%5Bname%5D=Bob%20Smith&user%5Bage%5D=47&user%5Bsex%5D=M&user%5Bdob%5D=5%2F12%2F1956&pastimes%5B0%5D=golf&pastimes%5B1%5D=opera&pastimes%5B2%5D=poker&pastimes%5B3%5D=rap&children%5Bbobby%5D%5Bage%5D=12&children%5Bbobby%5D%5Bsex%5D=M&children%5Bsally%5D%5Bage%5D=8&children%5Bsally%5D%5Bsex%5D=F'
);
} );
it( 'builds falsey values', () => {
const data = {
empty: '',
null: null,
undefined,
zero: 0,
};
expect( buildQueryString( data ) ).toBe( 'empty=&null=&zero=0' );
} );
it( 'builds an empty object as an empty string', () => {
expect( buildQueryString( {} ) ).toBe( '' );
} );
} );
describe( 'isValidQueryString', () => {
it( 'returns true if the query string is valid', () => {
expect( isValidQueryString( 'test' ) ).toBe( true );
expect( isValidQueryString( 'test=true' ) ).toBe( true );
expect( isValidQueryString( 'test=true&another' ) ).toBe( true );
expect( isValidQueryString( 'test=true&another=false' ) ).toBe( true );
expect( isValidQueryString( 'test[]=true&another[]=false' ) ).toBe(
true
);
expect( isValidQueryString( 'query=something%20with%20spaces' ) ).toBe(
true
);
expect(
isValidQueryString(
'source=hp&ei=tP7kW8-_FoK89QORoa2QBQ&q=test+url&oq=test+url&gs_l=psy-ab.3..0l10'
)
).toBe( true );
} );
it( 'returns false if the query string is invalid', () => {
expect( isValidQueryString() ).toBe( false );
expect( isValidQueryString( '' ) ).toBe( false );
expect( isValidQueryString( '?test=false' ) ).toBe( false );
expect( isValidQueryString( ' test=false ' ) ).toBe( false );
expect( isValidQueryString( 'test = false' ) ).toBe( false );
expect( isValidQueryString( 'test=f?alse' ) ).toBe( false );
expect( isValidQueryString( 'test=f#alse' ) ).toBe( false );
expect( isValidQueryString( 'test=f/alse' ) ).toBe( false );
expect( isValidQueryString( 'test=f?alse' ) ).toBe( false );
expect( isValidQueryString( '/test=false' ) ).toBe( false );
expect( isValidQueryString( 'test=false/' ) ).toBe( false );
} );
} );
describe( 'getPathAndQueryString', () => {
beforeAll( jest.resetModules );
afterAll( jest.resetModules );
it( 'combines the results of `getPath` and `getQueryString`', () => {
jest.doMock( '../get-path', () => ( {
getPath( { path } = {} ) {
return path;
},
} ) );
jest.doMock( '../get-query-string', () => ( {
getQueryString( { queryString } = {} ) {
return queryString;
},
} ) );
const {
getPathAndQueryString,
} = require( '../get-path-and-query-string' );
expect(
getPathAndQueryString( {
path: 'path',
queryString: 'queryString',
} )
).toBe( '/path?queryString' );
expect(
getPathAndQueryString( {
queryString: 'queryString',
} )
).toBe( '/?queryString' );
expect(
getPathAndQueryString( {
path: 'path',
} )
).toBe( '/path' );
expect( getPathAndQueryString() ).toBe( '/' );
} );
} );
describe( 'getFragment', () => {
it( 'returns the fragment of a URL', () => {
expect(
getFragment(
'https://user:password@www.test-this.com:1020/test-path/file.extension#fragment?query=params&more'
)
).toBe( '#fragment' );
expect(
getFragment(
'http://user:password@www.test-this.com:1020/test-path/file.extension?query=params&more#fragment'
)
).toBe( '#fragment' );
expect( getFragment( 'relative/url/#fragment' ) ).toBe( '#fragment' );
expect( getFragment( '/absolute/url/#fragment' ) ).toBe( '#fragment' );
} );
it( 'returns undefined when the provided does not contain a url fragment', () => {
expect( getFragment( '' ) ).toBeUndefined();
expect(
getFragment( 'https://wordpress.org/test-path?query' )
).toBeUndefined();
expect(
getFragment( 'https://wordpress.org/test-path' )
).toBeUndefined();
expect(
getFragment( 'https://wordpress.org/this%20is%20a%20test' )
).toBeUndefined();
expect(
getFragment(
'https://www.google.com/search?source=hp&ei=tP7kW8-_FoK89QORoa2QBQ&q=test+url&oq=test+url&gs_l=psy-ab.3..0l10'
)
).toBeUndefined();
expect( getFragment( 'https://wordpress.org' ) ).toBeUndefined();
expect( getFragment( 'https://localhost:8080' ) ).toBeUndefined();
expect( getFragment( 'https://' ) ).toBeUndefined();
expect( getFragment( 'https:///test' ) ).toBeUndefined();
expect( getFragment( 'https://?' ) ).toBeUndefined();
expect( getFragment( 'example.com' ) ).toBeUndefined();
} );
} );
describe( 'isValidFragment', () => {
it( 'returns true if the fragment is valid', () => {
expect( isValidFragment( '#' ) ).toBe( true );
expect( isValidFragment( '#yesitis' ) ).toBe( true );
expect( isValidFragment( '#yes_it_is' ) ).toBe( true );
expect( isValidFragment( '#yes~it~is' ) ).toBe( true );
expect( isValidFragment( '#yes-it-is' ) ).toBe( true );
} );
it( 'returns false if the fragment is invalid', () => {
expect( isValidFragment( '' ) ).toBe( false );
expect( isValidFragment( ' #no-it-isnt ' ) ).toBe( false );
expect( isValidFragment( '#no-it-isnt#' ) ).toBe( false );
expect( isValidFragment( '#no-it-#isnt' ) ).toBe( false );
expect( isValidFragment( '#no-it-isnt?' ) ).toBe( false );
expect( isValidFragment( '#no-it isnt' ) ).toBe( false );
expect( isValidFragment( '/#no-it-isnt' ) ).toBe( false );
expect( isValidFragment( '#no-it-isnt/' ) ).toBe( false );
} );
} );
describe( 'addQueryArgs', () => {
it( 'should append args to a URL without query string', () => {
const url = 'https://andalouses.example/beach';
const args = { sun: 'true', sand: 'false' };
expect( addQueryArgs( url, args ) ).toBe(
'https://andalouses.example/beach?sun=true&sand=false'
);
} );
it( 'should append args to a URL with query string', () => {
const url = 'https://andalouses.example/beach?night=false';
const args = { sun: 'true', sand: 'false' };
expect( addQueryArgs( url, args ) ).toBe(
'https://andalouses.example/beach?night=false&sun=true&sand=false'
);
} );
it( 'should update args to an URL with conflicting query string', () => {
const url =
'https://andalouses.example/beach?night=false&sun=false&sand=true';
const args = { sun: 'true', sand: 'false' };
expect( addQueryArgs( url, args ) ).toBe(
'https://andalouses.example/beach?night=false&sun=true&sand=false'
);
} );
it( 'should update args to an URL with array parameters', () => {
const url = 'https://andalouses.example/beach?time[]=10&time[]=11';
const args = { beach: [ 'sand', 'rock' ] };
expect( safeDecodeURI( addQueryArgs( url, args ) ) ).toBe(
'https://andalouses.example/beach?time[0]=10&time[1]=11&beach[0]=sand&beach[1]=rock'
);
} );
it( 'should disregard keys with undefined values', () => {
const url = 'https://andalouses.example/beach';
const args = { sun: 'true', sand: undefined };
expect( addQueryArgs( url, args ) ).toBe(
'https://andalouses.example/beach?sun=true'
);
} );
it( 'should encode spaces by RFC 3986', () => {
const url = 'https://andalouses.example/beach';
const args = { activity: 'fun in the sun' };
expect( addQueryArgs( url, args ) ).toBe(
'https://andalouses.example/beach?activity=fun%20in%20the%20sun'
);
} );
it( 'should return only querystring when passed undefined url', () => {
const url = undefined;
const args = { sun: 'true' };
expect( addQueryArgs( url, args ) ).toBe( '?sun=true' );
} );
it( 'should add query args before the url fragment', () => {
const url = 'https://andalouses.example/beach/#fragment';
const args = { sun: 'true' };
expect( addQueryArgs( url, args ) ).toBe(
'https://andalouses.example/beach/?sun=true#fragment'
);
} );
it( 'should return URL argument unaffected if no query arguments to append', () => {
[ '', 'https://example.com', 'https://example.com?' ].forEach(
( url ) => {
[ undefined, {} ].forEach( ( args ) => {
expect( addQueryArgs( url, args ) ).toBe( url );
} );
}
);
} );
} );
describe( 'getQueryArgs', () => {
it( 'should parse simple query arguments', () => {
const url = 'https://andalouses.example/beach?foo=bar&baz=quux';
expect( getQueryArgs( url ) ).toEqual( {
foo: 'bar',
baz: 'quux',
} );
} );
it( 'should accumulate array of values', () => {
const url =
'https://andalouses.example/beach?foo[]=zero&foo[]=one&foo[]=two';
expect( getQueryArgs( url ) ).toEqual( {
foo: [ 'zero', 'one', 'two' ],
} );
} );
it( 'should accumulate keyed array of values', () => {
const url =
'https://andalouses.example/beach?foo[1]=one&foo[0]=zero&foo[]=two';
expect( getQueryArgs( url ) ).toEqual( {
foo: [ 'zero', 'one', 'two' ],
} );
} );
it( 'should accumulate object of values', () => {
const url =
'https://andalouses.example/beach?foo[zero]=0&foo[one]=1&foo[]=empty';
expect( getQueryArgs( url ) ).toEqual( {
foo: {
'': 'empty',
zero: '0',
one: '1',
},
} );
} );
it( 'normalizes mixed numeric and named keys', () => {
const url = 'https://andalouses.example/beach?foo[0]=0&foo[one]=1';
expect( getQueryArgs( url ) ).toEqual( {
foo: {
0: '0',
one: '1',
},
} );
} );
it( 'should return empty object for URL without querystring', () => {
const urlWithoutQuerystring = 'https://andalouses.example/beach';
const urlWithEmptyQuerystring = 'https://andalouses.example/beach?';
const invalidURL = 'example';
expect( getQueryArgs( invalidURL ) ).toEqual( {} );
expect( getQueryArgs( urlWithoutQuerystring ) ).toEqual( {} );
expect( getQueryArgs( urlWithEmptyQuerystring ) ).toEqual( {} );
} );
it( 'should gracefully handle empty keys and values', () => {
const url = 'https://andalouses.example/beach?&foo';
expect( getQueryArgs( url ) ).toEqual( {
foo: '',
} );
} );
describe( 'reverses buildQueryString', () => {
it( 'unbuilds simple strings', () => {
const data = {
foo: 'bar',
baz: 'boom',
cow: 'milk',
php: 'hypertext processor',
};
expect(
getQueryArgs(
'https://example.com/?foo=bar&baz=boom&cow=milk&php=hypertext%20processor'
)
).toEqual( data );
} );
it( 'unbuilds complex data, with stringified values', () => {
const data = {
user: {
name: 'Bob Smith',
age: '47',
sex: 'M',
dob: '5/12/1956',
},
pastimes: [ 'golf', 'opera', 'poker', 'rap' ],
children: {
bobby: { age: '12', sex: 'M' },
sally: { age: '8', sex: 'F' },
},
};
expect(
getQueryArgs(
'https://example.com/?user%5Bname%5D=Bob%20Smith&user%5Bage%5D=47&user%5Bsex%5D=M&user%5Bdob%5D=5%2F12%2F1956&pastimes%5B0%5D=golf&pastimes%5B1%5D=opera&pastimes%5B2%5D=poker&pastimes%5B3%5D=rap&children%5Bbobby%5D%5Bage%5D=12&children%5Bbobby%5D%5Bsex%5D=M&children%5Bsally%5D%5Bage%5D=8&children%5Bsally%5D%5Bsex%5D=F'
)
).toEqual( data );
} );
it( 'should not blow up on malformed params', () => {
const url = 'https://andalouses.example/beach?foo=bar&baz=%E0%A4%A';
expect( () => getQueryArgs( url ) ).not.toThrow();
expect( getQueryArgs( url ) ).toEqual( {
baz: '%E0%A4%A',
foo: 'bar',
} );
} );
} );
} );
describe( 'getQueryArg', () => {
it( 'should get the value of an existing query arg', () => {
const url = 'https://andalouses.example/beach?foo=bar&bar=baz';
expect( getQueryArg( url, 'foo' ) ).toBe( 'bar' );
} );
it( 'should not return a value of an unknown query arg', () => {
const url = 'https://andalouses.example/beach?foo=bar&bar=baz';
expect( getQueryArg( url, 'baz' ) ).toBeUndefined();
} );
it( 'should not return what looks like a query arg after the url fragment', () => {
const url = 'https://andalouses.example/beach#fragment?foo=bar&bar=baz';
expect( getQueryArg( url, 'foo' ) ).toBeUndefined();
} );
it( 'should get the value of an array query arg', () => {
const url = 'https://andalouses.example/beach?foo[]=bar&foo[]=baz';
expect( getQueryArg( url, 'foo' ) ).toEqual( [ 'bar', 'baz' ] );
} );
it( 'continues to work when an anchor follows the query string', () => {
const url = 'https://andalouses.example/beach?foo=bar&bar=baz#foo';
expect( getQueryArg( url, 'foo' ) ).toEqual( 'bar' );
expect( getQueryArg( url, 'bar' ) ).toEqual( 'baz' );
} );
} );
describe( 'hasQueryArg', () => {
it( 'should return true for an existing query arg', () => {
const url = 'https://andalouses.example/beach?foo=bar&bar=baz';
expect( hasQueryArg( url, 'foo' ) ).toBeTruthy();
} );
it( 'should return false for an unknown query arg', () => {
const url = 'https://andalouses.example/beach?foo=bar&bar=baz';
expect( hasQueryArg( url, 'baz' ) ).toBeFalsy();
} );
it( 'should return false if the query arg is after url fragment', () => {
const url = 'https://andalouses.example/beach#fragment?foo=bar&bar=baz';
expect( hasQueryArg( url, 'foo' ) ).toBeFalsy();
} );
it( 'should return true for an array query arg', () => {
const url = 'https://andalouses.example/beach?foo[]=bar&foo[]=baz';
expect( hasQueryArg( url, 'foo' ) ).toBeTruthy();
} );
} );
describe( 'removeQueryArgs', () => {
it( 'should not change URL without a querystring', () => {
const url = 'https://andalouses.example/beach';
expect( removeQueryArgs( url, 'baz', 'test' ) ).toEqual( url );
} );
it( 'should not change URL not containing query args', () => {
const url = 'https://andalouses.example/beach?foo=bar&bar=baz';
expect( removeQueryArgs( url, 'baz', 'test' ) ).toEqual( url );
} );
it( 'should remove existing query args', () => {
const url = 'https://andalouses.example/beach?foo=bar&baz=foo&bar=baz';
expect( removeQueryArgs( url, 'foo', 'bar' ) ).toEqual(
'https://andalouses.example/beach?baz=foo'
);
} );
it( 'should not leave ? char after removing all query args', () => {
const url = 'https://andalouses.example/beach?foo=bar&bar=baz';
expect( removeQueryArgs( url, 'foo', 'bar' ) ).toEqual(
'https://andalouses.example/beach'
);
} );
it( 'should remove array query arg', () => {
const url =
'https://andalouses.example/beach?foo[]=bar&foo[]=baz&bar=foobar';
expect( removeQueryArgs( url, 'foo' ) ).toEqual(
'https://andalouses.example/beach?bar=foobar'
);
} );
it( 'should not remove the url fragment', () => {
const url =
'https://andalouses.example/beach?foo=bar¶m=value#fragment';
expect( removeQueryArgs( url, 'foo' ) ).toEqual(
'https://andalouses.example/beach?param=value#fragment'
);
} );
it( 'should not remove what looks like a query arg after url fragment', () => {
const url = 'https://andalouses.example/beach#fragment?foo=bar';
expect( removeQueryArgs( url, 'foo' ) ).toEqual(
'https://andalouses.example/beach#fragment?foo=bar'
);
} );
} );
describe( 'prependHTTP', () => {
it( 'should prepend http to a domain', () => {
const url = 'wordpress.org';
expect( prependHTTP( url ) ).toBe( 'http://' + url );
} );
it( 'shouldn’t prepend http to an email', () => {
const url = 'foo@wordpress.org';
expect( prependHTTP( url ) ).toBe( url );
} );
it( 'shouldn’t prepend http to an absolute URL', () => {
const url = '/wordpress';
expect( prependHTTP( url ) ).toBe( url );
} );
it( 'shouldn’t prepend http to a relative URL', () => {
const url = './wordpress';
expect( prependHTTP( url ) ).toBe( url );
} );
it( 'shouldn’t prepend http to an anchor URL', () => {
const url = '#wordpress';
expect( prependHTTP( url ) ).toBe( url );
} );
it( 'shouldn’t prepend http to a URL that already has http', () => {
const url = 'http://wordpress.org';
expect( prependHTTP( url ) ).toBe( url );
} );
it( 'shouldn’t prepend http to a URL that already has https', () => {
const url = 'https://wordpress.org';
expect( prependHTTP( url ) ).toBe( url );
} );
it( 'shouldn’t prepend http to a URL that already has ftp', () => {
const url = 'ftp://wordpress.org';
expect( prependHTTP( url ) ).toBe( url );
} );
it( 'shouldn’t prepend http to a URL that already has mailto', () => {
const url = 'mailto:foo@wordpress.org';
expect( prependHTTP( url ) ).toBe( url );
} );
it( 'should remove leading whitespace before prepending HTTP', () => {
const url = ' wordpress.org';
expect( prependHTTP( url ) ).toBe( 'http://wordpress.org' );
} );
it( 'should not have trailing whitespaces', () => {
const url = 'wordpress.org ';
expect( prependHTTP( url ) ).toBe( 'http://wordpress.org' );
} );
} );
describe( 'prependHTTPS', () => {
it( 'should prepend https to a domain', () => {
const url = 'wordpress.org';
expect( prependHTTPS( url ) ).toBe( 'https://' + url );
} );
it( 'should not prepend https to an email', () => {
const url = 'foo@wordpress.org';
expect( prependHTTPS( url ) ).toBe( url );
} );
it( 'should not prepend https to an absolute URL', () => {
const url = '/wordpress';
expect( prependHTTPS( url ) ).toBe( url );
} );
it( 'should not prepend https to a relative URL', () => {
const url = './wordpress';
expect( prependHTTPS( url ) ).toBe( url );
} );
it( 'should not prepend https to an anchor URL', () => {
const url = '#wordpress';
expect( prependHTTPS( url ) ).toBe( url );
} );
it( 'should not prepend https to a URL that already has https', () => {
const url = 'https://wordpress.org';
expect( prependHTTPS( url ) ).toBe( url );
} );
it( 'should not prepend https to a URL that already has http', () => {
const url = 'http://wordpress.org';
expect( prependHTTPS( url ) ).toBe( url );
} );
it( 'should not prepend https to a URL that already has ftp', () => {
const url = 'ftp://wordpress.org';
expect( prependHTTPS( url ) ).toBe( url );
} );
it( 'should not prepend https to a URL that already has mailto', () => {
const url = 'mailto:foo@wordpress.org';
expect( prependHTTPS( url ) ).toBe( url );
} );
it( 'should remove leading whitespace before prepending HTTPs', () => {
const url = ' wordpress.org';
expect( prependHTTPS( url ) ).toBe( 'https://wordpress.org' );
} );
it( 'should not have trailing whitespaces', () => {
const url = 'wordpress.org ';
expect( prependHTTPS( url ) ).toBe( 'https://wordpress.org' );
} );
} );
it( 'should prepend https to a domain with an anchor', () => {
const url = 'wordpress.org#something';
expect( prependHTTPS( url ) ).toBe( 'https://' + url );
} );
it( 'should prepend https to a domain with path', () => {
const url = 'wordpress.org/some/thing';
expect( prependHTTPS( url ) ).toBe( 'https://' + url );
} );
it( 'should prepend https to a domain with query arguments', () => {
const url = 'wordpress.org?foo=bar';
expect( prependHTTPS( url ) ).toBe( 'https://' + url );
} );
describe( 'safeDecodeURI', () => {
it( 'should decode URI if formed well', () => {
const encoded = 'https://mozilla.org/?x=%D1%88%D0%B5%D0%BB%D0%BB%D1%8B';
const decoded = 'https://mozilla.org/?x=шеллы';
expect( safeDecodeURI( encoded ) ).toBe( decoded );
} );
it( 'should return URI if malformed', () => {
const malformed = '%1';
expect( safeDecodeURI( malformed ) ).toBe( malformed );
} );
} );
describe( 'filterURLForDisplay', () => {
it( 'should return an empty string if the url is empty or falsy', () => {
let url = filterURLForDisplay( '' );
expect( url ).toBe( '' );
url = filterURLForDisplay( null );
expect( url ).toBe( '' );
} );
it( 'should remove protocol', () => {
let url = filterURLForDisplay( 'http://wordpress.org' );
expect( url ).toBe( 'wordpress.org' );
url = filterURLForDisplay( 'https://wordpress.org' );
expect( url ).toBe( 'wordpress.org' );
url = filterURLForDisplay( 'file:///folder/file.txt' );
expect( url ).toBe( '/folder/file.txt' );
url = filterURLForDisplay( 'tel:0123456789' );
expect( url ).toBe( '0123456789' );
url = filterURLForDisplay( 'blob:data' );
expect( url ).toBe( 'data' );
} );
it( 'should remove www subdomain', () => {
const url = filterURLForDisplay( 'http://www.wordpress.org' );
expect( url ).toBe( 'wordpress.org' );
} );
it( 'should remove single trailing slash', () => {
const url = filterURLForDisplay( 'http://www.wordpress.org/' );
expect( url ).toBe( 'wordpress.org' );
} );
it( 'should preserve slashes where the url has multiple in the path', () => {
const url = filterURLForDisplay(
'http://www.wordpress.org/something/'
);
expect( url ).toBe( 'wordpress.org/something/' );
} );
it( 'should preserve slash where the url has path after the initial slash', () => {
const url = filterURLForDisplay( 'http://www.wordpress.org/something' );
expect( url ).toBe( 'wordpress.org/something' );
} );
it( 'should preserve the original url if no argument max length', () => {
const url = filterURLForDisplay(
'http://www.wordpress.org/wp-content/uploads/myimage.jpg'
);
expect( url ).toBe( 'wordpress.org/wp-content/uploads/myimage.jpg' );
} );
it( 'should preserve the original url if the url is short enough', () => {
const url = filterURLForDisplay(
'http://www.wordpress.org/ig.jpg',
20
);
expect( url ).toBe( 'wordpress.org/ig.jpg' );
} );
it( 'should return ellipsis, upper level pieces url, and filename when the url is long enough but filename is short enough', () => {
const url = filterURLForDisplay(
'http://www.wordpress.org/wp-content/uploads/myimage.jpg',
20
);
expect( url ).toBe( '…/uploads/myimage.jpg' );
} );
it( 'should return filename split by ellipsis plus three characters when filename is long enough', () => {
const url = filterURLForDisplay(
'http://www.wordpress.org/wp-content/uploads/superlongtitlewithextension.jpeg',
20
);
expect( url ).toBe( 'superlongti…ion.jpeg' );
} );
it( 'should remove query arguments', () => {
const url = filterURLForDisplay(
'http://www.wordpress.org/wp-content/uploads/myimage.jpeg?query_args=a',
20
);
expect( url ).toBe( '…uploads/myimage.jpeg' );
} );
it( 'should preserve the original url when it is not a file', () => {
const url = filterURLForDisplay(
'http://www.wordpress.org/wp-content/url/',
20
);
expect( url ).toBe( 'wordpress.org/wp-content/url/' );
} );
it( 'should return file split by ellipsis when the file name has multiple periods', () => {
const url = filterURLForDisplay(
'http://www.wordpress.org/wp-content/uploads/filename.2020.12.20.png',
20
);
expect( url ).toBe( 'filename.202….20.png' );
} );
} );
describe( 'cleanForSlug', () => {
it( 'Should return string prepared for use as url slug', () => {
expect( cleanForSlug( '/Is th@t Déjà_vu? ' ) ).toBe( 'is-tht-deja_vu' );
} );
it( 'Should return an empty string for missing argument', () => {
expect( cleanForSlug() ).toBe( '' );
} );
it( 'Should return an empty string for falsy argument', () => {
expect( cleanForSlug( null ) ).toBe( '' );
} );
it( 'Should not allow characters used internally in rich-text', () => {
//The last space is an object replacement character
expect( cleanForSlug( 'the long cat' ) ).toBe( 'the-long-cat' );
} );
it( 'Creates a slug for languages that use multibyte encodings', () => {
expect( cleanForSlug( '新荣记 ' ) ).toBe( '新荣记' );
expect( cleanForSlug( '私のテンプレートパーツのテスト ' ) ).toBe(
'私のテンプレートパーツのテスト'
);
expect( cleanForSlug( 'ქართული ნაწილი' ) ).toBe( 'ქართული-ნაწილი' );
expect( cleanForSlug( 'Καλημέρα Κόσμε' ) ).toBe( 'καλημέρα-κόσμε' );
expect( cleanForSlug( '안녕하세요 ' ) ).toBe( '안녕하세요' );
expect( cleanForSlug( '繁体字 ' ) ).toBe( '繁体字' );
} );
it( 'Should trim multiple leading and trailing dashes', () => {
expect( cleanForSlug( ' -Is th@t Déjà_vu- ' ) ).toBe(
'is-tht-deja_vu'
);
} );
it( 'Should replace multiple hyphens with a single one', () => {
expect( cleanForSlug( 'the long - cat' ) ).toBe( 'the-long-cat' );
expect( cleanForSlug( 'the----long---cat' ) ).toBe( 'the-long-cat' );
} );
it( 'Should remove ampersands', () => {
expect( cleanForSlug( 'the long cat & dog' ) ).toBe(
'the-long-cat-dog'
);
expect(
cleanForSlug( 'the long cat & a dog && fish' )
).toBe( 'the-long-cat-a-dog-fish' );
expect( cleanForSlug( 'the long cat &amp; dog' ) ).toBe(
'the-long-cat-amp-dog'
);
} );
it( 'Should remove HTML entities', () => {
expect(
cleanForSlug( 'No Entities> – Here —<' )
).toBe( 'no-entities-here' );
} );
} );
describe( 'normalizePath', () => {
it( 'returns same value if no query parameters', () => {
const path = '/foo/bar';
expect( normalizePath( path ) ).toBe( path );
} );
it( 'returns a stable path', () => {
const abc = normalizePath( '/foo/bar?a=5&b=1&c=2' );
const bca = normalizePath( '/foo/bar?b=1&c=2&a=5' );
const bac = normalizePath( '/foo/bar?b=1&a=5&c=2' );
const acb = normalizePath( '/foo/bar?a=5&c=2&b=1' );
const cba = normalizePath( '/foo/bar?c=2&b=1&a=5' );
const cab = normalizePath( '/foo/bar?c=2&a=5&b=1' );
expect( abc ).toBe( bca );
expect( bca ).toBe( bac );
expect( bac ).toBe( acb );
expect( acb ).toBe( cba );
expect( cba ).toBe( cab );
} );
it( 'sorts urldecoded values and returns property urlencoded query string', () => {
const ab = normalizePath( '/foo/bar?a%2Ca=5,5&a,b=1,1' );
expect( ab ).toBe( '/foo/bar?a%2Ca=5%2C5&a%2Cb=1%2C1' );
} );
} );