test(load3d): add unit tests for AnimationManager, CameraManager, RecordingManager, and load3dService#11733
test(load3d): add unit tests for AnimationManager, CameraManager, RecordingManager, and load3dService#11733
Conversation
…ordingManager, and load3dService
📝 WalkthroughWalkthroughThis pull request introduces four comprehensive Vitest test suites for core 3D loading and animation modules: Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
🚥 Pre-merge checks | ✅ 6 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (6 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Review rate limit: 7/8 reviews remaining, refill in 7 minutes and 30 seconds.Comment |
🎨 Storybook: ✅ Built — View Storybook |
🎭 Playwright: ✅ 1425 passed, 0 failed📊 Browser Reports
|
📦 Bundle: 5.23 MB gzip 🔴 +217 BDetailsSummary
Category Glance App Entry Points — 22.5 kB (baseline 22.5 kB) • ⚪ 0 BMain entry bundles and manifests
Status: 1 added / 1 removed Graph Workspace — 1.24 MB (baseline 1.24 MB) • ⚪ 0 BGraph editor runtime, canvas, workflow orchestration
Status: 1 added / 1 removed Views & Navigation — 77.7 kB (baseline 77.7 kB) • ⚪ 0 BTop-level views, pages, and routed surfaces
Status: 9 added / 9 removed / 2 unchanged Panels & Settings — 484 kB (baseline 484 kB) • ⚪ 0 BConfiguration panels, inspectors, and settings screens
Status: 10 added / 10 removed / 11 unchanged User & Accounts — 17.4 kB (baseline 17.4 kB) • ⚪ 0 BAuthentication, profile, and account management bundles
Status: 5 added / 5 removed / 2 unchanged Editors & Dialogs — 113 kB (baseline 113 kB) • ⚪ 0 BModals, dialogs, drawers, and in-app editors
Status: 4 added / 4 removed UI Components — 61 kB (baseline 61 kB) • ⚪ 0 BReusable component library chunks
Status: 5 added / 5 removed / 8 unchanged Data & Services — 3.04 MB (baseline 3.04 MB) • ⚪ 0 BStores, services, APIs, and repositories
Status: 13 added / 13 removed / 4 unchanged Utilities & Hooks — 364 kB (baseline 364 kB) • ⚪ 0 BHelpers, composables, and utility bundles
Status: 13 added / 13 removed / 18 unchanged Vendor & Third-Party — 9.88 MB (baseline 9.88 MB) • ⚪ 0 BExternal libraries and shared vendor chunks Status: 16 unchanged Other — 8.83 MB (baseline 8.83 MB) • ⚪ 0 BBundles that do not match a named category
Status: 57 added / 57 removed / 78 unchanged ⚡ Performance Report
No regressions detected. All metrics
Historical variance (last 15 runs)
Trend (last 15 commits on main)
Raw data{
"timestamp": "2026-04-29T00:53:18.960Z",
"gitSha": "94388109ee5e1f09ce396afd3402ef7a32550e09",
"branch": "load3d-tier1-manager-tests",
"measurements": [
{
"name": "canvas-idle",
"durationMs": 2043.7919999999963,
"styleRecalcs": 9,
"styleRecalcDurationMs": 6.566000000000001,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 354.041,
"heapDeltaBytes": 828296,
"heapUsedBytes": 67309044,
"domNodes": 18,
"jsHeapTotalBytes": 18972672,
"scriptDurationMs": 13.468,
"eventListeners": 6,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "canvas-idle",
"durationMs": 2023.6710000000357,
"styleRecalcs": 11,
"styleRecalcDurationMs": 9.185000000000002,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 327.02,
"heapDeltaBytes": 1513880,
"heapUsedBytes": 67626028,
"domNodes": 22,
"jsHeapTotalBytes": 19496960,
"scriptDurationMs": 22.015,
"eventListeners": 6,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "canvas-idle",
"durationMs": 2006.9270000000188,
"styleRecalcs": 10,
"styleRecalcDurationMs": 7.9399999999999995,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 333.473,
"heapDeltaBytes": 2501760,
"heapUsedBytes": 68531812,
"domNodes": 20,
"jsHeapTotalBytes": 21233664,
"scriptDurationMs": 21.722,
"eventListeners": 6,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "canvas-mouse-sweep",
"durationMs": 1759.0109999999868,
"styleRecalcs": 73,
"styleRecalcDurationMs": 33.991,
"layouts": 12,
"layoutDurationMs": 2.937,
"taskDurationMs": 719.8340000000001,
"heapDeltaBytes": -18987112,
"heapUsedBytes": 47138576,
"domNodes": -255,
"jsHeapTotalBytes": 19623936,
"scriptDurationMs": 120.06400000000001,
"eventListeners": -131,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.670000000000012,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "canvas-mouse-sweep",
"durationMs": 1734.8610000000235,
"styleRecalcs": 72,
"styleRecalcDurationMs": 30.058,
"layouts": 12,
"layoutDurationMs": 2.972,
"taskDurationMs": 714.366,
"heapDeltaBytes": -20169308,
"heapUsedBytes": 46517744,
"domNodes": -263,
"jsHeapTotalBytes": 19361792,
"scriptDurationMs": 112.069,
"eventListeners": -127,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333335,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "canvas-mouse-sweep",
"durationMs": 1759.5939999999928,
"styleRecalcs": 73,
"styleRecalcDurationMs": 32.74399999999999,
"layouts": 12,
"layoutDurationMs": 2.941,
"taskDurationMs": 706.369,
"heapDeltaBytes": -19298756,
"heapUsedBytes": 47175080,
"domNodes": -259,
"jsHeapTotalBytes": 19886080,
"scriptDurationMs": 115.24699999999999,
"eventListeners": -127,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "canvas-zoom-sweep",
"durationMs": 1735.0959999999986,
"styleRecalcs": 31,
"styleRecalcDurationMs": 15.107000000000001,
"layouts": 6,
"layoutDurationMs": 0.636,
"taskDurationMs": 280.35900000000004,
"heapDeltaBytes": 5425912,
"heapUsedBytes": 71869052,
"domNodes": 77,
"jsHeapTotalBytes": 19234816,
"scriptDurationMs": 16.60799999999999,
"eventListeners": 21,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "canvas-zoom-sweep",
"durationMs": 1724.2889999999989,
"styleRecalcs": 31,
"styleRecalcDurationMs": 13.629999999999997,
"layouts": 6,
"layoutDurationMs": 0.48900000000000005,
"taskDurationMs": 272.745,
"heapDeltaBytes": 5690624,
"heapUsedBytes": 71981488,
"domNodes": 77,
"jsHeapTotalBytes": 19234816,
"scriptDurationMs": 21.911999999999995,
"eventListeners": 21,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "canvas-zoom-sweep",
"durationMs": 1705.1510000000007,
"styleRecalcs": 31,
"styleRecalcDurationMs": 13.575,
"layouts": 6,
"layoutDurationMs": 0.45899999999999996,
"taskDurationMs": 254.16700000000003,
"heapDeltaBytes": 132352,
"heapUsedBytes": 48366712,
"domNodes": 76,
"jsHeapTotalBytes": 14417920,
"scriptDurationMs": 15.999000000000002,
"eventListeners": 21,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.670000000000012,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "dom-widget-clipping",
"durationMs": 506.0579999999959,
"styleRecalcs": 11,
"styleRecalcDurationMs": 8.01,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 302.464,
"heapDeltaBytes": -10660972,
"heapUsedBytes": 55834692,
"domNodes": 18,
"jsHeapTotalBytes": 20447232,
"scriptDurationMs": 52.01599999999999,
"eventListeners": 2,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "dom-widget-clipping",
"durationMs": 547.6400000000012,
"styleRecalcs": 13,
"styleRecalcDurationMs": 8.225,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 311.72299999999996,
"heapDeltaBytes": -11804528,
"heapUsedBytes": 54126260,
"domNodes": 22,
"jsHeapTotalBytes": 20021248,
"scriptDurationMs": 58.148,
"eventListeners": 2,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "dom-widget-clipping",
"durationMs": 463.78200000003744,
"styleRecalcs": 10,
"styleRecalcDurationMs": 5.46,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 279.76199999999994,
"heapDeltaBytes": 4746252,
"heapUsedBytes": 51772860,
"domNodes": 16,
"jsHeapTotalBytes": 10223616,
"scriptDurationMs": 49.27000000000001,
"eventListeners": 0,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.700000000000273
},
{
"name": "large-graph-idle",
"durationMs": 2028.2210000000305,
"styleRecalcs": 9,
"styleRecalcDurationMs": 8.055999999999997,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 485.60499999999996,
"heapDeltaBytes": 12222924,
"heapUsedBytes": 70307464,
"domNodes": -261,
"jsHeapTotalBytes": -229376,
"scriptDurationMs": 87.977,
"eventListeners": -127,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "large-graph-idle",
"durationMs": 2039.5519999999578,
"styleRecalcs": 9,
"styleRecalcDurationMs": 7.49,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 497.36899999999997,
"heapDeltaBytes": 11730548,
"heapUsedBytes": 69801672,
"domNodes": -261,
"jsHeapTotalBytes": 1081344,
"scriptDurationMs": 89.307,
"eventListeners": -127,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "large-graph-idle",
"durationMs": 2020.7169999999905,
"styleRecalcs": 9,
"styleRecalcDurationMs": 7.477000000000001,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 487.774,
"heapDeltaBytes": 4097824,
"heapUsedBytes": 61258992,
"domNodes": -261,
"jsHeapTotalBytes": 557056,
"scriptDurationMs": 86.25099999999999,
"eventListeners": -127,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "large-graph-pan",
"durationMs": 2102.359999999976,
"styleRecalcs": 68,
"styleRecalcDurationMs": 16.169000000000004,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 960.8639999999999,
"heapDeltaBytes": -1791840,
"heapUsedBytes": 57296592,
"domNodes": -263,
"jsHeapTotalBytes": 1286144,
"scriptDurationMs": 360.735,
"eventListeners": -127,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333335,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "large-graph-pan",
"durationMs": 2116.3589999999886,
"styleRecalcs": 69,
"styleRecalcDurationMs": 17.000999999999998,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 973.9,
"heapDeltaBytes": 531240,
"heapUsedBytes": 58656028,
"domNodes": -261,
"jsHeapTotalBytes": 1286144,
"scriptDurationMs": 362.556,
"eventListeners": -125,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.699999999999818
},
{
"name": "large-graph-pan",
"durationMs": 2105.536000000029,
"styleRecalcs": 69,
"styleRecalcDurationMs": 15.702000000000004,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 980.4599999999999,
"heapDeltaBytes": 11798772,
"heapUsedBytes": 71041640,
"domNodes": -251,
"jsHeapTotalBytes": 761856,
"scriptDurationMs": 379.236,
"eventListeners": -125,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333335,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "large-graph-zoom",
"durationMs": 3135.1270000000113,
"styleRecalcs": 66,
"styleRecalcDurationMs": 17.319000000000003,
"layouts": 60,
"layoutDurationMs": 6.577,
"taskDurationMs": 1188.4869999999999,
"heapDeltaBytes": 9296864,
"heapUsedBytes": 69057692,
"domNodes": -267,
"jsHeapTotalBytes": 5799936,
"scriptDurationMs": 458.21099999999996,
"eventListeners": -123,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "large-graph-zoom",
"durationMs": 3116.9539999999643,
"styleRecalcs": 65,
"styleRecalcDurationMs": 15.526000000000002,
"layouts": 60,
"layoutDurationMs": 6.515999999999999,
"taskDurationMs": 1174.4620000000002,
"heapDeltaBytes": 8385984,
"heapUsedBytes": 69153312,
"domNodes": -267,
"jsHeapTotalBytes": 3899392,
"scriptDurationMs": 446.651,
"eventListeners": -123,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "large-graph-zoom",
"durationMs": 3132.111000000009,
"styleRecalcs": 65,
"styleRecalcDurationMs": 15.847,
"layouts": 60,
"layoutDurationMs": 6.44,
"taskDurationMs": 1175.7040000000002,
"heapDeltaBytes": 8583804,
"heapUsedBytes": 69342428,
"domNodes": -266,
"jsHeapTotalBytes": 5275648,
"scriptDurationMs": 456.751,
"eventListeners": -123,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "minimap-idle",
"durationMs": 2012.3779999999556,
"styleRecalcs": 8,
"styleRecalcDurationMs": 6.561000000000001,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 473.196,
"heapDeltaBytes": 6431600,
"heapUsedBytes": 65440324,
"domNodes": -264,
"jsHeapTotalBytes": 557056,
"scriptDurationMs": 85.368,
"eventListeners": -127,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333335,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "minimap-idle",
"durationMs": 2028.173000000038,
"styleRecalcs": 9,
"styleRecalcDurationMs": 11.097,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 483.965,
"heapDeltaBytes": 13238176,
"heapUsedBytes": 72212212,
"domNodes": -262,
"jsHeapTotalBytes": 294912,
"scriptDurationMs": 84.81700000000001,
"eventListeners": -127,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "minimap-idle",
"durationMs": 2021.3990000000877,
"styleRecalcs": 9,
"styleRecalcDurationMs": 7.817999999999999,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 476.81300000000005,
"heapDeltaBytes": 4918056,
"heapUsedBytes": 63990292,
"domNodes": -263,
"jsHeapTotalBytes": 557056,
"scriptDurationMs": 86.63600000000001,
"eventListeners": -127,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333335,
"p95FrameDurationMs": 16.699999999999818
},
{
"name": "subgraph-dom-widget-clipping",
"durationMs": 510.39800000000923,
"styleRecalcs": 47,
"styleRecalcDurationMs": 9.406,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 305.61499999999995,
"heapDeltaBytes": 9139256,
"heapUsedBytes": 58782804,
"domNodes": 20,
"jsHeapTotalBytes": 15466496,
"scriptDurationMs": 107.83,
"eventListeners": 8,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.669999999999998,
"p95FrameDurationMs": 16.700000000000273
},
{
"name": "subgraph-dom-widget-clipping",
"durationMs": 525.7239999999683,
"styleRecalcs": 47,
"styleRecalcDurationMs": 10.038999999999998,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 316.651,
"heapDeltaBytes": -10436780,
"heapUsedBytes": 56051340,
"domNodes": 20,
"jsHeapTotalBytes": 20971520,
"scriptDurationMs": 105.09100000000001,
"eventListeners": 8,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.669999999999998,
"p95FrameDurationMs": 16.700000000000273
},
{
"name": "subgraph-dom-widget-clipping",
"durationMs": 532.4459999999362,
"styleRecalcs": 48,
"styleRecalcDurationMs": 10.538,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 346.943,
"heapDeltaBytes": -10520616,
"heapUsedBytes": 55758792,
"domNodes": 22,
"jsHeapTotalBytes": 21233664,
"scriptDurationMs": 118.289,
"eventListeners": 8,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.670000000000012,
"p95FrameDurationMs": 16.700000000000273
},
{
"name": "subgraph-idle",
"durationMs": 2005.326000000025,
"styleRecalcs": 12,
"styleRecalcDurationMs": 9.163,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 332.71,
"heapDeltaBytes": 1592888,
"heapUsedBytes": 67984024,
"domNodes": 23,
"jsHeapTotalBytes": 19496960,
"scriptDurationMs": 18.536000000000005,
"eventListeners": 6,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "subgraph-idle",
"durationMs": 1992.1689999999899,
"styleRecalcs": 11,
"styleRecalcDurationMs": 8.525000000000002,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 324.33799999999997,
"heapDeltaBytes": 925596,
"heapUsedBytes": 67500872,
"domNodes": 22,
"jsHeapTotalBytes": 18972672,
"scriptDurationMs": 17.131,
"eventListeners": 6,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "subgraph-idle",
"durationMs": 2006.5069999999423,
"styleRecalcs": 11,
"styleRecalcDurationMs": 8.959999999999999,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 333.32300000000004,
"heapDeltaBytes": 1315628,
"heapUsedBytes": 67854724,
"domNodes": 22,
"jsHeapTotalBytes": 19234816,
"scriptDurationMs": 18.449,
"eventListeners": 6,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "subgraph-mouse-sweep",
"durationMs": 1997.4619999999845,
"styleRecalcs": 83,
"styleRecalcDurationMs": 38.269,
"layouts": 16,
"layoutDurationMs": 3.701,
"taskDurationMs": 861.421,
"heapDeltaBytes": 3842220,
"heapUsedBytes": 52487832,
"domNodes": -261,
"jsHeapTotalBytes": 14544896,
"scriptDurationMs": 85.174,
"eventListeners": -127,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "subgraph-mouse-sweep",
"durationMs": 1691.4730000000873,
"styleRecalcs": 76,
"styleRecalcDurationMs": 32.031,
"layouts": 16,
"layoutDurationMs": 3.86,
"taskDurationMs": 594.349,
"heapDeltaBytes": -7425596,
"heapUsedBytes": 58065260,
"domNodes": 63,
"jsHeapTotalBytes": 21069824,
"scriptDurationMs": 84.976,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.699999999999818
},
{
"name": "subgraph-mouse-sweep",
"durationMs": 1676.0649999999941,
"styleRecalcs": 76,
"styleRecalcDurationMs": 30.889,
"layouts": 16,
"layoutDurationMs": 3.525,
"taskDurationMs": 579.895,
"heapDeltaBytes": -6037060,
"heapUsedBytes": 60302176,
"domNodes": 62,
"jsHeapTotalBytes": 20447232,
"scriptDurationMs": 83.194,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.699999999999818
},
{
"name": "viewport-pan-sweep",
"durationMs": 8141.294000000016,
"styleRecalcs": 249,
"styleRecalcDurationMs": 45.26,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 3547.635,
"heapDeltaBytes": 18665784,
"heapUsedBytes": 76840976,
"domNodes": -264,
"jsHeapTotalBytes": 7053312,
"scriptDurationMs": 1299.184,
"eventListeners": -111,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333338,
"p95FrameDurationMs": 16.80000000000109
},
{
"name": "viewport-pan-sweep",
"durationMs": 8158.346000000051,
"styleRecalcs": 249,
"styleRecalcDurationMs": 46.355999999999995,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 3243.414,
"heapDeltaBytes": 11002524,
"heapUsedBytes": 69251160,
"domNodes": -261,
"jsHeapTotalBytes": 3121152,
"scriptDurationMs": 1140.647,
"eventListeners": -111,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "viewport-pan-sweep",
"durationMs": 8118.290999999999,
"styleRecalcs": 249,
"styleRecalcDurationMs": 44.642,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 3230.501,
"heapDeltaBytes": 9928464,
"heapUsedBytes": 67956676,
"domNodes": -261,
"jsHeapTotalBytes": 7577600,
"scriptDurationMs": 1142.502,
"eventListeners": -111,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.80000000000109
},
{
"name": "vue-large-graph-idle",
"durationMs": 10234.077000000014,
"styleRecalcs": 0,
"styleRecalcDurationMs": 0,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 10222.312,
"heapDeltaBytes": -43721712,
"heapUsedBytes": 173795296,
"domNodes": -9850,
"jsHeapTotalBytes": -16715776,
"scriptDurationMs": 505.78999999999996,
"eventListeners": -23961,
"totalBlockingTimeMs": 0,
"frameDurationMs": 17.223333333333358,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "vue-large-graph-idle",
"durationMs": 10267.415999999912,
"styleRecalcs": 0,
"styleRecalcDurationMs": 0,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 10251.101,
"heapDeltaBytes": -31269480,
"heapUsedBytes": 167997276,
"domNodes": -9850,
"jsHeapTotalBytes": 24702976,
"scriptDurationMs": 549.809,
"eventListeners": -23960,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "vue-large-graph-idle",
"durationMs": 9907.836999999972,
"styleRecalcs": 0,
"styleRecalcDurationMs": 0,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 9895.302,
"heapDeltaBytes": -28153024,
"heapUsedBytes": 173889008,
"domNodes": -9850,
"jsHeapTotalBytes": 24965120,
"scriptDurationMs": 512.4759999999999,
"eventListeners": -23963,
"totalBlockingTimeMs": 0,
"frameDurationMs": 17.223333333333358,
"p95FrameDurationMs": 16.80000000000291
},
{
"name": "vue-large-graph-pan",
"durationMs": 13068.19500000006,
"styleRecalcs": 66,
"styleRecalcDurationMs": 14.081000000000039,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 13049.938999999998,
"heapDeltaBytes": -53853492,
"heapUsedBytes": 164814600,
"domNodes": -9850,
"jsHeapTotalBytes": -12783616,
"scriptDurationMs": 771.778,
"eventListeners": -23983,
"totalBlockingTimeMs": 23,
"frameDurationMs": 17.223333333333358,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "vue-large-graph-pan",
"durationMs": 13022.888999999963,
"styleRecalcs": 67,
"styleRecalcDurationMs": 14.63600000000001,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 13002.209999999997,
"heapDeltaBytes": -41557580,
"heapUsedBytes": 176444560,
"domNodes": -9848,
"jsHeapTotalBytes": -10510336,
"scriptDurationMs": 813.852,
"eventListeners": -23983,
"totalBlockingTimeMs": 48,
"frameDurationMs": 17.223333333333358,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "vue-large-graph-pan",
"durationMs": 12855.70800000005,
"styleRecalcs": 65,
"styleRecalcDurationMs": 14.11800000000002,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 12836.624999999998,
"heapDeltaBytes": -34969164,
"heapUsedBytes": 172610780,
"domNodes": -9850,
"jsHeapTotalBytes": -16539648,
"scriptDurationMs": 811.0630000000001,
"eventListeners": -23957,
"totalBlockingTimeMs": 17,
"frameDurationMs": 17.223333333333237,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "workflow-execution",
"durationMs": 463.87000000004264,
"styleRecalcs": 17,
"styleRecalcDurationMs": 22.721000000000004,
"layouts": 5,
"layoutDurationMs": 1.374,
"taskDurationMs": 131.749,
"heapDeltaBytes": -14926720,
"heapUsedBytes": 52508112,
"domNodes": 167,
"jsHeapTotalBytes": 4816896,
"scriptDurationMs": 27.346999999999998,
"eventListeners": 71,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.799999999999727
},
{
"name": "workflow-execution",
"durationMs": 474.7790000000123,
"styleRecalcs": 20,
"styleRecalcDurationMs": 21.819,
"layouts": 5,
"layoutDurationMs": 1.283,
"taskDurationMs": 114.111,
"heapDeltaBytes": 5145388,
"heapUsedBytes": 56286560,
"domNodes": 178,
"jsHeapTotalBytes": 0,
"scriptDurationMs": 20.579,
"eventListeners": 71,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.663333333333338,
"p95FrameDurationMs": 16.700000000000273
},
{
"name": "workflow-execution",
"durationMs": 462.1759999999995,
"styleRecalcs": 16,
"styleRecalcDurationMs": 19.419,
"layouts": 4,
"layoutDurationMs": 0.9430000000000003,
"taskDurationMs": 115.67699999999999,
"heapDeltaBytes": -14991828,
"heapUsedBytes": 52783712,
"domNodes": 154,
"jsHeapTotalBytes": 4030464,
"scriptDurationMs": 21.055,
"eventListeners": 71,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
}
]
} |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (7)
src/extensions/core/load3d/CameraManager.test.ts (2)
34-37: Remove the inline explanatory comment.The helper name already explains the intent, and the comment adds noise without changing behavior. As per coding guidelines, avoid new usage of code comments; do not add or retain redundant comments.
Suggested cleanup
function makeRenderer(): THREE.WebGLRenderer { - // CameraManager only stores `_renderer` but never reads it. An empty object - // suffices and avoids needing a WebGL context in happy-dom. return {} as THREE.WebGLRenderer }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/extensions/core/load3d/CameraManager.test.ts` around lines 34 - 37, Remove the inline explanatory comment inside the makeRenderer() helper in CameraManager.test.ts so the function only returns the cast empty object; keep the function name and implementation (return {} as THREE.WebGLRenderer) unchanged and do not add any new comments—just delete the redundant comment text.
18-32: Type the controls stub instead of casting it at each use site.
makeControlsStub()currently returns an object that is later forced throughas unknown as OrbitControlsin several tests. That weakens the test contract and makes future CameraManager changes easier to miss.Suggested refactor
import * as THREE from 'three' import type { OrbitControls } from 'three/examples/jsm/controls/OrbitControls' import { beforeEach, describe, expect, it, vi } from 'vitest' import { CameraManager } from './CameraManager' import type { CameraState, EventManagerInterface } from './interfaces' function makeMockEventManager() { return { addEventListener: vi.fn(), removeEventListener: vi.fn(), emitEvent: vi.fn() } satisfies EventManagerInterface } type ControlsListener = () => void +type ControlsStub = Pick< + OrbitControls, + 'target' | 'object' | 'update' | 'addEventListener' +> & { + fire(event: string): void +} -function makeControlsStub() { +function makeControlsStub(): ControlsStub { const listeners: Record<string, ControlsListener[]> = {} return { target: new THREE.Vector3(), object: null as THREE.Camera | null, update: vi.fn(),🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/extensions/core/load3d/CameraManager.test.ts` around lines 18 - 32, makeControlsStub() is untyped and tests keep using "as unknown as OrbitControls" casts; change makeControlsStub to return a properly typed stub (e.g., declare its signature as returning OrbitControls or a narrow stub interface matching the used subset: target, object, update, addEventListener, fire) so callers no longer need casts. Update the function signature in CameraManager.test.ts to use that type, implement the ControlsListener and required method shapes inside the returned object to satisfy the type, and remove all "as unknown as OrbitControls" cast sites in the tests.src/extensions/core/load3d/AnimationManager.test.ts (1)
33-37: Restore spies inafterEachto keep failures isolated.
vi.clearAllMocks()only clears call history; if a warning assertion fails beforewarn.mockRestore(), the mockedconsole.warncan leak into later tests. A shared teardown makes the suite self-cleaning even on early exits.Proposed fix
-import { beforeEach, describe, expect, it, vi } from 'vitest' +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' describe('AnimationManager', () => { let events: ReturnType<typeof makeMockEventManager> let manager: AnimationManager beforeEach(() => { vi.clearAllMocks() events = makeMockEventManager() manager = new AnimationManager(events) }) + + afterEach(() => { + vi.restoreAllMocks() + })As per coding guidelines, keep module mocks contained; do not use global mutable state within test files.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/extensions/core/load3d/AnimationManager.test.ts` around lines 33 - 37, Add a shared teardown that restores spies after each test to prevent mocked globals leaking: in the test file add an afterEach that calls vi.clearAllMocks() and vi.restoreAllMocks() (or explicitly calls restore on any spy variables like the console.warn spy used in the suite) so that mocks set up during beforeEach/individual tests (e.g., via vi.spyOn(console, "warn") or similar) are fully restored and isolated between tests involving AnimationManager and makeMockEventManager.src/services/load3dService.test.ts (2)
26-35: Keep this tracker out of module scope.
createdNodesis shared mutable state across the whole file, so cleanup now depends on every path going throughmakeNode(). Move the tracker intovi.hoisted()or otherwise scope it per test so the suite stays isolated.As per coding guidelines, keep module mocks contained; do not use global mutable state within test files; use
vi.hoisted()if necessary.♻️ Possible cleanup
-const createdNodes = new Set<LGraphNode>() +const createdNodes = vi.hoisted(() => new Set<LGraphNode>())🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/services/load3dService.test.ts` around lines 26 - 35, The shared mutable Set createdNodes and factory makeNode are declared at module scope; move createdNodes into test-scoped hoisting using vi.hoisted() (or initialize it inside a beforeEach) and update makeNode to reference that hoisted/locally-scoped Set so each test gets an isolated tracker; ensure beforeEach drains the hoisted createdNodes and keep references to the unique symbols createdNodes and makeNode consistent so load3dService's viewerInstances cleanup still runs per test.
37-49: Tighten these fixtures instead of double-casting.
makeLoad3d(), thefactory as unknown as typeof useLoad3dViewerMockcast, and theObject.assign(... ) as LGraphNodefixture all bypass type checking. A minimal typed fixture orsatisfies-based helper would make these tests fail when the service contract drifts.Based on learnings, prefer
satisfies InterfaceTypeinstead of type assertions in test helpers.Also applies to: 194-207, 255-257
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/services/load3dService.test.ts` around lines 37 - 49, Replace the unsafe double-casts with minimally typed fixtures using TypeScript’s "satisfies" or a small generic test-helper so the compiler enforces the service contract: change makeLoad3d() to return an object that satisfies the Load3d interface (instead of "as unknown as Load3d"), update the factory cast (the "factory as unknown as typeof useLoad3dViewerMock" usage) to produce a properly typed mock via a helper/generic returning satisfies typeof useLoad3dViewerMock, and replace the "Object.assign(... ) as LGraphNode" assertion with an Object.assign result typed to satisfy LGraphNode; apply the same pattern for the other occurrences noted (around the factory and lines 194-207, 255-257) so tests will fail if the service contract drifts.src/extensions/core/load3d/RecordingManager.test.ts (2)
29-50: Keep the MediaRecorder mock state local to the suite.
MockMediaRecorder.instancesis shared mutable state across the file. It works only becausebeforeEachresets it, but that makes the suite fragile if setup fails early. Prefer capturing the constructed recorder in a suite-local holder instead.As per coding guidelines, "Keep module mocks contained; do not use global mutable state within test files; use vi.hoisted() if necessary."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/extensions/core/load3d/RecordingManager.test.ts` around lines 29 - 50, The MockMediaRecorder.instances static array is shared mutable state; remove the static instances property from class MockMediaRecorder and instead capture each constructed recorder in a suite-local variable (e.g., declare a hoisted let mockRecorder via vi.hoisted or a top-level let assigned in beforeEach) inside the test file; in MockMediaRecorder's constructor assign the newly created instance to that suite-local holder so tests call methods like mockRecorder.pushChunk(...) and assertions use that local variable rather than MockMediaRecorder.instances, ensuring no global mutable state is relied on.
205-210: Avoid mutatingrecordingStartTimedirectly.This reaches into a private field and relies on a type cast, so the test will break on internal refactors even if public behavior stays correct. Prefer controlling time through the public path (
Date.now()/fake timers) so the assertion stays behavior-focused.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/extensions/core/load3d/RecordingManager.test.ts` around lines 205 - 210, The test "reports a non-zero duration after recording" currently mutates the private recordingStartTime field; instead, control time via the public API by using fake timers or mocking Date.now so the test remains behavior-focused. Replace the direct assignment to recordingStartTime with a setup that calls manager.startRecording() while using jest.useFakeTimers()/jest.setSystemTime(...) or a Date.now mock to simulate Date.now() returning Date.now() - 2000, then proceed to stop/inspect the recording duration via the public methods on manager to assert a ~2s duration; keep references to manager.startRecording and the recording duration assertions (no private-field access).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/extensions/core/load3d/RecordingManager.test.ts`:
- Around line 90-97: The test currently calls vi.spyOn on
HTMLCanvasElement.prototype.captureStream which doesn't exist in happy-dom
20.0.11; define the shim first (e.g., assign or Object.defineProperty
HTMLCanvasElement.prototype.captureStream = makeStream) before calling vi.spyOn
so the property exists, then spy or mock it as needed—refer to
HTMLCanvasElement.prototype, captureStream, makeStream, and vi.spyOn in the
RecordingManager.test setup to locate where to add the definition.
---
Nitpick comments:
In `@src/extensions/core/load3d/AnimationManager.test.ts`:
- Around line 33-37: Add a shared teardown that restores spies after each test
to prevent mocked globals leaking: in the test file add an afterEach that calls
vi.clearAllMocks() and vi.restoreAllMocks() (or explicitly calls restore on any
spy variables like the console.warn spy used in the suite) so that mocks set up
during beforeEach/individual tests (e.g., via vi.spyOn(console, "warn") or
similar) are fully restored and isolated between tests involving
AnimationManager and makeMockEventManager.
In `@src/extensions/core/load3d/CameraManager.test.ts`:
- Around line 34-37: Remove the inline explanatory comment inside the
makeRenderer() helper in CameraManager.test.ts so the function only returns the
cast empty object; keep the function name and implementation (return {} as
THREE.WebGLRenderer) unchanged and do not add any new comments—just delete the
redundant comment text.
- Around line 18-32: makeControlsStub() is untyped and tests keep using "as
unknown as OrbitControls" casts; change makeControlsStub to return a properly
typed stub (e.g., declare its signature as returning OrbitControls or a narrow
stub interface matching the used subset: target, object, update,
addEventListener, fire) so callers no longer need casts. Update the function
signature in CameraManager.test.ts to use that type, implement the
ControlsListener and required method shapes inside the returned object to
satisfy the type, and remove all "as unknown as OrbitControls" cast sites in the
tests.
In `@src/extensions/core/load3d/RecordingManager.test.ts`:
- Around line 29-50: The MockMediaRecorder.instances static array is shared
mutable state; remove the static instances property from class MockMediaRecorder
and instead capture each constructed recorder in a suite-local variable (e.g.,
declare a hoisted let mockRecorder via vi.hoisted or a top-level let assigned in
beforeEach) inside the test file; in MockMediaRecorder's constructor assign the
newly created instance to that suite-local holder so tests call methods like
mockRecorder.pushChunk(...) and assertions use that local variable rather than
MockMediaRecorder.instances, ensuring no global mutable state is relied on.
- Around line 205-210: The test "reports a non-zero duration after recording"
currently mutates the private recordingStartTime field; instead, control time
via the public API by using fake timers or mocking Date.now so the test remains
behavior-focused. Replace the direct assignment to recordingStartTime with a
setup that calls manager.startRecording() while using
jest.useFakeTimers()/jest.setSystemTime(...) or a Date.now mock to simulate
Date.now() returning Date.now() - 2000, then proceed to stop/inspect the
recording duration via the public methods on manager to assert a ~2s duration;
keep references to manager.startRecording and the recording duration assertions
(no private-field access).
In `@src/services/load3dService.test.ts`:
- Around line 26-35: The shared mutable Set createdNodes and factory makeNode
are declared at module scope; move createdNodes into test-scoped hoisting using
vi.hoisted() (or initialize it inside a beforeEach) and update makeNode to
reference that hoisted/locally-scoped Set so each test gets an isolated tracker;
ensure beforeEach drains the hoisted createdNodes and keep references to the
unique symbols createdNodes and makeNode consistent so load3dService's
viewerInstances cleanup still runs per test.
- Around line 37-49: Replace the unsafe double-casts with minimally typed
fixtures using TypeScript’s "satisfies" or a small generic test-helper so the
compiler enforces the service contract: change makeLoad3d() to return an object
that satisfies the Load3d interface (instead of "as unknown as Load3d"), update
the factory cast (the "factory as unknown as typeof useLoad3dViewerMock" usage)
to produce a properly typed mock via a helper/generic returning satisfies typeof
useLoad3dViewerMock, and replace the "Object.assign(... ) as LGraphNode"
assertion with an Object.assign result typed to satisfy LGraphNode; apply the
same pattern for the other occurrences noted (around the factory and lines
194-207, 255-257) so tests will fail if the service contract drifts.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 2545f52b-9dcc-4648-a231-69272958a6c7
📒 Files selected for processing (4)
src/extensions/core/load3d/AnimationManager.test.tssrc/extensions/core/load3d/CameraManager.test.tssrc/extensions/core/load3d/RecordingManager.test.tssrc/services/load3dService.test.ts
| // happy-dom canvases lack captureStream; stub it on the prototype so | ||
| // every canvas the production code creates gets a usable stream. | ||
| vi.spyOn( | ||
| HTMLCanvasElement.prototype as unknown as { | ||
| captureStream: (fps?: number) => MediaStream | ||
| }, | ||
| 'captureStream' | ||
| ).mockImplementation(makeStream) |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -n "captureStream" srcRepository: Comfy-Org/ComfyUI_frontend
Length of output: 503
🏁 Script executed:
cat package.json | grep -A 5 -B 5 "happy-dom"Repository: Comfy-Org/ComfyUI_frontend
Length of output: 397
🏁 Script executed:
rg "happy-dom" --type jsonRepository: Comfy-Org/ComfyUI_frontend
Length of output: 112
🏁 Script executed:
# Check if there's any test setup or vitest config that handles this
fd "vitest.config|setupFiles|test.setup" srcRepository: Comfy-Org/ComfyUI_frontend
Length of output: 52
🏁 Script executed:
# Check the test file more closely to understand the full context
cat -n src/extensions/core/load3d/RecordingManager.test.ts | head -100Repository: Comfy-Org/ComfyUI_frontend
Length of output: 3746
🏁 Script executed:
# Find the actual happy-dom version from lock files
fd "pnpm-lock|package-lock|yarn.lock" | head -1 | xargs cat | rg "happy-dom@.*version" -A 2 | head -20Repository: Comfy-Org/ComfyUI_frontend
Length of output: 52
🏁 Script executed:
# Check pnpm workspace catalog if present
find . -name "pnpm-workspace.yaml" -o -name ".pnpmfile.cjs" | xargs cat 2>/dev/null | rg "happy-dom" -A 2Repository: Comfy-Org/ComfyUI_frontend
Length of output: 122
🏁 Script executed:
# Look for .npmrc or pnpm config that defines catalog
cat .npmrc .pnpmfile.cjs pnpm-workspace.yaml 2>/dev/null | rg -A 10 "catalog\|happy-dom"Repository: Comfy-Org/ComfyUI_frontend
Length of output: 52
🏁 Script executed:
# Check if there's a package.json root with catalog definitions
fd "package.json" . -type f | xargs rg "\"happy-dom\"" -B 5 -A 5 | grep -A 10 "catalog"Repository: Comfy-Org/ComfyUI_frontend
Length of output: 729
🌐 Web query:
happy-dom 20.0.11 HTMLCanvasElement captureStream
💡 Result:
In happy-dom version 20.0.11, HTMLCanvasElement.captureStream is not supported. Happy DOM provides only basic structural support for HTMLCanvasElement (added via issue #1332, closed August 2024), but lacks full Canvas API implementation, particularly getContext('2d') and advanced methods like captureStream. The related CanvasCaptureMediaStreamTrack.ts file exists in later versions (e.g., modified between v20.0.10 and v20.7.0), but no evidence confirms captureStream availability or tests in 20.0.11 changelogs. Issue #241 for Canvas support remains open with milestone for getContext, and users commonly mock Canvas contexts due to missing rendering. For real Canvas functionality, use pluggable adapters available in later versions (PR #2069, Feb 2026) or external libraries like node-canvas.
Citations:
- 1: capricorn86/happy-dom@v20.0.7...v20.0.11
- 2: Working with canvas capricorn86/happy-dom#1056
- 3: Add support for Canvas capricorn86/happy-dom#241
- 4: feat: [#241] Add canvas adapter pattern for pluggable rendering support (vol.2) capricorn86/happy-dom#2069
- 5: Generate basic implementation for missing HTML elements capricorn86/happy-dom#1332
- 6: capricorn86/happy-dom@v20.0.10...v20.7.0
🌐 Web query:
Vitest vi.spyOn non-existent property undefined
💡 Result:
The error "Vitest vi.spyOn non-existent property undefined" occurs when vi.spyOn(object, 'property') is called but object.property is undefined (or not a function). Vitest's vi.spyOn requires the spied property to exist on the object and be a function (or getter/setter if specified). Common causes and solutions: 1. Object or service is undefined: Ensure the object/service is properly instantiated before spying. // Wrong - service might be undefined due to missing mock providers const spy = vi.spyOn(service, 'method'); // Fix - provide mocks for dependencies const module = await Test.createTestingModule({ providers: [ DeviceSecretService, { provide: SecretManagerServiceClient, useValue: { /* mocks */ } } ] }).compile; const service = module.get(DeviceSecretService); const spy = vi.spyOn(service, 'method'); 2. Property doesn't exist on object: Verify the property name and that it's a method. // Wrong vi.spyOn(obj, 'nonExistentMethod'); // obj.nonExistentMethod === undefined // Check first console.log(obj); // Verify structure 3. ESM module exports: Import as namespace and spy after import. import * as mod from './module'; const spy = vi.spyOn(mod, 'exportedMethod'); 4. Browser mode limitations: vi.spyOn on ESM exports throws "Cannot redefine property". Use vi.mock('./module', { spy: true }) instead. 5. Spy timing: Spies only track calls made after vi.spyOn. Move spy before the code that calls the method. From Vitest docs: vi.spyOn(object, key) spies on existing methods. If the property is undefined, it fails with this error (similar to Jest's "Cannot spyOn on a primitive value; undefined given").
Citations:
- 1: https://stackoverflow.com/questions/69050482/cannot-spyon-on-a-primitive-value-undefined-given
- 2: https://v4.vitest.dev/api/mock
- 3: https://www.vitest.dev/api/vi
- 4: https://vitest.dev/guide/mocking/modules.html
🏁 Script executed:
# Check if this test has any skip markers or special handling
cat -n src/extensions/core/load3d/RecordingManager.test.ts | grep -E "skip|only|xdescribe|xit" | head -20Repository: Comfy-Org/ComfyUI_frontend
Length of output: 52
🏁 Script executed:
# Check if there are any error handlers around the spyOn call
cat -n src/extensions/core/load3d/RecordingManager.test.ts | sed -n '80,110p'Repository: Comfy-Org/ComfyUI_frontend
Length of output: 1511
Define captureStream on the prototype before spying, since happy-dom 20.0.11 does not provide it.
vi.spyOn requires the property to already exist on the object; it cannot spy on undefined properties. Since happy-dom 20.0.11 lacks HTMLCanvasElement.prototype.captureStream, this setup will throw an error during beforeEach. Install the shim directly using Object.defineProperty or assignment before attempting to spy:
HTMLCanvasElement.prototype.captureStream = makeStream
or use vi.stubGlobal with a custom HTMLCanvasElement class that includes the method.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/extensions/core/load3d/RecordingManager.test.ts` around lines 90 - 97,
The test currently calls vi.spyOn on HTMLCanvasElement.prototype.captureStream
which doesn't exist in happy-dom 20.0.11; define the shim first (e.g., assign or
Object.defineProperty HTMLCanvasElement.prototype.captureStream = makeStream)
before calling vi.spyOn so the property exists, then spy or mock it as
needed—refer to HTMLCanvasElement.prototype, captureStream, makeStream, and
vi.spyOn in the RecordingManager.test setup to locate where to add the
definition.
Codecov Report✅ All modified and coverable lines are covered by tests. @@ Coverage Diff @@
## main #11733 +/- ##
===========================================
- Coverage 69.86% 52.94% -16.93%
===========================================
Files 1485 1376 -109
Lines 84037 70445 -13592
Branches 22883 19629 -3254
===========================================
- Hits 58715 37294 -21421
- Misses 24420 32603 +8183
+ Partials 902 548 -354
Flags with carried forward coverage won't be shown. Click here to find out more. 🚀 New features to boost your workflow:
|
dante01yoon
left a comment
There was a problem hiding this comment.
Reviewed the added Load3D unit suites against head 38fb092e5b82381d42763acc4496fd01bdb460c8.
No blocking findings from me. The tests exercise the production managers/service rather than only mocks, and I verified the canvas capture shim path locally in the same dependency set. CodeRabbit's remaining notes look like test-maintainability cleanup rather than merge blockers for this coverage PR.
Validation:
pnpm test:unit -- src/extensions/core/load3d/AnimationManager.test.ts src/extensions/core/load3d/CameraManager.test.ts src/extensions/core/load3d/RecordingManager.test.ts src/services/load3dService.test.ts- GitHub checks are green at this head.
Summary
Add unit tests for the four largest untested logic-heavy modules in the load3d domain (
AnimationManager,CameraManager,RecordingManager, and theload3dServicefaçade).Changes
handleViewerCloseapply-changes flow,handleViewportRefreshcamera-toggle dance).Review Focus
copyLoad3dState(lines 217-333) — which is entangled with 3-4 subsystems and is intentionally deferred to a follow-up PR.RecordingManager.test.ts:MediaRecorder,HTMLCanvasElement.prototype.captureStream, andgetContext('2d')are all stubbed because happy-dom doesn't provide them.THREE.TextureLoaderis also mocked because the constructor eagerly loads an SVG indicator.load3dService.test.ts: the service holds a module-levelviewerInstancesMap that can't be reached from outside. Tests track every node they create in aSetand drain viaremoveViewerinbeforeEach. Cleaner thanvi.resetModules()(which would re-import the service and break the singleton identity).AnimationManager.setAnimationTimeclamps toduration, butAnimationMixer.setTime(duration)withLoopRepeatwrapsaction.timeback to 0. The clamping is therefore only observable through the emitted progress event, not viaaction.timedirectly — the test asserts via the event.┆Issue is synchronized with this Notion page by Unito