From 6875e13d60cbbb3e94c19e55a10d866be0d5713f Mon Sep 17 00:00:00 2001 From: petterive Date: Mon, 5 Aug 2019 22:51:05 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20add=20useUpsert?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/__tests__/useUpsert.test.ts | 74 +++++++++++++++++++++++++++++++++ src/useUpsert.ts | 37 +++++++++++++++++ 2 files changed, 111 insertions(+) create mode 100644 src/__tests__/useUpsert.test.ts create mode 100644 src/useUpsert.ts diff --git a/src/__tests__/useUpsert.test.ts b/src/__tests__/useUpsert.test.ts new file mode 100644 index 0000000000..6dabdb5307 --- /dev/null +++ b/src/__tests__/useUpsert.test.ts @@ -0,0 +1,74 @@ +import { act, renderHook } from '@testing-library/react-hooks'; +import useUpsert from '../useUpsert'; + +interface TestItem { + id: string; + text: string; +} + +const testItems: TestItem[] = [{ id: '1', text: '1' }, { id: '2', text: '2' }]; + +const itemsAreEqual = (a: TestItem, b: TestItem) => { + return a.id === b.id; +}; + +const setUp = (initialList: TestItem[] = []) => renderHook(() => useUpsert(itemsAreEqual, initialList)); + +describe('useUpsert', () => { + describe('initialization', () => { + const { result } = setUp(testItems); + const [list, utils] = result.current; + + it('properly initiates the list content', () => { + expect(list).toEqual(testItems); + }); + + it('returns an upsert function', () => { + expect(utils.upsert).toBeInstanceOf(Function); + }); + }); + + describe('upserting a new item', () => { + const { result } = setUp(testItems); + const [, utils] = result.current; + + const newItem: TestItem = { + id: '3', + text: '3', + }; + act(() => { + utils.upsert(newItem); + }); + + it('inserts a new item', () => { + expect(result.current[0]).toContain(newItem); + }); + it('works immutably', () => { + expect(result.current[0]).not.toBe(testItems); + }); + }); + + describe('upserting an existing item', () => { + const { result } = setUp(testItems); + const [, utils] = result.current; + + const newItem: TestItem = { + id: '2', + text: '4', + }; + act(() => { + utils.upsert(newItem); + }); + const updatedList = result.current[0]; + + it('has the same length', () => { + expect(updatedList).toHaveLength(testItems.length); + }); + it('updates the item', () => { + expect(updatedList).toContain(newItem); + }); + it('works immutably', () => { + expect(updatedList).not.toBe(testItems); + }); + }); +}); diff --git a/src/useUpsert.ts b/src/useUpsert.ts new file mode 100644 index 0000000000..29387226e6 --- /dev/null +++ b/src/useUpsert.ts @@ -0,0 +1,37 @@ +import useList, { Actions as ListActions } from './useList'; + +export interface Actions extends ListActions { + upsert: (item: T) => void; +} + +const useUpsert = ( + itemsAreTheSame: (upsertedItem: T, existingItem: T) => boolean, + initialList: T[] = [] +): [T[], Actions] => { + const [items, actions] = useList(initialList); + + const upsert = (upsertedItem: T) => { + const itemAlreadyExists = items.find(item => itemsAreTheSame(upsertedItem, item)); + if (itemAlreadyExists) { + return actions.set( + items.map(existingItem => { + if (itemsAreTheSame(upsertedItem, existingItem)) { + return upsertedItem; + } + return existingItem; + }) + ); + } + return actions.push(upsertedItem); + }; + + return [ + items, + { + ...actions, + upsert, + }, + ]; +}; + +export default useUpsert;