From 3e08965e48f1c6406afe62fcf8167dc3a5c0f149 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bj=C3=B6rn=20Fromme?=
+ Zum Portal +
+ {% endblock %} diff --git a/config/email_texts.yaml b/config/email_texts.yaml new file mode 100644 index 0000000..8c546ec --- /dev/null +++ b/config/email_texts.yaml @@ -0,0 +1,128 @@ +# Default wording of the admin-editable mails (App\Config\EmailTextKey), used until an +# admin overrides a mail under /admin/system/email-text. Removing that override is what +# "Zurücksetzen" does, so a wording fix made here reaches every mail nobody has edited. +# +# Parsed on demand by App\Config\EmailTextCatalog. This file is NOT loaded by the +# container, which is why it does not live in config/packages/. +# +# The text is plain text, never markup: {platzhalter} tokens are substituted by +# App\Email\EmailTextRenderer after escaping, *Sternchen* make text bold (write \* for a +# literal one), a blank line starts a new paragraph, and bare URLs and mail addresses +# become links by themselves. A placeholder without a value prints "-". +# +# Every mail closes with the same "Zum Portal" button, rendered by email/layout.html.twig. + +texts: + application_accepted: + label: 'Bewerbung angenommen' + placeholders: ['destination', 'specialAgreements', 'contractUploadDeadlineDays'] + subject: 'Dein Einsatz wurde angenommen' + headline: 'Herzlichen Glückwunsch!' + body: |- + *Dein Einsatz {destination} wurde angenommen!* + + Zusätzliche Absprachen: {specialAgreements} + + Schau einmal in das My E&P-Team Portal, um die Einsatzdetails einzusehen und deinen Honorarvertrag zu unterschreiben. + + Mit dem Erhalt dieser E-Mail hast du {contractUploadDeadlineDays} Tage Zeit deinen Einsatz zu bestätigen, indem du den unterschriebenen Vertrag hochlädst. Ist diese Frist vergangen, wird der Einsatz für deine Teamkolleg:innen freigeschaltet. + + Schön, dass du dabei bist und ganz viel Spaß in den Bergen! 😊 + + Bitte wende dich bei Rückfragen an team@ep-reisen.de. + + application_rejected: + label: 'Bewerbung abgelehnt' + placeholders: ['destination'] + subject: 'Deine Bewerbung wurde abgelehnt' + headline: 'Hallo aus Köln,' + body: |- + leider konnte deine Bewerbung für den Einsatz '{destination}' nicht angenommen werden. Dies kann unterschiedliche Gründe haben, wie bspw. Unpassende Buszustiege, zu späte Bewerbung, unpassendes Jobprofil, zu viele andere Bewerbungen o.Ä. + + Bewirb dich doch gerne auf weitere Einsätze, vielleicht hast du diesmal Glück! 😊 + + assignment_called_off: + label: 'Reise abgesagt' + placeholders: ['destination'] + subject: 'Die Reise zu deinem Einsatz wurde abgesagt' + headline: 'Hallo aus Köln,' + body: |- + leider mussten wir die Reise zu deinem Einsatz '{destination}' absagen, sodass dein Einsatz nicht stattfinden kann. + + Bitte entschuldige die Umstände! + + Bei Fragen melde dich gerne unter team@ep-reisen.de + + disposition_called_off: + label: 'Einsatz abgesagt' + placeholders: ['destination', 'reason'] + subject: 'Dein Einsatz wurde abgesagt' + headline: 'Hallo aus Köln,' + body: |- + leider mussten wir deinen Einsatz '{destination}' absagen, sodass dein Einsatz nicht stattfinden kann: + + {reason} + + Bei Fragen melde dich gerne unter team@ep-reisen.de + + document_rejected: + label: 'Dokument abgelehnt' + # documentType arrives capitalised, because subject and body both open with it. + placeholders: ['destination', 'documentType', 'comment'] + subject: '{documentType} wurde abgelehnt' + headline: 'Hallo aus Köln,' + body: |- + {documentType} wurde für den Einsatz {destination} leider mit folgender Begründung abgelehnt: + + {comment} + + Überarbeite das Dokument bitte und lade es erneut hoch. + + reminder_contract_upload: + label: 'Erinnerung: Honorarvertrag hochladen' + placeholders: ['destination'] + subject: 'Reminder: Fehlende Dokumente' + headline: 'Hallo aus Köln,' + body: |- + bitte denke daran, deinen ausgefüllten Honorarvertrag für folgenden Einsatz hochzuladen. Vorher können wir nicht fest mit dir planen und behalten uns vor den Einsatz anderweitig zu vergeben. + + *{destination}* + + reminder_invoice_upload: + label: 'Erinnerung: Honorarnote hochladen' + placeholders: ['destination', 'invoiceUploadDeadlineDays'] + subject: 'Reminder: Fehlende Dokumente' + headline: 'Hallo aus Köln,' + body: |- + vielen Dank für deinen Einsatz {destination}. Bitte lade deine Honorarnote innerhalb der nächsten {invoiceUploadDeadlineDays} Tage hoch, damit wir dein Honorar bearbeiten können. Solltest du deine Honorarnote zu spät hochladen, können wir sie eventuell nicht mehr annehmen. + + reminder_disposition: + label: 'Erinnerung: Einsatz steht bevor' + placeholders: ['destination', 'jobProfile', 'product', 'dateFrom', 'dateTo', 'diffInDays', 'invoiceUploadDeadlineDays'] + subject: 'Reminder: Dein Einsatz {destination}' + headline: 'Hallo aus Köln,' + body: |- + dein Einsatz als {jobProfile}, {product} vom {dateFrom} bis {dateTo} beginnt *in {diffInDays} Tagen*. + + Wir hoffen, dass du ich schon freust und wünschen dir viel Spaß und Erfolg! Finale Infos erhältst du, falls noch nicht geschehen, ein paar Tage vor deinem Einsatz von deinen zuständigen Haus- oder Reisemanager:innen. + + Bitte denk daran, deine *ausgefüllte Honorarnote bis zu {invoiceUploadDeadlineDays} Tage nach deinem Einsatz* in My E&P Team hochzuladen. + + Liebe Grüße, + dein Team Personalabteilung + +# Descriptions shown next to the form fields. They describe the placeholder itself rather +# than any one mail, so they are shared: a name means the same thing wherever it appears. +placeholders: + destination: 'Einsatz, z. B. "12.01.25 - 19.01.25 Skireise"' + specialAgreements: 'Zusätzliche Absprachen zum Einsatz' + contractUploadDeadlineDays: 'Frist zum Hochladen des Honorarvertrags in Tagen' + reason: 'Begründung der Absage' + documentType: 'Art des Dokuments, z. B. "Dein Honorarvertrag"' + comment: 'Begründung der Ablehnung' + invoiceUploadDeadlineDays: 'Frist zum Hochladen der Honorarnote in Tagen' + jobProfile: 'Jobprofil des Einsatzes' + product: 'Produkt der Reise' + dateFrom: 'Beginn des Einsatzes' + dateTo: 'Ende des Einsatzes' + diffInDays: 'Tage bis zum Beginn des Einsatzes' diff --git a/config/services.yaml b/config/services.yaml index 4a8ed06..cd4c912 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -9,6 +9,13 @@ parameters: teamer_inactive_period: '-2 years' + # Deadlines quoted to teamers in the transactional mails. They are passed to the mail + # texts as placeholders rather than typed into the wording, so what an admin edits can + # never drift from the period the application actually works with. + contract_upload_deadline_days: 7 + # Also drives when the reminder goes out: three days before the deadline expires. + invoice_upload_deadline_days: 14 + # Messenger transport the mails of a teamer mailing are queued on. An X-Bus-Transport # header wins over the routing in messenger.yaml (see SendersLocator::getSenders), # so this is what keeps a mailing off the shared queue - and what has to be switched @@ -58,6 +65,11 @@ services: $xmlExport: '@xml_export.storage' $xmlDump: '@xml_dump.storage' $teamerInactivePeriod: '%teamer_inactive_period%' + $contractUploadDeadlineDays: '%contract_upload_deadline_days%' + $invoiceUploadDeadlineDays: '%invoice_upload_deadline_days%' + # Only the path enters the container - the file itself is parsed on demand by + # App\Config\EmailTextCatalog, since its contents matter on few requests. + $emailTextsFile: '%kernel.project_dir%/config/email_texts.yaml' $mailingBusTransport: '%mailing_bus_transport%' App\: diff --git a/migrations/Version20260817074008.php b/migrations/Version20260817074008.php new file mode 100644 index 0000000..670a3d2 --- /dev/null +++ b/migrations/Version20260817074008.php @@ -0,0 +1,26 @@ +addSql('CREATE TABLE email_text (id INT AUTO_INCREMENT NOT NULL, template_key VARCHAR(64) NOT NULL, subject VARCHAR(255) NOT NULL, headline VARCHAR(255) DEFAULT NULL, body LONGTEXT NOT NULL, created_by VARCHAR(255) DEFAULT NULL, updated_by VARCHAR(255) DEFAULT NULL, created_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', updated_at DATETIME DEFAULT NULL COMMENT \'(DC2Type:datetime_immutable)\', UNIQUE INDEX uniq_email_text_template_key (template_key), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); + } + + public function down(Schema $schema): void + { + $this->addSql('DROP TABLE email_text'); + } +} diff --git a/src/Config/EmailTextCatalog.php b/src/Config/EmailTextCatalog.php new file mode 100644 index 0000000..0006764 --- /dev/null +++ b/src/Config/EmailTextCatalog.php @@ -0,0 +1,75 @@ +|null + */ + private ?array $definitions = null; + + public function __construct( + private readonly string $emailTextsFile, + ) { + } + + /** + * @return arraywith its own margins, so keeping real paragraphs preserves + // the spacing the hand-written templates had - a single
full of
would
+ // collapse it. nl2br then handles the breaks inside a block, which is what
+ // multi-line values such as a rejection comment need.
+ $paragraphs = [];
+
+ foreach (preg_split('/\R{2,}/', $body) as $block) {
+ $block = trim($block);
+
+ if ('' === $block) {
+ continue;
+ }
+
+ $paragraphs[] = '
'.nl2br($block).'
'; + } + + return $this->linkify(implode("\n", $paragraphs)); + } + + /** + * @param array{{ '{' ~ name ~ '}' }} – {{ description }}
+ - ausgegeben.
+ | + E-Mail + | ++ Betreff + | ++ Status + | ++ |
|---|---|---|---|
| + {{ definition.label }} + | ++ {{ emailText ? emailText.subject : definition.defaultSubject }} + | ++ {% if emailText %} + Angepasst + {% else %} + Standard + {% endif %} + | +
+
+ {% if emailText %}
+
+ {% endif %}
+
+
+
+ |
+
with padding-bottom:16px - pb-4 is the same spacing, so the preview matches. #} +
+ Zum Portal +
+[name] dargestellt und werden beim Versand durch
+ die echten Werte ersetzt. Logo und Fußzeile der E-Mail sind hier nicht abgebildet.
+ - - Dein Einsatz {{ disposition.assignment.destination }} - wurde angenommen! - -
- {% if disposition.specialAgreements %} -
- Zusätzliche Absprachen:
-
- {{ disposition.specialAgreements | nl2br }}
-
- Schau einmal in das My E&P-Team Portal, - um die Einsatzdetails einzusehen und deinen Honorarvertrag zu unterschreiben. -
-- Mit dem Erhalt dieser E-Mail hast du 7 Tage Zeit deinen Einsatz zu bestätigen, indem du den unterschriebenen - Vertrag hochlädst. Ist diese Frist vergangen, wird der Einsatz für deine Teamkolleg:innen freigeschaltet. -
-- Schön, dass du dabei bist und ganz viel Spaß in den Bergen! 😊 -
-- Bitte wende dich bei Rückfragen an team@ep-reisen.de. -
-- - Zum Portal - -
-{% endblock %} diff --git a/templates/email/application_rejected.html.twig b/templates/email/application_rejected.html.twig deleted file mode 100644 index f723662..0000000 --- a/templates/email/application_rejected.html.twig +++ /dev/null @@ -1,20 +0,0 @@ -{% extends 'email/layout.html.twig' %} - -{% block body %} -- leider konnte deine Bewerbung für den Einsatz '{{ application.assignment.destination }}' nicht angenommen - werden. Dies kann unterschiedliche Gründe haben, wie bspw. Unpassende Buszustiege, zu späte Bewerbung, - unpassendes Jobprofil, zu viele andere Bewerbungen o.Ä. -
-- Bewirb dich doch gerne auf weitere Einsätze, vielleicht hast du diesmal Glück! 😊 -
-- - Zum Portal - -
-{% endblock %} \ No newline at end of file diff --git a/templates/email/assignment_called_off.html.twig b/templates/email/assignment_called_off.html.twig deleted file mode 100644 index 2110ca8..0000000 --- a/templates/email/assignment_called_off.html.twig +++ /dev/null @@ -1,22 +0,0 @@ -{% extends 'email/layout.html.twig' %} - -{% block body %} -- leider mussten wir die Reise zu deinem Einsatz '{{ assignment.destination }}' absagen, sodass dein Einsatz - nicht stattfinden kann. -
-- Bitte entschuldige die Umstände! -
-- Bei Fragen melde dich gerne unter team@ep-reisen.de -
-- - Zum Portal - -
-{% endblock %} \ No newline at end of file diff --git a/templates/email/contract_uploaded.html.twig b/templates/email/contract_uploaded.html.twig index f59c0f2..0cf3f3c 100644 --- a/templates/email/contract_uploaded.html.twig +++ b/templates/email/contract_uploaded.html.twig @@ -7,9 +7,4 @@Es wurde ein neuer Honorarvertrag von {{ disposition.teamer }} hochgeladen, schau ihn dir an.
-- - Zum Portal - -
{% endblock %} \ No newline at end of file diff --git a/templates/email/datev_invoice.html.twig b/templates/email/datev_invoice.html.twig index fa55547..ec70ce3 100644 --- a/templates/email/datev_invoice.html.twig +++ b/templates/email/datev_invoice.html.twig @@ -9,3 +9,5 @@ wurde freigegeben. {% endblock %} + +{% block button %}{% endblock %} diff --git a/templates/email/disposition_called_off.html.twig b/templates/email/disposition_called_off.html.twig deleted file mode 100644 index e7f84e7..0000000 --- a/templates/email/disposition_called_off.html.twig +++ /dev/null @@ -1,22 +0,0 @@ -{% extends 'email/layout.html.twig' %} - -{% block body %} -- leider mussten wir deinen Einsatz '{{ assignment.destination }}' absagen, sodass dein Einsatz - nicht stattfinden kann: -
-- {{ reason | nl2br }} -
-- Bei Fragen melde dich gerne unter team@ep-reisen.de -
-- - Zum Portal - -
-{% endblock %} diff --git a/templates/email/document_rejected.html.twig b/templates/email/document_rejected.html.twig deleted file mode 100644 index 6015a05..0000000 --- a/templates/email/document_rejected.html.twig +++ /dev/null @@ -1,22 +0,0 @@ -{% extends 'email/layout.html.twig' %} - -{% block body %} -- leider wurde {{ documentTypeLabel }} für den Einsatz {{ disposition.assignment.destination}} mit folgender - Begründung abgelehnt: -
-- {{ comment | nl2br }} -
-- Überarbeite das Dokument bitte und lade es erneut hoch. -
-- - Zum Portal - -
-{% endblock %} \ No newline at end of file diff --git a/templates/email/generic.html.twig b/templates/email/generic.html.twig new file mode 100644 index 0000000..0d78bbd --- /dev/null +++ b/templates/email/generic.html.twig @@ -0,0 +1,15 @@ +{% extends 'email/layout.html.twig' %} + +{# Every string on `text` was escaped by App\Email\EmailTextRenderer before the , + and tags in it were introduced, which is what makes |raw safe here. Do not
+ pass anything into this template that did not come out of that renderer.
+ The closing button is rendered by the layout. #}
+{% block body %}
+ {% if text.headline is not empty %}
+
Es wurde eine neue Honorarnote von {{ disposition.teamer }} hochgeladen, gib diese bitte frei.
-
- Zum Portal
-
-
+ {{ text.headline | raw }}
+
+ {% endif %}
+
+ {{ text.bodyHtml | raw }}
+{% endblock %}
diff --git a/templates/email/invoice_uploaded.html.twig b/templates/email/invoice_uploaded.html.twig
index 403ee47..ac8203a 100644
--- a/templates/email/invoice_uploaded.html.twig
+++ b/templates/email/invoice_uploaded.html.twig
@@ -7,9 +7,4 @@
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Doloribus, eveniet!
{% endblock %} + {% block button %} ++ Zum Portal +
+ {% endblock %} diff --git a/templates/email/reminder_contract_upload.html.twig b/templates/email/reminder_contract_upload.html.twig deleted file mode 100644 index f8dad08..0000000 --- a/templates/email/reminder_contract_upload.html.twig +++ /dev/null @@ -1,23 +0,0 @@ -{% extends 'email/layout.html.twig' %} - -{% block body %} -- bitte denke daran, deinen ausgefüllten Honorarvertrag für folgenden Einsatz hochzuladen. Vorher können wir nicht - fest mit dir planen und behalten uns vor den Einsatz anderweitig zu vergeben. -
-- - - {{ disposition.assignment.destination}} - . - -
-- - Zum Portal - -
-{% endblock %} \ No newline at end of file diff --git a/templates/email/reminder_disposition.html.twig b/templates/email/reminder_disposition.html.twig deleted file mode 100644 index cf32735..0000000 --- a/templates/email/reminder_disposition.html.twig +++ /dev/null @@ -1,31 +0,0 @@ -{% extends 'email/layout.html.twig' %} - -{% block body %} - {% set destination = disposition.assignment.destination %} -- dein Einsatz als {{ disposition.assignment.jobProfile.name }}, {{ destination.product }} vom - {{ destination.dateFrom | date('d.m.Y') }} bis {{ destination.dateTo | date('d.m.Y') }} beginnt - in {{ diffInDays }} Tagen. -
-- Wir hoffen, dass du ich schon freust und wünschen dir viel Spaß und Erfolg! Finale Infos erhältst du, falls noch - nicht geschehen, ein paar Tage vor deinem Einsatz von deinen zuständigen Haus- oder Reisemanager:innen. -
-- Bitte denk daran, deine ausgefüllte Honorarnote bis zu 14 Tage nach deinem Einsatz in My - E&P Team hochzuladen. -
-
- Liebe Grüße,
-
- dein Team Personalabteilung
-
- - Zum Portal - -
-{% endblock %} \ No newline at end of file diff --git a/templates/email/reminder_invoice_upload.html.twig b/templates/email/reminder_invoice_upload.html.twig deleted file mode 100644 index 7c1639d..0000000 --- a/templates/email/reminder_invoice_upload.html.twig +++ /dev/null @@ -1,17 +0,0 @@ -{% extends 'email/layout.html.twig' %} - -{% block body %} -- vielen Dank für deinen Einsatz {{ disposition.assignment.destination}}. Bitte lade deine Honorarnote innerhalb - der nächsten 14 Tage hoch, damit wir dein Honorar bearbeiten können. Solltest du deine Honorarnote zu spät - hochladen, können wir sie eventuell nicht mehr annehmen. -
-- - Zum Portal - -
-{% endblock %} \ No newline at end of file diff --git a/templates/email/reminder_pending_feedback.html.twig b/templates/email/reminder_pending_feedback.html.twig index e4888bb..1ac3942 100644 --- a/templates/email/reminder_pending_feedback.html.twig +++ b/templates/email/reminder_pending_feedback.html.twig @@ -4,9 +4,4 @@- - Zum Portal - -
{% endblock %} \ No newline at end of file diff --git a/templates/email/teamer_mailing.html.twig b/templates/email/teamer_mailing.html.twig index 7d02483..e0f181b 100644 --- a/templates/email/teamer_mailing.html.twig +++ b/templates/email/teamer_mailing.html.twig @@ -4,9 +4,4 @@{{ message | nl2br }}
-- - Zum Portal - -
{% endblock %} diff --git a/tests/Config/EmailTextCatalogTest.php b/tests/Config/EmailTextCatalogTest.php new file mode 100644 index 0000000..0390c23 --- /dev/null +++ b/tests/Config/EmailTextCatalogTest.php @@ -0,0 +1,98 @@ +catalog(); + + foreach (EmailTextKey::cases() as $key) { + $definition = $catalog->get($key); + + $this->assertSame($key, $definition->key); + $this->assertNotSame('', $definition->label); + $this->assertNotSame('', $definition->defaultSubject); + $this->assertNotSame('', $definition->defaultBody); + } + } + + public function testDefaultsOnlyUseDeclaredPlaceholders(): void + { + $catalog = $this->catalog(); + + foreach (EmailTextKey::cases() as $key) { + $definition = $catalog->get($key); + $declared = $definition->getPlaceholderNames(); + + preg_match_all( + '/\{(\w+)\}/', + $definition->defaultSubject.' '.$definition->defaultHeadline.' '.$definition->defaultBody, + $matches + ); + $used = array_unique($matches[1]); + + $this->assertSame( + [], + array_diff($used, $declared), + sprintf('Default wording of "%s" uses undeclared placeholders', $key->value) + ); + } + } + + /** + * A placeholder nobody prints is either a leftover or a hint that the wording lost a + * detail it used to carry. + */ + public function testEveryDeclaredPlaceholderIsUsedByTheDefaults(): void + { + $catalog = $this->catalog(); + + foreach (EmailTextKey::cases() as $key) { + $definition = $catalog->get($key); + $text = $definition->defaultSubject.' '.$definition->defaultHeadline.' '.$definition->defaultBody; + + foreach ($definition->getPlaceholderNames() as $name) { + $this->assertStringContainsString( + '{'.$name.'}', + $text, + sprintf('Placeholder "%s" of "%s" is declared but never used', $name, $key->value) + ); + } + } + } + + public function testEveryPlaceholderIsDescribedForTheAdmin(): void + { + $catalog = $this->catalog(); + + foreach (EmailTextKey::cases() as $key) { + foreach ($catalog->get($key)->placeholders as $name => $description) { + $this->assertNotSame('', trim($description), sprintf('Placeholder "%s" has no description', $name)); + } + } + } + + private function catalog(): EmailTextCatalog + { + self::bootKernel(); + + return self::getContainer()->get(EmailTextCatalog::class); + } +} diff --git a/tests/Email/EmailTextMailRenderingTest.php b/tests/Email/EmailTextMailRenderingTest.php new file mode 100644 index 0000000..bb4c43c --- /dev/null +++ b/tests/Email/EmailTextMailRenderingTest.php @@ -0,0 +1,87 @@ +get(EmailTextCatalog::class); + $definition = $catalog->get($key); + + $placeholders = []; + + foreach ($definition->getPlaceholderNames() as $name) { + $placeholders[$name] = '['.$name.']'; + } + + // Renders the delivered wording: the test environment has no database, and the + // defaults are what a mail falls back to anyway. + $repository = $this->createMock(EmailTextRepository::class); + $repository->method('findByKey')->willReturn(null); + + $text = (new EmailTextRenderer($catalog, $repository)) + ->render($key, $placeholders) + ; + + $email = $container->get(Mailer::class)->create(['text' => $text], [ + 'from' => 'test@example.com', + 'to' => 'teamer@example.com', + 'subject' => $text->subject, + 'template' => 'email/generic.html.twig', + 'subject_parameters' => [], + 'attachments' => [], + 'transport' => null, + 'bus_transport' => null, + ]); + + $html = $email->getHtmlBody(); + + $this->assertNotEmpty($html); + $this->assertStringContainsString($text->headline, $html); + // The layout closes every mail with the same button, pointing at the site root + // rather than a deep link so that a client's link preview cannot trip the + // unauthorized-access logging. + $this->assertStringContainsString('class="button">Zum Portal', $html); + $this->assertStringNotContainsString('/teamer/disposition/detail', $html); + + // Every placeholder the mail declares has to reach the rendered body, or the + // wording lost a detail somewhere between catalogue and template. + foreach ($definition->getPlaceholderNames() as $name) { + $this->assertStringContainsString( + '['.$name.']', + $html.$email->getSubject(), + sprintf('Placeholder "%s" does not show up in the rendered mail', $name) + ); + } + } + + public static function provideKeys(): iterable + { + foreach (EmailTextKey::cases() as $key) { + yield $key->value => [$key]; + } + } +} diff --git a/tests/Email/EmailTextRendererTest.php b/tests/Email/EmailTextRendererTest.php new file mode 100644 index 0000000..40b612b --- /dev/null +++ b/tests/Email/EmailTextRendererTest.php @@ -0,0 +1,211 @@ +render( + $this->emailText(subject: 'Neuer Betreff', body: 'Neuer Text'), + ['destination' => 'Skireise'] + ); + + $this->assertSame('Neuer Betreff', $rendered->subject); + $this->assertSame('Neuer Text
', $rendered->bodyHtml); + } + + /** + * A fresh database has no rows at all, so the delivered wording has to carry the mails + * on its own. + */ + public function testTheDefaultWordingIsUsedWithoutAStoredRow(): void + { + $rendered = $this->render(null, ['destination' => 'Skireise']); + + $this->assertSame('Deine Bewerbung wurde abgelehnt', $rendered->subject); + $this->assertStringContainsString('Skireise', $rendered->bodyHtml); + } + + public function testMarkupTypedByAnAdminIsEscaped(): void + { + $rendered = $this->render( + $this->emailText(body: 'Hallo '), + ['destination' => 'Skireise'] + ); + + $this->assertStringNotContainsString('