Skip to content

Commit 56be6df

Browse files
authored
Merge pull request #307 from eccenca/release/v24.4.0
Release v24.4.0 into main branch
2 parents 89dde0b + 7b25d6b commit 56be6df

File tree

13 files changed

+1585
-692
lines changed

13 files changed

+1585
-692
lines changed

CHANGELOG.md

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,39 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
88

99
### Added
1010

11+
- `<ExtendedCodeEditor />`
12+
- `height` and `readOnly` properties to forward them to `<CodeEditor/>`
13+
- `<CodeAutocompleteField />`:
14+
- `outerDivAttributes` property: allows to set parameter of the container element
15+
- `height` and `readOnly` properties to forward them to `<ExtendedCodeEditor/>`
16+
- `<ActivityControlWidget />`
17+
- `additionalActions` property to include other more complex components between the action buttons and the context menu of the widget
18+
- `<Tooltip />`
19+
- `swapPlaceholderDelay` property to allow configuration of the delay time before the placeholder element is replaced by the actual tooltip component
20+
21+
### Fixed
22+
23+
- `<CodeEditor />`
24+
- Editor is re-created after certain property changes and is reset, i.e. loses it current state.
25+
- Enter key handling (adding new line) was broken when `onKeyDown` is defined.
26+
- `<CodeAutocompleteField />`
27+
- First auto-completion item not marked as active when drop down first shown.
28+
- Read-only mode does not work correctly. It is still possible to change the value via pressing Enter (in multiline mode) or clicking the clear button.
29+
30+
### Changed
31+
32+
- `<NodeContent />`
33+
- prevent start of a react flow drag action of a node when user clicks in the node menu section
34+
35+
### Deprecated
36+
37+
- `<CodeEditor />`
38+
- `onChange` property: support for `(v: any) => void` type will be exchanged to more specific `(v: string) => void`
39+
40+
## [24.3.0] - 2025-06-05
41+
42+
### Added
43+
1144
- added support for React Flow v12
1245
- `<NodeContent />` can used with `flowVersion="v12"`
1346
- more v12-only components: `EdgeDefaultV12`, `NodeDefaultV12`, `EdgeDefs`
@@ -16,7 +49,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
1649
### Deprecated
1750

1851
- `<EdgeDefaultV12 />` and `<NodeDefaultV12 />` will be removed when React Flow v12 is supported directly by `<EdgeDefault />` and `<NodeDefault />`
19-
- `flowVersion`: `legacy` and `next` will be removed/replaced by `v##` values
52+
- `flowVersion` property: `legacy` and `next` will be removed/replaced by `v##` values
2053

2154
## [24.2.0] - 2025-06-04
2255

package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@eccenca/gui-elements",
33
"description": "GUI elements based on other libraries, usable in React application, written in Typescript.",
4-
"version": "24.3.0",
4+
"version": "24.4.0",
55
"license": "Apache-2.0",
66
"homepage": "https://github.com/eccenca/gui-elements",
77
"bugs": "https://github.com/eccenca/gui-elements/issues",
@@ -149,8 +149,8 @@
149149
"eslint-plugin-simple-import-sort": "^12.1.1",
150150
"husky": "4",
151151
"identity-obj-proxy": "^3.0.0",
152-
"jest": "^29.7.0",
153-
"jest-environment-jsdom": "^29.7.0",
152+
"jest": "^30.0.5",
153+
"jest-environment-jsdom": "^30.0.5",
154154
"jest-pnp-resolver": "^1.2.3",
155155
"lint-staged": "^15.5.1",
156156
"node-sass-package-importer": "^5.3.3",

src/cmem/ActivityControl/ActivityControlWidget.stories.tsx

Lines changed: 45 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,17 @@
1-
import React from "react";
1+
import React, { useMemo, useState } from "react";
22
import { loremIpsum } from "react-lorem-ipsum";
3+
import { OverlaysProvider } from "@blueprintjs/core";
34
import { Meta, StoryFn } from "@storybook/react";
45

