Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(use-debounce): create useDebounce composable to deprecate Debounce decorator #1475

Merged
merged 5 commits into from
May 22, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
diegopf marked this conversation as resolved.
Show resolved Hide resolved
});

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);
diegopf marked this conversation as resolved.
Show resolved Hide resolved
});

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';
27 changes: 27 additions & 0 deletions packages/x-components/src/composables/use-debounce.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
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.
* @public
*/
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;
}
1 change: 0 additions & 1 deletion packages/x-components/src/composables/use-getter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import { useStore } from './use-store';
* @param module - The {@link XModuleName} of the getter.
* @param getters - List of getters names.
* @returns The requested getters from the module.
*
* @public
*/
export function useGetter<
Expand Down
6 changes: 6 additions & 0 deletions packages/x-components/src/composables/use-x-bus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { bus as xBus } from '../plugins/x-bus';
* by the `XPlugin` if it was not instantiated and uses the default `xBus` as fallback.
*
* @returns An object with the `on` and `emit` functions.
* @public
*/
export function useXBus(): UseXBusAPI {
const injectedLocation = inject<Ref<FeatureLocation> | FeatureLocation>('location', 'none');
Expand Down Expand Up @@ -87,6 +88,11 @@ interface PrivateExtendedVueComponent extends Vue {
xComponent?: Vue | undefined;
}

/**.
* UseXBus API interface
*
* @public
*/
export interface UseXBusAPI {
/* eslint-disable jsdoc/require-description-complete-sentence */
/** {@inheritDoc XBus.(on:1)} */
Expand Down
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
Loading