@cccsaurora/howler-ui 2.18.0-dev.736 → 2.18.0-dev.739
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/index.d.ts +0 -2
- package/api/index.js +2 -4
- package/api/search/facet/hit.d.ts +3 -1
- package/api/search/facet/index.d.ts +1 -3
- package/api/search/index.d.ts +1 -2
- package/api/search/index.js +1 -2
- package/commons/components/leftnav/LeftNavDrawer.js +1 -1
- package/components/app/App.js +7 -39
- package/components/app/hooks/useMatchers.d.ts +1 -1
- package/components/app/hooks/useMatchers.js +11 -23
- package/components/app/hooks/useMatchers.test.js +22 -22
- package/components/app/hooks/useTitle.js +3 -3
- package/components/app/providers/FavouritesProvider.js +2 -2
- package/components/app/providers/HitProvider.d.ts +22 -0
- package/components/app/providers/{RecordProvider.js → HitProvider.js} +41 -41
- package/components/app/providers/{RecordSearchProvider.d.ts → HitSearchProvider.d.ts} +6 -6
- package/components/app/providers/{RecordSearchProvider.js → HitSearchProvider.js} +17 -12
- package/components/app/providers/{RecordSearchProvider.test.js → HitSearchProvider.test.js} +70 -51
- package/components/app/providers/ModalProvider.d.ts +0 -1
- package/components/app/providers/ParameterProvider.d.ts +2 -9
- package/components/app/providers/ParameterProvider.js +240 -165
- package/components/app/providers/ParameterProvider.test.js +94 -346
- package/components/app/providers/UserListProvider.js +8 -28
- package/components/elements/PluginTypography.d.ts +1 -2
- package/components/elements/PluginTypography.js +2 -3
- package/components/elements/UserList.d.ts +2 -5
- package/components/elements/UserList.js +8 -18
- package/components/elements/addons/search/phrase/Phrase.js +1 -1
- package/components/elements/display/ChipPopper.d.ts +1 -1
- package/components/elements/display/HowlerCard.js +1 -1
- package/components/elements/display/Modal.js +0 -2
- package/components/elements/display/icons/BundleButton.d.ts +6 -0
- package/components/elements/display/icons/BundleButton.js +32 -0
- package/components/elements/hit/HitActions.js +4 -4
- package/components/elements/hit/HitBanner.d.ts +0 -1
- package/components/elements/hit/HitBanner.js +49 -29
- package/components/elements/hit/HitCard.d.ts +0 -2
- package/components/elements/hit/HitCard.js +7 -7
- package/components/elements/{record/RecordComments.d.ts → hit/HitComments.d.ts} +4 -5
- package/components/elements/{record/RecordComments.js → hit/HitComments.js} +28 -29
- package/components/elements/{ObjectDetails.js → hit/HitDetails.js} +17 -17
- package/components/elements/hit/HitLabels.js +2 -2
- package/components/elements/hit/HitOutline.d.ts +0 -1
- package/components/elements/hit/HitOutline.js +3 -3
- package/components/elements/hit/{HitPreview.d.ts → HitQuickSearch.d.ts} +3 -3
- package/components/elements/hit/{HitPreview.js → HitQuickSearch.js} +4 -10
- package/components/elements/hit/HitRelated.d.ts +6 -0
- package/components/elements/hit/HitRelated.js +7 -0
- package/components/elements/hit/HitSummary.d.ts +1 -2
- package/components/elements/hit/HitSummary.js +5 -6
- package/components/elements/{record/RecordWorklog.d.ts → hit/HitWorklog.d.ts} +3 -4
- package/components/elements/{record/RecordWorklog.js → hit/HitWorklog.js} +13 -15
- package/components/elements/hit/aggregate/HitGraph.js +8 -8
- package/components/elements/hit/outlines/DefaultOutline.js +1 -1
- package/components/elements/view/ViewTitle.d.ts +0 -1
- package/components/elements/view/ViewTitle.js +2 -9
- package/components/hooks/useHitActions.d.ts +1 -1
- package/components/hooks/useHitActions.js +4 -4
- package/components/hooks/{useRecordSelection.d.ts → useHitSelection.d.ts} +2 -2
- package/components/hooks/{useRecordSelection.js → useHitSelection.js} +33 -12
- package/components/hooks/useMyPreferences.js +1 -10
- package/components/hooks/useMySearch.js +2 -2
- package/components/hooks/useMySitemap.js +1 -4
- package/components/hooks/useMyTheme.js +2 -9
- package/components/hooks/useParamState.test.js +4 -3
- package/components/routes/action/edit/ActionEditor.js +2 -2
- package/components/routes/action/view/ActionSearch.js +1 -1
- package/components/routes/advanced/QueryBuilder.js +1 -1
- package/components/routes/advanced/QueryEditor.js +3 -3
- package/components/routes/advanced/historyCompletionProvider.js +3 -3
- package/components/routes/analytics/AnalyticDetails.js +2 -2
- package/components/routes/analytics/AnalyticSearch.js +1 -1
- package/components/routes/dossiers/DossierEditor.js +2 -2
- package/components/routes/dossiers/DossierEditor.test.js +1 -1
- package/components/routes/help/ApiDocumentation.js +1 -1
- package/components/routes/help/BundleDocumentation.d.ts +3 -0
- package/components/routes/help/BundleDocumentation.js +12 -0
- package/components/routes/help/HitBannerDocumentation.js +0 -1
- package/components/routes/help/HitDocumentation.js +3 -1
- package/components/routes/help/markdown/en/bundles.md.js +1 -0
- package/components/routes/help/markdown/fr/bundles.md.js +1 -0
- package/components/routes/hits/search/BundleParentMenu.d.ts +6 -0
- package/components/routes/hits/search/BundleParentMenu.js +32 -0
- package/components/routes/hits/search/BundleScroller.d.ts +2 -0
- package/components/routes/hits/search/BundleScroller.js +6 -0
- package/components/routes/hits/search/{RecordBrowser.js → HitBrowser.js} +9 -9
- package/components/{elements/record/RecordContextMenu.d.ts → routes/hits/search/HitContextMenu.d.ts} +3 -3
- package/components/routes/hits/search/HitContextMenu.js +227 -0
- package/components/{elements/record/RecordContextMenu.test.js → routes/hits/search/HitContextMenu.test.js} +39 -94
- package/components/routes/hits/search/{RecordQuery.d.ts → HitQuery.d.ts} +2 -2
- package/components/routes/hits/search/{RecordQuery.js → HitQuery.js} +6 -6
- package/components/routes/hits/search/InformationPane.d.ts +0 -1
- package/components/routes/hits/search/InformationPane.js +60 -47
- package/components/routes/hits/search/LayoutSettings.js +3 -3
- package/components/routes/hits/search/QuerySettings.js +1 -2
- package/components/routes/hits/search/QuerySettings.test.js +9 -14
- package/components/routes/hits/search/SearchPane.js +49 -26
- package/components/routes/hits/search/ViewLink.js +3 -3
- package/components/routes/hits/search/ViewLink.test.js +8 -8
- package/components/routes/hits/search/grid/AddColumnModal.js +4 -5
- package/components/routes/hits/search/grid/EnhancedCell.d.ts +1 -2
- package/components/routes/hits/search/grid/EnhancedCell.js +2 -2
- package/components/routes/hits/search/grid/HitGrid.js +18 -20
- package/components/routes/hits/search/grid/{RecordRow.d.ts → HitRow.d.ts} +2 -3
- package/components/routes/hits/search/grid/{RecordRow.js → HitRow.js} +8 -10
- package/components/routes/hits/view/HitViewer.js +13 -12
- package/components/routes/home/ViewCard.js +41 -47
- package/components/{elements/MarkdownEditor.js → routes/overviews/OverviewEditor.js} +3 -3
- package/components/routes/overviews/OverviewViewer.js +2 -2
- package/components/routes/views/ViewComposer.js +19 -46
- package/locales/en/translation.json +3 -89
- package/locales/fr/translation.json +3 -87
- package/models/WithMetadata.d.ts +1 -2
- package/models/entities/generated/{ThreatEnrichment.d.ts → Enrichment.d.ts} +1 -1
- package/models/entities/generated/Hit.d.ts +0 -1
- package/models/entities/generated/Howler.d.ts +4 -0
- package/models/entities/generated/Rule.d.ts +10 -2
- package/models/entities/generated/Threat.d.ts +2 -2
- package/models/entities/generated/View.d.ts +0 -1
- package/package.json +103 -120
- package/plugins/clue/components/ClueTypography.js +2 -2
- package/plugins/clue/utils.d.ts +1 -2
- package/tests/mocks.d.ts +1 -11
- package/tests/mocks.js +7 -12
- package/tests/server-handlers.js +1 -6
- package/tests/server.d.ts +1 -1
- package/tests/utils.d.ts +0 -4
- package/tests/utils.js +0 -20
- package/utils/constants.d.ts +3 -3
- package/utils/hitFunctions.d.ts +1 -2
- package/utils/hitFunctions.js +4 -4
- package/utils/viewUtils.js +0 -3
- package/api/search/case.d.ts +0 -4
- package/api/search/case.js +0 -8
- package/api/v2/case/index.d.ts +0 -8
- package/api/v2/case/index.js +0 -20
- package/api/v2/case/items.d.ts +0 -6
- package/api/v2/case/items.js +0 -18
- package/api/v2/index.d.ts +0 -4
- package/api/v2/index.js +0 -6
- package/api/v2/search/facet.d.ts +0 -3
- package/api/v2/search/facet.js +0 -12
- package/api/v2/search/index.d.ts +0 -5
- package/api/v2/search/index.js +0 -24
- package/components/app/providers/RecordProvider.d.ts +0 -23
- package/components/elements/ContextMenu.d.ts +0 -56
- package/components/elements/ContextMenu.js +0 -109
- package/components/elements/ContextMenu.test.js +0 -215
- package/components/elements/ObjectDetails.d.ts +0 -6
- package/components/elements/case/CaseCard.d.ts +0 -12
- package/components/elements/case/CaseCard.js +0 -42
- package/components/elements/case/CasePreview.d.ts +0 -6
- package/components/elements/case/CasePreview.js +0 -17
- package/components/elements/case/StatusIcon.d.ts +0 -5
- package/components/elements/case/StatusIcon.js +0 -13
- package/components/elements/hit/elements/AnalyticLink.d.ts +0 -9
- package/components/elements/hit/elements/AnalyticLink.js +0 -22
- package/components/elements/hit/related/RelatedRecords.js +0 -63
- package/components/elements/observable/ObservableCard.d.ts +0 -6
- package/components/elements/observable/ObservableCard.js +0 -22
- package/components/elements/observable/ObservablePreview.d.ts +0 -6
- package/components/elements/observable/ObservablePreview.js +0 -12
- package/components/elements/record/RecordContextMenu.js +0 -247
- package/components/elements/record/RecordContextMenu.test.d.ts +0 -1
- package/components/elements/record/RecordRelated.d.ts +0 -7
- package/components/elements/record/RecordRelated.js +0 -34
- package/components/hooks/useRelatedRecords.d.ts +0 -13
- package/components/hooks/useRelatedRecords.js +0 -32
- package/components/routes/cases/CaseViewer.d.ts +0 -2
- package/components/routes/cases/CaseViewer.js +0 -22
- package/components/routes/cases/Cases.d.ts +0 -2
- package/components/routes/cases/Cases.js +0 -101
- package/components/routes/cases/constants.d.ts +0 -5
- package/components/routes/cases/constants.js +0 -5
- package/components/routes/cases/detail/AlertPanel.d.ts +0 -6
- package/components/routes/cases/detail/AlertPanel.js +0 -33
- package/components/routes/cases/detail/CaseAssets.d.ts +0 -11
- package/components/routes/cases/detail/CaseAssets.js +0 -104
- package/components/routes/cases/detail/CaseAssets.test.d.ts +0 -1
- package/components/routes/cases/detail/CaseAssets.test.js +0 -167
- package/components/routes/cases/detail/CaseDashboard.d.ts +0 -7
- package/components/routes/cases/detail/CaseDashboard.js +0 -66
- package/components/routes/cases/detail/CaseDetails.d.ts +0 -6
- package/components/routes/cases/detail/CaseDetails.js +0 -61
- package/components/routes/cases/detail/CaseOverview.d.ts +0 -7
- package/components/routes/cases/detail/CaseOverview.js +0 -43
- package/components/routes/cases/detail/CaseSidebar.d.ts +0 -8
- package/components/routes/cases/detail/CaseSidebar.js +0 -107
- package/components/routes/cases/detail/CaseSidebar.test.d.ts +0 -1
- package/components/routes/cases/detail/CaseSidebar.test.js +0 -246
- package/components/routes/cases/detail/CaseTask.d.ts +0 -11
- package/components/routes/cases/detail/CaseTask.js +0 -57
- package/components/routes/cases/detail/CaseTimeline.d.ts +0 -12
- package/components/routes/cases/detail/CaseTimeline.js +0 -106
- package/components/routes/cases/detail/CaseTimeline.test.d.ts +0 -1
- package/components/routes/cases/detail/CaseTimeline.test.js +0 -227
- package/components/routes/cases/detail/ItemPage.d.ts +0 -6
- package/components/routes/cases/detail/ItemPage.js +0 -99
- package/components/routes/cases/detail/RelatedCasePanel.d.ts +0 -6
- package/components/routes/cases/detail/RelatedCasePanel.js +0 -34
- package/components/routes/cases/detail/TaskPanel.d.ts +0 -7
- package/components/routes/cases/detail/TaskPanel.js +0 -52
- package/components/routes/cases/detail/aggregates/CaseAggregate.d.ts +0 -11
- package/components/routes/cases/detail/aggregates/CaseAggregate.js +0 -24
- package/components/routes/cases/detail/aggregates/SourceAggregate.d.ts +0 -6
- package/components/routes/cases/detail/aggregates/SourceAggregate.js +0 -26
- package/components/routes/cases/detail/assets/Asset.d.ts +0 -14
- package/components/routes/cases/detail/assets/Asset.js +0 -12
- package/components/routes/cases/detail/assets/Asset.test.d.ts +0 -1
- package/components/routes/cases/detail/assets/Asset.test.js +0 -72
- package/components/routes/cases/detail/sidebar/CaseFolder.d.ts +0 -20
- package/components/routes/cases/detail/sidebar/CaseFolder.js +0 -83
- package/components/routes/cases/detail/sidebar/CaseFolder.test.d.ts +0 -1
- package/components/routes/cases/detail/sidebar/CaseFolder.test.js +0 -295
- package/components/routes/cases/detail/sidebar/CaseFolderContextMenu.d.ts +0 -34
- package/components/routes/cases/detail/sidebar/CaseFolderContextMenu.js +0 -103
- package/components/routes/cases/detail/sidebar/CaseFolderContextMenu.test.d.ts +0 -1
- package/components/routes/cases/detail/sidebar/CaseFolderContextMenu.test.js +0 -363
- package/components/routes/cases/detail/sidebar/FolderEntry.d.ts +0 -25
- package/components/routes/cases/detail/sidebar/FolderEntry.js +0 -88
- package/components/routes/cases/detail/sidebar/FolderEntry.test.d.ts +0 -1
- package/components/routes/cases/detail/sidebar/FolderEntry.test.js +0 -206
- package/components/routes/cases/detail/sidebar/RootDropZone.d.ts +0 -5
- package/components/routes/cases/detail/sidebar/RootDropZone.js +0 -33
- package/components/routes/cases/detail/sidebar/types.d.ts +0 -9
- package/components/routes/cases/detail/sidebar/utils.d.ts +0 -3
- package/components/routes/cases/detail/sidebar/utils.js +0 -29
- package/components/routes/cases/detail/sidebar/utils.test.d.ts +0 -1
- package/components/routes/cases/detail/sidebar/utils.test.js +0 -82
- package/components/routes/cases/hooks/useCase.d.ts +0 -13
- package/components/routes/cases/hooks/useCase.js +0 -51
- package/components/routes/cases/modals/AddToCaseModal.d.ts +0 -7
- package/components/routes/cases/modals/AddToCaseModal.js +0 -62
- package/components/routes/cases/modals/RenameItemModal.d.ts +0 -9
- package/components/routes/cases/modals/RenameItemModal.js +0 -48
- package/components/routes/cases/modals/ResolveModal.d.ts +0 -7
- package/components/routes/cases/modals/ResolveModal.js +0 -115
- package/components/routes/cases/modals/ResolveModal.test.d.ts +0 -1
- package/components/routes/cases/modals/ResolveModal.test.js +0 -384
- package/components/routes/hits/search/shared/IndexPicker.d.ts +0 -2
- package/components/routes/hits/search/shared/IndexPicker.js +0 -20
- package/components/routes/observables/ObservableViewer.d.ts +0 -7
- package/components/routes/observables/ObservableViewer.js +0 -27
- package/models/entities/generated/AttachmentsFile.d.ts +0 -12
- package/models/entities/generated/Case.d.ts +0 -28
- package/models/entities/generated/DestinationOriginal.d.ts +0 -19
- package/models/entities/generated/EmailAttachment.d.ts +0 -8
- package/models/entities/generated/EmailParent.d.ts +0 -19
- package/models/entities/generated/Enrichments.d.ts +0 -7
- package/models/entities/generated/EnrichmentsIndicator.d.ts +0 -21
- package/models/entities/generated/HttpResponse.d.ts +0 -11
- package/models/entities/generated/Item.d.ts +0 -9
- package/models/entities/generated/Observable.d.ts +0 -85
- package/models/entities/generated/ObservableCloud.d.ts +0 -20
- package/models/entities/generated/ObservableDestination.d.ts +0 -23
- package/models/entities/generated/ObservableEmail.d.ts +0 -30
- package/models/entities/generated/ObservableFile.d.ts +0 -36
- package/models/entities/generated/ObservableHowler.d.ts +0 -43
- package/models/entities/generated/ObservableHttp.d.ts +0 -11
- package/models/entities/generated/ObservableObserver.d.ts +0 -21
- package/models/entities/generated/ObservableOrganization.d.ts +0 -7
- package/models/entities/generated/ObservableProcess.d.ts +0 -34
- package/models/entities/generated/ObservableSource.d.ts +0 -23
- package/models/entities/generated/ObservableThreat.d.ts +0 -21
- package/models/entities/generated/ObservableTls.d.ts +0 -12
- package/models/entities/generated/ObserverIngress.d.ts +0 -9
- package/models/entities/generated/Task.d.ts +0 -10
- package/utils/typeUtils.d.ts +0 -7
- package/utils/typeUtils.js +0 -27
- /package/components/app/providers/{RecordSearchProvider.test.d.ts → HitSearchProvider.test.d.ts} +0 -0
- /package/components/elements/hit/{related/RelatedRecords.d.ts → HitDetails.d.ts} +0 -0
- /package/components/routes/hits/search/{RecordBrowser.d.ts → HitBrowser.d.ts} +0 -0
- /package/components/{elements/ContextMenu.test.d.ts → routes/hits/search/HitContextMenu.test.d.ts} +0 -0
- /package/components/{elements/MarkdownEditor.d.ts → routes/overviews/OverviewEditor.d.ts} +0 -0
|
@@ -1,18 +1,23 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { act, renderHook, waitFor } from '@testing-library/react';
|
|
3
|
-
import { setupReactRouterMock } from '@cccsaurora/howler-ui/tests/mocks';
|
|
4
3
|
import { useContextSelector } from 'use-context-selector';
|
|
5
4
|
import { DEFAULT_QUERY } from '@cccsaurora/howler-ui/utils/constants';
|
|
6
5
|
import ParameterProvider, { ParameterContext } from './ParameterProvider';
|
|
7
6
|
// Mock dependencies
|
|
8
|
-
const
|
|
7
|
+
const mockSetParams = vi.fn();
|
|
8
|
+
const mockLocation = { pathname: '/hits', search: '' };
|
|
9
|
+
const mockParams = { id: undefined };
|
|
10
|
+
let mockSearchParams = new URLSearchParams();
|
|
11
|
+
vi.mock('react-router-dom', () => ({
|
|
12
|
+
useLocation: vi.fn(() => mockLocation),
|
|
13
|
+
useParams: vi.fn(() => mockParams),
|
|
14
|
+
useSearchParams: vi.fn(() => [mockSearchParams, mockSetParams])
|
|
15
|
+
}));
|
|
9
16
|
const Wrapper = ({ children }) => {
|
|
10
17
|
return _jsx(ParameterProvider, { children: children });
|
|
11
18
|
};
|
|
12
19
|
beforeEach(() => {
|
|
13
|
-
|
|
14
|
-
mockSearchParams.delete(key);
|
|
15
|
-
}
|
|
20
|
+
mockSearchParams = new URLSearchParams();
|
|
16
21
|
mockSetParams.mockClear();
|
|
17
22
|
mockLocation.pathname = '/hits';
|
|
18
23
|
mockLocation.search = '';
|
|
@@ -34,13 +39,15 @@ describe('ParameterContext', () => {
|
|
|
34
39
|
expect(hook.result.current.trackTotalHits).toBe(false);
|
|
35
40
|
});
|
|
36
41
|
it('should initialize with values from URL params', async () => {
|
|
37
|
-
mockSearchParams
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
mockSearchParams = new URLSearchParams({
|
|
43
|
+
query: 'test query',
|
|
44
|
+
sort: 'test.field asc',
|
|
45
|
+
span: 'date.range.1.week',
|
|
46
|
+
offset: '25',
|
|
47
|
+
selected: 'test_id',
|
|
48
|
+
filter: 'status:open',
|
|
49
|
+
track_total_hits: 'true'
|
|
50
|
+
});
|
|
44
51
|
const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ({
|
|
45
52
|
query: ctx.query,
|
|
46
53
|
sort: ctx.sort,
|
|
@@ -59,9 +66,11 @@ describe('ParameterContext', () => {
|
|
|
59
66
|
expect(hook.result.current.trackTotalHits).toBe(true);
|
|
60
67
|
});
|
|
61
68
|
it('should handle custom date span with start and end dates', async () => {
|
|
62
|
-
mockSearchParams
|
|
63
|
-
|
|
64
|
-
|
|
69
|
+
mockSearchParams = new URLSearchParams({
|
|
70
|
+
span: 'date.range.custom',
|
|
71
|
+
start_date: '2025-01-01',
|
|
72
|
+
end_date: '2025-12-31'
|
|
73
|
+
});
|
|
65
74
|
const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ({
|
|
66
75
|
span: ctx.span,
|
|
67
76
|
startDate: ctx.startDate,
|
|
@@ -85,7 +94,7 @@ describe('ParameterContext', () => {
|
|
|
85
94
|
});
|
|
86
95
|
});
|
|
87
96
|
it('should not update if the value is the same', async () => {
|
|
88
|
-
mockSearchParams
|
|
97
|
+
mockSearchParams = new URLSearchParams({ query: 'existing query' });
|
|
89
98
|
const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ({
|
|
90
99
|
query: ctx.query,
|
|
91
100
|
setQuery: ctx.setQuery
|
|
@@ -125,9 +134,11 @@ describe('ParameterContext', () => {
|
|
|
125
134
|
});
|
|
126
135
|
});
|
|
127
136
|
it('should clear startDate and endDate when span does not end with custom', async () => {
|
|
128
|
-
mockSearchParams
|
|
129
|
-
|
|
130
|
-
|
|
137
|
+
mockSearchParams = new URLSearchParams({
|
|
138
|
+
span: 'date.range.custom',
|
|
139
|
+
start_date: '2025-01-01',
|
|
140
|
+
end_date: '2025-12-31'
|
|
141
|
+
});
|
|
131
142
|
const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ({
|
|
132
143
|
span: ctx.span,
|
|
133
144
|
startDate: ctx.startDate,
|
|
@@ -150,17 +161,19 @@ describe('ParameterContext', () => {
|
|
|
150
161
|
expect(hook.result.current).toEqual([]);
|
|
151
162
|
});
|
|
152
163
|
it('should initialize with single filter from URL', async () => {
|
|
153
|
-
mockSearchParams
|
|
164
|
+
mockSearchParams = new URLSearchParams({ filter: 'status:open' });
|
|
154
165
|
const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ctx.filters), { wrapper: Wrapper });
|
|
155
166
|
expect(hook.result.current).toEqual(['status:open']);
|
|
156
167
|
});
|
|
157
168
|
it('should initialize with multiple filters from URL', async () => {
|
|
169
|
+
mockSearchParams = new URLSearchParams();
|
|
158
170
|
mockSearchParams.append('filter', 'howler.escalation:hit');
|
|
159
171
|
mockSearchParams.append('filter', 'howler.assignment:someuser');
|
|
160
172
|
const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ctx.filters), { wrapper: Wrapper });
|
|
161
173
|
expect(hook.result.current).toEqual(['howler.escalation:hit', 'howler.assignment:someuser']);
|
|
162
174
|
});
|
|
163
175
|
it('should preserve filter order from URL', async () => {
|
|
176
|
+
mockSearchParams = new URLSearchParams();
|
|
164
177
|
mockSearchParams.append('filter', 'c');
|
|
165
178
|
mockSearchParams.append('filter', 'a');
|
|
166
179
|
mockSearchParams.append('filter', 'b');
|
|
@@ -168,6 +181,7 @@ describe('ParameterContext', () => {
|
|
|
168
181
|
expect(hook.result.current).toEqual(['c', 'a', 'b']);
|
|
169
182
|
});
|
|
170
183
|
it('should deduplicate multiple empty filter params to single empty string', async () => {
|
|
184
|
+
mockSearchParams = new URLSearchParams();
|
|
171
185
|
mockSearchParams.append('filter', '');
|
|
172
186
|
mockSearchParams.append('filter', '');
|
|
173
187
|
mockSearchParams.append('filter', '');
|
|
@@ -188,7 +202,7 @@ describe('ParameterContext', () => {
|
|
|
188
202
|
});
|
|
189
203
|
});
|
|
190
204
|
it('should append filter to existing filters', async () => {
|
|
191
|
-
mockSearchParams
|
|
205
|
+
mockSearchParams = new URLSearchParams({ filter: 'existing:filter' });
|
|
192
206
|
const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ({
|
|
193
207
|
filters: ctx.filters,
|
|
194
208
|
addFilter: ctx.addFilter
|
|
@@ -201,7 +215,7 @@ describe('ParameterContext', () => {
|
|
|
201
215
|
});
|
|
202
216
|
});
|
|
203
217
|
it('should not add duplicate filters', async () => {
|
|
204
|
-
mockSearchParams
|
|
218
|
+
mockSearchParams = new URLSearchParams({ filter: 'status:open' });
|
|
205
219
|
const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ({
|
|
206
220
|
filters: ctx.filters,
|
|
207
221
|
addFilter: ctx.addFilter
|
|
@@ -217,6 +231,7 @@ describe('ParameterContext', () => {
|
|
|
217
231
|
});
|
|
218
232
|
describe('removeFilter', () => {
|
|
219
233
|
it('should remove first matching filter', async () => {
|
|
234
|
+
mockSearchParams = new URLSearchParams();
|
|
220
235
|
mockSearchParams.append('filter', 'filter1');
|
|
221
236
|
mockSearchParams.append('filter', 'filter2');
|
|
222
237
|
mockSearchParams.append('filter', 'filter3');
|
|
@@ -232,7 +247,7 @@ describe('ParameterContext', () => {
|
|
|
232
247
|
});
|
|
233
248
|
});
|
|
234
249
|
it('should do nothing when removing nonexistent filter', async () => {
|
|
235
|
-
mockSearchParams
|
|
250
|
+
mockSearchParams = new URLSearchParams({ filter: 'existing' });
|
|
236
251
|
const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ({
|
|
237
252
|
filters: ctx.filters,
|
|
238
253
|
removeFilter: ctx.removeFilter
|
|
@@ -257,16 +272,17 @@ describe('ParameterContext', () => {
|
|
|
257
272
|
});
|
|
258
273
|
});
|
|
259
274
|
});
|
|
260
|
-
describe('
|
|
275
|
+
describe('clearFilters', () => {
|
|
261
276
|
it('should clear all filters', async () => {
|
|
277
|
+
mockSearchParams = new URLSearchParams();
|
|
262
278
|
mockSearchParams.append('filter', 'filter1');
|
|
263
279
|
mockSearchParams.append('filter', 'filter2');
|
|
264
280
|
const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ({
|
|
265
281
|
filters: ctx.filters,
|
|
266
|
-
|
|
282
|
+
clearFilters: ctx.clearFilters
|
|
267
283
|
})), { wrapper: Wrapper });
|
|
268
284
|
await act(async () => {
|
|
269
|
-
hook.result.current.
|
|
285
|
+
hook.result.current.clearFilters();
|
|
270
286
|
});
|
|
271
287
|
await waitFor(() => {
|
|
272
288
|
expect(hook.result.current.filters).toEqual([]);
|
|
@@ -275,10 +291,10 @@ describe('ParameterContext', () => {
|
|
|
275
291
|
it('should be no-op when already empty', async () => {
|
|
276
292
|
const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ({
|
|
277
293
|
filters: ctx.filters,
|
|
278
|
-
|
|
294
|
+
clearFilters: ctx.clearFilters
|
|
279
295
|
})), { wrapper: Wrapper });
|
|
280
296
|
await act(async () => {
|
|
281
|
-
hook.result.current.
|
|
297
|
+
hook.result.current.clearFilters();
|
|
282
298
|
});
|
|
283
299
|
await waitFor(() => {
|
|
284
300
|
expect(hook.result.current.filters).toEqual([]);
|
|
@@ -287,6 +303,7 @@ describe('ParameterContext', () => {
|
|
|
287
303
|
});
|
|
288
304
|
describe('setFilter', () => {
|
|
289
305
|
it('should update filter at specified index', async () => {
|
|
306
|
+
mockSearchParams = new URLSearchParams();
|
|
290
307
|
mockSearchParams.append('filter', 'filter1');
|
|
291
308
|
mockSearchParams.append('filter', 'filter2');
|
|
292
309
|
mockSearchParams.append('filter', 'filter3');
|
|
@@ -302,6 +319,7 @@ describe('ParameterContext', () => {
|
|
|
302
319
|
});
|
|
303
320
|
});
|
|
304
321
|
it('should update filter at index 0', async () => {
|
|
322
|
+
mockSearchParams = new URLSearchParams();
|
|
305
323
|
mockSearchParams.append('filter', 'old:filter');
|
|
306
324
|
mockSearchParams.append('filter', 'filter2');
|
|
307
325
|
const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ({
|
|
@@ -316,6 +334,7 @@ describe('ParameterContext', () => {
|
|
|
316
334
|
});
|
|
317
335
|
});
|
|
318
336
|
it('should update filter at last index', async () => {
|
|
337
|
+
mockSearchParams = new URLSearchParams();
|
|
319
338
|
mockSearchParams.append('filter', 'filter1');
|
|
320
339
|
mockSearchParams.append('filter', 'old:last');
|
|
321
340
|
const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ({
|
|
@@ -330,7 +349,7 @@ describe('ParameterContext', () => {
|
|
|
330
349
|
});
|
|
331
350
|
});
|
|
332
351
|
it('should do nothing when index is out of bounds', async () => {
|
|
333
|
-
mockSearchParams
|
|
352
|
+
mockSearchParams = new URLSearchParams({ filter: 'existing' });
|
|
334
353
|
const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ({
|
|
335
354
|
filters: ctx.filters,
|
|
336
355
|
setFilter: ctx.setFilter
|
|
@@ -343,7 +362,7 @@ describe('ParameterContext', () => {
|
|
|
343
362
|
});
|
|
344
363
|
});
|
|
345
364
|
it('should do nothing when index is negative', async () => {
|
|
346
|
-
mockSearchParams
|
|
365
|
+
mockSearchParams = new URLSearchParams({ filter: 'existing' });
|
|
347
366
|
const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ({
|
|
348
367
|
filters: ctx.filters,
|
|
349
368
|
setFilter: ctx.setFilter
|
|
@@ -368,6 +387,7 @@ describe('ParameterContext', () => {
|
|
|
368
387
|
});
|
|
369
388
|
});
|
|
370
389
|
it('should sync updated filter to URL', async () => {
|
|
390
|
+
mockSearchParams = new URLSearchParams();
|
|
371
391
|
mockSearchParams.append('filter', 'filter1');
|
|
372
392
|
mockSearchParams.append('filter', 'filter2');
|
|
373
393
|
const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ({
|
|
@@ -417,13 +437,14 @@ describe('ParameterContext', () => {
|
|
|
417
437
|
});
|
|
418
438
|
});
|
|
419
439
|
it('should remove all filter params when filters is empty', async () => {
|
|
440
|
+
mockSearchParams = new URLSearchParams();
|
|
420
441
|
mockSearchParams.append('filter', 'filter1');
|
|
421
442
|
mockSearchParams.append('filter', 'filter2');
|
|
422
443
|
const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ({
|
|
423
|
-
|
|
444
|
+
clearFilters: ctx.clearFilters
|
|
424
445
|
})), { wrapper: Wrapper });
|
|
425
446
|
await act(async () => {
|
|
426
|
-
hook.result.current.
|
|
447
|
+
hook.result.current.clearFilters();
|
|
427
448
|
});
|
|
428
449
|
await waitFor(() => {
|
|
429
450
|
expect(mockSetParams).toHaveBeenCalled();
|
|
@@ -546,11 +567,11 @@ describe('ParameterContext', () => {
|
|
|
546
567
|
});
|
|
547
568
|
});
|
|
548
569
|
it('should read changes from URL params', async () => {
|
|
549
|
-
mockSearchParams
|
|
570
|
+
mockSearchParams = new URLSearchParams({ query: 'initial query' });
|
|
550
571
|
const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ctx.query), { wrapper: Wrapper });
|
|
551
572
|
expect(hook.result.current).toBe('initial query');
|
|
552
573
|
// Simulate URL change
|
|
553
|
-
mockSearchParams
|
|
574
|
+
mockSearchParams = new URLSearchParams({ query: 'updated query' });
|
|
554
575
|
mockLocation.search = '?query=updated%20query';
|
|
555
576
|
hook.rerender();
|
|
556
577
|
await waitFor(() => {
|
|
@@ -568,22 +589,24 @@ describe('ParameterContext', () => {
|
|
|
568
589
|
it('should handle selected parameter in bundle when it matches bundle id', async () => {
|
|
569
590
|
mockLocation.pathname = '/bundles/bundle_123';
|
|
570
591
|
mockParams.id = 'bundle_123';
|
|
571
|
-
mockSearchParams
|
|
592
|
+
mockSearchParams = new URLSearchParams({ selected: 'bundle_123' });
|
|
572
593
|
const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ctx.selected), { wrapper: Wrapper });
|
|
573
594
|
expect(hook.result.current).toBe('bundle_123');
|
|
574
595
|
});
|
|
575
596
|
it('should handle selected parameter in bundle when it differs from bundle id', async () => {
|
|
576
597
|
mockLocation.pathname = '/bundles/bundle_123';
|
|
577
598
|
mockParams.id = 'bundle_123';
|
|
578
|
-
mockSearchParams
|
|
599
|
+
mockSearchParams = new URLSearchParams({ selected: 'different_hit_id' });
|
|
579
600
|
const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ctx.selected), { wrapper: Wrapper });
|
|
580
601
|
expect(hook.result.current).toBe('different_hit_id');
|
|
581
602
|
});
|
|
582
603
|
});
|
|
583
604
|
describe('useParameterContextSelector', () => {
|
|
584
605
|
it('should allow selecting specific values from context', async () => {
|
|
585
|
-
mockSearchParams
|
|
586
|
-
|
|
606
|
+
mockSearchParams = new URLSearchParams({
|
|
607
|
+
query: 'test query',
|
|
608
|
+
sort: 'test.field asc'
|
|
609
|
+
});
|
|
587
610
|
const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ({
|
|
588
611
|
query: ctx.query,
|
|
589
612
|
sort: ctx.sort
|
|
@@ -594,17 +617,17 @@ describe('ParameterContext', () => {
|
|
|
594
617
|
});
|
|
595
618
|
describe('edge cases', () => {
|
|
596
619
|
it('should handle offset of 0 in URL params', async () => {
|
|
597
|
-
mockSearchParams
|
|
620
|
+
mockSearchParams = new URLSearchParams({ offset: '0' });
|
|
598
621
|
const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ctx.offset), { wrapper: Wrapper });
|
|
599
622
|
expect(hook.result.current).toBe(0);
|
|
600
623
|
});
|
|
601
624
|
it('should handle trackTotalHits with various values', async () => {
|
|
602
|
-
mockSearchParams
|
|
625
|
+
mockSearchParams = new URLSearchParams({ track_total_hits: 'false' });
|
|
603
626
|
let hook = renderHook(() => useContextSelector(ParameterContext, ctx => ctx.trackTotalHits), {
|
|
604
627
|
wrapper: Wrapper
|
|
605
628
|
});
|
|
606
629
|
expect(hook.result.current).toBe(false);
|
|
607
|
-
mockSearchParams
|
|
630
|
+
mockSearchParams = new URLSearchParams({ track_total_hits: 'true' });
|
|
608
631
|
hook = renderHook(() => useContextSelector(ParameterContext, ctx => ctx.trackTotalHits), { wrapper: Wrapper });
|
|
609
632
|
expect(hook.result.current).toBe(true);
|
|
610
633
|
});
|
|
@@ -622,9 +645,11 @@ describe('ParameterContext', () => {
|
|
|
622
645
|
expect(hook.result.current.endDate).toBeNull();
|
|
623
646
|
});
|
|
624
647
|
it('should fallback to default values when URL params are cleared', async () => {
|
|
625
|
-
mockSearchParams
|
|
626
|
-
|
|
627
|
-
|
|
648
|
+
mockSearchParams = new URLSearchParams({
|
|
649
|
+
query: 'custom query',
|
|
650
|
+
sort: 'custom.sort asc',
|
|
651
|
+
span: 'date.range.1.week'
|
|
652
|
+
});
|
|
628
653
|
mockLocation.search = mockSearchParams.toString();
|
|
629
654
|
const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ({
|
|
630
655
|
query: ctx.query,
|
|
@@ -633,9 +658,7 @@ describe('ParameterContext', () => {
|
|
|
633
658
|
})), { wrapper: Wrapper });
|
|
634
659
|
expect(hook.result.current.query).toBe('custom query');
|
|
635
660
|
// Simulate clearing URL params
|
|
636
|
-
|
|
637
|
-
mockSearchParams.delete(key);
|
|
638
|
-
}
|
|
661
|
+
mockSearchParams = new URLSearchParams();
|
|
639
662
|
mockLocation.search = '';
|
|
640
663
|
hook.rerender();
|
|
641
664
|
await waitFor(() => {
|
|
@@ -663,7 +686,6 @@ describe('ParameterContext', () => {
|
|
|
663
686
|
hook.result.current.setSpan('date.range.1.week');
|
|
664
687
|
hook.result.current.addFilter('status:resolved');
|
|
665
688
|
});
|
|
666
|
-
hook.rerender();
|
|
667
689
|
await waitFor(() => {
|
|
668
690
|
expect(hook.result.current.query).toBe('multi query');
|
|
669
691
|
expect(hook.result.current.sort).toBe('multi.sort desc');
|
|
@@ -684,308 +706,25 @@ describe('ParameterContext', () => {
|
|
|
684
706
|
});
|
|
685
707
|
});
|
|
686
708
|
});
|
|
687
|
-
describe('indexes (multi-index support)', () => {
|
|
688
|
-
it('should initialize with default ["hit"] when no index params are present', async () => {
|
|
689
|
-
const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ctx.indexes), { wrapper: Wrapper });
|
|
690
|
-
expect(hook.result.current).toEqual(['hit']);
|
|
691
|
-
});
|
|
692
|
-
it('should initialize with single index from URL', async () => {
|
|
693
|
-
mockSearchParams.set('index', 'observable');
|
|
694
|
-
const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ctx.indexes), { wrapper: Wrapper });
|
|
695
|
-
expect(hook.result.current).toEqual(['observable']);
|
|
696
|
-
});
|
|
697
|
-
it('should initialize with multiple indexes from URL', async () => {
|
|
698
|
-
mockSearchParams.append('index', 'hit');
|
|
699
|
-
mockSearchParams.append('index', 'observable');
|
|
700
|
-
const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ctx.indexes), { wrapper: Wrapper });
|
|
701
|
-
expect(hook.result.current).toEqual(['hit', 'observable']);
|
|
702
|
-
});
|
|
703
|
-
it('should deduplicate repeated index values from URL', async () => {
|
|
704
|
-
mockSearchParams.append('index', 'hit');
|
|
705
|
-
mockSearchParams.append('index', 'hit');
|
|
706
|
-
const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ctx.indexes), { wrapper: Wrapper });
|
|
707
|
-
expect(hook.result.current).toEqual(['hit']);
|
|
708
|
-
});
|
|
709
|
-
describe('addIndex', () => {
|
|
710
|
-
it('should add an index to the default array', async () => {
|
|
711
|
-
const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ({
|
|
712
|
-
indexes: ctx.indexes,
|
|
713
|
-
addIndex: ctx.addIndex
|
|
714
|
-
})), { wrapper: Wrapper });
|
|
715
|
-
await act(async () => {
|
|
716
|
-
hook.result.current.addIndex('observable');
|
|
717
|
-
});
|
|
718
|
-
await waitFor(() => {
|
|
719
|
-
expect(hook.result.current.indexes).toEqual(['hit', 'observable']);
|
|
720
|
-
});
|
|
721
|
-
});
|
|
722
|
-
it('should not add a duplicate index', async () => {
|
|
723
|
-
const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ({
|
|
724
|
-
indexes: ctx.indexes,
|
|
725
|
-
addIndex: ctx.addIndex
|
|
726
|
-
})), { wrapper: Wrapper });
|
|
727
|
-
await act(async () => {
|
|
728
|
-
hook.result.current.addIndex('hit');
|
|
729
|
-
});
|
|
730
|
-
await waitFor(() => {
|
|
731
|
-
expect(hook.result.current.indexes).toEqual(['hit']);
|
|
732
|
-
});
|
|
733
|
-
});
|
|
734
|
-
});
|
|
735
|
-
describe('removeIndex', () => {
|
|
736
|
-
it('should remove an index from the list', async () => {
|
|
737
|
-
mockSearchParams.append('index', 'hit');
|
|
738
|
-
mockSearchParams.append('index', 'observable');
|
|
739
|
-
const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ({
|
|
740
|
-
indexes: ctx.indexes,
|
|
741
|
-
removeIndex: ctx.removeIndex
|
|
742
|
-
})), { wrapper: Wrapper });
|
|
743
|
-
await act(async () => {
|
|
744
|
-
hook.result.current.removeIndex('hit');
|
|
745
|
-
});
|
|
746
|
-
await waitFor(() => {
|
|
747
|
-
expect(hook.result.current.indexes).toEqual(['observable']);
|
|
748
|
-
});
|
|
749
|
-
});
|
|
750
|
-
it('should do nothing when removing a nonexistent index', async () => {
|
|
751
|
-
const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ({
|
|
752
|
-
indexes: ctx.indexes,
|
|
753
|
-
removeIndex: ctx.removeIndex
|
|
754
|
-
})), { wrapper: Wrapper });
|
|
755
|
-
await act(async () => {
|
|
756
|
-
hook.result.current.removeIndex('observable');
|
|
757
|
-
});
|
|
758
|
-
await waitFor(() => {
|
|
759
|
-
expect(hook.result.current.indexes).toEqual(['hit']);
|
|
760
|
-
});
|
|
761
|
-
});
|
|
762
|
-
it('should handle removing from empty array', async () => {
|
|
763
|
-
mockSearchParams.append('index', 'hit');
|
|
764
|
-
const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ({
|
|
765
|
-
indexes: ctx.indexes,
|
|
766
|
-
removeIndex: ctx.removeIndex
|
|
767
|
-
})), { wrapper: Wrapper });
|
|
768
|
-
await act(async () => {
|
|
769
|
-
hook.result.current.removeIndex('hit');
|
|
770
|
-
});
|
|
771
|
-
await waitFor(() => {
|
|
772
|
-
expect(hook.result.current.indexes).toEqual([]);
|
|
773
|
-
});
|
|
774
|
-
});
|
|
775
|
-
});
|
|
776
|
-
describe('setIndex', () => {
|
|
777
|
-
it('should update the index at the specified position', async () => {
|
|
778
|
-
mockSearchParams.append('index', 'hit');
|
|
779
|
-
mockSearchParams.append('index', 'observable');
|
|
780
|
-
const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ({
|
|
781
|
-
indexes: ctx.indexes,
|
|
782
|
-
setIndex: ctx.setIndex
|
|
783
|
-
})), { wrapper: Wrapper });
|
|
784
|
-
await act(async () => {
|
|
785
|
-
hook.result.current.setIndex(0, 'observable');
|
|
786
|
-
});
|
|
787
|
-
await waitFor(() => {
|
|
788
|
-
expect(hook.result.current.indexes).toEqual(['observable', 'observable']);
|
|
789
|
-
});
|
|
790
|
-
});
|
|
791
|
-
it('should do nothing when index is out of bounds', async () => {
|
|
792
|
-
const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ({
|
|
793
|
-
indexes: ctx.indexes,
|
|
794
|
-
setIndex: ctx.setIndex
|
|
795
|
-
})), { wrapper: Wrapper });
|
|
796
|
-
await act(async () => {
|
|
797
|
-
hook.result.current.setIndex(5, 'observable');
|
|
798
|
-
});
|
|
799
|
-
await waitFor(() => {
|
|
800
|
-
expect(hook.result.current.indexes).toEqual(['hit']);
|
|
801
|
-
});
|
|
802
|
-
});
|
|
803
|
-
it('should do nothing when position is negative', async () => {
|
|
804
|
-
const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ({
|
|
805
|
-
indexes: ctx.indexes,
|
|
806
|
-
setIndex: ctx.setIndex
|
|
807
|
-
})), { wrapper: Wrapper });
|
|
808
|
-
await act(async () => {
|
|
809
|
-
hook.result.current.setIndex(-1, 'observable');
|
|
810
|
-
});
|
|
811
|
-
await waitFor(() => {
|
|
812
|
-
expect(hook.result.current.indexes).toEqual(['hit']);
|
|
813
|
-
});
|
|
814
|
-
});
|
|
815
|
-
});
|
|
816
|
-
describe('setIndexes', () => {
|
|
817
|
-
it('should replace all indexes with the provided list', async () => {
|
|
818
|
-
const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ({
|
|
819
|
-
indexes: ctx.indexes,
|
|
820
|
-
setIndexes: ctx.setIndexes
|
|
821
|
-
})), { wrapper: Wrapper });
|
|
822
|
-
await act(async () => {
|
|
823
|
-
hook.result.current.setIndexes(['observable']);
|
|
824
|
-
});
|
|
825
|
-
await waitFor(() => {
|
|
826
|
-
expect(hook.result.current.indexes).toEqual(['observable']);
|
|
827
|
-
});
|
|
828
|
-
});
|
|
829
|
-
it('should deduplicate values when setting all indexes', async () => {
|
|
830
|
-
const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ({
|
|
831
|
-
indexes: ctx.indexes,
|
|
832
|
-
setIndexes: ctx.setIndexes
|
|
833
|
-
})), { wrapper: Wrapper });
|
|
834
|
-
await act(async () => {
|
|
835
|
-
hook.result.current.setIndexes(['hit', 'hit', 'observable']);
|
|
836
|
-
});
|
|
837
|
-
await waitFor(() => {
|
|
838
|
-
expect(hook.result.current.indexes).toEqual(['hit', 'observable']);
|
|
839
|
-
});
|
|
840
|
-
});
|
|
841
|
-
it('should set to empty array', async () => {
|
|
842
|
-
const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ({
|
|
843
|
-
indexes: ctx.indexes,
|
|
844
|
-
setIndexes: ctx.setIndexes
|
|
845
|
-
})), { wrapper: Wrapper });
|
|
846
|
-
await act(async () => {
|
|
847
|
-
hook.result.current.setIndexes([]);
|
|
848
|
-
});
|
|
849
|
-
await waitFor(() => {
|
|
850
|
-
expect(hook.result.current.indexes).toEqual([]);
|
|
851
|
-
});
|
|
852
|
-
});
|
|
853
|
-
});
|
|
854
|
-
describe('resetIndexes', () => {
|
|
855
|
-
it('should reset indexes to default ["hit"]', async () => {
|
|
856
|
-
mockSearchParams.append('index', 'hit');
|
|
857
|
-
mockSearchParams.append('index', 'observable');
|
|
858
|
-
const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ({
|
|
859
|
-
indexes: ctx.indexes,
|
|
860
|
-
resetIndexes: ctx.resetIndexes
|
|
861
|
-
})), { wrapper: Wrapper });
|
|
862
|
-
await act(async () => {
|
|
863
|
-
hook.result.current.resetIndexes();
|
|
864
|
-
});
|
|
865
|
-
await waitFor(() => {
|
|
866
|
-
expect(hook.result.current.indexes).toEqual(['hit']);
|
|
867
|
-
});
|
|
868
|
-
});
|
|
869
|
-
it('should reset to default even when called on empty array', async () => {
|
|
870
|
-
mockSearchParams.append('index', 'hit');
|
|
871
|
-
const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ({
|
|
872
|
-
indexes: ctx.indexes,
|
|
873
|
-
removeIndex: ctx.removeIndex,
|
|
874
|
-
resetIndexes: ctx.resetIndexes
|
|
875
|
-
})), { wrapper: Wrapper });
|
|
876
|
-
// First empty it
|
|
877
|
-
await act(async () => {
|
|
878
|
-
hook.result.current.removeIndex('hit');
|
|
879
|
-
});
|
|
880
|
-
await waitFor(() => {
|
|
881
|
-
expect(hook.result.current.indexes).toEqual([]);
|
|
882
|
-
});
|
|
883
|
-
// Resetting always returns to default ['hit']
|
|
884
|
-
await act(async () => {
|
|
885
|
-
hook.result.current.resetIndexes();
|
|
886
|
-
});
|
|
887
|
-
await waitFor(() => {
|
|
888
|
-
expect(hook.result.current.indexes).toEqual(['hit']);
|
|
889
|
-
});
|
|
890
|
-
});
|
|
891
|
-
});
|
|
892
|
-
describe('URL synchronization', () => {
|
|
893
|
-
it('should not write the default ["hit"] index to the URL', async () => {
|
|
894
|
-
renderHook(() => useContextSelector(ParameterContext, ctx => ctx.indexes), { wrapper: Wrapper });
|
|
895
|
-
// Allow any effects to flush
|
|
896
|
-
await waitFor(() => {
|
|
897
|
-
// If setParams was called, the URL must not contain ?index=hit
|
|
898
|
-
if (mockSetParams.mock.calls.length > 0) {
|
|
899
|
-
const call = mockSetParams.mock.calls[mockSetParams.mock.calls.length - 1];
|
|
900
|
-
const urlParams = typeof call[0] === 'function' ? call[0](mockSearchParams) : call[0];
|
|
901
|
-
expect(urlParams.getAll('index')).toEqual([]);
|
|
902
|
-
}
|
|
903
|
-
else {
|
|
904
|
-
// setParams not called at all is also fine
|
|
905
|
-
expect(true).toBe(true);
|
|
906
|
-
}
|
|
907
|
-
});
|
|
908
|
-
});
|
|
909
|
-
it('should write a non-default index to the URL', async () => {
|
|
910
|
-
const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ({
|
|
911
|
-
addIndex: ctx.addIndex,
|
|
912
|
-
resetIndexes: ctx.resetIndexes
|
|
913
|
-
})), { wrapper: Wrapper });
|
|
914
|
-
await act(async () => {
|
|
915
|
-
hook.result.current.resetIndexes();
|
|
916
|
-
hook.result.current.addIndex('observable');
|
|
917
|
-
});
|
|
918
|
-
await waitFor(() => {
|
|
919
|
-
expect(mockSetParams).toHaveBeenCalled();
|
|
920
|
-
const call = mockSetParams.mock.calls[mockSetParams.mock.calls.length - 1];
|
|
921
|
-
const urlParams = typeof call[0] === 'function' ? call[0](mockSearchParams) : call[0];
|
|
922
|
-
expect(urlParams.getAll('index')).toEqual(['hit', 'observable']);
|
|
923
|
-
});
|
|
924
|
-
});
|
|
925
|
-
it('should sync multiple indexes to URL as multiple index params', async () => {
|
|
926
|
-
const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ({
|
|
927
|
-
addIndex: ctx.addIndex
|
|
928
|
-
})), { wrapper: Wrapper });
|
|
929
|
-
await act(async () => {
|
|
930
|
-
hook.result.current.addIndex('observable');
|
|
931
|
-
});
|
|
932
|
-
await waitFor(() => {
|
|
933
|
-
expect(mockSetParams).toHaveBeenCalled();
|
|
934
|
-
const call = mockSetParams.mock.calls[mockSetParams.mock.calls.length - 1];
|
|
935
|
-
const urlParams = typeof call[0] === 'function' ? call[0](mockSearchParams) : call[0];
|
|
936
|
-
expect(urlParams.getAll('index')).toEqual(['hit', 'observable']);
|
|
937
|
-
});
|
|
938
|
-
});
|
|
939
|
-
it('should remove all index params from URL when state resets to default', async () => {
|
|
940
|
-
mockSearchParams.append('index', 'hit');
|
|
941
|
-
mockSearchParams.append('index', 'observable');
|
|
942
|
-
const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ({
|
|
943
|
-
resetIndexes: ctx.resetIndexes
|
|
944
|
-
})), { wrapper: Wrapper });
|
|
945
|
-
await act(async () => {
|
|
946
|
-
hook.result.current.resetIndexes();
|
|
947
|
-
});
|
|
948
|
-
await waitFor(() => {
|
|
949
|
-
expect(mockSetParams).toHaveBeenCalled();
|
|
950
|
-
const call = mockSetParams.mock.calls[mockSetParams.mock.calls.length - 1];
|
|
951
|
-
const urlParams = typeof call[0] === 'function' ? call[0](mockSearchParams) : call[0];
|
|
952
|
-
expect(urlParams.getAll('index')).toEqual([]);
|
|
953
|
-
});
|
|
954
|
-
});
|
|
955
|
-
it('should remove index param from URL when state returns to default', async () => {
|
|
956
|
-
mockSearchParams.set('index', 'observable');
|
|
957
|
-
const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ({
|
|
958
|
-
setIndexes: ctx.setIndexes
|
|
959
|
-
})), { wrapper: Wrapper });
|
|
960
|
-
await act(async () => {
|
|
961
|
-
hook.result.current.setIndexes(['hit']);
|
|
962
|
-
});
|
|
963
|
-
await waitFor(() => {
|
|
964
|
-
expect(mockSetParams).toHaveBeenCalled();
|
|
965
|
-
const call = mockSetParams.mock.calls[mockSetParams.mock.calls.length - 1];
|
|
966
|
-
const urlParams = typeof call[0] === 'function' ? call[0](mockSearchParams) : call[0];
|
|
967
|
-
expect(urlParams.getAll('index')).toEqual([]);
|
|
968
|
-
});
|
|
969
|
-
});
|
|
970
|
-
});
|
|
971
|
-
});
|
|
972
709
|
describe('views (multi-view support)', () => {
|
|
973
710
|
it('should initialize with empty array when no view params present', async () => {
|
|
974
711
|
const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ctx.views), { wrapper: Wrapper });
|
|
975
712
|
expect(hook.result.current).toEqual([]);
|
|
976
713
|
});
|
|
977
714
|
it('should initialize with single view from URL', async () => {
|
|
978
|
-
mockSearchParams
|
|
715
|
+
mockSearchParams = new URLSearchParams({ view: 'view_1' });
|
|
979
716
|
const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ctx.views), { wrapper: Wrapper });
|
|
980
717
|
expect(hook.result.current).toEqual(['view_1']);
|
|
981
718
|
});
|
|
982
719
|
it('should initialize with multiple views from URL', async () => {
|
|
720
|
+
mockSearchParams = new URLSearchParams();
|
|
983
721
|
mockSearchParams.append('view', 'view_1');
|
|
984
722
|
mockSearchParams.append('view', 'view_2');
|
|
985
723
|
const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ctx.views), { wrapper: Wrapper });
|
|
986
724
|
expect(hook.result.current).toEqual(['view_1', 'view_2']);
|
|
987
725
|
});
|
|
988
726
|
it('should preserve view order from URL', async () => {
|
|
727
|
+
mockSearchParams = new URLSearchParams();
|
|
989
728
|
mockSearchParams.append('view', 'view_c');
|
|
990
729
|
mockSearchParams.append('view', 'view_a');
|
|
991
730
|
mockSearchParams.append('view', 'view_b');
|
|
@@ -993,6 +732,7 @@ describe('ParameterContext', () => {
|
|
|
993
732
|
expect(hook.result.current).toEqual(['view_c', 'view_a', 'view_b']);
|
|
994
733
|
});
|
|
995
734
|
it('should deduplicate multiple empty view params to single empty string', async () => {
|
|
735
|
+
mockSearchParams = new URLSearchParams();
|
|
996
736
|
mockSearchParams.append('view', '');
|
|
997
737
|
mockSearchParams.append('view', '');
|
|
998
738
|
mockSearchParams.append('view', '');
|
|
@@ -1013,7 +753,7 @@ describe('ParameterContext', () => {
|
|
|
1013
753
|
});
|
|
1014
754
|
});
|
|
1015
755
|
it('should append view to existing views', async () => {
|
|
1016
|
-
mockSearchParams
|
|
756
|
+
mockSearchParams = new URLSearchParams({ view: 'existing_view' });
|
|
1017
757
|
const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ({
|
|
1018
758
|
views: ctx.views,
|
|
1019
759
|
addView: ctx.addView
|
|
@@ -1026,7 +766,7 @@ describe('ParameterContext', () => {
|
|
|
1026
766
|
});
|
|
1027
767
|
});
|
|
1028
768
|
it('should not add duplicate views', async () => {
|
|
1029
|
-
mockSearchParams
|
|
769
|
+
mockSearchParams = new URLSearchParams({ view: 'view_1' });
|
|
1030
770
|
const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ({
|
|
1031
771
|
views: ctx.views,
|
|
1032
772
|
addView: ctx.addView
|
|
@@ -1042,6 +782,7 @@ describe('ParameterContext', () => {
|
|
|
1042
782
|
});
|
|
1043
783
|
describe('removeView', () => {
|
|
1044
784
|
it('should remove first matching view', async () => {
|
|
785
|
+
mockSearchParams = new URLSearchParams();
|
|
1045
786
|
mockSearchParams.append('view', 'view_1');
|
|
1046
787
|
mockSearchParams.append('view', 'view_2');
|
|
1047
788
|
mockSearchParams.append('view', 'view_3');
|
|
@@ -1057,6 +798,7 @@ describe('ParameterContext', () => {
|
|
|
1057
798
|
});
|
|
1058
799
|
});
|
|
1059
800
|
it('should remove only first occurrence of duplicate views', async () => {
|
|
801
|
+
mockSearchParams = new URLSearchParams();
|
|
1060
802
|
mockSearchParams.append('view', 'dup');
|
|
1061
803
|
mockSearchParams.append('view', 'dup');
|
|
1062
804
|
mockSearchParams.append('view', 'other');
|
|
@@ -1072,7 +814,7 @@ describe('ParameterContext', () => {
|
|
|
1072
814
|
});
|
|
1073
815
|
});
|
|
1074
816
|
it('should do nothing when removing nonexistent view', async () => {
|
|
1075
|
-
mockSearchParams
|
|
817
|
+
mockSearchParams = new URLSearchParams({ view: 'existing' });
|
|
1076
818
|
const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ({
|
|
1077
819
|
views: ctx.views,
|
|
1078
820
|
removeView: ctx.removeView
|
|
@@ -1097,16 +839,17 @@ describe('ParameterContext', () => {
|
|
|
1097
839
|
});
|
|
1098
840
|
});
|
|
1099
841
|
});
|
|
1100
|
-
describe('
|
|
842
|
+
describe('clearViews', () => {
|
|
1101
843
|
it('should clear all views', async () => {
|
|
844
|
+
mockSearchParams = new URLSearchParams();
|
|
1102
845
|
mockSearchParams.append('view', 'view_1');
|
|
1103
846
|
mockSearchParams.append('view', 'view_2');
|
|
1104
847
|
const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ({
|
|
1105
848
|
views: ctx.views,
|
|
1106
|
-
|
|
849
|
+
clearViews: ctx.clearViews
|
|
1107
850
|
})), { wrapper: Wrapper });
|
|
1108
851
|
await act(async () => {
|
|
1109
|
-
hook.result.current.
|
|
852
|
+
hook.result.current.clearViews();
|
|
1110
853
|
});
|
|
1111
854
|
await waitFor(() => {
|
|
1112
855
|
expect(hook.result.current.views).toEqual([]);
|
|
@@ -1115,10 +858,10 @@ describe('ParameterContext', () => {
|
|
|
1115
858
|
it('should be no-op when already empty', async () => {
|
|
1116
859
|
const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ({
|
|
1117
860
|
views: ctx.views,
|
|
1118
|
-
|
|
861
|
+
clearViews: ctx.clearViews
|
|
1119
862
|
})), { wrapper: Wrapper });
|
|
1120
863
|
await act(async () => {
|
|
1121
|
-
hook.result.current.
|
|
864
|
+
hook.result.current.clearViews();
|
|
1122
865
|
});
|
|
1123
866
|
await waitFor(() => {
|
|
1124
867
|
expect(hook.result.current.views).toEqual([]);
|
|
@@ -1127,6 +870,7 @@ describe('ParameterContext', () => {
|
|
|
1127
870
|
});
|
|
1128
871
|
describe('setView', () => {
|
|
1129
872
|
it('should update view at specified index', async () => {
|
|
873
|
+
mockSearchParams = new URLSearchParams();
|
|
1130
874
|
mockSearchParams.append('view', 'view_1');
|
|
1131
875
|
mockSearchParams.append('view', 'view_2');
|
|
1132
876
|
mockSearchParams.append('view', 'view_3');
|
|
@@ -1142,6 +886,7 @@ describe('ParameterContext', () => {
|
|
|
1142
886
|
});
|
|
1143
887
|
});
|
|
1144
888
|
it('should update view at index 0', async () => {
|
|
889
|
+
mockSearchParams = new URLSearchParams();
|
|
1145
890
|
mockSearchParams.append('view', 'old_view');
|
|
1146
891
|
mockSearchParams.append('view', 'view_2');
|
|
1147
892
|
const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ({
|
|
@@ -1156,6 +901,7 @@ describe('ParameterContext', () => {
|
|
|
1156
901
|
});
|
|
1157
902
|
});
|
|
1158
903
|
it('should update view at last index', async () => {
|
|
904
|
+
mockSearchParams = new URLSearchParams();
|
|
1159
905
|
mockSearchParams.append('view', 'view_1');
|
|
1160
906
|
mockSearchParams.append('view', 'old_last');
|
|
1161
907
|
const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ({
|
|
@@ -1170,7 +916,7 @@ describe('ParameterContext', () => {
|
|
|
1170
916
|
});
|
|
1171
917
|
});
|
|
1172
918
|
it('should do nothing when index is out of bounds', async () => {
|
|
1173
|
-
mockSearchParams
|
|
919
|
+
mockSearchParams = new URLSearchParams({ view: 'existing' });
|
|
1174
920
|
const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ({
|
|
1175
921
|
views: ctx.views,
|
|
1176
922
|
setView: ctx.setView
|
|
@@ -1183,7 +929,7 @@ describe('ParameterContext', () => {
|
|
|
1183
929
|
});
|
|
1184
930
|
});
|
|
1185
931
|
it('should do nothing when index is negative', async () => {
|
|
1186
|
-
mockSearchParams
|
|
932
|
+
mockSearchParams = new URLSearchParams({ view: 'existing' });
|
|
1187
933
|
const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ({
|
|
1188
934
|
views: ctx.views,
|
|
1189
935
|
setView: ctx.setView
|
|
@@ -1208,6 +954,7 @@ describe('ParameterContext', () => {
|
|
|
1208
954
|
});
|
|
1209
955
|
});
|
|
1210
956
|
it('should sync updated view to URL', async () => {
|
|
957
|
+
mockSearchParams = new URLSearchParams();
|
|
1211
958
|
mockSearchParams.append('view', 'view_1');
|
|
1212
959
|
mockSearchParams.append('view', 'view_2');
|
|
1213
960
|
const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ({
|
|
@@ -1257,13 +1004,14 @@ describe('ParameterContext', () => {
|
|
|
1257
1004
|
});
|
|
1258
1005
|
});
|
|
1259
1006
|
it('should remove all view params when views is empty', async () => {
|
|
1007
|
+
mockSearchParams = new URLSearchParams();
|
|
1260
1008
|
mockSearchParams.append('view', 'view_1');
|
|
1261
1009
|
mockSearchParams.append('view', 'view_2');
|
|
1262
1010
|
const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ({
|
|
1263
|
-
|
|
1011
|
+
clearViews: ctx.clearViews
|
|
1264
1012
|
})), { wrapper: Wrapper });
|
|
1265
1013
|
await act(async () => {
|
|
1266
|
-
hook.result.current.
|
|
1014
|
+
hook.result.current.clearViews();
|
|
1267
1015
|
});
|
|
1268
1016
|
await waitFor(() => {
|
|
1269
1017
|
expect(mockSetParams).toHaveBeenCalled();
|