@nativerent/js-utils 1.2.2 → 1.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nativerent/js-utils",
3
- "version": "1.2.2",
3
+ "version": "1.2.3",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
package/src/index.ts CHANGED
@@ -791,7 +791,10 @@ export function flattenObject(
791
791
  const val = obj[k];
792
792
  const key = `${pre}${k}`;
793
793
 
794
- if (isObject(val) || Array.isArray(val)) {
794
+ if (
795
+ (isObject(val) || Array.isArray(val)) &&
796
+ getObjectKeys(obj[k]).length > 0
797
+ ) {
795
798
  Object.assign(acc, flattenObject(val, key));
796
799
  } else {
797
800
  acc[key] = val;
@@ -906,3 +909,17 @@ export function autoSizeText(
906
909
 
907
910
  resizeText();
908
911
  }
912
+
913
+ export async function monitor(
914
+ fn: Function,
915
+ interval: number = 10,
916
+ attempts: number = 100,
917
+ ): Promise<boolean> {
918
+ for (let i = 0; i < attempts; i++) {
919
+ if (await fn()) return true;
920
+ if (i < attempts - 1) {
921
+ await new Promise<void>((r) => setTimeout(r, interval));
922
+ }
923
+ }
924
+ return false;
925
+ }
package/tests/tests.ts CHANGED
@@ -72,6 +72,7 @@ import {
72
72
  toCamelCase,
73
73
  toPercent,
74
74
  toSnakeCase,
75
+ monitor,
75
76
  } from "../src";
76
77
 
77
78
  test.each([
@@ -2233,6 +2234,134 @@ describe("autoSizeText (using real getNumericStyleProp)", () => {
2233
2234
  });
2234
2235
  });
2235
2236
 
2237
+ describe("monitor", () => {
2238
+ beforeEach(() => jest.useFakeTimers());
2239
+
2240
+ afterEach(() => {
2241
+ jest.useRealTimers();
2242
+ jest.restoreAllMocks();
2243
+ });
2244
+
2245
+ test("returns true immediately when fn resolves true on first attempt (no sleep)", async () => {
2246
+ const fn = jest.fn().mockResolvedValue(true);
2247
+
2248
+ const promise = monitor(fn, 10, 100);
2249
+
2250
+ // fn resolves immediately; no timers needed
2251
+ await expect(promise).resolves.toBe(true);
2252
+ expect(fn).toHaveBeenCalledTimes(1);
2253
+ // no sleeps scheduled
2254
+ expect(jest.getTimerCount()).toBe(0);
2255
+ });
2256
+
2257
+ test("retries until fn resolves true, sleeping between attempts", async () => {
2258
+ const fn = jest
2259
+ .fn()
2260
+ .mockResolvedValueOnce(false)
2261
+ .mockResolvedValueOnce(false)
2262
+ .mockResolvedValueOnce(true);
2263
+
2264
+ const promise = monitor(fn, 25, 10);
2265
+
2266
+ // attempt 1 happens immediately
2267
+ await Promise.resolve();
2268
+ expect(fn).toHaveBeenCalledTimes(1);
2269
+
2270
+ // sleep 25ms -> attempt 2
2271
+ await jest.advanceTimersByTimeAsync(25);
2272
+ expect(fn).toHaveBeenCalledTimes(2);
2273
+
2274
+ // sleep 25ms -> attempt 3 succeeds
2275
+ await jest.advanceTimersByTimeAsync(25);
2276
+ await expect(promise).resolves.toBe(true);
2277
+
2278
+ expect(fn).toHaveBeenCalledTimes(3);
2279
+ });
2280
+
2281
+ test("returns false after exhausting attempts", async () => {
2282
+ const fn = jest.fn().mockResolvedValue(false);
2283
+
2284
+ const promise = monitor(fn, 10, 3);
2285
+
2286
+ // attempt 1
2287
+ await Promise.resolve();
2288
+ expect(fn).toHaveBeenCalledTimes(1);
2289
+
2290
+ // attempt 2
2291
+ await jest.advanceTimersByTimeAsync(10);
2292
+ expect(fn).toHaveBeenCalledTimes(2);
2293
+
2294
+ // attempt 3 (last) - no further sleep
2295
+ await jest.advanceTimersByTimeAsync(10);
2296
+ await expect(promise).resolves.toBe(false);
2297
+
2298
+ expect(fn).toHaveBeenCalledTimes(3);
2299
+ expect(jest.getTimerCount()).toBe(0);
2300
+ });
2301
+
2302
+ test("does not sleep after the last attempt (even if fn returns false)", async () => {
2303
+ const fn = jest.fn().mockResolvedValue(false);
2304
+
2305
+ const promise = monitor(fn, 50, 2);
2306
+
2307
+ // attempt 1 then one sleep scheduled
2308
+ await Promise.resolve();
2309
+ expect(fn).toHaveBeenCalledTimes(1);
2310
+ expect(jest.getTimerCount()).toBe(1);
2311
+
2312
+ // advance once -> attempt 2 (last)
2313
+ await jest.advanceTimersByTimeAsync(50);
2314
+ await expect(promise).resolves.toBe(false);
2315
+
2316
+ // should not schedule another sleep
2317
+ expect(fn).toHaveBeenCalledTimes(2);
2318
+ expect(jest.getTimerCount()).toBe(0);
2319
+ });
2320
+
2321
+ test("uses default interval=10 and attempts=100 when omitted", async () => {
2322
+ const fn = jest.fn().mockResolvedValue(false);
2323
+
2324
+ const promise = monitor(fn); // defaults
2325
+
2326
+ // attempt 1
2327
+ await Promise.resolve();
2328
+ expect(fn).toHaveBeenCalledTimes(1);
2329
+
2330
+ // drive 99 more attempts with 99 sleeps of 10ms each
2331
+ for (let i = 0; i < 99; i++) {
2332
+ await jest.advanceTimersByTimeAsync(10);
2333
+ }
2334
+
2335
+ await expect(promise).resolves.toBe(false);
2336
+ expect(fn).toHaveBeenCalledTimes(100);
2337
+ });
2338
+
2339
+ test("awaits fn each time (supports async fn that resolves later)", async () => {
2340
+ const fn = jest
2341
+ .fn()
2342
+ .mockImplementationOnce(
2343
+ () => new Promise<boolean>((res) => setTimeout(() => res(false), 5)),
2344
+ )
2345
+ .mockImplementationOnce(
2346
+ () => new Promise<boolean>((res) => setTimeout(() => res(true), 5)),
2347
+ );
2348
+
2349
+ const promise = monitor(fn, 20, 5);
2350
+
2351
+ // attempt 1's internal 5ms
2352
+ await jest.advanceTimersByTimeAsync(5);
2353
+ expect(fn).toHaveBeenCalledTimes(1);
2354
+
2355
+ // then monitor sleeps 20ms before attempt 2
2356
+ await jest.advanceTimersByTimeAsync(20);
2357
+ // attempt 2's internal 5ms
2358
+ await jest.advanceTimersByTimeAsync(5);
2359
+
2360
+ await expect(promise).resolves.toBe(true);
2361
+ expect(fn).toHaveBeenCalledTimes(2);
2362
+ });
2363
+ });
2364
+
2236
2365
  function mockRect(el: HTMLElement, rect: Partial<DOMRect>): void {
2237
2366
  jest.spyOn(el, "getBoundingClientRect").mockReturnValue({
2238
2367
  x: 0,