@tsed/react-formio 2.3.1 → 2.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. package/coverage.json +4 -4
  2. package/dist/components/actions-table/actionsTable.stories.d.ts +8 -53
  3. package/dist/components/form-access/formAccess.component.d.ts +2 -13
  4. package/dist/components/form-access/formAccess.stories.d.ts +3 -44
  5. package/dist/components/form-control/formControl.component.d.ts +3 -11
  6. package/dist/components/select/select.component.d.ts +5 -18
  7. package/dist/components/table/components/defaultCellOperations.component.d.ts +12 -1
  8. package/dist/components/table/components/defaultOperationButton.component.d.ts +4 -4
  9. package/dist/components/table/hooks/useOperations.hook.d.ts +2 -2
  10. package/dist/index.js +1178 -1330
  11. package/dist/index.js.map +1 -1
  12. package/dist/index.modern.js +291 -309
  13. package/dist/index.modern.js.map +1 -1
  14. package/package.json +11 -8
  15. package/src/components/__fixtures__/form-actions.json +240 -0
  16. package/src/components/actions-table/__fixtures__/data.json +12 -0
  17. package/src/components/actions-table/actionsTable.component.spec.tsx +42 -11
  18. package/src/components/actions-table/actionsTable.component.tsx +2 -1
  19. package/src/components/actions-table/actionsTable.stories.tsx +71 -289
  20. package/src/components/form-access/formAccess.component.tsx +2 -12
  21. package/src/components/form-access/formAccess.stories.tsx +55 -49
  22. package/src/components/form-control/formControl.component.tsx +4 -11
  23. package/src/components/select/select.component.tsx +9 -22
  24. package/src/components/table/components/defaultCellOperations.component.tsx +17 -4
  25. package/src/components/table/components/defaultOperationButton.component.tsx +9 -4
  26. package/src/components/table/hooks/useOperations.hook.tsx +3 -3
  27. package/dist/package.json +0 -3
@@ -1,5 +1,8 @@
1
- import React from "react";
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { expect, fn, userEvent, within } from "@storybook/test";
2
3
 
4
+ import availableActions from "../__fixtures__/form-actions.json";
5
+ import data from "./__fixtures__/data.json";
3
6
  import { ActionsTable } from "./actionsTable.component";
4
7
 
5
8
  export default {
@@ -21,11 +24,6 @@ export default {
21
24
  type: "boolean"
22
25
  }
23
26
  },
24
- isEmpty: {
25
- control: {
26
- type: "boolean"
27
- }
28
- },
29
27
  disableFilters: {
30
28
  control: {
31
29
  type: "boolean"
@@ -36,296 +34,80 @@ export default {
36
34
  type: "boolean"
37
35
  }
38
36
  },
39
- tags: {
40
- control: {
41
- type: "object"
42
- }
43
- },
44
37
  onAddAction: {
45
38
  action: "onAddAction"
46
39
  }
47
40
  },
48
41
  parameters: {}
49
- };
42
+ } satisfies Meta<typeof ActionsTable>;
50
43
 
51
- export const Sandbox = (args: any) => {
52
- return <ActionsTable {...args} data={args.isEmpty ? [] : args.data} />;
53
- };
44
+ type Story = StoryObj<typeof ActionsTable>;
54
45
 
