Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .babelrc.json
Original file line number Diff line number Diff line change
@@ -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"]
}
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"scripts": {
"build": "rimraf dist && NODE_ENV=production rollup -c",
"test": "jest",
"test:coverage": "jest --coverage",
"lint": "eslint src/**/*.js"
},
"peerDependencies": {
Expand Down
213 changes: 213 additions & 0 deletions src/dev.test.js
Original file line number Diff line number Diff line change
@@ -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 <div>{isDevMode ? 'DEV' : 'USER'}</div>;
};

const wrapper = mount(<TestComponent />);
expect(wrapper.text()).toBe('USER');
});

test('it should return true when dev mode is active', () => {
act(() => {
startDevMode();
});

const TestComponent = () => {
const isDevMode = useDevMode();
return <div>{isDevMode ? 'DEV' : 'USER'}</div>;
};

const wrapper = mount(<TestComponent />);
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 <div>{isDevMode ? 'DEV' : 'USER'}</div>;
};

const wrapper = mount(<TestComponent />);

// 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(
<DevModeWrapper id="ExampleComponent.container">
<div className="test-content">Test Content</div>
</DevModeWrapper>
);

expect(wrapper.find('.test-content')).toHaveLength(1);
expect(wrapper.text()).toContain('Test Content');
});

test('should render with multiple children', () => {
const wrapper = mount(
<DevModeWrapper id="ExampleComponent.container">
<span className="first">First</span>
<span className="second">Second</span>
</DevModeWrapper>
);

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(
<DevModeWrapper id="ExampleComponent.container">
<div className="child">Content</div>
</DevModeWrapper>
);

const children = wrapper.children();
expect(children).toHaveLength(1);
expect(children.get(0)).toEqual(<div className="child">Content</div>);
});

test('should create overlay div when rendered', () => {
mount(
<DevModeWrapper id="ExampleComponent.container">
<span>Content</span>
</DevModeWrapper>
);

// 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(
<DevModeWrapper id="ExampleComponent.container">
<div className="child">Content</div>
</DevModeWrapper>
);

const html = wrapper.html();
// Verify span appears before the the normal chilren
const spanIndex = html.indexOf('<span></span>');
const childIndex = html.indexOf('<div class="child">Content</div>');
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 <div className="child">Content</div>;
};

const wrapper = mount(
<DevModeWrapper id="ExampleComponent.container">
<ChildComponent />
</DevModeWrapper>
);

const initialRenderCount = renderCount;

act(() => {
startDevMode();
});

// Force update to trigger re-render
wrapper.update();

// Verify child wasn't re-rendered unnecessarily
expect(renderCount).toBe(initialRenderCount);
});
});
28 changes: 28 additions & 0 deletions src/overridable.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = () => <div>Anonymous</div>;
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}`,
Expand All @@ -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(<TwiceParametrizedComponent />);
assertTitleStyle(mounted.find('div'), 'First', {color: 'green'});
});
});

describe('Tests for Overridable render elements', () => {
Expand Down Expand Up @@ -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 = () => <Overridable id="NoChildrenComponent.container" />;

const mounted = mount(
<OverridableContext.Provider value={{}}>
<NoChildrenComponent />
</OverridableContext.Provider>
);

expect(mounted.html()).toEqual('');
});
});

describe('Tests for Overridable.component', () => {
Expand Down
11 changes: 11 additions & 0 deletions src/store.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
});
});