Skip to content

Commit 71f855f

Browse files
Copilothotlong
andcommitted
fix: strip objectName prefix from recordId in RecordDetailView onEdit + set modalSize to 'lg'
- RecordDetailView: strip `${objectName}-` prefix from URL recordId before passing to onEdit callback - App.tsx: set modalSize='lg' on ModalForm for better tablet experience - Add test for recordId stripping behavior Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
1 parent 6bfbe17 commit 71f855f

3 files changed

Lines changed: 130 additions & 1 deletion

File tree

apps/console/src/App.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -400,6 +400,7 @@ export function AppContent() {
400400
description: editingRecord ? `Update details for ${currentObjectDef?.label}` : `Add a new ${currentObjectDef?.label} to your database.`,
401401
open: isDialogOpen,
402402
onOpenChange: setIsDialogOpen,
403+
modalSize: 'lg',
403404
layout: 'vertical',
404405
fields: currentObjectDef.fields
405406
? (Array.isArray(currentObjectDef.fields)
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
/**
2+
* RecordDetailView — Edit recordId stripping tests
3+
*
4+
* Validates that when editing from the detail page, the objectName prefix
5+
* is stripped from the URL-based recordId before passing to the onEdit callback.
6+
*
7+
* Related: objectstack-ai/objectui — "Record not found" bug
8+
*/
9+
10+
import { describe, it, expect, vi } from 'vitest';
11+
import { render, screen, waitFor } from '@testing-library/react';
12+
import userEvent from '@testing-library/user-event';
13+
import '@testing-library/jest-dom';
14+
import { MemoryRouter, Routes, Route } from 'react-router-dom';
15+
import { RecordDetailView } from '../components/RecordDetailView';
16+
import type { DataSource } from '@object-ui/types';
17+
18+
// ─── Mocks ───────────────────────────────────────────────────────────────────
19+
20+
function createMockDataSource(): DataSource {
21+
return {
22+
async getObjectSchema() {
23+
return {
24+
name: 'contact',
25+
label: 'Contact',
26+
fields: {
27+
name: { name: 'name', label: 'Name', type: 'text' },
28+
email: { name: 'email', label: 'Email', type: 'email' },
29+
},
30+
};
31+
},
32+
findOne: vi.fn().mockResolvedValue({ id: '1772350253615-4', name: 'Alice' }),
33+
find: vi.fn().mockResolvedValue({ data: [] }),
34+
create: vi.fn().mockResolvedValue({ id: '1' }),
35+
update: vi.fn().mockResolvedValue({ id: '1' }),
36+
delete: vi.fn().mockResolvedValue(true),
37+
} as any;
38+
}
39+
40+
const mockObjects = [
41+
{
42+
name: 'contact',
43+
label: 'Contact',
44+
fields: {
45+
name: { name: 'name', label: 'Name', type: 'text' },
46+
email: { name: 'email', label: 'Email', type: 'email' },
47+
},
48+
},
49+
];
50+
51+
function renderDetailView(
52+
urlRecordId: string,
53+
objectName: string,
54+
onEdit: (record: any) => void,
55+
ds?: DataSource,
56+
) {
57+
const dataSource = ds ?? createMockDataSource();
58+
return render(
59+
<MemoryRouter initialEntries={[`/${objectName}/record/${urlRecordId}`]}>
60+
<Routes>
61+
<Route
62+
path="/:objectName/record/:recordId"
63+
element={
64+
<RecordDetailView
65+
dataSource={dataSource}
66+
objects={mockObjects}
67+
onEdit={onEdit}
68+
/>
69+
}
70+
/>
71+
</Routes>
72+
</MemoryRouter>,
73+
);
74+
}
75+
76+
// ─── Tests ───────────────────────────────────────────────────────────────────
77+
78+
describe('RecordDetailView — onEdit recordId stripping', () => {
79+
it('strips objectName prefix from recordId when editing', async () => {
80+
const onEdit = vi.fn();
81+
82+
renderDetailView('contact-1772350253615-4', 'contact', onEdit);
83+
84+
// Wait for the detail view to load
85+
await waitFor(() => {
86+
expect(screen.getByText('Contact')).toBeInTheDocument();
87+
});
88+
89+
// Click the Edit button
90+
const editButton = await screen.findByRole('button', { name: /edit/i });
91+
await userEvent.click(editButton);
92+
93+
// The onEdit callback should receive the STRIPPED recordId (no objectName prefix)
94+
expect(onEdit).toHaveBeenCalledWith(
95+
expect.objectContaining({
96+
id: '1772350253615-4',
97+
_id: '1772350253615-4',
98+
}),
99+
);
100+
});
101+
102+
it('passes recordId as-is when no objectName prefix', async () => {
103+
const onEdit = vi.fn();
104+
105+
renderDetailView('plain-id-12345', 'contact', onEdit);
106+
107+
await waitFor(() => {
108+
expect(screen.getByText('Contact')).toBeInTheDocument();
109+
});
110+
111+
const editButton = await screen.findByRole('button', { name: /edit/i });
112+
await userEvent.click(editButton);
113+
114+
// No prefix to strip — should pass the original recordId unchanged
115+
expect(onEdit).toHaveBeenCalledWith(
116+
expect.objectContaining({
117+
id: 'plain-id-12345',
118+
_id: 'plain-id-12345',
119+
}),
120+
);
121+
});
122+
});

apps/console/src/components/RecordDetailView.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,13 @@ export function RecordDetailView({ dataSource, objects, onEdit }: RecordDetailVi
244244
<DetailView
245245
schema={detailSchema}
246246
dataSource={dataSource}
247-
onEdit={() => onEdit({ _id: recordId, id: recordId })}
247+
onEdit={() => {
248+
// Strip objectName prefix from URL-based recordId (e.g. "contact-123" → "123")
249+
const pureId = recordId && objectName && recordId.startsWith(`${objectName}-`)
250+
? recordId.slice(objectName.length + 1)
251+
: recordId;
252+
onEdit({ _id: pureId, id: pureId });
253+
}}
248254
/>
249255

250256
{/* Comments & Discussion */}

0 commit comments

Comments
 (0)