diff --git a/packages/twenty-front/src/modules/ui/utilities/recoil-scope/scopes-internal/hooks/__tests__/useScopeInternalContextOrThrow.test.tsx b/packages/twenty-front/src/modules/ui/utilities/recoil-scope/scopes-internal/hooks/__tests__/useScopeInternalContextOrThrow.test.tsx new file mode 100644 index 000000000..c83c7d246 --- /dev/null +++ b/packages/twenty-front/src/modules/ui/utilities/recoil-scope/scopes-internal/hooks/__tests__/useScopeInternalContextOrThrow.test.tsx @@ -0,0 +1,44 @@ +import { createContext } from 'react'; +import { renderHook } from '@testing-library/react'; + +import { useScopeInternalContextOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useScopeInternalContextOrThrow'; +import { ScopeInternalContext } from '@/ui/utilities/recoil-scope/scopes-internal/types/ScopeInternalContext'; + +const mockedContextValue = 'mocked-scope-id'; +const MockedContext = createContext(mockedContextValue); +const nullContext = createContext(null); + +const ERROR_MESSAGE = + 'Using a scope context without a ScopeInternalContext.Provider wrapper for context'; + +const Wrapper = ({ children }: { children: React.ReactNode }) => ( + + {children} + +); + +describe('useScopeInternalContextOrThrow', () => { + it('should work as expected', () => { + const { result } = renderHook( + () => + useScopeInternalContextOrThrow( + MockedContext as ScopeInternalContext<{ scopeId: string }>, + ), + { + wrapper: Wrapper, + }, + ); + + expect(result.current).toBe(mockedContextValue); + }); + + it('should throw an error when used outside of the specified context', () => { + expect(() => { + renderHook(() => + useScopeInternalContextOrThrow( + nullContext as ScopeInternalContext<{ scopeId: string }>, + ), + ); + }).toThrow(ERROR_MESSAGE); + }); +}); diff --git a/packages/twenty-front/src/modules/ui/utilities/recoil-scope/scopes-internal/hooks/__tests__/useScopedState.test.ts b/packages/twenty-front/src/modules/ui/utilities/recoil-scope/scopes-internal/hooks/__tests__/useScopedState.test.ts new file mode 100644 index 000000000..1e5374797 --- /dev/null +++ b/packages/twenty-front/src/modules/ui/utilities/recoil-scope/scopes-internal/hooks/__tests__/useScopedState.test.ts @@ -0,0 +1,192 @@ +import { expect } from '@storybook/test'; +import { act, renderHook } from '@testing-library/react'; +import { + atomFamily, + RecoilRoot, + selector, + useRecoilCallback, + useRecoilState, + useRecoilValue, +} from 'recoil'; + +import { StateScopeMapKey } from '@/ui/utilities/recoil-scope/scopes-internal/types/StateScopeMapKey'; +import { createFamilyStateScopeMap } from '@/ui/utilities/recoil-scope/utils/createFamilyStateScopeMap'; +import { getFamilyScopeInjector } from '@/ui/utilities/recoil-scope/utils/getFamilyScopeInjector'; +import { getScopeInjector } from '@/ui/utilities/recoil-scope/utils/getScopeInjector'; +import { getSelectorScopeInjector } from '@/ui/utilities/recoil-scope/utils/getSelectorScopeInjector'; + +import { useScopedState } from '../useScopedState'; + +const scopeId = 'scopeId'; + +// scoped state +const defaultScopedState = 'defaultString'; +const scopedState = atomFamily({ + key: 'ScopedStateKey', + default: defaultScopedState, +}); +const scopedStateScopeInjector = getScopeInjector(scopedState); + +// scoped selector +const anotherScopedState = atomFamily({ + key: 'ScopedStateKey', + default: [1, 2, 3, 4, 5], +}); +const scopedSelector = ({ scopeId }: StateScopeMapKey) => + selector({ + key: 'FilteredState', + get: ({ get }) => { + const scopedStateValue = get(anotherScopedState({ scopeId })); + return scopedStateValue.filter((value) => value % 2 === 0); + }, + }); +const selectorScopeInjector = getSelectorScopeInjector(scopedSelector); + +// family state +const defaultValue = 'defaultString'; +const scopedFamilyState = createFamilyStateScopeMap({ + key: 'FamilyStateKey', + defaultValue, +}); +const familyScopeInjector = getFamilyScopeInjector(scopedFamilyState); + +describe('useScopedState', () => { + it('should get scoped state', () => { + const { + result: { + current: { getScopedState }, + }, + } = renderHook(() => useScopedState(scopeId)); + + const scopedState = getScopedState(scopedStateScopeInjector); + + const { result } = renderHook( + () => { + const [scoped, setScoped] = useRecoilState(scopedState); + return { scoped, setScoped }; + }, + { + wrapper: RecoilRoot, + }, + ); + + expect(result.current.scoped).toBe(defaultScopedState); + + const newValue = 'anotherValue'; + + act(() => { + result.current.setScoped(newValue); + }); + + expect(result.current.scoped).toBe(newValue); + }); + + it('should get scoped snapshot value', () => { + const { + result: { + current: { getScopedSnapshotValue }, + }, + } = renderHook(() => useScopedState(scopeId)); + + const { result } = renderHook( + () => + useRecoilCallback( + ({ snapshot }) => + () => + getScopedSnapshotValue(snapshot, scopedStateScopeInjector), + )(), + { wrapper: RecoilRoot }, + ); + + expect(result.current).toBe(defaultScopedState); + }); + + it('should get scoped selector', () => { + const { + result: { + current: { getScopedSelector }, + }, + } = renderHook(() => useScopedState(scopeId)); + + const recoilValue = getScopedSelector(selectorScopeInjector); + + const { result } = renderHook(() => useRecoilValue(recoilValue), { + wrapper: RecoilRoot, + }); + + expect(result.current).toEqual([2, 4]); + }); + + it('should get scoped selector snapshot value', () => { + const { + result: { + current: { getScopedSelectorSnapshotValue }, + }, + } = renderHook(() => useScopedState(scopeId)); + + const { result } = renderHook( + () => + useRecoilCallback( + ({ snapshot }) => + () => + getScopedSelectorSnapshotValue(snapshot, selectorScopeInjector), + )(), + { wrapper: RecoilRoot }, + ); + + expect(result.current).toEqual([2, 4]); + }); + + it('should get scoped family state', () => { + const { + result: { + current: { getScopedFamilyState }, + }, + } = renderHook(() => useScopedState(scopeId)); + + const scopedFamilyState = getScopedFamilyState(familyScopeInjector); + + const { result } = renderHook( + () => { + const [familyState, setFamilyState] = useRecoilState( + scopedFamilyState('familyKey'), + ); + + return { familyState, setFamilyState }; + }, + { + wrapper: RecoilRoot, + }, + ); + + expect(result.current.familyState).toBe('defaultString'); + + const newValue = 'newValue'; + + act(() => { + result.current.setFamilyState(newValue); + }); + + expect(result.current.familyState).toBe(newValue); + }); + + it('should get scoped family snapshot value', () => { + const { + result: { + current: { getScopedFamilySnapshotValue }, + }, + } = renderHook(() => useScopedState(scopeId)); + + const { result } = renderHook( + () => + useRecoilCallback( + ({ snapshot }) => + () => + getScopedFamilySnapshotValue(snapshot, familyScopeInjector), + )(), + { wrapper: RecoilRoot }, + ); + + expect(result.current('sampleKey')).toBe('defaultString'); + }); +}); diff --git a/packages/twenty-front/src/modules/ui/utilities/recoil-scope/utils/__tests__/getFamilyScopeInjector.test.ts b/packages/twenty-front/src/modules/ui/utilities/recoil-scope/utils/__tests__/getFamilyScopeInjector.test.ts new file mode 100644 index 000000000..559221bcb --- /dev/null +++ b/packages/twenty-front/src/modules/ui/utilities/recoil-scope/utils/__tests__/getFamilyScopeInjector.test.ts @@ -0,0 +1,40 @@ +import { act, renderHook } from '@testing-library/react'; +import { RecoilRoot, useRecoilState } from 'recoil'; + +import { createFamilyStateScopeMap } from '@/ui/utilities/recoil-scope/utils/createFamilyStateScopeMap'; + +import { getFamilyScopeInjector } from '../getFamilyScopeInjector'; + +const defaultValue = 'defaultString'; + +const testState = createFamilyStateScopeMap({ + key: 'familyStateKey', + defaultValue, +}); + +describe('getFamilyScopeInjector', () => { + it('should return a scoped family state', () => { + const familyScopeInjector = getFamilyScopeInjector(testState); + const familyState = familyScopeInjector('scopeId', 'familyKey'); + + const { result } = renderHook( + () => { + const [family, setFamily] = useRecoilState(familyState); + return { family, setFamily }; + }, + { + wrapper: RecoilRoot, + }, + ); + + expect(result.current.family).toBe(defaultValue); + + const newValue = 'anotherValue'; + + act(() => { + result.current.setFamily(newValue); + }); + + expect(result.current.family).toBe(newValue); + }); +}); diff --git a/packages/twenty-front/src/modules/ui/utilities/recoil-scope/utils/__tests__/getScopeInjector.test.ts b/packages/twenty-front/src/modules/ui/utilities/recoil-scope/utils/__tests__/getScopeInjector.test.ts new file mode 100644 index 000000000..ef8ab5993 --- /dev/null +++ b/packages/twenty-front/src/modules/ui/utilities/recoil-scope/utils/__tests__/getScopeInjector.test.ts @@ -0,0 +1,45 @@ +import { act, renderHook } from '@testing-library/react'; +import { atomFamily, RecoilRoot, useRecoilState } from 'recoil'; + +import { getScopeInjector } from '../getScopeInjector'; + +const defaultValue = 'defaultString'; + +const scopedState = atomFamily< + string, + { + scopeId: string; + } +>({ + key: 'myStateKey', + default: defaultValue, +}); + +describe('getScopeInjector', () => { + it('should return the scoped state for the given scopeId', () => { + const scopeInjector = getScopeInjector(scopedState); + + const scopeId = 'scopeId'; + const recoilState = scopeInjector(scopeId); + + const { result } = renderHook( + () => { + const [recoil, setRecoil] = useRecoilState(recoilState); + return { recoil, setRecoil }; + }, + { + wrapper: RecoilRoot, + }, + ); + + expect(result.current.recoil).toBe(defaultValue); + + const newValue = 'anotherValue'; + + act(() => { + result.current.setRecoil(newValue); + }); + + expect(result.current.recoil).toBe(newValue); + }); +}); diff --git a/packages/twenty-front/src/modules/ui/utilities/recoil-scope/utils/__tests__/getSelectorScopeInjector.test.ts b/packages/twenty-front/src/modules/ui/utilities/recoil-scope/utils/__tests__/getSelectorScopeInjector.test.ts new file mode 100644 index 000000000..0f3e6d0bd --- /dev/null +++ b/packages/twenty-front/src/modules/ui/utilities/recoil-scope/utils/__tests__/getSelectorScopeInjector.test.ts @@ -0,0 +1,38 @@ +import { renderHook } from '@testing-library/react'; +import { atomFamily, RecoilRoot, selector, useRecoilValue } from 'recoil'; + +import { StateScopeMapKey } from '@/ui/utilities/recoil-scope/scopes-internal/types/StateScopeMapKey'; + +import { getSelectorScopeInjector } from '../getSelectorScopeInjector'; + +const scopedState = atomFamily< + number[], + { + scopeId: string; + } +>({ + key: 'myStateKey', + default: [1, 2, 3, 4, 5], +}); + +const scopedSelector = ({ scopeId }: StateScopeMapKey) => + selector({ + key: 'FilteredState', + get: ({ get }) => { + const scopedStateValue = get(scopedState({ scopeId })); + return scopedStateValue.filter((value) => value % 2 === 0); + }, + }); + +describe('getSelectorScopeInjector', () => { + it('should return a valid SelectorScopeInjector', () => { + const selectorScopeInjector = getSelectorScopeInjector(scopedSelector); + const recoilValue = selectorScopeInjector('scopeId'); + + const { result } = renderHook(() => useRecoilValue(recoilValue), { + wrapper: RecoilRoot, + }); + + expect(result.current).toEqual([2, 4]); + }); +});