diff --git a/core/src/components/input/input.md.solid.scss b/core/src/components/input/input.md.solid.scss index 98cde92880a..05bcc00ea55 100644 --- a/core/src/components/input/input.md.solid.scss +++ b/core/src/components/input/input.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(.input-fill-solid) .input-bottom { border-top: none; } diff --git a/core/src/components/input/input.scss b/core/src/components/input/input.scss index 80ebc9896ae..cc648e6e2f0 100644 --- a/core/src/components/input/input.scss +++ b/core/src/components/input/input.scss @@ -306,6 +306,8 @@ border-top: var(--border-width) var(--border-style) var(--border-color); font-size: dynamic-font(12px); + + white-space: normal; } /** @@ -340,7 +342,7 @@ .input-bottom .helper-text { display: block; - color: #{$text-color-step-450}; + color: $text-color-step-300; } :host(.ion-touched.ion-invalid) .input-bottom .error-text { @@ -362,7 +364,7 @@ */ @include margin-horizontal(auto, null); - color: #{$text-color-step-450}; + color: $text-color-step-300; white-space: nowrap; diff --git a/core/src/components/input/test/a11y/input.e2e.ts-snapshots/input-scale-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/input/test/a11y/input.e2e.ts-snapshots/input-scale-ios-ltr-Mobile-Chrome-linux.png index 2b7fa4d25b0..bf00ab3aa73 100644 Binary files a/core/src/components/input/test/a11y/input.e2e.ts-snapshots/input-scale-ios-ltr-Mobile-Chrome-linux.png and b/core/src/components/input/test/a11y/input.e2e.ts-snapshots/input-scale-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/input/test/a11y/input.e2e.ts-snapshots/input-scale-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/input/test/a11y/input.e2e.ts-snapshots/input-scale-ios-ltr-Mobile-Firefox-linux.png index a325fe40587..04741a189c6 100644 Binary files a/core/src/components/input/test/a11y/input.e2e.ts-snapshots/input-scale-ios-ltr-Mobile-Firefox-linux.png and b/core/src/components/input/test/a11y/input.e2e.ts-snapshots/input-scale-ios-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/input/test/a11y/input.e2e.ts-snapshots/input-scale-ios-ltr-Mobile-Safari-linux.png b/core/src/components/input/test/a11y/input.e2e.ts-snapshots/input-scale-ios-ltr-Mobile-Safari-linux.png index 28e46d8de8b..4ed28c447e4 100644 Binary files a/core/src/components/input/test/a11y/input.e2e.ts-snapshots/input-scale-ios-ltr-Mobile-Safari-linux.png and b/core/src/components/input/test/a11y/input.e2e.ts-snapshots/input-scale-ios-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/input/test/a11y/input.e2e.ts-snapshots/input-scale-md-ltr-Mobile-Chrome-linux.png b/core/src/components/input/test/a11y/input.e2e.ts-snapshots/input-scale-md-ltr-Mobile-Chrome-linux.png index be5a13aab0f..5ecb5d80976 100644 Binary files a/core/src/components/input/test/a11y/input.e2e.ts-snapshots/input-scale-md-ltr-Mobile-Chrome-linux.png and b/core/src/components/input/test/a11y/input.e2e.ts-snapshots/input-scale-md-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/input/test/a11y/input.e2e.ts-snapshots/input-scale-md-ltr-Mobile-Firefox-linux.png b/core/src/components/input/test/a11y/input.e2e.ts-snapshots/input-scale-md-ltr-Mobile-Firefox-linux.png index f6aead399f7..143b233419e 100644 Binary files a/core/src/components/input/test/a11y/input.e2e.ts-snapshots/input-scale-md-ltr-Mobile-Firefox-linux.png and b/core/src/components/input/test/a11y/input.e2e.ts-snapshots/input-scale-md-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/input/test/a11y/input.e2e.ts-snapshots/input-scale-md-ltr-Mobile-Safari-linux.png b/core/src/components/input/test/a11y/input.e2e.ts-snapshots/input-scale-md-ltr-Mobile-Safari-linux.png index 93c695ba729..deaa3159125 100644 Binary files a/core/src/components/input/test/a11y/input.e2e.ts-snapshots/input-scale-md-ltr-Mobile-Safari-linux.png and b/core/src/components/input/test/a11y/input.e2e.ts-snapshots/input-scale-md-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/input/test/bottom-content/index.html b/core/src/components/input/test/bottom-content/index.html index d08d111c0eb..e0d0cf8ba69 100644 --- a/core/src/components/input/test/bottom-content/index.html +++ b/core/src/components/input/test/bottom-content/index.html @@ -15,10 +15,11 @@ - + @@ -52,67 +47,120 @@

