Merge branch 'integreat-fix-errors' into staging

This commit is contained in:
2026-05-24 21:54:54 -07:00
6 changed files with 5522 additions and 74 deletions

View File

@@ -1,14 +1,14 @@
import { test, expect } from '@playwright/test';
async function openEditModal(page: any) {
async function openEditModal(page: any, transactionIndex: number = 0) {
// Navigate to transactions page
await page.goto('/transaction2');
// Wait for the table to load
await page.waitForSelector('table tbody tr');
// Find and click the edit button for the test transaction
const editButton = page.locator('button[hx-get*="/transaction2/"][hx-get*="/edit"]').first();
// Find and click the edit button for the specified transaction
const editButton = page.locator('button[hx-get*="/transaction2/"][hx-get*="/edit"]').nth(transactionIndex);
await editButton.click();
// Wait for the modal to open
@@ -137,6 +137,49 @@ async function addNewAccount(page: any) {
await page.waitForTimeout(500);
}
async function setAccountLocation(page: any, rowIndex: number, location: string) {
const row = await findAccountRow(page, rowIndex);
const locationSelect = row.locator('select[name*="location"]').first();
// If the option doesn't exist, add it (for testing invalid locations)
const optionExists = await locationSelect.locator(`option[value="${location}"]`).count() > 0;
if (!optionExists) {
await locationSelect.evaluate((el: HTMLSelectElement, value: string) => {
const option = document.createElement('option');
option.value = value;
option.textContent = value;
el.appendChild(option);
}, location);
}
await locationSelect.selectOption(location);
await locationSelect.dispatchEvent('change');
await page.waitForTimeout(300);
}
async function getAccountLocation(page: any, rowIndex: number): Promise<string> {
const row = await findAccountRow(page, rowIndex);
const locationSelect = row.locator('select[name*="location"]').first();
return await locationSelect.inputValue();
}
async function removeAllAccounts(page: any) {
const allRows = page.locator('#account-grid-body tbody tr');
const rowCount = await allRows.count();
for (let i = rowCount - 1; i >= 0; i--) {
const row = allRows.nth(i);
const hasAccountInput = await row.locator('input[name*="transaction-account/account"]').count() > 0;
if (hasAccountInput) {
// Click the X button to remove (it's an <a> tag, not a <button>)
const removeButton = row.locator('a').first();
await removeButton.click();
// Wait for the Alpine.js removal animation (500ms + buffer)
await page.waitForTimeout(700);
}
}
}
async function saveTransaction(page: any) {
// Submit the form directly instead of clicking the button
// The Done button might not have type="submit"
@@ -175,6 +218,44 @@ async function toggleToDollarMode(page: any) {
test.describe.configure({ mode: 'serial' });
test.describe('Transaction Edit Shared Location', () => {
test('should spread Shared location to client locations on save and display correctly on reopen', async ({ page }) => {
// Use the second transaction to avoid interfering with other tests
const transactionIndex = 1;
// Step 1: Open edit modal and add an account with Shared location
await openEditModal(page, transactionIndex);
// Add a new account row
await addNewAccount(page);
// Select the account
await selectAccountFromTypeahead(page, 0, 'Test');
// Set location to Shared
await setAccountLocation(page, 0, 'Shared');
// Set amount to $200 (the full transaction amount for the second transaction)
await setAccountAmount(page, 0, '200');
// Save the transaction
await saveTransaction(page);
// Step 2: Re-open and verify location is not "Shared" but the actual client location
await openEditModal(page, transactionIndex);
// Wait for accounts to load
await page.waitForTimeout(500);
// Get the location of the first account
const location = await getAccountLocation(page, 0);
// The location should be the actual client location ("DT" in test data), not "Shared"
expect(location).not.toBe('Shared');
expect(location).toBe('DT');
});
});
test.describe('Transaction Edit Full Workflow', () => {
test('should code transaction with vendor using percentage, then split 50/50, then switch to dollars', async ({ page }) => {
// Step 1: Open edit modal and code with 100% to one account
@@ -246,14 +327,15 @@ test.describe('Transaction Edit Full Workflow', () => {
test.describe('Transaction Edit Validation', () => {
test('should show validation error when account totals do not match transaction amount', async ({ page }) => {
await openEditModal(page);
// Use the third transaction to avoid interference from other tests
await openEditModal(page, 2);
// Stay in dollar mode (default)
// Add an account
await addNewAccount(page);
await selectAccountFromTypeahead(page, 0, 'Test');
// Set amount to $50 (which doesn't match the $100 transaction)
// Set amount to $50 (which doesn't match the $300 transaction)
await setAccountAmount(page, 0, '50');
// Try to save - this should fail because $50 != $100
@@ -279,6 +361,12 @@ test.describe('Transaction Edit Validation', () => {
const amountInput = page.locator('input[name*="transaction-account/amount"]').first();
const value = await amountInput.inputValue();
expect(parseFloat(value)).toBeCloseTo(50.0, 1);
// Verify the user-friendly error message is displayed
const errorElement = page.locator('#form-errors .error-content');
await expect(errorElement).toBeVisible();
const errorText = await errorElement.textContent();
expect(errorText).toContain('The total of your expense accounts ($50.00) must equal the transaction amount ($300.00)');
});
});