All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
1.12.0 - 2025-12-03
-
Stabilization API - Wait for components to "stabilize" (stop rendering for a debounce period)
waitForStabilization(options?)- Wait for renders to stop fordebounceMsmillisecondstoEventuallyStabilize(options?)- Matcher version for cleaner assertions
const ProfiledList = withProfiler(VirtualList); const { rerender } = render(<ProfiledList items={items} scrollTop={0} />); // Start waiting for stabilization const promise = ProfiledList.waitForStabilization({ debounceMs: 50, // Wait 50ms after last render timeout: 2000 // Fail after 2s if still rendering }); // Simulate rapid scroll (many rerenders) for (let i = 1; i <= 10; i++) { rerender(<ProfiledList items={items} scrollTop={i * 100} />); } // Wait for list to stabilize const result = await promise; expect(result.renderCount).toBe(10); expect(result.lastPhase).toBe("update"); // Or use matcher await expect(ProfiledList).toEventuallyStabilize();
Key Use Cases:
- Virtualized lists - Wait for scroll to complete
- Debounced search - Wait for input to settle
- Animations - Wait for animation frames to complete
- Data loading cascades - Wait for dependent fetches to resolve
Options:
debounceMs?: number- Time without renders to consider stable (default: 50)timeout?: number- Max wait time before timeout error (default: 1000)
Result:
renderCount: number- Number of renders tracked during waitlastPhase?: PhaseType- Last render phase (undefined if no renders)
- Internal code deduplication - Reduced code duplication from <4% to >1%
- Added
jscpdcheck to pre-commit hook with 2% threshold
- Added
1.11.0 - 2025-12-02
-
Extended Matchers for Snapshot API - Flexible assertions for snapshot-based testing
toHaveRerendered()- Assert at least one rerender occurred after snapshottoHaveRerendered(n)- Assert exactlynrerenders after snapshot (with validation)toEventuallyRerender(options?)- Async matcher, wait for at least one rerendertoEventuallyRerenderTimes(n, options?)- Async matcher, wait for exact rerender count
// Sync matchers ProfiledComponent.snapshot(); fireEvent.click(button); expect(ProfiledComponent).toHaveRerendered(); // At least one expect(ProfiledComponent).toHaveRerendered(3); // Exactly 3 // Async matchers - wait for rerenders with timeout ProfiledComponent.snapshot(); triggerAsyncAction(); await expect(ProfiledComponent).toEventuallyRerender(); await expect(ProfiledComponent).toEventuallyRerenderTimes(2, { timeout: 2000 });
Key Features:
- Full
.notmodifier support for all matchers - Parameter validation (non-negative integers only)
- Early failure when count exceeded (async matchers don't wait for timeout)
- Detailed error messages with render history and tips
1.10.0 - 2025-12-01
-
Snapshot API - Create render baselines and measure deltas for optimization testing
snapshot()- Mark a baseline point for render countinggetRendersSinceSnapshot()- Get number of renders since last snapshottoHaveRerenderedOnce()- Assert exactly one rerender after snapshottoNotHaveRerendered()- Assert no rerenders after snapshot
const ProfiledCounter = withProfiler(Counter); render(<ProfiledCounter />); ProfiledCounter.snapshot(); // Create baseline fireEvent.click(screen.getByText('Increment')); expect(ProfiledCounter).toHaveRerenderedOnce(); // Verify single rerender
Key Use Cases:
- Testing single render per user action
- Testing
React.memoeffectiveness - Testing
useCallback/useMemostability - Performance budget testing for complex operations
-
Snapshot API property-based tests (
tests/property/snapshot.properties.tsx)- 6 invariants testing snapshot behavior under randomized conditions
- Validates snapshot/delta consistency, reset behavior, and timing invariants
-
Snapshot API stress tests (
tests/stress/snapshot.stress.tsx)- Tests for snapshot behavior under extreme load (10,000+ renders)
- Memory and performance validation
-
CI/CD workflow optimizations - Removed duplicate checks, saving ~25-30 seconds per run
release.yml: Removed explicittypecheck,test,buildsteps (already run byprepublishOnlyhook)ci.yml:test-examplesjob now downloads build artifacts instead of rebuilding
-
Bump size-limit to v12.0.0 - Bundle size monitoring dependency upgrade
- Breaking changes in v12: Node.js 18 dropped (project uses Node.js 24), chokidar replaced with native fs.watch
- No configuration changes required
1.9.0 - 2025-11-30
-
wrappersupport forprofileHookandcreateHookProfiler- Profile hooks that depend on React Context- New
ProfileHookOptionsinterface withrenderOptionsfor passing RTL render options (wrapper, container, etc.) - 4 function overloads covering all use cases:
profileHook(hook)- Hook without parametersprofileHook(hook, options)- Hook without parameters, with context wrapperprofileHook(hook, props)- Hook with parametersprofileHook(hook, props, options)- Hook with parameters and context wrapper
- Wrapper preserved during
rerender()calls automatically - Use case: Testing hooks that use
useContext(),useRouter(),useTheme(), etc.
// Before: ❌ Error - useRouter must be used within RouterProvider const { result } = profileHook(() => useRouter()); // After: ✅ Works with wrapper option const { result } = profileHook(() => useRouter(), { renderOptions: { wrapper: RouterProvider } });
- New
-
ProfileHookOptionstype export - Available from main package entry- Exported from
vitest-react-profilerfor TypeScript consumers - Consistent with
renderProfiledAPI pattern
- Exported from
-
isProfileHookOptionstype guard export - Runtime type checking utility- Distinguishes between
ProfileHookOptionsand hook props - Exported from
profileHookmodule for advanced use cases
- Distinguishes between
-
Comprehensive context hook examples (
examples/hooks/ContextHooks.test.tsx)- Theme context hook profiling
- Auth context with props and wrapper
- Router-like context (real-world scenario)
- Anti-pattern demonstration (missing provider error)
-
Integration tests for wrapper support (
tests/hooks/profileHook-context.test.tsx)- 308 lines covering all context scenarios
- Tests for wrapper preservation during rerenders
- Tests for nested context providers
- Tests for
createHookProfilerwith wrapper
- Simplified
createHookProfilerimplementation - Removed duplicate type guard - Optimized
profileHookargument parsing - Cleaner conditional flow - Examples vitest configs - Added
vitest-react-profileralias
1.8.0 - 2025-11-29
-
notToHaveRenderLoops()matcher - Detect suspicious render loop patterns before hitting circuit breaker- Catches render loops BEFORE
MAX_SAFE_RENDERS(10,000) kicks in - Configurable thresholds:
maxConsecutiveUpdates(default: 10),maxConsecutiveNested - Skip initialization renders:
ignoreInitialUpdatesoption - Detailed diagnostics with loop location, potential causes, and render history
- Use cases: Debug hanging tests, CI timeouts, useEffect→setState→useEffect cycles
- Example:
expect(Component).notToHaveRenderLoops({ maxConsecutiveUpdates: 5 })
- Catches render loops BEFORE
-
toMeetRenderCountBudget()matcher - Enforce render count constraints in tests- Check single constraint:
expect(Component).toMeetRenderCountBudget({ maxRenders: 3 }) - Check multiple constraints:
expect(Component).toMeetRenderCountBudget({ maxRenders: 5, maxMounts: 1, maxUpdates: 2 }) - Detailed error messages with emoji indicators (✅ pass / ❌ fail) and violation details
- Supports
.notmodifier for negative assertions - Automatically counts
nested-updatephases alongside regularupdatephases
- Check single constraint:
-
clearProfilerData()API - Selective cleanup for benchmarks and test scenarios- Clears render data while keeping components registered for reuse
- vs
clearRegistry(): Data-only reset (keeps registration) vs full cleanup (removes components) - Use cases:
afterEach()hooks, benchmarksetup(), test iteration cleanup
-
Benchmark stability improvements - 3x variance reduction (RME ±6-9% → ±2-5%)
- Added
gc()setup, increased warmup (100ms→300ms) and measurement time (1s→2s) - Fixed 11 benchmarks across cache-optimization, event-system, realistic-patterns files
- Added
-
Comprehensive test coverage for new matchers - Enterprise-grade testing with 84 new tests
-
Property-based tests (
tests/property/matchers.properties.tsx)- 17 invariant tests for
notToHaveRenderLoopsmatcher
- 17 invariant tests for
-
Stress tests (
tests/stress/matcher-render-loops.stress.tsx)- 21 tests for
notToHaveRenderLoopsunder extreme conditions
- 21 tests for
-
Performance benchmarks (
tests/benchmarks/)- 28 benchmarks for
notToHaveRenderLoopsmatcher - 18 benchmarks for
toMeetRenderCountBudgetmatcher
- 28 benchmarks for
-
Performance baselines established for regression detection in future versions
-
-
Default benchmark script now uses gc() -
npm run test:benchenables--expose-gcby default -
Benchmark architecture redesign - Fixed "Infinite render loop" errors from iteration accumulation
- vitest
bench()iterations accumulate renders (100 renders × 100 iterations = 10K triggers circuit breaker) - Added
clearProfilerData()+cleanup()to all benchmark loops (47 benchmarks across 4 files)
- vitest
-
Simplified MAX_SAFE_RENDERS constant - Removed dynamic calculation
-
Unified build constants - Replaced
import.meta.env.INTERNAL_TESTSwith__DEV__- Reduced from 3 constants (
__TEST__,__DEV__,INTERNAL_TESTS) to 1 (__DEV__) - Cleaner code:
if (__DEV__)instead ofif (import.meta.env.INTERNAL_TESTS === "true") - Same tree-shaking: esbuild string replacement removes dead code in production
- Reduced from 3 constants (
-
BENCHMARK_TESTSenvironment variable - No longer needed -
test:bench:stablenpm script - Merged into defaulttest:bench
- Benchmark stability and infinite loop errors - 11 benchmarks fixed, RME >6% → <6%
- Root causes: GC pauses, render accumulation, insufficient warmup, small sample size
- Fixed with
gc()setup,clearProfilerData()cleanup, increased warmup/measurement times
1.7.0 - 2025-11-25
-
Dependency Injection (DI) improvements - Simplified and more maintainable DI interfaces
- Extracted
ProfilerCacheInterfaceandProfilerEventsInterfacefor testability - Removed
getFrozenHistoryfrom cache interface (0% hit rate, dead code elimination) - Cleaner separation of concerns:
ProfilerDatahandles history freezing directly - Impact: Smaller interface surface area, better testability, easier mocking
- Updated test utilities:
createMockCache(),createSpyCache(),createNoOpCache() - All examples updated to demonstrate phase cache usage instead frozen history
- Extracted
-
Automatic registry cleanup - Zero-config memory management for stress tests
- Problem solved: Prevents memory accumulation in
ComponentRegistry.activeComponentsSet (~353 KB for 2,100 components) - Auto-setup enhancement: Added
afterAll()hook that callsregistry.reset()afterEach: Clears render data (keeps components for reuse in describe() blocks)afterAll: NEW - Completely resets registry (prevents accumulation across tests)
- Implementation: Added
reset()method toComponentRegistryclass- Clears component data (like
clearAll()) AND removes Set references (unlikeclearAll()) clearAll(): Between tests (keeps components for reuse) - called byafterEachreset(): After all tests in file (prevents accumulation) - called byafterAll
- Clears component data (like
- Public API:
clearRegistry()exported for edge cases (manual control if needed) - Impact: Fully automatic - no manual cleanup needed even in stress tests
- Files updated:
src/auto-setup.tsnow registers bothafterEachandafterAllhooks - Documentation: ARCHITECTURE.md sections updated with auto-setup behavior and memory analysis
- Problem solved: Prevents memory accumulation in
-
Built-in safety mechanisms - Infinite render loop (10K) & memory leak (100 listeners) detection
- New constants:
MAX_SAFE_RENDERS,MAX_LISTENERSinsrc/profiler/core/constants.ts
- New constants:
-
Parameter validation in async utilities - Validates count/timeout parameters, catches NaN/Infinity/negative/zero values (20 new tests)
-
Enhanced error messages - Component name context and
withProfiler()usage hints (3 new tests) -
Comprehensive benchmark suite - 25 benchmarks in
tests/benchmarks/formatting.bench.tsverifying O(n) scalability across different history sizes
-
ESLint configuration for tests - Centralized test-specific linting rules
- Added dedicated stress test override (
**/tests/**/*.stress.ts?(x)) with 20+ disabled rules for V8/GC profiling - Enhanced property test override with
cognitive-complexity: offfor generative testing logic - Enhanced unit test override with
expectTypeOf/expectTypesupport invitest/expect-expectrule - Impact: Eliminated 60-80 inline
eslint-disablecomments from stress test files - Cleaner, more maintainable test code with rules centrally configured
- Added dedicated stress test override (
-
formatRenderSummary()optimization - 3x faster with O(3n) → O(n) single-loop implementation -
Async matcher error messages - Improved timeout validation with clear error messages for invalid values (3 new tests)
-
ProfilerCache optimization - Removed
frozenHistorycache (internal optimization, no breaking changes)- Investigation revealed 0% hit rate in real test patterns (cache invalidates on every render)
- Simplified
ProfilerData.getHistory()to directObject.freeze([...renderHistory]) - Removed
getFrozenHistory()method fromProfilerCacheInterface(DI interface simplification) - Updated
CacheMetricstype:"phaseCache" | "closureCache"(removed"frozenHistory") - Performance improvement: Eliminated unnecessary caching overhead (0.96x-1.22x slowdown)
- Retained
phaseCache(95.7% hit rate) - still provides significant value - DI Impact: Cleaner interface contract, easier to implement custom cache strategies
-
Test helper type safety - Fixed unsafe type errors in mock implementations
- Removed invalid
getFrozenHistorymethod fromcreateNoOpCache()intests/helpers/mocks.ts - Added proper type annotation for
emitSpyparameter:info: Parameters<RenderListener>[0] - Removed unused
createSpyCacheimport fromtests/unit/profiler-data-di.test.ts
- Removed invalid
-
Mutation testing coverage gaps - 99.4% → 100.00% (all 6 survived mutants eliminated)
- Enhanced tests for error messages, string literals, and edge cases
-
Bundle size monitoring - Automated bundle size tracking and enforcement
- size-limit integration with strict size budgets
- ESM bundle: 40 KB limit (with tree-shaking for
{ withProfiler }) - CJS bundle: 45 KB limit (full package)
- ESM bundle: 40 KB limit (with tree-shaking for
- New workflow:
.github/workflows/size.yml- Runs on every pull request
- Reports ESM, CJS, and Types sizes (raw + gzipped)
- Warnings at >50KB, errors at >100KB
- Automatic PR comments with detailed size breakdown
- Script:
npm run sizefor local bundle size analysis
- size-limit integration with strict size budgets
-
Dead code detection - Automated unused code and dependency detection
- knip integration for comprehensive dead code analysis
- Detects unused exports, files, and dependencies
- Configured entry points: auto-setup, tests, benchmarks, property tests
- Ignores: examples, dist, coverage, reports, config files
- CI integration: Runs on every PR in lint job
- Script:
npm run lint:unusedfor local dead code checks - Configuration:
knip.jsonwith project-specific rules
- knip integration for comprehensive dead code analysis
-
Package validation - Automated package.json and exports validation
- publint integration for npm package correctness
- Validates package.json exports/imports
- Checks dual package hazards (ESM/CJS)
- Verifies TypeScript definitions
- CI integration: Runs on every PR in lint job
- Script:
npm run lint:packagefor local validation
- publint integration for npm package correctness
-
Pre-publish checks - Automated quality gates before npm publish
- Updated
prepublishOnlyscript with comprehensive checks:npm run lint:package- Package configuration validationnpm run lint:unused- Dead code detectionnpm run typecheck- TypeScript validationnpm run test:coverage- Full test suite with coveragenpm run build- Production build
- Prevents publishing packages with:
- Invalid package.json configuration
- Dead code or unused dependencies
- TypeScript errors
- Failing tests or low coverage
- Build failures
- Updated
-
Code quality metrics
- Coverage: 100% (maintained across all metrics)
- Mutation Score: 100.00%
- Bundle Size: Tracked and enforced (ESM + CJS < 85 KB combined)
- Dead Code: Zero unused exports or dependencies
-
Comprehensive JSDoc for property-based tests - All 12 property test files documented with detailed invariants
- Added
@fileoverviewblocks with complete test descriptions - 72 invariants documented across all property test files:
profiler-data.properties.tsx- ProfilerData core invariants (6 invariants)profiler-storage.properties.tsx- WeakMap isolation patterns (6 invariants)cache-metrics.properties.tsx- Cache performance metrics (6 invariants)withProfiler.properties.tsx- HOC mathematical properties (7 invariants)stress.properties.tsx- Extreme load testing (6 invariants)renderProfiled.properties.tsx- Component testing utility (6 invariants)profileHook.properties.tsx- Custom hook profiling (6 invariants)matchers.properties.tsx- Vitest matcher contracts (6 invariants)events.properties.tsx- Event system guarantees (6 invariants)api-events.properties.tsx- API event methods (6 invariants)async.properties.tsx- Async operation safety (6 invariants)formatting.properties.tsx- Output formatting rules (6 invariants)
- Each invariant includes:
- Detailed description of what is tested
- "Why important" rationale
- Testing strategy (number of runs, generators used)
- Technical implementation notes
- Real-world use case examples
- Translated to English for international audience
- Impact: Comprehensive test documentation, easier onboarding for contributors
- Added
-
ESLint security plugins integration - Enhanced code security analysis
- Added
eslint-plugin-security(20+ security rules)- Detects unsafe RegExp patterns, eval usage, command injection risks
- Configured to ignore false positives (e.g.,
security/detect-object-injection)
- Added
eslint-plugin-regexp(200+ RegExp best practices)- Validates regular expression patterns
- Detects performance issues and edge cases
- Centralized configuration in
eslint.config.mjs - Impact: Proactive security vulnerability detection during development
- Added
-
Safety limits - Internal constants with 100x margin for legitimate use cases (10K renders, 100 listeners)
-
ESLint security rules - Automated security vulnerability detection
- Command injection detection
- Unsafe RegExp pattern detection
- Eval usage detection
- Object injection pattern analysis
- 80+ false positives resolved by targeted rule configuration
1.6.0 - 2025-11-11
For 99% of users (using withProfiler()): No changes needed!
1. Removed interval parameter from async operations
The interval parameter has been removed from all async utilities and matchers as it's no longer needed with the new event-based architecture.
// ❌ Before (v1.5.0)
await waitForRenders(component, 3, { timeout: 2000, interval: 10 });
await expect(component).toEventuallyRenderTimes(3, {
timeout: 1000,
interval: 50,
});
// ✅ After (v1.6.0)
await waitForRenders(component, 3, { timeout: 2000 });
await expect(component).toEventuallyRenderTimes(3, { timeout: 1000 });Migration: Remove interval property from options objects (TypeScript will show errors if used).
2. Custom wrappers require onRender() method
All async operations now use event-based approach and require onRender() method.
// ✅ Using withProfiler() - no changes needed
const ProfiledComponent = withProfiler(MyComponent);
await waitForRenders(ProfiledComponent, 3); // Works!
// ⚠️ Custom wrappers - must implement onRender()
class CustomWrapper {
onRender(callback: (info: RenderEventInfo) => void): () => void {
// Subscribe to renders
return () => {}; // Unsubscribe function
}
}Impact: <1% of users (only those with custom wrappers not using withProfiler()).
-
New API methods - Subscribe to renders and wait for async updates
onRender(callback)- Subscribe to component renders with real-time notificationswaitForNextRender(options?)- Promise-based helper to wait for next renderRenderEventInfointerface - Structured render event information
-
Comprehensive examples - Real-world usage patterns
- 21 event-based examples in
examples/async/ - Examples covering: subscriptions, async waits, cleanup, complex conditions, performance
- 21 event-based examples in
-
Async matchers and utilities - Event-based implementation (10x faster)
- Matchers:
toEventuallyRenderTimes,toEventuallyRenderAtLeast,toEventuallyReachPhase - Utilities:
waitForRenders(),waitForMinimumRenders(),waitForPhase() - Performance improvement: ~50ms → <5ms (typical operation)
- Zero CPU overhead (no polling loops)
- Race condition protection and immediate resolution when condition already met
- Matchers:
-
Core methods performance - Improved baseline and stability
getRenderCount(): 91-105% of baseline (was 17-40%)getRenderHistory(): Optimized with lazy history evaluation- Relative Mean Error (RME): ±10% (was ±15-30% - 20-50% improvement)
- Better stability across all operations
-
Race conditions in async operations - Proper synchronization in event-based code
- Promise creation before action triggering prevents missed renders
- Immediate condition check after subscription prevents race conditions
- Timeout cleanup prevents memory leaks
-
Type safety improvements - Better TypeScript integration
- Removed unused generic parameters
- Enhanced type guards for ProfiledComponent validation
- Stricter null safety in event listeners
-
intervalparameter - Removed fromWaitOptionsinterface- No longer needed with event-based architecture
- TypeScript will show errors if used (compile-time safety)
-
Polling implementation - Replaced with event-based approach
- All async matchers and utilities now use events
- Dependency on
@testing-library/reactwaitFor removed
1.5.0 - 2025-11-06
-
RenderInfo → PhaseType simplification
RenderInfointerface removed (contained unusedtimestampfield)- Replaced with simple
PhaseTypeunion:"mount" | "update" | "nested-update" - Affected methods now return phase strings directly instead of objects:
getRenderHistory()- returnsPhaseType[]instead ofRenderInfo[]getLastRender()- returnsPhaseType | undefinedinstead ofRenderInfo | undefinedgetRenderAt(index)- returnsPhaseType | undefinedinstead ofRenderInfo | undefinedgetRendersByPhase(phase)- returnsreadonly PhaseType[]instead ofreadonly RenderInfo[]
Migration: Replace
.map(r => r.phase)with direct array access, replacerender.phasewith direct string comparison.
- Complete code reorganization - Improved modularity and maintainability
- Impact: 83 files changed, ~5,900 additions, ~5,477 deletions
- Reorganized
src/matchers/into logical structure:async/- Async matchers (toEventuallyRenderTimes,toEventuallyRenderAtLeast,toEventuallyReachPhase)sync/- Sync matchers (toHaveRendered,toHaveRenderedTimes, etc.)type-guards.ts- Type validation utilitiestypes.ts- Shared matcher typesindex.ts- Centralized exports
- Reorganized
src/profiler/for better separation of concerns:api/ProfilerAPI.ts- Public profiling APIcomponents/- React component profiling (withProfiler,ProfiledComponent, callbacks)core/- Core data structures (ProfilerData,ProfilerCache,ProfilerStorage)
- Removed monolithic files:
src/matchers.ts,src/withProfiler.tsx - Clear separation between public API and internal implementation
- Each module has single, well-defined responsibility
-
Enhanced test coverage for async matchers - Comprehensive
.notmodifier support- Added 3 tests for negated async assertions:
toEventuallyRenderTimes- Verify failure when exact count IS reachedtoEventuallyRenderAtLeast- Verify failure when minimum IS reachedtoEventuallyReachPhase- Verify failure when phase IS reached
- Impact: 100% code coverage (was 97.84% for async matchers)
- Result: All 378 tests passing, 242/242 branches covered
- Added 3 tests for negated async assertions:
-
Enhanced CI/CD infrastructure - Production-grade automation and code quality
-
SonarCloud integration improvements
- Type checking step before analysis
- SonarCloud package caching for faster builds
- Official
sonarcloud-github-actionfor reliability - Centralized configuration in
sonar-project.properties - NPM scripts:
sonarandsonar:localfor local analysis
-
Project guidelines for AI (
CLAUDE.md)- Code conventions and TypeScript standards
- Testing requirements (unit, integration, property-based)
- Performance optimization guidelines
- Security best practices
- Documentation standards
- Serves as context for Claude AI workflows
-
-
New test utilities - Better test organization and reusability
- Unit tests for new architecture:
tests/unit/profiled-component.test.tsx- ProfiledComponent behaviortests/unit/profiler-api.test.ts- Public API contractstests/unit/profiler-cache.test.ts- Cache invalidation logictests/unit/profiler-data.test.ts- Data structure integritytests/unit/profiler-storage.test.ts- Component storage isolation
tests/benchmarks/realistic-patterns.bench.tsx- Real-world usage benchmarks
- Unit tests for new architecture:
-
CI/CD infrastructure improvements
- SonarCloud integration with type checking and caching
- Official
sonarcloud-github-actionfor reliability - NPM scripts:
sonarandsonar:localfor local analysis - Codecov bundle analyzer integration
-
Simplified render history API - More intuitive and efficient
- Public API methods (ProfiledComponent interface):
getRenderHistory()returnsPhaseType[]instead ofRenderInfo[]getLastRender()returnsPhaseType | undefinedinstead ofRenderInfo | undefinedgetRenderAt(index)returnsPhaseType | undefinedinstead ofRenderInfo | undefinedgetRendersByPhase(phase)returnsreadonly PhaseType[]instead ofreadonly RenderInfo[]
- Internal utilities (not documented, no breaking change):
formatRenderHistory()andformatRenderSummary()updated to work withPhaseType[]- Used only for matcher error messages
- Direct string comparisons replace object property access
- Better TypeScript type narrowing and inference
- Public API methods (ProfiledComponent interface):
-
Optimized
withProfilerinitialization - Removed redundant storage check- Eliminated unnecessary
globalStorage.has()check beforegetOrCreate() createOnRenderCallbackalready callsgetOrCreate()internally- Impact: Cleaner code, identical functionality
- Simplified from 9 steps to 8 steps in component wrapping flow
- Eliminated unnecessary
-
CI/CD workflow improvements - Faster, more reliable automation
-
All workflows enhanced with:
- Concurrency control - Cancel duplicate runs to save CI minutes
- Full git history (
fetch-depth: 0) - Accurate diff comparisons - Enhanced permissions - Granular access control
- Latest actions versions - Security and performance improvements
-
Coverage workflow (
.github/workflows/coverage.yml)- Threshold checking step - Fail CI if coverage < 90%
- Step summary in Actions tab - Quick visibility
- Improved PR comments with badges and links
- Multiple file uploads for comprehensive reports
-
SonarCloud workflow (
.github/workflows/sonarcloud.yml)- Type checking before analysis - Catch errors early
- Official action for better reliability
- Simplified configuration
-
Claude workflows (
.github/workflows/claude*.yml)- Latest model (Sonnet 4.5) - Better code understanding
- Sticky comments - Single updating comment vs spam
- Progress tracking - Real-time visibility
- Enhanced tool access - More capabilities
-
-
Test organization - Better structure and maintainability
- All property-based tests updated for
PhaseType - Stress tests refactored to validate phase strings:
- "timestamps remain monotonically increasing" → "phase types remain valid throughout history"
- "all history entries have valid structure" → validates
PhaseTypestrings
- Removed 3 unused helper functions from
tests/property/helpers.tsx:createComponentWithPhases()- Not used in any testscreateNestedUpdateComponent()- Not used in any testswaitForRenderCount()- Replaced by async utilities
- File reduced from ~163 lines to 86 lines (-47%)
- Cleaner, more maintainable test helpers
- All property-based tests updated for
-
Bundle configuration for tree-shaking - Matchers now correctly included in production builds
- Updated
sideEffectsin package.json:./src/matchers.ts→./src/matchers/index.ts - Resolved esbuild warning: "Ignoring import because file was marked as having no side effects"
- Impact: Bundle size increased from ~6.5KB to ~12KB (matchers properly included)
- Fixed bundle:analyze command syntax for Codecov bundle analyzer
- Updated
-
Frozen array edge case - Empty arrays now consistently frozen
- Added
EMPTY_FROZEN_ARRAYconstant inProfilerAPI.ts - Methods return frozen empty array when no profiler data exists:
getRenderHistory()- was returning unfrozen[]getRendersByPhase()- was returning unfrozen[]
- Impact: API consistency - all arrays frozen regardless of component state
- Fixed failing test: "should return empty array when no profiler data exists"
- Added
-
Property-based stress tests - Updated for PhaseType API
- Fixed "timestamps remain monotonically increasing" test
- Now validates phase types instead of non-existent timestamps
- Checks all entries are valid
PhaseTypevalues
- Fixed "all history entries have valid structure" test
- Updated from checking
Number.isFinite(entry)to validating phase strings - Removed unnecessary falsy checks (strings are always truthy)
- Updated from checking
- All 135 property-based tests now passing ✅
- Fixed "timestamps remain monotonically increasing" test
-
Test suite status - Perfect test coverage achieved
- 379 unit/integration tests ✅ (was 367)
- 135 property-based tests ✅
- 100% code coverage - All lines, functions, branches, statements
- Comprehensive benchmark suite
- Property tests validate PhaseType invariants at scale (1,000-10,000 renders)
-
Code quality gates - Multiple layers of verification
- Stryker Mutation Testing - 96.31% score (improved from 93.40%)
- 313 mutants killed
- 12 mutants survived (all non-critical: formatting, timeout boundaries)
- 3 mutants timeout (edge cases in async polling)
- SonarCloud - Code quality and security analysis
- Codecov - Coverage tracking and bundle analysis
- ESLint - TypeScript strict mode
- Prettier - Consistent formatting
- Claude AI - Automated code review on PRs
- Stryker Mutation Testing - 96.31% score (improved from 93.40%)
-
Developer experience - Improved workflow efficiency
- Bundle analyzer for size monitoring
- NPM scripts for local quality checks
- Type-safe API with excellent IDE support
- Consistent frozen arrays for immutability guarantees
1.4.0 - 2025-11-02
vitest-react-profiler now focuses exclusively on render counting and phase tracking, removing all time-based performance measurement features.
Why this change? Time-based metrics in test environments don't reflect real-world performance and provide unreliable, misleading data. Instead of offering useful insights, they could lead to incorrect conclusions about component performance. The library now focuses on what actually matters: detecting unnecessary re-renders through deterministic render counting and phase tracking.
Removed from RenderInfo interface:
actualDuration- Time spent rendering the componentbaseDuration- Estimated time without memoizationstartTime- When React began renderingcommitTime- When React committed the render
Removed matchers:
toHaveRenderedWithin(ms)- Check last render durationtoHaveAverageRenderTime(ms)- Check average render time across all renders
Removed methods:
getAverageRenderTime()- Get average render duration
Removed utilities:
formatPerformanceMetrics()- Format performance metrics for display
-
Optimized
getRendersByPhase()performance - Added phase-specific caching- Impact: O(n) → O(1) for repeated calls with same phase
- Cache invalidated automatically on new renders
- Separate cache entries for "mount", "update", and "nested-update" phases
- Frozen arrays returned for immutability
-
Optimized
hasMounted()performance - Added boolean result caching- Impact: O(n) → O(1) for repeated calls
- Cache invalidated automatically on new renders
- Eliminates redundant array traversal
-
Removed unused
historyVersionfield - SimplifiedProfilerDatainterface- Field was declared but never used in the codebase
- Reduced memory footprint per component
-
Test execution memory exhaustion - Tests no longer consume all available memory
- Impact: Prevents IDE crashes during test runs
- Limited concurrent test threads to 4 (via
poolOptions.maxThreads) - Excluded stress tests and property-based tests from default test run
- Separate npm scripts:
test:stressandtest:propertiesfor targeted execution
-
Custom matchers not registered globally - Fixed 17 failing unit tests
- Added
import "../src/matchers"totests/setup.ts - Matchers now available in all test files without explicit import
- Resolved "Invalid Chai property" errors
- Added
-
Comprehensive documentation update - README.md updated throughout
- Removed all references to time-based matchers and methods
- Updated error message examples to show timestamp-based format
- Replaced "Performance Budget Testing" with "Render Count Monitoring"
- Updated "CI Performance Monitoring" to "CI Render Count Monitoring"
- Simplified API reference section
- Updated all code examples to focus on render counting
-
Examples cleanup - Removed 1,225 lines of time-based testing examples
- Updated
examples/basic/Basic.test.tsx - Updated
examples/hooks/*(4 files) - Updated
examples/memoization/*(2 files) - Updated
examples/performance/PerformanceTest.test.tsx - All examples now focus on render counting and phase tracking
- Updated
-
Enhanced caching tests - Comprehensive test coverage for new cache optimizations
- 6 new property-based tests in
tests/property/cache.properties.tsx getRendersByPhase()caching behavior testshasMounted()caching behavior tests- Cache invalidation tests
- Cache isolation tests between components
- 6 new property-based tests in
-
Property descriptor tests - Immutability verification
- Tests for non-writable
displayNameproperty - Tests for non-writable and non-enumerable
OriginalComponentproperty - Verification of Object.defineProperty descriptors
- Tests for non-writable
-
Edge-case test coverage
toHaveOnlyUpdated()edge case: component with only updates (no mount)- Empty profiler data edge cases for all methods
- All 237 tests passing with optimized memory usage
- Codebase reduced by ~2,051 lines (-73% in affected files)
1.3.2 - 2025-11-01
- Enhanced TypeScript type safety
- Replaced
ComponentType<any>withComponentType<Record<string, unknown>> - Created type-safe helper functions for WeakMap access:
getProfilerData()- get component profiler datasetProfilerData()- set component profiler datahasProfilerData()- check if component has profiler data
- Fixed TypeScript error with readonly
displayNameproperty - Removed one eslint-disable comment from top-level code
anyusage now limited to 3 internal helper functions (documented)- Better IDE autocompletion and type inference
- Reduced risk of type-related bugs
- Replaced
1.3.1 - 2025-11-01
- Critical memory leak in ComponentRegistry - Components were never removed from registry, causing memory accumulation in large test suites
- Impact: 99% memory reduction (from ~14 MB to ~0.3 MB overhead in 10,000 test scenarios)
- Introduced
WeakSetfor automatic garbage collection of unused components - Added
activeComponentsSet for managing active component lifecycle - Added
unregister()method for explicit component cleanup clearAll()now properly clears render data while preserving reusable components fromdescribe()blocks- 100% backward compatible - no breaking changes to public API
- 7 comprehensive unit tests for
ComponentRegistry(tests/unit/registry.test.tsx)- Verifies render data cleanup between tests
- Confirms no memory accumulation across 1,000+ test iterations
- Validates component reusability from
describe()blocks - Tests render history memory deallocation
- CI/CD reliability improvements
- Fixed property-based test timeouts by limiting worker pool to 2 threads
- Removed duplicate "Check formatting" task from GitHub Actions workflow
- Improved test stability in CI environment
- All 252 tests passing with enhanced memory efficiency
1.3.0 - 2025-10-28
-
Async testing support - Test components with asynchronous state updates
waitForRenders(component, count)- Wait for exact render countwaitForMinimumRenders(component, minCount)- Wait for at least N renderswaitForPhase(component, phase)- Wait for specific render phasetoEventuallyRenderTimes(count)- Async matcher for exact render counttoEventuallyRenderAtLeast(minCount)- Async matcher for minimum renderstoEventuallyReachPhase(phase)- Async matcher for render phase- Configurable timeout and polling intervals for all async utilities
-
Simplified API - More concise testing experience
renderProfiled(Component, props, options)- CombineswithProfiler()+render()in one call- Enhanced
rerender()function with automatic prop merging - Support for React Testing Library options (wrapper, container, etc.)
-
Enhanced error messages - Detailed, actionable failure information
- Visual render history table showing phase, timing, and duration
- Slow render detection with aggregate statistics
- Unexpected mount detection with detailed breakdown
- Contextual tips for debugging render issues
-
Performance benchmarking - Track library performance over time
- Benchmarks for
getRenderHistory()operations - Memory optimization benchmarks
- Stable benchmark configuration for consistent CI results
- Benchmarks for
- Improved documentation with:
- Complete async testing guide with real-world examples
renderProfiled()usage patterns and best practices- Enhanced error message examples showing before/after comparison
- Integration testing patterns for complex scenarios
- Flaky performance tests in CI environment with tolerance adjustments
- SonarCloud security hotspots and quality gate issues
- Mutation testing with Stryker - Advanced test quality analysis (93.40% mutation score)
- Automated detection of weak test cases and untested edge cases
- 245 comprehensive tests covering critical logic paths
- Incremental mode for fast CI/CD integration
- SonarCloud integration - Automated code quality and security analysis
- Codecov integration - Visual coverage tracking and reporting
- Quality gate badges for project health monitoring
- Improved CI/CD pipeline reliability
1.2.0 - 2025-10-27
- Hook profiling - Profile React hooks to detect extra renders caused by improper state management
profileHook()- Main hook profiling function that wraps hooks for render trackingcreateHookProfiler()- Simplified API with built-in assertion helpers- Type overloads for hooks with and without parameters
- Full integration with existing matchers (toHaveRenderedTimes, etc.)
- Automatic cleanup via existing registry system
- Comprehensive documentation with real-world anti-pattern examples
- 22 tests covering basic usage, edge cases, integration, and real-world scenarios
- Added "Hook Profiling" section to README.md with:
- Basic usage examples
- Common anti-pattern detection (useEffect state sync, data fetching)
- Real-world fixes and best practices
- Batch testing patterns
- React.StrictMode behavior notes
1.1.0 - 2025-10-26
This version removes the need for manual cleanup code in tests by introducing an internal registry system that automatically clears component data between tests.
- Component registry system (
src/registry.ts) - Internal tracking of all profiled components - Auto-setup module (
src/auto-setup.ts) - Automatically registersafterEachcleanup hook on import - Automatic cleanup between tests - No manual intervention needed
clearCounters()method removed fromProfiledComponentpublic API. Cleanup now happens automatically via internal registry
- All internal tests and examples updated to remove manual cleanup code
- Eliminates 3-5 lines of boilerplate per test file
sideEffectsin package.json now includesauto-setup.tsfor proper tree-shaking
1.0.0 - 2025-10-26
- Initial release of vitest-react-profiler
- Core
withProfiler()function to wrap React components with profiling capabilities - True automatic cleanup system - Zero boilerplate! Components auto-clear between tests
- Internal component registry (
src/registry.ts) for tracking profiled components - Auto-setup module (
src/auto-setup.ts) that registersafterEachcleanup hook on import - No manual
afterEach()orclearCounters()calls needed in tests
- Internal component registry (
- Custom Vitest matchers for performance testing:
toHaveRendered()- Assert component has renderedtoHaveRenderedTimes(count)- Assert exact number of renderstoHaveRenderedWithin(ms)- Assert render duration within budgettoHaveMountedOnce()- Assert component mounted exactly oncetoHaveNeverMounted()- Assert component never mountedtoHaveOnlyUpdated()- Assert component only updated (no mounts)toHaveAverageRenderTime(ms)- Assert average render time
- Profiled component API with methods:
getRenderCount()- Get total number of rendersgetRenderHistory()- Get complete render historygetLastRender()- Get last render informationgetRenderAt(index)- Get render at specific indexgetRendersByPhase(phase)- Filter renders by phasegetAverageRenderTime()- Calculate average render timehasMounted()- Check if component has mountedOriginalComponent- Reference to original unwrapped component
- Full TypeScript support with complete type definitions
- Comprehensive documentation and examples
- Examples directory with:
- Basic usage examples
- Memoization testing examples
- Performance testing examples
- Zero boilerplate - No manual cleanup code needed (no
afterEachhooks!) - Zero configuration setup - works out of the box with Vitest
- Precise render tracking using React Profiler API
- Phase detection (mount, update, nested update)
- Statistical analysis (average, min, max render times)
- True automatic test isolation with component registry system
- Tiny bundle size (< 10KB minified)
- Support for React 16.8+ (Hooks)
- Support for Vitest 1.0+
- Compatible with @testing-library/react
- Comprehensive test coverage (93 tests passing)
- ESLint configuration with strict TypeScript rules
- Prettier for code formatting
- Husky + lint-staged for pre-commit hooks
- Commitlint for conventional commits
- tsup for optimized build output (CJS + ESM)
- GitHub Actions CI/CD pipeline ready