From f514127eb3a33b0271b5b6c66e4029f086b18f78 Mon Sep 17 00:00:00 2001 From: Hugo Alliaume Date: Mon, 15 Sep 2025 09:24:28 +0200 Subject: [PATCH] [Translator] Add E2E tests --- apps/e2e/config/packages/translation.yaml | 1 + apps/e2e/config/packages/ux_translator.yaml | 4 + .../src/Controller/TranslatorController.php | 50 ++++++- apps/e2e/src/Repository/ExampleRepository.php | 8 ++ .../templates/ux_translator/basic.html.twig | 29 ++++ .../ux_translator/icu_date_time.html.twig | 35 +++++ .../icu_number_currency.html.twig | 35 +++++ .../icu_number_percent.html.twig | 35 +++++ .../ux_translator/icu_plural.html.twig | 35 +++++ .../ux_translator/icu_select.html.twig | 40 ++++++ .../ux_translator/icu_selectordinal.html.twig | 35 +++++ .../templates/ux_translator/index.html.twig | 3 - .../ux_translator/with_parameter.html.twig | 35 +++++ .../translations/messages+intl-icu.en.yaml | 35 +++++ .../translations/messages+intl-icu.fr.yaml | 33 +++++ .../assets/test/browser/placeholder.test.ts | 7 - .../assets/test/browser/translator.test.ts | 133 ++++++++++++++++++ 17 files changed, 538 insertions(+), 15 deletions(-) create mode 100644 apps/e2e/templates/ux_translator/basic.html.twig create mode 100644 apps/e2e/templates/ux_translator/icu_date_time.html.twig create mode 100644 apps/e2e/templates/ux_translator/icu_number_currency.html.twig create mode 100644 apps/e2e/templates/ux_translator/icu_number_percent.html.twig create mode 100644 apps/e2e/templates/ux_translator/icu_plural.html.twig create mode 100644 apps/e2e/templates/ux_translator/icu_select.html.twig create mode 100644 apps/e2e/templates/ux_translator/icu_selectordinal.html.twig delete mode 100644 apps/e2e/templates/ux_translator/index.html.twig create mode 100644 apps/e2e/templates/ux_translator/with_parameter.html.twig create mode 100644 apps/e2e/translations/messages+intl-icu.en.yaml create mode 100644 apps/e2e/translations/messages+intl-icu.fr.yaml delete mode 100644 src/Translator/assets/test/browser/placeholder.test.ts create mode 100644 src/Translator/assets/test/browser/translator.test.ts diff --git a/apps/e2e/config/packages/translation.yaml b/apps/e2e/config/packages/translation.yaml index 490bfc2c29e..d23b79e7be8 100644 --- a/apps/e2e/config/packages/translation.yaml +++ b/apps/e2e/config/packages/translation.yaml @@ -1,5 +1,6 @@ framework: default_locale: en + enabled_locales: ['en', 'fr'] translator: default_path: '%kernel.project_dir%/translations' providers: diff --git a/apps/e2e/config/packages/ux_translator.yaml b/apps/e2e/config/packages/ux_translator.yaml index 1c1c70608ce..06502e5e8db 100644 --- a/apps/e2e/config/packages/ux_translator.yaml +++ b/apps/e2e/config/packages/ux_translator.yaml @@ -1,3 +1,7 @@ ux_translator: # The directory where the JavaScript translations are dumped dump_directory: '%kernel.project_dir%/var/translations' + + # The translation domains to include in the dump + domains: + - messages diff --git a/apps/e2e/src/Controller/TranslatorController.php b/apps/e2e/src/Controller/TranslatorController.php index c30ecc24254..0d3ecf2611c 100644 --- a/apps/e2e/src/Controller/TranslatorController.php +++ b/apps/e2e/src/Controller/TranslatorController.php @@ -9,11 +9,51 @@ #[Route('/ux-translator')] final class TranslatorController extends AbstractController { - #[Route('/')] - public function index(): Response + #[Route('/basic')] + public function basic(): Response { - return $this->render('ux_translator/index.html.twig', [ - 'controller_name' => 'TranslatorController', - ]); + return $this->render('ux_translator/basic.html.twig'); + } + + #[Route('/with-parameter')] + public function withParameter(): Response + { + return $this->render('ux_translator/with_parameter.html.twig'); + } + + #[Route('/icu-select')] + public function icuSelect(): Response + { + return $this->render('ux_translator/icu_select.html.twig'); + } + + #[Route('/icu-plural')] + public function icuPlural(): Response + { + return $this->render('ux_translator/icu_plural.html.twig'); + } + + #[Route('/icu-selectordinal')] + public function icuSelectOrdinal(): Response + { + return $this->render('ux_translator/icu_selectordinal.html.twig'); + } + + #[Route('/icu-date-time')] + public function icuDateTime(): Response + { + return $this->render('ux_translator/icu_date_time.html.twig'); + } + + #[Route('/icu-number-percent')] + public function icuNumberPercent(): Response + { + return $this->render('ux_translator/icu_number_percent.html.twig'); + } + + #[Route('/icu-number-currency')] + public function icuNumberCurrency(): Response + { + return $this->render('ux_translator/icu_number_currency.html.twig'); } } diff --git a/apps/e2e/src/Repository/ExampleRepository.php b/apps/e2e/src/Repository/ExampleRepository.php index fcd908ec20c..51f3e2a3f9b 100644 --- a/apps/e2e/src/Repository/ExampleRepository.php +++ b/apps/e2e/src/Repository/ExampleRepository.php @@ -43,6 +43,14 @@ public function __construct() { new Example(UxPackage::Map, 'With rectangles (Google)', 'A map with two rectangles: one from Paris to Lille, the other from Lyon to Bordeaux', '/ux-map/with-rectangles?renderer=google'), new Example(UxPackage::React, 'Basic React Component', 'A basic React component that displays a welcoming message', '/ux-react/'), new Example(UxPackage::Svelte, 'Basic Svelte Component', 'A basic Svelte component that displays a welcoming message', '/ux-svelte/'), + new Example(UxPackage::Translator, 'Basic translation', 'A simple translation example using the Translator component', '/ux-translator/basic'), + new Example(UxPackage::Translator, 'Translation with parameter', 'Translation example with dynamic parameters', '/ux-translator/with-parameter'), + new Example(UxPackage::Translator, 'ICU Translation with `select` argument', 'ICU message format example using the `select` argument', '/ux-translator/icu-select'), + new Example(UxPackage::Translator, 'ICU Translation with `plural` argument', 'ICU message format example using the `plural` argument', '/ux-translator/icu-plural'), + new Example(UxPackage::Translator, 'ICU Translation with `selectordinal` argument', 'ICU message format example using the `selectordinal` argument', '/ux-translator/icu-selectordinal'), + new Example(UxPackage::Translator, 'ICU Translation with `date` and `time` arguments', 'ICU message format example using `date` and `time` arguments', '/ux-translator/icu-date-time'), + new Example(UxPackage::Translator, 'ICU Translation with `number` and `percent` arguments', 'ICU message format example using `number` and `percent` arguments', '/ux-translator/icu-number-percent'), + new Example(UxPackage::Translator, 'ICU Translation with `number` and `currency` arguments', 'ICU message format example using `number` and `currency` arguments', '/ux-translator/icu-number-currency'), new Example(UxPackage::Vue, 'Basic Vue Component', 'A basic Vue component that displays a welcoming message', '/ux-vue/'), ]; } diff --git a/apps/e2e/templates/ux_translator/basic.html.twig b/apps/e2e/templates/ux_translator/basic.html.twig new file mode 100644 index 00000000000..476d2f335b6 --- /dev/null +++ b/apps/e2e/templates/ux_translator/basic.html.twig @@ -0,0 +1,29 @@ +{% extends 'example.html.twig' %} + +{% block example %} +
+

