From 9130ec7e145559a0a5d9324bb001604f66047493 Mon Sep 17 00:00:00 2001 From: dealerweb Date: Wed, 30 Sep 2026 23:33:11 +0200 Subject: [PATCH] fix: wrap a filter condition's values in the expanded rule view The values of a multi-value condition were glued together as one unbreakable run - the space around each "or" was a margin, not a character the line could break at - so a rule with several addresses ran out of its card. Each value is now an item of its own: they wrap between each other under the first value, with "or" leading the next line and still inside the condition's chip, apart from the "and"/"or" between conditions. A single value too long for any line, in a condition or an action, breaks inside itself instead of overflowing. --- .../__tests__/filter-rule-summary.test.tsx | 70 +++++++++++++++++++ components/settings/filter-settings.tsx | 41 +++++------ 2 files changed, 91 insertions(+), 20 deletions(-) create mode 100644 components/settings/__tests__/filter-rule-summary.test.tsx diff --git a/components/settings/__tests__/filter-rule-summary.test.tsx b/components/settings/__tests__/filter-rule-summary.test.tsx new file mode 100644 index 000000000..246c75b93 --- /dev/null +++ b/components/settings/__tests__/filter-rule-summary.test.tsx @@ -0,0 +1,70 @@ +import { render, screen } from '@testing-library/react'; +import { describe, expect, it } from 'vitest'; +import React from 'react'; +import { VisualRuleSummary } from '../filter-settings'; +import type { FilterRule } from '@/lib/jmap/sieve-types'; + +const rule = (overrides: Partial): FilterRule => ({ + id: 'r1', + name: 'Rule', + enabled: true, + matchType: 'all', + conditions: [], + actions: [], + stopProcessing: false, + ...overrides, +}); + +/** The value items in the chip of the condition on `field` (next-intl returns keys). */ +const valuesOf = (field: string) => { + const chip = screen.getByText(`condition_fields.${field}`).parentElement!; + return { chip, values: [...chip.lastElementChild!.children].map((el) => el.textContent) }; +}; + +// The expanded view wraps a condition's values between each other, so each +// value is its own item and each "or" leads the value after it - a wrapped +// line then starts with "or", and no value is split from its "or". +describe('filter rule summary (expanded view)', () => { + it('keeps the values of one condition apart, each "or" leading the value after it', () => { + render(); + expect(valuesOf('from').values).toEqual([ + '“ReportingEMail@autoscout24.com”', + 'or “gebrauchtwagen.expert”', + 'or “noreply@webmobil24.com”', + ]); + }); + + it('keeps "or" inside the condition, apart from the "and" between conditions', () => { + render(); + const from = valuesOf('from'); + expect(from.chip).toHaveTextContent('or “b@example.com”'); + expect(from.chip).not.toHaveTextContent('and'); + expect(valuesOf('body').values).toEqual(['“Jochen Seith”']); + expect(from.chip.contains(screen.getByText('and'))).toBe(false); + }); + + it('shows no value for a condition that has none', () => { + render(); + const chip = screen.getByText('condition_fields.attachment').parentElement!; + expect([...chip.children].map((el) => el.textContent)).toEqual([ + 'condition_fields.attachment', + 'comparators.has_any', + ]); + }); +}); diff --git a/components/settings/filter-settings.tsx b/components/settings/filter-settings.tsx index aa5040aeb..2e3c9ba05 100644 --- a/components/settings/filter-settings.tsx +++ b/components/settings/filter-settings.tsx @@ -86,7 +86,7 @@ function RuleSummary({ rule }: { rule: FilterRule }) { ); } -function VisualRuleSummary({ rule }: { rule: FilterRule }) { +export function VisualRuleSummary({ rule }: { rule: FilterRule }) { const t = useTranslations("settings.filters"); const joiner = rule.matchType === "all" ? t("and") : t("or"); @@ -106,23 +106,24 @@ function VisualRuleSummary({ rule }: { rule: FilterRule }) { {i > 0 && ( {joiner} )} - - {field} - {comparator} + + {field} + {comparator} {!(c.field === "attachment" && c.comparator === "has_any") && ( - - {Array.isArray(c.value) - ? c.value.map((v, k) => ( - - {k > 0 && ( - - {t("or")} - - )} - “{v}” - - )) - : <>“{c.value}”} + // The values of one condition stay in its chip, where "or" + // cannot be mistaken for the joiner between conditions. They + // wrap between each other under the first value, the "or" + // leading the next line; a single value too long for any + // line breaks inside itself instead of leaving the card. + + {(Array.isArray(c.value) ? c.value : [c.value]).map((v, k) => ( + + {k > 0 && ( + {t("or")} + )} + “{v}” + + ))} )} @@ -143,9 +144,9 @@ function VisualRuleSummary({ rule }: { rule: FilterRule }) { {i > 0 && ( › )} - - {action} - {a.value && “{a.value}”} + + {action} + {a.value && “{a.value}”} );