From 3c3118e215bb37c1d8696e4f3a493a759eb81c22 Mon Sep 17 00:00:00 2001 From: Brandy Smith <6577830+brandyscarney@users.noreply.github.com> Date: Wed, 22 Jan 2025 13:13:19 -0500 Subject: [PATCH 01/14] feat(textarea): add css vars for helper and error text --- core/src/components/textarea/textarea.md.solid.scss | 4 ++++ core/src/components/textarea/textarea.scss | 11 +++++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/core/src/components/textarea/textarea.md.solid.scss b/core/src/components/textarea/textarea.md.solid.scss index 2dabbeb4e73..9d831daf0e0 100644 --- a/core/src/components/textarea/textarea.md.solid.scss +++ b/core/src/components/textarea/textarea.md.solid.scss @@ -32,6 +32,10 @@ --border-color: var(--highlight-color); } +/** + * The bottom content should never have + * a border with the solid style. + */ :host(.textarea-fill-solid) .textarea-bottom { border-top: none; } diff --git a/core/src/components/textarea/textarea.scss b/core/src/components/textarea/textarea.scss index 3a54391cf42..277307f4285 100644 --- a/core/src/components/textarea/textarea.scss +++ b/core/src/components/textarea/textarea.scss @@ -29,6 +29,9 @@ * @prop --padding-end: Right padding if direction is left-to-right, and left padding if direction is right-to-left of the textarea * @prop --padding-bottom: Bottom padding of the textarea * @prop --padding-start: Left padding if direction is left-to-right, and right padding if direction is right-to-left of the textarea + * + * @prop --helper-text-color: The color of the supporting text displayed beneath the textarea when the textarea is valid. + * @prop --error-text-color: The color of the supporting text displayed beneath the textarea when the textarea is invalid and touched. */ --background: initial; --color: initial; @@ -45,6 +48,8 @@ --highlight-color-focused: #{ion-color(primary, base)}; --highlight-color-valid: #{ion-color(success, base)}; --highlight-color-invalid: #{ion-color(danger, base)}; + --helper-text-color: #{$text-color-step-300}; + --error-text-color: var(--highlight-color-invalid); /** * This is a private API that is used to switch @@ -389,6 +394,8 @@ border-top: var(--border-width) var(--border-style) var(--border-color); font-size: dynamic-font(12px); + + white-space: normal; } /** @@ -411,13 +418,13 @@ .textarea-bottom .error-text { display: none; - color: var(--highlight-color-invalid); + color: var(--error-text-color); } .textarea-bottom .helper-text { display: block; - color: #{$text-color-step-450}; + color: var(--helper-text-color); } :host(.ion-touched.ion-invalid) .textarea-bottom .error-text { From be503747fd5ded4f1f27d84cc07dc8474fe7a0db Mon Sep 17 00:00:00 2001 From: Brandy Smith <6577830+brandyscarney@users.noreply.github.com> Date: Wed, 22 Jan 2025 13:13:31 -0500 Subject: [PATCH 02/14] test(textarea): update e2e test for new vars --- .../textarea/test/bottom-content/index.html | 101 +++-- .../test/bottom-content/textarea.e2e.ts | 356 +++++++++++------- 2 files changed, 283 insertions(+), 174 deletions(-) diff --git a/core/src/components/textarea/test/bottom-content/index.html b/core/src/components/textarea/test/bottom-content/index.html index 04de279bd2b..7e02ec505fd 100644 --- a/core/src/components/textarea/test/bottom-content/index.html +++ b/core/src/components/textarea/test/bottom-content/index.html @@ -15,7 +15,7 @@ - - `, - config - ); + test('textarea should have an aria-describedby attribute when error text is present', async ({ page }) => { + await page.setContent( + ``, + config + ); - const errorText = page.locator('ion-textarea .error-text'); - await expect(errorText).toHaveScreenshot(screenshot(`textarea-error-custom-color`)); - }); - test('textarea should have an aria-describedby attribute when error text is present', async ({ page }) => { - await page.setContent( - ``, - config - ); + const input = page.locator('ion-textarea textarea'); + const errorText = page.locator('ion-textarea .error-text'); + const errorTextId = await errorText.getAttribute('id'); + const ariaDescribedBy = await input.getAttribute('aria-describedby'); - const textarea = page.locator('ion-textarea textarea'); - const errorText = page.locator('ion-textarea .error-text'); - const errorTextId = await errorText.getAttribute('id'); - const ariaDescribedBy = await textarea.getAttribute('aria-describedby'); + expect(ariaDescribedBy).toBe(errorTextId); + }); + test('textarea should have aria-invalid attribute when input is invalid', async ({ page }) => { + await page.setContent( + ``, + config + ); - expect(ariaDescribedBy).toBe(errorTextId); - }); - test('textarea should have aria-invalid attribute when input is invalid', async ({ page }) => { - await page.setContent( - ``, - config - ); + const input = page.locator('ion-textarea textarea'); - const textarea = page.locator('ion-textarea textarea'); + await expect(input).toHaveAttribute('aria-invalid'); + }); + test('textarea should not have aria-invalid attribute when input is valid', async ({ page }) => { + await page.setContent( + ``, + config + ); - await expect(textarea).toHaveAttribute('aria-invalid'); - }); - test('textarea should not have aria-invalid attribute when input is valid', async ({ page }) => { - await page.setContent( - ``, - config - ); + const input = page.locator('ion-textarea textarea'); - const textarea = page.locator('ion-textarea textarea'); + await expect(input).not.toHaveAttribute('aria-invalid'); + }); + test('textarea should not have aria-describedby attribute when no hint or error text is present', async ({ + page, + }) => { + await page.setContent(``, config); - await expect(textarea).not.toHaveAttribute('aria-invalid'); - }); - test('textarea should not have aria-describedby attribute when no hint or error text is present', async ({ - page, - }) => { - await page.setContent(``, config); + const input = page.locator('ion-textarea textarea'); - const textarea = page.locator('ion-textarea textarea'); + await expect(input).not.toHaveAttribute('aria-describedby'); + }); + }); +}); - await expect(textarea).not.toHaveAttribute('aria-describedby'); - }); +/** + * Rendering is different across modes + */ +configs({ modes: ['ios', 'md'], directions: ['ltr'] }).forEach(({ title, screenshot, config }) => { + test.describe(title('textarea: helper text rendering'), () => { + test('should not have visual regressions when rendering helper text', async ({ page }) => { + await page.setContent(``, config); + + const bottomEl = page.locator('ion-textarea'); + await expect(bottomEl).toHaveScreenshot(screenshot(`textarea-helper-text`)); }); - test.describe('textarea: hint text rendering', () => { - test.describe('regular textareas', () => { - test('should not have visual regressions when rendering helper text', async ({ page }) => { - await page.setContent(``, config); - - const bottomEl = page.locator('ion-textarea .textarea-bottom'); - await expect(bottomEl).toHaveScreenshot(screenshot(`textarea-bottom-content-helper`)); - }); - test('should not have visual regressions when rendering error text', async ({ page }) => { - await page.setContent( - ``, - config - ); - - const bottomEl = page.locator('ion-textarea .textarea-bottom'); - await expect(bottomEl).toHaveScreenshot(screenshot(`textarea-bottom-content-error`)); - }); - }); + test('should not have visual regressions when rendering helper text with wrapping text', async ({ page }) => { + await page.setContent( + ``, + config + ); + + const bottomEl = page.locator('ion-textarea'); + await expect(bottomEl).toHaveScreenshot(screenshot(`textarea-helper-text-wrapping`)); + }); + test('should not have visual regressions when rendering helper text with a stacked label', async ({ page }) => { + await page.setContent( + ``, + config + ); + + const bottomEl = page.locator('ion-textarea'); + await expect(bottomEl).toHaveScreenshot(screenshot(`textarea-helper-text-stacked-label`)); + }); + }); + + test.describe(title('textarea: error text rendering'), () => { + test('should not have visual regressions when rendering error text', async ({ page }) => { + await page.setContent( + ``, + config + ); + + const bottomEl = page.locator('ion-textarea'); + await expect(bottomEl).toHaveScreenshot(screenshot(`textarea-error-text`)); + }); + test('should not have visual regressions when rendering error text with a stacked label', async ({ page }) => { + await page.setContent( + ``, + config + ); + + const bottomEl = page.locator('ion-textarea'); + await expect(bottomEl).toHaveScreenshot(screenshot(`textarea-error-text-stacked-label`)); + }); + }); +}); + +/** + * Customizing supporting text is the same across modes and directions + */ +configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, screenshot, config }) => { + test.describe(title('textarea: supporting text customization'), () => { + test('should not have visual regressions when rendering helper text with a custom color via css var', async ({ + page, + }) => { + await page.setContent( + ` + + + `, + config + ); + + const helperText = page.locator('ion-textarea'); + await expect(helperText).toHaveScreenshot(screenshot(`textarea-helper-text-custom-color-var`)); + }); + test('should not have visual regressions when rendering error text with a custom color via css var', async ({ + page, + }) => { + await page.setContent( + ` + + + `, + config + ); + + const errorText = page.locator('ion-textarea'); + await expect(errorText).toHaveScreenshot(screenshot(`textarea-error-text-custom-color-var`)); + }); + test('should not have visual regressions when rendering error text with a custom color via css highlight var', async ({ + page, + }) => { + await page.setContent( + ` + + + `, + config + ); + + const errorText = page.locator('ion-textarea'); + await expect(errorText).toHaveScreenshot(screenshot(`textarea-error-text-custom-highlight-var`)); }); }); }); @@ -139,7 +216,7 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, screenshot, co test('should not activate if maxlength is not specified even if bottom content is visible', async ({ page }) => { await page.setContent( ` - + `, config ); @@ -149,7 +226,7 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, screenshot, co test('default formatter should be used', async ({ page }) => { await page.setContent( ` - + `, config ); @@ -159,7 +236,7 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, screenshot, co test('custom formatter should be used when provided', async ({ page }) => { await page.setContent( ` - +