From 32fbf8a2a34ae76a29f03550a668396f342fd53d Mon Sep 17 00:00:00 2001 From: jerzy-mankowski Date: Mon, 25 Nov 2024 11:45:33 +0100 Subject: [PATCH 001/115] feat: mocha setup --- packages/react/.mocharc.js | 17 +++++++++++ packages/react/.mocharc.json | 8 +++++ packages/react/__tests__/ssr.test.tsx | 43 --------------------------- packages/react/package.json | 28 ++++++----------- packages/react/setup.jest.ts | 7 ----- packages/react/src/setupTests.js | 4 --- packages/react/tsconfig.json | 20 ++++++++----- packages/react/tsconfig.test.json | 6 ---- 8 files changed, 47 insertions(+), 86 deletions(-) create mode 100644 packages/react/.mocharc.js create mode 100644 packages/react/.mocharc.json delete mode 100644 packages/react/__tests__/ssr.test.tsx delete mode 100644 packages/react/src/setupTests.js delete mode 100644 packages/react/tsconfig.test.json diff --git a/packages/react/.mocharc.js b/packages/react/.mocharc.js new file mode 100644 index 00000000..185bb0b2 --- /dev/null +++ b/packages/react/.mocharc.js @@ -0,0 +1,17 @@ +process.env.TS_NODE_COMPILER_OPTIONS = JSON.stringify({ + allowJs: true, + module: 'commonjs', +}); + +process.env.TS_NODE_PREFER_TS_EXTS = 'true'; + +process.env.NODE_ENV = 'test'; +process.env.TZ = 'UTC'; + +module.exports = { + recursive: true, + parallel: true, + jobs: 3, + timeout: 20000, + require: ['tsconfig-paths/register', 'ts-node/register/transpile-only', 'jsdom-global/register'], +}; diff --git a/packages/react/.mocharc.json b/packages/react/.mocharc.json new file mode 100644 index 00000000..53bbad8d --- /dev/null +++ b/packages/react/.mocharc.json @@ -0,0 +1,8 @@ +{ + "extensions": ["ts", "tsx"], + "spec": ["**/*.test.*"], + "node-option": [ + "experimental-specifier-resolution=node", + "loader=ts-node/esm" + ] +} diff --git a/packages/react/__tests__/ssr.test.tsx b/packages/react/__tests__/ssr.test.tsx deleted file mode 100644 index e954b408..00000000 --- a/packages/react/__tests__/ssr.test.tsx +++ /dev/null @@ -1,43 +0,0 @@ -/** - * @jest-environment node - */ -import { AdvancedImage, placeholder, responsive, accessibility, lazyload } from '../src' -import { CloudinaryImage } from '@cloudinary/url-gen/assets/CloudinaryImage'; -import React from 'react'; -import { renderToString } from 'react-dom/server' - -const cloudinaryImage = new CloudinaryImage('sample', { cloudName: 'demo' }, { analytics: false }); - -describe('ssr', () => { - it('should render accessibility transformation with accessibility', function (done) { - const ElementImageHtml = renderToString(); - setTimeout(() => { - expect(ElementImageHtml).toContain('https://res.cloudinary.com/demo/image/upload/co_black,e_colorize:70/sample'); - done(); - }, 0);// one tick - }); - - it('should render the placeholder image in SSR', function (done) { - const ElementImageHtml = renderToString(); - setTimeout(() => { - expect(ElementImageHtml).toContain('https://res.cloudinary.com/demo/image/upload/e_vectorize/q_auto/f_svg/sample'); - done(); - }, 0);// one tick - }); - - it('should render original image when responsive', function (done) { - const ElementImageHtml = renderToString(); - setTimeout(() => { - expect(ElementImageHtml).toContain('https://res.cloudinary.com/demo/image/upload/sample'); - done(); - }, 0);// one tick - }); - - it('should render original image when lazy loaded', function (done) { - const ElementImageHtml = renderToString(); - setTimeout(() => { - expect(ElementImageHtml).toContain('https://res.cloudinary.com/demo/image/upload/sample'); - done(); - }, 0);// one tick - }); -}); diff --git a/packages/react/package.json b/packages/react/package.json index 2045b622..0f3f9b61 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -18,8 +18,8 @@ }, "scripts": { "build": "npm run build --prefix ../html && tsc && rollup -c && cp package.json ./dist", - "test": "jest --config jest.config.json", - "test-coverage": "jest --coverage" + "test": "mocha src/**/*.test.*", + "test-coverage": "exit 1" }, "peerDependencies": { "react": "^16.3.0 || ^17.0.0 || ^18.0.0" @@ -27,30 +27,19 @@ "devDependencies": { "@babel/preset-env": "^7.12.10", "@babel/preset-typescript": "^7.12.7", - "@cloudinary/html": "^1.0.1", "@cloudinary/url-gen": "^1.21.0", "@rollup/plugin-commonjs": "^21.0.1", "@rollup/plugin-json": "^4.1.0", "@rollup/plugin-node-resolve": "^13.1.3", "@rollup/plugin-replace": "^3.0.1", "@rollup/plugin-typescript": "^8.3.0", - "@sinonjs/fake-timers": "^6.0.1", - "@testing-library/jest-dom": "^4.2.4", - "@types/enzyme": "^3.10.8", - "@types/enzyme-adapter-react-16": "^1.0.6", - "@types/jest": "^27.5.2", "@types/node": "^12.12.38", "@types/react": "^16.9.27", "@types/react-dom": "^16.9.7", - "@types/sinonjs__fake-timers": "^6.0.2", "@typescript-eslint/eslint-plugin": "^2.26.0", "@typescript-eslint/parser": "^2.26.0", "babel-eslint": "^10.0.3", - "cheerio": "1.0.0-rc.12", "cross-env": "^7.0.2", - "enzyme": "^3.11.0", - "enzyme-adapter-react-16": "^1.15.5", - "enzyme-to-json": "^3.6.1", "eslint": "^6.8.0", "eslint-config-prettier": "^6.7.0", "eslint-config-standard": "^14.1.0", @@ -62,17 +51,18 @@ "eslint-plugin-react": "^7.17.0", "eslint-plugin-standard": "^4.0.1", "gh-pages": "^2.2.0", - "jest": "27.5.1", - "npm-run-all": "^4.1.5", "prettier": "^2.0.4", "react": "^16.13.1", "react-dom": "^16.13.1", - "react-scripts": "^3.4.1", "rollup": "^2.66.1", - "ts-jest": "^27.1.5", - "typescript": "^3.7.5" + "typescript": "^5.6.2" }, "dependencies": { - "@cloudinary/html": "^1.13.1" + "@types/mocha": "^10.0.10", + "assert": "^2.1.0", + "jsdom": "^21.1.0", + "jsdom-global": "^3.0.2", + "mocha": "^10.8.2", + "ts-node": "^10.9.2" } } diff --git a/packages/react/setup.jest.ts b/packages/react/setup.jest.ts index bcc44a6d..e69de29b 100644 --- a/packages/react/setup.jest.ts +++ b/packages/react/setup.jest.ts @@ -1,7 +0,0 @@ -// const { TextDecoder, TextEncoder, ReadableStream } = require('node:util'); -// -// globalThis.TextDecoder = TextDecoder; -// globalThis.TextEncoder = TextEncoder; -// globalThis.ReadableStream = ReadableStream; - -export {}; diff --git a/packages/react/src/setupTests.js b/packages/react/src/setupTests.js deleted file mode 100644 index edda72c3..00000000 --- a/packages/react/src/setupTests.js +++ /dev/null @@ -1,4 +0,0 @@ -import 'regenerator-runtime/runtime' -import Enzyme from 'enzyme' -import Adapter from 'enzyme-adapter-react-16' -Enzyme.configure({ adapter: new Adapter() }); diff --git a/packages/react/tsconfig.json b/packages/react/tsconfig.json index 424f7e36..0433f9e6 100644 --- a/packages/react/tsconfig.json +++ b/packages/react/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../tsconfig.json", "compilerOptions": { "module": "esnext", + "target": "esnext", "declarationDir" : "./dist", "lib": [ "dom", @@ -17,11 +18,9 @@ "noImplicitThis": true, "noImplicitAny": true, "strictNullChecks": true, - "suppressImplicitAnyIndexErrors": true, "noUnusedLocals": false, "noUnusedParameters": true, "allowSyntheticDefaultImports": true, - "target": "es5", "allowJs": true, "skipLibCheck": true, "strict": true, @@ -29,14 +28,21 @@ "resolveJsonModule": true, "isolatedModules": false, "emitDeclarationOnly": true, - "types": ["jest", "node"], - "baseUrl": "src" + "types": ["node"], + "baseUrl": "src", }, - "include": ["src/index.tsx"], + "include": ["src/**/*.ts", "src/**/*.tsx"], "exclude": [ "node_modules", "dist", "playground", - "__tests__" - ] + ], + "ts-node": { + "compilerOptions": { + "module": "CommonJS", + "target": "es5", + "esModuleInterop": true, + "moduleResolution": "node", + } + } } diff --git a/packages/react/tsconfig.test.json b/packages/react/tsconfig.test.json deleted file mode 100644 index 9c57932d..00000000 --- a/packages/react/tsconfig.test.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "module": "ES2015" - } -} From a42d2d308eb4b669ff7a8d009800d22bf380f32b Mon Sep 17 00:00:00 2001 From: jerzy-mankowski Date: Mon, 25 Nov 2024 14:57:29 +0100 Subject: [PATCH 002/115] feat: eslint setup --- packages/react/.eslintrc | 15 +++++++--- packages/react/jest.config.json | 50 --------------------------------- packages/react/package.json | 9 +++--- 3 files changed, 16 insertions(+), 58 deletions(-) diff --git a/packages/react/.eslintrc b/packages/react/.eslintrc index 2e8b1056..4d713cf7 100644 --- a/packages/react/.eslintrc +++ b/packages/react/.eslintrc @@ -6,15 +6,21 @@ "plugin:@typescript-eslint/eslint-recommended" ], "env": { - "node": true, - "jest": true + "node": true }, + "plugins": [ + "react", + "@typescript-eslint" + ], "parserOptions": { "ecmaVersion": 2020, "ecmaFeatures": { "legacyDecorators": true, "jsx": true - } + }, + "project": [ + "./tsconfig.json" + ] }, "settings": { "react": { @@ -29,6 +35,7 @@ "react/no-unused-prop-types": 0, "import/export": 0, "no-unused-vars": "off", - "semi": "off" + "semi": "off", + "@typescript-eslint/switch-exhaustiveness-check": "error" } } diff --git a/packages/react/jest.config.json b/packages/react/jest.config.json index 700a234c..e69de29b 100644 --- a/packages/react/jest.config.json +++ b/packages/react/jest.config.json @@ -1,50 +0,0 @@ -{ - "preset": "ts-jest", - "setupFiles": ["./setup.jest.ts"], - "bail": true, - "collectCoverageFrom": [ - "/src/**/*.ts" - ], - "transform": { - "^.+\\.(js|ts|tsx)$": "ts-jest" - }, - "testEnvironment": "jsdom", - "moduleFileExtensions": [ - "ts", - "tsx", - "js", - "jsx", - "json", - "node" - ], - "modulePaths": [ - "node_modules", - "/src" - ], - "testPathIgnorePatterns": [ - "./__tests__/testUtils" - ], - "moduleNameMapper": { - "^@cloudinary/html": "/../html/src" - }, - "snapshotSerializers": ["enzyme-to-json/serializer"], - "coverageThreshold": { - "global": { - "branches": 95, - "functions": 95, - "lines": 95, - "statements": 95 - } - }, - "globals": { - "ts-jest": { - "diagnostics": false - } - }, - "transformIgnorePatterns": [ - "/node_modules\/(?!@cloudinary/(html|url-gen|transformation-builder-sdk))(.*)" - ], - "setupFilesAfterEnv": [ - "./src/setupTests.js" - ] -} diff --git a/packages/react/package.json b/packages/react/package.json index 0f3f9b61..3f5de28f 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -14,10 +14,11 @@ ], "sideEffects": false, "engines": { - "node": ">=10" + "node": ">=16" }, "scripts": { "build": "npm run build --prefix ../html && tsc && rollup -c && cp package.json ./dist", + "lint": "eslint src/**/*", "test": "mocha src/**/*.test.*", "test-coverage": "exit 1" }, @@ -36,11 +37,11 @@ "@types/node": "^12.12.38", "@types/react": "^16.9.27", "@types/react-dom": "^16.9.7", - "@typescript-eslint/eslint-plugin": "^2.26.0", - "@typescript-eslint/parser": "^2.26.0", + "@typescript-eslint/eslint-plugin": "^5.62.0", + "@typescript-eslint/parser": "^5.62.0", "babel-eslint": "^10.0.3", "cross-env": "^7.0.2", - "eslint": "^6.8.0", + "eslint": "^8.0.1", "eslint-config-prettier": "^6.7.0", "eslint-config-standard": "^14.1.0", "eslint-config-standard-react": "^9.2.0", From fcc68bcc81b710087ad74f6aac07c01a552a28df Mon Sep 17 00:00:00 2001 From: jerzy-mankowski Date: Mon, 25 Nov 2024 14:58:58 +0100 Subject: [PATCH 003/115] chore: removed old tests --- .../react/__tests__/AdvancedImage.test.tsx | 53 ----- .../react/__tests__/AdvancedVideo.test.tsx | 197 ------------------ .../react/__tests__/accessibility.test.tsx | 56 ----- .../react/__tests__/analytics-ssr.test.tsx | 35 ---- packages/react/__tests__/analytics.test.tsx | 28 --- packages/react/__tests__/lazyload.test.tsx | 45 ---- packages/react/__tests__/package.tests.tsx | 16 -- packages/react/__tests__/placeholder.test.tsx | 94 --------- packages/react/__tests__/responsive.test.tsx | 157 -------------- .../__tests__/testUtils/dispatchResize.tsx | 12 -- .../testUtils/responsiveHelperWrapper.tsx | 22 -- 11 files changed, 715 deletions(-) delete mode 100644 packages/react/__tests__/AdvancedImage.test.tsx delete mode 100644 packages/react/__tests__/AdvancedVideo.test.tsx delete mode 100644 packages/react/__tests__/accessibility.test.tsx delete mode 100644 packages/react/__tests__/analytics-ssr.test.tsx delete mode 100644 packages/react/__tests__/analytics.test.tsx delete mode 100644 packages/react/__tests__/lazyload.test.tsx delete mode 100644 packages/react/__tests__/package.tests.tsx delete mode 100644 packages/react/__tests__/placeholder.test.tsx delete mode 100644 packages/react/__tests__/responsive.test.tsx delete mode 100644 packages/react/__tests__/testUtils/dispatchResize.tsx delete mode 100644 packages/react/__tests__/testUtils/responsiveHelperWrapper.tsx diff --git a/packages/react/__tests__/AdvancedImage.test.tsx b/packages/react/__tests__/AdvancedImage.test.tsx deleted file mode 100644 index d0ae7cc4..00000000 --- a/packages/react/__tests__/AdvancedImage.test.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import { AdvancedImage } from '../src' -import { CloudinaryImage } from '@cloudinary/url-gen/assets/CloudinaryImage'; -import { mount } from 'enzyme'; -import React from 'react'; - -const cloudinaryImage = new CloudinaryImage('sample', { cloudName: 'demo' }, { analytics: false }); - -describe('AdvancedImage', () => { - it('is truthy', () => { - expect(AdvancedImage).toBeTruthy() - }); - it('should create an img tag', async function() { - const component = await mount(); - expect(component.html()).toContain('src="https://res.cloudinary.com/demo/image/upload/sample"'); - }); - - it('should add style to img component', async function() { - const component = await mount(); - expect(component.find('img').prop('style')).toStrictEqual({ opacity: '0.5' }); - }); - - it('should resolve with a cancel on unmount', function(done) { - const component = mount( - { - return new Promise((resolve) => { - htmlPluginState.cleanupCallbacks.push(() => { - resolve('canceled'); - }); - }).then((res) => { - expect(res).toBe('canceled'); - done(); - }); - }]} - />); - - component.unmount(); - }); - - it('componentDidUpdate should trigger plugin rerun', function() { - const mock = jest.fn(); - const component = mount(); - - // plugins called once - expect(mock).toHaveBeenCalledTimes(1); - - // trigger componentDidUpdate - component.setProps(''); - - expect(mock).toHaveBeenCalledTimes(2); - }); -}); diff --git a/packages/react/__tests__/AdvancedVideo.test.tsx b/packages/react/__tests__/AdvancedVideo.test.tsx deleted file mode 100644 index c51730aa..00000000 --- a/packages/react/__tests__/AdvancedVideo.test.tsx +++ /dev/null @@ -1,197 +0,0 @@ -import { AdvancedVideo } from '../src'; -import { CloudinaryImage, CloudinaryVideo } from '@cloudinary/url-gen'; -import { mount } from 'enzyme'; -import React from 'react'; -import { auto, theora, vp9 } from '@cloudinary/url-gen/qualifiers/videoCodec'; -import { videoCodec } from '@cloudinary/url-gen/actions/transcode'; - -const cloudinaryImage = new CloudinaryImage('sample', { cloudName: 'demo' }, { analytics: false }); -const cloudinaryVideo = new CloudinaryVideo('sample', { cloudName: 'demo' }, { analytics: false }); -const cloudinaryVideoWithExtension = new CloudinaryVideo('sample.mp4', { cloudName: 'demo' }, { analytics: false }); -const cloudinaryVideoWithAnalytics = new CloudinaryVideo('sample', { cloudName: 'demo' }, { analytics: true }); - -describe('AdvancedVideo', () => { - it('should render video with default sources', function (done) { - const component = mount(); - setTimeout(() => { - expect(component.html()).toContain( - ''); - done(); - }, 0);// one tick - }); - - it('should generate url sources with correct placement of extension and url analytics', function (done) { - const component = mount(); - setTimeout(() => { - expect(component.html()).toContain( - 'https://res.cloudinary.com/demo/video/upload/sample.webm?_a='); - expect(component.html()).toContain( - 'https://res.cloudinary.com/demo/video/upload/sample.ogv?_a='); - expect(component.html()).toContain( - 'https://res.cloudinary.com/demo/video/upload/sample.mp4?_a='); - done(); - }, 0);// one tick - }); - - it('should render video with input sources', function (done) { - const sources = [ - { - type: 'mp4', - codecs: ['vp8', 'vorbis'], - transcode: videoCodec(auto()) - }, - { - type: 'webm', - codecs: ['avc1.4D401E', 'mp4a.40.2'], - transcode: videoCodec(vp9()) - }]; - - const component = mount(); - - setTimeout(() => { - expect(component.html()).toContain( - ''); - done(); - }, 0);// one tick - }); - - it('should render video with input sources when using useFetchFormat', function (done) { - const sources = [ - { - type: 'mp4', - codecs: ['vp8', 'vorbis'], - transcode: videoCodec(auto()) - }, - { - type: 'webm', - codecs: ['avc1.4D401E', 'mp4a.40.2'], - transcode: videoCodec(vp9()) - }, - { - type: 'ogv', - codecs: ['theora'], - transcode: videoCodec(theora()) - }]; - - const component = mount(); - - setTimeout(() => { - expect(component.html()).toContain( - ''); - done(); - }, 0);// one tick - }); - - it('should pass video attributes', function (done) { - const component = mount(); - - setTimeout(() => { - expect(component.html()).toContain('loop=""'); - expect(component.html()).not.toContain('playsinline'); - expect(component.html()).toContain('muted=""'); - expect(component.html()).toContain('autoplay=""'); - expect(component.html()).toContain('controls=""'); - done(); - }, 1000);// one tick - }); - - it('should contain poster', function (done) { - const component = mount(); - - setTimeout(() => { - expect(component.html()).toContain('poster="www.example.com"'); - done(); - }, 0);// one tick - }); - - it('should contain poster when "auto" is passed as cldPoster', function (done) { - const component = mount(); - - setTimeout(() => { - expect(component.html()).toContain('poster="https://res.cloudinary.com/demo/video/upload/q_auto/f_jpg/so_auto/sample"'); - done(); - }, 0);// one tick - }); - - it('should contain poster when cloudinary image is passed as cldPoster', function (done) { - const component = mount(); - - setTimeout(() => { - expect(component.html()).toContain('poster="https://res.cloudinary.com/demo/image/upload/sample"'); - done(); - }, 0);// one tick - }); - - it('should simulate onPlay event', function (done) { - const mockCallBack = jest.fn(); - - const component = mount(); - setTimeout(() => { - component.find('video').simulate('play'); - expect(mockCallBack.mock.calls.length).toEqual(1); - done(); - }, 0);// one tick - }); - - it('should simulate onLoadStart event', function (done) { - const mockCallBack = jest.fn(); - - const component = mount(); - setTimeout(() => { - component.find('video').simulate('loadstart'); - expect(mockCallBack.mock.calls.length).toEqual(1); - done(); - }, 0);// one tick - }); - - it('should simulate onEnded event', function (done) { - const mockCallBack = jest.fn(); - - const component = mount(); - setTimeout(() => { - component.find('video').simulate('ended'); - expect(mockCallBack.mock.calls.length).toEqual(1); - done(); - }, 0);// one tick - }); - - it('should simulate onError event', function (done) { - const mockCallBack = jest.fn(); - - const component = mount(); - setTimeout(() => { - component.find('video').simulate('error'); - expect(mockCallBack.mock.calls.length).toEqual(1); - done(); - }, 0);// one tick - }); - - it('should simulate onPlaying event', function (done) { - const mockCallBack = jest.fn(); - - const component = mount(); - setTimeout(() => { - component.find('video').simulate('playing'); - expect(mockCallBack.mock.calls.length).toEqual(1); - done(); - }, 0);// one tick - }); - - it('Should support forwarding innerRef to underlying video element', () => { - const myRef = React.createRef(); - mount(); - const video: any = myRef.current; - - ['play', 'pause', 'canPlayType', 'addTextTrack'].forEach((func) => { - expect(typeof video[func]).toBe('function'); - }); - }); -}); diff --git a/packages/react/__tests__/accessibility.test.tsx b/packages/react/__tests__/accessibility.test.tsx deleted file mode 100644 index 059c286a..00000000 --- a/packages/react/__tests__/accessibility.test.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import { AdvancedImage, accessibility } from '../src'; -import { CloudinaryImage } from '@cloudinary/url-gen/assets/CloudinaryImage'; -import { mount } from 'enzyme'; -import React from 'react'; - -const cloudinaryImage = new CloudinaryImage('sample', { cloudName: 'demo' }, { analytics: false }); - -describe('accessibility', () => { - it('should apply default', function (done) { - const component = mount(); - setTimeout(() => { - expect(component.html()).toContain('src="https://res.cloudinary.com/demo/image/upload/co_black,e_colorize:70/sample"'); - done(); - }, 0);// one tick - }); - - it('should apply darkmode', function () { - const component = mount(); - setTimeout(() => { - expect(component.html()).toContain('src="https://res.cloudinary.com/demo/image/upload/co_black,e_colorize:70/sample"'); - }, 0);// one tick - }); - - it('should apply brightmode', function (done) { - const component = mount(); - setTimeout(() => { - expect(component.html()).toContain('src="https://res.cloudinary.com/demo/image/upload/co_white,e_colorize:40/sample"'); - done(); - }, 0);// one tick - }); - - it('should apply monochrome', function (done) { - const component = mount(); - setTimeout(() => { - expect(component.html()).toContain('src="https://res.cloudinary.com/demo/image/upload/e_grayscale/sample"'); - done(); - }, 0);// one tick - }); - - it('should apply colorblind', function (done) { - const component = mount(); - setTimeout(() => { - expect(component.html()).toContain('src="https://res.cloudinary.com/demo/image/upload/e_assist_colorblind/sample"'); - done(); - }, 0);// one tick - }); - - it('should default if supplied with incorrect mode', function (done) { - const component = mount(); - setTimeout(() => { - expect(component.html()) - .toBe(''); - done(); - }, 0);// one tick - }); -}); diff --git a/packages/react/__tests__/analytics-ssr.test.tsx b/packages/react/__tests__/analytics-ssr.test.tsx deleted file mode 100644 index 1d200454..00000000 --- a/packages/react/__tests__/analytics-ssr.test.tsx +++ /dev/null @@ -1,35 +0,0 @@ -/** - * @jest-environment node - */ -import { AdvancedImage } from '../src' -import { CloudinaryImage } from '@cloudinary/url-gen/assets/CloudinaryImage' -import React from 'react' -import { SDKAnalyticsConstants } from '../src/internal/SDKAnalyticsConstants' -// @ts-ignore -import { version } from '../package.json' -import { renderToString } from 'react-dom/server' - -const cloudinaryImage = new CloudinaryImage('sample', { cloudName: 'demo' }) - -describe('ssr analytics', () => { - beforeEach(() => { - SDKAnalyticsConstants.sdkSemver = '1.0.0' - SDKAnalyticsConstants.techVersion = '10.2.5' - }) - - afterEach(() => { - SDKAnalyticsConstants.sdkSemver = version - SDKAnalyticsConstants.techVersion = React.version - }) - it('creates an img with analytics', function (done) { - const ElementImageHtml = renderToString( - - ) - setTimeout(() => { - expect(ElementImageHtml).toContain( - 'https://res.cloudinary.com/demo/image/upload/sample?_a=DAJAABDSZAA0' - ) - done() - }, 0) // one tick - }) -}) diff --git a/packages/react/__tests__/analytics.test.tsx b/packages/react/__tests__/analytics.test.tsx deleted file mode 100644 index 96f468d6..00000000 --- a/packages/react/__tests__/analytics.test.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import { AdvancedImage } from '../src'; -import { CloudinaryImage } from '@cloudinary/url-gen/assets/CloudinaryImage'; -import { mount } from 'enzyme'; -import React from 'react'; -import { SDKAnalyticsConstants } from '../src/internal/SDKAnalyticsConstants'; -// @ts-ignore -import { version } from '../package.json'; - -const cloudinaryImage = new CloudinaryImage('sample', { cloudName: 'demo' }); - -describe('analytics', () => { - beforeEach(() => { - SDKAnalyticsConstants.sdkSemver = '1.0.0'; - SDKAnalyticsConstants.techVersion = '10.2.5'; - }); - - afterEach(() => { - SDKAnalyticsConstants.sdkSemver = version; - SDKAnalyticsConstants.techVersion = React.version; - }); - it('creates an img with analytics', function (done) { - const component = mount(); - setTimeout(() => { - expect(component.html()).toEqual(''); - done(); - }, 0);// one tick - }); -}); diff --git a/packages/react/__tests__/lazyload.test.tsx b/packages/react/__tests__/lazyload.test.tsx deleted file mode 100644 index a56582ab..00000000 --- a/packages/react/__tests__/lazyload.test.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import { AdvancedImage, lazyload } from '../src' -import { CloudinaryImage } from '@cloudinary/url-gen/assets/CloudinaryImage'; -import { mount } from 'enzyme'; -import React from 'react'; -import { testWithMockedIntersectionObserver } from '../../../testUtils/testWithMockedIntersectionObserver' - -const cloudinaryImage = new CloudinaryImage('sample', { cloudName: 'demo' }, { analytics: false }); - -describe('lazy-load', () => { - it('should not have src pre-scroll', async function() { - const component = await mount( -
-
- -
- ); - // no src pre scroll - expect(component.html()).toBe('
'); - }); - - it('should have src when in view', function(done) { - const elm = document.createElement('img'); - testWithMockedIntersectionObserver((mockIntersectionEvent: ({}) => void) => { - const component = mount(); - mockIntersectionEvent([{ isIntersecting: true, target: component.getDOMNode() }]); - setTimeout(() => { - expect(component.html()).toContain('src="https://res.cloudinary.com/demo/image/upload/sample"'); - done(); - }, 0);// one tick - }); - }); - - it('should set lazyload root margin and threshold', function(done) { - const elm = document.createElement('img'); - testWithMockedIntersectionObserver((mockIntersectionEvent: ({}) => void) => { - // @ts-ignore - const component = mount(); - mockIntersectionEvent([{ isIntersecting: true, target: component.getDOMNode() }]); - setTimeout(() => { - expect(component.html()).toContain('src="https://res.cloudinary.com/demo/image/upload/sample"'); - done(); - }, 0);// one tick - }); - }); -}); diff --git a/packages/react/__tests__/package.tests.tsx b/packages/react/__tests__/package.tests.tsx deleted file mode 100644 index 7a176391..00000000 --- a/packages/react/__tests__/package.tests.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import {execSync} from "child_process"; -import fs from 'fs'; -import * as path from "path"; - -describe('tests for the package output', () => { - it('ensures that the pacakge contains the right files', () => { - execSync('npm run build'); - const dirContents = fs.readdirSync(path.resolve(__dirname, '../dist')); - - expect(dirContents.includes('index.js')).toBe(true); - expect(dirContents.includes('index.umd.js')).toBe(true); - expect(dirContents.includes('index.cjs.js')).toBe(true); - expect(dirContents.includes('index.d.ts')).toBe(true); - expect(dirContents.includes('package.json')).toBe(true); - }); -}); diff --git a/packages/react/__tests__/placeholder.test.tsx b/packages/react/__tests__/placeholder.test.tsx deleted file mode 100644 index e9a90c84..00000000 --- a/packages/react/__tests__/placeholder.test.tsx +++ /dev/null @@ -1,94 +0,0 @@ -import { AdvancedImage, placeholder } from '../src' -import { CloudinaryImage } from '@cloudinary/url-gen/assets/CloudinaryImage'; -import { PLACEHOLDER_IMAGE_OPTIONS } from '../../html/src/utils/internalConstants'; -import { mount } from 'enzyme'; -import React from 'react'; -import { sepia } from '@cloudinary/url-gen/actions/effect'; - -describe('placeholder', () => { - let cloudinaryImage: CloudinaryImage; - - const mockImage = { - src: null, - onload: () => {}, - onerror: () => {} - }; - beforeEach(() => { - // @ts-ignore - window.Image = function() { return mockImage }; - cloudinaryImage = new CloudinaryImage('sample', { cloudName: 'demo' }, { analytics: false }); - }); - it('should apply default', function (done) { - const component = mount(); - setTimeout(() => { - expect(component.html()).toContain(`src="https://res.cloudinary.com/demo/image/upload/${PLACEHOLDER_IMAGE_OPTIONS.vectorize}/sample"`); - done(); - }, 0);// one tick - }); - - it("should apply 'vectorize'", function () { - const component = mount(); - setTimeout(() => { - expect(component.html()).toContain(`src="https://res.cloudinary.com/demo/image/upload/${PLACEHOLDER_IMAGE_OPTIONS.vectorize}/sample"`); - }, 0);// one tick - }); - - it('should apply pixelate', function (done) { - const component = mount(); - setTimeout(() => { - expect(component.html()).toContain(`src="https://res.cloudinary.com/demo/image/upload/${PLACEHOLDER_IMAGE_OPTIONS.pixelate}/sample"`); - done(); - }, 0);// one tick - }); - - it('should apply blur', function (done) { - const component = mount(); - setTimeout(() => { - expect(component.html()).toContain(`src="https://res.cloudinary.com/demo/image/upload/${PLACEHOLDER_IMAGE_OPTIONS.blur}/sample"`); - done(); - }, 0);// one tick - }); - - it('should apply predominant-color', function (done) { - const component = mount(); - setTimeout(() => { - expect(component.html()).toContain(`src="https://res.cloudinary.com/demo/image/upload/${PLACEHOLDER_IMAGE_OPTIONS['predominant-color']}/sample"`); - done(); - }, 0);// one tick - }); - - it('should default if supplied with incorrect mode', function (done) { - const component = mount(); - setTimeout(() => { - expect(component.html()).toContain(`src="https://res.cloudinary.com/demo/image/upload/${PLACEHOLDER_IMAGE_OPTIONS.vectorize}/sample"`); - done(); - }, 0);// one tick - }); - - it('should append placeholder transformation', function (done) { - cloudinaryImage.effect(sepia()); - const component = mount(); - setTimeout(() => { - expect(component.html()).toContain(`src="https://res.cloudinary.com/demo/image/upload/e_sepia/${PLACEHOLDER_IMAGE_OPTIONS.vectorize}/sample"`); - done(); - }, 0);// one tick - }); - - /* - This test is built with two setTimouts since the placeholder plugin makes use of two promises. - The placeholder image loads first. Once it loads, the promise is resolved and the - larger image will load. Once the larger image loads, promised and plugin is resolved. - */ - it('should not fail error', function (done) { - const component = mount(); - setTimeout(() => { - // @ts-ignore - component.getDOMNode().onload(); // simulate element onload - setTimeout(() => { - mockImage.onerror(); // simulate image onerror - expect(mockImage.src).toBe('https://res.cloudinary.com/demo/image/upload/sample'); - done(); - }) - }, 5);// one tick - }); -}); diff --git a/packages/react/__tests__/responsive.test.tsx b/packages/react/__tests__/responsive.test.tsx deleted file mode 100644 index 9396c6b0..00000000 --- a/packages/react/__tests__/responsive.test.tsx +++ /dev/null @@ -1,157 +0,0 @@ -import { AdvancedImage, responsive } from '../src' -import { CloudinaryImage } from '@cloudinary/url-gen/assets/CloudinaryImage'; -import { mount } from 'enzyme'; -import React from 'react'; -import { ResponsiveHelper } from './testUtils/responsiveHelperWrapper'; -import { crop } from '@cloudinary/url-gen/actions/resize'; -import { dispatchResize } from './testUtils/dispatchResize'; -import FakeTimers from '@sinonjs/fake-timers' - -const cloudinaryImage = new CloudinaryImage('sample', { cloudName: 'demo' }, { analytics: false }); - -describe('responsive', () => { - let clock:any; - beforeEach(() => { - clock = FakeTimers.install() - }); - afterEach(() => { - clock.uninstall() - }); - - it('should apply initial container width (default 250)', async function () { - const component = mount( - - - ); - - window.dispatchEvent(new Event('resize')); - clock.tick(100); // timeout for debounce - - const el = component.find('#wrapper').getDOMNode(); - expect(el.clientWidth).toBe(250); - }); - - - it('Should respect single step and ignore default width of 250 (When Step < Width)', async function () { - const component = mount( - - - ); - - clock.tick(100); // timeout for debounce - - // Output is exactly 300 due to internal rounding: ROUND_UP(CONTAINER / STEP) * STEP - // When STEP < CONTAINER, output is always a multiplication of STEP - expect(component.html()).toContain('src="https://res.cloudinary.com/demo/image/upload/c_scale,w_300/sample"'); - }); - - - it('Should respect single step and ignore default width of 250 (When Step > Width)', async function () { - const component = mount( - - - ); - - clock.tick(100); // timeout for debounce - - // Output is exactly 251 due to internal rounding: ROUND_UP(CONTAINER / STEP) * STEP - // When STEP > CONTAINER, output is always STEP. - expect(component.html()).toContain('src="https://res.cloudinary.com/demo/image/upload/c_scale,w_251/sample"'); - }); - - it('Should respect steps and ignore default width of 250', async function () { - const component = mount( - - - ); - - clock.tick(100); // timeout for debounce - - // Output is closest number to parentElement, never exceeding the width of the max step ) - expect(component.html()).toContain('src="https://res.cloudinary.com/demo/image/upload/c_scale,w_30/sample"'); - }); - - it('should update container width on window resize', function () { - const component = mount( - - - ); - - const el = dispatchResize(component, 100); - clock.tick(100); // timeout for debounce - expect(el.clientWidth).toBe(100); - }); - - it('should step by the 100th', async function () { - const component = mount( - - - ); - - await clock.tickAsync(0); // one tick - window.dispatchEvent(new Event('resize')); - await clock.tickAsync(100); // timeout for debounce - expect(component.html()).toContain('src="https://res.cloudinary.com/demo/image/upload/c_scale,w_300/sample"'); - }); - - it('should step by breakpoints', async function () { - const component = mount( - - - ); - - await clock.tickAsync(0); // one tick - window.dispatchEvent(new Event('resize')); - await clock.tickAsync(100); // timeout for debounce - - expect(component.html()).toContain('src="https://res.cloudinary.com/demo/image/upload/c_scale,w_800/sample"'); - - // simulate resize to 975 - await clock.tickAsync(0); // one tick - dispatchResize(component, 975); - await clock.tickAsync(100); // timeout for debounce - - expect(component.html()).toContain('src="https://res.cloudinary.com/demo/image/upload/c_scale,w_1000/sample"'); - }); - - it('should not resize to larger than provided breakpoints', async function () { - const component = mount( - - - ); - - await clock.tickAsync(0); // one tick - dispatchResize(component, 4000); - await clock.tickAsync(100); // timeout for debounce - - expect(component.html()).toContain('src="https://res.cloudinary.com/demo/image/upload/c_scale,w_3000/sample"'); - }); - - it('should handle unordered breakpoints', async function () { - const component = mount( - - - ); - - await clock.tickAsync(0); // one tick - dispatchResize(component, 5000); - await clock.tickAsync(100); // timeout for debounce - - expect(component.html()).toContain('src="https://res.cloudinary.com/demo/image/upload/c_scale,w_3000/sample"'); - }); - - it('should append to existing transformation', async function () { - cloudinaryImage.resize(crop('500')); - - const component = mount( - - - ); - - await clock.tickAsync(0); // one tick - window.dispatchEvent(new Event('resize')); - await clock.tickAsync(100); // timeout for debounce - - expect(component.html()).toContain('src="https://res.cloudinary.com/demo/image/upload/c_crop,w_500/c_scale,w_250/sample"'); - }); -}); diff --git a/packages/react/__tests__/testUtils/dispatchResize.tsx b/packages/react/__tests__/testUtils/dispatchResize.tsx deleted file mode 100644 index 04406f24..00000000 --- a/packages/react/__tests__/testUtils/dispatchResize.tsx +++ /dev/null @@ -1,12 +0,0 @@ -/** - * Util function used to dispatch a resize event used in the responsive tests - * @param component - * @param resizeValue - */ -export function dispatchResize(component: any, resizeValue:number){ - const el = component.find('#wrapper').getDOMNode(); - Object.defineProperty(el, 'clientWidth', {value: resizeValue, configurable: true}); - window.dispatchEvent(new Event('resize')); - - return el; -} diff --git a/packages/react/__tests__/testUtils/responsiveHelperWrapper.tsx b/packages/react/__tests__/testUtils/responsiveHelperWrapper.tsx deleted file mode 100644 index e86ad38c..00000000 --- a/packages/react/__tests__/testUtils/responsiveHelperWrapper.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import React, {useEffect, useRef, useState} from 'react'; - -/** - * Simulates clientWidth - * Initializes clientWidth with 250 - */ -export function ResponsiveHelper(props: { children: React.ReactNode }) { - const ref = useRef(null); - const [refElement, setRefElement] = useState(false) - useEffect(() => { - const parentElement = ref.current as unknown as HTMLElement; - Object.defineProperty(parentElement, 'clientWidth', {value: 250, configurable: true}); - // @ts-ignore - setRefElement(ref.current); - }, []); - return
- {refElement && props.children} -
-} From bbdef3f8fa877450b58940458ac85732b742b564 Mon Sep 17 00:00:00 2001 From: jerzy-mankowski Date: Mon, 25 Nov 2024 15:23:33 +0100 Subject: [PATCH 004/115] chore: initial AdvancedImage + types --- packages/react/src/AdvancedImage.tsx | 242 +++++++++++++-------------- packages/react/src/types.ts | 45 +++++ 2 files changed, 166 insertions(+), 121 deletions(-) create mode 100644 packages/react/src/types.ts diff --git a/packages/react/src/AdvancedImage.tsx b/packages/react/src/AdvancedImage.tsx index 434745ec..9ac4c02a 100644 --- a/packages/react/src/AdvancedImage.tsx +++ b/packages/react/src/AdvancedImage.tsx @@ -1,127 +1,127 @@ -import React from 'react'; -import { CloudinaryImage } from '@cloudinary/url-gen/assets/CloudinaryImage'; - -import { - HtmlImageLayer, - Plugins, - isBrowser, - serverSideSrc, - cancelCurrentlyRunningPlugins -} from '@cloudinary/html' -import { SDKAnalyticsConstants } from './internal/SDKAnalyticsConstants'; - -interface ImgProps { - cldImg: CloudinaryImage; - plugins?: Plugins; - [x: string]: any; -} - -/** - * @mixin ReactSDK - * @description The Cloudinary React SDK contains components like \ to easily render your media assets from Cloudinary. - * The SDK also comes with support for optional JS plugins that make the components smart, with features like lazy loading, placeholder, accessibility & responsiveness. - * - * @example - * - * Please note that the order of the plugins is important. See {@link https://cloudinary.com/documentation/sdks/js/frontend-frameworks/index.html#plugin-order|Plugin Order} for more details. - * - * // Example - * import {CloudinaryImage} from "@cloudinary/url-gen/assets/CloudinaryImage"; - * import { - * AdvancedImage, - * accessibility, - * responsive, - * lazyload, - * placeholder - * } from '@cloudinary/react'; - * - * const App = () => { - * - * const myCld = new Cloudinary({ cloudName: 'demo'}); - * let img = myCld().image('sample'); - * - * return ( - *
- *
- * - *
- * ) - * }; - * - * - * - * - * - */ - -/** - * @memberOf ReactSDK - * @type {Component} - * @description The Cloudinary image component. - * @prop {CloudinaryImage} cldImg Generated by @cloudinary/url-gen - * @prop {Plugins} plugins Advanced image component plugins accessibility(), responsive(), lazyload(), placeholder() - */ -class AdvancedImage extends React.Component { - imageRef: React.RefObject; - htmlLayerInstance: HtmlImageLayer; - - constructor(props: ImgProps) { - super(props); - this.imageRef = React.createRef(); - } +import React, { forwardRef } from 'react'; +import {type CloudinaryImage as UrlGenCloudinaryImage} from '@cloudinary/url-gen/assets/CloudinaryImage'; +import {CloudinaryImageFormat, CloudinaryImageQuality, CloudinaryRemoveBackgroundOption} from "./types"; - /** - * On mount, creates a new HTMLLayer instance and initializes with ref to img element, - * user generated cloudinaryImage and the plugins to be used. - */ - componentDidMount() { - this.htmlLayerInstance = new HtmlImageLayer( - this.imageRef.current, - this.props.cldImg, - this.props.plugins, - SDKAnalyticsConstants - ) - } +interface CloudinaryImgLegacyProps { + cldImg: UrlGenCloudinaryImage; +} - /** - * On update, we cancel running plugins and update image instance with the state of user - * cloudinaryImage and the state of plugins. - */ - componentDidUpdate() { - cancelCurrentlyRunningPlugins(this.htmlLayerInstance.htmlPluginState); - // call html layer to update the dom again with plugins and reset toBeCanceled - this.htmlLayerInstance.update(this.props.cldImg, this.props.plugins, SDKAnalyticsConstants) - } +interface BaseResizeOptions { + width: number; + height: number; +} - /** - * On unmount, we cancel the currently running plugins, and destroy the html layer instance - */ - componentWillUnmount() { - // Safely cancel running events on unmount. - cancelCurrentlyRunningPlugins(this.htmlLayerInstance.htmlPluginState); - this.htmlLayerInstance.unmount(); - } +interface ScaleResizeOptions extends BaseResizeOptions { + type: 'scale'; + aspectRatio?: string; +} - render() { - const { - cldImg, - plugins, - ...otherProps // Assume any other props are for the base element - } = this.props; - if (isBrowser()) { // On client side render - return - } else { // on server side render - const src = serverSideSrc( - this.props.plugins, - this.props.cldImg, - SDKAnalyticsConstants - ); - return - } - } +interface FitResizeOptions extends BaseResizeOptions { + type: 'fit'; + aspectRatio?: string; +} + +interface LimitResizeOptions extends BaseResizeOptions { + type: 'limit'; + aspectRatio?: string; } -export { AdvancedImage }; +interface MfitResizeOptions extends BaseResizeOptions { + type: 'mfit'; + aspectRatio?: string; +} + +interface FillResizeOptions extends BaseResizeOptions { + type: 'fill'; + gravity?: string; + aspectRatio?: string; +} + +interface LfillResizeOptions extends BaseResizeOptions { + type: 'lfill'; + gravity?: string; + aspectRatio?: string; +} + +interface PadResizeOptions extends BaseResizeOptions { + type: 'pad'; + gravity?: string; + aspectRatio?: string; + background?: string; +} + +interface LpadResizeOptions extends BaseResizeOptions { + type: 'lpad'; + gravity?: string; + aspectRatio?: string; + background?: string; +} + +interface MpadResizeOptions extends BaseResizeOptions { + type: 'mpad'; + gravity?: string; + aspectRatio?: string; + background?: string; +} + +interface CropResizeOptions extends BaseResizeOptions { + type: 'crop'; + gravity?: string; + x?: number; + y?: number; + zoom?: number; + aspectRatio?: string; +} + +interface ThumbResizeOptions extends BaseResizeOptions { + type: 'thumb'; + gravity?: string; + aspectRatio?: string; +} + +interface ImaggaCropResizeOptions extends BaseResizeOptions { + type: 'imagga_crop'; + aspectRatio?: string; +} + +interface ImaggaScaleResizeOptions extends BaseResizeOptions { + type: 'imagga_scale'; + aspectRatio?: string; +} + +type ResizeOptions = + | ScaleResizeOptions + | FitResizeOptions + | LimitResizeOptions + | MfitResizeOptions + | FillResizeOptions + | LfillResizeOptions + | PadResizeOptions + | LpadResizeOptions + | MpadResizeOptions + | CropResizeOptions + | ThumbResizeOptions + | ImaggaCropResizeOptions + | ImaggaScaleResizeOptions; + +interface CloudinaryImgNewProps { + src: string; + alt: string; + quality?: CloudinaryImageQuality; + format?: CloudinaryImageFormat; + removeBackground?: CloudinaryRemoveBackgroundOption; + resize?: ResizeOptions; +} +export type CldImageProps = CloudinaryImgNewProps | CloudinaryImgLegacyProps; + +const isCloudinaryImgLegacyProps = (props: CldImageProps): props is CloudinaryImgLegacyProps => 'cldImg' in props; + +export const CloudinaryImg = forwardRef((props: CldImageProps, ref) => { + if (isCloudinaryImgLegacyProps(props)) { + const { cldImg, ...rest } = props; + return + } + + return +}) + diff --git a/packages/react/src/types.ts b/packages/react/src/types.ts new file mode 100644 index 00000000..6d950869 --- /dev/null +++ b/packages/react/src/types.ts @@ -0,0 +1,45 @@ +export type CloudinaryImageQuality = + | 'auto' + | 'auto:good' + | 'auto:eco' + | 'auto:low' + | 'auto:best' + | 'low' + | 'medium' + | 'high' + | 'best' + | 'jpegmini' + | 'jpegmini:0' + | 'jpegmini:1' + | 'jpegmini:2' + | 'good' + | 'eco'; + +export type CloudinaryImageFormat = + | 'auto' + | 'jpg' + | 'png' + | 'gif' + | 'webp' + | 'bmp' + | 'ico' + | 'pdf' + | 'tiff' + | 'eps' + | 'jpc' + | 'jp2' + | 'psd' + | 'svg' + | 'webm' + | 'mp4' + | 'mkv' + | 'flv' + | 'mov' + | '3gp' + | 'avi' + | 'wmv' + | 'avif' + | 'heic' + | 'heif'; + +export type CloudinaryRemoveBackgroundOption = boolean | 'fineEdges'; From 3ce032c906420c6144ab8c68273b8c73eff76fc1 Mon Sep 17 00:00:00 2001 From: jerzy-mankowski Date: Mon, 25 Nov 2024 15:23:55 +0100 Subject: [PATCH 005/115] feat: parseRemoveBackgroundOption + tests --- .../parsers/parseRemoveBackgroundOption.test.ts | 17 +++++++++++++++++ .../src/parsers/parseRemoveBackgroundOption.ts | 14 ++++++++++++++ 2 files changed, 31 insertions(+) create mode 100644 packages/react/src/parsers/parseRemoveBackgroundOption.test.ts create mode 100644 packages/react/src/parsers/parseRemoveBackgroundOption.ts diff --git a/packages/react/src/parsers/parseRemoveBackgroundOption.test.ts b/packages/react/src/parsers/parseRemoveBackgroundOption.test.ts new file mode 100644 index 00000000..850b9874 --- /dev/null +++ b/packages/react/src/parsers/parseRemoveBackgroundOption.test.ts @@ -0,0 +1,17 @@ +import 'mocha' +import assert from 'assert' +import { parseRemoveBackgroundOption } from './parseRemoveBackgroundOption' + +describe('parseRemoveBackgroundOption: returns transformation for', () => { + it('false value', () => { + assert.equal(parseRemoveBackgroundOption(false), '') + }) + + it('true value', () => { + assert.equal(parseRemoveBackgroundOption(true), 'e_background_removal') + }) + + it('string value', () => { + assert.equal(parseRemoveBackgroundOption('fineEdges'), 'e_background_removal:fineedges_y') + }) +}) diff --git a/packages/react/src/parsers/parseRemoveBackgroundOption.ts b/packages/react/src/parsers/parseRemoveBackgroundOption.ts new file mode 100644 index 00000000..0f956a1c --- /dev/null +++ b/packages/react/src/parsers/parseRemoveBackgroundOption.ts @@ -0,0 +1,14 @@ +import { CloudinaryRemoveBackgroundOption } from '../types' + +export const parseRemoveBackgroundOption = (removeBackground: CloudinaryRemoveBackgroundOption) => { + if (!removeBackground) { + return '' + } + + switch (removeBackground) { + case true: + return 'e_background_removal' + case 'fineEdges': + return 'e_background_removal:fineedges_y' + } +} From 9d6c08683e5d450036909d48e92dab498a460afe Mon Sep 17 00:00:00 2001 From: jerzy-mankowski Date: Mon, 25 Nov 2024 15:36:55 +0100 Subject: [PATCH 006/115] fix: parseRemoveBackgroundOption refinement --- .../src/parsers/parseRemoveBackgroundOption.test.ts | 6 +++--- .../react/src/parsers/parseRemoveBackgroundOption.ts | 8 +++----- packages/react/src/types.ts | 10 +++++----- 3 files changed, 11 insertions(+), 13 deletions(-) diff --git a/packages/react/src/parsers/parseRemoveBackgroundOption.test.ts b/packages/react/src/parsers/parseRemoveBackgroundOption.test.ts index 850b9874..825de240 100644 --- a/packages/react/src/parsers/parseRemoveBackgroundOption.test.ts +++ b/packages/react/src/parsers/parseRemoveBackgroundOption.test.ts @@ -3,15 +3,15 @@ import assert from 'assert' import { parseRemoveBackgroundOption } from './parseRemoveBackgroundOption' describe('parseRemoveBackgroundOption: returns transformation for', () => { - it('false value', () => { + it('"false" option', () => { assert.equal(parseRemoveBackgroundOption(false), '') }) - it('true value', () => { + it('"true" option', () => { assert.equal(parseRemoveBackgroundOption(true), 'e_background_removal') }) - it('string value', () => { + it('"fineEdges" option', () => { assert.equal(parseRemoveBackgroundOption('fineEdges'), 'e_background_removal:fineedges_y') }) }) diff --git a/packages/react/src/parsers/parseRemoveBackgroundOption.ts b/packages/react/src/parsers/parseRemoveBackgroundOption.ts index 0f956a1c..b544d858 100644 --- a/packages/react/src/parsers/parseRemoveBackgroundOption.ts +++ b/packages/react/src/parsers/parseRemoveBackgroundOption.ts @@ -1,11 +1,9 @@ import { CloudinaryRemoveBackgroundOption } from '../types' -export const parseRemoveBackgroundOption = (removeBackground: CloudinaryRemoveBackgroundOption) => { - if (!removeBackground) { - return '' - } - +export const parseRemoveBackgroundOption = (removeBackground: CloudinaryRemoveBackgroundOption): '' | `e_background_removal${string}` => { switch (removeBackground) { + case false: + return '' case true: return 'e_background_removal' case 'fineEdges': diff --git a/packages/react/src/types.ts b/packages/react/src/types.ts index 6d950869..4d2b0f89 100644 --- a/packages/react/src/types.ts +++ b/packages/react/src/types.ts @@ -4,16 +4,16 @@ export type CloudinaryImageQuality = | 'auto:eco' | 'auto:low' | 'auto:best' - | 'low' - | 'medium' - | 'high' - | 'best' | 'jpegmini' | 'jpegmini:0' | 'jpegmini:1' | 'jpegmini:2' + | 'low' + | 'eco' + | 'medium' | 'good' - | 'eco'; + | 'high' + | 'best'; export type CloudinaryImageFormat = | 'auto' From 6b827fbab632cf0cec1887359f061011bb8f96c6 Mon Sep 17 00:00:00 2001 From: jerzy-mankowski Date: Mon, 25 Nov 2024 15:37:07 +0100 Subject: [PATCH 007/115] fix: parseQuality + tests --- .../react/src/parsers/parseQuality.test.ts | 65 +++++++++++++++++++ packages/react/src/parsers/parseQuality.ts | 36 ++++++++++ 2 files changed, 101 insertions(+) create mode 100644 packages/react/src/parsers/parseQuality.test.ts create mode 100644 packages/react/src/parsers/parseQuality.ts diff --git a/packages/react/src/parsers/parseQuality.test.ts b/packages/react/src/parsers/parseQuality.test.ts new file mode 100644 index 00000000..f5ff9b7e --- /dev/null +++ b/packages/react/src/parsers/parseQuality.test.ts @@ -0,0 +1,65 @@ +import 'mocha' +import assert from 'assert' +import { parseQuality } from './parseQuality' + +describe('parseQuality: returns correct transformation component for', () => { + it('"auto" option', () => { + assert.equal(parseQuality('auto'), 'q_auto') + }) + + it('"auto:good" option', () => { + assert.equal(parseQuality('auto:good'), 'q_auto:good') + }) + + it('"auto:eco" option', () => { + assert.equal(parseQuality('auto:eco'), 'q_auto:eco') + }) + + it('"auto:low" option', () => { + assert.equal(parseQuality('auto:low'), 'q_auto:low') + }) + + it('"auto:best" option', () => { + assert.equal(parseQuality('auto:best'), 'q_auto:best') + }) + + it('"jpegmini" option', () => { + assert.equal(parseQuality('jpegmini'), 'q_jpegmini') + }) + + it('"jpegmini:0" option', () => { + assert.equal(parseQuality('jpegmini:0'), 'q_jpegmini:0') + }) + + it('"jpegmini:1" option', () => { + assert.equal(parseQuality('jpegmini:1'), 'q_jpegmini:1') + }) + + it('"jpegmini:2" option', () => { + assert.equal(parseQuality('jpegmini:2'), 'q_jpegmini:2') + }) + + it('"low" option', () => { + assert.equal(parseQuality('low'), 'q_low') + }) + + it('"eco" option', () => { + assert.equal(parseQuality('eco'), 'q_eco') + }) + + it('"medium" option', () => { + assert.equal(parseQuality('medium'), 'q_medium') + }) + + it('"good" option', () => { + assert.equal(parseQuality('good'), 'q_good') + }) + + it('"high" option', () => { + assert.equal(parseQuality('high'), 'q_high') + }) + + it('"best" option', () => { + assert.equal(parseQuality('best'), 'q_best') + }) +}) diff --git a/packages/react/src/parsers/parseQuality.ts b/packages/react/src/parsers/parseQuality.ts new file mode 100644 index 00000000..d20ff4e6 --- /dev/null +++ b/packages/react/src/parsers/parseQuality.ts @@ -0,0 +1,36 @@ +import { CloudinaryImageQuality } from '../types' + +export const parseQuality = (quality: CloudinaryImageQuality): `q_${string}` => { + switch (quality) { + case 'auto': + return 'q_auto' + case 'auto:good': + return 'q_auto:good' + case 'auto:eco': + return 'q_auto:eco' + case 'auto:low': + return 'q_auto:low' + case 'auto:best': + return 'q_auto:best' + case 'jpegmini': + return 'q_jpegmini' + case 'jpegmini:0': + return 'q_jpegmini:0' + case 'jpegmini:1': + return 'q_jpegmini:1' + case 'jpegmini:2': + return 'q_jpegmini:2' + case 'low': + return 'q_low' + case 'eco': + return 'q_eco' + case 'medium': + return 'q_medium' + case 'good': + return 'q_good' + case 'high': + return 'q_high' + case 'best': + return 'q_best' + } +} From d0d1ff4926635ba514044787762ef66ac665d269 Mon Sep 17 00:00:00 2001 From: jerzy-mankowski Date: Tue, 26 Nov 2024 10:01:36 +0100 Subject: [PATCH 008/115] fix: parseFormat + tests --- packages/react/src/parsers/parseFormat.test.ts | 9 +++++++++ packages/react/src/parsers/parseFormat.ts | 3 +++ 2 files changed, 12 insertions(+) create mode 100644 packages/react/src/parsers/parseFormat.test.ts create mode 100644 packages/react/src/parsers/parseFormat.ts diff --git a/packages/react/src/parsers/parseFormat.test.ts b/packages/react/src/parsers/parseFormat.test.ts new file mode 100644 index 00000000..f9dd8f0d --- /dev/null +++ b/packages/react/src/parsers/parseFormat.test.ts @@ -0,0 +1,9 @@ +import 'mocha' +import assert from 'assert' +import { parseFormat } from './parseFormat' + +describe('parseFormat', () => { + it('returns URL part for any format', () => { + assert.equal(parseFormat('auto'), 'f_auto') + }) +}) diff --git a/packages/react/src/parsers/parseFormat.ts b/packages/react/src/parsers/parseFormat.ts new file mode 100644 index 00000000..abfdf606 --- /dev/null +++ b/packages/react/src/parsers/parseFormat.ts @@ -0,0 +1,3 @@ +import { CloudinaryImageFormat } from '../types' + +export const parseFormat = (format: CloudinaryImageFormat) => `f_${format}` From c4aeeaf400f57df65cc28f297887408eff537bbb Mon Sep 17 00:00:00 2001 From: jerzy-mankowski Date: Tue, 26 Nov 2024 10:02:01 +0100 Subject: [PATCH 009/115] fix: adjusted image/video types --- packages/react/src/types.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/react/src/types.ts b/packages/react/src/types.ts index 4d2b0f89..93e18588 100644 --- a/packages/react/src/types.ts +++ b/packages/react/src/types.ts @@ -30,6 +30,11 @@ export type CloudinaryImageFormat = | 'jp2' | 'psd' | 'svg' + | 'avif' + | 'heic' + | 'heif'; + +export type CloudinaryVideoFormat = | 'webm' | 'mp4' | 'mkv' @@ -37,9 +42,6 @@ export type CloudinaryImageFormat = | 'mov' | '3gp' | 'avi' - | 'wmv' - | 'avif' - | 'heic' - | 'heif'; + | 'wmv'; export type CloudinaryRemoveBackgroundOption = boolean | 'fineEdges'; From 2b516713bcd0576d389781416d402ef236430b77 Mon Sep 17 00:00:00 2001 From: jerzy-mankowski Date: Wed, 27 Nov 2024 11:37:46 +0100 Subject: [PATCH 010/115] chore: prettier adjustment --- packages/react/.prettierrc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react/.prettierrc b/packages/react/.prettierrc index a9646d44..b657a303 100644 --- a/packages/react/.prettierrc +++ b/packages/react/.prettierrc @@ -1,7 +1,7 @@ { "singleQuote": true, "jsxSingleQuote": true, - "semi": false, + "semi": true, "tabWidth": 2, "bracketSpacing": true, "jsxBracketSameLine": false, From 2bf1ca668b813aab45de81c818615b5409442a5f Mon Sep 17 00:00:00 2001 From: jerzy-mankowski Date: Wed, 27 Nov 2024 11:38:19 +0100 Subject: [PATCH 011/115] fix: parseQuality.ts type refinement --- packages/react/src/parsers/parseQuality.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/react/src/parsers/parseQuality.ts b/packages/react/src/parsers/parseQuality.ts index d20ff4e6..7d462d45 100644 --- a/packages/react/src/parsers/parseQuality.ts +++ b/packages/react/src/parsers/parseQuality.ts @@ -1,6 +1,6 @@ -import { CloudinaryImageQuality } from '../types' +import { Quality } from '../types' -export const parseQuality = (quality: CloudinaryImageQuality): `q_${string}` => { +export const parseQuality = (quality: Quality): `q_${string}` => { switch (quality) { case 'auto': return 'q_auto' From eddfb26e335488803918b9f43790e7c45f5a123b Mon Sep 17 00:00:00 2001 From: jerzy-mankowski Date: Wed, 27 Nov 2024 11:41:39 +0100 Subject: [PATCH 012/115] fix: parseRemoveBackground.ts refinement --- .../src/parsers/parseRemoveBackground.test.ts | 17 +++++++++++++++++ ...groundOption.ts => parseRemoveBackground.ts} | 2 +- 2 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 packages/react/src/parsers/parseRemoveBackground.test.ts rename packages/react/src/parsers/{parseRemoveBackgroundOption.ts => parseRemoveBackground.ts} (64%) diff --git a/packages/react/src/parsers/parseRemoveBackground.test.ts b/packages/react/src/parsers/parseRemoveBackground.test.ts new file mode 100644 index 00000000..8da12a49 --- /dev/null +++ b/packages/react/src/parsers/parseRemoveBackground.test.ts @@ -0,0 +1,17 @@ +import 'mocha' +import assert from 'assert' +import { parseRemoveBackground } from './parseRemoveBackground' + +describe('parseRemoveBackground: returns transformation for', () => { + it('"false" option', () => { + assert.equal(parseRemoveBackground(false), '') + }) + + it('"true" option', () => { + assert.equal(parseRemoveBackground(true), 'e_background_removal') + }) + + it('"fineEdges" option', () => { + assert.equal(parseRemoveBackground('fineEdges'), 'e_background_removal:fineedges_y') + }) +}) diff --git a/packages/react/src/parsers/parseRemoveBackgroundOption.ts b/packages/react/src/parsers/parseRemoveBackground.ts similarity index 64% rename from packages/react/src/parsers/parseRemoveBackgroundOption.ts rename to packages/react/src/parsers/parseRemoveBackground.ts index b544d858..79dc04b6 100644 --- a/packages/react/src/parsers/parseRemoveBackgroundOption.ts +++ b/packages/react/src/parsers/parseRemoveBackground.ts @@ -1,6 +1,6 @@ import { CloudinaryRemoveBackgroundOption } from '../types' -export const parseRemoveBackgroundOption = (removeBackground: CloudinaryRemoveBackgroundOption): '' | `e_background_removal${string}` => { +export const parseRemoveBackground = (removeBackground: CloudinaryRemoveBackgroundOption): '' | `e_background_removal${string}` => { switch (removeBackground) { case false: return '' From 850ac5b814b6579c0cd0cc15773913aa726c893a Mon Sep 17 00:00:00 2001 From: jerzy-mankowski Date: Wed, 27 Nov 2024 11:42:28 +0100 Subject: [PATCH 013/115] feat: added parseHeight --- packages/react/src/parsers/parseHeight.ts | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 packages/react/src/parsers/parseHeight.ts diff --git a/packages/react/src/parsers/parseHeight.ts b/packages/react/src/parsers/parseHeight.ts new file mode 100644 index 00000000..82d360bf --- /dev/null +++ b/packages/react/src/parsers/parseHeight.ts @@ -0,0 +1,4 @@ +import { HeightOption } from '../types'; + +export const parseHeight = (height: HeightOption) => `h_${height}`; + From 93cdd7504cc013c723dd21670d56a3b14ec0fac0 Mon Sep 17 00:00:00 2001 From: jerzy-mankowski Date: Wed, 27 Nov 2024 11:43:40 +0100 Subject: [PATCH 014/115] feat: added parseWidth --- packages/react/src/parsers/parseWidth.ts | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 packages/react/src/parsers/parseWidth.ts diff --git a/packages/react/src/parsers/parseWidth.ts b/packages/react/src/parsers/parseWidth.ts new file mode 100644 index 00000000..72d0d255 --- /dev/null +++ b/packages/react/src/parsers/parseWidth.ts @@ -0,0 +1,5 @@ +import { WidthOption } from '../types'; + +export const parseWidth = (width: WidthOption) => `w_${width}`; + + From b0b038545fb43163e268ddfc07607eb42ee5aad7 Mon Sep 17 00:00:00 2001 From: jerzy-mankowski Date: Wed, 27 Nov 2024 11:43:57 +0100 Subject: [PATCH 015/115] feat: work in progress on types.ts --- packages/react/src/types.ts | 196 +++++++++++++++++++++++++++++++++++- 1 file changed, 193 insertions(+), 3 deletions(-) diff --git a/packages/react/src/types.ts b/packages/react/src/types.ts index 93e18588..7cd685f5 100644 --- a/packages/react/src/types.ts +++ b/packages/react/src/types.ts @@ -1,4 +1,32 @@ -export type CloudinaryImageQuality = +type RequireAtLeastOneProperty, Keys extends keyof Obj = keyof Obj> = + Keys extends infer A ? { + [K in Exclude]?: Obj[K]; + } & { [K in A]: Obj[A] } : never; + +type PickTwoKeys, Keys extends keyof T = keyof T> = + Keys extends infer FirstKey ? Pick extends infer SecondKey ? [FirstKey, SecondKey] : never : never; + +type k = PickTwoKeys<{ name: string; age: number; weight: number; }> + +type RequireAtLeastTwoProperties> = { + [K1 in keyof T]: { + [K2 in Exclude]: Required> & Partial> + }[Exclude] +}[keyof T]; + + +type p = { + a: string; + b: string; + c: string; +} + + +const d: RequireAtLeastOneProperty

& RequireAtLeastOneProperty

= { + a: 'a' +}; + +export type Quality = | 'auto' | 'auto:good' | 'auto:eco' @@ -15,7 +43,7 @@ export type CloudinaryImageQuality = | 'high' | 'best'; -export type CloudinaryImageFormat = +export type ImageFormat = | 'auto' | 'jpg' | 'png' @@ -34,7 +62,7 @@ export type CloudinaryImageFormat = | 'heic' | 'heif'; -export type CloudinaryVideoFormat = +export type VideoFormat = | 'webm' | 'mp4' | 'mkv' @@ -44,4 +72,166 @@ export type CloudinaryVideoFormat = | 'avi' | 'wmv'; +export type WidthOption = 'auto' | 'initial-width' | number; + +export type HeightOption = 'auto' | 'initial-height' | number; + +export type BackgroundOption = + | 'auto' + | { + type: 'color'; + color: string; +} + | { + type: 'auto' + mode?: 'border' | 'predominant' | 'border-contrast' | 'predominant-contrast'; +} + | { + type: 'auto'; + mode: 'predominant-gradient' | 'predominant-gradient-contrast' | 'border-gradient' | 'border-gradient-contrast'; + amountOfPredominantColorsToUse?: 2 | 4; + direction?: 'horizontal' | 'vertical' | 'diagonal-descending' | 'diagonal-ascending'; + borderPalette?: string[]; +} | { + type: 'blurred'; +} | { + type: 'blurred'; + intensity: number; + brightness?: number; +} | { + type: 'generativeAiFill'; + prompt?: string; + seed?: number; +} + export type CloudinaryRemoveBackgroundOption = boolean | 'fineEdges'; + +export type Gravity = + | 'north' + | 'north_west' + | 'north_east' + | 'south' + | 'south_west' + | 'south_east' + | 'west' + | 'east' + | 'center' + | 'face' + | 'faces' + | 'auto' + | 'adv_face' + | 'adv_faces' + | 'custom' + | 'custom:face' + | 'custom:faces' + | 'custom:auto' + | 'custom:adv_face' + | 'custom:adv_faces' + | 'ocr_text' + | 'body' + | 'liquid' + | 'auto:subject'; + +interface AspectRatio { + aspectRatio: `${number}:${number}` | number; +} + +interface SizeOptions { + width: number; + height: number; +} + +interface ScaleResizeOptions { + type: 'scale'; + aspectRatio?: AspectRatio; +} + +interface FitResizeOptions { + type: 'fit'; + aspectRatio?: AspectRatio; +} + +interface LimitResizeOptions { + type: 'limit'; + aspectRatio?: AspectRatio; +} + +interface MfitResizeOptions { + type: 'mfit'; + aspectRatio?: AspectRatio; +} + +interface FillResizeOptions { + type: 'fill'; + gravity?: Gravity; + aspectRatio?: AspectRatio; +} + +interface LfillResizeOptions { + type: 'lfill'; + gravity?: Gravity; + aspectRatio?: AspectRatio; +} + +interface PadResizeOptions { + type: 'pad'; + gravity?: Gravity; + aspectRatio?: AspectRatio; + background?: string; +} + +interface LpadResizeOptions { + type: 'lpad'; + gravity?: Gravity; + aspectRatio?: AspectRatio; + background?: string; +} + +// FIXME TwoOfTheFollowing type required +interface MpadResizeOptions { + type: 'mpad'; + gravity?: Gravity; + background?: string; +} + +interface CropResizeOptions { + type: 'crop'; + gravity?: Gravity; + x?: number; + y?: number; + zoom?: number; + aspectRatio?: AspectRatio; +} + +// FIXME TwoOfTheFollowing type required +interface ThumbResizeOptions { + type: 'thumb'; + gravity: Gravity; +} + +// DONE +type ImaggaCropResizeOptions = { + type: 'imagga_crop'; + aspectRatio?: AspectRatio; +} & RequireAtLeastOneProperty; + +// DONE +type ImaggaScaleResizeOptions = { + type: 'imagga_scale'; +} & RequireAtLeastOneProperty; + +export type ResizeOption = + | ScaleResizeOptions + | FitResizeOptions + | LimitResizeOptions + | MfitResizeOptions + | FillResizeOptions + | LfillResizeOptions + | PadResizeOptions + | LpadResizeOptions + | MpadResizeOptions + | CropResizeOptions + | ThumbResizeOptions + | ImaggaCropResizeOptions + | ImaggaScaleResizeOptions; + From f776e67ab6a3a775a989e4e9df50e5d34336b1b8 Mon Sep 17 00:00:00 2001 From: jerzy-mankowski Date: Wed, 27 Nov 2024 11:44:37 +0100 Subject: [PATCH 016/115] feat: basic serve setup with rollup --- packages/react/index.html | 12 +++ packages/react/package.json | 3 + packages/react/rollup-serve.config.js | 39 +++++++++ packages/react/src/AdvancedImage.tsx | 121 +++----------------------- packages/react/src/demo.tsx | 20 +++++ packages/react/src/index.tsx | 14 +-- 6 files changed, 87 insertions(+), 122 deletions(-) create mode 100644 packages/react/index.html create mode 100644 packages/react/rollup-serve.config.js create mode 100644 packages/react/src/demo.tsx diff --git a/packages/react/index.html b/packages/react/index.html new file mode 100644 index 00000000..ec421f7f --- /dev/null +++ b/packages/react/index.html @@ -0,0 +1,12 @@ + + + + + + + + +

Welcome to Cloudinary React SDK

+
+ + diff --git a/packages/react/package.json b/packages/react/package.json index 3f5de28f..47ba656b 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -18,6 +18,7 @@ }, "scripts": { "build": "npm run build --prefix ../html && tsc && rollup -c && cp package.json ./dist", + "serve": "rollup -c rollup-serve.config.js", "lint": "eslint src/**/*", "test": "mocha src/**/*.test.*", "test-coverage": "exit 1" @@ -64,6 +65,8 @@ "jsdom": "^21.1.0", "jsdom-global": "^3.0.2", "mocha": "^10.8.2", + "rollup-plugin-inject-process-env": "^1.3.1", + "rollup-plugin-serve": "^1.1.1", "ts-node": "^10.9.2" } } diff --git a/packages/react/rollup-serve.config.js b/packages/react/rollup-serve.config.js new file mode 100644 index 00000000..b868c240 --- /dev/null +++ b/packages/react/rollup-serve.config.js @@ -0,0 +1,39 @@ +import resolve from '@rollup/plugin-node-resolve'; +import typescript from '@rollup/plugin-typescript'; +import commonjs from '@rollup/plugin-commonjs'; +import replace from '@rollup/plugin-replace'; +import serve from 'rollup-plugin-serve'; +import injectProcessEnv from 'rollup-plugin-inject-process-env'; +import { version } from './package.json'; + +export default [ + { + input: 'src/demo.tsx', + output: [ + { + file: 'dist/demo.js', + format: 'esm' + } + ], + plugins: [ + resolve(), + replace({ + PACKAGE_VERSION_INJECTED_DURING_BUILD: version, + preventAssignment: false + }), + typescript({ target: 'es5' }), + commonjs(), + injectProcessEnv({ + NODE_ENV: process.env.NODE_ENV + }), + serve({ + open: true, + openPage: '/index.html', + contentBase: './', + historyApiFallback: false, + host: 'localhost', + port: 3001 + }) + ] + } +]; diff --git a/packages/react/src/AdvancedImage.tsx b/packages/react/src/AdvancedImage.tsx index 9ac4c02a..7a4d6a50 100644 --- a/packages/react/src/AdvancedImage.tsx +++ b/packages/react/src/AdvancedImage.tsx @@ -1,127 +1,28 @@ import React, { forwardRef } from 'react'; -import {type CloudinaryImage as UrlGenCloudinaryImage} from '@cloudinary/url-gen/assets/CloudinaryImage'; -import {CloudinaryImageFormat, CloudinaryImageQuality, CloudinaryRemoveBackgroundOption} from "./types"; +import { type CloudinaryImage as UrlGenCloudinaryImage } from '@cloudinary/url-gen/assets/CloudinaryImage'; +import { ImageFormat, Quality, CloudinaryRemoveBackgroundOption, ResizeOption } from './types'; interface CloudinaryImgLegacyProps { cldImg: UrlGenCloudinaryImage; } -interface BaseResizeOptions { - width: number; - height: number; -} - -interface ScaleResizeOptions extends BaseResizeOptions { - type: 'scale'; - aspectRatio?: string; -} - -interface FitResizeOptions extends BaseResizeOptions { - type: 'fit'; - aspectRatio?: string; -} - -interface LimitResizeOptions extends BaseResizeOptions { - type: 'limit'; - aspectRatio?: string; -} - -interface MfitResizeOptions extends BaseResizeOptions { - type: 'mfit'; - aspectRatio?: string; -} - -interface FillResizeOptions extends BaseResizeOptions { - type: 'fill'; - gravity?: string; - aspectRatio?: string; -} - -interface LfillResizeOptions extends BaseResizeOptions { - type: 'lfill'; - gravity?: string; - aspectRatio?: string; -} - -interface PadResizeOptions extends BaseResizeOptions { - type: 'pad'; - gravity?: string; - aspectRatio?: string; - background?: string; -} - -interface LpadResizeOptions extends BaseResizeOptions { - type: 'lpad'; - gravity?: string; - aspectRatio?: string; - background?: string; -} - -interface MpadResizeOptions extends BaseResizeOptions { - type: 'mpad'; - gravity?: string; - aspectRatio?: string; - background?: string; -} - -interface CropResizeOptions extends BaseResizeOptions { - type: 'crop'; - gravity?: string; - x?: number; - y?: number; - zoom?: number; - aspectRatio?: string; -} - -interface ThumbResizeOptions extends BaseResizeOptions { - type: 'thumb'; - gravity?: string; - aspectRatio?: string; -} - -interface ImaggaCropResizeOptions extends BaseResizeOptions { - type: 'imagga_crop'; - aspectRatio?: string; -} - -interface ImaggaScaleResizeOptions extends BaseResizeOptions { - type: 'imagga_scale'; - aspectRatio?: string; -} - -type ResizeOptions = - | ScaleResizeOptions - | FitResizeOptions - | LimitResizeOptions - | MfitResizeOptions - | FillResizeOptions - | LfillResizeOptions - | PadResizeOptions - | LpadResizeOptions - | MpadResizeOptions - | CropResizeOptions - | ThumbResizeOptions - | ImaggaCropResizeOptions - | ImaggaScaleResizeOptions; - interface CloudinaryImgNewProps { src: string; alt: string; - quality?: CloudinaryImageQuality; - format?: CloudinaryImageFormat; + quality?: Quality; + format?: ImageFormat; removeBackground?: CloudinaryRemoveBackgroundOption; - resize?: ResizeOptions; + resize?: ResizeOption; } -export type CldImageProps = CloudinaryImgNewProps | CloudinaryImgLegacyProps; -const isCloudinaryImgLegacyProps = (props: CldImageProps): props is CloudinaryImgLegacyProps => 'cldImg' in props; +// export type CldImageProps = ; -export const CloudinaryImg = forwardRef((props: CldImageProps, ref) => { - if (isCloudinaryImgLegacyProps(props)) { +export const CloudinaryImg = forwardRef((props, ref) => { + if ('cldImg' in props) { const { cldImg, ...rest } = props; - return + return ; } - return -}) + return ; +}); diff --git a/packages/react/src/demo.tsx b/packages/react/src/demo.tsx new file mode 100644 index 00000000..c5915f87 --- /dev/null +++ b/packages/react/src/demo.tsx @@ -0,0 +1,20 @@ +import React from 'react'; +import ReactDom from 'react-dom'; +import { CloudinaryImg } from './index'; + +const LegacyComponentUsage = () => { + +}; + +const NewComponentUsage = () => { + +}; + +const Demo = () => { + return ; +}; + +document.addEventListener('DOMContentLoaded', () => { + ReactDom.render(, document.getElementById('root')); +}); + diff --git a/packages/react/src/index.tsx b/packages/react/src/index.tsx index 10c310b2..d7655e1f 100644 --- a/packages/react/src/index.tsx +++ b/packages/react/src/index.tsx @@ -1,13 +1,3 @@ -/** - * Import and export all needed types - */ -import { - placeholder, - accessibility, - lazyload, - responsive -} from '@cloudinary/html' -import { AdvancedImage } from './AdvancedImage'; -import { AdvancedVideo } from './AdvancedVideo'; +export { CloudinaryImg } from './AdvancedImage'; +export { AdvancedVideo } from './AdvancedVideo'; -export { placeholder, accessibility, lazyload, responsive, AdvancedImage, AdvancedVideo }; From 468ecf30057613d26bc33390394839101d638bde Mon Sep 17 00:00:00 2001 From: jerzy-mankowski Date: Wed, 27 Nov 2024 11:44:54 +0100 Subject: [PATCH 017/115] chore: removed renamed file --- .../parsers/parseRemoveBackgroundOption.test.ts | 17 ----------------- 1 file changed, 17 deletions(-) delete mode 100644 packages/react/src/parsers/parseRemoveBackgroundOption.test.ts diff --git a/packages/react/src/parsers/parseRemoveBackgroundOption.test.ts b/packages/react/src/parsers/parseRemoveBackgroundOption.test.ts deleted file mode 100644 index 825de240..00000000 --- a/packages/react/src/parsers/parseRemoveBackgroundOption.test.ts +++ /dev/null @@ -1,17 +0,0 @@ -import 'mocha' -import assert from 'assert' -import { parseRemoveBackgroundOption } from './parseRemoveBackgroundOption' - -describe('parseRemoveBackgroundOption: returns transformation for', () => { - it('"false" option', () => { - assert.equal(parseRemoveBackgroundOption(false), '') - }) - - it('"true" option', () => { - assert.equal(parseRemoveBackgroundOption(true), 'e_background_removal') - }) - - it('"fineEdges" option', () => { - assert.equal(parseRemoveBackgroundOption('fineEdges'), 'e_background_removal:fineedges_y') - }) -}) From ed3c716a62a596d973b74c2f796ced27b99bd262 Mon Sep 17 00:00:00 2001 From: jerzy-mankowski Date: Wed, 27 Nov 2024 11:45:10 +0100 Subject: [PATCH 018/115] feat: parseFormat refinement --- packages/react/src/parsers/parseFormat.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/react/src/parsers/parseFormat.ts b/packages/react/src/parsers/parseFormat.ts index abfdf606..7cd62ce7 100644 --- a/packages/react/src/parsers/parseFormat.ts +++ b/packages/react/src/parsers/parseFormat.ts @@ -1,3 +1,3 @@ -import { CloudinaryImageFormat } from '../types' +import { ImageFormat } from '../types' -export const parseFormat = (format: CloudinaryImageFormat) => `f_${format}` +export const parseFormat = (format: ImageFormat) => `f_${format}` From a9295565e602218f66844a7aeecd14f9432919dd Mon Sep 17 00:00:00 2001 From: jerzy-mankowski Date: Wed, 27 Nov 2024 11:45:24 +0100 Subject: [PATCH 019/115] feat: basic support for parseBackground --- packages/react/src/parsers/parseBackground.ts | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 packages/react/src/parsers/parseBackground.ts diff --git a/packages/react/src/parsers/parseBackground.ts b/packages/react/src/parsers/parseBackground.ts new file mode 100644 index 00000000..64a99518 --- /dev/null +++ b/packages/react/src/parsers/parseBackground.ts @@ -0,0 +1,22 @@ +import { BackgroundOption } from '../types'; + +export const parseBackground = (background: BackgroundOption) => { + if (background === 'auto') { + return 'b_auto'; + } + + switch (background.type) { + case 'auto': + return 'b_auto'; + case 'color': + return `b_${background.color}`; + case 'blurred': + if ('intensity' in background) { + return `b_blurred:${background.intensity}${background.brightness ? `:${background.brightness}` : ''}`; + } + return 'b_blurred'; + case 'generativeAiFill': { + return `b_gen_fill${background.prompt ? `:prompt_${background.prompt}` : ''}${background.seed ? `:seed_${background.seed}` : ''}`; + } + } +}; From 7242f850e2c901cb588287cdd6f89931a1e68bcd Mon Sep 17 00:00:00 2001 From: jerzy-mankowski Date: Wed, 27 Nov 2024 13:50:40 +0100 Subject: [PATCH 020/115] feat: added typecheck script --- packages/react/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/react/package.json b/packages/react/package.json index 47ba656b..ac274e2d 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -20,6 +20,7 @@ "build": "npm run build --prefix ../html && tsc && rollup -c && cp package.json ./dist", "serve": "rollup -c rollup-serve.config.js", "lint": "eslint src/**/*", + "typecheck": "tsc --noEmit --skipLibCheck", "test": "mocha src/**/*.test.*", "test-coverage": "exit 1" }, From cf64b2ec543fc24cdb5a716ffe86ef1e6c755687 Mon Sep 17 00:00:00 2001 From: jerzy-mankowski Date: Wed, 27 Nov 2024 13:51:05 +0100 Subject: [PATCH 021/115] fix: parseBackground type adjustment --- packages/react/src/parsers/parseBackground.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react/src/parsers/parseBackground.ts b/packages/react/src/parsers/parseBackground.ts index 64a99518..7946089d 100644 --- a/packages/react/src/parsers/parseBackground.ts +++ b/packages/react/src/parsers/parseBackground.ts @@ -1,6 +1,6 @@ import { BackgroundOption } from '../types'; -export const parseBackground = (background: BackgroundOption) => { +export const parseBackground = (background: BackgroundOption): `b_${string}` => { if (background === 'auto') { return 'b_auto'; } From 66e78e1219deb06674f46541a281dbae2becf680 Mon Sep 17 00:00:00 2001 From: jerzy-mankowski Date: Wed, 27 Nov 2024 13:51:20 +0100 Subject: [PATCH 022/115] feat: parseQuality simplification --- packages/react/src/parsers/parseQuality.ts | 37 ++-------------------- 1 file changed, 2 insertions(+), 35 deletions(-) diff --git a/packages/react/src/parsers/parseQuality.ts b/packages/react/src/parsers/parseQuality.ts index 7d462d45..35affed7 100644 --- a/packages/react/src/parsers/parseQuality.ts +++ b/packages/react/src/parsers/parseQuality.ts @@ -1,36 +1,3 @@ -import { Quality } from '../types' +import { Quality } from '../types'; -export const parseQuality = (quality: Quality): `q_${string}` => { - switch (quality) { - case 'auto': - return 'q_auto' - case 'auto:good': - return 'q_auto:good' - case 'auto:eco': - return 'q_auto:eco' - case 'auto:low': - return 'q_auto:low' - case 'auto:best': - return 'q_auto:best' - case 'jpegmini': - return 'q_jpegmini' - case 'jpegmini:0': - return 'q_jpegmini:0' - case 'jpegmini:1': - return 'q_jpegmini:1' - case 'jpegmini:2': - return 'q_jpegmini:2' - case 'low': - return 'q_low' - case 'eco': - return 'q_eco' - case 'medium': - return 'q_medium' - case 'good': - return 'q_good' - case 'high': - return 'q_high' - case 'best': - return 'q_best' - } -} +export const parseQuality = (quality: Quality): `q_${string}` => `q_${quality}`; From 27b6e7691b51715de542c791af7c0a347c6eea6f Mon Sep 17 00:00:00 2001 From: jerzy-mankowski Date: Wed, 27 Nov 2024 13:51:49 +0100 Subject: [PATCH 023/115] chore: empty parseResize + test --- packages/react/src/parsers/parseResize.ts | 3 +++ packages/react/src/parsers/parseResizeOption.test.ts | 5 +++++ 2 files changed, 8 insertions(+) create mode 100644 packages/react/src/parsers/parseResize.ts create mode 100644 packages/react/src/parsers/parseResizeOption.test.ts diff --git a/packages/react/src/parsers/parseResize.ts b/packages/react/src/parsers/parseResize.ts new file mode 100644 index 00000000..30bf8ee5 --- /dev/null +++ b/packages/react/src/parsers/parseResize.ts @@ -0,0 +1,3 @@ +export const parseResize = () => { + +}; diff --git a/packages/react/src/parsers/parseResizeOption.test.ts b/packages/react/src/parsers/parseResizeOption.test.ts new file mode 100644 index 00000000..20e1ba02 --- /dev/null +++ b/packages/react/src/parsers/parseResizeOption.test.ts @@ -0,0 +1,5 @@ +import 'mocha'; + +describe('parseResizeOptions', () => { + +}); From c5b4f858125cf479075118d0b3ed5be6de20d4d1 Mon Sep 17 00:00:00 2001 From: jerzy-mankowski Date: Wed, 27 Nov 2024 13:52:13 +0100 Subject: [PATCH 024/115] chore: moved analytics file --- packages/react/src/{internal => }/SDKAnalyticsConstants.ts | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename packages/react/src/{internal => }/SDKAnalyticsConstants.ts (100%) diff --git a/packages/react/src/internal/SDKAnalyticsConstants.ts b/packages/react/src/SDKAnalyticsConstants.ts similarity index 100% rename from packages/react/src/internal/SDKAnalyticsConstants.ts rename to packages/react/src/SDKAnalyticsConstants.ts From 2653952d2f66ac19514f3493b31db360d2db9003 Mon Sep 17 00:00:00 2001 From: jerzy-mankowski Date: Wed, 27 Nov 2024 13:52:32 +0100 Subject: [PATCH 025/115] chore: typecheck adjustments --- packages/react/src/AdvancedImage.tsx | 23 +- packages/react/src/AdvancedVideo.tsx | 336 +++++++++++++-------------- packages/react/src/demo.tsx | 2 +- packages/react/src/index.tsx | 2 +- packages/react/src/types.ts | 28 +-- 5 files changed, 202 insertions(+), 189 deletions(-) diff --git a/packages/react/src/AdvancedImage.tsx b/packages/react/src/AdvancedImage.tsx index 7a4d6a50..c61ed03e 100644 --- a/packages/react/src/AdvancedImage.tsx +++ b/packages/react/src/AdvancedImage.tsx @@ -1,23 +1,36 @@ import React, { forwardRef } from 'react'; import { type CloudinaryImage as UrlGenCloudinaryImage } from '@cloudinary/url-gen/assets/CloudinaryImage'; -import { ImageFormat, Quality, CloudinaryRemoveBackgroundOption, ResizeOption } from './types'; +import { + ImageFormat, + Quality, + CloudinaryRemoveBackgroundOption, + ResizeOption, + WidthOption, + HeightOption +} from './types'; interface CloudinaryImgLegacyProps { cldImg: UrlGenCloudinaryImage; } -interface CloudinaryImgNewProps { +type ResizeProps = { + height?: HeightOption; + width?: WidthOption; +} | { + resize?: ResizeOption; +} + +type CloudinaryImgNewProps = ResizeProps & { src: string; alt: string; quality?: Quality; format?: ImageFormat; removeBackground?: CloudinaryRemoveBackgroundOption; - resize?: ResizeOption; } -// export type CldImageProps = ; +export type CldImageProps = CloudinaryImgNewProps | CloudinaryImgLegacyProps; -export const CloudinaryImg = forwardRef((props, ref) => { +export const CloudinaryImg = forwardRef((props, ref) => { if ('cldImg' in props) { const { cldImg, ...rest } = props; return ; diff --git a/packages/react/src/AdvancedVideo.tsx b/packages/react/src/AdvancedVideo.tsx index c8ef4ddf..5b575396 100644 --- a/packages/react/src/AdvancedVideo.tsx +++ b/packages/react/src/AdvancedVideo.tsx @@ -1,168 +1,168 @@ -import React, { Component, createRef, EventHandler, HTMLAttributes, MutableRefObject, SyntheticEvent } from 'react'; -import { CloudinaryImage, CloudinaryVideo } from '@cloudinary/url-gen'; - -import { - HtmlVideoLayer, - Plugins, - VideoPoster, - VideoSources, - cancelCurrentlyRunningPlugins -} from '@cloudinary/html'; - -type ReactEventHandler = EventHandler>; - -interface VideoProps extends HTMLAttributes{ - cldVid: CloudinaryVideo, - cldPoster?: VideoPoster, - plugins?: Plugins, - sources?: VideoSources, - innerRef?: ((instance: any) => void) | MutableRefObject | null - useFetchFormat?: boolean, - - // supported video attributes - controls?: boolean - loop?: boolean, - muted?: boolean, - poster?: string, - preload?: string, - autoPlay?: boolean, - playsInline?: boolean - - // supported video events - onPlay?: ReactEventHandler, - onLoadStart?: ReactEventHandler, - onPlaying?: ReactEventHandler, - onError?: ReactEventHandler, - onEnded?: ReactEventHandler -} - -const VIDEO_ATTRIBUTES_KEYS: string[] = ['controls', 'loop', 'muted', 'poster', 'preload', 'autoplay', 'playsinline']; - -/** - * @memberOf ReactSDK - * @type {Component} - * @description The Cloudinary video component. - * @prop {CloudinaryVideo} transformation Generated by @cloudinary/url-gen - * @prop {Plugins} plugins Advanced image component plugins lazyload() - * @prop videoAttributes Optional attributes include controls, loop, muted, poster, preload, autoplay - * @prop videoEvents Optional video events include play, loadstart, playing, error, ended - * @prop {VideoSources} sources Optional sources to generate - * @example - * - * Using custom defined resources. - * - * const vid = new CloudinaryVideo('dog', {cloudName: 'demo'}); - * const videoEl = useRef(); - * const sources = [ - * { - * type: 'mp4', - * codecs: ['vp8', 'vorbis'], - * transcode: videoCodec(auto()) - * }, - * { - * type: 'webm', - * codecs: ['avc1.4D401E', 'mp4a.40.2'], - * videoCodec: videoCodec(auto()) - * }]; - * - * return - */ -class AdvancedVideo extends Component { - videoRef: MutableRefObject - htmlVideoLayerInstance: HtmlVideoLayer; - - constructor(props: VideoProps) { - super(props); - this.videoRef = createRef(); - this.attachRef = this.attachRef.bind(this); - } - - /** - * On mount, creates a new HTMLVideoLayer instance and initializes with ref to video element, - * user generated cloudinaryVideo and the plugins to be used. - */ - componentDidMount() { - this.htmlVideoLayerInstance = new HtmlVideoLayer( - this.videoRef && this.videoRef.current, - this.props.cldVid, - this.props.sources, - this.props.plugins, - this.getVideoAttributes(), - this.props.cldPoster, - { - useFetchFormat: this.props.useFetchFormat - } - ) - } - - /** - * On update, we cancel running plugins and update the video instance if the src - * was changed. - */ - componentDidUpdate() { - cancelCurrentlyRunningPlugins(this.htmlVideoLayerInstance.htmlPluginState); - // call html layer to update the dom again with plugins and reset toBeCanceled - this.htmlVideoLayerInstance.update( - this.props.cldVid, - this.props.sources, - this.props.plugins, - this.getVideoAttributes(), - this.props.cldPoster - ) - } - - /** - * On unmount, we cancel the currently running plugins. - */ - componentWillUnmount() { - // safely cancel running events on unmount - cancelCurrentlyRunningPlugins(this.htmlVideoLayerInstance.htmlPluginState) - } - - /** - * Returns video attributes. - */ - getVideoAttributes() { - const result = {}; - VIDEO_ATTRIBUTES_KEYS.forEach((key: string) => { - if (key in this.props) { - result[key] = this.props[key]; - } - }); - - return result; - } - - /** - * Attach both this.videoRef and props.innerRef as ref to the given element. - * @param element - the element to attach a ref to - */ - attachRef(element: HTMLVideoElement) { - this.videoRef.current = element; - const { innerRef } = this.props; - - if (innerRef) { - if (innerRef instanceof Function) { - innerRef(element); - } else { - innerRef.current = element; - } - } - }; - - render() { - const { - cldVid, - cldPoster, - plugins, - sources, - innerRef, - useFetchFormat, - ...videoAttrs // Assume any other props are for the base element - } = this.props; - - return