Skip to content

Commit cbf963d

Browse files
committed
test(input-otp): add helper functions and constants, warn for duplicate separators
1 parent 18e0f70 commit cbf963d

6 files changed

Lines changed: 99 additions & 61 deletions

File tree

core/src/components/input-otp/input-otp.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,15 @@ export class InputOTP implements ComponentInterface {
217217
.filter((pos) => !isNaN(pos));
218218
}
219219

220+
// Check for duplicate separator positions
221+
const duplicates = separatorValues.filter((pos, index) => separatorValues.indexOf(pos) !== index);
222+
if (duplicates.length > 0) {
223+
printIonWarning(
224+
`[ion-input-otp] - Duplicate separator positions are not allowed. Received: ${separators}`,
225+
this.el
226+
);
227+
}
228+
220229
const invalidSeparators = separatorValues.filter((pos) => pos > length);
221230
if (invalidSeparators.length > 0) {
222231
printIonWarning(

core/src/components/input-otp/test/color/input-otp.e2e.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import { expect } from '@playwright/test';
22
import { configs, test } from '@utils/test/playwright';
33

4+
const VALID_FILLS = ['outline', 'solid'];
5+
46
configs({ directions: ['ltr'] }).forEach(({ title, screenshot, config }) => {
57
test.describe(title('input-otp: color'), () => {
68
// Test all colors with all fills
7-
['outline', 'solid'].forEach((fill) => {
9+
VALID_FILLS.forEach((fill) => {
810
test(`color with ${fill} fill should not have visual regressions`, async ({ page }) => {
911
await page.setContent(
1012
`

core/src/components/input-otp/test/fill/input-otp.e2e.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import { expect } from '@playwright/test';
22
import { configs, test } from '@utils/test/playwright';
33

4+
const VALID_FILLS = ['outline', 'solid'];
5+
46
configs({ directions: ['ltr'] }).forEach(({ title, screenshot, config }) => {
57
test.describe(title('input-otp: fill'), () => {
6-
['outline', 'solid'].forEach((fill) => {
8+
VALID_FILLS.forEach((fill) => {
79
test(`${fill} fill should not have visual regressions`, async ({ page }) => {
810
await page.setContent(
911
`
Lines changed: 76 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,39 @@
1+
import type { ConsoleMessage } from '@playwright/test';
12
import { expect } from '@playwright/test';
3+
import type { E2EPage } from '@utils/test/playwright';
24
import { configs, test } from '@utils/test/playwright';
35

6+
const DEFAULT_INPUT_LENGTH = 4;
7+
const VALID_SIZES = ['small', 'medium', 'large'];
8+
9+
/**
10+
* Helper function to check if the next sibling after
11+
* the input box is a separator
12+
*/
13+
const hasSeparatorAfter = async (page: E2EPage, index: number): Promise<boolean> => {
14+
const wrappers = page.locator('.input-otp-group > .native-wrapper');
15+
return await wrappers
16+
.nth(index)
17+
.evaluate((el: Element) => el.nextElementSibling?.classList.contains('input-otp-separator') ?? false);
18+
};
19+
20+
/**
21+
* Helper function to collect console warnings
22+
*/
23+
const collectWarnings = async (page: E2EPage): Promise<string[]> => {
24+
const warnings: string[] = [];
25+
page.on('console', (ev: ConsoleMessage) => {
26+
if (ev.type() === 'warning') {
27+
warnings.push(ev.text());
28+
}
29+
});
30+
return warnings;
31+
};
32+
433
configs({ directions: ['ltr'] }).forEach(({ title, screenshot, config }) => {
534
test.describe(title('input-otp: separators'), () => {
635
// Test separators with all sizes
7-
['small', 'medium', 'large'].forEach((size) => {
36+
VALID_SIZES.forEach((size) => {
837
test(`one separator with ${size} size should not have visual regressions`, async ({ page }) => {
938
await page.setContent(
1039
`
@@ -52,17 +81,10 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
5281
test('should render separators after the first and third input box', async ({ page }) => {
5382
await page.setContent(`<ion-input-otp separators="1,3">Description</ion-input-otp>`, config);
5483

55-
const group = page.locator('.input-otp-group');
56-
const wrappers = group.locator('> .native-wrapper');
57-
58-
// Helper to check if the next sibling is a separator
59-
const hasSeparatorAfter = async (index: number) =>
60-
wrappers.nth(index).evaluate((el) => el.nextElementSibling?.classList.contains('input-otp-separator') ?? false);
61-
62-
await expect(await hasSeparatorAfter(0)).toBe(true);
63-
await expect(await hasSeparatorAfter(1)).toBe(false);
64-
await expect(await hasSeparatorAfter(2)).toBe(true);
65-
await expect(await hasSeparatorAfter(3)).toBe(false);
84+
await expect(await hasSeparatorAfter(page, 0)).toBe(true);
85+
await expect(await hasSeparatorAfter(page, 1)).toBe(false);
86+
await expect(await hasSeparatorAfter(page, 2)).toBe(true);
87+
await expect(await hasSeparatorAfter(page, 3)).toBe(false);
6688
});
6789

6890
test('should render separators after the second and third input box', async ({ page }) => {
@@ -73,61 +95,42 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
7395
el.separators = [2, 3];
7496
});
7597

76-
const group = page.locator('.input-otp-group');
77-
const wrappers = group.locator('> .native-wrapper');
78-
79-
// Helper to check if the next sibling is a separator
80-
const hasSeparatorAfter = async (index: number) =>
81-
wrappers.nth(index).evaluate((el) => el.nextElementSibling?.classList.contains('input-otp-separator') ?? false);
82-
83-
await expect(await hasSeparatorAfter(0)).toBe(false);
84-
await expect(await hasSeparatorAfter(1)).toBe(true);
85-
await expect(await hasSeparatorAfter(2)).toBe(true);
86-
await expect(await hasSeparatorAfter(3)).toBe(false);
98+
await expect(await hasSeparatorAfter(page, 0)).toBe(false);
99+
await expect(await hasSeparatorAfter(page, 1)).toBe(true);
100+
await expect(await hasSeparatorAfter(page, 2)).toBe(true);
101+
await expect(await hasSeparatorAfter(page, 3)).toBe(false);
87102
});
88103

89104
test('should render all separators', async ({ page }) => {
90105
await page.setContent(`<ion-input-otp separators="all">Description</ion-input-otp>`, config);
91106

92-
const group = page.locator('.input-otp-group');
93-
const wrappers = group.locator('> .native-wrapper');
107+
await expect(await hasSeparatorAfter(page, 0)).toBe(true);
108+
await expect(await hasSeparatorAfter(page, 1)).toBe(true);
109+
await expect(await hasSeparatorAfter(page, 2)).toBe(true);
110+
await expect(await hasSeparatorAfter(page, 3)).toBe(false);
111+
});
94112

95-
// Helper to check if the next sibling is a separator
96-
const hasSeparatorAfter = async (index: number) =>
97-
wrappers.nth(index).evaluate((el) => el.nextElementSibling?.classList.contains('input-otp-separator') ?? false);
113+
test('should handle empty separators string', async ({ page }) => {
114+
await page.setContent(`<ion-input-otp separators="">Description</ion-input-otp>`, config);
98115

99-
await expect(await hasSeparatorAfter(0)).toBe(true);
100-
await expect(await hasSeparatorAfter(1)).toBe(true);
101-
await expect(await hasSeparatorAfter(2)).toBe(true);
102-
await expect(await hasSeparatorAfter(3)).toBe(false);
116+
await expect(await hasSeparatorAfter(page, 0)).toBe(false);
117+
await expect(await hasSeparatorAfter(page, 1)).toBe(false);
118+
await expect(await hasSeparatorAfter(page, 2)).toBe(false);
119+
await expect(await hasSeparatorAfter(page, 3)).toBe(false);
103120
});
104121

105122
test('should warn when setting separators to a position greater than the input length', async ({ page }) => {
106-
const warnings: string[] = [];
107-
108-
page.on('console', (ev) => {
109-
if (ev.type() === 'warning') {
110-
warnings.push(ev.text());
111-
}
112-
});
113-
123+
const warnings = await collectWarnings(page);
114124
await page.setContent(`<ion-input-otp separators="1,3,5,6,7">Description</ion-input-otp>`, config);
115125

116126
expect(warnings.length).toBe(1);
117127
expect(warnings[0]).toContain(
118-
'[Ionic Warning]: [ion-input-otp] - The following separator positions are greater than the input length (4): 5, 6, 7. These separators will be ignored.'
128+
`[Ionic Warning]: [ion-input-otp] - The following separator positions are greater than the input length (${DEFAULT_INPUT_LENGTH}): 5, 6, 7. These separators will be ignored.`
119129
);
120130
});
121131

122132
test('should warn when setting separators to an invalid space-separated string', async ({ page }) => {
123-
const warnings: string[] = [];
124-
125-
page.on('console', (ev) => {
126-
if (ev.type() === 'warning') {
127-
warnings.push(ev.text());
128-
}
129-
});
130-
133+
const warnings = await collectWarnings(page);
131134
const invalidSeparators = '1 2 3';
132135

133136
await page.setContent(`<ion-input-otp separators="${invalidSeparators}">Description</ion-input-otp>`, config);
@@ -139,15 +142,20 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
139142
});
140143

141144
test('should warn when setting separators to an invalid comma-separated string', async ({ page }) => {
142-
const warnings: string[] = [];
145+
const warnings = await collectWarnings(page);
146+
const invalidSeparators = '1,d,3';
143147

144-
page.on('console', (ev) => {
145-
if (ev.type() === 'warning') {
146-
warnings.push(ev.text());
147-
}
148-
});
148+
await page.setContent(`<ion-input-otp separators="${invalidSeparators}">Description</ion-input-otp>`, config);
149149

150-
const invalidSeparators = '1,d,3';
150+
expect(warnings.length).toBe(1);
151+
expect(warnings[0]).toContain(
152+
`[Ionic Warning]: [ion-input-otp] - Invalid separators format. Expected a comma-separated list of numbers, an array of numbers, or "all". Received: ${invalidSeparators}`
153+
);
154+
});
155+
156+
test('should warn when setting separators to negative numbers', async ({ page }) => {
157+
const warnings = await collectWarnings(page);
158+
const invalidSeparators = '-1,2,3';
151159

152160
await page.setContent(`<ion-input-otp separators="${invalidSeparators}">Description</ion-input-otp>`, config);
153161

@@ -156,5 +164,17 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
156164
`[Ionic Warning]: [ion-input-otp] - Invalid separators format. Expected a comma-separated list of numbers, an array of numbers, or "all". Received: ${invalidSeparators}`
157165
);
158166
});
167+
168+
test('should warn when setting separators to duplicate positions', async ({ page }) => {
169+
const warnings = await collectWarnings(page);
170+
const invalidSeparators = '1,1,2';
171+
172+
await page.setContent(`<ion-input-otp separators="${invalidSeparators}">Description</ion-input-otp>`, config);
173+
174+
expect(warnings.length).toBe(1);
175+
expect(warnings[0]).toContain(
176+
`[Ionic Warning]: [ion-input-otp] - Duplicate separator positions are not allowed. Received: ${invalidSeparators}`
177+
);
178+
});
159179
});
160180
});

core/src/components/input-otp/test/shape/input-otp.e2e.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
import { expect } from '@playwright/test';
22
import { configs, test } from '@utils/test/playwright';
33

4+
const VALID_SHAPES = ['rectangular', 'round', 'soft'];
5+
const VALID_SIZES = ['small', 'medium', 'large'];
6+
47
configs({ directions: ['ltr'] }).forEach(({ title, screenshot, config }) => {
58
test.describe(title('input-otp: shape'), () => {
69
// Test all shapes with all sizes
7-
['rectangular', 'round', 'soft'].forEach((shape) => {
8-
['small', 'medium', 'large'].forEach((size) => {
10+
VALID_SHAPES.forEach((shape) => {
11+
VALID_SIZES.forEach((size) => {
912
test(`${shape} shape with ${size} size should not have visual regressions`, async ({ page }) => {
1013
await page.setContent(
1114
`

core/src/components/input-otp/test/size/input-otp.e2e.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import { expect } from '@playwright/test';
22
import { configs, test } from '@utils/test/playwright';
33

4+
const VALID_SIZES = ['small', 'medium', 'large'];
5+
46
configs({ directions: ['ltr'] }).forEach(({ title, screenshot, config }) => {
57
test.describe(title('input-otp: size'), () => {
6-
['small', 'medium', 'large'].forEach((size) => {
8+
VALID_SIZES.forEach((size) => {
79
test(`${size} size should not have visual regressions`, async ({ page }) => {
810
await page.setContent(
911
`

0 commit comments

Comments
 (0)