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();
+ });
});