Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 74 additions & 38 deletions src/store.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
import { FLUSH, PAUSE, PERSIST, PURGE, REGISTER, REHYDRATE, persistReducer } from "redux-persist";
import {
FLUSH,
PAUSE,
PERSIST,
PURGE,
REGISTER,
REHYDRATE,
persistReducer,
} from "redux-persist";
import storage from "redux-persist/lib/storage";
import { UnknownAction, combineReducers } from "redux";
import tableFilters from "./slices/tableFilterSlice";
Expand Down Expand Up @@ -35,61 +43,87 @@ import autoMergeLevel2 from "redux-persist/lib/stateReconciler/autoMergeLevel2";
*/

// Configuration for persisting states in store
const tableFilterProfilesPersistConfig = { key: "tableFilterProfiles", storage, whitelist: ["profiles"] };
const tableFilterProfilesPersistConfig = {
key: "tableFilterProfiles",
storage,
whitelist: ["profiles"],
};
const eventsPersistConfig = { key: "events", storage, whitelist: ["columns"] };
const seriesPersistConfig = { key: "series", storage, whitelist: ["columns"] };
const tablePersistConfig = { key: "table", storage, whitelist: ["pagination", "sortBy", "reverse"] };
const recordingsPersistConfig = { key: "recordings", storage, whitelist: ["columns"] };
const tablePersistConfig = {
key: "table",
storage,
whitelist: ["pagination", "sortBy", "reverse"],
};
const recordingsPersistConfig = {
key: "recordings",
storage,
whitelist: ["columns"],
};
const jobsPersistConfig = { key: "jobs", storage, whitelist: ["columns"] };
const serversPersistConfig = { key: "servers", storage, whitelist: ["columns"] };
const servicesPersistConfig = { key: "services", storage, whitelist: ["columns"] };
const serversPersistConfig = {
key: "servers",
storage,
whitelist: ["columns"],
};
const servicesPersistConfig = {
key: "services",
storage,
whitelist: ["columns"],
};
const usersPersistConfig = { key: "users", storage, whitelist: ["columns"] };
const groupsPersistConfig = { key: "groups", storage, whitelist: ["columns"] };
const aclsPersistConfig = { key: "acls", storage, whitelist: ["columns"] };
const themesPersistConfig = { key: "themes", storage, whitelist: ["columns"] };

// form reducer and all other reducers used in this app
const reducers = combineReducers({
tableFilters,
tableFilterProfiles: persistReducer(tableFilterProfilesPersistConfig, tableFilterProfiles),
events: persistReducer(eventsPersistConfig, events),
series: persistReducer(seriesPersistConfig, series),
table: persistReducer(tablePersistConfig, table),
recordings: persistReducer(recordingsPersistConfig, recordings),
jobs: persistReducer(jobsPersistConfig, jobs),
servers: persistReducer(serversPersistConfig, servers),
services: persistReducer(servicesPersistConfig, services),
users: persistReducer(usersPersistConfig, users),
groups: persistReducer(groupsPersistConfig, groups),
acls: persistReducer(aclsPersistConfig, acls),
themes: persistReducer(themesPersistConfig, themes),
health,
notifications,
workflows,
eventDetails,
themeDetails,
seriesDetails,
recordingDetails,
userDetails,
groupDetails,
aclDetails,
userInfo,
statistics,
tableFilters,
tableFilterProfiles: persistReducer(
tableFilterProfilesPersistConfig,
tableFilterProfiles,
),
events: persistReducer(eventsPersistConfig, events),
series: persistReducer(seriesPersistConfig, series),
table: persistReducer(tablePersistConfig, table),
recordings: persistReducer(recordingsPersistConfig, recordings),
jobs: persistReducer(jobsPersistConfig, jobs),
servers: persistReducer(serversPersistConfig, servers),
services: persistReducer(servicesPersistConfig, services),
users: persistReducer(usersPersistConfig, users),
groups: persistReducer(groupsPersistConfig, groups),
acls: persistReducer(aclsPersistConfig, acls),
themes: persistReducer(themesPersistConfig, themes),
health,
notifications,
workflows,
eventDetails,
themeDetails,
seriesDetails,
recordingDetails,
userDetails,
groupDetails,
aclDetails,
userInfo,
statistics,
});

