From 743ba6c6b2b010f597e4053775bcc68d4b7a79d9 Mon Sep 17 00:00:00 2001 From: Justin Gordon Date: Thu, 6 Nov 2025 18:44:09 -1000 Subject: [PATCH 1/2] test: Add comprehensive tests for JavaScript debug logging MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add comprehensive test coverage for the debug logging features implemented in PR #1934 (Part 2 of 3 in the improvement series). The tests verify: - logComponentRegistration option for opt-in component registration logging - debugMode option for detailed debug output including component sizes - Performance metrics tracking using performance.now() with Date.now() fallback - Non-intrusive behavior with zero production impact - Option validation and reset functionality All tests pass and ensure the feature works as expected without affecting normal component registration functionality. Related to #1834 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../react-on-rails/tests/debugLogging.test.js | 248 ++++++++++++++++++ 1 file changed, 248 insertions(+) create mode 100644 packages/react-on-rails/tests/debugLogging.test.js diff --git a/packages/react-on-rails/tests/debugLogging.test.js b/packages/react-on-rails/tests/debugLogging.test.js new file mode 100644 index 0000000000..d3770cb65e --- /dev/null +++ b/packages/react-on-rails/tests/debugLogging.test.js @@ -0,0 +1,248 @@ +/* eslint-disable react/jsx-filename-extension */ + +import * as React from 'react'; +import ComponentRegistry from '../src/ComponentRegistry.ts'; +import ReactOnRails from '../src/ReactOnRails.client.ts'; + +describe('Debug Logging', () => { + let consoleLogSpy; + + beforeEach(() => { + // Clear registries before each test + ComponentRegistry.clear(); + + // Reset options to defaults + ReactOnRails.resetOptions(); + + // Spy on console.log + consoleLogSpy = jest.spyOn(console, 'log').mockImplementation(() => {}); + }); + + afterEach(() => { + // Restore console.log + consoleLogSpy.mockRestore(); + }); + + describe('logComponentRegistration option', () => { + it('does not log when logComponentRegistration is false (default)', () => { + const TestComponent = () =>
Test
; + + ReactOnRails.register({ TestComponent }); + + expect(consoleLogSpy).not.toHaveBeenCalled(); + }); + + it('logs component registration when logComponentRegistration is true', () => { + ReactOnRails.setOptions({ logComponentRegistration: true }); + + const TestComponent = () =>
Test
; + ReactOnRails.register({ TestComponent }); + + expect(consoleLogSpy).toHaveBeenCalledWith('[ReactOnRails] Component registration logging enabled'); + expect(consoleLogSpy).toHaveBeenCalledWith('[ReactOnRails] Registering 1 component(s): TestComponent'); + expect(consoleLogSpy).toHaveBeenCalledWith( + expect.stringMatching(/\[ReactOnRails\] Component registration completed in \d+\.\d+ms/), + ); + }); + + it('logs multiple components registration', () => { + ReactOnRails.setOptions({ logComponentRegistration: true }); + + const Component1 = () =>
One
; + const Component2 = () =>
Two
; + const Component3 = () =>
Three
; + + ReactOnRails.register({ Component1, Component2, Component3 }); + + expect(consoleLogSpy).toHaveBeenCalledWith( + '[ReactOnRails] Registering 3 component(s): Component1, Component2, Component3', + ); + expect(consoleLogSpy).toHaveBeenCalledWith( + expect.stringMatching(/\[ReactOnRails\] Component registration completed in \d+\.\d+ms/), + ); + }); + + it('measures registration timing using performance.now() when available', () => { + ReactOnRails.setOptions({ logComponentRegistration: true }); + + const TestComponent = () =>
Test
; + ReactOnRails.register({ TestComponent }); + + // Verify timing was logged in milliseconds with 2 decimal places + expect(consoleLogSpy).toHaveBeenCalledWith( + expect.stringMatching(/\[ReactOnRails\] Component registration completed in \d+\.\d{2}ms/), + ); + }); + }); + + describe('debugMode option', () => { + it('does not log when debugMode is false (default)', () => { + const TestComponent = () =>
Test
; + + ReactOnRails.register({ TestComponent }); + + expect(consoleLogSpy).not.toHaveBeenCalled(); + }); + + it('logs when debugMode is enabled', () => { + ReactOnRails.setOptions({ debugMode: true }); + + expect(consoleLogSpy).toHaveBeenCalledWith('[ReactOnRails] Debug mode enabled'); + }); + + it('logs component registration details when debugMode is true', () => { + ReactOnRails.setOptions({ debugMode: true }); + + const TestComponent = () =>
Test
; + ReactOnRails.register({ TestComponent }); + + expect(consoleLogSpy).toHaveBeenCalledWith('[ReactOnRails] Registering 1 component(s): TestComponent'); + expect(consoleLogSpy).toHaveBeenCalledWith( + expect.stringMatching(/\[ReactOnRails\] Component registration completed in \d+\.\d+ms/), + ); + expect(consoleLogSpy).toHaveBeenCalledWith( + expect.stringMatching(/\[ReactOnRails\] ✅ Registered: TestComponent \(~\d+\.\d+ chars\)/), + ); + }); + + it('logs individual component sizes in debug mode', () => { + ReactOnRails.setOptions({ debugMode: true }); + + const SmallComponent = () =>
S
; + const LargerComponent = () => ( +
+

This is a larger component with more content

+

And another paragraph to make it bigger

+
+ ); + + ReactOnRails.register({ SmallComponent, LargerComponent }); + + // Check that individual component registrations are logged with size info + expect(consoleLogSpy).toHaveBeenCalledWith( + expect.stringMatching(/\[ReactOnRails\] ✅ Registered: SmallComponent \(~\d+\.\d+ chars\)/), + ); + expect(consoleLogSpy).toHaveBeenCalledWith( + expect.stringMatching(/\[ReactOnRails\] ✅ Registered: LargerComponent \(~\d+\.\d+ chars\)/), + ); + }); + + it('logs all registration info when both debugMode and logComponentRegistration are enabled', () => { + ReactOnRails.setOptions({ + debugMode: true, + logComponentRegistration: true, + }); + + const TestComponent = () =>
Test
; + ReactOnRails.register({ TestComponent }); + + // Should log both general info and detailed component info + expect(consoleLogSpy).toHaveBeenCalledWith('[ReactOnRails] Debug mode enabled'); + expect(consoleLogSpy).toHaveBeenCalledWith('[ReactOnRails] Component registration logging enabled'); + expect(consoleLogSpy).toHaveBeenCalledWith('[ReactOnRails] Registering 1 component(s): TestComponent'); + expect(consoleLogSpy).toHaveBeenCalledWith( + expect.stringMatching(/\[ReactOnRails\] Component registration completed in \d+\.\d+ms/), + ); + expect(consoleLogSpy).toHaveBeenCalledWith( + expect.stringMatching(/\[ReactOnRails\] ✅ Registered: TestComponent \(~\d+\.\d+ chars\)/), + ); + }); + }); + + describe('performance fallback', () => { + it('falls back to Date.now() when performance is not available', () => { + // Save original performance object + const originalPerformance = global.performance; + + // Remove performance temporarily + delete global.performance; + + ReactOnRails.setOptions({ logComponentRegistration: true }); + + const TestComponent = () =>
Test
; + ReactOnRails.register({ TestComponent }); + + // Should still log timing information + expect(consoleLogSpy).toHaveBeenCalledWith( + expect.stringMatching(/\[ReactOnRails\] Component registration completed in \d+\.\d+ms/), + ); + + // Restore performance + global.performance = originalPerformance; + }); + }); + + describe('option validation', () => { + it('accepts valid debugMode option', () => { + expect(() => ReactOnRails.setOptions({ debugMode: true })).not.toThrow(); + expect(() => ReactOnRails.setOptions({ debugMode: false })).not.toThrow(); + }); + + it('accepts valid logComponentRegistration option', () => { + expect(() => ReactOnRails.setOptions({ logComponentRegistration: true })).not.toThrow(); + expect(() => ReactOnRails.setOptions({ logComponentRegistration: false })).not.toThrow(); + }); + + it('can retrieve options via option() method', () => { + ReactOnRails.setOptions({ debugMode: true, logComponentRegistration: true }); + + expect(ReactOnRails.option('debugMode')).toBe(true); + expect(ReactOnRails.option('logComponentRegistration')).toBe(true); + }); + + it('resetOptions() resets debug options to defaults', () => { + ReactOnRails.setOptions({ debugMode: true, logComponentRegistration: true }); + ReactOnRails.resetOptions(); + + expect(ReactOnRails.option('debugMode')).toBe(false); + expect(ReactOnRails.option('logComponentRegistration')).toBe(false); + }); + }); + + describe('non-intrusive logging', () => { + it('does not affect component registration functionality', () => { + ReactOnRails.setOptions({ debugMode: true, logComponentRegistration: true }); + + const TestComponent = () =>
Test
; + ReactOnRails.register({ TestComponent }); + + // Component should still be properly registered + const registered = ReactOnRails.getComponent('TestComponent'); + expect(registered.name).toBe('TestComponent'); + expect(registered.component).toBe(TestComponent); + }); + + it('does not affect multiple component registration', () => { + ReactOnRails.setOptions({ debugMode: true }); + + const Comp1 = () =>
1
; + const Comp2 = () =>
2
; + const Comp3 = () =>
3
; + + ReactOnRails.register({ Comp1, Comp2, Comp3 }); + + // All components should be registered correctly + expect(ReactOnRails.registeredComponents().size).toBe(3); + expect(ReactOnRails.getComponent('Comp1').component).toBe(Comp1); + expect(ReactOnRails.getComponent('Comp2').component).toBe(Comp2); + expect(ReactOnRails.getComponent('Comp3').component).toBe(Comp3); + }); + }); + + describe('zero production impact', () => { + it('has minimal overhead when debug options are disabled', () => { + const TestComponent = () =>
Test
; + + // Register without debug options + const startTime = performance.now(); + ReactOnRails.register({ TestComponent }); + const endTime = performance.now(); + + // No console logging should occur + expect(consoleLogSpy).not.toHaveBeenCalled(); + + // Registration should complete quickly (sanity check, not a strict performance test) + expect(endTime - startTime).toBeLessThan(100); + }); + }); +}); From c6e6567a040a49bf2316d4bfa4f0f88c4abc6710 Mon Sep 17 00:00:00 2001 From: Justin Gordon Date: Thu, 6 Nov 2025 19:56:10 -1000 Subject: [PATCH 2/2] fix: Show actual character count in debug logging MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Changed component size reporting from kilobytes (size/1024) to actual character count. This is more accurate and useful for developers debugging component registration. Before: "~1.5 chars" (actually KB) After: "342 chars" (actual character count) Updated tests to match the new format. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- packages/react-on-rails/src/base/client.ts | 2 +- packages/react-on-rails/tests/debugLogging.test.js | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/react-on-rails/src/base/client.ts b/packages/react-on-rails/src/base/client.ts index e1debcf7e6..44a5b2e954 100644 --- a/packages/react-on-rails/src/base/client.ts +++ b/packages/react-on-rails/src/base/client.ts @@ -205,7 +205,7 @@ Fix: Use only react-on-rails OR react-on-rails-pro, not both.`); componentNames.forEach((name) => { const component = components[name]; const size = component.toString().length; - console.log(`[ReactOnRails] ✅ Registered: ${name} (~${(size / 1024).toFixed(1)} chars)`); + console.log(`[ReactOnRails] ✅ Registered: ${name} (${size} chars)`); }); } } else { diff --git a/packages/react-on-rails/tests/debugLogging.test.js b/packages/react-on-rails/tests/debugLogging.test.js index d3770cb65e..2ef3f32f33 100644 --- a/packages/react-on-rails/tests/debugLogging.test.js +++ b/packages/react-on-rails/tests/debugLogging.test.js @@ -101,7 +101,7 @@ describe('Debug Logging', () => { expect.stringMatching(/\[ReactOnRails\] Component registration completed in \d+\.\d+ms/), ); expect(consoleLogSpy).toHaveBeenCalledWith( - expect.stringMatching(/\[ReactOnRails\] ✅ Registered: TestComponent \(~\d+\.\d+ chars\)/), + expect.stringMatching(/\[ReactOnRails\] ✅ Registered: TestComponent \(\d+ chars\)/), ); }); @@ -120,10 +120,10 @@ describe('Debug Logging', () => { // Check that individual component registrations are logged with size info expect(consoleLogSpy).toHaveBeenCalledWith( - expect.stringMatching(/\[ReactOnRails\] ✅ Registered: SmallComponent \(~\d+\.\d+ chars\)/), + expect.stringMatching(/\[ReactOnRails\] ✅ Registered: SmallComponent \(\d+ chars\)/), ); expect(consoleLogSpy).toHaveBeenCalledWith( - expect.stringMatching(/\[ReactOnRails\] ✅ Registered: LargerComponent \(~\d+\.\d+ chars\)/), + expect.stringMatching(/\[ReactOnRails\] ✅ Registered: LargerComponent \(\d+ chars\)/), ); }); @@ -144,7 +144,7 @@ describe('Debug Logging', () => { expect.stringMatching(/\[ReactOnRails\] Component registration completed in \d+\.\d+ms/), ); expect(consoleLogSpy).toHaveBeenCalledWith( - expect.stringMatching(/\[ReactOnRails\] ✅ Registered: TestComponent \(~\d+\.\d+ chars\)/), + expect.stringMatching(/\[ReactOnRails\] ✅ Registered: TestComponent \(\d+ chars\)/), ); }); });