All files / scripts/tools/debounce debounce.ts

100% Statements 39/39
100% Branches 9/9
100% Functions 11/11
100% Lines 36/36

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198                                          36x         36x 36x 36x   36x   36x     214x   10x               10x   6x               6x   13x               13x   185x               185x       36x                                             16x 16x 16x 16x 12x                                               9x 9x                                           22x 22x 22x 22x 16x 16x                                             193x 153x 153x 153x 153x     193x               1x  
/* eslint-disable @typescript-eslint/ban-types */
/* eslint-disable @typescript-eslint/no-explicit-any */
 
import { DebounceType } from './debounceType/debounceType';
 
/**
 * Debounce decorator. Inspired by
 * https://stackoverflow.com/questions/44634992/debounce-hostlistener-event.
 *
 * When a function is decorated with this, it won't be executed excessively
 * repeatidly. There are multiple {@link DebounceType} that govern the
 * behaviour.
 *
 * @param delay The acceptable delay between function calls
 * @param type The {@link DebounceType}
 * @returns The `MethodDecorator`
 */
export function debounce(
  delay = 200,
  type = DebounceType.PERIODIC
): MethodDecorator {
  return (
    target: Object,
    propertyKey: string | Symbol,
    descriptor: PropertyDescriptor
  ) => {
    const original = descriptor.value;
    const key = `__timeout__${propertyKey}`;
    const keyTimeStarted = key + '__timeStarted';
 
    descriptor[keyTimeStarted as keyof PropertyDescriptor] = false;
 
    descriptor.value = function (...args: unknown[]) {
      //https://stackoverflow.com/questions/59034388/jasmine-spying-an-exported-function-that-is-called-by-an-another-function-does
      //https://medium.com/@DavideRama/mock-spy-exported-functions-within-a-single-module-in-jest-cdf2b61af642
      switch (type) {
        case DebounceType.IMMEDIATE:
          exportedForTesting.immediate(
            this,
            key,
            keyTimeStarted,
            original,
            delay,
            args
          );
          break;
        case DebounceType.END:
          exportedForTesting.end(
            this,
            key,
            keyTimeStarted,
            original,
            delay,
            args
          );
          break;
        case DebounceType.BOTH:
          exportedForTesting.both(
            this,
            key,
            keyTimeStarted,
            original,
            delay,
            args
          );
          break;
        case DebounceType.PERIODIC:
          exportedForTesting.periodic(
            this,
            key,
            keyTimeStarted,
            original,
            delay,
            args
          );
          break;
      }
    };
 
    return descriptor;
  };
}
 
/**
 * Method used for {@link DebounceType} immediate behaviour
 *
 * @param target The instance calling the decorated method
 * @param key The key used as a field to track the elpased time
 * @param keyTimeStarted The key used as a field to track whether the debouncing
 *   process has started
 * @param original The decorated method
 * @param delay The delay
 * @param args The method args
 */
function immediate(
  target: Object,
  key: string,
  keyTimeStarted: string,
  original: any,
  delay: number,
  args: unknown[]
) {
  if (!(target as any)[keyTimeStarted]) original.apply(target, args);
  (target as any)[keyTimeStarted] = true;
  clearTimeout((target as any)[key]);
  (target as any)[key] = setTimeout(
    () => ((target as any)[keyTimeStarted] = false),
    delay
  );
}
 
/**
 * Method used for {@link DebounceType} end behaviour
 *
 * @param target The instance calling the decorated method
 * @param key The key used as a field to track the elpased time
 * @param keyTimeStarted The key used as a field to track whether the debouncing
 *   process has started
 * @param original The decorated method
 * @param delay The delay
 * @param args The method args
 */
function end(
  target: Object,
  key: string,
  _keyTimeStarted: string,
  original: any,
  delay: number,
  args: unknown[]
) {
  clearTimeout((target as any)[key]);
  (target as any)[key] = setTimeout(() => original.apply(target, args), delay);
}
 
/**
 * Method used for {@link DebounceType} both behaviour
 *
 * @param target The instance calling the decorated method
 * @param key The key used as a field to track the elpased time
 * @param keyTimeStarted The key used as a field to track whether the debouncing
 *   process has started
 * @param original The decorated method
 * @param delay The delay
 * @param args The method args
 */
function both(
  target: Object,
  key: string,
  keyTimeStarted: string,
  original: any,
  delay: number,
  args: unknown[]
) {
  if (!(target as any)[keyTimeStarted]) original.apply(target, args);
  (target as any)[keyTimeStarted] = true;
  clearTimeout((target as any)[key]);
  (target as any)[key] = setTimeout(() => {
    (target as any)[keyTimeStarted] = false;
    original.apply(target, args);
  }, delay);
}
 
/**
 * Method used for {@link DebounceType} periodic behaviour
 *
 * @param target The instance calling the decorated method
 * @param key The key used as a field to track the elpased time
 * @param keyTimeStarted The key used as a field to track whether the debouncing
 *   process has started
 * @param original The decorated method
 * @param delay The delay
 * @param args The method args
 */
function periodic(
  target: Object,
  key: string,
  keyTimeStarted: string,
  original: any,
  delay: number,
  args: unknown[]
) {
  if (!(target as any)[keyTimeStarted]) {
    original.apply(target, args);
    (target as any)[key] = setTimeout(() => {
      (target as any)[keyTimeStarted] = false;
      original.apply(target, args);
    }, delay);
  }
  (target as any)[keyTimeStarted] = true;
}
 
/**
 * Functions exported for unit testing purposes.
 * https://stackoverflow.com/questions/59034388/jasmine-spying-an-exported-function-that-is-called-by-an-another-function-does
 * https://medium.com/@DavideRama/mock-spy-exported-functions-within-a-single-module-in-jest-cdf2b61af642
 */
export const exportedForTesting = { immediate, end, both, periodic };