Skip to content
Merged
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
48 changes: 24 additions & 24 deletions apps/backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,38 +13,38 @@
"lint": "eslint source --ext .js,.ts"
},
"dependencies": {
"axios": "1.7.7",
"cookie-parser": "1.4.6",
"axios": "1.13.2",
"cookie-parser": "1.4.7",
"cors": "2.8.5",
"csurf": "1.11.0",
"express": "4.18.2",
"express": "5.1.0",
"express-winston": "4.2.0",
"jsonwebtoken": "9.0.2",
"lnmessage": "0.2.6",
"protobufjs": "7.4.0",
"ts-node": "10.9.1",
"winston": "3.11.0",
"lnmessage": "0.2.7",
"protobufjs": "7.5.4",
"ts-node": "10.9.2",
"winston": "3.18.3",
"ws": "8.18.3"
},
"devDependencies": {
"@eslint/js": "9.22.0",
"@types/cookie-parser": "1.4.6",
"@types/cors": "2.8.17",
"@eslint/js": "9.39.1",
"@types/cookie-parser": "1.4.10",
"@types/cors": "2.8.19",
"@types/csurf": "1.11.5",
"@types/express": "4.17.21",
"@types/jsonwebtoken": "9.0.5",
"@types/morgan": "1.9.9",
"@types/node": "20.9.4",
"@typescript-eslint/eslint-plugin": "8.33.0",
"@typescript-eslint/parser": "8.33.0",
"eslint": "9.27.0",
"eslint-plugin-node-dependencies": "0.12.0",
"eslint-plugin-react": "7.35.2",
"globals": "16.0.0",
"nodemon": "3.0.1",
"prettier": "3.1.0",
"typescript": "5.8.3",
"typescript-eslint": "8.26.0"
"@types/express": "5.0.5",
"@types/jsonwebtoken": "9.0.10",
"@types/morgan": "1.9.10",
"@types/node": "24.10.1",
"@typescript-eslint/eslint-plugin": "8.48.0",
"@typescript-eslint/parser": "8.48.0",
"eslint": "9.39.1",
"eslint-plugin-node-dependencies": "1.3.0",
"eslint-plugin-react": "7.37.5",
"globals": "16.5.0",
"nodemon": "3.1.11",
"prettier": "3.6.2",
"typescript": "5.9.3",
"typescript-eslint": "8.48.0"
},
"overrides": {
"chalk": "4.1.2",
Expand Down
2 changes: 1 addition & 1 deletion apps/backend/source/controllers/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ export class AuthController {
isUserAuthenticated = async (req: Request, res: Response, next: NextFunction) => {
try {
const uaRes = isAuthenticated(req.cookies.token);
if (req.body.returnResponse) {
if (req.body?.returnResponse || false) {
// Frontend is asking if user is authenticated or not
if (APP_CONSTANTS.APP_SINGLE_SIGN_ON === 'true') {
return res.status(201).json({ isAuthenticated: true, isValidPassword: true });
Expand Down
8 changes: 5 additions & 3 deletions apps/backend/source/controllers/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,9 @@ export class SharedController {
const FIAT_VENUE = FIAT_VENUES.hasOwnProperty(req.params.fiatCurrency)
? FIAT_VENUES[req.params.fiatCurrency]
: 'COINGECKO';
logger.info('Fiat URL: ' + FIAT_RATE_API + FIAT_VENUE + '/pairs/XBT/' + req.params.fiatCurrency);
logger.info(
'Fiat URL: ' + FIAT_RATE_API + FIAT_VENUE + '/pairs/XBT/' + req.params.fiatCurrency,
);
return axios
.get(FIAT_RATE_API + FIAT_VENUE + '/pairs/XBT/' + req.params.fiatCurrency)
.then((response: any) => {
Expand All @@ -100,11 +102,11 @@ export class SharedController {
})
.catch(err => {
logger.error('Fiat Error Response: ' + JSON.stringify(err));
res.status(200).json({ venue: "NONE", rate: "0" });
res.status(200).json({ venue: 'NONE', rate: '0' });
});
} catch (error: any) {
logger.error('Error from Fiat Rate: ' + JSON.stringify(error));
res.status(200).json({ venue: "NONE", rate: "0" });
res.status(200).json({ venue: 'NONE', rate: '0' });
}
};

Expand Down
70 changes: 37 additions & 33 deletions apps/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,53 +7,53 @@
"scripts": {
"start": "PORT=4300 react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"test": "react-scripts test --testTimeout=30000",
"lint": "eslint src --ext .js,.jsx,.ts,.tsx"
},
"dependencies": {
"@fortawesome/fontawesome-svg-core": "6.4.2",
"@fortawesome/free-solid-svg-icons": "6.4.2",
"@fortawesome/react-fontawesome": "0.2.0",
"@reduxjs/toolkit": "2.7.0",
"axios": "1.6.7",
"bootstrap": "5.3.2",
"@fortawesome/fontawesome-svg-core": "7.1.0",
"@fortawesome/free-solid-svg-icons": "7.1.0",
"@fortawesome/react-fontawesome": "3.1.0",
"@reduxjs/toolkit": "2.11.0",
"axios": "1.13.2",
"bootstrap": "5.3.8",
"copy-to-clipboard": "3.3.3",
"crypto-js": "4.2.0",
"framer-motion": "10.16.5",
"framer-motion": "12.23.24",
"moment": "2.30.1",
"node-sass": "9.0.0",
"qrcode.react": "3.1.0",
"react": "18.2.0",
"react-bootstrap": "2.10.1",
"react-datepicker": "8.1.0",
"react-dom": "18.2.0",
"qrcode.react": "4.2.0",
"react": "19.2.0",
"react-bootstrap": "2.10.10",
"react-datepicker": "8.10.0",
"react-dom": "19.2.0",
"react-perfect-scrollbar": "1.5.8",
"react-redux": "9.2.0",
"react-router-dom": "6.30.0",
"recharts": "2.15.1",
"redux-thunk": "3.1.0"
"react-router-dom": "7.9.6",
"recharts": "3.5.0",
"redux-thunk": "3.1.0",
"sass": "1.94.2"
},
"devDependencies": {
"@testing-library/jest-dom": "6.4.2",
"@testing-library/react": "14.2.1",
"@testing-library/user-event": "14.5.1",
"@types/jest": "29.5.12",
"@types/node": "20.9.4",
"@types/react": "18.2.61",
"@types/react-dom": "18.2.19",
"@testing-library/jest-dom": "6.9.1",
"@testing-library/react": "16.3.0",
"@testing-library/user-event": "14.6.1",
"@types/jest": "30.0.0",
"@types/node": "24.10.1",
"@types/react": "19.2.7",
"@types/react-dom": "19.2.3",
"@types/redux-mock-store": "1.5.0",
"axios-mock-adapter": "1.22.0",
"canvas": "2.11.2",
"axios-mock-adapter": "2.1.0",
"canvas": "3.2.0",
"react-scripts": "5.0.1",
"redux-mock-store": "1.5.5",
"@typescript-eslint/eslint-plugin": "8.33.0",
"@typescript-eslint/parser": "8.33.0",
"eslint": "9.27.0",
"@typescript-eslint/eslint-plugin": "8.48.0",
"@typescript-eslint/parser": "8.48.0",
"eslint": "9.39.1",
"eslint-plugin-jsx-a11y": "6.10.2",
"eslint-plugin-react": "7.37.5",
"eslint-plugin-react-hooks": "5.2.0",
"ts-jest": "29.1.2",
"typescript": "5.8.2"
"eslint-plugin-react-hooks": "7.0.1",
"ts-jest": "29.4.5",
"typescript": "5.9.3"
},
"overrides": {
"chalk": "4.1.2",
Expand All @@ -76,11 +76,15 @@
]
},
"jest": {
"moduleNameMapper": {
"^react-router-dom": "<rootDir>/../../node_modules/react-router-dom/dist/index.js",
"^react-router": "<rootDir>/../../node_modules/react-router/dist/production/index.js"
},
"transform": {
"^.+\\.[t|j]sx?$": "ts-jest"
},
"transformIgnorePatterns": [
"<rootDir>/node_modules/(?!axios|react-router-dom)/"
"node_modules/(?!axios|react-router-dom|react-router)/"
]
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,73 +3,102 @@ import { renderWithProviders } from '../../../../utilities/test-utilities/mockSt
import { mockBKPRAccountEvents, mockAppStore } from '../../../../utilities/test-utilities/mockData';
import AccountEventsGraph from './AccountEventsGraph';

// Mock ResizeObserver which Recharts uses
global.ResizeObserver = jest.fn().mockImplementation(() => ({
observe: jest.fn(),
unobserve: jest.fn(),
disconnect: jest.fn(),
}));

jest.mock('recharts', () => ({
ResponsiveContainer: ({ children }: any) => <div className="recharts-wrapper">{children}</div>,
BarChart: ({ children, data }: any) => (
<div data-testid="bar-chart" data-item-count={data?.length}>
{children}
</div>
),
Bar: ({ dataKey }: any) => <div data-testid={`bar-${dataKey}`} />,
XAxis: () => <div data-testid="x-axis" />,
YAxis: () => <div data-testid="y-axis" />,
CartesianGrid: () => <div data-testid="cartesian-grid" />,
Tooltip: () => <div data-testid="tooltip" />,
Legend: () => <div data-testid="legend" />,
}));

describe('Account Events Graph component', () => {
beforeEach(() => {
class ResizeObserverMock {
private callback: ResizeObserverCallback;
constructor(callback: ResizeObserverCallback) {
this.callback = callback;
it('should render the graph container', async () => {
await renderWithProviders(
<AccountEventsGraph periods={mockBKPRAccountEvents.periods} />,
{
preloadedState: mockAppStore,
initialRoute: ['/bookkeeper/accountevents'],
useRouter: false,
}
observe = (target: Element) => {
// Simulate dimensions
this.callback(
[
{
target,
contentRect: {
width: 500,
height: 400,
top: 0,
left: 0,
bottom: 400,
right: 500,
x: 0,
y: 0,
toJSON: () => {},
},
} as ResizeObserverEntry,
],
this
);
};
unobserve = jest.fn();
disconnect = jest.fn();
}
global.ResizeObserver = ResizeObserverMock as unknown as typeof ResizeObserver;
Object.defineProperty(HTMLElement.prototype, 'clientWidth', { configurable: true, value: 500 });
Object.defineProperty(HTMLElement.prototype, 'clientHeight', { configurable: true, value: 400 });
);

expect(screen.getByTestId('account-events-graph')).toBeInTheDocument();
});

it('should render the graph container', async () => {
it('should render the chart components', async () => {
await renderWithProviders(
<AccountEventsGraph periods={mockBKPRAccountEvents.periods} />,
{ preloadedState: mockAppStore, initialRoute: ['/bookkeeper/accountevents'] }
{
preloadedState: mockAppStore,
initialRoute: ['/bookkeeper/accountevents'],
useRouter: false,
}
);

// Verify the mocked Recharts components are rendered
expect(screen.getByTestId('account-events-graph')).toBeInTheDocument();
expect(screen.getByTestId('bar-chart')).toBeInTheDocument();
expect(screen.getByTestId('x-axis')).toBeInTheDocument();
expect(screen.getByTestId('y-axis')).toBeInTheDocument();
expect(screen.getByTestId('cartesian-grid')).toBeInTheDocument();
expect(screen.getByTestId('tooltip')).toBeInTheDocument();
expect(screen.getByTestId('legend')).toBeInTheDocument();
});

it('should render the correct number of bars based on account names', async () => {
await renderWithProviders( <AccountEventsGraph periods={mockBKPRAccountEvents.periods} />,
{ preloadedState: mockAppStore, initialRoute: ['/bookkeeper/accountevents'] }
it('should render bars for each account', async () => {
await renderWithProviders(
<AccountEventsGraph periods={mockBKPRAccountEvents.periods} />,
{
preloadedState: mockAppStore,
initialRoute: ['/bookkeeper/accountevents'],
useRouter: false,
}
);

// Get unique account names from mock data to verify bars are created
const uniqueAccounts = Array.from(
new Set(
mockBKPRAccountEvents.periods.flatMap(period =>
period.accounts.map(account => account.account)
)
)
);
const svg = screen.getByTestId('account-events-graph').querySelector('svg');
const bars = svg?.querySelectorAll('rect');
expect(bars?.length).toBeGreaterThan(0);

// Verify a bar is rendered for each unique account
uniqueAccounts.forEach(accountName => {
expect(screen.getByTestId(`bar-${accountName}`)).toBeInTheDocument();
});
});

it('should render XAxis and YAxis labels formatted correctly', async () => {
it('should pass correct data to BarChart', async () => {
await renderWithProviders(
<AccountEventsGraph periods={mockBKPRAccountEvents.periods} />,
{ preloadedState: mockAppStore, initialRoute: ['/bookkeeper/accountevents'] }
{
preloadedState: mockAppStore,
initialRoute: ['/bookkeeper/accountevents'],
useRouter: false,
}
);
const rechartsWrapper = screen.getByTestId('account-events-graph');
const svg = rechartsWrapper.querySelector('svg');
const ticksGroup = svg?.querySelector('g.recharts-cartesian-axis-ticks');
expect(ticksGroup).toBeInTheDocument();
const tickTexts = ticksGroup?.querySelectorAll('text');
expect(tickTexts?.length).toBeGreaterThan(0);
const tickValues = Array.from(tickTexts || []).map(tick => tick.textContent);
expect(tickValues).toContain(mockBKPRAccountEvents.periods[0].period_key);
});

const barChart = screen.getByTestId('bar-chart');
expect(barChart).toBeInTheDocument();

// Verify data is passed (number of periods)
const itemCount = barChart.getAttribute('data-item-count');
expect(itemCount).toBe(String(mockBKPRAccountEvents.periods.length));
});
});
Loading