-
Notifications
You must be signed in to change notification settings - Fork 99
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add: Extract a useEntitySave hook from EntityComponent
EntityComponent is a render prop component and can be replaced by several hooks.
- Loading branch information
1 parent
7fa7361
commit 75b9024
Showing
3 changed files
with
191 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
/* SPDX-FileCopyrightText: 2025 Greenbone AG | ||
* | ||
* SPDX-License-Identifier: AGPL-3.0-or-later | ||
*/ | ||
|
||
import {describe, test, expect, testing} from '@gsa/testing'; | ||
import useEntitySave from 'web/entity/hooks/useEntitySave'; | ||
import {rendererWith, wait} from 'web/utils/Testing'; | ||
|
||
describe('useEntitySave', () => { | ||
test('should allow to save an entity', async () => { | ||
const entity = {id: '123'}; | ||
const saveEntity = testing.fn().mockResolvedValue(entity); | ||
const gmp = { | ||
foo: {save: saveEntity}, | ||
}; | ||
const onSaved = testing.fn(); | ||
const onSaveError = testing.fn(); | ||
const onCreated = testing.fn(); | ||
const onCreateError = testing.fn(); | ||
const onInteraction = testing.fn(); | ||
const {renderHook} = rendererWith({gmp, store: true}); | ||
|
||
const {result} = renderHook(() => | ||
useEntitySave('foo', { | ||
onSaved, | ||
onSaveError, | ||
onCreated, | ||
onCreateError, | ||
onInteraction, | ||
}), | ||
); | ||
expect(result.current).toBeDefined; | ||
result.current(entity); | ||
await wait(); | ||
expect(saveEntity).toHaveBeenCalledWith(entity); | ||
expect(onSaved).toHaveBeenCalledWith(entity); | ||
expect(onSaveError).not.toHaveBeenCalled(); | ||
expect(onInteraction).toHaveBeenCalledOnce(); | ||
}); | ||
|
||
test('should call onSaveError when saving an entity fails', async () => { | ||
const saveEntity = testing.fn().mockRejectedValue(new Error('error')); | ||
const entity = {id: '123'}; | ||
const gmp = { | ||
foo: {save: saveEntity}, | ||
}; | ||
const onSaved = testing.fn(); | ||
const onSaveError = testing.fn(); | ||
const onCreated = testing.fn(); | ||
const onCreateError = testing.fn(); | ||
const onInteraction = testing.fn(); | ||
const {renderHook} = rendererWith({gmp, store: true}); | ||
|
||
const {result} = renderHook(() => | ||
useEntitySave('foo', { | ||
onSaved, | ||
onSaveError, | ||
onCreated, | ||
onCreateError, | ||
onInteraction, | ||
}), | ||
); | ||
expect(result.current).toBeDefined; | ||
result.current(entity); | ||
await wait(); | ||
expect(saveEntity).toHaveBeenCalledWith(entity); | ||
expect(onSaved).not.toHaveBeenCalled(); | ||
expect(onSaveError).toHaveBeenCalledOnce(); | ||
expect(onInteraction).toHaveBeenCalledOnce(); | ||
}); | ||
|
||
test('should allow to create an entity', async () => { | ||
const entity = {name: 'foo'}; | ||
const createEntity = testing.fn().mockResolvedValue(entity); | ||
const gmp = { | ||
foo: {create: createEntity}, | ||
}; | ||
const onSaved = testing.fn(); | ||
const onSaveError = testing.fn(); | ||
const onCreated = testing.fn(); | ||
const onCreateError = testing.fn(); | ||
const onInteraction = testing.fn(); | ||
const {renderHook} = rendererWith({gmp, store: true}); | ||
|
||
const {result} = renderHook(() => | ||
useEntitySave('foo', { | ||
onSaved, | ||
onSaveError, | ||
onCreated, | ||
onCreateError, | ||
onInteraction, | ||
}), | ||
); | ||
expect(result.current).toBeDefined; | ||
result.current(entity); | ||
await wait(); | ||
expect(createEntity).toHaveBeenCalledWith(entity); | ||
expect(onCreated).toHaveBeenCalledWith(entity); | ||
expect(onCreateError).not.toHaveBeenCalled(); | ||
expect(onInteraction).toHaveBeenCalledOnce(); | ||
}); | ||
|
||
test('should call onCreateError when creating an entity fails', async () => { | ||
const createEntity = testing.fn().mockRejectedValue(new Error('error')); | ||
const entity = {name: 'foo'}; | ||
const gmp = { | ||
foo: {create: createEntity}, | ||
}; | ||
const onSaved = testing.fn(); | ||
const onSaveError = testing.fn(); | ||
const onCreated = testing.fn(); | ||
const onCreateError = testing.fn(); | ||
const onInteraction = testing.fn(); | ||
const {renderHook} = rendererWith({gmp, store: true}); | ||
|
||
const {result} = renderHook(() => | ||
useEntitySave('foo', { | ||
onSaved, | ||
onSaveError, | ||
onCreated, | ||
onCreateError, | ||
onInteraction, | ||
}), | ||
); | ||
expect(result.current).toBeDefined; | ||
result.current(entity); | ||
await wait(); | ||
expect(createEntity).toHaveBeenCalledWith(entity); | ||
expect(onCreated).not.toHaveBeenCalled(); | ||
expect(onCreateError).toHaveBeenCalledOnce(); | ||
expect(onInteraction).toHaveBeenCalledOnce(); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
/* SPDX-FileCopyrightText: 2025 Greenbone AG | ||
* | ||
* SPDX-License-Identifier: AGPL-3.0-or-later | ||
*/ | ||
|
||
import {isDefined} from 'gmp/utils/identity'; | ||
import {actionFunction} from 'web/entity/hooks/utils'; | ||
import useGmp from 'web/hooks/useGmp'; | ||
|
||
/** | ||
* Custom hook to handle saving or creating an entity. | ||
* | ||
* @param {string} name - The name of the entity. | ||
* @param {Object} [callbacks={}] - Optional callbacks for various save events. | ||
* @param {Function} [callbacks.onSaveError] - Callback function to be called on save error. | ||
* @param {Function} [callbacks.onSaved] - Callback function to be called when the entity is saved. | ||
* @param {Function} [callbacks.onCreated] - Callback function to be called when the entity is created. | ||
* @param {Function} [callbacks.onCreateError] - Callback function to be called on create error. | ||
* @param {Function} [callbacks.onInteraction] - Callback function to be called on interaction. | ||
* @returns {Function} - A function to handle saving the entity. The function takes an entity as an argument. | ||
* If the entity has an id, it will be saved, otherwise it will be created. | ||
*/ | ||
const useEntitySave = ( | ||
name, | ||
{onSaveError, onSaved, onCreated, onCreateError, onInteraction} = {}, | ||
) => { | ||
const gmp = useGmp(); | ||
const cmd = gmp[name]; | ||
|
||
const handleInteraction = () => { | ||
if (isDefined(onInteraction)) { | ||
onInteraction(); | ||
} | ||
}; | ||
|
||
const handleEntitySave = async data => { | ||
handleInteraction(); | ||
|
||
if (isDefined(data.id)) { | ||
return actionFunction(cmd.save(data), onSaved, onSaveError); | ||
} | ||
|
||
return actionFunction(cmd.create(data), onCreated, onCreateError); | ||
}; | ||
return handleEntitySave; | ||
}; | ||
|
||
export default useEntitySave; |