Skip to content

Commit

Permalink
Create test suite for core functionality (#3)
Browse files Browse the repository at this point in the history
* write basic tests

* add component tests
  • Loading branch information
EskiMojo14 authored Apr 21, 2024
1 parent ff79496 commit 5c4b769
Show file tree
Hide file tree
Showing 7 changed files with 370 additions and 9 deletions.
58 changes: 58 additions & 0 deletions __tests__/01_basic.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { create } from 'zustand';
import { createSlice, withSlices } from '../src/index';

describe('basic spec', () => {
it('should export functions', () => {
expect(createSlice).toBeDefined();
expect(withSlices).toBeDefined();
});
describe('createSlice', () => {
it('should return a slice config', () => {
const slice = createSlice({
name: 'counter',
value: 0,
actions: {
increment: () => (prev) => prev + 1,
},
});
// returns the input
expect(createSlice(slice)).toBe(slice);
});
});
describe('withSlices', () => {
it('should combine slices and nest state', () => {
const countSlice = createSlice({
name: 'count',
value: 0,
actions: {
inc: () => (prev) => prev + 1,
reset: () => () => 0,
},
});

const textSlice = createSlice({
name: 'text',
value: 'Hello',
actions: {
updateText: (newText: string) => () => newText,
reset: () => () => 'Hello',
},
});

const combinedConfig = withSlices(countSlice, textSlice);

expect(combinedConfig).toBeInstanceOf(Function);

const store = create(combinedConfig);

const state = store.getState();

expect(state.count).toBe(countSlice.value);
expect(state.text).toBe(textSlice.value);

expect(state.inc).toBeInstanceOf(Function);
expect(state.reset).toBeInstanceOf(Function);
expect(state.updateText).toBeInstanceOf(Function);
});
});
});
8 changes: 0 additions & 8 deletions __tests__/01_basic_spec.tsx

This file was deleted.

File renamed without changes.
153 changes: 153 additions & 0 deletions __tests__/03_component.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
import { ReactNode, createContext, useContext, useState } from 'react';
import { StoreApi, create, useStore as useZustandStore } from 'zustand';
// eslint-disable-next-line import/no-extraneous-dependencies
import { render, RenderOptions } from '@testing-library/react';
// eslint-disable-next-line import/no-extraneous-dependencies
import userEvent from '@testing-library/user-event';
import { createSlice, withSlices } from '../src/index';

type ExtractState<S> = S extends {
getState: () => infer State;
}
? State
: never;

function createZustandContext<Store extends StoreApi<any>>(
makeStore: () => Store,
) {
const Context = createContext<Store | undefined>(undefined);
const StoreProvider = ({ children }: { children: ReactNode }) => {
const [store] = useState(makeStore);
return <Context.Provider value={store}>{children}</Context.Provider>;
};
function useStore() {
const store = useContext(Context);
if (!store) {
throw new Error('useStore must be used within a StoreProvider');
}
return store;
}
function useSelector<Selected>(
selector: (state: ExtractState<Store>) => Selected,
) {
const store = useStore();
return useZustandStore(store, selector);
}
return { StoreProvider, useStore, useSelector };
}

const countSlice = createSlice({
name: 'count',
value: 0,
actions: {
inc: () => (state) => state + 1,
reset: () => () => 0,
},
});

const textSlice = createSlice({
name: 'text',
value: 'Hello',
actions: {
updateText: (text: string) => () => text,
reset: () => () => 'Hello',
},
});

const makeStore = () => create(withSlices(countSlice, textSlice));

const { StoreProvider, useStore, useSelector } =
createZustandContext(makeStore);

const renderWithProvider = (
ui: ReactNode,
options?: Omit<RenderOptions, 'wrapper'>,
) => render(ui, { wrapper: StoreProvider, ...options });

const Counter = () => {
const count = useSelector((state) => state.count);
const { inc } = useStore().getState();
return (
<div>
<p data-testid="count">{count}</p>
<button type="button" onClick={inc}>
Increment
</button>
</div>
);
};

const Text = () => {
const text = useSelector((state) => state.text);
const { updateText } = useStore().getState();
return (
<div>
<input value={text} onChange={(e) => updateText(e.target.value)} />
</div>
);
};

const App = () => {
const { reset } = useStore().getState();
return (
<div>
<Counter />
<Text />
<button type="button" onClick={reset}>
Reset
</button>
</div>
);
};

describe('component spec', () => {
const user = userEvent.setup();
it('should render the app', () => {
const { getByRole, getByTestId } = renderWithProvider(<App />);
expect(getByTestId('count')).toHaveTextContent('0');
expect(getByRole('textbox')).toBeInTheDocument();
});
it('should increment the count when the button is pressed', async () => {
const { getByRole, getByTestId } = renderWithProvider(<App />);

const count = getByTestId('count');
expect(count).toHaveTextContent('0');

const button = getByRole('button', { name: 'Increment' });
await user.click(button);

expect(count).toHaveTextContent('1');
});
it('should update the text when the input is changed', async () => {
const { getByRole } = renderWithProvider(<App />);

const input = getByRole('textbox');
expect(input).toHaveValue('Hello');

await user.type(input, ' World');

expect(input).toHaveValue('Hello World');
});
it('should reset the state when the reset button is pressed', async () => {
const { getByRole, getByTestId } = renderWithProvider(<App />);

const resetButton = getByRole('button', { name: 'Reset' });

const count = getByTestId('count');
expect(count).toHaveTextContent('0');

const incrementButton = getByRole('button', { name: 'Increment' });
await user.click(incrementButton);
expect(count).toHaveTextContent('1');

const input = getByRole('textbox');
await user.type(input, ' World');
expect(input).toHaveValue('Hello World');

await user.click(resetButton);

// both slices reset because the action name is the same
expect(count).toHaveTextContent('0');
expect(input).toHaveValue('Hello');
});
});
8 changes: 7 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,10 @@
},
"jest": {
"testEnvironment": "jsdom",
"preset": "ts-jest/presets/js-with-ts"
"preset": "ts-jest/presets/js-with-ts",
"setupFilesAfterEnv": [
"./test-setup.ts"
]
},
"keywords": [
"react",
Expand All @@ -44,6 +47,9 @@
],
"license": "MIT",
"devDependencies": {
"@testing-library/jest-dom": "^6.4.2",
"@testing-library/react": "^15.0.2",
"@testing-library/user-event": "^14.5.2",
"@types/jest": "^29.5.12",
"@types/node": "^20.12.7",
"@types/react": "^18.2.79",
Expand Down
Loading

0 comments on commit 5c4b769

Please sign in to comment.