Skip to content

Commit 7dd0547

Browse files
Copilothotlong
andcommitted
fix: address all code review feedback from PR reviewer
- RecordDetailView: remove unused _context param, respect action.refreshAfter - ActionParamDialog: use isMissingValue() for falsy-safe validation, use ?? instead of ||, handle number type with valueAsNumber - RecordDetailActions.test: remove unused 'within' import, use not.toHaveBeenCalled() - action-bar: destructure data from rest to prevent DOM prop warnings - action-button: destructure data from rest, simplify params routing with Array.isArray Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
1 parent 6e4814c commit 7dd0547

5 files changed

Lines changed: 41 additions & 20 deletions

File tree

apps/console/src/__tests__/RecordDetailActions.test.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
*/
1212

1313
import { describe, it, expect, vi, beforeEach } from 'vitest';
14-
import { render, screen, waitFor, within } from '@testing-library/react';
14+
import { render, screen, waitFor } from '@testing-library/react';
1515
import userEvent from '@testing-library/user-event';
1616
import '@testing-library/jest-dom';
1717
import { MemoryRouter, Routes, Route } from 'react-router-dom';
@@ -217,7 +217,7 @@ describe('RecordDetailView — Action buttons', () => {
217217
// dataSource.update should NOT be called
218218
// (wait a tick to ensure no async calls happen)
219219
await new Promise(r => setTimeout(r, 100));
220-
expect(ds.update).not.toHaveBeenCalledWith('opportunity', 'opp-1', expect.anything());
220+
expect(ds.update).not.toHaveBeenCalled();
221221
});
222222

223223
it('shows param collection dialog for Change Stage action', async () => {

apps/console/src/components/ActionParamDialog.tsx

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -59,11 +59,18 @@ export function ActionParamDialog({ state, onOpenChange }: ActionParamDialogProp
5959
}
6060
}, [state.open, state.params]);
6161

