fix(textarea): update helper text and counter color (#30148)
Issue number: N/A --------- ## What is the current behavior? Helper text is lighter than it should be. ## What is the new behavior? - Updates helper and counter text to match MD design - Updates e2e test to include more coverage ## Does this introduce a breaking change? - [ ] Yes - [x] No ## Other information [Preview](https://ionic-framework-git-rou-11559-ionic1.vercel.app/src/components/textarea/test/bottom-content) > Note that the fill toggle will only work in `md` mode --------- Co-authored-by: Brandy Smith <6577830+brandyscarney@users.noreply.github.com>
Before Width: | Height: | Size: 6.9 KiB After Width: | Height: | Size: 6.4 KiB |
Before Width: | Height: | Size: 8.0 KiB After Width: | Height: | Size: 8.1 KiB |
Before Width: | Height: | Size: 6.4 KiB After Width: | Height: | Size: 6.5 KiB |
Before Width: | Height: | Size: 6.8 KiB After Width: | Height: | Size: 6.2 KiB |
Before Width: | Height: | Size: 7.9 KiB After Width: | Height: | Size: 7.9 KiB |
Before Width: | Height: | Size: 6.0 KiB After Width: | Height: | Size: 6.1 KiB |
@ -15,7 +15,7 @@
|
||||
<style>
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(250px, 1fr));
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
grid-row-gap: 20px;
|
||||
grid-column-gap: 20px;
|
||||
}
|
||||
@ -29,20 +29,13 @@
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 800px) {
|
||||
.grid {
|
||||
grid-template-columns: 1fr;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
ion-textarea.custom-error-color {
|
||||
--highlight-color-invalid: purple;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<body onLoad="onLoad()">
|
||||
<ion-app>
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
@ -54,67 +47,120 @@
|
||||
<div class="grid">
|
||||
<div class="grid-item">
|
||||
<h2>No Hint</h2>
|
||||
<ion-textarea label="Email"></ion-textarea>
|
||||
<ion-textarea label="Label"></ion-textarea>
|
||||
</div>
|
||||
|
||||
<div class="grid-item">
|
||||
<h2>Helper Hint</h2>
|
||||
<ion-textarea label="Email" helper-text="Enter your email"></ion-textarea>
|
||||
<h2>No Hint: Stacked</h2>
|
||||
<ion-textarea label="Label" label-placement="stacked"></ion-textarea>
|
||||
</div>
|
||||
|
||||
<div class="grid-item">
|
||||
<h2>Error Hint</h2>
|
||||
<h2>Helper Text</h2>
|
||||
<ion-textarea label="Label" helper-text="Helper text"></ion-textarea>
|
||||
</div>
|
||||
|
||||
<div class="grid-item">
|
||||
<h2>Helper Text: Stacked</h2>
|
||||
<ion-textarea label="Label" label-placement="stacked" helper-text="Helper text"></ion-textarea>
|
||||
</div>
|
||||
|
||||
<div class="grid-item">
|
||||
<h2>Error Text</h2>
|
||||
<ion-textarea class="ion-touched ion-invalid" label="Label" error-text="Error text"></ion-textarea>
|
||||
</div>
|
||||
|
||||
<div class="grid-item">
|
||||
<h2>Error Text: Stacked</h2>
|
||||
<ion-textarea
|
||||
class="ion-touched ion-invalid"
|
||||
label="Email"
|
||||
error-text="Please enter a valid email"
|
||||
label="Label"
|
||||
label-placement="stacked"
|
||||
error-text="Error text"
|
||||
></ion-textarea>
|
||||
</div>
|
||||
|
||||
<div class="grid-item">
|
||||
<h2>Custom Error Color</h2>
|
||||
<h2>Error Text: Custom Color</h2>
|
||||
<ion-textarea
|
||||
class="ion-touched ion-invalid custom-error-color"
|
||||
label="Email"
|
||||
error-text="Please enter a valid email"
|
||||
label="Label"
|
||||
error-text="Error text"
|
||||
></ion-textarea>
|
||||
</div>
|
||||
|
||||
<div class="grid-item">
|
||||
<h2>Helper Text: Wrapping</h2>
|
||||
<ion-textarea
|
||||
label="Label"
|
||||
helper-text="Helper text helper text helper text helper text helper text helper text helper text helper text helper text"
|
||||
>
|
||||
</ion-textarea>
|
||||
</div>
|
||||
|
||||
<div class="grid-item">
|
||||
<h2>Counter</h2>
|
||||
<ion-textarea label="Email" counter="true" maxlength="100"></ion-textarea>
|
||||
<ion-textarea label="Label" counter="true" maxlength="100"></ion-textarea>
|
||||
</div>
|
||||
|
||||
<div class="grid-item">
|
||||
<h2>Custom Counter</h2>
|
||||
<ion-textarea id="custom-counter" label="Email" counter="true" maxlength="100"></ion-textarea>
|
||||
<h2>Counter: Custom</h2>
|
||||
<ion-textarea id="custom-counter" label="Label" counter="true" maxlength="100"></ion-textarea>
|
||||
</div>
|
||||
|
||||
<div class="grid-item">
|
||||
<h2>Counter with Helper</h2>
|
||||
<ion-textarea label="Email" counter="true" maxlength="100" helper-text="Enter an email"></ion-textarea>
|
||||
<h2>Counter: with Helper</h2>
|
||||
<ion-textarea label="Label" counter="true" maxlength="100" helper-text="Helper text"></ion-textarea>
|
||||
</div>
|
||||
|
||||
<div class="grid-item">
|
||||
<h2>Counter with Error</h2>
|
||||
<h2>Counter: with Error</h2>
|
||||
<ion-textarea
|
||||
class="ion-touched ion-invalid"
|
||||
label="Email"
|
||||
label="Label"
|
||||
counter="true"
|
||||
maxlength="100"
|
||||
error-text="Please enter a valid email"
|
||||
error-text="Error text"
|
||||
></ion-textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const customCounterTextarea = document.querySelector('ion-textarea#custom-counter');
|
||||
customCounterTextarea.counterFormatter = (inputLength, maxLength) => {
|
||||
const length = maxLength - inputLength;
|
||||
return `${maxLength - inputLength} characters left`;
|
||||
};
|
||||
</script>
|
||||
<button class="expand" onclick="toggleFill()">Toggle Fill</button>
|
||||
</ion-content>
|
||||
</ion-app>
|
||||
|
||||
<script>
|
||||
// Hide the toggle fill button on ios mode since it's not supported
|
||||
function onLoad() {
|
||||
const toggleFillButton = document.querySelector('button');
|
||||
|
||||
if (Ionic.mode === 'ios' && toggleFillButton) {
|
||||
toggleFillButton.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
const customCounterTextarea = document.querySelector('ion-textarea#custom-counter');
|
||||
customCounterTextarea.counterFormatter = (inputLength, maxLength) => {
|
||||
const length = maxLength - inputLength;
|
||||
return `${maxLength - inputLength} characters left`;
|
||||
};
|
||||
|
||||
const textareas = document.querySelectorAll('ion-textarea');
|
||||
|
||||
function toggleFill() {
|
||||
textareas.forEach((textarea) => {
|
||||
switch (textarea.fill) {
|
||||
case 'outline':
|
||||
textarea.fill = 'solid';
|
||||
break;
|
||||
case 'solid':
|
||||
textarea.fill = undefined;
|
||||
break;
|
||||
default:
|
||||
textarea.fill = 'outline';
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -1,134 +1,209 @@
|
||||
import { expect } from '@playwright/test';
|
||||
import { configs, test } from '@utils/test/playwright';
|
||||
|
||||
configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
|
||||
test.describe(title('textarea: bottom content'), () => {
|
||||
test('should not render bottom content if no hint or counter is enabled', async ({ page }) => {
|
||||
await page.setContent(`<ion-textarea label="my textarea"></ion-textarea>`, config);
|
||||
/**
|
||||
* Functionality is the same across modes & directions
|
||||
*/
|
||||
configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) => {
|
||||
test.describe(title('textarea: bottom content functionality'), () => {
|
||||
test('should not render bottom content if no hint is enabled', async ({ page }) => {
|
||||
await page.setContent(`<ion-textarea label="Label"></ion-textarea>`, config);
|
||||
|
||||
const bottomEl = page.locator('ion-textarea .textarea-bottom');
|
||||
await expect(bottomEl).toHaveCount(0);
|
||||
});
|
||||
test('helper text should be visible initially', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`<ion-textarea label="Label" helper-text="Helper text" error-text="Error text"></ion-textarea>`,
|
||||
config
|
||||
);
|
||||
|
||||
const helperText = page.locator('ion-textarea .helper-text');
|
||||
const errorText = page.locator('ion-textarea .error-text');
|
||||
await expect(helperText).toBeVisible();
|
||||
await expect(helperText).toHaveText('Helper text');
|
||||
await expect(errorText).toBeHidden();
|
||||
});
|
||||
test('textarea should have an aria-describedby attribute when helper text is present', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`<ion-textarea label="Label" helper-text="Helper text" error-text="Error text"></ion-textarea>`,
|
||||
config
|
||||
);
|
||||
|
||||
const textarea = page.locator('ion-textarea textarea');
|
||||
const helperText = page.locator('ion-textarea .helper-text');
|
||||
const helperTextId = await helperText.getAttribute('id');
|
||||
const ariaDescribedBy = await textarea.getAttribute('aria-describedby');
|
||||
|
||||
expect(ariaDescribedBy).toBe(helperTextId);
|
||||
});
|
||||
test('error text should be visible when textarea is invalid', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`<ion-textarea label="Label" class="ion-invalid ion-touched" helper-text="Helper text" error-text="Error text"></ion-textarea>`,
|
||||
config
|
||||
);
|
||||
|
||||
const helperText = page.locator('ion-textarea .helper-text');
|
||||
const errorText = page.locator('ion-textarea .error-text');
|
||||
await expect(helperText).toBeHidden();
|
||||
await expect(errorText).toBeVisible();
|
||||
await expect(errorText).toHaveText('Error text');
|
||||
});
|
||||
|
||||
test('textarea should have an aria-describedby attribute when error text is present', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`<ion-textarea label="Label" class="ion-invalid ion-touched" helper-text="Helper text" error-text="Error text"></ion-textarea>`,
|
||||
config
|
||||
);
|
||||
|
||||
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 textarea is invalid', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`<ion-textarea label="Label" class="ion-invalid ion-touched" helper-text="Helper text" error-text="Error text"></ion-textarea>`,
|
||||
config
|
||||
);
|
||||
|
||||
const textarea = page.locator('ion-textarea textarea');
|
||||
|
||||
await expect(textarea).toHaveAttribute('aria-invalid');
|
||||
});
|
||||
test('textarea should not have aria-invalid attribute when textarea is valid', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`<ion-textarea label="Label" helper-text="Helper text" error-text="Error text"></ion-textarea>`,
|
||||
config
|
||||
);
|
||||
|
||||
const textarea = page.locator('ion-textarea textarea');
|
||||
|
||||
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(`<ion-textarea label="Label"></ion-textarea>`, config);
|
||||
|
||||
const textarea = page.locator('ion-textarea textarea');
|
||||
|
||||
await expect(textarea).not.toHaveAttribute('aria-describedby');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, screenshot, config }) => {
|
||||
test.describe(title('textarea: hint text'), () => {
|
||||
test.describe('textarea: hint text functionality', () => {
|
||||
test('helper text should be visible initially', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`<ion-textarea helper-text="my helper" error-text="my error" label="my textarea"></ion-textarea>`,
|
||||
config
|
||||
);
|
||||
/**
|
||||
* 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(`<ion-textarea label="Label" helper-text="Helper text"></ion-textarea>`, config);
|
||||
|
||||
const helperText = page.locator('ion-textarea .helper-text');
|
||||
const errorText = page.locator('ion-textarea .error-text');
|
||||
await expect(helperText).toBeVisible();
|
||||
await expect(helperText).toHaveText('my helper');
|
||||
await expect(errorText).toBeHidden();
|
||||
});
|
||||
test('textarea should have an aria-describedby attribute when helper text is present', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`<ion-textarea helper-text="my helper" error-text="my error" label="my textarea"></ion-textarea>`,
|
||||
config
|
||||
);
|
||||
|
||||
const textarea = page.locator('ion-textarea textarea');
|
||||
const helperText = page.locator('ion-textarea .helper-text');
|
||||
const helperTextId = await helperText.getAttribute('id');
|
||||
const ariaDescribedBy = await textarea.getAttribute('aria-describedby');
|
||||
|
||||
expect(ariaDescribedBy).toBe(helperTextId);
|
||||
});
|
||||
test('error text should be visible when textarea is invalid', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`<ion-textarea class="ion-invalid ion-touched" helper-text="my helper" error-text="my error" label="my textarea"></ion-textarea>`,
|
||||
config
|
||||
);
|
||||
|
||||
const helperText = page.locator('ion-textarea .helper-text');
|
||||
const errorText = page.locator('ion-textarea .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(
|
||||
`
|
||||
<style>
|
||||
ion-textarea.custom-textarea {
|
||||
--highlight-color-invalid: purple;
|
||||
}
|
||||
</style>
|
||||
<ion-textarea class="ion-invalid ion-touched custom-textarea" label="my label" error-text="my error"></ion-textarea>
|
||||
`,
|
||||
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(
|
||||
`<ion-textarea class="ion-invalid ion-touched" helper-text="my helper" error-text="my error" label="my textarea"></ion-textarea>`,
|
||||
config
|
||||
);
|
||||
|
||||
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(
|
||||
`<ion-textarea class="ion-invalid ion-touched" helper-text="my helper" error-text="my error" label="my textarea"></ion-textarea>`,
|
||||
config
|
||||
);
|
||||
|
||||
const textarea = page.locator('ion-textarea textarea');
|
||||
|
||||
await expect(textarea).toHaveAttribute('aria-invalid');
|
||||
});
|
||||
test('textarea should not have aria-invalid attribute when input is valid', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`<ion-textarea helper-text="my helper" error-text="my error" label="my textarea"></ion-textarea>`,
|
||||
config
|
||||
);
|
||||
|
||||
const textarea = page.locator('ion-textarea textarea');
|
||||
|
||||
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(`<ion-textarea label="my textarea"></ion-textarea>`, config);
|
||||
|
||||
const textarea = page.locator('ion-textarea textarea');
|
||||
|
||||
await expect(textarea).not.toHaveAttribute('aria-describedby');
|
||||
});
|
||||
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(`<ion-textarea helper-text="my helper" label="my textarea"></ion-textarea>`, config);
|
||||
test('should not have visual regressions when rendering helper text with wrapping text', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`<ion-textarea label="Label" helper-text="Helper text helper text helper text helper text helper text helper text helper text helper text helper text"></ion-textarea>`,
|
||||
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(
|
||||
`<ion-textarea class="ion-invalid ion-touched" error-text="my helper" label="my textarea"></ion-textarea>`,
|
||||
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(
|
||||
`<ion-textarea label="Label" label-placement="stacked" helper-text="Helper text"></ion-textarea>`,
|
||||
config
|
||||
);
|
||||
|
||||
const bottomEl = page.locator('ion-textarea .textarea-bottom');
|
||||
await expect(bottomEl).toHaveScreenshot(screenshot(`textarea-bottom-content-error`));
|
||||
});
|
||||
});
|
||||
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(
|
||||
`<ion-textarea label="Label" class="ion-invalid ion-touched" error-text="Error text"></ion-textarea>`,
|
||||
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(
|
||||
`<ion-textarea label="Label" class="ion-invalid ion-touched" error-text="Error text" label-placement="stacked"></ion-textarea>`,
|
||||
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: ['md'], directions: ['ltr'] }).forEach(({ title, screenshot, config }) => {
|
||||
test.describe(title('textarea: supporting text customization'), () => {
|
||||
test('should not have visual regressions when rendering helper text with custom css', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`
|
||||
<style>
|
||||
ion-textarea.custom-textarea.md .textarea-bottom .helper-text {
|
||||
font-size: 20px;
|
||||
color: green;
|
||||
}
|
||||
</style>
|
||||
<ion-textarea class="custom-textarea" label="Label" helper-text="Helper text"></ion-textarea>
|
||||
`,
|
||||
config
|
||||
);
|
||||
|
||||
const helperText = page.locator('ion-textarea');
|
||||
await expect(helperText).toHaveScreenshot(screenshot(`textarea-helper-text-custom-css`));
|
||||
});
|
||||
test('should not have visual regressions when rendering error text with custom css', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`
|
||||
<style>
|
||||
ion-textarea.custom-textarea.md .textarea-bottom .error-text {
|
||||
font-size: 20px;
|
||||
color: purple;
|
||||
}
|
||||
</style>
|
||||
<ion-textarea class="ion-invalid ion-touched custom-textarea" label="Label" error-text="Error text"></ion-textarea>
|
||||
`,
|
||||
config
|
||||
);
|
||||
|
||||
const errorText = page.locator('ion-textarea');
|
||||
await expect(errorText).toHaveScreenshot(screenshot(`textarea-error-text-custom-css`));
|
||||
});
|
||||
test('should not have visual regressions when rendering error text with a custom css variable', async ({
|
||||
page,
|
||||
}) => {
|
||||
await page.setContent(
|
||||
`
|
||||
<style>
|
||||
ion-textarea.custom-textarea {
|
||||
--highlight-color-invalid: purple;
|
||||
}
|
||||
</style>
|
||||
<ion-textarea class="ion-invalid ion-touched custom-textarea" label="Label" error-text="Error text"></ion-textarea>
|
||||
`,
|
||||
config
|
||||
);
|
||||
|
||||
const errorText = page.locator('ion-textarea');
|
||||
await expect(errorText).toHaveScreenshot(screenshot(`textarea-error-text-custom-css-var`));
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -139,7 +214,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(
|
||||
`
|
||||
<ion-textarea label="my label" counter="true" helper-text="helper text"></ion-textarea>
|
||||
<ion-textarea label="Label" counter="true" helper-text="helper text"></ion-textarea>
|
||||
`,
|
||||
config
|
||||
);
|
||||
@ -149,7 +224,7 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, screenshot, co
|
||||
test('default formatter should be used', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`
|
||||
<ion-textarea label="my label" counter="true" maxlength="20"></ion-textarea>
|
||||
<ion-textarea label="Label" counter="true" maxlength="20"></ion-textarea>
|
||||
`,
|
||||
config
|
||||
);
|
||||
@ -159,7 +234,7 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, screenshot, co
|
||||
test('custom formatter should be used when provided', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`
|
||||
<ion-textarea label="my label" counter="true" maxlength="20"></ion-textarea>
|
||||
<ion-textarea label="Label" counter="true" maxlength="20"></ion-textarea>
|
||||
<script>
|
||||
const textarea = document.querySelector('ion-textarea');
|
||||
textarea.counterFormatter = (inputLength, maxLength) => {
|
||||
@ -184,36 +259,31 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, screenshot, co
|
||||
});
|
||||
});
|
||||
test.describe('textarea: counter rendering', () => {
|
||||
test.describe('regular textareas', () => {
|
||||
test('should not have visual regressions when rendering counter', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`<ion-textarea counter="true" maxlength="20" label="my textarea"></ion-textarea>`,
|
||||
config
|
||||
);
|
||||
test('should not have visual regressions when rendering counter', async ({ page }) => {
|
||||
await page.setContent(`<ion-textarea counter="true" maxlength="20" label="Label"></ion-textarea>`, config);
|
||||
|
||||
const bottomEl = page.locator('ion-textarea .textarea-bottom');
|
||||
await expect(bottomEl).toHaveScreenshot(screenshot(`textarea-bottom-content-counter`));
|
||||
});
|
||||
const bottomEl = page.locator('ion-textarea');
|
||||
await expect(bottomEl).toHaveScreenshot(screenshot(`textarea-counter`));
|
||||
});
|
||||
|
||||
test('should not have visual regressions when rendering counter with helper text', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`<ion-textarea label="my textarea" counter="true" maxlength="20" helper-text="my helper"></ion-textarea>`,
|
||||
config
|
||||
);
|
||||
test('should not have visual regressions when rendering counter with helper text', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`<ion-textarea label="Label" counter="true" maxlength="20" helper-text="Helper"></ion-textarea>`,
|
||||
config
|
||||
);
|
||||
|
||||
const bottomEl = page.locator('ion-textarea .textarea-bottom');
|
||||
await expect(bottomEl).toHaveScreenshot(screenshot(`textarea-bottom-content-counter-helper-text`));
|
||||
});
|
||||
const bottomEl = page.locator('ion-textarea');
|
||||
await expect(bottomEl).toHaveScreenshot(screenshot(`textarea-counter-helper-text`));
|
||||
});
|
||||
|
||||
test('should not have visual regressions when rendering counter with error text', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`<ion-textarea class="ion-invalid ion-touched" label="my textarea" counter="true" maxlength="20" error-text="my error"></ion-textarea>`,
|
||||
config
|
||||
);
|
||||
test('should not have visual regressions when rendering counter with error text', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`<ion-textarea class="ion-invalid ion-touched" label="Label" counter="true" maxlength="20" error-text="Error text"></ion-textarea>`,
|
||||
config
|
||||
);
|
||||
|
||||
const bottomEl = page.locator('ion-textarea .textarea-bottom');
|
||||
await expect(bottomEl).toHaveScreenshot(screenshot(`textarea-bottom-content-counter-error-text`));
|
||||
});
|
||||
const bottomEl = page.locator('ion-textarea');
|
||||
await expect(bottomEl).toHaveScreenshot(screenshot(`textarea-counter-error-text`));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
Before Width: | Height: | Size: 4.6 KiB |
Before Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 4.6 KiB |
Before Width: | Height: | Size: 4.6 KiB |
Before Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 4.6 KiB |
Before Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 4.6 KiB |
Before Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 4.4 KiB |
Before Width: | Height: | Size: 4.7 KiB |
Before Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 4.7 KiB |
Before Width: | Height: | Size: 4.7 KiB |
Before Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 4.6 KiB |
Before Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 4.5 KiB |
Before Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 4.4 KiB |
Before Width: | Height: | Size: 2.3 KiB |
Before Width: | Height: | Size: 1.0 KiB |
Before Width: | Height: | Size: 2.3 KiB |
Before Width: | Height: | Size: 2.3 KiB |
Before Width: | Height: | Size: 1.0 KiB |
Before Width: | Height: | Size: 2.3 KiB |
Before Width: | Height: | Size: 698 B |
Before Width: | Height: | Size: 1018 B |
Before Width: | Height: | Size: 628 B |
Before Width: | Height: | Size: 2.3 KiB |
Before Width: | Height: | Size: 881 B |
Before Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 3.2 KiB |
Before Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 3.1 KiB |
Before Width: | Height: | Size: 3.2 KiB |
Before Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 3.1 KiB |
Before Width: | Height: | Size: 1.0 KiB |
Before Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 923 B |
Before Width: | Height: | Size: 3.0 KiB |
Before Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 3.0 KiB |
Before Width: | Height: | Size: 3.1 KiB |
Before Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 3.1 KiB |
Before Width: | Height: | Size: 3.1 KiB |
Before Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 3.0 KiB |
Before Width: | Height: | Size: 1.0 KiB |
Before Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 875 B |
Before Width: | Height: | Size: 2.9 KiB |
Before Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 2.9 KiB |
After Width: | Height: | Size: 2.3 KiB |
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 2.2 KiB |
After Width: | Height: | Size: 2.1 KiB |
After Width: | Height: | Size: 2.9 KiB |
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 2.5 KiB |
After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 873 B |
Before Width: | Height: | Size: 822 B |
Before Width: | Height: | Size: 751 B |
After Width: | Height: | Size: 2.5 KiB |
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 2.4 KiB |
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 2.6 KiB |
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 2.9 KiB |
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 2.6 KiB |
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 1.8 KiB |
After Width: | Height: | Size: 2.8 KiB |
After Width: | Height: | Size: 1.8 KiB |
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 2.6 KiB |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 2.5 KiB |
After Width: | Height: | Size: 3.7 KiB |