No Hint

- + +
+ +
+

No Hint: Stacked

+ +
+ +
+

Helper Text

+ +
+ +
+

Helper Text: Stacked

+
-

Helper Hint

- +

Error Text

+
-

Error Hint

+

Error Text: Stacked

-

Custom Error Color

+

Error Text: Custom Color

+
+

Helper Text: Wrapping

+ + +
+

Counter

- +
-

Custom Counter

- +

Counter: Custom

+
-

Counter with Helper

- +

Counter: with Helper

+
-

Counter with Error

+

Counter: with Error

- +
+ + diff --git a/core/src/components/input/test/bottom-content/input.e2e.ts b/core/src/components/input/test/bottom-content/input.e2e.ts index 5de0ca79e19..770ca5238f5 100644 --- a/core/src/components/input/test/bottom-content/input.e2e.ts +++ b/core/src/components/input/test/bottom-content/input.e2e.ts @@ -1,189 +1,218 @@ import { expect } from '@playwright/test'; import { configs, test } from '@utils/test/playwright'; -configs({ directions: ['ltr'] }).forEach(({ title, screenshot, config }) => { - test.describe(title('input: bottom content'), () => { - test('entire input component should render correctly with no fill', async ({ page }) => { +/** + * Functionality is the same across modes & directions + */ +configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) => { + test.describe(title('input: bottom content functionality'), () => { + test('should not render bottom content if no hint is enabled', async ({ page }) => { + await page.setContent(``, config); + + const bottomEl = page.locator('ion-input .input-bottom'); + await expect(bottomEl).toHaveCount(0); + }); + test('helper text should be visible initially', async ({ page }) => { await page.setContent( - ` - - `, + ``, config ); - const input = page.locator('ion-input'); - await expect(input).toHaveScreenshot(screenshot(`input-full-bottom-no-fill`)); + + const helperText = page.locator('ion-input .helper-text'); + const errorText = page.locator('ion-input .error-text'); + await expect(helperText).toBeVisible(); + await expect(helperText).toHaveText('Helper text'); + await expect(errorText).toBeHidden(); }); - test('entire input component should render correctly with solid fill', async ({ page }) => { + test('input should have an aria-describedby attribute when helper text is present', async ({ page }) => { await page.setContent( - ` - - `, + ``, config ); - const input = page.locator('ion-input'); - await expect(input).toHaveScreenshot(screenshot(`input-full-bottom-solid`)); + + const input = page.locator('ion-input input'); + const helperText = page.locator('ion-input .helper-text'); + const helperTextId = await helperText.getAttribute('id'); + const ariaDescribedBy = await input.getAttribute('aria-describedby'); + + expect(ariaDescribedBy).toBe(helperTextId); }); - test('entire input component should render correctly with outline fill', async ({ page }) => { + test('error text should be visible when input is invalid', async ({ page }) => { await page.setContent( - ` - - `, + ``, config ); - const input = page.locator('ion-input'); - await expect(input).toHaveScreenshot(screenshot(`input-full-bottom-outline`)); - }); - }); -}); -/** - * Rendering is the same across modes - */ -configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) => { - test.describe(title('input: bottom content functionality'), () => { - test('should not render bottom content if no hint or counter is enabled', async ({ page }) => { - await page.setContent(``, config); - - const bottomEl = page.locator('ion-input .input-bottom'); - await expect(bottomEl).toHaveCount(0); + const helperText = page.locator('ion-input .helper-text'); + const errorText = page.locator('ion-input .error-text'); + await expect(helperText).toBeHidden(); + await expect(errorText).toBeVisible(); + await expect(errorText).toHaveText('Error text'); }); - }); -}); -/** - * Rendering is the same across modes - */ -configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, screenshot, config }) => { - test.describe(title('input: hint text'), () => { - test.describe('input: hint text functionality', () => { - test('helper text should be visible initially', async ({ page }) => { - await page.setContent( - ``, - config - ); + test('input should have an aria-describedby attribute when error text is present', async ({ page }) => { + await page.setContent( + ``, + config + ); - const helperText = page.locator('ion-input .helper-text'); - const errorText = page.locator('ion-input .error-text'); - await expect(helperText).toBeVisible(); - await expect(helperText).toHaveText('my helper'); - await expect(errorText).toBeHidden(); - }); - test('input should have an aria-describedby attribute when helper text is present', async ({ page }) => { - await page.setContent( - ``, - config - ); + const input = page.locator('ion-input input'); + const errorText = page.locator('ion-input .error-text'); + const errorTextId = await errorText.getAttribute('id'); + const ariaDescribedBy = await input.getAttribute('aria-describedby'); - const input = page.locator('ion-input input'); - const helperText = page.locator('ion-input .helper-text'); - const helperTextId = await helperText.getAttribute('id'); - const ariaDescribedBy = await input.getAttribute('aria-describedby'); + expect(ariaDescribedBy).toBe(errorTextId); + }); + test('input should have aria-invalid attribute when input is invalid', async ({ page }) => { + await page.setContent( + ``, + config + ); - expect(ariaDescribedBy).toBe(helperTextId); - }); - test('error text should be visible when input is invalid', async ({ page }) => { - await page.setContent( - ``, - config - ); + const input = page.locator('ion-input input'); - const helperText = page.locator('ion-input .helper-text'); - const errorText = page.locator('ion-input .error-text'); - await expect(helperText).toBeHidden(); - await expect(errorText).toBeVisible(); - await expect(errorText).toHaveText('my error'); - }); - test('error text should change when variable is customized', async ({ page }) => { - await page.setContent( - ` - - - `, - config - ); + await expect(input).toHaveAttribute('aria-invalid'); + }); + test('input should not have aria-invalid attribute when input is valid', async ({ page }) => { + await page.setContent( + ``, + config + ); - const errorText = page.locator('ion-input .error-text'); - await expect(errorText).toHaveScreenshot(screenshot(`input-error-custom-color`)); - }); - test('input should have an aria-describedby attribute when error text is present', async ({ page }) => { - await page.setContent( - ``, - config - ); + const input = page.locator('ion-input input'); - const input = page.locator('ion-input input'); - const errorText = page.locator('ion-input .error-text'); - const errorTextId = await errorText.getAttribute('id'); - const ariaDescribedBy = await input.getAttribute('aria-describedby'); + await expect(input).not.toHaveAttribute('aria-invalid'); + }); + test('input should not have aria-describedby attribute when no hint or error text is present', async ({ page }) => { + await page.setContent(``, config); - expect(ariaDescribedBy).toBe(errorTextId); - }); - test('input should have aria-invalid attribute when input is invalid', async ({ page }) => { - await page.setContent( - ``, - config - ); + const input = page.locator('ion-input input'); - const input = page.locator('ion-input input'); + await expect(input).not.toHaveAttribute('aria-describedby'); + }); + }); +}); - await expect(input).toHaveAttribute('aria-invalid'); - }); - test('input should not have aria-invalid attribute when input is valid', async ({ page }) => { - await page.setContent( - ``, - config - ); +/** + * Rendering is different across modes + */ +configs({ modes: ['ios', 'md'], directions: ['ltr'] }).forEach(({ title, screenshot, config }) => { + test.describe(title('input: helper text rendering'), () => { + test('should not have visual regressions when rendering helper text', async ({ page }) => { + await page.setContent(``, config); - const input = page.locator('ion-input input'); + const bottomEl = page.locator('ion-input'); + await expect(bottomEl).toHaveScreenshot(screenshot(`input-helper-text`)); + }); + test('should not have visual regressions when rendering helper text with wrapping text', async ({ page }) => { + await page.setContent( + ``, + config + ); - await expect(input).not.toHaveAttribute('aria-invalid'); - }); - test('input should not have aria-describedby attribute when no hint or error text is present', async ({ - page, - }) => { - await page.setContent(``, config); + const bottomEl = page.locator('ion-input'); + await expect(bottomEl).toHaveScreenshot(screenshot(`input-helper-text-wrapping`)); + }); + test('should not have visual regressions when rendering helper text with a stacked label', async ({ page }) => { + await page.setContent( + ``, + config + ); - const input = page.locator('ion-input input'); + const bottomEl = page.locator('ion-input'); + await expect(bottomEl).toHaveScreenshot(screenshot(`input-helper-text-stacked-label`)); + }); + }); - await expect(input).not.toHaveAttribute('aria-describedby'); - }); + test.describe(title('input: error text rendering'), () => { + test('should not have visual regressions when rendering error text', async ({ page }) => { + await page.setContent( + ``, + config + ); + + const bottomEl = page.locator('ion-input'); + await expect(bottomEl).toHaveScreenshot(screenshot(`input-error-text`)); }); - test.describe('input: hint text rendering', () => { - test.describe('regular inputs', () => { - test('should not have visual regressions when rendering helper text', async ({ page }) => { - await page.setContent(``, config); - - const bottomEl = page.locator('ion-input .input-bottom'); - await expect(bottomEl).toHaveScreenshot(screenshot(`input-bottom-content-helper`)); - }); - test('should not have visual regressions when rendering error text', async ({ page }) => { - await page.setContent( - ``, - config - ); - - const bottomEl = page.locator('ion-input .input-bottom'); - await expect(bottomEl).toHaveScreenshot(screenshot(`input-bottom-content-error`)); - }); - }); + 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-input'); + await expect(bottomEl).toHaveScreenshot(screenshot(`input-error-text-stacked-label`)); }); }); }); /** - * Rendering is the same across modes + * Customizing supporting text is the same across modes and directions */ +configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, screenshot, config }) => { + test.describe(title('input: supporting text customization'), () => { + test('should not have visual regressions when rendering helper text with custom css', async ({ page }) => { + await page.setContent( + ` + + + `, + config + ); + + const helperText = page.locator('ion-input'); + await expect(helperText).toHaveScreenshot(screenshot(`input-helper-text-custom-css`)); + }); + test('should not have visual regressions when rendering error text with custom css', async ({ page }) => { + await page.setContent( + ` + + + `, + config + ); + + const errorText = page.locator('ion-input'); + await expect(errorText).toHaveScreenshot(screenshot(`input-error-text-custom-css`)); + }); + test('should not have visual regressions when rendering error text with a custom css variable', async ({ + page, + }) => { + await page.setContent( + ` + + + `, + config + ); + + const errorText = page.locator('ion-input'); + await expect(errorText).toHaveScreenshot(screenshot(`input-error-text-custom-css-var`)); + }); + }); +}); + configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, screenshot, config }) => { test.describe(title('input: counter'), () => { test.describe('input: counter functionality', () => { test('should not activate if maxlength is not specified even if bottom content is visible', async ({ page }) => { await page.setContent( ` - + `, config ); @@ -193,7 +222,7 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, screenshot, co test('default formatter should be used', async ({ page }) => { await page.setContent( ` - + `, config ); @@ -203,8 +232,7 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, screenshot, co test('custom formatter should be used when provided', async ({ page }) => { await page.setContent( ` - - +