From bda8cf1545b3b856064e608eea49b1b18861a3db Mon Sep 17 00:00:00 2001 From: nd0ut Date: Tue, 7 Jun 2022 15:42:00 +0300 Subject: [PATCH] feat: auto-generate `blurDataURL` property when `placeholder=blur` --- example/pages/index.tsx | 47 +++++++--- jest-setup.js | 1 + jest.config.ts | 3 +- package.json | 1 + src/__tests__/uploadcare-image.spec.tsx | 43 ++++++++- src/components/UploadcareImage.tsx | 23 ++++- src/utils/helpers.ts | 10 +++ yarn.lock | 115 +++++++++++++++++++++++- 8 files changed, 227 insertions(+), 16 deletions(-) create mode 100644 jest-setup.js diff --git a/example/pages/index.tsx b/example/pages/index.tsx index 9451955..24a3bec 100644 --- a/example/pages/index.tsx +++ b/example/pages/index.tsx @@ -5,15 +5,21 @@ import { FC } from 'react'; import styles from '../styles/Home.module.css'; type CodeProps = { - [key: string]: any -} -const Code: FC = (p) => + [key: string]: any; +}; +const Code: FC = (p) => ( +); const Home: NextPage = () => (
-

Uploadcare custom loader for Image Component @uploadcare/nextjs-loader

+

+ Uploadcare custom loader for Image Component{' '} + + @uploadcare/nextjs-loader + +

The following is an example of a reference to an image from the{' '} Uploadcare CDN at ucarecdn.com @@ -31,7 +37,8 @@ const Home: NextPage = () => ( />


- The following is an example of use of the UploadcareImage helper component. + The following is an example of use of the UploadcareImage{' '} + helper component.

( height={500} />
+

+ The following is an example of use of the UploadcareImage{' '} + helper component with placeholder=blur property. It's + better to enable network throttling in dev tools to see the blurred placeholder. +

+ +

The following is an example of a reference to an external image at{' '} assets.vercel.com. @@ -68,8 +88,11 @@ const Home: NextPage = () => ( height={64} loader={uploadcareLoader} /> -


-

Local image will be served AS IS in Development, and converted to the absolute URL and passed to the proxy in Production

+
+

+ Local image will be served AS IS in Development, and converted to the + absolute URL and passed to the proxy in Production +

A local image ( loader={uploadcareLoader} />
- Checkout the project documentation on Github @uploadcare/nextjs-loader. + Checkout the project documentation on Github{' '} + + @uploadcare/nextjs-loader + + .
-) +); -export default Home +export default Home; diff --git a/jest-setup.js b/jest-setup.js new file mode 100644 index 0000000..c44951a --- /dev/null +++ b/jest-setup.js @@ -0,0 +1 @@ +import '@testing-library/jest-dom' diff --git a/jest.config.ts b/jest.config.ts index 7f1937a..8078796 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -1,4 +1,5 @@ export default { testPathIgnorePatterns: ["/build/", "/src/__tests__/utils"], - collectCoverageFrom: ["src/**/*.{ts,js,tsx}"] + collectCoverageFrom: ["src/**/*.{ts,js,tsx}"], + setupFilesAfterEnv: ['/jest-setup.js'] } diff --git a/package.json b/package.json index f5f1dd2..93768fb 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,7 @@ "@babel/preset-env": "^7.15.6", "@babel/preset-react": "^7.14.5", "@babel/preset-typescript": "^7.15.0", + "@testing-library/jest-dom": "^5.16.4", "@testing-library/react": "^12.1.2", "@testing-library/react-hooks": "^7.0.2", "@types/jest": "^27.0.2", diff --git a/src/__tests__/uploadcare-image.spec.tsx b/src/__tests__/uploadcare-image.spec.tsx index 001c327..25614ca 100644 --- a/src/__tests__/uploadcare-image.spec.tsx +++ b/src/__tests__/uploadcare-image.spec.tsx @@ -17,7 +17,7 @@ describe('UploadcareImage', () => { removeEnvVar('NEXT_PUBLIC_UPLOADCARE_PUBLIC_KEY'); }); - test('The UploadcareImage component renders passed image with default settings properly', () => { + it('should render passed image with default settings properly', () => { const src = 'https://ucarecdn.com/a6f8abc8-f92e-460a-b7a1-c5cd70a18cdb/vercel.png'; @@ -38,7 +38,7 @@ describe('UploadcareImage', () => { ); }); - test('The UploadcareImage component should accept src without filename', () => { + it('should accept src without filename', () => { const src = 'https://ucarecdn.com/a6f8abc8-f92e-460a-b7a1-c5cd70a18cdb/'; render( @@ -56,4 +56,43 @@ describe('UploadcareImage', () => { 'https://ucarecdn.com/a6f8abc8-f92e-460a-b7a1-c5cd70a18cdb/-/format/auto/-/stretch/off/-/progressive/yes/-/resize/1080x/-/quality/normal/' ); }); + + it('should generate blurDataURL when placeholder=blur passed', () => { + const src = + 'https://ucarecdn.com/a6f8abc8-f92e-460a-b7a1-c5cd70a18cdb/image.png'; + + render( + + ); + + expect(screen.getByRole('img')).toHaveStyle( + 'background-image: url(https://ucarecdn.com/a6f8abc8-f92e-460a-b7a1-c5cd70a18cdb/-/format/auto/-/stretch/off/-/progressive/yes/-/resize/10x/-/quality/lightest/image.png)' + ); + }); + + it('should not override passed blurDataURL', () => { + const src = + 'https://ucarecdn.com/a6f8abc8-f92e-460a-b7a1-c5cd70a18cdb/image.png'; + + render( + + ); + + expect(screen.getByRole('img')).toHaveStyle( + `background-image: url(${src})` + ); + }); }); diff --git a/src/components/UploadcareImage.tsx b/src/components/UploadcareImage.tsx index 300118a..cff6e5c 100644 --- a/src/components/UploadcareImage.tsx +++ b/src/components/UploadcareImage.tsx @@ -1,7 +1,26 @@ import Image, { ImageProps } from 'next/image'; import React from 'react'; +import { getInt } from '../utils/helpers'; import { uploadcareLoader } from '../utils/loader'; -export function UploadcareImage(props: ImageProps): JSX.Element { - return ; +export function UploadcareImage({ + blurDataURL, + ...props +}: ImageProps): JSX.Element { + if ( + props.placeholder === 'blur' && + !blurDataURL && + typeof props.src === 'string' + ) { + const imageWidth = getInt(props.width); + const blurImageWidth = imageWidth ? Math.ceil(imageWidth * 0.01) : 10; + blurDataURL = uploadcareLoader({ + src: props.src, + width: blurImageWidth, + quality: 0 + }); + } + return ( + + ); } diff --git a/src/utils/helpers.ts b/src/utils/helpers.ts index 053993c..9c4f348 100644 --- a/src/utils/helpers.ts +++ b/src/utils/helpers.ts @@ -143,6 +143,16 @@ export function isJpegExtension(extension: string): boolean { return ['jpg', 'jpeg'].includes(extension.toLowerCase()); } +export function getInt(x: unknown): number | undefined { + if (typeof x === 'number') { + return x; + } + if (typeof x === 'string') { + return parseInt(x, 10); + } + return undefined; +} + function _parseUploadcareTransformationParam(param: string): string[] { return param.split('/'); } diff --git a/yarn.lock b/yarn.lock index 8193ae7..a633085 100644 --- a/yarn.lock +++ b/yarn.lock @@ -942,6 +942,13 @@ dependencies: regenerator-runtime "^0.13.4" +"@babel/runtime@^7.9.2": + version "7.18.3" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.18.3.tgz#c7b654b57f6f63cf7f8b418ac9ca04408c4579f4" + integrity sha512-38Y8f7YUhce/K7RMwTp7m0uCumpv9hZkitCbBClqQIow1qSbCvGkcegKOXpEWCQLfWmevgRiWokZ1GkpfhbZug== + dependencies: + regenerator-runtime "^0.13.4" + "@babel/template@^7.16.0", "@babel/template@^7.3.3": version "7.16.0" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.16.0.tgz#d16a35ebf4cd74e202083356fab21dd89363ddd6" @@ -1452,6 +1459,21 @@ lz-string "^1.4.4" pretty-format "^27.0.2" +"@testing-library/jest-dom@^5.16.4": + version "5.16.4" + resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-5.16.4.tgz#938302d7b8b483963a3ae821f1c0808f872245cd" + integrity sha512-Gy+IoFutbMQcky0k+bqqumXZ1cTGswLsFqmNLzNdSKkU9KGV2u9oXhukCbbJ9/LRPKiqwxEE8VpV/+YZlfkPUA== + dependencies: + "@babel/runtime" "^7.9.2" + "@types/testing-library__jest-dom" "^5.9.1" + aria-query "^5.0.0" + chalk "^3.0.0" + css "^3.0.0" + css.escape "^1.5.1" + dom-accessibility-api "^0.5.6" + lodash "^4.17.15" + redent "^3.0.0" + "@testing-library/react-hooks@^7.0.2": version "7.0.2" resolved "https://registry.yarnpkg.com/@testing-library/react-hooks/-/react-hooks-7.0.2.tgz#3388d07f562d91e7f2431a4a21b5186062ecfee0" @@ -1568,6 +1590,14 @@ dependencies: "@types/istanbul-lib-report" "*" +"@types/jest@*": + version "28.1.1" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-28.1.1.tgz#8c9ba63702a11f8c386ee211280e8b68cb093cd1" + integrity sha512-C2p7yqleUKtCkVjlOur9BWVA4HgUQmEj/HWCt5WzZ5mLXrWnyIfl0wGuArc+kBXsy0ZZfLp+7dywB4HtSVYGVA== + dependencies: + jest-matcher-utils "^27.0.0" + pretty-format "^27.0.0" + "@types/jest@^27.0.2": version "27.0.2" resolved "https://registry.yarnpkg.com/@types/jest/-/jest-27.0.2.tgz#ac383c4d4aaddd29bbf2b916d8d105c304a5fcd7" @@ -1644,6 +1674,13 @@ resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c" integrity sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw== +"@types/testing-library__jest-dom@^5.9.1": + version "5.14.3" + resolved "https://registry.yarnpkg.com/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.14.3.tgz#ee6c7ffe9f8595882ee7bda8af33ae7b8789ef17" + integrity sha512-oKZe+Mf4ioWlMuzVBaXQ9WDnEm1+umLx0InILg+yvZVBBDmzV5KfZyLrCvadtWcx8+916jLmHafcmqqffl+iIw== + dependencies: + "@types/jest" "*" + "@types/yargs-parser@*": version "20.2.1" resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-20.2.1.tgz#3b9ce2489919d9e4fea439b76916abc34b2df129" @@ -1900,6 +1937,11 @@ asynckit@^0.4.0: resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= +atob@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" + integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== + axios@^0.21.1: version "0.21.4" resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.4.tgz#c67b90dc0568e5c1cf2b0b858c43ba28e2eda575" @@ -2379,6 +2421,20 @@ cross-spawn@^7.0.2, cross-spawn@^7.0.3: shebang-command "^2.0.0" which "^2.0.1" +css.escape@^1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/css.escape/-/css.escape-1.5.1.tgz#42e27d4fa04ae32f931a4b4d4191fa9cddee97cb" + integrity sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg== + +css@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/css/-/css-3.0.0.tgz#4447a4d58fdd03367c516ca9f64ae365cee4aa5d" + integrity sha512-DG9pFfwOrzc+hawpmqX/dHYHJG+Bsdb0klhyi1sDneOgGOXy9wQIC8hzyVp1e4NRYDBdxcylvywPkkXCHAzTyQ== + dependencies: + inherits "^2.0.4" + source-map "^0.6.1" + source-map-resolve "^0.6.0" + cssom@^0.4.4: version "0.4.4" resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.4.4.tgz#5a66cf93d2d0b661d80bf6a44fb65f5c2e4e0a10" @@ -2445,6 +2501,11 @@ decimal.js@^10.2.1: resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.3.1.tgz#d8c3a444a9c6774ba60ca6ad7261c3a94fd5e783" integrity sha512-V0pfhfr8suzyPGOx3nmq4aHqabehUZn6Ch9kyFpV79TGDTWFmHqUqXdabR7QHqxzrYolF4+tVmJhUG4OURg5dQ== +decode-uri-component@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" + integrity sha512-hjf+xovcEn31w/EUYdTXQh/8smFL/dzYjohQGEIgjyNavaJfBY2p5F527Bo1VPATxv0VYTUC2bOcXvqFwk78Og== + dedent@^0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c" @@ -2487,6 +2548,11 @@ diff-sequences@^27.0.6: resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-27.0.6.tgz#3305cb2e55a033924054695cc66019fd7f8e5723" integrity sha512-ag6wfpBFyNXZ0p8pcuIDS//D8H062ZQJ3fzYxjpmeKjnz8W4pekL3AI8VohmyZmsWW2PWaHgjsmqR6L13101VQ== +diff-sequences@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-27.5.1.tgz#eaecc0d327fd68c8d9672a1e64ab8dccb2ef5327" + integrity sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ== + diff@^4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" @@ -2506,6 +2572,11 @@ doctrine@^3.0.0: dependencies: esutils "^2.0.2" +dom-accessibility-api@^0.5.6: + version "0.5.14" + resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.5.14.tgz#56082f71b1dc7aac69d83c4285eef39c15d93f56" + integrity sha512-NMt+m9zFMPZe0JcY9gN224Qvk6qLIdqex29clBvc/y75ZBX9YA9wNK3frsYvu2DI1xcCIwxwnX+TlsJ2DSOADg== + dom-accessibility-api@^0.5.9: version "0.5.10" resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.5.10.tgz#caa6d08f60388d0bb4539dd75fe458a9a1d0014c" @@ -3219,7 +3290,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@^2.0.3, inherits@~2.0.3: +inherits@2, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -3507,6 +3578,16 @@ jest-diff@^27.0.0, jest-diff@^27.3.1: jest-get-type "^27.3.1" pretty-format "^27.3.1" +jest-diff@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-27.5.1.tgz#a07f5011ac9e6643cf8a95a462b7b1ecf6680def" + integrity sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw== + dependencies: + chalk "^4.0.0" + diff-sequences "^27.5.1" + jest-get-type "^27.5.1" + pretty-format "^27.5.1" + jest-docblock@^27.0.6: version "27.0.6" resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-27.0.6.tgz#cc78266acf7fe693ca462cbbda0ea4e639e4e5f3" @@ -3555,6 +3636,11 @@ jest-get-type@^27.3.1: resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-27.3.1.tgz#a8a2b0a12b50169773099eee60a0e6dd11423eff" integrity sha512-+Ilqi8hgHSAdhlQ3s12CAVNd8H96ZkQBfYoXmArzZnOfAtVAJEiPDBirjByEblvG/4LPJmkL+nBqPO3A1YJAEg== +jest-get-type@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-27.5.1.tgz#3cd613c507b0f7ace013df407a1c1cd578bcb4f1" + integrity sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw== + jest-haste-map@^27.3.1: version "27.3.1" resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-27.3.1.tgz#7656fbd64bf48bda904e759fc9d93e2c807353ee" @@ -3607,6 +3693,16 @@ jest-leak-detector@^27.3.1: jest-get-type "^27.3.1" pretty-format "^27.3.1" +jest-matcher-utils@^27.0.0: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-27.5.1.tgz#9c0cdbda8245bc22d2331729d1091308b40cf8ab" + integrity sha512-z2uTx/T6LBaCoNWNFWwChLBKYxTMcGBRjAt+2SbP929/Fflb9aa5LGma654Rz8z9HLxsrUaYzxE9T/EFIL/PAw== + dependencies: + chalk "^4.0.0" + jest-diff "^27.5.1" + jest-get-type "^27.5.1" + pretty-format "^27.5.1" + jest-matcher-utils@^27.3.1: version "27.3.1" resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-27.3.1.tgz#257ad61e54a6d4044e080d85dbdc4a08811e9c1c" @@ -4583,6 +4679,15 @@ pretty-format@^27.0.0, pretty-format@^27.0.2, pretty-format@^27.3.1: ansi-styles "^5.0.0" react-is "^17.0.1" +pretty-format@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-27.5.1.tgz#2181879fdea51a7a5851fb39d920faa63f01d88e" + integrity sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ== + dependencies: + ansi-regex "^5.0.1" + ansi-styles "^5.0.0" + react-is "^17.0.1" + private@~0.1.5: version "0.1.8" resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff" @@ -5075,6 +5180,14 @@ source-map-js@^1.0.1: resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== +source-map-resolve@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.6.0.tgz#3d9df87e236b53f16d01e58150fc7711138e5ed2" + integrity sha512-KXBr9d/fO/bWo97NXsPIAW1bFSBOuCnjbNTBMO7N59hsv5i9yzRDfcYwwt0l04+VqnKC+EwzvJZIP/qkuMgR/w== + dependencies: + atob "^2.1.2" + decode-uri-component "^0.2.0" + source-map-support@^0.5.6: version "0.5.20" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.20.tgz#12166089f8f5e5e8c56926b377633392dd2cb6c9"