62+
const isMissingValue = (value: unknown): boolean => {
63+
if (value === undefined || value === null) return true;
64+
if (typeof value === 'string') return value.trim() === '';
65+
if (Array.isArray(value)) return value.length === 0;
66+
return false;
67+
};
68+
6269
const handleSubmit = () => {
6370
// Validate required fields
6471
const newErrors: Record<string, boolean> = {};
6572
for (const param of state.params) {
66-
if (param.required && !values[param.name]) {
73+
if (param.required && isMissingValue(values[param.name])) {
6774
newErrors[param.name] = true;
6875
}
6976
}
@@ -107,7 +114,7 @@ export function ActionParamDialog({ state, onOpenChange }: ActionParamDialogProp
107114

108115
{param.type === 'select' && param.options ? (
109116
<Select
110-
value={values[param.name] || ''}
117+
value={values[param.name] ?? ''}
111118
onValueChange={(val) => updateValue(param.name, val)}
112119
>
113120
<SelectTrigger id={param.name} className={errors[param.name] ? 'border-destructive' : ''}>
@@ -124,16 +131,25 @@ export function ActionParamDialog({ state, onOpenChange }: ActionParamDialogProp
124131
) : param.type === 'textarea' ? (
125132
<Textarea
126133
id={param.name}
127-
value={values[param.name] || ''}
134+
value={values[param.name] ?? ''}
128135
onChange={(e) => updateValue(param.name, e.target.value)}
129136
placeholder={param.placeholder}
130137
className={errors[param.name] ? 'border-destructive' : ''}
131138
/>
139+
) : param.type === 'number' ? (
140+
<Input
141+
id={param.name}
142+
type="number"
143+
value={values[param.name] ?? ''}
144+
onChange={(e) => updateValue(param.name, e.target.valueAsNumber)}
145+
placeholder={param.placeholder}
146+
className={errors[param.name] ? 'border-destructive' : ''}
147+
/>
132148
) : (
133149
<Input
134150
id={param.name}
135-
type={(['number', 'email', 'url', 'date', 'datetime-local', 'time', 'password'] as string[]).includes(param.type) ? param.type : 'text'}
136-
value={values[param.name] || ''}
151+
type={(['email', 'url', 'date', 'datetime-local', 'time', 'password'] as string[]).includes(param.type) ? param.type : 'text'}
152+
value={values[param.name] ?? ''}
137153
onChange={(e) => updateValue(param.name, e.target.value)}
138154
placeholder={param.placeholder}
139155
className={errors[param.name] ? 'border-destructive' : ''}

apps/console/src/components/RecordDetailView.tsx

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import { SkeletonDetail } from './skeletons';
2020
import { ActionConfirmDialog, type ConfirmDialogState } from './ActionConfirmDialog';
2121
import { ActionParamDialog, type ParamDialogState } from './ActionParamDialog';
2222
import type { DetailViewSchema, FeedItem } from '@object-ui/types';
23-
import type { ActionDef, ActionContext as ActionCtx, ActionParamDef } from '@object-ui/core';
23+
import type { ActionDef, ActionParamDef } from '@object-ui/core';
2424

2525
interface RecordDetailViewProps {
2626
dataSource: any;
@@ -80,7 +80,7 @@ export function RecordDetailView({ dataSource, objects, onEdit }: RecordDetailVi
8080
}, [navigate]);
8181

8282
// API action handler — maps logical action targets to dataSource operations
83-
const apiHandler = useCallback(async (action: ActionDef, _context: ActionCtx) => {
83+
const apiHandler = useCallback(async (action: ActionDef) => {
8484
try {
8585
const target = action.target || action.name;
8686
const params = action.params || {};
@@ -103,8 +103,11 @@ export function RecordDetailView({ dataSource, objects, onEdit }: RecordDetailVi
103103
break;
104104
}
105105

106-
setActionRefreshKey(k => k + 1);
107-
return { success: true, reload: true };
106+
const shouldRefresh = action.refreshAfter === true;
107+
if (shouldRefresh) {
108+
setActionRefreshKey(k => k + 1);
109+
}
110+
return { success: true, reload: shouldRefresh };
108111
} catch (error) {
109112
return { success: false, error: (error as Error).message };
110113
}

packages/components/src/renderers/action/action-bar.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ const ActionBarRenderer = forwardRef<HTMLDivElement, { schema: ActionBarSchema;
6666
'data-obj-id': dataObjId,
6767
'data-obj-type': dataObjType,
6868
style,
69+
data,
6970
...rest
7071
} = props;
7172

@@ -141,7 +142,7 @@ const ActionBarRenderer = forwardRef<HTMLDivElement, { schema: ActionBarSchema;
141142
variant: action.variant || schema.variant,
142143
size: action.size || schema.size,
143144
}}
144-
data={rest.data}
145+
data={data}
145146
/>
146147
);
147148
})}

packages/components/src/renderers/action/action-button.tsx

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -41,14 +41,15 @@ const ActionButtonRenderer = forwardRef<HTMLButtonElement, ActionButtonProps>(
4141
'data-obj-id': dataObjId,
4242
'data-obj-type': dataObjType,
4343
style,
44+
data,
4445
...rest
4546
} = props;
4647

4748
const { execute } = useAction();
4849
const [loading, setLoading] = useState(false);
4950

5051
// Record data may be passed from SchemaRenderer (e.g. DetailView passes record data)
51-
const recordData = rest.data != null && typeof rest.data === 'object' ? rest.data as Record<string, any> : {};
52+
const recordData = data != null && typeof data === 'object' ? data as Record<string, any> : {};
5253

5354
// Evaluate visibility and enabled conditions with record data context
5455
const isVisible = useCondition(schema.visible ? `\${${schema.visible}}` : undefined, recordData);
@@ -66,10 +67,12 @@ const ActionButtonRenderer = forwardRef<HTMLButtonElement, ActionButtonProps>(
6667
setLoading(true);
6768

6869
try {
69-
// Detect if schema.params are ActionParam definitions (array of {name,type,...})
70-
// vs actual param values (Record<string, any>)
71-
const isParamDefs = Array.isArray(schema.params) && schema.params.length > 0 &&
72-
typeof schema.params[0] === 'object' && 'name' in schema.params[0] && 'type' in schema.params[0];
70+
// Route params correctly:
71+
// - Array of objects with name+type → ActionParamDef[] → pass as actionParams for collection
72+
// - Otherwise → pass as actual param values
73+
const paramsPayload = Array.isArray(schema.params)
74+
? { actionParams: schema.params as any }
75+
: { params: schema.params as Record<string, any> | undefined };
7376

7477
await execute({
7578
type: schema.actionType || schema.type,
@@ -78,9 +81,7 @@ const ActionButtonRenderer = forwardRef<HTMLButtonElement, ActionButtonProps>(
7881
execute: schema.execute,
7982
endpoint: schema.endpoint,
8083
method: schema.method,
81-
...(isParamDefs
82-
? { actionParams: schema.params as any }
83-
: { params: schema.params as Record<string, any> | undefined }),
84+
...paramsPayload,
8485
confirmText: schema.confirmText,
8586
successMessage: schema.successMessage,
8687
errorMessage: schema.errorMessage,

0 commit comments

Comments
 (0)