From 558f653380dfe62f36d159a0fbdc88bc24c974ca Mon Sep 17 00:00:00 2001
From: Manu Chaudhary
Date: Tue, 10 Mar 2026 15:21:24 +0530
Subject: [PATCH 1/8] feat(templates): add template save/load functionality
with versioning
Add template management API to save and restore editor transformation stacks:
- Add getTemplate() and loadTemplate() methods to ImageKitEditorRef
- Implement v1 versioning system for backward compatibility
- Export TRANSFORMATION_STATE_VERSION constant
Add comprehensive testing infrastructure:
- Add backward compatibility test suite with v1 template fixtures
- Configure Vitest with coverage reporting
- Add test step to CI workflow
Update documentation:
- Add template management guide with examples
- Document version compatibility approach
- Add TypeScript usage examples
Other changes:
- Bump version from 2.1.0 to 2.2.0
- Add coverage directory to .gitignore
- Update React example with template save/load demo
---
.github/workflows/ci.yaml | 3 +-
.gitignore | 3 +-
README.md | 157 +-
examples/react-example/package.json | 5 +-
examples/react-example/src/index.tsx | 237 +-
package.json | 3 +
packages/imagekit-editor-dev/package.json | 13 +-
.../src/ImageKitEditor.tsx | 45 +-
.../src/backward-compatibility.test.ts | 2549 +++++++++++++++++
packages/imagekit-editor-dev/src/index.tsx | 3 +-
packages/imagekit-editor-dev/src/store.ts | 31 +-
packages/imagekit-editor-dev/vite.config.ts | 16 +
packages/imagekit-editor/package.json | 5 +-
turbo.json | 4 +
yarn.lock | 1730 ++++++++++-
15 files changed, 4767 insertions(+), 37 deletions(-)
create mode 100644 packages/imagekit-editor-dev/src/backward-compatibility.test.ts
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index da9c2de..0756460 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -22,10 +22,11 @@ jobs:
node-version: 20.x
cache: yarn
- - name: 📦 Install deps, build, pack
+ - name: 📦 Install deps, lint, test, build, pack
run: |
yarn install --frozen-lockfile
yarn lint
+ yarn test
yarn package
env:
CI: true
diff --git a/.gitignore b/.gitignore
index e1c4725..d208200 100644
--- a/.gitignore
+++ b/.gitignore
@@ -19,4 +19,5 @@ packages/imagekit-editor/*.tgz
.yarn
builds
packages/imagekit-editor/README.md
-.cursor
\ No newline at end of file
+.cursor
+coverage
\ No newline at end of file
diff --git a/README.md b/README.md
index 1358919..9a3d936 100644
--- a/README.md
+++ b/README.md
@@ -12,6 +12,7 @@ A powerful, React-based image editor component powered by ImageKit transformatio
- 🖼️ **Visual Image Editor**: Interactive UI for applying ImageKit transformations
- 📝 **Transformation History**: Track and manage applied transformations using ImageKit's chain transformations
+- 💾 **Template Management**: Save and restore editor templates with built-in serialization support
- 🎨 **Multiple Transformation Types**: Support for resize, crop, focus, quality adjustments, and more
- 🖥️ **Desktop Interface**: Modern interface built with Chakra UI for desktop environments
- 🔧 **TypeScript Support**: Full TypeScript support with comprehensive type definitions
@@ -141,9 +142,15 @@ interface ImageKitEditorRef {
loadImage: (image: string | FileElement) => void;
loadImages: (images: Array) => void;
setCurrentImage: (imageSrc: string) => void;
+ getTemplate: () => Transformation[];
+ loadTemplate: (template: Omit[]) => void;
}
```
+**Template Management Methods:**
+- `getTemplate()` - Returns the current editor template (transformation stack)
+- `loadTemplate(template)` - Loads a previously saved template into the editor
+
### Export Options
You can configure export functionality in two ways:
@@ -212,6 +219,124 @@ The `metadata` object can contain any contextual information your application ne
## Advanced Usage
+### Template Management
+
+You can save and restore editor templates, enabling features like:
+- Template library
+- Preset transformation stacks
+- Collaborative editing workflows
+- Quick application of common transformations
+
+**Template Versioning:** All templates are versioned (currently `v1`) to ensure backward compatibility and safe schema evolution.
+
+#### Saving a Template
+
+```tsx
+import { useRef } from 'react';
+import { ImageKitEditor, type ImageKitEditorRef, type Transformation } from '@imagekit/editor';
+
+function MyComponent() {
+ const editorRef = useRef(null);
+
+ const handleSaveTemplate = () => {
+ const template = editorRef.current?.getTemplate();
+ if (template) {
+ // Remove the auto-generated 'id' field before saving
+ const templateToSave = template.map(({ id, ...rest }) => rest);
+
+ // Save to localStorage
+ localStorage.setItem('editorTemplate', JSON.stringify(templateToSave));
+
+ // Or save to your backend
+ await api.saveTemplate(templateToSave);
+ }
+ };
+
+ return (
+
+ );
+}
+```
+
+#### Loading a Template
+
+```tsx
+const handleLoadTemplate = () => {
+ // Load from localStorage
+ const saved = localStorage.getItem('editorTemplate');
+
+ // Or load from your backend
+ // const saved = await api.getTemplate();
+
+ if (saved) {
+ const template = JSON.parse(saved);
+ editorRef.current?.loadTemplate(template);
+ }
+};
+```
+
+#### Template Structure
+
+A template is an array of transformation objects with version information:
+
+```tsx
+interface Transformation {
+ id: string; // Auto-generated, omit when saving
+ key: string; // e.g., 'adjust-background'
+ name: string; // e.g., 'Background'
+ type: 'transformation';
+ value: Record; // Transformation parameters
+ version?: 'v1'; // Template version for compatibility
+}
+
+// Version constant
+import { TRANSFORMATION_STATE_VERSION } from '@imagekit/editor';
+console.log(TRANSFORMATION_STATE_VERSION); // 'v1'
+```
+
+**Example template:**
+```json
+[
+ {
+ "key": "adjust-background",
+ "name": "Background",
+ "type": "transformation",
+ "value": {
+ "backgroundType": "color",
+ "background": "#FFFFFF"
+ },
+ "version": "v1"
+ },
+ {
+ "key": "resize_and_crop-resize_and_crop",
+ "name": "Resize and Crop",
+ "type": "transformation",
+ "value": {
+ "width": 800,
+ "height": 600,
+ "mode": "pad_resize"
+ },
+ "version": "v1"
+ }
+]
+```
+
+**Version Compatibility:**
+- `v1` - Current version with all transformation features
+- The `version` field is optional for backward compatibility
+- Future versions will maintain backward compatibility where possible
+
### Signed URLs
For private images that require signed URLs, you can pass file metadata that will be available in the signer function:
@@ -269,10 +394,40 @@ import type {
ImageKitEditorProps,
ImageKitEditorRef,
FileElement,
- Signer
+ Signer,
+ Transformation // For template management
} from '@imagekit/editor';
+
+// Version constant for template compatibility
+import { TRANSFORMATION_STATE_VERSION } from '@imagekit/editor';
```
+## Testing
+
+The package includes comprehensive tests to ensure schema stability and API consistency. Run tests:
+
+```bash
+# Run tests once
+yarn test
+
+# Watch mode
+yarn test:watch
+
+# With UI
+yarn test:ui
+```
+
+### Schema Versioning
+
+The transformation schema is locked down with tests to ensure:
+- All transformation categories exist and are stable
+- All transformation items have required properties
+- Schemas validate correctly
+- Template serialization/deserialization works consistently
+- Version compatibility is maintained
+
+Current schema version: **v1**
+
## Contributing
We welcome contributions! Please see our [contributing guidelines](./CONTRIBUTING.md) for more details.
diff --git a/examples/react-example/package.json b/examples/react-example/package.json
index 7fac272..6930172 100644
--- a/examples/react-example/package.json
+++ b/examples/react-example/package.json
@@ -3,7 +3,7 @@
"version": "0.1.0",
"private": true,
"dependencies": {
- "@imagekit/editor": "2.1.0",
+ "@imagekit/editor": "workspace:*",
"@types/node": "^20.11.24",
"@types/react": "^17.0.2",
"@types/react-dom": "^17.0.2",
@@ -18,7 +18,8 @@
"scripts": {
"dev": "vite --port 3000",
"start": "vite --port 3000",
- "preview": "vite preview"
+ "preview": "vite preview",
+ "test": "echo \"No tests in this example\""
},
"eslintConfig": {
"extends": [
diff --git a/examples/react-example/src/index.tsx b/examples/react-example/src/index.tsx
index a083ba6..c68b0a5 100644
--- a/examples/react-example/src/index.tsx
+++ b/examples/react-example/src/index.tsx
@@ -1,6 +1,11 @@
import { Icon } from "@chakra-ui/react"
-import { ImageKitEditor, type ImageKitEditorProps } from "@imagekit/editor"
-import type { ImageKitEditorRef } from "@imagekit/editor/dist/ImageKitEditor"
+import {
+ ImageKitEditor,
+ type ImageKitEditorProps,
+ type ImageKitEditorRef,
+ type Transformation,
+ TRANSFORMATION_STATE_VERSION,
+} from "@imagekit/editor"
import { PiDownload } from "@react-icons/all-files/pi/PiDownload"
import React, { useCallback, useEffect } from "react"
import ReactDOM from "react-dom"
@@ -12,6 +17,10 @@ function App() {
ImageKitEditorProps<{ requireSignedUrl: boolean; fileName: string }>
>()
const ref = React.useRef(null)
+ const [savedTemplate, setSavedTemplate] = React.useState<
+ Omit[] | null
+ >(null)
+ const [shouldLoadTemplate, setShouldLoadTemplate] = React.useState(false)
/**
* Function moved from EditorLayout component
@@ -23,6 +32,75 @@ function App() {
ref.current?.loadImage(randomImage)
}, [])
+ /**
+ * Load template when editor becomes available
+ */
+ React.useEffect(() => {
+ if (open && shouldLoadTemplate && ref.current && savedTemplate) {
+ ref.current.loadTemplate(savedTemplate)
+ console.log("Loaded template:", savedTemplate)
+ setShouldLoadTemplate(false)
+ }
+ }, [open, shouldLoadTemplate, savedTemplate])
+
+ /**
+ * Save the current editor template
+ */
+ const handleSaveTemplate = useCallback(() => {
+ const template = ref.current?.getTemplate()
+ if (template) {
+ // Remove the 'id' field from each transformation for storage
+ const templateToSave = template.map(({ id, ...rest }: Transformation) => rest)
+ setSavedTemplate(templateToSave)
+ // Also save to localStorage for persistence
+ localStorage.setItem("editorTemplate", JSON.stringify(templateToSave))
+ console.log("Saved template:", templateToSave)
+ alert(`✅ Saved template with ${templateToSave.length} transformation(s)!`)
+ } else {
+ alert("⚠️ No transformations to save")
+ }
+ }, [])
+
+ /**
+ * Load previously saved template
+ */
+ const handleLoadTemplate = useCallback(() => {
+ if (savedTemplate) {
+ // Flag to load template and open editor
+ setShouldLoadTemplate(true)
+ setOpen(true)
+ } else {
+ // Try to load from localStorage
+ const stored = localStorage.getItem("editorTemplate")
+ if (stored) {
+ try {
+ const parsed = JSON.parse(stored)
+ setSavedTemplate(parsed)
+ setShouldLoadTemplate(true)
+ setOpen(true)
+ console.log("Loaded template from localStorage:", parsed)
+ } catch (e) {
+ console.error("Failed to parse saved template:", e)
+ alert("❌ Failed to load saved template - invalid JSON")
+ }
+ } else {
+ alert("⚠️ No saved template found")
+ }
+ }
+ }, [savedTemplate])
+
+ /**
+ * Clear the saved template
+ */
+ const handleClearTemplate = useCallback(() => {
+ if (confirm("Are you sure you want to clear the saved template?")) {
+ setSavedTemplate(null)
+ localStorage.removeItem("editorTemplate")
+ console.log("Cleared saved template")
+ alert("🗑️ Template cleared!")
+ }
+ }, [])
+
useEffect(() => {
setEditorProps({
initialImages: [
@@ -59,11 +137,20 @@ function App() {
exportOptions: [
{
type: "button",
- label: "Export",
+ label: "Export Images",
icon: ,
isVisible: true,
onClick: (images, currentImage) => {
- console.log(images, currentImage)
+ console.log("Export images:", images, currentImage)
+ },
+ },
+ {
+ type: "button",
+ label: "Save Template",
+ icon: ,
+ isVisible: true,
+ onClick: () => {
+ handleSaveTemplate()
},
},
// {
@@ -89,18 +176,148 @@ function App() {
console.log("Signed URL", request.url)
return Promise.resolve(request.url)
},
- })
- }, [handleAddImage])
+ }) }, [handleAddImage, handleSaveTemplate])
const toggle = () => {
- setOpen((prev) => !prev)
+ setOpen((prev: boolean) => !prev)
}
return (
<>
- toggle()}>
- Open ImageKit Editor
-
+
+
ImageKit Editor - Template Management Demo
+
+ This demo shows how to save and restore editor templates using the
+ editor's ref methods.
+
+
+
+
toggle()}
+ style={{
+ padding: "10px 20px",
+ fontSize: "16px",
+ marginRight: "10px",
+ cursor: "pointer",
+ }}
+ >
+ {open ? "Close" : "Open"} ImageKit Editor
+
+
+
+ Load Saved Template
+
+
+ {savedTemplate && (
+
+ Clear Template
+
+ )}
+
+ {savedTemplate && (
+
+
+ ✓ Saved Template
+
+
+ Transformations: {savedTemplate.length}
+
+
+ Schema Version: {TRANSFORMATION_STATE_VERSION}
+
+
+ Types: {" "}
+ {Array.from(
+ new Set(savedTemplate.map((t) => t.type))
+ ).join(", ")}
+
+
+
+ 📋 View Template JSON
+
+
+ {JSON.stringify(savedTemplate, null, 2)}
+
+
+
+ )}
+
+
+
+
📖 How to use Template Features:
+
+ Click "Open ImageKit Editor" and apply some transformations
+ Click the "Save Template" button in the editor header
+ Close the editor
+
+ Click "Load Saved Template" - it will open the editor with all transformations restored
+
+ Use "Clear Template" to remove the saved template
+
+
+ 💾 Persistent Storage: Templates are saved to localStorage, so they persist across page reloads!
+
+
+ Note: Template IDs are automatically generated on load to ensure uniqueness and enable reusability.
+
+
+
+
{open && editorProps && }
>
)
diff --git a/package.json b/package.json
index 68cf98d..9c4303c 100644
--- a/package.json
+++ b/package.json
@@ -25,7 +25,10 @@
},
"devDependencies": {
"@biomejs/biome": "2.1.1",
+ "@types/jsdom": "^28",
+ "@vitest/coverage-v8": "^4.0.18",
"husky": "^9.1.7",
+ "jsdom": "^28.1.0",
"lint-staged": "^16.1.2",
"shx": "^0.4.0",
"turbo": "^2.0.1"
diff --git a/packages/imagekit-editor-dev/package.json b/packages/imagekit-editor-dev/package.json
index 45bbed6..d1bd98f 100644
--- a/packages/imagekit-editor-dev/package.json
+++ b/packages/imagekit-editor-dev/package.json
@@ -1,6 +1,6 @@
{
"name": "imagekit-editor-dev",
- "version": "0.0.0",
+ "version": "2.2.0",
"description": "AI Image Editor powered by ImageKit",
"scripts": {
"prepack": "yarn build",
@@ -8,7 +8,11 @@
"dev": "DEBUG=* vite build --watch",
"start": "vite build --watch",
"analyze": "vite build --mode analyze",
- "preview": "vite preview"
+ "preview": "vite preview",
+ "test": "vitest run",
+ "test:watch": "vitest",
+ "test:ui": "vitest --ui",
+ "test:coverage": "vitest run --coverage"
},
"keywords": [],
"author": {
@@ -29,13 +33,16 @@
"@types/react-color": "^2",
"@types/react-dom": "^17.0.2",
"@vitejs/plugin-react": "^4.5.2",
+ "@vitest/coverage-v8": "^2.1.9",
+ "@vitest/ui": "^2.0.0",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"rollup-plugin-visualizer": "^5.12.0",
"terser": "^5.43.1",
"typescript": "4.9.3",
"vite": "^6.3.5",
- "vite-plugin-dts": "5.0.0-beta.3"
+ "vite-plugin-dts": "5.0.0-beta.3",
+ "vitest": "^2.0.0"
},
"dependencies": {
"@chakra-ui/icons": "1.1.1",
diff --git a/packages/imagekit-editor-dev/src/ImageKitEditor.tsx b/packages/imagekit-editor-dev/src/ImageKitEditor.tsx
index 675f583..d9bdf1b 100644
--- a/packages/imagekit-editor-dev/src/ImageKitEditor.tsx
+++ b/packages/imagekit-editor-dev/src/ImageKitEditor.tsx
@@ -9,14 +9,54 @@ import {
type InputFileElement,
type RequiredMetadata,
type Signer,
+ type Transformation,
useEditorStore,
} from "./store"
import { themeOverrides } from "./theme"
export interface ImageKitEditorRef {
+ /**
+ * Loads a single image into the editor
+ * @param image - Image URL string or FileElement with metadata
+ */
loadImage: (image: string | InputFileElement) => void
+
+ /**
+ * Loads multiple images into the editor
+ * @param images - Array of image URL strings or FileElements with metadata
+ */
loadImages: (images: Array) => void
+
+ /**
+ * Switches the current active image
+ * @param imageSrc - URL of the image to set as current
+ */
setCurrentImage: (imageSrc: string) => void
+
+ /**
+ * Gets the current editor template (transformation stack)
+ * @returns Array of transformation objects representing the template
+ * @example
+ * ```tsx
+ * const template = editorRef.current?.getTemplate()
+ * // Save to localStorage or backend
+ * localStorage.setItem('editorTemplate', JSON.stringify(
+ * template.map(({ id, ...rest }) => rest)
+ * ))
+ * ```
+ */
+ getTemplate: () => Transformation[]
+
+ /**
+ * Loads a template (transformation stack) into the editor
+ * @param template - Array of transformation objects without the 'id' field
+ * @example
+ * ```tsx
+ * const saved = JSON.parse(localStorage.getItem('editorTemplate'))
+ * editorRef.current?.loadTemplate(saved)
+ * ```
+ */
+ loadTemplate: (template: Omit[]) => void
}
interface EditorProps {
@@ -41,6 +81,7 @@ function ImageKitEditorImpl(
transformations,
initialize,
destroy,
+ loadTemplate,
} = useEditorStore()
const handleOnClose = () => {
@@ -73,8 +114,10 @@ function ImageKitEditorImpl(
loadImage: addImage,
loadImages: addImages,
setCurrentImage,
+ getTemplate: () => transformations,
+ loadTemplate,
}),
- [addImage, addImages, setCurrentImage],
+ [addImage, addImages, setCurrentImage, transformations, loadTemplate],
)
const mergedThemes = merge(defaultTheme, themeOverrides, theme)
diff --git a/packages/imagekit-editor-dev/src/backward-compatibility.test.ts b/packages/imagekit-editor-dev/src/backward-compatibility.test.ts
new file mode 100644
index 0000000..4a644c0
--- /dev/null
+++ b/packages/imagekit-editor-dev/src/backward-compatibility.test.ts
@@ -0,0 +1,2549 @@
+import { describe, expect, it } from "vitest"
+import type { Transformation } from "./store"
+import { TRANSFORMATION_STATE_VERSION } from "./store"
+import { transformationFormatters, transformationSchema } from "./schema"
+
+/**
+ * V1 Template Fixtures
+ * These represent real saved templates from v1 of the editor.
+ * Tests ensure these templates continue to work even after UI/schema changes.
+ */
+
+// Simple single transformation template
+const V1_BASIC_TEMPLATE: Omit[] = [
+ {
+ key: "adjust-background",
+ name: "Background",
+ type: "transformation",
+ value: {
+ backgroundType: "color",
+ background: "#FFFFFF",
+ },
+ version: "v1",
+ },
+]
+
+// Multiple common transformations
+const V1_COMMON_TEMPLATE: Omit[] = [
+ {
+ key: "resize_and_crop-resize_and_crop",
+ name: "Resize and Crop",
+ type: "transformation",
+ value: {
+ width: 800,
+ height: 600,
+ mode: "pad_resize",
+ },
+ version: "v1",
+ },
+ {
+ key: "adjust-background",
+ name: "Background",
+ type: "transformation",
+ value: {
+ backgroundType: "color",
+ background: "#E8E8E8",
+ },
+ version: "v1",
+ },
+ {
+ key: "adjust-rotate",
+ name: "Rotate",
+ type: "transformation",
+ value: {
+ rotate: 90,
+ },
+ version: "v1",
+ },
+]
+
+// Complex template with gradient background
+const V1_GRADIENT_TEMPLATE: Omit[] = [
+ {
+ key: "adjust-background",
+ name: "Background",
+ type: "transformation",
+ value: {
+ backgroundType: "gradient",
+ backgroundGradient: {
+ from: "#FFFFFFFF",
+ to: "#00000000",
+ direction: "bottom",
+ stopPoint: 100,
+ },
+ },
+ version: "v1",
+ },
+]
+
+// AI transformations
+const V1_AI_TEMPLATE: Omit[] = [
+ {
+ key: "ai-bgremove",
+ name: "Remove Background",
+ type: "transformation",
+ value: {
+ bgremove: true,
+ },
+ version: "v1",
+ },
+ {
+ key: "ai-changebg",
+ name: "Change Background",
+ type: "transformation",
+ value: {
+ changebg: "beach sunset",
+ },
+ version: "v1",
+ },
+]
+
+// Delivery optimizations
+const V1_DELIVERY_TEMPLATE: Omit[] = [
+ {
+ key: "delivery-quality",
+ name: "Quality",
+ type: "transformation",
+ value: {
+ quality: 80,
+ },
+ version: "v1",
+ },
+ {
+ key: "delivery-format",
+ name: "Format",
+ type: "transformation",
+ value: {
+ format: "webp",
+ },
+ version: "v1",
+ },
+]
+
+// Layer transformations
+const V1_LAYER_TEXT_TEMPLATE: Omit[] = [
+ {
+ key: "layers-text",
+ name: "Text",
+ type: "transformation",
+ value: {
+ text: "Hello World",
+ fontSize: 48,
+ fontColor: "#000000",
+ x: 50,
+ y: 50,
+ fontFamily: "arial",
+ },
+ version: "v1",
+ },
+]
+
+// Advanced adjustments
+const V1_ADVANCED_TEMPLATE: Omit[] = [
+ {
+ key: "adjust-contrast",
+ name: "Contrast",
+ type: "transformation",
+ value: {
+ contrast: true,
+ },
+ version: "v1",
+ },
+ {
+ key: "adjust-blur",
+ name: "Blur",
+ type: "transformation",
+ value: {
+ blur: 10,
+ },
+ version: "v1",
+ },
+ {
+ key: "adjust-radius",
+ name: "Corner Radius",
+ type: "transformation",
+ value: {
+ radius: {
+ radius: 20,
+ },
+ },
+ version: "v1",
+ },
+]
+
+// Comprehensive template with many transformations
+const V1_COMPREHENSIVE_TEMPLATE: Omit[] = [
+ {
+ key: "resize_and_crop-resize_and_crop",
+ name: "Resize and Crop",
+ type: "transformation",
+ value: {
+ width: 1200,
+ height: 800,
+ mode: "pad_resize",
+ },
+ version: "v1",
+ },
+ {
+ key: "adjust-background",
+ name: "Background",
+ type: "transformation",
+ value: {
+ backgroundType: "color",
+ background: "#FAFAFA",
+ },
+ version: "v1",
+ },
+ {
+ key: "adjust-radius",
+ name: "Corner Radius",
+ type: "transformation",
+ value: {
+ radius: {
+ radius: 15,
+ },
+ },
+ version: "v1",
+ },
+ {
+ key: "adjust-border",
+ name: "Border",
+ type: "transformation",
+ value: {
+ border: 5,
+ borderColor: "#333333",
+ },
+ version: "v1",
+ },
+ {
+ key: "delivery-quality",
+ name: "Quality",
+ type: "transformation",
+ value: {
+ quality: 85,
+ },
+ version: "v1",
+ },
+ {
+ key: "delivery-format",
+ name: "Format",
+ type: "transformation",
+ value: {
+ format: "webp",
+ },
+ version: "v1",
+ },
+]
+
+// Template without version field (backward compatibility)
+const V1_UNVERSIONED_TEMPLATE: Omit[] = [
+ {
+ key: "adjust-background",
+ name: "Background",
+ type: "transformation",
+ value: {
+ backgroundType: "color",
+ background: "#FFFFFF",
+ },
+ },
+ {
+ key: "adjust-rotate",
+ name: "Rotate",
+ type: "transformation",
+ value: {
+ rotate: 180,
+ },
+ },
+]
+
+/**
+ * Helper to find a transformation schema by key
+ */
+function findTransformationSchema(key: string) {
+ for (const category of transformationSchema) {
+ const item = category.items.find((item) => item.key === key)
+ if (item) {
+ return item
+ }
+ }
+ return null
+}
+
+/**
+ * Helper to check if a transformation key exists in the schema
+ */
+function isTransformationKeyValid(key: string): boolean {
+ return findTransformationSchema(key) !== null
+}
+
+/**
+ * Validates that a transformation can be processed by the editor
+ * - Key must exist in current schema
+ * - Structure must be valid (name, type, value)
+ * - Value must pass Zod schema validation
+ * Returns validation result with details
+ */
+function validateTransformation(t: Omit): {
+ valid: boolean
+ errors: string[]
+} {
+ const errors: string[] = []
+
+ // Check basic structure
+ if (!t.name) {
+ errors.push("Missing 'name' field")
+ }
+ if (t.type !== "transformation") {
+ errors.push(`Invalid type: expected 'transformation', got '${t.type}'`)
+ }
+ if (!t.value) {
+ errors.push("Missing 'value' field")
+ }
+
+ // Check if key exists in schema
+ const schemaItem = findTransformationSchema(t.key)
+ if (!schemaItem) {
+ errors.push(`Transformation key '${t.key}' not found in current schema`)
+ return { valid: false, errors }
+ }
+
+ // Validate value against Zod schema
+ try {
+ const result = schemaItem.schema.safeParse(t.value)
+ if (!result.success) {
+ result.error.errors.forEach((err) => {
+ errors.push(
+ `Schema validation failed for '${err.path.join(".")}': ${err.message}`
+ )
+ })
+ }
+ } catch (error) {
+ errors.push(`Schema validation error: ${error}`)
+ }
+
+ return {
+ valid: errors.length === 0,
+ errors,
+ }
+}
+
+describe("Backward Compatibility - V1 Templates", () => {
+ describe("Version Constant", () => {
+ it("should have v1 as current version", () => {
+ expect(TRANSFORMATION_STATE_VERSION).toBe("v1")
+ })
+ })
+
+ describe("V1 Basic Template", () => {
+ it("should parse basic template as valid JSON", () => {
+ const json = JSON.stringify(V1_BASIC_TEMPLATE)
+ const parsed = JSON.parse(json)
+ expect(Array.isArray(parsed)).toBe(true)
+ expect(parsed.length).toBe(1)
+ })
+
+ it("should have valid transformation keys", () => {
+ V1_BASIC_TEMPLATE.forEach((t) => {
+ expect(isTransformationKeyValid(t.key)).toBe(true)
+ })
+ })
+
+ it("should have version field set to v1", () => {
+ V1_BASIC_TEMPLATE.forEach((t) => {
+ expect(t.version).toBe("v1")
+ })
+ })
+
+ it("should pass Zod schema validation", () => {
+ V1_BASIC_TEMPLATE.forEach((t) => {
+ const result = validateTransformation(t)
+ if (!result.valid) {
+ console.error(`Validation errors for ${t.key}:`, result.errors)
+ }
+ expect(result.valid).toBe(true)
+ expect(result.errors).toEqual([])
+ })
+ })
+ })
+
+ describe("V1 Common Template", () => {
+ it("should parse template as valid JSON", () => {
+ const json = JSON.stringify(V1_COMMON_TEMPLATE)
+ const parsed = JSON.parse(json)
+ expect(Array.isArray(parsed)).toBe(true)
+ expect(parsed.length).toBe(3)
+ })
+
+ it("should have all valid transformation keys", () => {
+ V1_COMMON_TEMPLATE.forEach((t) => {
+ expect(isTransformationKeyValid(t.key)).toBe(true)
+ })
+ })
+
+ it("should pass Zod schema validation", () => {
+ V1_COMMON_TEMPLATE.forEach((t) => {
+ const result = validateTransformation(t)
+ if (!result.valid) {
+ console.error(`Validation errors for ${t.key}:`, result.errors)
+ }
+ expect(result.valid).toBe(true)
+ expect(result.errors).toEqual([])
+ })
+ })
+ })
+
+ describe("V1 Gradient Template", () => {
+ it("should parse template as valid JSON", () => {
+ const json = JSON.stringify(V1_GRADIENT_TEMPLATE)
+ const parsed = JSON.parse(json)
+ expect(Array.isArray(parsed)).toBe(true)
+ })
+
+ it("should preserve complex gradient values", () => {
+ const json = JSON.stringify(V1_GRADIENT_TEMPLATE)
+ const parsed = JSON.parse(json)
+ const gradient = parsed[0].value.backgroundGradient
+ expect(gradient.from).toBe("#FFFFFFFF")
+ expect(gradient.to).toBe("#00000000")
+ expect(gradient.direction).toBe("bottom")
+ expect(gradient.stopPoint).toBe(100)
+ })
+
+ it("should pass Zod schema validation", () => {
+ V1_GRADIENT_TEMPLATE.forEach((t) => {
+ const result = validateTransformation(t)
+ if (!result.valid) {
+ console.error(`Validation errors for ${t.key}:`, result.errors)
+ }
+ expect(result.valid).toBe(true)
+ expect(result.errors).toEqual([])
+ })
+ })
+ })
+
+ describe("V1 AI Template", () => {
+ it("should parse AI transformations as valid JSON", () => {
+ const json = JSON.stringify(V1_AI_TEMPLATE)
+ const parsed = JSON.parse(json)
+ expect(Array.isArray(parsed)).toBe(true)
+ expect(parsed.length).toBe(2)
+ })
+
+ it("should have valid AI transformation keys", () => {
+ V1_AI_TEMPLATE.forEach((t) => {
+ expect(isTransformationKeyValid(t.key)).toBe(true)
+ })
+ })
+
+ it("should pass Zod schema validation", () => {
+ V1_AI_TEMPLATE.forEach((t) => {
+ const result = validateTransformation(t)
+ if (!result.valid) {
+ console.error(`Validation errors for ${t.key}:`, result.errors)
+ }
+ expect(result.valid).toBe(true)
+ expect(result.errors).toEqual([])
+ })
+ })
+ })
+
+ describe("V1 Delivery Template", () => {
+ it("should parse delivery optimizations as valid JSON", () => {
+ const json = JSON.stringify(V1_DELIVERY_TEMPLATE)
+ const parsed = JSON.parse(json)
+ expect(Array.isArray(parsed)).toBe(true)
+ expect(parsed.length).toBe(2)
+ })
+
+ it("should have valid delivery transformation keys", () => {
+ V1_DELIVERY_TEMPLATE.forEach((t) => {
+ expect(isTransformationKeyValid(t.key)).toBe(true)
+ })
+ })
+
+ it("should pass Zod schema validation", () => {
+ V1_DELIVERY_TEMPLATE.forEach((t) => {
+ const result = validateTransformation(t)
+ if (!result.valid) {
+ console.error(`Validation errors for ${t.key}:`, result.errors)
+ }
+ expect(result.valid).toBe(true)
+ expect(result.errors).toEqual([])
+ })
+ })
+ })
+
+ describe("V1 Layer Text Template", () => {
+ it("should parse text layer as valid JSON", () => {
+ const json = JSON.stringify(V1_LAYER_TEXT_TEMPLATE)
+ const parsed = JSON.parse(json)
+ expect(Array.isArray(parsed)).toBe(true)
+ })
+
+ it("should preserve text layer values", () => {
+ const json = JSON.stringify(V1_LAYER_TEXT_TEMPLATE)
+ const parsed = JSON.parse(json)
+ expect(parsed[0].value.text).toBe("Hello World")
+ expect(parsed[0].value.fontSize).toBe(48)
+ expect(parsed[0].value.fontColor).toBe("#000000")
+ })
+
+ it("should have valid text layer key", () => {
+ expect(isTransformationKeyValid(V1_LAYER_TEXT_TEMPLATE[0].key)).toBe(true)
+ })
+ })
+
+ describe("V1 Advanced Template", () => {
+ it("should parse advanced adjustments as valid JSON", () => {
+ const json = JSON.stringify(V1_ADVANCED_TEMPLATE)
+ const parsed = JSON.parse(json)
+ expect(Array.isArray(parsed)).toBe(true)
+ expect(parsed.length).toBe(3)
+ })
+
+ it("should have all valid transformation keys", () => {
+ V1_ADVANCED_TEMPLATE.forEach((t) => {
+ expect(isTransformationKeyValid(t.key)).toBe(true)
+ })
+ })
+
+ it("should pass Zod schema validation", () => {
+ V1_ADVANCED_TEMPLATE.forEach((t) => {
+ const result = validateTransformation(t)
+ if (!result.valid) {
+ console.error(`Validation errors for ${t.key}:`, result.errors)
+ }
+ expect(result.valid).toBe(true)
+ expect(result.errors).toEqual([])
+ })
+ })
+ })
+
+ describe("V1 Comprehensive Template", () => {
+ it("should parse template with many transforms", () => {
+ const json = JSON.stringify(V1_COMPREHENSIVE_TEMPLATE)
+ const parsed = JSON.parse(json)
+ expect(Array.isArray(parsed)).toBe(true)
+ expect(parsed.length).toBe(6)
+ })
+
+ it("should have all valid transformation keys", () => {
+ V1_COMPREHENSIVE_TEMPLATE.forEach((t) => {
+ expect(isTransformationKeyValid(t.key)).toBe(true)
+ })
+ })
+
+ it("should pass Zod schema validation", () => {
+ V1_COMPREHENSIVE_TEMPLATE.forEach((t) => {
+ const result = validateTransformation(t)
+ if (!result.valid) {
+ console.error(`Validation errors for ${t.key}:`, result.errors)
+ }
+ expect(result.valid).toBe(true)
+ expect(result.errors).toEqual([])
+ })
+ })
+ })
+
+ describe("V1 Unversioned Template (Backward Compatibility)", () => {
+ it("should parse template as valid JSON", () => {
+ const json = JSON.stringify(V1_UNVERSIONED_TEMPLATE)
+ const parsed = JSON.parse(json)
+ expect(Array.isArray(parsed)).toBe(true)
+ expect(parsed.length).toBe(2)
+ })
+
+ it("should handle missing version field", () => {
+ V1_UNVERSIONED_TEMPLATE.forEach((t) => {
+ expect(t.version).toBeUndefined()
+ })
+ })
+
+ it("should have valid transformation keys even without version", () => {
+ V1_UNVERSIONED_TEMPLATE.forEach((t) => {
+ expect(isTransformationKeyValid(t.key)).toBe(true)
+ })
+ })
+
+ it("should pass Zod schema validation", () => {
+ V1_UNVERSIONED_TEMPLATE.forEach((t) => {
+ const result = validateTransformation(t as Omit)
+ if (!result.valid) {
+ console.error(`Validation errors for ${t.key}:`, result.errors)
+ }
+ expect(result.valid).toBe(true)
+ expect(result.errors).toEqual([])
+ })
+ })
+
+ it("should be able to add version to unversioned state", () => {
+ const withVersion = V1_UNVERSIONED_TEMPLATE.map((t) => ({
+ ...t,
+ version: TRANSFORMATION_STATE_VERSION,
+ }))
+
+ withVersion.forEach((t) => {
+ expect(t.version).toBe("v1")
+ })
+ })
+ })
+
+ describe("Template Serialization Consistency", () => {
+ it("should preserve all properties during JSON round-trip", () => {
+ const original = V1_COMPREHENSIVE_TEMPLATE
+ const json = JSON.stringify(original)
+ const parsed = JSON.parse(json)
+
+ expect(parsed.length).toBe(original.length)
+ parsed.forEach((t: Omit, i: number) => {
+ expect(t.key).toBe(original[i].key)
+ expect(t.name).toBe(original[i].name)
+ expect(t.type).toBe(original[i].type)
+ expect(t.version).toBe(original[i].version)
+ expect(JSON.stringify(t.value)).toBe(JSON.stringify(original[i].value))
+ })
+ })
+
+ it("should handle removal and addition of id field", () => {
+ const withId: Transformation = {
+ id: "test-123",
+ ...V1_BASIC_TEMPLATE[0],
+ }
+
+ // Remove id for storage
+ const { id, ...forStorage } = withId
+ expect(forStorage.id).toBeUndefined()
+
+ // Add id back when loading
+ const loaded = {
+ ...forStorage,
+ id: "new-id-456",
+ }
+ expect(loaded.id).toBe("new-id-456")
+ })
+ })
+
+ describe("Schema Key Validation", () => {
+ it("should validate all v1 fixture keys exist in current schema", () => {
+ const allFixtures = [
+ ...V1_BASIC_TEMPLATE,
+ ...V1_COMMON_TEMPLATE,
+ ...V1_GRADIENT_TEMPLATE,
+ ...V1_AI_TEMPLATE,
+ ...V1_DELIVERY_TEMPLATE,
+ ...V1_LAYER_TEXT_TEMPLATE,
+ ...V1_ADVANCED_TEMPLATE,
+ ...V1_COMPREHENSIVE_TEMPLATE,
+ ]
+
+ const uniqueKeys = new Set(allFixtures.map((t) => t.key))
+ const missingKeys: string[] = []
+
+ uniqueKeys.forEach((key) => {
+ if (!isTransformationKeyValid(key)) {
+ missingKeys.push(key)
+ }
+ })
+
+ expect(missingKeys).toEqual([])
+ })
+ })
+
+ describe("Validation Actually Works (Negative Tests)", () => {
+ it("should reject transformation with invalid key", () => {
+ const invalid: Omit = {
+ key: "nonexistent-transformation",
+ name: "Invalid",
+ type: "transformation",
+ value: {},
+ version: "v1",
+ }
+
+ const result = validateTransformation(invalid)
+ expect(result.valid).toBe(false)
+ expect(result.errors.length).toBeGreaterThan(0)
+ expect(result.errors.some((e) => e.includes("not found in current schema"))).toBe(true)
+ })
+
+ it("should reject transformation with wrong type", () => {
+ const invalid: any = {
+ key: "adjust-background",
+ name: "Background",
+ type: "wrong-type",
+ value: { background: "#FFF" },
+ version: "v1",
+ }
+
+ const result = validateTransformation(invalid)
+ expect(result.valid).toBe(false)
+ expect(result.errors.some((e) => e.includes("Invalid type"))).toBe(true)
+ })
+
+ it("should reject transformation with invalid value structure", () => {
+ const invalid: Omit = {
+ key: "adjust-radius",
+ name: "Corner Radius",
+ type: "transformation",
+ value: {
+ radius: 999, // Should be an object with {radius: number}
+ },
+ version: "v1",
+ }
+
+ const result = validateTransformation(invalid)
+ expect(result.valid).toBe(false)
+ expect(result.errors.length).toBeGreaterThan(0)
+ })
+
+ it("should reject transformation with missing required fields", () => {
+ const invalid: Omit = {
+ key: "adjust-rotate",
+ name: "Rotate",
+ type: "transformation",
+ value: {}, // Missing required 'rotate' field
+ version: "v1",
+ }
+
+ const result = validateTransformation(invalid)
+ expect(result.valid).toBe(false)
+ expect(result.errors.length).toBeGreaterThan(0)
+ })
+
+ it("should reject transformation with invalid data types", () => {
+ const invalid: Omit = {
+ key: "adjust-rotate",
+ name: "Rotate",
+ type: "transformation",
+ value: {
+ rotate: "not-a-number", // Should be a number
+ },
+ version: "v1",
+ }
+
+ const result = validateTransformation(invalid)
+ expect(result.valid).toBe(false)
+ expect(result.errors.length).toBeGreaterThan(0)
+ })
+ })
+
+ /**
+ * Deep Schema Validation Tests
+ * These tests exercise custom validators and complex schema logic to achieve high coverage
+ */
+ describe("Schema Validators - Width & Height", () => {
+ it("should validate width as number", () => {
+ const template: Omit = {
+ key: "resize_and_crop-resize_and_crop",
+ name: "Resize",
+ type: "transformation",
+ value: { width: 800, height: 600, mode: "c-force" },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(true)
+ })
+
+ it("should validate width as decimal", () => {
+ const template: Omit = {
+ key: "resize_and_crop-resize_and_crop",
+ name: "Resize",
+ type: "transformation",
+ value: { width: 0.5, height: 600, mode: "c-force" },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(true)
+ })
+
+ it("should validate width as expression", () => {
+ const template: Omit = {
+ key: "resize_and_crop-resize_and_crop",
+ name: "Resize",
+ type: "transformation",
+ value: { width: "iw_div_2", height: 600, mode: "c-force" },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(true)
+ })
+
+ it("should validate height as expression", () => {
+ const template: Omit = {
+ key: "resize_and_crop-resize_and_crop",
+ name: "Resize",
+ type: "transformation",
+ value: { width: 800, height: "ih_mul_1.5", mode: "c-force" },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(true)
+ })
+
+ it("should reject invalid width expression", () => {
+ const template: Omit = {
+ key: "resize_and_crop-resize_and_crop",
+ name: "Resize",
+ type: "transformation",
+ value: { width: "invalid_expr", height: 600, mode: "c-force" },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(false)
+ })
+ })
+
+ describe("Schema Validators - Color", () => {
+ it("should validate 6-digit hex color", () => {
+ const template: Omit = {
+ key: "adjust-background",
+ name: "Background",
+ type: "transformation",
+ value: { backgroundType: "color", background: "#FF5533" },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(true)
+ })
+
+ it("should validate 3-digit hex color", () => {
+ const template: Omit = {
+ key: "adjust-background",
+ name: "Background",
+ type: "transformation",
+ value: { backgroundType: "color", background: "#F53" },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(true)
+ })
+
+ it("should validate 8-digit hex color with alpha", () => {
+ const template: Omit = {
+ key: "adjust-background",
+ name: "Background",
+ type: "transformation",
+ value: { backgroundType: "color", background: "#FF5533AA" },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(true)
+ })
+
+ it("should validate hex without # prefix", () => {
+ const template: Omit = {
+ key: "adjust-background",
+ name: "Background",
+ type: "transformation",
+ value: { backgroundType: "color", background: "FF5533" },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(true)
+ })
+
+ it("should reject invalid hex color", () => {
+ const template: Omit = {
+ key: "adjust-background",
+ name: "Background",
+ type: "transformation",
+ value: { backgroundType: "color", background: "#GGGGGG" },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(false)
+ })
+ })
+
+ describe("Schema Validators - Aspect Ratio", () => {
+ it("should validate aspect ratio value format", () => {
+ const template: Omit = {
+ key: "resize_and_crop-resize_and_crop",
+ name: "Resize",
+ type: "transformation",
+ value: { width: 800, aspectRatio: "16-9" },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(true)
+ })
+
+ it("should validate aspect ratio with decimals", () => {
+ const template: Omit = {
+ key: "resize_and_crop-resize_and_crop",
+ name: "Resize",
+ type: "transformation",
+ value: { width: 800, aspectRatio: "16.5-9.5" },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(true)
+ })
+
+ it("should validate aspect ratio expression", () => {
+ const template: Omit = {
+ key: "resize_and_crop-resize_and_crop",
+ name: "Resize",
+ type: "transformation",
+ value: { width: 800, aspectRatio: "iar_mul_1.5" },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(true)
+ })
+
+ it("should reject invalid aspect ratio", () => {
+ const template: Omit = {
+ key: "resize_and_crop-resize_and_crop",
+ name: "Resize",
+ type: "transformation",
+ value: { width: 800, aspectRatio: "16:9" },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(false)
+ })
+
+ it("should reject aspect ratio without width or height", () => {
+ const template: Omit = {
+ key: "resize_and_crop-resize_and_crop",
+ name: "Resize",
+ type: "transformation",
+ value: { aspectRatio: "16-9" },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(false)
+ })
+ })
+
+ describe("Schema Validators - Layer Positioning", () => {
+ it("should validate layer X as number", () => {
+ const template: Omit = {
+ key: "layers-text",
+ name: "Text",
+ type: "transformation",
+ value: { text: "Hello", positionX: "100", fontSize: 24, radius: 0 },
+ version: "v1",
+ }
+ const result = validateTransformation(template)
+ if (!result.valid) {
+ console.log("Layer X validation errors:", result.errors)
+ }
+ expect(result.valid).toBe(true)
+ })
+
+ it("should validate layer X as negative number", () => {
+ const template: Omit = {
+ key: "layers-text",
+ name: "Text",
+ type: "transformation",
+ value: { text: "Hello", positionX: "-50", fontSize: 24, radius: 0 },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(true)
+ })
+
+ it("should validate layer X as expression", () => {
+ const template: Omit = {
+ key: "layers-text",
+ name: "Text",
+ type: "transformation",
+ value: { text: "Hello", positionX: "bw_div_2", fontSize: 24, radius: 0 },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(true)
+ })
+
+ it("should validate layer Y as expression", () => {
+ const template: Omit = {
+ key: "layers-text",
+ name: "Text",
+ type: "transformation",
+ value: { text: "Hello", positionY: "bh_sub_100", fontSize: 24, radius: 0 },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(true)
+ })
+ })
+
+ describe("Resize & Crop Complex Validations", () => {
+ it("should require mode when both width and height are specified", () => {
+ const template: Omit = {
+ key: "resize_and_crop-resize_and_crop",
+ name: "Resize",
+ type: "transformation",
+ value: { width: 800, height: 600 }, // Missing mode
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(false)
+ })
+
+ it("should validate cm-pad_resize mode", () => {
+ const template: Omit = {
+ key: "resize_and_crop-resize_and_crop",
+ name: "Resize",
+ type: "transformation",
+ value: {
+ width: 800,
+ height: 600,
+ mode: "cm-pad_resize",
+ backgroundType: "color",
+ background: "#FFFFFF",
+ },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(true)
+ })
+
+ it("should require both dimensions for blurred background in pad_resize", () => {
+ const template: Omit = {
+ key: "resize_and_crop-resize_and_crop",
+ name: "Resize",
+ type: "transformation",
+ value: {
+ width: 800,
+ mode: "cm-pad_resize",
+ backgroundType: "blurred",
+ },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(false)
+ })
+
+ it("should require both dimensions for generative_fill background", () => {
+ const template: Omit = {
+ key: "resize_and_crop-resize_and_crop",
+ name: "Resize",
+ type: "transformation",
+ value: {
+ height: 600,
+ mode: "cm-pad_resize",
+ backgroundType: "generative_fill",
+ },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(false)
+ })
+
+ it("should validate cm-extract mode with focus object", () => {
+ const template: Omit = {
+ key: "resize_and_crop-resize_and_crop",
+ name: "Resize",
+ type: "transformation",
+ value: {
+ width: 800,
+ height: 600,
+ mode: "cm-extract",
+ focus: "object",
+ focusObject: "person",
+ },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(true)
+ })
+
+ it("should require focusObject when extract mode has object focus", () => {
+ const template: Omit = {
+ key: "resize_and_crop-resize_and_crop",
+ name: "Resize",
+ type: "transformation",
+ value: {
+ width: 800,
+ height: 600,
+ mode: "cm-extract",
+ focus: "object",
+ },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(false)
+ })
+
+ it("should require focusAnchor when extract mode has anchor focus", () => {
+ const template: Omit = {
+ key: "resize_and_crop-resize_and_crop",
+ name: "Resize",
+ type: "transformation",
+ value: {
+ width: 800,
+ height: 600,
+ mode: "cm-extract",
+ focus: "anchor",
+ },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(false)
+ })
+
+ it("should validate extract mode with topleft coordinates", () => {
+ const template: Omit = {
+ key: "resize_and_crop-resize_and_crop",
+ name: "Resize",
+ type: "transformation",
+ value: {
+ width: 800,
+ height: 600,
+ mode: "cm-extract",
+ focus: "coordinates",
+ coordinateMethod: "topleft",
+ x: "100",
+ y: "100",
+ },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(true)
+ })
+
+ it("should require coordinates when extract uses coordinates focus", () => {
+ const template: Omit = {
+ key: "resize_and_crop-resize_and_crop",
+ name: "Resize",
+ type: "transformation",
+ value: {
+ width: 800,
+ height: 600,
+ mode: "cm-extract",
+ focus: "coordinates",
+ coordinateMethod: "topleft",
+ },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(false)
+ })
+
+ it("should validate extract mode with center coordinates", () => {
+ const template: Omit = {
+ key: "resize_and_crop-resize_and_crop",
+ name: "Resize",
+ type: "transformation",
+ value: {
+ width: 800,
+ height: 600,
+ mode: "cm-extract",
+ focus: "coordinates",
+ coordinateMethod: "center",
+ xc: "400",
+ yc: "300",
+ },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(true)
+ })
+
+ it("should validate DPR with width", () => {
+ const template: Omit = {
+ key: "resize_and_crop-resize_and_crop",
+ name: "Resize",
+ type: "transformation",
+ value: {
+ width: 800,
+ dprEnabled: true,
+ dpr: 2,
+ },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(true)
+ })
+
+ it("should validate DPR as auto", () => {
+ const template: Omit = {
+ key: "resize_and_crop-resize_and_crop",
+ name: "Resize",
+ type: "transformation",
+ value: {
+ width: 800,
+ dprEnabled: true,
+ dpr: "auto",
+ },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(true)
+ })
+
+ it("should reject DPR without width or height", () => {
+ const template: Omit = {
+ key: "resize_and_crop-resize_and_crop",
+ name: "Resize",
+ type: "transformation",
+ value: {
+ dpr: 2,
+ },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(false)
+ })
+
+ it("should validate c-maintain_ratio with focus anchor", () => {
+ const template: Omit = {
+ key: "resize_and_crop-resize_and_crop",
+ name: "Resize",
+ type: "transformation",
+ value: {
+ width: 800,
+ height: 600,
+ mode: "c-maintain_ratio",
+ focus: "anchor",
+ focusAnchor: "center",
+ },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(true)
+ })
+
+ it("should require focusAnchor in maintain_ratio with anchor focus", () => {
+ const template: Omit = {
+ key: "resize_and_crop-resize_and_crop",
+ name: "Resize",
+ type: "transformation",
+ value: {
+ width: 800,
+ height: 600,
+ mode: "c-maintain_ratio",
+ focus: "anchor",
+ },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(false)
+ })
+
+ it("should validate all resize modes", () => {
+ const modes = [
+ "c-maintain_ratio",
+ "cm-pad_resize",
+ "cm-extract",
+ "cm-pad_extract",
+ "c-force",
+ "c-at_max",
+ "c-at_max_enlarge",
+ "c-at_least",
+ ]
+
+ modes.forEach((mode) => {
+ const template: Omit = {
+ key: "resize_and_crop-resize_and_crop",
+ name: "Resize",
+ type: "transformation",
+ value: { width: 800, height: 600, mode },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(true)
+ })
+ })
+ })
+
+ describe("Unsharpen Mask Validation", () => {
+ it("should validate complete unsharpen mask", () => {
+ const template: Omit = {
+ key: "adjust-unsharpen-mask",
+ name: "Unsharpen Mask",
+ type: "transformation",
+ value: {
+ unsharpenMaskRadius: 2,
+ unsharpenMaskSigma: 1.5,
+ unsharpenMaskAmount: 1.2,
+ unsharpenMaskThreshold: 0.1,
+ },
+ version: "v1",
+ }
+ const result = validateTransformation(template)
+ if (!result.valid) {
+ console.log("Unsharpen mask errors:", result.errors)
+ }
+ expect(result.valid).toBe(true)
+ })
+
+ it("should require all fields when unsharpen mask is enabled", () => {
+ const template: Omit = {
+ key: "adjust-unsharpen-mask",
+ name: "Unsharpen Mask",
+ type: "transformation",
+ value: {
+ unsharpenMaskRadius: 2,
+ // Missing other required fields (sigma, amount, threshold)
+ },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(false)
+ })
+ })
+
+ describe("Additional Transformations Coverage", () => {
+ it("should validate shadow transformation", () => {
+ const template: Omit = {
+ key: "adjust-shadow",
+ name: "Shadow",
+ type: "transformation",
+ value: {
+ shadow: 5,
+ shadowBlur: 10,
+ shadowOffsetX: 5,
+ shadowOffsetY: 5,
+ },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(true)
+ })
+
+ it("should validate distort transformation", () => {
+ const template: Omit = {
+ key: "adjust-distort",
+ name: "Distort",
+ type: "transformation",
+ value: {
+ distort: true,
+ distortType: "perspective",
+ distortPerspective: {
+ x1: "10",
+ y1: "10",
+ x2: "100",
+ y2: "10",
+ x3: "100",
+ y3: "100",
+ x4: "10",
+ y4: "100",
+ },
+ },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(true)
+ })
+
+ it("should validate border transformation", () => {
+ const template: Omit = {
+ key: "adjust-border",
+ name: "Border",
+ type: "transformation",
+ value: {
+ borderWidth: 10,
+ borderColor: "#000000",
+ },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(true)
+ })
+
+ it("should validate trim transformation", () => {
+ const template: Omit = {
+ key: "adjust-trim",
+ name: "Trim",
+ type: "transformation",
+ value: {
+ trimEnabled: true,
+ trim: 10,
+ },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(true)
+ })
+
+ it("should validate color replace transformation", () => {
+ const template: Omit = {
+ key: "adjust-color-replace",
+ name: "Color Replace",
+ type: "transformation",
+ value: {
+ fromColor: "FF0000",
+ toColor: "00FF00",
+ tolerance: 20,
+ },
+ version: "v1",
+ }
+ const result = validateTransformation(template)
+ if (!result.valid) {
+ console.log("Color replace errors:", result.errors)
+ }
+ expect(result.valid).toBe(true)
+ })
+
+ it("should validate sharpen transformation", () => {
+ const template: Omit = {
+ key: "adjust-sharpen",
+ name: "Sharpen",
+ type: "transformation",
+ value: {
+ sharpenEnabled: true,
+ sharpen: 5,
+ },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(true)
+ })
+
+ it("should validate flip transformation", () => {
+ const template: Omit = {
+ key: "adjust-flip",
+ name: "Flip",
+ type: "transformation",
+ value: {
+ flip: "both",
+ },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(true)
+ })
+
+ it("should validate opacity transformation", () => {
+ const template: Omit = {
+ key: "adjust-opacity",
+ name: "Opacity",
+ type: "transformation",
+ value: {
+ opacity: 50,
+ },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(true)
+ })
+
+ it("should validate AI drop shadow", () => {
+ const template: Omit = {
+ key: "ai-dropshadow",
+ name: "Drop Shadow",
+ type: "transformation",
+ value: {
+ dropshadow: true,
+ },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(true)
+ })
+
+ it("should validate AI upscale", () => {
+ const template: Omit = {
+ key: "ai-upscale",
+ name: "Upscale",
+ type: "transformation",
+ value: {
+ upscale: true,
+ },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(true)
+ })
+
+ it("should validate AI edit transformation", () => {
+ const template: Omit = {
+ key: "ai-edit",
+ name: "Edit Image",
+ type: "transformation",
+ value: {
+ edit: "replace dog with cat",
+ },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(true)
+ })
+
+ it("should validate image layer", () => {
+ const template: Omit = {
+ key: "layers-image",
+ name: "Image Layer",
+ type: "transformation",
+ value: {
+ imageUrl: "https://example.com/image.jpg",
+ width: 200,
+ height: 200,
+ },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(true)
+ })
+
+ it("should validate complex text layer with all properties", () => {
+ const template: Omit = {
+ key: "layers-text",
+ name: "Text Layer",
+ type: "transformation",
+ value: {
+ text: "Hello World",
+ fontSize: 48,
+ fontFamily: "Arial",
+ color: "000000",
+ backgroundColor: "FFFFFF",
+ positionX: "100",
+ positionY: "200",
+ width: 400,
+ innerAlignment: "center",
+ opacity: 8,
+ rotation: 45,
+ radius: 10,
+ },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(true)
+ })
+
+ it("should validate gradient transformation", () => {
+ const template: Omit = {
+ key: "adjust-gradient",
+ name: "Gradient",
+ type: "transformation",
+ value: {
+ gradientSwitch: true,
+ gradient: {
+ from: "#FF0000",
+ to: "#0000FF",
+ direction: "bottom",
+ stopPoint: 50,
+ },
+ },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(true)
+ })
+
+ it("should validate grayscale transformation", () => {
+ const template: Omit = {
+ key: "adjust-grayscale",
+ name: "Grayscale",
+ type: "transformation",
+ value: {
+ grayscale: true,
+ },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(true)
+ })
+ })
+
+ describe("Transformation Formatters", () => {
+ it("should format background with color", () => {
+ const values = { backgroundType: "color", background: "#FF5533" }
+ const transforms: Record = {}
+ transformationFormatters.background(values, transforms)
+ expect(transforms.background).toBe("FF5533")
+ })
+
+ it("should format background with dominant color", () => {
+ const values = {
+ backgroundType: "color",
+ backgroundDominantAuto: true,
+ }
+ const transforms: Record = {}
+ transformationFormatters.background(values, transforms)
+ expect(transforms.background).toBe("dominant")
+ })
+
+ it("should format gradient with auto dominant", () => {
+ const values = {
+ backgroundType: "gradient",
+ backgroundGradientAutoDominant: true,
+ backgroundGradientPaletteSize: "3",
+ backgroundGradientMode: "linear",
+ }
+ const transforms: Record = {}
+ transformationFormatters.background(values, transforms)
+ expect(transforms.background).toBe("gradient_linear_3")
+ })
+
+ it("should format blurred background with negative brightness", () => {
+ const values = {
+ backgroundType: "blurred",
+ backgroundBlurIntensity: "10",
+ backgroundBlurBrightness: "-50",
+ }
+ const transforms: Record = {}
+ transformationFormatters.background(values, transforms)
+ // Should create blurred background with intensity and brightness
+ expect(transforms.background).toBe("blurred_10_N50")
+ })
+ })
+
+ describe("Validator Edge Cases - Reaching 100% Coverage", () => {
+ it("should handle empty string in layerX validator", () => {
+ const template: Omit = {
+ key: "layers-text",
+ name: "Text",
+ type: "transformation",
+ value: { text: "Hello", positionX: "", fontSize: 24, radius: 0 },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(true)
+ })
+
+ it("should handle undefined in layerX validator", () => {
+ const template: Omit = {
+ key: "layers-text",
+ name: "Text",
+ type: "transformation",
+ value: { text: "Hello", fontSize: 24, radius: 0 },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(true)
+ })
+
+ it("should reject invalid layerX expression", () => {
+ const template: Omit = {
+ key: "layers-text",
+ name: "Text",
+ type: "transformation",
+ value: { text: "Hello", positionX: "invalid_expr", fontSize: 24, radius: 0 },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(false)
+ })
+
+ it("should handle empty string in layerY validator", () => {
+ const template: Omit = {
+ key: "layers-text",
+ name: "Text",
+ type: "transformation",
+ value: { text: "Hello", positionY: "", fontSize: 24, radius: 0 },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(true)
+ })
+
+ it("should reject invalid layerY expression", () => {
+ const template: Omit = {
+ key: "layers-text",
+ name: "Text",
+ type: "transformation",
+ value: { text: "Hello", positionY: "badexpr", fontSize: 24, radius: 0 },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(false)
+ })
+
+ it("should validate line height as integer", () => {
+ const template: Omit = {
+ key: "layers-text",
+ name: "Text",
+ type: "transformation",
+ value: { text: "Hello", lineHeight: "24", fontSize: 24, radius: 0 },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(true)
+ })
+
+ it("should validate line height as expression", () => {
+ const template: Omit = {
+ key: "layers-text",
+ name: "Text",
+ type: "transformation",
+ value: { text: "Hello", lineHeight: "ih_mul_1.5", fontSize: 24, radius: 0 },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(true)
+ })
+
+ it("should handle empty line height", () => {
+ const template: Omit = {
+ key: "layers-text",
+ name: "Text",
+ type: "transformation",
+ value: { text: "Hello", lineHeight: "", fontSize: 24, radius: 0 },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(true)
+ })
+
+ it("should reject invalid line height", () => {
+ const template: Omit = {
+ key: "layers-text",
+ name: "Text",
+ type: "transformation",
+ value: { text: "Hello", lineHeight: "not_valid", fontSize: 24, radius: 0 },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(false)
+ })
+
+ it("should handle empty aspect ratio", () => {
+ const template: Omit = {
+ key: "resize_and_crop-resize_and_crop",
+ name: "Resize",
+ type: "transformation",
+ value: { width: 800, aspectRatio: "" },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(true)
+ })
+
+ it("should handle undefined aspect ratio", () => {
+ const template: Omit = {
+ key: "resize_and_crop-resize_and_crop",
+ name: "Resize",
+ type: "transformation",
+ value: { width: 800 },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(true)
+ })
+ })
+
+ describe("Distort Perspective Validation - Full Coverage", () => {
+ it("should reject invalid perspective coordinates (non-numeric)", () => {
+ const template: Omit = {
+ key: "adjust-distort",
+ name: "Distort",
+ type: "transformation",
+ value: {
+ distort: true,
+ distortType: "perspective",
+ distortPerspective: {
+ x1: "abc",
+ y1: "10",
+ x2: "100",
+ y2: "10",
+ x3: "100",
+ y3: "100",
+ x4: "10",
+ y4: "100",
+ },
+ },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(false)
+ })
+
+ it("should reject incomplete perspective coordinates", () => {
+ const template: Omit = {
+ key: "adjust-distort",
+ name: "Distort",
+ type: "transformation",
+ value: {
+ distort: true,
+ distortType: "perspective",
+ distortPerspective: {
+ x1: "10",
+ y1: "10",
+ x2: "",
+ y2: "",
+ },
+ },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(false)
+ })
+
+ it("should reject invalid perspective coordinate arrangement", () => {
+ const template: Omit = {
+ key: "adjust-distort",
+ name: "Distort",
+ type: "transformation",
+ value: {
+ distort: true,
+ distortType: "perspective",
+ distortPerspective: {
+ x1: "100",
+ y1: "100",
+ x2: "10",
+ y2: "10",
+ x3: "10",
+ y3: "10",
+ x4: "100",
+ y4: "100",
+ },
+ },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(false)
+ })
+
+ it("should validate arc distortion with positive degree", () => {
+ const template: Omit = {
+ key: "adjust-distort",
+ name: "Distort",
+ type: "transformation",
+ value: {
+ distort: true,
+ distortType: "arc",
+ distortArcDegree: "45",
+ },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(true)
+ })
+
+ it("should validate arc distortion with negative degree", () => {
+ const template: Omit = {
+ key: "adjust-distort",
+ name: "Distort",
+ type: "transformation",
+ value: {
+ distort: true,
+ distortType: "arc",
+ distortArcDegree: "-45",
+ },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(true)
+ })
+
+ it("should validate arc distortion with N prefix for negative", () => {
+ const template: Omit = {
+ key: "adjust-distort",
+ name: "Distort",
+ type: "transformation",
+ value: {
+ distort: true,
+ distortType: "arc",
+ distortArcDegree: "N45",
+ },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(true)
+ })
+
+ it("should reject arc with zero degree", () => {
+ const template: Omit = {
+ key: "adjust-distort",
+ name: "Distort",
+ type: "transformation",
+ value: {
+ distort: true,
+ distortType: "arc",
+ distortArcDegree: "0",
+ },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(false)
+ })
+
+ it("should reject arc with missing degree", () => {
+ const template: Omit = {
+ key: "adjust-distort",
+ name: "Distort",
+ type: "transformation",
+ value: {
+ distort: true,
+ distortType: "arc",
+ },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(false)
+ })
+
+ it("should reject arc with non-numeric degree", () => {
+ const template: Omit = {
+ key: "adjust-distort",
+ name: "Distort",
+ type: "transformation",
+ value: {
+ distort: true,
+ distortType: "arc",
+ distortArcDegree: "abc",
+ },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(false)
+ })
+
+ it("should validate perspective with N prefix coordinates", () => {
+ const template: Omit = {
+ key: "adjust-distort",
+ name: "Distort",
+ type: "transformation",
+ value: {
+ distort: true,
+ distortType: "perspective",
+ distortPerspective: {
+ x1: "10",
+ y1: "10",
+ x2: "100",
+ y2: "10",
+ x3: "100",
+ y3: "100",
+ x4: "N5",
+ y4: "100",
+ },
+ },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(true)
+ })
+ })
+
+ describe("Background Field Contexts and Formatters", () => {
+ it("should format background for generative fill without prompt", () => {
+ const values = {
+ backgroundType: "generative_fill",
+ }
+ const transforms: Record = {}
+ transformationFormatters.background(values, transforms)
+ expect(transforms.background).toBe("genfill")
+ })
+
+ it("should format background for generative fill with simple prompt", () => {
+ const values = {
+ backgroundType: "generative_fill",
+ backgroundGenerativeFill: "beach",
+ }
+ const transforms: Record = {}
+ transformationFormatters.background(values, transforms)
+ expect(transforms.background).toBe("genfill-prompt-beach")
+ })
+
+ it("should format background for blurred with auto intensity and brightness", () => {
+ const values = {
+ backgroundType: "blurred",
+ backgroundBlurIntensity: "auto",
+ backgroundBlurBrightness: "50",
+ }
+ const transforms: Record = {}
+ transformationFormatters.background(values, transforms)
+ expect(transforms.background).toBe("blurred_auto_50")
+ })
+
+ it("should format background for blurred with auto intensity only", () => {
+ const values = {
+ backgroundType: "blurred",
+ backgroundBlurIntensity: "auto",
+ }
+ const transforms: Record = {}
+ transformationFormatters.background(values, transforms)
+ expect(transforms.background).toBe("blurred_auto")
+ })
+
+ it("should format background for blurred with numeric intensity only", () => {
+ const values = {
+ backgroundType: "blurred",
+ backgroundBlurIntensity: "5",
+ }
+ const transforms: Record = {}
+ transformationFormatters.background(values, transforms)
+ expect(transforms.background).toBe("blurred_5")
+ })
+
+ it("should format background for blurred with numeric intensity and brightness", () => {
+ const values = {
+ backgroundType: "blurred",
+ backgroundBlurIntensity: "5",
+ backgroundBlurBrightness: "25",
+ }
+ const transforms: Record = {}
+ transformationFormatters.background(values, transforms)
+ expect(transforms.background).toBe("blurred_5_25")
+ })
+
+ it("should format background for blurred fallback", () => {
+ const values = {
+ backgroundType: "blurred",
+ backgroundBlurIntensity: "invalid",
+ }
+ const transforms: Record = {}
+ transformationFormatters.background(values, transforms)
+ expect(transforms.background).toBe("blurred")
+ })
+
+ it("should validate gradient with manual colors", () => {
+ const template: Omit = {
+ key: "adjust-background",
+ name: "Background",
+ type: "transformation",
+ value: {
+ backgroundType: "gradient",
+ backgroundGradient: {
+ from: "#FF0000",
+ to: "#0000FF",
+ direction: "top",
+ stopPoint: 75,
+ },
+ },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(true)
+ })
+
+ it("should validate background with auto gradient different modes", () => {
+ const template: Omit = {
+ key: "adjust-background",
+ name: "Background",
+ type: "transformation",
+ value: {
+ backgroundType: "gradient",
+ backgroundGradientAutoDominant: true,
+ backgroundGradientMode: "radial",
+ backgroundGradientPaletteSize: "4",
+ },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(true)
+ })
+ })
+
+ describe("Additional Resize & Crop Edge Cases", () => {
+ it("should validate cm-pad_extract mode", () => {
+ const template: Omit = {
+ key: "resize_and_crop-resize_and_crop",
+ name: "Resize",
+ type: "transformation",
+ value: {
+ width: 800,
+ height: 600,
+ mode: "cm-pad_extract",
+ backgroundType: "color",
+ background: "#FFFFFF",
+ },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(true)
+ })
+
+ it("should validate c-at_least mode", () => {
+ const template: Omit = {
+ key: "resize_and_crop-resize_and_crop",
+ name: "Resize",
+ type: "transformation",
+ value: {
+ width: 800,
+ height: 600,
+ mode: "c-at_least",
+ },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(true)
+ })
+
+ it("should validate width with various expression operators", () => {
+ const operators = ["add", "sub", "mul", "div", "mod", "pow"]
+ operators.forEach((op) => {
+ const template: Omit = {
+ key: "resize_and_crop-resize_and_crop",
+ name: "Resize",
+ type: "transformation",
+ value: { width: `iw_${op}_2` },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(true)
+ })
+ })
+
+ it("should validate height with various base dimensions", () => {
+ const bases = ["ih", "bh", "ch"]
+ bases.forEach((base) => {
+ const template: Omit = {
+ key: "resize_and_crop-resize_and_crop",
+ name: "Resize",
+ type: "transformation",
+ value: { height: `${base}_mul_0.5` },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(true)
+ })
+ })
+
+ it("should validate aspect ratio with car expression", () => {
+ const template: Omit = {
+ key: "resize_and_crop-resize_and_crop",
+ name: "Resize",
+ type: "transformation",
+ value: { width: 800, aspectRatio: "car_mul_1.2" },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(true)
+ })
+
+ it("should handle object focus in maintain_ratio mode", () => {
+ const template: Omit = {
+ key: "resize_and_crop-resize_and_crop",
+ name: "Resize",
+ type: "transformation",
+ value: {
+ width: 800,
+ height: 600,
+ mode: "c-maintain_ratio",
+ focus: "object",
+ focusObject: "car",
+ },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(true)
+ })
+
+ it("should require focusObject in maintain_ratio with object focus", () => {
+ const template: Omit = {
+ key: "resize_and_crop-resize_and_crop",
+ name: "Resize",
+ type: "transformation",
+ value: {
+ width: 800,
+ height: 600,
+ mode: "c-maintain_ratio",
+ focus: "object",
+ },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(false)
+ })
+
+ it("should validate extract with center coordinates only xc", () => {
+ const template: Omit = {
+ key: "resize_and_crop-resize_and_crop",
+ name: "Resize",
+ type: "transformation",
+ value: {
+ width: 800,
+ height: 600,
+ mode: "cm-extract",
+ focus: "coordinates",
+ coordinateMethod: "center",
+ xc: "400",
+ },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(true)
+ })
+
+ it("should validate extract with topleft coordinates only x", () => {
+ const template: Omit = {
+ key: "resize_and_crop-resize_and_crop",
+ name: "Resize",
+ type: "transformation",
+ value: {
+ width: 800,
+ height: 600,
+ mode: "cm-extract",
+ focus: "coordinates",
+ coordinateMethod: "topleft",
+ x: "100",
+ },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(true)
+ })
+ })
+
+ describe("Typography and Text Layer Advanced", () => {
+ it("should validate text with typography array", () => {
+ const template: Omit = {
+ key: "layers-text",
+ name: "Text",
+ type: "transformation",
+ value: {
+ text: "Hello",
+ fontSize: 24,
+ typography: ["bold", "italic"],
+ radius: 0,
+ },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(true)
+ })
+
+ it("should validate text with flip array", () => {
+ const template: Omit = {
+ key: "layers-text",
+ name: "Text",
+ type: "transformation",
+ value: {
+ text: "Hello",
+ fontSize: 24,
+ flip: ["horizontal"],
+ radius: 0,
+ },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(true)
+ })
+
+ it("should validate text with max radius", () => {
+ const template: Omit = {
+ key: "layers-text",
+ name: "Text",
+ type: "transformation",
+ value: {
+ text: "Hello",
+ fontSize: 24,
+ radius: "max",
+ },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(true)
+ })
+
+ it("should validate text layer with all alignment options", () => {
+ const alignments: Array<"left" | "right" | "center"> = ["left", "right", "center"]
+ alignments.forEach((align) => {
+ const template: Omit = {
+ key: "layers-text",
+ name: "Text",
+ type: "transformation",
+ value: {
+ text: "Hello",
+ fontSize: 24,
+ innerAlignment: align,
+ radius: 0,
+ },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(true)
+ })
+ })
+
+ it("should validate text opacity boundary values", () => {
+ const template: Omit = {
+ key: "layers-text",
+ name: "Text",
+ type: "transformation",
+ value: {
+ text: "Hello",
+ fontSize: 24,
+ opacity: 1,
+ radius: 0,
+ },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(true)
+ })
+ })
+
+ describe("Image Layer Complex Validations", () => {
+ it("should validate image layer with border using expression", () => {
+ const template: Omit = {
+ key: "layers-image",
+ name: "Image Layer",
+ type: "transformation",
+ value: {
+ imageUrl: "overlay.png",
+ borderWidth: "bw_div_10",
+ borderColor: "FF0000",
+ },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(true)
+ })
+
+ it("should reject image layer border with invalid expression", () => {
+ const template: Omit = {
+ key: "layers-image",
+ name: "Image Layer",
+ type: "transformation",
+ value: {
+ imageUrl: "overlay.png",
+ borderWidth: "invalid_expr",
+ borderColor: "FF0000",
+ },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(false)
+ })
+
+ it("should validate image layer with unsharpen mask enabled", () => {
+ const template: Omit = {
+ key: "layers-image",
+ name: "Image Layer",
+ type: "transformation",
+ value: {
+ imageUrl: "overlay.png",
+ unsharpenMask: true,
+ unsharpenMaskRadius: 2,
+ unsharpenMaskSigma: 1,
+ unsharpenMaskAmount: 1.5,
+ unsharpenMaskThreshold: 0.05,
+ },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(true)
+ })
+
+ it("should require all unsharpen mask fields when enabled for image layer", () => {
+ const template: Omit = {
+ key: "layers-image",
+ name: "Image Layer",
+ type: "transformation",
+ value: {
+ imageUrl: "overlay.png",
+ unsharpenMask: true,
+ unsharpenMaskRadius: 2,
+ },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(false)
+ })
+
+ it("should validate image layer with DPR", () => {
+ const template: Omit = {
+ key: "layers-image",
+ name: "Image Layer",
+ type: "transformation",
+ value: {
+ imageUrl: "overlay.png",
+ width: 200,
+ dprEnabled: true,
+ dpr: 2,
+ },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(true)
+ })
+
+ it("should reject image layer DPR without dimensions", () => {
+ const template: Omit = {
+ key: "layers-image",
+ name: "Image Layer",
+ type: "transformation",
+ value: {
+ imageUrl: "overlay.png",
+ dpr: 2,
+ },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(false)
+ })
+
+ it("should validate image layer with focus object", () => {
+ const template: Omit = {
+ key: "layers-image",
+ name: "Image Layer",
+ type: "transformation",
+ value: {
+ imageUrl: "overlay.png",
+ crop: "cm-extract",
+ focus: "object",
+ focusObject: "person",
+ },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(true)
+ })
+
+ it("should require focusObject when image layer has object focus", () => {
+ const template: Omit = {
+ key: "layers-image",
+ name: "Image Layer",
+ type: "transformation",
+ value: {
+ imageUrl: "overlay.png",
+ crop: "cm-extract",
+ focus: "object",
+ },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(false)
+ })
+
+ it("should validate image layer with focus anchor", () => {
+ const template: Omit = {
+ key: "layers-image",
+ name: "Image Layer",
+ type: "transformation",
+ value: {
+ imageUrl: "overlay.png",
+ crop: "cm-extract",
+ focus: "anchor",
+ focusAnchor: "center",
+ },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(true)
+ })
+
+ it("should require focusAnchor when image layer has anchor focus", () => {
+ const template: Omit = {
+ key: "layers-image",
+ name: "Image Layer",
+ type: "transformation",
+ value: {
+ imageUrl: "overlay.png",
+ crop: "cm-extract",
+ focus: "anchor",
+ },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(false)
+ })
+
+ it("should validate image layer with topleft coordinates", () => {
+ const template: Omit = {
+ key: "layers-image",
+ name: "Image Layer",
+ type: "transformation",
+ value: {
+ imageUrl: "overlay.png",
+ crop: "cm-extract",
+ focus: "coordinates",
+ coordinateMethod: "topleft",
+ x: "50",
+ y: "50",
+ },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(true)
+ })
+
+ it("should require at least one topleft coordinate for image layer", () => {
+ const template: Omit = {
+ key: "layers-image",
+ name: "Image Layer",
+ type: "transformation",
+ value: {
+ imageUrl: "overlay.png",
+ crop: "cm-extract",
+ focus: "coordinates",
+ coordinateMethod: "topleft",
+ },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(false)
+ })
+
+ it("should validate image layer with center coordinates", () => {
+ const template: Omit = {
+ key: "layers-image",
+ name: "Image Layer",
+ type: "transformation",
+ value: {
+ imageUrl: "overlay.png",
+ crop: "cm-extract",
+ focus: "coordinates",
+ coordinateMethod: "center",
+ xc: "100",
+ yc: "100",
+ },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(true)
+ })
+
+ it("should require at least one center coordinate for image layer", () => {
+ const template: Omit = {
+ key: "layers-image",
+ name: "Image Layer",
+ type: "transformation",
+ value: {
+ imageUrl: "overlay.png",
+ crop: "cm-extract",
+ focus: "coordinates",
+ coordinateMethod: "center",
+ },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(false)
+ })
+
+ it("should validate image layer with distort perspective", () => {
+ const template: Omit = {
+ key: "layers-image",
+ name: "Image Layer",
+ type: "transformation",
+ value: {
+ imageUrl: "overlay.png",
+ distort: true,
+ distortType: "perspective",
+ distortPerspective: {
+ x1: "10",
+ y1: "10",
+ x2: "100",
+ y2: "10",
+ x3: "100",
+ y3: "100",
+ x4: "10",
+ y4: "100",
+ },
+ },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(true)
+ })
+
+ it("should validate image layer with arc distortion", () => {
+ const template: Omit = {
+ key: "layers-image",
+ name: "Image Layer",
+ type: "transformation",
+ value: {
+ imageUrl: "overlay.png",
+ distort: true,
+ distortType: "arc",
+ distortArcDegree: "30",
+ },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(true)
+ })
+
+ it("should validate image layer with all overlay effects", () => {
+ const template: Omit = {
+ key: "layers-image",
+ name: "Image Layer",
+ type: "transformation",
+ value: {
+ imageUrl: "overlay.png",
+ width: 200,
+ height: 200,
+ blur: 5,
+ shadow: true,
+ shadowBlur: 10,
+ shadowOffsetX: 5,
+ shadowOffsetY: 5,
+ grayscale: true,
+ opacity: 80,
+ },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(true)
+ })
+ })
+
+ describe("Common Number and Expression Validator Coverage", () => {
+ it("should validate positive number", () => {
+ const template: Omit = {
+ key: "layers-image",
+ name: "Image Layer",
+ type: "transformation",
+ value: {
+ imageUrl: "overlay.png",
+ borderWidth: 10,
+ },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(true)
+ })
+
+ it("should validate ih expression", () => {
+ const template: Omit = {
+ key: "layers-image",
+ name: "Image Layer",
+ type: "transformation",
+ value: {
+ imageUrl: "overlay.png",
+ borderWidth: "ih_div_20",
+ },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(true)
+ })
+
+ it("should validate bh expression", () => {
+ const template: Omit = {
+ key: "layers-image",
+ name: "Image Layer",
+ type: "transformation",
+ value: {
+ imageUrl: "overlay.png",
+ borderWidth: "bh_mul_0.05",
+ },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(true)
+ })
+
+ it("should validate ch expression", () => {
+ const template: Omit = {
+ key: "layers-image",
+ name: "Image Layer",
+ type: "transformation",
+ value: {
+ imageUrl: "overlay.png",
+ borderWidth: "ch_add_10",
+ },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(true)
+ })
+
+ it("should reject negative number for common validator", () => {
+ const template: Omit = {
+ key: "layers-image",
+ name: "Image Layer",
+ type: "transformation",
+ value: {
+ imageUrl: "overlay.png",
+ borderWidth: -5,
+ },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(false)
+ })
+
+ it("should reject invalid expression format", () => {
+ const template: Omit = {
+ key: "layers-image",
+ name: "Image Layer",
+ type: "transformation",
+ value: {
+ imageUrl: "overlay.png",
+ borderWidth: "invalid_format",
+ },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(false)
+ })
+ })
+})
diff --git a/packages/imagekit-editor-dev/src/index.tsx b/packages/imagekit-editor-dev/src/index.tsx
index 5d74dfd..18ce1c9 100644
--- a/packages/imagekit-editor-dev/src/index.tsx
+++ b/packages/imagekit-editor-dev/src/index.tsx
@@ -1,4 +1,5 @@
export type { ImageKitEditorProps, ImageKitEditorRef } from "./ImageKitEditor"
export { ImageKitEditor } from "./ImageKitEditor"
export { DEFAULT_FOCUS_OBJECTS } from "./schema"
-export type { FileElement, Signer } from "./store"
+export type { FileElement, Signer, Transformation } from "./store"
+export { TRANSFORMATION_STATE_VERSION } from "./store"
diff --git a/packages/imagekit-editor-dev/src/store.ts b/packages/imagekit-editor-dev/src/store.ts
index b0cdd83..e2c1628 100644
--- a/packages/imagekit-editor-dev/src/store.ts
+++ b/packages/imagekit-editor-dev/src/store.ts
@@ -15,12 +15,15 @@ import {
} from "./schema"
import { extractImagePath } from "./utils"
+export const TRANSFORMATION_STATE_VERSION = "v1" as const
+
export interface Transformation {
id: string
key: string
name: string
type: "transformation"
value: IKTransformation
+ version?: typeof TRANSFORMATION_STATE_VERSION
}
export type RequiredMetadata = { requireSignedUrl: boolean }
@@ -104,7 +107,7 @@ export type EditorActions<
addImage: (imageSrc: string | InputFileElement) => void
addImages: (imageSrcs: Array>) => void
removeImage: (imageSrc: string) => void
- setTransformations: (transformations: Omit[]) => void
+ loadTemplate: (template: Omit[]) => void
moveTransformation: (
activeId: UniqueIdentifier,
overId: UniqueIdentifier,
@@ -302,12 +305,30 @@ const useEditorStore = create()(
})
},
- setTransformations: (transformations) => {
- const transformationsWithIds = transformations.map((transformation) => ({
+ loadTemplate: (template) => {
+ const transformationsWithIds = template.map((transformation, index) => ({
...transformation,
- id: `transformation-${Date.now()}`,
+ id: `transformation-${Date.now()}-${index}`,
+ version: TRANSFORMATION_STATE_VERSION,
+ }))
+
+ const visibleTransformations: Record = {}
+ transformationsWithIds.forEach((t) => {
+ visibleTransformations[t.id] = true
+ })
+
+ set((state) => ({
+ transformations: transformationsWithIds,
+ visibleTransformations: {
+ ...state.visibleTransformations,
+ ...visibleTransformations,
+ },
+ _internalState: {
+ sidebarState: "none",
+ selectedTransformationKey: null,
+ transformationToEdit: null,
+ },
}))
- set({ transformations: transformationsWithIds })
},
moveTransformation: (activeId, overId) => {
diff --git a/packages/imagekit-editor-dev/vite.config.ts b/packages/imagekit-editor-dev/vite.config.ts
index 0a8cdda..15d7a68 100644
--- a/packages/imagekit-editor-dev/vite.config.ts
+++ b/packages/imagekit-editor-dev/vite.config.ts
@@ -16,6 +16,22 @@ export default defineConfig({
outDir: "../imagekit-editor/dist/types",
}),
],
+ test: {
+ globals: true,
+ environment: "jsdom",
+ setupFiles: [],
+ include: ["src/**/*.{test,spec}.{ts,tsx}"],
+ coverage: {
+ provider: "v8",
+ reporter: ["text", "json", "html"],
+ include: ["src/**/*.{ts,tsx}"],
+ exclude: [
+ "src/**/*.{test,spec}.{ts,tsx}",
+ "src/index.tsx",
+ "node_modules/**",
+ ],
+ },
+ },
build: {
lib: {
entry: path.resolve(__dirname, "src/index.tsx"),
diff --git a/packages/imagekit-editor/package.json b/packages/imagekit-editor/package.json
index 230658f..84f66d6 100644
--- a/packages/imagekit-editor/package.json
+++ b/packages/imagekit-editor/package.json
@@ -1,6 +1,6 @@
{
"name": "@imagekit/editor",
- "version": "2.1.0",
+ "version": "2.2.0",
"description": "Image Editor powered by ImageKit",
"main": "dist/index.cjs.js",
"module": "dist/index.es.js",
@@ -18,6 +18,9 @@
"url": "https://github.com/imagekit-developer/imagekit-editor/issues"
},
"homepage": "https://imagekit.io",
+ "scripts": {
+ "test": "echo \"No tests in this package\""
+ },
"peerDependencies": {
"@chakra-ui/icons": "1.1.1",
"@chakra-ui/react": "~1.8.9",
diff --git a/turbo.json b/turbo.json
index d2abe72..2895ca5 100644
--- a/turbo.json
+++ b/turbo.json
@@ -13,6 +13,10 @@
"cache": false,
"persistent": true,
"dependsOn": []
+ },
+ "test": {
+ "cache": false,
+ "outputs": []
}
}
}
diff --git a/yarn.lock b/yarn.lock
index 5b64324..ee5faf0 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -5,7 +5,14 @@ __metadata:
version: 8
cacheKey: 10c0
-"@ampproject/remapping@npm:^2.2.0":
+"@acemir/cssom@npm:^0.9.31":
+ version: 0.9.31
+ resolution: "@acemir/cssom@npm:0.9.31"
+ checksum: 10c0/cbfff98812642104ec3b37de1ad3a53f216ddc437e7b9276a23f46f2453844ea3c3f46c200bc4656a2f747fb26567560b3cc5183d549d119a758926551b5f566
+ languageName: node
+ linkType: hard
+
+"@ampproject/remapping@npm:^2.2.0, @ampproject/remapping@npm:^2.3.0":
version: 2.3.0
resolution: "@ampproject/remapping@npm:2.3.0"
dependencies:
@@ -15,6 +22,39 @@ __metadata:
languageName: node
linkType: hard
+"@asamuzakjp/css-color@npm:^5.0.1":
+ version: 5.0.1
+ resolution: "@asamuzakjp/css-color@npm:5.0.1"
+ dependencies:
+ "@csstools/css-calc": "npm:^3.1.1"
+ "@csstools/css-color-parser": "npm:^4.0.2"
+ "@csstools/css-parser-algorithms": "npm:^4.0.0"
+ "@csstools/css-tokenizer": "npm:^4.0.0"
+ lru-cache: "npm:^11.2.6"
+ checksum: 10c0/3e8d74a3b7f3005a325cb8e7f3da1aa32aeac4cd9ce387826dc25b16eaab4dc0e4a6faded8ccc1895959141f4a4a70e8bc38723347b89667b7b224990d16683c
+ languageName: node
+ linkType: hard
+
+"@asamuzakjp/dom-selector@npm:^6.8.1":
+ version: 6.8.1
+ resolution: "@asamuzakjp/dom-selector@npm:6.8.1"
+ dependencies:
+ "@asamuzakjp/nwsapi": "npm:^2.3.9"
+ bidi-js: "npm:^1.0.3"
+ css-tree: "npm:^3.1.0"
+ is-potential-custom-element-name: "npm:^1.0.1"
+ lru-cache: "npm:^11.2.6"
+ checksum: 10c0/635de2c3b11971c07e2d491fd2833d2499bafbab05b616f5d38041031718879c404456644f60c45e9ba4ca2423e5bb48bf3c46179b0c58a0ea68eaae8c61e85f
+ languageName: node
+ linkType: hard
+
+"@asamuzakjp/nwsapi@npm:^2.3.9":
+ version: 2.3.9
+ resolution: "@asamuzakjp/nwsapi@npm:2.3.9"
+ checksum: 10c0/869b81382e775499c96c45c6dbe0d0766a6da04bcf0abb79f5333535c4e19946851acaa43398f896e2ecc5a1de9cf3db7cf8c4b1afac1ee3d15e21584546d74d
+ languageName: node
+ linkType: hard
+
"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.27.1":
version: 7.27.1
resolution: "@babel/code-frame@npm:7.27.1"
@@ -140,6 +180,13 @@ __metadata:
languageName: node
linkType: hard
+"@babel/helper-validator-identifier@npm:^7.28.5":
+ version: 7.28.5
+ resolution: "@babel/helper-validator-identifier@npm:7.28.5"
+ checksum: 10c0/42aaebed91f739a41f3d80b72752d1f95fd7c72394e8e4bd7cdd88817e0774d80a432451bcba17c2c642c257c483bf1d409dd4548883429ea9493a3bc4ab0847
+ languageName: node
+ linkType: hard
+
"@babel/helper-validator-option@npm:^7.27.1":
version: 7.27.1
resolution: "@babel/helper-validator-option@npm:7.27.1"
@@ -168,6 +215,17 @@ __metadata:
languageName: node
linkType: hard
+"@babel/parser@npm:^7.25.4, @babel/parser@npm:^7.29.0":
+ version: 7.29.0
+ resolution: "@babel/parser@npm:7.29.0"
+ dependencies:
+ "@babel/types": "npm:^7.29.0"
+ bin:
+ parser: ./bin/babel-parser.js
+ checksum: 10c0/333b2aa761264b91577a74bee86141ef733f9f9f6d4fc52548e4847dc35dfbf821f58c46832c637bfa761a6d9909d6a68f7d1ed59e17e4ffbb958dc510c17b62
+ languageName: node
+ linkType: hard
+
"@babel/parser@npm:^7.27.2, @babel/parser@npm:^7.27.4, @babel/parser@npm:^7.27.5":
version: 7.27.5
resolution: "@babel/parser@npm:7.27.5"
@@ -252,6 +310,16 @@ __metadata:
languageName: node
linkType: hard
+"@babel/types@npm:^7.25.4, @babel/types@npm:^7.29.0":
+ version: 7.29.0
+ resolution: "@babel/types@npm:7.29.0"
+ dependencies:
+ "@babel/helper-string-parser": "npm:^7.27.1"
+ "@babel/helper-validator-identifier": "npm:^7.28.5"
+ checksum: 10c0/23cc3466e83bcbfab8b9bd0edaafdb5d4efdb88b82b3be6728bbade5ba2f0996f84f63b1c5f7a8c0d67efded28300898a5f930b171bb40b311bca2029c4e9b4f
+ languageName: node
+ linkType: hard
+
"@babel/types@npm:^7.27.1, @babel/types@npm:^7.27.3, @babel/types@npm:^7.27.6":
version: 7.27.6
resolution: "@babel/types@npm:7.27.6"
@@ -262,6 +330,20 @@ __metadata:
languageName: node
linkType: hard
+"@bcoe/v8-coverage@npm:^0.2.3":
+ version: 0.2.3
+ resolution: "@bcoe/v8-coverage@npm:0.2.3"
+ checksum: 10c0/6b80ae4cb3db53f486da2dc63b6e190a74c8c3cca16bb2733f234a0b6a9382b09b146488ae08e2b22cf00f6c83e20f3e040a2f7894f05c045c946d6a090b1d52
+ languageName: node
+ linkType: hard
+
+"@bcoe/v8-coverage@npm:^1.0.2":
+ version: 1.0.2
+ resolution: "@bcoe/v8-coverage@npm:1.0.2"
+ checksum: 10c0/1eb1dc93cc17fb7abdcef21a6e7b867d6aa99a7ec88ec8207402b23d9083ab22a8011213f04b2cf26d535f1d22dc26139b7929e6c2134c254bd1e14ba5e678c3
+ languageName: node
+ linkType: hard
+
"@biomejs/biome@npm:2.1.1":
version: 2.1.1
resolution: "@biomejs/biome@npm:2.1.1"
@@ -353,6 +435,17 @@ __metadata:
languageName: node
linkType: hard
+"@bramus/specificity@npm:^2.4.2":
+ version: 2.4.2
+ resolution: "@bramus/specificity@npm:2.4.2"
+ dependencies:
+ css-tree: "npm:^3.0.0"
+ bin:
+ specificity: bin/cli.js
+ checksum: 10c0/c5f4e04e0bca0d2202598207a5eb0733c8109d12a68a329caa26373bec598d99db5bb785b8865fefa00fc01b08c6068138807ceb11a948fe15e904ed6cf4ba72
+ languageName: node
+ linkType: hard
+
"@chakra-ui/accordion@npm:1.4.12":
version: 1.4.12
resolution: "@chakra-ui/accordion@npm:1.4.12"
@@ -1221,6 +1314,59 @@ __metadata:
languageName: node
linkType: hard
+"@csstools/color-helpers@npm:^6.0.2":
+ version: 6.0.2
+ resolution: "@csstools/color-helpers@npm:6.0.2"
+ checksum: 10c0/4c66574563d7c960010c11e41c2673675baff07c427cca6e8dddffa5777de45770d13ff3efce1c0642798089ad55de52870d9d8141f78db3fa5bba012f2d3789
+ languageName: node
+ linkType: hard
+
+"@csstools/css-calc@npm:^3.1.1":
+ version: 3.1.1
+ resolution: "@csstools/css-calc@npm:3.1.1"
+ peerDependencies:
+ "@csstools/css-parser-algorithms": ^4.0.0
+ "@csstools/css-tokenizer": ^4.0.0
+ checksum: 10c0/6efcc016d988edf66e54c7bad03e352d61752cbd1b56c7557fd013868aab23505052ded8f912cd4034e216943ea1e04c957d81012489e3eddc14a57b386510ef
+ languageName: node
+ linkType: hard
+
+"@csstools/css-color-parser@npm:^4.0.2":
+ version: 4.0.2
+ resolution: "@csstools/css-color-parser@npm:4.0.2"
+ dependencies:
+ "@csstools/color-helpers": "npm:^6.0.2"
+ "@csstools/css-calc": "npm:^3.1.1"
+ peerDependencies:
+ "@csstools/css-parser-algorithms": ^4.0.0
+ "@csstools/css-tokenizer": ^4.0.0
+ checksum: 10c0/487cf507ef4630f74bd67d84298294ed269900b206ade015a968d20047e07ff46f235b72e26fe0c6b949a03f8f9f00a22c363da49c1b06ca60b32d0188e546be
+ languageName: node
+ linkType: hard
+
+"@csstools/css-parser-algorithms@npm:^4.0.0":
+ version: 4.0.0
+ resolution: "@csstools/css-parser-algorithms@npm:4.0.0"
+ peerDependencies:
+ "@csstools/css-tokenizer": ^4.0.0
+ checksum: 10c0/94558c2428d6ef0ddef542e86e0a8376aa1263a12a59770abb13ba50d7b83086822c75433f32aa2e7fef00555e1cc88292f9ca5bce79aed232bb3fed73b1528d
+ languageName: node
+ linkType: hard
+
+"@csstools/css-syntax-patches-for-csstree@npm:^1.0.28":
+ version: 1.1.0
+ resolution: "@csstools/css-syntax-patches-for-csstree@npm:1.1.0"
+ checksum: 10c0/ef84e09ead31d204e238eb674016b34a54083344348b4e4fd63cb03486dcaa5b53feeff74a6c246763973cca0eb3213a70f49ca8545ce26a3b3d9c97255f4dd1
+ languageName: node
+ linkType: hard
+
+"@csstools/css-tokenizer@npm:^4.0.0":
+ version: 4.0.0
+ resolution: "@csstools/css-tokenizer@npm:4.0.0"
+ checksum: 10c0/669cf3d0f9c8e1ffdf8c9955ad8beba0c8cfe03197fe29a4fcbd9ee6f7a18856cfa42c62670021a75183d9ab37f5d14a866e6a9df753a6c07f59e36797a9ea9f
+ languageName: node
+ linkType: hard
+
"@ctrl/tinycolor@npm:^3.4.0":
version: 3.6.1
resolution: "@ctrl/tinycolor@npm:3.6.1"
@@ -1439,6 +1585,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/aix-ppc64@npm:0.21.5":
+ version: 0.21.5
+ resolution: "@esbuild/aix-ppc64@npm:0.21.5"
+ conditions: os=aix & cpu=ppc64
+ languageName: node
+ linkType: hard
+
"@esbuild/aix-ppc64@npm:0.25.5":
version: 0.25.5
resolution: "@esbuild/aix-ppc64@npm:0.25.5"
@@ -1446,6 +1599,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/android-arm64@npm:0.21.5":
+ version: 0.21.5
+ resolution: "@esbuild/android-arm64@npm:0.21.5"
+ conditions: os=android & cpu=arm64
+ languageName: node
+ linkType: hard
+
"@esbuild/android-arm64@npm:0.25.5":
version: 0.25.5
resolution: "@esbuild/android-arm64@npm:0.25.5"
@@ -1453,6 +1613,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/android-arm@npm:0.21.5":
+ version: 0.21.5
+ resolution: "@esbuild/android-arm@npm:0.21.5"
+ conditions: os=android & cpu=arm
+ languageName: node
+ linkType: hard
+
"@esbuild/android-arm@npm:0.25.5":
version: 0.25.5
resolution: "@esbuild/android-arm@npm:0.25.5"
@@ -1460,6 +1627,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/android-x64@npm:0.21.5":
+ version: 0.21.5
+ resolution: "@esbuild/android-x64@npm:0.21.5"
+ conditions: os=android & cpu=x64
+ languageName: node
+ linkType: hard
+
"@esbuild/android-x64@npm:0.25.5":
version: 0.25.5
resolution: "@esbuild/android-x64@npm:0.25.5"
@@ -1467,6 +1641,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/darwin-arm64@npm:0.21.5":
+ version: 0.21.5
+ resolution: "@esbuild/darwin-arm64@npm:0.21.5"
+ conditions: os=darwin & cpu=arm64
+ languageName: node
+ linkType: hard
+
"@esbuild/darwin-arm64@npm:0.25.5":
version: 0.25.5
resolution: "@esbuild/darwin-arm64@npm:0.25.5"
@@ -1474,6 +1655,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/darwin-x64@npm:0.21.5":
+ version: 0.21.5
+ resolution: "@esbuild/darwin-x64@npm:0.21.5"
+ conditions: os=darwin & cpu=x64
+ languageName: node
+ linkType: hard
+
"@esbuild/darwin-x64@npm:0.25.5":
version: 0.25.5
resolution: "@esbuild/darwin-x64@npm:0.25.5"
@@ -1481,6 +1669,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/freebsd-arm64@npm:0.21.5":
+ version: 0.21.5
+ resolution: "@esbuild/freebsd-arm64@npm:0.21.5"
+ conditions: os=freebsd & cpu=arm64
+ languageName: node
+ linkType: hard
+
"@esbuild/freebsd-arm64@npm:0.25.5":
version: 0.25.5
resolution: "@esbuild/freebsd-arm64@npm:0.25.5"
@@ -1488,6 +1683,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/freebsd-x64@npm:0.21.5":
+ version: 0.21.5
+ resolution: "@esbuild/freebsd-x64@npm:0.21.5"
+ conditions: os=freebsd & cpu=x64
+ languageName: node
+ linkType: hard
+
"@esbuild/freebsd-x64@npm:0.25.5":
version: 0.25.5
resolution: "@esbuild/freebsd-x64@npm:0.25.5"
@@ -1495,6 +1697,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/linux-arm64@npm:0.21.5":
+ version: 0.21.5
+ resolution: "@esbuild/linux-arm64@npm:0.21.5"
+ conditions: os=linux & cpu=arm64
+ languageName: node
+ linkType: hard
+
"@esbuild/linux-arm64@npm:0.25.5":
version: 0.25.5
resolution: "@esbuild/linux-arm64@npm:0.25.5"
@@ -1502,6 +1711,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/linux-arm@npm:0.21.5":
+ version: 0.21.5
+ resolution: "@esbuild/linux-arm@npm:0.21.5"
+ conditions: os=linux & cpu=arm
+ languageName: node
+ linkType: hard
+
"@esbuild/linux-arm@npm:0.25.5":
version: 0.25.5
resolution: "@esbuild/linux-arm@npm:0.25.5"
@@ -1509,6 +1725,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/linux-ia32@npm:0.21.5":
+ version: 0.21.5
+ resolution: "@esbuild/linux-ia32@npm:0.21.5"
+ conditions: os=linux & cpu=ia32
+ languageName: node
+ linkType: hard
+
"@esbuild/linux-ia32@npm:0.25.5":
version: 0.25.5
resolution: "@esbuild/linux-ia32@npm:0.25.5"
@@ -1516,6 +1739,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/linux-loong64@npm:0.21.5":
+ version: 0.21.5
+ resolution: "@esbuild/linux-loong64@npm:0.21.5"
+ conditions: os=linux & cpu=loong64
+ languageName: node
+ linkType: hard
+
"@esbuild/linux-loong64@npm:0.25.5":
version: 0.25.5
resolution: "@esbuild/linux-loong64@npm:0.25.5"
@@ -1523,6 +1753,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/linux-mips64el@npm:0.21.5":
+ version: 0.21.5
+ resolution: "@esbuild/linux-mips64el@npm:0.21.5"
+ conditions: os=linux & cpu=mips64el
+ languageName: node
+ linkType: hard
+
"@esbuild/linux-mips64el@npm:0.25.5":
version: 0.25.5
resolution: "@esbuild/linux-mips64el@npm:0.25.5"
@@ -1530,6 +1767,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/linux-ppc64@npm:0.21.5":
+ version: 0.21.5
+ resolution: "@esbuild/linux-ppc64@npm:0.21.5"
+ conditions: os=linux & cpu=ppc64
+ languageName: node
+ linkType: hard
+
"@esbuild/linux-ppc64@npm:0.25.5":
version: 0.25.5
resolution: "@esbuild/linux-ppc64@npm:0.25.5"
@@ -1537,6 +1781,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/linux-riscv64@npm:0.21.5":
+ version: 0.21.5
+ resolution: "@esbuild/linux-riscv64@npm:0.21.5"
+ conditions: os=linux & cpu=riscv64
+ languageName: node
+ linkType: hard
+
"@esbuild/linux-riscv64@npm:0.25.5":
version: 0.25.5
resolution: "@esbuild/linux-riscv64@npm:0.25.5"
@@ -1544,6 +1795,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/linux-s390x@npm:0.21.5":
+ version: 0.21.5
+ resolution: "@esbuild/linux-s390x@npm:0.21.5"
+ conditions: os=linux & cpu=s390x
+ languageName: node
+ linkType: hard
+
"@esbuild/linux-s390x@npm:0.25.5":
version: 0.25.5
resolution: "@esbuild/linux-s390x@npm:0.25.5"
@@ -1551,6 +1809,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/linux-x64@npm:0.21.5":
+ version: 0.21.5
+ resolution: "@esbuild/linux-x64@npm:0.21.5"
+ conditions: os=linux & cpu=x64
+ languageName: node
+ linkType: hard
+
"@esbuild/linux-x64@npm:0.25.5":
version: 0.25.5
resolution: "@esbuild/linux-x64@npm:0.25.5"
@@ -1565,6 +1830,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/netbsd-x64@npm:0.21.5":
+ version: 0.21.5
+ resolution: "@esbuild/netbsd-x64@npm:0.21.5"
+ conditions: os=netbsd & cpu=x64
+ languageName: node
+ linkType: hard
+
"@esbuild/netbsd-x64@npm:0.25.5":
version: 0.25.5
resolution: "@esbuild/netbsd-x64@npm:0.25.5"
@@ -1579,6 +1851,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/openbsd-x64@npm:0.21.5":
+ version: 0.21.5
+ resolution: "@esbuild/openbsd-x64@npm:0.21.5"
+ conditions: os=openbsd & cpu=x64
+ languageName: node
+ linkType: hard
+
"@esbuild/openbsd-x64@npm:0.25.5":
version: 0.25.5
resolution: "@esbuild/openbsd-x64@npm:0.25.5"
@@ -1586,6 +1865,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/sunos-x64@npm:0.21.5":
+ version: 0.21.5
+ resolution: "@esbuild/sunos-x64@npm:0.21.5"
+ conditions: os=sunos & cpu=x64
+ languageName: node
+ linkType: hard
+
"@esbuild/sunos-x64@npm:0.25.5":
version: 0.25.5
resolution: "@esbuild/sunos-x64@npm:0.25.5"
@@ -1593,6 +1879,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/win32-arm64@npm:0.21.5":
+ version: 0.21.5
+ resolution: "@esbuild/win32-arm64@npm:0.21.5"
+ conditions: os=win32 & cpu=arm64
+ languageName: node
+ linkType: hard
+
"@esbuild/win32-arm64@npm:0.25.5":
version: 0.25.5
resolution: "@esbuild/win32-arm64@npm:0.25.5"
@@ -1600,6 +1893,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/win32-ia32@npm:0.21.5":
+ version: 0.21.5
+ resolution: "@esbuild/win32-ia32@npm:0.21.5"
+ conditions: os=win32 & cpu=ia32
+ languageName: node
+ linkType: hard
+
"@esbuild/win32-ia32@npm:0.25.5":
version: 0.25.5
resolution: "@esbuild/win32-ia32@npm:0.25.5"
@@ -1607,6 +1907,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/win32-x64@npm:0.21.5":
+ version: 0.21.5
+ resolution: "@esbuild/win32-x64@npm:0.21.5"
+ conditions: os=win32 & cpu=x64
+ languageName: node
+ linkType: hard
+
"@esbuild/win32-x64@npm:0.25.5":
version: 0.25.5
resolution: "@esbuild/win32-x64@npm:0.25.5"
@@ -1614,6 +1921,18 @@ __metadata:
languageName: node
linkType: hard
+"@exodus/bytes@npm:^1.11.0, @exodus/bytes@npm:^1.6.0":
+ version: 1.15.0
+ resolution: "@exodus/bytes@npm:1.15.0"
+ peerDependencies:
+ "@noble/hashes": ^1.8.0 || ^2.0.0
+ peerDependenciesMeta:
+ "@noble/hashes":
+ optional: true
+ checksum: 10c0/b48aad9729653385d6ed055c28cfcf0b1b1481cf5d83f4375c12abd7988f1d20f69c80b5f95d4a1cc24d9abe32b9efc352a812d53884c26efea172aca8b6356d
+ languageName: node
+ linkType: hard
+
"@floating-ui/core@npm:^1.7.3":
version: 1.7.3
resolution: "@floating-ui/core@npm:1.7.3"
@@ -1651,7 +1970,7 @@ __metadata:
languageName: node
linkType: hard
-"@imagekit/editor@npm:2.1.0, @imagekit/editor@workspace:packages/imagekit-editor":
+"@imagekit/editor@workspace:*, @imagekit/editor@workspace:packages/imagekit-editor":
version: 0.0.0-use.local
resolution: "@imagekit/editor@workspace:packages/imagekit-editor"
peerDependencies:
@@ -1696,6 +2015,13 @@ __metadata:
languageName: node
linkType: hard
+"@istanbuljs/schema@npm:^0.1.2":
+ version: 0.1.3
+ resolution: "@istanbuljs/schema@npm:0.1.3"
+ checksum: 10c0/61c5286771676c9ca3eb2bd8a7310a9c063fb6e0e9712225c8471c582d157392c88f5353581c8c9adbe0dff98892317d2fdfc56c3499aa42e0194405206a963a
+ languageName: node
+ linkType: hard
+
"@jridgewell/gen-mapping@npm:^0.3.5":
version: 0.3.5
resolution: "@jridgewell/gen-mapping@npm:0.3.5"
@@ -1738,6 +2064,23 @@ __metadata:
languageName: node
linkType: hard
+"@jridgewell/sourcemap-codec@npm:^1.5.5":
+ version: 1.5.5
+ resolution: "@jridgewell/sourcemap-codec@npm:1.5.5"
+ checksum: 10c0/f9e538f302b63c0ebc06eecb1dd9918dd4289ed36147a0ddce35d6ea4d7ebbda243cda7b2213b6a5e1d8087a298d5cf630fb2bd39329cdecb82017023f6081a0
+ languageName: node
+ linkType: hard
+
+"@jridgewell/trace-mapping@npm:^0.3.23, @jridgewell/trace-mapping@npm:^0.3.31":
+ version: 0.3.31
+ resolution: "@jridgewell/trace-mapping@npm:0.3.31"
+ dependencies:
+ "@jridgewell/resolve-uri": "npm:^3.1.0"
+ "@jridgewell/sourcemap-codec": "npm:^1.4.14"
+ checksum: 10c0/4b30ec8cd56c5fd9a661f088230af01e0c1a3888d11ffb6b47639700f71225be21d1f7e168048d6d4f9449207b978a235c07c8f15c07705685d16dc06280e9d9
+ languageName: node
+ linkType: hard
+
"@jridgewell/trace-mapping@npm:^0.3.24, @jridgewell/trace-mapping@npm:^0.3.25":
version: 0.3.25
resolution: "@jridgewell/trace-mapping@npm:0.3.25"
@@ -1921,6 +2264,13 @@ __metadata:
languageName: node
linkType: hard
+"@polka/url@npm:^1.0.0-next.24":
+ version: 1.0.0-next.29
+ resolution: "@polka/url@npm:1.0.0-next.29"
+ checksum: 10c0/0d58e081844095cb029d3c19a659bfefd09d5d51a2f791bc61eba7ea826f13d6ee204a8a448c2f5a855c17df07b37517373ff916dd05801063c0568ae9937684
+ languageName: node
+ linkType: hard
+
"@popperjs/core@npm:^2.9.3":
version: 2.11.8
resolution: "@popperjs/core@npm:2.11.8"
@@ -2009,6 +2359,13 @@ __metadata:
languageName: node
linkType: hard
+"@rollup/rollup-android-arm-eabi@npm:4.59.0":
+ version: 4.59.0
+ resolution: "@rollup/rollup-android-arm-eabi@npm:4.59.0"
+ conditions: os=android & cpu=arm
+ languageName: node
+ linkType: hard
+
"@rollup/rollup-android-arm64@npm:4.44.0":
version: 4.44.0
resolution: "@rollup/rollup-android-arm64@npm:4.44.0"
@@ -2016,6 +2373,13 @@ __metadata:
languageName: node
linkType: hard
+"@rollup/rollup-android-arm64@npm:4.59.0":
+ version: 4.59.0
+ resolution: "@rollup/rollup-android-arm64@npm:4.59.0"
+ conditions: os=android & cpu=arm64
+ languageName: node
+ linkType: hard
+
"@rollup/rollup-darwin-arm64@npm:4.44.0":
version: 4.44.0
resolution: "@rollup/rollup-darwin-arm64@npm:4.44.0"
@@ -2023,6 +2387,13 @@ __metadata:
languageName: node
linkType: hard
+"@rollup/rollup-darwin-arm64@npm:4.59.0":
+ version: 4.59.0
+ resolution: "@rollup/rollup-darwin-arm64@npm:4.59.0"
+ conditions: os=darwin & cpu=arm64
+ languageName: node
+ linkType: hard
+
"@rollup/rollup-darwin-x64@npm:4.44.0":
version: 4.44.0
resolution: "@rollup/rollup-darwin-x64@npm:4.44.0"
@@ -2030,6 +2401,13 @@ __metadata:
languageName: node
linkType: hard
+"@rollup/rollup-darwin-x64@npm:4.59.0":
+ version: 4.59.0
+ resolution: "@rollup/rollup-darwin-x64@npm:4.59.0"
+ conditions: os=darwin & cpu=x64
+ languageName: node
+ linkType: hard
+
"@rollup/rollup-freebsd-arm64@npm:4.44.0":
version: 4.44.0
resolution: "@rollup/rollup-freebsd-arm64@npm:4.44.0"
@@ -2037,6 +2415,13 @@ __metadata:
languageName: node
linkType: hard
+"@rollup/rollup-freebsd-arm64@npm:4.59.0":
+ version: 4.59.0
+ resolution: "@rollup/rollup-freebsd-arm64@npm:4.59.0"
+ conditions: os=freebsd & cpu=arm64
+ languageName: node
+ linkType: hard
+
"@rollup/rollup-freebsd-x64@npm:4.44.0":
version: 4.44.0
resolution: "@rollup/rollup-freebsd-x64@npm:4.44.0"
@@ -2044,6 +2429,13 @@ __metadata:
languageName: node
linkType: hard
+"@rollup/rollup-freebsd-x64@npm:4.59.0":
+ version: 4.59.0
+ resolution: "@rollup/rollup-freebsd-x64@npm:4.59.0"
+ conditions: os=freebsd & cpu=x64
+ languageName: node
+ linkType: hard
+
"@rollup/rollup-linux-arm-gnueabihf@npm:4.44.0":
version: 4.44.0
resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.44.0"
@@ -2051,6 +2443,13 @@ __metadata:
languageName: node
linkType: hard
+"@rollup/rollup-linux-arm-gnueabihf@npm:4.59.0":
+ version: 4.59.0
+ resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.59.0"
+ conditions: os=linux & cpu=arm & libc=glibc
+ languageName: node
+ linkType: hard
+
"@rollup/rollup-linux-arm-musleabihf@npm:4.44.0":
version: 4.44.0
resolution: "@rollup/rollup-linux-arm-musleabihf@npm:4.44.0"
@@ -2058,6 +2457,13 @@ __metadata:
languageName: node
linkType: hard
+"@rollup/rollup-linux-arm-musleabihf@npm:4.59.0":
+ version: 4.59.0
+ resolution: "@rollup/rollup-linux-arm-musleabihf@npm:4.59.0"
+ conditions: os=linux & cpu=arm & libc=musl
+ languageName: node
+ linkType: hard
+
"@rollup/rollup-linux-arm64-gnu@npm:4.44.0":
version: 4.44.0
resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.44.0"
@@ -2065,6 +2471,13 @@ __metadata:
languageName: node
linkType: hard
+"@rollup/rollup-linux-arm64-gnu@npm:4.59.0":
+ version: 4.59.0
+ resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.59.0"
+ conditions: os=linux & cpu=arm64 & libc=glibc
+ languageName: node
+ linkType: hard
+
"@rollup/rollup-linux-arm64-musl@npm:4.44.0":
version: 4.44.0
resolution: "@rollup/rollup-linux-arm64-musl@npm:4.44.0"
@@ -2072,6 +2485,27 @@ __metadata:
languageName: node
linkType: hard
+"@rollup/rollup-linux-arm64-musl@npm:4.59.0":
+ version: 4.59.0
+ resolution: "@rollup/rollup-linux-arm64-musl@npm:4.59.0"
+ conditions: os=linux & cpu=arm64 & libc=musl
+ languageName: node
+ linkType: hard
+
+"@rollup/rollup-linux-loong64-gnu@npm:4.59.0":
+ version: 4.59.0
+ resolution: "@rollup/rollup-linux-loong64-gnu@npm:4.59.0"
+ conditions: os=linux & cpu=loong64 & libc=glibc
+ languageName: node
+ linkType: hard
+
+"@rollup/rollup-linux-loong64-musl@npm:4.59.0":
+ version: 4.59.0
+ resolution: "@rollup/rollup-linux-loong64-musl@npm:4.59.0"
+ conditions: os=linux & cpu=loong64 & libc=musl
+ languageName: node
+ linkType: hard
+
"@rollup/rollup-linux-loongarch64-gnu@npm:4.44.0":
version: 4.44.0
resolution: "@rollup/rollup-linux-loongarch64-gnu@npm:4.44.0"
@@ -2086,6 +2520,20 @@ __metadata:
languageName: node
linkType: hard
+"@rollup/rollup-linux-ppc64-gnu@npm:4.59.0":
+ version: 4.59.0
+ resolution: "@rollup/rollup-linux-ppc64-gnu@npm:4.59.0"
+ conditions: os=linux & cpu=ppc64 & libc=glibc
+ languageName: node
+ linkType: hard
+
+"@rollup/rollup-linux-ppc64-musl@npm:4.59.0":
+ version: 4.59.0
+ resolution: "@rollup/rollup-linux-ppc64-musl@npm:4.59.0"
+ conditions: os=linux & cpu=ppc64 & libc=musl
+ languageName: node
+ linkType: hard
+
"@rollup/rollup-linux-riscv64-gnu@npm:4.44.0":
version: 4.44.0
resolution: "@rollup/rollup-linux-riscv64-gnu@npm:4.44.0"
@@ -2093,6 +2541,13 @@ __metadata:
languageName: node
linkType: hard
+"@rollup/rollup-linux-riscv64-gnu@npm:4.59.0":
+ version: 4.59.0
+ resolution: "@rollup/rollup-linux-riscv64-gnu@npm:4.59.0"
+ conditions: os=linux & cpu=riscv64 & libc=glibc
+ languageName: node
+ linkType: hard
+
"@rollup/rollup-linux-riscv64-musl@npm:4.44.0":
version: 4.44.0
resolution: "@rollup/rollup-linux-riscv64-musl@npm:4.44.0"
@@ -2100,6 +2555,13 @@ __metadata:
languageName: node
linkType: hard
+"@rollup/rollup-linux-riscv64-musl@npm:4.59.0":
+ version: 4.59.0
+ resolution: "@rollup/rollup-linux-riscv64-musl@npm:4.59.0"
+ conditions: os=linux & cpu=riscv64 & libc=musl
+ languageName: node
+ linkType: hard
+
"@rollup/rollup-linux-s390x-gnu@npm:4.44.0":
version: 4.44.0
resolution: "@rollup/rollup-linux-s390x-gnu@npm:4.44.0"
@@ -2107,6 +2569,13 @@ __metadata:
languageName: node
linkType: hard
+"@rollup/rollup-linux-s390x-gnu@npm:4.59.0":
+ version: 4.59.0
+ resolution: "@rollup/rollup-linux-s390x-gnu@npm:4.59.0"
+ conditions: os=linux & cpu=s390x & libc=glibc
+ languageName: node
+ linkType: hard
+
"@rollup/rollup-linux-x64-gnu@npm:4.44.0":
version: 4.44.0
resolution: "@rollup/rollup-linux-x64-gnu@npm:4.44.0"
@@ -2114,6 +2583,13 @@ __metadata:
languageName: node
linkType: hard
+"@rollup/rollup-linux-x64-gnu@npm:4.59.0":
+ version: 4.59.0
+ resolution: "@rollup/rollup-linux-x64-gnu@npm:4.59.0"
+ conditions: os=linux & cpu=x64 & libc=glibc
+ languageName: node
+ linkType: hard
+
"@rollup/rollup-linux-x64-musl@npm:4.44.0":
version: 4.44.0
resolution: "@rollup/rollup-linux-x64-musl@npm:4.44.0"
@@ -2121,10 +2597,38 @@ __metadata:
languageName: node
linkType: hard
-"@rollup/rollup-win32-arm64-msvc@npm:4.44.0":
- version: 4.44.0
- resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.44.0"
- conditions: os=win32 & cpu=arm64
+"@rollup/rollup-linux-x64-musl@npm:4.59.0":
+ version: 4.59.0
+ resolution: "@rollup/rollup-linux-x64-musl@npm:4.59.0"
+ conditions: os=linux & cpu=x64 & libc=musl
+ languageName: node
+ linkType: hard
+
+"@rollup/rollup-openbsd-x64@npm:4.59.0":
+ version: 4.59.0
+ resolution: "@rollup/rollup-openbsd-x64@npm:4.59.0"
+ conditions: os=openbsd & cpu=x64
+ languageName: node
+ linkType: hard
+
+"@rollup/rollup-openharmony-arm64@npm:4.59.0":
+ version: 4.59.0
+ resolution: "@rollup/rollup-openharmony-arm64@npm:4.59.0"
+ conditions: os=openharmony & cpu=arm64
+ languageName: node
+ linkType: hard
+
+"@rollup/rollup-win32-arm64-msvc@npm:4.44.0":
+ version: 4.44.0
+ resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.44.0"
+ conditions: os=win32 & cpu=arm64
+ languageName: node
+ linkType: hard
+
+"@rollup/rollup-win32-arm64-msvc@npm:4.59.0":
+ version: 4.59.0
+ resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.59.0"
+ conditions: os=win32 & cpu=arm64
languageName: node
linkType: hard
@@ -2135,6 +2639,20 @@ __metadata:
languageName: node
linkType: hard
+"@rollup/rollup-win32-ia32-msvc@npm:4.59.0":
+ version: 4.59.0
+ resolution: "@rollup/rollup-win32-ia32-msvc@npm:4.59.0"
+ conditions: os=win32 & cpu=ia32
+ languageName: node
+ linkType: hard
+
+"@rollup/rollup-win32-x64-gnu@npm:4.59.0":
+ version: 4.59.0
+ resolution: "@rollup/rollup-win32-x64-gnu@npm:4.59.0"
+ conditions: os=win32 & cpu=x64
+ languageName: node
+ linkType: hard
+
"@rollup/rollup-win32-x64-msvc@npm:4.44.0":
version: 4.44.0
resolution: "@rollup/rollup-win32-x64-msvc@npm:4.44.0"
@@ -2142,6 +2660,13 @@ __metadata:
languageName: node
linkType: hard
+"@rollup/rollup-win32-x64-msvc@npm:4.59.0":
+ version: 4.59.0
+ resolution: "@rollup/rollup-win32-x64-msvc@npm:4.59.0"
+ conditions: os=win32 & cpu=x64
+ languageName: node
+ linkType: hard
+
"@rushstack/node-core-library@npm:3.59.0":
version: 3.59.0
resolution: "@rushstack/node-core-library@npm:3.59.0"
@@ -2246,6 +2771,18 @@ __metadata:
languageName: node
linkType: hard
+"@types/jsdom@npm:^28":
+ version: 28.0.0
+ resolution: "@types/jsdom@npm:28.0.0"
+ dependencies:
+ "@types/node": "npm:*"
+ "@types/tough-cookie": "npm:*"
+ parse5: "npm:^7.0.0"
+ undici-types: "npm:^7.21.0"
+ checksum: 10c0/7b4b06dee1c611e37766ae2c5f92b0a881e3a2da8e38cc34999e812ab030b54b7250b0b9cc9af24dbeadc0fc2d341cc4e0adc5e5ca7d624d134ced1414a1ea5e
+ languageName: node
+ linkType: hard
+
"@types/lodash.mergewith@npm:4.6.6":
version: 4.6.6
resolution: "@types/lodash.mergewith@npm:4.6.6"
@@ -2262,6 +2799,15 @@ __metadata:
languageName: node
linkType: hard
+"@types/node@npm:*":
+ version: 25.4.0
+ resolution: "@types/node@npm:25.4.0"
+ dependencies:
+ undici-types: "npm:~7.18.0"
+ checksum: 10c0/da81e8b0a3a57964b1b5f85d134bfefc1b923fd67ed41756842348a049d7915b72e8773f5598d6929b9cb8119c2427993c55d364fd93bd572a3450e58b98a60e
+ languageName: node
+ linkType: hard
+
"@types/node@npm:^20.11.24":
version: 20.19.1
resolution: "@types/node@npm:20.19.1"
@@ -2341,6 +2887,13 @@ __metadata:
languageName: node
linkType: hard
+"@types/tough-cookie@npm:*":
+ version: 4.0.5
+ resolution: "@types/tough-cookie@npm:4.0.5"
+ checksum: 10c0/68c6921721a3dcb40451543db2174a145ef915bc8bcbe7ad4e59194a0238e776e782b896c7a59f4b93ac6acefca9161fccb31d1ce3b3445cb6faa467297fb473
+ languageName: node
+ linkType: hard
+
"@types/warning@npm:^3.0.0":
version: 3.0.3
resolution: "@types/warning@npm:3.0.3"
@@ -2364,6 +2917,173 @@ __metadata:
languageName: node
linkType: hard
+"@vitest/coverage-v8@npm:^2.1.9":
+ version: 2.1.9
+ resolution: "@vitest/coverage-v8@npm:2.1.9"
+ dependencies:
+ "@ampproject/remapping": "npm:^2.3.0"
+ "@bcoe/v8-coverage": "npm:^0.2.3"
+ debug: "npm:^4.3.7"
+ istanbul-lib-coverage: "npm:^3.2.2"
+ istanbul-lib-report: "npm:^3.0.1"
+ istanbul-lib-source-maps: "npm:^5.0.6"
+ istanbul-reports: "npm:^3.1.7"
+ magic-string: "npm:^0.30.12"
+ magicast: "npm:^0.3.5"
+ std-env: "npm:^3.8.0"
+ test-exclude: "npm:^7.0.1"
+ tinyrainbow: "npm:^1.2.0"
+ peerDependencies:
+ "@vitest/browser": 2.1.9
+ vitest: 2.1.9
+ peerDependenciesMeta:
+ "@vitest/browser":
+ optional: true
+ checksum: 10c0/ccf5871954a630453af9393e84ff40a0f8a4515e988ea32c7ebac5db7c79f17535a12c1c2567cbb78ea01a1eb99abdde94e297f6b6ccd5f7f7fc9b8b01c5963c
+ languageName: node
+ linkType: hard
+
+"@vitest/coverage-v8@npm:^4.0.18":
+ version: 4.0.18
+ resolution: "@vitest/coverage-v8@npm:4.0.18"
+ dependencies:
+ "@bcoe/v8-coverage": "npm:^1.0.2"
+ "@vitest/utils": "npm:4.0.18"
+ ast-v8-to-istanbul: "npm:^0.3.10"
+ istanbul-lib-coverage: "npm:^3.2.2"
+ istanbul-lib-report: "npm:^3.0.1"
+ istanbul-reports: "npm:^3.2.0"
+ magicast: "npm:^0.5.1"
+ obug: "npm:^2.1.1"
+ std-env: "npm:^3.10.0"
+ tinyrainbow: "npm:^3.0.3"
+ peerDependencies:
+ "@vitest/browser": 4.0.18
+ vitest: 4.0.18
+ peerDependenciesMeta:
+ "@vitest/browser":
+ optional: true
+ checksum: 10c0/e23e0da86f0b2a020c51562bc40ebdc7fc7553c24f8071dfb39a6df0161badbd5eaf2eebbf8ceaef18933a18c1934ff52d1c0c4bde77bb87e0c1feb0c8cbee4d
+ languageName: node
+ linkType: hard
+
+"@vitest/expect@npm:2.1.9":
+ version: 2.1.9
+ resolution: "@vitest/expect@npm:2.1.9"
+ dependencies:
+ "@vitest/spy": "npm:2.1.9"
+ "@vitest/utils": "npm:2.1.9"
+ chai: "npm:^5.1.2"
+ tinyrainbow: "npm:^1.2.0"
+ checksum: 10c0/98d1cf02917316bebef9e4720723e38298a1c12b3c8f3a81f259bb822de4288edf594e69ff64f0b88afbda6d04d7a4f0c2f720f3fec16b4c45f5e2669f09fdbb
+ languageName: node
+ linkType: hard
+
+"@vitest/mocker@npm:2.1.9":
+ version: 2.1.9
+ resolution: "@vitest/mocker@npm:2.1.9"
+ dependencies:
+ "@vitest/spy": "npm:2.1.9"
+ estree-walker: "npm:^3.0.3"
+ magic-string: "npm:^0.30.12"
+ peerDependencies:
+ msw: ^2.4.9
+ vite: ^5.0.0
+ peerDependenciesMeta:
+ msw:
+ optional: true
+ vite:
+ optional: true
+ checksum: 10c0/f734490d8d1206a7f44dfdfca459282f5921d73efa72935bb1dc45307578defd38a4131b14853316373ec364cbe910dbc74594ed4137e0da35aa4d9bb716f190
+ languageName: node
+ linkType: hard
+
+"@vitest/pretty-format@npm:2.1.9, @vitest/pretty-format@npm:^2.1.9":
+ version: 2.1.9
+ resolution: "@vitest/pretty-format@npm:2.1.9"
+ dependencies:
+ tinyrainbow: "npm:^1.2.0"
+ checksum: 10c0/155f9ede5090eabed2a73361094bb35ed4ec6769ae3546d2a2af139166569aec41bb80e031c25ff2da22b71dd4ed51e5468e66a05e6aeda5f14b32e30bc18f00
+ languageName: node
+ linkType: hard
+
+"@vitest/pretty-format@npm:4.0.18":
+ version: 4.0.18
+ resolution: "@vitest/pretty-format@npm:4.0.18"
+ dependencies:
+ tinyrainbow: "npm:^3.0.3"
+ checksum: 10c0/0086b8c88eeca896d8e4b98fcdef452c8041a1b63eb9e85d3e0bcc96c8aa76d8e9e0b6990ebb0bb0a697c4ebab347e7735888b24f507dbff2742ddce7723fd94
+ languageName: node
+ linkType: hard
+
+"@vitest/runner@npm:2.1.9":
+ version: 2.1.9
+ resolution: "@vitest/runner@npm:2.1.9"
+ dependencies:
+ "@vitest/utils": "npm:2.1.9"
+ pathe: "npm:^1.1.2"
+ checksum: 10c0/e81f176badb12a815cbbd9bd97e19f7437a0b64e8934d680024b0f768d8670d59cad698ef0e3dada5241b6731d77a7bb3cd2c7cb29f751fd4dd35eb11c42963a
+ languageName: node
+ linkType: hard
+
+"@vitest/snapshot@npm:2.1.9":
+ version: 2.1.9
+ resolution: "@vitest/snapshot@npm:2.1.9"
+ dependencies:
+ "@vitest/pretty-format": "npm:2.1.9"
+ magic-string: "npm:^0.30.12"
+ pathe: "npm:^1.1.2"
+ checksum: 10c0/394974b3a1fe96186a3c87f933b2f7f1f7b7cc42f9c781d80271dbb4c987809bf035fecd7398b8a3a2d54169e3ecb49655e38a0131d0e7fea5ce88960613b526
+ languageName: node
+ linkType: hard
+
+"@vitest/spy@npm:2.1.9":
+ version: 2.1.9
+ resolution: "@vitest/spy@npm:2.1.9"
+ dependencies:
+ tinyspy: "npm:^3.0.2"
+ checksum: 10c0/12a59b5095e20188b819a1d797e0a513d991b4e6a57db679927c43b362a3eff52d823b34e855a6dd9e73c9fa138dcc5ef52210841a93db5cbf047957a60ca83c
+ languageName: node
+ linkType: hard
+
+"@vitest/ui@npm:^2.0.0":
+ version: 2.1.9
+ resolution: "@vitest/ui@npm:2.1.9"
+ dependencies:
+ "@vitest/utils": "npm:2.1.9"
+ fflate: "npm:^0.8.2"
+ flatted: "npm:^3.3.1"
+ pathe: "npm:^1.1.2"
+ sirv: "npm:^3.0.0"
+ tinyglobby: "npm:^0.2.10"
+ tinyrainbow: "npm:^1.2.0"
+ peerDependencies:
+ vitest: 2.1.9
+ checksum: 10c0/b091f5afd5e7327d1dfc37e26af16d58066bd6c37ec0a1547796f1843eff3170c59062243475fb250ca36d8d7c7293ab78b36b2d112d7839ba8331625ab9b1d3
+ languageName: node
+ linkType: hard
+
+"@vitest/utils@npm:2.1.9":
+ version: 2.1.9
+ resolution: "@vitest/utils@npm:2.1.9"
+ dependencies:
+ "@vitest/pretty-format": "npm:2.1.9"
+ loupe: "npm:^3.1.2"
+ tinyrainbow: "npm:^1.2.0"
+ checksum: 10c0/81a346cd72b47941f55411f5df4cc230e5f740d1e97e0d3f771b27f007266fc1f28d0438582f6409ea571bc0030ed37f684c64c58d1947d6298d770c21026fdf
+ languageName: node
+ linkType: hard
+
+"@vitest/utils@npm:4.0.18":
+ version: 4.0.18
+ resolution: "@vitest/utils@npm:4.0.18"
+ dependencies:
+ "@vitest/pretty-format": "npm:4.0.18"
+ tinyrainbow: "npm:^3.0.3"
+ checksum: 10c0/4a3c43c1421eb90f38576926496f6c80056167ba111e63f77cf118983902673737a1a38880b890d7c06ec0a12475024587344ee502b3c43093781533022f2aeb
+ languageName: node
+ linkType: hard
+
"@volar/language-core@npm:2.4.17":
version: 2.4.17
resolution: "@volar/language-core@npm:2.4.17"
@@ -2483,6 +3203,24 @@ __metadata:
languageName: node
linkType: hard
+"assertion-error@npm:^2.0.1":
+ version: 2.0.1
+ resolution: "assertion-error@npm:2.0.1"
+ checksum: 10c0/bbbcb117ac6480138f8c93cf7f535614282dea9dc828f540cdece85e3c665e8f78958b96afac52f29ff883c72638e6a87d469ecc9fe5bc902df03ed24a55dba8
+ languageName: node
+ linkType: hard
+
+"ast-v8-to-istanbul@npm:^0.3.10":
+ version: 0.3.12
+ resolution: "ast-v8-to-istanbul@npm:0.3.12"
+ dependencies:
+ "@jridgewell/trace-mapping": "npm:^0.3.31"
+ estree-walker: "npm:^3.0.3"
+ js-tokens: "npm:^10.0.0"
+ checksum: 10c0/bad6ba222b1073c165c8d65dbf366193d4a90536dabe37f93a3df162269b1c9473975756e4c048f708c235efccc26f8e5321c547b7e9563b64b21b2e0f27cbc9
+ languageName: node
+ linkType: hard
+
"babel-plugin-macros@npm:^3.1.0":
version: 3.1.0
resolution: "babel-plugin-macros@npm:3.1.0"
@@ -2501,6 +3239,13 @@ __metadata:
languageName: node
linkType: hard
+"balanced-match@npm:^4.0.2":
+ version: 4.0.4
+ resolution: "balanced-match@npm:4.0.4"
+ checksum: 10c0/07e86102a3eb2ee2a6a1a89164f29d0dbaebd28f2ca3f5ca786f36b8b23d9e417eb3be45a4acf754f837be5ac0a2317de90d3fcb7f4f4dc95720a1f36b26a17b
+ languageName: node
+ linkType: hard
+
"base64-arraybuffer@npm:^1.0.2":
version: 1.0.2
resolution: "base64-arraybuffer@npm:1.0.2"
@@ -2508,6 +3253,15 @@ __metadata:
languageName: node
linkType: hard
+"bidi-js@npm:^1.0.3":
+ version: 1.0.3
+ resolution: "bidi-js@npm:1.0.3"
+ dependencies:
+ require-from-string: "npm:^2.0.2"
+ checksum: 10c0/fdddea4aa4120a34285486f2267526cd9298b6e8b773ad25e765d4f104b6d7437ab4ba542e6939e3ac834a7570bcf121ee2cf6d3ae7cd7082c4b5bedc8f271e1
+ languageName: node
+ linkType: hard
+
"brace-expansion@npm:^2.0.1":
version: 2.0.1
resolution: "brace-expansion@npm:2.0.1"
@@ -2517,6 +3271,15 @@ __metadata:
languageName: node
linkType: hard
+"brace-expansion@npm:^5.0.2":
+ version: 5.0.4
+ resolution: "brace-expansion@npm:5.0.4"
+ dependencies:
+ balanced-match: "npm:^4.0.2"
+ checksum: 10c0/359cbcfa80b2eb914ca1f3440e92313fbfe7919ee6b274c35db55bec555aded69dac5ee78f102cec90c35f98c20fa43d10936d0cd9978158823c249257e1643a
+ languageName: node
+ linkType: hard
+
"braces@npm:^3.0.3":
version: 3.0.3
resolution: "braces@npm:3.0.3"
@@ -2547,6 +3310,13 @@ __metadata:
languageName: node
linkType: hard
+"cac@npm:^6.7.14":
+ version: 6.7.14
+ resolution: "cac@npm:6.7.14"
+ checksum: 10c0/4ee06aaa7bab8981f0d54e5f5f9d4adcd64058e9697563ce336d8a3878ed018ee18ebe5359b2430eceae87e0758e62ea2019c3f52ae6e211b1bd2e133856cd10
+ languageName: node
+ linkType: hard
+
"cacache@npm:^19.0.1":
version: 19.0.1
resolution: "cacache@npm:19.0.1"
@@ -2581,6 +3351,19 @@ __metadata:
languageName: node
linkType: hard
+"chai@npm:^5.1.2":
+ version: 5.3.3
+ resolution: "chai@npm:5.3.3"
+ dependencies:
+ assertion-error: "npm:^2.0.1"
+ check-error: "npm:^2.1.1"
+ deep-eql: "npm:^5.0.1"
+ loupe: "npm:^3.1.0"
+ pathval: "npm:^2.0.0"
+ checksum: 10c0/b360fd4d38861622e5010c2f709736988b05c7f31042305fa3f4e9911f6adb80ccfb4e302068bf8ed10e835c2e2520cba0f5edc13d878b886987e5aa62483f53
+ languageName: node
+ linkType: hard
+
"chalk@npm:^5.4.1":
version: 5.4.1
resolution: "chalk@npm:5.4.1"
@@ -2588,6 +3371,13 @@ __metadata:
languageName: node
linkType: hard
+"check-error@npm:^2.1.1":
+ version: 2.1.3
+ resolution: "check-error@npm:2.1.3"
+ checksum: 10c0/878e99038fb6476316b74668cd6a498c7e66df3efe48158fa40db80a06ba4258742ac3ee2229c4a2a98c5e73f5dff84eb3e50ceb6b65bbd8f831eafc8338607d
+ languageName: node
+ linkType: hard
+
"chownr@npm:^3.0.0":
version: 3.0.0
resolution: "chownr@npm:3.0.0"
@@ -2782,6 +3572,28 @@ __metadata:
languageName: node
linkType: hard
+"css-tree@npm:^3.0.0, css-tree@npm:^3.1.0":
+ version: 3.2.1
+ resolution: "css-tree@npm:3.2.1"
+ dependencies:
+ mdn-data: "npm:2.27.1"
+ source-map-js: "npm:^1.2.1"
+ checksum: 10c0/1f65e9ccaa56112a4706d6f003dd43d777f0dbcf848e66fd320f823192533581f8dd58daa906cb80622658332d50284d6be13b87a6ab4556cbbfe9ef535bbf7e
+ languageName: node
+ linkType: hard
+
+"cssstyle@npm:^6.0.1":
+ version: 6.2.0
+ resolution: "cssstyle@npm:6.2.0"
+ dependencies:
+ "@asamuzakjp/css-color": "npm:^5.0.1"
+ "@csstools/css-syntax-patches-for-csstree": "npm:^1.0.28"
+ css-tree: "npm:^3.1.0"
+ lru-cache: "npm:^11.2.6"
+ checksum: 10c0/d5e61973a8c1b4fb9727edddfb9f2677c9a91b1db63787fc0c8bed639a227a97fcf930e5aabc3c64c8280d63169c632015a39da4a84083d1731c949437d0a2a2
+ languageName: node
+ linkType: hard
+
"csstype@npm:3.0.9":
version: 3.0.9
resolution: "csstype@npm:3.0.9"
@@ -2796,6 +3608,16 @@ __metadata:
languageName: node
linkType: hard
+"data-urls@npm:^7.0.0":
+ version: 7.0.0
+ resolution: "data-urls@npm:7.0.0"
+ dependencies:
+ whatwg-mimetype: "npm:^5.0.0"
+ whatwg-url: "npm:^16.0.0"
+ checksum: 10c0/08d88ef50d8966a070ffdaa703e1e4b29f01bb2da364dfbc1612b1c2a4caa8045802c9532d81347b21781100132addb36a585071c8323b12cce97973961dee9f
+ languageName: node
+ linkType: hard
+
"debug@npm:4, debug@npm:^4.1.0, debug@npm:^4.3.1, debug@npm:^4.3.4":
version: 4.3.7
resolution: "debug@npm:4.3.7"
@@ -2808,6 +3630,18 @@ __metadata:
languageName: node
linkType: hard
+"debug@npm:^4.1.1, debug@npm:^4.3.7":
+ version: 4.4.3
+ resolution: "debug@npm:4.4.3"
+ dependencies:
+ ms: "npm:^2.1.3"
+ peerDependenciesMeta:
+ supports-color:
+ optional: true
+ checksum: 10c0/d79136ec6c83ecbefd0f6a5593da6a9c91ec4d7ddc4b54c883d6e71ec9accb5f67a1a5e96d00a328196b5b5c86d365e98d8a3a70856aaf16b4e7b1985e67f5a6
+ languageName: node
+ linkType: hard
+
"debug@npm:^4.4.0, debug@npm:^4.4.1":
version: 4.4.1
resolution: "debug@npm:4.4.1"
@@ -2820,6 +3654,20 @@ __metadata:
languageName: node
linkType: hard
+"decimal.js@npm:^10.6.0":
+ version: 10.6.0
+ resolution: "decimal.js@npm:10.6.0"
+ checksum: 10c0/07d69fbcc54167a340d2d97de95f546f9ff1f69d2b45a02fd7a5292412df3cd9eb7e23065e532a318f5474a2e1bccf8392fdf0443ef467f97f3bf8cb0477e5aa
+ languageName: node
+ linkType: hard
+
+"deep-eql@npm:^5.0.1":
+ version: 5.0.2
+ resolution: "deep-eql@npm:5.0.2"
+ checksum: 10c0/7102cf3b7bb719c6b9c0db2e19bf0aa9318d141581befe8c7ce8ccd39af9eaa4346e5e05adef7f9bd7015da0f13a3a25dcfe306ef79dc8668aedbecb658dd247
+ languageName: node
+ linkType: hard
+
"define-lazy-prop@npm:^2.0.0":
version: 2.0.0
resolution: "define-lazy-prop@npm:2.0.0"
@@ -2897,6 +3745,13 @@ __metadata:
languageName: node
linkType: hard
+"entities@npm:^6.0.0":
+ version: 6.0.1
+ resolution: "entities@npm:6.0.1"
+ checksum: 10c0/ed836ddac5acb34341094eb495185d527bd70e8632b6c0d59548cbfa23defdbae70b96f9a405c82904efa421230b5b3fd2283752447d737beffd3f3e6ee74414
+ languageName: node
+ linkType: hard
+
"env-paths@npm:^2.2.0":
version: 2.2.1
resolution: "env-paths@npm:2.2.1"
@@ -2927,6 +3782,93 @@ __metadata:
languageName: node
linkType: hard
+"es-module-lexer@npm:^1.5.4":
+ version: 1.7.0
+ resolution: "es-module-lexer@npm:1.7.0"
+ checksum: 10c0/4c935affcbfeba7fb4533e1da10fa8568043df1e3574b869385980de9e2d475ddc36769891936dbb07036edb3c3786a8b78ccf44964cd130dedc1f2c984b6c7b
+ languageName: node
+ linkType: hard
+
+"esbuild@npm:^0.21.3":
+ version: 0.21.5
+ resolution: "esbuild@npm:0.21.5"
+ dependencies:
+ "@esbuild/aix-ppc64": "npm:0.21.5"
+ "@esbuild/android-arm": "npm:0.21.5"
+ "@esbuild/android-arm64": "npm:0.21.5"
+ "@esbuild/android-x64": "npm:0.21.5"
+ "@esbuild/darwin-arm64": "npm:0.21.5"
+ "@esbuild/darwin-x64": "npm:0.21.5"
+ "@esbuild/freebsd-arm64": "npm:0.21.5"
+ "@esbuild/freebsd-x64": "npm:0.21.5"
+ "@esbuild/linux-arm": "npm:0.21.5"
+ "@esbuild/linux-arm64": "npm:0.21.5"
+ "@esbuild/linux-ia32": "npm:0.21.5"
+ "@esbuild/linux-loong64": "npm:0.21.5"
+ "@esbuild/linux-mips64el": "npm:0.21.5"
+ "@esbuild/linux-ppc64": "npm:0.21.5"
+ "@esbuild/linux-riscv64": "npm:0.21.5"
+ "@esbuild/linux-s390x": "npm:0.21.5"
+ "@esbuild/linux-x64": "npm:0.21.5"
+ "@esbuild/netbsd-x64": "npm:0.21.5"
+ "@esbuild/openbsd-x64": "npm:0.21.5"
+ "@esbuild/sunos-x64": "npm:0.21.5"
+ "@esbuild/win32-arm64": "npm:0.21.5"
+ "@esbuild/win32-ia32": "npm:0.21.5"
+ "@esbuild/win32-x64": "npm:0.21.5"
+ dependenciesMeta:
+ "@esbuild/aix-ppc64":
+ optional: true
+ "@esbuild/android-arm":
+ optional: true
+ "@esbuild/android-arm64":
+ optional: true
+ "@esbuild/android-x64":
+ optional: true
+ "@esbuild/darwin-arm64":
+ optional: true
+ "@esbuild/darwin-x64":
+ optional: true
+ "@esbuild/freebsd-arm64":
+ optional: true
+ "@esbuild/freebsd-x64":
+ optional: true
+ "@esbuild/linux-arm":
+ optional: true
+ "@esbuild/linux-arm64":
+ optional: true
+ "@esbuild/linux-ia32":
+ optional: true
+ "@esbuild/linux-loong64":
+ optional: true
+ "@esbuild/linux-mips64el":
+ optional: true
+ "@esbuild/linux-ppc64":
+ optional: true
+ "@esbuild/linux-riscv64":
+ optional: true
+ "@esbuild/linux-s390x":
+ optional: true
+ "@esbuild/linux-x64":
+ optional: true
+ "@esbuild/netbsd-x64":
+ optional: true
+ "@esbuild/openbsd-x64":
+ optional: true
+ "@esbuild/sunos-x64":
+ optional: true
+ "@esbuild/win32-arm64":
+ optional: true
+ "@esbuild/win32-ia32":
+ optional: true
+ "@esbuild/win32-x64":
+ optional: true
+ bin:
+ esbuild: bin/esbuild
+ checksum: 10c0/fa08508adf683c3f399e8a014a6382a6b65542213431e26206c0720e536b31c09b50798747c2a105a4bbba1d9767b8d3615a74c2f7bf1ddf6d836cd11eb672de
+ languageName: node
+ linkType: hard
+
"esbuild@npm:^0.25.0":
version: 0.25.5
resolution: "esbuild@npm:0.25.5"
@@ -3034,6 +3976,15 @@ __metadata:
languageName: node
linkType: hard
+"estree-walker@npm:^3.0.3":
+ version: 3.0.3
+ resolution: "estree-walker@npm:3.0.3"
+ dependencies:
+ "@types/estree": "npm:^1.0.0"
+ checksum: 10c0/c12e3c2b2642d2bcae7d5aa495c60fa2f299160946535763969a1c83fc74518ffa9c2cd3a8b69ac56aea547df6a8aac25f729a342992ef0bbac5f1c73e78995d
+ languageName: node
+ linkType: hard
+
"eventemitter3@npm:^5.0.1":
version: 5.0.1
resolution: "eventemitter3@npm:5.0.1"
@@ -3056,6 +4007,13 @@ __metadata:
languageName: node
linkType: hard
+"expect-type@npm:^1.1.0":
+ version: 1.3.0
+ resolution: "expect-type@npm:1.3.0"
+ checksum: 10c0/8412b3fe4f392c420ab41dae220b09700e4e47c639a29ba7ba2e83cc6cffd2b4926f7ac9e47d7e277e8f4f02acda76fd6931cb81fd2b382fa9477ef9ada953fd
+ languageName: node
+ linkType: hard
+
"exponential-backoff@npm:^3.1.1":
version: 3.1.2
resolution: "exponential-backoff@npm:3.1.2"
@@ -3118,6 +4076,25 @@ __metadata:
languageName: node
linkType: hard
+"fdir@npm:^6.5.0":
+ version: 6.5.0
+ resolution: "fdir@npm:6.5.0"
+ peerDependencies:
+ picomatch: ^3 || ^4
+ peerDependenciesMeta:
+ picomatch:
+ optional: true
+ checksum: 10c0/e345083c4306b3aed6cb8ec551e26c36bab5c511e99ea4576a16750ddc8d3240e63826cc624f5ae17ad4dc82e68a253213b60d556c11bfad064b7607847ed07f
+ languageName: node
+ linkType: hard
+
+"fflate@npm:^0.8.2":
+ version: 0.8.2
+ resolution: "fflate@npm:0.8.2"
+ checksum: 10c0/03448d630c0a583abea594835a9fdb2aaf7d67787055a761515bf4ed862913cfd693b4c4ffd5c3f3b355a70cf1e19033e9ae5aedcca103188aaff91b8bd6e293
+ languageName: node
+ linkType: hard
+
"fill-range@npm:^7.1.1":
version: 7.1.1
resolution: "fill-range@npm:7.1.1"
@@ -3134,6 +4111,13 @@ __metadata:
languageName: node
linkType: hard
+"flatted@npm:^3.3.1":
+ version: 3.4.1
+ resolution: "flatted@npm:3.4.1"
+ checksum: 10c0/3987a7f1e39bc7215cece001354313b462cdb4fb2dde0df4f7acd9e5016fbae56ee6fb3f0870b2150145033be8bda4f01af6f87a00946049651131bbfca7dfa6
+ languageName: node
+ linkType: hard
+
"focus-lock@npm:^0.9.1":
version: 0.9.2
resolution: "focus-lock@npm:0.9.2"
@@ -3300,6 +4284,22 @@ __metadata:
languageName: node
linkType: hard
+"glob@npm:^10.4.1":
+ version: 10.5.0
+ resolution: "glob@npm:10.5.0"
+ dependencies:
+ foreground-child: "npm:^3.1.0"
+ jackspeak: "npm:^3.1.2"
+ minimatch: "npm:^9.0.4"
+ minipass: "npm:^7.1.2"
+ package-json-from-dist: "npm:^1.0.0"
+ path-scurry: "npm:^1.11.1"
+ bin:
+ glob: dist/esm/bin.mjs
+ checksum: 10c0/100705eddbde6323e7b35e1d1ac28bcb58322095bd8e63a7d0bef1a2cdafe0d0f7922a981b2b48369a4f8c1b077be5c171804534c3509dfe950dde15fbe6d828
+ languageName: node
+ linkType: hard
+
"globals@npm:^11.1.0":
version: 11.12.0
resolution: "globals@npm:11.12.0"
@@ -3314,6 +4314,13 @@ __metadata:
languageName: node
linkType: hard
+"has-flag@npm:^4.0.0":
+ version: 4.0.0
+ resolution: "has-flag@npm:4.0.0"
+ checksum: 10c0/2e789c61b7888d66993e14e8331449e525ef42aac53c627cc53d1c3334e768bcb6abdc4f5f0de1478a25beec6f0bd62c7549058b7ac53e924040d4f301f02fd1
+ languageName: node
+ linkType: hard
+
"hasown@npm:^2.0.2":
version: 2.0.2
resolution: "hasown@npm:2.0.2"
@@ -3339,6 +4346,22 @@ __metadata:
languageName: node
linkType: hard
+"html-encoding-sniffer@npm:^6.0.0":
+ version: 6.0.0
+ resolution: "html-encoding-sniffer@npm:6.0.0"
+ dependencies:
+ "@exodus/bytes": "npm:^1.6.0"
+ checksum: 10c0/66dc3f6f5539cc3beb814fcbfae7eacf4ec38cf824d6e1425b72039b51a40f4456bd8541ba66f4f4fe09cdf885ab5cd5bae6ec6339d6895a930b2fdb83c53025
+ languageName: node
+ linkType: hard
+
+"html-escaper@npm:^2.0.0":
+ version: 2.0.2
+ resolution: "html-escaper@npm:2.0.2"
+ checksum: 10c0/208e8a12de1a6569edbb14544f4567e6ce8ecc30b9394fcaa4e7bb1e60c12a7c9a1ed27e31290817157e8626f3a4f29e76c8747030822eb84a6abb15c255f0a0
+ languageName: node
+ linkType: hard
+
"html2canvas@npm:^1.4.1":
version: 1.4.1
resolution: "html2canvas@npm:1.4.1"
@@ -3356,7 +4379,7 @@ __metadata:
languageName: node
linkType: hard
-"http-proxy-agent@npm:^7.0.0":
+"http-proxy-agent@npm:^7.0.0, http-proxy-agent@npm:^7.0.2":
version: 7.0.2
resolution: "http-proxy-agent@npm:7.0.2"
dependencies:
@@ -3366,7 +4389,7 @@ __metadata:
languageName: node
linkType: hard
-"https-proxy-agent@npm:^7.0.1":
+"https-proxy-agent@npm:^7.0.1, https-proxy-agent@npm:^7.0.6":
version: 7.0.6
resolution: "https-proxy-agent@npm:7.0.6"
dependencies:
@@ -3416,6 +4439,8 @@ __metadata:
"@types/react-color": "npm:^2"
"@types/react-dom": "npm:^17.0.2"
"@vitejs/plugin-react": "npm:^4.5.2"
+ "@vitest/coverage-v8": "npm:^2.1.9"
+ "@vitest/ui": "npm:^2.0.0"
framer-motion: "npm:6.5.1"
imagekit-javascript: "npm:^3.0.2"
lodash: "npm:^4.17.21"
@@ -3429,6 +4454,7 @@ __metadata:
typescript: "npm:4.9.3"
vite: "npm:^6.3.5"
vite-plugin-dts: "npm:5.0.0-beta.3"
+ vitest: "npm:^2.0.0"
zod: "npm:^3.25.0"
zustand: "npm:4.5.7"
peerDependencies:
@@ -3442,7 +4468,10 @@ __metadata:
resolution: "imagekit-editor@workspace:."
dependencies:
"@biomejs/biome": "npm:2.1.1"
+ "@types/jsdom": "npm:^28"
+ "@vitest/coverage-v8": "npm:^4.0.18"
husky: "npm:^9.1.7"
+ jsdom: "npm:^28.1.0"
lint-staged: "npm:^16.1.2"
react-select: "npm:^5.2.1"
shx: "npm:^0.4.0"
@@ -3569,6 +4598,13 @@ __metadata:
languageName: node
linkType: hard
+"is-potential-custom-element-name@npm:^1.0.1":
+ version: 1.0.1
+ resolution: "is-potential-custom-element-name@npm:1.0.1"
+ checksum: 10c0/b73e2f22bc863b0939941d369486d308b43d7aef1f9439705e3582bfccaa4516406865e32c968a35f97a99396dac84e2624e67b0a16b0a15086a785e16ce7db9
+ languageName: node
+ linkType: hard
+
"is-stream@npm:^1.1.0":
version: 1.1.0
resolution: "is-stream@npm:1.1.0"
@@ -3599,6 +4635,45 @@ __metadata:
languageName: node
linkType: hard
+"istanbul-lib-coverage@npm:^3.0.0, istanbul-lib-coverage@npm:^3.2.2":
+ version: 3.2.2
+ resolution: "istanbul-lib-coverage@npm:3.2.2"
+ checksum: 10c0/6c7ff2106769e5f592ded1fb418f9f73b4411fd5a084387a5410538332b6567cd1763ff6b6cadca9b9eb2c443cce2f7ea7d7f1b8d315f9ce58539793b1e0922b
+ languageName: node
+ linkType: hard
+
+"istanbul-lib-report@npm:^3.0.0, istanbul-lib-report@npm:^3.0.1":
+ version: 3.0.1
+ resolution: "istanbul-lib-report@npm:3.0.1"
+ dependencies:
+ istanbul-lib-coverage: "npm:^3.0.0"
+ make-dir: "npm:^4.0.0"
+ supports-color: "npm:^7.1.0"
+ checksum: 10c0/84323afb14392de8b6a5714bd7e9af845cfbd56cfe71ed276cda2f5f1201aea673c7111901227ee33e68e4364e288d73861eb2ed48f6679d1e69a43b6d9b3ba7
+ languageName: node
+ linkType: hard
+
+"istanbul-lib-source-maps@npm:^5.0.6":
+ version: 5.0.6
+ resolution: "istanbul-lib-source-maps@npm:5.0.6"
+ dependencies:
+ "@jridgewell/trace-mapping": "npm:^0.3.23"
+ debug: "npm:^4.1.1"
+ istanbul-lib-coverage: "npm:^3.0.0"
+ checksum: 10c0/ffe75d70b303a3621ee4671554f306e0831b16f39ab7f4ab52e54d356a5d33e534d97563e318f1333a6aae1d42f91ec49c76b6cd3f3fb378addcb5c81da0255f
+ languageName: node
+ linkType: hard
+
+"istanbul-reports@npm:^3.1.7, istanbul-reports@npm:^3.2.0":
+ version: 3.2.0
+ resolution: "istanbul-reports@npm:3.2.0"
+ dependencies:
+ html-escaper: "npm:^2.0.0"
+ istanbul-lib-report: "npm:^3.0.0"
+ checksum: 10c0/d596317cfd9c22e1394f22a8d8ba0303d2074fe2e971887b32d870e4b33f8464b10f8ccbe6847808f7db485f084eba09e6c2ed706b3a978e4b52f07085b8f9bc
+ languageName: node
+ linkType: hard
+
"jackspeak@npm:^3.1.2":
version: 3.4.3
resolution: "jackspeak@npm:3.4.3"
@@ -3619,6 +4694,13 @@ __metadata:
languageName: node
linkType: hard
+"js-tokens@npm:^10.0.0":
+ version: 10.0.0
+ resolution: "js-tokens@npm:10.0.0"
+ checksum: 10c0/a93498747812ba3e0c8626f95f75ab29319f2a13613a0de9e610700405760931624433a0de59eb7c27ff8836e526768fb20783861b86ef89be96676f2c996b64
+ languageName: node
+ linkType: hard
+
"js-tokens@npm:^3.0.0 || ^4.0.0, js-tokens@npm:^4.0.0":
version: 4.0.0
resolution: "js-tokens@npm:4.0.0"
@@ -3633,6 +4715,40 @@ __metadata:
languageName: node
linkType: hard
+"jsdom@npm:^28.1.0":
+ version: 28.1.0
+ resolution: "jsdom@npm:28.1.0"
+ dependencies:
+ "@acemir/cssom": "npm:^0.9.31"
+ "@asamuzakjp/dom-selector": "npm:^6.8.1"
+ "@bramus/specificity": "npm:^2.4.2"
+ "@exodus/bytes": "npm:^1.11.0"
+ cssstyle: "npm:^6.0.1"
+ data-urls: "npm:^7.0.0"
+ decimal.js: "npm:^10.6.0"
+ html-encoding-sniffer: "npm:^6.0.0"
+ http-proxy-agent: "npm:^7.0.2"
+ https-proxy-agent: "npm:^7.0.6"
+ is-potential-custom-element-name: "npm:^1.0.1"
+ parse5: "npm:^8.0.0"
+ saxes: "npm:^6.0.0"
+ symbol-tree: "npm:^3.2.4"
+ tough-cookie: "npm:^6.0.0"
+ undici: "npm:^7.21.0"
+ w3c-xmlserializer: "npm:^5.0.0"
+ webidl-conversions: "npm:^8.0.1"
+ whatwg-mimetype: "npm:^5.0.0"
+ whatwg-url: "npm:^16.0.0"
+ xml-name-validator: "npm:^5.0.0"
+ peerDependencies:
+ canvas: ^3.0.0
+ peerDependenciesMeta:
+ canvas:
+ optional: true
+ checksum: 10c0/341ecb4005be2dab3247dacc349a20285d7991b5cee3382301fcd69a4294b705b4147e7d9ae1ddfab466ba4b3aace97ded4f7b070de285262221cb2782965b25
+ languageName: node
+ linkType: hard
+
"jsesc@npm:^3.0.2":
version: 3.1.0
resolution: "jsesc@npm:3.1.0"
@@ -3802,6 +4918,13 @@ __metadata:
languageName: node
linkType: hard
+"loupe@npm:^3.1.0, loupe@npm:^3.1.2":
+ version: 3.2.1
+ resolution: "loupe@npm:3.2.1"
+ checksum: 10c0/910c872cba291309664c2d094368d31a68907b6f5913e989d301b5c25f30e97d76d77f23ab3bf3b46d0f601ff0b6af8810c10c31b91d2c6b2f132809ca2cc705
+ languageName: node
+ linkType: hard
+
"lru-cache@npm:^10.0.1, lru-cache@npm:^10.2.0":
version: 10.4.3
resolution: "lru-cache@npm:10.4.3"
@@ -3809,6 +4932,13 @@ __metadata:
languageName: node
linkType: hard
+"lru-cache@npm:^11.2.6":
+ version: 11.2.6
+ resolution: "lru-cache@npm:11.2.6"
+ checksum: 10c0/73bbffb298760e71b2bfe8ebc16a311c6a60ceddbba919cfedfd8635c2d125fbfb5a39b71818200e67973b11f8d59c5a9e31d6f90722e340e90393663a66e5cd
+ languageName: node
+ linkType: hard
+
"lru-cache@npm:^5.1.1":
version: 5.1.1
resolution: "lru-cache@npm:5.1.1"
@@ -3827,6 +4957,15 @@ __metadata:
languageName: node
linkType: hard
+"magic-string@npm:^0.30.12":
+ version: 0.30.21
+ resolution: "magic-string@npm:0.30.21"
+ dependencies:
+ "@jridgewell/sourcemap-codec": "npm:^1.5.5"
+ checksum: 10c0/299378e38f9a270069fc62358522ddfb44e94244baa0d6a8980ab2a9b2490a1d03b236b447eee309e17eb3bddfa482c61259d47960eb018a904f0ded52780c4a
+ languageName: node
+ linkType: hard
+
"magic-string@npm:^0.30.17":
version: 0.30.17
resolution: "magic-string@npm:0.30.17"
@@ -3836,6 +4975,37 @@ __metadata:
languageName: node
linkType: hard
+"magicast@npm:^0.3.5":
+ version: 0.3.5
+ resolution: "magicast@npm:0.3.5"
+ dependencies:
+ "@babel/parser": "npm:^7.25.4"
+ "@babel/types": "npm:^7.25.4"
+ source-map-js: "npm:^1.2.0"
+ checksum: 10c0/a6cacc0a848af84f03e3f5bda7b0de75e4d0aa9ddce5517fd23ed0f31b5ddd51b2d0ff0b7e09b51f7de0f4053c7a1107117edda6b0732dca3e9e39e6c5a68c64
+ languageName: node
+ linkType: hard
+
+"magicast@npm:^0.5.1":
+ version: 0.5.2
+ resolution: "magicast@npm:0.5.2"
+ dependencies:
+ "@babel/parser": "npm:^7.29.0"
+ "@babel/types": "npm:^7.29.0"
+ source-map-js: "npm:^1.2.1"
+ checksum: 10c0/924af677643c5a0a7d6cdb3247c0eb96fa7611b2ba6a5e720d35d81c503d3d9f5948eb5227f80f90f82ea3e7d38cffd10bb988f3fc09020db428e14f26e960d7
+ languageName: node
+ linkType: hard
+
+"make-dir@npm:^4.0.0":
+ version: 4.0.0
+ resolution: "make-dir@npm:4.0.0"
+ dependencies:
+ semver: "npm:^7.5.3"
+ checksum: 10c0/69b98a6c0b8e5c4fe9acb61608a9fbcfca1756d910f51e5dbe7a9e5cfb74fca9b8a0c8a0ffdf1294a740826c1ab4871d5bf3f62f72a3049e5eac6541ddffed68
+ languageName: node
+ linkType: hard
+
"make-fetch-happen@npm:^14.0.3":
version: 14.0.3
resolution: "make-fetch-happen@npm:14.0.3"
@@ -3855,6 +5025,13 @@ __metadata:
languageName: node
linkType: hard
+"mdn-data@npm:2.27.1":
+ version: 2.27.1
+ resolution: "mdn-data@npm:2.27.1"
+ checksum: 10c0/eb8abf5d22e4d1e090346f5e81b67d23cef14c83940e445da5c44541ad874dc8fb9f6ca236e8258c3a489d9fb5884188a4d7d58773adb9089ac2c0b966796393
+ languageName: node
+ linkType: hard
+
"memoize-one@npm:^6.0.0":
version: 6.0.0
resolution: "memoize-one@npm:6.0.0"
@@ -3886,6 +5063,15 @@ __metadata:
languageName: node
linkType: hard
+"minimatch@npm:^10.2.2":
+ version: 10.2.4
+ resolution: "minimatch@npm:10.2.4"
+ dependencies:
+ brace-expansion: "npm:^5.0.2"
+ checksum: 10c0/35f3dfb7b99b51efd46afd378486889f590e7efb10e0f6a10ba6800428cf65c9a8dedb74427d0570b318d749b543dc4e85f06d46d2858bc8cac7e1eb49a95945
+ languageName: node
+ linkType: hard
+
"minimatch@npm:^9.0.4":
version: 9.0.5
resolution: "minimatch@npm:9.0.5"
@@ -3999,6 +5185,13 @@ __metadata:
languageName: node
linkType: hard
+"mrmime@npm:^2.0.0":
+ version: 2.0.1
+ resolution: "mrmime@npm:2.0.1"
+ checksum: 10c0/af05afd95af202fdd620422f976ad67dc18e6ee29beb03dd1ce950ea6ef664de378e44197246df4c7cdd73d47f2e7143a6e26e473084b9e4aa2095c0ad1e1761
+ languageName: node
+ linkType: hard
+
"ms@npm:^2.1.3":
version: 2.1.3
resolution: "ms@npm:2.1.3"
@@ -4090,6 +5283,13 @@ __metadata:
languageName: node
linkType: hard
+"obug@npm:^2.1.1":
+ version: 2.1.1
+ resolution: "obug@npm:2.1.1"
+ checksum: 10c0/59dccd7de72a047e08f8649e94c1015ec72f94eefb6ddb57fb4812c4b425a813bc7e7cd30c9aca20db3c59abc3c85cc7a62bb656a968741d770f4e8e02bc2e78
+ languageName: node
+ linkType: hard
+
"once@npm:^1.3.1, once@npm:^1.4.0":
version: 1.4.0
resolution: "once@npm:1.4.0"
@@ -4161,6 +5361,24 @@ __metadata:
languageName: node
linkType: hard
+"parse5@npm:^7.0.0":
+ version: 7.3.0
+ resolution: "parse5@npm:7.3.0"
+ dependencies:
+ entities: "npm:^6.0.0"
+ checksum: 10c0/7fd2e4e247e85241d6f2a464d0085eed599a26d7b0a5233790c49f53473232eb85350e8133344d9b3fd58b89339e7ad7270fe1f89d28abe50674ec97b87f80b5
+ languageName: node
+ linkType: hard
+
+"parse5@npm:^8.0.0":
+ version: 8.0.0
+ resolution: "parse5@npm:8.0.0"
+ dependencies:
+ entities: "npm:^6.0.0"
+ checksum: 10c0/8279892dcd77b2f2229707f60eb039e303adf0288812b2a8fd5acf506a4d432da833c6c5d07a6554bef722c2367a81ef4a1f7e9336564379a7dba3e798bf16b3
+ languageName: node
+ linkType: hard
+
"path-browserify@npm:^1.0.1":
version: 1.0.1
resolution: "path-browserify@npm:1.0.1"
@@ -4206,6 +5424,13 @@ __metadata:
languageName: node
linkType: hard
+"pathe@npm:^1.1.2":
+ version: 1.1.2
+ resolution: "pathe@npm:1.1.2"
+ checksum: 10c0/64ee0a4e587fb0f208d9777a6c56e4f9050039268faaaaecd50e959ef01bf847b7872785c36483fa5cdcdbdfdb31fef2ff222684d4fc21c330ab60395c681897
+ languageName: node
+ linkType: hard
+
"pathe@npm:^2.0.1, pathe@npm:^2.0.3":
version: 2.0.3
resolution: "pathe@npm:2.0.3"
@@ -4213,6 +5438,13 @@ __metadata:
languageName: node
linkType: hard
+"pathval@npm:^2.0.0":
+ version: 2.0.1
+ resolution: "pathval@npm:2.0.1"
+ checksum: 10c0/460f4709479fbf2c45903a65655fc8f0a5f6d808f989173aeef5fdea4ff4f303dc13f7870303999add60ec49d4c14733895c0a869392e9866f1091fa64fd7581
+ languageName: node
+ linkType: hard
+
"picocolors@npm:^1.1.1":
version: 1.1.1
resolution: "picocolors@npm:1.1.1"
@@ -4234,6 +5466,13 @@ __metadata:
languageName: node
linkType: hard
+"picomatch@npm:^4.0.3":
+ version: 4.0.3
+ resolution: "picomatch@npm:4.0.3"
+ checksum: 10c0/9582c951e95eebee5434f59e426cddd228a7b97a0161a375aed4be244bd3fe8e3a31b846808ea14ef2c8a2527a6eeab7b3946a67d5979e81694654f939473ae2
+ languageName: node
+ linkType: hard
+
"pidtree@npm:^0.6.0":
version: 0.6.0
resolution: "pidtree@npm:0.6.0"
@@ -4277,6 +5516,17 @@ __metadata:
languageName: node
linkType: hard
+"postcss@npm:^8.4.43":
+ version: 8.5.8
+ resolution: "postcss@npm:8.5.8"
+ dependencies:
+ nanoid: "npm:^3.3.11"
+ picocolors: "npm:^1.1.1"
+ source-map-js: "npm:^1.2.1"
+ checksum: 10c0/dd918f7127ee7c60a0295bae2e72b3787892296e1d1c3c564d7a2a00c68d8df83cadc3178491259daa19ccc54804fb71ed8c937c6787e08d8bd4bedf8d17044c
+ languageName: node
+ linkType: hard
+
"postcss@npm:^8.5.3":
version: 8.5.6
resolution: "postcss@npm:8.5.6"
@@ -4326,7 +5576,7 @@ __metadata:
languageName: node
linkType: hard
-"punycode@npm:^2.1.0":
+"punycode@npm:^2.1.0, punycode@npm:^2.3.1":
version: 2.3.1
resolution: "punycode@npm:2.3.1"
checksum: 10c0/14f76a8206bc3464f794fb2e3d3cc665ae416c01893ad7a02b23766eb07159144ee612ad67af5e84fa4479ccfe67678c4feb126b0485651b302babf66f04f9e9
@@ -4393,7 +5643,7 @@ __metadata:
version: 0.0.0-use.local
resolution: "react-example@workspace:examples/react-example"
dependencies:
- "@imagekit/editor": "npm:2.1.0"
+ "@imagekit/editor": "workspace:*"
"@types/node": "npm:^20.11.24"
"@types/react": "npm:^17.0.2"
"@types/react-dom": "npm:^17.0.2"
@@ -4563,6 +5813,13 @@ __metadata:
languageName: node
linkType: hard
+"require-from-string@npm:^2.0.2":
+ version: 2.0.2
+ resolution: "require-from-string@npm:2.0.2"
+ checksum: 10c0/aaa267e0c5b022fc5fd4eef49d8285086b15f2a1c54b28240fdf03599cbd9c26049fee3eab894f2e1f6ca65e513b030a7c264201e3f005601e80c49fb2937ce2
+ languageName: node
+ linkType: hard
+
"resolve-from@npm:^4.0.0":
version: 4.0.0
resolution: "resolve-from@npm:4.0.0"
@@ -4695,6 +5952,96 @@ __metadata:
languageName: node
linkType: hard
+"rollup@npm:^4.20.0":
+ version: 4.59.0
+ resolution: "rollup@npm:4.59.0"
+ dependencies:
+ "@rollup/rollup-android-arm-eabi": "npm:4.59.0"
+ "@rollup/rollup-android-arm64": "npm:4.59.0"
+ "@rollup/rollup-darwin-arm64": "npm:4.59.0"
+ "@rollup/rollup-darwin-x64": "npm:4.59.0"
+ "@rollup/rollup-freebsd-arm64": "npm:4.59.0"
+ "@rollup/rollup-freebsd-x64": "npm:4.59.0"
+ "@rollup/rollup-linux-arm-gnueabihf": "npm:4.59.0"
+ "@rollup/rollup-linux-arm-musleabihf": "npm:4.59.0"
+ "@rollup/rollup-linux-arm64-gnu": "npm:4.59.0"
+ "@rollup/rollup-linux-arm64-musl": "npm:4.59.0"
+ "@rollup/rollup-linux-loong64-gnu": "npm:4.59.0"
+ "@rollup/rollup-linux-loong64-musl": "npm:4.59.0"
+ "@rollup/rollup-linux-ppc64-gnu": "npm:4.59.0"
+ "@rollup/rollup-linux-ppc64-musl": "npm:4.59.0"
+ "@rollup/rollup-linux-riscv64-gnu": "npm:4.59.0"
+ "@rollup/rollup-linux-riscv64-musl": "npm:4.59.0"
+ "@rollup/rollup-linux-s390x-gnu": "npm:4.59.0"
+ "@rollup/rollup-linux-x64-gnu": "npm:4.59.0"
+ "@rollup/rollup-linux-x64-musl": "npm:4.59.0"
+ "@rollup/rollup-openbsd-x64": "npm:4.59.0"
+ "@rollup/rollup-openharmony-arm64": "npm:4.59.0"
+ "@rollup/rollup-win32-arm64-msvc": "npm:4.59.0"
+ "@rollup/rollup-win32-ia32-msvc": "npm:4.59.0"
+ "@rollup/rollup-win32-x64-gnu": "npm:4.59.0"
+ "@rollup/rollup-win32-x64-msvc": "npm:4.59.0"
+ "@types/estree": "npm:1.0.8"
+ fsevents: "npm:~2.3.2"
+ dependenciesMeta:
+ "@rollup/rollup-android-arm-eabi":
+ optional: true
+ "@rollup/rollup-android-arm64":
+ optional: true
+ "@rollup/rollup-darwin-arm64":
+ optional: true
+ "@rollup/rollup-darwin-x64":
+ optional: true
+ "@rollup/rollup-freebsd-arm64":
+ optional: true
+ "@rollup/rollup-freebsd-x64":
+ optional: true
+ "@rollup/rollup-linux-arm-gnueabihf":
+ optional: true
+ "@rollup/rollup-linux-arm-musleabihf":
+ optional: true
+ "@rollup/rollup-linux-arm64-gnu":
+ optional: true
+ "@rollup/rollup-linux-arm64-musl":
+ optional: true
+ "@rollup/rollup-linux-loong64-gnu":
+ optional: true
+ "@rollup/rollup-linux-loong64-musl":
+ optional: true
+ "@rollup/rollup-linux-ppc64-gnu":
+ optional: true
+ "@rollup/rollup-linux-ppc64-musl":
+ optional: true
+ "@rollup/rollup-linux-riscv64-gnu":
+ optional: true
+ "@rollup/rollup-linux-riscv64-musl":
+ optional: true
+ "@rollup/rollup-linux-s390x-gnu":
+ optional: true
+ "@rollup/rollup-linux-x64-gnu":
+ optional: true
+ "@rollup/rollup-linux-x64-musl":
+ optional: true
+ "@rollup/rollup-openbsd-x64":
+ optional: true
+ "@rollup/rollup-openharmony-arm64":
+ optional: true
+ "@rollup/rollup-win32-arm64-msvc":
+ optional: true
+ "@rollup/rollup-win32-ia32-msvc":
+ optional: true
+ "@rollup/rollup-win32-x64-gnu":
+ optional: true
+ "@rollup/rollup-win32-x64-msvc":
+ optional: true
+ fsevents:
+ optional: true
+ bin:
+ rollup: dist/bin/rollup
+ checksum: 10c0/f38742da34cfee5e899302615fa157aa77cb6a2a1495e5e3ce4cc9c540d3262e235bbe60caa31562bbfe492b01fdb3e7a8c43c39d842d3293bcf843123b766fc
+ languageName: node
+ linkType: hard
+
"rollup@npm:^4.34.9":
version: 4.44.0
resolution: "rollup@npm:4.44.0"
@@ -4786,6 +6133,15 @@ __metadata:
languageName: node
linkType: hard
+"saxes@npm:^6.0.0":
+ version: 6.0.0
+ resolution: "saxes@npm:6.0.0"
+ dependencies:
+ xmlchars: "npm:^2.2.0"
+ checksum: 10c0/3847b839f060ef3476eb8623d099aa502ad658f5c40fd60c105ebce86d244389b0d76fcae30f4d0c728d7705ceb2f7e9b34bb54717b6a7dbedaf5dad2d9a4b74
+ languageName: node
+ linkType: hard
+
"scheduler@npm:^0.20.2":
version: 0.20.2
resolution: "scheduler@npm:0.20.2"
@@ -4823,6 +6179,15 @@ __metadata:
languageName: node
linkType: hard
+"semver@npm:^7.5.3":
+ version: 7.7.4
+ resolution: "semver@npm:7.7.4"
+ bin:
+ semver: bin/semver.js
+ checksum: 10c0/5215ad0234e2845d4ea5bb9d836d42b03499546ddafb12075566899fc617f68794bb6f146076b6881d755de17d6c6cc73372555879ec7dce2c2feee947866ad2
+ languageName: node
+ linkType: hard
+
"semver@npm:~7.3.0":
version: 7.3.8
resolution: "semver@npm:7.3.8"
@@ -4892,6 +6257,13 @@ __metadata:
languageName: node
linkType: hard
+"siginfo@npm:^2.0.0":
+ version: 2.0.0
+ resolution: "siginfo@npm:2.0.0"
+ checksum: 10c0/3def8f8e516fbb34cb6ae415b07ccc5d9c018d85b4b8611e3dc6f8be6d1899f693a4382913c9ed51a06babb5201639d76453ab297d1c54a456544acf5c892e34
+ languageName: node
+ linkType: hard
+
"signal-exit@npm:^3.0.0":
version: 3.0.7
resolution: "signal-exit@npm:3.0.7"
@@ -4906,6 +6278,17 @@ __metadata:
languageName: node
linkType: hard
+"sirv@npm:^3.0.0":
+ version: 3.0.2
+ resolution: "sirv@npm:3.0.2"
+ dependencies:
+ "@polka/url": "npm:^1.0.0-next.24"
+ mrmime: "npm:^2.0.0"
+ totalist: "npm:^3.0.0"
+ checksum: 10c0/5930e4397afdb14fbae13751c3be983af4bda5c9aadec832607dc2af15a7162f7d518c71b30e83ae3644b9a24cea041543cc969e5fe2b80af6ce8ea3174b2d04
+ languageName: node
+ linkType: hard
+
"slice-ansi@npm:^5.0.0":
version: 5.0.0
resolution: "slice-ansi@npm:5.0.0"
@@ -4954,7 +6337,7 @@ __metadata:
languageName: node
linkType: hard
-"source-map-js@npm:^1.2.1":
+"source-map-js@npm:^1.2.0, source-map-js@npm:^1.2.1":
version: 1.2.1
resolution: "source-map-js@npm:1.2.1"
checksum: 10c0/7bda1fc4c197e3c6ff17de1b8b2c20e60af81b63a52cb32ec5a5d67a20a7d42651e2cb34ebe93833c5a2a084377e17455854fee3e21e7925c64a51b6a52b0faf
@@ -5015,6 +6398,20 @@ __metadata:
languageName: node
linkType: hard
+"stackback@npm:0.0.2":
+ version: 0.0.2
+ resolution: "stackback@npm:0.0.2"
+ checksum: 10c0/89a1416668f950236dd5ac9f9a6b2588e1b9b62b1b6ad8dff1bfc5d1a15dbf0aafc9b52d2226d00c28dffff212da464eaeebfc6b7578b9d180cef3e3782c5983
+ languageName: node
+ linkType: hard
+
+"std-env@npm:^3.10.0, std-env@npm:^3.8.0":
+ version: 3.10.0
+ resolution: "std-env@npm:3.10.0"
+ checksum: 10c0/1814927a45004d36dde6707eaf17552a546769bc79a6421be2c16ce77d238158dfe5de30910b78ec30d95135cc1c59ea73ee22d2ca170f8b9753f84da34c427f
+ languageName: node
+ linkType: hard
+
"string-argv@npm:^0.3.2, string-argv@npm:~0.3.1":
version: 0.3.2
resolution: "string-argv@npm:0.3.2"
@@ -5104,6 +6501,15 @@ __metadata:
languageName: node
linkType: hard
+"supports-color@npm:^7.1.0":
+ version: 7.2.0
+ resolution: "supports-color@npm:7.2.0"
+ dependencies:
+ has-flag: "npm:^4.0.0"
+ checksum: 10c0/afb4c88521b8b136b5f5f95160c98dee7243dc79d5432db7efc27efb219385bbc7d9427398e43dd6cc730a0f87d5085ce1652af7efbe391327bc0a7d0f7fc124
+ languageName: node
+ linkType: hard
+
"supports-preserve-symlinks-flag@npm:^1.0.0":
version: 1.0.0
resolution: "supports-preserve-symlinks-flag@npm:1.0.0"
@@ -5111,6 +6517,13 @@ __metadata:
languageName: node
linkType: hard
+"symbol-tree@npm:^3.2.4":
+ version: 3.2.4
+ resolution: "symbol-tree@npm:3.2.4"
+ checksum: 10c0/dfbe201ae09ac6053d163578778c53aa860a784147ecf95705de0cd23f42c851e1be7889241495e95c37cabb058edb1052f141387bef68f705afc8f9dd358509
+ languageName: node
+ linkType: hard
+
"tar@npm:^7.4.3":
version: 7.4.3
resolution: "tar@npm:7.4.3"
@@ -5139,6 +6552,17 @@ __metadata:
languageName: node
linkType: hard
+"test-exclude@npm:^7.0.1":
+ version: 7.0.2
+ resolution: "test-exclude@npm:7.0.2"
+ dependencies:
+ "@istanbuljs/schema": "npm:^0.1.2"
+ glob: "npm:^10.4.1"
+ minimatch: "npm:^10.2.2"
+ checksum: 10c0/b79b855af9168c6a362146015ccf40f5e3a25e307304ba9bea930818507f6319d230380d5d7b5baa659c981ccc11f1bd21b6f012f85606353dec07e02dee67c9
+ languageName: node
+ linkType: hard
+
"text-segmentation@npm:^1.0.3":
version: 1.0.3
resolution: "text-segmentation@npm:1.0.3"
@@ -5155,6 +6579,13 @@ __metadata:
languageName: node
linkType: hard
+"tinybench@npm:^2.9.0":
+ version: 2.9.0
+ resolution: "tinybench@npm:2.9.0"
+ checksum: 10c0/c3500b0f60d2eb8db65250afe750b66d51623057ee88720b7f064894a6cb7eb93360ca824a60a31ab16dab30c7b1f06efe0795b352e37914a9d4bad86386a20c
+ languageName: node
+ linkType: hard
+
"tinycolor2@npm:1.4.2":
version: 1.4.2
resolution: "tinycolor2@npm:1.4.2"
@@ -5162,6 +6593,23 @@ __metadata:
languageName: node
linkType: hard
+"tinyexec@npm:^0.3.1":
+ version: 0.3.2
+ resolution: "tinyexec@npm:0.3.2"
+ checksum: 10c0/3efbf791a911be0bf0821eab37a3445c2ba07acc1522b1fa84ae1e55f10425076f1290f680286345ed919549ad67527d07281f1c19d584df3b74326909eb1f90
+ languageName: node
+ linkType: hard
+
+"tinyglobby@npm:^0.2.10":
+ version: 0.2.15
+ resolution: "tinyglobby@npm:0.2.15"
+ dependencies:
+ fdir: "npm:^6.5.0"
+ picomatch: "npm:^4.0.3"
+ checksum: 10c0/869c31490d0d88eedb8305d178d4c75e7463e820df5a9b9d388291daf93e8b1eb5de1dad1c1e139767e4269fe75f3b10d5009b2cc14db96ff98986920a186844
+ languageName: node
+ linkType: hard
+
"tinyglobby@npm:^0.2.12, tinyglobby@npm:^0.2.13":
version: 0.2.14
resolution: "tinyglobby@npm:0.2.14"
@@ -5172,6 +6620,52 @@ __metadata:
languageName: node
linkType: hard
+"tinypool@npm:^1.0.1":
+ version: 1.1.1
+ resolution: "tinypool@npm:1.1.1"
+ checksum: 10c0/bf26727d01443061b04fa863f571016950888ea994ba0cd8cba3a1c51e2458d84574341ab8dbc3664f1c3ab20885c8cf9ff1cc4b18201f04c2cde7d317fff69b
+ languageName: node
+ linkType: hard
+
+"tinyrainbow@npm:^1.2.0":
+ version: 1.2.0
+ resolution: "tinyrainbow@npm:1.2.0"
+ checksum: 10c0/7f78a4b997e5ba0f5ecb75e7ed786f30bab9063716e7dff24dd84013fb338802e43d176cb21ed12480561f5649a82184cf31efb296601a29d38145b1cdb4c192
+ languageName: node
+ linkType: hard
+
+"tinyrainbow@npm:^3.0.3":
+ version: 3.0.3
+ resolution: "tinyrainbow@npm:3.0.3"
+ checksum: 10c0/1e799d35cd23cabe02e22550985a3051dc88814a979be02dc632a159c393a998628eacfc558e4c746b3006606d54b00bcdea0c39301133956d10a27aa27e988c
+ languageName: node
+ linkType: hard
+
+"tinyspy@npm:^3.0.2":
+ version: 3.0.2
+ resolution: "tinyspy@npm:3.0.2"
+ checksum: 10c0/55ffad24e346622b59292e097c2ee30a63919d5acb7ceca87fc0d1c223090089890587b426e20054733f97a58f20af2c349fb7cc193697203868ab7ba00bcea0
+ languageName: node
+ linkType: hard
+
+"tldts-core@npm:^7.0.25":
+ version: 7.0.25
+ resolution: "tldts-core@npm:7.0.25"
+ checksum: 10c0/fd07a555a27a6f11ed87365845d80bf7755d490cd52adf6cd474cbb9148fdaa97c79f58a8ebbc19ccfec0578e76ced5f5ae3b88e85338529291918f5fe815914
+ languageName: node
+ linkType: hard
+
+"tldts@npm:^7.0.5":
+ version: 7.0.25
+ resolution: "tldts@npm:7.0.25"
+ dependencies:
+ tldts-core: "npm:^7.0.25"
+ bin:
+ tldts: bin/cli.js
+ checksum: 10c0/8affd92849a4b0e290c5b211dce58ddee79d5e6d7079fc592afb97d95decd90028194b844c0b3cfb9254de02fb73e01f89500f3e47073dd96a3266223ca221b9
+ languageName: node
+ linkType: hard
+
"to-fast-properties@npm:^2.0.0":
version: 2.0.0
resolution: "to-fast-properties@npm:2.0.0"
@@ -5195,6 +6689,31 @@ __metadata:
languageName: node
linkType: hard
+"totalist@npm:^3.0.0":
+ version: 3.0.1
+ resolution: "totalist@npm:3.0.1"
+ checksum: 10c0/4bb1fadb69c3edbef91c73ebef9d25b33bbf69afe1e37ce544d5f7d13854cda15e47132f3e0dc4cafe300ddb8578c77c50a65004d8b6e97e77934a69aa924863
+ languageName: node
+ linkType: hard
+
+"tough-cookie@npm:^6.0.0":
+ version: 6.0.0
+ resolution: "tough-cookie@npm:6.0.0"
+ dependencies:
+ tldts: "npm:^7.0.5"
+ checksum: 10c0/7b17a461e9c2ac0d0bea13ab57b93b4346d0b8c00db174c963af1e46e4ea8d04148d2a55f2358fc857db0c0c65208a98e319d0c60693e32e0c559a9d9cf20cb5
+ languageName: node
+ linkType: hard
+
+"tr46@npm:^6.0.0":
+ version: 6.0.0
+ resolution: "tr46@npm:6.0.0"
+ dependencies:
+ punycode: "npm:^2.3.1"
+ checksum: 10c0/83130df2f649228aa91c17754b66248030a3af34911d713b5ea417066fa338aa4bc8668d06bd98aa21a2210f43fc0a3db8b9099e7747fb5830e40e39a6a1058e
+ languageName: node
+ linkType: hard
+
"tslib@npm:^1.0.0":
version: 1.14.1
resolution: "tslib@npm:1.14.1"
@@ -5327,6 +6846,13 @@ __metadata:
languageName: node
linkType: hard
+"undici-types@npm:^7.21.0":
+ version: 7.22.0
+ resolution: "undici-types@npm:7.22.0"
+ checksum: 10c0/5e6f2513c41d07404c719eb7c1c499b8d4cde042f1269b3bc2be335f059e6ef53eb04f316696c728d5e8064c4d522b98f63d7ae8938adf6317351e07ae9c943e
+ languageName: node
+ linkType: hard
+
"undici-types@npm:~6.21.0":
version: 6.21.0
resolution: "undici-types@npm:6.21.0"
@@ -5334,6 +6860,20 @@ __metadata:
languageName: node
linkType: hard
+"undici-types@npm:~7.18.0":
+ version: 7.18.2
+ resolution: "undici-types@npm:7.18.2"
+ checksum: 10c0/85a79189113a238959d7a647368e4f7c5559c3a404ebdb8fc4488145ce9426fcd82252a844a302798dfc0e37e6fb178ff481ed03bc4caf634c5757d9ef43521d
+ languageName: node
+ linkType: hard
+
+"undici@npm:^7.21.0":
+ version: 7.22.0
+ resolution: "undici@npm:7.22.0"
+ checksum: 10c0/09777c06f3f18f761f03e3a4c9c04fd9fcca8ad02ccea43602ee4adf73fcba082806f1afb637f6ea714ef6279c5323c25b16d435814c63db720f63bfc20d316b
+ languageName: node
+ linkType: hard
+
"unique-filename@npm:^4.0.0":
version: 4.0.0
resolution: "unique-filename@npm:4.0.0"
@@ -5504,6 +7044,21 @@ __metadata:
languageName: node
linkType: hard
+"vite-node@npm:2.1.9":
+ version: 2.1.9
+ resolution: "vite-node@npm:2.1.9"
+ dependencies:
+ cac: "npm:^6.7.14"
+ debug: "npm:^4.3.7"
+ es-module-lexer: "npm:^1.5.4"
+ pathe: "npm:^1.1.2"
+ vite: "npm:^5.0.0"
+ bin:
+ vite-node: vite-node.mjs
+ checksum: 10c0/0d3589f9f4e9cff696b5b49681fdb75d1638c75053728be52b4013f70792f38cb0120a9c15e3a4b22bdd6b795ad7c2da13bcaf47242d439f0906049e73bdd756
+ languageName: node
+ linkType: hard
+
"vite-plugin-dts@npm:5.0.0-beta.3":
version: 5.0.0-beta.3
resolution: "vite-plugin-dts@npm:5.0.0-beta.3"
@@ -5524,6 +7079,49 @@ __metadata:
languageName: node
linkType: hard
+"vite@npm:^5.0.0":
+ version: 5.4.21
+ resolution: "vite@npm:5.4.21"
+ dependencies:
+ esbuild: "npm:^0.21.3"
+ fsevents: "npm:~2.3.3"
+ postcss: "npm:^8.4.43"
+ rollup: "npm:^4.20.0"
+ peerDependencies:
+ "@types/node": ^18.0.0 || >=20.0.0
+ less: "*"
+ lightningcss: ^1.21.0
+ sass: "*"
+ sass-embedded: "*"
+ stylus: "*"
+ sugarss: "*"
+ terser: ^5.4.0
+ dependenciesMeta:
+ fsevents:
+ optional: true
+ peerDependenciesMeta:
+ "@types/node":
+ optional: true
+ less:
+ optional: true
+ lightningcss:
+ optional: true
+ sass:
+ optional: true
+ sass-embedded:
+ optional: true
+ stylus:
+ optional: true
+ sugarss:
+ optional: true
+ terser:
+ optional: true
+ bin:
+ vite: bin/vite.js
+ checksum: 10c0/468336a1409f728b464160cbf02672e72271fb688d0e605e776b74a89d27e1029509eef3a3a6c755928d8011e474dbf234824d054d07960be5f23cd176bc72de
+ languageName: node
+ linkType: hard
+
"vite@npm:^6.3.5":
version: 6.3.5
resolution: "vite@npm:6.3.5"
@@ -5579,6 +7177,56 @@ __metadata:
languageName: node
linkType: hard
+"vitest@npm:^2.0.0":
+ version: 2.1.9
+ resolution: "vitest@npm:2.1.9"
+ dependencies:
+ "@vitest/expect": "npm:2.1.9"
+ "@vitest/mocker": "npm:2.1.9"
+ "@vitest/pretty-format": "npm:^2.1.9"
+ "@vitest/runner": "npm:2.1.9"
+ "@vitest/snapshot": "npm:2.1.9"
+ "@vitest/spy": "npm:2.1.9"
+ "@vitest/utils": "npm:2.1.9"
+ chai: "npm:^5.1.2"
+ debug: "npm:^4.3.7"
+ expect-type: "npm:^1.1.0"
+ magic-string: "npm:^0.30.12"
+ pathe: "npm:^1.1.2"
+ std-env: "npm:^3.8.0"
+ tinybench: "npm:^2.9.0"
+ tinyexec: "npm:^0.3.1"
+ tinypool: "npm:^1.0.1"
+ tinyrainbow: "npm:^1.2.0"
+ vite: "npm:^5.0.0"
+ vite-node: "npm:2.1.9"
+ why-is-node-running: "npm:^2.3.0"
+ peerDependencies:
+ "@edge-runtime/vm": "*"
+ "@types/node": ^18.0.0 || >=20.0.0
+ "@vitest/browser": 2.1.9
+ "@vitest/ui": 2.1.9
+ happy-dom: "*"
+ jsdom: "*"
+ peerDependenciesMeta:
+ "@edge-runtime/vm":
+ optional: true
+ "@types/node":
+ optional: true
+ "@vitest/browser":
+ optional: true
+ "@vitest/ui":
+ optional: true
+ happy-dom:
+ optional: true
+ jsdom:
+ optional: true
+ bin:
+ vitest: vitest.mjs
+ checksum: 10c0/e339e16dccacf4589ff43cb1f38c7b4d14427956ae8ef48702af6820a9842347c2b6c77356aeddb040329759ca508a3cb2b104ddf78103ea5bc98ab8f2c3a54e
+ languageName: node
+ linkType: hard
+
"vscode-uri@npm:^3.0.8":
version: 3.1.0
resolution: "vscode-uri@npm:3.1.0"
@@ -5586,6 +7234,15 @@ __metadata:
languageName: node
linkType: hard
+"w3c-xmlserializer@npm:^5.0.0":
+ version: 5.0.0
+ resolution: "w3c-xmlserializer@npm:5.0.0"
+ dependencies:
+ xml-name-validator: "npm:^5.0.0"
+ checksum: 10c0/8712774c1aeb62dec22928bf1cdfd11426c2c9383a1a63f2bcae18db87ca574165a0fbe96b312b73652149167ac6c7f4cf5409f2eb101d9c805efe0e4bae798b
+ languageName: node
+ linkType: hard
+
"warning@npm:^4.0.3":
version: 4.0.3
resolution: "warning@npm:4.0.3"
@@ -5595,6 +7252,13 @@ __metadata:
languageName: node
linkType: hard
+"webidl-conversions@npm:^8.0.1":
+ version: 8.0.1
+ resolution: "webidl-conversions@npm:8.0.1"
+ checksum: 10c0/3f6f327ca5fa0c065ed8ed0ef3b72f33623376e68f958e9b7bd0df49fdb0b908139ac2338d19fb45bd0e05595bda96cb6d1622222a8b413daa38a17aacc4dd46
+ languageName: node
+ linkType: hard
+
"webpack-virtual-modules@npm:^0.6.2":
version: 0.6.2
resolution: "webpack-virtual-modules@npm:0.6.2"
@@ -5602,6 +7266,24 @@ __metadata:
languageName: node
linkType: hard
+"whatwg-mimetype@npm:^5.0.0":
+ version: 5.0.0
+ resolution: "whatwg-mimetype@npm:5.0.0"
+ checksum: 10c0/eead164fe73a00dd82f817af6fc0bd22e9c273e1d55bf4bc6bdf2da7ad8127fca82ef00ea6a37892f5f5641f8e34128e09508f92126086baba126b9e0d57feb4
+ languageName: node
+ linkType: hard
+
+"whatwg-url@npm:^16.0.0":
+ version: 16.0.1
+ resolution: "whatwg-url@npm:16.0.1"
+ dependencies:
+ "@exodus/bytes": "npm:^1.11.0"
+ tr46: "npm:^6.0.0"
+ webidl-conversions: "npm:^8.0.1"
+ checksum: 10c0/e75565566abf3a2cdbd9f06c965dbcccee6ec4e9f0d3728ad5e08ceb9944279848bcaa211d35a29cb6d2df1e467dd05cfb59fbddf8a0adcd7d0bce9ffb703fd2
+ languageName: node
+ linkType: hard
+
"which@npm:^1.2.9":
version: 1.3.1
resolution: "which@npm:1.3.1"
@@ -5635,6 +7317,18 @@ __metadata:
languageName: node
linkType: hard
+"why-is-node-running@npm:^2.3.0":
+ version: 2.3.0
+ resolution: "why-is-node-running@npm:2.3.0"
+ dependencies:
+ siginfo: "npm:^2.0.0"
+ stackback: "npm:0.0.2"
+ bin:
+ why-is-node-running: cli.js
+ checksum: 10c0/1cde0b01b827d2cf4cb11db962f3958b9175d5d9e7ac7361d1a7b0e2dc6069a263e69118bd974c4f6d0a890ef4eedfe34cf3d5167ec14203dbc9a18620537054
+ languageName: node
+ linkType: hard
+
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0, wrap-ansi@npm:^7.0.0":
version: 7.0.0
resolution: "wrap-ansi@npm:7.0.0"
@@ -5675,6 +7369,20 @@ __metadata:
languageName: node
linkType: hard
+"xml-name-validator@npm:^5.0.0":
+ version: 5.0.0
+ resolution: "xml-name-validator@npm:5.0.0"
+ checksum: 10c0/3fcf44e7b73fb18be917fdd4ccffff3639373c7cb83f8fc35df6001fecba7942f1dbead29d91ebb8315e2f2ff786b508f0c9dc0215b6353f9983c6b7d62cb1f5
+ languageName: node
+ linkType: hard
+
+"xmlchars@npm:^2.2.0":
+ version: 2.2.0
+ resolution: "xmlchars@npm:2.2.0"
+ checksum: 10c0/b64b535861a6f310c5d9bfa10834cf49127c71922c297da9d4d1b45eeaae40bf9b4363275876088fbe2667e5db028d2cd4f8ee72eed9bede840a67d57dab7593
+ languageName: node
+ linkType: hard
+
"y18n@npm:^5.0.5":
version: 5.0.8
resolution: "y18n@npm:5.0.8"
From 93f2cb24412852c03f23b3188005eec4185ce2d6 Mon Sep 17 00:00:00 2001
From: Manu Chaudhary
Date: Tue, 10 Mar 2026 15:25:31 +0530
Subject: [PATCH 2/8] lint fix
---
examples/react-example/src/index.tsx | 70 +++++++++++++------
.../src/ImageKitEditor.tsx | 8 +--
.../src/backward-compatibility.test.ts | 57 +++++++++++----
packages/imagekit-editor-dev/src/store.ts | 6 +-
4 files changed, 101 insertions(+), 40 deletions(-)
diff --git a/examples/react-example/src/index.tsx b/examples/react-example/src/index.tsx
index c68b0a5..a1b3bef 100644
--- a/examples/react-example/src/index.tsx
+++ b/examples/react-example/src/index.tsx
@@ -3,8 +3,8 @@ import {
ImageKitEditor,
type ImageKitEditorProps,
type ImageKitEditorRef,
- type Transformation,
TRANSFORMATION_STATE_VERSION,
+ type Transformation,
} from "@imagekit/editor"
import { PiDownload } from "@react-icons/all-files/pi/PiDownload"
import React, { useCallback, useEffect } from "react"
@@ -50,12 +50,16 @@ function App() {
const template = ref.current?.getTemplate()
if (template) {
// Remove the 'id' field from each transformation for storage
- const templateToSave = template.map(({ id, ...rest }: Transformation) => rest)
+ const templateToSave = template.map(
+ ({ id, ...rest }: Transformation) => rest,
+ )
setSavedTemplate(templateToSave)
// Also save to localStorage for persistence
localStorage.setItem("editorTemplate", JSON.stringify(templateToSave))
console.log("Saved template:", templateToSave)
- alert(`✅ Saved template with ${templateToSave.length} transformation(s)!`)
+ alert(
+ `✅ Saved template with ${templateToSave.length} transformation(s)!`,
+ )
} else {
alert("⚠️ No transformations to save")
}
@@ -176,7 +180,8 @@ function App() {
console.log("Signed URL", request.url)
return Promise.resolve(request.url)
},
- }) }, [handleAddImage, handleSaveTemplate])
+ })
+ }, [handleAddImage, handleSaveTemplate])
const toggle = () => {
setOpen((prev: boolean) => !prev)
@@ -213,12 +218,14 @@ function App() {
padding: "10px 20px",
fontSize: "16px",
marginRight: "10px",
- cursor: savedTemplate || localStorage.getItem("editorTemplate")
- ? "pointer"
- : "not-allowed",
- opacity: savedTemplate || localStorage.getItem("editorTemplate")
- ? 1
- : 0.5,
+ cursor:
+ savedTemplate || localStorage.getItem("editorTemplate")
+ ? "pointer"
+ : "not-allowed",
+ opacity:
+ savedTemplate || localStorage.getItem("editorTemplate")
+ ? 1
+ : 0.5,
}}
>
Load Saved Template
@@ -264,12 +271,18 @@ function App() {
Types: {" "}
- {Array.from(
- new Set(savedTemplate.map((t) => t.type))
- ).join(", ")}
+ {Array.from(new Set(savedTemplate.map((t) => t.type))).join(
+ ", ",
+ )}
-
+
📋 View Template JSON
📖 How to use Template Features:
Click "Open ImageKit Editor" and apply some transformations
- Click the "Save Template" button in the editor header
+
+ Click the "Save Template" button in the editor
+ header
+
Close the editor
- Click "Load Saved Template" - it will open the editor with all transformations restored
+ Click "Load Saved Template" - it will open the
+ editor with all transformations restored
+
+
+ Use "Clear Template" to remove the saved template
- Use "Clear Template" to remove the saved template
-
- 💾 Persistent Storage: Templates are saved to localStorage, so they persist across page reloads!
+
+ 💾 Persistent Storage: Templates are saved to
+ localStorage, so they persist across page reloads!
- Note: Template IDs are automatically generated on load to ensure uniqueness and enable reusability.
+ Note: Template IDs are automatically generated on
+ load to ensure uniqueness and enable reusability.
diff --git a/packages/imagekit-editor-dev/src/ImageKitEditor.tsx b/packages/imagekit-editor-dev/src/ImageKitEditor.tsx
index d9bdf1b..89689e7 100644
--- a/packages/imagekit-editor-dev/src/ImageKitEditor.tsx
+++ b/packages/imagekit-editor-dev/src/ImageKitEditor.tsx
@@ -20,19 +20,19 @@ export interface ImageKitEditorRef {
* @param image - Image URL string or FileElement with metadata
*/
loadImage: (image: string | InputFileElement) => void
-
+
/**
* Loads multiple images into the editor
* @param images - Array of image URL strings or FileElements with metadata
*/
loadImages: (images: Array) => void
-
+
/**
* Switches the current active image
* @param imageSrc - URL of the image to set as current
*/
setCurrentImage: (imageSrc: string) => void
-
+
/**
* Gets the current editor template (transformation stack)
* @returns Array of transformation objects representing the template
@@ -46,7 +46,7 @@ export interface ImageKitEditorRef {
* ```
*/
getTemplate: () => Transformation[]
-
+
/**
* Loads a template (transformation stack) into the editor
* @param template - Array of transformation objects without the 'id' field
diff --git a/packages/imagekit-editor-dev/src/backward-compatibility.test.ts b/packages/imagekit-editor-dev/src/backward-compatibility.test.ts
index 4a644c0..8e1fbc2 100644
--- a/packages/imagekit-editor-dev/src/backward-compatibility.test.ts
+++ b/packages/imagekit-editor-dev/src/backward-compatibility.test.ts
@@ -1,7 +1,7 @@
import { describe, expect, it } from "vitest"
+import { transformationFormatters, transformationSchema } from "./schema"
import type { Transformation } from "./store"
import { TRANSFORMATION_STATE_VERSION } from "./store"
-import { transformationFormatters, transformationSchema } from "./schema"
/**
* V1 Template Fixtures
@@ -313,7 +313,7 @@ function validateTransformation(t: Omit): {
if (!result.success) {
result.error.errors.forEach((err) => {
errors.push(
- `Schema validation failed for '${err.path.join(".")}': ${err.message}`
+ `Schema validation failed for '${err.path.join(".")}': ${err.message}`,
)
})
}
@@ -611,7 +611,7 @@ describe("Backward Compatibility - V1 Templates", () => {
}
// Remove id for storage
- const { id, ...forStorage } = withId
+ const { id: _id, ...forStorage } = withId
expect(forStorage.id).toBeUndefined()
// Add id back when loading
@@ -662,11 +662,13 @@ describe("Backward Compatibility - V1 Templates", () => {
const result = validateTransformation(invalid)
expect(result.valid).toBe(false)
expect(result.errors.length).toBeGreaterThan(0)
- expect(result.errors.some((e) => e.includes("not found in current schema"))).toBe(true)
+ expect(
+ result.errors.some((e) => e.includes("not found in current schema")),
+ ).toBe(true)
})
it("should reject transformation with wrong type", () => {
- const invalid: any = {
+ const invalid: Record = {
key: "adjust-background",
name: "Background",
type: "wrong-type",
@@ -685,7 +687,7 @@ describe("Backward Compatibility - V1 Templates", () => {
name: "Corner Radius",
type: "transformation",
value: {
- radius: 999, // Should be an object with {radius: number}
+ radius: 999, // Should be an object with {radius: number}
},
version: "v1",
}
@@ -933,7 +935,12 @@ describe("Backward Compatibility - V1 Templates", () => {
key: "layers-text",
name: "Text",
type: "transformation",
- value: { text: "Hello", positionX: "bw_div_2", fontSize: 24, radius: 0 },
+ value: {
+ text: "Hello",
+ positionX: "bw_div_2",
+ fontSize: 24,
+ radius: 0,
+ },
version: "v1",
}
expect(validateTransformation(template).valid).toBe(true)
@@ -944,7 +951,12 @@ describe("Backward Compatibility - V1 Templates", () => {
key: "layers-text",
name: "Text",
type: "transformation",
- value: { text: "Hello", positionY: "bh_sub_100", fontSize: 24, radius: 0 },
+ value: {
+ text: "Hello",
+ positionY: "bh_sub_100",
+ fontSize: 24,
+ radius: 0,
+ },
version: "v1",
}
expect(validateTransformation(template).valid).toBe(true)
@@ -1227,7 +1239,7 @@ describe("Backward Compatibility - V1 Templates", () => {
unsharpenMaskAmount: 1.2,
unsharpenMaskThreshold: 0.1,
},
- version: "v1",
+ version: "v1",
}
const result = validateTransformation(template)
if (!result.valid) {
@@ -1561,7 +1573,12 @@ describe("Backward Compatibility - V1 Templates", () => {
key: "layers-text",
name: "Text",
type: "transformation",
- value: { text: "Hello", positionX: "invalid_expr", fontSize: 24, radius: 0 },
+ value: {
+ text: "Hello",
+ positionX: "invalid_expr",
+ fontSize: 24,
+ radius: 0,
+ },
version: "v1",
}
expect(validateTransformation(template).valid).toBe(false)
@@ -1605,7 +1622,12 @@ describe("Backward Compatibility - V1 Templates", () => {
key: "layers-text",
name: "Text",
type: "transformation",
- value: { text: "Hello", lineHeight: "ih_mul_1.5", fontSize: 24, radius: 0 },
+ value: {
+ text: "Hello",
+ lineHeight: "ih_mul_1.5",
+ fontSize: 24,
+ radius: 0,
+ },
version: "v1",
}
expect(validateTransformation(template).valid).toBe(true)
@@ -1627,7 +1649,12 @@ describe("Backward Compatibility - V1 Templates", () => {
key: "layers-text",
name: "Text",
type: "transformation",
- value: { text: "Hello", lineHeight: "not_valid", fontSize: 24, radius: 0 },
+ value: {
+ text: "Hello",
+ lineHeight: "not_valid",
+ fontSize: 24,
+ radius: 0,
+ },
version: "v1",
}
expect(validateTransformation(template).valid).toBe(false)
@@ -2138,7 +2165,11 @@ describe("Backward Compatibility - V1 Templates", () => {
})
it("should validate text layer with all alignment options", () => {
- const alignments: Array<"left" | "right" | "center"> = ["left", "right", "center"]
+ const alignments: Array<"left" | "right" | "center"> = [
+ "left",
+ "right",
+ "center",
+ ]
alignments.forEach((align) => {
const template: Omit = {
key: "layers-text",
diff --git a/packages/imagekit-editor-dev/src/store.ts b/packages/imagekit-editor-dev/src/store.ts
index e2c1628..2220a89 100644
--- a/packages/imagekit-editor-dev/src/store.ts
+++ b/packages/imagekit-editor-dev/src/store.ts
@@ -311,13 +311,13 @@ const useEditorStore = create()(
id: `transformation-${Date.now()}-${index}`,
version: TRANSFORMATION_STATE_VERSION,
}))
-
+
const visibleTransformations: Record = {}
transformationsWithIds.forEach((t) => {
visibleTransformations[t.id] = true
})
-
- set((state) => ({
+
+ set((state) => ({
transformations: transformationsWithIds,
visibleTransformations: {
...state.visibleTransformations,
From 9f91d943fa2d59bd02d197ed236540e9e90b5bde Mon Sep 17 00:00:00 2001
From: Manu Chaudhary
Date: Tue, 10 Mar 2026 15:31:34 +0530
Subject: [PATCH 3/8] feat: add test script to package.json
---
package.json | 1 +
1 file changed, 1 insertion(+)
diff --git a/package.json b/package.json
index 9c4303c..5046f3a 100644
--- a/package.json
+++ b/package.json
@@ -16,6 +16,7 @@
"dev": "turbo run dev",
"start": "turbo run start",
"build": "turbo run build",
+ "test": "turbo run test",
"version": "yarn workspace @imagekit/editor version",
"package": "yarn build && shx cp README.md ./packages/imagekit-editor/ && yarn workspace @imagekit/editor pack --out ../../builds/imagekit-editor-%v.tgz",
"release": "yarn build && shx cp README.md ./packages/imagekit-editor/ && yarn workspace @imagekit/editor pack --out ../../builds/imagekit-editor-%v.tgz && yarn workspace @imagekit/editor publish",
From b6b2bdd2164addd2089c684b35b0d444e4e4502d Mon Sep 17 00:00:00 2001
From: Manu Chaudhary
Date: Tue, 10 Mar 2026 16:59:13 +0530
Subject: [PATCH 4/8] test: add coverage tests for background and resize/crop
field visibility logic
---
.../src/backward-compatibility.test.ts | 445 ++++++++++++++++
.../src/schema/field-config.test.ts | 496 ++++++++++++++++++
packages/imagekit-editor-dev/vite.config.ts | 10 +-
3 files changed, 949 insertions(+), 2 deletions(-)
create mode 100644 packages/imagekit-editor-dev/src/schema/field-config.test.ts
diff --git a/packages/imagekit-editor-dev/src/backward-compatibility.test.ts b/packages/imagekit-editor-dev/src/backward-compatibility.test.ts
index 8e1fbc2..62799e9 100644
--- a/packages/imagekit-editor-dev/src/backward-compatibility.test.ts
+++ b/packages/imagekit-editor-dev/src/backward-compatibility.test.ts
@@ -2577,4 +2577,449 @@ describe("Backward Compatibility - V1 Templates", () => {
expect(validateTransformation(template).valid).toBe(false)
})
})
+
+ describe("Height Validator Coverage", () => {
+ it("should reject invalid height expression", () => {
+ const template: Omit = {
+ key: "resize_and_crop-resize_and_crop",
+ name: "Resize and Crop",
+ type: "transformation",
+ value: {
+ width: 100,
+ height: "invalid_height_expr",
+ mode: "cm-pad_resize",
+ },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(false)
+ })
+ })
+
+
+
+ describe("Unsharpen Mask Error Coverage", () => {
+ it("should require sigma when unsharpen mask is enabled", () => {
+ const template: Omit = {
+ key: "layers-image",
+ name: "Image Layer",
+ type: "transformation",
+ value: {
+ imageUrl: "overlay.png",
+ unsharpenMask: true,
+ unsharpenMaskRadius: 2,
+ // Missing sigma and other required fields
+ },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(false)
+ })
+
+ it("should require amount when unsharpen mask is enabled", () => {
+ const template: Omit = {
+ key: "layers-image",
+ name: "Image Layer",
+ type: "transformation",
+ value: {
+ imageUrl: "overlay.png",
+ unsharpenMask: true,
+ unsharpenMaskRadius: 2,
+ unsharpenMaskSigma: 1.5,
+ // Missing amount
+ },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(false)
+ })
+
+ it("should require threshold when unsharpen mask is enabled", () => {
+ const template: Omit = {
+ key: "layers-image",
+ name: "Image Layer",
+ type: "transformation",
+ value: {
+ imageUrl: "overlay.png",
+ unsharpenMask: true,
+ unsharpenMaskRadius: 2,
+ unsharpenMaskSigma: 1.5,
+ unsharpenMaskAmount: 1.2,
+ // Missing threshold
+ },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(false)
+ })
+ })
+
+ describe("Background Gradient Auto Coverage", () => {
+ it("should validate background gradient with radial mode", () => {
+ const template: Omit = {
+ key: "adjust-background",
+ name: "Background",
+ type: "transformation",
+ value: {
+ backgroundType: "gradient",
+ backgroundGradientAutoDominant: true,
+ backgroundGradientMode: "radial",
+ backgroundGradientPaletteSize: "2",
+ },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(true)
+ })
+
+ it("should validate background gradient with linear mode", () => {
+ const template: Omit = {
+ key: "adjust-background",
+ name: "Background",
+ type: "transformation",
+ value: {
+ backgroundType: "gradient",
+ backgroundGradientAutoDominant: true,
+ backgroundGradientMode: "linear",
+ backgroundGradientPaletteSize: "4",
+ },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(true)
+ })
+
+ it("should validate manual background gradient", () => {
+ const template: Omit = {
+ key: "adjust-background",
+ name: "Background",
+ type: "transformation",
+ value: {
+ backgroundType: "gradient",
+ backgroundGradientAutoDominant: false,
+ backgroundGradient: {
+ type: "linear",
+ angle: "90",
+ stops: [
+ { color: "#FF0000", stopPoint: 0 },
+ { color: "#0000FF", stopPoint: 100 },
+ ],
+ },
+ },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(true)
+ })
+ })
+
+ describe("Resize Mode Conversion Coverage", () => {
+ it("should validate c-at_max_enlarge mode", () => {
+ const template: Omit = {
+ key: "resize_and_crop-resize_and_crop",
+ name: "Resize and Crop",
+ type: "transformation",
+ value: {
+ width: 800,
+ height: 600,
+ mode: "c-at_max_enlarge",
+ },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(true)
+ })
+
+ it("should validate c-force mode", () => {
+ const template: Omit = {
+ key: "resize_and_crop-resize_and_crop",
+ name: "Resize and Crop",
+ type: "transformation",
+ value: {
+ width: 800,
+ height: 600,
+ mode: "c-force",
+ },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(true)
+ })
+
+ it("should validate c-at_max mode", () => {
+ const template: Omit = {
+ key: "resize_and_crop-resize_and_crop",
+ name: "Resize and Crop",
+ type: "transformation",
+ value: {
+ width: 800,
+ mode: "c-at_max",
+ },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(true)
+ })
+ })
+
+
+
+
+
+ describe("Maintain Ratio Focus Validations", () => {
+ it("should validate maintain_ratio with anchor focus", () => {
+ const template: Omit = {
+ key: "resize_and_crop-resize_and_crop",
+ name: "Resize and Crop",
+ type: "transformation",
+ value: {
+ width: 800,
+ mode: "c-maintain_ratio",
+ focus: "anchor",
+ focusAnchor: "center",
+ },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(true)
+ })
+
+ it("should require focusAnchor for maintain_ratio with anchor focus", () => {
+ const template: Omit = {
+ key: "resize_and_crop-resize_and_crop",
+ name: "Resize and Crop",
+ type: "transformation",
+ value: {
+ width: 800,
+ mode: "c-maintain_ratio",
+ focus: "anchor",
+ // Missing focusAnchor
+ },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(false)
+ })
+
+ it("should validate maintain_ratio with object focus", () => {
+ const template: Omit = {
+ key: "resize_and_crop-resize_and_crop",
+ name: "Resize and Crop",
+ type: "transformation",
+ value: {
+ width: 800,
+ mode: "c-maintain_ratio",
+ focus: "object",
+ focusObject: "person",
+ },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(true)
+ })
+
+ it("should require focusObject for maintain_ratio with object focus", () => {
+ const template: Omit = {
+ key: "resize_and_crop-resize_and_crop",
+ name: "Resize and Crop",
+ type: "transformation",
+ value: {
+ width: 800,
+ mode: "c-maintain_ratio",
+ focus: "object",
+ // Missing focusObject
+ },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(false)
+ })
+ })
+
+ describe("Pad Resize Background Validation Errors", () => {
+ it("should require width when using blurred background", () => {
+ const template: Omit = {
+ key: "resize_and_crop-resize_and_crop",
+ name: "Resize and Crop",
+ type: "transformation",
+ value: {
+ // width missing
+ height: 600,
+ mode: "cm-pad_resize",
+ backgroundType: "blurred",
+ backgroundBlurIntensity: 10,
+ },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(false)
+ })
+
+ it("should require height when using blurred background", () => {
+ const template: Omit = {
+ key: "resize_and_crop-resize_and_crop",
+ name: "Resize and Crop",
+ type: "transformation",
+ value: {
+ width: 800,
+ // height missing
+ mode: "cm-pad_resize",
+ backgroundType: "blurred",
+ backgroundBlurIntensity: 10,
+ },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(false)
+ })
+
+ it("should require width when using generative fill", () => {
+ const template: Omit = {
+ key: "resize_and_crop-resize_and_crop",
+ name: "Resize and Crop",
+ type: "transformation",
+ value: {
+ // width missing
+ height: 600,
+ mode: "cm-pad_resize",
+ backgroundType: "generative_fill",
+ },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(false)
+ })
+
+ it("should require height when using generative fill", () => {
+ const template: Omit = {
+ key: "resize_and_crop-resize_and_crop",
+ name: "Resize and Crop",
+ type: "transformation",
+ value: {
+ width: 800,
+ // height missing
+ mode: "cm-pad_resize",
+ backgroundType: "generative_fill",
+ },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(false)
+ })
+
+ it("should pass validation with both dimensions for blurred background", () => {
+ const template: Omit = {
+ key: "resize_and_crop-resize_and_crop",
+ name: "Resize and Crop",
+ type: "transformation",
+ value: {
+ width: 800,
+ height: 600,
+ mode: "cm-pad_resize",
+ backgroundType: "blurred",
+ backgroundBlurIntensity: 10,
+ },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(true)
+ })
+
+ it("should pass validation with both dimensions for generative fill", () => {
+ const template: Omit = {
+ key: "resize_and_crop-resize_and_crop",
+ name: "Resize and Crop",
+ type: "transformation",
+ value: {
+ width: 800,
+ height: 600,
+ mode: "cm-pad_resize",
+ backgroundType: "generative_fill",
+ },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(true)
+ })
+ })
+
+ describe("Final Coverage Gaps - Missing Validations", () => {
+ it("should reject aspect ratio without width or height", () => {
+ const template: Omit = {
+ key: "resize_and_crop-resize_and_crop",
+ name: "Resize and Crop",
+ type: "transformation",
+ value: {
+ aspectRatio: "16-9",
+ // No width or height
+ },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(false)
+ })
+
+ it("should accept aspect ratio with width", () => {
+ const template: Omit = {
+ key: "resize_and_crop-resize_and_crop",
+ name: "Resize and Crop",
+ type: "transformation",
+ value: {
+ width: 800,
+ aspectRatio: "16-9",
+ },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(true)
+ })
+
+ it("should require at least one center coordinate for cm-extract with coordinates", () => {
+ const template: Omit = {
+ key: "resize_and_crop-resize_and_crop",
+ name: "Resize and Crop",
+ type: "transformation",
+ value: {
+ width: 800,
+ height: 600,
+ mode: "cm-extract",
+ focus: "coordinates",
+ coordinateMethod: "center",
+ // Missing both xc and yc
+ },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(false)
+ })
+
+ it("should accept center coordinates with at least xc for cm-extract", () => {
+ const template: Omit = {
+ key: "resize_and_crop-resize_and_crop",
+ name: "Resize and Crop",
+ type: "transformation",
+ value: {
+ width: 800,
+ height: 600,
+ mode: "cm-extract",
+ focus: "coordinates",
+ coordinateMethod: "center",
+ xc: "400",
+ },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(true)
+ })
+
+ it("should reject unsharpen mask with threshold = 0 as invalid", () => {
+ const template: Omit = {
+ key: "adjust-unsharpen-mask",
+ name: "Unsharpen Mask",
+ type: "transformation",
+ value: {
+ unsharpenMask: true,
+ unsharpenMaskRadius: 2,
+ unsharpenMaskSigma: 1,
+ unsharpenMaskAmount: 0.5,
+ unsharpenMaskThreshold: 0, // Falsy value
+ },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(false)
+ })
+
+ it("should accept unsharpen mask with valid positive threshold", () => {
+ const template: Omit = {
+ key: "adjust-unsharpen-mask",
+ name: "Unsharpen Mask",
+ type: "transformation",
+ value: {
+ unsharpenMask: true,
+ unsharpenMaskRadius: 2,
+ unsharpenMaskSigma: 1,
+ unsharpenMaskAmount: 0.5,
+ unsharpenMaskThreshold: 0.05,
+ },
+ version: "v1",
+ }
+ expect(validateTransformation(template).valid).toBe(true)
+ })
+ })
})
diff --git a/packages/imagekit-editor-dev/src/schema/field-config.test.ts b/packages/imagekit-editor-dev/src/schema/field-config.test.ts
new file mode 100644
index 0000000..4d7748e
--- /dev/null
+++ b/packages/imagekit-editor-dev/src/schema/field-config.test.ts
@@ -0,0 +1,496 @@
+import { describe, expect, it } from "vitest"
+import { backgroundTransformations } from "./background"
+import {
+ getDefaultTransformationFromMode,
+ resizeAndCropTransformations,
+} from "./resizeAndCrop"
+
+describe("Field Configuration Tests", () => {
+ describe("Background Fields - Visibility Logic", () => {
+ describe("background field (color picker)", () => {
+ it("should be visible for root_image when type is color and auto is off", () => {
+ const field = backgroundTransformations.background({
+ transformationGroup: "background",
+ context: "root_image",
+ })
+
+ const visible = field.isVisible?.({
+ backgroundType: "color",
+ backgroundDominantAuto: false,
+ })
+
+ expect(visible).toBe(true)
+ })
+
+ it("should be hidden for root_image when auto dominant is enabled", () => {
+ const field = backgroundTransformations.background({
+ transformationGroup: "background",
+ context: "root_image",
+ })
+
+ const visible = field.isVisible?.({
+ backgroundType: "color",
+ backgroundDominantAuto: true,
+ })
+
+ expect(visible).toBe(false)
+ })
+
+ it("should be visible for pad_resize when type is color and auto is off", () => {
+ const field = backgroundTransformations.background({
+ transformationGroup: "background",
+ context: "pad_resize",
+ })
+
+ const visible = field.isVisible?.({
+ backgroundType: "color",
+ backgroundDominantAuto: false,
+ })
+
+ expect(visible).toBe(true)
+ })
+
+ it("should be visible for pad_extract when type is color and auto is off", () => {
+ const field = backgroundTransformations.background({
+ transformationGroup: "background",
+ context: "pad_extract",
+ })
+
+ const visible = field.isVisible?.({
+ backgroundType: "color",
+ backgroundDominantAuto: false,
+ })
+
+ expect(visible).toBe(true)
+ })
+ })
+
+ describe("backgroundGradientMode field", () => {
+ it("should be visible when type is gradient and auto dominant is true", () => {
+ const field = backgroundTransformations.backgroundGradientMode({
+ transformationGroup: "background",
+ context: "root_image",
+ })
+
+ const visible = field.isVisible?.({
+ backgroundType: "gradient",
+ backgroundGradientAutoDominant: true,
+ })
+
+ expect(visible).toBe(true)
+ })
+
+ it("should be hidden when auto dominant is false", () => {
+ const field = backgroundTransformations.backgroundGradientMode({
+ transformationGroup: "background",
+ context: "root_image",
+ })
+
+ const visible = field.isVisible?.({
+ backgroundType: "gradient",
+ backgroundGradientAutoDominant: false,
+ })
+
+ expect(visible).toBe(false)
+ })
+ })
+
+ describe("backgroundGradientPaletteSize field", () => {
+ it("should be visible when type is gradient and auto dominant is true", () => {
+ const field = backgroundTransformations.backgroundGradientPaletteSize({
+ transformationGroup: "background",
+ context: "root_image",
+ })
+
+ const visible = field.isVisible?.({
+ backgroundType: "gradient",
+ backgroundGradientAutoDominant: true,
+ })
+
+ expect(visible).toBe(true)
+ })
+
+ it("should be hidden when background type is not gradient", () => {
+ const field = backgroundTransformations.backgroundGradientPaletteSize({
+ transformationGroup: "background",
+ context: "root_image",
+ })
+
+ const visible = field.isVisible?.({
+ backgroundType: "color",
+ backgroundGradientAutoDominant: true,
+ })
+
+ expect(visible).toBe(false)
+ })
+ })
+
+ describe("backgroundGradient field (manual gradient)", () => {
+ it("should be visible when type is gradient and auto dominant is false", () => {
+ const field = backgroundTransformations.backgroundGradient({
+ transformationGroup: "background",
+ context: "root_image",
+ })
+
+ const visible = field.isVisible?.({
+ backgroundType: "gradient",
+ backgroundGradientAutoDominant: false,
+ })
+
+ expect(visible).toBe(true)
+ })
+
+ it("should be hidden when auto dominant is true", () => {
+ const field = backgroundTransformations.backgroundGradient({
+ transformationGroup: "background",
+ context: "root_image",
+ })
+
+ const visible = field.isVisible?.({
+ backgroundType: "gradient",
+ backgroundGradientAutoDominant: true,
+ })
+
+ expect(visible).toBe(false)
+ })
+ })
+ })
+
+ describe("Resize and Crop Fields - Visibility and Helpers", () => {
+ describe("coordinate field visibility", () => {
+ it("should show x field for topleft coordinates in extract mode", () => {
+ const xField = resizeAndCropTransformations.find((f) => f.name === "x")
+
+ const visible = xField?.isVisible?.({
+ width: 100,
+ height: 100,
+ mode: "cm-extract",
+ focus: "coordinates",
+ coordinateMethod: "topleft",
+ })
+
+ expect(visible).toBe(true)
+ })
+
+ it("should hide x field when coordinate method is not topleft", () => {
+ const xField = resizeAndCropTransformations.find((f) => f.name === "x")
+
+ const visible = xField?.isVisible?.({
+ width: 100,
+ height: 100,
+ mode: "cm-extract",
+ focus: "coordinates",
+ coordinateMethod: "center",
+ })
+
+ expect(visible).toBe(false)
+ })
+
+ it("should show y field for topleft coordinates in extract mode", () => {
+ const yField = resizeAndCropTransformations.find((f) => f.name === "y")
+
+ const visible = yField?.isVisible?.({
+ width: 100,
+ height: 100,
+ mode: "cm-extract",
+ focus: "coordinates",
+ coordinateMethod: "topleft",
+ })
+
+ expect(visible).toBe(true)
+ })
+
+ it("should show xc field for center coordinates in extract mode", () => {
+ const xcField = resizeAndCropTransformations.find((f) => f.name === "xc")
+
+ const visible = xcField?.isVisible?.({
+ width: 100,
+ height: 100,
+ mode: "cm-extract",
+ focus: "coordinates",
+ coordinateMethod: "center",
+ })
+
+ expect(visible).toBe(true)
+ })
+
+ it("should show yc field for center coordinates in extract mode", () => {
+ const ycField = resizeAndCropTransformations.find((f) => f.name === "yc")
+
+ const visible = ycField?.isVisible?.({
+ width: 100,
+ height: 100,
+ mode: "cm-extract",
+ focus: "coordinates",
+ coordinateMethod: "center",
+ })
+
+ expect(visible).toBe(true)
+ })
+ })
+
+ describe("focus field visibility", () => {
+ it("should show focusAnchor when focus is anchor", () => {
+ const focusAnchorField = resizeAndCropTransformations.find(
+ (f) => f.name === "focusAnchor",
+ )
+
+ const visible = focusAnchorField?.isVisible?.({
+ width: 100,
+ height: 100,
+ mode: "cm-extract",
+ focus: "anchor",
+ })
+
+ expect(visible).toBe(true)
+ })
+
+ it("should show focusObject when focus is object", () => {
+ const focusObjectField = resizeAndCropTransformations.find(
+ (f) => f.name === "focusObject",
+ )
+
+ const visible = focusObjectField?.isVisible?.({
+ width: 100,
+ height: 100,
+ mode: "cm-extract",
+ focus: "object",
+ })
+
+ expect(visible).toBe(true)
+ })
+
+ it("should show coordinateMethod when focus is coordinates", () => {
+ const coordinateMethodField = resizeAndCropTransformations.find(
+ (f) => f.name === "coordinateMethod",
+ )
+
+ const visible = coordinateMethodField?.isVisible?.({
+ width: 100,
+ height: 100,
+ mode: "cm-extract",
+ focus: "coordinates",
+ })
+
+ expect(visible).toBe(true)
+ })
+ })
+
+ describe("mode-specific field visibility", () => {
+ it("should show focus field for extract mode", () => {
+ const focusFields = resizeAndCropTransformations.filter(
+ (f) => f.name === "focus",
+ )
+ // Find the one for extract mode
+ const extractFocusField = focusFields.find((f) =>
+ f.isVisible?.({
+ width: 100,
+ height: 100,
+ mode: "cm-extract",
+ }),
+ )
+
+ expect(extractFocusField).toBeDefined()
+ })
+
+ it("should show focus field for maintain_ratio crop", () => {
+ const focusFields = resizeAndCropTransformations.filter(
+ (f) => f.name === "focus",
+ )
+ // Find the one for maintain_ratio mode
+ const maintainRatioFocusField = focusFields.find((f) =>
+ f.isVisible?.({
+ width: 100,
+ height: 100,
+ mode: "c-maintain_ratio",
+ }),
+ )
+
+ expect(maintainRatioFocusField).toBeDefined()
+ })
+ })
+ })
+
+ describe("Helper Functions - getDefaultTransformationfromMode", () => {
+ it("should return cropMode pad_resize for cm-pad_resize", () => {
+ const result = getDefaultTransformationFromMode("cm-pad_resize")
+ expect(result).toEqual({ cropMode: "pad_resize" })
+ })
+
+ it("should return cropMode extract for cm-extract", () => {
+ const result = getDefaultTransformationFromMode("cm-extract")
+ expect(result).toEqual({ cropMode: "extract" })
+ })
+
+ it("should return cropMode pad_extract for cm-pad_extract", () => {
+ const result = getDefaultTransformationFromMode("cm-pad_extract")
+ expect(result).toEqual({ cropMode: "pad_extract" })
+ })
+
+ it("should return crop maintain_ratio for c-maintain_ratio", () => {
+ const result = getDefaultTransformationFromMode("c-maintain_ratio")
+ expect(result).toEqual({ crop: "maintain_ratio" })
+ })
+
+ it("should return crop force for c-force", () => {
+ const result = getDefaultTransformationFromMode("c-force")
+ expect(result).toEqual({ crop: "force" })
+ })
+
+ it("should return crop at_max for c-at_max", () => {
+ const result = getDefaultTransformationFromMode("c-at_max")
+ expect(result).toEqual({ crop: "at_max" })
+ })
+
+ it("should return crop at_max_enlarge for c-at_max_enlarge", () => {
+ const result = getDefaultTransformationFromMode("c-at_max_enlarge")
+ expect(result).toEqual({ crop: "at_max_enlarge" })
+ })
+
+ it("should return crop at_least for c-at_least", () => {
+ const result = getDefaultTransformationFromMode("c-at_least")
+ expect(result).toEqual({ crop: "at_least" })
+ })
+
+ it("should return empty object for unknown mode", () => {
+ const result = getDefaultTransformationFromMode("unknown-mode")
+ expect(result).toEqual({})
+ })
+ })
+
+ describe("Additional Field Visibility Coverage", () => {
+ it("should show DPR field when enabled and width exists", () => {
+ const dprField = resizeAndCropTransformations.find((f) => f.name === "dpr")
+
+ const visible = dprField?.isVisible?.({
+ dprEnabled: true,
+ width: 100,
+ })
+
+ expect(visible).toBe(true)
+ })
+
+ it("should show DPR field when enabled and height exists", () => {
+ const dprField = resizeAndCropTransformations.find((f) => f.name === "dpr")
+
+ const visible = dprField?.isVisible?.({
+ dprEnabled: true,
+ height: 100,
+ })
+
+ expect(visible).toBe(true)
+ })
+
+ it("should hide DPR field when not enabled", () => {
+ const dprField = resizeAndCropTransformations.find((f) => f.name === "dpr")
+
+ const visible = dprField?.isVisible?.({
+ dprEnabled: false,
+ width: 100,
+ })
+
+ expect(visible).toBe(false)
+ })
+
+ it("should show zoom field for face focus in extract mode", () => {
+ const zoomField = resizeAndCropTransformations.find((f) => f.name === "zoom")
+
+ const visible = zoomField?.isVisible?.({
+ width: 100,
+ height: 100,
+ mode: "cm-extract",
+ focus: "face",
+ })
+
+ expect(visible).toBe(true)
+ })
+
+ it("should show zoom field for object focus in maintain_ratio", () => {
+ const zoomField = resizeAndCropTransformations.find((f) => f.name === "zoom")
+
+ const visible = zoomField?.isVisible?.({
+ width: 100,
+ height: 100,
+ mode: "c-maintain_ratio",
+ focus: "object",
+ })
+
+ expect(visible).toBe(true)
+ })
+
+ it("should hide zoom field for anchor focus", () => {
+ const zoomField = resizeAndCropTransformations.find((f) => f.name === "zoom")
+
+ const visible = zoomField?.isVisible?.({
+ width: 100,
+ height: 100,
+ mode: "cm-extract",
+ focus: "anchor",
+ })
+
+ expect(visible).toBe(false)
+ })
+
+ it("should show focus field for c-force mode", () => {
+ const focusFields = resizeAndCropTransformations.filter(
+ (f) => f.name === "focus",
+ )
+ // Find the one for force mode (has only auto option)
+ const forceField = focusFields.find((f) =>
+ f.isVisible?.({
+ width: 100,
+ height: 100,
+ mode: "c-force",
+ }),
+ )
+
+ expect(forceField).toBeDefined()
+ expect(forceField?.fieldProps?.options).toHaveLength(1)
+ expect(forceField?.fieldProps?.options?.[0].value).toBe("auto")
+ })
+
+ it("should test pad_resize background field wrapper", () => {
+ // Find background fields that are visible for pad_resize
+ const backgroundFields = resizeAndCropTransformations.filter(
+ (f) =>
+ f.transformationGroup === "background" ||
+ f.name === "backgroundType" ||
+ f.name === "background",
+ )
+
+ // At least one should be visible for pad_resize with dimensions
+ const visibleForPadResize = backgroundFields.some((f) =>
+ f.isVisible?.({
+ width: 100,
+ height: 100,
+ mode: "cm-pad_resize",
+ backgroundType: "color",
+ }),
+ )
+
+ expect(visibleForPadResize).toBe(true)
+ })
+
+ it("should test pad_extract background field wrapper", () => {
+ // Find background fields that are visible for pad_extract
+ const backgroundFields = resizeAndCropTransformations.filter(
+ (f) =>
+ f.transformationGroup === "background" ||
+ f.name === "backgroundType" ||
+ f.name === "background",
+ )
+
+ // At least one should be visible for pad_extract with dimensions
+ const visibleForPadExtract = backgroundFields.some((f) =>
+ f.isVisible?.({
+ width: 100,
+ height: 100,
+ mode: "cm-pad_extract",
+ backgroundType: "color",
+ }),
+ )
+
+ expect(visibleForPadExtract).toBe(true)
+ })
+ })
+})
diff --git a/packages/imagekit-editor-dev/vite.config.ts b/packages/imagekit-editor-dev/vite.config.ts
index 15d7a68..70de203 100644
--- a/packages/imagekit-editor-dev/vite.config.ts
+++ b/packages/imagekit-editor-dev/vite.config.ts
@@ -24,12 +24,18 @@ export default defineConfig({
coverage: {
provider: "v8",
reporter: ["text", "json", "html"],
- include: ["src/**/*.{ts,tsx}"],
+ include: ["src/schema/**/*.{ts,tsx}"],
exclude: [
"src/**/*.{test,spec}.{ts,tsx}",
- "src/index.tsx",
"node_modules/**",
],
+ thresholds: {
+ // Only enforced on src/schema files - focusing on validation logic
+ lines: 85, // Realistic threshold given UI visibility code
+ branches: 85,
+ statements: 85,
+ perFile: false, // Global threshold across all schema files
+ },
},
},
build: {
From e7044a2f30fac5aa008ab604d975c96e56b7d4cf Mon Sep 17 00:00:00 2001
From: Manu Chaudhary
Date: Tue, 10 Mar 2026 16:59:29 +0530
Subject: [PATCH 5/8] ci: update test command to run coverage tests
---
.github/workflows/ci.yaml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index 0756460..3ae2a6c 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -26,7 +26,7 @@ jobs:
run: |
yarn install --frozen-lockfile
yarn lint
- yarn test
+ yarn test:coverage
yarn package
env:
CI: true
From aee07842deaae4aa1a7f3b0d6ad885c51e9026fb Mon Sep 17 00:00:00 2001
From: Manu Chaudhary
Date: Tue, 10 Mar 2026 17:07:34 +0530
Subject: [PATCH 6/8] test: add validation tests for backward compatibility and
transformation formatters
---
.../src/backward-compatibility.test.ts | 83 +++++
.../src/schema/formatters.test.ts | 320 ++++++++++++++++++
.../src/schema/transformation.ts | 19 --
3 files changed, 403 insertions(+), 19 deletions(-)
create mode 100644 packages/imagekit-editor-dev/src/schema/formatters.test.ts
diff --git a/packages/imagekit-editor-dev/src/backward-compatibility.test.ts b/packages/imagekit-editor-dev/src/backward-compatibility.test.ts
index 62799e9..eb4eb29 100644
--- a/packages/imagekit-editor-dev/src/backward-compatibility.test.ts
+++ b/packages/imagekit-editor-dev/src/backward-compatibility.test.ts
@@ -3005,6 +3005,25 @@ describe("Backward Compatibility - V1 Templates", () => {
expect(validateTransformation(template).valid).toBe(false)
})
+ it("should reject unsharpen mask with missing threshold", () => {
+ const template: Omit = {
+ key: "adjust-unsharpen-mask",
+ name: "Unsharpen Mask",
+ type: "transformation",
+ value: {
+ unsharpenMask: true,
+ unsharpenMaskRadius: 2,
+ unsharpenMaskSigma: 1,
+ unsharpenMaskAmount: 0.5,
+ // Missing unsharpenMaskThreshold entirely
+ },
+ version: "v1",
+ }
+ const result = validateTransformation(template)
+ expect(result.valid).toBe(false)
+ expect(result.errors?.some(e => e.includes("Threshold"))).toBe(true)
+ })
+
it("should accept unsharpen mask with valid positive threshold", () => {
const template: Omit = {
key: "adjust-unsharpen-mask",
@@ -3022,4 +3041,68 @@ describe("Backward Compatibility - V1 Templates", () => {
expect(validateTransformation(template).valid).toBe(true)
})
})
+
+ describe("Empty Transformation Validation - At Least One Value Required", () => {
+ it("should reject contrast transformation with no values", () => {
+ const template: Omit = {
+ key: "adjust-contrast",
+ name: "Contrast",
+ type: "transformation",
+ value: {},
+ version: "v1",
+ }
+ const result = validateTransformation(template)
+ expect(result.valid).toBe(false)
+ expect(result.errors?.some(e => e.includes("At least one value"))).toBe(true)
+ })
+
+ it("should reject shadow transformation with no values", () => {
+ const template: Omit = {
+ key: "adjust-shadow",
+ name: "Shadow",
+ type: "transformation",
+ value: {},
+ version: "v1",
+ }
+ const result = validateTransformation(template)
+ expect(result.valid).toBe(false)
+ expect(result.errors?.some(e => e.includes("At least one value"))).toBe(true)
+ })
+
+ it("should reject grayscale transformation with no values", () => {
+ const template: Omit = {
+ key: "adjust-grayscale",
+ name: "Grayscale",
+ type: "transformation",
+ value: {},
+ version: "v1",
+ }
+ const result = validateTransformation(template)
+ expect(result.valid).toBe(false)
+ })
+
+ it("should reject radius transformation with no values", () => {
+ const template: Omit = {
+ key: "adjust-radius",
+ name: "Radius",
+ type: "transformation",
+ value: {},
+ version: "v1",
+ }
+ const result = validateTransformation(template)
+ expect(result.valid).toBe(false)
+ })
+
+ it("should reject trim transformation with no values", () => {
+ const template: Omit = {
+ key: "adjust-trim",
+ name: "Trim",
+ type: "transformation",
+ value: {},
+ version: "v1",
+ }
+ const result = validateTransformation(template)
+ expect(result.valid).toBe(false)
+ })
+ })
})
diff --git a/packages/imagekit-editor-dev/src/schema/formatters.test.ts b/packages/imagekit-editor-dev/src/schema/formatters.test.ts
new file mode 100644
index 0000000..557fc95
--- /dev/null
+++ b/packages/imagekit-editor-dev/src/schema/formatters.test.ts
@@ -0,0 +1,320 @@
+import { describe, expect, it } from "vitest"
+import { transformationFormatters } from "./index"
+
+describe("Transformation Formatters", () => {
+ describe("background formatter", () => {
+ it("should format color background with dominant auto", () => {
+ const transforms: Record = {}
+ transformationFormatters.background(
+ {
+ backgroundType: "color",
+ backgroundDominantAuto: true,
+ },
+ transforms,
+ )
+ expect(transforms.background).toBe("dominant")
+ })
+
+ it("should format gradient background with auto dominant", () => {
+ const transforms: Record = {}
+ transformationFormatters.background(
+ {
+ backgroundType: "gradient",
+ backgroundGradientAutoDominant: true,
+ backgroundGradientPaletteSize: "4",
+ backgroundGradientMode: "contrast",
+ },
+ transforms,
+ )
+ expect(transforms.background).toBe("gradient_contrast_4")
+ })
+
+ it("should format gradient background with default values", () => {
+ const transforms: Record = {}
+ transformationFormatters.background(
+ {
+ backgroundType: "gradient",
+ backgroundGradientAutoDominant: true,
+ },
+ transforms,
+ )
+ expect(transforms.background).toBe("gradient_dominant_2")
+ })
+
+ it("should format manual gradient background", () => {
+ const transforms: Record = {}
+ transformationFormatters.background(
+ {
+ backgroundType: "gradient",
+ backgroundGradientAutoDominant: false,
+ backgroundGradient: {
+ from: "#FF0000",
+ to: "#0000FF",
+ direction: "top",
+ stopPoint: 50,
+ },
+ },
+ transforms,
+ )
+ expect(transforms.raw).toContain("e-gradient")
+ expect(transforms.raw).toContain("from-FF0000")
+ expect(transforms.raw).toContain("to-0000FF")
+ })
+
+ it("should format blurred background with auto intensity", () => {
+ const transforms: Record = {}
+ transformationFormatters.background(
+ {
+ backgroundType: "blurred",
+ backgroundBlurIntensity: "auto",
+ },
+ transforms,
+ )
+ expect(transforms.background).toBe("blurred_auto")
+ })
+
+ it("should format blurred background with auto intensity and brightness", () => {
+ const transforms: Record = {}
+ transformationFormatters.background(
+ {
+ backgroundType: "blurred",
+ backgroundBlurIntensity: "auto",
+ backgroundBlurBrightness: "50",
+ },
+ transforms,
+ )
+ expect(transforms.background).toBe("blurred_auto_50")
+ })
+
+ it("should format blurred background with numeric intensity", () => {
+ const transforms: Record = {}
+ transformationFormatters.background(
+ {
+ backgroundType: "blurred",
+ backgroundBlurIntensity: "10",
+ },
+ transforms,
+ )
+ expect(transforms.background).toBe("blurred_10")
+ })
+
+ it("should format blurred background with intensity and brightness", () => {
+ const transforms: Record = {}
+ transformationFormatters.background(
+ {
+ backgroundType: "blurred",
+ backgroundBlurIntensity: "10",
+ backgroundBlurBrightness: "20",
+ },
+ transforms,
+ )
+ expect(transforms.background).toBe("blurred_10_20")
+ })
+
+ it("should handle negative blur brightness", () => {
+ const transforms: Record = {}
+ transformationFormatters.background(
+ {
+ backgroundType: "blurred",
+ backgroundBlurIntensity: "10",
+ backgroundBlurBrightness: "-20",
+ },
+ transforms,
+ )
+ expect(transforms.background).toBe("blurred_10_N20")
+ })
+
+ it("should format generative fill background without prompt", () => {
+ const transforms: Record = {}
+ transformationFormatters.background(
+ {
+ backgroundType: "generative_fill",
+ },
+ transforms,
+ )
+ expect(transforms.background).toBe("genfill")
+ })
+
+ it("should format generative fill with simple text prompt", () => {
+ const transforms: Record = {}
+ transformationFormatters.background(
+ {
+ backgroundType: "generative_fill",
+ backgroundGenerativeFill: "beach",
+ },
+ transforms,
+ )
+ expect(transforms.background).toBe("genfill-prompt-beach")
+ })
+
+ it("should format generative fill with complex prompt", () => {
+ const transforms: Record = {}
+ transformationFormatters.background(
+ {
+ backgroundType: "generative_fill",
+ backgroundGenerativeFill: "beach with palm trees!",
+ },
+ transforms,
+ )
+ expect(transforms.background).toContain("genfill-prompte-")
+ })
+
+ it("should format color background with manual color", () => {
+ const transforms: Record = {}
+ transformationFormatters.background(
+ {
+ backgroundType: "color",
+ backgroundDominantAuto: false,
+ background: "#FF5733",
+ },
+ transforms,
+ )
+ expect(transforms.background).toBe("FF5733")
+ })
+
+ it("should default to blurred when intensity is invalid", () => {
+ const transforms: Record = {}
+ transformationFormatters.background(
+ {
+ backgroundType: "blurred",
+ backgroundBlurIntensity: "invalid",
+ },
+ transforms,
+ )
+ expect(transforms.background).toBe("blurred")
+ })
+ })
+
+ describe("focus formatter", () => {
+ it("should format focus with anchor", () => {
+ const transforms: Record = {}
+ transformationFormatters.focus(
+ {
+ focus: "anchor",
+ focusAnchor: "top_left",
+ },
+ transforms,
+ )
+ expect(transforms.focus).toBe("top_left")
+ })
+
+ it("should format focus with object", () => {
+ const transforms: Record = {}
+ transformationFormatters.focus(
+ {
+ focus: "object",
+ focusObject: "face",
+ },
+ transforms,
+ )
+ expect(transforms.focus).toBe("face")
+ })
+
+ it("should format focus with auto", () => {
+ const transforms: Record = {}
+ transformationFormatters.focus(
+ {
+ focus: "auto",
+ },
+ transforms,
+ )
+ expect(transforms.focus).toBe("auto")
+ })
+
+ it("should format focus with center coordinates", () => {
+ const transforms: Record = {}
+ transformationFormatters.focus(
+ {
+ focus: "coordinates",
+ coordinateMethod: "center",
+ xc: "100",
+ yc: "200",
+ },
+ transforms,
+ )
+ expect(transforms.xc).toBe("100")
+ expect(transforms.yc).toBe("200")
+ })
+
+ it("should format focus with topleft coordinates", () => {
+ const transforms: Record = {}
+ transformationFormatters.focus(
+ {
+ focus: "coordinates",
+ coordinateMethod: "topleft",
+ x: "50",
+ y: "75",
+ },
+ transforms,
+ )
+ expect(transforms.x).toBe("50")
+ expect(transforms.y).toBe("75")
+ })
+
+ it("should format focus with zoom", () => {
+ const transforms: Record = {}
+ transformationFormatters.focus(
+ {
+ focus: "auto",
+ zoom: 150,
+ },
+ transforms,
+ )
+ expect(transforms.zoom).toBe(1.5)
+ })
+ })
+
+ describe("shadow formatter", () => {
+ it("should format shadow with all parameters", () => {
+ const transforms: Record = {}
+ transformationFormatters.shadow(
+ {
+ shadow: true,
+ shadowBlur: 10,
+ shadowSaturation: 50,
+ shadowOffsetX: 5,
+ shadowOffsetY: 8,
+ },
+ transforms,
+ )
+ expect(transforms.shadow).toBe("bl-10_st-50_x-5_y-8")
+ })
+
+ it("should skip shadow when disabled", () => {
+ const transforms: Record = {}
+ transformationFormatters.shadow(
+ {
+ shadow: false,
+ },
+ transforms,
+ )
+ expect(transforms.shadow).toBeUndefined()
+ })
+
+ it("should handle negative shadow offsets", () => {
+ const transforms: Record = {}
+ transformationFormatters.shadow(
+ {
+ shadow: true,
+ shadowOffsetX: -5,
+ shadowOffsetY: -10,
+ },
+ transforms,
+ )
+ expect(transforms.shadow).toContain("x-N5")
+ expect(transforms.shadow).toContain("y-N10")
+ })
+
+ it("should format shadow with only blur", () => {
+ const transforms: Record = {}
+ transformationFormatters.shadow(
+ {
+ shadow: true,
+ shadowBlur: 15,
+ },
+ transforms,
+ )
+ expect(transforms.shadow).toBe("bl-15")
+ })
+ })
+})
diff --git a/packages/imagekit-editor-dev/src/schema/transformation.ts b/packages/imagekit-editor-dev/src/schema/transformation.ts
index e188df5..6b05c4e 100644
--- a/packages/imagekit-editor-dev/src/schema/transformation.ts
+++ b/packages/imagekit-editor-dev/src/schema/transformation.ts
@@ -146,25 +146,6 @@ export const commonNumberAndExpressionValidator = z
})
})
-const overlayBlockExpr = z
- .string()
- .regex(/^(?:bh|bw|bar)_(?:add|sub|mul|div|mod|pow)_(?:\d+(\.\d{1,2})?)$/, {
- message: "String must be a valid expression string.",
- })
-
-export const overlayBlockExprValidator = z.any().superRefine((val, ctx) => {
- if (commonNumber.safeParse(val).success) {
- return
- }
- if (overlayBlockExpr.safeParse(val).success) {
- return
- }
- ctx.addIssue({
- code: z.ZodIssueCode.custom,
- message: "Must be a positive number or a valid expression string.",
- })
-})
-
const lineHeightInteger = z.coerce.string().regex(/^\d+$/)
const lineHeightExpr = z
From ed70e312f8ea969b40b3778bbe24976350322797 Mon Sep 17 00:00:00 2001
From: Manu Chaudhary
Date: Tue, 10 Mar 2026 17:08:04 +0530
Subject: [PATCH 7/8] refactor: clean up code formatting and improve
readability in tests and configuration
---
.../src/backward-compatibility.test.ts | 16 ++++------
.../src/schema/field-config.test.ts | 32 ++++++++++++++-----
packages/imagekit-editor-dev/vite.config.ts | 5 +--
3 files changed, 32 insertions(+), 21 deletions(-)
diff --git a/packages/imagekit-editor-dev/src/backward-compatibility.test.ts b/packages/imagekit-editor-dev/src/backward-compatibility.test.ts
index eb4eb29..7807c90 100644
--- a/packages/imagekit-editor-dev/src/backward-compatibility.test.ts
+++ b/packages/imagekit-editor-dev/src/backward-compatibility.test.ts
@@ -2595,8 +2595,6 @@ describe("Backward Compatibility - V1 Templates", () => {
})
})
-
-
describe("Unsharpen Mask Error Coverage", () => {
it("should require sigma when unsharpen mask is enabled", () => {
const template: Omit = {
@@ -2752,10 +2750,6 @@ describe("Backward Compatibility - V1 Templates", () => {
})
})
-
-
-
-
describe("Maintain Ratio Focus Validations", () => {
it("should validate maintain_ratio with anchor focus", () => {
const template: Omit = {
@@ -3021,7 +3015,7 @@ describe("Backward Compatibility - V1 Templates", () => {
}
const result = validateTransformation(template)
expect(result.valid).toBe(false)
- expect(result.errors?.some(e => e.includes("Threshold"))).toBe(true)
+ expect(result.errors?.some((e) => e.includes("Threshold"))).toBe(true)
})
it("should accept unsharpen mask with valid positive threshold", () => {
@@ -3053,7 +3047,9 @@ describe("Backward Compatibility - V1 Templates", () => {
}
const result = validateTransformation(template)
expect(result.valid).toBe(false)
- expect(result.errors?.some(e => e.includes("At least one value"))).toBe(true)
+ expect(result.errors?.some((e) => e.includes("At least one value"))).toBe(
+ true,
+ )
})
it("should reject shadow transformation with no values", () => {
@@ -3066,7 +3062,9 @@ describe("Backward Compatibility - V1 Templates", () => {
}
const result = validateTransformation(template)
expect(result.valid).toBe(false)
- expect(result.errors?.some(e => e.includes("At least one value"))).toBe(true)
+ expect(result.errors?.some((e) => e.includes("At least one value"))).toBe(
+ true,
+ )
})
it("should reject grayscale transformation with no values", () => {
diff --git a/packages/imagekit-editor-dev/src/schema/field-config.test.ts b/packages/imagekit-editor-dev/src/schema/field-config.test.ts
index 4d7748e..80882eb 100644
--- a/packages/imagekit-editor-dev/src/schema/field-config.test.ts
+++ b/packages/imagekit-editor-dev/src/schema/field-config.test.ts
@@ -201,7 +201,9 @@ describe("Field Configuration Tests", () => {
})
it("should show xc field for center coordinates in extract mode", () => {
- const xcField = resizeAndCropTransformations.find((f) => f.name === "xc")
+ const xcField = resizeAndCropTransformations.find(
+ (f) => f.name === "xc",
+ )
const visible = xcField?.isVisible?.({
width: 100,
@@ -215,7 +217,9 @@ describe("Field Configuration Tests", () => {
})
it("should show yc field for center coordinates in extract mode", () => {
- const ycField = resizeAndCropTransformations.find((f) => f.name === "yc")
+ const ycField = resizeAndCropTransformations.find(
+ (f) => f.name === "yc",
+ )
const visible = ycField?.isVisible?.({
width: 100,
@@ -360,7 +364,9 @@ describe("Field Configuration Tests", () => {
describe("Additional Field Visibility Coverage", () => {
it("should show DPR field when enabled and width exists", () => {
- const dprField = resizeAndCropTransformations.find((f) => f.name === "dpr")
+ const dprField = resizeAndCropTransformations.find(
+ (f) => f.name === "dpr",
+ )
const visible = dprField?.isVisible?.({
dprEnabled: true,
@@ -371,7 +377,9 @@ describe("Field Configuration Tests", () => {
})
it("should show DPR field when enabled and height exists", () => {
- const dprField = resizeAndCropTransformations.find((f) => f.name === "dpr")
+ const dprField = resizeAndCropTransformations.find(
+ (f) => f.name === "dpr",
+ )
const visible = dprField?.isVisible?.({
dprEnabled: true,
@@ -382,7 +390,9 @@ describe("Field Configuration Tests", () => {
})
it("should hide DPR field when not enabled", () => {
- const dprField = resizeAndCropTransformations.find((f) => f.name === "dpr")
+ const dprField = resizeAndCropTransformations.find(
+ (f) => f.name === "dpr",
+ )
const visible = dprField?.isVisible?.({
dprEnabled: false,
@@ -393,7 +403,9 @@ describe("Field Configuration Tests", () => {
})
it("should show zoom field for face focus in extract mode", () => {
- const zoomField = resizeAndCropTransformations.find((f) => f.name === "zoom")
+ const zoomField = resizeAndCropTransformations.find(
+ (f) => f.name === "zoom",
+ )
const visible = zoomField?.isVisible?.({
width: 100,
@@ -406,7 +418,9 @@ describe("Field Configuration Tests", () => {
})
it("should show zoom field for object focus in maintain_ratio", () => {
- const zoomField = resizeAndCropTransformations.find((f) => f.name === "zoom")
+ const zoomField = resizeAndCropTransformations.find(
+ (f) => f.name === "zoom",
+ )
const visible = zoomField?.isVisible?.({
width: 100,
@@ -419,7 +433,9 @@ describe("Field Configuration Tests", () => {
})
it("should hide zoom field for anchor focus", () => {
- const zoomField = resizeAndCropTransformations.find((f) => f.name === "zoom")
+ const zoomField = resizeAndCropTransformations.find(
+ (f) => f.name === "zoom",
+ )
const visible = zoomField?.isVisible?.({
width: 100,
diff --git a/packages/imagekit-editor-dev/vite.config.ts b/packages/imagekit-editor-dev/vite.config.ts
index 70de203..84f1f2d 100644
--- a/packages/imagekit-editor-dev/vite.config.ts
+++ b/packages/imagekit-editor-dev/vite.config.ts
@@ -25,10 +25,7 @@ export default defineConfig({
provider: "v8",
reporter: ["text", "json", "html"],
include: ["src/schema/**/*.{ts,tsx}"],
- exclude: [
- "src/**/*.{test,spec}.{ts,tsx}",
- "node_modules/**",
- ],
+ exclude: ["src/**/*.{test,spec}.{ts,tsx}", "node_modules/**"],
thresholds: {
// Only enforced on src/schema files - focusing on validation logic
lines: 85, // Realistic threshold given UI visibility code
From 2aa122fb596687ef6999874c74ec75236faf8755 Mon Sep 17 00:00:00 2001
From: Manu Chaudhary
Date: Tue, 10 Mar 2026 17:09:20 +0530
Subject: [PATCH 8/8] fix: increase coverage thresholds for lines, branches,
and statements in Vite config
---
packages/imagekit-editor-dev/vite.config.ts | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/packages/imagekit-editor-dev/vite.config.ts b/packages/imagekit-editor-dev/vite.config.ts
index 84f1f2d..40053ea 100644
--- a/packages/imagekit-editor-dev/vite.config.ts
+++ b/packages/imagekit-editor-dev/vite.config.ts
@@ -28,9 +28,9 @@ export default defineConfig({
exclude: ["src/**/*.{test,spec}.{ts,tsx}", "node_modules/**"],
thresholds: {
// Only enforced on src/schema files - focusing on validation logic
- lines: 85, // Realistic threshold given UI visibility code
- branches: 85,
- statements: 85,
+ lines: 90, // Realistic threshold given UI visibility code
+ branches: 90,
+ statements: 90,
perFile: false, // Global threshold across all schema files
},
},