Skip to content

Commit

Permalink
feat(use-debounce): create useDebounce composable to deprecate Deboun…
Browse files Browse the repository at this point in the history
…ce decorator

EMP-4087
  • Loading branch information
joseacabaneros committed May 20, 2024
1 parent 9485e45 commit 8416b58
Show file tree
Hide file tree
Showing 9 changed files with 120 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ import { WireMetadata } from '../../wiring/wiring.types';
* execution.
* @returns Decorator that creates a subscription to an {@link XEvent} and un-subscribes on the
* beforeDestroy hook.
*
* @public
* @deprecated Use {@link useXBus} composable instead.
*/
export function XOn<Event extends XEvent>(
xEvent: Event | Event[] | ((component: Vue) => Event | Event[]),
Expand Down Expand Up @@ -141,7 +143,9 @@ interface SubscriptionMetadata<Event extends XEvent> {
* @param xEvent - The event to emit.
* @param watcherOptions - Options for Vue's watcher.
* @returns Decorator that makes the component emit an event when the decorated property changes.
*
* @public
* @deprecated Use {@link useXBus} composable instead.
*/
export function XEmit<Event extends XEvent>(
xEvent: Event,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { DebounceOptions, DecoratorFor } from '../../utils/types';
* @returns Decorator that applies debounce.
*
* @public
* @deprecated Use {@link useDebounce} composable instead.
*/
export function Debounce(
debounceTimeInMs: number,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ type AnyInjectKey<Type> = XInjectKey<Type> | string;
* type.
*
* @public
* @deprecated Use native Vue `provide` instead.
*/
export function XProvide<Type>(provideKey: AnyInjectKey<Type>): DecoratorFor<Type> {
return createDecorator((options, componentKey) => {
Expand Down Expand Up @@ -108,6 +109,7 @@ export function XProvide<Type>(provideKey: AnyInjectKey<Type>): DecoratorFor<Typ
* \@XInject(myKey)
*
* @public
* @deprecated Use native Vue `inject` instead.
*/
export function XInject<Type>(
injectKey: AnyInjectKey<Type>,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ import { ExtractGetters, ExtractState, XModuleName } from '../../x-modules/x-mod
* @param module - The {@link XModuleName} of the getter.
* @param path - The state path.
* @returns Decorator with the state properties of the module.
*
* @public
* @deprecated Use {@link useState} composable instead.
*/
export function State<Module extends XModuleName, Path extends keyof ExtractState<Module>>(
module: Module,
Expand All @@ -38,7 +40,9 @@ export function State<Module extends XModuleName, Path extends keyof ExtractStat
* @param module - The {@link XModuleName} of the getter.
* @param getter - The getter name.
* @returns Decorator with the getters of the module.
*
* @public
* @deprecated Use {@link useGetter} composable instead.
*/
export function Getter<Module extends XModuleName, GetterName extends keyof ExtractGetters<Module>>(
module: Module,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { mount } from '@vue/test-utils';
import { DebounceOptions } from '../../utils/types';
import { useDebounce } from '../use-debounce';

const fnMock = jest.fn();
const fnParamStub = 'It is a parameter';

function render(debounceTimeInMs = 200, debounceOptions: DebounceOptions = {}) {
const wrapper = mount({
setup: () => {
const debouncedFnMock = useDebounce(
(param: string) => fnMock(param),
debounceTimeInMs,
debounceOptions
);
const onClick = () => debouncedFnMock(fnParamStub);
return { onClick };
},
template: `<button @click="onClick">Execute fn debounced</button>`
});

return {
wrapper,
runDebouncedFnMock: async () => await wrapper.trigger('click')
};
}

describe('testing useDebounce composable', () => {
jest.useFakeTimers();

beforeEach(() => {
fnMock.mockClear();
});

it('should debounce the fn with the debounced time', async () => {
const { runDebouncedFnMock } = render();

await runDebouncedFnMock();
expect(fnMock).toHaveBeenCalledTimes(0);
jest.advanceTimersByTime(200);
expect(fnMock).toHaveBeenCalledTimes(1);
expect(fnMock).toHaveBeenCalledWith(fnParamStub);

fnMock.mockClear();
await runDebouncedFnMock();
jest.advanceTimersByTime(100);
await runDebouncedFnMock();
jest.advanceTimersByTime(150);
expect(fnMock).toHaveBeenCalledTimes(0);
jest.advanceTimersByTime(50);
expect(fnMock).toHaveBeenCalledTimes(1);
});

it('should propagate options to the debounce util', async () => {
const { runDebouncedFnMock } = render(200, { leading: true });

await runDebouncedFnMock();
expect(fnMock).toHaveBeenCalledTimes(1);
await runDebouncedFnMock();
jest.advanceTimersByTime(200);
expect(fnMock).toHaveBeenCalledTimes(2);
});

it('should cancel debounce fn when component is unmounted', async () => {
const { wrapper, runDebouncedFnMock } = render();

await runDebouncedFnMock();
jest.advanceTimersByTime(100);
wrapper.destroy();
jest.advanceTimersByTime(500);
expect(fnMock).toHaveBeenCalledTimes(0);
});
});
14 changes: 8 additions & 6 deletions packages/x-components/src/composables/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
export * from './create-use-device.composable';
export * from './use-no-element-render';
export * from './create-use-device';
export * from './use-$x';
export * from './use-register-x-module';
export * from './use-alias-api';
export * from './use-debounce';
export * from './use-getter';
export * from './use-no-element-render';
export * from './use-on-display';
export * from './use-store';
export * from './use-register-x-module';
export * from './use-state';
export * from './use-getter';
export * from './use-alias-api';
export * from './use-store';
export * from './use-x-bus';
26 changes: 26 additions & 0 deletions packages/x-components/src/composables/use-debounce.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { onBeforeUnmount } from 'vue';
import { debounce } from '../utils/debounce';
import { DebounceOptions } from '../utils/types';

/**
* Composable which wraps the function passed as parameter into a debounced function and returns it.
* It also cancels the debounced function when component is unmounted.
*
* @param fn - Function to be debounced.
* @param debounceTimeInMs - Time of debounce in ms.
* @param debounceOptions - The options for the debounce strategy.
* @returns Debounced function obtained from `fn` parameter.
*/
export function useDebounce<Params extends any[]>(
fn: (...args: Params) => void,
debounceTimeInMs: number,
debounceOptions: DebounceOptions = {}
) {
const debouncedFn = debounce(fn, debounceTimeInMs, debounceOptions);

onBeforeUnmount(() => {
debouncedFn.cancel();
});

return debouncedFn;
}
4 changes: 2 additions & 2 deletions packages/x-components/tests/unit/useDevice.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { mount } from 'cypress/vue2';
import { createUseDevice } from '../../src/composables/create-use-device.composable';
import { createUseDevice } from '../../src/composables/create-use-device';

/**
* Mounts a test component displaying all the values of the composable created with
Expand All @@ -21,7 +21,7 @@ function mountTestComponent<Device extends string>(
template: `
<div>
<div v-for="(value, device) in devices" :key="device">
{{ device }}:
{{ device }}:
<span :data-test="device">{{ value.value }}</span>
</div>
</div>
Expand Down

0 comments on commit 8416b58

Please sign in to comment.