Skip to content
Draft
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
14 changes: 1 addition & 13 deletions .storybook/main.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,10 @@
module.exports = {
stories: ['../app/**/*.stories.mdx', '../app/**/*.stories.@(js|jsx)'],

addons: [
'@storybook/addon-essentials',
'@storybook/addon-controls',
'@bumped-inc/storybook-addon-lingui-v3',
'@storybook/addon-a11y'
],

framework: {
name: '@storybook/react-webpack5',
options: {}
},

docs: {
autodocs: true
},

framework: '@storybook/react',
core: {
builder: '@storybook/builder-webpack5'
}
Expand Down
61 changes: 61 additions & 0 deletions .storybook/preview.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,65 @@
import enMessages from '../app/translations/en.json';
import ReactDOM from 'react-dom';

// Prevent jsdom "Not implemented: window.scrollTo" errors in Storybook & test-runner
if (typeof window !== 'undefined') {
if (!window.scrollTo) {
window.scrollTo = () => {};
}
}

// Polyfill for React 19 compatibility - ReactDOM.unmountComponentAtNode was removed
if (!ReactDOM.unmountComponentAtNode) {
ReactDOM.unmountComponentAtNode = (container) => {
// In React 19, we need to use the new createRoot API
// For Storybook compatibility, we'll try to unmount if there's a root
try {
if (container._reactRoot) {
container._reactRoot.unmount();
delete container._reactRoot;
return true;
}
// Fallback: clear the container
if (container) {
container.innerHTML = '';
}
return true;
} catch (error) {
console.warn('Failed to unmount component:', error);
return false;
}
};
}

// Polyfill for React 19 compatibility - ReactDOM.render was removed
if (!ReactDOM.render) {
// Import createRoot from react-dom/client (React 18+ API)
const { createRoot } = require('react-dom/client');

ReactDOM.render = (element, container, callback) => {
try {
// Check if we already have a root for this container
if (!container._reactRoot) {
container._reactRoot = createRoot(container);
}

// Render the element
container._reactRoot.render(element);

// Call the callback if provided (legacy behavior)
if (callback) {
// Use setTimeout to match legacy ReactDOM.render callback timing
setTimeout(callback, 0);
}

return container._reactRoot;
} catch (error) {
console.warn('Failed to render component:', error);
return null;
}
};
}

export const parameters = {
actions: { argTypesRegex: '^on[A-Z].*' },
controls: {
Expand Down
57 changes: 57 additions & 0 deletions .storybook/test-runner.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,63 @@ const { injectAxe, checkA11y } = require('axe-playwright');
module.exports = {
async preRender(page) {
await injectAxe(page);
// Add scrollTo mock and ReactDOM polyfill to prevent browser API errors
await page.addInitScript(() => {
if (!window.scrollTo) {
window.scrollTo = () => {};
}

// Polyfill for React 19 compatibility - ReactDOM.unmountComponentAtNode was removed
if (window.ReactDOM && !window.ReactDOM.unmountComponentAtNode) {
window.ReactDOM.unmountComponentAtNode = (container) => {
try {
if (container._reactRoot) {
container._reactRoot.unmount();
delete container._reactRoot;
return true;
}
if (container) {
container.innerHTML = '';
}
return true;
} catch (error) {
console.warn('Failed to unmount component:', error);
return false;
}
};
}

// Polyfill for React 19 compatibility - ReactDOM.render was removed
if (window.ReactDOM && !window.ReactDOM.render) {
window.ReactDOM.render = (element, container, callback) => {
try {
// Check if we already have a root for this container
if (!container._reactRoot && window.ReactDOMClient && window.ReactDOMClient.createRoot) {
container._reactRoot = window.ReactDOMClient.createRoot(container);
}

if (container._reactRoot) {
// Render the element
container._reactRoot.render(element);

// Call the callback if provided (legacy behavior)
if (callback) {
setTimeout(callback, 0);
}

return container._reactRoot;
} else {
console.warn('createRoot not available, falling back to innerHTML');
container.innerHTML = '';
return null;
}
} catch (error) {
console.warn('Failed to render component:', error);
return null;
}
};
}
});
},
async postRender(page) {
await checkA11y(page, '#root', {
Expand Down
13 changes: 11 additions & 2 deletions .storybook/webpack.config.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,21 @@
const path = require('path');
const genBaseConfig = require('../internals/webpack/webpack.config.base');
const colors = require('../app/themes/colors');

module.exports = ({ config }) => {
// hack cause smart knobs is not working on production
process.env.NODE_ENV = 'development';

config.resolve.alias = genBaseConfig(config).resolve.alias;
// Set up basic aliases without importing the base config that has image optimization
config.resolve.alias = {
...config.resolve.alias,
'@app': path.resolve(__dirname, '../app'),
'@components': path.resolve(__dirname, '../app/components'),
'@containers': path.resolve(__dirname, '../app/containers'),
'@services': path.resolve(__dirname, '../app/services'),
'@utils': path.resolve(__dirname, '../app/utils'),
'@themes': path.resolve(__dirname, '../app/themes'),
'@images': path.resolve(__dirname, '../app/images'),
};

config.module.rules.push({
test: /\.less$/,
Expand Down
4 changes: 3 additions & 1 deletion app/components/ScrollToTop/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ export const ScrollToTop = (props) => {

// Automatically scrolls to top whenever pathname changes
useEffect(() => {
window.scrollTo(0, 0);
if (typeof window !== 'undefined' && window.scrollTo) {
window.scrollTo(0, 0);
}
}, [pathname]);
return props.children;
};
Expand Down
69 changes: 69 additions & 0 deletions jest.setup.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,72 @@ jest.mock('redux-persist', () => ({
...jest.requireActual('redux-persist'),
persistReducer: jest.fn().mockImplementation((config, reducer) => reducer)
}));

// Mock window.scrollTo to prevent jsdom "Not implemented" errors
if (!window.scrollTo) {
Object.defineProperty(window, 'scrollTo', {
value: jest.fn(),
writable: true
});
}

// Mock window.location.reload to prevent jsdom "Not implemented" errors
if (!window.location.reload) {
Object.defineProperty(window.location, 'reload', {
value: jest.fn(),
writable: true
});
}

// Polyfill for React 19 compatibility - ReactDOM.unmountComponentAtNode was removed
import ReactDOM from 'react-dom';
if (!ReactDOM.unmountComponentAtNode) {
ReactDOM.unmountComponentAtNode = (container) => {
// In React 19, we need to use the new createRoot API
// For Storybook compatibility, we'll try to unmount if there's a root
try {
if (container._reactRoot) {
container._reactRoot.unmount();
delete container._reactRoot;
return true;
}
// Fallback: clear the container
if (container) {
container.innerHTML = '';
}
return true;
} catch (error) {
console.warn('Failed to unmount component:', error);
return false;
}
};
}

// Polyfill for React 19 compatibility - ReactDOM.render was removed
if (!ReactDOM.render) {
// Import createRoot from react-dom/client (React 18+ API)
const { createRoot } = require('react-dom/client');

ReactDOM.render = (element, container, callback) => {
try {
// Check if we already have a root for this container
if (!container._reactRoot) {
container._reactRoot = createRoot(container);
}

// Render the element
container._reactRoot.render(element);

// Call the callback if provided (legacy behavior)
if (callback) {
// Use setTimeout to match legacy ReactDOM.render callback timing
setTimeout(callback, 0);
}

return container._reactRoot;
} catch (error) {
console.warn('Failed to render component:', error);
return null;
}
};
}
Loading