@usels/core 0.0.1
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/README.md +21 -0
- package/dist/browser/useEventListener/index.d.mts +56 -0
- package/dist/browser/useEventListener/index.d.ts +56 -0
- package/dist/browser/useEventListener/index.js +112 -0
- package/dist/browser/useEventListener/index.js.map +1 -0
- package/dist/browser/useEventListener/index.mjs +88 -0
- package/dist/browser/useEventListener/index.mjs.map +1 -0
- package/dist/browser/useMediaQuery/demo.d.mts +5 -0
- package/dist/browser/useMediaQuery/demo.d.ts +5 -0
- package/dist/browser/useMediaQuery/demo.js +83 -0
- package/dist/browser/useMediaQuery/demo.js.map +1 -0
- package/dist/browser/useMediaQuery/demo.mjs +63 -0
- package/dist/browser/useMediaQuery/demo.mjs.map +1 -0
- package/dist/browser/useMediaQuery/index.d.mts +11 -0
- package/dist/browser/useMediaQuery/index.d.ts +11 -0
- package/dist/browser/useMediaQuery/index.js +89 -0
- package/dist/browser/useMediaQuery/index.js.map +1 -0
- package/dist/browser/useMediaQuery/index.mjs +64 -0
- package/dist/browser/useMediaQuery/index.mjs.map +1 -0
- package/dist/components/Auto/index.d.mts +33 -0
- package/dist/components/Auto/index.d.ts +33 -0
- package/dist/components/Auto/index.js +66 -0
- package/dist/components/Auto/index.js.map +1 -0
- package/dist/components/Auto/index.mjs +34 -0
- package/dist/components/Auto/index.mjs.map +1 -0
- package/dist/elements/useDocumentVisibility/demo.d.mts +5 -0
- package/dist/elements/useDocumentVisibility/demo.d.ts +5 -0
- package/dist/elements/useDocumentVisibility/demo.js +130 -0
- package/dist/elements/useDocumentVisibility/demo.js.map +1 -0
- package/dist/elements/useDocumentVisibility/demo.mjs +114 -0
- package/dist/elements/useDocumentVisibility/demo.mjs.map +1 -0
- package/dist/elements/useDocumentVisibility/index.d.mts +5 -0
- package/dist/elements/useDocumentVisibility/index.d.ts +5 -0
- package/dist/elements/useDocumentVisibility/index.js +45 -0
- package/dist/elements/useDocumentVisibility/index.js.map +1 -0
- package/dist/elements/useDocumentVisibility/index.mjs +21 -0
- package/dist/elements/useDocumentVisibility/index.mjs.map +1 -0
- package/dist/elements/useElementBounding/demo.d.mts +5 -0
- package/dist/elements/useElementBounding/demo.d.ts +5 -0
- package/dist/elements/useElementBounding/demo.js +87 -0
- package/dist/elements/useElementBounding/demo.js.map +1 -0
- package/dist/elements/useElementBounding/demo.mjs +67 -0
- package/dist/elements/useElementBounding/demo.mjs.map +1 -0
- package/dist/elements/useElementBounding/index.d.mts +46 -0
- package/dist/elements/useElementBounding/index.d.ts +46 -0
- package/dist/elements/useElementBounding/index.js +122 -0
- package/dist/elements/useElementBounding/index.js.map +1 -0
- package/dist/elements/useElementBounding/index.mjs +98 -0
- package/dist/elements/useElementBounding/index.mjs.map +1 -0
- package/dist/elements/useElementSize/demo.d.mts +5 -0
- package/dist/elements/useElementSize/demo.d.ts +5 -0
- package/dist/elements/useElementSize/demo.js +83 -0
- package/dist/elements/useElementSize/demo.js.map +1 -0
- package/dist/elements/useElementSize/demo.mjs +63 -0
- package/dist/elements/useElementSize/demo.mjs.map +1 -0
- package/dist/elements/useElementSize/index.d.mts +34 -0
- package/dist/elements/useElementSize/index.d.ts +34 -0
- package/dist/elements/useElementSize/index.js +85 -0
- package/dist/elements/useElementSize/index.js.map +1 -0
- package/dist/elements/useElementSize/index.mjs +61 -0
- package/dist/elements/useElementSize/index.mjs.map +1 -0
- package/dist/elements/useElementVisibility/demo.d.mts +5 -0
- package/dist/elements/useElementVisibility/demo.d.ts +5 -0
- package/dist/elements/useElementVisibility/demo.js +110 -0
- package/dist/elements/useElementVisibility/demo.js.map +1 -0
- package/dist/elements/useElementVisibility/demo.mjs +90 -0
- package/dist/elements/useElementVisibility/demo.mjs.map +1 -0
- package/dist/elements/useElementVisibility/index.d.mts +43 -0
- package/dist/elements/useElementVisibility/index.d.ts +43 -0
- package/dist/elements/useElementVisibility/index.js +58 -0
- package/dist/elements/useElementVisibility/index.js.map +1 -0
- package/dist/elements/useElementVisibility/index.mjs +34 -0
- package/dist/elements/useElementVisibility/index.mjs.map +1 -0
- package/dist/elements/useIntersectionObserver/demo.d.mts +5 -0
- package/dist/elements/useIntersectionObserver/demo.d.ts +5 -0
- package/dist/elements/useIntersectionObserver/demo.js +173 -0
- package/dist/elements/useIntersectionObserver/demo.js.map +1 -0
- package/dist/elements/useIntersectionObserver/demo.mjs +153 -0
- package/dist/elements/useIntersectionObserver/demo.mjs.map +1 -0
- package/dist/elements/useIntersectionObserver/index.d.mts +47 -0
- package/dist/elements/useIntersectionObserver/index.d.ts +47 -0
- package/dist/elements/useIntersectionObserver/index.js +111 -0
- package/dist/elements/useIntersectionObserver/index.js.map +1 -0
- package/dist/elements/useIntersectionObserver/index.mjs +87 -0
- package/dist/elements/useIntersectionObserver/index.mjs.map +1 -0
- package/dist/elements/useMouseInElement/demo.d.mts +5 -0
- package/dist/elements/useMouseInElement/demo.d.ts +5 -0
- package/dist/elements/useMouseInElement/demo.js +104 -0
- package/dist/elements/useMouseInElement/demo.js.map +1 -0
- package/dist/elements/useMouseInElement/demo.mjs +84 -0
- package/dist/elements/useMouseInElement/demo.mjs.map +1 -0
- package/dist/elements/useMouseInElement/index.d.mts +56 -0
- package/dist/elements/useMouseInElement/index.d.ts +56 -0
- package/dist/elements/useMouseInElement/index.js +148 -0
- package/dist/elements/useMouseInElement/index.js.map +1 -0
- package/dist/elements/useMouseInElement/index.mjs +124 -0
- package/dist/elements/useMouseInElement/index.mjs.map +1 -0
- package/dist/elements/useMutationObserver/demo.d.mts +5 -0
- package/dist/elements/useMutationObserver/demo.d.ts +5 -0
- package/dist/elements/useMutationObserver/demo.js +240 -0
- package/dist/elements/useMutationObserver/demo.js.map +1 -0
- package/dist/elements/useMutationObserver/demo.mjs +220 -0
- package/dist/elements/useMutationObserver/demo.mjs.map +1 -0
- package/dist/elements/useMutationObserver/index.d.mts +15 -0
- package/dist/elements/useMutationObserver/index.d.ts +15 -0
- package/dist/elements/useMutationObserver/index.js +69 -0
- package/dist/elements/useMutationObserver/index.js.map +1 -0
- package/dist/elements/useMutationObserver/index.mjs +45 -0
- package/dist/elements/useMutationObserver/index.mjs.map +1 -0
- package/dist/elements/useParentElement/demo.d.mts +5 -0
- package/dist/elements/useParentElement/demo.d.ts +5 -0
- package/dist/elements/useParentElement/demo.js +132 -0
- package/dist/elements/useParentElement/demo.js.map +1 -0
- package/dist/elements/useParentElement/demo.mjs +112 -0
- package/dist/elements/useParentElement/demo.mjs.map +1 -0
- package/dist/elements/useParentElement/index.d.mts +7 -0
- package/dist/elements/useParentElement/index.d.ts +7 -0
- package/dist/elements/useParentElement/index.js +47 -0
- package/dist/elements/useParentElement/index.js.map +1 -0
- package/dist/elements/useParentElement/index.mjs +23 -0
- package/dist/elements/useParentElement/index.mjs.map +1 -0
- package/dist/elements/useRef$/index.js +89 -0
- package/dist/elements/useRef$/index.js.map +1 -0
- package/dist/elements/useRef$/index.mjs +62 -0
- package/dist/elements/useRef$/index.mjs.map +1 -0
- package/dist/elements/useRef_/index.d.mts +60 -0
- package/dist/elements/useRef_/index.d.ts +60 -0
- package/dist/elements/useResizeObserver/demo.d.mts +5 -0
- package/dist/elements/useResizeObserver/demo.d.ts +5 -0
- package/dist/elements/useResizeObserver/demo.js +90 -0
- package/dist/elements/useResizeObserver/demo.js.map +1 -0
- package/dist/elements/useResizeObserver/demo.mjs +70 -0
- package/dist/elements/useResizeObserver/demo.mjs.map +1 -0
- package/dist/elements/useResizeObserver/index.d.mts +36 -0
- package/dist/elements/useResizeObserver/index.d.ts +36 -0
- package/dist/elements/useResizeObserver/index.js +74 -0
- package/dist/elements/useResizeObserver/index.js.map +1 -0
- package/dist/elements/useResizeObserver/index.mjs +49 -0
- package/dist/elements/useResizeObserver/index.mjs.map +1 -0
- package/dist/elements/useWindowFocus/demo.d.mts +5 -0
- package/dist/elements/useWindowFocus/demo.d.ts +5 -0
- package/dist/elements/useWindowFocus/demo.js +104 -0
- package/dist/elements/useWindowFocus/demo.js.map +1 -0
- package/dist/elements/useWindowFocus/demo.mjs +84 -0
- package/dist/elements/useWindowFocus/demo.mjs.map +1 -0
- package/dist/elements/useWindowFocus/index.d.mts +5 -0
- package/dist/elements/useWindowFocus/index.d.ts +5 -0
- package/dist/elements/useWindowFocus/index.js +42 -0
- package/dist/elements/useWindowFocus/index.js.map +1 -0
- package/dist/elements/useWindowFocus/index.mjs +18 -0
- package/dist/elements/useWindowFocus/index.mjs.map +1 -0
- package/dist/elements/useWindowSize/demo.d.mts +5 -0
- package/dist/elements/useWindowSize/demo.d.ts +5 -0
- package/dist/elements/useWindowSize/demo.js +79 -0
- package/dist/elements/useWindowSize/demo.js.map +1 -0
- package/dist/elements/useWindowSize/demo.mjs +59 -0
- package/dist/elements/useWindowSize/demo.mjs.map +1 -0
- package/dist/elements/useWindowSize/index.d.mts +17 -0
- package/dist/elements/useWindowSize/index.d.ts +17 -0
- package/dist/elements/useWindowSize/index.js +96 -0
- package/dist/elements/useWindowSize/index.js.map +1 -0
- package/dist/elements/useWindowSize/index.mjs +76 -0
- package/dist/elements/useWindowSize/index.mjs.map +1 -0
- package/dist/function/get/index.d.mts +45 -0
- package/dist/function/get/index.d.ts +45 -0
- package/dist/function/get/index.js +39 -0
- package/dist/function/get/index.js.map +1 -0
- package/dist/function/get/index.mjs +15 -0
- package/dist/function/get/index.mjs.map +1 -0
- package/dist/function/peek/index.d.mts +46 -0
- package/dist/function/peek/index.d.ts +46 -0
- package/dist/function/peek/index.js +39 -0
- package/dist/function/peek/index.js.map +1 -0
- package/dist/function/peek/index.mjs +15 -0
- package/dist/function/peek/index.mjs.map +1 -0
- package/dist/function/useMayObservableOptions/index.d.mts +59 -0
- package/dist/function/useMayObservableOptions/index.d.ts +59 -0
- package/dist/function/useMayObservableOptions/index.js +109 -0
- package/dist/function/useMayObservableOptions/index.js.map +1 -0
- package/dist/function/useMayObservableOptions/index.mjs +88 -0
- package/dist/function/useMayObservableOptions/index.mjs.map +1 -0
- package/dist/function/useSupported/index.d.mts +6 -0
- package/dist/function/useSupported/index.d.ts +6 -0
- package/dist/function/useSupported/index.js +37 -0
- package/dist/function/useSupported/index.js.map +1 -0
- package/dist/function/useSupported/index.mjs +13 -0
- package/dist/function/useSupported/index.mjs.map +1 -0
- package/dist/function/useWhenMounted/index.d.mts +6 -0
- package/dist/function/useWhenMounted/index.d.ts +6 -0
- package/dist/function/useWhenMounted/index.js +37 -0
- package/dist/function/useWhenMounted/index.js.map +1 -0
- package/dist/function/useWhenMounted/index.mjs +13 -0
- package/dist/function/useWhenMounted/index.mjs.map +1 -0
- package/dist/index.d.mts +24 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.js +63 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +22 -0
- package/dist/index.mjs.map +1 -0
- package/dist/sensors/useScroll/demo.d.mts +5 -0
- package/dist/sensors/useScroll/demo.d.ts +5 -0
- package/dist/sensors/useScroll/demo.js +122 -0
- package/dist/sensors/useScroll/demo.js.map +1 -0
- package/dist/sensors/useScroll/demo.mjs +102 -0
- package/dist/sensors/useScroll/demo.mjs.map +1 -0
- package/dist/sensors/useScroll/index.d.mts +42 -0
- package/dist/sensors/useScroll/index.d.ts +42 -0
- package/dist/sensors/useScroll/index.js +149 -0
- package/dist/sensors/useScroll/index.js.map +1 -0
- package/dist/sensors/useScroll/index.mjs +125 -0
- package/dist/sensors/useScroll/index.mjs.map +1 -0
- package/dist/sensors/useWindowScroll/demo.d.mts +5 -0
- package/dist/sensors/useWindowScroll/demo.d.ts +5 -0
- package/dist/sensors/useWindowScroll/demo.js +85 -0
- package/dist/sensors/useWindowScroll/demo.js.map +1 -0
- package/dist/sensors/useWindowScroll/demo.mjs +65 -0
- package/dist/sensors/useWindowScroll/demo.mjs.map +1 -0
- package/dist/sensors/useWindowScroll/index.d.mts +9 -0
- package/dist/sensors/useWindowScroll/index.d.ts +9 -0
- package/dist/sensors/useWindowScroll/index.js +36 -0
- package/dist/sensors/useWindowScroll/index.js.map +1 -0
- package/dist/sensors/useWindowScroll/index.mjs +12 -0
- package/dist/sensors/useWindowScroll/index.mjs.map +1 -0
- package/dist/shared/configurable.d.mts +21 -0
- package/dist/shared/configurable.d.ts +21 -0
- package/dist/shared/configurable.js +39 -0
- package/dist/shared/configurable.js.map +1 -0
- package/dist/shared/configurable.mjs +12 -0
- package/dist/shared/configurable.mjs.map +1 -0
- package/dist/shared/index.d.mts +4 -0
- package/dist/shared/index.d.ts +4 -0
- package/dist/shared/index.js +31 -0
- package/dist/shared/index.js.map +1 -0
- package/dist/shared/index.mjs +7 -0
- package/dist/shared/index.mjs.map +1 -0
- package/dist/shared/normalizeTargets/index.d.mts +21 -0
- package/dist/shared/normalizeTargets/index.d.ts +21 -0
- package/dist/shared/normalizeTargets/index.js +36 -0
- package/dist/shared/normalizeTargets/index.js.map +1 -0
- package/dist/shared/normalizeTargets/index.mjs +12 -0
- package/dist/shared/normalizeTargets/index.mjs.map +1 -0
- package/dist/shared/utils.d.mts +15 -0
- package/dist/shared/utils.d.ts +15 -0
- package/dist/shared/utils.js +87 -0
- package/dist/shared/utils.js.map +1 -0
- package/dist/shared/utils.mjs +52 -0
- package/dist/shared/utils.mjs.map +1 -0
- package/dist/types.d.mts +52 -0
- package/dist/types.d.ts +52 -0
- package/dist/types.js +17 -0
- package/dist/types.js.map +1 -0
- package/dist/types.mjs +1 -0
- package/dist/types.mjs.map +1 -0
- package/package.json +54 -0
- package/src/browser/useEventListener/index.md +109 -0
- package/src/browser/useEventListener/index.spec.ts +611 -0
- package/src/browser/useEventListener/index.ts +242 -0
- package/src/browser/useMediaQuery/demo.tsx +63 -0
- package/src/browser/useMediaQuery/index.md +43 -0
- package/src/browser/useMediaQuery/index.spec.ts +267 -0
- package/src/browser/useMediaQuery/index.ts +96 -0
- package/src/components/Auto/index.tsx +65 -0
- package/src/elements/useDocumentVisibility/demo.tsx +111 -0
- package/src/elements/useDocumentVisibility/index.md +54 -0
- package/src/elements/useDocumentVisibility/index.spec.ts +114 -0
- package/src/elements/useDocumentVisibility/index.ts +26 -0
- package/src/elements/useElementBounding/demo.tsx +68 -0
- package/src/elements/useElementBounding/index.md +64 -0
- package/src/elements/useElementBounding/index.ts +159 -0
- package/src/elements/useElementSize/demo.tsx +53 -0
- package/src/elements/useElementSize/index.md +65 -0
- package/src/elements/useElementSize/index.spec.ts +295 -0
- package/src/elements/useElementSize/index.ts +100 -0
- package/src/elements/useElementVisibility/deep-observable-pattern.spec.ts +453 -0
- package/src/elements/useElementVisibility/demo.tsx +97 -0
- package/src/elements/useElementVisibility/index.md +98 -0
- package/src/elements/useElementVisibility/index.spec.ts +227 -0
- package/src/elements/useElementVisibility/index.ts +78 -0
- package/src/elements/useIntersectionObserver/demo.tsx +180 -0
- package/src/elements/useIntersectionObserver/index.md +99 -0
- package/src/elements/useIntersectionObserver/index.spec.ts +482 -0
- package/src/elements/useIntersectionObserver/index.ts +149 -0
- package/src/elements/useMouseInElement/demo.tsx +88 -0
- package/src/elements/useMouseInElement/index.md +76 -0
- package/src/elements/useMouseInElement/index.spec.ts +398 -0
- package/src/elements/useMouseInElement/index.ts +209 -0
- package/src/elements/useMutationObserver/demo.tsx +270 -0
- package/src/elements/useMutationObserver/index.md +99 -0
- package/src/elements/useMutationObserver/index.spec.ts +421 -0
- package/src/elements/useMutationObserver/index.ts +66 -0
- package/src/elements/useParentElement/demo.tsx +120 -0
- package/src/elements/useParentElement/index.md +67 -0
- package/src/elements/useParentElement/index.spec.ts +208 -0
- package/src/elements/useParentElement/index.ts +35 -0
- package/src/elements/useRef$/index.md +62 -0
- package/src/elements/useRef$/index.spec.ts +205 -0
- package/src/elements/useRef$/index.ts +137 -0
- package/src/elements/useRef$/useImperativeHandle.spec.ts +339 -0
- package/src/elements/useResizeObserver/demo.tsx +62 -0
- package/src/elements/useResizeObserver/index.md +51 -0
- package/src/elements/useResizeObserver/index.spec.ts +312 -0
- package/src/elements/useResizeObserver/index.ts +106 -0
- package/src/elements/useWindowFocus/demo.tsx +79 -0
- package/src/elements/useWindowFocus/index.md +38 -0
- package/src/elements/useWindowFocus/index.spec.ts +103 -0
- package/src/elements/useWindowFocus/index.ts +21 -0
- package/src/elements/useWindowSize/demo.tsx +51 -0
- package/src/elements/useWindowSize/index.md +55 -0
- package/src/elements/useWindowSize/index.spec.ts +310 -0
- package/src/elements/useWindowSize/index.ts +107 -0
- package/src/function/get/index.md +25 -0
- package/src/function/get/index.spec.ts +87 -0
- package/src/function/get/index.ts +70 -0
- package/src/function/peek/index.spec.ts +97 -0
- package/src/function/peek/index.ts +69 -0
- package/src/function/useMayObservableOptions/index.spec.ts +521 -0
- package/src/function/useMayObservableOptions/index.ts +173 -0
- package/src/function/useSupported/index.md +43 -0
- package/src/function/useSupported/index.spec.ts +116 -0
- package/src/function/useSupported/index.ts +14 -0
- package/src/function/useWhenMounted/index.md +25 -0
- package/src/function/useWhenMounted/index.spec.ts +120 -0
- package/src/function/useWhenMounted/index.ts +16 -0
- package/src/index.ts +25 -0
- package/src/sensors/useScroll/demo.tsx +103 -0
- package/src/sensors/useScroll/index.md +117 -0
- package/src/sensors/useScroll/index.spec.ts +678 -0
- package/src/sensors/useScroll/index.ts +201 -0
- package/src/sensors/useWindowScroll/demo.tsx +78 -0
- package/src/sensors/useWindowScroll/index.md +98 -0
- package/src/sensors/useWindowScroll/index.spec.ts +69 -0
- package/src/sensors/useWindowScroll/index.ts +11 -0
- package/src/shared/configurable.ts +35 -0
- package/src/shared/index.ts +4 -0
- package/src/shared/normalizeTargets/index.spec.ts +76 -0
- package/src/shared/normalizeTargets/index.ts +27 -0
- package/src/shared/utils.ts +67 -0
- package/src/types.ts +56 -0
- package/tsconfig.json +9 -0
- package/tsup.config.ts +10 -0
- package/vitest.config.ts +22 -0
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from "vitest";
|
|
2
|
+
import { peek } from ".";
|
|
3
|
+
import { observable } from "@legendapp/state";
|
|
4
|
+
|
|
5
|
+
describe("peek() - single argument", () => {
|
|
6
|
+
it("returns raw value as-is", () => {
|
|
7
|
+
expect(peek("hello")).toBe("hello");
|
|
8
|
+
expect(peek(42)).toBe(42);
|
|
9
|
+
expect(peek(true)).toBe(true);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it("extracts value from Observable without tracking", () => {
|
|
13
|
+
const obs$ = observable("world");
|
|
14
|
+
expect(peek(obs$)).toBe("world");
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it("handles null and undefined", () => {
|
|
18
|
+
expect(peek(null)).toBe(null);
|
|
19
|
+
expect(peek(undefined)).toBe(undefined);
|
|
20
|
+
expect(peek(observable(null))).toBe(null);
|
|
21
|
+
expect(peek(observable(undefined))).toBe(undefined);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("handles objects", () => {
|
|
25
|
+
const obj = { name: "John" };
|
|
26
|
+
expect(peek(obj)).toEqual(obj);
|
|
27
|
+
expect(peek(observable(obj))).toEqual(obj);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("handles arrays", () => {
|
|
31
|
+
const arr = [1, 2, 3];
|
|
32
|
+
expect(peek(arr)).toEqual(arr);
|
|
33
|
+
expect(peek(observable(arr))).toEqual(arr);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("calls .peek() on Observable, not .get()", () => {
|
|
37
|
+
const obs$ = observable("value");
|
|
38
|
+
const getSpy = vi.spyOn(obs$, "get");
|
|
39
|
+
const peekSpy = vi.spyOn(obs$, "peek");
|
|
40
|
+
|
|
41
|
+
peek(obs$);
|
|
42
|
+
|
|
43
|
+
expect(peekSpy).toHaveBeenCalledTimes(1);
|
|
44
|
+
expect(getSpy).not.toHaveBeenCalled();
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
describe("peek() - two arguments (property access)", () => {
|
|
49
|
+
it("extracts property from raw object", () => {
|
|
50
|
+
const obj = { name: "John", age: 30 };
|
|
51
|
+
expect(peek(obj, "name")).toBe("John");
|
|
52
|
+
expect(peek(obj, "age")).toBe(30);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("extracts property from Observable object", () => {
|
|
56
|
+
const obs$ = observable({ name: "Jane", age: 25 });
|
|
57
|
+
expect(peek(obs$, "name")).toBe("Jane");
|
|
58
|
+
expect(peek(obs$, "age")).toBe(25);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("reads current value non-reactively (two-arg form)", () => {
|
|
62
|
+
// Verifies behavioral correctness: peek returns the correct value
|
|
63
|
+
// without relying on spy internals (Legend-State object observables
|
|
64
|
+
// expose .get/.peek through Proxy, not as own enumerable properties).
|
|
65
|
+
const obs$ = observable({ initialValue: true, rootMargin: "0px" });
|
|
66
|
+
expect(peek(obs$, "initialValue")).toBe(true);
|
|
67
|
+
expect(peek(obs$, "rootMargin")).toBe("0px");
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("returns undefined for missing keys", () => {
|
|
71
|
+
const obj = { name: "John" };
|
|
72
|
+
expect(peek(obj, "age" as any)).toBe(undefined);
|
|
73
|
+
expect(peek(observable(obj), "age" as any)).toBe(undefined);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("handles null and undefined gracefully", () => {
|
|
77
|
+
expect(peek(null, "key" as any)).toBe(undefined);
|
|
78
|
+
expect(peek(undefined, "key" as any)).toBe(undefined);
|
|
79
|
+
expect(peek(observable(null), "key" as any)).toBe(undefined);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("preserves property value types", () => {
|
|
83
|
+
const obj = {
|
|
84
|
+
str: "text",
|
|
85
|
+
num: 42,
|
|
86
|
+
bool: true,
|
|
87
|
+
arr: [1, 2, 3],
|
|
88
|
+
nested: { value: "deep" },
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
expect(peek(obj, "str")).toBe("text");
|
|
92
|
+
expect(peek(obj, "num")).toBe(42);
|
|
93
|
+
expect(peek(obj, "bool")).toBe(true);
|
|
94
|
+
expect(peek(obj, "arr")).toEqual([1, 2, 3]);
|
|
95
|
+
expect(peek(obj, "nested")).toEqual({ value: "deep" });
|
|
96
|
+
});
|
|
97
|
+
});
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { isObservable } from "@legendapp/state";
|
|
2
|
+
import { MaybeObservable } from "../../types";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Extracts the value from a MaybeObservable **without** registering a tracking dependency.
|
|
6
|
+
* If the value is an Observable, calls `.peek()` to extract it non-reactively.
|
|
7
|
+
* Otherwise returns the value as-is.
|
|
8
|
+
*
|
|
9
|
+
* Use this for mount-time-only options that should not trigger re-runs when changed.
|
|
10
|
+
* Prefer `get()` inside reactive contexts (`useObserve`, `useObservable`) when reactivity is needed.
|
|
11
|
+
*
|
|
12
|
+
* @param maybeObservable - A value that might be an Observable
|
|
13
|
+
* @returns The extracted value (no tracking dependency registered)
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```ts
|
|
17
|
+
* import { observable } from '@legendapp/state'
|
|
18
|
+
* import { peek } from '@usels/core'
|
|
19
|
+
*
|
|
20
|
+
* const value = peek('hello') // 'hello'
|
|
21
|
+
* const obsValue = peek(observable(42)) // 42 — no dep registered
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export function peek<T>(maybeObservable: MaybeObservable<T>): T;
|
|
25
|
+
export function peek<T>(maybeObservable: MaybeObservable<T> | undefined): T | undefined;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Extracts a property value from a MaybeObservable object **without** registering a tracking dependency.
|
|
29
|
+
*
|
|
30
|
+
* @param maybeObservable - A value that might be an Observable
|
|
31
|
+
* @param key - The property key to extract
|
|
32
|
+
* @returns The property value, or undefined if not found
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```ts
|
|
36
|
+
* import { observable } from '@legendapp/state'
|
|
37
|
+
* import { peek } from '@usels/core'
|
|
38
|
+
*
|
|
39
|
+
* const obs$ = observable({ initialValue: true, rootMargin: '0px' })
|
|
40
|
+
*
|
|
41
|
+
* peek(obs$, 'initialValue') // true — no dep registered (mount-time-only read)
|
|
42
|
+
* peek(obs$, 'rootMargin') // '0px' — no dep registered
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
export function peek<T, K extends keyof T>(
|
|
46
|
+
maybeObservable: MaybeObservable<T>,
|
|
47
|
+
key: K,
|
|
48
|
+
): T[K] | undefined;
|
|
49
|
+
|
|
50
|
+
// Implementation
|
|
51
|
+
export function peek<T>(
|
|
52
|
+
maybeObservable: MaybeObservable<T>,
|
|
53
|
+
key?: keyof T,
|
|
54
|
+
): any {
|
|
55
|
+
// Extract the base value without registering a tracking dependency
|
|
56
|
+
const value = isObservable(maybeObservable)
|
|
57
|
+
? maybeObservable.peek()
|
|
58
|
+
: maybeObservable;
|
|
59
|
+
|
|
60
|
+
if (key === undefined) {
|
|
61
|
+
return value;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (value !== null && value !== undefined && typeof value === "object") {
|
|
65
|
+
return (value as any)[key];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return undefined;
|
|
69
|
+
}
|
|
@@ -0,0 +1,521 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
import { renderHook, act } from "@testing-library/react";
|
|
3
|
+
import { describe, it, expect, vi } from "vitest";
|
|
4
|
+
import { isObservable, observable, ObservableHint } from "@legendapp/state";
|
|
5
|
+
import type { OpaqueObject } from "@legendapp/state";
|
|
6
|
+
import { useMayObservableOptions } from ".";
|
|
7
|
+
import { useRef$ } from "../../elements/useRef$";
|
|
8
|
+
|
|
9
|
+
interface SimpleOpts {
|
|
10
|
+
val: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// =============================================================================
|
|
14
|
+
// No transform
|
|
15
|
+
// =============================================================================
|
|
16
|
+
|
|
17
|
+
describe("useMayObservableOptions() — no transform", () => {
|
|
18
|
+
it("undefined options → Observable<undefined>", () => {
|
|
19
|
+
const { result } = renderHook(() => useMayObservableOptions(undefined));
|
|
20
|
+
expect(isObservable(result.current)).toBe(true);
|
|
21
|
+
expect(result.current.get()).toBeUndefined();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("plain object → fields readable via .get()", () => {
|
|
25
|
+
const { result } = renderHook(() =>
|
|
26
|
+
useMayObservableOptions<{ x: number; y: number }>({ x: 10, y: 20 }),
|
|
27
|
+
);
|
|
28
|
+
expect(result.current.x.get()).toBe(10);
|
|
29
|
+
expect(result.current.y.get()).toBe(20);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("per-field Observable → auto-deref, reactive", () => {
|
|
33
|
+
const val$ = observable("hello");
|
|
34
|
+
const opts = { val: val$ }; // stable reference
|
|
35
|
+
const { result } = renderHook(() =>
|
|
36
|
+
useMayObservableOptions<SimpleOpts>(opts),
|
|
37
|
+
);
|
|
38
|
+
expect(result.current.val.get()).toBe("hello");
|
|
39
|
+
act(() => { val$.set("world"); });
|
|
40
|
+
expect(result.current.val.get()).toBe("world");
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("outer Observable → reacts to whole-object replace", () => {
|
|
44
|
+
const options$ = observable<SimpleOpts>({ val: "a" });
|
|
45
|
+
const { result } = renderHook(() =>
|
|
46
|
+
useMayObservableOptions<SimpleOpts>(options$),
|
|
47
|
+
);
|
|
48
|
+
expect(result.current.val.get()).toBe("a");
|
|
49
|
+
act(() => { options$.set({ val: "b" }); });
|
|
50
|
+
expect(result.current.val.get()).toBe("b");
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("outer Observable child-field mutation → opts$ recomputes (get() dep on options$ catches child notifications)", () => {
|
|
54
|
+
// compute() calls get(raw) which is options$.get() — this registers dep on options$.
|
|
55
|
+
// Legend-State notifies parent deps when a child field mutates,
|
|
56
|
+
// so opts$ recomputes and returns the updated value.
|
|
57
|
+
const options$ = observable<SimpleOpts>({ val: "a" });
|
|
58
|
+
const { result } = renderHook(() =>
|
|
59
|
+
useMayObservableOptions<SimpleOpts>(options$),
|
|
60
|
+
);
|
|
61
|
+
act(() => { options$.val.set("b"); });
|
|
62
|
+
expect(result.current.val.get()).toBe("b");
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("returns an Observable", () => {
|
|
66
|
+
const { result } = renderHook(() =>
|
|
67
|
+
useMayObservableOptions<SimpleOpts>({ val: "x" }),
|
|
68
|
+
);
|
|
69
|
+
expect(isObservable(result.current)).toBe(true);
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// =============================================================================
|
|
74
|
+
// Function-form transform
|
|
75
|
+
// =============================================================================
|
|
76
|
+
|
|
77
|
+
describe("useMayObservableOptions() — function-form transform", () => {
|
|
78
|
+
it("custom compute fn is called, its return value is what opts$ resolves to", () => {
|
|
79
|
+
const compute = vi.fn((_raw) => ({ val: "computed" }));
|
|
80
|
+
const { result } = renderHook(() =>
|
|
81
|
+
useMayObservableOptions<SimpleOpts>({ val: "ignored" }, compute),
|
|
82
|
+
);
|
|
83
|
+
// useObservable is lazy — trigger the compute by reading the value
|
|
84
|
+
expect(result.current.val.get()).toBe("computed");
|
|
85
|
+
expect(compute).toHaveBeenCalled();
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it("compute fn receives optionsRef.current (the raw options)", () => {
|
|
89
|
+
const rawOpts = { val: "raw" };
|
|
90
|
+
let captured: unknown;
|
|
91
|
+
const { result } = renderHook(() =>
|
|
92
|
+
useMayObservableOptions<SimpleOpts>(rawOpts, (raw) => {
|
|
93
|
+
captured = raw;
|
|
94
|
+
return { val: "x" };
|
|
95
|
+
}),
|
|
96
|
+
);
|
|
97
|
+
result.current.get(); // trigger lazy compute
|
|
98
|
+
expect(captured).toBe(rawOpts);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it("compute fn can register reactive deps via get() on outer Observable", () => {
|
|
102
|
+
const options$ = observable<SimpleOpts>({ val: "a" });
|
|
103
|
+
const { result } = renderHook(() =>
|
|
104
|
+
useMayObservableOptions<SimpleOpts>(options$, (raw) => {
|
|
105
|
+
const v = isObservable(raw) ? (raw as typeof options$).get() : raw;
|
|
106
|
+
return v ? { val: v.val + "!" } : undefined;
|
|
107
|
+
}),
|
|
108
|
+
);
|
|
109
|
+
expect(result.current.val.get()).toBe("a!");
|
|
110
|
+
act(() => { options$.set({ val: "b" }); });
|
|
111
|
+
expect(result.current.val.get()).toBe("b!");
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it("plain options reference change (new depKey Symbol) → compute re-evaluated", () => {
|
|
115
|
+
let evalCount = 0;
|
|
116
|
+
const { result, rerender } = renderHook(
|
|
117
|
+
({ opts }: { opts: SimpleOpts }) =>
|
|
118
|
+
useMayObservableOptions<SimpleOpts>(opts, () => {
|
|
119
|
+
evalCount++;
|
|
120
|
+
return undefined;
|
|
121
|
+
}),
|
|
122
|
+
{ initialProps: { opts: { val: "a" } } },
|
|
123
|
+
);
|
|
124
|
+
result.current.get(); // trigger initial lazy compute
|
|
125
|
+
const after1 = evalCount;
|
|
126
|
+
rerender({ opts: { val: "b" } }); // new object reference → new Symbol depKey
|
|
127
|
+
result.current.get(); // trigger recompute
|
|
128
|
+
expect(evalCount).toBeGreaterThan(after1);
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
// =============================================================================
|
|
133
|
+
// Object-form: 'peek' hint
|
|
134
|
+
// =============================================================================
|
|
135
|
+
|
|
136
|
+
describe("useMayObservableOptions() — object-form: 'peek'", () => {
|
|
137
|
+
it("resolves per-field Observable to its current value at compute time", () => {
|
|
138
|
+
const val$ = observable("initial");
|
|
139
|
+
const opts = { val: val$ }; // stable reference
|
|
140
|
+
const { result } = renderHook(() =>
|
|
141
|
+
useMayObservableOptions<SimpleOpts>(opts, { val: 'peek' }),
|
|
142
|
+
);
|
|
143
|
+
expect(result.current.val.get()).toBe("initial");
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it("plain field value is resolved as-is", () => {
|
|
147
|
+
const opts = { val: "static" };
|
|
148
|
+
const { result } = renderHook(() =>
|
|
149
|
+
useMayObservableOptions<SimpleOpts>(opts, { val: 'peek' }),
|
|
150
|
+
);
|
|
151
|
+
expect(result.current.val.get()).toBe("static");
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it("changing source Observable does NOT update field when options ref is stable (non-reactive)", () => {
|
|
155
|
+
// Stable options ref → depKey stays the same Symbol → compute does NOT re-run on val$ change.
|
|
156
|
+
// peek() inside compute does not register a dep on val$ → field stays at compute-time snapshot.
|
|
157
|
+
const val$ = observable("initial");
|
|
158
|
+
const opts = { val: val$ }; // stable: same reference every render
|
|
159
|
+
const { result } = renderHook(() =>
|
|
160
|
+
useMayObservableOptions<SimpleOpts>(opts, { val: 'peek' }),
|
|
161
|
+
);
|
|
162
|
+
expect(result.current.val.get()).toBe("initial");
|
|
163
|
+
act(() => { val$.set("updated"); });
|
|
164
|
+
// No dep registered (peek) + stable options ref (no depKey change) → stays "initial"
|
|
165
|
+
expect(result.current.val.get()).toBe("initial");
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
// =============================================================================
|
|
170
|
+
// Object-form: 'get' hint (explicit and default)
|
|
171
|
+
// =============================================================================
|
|
172
|
+
|
|
173
|
+
describe("useMayObservableOptions() — object-form: 'get' (explicit or omitted)", () => {
|
|
174
|
+
it("explicit 'get' → per-field Observable is reactive via Legend-State auto-deref", () => {
|
|
175
|
+
const val$ = observable("initial");
|
|
176
|
+
const opts = { val: val$ };
|
|
177
|
+
const { result } = renderHook(() =>
|
|
178
|
+
useMayObservableOptions<SimpleOpts>(opts, { val: 'get' }),
|
|
179
|
+
);
|
|
180
|
+
act(() => { val$.set("updated"); });
|
|
181
|
+
expect(result.current.val.get()).toBe("updated");
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it("omitted field defaults to 'get' → reactive", () => {
|
|
185
|
+
const val$ = observable("initial");
|
|
186
|
+
const opts = { val: val$ };
|
|
187
|
+
const { result } = renderHook(() =>
|
|
188
|
+
// empty FieldTransformMap: val is unspecified → defaults to 'get'
|
|
189
|
+
useMayObservableOptions<SimpleOpts>(opts, {}),
|
|
190
|
+
);
|
|
191
|
+
act(() => { val$.set("updated"); });
|
|
192
|
+
expect(result.current.val.get()).toBe("updated");
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it("mixed: 'peek' freezes one field, default 'get' keeps another reactive (stable options ref)", () => {
|
|
196
|
+
interface Opts { reactive: string; frozen: string }
|
|
197
|
+
const reactive$ = observable("r-init");
|
|
198
|
+
const frozen$ = observable("f-init");
|
|
199
|
+
const opts = { reactive: reactive$, frozen: frozen$ }; // stable reference
|
|
200
|
+
const { result } = renderHook(() =>
|
|
201
|
+
useMayObservableOptions<Opts>(
|
|
202
|
+
opts,
|
|
203
|
+
{ frozen: 'peek' }, // reactive: omitted → defaults to 'get'
|
|
204
|
+
),
|
|
205
|
+
);
|
|
206
|
+
expect(result.current.reactive.get()).toBe("r-init");
|
|
207
|
+
expect(result.current.frozen.get()).toBe("f-init");
|
|
208
|
+
act(() => {
|
|
209
|
+
reactive$.set("r-updated");
|
|
210
|
+
frozen$.set("f-updated");
|
|
211
|
+
});
|
|
212
|
+
expect(result.current.reactive.get()).toBe("r-updated"); // reactive ✓
|
|
213
|
+
expect(result.current.frozen.get()).toBe("f-init"); // frozen (peek) ✓
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
// =============================================================================
|
|
218
|
+
// Object-form: 'get.opaque' hint
|
|
219
|
+
// =============================================================================
|
|
220
|
+
|
|
221
|
+
describe("useMayObservableOptions() — object-form: 'get.opaque'", () => {
|
|
222
|
+
it("calls ObservableHint.opaque with the resolved value", () => {
|
|
223
|
+
const spy = vi.spyOn(ObservableHint, 'opaque');
|
|
224
|
+
const element = document.createElement("div");
|
|
225
|
+
const { result } = renderHook(() =>
|
|
226
|
+
useMayObservableOptions<{ element: HTMLElement }>({ element }, { element: 'get.opaque' }),
|
|
227
|
+
);
|
|
228
|
+
result.current.get(); // trigger lazy compute
|
|
229
|
+
expect(spy).toHaveBeenCalledWith(element);
|
|
230
|
+
spy.mockRestore();
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it("value is accessible via .get()", () => {
|
|
234
|
+
const element = document.createElement("div");
|
|
235
|
+
const { result } = renderHook(() =>
|
|
236
|
+
useMayObservableOptions<{ element: HTMLElement }>({ element }, { element: 'get.opaque' }),
|
|
237
|
+
);
|
|
238
|
+
expect(result.current.element.get()).toBe(element);
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
it("null field value (via Observable) → ObservableHint.opaque NOT called (null-safe)", () => {
|
|
242
|
+
// get(el$) resolves to null → v != null is false → opaque NOT applied
|
|
243
|
+
const spy = vi.spyOn(ObservableHint, 'opaque');
|
|
244
|
+
const el$ = observable<HTMLElement | null>(null);
|
|
245
|
+
const { result } = renderHook(() =>
|
|
246
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
247
|
+
useMayObservableOptions({ element: el$ } as any, { element: 'get.opaque' } as any),
|
|
248
|
+
);
|
|
249
|
+
result.current.get(); // trigger lazy compute
|
|
250
|
+
expect(spy).not.toHaveBeenCalled();
|
|
251
|
+
spy.mockRestore();
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
it("undefined field → ObservableHint.opaque NOT called (null-safe)", () => {
|
|
255
|
+
const spy = vi.spyOn(ObservableHint, 'opaque');
|
|
256
|
+
const { result } = renderHook(() =>
|
|
257
|
+
useMayObservableOptions<{ element?: HTMLElement }>(
|
|
258
|
+
{ element: undefined },
|
|
259
|
+
{ element: 'get.opaque' },
|
|
260
|
+
),
|
|
261
|
+
);
|
|
262
|
+
result.current.get(); // trigger lazy compute
|
|
263
|
+
expect(spy).not.toHaveBeenCalled();
|
|
264
|
+
spy.mockRestore();
|
|
265
|
+
});
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
// =============================================================================
|
|
269
|
+
// Object-form: 'get.plain' hint
|
|
270
|
+
// =============================================================================
|
|
271
|
+
|
|
272
|
+
describe("useMayObservableOptions() — object-form: 'get.plain'", () => {
|
|
273
|
+
it("calls ObservableHint.plain with the resolved value", () => {
|
|
274
|
+
const spy = vi.spyOn(ObservableHint, 'plain');
|
|
275
|
+
const nested = { key: "value" };
|
|
276
|
+
const { result } = renderHook(() =>
|
|
277
|
+
useMayObservableOptions<{ nested: object }>({ nested }, { nested: 'get.plain' }),
|
|
278
|
+
);
|
|
279
|
+
result.current.get(); // trigger lazy compute
|
|
280
|
+
expect(spy).toHaveBeenCalledWith(nested);
|
|
281
|
+
spy.mockRestore();
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
it("null field value (via Observable) → ObservableHint.plain NOT called (null-safe)", () => {
|
|
285
|
+
const spy = vi.spyOn(ObservableHint, 'plain');
|
|
286
|
+
const nested$ = observable<object | null>(null);
|
|
287
|
+
const { result } = renderHook(() =>
|
|
288
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
289
|
+
useMayObservableOptions({ nested: nested$ } as any, { nested: 'get.plain' } as any),
|
|
290
|
+
);
|
|
291
|
+
result.current.get(); // trigger lazy compute
|
|
292
|
+
expect(spy).not.toHaveBeenCalled();
|
|
293
|
+
spy.mockRestore();
|
|
294
|
+
});
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
// =============================================================================
|
|
298
|
+
// Object-form: 'get.function' hint
|
|
299
|
+
// =============================================================================
|
|
300
|
+
|
|
301
|
+
describe("useMayObservableOptions() — object-form: 'get.function'", () => {
|
|
302
|
+
it("calls ObservableHint.function with the resolved value", () => {
|
|
303
|
+
const spy = vi.spyOn(ObservableHint, 'function');
|
|
304
|
+
const cb = () => {};
|
|
305
|
+
const { result } = renderHook(() =>
|
|
306
|
+
useMayObservableOptions<{ cb: () => void }>({ cb }, { cb: 'get.function' }),
|
|
307
|
+
);
|
|
308
|
+
result.current.get(); // trigger lazy compute
|
|
309
|
+
expect(spy).toHaveBeenCalledWith(cb);
|
|
310
|
+
spy.mockRestore();
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
it("null field value (via Observable) → ObservableHint.function NOT called (null-safe)", () => {
|
|
314
|
+
const spy = vi.spyOn(ObservableHint, 'function');
|
|
315
|
+
const cb$ = observable<(() => void) | null>(null);
|
|
316
|
+
const { result } = renderHook(() =>
|
|
317
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
318
|
+
useMayObservableOptions({ cb: cb$ } as any, { cb: 'get.function' } as any),
|
|
319
|
+
);
|
|
320
|
+
result.current.get(); // trigger lazy compute
|
|
321
|
+
expect(spy).not.toHaveBeenCalled();
|
|
322
|
+
spy.mockRestore();
|
|
323
|
+
});
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
// =============================================================================
|
|
327
|
+
// Object-form: 'get.element' hint
|
|
328
|
+
// =============================================================================
|
|
329
|
+
|
|
330
|
+
describe("useMayObservableOptions() — object-form: 'get.element'", () => {
|
|
331
|
+
it("plain HTMLElement-bearing Observable → resolved and wrapped in ObservableHint.opaque", () => {
|
|
332
|
+
const div = document.createElement("div");
|
|
333
|
+
const el$ = observable<OpaqueObject<HTMLElement> | null>(ObservableHint.opaque(div));
|
|
334
|
+
const { result } = renderHook(() =>
|
|
335
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
336
|
+
useMayObservableOptions<{ el: any }>({ el: el$ }, { el: 'get.element' }),
|
|
337
|
+
);
|
|
338
|
+
result.current.get(); // trigger lazy compute
|
|
339
|
+
const stored = result.current.el.get();
|
|
340
|
+
expect(stored).not.toBeNull();
|
|
341
|
+
// valueOf() on the stored OpaqueObject returns the raw element
|
|
342
|
+
expect((stored as OpaqueObject<HTMLElement>).valueOf()).toBe(div);
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
it("Ref$ not yet mounted (null) → result[key] = null", () => {
|
|
346
|
+
const { result } = renderHook(() => {
|
|
347
|
+
const el$ = useRef$<HTMLDivElement>();
|
|
348
|
+
return useMayObservableOptions<{ el: any }>({ el: el$ }, { el: 'get.element' });
|
|
349
|
+
});
|
|
350
|
+
result.current.get(); // trigger lazy compute
|
|
351
|
+
expect(result.current.el.get()).toBeNull();
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
it("Ref$ mounted after render → opts$ recomputes, result[key] updates to OpaqueObject", () => {
|
|
355
|
+
const div = document.createElement("div");
|
|
356
|
+
const { result } = renderHook(() => {
|
|
357
|
+
const el$ = useRef$<HTMLDivElement>();
|
|
358
|
+
return { el$, opts$: useMayObservableOptions<{ el: any }>({ el: el$ }, { el: 'get.element' }) };
|
|
359
|
+
});
|
|
360
|
+
expect(result.current.opts$.el.get()).toBeNull();
|
|
361
|
+
|
|
362
|
+
act(() => result.current.el$(div));
|
|
363
|
+
|
|
364
|
+
const stored = result.current.opts$.el.get();
|
|
365
|
+
expect(stored).not.toBeNull();
|
|
366
|
+
expect((stored as OpaqueObject<HTMLDivElement>).valueOf()).toBe(div);
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
it("null field → result[key] = null (null-safe)", () => {
|
|
370
|
+
const { result } = renderHook(() =>
|
|
371
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
372
|
+
useMayObservableOptions<{ el: any }>({ el: null }, { el: 'get.element' }),
|
|
373
|
+
);
|
|
374
|
+
result.current.get();
|
|
375
|
+
expect(result.current.el.get()).toBeNull();
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
it("undefined field → result[key] unchanged (undefined-safe)", () => {
|
|
379
|
+
const { result } = renderHook(() =>
|
|
380
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
381
|
+
useMayObservableOptions<{ el?: any }>({ el: undefined }, { el: 'get.element' }),
|
|
382
|
+
);
|
|
383
|
+
result.current.get();
|
|
384
|
+
expect(result.current.el.get()).toBeUndefined();
|
|
385
|
+
});
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
// =============================================================================
|
|
389
|
+
// Object-form: 'peek.element' hint
|
|
390
|
+
// =============================================================================
|
|
391
|
+
|
|
392
|
+
describe("useMayObservableOptions() — object-form: 'peek.element'", () => {
|
|
393
|
+
it("Observable<OpaqueObject<Element>> → peekElement() called, result wrapped in opaque", () => {
|
|
394
|
+
const div = document.createElement("div");
|
|
395
|
+
const el$ = observable<OpaqueObject<HTMLElement> | null>(ObservableHint.opaque(div));
|
|
396
|
+
const { result } = renderHook(() =>
|
|
397
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
398
|
+
useMayObservableOptions<{ el: any }>({ el: el$ }, { el: 'peek.element' }),
|
|
399
|
+
);
|
|
400
|
+
result.current.get(); // trigger lazy compute
|
|
401
|
+
const stored = result.current.el.get();
|
|
402
|
+
expect(stored).not.toBeNull();
|
|
403
|
+
expect((stored as OpaqueObject<HTMLElement>).valueOf()).toBe(div);
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
it("null field → result[key] = null (null-safe)", () => {
|
|
407
|
+
const { result } = renderHook(() =>
|
|
408
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
409
|
+
useMayObservableOptions<{ el: any }>({ el: null }, { el: 'peek.element' }),
|
|
410
|
+
);
|
|
411
|
+
result.current.get();
|
|
412
|
+
expect(result.current.el.get()).toBeNull();
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
it("undefined field → result[key] unchanged (undefined-safe)", () => {
|
|
416
|
+
const { result } = renderHook(() =>
|
|
417
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
418
|
+
useMayObservableOptions<{ el?: any }>({ el: undefined }, { el: 'peek.element' }),
|
|
419
|
+
);
|
|
420
|
+
result.current.get();
|
|
421
|
+
expect(result.current.el.get()).toBeUndefined();
|
|
422
|
+
});
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
// =============================================================================
|
|
426
|
+
// Object-form: custom function hint
|
|
427
|
+
// =============================================================================
|
|
428
|
+
|
|
429
|
+
describe("useMayObservableOptions() — object-form: custom function hint", () => {
|
|
430
|
+
it("calls custom fn with the raw field value (Observable or plain)", () => {
|
|
431
|
+
const val$ = observable("hello");
|
|
432
|
+
const opts = { val: val$ };
|
|
433
|
+
const customHint = vi.fn((v: unknown) =>
|
|
434
|
+
isObservable(v) ? (v as typeof val$).get() : v,
|
|
435
|
+
);
|
|
436
|
+
const { result } = renderHook(() =>
|
|
437
|
+
useMayObservableOptions<SimpleOpts>(opts, { val: customHint }),
|
|
438
|
+
);
|
|
439
|
+
result.current.val.get(); // trigger lazy compute
|
|
440
|
+
expect(customHint).toHaveBeenCalledWith(val$);
|
|
441
|
+
expect(result.current.val.get()).toBe("hello");
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
it("custom fn return value is stored in opts$", () => {
|
|
445
|
+
const customHint = (_v: unknown) => "custom-result";
|
|
446
|
+
const opts = { val: "ignored" };
|
|
447
|
+
const { result } = renderHook(() =>
|
|
448
|
+
useMayObservableOptions<SimpleOpts>(opts, { val: customHint }),
|
|
449
|
+
);
|
|
450
|
+
expect(result.current.val.get()).toBe("custom-result");
|
|
451
|
+
});
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
// =============================================================================
|
|
455
|
+
// Object-form bypassed for outer Observable
|
|
456
|
+
// (applyObjectTransform is skipped when options is an Observable — isObservable check)
|
|
457
|
+
// =============================================================================
|
|
458
|
+
|
|
459
|
+
describe("useMayObservableOptions() — object-form bypassed for outer Observable", () => {
|
|
460
|
+
it("'peek' hints have no effect — opts$ reacts to whole-object replace as normal", () => {
|
|
461
|
+
// When options is Observable<T>: isObservable(raw) === true →
|
|
462
|
+
// applyObjectTransform is NOT called → transform hints are irrelevant.
|
|
463
|
+
// opts$ still reactively tracks the outer observable via get(options$) dep.
|
|
464
|
+
const options$ = observable<SimpleOpts>({ val: "initial" });
|
|
465
|
+
const { result } = renderHook(() =>
|
|
466
|
+
useMayObservableOptions<SimpleOpts>(options$, { val: 'peek' }),
|
|
467
|
+
);
|
|
468
|
+
expect(result.current.val.get()).toBe("initial");
|
|
469
|
+
act(() => { options$.set({ val: "replaced" }); });
|
|
470
|
+
expect(result.current.val.get()).toBe("replaced");
|
|
471
|
+
});
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
// =============================================================================
|
|
475
|
+
// Edge cases
|
|
476
|
+
// =============================================================================
|
|
477
|
+
|
|
478
|
+
describe("useMayObservableOptions() — edge cases", () => {
|
|
479
|
+
it("plain options reference change between renders → opts$ recomputes with new value", () => {
|
|
480
|
+
const { result, rerender } = renderHook(
|
|
481
|
+
({ opts }: { opts: SimpleOpts }) =>
|
|
482
|
+
useMayObservableOptions<SimpleOpts>(opts),
|
|
483
|
+
{ initialProps: { opts: { val: "first" } } },
|
|
484
|
+
);
|
|
485
|
+
expect(result.current.val.get()).toBe("first");
|
|
486
|
+
rerender({ opts: { val: "second" } });
|
|
487
|
+
expect(result.current.val.get()).toBe("second");
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
it("same-reference options between renders → value is stable", () => {
|
|
491
|
+
const opts = { val: "stable" };
|
|
492
|
+
const { result, rerender } = renderHook(() =>
|
|
493
|
+
useMayObservableOptions<SimpleOpts>(opts),
|
|
494
|
+
);
|
|
495
|
+
rerender();
|
|
496
|
+
expect(result.current.val.get()).toBe("stable");
|
|
497
|
+
});
|
|
498
|
+
|
|
499
|
+
it("null options with object-form transform → Observable<undefined> (null-safe)", () => {
|
|
500
|
+
const { result } = renderHook(() =>
|
|
501
|
+
useMayObservableOptions<SimpleOpts>(null as any, { val: 'peek' }),
|
|
502
|
+
);
|
|
503
|
+
expect(result.current.get()).toBeUndefined();
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
it("undefined options with object-form transform → Observable<undefined>", () => {
|
|
507
|
+
const { result } = renderHook(() =>
|
|
508
|
+
useMayObservableOptions<SimpleOpts>(undefined, { val: 'peek' }),
|
|
509
|
+
);
|
|
510
|
+
expect(result.current.get()).toBeUndefined();
|
|
511
|
+
});
|
|
512
|
+
|
|
513
|
+
it("undefined options with function-form transform → transform receives undefined", () => {
|
|
514
|
+
const compute = vi.fn((_raw) => undefined);
|
|
515
|
+
const { result } = renderHook(() =>
|
|
516
|
+
useMayObservableOptions<SimpleOpts>(undefined, compute),
|
|
517
|
+
);
|
|
518
|
+
result.current.get(); // trigger lazy compute
|
|
519
|
+
expect(compute).toHaveBeenCalledWith(undefined);
|
|
520
|
+
});
|
|
521
|
+
});
|