Skip to content

Commit 5a58d0d

Browse files
authored
Merge pull request #720 from objectstack-ai/copilot/fix-toolbar-toggle-issue
2 parents 9d193ed + 989926f commit 5a58d0d

File tree

5 files changed

+173
-1
lines changed

5 files changed

+173
-1
lines changed

ROADMAP.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -216,12 +216,16 @@ ObjectUI is a universal Server-Driven UI (SDUI) engine built on React + Tailwind
216216
1. ~~**`generateViewSchema` (plugin-view):** Hardcodes `showSearch: false` for non-grid views~~ → Now propagates from `activeView`
217217
2. ~~**Console `renderListView`:** Omits toolbar/display flags from `fullSchema`~~ → Now passes all config properties
218218
3. ~~**`NamedListView` type:** Missing toolbar/display properties~~ → Added as first-class properties
219-
4. **No per-view-type integration tests:** Pending — tests verify config reaches `fullSchema`, but per-renderer integration tests still needed
219+
4. ~~**Plugin `renderListView` schema missing toolbar flags:** `renderContent``renderListView` schema did not include `showSearch`/`showFilters`/`showSort`~~ → Now propagated (PR #771)
220+
5. ~~**ListView toolbar unconditionally rendered:** Search/Filter/Sort buttons always visible regardless of schema flags~~ → Now conditionally rendered based on `schema.showSearch`/`showFilters`/`showSort` (PR #771)
221+
6. **No per-view-type integration tests:** Pending — tests verify config reaches `fullSchema`, but per-renderer integration tests still needed
220222

221223
**Phase 1 — Grid/Table View (baseline, already complete):**
222224
- [x] `gridSchema` includes `striped`/`bordered` from `activeView`
223225
- [x] `showSort`/`showSearch`/`showFilters` passed via `ObjectViewSchema`
224226
- [x] `useMemo` dependency arrays cover all grid config
227+
- [x] ListView toolbar buttons conditionally rendered based on `schema.showSearch`/`showFilters`/`showSort` (PR #771)
228+
- [x] `renderListView` schema includes toolbar toggle flags (`showSearch`/`showFilters`/`showSort`) and display props (`striped`/`bordered`/`color`) (PR #771)
225229

226230
**Phase 2 — Kanban Live Preview:**
227231
- [x] Propagate `showSort`/`showSearch`/`showFilters` through `generateViewSchema` kanban branch

packages/plugin-list/src/ListView.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -871,6 +871,7 @@ export const ListView: React.FC<ListViewProps> = ({
871871
</Popover>
872872

873873
{/* Filter */}
874+
{schema.showFilters !== false && (
874875
<Popover open={showFilters} onOpenChange={setShowFilters}>
875876
<PopoverTrigger asChild>
876877
<Button
@@ -906,6 +907,7 @@ export const ListView: React.FC<ListViewProps> = ({
906907
</div>
907908
</PopoverContent>
908909
</Popover>
910+
)}
909911

910912
{/* Group */}
911913
<Button
@@ -919,6 +921,7 @@ export const ListView: React.FC<ListViewProps> = ({
919921
</Button>
920922

921923
{/* Sort */}
924+
{schema.showSort !== false && (
922925
<Popover open={showSort} onOpenChange={setShowSort}>
923926
<PopoverTrigger asChild>
924927
<Button
@@ -954,6 +957,7 @@ export const ListView: React.FC<ListViewProps> = ({
954957
</div>
955958
</PopoverContent>
956959
</Popover>
960+
)}
957961

958962
{/* Color */}
959963
<Button
@@ -1026,6 +1030,7 @@ export const ListView: React.FC<ListViewProps> = ({
10261030
</div>
10271031

10281032
{/* Right: Search */}
1033+
{schema.showSearch !== false && (
10291034
<div className="flex items-center gap-1">
10301035
{searchExpanded ? (
10311036
<div className="relative w-36 sm:w-48 lg:w-64">
@@ -1064,6 +1069,7 @@ export const ListView: React.FC<ListViewProps> = ({
10641069
</Button>
10651070
)}
10661071
</div>
1072+
)}
10671073
</div>
10681074

10691075

packages/plugin-list/src/__tests__/ListView.test.tsx

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -598,4 +598,99 @@ describe('ListView', () => {
598598
expect(screen.queryByTestId('user-filters')).not.toBeInTheDocument();
599599
});
600600
});
601+
602+
// ============================
603+
// Toolbar Toggle Visibility
604+
// ============================
605+
describe('Toolbar Toggle Visibility', () => {
606+
it('should hide Search button when showSearch is false', () => {
607+
const schema: ListViewSchema = {
608+
type: 'list-view',
609+
objectName: 'contacts',
610+
viewType: 'grid',
611+
fields: ['name', 'email'],
612+
showSearch: false,
613+
};
614+
615+
renderWithProvider(<ListView schema={schema} />);
616+
expect(screen.queryByRole('button', { name: /search/i })).not.toBeInTheDocument();
617+
});
618+
619+
it('should show Search button when showSearch is true', () => {
620+
const schema: ListViewSchema = {
621+
type: 'list-view',
622+
objectName: 'contacts',
623+
viewType: 'grid',
624+
fields: ['name', 'email'],
625+
showSearch: true,
626+
};
627+
628+
renderWithProvider(<ListView schema={schema} />);
629+
expect(screen.getByRole('button', { name: /search/i })).toBeInTheDocument();
630+
});
631+
632+
it('should show Search button when showSearch is undefined (default)', () => {
633+
const schema: ListViewSchema = {
634+
type: 'list-view',
635+
objectName: 'contacts',
636+
viewType: 'grid',
637+
fields: ['name', 'email'],
638+
};
639+
640+
renderWithProvider(<ListView schema={schema} />);
641+
expect(screen.getByRole('button', { name: /search/i })).toBeInTheDocument();
642+
});
643+
644+
it('should hide Filter button when showFilters is false', () => {
645+
const schema: ListViewSchema = {
646+
type: 'list-view',
647+
objectName: 'contacts',
648+
viewType: 'grid',
649+
fields: ['name', 'email'],
650+
showFilters: false,
651+
};
652+
653+
renderWithProvider(<ListView schema={schema} />);
654+
expect(screen.queryByRole('button', { name: /filter/i })).not.toBeInTheDocument();
655+
});
656+
657+
it('should show Filter button when showFilters is true', () => {
658+
const schema: ListViewSchema = {
659+
type: 'list-view',
660+
objectName: 'contacts',
661+
viewType: 'grid',
662+
fields: ['name', 'email'],
663+
showFilters: true,
664+
};
665+
666+
renderWithProvider(<ListView schema={schema} />);
667+
expect(screen.getByRole('button', { name: /filter/i })).toBeInTheDocument();
668+
});
669+
670+
it('should hide Sort button when showSort is false', () => {
671+
const schema: ListViewSchema = {
672+
type: 'list-view',
673+
objectName: 'contacts',
674+
viewType: 'grid',
675+
fields: ['name', 'email'],
676+
showSort: false,
677+
};
678+
679+
renderWithProvider(<ListView schema={schema} />);
680+
expect(screen.queryByRole('button', { name: /^sort$/i })).not.toBeInTheDocument();
681+
});
682+
683+
it('should show Sort button when showSort is true', () => {
684+
const schema: ListViewSchema = {
685+
type: 'list-view',
686+
objectName: 'contacts',
687+
viewType: 'grid',
688+
fields: ['name', 'email'],
689+
showSort: true,
690+
};
691+
692+
renderWithProvider(<ListView schema={schema} />);
693+
expect(screen.getByRole('button', { name: /^sort$/i })).toBeInTheDocument();
694+
});
695+
});
601696
});

packages/plugin-view/src/ObjectView.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -813,6 +813,14 @@ export const ObjectView: React.FC<ObjectViewProps> = ({
813813
densityMode: activeView?.densityMode,
814814
groupBy: activeView?.groupBy,
815815
options: currentNamedViewConfig?.options || activeView,
816+
// Propagate toolbar toggle flags
817+
showSearch: activeView?.showSearch ?? schema.showSearch,
818+
showFilters: activeView?.showFilters ?? schema.showFilters,
819+
showSort: activeView?.showSort ?? schema.showSort,
820+
// Propagate display properties
821+
striped: activeView?.striped ?? (schema as any).striped,
822+
bordered: activeView?.bordered ?? (schema as any).bordered,
823+
color: activeView?.color ?? (schema as any).color,
816824
},
817825
dataSource,
818826
onEdit: handleEdit,

packages/plugin-view/src/__tests__/ObjectView.test.tsx

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -549,5 +549,64 @@ describe('ObjectView', () => {
549549
// Component renders without crash — showSort is respected
550550
expect(screen.getByTestId('object-grid')).toBeInTheDocument();
551551
});
552+
553+
it('should include showSearch/showFilters/showSort in renderListView schema', async () => {
554+
const schema: ObjectViewSchema = {
555+
type: 'object-view',
556+
objectName: 'contacts',
557+
showSearch: false,
558+
showFilters: false,
559+
showSort: false,
560+
};
561+
562+
const renderListViewSpy = vi.fn(({ schema: listSchema }: any) => (
563+
<div data-testid="custom-list">Custom ListView</div>
564+
));
565+
566+
render(
567+
<ObjectView
568+
schema={schema}
569+
dataSource={mockDataSource}
570+
renderListView={renderListViewSpy}
571+
/>,
572+
);
573+
574+
expect(renderListViewSpy).toHaveBeenCalled();
575+
const callSchema = renderListViewSpy.mock.calls[0]?.[0]?.schema;
576+
expect(callSchema?.showSearch).toBe(false);
577+
expect(callSchema?.showFilters).toBe(false);
578+
expect(callSchema?.showSort).toBe(false);
579+
});
580+
581+
it('should propagate showSearch/showFilters/showSort from activeView in renderListView', async () => {
582+
const schema: ObjectViewSchema = {
583+
type: 'object-view',
584+
objectName: 'contacts',
585+
};
586+
587+
const renderListViewSpy = vi.fn(({ schema: listSchema }: any) => (
588+
<div data-testid="custom-list">Custom ListView</div>
589+
));
590+
591+
const views = [
592+
{ id: 'v1', label: 'View 1', type: 'grid' as const, showSearch: false, showFilters: false, showSort: false },
593+
];
594+
595+
render(
596+
<ObjectView
597+
schema={schema}
598+
dataSource={mockDataSource}
599+
views={views}
600+
activeViewId="v1"
601+
renderListView={renderListViewSpy}
602+
/>,
603+
);
604+
605+
expect(renderListViewSpy).toHaveBeenCalled();
606+
const callSchema = renderListViewSpy.mock.calls[0]?.[0]?.schema;
607+
expect(callSchema?.showSearch).toBe(false);
608+
expect(callSchema?.showFilters).toBe(false);
609+
expect(callSchema?.showSort).toBe(false);
610+
});
552611
});
553612
});

0 commit comments

Comments
 (0)