@cccsaurora/howler-ui 2.16.0-dev.378 → 2.16.0-dev.381
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/components/app/App.js +2 -0
- package/components/app/hooks/useMatchers.js +0 -4
- package/components/app/providers/FavouritesProvider.js +2 -1
- package/components/app/providers/FieldProvider.d.ts +2 -2
- package/components/app/providers/HitProvider.d.ts +3 -3
- package/components/app/providers/HitSearchProvider.d.ts +7 -8
- package/components/app/providers/HitSearchProvider.js +64 -39
- package/components/app/providers/HitSearchProvider.test.d.ts +1 -0
- package/components/app/providers/HitSearchProvider.test.js +505 -0
- package/components/app/providers/ParameterProvider.d.ts +13 -5
- package/components/app/providers/ParameterProvider.js +240 -84
- package/components/app/providers/ParameterProvider.test.d.ts +1 -0
- package/components/app/providers/ParameterProvider.test.js +1041 -0
- package/components/app/providers/ViewProvider.d.ts +3 -2
- package/components/app/providers/ViewProvider.js +21 -14
- package/components/app/providers/ViewProvider.test.js +19 -29
- package/components/elements/display/ChipPopper.d.ts +21 -0
- package/components/elements/display/ChipPopper.js +36 -0
- package/components/elements/display/ChipPopper.test.d.ts +1 -0
- package/components/elements/display/ChipPopper.test.js +309 -0
- package/components/elements/hit/HitActions.js +3 -3
- package/components/elements/hit/HitSummary.d.ts +0 -1
- package/components/elements/hit/HitSummary.js +11 -21
- package/components/elements/hit/aggregate/HitGraph.d.ts +1 -3
- package/components/elements/hit/aggregate/HitGraph.js +9 -15
- package/components/routes/dossiers/DossierCard.test.js +0 -2
- package/components/routes/dossiers/DossierEditor.test.js +27 -33
- package/components/routes/hits/search/HitBrowser.js +7 -48
- package/components/routes/hits/search/HitContextMenu.test.js +11 -29
- package/components/routes/hits/search/InformationPane.js +1 -1
- package/components/routes/hits/search/QuerySettings.js +30 -0
- package/components/routes/hits/search/QuerySettings.test.d.ts +1 -0
- package/components/routes/hits/search/QuerySettings.test.js +553 -0
- package/components/routes/hits/search/SearchPane.js +8 -10
- package/components/routes/hits/search/ViewLink.d.ts +4 -1
- package/components/routes/hits/search/ViewLink.js +37 -19
- package/components/routes/hits/search/ViewLink.test.js +349 -303
- package/components/routes/hits/search/grid/HitGrid.js +2 -6
- package/components/routes/hits/search/shared/HitFilter.d.ts +2 -0
- package/components/routes/hits/search/shared/HitFilter.js +31 -23
- package/components/routes/hits/search/shared/HitSort.js +16 -8
- package/components/routes/hits/search/shared/SearchSpan.js +19 -10
- package/components/routes/views/ViewComposer.js +7 -6
- package/components/routes/views/Views.js +2 -1
- package/locales/en/translation.json +6 -0
- package/locales/fr/translation.json +6 -0
- package/package.json +2 -2
- package/setupTests.js +4 -1
- package/tests/mocks.d.ts +18 -0
- package/tests/mocks.js +65 -0
- package/tests/server-handlers.js +10 -28
- package/utils/viewUtils.d.ts +2 -0
- package/utils/viewUtils.js +11 -0
- package/components/routes/hits/search/shared/QuerySettings.js +0 -22
- /package/components/routes/hits/search/{shared/QuerySettings.d.ts → QuerySettings.d.ts} +0 -0
- /package/components/routes/hits/search/{CustomSort.d.ts → shared/CustomSort.d.ts} +0 -0
- /package/components/routes/hits/search/{CustomSort.js → shared/CustomSort.js} +0 -0
|
@@ -12,10 +12,11 @@ export interface ViewContextType {
|
|
|
12
12
|
addView: (v: View) => Promise<View>;
|
|
13
13
|
editView: (id: string, newView: Partial<Omit<View, 'view_id' | 'owner'>>) => Promise<View>;
|
|
14
14
|
removeView: (id: string) => Promise<void>;
|
|
15
|
-
|
|
15
|
+
getCurrentViews: (config?: {
|
|
16
16
|
viewId?: string;
|
|
17
17
|
lazy?: boolean;
|
|
18
|
-
|
|
18
|
+
ignoreParams?: boolean;
|
|
19
|
+
}) => Promise<View[]>;
|
|
19
20
|
}
|
|
20
21
|
export declare const ViewContext: import("use-context-selector").Context<ViewContextType>;
|
|
21
22
|
declare const ViewProvider: FC<PropsWithChildren>;
|
|
@@ -5,7 +5,7 @@ import useMyApi from '@cccsaurora/howler-ui/components/hooks/useMyApi';
|
|
|
5
5
|
import { useMyLocalStorageItem } from '@cccsaurora/howler-ui/components/hooks/useMyLocalStorage';
|
|
6
6
|
import { has, omit } from 'lodash-es';
|
|
7
7
|
import { useCallback, useEffect, useState } from 'react';
|
|
8
|
-
import {
|
|
8
|
+
import { useSearchParams } from 'react-router-dom';
|
|
9
9
|
import { createContext, useContextSelector } from 'use-context-selector';
|
|
10
10
|
import { StorageKey } from '@cccsaurora/howler-ui/utils/constants';
|
|
11
11
|
export const ViewContext = createContext(null);
|
|
@@ -13,8 +13,7 @@ const ViewProvider = ({ children }) => {
|
|
|
13
13
|
const { dispatchApi } = useMyApi();
|
|
14
14
|
const appUser = useAppUser();
|
|
15
15
|
const [defaultView, setDefaultView] = useMyLocalStorageItem(StorageKey.DEFAULT_VIEW);
|
|
16
|
-
const
|
|
17
|
-
const routeParams = useParams();
|
|
16
|
+
const [searchParams] = useSearchParams();
|
|
18
17
|
const [views, setViews] = useState({});
|
|
19
18
|
const fetchViews = useCallback(async (ids) => {
|
|
20
19
|
if (!ids) {
|
|
@@ -60,18 +59,26 @@ const ViewProvider = ({ children }) => {
|
|
|
60
59
|
}
|
|
61
60
|
})();
|
|
62
61
|
}, [defaultView, fetchViews, setDefaultView, views]);
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
62
|
+
const getCurrentViews = useCallback(async ({ viewId, lazy = false, ignoreParams = false } = {}) => {
|
|
63
|
+
const currentViews = ignoreParams ? [] : searchParams.getAll('view');
|
|
64
|
+
if (viewId && !currentViews.includes(viewId)) {
|
|
65
|
+
currentViews.push(viewId);
|
|
66
66
|
}
|
|
67
|
-
if (
|
|
68
|
-
return
|
|
69
|
-
}
|
|
70
|
-
if (!has(views, viewId) && !lazy) {
|
|
71
|
-
return (await fetchViews([viewId]))[0];
|
|
67
|
+
if (currentViews.length < 1) {
|
|
68
|
+
return [];
|
|
72
69
|
}
|
|
73
|
-
|
|
74
|
-
|
|
70
|
+
const results = [];
|
|
71
|
+
const missing = [];
|
|
72
|
+
currentViews.forEach(_view => {
|
|
73
|
+
if (has(views, _view)) {
|
|
74
|
+
results.push(views[_view]);
|
|
75
|
+
}
|
|
76
|
+
else if (!lazy) {
|
|
77
|
+
missing.push(_view);
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
return [...results, ...(await fetchViews(missing))];
|
|
81
|
+
}, [fetchViews, searchParams, views]);
|
|
75
82
|
const editView = useCallback(async (id, partialView) => {
|
|
76
83
|
const result = await dispatchApi(api.view.put(id, partialView));
|
|
77
84
|
setViews(_views => ({
|
|
@@ -118,7 +125,7 @@ const ViewProvider = ({ children }) => {
|
|
|
118
125
|
removeView,
|
|
119
126
|
defaultView,
|
|
120
127
|
setDefaultView,
|
|
121
|
-
|
|
128
|
+
getCurrentViews
|
|
122
129
|
}, children: children }));
|
|
123
130
|
};
|
|
124
131
|
export const useViewContextSelector = (selector) => {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { act, renderHook, waitFor } from '@testing-library/react';
|
|
3
3
|
import { hget, hpost, hput } from '@cccsaurora/howler-ui/api';
|
|
4
|
-
import
|
|
4
|
+
import { setupLocalStorageMock, setupReactRouterMock } from '@cccsaurora/howler-ui/tests/mocks';
|
|
5
5
|
import { MOCK_RESPONSES } from '@cccsaurora/howler-ui/tests/server-handlers';
|
|
6
6
|
import { useContextSelector } from 'use-context-selector';
|
|
7
7
|
import { DEFAULT_QUERY, MY_LOCAL_STORAGE_PREFIX, StorageKey } from '@cccsaurora/howler-ui/utils/constants';
|
|
@@ -9,23 +9,17 @@ import ViewProvider, { ViewContext } from './ViewProvider';
|
|
|
9
9
|
let mockUser = {
|
|
10
10
|
favourite_views: ['favourited_view_id']
|
|
11
11
|
};
|
|
12
|
+
setupReactRouterMock();
|
|
13
|
+
const mockLocalStorage = setupLocalStorageMock();
|
|
14
|
+
import { useSearchParams } from 'react-router-dom';
|
|
15
|
+
vi.mocked(useSearchParams).mockReturnValue([new URLSearchParams('?view=searched_view_id')]);
|
|
12
16
|
vi.mock('api', { spy: true });
|
|
13
|
-
vi.mock('react-router-dom', () => ({
|
|
14
|
-
useLocation: vi.fn(() => ({ pathname: '/views/searched_view_id' })),
|
|
15
|
-
useParams: vi.fn(() => ({ id: 'searched_view_id' }))
|
|
16
|
-
}));
|
|
17
17
|
vi.mock('commons/components/app/hooks', () => ({
|
|
18
18
|
useAppUser: () => ({
|
|
19
19
|
user: mockUser,
|
|
20
20
|
setUser: _user => (mockUser = _user)
|
|
21
21
|
})
|
|
22
22
|
}));
|
|
23
|
-
const mockLocalStorage = new MockLocalStorage();
|
|
24
|
-
// Replace localStorage in global scope
|
|
25
|
-
Object.defineProperty(window, 'localStorage', {
|
|
26
|
-
value: mockLocalStorage,
|
|
27
|
-
writable: true
|
|
28
|
-
});
|
|
29
23
|
const Wrapper = ({ children }) => {
|
|
30
24
|
return _jsx(ViewProvider, { children: children });
|
|
31
25
|
};
|
|
@@ -35,7 +29,7 @@ beforeEach(() => {
|
|
|
35
29
|
describe('ViewContext', () => {
|
|
36
30
|
it('should fetch the defaultView on initialization', async () => {
|
|
37
31
|
mockLocalStorage.setItem(`${MY_LOCAL_STORAGE_PREFIX}.${StorageKey.DEFAULT_VIEW}`, JSON.stringify('searched_view_id'));
|
|
38
|
-
|
|
32
|
+
const hook = renderHook(() => useContextSelector(ViewContext, ctx => ctx.views), { wrapper: Wrapper });
|
|
39
33
|
await waitFor(() => expect(hook.result.current.searched_view_id).not.toBeFalsy());
|
|
40
34
|
expect(hook.result.current.searched_view_id).toEqual(MOCK_RESPONSES['/api/v1/search/view'].items[0]);
|
|
41
35
|
});
|
|
@@ -73,16 +67,16 @@ describe('ViewContext', () => {
|
|
|
73
67
|
}));
|
|
74
68
|
hook.rerender();
|
|
75
69
|
expect(hook.result.current.views[result.view_id]).toEqual(result);
|
|
76
|
-
await act(async () =>
|
|
70
|
+
await act(async () => {
|
|
71
|
+
await hook.result.current.removeView(result.view_id);
|
|
72
|
+
});
|
|
77
73
|
hook.rerender();
|
|
78
74
|
expect(hook.result.current.views[result.view_id]).toBeFalsy();
|
|
79
75
|
});
|
|
80
76
|
describe('fetchViews', () => {
|
|
81
77
|
let hook;
|
|
82
|
-
beforeEach(
|
|
83
|
-
hook =
|
|
84
|
-
return renderHook(() => useContextSelector(ViewContext, ctx => ctx.fetchViews), { wrapper: Wrapper });
|
|
85
|
-
});
|
|
78
|
+
beforeEach(() => {
|
|
79
|
+
hook = renderHook(() => useContextSelector(ViewContext, ctx => ctx.fetchViews), { wrapper: Wrapper });
|
|
86
80
|
vi.mocked(hpost).mockClear();
|
|
87
81
|
vi.mocked(hget).mockClear();
|
|
88
82
|
});
|
|
@@ -127,32 +121,28 @@ describe('ViewContext', () => {
|
|
|
127
121
|
expect(hpost).toHaveBeenCalledOnce();
|
|
128
122
|
});
|
|
129
123
|
});
|
|
130
|
-
describe('
|
|
124
|
+
describe('getCurrentViews', () => {
|
|
131
125
|
let hook;
|
|
132
126
|
beforeEach(async () => {
|
|
133
|
-
hook =
|
|
134
|
-
return renderHook(() => useContextSelector(ViewContext, ctx => ctx.getCurrentView), { wrapper: Wrapper });
|
|
135
|
-
});
|
|
127
|
+
hook = renderHook(() => useContextSelector(ViewContext, ctx => ctx.getCurrentViews), { wrapper: Wrapper });
|
|
136
128
|
});
|
|
137
129
|
it('should allow the user to fetch their current view based on the location', async () => {
|
|
138
130
|
// lazy load should return nothing
|
|
139
|
-
await expect(hook.result.current({ lazy: true })).resolves.
|
|
131
|
+
await expect(hook.result.current({ lazy: true })).resolves.toEqual([]);
|
|
140
132
|
const result = await act(async () => hook.result.current());
|
|
141
|
-
expect(result).toEqual(MOCK_RESPONSES['/api/v1/search/view'].items[0]);
|
|
133
|
+
expect(result).toEqual([MOCK_RESPONSES['/api/v1/search/view'].items[0]]);
|
|
142
134
|
});
|
|
143
135
|
it('should allow the user to fetch their current view based on the view ID', async () => {
|
|
144
136
|
// lazy load should return nothing
|
|
145
|
-
await expect(hook.result.current({ lazy: true })).resolves.
|
|
137
|
+
await expect(hook.result.current({ lazy: true })).resolves.toEqual([]);
|
|
146
138
|
const result = await act(async () => hook.result.current({ viewId: 'searched_view_id' }));
|
|
147
|
-
expect(result).toEqual(MOCK_RESPONSES['/api/v1/search/view'].items[0]);
|
|
139
|
+
expect(result).toEqual([MOCK_RESPONSES['/api/v1/search/view'].items[0]]);
|
|
148
140
|
});
|
|
149
141
|
});
|
|
150
142
|
describe('editView', () => {
|
|
151
143
|
let hook;
|
|
152
|
-
beforeAll(
|
|
153
|
-
hook =
|
|
154
|
-
return renderHook(() => useContextSelector(ViewContext, ctx => ctx.editView), { wrapper: Wrapper });
|
|
155
|
-
});
|
|
144
|
+
beforeAll(() => {
|
|
145
|
+
hook = renderHook(() => useContextSelector(ViewContext, ctx => ctx.editView), { wrapper: Wrapper });
|
|
156
146
|
});
|
|
157
147
|
beforeEach(() => {
|
|
158
148
|
vi.mocked(hput).mockClear();
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ChipProps, PaperProps, SxProps } from '@mui/material';
|
|
2
|
+
import type { ReactElement, ReactNode } from 'react';
|
|
3
|
+
interface ChipPopperProps {
|
|
4
|
+
icon?: ReactElement;
|
|
5
|
+
deleteIcon?: ReactElement;
|
|
6
|
+
label?: ReactNode;
|
|
7
|
+
children: ReactNode;
|
|
8
|
+
slotProps?: {
|
|
9
|
+
chip?: Partial<ChipProps>;
|
|
10
|
+
paper?: PaperProps;
|
|
11
|
+
};
|
|
12
|
+
paperSx?: SxProps;
|
|
13
|
+
minWidth?: string | number;
|
|
14
|
+
placement?: 'bottom-start' | 'bottom-end' | 'bottom';
|
|
15
|
+
onToggle?: (show: boolean) => void;
|
|
16
|
+
onDelete?: (event?: any) => void;
|
|
17
|
+
toggleOnDelete?: boolean;
|
|
18
|
+
closeOnClick?: boolean;
|
|
19
|
+
}
|
|
20
|
+
declare const _default: import("react").NamedExoticComponent<ChipPopperProps>;
|
|
21
|
+
export default _default;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Chip, ClickAwayListener, Collapse, Paper, Popper } from '@mui/material';
|
|
3
|
+
import { memo, useRef, useState } from 'react';
|
|
4
|
+
const ChipPopper = ({ icon, deleteIcon, label, children, minWidth, placement = 'bottom-start', onToggle, onDelete, toggleOnDelete = false, closeOnClick = false, slotProps = {} }) => {
|
|
5
|
+
const [show, setShow] = useState(false);
|
|
6
|
+
const anchorEl = useRef(null);
|
|
7
|
+
const handleToggle = (newShow) => {
|
|
8
|
+
setShow(newShow);
|
|
9
|
+
onToggle?.(newShow);
|
|
10
|
+
};
|
|
11
|
+
return (_jsxs(_Fragment, { children: [_jsx(Chip, { icon: icon, deleteIcon: deleteIcon, label: label, onClick: e => {
|
|
12
|
+
handleToggle(!show);
|
|
13
|
+
e.stopPropagation();
|
|
14
|
+
}, onDelete: onDelete ?? (toggleOnDelete ? () => handleToggle(!show) : null), ref: anchorEl, sx: [
|
|
15
|
+
theme => ({
|
|
16
|
+
position: 'relative',
|
|
17
|
+
zIndex: 1,
|
|
18
|
+
transition: theme.transitions.create(['border-bottom-left-radius', 'border-bottom-right-radius'])
|
|
19
|
+
}),
|
|
20
|
+
show && { borderBottomLeftRadius: '0', borderBottomRightRadius: '0' },
|
|
21
|
+
...(Array.isArray(slotProps.chip?.sx) ? slotProps.chip.sx : [slotProps.chip?.sx])
|
|
22
|
+
], ...(slotProps.chip ?? {}) }), _jsx(Popper, { placement: placement, anchorEl: anchorEl.current, disablePortal: true, open: true, sx: {
|
|
23
|
+
minWidth: Math.max(typeof minWidth === 'number' ? minWidth : parseInt(minWidth?.replace('px', '')) || 0, anchorEl.current?.clientWidth || 0),
|
|
24
|
+
zIndex: 1
|
|
25
|
+
}, children: _jsx(Collapse, { in: show, unmountOnExit: true, onClick: () => {
|
|
26
|
+
if (closeOnClick) {
|
|
27
|
+
handleToggle(false);
|
|
28
|
+
}
|
|
29
|
+
}, children: _jsx(ClickAwayListener, { onClickAway: () => {
|
|
30
|
+
handleToggle(false);
|
|
31
|
+
}, children: _jsx(Paper, { sx: [
|
|
32
|
+
{ borderTopLeftRadius: 0, borderTopRightRadius: 0, px: 1, py: 2 },
|
|
33
|
+
...(Array.isArray(slotProps.paper?.sx) ? slotProps.paper.sx : [slotProps.paper?.sx])
|
|
34
|
+
], ...(slotProps.paper ?? {}), children: children }) }) }) })] }));
|
|
35
|
+
};
|
|
36
|
+
export default memo(ChipPopper);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/// <reference types="vitest" />
|
|
3
|
+
import { Info } from '@mui/icons-material';
|
|
4
|
+
import { render, screen, waitFor } from '@testing-library/react';
|
|
5
|
+
import userEvent, {} from '@testing-library/user-event';
|
|
6
|
+
import { vi } from 'vitest';
|
|
7
|
+
globalThis.IS_REACT_ACT_ENVIRONMENT = true;
|
|
8
|
+
import ChipPopper from './ChipPopper';
|
|
9
|
+
describe('ChipPopper', () => {
|
|
10
|
+
let user;
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
user = userEvent.setup();
|
|
13
|
+
vi.clearAllMocks();
|
|
14
|
+
});
|
|
15
|
+
describe('Rendering', () => {
|
|
16
|
+
it('should render chip with icon and label', () => {
|
|
17
|
+
render(_jsx(ChipPopper, { icon: _jsx(Info, { id: "chip-icon" }), label: "Test Label", children: _jsx("div", { children: "Content" }) }));
|
|
18
|
+
expect(screen.getByTestId('chip-icon')).toBeInTheDocument();
|
|
19
|
+
expect(screen.getByText('Test Label')).toBeInTheDocument();
|
|
20
|
+
});
|
|
21
|
+
it('should render chip with complex label', () => {
|
|
22
|
+
render(_jsx(ChipPopper, { icon: _jsx(Info, {}), label: _jsxs("div", { children: [_jsx("span", { children: "Complex" }), " ", _jsx("strong", { children: "Label" })] }), children: _jsx("div", { children: "Content" }) }));
|
|
23
|
+
expect(screen.getByText('Complex')).toBeInTheDocument();
|
|
24
|
+
expect(screen.getByText('Label')).toBeInTheDocument();
|
|
25
|
+
});
|
|
26
|
+
it('should not show popper content initially', () => {
|
|
27
|
+
render(_jsx(ChipPopper, { icon: _jsx(Info, {}), label: "Test", children: _jsx("div", { id: "popper-content", children: "Content" }) }));
|
|
28
|
+
// Content should not be visible initially
|
|
29
|
+
expect(screen.queryByTestId('popper-content')).toBeNull();
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
describe('Toggle Behavior', () => {
|
|
33
|
+
it('should show popper content when chip is clicked', async () => {
|
|
34
|
+
render(_jsx(ChipPopper, { icon: _jsx(Info, {}), label: "Test", children: _jsx("div", { id: "popper-content", children: "Content" }) }));
|
|
35
|
+
const chip = screen.getByText('Test').closest('.MuiChip-root');
|
|
36
|
+
await user.click(chip);
|
|
37
|
+
await waitFor(() => {
|
|
38
|
+
expect(screen.getByTestId('popper-content')).toBeVisible();
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
it('should hide popper content when chip is clicked again', async () => {
|
|
42
|
+
render(_jsx(ChipPopper, { icon: _jsx(Info, {}), label: "Test", children: _jsx("div", { id: "popper-content", children: "Content" }) }));
|
|
43
|
+
const chip = screen.getByText('Test').closest('.MuiChip-root');
|
|
44
|
+
// Open
|
|
45
|
+
await user.click(chip);
|
|
46
|
+
await waitFor(() => {
|
|
47
|
+
expect(screen.getByTestId('popper-content')).toBeVisible();
|
|
48
|
+
});
|
|
49
|
+
// Close
|
|
50
|
+
await user.click(chip);
|
|
51
|
+
await waitFor(() => {
|
|
52
|
+
expect(screen.queryByTestId('popper-content')).toBeNull();
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
it('should call onToggle callback when toggled', async () => {
|
|
56
|
+
const onToggle = vi.fn();
|
|
57
|
+
render(_jsx(ChipPopper, { icon: _jsx(Info, {}), label: "Test", onToggle: onToggle, children: _jsx("div", { children: "Content" }) }));
|
|
58
|
+
const chip = screen.getByText('Test').closest('.MuiChip-root');
|
|
59
|
+
await user.click(chip);
|
|
60
|
+
expect(onToggle).toHaveBeenCalledWith(true);
|
|
61
|
+
await user.click(chip);
|
|
62
|
+
expect(onToggle).toHaveBeenCalledWith(false);
|
|
63
|
+
expect(onToggle).toHaveBeenCalledTimes(2);
|
|
64
|
+
});
|
|
65
|
+
it('should stop event propagation when chip is clicked', async () => {
|
|
66
|
+
const parentClickHandler = vi.fn();
|
|
67
|
+
render(_jsx("div", { onClick: parentClickHandler, children: _jsx(ChipPopper, { icon: _jsx(Info, {}), label: "Test", children: _jsx("div", { children: "Content" }) }) }));
|
|
68
|
+
const chip = screen.getByText('Test').closest('.MuiChip-root');
|
|
69
|
+
await user.click(chip);
|
|
70
|
+
expect(parentClickHandler).not.toHaveBeenCalled();
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
describe('Click Away Behavior', () => {
|
|
74
|
+
it('should close popper when clicking away', async () => {
|
|
75
|
+
render(_jsxs("div", { children: [_jsx(ChipPopper, { icon: _jsx(Info, {}), label: "Test", children: _jsx("div", { id: "popper-content", children: "Content" }) }), _jsx("button", { id: "outside-button", children: "Outside" })] }));
|
|
76
|
+
const chip = screen.getByText('Test').closest('.MuiChip-root');
|
|
77
|
+
await user.click(chip);
|
|
78
|
+
await waitFor(() => {
|
|
79
|
+
expect(screen.getByTestId('popper-content')).toBeVisible();
|
|
80
|
+
});
|
|
81
|
+
// Click outside
|
|
82
|
+
const outsideButton = screen.getByTestId('outside-button');
|
|
83
|
+
await user.click(outsideButton);
|
|
84
|
+
await waitFor(() => {
|
|
85
|
+
expect(screen.queryByTestId('popper-content')).toBeNull();
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
it('should call onToggle when clicking away', async () => {
|
|
89
|
+
const onToggle = vi.fn();
|
|
90
|
+
render(_jsxs("div", { children: [_jsx(ChipPopper, { icon: _jsx(Info, {}), label: "Test", onToggle: onToggle, children: _jsx("div", { children: "Content" }) }), _jsx("button", { id: "outside-button", children: "Outside" })] }));
|
|
91
|
+
const chip = screen.getByText('Test').closest('.MuiChip-root');
|
|
92
|
+
await user.click(chip);
|
|
93
|
+
expect(onToggle).toHaveBeenCalledWith(true);
|
|
94
|
+
onToggle.mockClear();
|
|
95
|
+
// Click outside
|
|
96
|
+
const outsideButton = screen.getByTestId('outside-button');
|
|
97
|
+
await user.click(outsideButton);
|
|
98
|
+
expect(onToggle).toHaveBeenCalledWith(false);
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
describe('Props Customization', () => {
|
|
102
|
+
it('should apply custom chip props', () => {
|
|
103
|
+
render(_jsx(ChipPopper, { icon: _jsx(Info, {}), label: "Test", slotProps: {
|
|
104
|
+
chip: {
|
|
105
|
+
color: 'primary',
|
|
106
|
+
variant: 'outlined',
|
|
107
|
+
id: 'custom-chip'
|
|
108
|
+
}
|
|
109
|
+
}, children: _jsx("div", { children: "Content" }) }));
|
|
110
|
+
const chip = screen.getByTestId('custom-chip');
|
|
111
|
+
expect(chip).toHaveClass('MuiChip-colorPrimary');
|
|
112
|
+
expect(chip).toHaveClass('MuiChip-outlined');
|
|
113
|
+
});
|
|
114
|
+
it('should apply custom chip sx prop', () => {
|
|
115
|
+
const { container } = render(_jsx(ChipPopper, { icon: _jsx(Info, {}), label: "Test", slotProps: {
|
|
116
|
+
chip: {
|
|
117
|
+
sx: { backgroundColor: 'red' }
|
|
118
|
+
}
|
|
119
|
+
}, children: _jsx("div", { children: "Content" }) }));
|
|
120
|
+
const chip = container.querySelector('.MuiChip-root');
|
|
121
|
+
expect(window.getComputedStyle(chip).getPropertyValue('background-color')).toBe('rgb(255, 0, 0)');
|
|
122
|
+
});
|
|
123
|
+
it('should apply custom paper props', async () => {
|
|
124
|
+
render(_jsx(ChipPopper, { icon: _jsx(Info, {}), label: "Test", slotProps: {
|
|
125
|
+
paper: {
|
|
126
|
+
elevation: 8,
|
|
127
|
+
id: 'custom-paper'
|
|
128
|
+
}
|
|
129
|
+
}, children: _jsx("div", { children: "Content" }) }));
|
|
130
|
+
const chip = screen.getByText('Test').closest('.MuiChip-root');
|
|
131
|
+
await user.click(chip);
|
|
132
|
+
await waitFor(() => {
|
|
133
|
+
const paper = screen.getByTestId('custom-paper');
|
|
134
|
+
expect(paper).toBeInTheDocument();
|
|
135
|
+
expect(paper).toHaveClass('MuiPaper-elevation8');
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
it('should apply custom paper sx prop', async () => {
|
|
139
|
+
const { container } = render(_jsx(ChipPopper, { icon: _jsx(Info, {}), label: "Test", slotProps: {
|
|
140
|
+
paper: {
|
|
141
|
+
sx: { backgroundColor: 'blue' }
|
|
142
|
+
}
|
|
143
|
+
}, children: _jsx("div", { children: "Content" }) }));
|
|
144
|
+
const chip = screen.getByText('Test').closest('.MuiChip-root');
|
|
145
|
+
await user.click(chip);
|
|
146
|
+
await waitFor(() => {
|
|
147
|
+
const paper = container.querySelector('.MuiPaper-root');
|
|
148
|
+
expect(window.getComputedStyle(paper).getPropertyValue('background-color')).toBe('rgb(0, 0, 255)');
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
it('should use custom placement', async () => {
|
|
152
|
+
const { container } = render(_jsx(ChipPopper, { icon: _jsx(Info, {}), label: "Test", placement: "bottom-end", children: _jsx("div", { children: "Content" }) }));
|
|
153
|
+
const chip = screen.getByText('Test').closest('.MuiChip-root');
|
|
154
|
+
await user.click(chip);
|
|
155
|
+
await waitFor(() => {
|
|
156
|
+
const popper = container.querySelector('[data-popper-placement]');
|
|
157
|
+
expect(popper).toHaveAttribute('data-popper-placement', 'bottom-end');
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
it('should use custom minWidth when provided', async () => {
|
|
161
|
+
const { container } = render(_jsx(ChipPopper, { icon: _jsx(Info, {}), label: "Test", minWidth: "500px", children: _jsx("div", { children: "Content" }) }));
|
|
162
|
+
const chip = screen.getByText('Test').closest('.MuiChip-root');
|
|
163
|
+
await user.click(chip);
|
|
164
|
+
await waitFor(() => {
|
|
165
|
+
const popper = container.querySelector('.MuiPopper-root');
|
|
166
|
+
expect(popper).toHaveStyle({ minWidth: '500px' });
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
it('should default to chip width when minWidth not provided', async () => {
|
|
170
|
+
const { container } = render(_jsx(ChipPopper, { icon: _jsx(Info, {}), label: "Test", children: _jsx("div", { children: "Content" }) }));
|
|
171
|
+
const chip = screen.getByText('Test').closest('.MuiChip-root');
|
|
172
|
+
await user.click(chip);
|
|
173
|
+
await waitFor(() => {
|
|
174
|
+
const popper = container.querySelector('.MuiPopper-root');
|
|
175
|
+
expect(popper).toBeInTheDocument();
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
describe('Styling', () => {
|
|
180
|
+
it('should apply border radius transition styles to chip', () => {
|
|
181
|
+
const { container } = render(_jsx(ChipPopper, { icon: _jsx(Info, {}), label: "Test", children: _jsx("div", { children: "Content" }) }));
|
|
182
|
+
const chip = container.querySelector('.MuiChip-root');
|
|
183
|
+
expect(chip).toHaveStyle({ position: 'relative', zIndex: '1' });
|
|
184
|
+
});
|
|
185
|
+
it('should apply rounded corner styles when popper is closed', () => {
|
|
186
|
+
const { container } = render(_jsx(ChipPopper, { icon: _jsx(Info, {}), label: "Test", children: _jsx("div", { children: "Content" }) }));
|
|
187
|
+
const chip = container.querySelector('.MuiChip-root');
|
|
188
|
+
// Should have all corners rounded when closed
|
|
189
|
+
expect(chip).not.toHaveStyle({
|
|
190
|
+
borderBottomLeftRadius: '0',
|
|
191
|
+
borderBottomRightRadius: '0'
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
it('should remove bottom border radius when popper is open', async () => {
|
|
195
|
+
render(_jsx(ChipPopper, { icon: _jsx(Info, {}), label: "Test", children: _jsx("div", { children: "Content" }) }));
|
|
196
|
+
const chip = screen.getByText('Test').closest('.MuiChip-root');
|
|
197
|
+
await user.click(chip);
|
|
198
|
+
await waitFor(() => {
|
|
199
|
+
expect(chip).toHaveStyle({
|
|
200
|
+
borderBottomLeftRadius: '0',
|
|
201
|
+
borderBottomRightRadius: '0'
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
});
|
|
205
|
+
it('should apply top border radius 0 to paper', async () => {
|
|
206
|
+
const { container } = render(_jsx(ChipPopper, { icon: _jsx(Info, {}), label: "Test", children: _jsx("div", { children: "Content" }) }));
|
|
207
|
+
const chip = screen.getByText('Test').closest('.MuiChip-root');
|
|
208
|
+
await user.click(chip);
|
|
209
|
+
await waitFor(() => {
|
|
210
|
+
const paper = container.querySelector('.MuiPaper-root');
|
|
211
|
+
expect(paper).toHaveStyle({
|
|
212
|
+
borderTopLeftRadius: '0',
|
|
213
|
+
borderTopRightRadius: '0'
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
});
|
|
218
|
+
describe('Children Rendering', () => {
|
|
219
|
+
it('should render children content inside popper', async () => {
|
|
220
|
+
render(_jsxs(ChipPopper, { icon: _jsx(Info, {}), label: "Test", children: [_jsx("div", { id: "child-1", children: "Child 1" }), _jsx("div", { id: "child-2", children: "Child 2" })] }));
|
|
221
|
+
const chip = screen.getByText('Test').closest('.MuiChip-root');
|
|
222
|
+
await user.click(chip);
|
|
223
|
+
await waitFor(() => {
|
|
224
|
+
expect(screen.getByTestId('child-1')).toBeVisible();
|
|
225
|
+
expect(screen.getByTestId('child-2')).toBeVisible();
|
|
226
|
+
});
|
|
227
|
+
});
|
|
228
|
+
it('should render complex children components', async () => {
|
|
229
|
+
render(_jsx(ChipPopper, { icon: _jsx(Info, {}), label: "Test", children: _jsxs("div", { children: [_jsx("input", { id: "test-input", type: "text" }), _jsx("button", { id: "test-button", children: "Click me" })] }) }));
|
|
230
|
+
const chip = screen.getByText('Test').closest('.MuiChip-root');
|
|
231
|
+
await user.click(chip);
|
|
232
|
+
await waitFor(() => {
|
|
233
|
+
expect(screen.getByTestId('test-input')).toBeVisible();
|
|
234
|
+
expect(screen.getByTestId('test-button')).toBeVisible();
|
|
235
|
+
});
|
|
236
|
+
});
|
|
237
|
+
it('should allow interaction with children', async () => {
|
|
238
|
+
const buttonClick = vi.fn();
|
|
239
|
+
render(_jsx(ChipPopper, { icon: _jsx(Info, {}), label: "Test", children: _jsx("button", { id: "child-button", onClick: buttonClick, children: "Click me" }) }));
|
|
240
|
+
const chip = screen.getByText('Test').closest('.MuiChip-root');
|
|
241
|
+
await user.click(chip);
|
|
242
|
+
await waitFor(() => {
|
|
243
|
+
expect(screen.getByTestId('child-button')).toBeVisible();
|
|
244
|
+
});
|
|
245
|
+
const button = screen.getByTestId('child-button');
|
|
246
|
+
await user.click(button);
|
|
247
|
+
expect(buttonClick).toHaveBeenCalledTimes(1);
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
describe('Edge Cases', () => {
|
|
251
|
+
it('should handle rapid toggling', async () => {
|
|
252
|
+
const onToggle = vi.fn();
|
|
253
|
+
render(_jsx(ChipPopper, { icon: _jsx(Info, {}), label: "Test", onToggle: onToggle, children: _jsx("div", { children: "Content" }) }));
|
|
254
|
+
const chip = screen.getByText('Test').closest('.MuiChip-root');
|
|
255
|
+
// Rapidly toggle multiple times
|
|
256
|
+
await user.click(chip);
|
|
257
|
+
await user.click(chip);
|
|
258
|
+
await user.click(chip);
|
|
259
|
+
await user.click(chip);
|
|
260
|
+
expect(onToggle).toHaveBeenCalledTimes(4);
|
|
261
|
+
expect(onToggle).toHaveBeenNthCalledWith(1, true);
|
|
262
|
+
expect(onToggle).toHaveBeenNthCalledWith(2, false);
|
|
263
|
+
expect(onToggle).toHaveBeenNthCalledWith(3, true);
|
|
264
|
+
expect(onToggle).toHaveBeenNthCalledWith(4, false);
|
|
265
|
+
});
|
|
266
|
+
it('should work without onToggle callback', async () => {
|
|
267
|
+
render(_jsx(ChipPopper, { icon: _jsx(Info, {}), label: "Test", children: _jsx("div", { id: "content", children: "Content" }) }));
|
|
268
|
+
const chip = screen.getByText('Test').closest('.MuiChip-root');
|
|
269
|
+
// Should not throw error
|
|
270
|
+
await user.click(chip);
|
|
271
|
+
await waitFor(() => {
|
|
272
|
+
expect(screen.getByTestId('content')).toBeVisible();
|
|
273
|
+
});
|
|
274
|
+
});
|
|
275
|
+
it('should handle empty children', async () => {
|
|
276
|
+
render(_jsx(ChipPopper, { icon: _jsx(Info, {}), label: "Test", children: null }));
|
|
277
|
+
const chip = screen.getByText('Test').closest('.MuiChip-root');
|
|
278
|
+
await user.click(chip);
|
|
279
|
+
// Should not throw error
|
|
280
|
+
await waitFor(() => {
|
|
281
|
+
const paper = document.querySelector('.MuiPaper-root');
|
|
282
|
+
expect(paper).toBeInTheDocument();
|
|
283
|
+
});
|
|
284
|
+
});
|
|
285
|
+
it('should handle array sx props in chip slotProps', () => {
|
|
286
|
+
const { container } = render(_jsx(ChipPopper, { icon: _jsx(Info, {}), label: "Test", slotProps: {
|
|
287
|
+
chip: {
|
|
288
|
+
sx: [{ backgroundColor: 'red' }, { color: 'white !important' }]
|
|
289
|
+
}
|
|
290
|
+
}, children: _jsx("div", { children: "Content" }) }));
|
|
291
|
+
const chip = container.querySelector('.MuiChip-root');
|
|
292
|
+
expect(window.getComputedStyle(chip).getPropertyValue('color')).toBe('rgb(255, 255, 255)');
|
|
293
|
+
expect(window.getComputedStyle(chip).getPropertyValue('background-color')).toBe('rgb(255, 0, 0)');
|
|
294
|
+
});
|
|
295
|
+
it('should handle array sx props in paper slotProps', async () => {
|
|
296
|
+
const { container } = render(_jsx(ChipPopper, { icon: _jsx(Info, {}), label: "Test", slotProps: {
|
|
297
|
+
paper: {
|
|
298
|
+
sx: [{ backgroundColor: 'blue !important' }, { padding: '20px' }]
|
|
299
|
+
}
|
|
300
|
+
}, children: _jsx("div", { children: "Content" }) }));
|
|
301
|
+
const chip = screen.getByText('Test').closest('.MuiChip-root');
|
|
302
|
+
await user.click(chip);
|
|
303
|
+
await waitFor(() => {
|
|
304
|
+
const paper = container.querySelector('.MuiPaper-root');
|
|
305
|
+
expect(paper).toHaveStyle({ backgroundColor: 'blue !important', padding: '20px' });
|
|
306
|
+
});
|
|
307
|
+
});
|
|
308
|
+
});
|
|
309
|
+
});
|
|
@@ -27,7 +27,7 @@ const HitActions = ({ hit, orientation = 'horizontal' }) => {
|
|
|
27
27
|
const { values, set } = useMyLocalStorageProvider();
|
|
28
28
|
const pluginStore = usePluginStore();
|
|
29
29
|
const { getMatchingAnalytic } = useMatchers();
|
|
30
|
-
const
|
|
30
|
+
const getCurrentViews = useContextSelector(ViewContext, ctx => ctx.getCurrentViews);
|
|
31
31
|
const selected = useContextSelector(ParameterContext, ctx => ctx?.selected);
|
|
32
32
|
const setSelected = useContextSelector(ParameterContext, ctx => ctx?.setSelected);
|
|
33
33
|
const clearSelectedHits = useContextSelector(HitContext, ctx => ctx.clearSelectedHits);
|
|
@@ -67,7 +67,7 @@ const HitActions = ({ hit, orientation = 'horizontal' }) => {
|
|
|
67
67
|
actionFunction: async () => {
|
|
68
68
|
if (!loading) {
|
|
69
69
|
await assess(assessment, analytic?.triage_settings?.skip_rationale);
|
|
70
|
-
if ((await
|
|
70
|
+
if ((await getCurrentViews())[0]?.settings?.advance_on_triage && nextHit) {
|
|
71
71
|
clearSelectedHits(nextHit.howler.id);
|
|
72
72
|
setSelected?.(nextHit.howler.id);
|
|
73
73
|
}
|
|
@@ -86,7 +86,7 @@ const HitActions = ({ hit, orientation = 'horizontal' }) => {
|
|
|
86
86
|
canVote,
|
|
87
87
|
clearSelectedHits,
|
|
88
88
|
config.lookups,
|
|
89
|
-
|
|
89
|
+
getCurrentViews,
|
|
90
90
|
loading,
|
|
91
91
|
nextHit,
|
|
92
92
|
setSelected,
|
|
@@ -2,7 +2,6 @@ import type { HowlerSearchResponse } from '@cccsaurora/howler-ui/api/search';
|
|
|
2
2
|
import type { Hit } from '@cccsaurora/howler-ui/models/entities/generated/Hit';
|
|
3
3
|
import type { WithMetadata } from '@cccsaurora/howler-ui/models/WithMetadata';
|
|
4
4
|
declare const _default: import("react").NamedExoticComponent<{
|
|
5
|
-
query: string;
|
|
6
5
|
response?: HowlerSearchResponse<WithMetadata<Hit>>;
|
|
7
6
|
execute?: boolean;
|
|
8
7
|
onStart?: () => void;
|