// Configuration for persisting store
const persistConfig = {
key: "root",
storage,
stateReconciler: autoMergeLevel2,
whitelist: ["tableFilters"],
key: "root",
storage,
stateReconciler: autoMergeLevel2,
whitelist: ["tableFilters"],
};

const persistedReducer = persistReducer<ReturnType<typeof reducers>>(persistConfig, reducers);
const persistedReducer = persistReducer<ReturnType<typeof reducers>>(
persistConfig,
reducers,
);

const store = configureStore({
reducer: persistedReducer,
middleware: getDefaultMiddleware =>
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
serializableCheck: {
ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER],
Expand All @@ -110,6 +144,8 @@ export type AppThunk<ReturnType = void> = ThunkAction<
RootState,
unknown,
UnknownAction
>
>;

export const rootReducer = reducers;
Copy link
Member

@Arnei Arnei Aug 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not directly export reducers? This feels needlessly convoluted.


export default store;
7 changes: 4 additions & 3 deletions tests/components/navBar.test.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { describe, it, expect, vi } from "vitest";
import userEvent from "@testing-library/user-event";
import NavBar from "../../src/components/NavBar";
import renderWithStore from "./navBarSetupStore";
import { renderWithProviders } from "../utils/setUpStore";
import { rootReducer } from "./navBarSetupStore";

vi.mock("../../src/components/shared/MainNav", () => ({
default: () => <div />,
Expand Down Expand Up @@ -34,7 +35,7 @@ describe("NavBar", () => {
},
};

const { getByRole } = renderWithStore(
const { getByRole } = renderWithProviders(
<NavBar
displayNavigation={true}
setNavigation={() => {}}
Expand All @@ -48,7 +49,7 @@ describe("NavBar", () => {
isDisplay: true,
}}
/>,
preloadedState
{ preloadedState, reducers: rootReducer, useRouter: true },
);

const user = userEvent.setup();
Expand Down
58 changes: 23 additions & 35 deletions tests/components/navBarSetupStore.tsx
Original file line number Diff line number Diff line change
@@ -1,41 +1,29 @@
import React from "react";
import { Provider } from "react-redux";
import { configureStore, combineReducers } from "@reduxjs/toolkit";
import { render } from "@testing-library/react";
import { BrowserRouter } from "react-router";
import { combineReducers } from "redux";

const userInfoReducer = (
state = {
user: { name: "Test User" },
roles: ["admin"],
org: { id: "org1", properties: {} },
},
) => state;
// Stub reducers returning default state (no action handling needed for tests)
const userInfoReducer = () => ({
user: { name: "Test User" },
roles: ["admin"],
org: { id: "org1", properties: {} },
});

const eventsReducer = (
state = {
uploadSourceOptions: [],
uploadAssetOptions: [],
isFetchingAssetUploadOptions: false,
metadata: { title: "event", flavor: "someFlavor", fields: [] },
extendedMetadata: [],
total: 0,
},
) => state;
const eventsReducer = () => ({
uploadSourceOptions: [],
uploadAssetOptions: [],
isFetchingAssetUploadOptions: false,
metadata: { title: "event", flavor: "someFlavor", fields: [] },
extendedMetadata: [],
total: 0,
});

const rootReducer = combineReducers({
// Root reducer object with your test slices
export const rootReducer = {
userInfo: userInfoReducer,
events: eventsReducer,
});
};

export const combinedReducer = combineReducers(rootReducer);

export type TestRootState = ReturnType<typeof combinedReducer>;

export default function renderWithStore(
ui: React.ReactElement,
preloadedState = {},
) {
const store = configureStore({ reducer: rootReducer, preloadedState });
return render(
<Provider store={store}>
<BrowserRouter>{ui}</BrowserRouter>
</Provider>,
);
}
export default rootReducer;
71 changes: 50 additions & 21 deletions tests/components/shared/table.test.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,56 @@
import "@testing-library/jest-dom";
import Table from "../../../src/components/shared/Table";
import renderWithProviders, { store, rootReducer } from "./tableSetupStore";
import { combineReducers } from "@reduxjs/toolkit";
import {
rootReducer,
dummyResourceState,
dummyReverseState,
dummyMultiSelect,
} from "./tableSetupStore";
import { renderWithProviders } from "../../utils/setUpStore";
import type { TableState } from "../../../src/slices/tableSlice";

describe("Pagination in Table Component", () => {
const firstPageState: { table: TableState } = {
table: {
rows: [],
columns: [],
pagination: {
offset: 0,
limit: 10,
totalItems: 20,
directAccessibleNo: 5,
},
pages: [
{ number: 0, label: "1", active: true },
{ number: 1, label: "2", active: false },
],
resource: "events",
sortBy: dummyResourceState,
reverse: dummyReverseState,
multiSelect: dummyMultiSelect,
status: "loading",
error: null,
predicate: "",
maxLabel: "",
},
};

const secondPageState = {
table: {
...firstPageState.table,
pagination: { ...firstPageState.table.pagination, offset: 1 },
pages: [
{ number: 0, label: "1", active: false },
{ number: 1, label: "2", active: true },
],
},
};

it("Previous button is disabled on first page", () => {
const { container } = renderWithProviders(<Table templateMap={{}} />);
const { container } = renderWithProviders(<Table templateMap={{}} />, {
reducers: rootReducer,
preloadedState: firstPageState,
});

const prevButton = container.querySelector(".prev");
const nextButton = container.querySelector(".next");
Expand All @@ -15,25 +60,9 @@ describe("Pagination in Table Component", () => {
});

it("Next button is disabled on last page", () => {
store.replaceReducer(
combineReducers({
...rootReducer,
table: () => ({
...rootReducer.table(),
pagination: {
...rootReducer.table().pagination,
offset: 1,
},
pages: [
{ number: 0, label: "1", active: false },
{ number: 1, label: "2", active: true },
],
}),
})
);

const { container } = renderWithProviders(<Table templateMap={{}} />, {
storeInstance: store,
reducers: rootReducer,
preloadedState: secondPageState,
});
const prevButton = container.querySelector(".prev");
const nextButton = container.querySelector(".next");
Expand Down
14 changes: 9 additions & 5 deletions tests/components/shared/tableFilters.test.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import renderWithProviders from "./tableFiltersSetupStore";
import TableFilters from "../../../src/components/shared/TableFilters";
import { createAsyncThunk, Dispatch } from "@reduxjs/toolkit";
import { rootReducer, TestRootState } from "./tableFiltersSetupStore";
import { DeepPartial, renderWithProviders } from "../../utils/setUpStore";

const mockLoadResource = createAsyncThunk("resource/load", async () =>
Promise.resolve()
Promise.resolve(),
);
const mockLoadResourceIntoTable = () => (dispatch: Dispatch) => {}; // dummy thunk does nothing
const mockResource = "events";
Expand All @@ -27,15 +28,18 @@ describe("TableFilterText", () => {
tableFilterProfiles: {
profiles: [],
},
};
} as DeepPartial<TestRootState>;

const { store } = renderWithProviders(
<TableFilters
loadResource={mockLoadResource}
loadResourceIntoTable={mockLoadResourceIntoTable}
resource={mockResource}
/>,
{ preloadedState }
{
reducers: rootReducer,
preloadedState,
},
);

const input = screen.getByPlaceholderText(/Search/i);
Expand All @@ -48,7 +52,7 @@ describe("TableFilterText", () => {
expect(store.getState().tableFilters.textFilter).toEqual(
expect.arrayContaining([
expect.objectContaining({ resource: mockResource, text: "hello" }),
])
]),
);
});
});
Loading