56
import { helpersArgTypes } from "../../../.storybook/helpers";
6-
import { ActivityControlWidget, Tag, TagList } from "../../../index";
7+
import {
8+
ActivityControlWidget,
9+
ActivityControlWidgetAction,
10+
IconButton,
11+
SimpleDialog,
12+
Tag,
13+
TagList,
14+
} from "../../../index";
715

816
export default {
917
title: "Cmem/ActivityControlWidget",
@@ -19,7 +27,7 @@ const Template: StoryFn<typeof ActivityControlWidget> = (args) => <ActivityContr
1927

2028
export const FullExample = Template.bind({});
2129

22-
const actions = [
30+
const actions: ActivityControlWidgetAction[] = [
2331
{
2432
"data-test-id": "activity-reload-activity",
2533
icon: "item-reload",
@@ -30,13 +38,15 @@ const actions = [
3038
{
3139
"data-test-id": "activity-start-activity",
3240
icon: "item-start",
41+
// eslint-disable-next-line no-console
3342
action: () => console.log("start"),
3443
tooltip: "Start Activity",
3544
disabled: false,
3645
},
3746
{
3847
"data-test-id": "activity-stop-activity",
3948
icon: "item-stop",
49+
// eslint-disable-next-line no-console
4050
action: () => console.log("cancel"),
4151
tooltip: "Stop Activity",
4252
disabled: false,
@@ -57,8 +67,8 @@ const commonWidgetArgs = {
5767
progressSpinner: {
5868
intent: "none",
5969
value: 0.5,
60-
},
61-
};
70+
} as const,
71+
} as const;
6272

6373
FullExample.args = {
6474
...commonWidgetArgs,
@@ -80,3 +90,33 @@ WidgetWithTags.args = {
8090
...commonWidgetArgs,
8191
tags: widgetTags,
8292
};
93+
94+
export const WidgetWithAdditionalActions: StoryFn<typeof ActivityControlWidget> = (args) => {
95+
const [isOpen, setIsOpen] = useState(false);
96+
97+
const params = useMemo(
98+
() => ({
99+
...commonWidgetArgs,
100+
...args,
101+
additionalActions: args.additionalActions ?? [
102+
<IconButton name="application-explore" onClick={() => setIsOpen(true)} />,
103+
],
104+
}),
105+
[]
106+
);
107+
108+
return (
109+
<OverlaysProvider>
110+
<ActivityControlWidget {...params} />
111+
<SimpleDialog
112+
title="Additional actions dialog"
113+
isOpen={isOpen}
114+
onClose={() => setIsOpen(false)}
115+
canOutsideClickClose
116+
canEscapeKeyClose
117+
>
118+
Modal content
119+
</SimpleDialog>
120+
</OverlaysProvider>
121+
);
122+
};

src/cmem/ActivityControl/ActivityControlWidget.tsx

Lines changed: 47 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,10 @@ export interface ActivityControlWidgetProps extends TestableComponent {
8181
* execution timer messages for waiting and running times.
8282
*/
8383
timerExecutionMsg?: JSX.Element | null;
84+
/**
85+
* additional actions that can serve as a complex component, positioned between the default actions and the context menu
86+
*/
87+
additionalActions?: React.ReactElement<unknown>[];
8488
}
8589

8690
interface IActivityContextMenu extends TestableComponent {
@@ -110,11 +114,13 @@ interface IActivityMenuAction extends ActivityControlWidgetAction {
110114
/** Shows the status of activities and supports actions on these activities. */
111115
export function ActivityControlWidget(props: ActivityControlWidgetProps) {
112116
const {
113-
"data-test-id": dataTestId,
117+
"data-test-id": dataTestIdLegacy,
118+
"data-testid": dataTestId,
114119
progressBar,
115120
progressSpinner,
116121
activityActions,
117122
activityContextMenu,
123+
additionalActions,
118124
small,
119125
border,
120126
hasSpacing,
@@ -126,10 +132,19 @@ export function ActivityControlWidget(props: ActivityControlWidgetProps) {
126132
} = props;
127133
const spinnerClassNames = (progressSpinner?.className ?? "") + ` ${eccgui}-spinner--permanent`;
128134
const widget = (
129-
<OverviewItem data-test-id={dataTestId} hasSpacing={border || hasSpacing} densityHigh={small}>
135+
<OverviewItem
136+
data-test-id={dataTestIdLegacy}
137+
data-testid={dataTestId}
138+
hasSpacing={border || hasSpacing}
139+
densityHigh={small}
140+
>
130141
{progressBar && <ProgressBar {...progressBar} />}
131142
{(progressSpinner || progressSpinnerFinishedIcon) && (
132-
<OverviewItemDepiction keepColors>
143+
<OverviewItemDepiction
144+
data-testid={dataTestId ? `${dataTestId}-progress-spinner` : undefined}
145+
data-test-id={dataTestIdLegacy ? `${dataTestIdLegacy}-progress-spinner` : undefined}
146+
keepColors
147+
>
133148
{progressSpinnerFinishedIcon ? (
134149
React.cloneElement(progressSpinnerFinishedIcon as JSX.Element, { small, large: !small })
135150
) : (
@@ -145,13 +160,21 @@ export function ActivityControlWidget(props: ActivityControlWidgetProps) {
145160
)}
146161
<OverviewItemDescription>
147162
{props.label && (
148-
<OverviewItemLine small={small}>
163+
<OverviewItemLine
164+
data-testid={dataTestId ? `${dataTestId}-label` : undefined}
165+
data-test-id={dataTestIdLegacy ? `${dataTestIdLegacy}-label` : undefined}
166+
small={small}
167+
>
149168
{React.cloneElement(labelWrapper, {}, props.label)}
150169
{timerExecutionMsg && (props.statusMessage || tags) && <>&nbsp;({timerExecutionMsg})</>}
151170
</OverviewItemLine>
152171
)}
153172
{(props.statusMessage || tags) && (
154-
<OverviewItemLine small>
173+
<OverviewItemLine
174+
data-testid={dataTestId ? `${dataTestId}-status-message` : undefined}
175+
data-test-id={dataTestIdLegacy ? `${dataTestIdLegacy}-status-message` : undefined}
176+
small
177+
>
155178
{tags}
156179
{props.statusMessage && (
157180
<OverflowText passDown>
@@ -172,28 +195,43 @@ export function ActivityControlWidget(props: ActivityControlWidgetProps) {
172195
</OverviewItemLine>
173196
)}
174197
{timerExecutionMsg && !(props.statusMessage || tags) && (
175-
<OverviewItemLine small>{timerExecutionMsg}</OverviewItemLine>
198+
<OverviewItemLine
199+
data-testid={dataTestId ? `${dataTestId}-status-message` : undefined}
200+
data-test-id={dataTestIdLegacy ? `${dataTestIdLegacy}-status-message` : undefined}
201+
small
202+
>
203+
{timerExecutionMsg}
204+
</OverviewItemLine>
176205
)}
177206
</OverviewItemDescription>
178-
<OverviewItemActions>
207+
<OverviewItemActions
208+
data-testid={dataTestId ? `${dataTestId}-actions` : undefined}
209+
data-test-id={dataTestIdLegacy ? `${dataTestIdLegacy}-actions` : undefined}
210+
>
179211
{activityActions &&
180212
activityActions.map((action, idx) => {
181213
return (
182214
<IconButton
183-
key={typeof action.icon === "string" ? action.icon : action["data-test-id"] ?? idx}
215+
key={
216+
typeof action.icon === "string"
217+
? action.icon
218+
: action["data-test-id"] ?? action["data-testid"] ?? idx
219+
}
184220
data-test-id={action["data-test-id"]}
221+
data-testid={action["data-testid"]}
185222
name={action.icon}
186223
text={action.tooltip}
187224
onClick={action.action}
188225
disabled={action.disabled}
189-
hasStateWarning={action.hasStateWarning}
226+
intent={action.hasStateWarning ? "warning" : undefined}
190227
tooltipProps={{
191228
hoverOpenDelay: 200,
192229
placement: "bottom",
193230
}}
194231
/>
195232
);
196233
})}
234+
{additionalActions}
197235
{activityContextMenu && activityContextMenu.menuItems.length > 0 && (
198236
<ContextMenu
199237
data-test-id={activityContextMenu["data-test-id"]}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import React from "react";
2+
import { fireEvent, render, screen } from "@testing-library/react";
3+
4+
import "@testing-library/jest-dom";
5+
6+
import { IconButton, Tag, TagList } from "../../../index";
7+
import { ActivityControlWidget, ActivityControlWidgetAction } from "../ActivityControlWidget";
8+
9+
describe("ActivityControlWidget", () => {
10+
it("Renders basic widget with actions and handles clicks", () => {
11+
const mockAction1 = jest.fn();
12+
const mockAction2 = jest.fn();
13+
const actions: ActivityControlWidgetAction[] = [
14+
{
15+
"data-testid": "action-1",
16+
icon: "item-reload",
17+
action: mockAction1,
18+
tooltip: "Action 1",
19+
},
20+
{
21+
"data-testid": "action-2",
22+
icon: "item-start",
23+
action: mockAction2,
24+
tooltip: "Action 2",
25+
},
26+
];
27+
28+
render(
29+
<ActivityControlWidget
30+
label="Basic widget"
31+
data-testid="basic-widget"
32+
activityActions={actions}
33+
statusMessage="Status message"
34+
/>
35+
);
36+
37+
const button1 = screen.getByTestId("action-1");
38+
const button2 = screen.getByTestId("action-2");
39+
40+
const label = screen.getByTestId("basic-widget-label");
41+
const statusMessage = screen.getByTestId("basic-widget-status-message");
42+
const actionsContainer = screen.getByTestId("basic-widget-actions");
43+
44+
expect(label).toBeInTheDocument();
45+
expect(statusMessage).toBeInTheDocument();
46+
expect(actionsContainer).toBeInTheDocument();
47+
48+
expect(label).toHaveTextContent("Basic widget");
49+
expect(statusMessage).toHaveTextContent("Status message");
50+
51+
expect(button1).toBeInTheDocument();
52+
expect(button2).toBeInTheDocument();
53+
54+
fireEvent.click(button1);
55+
expect(mockAction1).toHaveBeenCalledTimes(1);
56+
57+
fireEvent.click(button2);
58+
expect(mockAction2).toHaveBeenCalledTimes(1);
59+
});
60+
61+
it("Renders widget with tags", () => {
62+
const tags = (
63+
<TagList>
64+
<Tag>Tag one</Tag>
65+
<Tag>Other tag</Tag>
66+
</TagList>
67+
);
68+
render(<ActivityControlWidget label="Widget with tags" tags={tags} data-testid="widget-with-tags" />);
69+
70+
const label = screen.getByTestId("widget-with-tags-label");
71+
const statusMessage = screen.getByTestId("widget-with-tags-status-message");
72+
73+
expect(label).toBeInTheDocument();
74+
expect(statusMessage).toBeInTheDocument();
75+
76+
expect(label).toHaveTextContent("Widget with tags");
77+
expect(statusMessage).toHaveTextContent("Tag one");
78+
expect(statusMessage).toHaveTextContent("Other tag");
79+
});
80+
81+
it("Renders widget with additional actions and handles click", () => {
82+
const mockAction = jest.fn();
83+
const additionalActions = [
84+
<IconButton
85+
key="add-btn"
86+
name="application-explore"
87+
onClick={mockAction}
88+
data-testid="additional-action"
89+
/>,
90+
];
91+
render(<ActivityControlWidget additionalActions={additionalActions} />);
92+
93+
const customButton = screen.getByTestId("additional-action");
94+
expect(customButton).toBeInTheDocument();
95+
96+
fireEvent.click(customButton);
97+
expect(mockAction).toHaveBeenCalledTimes(1);
98+
});
99+
});

0 commit comments

Comments
 (0)