diff --git a/apps/frontend/src/components/bookkeeper/BalanceSheet/Graph/BalanceSheetGraph.test.tsx b/apps/frontend/src/components/bookkeeper/BalanceSheet/Graph/BalanceSheetGraph.test.tsx index d1ad679..dc6c3d3 100644 --- a/apps/frontend/src/components/bookkeeper/BalanceSheet/Graph/BalanceSheetGraph.test.tsx +++ b/apps/frontend/src/components/bookkeeper/BalanceSheet/Graph/BalanceSheetGraph.test.tsx @@ -1,15 +1,101 @@ -import { render, screen } from '@testing-library/react'; +import { act, fireEvent, waitFor } from '@testing-library/react'; import BalanceSheetGraph from './BalanceSheetGraph'; +import { + getMockStoreData, + mockBalanceSheetData, + renderWithMockContext, +} from '../../../../utilities/test-utilities'; -jest.mock('react-router-dom', () => ({ - ...jest.requireActual('react-router-dom'), - useNavigate: jest.fn(), -})); +describe('Balance Sheet Graph component ', () => { + let providerProps = JSON.parse(JSON.stringify(getMockStoreData())); + let container; -describe.skip('Balance Sheet Graph component ', () => { - beforeEach(() => render()); + beforeEach(() => { + ({ container } = renderWithMockContext( + , + { providerProps }, + )); + }); + + it('should render the graph', () => { + const svg = container.querySelector('svg'); + expect(svg).toBeInTheDocument(); + }); + + it('renders correctly with empty data', () => { + renderWithMockContext(, { + providerProps, + }); + const svg = container.querySelector('svg'); + expect(svg).toBeInTheDocument(); + }); + + it('renders correct number of bars for the periods', () => { + const bars = container.querySelectorAll('g.bar-group'); + expect(bars.length).toBe(2); + }); + + it('displays tooltip with correct content on hover', async () => { + const bar = container.querySelector('rect.bar'); + let tooltip = container.querySelector('.sats-flow-tooltip'); + + expect(tooltip).toBeNull(); + expect(bar).not.toBeNull(); + + await act(async () => fireEvent.mouseOver(bar)); + + tooltip = await waitFor(() => { + const tooltipElement = document.querySelector('.balance-sheet-tooltip'); + if (!tooltipElement) throw new Error('Tooltip not found'); + return tooltipElement; + }); + + expect(tooltip).toBeVisible(); + + const tooltipText = tooltip.textContent?.replace(/\s+/g, ' ').trim(); + const expectedText = ` + Short Channel ID: 12345x12345x1 + Remote Alias: Wallet 1 + Balance: 500,000.000 + Percentage: 50% + Account: onchain_wallet + Total Period Balance: 1,000,000.000 + ` + .replace(/\s+/g, ' ') + .trim(); + expect(tooltipText).toContain(expectedText); + }); + + it('hides tooltip on mouseout', async () => { + const bar = container.querySelector('rect.bar'); + let tooltip = container.querySelector('.balance-sheet-tooltip'); + + await act(async () => fireEvent.mouseOver(bar)); + + tooltip = await waitFor(() => { + const tooltipElement = document.querySelector('.balance-sheet-tooltip'); + if (!tooltipElement) throw new Error('Tooltip not found'); + return tooltipElement; + }); + + expect(tooltip).toBeVisible(); + + await act(async () => fireEvent.mouseOut(bar)); + + tooltip = await waitFor(() => { + const tooltipElement = document.querySelector('.balance-sheet-tooltip'); + if (!tooltipElement) throw new Error('Tooltip not found'); + return tooltipElement; + }); + + expect(tooltip).not.toBeVisible(); + }); + it('renders the x-axis and y-axis correctly', () => { + const xAxisLabels = container.querySelectorAll('.x-axis text'); + const yAxisLabels = container.querySelectorAll('.y-axis text'); - it('should be in the document', () => { - expect(screen.getByTestId('balancesheetgraph-container')).not.toBeEmptyDOMElement() + expect(xAxisLabels.length).toBe(mockBalanceSheetData.periods.length); + expect(yAxisLabels.length).toBeGreaterThan(0); + expect(xAxisLabels[0].textContent).toBe(mockBalanceSheetData.periods[0].periodKey); }); }); diff --git a/apps/frontend/src/components/bookkeeper/BalanceSheet/Graph/BalanceSheetGraph.tsx b/apps/frontend/src/components/bookkeeper/BalanceSheet/Graph/BalanceSheetGraph.tsx index f505d0b..2de7864 100644 --- a/apps/frontend/src/components/bookkeeper/BalanceSheet/Graph/BalanceSheetGraph.tsx +++ b/apps/frontend/src/components/bookkeeper/BalanceSheet/Graph/BalanceSheetGraph.tsx @@ -124,6 +124,7 @@ function BalanceSheetGraph({ balanceSheetData, width }) { barsGroup.attr("clip-path", "url(#chart-area-clip"); svg.append("g") + .attr("class", "y-axis") .call(d3.axisLeft(yScale) .tickSizeInner(0) .tickSizeOuter(0) diff --git a/apps/frontend/src/components/bookkeeper/BkprRoot/BkprRoot.test.tsx b/apps/frontend/src/components/bookkeeper/BkprRoot/BkprRoot.test.tsx index 72fdd2d..e140586 100644 --- a/apps/frontend/src/components/bookkeeper/BkprRoot/BkprRoot.test.tsx +++ b/apps/frontend/src/components/bookkeeper/BkprRoot/BkprRoot.test.tsx @@ -1,10 +1,79 @@ -import { render, screen } from '@testing-library/react'; +import { act, fireEvent, screen } from '@testing-library/react'; import Bookkeeper from './BkprRoot'; +import { useNavigate } from 'react-router-dom'; +import { getMockStoreData, renderWithMockContext } from '../../../utilities/test-utilities'; describe('Bookkeeper component ', () => { - beforeEach(() => render()); + let mockNavigate: jest.Mock; + let providerProps = JSON.parse(JSON.stringify(getMockStoreData())); + + beforeEach(() => { + mockNavigate = jest.fn(); + (useNavigate as jest.Mock).mockReturnValue(mockNavigate); + renderWithMockContext(, { providerProps }); + }); it('should be in the document', () => { - expect(screen.getByTestId('bookkeeper-container')).not.toBeEmptyDOMElement() + expect(screen.getByTestId('bookkeeper-container')).not.toBeEmptyDOMElement(); + }); + + it('should display the dashboard header', () => { + expect(screen.getByText('Bookkeeper Dashboard')).toBeInTheDocument(); + }); + + it('should display the Balance Sheet section', () => { + expect(screen.getByText('Balance Sheet')).toBeInTheDocument(); + expect(screen.getByText('Total Number of Channels')).toBeInTheDocument(); + expect(screen.getByText('Total Balance in Channels')).toBeInTheDocument(); + expect(screen.getByText('Total Balance in Wallet')).toBeInTheDocument(); + }); + + it('should display the Sats Flow section', () => { + expect(screen.getByText('Sats Flow')).toBeInTheDocument(); + expect(screen.getByText('Inflow this month')).toBeInTheDocument(); + expect(screen.getByText('Outflow this month')).toBeInTheDocument(); + }); + + it('should display the Volume Chart section', () => { + expect(screen.getByText('Volume Chart')).toBeInTheDocument(); + expect(screen.getByText('Track route performance.')).toBeInTheDocument(); + }); + + it('should navigate to Terminal when the Terminal button is clicked', async () => { + const terminalButton = screen.getByText('Terminal'); + await act(async () => fireEvent.click(terminalButton)); + + expect(mockNavigate).toHaveBeenCalledWith('/bookkeeper/terminal'); + }); + + it('should navigate to Balance Sheet on View More click', async () => { + const viewMoreButton = screen.getAllByText('View More')[0]; + await act(async () => fireEvent.click(viewMoreButton)); + + expect(mockNavigate).toHaveBeenCalledWith('/bookkeeper/balancesheet'); + }); + + it('should navigate to Sats Flow on View More click', async () => { + const viewMoreButton = screen.getAllByText('View More')[1]; + await act(async () => fireEvent.click(viewMoreButton)); + + expect(mockNavigate).toHaveBeenCalledWith('/bookkeeper/satsflow'); + }); + + it('should navigate to Volume on View More click', async () => { + const viewMoreButton = screen.getAllByText('View More')[2]; + await act(async () => fireEvent.click(viewMoreButton)); + + expect(mockNavigate).toHaveBeenCalledWith('/bookkeeper/volume'); + }); + + it('should display fetched data when available', async () => { + expect(await screen.findByText('5')).toBeInTheDocument(); + expect(await screen.findByText('100,000.000')).toBeInTheDocument(); + expect(await screen.findByText('50,000.000')).toBeInTheDocument(); + expect(await screen.findByText('2,000.000')).toBeInTheDocument(); + expect(await screen.findByText('−1,000.000')).toBeInTheDocument(); + expect(await screen.findByText('route1')).toBeInTheDocument(); + expect(await screen.findByText('route2')).toBeInTheDocument(); }); }); diff --git a/apps/frontend/src/components/bookkeeper/SatsFlow/SatsFlowGraph/SatsFlowGraph.test.tsx b/apps/frontend/src/components/bookkeeper/SatsFlow/SatsFlowGraph/SatsFlowGraph.test.tsx index e69de29..8713d8d 100644 --- a/apps/frontend/src/components/bookkeeper/SatsFlow/SatsFlowGraph/SatsFlowGraph.test.tsx +++ b/apps/frontend/src/components/bookkeeper/SatsFlow/SatsFlowGraph/SatsFlowGraph.test.tsx @@ -0,0 +1,106 @@ +import SatsFlowGraph from './SatsFlowGraph'; +import { + getMockStoreData, + mockSatsFlowData, + renderWithMockContext, +} from '../../../../utilities/test-utilities'; +import { act, fireEvent, waitFor } from '@testing-library/react'; + +describe('Sats Flow Graph component ', () => { + let providerProps = JSON.parse(JSON.stringify(getMockStoreData())); + let container; + + beforeEach(() => { + ({ container } = renderWithMockContext( + , + { + providerProps, + }, + )); + }); + + it('should render the graph', () => { + const svg = container.querySelector('svg'); + expect(svg).toBeInTheDocument(); + }); + + it('renders correctly with empty data', () => { + renderWithMockContext(, { + providerProps, + }); + const svg = container.querySelector('svg'); + expect(svg).toBeInTheDocument(); + }); + + it('renders correct number of bars for the periods', () => { + const bars = container.querySelectorAll('g.bar-group'); + expect(bars.length).toBe(2); + }); + + it('displays tooltip with correct content on hover', async () => { + const bar = container.querySelector('rect.bar'); + let tooltip = container.querySelector('.sats-flow-tooltip'); + + expect(tooltip).toBeNull(); + expect(bar).not.toBeNull(); + + await act(async () => fireEvent.mouseOver(bar)); + + tooltip = await waitFor(() => { + const tooltipElement = document.querySelector('.sats-flow-tooltip'); + if (!tooltipElement) throw new Error('Tooltip not found'); + return tooltipElement; + }); + + expect(tooltip).toBeVisible(); + + const tooltipText = tooltip.textContent?.replace(/\s+/g, ' ').trim(); + const expectedText = ` + Event Tag: deposit + Net Inflow: 800.000 + Credits: 1,000.000 + Debits: 200.000 + Volume: 1,200.000 + Period Inflow: 1,500.000 + Period Outflow: 200.000 + Period Net Inflow: 1,300.000 + Period Volume: 1,700.000 + ` + .replace(/\s+/g, ' ') + .trim(); + expect(tooltipText).toContain(expectedText); + }); + + it('hides tooltip on mouseout', async () => { + const bar = container.querySelector('rect.bar'); + let tooltip = container.querySelector('.sats-flow-tooltip'); + + await act(async () => fireEvent.mouseOver(bar)); + + tooltip = await waitFor(() => { + const tooltipElement = document.querySelector('.sats-flow-tooltip'); + if (!tooltipElement) throw new Error('Tooltip not found'); + return tooltipElement; + }); + + expect(tooltip).toBeVisible(); + + await act(async () => fireEvent.mouseOut(bar)); + + tooltip = await waitFor(() => { + const tooltipElement = document.querySelector('.sats-flow-tooltip'); + if (!tooltipElement) throw new Error('Tooltip not found'); + return tooltipElement; + }); + + expect(tooltip).not.toBeVisible(); + }); + + it('renders the x-axis and y-axis correctly', () => { + const xAxisLabels = container.querySelectorAll('.x-axis-labels text'); + const yAxisLabels = container.querySelectorAll('.y-axis text'); + + expect(xAxisLabels.length).toBe(2); + expect(yAxisLabels.length).toBeGreaterThan(0); + }); +}); diff --git a/apps/frontend/src/components/bookkeeper/SatsFlow/SatsFlowGraph/SatsFlowGraph.tsx b/apps/frontend/src/components/bookkeeper/SatsFlow/SatsFlowGraph/SatsFlowGraph.tsx index 51fc7ac..0d21129 100644 --- a/apps/frontend/src/components/bookkeeper/SatsFlow/SatsFlowGraph/SatsFlowGraph.tsx +++ b/apps/frontend/src/components/bookkeeper/SatsFlow/SatsFlowGraph/SatsFlowGraph.tsx @@ -159,7 +159,8 @@ function SatsFlowGraph({ satsFlowData, width }: { satsFlowData: SatsFlow, width: //set up y axis const yAxisTickFormat = d => `${d3.format(",")(d)}`; - svg.append("g") + const yAxisGroup = svg.append("g") + .attr("class", "y-axis") .call(d3.axisLeft(yScale) .tickSizeInner(0) .tickSizeOuter(0) diff --git a/apps/frontend/src/components/bookkeeper/SatsFlow/SatsFlowRoot.test.tsx b/apps/frontend/src/components/bookkeeper/SatsFlow/SatsFlowRoot.test.tsx index e69de29..32e42b4 100644 --- a/apps/frontend/src/components/bookkeeper/SatsFlow/SatsFlowRoot.test.tsx +++ b/apps/frontend/src/components/bookkeeper/SatsFlow/SatsFlowRoot.test.tsx @@ -0,0 +1,15 @@ +import { render, screen } from '@testing-library/react'; +import SatsFlowRoot from './SatsFlowRoot'; + +jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + useNavigate: jest.fn(), +})); + +describe('Sats Flow component ', () => { + beforeEach(() => render()); + + it('should be in the document', () => { + expect(screen.getByTestId('satsflow-container')).not.toBeEmptyDOMElement(); + }); +}); diff --git a/apps/frontend/src/components/bookkeeper/Terminal/TerminalComponent/TerminalComponent.test.tsx b/apps/frontend/src/components/bookkeeper/Terminal/TerminalComponent/TerminalComponent.test.tsx index e69de29..32010a5 100644 --- a/apps/frontend/src/components/bookkeeper/Terminal/TerminalComponent/TerminalComponent.test.tsx +++ b/apps/frontend/src/components/bookkeeper/Terminal/TerminalComponent/TerminalComponent.test.tsx @@ -0,0 +1,199 @@ +import { screen, fireEvent, waitFor } from '@testing-library/react'; +import TerminalComponent from './TerminalComponent'; +import { getMockStoreData, renderWithMockContext } from '../../../../utilities/test-utilities'; +import useHttp from '../../../../utilities/test-use-http'; + +jest.mock('../../../../utilities/test-use-http', () => ({ + __esModule: true, + default: jest.fn(), +})); + +describe('TerminalComponent', () => { + let providerProps = JSON.parse(JSON.stringify(getMockStoreData())); + let executeSqlMock: jest.Mock; + + beforeEach(() => { + executeSqlMock = jest.fn().mockResolvedValue({ + data: { rows: [{ id: 1, name: 'Alice' }] }, + }); + + (useHttp as jest.Mock).mockReturnValue({ + executeSql: executeSqlMock, + }); + + renderWithMockContext(, { providerProps }); + }); + + it('should render the terminal container', () => { + const terminalContainer = screen.getByTestId('terminal-container'); + expect(terminalContainer).toBeInTheDocument(); + }); + + it('should display initial placeholder in the input field', () => { + const inputField = screen.getByPlaceholderText('Enter SQL query...'); + expect(inputField).toBeInTheDocument(); + }); + + it('should update query state on input change', () => { + const inputField = screen.getByPlaceholderText('Enter SQL query...'); + + fireEvent.change(inputField, { target: { value: 'select * from bkpr_accountevents' } }); + + expect(inputField).toHaveValue('select * from bkpr_accountevents'); + }); + + it('should call executeSql and display result in the output area', async () => { + const inputField = screen.getByPlaceholderText('Enter SQL query...'); + const executeButton = screen.getByText('Execute'); + const mockResponse = { + data: { + rows: [ + [ + 68002, + 'wallet', + 'chain', + 'deposit', + 312500000000, + 0, + 'bcrt', + 1727741999, + '802839b3d0c5073956110b9ddebd2d6d3912a23d01f7c3d620c02bcb35b50328:0', + 602, + null, + null, + null, + null, + null, + null, + null, + ], + [ + 68003, + 'wallet', + 'chain', + 'withdrawal', + 0, + 312500000000, + 'bcrt', + 1727742192, + '802839b3d0c5073956110b9ddebd2d6d3912a23d01f7c3d620c02bcb35b50328:0', + 705, + null, + null, + '6b3aeca4168b1be924322b0906ad8f8068580783011e9d8dd3fc9137eaaeb241', + null, + null, + null, + null, + ], + ], + }, + }; + + executeSqlMock.mockResolvedValueOnce(mockResponse); + + fireEvent.change(inputField, { target: { value: 'select * from bkpr_accountevents' } }); + fireEvent.click(executeButton); + + await waitFor(() => { + const output = screen.getByTestId('terminal-container').textContent; + expect(output).toContain('select * from bkpr_accountevents'); + expect(output).toContain(JSON.stringify(mockResponse.data.rows, null, 2)); + }); + }); + + it('should handle error and display error message in the output area', async () => { + const inputField = screen.getByPlaceholderText('Enter SQL query...'); + const executeButton = screen.getByText('Execute'); + const mockError = new Error('Something went wrong'); + + executeSqlMock.mockRejectedValueOnce(mockError); + + fireEvent.change(inputField, { target: { value: 'select * from bkpr_accountevents' } }); + fireEvent.click(executeButton); + + await waitFor(() => { + const output = screen.getByTestId('terminal-container').textContent; + expect(output).toContain('Error: Something went wrong'); + }); + }); + + it('should open the help link when Help button is clicked', () => { + const helpButton = screen.getByText('Help'); + const windowOpenSpy = jest.spyOn(window, 'open').mockImplementation(() => null); + + fireEvent.click(helpButton); + + expect(windowOpenSpy).toHaveBeenCalledWith( + 'https://docs.corelightning.org/reference/lightning-sql', + '_blank', + ); + }); + + it('should clear the output when Clear button is clicked', async () => { + const inputField = screen.getByPlaceholderText('Enter SQL query...'); + const executeButton = screen.getByText('Execute'); + const clearButton = screen.getByText('Clear'); + + const mockResponse = { + data: { + rows: [ + [ + 68002, + 'wallet', + 'chain', + 'deposit', + 312500000000, + 0, + 'bcrt', + 1727741999, + '802839b3d0c5073956110b9ddebd2d6d3912a23d01f7c3d620c02bcb35b50328:0', + 602, + null, + null, + null, + null, + null, + null, + null, + ], + [ + 68003, + 'wallet', + 'chain', + 'withdrawal', + 0, + 312500000000, + 'bcrt', + 1727742192, + '802839b3d0c5073956110b9ddebd2d6d3912a23d01f7c3d620c02bcb35b50328:0', + 705, + null, + null, + '6b3aeca4168b1be924322b0906ad8f8068580783011e9d8dd3fc9137eaaeb241', + null, + null, + null, + null, + ], + ], + }, + }; + + executeSqlMock.mockResolvedValueOnce(mockResponse); + + fireEvent.change(inputField, { target: { value: 'select * from bkpr_accountevents' } }); + fireEvent.click(executeButton); + + await waitFor(() => { + const output = screen.getByTestId('terminal-container').textContent; + expect(output).toContain('select * from bkpr_accountevents'); + expect(output).toContain(JSON.stringify(mockResponse.data.rows, null, 2)); + }); + + fireEvent.click(clearButton); + + const outputAfterClear = screen.getByTestId('terminal-container').textContent; + expect(outputAfterClear).not.toContain('select * from bkpr_accountevents'); + }); +}); diff --git a/apps/frontend/src/components/bookkeeper/Terminal/TerminalComponent/TerminalComponent.tsx b/apps/frontend/src/components/bookkeeper/Terminal/TerminalComponent/TerminalComponent.tsx index dd98fd2..933ae05 100644 --- a/apps/frontend/src/components/bookkeeper/Terminal/TerminalComponent/TerminalComponent.tsx +++ b/apps/frontend/src/components/bookkeeper/Terminal/TerminalComponent/TerminalComponent.tsx @@ -43,7 +43,7 @@ function TerminalComponent() { }, [output]); return ( -
+
         {output}
       
diff --git a/apps/frontend/src/components/bookkeeper/Terminal/TerminalRoot.test.tsx b/apps/frontend/src/components/bookkeeper/Terminal/TerminalRoot.test.tsx index e69de29..254e4e1 100644 --- a/apps/frontend/src/components/bookkeeper/Terminal/TerminalRoot.test.tsx +++ b/apps/frontend/src/components/bookkeeper/Terminal/TerminalRoot.test.tsx @@ -0,0 +1,20 @@ +import { screen } from '@testing-library/react'; +import TerminalRoot from './TerminalRoot'; +import { getMockStoreData, renderWithMockContext } from '../../../utilities/test-utilities'; + +jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + useNavigate: jest.fn(), +})); + +describe('Terminal component ', () => { + let providerProps = JSON.parse(JSON.stringify(getMockStoreData())); + + beforeEach(() => { + renderWithMockContext(, { providerProps }); + }); + + it('should be in the document', () => { + expect(screen.getByTestId('terminal-container')).not.toBeEmptyDOMElement(); + }); +}); diff --git a/apps/frontend/src/components/bookkeeper/Volume/VolumeGraph/VolumeGraph.test.tsx b/apps/frontend/src/components/bookkeeper/Volume/VolumeGraph/VolumeGraph.test.tsx new file mode 100644 index 0000000..1e0ad8c --- /dev/null +++ b/apps/frontend/src/components/bookkeeper/Volume/VolumeGraph/VolumeGraph.test.tsx @@ -0,0 +1,47 @@ +import * as d3 from 'd3'; +import { + getMockStoreData, + mockVolumeData, + renderWithMockContext, +} from '../../../../utilities/test-utilities'; +import VolumeGraph from './VolumeGraph'; + +// TODO: unable to test due to unable to mock[TypeError: _d3$select$append$app.getBBox is not a function] +describe.skip('Volume Graph component ', () => { + let providerProps = JSON.parse(JSON.stringify(getMockStoreData())); + let container; + + beforeEach(() => { + ({ container } = renderWithMockContext( + , + { providerProps }, + )); + }); + + it('should render the graph', () => { + const svg = container.querySelector('svg'); + expect(svg).toBeInTheDocument(); + }); + + it('renders correctly with empty data', () => { + renderWithMockContext( + , + { + providerProps, + }, + ); + const svg = container.querySelector('svg'); + expect(svg).toBeInTheDocument(); + }); + + it('renders inbound and outbound labels', () => { + const inboundLabel = container.getByText('Inbound'); + const outboundLabel = container.getByText('Outbound'); + + expect(inboundLabel).toBeInTheDocument(); + expect(outboundLabel).toBeInTheDocument(); + }); +}); diff --git a/apps/frontend/src/components/bookkeeper/Volume/VolumeRoot.test.tsx b/apps/frontend/src/components/bookkeeper/Volume/VolumeRoot.test.tsx new file mode 100644 index 0000000..15d7fa6 --- /dev/null +++ b/apps/frontend/src/components/bookkeeper/Volume/VolumeRoot.test.tsx @@ -0,0 +1,15 @@ +import { render, screen } from '@testing-library/react'; +import VolumeRoot from './VolumeRoot'; + +jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + useNavigate: jest.fn(), +})); + +describe('Sats Flow component ', () => { + beforeEach(() => render()); + + it('should be in the document', () => { + expect(screen.getByTestId('volume-container')).not.toBeEmptyDOMElement(); + }); +}); diff --git a/apps/frontend/src/types/lightning-volume.type.ts b/apps/frontend/src/types/lightning-volume.type.ts index 670fb07..507638d 100644 --- a/apps/frontend/src/types/lightning-volume.type.ts +++ b/apps/frontend/src/types/lightning-volume.type.ts @@ -16,7 +16,6 @@ export type Forward = { feeSat: number }; - export type VolumeResultSet = { rows: VolumeRow[] }; diff --git a/apps/frontend/src/utilities/test-use-http.tsx b/apps/frontend/src/utilities/test-use-http.tsx index 2024fe7..5b411f5 100644 --- a/apps/frontend/src/utilities/test-use-http.tsx +++ b/apps/frontend/src/utilities/test-use-http.tsx @@ -1,4 +1,4 @@ -import { mockInvoiceRune, mockStoreData, mockInvoice, mockOffer } from '../utilities/test-utilities'; +import { mockInvoiceRune, mockStoreData, mockInvoice, mockOffer, mockBookkeeperLanding } from '../utilities/test-utilities'; const useHttp = () => { const mockData = { @@ -19,6 +19,11 @@ const useHttp = () => { decodeInvoice: jest.fn((input) => Promise.resolve({data: (input.startsWith('lnb') ? mockInvoice : input.startsWith('lno') ? mockOffer : 'invalid')})), fetchInvoice: jest.fn(() => Promise.resolve()), createInvoiceRune: jest.fn(() => new Promise((resolve) => setTimeout(() => resolve(mockInvoiceRune), 1000))), + getBookkeeperLanding: jest.fn(() => Promise.resolve(mockBookkeeperLanding)), + getBalanceSheet: jest.fn(() => Promise.resolve()), + getSatsFlow: jest.fn(() => Promise.resolve()), + getVolumeData: jest.fn(() => Promise.resolve()), + executeSql: jest.fn(() => Promise.resolve()), userLogin: jest.fn(() => Promise.resolve()), resetUserPassword: jest.fn(() => Promise.resolve()), userLogout: jest.fn(() => Promise.resolve()), diff --git a/apps/frontend/src/utilities/test-utilities.tsx b/apps/frontend/src/utilities/test-utilities.tsx index 60d68bd..9c0aedc 100644 --- a/apps/frontend/src/utilities/test-utilities.tsx +++ b/apps/frontend/src/utilities/test-utilities.tsx @@ -2,6 +2,10 @@ import { render } from '@testing-library/react'; import { AppContext } from '../store/AppContext'; import { ApplicationModes, Units } from './constants'; import { Offer, LightningTransaction, Invoice, BkprTransaction, PeerChannel, Rune } from '../types/lightning-wallet.type'; +import { BookkeeperLandingData } from '../types/lightning-bookkeeper-landing.type'; +import { SatsFlow } from '../types/lightning-satsflow.type'; +import { BalanceSheet } from '../types/lightning-balancesheet.type'; +import { VolumeData } from '../types/lightning-volume.type'; export const getMockStoreData = (replaceKey?: string, replaceValue?: any) => { if (replaceKey && replaceKey !== '' && !!replaceValue) { @@ -11,10 +15,12 @@ export const getMockStoreData = (replaceKey?: string, replaceValue?: any) => { }; export const renderWithMockContext = (ui, { providerProps, ...renderOptions }) => { - return render( + const rendered = render( {ui}, renderOptions ); + + return { ...rendered }; }; export const mockInvoiceRune: Rune = { @@ -33,6 +39,205 @@ export const mockInvoice: Invoice = { created_index: 3 }; +export const mockBookkeeperLanding: BookkeeperLandingData = { + balanceSheetSummary: { + numberOfChannels: 5, + balanceInChannels: 100000, + balanceInWallet: 50000, + }, + satsFlowSummary: { + inflows: 2000, + outflows: 1000, + }, + volumeSummary: { + mostTrafficRoute: 'route1', + leastTrafficRoute: 'route2', + }, +}; + +export const mockBalanceSheetData: BalanceSheet = { + periods: [ + { + periodKey: '2024-12-01', + accounts: [ + { + short_channel_id: '12345x12345x1', + remote_alias: 'Wallet 1', + balance: 500000, + percentage: '50%', + account: 'onchain_wallet', + }, + { + short_channel_id: '67890x67890x2', + remote_alias: 'Channel 1', + balance: 300000, + percentage: '30%', + account: 'channel_1', + }, + { + short_channel_id: '54321x54321x3', + remote_alias: 'Channel 2', + balance: 200000, + percentage: '20%', + account: 'channel_2', + }, + ], + totalBalanceAcrossAccounts: 1000000, + }, + { + periodKey: '2024-11-01', + accounts: [ + { + short_channel_id: '11223x11223x1', + remote_alias: 'Wallet 2', + balance: 600000, + percentage: '60%', + account: 'onchain_wallet', + }, + { + short_channel_id: '44556x44556x2', + remote_alias: 'Channel 3', + balance: 400000, + percentage: '40%', + account: 'channel_3', + }, + ], + totalBalanceAcrossAccounts: 1000000, + }, + ], +}; + +export const mockSatsFlowData: SatsFlow = { + periods: [ + { + periodKey: '2024-12-01', + tagGroups: [ + { + events: [ + { + netInflowSat: 1000, + account: 'onchain_wallet', + tag: 'deposit', + creditSat: 1000, + debitSat: 0, + currency: 'BTC', + timestampUnix: 1698796800, + description: 'Deposit from external wallet', + outpoint: 'txid1234', + txid: 'txid1234', + paymentId: 'paymentId1234', + }, + { + netInflowSat: -200, + account: 'channel_1', + tag: 'channel_fee', + creditSat: 0, + debitSat: 200, + currency: 'BTC', + timestampUnix: 1698797400, + description: 'Channel fee for route1', + outpoint: 'txid5678', + txid: 'txid5678', + paymentId: 'paymentId5678', + }, + ], + tag: 'deposit', + netInflowSat: 800, + creditSat: 1000, + debitSat: 200, + volumeSat: 1200, + }, + { + events: [ + { + netInflowSat: 500, + account: 'onchain_wallet', + tag: 'payment_received', + creditSat: 500, + debitSat: 0, + currency: 'BTC', + timestampUnix: 1698798000, + description: 'Payment received from user X', + outpoint: 'txid91011', + txid: 'txid91011', + paymentId: 'paymentId91011', + }, + ], + tag: 'payment_received', + netInflowSat: 500, + creditSat: 500, + debitSat: 0, + volumeSat: 500, + }, + ], + inflowSat: 1500, + outflowSat: 200, + netInflowSat: 1300, + totalVolumeSat: 1700, + }, + { + periodKey: '2024-11-01', + tagGroups: [ + { + events: [ + { + netInflowSat: 1200, + account: 'onchain_wallet', + tag: 'deposit', + creditSat: 1200, + debitSat: 0, + currency: 'BTC', + timestampUnix: 1696204800, + description: 'Deposit from external wallet', + outpoint: 'txid1234', + txid: 'txid1234', + paymentId: 'paymentId1234', + }, + ], + tag: 'deposit', + netInflowSat: 1200, + creditSat: 1200, + debitSat: 0, + volumeSat: 1200, + }, + ], + inflowSat: 1200, + outflowSat: 0, + netInflowSat: 1200, + totalVolumeSat: 1200, + }, + ], +}; + +export const mockVolumeData: VolumeData = { + forwards: [ + { + inboundChannelSCID: '705x1x0', + inboundPeerId: '03e1da3aa6c14f4e6fac78582d5afbb66c14414ca753d1aa14357d66675e594da3', + inboundPeerAlias: 'WRONGANALYST-11rc1-1199-g9d88ce3', + inboundSat: 332918.432, + outboundChannelSCID: '907x1x0', + outboundPeerId: '031c5439381eae94ca4a343f67999b8c3ee7fcf3ea2648ee1ffe763c8527dbccd9', + outboundPeerAlias: 'BLUEFEED-v23.11rc1-1199-g9d88ce3', + outboundSat: 332915.1, + feeSat: 3.332, + }, + { + inboundChannelSCID: '907x1x0', + inboundPeerId: '031c5439381eae94ca4a343f67999b8c3ee7fcf3ea2648ee1ffe763c8527dbccd9', + inboundPeerAlias: 'BLUEFEED-v23.11rc1-1199-g9d88ce3', + inboundSat: 1231.244, + outboundChannelSCID: '705x1x0', + outboundPeerId: '03e1da3aa6c14f4e6fac78582d5afbb66c14414ca753d1aa14357d66675e594da3', + outboundPeerAlias: 'WRONGANALYST-11rc1-1199-g9d88ce3', + outboundSat: 1231.231, + feeSat: 0.013, + }, + ], + totalOutboundSat: 334146.331, + totalFeeSat: 3.3449999999999998, +}; + export const mockOffer: Offer = { offer_id: "f6f68cefe4ab28548fc746a13b671eceff11ce47339dfa6cb32a831bf14d08ff", active: true,