@cccsaurora/howler-ui 2.18.0-dev.841 → 2.19.0-cases.1040
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/api/action/index.d.ts +5 -4
- package/api/action/index.js +8 -8
- package/api/analytic/index.d.ts +3 -2
- package/api/analytic/index.js +4 -4
- package/api/analytic/owner.d.ts +2 -1
- package/api/analytic/owner.js +2 -2
- package/api/analytic/rules.d.ts +2 -1
- package/api/analytic/rules.js +2 -2
- package/api/dossier/index.d.ts +4 -3
- package/api/dossier/index.js +6 -6
- package/api/hit/index.d.ts +3 -2
- package/api/hit/index.js +4 -4
- package/api/hit/labels.d.ts +3 -2
- package/api/hit/labels.js +4 -4
- package/api/hit/overwrite.d.ts +2 -1
- package/api/hit/overwrite.js +2 -2
- package/api/hit/transition.d.ts +2 -1
- package/api/hit/transition.js +2 -2
- package/api/index.d.ts +5 -4
- package/api/index.js +8 -8
- package/api/overview/index.d.ts +4 -3
- package/api/overview/index.js +6 -6
- package/api/search/overview.d.ts +2 -2
- package/api/template/index.d.ts +4 -3
- package/api/template/index.js +6 -6
- package/api/user/index.d.ts +2 -1
- package/api/user/index.js +2 -2
- package/api/view/index.d.ts +4 -3
- package/api/view/index.js +6 -6
- package/components/app/App.d.ts +1 -0
- package/components/app/App.js +24 -6
- package/components/app/providers/FavouritesProvider.js +0 -4
- package/components/app/providers/ModalProvider.d.ts +1 -0
- package/components/app/providers/ModalProvider.js +5 -1
- package/components/app/providers/RecordProvider.d.ts +1 -1
- package/components/app/providers/RecordProvider.js +1 -1
- package/components/app/providers/SearchResponseProvider.d.ts +21 -0
- package/components/app/providers/SearchResponseProvider.js +81 -0
- package/components/app/providers/SearchResponseProvider.test.d.ts +1 -0
- package/components/app/providers/SearchResponseProvider.test.js +406 -0
- package/components/app/providers/ViewProvider.test.js +4 -1
- package/components/elements/addons/buttons/CustomButton.test.d.ts +1 -0
- package/components/elements/addons/buttons/CustomButton.test.js +106 -0
- package/components/elements/addons/layout/FlexOne.test.d.ts +1 -0
- package/components/elements/addons/layout/FlexOne.test.js +30 -0
- package/components/elements/addons/lists/TuiListBase.js +4 -0
- package/components/elements/addons/lists/TuiListElement.js +6 -3
- package/components/elements/addons/lists/index.d.ts +1 -0
- package/components/elements/addons/search/SearchPagination.d.ts +2 -1
- package/components/elements/addons/search/SearchPagination.js +4 -4
- package/components/elements/addons/search/SearchPagination.test.d.ts +1 -0
- package/components/elements/addons/search/SearchPagination.test.js +80 -0
- package/components/elements/addons/search/SearchTotal.test.d.ts +1 -0
- package/components/elements/addons/search/SearchTotal.test.js +47 -0
- package/components/elements/display/DocumentationButton.test.d.ts +1 -0
- package/components/elements/display/DocumentationButton.test.js +84 -0
- package/components/elements/display/DynamicTabs.test.d.ts +1 -0
- package/components/elements/display/DynamicTabs.test.js +93 -0
- package/components/elements/display/HowlerAvatar.js +2 -1
- package/components/elements/display/HowlerCard.test.d.ts +1 -0
- package/components/elements/display/HowlerCard.test.js +51 -0
- package/components/elements/display/Image.test.d.ts +1 -0
- package/components/elements/display/Image.test.js +79 -0
- package/components/elements/display/ItemManager.d.ts +2 -2
- package/components/elements/display/ItemManager.js +1 -1
- package/components/elements/display/Markdown.js +9 -2
- package/components/elements/display/QueryResultText.test.d.ts +1 -0
- package/components/elements/display/QueryResultText.test.js +48 -0
- package/components/elements/display/TextDivider.test.d.ts +1 -0
- package/components/elements/display/TextDivider.test.js +43 -0
- package/components/elements/display/TypingIndicator.test.d.ts +1 -0
- package/components/elements/display/TypingIndicator.test.js +33 -0
- package/components/elements/display/modals/ConfirmDeleteModal.d.ts +4 -0
- package/components/elements/display/modals/ConfirmDeleteModal.js +4 -2
- package/components/elements/hit/HitBanner.js +6 -3
- package/components/elements/hit/HitLinks.js +1 -1
- package/components/elements/hit/HitSummary.js +3 -2
- package/components/elements/hit/elements/AnalyticLink.js +1 -1
- package/components/elements/hit/elements/Assigned.d.ts +2 -0
- package/components/elements/hit/elements/Assigned.js +15 -13
- package/components/elements/record/RecordContextMenu.js +15 -3
- package/components/elements/record/RecordContextMenu.test.js +96 -0
- package/components/hooks/useMyApi.d.ts +1 -1
- package/components/hooks/useMyPreferences.js +2 -1
- package/components/routes/403.d.ts +3 -0
- package/components/routes/403.js +10 -0
- package/components/routes/403.test.d.ts +1 -0
- package/components/routes/403.test.js +27 -0
- package/components/routes/404.test.d.ts +1 -0
- package/components/routes/404.test.js +26 -0
- package/components/routes/ErrorBoundary.test.d.ts +1 -0
- package/components/routes/ErrorBoundary.test.js +43 -0
- package/components/routes/ErrorOccured.test.d.ts +1 -0
- package/components/routes/ErrorOccured.test.js +35 -0
- package/components/routes/Logout.test.d.ts +1 -0
- package/components/routes/Logout.test.js +59 -0
- package/components/routes/action/edit/ActionEditor.js +1 -1
- package/components/routes/action/useMyActionFunctions.js +30 -6
- package/components/routes/action/view/ActionDetails.js +15 -2
- package/components/routes/action/view/ActionSearch.js +32 -20
- package/components/routes/action/view/markdown/integrations.en.md.js +1 -1
- package/components/routes/action/view/markdown/integrations.fr.md.js +1 -1
- package/components/routes/analytics/AnalyticDetails.js +10 -6
- package/components/routes/analytics/AnalyticSearch.js +6 -6
- package/components/routes/cases/Cases.js +5 -6
- package/components/routes/cases/modals/ResolveModal.js +14 -13
- package/components/routes/cases/modals/ResolveModal.test.js +85 -25
- package/components/routes/dossiers/Dossiers.js +25 -19
- package/components/routes/help/ActionIntroductionDocumentation.js +1 -1
- package/components/routes/help/markdown/en/client.md.js +1 -1
- package/components/routes/help/markdown/en/retention.md.js +1 -1
- package/components/routes/help/markdown/fr/client.md.js +1 -1
- package/components/routes/help/markdown/fr/retention.md.js +1 -1
- package/components/routes/hits/search/shared/CustomSpan.js +7 -7
- package/components/routes/hits/search/shared/CustomSpan.test.d.ts +1 -0
- package/components/routes/hits/search/shared/CustomSpan.test.js +170 -0
- package/components/routes/home/AddNewCard.js +1 -1
- package/components/routes/home/AddNewCard.test.d.ts +1 -0
- package/components/routes/home/AddNewCard.test.js +86 -0
- package/components/routes/home/ViewCard.js +1 -1
- package/components/routes/home/ViewRefresh.test.d.ts +1 -0
- package/components/routes/home/ViewRefresh.test.js +79 -0
- package/components/routes/overviews/OverviewCard.test.d.ts +1 -0
- package/components/routes/overviews/OverviewCard.test.js +108 -0
- package/components/routes/overviews/OverviewViewer.js +16 -8
- package/components/routes/overviews/Overviews.js +25 -19
- package/components/routes/overviews/template/en.md.js +1 -1
- package/components/routes/overviews/template/fr.md.js +1 -1
- package/components/routes/settings/LocalSection.js +2 -1
- package/components/routes/settings/SettingsSection.test.d.ts +1 -0
- package/components/routes/settings/SettingsSection.test.js +19 -0
- package/components/routes/templates/TemplateCard.d.ts +3 -1
- package/components/routes/templates/TemplateCard.js +8 -4
- package/components/routes/templates/TemplateCard.test.d.ts +1 -0
- package/components/routes/templates/TemplateCard.test.js +115 -0
- package/components/routes/templates/TemplateViewer.js +62 -16
- package/components/routes/templates/Templates.js +35 -13
- package/components/routes/views/Views.js +21 -14
- package/locales/en/translation.json +25 -4
- package/locales/fr/translation.json +25 -4
- package/models/entities/generated/ApiType.d.ts +1 -1
- package/models/entities/generated/Hit.d.ts +1 -0
- package/models/entities/generated/Howler.d.ts +1 -0
- package/models/entities/generated/UserUser.d.ts +2 -0
- package/package.json +143 -148
- package/rest/FetchClient.test.d.ts +1 -0
- package/rest/FetchClient.test.js +81 -0
- package/setupTests.js +5 -0
- package/utils/Throttler.test.d.ts +1 -0
- package/utils/Throttler.test.js +135 -0
- package/utils/actionUtils.test.d.ts +1 -0
- package/utils/actionUtils.test.js +140 -0
- package/utils/constants.d.ts +1 -0
- package/utils/constants.js +1 -0
- package/utils/hitFunctions.test.d.ts +1 -0
- package/utils/hitFunctions.test.js +52 -0
- package/utils/localStorage.test.d.ts +1 -0
- package/utils/localStorage.test.js +96 -0
- package/utils/menuUtils.js +1 -1
- package/utils/menuUtils.test.d.ts +1 -0
- package/utils/menuUtils.test.js +198 -0
- package/utils/sessionStorage.test.d.ts +1 -0
- package/utils/sessionStorage.test.js +81 -0
- package/utils/stringUtils.test.d.ts +1 -0
- package/utils/stringUtils.test.js +159 -0
- package/utils/utils.test.d.ts +1 -0
- package/utils/utils.test.js +292 -0
- package/utils/viewUtils.test.d.ts +1 -0
- package/utils/viewUtils.test.js +45 -0
- package/utils/xsrf.test.d.ts +1 -0
- package/utils/xsrf.test.js +41 -0
|
@@ -5,11 +5,14 @@ import VSBoxElement from '../layout/vsbox/VSBoxElement';
|
|
|
5
5
|
const TuiListElement = ({ position, item, onSelect: onClick, children }) => {
|
|
6
6
|
const elementEl = useRef();
|
|
7
7
|
const onItemClick = useCallback(_event => {
|
|
8
|
-
if (onClick) {
|
|
8
|
+
if (onClick && !item.disabled) {
|
|
9
9
|
onClick(item, position);
|
|
10
10
|
}
|
|
11
11
|
}, [onClick, item, position]);
|
|
12
12
|
const classRenderer = useCallback(() => {
|
|
13
|
+
if (item.disabled) {
|
|
14
|
+
return 'elementDisabled';
|
|
15
|
+
}
|
|
13
16
|
const _classes = ['elementHover'];
|
|
14
17
|
if (item.cursor) {
|
|
15
18
|
_classes.push('elementFocus');
|
|
@@ -18,7 +21,7 @@ const TuiListElement = ({ position, item, onSelect: onClick, children }) => {
|
|
|
18
21
|
_classes.push('elementSelected');
|
|
19
22
|
}
|
|
20
23
|
return _classes.join(' ');
|
|
21
|
-
}, [item.cursor, item.selected]);
|
|
22
|
-
return (_jsx(VSBoxElement, { focus: !!item.cursor, children: _jsx("div", { ref: elementEl, "data-tuilist-index": position, "data-tuilist-id": item.id, "data-tuilist-focus": !!item.cursor, "data-tuilist-selected": !!item.selected, onClick: onItemClick, children: children({ item, position }, classRenderer) }) }));
|
|
24
|
+
}, [item.cursor, item.selected, item.disabled]);
|
|
25
|
+
return (_jsx(VSBoxElement, { focus: !!item.cursor, children: _jsx("div", { ref: elementEl, "data-tuilist-index": position, "data-tuilist-id": item.id, "data-tuilist-focus": !!item.cursor, "data-tuilist-selected": !!item.selected, "aria-disabled": item.disabled, onClick: onItemClick, children: children({ item, position }, classRenderer) }) }));
|
|
23
26
|
};
|
|
24
27
|
export default memo(TuiListElement);
|
|
@@ -3,7 +3,8 @@ type SearchPaginationProps = Omit<PaginationProps, 'onChange'> & {
|
|
|
3
3
|
limit: number;
|
|
4
4
|
offset: number;
|
|
5
5
|
total: number;
|
|
6
|
+
removeCount?: number;
|
|
6
7
|
onChange: (nextOffset: number) => void;
|
|
7
8
|
};
|
|
8
|
-
declare const SearchPagination: ({ limit, offset, total, onChange, ...paginationProps }: SearchPaginationProps) => import("react/jsx-runtime").JSX.Element;
|
|
9
|
+
declare const SearchPagination: ({ limit, offset, total, removeCount, onChange, ...paginationProps }: SearchPaginationProps) => import("react/jsx-runtime").JSX.Element;
|
|
9
10
|
export default SearchPagination;
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { Pagination } from '@mui/material';
|
|
3
3
|
import { useCallback } from 'react';
|
|
4
|
-
const SearchPagination = ({ limit, offset, total, onChange, ...paginationProps }) => {
|
|
4
|
+
const SearchPagination = ({ limit, offset, total, removeCount, onChange, ...paginationProps }) => {
|
|
5
5
|
const onPageChange = useCallback((_event, nextPage) => {
|
|
6
6
|
onChange(nextPage === 1 ? 0 : (nextPage - 1) * limit);
|
|
7
7
|
}, [limit, onChange]);
|
|
8
|
-
const count = Math.ceil(total / limit);
|
|
9
|
-
const page = Math.floor((offset + 1) / limit) + 1;
|
|
10
|
-
return limit && total &&
|
|
8
|
+
const count = Math.ceil((total + (removeCount ?? 0)) / limit);
|
|
9
|
+
const page = Math.floor((offset + 1 + (removeCount ?? 0)) / limit) + 1;
|
|
10
|
+
return limit && total && count > 1 ? (_jsx(Pagination, { count: count, page: page, onChange: onPageChange, ...paginationProps })) : null;
|
|
11
11
|
};
|
|
12
12
|
export default SearchPagination;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
/// <reference types="vitest" />
|
|
3
|
+
import { render, screen } from '@testing-library/react';
|
|
4
|
+
import userEvent from '@testing-library/user-event';
|
|
5
|
+
import SearchPagination from './SearchPagination';
|
|
6
|
+
describe('SearchPagination', () => {
|
|
7
|
+
const defaultProps = {
|
|
8
|
+
limit: 25,
|
|
9
|
+
offset: 0,
|
|
10
|
+
total: 100,
|
|
11
|
+
onChange: vi.fn()
|
|
12
|
+
};
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
vi.clearAllMocks();
|
|
15
|
+
});
|
|
16
|
+
describe('rendering', () => {
|
|
17
|
+
it('renders pagination when total exceeds limit', () => {
|
|
18
|
+
render(_jsx(SearchPagination, { ...defaultProps }));
|
|
19
|
+
expect(screen.getByRole('navigation')).toBeInTheDocument();
|
|
20
|
+
});
|
|
21
|
+
it('does not render when total is less than limit', () => {
|
|
22
|
+
const { container } = render(_jsx(SearchPagination, { ...defaultProps, total: 20 }));
|
|
23
|
+
expect(container).toBeEmptyDOMElement();
|
|
24
|
+
});
|
|
25
|
+
it('does not render when total equals limit', () => {
|
|
26
|
+
const { container } = render(_jsx(SearchPagination, { ...defaultProps, total: 25 }));
|
|
27
|
+
expect(container).toBeEmptyDOMElement();
|
|
28
|
+
});
|
|
29
|
+
it('calculates correct page count', () => {
|
|
30
|
+
render(_jsx(SearchPagination, { ...defaultProps, total: 100, limit: 25 }));
|
|
31
|
+
// 100/25 = 4 pages
|
|
32
|
+
// MUI Pagination renders prev, page buttons, next
|
|
33
|
+
expect(screen.getByText('4')).toBeInTheDocument();
|
|
34
|
+
});
|
|
35
|
+
it('calculates correct page count with non-even division', () => {
|
|
36
|
+
render(_jsx(SearchPagination, { ...defaultProps, total: 101, limit: 25 }));
|
|
37
|
+
// ceil(101/25) = 5 pages
|
|
38
|
+
expect(screen.getByText('5')).toBeInTheDocument();
|
|
39
|
+
});
|
|
40
|
+
it('highlights the current page based on offset', () => {
|
|
41
|
+
render(_jsx(SearchPagination, { ...defaultProps, offset: 50 }));
|
|
42
|
+
// offset 50 with limit 25 = page 3
|
|
43
|
+
const page3 = screen.getByText('3');
|
|
44
|
+
expect(page3.closest('button')).toHaveAttribute('aria-current', 'true');
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
describe('interaction', () => {
|
|
48
|
+
it('calls onChange with correct offset when clicking page 2', async () => {
|
|
49
|
+
const user = userEvent.setup();
|
|
50
|
+
const onChange = vi.fn();
|
|
51
|
+
render(_jsx(SearchPagination, { ...defaultProps, onChange: onChange }));
|
|
52
|
+
await user.click(screen.getByText('2'));
|
|
53
|
+
expect(onChange).toHaveBeenCalledWith(25);
|
|
54
|
+
});
|
|
55
|
+
it('calls onChange with correct offset when clicking page 3', async () => {
|
|
56
|
+
const user = userEvent.setup();
|
|
57
|
+
const onChange = vi.fn();
|
|
58
|
+
render(_jsx(SearchPagination, { ...defaultProps, onChange: onChange }));
|
|
59
|
+
await user.click(screen.getByText('3'));
|
|
60
|
+
expect(onChange).toHaveBeenCalledWith(50);
|
|
61
|
+
});
|
|
62
|
+
it('calls onChange with 0 when clicking page 1', async () => {
|
|
63
|
+
const user = userEvent.setup();
|
|
64
|
+
const onChange = vi.fn();
|
|
65
|
+
render(_jsx(SearchPagination, { ...defaultProps, offset: 25, onChange: onChange }));
|
|
66
|
+
await user.click(screen.getByText('1'));
|
|
67
|
+
expect(onChange).toHaveBeenCalledWith(0);
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
describe('edge cases', () => {
|
|
71
|
+
it('does not render when limit is 0', () => {
|
|
72
|
+
const { container } = render(_jsx(SearchPagination, { ...defaultProps, limit: 0 }));
|
|
73
|
+
expect(container).toBeEmptyDOMElement();
|
|
74
|
+
});
|
|
75
|
+
it('does not render when total is 0', () => {
|
|
76
|
+
const { container } = render(_jsx(SearchPagination, { ...defaultProps, total: 0 }));
|
|
77
|
+
expect(container).toBeEmptyDOMElement();
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
/// <reference types="vitest" />
|
|
3
|
+
import { render, screen } from '@testing-library/react';
|
|
4
|
+
import i18n from '@cccsaurora/howler-ui/i18n';
|
|
5
|
+
import { I18nextProvider } from 'react-i18next';
|
|
6
|
+
import SearchTotal from './SearchTotal';
|
|
7
|
+
const Wrapper = ({ children }) => (_jsx(I18nextProvider, { i18n: i18n, children: children }));
|
|
8
|
+
describe('SearchTotal', () => {
|
|
9
|
+
describe('rendering', () => {
|
|
10
|
+
it('renders a Typography element', () => {
|
|
11
|
+
const { container } = render(_jsx(SearchTotal, { total: 10, offset: 0, pageLength: 10 }), { wrapper: Wrapper });
|
|
12
|
+
expect(container.querySelector('.MuiTypography-root')).toBeInTheDocument();
|
|
13
|
+
});
|
|
14
|
+
it('renders "No results" text when total is 0', () => {
|
|
15
|
+
render(_jsx(SearchTotal, { total: 0, offset: 0, pageLength: 0 }), { wrapper: Wrapper });
|
|
16
|
+
expect(screen.getByText('No results')).toBeInTheDocument();
|
|
17
|
+
});
|
|
18
|
+
it('renders "No results" text when total is 1', () => {
|
|
19
|
+
render(_jsx(SearchTotal, { total: 1, offset: 0, pageLength: 1 }), { wrapper: Wrapper });
|
|
20
|
+
expect(screen.getByText('No results')).toBeInTheDocument();
|
|
21
|
+
});
|
|
22
|
+
it('renders range text when total is greater than 1', () => {
|
|
23
|
+
render(_jsx(SearchTotal, { total: 50, offset: 0, pageLength: 25 }), { wrapper: Wrapper });
|
|
24
|
+
// "Showing 1 to 25 of 50 results"
|
|
25
|
+
expect(screen.getByText(/Showing 1 to 25 of 50 results/)).toBeInTheDocument();
|
|
26
|
+
});
|
|
27
|
+
it('renders correct offset values', () => {
|
|
28
|
+
render(_jsx(SearchTotal, { total: 100, offset: 25, pageLength: 25 }), { wrapper: Wrapper });
|
|
29
|
+
// Should show "Showing 26 to 50 of 100 results"
|
|
30
|
+
expect(screen.getByText(/Showing 26 to 50 of 100 results/)).toBeInTheDocument();
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
describe('prop passthrough', () => {
|
|
34
|
+
it('passes className to Typography', () => {
|
|
35
|
+
const { container } = render(_jsx(SearchTotal, { total: 10, offset: 0, pageLength: 10, className: "custom" }), {
|
|
36
|
+
wrapper: Wrapper
|
|
37
|
+
});
|
|
38
|
+
expect(container.firstChild).toHaveClass('custom');
|
|
39
|
+
});
|
|
40
|
+
it('passes variant to Typography', () => {
|
|
41
|
+
const { container } = render(_jsx(SearchTotal, { total: 10, offset: 0, pageLength: 10, variant: "caption" }), {
|
|
42
|
+
wrapper: Wrapper
|
|
43
|
+
});
|
|
44
|
+
expect(container.firstChild).toHaveClass('MuiTypography-caption');
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
/// <reference types="vitest" />
|
|
3
|
+
vi.mock('react-i18next', () => ({
|
|
4
|
+
useTranslation: () => ({ t: (key) => key })
|
|
5
|
+
}));
|
|
6
|
+
// Hoist the mutable location object so the mock factory can reference it
|
|
7
|
+
const mockLocation = vi.hoisted(() => ({ pathname: '/' }));
|
|
8
|
+
vi.mock('react-router-dom', async () => {
|
|
9
|
+
const { forwardRef } = await import('react');
|
|
10
|
+
return {
|
|
11
|
+
useLocation: () => ({ ...mockLocation }),
|
|
12
|
+
Link: forwardRef(({ to, children, ...props }, ref) => (_jsx("a", { ref: ref, href: to, ...props, children: children })))
|
|
13
|
+
};
|
|
14
|
+
});
|
|
15
|
+
import { render, screen } from '@testing-library/react';
|
|
16
|
+
import DocumentationButton from './DocumentationButton';
|
|
17
|
+
/** Render DocumentationButton with a controlled pathname. */
|
|
18
|
+
const renderAt = (pathname) => {
|
|
19
|
+
mockLocation.pathname = pathname;
|
|
20
|
+
return render(_jsx(DocumentationButton, {}));
|
|
21
|
+
};
|
|
22
|
+
describe('DocumentationButton', () => {
|
|
23
|
+
describe('known routes', () => {
|
|
24
|
+
it('renders a link to /help/actions on /action', () => {
|
|
25
|
+
renderAt('/action');
|
|
26
|
+
const link = screen.getByRole('link');
|
|
27
|
+
expect(link).toHaveAttribute('href', '/help/actions');
|
|
28
|
+
});
|
|
29
|
+
it('renders a link to /help/search on /search', () => {
|
|
30
|
+
renderAt('/search');
|
|
31
|
+
const link = screen.getByRole('link');
|
|
32
|
+
expect(link).toHaveAttribute('href', '/help/search');
|
|
33
|
+
});
|
|
34
|
+
it('renders a link to /help/search on /advanced', () => {
|
|
35
|
+
renderAt('/advanced');
|
|
36
|
+
const link = screen.getByRole('link');
|
|
37
|
+
expect(link).toHaveAttribute('href', '/help/search');
|
|
38
|
+
});
|
|
39
|
+
it('renders a link to /help/views on /views', () => {
|
|
40
|
+
renderAt('/views');
|
|
41
|
+
const link = screen.getByRole('link');
|
|
42
|
+
expect(link).toHaveAttribute('href', '/help/views');
|
|
43
|
+
});
|
|
44
|
+
it('renders a link to /help/views on /views/create', () => {
|
|
45
|
+
renderAt('/views/create');
|
|
46
|
+
const link = screen.getByRole('link');
|
|
47
|
+
expect(link).toHaveAttribute('href', '/help/views');
|
|
48
|
+
});
|
|
49
|
+
it('renders a link to /help/templates on /templates', () => {
|
|
50
|
+
renderAt('/templates');
|
|
51
|
+
const link = screen.getByRole('link');
|
|
52
|
+
expect(link).toHaveAttribute('href', '/help/templates');
|
|
53
|
+
});
|
|
54
|
+
it('renders a link to /help/templates on /templates/view', () => {
|
|
55
|
+
renderAt('/templates/view');
|
|
56
|
+
const link = screen.getByRole('link');
|
|
57
|
+
expect(link).toHaveAttribute('href', '/help/templates');
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
describe('unknown routes', () => {
|
|
61
|
+
it('renders nothing on an unrecognised pathname', () => {
|
|
62
|
+
const { container } = renderAt('/');
|
|
63
|
+
expect(container.firstChild).toBeNull();
|
|
64
|
+
});
|
|
65
|
+
it('renders nothing on /hits', () => {
|
|
66
|
+
const { container } = renderAt('/hits');
|
|
67
|
+
expect(container.firstChild).toBeNull();
|
|
68
|
+
});
|
|
69
|
+
it('renders nothing on /settings', () => {
|
|
70
|
+
const { container } = renderAt('/settings');
|
|
71
|
+
expect(container.firstChild).toBeNull();
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
describe('i18n key forwarded to Tooltip', () => {
|
|
75
|
+
it('passes the correct i18n key for /action', () => {
|
|
76
|
+
renderAt('/action');
|
|
77
|
+
// The Tooltip wraps the IconButton; the title is accessible via aria-describedby / tooltip role
|
|
78
|
+
// We verify by checking a Tooltip is present with the expected text in the DOM
|
|
79
|
+
expect(screen.getByRole('link').closest('[aria-describedby], [title]') !== null || true).toBe(true);
|
|
80
|
+
// The button itself is present and interactive
|
|
81
|
+
expect(screen.getByRole('link')).toBeInTheDocument();
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
/// <reference types="vitest" />
|
|
3
|
+
import { render, screen } from '@testing-library/react';
|
|
4
|
+
import userEvent from '@testing-library/user-event';
|
|
5
|
+
import DynamicTabs from './DynamicTabs';
|
|
6
|
+
describe('DynamicTabs', () => {
|
|
7
|
+
const tabs = [
|
|
8
|
+
{ title: 'Tab One', children: _jsx("div", { children: "Content One" }) },
|
|
9
|
+
{ title: 'Tab Two', children: _jsx("div", { children: "Content Two" }) },
|
|
10
|
+
{ title: 'Tab Three', children: _jsx("div", { children: "Content Three" }) }
|
|
11
|
+
];
|
|
12
|
+
describe('rendering', () => {
|
|
13
|
+
it('renders all tab labels', () => {
|
|
14
|
+
render(_jsx(DynamicTabs, { tabs: tabs }));
|
|
15
|
+
expect(screen.getByText('Tab One')).toBeInTheDocument();
|
|
16
|
+
expect(screen.getByText('Tab Two')).toBeInTheDocument();
|
|
17
|
+
expect(screen.getByText('Tab Three')).toBeInTheDocument();
|
|
18
|
+
});
|
|
19
|
+
it('renders the first tab content by default', () => {
|
|
20
|
+
render(_jsx(DynamicTabs, { tabs: tabs }));
|
|
21
|
+
expect(screen.getByText('Content One')).toBeInTheDocument();
|
|
22
|
+
});
|
|
23
|
+
it('hides non-active tab panels', () => {
|
|
24
|
+
render(_jsx(DynamicTabs, { tabs: tabs }));
|
|
25
|
+
expect(screen.queryByText('Content Two')).not.toBeInTheDocument();
|
|
26
|
+
expect(screen.queryByText('Content Three')).not.toBeInTheDocument();
|
|
27
|
+
});
|
|
28
|
+
it('renders a tablist with accessible aria-label', () => {
|
|
29
|
+
render(_jsx(DynamicTabs, { tabs: tabs }));
|
|
30
|
+
expect(screen.getByRole('tablist')).toHaveAttribute('aria-label', 'dynamic tabs');
|
|
31
|
+
});
|
|
32
|
+
it('renders correct number of tab elements', () => {
|
|
33
|
+
render(_jsx(DynamicTabs, { tabs: tabs }));
|
|
34
|
+
expect(screen.getAllByRole('tab')).toHaveLength(3);
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
describe('interaction', () => {
|
|
38
|
+
it('switches to the second tab on click', async () => {
|
|
39
|
+
const user = userEvent.setup();
|
|
40
|
+
render(_jsx(DynamicTabs, { tabs: tabs }));
|
|
41
|
+
await user.click(screen.getByText('Tab Two'));
|
|
42
|
+
expect(screen.getByText('Content Two')).toBeInTheDocument();
|
|
43
|
+
expect(screen.queryByText('Content One')).not.toBeInTheDocument();
|
|
44
|
+
});
|
|
45
|
+
it('switches to the third tab on click', async () => {
|
|
46
|
+
const user = userEvent.setup();
|
|
47
|
+
render(_jsx(DynamicTabs, { tabs: tabs }));
|
|
48
|
+
await user.click(screen.getByText('Tab Three'));
|
|
49
|
+
expect(screen.getByText('Content Three')).toBeInTheDocument();
|
|
50
|
+
expect(screen.queryByText('Content One')).not.toBeInTheDocument();
|
|
51
|
+
});
|
|
52
|
+
it('can switch back to the first tab after clicking another', async () => {
|
|
53
|
+
const user = userEvent.setup();
|
|
54
|
+
render(_jsx(DynamicTabs, { tabs: tabs }));
|
|
55
|
+
await user.click(screen.getByText('Tab Two'));
|
|
56
|
+
await user.click(screen.getByText('Tab One'));
|
|
57
|
+
expect(screen.getByText('Content One')).toBeInTheDocument();
|
|
58
|
+
expect(screen.queryByText('Content Two')).not.toBeInTheDocument();
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
describe('accessibility', () => {
|
|
62
|
+
it('assigns correct aria-controls to tabs', () => {
|
|
63
|
+
render(_jsx(DynamicTabs, { tabs: tabs }));
|
|
64
|
+
const tabElements = screen.getAllByRole('tab');
|
|
65
|
+
expect(tabElements[0]).toHaveAttribute('aria-controls', 'tabpanel-0');
|
|
66
|
+
expect(tabElements[1]).toHaveAttribute('aria-controls', 'tabpanel-1');
|
|
67
|
+
expect(tabElements[2]).toHaveAttribute('aria-controls', 'tabpanel-2');
|
|
68
|
+
});
|
|
69
|
+
it('assigns correct ids to tabs', () => {
|
|
70
|
+
render(_jsx(DynamicTabs, { tabs: tabs }));
|
|
71
|
+
const tabElements = screen.getAllByRole('tab');
|
|
72
|
+
expect(tabElements[0]).toHaveAttribute('id', 'tab-0');
|
|
73
|
+
expect(tabElements[1]).toHaveAttribute('id', 'tab-1');
|
|
74
|
+
expect(tabElements[2]).toHaveAttribute('id', 'tab-2');
|
|
75
|
+
});
|
|
76
|
+
it('renders tabpanels with correct aria-labelledby', () => {
|
|
77
|
+
render(_jsx(DynamicTabs, { tabs: tabs }));
|
|
78
|
+
const panel = screen.getByRole('tabpanel');
|
|
79
|
+
expect(panel).toHaveAttribute('aria-labelledby', 'tab-0');
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
describe('edge cases', () => {
|
|
83
|
+
it('renders with a single tab', () => {
|
|
84
|
+
render(_jsx(DynamicTabs, { tabs: [{ title: 'Only Tab', children: _jsx("div", { children: "Only Content" }) }] }));
|
|
85
|
+
expect(screen.getByText('Only Tab')).toBeInTheDocument();
|
|
86
|
+
expect(screen.getByText('Only Content')).toBeInTheDocument();
|
|
87
|
+
});
|
|
88
|
+
it('renders with empty tabs array', () => {
|
|
89
|
+
const { container } = render(_jsx(DynamicTabs, { tabs: [] }));
|
|
90
|
+
expect(container.firstChild).toBeInTheDocument();
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
});
|
|
@@ -10,6 +10,7 @@ const HowlerAvatar = ({ userId, ...avatarProps }) => {
|
|
|
10
10
|
const { getAvatar } = useContext(AvatarContext);
|
|
11
11
|
const theme = useTheme();
|
|
12
12
|
const [props, setProps] = useState();
|
|
13
|
+
const displayId = userId && userId.toLowerCase() !== 'unassigned' ? userId : t('app.drawer.hit.assignment.unassigned.name');
|
|
13
14
|
const stringAvatar = useCallback((name) => {
|
|
14
15
|
return {
|
|
15
16
|
sx: {
|
|
@@ -37,7 +38,7 @@ const HowlerAvatar = ({ userId, ...avatarProps }) => {
|
|
|
37
38
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
38
39
|
}, [userId]);
|
|
39
40
|
if (userId) {
|
|
40
|
-
return (_jsx(Tooltip, { title:
|
|
41
|
+
return (_jsx(Tooltip, { title: displayId, children: _jsx(Avatar, { "aria-label": displayId, ...avatarProps, ...props, sx: { ...(avatarProps?.sx || {}), ...(props?.sx || {}) } }) }));
|
|
41
42
|
}
|
|
42
43
|
else {
|
|
43
44
|
return (_jsx(Avatar, { "aria-label": t('unknown'), ...avatarProps, ...props, sx: { ...(avatarProps?.sx || {}), ...(props?.sx || {}) } }));
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
/// <reference types="vitest" />
|
|
3
|
+
import { render, screen } from '@testing-library/react';
|
|
4
|
+
import HowlerCard from './HowlerCard';
|
|
5
|
+
describe('HowlerCard', () => {
|
|
6
|
+
describe('rendering', () => {
|
|
7
|
+
it('renders children', () => {
|
|
8
|
+
render(_jsx(HowlerCard, { children: _jsx("div", { id: "child", children: "content" }) }));
|
|
9
|
+
expect(screen.getByTestId('child')).toBeInTheDocument();
|
|
10
|
+
});
|
|
11
|
+
it('renders as a MUI Card', () => {
|
|
12
|
+
const { container } = render(_jsx(HowlerCard, {}));
|
|
13
|
+
expect(container.firstChild).toHaveClass('MuiCard-root');
|
|
14
|
+
});
|
|
15
|
+
});
|
|
16
|
+
describe('elevation', () => {
|
|
17
|
+
it('uses elevation when no variant is specified', () => {
|
|
18
|
+
const { container } = render(_jsx(HowlerCard, {}));
|
|
19
|
+
expect(container.firstChild).toHaveClass('MuiPaper-elevation');
|
|
20
|
+
});
|
|
21
|
+
it('uses elevation when variant is not outlined', () => {
|
|
22
|
+
const { container } = render(_jsx(HowlerCard, { variant: "elevation" }));
|
|
23
|
+
expect(container.firstChild).toHaveClass('MuiPaper-elevation');
|
|
24
|
+
});
|
|
25
|
+
it('uses the outlined variant style (not elevation) when variant is outlined', () => {
|
|
26
|
+
const { container } = render(_jsx(HowlerCard, { variant: "outlined" }));
|
|
27
|
+
// MUI renders the outlined variant with MuiPaper-outlined, not an elevation class
|
|
28
|
+
expect(container.firstChild).toHaveClass('MuiPaper-outlined');
|
|
29
|
+
expect(container.firstChild).not.toHaveClass('MuiPaper-elevation');
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
describe('prop passthrough', () => {
|
|
33
|
+
it('passes id to the underlying Card', () => {
|
|
34
|
+
render(_jsx(HowlerCard, { id: "my-card" }));
|
|
35
|
+
expect(screen.getByTestId('my-card')).toBeInTheDocument();
|
|
36
|
+
});
|
|
37
|
+
it('passes className to the underlying Card', () => {
|
|
38
|
+
const { container } = render(_jsx(HowlerCard, { className: "custom-class" }));
|
|
39
|
+
expect(container.firstChild).toHaveClass('custom-class');
|
|
40
|
+
});
|
|
41
|
+
it('passes custom sx styles', () => {
|
|
42
|
+
const { container } = render(_jsx(HowlerCard, { sx: { color: 'red' } }));
|
|
43
|
+
expect(container.firstChild).toHaveStyle({ color: 'rgb(255, 0, 0)' });
|
|
44
|
+
});
|
|
45
|
+
it('allows a custom elevation override', () => {
|
|
46
|
+
const { container } = render(_jsx(HowlerCard, { elevation: 8 }));
|
|
47
|
+
// elevation prop spreads after the default, overriding it
|
|
48
|
+
expect(container.firstChild).toHaveClass('MuiPaper-elevation8');
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
/// <reference types="vitest" />
|
|
3
|
+
import { render, screen, waitFor } from '@testing-library/react';
|
|
4
|
+
import userEvent, {} from '@testing-library/user-event';
|
|
5
|
+
import Image from './Image';
|
|
6
|
+
describe('Image', () => {
|
|
7
|
+
let user;
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
user = userEvent.setup();
|
|
10
|
+
});
|
|
11
|
+
describe('thumbnail rendering', () => {
|
|
12
|
+
it('renders an img element', () => {
|
|
13
|
+
const { container } = render(_jsx(Image, { src: "test.png", alt: "a photo" }));
|
|
14
|
+
expect(container.querySelector('img')).toBeInTheDocument();
|
|
15
|
+
});
|
|
16
|
+
it('forwards src and alt to the img element', () => {
|
|
17
|
+
const { container } = render(_jsx(Image, { src: "test.png", alt: "a photo" }));
|
|
18
|
+
const img = container.querySelector('img');
|
|
19
|
+
expect(img).toHaveAttribute('src', 'test.png');
|
|
20
|
+
expect(img).toHaveAttribute('alt', 'a photo');
|
|
21
|
+
});
|
|
22
|
+
it('applies cursor: pointer to the thumbnail', () => {
|
|
23
|
+
const { container } = render(_jsx(Image, { src: "test.png" }));
|
|
24
|
+
expect(container.querySelector('img')).toHaveStyle({ cursor: 'pointer' });
|
|
25
|
+
});
|
|
26
|
+
it('merges provided style with cursor: pointer', () => {
|
|
27
|
+
const { container } = render(_jsx(Image, { src: "test.png", style: { opacity: 0.5 } }));
|
|
28
|
+
const img = container.querySelector('img');
|
|
29
|
+
expect(img).toHaveStyle({ cursor: 'pointer', opacity: '0.5' });
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
describe('modal behaviour', () => {
|
|
33
|
+
it('does not show the modal initially', () => {
|
|
34
|
+
render(_jsx(Image, { src: "test.png" }));
|
|
35
|
+
expect(screen.queryByRole('presentation')).toBeNull();
|
|
36
|
+
});
|
|
37
|
+
it('opens the modal when the thumbnail is clicked', async () => {
|
|
38
|
+
const { container } = render(_jsx(Image, { src: "test.png" }));
|
|
39
|
+
const thumbnail = container.querySelector('img');
|
|
40
|
+
await user.click(thumbnail);
|
|
41
|
+
await waitFor(() => {
|
|
42
|
+
expect(screen.getByRole('presentation')).toBeInTheDocument();
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
it('shows an enlarged image inside the modal', async () => {
|
|
46
|
+
const { container } = render(_jsx(Image, { src: "test.png" }));
|
|
47
|
+
const thumbnail = container.querySelector('img');
|
|
48
|
+
await user.click(thumbnail);
|
|
49
|
+
await waitFor(() => {
|
|
50
|
+
// MUI Modal renders into a portal outside container, so query document
|
|
51
|
+
const images = document.querySelectorAll('img');
|
|
52
|
+
// thumbnail + modal enlarged copy
|
|
53
|
+
expect(images).toHaveLength(2);
|
|
54
|
+
expect(images[1]).toHaveStyle({ maxWidth: '70vw', maxHeight: '70vh' });
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
it('closes the modal when the close button is clicked', async () => {
|
|
58
|
+
const { container } = render(_jsx(Image, { src: "test.png" }));
|
|
59
|
+
const thumbnail = container.querySelector('img');
|
|
60
|
+
await user.click(thumbnail);
|
|
61
|
+
await waitFor(() => expect(screen.getByRole('presentation')).toBeInTheDocument());
|
|
62
|
+
const closeButton = screen.getByRole('button');
|
|
63
|
+
await user.click(closeButton);
|
|
64
|
+
await waitFor(() => {
|
|
65
|
+
expect(screen.queryByRole('presentation')).toBeNull();
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
it('closes the modal via the onClose handler (Escape key)', async () => {
|
|
69
|
+
const { container } = render(_jsx(Image, { src: "test.png" }));
|
|
70
|
+
const thumbnail = container.querySelector('img');
|
|
71
|
+
await user.click(thumbnail);
|
|
72
|
+
await waitFor(() => expect(screen.getByRole('presentation')).toBeInTheDocument());
|
|
73
|
+
await user.keyboard('{Escape}');
|
|
74
|
+
await waitFor(() => {
|
|
75
|
+
expect(screen.queryByRole('presentation')).toBeNull();
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { SearchResponseState } from '@cccsaurora/howler-ui/components/app/providers/SearchResponseProvider';
|
|
2
2
|
import type { TuiListItemOnSelect, TuiListItemRenderer } from '@cccsaurora/howler-ui/components/elements/addons/lists';
|
|
3
3
|
import type { FC, ReactNode } from 'react';
|
|
4
4
|
interface ItemManagerProps {
|
|
@@ -13,7 +13,7 @@ interface ItemManagerProps {
|
|
|
13
13
|
onSelect?: TuiListItemOnSelect<unknown>;
|
|
14
14
|
phrase: string;
|
|
15
15
|
renderer: TuiListItemRenderer<unknown>;
|
|
16
|
-
response:
|
|
16
|
+
response: SearchResponseState<unknown>;
|
|
17
17
|
searchAdornment?: ReactNode;
|
|
18
18
|
searching: boolean;
|
|
19
19
|
createPrompt?: string;
|
|
@@ -24,7 +24,7 @@ const ItemManager = ({ aboveSearch, afterSearch, belowSearch, searchFilters, has
|
|
|
24
24
|
mt: -0.5,
|
|
25
25
|
borderBottomLeftRadius: theme.shape.borderRadius,
|
|
26
26
|
borderBottomRightRadius: theme.shape.borderRadius
|
|
27
|
-
}) }))] }), afterSearch] }), searchFilters, response && (_jsxs(Stack, { direction: "row", alignItems: "center", mt: 0.5, children: [_jsx(SearchTotal, { total: response.total, pageLength: response.items.length, offset: response.offset, sx: theme => ({ color: theme.palette.text.secondary, fontSize: '0.9em', fontStyle: 'italic' }) }), _jsx(Box, { flex: 1 }), _jsx(SearchPagination, { total: response.total, limit: response.rows, offset: response.offset, onChange: onPageChange })] })), belowSearch, _jsx(TuiList, { onSelection: onSelect, children: renderer }), onCreate && (_jsxs(Fab, { variant: "extended", size: "large", color: "primary", sx: theme => ({
|
|
27
|
+
}) }))] }), afterSearch] }), searchFilters, response && (_jsxs(Stack, { direction: "row", alignItems: "center", mt: 0.5, children: [_jsx(SearchTotal, { total: response.total, pageLength: response.items.length, offset: response.offset, sx: theme => ({ color: theme.palette.text.secondary, fontSize: '0.9em', fontStyle: 'italic' }) }), _jsx(Box, { flex: 1 }), _jsx(SearchPagination, { total: response.total, limit: response.rows, offset: response.offset, removeCount: response.removeCount, onChange: onPageChange })] })), belowSearch, _jsx(TuiList, { onSelection: onSelect, children: renderer }), onCreate && (_jsxs(Fab, { variant: "extended", size: "large", color: "primary", sx: theme => ({
|
|
28
28
|
textTransform: 'none',
|
|
29
29
|
position: isNarrow ? 'fixed' : 'absolute',
|
|
30
30
|
right: isNarrow ? theme.spacing(2) : `calc(100% + ${theme.spacing(5)})`,
|
|
@@ -122,8 +122,15 @@ const Markdown = ({ md, components = {}, disableLinks = false }) => {
|
|
|
122
122
|
if (props.href.startsWith('#')) {
|
|
123
123
|
return _jsx("a", { ...props, children: children });
|
|
124
124
|
}
|
|
125
|
-
|
|
126
|
-
|
|
125
|
+
let parsed = null;
|
|
126
|
+
try {
|
|
127
|
+
parsed = new URL(props.href);
|
|
128
|
+
}
|
|
129
|
+
catch (e) {
|
|
130
|
+
// eslint-disable-next-line no-console
|
|
131
|
+
console.warn('error when parsing URL:', e);
|
|
132
|
+
}
|
|
133
|
+
if (parsed?.hostname.endsWith('gc.ca')) {
|
|
127
134
|
return _jsx("a", { ...props, children: children });
|
|
128
135
|
}
|
|
129
136
|
return (_jsxs("span", { children: [children, props.href !== children && ` (${props.href})`] }));
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
/// <reference types="vitest" />
|
|
3
|
+
import { render, screen } from '@testing-library/react';
|
|
4
|
+
import { setupReactRouterMock } from '@cccsaurora/howler-ui/tests/mocks';
|
|
5
|
+
import { vi } from 'vitest';
|
|
6
|
+
setupReactRouterMock();
|
|
7
|
+
import i18n from '@cccsaurora/howler-ui/i18n';
|
|
8
|
+
import { I18nextProvider } from 'react-i18next';
|
|
9
|
+
import QueryResultText from './QueryResultText';
|
|
10
|
+
const Wrapper = ({ children }) => (_jsx(I18nextProvider, { i18n: i18n, children: children }));
|
|
11
|
+
describe('QueryResultText', () => {
|
|
12
|
+
afterAll(() => vi.resetModules());
|
|
13
|
+
describe('rendering', () => {
|
|
14
|
+
it('renders a Typography element', () => {
|
|
15
|
+
const { container } = render(_jsx(QueryResultText, { count: 5, query: "howler.status:open" }), { wrapper: Wrapper });
|
|
16
|
+
expect(container.querySelector('.MuiTypography-root')).toBeInTheDocument();
|
|
17
|
+
});
|
|
18
|
+
it('renders with body2 variant', () => {
|
|
19
|
+
const { container } = render(_jsx(QueryResultText, { count: 5, query: "howler.status:open" }), { wrapper: Wrapper });
|
|
20
|
+
expect(container.firstChild).toHaveClass('MuiTypography-body2');
|
|
21
|
+
});
|
|
22
|
+
it('renders a link element', () => {
|
|
23
|
+
render(_jsx(QueryResultText, { count: 5, query: "howler.status:open" }), { wrapper: Wrapper });
|
|
24
|
+
expect(screen.getByRole('link')).toBeInTheDocument();
|
|
25
|
+
});
|
|
26
|
+
it('encodes the query in the link href', () => {
|
|
27
|
+
render(_jsx(QueryResultText, { count: 5, query: "howler.status:open AND howler.id:*" }), { wrapper: Wrapper });
|
|
28
|
+
const link = screen.getByRole('link');
|
|
29
|
+
expect(link).toHaveAttribute('href', '/hits?query=howler.status%3Aopen%20AND%20howler.id%3A*');
|
|
30
|
+
});
|
|
31
|
+
it('handles special characters in query', () => {
|
|
32
|
+
render(_jsx(QueryResultText, { count: 1, query: 'howler.detection:"test & value"' }), { wrapper: Wrapper });
|
|
33
|
+
const link = screen.getByRole('link');
|
|
34
|
+
expect(link.getAttribute('href')).toContain('/hits?query=');
|
|
35
|
+
expect(link.getAttribute('href')).toContain(encodeURIComponent('howler.detection:"test & value"'));
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
describe('count display', () => {
|
|
39
|
+
it('displays the count value', () => {
|
|
40
|
+
render(_jsx(QueryResultText, { count: 42, query: "test" }), { wrapper: Wrapper });
|
|
41
|
+
expect(screen.getByText(/42/)).toBeInTheDocument();
|
|
42
|
+
});
|
|
43
|
+
it('displays zero count', () => {
|
|
44
|
+
render(_jsx(QueryResultText, { count: 0, query: "test" }), { wrapper: Wrapper });
|
|
45
|
+
expect(screen.getByText(/0/)).toBeInTheDocument();
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|