diff --git a/client/src/components/Budget/CostsPanel.summary.test.tsx b/client/src/components/Budget/CostsPanel.summary.test.tsx index e42bb2d16..eba1d89cd 100644 --- a/client/src/components/Budget/CostsPanel.summary.test.tsx +++ b/client/src/components/Budget/CostsPanel.summary.test.tsx @@ -123,4 +123,20 @@ describe('CostsPanel: summary footers and won amounts (#2604)', () => { await user.type(shares[0], '1000.5'); expect(shares[0].value).toBe('1000.'); }); + + it('FE-COSTS-2604-005: edit and delete on a row say which expense they act on', async () => { + mount([owedByBob()]); + await screen.findByText('Museum'); + expect(screen.getByRole('button', { name: 'Edit: Museum' })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: 'Delete: Museum' })).toBeInTheDocument(); + }); + + it('FE-COSTS-2604-006: a settle-up row and its button name who pays whom', async () => { + mount([owedByBob()], { + flows: [{ from: { user_id: 2, username: 'bob' }, to: { user_id: 1, username: 'alice' }, amount: 20 }], + }); + await screen.findByText('Museum'); + expect(screen.getByRole('button', { name: /^Settle: bob → you, 20,00\s€$/ })).toBeInTheDocument(); + expect(screen.getAllByText('bob → you').length).toBeGreaterThan(0); + }); }); diff --git a/client/src/components/Budget/CostsPanel.test.tsx b/client/src/components/Budget/CostsPanel.test.tsx index 3d53b216a..e727b3115 100644 --- a/client/src/components/Budget/CostsPanel.test.tsx +++ b/client/src/components/Budget/CostsPanel.test.tsx @@ -52,7 +52,7 @@ describe('CostsPanel — settlements in the ledger', () => { await screen.findByText('Dinner') await screen.findByText('Payment') // The payment row exposes an inline undo (no need to open a separate History modal). - expect(screen.getByRole('button', { name: 'Undo' })).toBeInTheDocument() + expect(screen.getByRole('button', { name: /^Undo(:|$)/ })).toBeInTheDocument() }) it('records a manual payment via the Add payment button', async () => { @@ -242,7 +242,7 @@ describe('CostsPanel — settlements in the ledger', () => { render() await screen.findByText('Lunch') - await user.click(screen.getAllByRole('button', { name: 'Edit' })[0]) + await user.click(screen.getAllByRole('button', { name: /^Edit(:|$)/ })[0]) await user.click(await screen.findByRole('button', { name: 'Save' })) await waitFor(() => expect(patched).toBeTruthy()) @@ -453,7 +453,7 @@ describe('CostsPanel — settlements in the ledger', () => { render() await screen.findByText('Hotel') - await user.click(screen.getByRole('button', { name: 'Edit' })) + await user.click(screen.getByRole('button', { name: /^Edit(:|$)/ })) // Nobody paid this expense — reopening it must not silently reselect "You". expect(await screen.findByRole('radio', { name: 'No one paid yet' })).toBeChecked() @@ -579,7 +579,7 @@ describe('CostsPanel — settlements in the ledger', () => { render() await screen.findByText('Dinner') - await user.click(screen.getByRole('button', { name: 'Edit' })) + await user.click(screen.getByRole('button', { name: /^Edit(:|$)/ })) // Loading used to be payers.find(...), which silently dropped the second payer. // Seeded with two decimals since #2175, localized for EUR. @@ -768,7 +768,7 @@ describe('CostsPanel — settlements in the ledger', () => { render() await screen.findByText('Payment') - await user.click(screen.getByRole('button', { name: 'Edit' })) + await user.click(screen.getByRole('button', { name: /^Edit(:|$)/ })) // The stored USD amount comes back as-is (padded to two decimals, #2175), // not silently reread as euros. @@ -984,7 +984,7 @@ describe('CostsPanel — settle up', () => { await screen.findByText('45,00 €') // The "you're owed" card names who still owes me. expect(screen.getByText("You're owed")).toBeInTheDocument() - fireEvent.click(screen.getByRole('button', { name: 'Settle' })) + fireEvent.click(screen.getByRole('button', { name: /^Settle(:|$)/ })) await waitFor(() => expect(posted).toHaveLength(1)) expect(posted[0]).toMatchObject({ from_user_id: 2, to_user_id: 1, amount: 45, currency: 'EUR' }) @@ -1018,7 +1018,7 @@ describe('CostsPanel — settle up', () => { server.use(http.post('/api/trips/1/budget/settlements', () => HttpResponse.json({ error: 'no' }, { status: 500 }))) mount([], { flows: [flows[0]] }) - fireEvent.click(await screen.findByRole('button', { name: 'Settle' })) + fireEvent.click(await screen.findByRole('button', { name: /^Settle(:|$)/ })) await waitFor(() => expect(addToast).toHaveBeenCalledWith('Unknown error', 'error', undefined)) addToast.mockClear() @@ -1037,10 +1037,10 @@ describe('CostsPanel — settle up', () => { })) mount([], { settlements: [{ id: 7, from_user_id: 2, to_user_id: 1, amount: 30, created_at: '2025-06-16 10:00:00' }] }) - fireEvent.click(await screen.findByRole('button', { name: 'Undo' })) + fireEvent.click(await screen.findByRole('button', { name: /^Undo(:|$)/ })) await waitFor(() => expect(deleted).toBe(1)) - fireEvent.click(screen.getByRole('button', { name: 'Undo' })) + fireEvent.click(screen.getByRole('button', { name: /^Undo(:|$)/ })) await waitFor(() => expect(addToast).toHaveBeenCalledWith('Unknown error', 'error', undefined)) delete window.__addToast }) @@ -1252,7 +1252,7 @@ describe('CostsPanel — expense rows', () => { mount([dinner()]) await screen.findByText('Dinner') - fireEvent.click(screen.getByRole('button', { name: 'Delete' })) + fireEvent.click(screen.getByRole('button', { name: /^Delete(:|$)/ })) await waitFor(() => expect(addToast).toHaveBeenCalledWith('Unknown error', 'error', undefined)) // The optimistic removal is rolled back by the store. @@ -1274,9 +1274,9 @@ describe('CostsPanel — expense rows', () => { expect(screen.queryByRole('button', { name: 'Add expense' })).not.toBeInTheDocument() expect(screen.queryByRole('button', { name: 'Settle up' })).not.toBeInTheDocument() expect(screen.queryByRole('button', { name: 'Add payment' })).not.toBeInTheDocument() - expect(screen.queryByRole('button', { name: 'Settle' })).not.toBeInTheDocument() - expect(screen.queryByRole('button', { name: 'Delete' })).not.toBeInTheDocument() - expect(screen.queryByRole('button', { name: 'Undo' })).not.toBeInTheDocument() + expect(screen.queryByRole('button', { name: /^Settle(:|$)/ })).not.toBeInTheDocument() + expect(screen.queryByRole('button', { name: /^Delete(:|$)/ })).not.toBeInTheDocument() + expect(screen.queryByRole('button', { name: /^Undo(:|$)/ })).not.toBeInTheDocument() }) }) @@ -1353,7 +1353,7 @@ describe('CostsPanel — payment modal', () => { })) mount([], { settlements: [{ id: 7, from_user_id: 2, to_user_id: 1, amount: 30, created_at: '2025-06-16 10:00:00' }] }) - await user.click(await screen.findByRole('button', { name: 'Edit' })) + await user.click(await screen.findByRole('button', { name: /^Edit(:|$)/ })) const amount = await screen.findByPlaceholderText('0,00') await user.clear(amount) await user.type(amount, '12,50') @@ -1428,7 +1428,7 @@ describe('CostsPanel — payment modal', () => { })) mount([], { settlements: [{ id: 7, from_user_id: 2, to_user_id: 1, amount: 30, settled_at: '2025-06-10', created_at: '2025-06-16 10:00:00' }] }) - await user.click(await screen.findByRole('button', { name: 'Edit' })) + await user.click(await screen.findByRole('button', { name: /^Edit(:|$)/ })) await user.click(screen.getByRole('button', { name: 'Save' })) await waitFor(() => expect(put).toBeTruthy()) @@ -1441,7 +1441,7 @@ describe('CostsPanel — payment modal', () => { const user = userEvent.setup() mount([], { settlements: [{ id: 7, from_user_id: 2, to_user_id: 1, amount: 30, settled_at: '2025-06-10', created_at: '2025-06-16 10:00:00' }] }) - await user.click(await screen.findByRole('button', { name: 'Edit' })) + await user.click(await screen.findByRole('button', { name: /^Edit(:|$)/ })) const save = screen.getByRole('button', { name: 'Save' }) expect(save).toBeEnabled() @@ -1477,7 +1477,7 @@ describe('CostsPanel — expense modal', () => { mount([expense({ id: 150, name: 'Market run', category: 'groceries', total_price: 30, note, payers: [{ user_id: 1, amount: 30 }], members: [{ user_id: 1, username: 'alice', amount: 15 }, { user_id: 2, username: 'bob', amount: 15 }] })]) await screen.findByText('Market run') - await user.click(screen.getByRole('button', { name: 'Edit' })) + await user.click(screen.getByRole('button', { name: /^Edit(:|$)/ })) expect(await screen.findByDisplayValue('Apples')).toBeInTheDocument() expect(screen.getByDisplayValue('Cake')).toBeInTheDocument() @@ -1499,7 +1499,7 @@ describe('CostsPanel — expense modal', () => { mount([expense({ id: 151, name: 'Market run', category: 'groceries', total_price: 30, note: 'TICKETJSON:{oops', payers: [{ user_id: 1, amount: 30 }], members: [{ user_id: 1, username: 'alice' }] })]) await screen.findByText('Market run') - await user.click(screen.getByRole('button', { name: 'Edit' })) + await user.click(screen.getByRole('button', { name: /^Edit(:|$)/ })) expect(await screen.findByRole('button', { name: /Add item/i })).toBeInTheDocument() expect(screen.queryByPlaceholderText('Item name')).not.toBeInTheDocument() @@ -1520,7 +1520,7 @@ describe('CostsPanel — expense modal', () => { })]) await screen.findByText('Dinner') - await user.click(screen.getByRole('button', { name: 'Edit' })) + await user.click(screen.getByRole('button', { name: /^Edit(:|$)/ })) // Custom shares reopen padded to two decimals (#2175), localized for EUR. expect((await screen.findByDisplayValue('70,00')).tagName).toBe('INPUT') @@ -1801,7 +1801,7 @@ describe('CostsPanel — remaining paths', () => { mount([dinner()]) await screen.findByText('Dinner') - fireEvent.click(screen.getByRole('button', { name: 'Delete' })) + fireEvent.click(screen.getByRole('button', { name: /^Delete(:|$)/ })) await waitFor(() => expect(deleted).toBe(true)) expect(screen.queryByText('Dinner')).not.toBeInTheDocument() @@ -1872,7 +1872,7 @@ describe('CostsPanel — remaining paths', () => { ) render() - await user.click(await screen.findByRole('button', { name: 'Edit' })) + await user.click(await screen.findByRole('button', { name: /^Edit(:|$)/ })) // No symbol is known, so the code itself prefixes the amount and labels the option. await screen.findByPlaceholderText('0.00') expect(screen.getAllByText('XBT').length).toBeGreaterThanOrEqual(2) @@ -1890,7 +1890,7 @@ describe('CostsPanel — remaining paths', () => { mount([expense({ id: 240, name: 'Mining rig', category: 'other', total_price: 2, currency: 'XBT', payers: [{ user_id: 1, amount: 2 }], members: [{ user_id: 1, username: 'alice' }] })]) await screen.findByText('Mining rig') - await user.click(screen.getByRole('button', { name: 'Edit' })) + await user.click(screen.getByRole('button', { name: /^Edit(:|$)/ })) // XBT is unknown to the locale map → dot separator, two decimals (#2175). expect(await screen.findByDisplayValue('2.00')).toBeInTheDocument() @@ -2038,7 +2038,7 @@ describe('CostsPanel — split modes and guests', () => { })]) await screen.findByText('Hotel') - await user.click(screen.getByRole('button', { name: 'Edit' })) + await user.click(screen.getByRole('button', { name: /^Edit(:|$)/ })) // Nobody paid, so switching to multi-payer seeds me as the only payer. await user.click(await screen.findByRole('button', { name: 'Multiple people paid' })) @@ -2159,11 +2159,11 @@ describe('CostsPanel — decimal padding on edit (#2175)', () => { ]) await screen.findByText('Coffee') - await user.click(screen.getAllByRole('button', { name: 'Edit' })[0]) + await user.click(screen.getAllByRole('button', { name: /^Edit(:|$)/ })[0]) expect(await screen.findByDisplayValue('4,90')).toBeInTheDocument() await user.click(screen.getByRole('button', { name: 'Cancel' })) - await user.click(screen.getAllByRole('button', { name: 'Edit' })[1]) + await user.click(screen.getAllByRole('button', { name: /^Edit(:|$)/ })[1]) expect(await screen.findByDisplayValue('5,00')).toBeInTheDocument() }) @@ -2243,7 +2243,7 @@ describe('CostsPanel — negative amounts (#2176)', () => { // Not "Unfinished": the refund has its recipient recorded. expect(within(row).queryByText('Unfinished')).toBeNull() - await user.click(screen.getByRole('button', { name: 'Edit' })) + await user.click(screen.getByRole('button', { name: /^Edit(:|$)/ })) // The negative payer reopens selected instead of resetting to "Nobody", // and the total is seeded signed and padded. expect(await screen.findByRole('radio', { name: 'You' })).toBeChecked() @@ -2540,7 +2540,7 @@ describe('CostsPanel: a bill entered in the display currency (#2525)', () => { mount([hotel()]) await screen.findByText('Aparthotel Silver') - await user.click(screen.getByRole('button', { name: 'Edit' })) + await user.click(screen.getByRole('button', { name: /^Edit(:|$)/ })) expect(await screen.findByDisplayValue('801.76')).toBeInTheDocument() // Same currency as the list, so the dialog said nothing, and 801.76 there beside // $791.57 in the list looked like two different expenses. @@ -2610,7 +2610,7 @@ describe('CostsPanel: a bill entered in the display currency (#2525)', () => { // The list reads the missing currency as the trip's own. expect(screen.getByText(/100,00\s€ → \$115\.51/)).toBeInTheDocument() - await user.click(screen.getByRole('button', { name: 'Edit' })) + await user.click(screen.getByRole('button', { name: /^Edit(:|$)/ })) expect(await screen.findByDisplayValue('100,00')).toBeInTheDocument() // Seeding the display currency here would label 100 euro as 100 dollars, and a // plain save would then store it that way. @@ -2676,7 +2676,7 @@ describe('CostsPanel: a bill the server cannot convert', () => { // Only the currency the settlement is missing, at the rate the browser holds. await waitFor(() => expect(frozen).toEqual([{ fallback_fx: { base: 'AUD', rates: { VND: 18241.3 } } }])) // The settlement is read again and Bob's half can be settled. - expect(await screen.findByRole('button', { name: 'Settle' })).toBeInTheDocument() + expect(await screen.findByRole('button', { name: /^Settle(:|$)/ })).toBeInTheDocument() expect(screen.getAllByText(/244\.50/).length).toBeGreaterThan(0) // Nothing on the page counts the bill as 8,920,000 dollars any more. expect(screen.queryByText(/\$8,920,000/)).toBeNull() @@ -2719,7 +2719,7 @@ describe('CostsPanel: a bill the server cannot convert', () => { ) render() - fireEvent.click(await screen.findByRole('button', { name: 'Settle' })) + fireEvent.click(await screen.findByRole('button', { name: /^Settle(:|$)/ })) await waitFor(() => expect(posted).toHaveLength(1)) // The transfer freezes at the rate the settlement was labelled with, so it cancels its // flow to the cent. diff --git a/client/src/components/Budget/CostsPanel.tsx b/client/src/components/Budget/CostsPanel.tsx index b877e1c95..349eb3a39 100644 --- a/client/src/components/Budget/CostsPanel.tsx +++ b/client/src/components/Budget/CostsPanel.tsx @@ -109,6 +109,7 @@ export default function CostsPanel({ tripId, tripMembers = [] }: CostsPanelProps const idx = people.findIndex(p => p.id === id) return SPLIT_COLORS[(idx >= 0 ? idx : 0) % SPLIT_COLORS.length].gradient }, [people]) + const transferLabel = (fromId: number, toId: number) => `${personName(fromId)} → ${toId === me ? t('costs.youLower') : personName(toId)}` const initial = useCallback((id: number) => id === me ? t('costs.youShort') : nameOf(id).charAt(0).toUpperCase(), [me, nameOf, t]) const fmt = useCallback((v: number, c = base) => formatMoney(v, c, locale), [base, locale]) @@ -447,14 +448,15 @@ export default function CostsPanel({ tripId, tripMembers = [] }: CostsPanelProps
{flows.map((f, i) => (
- +
+ {transferLabel(f.from.user_id, f.to.user_id)}
{fmt(f.amount)} - {canEdit && } + {canEdit && }
))} @@ -603,7 +605,7 @@ export default function CostsPanel({ tripId, tripMembers = [] }: CostsPanelProps * mobile card has always done it this way, and out here the buttons stop * competing with the amount for the right edge of the card. */ - function RowActions({ onEdit, onDelete, deleteLabel }: { onEdit: () => void; onDelete: () => void; deleteLabel: string }) { + function RowActions({ onEdit, onDelete, deleteLabel, name }: { onEdit: () => void; onDelete: () => void; deleteLabel: string; name: string }) { // No `background` here on purpose: an inline value would beat the :hover // rule in the stylesheet block below, which is how the first attempt at // this silently did nothing. @@ -615,10 +617,10 @@ export default function CostsPanel({ tripId, tripMembers = [] }: CostsPanelProps return (
- + - +
) @@ -741,7 +743,7 @@ export default function CostsPanel({ tripId, tripMembers = [] }: CostsPanelProps
- {canEdit && RowActions({ onEdit: () => { setEditing(e); setModalOpen(true) }, onDelete: () => handleDelete(e.id), deleteLabel: t('common.delete') })} + {canEdit && RowActions({ onEdit: () => { setEditing(e); setModalOpen(true) }, onDelete: () => handleDelete(e.id), deleteLabel: t('common.delete'), name: e.name })} ) } @@ -803,6 +805,7 @@ export default function CostsPanel({ tripId, tripMembers = [] }: CostsPanelProps function SettlementRow({ s }: { s: Settlement }) { // Booked in the trip currency like an expense, so it is explained the same way. const line = paymentLineOf(s, ctx, settled(s)) + const who = `${personName(s.from_user_id)} → ${personName(s.to_user_id)}` return (
@@ -812,10 +815,10 @@ export default function CostsPanel({ tripId, tripMembers = [] }: CostsPanelProps {t('costs.payment')} {line && {fmt(line.entered.amount, line.entered.currency)} → {fmt(line.into.amount, line.into.currency)}}
- +
- {personName(s.from_user_id)} → {personName(s.to_user_id)} + {who}
@@ -829,10 +832,10 @@ export default function CostsPanel({ tripId, tripMembers = [] }: CostsPanelProps {canEdit && (
- + - +
)} @@ -935,7 +938,6 @@ export default function CostsPanel({ tripId, tripMembers = [] }: CostsPanelProps {value} ) - const transferLabel = (fromId: number, toId: number) => `${personName(fromId)} → ${toId === me ? t('costs.youLower') : personName(toId)}` // Capped and scrollable: a long trip's expense list would otherwise push the // rest of the sidebar off the screen every time a name is tapped. const listCls = { display: 'flex', flexDirection: 'column', gap: 5, marginTop: 6, maxHeight: 180, overflowY: 'auto' } as const diff --git a/client/src/components/Journey/JourneyShareSection.test.tsx b/client/src/components/Journey/JourneyShareSection.test.tsx index 19aea90a6..4430658bf 100644 --- a/client/src/components/Journey/JourneyShareSection.test.tsx +++ b/client/src/components/Journey/JourneyShareSection.test.tsx @@ -100,6 +100,9 @@ describe('JourneyShareSection', () => { expect(timeline.className).toContain('bg-zinc-900') expect(screen.getByRole('button', { name: 'Map' }).className).toContain('bg-zinc-900') expect(screen.getByRole('button', { name: 'Gallery' }).className).not.toContain('bg-zinc-900') + // The state is said, not only shown (#2604). + expect(timeline).toHaveAttribute('aria-pressed', 'true') + expect(screen.getByRole('button', { name: 'Gallery' })).toHaveAttribute('aria-pressed', 'false') expect(screen.getByText(`${window.location.origin}/public/journey/tok-abc`)).toBeInTheDocument() }) diff --git a/client/src/components/Journey/JourneyShareSection.tsx b/client/src/components/Journey/JourneyShareSection.tsx index 24a38bef3..58651d316 100644 --- a/client/src/components/Journey/JourneyShareSection.tsx +++ b/client/src/components/Journey/JourneyShareSection.tsx @@ -48,6 +48,7 @@ export default function JourneyShareSection({ journeyId }: { journeyId: number } ].map(({ key, label, icon: Icon }) => ( - @@ -707,6 +707,7 @@ function PaymentRow({ settlement, ctx, base, locale, t, personName, canEdit, onE // A transfer without a currency was entered in the display currency, not the trip's. const amount = paymentAmount(settlement, ctx) const line = paymentLineOf(settlement, ctx, amount) + const who = `${personName(settlement.from_user_id)} → ${personName(settlement.to_user_id)}` return (
@@ -716,7 +717,7 @@ function PaymentRow({ settlement, ctx, base, locale, t, personName, canEdit, onE
{t('costs.payment')}
-
{personName(settlement.from_user_id)} → {personName(settlement.to_user_id)}
+
{who}
{line && (
{formatMoney(line.entered.amount, line.entered.currency, locale)} {'→'} {formatMoney(line.into.amount, line.into.currency, locale)} @@ -732,10 +733,10 @@ function PaymentRow({ settlement, ctx, base, locale, t, personName, canEdit, onE {canEdit && (
- -
diff --git a/client/tests/unit/mobile/trip/MCostSheet.test.tsx b/client/tests/unit/mobile/trip/MCostSheet.test.tsx index b6d2b0f51..f4e6bb9a9 100644 --- a/client/tests/unit/mobile/trip/MCostSheet.test.tsx +++ b/client/tests/unit/mobile/trip/MCostSheet.test.tsx @@ -252,7 +252,7 @@ describe('MCostSheet', () => { expect(screen.queryByText(/live rate/)).not.toBeInTheDocument() fireEvent.click(screen.getByRole('button', { name: 'EUR €' })) - fireEvent.change(screen.getByPlaceholderText('...'), { target: { value: 'USD' } }) + fireEvent.change(screen.getByPlaceholderText('Search'), { target: { value: 'USD' } }) fireEvent.click(screen.getByRole('button', { name: 'USD $' })) expect(screen.getByText('$100.00')).toBeInTheDocument() diff --git a/client/tests/unit/mobile/trip/MCostsTab.test.tsx b/client/tests/unit/mobile/trip/MCostsTab.test.tsx index af71a0f0c..76ace1fcf 100644 --- a/client/tests/unit/mobile/trip/MCostsTab.test.tsx +++ b/client/tests/unit/mobile/trip/MCostsTab.test.tsx @@ -381,10 +381,16 @@ describe('MCostsTab', () => { expect(dayTrigger).toHaveAttribute('aria-expanded', 'true') }) + it('FE-MOB-COSTT-024b: edit and delete on a row name the expense (#2604)', async () => { + await renderTab() + expect(within(rowOf('Ramen')).getByRole('button', { name: 'common.edit: Ramen' })).toBeInTheDocument() + expect(within(rowOf('Ramen')).getByRole('button', { name: 'common.delete: Ramen' })).toBeInTheDocument() + }) + it('FE-MOB-COSTT-024: opens the edit sheet from a row and reloads on save', async () => { const p = planner() await renderTab(p) - fireEvent.click(within(rowOf('Ramen')).getByRole('button', { name: 'common.edit' })) + fireEvent.click(within(rowOf('Ramen')).getByRole('button', { name: /^common\.edit(:|$)/ })) expect(screen.getByText('sheet editing 11')).toBeInTheDocument() fireEvent.click(screen.getByText('sheet save')) @@ -396,7 +402,7 @@ describe('MCostsTab', () => { it('FE-MOB-COSTT-025: deletes an expense after confirming and refreshes the settlement', async () => { const p = planner() await renderTab(p) - fireEvent.click(within(rowOf('Ramen')).getByRole('button', { name: 'common.delete' })) + fireEvent.click(within(rowOf('Ramen')).getByRole('button', { name: /^common\.delete(:|$)/ })) const dialog = screen.getByRole('dialog', { name: 'costs.confirm.deleteTitle' }) expect(within(dialog).getByText('costs.confirm.deleteBody:Ramen')).toBeInTheDocument() fireEvent.click(within(dialog).getByText('common.delete')) @@ -408,7 +414,7 @@ describe('MCostsTab', () => { const p = planner() vi.mocked(p.tripActions.deleteBudgetItem).mockRejectedValueOnce(new Error('offline')) await renderTab(p) - fireEvent.click(within(rowOf('Ramen')).getByRole('button', { name: 'common.delete' })) + fireEvent.click(within(rowOf('Ramen')).getByRole('button', { name: /^common\.delete(:|$)/ })) fireEvent.click(within(screen.getByRole('dialog')).getByText('common.delete')) await waitFor(() => expect(p.toast.error).toHaveBeenCalledWith('common.unknownError')) }) @@ -500,8 +506,8 @@ describe('MCostsTab', () => { it('FE-MOB-COSTT-032: a read-only member gets no add, edit or delete affordances', async () => { await renderTab(planner({ can: vi.fn(() => false) as unknown as TripPlanner['can'] })) expect(screen.queryByRole('button', { name: 'costs.addPayment' })).not.toBeInTheDocument() - expect(screen.queryByRole('button', { name: 'common.edit' })).not.toBeInTheDocument() - expect(screen.queryByRole('button', { name: 'common.delete' })).not.toBeInTheDocument() + expect(screen.queryByRole('button', { name: /^common\.edit(:|$)/ })).not.toBeInTheDocument() + expect(screen.queryByRole('button', { name: /^common\.delete(:|$)/ })).not.toBeInTheDocument() expect(within(cardOf('Ramen')).getByRole('button', { pressed: false })).toBeDisabled() }) @@ -533,7 +539,7 @@ describe('MCostsTab', () => { it('FE-MOB-COSTT-035: cancelling the confirm keeps the expense', async () => { const p = planner() await renderTab(p) - fireEvent.click(within(rowOf('Ramen')).getByRole('button', { name: 'common.delete' })) + fireEvent.click(within(rowOf('Ramen')).getByRole('button', { name: /^common\.delete(:|$)/ })) fireEvent.click(within(screen.getByRole('dialog')).getByText('common.cancel')) expect(p.tripActions.deleteBudgetItem).not.toHaveBeenCalled() expect(screen.getByText('Ramen')).toBeInTheDocument() @@ -682,7 +688,7 @@ describe('MCostsTab', () => { const update = vi.spyOn(budgetApi, 'updateSettlement').mockResolvedValue({}) await renderTab() - fireEvent.click(within(rowOf('costs.payment')).getByRole('button', { name: 'common.edit' })) + fireEvent.click(within(rowOf('costs.payment')).getByRole('button', { name: /^common\.edit(:|$)/ })) const dialog = screen.getByRole('dialog', { name: 'costs.editPayment' }) expect(within(dialog).getByDisplayValue('25.00')).toBeInTheDocument() @@ -700,7 +706,7 @@ describe('MCostsTab', () => { const del = vi.spyOn(budgetApi, 'deleteSettlement').mockResolvedValue({ success: true }) await renderTab() - fireEvent.click(within(rowOf('costs.payment')).getByRole('button', { name: 'costs.undo' })) + fireEvent.click(within(rowOf('costs.payment')).getByRole('button', { name: /^costs\.undo(:|$)/ })) expect(del).toHaveBeenCalledWith(7, 502) await waitFor(() => expect(settlementBases).toHaveLength(2)) }) @@ -718,7 +724,7 @@ describe('MCostsTab', () => { expect(within(row).getByText('£10.00 → $20.00')).toBeInTheDocument() expect(within(row).getByText('$20.00')).toBeInTheDocument() - fireEvent.click(within(row).getByRole('button', { name: 'common.edit' })) + fireEvent.click(within(row).getByRole('button', { name: /^common\.edit(:|$)/ })) const dialog = screen.getByRole('dialog', { name: 'costs.editPayment' }) expect(within(dialog).getByDisplayValue('10.00')).toBeInTheDocument() expect(within(dialog).getByRole('button', { name: /GBP/ })).toBeInTheDocument() diff --git a/shared/src/i18n/ja/budget.ts b/shared/src/i18n/ja/budget.ts index f0f183980..45dca4f75 100644 --- a/shared/src/i18n/ja/budget.ts +++ b/shared/src/i18n/ja/budget.ts @@ -42,7 +42,7 @@ const budget: TranslationStrings = { 'budget.categoriesLabel': 'カテゴリ', 'costs.you': '自分', 'costs.youShort': 'Y', - 'costs.youLower': 'you', + 'costs.youLower': '自分', 'costs.youOwe': '支払う額', 'costs.youOweSub': '他の人に支払うべき額', 'costs.youreOwed': '受け取る額',