diff --git a/static/js/formatters-internal.js b/static/js/formatters-internal.js index 75474835a..66d0b8b3d 100644 --- a/static/js/formatters-internal.js +++ b/static/js/formatters-internal.js @@ -311,110 +311,52 @@ export function image(simpleOrComplexImage = {}, desiredSize = '200x', atLeastAs if (!(Object.prototype.toString.call(image).indexOf('Object') > 0)) { throw new Error("Expected parameter of type Map"); } - if ((typeof desiredSize !== 'string') || (desiredSize == null)) { + if (typeof desiredSize !== 'string') { throw new Error(`Object of type string expected. Got ${typeof desiredSize}.`); } if (desiredSize.indexOf('x') === -1) { throw new Error("Invalid desired size"); } - if ((typeof atLeastAsLarge !== 'boolean') || (atLeastAsLarge == null)) { + if (typeof atLeastAsLarge !== 'boolean') { throw new Error(`Object of type boolean expected. Got ${typeof atLeastAsLarge}.`); } - const isEuImage = image.url.includes('eu.mktgcdn.com'); - - const dynamicUrl = isEuImage - ? _getEuImageDynamicUrl(image, desiredSize, atLeastAsLarge) - : _getUsImageDynamicUrl(image.url, desiredSize, atLeastAsLarge); - - return Object.assign( - {}, - image, - { - url: dynamicUrl.replace('http://', 'https://') + let url; + try { + url = new URL(image.url); + let urlPath = url.pathname; + if (url.pathname.match('^\/p')) { + urlPath = _removePhotoImageUrlExtension(urlPath); } - ); -} -/** - * Given a US image url, returns the dynamic url. - * - * @param {string} imageUrl Image's url (e.g. - * 'https://dynl.mktgcdn.com/p/ldMLwj1JkN94-2pwh6CjR_OMy4KnexHJCfZhPAZCbi0/196x400.jpg', - * 'https://a.mktgcdn.com/p/ldMLwj1JkN94-2pwh6CjR_OMy4KnexHJCfZhPAZCbi0/196x400.jpg') - * @param {string} desiredSize The desired size of the image ('x') - * @param {boolean} atLeastAsLarge Whether the image should be at least as large as the desired - * size in one dimension or smaller than the desired size in both - * dimensions. - * @returns {string} A dynamic url (e.g. 'https://dynl.mktgcdn.com/p/ldMLwj1JkN94-2pwh6CjR_OMy4KnexHJCfZhPAZCbi0/200x1.jpg') - */ -function _getUsImageDynamicUrl(imageUrl, desiredSize, atLeastAsLarge) { - const [urlWithoutExtension, extension] = _splitStringOnIndex(imageUrl, imageUrl.lastIndexOf('.')); - const [urlBeforeDimensions, dimensions] = _splitStringOnIndex(urlWithoutExtension, urlWithoutExtension.lastIndexOf('/') + 1); - const fullSizeDims = dimensions.split('x'); + const hostname = url.hostname.replace(/^[a-z]+\./, 'dyn.'); + const formatOptionsString = _getImageFormatOptions(desiredSize, atLeastAsLarge, image.width, image.height); - let desiredWidth, desiredHeight; - let desiredDims = desiredSize.split('x'); + return Object.assign( + {}, + image, + { + url: `https://${hostname}${urlPath}${formatOptionsString}`, + } + ); - if (desiredDims[0] !== '') { - desiredWidth = Number.parseInt(desiredDims[0]); - if (Number.isNaN(desiredWidth)) { - throw new Error("Invalid width specified"); - } - } else { - desiredWidth = atLeastAsLarge ? 1 : Number.parseInt(fullSizeDims[0]); + } catch (error) { + throw new Error(`Error processing image url ${image.url}: ${error}`); } - - if (desiredDims[1] !== '') { - desiredHeight = Number.parseInt(desiredDims[1]); - if (Number.isNaN(desiredHeight)) { - throw new Error("Invalid height specified"); - } - } else { - desiredHeight = atLeastAsLarge ? 1 : Number.parseInt(fullSizeDims[1]); - } - - const urlWithDesiredDims = urlBeforeDimensions + desiredWidth + 'x' + desiredHeight + extension; - - return atLeastAsLarge - ? _replaceUrlHost(urlWithDesiredDims, 'dynl.mktgcdn.com') - : _replaceUrlHost(urlWithDesiredDims, 'dynm.mktgcdn.com'); } /** - * Given an EU image url, returns the dynamic url. + * Construct the format options string with given parameters. * - * @param {Object} image The image object. (e.g. - * { - * url: 'https://a.eu.mktgcdn.com/f/0/FLVfkpR1IwpWrWDuyNYCJWVYIDfPO6x1QSztXozMIzo.jpg', - * sourceUrl: 'https://a.mktgcdn.com/p/UN9RPhz0V9D8bNZ3XfNpkGnAk6ikFhVmgvntlBjVyMA/1200x675.jpg', - * width: 1200, - * height: 675, - * }) * @param {string} desiredSize The desired size of the image ('x') * @param {boolean} atLeastAsLarge Whether the image should be at least as large as the desired * size in one dimension or smaller than the desired size in both * dimensions. - * @returns {string} A dynamic url (e.g. 'https://dyn.eu.mktgcdn.com/f/0/FLVfkpR1IwpWrWDuyNYCJWVYIDfPO6x1QSztXozMIzo.jpg/width=200,fit=contain') + * @param {number?} fullSizeWidth The full size width of the original image + * @param {number?} fullSizeHeight The full size height of the original image + * @returns {string} A string representing the format options (e.g. 'height=200,width=100,fit=cover') */ -function _getEuImageDynamicUrl(image, desiredSize, atLeastAsLarge) { - let fullSizeWidth, fullSizeHeight; - if (image.width) { - fullSizeWidth = image.width; - } - if (image.height) { - fullSizeHeight = image.height; - } - - if (image.sourceUrl && (!fullSizeWidth || !fullSizeHeight)) { - const [urlWithoutExtension, _] = _splitStringOnIndex(image.sourceUrl, image.sourceUrl.lastIndexOf('.')); - const [__, dimensions] = _splitStringOnIndex(urlWithoutExtension, urlWithoutExtension.lastIndexOf('/') + 1); - const fullSizeDims = dimensions.split('x'); - - fullSizeWidth = Number.parseInt(fullSizeDims[0]); - fullSizeHeight = Number.parseInt(fullSizeDims[1]); - } - +function _getImageFormatOptions(desiredSize, atLeastAsLarge, fullSizeWidth, fullSizeHeight) { let desiredDims = desiredSize.split('x'); let formatOptions = []; @@ -442,33 +384,21 @@ function _getEuImageDynamicUrl(image, desiredSize, atLeastAsLarge) { formatOptions.push(`fit=${atLeastAsLarge ? 'cover' : 'contain'}`); - const urlWithOptions = image.url + `/${formatOptions.join(',')}`; - - return _replaceUrlHost(urlWithOptions, 'dyn.eu.mktgcdn.com'); -} - -/** - * Splits a string into two parts at the specified index. - * - * @param {string} str The string to be split - * @param {number} index The index at which to split the string - * @returns {Array} The two parts of the string after splitting - */ -function _splitStringOnIndex(str, index) { - return [str.slice(0, index), str.slice(index)]; + return `/${formatOptions.join(',')}`; } /** - * Replaces the current host of a url with the specified host. + * Given a photo image url path, remove the trailing extension. * - * @param {string} url The url whose host is to be changed - * @param {string} host The new host to change to - * @returns {string} The url updated with the specified host + * @param {string} imageUrlPath Image's url path (e.g. + * '/p/mFsjqWGQEOMQGNoNIcnq61JtdSGiCs/225x225.jpg', + * '/p-sandbox/mFsjqWGQEOMQGNoNIcnq61JtdSGiCs/225x225.jpg') + * @returns {string} A canonicalized image url path (e.g. + * '/p/mFsjqWGQEOMQGNoNIcnq61JtdSGiCs', + * '/p-sandbox/mFsjqWGQEOMQGNoNIcnq61JtdSGiCs') */ -function _replaceUrlHost(url, host) { - const splitUrl = url.split('://'); - const urlAfterHost = splitUrl[1].slice(splitUrl[1].indexOf('/')); - return splitUrl[0] + '://' + host + urlAfterHost; +function _removePhotoImageUrlExtension(imageUrlPath) { + return imageUrlPath.replace(/(\/[0-9]+x[0-9]+\.[a-z]+)|(\/)$/, ''); } /** diff --git a/tests/static/js/formatters-internal/image.js b/tests/static/js/formatters-internal/image.js index b56e7db10..0b534bac0 100644 --- a/tests/static/js/formatters-internal/image.js +++ b/tests/static/js/formatters-internal/image.js @@ -1,106 +1,159 @@ import Formatters from 'static/js/formatters.js'; describe('image formatter', () => { - const usUrl = 'https://a.mktgcdn.com/p/1024x768.jpg'; - const euUrl = 'https://dyn.eu.mktgcdn.com/f/0/FOO.jpg'; - const usImg = {url: usUrl}; - const euImg = { - url: euUrl, + const photoUrl = 'https://a.mktgcdn.com/p/FOO/1024x768.jpg'; + const oldFileUrl = 'https://a.mktgcdn.com/f/0/FOO.jpg'; + const newFileUrl = 'https://a.mktgcdn.com/f/FOO.jpg'; + const euFileUrl = 'https://a.eu.mktgcdn.com/f/0/FOO.jpg'; + + const photoImg = { + url: photoUrl, + width: 1024, + height: 768, + }; + + const oldFileImg = { + url: oldFileUrl, + width: 1024, + height: 768, + }; + + const newFileImg = { + url: newFileUrl, + width: 1024, + height: 768, + }; + + const euFileImg = { + url: euFileUrl, width: 1024, height: 768, - sourceUrl: 'https://a.mktgcdn.com/p/FOO/1024x768.jpg', } describe('when choosing the smallest image over threshold', () => { it('By default chooses the smallest image with width >= 200', () => { - const usImageUrl = Formatters.image(usImg).url; - expect(usImageUrl).toEqual('https://dynl.mktgcdn.com/p/200x1.jpg'); - const euImageUrl = Formatters.image(euImg).url; - expect(euImageUrl).toEqual('https://dyn.eu.mktgcdn.com/f/0/FOO.jpg/width=200,fit=cover'); + const photoImgUrl = Formatters.image(photoImg).url; + expect(photoImgUrl).toEqual('https://dyn.mktgcdn.com/p/FOO/width=200,fit=cover'); + const oldFileImgUrl = Formatters.image(oldFileImg).url; + expect(oldFileImgUrl).toEqual('https://dyn.mktgcdn.com/f/0/FOO.jpg/width=200,fit=cover'); + const newFileImgUrl = Formatters.image(newFileImg).url; + expect(newFileImgUrl).toEqual('https://dyn.mktgcdn.com/f/FOO.jpg/width=200,fit=cover'); + const euFileImgUrl = Formatters.image(euFileImg).url; + expect(euFileImgUrl).toEqual('https://dyn.eu.mktgcdn.com/f/0/FOO.jpg/width=200,fit=cover'); }); it('Can restrict the dimensions by width', () => { - const usImageUrl = Formatters.image(usImg, '601x').url; - expect(usImageUrl).toEqual('https://dynl.mktgcdn.com/p/601x1.jpg'); - const euImageUrl = Formatters.image(euImg, '601x').url; - expect(euImageUrl).toEqual('https://dyn.eu.mktgcdn.com/f/0/FOO.jpg/width=601,fit=cover'); + const photoImgUrl = Formatters.image(photoImg, '601x').url; + expect(photoImgUrl).toEqual('https://dyn.mktgcdn.com/p/FOO/width=601,fit=cover'); + const oldFileImgUrl = Formatters.image(oldFileImg, '601x').url; + expect(oldFileImgUrl).toEqual('https://dyn.mktgcdn.com/f/0/FOO.jpg/width=601,fit=cover'); + const newFileImgUrl = Formatters.image(newFileImg, '601x').url; + expect(newFileImgUrl).toEqual('https://dyn.mktgcdn.com/f/FOO.jpg/width=601,fit=cover'); + const euFileImgUrl = Formatters.image(euFileImg, '601x').url; + expect(euFileImgUrl).toEqual('https://dyn.eu.mktgcdn.com/f/0/FOO.jpg/width=601,fit=cover'); }); it('Can restrict the dimensions by height', () => { - const usImageUrl = Formatters.image(usImg, 'x338').url; - expect(usImageUrl).toEqual('https://dynl.mktgcdn.com/p/1x338.jpg'); - const euImageUrl = Formatters.image(euImg, 'x338').url; - expect(euImageUrl).toEqual('https://dyn.eu.mktgcdn.com/f/0/FOO.jpg/height=338,fit=cover'); + const photoImgUrl = Formatters.image(photoImg, 'x338').url; + expect(photoImgUrl).toEqual('https://dyn.mktgcdn.com/p/FOO/height=338,fit=cover'); + const oldFileImgUrl = Formatters.image(oldFileImg, 'x338').url; + expect(oldFileImgUrl).toEqual('https://dyn.mktgcdn.com/f/0/FOO.jpg/height=338,fit=cover'); + const newFileImgUrl = Formatters.image(newFileImg, 'x338').url; + expect(newFileImgUrl).toEqual('https://dyn.mktgcdn.com/f/FOO.jpg/height=338,fit=cover'); + const euFileImgUrl = Formatters.image(euFileImg, 'x338').url; + expect(euFileImgUrl).toEqual('https://dyn.eu.mktgcdn.com/f/0/FOO.jpg/height=338,fit=cover'); }); it('Can restrict by both dimensions', () => { - const usImageUrl = Formatters.image(usImg, '601x338').url; - expect(usImageUrl).toEqual('https://dynl.mktgcdn.com/p/601x338.jpg'); - const euImageUrl = Formatters.image(euImg, '601x338').url; - expect(euImageUrl).toEqual('https://dyn.eu.mktgcdn.com/f/0/FOO.jpg/width=601,height=338,fit=cover'); + const photoImgUrl = Formatters.image(photoImg, '601x338').url; + expect(photoImgUrl).toEqual('https://dyn.mktgcdn.com/p/FOO/width=601,height=338,fit=cover'); + const oldFileImgUrl = Formatters.image(oldFileImg, '601x338').url; + expect(oldFileImgUrl).toEqual('https://dyn.mktgcdn.com/f/0/FOO.jpg/width=601,height=338,fit=cover'); + const newFileImgUrl = Formatters.image(newFileImg, '601x338').url; + expect(newFileImgUrl).toEqual('https://dyn.mktgcdn.com/f/FOO.jpg/width=601,height=338,fit=cover'); + const euFileImgUrl = Formatters.image(euFileImg, '601x338').url; + expect(euFileImgUrl).toEqual('https://dyn.eu.mktgcdn.com/f/0/FOO.jpg/width=601,height=338,fit=cover'); }); it('returns the smallest image when no dimensions given', () => { - const usImageUrl = Formatters.image(usImg, 'x').url; - expect(usImageUrl).toEqual('https://dynl.mktgcdn.com/p/1x1.jpg'); - const euImageUrl = Formatters.image(euImg, 'x').url; - expect(euImageUrl).toEqual('https://dyn.eu.mktgcdn.com/f/0/FOO.jpg/fit=cover'); + const photoImgUrl = Formatters.image(photoImg, 'x').url; + expect(photoImgUrl).toEqual('https://dyn.mktgcdn.com/p/FOO/fit=cover'); + const oldFileImgUrl = Formatters.image(oldFileImg, 'x').url; + expect(oldFileImgUrl).toEqual('https://dyn.mktgcdn.com/f/0/FOO.jpg/fit=cover'); + const newFileImgUrl = Formatters.image(newFileImg, 'x').url; + expect(newFileImgUrl).toEqual('https://dyn.mktgcdn.com/f/FOO.jpg/fit=cover'); + const euFileImgUrl = Formatters.image(euFileImg, 'x').url; + expect(euFileImgUrl).toEqual('https://dyn.eu.mktgcdn.com/f/0/FOO.jpg/fit=cover'); }); }); describe('when choosing the biggest image under threshold', () => { it('Can restrict the dimensions by width', () => { - const usImageUrl = Formatters.image(usImg, '601x', false).url; - expect(usImageUrl).toEqual('https://dynm.mktgcdn.com/p/601x768.jpg'); - const euImageUrl = Formatters.image(euImg, '601x', false).url; - expect(euImageUrl).toEqual('https://dyn.eu.mktgcdn.com/f/0/FOO.jpg/width=601,height=768,fit=contain'); + const photoImgUrl = Formatters.image(photoImg, '601x', false).url; + expect(photoImgUrl).toEqual('https://dyn.mktgcdn.com/p/FOO/width=601,height=768,fit=contain'); + const oldFileImgUrl = Formatters.image(oldFileImg, '601x', false).url; + expect(oldFileImgUrl).toEqual('https://dyn.mktgcdn.com/f/0/FOO.jpg/width=601,height=768,fit=contain'); + const newFileImgUrl = Formatters.image(newFileImg, '601x', false).url; + expect(newFileImgUrl).toEqual('https://dyn.mktgcdn.com/f/FOO.jpg/width=601,height=768,fit=contain'); + const euFileImgUrl = Formatters.image(euFileImg, '601x', false).url; + expect(euFileImgUrl).toEqual('https://dyn.eu.mktgcdn.com/f/0/FOO.jpg/width=601,height=768,fit=contain'); }); it('Can restrict the dimensions by height', () => { - const usImageUrl = Formatters.image(usImg, 'x338', false).url; - expect(usImageUrl).toEqual('https://dynm.mktgcdn.com/p/1024x338.jpg'); - const euImageUrl = Formatters.image(euImg, 'x338', false).url; - expect(euImageUrl).toEqual('https://dyn.eu.mktgcdn.com/f/0/FOO.jpg/width=1024,height=338,fit=contain'); + const photoImgUrl = Formatters.image(photoImg, 'x338', false).url; + expect(photoImgUrl).toEqual('https://dyn.mktgcdn.com/p/FOO/width=1024,height=338,fit=contain'); + const oldFileImgUrl = Formatters.image(oldFileImg, 'x338', false).url; + expect(oldFileImgUrl).toEqual('https://dyn.mktgcdn.com/f/0/FOO.jpg/width=1024,height=338,fit=contain'); + const newFileImgUrl = Formatters.image(newFileImg, 'x338', false).url; + expect(newFileImgUrl).toEqual('https://dyn.mktgcdn.com/f/FOO.jpg/width=1024,height=338,fit=contain'); + const euFileImgUrl = Formatters.image(euFileImg, 'x338', false).url; + expect(euFileImgUrl).toEqual('https://dyn.eu.mktgcdn.com/f/0/FOO.jpg/width=1024,height=338,fit=contain'); }); it('Can restrict by both dimensions', () => { - const usImageUrl = Formatters.image(usImg, '999x338', false).url; - expect(usImageUrl).toEqual('https://dynm.mktgcdn.com/p/999x338.jpg'); - const euImageUrl = Formatters.image(euImg, '999x338', false).url; - expect(euImageUrl).toEqual('https://dyn.eu.mktgcdn.com/f/0/FOO.jpg/width=999,height=338,fit=contain'); + const photoImgUrl = Formatters.image(photoImg, '999x338', false).url; + expect(photoImgUrl).toEqual('https://dyn.mktgcdn.com/p/FOO/width=999,height=338,fit=contain'); + const oldFileImgUrl = Formatters.image(oldFileImg, '999x338', false).url; + expect(oldFileImgUrl).toEqual('https://dyn.mktgcdn.com/f/0/FOO.jpg/width=999,height=338,fit=contain'); + const newFileImgUrl = Formatters.image(newFileImg, '999x338', false).url; + expect(newFileImgUrl).toEqual('https://dyn.mktgcdn.com/f/FOO.jpg/width=999,height=338,fit=contain'); + const euFileImgUrl = Formatters.image(euFileImg, '999x338', false).url; + expect(euFileImgUrl).toEqual('https://dyn.eu.mktgcdn.com/f/0/FOO.jpg/width=999,height=338,fit=contain'); }); it('return the largest image when no dimensions given', () => { - const usImageUrl = Formatters.image(usImg, 'x', false).url; - expect(usImageUrl).toEqual('https://dynm.mktgcdn.com/p/1024x768.jpg'); - const euImageUrl = Formatters.image(euImg, 'x', false).url; - expect(euImageUrl).toEqual('https://dyn.eu.mktgcdn.com/f/0/FOO.jpg/width=1024,height=768,fit=contain'); + const photoImgUrl = Formatters.image(photoImg, 'x', false).url; + expect(photoImgUrl).toEqual('https://dyn.mktgcdn.com/p/FOO/width=1024,height=768,fit=contain'); + const oldFileImgUrl = Formatters.image(oldFileImg, 'x', false).url; + expect(oldFileImgUrl).toEqual('https://dyn.mktgcdn.com/f/0/FOO.jpg/width=1024,height=768,fit=contain'); + const newFileImgUrl = Formatters.image(newFileImg, 'x', false).url; + expect(newFileImgUrl).toEqual('https://dyn.mktgcdn.com/f/FOO.jpg/width=1024,height=768,fit=contain'); + const euFileImgUrl = Formatters.image(euFileImg, 'x', false).url; + expect(euFileImgUrl).toEqual('https://dyn.eu.mktgcdn.com/f/0/FOO.jpg/width=1024,height=768,fit=contain'); }); }); - describe('when image is served from EU with no dimensions specified', () => { + describe('when image has no dimensions specified', () => { it('when choosing the biggest image under threshold, use the width and height on the image object if exists', () => { - const euImageUrl = Formatters.image( - { url: euUrl, - width: 1024, - height: 768, - sourceUrl: 'https://a.mktgcdn.com/p/FOO/516x384.jpg', - }, 'x', false).url; - expect(euImageUrl).toEqual('https://dyn.eu.mktgcdn.com/f/0/FOO.jpg/width=1024,height=768,fit=contain'); - }); - - it('when choosing the biggest image under threshold, use dimensions from the sourceUrl if width/height does not exist', () => { - const euImageUrl = Formatters.image( - { url: euUrl, - width: 1024, - sourceUrl: 'https://a.mktgcdn.com/p/FOO/516x384.jpg' - }, 'x', false).url; - expect(euImageUrl).toEqual('https://dyn.eu.mktgcdn.com/f/0/FOO.jpg/width=516,height=384,fit=contain'); + const photoImgUrl = Formatters.image({url: photoUrl, width: 1024}, 'x', false).url; + expect(photoImgUrl).toEqual('https://dyn.mktgcdn.com/p/FOO/width=1024,fit=contain'); + const oldFileImgUrl = Formatters.image({url: oldFileUrl, width: 1024}, 'x', false).url; + expect(oldFileImgUrl).toEqual('https://dyn.mktgcdn.com/f/0/FOO.jpg/width=1024,fit=contain'); + const newFileImgUrl = Formatters.image({url: newFileUrl, width: 1024}, 'x', false).url; + expect(newFileImgUrl).toEqual('https://dyn.mktgcdn.com/f/FOO.jpg/width=1024,fit=contain'); + const euFileImgUrl = Formatters.image({url: euFileUrl, width: 1024}, 'x', false).url; + expect(euFileImgUrl).toEqual('https://dyn.eu.mktgcdn.com/f/0/FOO.jpg/width=1024,fit=contain'); }); it('when choosing the smallest image over threshold, omit width/height if can\'t parse it from the image object', () => { - const euImageUrl = Formatters.image({url: euUrl}, 'x', true).url; - expect(euImageUrl).toEqual('https://dyn.eu.mktgcdn.com/f/0/FOO.jpg/fit=cover'); + const photoImgUrl = Formatters.image({url: photoUrl}, 'x', true).url; + expect(photoImgUrl).toEqual('https://dyn.mktgcdn.com/p/FOO/fit=cover'); + const oldFileImgUrl = Formatters.image({url: oldFileUrl}, 'x', true).url; + expect(oldFileImgUrl).toEqual('https://dyn.mktgcdn.com/f/0/FOO.jpg/fit=cover'); + const newFileImgUrl = Formatters.image({url: newFileUrl}, 'x', true).url; + expect(newFileImgUrl).toEqual('https://dyn.mktgcdn.com/f/FOO.jpg/fit=cover'); + const euFileImgUrl = Formatters.image({url: euFileUrl}, 'x', true).url; + expect(euFileImgUrl).toEqual('https://dyn.eu.mktgcdn.com/f/0/FOO.jpg/fit=cover'); }); }); }); \ No newline at end of file