Skip to content

Commit

Permalink
Add selectors for global context
Browse files Browse the repository at this point in the history
  • Loading branch information
langdal committed Jul 8, 2022
1 parent 7c07e67 commit 21c21ad
Show file tree
Hide file tree
Showing 7 changed files with 83 additions and 14 deletions.
10 changes: 4 additions & 6 deletions src/components/experiment/configurationTab.tsx
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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
Expand Down
11 changes: 6 additions & 5 deletions src/context/experiment/experiment-context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ type ExperimentProviderProps = {
children: any
}

function ExperimentProvider({
export function ExperimentProvider({
experimentId,
children,
}: ExperimentProviderProps) {
Expand Down Expand Up @@ -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')
Expand Down Expand Up @@ -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 }
35 changes: 35 additions & 0 deletions src/context/global/global-context.test.tsx
Original file line number Diff line number Diff line change
@@ -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 }) => (
<GlobalStateProvider>{children}</GlobalStateProvider>
)

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
})
})
12 changes: 9 additions & 3 deletions src/context/global/global-context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ interface GlobalStateProviderProps {
children: React.ReactNode
}

function GlobalStateProvider({ children }: GlobalStateProviderProps) {
export function GlobalStateProvider({ children }: GlobalStateProviderProps) {
const [state, dispatch] = useLocalStorageReducer(
reducer,
initialState,
Expand Down Expand Up @@ -55,12 +55,18 @@ 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')
}
return context
}

export { GlobalStateProvider, useGlobal }
export const useSelector = <T,>(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)
}
21 changes: 21 additions & 0 deletions src/context/global/global-selectors.test.ts
Original file line number Diff line number Diff line change
@@ -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()
})
})
})
8 changes: 8 additions & 0 deletions src/context/global/global-selectors.ts
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit 21c21ad

Please sign in to comment.