Skip to content

Commit 396d6d8

Browse files
committed
Add createConditionalFallback and related tests
1 parent b332226 commit 396d6d8

File tree

2 files changed

+146
-9
lines changed

2 files changed

+146
-9
lines changed

src/__tests__/index.test.tsx

Lines changed: 98 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,102 @@
1-
// import { render } from '@testing-library/react';
2-
// import { axe } from 'jest-axe';
3-
// import { PropsWithChildren } from 'react';
1+
import { act, render, waitForElementToBeRemoved } from '@testing-library/react';
2+
3+
import createConditionalFallback from "index";
4+
5+
function MainComponent() {
6+
return <div data-testid="main">Condition is FALSE.</div>;
7+
}
8+
9+
function FallbackComponent() {
10+
return <div data-testid="fallback">Condition is TRUE.</div>;
11+
}
412

513
describe('#react-conditional-fallback', () => {
6-
xit('xxx', async () => {
14+
it('renders the happy path', async () => {
15+
const { ConditionalFallback } = createConditionalFallback(false);
16+
17+
const { queryByTestId } = render(
18+
<ConditionalFallback fallback={<FallbackComponent />}>
19+
<MainComponent />
20+
</ConditionalFallback>,
21+
);
22+
23+
expect(await queryByTestId('main')).not.toEqual(null);
24+
expect(await queryByTestId('fallback')).toEqual(null);
25+
});
26+
27+
it('renders the fallback path', async () => {
28+
const { ConditionalFallback } = createConditionalFallback(true);
29+
30+
const { queryByTestId } = render(
31+
<ConditionalFallback fallback={<FallbackComponent />}>
32+
<MainComponent />
33+
</ConditionalFallback>,
34+
);
35+
36+
expect(await queryByTestId('main')).toEqual(null);
37+
expect(await queryByTestId('fallback')).not.toEqual(null);
38+
});
39+
40+
it('updates on state change', async () => {
41+
const { ConditionalFallback, set } = createConditionalFallback(false);
42+
43+
const { queryByTestId } = render(
44+
<ConditionalFallback fallback={<FallbackComponent />}>
45+
<MainComponent />
46+
</ConditionalFallback>,
47+
);
48+
49+
expect(await queryByTestId('main')).not.toEqual(null);
50+
expect(await queryByTestId('fallback')).toEqual(null);
51+
52+
act(() => {
53+
set();
54+
});
55+
56+
expect(await queryByTestId('main')).toEqual(null);
57+
expect(await queryByTestId('fallback')).not.toEqual(null);
58+
});
59+
60+
it('works with the provider as an ancestor', async () => {
61+
const { Provider, ConditionalFallback, set } = createConditionalFallback(false);
62+
63+
const { queryByTestId } = render(
64+
<Provider>
65+
<ConditionalFallback fallback={<FallbackComponent />}>
66+
<MainComponent />
67+
</ConditionalFallback>
68+
</Provider>,
69+
);
70+
71+
expect(await queryByTestId('main')).not.toEqual(null);
72+
expect(await queryByTestId('fallback')).toEqual(null);
73+
74+
act(() => {
75+
set();
76+
});
77+
78+
expect(await queryByTestId('main')).toEqual(null);
79+
expect(await queryByTestId('fallback')).not.toEqual(null);
80+
});
81+
82+
it('provides the correct value to the hook', async () => {
83+
const { Provider, useCondition, set } = createConditionalFallback(false);
84+
85+
function ConditionValue() {
86+
const value = useCondition();
87+
return <pre>{value.toString().toUpperCase()}</pre>;
88+
}
89+
90+
const { queryByText } = render(<Provider><ConditionValue /></Provider>);
91+
92+
expect(await queryByText('TRUE')).toEqual(null);
93+
expect(await queryByText('FALSE')).not.toEqual(null);
94+
95+
act(() => {
96+
set();
97+
});
98+
99+
expect(await queryByText('TRUE')).not.toEqual(null);
100+
expect(await queryByText('FALSE')).toEqual(null);
7101
});
8102
});

src/index.tsx

Lines changed: 48 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,50 @@
1-
import { PropsWithChildren } from 'react';
1+
import ConditionStore from 'ConditionStore';
2+
import React, { PropsWithChildren, ReactNode, createContext, useContext, useMemo, useSyncExternalStore } from 'react';
23

3-
export default function createConditionalFallback({
4-
children,
5-
}: PropsWithChildren) {
6-
return <>{children}</>
4+
export type ConditionalFallbackProps = PropsWithChildren<{fallback: ReactNode}>;
5+
6+
export type ConditionalFallbackContext = {
7+
value: boolean,
8+
set: () => void,
9+
clear: () => void,
10+
};
11+
12+
export default function createConditionalFallback(initialValue?: boolean) {
13+
const store = new ConditionStore(initialValue);
14+
15+
const set = store.set.bind(store);
16+
const clear = store.clear.bind(store);
17+
const subscribe = store.subscribe.bind(store);
18+
const getSnapshot = store.getSnapshot.bind(store);
19+
20+
const Context = createContext<ConditionalFallbackContext | null>(null);
21+
22+
function Provider({children}: PropsWithChildren) {
23+
const value = useSyncExternalStore(subscribe, getSnapshot);
24+
const context = useMemo(() => ({ value, set, clear }), [value]);
25+
return <Context.Provider value={context}>{children}</Context.Provider>;
26+
}
27+
28+
function ConditionalFallback({ fallback, children }: ConditionalFallbackProps) {
29+
const context = useContext(Context);
30+
31+
if( context === null) {
32+
return <Provider><ConditionalFallback fallback={fallback}>{children}</ConditionalFallback></Provider>;
33+
}
34+
35+
return <>{context.value ? fallback : children}</>
36+
}
37+
38+
function useCondition() {
39+
const { value } = useContext(Context) || { value: initialValue };
40+
return Boolean(value);
41+
}
42+
43+
return {
44+
set,
45+
clear,
46+
Provider,
47+
ConditionalFallback,
48+
useCondition,
49+
};
750
}

0 commit comments

Comments
 (0)