Input

+ +
+ +{% endblock %} + +{% block javascripts %} + {{ parent() }} + +{% endblock %} diff --git a/apps/e2e/templates/ux_translator/icu_date_time.html.twig b/apps/e2e/templates/ux_translator/icu_date_time.html.twig new file mode 100644 index 00000000000..9d7740fb956 --- /dev/null +++ b/apps/e2e/templates/ux_translator/icu_date_time.html.twig @@ -0,0 +1,35 @@ +{% extends 'example.html.twig' %} + +{% block example %} +
+

Input

+
+ + +
+ +
+ +{% endblock %} + +{% block javascripts %} + {{ parent() }} + +{% endblock %} diff --git a/apps/e2e/templates/ux_translator/icu_number_currency.html.twig b/apps/e2e/templates/ux_translator/icu_number_currency.html.twig new file mode 100644 index 00000000000..1a8e2e5c533 --- /dev/null +++ b/apps/e2e/templates/ux_translator/icu_number_currency.html.twig @@ -0,0 +1,35 @@ +{% extends 'example.html.twig' %} + +{% block example %} +
+

Input

+
+ + +
+ +
+ +{% endblock %} + +{% block javascripts %} + {{ parent() }} + +{% endblock %} diff --git a/apps/e2e/templates/ux_translator/icu_number_percent.html.twig b/apps/e2e/templates/ux_translator/icu_number_percent.html.twig new file mode 100644 index 00000000000..458ec1cf687 --- /dev/null +++ b/apps/e2e/templates/ux_translator/icu_number_percent.html.twig @@ -0,0 +1,35 @@ +{% extends 'example.html.twig' %} + +{% block example %} +
+

