Skip to content

Commit

Permalink
implement test for Search context and useSearch hook
Browse files Browse the repository at this point in the history
  • Loading branch information
DevAnsar committed Jan 3, 2024
1 parent 9ced40b commit f7d02db
Show file tree
Hide file tree
Showing 9 changed files with 3,709 additions and 8,573 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
node_modules
dist
.DS_Store
example
example
coverage
19 changes: 19 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
module.exports = {
testEnvironment: 'jsdom',
transform: {
'^.+\\.tsx?$': 'ts-jest',
},
preset: 'ts-jest',
setupFilesAfterEnv: ['@testing-library/react/dont-cleanup-after-each'],
testMatch: ['<rootDir>/src/**/*.test.{js,jsx,ts,tsx}'],
moduleFileExtensions: ['js', 'jsx', 'ts', 'tsx'],
moduleNameMapper: {
'\\.(css|less|scss|sass)$': 'identity-obj-proxy',
},
verbose: true,
collectCoverage: true,
testEnvironment: 'jest-environment-jsdom',
moduleNameMapper: {
'react-dom/test-utils': 'react-dom/test-utils',
},
}
24 changes: 19 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "react-search-hook",
"version": "0.1.0",
"version": "0.2.0",
"description": "Search Library for React",
"keywords": [
"react",
Expand All @@ -13,9 +13,10 @@
"main": "dist/index.js",
"unpkg": "dist/index.umd.js",
"types": "dist/index.d.ts",
"type": "module",
"type": "commonjs",
"directories": {
"src": "src"
"src": "src",
"test": "__tests__"
},
"files": [
"dist",
Expand All @@ -27,9 +28,12 @@
"url": "git+https://github.com/DevAnsar/react-search-hook.git"
},
"scripts": {
"build": "rollup -c",
"build": "run-s build:*",
"build:package": "rollup -c",
"lint": "eslint . --ext .js,.jsx,.ts,.tsx",
"prettier": "prettier --write \"src/**/*.{js,ts,jsx,tsx}\""
"prettier": "prettier --write \"src/**/*.{js,ts,jsx,tsx}\"",
"test": "jest",
"test:watch": "jest --watch"
},
"bugs": {
"url": "https://github.com/DevAnsar/react-search-hook/issues"
Expand All @@ -38,14 +42,24 @@
"@rollup/plugin-commonjs": "^25.0.7",
"@rollup/plugin-node-resolve": "^15.2.3",
"@rollup/plugin-terser": "^0.4.4",
"@testing-library/jest-dom": "^6.1.6",
"@testing-library/react": "^14.1.2",
"@types/jest": "^29.5.11",
"@types/react-dom": "^18.2.18",
"eslint": "^8.56.0",
"eslint-plugin-react": "^7.33.2",
"eslint-plugin-react-hooks": "^4.6.0",
"jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0",
"npm-run-all": "^4.1.5",
"prettier": "^3.1.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"rollup": "^4.9.0",
"rollup-plugin-filesize": "^10.0.0",
"rollup-plugin-peer-deps-external": "^2.2.4",
"rollup-plugin-typescript2": "^0.36.0",
"ts-jest": "^29.1.1",
"typescript": "^5.3.3"
},
"peerDependencies": {
Expand Down
6 changes: 5 additions & 1 deletion src/SearchProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,13 @@ import { InferStoreNames, SearchProviderProps } from './types'
* @return Functional Component
*/
const SearchProvider: React.FunctionComponent<SearchProviderProps> = ({ children, stores }) => {
type StoreNames = InferStoreNames<typeof stores>
type StoreNames = InferStoreNames<typeof stores extends string[] ? typeof stores : string[]>

const initialState = React.useMemo(() => {
if (!stores) {
return {} as Record<StoreNames, string>
}

return stores.reduce(
(acc, store) => {
acc[store] = ''
Expand Down
23 changes: 23 additions & 0 deletions src/__tests__/SearchProvider.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { render, screen } from '@testing-library/react'
import SearchProvider from '../SearchProvider'
import React from 'react'

describe('SearchProvider renders successfully', () => {
it('With array of store names', () => {
render(
<SearchProvider stores={['products']}>
<div>Empty</div>
</SearchProvider>,
)
})
})

// describe('SearchProvider renders successfully', () => {
// it('Without any store names props', () => {
// render(
// <SearchProvider>
// <div>Empty</div>
// </SearchProvider>,
// )
// })
// })
137 changes: 137 additions & 0 deletions src/__tests__/hook/useSearch.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import React from 'react'
import { renderHook } from '@testing-library/react'
import useSearch from '../../hooks/useSearch'
import SearchProvider from '../../SearchProvider'
import SearchContext from '../../SearchContext'
import { act } from 'react-dom/test-utils'
import { SearchContextType, UseSearchOptions } from '../../types'

describe('useSearch', () => {
it('should thrown an error if the SearchContext is not available', () => {
expect(useSearch).toThrow(Error)
})

it('useSearch should throw Error when store name is not find', () => {
const fakeSearchState = {
stores: {},
changeStoreValue: jest.fn(),
}
const wrapper = ({ children }: { children: React.ReactNode }) => (
<SearchContext.Provider value={fakeSearchState}>{children}</SearchContext.Provider>
)

expect(() => renderHook(() => useSearch('product'), { wrapper })).toThrow(Error(`Invalid store name: product`))
})

it('should get search value from context with useSearch successfully', () => {
const fakeSearchState = {
stores: { product: 'test_product' },
changeStoreValue: jest.fn(),
}
const wrapper = ({ children }: { children: React.ReactNode }) => (
<SearchContext.Provider value={fakeSearchState}>{children}</SearchContext.Provider>
)
const { result } = renderHook(() => useSearch('product'), { wrapper })
expect(result.current.search).toEqual('test_product')
})

it('should define search when it specify with SearchProvider', () => {
const fakeSearchState = {
stores: {},
changeStoreValue: jest.fn(),
}
const wrapper = ({ children }: { children: React.ReactNode }) => (
<SearchContext.Provider value={fakeSearchState}>
<SearchProvider stores={['product']}>{children}</SearchProvider>
</SearchContext.Provider>
)
const { result } = renderHook(() => useSearch('product'), { wrapper })
expect(result.current.search).toBeDefined()
})

it('should update search state with setSearch', () => {
const fakeSearchState = {
stores: {},
changeStoreValue: jest.fn(),
}
const wrapper = ({ children }: { children: React.ReactNode }) => (
<SearchContext.Provider value={fakeSearchState}>
<SearchProvider stores={['product']}>{children}</SearchProvider>
</SearchContext.Provider>
)
const { result } = renderHook(() => useSearch('product'), { wrapper })
act(() => {
result.current.setSearch('product_search_value')
})
expect(result.current.search).toEqual('product_search_value')
})

it('should filter data with search value (array of string)', () => {
const fakeSearchContextValue: SearchContextType = {
stores: {},
changeStoreValue: jest.fn(),
}

const fakeItems: string[] = ['product1', 'product2', 'product3', 'product3', 'product4']
const fakeUseSearchOptions: UseSearchOptions = { items: fakeItems }

const wrapper = ({ children }: { children: React.ReactNode }) => (
<SearchContext.Provider value={fakeSearchContextValue}>
<SearchProvider stores={['products']}>{children}</SearchProvider>
</SearchContext.Provider>
)
const { result } = renderHook(() => useSearch('products', fakeUseSearchOptions), { wrapper })
act(() => {
result.current.setSearch('product3')
})
expect(result.current.searchResult).toEqual(['product3', 'product3'])
})

it('should filter data with search value (array of object - with correct searchProp)', () => {
const fakeSearchContextValue: SearchContextType = {
stores: {},
changeStoreValue: jest.fn(),
}

const fakeItems = [
{ name: 'product1' },
{ name: 'product2' },
{ name: 'product3' },
{ name: 'product3' },
{ name: 'product4' },
]
const fakeUseSearchOptions: UseSearchOptions<{ name: string }> = { items: fakeItems, searchProp: 'name' }

const wrapper = ({ children }: { children: React.ReactNode }) => (
<SearchContext.Provider value={fakeSearchContextValue}>
<SearchProvider stores={['products']}>{children}</SearchProvider>
</SearchContext.Provider>
)
const { result } = renderHook(() => useSearch('products', fakeUseSearchOptions), { wrapper })
act(() => {
result.current.setSearch('product3')
})
expect(result.current.searchResult).toHaveLength(2)
})

it('should filter data with search value (array of object - with non-correct searchProp)', () => {
const fakeSearchContextValue: SearchContextType = {
stores: {},
changeStoreValue: jest.fn(),
}

const fakeItems = [{ name: 'product1' }, { name: 'product2' }]
const fakeUseSearchOptions: UseSearchOptions<{ name: string }> = { items: fakeItems, searchProp: 'name1' }

const wrapper = ({ children }: { children: React.ReactNode }) => (
<SearchContext.Provider value={fakeSearchContextValue}>
<SearchProvider stores={['products']}>{children}</SearchProvider>
</SearchContext.Provider>
)
const { result } = renderHook(() => useSearch('products', fakeUseSearchOptions), { wrapper })
act(() => {
result.current.setSearch('product1')
})
expect(result.current.searchResult).toHaveLength(0)
})
})
2 changes: 1 addition & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export type SearchContextType = {
*/
export interface SearchProviderProps {
children: React.ReactNode
stores: string[]
stores?: string[]
}

export type InferStoreNames<T extends string[]> = T[number]
Expand Down
8 changes: 6 additions & 2 deletions src/utils/filter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,15 @@
*/

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

0 comments on commit f7d02db

Please sign in to comment.