From 2cace2987d5ae0dc651225eab2332c0a18eca7d2 Mon Sep 17 00:00:00 2001 From: Princi Vershwal Date: Thu, 11 Jul 2024 14:02:55 +0530 Subject: [PATCH] Added test for hooks - useSortableIndexedList, usePagination (admin-x-design-system) Ref ENG-1351 ENG-1373 --- apps/admin-x-design-system/package.json | 5 +- .../{hello.test.ts => unit/hello.test.js} | 2 +- .../test/unit/hooks/usePagination.test.ts | 116 ++++++++++++++ .../unit/hooks/useSortableIndexedList.test.ts | 150 ++++++++++++++++++ yarn.lock | 15 ++ 5 files changed, 286 insertions(+), 2 deletions(-) rename apps/admin-x-design-system/test/{hello.test.ts => unit/hello.test.js} (81%) create mode 100644 apps/admin-x-design-system/test/unit/hooks/usePagination.test.ts create mode 100644 apps/admin-x-design-system/test/unit/hooks/useSortableIndexedList.test.ts diff --git a/apps/admin-x-design-system/package.json b/apps/admin-x-design-system/package.json index 413d2a26b9..052a57d1d4 100644 --- a/apps/admin-x-design-system/package.json +++ b/apps/admin-x-design-system/package.json @@ -11,7 +11,8 @@ "scripts": { "build": "concurrently \"vite build\" \"tsc -p tsconfig.declaration.json\"", "prepare": "yarn build", - "test": "yarn test:types", + "test": "yarn test:unit && yarn test:types", + "test:unit": "yarn nx build && vitest run", "test:types": "tsc --noEmit", "lint:code": "eslint --ext .js,.ts,.cjs,.tsx src/ --cache", "lint": "yarn lint:code && yarn lint:test", @@ -36,6 +37,7 @@ "@storybook/react-vite": "7.6.4", "@storybook/testing-library": "0.2.2", "@testing-library/react": "14.1.0", + "@testing-library/react-hooks" : "8.0.1", "@vitejs/plugin-react": "4.2.1", "c8": "8.0.1", "eslint-plugin-react-hooks": "4.6.0", @@ -43,6 +45,7 @@ "eslint-plugin-tailwindcss": "3.13.0", "jsdom": "24.1.0", "mocha": "10.2.0", + "chai": "4.3.8", "react": "18.3.1", "react-dom": "18.3.1", "rollup-plugin-node-builtins": "2.1.2", diff --git a/apps/admin-x-design-system/test/hello.test.ts b/apps/admin-x-design-system/test/unit/hello.test.js similarity index 81% rename from apps/admin-x-design-system/test/hello.test.ts rename to apps/admin-x-design-system/test/unit/hello.test.js index e66b88fad4..926450737b 100644 --- a/apps/admin-x-design-system/test/hello.test.ts +++ b/apps/admin-x-design-system/test/unit/hello.test.js @@ -3,6 +3,6 @@ import assert from 'assert/strict'; describe('Hello world', function () { it('Runs a test', function () { // TODO: Write me! - assert.ok(require('../')); + assert.equal(1, 1); }); }); diff --git a/apps/admin-x-design-system/test/unit/hooks/usePagination.test.ts b/apps/admin-x-design-system/test/unit/hooks/usePagination.test.ts new file mode 100644 index 0000000000..85ed5af4a7 --- /dev/null +++ b/apps/admin-x-design-system/test/unit/hooks/usePagination.test.ts @@ -0,0 +1,116 @@ +import {expect} from 'chai'; +import {renderHook, act} from '@testing-library/react-hooks'; +import {usePagination, PaginationMeta, PaginationData} from '../../../src/hooks/usePagination'; + +describe('usePagination', function () { + const initialMeta: PaginationMeta = { + limit: 10, + pages: 5, + total: 50, + next: null, + prev: null + }; + + it('should initialize with the given meta and page', function () { + const {result} = renderHook(() => usePagination({ + meta: initialMeta, + limit: 10, + page: 1, + setPage: () => {} + }) + ); + + const expectedData: PaginationData = { + page: 1, + pages: initialMeta.pages, + total: initialMeta.total, + limit: initialMeta.limit, + setPage: result.current.setPage, + nextPage: result.current.nextPage, + prevPage: result.current.prevPage + }; + + expect(result.current).to.deep.equal(expectedData); + }); + + it('should update page correctly when nextPage and prevPage are called', function () { + let currentPage = 1; + const setPage = (newPage: number) => { + currentPage = newPage; + }; + + const {result} = renderHook(() => usePagination({ + meta: initialMeta, + limit: 10, + page: currentPage, + setPage + }) + ); + + act(() => { + result.current.nextPage(); + }); + + expect(currentPage).to.equal(2); + + act(() => { + result.current.prevPage(); + }); + + expect(currentPage).to.equal(1); + }); + + it('should update page correctly when setPage is called', function () { + let currentPage = 3; + const setPage = (newPage: number) => { + currentPage = newPage; + }; + + const {result} = renderHook(() => usePagination({ + meta: initialMeta, + limit: 10, + page: currentPage, + setPage + }) + ); + + const newPage = 5; + + act(() => { + result.current.setPage(newPage); + }); + + expect(currentPage).to.equal(newPage); + }); + + it('should handle edge cases where meta.pages < page when setting meta', function () { + let currentPage = 5; + const setPage = (newPage: number) => { + currentPage = newPage; + }; + + const {rerender} = renderHook( + ({meta}) => usePagination({ + meta, + limit: 10, + page: currentPage, + setPage + }), + {initialProps: {meta: initialMeta}} + ); + + const updatedMeta: PaginationMeta = { + limit: 10, + pages: 4, + total: 40, + next: null, + prev: null + }; + + act(() => { + rerender({meta: updatedMeta}); + }); + + expect(currentPage).to.equal(4); + }); +}); diff --git a/apps/admin-x-design-system/test/unit/hooks/useSortableIndexedList.test.ts b/apps/admin-x-design-system/test/unit/hooks/useSortableIndexedList.test.ts new file mode 100644 index 0000000000..9a8f444915 --- /dev/null +++ b/apps/admin-x-design-system/test/unit/hooks/useSortableIndexedList.test.ts @@ -0,0 +1,150 @@ +import {expect} from 'chai'; +import {renderHook, act} from '@testing-library/react-hooks'; +import useSortableIndexedList from '../../../src/hooks/useSortableIndexedList'; +import sinon from 'sinon'; + +describe('useSortableIndexedList', function () { + // Mock initial items and blank item + const initialItems = [{name: 'Item 1'}, {name: 'Item 2'}]; + const blankItem = {name: ''}; + + // Mock canAddNewItem function + const canAddNewItem = (item: { name: string }) => !!item.name; + + it('should initialize with the given items', function () { + const setItems = sinon.spy(); + + const {result} = renderHook(() => useSortableIndexedList({ + items: initialItems, + setItems, + blank: blankItem, + canAddNewItem + }) + ); + + // Assert initial items setup correctly + expect(result.current.items).to.deep.equal(initialItems.map((item, index) => ({item, id: index.toString()}))); + }); + + it('should add a new item', function () { + let items = initialItems; + const setItems = (newItems: any[]) => { + items = newItems; + }; + + const {result} = renderHook(() => useSortableIndexedList({ + items, + setItems, + blank: blankItem, + canAddNewItem + }) + ); + + act(() => { + result.current.setNewItem({name: 'New Item'}); + result.current.addItem(); + }); + + // Assert items updated correctly after adding new item + expect(items).to.deep.equal([...initialItems, {name: 'New Item'}]); + }); + + it('should update an item', function () { + let items = initialItems; + const setItems = (newItems: any[]) => { + items = newItems; + }; + + const {result} = renderHook(() => useSortableIndexedList({ + items, + setItems, + blank: blankItem, + canAddNewItem + }) + ); + + act(() => { + result.current.updateItem('0', {name: 'Updated Item 1'}); + }); + + // Assert item updated correctly + expect(items[0]).to.deep.equal({name: 'Updated Item 1'}); + }); + + it('should remove an item', function () { + let items = initialItems; + const setItems = (newItems: any[]) => { + items = newItems; + }; + + const {result} = renderHook(() => useSortableIndexedList({ + items, + setItems, + blank: blankItem, + canAddNewItem + }) + ); + + act(() => { + result.current.removeItem('0'); + }); + + // Assert item removed correctly + expect(items).to.deep.equal([initialItems[1]]); + }); + + it('should move an item', function () { + let items = initialItems; + const setItems = (newItems: any[]) => { + items = newItems; + }; + + const {result} = renderHook(() => useSortableIndexedList({ + items, + setItems, + blank: blankItem, + canAddNewItem + }) + ); + + act(() => { + result.current.moveItem('0', '1'); + }); + + // Assert item moved correctly + expect(items).to.deep.equal([initialItems[1], initialItems[0]]); + }); + + it('should not setItems for deeply equal items regardless of property order', function () { + const setItems = sinon.spy(); + const initialItem = [{name: 'Item 1', url: 'http://example.com'}]; + const blankItem1 = {name: '', url: ''}; + + const {rerender} = renderHook( + // eslint-disable-next-line + ({items, setItems}) => useSortableIndexedList({ + items, + setItems, + blank: blankItem1, + canAddNewItem + }), + { + initialProps: { + items: initialItem, + setItems + } + } + ); + + expect(setItems.callCount).to.equal(0); + + // Re-render with items in different order but same content + rerender({ + items: [{url: 'http://example.com', name: 'Item 1'}], + setItems + }); + + // Expect no additional calls because the items are deeply equal + expect(setItems.callCount).to.equal(0); + }); +}); diff --git a/yarn.lock b/yarn.lock index 1ab878fa60..1dbd6d4252 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7810,6 +7810,14 @@ lodash "^4.17.15" redent "^3.0.0" +"@testing-library/react-hooks@8.0.1": + version "8.0.1" + resolved "https://registry.yarnpkg.com/@testing-library/react-hooks/-/react-hooks-8.0.1.tgz#0924bbd5b55e0c0c0502d1754657ada66947ca12" + integrity sha512-Aqhl2IVmLt8IovEVarNDFuJDVWVvhnr9/GCU6UUnrYXwgDFF9h2L2o2P9KBni1AST5sT6riAyoukFLyjQUgD/g== + dependencies: + "@babel/runtime" "^7.12.5" + react-error-boundary "^3.1.0" + "@testing-library/react@12.1.5": version "12.1.5" resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-12.1.5.tgz#bb248f72f02a5ac9d949dea07279095fa577963b" @@ -27785,6 +27793,13 @@ react-element-to-jsx-string@^15.0.0: is-plain-object "5.0.0" react-is "18.1.0" +react-error-boundary@^3.1.0: + version "3.1.4" + resolved "https://registry.yarnpkg.com/react-error-boundary/-/react-error-boundary-3.1.4.tgz#255db92b23197108757a888b01e5b729919abde0" + integrity sha512-uM9uPzZJTF6wRQORmSrvOIgt4lJ9MC1sNgEOj2XGsDTRE4kmpWxg7ENK9EWNKJRMAOY9z0MuF4yIfl6gp4sotA== + dependencies: + "@babel/runtime" "^7.12.5" + react-hot-toast@2.4.1: version "2.4.1" resolved "https://registry.yarnpkg.com/react-hot-toast/-/react-hot-toast-2.4.1.tgz#df04295eda8a7b12c4f968e54a61c8d36f4c0994"