Input

+
+ + +
+ +
+ +{% endblock %} + +{% block javascripts %} + {{ parent() }} + +{% endblock %} diff --git a/apps/e2e/templates/ux_translator/icu_plural.html.twig b/apps/e2e/templates/ux_translator/icu_plural.html.twig new file mode 100644 index 00000000000..1a340a443b0 --- /dev/null +++ b/apps/e2e/templates/ux_translator/icu_plural.html.twig @@ -0,0 +1,35 @@ +{% extends 'example.html.twig' %} + +{% block example %} +
+

Input

+
+ + +
+ +
+ +{% endblock %} + +{% block javascripts %} + {{ parent() }} + +{% endblock %} diff --git a/apps/e2e/templates/ux_translator/icu_select.html.twig b/apps/e2e/templates/ux_translator/icu_select.html.twig new file mode 100644 index 00000000000..163c87fbf67 --- /dev/null +++ b/apps/e2e/templates/ux_translator/icu_select.html.twig @@ -0,0 +1,40 @@ +{% extends 'example.html.twig' %} + +{% block example %} +
+

Input

+
+ + +
+ +
+ +{% endblock %} + +{% block javascripts %} + {{ parent() }} + +{% endblock %} diff --git a/apps/e2e/templates/ux_translator/icu_selectordinal.html.twig b/apps/e2e/templates/ux_translator/icu_selectordinal.html.twig new file mode 100644 index 00000000000..e70e490d121 --- /dev/null +++ b/apps/e2e/templates/ux_translator/icu_selectordinal.html.twig @@ -0,0 +1,35 @@ +{% extends 'example.html.twig' %} + +{% block example %} +
+

Input

+
+ + +
+ +
+ +{% endblock %} + +{% block javascripts %} + {{ parent() }} + +{% endblock %} diff --git a/apps/e2e/templates/ux_translator/index.html.twig b/apps/e2e/templates/ux_translator/index.html.twig deleted file mode 100644 index 78c01e96007..00000000000 --- a/apps/e2e/templates/ux_translator/index.html.twig +++ /dev/null @@ -1,3 +0,0 @@ -{% extends 'example.html.twig' %} - -{% block example %}{% endblock %} diff --git a/apps/e2e/templates/ux_translator/with_parameter.html.twig b/apps/e2e/templates/ux_translator/with_parameter.html.twig new file mode 100644 index 00000000000..9bbc86ef33f --- /dev/null +++ b/apps/e2e/templates/ux_translator/with_parameter.html.twig @@ -0,0 +1,35 @@ +{% extends 'example.html.twig' %} + +{% block example %} +
+

Input

