diff --git a/packages/ui/package.json b/packages/ui/package.json index f009ca4..59e9624 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -11,6 +11,10 @@ "./chart/chart-theme.css": "./src/chart/chart-theme.css", "./styles/*": "./src/styles/*" }, + "scripts": { + "test": "vitest run", + "test:watch": "vitest" + }, "dependencies": { "@calab/community": "*", "@calab/compute": "*", diff --git a/packages/ui/src/CommunityBrowserShell.tsx b/packages/ui/src/CommunityBrowserShell.tsx index 9ca503f..2139407 100644 --- a/packages/ui/src/CommunityBrowserShell.tsx +++ b/packages/ui/src/CommunityBrowserShell.tsx @@ -18,6 +18,7 @@ import { createSignal, createEffect, createMemo, Show, on } from 'solid-js'; import type { JSX, Accessor } from 'solid-js'; import type { BaseSubmission, BaseFilterState, DataSource } from '@calab/community'; import { supabaseEnabled, user, fieldOptions, loadFieldOptions } from '@calab/community'; +import { matchesSourceBucket } from './source-bucket.ts'; import './styles/community.css'; /** Stale-while-revalidate threshold: 5 minutes in milliseconds. */ @@ -110,7 +111,7 @@ export function CommunityBrowserShell< const f = props.filters(); const ds = dataSource(); return subs.filter((s) => { - if (s.data_source !== ds) return false; + if (!matchesSourceBucket(s.data_source, ds)) return false; if (f.indicator && s.indicator !== f.indicator) return false; if (f.species && s.species !== f.species) return false; if (f.brainRegion && s.brain_region !== f.brainRegion) return false; @@ -127,7 +128,7 @@ export function CommunityBrowserShell< /** Submissions matching the current data source (before dropdown filters). */ const sourceSubmissions = createMemo(() => { const ds = dataSource(); - return submissions().filter((s) => s.data_source === ds); + return submissions().filter((s) => matchesSourceBucket(s.data_source, ds)); }); /** User params for "Compare my params" overlay. */ diff --git a/packages/ui/src/FilterBar.tsx b/packages/ui/src/FilterBar.tsx index 88567e7..bc81dc8 100644 --- a/packages/ui/src/FilterBar.tsx +++ b/packages/ui/src/FilterBar.tsx @@ -117,20 +117,6 @@ export function FilterBar(props: FilterBarProps) { ))} - - {/* Render extra filters alongside base filters when not in extra-only mode */} - {(props.extraFilters ?? []).map((ef) => ( - - ))} )} diff --git a/packages/ui/src/__tests__/source-bucket.test.ts b/packages/ui/src/__tests__/source-bucket.test.ts new file mode 100644 index 0000000..a490730 --- /dev/null +++ b/packages/ui/src/__tests__/source-bucket.test.ts @@ -0,0 +1,36 @@ +import { describe, it, expect } from 'vitest'; +import { matchesSourceBucket } from '../source-bucket.ts'; + +describe('matchesSourceBucket', () => { + // Regression: the browser source toggle only ever selects 'demo' or 'user'. + // Submissions are stored with their exact source, so bridge/training rows + // (e.g. real data loaded via the Python calab.tune bridge) were silently + // filtered out of the 'user' bucket and never appeared in the community + // browser. The 'user' bucket must include every non-demo source. + + it('matches user submissions to the user bucket', () => { + expect(matchesSourceBucket('user', 'user')).toBe(true); + }); + + it('matches bridge submissions to the user bucket', () => { + expect(matchesSourceBucket('bridge', 'user')).toBe(true); + }); + + it('matches training submissions to the user bucket', () => { + expect(matchesSourceBucket('training', 'user')).toBe(true); + }); + + it('matches demo submissions to the demo bucket', () => { + expect(matchesSourceBucket('demo', 'demo')).toBe(true); + }); + + it('does not show demo submissions in the user bucket', () => { + expect(matchesSourceBucket('demo', 'user')).toBe(false); + }); + + it('does not show non-demo submissions in the demo bucket', () => { + expect(matchesSourceBucket('user', 'demo')).toBe(false); + expect(matchesSourceBucket('bridge', 'demo')).toBe(false); + expect(matchesSourceBucket('training', 'demo')).toBe(false); + }); +}); diff --git a/packages/ui/src/source-bucket.ts b/packages/ui/src/source-bucket.ts new file mode 100644 index 0000000..288f4cd --- /dev/null +++ b/packages/ui/src/source-bucket.ts @@ -0,0 +1,16 @@ +// Data-source bucket matching for the community browser. + +import type { DataSource } from '@calab/community'; + +/** + * Does a submission's stored `data_source` belong in the currently selected + * browser bucket? The browser's source toggle only ever selects 'demo' or + * 'user' (bridge/training collapse to 'user' — see the appDataSource effect in + * CommunityBrowserShell), but submissions are stored with their exact source + * ('bridge', 'training', ...). So the 'user' bucket must match every non-demo + * source, otherwise bridge/training submissions are silently filtered out and + * never appear in the browser. + */ +export function matchesSourceBucket(rowSource: DataSource, bucket: DataSource): boolean { + return bucket === 'demo' ? rowSource === 'demo' : rowSource !== 'demo'; +} diff --git a/packages/ui/vitest.config.ts b/packages/ui/vitest.config.ts new file mode 100644 index 0000000..6ec74ee --- /dev/null +++ b/packages/ui/vitest.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + include: ['src/**/*.test.ts'], + }, +});