diff --git a/src/components/experiment/configurationTab.tsx b/src/components/experiment/configurationTab.tsx
index 703ca6db..4a205260 100644
--- a/src/components/experiment/configurationTab.tsx
+++ b/src/components/experiment/configurationTab.tsx
@@ -1,6 +1,6 @@
import { Grid } from '@mui/material'
import { useExperiment } from '@/context/experiment'
-import { useGlobal } from '@/context/global'
+import { useSelector } from '@/context/global'
import {
ValueVariableType,
CategoricalVariableType,
@@ -9,17 +9,15 @@ import {
import Details from '../details'
import OptimizerModel from '../input-model/optimizer-model'
import OptimizerConfigurator from '../optimizer-configurator'
+import { selectAdvancedConfiguration } from '@/context/global/global-selectors'
export const ConfigurationTab = () => {
const {
state: { experiment },
dispatch,
} = useExperiment()
- const {
- state: {
- flags: { advancedConfiguration },
- },
- } = useGlobal()
+
+ const advancedConfiguration = useSelector(selectAdvancedConfiguration)
const valueVariables = experiment.valueVariables
const categoricalVariables = experiment.categoricalVariables
diff --git a/src/context/experiment/experiment-context.tsx b/src/context/experiment/experiment-context.tsx
index 145ad14d..d8949b6e 100644
--- a/src/context/experiment/experiment-context.tsx
+++ b/src/context/experiment/experiment-context.tsx
@@ -22,7 +22,7 @@ type ExperimentProviderProps = {
children: any
}
-function ExperimentProvider({
+export function ExperimentProvider({
experimentId,
children,
}: ExperimentProviderProps) {
@@ -63,7 +63,7 @@ function ExperimentProvider({
)
}
-function useExperiment() {
+export function useExperiment() {
const context = React.useContext(ExperimentContext)
if (context === undefined) {
throw new Error('useExperiment must be used within an ExperimentProvider')
@@ -124,9 +124,10 @@ const fetchExperimentResult = async (
return experimentResult
}
-async function runExperiment(dispatch: Dispatch, experiment: ExperimentType) {
+export async function runExperiment(
+ dispatch: Dispatch,
+ experiment: ExperimentType
+) {
const result = await fetchExperimentResult(experiment)
dispatch({ type: 'registerResult', payload: result })
}
-
-export { ExperimentProvider, useExperiment, runExperiment }
diff --git a/src/context/experiment/experiment-selectors.test.tsx b/src/context/experiment/experiment-selectors.test.ts
similarity index 100%
rename from src/context/experiment/experiment-selectors.test.tsx
rename to src/context/experiment/experiment-selectors.test.ts
diff --git a/src/context/global/global-context.test.tsx b/src/context/global/global-context.test.tsx
new file mode 100644
index 00000000..78e8a0ba
--- /dev/null
+++ b/src/context/global/global-context.test.tsx
@@ -0,0 +1,35 @@
+import { renderHook } from '@testing-library/react'
+import { FC } from 'react'
+import { State } from '@/context/global'
+import { GlobalStateProvider, useGlobal, useSelector } from '../global'
+
+const GlobalWrapper: FC<{ children: React.ReactNode }> = ({ children }) => (
+ {children}
+)
+
+describe('useGlobal', () => {
+ it('fails if called outside provider', async () => {
+ console.error = jest.fn()
+ expect(() => renderHook(() => useGlobal())).toThrow(
+ 'useGlobal must be used within a GlobalStateProvider'
+ )
+ expect(console.error).toHaveBeenCalled()
+ })
+
+ it('provides context when called inside provider', async () => {
+ const { result } = renderHook(() => useGlobal(), {
+ wrapper: GlobalWrapper,
+ })
+ expect(result.current.state.debug).toBeFalsy()
+ })
+})
+
+describe('useSelector', () => {
+ it('should bind selector to state', () => {
+ const testSelector = (state: State) => state.debug
+ const { result } = renderHook(() => useSelector(testSelector), {
+ wrapper: GlobalWrapper,
+ })
+ expect(result.current).toBeFalsy
+ })
+})
diff --git a/src/context/global/global-context.tsx b/src/context/global/global-context.tsx
index 1e6cca7f..c6c5a639 100644
--- a/src/context/global/global-context.tsx
+++ b/src/context/global/global-context.tsx
@@ -17,7 +17,7 @@ interface GlobalStateProviderProps {
children: React.ReactNode
}
-function GlobalStateProvider({ children }: GlobalStateProviderProps) {
+export function GlobalStateProvider({ children }: GlobalStateProviderProps) {
const [state, dispatch] = useLocalStorageReducer(
reducer,
initialState,
@@ -55,7 +55,7 @@ function GlobalStateProvider({ children }: GlobalStateProviderProps) {
)
}
-function useGlobal() {
+export function useGlobal() {
const context = React.useContext(GlobalContext)
if (context === undefined) {
throw new Error('useGlobal must be used within a GlobalStateProvider')
@@ -63,4 +63,10 @@ function useGlobal() {
return context
}
-export { GlobalStateProvider, useGlobal }
+export const useSelector = (selector: (state: State) => T) => {
+ const context = React.useContext(GlobalContext)
+ if (context === undefined) {
+ throw new Error('useSelector must be used within an GlobalStateProvider')
+ }
+ return selector(context.state)
+}
diff --git a/src/context/global/global-selectors.test.ts b/src/context/global/global-selectors.test.ts
new file mode 100644
index 00000000..8a3b19d8
--- /dev/null
+++ b/src/context/global/global-selectors.test.ts
@@ -0,0 +1,21 @@
+import { initialState, State } from '@/context/global'
+import { selectDebug, selectAdvancedConfiguration } from './global-selectors'
+
+describe('Experiment selectors', () => {
+ let state: State
+ beforeEach(() => {
+ state = JSON.parse(JSON.stringify(initialState))
+ })
+
+ it('should select debug', () => {
+ state.debug = true
+ expect(selectDebug(state)).toBeTruthy()
+ })
+
+ describe('Flags', () => {
+ it('should selectAdvancedConfiguration', () => {
+ state.flags.advancedConfiguration = true
+ expect(selectAdvancedConfiguration(state)).toBeTruthy()
+ })
+ })
+})
diff --git a/src/context/global/global-selectors.ts b/src/context/global/global-selectors.ts
new file mode 100644
index 00000000..5d8cd107
--- /dev/null
+++ b/src/context/global/global-selectors.ts
@@ -0,0 +1,8 @@
+import { State } from '@/context/global'
+
+export const selectDebug = (state: State) => state.debug
+
+export const selectFlags = (state: State) => state.flags
+
+export const selectAdvancedConfiguration = (state: State) =>
+ selectFlags(state).advancedConfiguration