Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
0e2bf66
fix(e2e): phase 4 e2e fixes – contact api, reports, vat, bank helpers
africanitem Jan 25, 2026
d425af2
Fix bank account transaction types dropdown to show correct transacti…
africanitem Jan 28, 2026
a9639d7
Fix getBankTransactionTypes endpoint - clear lazy-loaded relationships
africanitem Jan 29, 2026
c2b9332
Fix Money Received transaction type and expense reducer mutations
africanitem Jan 29, 2026
d73ccf5
fix: journal, receipt, opening-balance APIs and frontend issues
africanitem Feb 1, 2026
1d29da1
fix: show backend error when invoice posting fails (line item has no …
africanitem Feb 1, 2026
db74c4d
fix: quotation view, send, receipt detail, and related backend/fronte…
africanitem Feb 2, 2026
559f6eb
chore: close #507 and #508 - transaction linking tests implemented
africanitem Feb 2, 2026
cc3a6c8
chore: close epic #512 and tasks - invoice-to-payment workflow e2e tests
africanitem Feb 2, 2026
73eba1d
style: apply prettier formatting
africanitem Feb 2, 2026
859ca80
fix: resolve eslint errors blocking pre-push
africanitem Feb 2, 2026
caef6ad
style: format expense screen
africanitem Feb 2, 2026
faeae51
Merge pull request #1 from africanitem/phase-4-banking-reporting-e2e-…
africanitem Feb 2, 2026
7a58330
ci: trigger tests for pr #667
africanitem Feb 2, 2026
4163fee
Merge branch 'develop' into develop
MohsinHashmi-DataInn Feb 3, 2026
0517aeb
Merge branch 'develop' into develop
MohsinHashmi-DataInn Feb 3, 2026
8d0b74a
ci: fix gitleaks arm64 binary, use maven wrapper in workflows
africanitem Feb 3, 2026
eae0036
fix: backend ci and messageutil, remove ide-specific paths from code
africanitem Feb 3, 2026
3ab8909
ci(security): run codeql on ubuntu-latest (x64)
africanitem Feb 3, 2026
dba2f91
Merge upstream develop into develop
africanitem Feb 3, 2026
4538878
fix(devcontainer): allow playwright install to fail in multi-platform…
africanitem Feb 3, 2026
9c02c7a
test(contact): fix DetailContact tests - mock getContactById to retur…
africanitem Feb 4, 2026
0496488
Merge upstream/develop into develop (sync with SimpleAccounts)
africanitem Feb 4, 2026
ba20c82
test(contact): fix DetailContact and CreateContact tests for CI
africanitem Feb 5, 2026
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
Original file line number Diff line number Diff line change
Expand Up @@ -172,11 +172,9 @@ describe('CreateContact Component', () => {
it('should render create contact form', async () => {
renderComponent();

// Wait for form to load
await waitFor(() => {
expect(screen.getByText(/create.*contact/i)).toBeInTheDocument();
});
});
const heading = await screen.findByText(/create.*contact/i, {}, { timeout: 15000 });
expect(heading).toBeInTheDocument();
}, 20000);

it('should display name input field', async () => {
renderComponent();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ vi.mock('../../actions', () => ({
checkValidation: () => () => Promise.resolve({ status: 200, data: { exist: false } }),
}));

// Module mock: thunk must return Promise so dispatch(thunk) resolves when connect() is used (inline data; vi.mock is hoisted)
vi.mock('./actions', () => ({
getContactById: () => () =>
Promise.resolve({
Expand All @@ -55,6 +56,32 @@ vi.mock('./actions', () => ({
lastName: 'Doe',
email: 'john.doe@example.com',
isActive: true,
taxTreatmentId: '',
billingEmail: '',
city: '',
countryId: '',
addressLine1: '',
postZipCode: '',
stateId: '',
billingTelephone: '',
fax: '',
shippingCity: '',
shippingCountryId: '',
addressLine2: '',
shippingPostZipCode: '',
shippingStateId: '',
shippingTelephone: '',
shippingFax: '',
contactType: '',
currencyCode: '',
middleName: '',
website: '',
mobileNumber: '',
organization: '',
telephone: '',
vatRegistrationNumber: '',
isRegisteredForVat: false,
isBillingAndShippingAddressSame: false,
},
}),
updateContact: () => () => Promise.resolve({ status: 200 }),
Expand Down Expand Up @@ -113,26 +140,55 @@ vi.mock('screens/contact/sections', () => ({
// Import component AFTER mocks are set up
import DetailContact from '../screen';

// Mock prop action objects
// Contact data returned by getContactById - component expects this shape to leave loading state
const mockContactData = {
contactId: 1,
firstName: 'John',
lastName: 'Doe',
email: 'john.doe@example.com',
isActive: true,
taxTreatmentId: '',
billingEmail: '',
city: '',
countryId: '',
addressLine1: '',
postZipCode: '',
stateId: '',
billingTelephone: '',
fax: '',
shippingCity: '',
shippingCountryId: '',
addressLine2: '',
shippingPostZipCode: '',
shippingStateId: '',
shippingTelephone: '',
shippingFax: '',
contactType: '',
currencyCode: '',
middleName: '',
website: '',
mobileNumber: '',
organization: '',
telephone: '',
vatRegistrationNumber: '',
isRegisteredForVat: false,
isBillingAndShippingAddressSame: false,
};

// Mock prop action objects. getContactById must return a Promise (not a thunk) so component's .then() runs and setLoading(false).
const mockActions = {
getContactById: vi.fn(
() => () =>
Promise.resolve({
status: 200,
data: {
contactId: 1,
firstName: 'John',
lastName: 'Doe',
email: 'john.doe@example.com',
isActive: true,
},
})
getContactById: vi.fn(() =>
Promise.resolve({
status: 200,
data: mockContactData,
})
),
getTaxTreatment: vi.fn(() => () => Promise.resolve({ status: 200, data: [] })),
getCountryList: vi.fn(() => () => Promise.resolve({ data: [] })),
getStateList: vi.fn(() => () => Promise.resolve({ data: [] })),
getCityList: vi.fn(() => () => Promise.resolve({ data: [] })),
getContactTypeList: vi.fn(() => () => Promise.resolve({ data: [] })),
getInvoicesCountContact: vi.fn(() => Promise.resolve({ data: 0 })),
updateContact: vi.fn(() => () => Promise.resolve({ status: 200 })),
deleteContact: vi.fn(() => () => Promise.resolve({ status: 200 })),
checkValidation: vi.fn(() => () => Promise.resolve({ status: 200, data: { exist: false } })),
Expand Down Expand Up @@ -217,12 +273,10 @@ describe('DetailContact Component', () => {
it('should render detail contact form', async () => {
renderComponent();

// Wait for form to load - use getAllByText since "Update Contact" appears in breadcrumb and header
await waitFor(() => {
const updateContactElements = screen.getAllByText('Update Contact');
expect(updateContactElements.length).toBeGreaterThan(0);
});
});
// Wait for form to load (button appears after getContactById resolves); avoid relying on localized heading
const updateButton = await screen.findByRole('button', { name: /update/i }, { timeout: 15000 });
expect(updateButton).toBeInTheDocument();
}, 20000);

it('should display form inputs', async () => {
renderComponent();
Expand All @@ -245,44 +299,31 @@ describe('DetailContact Component', () => {
it('should display update button', async () => {
renderComponent();

await waitFor(() => {
const updateButton = screen.getByRole('button', { name: /update/i });
expect(updateButton).toBeInTheDocument();
});
const updateButton = await screen.findByRole('button', { name: /update/i }, { timeout: 15000 });
expect(updateButton).toBeInTheDocument();
});

it('should display cancel button', async () => {
renderComponent();

await waitFor(() => {
const cancelButton = screen.getByRole('button', { name: /cancel/i });
expect(cancelButton).toBeInTheDocument();
});
const cancelButton = await screen.findByRole('button', { name: /cancel/i }, { timeout: 15000 });
expect(cancelButton).toBeInTheDocument();
});

it('should navigate back on cancel', async () => {
// Clear mockNavigate before this test
mockNavigate.mockClear();

renderComponent();

// Wait for component to load - use getAllByText since "Update Contact" appears in breadcrumb and header
await waitFor(() => {
const updateContactElements = screen.getAllByText('Update Contact');
expect(updateContactElements.length).toBeGreaterThan(0);
});

const cancelButton = screen.getByRole('button', { name: /cancel/i });
const cancelButton = await screen.findByRole('button', { name: /cancel/i }, { timeout: 15000 });
fireEvent.click(cancelButton);

// Cancel button uses navigate('/admin/master/contact') from useNavigate hook
await waitFor(
() => {
expect(mockNavigate).toHaveBeenCalledWith('/admin/master/contact');
},
{ timeout: 2000 }
{ timeout: 3000 }
);
});
}, 20000);

it('should display address components', async () => {
renderComponent();
Expand Down
Loading