@pwtap/create 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/core-manifest.json +50 -0
- package/dist/commands/add.d.ts +8 -0
- package/dist/commands/add.d.ts.map +1 -0
- package/dist/commands/add.js +9 -0
- package/dist/commands/add.js.map +1 -0
- package/dist/commands/create.d.ts +12 -0
- package/dist/commands/create.d.ts.map +1 -0
- package/dist/commands/create.js +114 -0
- package/dist/commands/create.js.map +1 -0
- package/dist/commands/remove.d.ts +7 -0
- package/dist/commands/remove.d.ts.map +1 -0
- package/dist/commands/remove.js +9 -0
- package/dist/commands/remove.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +55 -0
- package/dist/index.js.map +1 -0
- package/dist/injectors/assets.d.ts +6 -0
- package/dist/injectors/assets.d.ts.map +1 -0
- package/dist/injectors/assets.js +53 -0
- package/dist/injectors/assets.js.map +1 -0
- package/dist/injectors/envJson.d.ts +6 -0
- package/dist/injectors/envJson.d.ts.map +1 -0
- package/dist/injectors/envJson.js +39 -0
- package/dist/injectors/envJson.js.map +1 -0
- package/dist/injectors/fixturesBarrel.d.ts +11 -0
- package/dist/injectors/fixturesBarrel.d.ts.map +1 -0
- package/dist/injectors/fixturesBarrel.js +66 -0
- package/dist/injectors/fixturesBarrel.js.map +1 -0
- package/dist/injectors/packageJson.d.ts +6 -0
- package/dist/injectors/packageJson.d.ts.map +1 -0
- package/dist/injectors/packageJson.js +25 -0
- package/dist/injectors/packageJson.js.map +1 -0
- package/dist/injectors/pwConfig.d.ts +10 -0
- package/dist/injectors/pwConfig.d.ts.map +1 -0
- package/dist/injectors/pwConfig.js +40 -0
- package/dist/injectors/pwConfig.js.map +1 -0
- package/dist/manifest.d.ts +65 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/manifest.js +22 -0
- package/dist/manifest.js.map +1 -0
- package/dist/plugin-apply.d.ts +13 -0
- package/dist/plugin-apply.d.ts.map +1 -0
- package/dist/plugin-apply.js +89 -0
- package/dist/plugin-apply.js.map +1 -0
- package/dist/prompts.d.ts +22 -0
- package/dist/prompts.d.ts.map +1 -0
- package/dist/prompts.js +65 -0
- package/dist/prompts.js.map +1 -0
- package/dist/registry.d.ts +26 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +34 -0
- package/dist/registry.js.map +1 -0
- package/dist/util/fs.d.ts +13 -0
- package/dist/util/fs.d.ts.map +1 -0
- package/dist/util/fs.js +32 -0
- package/dist/util/fs.js.map +1 -0
- package/dist/util/log.d.ts +8 -0
- package/dist/util/log.d.ts.map +1 -0
- package/dist/util/log.js +9 -0
- package/dist/util/log.js.map +1 -0
- package/dist/util/markers.d.ts +20 -0
- package/dist/util/markers.d.ts.map +1 -0
- package/dist/util/markers.js +51 -0
- package/dist/util/markers.js.map +1 -0
- package/dist/util/run.d.ts +8 -0
- package/dist/util/run.d.ts.map +1 -0
- package/dist/util/run.js +27 -0
- package/dist/util/run.js.map +1 -0
- package/package.json +31 -0
- package/template/.commitlintrc.json +3 -0
- package/template/.prettierrc +17 -0
- package/template/api/core/ApiClient.ts +99 -0
- package/template/api/core/types.ts +29 -0
- package/template/api/index.ts +4 -0
- package/template/api/models/pet.ts +24 -0
- package/template/api/services/PetService.ts +67 -0
- package/template/config/envUtils.ts +70 -0
- package/template/config/index.ts +2 -0
- package/template/config/loadEnv.ts +110 -0
- package/template/env/environments.example.json +20 -0
- package/template/eslint.config.js +74 -0
- package/template/fixtures/api.ts +46 -0
- package/template/fixtures/auth.ts +150 -0
- package/template/fixtures/index.ts +38 -0
- package/template/fixtures/ui.ts +112 -0
- package/template/pages/BasePage.ts +225 -0
- package/template/pages/LoginPage.ts +50 -0
- package/template/pages/index.ts +2 -0
- package/template/playwright.config.ts +71 -0
- package/template/templates/gitignore +19 -0
- package/template/testData/users.example.json +17 -0
- package/template/tests/api/pet.api.ts +60 -0
- package/template/tests/example/authSession.spec.ts +34 -0
- package/template/tests/example/login.spec.ts +50 -0
- package/template/tsconfig.json +33 -0
- package/template/utils/apiUtils.ts +224 -0
- package/template/utils/dateUtils.ts +158 -0
- package/template/utils/index.ts +15 -0
- package/template/utils/stringUtils.ts +192 -0
- package/template/utils/uiUtils.ts +236 -0
- package/template/utils/validationUtils.ts +190 -0
- package/template/utils/waitUtils.ts +207 -0
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* String Utility Functions
|
|
3
|
+
* Provides string manipulation and formatting helpers
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export class StringUtils {
|
|
7
|
+
/**
|
|
8
|
+
* Generate random string
|
|
9
|
+
*/
|
|
10
|
+
static generateRandomString(length: number = 10): string {
|
|
11
|
+
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
|
12
|
+
let result = '';
|
|
13
|
+
for (let i = 0; i < length; i++) {
|
|
14
|
+
result += characters.charAt(Math.floor(Math.random() * characters.length));
|
|
15
|
+
}
|
|
16
|
+
return result;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Generate random alphanumeric string
|
|
21
|
+
*/
|
|
22
|
+
static generateRandomAlphanumeric(length: number = 10): string {
|
|
23
|
+
return this.generateRandomString(length);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Generate random numeric string
|
|
28
|
+
*/
|
|
29
|
+
static generateRandomNumeric(length: number = 10): string {
|
|
30
|
+
let result = '';
|
|
31
|
+
for (let i = 0; i < length; i++) {
|
|
32
|
+
result += Math.floor(Math.random() * 10);
|
|
33
|
+
}
|
|
34
|
+
return result;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Generate random email
|
|
39
|
+
*/
|
|
40
|
+
static generateRandomEmail(domain: string = 'example.com'): string {
|
|
41
|
+
const username = this.generateRandomString(10).toLowerCase();
|
|
42
|
+
return `${username}@${domain}`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Generate a timestamp-based email, unique per run.
|
|
47
|
+
*
|
|
48
|
+
* @param prefix - Username prefix (default: 'test_user')
|
|
49
|
+
* @param domain - Email domain (default: 'example.com')
|
|
50
|
+
* @param digits - Number of trailing timestamp digits to append (default: 4)
|
|
51
|
+
* @returns Email like "test_user_5821@example.com"
|
|
52
|
+
*/
|
|
53
|
+
static generateTimestampEmail(
|
|
54
|
+
prefix: string = 'test_user',
|
|
55
|
+
domain: string = 'example.com',
|
|
56
|
+
digits: number = 4,
|
|
57
|
+
): string {
|
|
58
|
+
const timestamp = Date.now().toString();
|
|
59
|
+
const suffix = timestamp.slice(-digits);
|
|
60
|
+
return `${prefix}_${suffix}@${domain}`;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Generate random phone number
|
|
65
|
+
* @param countryCode - Country code (default: '+1')
|
|
66
|
+
* @param prefix - Phone number prefix (default: '555')
|
|
67
|
+
* @param length - Number of random digits after prefix (default: 7)
|
|
68
|
+
* @returns Phone number in format like "+1 5551234567"
|
|
69
|
+
*/
|
|
70
|
+
static generateRandomPhoneNumber(
|
|
71
|
+
countryCode: string = '+1',
|
|
72
|
+
prefix: string = '555',
|
|
73
|
+
length: number = 7,
|
|
74
|
+
): string {
|
|
75
|
+
const randomDigits = this.generateRandomNumeric(length);
|
|
76
|
+
return `${countryCode} ${prefix}${randomDigits}`;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Generate UUID v4
|
|
81
|
+
*/
|
|
82
|
+
static generateUUID(): string {
|
|
83
|
+
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
|
|
84
|
+
const r = (Math.random() * 16) | 0;
|
|
85
|
+
const v = c === 'x' ? r : (r & 0x3) | 0x8;
|
|
86
|
+
return v.toString(16);
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Capitalize first letter
|
|
92
|
+
*/
|
|
93
|
+
static capitalize(str: string): string {
|
|
94
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Convert to title case
|
|
99
|
+
*/
|
|
100
|
+
static toTitleCase(str: string): string {
|
|
101
|
+
return str
|
|
102
|
+
.toLowerCase()
|
|
103
|
+
.split(' ')
|
|
104
|
+
.map(word => this.capitalize(word))
|
|
105
|
+
.join(' ');
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Convert to camelCase
|
|
110
|
+
*/
|
|
111
|
+
static toCamelCase(str: string): string {
|
|
112
|
+
return str
|
|
113
|
+
.replace(/(?:^\w|[A-Z]|\b\w)/g, (word, index) =>
|
|
114
|
+
index === 0 ? word.toLowerCase() : word.toUpperCase(),
|
|
115
|
+
)
|
|
116
|
+
.replace(/\s+/g, '');
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Convert to snake_case
|
|
121
|
+
*/
|
|
122
|
+
static toSnakeCase(str: string): string {
|
|
123
|
+
return str
|
|
124
|
+
.replace(/\W+/g, ' ')
|
|
125
|
+
.split(/ |\B(?=[A-Z])/)
|
|
126
|
+
.map(word => word.toLowerCase())
|
|
127
|
+
.join('_');
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Convert to kebab-case
|
|
132
|
+
*/
|
|
133
|
+
static toKebabCase(str: string): string {
|
|
134
|
+
return str
|
|
135
|
+
.replace(/\W+/g, ' ')
|
|
136
|
+
.split(/ |\B(?=[A-Z])/)
|
|
137
|
+
.map(word => word.toLowerCase())
|
|
138
|
+
.join('-');
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Truncate string to specified length
|
|
143
|
+
*/
|
|
144
|
+
static truncate(str: string, length: number, suffix: string = '...'): string {
|
|
145
|
+
if (str.length <= length) return str;
|
|
146
|
+
return str.slice(0, length - suffix.length) + suffix;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Check if string is empty or whitespace
|
|
151
|
+
*/
|
|
152
|
+
static isBlank(str: string | null | undefined): boolean {
|
|
153
|
+
return !str || str.trim().length === 0;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Remove extra whitespace
|
|
158
|
+
*/
|
|
159
|
+
static normalizeWhitespace(str: string): string {
|
|
160
|
+
return str.trim().replace(/\s+/g, ' ');
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Escape HTML special characters
|
|
165
|
+
*/
|
|
166
|
+
static escapeHtml(str: string): string {
|
|
167
|
+
const htmlEntities: Record<string, string> = {
|
|
168
|
+
'&': '&',
|
|
169
|
+
'<': '<',
|
|
170
|
+
'>': '>',
|
|
171
|
+
'"': '"',
|
|
172
|
+
"'": ''',
|
|
173
|
+
};
|
|
174
|
+
return str.replace(/[&<>"']/g, char => htmlEntities[char]);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Extract numbers from string
|
|
179
|
+
*/
|
|
180
|
+
static extractNumbers(str: string): string {
|
|
181
|
+
return str.replace(/\D/g, '');
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Mask string (e.g., for sensitive data)
|
|
186
|
+
*/
|
|
187
|
+
static mask(str: string, visibleChars: number = 4, maskChar: string = '*'): string {
|
|
188
|
+
if (str.length <= visibleChars) return str;
|
|
189
|
+
const masked = maskChar.repeat(str.length - visibleChars);
|
|
190
|
+
return masked + str.slice(-visibleChars);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import type { Locator, Page } from '@playwright/test';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* UI Utility Functions for Playwright Tests
|
|
5
|
+
* Provides common UI interaction helpers
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export class UIUtils {
|
|
9
|
+
/**
|
|
10
|
+
* Scroll to element and make it visible
|
|
11
|
+
*/
|
|
12
|
+
static async scrollToElement(element: Locator): Promise<void> {
|
|
13
|
+
await element.scrollIntoViewIfNeeded();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
static async clickButton(page: Page, button: string): Promise<void> {
|
|
17
|
+
await page.getByRole('button', { name: button }).first().click();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Click element with retry logic
|
|
22
|
+
*/
|
|
23
|
+
static async clickWithRetry(
|
|
24
|
+
element: Locator,
|
|
25
|
+
maxRetries: number = 3,
|
|
26
|
+
delay: number = 1000,
|
|
27
|
+
): Promise<void> {
|
|
28
|
+
for (let i = 0; i < maxRetries; i++) {
|
|
29
|
+
try {
|
|
30
|
+
await element.click({ timeout: 5000 });
|
|
31
|
+
return;
|
|
32
|
+
} catch (error) {
|
|
33
|
+
if (i === maxRetries - 1) throw error;
|
|
34
|
+
await new Promise(resolve => setTimeout(resolve, delay));
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Force click on element (useful for hidden or overlapped elements)
|
|
41
|
+
*/
|
|
42
|
+
static async forceClick(element: Locator): Promise<void> {
|
|
43
|
+
await element.click({ force: true });
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Double click on element
|
|
48
|
+
*/
|
|
49
|
+
static async doubleClick(element: Locator): Promise<void> {
|
|
50
|
+
await element.dblclick();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Right click on element
|
|
55
|
+
*/
|
|
56
|
+
static async rightClick(element: Locator): Promise<void> {
|
|
57
|
+
await element.click({ button: 'right' });
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Hover over element
|
|
62
|
+
*/
|
|
63
|
+
static async hover(element: Locator): Promise<void> {
|
|
64
|
+
await element.hover();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Fill input with clearing first
|
|
69
|
+
*/
|
|
70
|
+
static async fillInput(element: Locator, text: string): Promise<void> {
|
|
71
|
+
await element.clear();
|
|
72
|
+
await element.fill(text);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Type text slowly (character by character)
|
|
77
|
+
*/
|
|
78
|
+
static async typeSlowly(element: Locator, text: string, delay: number = 100): Promise<void> {
|
|
79
|
+
await element.click();
|
|
80
|
+
await element.pressSequentially(text, { delay });
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Select option from dropdown by value
|
|
85
|
+
*/
|
|
86
|
+
static async selectDropdownByValue(element: Locator, value: string): Promise<void> {
|
|
87
|
+
await element.selectOption({ value });
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Select option from dropdown by label
|
|
92
|
+
*/
|
|
93
|
+
static async selectDropdownByLabel(element: Locator, label: string): Promise<void> {
|
|
94
|
+
await element.selectOption({ label });
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Select option from dropdown by index
|
|
99
|
+
*/
|
|
100
|
+
static async selectDropdownByIndex(element: Locator, index: number): Promise<void> {
|
|
101
|
+
await element.selectOption({ index });
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Check checkbox
|
|
106
|
+
*/
|
|
107
|
+
static async checkCheckbox(element: Locator): Promise<void> {
|
|
108
|
+
await element.check();
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Uncheck checkbox
|
|
113
|
+
*/
|
|
114
|
+
static async uncheckCheckbox(element: Locator): Promise<void> {
|
|
115
|
+
await element.uncheck();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Toggle checkbox
|
|
120
|
+
*/
|
|
121
|
+
static async toggleCheckbox(element: Locator): Promise<void> {
|
|
122
|
+
const isChecked = await element.isChecked();
|
|
123
|
+
if (isChecked) {
|
|
124
|
+
await element.uncheck();
|
|
125
|
+
} else {
|
|
126
|
+
await element.check();
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Get element text content
|
|
132
|
+
*/
|
|
133
|
+
static async getText(element: Locator): Promise<string> {
|
|
134
|
+
return (await element.textContent()) || '';
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Get element input value
|
|
139
|
+
*/
|
|
140
|
+
static async getValue(element: Locator): Promise<string> {
|
|
141
|
+
return await element.inputValue();
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Check if element is visible
|
|
146
|
+
*/
|
|
147
|
+
static async isVisible(element: Locator): Promise<boolean> {
|
|
148
|
+
return await element.isVisible();
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Check if element is enabled
|
|
153
|
+
*/
|
|
154
|
+
static async isEnabled(element: Locator): Promise<boolean> {
|
|
155
|
+
return await element.isEnabled();
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Check if element exists in DOM
|
|
160
|
+
*/
|
|
161
|
+
static async exists(element: Locator): Promise<boolean> {
|
|
162
|
+
return (await element.count()) > 0;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Upload file to input
|
|
167
|
+
*/
|
|
168
|
+
static async uploadFile(element: Locator, filePath: string | string[]): Promise<void> {
|
|
169
|
+
await element.setInputFiles(filePath);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Clear file input
|
|
174
|
+
*/
|
|
175
|
+
static async clearFileInput(element: Locator): Promise<void> {
|
|
176
|
+
await element.setInputFiles([]);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Get attribute value
|
|
181
|
+
*/
|
|
182
|
+
static async getAttribute(element: Locator, name: string): Promise<string | null> {
|
|
183
|
+
return await element.getAttribute(name);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Focus element
|
|
188
|
+
*/
|
|
189
|
+
static async focus(element: Locator): Promise<void> {
|
|
190
|
+
await element.focus();
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Blur element
|
|
195
|
+
*/
|
|
196
|
+
static async blur(element: Locator): Promise<void> {
|
|
197
|
+
await element.blur();
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Press keyboard key on element
|
|
202
|
+
*/
|
|
203
|
+
static async pressKey(element: Locator, key: string): Promise<void> {
|
|
204
|
+
await element.press(key);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Drag and drop element
|
|
209
|
+
*/
|
|
210
|
+
static async dragAndDrop(source: Locator, target: Locator): Promise<void> {
|
|
211
|
+
await source.dragTo(target);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Take screenshot of element
|
|
216
|
+
*/
|
|
217
|
+
static async screenshot(element: Locator, path: string): Promise<Buffer> {
|
|
218
|
+
return await element.screenshot({ path });
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Wait for element animation to complete
|
|
223
|
+
*/
|
|
224
|
+
static async waitForAnimation(element: Locator, timeout: number = 1000): Promise<void> {
|
|
225
|
+
await element.evaluate((el, t) => {
|
|
226
|
+
return new Promise<void>(resolve => {
|
|
227
|
+
const animations = el.getAnimations();
|
|
228
|
+
if (animations.length === 0) {
|
|
229
|
+
setTimeout(resolve, t);
|
|
230
|
+
} else {
|
|
231
|
+
Promise.all(animations.map(a => a.finished)).then(() => resolve());
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
}, timeout);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validation Utility Functions
|
|
3
|
+
* Provides data validation helpers
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export class ValidationUtils {
|
|
7
|
+
/**
|
|
8
|
+
* Validate email format
|
|
9
|
+
*/
|
|
10
|
+
static isValidEmail(email: string): boolean {
|
|
11
|
+
const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
|
|
12
|
+
return emailRegex.test(email);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Validate password strength
|
|
17
|
+
*/
|
|
18
|
+
static isStrongPassword(
|
|
19
|
+
password: string,
|
|
20
|
+
options?: {
|
|
21
|
+
minLength?: number;
|
|
22
|
+
requireUppercase?: boolean;
|
|
23
|
+
requireLowercase?: boolean;
|
|
24
|
+
requireNumbers?: boolean;
|
|
25
|
+
requireSpecialChars?: boolean;
|
|
26
|
+
},
|
|
27
|
+
): { valid: boolean; errors: string[] } {
|
|
28
|
+
const {
|
|
29
|
+
minLength = 8,
|
|
30
|
+
requireUppercase = true,
|
|
31
|
+
requireLowercase = true,
|
|
32
|
+
requireNumbers = true,
|
|
33
|
+
requireSpecialChars = true,
|
|
34
|
+
} = options || {};
|
|
35
|
+
|
|
36
|
+
const errors: string[] = [];
|
|
37
|
+
|
|
38
|
+
if (password.length < minLength) {
|
|
39
|
+
errors.push(`Password must be at least ${minLength} characters long`);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (requireUppercase && !/[A-Z]/.test(password)) {
|
|
43
|
+
errors.push('Password must contain at least one uppercase letter');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (requireLowercase && !/[a-z]/.test(password)) {
|
|
47
|
+
errors.push('Password must contain at least one lowercase letter');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (requireNumbers && !/\d/.test(password)) {
|
|
51
|
+
errors.push('Password must contain at least one number');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (requireSpecialChars && !/[!@#$%^&*(),.?":{}|<>]/.test(password)) {
|
|
55
|
+
errors.push('Password must contain at least one special character');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
valid: errors.length === 0,
|
|
60
|
+
errors,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Validate phone number
|
|
66
|
+
*/
|
|
67
|
+
static isValidPhoneNumber(phone: string, countryCode?: string): boolean {
|
|
68
|
+
// Remove all non-numeric characters
|
|
69
|
+
const cleaned = phone.replace(/\D/g, '');
|
|
70
|
+
|
|
71
|
+
// Basic validation (10-15 digits)
|
|
72
|
+
if (cleaned.length < 10 || cleaned.length > 15) {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Country-specific validation
|
|
77
|
+
if (countryCode === 'US') {
|
|
78
|
+
return /^1?\d{10}$/.test(cleaned);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Validate URL format
|
|
86
|
+
*/
|
|
87
|
+
static isValidURL(url: string): boolean {
|
|
88
|
+
try {
|
|
89
|
+
const urlObj = new URL(url);
|
|
90
|
+
return ['http:', 'https:'].includes(urlObj.protocol);
|
|
91
|
+
} catch {
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Validate credit card number (Luhn algorithm)
|
|
98
|
+
*/
|
|
99
|
+
static isValidCreditCard(cardNumber: string): boolean {
|
|
100
|
+
const cleaned = cardNumber.replace(/\D/g, '');
|
|
101
|
+
|
|
102
|
+
if (cleaned.length < 13 || cleaned.length > 19) {
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
let sum = 0;
|
|
107
|
+
let isEven = false;
|
|
108
|
+
|
|
109
|
+
for (let i = cleaned.length - 1; i >= 0; i--) {
|
|
110
|
+
let digit = parseInt(cleaned[i], 10);
|
|
111
|
+
|
|
112
|
+
if (isEven) {
|
|
113
|
+
digit *= 2;
|
|
114
|
+
if (digit > 9) {
|
|
115
|
+
digit -= 9;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
sum += digit;
|
|
120
|
+
isEven = !isEven;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return sum % 10 === 0;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Validate date string
|
|
128
|
+
*/
|
|
129
|
+
static isValidDate(dateString: string): boolean {
|
|
130
|
+
const date = new Date(dateString);
|
|
131
|
+
return !isNaN(date.getTime());
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Validate JSON string
|
|
136
|
+
*/
|
|
137
|
+
static isValidJSON(str: string): boolean {
|
|
138
|
+
try {
|
|
139
|
+
JSON.parse(str);
|
|
140
|
+
return true;
|
|
141
|
+
} catch {
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Validate UUID format
|
|
148
|
+
*/
|
|
149
|
+
static isValidUUID(uuid: string): boolean {
|
|
150
|
+
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
151
|
+
return uuidRegex.test(uuid);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Validate IP address (v4)
|
|
156
|
+
*/
|
|
157
|
+
static isValidIPv4(ip: string): boolean {
|
|
158
|
+
const ipv4Regex =
|
|
159
|
+
/^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
|
|
160
|
+
return ipv4Regex.test(ip);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Check if value is within range
|
|
165
|
+
*/
|
|
166
|
+
static isInRange(value: number, min: number, max: number): boolean {
|
|
167
|
+
return value >= min && value <= max;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Check if string matches pattern
|
|
172
|
+
*/
|
|
173
|
+
static matchesPattern(str: string, pattern: RegExp): boolean {
|
|
174
|
+
return pattern.test(str);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Validate array is not empty
|
|
179
|
+
*/
|
|
180
|
+
static isNonEmptyArray(arr: unknown): boolean {
|
|
181
|
+
return Array.isArray(arr) && arr.length > 0;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Validate object has required properties
|
|
186
|
+
*/
|
|
187
|
+
static hasRequiredProperties(obj: Record<string, unknown>, requiredProps: string[]): boolean {
|
|
188
|
+
return requiredProps.every(prop => prop in obj && obj[prop] !== undefined);
|
|
189
|
+
}
|
|
190
|
+
}
|