diff --git a/automation/run-e2e/docker/mxbuild.Dockerfile b/automation/run-e2e/docker/mxbuild.Dockerfile index c6a3934271..739fdc3985 100644 --- a/automation/run-e2e/docker/mxbuild.Dockerfile +++ b/automation/run-e2e/docker/mxbuild.Dockerfile @@ -3,6 +3,8 @@ FROM --platform=$BUILDPLATFORM eclipse-temurin:21-jdk-jammy ARG MENDIX_VERSION ARG BUILDPLATFORM +ENV LD_PRELOAD=libfontconfig.so.1 + SHELL ["/bin/bash", "-c"] RUN \ echo "Downloading mxbuild ${MENDIX_VERSION} and docker building for ${BUILDPLATFORM}..." \ @@ -26,7 +28,7 @@ echo "Downloading mxbuild ${MENDIX_VERSION} and docker building for ${BUILDPLATF \ rm -rf /var/lib/apt/lists/* && \ apt-get update --allow-insecure-repositories -qqy && \ - apt-get install -qqy --allow-unauthenticated libicu70 && \ + apt-get install -qqy --allow-unauthenticated libicu70 libgdiplus libfontconfig1 && \ apt-get -qqy remove --auto-remove wget && \ apt-get clean && \ rm -rf /var/lib/apt/lists/* && \ diff --git a/package.json b/package.json index 97ef1d2f0a..a99b319d5c 100644 --- a/package.json +++ b/package.json @@ -72,6 +72,7 @@ "d3-color@<3.1.0": ">=3.1.0", "glob@10": "^10.5.0", "fast-uri": "3.1.4", + "mendix": "11.10.0", "react": ">=18.0.0 <19.0.0", "react-big-calendar@1>clsx": "2.1.1", "react-dom": ">=18.0.0 <19.0.0", diff --git a/packages/pluggableWidgets/image-cropper-web/src/__tests__/ImageCropper.spec.tsx b/packages/pluggableWidgets/image-cropper-web/src/__tests__/ImageCropper.spec.tsx index 6bf764e3a8..93c2d32006 100644 --- a/packages/pluggableWidgets/image-cropper-web/src/__tests__/ImageCropper.spec.tsx +++ b/packages/pluggableWidgets/image-cropper-web/src/__tests__/ImageCropper.spec.tsx @@ -3,7 +3,7 @@ import { Big } from "big.js"; import { ValueStatus } from "mendix"; import { Ref } from "react"; import type { Crop, PixelCrop } from "react-image-crop"; -import { actionValue, dynamic } from "@mendix/widget-plugin-test-utils"; +import { actionValue, dynamic, editableImage } from "@mendix/widget-plugin-test-utils"; import type { ImageCropperContainerProps } from "../../typings/ImageCropperProps"; // Capture the container's callbacks via a mocked CropArea. Real ReactCrop only fires @@ -67,13 +67,7 @@ const PERCENT_CROP: Crop = { unit: "%", x: 5, y: 5, width: 50, height: 50 }; function makeImageProp(overrides: Partial = {}): ImageProp { return { - status: ValueStatus.Available, - value: { uri: "http://localhost/img.png", name: "img.png" } as WebImage, - readOnly: false, - validation: undefined, - setValidator: jest.fn(), - setValue: jest.fn(), - setThumbnailSize: jest.fn(), + ...editableImage.with({ uri: "http://localhost/img.png", name: "img.png" }), ...overrides } as ImageProp; } diff --git a/packages/pluggableWidgets/image-cropper-web/src/__tests__/ImageCropperGrayscale.spec.tsx b/packages/pluggableWidgets/image-cropper-web/src/__tests__/ImageCropperGrayscale.spec.tsx index e43fe42904..76ff907009 100644 --- a/packages/pluggableWidgets/image-cropper-web/src/__tests__/ImageCropperGrayscale.spec.tsx +++ b/packages/pluggableWidgets/image-cropper-web/src/__tests__/ImageCropperGrayscale.spec.tsx @@ -1,9 +1,8 @@ import { act, fireEvent, render, screen } from "@testing-library/react"; import { Big } from "big.js"; -import { ValueStatus } from "mendix"; import { Ref } from "react"; import type { Crop, PixelCrop } from "react-image-crop"; -import { actionValue } from "@mendix/widget-plugin-test-utils"; +import { actionValue, editableImage } from "@mendix/widget-plugin-test-utils"; import type { ImageCropperContainerProps } from "../../typings/ImageCropperProps"; // Integration test: proves grayscale reversibility after rotate. @@ -76,15 +75,7 @@ const PIXEL_CROP: PixelCrop = { unit: "px", x: 10, y: 10, width: 100, height: 10 const PERCENT_CROP: Crop = { unit: "%", x: 5, y: 5, width: 50, height: 50 }; function makeImageProp(): ImageProp { - return { - status: ValueStatus.Available, - value: { uri: "http://localhost/img.png", name: "img.png" } as WebImage, - readOnly: false, - validation: undefined, - setValidator: jest.fn(), - setValue: jest.fn(), - setThumbnailSize: jest.fn() - } as ImageProp; + return editableImage.with({ uri: "http://localhost/img.png", name: "img.png" }) as ImageProp; } function makeProps(overrides: Partial = {}): ImageCropperContainerProps { diff --git a/packages/pluggableWidgets/image-cropper-web/src/__tests__/ImageCropperMultiInstance.spec.tsx b/packages/pluggableWidgets/image-cropper-web/src/__tests__/ImageCropperMultiInstance.spec.tsx index 49af30088f..c3e1baed60 100644 --- a/packages/pluggableWidgets/image-cropper-web/src/__tests__/ImageCropperMultiInstance.spec.tsx +++ b/packages/pluggableWidgets/image-cropper-web/src/__tests__/ImageCropperMultiInstance.spec.tsx @@ -1,9 +1,8 @@ import { act, render } from "@testing-library/react"; import { Big } from "big.js"; -import { ValueStatus } from "mendix"; import { Ref } from "react"; import type { Crop, PixelCrop } from "react-image-crop"; -import { actionValue } from "@mendix/widget-plugin-test-utils"; +import { actionValue, editableImage } from "@mendix/widget-plugin-test-utils"; import type { ImageCropperContainerProps } from "../../typings/ImageCropperProps"; // Multi-instance integration test: verifies that auto-commit only fires for the @@ -68,15 +67,7 @@ const PIXEL_CROP: PixelCrop = { unit: "px", x: 10, y: 10, width: 100, height: 10 const PERCENT_CROP: Crop = { unit: "%", x: 5, y: 5, width: 50, height: 50 }; function makeImageProp(): ImageProp { - return { - status: ValueStatus.Available, - value: { uri: "http://localhost/img.png", name: "img.png" } as WebImage, - readOnly: false, - validation: undefined, - setValidator: jest.fn(), - setValue: jest.fn(), - setThumbnailSize: jest.fn() - } as ImageProp; + return editableImage.with({ uri: "http://localhost/img.png", name: "img.png" }) as ImageProp; } function makeProps(overrides: Partial = {}): ImageCropperContainerProps { diff --git a/packages/pluggableWidgets/image-cropper-web/src/__tests__/ImageCropperRotation.spec.tsx b/packages/pluggableWidgets/image-cropper-web/src/__tests__/ImageCropperRotation.spec.tsx index b3806b507e..78e205d995 100644 --- a/packages/pluggableWidgets/image-cropper-web/src/__tests__/ImageCropperRotation.spec.tsx +++ b/packages/pluggableWidgets/image-cropper-web/src/__tests__/ImageCropperRotation.spec.tsx @@ -1,9 +1,8 @@ import { act, fireEvent, render, screen } from "@testing-library/react"; import { Big } from "big.js"; -import { ValueStatus } from "mendix"; import { Ref } from "react"; import type { Crop, PixelCrop } from "react-image-crop"; -import { actionValue } from "@mendix/widget-plugin-test-utils"; +import { actionValue, editableImage } from "@mendix/widget-plugin-test-utils"; import type { ImageCropperContainerProps } from "../../typings/ImageCropperProps"; // Integration test: proves the rotate/grayscale actions reach the right util with the right args. @@ -85,15 +84,7 @@ const PIXEL_CROP: PixelCrop = { unit: "px", x: 10, y: 10, width: 100, height: 10 const PERCENT_CROP: Crop = { unit: "%", x: 5, y: 5, width: 50, height: 50 }; function makeImageProp(): ImageProp { - return { - status: ValueStatus.Available, - value: { uri: "http://localhost/img.png", name: "img.png" } as WebImage, - readOnly: false, - validation: undefined, - setValidator: jest.fn(), - setValue: jest.fn(), - setThumbnailSize: jest.fn() - } as ImageProp; + return editableImage.with({ uri: "http://localhost/img.png", name: "img.png" }) as ImageProp; } function makeProps(overrides: Partial = {}): ImageCropperContainerProps { diff --git a/packages/pluggableWidgets/image-cropper-web/src/stores/__tests__/ImageCropperStore.spec.ts b/packages/pluggableWidgets/image-cropper-web/src/stores/__tests__/ImageCropperStore.spec.ts index f8df72000c..60a6566678 100644 --- a/packages/pluggableWidgets/image-cropper-web/src/stores/__tests__/ImageCropperStore.spec.ts +++ b/packages/pluggableWidgets/image-cropper-web/src/stores/__tests__/ImageCropperStore.spec.ts @@ -1,8 +1,8 @@ import { Big } from "big.js"; -import { ValueStatus } from "mendix"; import { action, makeObservable, observable } from "mobx"; import type { Crop, PixelCrop } from "react-image-crop"; import { DerivedPropsGate } from "@mendix/widget-plugin-mobx-kit/main"; +import { editableImage } from "@mendix/widget-plugin-test-utils"; import type { ImageCropperContainerProps } from "../../../typings/ImageCropperProps"; // The store calls cropImage/rotateImage (async canvas work). Mock them so the spec asserts @@ -33,13 +33,7 @@ type WebImage = NonNullable; function makeImageProp(overrides: Partial = {}): ImageProp { return { - status: ValueStatus.Available, - value: { uri: "http://localhost/img.png", name: "img.png" } as WebImage, - readOnly: false, - validation: undefined, - setValidator: jest.fn(), - setValue: jest.fn(), - setThumbnailSize: jest.fn(), + ...editableImage.with({ uri: "http://localhost/img.png", name: "img.png" }), ...overrides } as unknown as ImageProp; } diff --git a/packages/pluggableWidgets/image-web/CHANGELOG.md b/packages/pluggableWidgets/image-web/CHANGELOG.md index e942e390fb..af0780e6e8 100644 --- a/packages/pluggableWidgets/image-web/CHANGELOG.md +++ b/packages/pluggableWidgets/image-web/CHANGELOG.md @@ -12,6 +12,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), - We fixed an issue where the Image widget would briefly render in an empty or broken state while the image data was still loading. The widget now waits until the image is ready before displaying it. +- We fixed a deprecation warning by migrating the image source properties to the `allowUpload` property type. This raises the minimum supported Mendix version to 11.12.0. + ## [1.5.1] - 2025-10-29 ### Fixed diff --git a/packages/pluggableWidgets/image-web/e2e/onClick.spec.js b/packages/pluggableWidgets/image-web/e2e/onClick.spec.js index 986f0f9bb9..713f62527d 100644 --- a/packages/pluggableWidgets/image-web/e2e/onClick.spec.js +++ b/packages/pluggableWidgets/image-web/e2e/onClick.spec.js @@ -5,36 +5,42 @@ test.describe("Image viewer", () => { const dynamicImage = "https://www.learningcontainer.com/wp-content/uploads/2020/08/Sample-png-Image-for-Testing.png"; + const imageButton = (page, widgetClass) => page.locator(`${widgetClass} [role='button']`); + test("triggers a Microflow on click", async ({ page }) => { await page.goto("/p/onClickMicroflow"); await waitForMendixApp(page); - await page.click(".mx-name-image1"); - const modalDialog = await page.locator(".modal-dialog"); + await imageButton(page, ".mx-name-image1").click(); + const modalDialog = page.locator(".modal-dialog"); + await expect(modalDialog).toBeVisible(); await expect(modalDialog).toContainText("You clicked this image"); }); test("triggers a Nanoflow on click", async ({ page }) => { await page.goto("/p/onClickNanoflow"); await waitForMendixApp(page); - await page.click(".mx-name-image1"); - const modalDialog = await page.locator(".modal-dialog"); + await imageButton(page, ".mx-name-image1").click(); + const modalDialog = page.locator(".modal-dialog"); + await expect(modalDialog).toBeVisible(); await expect(modalDialog).toContainText(dynamicImage); }); test("opens a Page on click", async ({ page }) => { await page.goto("/p/onClickShowPage"); await waitForMendixApp(page); - await page.click(".mx-name-image1"); - const modalDialog = await page.locator(".modal-dialog"); - const caption = await modalDialog.locator("#mxui_widget_Window_0_caption"); + await imageButton(page, ".mx-name-image1").click(); + const modalDialog = page.locator(".modal-dialog"); + await expect(modalDialog).toBeVisible(); + const caption = modalDialog.locator("#mxui_widget_Window_0_caption"); await expect(caption).toContainText("GazaLand"); }); test("shows full screen image on click", async ({ page }) => { await page.goto("/p/onClickOpenFullScreen"); await waitForMendixApp(page); - await page.click(".mx-name-imageRender1"); - const lightboxImage = await page.locator(".mx-image-viewer-lightbox img"); + await imageButton(page, ".mx-name-imageRender1").click(); + const lightboxImage = page.locator(".mx-image-viewer-lightbox img"); + await expect(lightboxImage).toBeVisible(); await expect(lightboxImage).toHaveAttribute("src", /ImageViewer\$Images\$landscape_2\.png/); }); }); diff --git a/packages/pluggableWidgets/image-web/openspec/changes/fix-image-allow-upload-deprecation/.openspec.yaml b/packages/pluggableWidgets/image-web/openspec/changes/fix-image-allow-upload-deprecation/.openspec.yaml new file mode 100644 index 0000000000..e7ed6cbc07 --- /dev/null +++ b/packages/pluggableWidgets/image-web/openspec/changes/fix-image-allow-upload-deprecation/.openspec.yaml @@ -0,0 +1,2 @@ +schema: tdd-refactor +created: 2026-07-23 diff --git a/packages/pluggableWidgets/image-web/openspec/changes/fix-image-allow-upload-deprecation/proposal.md b/packages/pluggableWidgets/image-web/openspec/changes/fix-image-allow-upload-deprecation/proposal.md new file mode 100644 index 0000000000..a6fe237fa9 --- /dev/null +++ b/packages/pluggableWidgets/image-web/openspec/changes/fix-image-allow-upload-deprecation/proposal.md @@ -0,0 +1,39 @@ +## Why + +The Image widget's `imageObject` and `defaultImageDynamic` properties are declared as `type="image"` without `allowUpload`. Mendix has deprecated this form of the image/file property type — `DynamicValue` will be removed in Mendix 12 — and apps using the widget on Studio Pro 11.8+ already see a deprecation warning at design time. The fix is to set `allowUpload="true"` on both properties, per Mendix's own migration guidance. + +## What Changes + +- `allowUpload="true"` added to `imageObject` and `defaultImageDynamic` in `Image.xml`. +- Generated typings (`ImageProps.d.ts`) change `imageObject`/`defaultImageDynamic` from `DynamicValue` to `EditableImageValue`. +- `Image.tsx` reads `.status` and `.value.uri` off both props today — `EditableImageValue` exposes the same shape (extends the same status/value base as `DynamicValue`), so no logic change is required. Widget stays display-only; no upload UI is added. +- `package.json` `marketplace.minimumMXVersion` bumped to `11.12.0` (LTS, confirmed with ticket reporter — 11.11 has native `allowUpload` support but 11.12 is the intended floor). +- CHANGELOG entry added under `[Unreleased]`. Widget version stays unbumped for now — version bumps happen at release time per repo convention; this change requires a major bump (2.0.0) once released, since `minimumMXVersion` is raised. + +Verified in Studio Pro (11.12, fresh test app) that `allowUpload="true"` does not remove the "Static" image-source option — both Static and Dynamic configuration remain available in the property editor, so existing apps configuring `imageObject`/`defaultImageDynamic` as a static asset are not broken by this change. + +## Capabilities + +### New Capabilities + + + +### Modified Capabilities + + + +## Root cause + +Widget XML predates the `allowUpload` attribute (introduced Mendix 11.8, native widget support 11.11). Not fixed proactively when the attribute was introduced; ticket WC-3471 flags the resulting deprecation warning. + +## Impact + +- **Files**: + - `packages/pluggableWidgets/image-web/src/Image.xml` + - `packages/pluggableWidgets/image-web/typings/ImageProps.d.ts` (regenerated) + - `packages/pluggableWidgets/image-web/package.json` (version, minimumMXVersion) + - `packages/pluggableWidgets/image-web/CHANGELOG.md` +- **Behavior**: none — widget remains display-only, no upload UI added, no runtime logic change +- **Studio Pro UX**: none — Static/Dynamic image-source configuration both remain available (manually verified) +- **Breaking change**: minimum Mendix version raised from 9.24.0 to 11.12.0 — apps on older Mendix versions cannot upgrade to this widget version once released +- **Affected widget**: `@mendix/image-web` — requires a major version bump (2.0.0) at release time; not bumped in this change diff --git a/packages/pluggableWidgets/image-web/openspec/changes/fix-image-allow-upload-deprecation/tests.md b/packages/pluggableWidgets/image-web/openspec/changes/fix-image-allow-upload-deprecation/tests.md new file mode 100644 index 0000000000..a94aa7749a --- /dev/null +++ b/packages/pluggableWidgets/image-web/openspec/changes/fix-image-allow-upload-deprecation/tests.md @@ -0,0 +1,17 @@ +- [x] **Container renders the bound image when imageObject is Available** + - **Type:** unit + - **Given:** `ImageContainerProps` with `datasource: "image"`, `imageObject` as an `EditableImageValue`-shaped object with `status: ValueStatus.Available` and `value.uri: "https://example.com/a.png"` + - **When:** `Image` (src/Image.tsx container) is rendered + - **Then:** the rendered `` `src` equals `"https://example.com/a.png"` + +- [x] **Container falls back to defaultImageDynamic when imageObject is Unavailable** + - **Type:** unit + - **Given:** `imageObject` with `status: ValueStatus.Unavailable`, `defaultImageDynamic` as `EditableImageValue`-shaped with `status: ValueStatus.Available` and `value.uri: "https://example.com/default.png"` + - **When:** `Image` is rendered + - **Then:** the rendered `` `src` equals `"https://example.com/default.png"` + +- [x] **Container renders nothing bound when both imageObject and defaultImageDynamic are unavailable** + - **Type:** unit + - **Given:** `imageObject` with `status: ValueStatus.Unavailable`, `defaultImageDynamic` undefined + - **When:** `Image` is rendered + - **Then:** the rendered image element has no meaningful `src` (placeholder/empty state), matching current behavior before this change diff --git a/packages/pluggableWidgets/image-web/package.json b/packages/pluggableWidgets/image-web/package.json index bf4bcc3cac..f094744e2d 100644 --- a/packages/pluggableWidgets/image-web/package.json +++ b/packages/pluggableWidgets/image-web/package.json @@ -17,7 +17,7 @@ }, "packagePath": "com.mendix.widget.web", "marketplace": { - "minimumMXVersion": "9.24.0", + "minimumMXVersion": "11.12.0", "appNumber": 118579, "appName": "Image", "reactReady": true @@ -31,7 +31,7 @@ "create-gh-release": "rui-create-gh-release", "create-translation": "rui-create-translation", "dev": "pluggable-widgets-tools start:web", - "e2e": "run-e2e ci", + "e2e": "MENDIX_VERSION=11.12.0 run-e2e ci", "e2edev": "run-e2e dev --with-preps", "format": "prettier --ignore-path ./node_modules/@mendix/prettier-config-web-widgets/global-prettierignore --write .", "lint": "eslint src/ package.json", diff --git a/packages/pluggableWidgets/image-web/src/Image.xml b/packages/pluggableWidgets/image-web/src/Image.xml index 5daaad62fe..351e505915 100644 --- a/packages/pluggableWidgets/image-web/src/Image.xml +++ b/packages/pluggableWidgets/image-web/src/Image.xml @@ -17,11 +17,11 @@ Icon - + Image source - + Default image This is the image that is displayed if no image is uploaded. diff --git a/packages/pluggableWidgets/image-web/src/__tests__/Image.spec.tsx b/packages/pluggableWidgets/image-web/src/__tests__/Image.spec.tsx new file mode 100644 index 0000000000..40c592546b --- /dev/null +++ b/packages/pluggableWidgets/image-web/src/__tests__/Image.spec.tsx @@ -0,0 +1,63 @@ +import { render } from "@testing-library/react"; +import { editableImage } from "@mendix/widget-plugin-test-utils"; +import { ImageContainerProps } from "../../typings/ImageProps"; +import { Image } from "../Image"; + +function makeProps(overrides: Partial = {}): ImageContainerProps { + return { + name: "image1", + class: "", + style: undefined, + tabIndex: 0, + datasource: "image", + isBackgroundImage: false, + onClickType: "action", + widthUnit: "auto", + width: 100, + heightUnit: "auto", + height: 100, + minHeightUnit: "none", + minHeight: 0, + maxHeightUnit: "none", + maxHeight: 0, + iconSize: 14, + displayAs: "fullImage", + responsive: true, + ...overrides + } as ImageContainerProps; +} + +describe("Image container", () => { + it("renders the bound image when imageObject is Available", () => { + const { getByRole } = render( + + ); + const image = getByRole("img") as HTMLImageElement; + expect(image.src).toBe("https://example.com/a.png"); + }); + + it("falls back to defaultImageDynamic when imageObject is Unavailable", () => { + const { getByRole } = render( + b.isUnavailable().build()), + defaultImageDynamic: editableImage.with({ uri: "https://example.com/default.png" }) + })} + /> + ); + const image = getByRole("img") as HTMLImageElement; + expect(image.src).toBe("https://example.com/default.png"); + }); + + it("renders no image src when both imageObject and defaultImageDynamic are unavailable", () => { + const { container } = render( + b.isUnavailable().build()) + })} + /> + ); + const image = container.querySelector("img"); + expect(image?.getAttribute("src")).toBeFalsy(); + }); +}); diff --git a/packages/pluggableWidgets/image-web/typings/ImageProps.d.ts b/packages/pluggableWidgets/image-web/typings/ImageProps.d.ts index 0d3a7d156a..59472fd39e 100644 --- a/packages/pluggableWidgets/image-web/typings/ImageProps.d.ts +++ b/packages/pluggableWidgets/image-web/typings/ImageProps.d.ts @@ -3,7 +3,7 @@ * WARNING: All changes made to this file will be overwritten * @author Mendix Widgets Framework Team */ -import { ActionValue, DynamicValue, WebIcon, WebImage } from "mendix"; +import { ActionValue, DynamicValue, EditableImageValue, WebIcon, WebImage } from "mendix"; import { ComponentType, CSSProperties, ReactNode } from "react"; export type DatasourceEnum = "image" | "imageUrl" | "icon"; @@ -26,8 +26,8 @@ export interface ImageContainerProps { style?: CSSProperties; tabIndex?: number; datasource: DatasourceEnum; - imageObject?: DynamicValue; - defaultImageDynamic?: DynamicValue; + imageObject?: EditableImageValue; + defaultImageDynamic?: EditableImageValue; imageUrl?: DynamicValue; imageIcon?: DynamicValue; isBackgroundImage: boolean; diff --git a/packages/shared/filter-commons/package.json b/packages/shared/filter-commons/package.json index 55f1d7a872..32d195cee7 100644 --- a/packages/shared/filter-commons/package.json +++ b/packages/shared/filter-commons/package.json @@ -32,7 +32,7 @@ "test": "jest" }, "dependencies": { - "mendix": "^10.24.75382" + "mendix": "*" }, "peerDependencies": { "mobx": "6.12.3", diff --git a/packages/shared/widget-plugin-dropdown-filter/package.json b/packages/shared/widget-plugin-dropdown-filter/package.json index 350d93fae0..513498cca2 100644 --- a/packages/shared/widget-plugin-dropdown-filter/package.json +++ b/packages/shared/widget-plugin-dropdown-filter/package.json @@ -38,7 +38,7 @@ "@mendix/widget-plugin-hooks": "workspace:*", "@mendix/widget-plugin-mobx-kit": "workspace:^", "downshift": "^9.0.9", - "mendix": "^10.24.75382", + "mendix": "*", "mobx": "6.12.3", "mobx-react-lite": "4.0.7" }, diff --git a/packages/shared/widget-plugin-filtering/package.json b/packages/shared/widget-plugin-filtering/package.json index 110ddd833d..5b31ab8271 100644 --- a/packages/shared/widget-plugin-filtering/package.json +++ b/packages/shared/widget-plugin-filtering/package.json @@ -41,7 +41,7 @@ "@mendix/widget-plugin-mobx-kit": "workspace:^", "@mendix/widget-plugin-platform": "workspace:*", "downshift": "^9.0.8", - "mendix": "10.24.75382", + "mendix": "*", "mobx": "6.12.3", "mobx-react-lite": "4.0.7" }, diff --git a/packages/shared/widget-plugin-test-utils/package.json b/packages/shared/widget-plugin-test-utils/package.json index b1b034875b..c7d4ca5c3e 100644 --- a/packages/shared/widget-plugin-test-utils/package.json +++ b/packages/shared/widget-plugin-test-utils/package.json @@ -30,7 +30,7 @@ "test": "jest" }, "dependencies": { - "mendix": "10.24.75382" + "mendix": "*" }, "devDependencies": { "@mendix/eslint-config-web-widgets": "workspace:*", diff --git a/packages/shared/widget-plugin-test-utils/src/builders/EditableImageValueBuilder.ts b/packages/shared/widget-plugin-test-utils/src/builders/EditableImageValueBuilder.ts new file mode 100644 index 0000000000..8a4668b198 --- /dev/null +++ b/packages/shared/widget-plugin-test-utils/src/builders/EditableImageValueBuilder.ts @@ -0,0 +1,44 @@ +import type { EditableImageValue, ImageValue } from "mendix"; +import { Status } from "../constants.js"; +import { Writable } from "./type-utils.js"; + +export class EditableImageValueBuilder { + private readonly editableImageValue: Writable> = { + value: undefined, + status: Status.Available, + readOnly: false, + validation: undefined, + setValidator: jest.fn(), + setValue: jest.fn((value?: T) => this.withValue(value)), + setThumbnailSize: jest.fn() + }; + + withValue(value?: T): EditableImageValueBuilder { + this.editableImageValue.value = value; + return this; + } + + isReadOnly(): EditableImageValueBuilder { + this.editableImageValue.readOnly = true; + return this; + } + + isLoading(): EditableImageValueBuilder { + this.editableImageValue.status = Status.Loading; + return this.isReadOnly(); + } + + isUnavailable(): EditableImageValueBuilder { + this.editableImageValue.status = Status.Unavailable; + return this.isReadOnly(); + } + + withValidation(validation?: string): EditableImageValueBuilder { + this.editableImageValue.validation = validation; + return this; + } + + build(): EditableImageValue { + return this.editableImageValue; + } +} diff --git a/packages/shared/widget-plugin-test-utils/src/main.ts b/packages/shared/widget-plugin-test-utils/src/main.ts index d2b9b7e794..762c1f1990 100644 --- a/packages/shared/widget-plugin-test-utils/src/main.ts +++ b/packages/shared/widget-plugin-test-utils/src/main.ts @@ -1,3 +1,4 @@ +export * from "./builders/EditableImageValueBuilder.js"; export * from "./builders/EditableValueBuilder.js"; export * from "./builders/ReferenceValueBuilder.js"; export * from "./builders/ReferenceSetValueBuilder.js"; @@ -19,6 +20,7 @@ export * from "./primitives/action.js"; export * from "./primitives/attrId.js"; export * from "./primitives/dynamic.js"; export * from "./primitives/editable.js"; +export * from "./primitives/editableImage.js"; export * from "./primitives/obj.js"; export * from "./primitives/objArray.js"; export * from "./utils/IntersectionObserverStub.js"; diff --git a/packages/shared/widget-plugin-test-utils/src/primitives/editableImage.ts b/packages/shared/widget-plugin-test-utils/src/primitives/editableImage.ts new file mode 100644 index 0000000000..9f49e039f7 --- /dev/null +++ b/packages/shared/widget-plugin-test-utils/src/primitives/editableImage.ts @@ -0,0 +1,24 @@ +import type { EditableImageValue, ImageValue } from "mendix"; +import { EditableImageValueBuilder } from "../builders/EditableImageValueBuilder.js"; + +type Factory = (builder: EditableImageValueBuilder) => EditableImageValue; + +/** + * Short function to mock EditableImageValue. + * @param factory - optional factory. Accept builder as first argument. + * @returns {EditableImageValue} + */ +export function editableImage(factory?: Factory): EditableImageValue { + factory ??= builder => builder.build(); + return factory(new EditableImageValueBuilder()); +} + +/** + * Shorthand for builder.withValue(). + * @param value + */ +editableImage.with = _with; + +function _with(value: T): EditableImageValue { + return editableImage(builder => builder.withValue(value).build()); +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9f699603b1..2ede947811 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -27,6 +27,7 @@ overrides: d3-color@<3.1.0: '>=3.1.0' glob@10: ^10.5.0 fast-uri: 3.1.4 + mendix: 11.10.0 react: '>=18.0.0 <19.0.0' react-big-calendar@1>clsx: 2.1.1 react-dom: '>=18.0.0 <19.0.0' @@ -2777,8 +2778,8 @@ importers: packages/shared/filter-commons: dependencies: mendix: - specifier: ^10.24.75382 - version: 10.24.75382 + specifier: 11.10.0 + version: 11.10.0 mobx: specifier: 6.12.3 version: 6.12.3(patch_hash=39c55279e8f75c9a322eba64dd22e1a398f621c64bbfc3632e55a97f46edfeb9) @@ -2879,8 +2880,8 @@ importers: specifier: ^9.0.9 version: 9.3.6(react@18.3.1) mendix: - specifier: ^10.24.75382 - version: 10.24.75382 + specifier: 11.10.0 + version: 11.10.0 mobx: specifier: 6.12.3 version: 6.12.3(patch_hash=39c55279e8f75c9a322eba64dd22e1a398f621c64bbfc3632e55a97f46edfeb9) @@ -2953,8 +2954,8 @@ importers: specifier: ^9.0.8 version: 9.3.6(react@18.3.1) mendix: - specifier: 10.24.75382 - version: 10.24.75382 + specifier: 11.10.0 + version: 11.10.0 mobx: specifier: 6.12.3 version: 6.12.3(patch_hash=39c55279e8f75c9a322eba64dd22e1a398f621c64bbfc3632e55a97f46edfeb9) @@ -3147,8 +3148,8 @@ importers: packages/shared/widget-plugin-test-utils: dependencies: mendix: - specifier: 10.24.75382 - version: 10.24.75382 + specifier: 11.10.0 + version: 11.10.0 devDependencies: '@mendix/eslint-config-web-widgets': specifier: workspace:* @@ -8565,9 +8566,6 @@ packages: memoize-one@6.0.0: resolution: {integrity: sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==} - mendix@10.24.75382: - resolution: {integrity: sha512-ICMxqkWUejsc3KeFD9BJYvC+T4soi/NB2iapwWPC7oN0lCrFx36upzwI4rU77oMdRHsrVSsFVYLBy7sJJOABHw==} - mendix@11.10.0: resolution: {integrity: sha512-OsLdgNJfhwG4/TcOIMMk0gu0ewHq3Xlv2CNW1YJy2ujqD19ngFvBVB9YTjYxmf+RMn1EG0FQYrn6zCSFzmbL4A==} @@ -17920,11 +17918,6 @@ snapshots: memoize-one@6.0.0: {} - mendix@10.24.75382: - dependencies: - '@types/big.js': 6.2.2 - '@types/react': 19.2.17 - mendix@11.10.0: dependencies: '@types/big.js': 6.2.2