@tft/crispr-forms-testing 21.0.1
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/CLAUDE.md +105 -0
- package/field-tests/autocomplete-chiplist.tests.d.ts +10 -0
- package/field-tests/autocomplete-chiplist.tests.js +51 -0
- package/field-tests/autocomplete-chiplist.tests.js.map +1 -0
- package/field-tests/autocomplete.tests.d.ts +11 -0
- package/field-tests/autocomplete.tests.js +42 -0
- package/field-tests/autocomplete.tests.js.map +1 -0
- package/field-tests/checkbox.tests.d.ts +6 -0
- package/field-tests/checkbox.tests.js +27 -0
- package/field-tests/checkbox.tests.js.map +1 -0
- package/field-tests/datepicker.tests.d.ts +6 -0
- package/field-tests/datepicker.tests.js +30 -0
- package/field-tests/datepicker.tests.js.map +1 -0
- package/field-tests/field-test-factory.d.ts +21 -0
- package/field-tests/field-test-factory.js +69 -0
- package/field-tests/field-test-factory.js.map +1 -0
- package/field-tests/file-upload.tests.d.ts +6 -0
- package/field-tests/file-upload.tests.js +27 -0
- package/field-tests/file-upload.tests.js.map +1 -0
- package/field-tests/input.tests.d.ts +15 -0
- package/field-tests/input.tests.js +73 -0
- package/field-tests/input.tests.js.map +1 -0
- package/field-tests/select.tests.d.ts +6 -0
- package/field-tests/select.tests.js +39 -0
- package/field-tests/select.tests.js.map +1 -0
- package/field-tests/slider.tests.d.ts +13 -0
- package/field-tests/slider.tests.js +37 -0
- package/field-tests/slider.tests.js.map +1 -0
- package/field-tests/textarea.tests.d.ts +6 -0
- package/field-tests/textarea.tests.js +28 -0
- package/field-tests/textarea.tests.js.map +1 -0
- package/field-tests/unit-conversion.tests.d.ts +11 -0
- package/field-tests/unit-conversion.tests.js +29 -0
- package/field-tests/unit-conversion.tests.js.map +1 -0
- package/helpers/form.helpers.d.ts +80 -0
- package/helpers/form.helpers.js +123 -0
- package/helpers/form.helpers.js.map +1 -0
- package/index.d.ts +14 -0
- package/index.js +18 -0
- package/index.js.map +1 -0
- package/package.json +18 -0
package/CLAUDE.md
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# crispr-forms-testing
|
|
2
|
+
|
|
3
|
+
Playwright helpers and field test factories for testing forms built with `@tft/crispr-forms`.
|
|
4
|
+
|
|
5
|
+
## Setup
|
|
6
|
+
|
|
7
|
+
### 1. Load the Angular JIT compiler
|
|
8
|
+
The lib re-exports `@angular/compiler` automatically when you import from it. You do NOT need a separate fixture file — just import directly:
|
|
9
|
+
|
|
10
|
+
```ts
|
|
11
|
+
import { testField, getMatFormField } from '@tft/crispr-forms-testing';
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
If you are NOT importing from this lib in a given file, add this at the top of your Playwright setup or fixture:
|
|
15
|
+
```ts
|
|
16
|
+
import '@angular/compiler';
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
### 2. Use `testField` to generate standard assertions from a config
|
|
20
|
+
```ts
|
|
21
|
+
import { test } from '@playwright/test';
|
|
22
|
+
import { testField } from '@tft/crispr-forms-testing';
|
|
23
|
+
import { myFormConfig } from './my-form.config';
|
|
24
|
+
|
|
25
|
+
test.describe('My Form', () => {
|
|
26
|
+
test.beforeEach(async ({ page }) => page.goto('/my-form'));
|
|
27
|
+
for (const field of myFormConfig.fields) {
|
|
28
|
+
testField(field);
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## testId attribute pattern
|
|
34
|
+
|
|
35
|
+
Every crispr-forms field has `data-testid` set automatically. The value is the full control path:
|
|
36
|
+
|
|
37
|
+
| Context | testId value |
|
|
38
|
+
|---------|-------------|
|
|
39
|
+
| Top-level field `firstName` | `firstName` |
|
|
40
|
+
| In a subGroup `address` | `address.firstName` |
|
|
41
|
+
| In a formGroupList `contacts` at index 2 | `contacts[2].firstName` |
|
|
42
|
+
| Doubly-nested | `contacts[2].address.city` |
|
|
43
|
+
|
|
44
|
+
## Helper functions
|
|
45
|
+
|
|
46
|
+
### Locators
|
|
47
|
+
- `getMatFormField(page, testId)` — the root element with `data-testid` (usually `mat-form-field`, `mat-checkbox`, or `mat-slider`)
|
|
48
|
+
- `getInput(page, testId)` — `input` inside the field
|
|
49
|
+
- `getLabel(page, testId)` — `mat-label` inside the field
|
|
50
|
+
- `getHint(page, testId)` — `mat-hint` inside the field
|
|
51
|
+
- `getMatSelect(page, testId)` — `mat-select` inside a select field
|
|
52
|
+
- `getCheckbox(page, testId)` — `mat-checkbox` element
|
|
53
|
+
- `getAutocompleteInput(page, testId)` — the visible `role="textbox"` input in autocomplete (NOT the hidden formControlName anchor)
|
|
54
|
+
- `getError(page, testId, fieldType?)` — the `<p class="tft-control-error">` paragraph. `fieldType` defaults to `'input'`; pass the field's kebab-case type for other fields (e.g. `'select'`, `'autocomplete'`, `'autocomplete-chiplist'`, `'checkbox'`, `'datepicker'`, `'textarea'`)
|
|
55
|
+
|
|
56
|
+
### Actions
|
|
57
|
+
- `touchInput(page, testId)` — click + Tab to blur
|
|
58
|
+
- `touchSelect(page, testId)` — open + Escape + Tab (Tab is required; Escape alone does not fire blur)
|
|
59
|
+
- `touchCheckbox(page, testId)` — click + click + Tab (leaves unchecked, triggers requiredTrue)
|
|
60
|
+
- `touchAutocomplete(page, testId)` — click visible input + Tab
|
|
61
|
+
- `touchDatepicker(page, testId)` — click input + Tab
|
|
62
|
+
- `openSelect(page, testId)` — clicks mat-select to open panel
|
|
63
|
+
- `selectOption(page, testId, optionLabel)` — opens select and clicks the option
|
|
64
|
+
- `submitForm(page, nth?)` — programmatically clicks crispr-form's hidden submit button (index `nth`, defaults to 0)
|
|
65
|
+
|
|
66
|
+
## Known quirks
|
|
67
|
+
|
|
68
|
+
### Autocomplete and chiplist: use submitForm() to trigger errors
|
|
69
|
+
Both fields use a hidden `display:none` `[formControlName]` anchor for validation. The visible input has no validators. Blurring the visible input does NOT touch the hidden control. Use `submitForm()` instead — it fires `ControlErrorsFormDirective.submit$` which forces all controls to display errors regardless of touched state.
|
|
70
|
+
|
|
71
|
+
### submitForm() must use page.evaluate
|
|
72
|
+
The hidden submit button is `display:none`, so Playwright pointer events can't reach it. `submitForm()` calls `document.querySelector('[data-testid="submit-trigger"]').click()` via `page.evaluate`.
|
|
73
|
+
|
|
74
|
+
### touchSelect() requires Tab after Escape
|
|
75
|
+
Pressing Escape dismisses the mat-select panel but leaves the trigger element focused. `ControlErrorsDirective` listens for `blur` events. Without the Tab press, no blur fires and the error never appears.
|
|
76
|
+
|
|
77
|
+
### getError() targets the inner <p>, not the host element
|
|
78
|
+
`tft-control-error` host elements are always in the DOM (even when empty). Autocomplete/chiplist render two of them (one per internal input). Targeting `p.tft-control-error` (the inner paragraph, only present when there is error text) avoids Playwright strict-mode failures.
|
|
79
|
+
|
|
80
|
+
### Slider: plain <label>, not mat-label
|
|
81
|
+
Slider is not inside a `mat-form-field`, so it uses a plain `<label>`. `getLabel()` (which queries `mat-label`) won't work — query `.locator('label')` on the slider host instead.
|
|
82
|
+
|
|
83
|
+
### Slider: min/max are native HTML attributes
|
|
84
|
+
Read `min`/`max` from `input[matSliderThumb]` native HTML attributes, not `aria-valuemin`/`aria-valuemax`.
|
|
85
|
+
|
|
86
|
+
### Unit conversion: required errors are not testable
|
|
87
|
+
The `displayValueControl` (visible input) is separate from the `[formControlName]` that holds validators. Validation errors are never wired to the visible input, so required error tests are not possible for this field type.
|
|
88
|
+
|
|
89
|
+
## Field test factories
|
|
90
|
+
|
|
91
|
+
Each factory registers Playwright `test()` calls conditioned on the config:
|
|
92
|
+
|
|
93
|
+
| Factory | Config type | Tests generated |
|
|
94
|
+
|---------|-------------|-----------------|
|
|
95
|
+
| `testInputField` | `InputFieldConfig` | label, input type, placeholder, hint, required, min, max, maxLength |
|
|
96
|
+
| `testTextareaField` | `TextareaFieldConfig` | label, hint, required |
|
|
97
|
+
| `testCheckboxField` | `CheckboxFieldConfig` | label, requiredTrue error |
|
|
98
|
+
| `testSelectField` | `SelectFieldConfig` | label, hint, options count, select option, required |
|
|
99
|
+
| `testDatepickerField` | `DatepickerFieldConfig` | label, hint, toggle button, required |
|
|
100
|
+
| `testSliderField` | `SliderFieldConfig` | renders, label, min, max |
|
|
101
|
+
| `testAutocompleteField` | `AutocompleteFieldConfig` | label, hint, dropdown, select option, required (via submitForm) |
|
|
102
|
+
| `testAutocompleteChiplistField` | `AutocompleteChiplistFieldConfig` | label, hint, type+see options, add chip, remove chip, minArrayLength error |
|
|
103
|
+
| `testFileUploadField` | `FileUploadFieldConfig` | label, drop zone text, select button |
|
|
104
|
+
| `testUnitConversionField` | `UnitConversionFieldConfig` | label, number input, unit select |
|
|
105
|
+
| `testField` | `AnyFieldConfig` | dispatches to the correct factory above; skips non-control fields |
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { AutocompleteChiplistFieldConfig } from '@tft/crispr-forms/utils';
|
|
2
|
+
/**
|
|
3
|
+
* Registers Playwright tests for an AUTOCOMPLETE_CHIPLIST field config.
|
|
4
|
+
* Call inside a `test.describe` block that has already navigated to the page.
|
|
5
|
+
*
|
|
6
|
+
* Note: like autocomplete, errors require `submitForm()` rather than blur because
|
|
7
|
+
* the hidden `[formControlName]` anchor is what holds validators. Blurring the
|
|
8
|
+
* visible search input does not touch the hidden control.
|
|
9
|
+
*/
|
|
10
|
+
export declare function testAutocompleteChiplistField(config: AutocompleteChiplistFieldConfig): void;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { test, expect } from '@playwright/test';
|
|
2
|
+
import { getLabel, getHint, getError, getMatFormField, submitForm } from '../helpers/form.helpers';
|
|
3
|
+
/**
|
|
4
|
+
* Registers Playwright tests for an AUTOCOMPLETE_CHIPLIST field config.
|
|
5
|
+
* Call inside a `test.describe` block that has already navigated to the page.
|
|
6
|
+
*
|
|
7
|
+
* Note: like autocomplete, errors require `submitForm()` rather than blur because
|
|
8
|
+
* the hidden `[formControlName]` anchor is what holds validators. Blurring the
|
|
9
|
+
* visible search input does not touch the hidden control.
|
|
10
|
+
*/
|
|
11
|
+
export function testAutocompleteChiplistField(config) {
|
|
12
|
+
if (config.label) {
|
|
13
|
+
test('renders label', async ({ page }) => {
|
|
14
|
+
await expect(getLabel(page, config.controlName)).toHaveText(config.label);
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
if (config.hint) {
|
|
18
|
+
test('shows hint when untouched', async ({ page }) => {
|
|
19
|
+
await expect(getHint(page, config.controlName)).toBeVisible();
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
test('can type and see options', async ({ page }) => {
|
|
23
|
+
const searchInput = getMatFormField(page, config.controlName).locator('input[matInput]');
|
|
24
|
+
await searchInput.fill('Option');
|
|
25
|
+
const options = page.locator('mat-option');
|
|
26
|
+
await expect(options.first()).toBeVisible();
|
|
27
|
+
});
|
|
28
|
+
test('can add a chip by selecting an option', async ({ page }) => {
|
|
29
|
+
const searchInput = getMatFormField(page, config.controlName).locator('input[matInput]');
|
|
30
|
+
await searchInput.fill('Option A');
|
|
31
|
+
await page.locator('mat-option').filter({ hasText: 'Option A' }).click();
|
|
32
|
+
const chip = getMatFormField(page, config.controlName).locator('mat-chip-row').first();
|
|
33
|
+
await expect(chip).toContainText('Option A');
|
|
34
|
+
});
|
|
35
|
+
test('can remove a chip', async ({ page }) => {
|
|
36
|
+
const searchInput = getMatFormField(page, config.controlName).locator('input[matInput]');
|
|
37
|
+
await searchInput.fill('Option A');
|
|
38
|
+
await page.locator('mat-option').filter({ hasText: 'Option A' }).click();
|
|
39
|
+
const removeBtn = getMatFormField(page, config.controlName).locator('mat-chip-row mat-icon[matChipRemove]').first();
|
|
40
|
+
if (await removeBtn.isVisible()) {
|
|
41
|
+
await removeBtn.click();
|
|
42
|
+
const chips = getMatFormField(page, config.controlName).locator('mat-chip-row');
|
|
43
|
+
await expect(chips).toHaveCount(0);
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
test('shows error when not enough chips selected', async ({ page }) => {
|
|
47
|
+
await submitForm(page);
|
|
48
|
+
await expect(getError(page, config.controlName, 'autocomplete-chiplist')).toBeVisible();
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=autocomplete-chiplist.tests.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"autocomplete-chiplist.tests.js","sourceRoot":"","sources":["../../../../libs/crispr-forms-testing/src/field-tests/autocomplete-chiplist.tests.ts"],"names":[],"mappings":"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","sourcesContent":["import { test, expect } from '@playwright/test';\nimport type { AutocompleteChiplistFieldConfig } from '@tft/crispr-forms/utils';\nimport { getLabel, getHint, getError, getMatFormField, submitForm } from '../helpers/form.helpers';\n\n/**\n * Registers Playwright tests for an AUTOCOMPLETE_CHIPLIST field config.\n * Call inside a `test.describe` block that has already navigated to the page.\n *\n * Note: like autocomplete, errors require `submitForm()` rather than blur because\n * the hidden `[formControlName]` anchor is what holds validators. Blurring the\n * visible search input does not touch the hidden control.\n */\nexport function testAutocompleteChiplistField(config: AutocompleteChiplistFieldConfig) {\n if (config.label) {\n test('renders label', async ({ page }) => {\n await expect(getLabel(page, config.controlName)).toHaveText(config.label);\n });\n }\n\n if (config.hint) {\n test('shows hint when untouched', async ({ page }) => {\n await expect(getHint(page, config.controlName)).toBeVisible();\n });\n }\n\n test('can type and see options', async ({ page }) => {\n const searchInput = getMatFormField(page, config.controlName).locator('input[matInput]');\n await searchInput.fill('Option');\n const options = page.locator('mat-option');\n await expect(options.first()).toBeVisible();\n });\n\n test('can add a chip by selecting an option', async ({ page }) => {\n const searchInput = getMatFormField(page, config.controlName).locator('input[matInput]');\n await searchInput.fill('Option A');\n await page.locator('mat-option').filter({ hasText: 'Option A' }).click();\n const chip = getMatFormField(page, config.controlName).locator('mat-chip-row').first();\n await expect(chip).toContainText('Option A');\n });\n\n test('can remove a chip', async ({ page }) => {\n const searchInput = getMatFormField(page, config.controlName).locator('input[matInput]');\n await searchInput.fill('Option A');\n await page.locator('mat-option').filter({ hasText: 'Option A' }).click();\n const removeBtn = getMatFormField(page, config.controlName).locator('mat-chip-row mat-icon[matChipRemove]').first();\n if (await removeBtn.isVisible()) {\n await removeBtn.click();\n const chips = getMatFormField(page, config.controlName).locator('mat-chip-row');\n await expect(chips).toHaveCount(0);\n }\n });\n\n test('shows error when not enough chips selected', async ({ page }) => {\n await submitForm(page);\n await expect(getError(page, config.controlName, 'autocomplete-chiplist')).toBeVisible();\n });\n}\n"]}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { AutocompleteFieldConfig } from '@tft/crispr-forms/utils';
|
|
2
|
+
/**
|
|
3
|
+
* Registers Playwright tests for an AUTOCOMPLETE field config.
|
|
4
|
+
* Call inside a `test.describe` block that has already navigated to the page.
|
|
5
|
+
*
|
|
6
|
+
* Note: errors are triggered via `submitForm()` rather than blur because the
|
|
7
|
+
* autocomplete uses a hidden `[formControlName]` anchor for validation — blurring
|
|
8
|
+
* the visible input does not touch the hidden control. `submitForm()` fires
|
|
9
|
+
* `ControlErrorsFormDirective.submit$` which forces all controls to show errors.
|
|
10
|
+
*/
|
|
11
|
+
export declare function testAutocompleteField(config: AutocompleteFieldConfig): void;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { test, expect } from '@playwright/test';
|
|
2
|
+
import { Validators } from '@angular/forms';
|
|
3
|
+
import { getLabel, getHint, getError, getAutocompleteInput, submitForm } from '../helpers/form.helpers';
|
|
4
|
+
/**
|
|
5
|
+
* Registers Playwright tests for an AUTOCOMPLETE field config.
|
|
6
|
+
* Call inside a `test.describe` block that has already navigated to the page.
|
|
7
|
+
*
|
|
8
|
+
* Note: errors are triggered via `submitForm()` rather than blur because the
|
|
9
|
+
* autocomplete uses a hidden `[formControlName]` anchor for validation — blurring
|
|
10
|
+
* the visible input does not touch the hidden control. `submitForm()` fires
|
|
11
|
+
* `ControlErrorsFormDirective.submit$` which forces all controls to show errors.
|
|
12
|
+
*/
|
|
13
|
+
export function testAutocompleteField(config) {
|
|
14
|
+
if (config.label) {
|
|
15
|
+
test('renders label', async ({ page }) => {
|
|
16
|
+
await expect(getLabel(page, config.controlName)).toHaveText(config.label);
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
if (config.hint) {
|
|
20
|
+
test('shows hint when untouched', async ({ page }) => {
|
|
21
|
+
await expect(getHint(page, config.controlName)).toBeVisible();
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
test('shows dropdown options when typing', async ({ page }) => {
|
|
25
|
+
await getAutocompleteInput(page, config.controlName).fill('Option');
|
|
26
|
+
const options = page.locator('mat-option');
|
|
27
|
+
await expect(options.first()).toBeVisible();
|
|
28
|
+
});
|
|
29
|
+
test('can select an autocomplete option', async ({ page }) => {
|
|
30
|
+
await getAutocompleteInput(page, config.controlName).fill('Option A');
|
|
31
|
+
await page.locator('mat-option').filter({ hasText: 'Option A' }).click();
|
|
32
|
+
await expect(getAutocompleteInput(page, config.controlName)).toHaveValue('Option A');
|
|
33
|
+
});
|
|
34
|
+
if (config.validators?.includes(Validators.required)) {
|
|
35
|
+
test('shows required error when form submitted empty', async ({ page }) => {
|
|
36
|
+
await getAutocompleteInput(page, config.controlName).fill('');
|
|
37
|
+
await submitForm(page);
|
|
38
|
+
await expect(getError(page, config.controlName, 'autocomplete')).toBeVisible();
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=autocomplete.tests.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"autocomplete.tests.js","sourceRoot":"","sources":["../../../../libs/crispr-forms-testing/src/field-tests/autocomplete.tests.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAEhD,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAC5C,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,oBAAoB,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAExG;;;;;;;;GAQG;AACH,MAAM,UAAU,qBAAqB,CAAC,MAA+B;IACnE,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;QACjB,IAAI,CAAC,eAAe,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE;YACvC,MAAM,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,UAAU,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAC5E,CAAC,CAAC,CAAC;IACL,CAAC;IAED,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC;QAChB,IAAI,CAAC,2BAA2B,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE;YACnD,MAAM,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;QAChE,CAAC,CAAC,CAAC;IACL,CAAC;IAED,IAAI,CAAC,oCAAoC,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE;QAC5D,MAAM,oBAAoB,CAAC,IAAI,EAAE,MAAM,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACpE,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC;QAC3C,MAAM,MAAM,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;IAC9C,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,mCAAmC,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE;QAC3D,MAAM,oBAAoB,CAAC,IAAI,EAAE,MAAM,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QACtE,MAAM,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,UAAU,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC;QACzE,MAAM,MAAM,CAAC,oBAAoB,CAAC,IAAI,EAAE,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,WAAW,CAAC,UAAU,CAAC,CAAC;IACvF,CAAC,CAAC,CAAC;IAEH,IAAI,MAAM,CAAC,UAAU,EAAE,QAAQ,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QACrD,IAAI,CAAC,gDAAgD,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE;YACxE,MAAM,oBAAoB,CAAC,IAAI,EAAE,MAAM,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YAC9D,MAAM,UAAU,CAAC,IAAI,CAAC,CAAC;YACvB,MAAM,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,WAAW,EAAE,cAAc,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;QACjF,CAAC,CAAC,CAAC;IACL,CAAC;AACH,CAAC","sourcesContent":["import { test, expect } from '@playwright/test';\nimport type { AutocompleteFieldConfig } from '@tft/crispr-forms/utils';\nimport { Validators } from '@angular/forms';\nimport { getLabel, getHint, getError, getAutocompleteInput, submitForm } from '../helpers/form.helpers';\n\n/**\n * Registers Playwright tests for an AUTOCOMPLETE field config.\n * Call inside a `test.describe` block that has already navigated to the page.\n *\n * Note: errors are triggered via `submitForm()` rather than blur because the\n * autocomplete uses a hidden `[formControlName]` anchor for validation — blurring\n * the visible input does not touch the hidden control. `submitForm()` fires\n * `ControlErrorsFormDirective.submit$` which forces all controls to show errors.\n */\nexport function testAutocompleteField(config: AutocompleteFieldConfig) {\n if (config.label) {\n test('renders label', async ({ page }) => {\n await expect(getLabel(page, config.controlName)).toHaveText(config.label);\n });\n }\n\n if (config.hint) {\n test('shows hint when untouched', async ({ page }) => {\n await expect(getHint(page, config.controlName)).toBeVisible();\n });\n }\n\n test('shows dropdown options when typing', async ({ page }) => {\n await getAutocompleteInput(page, config.controlName).fill('Option');\n const options = page.locator('mat-option');\n await expect(options.first()).toBeVisible();\n });\n\n test('can select an autocomplete option', async ({ page }) => {\n await getAutocompleteInput(page, config.controlName).fill('Option A');\n await page.locator('mat-option').filter({ hasText: 'Option A' }).click();\n await expect(getAutocompleteInput(page, config.controlName)).toHaveValue('Option A');\n });\n\n if (config.validators?.includes(Validators.required)) {\n test('shows required error when form submitted empty', async ({ page }) => {\n await getAutocompleteInput(page, config.controlName).fill('');\n await submitForm(page);\n await expect(getError(page, config.controlName, 'autocomplete')).toBeVisible();\n });\n }\n}\n"]}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { CheckboxFieldConfig } from '@tft/crispr-forms/utils';
|
|
2
|
+
/**
|
|
3
|
+
* Registers Playwright tests for a CHECKBOX field config.
|
|
4
|
+
* Call inside a `test.describe` block that has already navigated to the page.
|
|
5
|
+
*/
|
|
6
|
+
export declare function testCheckboxField(config: CheckboxFieldConfig): void;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { test, expect } from '@playwright/test';
|
|
2
|
+
import { Validators } from '@angular/forms';
|
|
3
|
+
import { getCheckbox, getError } from '../helpers/form.helpers';
|
|
4
|
+
/**
|
|
5
|
+
* Registers Playwright tests for a CHECKBOX field config.
|
|
6
|
+
* Call inside a `test.describe` block that has already navigated to the page.
|
|
7
|
+
*/
|
|
8
|
+
export function testCheckboxField(config) {
|
|
9
|
+
test('renders label', async ({ page }) => {
|
|
10
|
+
const label = getCheckbox(page, config.controlName).locator('.mdc-label');
|
|
11
|
+
await expect(label).toContainText(config.label);
|
|
12
|
+
});
|
|
13
|
+
if (config.validators?.includes(Validators.requiredTrue)) {
|
|
14
|
+
test('shows error when unchecked and blurred', async ({ page }) => {
|
|
15
|
+
await getCheckbox(page, config.controlName).click();
|
|
16
|
+
await getCheckbox(page, config.controlName).click();
|
|
17
|
+
await page.keyboard.press('Tab');
|
|
18
|
+
await expect(getError(page, config.controlName, 'checkbox')).toBeVisible();
|
|
19
|
+
});
|
|
20
|
+
test('no error when checked', async ({ page }) => {
|
|
21
|
+
await getCheckbox(page, config.controlName).click();
|
|
22
|
+
await page.keyboard.press('Tab');
|
|
23
|
+
await expect(getError(page, config.controlName, 'checkbox')).not.toBeVisible();
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=checkbox.tests.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"checkbox.tests.js","sourceRoot":"","sources":["../../../../libs/crispr-forms-testing/src/field-tests/checkbox.tests.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAEhD,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAC5C,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AAEhE;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAAC,MAA2B;IAC3D,IAAI,CAAC,eAAe,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE;QACvC,MAAM,KAAK,GAAG,WAAW,CAAC,IAAI,EAAE,MAAM,CAAC,WAAW,CAAC,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC;QAC1E,MAAM,MAAM,CAAC,KAAK,CAAC,CAAC,aAAa,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAClD,CAAC,CAAC,CAAC;IAEH,IAAI,MAAM,CAAC,UAAU,EAAE,QAAQ,CAAC,UAAU,CAAC,YAAY,CAAC,EAAE,CAAC;QACzD,IAAI,CAAC,wCAAwC,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE;YAChE,MAAM,WAAW,CAAC,IAAI,EAAE,MAAM,CAAC,WAAW,CAAC,CAAC,KAAK,EAAE,CAAC;YACpD,MAAM,WAAW,CAAC,IAAI,EAAE,MAAM,CAAC,WAAW,CAAC,CAAC,KAAK,EAAE,CAAC;YACpD,MAAM,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;YACjC,MAAM,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,WAAW,EAAE,UAAU,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;QAC7E,CAAC,CAAC,CAAC;QAEH,IAAI,CAAC,uBAAuB,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE;YAC/C,MAAM,WAAW,CAAC,IAAI,EAAE,MAAM,CAAC,WAAW,CAAC,CAAC,KAAK,EAAE,CAAC;YACpD,MAAM,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;YACjC,MAAM,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,WAAW,EAAE,UAAU,CAAC,CAAC,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC;QACjF,CAAC,CAAC,CAAC;IACL,CAAC;AACH,CAAC","sourcesContent":["import { test, expect } from '@playwright/test';\nimport type { CheckboxFieldConfig } from '@tft/crispr-forms/utils';\nimport { Validators } from '@angular/forms';\nimport { getCheckbox, getError } from '../helpers/form.helpers';\n\n/**\n * Registers Playwright tests for a CHECKBOX field config.\n * Call inside a `test.describe` block that has already navigated to the page.\n */\nexport function testCheckboxField(config: CheckboxFieldConfig) {\n test('renders label', async ({ page }) => {\n const label = getCheckbox(page, config.controlName).locator('.mdc-label');\n await expect(label).toContainText(config.label);\n });\n\n if (config.validators?.includes(Validators.requiredTrue)) {\n test('shows error when unchecked and blurred', async ({ page }) => {\n await getCheckbox(page, config.controlName).click();\n await getCheckbox(page, config.controlName).click();\n await page.keyboard.press('Tab');\n await expect(getError(page, config.controlName, 'checkbox')).toBeVisible();\n });\n\n test('no error when checked', async ({ page }) => {\n await getCheckbox(page, config.controlName).click();\n await page.keyboard.press('Tab');\n await expect(getError(page, config.controlName, 'checkbox')).not.toBeVisible();\n });\n }\n}\n"]}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { DatepickerFieldConfig } from '@tft/crispr-forms/utils';
|
|
2
|
+
/**
|
|
3
|
+
* Registers Playwright tests for a DATEPICKER field config.
|
|
4
|
+
* Call inside a `test.describe` block that has already navigated to the page.
|
|
5
|
+
*/
|
|
6
|
+
export declare function testDatepickerField(config: DatepickerFieldConfig): void;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { test, expect } from '@playwright/test';
|
|
2
|
+
import { Validators } from '@angular/forms';
|
|
3
|
+
import { getLabel, getHint, getError, getMatFormField, touchDatepicker } from '../helpers/form.helpers';
|
|
4
|
+
/**
|
|
5
|
+
* Registers Playwright tests for a DATEPICKER field config.
|
|
6
|
+
* Call inside a `test.describe` block that has already navigated to the page.
|
|
7
|
+
*/
|
|
8
|
+
export function testDatepickerField(config) {
|
|
9
|
+
if (config.label) {
|
|
10
|
+
test('renders label', async ({ page }) => {
|
|
11
|
+
await expect(getLabel(page, config.controlName)).toHaveText(config.label);
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
if (config.hint) {
|
|
15
|
+
test('shows hint when untouched', async ({ page }) => {
|
|
16
|
+
await expect(getHint(page, config.controlName)).toBeVisible();
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
test('renders datepicker toggle button', async ({ page }) => {
|
|
20
|
+
const toggle = getMatFormField(page, config.controlName).locator('mat-datepicker-toggle');
|
|
21
|
+
await expect(toggle).toBeVisible();
|
|
22
|
+
});
|
|
23
|
+
if (config.validators?.includes(Validators.required)) {
|
|
24
|
+
test('shows required error when blurred without date', async ({ page }) => {
|
|
25
|
+
await touchDatepicker(page, config.controlName);
|
|
26
|
+
await expect(getError(page, config.controlName, 'datepicker')).toBeVisible();
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=datepicker.tests.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"datepicker.tests.js","sourceRoot":"","sources":["../../../../libs/crispr-forms-testing/src/field-tests/datepicker.tests.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAEhD,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAC5C,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAExG;;;GAGG;AACH,MAAM,UAAU,mBAAmB,CAAC,MAA6B;IAC/D,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;QACjB,IAAI,CAAC,eAAe,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE;YACvC,MAAM,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,UAAU,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAC5E,CAAC,CAAC,CAAC;IACL,CAAC;IAED,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC;QAChB,IAAI,CAAC,2BAA2B,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE;YACnD,MAAM,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;QAChE,CAAC,CAAC,CAAC;IACL,CAAC;IAED,IAAI,CAAC,kCAAkC,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE;QAC1D,MAAM,MAAM,GAAG,eAAe,CAAC,IAAI,EAAE,MAAM,CAAC,WAAW,CAAC,CAAC,OAAO,CAAC,uBAAuB,CAAC,CAAC;QAC1F,MAAM,MAAM,CAAC,MAAM,CAAC,CAAC,WAAW,EAAE,CAAC;IACrC,CAAC,CAAC,CAAC;IAEH,IAAI,MAAM,CAAC,UAAU,EAAE,QAAQ,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QACrD,IAAI,CAAC,gDAAgD,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE;YACxE,MAAM,eAAe,CAAC,IAAI,EAAE,MAAM,CAAC,WAAW,CAAC,CAAC;YAChD,MAAM,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,WAAW,EAAE,YAAY,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;QAC/E,CAAC,CAAC,CAAC;IACL,CAAC;AACH,CAAC","sourcesContent":["import { test, expect } from '@playwright/test';\nimport type { DatepickerFieldConfig } from '@tft/crispr-forms/utils';\nimport { Validators } from '@angular/forms';\nimport { getLabel, getHint, getError, getMatFormField, touchDatepicker } from '../helpers/form.helpers';\n\n/**\n * Registers Playwright tests for a DATEPICKER field config.\n * Call inside a `test.describe` block that has already navigated to the page.\n */\nexport function testDatepickerField(config: DatepickerFieldConfig) {\n if (config.label) {\n test('renders label', async ({ page }) => {\n await expect(getLabel(page, config.controlName)).toHaveText(config.label);\n });\n }\n\n if (config.hint) {\n test('shows hint when untouched', async ({ page }) => {\n await expect(getHint(page, config.controlName)).toBeVisible();\n });\n }\n\n test('renders datepicker toggle button', async ({ page }) => {\n const toggle = getMatFormField(page, config.controlName).locator('mat-datepicker-toggle');\n await expect(toggle).toBeVisible();\n });\n\n if (config.validators?.includes(Validators.required)) {\n test('shows required error when blurred without date', async ({ page }) => {\n await touchDatepicker(page, config.controlName);\n await expect(getError(page, config.controlName, 'datepicker')).toBeVisible();\n });\n }\n}\n"]}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { AnyFieldConfig } from '@tft/crispr-forms/utils';
|
|
2
|
+
/**
|
|
3
|
+
* Registers a `test.describe` block for a field config, automatically selecting
|
|
4
|
+
* the correct test suite based on `controlType`.
|
|
5
|
+
*
|
|
6
|
+
* Non-control fields (heading, divider, button) are skipped silently.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```ts
|
|
10
|
+
* import { testField } from '@tft/crispr-forms-testing';
|
|
11
|
+
* import { myFormConfig } from './my-form.config';
|
|
12
|
+
*
|
|
13
|
+
* test.describe('My Form', () => {
|
|
14
|
+
* test.beforeEach(async ({ page }) => page.goto('/my-form'));
|
|
15
|
+
* for (const field of myFormConfig.fields) {
|
|
16
|
+
* testField(field);
|
|
17
|
+
* }
|
|
18
|
+
* });
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
export declare function testField(config: AnyFieldConfig): void;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { test } from '@playwright/test';
|
|
2
|
+
import { testInputField } from './input.tests';
|
|
3
|
+
import { testTextareaField } from './textarea.tests';
|
|
4
|
+
import { testCheckboxField } from './checkbox.tests';
|
|
5
|
+
import { testSelectField } from './select.tests';
|
|
6
|
+
import { testSliderField } from './slider.tests';
|
|
7
|
+
import { testDatepickerField } from './datepicker.tests';
|
|
8
|
+
import { testAutocompleteField } from './autocomplete.tests';
|
|
9
|
+
import { testAutocompleteChiplistField } from './autocomplete-chiplist.tests';
|
|
10
|
+
import { testFileUploadField } from './file-upload.tests';
|
|
11
|
+
import { testUnitConversionField } from './unit-conversion.tests';
|
|
12
|
+
/**
|
|
13
|
+
* Registers a `test.describe` block for a field config, automatically selecting
|
|
14
|
+
* the correct test suite based on `controlType`.
|
|
15
|
+
*
|
|
16
|
+
* Non-control fields (heading, divider, button) are skipped silently.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```ts
|
|
20
|
+
* import { testField } from '@tft/crispr-forms-testing';
|
|
21
|
+
* import { myFormConfig } from './my-form.config';
|
|
22
|
+
*
|
|
23
|
+
* test.describe('My Form', () => {
|
|
24
|
+
* test.beforeEach(async ({ page }) => page.goto('/my-form'));
|
|
25
|
+
* for (const field of myFormConfig.fields) {
|
|
26
|
+
* testField(field);
|
|
27
|
+
* }
|
|
28
|
+
* });
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
export function testField(config) {
|
|
32
|
+
if (!('controlName' in config))
|
|
33
|
+
return;
|
|
34
|
+
test.describe(config.controlName, () => {
|
|
35
|
+
switch (config.controlType) {
|
|
36
|
+
case 'input':
|
|
37
|
+
testInputField(config);
|
|
38
|
+
break;
|
|
39
|
+
case 'textarea':
|
|
40
|
+
testTextareaField(config);
|
|
41
|
+
break;
|
|
42
|
+
case 'checkbox':
|
|
43
|
+
testCheckboxField(config);
|
|
44
|
+
break;
|
|
45
|
+
case 'select':
|
|
46
|
+
testSelectField(config);
|
|
47
|
+
break;
|
|
48
|
+
case 'slider':
|
|
49
|
+
testSliderField(config);
|
|
50
|
+
break;
|
|
51
|
+
case 'datepicker':
|
|
52
|
+
testDatepickerField(config);
|
|
53
|
+
break;
|
|
54
|
+
case 'autocomplete':
|
|
55
|
+
testAutocompleteField(config);
|
|
56
|
+
break;
|
|
57
|
+
case 'autocompleteChiplist':
|
|
58
|
+
testAutocompleteChiplistField(config);
|
|
59
|
+
break;
|
|
60
|
+
case 'fileUpload':
|
|
61
|
+
testFileUploadField(config);
|
|
62
|
+
break;
|
|
63
|
+
case 'unitConversion':
|
|
64
|
+
testUnitConversionField(config);
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=field-test-factory.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"field-test-factory.js","sourceRoot":"","sources":["../../../../libs/crispr-forms-testing/src/field-tests/field-test-factory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAcxC,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,EAAE,6BAA6B,EAAE,MAAM,+BAA+B,CAAC;AAC9E,OAAO,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,EAAE,uBAAuB,EAAE,MAAM,yBAAyB,CAAC;AAElE;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,SAAS,CAAC,MAAsB;IAC9C,IAAI,CAAC,CAAC,aAAa,IAAI,MAAM,CAAC;QAAE,OAAO;IAEvC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,WAAW,EAAE,GAAG,EAAE;QACrC,QAAQ,MAAM,CAAC,WAAW,EAAE,CAAC;YAC3B,KAAK,OAAO;gBACV,cAAc,CAAC,MAA0B,CAAC,CAAC;gBAC3C,MAAM;YACR,KAAK,UAAU;gBACb,iBAAiB,CAAC,MAA6B,CAAC,CAAC;gBACjD,MAAM;YACR,KAAK,UAAU;gBACb,iBAAiB,CAAC,MAA6B,CAAC,CAAC;gBACjD,MAAM;YACR,KAAK,QAAQ;gBACX,eAAe,CAAC,MAA2B,CAAC,CAAC;gBAC7C,MAAM;YACR,KAAK,QAAQ;gBACX,eAAe,CAAC,MAA2B,CAAC,CAAC;gBAC7C,MAAM;YACR,KAAK,YAAY;gBACf,mBAAmB,CAAC,MAA+B,CAAC,CAAC;gBACrD,MAAM;YACR,KAAK,cAAc;gBACjB,qBAAqB,CAAC,MAAiC,CAAC,CAAC;gBACzD,MAAM;YACR,KAAK,sBAAsB;gBACzB,6BAA6B,CAAC,MAAyC,CAAC,CAAC;gBACzE,MAAM;YACR,KAAK,YAAY;gBACf,mBAAmB,CAAC,MAA+B,CAAC,CAAC;gBACrD,MAAM;YACR,KAAK,gBAAgB;gBACnB,uBAAuB,CAAC,MAAmC,CAAC,CAAC;gBAC7D,MAAM;QACV,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC","sourcesContent":["import { test } from '@playwright/test';\nimport type {\n AnyFieldConfig,\n InputFieldConfig,\n TextareaFieldConfig,\n CheckboxFieldConfig,\n SelectFieldConfig,\n SliderFieldConfig,\n DatepickerFieldConfig,\n AutocompleteFieldConfig,\n AutocompleteChiplistFieldConfig,\n FileUploadFieldConfig,\n UnitConversionFieldConfig,\n} from '@tft/crispr-forms/utils';\nimport { testInputField } from './input.tests';\nimport { testTextareaField } from './textarea.tests';\nimport { testCheckboxField } from './checkbox.tests';\nimport { testSelectField } from './select.tests';\nimport { testSliderField } from './slider.tests';\nimport { testDatepickerField } from './datepicker.tests';\nimport { testAutocompleteField } from './autocomplete.tests';\nimport { testAutocompleteChiplistField } from './autocomplete-chiplist.tests';\nimport { testFileUploadField } from './file-upload.tests';\nimport { testUnitConversionField } from './unit-conversion.tests';\n\n/**\n * Registers a `test.describe` block for a field config, automatically selecting\n * the correct test suite based on `controlType`.\n *\n * Non-control fields (heading, divider, button) are skipped silently.\n *\n * @example\n * ```ts\n * import { testField } from '@tft/crispr-forms-testing';\n * import { myFormConfig } from './my-form.config';\n *\n * test.describe('My Form', () => {\n * test.beforeEach(async ({ page }) => page.goto('/my-form'));\n * for (const field of myFormConfig.fields) {\n * testField(field);\n * }\n * });\n * ```\n */\nexport function testField(config: AnyFieldConfig) {\n if (!('controlName' in config)) return;\n\n test.describe(config.controlName, () => {\n switch (config.controlType) {\n case 'input':\n testInputField(config as InputFieldConfig);\n break;\n case 'textarea':\n testTextareaField(config as TextareaFieldConfig);\n break;\n case 'checkbox':\n testCheckboxField(config as CheckboxFieldConfig);\n break;\n case 'select':\n testSelectField(config as SelectFieldConfig);\n break;\n case 'slider':\n testSliderField(config as SliderFieldConfig);\n break;\n case 'datepicker':\n testDatepickerField(config as DatepickerFieldConfig);\n break;\n case 'autocomplete':\n testAutocompleteField(config as AutocompleteFieldConfig);\n break;\n case 'autocompleteChiplist':\n testAutocompleteChiplistField(config as AutocompleteChiplistFieldConfig);\n break;\n case 'fileUpload':\n testFileUploadField(config as FileUploadFieldConfig);\n break;\n case 'unitConversion':\n testUnitConversionField(config as UnitConversionFieldConfig);\n break;\n }\n });\n}\n"]}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { FileUploadFieldConfig } from '@tft/crispr-forms/utils';
|
|
2
|
+
/**
|
|
3
|
+
* Registers Playwright tests for a FILE_UPLOAD field config.
|
|
4
|
+
* Call inside a `test.describe` block that has already navigated to the page.
|
|
5
|
+
*/
|
|
6
|
+
export declare function testFileUploadField(config: FileUploadFieldConfig): void;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { test, expect } from '@playwright/test';
|
|
2
|
+
import { getLabel, getMatFormField } from '../helpers/form.helpers';
|
|
3
|
+
/**
|
|
4
|
+
* Registers Playwright tests for a FILE_UPLOAD field config.
|
|
5
|
+
* Call inside a `test.describe` block that has already navigated to the page.
|
|
6
|
+
*/
|
|
7
|
+
export function testFileUploadField(config) {
|
|
8
|
+
if (config.label) {
|
|
9
|
+
test('renders label', async ({ page }) => {
|
|
10
|
+
await expect(getLabel(page, config.controlName)).toHaveText(config.label);
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
test('shows drop zone text', async ({ page }) => {
|
|
14
|
+
if (config.dropZoneText) {
|
|
15
|
+
const dropZone = getMatFormField(page, config.controlName).locator('.drop-info');
|
|
16
|
+
await expect(dropZone).toContainText(config.dropZoneText);
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
test('shows select files button', async ({ page }) => {
|
|
20
|
+
const button = getMatFormField(page, config.controlName).locator('button');
|
|
21
|
+
await expect(button).toBeVisible();
|
|
22
|
+
if (config.selectButtonText) {
|
|
23
|
+
await expect(button).toContainText(config.selectButtonText);
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=file-upload.tests.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-upload.tests.js","sourceRoot":"","sources":["../../../../libs/crispr-forms-testing/src/field-tests/file-upload.tests.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAEhD,OAAO,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAEpE;;;GAGG;AACH,MAAM,UAAU,mBAAmB,CAAC,MAA6B;IAC/D,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;QACjB,IAAI,CAAC,eAAe,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE;YACvC,MAAM,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,UAAU,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAC5E,CAAC,CAAC,CAAC;IACL,CAAC;IAED,IAAI,CAAC,sBAAsB,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE;QAC9C,IAAI,MAAM,CAAC,YAAY,EAAE,CAAC;YACxB,MAAM,QAAQ,GAAG,eAAe,CAAC,IAAI,EAAE,MAAM,CAAC,WAAW,CAAC,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC;YACjF,MAAM,MAAM,CAAC,QAAQ,CAAC,CAAC,aAAa,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC;QAC5D,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,2BAA2B,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE;QACnD,MAAM,MAAM,GAAG,eAAe,CAAC,IAAI,EAAE,MAAM,CAAC,WAAW,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QAC3E,MAAM,MAAM,CAAC,MAAM,CAAC,CAAC,WAAW,EAAE,CAAC;QACnC,IAAI,MAAM,CAAC,gBAAgB,EAAE,CAAC;YAC5B,MAAM,MAAM,CAAC,MAAM,CAAC,CAAC,aAAa,CAAC,MAAM,CAAC,gBAAgB,CAAC,CAAC;QAC9D,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC","sourcesContent":["import { test, expect } from '@playwright/test';\nimport type { FileUploadFieldConfig } from '@tft/crispr-forms/utils';\nimport { getLabel, getMatFormField } from '../helpers/form.helpers';\n\n/**\n * Registers Playwright tests for a FILE_UPLOAD field config.\n * Call inside a `test.describe` block that has already navigated to the page.\n */\nexport function testFileUploadField(config: FileUploadFieldConfig) {\n if (config.label) {\n test('renders label', async ({ page }) => {\n await expect(getLabel(page, config.controlName)).toHaveText(config.label);\n });\n }\n\n test('shows drop zone text', async ({ page }) => {\n if (config.dropZoneText) {\n const dropZone = getMatFormField(page, config.controlName).locator('.drop-info');\n await expect(dropZone).toContainText(config.dropZoneText);\n }\n });\n\n test('shows select files button', async ({ page }) => {\n const button = getMatFormField(page, config.controlName).locator('button');\n await expect(button).toBeVisible();\n if (config.selectButtonText) {\n await expect(button).toContainText(config.selectButtonText);\n }\n });\n}\n"]}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { InputFieldConfig } from '@tft/crispr-forms/utils';
|
|
2
|
+
/**
|
|
3
|
+
* Registers Playwright tests for an INPUT field config.
|
|
4
|
+
* Call inside a `test.describe` block that has already navigated to the page.
|
|
5
|
+
*
|
|
6
|
+
* Tests generated based on config:
|
|
7
|
+
* - Always: renders label, has correct input type
|
|
8
|
+
* - If `placeholder`: renders placeholder attribute
|
|
9
|
+
* - If `hint`: shows hint when untouched
|
|
10
|
+
* - If `Validators.required`: shows required error on blur
|
|
11
|
+
* - If `min`: validates min boundary
|
|
12
|
+
* - If `max`: validates max boundary
|
|
13
|
+
* - If `maxLength`: enforces maxLength attribute
|
|
14
|
+
*/
|
|
15
|
+
export declare function testInputField(config: InputFieldConfig): void;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { test, expect } from '@playwright/test';
|
|
2
|
+
import { Validators } from '@angular/forms';
|
|
3
|
+
import { getInput, getError, getLabel, getHint, touchInput } from '../helpers/form.helpers';
|
|
4
|
+
/**
|
|
5
|
+
* Registers Playwright tests for an INPUT field config.
|
|
6
|
+
* Call inside a `test.describe` block that has already navigated to the page.
|
|
7
|
+
*
|
|
8
|
+
* Tests generated based on config:
|
|
9
|
+
* - Always: renders label, has correct input type
|
|
10
|
+
* - If `placeholder`: renders placeholder attribute
|
|
11
|
+
* - If `hint`: shows hint when untouched
|
|
12
|
+
* - If `Validators.required`: shows required error on blur
|
|
13
|
+
* - If `min`: validates min boundary
|
|
14
|
+
* - If `max`: validates max boundary
|
|
15
|
+
* - If `maxLength`: enforces maxLength attribute
|
|
16
|
+
*/
|
|
17
|
+
export function testInputField(config) {
|
|
18
|
+
test('renders label', async ({ page }) => {
|
|
19
|
+
await expect(getLabel(page, config.controlName)).toHaveText(config.label);
|
|
20
|
+
});
|
|
21
|
+
test(`has input type "${config.inputType ?? 'text'}"`, async ({ page }) => {
|
|
22
|
+
await expect(getInput(page, config.controlName)).toHaveAttribute('type', config.inputType ?? 'text');
|
|
23
|
+
});
|
|
24
|
+
if (config.placeholder) {
|
|
25
|
+
test('renders placeholder', async ({ page }) => {
|
|
26
|
+
await expect(getInput(page, config.controlName)).toHaveAttribute('placeholder', config.placeholder);
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
if (config.hint) {
|
|
30
|
+
test('shows hint when untouched', async ({ page }) => {
|
|
31
|
+
await expect(getHint(page, config.controlName)).toBeVisible();
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
if (config.validators?.includes(Validators.required)) {
|
|
35
|
+
test('shows required error when blurred empty', async ({ page }) => {
|
|
36
|
+
await touchInput(page, config.controlName);
|
|
37
|
+
await expect(getError(page, config.controlName)).toBeVisible();
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
if (config.min !== undefined) {
|
|
41
|
+
test(`shows error when value is below min (${config.min})`, async ({ page }) => {
|
|
42
|
+
await getInput(page, config.controlName).fill(String(config.min - 1));
|
|
43
|
+
await touchInput(page, config.controlName);
|
|
44
|
+
await expect(getError(page, config.controlName)).toBeVisible();
|
|
45
|
+
});
|
|
46
|
+
test(`accepts value at min boundary (${config.min})`, async ({ page }) => {
|
|
47
|
+
await getInput(page, config.controlName).fill(String(config.min));
|
|
48
|
+
await touchInput(page, config.controlName);
|
|
49
|
+
await expect(getError(page, config.controlName)).not.toBeVisible();
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
if (config.max !== undefined) {
|
|
53
|
+
test(`shows error when value exceeds max (${config.max})`, async ({ page }) => {
|
|
54
|
+
await getInput(page, config.controlName).fill(String(config.max + 1));
|
|
55
|
+
await touchInput(page, config.controlName);
|
|
56
|
+
await expect(getError(page, config.controlName)).toBeVisible();
|
|
57
|
+
});
|
|
58
|
+
test(`accepts value at max boundary (${config.max})`, async ({ page }) => {
|
|
59
|
+
await getInput(page, config.controlName).fill(String(config.max));
|
|
60
|
+
await touchInput(page, config.controlName);
|
|
61
|
+
await expect(getError(page, config.controlName)).not.toBeVisible();
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
if (config.maxLength !== undefined) {
|
|
65
|
+
test(`enforces maxLength of ${config.maxLength}`, async ({ page }) => {
|
|
66
|
+
const overLimit = 'a'.repeat(config.maxLength + 1);
|
|
67
|
+
await getInput(page, config.controlName).fill(overLimit);
|
|
68
|
+
const value = await getInput(page, config.controlName).inputValue();
|
|
69
|
+
expect(value.length).toBeLessThanOrEqual(config.maxLength);
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=input.tests.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"input.tests.js","sourceRoot":"","sources":["../../../../libs/crispr-forms-testing/src/field-tests/input.tests.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAEhD,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAC5C,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAE5F;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,cAAc,CAAC,MAAwB;IACrD,IAAI,CAAC,eAAe,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE;QACvC,MAAM,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,UAAU,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAC5E,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,mBAAmB,MAAM,CAAC,SAAS,IAAI,MAAM,GAAG,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE;QACxE,MAAM,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,eAAe,CAAC,MAAM,EAAE,MAAM,CAAC,SAAS,IAAI,MAAM,CAAC,CAAC;IACvG,CAAC,CAAC,CAAC;IAEH,IAAI,MAAM,CAAC,WAAW,EAAE,CAAC;QACvB,IAAI,CAAC,qBAAqB,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE;YAC7C,MAAM,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,eAAe,CAAC,aAAa,EAAE,MAAM,CAAC,WAAW,CAAC,CAAC;QACtG,CAAC,CAAC,CAAC;IACL,CAAC;IAED,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC;QAChB,IAAI,CAAC,2BAA2B,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE;YACnD,MAAM,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;QAChE,CAAC,CAAC,CAAC;IACL,CAAC;IAED,IAAI,MAAM,CAAC,UAAU,EAAE,QAAQ,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QACrD,IAAI,CAAC,yCAAyC,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE;YACjE,MAAM,UAAU,CAAC,IAAI,EAAE,MAAM,CAAC,WAAW,CAAC,CAAC;YAC3C,MAAM,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;QACjE,CAAC,CAAC,CAAC;IACL,CAAC;IAED,IAAI,MAAM,CAAC,GAAG,KAAK,SAAS,EAAE,CAAC;QAC7B,IAAI,CAAC,wCAAwC,MAAM,CAAC,GAAG,GAAG,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE;YAC7E,MAAM,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC;YACtE,MAAM,UAAU,CAAC,IAAI,EAAE,MAAM,CAAC,WAAW,CAAC,CAAC;YAC3C,MAAM,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;QACjE,CAAC,CAAC,CAAC;QAEH,IAAI,CAAC,kCAAkC,MAAM,CAAC,GAAG,GAAG,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE;YACvE,MAAM,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;YAClE,MAAM,UAAU,CAAC,IAAI,EAAE,MAAM,CAAC,WAAW,CAAC,CAAC;YAC3C,MAAM,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC;QACrE,CAAC,CAAC,CAAC;IACL,CAAC;IAED,IAAI,MAAM,CAAC,GAAG,KAAK,SAAS,EAAE,CAAC;QAC7B,IAAI,CAAC,uCAAuC,MAAM,CAAC,GAAG,GAAG,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE;YAC5E,MAAM,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC;YACtE,MAAM,UAAU,CAAC,IAAI,EAAE,MAAM,CAAC,WAAW,CAAC,CAAC;YAC3C,MAAM,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;QACjE,CAAC,CAAC,CAAC;QAEH,IAAI,CAAC,kCAAkC,MAAM,CAAC,GAAG,GAAG,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE;YACvE,MAAM,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;YAClE,MAAM,UAAU,CAAC,IAAI,EAAE,MAAM,CAAC,WAAW,CAAC,CAAC;YAC3C,MAAM,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC;QACrE,CAAC,CAAC,CAAC;IACL,CAAC;IAED,IAAI,MAAM,CAAC,SAAS,KAAK,SAAS,EAAE,CAAC;QACnC,IAAI,CAAC,yBAAyB,MAAM,CAAC,SAAS,EAAE,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE;YACnE,MAAM,SAAS,GAAG,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,GAAG,CAAC,CAAC,CAAC;YACnD,MAAM,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;YACzD,MAAM,KAAK,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,WAAW,CAAC,CAAC,UAAU,EAAE,CAAC;YACpE,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,mBAAmB,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;QAC7D,CAAC,CAAC,CAAC;IACL,CAAC;AACH,CAAC","sourcesContent":["import { test, expect } from '@playwright/test';\nimport type { InputFieldConfig } from '@tft/crispr-forms/utils';\nimport { Validators } from '@angular/forms';\nimport { getInput, getError, getLabel, getHint, touchInput } from '../helpers/form.helpers';\n\n/**\n * Registers Playwright tests for an INPUT field config.\n * Call inside a `test.describe` block that has already navigated to the page.\n *\n * Tests generated based on config:\n * - Always: renders label, has correct input type\n * - If `placeholder`: renders placeholder attribute\n * - If `hint`: shows hint when untouched\n * - If `Validators.required`: shows required error on blur\n * - If `min`: validates min boundary\n * - If `max`: validates max boundary\n * - If `maxLength`: enforces maxLength attribute\n */\nexport function testInputField(config: InputFieldConfig) {\n test('renders label', async ({ page }) => {\n await expect(getLabel(page, config.controlName)).toHaveText(config.label);\n });\n\n test(`has input type \"${config.inputType ?? 'text'}\"`, async ({ page }) => {\n await expect(getInput(page, config.controlName)).toHaveAttribute('type', config.inputType ?? 'text');\n });\n\n if (config.placeholder) {\n test('renders placeholder', async ({ page }) => {\n await expect(getInput(page, config.controlName)).toHaveAttribute('placeholder', config.placeholder);\n });\n }\n\n if (config.hint) {\n test('shows hint when untouched', async ({ page }) => {\n await expect(getHint(page, config.controlName)).toBeVisible();\n });\n }\n\n if (config.validators?.includes(Validators.required)) {\n test('shows required error when blurred empty', async ({ page }) => {\n await touchInput(page, config.controlName);\n await expect(getError(page, config.controlName)).toBeVisible();\n });\n }\n\n if (config.min !== undefined) {\n test(`shows error when value is below min (${config.min})`, async ({ page }) => {\n await getInput(page, config.controlName).fill(String(config.min - 1));\n await touchInput(page, config.controlName);\n await expect(getError(page, config.controlName)).toBeVisible();\n });\n\n test(`accepts value at min boundary (${config.min})`, async ({ page }) => {\n await getInput(page, config.controlName).fill(String(config.min));\n await touchInput(page, config.controlName);\n await expect(getError(page, config.controlName)).not.toBeVisible();\n });\n }\n\n if (config.max !== undefined) {\n test(`shows error when value exceeds max (${config.max})`, async ({ page }) => {\n await getInput(page, config.controlName).fill(String(config.max + 1));\n await touchInput(page, config.controlName);\n await expect(getError(page, config.controlName)).toBeVisible();\n });\n\n test(`accepts value at max boundary (${config.max})`, async ({ page }) => {\n await getInput(page, config.controlName).fill(String(config.max));\n await touchInput(page, config.controlName);\n await expect(getError(page, config.controlName)).not.toBeVisible();\n });\n }\n\n if (config.maxLength !== undefined) {\n test(`enforces maxLength of ${config.maxLength}`, async ({ page }) => {\n const overLimit = 'a'.repeat(config.maxLength + 1);\n await getInput(page, config.controlName).fill(overLimit);\n const value = await getInput(page, config.controlName).inputValue();\n expect(value.length).toBeLessThanOrEqual(config.maxLength);\n });\n }\n}\n"]}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { SelectFieldConfig } from '@tft/crispr-forms/utils';
|
|
2
|
+
/**
|
|
3
|
+
* Registers Playwright tests for a SELECT field config.
|
|
4
|
+
* Call inside a `test.describe` block that has already navigated to the page.
|
|
5
|
+
*/
|
|
6
|
+
export declare function testSelectField(config: SelectFieldConfig): void;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { test, expect } from '@playwright/test';
|
|
2
|
+
import { Validators } from '@angular/forms';
|
|
3
|
+
import { getLabel, getHint, getError, getMatSelect, openSelect, selectOption, touchSelect } from '../helpers/form.helpers';
|
|
4
|
+
/**
|
|
5
|
+
* Registers Playwright tests for a SELECT field config.
|
|
6
|
+
* Call inside a `test.describe` block that has already navigated to the page.
|
|
7
|
+
*/
|
|
8
|
+
export function testSelectField(config) {
|
|
9
|
+
if (config.label) {
|
|
10
|
+
test('renders label', async ({ page }) => {
|
|
11
|
+
await expect(getLabel(page, config.controlName)).toHaveText(config.label);
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
if (config.hint) {
|
|
15
|
+
test('shows hint when untouched', async ({ page }) => {
|
|
16
|
+
await expect(getHint(page, config.controlName)).toBeVisible();
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
if (Array.isArray(config.options)) {
|
|
20
|
+
test('shows all options when opened', async ({ page }) => {
|
|
21
|
+
await openSelect(page, config.controlName);
|
|
22
|
+
const options = page.locator('mat-option');
|
|
23
|
+
await expect(options).toHaveCount(config.options.length);
|
|
24
|
+
await page.keyboard.press('Escape');
|
|
25
|
+
});
|
|
26
|
+
test('can select an option', async ({ page }) => {
|
|
27
|
+
const firstOption = config.options[0];
|
|
28
|
+
await selectOption(page, config.controlName, firstOption.label);
|
|
29
|
+
await expect(getMatSelect(page, config.controlName)).toContainText(firstOption.label);
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
if (config.validators?.includes(Validators.required)) {
|
|
33
|
+
test('shows required error when blurred without selection', async ({ page }) => {
|
|
34
|
+
await touchSelect(page, config.controlName);
|
|
35
|
+
await expect(getError(page, config.controlName, 'select')).toBeVisible();
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=select.tests.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"select.tests.js","sourceRoot":"","sources":["../../../../libs/crispr-forms-testing/src/field-tests/select.tests.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAEhD,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAC5C,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,UAAU,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAE3H;;;GAGG;AACH,MAAM,UAAU,eAAe,CAAC,MAAyB;IACvD,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;QACjB,IAAI,CAAC,eAAe,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE;YACvC,MAAM,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,UAAU,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAC5E,CAAC,CAAC,CAAC;IACL,CAAC;IAED,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC;QAChB,IAAI,CAAC,2BAA2B,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE;YACnD,MAAM,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;QAChE,CAAC,CAAC,CAAC;IACL,CAAC;IAED,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC;QAClC,IAAI,CAAC,+BAA+B,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE;YACvD,MAAM,UAAU,CAAC,IAAI,EAAE,MAAM,CAAC,WAAW,CAAC,CAAC;YAC3C,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC;YAC3C,MAAM,MAAM,CAAC,OAAO,CAAC,CAAC,WAAW,CAAE,MAAM,CAAC,OAA0B,CAAC,MAAM,CAAC,CAAC;YAC7E,MAAM,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;QACtC,CAAC,CAAC,CAAC;QAEH,IAAI,CAAC,sBAAsB,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE;YAC9C,MAAM,WAAW,GAAI,MAAM,CAAC,OAAoD,CAAC,CAAC,CAAC,CAAC;YACpF,MAAM,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,WAAW,EAAE,WAAW,CAAC,KAAK,CAAC,CAAC;YAChE,MAAM,MAAM,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,aAAa,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;QACxF,CAAC,CAAC,CAAC;IACL,CAAC;IAED,IAAI,MAAM,CAAC,UAAU,EAAE,QAAQ,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QACrD,IAAI,CAAC,qDAAqD,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE;YAC7E,MAAM,WAAW,CAAC,IAAI,EAAE,MAAM,CAAC,WAAW,CAAC,CAAC;YAC5C,MAAM,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;QAC3E,CAAC,CAAC,CAAC;IACL,CAAC;AACH,CAAC","sourcesContent":["import { test, expect } from '@playwright/test';\nimport type { SelectFieldConfig, SelectOption } from '@tft/crispr-forms/utils';\nimport { Validators } from '@angular/forms';\nimport { getLabel, getHint, getError, getMatSelect, openSelect, selectOption, touchSelect } from '../helpers/form.helpers';\n\n/**\n * Registers Playwright tests for a SELECT field config.\n * Call inside a `test.describe` block that has already navigated to the page.\n */\nexport function testSelectField(config: SelectFieldConfig) {\n if (config.label) {\n test('renders label', async ({ page }) => {\n await expect(getLabel(page, config.controlName)).toHaveText(config.label);\n });\n }\n\n if (config.hint) {\n test('shows hint when untouched', async ({ page }) => {\n await expect(getHint(page, config.controlName)).toBeVisible();\n });\n }\n\n if (Array.isArray(config.options)) {\n test('shows all options when opened', async ({ page }) => {\n await openSelect(page, config.controlName);\n const options = page.locator('mat-option');\n await expect(options).toHaveCount((config.options as SelectOption[]).length);\n await page.keyboard.press('Escape');\n });\n\n test('can select an option', async ({ page }) => {\n const firstOption = (config.options as Array<{ label: string; value: unknown }>)[0];\n await selectOption(page, config.controlName, firstOption.label);\n await expect(getMatSelect(page, config.controlName)).toContainText(firstOption.label);\n });\n }\n\n if (config.validators?.includes(Validators.required)) {\n test('shows required error when blurred without selection', async ({ page }) => {\n await touchSelect(page, config.controlName);\n await expect(getError(page, config.controlName, 'select')).toBeVisible();\n });\n }\n}\n"]}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { SliderFieldConfig } from '@tft/crispr-forms/utils';
|
|
2
|
+
/**
|
|
3
|
+
* Registers Playwright tests for a SLIDER field config.
|
|
4
|
+
* Call inside a `test.describe` block that has already navigated to the page.
|
|
5
|
+
*
|
|
6
|
+
* Note: slider uses a plain `<label>` (not `<mat-label>`) because it is not
|
|
7
|
+
* rendered inside a `mat-form-field`. The `data-testid` is on the `<mat-slider>`
|
|
8
|
+
* element itself.
|
|
9
|
+
*
|
|
10
|
+
* Note: `min`/`max` are read from the native HTML attributes on `input[matSliderThumb]`,
|
|
11
|
+
* not from `aria-valuemin`/`aria-valuemax`.
|
|
12
|
+
*/
|
|
13
|
+
export declare function testSliderField(config: SliderFieldConfig): void;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { test, expect } from '@playwright/test';
|
|
2
|
+
/**
|
|
3
|
+
* Registers Playwright tests for a SLIDER field config.
|
|
4
|
+
* Call inside a `test.describe` block that has already navigated to the page.
|
|
5
|
+
*
|
|
6
|
+
* Note: slider uses a plain `<label>` (not `<mat-label>`) because it is not
|
|
7
|
+
* rendered inside a `mat-form-field`. The `data-testid` is on the `<mat-slider>`
|
|
8
|
+
* element itself.
|
|
9
|
+
*
|
|
10
|
+
* Note: `min`/`max` are read from the native HTML attributes on `input[matSliderThumb]`,
|
|
11
|
+
* not from `aria-valuemin`/`aria-valuemax`.
|
|
12
|
+
*/
|
|
13
|
+
export function testSliderField(config) {
|
|
14
|
+
test('renders slider', async ({ page }) => {
|
|
15
|
+
const slider = page.locator(`[data-testid="${config.controlName}"]`);
|
|
16
|
+
await expect(slider).toBeVisible();
|
|
17
|
+
});
|
|
18
|
+
if (config.label) {
|
|
19
|
+
test('renders label', async ({ page }) => {
|
|
20
|
+
const sliderHost = page.locator(`crispr-slider-field:has([data-testid="${config.controlName}"])`);
|
|
21
|
+
await expect(sliderHost.locator('label')).toContainText(config.label);
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
if (config.min !== undefined) {
|
|
25
|
+
test(`has correct min value (${config.min})`, async ({ page }) => {
|
|
26
|
+
const thumb = page.locator(`[data-testid="${config.controlName}"] input`);
|
|
27
|
+
await expect(thumb).toHaveAttribute('min', String(config.min));
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
if (config.max !== undefined) {
|
|
31
|
+
test(`has correct max value (${config.max})`, async ({ page }) => {
|
|
32
|
+
const thumb = page.locator(`[data-testid="${config.controlName}"] input`);
|
|
33
|
+
await expect(thumb).toHaveAttribute('max', String(config.max));
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=slider.tests.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"slider.tests.js","sourceRoot":"","sources":["../../../../libs/crispr-forms-testing/src/field-tests/slider.tests.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAGhD;;;;;;;;;;GAUG;AACH,MAAM,UAAU,eAAe,CAAC,MAAyB;IACvD,IAAI,CAAC,gBAAgB,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE;QACxC,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,iBAAiB,MAAM,CAAC,WAAW,IAAI,CAAC,CAAC;QACrE,MAAM,MAAM,CAAC,MAAM,CAAC,CAAC,WAAW,EAAE,CAAC;IACrC,CAAC,CAAC,CAAC;IAEH,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;QACjB,IAAI,CAAC,eAAe,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE;YACvC,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,yCAAyC,MAAM,CAAC,WAAW,KAAK,CAAC,CAAC;YAClG,MAAM,MAAM,CAAC,UAAU,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,aAAa,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACxE,CAAC,CAAC,CAAC;IACL,CAAC;IAED,IAAI,MAAM,CAAC,GAAG,KAAK,SAAS,EAAE,CAAC;QAC7B,IAAI,CAAC,0BAA0B,MAAM,CAAC,GAAG,GAAG,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE;YAC/D,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,iBAAiB,MAAM,CAAC,WAAW,UAAU,CAAC,CAAC;YAC1E,MAAM,MAAM,CAAC,KAAK,CAAC,CAAC,eAAe,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;QACjE,CAAC,CAAC,CAAC;IACL,CAAC;IAED,IAAI,MAAM,CAAC,GAAG,KAAK,SAAS,EAAE,CAAC;QAC7B,IAAI,CAAC,0BAA0B,MAAM,CAAC,GAAG,GAAG,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE;YAC/D,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,iBAAiB,MAAM,CAAC,WAAW,UAAU,CAAC,CAAC;YAC1E,MAAM,MAAM,CAAC,KAAK,CAAC,CAAC,eAAe,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;QACjE,CAAC,CAAC,CAAC;IACL,CAAC;AACH,CAAC","sourcesContent":["import { test, expect } from '@playwright/test';\nimport type { SliderFieldConfig } from '@tft/crispr-forms/utils';\n\n/**\n * Registers Playwright tests for a SLIDER field config.\n * Call inside a `test.describe` block that has already navigated to the page.\n *\n * Note: slider uses a plain `<label>` (not `<mat-label>`) because it is not\n * rendered inside a `mat-form-field`. The `data-testid` is on the `<mat-slider>`\n * element itself.\n *\n * Note: `min`/`max` are read from the native HTML attributes on `input[matSliderThumb]`,\n * not from `aria-valuemin`/`aria-valuemax`.\n */\nexport function testSliderField(config: SliderFieldConfig) {\n test('renders slider', async ({ page }) => {\n const slider = page.locator(`[data-testid=\"${config.controlName}\"]`);\n await expect(slider).toBeVisible();\n });\n\n if (config.label) {\n test('renders label', async ({ page }) => {\n const sliderHost = page.locator(`crispr-slider-field:has([data-testid=\"${config.controlName}\"])`);\n await expect(sliderHost.locator('label')).toContainText(config.label);\n });\n }\n\n if (config.min !== undefined) {\n test(`has correct min value (${config.min})`, async ({ page }) => {\n const thumb = page.locator(`[data-testid=\"${config.controlName}\"] input`);\n await expect(thumb).toHaveAttribute('min', String(config.min));\n });\n }\n\n if (config.max !== undefined) {\n test(`has correct max value (${config.max})`, async ({ page }) => {\n const thumb = page.locator(`[data-testid=\"${config.controlName}\"] input`);\n await expect(thumb).toHaveAttribute('max', String(config.max));\n });\n }\n}\n"]}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { TextareaFieldConfig } from '@tft/crispr-forms/utils';
|
|
2
|
+
/**
|
|
3
|
+
* Registers Playwright tests for a TEXTAREA field config.
|
|
4
|
+
* Call inside a `test.describe` block that has already navigated to the page.
|
|
5
|
+
*/
|
|
6
|
+
export declare function testTextareaField(config: TextareaFieldConfig): void;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { test, expect } from '@playwright/test';
|
|
2
|
+
import { Validators } from '@angular/forms';
|
|
3
|
+
import { getMatFormField, getError, getLabel, getHint } from '../helpers/form.helpers';
|
|
4
|
+
/**
|
|
5
|
+
* Registers Playwright tests for a TEXTAREA field config.
|
|
6
|
+
* Call inside a `test.describe` block that has already navigated to the page.
|
|
7
|
+
*/
|
|
8
|
+
export function testTextareaField(config) {
|
|
9
|
+
if (config.label) {
|
|
10
|
+
test('renders label', async ({ page }) => {
|
|
11
|
+
await expect(getLabel(page, config.controlName)).toHaveText(config.label);
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
if (config.hint) {
|
|
15
|
+
test('shows hint when untouched', async ({ page }) => {
|
|
16
|
+
await expect(getHint(page, config.controlName)).toBeVisible();
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
if (config.validators?.includes(Validators.required)) {
|
|
20
|
+
test('shows required error when blurred empty', async ({ page }) => {
|
|
21
|
+
const textarea = getMatFormField(page, config.controlName).locator('textarea');
|
|
22
|
+
await textarea.click();
|
|
23
|
+
await page.keyboard.press('Tab');
|
|
24
|
+
await expect(getError(page, config.controlName, 'textarea')).toBeVisible();
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=textarea.tests.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"textarea.tests.js","sourceRoot":"","sources":["../../../../libs/crispr-forms-testing/src/field-tests/textarea.tests.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAEhD,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAC5C,OAAO,EAAE,eAAe,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAC;AAEvF;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAAC,MAA2B;IAC3D,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;QACjB,IAAI,CAAC,eAAe,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE;YACvC,MAAM,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,UAAU,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAC5E,CAAC,CAAC,CAAC;IACL,CAAC;IAED,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC;QAChB,IAAI,CAAC,2BAA2B,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE;YACnD,MAAM,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;QAChE,CAAC,CAAC,CAAC;IACL,CAAC;IAED,IAAI,MAAM,CAAC,UAAU,EAAE,QAAQ,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QACrD,IAAI,CAAC,yCAAyC,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE;YACjE,MAAM,QAAQ,GAAG,eAAe,CAAC,IAAI,EAAE,MAAM,CAAC,WAAW,CAAC,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;YAC/E,MAAM,QAAQ,CAAC,KAAK,EAAE,CAAC;YACvB,MAAM,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;YACjC,MAAM,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,WAAW,EAAE,UAAU,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;QAC7E,CAAC,CAAC,CAAC;IACL,CAAC;AACH,CAAC","sourcesContent":["import { test, expect } from '@playwright/test';\nimport type { TextareaFieldConfig } from '@tft/crispr-forms/utils';\nimport { Validators } from '@angular/forms';\nimport { getMatFormField, getError, getLabel, getHint } from '../helpers/form.helpers';\n\n/**\n * Registers Playwright tests for a TEXTAREA field config.\n * Call inside a `test.describe` block that has already navigated to the page.\n */\nexport function testTextareaField(config: TextareaFieldConfig) {\n if (config.label) {\n test('renders label', async ({ page }) => {\n await expect(getLabel(page, config.controlName)).toHaveText(config.label);\n });\n }\n\n if (config.hint) {\n test('shows hint when untouched', async ({ page }) => {\n await expect(getHint(page, config.controlName)).toBeVisible();\n });\n }\n\n if (config.validators?.includes(Validators.required)) {\n test('shows required error when blurred empty', async ({ page }) => {\n const textarea = getMatFormField(page, config.controlName).locator('textarea');\n await textarea.click();\n await page.keyboard.press('Tab');\n await expect(getError(page, config.controlName, 'textarea')).toBeVisible();\n });\n }\n}\n"]}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { UnitConversionFieldConfig } from '@tft/crispr-forms/utils';
|
|
2
|
+
/**
|
|
3
|
+
* Registers Playwright tests for a UNIT_CONVERSION field config.
|
|
4
|
+
* Call inside a `test.describe` block that has already navigated to the page.
|
|
5
|
+
*
|
|
6
|
+
* Note: required error display is not testable for this field type. The
|
|
7
|
+
* `displayValueControl` (what the user types into) is separate from the
|
|
8
|
+
* `[formControlName]` that holds validators, so validation errors are never
|
|
9
|
+
* wired to the visible input.
|
|
10
|
+
*/
|
|
11
|
+
export declare function testUnitConversionField(config: UnitConversionFieldConfig): void;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { test, expect } from '@playwright/test';
|
|
2
|
+
import { getLabel, getMatFormField } from '../helpers/form.helpers';
|
|
3
|
+
/**
|
|
4
|
+
* Registers Playwright tests for a UNIT_CONVERSION field config.
|
|
5
|
+
* Call inside a `test.describe` block that has already navigated to the page.
|
|
6
|
+
*
|
|
7
|
+
* Note: required error display is not testable for this field type. The
|
|
8
|
+
* `displayValueControl` (what the user types into) is separate from the
|
|
9
|
+
* `[formControlName]` that holds validators, so validation errors are never
|
|
10
|
+
* wired to the visible input.
|
|
11
|
+
*/
|
|
12
|
+
export function testUnitConversionField(config) {
|
|
13
|
+
if (config.label) {
|
|
14
|
+
test('renders label', async ({ page }) => {
|
|
15
|
+
await expect(getLabel(page, config.controlName)).toHaveText(config.label);
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
test('renders number input', async ({ page }) => {
|
|
19
|
+
const numberInput = getMatFormField(page, config.controlName).locator('input[type="number"]');
|
|
20
|
+
await expect(numberInput).toBeVisible();
|
|
21
|
+
});
|
|
22
|
+
if (config.showUnitSelect) {
|
|
23
|
+
test('renders unit select when showUnitSelect is true', async ({ page }) => {
|
|
24
|
+
const unitSelect = page.locator(`crispr-unit-conversion-field:has([data-testid="${config.controlName}"]) .unit-field mat-select`);
|
|
25
|
+
await expect(unitSelect).toBeVisible();
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=unit-conversion.tests.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"unit-conversion.tests.js","sourceRoot":"","sources":["../../../../libs/crispr-forms-testing/src/field-tests/unit-conversion.tests.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAEhD,OAAO,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAEpE;;;;;;;;GAQG;AACH,MAAM,UAAU,uBAAuB,CAAC,MAAiC;IACvE,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;QACjB,IAAI,CAAC,eAAe,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE;YACvC,MAAM,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,UAAU,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAC5E,CAAC,CAAC,CAAC;IACL,CAAC;IAED,IAAI,CAAC,sBAAsB,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE;QAC9C,MAAM,WAAW,GAAG,eAAe,CAAC,IAAI,EAAE,MAAM,CAAC,WAAW,CAAC,CAAC,OAAO,CAAC,sBAAsB,CAAC,CAAC;QAC9F,MAAM,MAAM,CAAC,WAAW,CAAC,CAAC,WAAW,EAAE,CAAC;IAC1C,CAAC,CAAC,CAAC;IAEH,IAAI,MAAM,CAAC,cAAc,EAAE,CAAC;QAC1B,IAAI,CAAC,iDAAiD,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE;YACzE,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,kDAAkD,MAAM,CAAC,WAAW,4BAA4B,CAAC,CAAC;YAClI,MAAM,MAAM,CAAC,UAAU,CAAC,CAAC,WAAW,EAAE,CAAC;QACzC,CAAC,CAAC,CAAC;IACL,CAAC;AACH,CAAC","sourcesContent":["import { test, expect } from '@playwright/test';\nimport type { UnitConversionFieldConfig } from '@tft/crispr-forms/utils';\nimport { getLabel, getMatFormField } from '../helpers/form.helpers';\n\n/**\n * Registers Playwright tests for a UNIT_CONVERSION field config.\n * Call inside a `test.describe` block that has already navigated to the page.\n *\n * Note: required error display is not testable for this field type. The\n * `displayValueControl` (what the user types into) is separate from the\n * `[formControlName]` that holds validators, so validation errors are never\n * wired to the visible input.\n */\nexport function testUnitConversionField(config: UnitConversionFieldConfig) {\n if (config.label) {\n test('renders label', async ({ page }) => {\n await expect(getLabel(page, config.controlName)).toHaveText(config.label);\n });\n }\n\n test('renders number input', async ({ page }) => {\n const numberInput = getMatFormField(page, config.controlName).locator('input[type=\"number\"]');\n await expect(numberInput).toBeVisible();\n });\n\n if (config.showUnitSelect) {\n test('renders unit select when showUnitSelect is true', async ({ page }) => {\n const unitSelect = page.locator(`crispr-unit-conversion-field:has([data-testid=\"${config.controlName}\"]) .unit-field mat-select`);\n await expect(unitSelect).toBeVisible();\n });\n }\n}\n"]}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { Page, Locator } from '@playwright/test';
|
|
2
|
+
/**
|
|
3
|
+
* Returns the locator for a field's root element (the element with `data-testid`).
|
|
4
|
+
* For most fields this is a `mat-form-field`; for checkbox/slider it is the control element.
|
|
5
|
+
*
|
|
6
|
+
* For nested fields, pass the full hierarchical testId path:
|
|
7
|
+
* - subGroup child: `'address.city'`
|
|
8
|
+
* - formGroupList item child: `'contacts[0].email'`
|
|
9
|
+
*/
|
|
10
|
+
export declare function getMatFormField(page: Page, testId: string): Locator;
|
|
11
|
+
/**
|
|
12
|
+
* Returns the `<input>` inside a field. Works for INPUT, DATEPICKER, and other fields
|
|
13
|
+
* that render a single native input.
|
|
14
|
+
*/
|
|
15
|
+
export declare function getInput(page: Page, testId: string): Locator;
|
|
16
|
+
/**
|
|
17
|
+
* Returns the validation error paragraph for a field.
|
|
18
|
+
*
|
|
19
|
+
* Targets the inner `<p class="tft-control-error">` rather than the host element to
|
|
20
|
+
* avoid Playwright strict-mode failures on fields like autocomplete/chiplist that
|
|
21
|
+
* render two `tft-control-error` host elements (one for each internal input).
|
|
22
|
+
*
|
|
23
|
+
* @param fieldType - The kebab-case field type used in the component selector,
|
|
24
|
+
* e.g. `'input'`, `'select'`, `'autocomplete'`, `'autocomplete-chiplist'`,
|
|
25
|
+
* `'checkbox'`, `'datepicker'`, `'textarea'`, `'slider'`. Defaults to `'input'`.
|
|
26
|
+
*/
|
|
27
|
+
export declare function getError(page: Page, testId: string, fieldType?: string): Locator;
|
|
28
|
+
/**
|
|
29
|
+
* Returns the `<mat-label>` element inside a field's form field.
|
|
30
|
+
*/
|
|
31
|
+
export declare function getLabel(page: Page, testId: string): Locator;
|
|
32
|
+
/**
|
|
33
|
+
* Returns the `<mat-hint>` element inside a field's form field.
|
|
34
|
+
*/
|
|
35
|
+
export declare function getHint(page: Page, testId: string): Locator;
|
|
36
|
+
/**
|
|
37
|
+
* Clicks an input and presses Tab to trigger a real blur event.
|
|
38
|
+
* Angular's touched state is driven by blur, not just focus-out.
|
|
39
|
+
*/
|
|
40
|
+
export declare function touchInput(page: Page, testId: string): Promise<void>;
|
|
41
|
+
/** Returns the `<mat-select>` element inside a select field. */
|
|
42
|
+
export declare function getMatSelect(page: Page, testId: string): Locator;
|
|
43
|
+
/** Opens a `mat-select` dropdown. */
|
|
44
|
+
export declare function openSelect(page: Page, testId: string): Promise<void>;
|
|
45
|
+
/** Opens a `mat-select`, clicks the option with the given label, and waits for it to close. */
|
|
46
|
+
export declare function selectOption(page: Page, testId: string, optionLabel: string): Promise<void>;
|
|
47
|
+
/**
|
|
48
|
+
* Opens a `mat-select`, presses Escape, then Tab to blur it.
|
|
49
|
+
* The extra Tab is required — Escape alone dismisses the panel but does not blur
|
|
50
|
+
* the trigger element, so `ControlErrorsDirective` never sees a blur event.
|
|
51
|
+
*/
|
|
52
|
+
export declare function touchSelect(page: Page, testId: string): Promise<void>;
|
|
53
|
+
/** Returns the `<mat-checkbox>` element for a checkbox field. */
|
|
54
|
+
export declare function getCheckbox(page: Page, testId: string): Locator;
|
|
55
|
+
/**
|
|
56
|
+
* Clicks a checkbox twice (check then uncheck) and presses Tab to blur,
|
|
57
|
+
* leaving it unchecked and touched — useful for triggering `requiredTrue` errors.
|
|
58
|
+
*/
|
|
59
|
+
export declare function touchCheckbox(page: Page, testId: string): Promise<void>;
|
|
60
|
+
/**
|
|
61
|
+
* Returns the visible search input for an autocomplete field.
|
|
62
|
+
* Note: this is the `role="textbox"` input, NOT the hidden `[formControlName]` anchor.
|
|
63
|
+
*/
|
|
64
|
+
export declare function getAutocompleteInput(page: Page, testId: string): Locator;
|
|
65
|
+
/** Clicks the autocomplete input and presses Tab to blur. */
|
|
66
|
+
export declare function touchAutocomplete(page: Page, testId: string): Promise<void>;
|
|
67
|
+
/**
|
|
68
|
+
* Programmatically clicks the hidden crispr-form submit button.
|
|
69
|
+
*
|
|
70
|
+
* This fires both the native `submit` event (required by `ControlErrorsFormDirective`
|
|
71
|
+
* to show errors on untouched controls) and Angular's `ngSubmit` event.
|
|
72
|
+
*
|
|
73
|
+
* Must use `evaluate` + native `.click()` because `display:none` elements
|
|
74
|
+
* have no layout position for Playwright pointer events.
|
|
75
|
+
*
|
|
76
|
+
* @param nth - Zero-based index of the form on the page. Defaults to `0`.
|
|
77
|
+
*/
|
|
78
|
+
export declare function submitForm(page: Page, nth?: number): Promise<void>;
|
|
79
|
+
/** Clicks the datepicker input and presses Tab to blur. */
|
|
80
|
+
export declare function touchDatepicker(page: Page, testId: string): Promise<void>;
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns the locator for a field's root element (the element with `data-testid`).
|
|
3
|
+
* For most fields this is a `mat-form-field`; for checkbox/slider it is the control element.
|
|
4
|
+
*
|
|
5
|
+
* For nested fields, pass the full hierarchical testId path:
|
|
6
|
+
* - subGroup child: `'address.city'`
|
|
7
|
+
* - formGroupList item child: `'contacts[0].email'`
|
|
8
|
+
*/
|
|
9
|
+
export function getMatFormField(page, testId) {
|
|
10
|
+
return page.locator(`[data-testid="${testId}"]`);
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Returns the `<input>` inside a field. Works for INPUT, DATEPICKER, and other fields
|
|
14
|
+
* that render a single native input.
|
|
15
|
+
*/
|
|
16
|
+
export function getInput(page, testId) {
|
|
17
|
+
return getMatFormField(page, testId).locator('input');
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Returns the validation error paragraph for a field.
|
|
21
|
+
*
|
|
22
|
+
* Targets the inner `<p class="tft-control-error">` rather than the host element to
|
|
23
|
+
* avoid Playwright strict-mode failures on fields like autocomplete/chiplist that
|
|
24
|
+
* render two `tft-control-error` host elements (one for each internal input).
|
|
25
|
+
*
|
|
26
|
+
* @param fieldType - The kebab-case field type used in the component selector,
|
|
27
|
+
* e.g. `'input'`, `'select'`, `'autocomplete'`, `'autocomplete-chiplist'`,
|
|
28
|
+
* `'checkbox'`, `'datepicker'`, `'textarea'`, `'slider'`. Defaults to `'input'`.
|
|
29
|
+
*/
|
|
30
|
+
export function getError(page, testId, fieldType = 'input') {
|
|
31
|
+
return page.locator(`crispr-${fieldType}-field:has([data-testid="${testId}"]) p.tft-control-error`);
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Returns the `<mat-label>` element inside a field's form field.
|
|
35
|
+
*/
|
|
36
|
+
export function getLabel(page, testId) {
|
|
37
|
+
return getMatFormField(page, testId).locator('mat-label');
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Returns the `<mat-hint>` element inside a field's form field.
|
|
41
|
+
*/
|
|
42
|
+
export function getHint(page, testId) {
|
|
43
|
+
return getMatFormField(page, testId).locator('mat-hint');
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Clicks an input and presses Tab to trigger a real blur event.
|
|
47
|
+
* Angular's touched state is driven by blur, not just focus-out.
|
|
48
|
+
*/
|
|
49
|
+
export async function touchInput(page, testId) {
|
|
50
|
+
await getInput(page, testId).click();
|
|
51
|
+
await page.keyboard.press('Tab');
|
|
52
|
+
}
|
|
53
|
+
/** Returns the `<mat-select>` element inside a select field. */
|
|
54
|
+
export function getMatSelect(page, testId) {
|
|
55
|
+
return getMatFormField(page, testId).locator('mat-select');
|
|
56
|
+
}
|
|
57
|
+
/** Opens a `mat-select` dropdown. */
|
|
58
|
+
export async function openSelect(page, testId) {
|
|
59
|
+
await getMatSelect(page, testId).click();
|
|
60
|
+
}
|
|
61
|
+
/** Opens a `mat-select`, clicks the option with the given label, and waits for it to close. */
|
|
62
|
+
export async function selectOption(page, testId, optionLabel) {
|
|
63
|
+
await openSelect(page, testId);
|
|
64
|
+
await page.locator('mat-option').filter({ hasText: optionLabel }).click();
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Opens a `mat-select`, presses Escape, then Tab to blur it.
|
|
68
|
+
* The extra Tab is required — Escape alone dismisses the panel but does not blur
|
|
69
|
+
* the trigger element, so `ControlErrorsDirective` never sees a blur event.
|
|
70
|
+
*/
|
|
71
|
+
export async function touchSelect(page, testId) {
|
|
72
|
+
await openSelect(page, testId);
|
|
73
|
+
await page.keyboard.press('Escape');
|
|
74
|
+
await page.keyboard.press('Tab');
|
|
75
|
+
}
|
|
76
|
+
/** Returns the `<mat-checkbox>` element for a checkbox field. */
|
|
77
|
+
export function getCheckbox(page, testId) {
|
|
78
|
+
return page.locator(`[data-testid="${testId}"]`);
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Clicks a checkbox twice (check then uncheck) and presses Tab to blur,
|
|
82
|
+
* leaving it unchecked and touched — useful for triggering `requiredTrue` errors.
|
|
83
|
+
*/
|
|
84
|
+
export async function touchCheckbox(page, testId) {
|
|
85
|
+
await getCheckbox(page, testId).click();
|
|
86
|
+
await getCheckbox(page, testId).click();
|
|
87
|
+
await page.keyboard.press('Tab');
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Returns the visible search input for an autocomplete field.
|
|
91
|
+
* Note: this is the `role="textbox"` input, NOT the hidden `[formControlName]` anchor.
|
|
92
|
+
*/
|
|
93
|
+
export function getAutocompleteInput(page, testId) {
|
|
94
|
+
return getMatFormField(page, testId).locator('input[type="textbox"]');
|
|
95
|
+
}
|
|
96
|
+
/** Clicks the autocomplete input and presses Tab to blur. */
|
|
97
|
+
export async function touchAutocomplete(page, testId) {
|
|
98
|
+
await getAutocompleteInput(page, testId).click();
|
|
99
|
+
await page.keyboard.press('Tab');
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Programmatically clicks the hidden crispr-form submit button.
|
|
103
|
+
*
|
|
104
|
+
* This fires both the native `submit` event (required by `ControlErrorsFormDirective`
|
|
105
|
+
* to show errors on untouched controls) and Angular's `ngSubmit` event.
|
|
106
|
+
*
|
|
107
|
+
* Must use `evaluate` + native `.click()` because `display:none` elements
|
|
108
|
+
* have no layout position for Playwright pointer events.
|
|
109
|
+
*
|
|
110
|
+
* @param nth - Zero-based index of the form on the page. Defaults to `0`.
|
|
111
|
+
*/
|
|
112
|
+
export async function submitForm(page, nth = 0) {
|
|
113
|
+
await page.evaluate((n) => {
|
|
114
|
+
const triggers = document.querySelectorAll('[data-testid="submit-trigger"]');
|
|
115
|
+
triggers[n]?.click();
|
|
116
|
+
}, nth);
|
|
117
|
+
}
|
|
118
|
+
/** Clicks the datepicker input and presses Tab to blur. */
|
|
119
|
+
export async function touchDatepicker(page, testId) {
|
|
120
|
+
await getMatFormField(page, testId).locator('input').click();
|
|
121
|
+
await page.keyboard.press('Tab');
|
|
122
|
+
}
|
|
123
|
+
//# sourceMappingURL=form.helpers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"form.helpers.js","sourceRoot":"","sources":["../../../../libs/crispr-forms-testing/src/helpers/form.helpers.ts"],"names":[],"mappings":"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","sourcesContent":["import { Page, Locator } from '@playwright/test';\n\n/**\n * Returns the locator for a field's root element (the element with `data-testid`).\n * For most fields this is a `mat-form-field`; for checkbox/slider it is the control element.\n *\n * For nested fields, pass the full hierarchical testId path:\n * - subGroup child: `'address.city'`\n * - formGroupList item child: `'contacts[0].email'`\n */\nexport function getMatFormField(page: Page, testId: string): Locator {\n return page.locator(`[data-testid=\"${testId}\"]`);\n}\n\n/**\n * Returns the `<input>` inside a field. Works for INPUT, DATEPICKER, and other fields\n * that render a single native input.\n */\nexport function getInput(page: Page, testId: string): Locator {\n return getMatFormField(page, testId).locator('input');\n}\n\n/**\n * Returns the validation error paragraph for a field.\n *\n * Targets the inner `<p class=\"tft-control-error\">` rather than the host element to\n * avoid Playwright strict-mode failures on fields like autocomplete/chiplist that\n * render two `tft-control-error` host elements (one for each internal input).\n *\n * @param fieldType - The kebab-case field type used in the component selector,\n * e.g. `'input'`, `'select'`, `'autocomplete'`, `'autocomplete-chiplist'`,\n * `'checkbox'`, `'datepicker'`, `'textarea'`, `'slider'`. Defaults to `'input'`.\n */\nexport function getError(page: Page, testId: string, fieldType = 'input'): Locator {\n return page.locator(`crispr-${fieldType}-field:has([data-testid=\"${testId}\"]) p.tft-control-error`);\n}\n\n/**\n * Returns the `<mat-label>` element inside a field's form field.\n */\nexport function getLabel(page: Page, testId: string): Locator {\n return getMatFormField(page, testId).locator('mat-label');\n}\n\n/**\n * Returns the `<mat-hint>` element inside a field's form field.\n */\nexport function getHint(page: Page, testId: string): Locator {\n return getMatFormField(page, testId).locator('mat-hint');\n}\n\n/**\n * Clicks an input and presses Tab to trigger a real blur event.\n * Angular's touched state is driven by blur, not just focus-out.\n */\nexport async function touchInput(page: Page, testId: string): Promise<void> {\n await getInput(page, testId).click();\n await page.keyboard.press('Tab');\n}\n\n/** Returns the `<mat-select>` element inside a select field. */\nexport function getMatSelect(page: Page, testId: string): Locator {\n return getMatFormField(page, testId).locator('mat-select');\n}\n\n/** Opens a `mat-select` dropdown. */\nexport async function openSelect(page: Page, testId: string): Promise<void> {\n await getMatSelect(page, testId).click();\n}\n\n/** Opens a `mat-select`, clicks the option with the given label, and waits for it to close. */\nexport async function selectOption(page: Page, testId: string, optionLabel: string): Promise<void> {\n await openSelect(page, testId);\n await page.locator('mat-option').filter({ hasText: optionLabel }).click();\n}\n\n/**\n * Opens a `mat-select`, presses Escape, then Tab to blur it.\n * The extra Tab is required — Escape alone dismisses the panel but does not blur\n * the trigger element, so `ControlErrorsDirective` never sees a blur event.\n */\nexport async function touchSelect(page: Page, testId: string): Promise<void> {\n await openSelect(page, testId);\n await page.keyboard.press('Escape');\n await page.keyboard.press('Tab');\n}\n\n/** Returns the `<mat-checkbox>` element for a checkbox field. */\nexport function getCheckbox(page: Page, testId: string): Locator {\n return page.locator(`[data-testid=\"${testId}\"]`);\n}\n\n/**\n * Clicks a checkbox twice (check then uncheck) and presses Tab to blur,\n * leaving it unchecked and touched — useful for triggering `requiredTrue` errors.\n */\nexport async function touchCheckbox(page: Page, testId: string): Promise<void> {\n await getCheckbox(page, testId).click();\n await getCheckbox(page, testId).click();\n await page.keyboard.press('Tab');\n}\n\n/**\n * Returns the visible search input for an autocomplete field.\n * Note: this is the `role=\"textbox\"` input, NOT the hidden `[formControlName]` anchor.\n */\nexport function getAutocompleteInput(page: Page, testId: string): Locator {\n return getMatFormField(page, testId).locator('input[type=\"textbox\"]');\n}\n\n/** Clicks the autocomplete input and presses Tab to blur. */\nexport async function touchAutocomplete(page: Page, testId: string): Promise<void> {\n await getAutocompleteInput(page, testId).click();\n await page.keyboard.press('Tab');\n}\n\n/**\n * Programmatically clicks the hidden crispr-form submit button.\n *\n * This fires both the native `submit` event (required by `ControlErrorsFormDirective`\n * to show errors on untouched controls) and Angular's `ngSubmit` event.\n *\n * Must use `evaluate` + native `.click()` because `display:none` elements\n * have no layout position for Playwright pointer events.\n *\n * @param nth - Zero-based index of the form on the page. Defaults to `0`.\n */\nexport async function submitForm(page: Page, nth = 0): Promise<void> {\n await page.evaluate((n) => {\n const triggers = document.querySelectorAll<HTMLButtonElement>('[data-testid=\"submit-trigger\"]');\n triggers[n]?.click();\n }, nth);\n}\n\n/** Clicks the datepicker input and presses Tab to blur. */\nexport async function touchDatepicker(page: Page, testId: string): Promise<void> {\n await getMatFormField(page, testId).locator('input').click();\n await page.keyboard.press('Tab');\n}\n"]}
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import '@angular/compiler';
|
|
2
|
+
export * from '@playwright/test';
|
|
3
|
+
export * from './helpers/form.helpers';
|
|
4
|
+
export * from './field-tests/input.tests';
|
|
5
|
+
export * from './field-tests/textarea.tests';
|
|
6
|
+
export * from './field-tests/checkbox.tests';
|
|
7
|
+
export * from './field-tests/select.tests';
|
|
8
|
+
export * from './field-tests/slider.tests';
|
|
9
|
+
export * from './field-tests/datepicker.tests';
|
|
10
|
+
export * from './field-tests/autocomplete.tests';
|
|
11
|
+
export * from './field-tests/autocomplete-chiplist.tests';
|
|
12
|
+
export * from './field-tests/file-upload.tests';
|
|
13
|
+
export * from './field-tests/unit-conversion.tests';
|
|
14
|
+
export * from './field-tests/field-test-factory';
|
package/index.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// Load Angular's JIT compiler. Required because crispr-forms uses partial compilation
|
|
2
|
+
// (Ivy linker format) which needs the JIT compiler as a fallback in Node/Playwright environments.
|
|
3
|
+
import '@angular/compiler';
|
|
4
|
+
// Re-export Playwright so consumers only need one import.
|
|
5
|
+
export * from '@playwright/test';
|
|
6
|
+
export * from './helpers/form.helpers';
|
|
7
|
+
export * from './field-tests/input.tests';
|
|
8
|
+
export * from './field-tests/textarea.tests';
|
|
9
|
+
export * from './field-tests/checkbox.tests';
|
|
10
|
+
export * from './field-tests/select.tests';
|
|
11
|
+
export * from './field-tests/slider.tests';
|
|
12
|
+
export * from './field-tests/datepicker.tests';
|
|
13
|
+
export * from './field-tests/autocomplete.tests';
|
|
14
|
+
export * from './field-tests/autocomplete-chiplist.tests';
|
|
15
|
+
export * from './field-tests/file-upload.tests';
|
|
16
|
+
export * from './field-tests/unit-conversion.tests';
|
|
17
|
+
export * from './field-tests/field-test-factory';
|
|
18
|
+
//# sourceMappingURL=index.js.map
|
package/index.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../libs/crispr-forms-testing/src/index.ts"],"names":[],"mappings":"AAAA,sFAAsF;AACtF,kGAAkG;AAClG,OAAO,mBAAmB,CAAC;AAE3B,0DAA0D;AAC1D,cAAc,kBAAkB,CAAC;AAEjC,cAAc,wBAAwB,CAAC;AACvC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,gCAAgC,CAAC;AAC/C,cAAc,kCAAkC,CAAC;AACjD,cAAc,2CAA2C,CAAC;AAC1D,cAAc,iCAAiC,CAAC;AAChD,cAAc,qCAAqC,CAAC;AACpD,cAAc,kCAAkC,CAAC","sourcesContent":["// Load Angular's JIT compiler. Required because crispr-forms uses partial compilation\n// (Ivy linker format) which needs the JIT compiler as a fallback in Node/Playwright environments.\nimport '@angular/compiler';\n\n// Re-export Playwright so consumers only need one import.\nexport * from '@playwright/test';\n\nexport * from './helpers/form.helpers';\nexport * from './field-tests/input.tests';\nexport * from './field-tests/textarea.tests';\nexport * from './field-tests/checkbox.tests';\nexport * from './field-tests/select.tests';\nexport * from './field-tests/slider.tests';\nexport * from './field-tests/datepicker.tests';\nexport * from './field-tests/autocomplete.tests';\nexport * from './field-tests/autocomplete-chiplist.tests';\nexport * from './field-tests/file-upload.tests';\nexport * from './field-tests/unit-conversion.tests';\nexport * from './field-tests/field-test-factory';\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@tft/crispr-forms-testing",
|
|
3
|
+
"version": "21.0.1",
|
|
4
|
+
"main": "index.js",
|
|
5
|
+
"types": "index.d.ts",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": {
|
|
8
|
+
"types": "./index.d.ts",
|
|
9
|
+
"default": "./index.js"
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
"peerDependencies": {
|
|
13
|
+
"@angular/compiler": "^21.0.0",
|
|
14
|
+
"@angular/forms": "^21.0.0",
|
|
15
|
+
"@playwright/test": ">=1.40.0",
|
|
16
|
+
"@tft/crispr-forms": "*"
|
|
17
|
+
}
|
|
18
|
+
}
|