+
+ + +
+ +
+ +{% endblock %} + +{% block javascripts %} + {{ parent() }} + +{% endblock %} diff --git a/apps/e2e/translations/messages+intl-icu.en.yaml b/apps/e2e/translations/messages+intl-icu.en.yaml new file mode 100644 index 00000000000..03bfcdd7a8c --- /dev/null +++ b/apps/e2e/translations/messages+intl-icu.en.yaml @@ -0,0 +1,35 @@ +hello: 'Hello!' +say_hello: 'Hello {name}!' + +# Select +invitation_title: >- + {organizer_gender, select, + female {{organizer_name} has invited you to her party!} + male {{organizer_name} has invited you to his party!} + multiple {{organizer_name} have invited you to their party!} + other {{organizer_name} has invited you to their party!} + } + +# Pluralization +num_of_apples: >- + {apples, plural, + =0 {There are no apples} + =1 {There is one apple...} + other {There are # apples!} + } + +# Ordinal +finish_place: >- + You finished {place, selectordinal, + one {#st} + two {#nd} + few {#rd} + other {#th} + }! + +# Date and Time +published_at: 'Published at {publication_date, date} - {publication_date, time, short}' + +# Numbers +progress: '{progress, number, percent} of the work is done' +value_of_object: 'This artifact is worth {price, number, ::currency/EUR}' diff --git a/apps/e2e/translations/messages+intl-icu.fr.yaml b/apps/e2e/translations/messages+intl-icu.fr.yaml new file mode 100644 index 00000000000..3e012aac299 --- /dev/null +++ b/apps/e2e/translations/messages+intl-icu.fr.yaml @@ -0,0 +1,33 @@ +hello: 'Bonjour !' +say_hello: 'Bonjour {name} !' + +# Select +invitation_title: >- + {organizer_gender, select, + female {{organizer_name} t'a invité à sa fête !} + male {{organizer_name} t'a invité à sa fête !} + multiple {{organizer_name} t'a invité à sa fête !} + other {{organizer_name} t'a invité à sa fête !} + } + +# Pluralization +num_of_apples: >- + {apples, plural, + =0 {Il n'y a pas de pommes} + =1 {Il y a une pomme...} + other {Il y a # pommes !} + } + +# Ordinal +finish_place: >- + Tu as terminé {place, selectordinal, + one {#er} + other {#e} + } ! + +# Date and Time +published_at: 'Publié le {publication_date, date} - {publication_date, time, short}' + +# Numbers +progress: '{progress, number, percent} du travail est fait' +value_of_object: "Cet artéfact vaut {price, number, ::currency/EUR}" diff --git a/src/Translator/assets/test/browser/placeholder.test.ts b/src/Translator/assets/test/browser/placeholder.test.ts deleted file mode 100644 index f5f1d10f22e..00000000000 --- a/src/Translator/assets/test/browser/placeholder.test.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { expect, test } from '@playwright/test'; - -test('Can see homepage', async ({ page }) => { - await page.goto('/'); - - await expect(page.getByText("Symfony UX's E2E App")).toBeVisible(); -}); diff --git a/src/Translator/assets/test/browser/translator.test.ts b/src/Translator/assets/test/browser/translator.test.ts new file mode 100644 index 00000000000..a7d4eef7af2 --- /dev/null +++ b/src/Translator/assets/test/browser/translator.test.ts @@ -0,0 +1,133 @@ +import { expect, type Page, test } from '@playwright/test'; + +function expectOutputToBeEmpty(page: Page) { + return expect(page.getByTestId('output')).toBeEmpty(); +} + +function expectOutputToContainText(page: Page, text: string) { + return expect(page.getByTestId('output')).toContainText(text); +} + +test('Can translate basic message', async ({ page }) => { + await page.goto('/ux-translator/basic'); + await expectOutputToBeEmpty(page); + + await page.getByRole('button', { name: 'Render' }).click(); + await expectOutputToContainText(page, '🇬🇧 Hello!'); + await expectOutputToContainText(page, '🇫🇷 Bonjour !'); +}); + +test('Can translate message with parameter', async ({ page }) => { + await page.goto('/ux-translator/with-parameter'); + await expectOutputToBeEmpty(page); + + await page.getByRole('button', { name: 'Render' }).click(); + await expectOutputToContainText(page, '🇬🇧 Hello Fabien!'); + await expectOutputToContainText(page, '🇫🇷 Bonjour Fabien !'); + + await page.getByLabel('Name').clear(); + await page.getByLabel('Name').fill('Hugo'); + await page.getByRole('button', { name: 'Render' }).click(); + await expectOutputToContainText(page, '🇬🇧 Hello Hugo!'); + await expectOutputToContainText(page, '🇫🇷 Bonjour Hugo !'); +}); + +test('Can translate ICU message with `select` argument', async ({ page }) => { + await page.goto('/ux-translator/icu-select'); + await expectOutputToBeEmpty(page); + + await page.getByRole('button', { name: 'Render' }).click(); + await expectOutputToContainText(page, '🇬🇧 Alex has invited you to her party!'); + await expectOutputToContainText(page, `🇫🇷 Alex t'a invité à sa fête !`); + + await page.getByLabel('Gender').selectOption({ label: 'Male' }); + await page.getByRole('button', { name: 'Render' }).click(); + await expectOutputToContainText(page, '🇬🇧 Alex has invited you to his party!'); + await expectOutputToContainText(page, `🇫🇷 Alex t'a invité à sa fête !`); +}); + +test('Can translate ICU message with `plural` argument', async ({ page }) => { + await page.goto('/ux-translator/icu-plural'); + await expectOutputToBeEmpty(page); + + await page.getByRole('button', { name: 'Render' }).click(); + await expectOutputToContainText(page, '🇬🇧 There is one apple...'); + await expectOutputToContainText(page, '🇫🇷 Il y a une pomme...'); + + await page.getByLabel('Apples').clear(); + await page.getByLabel('Apples').fill('0'); + await page.getByRole('button', { name: 'Render' }).click(); + await expectOutputToContainText(page, '🇬🇧 There are no apples'); + await expectOutputToContainText(page, `🇫🇷 Il n'y a pas de pommes`); + + await page.getByLabel('Apples').clear(); + await page.getByLabel('Apples').fill('3'); + await page.getByRole('button', { name: 'Render' }).click(); + await expectOutputToContainText(page, '🇬🇧 There are 3 apples!'); + await expectOutputToContainText(page, '🇫🇷 Il y a 3 pommes !'); +}); + +test('Can translate ICU message with `selectordinal` argument', async ({ page }) => { + await page.goto('/ux-translator/icu-selectordinal'); + await expectOutputToBeEmpty(page); + + await page.getByRole('button', { name: 'Render' }).click(); + await expectOutputToContainText(page, '🇬🇧 You finished 1st!'); + await expectOutputToContainText(page, '🇫🇷 Tu as terminé 1er !'); + + await page.getByLabel('Place').clear(); + await page.getByLabel('Place').fill('2'); + await page.getByRole('button', { name: 'Render' }).click(); + await expectOutputToContainText(page, '🇬🇧 You finished 2nd!'); + await expectOutputToContainText(page, '🇫🇷 Tu as terminé 2e !'); + + await page.getByLabel('Place').clear(); + await page.getByLabel('Place').fill('3'); + await page.getByRole('button', { name: 'Render' }).click(); + await expectOutputToContainText(page, '🇬🇧 You finished 3rd!'); + await expectOutputToContainText(page, '🇫🇷 Tu as terminé 3e !'); +}); + +test('Can translate ICU message with `date` and `time` arguments', async ({ page }) => { + await page.goto('/ux-translator/icu-date-time'); + await expectOutputToBeEmpty(page); + + await page.getByRole('button', { name: 'Render' }).click(); + await expectOutputToContainText(page, '🇬🇧 Published at 4/27/2023 - 8:12 AM'); + await expectOutputToContainText(page, '🇫🇷 Publié le 27/04/2023 - 08:12'); + + await page.getByLabel('Date').clear(); + await page.getByLabel('Date').fill('2024-03-17T09:30'); + await page.getByRole('button', { name: 'Render' }).click(); + await expectOutputToContainText(page, '🇬🇧 Published at 3/17/2024 - 9:30 AM'); + await expectOutputToContainText(page, '🇫🇷 Publié le 17/03/2024 - 09:30'); +}); + +test('Can translate ICU message with `number` and `percent` arguments', async ({ page }) => { + await page.goto('/ux-translator/icu-number-percent'); + await expectOutputToBeEmpty(page); + + await page.getByRole('button', { name: 'Render' }).click(); + await expectOutputToContainText(page, '🇬🇧 50% of the work is done'); + await expectOutputToContainText(page, '🇫🇷 50 % du travail est fait'); + + await page.getByLabel('Progress').fill('0.75'); + await page.getByRole('button', { name: 'Render' }).click(); + await expectOutputToContainText(page, '🇬🇧 75% of the work is done'); + await expectOutputToContainText(page, '🇫🇷 75 % du travail est fait'); +}); + +test('Can translate ICU message with `number` and `currency` arguments', async ({ page }) => { + await page.goto('/ux-translator/icu-number-currency'); + await expectOutputToBeEmpty(page); + + await page.getByRole('button', { name: 'Render' }).click(); + await expectOutputToContainText(page, '🇬🇧 This artifact is worth €30.00'); + await expectOutputToContainText(page, '🇫🇷 Cet artéfact vaut 30,00 €'); + + await page.getByLabel('Price').clear(); + await page.getByLabel('Price').fill('12.34'); + await page.getByRole('button', { name: 'Render' }).click(); + await expectOutputToContainText(page, '🇬🇧 This artifact is worth €12.34'); + await expectOutputToContainText(page, '🇫🇷 Cet artéfact vaut 12,34 €'); +});