diff --git a/packages/@react-facet/core/src/createFacetContext.spec.tsx b/packages/@react-facet/core/src/createFacetContext.spec.tsx index 08fc2eb1..53fb9b94 100644 --- a/packages/@react-facet/core/src/createFacetContext.spec.tsx +++ b/packages/@react-facet/core/src/createFacetContext.spec.tsx @@ -2,7 +2,7 @@ import React, { useContext } from 'react' import { act, render } from '@react-facet/dom-fiber-testing-library' import { createFacetContext } from './createFacetContext' import { useFacetState } from './hooks' -import { Setter } from './types' +import { Facet, Setter } from './types' it(`has default value`, () => { const defaultValue = 'defaultValue' @@ -71,3 +71,59 @@ it(`it updates the context value without re-conciliation`, () => { expect(result.baseElement).toContainHTML(evenNewerValue) expect(hasRenderedMock).toBeCalledTimes(1) }) + +// Tests that the intial value of the createFacetContext is static +describe('createFacetContext initial facet', () => { + const env = process.env + let staticFacet: Facet + const defaultValue = 'defaultValue' + + beforeEach(() => { + jest.resetModules() + process.env = { ...env } + + const context = createFacetContext(defaultValue) + const Component = () => { + const stringFacet = useContext(context) + staticFacet = stringFacet + return + } + + render() + }) + + afterEach(() => { + process.env = env + }) + + it(`it can be read but not mutated`, () => { + act(() => { + expect(staticFacet.get()).toBe(defaultValue) + expect('set' in staticFacet).toBe(false) + }) + }) + + it(`it responds with the same value if you observe it and warns you in a non-production environment`, () => { + const consoleLogMock = jest.spyOn(console, 'log').mockImplementation() + + const update = jest.fn() + + staticFacet.observe(update) + expect(update).toHaveBeenCalledTimes(1) + expect(update).toHaveBeenCalledWith(defaultValue) + expect(consoleLogMock).toHaveBeenCalledTimes(1) + expect(consoleLogMock).toHaveBeenCalledWith( + `Accessing a static facet created through createFacetContext, perhaps you're missing a Context Provider?`, + ) + + update.mockClear() + consoleLogMock.mockClear() + + process.env.NODE_ENV = 'production' + + staticFacet.observe(update) + expect(update).toHaveBeenCalledTimes(1) + expect(update).toHaveBeenCalledWith(defaultValue) + expect(consoleLogMock).toHaveBeenCalledTimes(0) + }) +}) diff --git a/packages/@react-facet/core/src/createFacetContext.tsx b/packages/@react-facet/core/src/createFacetContext.tsx index 5c4fa8f8..6fa439c6 100644 --- a/packages/@react-facet/core/src/createFacetContext.tsx +++ b/packages/@react-facet/core/src/createFacetContext.tsx @@ -6,7 +6,9 @@ export function createFacetContext(initialValue: T) { get: () => initialValue, observe: (listener) => { if (process.env.NODE_ENV !== 'production') { - console.log('Missing Provider') + console.log( + `Accessing a static facet created through createFacetContext, perhaps you're missing a Context Provider?`, + ) } listener(initialValue) return () => {} diff --git a/packages/@react-facet/core/src/facet/createFacet.spec.ts b/packages/@react-facet/core/src/facet/createFacet.spec.ts index 5c0fa202..57ef809b 100644 --- a/packages/@react-facet/core/src/facet/createFacet.spec.ts +++ b/packages/@react-facet/core/src/facet/createFacet.spec.ts @@ -1,5 +1,4 @@ import 'react' -import { defaultEqualityCheck } from '../equalityChecks' import { NO_VALUE } from '../types' import { createFacet } from './createFacet' @@ -8,7 +7,7 @@ describe('equalityChecks', () => { it('fires for object values, since it can be mutated', () => { const update = jest.fn() const initialValue = {} - const mock = createFacet({ initialValue, equalityCheck: defaultEqualityCheck }) + const mock = createFacet({ initialValue }) mock.observe(update) expect(update).toHaveBeenCalledTimes(1) expect(update).toHaveBeenCalledWith(initialValue) @@ -22,7 +21,7 @@ describe('equalityChecks', () => { it('fires for array values, since it can be mutated', () => { const update = jest.fn() const initialValue: string[] = [] - const mock = createFacet({ initialValue, equalityCheck: defaultEqualityCheck }) + const mock = createFacet({ initialValue }) mock.observe(update) expect(update).toHaveBeenCalledTimes(1) expect(update).toHaveBeenCalledWith(initialValue) @@ -36,7 +35,7 @@ describe('equalityChecks', () => { it('does not fire for string', () => { const update = jest.fn() const initialValue = 'string' - const mock = createFacet({ initialValue, equalityCheck: defaultEqualityCheck }) + const mock = createFacet({ initialValue }) mock.observe(update) expect(update).toHaveBeenCalledTimes(1) expect(update).toHaveBeenCalledWith(initialValue) @@ -49,7 +48,7 @@ describe('equalityChecks', () => { it('does not fire for boolean', () => { const update = jest.fn() const initialValue = true - const mock = createFacet({ initialValue, equalityCheck: defaultEqualityCheck }) + const mock = createFacet({ initialValue }) mock.observe(update) expect(update).toHaveBeenCalledTimes(1) expect(update).toHaveBeenCalledWith(initialValue) @@ -62,7 +61,7 @@ describe('equalityChecks', () => { it('does not fire for number', () => { const update = jest.fn() const initialValue = 1 - const mock = createFacet({ initialValue, equalityCheck: defaultEqualityCheck }) + const mock = createFacet({ initialValue }) mock.observe(update) expect(update).toHaveBeenCalledTimes(1) expect(update).toHaveBeenCalledWith(initialValue) @@ -75,7 +74,7 @@ describe('equalityChecks', () => { it('does not fire for null', () => { const update = jest.fn() const initialValue = null - const mock = createFacet({ initialValue, equalityCheck: defaultEqualityCheck }) + const mock = createFacet({ initialValue }) mock.observe(update) expect(update).toHaveBeenCalledTimes(1) expect(update).toHaveBeenCalledWith(initialValue) @@ -88,7 +87,7 @@ describe('equalityChecks', () => { it('does not fire for undefined', () => { const update = jest.fn() const initialValue = undefined - const mock = createFacet({ initialValue, equalityCheck: defaultEqualityCheck }) + const mock = createFacet({ initialValue }) mock.observe(update) expect(update).toHaveBeenCalledTimes(1) expect(update).toHaveBeenCalledWith(initialValue) @@ -101,7 +100,7 @@ describe('equalityChecks', () => { it('fires if the primitive value changed', () => { const update = jest.fn() const initialValue = 'initial' - const mock = createFacet({ initialValue, equalityCheck: defaultEqualityCheck }) + const mock = createFacet({ initialValue }) mock.observe(update) expect(update).toHaveBeenCalledTimes(1) expect(update).toHaveBeenCalledWith(initialValue) diff --git a/packages/@react-facet/core/src/facet/createFacet.ts b/packages/@react-facet/core/src/facet/createFacet.ts index e8535e68..c3355afb 100644 --- a/packages/@react-facet/core/src/facet/createFacet.ts +++ b/packages/@react-facet/core/src/facet/createFacet.ts @@ -12,8 +12,14 @@ export interface FacetOptions { startSubscription?: StartSubscription equalityCheck?: EqualityCheck } - -export function createFacet({ initialValue, startSubscription, equalityCheck }: FacetOptions): WritableFacet { +/** + * The low level function to create a Facet, not recommended to be used if you can use any of the react facet hooks to create facets instead (Ex: useFacetState, useFacetWrap) + */ +export function createFacet({ + initialValue, + startSubscription, + equalityCheck = defaultEqualityCheck, +}: FacetOptions): WritableFacet { const listeners: Set> = new Set() let currentValue = initialValue let cleanupSubscription: Cleanup | undefined diff --git a/packages/@react-facet/core/src/facet/createStaticFacet.spec.ts b/packages/@react-facet/core/src/facet/createStaticFacet.spec.ts new file mode 100644 index 00000000..a5876401 --- /dev/null +++ b/packages/@react-facet/core/src/facet/createStaticFacet.spec.ts @@ -0,0 +1,27 @@ +import { createStaticFacet } from './createStaticFacet' + +describe('createStaticFacet', () => { + it(`it can be read but not mutated`, () => { + const initialValue = {} + const mock = createStaticFacet(initialValue) + + expect(mock.get()).toBe(initialValue) + expect('set' in mock).toBe(false) + }) + + it(`it responds with the same value if you observe it and warns you in a non-production environment`, () => { + const update = jest.fn() + const initialValue = {} + const mock = createStaticFacet(initialValue) + + mock.observe(update) + expect(update).toHaveBeenCalledTimes(1) + expect(update).toHaveBeenCalledWith(initialValue) + + update.mockClear() + + mock.observe(update) + expect(update).toHaveBeenCalledTimes(1) + expect(update).toHaveBeenCalledWith(initialValue) + }) +}) diff --git a/packages/@react-facet/core/src/facet/createStaticFacet.ts b/packages/@react-facet/core/src/facet/createStaticFacet.ts new file mode 100644 index 00000000..88001ade --- /dev/null +++ b/packages/@react-facet/core/src/facet/createStaticFacet.ts @@ -0,0 +1,15 @@ +import { Facet } from '..' +/** + * Creates a nonwritable barebones static facet to be used when you need an initial facet value outside the react context + * that's meant to be replaced later by a real facet. Ex: with `createContext()` + */ +export function createStaticFacet(value: T): Facet { + const facet: Facet = { + get: () => value, + observe: (listener) => { + listener(value) + return () => {} + }, + } + return facet +} diff --git a/packages/@react-facet/core/src/facet/index.ts b/packages/@react-facet/core/src/facet/index.ts index 5259c030..4da7cc30 100644 --- a/packages/@react-facet/core/src/facet/index.ts +++ b/packages/@react-facet/core/src/facet/index.ts @@ -1,2 +1,3 @@ export * from './createFacet' export * from './createReadOnlyFacet' +export * from './createStaticFacet' diff --git a/packages/@react-facet/core/src/index.spec.ts b/packages/@react-facet/core/src/index.spec.ts index 94cd855c..7fd51ef1 100644 --- a/packages/@react-facet/core/src/index.spec.ts +++ b/packages/@react-facet/core/src/index.spec.ts @@ -10,9 +10,14 @@ describe('regression testing preventing accidental removal of APIs', () => { it('exposes the core facets', () => { expect(facet.createFacet).toBeDefined() + expect(facet.createStaticFacet).toBeDefined() expect(facet.createReadOnlyFacet).toBeDefined() }) + it('exposes the react facet methods', () => { + expect(facet.createFacetContext).toBeDefined() + }) + it('exposes the hooks', () => { expect(facet.useFacetCallback).toBeDefined() expect(facet.useFacetEffect).toBeDefined()