Skip to content

Commit 5aa2c71

Browse files
committed
Show NX badges in explorer where NX visualizations are actually supported
1 parent 980728e commit 5aa2c71

File tree

8 files changed

+54
-46
lines changed

8 files changed

+54
-46
lines changed

packages/app/src/__tests__/Explorer.test.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { page } from 'vitest/browser';
33

44
import {
55
getExplorerItem,
6+
getNexusExplorerItem,
67
mockDelay,
78
renderApp,
89
waitForAllLoaders,
@@ -178,7 +179,7 @@ test('navigate with home and end keys', async () => {
178179
const { user } = await renderApp();
179180

180181
const root = getExplorerItem('source.h5');
181-
const lastItem = getExplorerItem('resilience');
182+
const lastItem = getNexusExplorerItem('resilience');
182183

183184
// From root to last item
184185
await user.keyboard('{End}');

packages/app/src/__tests__/NexusPack.test.tsx

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -184,47 +184,47 @@ test('handle unknown/incompatible interpretation gracefully', async () => {
184184

185185
test('show error/fallback for malformed NeXus entity', async () => {
186186
const errorSpy = mockConsoleMethod('error');
187-
const { selectNexusExplorerNode } = await renderApp('/nexus_malformed');
187+
const { selectExplorerNode } = await renderApp('/nexus_malformed');
188188

189189
// `default` attribute points to non-existant entity
190-
await selectNexusExplorerNode('default_not_found');
190+
await selectExplorerNode('default_not_found');
191191
expect(page.getByText('No entity found at /test')).toBeVisible();
192192
errorSpy.mockClear();
193193

194194
// No `signal` attribute
195-
await selectNexusExplorerNode('no_signal');
195+
await selectExplorerNode('no_signal');
196196
expect(page.getByText('Nothing to display')).toBeInTheDocument();
197197
expect(errorSpy).not.toHaveBeenCalled();
198198
errorSpy.mockClear();
199199

200200
// `signal` attribute points to non-existant dataset
201-
await selectNexusExplorerNode('signal_not_found');
201+
await selectExplorerNode('signal_not_found');
202202
expect(
203203
page.getByText('Expected "unknown" signal entity to exist'),
204204
).toBeVisible();
205205
errorSpy.mockClear();
206206

207207
// Signal entity is not a dataset
208-
await selectNexusExplorerNode('signal_not_dataset');
208+
await selectExplorerNode('signal_not_dataset');
209209
expect(
210210
page.getByText('Expected "some_group" signal to be a dataset'),
211211
).toBeVisible();
212212
errorSpy.mockClear();
213213

214214
// Old-style signal entity is not a dataset
215-
await selectNexusExplorerNode('signal_old-style_not_dataset');
215+
await selectExplorerNode('signal_old-style_not_dataset');
216216
expect(
217217
page.getByText('Expected old-style "some_group" signal to be a dataset'),
218218
).toBeVisible();
219219
errorSpy.mockClear();
220220

221221
// Shape of signal dataset is not array
222-
await selectNexusExplorerNode('signal_not_array');
222+
await selectExplorerNode('signal_not_array');
223223
expect(page.getByText('Expected array shape')).toBeVisible();
224224
errorSpy.mockClear();
225225

226226
// Type of signal dataset is not numeric
227-
await selectNexusExplorerNode('signal_not_numeric');
227+
await selectExplorerNode('signal_not_numeric');
228228
expect(
229229
page.getByText('Expected numeric, boolean, enum or complex type'),
230230
).toBeVisible();
@@ -393,3 +393,8 @@ test('retry fetching automatically when selecting other NxHeatmap slice', async
393393
runAll();
394394
await expect.element(page.getByRole('figure')).toBeVisible();
395395
});
396+
397+
test('visualize NXnote group', async () => {
398+
await renderApp('/nexus_note');
399+
expect(page.getByText('"energy": 10.2')).toBeVisible();
400+
});

packages/app/src/__tests__/Visualizer.test.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@ import { page } from 'vitest/browser';
44
import { mockConsoleMethod, mockDelay, renderApp } from '../test-utils';
55
import { Vis } from '../vis-packs/core/visualizations';
66

7+
test('visualize default dataset', async () => {
8+
const { selectExplorerNode } = await renderApp('/entities');
9+
await selectExplorerNode('default_dataset');
10+
expect(page.getByText('foo')).toBeVisible();
11+
});
12+
713
test('show fallback message when no visualization is supported', async () => {
814
await renderApp('/entities'); // simple group
915
expect(page.getByText('Nothing to display')).toBeVisible();

packages/app/src/explorer/EntityItem.tsx

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import {
1010
useLayoutEffect,
1111
useRef,
1212
} from 'react';
13-
import { ErrorBoundary } from 'react-error-boundary';
1413
import { FiRefreshCw } from 'react-icons/fi';
1514

1615
import EntityList from './EntityList';
@@ -132,11 +131,11 @@ function EntityItem(props: Props) {
132131
<Icon className={styles.icon} />
133132
<span className={styles.name}>{entity.name}</span>
134133

135-
<ErrorBoundary fallback={null}>
134+
{isGroup(entity) && (
136135
<Suspense fallback={<span data-testid="LoadingNxBadge" />}>
137-
<NxBadge entity={entity} />
136+
<NxBadge group={entity} />
138137
</Suspense>
139-
</ErrorBoundary>
138+
)}
140139
</button>
141140

142141
{isGroup(entity) && isExpanded && (

packages/app/src/explorer/NxBadge.tsx

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,29 @@
1-
import { type ChildEntity } from '@h5web/shared/hdf5-models';
1+
import { type Group } from '@h5web/shared/hdf5-models';
22

33
import { useDataContext } from '../providers/DataProvider';
4+
import { resolvePath } from '../visualizer/utils';
45
import styles from './Explorer.module.css';
5-
import { needsNxBadge } from './utils';
66

77
interface Props {
8-
entity: ChildEntity;
8+
group: Group;
99
}
1010

1111
function NxBadge(props: Props) {
12-
const { entity } = props;
13-
const { attrValuesStore } = useDataContext();
12+
const { group } = props;
13+
const { entitiesStore, attrValuesStore } = useDataContext();
1414

15-
if (!needsNxBadge(entity, attrValuesStore)) {
16-
return null;
15+
try {
16+
const resolution = resolvePath(group.path, entitiesStore, attrValuesStore);
17+
18+
if (!resolution?.supportedVis.some((vis) => vis.name.startsWith('NX'))) {
19+
return null;
20+
}
21+
} catch (error: unknown) {
22+
if (error instanceof Error) {
23+
return null; // no badge if malformed NeXus metadata
24+
}
25+
26+
throw error; // Suspense promises
1727
}
1828

1929
return (

packages/app/src/explorer/utils.ts

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,7 @@ import { PiEmptyBold, PiGridFourBold } from 'react-icons/pi';
2020
import { RxDotFilled } from 'react-icons/rx';
2121
import { TbCube, TbTimeline } from 'react-icons/tb';
2222

23-
import { type AttrValuesStore } from '../providers/models';
24-
import { hasAttribute } from '../utils';
25-
import { getNxClass } from '../vis-packs/nexus/utils';
26-
2723
const DATASET_ICONS = [RxDotFilled, TbTimeline, PiGridFourBold, TbCube];
28-
const SUPPORTED_NX_CLASSES = new Set(['NXdata', 'NXentry', 'NXprocess']);
2924

3025
export const EXPLORER_ID = 'h5web-explorer-tree';
3126

@@ -49,22 +44,6 @@ export function getIcon(entity: ChildEntity, isExpanded: boolean): IconType {
4944
return FiLink;
5045
}
5146

52-
export function needsNxBadge(
53-
entity: ChildEntity,
54-
attrValuesStore: AttrValuesStore,
55-
): boolean {
56-
if (!isGroup(entity)) {
57-
return false;
58-
}
59-
60-
if (hasAttribute(entity, 'default')) {
61-
return true;
62-
}
63-
64-
const nxClass = getNxClass(entity, attrValuesStore);
65-
return !!nxClass && SUPPORTED_NX_CLASSES.has(nxClass);
66-
}
67-
6847
function getButtonList(
6948
parent = document.querySelector(`#${EXPLORER_ID}`),
7049
): HTMLButtonElement[] {

packages/app/src/providers/mock/mock-file.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,9 @@ export function makeMockFile(): GroupWithChildren {
5252
children: [
5353
group('entities', [
5454
group('empty_group'),
55+
group('default_dataset', [scalar('scalar_str', 'foo')], {
56+
attributes: [scalarAttr('default', 'scalar_str')],
57+
}),
5558
dataset('empty_dataset', nullShape(), unknownType(), null),
5659
datatype('datatype', compoundType([['int', intType()]])),
5760
scalar('raw', { int: 42 }),

packages/app/src/test-utils.tsx

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -72,17 +72,15 @@ export async function renderApp(
7272
...renderResult,
7373

7474
selectExplorerNode: async (name) => {
75-
await page.getByRole('treeitem', { name, exact: true }).click();
75+
await getExplorerItem(name).click();
7676

7777
if (waitForLoaders) {
7878
await waitForAllLoaders();
7979
}
8080
},
8181

8282
selectNexusExplorerNode: async (name) => {
83-
await page
84-
.getByRole('treeitem', { name: `${name} (NeXus group)`, exact: true })
85-
.click();
83+
await getNexusExplorerItem(name).click();
8684

8785
if (waitForLoaders) {
8886
await waitForAllLoaders();
@@ -104,7 +102,14 @@ export async function waitForAllLoaders(): Promise<void> {
104102
}
105103

106104
export function getExplorerItem(name: string): Locator {
107-
return page.getByRole('treeitem', { name });
105+
return page.getByRole('treeitem', { name, exact: true });
106+
}
107+
108+
export function getNexusExplorerItem(name: string): Locator {
109+
return page.getByRole('treeitem', {
110+
name: `${name} (NeXus group)`,
111+
exact: true,
112+
});
108113
}
109114

110115
function getVisSelector(): Locator {

0 commit comments

Comments
 (0)