Skip to content

Commit f7d02db

Browse files
committed
implement test for Search context and useSearch hook
1 parent 9ced40b commit f7d02db

File tree

9 files changed

+3709
-8573
lines changed

9 files changed

+3709
-8573
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
node_modules
22
dist
33
.DS_Store
4-
example
4+
example
5+
coverage

jest.config.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
module.exports = {
2+
testEnvironment: 'jsdom',
3+
transform: {
4+
'^.+\\.tsx?$': 'ts-jest',
5+
},
6+
preset: 'ts-jest',
7+
setupFilesAfterEnv: ['@testing-library/react/dont-cleanup-after-each'],
8+
testMatch: ['<rootDir>/src/**/*.test.{js,jsx,ts,tsx}'],
9+
moduleFileExtensions: ['js', 'jsx', 'ts', 'tsx'],
10+
moduleNameMapper: {
11+
'\\.(css|less|scss|sass)$': 'identity-obj-proxy',
12+
},
13+
verbose: true,
14+
collectCoverage: true,
15+
testEnvironment: 'jest-environment-jsdom',
16+
moduleNameMapper: {
17+
'react-dom/test-utils': 'react-dom/test-utils',
18+
},
19+
}

package.json

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "react-search-hook",
3-
"version": "0.1.0",
3+
"version": "0.2.0",
44
"description": "Search Library for React",
55
"keywords": [
66
"react",
@@ -13,9 +13,10 @@
1313
"main": "dist/index.js",
1414
"unpkg": "dist/index.umd.js",
1515
"types": "dist/index.d.ts",
16-
"type": "module",
16+
"type": "commonjs",
1717
"directories": {
18-
"src": "src"
18+
"src": "src",
19+
"test": "__tests__"
1920
},
2021
"files": [
2122
"dist",
@@ -27,9 +28,12 @@
2728
"url": "git+https://github.com/DevAnsar/react-search-hook.git"
2829
},
2930
"scripts": {
30-
"build": "rollup -c",
31+
"build": "run-s build:*",
32+
"build:package": "rollup -c",
3133
"lint": "eslint . --ext .js,.jsx,.ts,.tsx",
32-
"prettier": "prettier --write \"src/**/*.{js,ts,jsx,tsx}\""
34+
"prettier": "prettier --write \"src/**/*.{js,ts,jsx,tsx}\"",
35+
"test": "jest",
36+
"test:watch": "jest --watch"
3337
},
3438
"bugs": {
3539
"url": "https://github.com/DevAnsar/react-search-hook/issues"
@@ -38,14 +42,24 @@
3842
"@rollup/plugin-commonjs": "^25.0.7",
3943
"@rollup/plugin-node-resolve": "^15.2.3",
4044
"@rollup/plugin-terser": "^0.4.4",
45+
"@testing-library/jest-dom": "^6.1.6",
46+
"@testing-library/react": "^14.1.2",
47+
"@types/jest": "^29.5.11",
48+
"@types/react-dom": "^18.2.18",
4149
"eslint": "^8.56.0",
4250
"eslint-plugin-react": "^7.33.2",
4351
"eslint-plugin-react-hooks": "^4.6.0",
52+
"jest": "^29.7.0",
53+
"jest-environment-jsdom": "^29.7.0",
54+
"npm-run-all": "^4.1.5",
4455
"prettier": "^3.1.1",
56+
"react": "^18.2.0",
57+
"react-dom": "^18.2.0",
4558
"rollup": "^4.9.0",
4659
"rollup-plugin-filesize": "^10.0.0",
4760
"rollup-plugin-peer-deps-external": "^2.2.4",
4861
"rollup-plugin-typescript2": "^0.36.0",
62+
"ts-jest": "^29.1.1",
4963
"typescript": "^5.3.3"
5064
},
5165
"peerDependencies": {

src/SearchProvider.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,13 @@ import { InferStoreNames, SearchProviderProps } from './types'
3131
* @return Functional Component
3232
*/
3333
const SearchProvider: React.FunctionComponent<SearchProviderProps> = ({ children, stores }) => {
34-
type StoreNames = InferStoreNames<typeof stores>
34+
type StoreNames = InferStoreNames<typeof stores extends string[] ? typeof stores : string[]>
3535

3636
const initialState = React.useMemo(() => {
37+
if (!stores) {
38+
return {} as Record<StoreNames, string>
39+
}
40+
3741
return stores.reduce(
3842
(acc, store) => {
3943
acc[store] = ''

src/__tests__/SearchProvider.test.tsx

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { render, screen } from '@testing-library/react'
2+
import SearchProvider from '../SearchProvider'
3+
import React from 'react'
4+
5+
describe('SearchProvider renders successfully', () => {
6+
it('With array of store names', () => {
7+
render(
8+
<SearchProvider stores={['products']}>
9+
<div>Empty</div>
10+
</SearchProvider>,
11+
)
12+
})
13+
})
14+
15+
// describe('SearchProvider renders successfully', () => {
16+
// it('Without any store names props', () => {
17+
// render(
18+
// <SearchProvider>
19+
// <div>Empty</div>
20+
// </SearchProvider>,
21+
// )
22+
// })
23+
// })

src/__tests__/hook/useSearch.test.tsx

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
import React from 'react'
2+
import { renderHook } from '@testing-library/react'
3+
import useSearch from '../../hooks/useSearch'
4+
import SearchProvider from '../../SearchProvider'
5+
import SearchContext from '../../SearchContext'
6+
import { act } from 'react-dom/test-utils'
7+
import { SearchContextType, UseSearchOptions } from '../../types'
8+
9+
describe('useSearch', () => {
10+
it('should thrown an error if the SearchContext is not available', () => {
11+
expect(useSearch).toThrow(Error)
12+
})
13+
14+
it('useSearch should throw Error when store name is not find', () => {
15+
const fakeSearchState = {
16+
stores: {},
17+
changeStoreValue: jest.fn(),
18+
}
19+
const wrapper = ({ children }: { children: React.ReactNode }) => (
20+
<SearchContext.Provider value={fakeSearchState}>{children}</SearchContext.Provider>
21+
)
22+
23+
expect(() => renderHook(() => useSearch('product'), { wrapper })).toThrow(Error(`Invalid store name: product`))
24+
})
25+
26+
it('should get search value from context with useSearch successfully', () => {
27+
const fakeSearchState = {
28+
stores: { product: 'test_product' },
29+
changeStoreValue: jest.fn(),
30+
}
31+
const wrapper = ({ children }: { children: React.ReactNode }) => (
32+
<SearchContext.Provider value={fakeSearchState}>{children}</SearchContext.Provider>
33+
)
34+
const { result } = renderHook(() => useSearch('product'), { wrapper })
35+
expect(result.current.search).toEqual('test_product')
36+
})
37+
38+
it('should define search when it specify with SearchProvider', () => {
39+
const fakeSearchState = {
40+
stores: {},
41+
changeStoreValue: jest.fn(),
42+
}
43+
const wrapper = ({ children }: { children: React.ReactNode }) => (
44+
<SearchContext.Provider value={fakeSearchState}>
45+
<SearchProvider stores={['product']}>{children}</SearchProvider>
46+
</SearchContext.Provider>
47+
)
48+
const { result } = renderHook(() => useSearch('product'), { wrapper })
49+
expect(result.current.search).toBeDefined()
50+
})
51+
52+
it('should update search state with setSearch', () => {
53+
const fakeSearchState = {
54+
stores: {},
55+
changeStoreValue: jest.fn(),
56+
}
57+
const wrapper = ({ children }: { children: React.ReactNode }) => (
58+
<SearchContext.Provider value={fakeSearchState}>
59+
<SearchProvider stores={['product']}>{children}</SearchProvider>
60+
</SearchContext.Provider>
61+
)
62+
const { result } = renderHook(() => useSearch('product'), { wrapper })
63+
act(() => {
64+
result.current.setSearch('product_search_value')
65+
})
66+
expect(result.current.search).toEqual('product_search_value')
67+
})
68+
69+
it('should filter data with search value (array of string)', () => {
70+
const fakeSearchContextValue: SearchContextType = {
71+
stores: {},
72+
changeStoreValue: jest.fn(),
73+
}
74+
75+
const fakeItems: string[] = ['product1', 'product2', 'product3', 'product3', 'product4']
76+
const fakeUseSearchOptions: UseSearchOptions = { items: fakeItems }
77+
78+
const wrapper = ({ children }: { children: React.ReactNode }) => (
79+
<SearchContext.Provider value={fakeSearchContextValue}>
80+
<SearchProvider stores={['products']}>{children}</SearchProvider>
81+
</SearchContext.Provider>
82+
)
83+
const { result } = renderHook(() => useSearch('products', fakeUseSearchOptions), { wrapper })
84+
act(() => {
85+
result.current.setSearch('product3')
86+
})
87+
expect(result.current.searchResult).toEqual(['product3', 'product3'])
88+
})
89+
90+
it('should filter data with search value (array of object - with correct searchProp)', () => {
91+
const fakeSearchContextValue: SearchContextType = {
92+
stores: {},
93+
changeStoreValue: jest.fn(),
94+
}
95+
96+
const fakeItems = [
97+
{ name: 'product1' },
98+
{ name: 'product2' },
99+
{ name: 'product3' },
100+
{ name: 'product3' },
101+
{ name: 'product4' },
102+
]
103+
const fakeUseSearchOptions: UseSearchOptions<{ name: string }> = { items: fakeItems, searchProp: 'name' }
104+
105+
const wrapper = ({ children }: { children: React.ReactNode }) => (
106+
<SearchContext.Provider value={fakeSearchContextValue}>
107+
<SearchProvider stores={['products']}>{children}</SearchProvider>
108+
</SearchContext.Provider>
109+
)
110+
const { result } = renderHook(() => useSearch('products', fakeUseSearchOptions), { wrapper })
111+
act(() => {
112+
result.current.setSearch('product3')
113+
})
114+
expect(result.current.searchResult).toHaveLength(2)
115+
})
116+
117+
it('should filter data with search value (array of object - with non-correct searchProp)', () => {
118+
const fakeSearchContextValue: SearchContextType = {
119+
stores: {},
120+
changeStoreValue: jest.fn(),
121+
}
122+
123+
const fakeItems = [{ name: 'product1' }, { name: 'product2' }]
124+
const fakeUseSearchOptions: UseSearchOptions<{ name: string }> = { items: fakeItems, searchProp: 'name1' }
125+
126+
const wrapper = ({ children }: { children: React.ReactNode }) => (
127+
<SearchContext.Provider value={fakeSearchContextValue}>
128+
<SearchProvider stores={['products']}>{children}</SearchProvider>
129+
</SearchContext.Provider>
130+
)
131+
const { result } = renderHook(() => useSearch('products', fakeUseSearchOptions), { wrapper })
132+
act(() => {
133+
result.current.setSearch('product1')
134+
})
135+
expect(result.current.searchResult).toHaveLength(0)
136+
})
137+
})

src/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ export type SearchContextType = {
3131
*/
3232
export interface SearchProviderProps {
3333
children: React.ReactNode
34-
stores: string[]
34+
stores?: string[]
3535
}
3636

3737
export type InferStoreNames<T extends string[]> = T[number]

src/utils/filter.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,15 @@
1919
*/
2020

2121
export const getfilterItems = <TItem extends any>(searchText: string, items: TItem[], searchProp?: string): TItem[] => {
22-
console.log('i', typeof items[0] === 'object', searchProp)
2322
if (typeof items[0] === 'string') {
2423
// If data is an array of strings, filter based on the search term
2524
return items.filter((item) => (item as string).toLowerCase().includes(searchText.toLowerCase()))
26-
} else if (items.length > 0 && typeof items[0] === 'object' && searchProp) {
25+
} else if (items.length > 0 && typeof items[0] === 'object') {
26+
if (!searchProp)
27+
throw new Error(`
28+
If the items are an array of objects, searchProp will be a required prop for the search engine!
29+
For more information, please refer to: https://github.com/DevAnsar/react-search-hook?tab=readme-ov-file#usesearch-options
30+
`)
2731
// If data is an array of objects, filter based on the specified property
2832
return items.filter((item) => {
2933
if (typeof item === 'object' && item !== null && searchProp in item) {

0 commit comments

Comments
 (0)