55
- Sandbox.args = {
56
- icon: "server",
57
- availableActions: [
58
- {
59
- name: "email",
60
- title: "Email",
61
- description: "Allows you to email people on submission.",
62
- priority: 0,
63
- defaults: {
64
- handler: ["after"],
65
- method: ["create"],
66
- priority: 0,
67
- name: "email",
68
- title: "Email"
69
- }
70
- },
71
- {
72
- name: "webhook",
73
- title: "Webhook (Premium)",
74
- description: "Allows you to trigger an external interface.",
75
- priority: 0,
76
- defaults: {
77
- handler: ["after"],
78
- method: ["create", "update", "delete"],
79
- priority: 0,
80
- name: "webhook",
81
- title: "Webhook (Premium)"
82
- },
83
- premium: true
84
- },
85
- {
86
- name: "sql",
87
- title: "SQL Query",
88
- description: "Allows you to execute a remote SQL Query.",
89
- priority: 0,
90
- defaults: {
91
- handler: ["after"],
92
- method: ["create"],
93
- priority: 0,
94
- name: "sql",
95
- title: "SQL Query"
96
- }
97
- },
98
- {
99
- name: "role",
100
- title: "Role Assignment",
101
- description: "Provides the Role Assignment capabilities.",
102
- priority: 1,
103
- defaults: {
104
- handler: ["after"],
105
- method: ["create"],
106
- priority: 1,
107
- name: "role",
108
- title: "Role Assignment"
109
- },
110
- access: { handler: false, method: false }
111
- },
112
- {
113
- name: "resetpass",
114
- title: "Reset Password",
115
- description: "Provides a way to reset a password field.",
116
- defaults: {
117
- handler: ["after", "before"],
118
- method: ["form", "create"],
119
- priority: 0,
120
- name: "resetpass",
121
- title: "Reset Password"
122
- },
123
- access: { handler: false, method: false }
124
- },
125
- {
126
- name: "save",
127
- title: "Save Submission",
128
- description: "Saves the submission into the database.",
129
- priority: 10,
130
- defaults: {
131
- handler: ["before"],
132
- method: ["create", "update"],
133
- priority: 10,
134
- name: "save",
135
- title: "Save Submission"
136
- },
137
- access: { handler: false, method: false }
138
- },
139
- {
140
- name: "login",
141
- title: "Login",
142
- description: "Provides a way to login to the application.",
143
- priority: 2,
144
- defaults: {
145
- handler: ["before"],
146
- method: ["create"],
147
- priority: 2,
148
- name: "login",
149
- title: "Login"
150
- },
151
- access: { handler: false, method: false }
152
- },
153
- {
154
- name: "office365contact",
155
- title: "Office 365 Contacts (Premium)",
156
- description: "Allows you to integrate into your Office 365 Contacts.",
157
- priority: 0,
158
- defaults: {
159
- handler: ["after"],
160
- method: ["create", "update", "delete"],
161
- priority: 0,
162
- name: "office365contact",
163
- title: "Office 365 Contacts (Premium)"
164
- },
165
- premium: true
166
- },
167
- {
168
- name: "office365calendar",
169
- title: "Office 365 Calendar (Premium)",
170
- description: "Allows you to integrate into your Office 365 Calendar.",
171
- premium: true,
172
- priority: 0,
173
- defaults: {
174
- handler: ["after"],
175
- method: ["create", "update", "delete"],
176
- priority: 0,
177
- name: "office365calendar",
178
- title: "Office 365 Calendar (Premium)"
179
- }
180
- },
181
- {
182
- name: "hubspotContact",
183
- title: "Hubspot Contact (Premium)",
184
- description: "Allows you to change contact fields in hubspot.",
185
- priority: 0,
186
- defaults: {
187
- handler: ["after"],
188
- method: ["create"],
189
- priority: 0,
190
- name: "hubspotContact",
191
- title: "Hubspot Contact (Premium)"
192
- },
193
- premium: true
194
- },
195
- {
196
- name: "oauth",
197
- title: "OAuth (Premium)",
198
- description: "Provides OAuth authentication behavior to this form.",
199
- priority: 20,
200
- defaults: {
201
- handler: ["after"],
202
- method: ["form", "create"],
203
- priority: 20,
204
- name: "oauth",
205
- title: "OAuth (Premium)"
206
- },
207
- premium: true
208
- },
209
- {
210
- name: "ldap",
211
- title: "LDAP Login (Premium)",
212
- description: "Provides ldap login.",
213
- priority: 3,
214
- defaults: {
215
- handler: ["before"],
216
- method: ["create"],
217
- priority: 3,
218
- name: "ldap",
219
- title: "LDAP Login (Premium)"
220
- },
221
- premium: true
222
- },
223
- {
224
- name: "googlesheet",
225
- title: "Google Sheets (Premium)",
226
- description: "Allows you to integrate data into Google sheets.",
227
- priority: 0,
228
- defaults: {
229
- handler: ["after"],
230
- method: ["create", "update", "delete"],
231
- priority: 0,
232
- name: "googlesheet",
233
- title: "Google Sheets (Premium)"
234
- },
235
- premium: true
236
- },
237
- {
238
- name: "sqlconnector",
239
- title: "SQL Connector (Premium)",
240
- description: "Allows you to execute a remote SQL Query via Resquel.",
241
- priority: 0,
242
- defaults: {
243
- handler: ["after"],
244
- method: ["create", "update", "delete"],
245
- priority: 0,
246
- name: "sqlconnector",
247
- title: "SQL Connector (Premium)"
248
- },
249
- premium: true
250
- },
251
- {
252
- name: "jira",
253
- title: "Jira (Premium)",
254
- description: "Allows you to create issues within Jira.",
255
- priority: 0,
256
- defaults: {
257
- handler: ["after"],
258
- method: ["create", "update", "delete"],
259
- priority: 0,
260
- name: "jira",
261
- title: "Jira (Premium)"
262
- },
263
- premium: true
264
- },
265
- {
266
- name: "group",
267
- title: "Group Assignment (Premium)",
268
- premium: true,
269
- description: "Provides the Group Assignment capabilities.",
270
- priority: 5,
271
- defaults: {
272
- handler: ["after"],
273
- method: ["create", "update", "delete"],
274
- priority: 5,
275
- name: "group",
276
- title: "Group Assignment (Premium)"
46
+ export const Sandbox: Story = {
47
+ async play({ canvasElement, args }) {
48
+ const canvas = within(canvasElement);
49
+
50
+ const select = canvas.getByTestId("action-table-select");
51
+ const addButton = canvas.getByTestId("action-table-add");
52
+
53
+ expect(addButton).toHaveAttribute("disabled");
54
+
55
+ await userEvent.selectOptions(select, "save", {
56
+ delay: 100
57
+ });
58
+
59
+ await userEvent.click(addButton);
60
+
61
+ expect(args.onAddAction).toHaveBeenCalledWith("save");
62
+
63
+ const editButton = await canvas.getByRole("button", { name: /Operation button: Edit/i });
64
+ const deleteButton = await canvas.getByRole("button", { name: /Operation button: delete/i });
65
+
66
+ expect(editButton).toBeInTheDocument();
67
+ expect(deleteButton).toBeInTheDocument();
68
+
69
+ await userEvent.click(editButton);
70
+
71
+ expect(args.onClick).toHaveBeenCalledWith(
72
+ args.data[0],
73
+ args.operations!.find(({ action }) => action === "edit")
74
+ );
75
+
76
+ await userEvent.click(deleteButton);
77
+
78
+ expect(args.onClick).toHaveBeenCalledWith(
79
+ args.data[0],
80
+ args.operations!.find(({ action }) => action === "delete")
81
+ );
82
+ },
83
+ args: {
84
+ onAddAction: fn(),
85
+ onClick: fn(),
86
+ availableActions: availableActions.map(({ name, title }) => ({
87
+ label: title,
88
+ value: name
89
+ })),
90
+ data: data as never,
91
+ operations: [
92
+ {
93
+ title: "Edit",
94
+ action: "edit",
95
+ alias: "row",
96
+ path: "/resources/:resourceId/submissions/:submissionId",
97
+ icon: "edit",
98
+ permissionsResolver() {
99
+ return true;
100
+ }
277
101
  },
278
- access: { handler: false, method: false }
279
- },
280
- {
281
- name: "twilioSMS",
282
- title: "Twilio SMS (Premium)",
283
- premium: true,
284
- description: "Allows you to send SMS to phone numbers.",
285
- priority: 0,
286
- defaults: {
287
- handler: ["after"],
288
- method: ["create"],
289
- priority: 0,
290
- name: "twilioSMS",
291
- title: "Twilio SMS (Premium)"
292
- }
293
- }
294
- ].map(({ name, title }) => ({
295
- label: title,
296
- value: name
297
- })),
298
- data: [
299
- {
300
- _id: "602967600685b2158b24e99a",
301
- handler: ["before"],
302
- method: ["create", "update"],
303
- priority: 10,
304
- name: "save",
305
- title: "Save Submission",
306
- form: "602967600685b24dbe24e999",
307
- machineName: "tcspjwhsevrzpcd:testForm:save"
308
- }
309
- ],
310
- operations: [
311
- {
312
- title: "Edit",
313
- action: "edit",
314
- alias: "row",
315
- path: "/resources/:resourceId/submissions/:submissionId",
316
- icon: "edit",
317
- permissionsResolver() {
318
- return true;
102
+ {
103
+ action: "delete",
104
+ path: "/resources/:resourceId/submissions/:submissionId/delete",
105
+ icon: "trash",
106
+ buttonType: "danger",
107
+ permissionsResolver() {
108
+ return true;
109
+ }
319
110
  }
320
- },
321
- {
322
- action: "delete",
323
- path: "/resources/:resourceId/submissions/:submissionId/delete",
324
- icon: "trash",
325
- buttonType: "danger",
326
- permissionsResolver() {
327
- return true;
328
- }
329
- }
330
- ]
111
+ ]
112
+ }
331
113
  };
@@ -1,5 +1,4 @@
1
- import PropTypes from "prop-types";
2
- import React, { PropsWithChildren, ReactElement, useCallback, useEffect, useMemo, useState } from "react";
1
+ import React, { PropsWithChildren, useCallback, useEffect, useMemo, useState } from "react";
3
2
 
4
3
  import type { FormOptions, FormSchema, Submission } from "../../interfaces";
5
4
  import { Card } from "../card/card.component";
@@ -96,7 +95,7 @@ function NamedFormAccess({ name, form, submissions, options, onChange, onSubmit,
96
95
  );
97
96
  }
98
97
 
99
- export function FormAccess(props: PropsWithChildren<FormAccessProps>): ReactElement {
98
+ export function FormAccess(props: PropsWithChildren<FormAccessProps>) {
100
99
  const { type, form, submissions, options, onChange, onSubmit } = useFormAccess(props);
101
100
 
102
101
  return (
@@ -155,12 +154,3 @@ export function FormAccess(props: PropsWithChildren<FormAccessProps>): ReactElem
155
154
  </div>
156
155
  );
157
156
  }
158
-
159
- FormAccess.propTypes = {
160
- type: PropTypes.string.isRequired,
161
- form: PropTypes.object,
162
- roles: PropTypes.any,
163
- children: PropTypes.any,
164
- options: PropTypes.any,
165
- onSubmit: PropTypes.func
166
- };
@@ -1,4 +1,5 @@
1
- import React from "react";
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { expect, fn, userEvent, within } from "@storybook/test";
2
3
 
3
4
  import { FormAccess } from "./formAccess.component";
4
5
 
@@ -6,17 +7,7 @@ export default {
6
7
  title: "ReactFormio/FormAccess",
7
8
  component: FormAccess,
8
9
  argTypes: {
9
- onSubmit: { action: "onSubmit" },
10
- actionInfo: {
11
- control: {
12
- type: "object"
13
- }
14
- },
15
- options: {
16
- control: {
17
- type: "object"
18
- }
19
- }
10
+ onSubmit: { action: "onSubmit" }
20
11
  },
21
12
  parameters: {
22
13
  docs: {
@@ -25,45 +16,60 @@ export default {
25
16
  }
26
17
  }
27
18
  }
28
- };
19
+ } satisfies Meta<typeof FormAccess>;
29
20
 
30
- export const Sandbox = (args: any) => {
31
- // @ts-ignore
32
- return <FormAccess {...args} options={{ template: "tailwind", iconset: "bx" }} />;
33
- };
21
+ type Story = StoryObj<typeof FormAccess>;
34
22
 
35
- Sandbox.args = {
36
- form: {
37
- _id: "6023f8fe4b1a2ab9a3aae096",
38
- type: "form",
39
- tags: [],
40
- owner: "5d0797a382461b6656d2c790",
41
- access: [
42
- {
43
- roles: ["5d0797bc872fc747da559858", "5d0797bc872fc71d05559859", "5d0797bc872fc7da3b55985a"],
44
- type: "read_all"
45
- }
46
- ],
47
- submissionAccess: [
48
- {
49
- roles: ["5d0797bc872fc747da559858"],
50
- type: "read_all"
51
- }
52
- ],
53
- controller: "",
54
- properties: {},
55
- settings: {},
56
- name: "textField",
57
- path: "textfield",
58
- machineName: "tcspjwhsevrzpcd:textField"
23
+ export const Sandbox: Story = {
24
+ play: async ({ canvasElement, args }) => {
25
+ const canvas = within(canvasElement);
26
+
27
+ // Vérifiez que le bouton "Save access" est présent
28
+ const saveButtons = await canvas.getAllByRole("button", { name: /Save access/i });
29
+ const saveButton = saveButtons[0];
30
+
31
+ expect(saveButton).toBeInTheDocument();
32
+
33
+ // Simulez un clic sur le bouton "Save access"
34
+ await userEvent.click(saveButton);
35
+
36
+ // Vérifiez que l'action onSubmit a été appelée
37
+ expect(args.onSubmit).toHaveBeenCalled();
59
38
  },
60
- roles: [
61
- {
62
- title: "Administrator",
63
- _id: "5d0797bc872fc747da559858"
39
+ args: {
40
+ onSubmit: fn(),
41
+ form: {
42
+ _id: "6023f8fe4b1a2ab9a3aae096",
43
+ type: "form",
44
+ tags: [],
45
+ owner: "5d0797a382461b6656d2c790",
46
+ access: [
47
+ {
48
+ roles: ["5d0797bc872fc747da559858", "5d0797bc872fc71d05559859", "5d0797bc872fc7da3b55985a"],
49
+ type: "read_all"
50
+ }
51
+ ],
52
+ submissionAccess: [
53
+ {
54
+ roles: ["5d0797bc872fc747da559858"],
55
+ type: "read_all"
56
+ }
57
+ ],
58
+ controller: "",
59
+ properties: {},
60
+ settings: {},
61
+ name: "textField",
62
+ path: "textfield",
63
+ machineName: "tcspjwhsevrzpcd:textField"
64
64
  },
65
- { title: "Authenticated", _id: "5d0797bc872fc71d05559859" },
66
- { title: "Anonymous", _id: "5d0797bc872fc7da3b55985a" }
67
- ],
68
- options: { template: "tailwind", iconset: "bx" }
65
+ roles: [
66
+ {
67
+ title: "Administrator",
68
+ _id: "5d0797bc872fc747da559858"
69
+ },
70
+ { title: "Authenticated", _id: "5d0797bc872fc71d05559859" },
71
+ { title: "Anonymous", _id: "5d0797bc872fc7da3b55985a" }
72
+ ],
73
+ options: { template: "tailwind", iconset: "bx" }
74
+ }
69
75
  };
@@ -1,12 +1,13 @@
1
1
  import classnames from "classnames";
2
- import PropTypes from "prop-types";
3
- import React from "react";
2
+ import React, { HTMLAttributes } from "react";
4
3
 
5
- export interface FormControlProps {
4
+ export interface FormControlProps<Data = any> {
6
5
  name: string;
6
+ value?: Data;
7
7
  required?: boolean;
8
8
  label?: string;
9
9
  className?: string;
10
+ onChange?: (name: string, value: any) => void;
10
11
  description?: string | React.ComponentType | any;
11
12
  prefix?: JSX.Element | React.ComponentType | any;
12
13
  suffix?: JSX.Element | React.ComponentType | any;
@@ -55,11 +56,3 @@ export function FormControl({
55
56
  </div>
56
57
  );
57
58
  }
58
-
59
- FormControl.propTypes = {
60
- label: PropTypes.string,
61
- name: PropTypes.string.isRequired,
62
- children: PropTypes.any,
63
- required: PropTypes.bool,
64
- description: PropTypes.any
65
- };
@@ -1,24 +1,20 @@
1
1
  import Choices from "@formio/choices.js";
2
2
  import classnames from "classnames";
3
- import PropTypes from "prop-types";
4
- import React, { ReactElement, useEffect, useRef } from "react";
3
+ import React, { HTMLAttributes, ReactElement, useEffect, useRef } from "react";
5
4
 
6
5
  import { getEventValue } from "../../utils/getEventValue";
7
6
  import { FormControl, FormControlProps } from "../form-control/formControl.component";
8
7
 
9
- export interface SelectProps<T = any> extends FormControlProps {
10
- value?: any;
8
+ export interface SelectProps<Data = any> extends FormControlProps, Omit<HTMLAttributes<HTMLSelectElement>, "onChange" | "prefix"> {
11
9
  size?: string;
12
- onChange?: (name: string, value: any) => void;
13
10
  placeholder?: string;
14
- choices: { label: string; value: T }[];
11
+ choices: { label: string; value: Data }[];
15
12
  layout?: "html5" | "choicesjs";
13
+ disabled?: boolean;
16
14
  multiple?: boolean;
17
-
18
- [key: string]: any;
19
15
  }
20
16
 
21
- export function Select<T = any>({
17
+ export function Select<Data = any>({
22
18
  name,
23
19
  label,
24
20
  size,
@@ -33,14 +29,14 @@ export function Select<T = any>({
33
29
  multiple,
34
30
  layout,
35
31
  ...props
36
- }: SelectProps<T>): ReactElement {
37
- const ref = useRef<any>();
32
+ }: SelectProps<Data>): ReactElement {
33
+ const ref = useRef<HTMLSelectElement>(null);
38
34
 
39
35
  useEffect(() => {
40
36
  let instance: any;
41
37
 
42
38
  if (layout === "choicesjs") {
43
- instance = new Choices(ref.current, {
39
+ instance = new Choices(ref.current as unknown as HTMLInputElement, {
44
40
  removeItemButton: true,
45
41
  placeholderValue: placeholder
46
42
  });
@@ -67,8 +63,8 @@ export function Select<T = any>({
67
63
  {/* eslint-disable-next-line jsx-a11y/no-onchange */}
68
64
  <select
69
65
  ref={ref}
70
- {...props}
71
66
  data-testid={`select_${name}`}
67
+ {...props}
72
68
  className={classnames("form-control", size && `form-control-${size}`)}
73
69
  name={name}
74
70
  id={name}
@@ -90,12 +86,3 @@ export function Select<T = any>({
90
86
  </FormControl>
91
87
  );
92
88
  }
93
-
94
- Select.propTypes = {
95
- label: PropTypes.string,
96
- name: PropTypes.string.isRequired,
97
- value: PropTypes.any,
98
- required: PropTypes.bool,
99
- onChange: PropTypes.func,
100
- choices: PropTypes.array.isRequired
101
- };
@@ -1,19 +1,32 @@
1
1
  import React from "react";
2
2
 
3
- import { DefaultOperationButton } from "./defaultOperationButton.component";
3
+ import { DefaultOperationButton, OperationButtonProps } from "./defaultOperationButton.component";
4
4
 
5
- export function DefaultCellOperations({ operations, row, onClick, ctx, i18n }: any) {
5
+ export interface DefaultCellOperationsProps {
6
+ operations: (OperationButtonProps & {
7
+ OperationButton: typeof DefaultOperationButton;
8
+ permissionsResolver?(data: unknown, ctx: any): boolean;
9
+ })[];
10
+ row: any;
11
+
12
+ onClick: (data: any, action: string) => void;
13
+ ctx: any;
14
+ i18n: (i18n: string) => string;
15
+ }
16
+
17
+ export function DefaultCellOperations({ operations, row, onClick, ctx, i18n }: DefaultCellOperationsProps) {
6
18
  const data = row.original;
7
19
 
8
20
  return (
9
21
  <div className='btn-group'>
10
22
  {operations
11
- .filter(({ permissionsResolver }: any) => !permissionsResolver || permissionsResolver(data, ctx))
12
- .map(({ OperationButton = DefaultOperationButton, ...operation }: any) => {
23
+ .filter(({ permissionsResolver }) => !permissionsResolver || permissionsResolver(data, ctx))
24
+ .map(({ OperationButton = DefaultOperationButton, ...operation }, index: number) => {
13
25
  return (
14
26
  <OperationButton
15
27
  key={operation.action}
16
28
  {...operation}
29
+ data-testid={`operation-${index}-${operation.action}`}
17
30
  onClick={(action: string) => onClick(data, action)}
18
31
  data={data}
19
32
  i18n={i18n}