From 26a77194b5ddeba5a35f1755b410089bdd4dfc4b Mon Sep 17 00:00:00 2001 From: David Maskasky Date: Tue, 24 Sep 2024 16:26:56 -0700 Subject: [PATCH] chore(format): format the world --- .eslintrc.json | 26 +- CHANGELOG.md | 22 + README.md | 14 +- __tests__/ScopeProvider/01_basic_spec.tsx | 546 ++++++++---------- __tests__/ScopeProvider/02_removeScope.tsx | 220 +++---- __tests__/ScopeProvider/03_nested.tsx | 111 ++-- __tests__/ScopeProvider/04_derived.tsx | 97 ++-- __tests__/ScopeProvider/05_derived_self.tsx | 48 +- .../ScopeProvider/06_implicit_parent.tsx | 92 ++- __tests__/ScopeProvider/07_writable.tsx | 95 ++- __tests__/ScopeProvider/08_family.tsx | 144 ++--- __tests__/ScopeProvider/09_scope_provider.tsx | 50 +- __tests__/createIsolation/01_basic_spec.tsx | 8 +- __tests__/utils.ts | 30 +- examples/01_isolation/src/App.tsx | 20 +- examples/01_isolation/src/index.tsx | 8 +- examples/02_removeScope/src/App.tsx | 42 +- examples/02_removeScope/src/index.tsx | 8 +- examples/03_nested/src/App.tsx | 45 +- examples/03_nested/src/index.tsx | 8 +- examples/04_derived/src/App.tsx | 31 +- examples/04_derived/src/index.tsx | 8 +- src/ScopeProvider/ScopeProvider.tsx | 87 ++- src/ScopeProvider/patchedStore.ts | 24 +- src/ScopeProvider/scope.ts | 143 ++--- src/ScopeProvider/types.ts | 24 +- src/createIsolation.tsx | 64 +- src/index.ts | 4 +- webpack.config.js | 6 +- 29 files changed, 888 insertions(+), 1137 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 6bae3d3..e1af2c3 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,11 +1,7 @@ { "parser": "@typescript-eslint/parser", "plugins": ["@typescript-eslint", "prettier"], - "extends": [ - "plugin:@typescript-eslint/recommended", - "airbnb", - "plugin:prettier/recommended" - ], + "extends": ["plugin:@typescript-eslint/recommended", "airbnb", "plugin:prettier/recommended"], "env": { "browser": true }, @@ -29,15 +25,9 @@ "@typescript-eslint/explicit-function-return-type": "off", "@typescript-eslint/explicit-module-boundary-types": "off", "@typescript-eslint/no-explicit-any": "off", - "@typescript-eslint/no-unused-vars": [ - "error", - { "argsIgnorePattern": "^_" } - ], + "@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_" }], "react/button-has-type": "off", - "react/jsx-filename-extension": [ - "error", - { "extensions": [".js", ".tsx"] } - ], + "react/jsx-filename-extension": ["error", { "extensions": [".js", ".tsx"] }], "react/prop-types": "off", "react/jsx-one-expression-per-line": "off", "react/jsx-no-useless-fragment": "off", @@ -55,10 +45,7 @@ "no-unused-vars": "off", "no-redeclare": "off", "camelcase": ["error", { "allow": ["^INTERNAL_", "^unstable_"] }], - "react/function-component-definition": [ - "error", - { "namedComponents": "function-declaration" } - ], + "react/function-component-definition": ["error", { "namedComponents": "function-declaration" }], "react/no-unescaped-entities": "off", "react/no-array-index-key": "off", "react/require-default-props": "off", @@ -73,10 +60,7 @@ "jest": true }, "rules": { - "import/no-extraneous-dependencies": [ - "error", - { "devDependencies": true } - ] + "import/no-extraneous-dependencies": ["error", { "devDependencies": true }] } }, { diff --git a/CHANGELOG.md b/CHANGELOG.md index 02f976d..c05da46 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,45 +3,67 @@ ## [Unreleased] ## [0.6.0] - 2024-05-07 + ### Changed + - feat(scope): support using derived atom in ScopeProvider (#30) ## [0.5.2] - 2024-04-30 + ### Changed + - feat(isolation): export useStore hook using the isolated context (#27) ## [0.5.1] - 2024-01-30 + ### Changed + - fix(isolation): fix prop types #15 ## [0.5.0] - 2024-01-26 + ### Changed + - feat(scope): A complete rewrite of ScopeProvider #20 ## [0.4.1] - 2023-11-01 + ### Changed + - feat(scope): Avoid extra re-renders with memoization #10 ## [0.4.0] - 2023-10-24 + ### Added + - feat(scope): patch store to avoid patching hooks #9 ## [0.3.1] - 2023-10-24 + ### Changed + - fix(scope): useScopedAtom and useHydrateAtoms #7 ## [0.3.0] - 2023-10-23 + ### Added + - feat(scope): new implementation scoping atoms instead of store #5 ## [0.2.0] - 2023-10-01 + ### Added + - new scope util #2 ## [0.1.1] - 2023-04-21 + ### Changed + - refactor: simplify the use of useHydratedAtoms #1 ## [0.1.0] - 2023-04-20 + ### Added + - Initial release diff --git a/README.md b/README.md index ba26c7d..bc848be 100644 --- a/README.md +++ b/README.md @@ -16,15 +16,15 @@ https://jotai.org/docs/integrations/scope Taking the following setting in mind: ```javascript -const base = atom(0); -const derived1 = atom((get) => get(base)); -const derived2 = atom((get) => get(base)); +const base = atom(0) +const derived1 = atom((get) => get(base)) +const derived2 = atom((get) => get(base)) const Component = () => { - useAtom(base); - useAtom(derived1); - useAtom(derived2); -}; + useAtom(base) + useAtom(derived1) + useAtom(derived2) +} ``` ### Example1: base and derived1 are scoped diff --git a/__tests__/ScopeProvider/01_basic_spec.tsx b/__tests__/ScopeProvider/01_basic_spec.tsx index 4a5c8e0..1c9d6e2 100644 --- a/__tests__/ScopeProvider/01_basic_spec.tsx +++ b/__tests__/ScopeProvider/01_basic_spec.tsx @@ -1,4 +1,4 @@ -import { render } from '@testing-library/react'; +import { render } from '@testing-library/react' import { useAtom, useSetAtom, @@ -6,10 +6,10 @@ import { atom, type WritableAtom, type SetStateAction, -} from 'jotai'; -import { atomWithReducer } from 'jotai/vanilla/utils'; -import { ScopeProvider } from '../../src/index'; -import { clickButton, getTextContents } from '../utils'; +} from 'jotai' +import { atomWithReducer } from 'jotai/vanilla/utils' +import { ScopeProvider } from '../../src/index' +import { clickButton, getTextContents } from '../utils' describe('Counter', () => { /* @@ -18,10 +18,10 @@ describe('Counter', () => { S1[]: base0 */ test('01. ScopeProvider does not provide isolation for unscoped primitive atoms', () => { - const baseAtom = atom(0); - baseAtom.debugLabel = 'base'; + const baseAtom = atom(0) + baseAtom.debugLabel = 'base' function Counter({ level }: { level: string }) { - const [base, increaseBase] = useAtom(baseAtom); + const [base, increaseBase] = useAtom(baseAtom) return (
base:{base} @@ -33,7 +33,7 @@ describe('Counter', () => { increase
- ); + ) } function App() { @@ -46,30 +46,30 @@ describe('Counter', () => { - ); + ) } - const { container } = render(); - const increaseUnscopedBase = '.level0.setBase'; - const increaseScopedBase = '.level1.setBase'; - const atomValueSelectors = ['.level0.base', '.level1.base']; + const { container } = render() + const increaseUnscopedBase = '.level0.setBase' + const increaseScopedBase = '.level1.setBase' + const atomValueSelectors = ['.level0.base', '.level1.base'] expect(getTextContents(container, atomValueSelectors)).toEqual([ '0', // level0 base '0', // level1 base - ]); + ]) - clickButton(container, increaseUnscopedBase); + clickButton(container, increaseUnscopedBase) expect(getTextContents(container, atomValueSelectors)).toEqual([ '1', // level0 base '1', // level1 base - ]); + ]) - clickButton(container, increaseScopedBase); + clickButton(container, increaseScopedBase) expect(getTextContents(container, atomValueSelectors)).toEqual([ '2', // level0 base '2', // level1 base - ]); - }); + ]) + }) /* base, Derived(base) @@ -77,27 +77,23 @@ describe('Counter', () => { S1[]: base0 Derived0(base0) */ test('02. unscoped derived atoms are unaffected in ScopeProvider', () => { - const baseAtom = atom(0); + const baseAtom = atom(0) const derivedAtom = atom( (get) => get(baseAtom), (_get, set, value: SetStateAction) => set(baseAtom, value), - ); - baseAtom.debugLabel = 'base'; + ) + baseAtom.debugLabel = 'base' function Counter({ level }: { level: string }) { - const [derived, setDerived] = useAtom(derivedAtom); - const increaseDerived = () => setDerived((c) => c + 1); + const [derived, setDerived] = useAtom(derivedAtom) + const increaseDerived = () => setDerived((c) => c + 1) return (
base:{derived} -
- ); + ) } function App() { @@ -110,30 +106,30 @@ describe('Counter', () => { - ); + ) } - const { container } = render(); - const increaseUnscopedBase = '.level0.setDerived'; - const increaseScopedBase = '.level1.setDerived'; - const atomValueSelectors = ['.level0.derived', '.level1.derived']; + const { container } = render() + const increaseUnscopedBase = '.level0.setDerived' + const increaseScopedBase = '.level1.setDerived' + const atomValueSelectors = ['.level0.derived', '.level1.derived'] expect(getTextContents(container, atomValueSelectors)).toEqual([ '0', // level 0 derived '0', // level 1 derived - ]); + ]) - clickButton(container, increaseUnscopedBase); + clickButton(container, increaseUnscopedBase) expect(getTextContents(container, atomValueSelectors)).toEqual([ '1', // level 0 derived '1', // level 1 derived - ]); + ]) - clickButton(container, increaseScopedBase); + clickButton(container, increaseScopedBase) expect(getTextContents(container, atomValueSelectors)).toEqual([ '2', // level 0 derived '2', // level 1 derived - ]); - }); + ]) + }) /* base @@ -141,10 +137,10 @@ describe('Counter', () => { S1[base]: base1 */ test('03. ScopeProvider provides isolation for scoped primitive atoms', () => { - const baseAtom = atom(0); - baseAtom.debugLabel = 'base'; + const baseAtom = atom(0) + baseAtom.debugLabel = 'base' function Counter({ level }: { level: string }) { - const [base, increaseBase] = useAtom(baseAtom); + const [base, increaseBase] = useAtom(baseAtom) return (
base:{base} @@ -156,7 +152,7 @@ describe('Counter', () => { increase
- ); + ) } function App() { @@ -169,30 +165,30 @@ describe('Counter', () => { - ); + ) } - const { container } = render(); - const increaseUnscopedBase = '.level0.setBase'; - const increaseScopedBase = '.level1.setBase'; - const atomValueSelectors = ['.level0.base', '.level1.base']; + const { container } = render() + const increaseUnscopedBase = '.level0.setBase' + const increaseScopedBase = '.level1.setBase' + const atomValueSelectors = ['.level0.base', '.level1.base'] expect(getTextContents(container, atomValueSelectors)).toEqual([ '0', // level0 base '0', // level1 base - ]); + ]) - clickButton(container, increaseUnscopedBase); + clickButton(container, increaseUnscopedBase) expect(getTextContents(container, atomValueSelectors)).toEqual([ '1', // level0 base '0', // level1 base - ]); + ]) - clickButton(container, increaseScopedBase); + clickButton(container, increaseScopedBase) expect(getTextContents(container, atomValueSelectors)).toEqual([ '1', // level0 base '1', // level1 base - ]); - }); + ]) + }) /* base, derived(base) @@ -200,30 +196,26 @@ describe('Counter', () => { S1[base]: derived0(base1) */ test('04. unscoped derived can read and write to scoped primitive atoms', () => { - const baseAtom = atom(0); - baseAtom.debugLabel = 'base'; + const baseAtom = atom(0) + baseAtom.debugLabel = 'base' const derivedAtom = atom( (get) => get(baseAtom), (get, set) => set(baseAtom, get(baseAtom) + 1), - ); - derivedAtom.debugLabel = 'derived'; + ) + derivedAtom.debugLabel = 'derived' function Counter({ level }: { level: string }) { - const [derived, increaseFromDerived] = useAtom(derivedAtom); - const value = useAtomValue(baseAtom); + const [derived, increaseFromDerived] = useAtom(derivedAtom) + const value = useAtomValue(baseAtom) return (
base:{derived} value:{value} -
- ); + ) } function App() { @@ -236,41 +228,36 @@ describe('Counter', () => { - ); + ) } - const { container } = render(); - const increaseUnscopedBase = '.level0.setBase'; - const increaseScopedBase = '.level1.setBase'; - const atomValueSelectors = [ - '.level0.base', - '.level0.value', - '.level1.base', - '.level1.value', - ]; + const { container } = render() + const increaseUnscopedBase = '.level0.setBase' + const increaseScopedBase = '.level1.setBase' + const atomValueSelectors = ['.level0.base', '.level0.value', '.level1.base', '.level1.value'] expect(getTextContents(container, atomValueSelectors)).toEqual([ '0', // level0 base '0', // level0 value '0', // level1 base '0', // level1 value - ]); + ]) - clickButton(container, increaseUnscopedBase); + clickButton(container, increaseUnscopedBase) expect(getTextContents(container, atomValueSelectors)).toEqual([ '1', // level0 base '1', // level0 value '0', // level1 base '0', // level1 value - ]); + ]) - clickButton(container, increaseScopedBase); + clickButton(container, increaseScopedBase) expect(getTextContents(container, atomValueSelectors)).toEqual([ '1', // level0 base '1', // level0 value '1', // level1 base '1', // level1 value - ]); - }); + ]) + }) /* base, notScoped, derived(base + notScoped) @@ -278,46 +265,38 @@ describe('Counter', () => { S1[base]: derived0(base1 + notScoped0) */ test('05. unscoped derived can read both scoped and unscoped atoms', () => { - const baseAtom = atomWithReducer(0, (v) => v + 1); - baseAtom.debugLabel = 'base'; - const notScopedAtom = atomWithReducer(0, (v) => v + 1); - notScopedAtom.debugLabel = 'notScoped'; + const baseAtom = atomWithReducer(0, (v) => v + 1) + baseAtom.debugLabel = 'base' + const notScopedAtom = atomWithReducer(0, (v) => v + 1) + notScopedAtom.debugLabel = 'notScoped' const derivedAtom = atom((get) => ({ base: get(baseAtom), notScoped: get(notScopedAtom), - })); - derivedAtom.debugLabel = 'derived'; + })) + derivedAtom.debugLabel = 'derived' function Counter({ level }: { level: string }) { - const increaseBase = useSetAtom(baseAtom); - const derived = useAtomValue(derivedAtom); + const increaseBase = useSetAtom(baseAtom) + const derived = useAtomValue(derivedAtom) return (
base:{derived.base} not scoped: {derived.notScoped} -
- ); + ) } function IncreaseUnscoped() { - const increaseNotScoped = useSetAtom(notScopedAtom); + const increaseNotScoped = useSetAtom(notScopedAtom) return ( - - ); + ) } function App() { @@ -331,45 +310,41 @@ describe('Counter', () => { - ); + ) } - const { container } = render(); - const increaseUnscopedBase = '.level0.setBase'; - const increaseScopedBase = '.level1.setBase'; - const increaseNotScoped = '.increaseNotScoped'; - const atomValueSelectors = [ - '.level0.base', - '.level1.base', - '.level1.notScoped', - ]; + const { container } = render() + const increaseUnscopedBase = '.level0.setBase' + const increaseScopedBase = '.level1.setBase' + const increaseNotScoped = '.increaseNotScoped' + const atomValueSelectors = ['.level0.base', '.level1.base', '.level1.notScoped'] expect(getTextContents(container, atomValueSelectors)).toEqual([ '0', // level0 base '0', // level1 base '0', // level1 notScoped - ]); + ]) - clickButton(container, increaseUnscopedBase); + clickButton(container, increaseUnscopedBase) expect(getTextContents(container, atomValueSelectors)).toEqual([ '1', // level0 base '0', // level1 base '0', // level1 notScoped - ]); + ]) - clickButton(container, increaseScopedBase); + clickButton(container, increaseScopedBase) expect(getTextContents(container, atomValueSelectors)).toEqual([ '1', // level0 base '1', // level1 base '0', // level1 notScoped - ]); + ]) - clickButton(container, increaseNotScoped); + clickButton(container, increaseNotScoped) expect(getTextContents(container, atomValueSelectors)).toEqual([ '1', // level0 base '1', // level1 base '1', // level1 notScoped - ]); - }); + ]) + }) /* base, derived(base), @@ -377,37 +352,29 @@ describe('Counter', () => { S1[derived]: derived1(base1) */ test('06. dependencies of scoped derived are implicitly scoped', () => { - const baseAtom = atomWithReducer(0, (v) => v + 1); - baseAtom.debugLabel = 'base'; + const baseAtom = atomWithReducer(0, (v) => v + 1) + baseAtom.debugLabel = 'base' const derivedAtom = atom( (get) => get(baseAtom), (_get, set) => set(baseAtom), - ); - derivedAtom.debugLabel = 'derived'; + ) + derivedAtom.debugLabel = 'derived' function Counter({ level }: { level: string }) { - const increaseBase = useSetAtom(baseAtom); - const [derived, setDerived] = useAtom(derivedAtom); + const increaseBase = useSetAtom(baseAtom) + const [derived, setDerived] = useAtom(derivedAtom) return (
base:{derived} - -
- ); + ) } function App() { @@ -420,25 +387,25 @@ describe('Counter', () => { - ); + ) } - const { container } = render(); - const increaseUnscopedBase = '.level0.setBase'; - const increaseScopedBase = '.level1.setBase'; - const increaseScopedDerived = '.level1.setDerived'; - const atomValueSelectors = ['.level0.base', '.level1.base']; + const { container } = render() + const increaseUnscopedBase = '.level0.setBase' + const increaseScopedBase = '.level1.setBase' + const increaseScopedDerived = '.level1.setDerived' + const atomValueSelectors = ['.level0.base', '.level1.base'] - expect(getTextContents(container, atomValueSelectors)).toEqual(['0', '0']); + expect(getTextContents(container, atomValueSelectors)).toEqual(['0', '0']) - clickButton(container, increaseUnscopedBase); - expect(getTextContents(container, atomValueSelectors)).toEqual(['1', '0']); + clickButton(container, increaseUnscopedBase) + expect(getTextContents(container, atomValueSelectors)).toEqual(['1', '0']) - clickButton(container, increaseScopedBase); - expect(getTextContents(container, atomValueSelectors)).toEqual(['2', '0']); + clickButton(container, increaseScopedBase) + expect(getTextContents(container, atomValueSelectors)).toEqual(['2', '0']) - clickButton(container, increaseScopedDerived); - expect(getTextContents(container, atomValueSelectors)).toEqual(['2', '1']); - }); + clickButton(container, increaseScopedDerived) + expect(getTextContents(container, atomValueSelectors)).toEqual(['2', '1']) + }) /* base, derivedA(base), derivemB(base) @@ -446,23 +413,23 @@ describe('Counter', () => { S1[derivedA, derivedB]: derivedA1(base1), derivedB1(base1) */ test('07. scoped derived atoms can share implicitly scoped dependencies', () => { - const baseAtom = atomWithReducer(0, (v) => v + 1); - baseAtom.debugLabel = 'base'; + const baseAtom = atomWithReducer(0, (v) => v + 1) + baseAtom.debugLabel = 'base' const derivedAtomA = atom( (get) => get(baseAtom), (_get, set) => set(baseAtom), - ); - derivedAtomA.debugLabel = 'derivedAtomA'; + ) + derivedAtomA.debugLabel = 'derivedAtomA' const derivedAtomB = atom( (get) => get(baseAtom), (_get, set) => set(baseAtom), - ); - derivedAtomB.debugLabel = 'derivedAtomB'; + ) + derivedAtomB.debugLabel = 'derivedAtomB' function Counter({ level }: { level: string }) { - const setBase = useSetAtom(baseAtom); - const [derivedA, setDerivedA] = useAtom(derivedAtomA); - const [derivedB, setDerivedB] = useAtom(derivedAtomB); + const setBase = useSetAtom(baseAtom) + const [derivedA, setDerivedA] = useAtom(derivedAtomA) + const [derivedB, setDerivedB] = useAtom(derivedAtomB) return (
base:{derivedA} @@ -470,29 +437,17 @@ describe('Counter', () => { {derivedA} derivedB: {derivedB} - - -
- ); + ) } function App() { @@ -505,53 +460,49 @@ describe('Counter', () => { - ); + ) } - const { container } = render(); - const increaseLevel0Base = '.level0.setBase'; - const increaseLevel1Base = '.level1.setBase'; - const increaseLevel1DerivedA = '.level1.setDerivedA'; - const increaseLevel1DerivedB = '.level1.setDerivedB'; - const atomValueSelectors = [ - '.level0.derivedA', - '.level1.derivedA', - '.level1.derivedB', - ]; + const { container } = render() + const increaseLevel0Base = '.level0.setBase' + const increaseLevel1Base = '.level1.setBase' + const increaseLevel1DerivedA = '.level1.setDerivedA' + const increaseLevel1DerivedB = '.level1.setDerivedB' + const atomValueSelectors = ['.level0.derivedA', '.level1.derivedA', '.level1.derivedB'] expect(getTextContents(container, atomValueSelectors)).toEqual([ '0', // level0 derivedA '0', // level1 derivedA '0', // level1 derivedB - ]); + ]) - clickButton(container, increaseLevel0Base); + clickButton(container, increaseLevel0Base) expect(getTextContents(container, atomValueSelectors)).toEqual([ '1', // level0 derivedA '0', // level1 derivedA '0', // level1 derivedB - ]); + ]) - clickButton(container, increaseLevel1Base); + clickButton(container, increaseLevel1Base) expect(getTextContents(container, atomValueSelectors)).toEqual([ '2', // level0 derivedA '0', // level1 derivedA '0', // level1 derivedB - ]); + ]) - clickButton(container, increaseLevel1DerivedA); + clickButton(container, increaseLevel1DerivedA) expect(getTextContents(container, atomValueSelectors)).toEqual([ '2', // level0 derivedA '1', // level1 derivedA '1', // level1 derivedB - ]); + ]) - clickButton(container, increaseLevel1DerivedB); + clickButton(container, increaseLevel1DerivedB) expect(getTextContents(container, atomValueSelectors)).toEqual([ '2', // level0 derivedA '2', // level1 derivedA '2', // level1 derivedB - ]); - }); + ]) + }) /* base, derivedA(base), derivedB(base) @@ -561,22 +512,18 @@ describe('Counter', () => { S3[base]: base3 */ test('08. nested scopes provide isolation for primitive atoms at every level', () => { - const baseAtom = atomWithReducer(0, (v) => v + 1); + const baseAtom = atomWithReducer(0, (v) => v + 1) function Counter({ level }: { level: string }) { - const [base, increaseBase] = useAtom(baseAtom); + const [base, increaseBase] = useAtom(baseAtom) return (
base:{base} -
- ); + ) } function App() { @@ -592,41 +539,25 @@ describe('Counter', () => { - ); + ) } - const { container } = render(); - const increaseUnscopedBase = '.level0.setBase'; - const increaseScopedBase = '.level1.setBase'; - const increaseDoubleScopedBase = '.level2.setBase'; - const atomValueSelectors = ['.level0.base', '.level1.base', '.level2.base']; + const { container } = render() + const increaseUnscopedBase = '.level0.setBase' + const increaseScopedBase = '.level1.setBase' + const increaseDoubleScopedBase = '.level2.setBase' + const atomValueSelectors = ['.level0.base', '.level1.base', '.level2.base'] - expect(getTextContents(container, atomValueSelectors)).toEqual([ - '0', - '0', - '0', - ]); + expect(getTextContents(container, atomValueSelectors)).toEqual(['0', '0', '0']) - clickButton(container, increaseUnscopedBase); - expect(getTextContents(container, atomValueSelectors)).toEqual([ - '1', - '0', - '0', - ]); + clickButton(container, increaseUnscopedBase) + expect(getTextContents(container, atomValueSelectors)).toEqual(['1', '0', '0']) - clickButton(container, increaseScopedBase); - expect(getTextContents(container, atomValueSelectors)).toEqual([ - '1', - '1', - '0', - ]); + clickButton(container, increaseScopedBase) + expect(getTextContents(container, atomValueSelectors)).toEqual(['1', '1', '0']) - clickButton(container, increaseDoubleScopedBase); - expect(getTextContents(container, atomValueSelectors)).toEqual([ - '1', - '1', - '1', - ]); - }); + clickButton(container, increaseDoubleScopedBase) + expect(getTextContents(container, atomValueSelectors)).toEqual(['1', '1', '1']) + }) /* baseA, baseB, baseC, derived(baseA + baseB + baseC) @@ -636,12 +567,12 @@ describe('Counter', () => { */ test('09. unscoped derived atoms in nested scoped can read and write to scoped primitive atoms at every level', async () => { function clickButtonGetResults(buttonSelector: string) { - const baseAAtom = atom(0); - baseAAtom.debugLabel = 'baseA'; - const baseBAtom = atom(0); - baseBAtom.debugLabel = 'baseB'; - const baseCAtom = atom(0); - baseCAtom.debugLabel = 'baseC'; + const baseAAtom = atom(0) + baseAAtom.debugLabel = 'baseA' + const baseBAtom = atom(0) + baseBAtom.debugLabel = 'baseB' + const baseCAtom = atom(0) + baseCAtom.debugLabel = 'baseC' const derivedAtom = atom( (get) => ({ baseA: get(baseAAtom), @@ -649,25 +580,25 @@ describe('Counter', () => { baseC: get(baseCAtom), }), (get, set) => { - set(baseAAtom, get(baseAAtom) + 1); - set(baseBAtom, get(baseBAtom) + 1); - set(baseCAtom, get(baseCAtom) + 1); + set(baseAAtom, get(baseAAtom) + 1) + set(baseBAtom, get(baseBAtom) + 1) + set(baseCAtom, get(baseCAtom) + 1) }, - ); - derivedAtom.debugLabel = 'derived'; + ) + derivedAtom.debugLabel = 'derived' function Counter({ level, baseAtom, }: { - level: string; - baseAtom: WritableAtom], void>; + level: string + baseAtom: WritableAtom], void> }) { - const setBase = useSetAtom(baseAtom); - const [{ baseA, baseB, baseC }, increaseAll] = useAtom(derivedAtom); - const valueA = useAtomValue(baseAAtom); - const valueB = useAtomValue(baseBAtom); - const valueC = useAtomValue(baseCAtom); + const setBase = useSetAtom(baseAtom) + const [{ baseA, baseB, baseC }, increaseAll] = useAtom(derivedAtom) + const valueA = useAtomValue(baseAAtom) + const valueB = useAtomValue(baseBAtom) + const valueC = useAtomValue(baseCAtom) return (
baseA:{baseA} @@ -680,7 +611,7 @@ describe('Counter', () => { className={`${level} increaseBase`} type="button" onClick={() => { - setBase((c) => c + 1); + setBase((c) => c + 1) }} > increase base @@ -689,13 +620,13 @@ describe('Counter', () => { className={`${level} increaseAll`} type="button" onClick={() => { - increaseAll(); + increaseAll() }} > increase all
- ); + ) } function App() { @@ -711,12 +642,12 @@ describe('Counter', () => { - ); + ) } - const { container } = render(); - expectAllZeroes(container); - clickButton(container, buttonSelector); - return getTextContents(container, atomValueSelectors).join(''); + const { container } = render() + expectAllZeroes(container) + clickButton(container, buttonSelector) + return getTextContents(container, atomValueSelectors).join('') } function expectAllZeroes(container: HTMLElement) { expect(getTextContents(container, atomValueSelectors).join('')).toEqual( @@ -743,7 +674,7 @@ describe('Counter', () => { '0', // valueB1 '0', // valueC2 ].join(''), - ); + ) } const atomValueSelectors = [ '.level0.baseA', @@ -764,13 +695,13 @@ describe('Counter', () => { '.level2.valueA', '.level2.valueB', '.level2.valueC', - ]; - const increaseLevel0BaseA = '.level0.increaseBase'; - const increaseLevel1BaseB = '.level1.increaseBase'; - const increaseLevel2BaseC = '.level2.increaseBase'; - const increaseLevel0All = '.level0.increaseAll'; - const increaseLevel1All = '.level1.increaseAll'; - const increaseLevel2All = '.level2.increaseAll'; + ] + const increaseLevel0BaseA = '.level0.increaseBase' + const increaseLevel1BaseB = '.level1.increaseBase' + const increaseLevel2BaseC = '.level2.increaseBase' + const increaseLevel0All = '.level0.increaseAll' + const increaseLevel1All = '.level1.increaseAll' + const increaseLevel2All = '.level2.increaseAll' expect(clickButtonGetResults(increaseLevel0BaseA)).toEqual( [ @@ -796,7 +727,7 @@ describe('Counter', () => { '0', // valueB1 '0', // valueC2 ].join(''), - ); + ) expect(clickButtonGetResults(increaseLevel1BaseB)).toEqual( [ @@ -822,7 +753,7 @@ describe('Counter', () => { '1', // valueB1 '0', // valueC2 ].join(''), - ); + ) expect(clickButtonGetResults(increaseLevel2BaseC)).toEqual( [ @@ -848,7 +779,7 @@ describe('Counter', () => { '0', // valueB1 '1', // valueC2 ].join(''), - ); + ) expect(clickButtonGetResults(increaseLevel0All)).toEqual( [ @@ -874,7 +805,7 @@ describe('Counter', () => { '0', // valueB1 '0', // valueC2 ].join(''), - ); + ) expect(clickButtonGetResults(increaseLevel1All)).toEqual( [ @@ -900,7 +831,7 @@ describe('Counter', () => { '1', // valueB1 '0', // valueC2 ].join(''), - ); + ) expect(clickButtonGetResults(increaseLevel2All)).toEqual( [ @@ -926,8 +857,8 @@ describe('Counter', () => { '1', // valueB1 '1', // valueC2 ].join(''), - ); - }); + ) + }) /* baseA, baseB, derived(baseA + baseB) @@ -935,11 +866,11 @@ describe('Counter', () => { S2[baseB]: derived1(baseA1 + baseB2) */ test('10. inherited scoped derived atoms can read and write to scoped primitive atoms at every nested level', () => { - const baseAAtom = atomWithReducer(0, (v) => v + 1); - baseAAtom.debugLabel = 'baseA'; + const baseAAtom = atomWithReducer(0, (v) => v + 1) + baseAAtom.debugLabel = 'baseA' - const baseBAtom = atomWithReducer(0, (v) => v + 1); - baseBAtom.debugLabel = 'baseB'; + const baseBAtom = atomWithReducer(0, (v) => v + 1) + baseBAtom.debugLabel = 'baseB' const derivedAtom = atom( (get) => ({ @@ -947,27 +878,23 @@ describe('Counter', () => { baseB: get(baseBAtom), }), (_get, set) => { - set(baseAAtom); - set(baseBAtom); + set(baseAAtom) + set(baseBAtom) }, - ); - derivedAtom.debugLabel = 'derived'; + ) + derivedAtom.debugLabel = 'derived' function Counter({ level }: { level: string }) { - const [{ baseA, baseB }, increaseAll] = useAtom(derivedAtom); + const [{ baseA, baseB }, increaseAll] = useAtom(derivedAtom) return (
baseA:{baseA} baseB:{baseB} -
- ); + ) } function App() { @@ -982,18 +909,13 @@ describe('Counter', () => { - ); + ) } - const { container } = render(); + const { container } = render() - const increaseLevel1All = '.level1.increaseAll'; - const increaseLevel2All = '.level2.increaseAll'; - const atomValueSelectors = [ - '.level1.baseA', - '.level1.baseB', - '.level2.baseA', - '.level2.baseB', - ]; + const increaseLevel1All = '.level1.increaseAll' + const increaseLevel2All = '.level2.increaseAll' + const atomValueSelectors = ['.level1.baseA', '.level1.baseB', '.level2.baseA', '.level2.baseB'] /* baseA, baseB, derived(baseA + baseB) @@ -1005,32 +927,32 @@ describe('Counter', () => { '0', // level1 baseB1 '0', // level2 baseA1 '0', // level2 baseB2 - ]); + ]) /* baseA, baseB, derived(baseA + baseB) S1[baseB, derived]: derived1(baseA1 + baseB1) S2[baseB]: derived1(baseA1 + baseB2) */ - clickButton(container, increaseLevel1All); + clickButton(container, increaseLevel1All) expect(getTextContents(container, atomValueSelectors)).toEqual([ '1', // level1 baseA1 '1', // level1 baseB1 '1', // level2 baseA1 '0', // level2 baseB2 - ]); + ]) /* baseA, baseB, derived(baseA + baseB) S1[baseB, derived]: derived1(baseA1 + baseB1) S2[baseB]: derived1(baseA1 + baseB2) */ - clickButton(container, increaseLevel2All); + clickButton(container, increaseLevel2All) expect(getTextContents(container, atomValueSelectors)).toEqual([ '2', // level1 baseA1 '1', // level1 baseB1 '2', // level2 baseA1 '1', // level2 baseB2 - ]); - }); -}); + ]) + }) +}) diff --git a/__tests__/ScopeProvider/02_removeScope.tsx b/__tests__/ScopeProvider/02_removeScope.tsx index de4b8d0..0f7c8f1 100644 --- a/__tests__/ScopeProvider/02_removeScope.tsx +++ b/__tests__/ScopeProvider/02_removeScope.tsx @@ -1,17 +1,17 @@ -import { render } from '@testing-library/react'; -import type { PropsWithChildren } from 'react'; -import { atom, useAtom, useAtomValue } from 'jotai'; -import { atomWithReducer } from 'jotai/vanilla/utils'; -import { ScopeProvider } from '../../src/index'; -import { clickButton, getTextContents } from '../utils'; +import { render } from '@testing-library/react' +import type { PropsWithChildren } from 'react' +import { atom, useAtom, useAtomValue } from 'jotai' +import { atomWithReducer } from 'jotai/vanilla/utils' +import { ScopeProvider } from '../../src/index' +import { clickButton, getTextContents } from '../utils' -const baseAtom1 = atomWithReducer(0, (v) => v + 1); -const baseAtom2 = atomWithReducer(0, (v) => v + 1); -const shouldHaveScopeAtom = atom(true); +const baseAtom1 = atomWithReducer(0, (v) => v + 1) +const baseAtom2 = atomWithReducer(0, (v) => v + 1) +const shouldHaveScopeAtom = atom(true) function Counter({ counterClass }: { counterClass: string }) { - const [base1, increaseBase1] = useAtom(baseAtom1); - const [base2, increaseBase2] = useAtom(baseAtom2); + const [base1, increaseBase1] = useAtom(baseAtom1) + const [base2, increaseBase2] = useAtom(baseAtom2) return ( <>
@@ -35,29 +35,21 @@ function Counter({ counterClass }: { counterClass: string }) {
- ); + ) } function Wrapper({ children }: PropsWithChildren) { - const shouldHaveScope = useAtomValue(shouldHaveScopeAtom); - return shouldHaveScope ? ( - {children} - ) : ( - children - ); + const shouldHaveScope = useAtomValue(shouldHaveScopeAtom) + return shouldHaveScope ? {children} : children } function ScopeButton() { - const [shouldHaveScope, setShouldHaveScope] = useAtom(shouldHaveScopeAtom); + const [shouldHaveScope, setShouldHaveScope] = useAtom(shouldHaveScopeAtom) return ( - - ); + ) } function App() { @@ -71,137 +63,67 @@ function App() { - ); + ) } describe('Counter', () => { test('atom get correct value when ScopeProvider is added/removed', () => { - const { container } = render(); - const increaseUnscopedBase1 = '.unscoped.setBase1'; - const increaseUnscopedBase2 = '.unscoped.setBase2'; - const increaseScopedBase1 = '.scoped.setBase1'; - const increaseScopedBase2 = '.scoped.setBase2'; - const toggleScope = '#toggleScope'; + const { container } = render() + const increaseUnscopedBase1 = '.unscoped.setBase1' + const increaseUnscopedBase2 = '.unscoped.setBase2' + const increaseScopedBase1 = '.scoped.setBase1' + const increaseScopedBase2 = '.scoped.setBase2' + const toggleScope = '#toggleScope' const atomValueSelectors = [ '.unscoped.base1', '.unscoped.base2', '.scoped.base1', '.scoped.base2', - ]; - - expect(getTextContents(container, atomValueSelectors)).toEqual([ - '0', - '0', - '0', - '0', - ]); - - clickButton(container, increaseUnscopedBase1); - expect(getTextContents(container, atomValueSelectors)).toEqual([ - '1', - '0', - '1', - '0', - ]); - - clickButton(container, increaseUnscopedBase2); - expect(getTextContents(container, atomValueSelectors)).toEqual([ - '1', - '1', - '1', - '0', - ]); - - clickButton(container, increaseScopedBase1); - expect(getTextContents(container, atomValueSelectors)).toEqual([ - '2', - '1', - '2', - '0', - ]); - - clickButton(container, increaseScopedBase2); - clickButton(container, increaseScopedBase2); - clickButton(container, increaseScopedBase2); - - expect(getTextContents(container, atomValueSelectors)).toEqual([ - '2', - '1', - '2', - '3', - ]); - - clickButton(container, toggleScope); - expect(getTextContents(container, atomValueSelectors)).toEqual([ - '2', - '1', - '2', - '1', - ]); - - clickButton(container, increaseUnscopedBase1); - expect(getTextContents(container, atomValueSelectors)).toEqual([ - '3', - '1', - '3', - '1', - ]); - - clickButton(container, increaseUnscopedBase2); - expect(getTextContents(container, atomValueSelectors)).toEqual([ - '3', - '2', - '3', - '2', - ]); - - clickButton(container, increaseScopedBase1); - expect(getTextContents(container, atomValueSelectors)).toEqual([ - '4', - '2', - '4', - '2', - ]); - - clickButton(container, increaseScopedBase2); - expect(getTextContents(container, atomValueSelectors)).toEqual([ - '4', - '3', - '4', - '3', - ]); - - clickButton(container, toggleScope); - expect(getTextContents(container, atomValueSelectors)).toEqual([ - '4', - '3', - '4', - '0', - ]); - - clickButton(container, increaseScopedBase2); - expect(getTextContents(container, atomValueSelectors)).toEqual([ - '4', - '3', - '4', - '1', - ]); - - clickButton(container, increaseScopedBase2); - expect(getTextContents(container, atomValueSelectors)).toEqual([ - '4', - '3', - '4', - '2', - ]); - - clickButton(container, increaseScopedBase2); - expect(getTextContents(container, atomValueSelectors)).toEqual([ - '4', - '3', - '4', - '3', - ]); - }); -}); + ] + + expect(getTextContents(container, atomValueSelectors)).toEqual(['0', '0', '0', '0']) + + clickButton(container, increaseUnscopedBase1) + expect(getTextContents(container, atomValueSelectors)).toEqual(['1', '0', '1', '0']) + + clickButton(container, increaseUnscopedBase2) + expect(getTextContents(container, atomValueSelectors)).toEqual(['1', '1', '1', '0']) + + clickButton(container, increaseScopedBase1) + expect(getTextContents(container, atomValueSelectors)).toEqual(['2', '1', '2', '0']) + + clickButton(container, increaseScopedBase2) + clickButton(container, increaseScopedBase2) + clickButton(container, increaseScopedBase2) + + expect(getTextContents(container, atomValueSelectors)).toEqual(['2', '1', '2', '3']) + + clickButton(container, toggleScope) + expect(getTextContents(container, atomValueSelectors)).toEqual(['2', '1', '2', '1']) + + clickButton(container, increaseUnscopedBase1) + expect(getTextContents(container, atomValueSelectors)).toEqual(['3', '1', '3', '1']) + + clickButton(container, increaseUnscopedBase2) + expect(getTextContents(container, atomValueSelectors)).toEqual(['3', '2', '3', '2']) + + clickButton(container, increaseScopedBase1) + expect(getTextContents(container, atomValueSelectors)).toEqual(['4', '2', '4', '2']) + + clickButton(container, increaseScopedBase2) + expect(getTextContents(container, atomValueSelectors)).toEqual(['4', '3', '4', '3']) + + clickButton(container, toggleScope) + expect(getTextContents(container, atomValueSelectors)).toEqual(['4', '3', '4', '0']) + + clickButton(container, increaseScopedBase2) + expect(getTextContents(container, atomValueSelectors)).toEqual(['4', '3', '4', '1']) + + clickButton(container, increaseScopedBase2) + expect(getTextContents(container, atomValueSelectors)).toEqual(['4', '3', '4', '2']) + + clickButton(container, increaseScopedBase2) + expect(getTextContents(container, atomValueSelectors)).toEqual(['4', '3', '4', '3']) + }) +}) diff --git a/__tests__/ScopeProvider/03_nested.tsx b/__tests__/ScopeProvider/03_nested.tsx index deb0b2a..36e8562 100644 --- a/__tests__/ScopeProvider/03_nested.tsx +++ b/__tests__/ScopeProvider/03_nested.tsx @@ -1,25 +1,25 @@ -import { render } from '@testing-library/react'; -import { atom, useAtom, useAtomValue, useSetAtom } from 'jotai'; -import { atomWithReducer } from 'jotai/vanilla/utils'; -import { clickButton, getTextContents } from '../utils'; +import { render } from '@testing-library/react' +import { atom, useAtom, useAtomValue, useSetAtom } from 'jotai' +import { atomWithReducer } from 'jotai/vanilla/utils' +import { clickButton, getTextContents } from '../utils' -import { ScopeProvider } from '../../src/index'; +import { ScopeProvider } from '../../src/index' -const baseAtom1 = atomWithReducer(0, (v) => v + 1); -const baseAtom2 = atomWithReducer(0, (v) => v + 1); -const baseAtom = atom(0); +const baseAtom1 = atomWithReducer(0, (v) => v + 1) +const baseAtom2 = atomWithReducer(0, (v) => v + 1) +const baseAtom = atom(0) const writeProxyAtom = atom('unused', (get, set) => { - set(baseAtom, get(baseAtom) + 1); - set(baseAtom1); - set(baseAtom2); -}); + set(baseAtom, get(baseAtom) + 1) + set(baseAtom1) + set(baseAtom2) +}) function Counter({ counterClass }: { counterClass: string }) { - const [base1, increaseBase1] = useAtom(baseAtom1); - const [base2, increaseBase2] = useAtom(baseAtom2); - const base = useAtomValue(baseAtom); - const increaseAll = useSetAtom(writeProxyAtom); + const [base1, increaseBase1] = useAtom(baseAtom1) + const [base2, increaseBase2] = useAtom(baseAtom2) + const base = useAtomValue(baseAtom) + const increaseAll = useSetAtom(writeProxyAtom) return ( <>
@@ -45,15 +45,11 @@ function Counter({ counterClass }: { counterClass: string }) {
base: {base}
- - ); + ) } function App() { @@ -66,16 +62,13 @@ function App() {

Layer 2: Scope base 2

-

- base 1 should be shared between layer 1 and layer 2, base should be - globally shared -

+

base 1 should be shared between layer 1 and layer 2, base should be globally shared

- ); + ) } describe('Counter', () => { @@ -85,16 +78,16 @@ describe('Counter', () => { S2[baseB]: baseA1 baseB2 baseC0 */ test('nested primitive atoms are correctly scoped', () => { - const { container } = render(); - const increaseUnscopedBase1 = '.unscoped.setBase1'; - const increaseUnscopedBase2 = '.unscoped.setBase2'; - const increaseAllUnscoped = '.unscoped.setAll'; - const increaseLayer1Base1 = '.layer1.setBase1'; - const increaseLayer1Base2 = '.layer1.setBase2'; - const increaseAllLayer1 = '.layer1.setAll'; - const increaseLayer2Base1 = '.layer2.setBase1'; - const increaseLayer2Base2 = '.layer2.setBase2'; - const increaseAllLayer2 = '.layer2.setAll'; + const { container } = render() + const increaseUnscopedBase1 = '.unscoped.setBase1' + const increaseUnscopedBase2 = '.unscoped.setBase2' + const increaseAllUnscoped = '.unscoped.setAll' + const increaseLayer1Base1 = '.layer1.setBase1' + const increaseLayer1Base2 = '.layer1.setBase2' + const increaseAllLayer1 = '.layer1.setAll' + const increaseLayer2Base1 = '.layer2.setBase1' + const increaseLayer2Base2 = '.layer2.setBase2' + const increaseAllLayer2 = '.layer2.setAll' const atomValueSelectors = [ '.unscoped.base1', @@ -106,7 +99,7 @@ describe('Counter', () => { '.layer2.base1', '.layer2.base2', '.layer2.base', - ]; + ] expect(getTextContents(container, atomValueSelectors)).toEqual([ '0', @@ -118,9 +111,9 @@ describe('Counter', () => { '0', '0', '0', - ]); + ]) - clickButton(container, increaseUnscopedBase1); + clickButton(container, increaseUnscopedBase1) expect(getTextContents(container, atomValueSelectors)).toEqual([ '1', '0', @@ -131,9 +124,9 @@ describe('Counter', () => { '0', '0', '0', - ]); + ]) - clickButton(container, increaseUnscopedBase2); + clickButton(container, increaseUnscopedBase2) expect(getTextContents(container, atomValueSelectors)).toEqual([ '1', '1', @@ -144,9 +137,9 @@ describe('Counter', () => { '0', '0', '0', - ]); + ]) - clickButton(container, increaseAllUnscoped); + clickButton(container, increaseAllUnscoped) expect(getTextContents(container, atomValueSelectors)).toEqual([ '2', '2', @@ -157,9 +150,9 @@ describe('Counter', () => { '0', '0', '1', - ]); + ]) - clickButton(container, increaseLayer1Base1); + clickButton(container, increaseLayer1Base1) expect(getTextContents(container, atomValueSelectors)).toEqual([ '2', '2', @@ -170,9 +163,9 @@ describe('Counter', () => { '1', '0', '1', - ]); + ]) - clickButton(container, increaseLayer1Base2); + clickButton(container, increaseLayer1Base2) expect(getTextContents(container, atomValueSelectors)).toEqual([ '2', '3', @@ -183,9 +176,9 @@ describe('Counter', () => { '1', '0', '1', - ]); + ]) - clickButton(container, increaseAllLayer1); + clickButton(container, increaseAllLayer1) expect(getTextContents(container, atomValueSelectors)).toEqual([ '2', '4', @@ -196,9 +189,9 @@ describe('Counter', () => { '2', '0', '2', - ]); + ]) - clickButton(container, increaseLayer2Base1); + clickButton(container, increaseLayer2Base1) expect(getTextContents(container, atomValueSelectors)).toEqual([ '2', '4', @@ -209,9 +202,9 @@ describe('Counter', () => { '3', '0', '2', - ]); + ]) - clickButton(container, increaseLayer2Base2); + clickButton(container, increaseLayer2Base2) expect(getTextContents(container, atomValueSelectors)).toEqual([ '2', '4', @@ -222,9 +215,9 @@ describe('Counter', () => { '3', '1', '2', - ]); + ]) - clickButton(container, increaseAllLayer2); + clickButton(container, increaseAllLayer2) expect(getTextContents(container, atomValueSelectors)).toEqual([ '2', '4', @@ -235,6 +228,6 @@ describe('Counter', () => { '4', '2', '3', - ]); - }); -}); + ]) + }) +}) diff --git a/__tests__/ScopeProvider/04_derived.tsx b/__tests__/ScopeProvider/04_derived.tsx index ae0fc31..a728bc2 100644 --- a/__tests__/ScopeProvider/04_derived.tsx +++ b/__tests__/ScopeProvider/04_derived.tsx @@ -1,7 +1,7 @@ -import { render } from '@testing-library/react'; -import { atom, useAtom } from 'jotai'; -import { clickButton, getTextContents } from '../utils'; -import { ScopeProvider } from '../../src/index'; +import { render } from '@testing-library/react' +import { atom, useAtom } from 'jotai' +import { clickButton, getTextContents } from '../utils' +import { ScopeProvider } from '../../src/index' const atomValueSelectors = [ '.case1.base', @@ -16,28 +16,28 @@ const atomValueSelectors = [ '.layer2.base', '.layer2.derivedA', '.layer2.derivedB', -]; +] function clickButtonGetResults(buttonSelector: string) { - const baseAtom = atom(0); + const baseAtom = atom(0) const derivedAtomA = atom( (get) => get(baseAtom), (get, set) => { - set(baseAtom, get(baseAtom) + 1); + set(baseAtom, get(baseAtom) + 1) }, - ); + ) const derivedAtomB = atom( (get) => get(baseAtom), (get, set) => { - set(baseAtom, get(baseAtom) + 1); + set(baseAtom, get(baseAtom) + 1) }, - ); + ) function Counter({ counterClass }: { counterClass: string }) { - const [base, setBase] = useAtom(baseAtom); - const [derivedA, setDerivedA] = useAtom(derivedAtomA); - const [derivedB, setDerivedB] = useAtom(derivedAtomB); + const [base, setBase] = useAtom(baseAtom) + const [derivedA, setDerivedA] = useAtom(derivedAtomA) + const [derivedB, setDerivedB] = useAtom(derivedAtomB) return ( <>
@@ -73,7 +73,7 @@ function clickButtonGetResults(buttonSelector: string) {
- ); + ) } function App() { @@ -94,22 +94,19 @@ function clickButtonGetResults(buttonSelector: string) {

Layer2: Base and derivedB are scoped

-

- derivedA should use layer2's atom, base and derivedB are layer - 2 scoped -

+

derivedA should use layer2's atom, base and derivedB are layer 2 scoped

- ); + ) } - const { container } = render(); - expectAllZeroes(container); - clickButton(container, buttonSelector); - return getTextContents(container, atomValueSelectors); + const { container } = render() + expectAllZeroes(container) + clickButton(container, buttonSelector) + return getTextContents(container, atomValueSelectors) } function expectAllZeroes(container: HTMLElement) { @@ -133,23 +130,23 @@ function expectAllZeroes(container: HTMLElement) { '0', // base '0', // derivedA '0', // derivedB - ]); + ]) } describe('Counter', () => { test("parent scope's derived atom is prior to nested scope's scoped base", () => { - const increaseCase1Base = '.case1.setBase'; - const increaseCase1DerivedA = '.case1.setDerivedA'; - const increaseCase1DerivedB = '.case1.setDerivedB'; - const increaseCase2Base = '.case2.setBase'; - const increaseCase2DerivedA = '.case2.setDerivedA'; - const increaseCase2DerivedB = '.case2.setDerivedB'; - const increaseLayer1Base = '.layer1.setBase'; - const increaseLayer1DerivedA = '.layer1.setDerivedA'; - const increaseLayer1DerivedB = '.layer1.setDerivedB'; - const increaseLayer2Base = '.layer2.setBase'; - const increaseLayer2DerivedA = '.layer2.setDerivedA'; - const increaseLayer2DerivedB = '.layer2.setDerivedB'; + const increaseCase1Base = '.case1.setBase' + const increaseCase1DerivedA = '.case1.setDerivedA' + const increaseCase1DerivedB = '.case1.setDerivedB' + const increaseCase2Base = '.case2.setBase' + const increaseCase2DerivedA = '.case2.setDerivedA' + const increaseCase2DerivedB = '.case2.setDerivedB' + const increaseLayer1Base = '.layer1.setBase' + const increaseLayer1DerivedA = '.layer1.setDerivedA' + const increaseLayer1DerivedB = '.layer1.setDerivedB' + const increaseLayer2Base = '.layer2.setBase' + const increaseLayer2DerivedA = '.layer2.setDerivedA' + const increaseLayer2DerivedB = '.layer2.setDerivedB' /* base, derivedA(base), derivedB(base) @@ -178,7 +175,7 @@ describe('Counter', () => { '0', // base '0', // derivedA '0', // derivedB - ]); + ]) /* base, derivedA(base), derivedB(base) @@ -207,7 +204,7 @@ describe('Counter', () => { '0', // base '0', // derivedA '0', // derivedB - ]); + ]) /* base, derivedA(base), derivedB(base) @@ -236,7 +233,7 @@ describe('Counter', () => { '0', // base '0', // derivedA '0', // derivedB - ]); + ]) /* base, derivedA(base), derivedB(base) @@ -265,7 +262,7 @@ describe('Counter', () => { '0', // base '0', // derivedA '0', // derivedB - ]); + ]) /* base, derivedA(base), derivedB(base) @@ -294,7 +291,7 @@ describe('Counter', () => { '0', // base actual: 0, '0', // derivedA actual: 0, '0', // derivedB actual: 0 - ]); + ]) /* base, derivedA(base), derivedB(base) @@ -323,7 +320,7 @@ describe('Counter', () => { '0', // base '0', // derivedA '0', // derivedB - ]); + ]) /* base, derivedA(base), derivedB(base) @@ -352,7 +349,7 @@ describe('Counter', () => { '0', // base '0', // derivedA '0', // derivedB - ]); + ]) /* base, derivedA(base), derivedB(base) @@ -381,7 +378,7 @@ describe('Counter', () => { '0', // base '0', // derivedA '0', // derivedB - ]); + ]) /* base, derivedA(base), derivedB(base) @@ -410,7 +407,7 @@ describe('Counter', () => { '0', // base '0', // derivedA '0', // derivedB - ]); + ]) /* base, derivedA(base), derivedB(base) @@ -439,7 +436,7 @@ describe('Counter', () => { '1', // base '1', // derivedA '1', // derivedB - ]); + ]) /* base, derivedA(base), derivedB(base) @@ -468,7 +465,7 @@ describe('Counter', () => { '1', // base '1', // derivedA '1', // derivedB - ]); + ]) /* base, derivedA(base), derivedB(base) @@ -497,6 +494,6 @@ describe('Counter', () => { '1', // base '1', // derivedA '1', // derivedB - ]); - }); -}); + ]) + }) +}) diff --git a/__tests__/ScopeProvider/05_derived_self.tsx b/__tests__/ScopeProvider/05_derived_self.tsx index d0dcdcb..0f8e190 100644 --- a/__tests__/ScopeProvider/05_derived_self.tsx +++ b/__tests__/ScopeProvider/05_derived_self.tsx @@ -1,33 +1,27 @@ -import { render } from '@testing-library/react'; -import { atom, useAtom } from 'jotai'; -import { useHydrateAtoms } from 'jotai/utils'; -import { getTextContents } from '../utils'; -import { ScopeProvider } from '../../src/index'; +import { render } from '@testing-library/react' +import { atom, useAtom } from 'jotai' +import { useHydrateAtoms } from 'jotai/utils' +import { getTextContents } from '../utils' +import { ScopeProvider } from '../../src/index' -const baseAtom = atom(0); +const baseAtom = atom(0) const derivedAtom1 = atom( (get) => get(baseAtom), (get): number => { - return get(derivedAtom1); + return get(derivedAtom1) }, -); +) -function Component({ - className, - initialValue = 0, -}: { - className: string; - initialValue?: number; -}) { - useHydrateAtoms([[baseAtom, initialValue]]); - const [atom1ReadValue, setAtom1Value] = useAtom(derivedAtom1); - const atom1WriteValue = setAtom1Value(); +function Component({ className, initialValue = 0 }: { className: string; initialValue?: number }) { + useHydrateAtoms([[baseAtom, initialValue]]) + const [atom1ReadValue, setAtom1Value] = useAtom(derivedAtom1) + const atom1WriteValue = setAtom1Value() return (
{atom1ReadValue} {atom1WriteValue}
- ); + ) } function App() { @@ -42,7 +36,7 @@ function App() { - ); + ) } describe('Self', () => { @@ -51,13 +45,9 @@ describe('Self', () => { S1[baseA]: baseA1, derivedB0(baseA1, derivedB0) */ test('derived dep scope is preserved in self reference', () => { - const { container } = render(); - expect( - getTextContents(container, ['.unscoped .read', '.unscoped .write']), - ).toEqual(['0', '0']); + const { container } = render() + expect(getTextContents(container, ['.unscoped .read', '.unscoped .write'])).toEqual(['0', '0']) - expect( - getTextContents(container, ['.scoped .read', '.scoped .write']), - ).toEqual(['1', '1']); - }); -}); + expect(getTextContents(container, ['.scoped .read', '.scoped .write'])).toEqual(['1', '1']) + }) +}) diff --git a/__tests__/ScopeProvider/06_implicit_parent.tsx b/__tests__/ScopeProvider/06_implicit_parent.tsx index 1c6fca0..95b1a10 100644 --- a/__tests__/ScopeProvider/06_implicit_parent.tsx +++ b/__tests__/ScopeProvider/06_implicit_parent.tsx @@ -1,35 +1,31 @@ -import type { FC } from 'react'; -import { render } from '@testing-library/react'; -import { atom, useAtom, useAtomValue } from 'jotai'; -import { atomWithReducer } from 'jotai/vanilla/utils'; -import { clickButton, getTextContents } from '../utils'; -import { ScopeProvider } from '../../src/index'; +import type { FC } from 'react' +import { render } from '@testing-library/react' +import { atom, useAtom, useAtomValue } from 'jotai' +import { atomWithReducer } from 'jotai/vanilla/utils' +import { clickButton, getTextContents } from '../utils' +import { ScopeProvider } from '../../src/index' function renderWithOrder(level1: 'BD' | 'DB', level2: 'BD' | 'DB') { - const baseAtom = atomWithReducer(0, (v) => v + 1); - baseAtom.debugLabel = 'baseAtom'; + const baseAtom = atomWithReducer(0, (v) => v + 1) + baseAtom.debugLabel = 'baseAtom' baseAtom.toString = function toString() { - return this.debugLabel ?? 'Unknown Atom'; - }; + return this.debugLabel ?? 'Unknown Atom' + } - const derivedAtom = atom((get) => get(baseAtom)); - derivedAtom.debugLabel = 'derivedAtom'; + const derivedAtom = atom((get) => get(baseAtom)) + derivedAtom.debugLabel = 'derivedAtom' derivedAtom.toString = function toString() { - return this.debugLabel ?? 'Unknown Atom'; - }; + return this.debugLabel ?? 'Unknown Atom' + } function BaseThenDerived({ level }: { level: string }) { - const [base, increaseBase] = useAtom(baseAtom); - const derived = useAtomValue(derivedAtom); + const [base, increaseBase] = useAtom(baseAtom) + const derived = useAtomValue(derivedAtom) return ( <>
base: {base} -
@@ -37,21 +33,17 @@ function renderWithOrder(level1: 'BD' | 'DB', level2: 'BD' | 'DB') { derived:{derived} - ); + ) } function DerivedThenBase({ level }: { level: string }) { - const derived = useAtomValue(derivedAtom); - const [base, increaseBase] = useAtom(baseAtom); + const derived = useAtomValue(derivedAtom) + const [base, increaseBase] = useAtom(baseAtom) return ( <>
base:{base} -
@@ -59,13 +51,13 @@ function renderWithOrder(level1: 'BD' | 'DB', level2: 'BD' | 'DB') { derived:{derived} - ); + ) } function App(props: { - Level1Counter: FC<{ level: string }>; - Level2Counter: FC<{ level: string }>; + Level1Counter: FC<{ level: string }> + Level2Counter: FC<{ level: string }> }) { - const { Level1Counter, Level2Counter } = props; + const { Level1Counter, Level2Counter } = props return (

Layer 1: Scope derived

@@ -79,17 +71,12 @@ function renderWithOrder(level1: 'BD' | 'DB', level2: 'BD' | 'DB') {
- ); + ) } function getCounter(order: 'BD' | 'DB') { - return order === 'BD' ? BaseThenDerived : DerivedThenBase; + return order === 'BD' ? BaseThenDerived : DerivedThenBase } - return render( - , - ); + return render() } /* @@ -103,20 +90,15 @@ describe('Implicit parent does not affect unscoped', () => { ['BD', 'DB'], ['DB', 'BD'], ['DB', 'DB'], - ] as const; + ] as const test.each(cases)('level 1: %p and level 2: %p', (level1, level2) => { - const { container } = renderWithOrder(level1, level2); - const increaseLayer2Base = '.layer2.setBase'; - const selectors = [ - '.layer1.base', - '.layer1.derived', - '.layer2.base', - '.layer2.derived', - ]; + const { container } = renderWithOrder(level1, level2) + const increaseLayer2Base = '.layer2.setBase' + const selectors = ['.layer1.base', '.layer1.derived', '.layer2.base', '.layer2.derived'] - expect(getTextContents(container, selectors).join('')).toEqual('0000'); + expect(getTextContents(container, selectors).join('')).toEqual('0000') - clickButton(container, increaseLayer2Base); - expect(getTextContents(container, selectors).join('')).toEqual('1010'); - }); -}); + clickButton(container, increaseLayer2Base) + expect(getTextContents(container, selectors).join('')).toEqual('1010') + }) +}) diff --git a/__tests__/ScopeProvider/07_writable.tsx b/__tests__/ScopeProvider/07_writable.tsx index 47f493b..b1e4c9f 100644 --- a/__tests__/ScopeProvider/07_writable.tsx +++ b/__tests__/ScopeProvider/07_writable.tsx @@ -1,37 +1,33 @@ -import { render } from '@testing-library/react'; -import { type WritableAtom, type PrimitiveAtom, atom, useAtom } from 'jotai'; -import { clickButton, getTextContents } from '../utils'; -import { ScopeProvider } from '../../src/index'; +import { render } from '@testing-library/react' +import { type WritableAtom, type PrimitiveAtom, atom, useAtom } from 'jotai' +import { clickButton, getTextContents } from '../utils' +import { ScopeProvider } from '../../src/index' -let baseAtom: PrimitiveAtom; +let baseAtom: PrimitiveAtom -type WritableNumberAtom = WritableAtom; +type WritableNumberAtom = WritableAtom const writableAtom: WritableNumberAtom = atom(0, (get, set, value = 0) => { - set(writableAtom, get(writableAtom) + get(baseAtom) + value); -}); + set(writableAtom, get(writableAtom) + get(baseAtom) + value) +}) const thisWritableAtom: WritableNumberAtom = atom( 0, function write(this: WritableNumberAtom, get, set, value = 0) { - set(this, get(this) + get(baseAtom) + value); + set(this, get(this) + get(baseAtom) + value) }, -); +) function renderTest(targetAtom: WritableNumberAtom) { - baseAtom = atom(0); + baseAtom = atom(0) function Component({ level }: { level: string }) { - const [value, increaseWritable] = useAtom(targetAtom); - const [baseValue, increaseBase] = useAtom(baseAtom); + const [value, increaseWritable] = useAtom(targetAtom) + const [baseValue, increaseBase] = useAtom(baseAtom) return (
{value}
{baseValue}
-
- ); + ) } function App() { @@ -53,15 +49,14 @@ function renderTest(targetAtom: WritableNumberAtom) {

scoped

- writable atom should update its value in both scoped and unscoped - and read scoped atom + writable atom should update its value in both scoped and unscoped and read scoped atom

- ); + ) } - return render(); + return render() } /* @@ -73,21 +68,15 @@ describe('Self', () => { test.each(['writableAtom', 'thisWritableAtom'])( '%p updates its value in both scoped and unscoped and read scoped atom', (atomKey) => { - const target = - atomKey === 'writableAtom' ? writableAtom : thisWritableAtom; - const { container } = renderTest(target); - - const increaseLevel0BaseAtom = '.level0 .writeBase'; - const increaseLevel0Writable = '.level0 .write'; - const increaseLevel1BaseAtom = '.level1 .writeBase'; - const increaseLevel1Writable = '.level1 .write'; - - const selectors = [ - '.level0 .readBase', - '.level0 .read', - '.level1 .readBase', - '.level1 .read', - ]; + const target = atomKey === 'writableAtom' ? writableAtom : thisWritableAtom + const { container } = renderTest(target) + + const increaseLevel0BaseAtom = '.level0 .writeBase' + const increaseLevel0Writable = '.level0 .write' + const increaseLevel1BaseAtom = '.level1 .writeBase' + const increaseLevel1Writable = '.level1 .write' + + const selectors = ['.level0 .readBase', '.level0 .read', '.level1 .readBase', '.level1 .read'] // all initial values are zero expect(getTextContents(container, selectors)).toEqual([ @@ -95,63 +84,63 @@ describe('Self', () => { '0', // level0 read '0', // level1 readBase '0', // level1 read - ]); + ]) // level0 base atom updates its value to 1 - clickButton(container, increaseLevel0BaseAtom); + clickButton(container, increaseLevel0BaseAtom) expect(getTextContents(container, selectors)).toEqual([ '1', // level0 readBase '0', // level0 read '0', // level1 readBase '0', // level1 read - ]); + ]) // level0 writable atom increases its value, level1 writable atom shares the same value - clickButton(container, increaseLevel0Writable); + clickButton(container, increaseLevel0Writable) expect(getTextContents(container, selectors)).toEqual([ '1', // level0 readBase '1', // level0 read '0', // level1 readBase '1', // level1 read - ]); + ]) // level1 writable atom increases its value, // but since level1 base atom is zero, // level0 and level1 writable atoms value should not change - clickButton(container, increaseLevel1Writable); + clickButton(container, increaseLevel1Writable) expect(getTextContents(container, selectors)).toEqual([ '1', // level0 readBase '1', // level0 read '0', // level1 readBase '1', // level1 read - ]); + ]) // level1 base atom updates its value to 10 - clickButton(container, increaseLevel1BaseAtom); + clickButton(container, increaseLevel1BaseAtom) expect(getTextContents(container, selectors)).toEqual([ '1', // level0 readBase '1', // level0 read '10', // level1 readBase '1', // level1 read - ]); + ]) // level0 writable atom increases its value using level0 base atom - clickButton(container, increaseLevel0Writable); + clickButton(container, increaseLevel0Writable) expect(getTextContents(container, selectors)).toEqual([ '1', // level0 readBase '2', // level0 read '10', // level1 readBase '2', // level1 read - ]); + ]) // level1 writable atom increases its value using level1 base atom - clickButton(container, increaseLevel1Writable); + clickButton(container, increaseLevel1Writable) expect(getTextContents(container, selectors)).toEqual([ '1', // level0 readBase '12', // level0 read '10', // level1 readBase '12', // level1 read - ]); + ]) }, - ); -}); + ) +}) diff --git a/__tests__/ScopeProvider/08_family.tsx b/__tests__/ScopeProvider/08_family.tsx index 1c93e0b..67b5bcd 100644 --- a/__tests__/ScopeProvider/08_family.tsx +++ b/__tests__/ScopeProvider/08_family.tsx @@ -1,8 +1,8 @@ -import { render, act } from '@testing-library/react'; -import { useAtom, atom, useSetAtom } from 'jotai'; -import { atomFamily, atomWithReducer } from 'jotai/utils'; -import { ScopeProvider } from '../../src/index'; -import { clickButton, getTextContents } from '../utils'; +import { render, act } from '@testing-library/react' +import { useAtom, atom, useSetAtom } from 'jotai' +import { atomFamily, atomWithReducer } from 'jotai/utils' +import { ScopeProvider } from '../../src/index' +import { clickButton, getTextContents } from '../utils' describe('AtomFamily with ScopeProvider', () => { /* @@ -11,13 +11,13 @@ describe('AtomFamily with ScopeProvider', () => { S1[aFamily]: a1 b1 */ test('01. Scoped atom families provide isolated state', () => { - const aFamily = atomFamily(() => atom(0)); - const aAtom = aFamily('a'); - aAtom.debugLabel = 'aAtom'; - const bAtom = aFamily('b'); - bAtom.debugLabel = 'bAtom'; + const aFamily = atomFamily(() => atom(0)) + const aAtom = aFamily('a') + aAtom.debugLabel = 'aAtom' + const bAtom = aFamily('b') + bAtom.debugLabel = 'bAtom' function Counter({ level, param }: { level: string; param: string }) { - const [value, setValue] = useAtom(aFamily(param)); + const [value, setValue] = useAtom(aFamily(param)) return (
{param}:{value} @@ -29,7 +29,7 @@ describe('AtomFamily with ScopeProvider', () => { increase
- ); + ) } function App() { @@ -44,43 +44,43 @@ describe('AtomFamily with ScopeProvider', () => { - ); + ) } - const { container } = render(); - const selectors = ['.level0.a', '.level0.b', '.level1.a', '.level1.b']; + const { container } = render() + const selectors = ['.level0.a', '.level0.b', '.level1.a', '.level1.b'] expect(getTextContents(container, selectors)).toEqual([ '0', // level0 a '0', // level0 b '0', // level1 a '0', // level1 b - ]); + ]) - clickButton(container, '.level0.set-a'); + clickButton(container, '.level0.set-a') expect(getTextContents(container, selectors)).toEqual([ '1', // level0 a '0', // level0 b '0', // level1 a '0', // level1 b - ]); + ]) - clickButton(container, '.level1.set-a'); + clickButton(container, '.level1.set-a') expect(getTextContents(container, selectors)).toEqual([ '1', // level0 a '0', // level0 b '1', // level1 a '0', // level1 b - ]); + ]) - clickButton(container, '.level1.set-b'); + clickButton(container, '.level1.set-b') expect(getTextContents(container, selectors)).toEqual([ '1', // level0 a '0', // level0 b '1', // level1 a '1', // level1 b - ]); - }); + ]) + }) /* aFamily('a'), aFamily.remove('a') @@ -89,14 +89,14 @@ describe('AtomFamily with ScopeProvider', () => { */ // TODO: refactor atomFamily to support descoping removing atoms test.skip('02. Removing atom from atomFamily does not affect scoped state', () => { - const aFamily = atomFamily(() => atom(0)); - const atomA = aFamily('a'); - atomA.debugLabel = 'atomA'; - const rerenderAtom = atomWithReducer(0, (s) => s + 1); - rerenderAtom.debugLabel = 'rerenderAtom'; + const aFamily = atomFamily(() => atom(0)) + const atomA = aFamily('a') + atomA.debugLabel = 'atomA' + const rerenderAtom = atomWithReducer(0, (s) => s + 1) + rerenderAtom.debugLabel = 'rerenderAtom' function Counter({ level, param }: { level: string; param: string }) { - const [value, setValue] = useAtom(atomA); - useAtom(rerenderAtom); + const [value, setValue] = useAtom(atomA) + useAtom(rerenderAtom) return (
{param}:{value} @@ -108,11 +108,11 @@ describe('AtomFamily with ScopeProvider', () => { increase
- ); + ) } function App() { - const rerender = useSetAtom(rerenderAtom); + const rerender = useSetAtom(rerenderAtom) return (

Unscoped

@@ -121,8 +121,8 @@ describe('AtomFamily with ScopeProvider', () => { className="remove-atom" type="button" onClick={() => { - aFamily.remove('a'); - rerender(); + aFamily.remove('a') + rerender() }} > remove a from atomFamily @@ -132,38 +132,38 @@ describe('AtomFamily with ScopeProvider', () => {
- ); + ) } - const { container } = render(); - const selectors = ['.level0.a', '.level1.a']; + const { container } = render() + const selectors = ['.level0.a', '.level1.a'] expect(getTextContents(container, selectors)).toEqual([ '0', // level0 a '0', // level1 a - ]); + ]) - clickButton(container, '.level0.set-a'); + clickButton(container, '.level0.set-a') expect(getTextContents(container, selectors)).toEqual([ '1', // level0 a '0', // level1 a - ]); + ]) act(() => { - clickButton(container, '.remove-atom'); - }); + clickButton(container, '.remove-atom') + }) expect(getTextContents(container, ['.level0.a', '.level1.a'])).toEqual([ '1', // level0 a '1', // level1 a // atomA is now unscoped - ]); + ]) - clickButton(container, '.level1.set-a'); + clickButton(container, '.level1.set-a') expect(getTextContents(container, ['.level0.a', '.level1.a'])).toEqual([ '2', // level0 a '2', // level1 a - ]); - }); + ]) + }) /* aFamily.setShouldRemove((createdAt, param) => param === 'b') @@ -172,17 +172,17 @@ describe('AtomFamily with ScopeProvider', () => { */ // TODO: refactor atomFamily to support descoping removing atoms test.skip('03. Scoped atom families respect custom removal conditions', () => { - const aFamily = atomFamily(() => atom(0)); - const atomA = aFamily('a'); - atomA.debugLabel = 'atomA'; - const atomB = aFamily('b'); - atomB.debugLabel = 'atomB'; - const rerenderAtom = atomWithReducer(0, (s) => s + 1); - rerenderAtom.debugLabel = 'rerenderAtom'; + const aFamily = atomFamily(() => atom(0)) + const atomA = aFamily('a') + atomA.debugLabel = 'atomA' + const atomB = aFamily('b') + atomB.debugLabel = 'atomB' + const rerenderAtom = atomWithReducer(0, (s) => s + 1) + rerenderAtom.debugLabel = 'rerenderAtom' function Counter({ level, param }: { level: string; param: string }) { - const [value, setValue] = useAtom(aFamily(param)); - useAtom(rerenderAtom); + const [value, setValue] = useAtom(aFamily(param)) + useAtom(rerenderAtom) return (
{param}:{value} @@ -194,19 +194,19 @@ describe('AtomFamily with ScopeProvider', () => { increase
- ); + ) } function App() { - const rerender = useSetAtom(rerenderAtom); + const rerender = useSetAtom(rerenderAtom) return (
- ); + ) } - const { container } = render(); - const removeBButton = '.remove-b'; - const selectors = ['.level0.a', '.level0.b', '.level1.a', '.level1.b']; + const { container } = render() + const removeBButton = '.remove-b' + const selectors = ['.level0.a', '.level0.b', '.level1.a', '.level1.b'] expect(getTextContents(container, selectors)).toEqual([ '0', // level0 a '0', // level0 b '0', // level1 a '0', // level1 b - ]); + ]) - clickButton(container, '.level0.set-a'); - clickButton(container, '.level0.set-b'); + clickButton(container, '.level0.set-a') + clickButton(container, '.level0.set-b') expect(getTextContents(container, selectors)).toEqual([ '1', // level0 a '1', // level0 b '0', // level1 a // a is scoped '0', // level1 b // b is scoped - ]); + ]) act(() => { - clickButton(container, removeBButton); - }); + clickButton(container, removeBButton) + }) expect(getTextContents(container, selectors)).toEqual([ '1', // level0 a '1', // level0 b '0', // level1 a // a is still scoped '1', // level1 b // b is no longer scoped - ]); - }); -}); + ]) + }) +}) diff --git a/__tests__/ScopeProvider/09_scope_provider.tsx b/__tests__/ScopeProvider/09_scope_provider.tsx index 4bc35c8..f223c20 100644 --- a/__tests__/ScopeProvider/09_scope_provider.tsx +++ b/__tests__/ScopeProvider/09_scope_provider.tsx @@ -1,26 +1,22 @@ -import { useState } from 'react'; -import { render, act } from '@testing-library/react'; -import { atom, useAtomValue } from 'jotai'; -import { ScopeProvider } from '../../src/index'; -import { clickButton } from '../utils'; +import { useState } from 'react' +import { render, act } from '@testing-library/react' +import { atom, useAtomValue } from 'jotai' +import { ScopeProvider } from '../../src/index' +import { clickButton } from '../utils' describe('ScopeProvider', () => { it('mounts and unmounts successfully', () => { - const baseAtom = atom(0); + const baseAtom = atom(0) function Component() { - const base = useAtomValue(baseAtom); - return
{base}
; + const base = useAtomValue(baseAtom) + return
{base}
} function App() { - const [isMounted, setIsMounted] = useState(false); + const [isMounted, setIsMounted] = useState(false) return ( <>
-
@@ -30,18 +26,18 @@ describe('ScopeProvider', () => { )} - ); + ) } - const { unmount, container } = render(); - const mountButton = '.mount'; - const base = '.base'; + const { unmount, container } = render() + const mountButton = '.mount' + const base = '.base' - act(() => clickButton(container, mountButton)); - expect(container.querySelector(base)).not.toBe(null); - act(() => clickButton(container, mountButton)); - expect(container.querySelector(base)).toBe(null); - act(() => clickButton(container, mountButton)); - unmount(); - expect(container.querySelector(base)).toBe(null); - }); -}); + act(() => clickButton(container, mountButton)) + expect(container.querySelector(base)).not.toBe(null) + act(() => clickButton(container, mountButton)) + expect(container.querySelector(base)).toBe(null) + act(() => clickButton(container, mountButton)) + unmount() + expect(container.querySelector(base)).toBe(null) + }) +}) diff --git a/__tests__/createIsolation/01_basic_spec.tsx b/__tests__/createIsolation/01_basic_spec.tsx index db63706..f7f96da 100644 --- a/__tests__/createIsolation/01_basic_spec.tsx +++ b/__tests__/createIsolation/01_basic_spec.tsx @@ -1,7 +1,7 @@ -import { createIsolation } from '../../src/index'; +import { createIsolation } from '../../src/index' describe('basic spec', () => { it('should export functions', () => { - expect(createIsolation).toBeDefined(); - }); -}); + expect(createIsolation).toBeDefined() + }) +}) diff --git a/__tests__/utils.ts b/__tests__/utils.ts index 4a68e3c..d08ffa0 100644 --- a/__tests__/utils.ts +++ b/__tests__/utils.ts @@ -1,31 +1,23 @@ -import { fireEvent } from '@testing-library/react'; +import { fireEvent } from '@testing-library/react' -function getElements( - container: HTMLElement, - querySelectors: string[], -): Element[] { +function getElements(container: HTMLElement, querySelectors: string[]): Element[] { return querySelectors.map((querySelector) => { - const element = container.querySelector(querySelector); + const element = container.querySelector(querySelector) if (!element) { - throw new Error(`Element not found: ${querySelector}`); + throw new Error(`Element not found: ${querySelector}`) } - return element; - }); + return element + }) } -export function getTextContents( - container: HTMLElement, - selectors: string[], -): string[] { - return getElements(container, selectors).map( - (element) => element.textContent!, - ); +export function getTextContents(container: HTMLElement, selectors: string[]): string[] { + return getElements(container, selectors).map((element) => element.textContent!) } export function clickButton(container: HTMLElement, querySelector: string) { - const button = container.querySelector(querySelector); + const button = container.querySelector(querySelector) if (!button) { - throw new Error(`Button not found: ${querySelector}`); + throw new Error(`Button not found: ${querySelector}`) } - fireEvent.click(button); + fireEvent.click(button) } diff --git a/examples/01_isolation/src/App.tsx b/examples/01_isolation/src/App.tsx index 6480734..5456561 100644 --- a/examples/01_isolation/src/App.tsx +++ b/examples/01_isolation/src/App.tsx @@ -1,12 +1,12 @@ -import { atom, useAtom } from 'jotai'; -import { createIsolation } from 'jotai-scope'; +import { atom, useAtom } from 'jotai' +import { createIsolation } from 'jotai-scope' -const { Provider: MyProvider, useAtom: useMyAtom } = createIsolation(); +const { Provider: MyProvider, useAtom: useMyAtom } = createIsolation() -const countAtom = atom(0); +const countAtom = atom(0) function Counter() { - const [count, setCount] = useAtom(countAtom); + const [count, setCount] = useAtom(countAtom) return (
count: {count} @@ -14,11 +14,11 @@ function Counter() { increment
- ); + ) } function MyCounter() { - const [count, setCount] = useMyAtom(countAtom); + const [count, setCount] = useMyAtom(countAtom) return (
scoped count: {count} @@ -26,7 +26,7 @@ function MyCounter() { increment
- ); + ) } function App() { @@ -43,7 +43,7 @@ function App() { - ); + ) } -export default App; +export default App diff --git a/examples/01_isolation/src/index.tsx b/examples/01_isolation/src/index.tsx index 55b74de..ea17df4 100644 --- a/examples/01_isolation/src/index.tsx +++ b/examples/01_isolation/src/index.tsx @@ -1,8 +1,8 @@ -import { createRoot } from 'react-dom/client'; +import { createRoot } from 'react-dom/client' -import App from './App'; +import App from './App' -const ele = document.getElementById('app'); +const ele = document.getElementById('app') if (ele) { - createRoot(ele).render(); + createRoot(ele).render() } diff --git a/examples/02_removeScope/src/App.tsx b/examples/02_removeScope/src/App.tsx index b8c4df1..913783a 100644 --- a/examples/02_removeScope/src/App.tsx +++ b/examples/02_removeScope/src/App.tsx @@ -1,15 +1,15 @@ -import { atom, useAtom, useAtomValue } from 'jotai'; -import { ScopeProvider } from 'jotai-scope'; -import { atomWithReducer } from 'jotai/vanilla/utils'; -import { PropsWithChildren } from 'react'; +import { atom, useAtom, useAtomValue } from 'jotai' +import { ScopeProvider } from 'jotai-scope' +import { atomWithReducer } from 'jotai/vanilla/utils' +import { PropsWithChildren } from 'react' -const baseAtom1 = atomWithReducer(0, (v) => v + 1); -const baseAtom2 = atomWithReducer(0, (v) => v + 1); -const shouldHaveScopeAtom = atom(true); +const baseAtom1 = atomWithReducer(0, (v) => v + 1) +const baseAtom2 = atomWithReducer(0, (v) => v + 1) +const shouldHaveScopeAtom = atom(true) function Counter({ counterClass }: { counterClass: string }) { - const [base1, increaseBase1] = useAtom(baseAtom1); - const [base2, increaseBase2] = useAtom(baseAtom2); + const [base1, increaseBase1] = useAtom(baseAtom1) + const [base2, increaseBase2] = useAtom(baseAtom2) return ( <>
@@ -33,29 +33,21 @@ function Counter({ counterClass }: { counterClass: string }) {
- ); + ) } function Wrapper({ children }: PropsWithChildren) { - const shouldHaveScope = useAtomValue(shouldHaveScopeAtom); - return shouldHaveScope ? ( - {children} - ) : ( - children - ); + const shouldHaveScope = useAtomValue(shouldHaveScopeAtom) + return shouldHaveScope ? {children} : children } function ScopeButton() { - const [shouldHaveScope, setShouldHaveScope] = useAtom(shouldHaveScopeAtom); + const [shouldHaveScope, setShouldHaveScope] = useAtom(shouldHaveScopeAtom) return ( - - ); + ) } function App() { @@ -69,7 +61,7 @@ function App() { - ); + ) } -export default App; +export default App diff --git a/examples/02_removeScope/src/index.tsx b/examples/02_removeScope/src/index.tsx index 55b74de..ea17df4 100644 --- a/examples/02_removeScope/src/index.tsx +++ b/examples/02_removeScope/src/index.tsx @@ -1,8 +1,8 @@ -import { createRoot } from 'react-dom/client'; +import { createRoot } from 'react-dom/client' -import App from './App'; +import App from './App' -const ele = document.getElementById('app'); +const ele = document.getElementById('app') if (ele) { - createRoot(ele).render(); + createRoot(ele).render() } diff --git a/examples/03_nested/src/App.tsx b/examples/03_nested/src/App.tsx index 858fcb2..88b82fa 100644 --- a/examples/03_nested/src/App.tsx +++ b/examples/03_nested/src/App.tsx @@ -1,22 +1,22 @@ -import { atom, useAtom, useAtomValue, useSetAtom } from 'jotai'; -import { ScopeProvider } from 'jotai-scope'; -import { atomWithReducer } from 'jotai/vanilla/utils'; +import { atom, useAtom, useAtomValue, useSetAtom } from 'jotai' +import { ScopeProvider } from 'jotai-scope' +import { atomWithReducer } from 'jotai/vanilla/utils' -const baseAtom1 = atomWithReducer(0, (v) => v + 1); -const baseAtom2 = atomWithReducer(0, (v) => v + 1); -const baseAtom = atom(0); +const baseAtom1 = atomWithReducer(0, (v) => v + 1) +const baseAtom2 = atomWithReducer(0, (v) => v + 1) +const baseAtom = atom(0) const writeProxyAtom = atom('unused', (get, set) => { - set(baseAtom, get(baseAtom) + 1); - set(baseAtom1); - set(baseAtom2); -}); + set(baseAtom, get(baseAtom) + 1) + set(baseAtom1) + set(baseAtom2) +}) function Counter({ counterClass }: { counterClass: string }) { - const [base1, increaseBase1] = useAtom(baseAtom1); - const [base2, increaseBase2] = useAtom(baseAtom2); - const base = useAtomValue(baseAtom); - const increaseAll = useSetAtom(writeProxyAtom); + const [base1, increaseBase1] = useAtom(baseAtom1) + const [base2, increaseBase2] = useAtom(baseAtom2) + const base = useAtomValue(baseAtom) + const increaseAll = useSetAtom(writeProxyAtom) return ( <>
@@ -42,15 +42,11 @@ function Counter({ counterClass }: { counterClass: string }) {
base: {base}
- - ); + ) } function App() { @@ -63,16 +59,13 @@ function App() {

Layer 2: Scope base 2

-

- base 1 should be shared between layer 1 and layer 2, base should be - globally shared -

+

base 1 should be shared between layer 1 and layer 2, base should be globally shared

- ); + ) } -export default App; +export default App diff --git a/examples/03_nested/src/index.tsx b/examples/03_nested/src/index.tsx index 55b74de..ea17df4 100644 --- a/examples/03_nested/src/index.tsx +++ b/examples/03_nested/src/index.tsx @@ -1,8 +1,8 @@ -import { createRoot } from 'react-dom/client'; +import { createRoot } from 'react-dom/client' -import App from './App'; +import App from './App' -const ele = document.getElementById('app'); +const ele = document.getElementById('app') if (ele) { - createRoot(ele).render(); + createRoot(ele).render() } diff --git a/examples/04_derived/src/App.tsx b/examples/04_derived/src/App.tsx index fe7f52f..c7cb190 100644 --- a/examples/04_derived/src/App.tsx +++ b/examples/04_derived/src/App.tsx @@ -1,25 +1,25 @@ -import { atom, useAtom } from 'jotai'; -import { ScopeProvider } from 'jotai-scope'; +import { atom, useAtom } from 'jotai' +import { ScopeProvider } from 'jotai-scope' -const baseAtom = atom(0); +const baseAtom = atom(0) const derivedAtom1 = atom( (get) => get(baseAtom), (get, set) => { - set(baseAtom, get(baseAtom) + 1); + set(baseAtom, get(baseAtom) + 1) }, -); +) const derivedAtom2 = atom( (get) => get(baseAtom), (get, set) => { - set(baseAtom, get(baseAtom) + 1); + set(baseAtom, get(baseAtom) + 1) }, -); +) function Counter({ counterClass }: { counterClass: string }) { - const [base, setBase] = useAtom(baseAtom); - const [derived1, setDerived1] = useAtom(derivedAtom1); - const [derived2, setDerived2] = useAtom(derivedAtom2); + const [base, setBase] = useAtom(baseAtom) + const [derived1, setDerived1] = useAtom(derivedAtom1) + const [derived2, setDerived2] = useAtom(derivedAtom2) return ( <>
@@ -53,7 +53,7 @@ function Counter({ counterClass }: { counterClass: string }) {
- ); + ) } function App() { @@ -74,16 +74,13 @@ function App() {

Layer2: Base and derived2 are scoped

-

- derived1 should use layer1's atom, base and derived2 are layer 2 - scoped -

+

derived1 should use layer1's atom, base and derived2 are layer 2 scoped

- ); + ) } -export default App; +export default App diff --git a/examples/04_derived/src/index.tsx b/examples/04_derived/src/index.tsx index 55b74de..ea17df4 100644 --- a/examples/04_derived/src/index.tsx +++ b/examples/04_derived/src/index.tsx @@ -1,8 +1,8 @@ -import { createRoot } from 'react-dom/client'; +import { createRoot } from 'react-dom/client' -import App from './App'; +import App from './App' -const ele = document.getElementById('app'); +const ele = document.getElementById('app') if (ele) { - createRoot(ele).render(); + createRoot(ele).render() } diff --git a/src/ScopeProvider/ScopeProvider.tsx b/src/ScopeProvider/ScopeProvider.tsx index 4008d7b..2c7e9ee 100644 --- a/src/ScopeProvider/ScopeProvider.tsx +++ b/src/ScopeProvider/ScopeProvider.tsx @@ -1,4 +1,4 @@ -import { Provider, useStore } from 'jotai/react'; +import { Provider, useStore } from 'jotai/react' import { type EffectCallback, createContext, @@ -7,15 +7,15 @@ import { useRef, useState, type PropsWithChildren, -} from 'react'; -import { createScope } from './scope'; -import type { AnyAtom, AnyAtomFamily, Store, Scope } from './types'; -import { createPatchedStore, isTopLevelScope } from './patchedStore'; +} from 'react' +import { createScope } from './scope' +import type { AnyAtom, AnyAtomFamily, Store, Scope } from './types' +import { createPatchedStore, isTopLevelScope } from './patchedStore' const ScopeContext = createContext<{ - scope: Scope | undefined; - baseStore: Store | undefined; -}>({ scope: undefined, baseStore: undefined }); + scope: Scope | undefined + baseStore: Store | undefined +}>({ scope: undefined, baseStore: undefined }) export function ScopeProvider({ atoms, @@ -23,86 +23,85 @@ export function ScopeProvider({ children, debugName, }: PropsWithChildren<{ - atoms: Iterable; - atomFamilies?: Iterable; - debugName?: string; -}>): JSX.Element; + atoms: Iterable + atomFamilies?: Iterable + debugName?: string +}>): JSX.Element export function ScopeProvider({ atoms, atomFamilies, children, debugName, }: PropsWithChildren<{ - atoms?: Iterable; - atomFamilies: Iterable; - debugName?: string; -}>): JSX.Element; + atoms?: Iterable + atomFamilies: Iterable + debugName?: string +}>): JSX.Element export function ScopeProvider({ atoms, atomFamilies, children, debugName, }: PropsWithChildren<{ - atoms?: Iterable; - atomFamilies?: Iterable; - debugName?: string; + atoms?: Iterable + atomFamilies?: Iterable + debugName?: string }>) { - const parentStore: Store = useStore(); - let { scope: parentScope, baseStore = parentStore } = - useContext(ScopeContext); + const parentStore: Store = useStore() + let { scope: parentScope, baseStore = parentStore } = useContext(ScopeContext) // if this ScopeProvider is the first descendant scope under Provider then it is the top level scope // https://github.com/jotaijs/jotai-scope/pull/33#discussion_r1604268003 if (isTopLevelScope(parentStore)) { - parentScope = undefined; - baseStore = parentStore; + parentScope = undefined + baseStore = parentStore } // atomSet is used to detect if the atoms prop has changed. - const atomSet = new Set(atoms); - const atomFamilySet = new Set(atomFamilies); + const atomSet = new Set(atoms) + const atomFamilySet = new Set(atomFamilies) function initialize() { - const scope = createScope(atomSet, atomFamilySet, parentScope, debugName); + const scope = createScope(atomSet, atomFamilySet, parentScope, debugName) return { patchedStore: createPatchedStore(baseStore, scope), scopeContext: { scope, baseStore }, hasChanged(current: { - baseStore: Store; - parentScope: Scope | undefined; - atomSet: Set; - atomFamilySet: Set; + baseStore: Store + parentScope: Scope | undefined + atomSet: Set + atomFamilySet: Set }) { return ( parentScope !== current.parentScope || baseStore !== current.baseStore || !isEqualSet(atomSet, current.atomSet) || !isEqualSet(atomFamilySet, current.atomFamilySet) - ); + ) }, - }; + } } - const [state, setState] = useState(initialize); - const { hasChanged, scopeContext, patchedStore } = state; + const [state, setState] = useState(initialize) + const { hasChanged, scopeContext, patchedStore } = state if (hasChanged({ parentScope, atomSet, atomFamilySet, baseStore })) { - scopeContext.scope?.cleanup(); - setState(initialize); + scopeContext.scope?.cleanup() + setState(initialize) } - const { cleanup } = scopeContext.scope; - useEvent(() => cleanup, []); + const { cleanup } = scopeContext.scope + useEvent(() => cleanup, []) return ( {children} - ); + ) } function isEqualSet(a: Set, b: Set) { - return a === b || (a.size === b.size && Array.from(a).every((v) => b.has(v))); + return a === b || (a.size === b.size && Array.from(a).every((v) => b.has(v))) } function useEvent(fn: EffectCallback, deps: unknown[]) { - const ref = useRef(fn); - ref.current = fn; - useEffect(() => ref.current(), deps); + const ref = useRef(fn) + ref.current = fn + useEffect(() => ref.current(), deps) } diff --git a/src/ScopeProvider/patchedStore.ts b/src/ScopeProvider/patchedStore.ts index 8b830cb..3081c62 100644 --- a/src/ScopeProvider/patchedStore.ts +++ b/src/ScopeProvider/patchedStore.ts @@ -1,4 +1,4 @@ -import type { Store, Scope } from './types'; +import type { Store, Scope } from './types' function PatchedStore() {} @@ -9,30 +9,30 @@ export function createPatchedStore(baseStore: Store, scope: Scope): Store { const store: Store = { ...baseStore, get(anAtom, ...args) { - const [scopedAtom] = scope.getAtom(anAtom); - return baseStore.get(scopedAtom, ...args); + const [scopedAtom] = scope.getAtom(anAtom) + return baseStore.get(scopedAtom, ...args) }, set(anAtom, ...args) { - const [scopedAtom, implicitScope] = scope.getAtom(anAtom); - const restore = scope.prepareWriteAtom(scopedAtom, anAtom, implicitScope); + const [scopedAtom, implicitScope] = scope.getAtom(anAtom) + const restore = scope.prepareWriteAtom(scopedAtom, anAtom, implicitScope) try { - return baseStore.set(scopedAtom, ...args); + return baseStore.set(scopedAtom, ...args) } finally { - restore?.(); + restore?.() } }, sub(anAtom, ...args) { - const [scopedAtom] = scope.getAtom(anAtom); - return baseStore.sub(scopedAtom, ...args); + const [scopedAtom] = scope.getAtom(anAtom) + return baseStore.sub(scopedAtom, ...args) }, // TODO: update this patch to support devtools - }; - return Object.assign(Object.create(PatchedStore.prototype), store); + } + return Object.assign(Object.create(PatchedStore.prototype), store) } /** * @returns true if the current scope is the first descendant scope under Provider */ export function isTopLevelScope(parentStore: Store) { - return !(parentStore instanceof PatchedStore); + return !(parentStore instanceof PatchedStore) } diff --git a/src/ScopeProvider/scope.ts b/src/ScopeProvider/scope.ts index 1c50cc7..3079f41 100644 --- a/src/ScopeProvider/scope.ts +++ b/src/ScopeProvider/scope.ts @@ -1,13 +1,13 @@ -import { atom, type Atom } from 'jotai'; -import type { AnyAtomFamily, AnyAtom, AnyWritableAtom, Scope } from './types'; +import { atom, type Atom } from 'jotai' +import type { AnyAtomFamily, AnyAtom, AnyWritableAtom, Scope } from './types' -const globalScopeKey: { name?: string } = {}; +const globalScopeKey: { name?: string } = {} if (process.env.NODE_ENV !== 'production') { - globalScopeKey.name = 'unscoped'; - globalScopeKey.toString = toString; + globalScopeKey.name = 'unscoped' + globalScopeKey.toString = toString } -type GlobalScopeKey = typeof globalScopeKey; +type GlobalScopeKey = typeof globalScopeKey export function createScope( atoms: Set, @@ -15,10 +15,10 @@ export function createScope( parentScope: Scope | undefined, scopeName?: string | undefined, ): Scope { - const explicit = new WeakMap(); - const implicit = new WeakMap(); - type ScopeMap = WeakMap; - const inherited = new WeakMap(); + const explicit = new WeakMap() + const implicit = new WeakMap() + type ScopeMap = WeakMap + const inherited = new WeakMap() const currentScope: Scope = { getAtom, @@ -34,52 +34,50 @@ export function createScope( // atom is writable with init and holds a value // we need to preserve the value, so we don't want to copy the atom // instead, we need to override write until the write is finished - const { write } = originalAtom; + const { write } = originalAtom anAtom.write = createScopedWrite( - originalAtom.write.bind( - originalAtom, - ) as (typeof originalAtom)['write'], + originalAtom.write.bind(originalAtom) as (typeof originalAtom)['write'], implicitScope, - ); + ) return () => { - anAtom.write = write; - }; + anAtom.write = write + } } - return undefined; + return undefined }, - }; + } if (scopeName && process.env.NODE_ENV !== 'production') { - currentScope.name = scopeName; - currentScope.toString = toString; + currentScope.name = scopeName + currentScope.toString = toString } // populate explicitly scoped atoms for (const anAtom of atoms) { - explicit.set(anAtom, [cloneAtom(anAtom, currentScope), currentScope]); + explicit.set(anAtom, [cloneAtom(anAtom, currentScope), currentScope]) } - const cleanupFamiliesSet = new Set<() => void>(); + const cleanupFamiliesSet = new Set<() => void>() for (const atomFamily of atomFamilies) { for (const param of atomFamily.getParams()) { - const anAtom = atomFamily(param); + const anAtom = atomFamily(param) if (!explicit.has(anAtom)) { - explicit.set(anAtom, [cloneAtom(anAtom, currentScope), currentScope]); + explicit.set(anAtom, [cloneAtom(anAtom, currentScope), currentScope]) } } const cleanupFamily = atomFamily.unstable_listen((e) => { if (e.type === 'CREATE' && !explicit.has(e.atom)) { - explicit.set(e.atom, [cloneAtom(e.atom, currentScope), currentScope]); + explicit.set(e.atom, [cloneAtom(e.atom, currentScope), currentScope]) } else if (!atoms.has(e.atom)) { - explicit.delete(e.atom); + explicit.delete(e.atom) } - }); - cleanupFamiliesSet.add(cleanupFamily); + }) + cleanupFamiliesSet.add(cleanupFamily) } currentScope.cleanup = combineVoidFunctions( currentScope.cleanup, ...Array.from(cleanupFamiliesSet), - ); + ) /** * Returns a scoped atom from the original atom. @@ -87,47 +85,41 @@ export function createScope( * @param implicitScope the atom is implicitly scoped in the provided scope * @returns the scoped atom and the scope of the atom */ - function getAtom( - anAtom: T, - implicitScope?: Scope, - ): [T, Scope?] { + function getAtom(anAtom: T, implicitScope?: Scope): [T, Scope?] { if (explicit.has(anAtom)) { - return explicit.get(anAtom) as [T, Scope]; + return explicit.get(anAtom) as [T, Scope] } if (implicitScope === currentScope) { // dependencies of explicitly scoped atoms are implicitly scoped // implicitly scoped atoms are only accessed by implicit and explicit scoped atoms if (!implicit.has(anAtom)) { - implicit.set(anAtom, [cloneAtom(anAtom, implicitScope), implicitScope]); + implicit.set(anAtom, [cloneAtom(anAtom, implicitScope), implicitScope]) } - return implicit.get(anAtom) as [T, Scope]; + return implicit.get(anAtom) as [T, Scope] } - const scopeKey = implicitScope ?? globalScopeKey; + const scopeKey = implicitScope ?? globalScopeKey if (parentScope) { // inherited atoms are copied so they can access scoped atoms // but they are not explicitly scoped // dependencies of inherited atoms first check if they are explicitly scoped // otherwise they use their original scope's atom if (!inherited.get(scopeKey)?.has(anAtom)) { - const [ancestorAtom, explicitScope] = parentScope.getAtom( - anAtom, - implicitScope, - ); + const [ancestorAtom, explicitScope] = parentScope.getAtom(anAtom, implicitScope) setInheritedAtom( inheritAtom(ancestorAtom, anAtom, explicitScope), anAtom, implicitScope, explicitScope, - ); + ) } - return inherited.get(scopeKey)!.get(anAtom) as [T, Scope]; + return inherited.get(scopeKey)!.get(anAtom) as [T, Scope] } if (!inherited.get(scopeKey)?.has(anAtom)) { // non-primitive atoms may need to access scoped atoms // so we need to create a copy of the atom - setInheritedAtom(inheritAtom(anAtom, anAtom), anAtom); + setInheritedAtom(inheritAtom(anAtom, anAtom), anAtom) } - return inherited.get(scopeKey)!.get(anAtom) as [T, Scope?]; + return inherited.get(scopeKey)!.get(anAtom) as [T, Scope?] } function setInheritedAtom( @@ -136,9 +128,9 @@ export function createScope( implicitScope?: Scope, explicitScope?: Scope, ) { - const scopeKey = implicitScope ?? globalScopeKey; + const scopeKey = implicitScope ?? globalScopeKey if (!inherited.has(scopeKey)) { - inherited.set(scopeKey, new WeakMap()); + inherited.set(scopeKey, new WeakMap()) } inherited.get(scopeKey)!.set( originalAtom, @@ -146,21 +138,17 @@ export function createScope( scopedAtom, // explicitScope, ].filter(Boolean) as [T, Scope?], - ); + ) } /** * @returns a copy of the atom for derived atoms or the original atom for primitive and writable atoms */ - function inheritAtom( - anAtom: Atom, - originalAtom: Atom, - implicitScope?: Scope, - ) { + function inheritAtom(anAtom: Atom, originalAtom: Atom, implicitScope?: Scope) { if (originalAtom.read !== defaultRead) { - return cloneAtom(originalAtom, implicitScope); + return cloneAtom(originalAtom, implicitScope) } - return anAtom; + return anAtom } /** @@ -171,13 +159,13 @@ export function createScope( const scopedAtom: Atom = Object.create( Object.getPrototypeOf(originalAtom), Object.getOwnPropertyDescriptors(originalAtom), - ); + ) if (scopedAtom.read !== defaultRead) { scopedAtom.read = createScopedRead( originalAtom.read.bind(originalAtom), implicitScope, - ); + ) } if ( @@ -185,13 +173,10 @@ export function createScope( isWritableAtom(originalAtom) && scopedAtom.write !== defaultWrite ) { - scopedAtom.write = createScopedWrite( - originalAtom.write.bind(originalAtom), - implicitScope, - ); + scopedAtom.write = createScopedWrite(originalAtom.write.bind(originalAtom), implicitScope) } - return scopedAtom; + return scopedAtom } function createScopedRead>( @@ -201,12 +186,12 @@ export function createScope( return function scopedRead(get, opts) { return read( function scopedGet(a) { - const [scopedAtom] = getAtom(a, implicitScope); - return get(scopedAtom); + const [scopedAtom] = getAtom(a, implicitScope) + return get(scopedAtom) }, // opts, - ); - }; + ) + } } function createScopedWrite( @@ -216,35 +201,35 @@ export function createScope( return function scopedWrite(get, set, ...args) { return write( function scopedGet(a) { - const [scopedAtom] = getAtom(a, implicitScope); - return get(scopedAtom); + const [scopedAtom] = getAtom(a, implicitScope) + return get(scopedAtom) }, function scopedSet(a, ...v) { - const [scopedAtom] = getAtom(a, implicitScope); - return set(scopedAtom, ...v); + const [scopedAtom] = getAtom(a, implicitScope) + return set(scopedAtom, ...v) }, ...args, - ); - }; + ) + } } - return currentScope; + return currentScope } function isWritableAtom(anAtom: AnyAtom): anAtom is AnyWritableAtom { - return 'write' in anAtom; + return 'write' in anAtom } -const { read: defaultRead, write: defaultWrite } = atom(null); +const { read: defaultRead, write: defaultWrite } = atom(null) function toString(this: { name: string }) { - return this.name; + return this.name } function combineVoidFunctions(...fns: (() => void)[]) { return function combinedFunctions() { for (const fn of fns) { - fn(); + fn() } - }; + } } diff --git a/src/ScopeProvider/types.ts b/src/ScopeProvider/types.ts index d491235..3da6933 100644 --- a/src/ScopeProvider/types.ts +++ b/src/ScopeProvider/types.ts @@ -1,13 +1,13 @@ -import type { Atom, WritableAtom, getDefaultStore } from 'jotai'; -import { AtomFamily } from 'jotai/vanilla/utils/atomFamily'; +import type { Atom, WritableAtom, getDefaultStore } from 'jotai' +import { AtomFamily } from 'jotai/vanilla/utils/atomFamily' -export type AnyAtom = Atom | WritableAtom; +export type AnyAtom = Atom | WritableAtom -export type AnyAtomFamily = AtomFamily; +export type AnyAtomFamily = AtomFamily -export type AnyWritableAtom = WritableAtom; +export type AnyWritableAtom = WritableAtom -export type Store = ReturnType; +export type Store = ReturnType export type Scope = { /** @@ -16,12 +16,12 @@ export type Scope = { * @param implicitScope the atom is implicitly scoped in the provided scope * @returns the scoped atom and the scope of the atom */ - getAtom: (anAtom: T, implicitScope?: Scope) => [T, Scope?]; + getAtom: (anAtom: T, implicitScope?: Scope) => [T, Scope?] /** * Cleans up the scope */ - cleanup: () => void; + cleanup: () => void /** * @modifies the atom's write function for atoms that can hold a value @@ -31,15 +31,15 @@ export type Scope = { anAtom: T, originalAtom: T, implicitScope?: Scope, - ) => (() => void) | undefined; + ) => (() => void) | undefined /** * @debug */ - name?: string; + name?: string /** * @debug */ - toString?: () => string; -}; + toString?: () => string +} diff --git a/src/createIsolation.tsx b/src/createIsolation.tsx index c464524..dc76cd2 100644 --- a/src/createIsolation.tsx +++ b/src/createIsolation.tsx @@ -1,62 +1,58 @@ -import { createContext, useContext, useRef } from 'react'; -import type { ReactNode } from 'react'; -import { createStore } from 'jotai/vanilla'; -import type { WritableAtom } from 'jotai/vanilla'; +import { createContext, useContext, useRef } from 'react' +import type { ReactNode } from 'react' +import { createStore } from 'jotai/vanilla' +import type { WritableAtom } from 'jotai/vanilla' import { useAtom as useAtomOrig, useAtomValue as useAtomValueOrig, useSetAtom as useSetAtomOrig, useStore as useStoreOrig, -} from 'jotai/react'; -import { useHydrateAtoms } from 'jotai/react/utils'; +} from 'jotai/react' +import { useHydrateAtoms } from 'jotai/react/utils' -type Store = ReturnType; -type AnyWritableAtom = WritableAtom; +type Store = ReturnType +type AnyWritableAtom = WritableAtom export function createIsolation() { - const StoreContext = createContext(null); + const StoreContext = createContext(null) function Provider({ store, initialValues = [], children, }: { - store?: Store; - initialValues?: Iterable; - children: ReactNode; + store?: Store + initialValues?: Iterable + children: ReactNode }) { - const storeRef = useRef(store); + const storeRef = useRef(store) if (!storeRef.current) { - storeRef.current = createStore(); + storeRef.current = createStore() } - useHydrateAtoms(initialValues as any, { store: storeRef.current }); - return ( - - {children} - - ); + useHydrateAtoms(initialValues as any, { store: storeRef.current }) + return {children} } const useStore = ((options?: any) => { - const store = useContext(StoreContext); - if (!store) throw new Error('Missing Provider from createIsolation'); - return useStoreOrig({ store, ...options }); - }) as typeof useStoreOrig; + const store = useContext(StoreContext) + if (!store) throw new Error('Missing Provider from createIsolation') + return useStoreOrig({ store, ...options }) + }) as typeof useStoreOrig const useAtom = ((anAtom: any, options?: any) => { - const store = useStore(); - return useAtomOrig(anAtom, { store, ...options }); - }) as typeof useAtomOrig; + const store = useStore() + return useAtomOrig(anAtom, { store, ...options }) + }) as typeof useAtomOrig const useAtomValue = ((anAtom: any, options?: any) => { - const store = useStore(); - return useAtomValueOrig(anAtom, { store, ...options }); - }) as typeof useAtomValueOrig; + const store = useStore() + return useAtomValueOrig(anAtom, { store, ...options }) + }) as typeof useAtomValueOrig const useSetAtom = ((anAtom: any, options?: any) => { - const store = useStore(); - return useSetAtomOrig(anAtom, { store, ...options }); - }) as typeof useSetAtomOrig; + const store = useStore() + return useSetAtomOrig(anAtom, { store, ...options }) + }) as typeof useSetAtomOrig - return { Provider, useStore, useAtom, useAtomValue, useSetAtom }; + return { Provider, useStore, useAtom, useAtomValue, useSetAtom } } diff --git a/src/index.ts b/src/index.ts index 0c90ebb..d06598c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,2 +1,2 @@ -export { createIsolation } from './createIsolation'; -export { ScopeProvider } from './ScopeProvider/ScopeProvider'; +export { createIsolation } from './createIsolation' +export { ScopeProvider } from './ScopeProvider/ScopeProvider' diff --git a/webpack.config.js b/webpack.config.js index d20affb..1c1f2fd 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,7 +1,7 @@ // eslint-disable-next-line @typescript-eslint/no-var-requires -const HtmlWebpackPlugin = require('html-webpack-plugin'); +const HtmlWebpackPlugin = require('html-webpack-plugin') -const { DIR, EXT = 'ts' } = process.env; +const { DIR, EXT = 'ts' } = process.env module.exports = { mode: 'development', @@ -40,4 +40,4 @@ module.exports = { }, historyApiFallback: true, }, -}; +}