@blac/react 2.0.0-rc-14 → 2.0.0-rc.0
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/dist/.tsbuildinfo +1 -0
- package/dist/index.cjs +13 -0
- package/dist/index.d.ts +4 -58
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js.map +1 -0
- package/dist/useBloc.d.ts +20 -0
- package/dist/useBloc.d.ts.map +1 -0
- package/dist/useBloc.js +175 -0
- package/dist/useBloc.js.map +1 -0
- package/dist/useExternalBlocStore.d.ts +31 -0
- package/dist/useExternalBlocStore.d.ts.map +1 -0
- package/dist/useExternalBlocStore.js +93 -0
- package/dist/useExternalBlocStore.js.map +1 -0
- package/package.json +24 -14
- package/dist/index.cjs.js +0 -3
- package/dist/index.cjs.js.map +0 -1
- package/dist/index.es.js +0 -140
- package/dist/index.es.js.map +0 -1
- package/src/__tests__/rerenderLogging.test.tsx +0 -257
- package/src/__tests__/useBloc.disposal.test.tsx +0 -377
- package/src/__tests__/useBloc.instanceChange.test.tsx +0 -200
- package/src/__tests__/useBloc.props.test.tsx +0 -435
- package/src/__tests__/useBloc.proxyConfig.test.tsx +0 -228
- package/src/__tests__/useBloc.staticProps.test.tsx +0 -297
- package/src/__tests__/useBloc.tracking.test.tsx +0 -202
- package/src/index.ts +0 -3
- package/src/useBloc.ts +0 -227
- package/src/useExternalBlocStore.ts +0 -145
package/package.json
CHANGED
|
@@ -1,15 +1,24 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blac/react",
|
|
3
|
-
"version": "2.0.0-rc
|
|
3
|
+
"version": "2.0.0-rc.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"author": "Brendan Mullins <jsnanigans@gmail.com>",
|
|
6
|
-
"main": "dist/index.
|
|
7
|
-
"module": "dist/index.
|
|
8
|
-
"
|
|
9
|
-
"
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": {
|
|
13
|
+
"development": "./src/index.ts",
|
|
14
|
+
"default": "./dist/index.js"
|
|
15
|
+
},
|
|
16
|
+
"require": "./dist/index.cjs",
|
|
17
|
+
"source": "./src/index.ts"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
10
20
|
"files": [
|
|
11
21
|
"dist",
|
|
12
|
-
"src",
|
|
13
22
|
"README.md",
|
|
14
23
|
"LICENSE"
|
|
15
24
|
],
|
|
@@ -27,10 +36,8 @@
|
|
|
27
36
|
"dependencies": {},
|
|
28
37
|
"peerDependencies": {
|
|
29
38
|
"@types/react": "^18.0.0 || ^19.0.0",
|
|
30
|
-
"@types/react-dom": "^18.0.0 || ^19.0.0",
|
|
31
39
|
"react": "^18.0.0 || ^19.0.0",
|
|
32
|
-
"
|
|
33
|
-
"@blac/core": "2.0.0-rc-13"
|
|
40
|
+
"@blac/core": "2.0.0-rc.0"
|
|
34
41
|
},
|
|
35
42
|
"peerDependenciesMeta": {
|
|
36
43
|
"@types/react": {
|
|
@@ -55,17 +62,20 @@
|
|
|
55
62
|
"react": "^19.1.1",
|
|
56
63
|
"react-dom": "^19.1.1",
|
|
57
64
|
"typescript": "^5.9.2",
|
|
58
|
-
"vite": "^7.0.6",
|
|
59
|
-
"vite-plugin-dts": "^4.5.4",
|
|
60
65
|
"vitest": "^3.2.4",
|
|
61
|
-
"@blac/
|
|
66
|
+
"@blac/core": "2.0.0-rc.0",
|
|
67
|
+
"@blac/plugin-render-logging": "2.0.0-rc.0"
|
|
62
68
|
},
|
|
63
69
|
"scripts": {
|
|
70
|
+
"dev": "tsc --watch --project tsconfig.build.json",
|
|
71
|
+
"build": "pnpm run clean && pnpm run build:esm && pnpm run build:cjs",
|
|
72
|
+
"build:esm": "tsc --project tsconfig.build.json",
|
|
73
|
+
"build:cjs": "tsc --project tsconfig.build.json --module commonjs --outDir dist --declaration false --declarationMap false && mv dist/index.js dist/index.cjs",
|
|
74
|
+
"clean": "rm -rf dist",
|
|
64
75
|
"format": "prettier --write \".\"",
|
|
65
76
|
"test": "vitest run --config vitest.config.ts",
|
|
66
77
|
"test:watch": "vitest --watch --config vitest.config.ts",
|
|
67
78
|
"typecheck": "tsc --noEmit",
|
|
68
|
-
"
|
|
69
|
-
"deploy": "pnpm run build && pnpm publish --access public"
|
|
79
|
+
"deploy": "pnpm publish --access public"
|
|
70
80
|
}
|
|
71
81
|
}
|
package/dist/index.cjs.js
DELETED
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const b=require("@blac/core"),t=require("react");function P(u,a){const I=t.useRef(0);I.current++;const S=t.useRef({}),c=t.useRef("");if(!c.current)try{const M=(new Error().stack||"").split(`
|
|
2
|
-
`);for(let l=2;l<M.length&&l<15;l++){const h=M[l];let r=h.match(/at\s+(?:Object\.)?([A-Z][a-zA-Z0-9_$]*)/);if(r||(r=h.match(/([A-Z][a-zA-Z0-9_$]*)\.tsx/)),r||(r=h.match(/render([A-Z][a-zA-Z0-9_$]*)/),r&&r[1]&&(r[1]=r[1])),r&&r[1]!=="Object"&&!r[1].startsWith("use")&&!r[1].startsWith("Use")){c.current=r[1];break}}if(!c.current){const l=u.name;l&&l!=="Object"?c.current=l.replace(/(Cubit|Bloc)$/,"")||"Component":c.current="Component"}}catch{c.current="Component"}const e=a,y=t.useMemo(()=>e?.instanceId?e.instanceId:e?.staticProps&&b.generateInstanceIdFromProps(e.staticProps)||null,[e?.instanceId,e?.staticProps]),n=t.useMemo(()=>{const o=new b.BlacAdapter({componentRef:S,blocConstructor:u},{instanceId:e?.instanceId,dependencies:e?.dependencies,staticProps:e?.staticProps,onMount:e?.onMount,onUnmount:e?.onUnmount});return c.current&&o.setComponentName(c.current),o},[u,y]);n.resetTracking(),n.notifyRender();const s=t.useRef(0);t.useEffect(()=>{s.current++,n.options={instanceId:e?.instanceId,dependencies:e?.dependencies,staticProps:e?.staticProps,onMount:e?.onMount,onUnmount:e?.onUnmount}},[n,e?.dependencies,e?.onMount,e?.onUnmount]);const i=t.useRef(0);t.useEffect(()=>(i.current++,n.mount(),()=>{n.unmount()}),[n]);const f=t.useRef(0),m=t.useMemo(()=>(f.current++,o=>{const R=n.createSubscription({onChange:()=>{o()}});return()=>{R()}}),[n]),B=t.useRef(0),d=t.useRef(0),p=t.useSyncExternalStore(m,()=>(B.current++,n.blocInstance.state),()=>{const o=n.blocInstance;return d.current++,o.state}),g=t.useRef(0),x=t.useMemo(()=>(g.current++,n.getStateProxy()),[p,n]),C=t.useRef(0),U=t.useMemo(()=>(C.current++,n.getBlocProxy()),[n]);return[x,U]}function A(u,a={}){const I=t.useRef(`external-${b.generateUUID()}`),S=t.useRef(new Set),c=t.useRef(new Set),e=t.useRef(null),y=t.useMemo(()=>{const s=b.Blac.getInstance();if(u.constructor.isolated||u.isolated){const f=new u(a?.staticProps),m=a?.id||`${u.name}_${b.generateUUID()}`;return f._id=m,s.activateBloc(f),f}return s.getBloc(u,{id:a?.id,constructorParams:a?.staticProps})},[u,a?.id]),n=t.useMemo(()=>({getSnapshot:()=>{const s=e.current;return s?s.state:void 0},subscribe:s=>{const i=e.current;if(!i)return()=>{};const f=(d,p)=>{try{S.current.clear(),c.current.clear(),a.selector&&i&&a.selector(d,p,i),s.length>0?s(d):s()}catch(g){console.error("Listener error in useExternalBlocStore:",g)}};let m=i.state;return i.subscribe(d=>{const p=m;m=d,f(d,p)})},getServerSnapshot:()=>{const s=e.current;return s?s.state:void 0}}),[]);return e.current=y,{externalStore:n,instance:e,usedKeys:S,usedClassPropKeys:c,rid:I.current}}exports.useBloc=P;exports.useBlocNext=P;exports.useExternalBlocStore=A;
|
|
3
|
-
//# sourceMappingURL=index.cjs.js.map
|
package/dist/index.cjs.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs.js","sources":["../src/useBloc.ts","../src/useExternalBlocStore.ts"],"sourcesContent":["import {\n BlacAdapter,\n BlocBase,\n BlocConstructor,\n BlocState,\n generateInstanceIdFromProps,\n Blac,\n} from '@blac/core';\nimport { useEffect, useMemo, useRef, useSyncExternalStore } from 'react';\n\n/**\n * Type definition for the return type of the useBloc hook\n */\ntype HookTypes<B extends BlocConstructor<BlocBase<any>>> = [\n BlocState<InstanceType<B>>,\n InstanceType<B>,\n];\n\n/**\n * React hook for integrating with Blac state management\n */\nfunction useBloc<B extends BlocConstructor<BlocBase<any>>>(\n blocConstructor: B,\n options?: {\n staticProps?: ConstructorParameters<B>[0];\n instanceId?: string;\n dependencies?: (bloc: InstanceType<B>) => unknown[];\n onMount?: (bloc: InstanceType<B>) => void;\n onUnmount?: (bloc: InstanceType<B>) => void;\n },\n): HookTypes<B> {\n // Create a unique identifier for this hook instance\n const renderCount = useRef(0);\n renderCount.current++;\n\n const componentRef = useRef<object>({});\n\n // Get component name for debugging\n const componentName = useRef<string>('');\n if (!componentName.current) {\n // Try to get component name from stack trace\n try {\n const error = new Error();\n const stack = error.stack || '';\n const lines = stack.split('\\n');\n\n // Look for React component in stack - try multiple patterns\n for (let i = 2; i < lines.length && i < 15; i++) {\n const line = lines[i];\n\n // Pattern 1: \"at ComponentName\" or \"at Object.ComponentName\"\n let match = line.match(/at\\s+(?:Object\\.)?([A-Z][a-zA-Z0-9_$]*)/);\n\n // Pattern 2: Look for component files like \"ComponentName.tsx\"\n if (!match) {\n match = line.match(/([A-Z][a-zA-Z0-9_$]*)\\.tsx/);\n }\n\n // Pattern 3: Look for render functions\n if (!match) {\n match = line.match(/render([A-Z][a-zA-Z0-9_$]*)/);\n if (match && match[1]) {\n match[1] = match[1]; // Use the part after \"render\"\n }\n }\n\n if (\n match &&\n match[1] !== 'Object' &&\n !match[1].startsWith('use') &&\n !match[1].startsWith('Use')\n ) {\n componentName.current = match[1];\n break;\n }\n }\n\n // If still no name, try to get it from the bloc constructor name\n if (!componentName.current) {\n const blocName = blocConstructor.name;\n if (blocName && blocName !== 'Object') {\n // Remove 'Cubit' or 'Bloc' suffix to guess component name\n componentName.current =\n blocName.replace(/(Cubit|Bloc)$/, '') || 'Component';\n } else {\n componentName.current = 'Component';\n }\n }\n } catch (e) {\n componentName.current = 'Component';\n }\n }\n\n // Pass through options\n const normalizedOptions = options;\n\n // Generate instance id from static props if needed\n const instanceKey = useMemo(() => {\n if (normalizedOptions?.instanceId) {\n return normalizedOptions.instanceId;\n }\n if (normalizedOptions?.staticProps) {\n return generateInstanceIdFromProps(normalizedOptions.staticProps) || null;\n }\n return null;\n }, [normalizedOptions?.instanceId, normalizedOptions?.staticProps]);\n\n // Track adapter creation - recreate when instanceId/staticProps change\n const adapter = useMemo(() => {\n const newAdapter = new BlacAdapter<B>(\n {\n componentRef: componentRef,\n blocConstructor,\n },\n {\n instanceId: normalizedOptions?.instanceId,\n dependencies: normalizedOptions?.dependencies,\n staticProps: normalizedOptions?.staticProps,\n onMount: normalizedOptions?.onMount,\n onUnmount: normalizedOptions?.onUnmount,\n },\n );\n // Set component name for rerender logging\n if (componentName.current) {\n newAdapter.setComponentName(componentName.current);\n }\n return newAdapter;\n }, [blocConstructor, instanceKey]); // Recreate adapter when instance key changes\n\n // Reset tracking at the start of each render to ensure we only track\n // properties accessed during the current render\n adapter.resetTracking();\n\n // Notify plugins about render\n adapter.notifyRender();\n\n // Update adapter options when they change (except instanceId/staticProps which recreate the adapter)\n const optionsChangeCount = useRef(0);\n useEffect(() => {\n optionsChangeCount.current++;\n adapter.options = {\n instanceId: normalizedOptions?.instanceId,\n dependencies: normalizedOptions?.dependencies,\n staticProps: normalizedOptions?.staticProps,\n onMount: normalizedOptions?.onMount,\n onUnmount: normalizedOptions?.onUnmount,\n };\n }, [\n adapter,\n normalizedOptions?.dependencies,\n normalizedOptions?.onMount,\n normalizedOptions?.onUnmount,\n ]);\n\n // Register as consumer and handle lifecycle\n const mountEffectCount = useRef(0);\n useEffect(() => {\n mountEffectCount.current++;\n adapter.mount();\n\n return () => {\n adapter.unmount();\n };\n }, [adapter]);\n\n // Subscribe to state changes using useSyncExternalStore\n const subscribeMemoCount = useRef(0);\n const subscribe = useMemo(() => {\n subscribeMemoCount.current++;\n let subscriptionCount = 0;\n\n return (onStoreChange: () => void) => {\n subscriptionCount++;\n const unsubscribe = adapter.createSubscription({\n onChange: () => {\n onStoreChange();\n },\n });\n\n return () => {\n unsubscribe();\n };\n };\n }, [adapter]);\n\n const snapshotCount = useRef(0);\n const serverSnapshotCount = useRef(0);\n\n const rawState: BlocState<InstanceType<B>> = useSyncExternalStore(\n subscribe,\n // Get snapshot\n () => {\n snapshotCount.current++;\n const bloc = adapter.blocInstance;\n const state = bloc.state;\n return state;\n },\n // Get server snapshot (same as client for now)\n () => {\n const bloc = adapter.blocInstance;\n serverSnapshotCount.current++;\n const state = bloc.state;\n return state;\n },\n );\n\n // Create proxies for fine-grained tracking (if enabled)\n const stateMemoCount = useRef(0);\n const finalState = useMemo(() => {\n stateMemoCount.current++;\n // Always return the proxy - it will handle whether to actually proxy or not\n const proxyState = adapter.getStateProxy();\n return proxyState;\n }, [rawState, adapter]);\n\n const blocMemoCount = useRef(0);\n const finalBloc = useMemo(() => {\n blocMemoCount.current++;\n const proxyBloc = adapter.getBlocProxy();\n return proxyBloc;\n }, [adapter]);\n\n // Log final hook return\n return [finalState, finalBloc];\n}\n\nexport default useBloc;\n","import {\n Blac,\n BlocBase,\n BlocConstructor,\n BlocState,\n generateUUID,\n} from '@blac/core';\nimport { useMemo, useRef } from 'react';\n\ninterface ExternalStoreOptions<B extends BlocBase<any>> {\n id?: string;\n staticProps?: ConstructorParameters<BlocConstructor<B>>[0];\n selector?: (\n currentState: BlocState<B>,\n previousState: BlocState<B>,\n instance: B,\n ) => any[];\n}\n\ninterface ExternalStore<T> {\n getSnapshot: () => T | undefined;\n subscribe: (listener: () => void) => () => void;\n getServerSnapshot?: () => T | undefined;\n}\n\ninterface ExternalBlocStoreResult<B extends BlocBase<any>> {\n externalStore: ExternalStore<BlocState<B>>;\n instance: { current: B | null };\n usedKeys: { current: Set<string> };\n usedClassPropKeys: { current: Set<string> };\n rid: string;\n}\n\n/**\n * Hook for using an external bloc store\n * Provides low-level access to bloc instances for use with React's useSyncExternalStore\n */\nexport default function useExternalBlocStore<\n B extends BlocConstructor<BlocBase<any>>,\n>(\n blocConstructor: B,\n options: ExternalStoreOptions<InstanceType<B>> = {},\n): ExternalBlocStoreResult<InstanceType<B>> {\n const ridRef = useRef<string>(`external-${generateUUID()}`);\n const usedKeysRef = useRef<Set<string>>(new Set());\n const usedClassPropKeysRef = useRef<Set<string>>(new Set());\n const instanceRef = useRef<InstanceType<B> | null>(null);\n\n // Get or create bloc instance\n const bloc = useMemo(() => {\n const blac = Blac.getInstance();\n const base = blocConstructor as unknown as BlocBase<any>;\n\n // For isolated blocs, always create a new instance\n if (\n (base.constructor as any).isolated ||\n (blocConstructor as any).isolated\n ) {\n const newBloc = new blocConstructor(\n options?.staticProps,\n ) as InstanceType<B>;\n const uniqueId =\n options?.id || `${blocConstructor.name}_${generateUUID()}`;\n newBloc._id = uniqueId;\n blac.activateBloc(newBloc);\n return newBloc;\n }\n\n // For shared blocs, use the existing getBloc logic\n return blac.getBloc(blocConstructor, {\n id: options?.id,\n constructorParams: options?.staticProps,\n });\n }, [blocConstructor, options?.id]);\n\n // Create external store interface\n const externalStore = useMemo<ExternalStore<BlocState<InstanceType<B>>>>(\n () => ({\n getSnapshot: () => {\n const currentInstance = instanceRef.current;\n return currentInstance ? currentInstance.state : undefined;\n },\n subscribe: (listener: () => void) => {\n const currentInstance = instanceRef.current;\n if (!currentInstance) {\n // Return no-op unsubscribe if no instance\n return () => {};\n }\n\n // Wrap listener to handle errors gracefully\n const safeListener = (\n newState: BlocState<InstanceType<B>>,\n oldState: BlocState<InstanceType<B>>,\n ) => {\n try {\n // Reset tracking keys on each listener call\n usedKeysRef.current.clear();\n usedClassPropKeysRef.current.clear();\n\n // Call selector if provided\n if (options.selector && currentInstance) {\n options.selector(newState, oldState, currentInstance);\n }\n\n // Call listener with state if it expects it\n if (listener.length > 0) {\n (listener as any)(newState);\n } else {\n listener();\n }\n } catch (error) {\n console.error('Listener error in useExternalBlocStore:', error);\n // Don't rethrow to prevent breaking other listeners\n }\n };\n\n // Store the previous state for the listener\n let previousState = currentInstance.state;\n\n const unsubscribe = currentInstance.subscribe((state) => {\n const oldState = previousState;\n previousState = state;\n safeListener(state, oldState);\n });\n return unsubscribe;\n },\n getServerSnapshot: () => {\n const currentInstance = instanceRef.current;\n return currentInstance ? currentInstance.state : undefined;\n },\n }),\n [],\n );\n\n // Update instance ref\n instanceRef.current = bloc;\n\n return {\n externalStore,\n instance: instanceRef,\n usedKeys: usedKeysRef,\n usedClassPropKeys: usedClassPropKeysRef,\n rid: ridRef.current,\n };\n}\n"],"names":["useBloc","blocConstructor","options","renderCount","useRef","componentRef","componentName","lines","i","line","match","blocName","normalizedOptions","instanceKey","useMemo","generateInstanceIdFromProps","adapter","newAdapter","BlacAdapter","optionsChangeCount","useEffect","mountEffectCount","subscribeMemoCount","subscribe","onStoreChange","unsubscribe","snapshotCount","serverSnapshotCount","rawState","useSyncExternalStore","bloc","stateMemoCount","finalState","blocMemoCount","finalBloc","useExternalBlocStore","ridRef","generateUUID","usedKeysRef","usedClassPropKeysRef","instanceRef","blac","Blac","newBloc","uniqueId","externalStore","currentInstance","listener","safeListener","newState","oldState","error","previousState","state"],"mappings":"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"}
|
package/dist/index.es.js
DELETED
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
import { generateInstanceIdFromProps as R, BlacAdapter as $, generateUUID as C, Blac as v } from "@blac/core";
|
|
2
|
-
import { useRef as r, useMemo as m, useEffect as M, useSyncExternalStore as E } from "react";
|
|
3
|
-
function K(a, u) {
|
|
4
|
-
const h = r(0);
|
|
5
|
-
h.current++;
|
|
6
|
-
const S = r({}), c = r("");
|
|
7
|
-
if (!c.current)
|
|
8
|
-
try {
|
|
9
|
-
const g = (new Error().stack || "").split(`
|
|
10
|
-
`);
|
|
11
|
-
for (let l = 2; l < g.length && l < 15; l++) {
|
|
12
|
-
const x = g[l];
|
|
13
|
-
let s = x.match(/at\s+(?:Object\.)?([A-Z][a-zA-Z0-9_$]*)/);
|
|
14
|
-
if (s || (s = x.match(/([A-Z][a-zA-Z0-9_$]*)\.tsx/)), s || (s = x.match(/render([A-Z][a-zA-Z0-9_$]*)/), s && s[1] && (s[1] = s[1])), s && s[1] !== "Object" && !s[1].startsWith("use") && !s[1].startsWith("Use")) {
|
|
15
|
-
c.current = s[1];
|
|
16
|
-
break;
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
if (!c.current) {
|
|
20
|
-
const l = a.name;
|
|
21
|
-
l && l !== "Object" ? c.current = l.replace(/(Cubit|Bloc)$/, "") || "Component" : c.current = "Component";
|
|
22
|
-
}
|
|
23
|
-
} catch {
|
|
24
|
-
c.current = "Component";
|
|
25
|
-
}
|
|
26
|
-
const t = u, y = m(() => t?.instanceId ? t.instanceId : t?.staticProps && R(t.staticProps) || null, [t?.instanceId, t?.staticProps]), e = m(() => {
|
|
27
|
-
const o = new $(
|
|
28
|
-
{
|
|
29
|
-
componentRef: S,
|
|
30
|
-
blocConstructor: a
|
|
31
|
-
},
|
|
32
|
-
{
|
|
33
|
-
instanceId: t?.instanceId,
|
|
34
|
-
dependencies: t?.dependencies,
|
|
35
|
-
staticProps: t?.staticProps,
|
|
36
|
-
onMount: t?.onMount,
|
|
37
|
-
onUnmount: t?.onUnmount
|
|
38
|
-
}
|
|
39
|
-
);
|
|
40
|
-
return c.current && o.setComponentName(c.current), o;
|
|
41
|
-
}, [a, y]);
|
|
42
|
-
e.resetTracking(), e.notifyRender();
|
|
43
|
-
const n = r(0);
|
|
44
|
-
M(() => {
|
|
45
|
-
n.current++, e.options = {
|
|
46
|
-
instanceId: t?.instanceId,
|
|
47
|
-
dependencies: t?.dependencies,
|
|
48
|
-
staticProps: t?.staticProps,
|
|
49
|
-
onMount: t?.onMount,
|
|
50
|
-
onUnmount: t?.onUnmount
|
|
51
|
-
};
|
|
52
|
-
}, [
|
|
53
|
-
e,
|
|
54
|
-
t?.dependencies,
|
|
55
|
-
t?.onMount,
|
|
56
|
-
t?.onUnmount
|
|
57
|
-
]);
|
|
58
|
-
const i = r(0);
|
|
59
|
-
M(() => (i.current++, e.mount(), () => {
|
|
60
|
-
e.unmount();
|
|
61
|
-
}), [e]);
|
|
62
|
-
const d = r(0), f = m(() => (d.current++, (o) => {
|
|
63
|
-
const I = e.createSubscription({
|
|
64
|
-
onChange: () => {
|
|
65
|
-
o();
|
|
66
|
-
}
|
|
67
|
-
});
|
|
68
|
-
return () => {
|
|
69
|
-
I();
|
|
70
|
-
};
|
|
71
|
-
}), [e]), B = r(0), p = r(0), b = E(
|
|
72
|
-
f,
|
|
73
|
-
// Get snapshot
|
|
74
|
-
() => (B.current++, e.blocInstance.state),
|
|
75
|
-
// Get server snapshot (same as client for now)
|
|
76
|
-
() => {
|
|
77
|
-
const o = e.blocInstance;
|
|
78
|
-
return p.current++, o.state;
|
|
79
|
-
}
|
|
80
|
-
), P = r(0), A = m(() => (P.current++, e.getStateProxy()), [b, e]), U = r(0), w = m(() => (U.current++, e.getBlocProxy()), [e]);
|
|
81
|
-
return [A, w];
|
|
82
|
-
}
|
|
83
|
-
function _(a, u = {}) {
|
|
84
|
-
const h = r(`external-${C()}`), S = r(/* @__PURE__ */ new Set()), c = r(/* @__PURE__ */ new Set()), t = r(null), y = m(() => {
|
|
85
|
-
const n = v.getInstance();
|
|
86
|
-
if (a.constructor.isolated || a.isolated) {
|
|
87
|
-
const d = new a(
|
|
88
|
-
u?.staticProps
|
|
89
|
-
), f = u?.id || `${a.name}_${C()}`;
|
|
90
|
-
return d._id = f, n.activateBloc(d), d;
|
|
91
|
-
}
|
|
92
|
-
return n.getBloc(a, {
|
|
93
|
-
id: u?.id,
|
|
94
|
-
constructorParams: u?.staticProps
|
|
95
|
-
});
|
|
96
|
-
}, [a, u?.id]), e = m(
|
|
97
|
-
() => ({
|
|
98
|
-
getSnapshot: () => {
|
|
99
|
-
const n = t.current;
|
|
100
|
-
return n ? n.state : void 0;
|
|
101
|
-
},
|
|
102
|
-
subscribe: (n) => {
|
|
103
|
-
const i = t.current;
|
|
104
|
-
if (!i)
|
|
105
|
-
return () => {
|
|
106
|
-
};
|
|
107
|
-
const d = (p, b) => {
|
|
108
|
-
try {
|
|
109
|
-
S.current.clear(), c.current.clear(), u.selector && i && u.selector(p, b, i), n.length > 0 ? n(p) : n();
|
|
110
|
-
} catch (P) {
|
|
111
|
-
console.error("Listener error in useExternalBlocStore:", P);
|
|
112
|
-
}
|
|
113
|
-
};
|
|
114
|
-
let f = i.state;
|
|
115
|
-
return i.subscribe((p) => {
|
|
116
|
-
const b = f;
|
|
117
|
-
f = p, d(p, b);
|
|
118
|
-
});
|
|
119
|
-
},
|
|
120
|
-
getServerSnapshot: () => {
|
|
121
|
-
const n = t.current;
|
|
122
|
-
return n ? n.state : void 0;
|
|
123
|
-
}
|
|
124
|
-
}),
|
|
125
|
-
[]
|
|
126
|
-
);
|
|
127
|
-
return t.current = y, {
|
|
128
|
-
externalStore: e,
|
|
129
|
-
instance: t,
|
|
130
|
-
usedKeys: S,
|
|
131
|
-
usedClassPropKeys: c,
|
|
132
|
-
rid: h.current
|
|
133
|
-
};
|
|
134
|
-
}
|
|
135
|
-
export {
|
|
136
|
-
K as useBloc,
|
|
137
|
-
K as useBlocNext,
|
|
138
|
-
_ as useExternalBlocStore
|
|
139
|
-
};
|
|
140
|
-
//# sourceMappingURL=index.es.js.map
|
package/dist/index.es.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.es.js","sources":["../src/useBloc.ts","../src/useExternalBlocStore.ts"],"sourcesContent":["import {\n BlacAdapter,\n BlocBase,\n BlocConstructor,\n BlocState,\n generateInstanceIdFromProps,\n Blac,\n} from '@blac/core';\nimport { useEffect, useMemo, useRef, useSyncExternalStore } from 'react';\n\n/**\n * Type definition for the return type of the useBloc hook\n */\ntype HookTypes<B extends BlocConstructor<BlocBase<any>>> = [\n BlocState<InstanceType<B>>,\n InstanceType<B>,\n];\n\n/**\n * React hook for integrating with Blac state management\n */\nfunction useBloc<B extends BlocConstructor<BlocBase<any>>>(\n blocConstructor: B,\n options?: {\n staticProps?: ConstructorParameters<B>[0];\n instanceId?: string;\n dependencies?: (bloc: InstanceType<B>) => unknown[];\n onMount?: (bloc: InstanceType<B>) => void;\n onUnmount?: (bloc: InstanceType<B>) => void;\n },\n): HookTypes<B> {\n // Create a unique identifier for this hook instance\n const renderCount = useRef(0);\n renderCount.current++;\n\n const componentRef = useRef<object>({});\n\n // Get component name for debugging\n const componentName = useRef<string>('');\n if (!componentName.current) {\n // Try to get component name from stack trace\n try {\n const error = new Error();\n const stack = error.stack || '';\n const lines = stack.split('\\n');\n\n // Look for React component in stack - try multiple patterns\n for (let i = 2; i < lines.length && i < 15; i++) {\n const line = lines[i];\n\n // Pattern 1: \"at ComponentName\" or \"at Object.ComponentName\"\n let match = line.match(/at\\s+(?:Object\\.)?([A-Z][a-zA-Z0-9_$]*)/);\n\n // Pattern 2: Look for component files like \"ComponentName.tsx\"\n if (!match) {\n match = line.match(/([A-Z][a-zA-Z0-9_$]*)\\.tsx/);\n }\n\n // Pattern 3: Look for render functions\n if (!match) {\n match = line.match(/render([A-Z][a-zA-Z0-9_$]*)/);\n if (match && match[1]) {\n match[1] = match[1]; // Use the part after \"render\"\n }\n }\n\n if (\n match &&\n match[1] !== 'Object' &&\n !match[1].startsWith('use') &&\n !match[1].startsWith('Use')\n ) {\n componentName.current = match[1];\n break;\n }\n }\n\n // If still no name, try to get it from the bloc constructor name\n if (!componentName.current) {\n const blocName = blocConstructor.name;\n if (blocName && blocName !== 'Object') {\n // Remove 'Cubit' or 'Bloc' suffix to guess component name\n componentName.current =\n blocName.replace(/(Cubit|Bloc)$/, '') || 'Component';\n } else {\n componentName.current = 'Component';\n }\n }\n } catch (e) {\n componentName.current = 'Component';\n }\n }\n\n // Pass through options\n const normalizedOptions = options;\n\n // Generate instance id from static props if needed\n const instanceKey = useMemo(() => {\n if (normalizedOptions?.instanceId) {\n return normalizedOptions.instanceId;\n }\n if (normalizedOptions?.staticProps) {\n return generateInstanceIdFromProps(normalizedOptions.staticProps) || null;\n }\n return null;\n }, [normalizedOptions?.instanceId, normalizedOptions?.staticProps]);\n\n // Track adapter creation - recreate when instanceId/staticProps change\n const adapter = useMemo(() => {\n const newAdapter = new BlacAdapter<B>(\n {\n componentRef: componentRef,\n blocConstructor,\n },\n {\n instanceId: normalizedOptions?.instanceId,\n dependencies: normalizedOptions?.dependencies,\n staticProps: normalizedOptions?.staticProps,\n onMount: normalizedOptions?.onMount,\n onUnmount: normalizedOptions?.onUnmount,\n },\n );\n // Set component name for rerender logging\n if (componentName.current) {\n newAdapter.setComponentName(componentName.current);\n }\n return newAdapter;\n }, [blocConstructor, instanceKey]); // Recreate adapter when instance key changes\n\n // Reset tracking at the start of each render to ensure we only track\n // properties accessed during the current render\n adapter.resetTracking();\n\n // Notify plugins about render\n adapter.notifyRender();\n\n // Update adapter options when they change (except instanceId/staticProps which recreate the adapter)\n const optionsChangeCount = useRef(0);\n useEffect(() => {\n optionsChangeCount.current++;\n adapter.options = {\n instanceId: normalizedOptions?.instanceId,\n dependencies: normalizedOptions?.dependencies,\n staticProps: normalizedOptions?.staticProps,\n onMount: normalizedOptions?.onMount,\n onUnmount: normalizedOptions?.onUnmount,\n };\n }, [\n adapter,\n normalizedOptions?.dependencies,\n normalizedOptions?.onMount,\n normalizedOptions?.onUnmount,\n ]);\n\n // Register as consumer and handle lifecycle\n const mountEffectCount = useRef(0);\n useEffect(() => {\n mountEffectCount.current++;\n adapter.mount();\n\n return () => {\n adapter.unmount();\n };\n }, [adapter]);\n\n // Subscribe to state changes using useSyncExternalStore\n const subscribeMemoCount = useRef(0);\n const subscribe = useMemo(() => {\n subscribeMemoCount.current++;\n let subscriptionCount = 0;\n\n return (onStoreChange: () => void) => {\n subscriptionCount++;\n const unsubscribe = adapter.createSubscription({\n onChange: () => {\n onStoreChange();\n },\n });\n\n return () => {\n unsubscribe();\n };\n };\n }, [adapter]);\n\n const snapshotCount = useRef(0);\n const serverSnapshotCount = useRef(0);\n\n const rawState: BlocState<InstanceType<B>> = useSyncExternalStore(\n subscribe,\n // Get snapshot\n () => {\n snapshotCount.current++;\n const bloc = adapter.blocInstance;\n const state = bloc.state;\n return state;\n },\n // Get server snapshot (same as client for now)\n () => {\n const bloc = adapter.blocInstance;\n serverSnapshotCount.current++;\n const state = bloc.state;\n return state;\n },\n );\n\n // Create proxies for fine-grained tracking (if enabled)\n const stateMemoCount = useRef(0);\n const finalState = useMemo(() => {\n stateMemoCount.current++;\n // Always return the proxy - it will handle whether to actually proxy or not\n const proxyState = adapter.getStateProxy();\n return proxyState;\n }, [rawState, adapter]);\n\n const blocMemoCount = useRef(0);\n const finalBloc = useMemo(() => {\n blocMemoCount.current++;\n const proxyBloc = adapter.getBlocProxy();\n return proxyBloc;\n }, [adapter]);\n\n // Log final hook return\n return [finalState, finalBloc];\n}\n\nexport default useBloc;\n","import {\n Blac,\n BlocBase,\n BlocConstructor,\n BlocState,\n generateUUID,\n} from '@blac/core';\nimport { useMemo, useRef } from 'react';\n\ninterface ExternalStoreOptions<B extends BlocBase<any>> {\n id?: string;\n staticProps?: ConstructorParameters<BlocConstructor<B>>[0];\n selector?: (\n currentState: BlocState<B>,\n previousState: BlocState<B>,\n instance: B,\n ) => any[];\n}\n\ninterface ExternalStore<T> {\n getSnapshot: () => T | undefined;\n subscribe: (listener: () => void) => () => void;\n getServerSnapshot?: () => T | undefined;\n}\n\ninterface ExternalBlocStoreResult<B extends BlocBase<any>> {\n externalStore: ExternalStore<BlocState<B>>;\n instance: { current: B | null };\n usedKeys: { current: Set<string> };\n usedClassPropKeys: { current: Set<string> };\n rid: string;\n}\n\n/**\n * Hook for using an external bloc store\n * Provides low-level access to bloc instances for use with React's useSyncExternalStore\n */\nexport default function useExternalBlocStore<\n B extends BlocConstructor<BlocBase<any>>,\n>(\n blocConstructor: B,\n options: ExternalStoreOptions<InstanceType<B>> = {},\n): ExternalBlocStoreResult<InstanceType<B>> {\n const ridRef = useRef<string>(`external-${generateUUID()}`);\n const usedKeysRef = useRef<Set<string>>(new Set());\n const usedClassPropKeysRef = useRef<Set<string>>(new Set());\n const instanceRef = useRef<InstanceType<B> | null>(null);\n\n // Get or create bloc instance\n const bloc = useMemo(() => {\n const blac = Blac.getInstance();\n const base = blocConstructor as unknown as BlocBase<any>;\n\n // For isolated blocs, always create a new instance\n if (\n (base.constructor as any).isolated ||\n (blocConstructor as any).isolated\n ) {\n const newBloc = new blocConstructor(\n options?.staticProps,\n ) as InstanceType<B>;\n const uniqueId =\n options?.id || `${blocConstructor.name}_${generateUUID()}`;\n newBloc._id = uniqueId;\n blac.activateBloc(newBloc);\n return newBloc;\n }\n\n // For shared blocs, use the existing getBloc logic\n return blac.getBloc(blocConstructor, {\n id: options?.id,\n constructorParams: options?.staticProps,\n });\n }, [blocConstructor, options?.id]);\n\n // Create external store interface\n const externalStore = useMemo<ExternalStore<BlocState<InstanceType<B>>>>(\n () => ({\n getSnapshot: () => {\n const currentInstance = instanceRef.current;\n return currentInstance ? currentInstance.state : undefined;\n },\n subscribe: (listener: () => void) => {\n const currentInstance = instanceRef.current;\n if (!currentInstance) {\n // Return no-op unsubscribe if no instance\n return () => {};\n }\n\n // Wrap listener to handle errors gracefully\n const safeListener = (\n newState: BlocState<InstanceType<B>>,\n oldState: BlocState<InstanceType<B>>,\n ) => {\n try {\n // Reset tracking keys on each listener call\n usedKeysRef.current.clear();\n usedClassPropKeysRef.current.clear();\n\n // Call selector if provided\n if (options.selector && currentInstance) {\n options.selector(newState, oldState, currentInstance);\n }\n\n // Call listener with state if it expects it\n if (listener.length > 0) {\n (listener as any)(newState);\n } else {\n listener();\n }\n } catch (error) {\n console.error('Listener error in useExternalBlocStore:', error);\n // Don't rethrow to prevent breaking other listeners\n }\n };\n\n // Store the previous state for the listener\n let previousState = currentInstance.state;\n\n const unsubscribe = currentInstance.subscribe((state) => {\n const oldState = previousState;\n previousState = state;\n safeListener(state, oldState);\n });\n return unsubscribe;\n },\n getServerSnapshot: () => {\n const currentInstance = instanceRef.current;\n return currentInstance ? currentInstance.state : undefined;\n },\n }),\n [],\n );\n\n // Update instance ref\n instanceRef.current = bloc;\n\n return {\n externalStore,\n instance: instanceRef,\n usedKeys: usedKeysRef,\n usedClassPropKeys: usedClassPropKeysRef,\n rid: ridRef.current,\n };\n}\n"],"names":["useBloc","blocConstructor","options","renderCount","useRef","componentRef","componentName","lines","i","line","match","blocName","normalizedOptions","instanceKey","useMemo","generateInstanceIdFromProps","adapter","newAdapter","BlacAdapter","optionsChangeCount","useEffect","mountEffectCount","subscribeMemoCount","subscribe","onStoreChange","unsubscribe","snapshotCount","serverSnapshotCount","rawState","useSyncExternalStore","bloc","stateMemoCount","finalState","blocMemoCount","finalBloc","useExternalBlocStore","ridRef","generateUUID","usedKeysRef","usedClassPropKeysRef","instanceRef","blac","Blac","newBloc","uniqueId","externalStore","currentInstance","listener","safeListener","newState","oldState","error","previousState","state"],"mappings":"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"}
|
|
@@ -1,257 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
2
|
-
import { renderHook, act } from '@testing-library/react';
|
|
3
|
-
import { Blac, Cubit, RerenderLogger } from '@blac/core';
|
|
4
|
-
import { RenderLoggingPlugin } from '@blac/plugin-render-logging';
|
|
5
|
-
import useBloc from '../useBloc';
|
|
6
|
-
|
|
7
|
-
class TestCubit extends Cubit<{ count: number; name: string }> {
|
|
8
|
-
constructor() {
|
|
9
|
-
super({ count: 0, name: 'test' });
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
increment = () => this.emit({ ...this.state, count: this.state.count + 1 });
|
|
13
|
-
changeName = (name: string) => this.emit({ ...this.state, name });
|
|
14
|
-
changeAll = () => this.emit({ count: this.state.count + 1, name: 'changed' });
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
describe('Rerender Logging', () => {
|
|
18
|
-
let consoleSpy: any;
|
|
19
|
-
let plugin: RenderLoggingPlugin;
|
|
20
|
-
|
|
21
|
-
beforeEach(() => {
|
|
22
|
-
Blac.resetInstance();
|
|
23
|
-
RerenderLogger.clear();
|
|
24
|
-
consoleSpy = {
|
|
25
|
-
log: vi.spyOn(console, 'log').mockImplementation(() => {}),
|
|
26
|
-
group: vi.spyOn(console, 'group').mockImplementation(() => {}),
|
|
27
|
-
groupEnd: vi.spyOn(console, 'groupEnd').mockImplementation(() => {}),
|
|
28
|
-
table: vi.spyOn(console, 'table').mockImplementation(() => {}),
|
|
29
|
-
};
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
afterEach(() => {
|
|
33
|
-
consoleSpy.log.mockRestore();
|
|
34
|
-
consoleSpy.group.mockRestore();
|
|
35
|
-
consoleSpy.groupEnd.mockRestore();
|
|
36
|
-
consoleSpy.table.mockRestore();
|
|
37
|
-
// Remove plugin if it was added
|
|
38
|
-
if (plugin) {
|
|
39
|
-
Blac.getInstance().plugins.remove(plugin.name);
|
|
40
|
-
}
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
it('should not log when rerender logging is disabled', () => {
|
|
44
|
-
// No plugin registered
|
|
45
|
-
const { result } = renderHook(() => useBloc(TestCubit));
|
|
46
|
-
|
|
47
|
-
act(() => {
|
|
48
|
-
result.current[1].increment();
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
expect(consoleSpy.log).not.toHaveBeenCalled();
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
it('should log rerenders when enabled with minimal level', () => {
|
|
55
|
-
plugin = new RenderLoggingPlugin({ enabled: true, level: 'minimal' });
|
|
56
|
-
Blac.getInstance().plugins.add(plugin);
|
|
57
|
-
|
|
58
|
-
const { result } = renderHook(() => useBloc(TestCubit));
|
|
59
|
-
|
|
60
|
-
// Clear logs from mount
|
|
61
|
-
consoleSpy.log.mockClear();
|
|
62
|
-
|
|
63
|
-
act(() => {
|
|
64
|
-
result.current[1].increment();
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
expect(consoleSpy.log).toHaveBeenCalled();
|
|
68
|
-
const logCall = consoleSpy.log.mock.calls[0][0];
|
|
69
|
-
expect(logCall).toContain('#2'); // Second render (first is mount)
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
it('should log state changes with normal level', () => {
|
|
73
|
-
plugin = new RenderLoggingPlugin({ enabled: true, level: 'normal' });
|
|
74
|
-
Blac.getInstance().plugins.add(plugin);
|
|
75
|
-
|
|
76
|
-
const { result } = renderHook(() => useBloc(TestCubit));
|
|
77
|
-
|
|
78
|
-
// Clear logs from mount
|
|
79
|
-
consoleSpy.log.mockClear();
|
|
80
|
-
|
|
81
|
-
act(() => {
|
|
82
|
-
result.current[1].increment();
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
expect(consoleSpy.log).toHaveBeenCalled();
|
|
86
|
-
const logCall = consoleSpy.log.mock.calls[0];
|
|
87
|
-
expect(logCall.join(' ')).toContain('State changed');
|
|
88
|
-
expect(logCall.join(' ')).toContain('(entire state)'); // Without proxy tracking individual properties
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
it('should log detailed information with detailed level', () => {
|
|
92
|
-
plugin = new RenderLoggingPlugin({ enabled: true, level: 'detailed' });
|
|
93
|
-
Blac.getInstance().plugins.add(plugin);
|
|
94
|
-
|
|
95
|
-
const { result } = renderHook(() => useBloc(TestCubit));
|
|
96
|
-
|
|
97
|
-
// Clear logs from mount
|
|
98
|
-
consoleSpy.log.mockClear();
|
|
99
|
-
consoleSpy.group.mockClear();
|
|
100
|
-
consoleSpy.table.mockClear();
|
|
101
|
-
|
|
102
|
-
act(() => {
|
|
103
|
-
result.current[1].increment();
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
expect(consoleSpy.group).toHaveBeenCalled();
|
|
107
|
-
expect(consoleSpy.table).toHaveBeenCalled();
|
|
108
|
-
|
|
109
|
-
const tableData = consoleSpy.table.mock.calls[0][0];
|
|
110
|
-
// Since we haven't accessed specific properties, it shows '(entire state)'
|
|
111
|
-
expect(tableData).toHaveProperty('(entire state)');
|
|
112
|
-
expect(tableData['(entire state)']).toHaveProperty('old');
|
|
113
|
-
expect(tableData['(entire state)']).toHaveProperty('new');
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
it('should track dependency changes when using manual dependencies', () => {
|
|
117
|
-
plugin = new RenderLoggingPlugin({ enabled: true, level: 'normal' });
|
|
118
|
-
Blac.getInstance().plugins.add(plugin);
|
|
119
|
-
|
|
120
|
-
let depValue = 1;
|
|
121
|
-
const { result, rerender } = renderHook(() =>
|
|
122
|
-
useBloc(TestCubit, {
|
|
123
|
-
dependencies: () => [depValue],
|
|
124
|
-
}),
|
|
125
|
-
);
|
|
126
|
-
|
|
127
|
-
// Clear logs from mount
|
|
128
|
-
consoleSpy.log.mockClear();
|
|
129
|
-
|
|
130
|
-
// Change dependency
|
|
131
|
-
depValue = 2;
|
|
132
|
-
rerender();
|
|
133
|
-
|
|
134
|
-
expect(consoleSpy.log).toHaveBeenCalled();
|
|
135
|
-
const logCall = consoleSpy.log.mock.calls[0];
|
|
136
|
-
const logMessage = logCall.join(' ');
|
|
137
|
-
|
|
138
|
-
// The plugin logs "Manual dependencies changed" in the description
|
|
139
|
-
expect(logMessage).toContain('Manual dependencies changed');
|
|
140
|
-
// Verify it's logged as a dependency change (check for the emoji)
|
|
141
|
-
expect(logMessage).toContain('🔗');
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
it('should respect filter function', () => {
|
|
145
|
-
plugin = new RenderLoggingPlugin({
|
|
146
|
-
enabled: true,
|
|
147
|
-
filter: ({ blocName }) => blocName !== 'TestCubit',
|
|
148
|
-
});
|
|
149
|
-
Blac.getInstance().plugins.add(plugin);
|
|
150
|
-
|
|
151
|
-
const { result } = renderHook(() => useBloc(TestCubit));
|
|
152
|
-
|
|
153
|
-
act(() => {
|
|
154
|
-
result.current[1].increment();
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
expect(consoleSpy.log).not.toHaveBeenCalled();
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
it('should group consecutive rerenders when groupRerenders is enabled', async () => {
|
|
161
|
-
plugin = new RenderLoggingPlugin({
|
|
162
|
-
enabled: true,
|
|
163
|
-
groupRerenders: true,
|
|
164
|
-
});
|
|
165
|
-
Blac.getInstance().plugins.add(plugin);
|
|
166
|
-
|
|
167
|
-
const { result } = renderHook(() => useBloc(TestCubit));
|
|
168
|
-
|
|
169
|
-
// Clear logs from mount
|
|
170
|
-
consoleSpy.log.mockClear();
|
|
171
|
-
consoleSpy.group.mockClear();
|
|
172
|
-
|
|
173
|
-
// Multiple rapid state changes - React may batch these
|
|
174
|
-
act(() => {
|
|
175
|
-
result.current[1].increment();
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
act(() => {
|
|
179
|
-
result.current[1].increment();
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
act(() => {
|
|
183
|
-
result.current[1].increment();
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
// Wait for debounce
|
|
187
|
-
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
188
|
-
|
|
189
|
-
expect(consoleSpy.group).toHaveBeenCalled();
|
|
190
|
-
const groupCall = consoleSpy.group.mock.calls[0][0];
|
|
191
|
-
// Should have at least 2 renders (React may batch some updates)
|
|
192
|
-
expect(groupCall).toMatch(/rendered \d+ times/);
|
|
193
|
-
// Verify it grouped multiple renders
|
|
194
|
-
expect(
|
|
195
|
-
parseInt(groupCall.match(/rendered (\d+) times/)?.[1] || '0'),
|
|
196
|
-
).toBeGreaterThanOrEqual(2);
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
it('should track multiple state properties changes', () => {
|
|
200
|
-
plugin = new RenderLoggingPlugin({ enabled: true, level: 'normal' });
|
|
201
|
-
Blac.getInstance().plugins.add(plugin);
|
|
202
|
-
|
|
203
|
-
const { result } = renderHook(() => useBloc(TestCubit));
|
|
204
|
-
|
|
205
|
-
// Clear logs from mount
|
|
206
|
-
consoleSpy.log.mockClear();
|
|
207
|
-
|
|
208
|
-
// Change all properties
|
|
209
|
-
act(() => {
|
|
210
|
-
result.current[1].changeAll();
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
expect(consoleSpy.log).toHaveBeenCalled();
|
|
214
|
-
const logCall = consoleSpy.log.mock.calls[0].join(' ');
|
|
215
|
-
expect(logCall).toContain('(entire state)'); // Without proxy tracking, all properties changed
|
|
216
|
-
});
|
|
217
|
-
|
|
218
|
-
it('should show mount reason for first render', () => {
|
|
219
|
-
plugin = new RenderLoggingPlugin({ enabled: true });
|
|
220
|
-
Blac.getInstance().plugins.add(plugin);
|
|
221
|
-
|
|
222
|
-
renderHook(() => useBloc(TestCubit));
|
|
223
|
-
|
|
224
|
-
expect(consoleSpy.log).toHaveBeenCalled();
|
|
225
|
-
const logCall = consoleSpy.log.mock.calls[0];
|
|
226
|
-
expect(logCall.join(' ')).toContain('mount');
|
|
227
|
-
expect(logCall.join(' ')).toContain('Component mounted');
|
|
228
|
-
});
|
|
229
|
-
|
|
230
|
-
it('should include stack trace in detailed mode when enabled', () => {
|
|
231
|
-
plugin = new RenderLoggingPlugin({
|
|
232
|
-
enabled: true,
|
|
233
|
-
level: 'detailed',
|
|
234
|
-
includeStackTrace: true,
|
|
235
|
-
});
|
|
236
|
-
Blac.getInstance().plugins.add(plugin);
|
|
237
|
-
|
|
238
|
-
const { result } = renderHook(() => useBloc(TestCubit));
|
|
239
|
-
|
|
240
|
-
// Clear logs from mount
|
|
241
|
-
consoleSpy.log.mockClear();
|
|
242
|
-
consoleSpy.group.mockClear();
|
|
243
|
-
|
|
244
|
-
act(() => {
|
|
245
|
-
result.current[1].increment();
|
|
246
|
-
});
|
|
247
|
-
|
|
248
|
-
// In detailed mode with stack trace, it logs the stack
|
|
249
|
-
const logCalls = consoleSpy.log.mock.calls;
|
|
250
|
-
const hasStackTrace = logCalls.some((call: any[]) =>
|
|
251
|
-
call.some(
|
|
252
|
-
(arg: any) => typeof arg === 'string' && arg.includes('Stack trace:'),
|
|
253
|
-
),
|
|
254
|
-
);
|
|
255
|
-
expect(hasStackTrace).toBe(true);
|
|
256
|
-
});
|
|
257
|
-
});
|