From 2d9d82e8dba5211e836005e0e629ecf259051271 Mon Sep 17 00:00:00 2001 From: Miroslav Bauer Date: Mon, 9 Feb 2026 13:12:56 +0100 Subject: [PATCH] test: improve test coverage * Adds @babel/plugin-transform-runtime to support async/await in tests * Adds test:coverage npm script * Adds dev module tests, improves overridable & store coverage --- .babelrc.json | 5 +- package.json | 1 + src/dev.test.js | 213 ++++++++++++++++++++++++++++++++++++++++ src/overridable.test.js | 28 ++++++ src/store.test.js | 11 +++ 5 files changed, 257 insertions(+), 1 deletion(-) create mode 100644 src/dev.test.js diff --git a/.babelrc.json b/.babelrc.json index 827e425..88cb76c 100644 --- a/.babelrc.json +++ b/.babelrc.json @@ -1,4 +1,7 @@ { - "plugins": ["@babel/plugin-proposal-class-properties"], + "plugins": [ + "@babel/plugin-proposal-class-properties", + "@babel/plugin-transform-runtime" + ], "presets": ["@babel/preset-env", "@babel/preset-react"] } diff --git a/package.json b/package.json index 97032e7..2dd7797 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "scripts": { "build": "rimraf dist && NODE_ENV=production rollup -c", "test": "jest", + "test:coverage": "jest --coverage", "lint": "eslint src/**/*.js" }, "peerDependencies": { diff --git a/src/dev.test.js b/src/dev.test.js new file mode 100644 index 0000000..3f2da0a --- /dev/null +++ b/src/dev.test.js @@ -0,0 +1,213 @@ +import {mount} from 'enzyme'; +import React from 'react'; +import {act} from 'react-dom/test-utils'; +import {startDevMode, useDevMode, DevModeWrapper} from './dev'; + +// Shared event listener mocks +const originalAddEventListener = window.addEventListener; +const originalRemoveEventListener = window.removeEventListener; + +const eventName = 'ReactOverridableDevMode'; + +function setupWindowEventListenerMocks() { + const addedListeners = []; + const removedListeners = []; + + window.addEventListener = jest.fn((event, handler) => { + if (event === eventName) { + addedListeners.push(handler); + } + return originalAddEventListener.call(window, event, handler); + }); + + window.removeEventListener = jest.fn((event, handler) => { + if (event === eventName) { + removedListeners.push(handler); + } + return originalRemoveEventListener.call(window, event, handler); + }); + + return {addedListeners, removedListeners}; +} + +function restoreEventListenerMocks() { + window.addEventListener = originalAddEventListener; + window.removeEventListener = originalRemoveEventListener; +} + +describe('Tests for dev module start dev mode function', () => { + test('it should expose reactOverridableEnableDevMode function to window', () => { + expect(window.reactOverridableEnableDevMode).toBeDefined(); + expect(typeof window.reactOverridableEnableDevMode).toBe('function'); + }); + + test('it should start dev mode when reactOverridableEnableDevMode gets called', () => { + const eventListener = jest.fn(); + window.addEventListener(eventName, eventListener); + + act(() => { + startDevMode(); + }); + + expect(eventListener).toHaveBeenCalled(); + expect(window._ReactOverridableIsDevMode).toBe(true); + + window.removeEventListener(eventName, eventListener); + }); +}); + +describe('Tests for useDevMode hook', () => { + test('it should return false when dev mode is not active', () => { + // Resets dev mode state + delete window._ReactOverridableIsDevMode; + + const TestComponent = () => { + const isDevMode = useDevMode(); + return
{isDevMode ? 'DEV' : 'USER'}
; + }; + + const wrapper = mount(); + expect(wrapper.text()).toBe('USER'); + }); + + test('it should return true when dev mode is active', () => { + act(() => { + startDevMode(); + }); + + const TestComponent = () => { + const isDevMode = useDevMode(); + return
{isDevMode ? 'DEV' : 'USER'}
; + }; + + const wrapper = mount(); + expect(wrapper.text()).toBe('DEV'); + }); + + test('it should register & clean up ReactOverridableDevMode event listener', () => { + delete window._ReactOverridableIsDevMode; + + const {addedListeners, removedListeners} = setupWindowEventListenerMocks(); + + const TestComponent = () => { + const isDevMode = useDevMode(); + return
{isDevMode ? 'DEV' : 'USER'}
; + }; + + const wrapper = mount(); + + // Verify listener was added + expect(addedListeners.length).toBe(1); + + // Unmount component + wrapper.unmount(); + + // Verify the same listener was removed (cleanup function called) + expect(removedListeners.length).toBe(1); + expect(removedListeners[0]).toBe(addedListeners[0]); + + restoreEventListenerMocks(); + }); +}); + +describe('Tests for DevModeWrapper', () => { + test('should render children when not in dev mode', () => { + // Resets dev mode state + delete window._ReactOverridableIsDevMode; + + const wrapper = mount( + +
Test Content
+
+ ); + + expect(wrapper.find('.test-content')).toHaveLength(1); + expect(wrapper.text()).toContain('Test Content'); + }); + + test('should render with multiple children', () => { + const wrapper = mount( + + First + Second + + ); + + expect(wrapper.find('.first')).toHaveLength(1); + expect(wrapper.find('.second')).toHaveLength(1); + }); + + test('should not wrap children in extra DOM elements when not in dev mode', () => { + // Resets dev mode state + delete window._ReactOverridableIsDevMode; + + const wrapper = mount( + +
Content
+
+ ); + + const children = wrapper.children(); + expect(children).toHaveLength(1); + expect(children.get(0)).toEqual(
Content
); + }); + + test('should create overlay div when rendered', () => { + mount( + + Content + + ); + + // There should now be a single div appended to body as _overlayRoot. + const bodyDivs = document.body.querySelectorAll(':scope > div'); + expect(bodyDivs.length).toEqual(1); + }); + + test('should insert span anchor when dev mode is active', () => { + act(() => { + startDevMode(); + }); + + const wrapper = mount( + +
Content
+
+ ); + + const html = wrapper.html(); + // Verify span appears before the the normal chilren + const spanIndex = html.indexOf(''); + const childIndex = html.indexOf('
Content
'); + expect(spanIndex).toBeGreaterThanOrEqual(0); + expect(childIndex).toBeGreaterThanOrEqual(0); + expect(spanIndex).toBeLessThan(childIndex); + }); + + test('should not re-render children when dev mode is activated', () => { + let renderCount = 0; + + const ChildComponent = () => { + renderCount++; + return
Content
; + }; + + const wrapper = mount( + + + + ); + + const initialRenderCount = renderCount; + + act(() => { + startDevMode(); + }); + + // Force update to trigger re-render + wrapper.update(); + + // Verify child wasn't re-rendered unnecessarily + expect(renderCount).toBe(initialRenderCount); + }); +}); diff --git a/src/overridable.test.js b/src/overridable.test.js index b7a1f4b..9d83151 100644 --- a/src/overridable.test.js +++ b/src/overridable.test.js @@ -106,6 +106,13 @@ describe('Tests for parametrized', () => { expect(children).toHaveLength(1); }); + test('it should use fallback name when component has no displayName', () => { + const AnonymousComponent = () =>
Anonymous
; + const ParametrizedAnonymous = parametrize(AnonymousComponent, {id: 1}); + + expect(ParametrizedAnonymous.displayName).toEqual('Parametrized(AnonymousComponent)'); + }); + test('it should render the cmp with id `ExampleComponent` with new props passed as function', () => { const parametrized = parametrize(OverridableExampleComponent, ({title, ...props}) => ({ title: `Other ${title}`, @@ -127,6 +134,15 @@ describe('Tests for parametrized', () => { const children = ExampleCmp.find('p').children(); expect(children).toHaveLength(1); }); + + // eslint-disable-next-line jest/expect-expect + test('it should handle parametrize called twice on same component', () => { + const OnceParametrizedComponent = parametrize(OverridableExampleComponent, {title: 'First'}); + const TwiceParametrizedComponent = parametrize(OnceParametrizedComponent, {color: 'green'}); + + const mounted = mount(); + assertTitleStyle(mounted.find('div'), 'First', {color: 'green'}); + }); }); describe('Tests for Overridable render elements', () => { @@ -187,6 +203,18 @@ describe('Tests for Overridable render elements', () => { ); expect(() => mount(WrongComponent)).toThrow(); }); + + test('it should render nothing when the overriden cmp with id `ExampleComponent` has no overrides & children', () => { + const NoChildrenComponent = () => ; + + const mounted = mount( + + + + ); + + expect(mounted.html()).toEqual(''); + }); }); describe('Tests for Overridable.component', () => { diff --git a/src/store.test.js b/src/store.test.js index 3ebafd3..c52bbbc 100644 --- a/src/store.test.js +++ b/src/store.test.js @@ -56,4 +56,15 @@ describe('Tests for store utility object.', () => { expect(NewCmp.find('ul')).toHaveLength(0); }); + + test('it should return component by id', () => { + overrideStore.add(CMP_ID, ExampleComponent); + const component = overrideStore.get(CMP_ID); + expect(component).toBeDefined(); + expect(component).toBe(ExampleComponent); + }); + + test('it should return null for non-existent component', () => { + expect(overrideStore.get('nonexistent-id')).toBeUndefined(); + }); });