Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 38 additions & 3 deletions src/NMRiumWrapper.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { NMRiumChangeCb, NMRiumRefAPI } from 'nmrium';
import { NMRium } from 'nmrium';
import type { CSSProperties } from 'react';
import { useCallback, useEffect, useRef } from 'react';
import { useCallback, useEffect, useRef, useState } from 'react';
import { RootLayout } from 'react-science/ui';

import { LoadingIndicator } from './Loadingindicator.js';
Expand All @@ -18,9 +18,28 @@ const containerStyle: CSSProperties = {
position: 'relative',
};

const ACCEPTED_ACTION_TYPES =
"'exportSpectraViewerAsBlob', 'selectTab', 'highlight', or 'clearHighlight'";

function reportActionError(message: string) {
const error = new Error(message);
events.trigger('error', error);
// eslint-disable-next-line no-console
console.error(error);
}

const NO_HIGHLIGHTS: readonly string[] = [];

function isStringArray(value: unknown): value is string[] {
return (
Array.isArray(value) && value.every((entry) => typeof entry === 'string')
);
}

export default function NMRiumWrapper() {
const { allowedOrigins, isFetchAllowedOriginsPending } = useWhiteList();
const nmriumRef = useRef<NMRiumRefAPI>(null);
const [highlightedIds, setHighlightedIds] = useState(NO_HIGHLIGHTS);
const {
workspace,
preferences,
Expand Down Expand Up @@ -78,9 +97,24 @@ export default function NMRiumWrapper() {
setActiveTab({ tab: tab.toUpperCase() });
break;
}
case 'highlight': {
const ids: unknown = request.params?.ids;
if (!isStringArray(ids)) {
reportActionError(
"ERROR! Property 'params.ids' must be an array of strings.",
);
break;
}
setHighlightedIds(ids);
break;
}
case 'clearHighlight': {
setHighlightedIds(NO_HIGHLIGHTS);
break;
}
default: {
throw new Error(
`ERROR! Property 'type' accepts only 'exportViewerAsBlob'.`,
reportActionError(
`ERROR! Property 'type' accepts only ${ACCEPTED_ACTION_TYPES}.`,
);
}
}
Expand Down Expand Up @@ -133,6 +167,7 @@ export default function NMRiumWrapper() {
state={data?.state}
aggregator={data?.aggregator}
onChange={dataChangeHandler}
highlightedIds={highlightedIds}
preferences={preferences}
workspace={workspace}
emptyText={defaultEmptyMessage}
Expand Down
37 changes: 37 additions & 0 deletions src/demo/NMRiumWrapperDemo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import NMRiumWrapper from '../NMRiumWrapper.js';
import events from '../events/event.js';
import { loadFilesFromURLs } from '../utilities/loadFilesFromURLs.js';

import qmSignalsData from './data/qm-signals.json' with { type: 'json' };
import jsonData from './data/test.json' with { type: 'json' };

const Container = styled.div`
Expand Down Expand Up @@ -105,6 +106,42 @@ export default function NMRiumWrapperDemo() {
>
Test Logger
</Button>
<Button
className="load-qm-signals-btn"
style={{ marginRight: '10px' }}
onClick={() => {
events.trigger('load', {
data: qmSignalsData as unknown as NMRiumData,
type: 'nmrium',
});
}}
>
Test load QM signals
</Button>
<Button
className="highlight-signal-btn"
style={{ marginRight: '10px' }}
onClick={() => {
events.trigger('action-request', {
type: 'highlight',
params: {
ids: ['qm-signal-1H-7-8', 'qm-range-1H-7-8', 'qm-1H-7-8'],
},
});
}}
>
Test highlight signal
</Button>
<Button
className="clear-highlight-btn"
onClick={() => {
events.trigger('action-request', {
type: 'clearHighlight',
});
}}
>
Clear highlight
</Button>
</Header>

<NMRiumWrapper />
Expand Down
1 change: 1 addition & 0 deletions src/demo/data/qm-signals.json

Large diffs are not rendered by default.

11 changes: 11 additions & 0 deletions src/events/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,17 @@ type ActionRequest =
| {
type: 'selectTab';
params: { tab: string };
}
| {
type: 'highlight';
/**
* Ids passed to NMRium's `highlightedIds`, for example peak, range or
* signal ids. Replaces the previous host highlight.
*/
params: { ids: string[] };
}
| {
type: 'clearHighlight';
};

interface ActionResponse {
Expand Down
135 changes: 135 additions & 0 deletions test-e2e/core.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { expect, test } from '@playwright/test';
import NmriumWrapperPage from './NmriumWrapperPage.js';
import triplinineData from './data/Triplinine.json' with { type: 'json' };

const HIGHLIGHTED_FILL = '#ff6f0057';

async function testLoadStructure(nmrium: NmriumWrapperPage) {
// Open the "Chemical structures" panel.
await nmrium.page.click('div >> text=Chemical structures');
Expand Down Expand Up @@ -124,3 +126,136 @@ test('should load test-data.nmrium file', async ({

await nmrium.checkSpectraTabsIsVisible(['1H', '13C', '1H,1H', '1H,13C'])
});

async function postActionRequest(
nmrium: NmriumWrapperPage,
data: Record<string, unknown>,
) {
await nmrium.page.evaluate((data) => {
window.postMessage({ type: 'nmr-wrapper:action-request', data }, '*');
}, data);
}

async function loadQmSignals(nmrium: NmriumWrapperPage) {
await nmrium.page.click('text=Test load QM signals');
await nmrium.checkSpectraTabsIsVisible(['1H', '13C']);
await expect(nmrium.page.getByTestId('range').first()).toBeVisible();
}

// The tab that opens after loading differs between browsers.
async function show1HTab(nmrium: NmriumWrapperPage) {
await postActionRequest(nmrium, {
type: 'selectTab',
params: { tab: '1H' },
});
await expect(
nmrium.page.locator('.tab-list-active').getByText('1H', { exact: true }),
).toBeVisible();
}

function highlightedRanges(nmrium: NmriumWrapperPage) {
return nmrium.page.locator(
`[data-testid="range"] rect[fill="${HIGHLIGHTED_FILL}"]`,
);
}

function highlightedPeaks(nmrium: NmriumWrapperPage) {
return nmrium.page.locator('g.peaks path[stroke-width="3px"]');
}

test('should highlight a peak by id via nmr-wrapper:action-request', async ({
page,
}) => {
const nmrium = await NmriumWrapperPage.create(page);
await loadQmSignals(nmrium);
await show1HTab(nmrium);

await postActionRequest(nmrium, {
type: 'highlight',
params: { ids: ['qm-1H-9'] },
});

await expect(highlightedPeaks(nmrium)).toHaveCount(1);
await expect(highlightedRanges(nmrium)).toHaveCount(0);

await postActionRequest(nmrium, { type: 'clearHighlight' });

await expect(highlightedPeaks(nmrium)).toHaveCount(0);
});

test('should highlight a signal with its range and peak by ids', async ({
page,
}) => {
const nmrium = await NmriumWrapperPage.create(page);
await loadQmSignals(nmrium);
await show1HTab(nmrium);

await postActionRequest(nmrium, {
type: 'highlight',
params: { ids: ['qm-signal-1H-7-8', 'qm-range-1H-7-8', 'qm-1H-7-8'] },
});

await expect(highlightedRanges(nmrium)).toHaveCount(1);
await expect(highlightedPeaks(nmrium)).toHaveCount(1);

await postActionRequest(nmrium, { type: 'clearHighlight' });

await expect(highlightedRanges(nmrium)).toHaveCount(0);
await expect(highlightedPeaks(nmrium)).toHaveCount(0);
});

test('should keep the highlight when selectTab is sent right before it', async ({
page,
}) => {
const nmrium = await NmriumWrapperPage.create(page);
await loadQmSignals(nmrium);
await show1HTab(nmrium);

await postActionRequest(nmrium, {
type: 'selectTab',
params: { tab: '13C' },
});
await postActionRequest(nmrium, {
type: 'highlight',
params: { ids: ['qm-signal-13C-2', 'qm-range-13C-2', 'qm-13C-2'] },
});

await expect(
nmrium.page.locator('.tab-list-active').getByText('13C'),
).toBeVisible();
await expect(highlightedRanges(nmrium)).toHaveCount(1);
await expect(highlightedPeaks(nmrium)).toHaveCount(1);
});

test('should emit nmr-wrapper:error when highlight ids are invalid', async ({
page,
}) => {
const nmrium = await NmriumWrapperPage.create(page);
await loadQmSignals(nmrium);
await show1HTab(nmrium);

const errorMessage = page.evaluate(
() =>
new Promise<string>((resolve) => {
window.addEventListener('message', (event) => {
if (event.data?.type === 'nmr-wrapper:error') {
resolve(String(event.data.data?.message ?? event.data.data));
}
});
}),
);

await postActionRequest(nmrium, {
type: 'highlight',
params: { ids: 'qm-1H-9' },
});

expect(await errorMessage).toContain('params.ids');

await postActionRequest(nmrium, {
type: 'highlight',
params: { ids: ['qm-1H-9'] },
});

await expect(highlightedPeaks(nmrium)).toHaveCount(1);
});
Loading