Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
schema: tdd-refactor
created: 2026-07-23
39 changes: 39 additions & 0 deletions openspec/changes/fix-image-allow-upload-deprecation/proposal.md
Original file line number Diff line number Diff line change
@@ -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<WebImage>` 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<WebImage>` to `EditableImageValue<ImageValue>`.
- `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

<!-- none -->

### Modified Capabilities

<!-- none -->

## 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
17 changes: 17 additions & 0 deletions openspec/changes/fix-image-allow-upload-deprecation/tests.md
Original file line number Diff line number Diff line change
@@ -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<ImageValue>`-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 `<img>` `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<ImageValue>`-shaped with `status: ValueStatus.Available` and `value.uri: "https://example.com/default.png"`
- **When:** `Image` is rendered
- **Then:** the rendered `<img>` `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
2 changes: 2 additions & 0 deletions packages/pluggableWidgets/image-web/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),

- We fixed an issue where previews in Design and Structure modes did not respect the configured minimum and maximum image height.

- 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
Expand Down
2 changes: 1 addition & 1 deletion packages/pluggableWidgets/image-web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
},
"packagePath": "com.mendix.widget.web",
"marketplace": {
"minimumMXVersion": "9.24.0",
"minimumMXVersion": "11.12.0",
"appNumber": 118579,
"appName": "Image",
"reactReady": true
Expand Down
4 changes: 2 additions & 2 deletions packages/pluggableWidgets/image-web/src/Image.xml
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@
<enumerationValue key="icon">Icon</enumerationValue>
</enumerationValues>
</property>
<property key="imageObject" type="image" required="false">
<property key="imageObject" type="image" required="false" allowUpload="true">
<caption>Image source</caption>
<description />
</property>
<property key="defaultImageDynamic" type="image" required="false">
<property key="defaultImageDynamic" type="image" required="false" allowUpload="true">
<caption>Default image</caption>
<description>This is the image that is displayed if no image is uploaded.</description>
</property>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { render } from "@testing-library/react";
import { ValueStatus } from "mendix";
import { ImageContainerProps } from "../../typings/ImageProps";
import { Image } from "../Image";

type ImageProp = NonNullable<ImageContainerProps["imageObject"]>;

function makeImageValue(overrides: Partial<ImageProp> = {}): ImageProp {
return {
status: ValueStatus.Available,
value: { uri: "https://example.com/a.png", name: "a.png" },
readOnly: false,
validation: undefined,
setValidator: jest.fn(),
setValue: jest.fn(),
setThumbnailSize: jest.fn(),
...overrides
} as ImageProp;
}

function makeProps(overrides: Partial<ImageContainerProps> = {}): 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(<Image {...makeProps({ imageObject: makeImageValue() })} />);
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(
<Image
{...makeProps({
imageObject: makeImageValue({ status: ValueStatus.Unavailable, value: undefined }),
defaultImageDynamic: makeImageValue({ value: { 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(
<Image
{...makeProps({
imageObject: makeImageValue({ status: ValueStatus.Unavailable, value: undefined })
})}
/>
);
const image = container.querySelector("img");
expect(image?.getAttribute("src")).toBeFalsy();
});
});
6 changes: 3 additions & 3 deletions packages/pluggableWidgets/image-web/typings/ImageProps.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -26,8 +26,8 @@ export interface ImageContainerProps {
style?: CSSProperties;
tabIndex?: number;
datasource: DatasourceEnum;
imageObject?: DynamicValue<WebImage>;
defaultImageDynamic?: DynamicValue<WebImage>;
imageObject?: EditableImageValue<WebImage>;
defaultImageDynamic?: EditableImageValue<WebImage>;
imageUrl?: DynamicValue<string>;
imageIcon?: DynamicValue<WebIcon>;
isBackgroundImage: boolean;
Expand Down
Loading