diff --git a/.gitattributes b/.gitattributes
index 9670e954e..ed8103553 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -1,9 +1,10 @@
-.gitattributes export-ignore
-.gitignore export-ignore
-.github export-ignore
-ncs.* export-ignore
-phpstan.neon export-ignore
-tests/ export-ignore
+.gitattributes export-ignore
+.github/ export-ignore
+.gitignore export-ignore
+CLAUDE.md export-ignore
+ncs.* export-ignore
+phpstan*.neon export-ignore
+tests/ export-ignore
-*.sh eol=lf
-*.php* diff=php linguist-language=PHP
+*.php* diff=php
+*.sh text eol=lf
diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml
index 9c579d8ed..3d934c93b 100644
--- a/.github/workflows/static-analysis.yml
+++ b/.github/workflows/static-analysis.yml
@@ -1,4 +1,4 @@
-name: Static Analysis (only informative)
+name: Static Analysis
on: [push, pull_request]
@@ -15,4 +15,3 @@ jobs:
- run: composer install --no-progress --prefer-dist
- run: composer phpstan
- continue-on-error: true # is only informative
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index 519de812f..e5b945e67 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -20,7 +20,7 @@ jobs:
coverage: none
- run: composer install --no-progress --prefer-dist
- - run: vendor/bin/tester tests -s -C
+ - run: composer tester
- if: failure()
uses: actions/upload-artifact@v4
with:
@@ -39,7 +39,7 @@ jobs:
coverage: none
- run: composer update --no-progress --prefer-dist --prefer-lowest --prefer-stable
- - run: vendor/bin/tester tests -s -C
+ - run: composer tester
code_coverage:
@@ -53,7 +53,7 @@ jobs:
coverage: none
- run: composer install --no-progress --prefer-dist
- - run: vendor/bin/tester -p phpdbg tests -s -C --coverage ./coverage.xml --coverage-src ./src
+ - run: composer tester -- -p phpdbg --coverage ./coverage.xml --coverage-src ./src
- run: wget https://github.com/php-coveralls/php-coveralls/releases/download/v2.4.3/php-coveralls.phar
- env:
COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/CLAUDE.md b/CLAUDE.md
new file mode 100644
index 000000000..325ac836c
--- /dev/null
+++ b/CLAUDE.md
@@ -0,0 +1,627 @@
+# CLAUDE.md
+
+This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
+
+## Project Overview
+
+Nette Forms is a mature PHP library (since 2004) for creating, validating, and processing web forms with both server-side (PHP) and client-side (JavaScript) validation. Part of the Nette Framework ecosystem.
+
+- **PHP Requirements:** 8.2 - 8.5
+- **Dependencies:** nette/component-model, nette/http, nette/utils
+- **Latte Integration:** Requires Latte 3.1+ (conflict with < 3.1 or >= 3.2)
+- **Current Branch:** v3.3-dev
+
+## Essential Commands
+
+### PHP Development
+
+```bash
+# Install dependencies
+composer install
+
+# Run all tests
+composer run tester
+# Or directly:
+vendor/bin/tester tests -s -C
+
+# Run tests in specific directory
+vendor/bin/tester tests/Forms/ -s -C
+
+# Run single test file
+php tests/Forms/Form.render.phpt
+
+# Static analysis
+composer run phpstan
+```
+
+### JavaScript Development
+
+```bash
+# Install dependencies
+npm install
+
+# Build JavaScript assets (UMD + minified + types)
+npm run build
+
+# Run JavaScript tests (Vitest)
+npm run test
+npm run test:watch # Watch mode
+npm run test:ui # UI mode
+
+# Type checking
+npm run typecheck
+
+# Linting
+npm run lint
+npm run lint:fix
+```
+
+**Build Output:** `src/assets/netteForms.js`, `netteForms.min.js`, `netteForms.d.ts`
+
+## Architecture Overview
+
+### Core PHP Structure
+
+**Class Hierarchy:**
+- `Form` (extends `Container`) - Main entry point for form creation
+- `Container` - Holds controls and nested containers
+- `Control` (interface) - Contract for all form controls
+- `BaseControl` (abstract) - Base implementation for controls
+
+**Form Controls** (19 types in `src/Forms/Controls/`):
+- Text inputs: `TextInput`, `TextArea`, `EmailControl`, `PasswordInput`
+- Choice controls: `SelectBox`, `RadioList`, `CheckboxList`, `MultiSelectBox`
+- Special: `Button`, `SubmitButton`, `ImageButton`, `Checkbox`, `HiddenField`, `ColorPicker`, `DateTimeControl`, `UploadControl`
+
+**Validation System:**
+- `Rules` - Manages validation rules per control
+- `Rule` - Value object for single validation rule
+- `Validator` - Built-in validators (email, URL, range, file size, etc.)
+- Supports conditional rules and custom validators
+
+**Rendering:**
+- `FormRenderer` (interface) - Rendering contract
+- `DefaultFormRenderer` - Default HTML output
+- Multiple strategies supported (Bootstrap 4/5, custom)
+
+### Bridge Integrations
+
+**`Bridges/FormsDI/`** - Nette DI container extension
+- `FormsExtension` - DI integration for forms
+
+**`Bridges/FormsLatte/`** - Latte 3.1+ templating integration
+- `FormsExtension` - Adds Latte tags: `{form}`, `{input}`, `{label}`, `{inputError}`, `{formContainer}`, `{formPrint}`
+- `Runtime` - Non-static runtime class (recently refactored from static)
+- `Nodes/` - Latte compiler nodes for template processing
+
+### JavaScript Architecture
+
+**Source:** `src/assets/` (TypeScript)
+- `formValidator.ts` - Main validation orchestrator
+- `validators.ts` - Collection of validation functions
+- `types.ts` - TypeScript type definitions
+- `webalize.ts` - String utilities
+
+**Build System:** Rollup with custom transformations
+- Converts spaces to tabs (project standard)
+- Adds header comment
+- Generates UMD module with auto-init on load
+- Produces TypeScript definitions
+
+**Build Configuration:**
+- `rollup.config.js` - UMD build + TypeScript definitions
+- Custom plugins: `fix()` adds header and auto-init, `spaces2tabs()` enforces indentation
+
+## Testing Strategy
+
+### PHP Tests (Nette Tester)
+
+- **Location:** `tests/` directory
+- **Format:** `.phpt` files with `test()` or `testException()` functions
+- **Bootstrap:** `tests/bootstrap.php` sets up environment
+- **Coverage:** ~100 test files covering all components
+
+**Test Organization:**
+- `tests/Forms/` - Core form tests (Controls, validation, rendering)
+- `tests/Forms.DI/` - DI integration tests
+- `tests/Forms.Latte/` - Latte template integration tests
+
+**Common Test Patterns:**
+```php
+test('description of what is tested', function () {
+ // test code
+ Assert::same($expected, $actual);
+});
+
+testException('description', function () {
+ // code that should throw
+}, ExceptionClass::class, 'message pattern %a%');
+```
+
+### JavaScript Tests (Vitest)
+
+- **Location:** `tests/netteForms/`
+- **Files:** `Nette.validateRule.spec.js`, `Nette.validators.spec.js`
+- **Setup:** `tests/netteForms/setup.js`
+- **Environment:** jsdom for DOM testing
+
+## Code Standards
+
+### PHP Conventions
+
+- Every file must have `declare(strict_types=1)`
+- Use TABS for indentation (not spaces)
+- All properties, parameters, and return values must have types
+- Single quotes for strings (unless containing apostrophes)
+- PascalCase for classes, camelCase for methods/properties
+- No abbreviations unless full name is too long
+
+### Recent Breaking Changes (v3.3)
+
+- Latte Runtime refactored from static to non-static class
+- Removed Latte 2 support (requires Latte 3.1+)
+- Removed deprecated functionality
+- Removed old class name compatibility
+
+## Key Configuration Files
+
+- `composer.json` - PHP dependencies, scripts
+- `package.json` - JavaScript dependencies, build scripts
+- `phpstan.neon` - Static analysis (level 5, Nette extension)
+- `eslint.config.js` - TypeScript linting with @nette/eslint-plugin
+- `rollup.config.js` - JavaScript build configuration
+- `vitest.config.ts` - JavaScript test runner
+- `tests/bootstrap.php` - Test environment setup
+
+## Development Workflow
+
+1. **PHP Changes:**
+ - Modify source in `src/Forms/` or `src/Bridges/`
+ - Run tests: `vendor/bin/tester tests -s`
+ - Run PHPStan: `composer run phpstan`
+
+2. **JavaScript Changes:**
+ - Modify source in `src/assets/*.ts`
+ - Build: `npm run build` (auto-runs tests after build)
+ - Lint: `npm run lint:fix`
+
+3. **Adding New Form Control:**
+ - Create class in `src/Forms/Controls/`
+ - Extend `BaseControl` or implement `Control` interface
+ - Add validation support in `Validator.php` if needed
+ - Add client-side validation in `src/assets/validators.ts`
+ - Add tests in `tests/Forms/Controls.{ControlName}.*.phpt`
+
+4. **Latte Integration Changes:**
+ - Modify `src/Bridges/FormsLatte/`
+ - Update Runtime or add/modify Nodes
+ - Test in `tests/Forms.Latte/`
+
+## Latte Template Integration
+
+Nette Forms provides deep integration with Latte templating engine through custom tags and attributes.
+
+### Core Latte Tags
+
+**`{form}` and `{control}`:**
+```latte
+{* Simple rendering - outputs entire form *}
+{control signInForm}
+
+{* Manual form structure with {form} tag *}
+{form signInForm}
+ {* form content *}
+{/form}
+```
+
+**`n:name` attribute** - Links PHP form definition with HTML:
+```latte
+
+```
+
+**`{input}` and `{label}` tags** - Universal rendering:
+```latte
+{label username}Username: {input username, size: 20, autofocus: true}{/label}
+{inputError username}
+```
+
+**`{inputError}`** - Displays validation errors:
+```latte
+{inputError $input}
+```
+
+**`{formContainer}`** - Renders nested containers:
+```latte
+{formContainer emailNews}
+
+ - {input sport} {label sport /}
+ - {input science} {label science /}
+
+{/formContainer}
+```
+
+### Rendering Patterns
+
+**Automatic rendering** - Generic template for any form:
+```latte
+
+```
+
+**RadioList/CheckboxList item-by-item:**
+```latte
+{foreach $form[gender]->getItems() as $key => $label}
+
+{/foreach}
+```
+
+## Validation System
+
+### Built-in Validation Rules
+
+All rules are constants of `Nette\Forms\Form` class:
+
+**Universal rules:**
+- `Required` / `Filled` - required control
+- `Blank` - control must be empty
+- `Equal` / `NotEqual` - value comparison
+- `IsIn` / `IsNotIn` - value in/not in array
+- `Valid` - control filled correctly (for conditions)
+
+**Text input rules:**
+- `MinLength` / `MaxLength` / `Length` - text length validation
+- `Email` - valid email address
+- `URL` - absolute URL (auto-completes scheme)
+- `Pattern` / `PatternInsensitive` - regex matching
+- `Integer` / `Numeric` / `Float` - numeric validation
+- `Min` / `Max` / `Range` - numeric range
+
+**File upload rules:**
+- `MaxFileSize` - maximum file size in bytes
+- `MimeType` - MIME type validation (wildcards: `'video/*'`)
+- `Image` - JPEG, PNG, GIF, WebP, AVIF validation
+
+**Multiple items rules (CheckboxList, MultiSelect, MultiUpload):**
+- `MinLength` / `MaxLength` / `Length` - count validation
+
+### Error Message Placeholders
+
+```php
+$form->addInteger('id')
+ ->addRule($form::Range, 'at least %d and at most %d', [5, 10]);
+ // %d - replaced by arguments
+ // %n$d - replaced by n-th argument
+ // %label - control label
+ // %name - control name
+ // %value - user input
+```
+
+### Custom Validators
+
+**PHP side:**
+```php
+class MyValidators
+{
+ public static function validateDivisibility(BaseControl $input, $arg): bool
+ {
+ return $input->getValue() % $arg === 0;
+ }
+}
+
+$form->addInteger('num')
+ ->addRule([MyValidators::class, 'validateDivisibility'],
+ 'Value must be multiple of %d', 8);
+```
+
+**JavaScript side** - Add to `Nette.validators`:
+```js
+Nette.validators['AppMyValidators_validateDivisibility'] = (elem, args, val) => {
+ return val % args === 0;
+};
+```
+
+### Validation Conditions
+
+**Conditional validation:**
+```php
+$form->addPassword('password')
+ ->addCondition($form::MaxLength, 8)
+ ->addRule($form::Pattern, 'Must contain digit', '.*[0-9].*');
+```
+
+**Conditional on another control:**
+```php
+$form->addCheckbox('newsletters');
+$form->addEmail('email')
+ ->addConditionOn($form['newsletters'], $form::Equal, true)
+ ->setRequired('Enter email');
+```
+
+**Complex structures:**
+```php
+$form->addText('field')
+ ->addCondition(/* ... */)
+ ->addConditionOn(/* ... */)
+ ->addRule(/* ... */)
+ ->elseCondition()
+ ->addRule(/* ... */)
+ ->endCondition()
+ ->addRule(/* ... */);
+```
+
+### Dynamic JavaScript (Toggle)
+
+Show/hide elements based on conditions:
+```php
+$form->addCheckbox('send_it')
+ ->addCondition($form::Equal, true)
+ ->toggle('#address-container'); // Shows element when checked
+```
+
+Custom toggle behavior:
+```js
+Nette.toggle = (selector, visible, srcElement, event) => {
+ document.querySelectorAll(selector).forEach((el) => {
+ // Custom show/hide logic with animations
+ });
+};
+```
+
+## Form Configuration (NEON)
+
+Customize default error messages:
+```neon
+forms:
+ messages:
+ Equal: 'Please enter %s.'
+ Filled: 'This field is required.'
+ MinLength: 'Please enter at least %d characters.'
+ Email: 'Please enter a valid email address.'
+ # ... other messages
+```
+
+Standalone usage (without framework):
+```php
+Nette\Forms\Validator::$messages['Equal'] = 'Custom message';
+```
+
+## Common Patterns
+
+### Data Mapping to Classes
+
+**Basic mapping:**
+```php
+class RegistrationFormData
+{
+ public string $name;
+ public int $age;
+ public string $password;
+}
+
+$data = $form->getValues(RegistrationFormData::class);
+// Returns typed object instead of ArrayHash
+```
+
+**Nested containers:**
+```php
+class PersonFormData
+{
+ public string $firstName;
+ public string $lastName;
+}
+
+class RegistrationFormData
+{
+ public PersonFormData $person;
+ public int $age;
+}
+
+$person = $form->addContainer('person');
+$person->addText('firstName');
+$person->addText('lastName');
+
+$data = $form->getValues(RegistrationFormData::class);
+```
+
+**Generate data class:**
+```php
+// Outputs class definition to browser
+Nette\Forms\Blueprint::dataClass($form);
+```
+
+### Multiple Submit Buttons
+
+```php
+$form->addSubmit('save', 'Save');
+$form->addSubmit('delete', 'Delete');
+
+if ($form->isSuccess()) {
+ if ($form['save']->isSubmittedBy()) {
+ // Save logic
+ }
+ if ($form['delete']->isSubmittedBy()) {
+ // Delete logic
+ }
+}
+```
+
+**Partial validation:**
+```php
+$form->addSubmit('preview')
+ ->setValidationScope([]); // No validation
+
+$form->addSubmit('save')
+ ->setValidationScope([$form['name']]); // Only name field
+```
+
+### Containers for Grouped Controls
+
+```php
+$form->addContainer('personal')
+ ->addText('name')
+ ->addInteger('age');
+
+$form->addContainer('address')
+ ->addText('street')
+ ->addText('city');
+
+// Returns nested structure:
+// ['personal' => ['name' => ..., 'age' => ...], 'address' => [...]]
+```
+
+### Control Value Filtering
+
+```php
+$form->addText('zip')
+ ->addFilter(fn($value) => str_replace(' ', '', $value))
+ ->addRule($form::Pattern, 'Must be 5 digits', '\d{5}');
+```
+
+### Omitted Values
+
+Exclude values from `getValues()` result:
+```php
+$form->addPassword('passwordVerify')
+ ->addRule($form::Equal, 'Passwords do not match', $form['password'])
+ ->setOmitted(); // Not included in getValues()
+```
+
+## Security
+
+### CSRF Protection
+
+**Sec-Fetch/Origin header protection** (enabled by default):
+```php
+// Create form before sending output to set _nss cookie
+$form = new Form;
+```
+
+**Cross-origin forms** (use carefully):
+```php
+$form->allowCrossOrigin(); // Disables CSRF protection!
+```
+
+### Automatic Security Features
+
+- UTF-8 validation on all inputs
+- Control character filtering
+- Line break removal in single-line inputs
+- Line break normalization in multi-line inputs
+- Select/radio/checkbox forgery prevention
+- Automatic whitespace trimming
+
+### Safe Hidden Fields
+
+```php
+$form->addHidden('userId');
+// WARNING: Hidden field values can be spoofed!
+// Always validate on server side
+```
+
+## JavaScript Integration
+
+### Loading netteForms.js
+
+**Via CDN:**
+```latte
+
+```
+
+**Via npm:**
+```bash
+npm install nette-forms
+```
+```js
+import netteForms from 'nette-forms';
+netteForms.initOnLoad();
+```
+
+**Local copy:**
+```latte
+
+```
+
+### Validation Transfer
+
+Validation rules and conditions are automatically transferred to JavaScript via `data-nette-rules` HTML attributes. The script intercepts form submit and performs client-side validation.
+
+### Disable Auto-init
+
+```html
+
+
+```
+
+## Rendering Customization
+
+### DefaultFormRenderer Configuration
+
+Change wrapper elements via `$wrappers` array:
+```php
+$renderer = $form->getRenderer();
+$renderer->wrappers['controls']['container'] = 'dl';
+$renderer->wrappers['pair']['container'] = null;
+$renderer->wrappers['label']['container'] = 'dt';
+$renderer->wrappers['control']['container'] = 'dd';
+```
+
+### Control Groups (Fieldsets)
+
+```php
+$form->addGroup('Personal data');
+$form->addText('name');
+$form->addInteger('age');
+
+$form->addGroup('Shipping address');
+$form->addText('street');
+$form->addText('city');
+```
+
+### HTML Attributes
+
+**Per-item attributes (RadioList, CheckboxList):**
+```php
+$form->addCheckboxList('colors', 'Colors:', ['r' => 'red', 'g' => 'green'])
+ ->setHtmlAttribute('style:', ['r' => 'background:red', 'g' => 'background:green']);
+ // Colon after 'style:' selects value by key
+```
+
+**Boolean attributes:**
+```php
+$form->addCheckboxList('colors', 'Colors:', $colors)
+ ->setHtmlAttribute('readonly?', 'r'); // Only 'r' gets readonly
+```
+
+**Select option attributes:**
+```php
+$form->addSelect('colors', 'Colors:', $colors)
+ ->setOptionAttribute('style:', $styles);
+```
+
+### Control Prototypes
+
+Modify HTML templates directly:
+```php
+$input = $form->addInteger('number');
+$input->getControlPrototype()->class('big-number');
+$input->getLabelPrototype()->class('distinctive');
+
+// Container wrapper (Checkbox, CheckboxList, RadioList)
+$input->getContainerPrototype()->setName('div')->class('check');
+```
diff --git a/composer.json b/composer.json
index 6a343bb3a..9331c3462 100644
--- a/composer.json
+++ b/composer.json
@@ -18,15 +18,17 @@
"php": "8.1 - 8.5",
"nette/component-model": "^3.1",
"nette/http": "^3.3",
- "nette/utils": "^4.0.4"
+ "nette/utils": "^4.0.10"
},
"require-dev": {
"nette/application": "^3.0",
"nette/di": "^3.0",
- "nette/tester": "^2.5.2",
+ "nette/tester": "^2.6",
"latte/latte": "^2.10.2 || ^3.0.12",
"tracy/tracy": "^2.9",
- "phpstan/phpstan-nette": "^2.0@stable"
+ "phpstan/phpstan": "^2.1@stable",
+ "phpstan/extension-installer": "^1.4@stable",
+ "nette/phpstan-rules": "^1.0"
},
"conflict": {
"latte/latte": ">=3.0.0 <3.0.12 || >=3.2"
@@ -49,5 +51,10 @@
"branch-alias": {
"dev-master": "3.2-dev"
}
+ },
+ "config": {
+ "allow-plugins": {
+ "phpstan/extension-installer": true
+ }
}
}
diff --git a/package.json b/package.json
index 01018a6de..c73dbdafe 100644
--- a/package.json
+++ b/package.json
@@ -6,24 +6,24 @@
"@rollup/plugin-node-resolve": "^16.0.1",
"@rollup/plugin-terser": "^0.4.4",
"@rollup/plugin-typescript": "^12.1.2",
+ "@vitest/ui": "^4.0.16",
"eslint": "^9.26.0",
"globals": "^15.3.0",
- "jasmine": "^5.7.1",
- "jasmine-core": "^5.7.1",
- "karma": "^6.4.4",
- "karma-chrome-launcher": "^3.2.0",
- "karma-jasmine": "^5.1.0",
+ "jsdom": "^27.3.0",
"rollup": "^4.40.2",
"rollup-plugin-dts": "^6.2.1",
"terser": "^5.39.1",
"typescript": "^5.8.3",
- "typescript-eslint": "^8.32.1"
+ "typescript-eslint": "^8.32.1",
+ "vitest": "^4.0.16"
},
"scripts": {
"typecheck": "tsc -noemit",
"lint": "eslint --cache",
"lint:fix": "eslint --cache --fix",
- "test": "karma start tests/netteForms/karma.conf.ts",
+ "test": "vitest run",
+ "test:watch": "vitest",
+ "test:ui": "vitest --ui",
"build": "rollup -c",
"postbuild": "npm run test"
}
diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon
new file mode 100644
index 000000000..e5e94868a
--- /dev/null
+++ b/phpstan-baseline.neon
@@ -0,0 +1,343 @@
+parameters:
+ ignoreErrors:
+ -
+ message: '#^Using nullsafe property access on non\-nullable type Latte\\Compiler\\Nodes\\FragmentNode\. Use \-\> instead\.$#'
+ identifier: nullsafe.neverNull
+ count: 1
+ path: src/Bridges/FormsLatte/Nodes/FieldNNameNode.php
+
+ -
+ message: '#^Call to an undefined method Nette\\Forms\\Control\:\:getControl\(\)\.$#'
+ identifier: method.notFound
+ count: 1
+ path: src/Bridges/FormsLatte/Runtime.php
+
+ -
+ message: '#^Call to an undefined method Nette\\Forms\\Control\:\:getOption\(\)\.$#'
+ identifier: method.notFound
+ count: 2
+ path: src/Bridges/FormsLatte/Runtime.php
+
+ -
+ message: '#^Call to an undefined method Nette\\Forms\\Control\:\:setOption\(\)\.$#'
+ identifier: method.notFound
+ count: 1
+ path: src/Bridges/FormsLatte/Runtime.php
+
+ -
+ message: '#^Call to an undefined method Nette\\Forms\\Control\:\:lookupPath\(\)\.$#'
+ identifier: method.notFound
+ count: 1
+ path: src/Forms/Blueprint.php
+
+ -
+ message: '#^Method Nette\\Forms\\Controls\\BaseControl@anonymous/Forms/Blueprint\.php\:97\:\:getControl\(\) never returns Nette\\Utils\\Html so it can be removed from the return type\.$#'
+ identifier: return.unusedType
+ count: 1
+ path: src/Forms/Blueprint.php
+
+ -
+ message: '#^Method Nette\\Forms\\Controls\\BaseControl@anonymous/Forms/Blueprint\.php\:97\:\:getLabel\(\) never returns Nette\\Utils\\Html so it can be removed from the return type\.$#'
+ identifier: return.unusedType
+ count: 1
+ path: src/Forms/Blueprint.php
+
+ -
+ message: '#^Method Nette\\Forms\\Form@anonymous/Forms/Blueprint\.php\:89\:\:receiveHttpData\(\) never returns null so it can be removed from the return type\.$#'
+ identifier: return.unusedType
+ count: 1
+ path: src/Forms/Blueprint.php
+
+ -
+ message: '#^Property Nette\\Forms\\Controls\\BaseControl@anonymous/Forms/Blueprint\.php\:97\:\:\$inner has no type specified\.$#'
+ identifier: missingType.property
+ count: 1
+ path: src/Forms/Blueprint.php
+
+ -
+ message: '#^Call to an undefined method Nette\\ComponentModel\\IComponent&Nette\\Forms\\Control\:\:isDisabled\(\)\.$#'
+ identifier: method.notFound
+ count: 1
+ path: src/Forms/Container.php
+
+ -
+ message: '#^Method Nette\\Forms\\Container\:\:getUnsafeValues\(\) has no return type specified\.$#'
+ identifier: missingType.return
+ count: 1
+ path: src/Forms/Container.php
+
+ -
+ message: '#^Method Nette\\Forms\\Container\:\:getUnsafeValues\(\) has parameter \$controls with no value type specified in iterable type array\.$#'
+ identifier: missingType.iterableValue
+ count: 1
+ path: src/Forms/Container.php
+
+ -
+ message: '#^Method Nette\\Forms\\Container\:\:getUnsafeValues\(\) has parameter \$returnType with no type specified\.$#'
+ identifier: missingType.parameter
+ count: 1
+ path: src/Forms/Container.php
+
+ -
+ message: '#^Parameter \#1 \.\.\.\$items of method Nette\\Forms\\ControlGroup\:\:add\(\) expects iterable\\|Nette\\Forms\\Container\|Nette\\Forms\\Control, Nette\\ComponentModel\\IComponent given\.$#'
+ identifier: argument.type
+ count: 1
+ path: src/Forms/Container.php
+
+ -
+ message: '#^Unable to resolve the template type T in call to method Nette\\Forms\\Container\:\:getUntrustedValues\(\)$#'
+ identifier: argument.templateType
+ count: 1
+ path: src/Forms/Container.php
+
+ -
+ message: '#^Call to an undefined method Nette\\Forms\\Control\:\:getForm\(\)\.$#'
+ identifier: method.notFound
+ count: 1
+ path: src/Forms/ControlGroup.php
+
+ -
+ message: '#^Parameter \#1 \.\.\.\$items of method Nette\\Forms\\ControlGroup\:\:add\(\) expects iterable\\|Nette\\Forms\\Container\|Nette\\Forms\\Control, Nette\\ComponentModel\\IComponent given\.$#'
+ identifier: argument.type
+ count: 1
+ path: src/Forms/ControlGroup.php
+
+ -
+ message: '#^Method Nette\\Forms\\Controls\\BaseControl\:\:getHttpData\(\) has parameter \$type with no type specified\.$#'
+ identifier: missingType.parameter
+ count: 1
+ path: src/Forms/Controls/BaseControl.php
+
+ -
+ message: '#^Method Nette\\Forms\\Controls\\BaseControl\:\:getOption\(\) has parameter \$key with no type specified\.$#'
+ identifier: missingType.parameter
+ count: 1
+ path: src/Forms/Controls/BaseControl.php
+
+ -
+ message: '#^Method Nette\\Forms\\Controls\\BaseControl\:\:setOption\(\) has parameter \$key with no type specified\.$#'
+ identifier: missingType.parameter
+ count: 1
+ path: src/Forms/Controls/BaseControl.php
+
+ -
+ message: '#^Method Nette\\Forms\\Controls\\Button\:\:getLabel\(\) has parameter \$caption with no type specified\.$#'
+ identifier: missingType.parameter
+ count: 1
+ path: src/Forms/Controls/Button.php
+
+ -
+ message: '#^Method Nette\\Forms\\Controls\\Checkbox\:\:getLabel\(\) has parameter \$caption with no type specified\.$#'
+ identifier: missingType.parameter
+ count: 1
+ path: src/Forms/Controls/Checkbox.php
+
+ -
+ message: '#^Method Nette\\Forms\\Controls\\CheckboxList\:\:getControlPart\(\) has parameter \$key with no type specified\.$#'
+ identifier: missingType.parameter
+ count: 1
+ path: src/Forms/Controls/CheckboxList.php
+
+ -
+ message: '#^Method Nette\\Forms\\Controls\\CheckboxList\:\:getLabel\(\) has parameter \$caption with no type specified\.$#'
+ identifier: missingType.parameter
+ count: 1
+ path: src/Forms/Controls/CheckboxList.php
+
+ -
+ message: '#^Method Nette\\Forms\\Controls\\CheckboxList\:\:getLabelPart\(\) has parameter \$key with no type specified\.$#'
+ identifier: missingType.parameter
+ count: 1
+ path: src/Forms/Controls/CheckboxList.php
+
+ -
+ message: '#^Call to function is_string\(\) with string will always evaluate to true\.$#'
+ identifier: function.alreadyNarrowedType
+ count: 1
+ path: src/Forms/Controls/ColorPicker.php
+
+ -
+ message: '#^Match expression does not handle remaining values\: int\\|int\<4, max\>$#'
+ identifier: match.unhandled
+ count: 4
+ path: src/Forms/Controls/DateTimeControl.php
+
+ -
+ message: '#^Method Nette\\Forms\\Controls\\HiddenField\:\:getLabel\(\) has parameter \$caption with no type specified\.$#'
+ identifier: missingType.parameter
+ count: 1
+ path: src/Forms/Controls/HiddenField.php
+
+ -
+ message: '#^Method Nette\\Forms\\Controls\\RadioList\:\:getControlPart\(\) has parameter \$key with no type specified\.$#'
+ identifier: missingType.parameter
+ count: 1
+ path: src/Forms/Controls/RadioList.php
+
+ -
+ message: '#^Method Nette\\Forms\\Controls\\RadioList\:\:getLabel\(\) has parameter \$caption with no type specified\.$#'
+ identifier: missingType.parameter
+ count: 1
+ path: src/Forms/Controls/RadioList.php
+
+ -
+ message: '#^Method Nette\\Forms\\Controls\\RadioList\:\:getLabelPart\(\) has parameter \$key with no type specified\.$#'
+ identifier: missingType.parameter
+ count: 1
+ path: src/Forms/Controls/RadioList.php
+
+ -
+ message: '#^Method Nette\\Forms\\Controls\\SubmitButton\:\:getControl\(\) has parameter \$caption with no type specified\.$#'
+ identifier: missingType.parameter
+ count: 1
+ path: src/Forms/Controls/SubmitButton.php
+
+ -
+ message: '#^Call to an undefined method Nette\\ComponentModel\\IComponent\:\:getValue\(\)\.$#'
+ identifier: method.notFound
+ count: 1
+ path: src/Forms/Form.php
+
+ -
+ message: '#^Call to an undefined method Nette\\Forms\\Control\:\:getParent\(\)\.$#'
+ identifier: method.notFound
+ count: 1
+ path: src/Forms/Form.php
+
+ -
+ message: '#^Method Nette\\Forms\\Form\:\:beforeRender\(\) has no return type specified\.$#'
+ identifier: missingType.return
+ count: 1
+ path: src/Forms/Form.php
+
+ -
+ message: '#^Method Nette\\Forms\\Form\:\:invokeHandlers\(\) has parameter \$handlers with no signature specified for callable\.$#'
+ identifier: missingType.callable
+ count: 1
+ path: src/Forms/Form.php
+
+ -
+ message: '#^Call to an undefined method Nette\\Forms\\Control\:\:getHtmlName\(\)\.$#'
+ identifier: method.notFound
+ count: 2
+ path: src/Forms/Helpers.php
+
+ -
+ message: '#^Method Nette\\Forms\\Helpers\:\:sanitize\(\) never returns array\ so it can be removed from the return type\.$#'
+ identifier: return.unusedType
+ count: 1
+ path: src/Forms/Helpers.php
+
+ -
+ message: '#^Parameter \#1 \$callback of function array_map expects \(callable\(Nette\\Utils\\ImageType\)\: mixed\)\|null, Closure\(1\|2\|3\|6\|18\|19\)\: string given\.$#'
+ identifier: argument.type
+ count: 1
+ path: src/Forms/Helpers.php
+
+ -
+ message: '#^Strict comparison using \=\=\= between '''' and '''' will always evaluate to true\.$#'
+ identifier: identical.alwaysTrue
+ count: 1
+ path: src/Forms/Helpers.php
+
+ -
+ message: '#^Call to an undefined method Nette\\Forms\\Control\:\:getControl\(\)\.$#'
+ identifier: method.notFound
+ count: 2
+ path: src/Forms/Rendering/DefaultFormRenderer.php
+
+ -
+ message: '#^Call to an undefined method Nette\\Forms\\Control\:\:getForm\(\)\.$#'
+ identifier: method.notFound
+ count: 2
+ path: src/Forms/Rendering/DefaultFormRenderer.php
+
+ -
+ message: '#^Call to an undefined method Nette\\Forms\\Control\:\:getLabel\(\)\.$#'
+ identifier: method.notFound
+ count: 1
+ path: src/Forms/Rendering/DefaultFormRenderer.php
+
+ -
+ message: '#^Call to an undefined method Nette\\Forms\\Control\:\:getOption\(\)\.$#'
+ identifier: method.notFound
+ count: 12
+ path: src/Forms/Rendering/DefaultFormRenderer.php
+
+ -
+ message: '#^Call to an undefined method Nette\\Forms\\Control\:\:hasErrors\(\)\.$#'
+ identifier: method.notFound
+ count: 3
+ path: src/Forms/Rendering/DefaultFormRenderer.php
+
+ -
+ message: '#^Call to an undefined method Nette\\Forms\\Control\:\:isRequired\(\)\.$#'
+ identifier: method.notFound
+ count: 4
+ path: src/Forms/Rendering/DefaultFormRenderer.php
+
+ -
+ message: '#^Call to an undefined method Nette\\Forms\\Control\:\:setOption\(\)\.$#'
+ identifier: method.notFound
+ count: 3
+ path: src/Forms/Rendering/DefaultFormRenderer.php
+
+ -
+ message: '#^Instanceof between Nette\\Forms\\Control and Nette\\Forms\\Control will always evaluate to true\.$#'
+ identifier: instanceof.alwaysTrue
+ count: 1
+ path: src/Forms/Rendering/DefaultFormRenderer.php
+
+ -
+ message: '#^Variable \$control might not be defined\.$#'
+ identifier: variable.undefined
+ count: 1
+ path: src/Forms/Rendering/DefaultFormRenderer.php
+
+ -
+ message: '#^Access to an undefined property Nette\\Forms\\Control\:\:\$name\.$#'
+ identifier: property.notFound
+ count: 1
+ path: src/Forms/Rules.php
+
+ -
+ message: '#^Call to an undefined method Nette\\Forms\\Control\:\:addError\(\)\.$#'
+ identifier: method.notFound
+ count: 1
+ path: src/Forms/Rules.php
+
+ -
+ message: '#^Call to an undefined method Nette\\Forms\\Control\:\:isFilled\(\)\.$#'
+ identifier: method.notFound
+ count: 2
+ path: src/Forms/Rules.php
+
+ -
+ message: '#^Method Nette\\Forms\\Rules\:\:getCallback\(\) return type has no signature specified for callable\.$#'
+ identifier: missingType.callable
+ count: 1
+ path: src/Forms/Rules.php
+
+ -
+ message: '#^Method Nette\\Forms\\Rules\:\:getCallback\(\) return type has no value type specified in iterable type array\.$#'
+ identifier: missingType.iterableValue
+ count: 1
+ path: src/Forms/Rules.php
+
+ -
+ message: '#^Call to an undefined method Nette\\Forms\\Control\:\:getForm\(\)\.$#'
+ identifier: method.notFound
+ count: 1
+ path: src/Forms/Validator.php
+
+ -
+ message: '#^Call to an undefined method Nette\\Forms\\Control\:\:getName\(\)\.$#'
+ identifier: method.notFound
+ count: 2
+ path: src/Forms/Validator.php
+
+ -
+ message: '#^Call to an undefined method Nette\\HtmlStringable\:\:getText\(\)\.$#'
+ identifier: method.notFound
+ count: 1
+ path: src/Forms/Validator.php
diff --git a/phpstan.neon b/phpstan.neon
index fcfc2a627..c9bea16b4 100644
--- a/phpstan.neon
+++ b/phpstan.neon
@@ -1,11 +1,17 @@
parameters:
- level: 5
+ level: 6
paths:
- src
- treatPhpDocTypesAsCertain: false
+ excludePaths:
+ - src/Bridges/FormsLatte/FormMacros.php
+ - src/compatibility.php
+ ignoreErrors:
+ - # Latte nodes use new static() by design for extensibility
+ identifier: new.static
+ path: src/Bridges/FormsLatte/Nodes/*
includes:
- - vendor/phpstan/phpstan-nette/extension.neon
+ - phpstan-baseline.neon
diff --git a/src/Bridges/FormsDI/FormsExtension.php b/src/Bridges/FormsDI/FormsExtension.php
index 49a66883c..753d7528e 100644
--- a/src/Bridges/FormsDI/FormsExtension.php
+++ b/src/Bridges/FormsDI/FormsExtension.php
@@ -10,7 +10,7 @@
namespace Nette\Bridges\FormsDI;
use Nette;
-use function defined;
+use function defined, is_object;
/**
@@ -31,6 +31,7 @@ public function afterCompile(Nette\PhpGenerator\ClassType $class): void
{
$initialize = $this->initialization ?? $class->getMethod('initialize');
+ assert(is_object($this->config));
foreach ($this->config->messages as $name => $text) {
if (defined('Nette\Forms\Form::' . $name)) {
$initialize->addBody('Nette\Forms\Validator::$messages[Nette\Forms\Form::?] = ?;', [$name, $text]);
diff --git a/src/Bridges/FormsLatte/Nodes/FieldNNameNode.php b/src/Bridges/FormsLatte/Nodes/FieldNNameNode.php
index fb18630ee..b8fa68ae0 100644
--- a/src/Bridges/FormsLatte/Nodes/FieldNNameNode.php
+++ b/src/Bridges/FormsLatte/Nodes/FieldNNameNode.php
@@ -98,7 +98,10 @@ private function init(Tag $tag): void
}
- /** @internal */
+ /**
+ * @internal
+ * @return string[]
+ */
public static function findUsedAttributes(ElementNode $el): array
{
$res = [];
diff --git a/src/Bridges/FormsLatte/Nodes/FormContainerNode.php b/src/Bridges/FormsLatte/Nodes/FormContainerNode.php
index f6bac29ab..205a0db2b 100644
--- a/src/Bridges/FormsLatte/Nodes/FormContainerNode.php
+++ b/src/Bridges/FormsLatte/Nodes/FormContainerNode.php
@@ -17,7 +17,8 @@
/**
- * {formContainer ...}
+ * {formContainer name} ... {/formContainer}
+ * Enters form container context for nested controls.
*/
class FormContainerNode extends StatementNode
{
@@ -25,7 +26,7 @@ class FormContainerNode extends StatementNode
public AreaNode $content;
- /** @return \Generator */
+ /** @return \Generator, array{AreaNode, ?Tag}, static|AreaNode> */
public static function create(Tag $tag): \Generator
{
$tag->outputMode = $tag::OutputRemoveIndentation;
diff --git a/src/Bridges/FormsLatte/Nodes/FormNode.php b/src/Bridges/FormsLatte/Nodes/FormNode.php
index b4ee002e6..8abddeca2 100644
--- a/src/Bridges/FormsLatte/Nodes/FormNode.php
+++ b/src/Bridges/FormsLatte/Nodes/FormNode.php
@@ -21,8 +21,9 @@
/**
- * {form name} ... {/form}
- * {formContext ...}
+ * {form name [, attributes]} ... {/form}
+ * {formContext name} ... {/formContext}
+ * Renders form tags and initializes form context.
*/
class FormNode extends StatementNode
{
@@ -33,7 +34,7 @@ class FormNode extends StatementNode
public ?Position $endLine;
- /** @return \Generator */
+ /** @return \Generator, array{AreaNode, ?Tag}, static|AreaNode> */
public static function create(Tag $tag): \Generator
{
if ($tag->isNAttribute()) {
diff --git a/src/Bridges/FormsLatte/Nodes/FormPrintNode.php b/src/Bridges/FormsLatte/Nodes/FormPrintNode.php
index 58ff946a7..78bb4987d 100644
--- a/src/Bridges/FormsLatte/Nodes/FormPrintNode.php
+++ b/src/Bridges/FormsLatte/Nodes/FormPrintNode.php
@@ -16,8 +16,9 @@
/**
- * {formPrint [ClassName]}
- * {formClassPrint [ClassName]}
+ * {formPrint [name]}
+ * {formClassPrint [name]}
+ * Generates Latte template or data class blueprint for form.
*/
class FormPrintNode extends StatementNode
{
diff --git a/src/Bridges/FormsLatte/Nodes/InputErrorNode.php b/src/Bridges/FormsLatte/Nodes/InputErrorNode.php
index 014e16d69..cf7b15bf6 100644
--- a/src/Bridges/FormsLatte/Nodes/InputErrorNode.php
+++ b/src/Bridges/FormsLatte/Nodes/InputErrorNode.php
@@ -16,7 +16,8 @@
/**
- * {inputError ...}
+ * {inputError name}
+ * Renders form control error message.
*/
class InputErrorNode extends StatementNode
{
diff --git a/src/Bridges/FormsLatte/Nodes/InputNode.php b/src/Bridges/FormsLatte/Nodes/InputNode.php
index d9f6841eb..bb037385b 100644
--- a/src/Bridges/FormsLatte/Nodes/InputNode.php
+++ b/src/Bridges/FormsLatte/Nodes/InputNode.php
@@ -18,7 +18,8 @@
/**
- * {input ...}
+ * {input name[:part] [, attributes]}
+ * Renders form control HTML.
*/
class InputNode extends StatementNode
{
diff --git a/src/Bridges/FormsLatte/Nodes/LabelNode.php b/src/Bridges/FormsLatte/Nodes/LabelNode.php
index 26d30d333..e96045675 100644
--- a/src/Bridges/FormsLatte/Nodes/LabelNode.php
+++ b/src/Bridges/FormsLatte/Nodes/LabelNode.php
@@ -21,7 +21,9 @@
/**
- * {label ...} ... {/label}
+ * {label name[:part] [, attributes]} ... {/label}
+ * {label name /}
+ * Renders form control label.
*/
class LabelNode extends StatementNode
{
@@ -33,7 +35,7 @@ class LabelNode extends StatementNode
public ?Position $endLine;
- /** @return \Generator */
+ /** @return \Generator, array{AreaNode, ?Tag}, static|AreaNode> */
public static function create(Tag $tag): \Generator
{
if ($tag->isNAttribute()) {
diff --git a/src/Bridges/FormsLatte/Runtime.php b/src/Bridges/FormsLatte/Runtime.php
index 6ccfd960e..2f42f3d0a 100644
--- a/src/Bridges/FormsLatte/Runtime.php
+++ b/src/Bridges/FormsLatte/Runtime.php
@@ -35,6 +35,7 @@ public static function initializeForm(Form $form): void
/**
* Renders form begin.
+ * @param array $attrs
*/
public static function renderFormBegin(Form $form, array $attrs, bool $withTags = true): string
{
@@ -77,7 +78,8 @@ public static function renderFormEnd(Form $form, bool $withTags = true): string
}
- public static function item($item, $global): object
+ /** @param object{formsStack: Form[]} $global */
+ public static function item(object|string|int $item, object $global): object
{
if (is_object($item)) {
return $item;
diff --git a/src/Forms/Container.php b/src/Forms/Container.php
index de730b937..4cc14855a 100644
--- a/src/Forms/Container.php
+++ b/src/Forms/Container.php
@@ -18,9 +18,10 @@
/**
* Container for form controls.
*
- * @property ArrayHash $values
+ * @property ArrayHash $values
* @property-read \Iterator $controls
- * @property-read Form|null $form
+ * @property-read ?Form $form
+ * @implements \ArrayAccess
*/
class Container extends Nette\ComponentModel\Container implements \ArrayAccess
{
@@ -30,12 +31,12 @@ class Container extends Nette\ComponentModel\Container implements \ArrayAccess
/**
* Occurs when the form was validated
- * @var array
+ * @var array
*/
public array $onValidate = [];
protected ?ControlGroup $currentGroup = null;
- /** @var callable[] extension methods */
+ /** @var array */
private static array $extMethods = [];
private ?bool $validated = false;
private ?string $mappedType = null;
@@ -46,17 +47,19 @@ class Container extends Nette\ComponentModel\Container implements \ArrayAccess
/**
* Fill-in with default values.
+ * @param mixed[]|object $values
*/
- public function setDefaults(array|object $data, bool $erase = false): static
+ public function setDefaults(array|object $values, bool $erase = false): static
{
- $form = $this->getForm(false);
- $this->setValues($data, $erase, $form?->isAnchored() && $form->isSubmitted());
+ $form = $this->getForm(throw: false);
+ $this->setValues($values, $erase, $form?->isAnchored() && $form->isSubmitted());
return $this;
}
/**
* Fill-in with values.
+ * @param mixed[]|object $values
* @internal
*/
public function setValues(array|object $values, bool $erase = false, bool $onlyDisabled = false): static
@@ -83,11 +86,14 @@ public function setValues(array|object $values, bool $erase = false, bool $onlyD
/**
* Returns the values submitted by the form.
- * @param Control[]|null $controls
+ * @template T of object
+ * @param class-string|T|'array'|true|null $returnType
+ * @param ?list $controls
+ * @return ($returnType is class-string|T ? T : ($returnType is 'array'|true ? mixed[] : ArrayHash))
*/
public function getValues(string|object|bool|null $returnType = null, ?array $controls = null): object|array
{
- $form = $this->getForm(false);
+ $form = $this->getForm(throw: false);
if ($form && ($submitter = $form->isSubmitted())) {
if ($this->validated === null) {
throw new Nette\InvalidStateException('You cannot call getValues() during the validation process. Use getUntrustedValues() instead.');
@@ -98,10 +104,10 @@ public function getValues(string|object|bool|null $returnType = null, ?array $co
if ($controls === null && $submitter instanceof SubmitterControl) {
$controls = $submitter->getValidationScope();
- if ($controls !== null && !in_array($this, $controls, true)) {
+ if ($controls !== null && !in_array($this, $controls, strict: true)) {
$scope = $this;
while (($scope = $scope->getParent()) instanceof self) {
- if (in_array($scope, $controls, true)) {
+ if (in_array($scope, $controls, strict: true)) {
$controls[] = $this;
break;
}
@@ -121,7 +127,10 @@ public function getValues(string|object|bool|null $returnType = null, ?array $co
/**
* Returns the potentially unvalidated values submitted by the form.
- * @param Control[]|null $controls
+ * @template T of object
+ * @param class-string|T|'array'|null $returnType
+ * @param ?list $controls
+ * @return ($returnType is class-string|T ? T : ($returnType is 'array' ? mixed[] : ArrayHash))
*/
public function getUntrustedValues(string|object|null $returnType = null, ?array $controls = null): object|array
{
@@ -130,7 +139,7 @@ public function getUntrustedValues(string|object|null $returnType = null, ?array
$properties = (new \ReflectionClass($resultObj))->getProperties();
} else {
- $returnType = ($returnType ?? $this->mappedType ?? ArrayHash::class);
+ $returnType ??= $this->mappedType ?? ArrayHash::class;
$rc = new \ReflectionClass($returnType === self::Array ? \stdClass::class : $returnType);
$constructor = $rc->hasMethod('__construct') ? $rc->getMethod('__construct') : null;
if ($constructor?->getNumberOfRequiredParameters()) {
@@ -146,7 +155,7 @@ public function getUntrustedValues(string|object|null $returnType = null, ?array
$properties = array_combine(array_map(fn($p) => $p->getName(), $properties), $properties);
foreach ($this->getComponents() as $name => $control) {
- $allowed = $controls === null || in_array($this, $controls, true) || in_array($control, $controls, true);
+ $allowed = $controls === null || in_array($this, $controls, strict: true) || in_array($control, $controls, strict: true);
$name = (string) $name;
$property = $properties[$name] ?? null;
if (
@@ -179,6 +188,7 @@ public function getUnsafeValues($returnType, ?array $controls = null)
}
+ /** @param class-string $type */
public function setMappedType(string $type): static
{
$this->mappedType = $type;
@@ -237,6 +247,7 @@ public function validate(?array $controls = null): void
/**
* Returns all validation errors.
+ * @return list
*/
public function getErrors(): array
{
@@ -286,10 +297,17 @@ public function addComponent(
/**
* Iterates over all form controls.
+ * @return iterable
*/
- public function getControls(): \Iterator
+ public function getControls(): iterable
{
- return $this->getComponents(true, Control::class);
+ return Nette\Utils\Iterables::repeatable(function () {
+ foreach ($this->getComponentTree() as $component) {
+ if ($component instanceof Control) {
+ yield $component->getName() => $component;
+ }
+ }
+ });
}
@@ -393,7 +411,7 @@ public function addFloat(string $name, string|Stringable|null $label = null): Co
/**
* Adds input for date selection.
*/
- public function addDate(string $name, string|object|null $label = null): Controls\DateTimeControl
+ public function addDate(string $name, string|Stringable|null $label = null): Controls\DateTimeControl
{
return $this[$name] = new Controls\DateTimeControl($label, Controls\DateTimeControl::TypeDate);
}
@@ -404,7 +422,7 @@ public function addDate(string $name, string|object|null $label = null): Control
*/
public function addTime(
string $name,
- string|object|null $label = null,
+ string|Stringable|null $label = null,
bool $withSeconds = false,
): Controls\DateTimeControl
{
@@ -417,7 +435,7 @@ public function addTime(
*/
public function addDateTime(
string $name,
- string|object|null $label = null,
+ string|Stringable|null $label = null,
bool $withSeconds = false,
): Controls\DateTimeControl
{
@@ -446,7 +464,7 @@ public function addMultiUpload(string $name, string|Stringable|null $label = nul
/**
* Adds hidden form control used to store a non-displayed value.
*/
- public function addHidden(string $name, $default = null): Controls\HiddenField
+ public function addHidden(string $name, mixed $default = null): Controls\HiddenField
{
return $this[$name] = (new Controls\HiddenField)
->setDefaultValue($default);
@@ -464,6 +482,7 @@ public function addCheckbox(string $name, string|Stringable|null $caption = null
/**
* Adds set of radio button controls to the form.
+ * @param ?mixed[] $items
*/
public function addRadioList(
string $name,
@@ -477,6 +496,7 @@ public function addRadioList(
/**
* Adds set of checkbox controls to the form.
+ * @param ?mixed[] $items
*/
public function addCheckboxList(
string $name,
@@ -490,6 +510,7 @@ public function addCheckboxList(
/**
* Adds select box control that allows single item selection.
+ * @param ?mixed[] $items
*/
public function addSelect(
string $name,
@@ -505,6 +526,7 @@ public function addSelect(
/**
* Adds select box control that allows multiple item selection.
+ * @param ?mixed[] $items
*/
public function addMultiSelect(
string $name,
@@ -578,7 +600,8 @@ public function addContainer(string|int $name): self
/********************* extension methods ****************d*g**/
- public function __call(string $name, array $args)
+ /** @param mixed[] $args */
+ public function __call(string $name, array $args): mixed
{
if (isset(self::$extMethods[$name])) {
return (self::$extMethods[$name])($this, ...$args);
@@ -588,7 +611,8 @@ public function __call(string $name, array $args)
}
- public static function extensionMethod(string $name, /*callable*/ $callback): void
+ /** @param callable(self): mixed $callback */
+ public static function extensionMethod(string $name, callable $callback): void
{
if (str_contains($name, '::')) { // back compatibility
[, $name] = explode('::', $name);
diff --git a/src/Forms/Control.php b/src/Forms/Control.php
index 9eff4bf70..aa9ac0dc9 100644
--- a/src/Forms/Control.php
+++ b/src/Forms/Control.php
@@ -32,6 +32,7 @@ function validate(): void;
/**
* Returns errors corresponding to control.
+ * @return list
*/
function getErrors(): array;
diff --git a/src/Forms/ControlGroup.php b/src/Forms/ControlGroup.php
index ed7372241..47059f286 100644
--- a/src/Forms/ControlGroup.php
+++ b/src/Forms/ControlGroup.php
@@ -18,7 +18,10 @@
*/
class ControlGroup
{
+ /** @var \WeakMap */
protected \WeakMap $controls;
+
+ /** @var array */
private array $options = [];
@@ -28,7 +31,8 @@ public function __construct()
}
- public function add(...$items): static
+ /** @param Control|Container|iterable ...$items */
+ public function add(Control|Container|iterable ...$items): static
{
foreach ($items as $item) {
if ($item instanceof Control) {
@@ -38,12 +42,8 @@ public function add(...$items): static
foreach ($item->getComponents() as $component) {
$this->add($component);
}
- } elseif (is_iterable($item)) {
- $this->add(...$item);
-
} else {
- $type = get_debug_type($item);
- throw new Nette\InvalidArgumentException("Control or Container items expected, $type given.");
+ $this->add(...$item);
}
}
@@ -115,6 +115,7 @@ public function getOption(string $key): mixed
/**
* Returns user-specific options.
+ * @return array
*/
public function getOptions(): array
{
diff --git a/src/Forms/Controls/BaseControl.php b/src/Forms/Controls/BaseControl.php
index d0f7fede8..dee16e809 100644
--- a/src/Forms/Controls/BaseControl.php
+++ b/src/Forms/Controls/BaseControl.php
@@ -34,8 +34,8 @@
* @property-read Html $labelPrototype
* @property bool $required
* @property-read bool $filled
- * @property-read array $errors
- * @property-read array $options
+ * @property-read string[] $errors
+ * @property-read array $options
* @property-read string $error
*/
abstract class BaseControl extends Nette\ComponentModel\Component implements Control
@@ -49,15 +49,19 @@ abstract class BaseControl extends Nette\ComponentModel\Component implements Con
/** @var bool|bool[] */
protected bool|array $disabled = false;
- /** @var callable[][] extension methods */
+ /** @var array> */
private static array $extMethods = [];
private string|Stringable|null $caption;
+
+ /** @var list */
private array $errors = [];
private ?bool $omitted = null;
private Rules $rules;
/** true means autodetect */
- private Nette\Localization\Translator|bool|null $translator = true;
+ private Nette\Localization\Translator|true|null $translator = true;
+
+ /** @var array */
private array $options = [];
@@ -168,7 +172,7 @@ public function isFilled(): bool
* Sets control's default value.
* @return static
*/
- public function setDefaultValue($value)
+ public function setDefaultValue(mixed $value)
{
$form = $this->getForm(throw: false);
if ($this->isDisabled() || !$form || !$form->isAnchored() || !$form->isSubmitted()) {
@@ -325,7 +329,7 @@ public function setHtmlAttribute(string $name, mixed $value = true): static
$this->control->$name = $value;
if (
$name === 'name'
- && ($form = $this->getForm(false))
+ && ($form = $this->getForm(throw: false))
&& !$this->isDisabled()
&& $form->isAnchored()
&& $form->isSubmitted()
@@ -365,7 +369,7 @@ public function setTranslator(?Nette\Localization\Translator $translator): stati
public function getTranslator(): ?Nette\Localization\Translator
{
if ($this->translator === true) {
- return $this->getForm(false)
+ return $this->getForm(throw: false)
? $this->getForm()->getTranslator()
: null;
}
@@ -377,7 +381,7 @@ public function getTranslator(): ?Nette\Localization\Translator
/**
* Returns translated string.
*/
- public function translate($value, ...$parameters): mixed
+ public function translate(mixed $value, mixed ...$parameters): mixed
{
if ($translator = $this->getTranslator()) {
$tmp = is_array($value) ? [&$value] : [[&$value]];
@@ -397,6 +401,7 @@ public function translate($value, ...$parameters): mixed
/**
* Adds a validation rule.
+ * @param (callable(Control): bool)|string $validator
* @return static
*/
public function addRule(
@@ -411,8 +416,9 @@ public function addRule(
/**
* Adds a validation condition a returns new branch.
+ * @param (callable(Control): bool)|string|bool $validator
*/
- public function addCondition($validator, $value = null): Rules
+ public function addCondition($validator, mixed $value = null): Rules
{
return $this->rules->addCondition($validator, $value);
}
@@ -420,8 +426,9 @@ public function addCondition($validator, $value = null): Rules
/**
* Adds a validation condition based on another control a returns new branch.
+ * @param (callable(Control): bool)|string $validator
*/
- public function addConditionOn(Control $control, $validator, $value = null): Rules
+ public function addConditionOn(Control $control, $validator, mixed $value = null): Rules
{
return $this->rules->addConditionOn($control, $validator, $value);
}
@@ -429,6 +436,7 @@ public function addConditionOn(Control $control, $validator, $value = null): Rul
/**
* Adds an input filter callback.
+ * @param callable(mixed): mixed $filter
*/
public function addFilter(callable $filter): static
{
@@ -496,6 +504,7 @@ public function getError(): ?string
/**
* Returns errors corresponding to control.
+ * @return list
*/
public function getErrors(): array
{
@@ -548,6 +557,7 @@ public function getOption($key): mixed
/**
* Returns user-specific options.
+ * @return array
*/
public function getOptions(): array
{
@@ -558,6 +568,7 @@ public function getOptions(): array
/********************* extension methods ****************d*g**/
+ /** @param mixed[] $args */
public function __call(string $name, array $args)
{
$class = static::class;
@@ -573,7 +584,8 @@ public function __call(string $name, array $args)
}
- public static function extensionMethod(string $name, /*callable*/ $callback): void
+ /** @param callable(self): mixed $callback */
+ public static function extensionMethod(string $name, callable $callback): void
{
if (str_contains($name, '::')) { // back compatibility
[, $name] = explode('::', $name);
diff --git a/src/Forms/Controls/CheckboxList.php b/src/Forms/Controls/CheckboxList.php
index b9809d07e..da300fba5 100644
--- a/src/Forms/Controls/CheckboxList.php
+++ b/src/Forms/Controls/CheckboxList.php
@@ -29,6 +29,7 @@ class CheckboxList extends MultiChoiceControl
protected Html $itemLabel;
+ /** @param ?mixed[] $items */
public function __construct(string|Stringable|null $label = null, ?array $items = null)
{
parent::__construct($label, $items);
diff --git a/src/Forms/Controls/ChoiceControl.php b/src/Forms/Controls/ChoiceControl.php
index cfd27c2ad..a9bf31cd7 100644
--- a/src/Forms/Controls/ChoiceControl.php
+++ b/src/Forms/Controls/ChoiceControl.php
@@ -16,16 +16,20 @@
/**
* Choice control that allows single item selection.
*
- * @property array $items
+ * @property mixed[] $items
+ * @property bool|array $disabled
* @property-read mixed $selectedItem
*/
abstract class ChoiceControl extends BaseControl
{
private bool $checkDefaultValue = true;
+
+ /** @var mixed[] */
private array $items = [];
- public function __construct($label = null, ?array $items = null)
+ /** @param ?mixed[] $items */
+ public function __construct(string|\Stringable|null $label = null, ?array $items = null)
{
parent::__construct($label);
if ($items !== null) {
@@ -97,6 +101,7 @@ public function isFilled(): bool
/**
* Sets items from which to choose.
+ * @param mixed[] $items
* @return static
*/
public function setItems(array $items, bool $useKeys = true)
@@ -108,6 +113,7 @@ public function setItems(array $items, bool $useKeys = true)
/**
* Returns items from which to choose.
+ * @return mixed[]
*/
public function getItems(): array
{
@@ -127,6 +133,7 @@ public function getSelectedItem(): mixed
/**
* Disables or enables control or items.
+ * @param bool|array $value
*/
public function setDisabled(bool|array $value = true): static
{
diff --git a/src/Forms/Controls/ColorPicker.php b/src/Forms/Controls/ColorPicker.php
index 2cd81ad40..44d7eaede 100644
--- a/src/Forms/Controls/ColorPicker.php
+++ b/src/Forms/Controls/ColorPicker.php
@@ -18,7 +18,7 @@
*/
class ColorPicker extends BaseControl
{
- public function __construct($label = null)
+ public function __construct(string|\Stringable|null $label = null)
{
parent::__construct($label);
$this->setOption('type', 'color');
@@ -26,7 +26,7 @@ public function __construct($label = null)
/**
- * @param ?string $value
+ * @param ?string $value
*/
public function setValue($value): static
{
diff --git a/src/Forms/Controls/DateTimeControl.php b/src/Forms/Controls/DateTimeControl.php
index 5a5c96b30..8ae1c6216 100644
--- a/src/Forms/Controls/DateTimeControl.php
+++ b/src/Forms/Controls/DateTimeControl.php
@@ -28,19 +28,14 @@ class DateTimeControl extends BaseControl
public const
FormatObject = 'object',
FormatTimestamp = 'timestamp';
-
- private int $type;
- private bool $withSeconds;
private string $format = self::FormatObject;
public function __construct(
string|Stringable|null $label = null,
- int $type = self::TypeDate,
- bool $withSeconds = false,
+ private int $type = self::TypeDate,
+ private bool $withSeconds = false,
) {
- $this->type = $type;
- $this->withSeconds = $withSeconds;
parent::__construct($label);
$this->control->step = $withSeconds ? 1 : null;
$this->setOption('type', 'datetime');
@@ -58,7 +53,7 @@ public function setFormat(string $format): static
/**
- * @param \DateTimeInterface|string|int|null $value
+ * @param \DateTimeInterface|string|int|null $value
*/
public function setValue($value): static
{
@@ -80,7 +75,7 @@ public function getValue(): \DateTimeImmutable|string|int|null
/**
- * @param \DateTimeInterface|string|int $value
+ * @param \DateTimeInterface|string|int $value
*/
private function normalizeValue(mixed $value): \DateTimeImmutable
{
@@ -148,6 +143,7 @@ public function formatLocaleText(\DateTimeInterface|string|int $value): string
}
+ /** @return array */
private function getAttributesFromRules(): array
{
$attrs = [];
diff --git a/src/Forms/Controls/HiddenField.php b/src/Forms/Controls/HiddenField.php
index ab6a7010b..5f8826b47 100644
--- a/src/Forms/Controls/HiddenField.php
+++ b/src/Forms/Controls/HiddenField.php
@@ -24,7 +24,7 @@ class HiddenField extends BaseControl
private bool $nullable = false;
- public function __construct($persistentValue = null)
+ public function __construct(mixed $persistentValue = null)
{
parent::__construct();
$this->control->type = 'hidden';
diff --git a/src/Forms/Controls/MultiChoiceControl.php b/src/Forms/Controls/MultiChoiceControl.php
index aaab133e9..5f9170f9a 100644
--- a/src/Forms/Controls/MultiChoiceControl.php
+++ b/src/Forms/Controls/MultiChoiceControl.php
@@ -16,16 +16,20 @@
/**
* Choice control that allows multiple items selection.
*
- * @property array $items
- * @property-read array $selectedItems
+ * @property mixed[] $items
+ * @property bool|array $disabled
+ * @property-read mixed[] $selectedItems
*/
abstract class MultiChoiceControl extends BaseControl
{
private bool $checkDefaultValue = true;
+
+ /** @var mixed[] */
private array $items = [];
- public function __construct($label = null, ?array $items = null)
+ /** @param ?mixed[] $items */
+ public function __construct(string|\Stringable|null $label = null, ?array $items = null)
{
parent::__construct($label);
if ($items !== null) {
@@ -78,6 +82,7 @@ public function setValue($values)
/**
* Returns selected keys.
+ * @return list
*/
public function getValue(): array
{
@@ -87,6 +92,7 @@ public function getValue(): array
/**
* Returns selected keys (not checked).
+ * @return list
*/
public function getRawValue(): array
{
@@ -96,6 +102,7 @@ public function getRawValue(): array
/**
* Sets items from which to choose.
+ * @param mixed[] $items
* @return static
*/
public function setItems(array $items, bool $useKeys = true)
@@ -107,6 +114,7 @@ public function setItems(array $items, bool $useKeys = true)
/**
* Returns items from which to choose.
+ * @return mixed[]
*/
public function getItems(): array
{
@@ -116,6 +124,7 @@ public function getItems(): array
/**
* Returns selected values.
+ * @return mixed[]
*/
public function getSelectedItems(): array
{
@@ -131,6 +140,7 @@ public function getSelectedItems(): array
/**
* Disables or enables control or items.
+ * @param bool|array $value
*/
public function setDisabled(bool|array $value = true): static
{
diff --git a/src/Forms/Controls/MultiSelectBox.php b/src/Forms/Controls/MultiSelectBox.php
index e0832c3ec..baddab7f2 100644
--- a/src/Forms/Controls/MultiSelectBox.php
+++ b/src/Forms/Controls/MultiSelectBox.php
@@ -18,12 +18,15 @@
*/
class MultiSelectBox extends MultiChoiceControl
{
- /** of option / optgroup */
+ /** @var mixed[] option / optgroup */
private array $options = [];
+
+ /** @var array */
private array $optionAttributes = [];
- public function __construct($label = null, ?array $items = null)
+ /** @param ?mixed[] $items */
+ public function __construct(string|\Stringable|null $label = null, ?array $items = null)
{
parent::__construct($label, $items);
$this->setOption('type', 'select');
@@ -32,6 +35,7 @@ public function __construct($label = null, ?array $items = null)
/**
* Sets options and option groups from which to choose.
+ * @param mixed[] $items
* @return static
*/
public function setItems(array $items, bool $useKeys = true)
@@ -74,7 +78,10 @@ public function getControl(): Nette\Utils\Html
}
- /** @deprecated use setOptionAttribute() */
+ /**
+ * @param array $attributes
+ * @deprecated use setOptionAttribute()
+ */
public function addOptionAttributes(array $attributes): static
{
$this->optionAttributes = $attributes + $this->optionAttributes;
@@ -89,6 +96,7 @@ public function setOptionAttribute(string $name, mixed $value = true): static
}
+ /** @return array */
public function getOptionAttributes(): array
{
return $this->optionAttributes;
diff --git a/src/Forms/Controls/RadioList.php b/src/Forms/Controls/RadioList.php
index 91b3b2460..5d6348fb9 100644
--- a/src/Forms/Controls/RadioList.php
+++ b/src/Forms/Controls/RadioList.php
@@ -30,6 +30,7 @@ class RadioList extends ChoiceControl
protected Html $itemLabel;
+ /** @param ?mixed[] $items */
public function __construct(string|Stringable|null $label = null, ?array $items = null)
{
parent::__construct($label, $items);
diff --git a/src/Forms/Controls/SelectBox.php b/src/Forms/Controls/SelectBox.php
index 6555e39b0..dacaa679b 100644
--- a/src/Forms/Controls/SelectBox.php
+++ b/src/Forms/Controls/SelectBox.php
@@ -25,13 +25,16 @@ class SelectBox extends ChoiceControl
/** @deprecated use SelectBox::Valid */
public const VALID = self::Valid;
- /** of option / optgroup */
+ /** @var mixed[] option / optgroup */
private array $options = [];
private string|Stringable|false $prompt = false;
+
+ /** @var array */
private array $optionAttributes = [];
- public function __construct($label = null, ?array $items = null)
+ /** @param ?mixed[] $items */
+ public function __construct(string|\Stringable|null $label = null, ?array $items = null)
{
parent::__construct($label, $items);
$this->setOption('type', 'select');
@@ -64,6 +67,7 @@ public function getPrompt(): string|Stringable|false
/**
* Sets options and option groups from which to choose.
+ * @param mixed[] $items
* @return static
*/
public function setItems(array $items, bool $useKeys = true)
@@ -118,7 +122,10 @@ public function getControl(): Nette\Utils\Html
}
- /** @deprecated use setOptionAttribute() */
+ /**
+ * @param array $attributes
+ * @deprecated use setOptionAttribute()
+ */
public function addOptionAttributes(array $attributes): static
{
$this->optionAttributes = $attributes + $this->optionAttributes;
@@ -143,6 +150,7 @@ public function isOk(): bool
}
+ /** @return array */
public function getOptionAttributes(): array
{
return $this->optionAttributes;
diff --git a/src/Forms/Controls/SubmitButton.php b/src/Forms/Controls/SubmitButton.php
index 6d845a94f..7a098f1fe 100644
--- a/src/Forms/Controls/SubmitButton.php
+++ b/src/Forms/Controls/SubmitButton.php
@@ -10,6 +10,8 @@
namespace Nette\Forms\Controls;
use Nette;
+use Nette\Forms\Container;
+use Nette\Forms\Control;
use Stringable;
use function is_string;
@@ -23,12 +25,14 @@ class SubmitButton extends Button implements Nette\Forms\SubmitterControl
{
/**
* Occurs when the button is clicked and form is successfully validated
- * @var array
+ * @var array
*/
public array $onClick = [];
/** @var array Occurs when the button is clicked and form is not validated */
public array $onInvalidClick = [];
+
+ /** @var ?list */
private ?array $validationScope = null;
@@ -59,7 +63,7 @@ public function isSubmittedBy(): bool
/**
* Sets the validation scope. Clicking the button validates only the controls within the specified scope.
- * @param ?iterable $scope
+ * @param ?iterable $scope
*/
public function setValidationScope(?iterable $scope): static
{
@@ -73,7 +77,7 @@ public function setValidationScope(?iterable $scope): static
if (is_string($control)) {
$control = $this->getForm()->getComponent($control);
}
- if (!$control instanceof Nette\Forms\Container && !$control instanceof Nette\Forms\Control) {
+ if (!$control instanceof Container && !$control instanceof Control) {
throw new Nette\InvalidArgumentException('Validation scope accepts only Nette\Forms\Container or Nette\Forms\Control instances.');
}
@@ -85,7 +89,7 @@ public function setValidationScope(?iterable $scope): static
/**
* Gets the validation scope.
- * @return ?array
+ * @return ?array
*/
public function getValidationScope(): ?array
{
diff --git a/src/Forms/Controls/TextBase.php b/src/Forms/Controls/TextBase.php
index 0f351b4b5..cd6eabc3e 100644
--- a/src/Forms/Controls/TextBase.php
+++ b/src/Forms/Controls/TextBase.php
@@ -126,7 +126,10 @@ protected function getRenderedValue(): ?string
}
- /** @return static */
+ /**
+ * @param (callable(Nette\Forms\Control): bool)|string $validator
+ * @return static
+ */
public function addRule(
callable|string $validator,
string|Stringable|null $errorMessage = null,
diff --git a/src/Forms/Controls/TextInput.php b/src/Forms/Controls/TextInput.php
index 2dd12ad11..64b5812ad 100644
--- a/src/Forms/Controls/TextInput.php
+++ b/src/Forms/Controls/TextInput.php
@@ -57,12 +57,15 @@ public function getControl(): Nette\Utils\Html
{
return parent::getControl()->addAttributes([
'value' => $this->control->type === 'password' ? $this->control->value : $this->getRenderedValue(),
- 'type' => $this->control->type ?: 'text',
+ 'type' => $this->control->type ?? 'text',
]);
}
- /** @return static */
+ /**
+ * @param (callable(Nette\Forms\Control): bool)|string $validator
+ * @return static
+ */
public function addRule(
callable|string $validator,
string|Stringable|null $errorMessage = null,
@@ -74,13 +77,13 @@ public function addRule(
}
}
- if ($this->control->type === null && in_array($validator, [Form::Email, Form::URL, Form::Integer], true)) {
+ if ($this->control->type === null && in_array($validator, [Form::Email, Form::URL, Form::Integer], strict: true)) {
$types = [Form::Email => 'email', Form::URL => 'url', Form::Integer => 'number'];
$this->control->type = $types[$validator];
} elseif (
- in_array($validator, [Form::Min, Form::Max, Form::Range], true)
- && in_array($this->control->type, ['number', 'range', 'datetime-local', 'datetime', 'date', 'month', 'week', 'time'], true)
+ in_array($validator, [Form::Min, Form::Max, Form::Range], strict: true)
+ && in_array($this->control->type, ['number', 'range', 'datetime-local', 'datetime', 'date', 'month', 'week', 'time'], strict: true)
) {
if ($validator === Form::Min) {
$range = [$arg, null];
@@ -105,7 +108,7 @@ public function addRule(
} elseif (
$validator === Form::Pattern
&& is_scalar($arg)
- && in_array($this->control->type, [null, 'text', 'search', 'tel', 'url', 'email', 'password'], true)
+ && in_array($this->control->type, [null, 'text', 'search', 'tel', 'url', 'email', 'password'], strict: true)
) {
$this->control->pattern = $arg;
}
diff --git a/src/Forms/Controls/UploadControl.php b/src/Forms/Controls/UploadControl.php
index fdcca95a8..42c57a67d 100644
--- a/src/Forms/Controls/UploadControl.php
+++ b/src/Forms/Controls/UploadControl.php
@@ -77,6 +77,7 @@ public function setValue($value)
}
+ /** @return FileUpload|FileUpload[]|null */
public function getValue(): FileUpload|array|null
{
return $this->value ?? ($this->nullable ? null : new FileUpload(null));
@@ -122,7 +123,10 @@ public function isOk(): bool
}
- /** @return static */
+ /**
+ * @param (callable(Nette\Forms\Control): bool)|string $validator
+ * @return static
+ */
public function addRule(
callable|string $validator,
string|Stringable|null $errorMessage = null,
diff --git a/src/Forms/Form.php b/src/Forms/Form.php
index c10551d9b..bea8a6cd0 100644
--- a/src/Forms/Form.php
+++ b/src/Forms/Form.php
@@ -20,8 +20,8 @@
/**
* Creates, validates and renders HTML forms.
*
- * @property-read array $errors
- * @property-read array $ownErrors
+ * @property-read string[] $errors
+ * @property-read array $ownErrors
* @property-read Html $elementPrototype
* @property-read FormRenderer $renderer
* @property string $action
@@ -189,7 +189,7 @@ class Form extends Container implements Nette\HtmlStringable
/**
* Occurs when the form is submitted and successfully validated
- * @var array
+ * @var array
*/
public array $onSuccess = [];
@@ -209,6 +209,8 @@ class Form extends Container implements Nette\HtmlStringable
protected $crossOrigin = false;
private static ?Nette\Http\IRequest $defaultHttpRequest = null;
private SubmitterControl|bool $submittedBy = false;
+
+ /** @var mixed[] */
private array $httpData;
private Html $element;
private FormRenderer $renderer;
@@ -216,6 +218,8 @@ class Form extends Container implements Nette\HtmlStringable
/** @var ControlGroup[] */
private array $groups = [];
+
+ /** @var list */
private array $errors = [];
private bool $beforeRenderCalled = false;
@@ -459,6 +463,7 @@ public function setSubmittedBy(?SubmitterControl $by): static
/**
* Returns submitted HTTP data.
+ * @return string|string[]|Nette\Http\FileUpload|null
*/
public function getHttpData(?int $type = null, ?string $htmlName = null): string|array|Nette\Http\FileUpload|null
{
@@ -517,7 +522,8 @@ public function fireEvents(): void
}
- private function invokeHandlers(iterable $handlers, $button = null): void
+ /** @param iterable $handlers */
+ private function invokeHandlers(iterable $handlers, ?SubmitterControl $button = null): void
{
foreach ($handlers as $handler) {
$params = Nette\Utils\Callback::toReflection($handler)->getParameters();
@@ -557,6 +563,7 @@ public function reset(): static
/**
* Internal: returns submitted HTTP data or null when form was not submitted.
+ * @return ?mixed[]
*/
protected function receiveHttpData(): ?array
{
@@ -591,6 +598,7 @@ protected function receiveHttpData(): ?array
/********************* validation ****************d*g**/
+ /** @param ?(Control|Container)[] $controls */
public function validate(?array $controls = null): void
{
$this->cleanErrors();
@@ -632,6 +640,7 @@ public function addError(string|Stringable $message, bool $translate = true): vo
/**
* Returns global validation errors.
+ * @return list
*/
public function getErrors(): array
{
@@ -653,6 +662,7 @@ public function cleanErrors(): void
/**
* Returns form's validation errors.
+ * @return list
*/
public function getOwnErrors(): array
{
@@ -722,7 +732,7 @@ public function fireRenderEvents(): void
/**
* Renders form.
*/
- public function render(...$args): void
+ public function render(mixed ...$args): void
{
$this->fireRenderEvents();
echo $this->getRenderer()->render($this, ...$args);
@@ -739,6 +749,7 @@ public function __toString(): string
}
+ /** @return array */
public function getToggles(): array
{
$toggles = [];
@@ -769,7 +780,7 @@ public static function initialize(bool $reinit = false): void
self::$defaultHttpRequest = (new Nette\Http\RequestFactory)->fromGlobals();
- if (!in_array(PHP_SAPI, ['cli', 'phpdbg', 'embed'], true)) {
+ if (!in_array(PHP_SAPI, ['cli', 'phpdbg', 'embed'], strict: true)) {
if (headers_sent($file, $line)) {
throw new Nette\InvalidStateException(
'Create a form or call Nette\Forms\Form::initialize() before the headers are sent to initialize CSRF protection.'
diff --git a/src/Forms/Helpers.php b/src/Forms/Helpers.php
index 5ef225720..86d1b1736 100644
--- a/src/Forms/Helpers.php
+++ b/src/Forms/Helpers.php
@@ -32,7 +32,9 @@ final class Helpers
/**
* Extracts and sanitizes submitted form data for single control.
+ * @param mixed[] $data
* @param int $type type Form::DataText, DataLine, DataFile, DataKeys
+ * @return string|mixed[]|Nette\Http\FileUpload|null
* @internal
*/
public static function extractHttpData(
@@ -68,7 +70,8 @@ public static function extractHttpData(
}
- private static function sanitize(int $type, $value): string|array|Nette\Http\FileUpload|null
+ /** @return string|mixed[]|Nette\Http\FileUpload|null */
+ private static function sanitize(int $type, mixed $value): string|array|Nette\Http\FileUpload|null
{
if ($type === Form::DataText) {
return is_scalar($value)
@@ -94,7 +97,7 @@ private static function sanitize(int $type, $value): string|array|Nette\Http\Fil
*/
public static function generateHtmlName(string $id): string
{
- $name = str_replace(Nette\ComponentModel\IComponent::NAME_SEPARATOR, '][', $id, $count);
+ $name = str_replace(Nette\ComponentModel\IComponent::NameSeparator, '][', $id, $count);
if ($count) {
$name = substr_replace($name, '', strpos($name, ']'), 1) . ']';
}
@@ -107,6 +110,7 @@ public static function generateHtmlName(string $id): string
}
+ /** @return list> */
public static function exportRules(Rules $rules): array
{
$payload = [];
@@ -172,11 +176,16 @@ private static function exportArgument(mixed $value, Control $control): mixed
}
+ /**
+ * @param mixed[] $items
+ * @param ?array $inputAttrs
+ * @param ?array $labelAttrs
+ */
public static function createInputList(
array $items,
?array $inputAttrs = null,
?array $labelAttrs = null,
- $wrapper = null,
+ Html|string|null $wrapper = null,
): string
{
[$inputAttrs, $inputTag] = self::prepareAttrs($inputAttrs, 'input');
@@ -208,7 +217,11 @@ public static function createInputList(
}
- public static function createSelectBox(array $items, ?array $optionAttrs = null, $selected = null): Html
+ /**
+ * @param mixed[] $items
+ * @param ?array $optionAttrs
+ */
+ public static function createSelectBox(array $items, ?array $optionAttrs = null, mixed $selected = null): Html
{
if ($selected !== null) {
$optionAttrs['selected?'] = $selected;
@@ -253,6 +266,10 @@ public static function createSelectBox(array $items, ?array $optionAttrs = null,
}
+ /**
+ * @param ?array $attrs
+ * @return array{array, string}
+ */
private static function prepareAttrs(?array $attrs, string $name): array
{
$dynamic = [];
@@ -285,8 +302,11 @@ public static function iniGetSize(string $name): int
}
- /** @internal */
- public static function getSingleType($reflection): ?string
+ /**
+ * @internal
+ * @return ?class-string
+ */
+ public static function getSingleType(\ReflectionParameter|\ReflectionProperty $reflection): ?string
{
$type = Nette\Utils\Type::fromReflection($reflection);
if (!$type) {
@@ -302,7 +322,10 @@ public static function getSingleType($reflection): ?string
/** @internal */
- public static function tryEnumConversion(mixed $value, $reflection): mixed
+ public static function tryEnumConversion(
+ mixed $value,
+ \ReflectionParameter|\ReflectionProperty|null $reflection,
+ ): mixed
{
if ($value !== null
&& $reflection
@@ -316,9 +339,12 @@ public static function tryEnumConversion(mixed $value, $reflection): mixed
}
- /** @internal */
+ /**
+ * @internal
+ * @return string[]
+ */
public static function getSupportedImages(): array
{
- return array_values(array_map(fn($type) => Image::typeToMimeType($type), Image::getSupportedTypes()));
+ return array_values(array_map(Image::typeToMimeType(...), Image::getSupportedTypes()));
}
}
diff --git a/src/Forms/Rendering/DefaultFormRenderer.php b/src/Forms/Rendering/DefaultFormRenderer.php
index ef02937a4..da8957005 100644
--- a/src/Forms/Rendering/DefaultFormRenderer.php
+++ b/src/Forms/Rendering/DefaultFormRenderer.php
@@ -56,6 +56,7 @@ class DefaultFormRenderer implements Nette\Forms\FormRenderer
* \---
* \---
* \--
+ * @var array>
*/
public array $wrappers = [
'form' => [
@@ -220,6 +221,7 @@ public function renderErrors(?Nette\Forms\Control $control = null, bool $own = t
}
+ /** @param list $errors */
private function doRenderErrors(array $errors, bool $control): string
{
if (!$errors) {
diff --git a/src/Forms/Rule.php b/src/Forms/Rule.php
index 361405f50..e6dc0eb6d 100644
--- a/src/Forms/Rule.php
+++ b/src/Forms/Rule.php
@@ -20,6 +20,8 @@
final class Rule
{
public Control $control;
+
+ /** @var (callable(Control): bool)|string */
public mixed $validator;
public mixed $arg = null;
public bool $isNegative = false;
diff --git a/src/Forms/Rules.php b/src/Forms/Rules.php
index f2b9def23..4fc3bf261 100644
--- a/src/Forms/Rules.php
+++ b/src/Forms/Rules.php
@@ -30,13 +30,14 @@ final class Rules implements \IteratorAggregate
/** @var Rule[] */
private array $rules = [];
private Rules $parent;
+
+ /** @var array */
private array $toggles = [];
- private Control $control;
- public function __construct(Control $control)
- {
- $this->control = $control;
+ public function __construct(
+ private readonly Control $control,
+ ) {
}
@@ -66,6 +67,7 @@ public function isRequired(): bool
/**
* Adds a validation rule for the current control.
+ * @param (callable(Control): bool)|string $validator
*/
public function addRule(
callable|string $validator,
@@ -95,6 +97,7 @@ public function addRule(
/**
* Removes a validation rule for the current control.
+ * @param (callable(Control): bool)|string $validator
*/
public function removeRule(callable|string $validator): static
{
@@ -114,8 +117,9 @@ public function removeRule(callable|string $validator): static
/**
* Adds a validation condition and returns new branch.
+ * @param (callable(Control): bool)|string|bool $validator
*/
- public function addCondition($validator, $arg = null): static
+ public function addCondition(callable|string|bool $validator, mixed $arg = null): static
{
if ($validator === Form::Valid || $validator === ~Form::Valid) {
throw new Nette\InvalidArgumentException('You cannot use Form::Valid in the addCondition method.');
@@ -130,8 +134,9 @@ public function addCondition($validator, $arg = null): static
/**
* Adds a validation condition on specified control a returns new branch.
+ * @param (callable(Control): bool)|string $validator
*/
- public function addConditionOn(Control $control, $validator, $arg = null): static
+ public function addConditionOn(Control $control, callable|string $validator, mixed $arg = null): static
{
$rule = new Rule;
$rule->control = $control;
@@ -176,6 +181,7 @@ public function endCondition(): static
/**
* Adds a filter callback.
+ * @param callable(mixed): mixed $filter
*/
public function addFilter(callable $filter): static
{
@@ -199,13 +205,18 @@ public function toggle(string $id, bool $hide = true): static
}
+ /** @return array */
public function getToggles(bool $actual = false): array
{
return $actual ? $this->getToggleStates() : $this->toggles;
}
- /** @internal */
+ /**
+ * @internal
+ * @param array $toggles
+ * @return array
+ */
public function getToggleStates(array $toggles = [], bool $success = true, ?bool $emptyOptional = null): array
{
foreach ($this->toggles as $id => $hide) {
@@ -240,7 +251,7 @@ public function validate(?bool $emptyOptional = null): bool
continue;
}
- $success = $this->validateRule($rule);
+ $success = self::validateRule($rule);
if (
$success
&& $rule->branch
@@ -284,7 +295,7 @@ public static function validateRule(Rule $rule): bool
/**
* Iterates over complete ruleset.
- * @return \ArrayIterator
+ * @return \Iterator
*/
public function getIterator(): \Iterator
{
@@ -327,7 +338,7 @@ private function adjustOperation(Rule $rule): void
$rule->arg = Helpers::getSupportedImages();
}
- if (!is_callable($this->getCallback($rule))) {
+ if (!is_callable(self::getCallback($rule))) {
$validator = is_scalar($rule->validator)
? " '$rule->validator'"
: '';
@@ -336,10 +347,10 @@ private function adjustOperation(Rule $rule): void
}
- private static function getCallback(Rule $rule)
+ private static function getCallback(Rule $rule): array|callable|string
{
$op = $rule->validator;
- return is_string($op) && strncmp($op, ':', 1) === 0
+ return is_string($op) && str_starts_with($op, ':')
? [Validator::class, 'validate' . ltrim($op, ':')]
: $op;
}
diff --git a/src/Forms/SubmitterControl.php b/src/Forms/SubmitterControl.php
index e59f87cda..d341b766f 100644
--- a/src/Forms/SubmitterControl.php
+++ b/src/Forms/SubmitterControl.php
@@ -17,6 +17,7 @@ interface SubmitterControl extends Control
{
/**
* Gets the validation scope. Clicking the button validates only the controls within the specified scope.
+ * @return ?list
*/
function getValidationScope(): ?array;
}
diff --git a/src/Forms/Validator.php b/src/Forms/Validator.php
index 929b425c2..24886f25c 100644
--- a/src/Forms/Validator.php
+++ b/src/Forms/Validator.php
@@ -12,6 +12,7 @@
use Nette;
use Nette\Utils\Strings;
use Nette\Utils\Validators;
+use Stringable;
use function array_map, count, explode, in_array, is_array, is_float, is_int, is_object, is_string, preg_replace, preg_replace_callback, rtrim, str_replace, strtolower;
use const UPLOAD_ERR_INI_SIZE;
@@ -23,6 +24,7 @@ final class Validator
{
use Nette\StaticClass;
+ /** @var array */
public static array $messages = [
Controls\CsrfProtection::Protection => 'Your session has expired. Please return to the home page and try again.',
Form::Equal => 'Please enter %s.',
@@ -57,6 +59,9 @@ public static function formatMessage(Rule $rule, bool $withValue = true): string
if ($message instanceof Nette\HtmlStringable) {
return $message;
+ } elseif ($message instanceof Stringable) {
+ return (string)$message;
+
} elseif ($message === null && is_string($rule->validator) && isset(static::$messages[$rule->validator])) {
$message = static::$messages[$rule->validator];
@@ -114,7 +119,7 @@ public static function formatMessage(Rule $rule, bool $withValue = true): string
/**
* Is control's value equal with second parameter?
*/
- public static function validateEqual(Control $control, $arg): bool
+ public static function validateEqual(Control $control, mixed $arg): bool
{
$value = $control->getValue();
$values = is_array($value) ? $value : [$value];
@@ -141,7 +146,7 @@ public static function validateEqual(Control $control, $arg): bool
/**
* Is control's value not equal with second parameter?
*/
- public static function validateNotEqual(Control $control, $arg): bool
+ public static function validateNotEqual(Control $control, mixed $arg): bool
{
return !static::validateEqual($control, $arg);
}
@@ -185,6 +190,7 @@ public static function validateValid(Controls\BaseControl $control): bool
/**
* Is a control's value number in specified range?
+ * @param array{int|float|string|\DateTimeInterface|null, int|float|string|\DateTimeInterface|null} $range
*/
public static function validateRange(Control $control, array $range): bool
{
@@ -199,7 +205,7 @@ public static function validateRange(Control $control, array $range): bool
/**
* Is a control's value number greater than or equal to the specified minimum?
*/
- public static function validateMin(Control $control, $minimum): bool
+ public static function validateMin(Control $control, int|float|string|\DateTimeInterface $minimum): bool
{
return Validators::isInRange($control->getValue(), [$minimum === '' ? null : $minimum, null]);
}
@@ -208,7 +214,7 @@ public static function validateMin(Control $control, $minimum): bool
/**
* Is a control's value number less than or equal to the specified maximum?
*/
- public static function validateMax(Control $control, $maximum): bool
+ public static function validateMax(Control $control, int|float|string|\DateTimeInterface $maximum): bool
{
return Validators::isInRange($control->getValue(), [null, $maximum === '' ? null : $maximum]);
}
@@ -216,6 +222,7 @@ public static function validateMax(Control $control, $maximum): bool
/**
* Count/length validator. Range is array, min and max length pair.
+ * @param array{?int, ?int}|int $range
*/
public static function validateLength(Control $control, array|int $range): bool
{
@@ -231,7 +238,7 @@ public static function validateLength(Control $control, array|int $range): bool
/**
* Has control's value minimal count/length?
*/
- public static function validateMinLength(Control $control, $length): bool
+ public static function validateMinLength(Control $control, int $length): bool
{
return static::validateLength($control, [$length, null]);
}
@@ -240,7 +247,7 @@ public static function validateMinLength(Control $control, $length): bool
/**
* Is control's value count/length in limit?
*/
- public static function validateMaxLength(Control $control, $length): bool
+ public static function validateMaxLength(Control $control, int $length): bool
{
return static::validateLength($control, [null, $length]);
}
@@ -358,7 +365,7 @@ public static function validateFloat(Control $control): bool
/**
* Is file size in limit?
*/
- public static function validateFileSize(Controls\UploadControl $control, $limit): bool
+ public static function validateFileSize(Controls\UploadControl $control, int $limit): bool
{
foreach (static::toArray($control->getValue()) as $file) {
if ($file->getSize() > $limit || $file->getError() === UPLOAD_ERR_INI_SIZE) {
@@ -403,7 +410,8 @@ public static function validateImage(Controls\UploadControl $control): bool
}
- private static function toArray($value): array
+ /** @return mixed[] */
+ private static function toArray(mixed $value): array
{
return is_object($value) ? [$value] : (array) $value;
}
diff --git a/tests/Forms/BaseControl.extensionMethod.phpt b/tests/Controls/BaseControl.extensionMethod.phpt
similarity index 100%
rename from tests/Forms/BaseControl.extensionMethod.phpt
rename to tests/Controls/BaseControl.extensionMethod.phpt
diff --git a/tests/Forms/Controls.BaseControl.phpt b/tests/Controls/BaseControl.phpt
similarity index 94%
rename from tests/Forms/Controls.BaseControl.phpt
rename to tests/Controls/BaseControl.phpt
index faab75123..7a0cfd4a0 100644
--- a/tests/Forms/Controls.BaseControl.phpt
+++ b/tests/Controls/BaseControl.phpt
@@ -15,6 +15,8 @@ require __DIR__ . '/../bootstrap.php';
setUp(function () {
+ $_SERVER['REQUEST_METHOD'] = 'POST';
+ $_POST = $_FILES = [];
ob_start();
Form::initialize(true);
});
@@ -22,6 +24,7 @@ setUp(function () {
test('error handling for required text input', function () {
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addText('text')
->setRequired('error');
@@ -42,6 +45,7 @@ test('error handling for required text input', function () {
test('validation methods for text input values', function () {
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addText('text');
$input->setValue(123);
@@ -79,6 +83,7 @@ test('validation methods for text input values', function () {
test('multiSelect validation and length checks', function () {
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addMultiSelect('select', null, ['a', 'b', 'c', 'd']);
$input->setValue([1, 2, 3]);
@@ -102,6 +107,7 @@ test('multiSelect validation and length checks', function () {
test('custom HTML ID for text input', function () {
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addText('text')->setHtmlId('myId');
Assert::same('', (string) $input->getControl());
@@ -110,6 +116,7 @@ test('custom HTML ID for text input', function () {
test('input name conflict resolution', function () {
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addText('submit');
Assert::same('', (string) $input->getControl());
@@ -117,7 +124,10 @@ test('input name conflict resolution', function () {
test('disabled input retains default value', function () {
+ $_SERVER['REQUEST_METHOD'] = 'GET';
+
$form = new Form;
+ $form->allowCrossOrigin();
$form->addText('disabled')
->setDisabled()
->setDefaultValue('default');
@@ -129,11 +139,10 @@ test('disabled input retains default value', function () {
test('disabled inputs ignore POST data', function () {
- $_SERVER['REQUEST_METHOD'] = 'POST';
$_POST = ['disabled' => 'submitted value'];
- $_COOKIE[Nette\Http\Helpers::StrictCookieName] = '1';
$form = new Form;
+ $form->allowCrossOrigin();
$form->addText('disabled')
->setDisabled()
->setDefaultValue('default');
@@ -158,6 +167,7 @@ test('disabled inputs ignore POST data', function () {
test('translator integration for labels and errors', function () {
$form = new Form;
+ $form->allowCrossOrigin();
$form->setTranslator(new class implements Nette\Localization\ITranslator {
public function translate($s, ...$parameters): string
{
@@ -192,7 +202,9 @@ test('translator integration for labels and errors', function () {
test('dynamic HTML name attribute handling', function () {
$_POST = ['b' => '123', 'send' => ''];
+
$form = new Form;
+ $form->allowCrossOrigin();
$form->addSubmit('send', 'Send');
$input = $form->addText('a');
diff --git a/tests/Forms/Controls.Button.loadData.phpt b/tests/Controls/Button.loadData.phpt
similarity index 90%
rename from tests/Forms/Controls.Button.loadData.phpt
rename to tests/Controls/Button.loadData.phpt
index 8a8ef0d93..060e5d159 100644
--- a/tests/Forms/Controls.Button.loadData.phpt
+++ b/tests/Controls/Button.loadData.phpt
@@ -16,7 +16,6 @@ require __DIR__ . '/../bootstrap.php';
setUp(function () {
$_SERVER['REQUEST_METHOD'] = 'POST';
$_POST = $_FILES = [];
- $_COOKIE[Nette\Http\Helpers::StrictCookieName] = '1';
ob_start();
Form::initialize(true);
});
@@ -28,6 +27,7 @@ test('submit button captures POST value', function () {
];
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addSubmit('button');
Assert::true($input->isFilled());
Assert::same('x', $input->getValue());
@@ -41,11 +41,13 @@ test('submit button with empty and zero values', function () {
];
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addSubmit('button1');
Assert::true($input->isFilled());
Assert::same('', $input->getValue());
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addSubmit('button2');
Assert::true($input->isFilled());
Assert::same('0', $input->getValue());
@@ -54,6 +56,7 @@ test('submit button with empty and zero values', function () {
test('unsubmitted button state', function () {
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addSubmit('button');
Assert::false($input->isFilled());
Assert::null($input->getValue());
@@ -66,6 +69,7 @@ test('handling malformed POST data for button', function () {
];
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addSubmit('malformed');
Assert::false($input->isFilled());
Assert::null($input->getValue());
diff --git a/tests/Forms/Controls.Button.render.phpt b/tests/Controls/Button.render.phpt
similarity index 100%
rename from tests/Forms/Controls.Button.render.phpt
rename to tests/Controls/Button.render.phpt
diff --git a/tests/Forms/Controls.Checkbox.loadData.phpt b/tests/Controls/Checkbox.loadData.phpt
similarity index 93%
rename from tests/Forms/Controls.Checkbox.loadData.phpt
rename to tests/Controls/Checkbox.loadData.phpt
index 300d321cd..2fba8df13 100644
--- a/tests/Forms/Controls.Checkbox.loadData.phpt
+++ b/tests/Controls/Checkbox.loadData.phpt
@@ -16,7 +16,6 @@ require __DIR__ . '/../bootstrap.php';
setUp(function () {
$_SERVER['REQUEST_METHOD'] = 'POST';
$_POST = $_FILES = [];
- $_COOKIE[Nette\Http\Helpers::StrictCookieName] = '1';
ob_start();
Form::initialize(true);
});
@@ -29,6 +28,7 @@ test('checkbox on/off states from POST', function () {
];
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addCheckbox('off');
Assert::false($input->getValue());
@@ -45,6 +45,7 @@ test('malformed checkbox array handling', function () {
$_POST = ['malformed' => ['']];
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addCheckbox('malformed');
Assert::false($input->getValue());
@@ -54,6 +55,7 @@ test('malformed checkbox array handling', function () {
testException('array value exception for checkbox', function () {
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addCheckbox('checkbox');
$input->setValue([]);
}, Nette\InvalidArgumentException::class, "Value must be scalar or null, array given in field 'checkbox'.");
diff --git a/tests/Forms/Controls.Checkbox.render.phpt b/tests/Controls/Checkbox.render.phpt
similarity index 100%
rename from tests/Forms/Controls.Checkbox.render.phpt
rename to tests/Controls/Checkbox.render.phpt
diff --git a/tests/Forms/Controls.CheckboxList.loadData.phpt b/tests/Controls/CheckboxList.loadData.phpt
similarity index 93%
rename from tests/Forms/Controls.CheckboxList.loadData.phpt
rename to tests/Controls/CheckboxList.loadData.phpt
index a819e071e..c00bee227 100644
--- a/tests/Forms/Controls.CheckboxList.loadData.phpt
+++ b/tests/Controls/CheckboxList.loadData.phpt
@@ -18,7 +18,6 @@ require __DIR__ . '/../bootstrap.php';
setUp(function () {
$_SERVER['REQUEST_METHOD'] = 'POST';
$_POST = $_FILES = [];
- $_COOKIE[Nette\Http\Helpers::StrictCookieName] = '1';
ob_start();
Form::initialize(true);
});
@@ -36,6 +35,7 @@ test('empty checkbox list submission', function () use ($series) {
$_POST = [];
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addCheckboxList('list', null, $series);
Assert::true($form->isValid());
@@ -49,6 +49,7 @@ test('multiple valid selections', function () use ($series) {
$_POST = ['list' => 'red-dwarf,0'];
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addCheckboxList('list', null, $series);
Assert::true($form->isValid());
@@ -62,6 +63,7 @@ test('filtering invalid selections', function () use ($series) {
$_POST = ['multi' => ['red-dwarf', 'unknown', '0']];
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addCheckboxList('multi', null, $series);
Assert::true($form->isValid());
@@ -76,6 +78,7 @@ test('empty string as valid selection', function () use ($series) {
$_POST = ['empty' => ['']];
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addCheckboxList('empty', null, $series);
Assert::true($form->isValid());
@@ -87,6 +90,7 @@ test('empty string as valid selection', function () use ($series) {
test('missing checkbox list data', function () use ($series) {
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addCheckboxList('missing', null, $series);
Assert::true($form->isValid());
@@ -100,6 +104,7 @@ test('disabled checkbox list ignores input', function () use ($series) {
$_POST = ['disabled' => 'red-dwarf'];
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addCheckboxList('disabled', null, $series)
->setDisabled();
@@ -112,6 +117,7 @@ test('nested malformed array input', function () use ($series) {
$_POST = ['malformed' => [['']]];
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addCheckboxList('malformed', null, $series);
Assert::true($form->isValid());
@@ -125,6 +131,7 @@ test('selection length validation', function () use ($series) {
$_POST = ['multi' => ['red-dwarf', 'unknown', '0']];
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addCheckboxList('multi', null, $series);
Assert::true(Validator::validateLength($input, 2));
@@ -138,6 +145,7 @@ test('equality validation with mixed values', function () use ($series) {
$_POST = ['multi' => ['red-dwarf', 'unknown', '0']];
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addCheckboxList('multi', null, $series);
Assert::true(Validator::validateEqual($input, ['red-dwarf', 0]));
@@ -152,6 +160,7 @@ test('empty list equality checks', function () use ($series) {
$_POST = [];
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addCheckboxList('multi', null, $series);
Assert::false(Validator::validateEqual($input, ['red-dwarf', 0]));
@@ -164,6 +173,7 @@ test('empty list equality checks', function () use ($series) {
testException('invalid selection exception', function () use ($series) {
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addCheckboxList('list', null, $series);
$input->setValue(null);
$input->setValue('unknown');
@@ -172,6 +182,7 @@ testException('invalid selection exception', function () use ($series) {
test('dateTime object as selection key', function () {
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addCheckboxList('list', null, ['2013-07-05 00:00:00' => 1])
->setValue([new DateTime('2013-07-05')]);
@@ -181,6 +192,7 @@ test('dateTime object as selection key', function () {
test('dateTime items without keys', function () {
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addCheckboxList('list')
->setItems([new DateTime('2013-07-05')], useKeys: false)
->setValue('2013-07-05 00:00:00');
@@ -193,6 +205,7 @@ test('disabled item filtering', function () use ($series) {
$_POST = ['list' => ['red-dwarf', '0']];
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addCheckboxList('list', null, $series)
->setDisabled(['red-dwarf']);
diff --git a/tests/Forms/Controls.CheckboxList.render.phpt b/tests/Controls/CheckboxList.render.phpt
similarity index 100%
rename from tests/Forms/Controls.CheckboxList.render.phpt
rename to tests/Controls/CheckboxList.render.phpt
diff --git a/tests/Forms/Controls.ChoiceControl.loadData.phpt b/tests/Controls/ChoiceControl.loadData.phpt
similarity index 93%
rename from tests/Forms/Controls.ChoiceControl.loadData.phpt
rename to tests/Controls/ChoiceControl.loadData.phpt
index c525e2164..9ae815426 100644
--- a/tests/Forms/Controls.ChoiceControl.loadData.phpt
+++ b/tests/Controls/ChoiceControl.loadData.phpt
@@ -22,7 +22,6 @@ class ChoiceControl extends Nette\Forms\Controls\ChoiceControl
setUp(function () {
$_SERVER['REQUEST_METHOD'] = 'POST';
$_POST = $_FILES = [];
- $_COOKIE[Nette\Http\Helpers::StrictCookieName] = '1';
ob_start();
Form::initialize(true);
});
@@ -40,6 +39,7 @@ test('valid selection handling', function () use ($series) {
$_POST = ['select' => 'red-dwarf'];
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form['select'] = new ChoiceControl(null, $series);
Assert::true($form->isValid());
@@ -53,6 +53,7 @@ test('invalid selection ignored', function () use ($series) {
$_POST = ['select' => 'days-of-our-lives'];
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form['select'] = new ChoiceControl(null, $series);
Assert::true($form->isValid());
@@ -66,6 +67,7 @@ test('zero value as valid key', function () use ($series) {
$_POST = ['zero' => '0'];
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form['zero'] = new ChoiceControl(null, $series);
Assert::true($form->isValid());
@@ -80,6 +82,7 @@ test('empty string as valid key', function () use ($series) {
$_POST = ['empty' => ''];
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form['empty'] = new ChoiceControl(null, $series);
Assert::true($form->isValid());
@@ -91,6 +94,7 @@ test('empty string as valid key', function () use ($series) {
test('missing input results in null', function () use ($series) {
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form['missing'] = new ChoiceControl(null, $series);
Assert::true($form->isValid());
@@ -104,6 +108,7 @@ test('disabled input ignores submission', function () use ($series) {
$_POST = ['disabled' => 'red-dwarf'];
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form['disabled'] = new ChoiceControl(null, $series);
$input->setDisabled();
@@ -117,6 +122,7 @@ test('malformed array input handling', function () use ($series) {
$_POST = ['malformed' => ['']];
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form['malformed'] = new ChoiceControl(null, $series);
Assert::true($form->isValid());
@@ -130,6 +136,7 @@ test('using keys as items without labels', function () use ($series) {
$_POST = ['select' => 'red-dwarf'];
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form['select'] = new ChoiceControl;
$input->setItems(array_keys($series), useKeys: false);
Assert::same([
@@ -148,6 +155,7 @@ test('using keys as items without labels', function () use ($series) {
testException('exception on invalid value', function () use ($series) {
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form['select'] = new ChoiceControl(null, $series);
$input->setValue('unknown');
}, Nette\InvalidArgumentException::class, "Value 'unknown' is out of allowed set ['red-dwarf', 'the-simpsons', 0, ''] in field 'select'.");
@@ -155,6 +163,7 @@ testException('exception on invalid value', function () use ($series) {
test('invalid value ignored with checkDefaultValue', function () use ($series) {
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form['select'] = new ChoiceControl(null, $series);
$input->checkDefaultValue(false);
$input->setValue('unknown');
@@ -164,6 +173,7 @@ test('invalid value ignored with checkDefaultValue', function () use ($series) {
test('dateTime object as value', function () {
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form['select'] = new ChoiceControl(null, ['2013-07-05 00:00:00' => 1]);
$input->setValue(new DateTime('2013-07-05'));
@@ -173,6 +183,7 @@ test('dateTime object as value', function () {
test('dateTime items without keys', function () {
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form['select'] = new ChoiceControl;
$input->setItems([new DateTime('2013-07-05')], useKeys: false)
->setValue(new DateTime('2013-07-05'));
@@ -185,6 +196,7 @@ test('disabled items ignored', function () use ($series) {
$_POST = ['select' => 'red-dwarf'];
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form['select'] = new ChoiceControl(null, $series);
$input->setDisabled(['red-dwarf']);
@@ -202,6 +214,7 @@ test('items with null labels', function () {
$_POST = ['select' => '1'];
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form['select'] = new ChoiceControl(null);
$input->setItems([
1 => null,
diff --git a/tests/Forms/Controls.ColorPicker.loadData.phpt b/tests/Controls/ColorPicker.loadData.phpt
similarity index 92%
rename from tests/Forms/Controls.ColorPicker.loadData.phpt
rename to tests/Controls/ColorPicker.loadData.phpt
index be14ef5d3..121329023 100644
--- a/tests/Forms/Controls.ColorPicker.loadData.phpt
+++ b/tests/Controls/ColorPicker.loadData.phpt
@@ -15,7 +15,6 @@ require __DIR__ . '/../bootstrap.php';
setUp(function () {
$_SERVER['REQUEST_METHOD'] = 'POST';
$_POST = $_FILES = [];
- $_COOKIE[Nette\Http\Helpers::StrictCookieName] = '1';
ob_start();
Form::initialize(true);
});
@@ -25,6 +24,7 @@ test('default color for empty input', function () {
$_POST = ['color' => ''];
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addColor('color');
Assert::same('#000000', $input->getValue());
@@ -36,6 +36,7 @@ test('invalid color format handling', function () {
$_POST = ['color' => '#abc'];
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addColor('color');
Assert::same('#000000', $input->getValue());
@@ -47,6 +48,7 @@ test('valid color value handling', function () {
$_POST = ['color' => '#1020aa'];
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addColor('color');
Assert::same('#1020aa', $input->getValue());
diff --git a/tests/Forms/Controls.ColorPicker.render.phpt b/tests/Controls/ColorPicker.render.phpt
similarity index 100%
rename from tests/Forms/Controls.ColorPicker.render.phpt
rename to tests/Controls/ColorPicker.render.phpt
diff --git a/tests/Forms/Controls.translate().phpt b/tests/Controls/Controls.translate().phpt
similarity index 100%
rename from tests/Forms/Controls.translate().phpt
rename to tests/Controls/Controls.translate().phpt
diff --git a/tests/Forms/Controls.CsrfProtection.breachAttack.phpt b/tests/Controls/CsrfProtection.breachAttack.phpt
similarity index 100%
rename from tests/Forms/Controls.CsrfProtection.breachAttack.phpt
rename to tests/Controls/CsrfProtection.breachAttack.phpt
diff --git a/tests/Forms/Controls.CsrfProtection.phpt b/tests/Controls/CsrfProtection.phpt
similarity index 96%
rename from tests/Forms/Controls.CsrfProtection.phpt
rename to tests/Controls/CsrfProtection.phpt
index 513a0b366..782cea267 100644
--- a/tests/Forms/Controls.CsrfProtection.phpt
+++ b/tests/Controls/CsrfProtection.phpt
@@ -15,10 +15,9 @@ require __DIR__ . '/../bootstrap.php';
$_SERVER['REQUEST_METHOD'] = 'POST';
-$_COOKIE[Nette\Http\Helpers::StrictCookieName] = '1';
-
$form = new Form;
+$form->allowCrossOrigin();
$input = $form->addProtection('Security token did not match. Possible CSRF attack.');
@@ -49,6 +48,7 @@ Assert::false(CsrfProtection::validateCsrf($input));
// protection is always the first
$form = new Form;
+$form->allowCrossOrigin();
$form->addText('text');
$form->addProtection();
Assert::same([
diff --git a/tests/Forms/Controls.DateTimeControl.format.phpt b/tests/Controls/DateTimeControl.format.phpt
similarity index 100%
rename from tests/Forms/Controls.DateTimeControl.format.phpt
rename to tests/Controls/DateTimeControl.format.phpt
diff --git a/tests/Forms/Controls.DateTimeControl.loadData.phpt b/tests/Controls/DateTimeControl.loadData.phpt
similarity index 89%
rename from tests/Forms/Controls.DateTimeControl.loadData.phpt
rename to tests/Controls/DateTimeControl.loadData.phpt
index 6bb1b59bd..c969c4c38 100644
--- a/tests/Forms/Controls.DateTimeControl.loadData.phpt
+++ b/tests/Controls/DateTimeControl.loadData.phpt
@@ -16,7 +16,6 @@ require __DIR__ . '/../bootstrap.php';
setUp(function () {
$_SERVER['REQUEST_METHOD'] = 'POST';
$_POST = $_FILES = [];
- $_COOKIE[Nette\Http\Helpers::StrictCookieName] = '1';
ob_start();
Form::initialize(true);
});
@@ -24,6 +23,7 @@ setUp(function () {
test('unknown date input handling', function () {
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addDate('unknown');
Assert::null($input->getValue());
Assert::false($input->isFilled());
@@ -33,6 +33,7 @@ test('unknown date input handling', function () {
test('malformed date input', function () {
$_POST = ['malformed' => ['']];
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addDate('malformed');
Assert::null($input->getValue());
Assert::false($input->isFilled());
@@ -42,6 +43,7 @@ test('malformed date input', function () {
test('invalid text date input', function () {
$_POST = ['text' => 'invalid'];
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addDate('date');
Assert::null($input->getValue());
Assert::false($input->isFilled());
@@ -51,6 +53,7 @@ test('invalid text date input', function () {
test('invalid date string', function () {
$_POST = ['date' => '2023-13-22'];
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addDate('date');
Assert::null($input->getValue());
Assert::false($input->isFilled());
@@ -60,6 +63,7 @@ test('invalid date string', function () {
test('invalid time value', function () {
$_POST = ['time' => '10:60'];
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addTime('time');
Assert::null($input->getValue());
Assert::false($input->isFilled());
@@ -69,6 +73,7 @@ test('invalid time value', function () {
test('empty date input', function () {
$_POST = ['date' => ''];
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addDate('date');
Assert::null($input->getValue());
Assert::false($input->isFilled());
@@ -78,6 +83,7 @@ test('empty date input', function () {
test('empty time input', function () {
$_POST = ['time' => ''];
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addTime('time');
Assert::null($input->getValue());
Assert::false($input->isFilled());
@@ -87,6 +93,7 @@ test('empty time input', function () {
test('empty datetime input', function () {
$_POST = ['date' => ''];
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addDateTime('date');
Assert::null($input->getValue());
Assert::false($input->isFilled());
@@ -96,6 +103,7 @@ test('empty datetime input', function () {
test('valid date submission', function () {
$_POST = ['date' => '2023-10-22'];
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addDate('date');
Assert::equal(new DateTimeImmutable('2023-10-22 00:00'), $input->getValue());
Assert::true($input->isFilled());
@@ -105,6 +113,7 @@ test('valid date submission', function () {
test('time without seconds', function () {
$_POST = ['time' => '10:22:33.44'];
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addTime('time');
Assert::equal(new DateTimeImmutable('0001-01-01 10:22'), $input->getValue());
Assert::true($input->isFilled());
@@ -114,6 +123,7 @@ test('time without seconds', function () {
test('time with seconds', function () {
$_POST = ['time' => '10:22:33.44'];
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addTime('time', withSeconds: true);
Assert::equal(new DateTimeImmutable('0001-01-01 10:22:33'), $input->getValue());
Assert::true($input->isFilled());
@@ -123,6 +133,7 @@ test('time with seconds', function () {
test('datetime without seconds', function () {
$_POST = ['date' => '2023-10-22T10:23:11.123'];
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addDateTime('date');
Assert::equal(new DateTimeImmutable('2023-10-22 10:23:00'), $input->getValue());
Assert::true($input->isFilled());
@@ -132,6 +143,7 @@ test('datetime without seconds', function () {
test('datetime with seconds', function () {
$_POST = ['date' => '2023-10-22T10:23:11.123'];
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addDateTime('date', withSeconds: true);
Assert::equal(new DateTimeImmutable('2023-10-22 10:23:11'), $input->getValue());
Assert::true($input->isFilled());
@@ -141,6 +153,7 @@ test('datetime with seconds', function () {
test('alternative date format parsing', function () {
$_POST = ['date' => '22.10.2023'];
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addDate('date');
Assert::equal(new DateTimeImmutable('2023-10-22 00:00'), $input->getValue());
Assert::true($input->isFilled());
diff --git a/tests/Forms/Controls.DateTimeControl.render.phpt b/tests/Controls/DateTimeControl.render.phpt
similarity index 100%
rename from tests/Forms/Controls.DateTimeControl.render.phpt
rename to tests/Controls/DateTimeControl.render.phpt
diff --git a/tests/Forms/Controls.DateTimeControl.value.phpt b/tests/Controls/DateTimeControl.value.phpt
similarity index 100%
rename from tests/Forms/Controls.DateTimeControl.value.phpt
rename to tests/Controls/DateTimeControl.value.phpt
diff --git a/tests/Forms/Controls.HiddenField.loadData.phpt b/tests/Controls/HiddenField.loadData.phpt
similarity index 90%
rename from tests/Forms/Controls.HiddenField.loadData.phpt
rename to tests/Controls/HiddenField.loadData.phpt
index fd671c376..f7214f986 100644
--- a/tests/Forms/Controls.HiddenField.loadData.phpt
+++ b/tests/Controls/HiddenField.loadData.phpt
@@ -16,7 +16,6 @@ require __DIR__ . '/../bootstrap.php';
setUp(function () {
$_SERVER['REQUEST_METHOD'] = 'POST';
$_POST = $_FILES = [];
- $_COOKIE[Nette\Http\Helpers::StrictCookieName] = '1';
ob_start();
Form::initialize(true);
});
@@ -25,6 +24,7 @@ setUp(function () {
test('input normalization', function () {
$_POST = ['text' => " a\r b \n c "];
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addHidden('text');
Assert::same(" a\n b \n c ", $input->getValue());
Assert::true($input->isFilled());
@@ -33,6 +33,7 @@ test('input normalization', function () {
test('missing POST data handling', function () {
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addHidden('unknown');
Assert::same('', $input->getValue());
Assert::false($input->isFilled());
@@ -42,6 +43,7 @@ test('missing POST data handling', function () {
test('malformed array input', function () {
$_POST = ['malformed' => ['']];
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addHidden('malformed');
Assert::same('', $input->getValue());
Assert::false($input->isFilled());
@@ -50,6 +52,7 @@ test('malformed array input', function () {
test('error propagation to form', function () {
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addHidden('hidden');
$input->addError('error');
Assert::same([], $input->getErrors());
@@ -59,6 +62,7 @@ test('error propagation to form', function () {
testException('array value exception', function () {
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addHidden('hidden');
$input->setValue([]);
}, Nette\InvalidArgumentException::class, "Value must be scalar or null, array given in field 'hidden'.");
@@ -66,6 +70,7 @@ testException('array value exception', function () {
test('object value retention', function () {
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addHidden('hidden')
->setValue($data = new Nette\Utils\DateTime('2013-07-05'));
@@ -77,6 +82,7 @@ test('filter application on validation', function () {
$date = new Nette\Utils\DateTime('2013-07-05');
$_POST = ['text' => (string) $date];
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addHidden('text');
$input->addFilter(fn($value) => $value ? new Nette\Utils\DateTime($value) : $value);
@@ -89,6 +95,7 @@ test('filter application on validation', function () {
test('integer validation and conversion', function () {
$_POST = ['text' => '10'];
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addHidden('text');
$input->addRule($form::Integer);
@@ -100,6 +107,7 @@ test('integer validation and conversion', function () {
test('persistent value handling', function () {
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form['hidden'] = new Nette\Forms\Controls\HiddenField('persistent');
$input->setValue('other');
@@ -109,6 +117,7 @@ test('persistent value handling', function () {
test('nullable with empty string', function () {
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addHidden('hidden');
$input->setValue('');
$input->setNullable();
@@ -118,6 +127,7 @@ test('nullable with empty string', function () {
test('nullable with null', function () {
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addHidden('hidden');
$input->setValue(null);
$input->setNullable();
diff --git a/tests/Forms/Controls.HiddenField.render.phpt b/tests/Controls/HiddenField.render.phpt
similarity index 100%
rename from tests/Forms/Controls.HiddenField.render.phpt
rename to tests/Controls/HiddenField.render.phpt
diff --git a/tests/Forms/Controls.ImageButton.loadData.phpt b/tests/Controls/ImageButton.loadData.phpt
similarity index 93%
rename from tests/Forms/Controls.ImageButton.loadData.phpt
rename to tests/Controls/ImageButton.loadData.phpt
index ab320a579..a4776dc81 100644
--- a/tests/Forms/Controls.ImageButton.loadData.phpt
+++ b/tests/Controls/ImageButton.loadData.phpt
@@ -16,7 +16,6 @@ require __DIR__ . '/../bootstrap.php';
setUp(function () {
$_SERVER['REQUEST_METHOD'] = 'POST';
$_POST = $_FILES = [];
- $_COOKIE[Nette\Http\Helpers::StrictCookieName] = '1';
ob_start();
Form::initialize(true);
});
@@ -31,6 +30,7 @@ test('image button captures coordinates', function () {
];
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addImageButton('image');
Assert::true($input->isFilled());
Assert::same([1, 2], $input->getValue());
@@ -42,6 +42,7 @@ test('image button captures coordinates', function () {
test('missing image button data', function () {
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addImageButton('missing');
Assert::false($input->isFilled());
Assert::null($input->getValue());
@@ -55,6 +56,7 @@ test('malformed image button data', function () {
];
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addImageButton('malformed1');
Assert::true($input->isFilled());
Assert::same([1, 0], $input->getValue());
diff --git a/tests/Forms/Controls.ImageButton.render.phpt b/tests/Controls/ImageButton.render.phpt
similarity index 100%
rename from tests/Forms/Controls.ImageButton.render.phpt
rename to tests/Controls/ImageButton.render.phpt
diff --git a/tests/Forms/Controls.MultiChoiceControl.loadData.phpt b/tests/Controls/MultiChoiceControl.loadData.phpt
similarity index 94%
rename from tests/Forms/Controls.MultiChoiceControl.loadData.phpt
rename to tests/Controls/MultiChoiceControl.loadData.phpt
index e2cbb45c2..ff6bee97c 100644
--- a/tests/Forms/Controls.MultiChoiceControl.loadData.phpt
+++ b/tests/Controls/MultiChoiceControl.loadData.phpt
@@ -23,7 +23,6 @@ class MultiChoiceControl extends Nette\Forms\Controls\MultiChoiceControl
setUp(function () {
$_SERVER['REQUEST_METHOD'] = 'POST';
$_POST = $_FILES = [];
- $_COOKIE[Nette\Http\Helpers::StrictCookieName] = '1';
ob_start();
Form::initialize(true);
});
@@ -41,6 +40,7 @@ test('single value treated as empty', function () use ($series) {
$_POST = ['select' => 'red-dwarf'];
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form['select'] = new MultiChoiceControl(null, $series);
Assert::true($form->isValid());
@@ -54,6 +54,7 @@ test('multiple selections with invalid entries', function () use ($series) {
$_POST = ['multi' => ['red-dwarf', 'unknown', '0']];
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form['multi'] = new MultiChoiceControl(null, $series);
Assert::true($form->isValid());
@@ -68,6 +69,7 @@ test('empty string as valid selection', function () use ($series) {
$_POST = ['empty' => ['']];
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form['empty'] = new MultiChoiceControl(null, $series);
Assert::true($form->isValid());
@@ -79,6 +81,7 @@ test('empty string as valid selection', function () use ($series) {
test('missing multi-choice input', function () use ($series) {
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form['missing'] = new MultiChoiceControl(null, $series);
Assert::true($form->isValid());
@@ -92,6 +95,7 @@ test('disabled multi-choice ignores input', function () use ($series) {
$_POST = ['disabled' => 'red-dwarf'];
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form['disabled'] = new MultiChoiceControl(null, $series);
$input->setDisabled();
@@ -104,6 +108,7 @@ test('malformed array input', function () use ($series) {
$_POST = ['malformed' => [['']]];
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form['malformed'] = new MultiChoiceControl(null, $series);
Assert::true($form->isValid());
@@ -117,6 +122,7 @@ test('keys as items without labels', function () use ($series) {
$_POST = ['multi' => ['red-dwarf']];
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form['multi'] = new MultiChoiceControl;
$input->setItems(array_keys($series), useKeys: false);
Assert::same([
@@ -137,6 +143,7 @@ test('selection length validation', function () use ($series) {
$_POST = ['multi' => ['red-dwarf', 'unknown', '0']];
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form['multi'] = new MultiChoiceControl(null, $series);
Assert::true(Validator::validateLength($input, 2));
@@ -150,6 +157,7 @@ test('equality validation with mixed values', function () use ($series) {
$_POST = ['multi' => ['red-dwarf', 'unknown', '0']];
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form['multi'] = new MultiChoiceControl(null, $series);
Assert::true(Validator::validateEqual($input, ['red-dwarf', 0]));
@@ -164,6 +172,7 @@ test('empty submission validation', function () use ($series) {
$_POST = [];
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form['multi'] = new MultiChoiceControl(null, $series);
Assert::false(Validator::validateEqual($input, ['red-dwarf', 0]));
@@ -176,6 +185,7 @@ test('empty submission validation', function () use ($series) {
test('exceptions for invalid values', function () use ($series) {
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form['select'] = new MultiChoiceControl(null, $series);
$input->setValue(null);
@@ -201,6 +211,7 @@ test('exceptions for invalid values', function () use ($series) {
test('invalid values ignored with checkDefaultValue', function () use ($series) {
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form['select'] = new MultiChoiceControl(null, $series);
$input->checkDefaultValue(false);
$input->setValue('unknown');
@@ -222,6 +233,7 @@ test('invalid values ignored with checkDefaultValue', function () use ($series)
test('dateTime object as value', function () {
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form['select'] = new MultiChoiceControl(null, ['2013-07-05 00:00:00' => 1]);
$input->setValue([new DateTime('2013-07-05')]);
@@ -233,6 +245,7 @@ test('disabled items ignored in multi-choice', function () use ($series) {
$_POST = ['select' => ['red-dwarf', '0']];
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form['select'] = new MultiChoiceControl(null, $series);
$input->setDisabled(['red-dwarf']);
@@ -252,6 +265,7 @@ test('order of selected items preserved', function () {
$_POST = ['select' => ['3', '2']];
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form['select'] = new MultiChoiceControl(null, $series);
Assert::same([3, 2], $input->getValue());
diff --git a/tests/Forms/Controls.MultiSelectBox.loadData.phpt b/tests/Controls/MultiSelectBox.loadData.phpt
similarity index 93%
rename from tests/Forms/Controls.MultiSelectBox.loadData.phpt
rename to tests/Controls/MultiSelectBox.loadData.phpt
index 2bdbfb694..35204a2ad 100644
--- a/tests/Forms/Controls.MultiSelectBox.loadData.phpt
+++ b/tests/Controls/MultiSelectBox.loadData.phpt
@@ -18,7 +18,6 @@ require __DIR__ . '/../bootstrap.php';
setUp(function () {
$_SERVER['REQUEST_METHOD'] = 'POST';
$_POST = $_FILES = [];
- $_COOKIE[Nette\Http\Helpers::StrictCookieName] = '1';
ob_start();
Form::initialize(true);
});
@@ -36,6 +35,7 @@ test('selection within grouped items', function () use ($series) {
$_POST = ['multi' => ['red-dwarf']];
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addMultiSelect('multi', null, [
'usa' => [
'the-simpsons' => 'The Simpsons',
@@ -57,6 +57,7 @@ test('single value treated as empty', function () use ($series) {
$_POST = ['select' => 'red-dwarf'];
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addMultiSelect('select', null, $series);
Assert::true($form->isValid());
@@ -70,6 +71,7 @@ test('multiple selections with invalid entries', function () use ($series) {
$_POST = ['multi' => ['red-dwarf', 'unknown', '0']];
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addMultiSelect('multi', null, $series);
Assert::true($form->isValid());
@@ -84,6 +86,7 @@ test('empty string as valid selection', function () use ($series) {
$_POST = ['empty' => ['']];
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addMultiSelect('empty', null, $series);
Assert::true($form->isValid());
@@ -95,6 +98,7 @@ test('empty string as valid selection', function () use ($series) {
test('missing multi-select input', function () use ($series) {
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addMultiSelect('missing', null, $series);
Assert::true($form->isValid());
@@ -108,6 +112,7 @@ test('disabled multi-select ignores input', function () use ($series) {
$_POST = ['disabled' => 'red-dwarf'];
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addMultiSelect('disabled', null, $series)
->setDisabled();
@@ -120,6 +125,7 @@ test('malformed array input', function () use ($series) {
$_POST = ['malformed' => [['']]];
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addMultiSelect('malformed', null, $series);
Assert::true($form->isValid());
@@ -133,6 +139,7 @@ test('selection length validation', function () use ($series) {
$_POST = ['multi' => ['red-dwarf', 'unknown', '0']];
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addMultiSelect('multi', null, $series);
Assert::true(Validator::validateLength($input, 2));
@@ -146,6 +153,7 @@ test('equality validation with mixed values', function () use ($series) {
$_POST = ['multi' => ['red-dwarf', 'unknown', '0']];
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addMultiSelect('multi', null, $series);
Assert::true(Validator::validateEqual($input, ['red-dwarf', 0]));
@@ -160,6 +168,7 @@ test('empty submission validation', function () use ($series) {
$_POST = [];
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addMultiSelect('multi', null, $series);
Assert::false(Validator::validateEqual($input, ['red-dwarf', 0]));
@@ -174,6 +183,7 @@ test('keys as items without labels', function () use ($series) {
$_POST = ['multi' => ['red-dwarf']];
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addMultiSelect('multi')->setItems(array_keys($series), useKeys: false);
Assert::same([
'red-dwarf' => 'red-dwarf',
@@ -191,6 +201,7 @@ test('keys as items without labels', function () use ($series) {
test('numeric keys handling', function () use ($series) {
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addMultiSelect('select')->setItems(range(1, 5), useKeys: false);
Assert::same([1 => 1, 2, 3, 4, 5], $input->getItems());
});
@@ -200,6 +211,7 @@ test('grouped items without keys', function () {
$_POST = ['multi' => ['red-dwarf']];
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addMultiSelect('multi')->setItems([
'usa' => ['the-simpsons', 0],
'uk' => ['red-dwarf'],
@@ -214,6 +226,7 @@ test('grouped items without keys', function () {
testException('exception on invalid value', function () use ($series) {
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addMultiSelect('select', null, $series);
$input->setValue('unknown');
}, Nette\InvalidArgumentException::class, "Value 'unknown' are out of allowed set ['red-dwarf', 'the-simpsons', 0, ''] in field 'select'.");
@@ -221,6 +234,7 @@ testException('exception on invalid value', function () use ($series) {
test('dateTime object as value', function () {
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addMultiSelect('select', null, ['2013-07-05 00:00:00' => 1])
->setValue([new DateTime('2013-07-05')]);
@@ -230,6 +244,7 @@ test('dateTime object as value', function () {
test('dateTime items without keys', function () {
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addMultiSelect('select')
->setItems([
'group' => [new DateTime('2013-07-05')],
@@ -245,6 +260,7 @@ test('disabled items ignored in multi-select', function () use ($series) {
$_POST = ['select' => ['red-dwarf', '0']];
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addMultiSelect('select', null, $series)
->setDisabled(['red-dwarf']);
diff --git a/tests/Forms/Controls.MultiSelectBox.render.phpt b/tests/Controls/MultiSelectBox.render.phpt
similarity index 100%
rename from tests/Forms/Controls.MultiSelectBox.render.phpt
rename to tests/Controls/MultiSelectBox.render.phpt
diff --git a/tests/Forms/Controls.RadioList.loadData.phpt b/tests/Controls/RadioList.loadData.phpt
similarity index 92%
rename from tests/Forms/Controls.RadioList.loadData.phpt
rename to tests/Controls/RadioList.loadData.phpt
index 24578282e..07d8b2bc7 100644
--- a/tests/Forms/Controls.RadioList.loadData.phpt
+++ b/tests/Controls/RadioList.loadData.phpt
@@ -17,7 +17,6 @@ require __DIR__ . '/../bootstrap.php';
setUp(function () {
$_SERVER['REQUEST_METHOD'] = 'POST';
$_POST = $_FILES = [];
- $_COOKIE[Nette\Http\Helpers::StrictCookieName] = '1';
ob_start();
Form::initialize(true);
});
@@ -35,6 +34,7 @@ test('valid radio selection', function () use ($series) {
$_POST = ['radio' => 'red-dwarf'];
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addRadioList('radio', null, $series);
Assert::true($form->isValid());
@@ -48,6 +48,7 @@ test('invalid radio selection', function () use ($series) {
$_POST = ['radio' => 'days-of-our-lives'];
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addRadioList('radio', null, $series);
Assert::true($form->isValid());
@@ -61,6 +62,7 @@ test('zero value handling', function () use ($series) {
$_POST = ['zero' => '0'];
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addRadioList('zero', null, $series);
Assert::true($form->isValid());
@@ -75,6 +77,7 @@ test('empty string value handling', function () use ($series) {
$_POST = ['empty' => ''];
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addRadioList('empty', null, $series);
Assert::true($form->isValid());
@@ -86,6 +89,7 @@ test('empty string value handling', function () use ($series) {
test('missing POST data handling', function () use ($series) {
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addRadioList('missing', null, $series);
Assert::true($form->isValid());
@@ -99,6 +103,7 @@ test('disabled radio list handling', function () use ($series) {
$_POST = ['disabled' => 'red-dwarf'];
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addRadioList('disabled', null, $series)
->setDisabled();
@@ -112,6 +117,7 @@ test('malformed radio data', function () use ($series) {
$_POST = ['malformed' => ['']];
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addRadioList('malformed', null, $series);
Assert::true($form->isValid());
@@ -125,6 +131,7 @@ test('items without keys handling', function () use ($series) {
$_POST = ['select' => 'red-dwarf'];
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addRadioList('select')->setItems(array_keys($series), useKeys: false);
Assert::true($form->isValid());
@@ -136,6 +143,7 @@ test('items without keys handling', function () use ($series) {
testException('invalid value exception', function () use ($series) {
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addRadioList('radio', null, $series);
$input->setValue('unknown');
}, Nette\InvalidArgumentException::class, "Value 'unknown' is out of allowed set ['red-dwarf', 'the-simpsons', 0, ''] in field 'radio'.");
@@ -143,6 +151,7 @@ testException('invalid value exception', function () use ($series) {
test('dateTime value handling', function () {
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addRadioList('radio', null, ['2013-07-05 00:00:00' => 1])
->setValue(new DateTime('2013-07-05'));
@@ -152,6 +161,7 @@ test('dateTime value handling', function () {
test('dateTime items handling', function () {
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addRadioList('radio')
->setItems([new DateTime('2013-07-05')], useKeys: false)
->setValue(new DateTime('2013-07-05'));
@@ -164,6 +174,7 @@ test('disabled options handling', function () use ($series) {
$_POST = ['radio' => 'red-dwarf'];
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addRadioList('radio', null, $series)
->setDisabled(['red-dwarf']);
diff --git a/tests/Forms/Controls.RadioList.render.phpt b/tests/Controls/RadioList.render.phpt
similarity index 100%
rename from tests/Forms/Controls.RadioList.render.phpt
rename to tests/Controls/RadioList.render.phpt
diff --git a/tests/Forms/Controls.SelectBox.isOk.phpt b/tests/Controls/SelectBox.isOk.phpt
similarity index 95%
rename from tests/Forms/Controls.SelectBox.isOk.phpt
rename to tests/Controls/SelectBox.isOk.phpt
index a85edefed..daf1a05da 100644
--- a/tests/Forms/Controls.SelectBox.isOk.phpt
+++ b/tests/Controls/SelectBox.isOk.phpt
@@ -15,6 +15,7 @@ require __DIR__ . '/../bootstrap.php';
$form = new Form;
+$form->allowCrossOrigin();
$select = $form->addSelect('foo', null, ['bar' => 'Bar']);
Assert::false($select->isOk());
@@ -43,10 +44,10 @@ Assert::false($select->isOk());
// error message is processed via Rules
$_SERVER['REQUEST_METHOD'] = 'POST';
-$_COOKIE[Nette\Http\Helpers::StrictCookieName] = '1';
Form::initialize(true);
Validator::$messages[Nette\Forms\Controls\SelectBox::Valid] = 'SelectBox "%label" must be filled.';
$form = new Form;
+$form->allowCrossOrigin();
$form->addSelect('foo', 'Foo', ['bar' => 'Bar']);
$form->onSuccess[] = function () {};
$form->fireEvents();
diff --git a/tests/Forms/Controls.SelectBox.loadData.phpt b/tests/Controls/SelectBox.loadData.phpt
similarity index 92%
rename from tests/Forms/Controls.SelectBox.loadData.phpt
rename to tests/Controls/SelectBox.loadData.phpt
index 9ea66a11d..347df05bb 100644
--- a/tests/Forms/Controls.SelectBox.loadData.phpt
+++ b/tests/Controls/SelectBox.loadData.phpt
@@ -17,7 +17,6 @@ require __DIR__ . '/../bootstrap.php';
setUp(function () {
$_SERVER['REQUEST_METHOD'] = 'POST';
$_POST = $_FILES = [];
- $_COOKIE[Nette\Http\Helpers::StrictCookieName] = '1';
ob_start();
Form::initialize(true);
});
@@ -35,6 +34,7 @@ test('valid select box selection', function () use ($series) {
$_POST = ['select' => 'red-dwarf'];
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addSelect('select', null, $series);
Assert::true($form->isValid());
@@ -48,6 +48,7 @@ test('no items handling', function () {
$_POST = ['select' => 'red-dwarf'];
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addSelect('select');
Assert::true($form->isValid());
@@ -61,6 +62,7 @@ test('select box with prompt', function () use ($series) {
$_POST = ['select' => 'red-dwarf'];
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addSelect('select', null, $series)->setPrompt('Select series');
Assert::true($form->isValid());
@@ -72,6 +74,7 @@ test('select box with prompt', function () use ($series) {
test('control prototype modification', function () use ($series) {
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addSelect('select', null, $series);
$input->getControlPrototype()->size = 2;
@@ -86,6 +89,7 @@ test('grouped items selection', function () {
$_POST = ['select' => 'red-dwarf'];
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addSelect('select', null, [
'usa' => [
'the-simpsons' => 'The Simpsons',
@@ -107,6 +111,7 @@ test('invalid selection handling', function () use ($series) {
$_POST = ['select' => 'days-of-our-lives'];
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addSelect('select', null, $series);
Assert::false($form->isValid());
@@ -118,6 +123,7 @@ test('invalid selection handling', function () use ($series) {
test('unselected prompt handling', function () use ($series) {
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addSelect('select', null, $series)->setPrompt('Select series');
Assert::true($form->isValid());
@@ -131,6 +137,7 @@ test('zero value selection', function () use ($series) {
$_POST = ['zero' => '0'];
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addSelect('zero', null, $series);
Assert::true($form->isValid());
@@ -145,6 +152,7 @@ test('empty string selection', function () use ($series) {
$_POST = ['empty' => ''];
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addSelect('empty', null, $series);
Assert::true($form->isValid());
@@ -156,6 +164,7 @@ test('empty string selection', function () use ($series) {
test('missing data handling', function () use ($series) {
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addSelect('missing', null, $series);
Assert::false($form->isValid());
@@ -169,6 +178,7 @@ test('disabled select box', function () use ($series) {
$_POST = ['disabled' => 'red-dwarf'];
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addSelect('disabled', null, $series)
->setDisabled();
@@ -181,6 +191,7 @@ test('malformed select data', function () use ($series) {
$_POST = ['malformed' => ['']];
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addSelect('malformed', null, $series);
Assert::false($form->isValid());
@@ -194,6 +205,7 @@ test('items without keys', function () use ($series) {
$_POST = ['select' => 'red-dwarf'];
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addSelect('select')->setItems(array_keys($series), useKeys: false);
Assert::same([
'red-dwarf' => 'red-dwarf',
@@ -211,6 +223,7 @@ test('items without keys', function () use ($series) {
test('numeric range items', function () {
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addSelect('select')->setItems(range(1, 5), useKeys: false);
Assert::same([1 => 1, 2, 3, 4, 5], $input->getItems());
});
@@ -220,6 +233,7 @@ test('grouped items without keys', function () {
$_POST = ['select' => 'red-dwarf'];
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addSelect('select')->setItems([
'usa' => ['the-simpsons', 0],
'uk' => ['red-dwarf'],
@@ -234,6 +248,7 @@ test('grouped items without keys', function () {
testException('invalid value exception', function () use ($series) {
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addSelect('select', null, $series);
$input->setValue('unknown');
}, Nette\InvalidArgumentException::class, "Value 'unknown' is out of allowed set ['red-dwarf', 'the-simpsons', 0, ''] in field 'select'.");
@@ -241,6 +256,7 @@ testException('invalid value exception', function () use ($series) {
test('dateTime value handling', function () {
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addSelect('select', null, ['2013-07-05 00:00:00' => 1])
->setValue(new DateTime('2013-07-05'));
@@ -250,6 +266,7 @@ test('dateTime value handling', function () {
test('dateTime items in groups', function () {
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addSelect('select')
->setItems([
'group' => [new DateTime('2013-07-05')],
@@ -265,6 +282,7 @@ test('disabled options handling', function () use ($series) {
$_POST = ['select' => 'red-dwarf'];
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addSelect('select', null, $series)
->setDisabled(['red-dwarf']);
@@ -282,6 +300,7 @@ test('null item caption handling', function () {
$_POST = ['select' => '1'];
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addSelect('select', null, [
1 => null,
2 => 'Red dwarf',
diff --git a/tests/Forms/Controls.SelectBox.render.phpt b/tests/Controls/SelectBox.render.phpt
similarity index 100%
rename from tests/Forms/Controls.SelectBox.render.phpt
rename to tests/Controls/SelectBox.render.phpt
diff --git a/tests/Forms/Controls.TestBase.validators.phpt b/tests/Controls/TestBase.validators.phpt
similarity index 100%
rename from tests/Forms/Controls.TestBase.validators.phpt
rename to tests/Controls/TestBase.validators.phpt
diff --git a/tests/Forms/Controls.TextArea.render.phpt b/tests/Controls/TextArea.render.phpt
similarity index 100%
rename from tests/Forms/Controls.TextArea.render.phpt
rename to tests/Controls/TextArea.render.phpt
diff --git a/tests/Forms/Controls.TextBase.loadData.phpt b/tests/Controls/TextBase.loadData.phpt
similarity index 90%
rename from tests/Forms/Controls.TextBase.loadData.phpt
rename to tests/Controls/TextBase.loadData.phpt
index 2e6ee3433..46646a90e 100644
--- a/tests/Forms/Controls.TextBase.loadData.phpt
+++ b/tests/Controls/TextBase.loadData.phpt
@@ -16,7 +16,6 @@ require __DIR__ . '/../bootstrap.php';
setUp(function () {
$_SERVER['REQUEST_METHOD'] = 'POST';
$_POST = $_FILES = [];
- $_COOKIE[Nette\Http\Helpers::StrictCookieName] = '1';
ob_start();
Form::initialize(true);
});
@@ -26,6 +25,7 @@ test('whitespace trimming', function () {
$_POST = ['text' => " a\r b \n c "];
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addText('text');
Assert::same('a b c', $input->getValue());
@@ -37,6 +37,7 @@ test('textarea line breaks', function () {
$_POST = ['text' => " a\r b \n c "];
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addTextArea('text');
Assert::same(" a\n b \n c ", $input->getValue());
@@ -47,6 +48,7 @@ test('empty value detection', function () {
$_POST = ['url' => 'nette.org'];
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addText('url')
->setEmptyValue('nette.org');
@@ -58,6 +60,7 @@ test('custom empty value', function () {
$_POST = ['phone' => '+420 '];
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addText('phone')
->setEmptyValue('+420 ');
@@ -69,6 +72,7 @@ test('invalid UTF input', function () {
$_POST = ['invalidutf' => "invalid\xAA\xAA\xAAutf"];
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addText('invalidutf');
Assert::same('', $input->getValue());
});
@@ -76,6 +80,7 @@ test('invalid UTF input', function () {
test('missing POST handling', function () {
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addText('unknown');
Assert::same('', $input->getValue());
@@ -87,6 +92,7 @@ test('malformed POST data', function () {
$_POST = ['malformed' => ['']];
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addText('malformed');
Assert::same('', $input->getValue());
@@ -98,6 +104,7 @@ testException('invalid value type exception', function () {
$_POST = ['text' => " a\r b \n c "];
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addText('text');
$input->setValue([]);
}, Nette\InvalidArgumentException::class, "Value must be scalar or null, array given in field 'text'.");
@@ -107,6 +114,7 @@ test('float rule processing', function () {
$_POST = ['number' => ' 10,5 '];
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addText('number')
->addRule($form::Float);
@@ -121,6 +129,7 @@ test('conditional validation', function () {
$_POST = ['number' => ' 10,5 '];
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addText('number');
$input->addCondition($form::Filled)
->addRule($form::Float);
@@ -134,6 +143,7 @@ test('negative rule handling', function () {
$_POST = ['number' => ' 10,5 '];
$form = new Form;
+ $form->allowCrossOrigin();
$input = @$form->addText('number')
->addRule(~$form::Float); // @ - negative rules are deprecated
@@ -146,6 +156,7 @@ test('URL auto-correction', function () {
$_POST = ['url' => 'nette.org'];
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addText('url')
->addRule($form::URL);
@@ -156,6 +167,7 @@ test('URL auto-correction', function () {
test('dateTime value handling', function () {
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addText('text')
->setValue($date = new Nette\Utils\DateTime('2013-07-05'));
@@ -167,6 +179,7 @@ test('post-validation filtering', function () {
$_POST = ['text' => 'hello'];
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addText('text')
->addFilter('strrev');
@@ -180,6 +193,7 @@ test('conditional filtering', function () {
$_POST = ['text' => 'hello'];
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addText('text');
$input->addCondition($form::Filled)
->addFilter('strrev');
@@ -194,6 +208,7 @@ test('blank condition filter', function () {
$_POST = ['text' => ''];
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addText('text');
$input->addCondition($form::Blank)
->addFilter(fn() => 'default');
@@ -208,6 +223,7 @@ test('else condition filter', function () {
$_POST = ['text' => ''];
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addText('text');
$input->addCondition($form::Filled)
->elseCondition()
diff --git a/tests/Forms/Controls.TextInput.render.phpt b/tests/Controls/TextInput.render.phpt
similarity index 100%
rename from tests/Forms/Controls.TextInput.render.phpt
rename to tests/Controls/TextInput.render.phpt
diff --git a/tests/Forms/Controls.TextInput.valueObjectValidation.phpt b/tests/Controls/TextInput.valueObjectValidation.phpt
similarity index 100%
rename from tests/Forms/Controls.TextInput.valueObjectValidation.phpt
rename to tests/Controls/TextInput.valueObjectValidation.phpt
diff --git a/tests/Forms/Controls.UploadControl.loadData.phpt b/tests/Controls/UploadControl.loadData.phpt
similarity index 78%
rename from tests/Forms/Controls.UploadControl.loadData.phpt
rename to tests/Controls/UploadControl.loadData.phpt
index 1ed34c74f..6cc4e8b4a 100644
--- a/tests/Forms/Controls.UploadControl.loadData.phpt
+++ b/tests/Controls/UploadControl.loadData.phpt
@@ -15,58 +15,60 @@ use Tester\Assert;
require __DIR__ . '/../bootstrap.php';
-$_SERVER['REQUEST_METHOD'] = 'POST';
-$_COOKIE[Nette\Http\Helpers::StrictCookieName] = '1';
-
-$_FILES = [
- 'avatar' => [
- 'name' => 'license.txt',
- 'type' => 'text/plain',
- 'tmp_name' => __DIR__ . '/files/logo.gif',
- 'error' => 0,
- 'size' => 3013,
- ],
- 'container' => [
- 'name' => ['avatar' => "invalid\xAA\xAA\xAAutf"],
- 'type' => ['avatar' => 'text/plain'],
- 'tmp_name' => ['avatar' => 'C:\PHP\temp\php1D5C.tmp'],
- 'error' => ['avatar' => 0],
- 'size' => ['avatar' => 3013],
- ],
- 'multiple' => [
- 'name' => ['avatar' => ['image.gif', 'image.png']],
- 'type' => ['avatar' => ['a', 'b']],
- 'tmp_name' => ['avatar' => [__DIR__ . '/files/logo.gif', __DIR__ . '/files/logo.gif']],
- 'error' => ['avatar' => [0, 0]],
- 'size' => ['avatar' => [100, 200]],
- ],
- 'empty' => [
- 'name' => [''],
- 'type' => [''],
- 'tmp_name' => [''],
- 'error' => [UPLOAD_ERR_NO_FILE],
- 'size' => [0],
- ],
- 'invalid1' => [
- 'name' => [null],
- 'type' => [null],
- 'tmp_name' => [null],
- 'error' => [null],
- 'size' => [null],
- ],
- 'invalid2' => '',
- 'partial' => [
- 'name' => 'license.txt',
- 'type' => 'text/plain',
- 'tmp_name' => __DIR__ . '/files/logo.gif',
- 'error' => UPLOAD_ERR_PARTIAL,
- 'size' => 3013,
- ],
-];
+setUp(function () {
+ $_SERVER['REQUEST_METHOD'] = 'POST';
+
+ $_FILES = [
+ 'avatar' => [
+ 'name' => 'license.txt',
+ 'type' => 'text/plain',
+ 'tmp_name' => __DIR__ . '/files/logo.gif',
+ 'error' => 0,
+ 'size' => 3013,
+ ],
+ 'container' => [
+ 'name' => ['avatar' => "invalid\xAA\xAA\xAAutf"],
+ 'type' => ['avatar' => 'text/plain'],
+ 'tmp_name' => ['avatar' => 'C:\PHP\temp\php1D5C.tmp'],
+ 'error' => ['avatar' => 0],
+ 'size' => ['avatar' => 3013],
+ ],
+ 'multiple' => [
+ 'name' => ['avatar' => ['image.gif', 'image.png']],
+ 'type' => ['avatar' => ['a', 'b']],
+ 'tmp_name' => ['avatar' => [__DIR__ . '/files/logo.gif', __DIR__ . '/files/logo.gif']],
+ 'error' => ['avatar' => [0, 0]],
+ 'size' => ['avatar' => [100, 200]],
+ ],
+ 'empty' => [
+ 'name' => [''],
+ 'type' => [''],
+ 'tmp_name' => [''],
+ 'error' => [UPLOAD_ERR_NO_FILE],
+ 'size' => [0],
+ ],
+ 'invalid1' => [
+ 'name' => [null],
+ 'type' => [null],
+ 'tmp_name' => [null],
+ 'error' => [null],
+ 'size' => [null],
+ ],
+ 'invalid2' => '',
+ 'partial' => [
+ 'name' => 'license.txt',
+ 'type' => 'text/plain',
+ 'tmp_name' => __DIR__ . '/files/logo.gif',
+ 'error' => UPLOAD_ERR_PARTIAL,
+ 'size' => 3013,
+ ],
+ ];
+});
test('valid file upload handling', function () {
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addUpload('avatar');
Assert::true($form->isValid());
@@ -84,6 +86,7 @@ test('valid file upload handling', function () {
test('container file upload with invalid UTF', function () {
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addContainer('container')->addUpload('avatar');
Assert::true($form->isValid());
@@ -101,6 +104,7 @@ test('container file upload with invalid UTF', function () {
test('multiple file uploads', function () {
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addContainer('multiple')->addMultiUpload('avatar');
Assert::true($form->isValid());
@@ -124,6 +128,7 @@ test('multiple file uploads', function () {
test('required multi-upload with empty data', function () {
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addMultiUpload('empty')
->setRequired();
@@ -136,6 +141,7 @@ test('required multi-upload with empty data', function () {
test('missing upload field handling', function () {
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addUpload('missing')
->setRequired();
@@ -148,6 +154,7 @@ test('missing upload field handling', function () {
test('nullable multi-upload handling', function () {
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addMultiUpload('empty')
->setNullable() // has no effect
->setRequired();
@@ -161,6 +168,7 @@ test('nullable multi-upload handling', function () {
test('nullable single upload handling', function () {
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addUpload('missing')
->setNullable()
->setRequired();
@@ -174,6 +182,7 @@ test('nullable single upload handling', function () {
test('invalid upload data structures', function () {
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addUpload('invalid1');
Assert::true($form->isValid());
@@ -182,6 +191,7 @@ test('invalid upload data structures', function () {
Assert::false($input->isOk());
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addUpload('invalid2');
Assert::true($form->isValid());
@@ -190,6 +200,7 @@ test('invalid upload data structures', function () {
Assert::false($input->isOk());
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addMultiUpload('avatar');
Assert::true($form->isValid());
@@ -198,6 +209,7 @@ test('invalid upload data structures', function () {
Assert::false($input->isOk());
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addContainer('multiple')->addUpload('avatar');
Assert::true($form->isValid());
@@ -209,6 +221,7 @@ test('invalid upload data structures', function () {
test('partial upload error handling', function () {
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addUpload('partial')
->setRequired();
@@ -227,6 +240,7 @@ test('partial upload error handling', function () {
test('file size and MIME validation', function () {
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addUpload('avatar')
->addRule($form::MaxFileSize, null, 3000);
@@ -246,6 +260,7 @@ test('file size and MIME validation', function () {
test('multi-upload file validations', function () {
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addContainer('multiple')->addMultiUpload('avatar')
->addRule($form::MaxFileSize, null, 3000);
@@ -265,6 +280,7 @@ test('multi-upload file validations', function () {
test('upload control rule count', function () {
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addUpload('invalid1');
$rules = iterator_to_array($input->getRules());
diff --git a/tests/Forms/Controls.UploadControl.render.phpt b/tests/Controls/UploadControl.render.phpt
similarity index 100%
rename from tests/Forms/Controls.UploadControl.render.phpt
rename to tests/Controls/UploadControl.render.phpt
diff --git a/tests/Forms/files/logo.gif b/tests/Controls/files/logo.gif
similarity index 100%
rename from tests/Forms/files/logo.gif
rename to tests/Controls/files/logo.gif
diff --git a/tests/Forms.DI/FormsExtension.phpt b/tests/Forms.DI/FormsExtension.phpt
index a298ce134..9f455daf2 100644
--- a/tests/Forms.DI/FormsExtension.phpt
+++ b/tests/Forms.DI/FormsExtension.phpt
@@ -24,9 +24,9 @@ test('', function () {
$config = $loader->load(Tester\FileMock::create('
forms:
messages:
- EQUAL: "Testing equal %s."
- FILLED: "Testing filled"
- \'Nette\Forms\Controls\SelectBox::VALID\': "SelectBox test"
+ Equal: "Testing equal %s."
+ Filled: "Testing filled"
+ \'Nette\Forms\Controls\SelectBox::Valid\': "SelectBox test"
', 'neon'));
eval($compiler->addConfig($config)->setClassName('Container1')->compile());
diff --git a/tests/Forms/Container.values.ArrayHash.phpt b/tests/Forms/Container.values.ArrayHash.phpt
index 3a121c43d..36de05653 100644
--- a/tests/Forms/Container.values.ArrayHash.phpt
+++ b/tests/Forms/Container.values.ArrayHash.phpt
@@ -10,24 +10,26 @@ use Tester\Assert;
require __DIR__ . '/../bootstrap.php';
-$_COOKIE[Nette\Http\Helpers::StrictCookieName] = '1';
-$_POST = [
- 'title' => 'sent title',
- 'first' => [
- 'age' => '999',
- 'second' => [
- 'city' => 'sent city',
+setUp(function () {
+ $_SERVER['REQUEST_METHOD'] = 'POST';
+ $_POST = [
+ 'title' => 'sent title',
+ 'first' => [
+ 'age' => '999',
+ 'second' => [
+ 'city' => 'sent city',
+ ],
],
- ],
-];
+ ];
+ ob_start();
+ Form::initialize(true);
+});
function createForm(): Form
{
- ob_start();
- Form::initialize(true);
-
$form = new Form;
+ $form->allowCrossOrigin();
$form->addText('title');
$first = $form->addContainer('first');
@@ -41,6 +43,8 @@ function createForm(): Form
test('setting defaults using ArrayHash', function () {
+ $_SERVER['REQUEST_METHOD'] = 'GET';
+
$form = createForm();
Assert::false($form->isSubmitted());
@@ -70,8 +74,6 @@ test('setting defaults using ArrayHash', function () {
test('retrieving POST data as ArrayHash', function () {
- $_SERVER['REQUEST_METHOD'] = 'POST';
-
$form = createForm();
Assert::truthy($form->isSubmitted());
Assert::equal(ArrayHash::from([
@@ -88,8 +90,6 @@ test('retrieving POST data as ArrayHash', function () {
test('resetting form with ArrayHash values', function () {
- $_SERVER['REQUEST_METHOD'] = 'POST';
-
$form = createForm();
Assert::truthy($form->isSubmitted());
@@ -110,8 +110,6 @@ test('resetting form with ArrayHash values', function () {
test('updating values with ArrayHash and erase', function () {
- $_SERVER['REQUEST_METHOD'] = 'POST';
-
$form = createForm();
Assert::truthy($form->isSubmitted());
@@ -155,8 +153,6 @@ test('updating values with ArrayHash and erase', function () {
test('onSuccess event with ArrayHash values', function () {
- $_SERVER['REQUEST_METHOD'] = 'POST';
-
$form = createForm();
$form->onSuccess[] = function (Form $form, array $values) {
Assert::same([
diff --git a/tests/Forms/Container.values.array.phpt b/tests/Forms/Container.values.array.phpt
index c36515d3c..2cfefe183 100644
--- a/tests/Forms/Container.values.array.phpt
+++ b/tests/Forms/Container.values.array.phpt
@@ -10,24 +10,26 @@ use Tester\Assert;
require __DIR__ . '/../bootstrap.php';
-$_COOKIE[Nette\Http\Helpers::StrictCookieName] = '1';
-$_POST = [
- 'title' => 'sent title',
- 'first' => [
- 'age' => '999',
- 'second' => [
- 'city' => 'sent city',
+setUp(function () {
+ $_SERVER['REQUEST_METHOD'] = 'POST';
+ $_POST = [
+ 'title' => 'sent title',
+ 'first' => [
+ 'age' => '999',
+ 'second' => [
+ 'city' => 'sent city',
+ ],
],
- ],
-];
+ ];
+ ob_start();
+ Form::initialize(true);
+});
function createForm(): Form
{
- ob_start();
- Form::initialize(true);
-
$form = new Form;
+ $form->allowCrossOrigin();
$form->addText('title');
$first = $form->addContainer('first');
@@ -41,6 +43,8 @@ function createForm(): Form
test('setting form defaults and retrieving array values', function () {
+ $_SERVER['REQUEST_METHOD'] = 'GET';
+
$form = createForm();
Assert::false($form->isSubmitted());
@@ -70,8 +74,6 @@ test('setting form defaults and retrieving array values', function () {
test('handles POST submission with nested data', function () {
- $_SERVER['REQUEST_METHOD'] = 'POST';
-
$form = createForm();
Assert::truthy($form->isSubmitted());
Assert::equal([
@@ -88,8 +90,6 @@ test('handles POST submission with nested data', function () {
test('resetting form clears submitted values', function () {
- $_SERVER['REQUEST_METHOD'] = 'POST';
-
$form = createForm();
Assert::truthy($form->isSubmitted());
@@ -110,8 +110,6 @@ test('resetting form clears submitted values', function () {
test('setting form values with erase option', function () {
- $_SERVER['REQUEST_METHOD'] = 'POST';
-
$form = createForm();
Assert::truthy($form->isSubmitted());
@@ -155,8 +153,6 @@ test('setting form values with erase option', function () {
test('updating form values without erasing', function () {
- $_SERVER['REQUEST_METHOD'] = 'POST';
-
$form = createForm();
Assert::truthy($form->isSubmitted());
@@ -182,8 +178,6 @@ test('updating form values without erasing', function () {
test('using array as mapped type for form values', function () {
- $_SERVER['REQUEST_METHOD'] = 'POST';
-
$form = createForm();
$form->setMappedType('array');
@@ -210,8 +204,6 @@ test('using array as mapped type for form values', function () {
test('triggering onSuccess with correct value types', function () {
- $_SERVER['REQUEST_METHOD'] = 'POST';
-
$form = createForm();
$form->onSuccess[] = function (Form $form, array $values) {
Assert::same([
@@ -263,7 +255,6 @@ test('triggering onSuccess with correct value types', function () {
test('validation scope limits submitted data', function () {
- $_SERVER['REQUEST_METHOD'] = 'POST';
$_POST['send'] = '';
$form = createForm();
@@ -281,7 +272,6 @@ test('validation scope limits submitted data', function () {
test('validation scope applied to container', function () {
- $_SERVER['REQUEST_METHOD'] = 'POST';
$_POST['send'] = '';
$form = createForm();
@@ -298,7 +288,6 @@ test('validation scope applied to container', function () {
});
test('validation scope on nested container fields', function () {
- $_SERVER['REQUEST_METHOD'] = 'POST';
$_POST['send'] = '';
$form = createForm();
diff --git a/tests/Forms/Container.values.mapping-constructor.phpt b/tests/Forms/Container.values.mapping-constructor.phpt
index 2b8049d99..4b93d8b82 100644
--- a/tests/Forms/Container.values.mapping-constructor.phpt
+++ b/tests/Forms/Container.values.mapping-constructor.phpt
@@ -25,7 +25,7 @@ class FormFirstLevelConstruct
public function __construct(
public string $name,
public ?FormSecondLevelConstruct $second = null,
- public int|null $age = null,
+ public ?int $age = null,
) {
}
}
diff --git a/tests/Forms/Container.values.mapping.phpt b/tests/Forms/Container.values.mapping.phpt
index 043d6cbc7..77c7939b4 100644
--- a/tests/Forms/Container.values.mapping.phpt
+++ b/tests/Forms/Container.values.mapping.phpt
@@ -34,7 +34,6 @@ class FormSecondLevel
setUp(function () {
- $_COOKIE[Nette\Http\Helpers::StrictCookieName] = '1';
$_SERVER['REQUEST_METHOD'] = 'POST';
$_POST = [
'title' => 'sent title',
@@ -45,15 +44,15 @@ setUp(function () {
],
],
];
+ ob_start();
+ Form::initialize(true);
});
function createForm(): Form
{
- ob_start();
- Form::initialize(true);
-
$form = new Form;
+ $form->allowCrossOrigin();
$form->addText('title');
$first = $form->addContainer('first');
diff --git a/tests/Forms/Forms.callbackParameters.phpt b/tests/Forms/Forms.callbackParameters.phpt
index af919c060..f07d52d09 100644
--- a/tests/Forms/Forms.callbackParameters.phpt
+++ b/tests/Forms/Forms.callbackParameters.phpt
@@ -14,11 +14,11 @@ use Tester\Assert;
require __DIR__ . '/../bootstrap.php';
$_SERVER['REQUEST_METHOD'] = 'POST';
-$_COOKIE[Nette\Http\Helpers::StrictCookieName] = '1';
$_POST['text'] = 'a';
$_POST['btn'] = 'b';
$form = new Form;
+$form->allowCrossOrigin();
$form->addText('text');
$form->addSubmit('btn');
diff --git a/tests/Forms/Forms.crossOrigin.phpt b/tests/Forms/Forms.crossOrigin.phpt
index e99236c5c..9407cfb78 100644
--- a/tests/Forms/Forms.crossOrigin.phpt
+++ b/tests/Forms/Forms.crossOrigin.phpt
@@ -21,7 +21,7 @@ setUp(function () {
});
-test('form success without data', function () {
+test('form success without strict cookie', function () {
$form = new Form;
Assert::false($form->isSuccess());
});
@@ -35,7 +35,7 @@ test('strict cookie form submission', function () {
});
-test('cross-origin form submission', function () {
+test('allowed cross-origin form submission', function () {
$form = new Form;
$form->allowCrossOrigin();
Assert::true($form->isSuccess());
diff --git a/tests/Forms/Forms.enum.phpt b/tests/Forms/Forms.enum.phpt
index a63c8d8bc..602c2ee84 100644
--- a/tests/Forms/Forms.enum.phpt
+++ b/tests/Forms/Forms.enum.phpt
@@ -21,8 +21,6 @@ enum TestEnum: string
test('enum value validation', function () {
- ob_start();
- Form::initialize(true);
$form = new Form;
$input = $form->addText('text');
$input->setValue(TestEnum::Case1->value);
@@ -37,8 +35,6 @@ test('enum value validation', function () {
test('setting enum defaults in selects', function () {
$items = ['case 1' => '1', 'case 2' => '2', 'case 3' => '3', 'case 4' => '4'];
- ob_start();
- Form::initialize(true);
$form = new Form;
$form->addSelect('select', null, $items);
$form->addMultiSelect('multi', null, $items);
diff --git a/tests/Forms/Forms.getHttpData.post.phpt b/tests/Forms/Forms.getHttpData.post.phpt
index af107f9a5..b4f208bae 100644
--- a/tests/Forms/Forms.getHttpData.post.phpt
+++ b/tests/Forms/Forms.getHttpData.post.phpt
@@ -16,37 +16,12 @@ require __DIR__ . '/../bootstrap.php';
setUp(function () {
$_SERVER['REQUEST_METHOD'] = 'POST';
- $_COOKIE[Nette\Http\Helpers::StrictCookieName] = '1';
$_GET = $_POST = $_FILES = [];
ob_start();
Form::initialize(true);
});
-test('POST submission with strict cookie', function () {
- $form = new Form;
- $form->addSubmit('send', 'Send');
-
- Assert::truthy($form->isSubmitted());
- Assert::true($form->isSuccess());
- Assert::same([], $form->getHttpData());
- Assert::same([], $form->getValues('array'));
-});
-
-
-test('missing cookie in POST', function () {
- unset($_COOKIE[Nette\Http\Helpers::StrictCookieName]);
-
- $form = new Form;
- $form->addSubmit('send', 'Send');
-
- Assert::false($form->isSubmitted());
- Assert::false($form->isSuccess());
- Assert::same([], $form->getHttpData());
- Assert::same([], $form->getValues('array'));
-});
-
-
test('GET method in POST context', function () {
$form = new Form;
$form->setMethod($form::Get);
@@ -64,6 +39,7 @@ test('tracker ID in POST submission', function () {
$_POST = [Form::TrackerId => $name];
$form = new Form($name);
+ $form->allowCrossOrigin();
$form->addSubmit('send', 'Send');
Assert::truthy($form->isSubmitted());
@@ -75,6 +51,7 @@ test('tracker ID in POST submission', function () {
test('submit button not pressed', function () {
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addSubmit('send', 'Send');
Assert::false($input->isSubmittedBy());
Assert::false(Validator::validateSubmitted($input));
@@ -84,6 +61,7 @@ test('submit button not pressed', function () {
test('successful POST submission', function () {
$_POST = ['send' => ''];
$form = new Form;
+ $form->allowCrossOrigin();
$input = $form->addSubmit('send', 'Send');
Assert::true($input->isSubmittedBy());
Assert::true(Validator::validateSubmitted($input));
diff --git a/tests/Forms/Forms.isValid.phpt b/tests/Forms/Forms.isValid.phpt
index e4826ed6b..bc65da3d8 100644
--- a/tests/Forms/Forms.isValid.phpt
+++ b/tests/Forms/Forms.isValid.phpt
@@ -15,13 +15,13 @@ require __DIR__ . '/../bootstrap.php';
setUp(function () {
$_SERVER['REQUEST_METHOD'] = 'POST';
- $_COOKIE[Nette\Http\Helpers::StrictCookieName] = '1';
$_GET = $_POST = $_FILES = [];
});
test('error accumulation and validation', function () {
$form = new Form;
+ $form->allowCrossOrigin();
$form->addText('item');
Assert::true($form->isSubmitted());
@@ -52,6 +52,7 @@ test('error accumulation and validation', function () {
test('global form errors', function () {
$form = new Form;
+ $form->allowCrossOrigin();
$form->addText('item');
$form->addError('1');
@@ -64,6 +65,7 @@ test('global form errors', function () {
test('control-specific errors', function () {
$form = new Form;
+ $form->allowCrossOrigin();
$form->addText('item');
$form['item']->addError('1');
@@ -76,6 +78,7 @@ test('control-specific errors', function () {
test('combined global and control errors', function () {
$form = new Form;
+ $form->allowCrossOrigin();
$form->addText('item');
$form->addError('1');
@@ -89,6 +92,7 @@ test('combined global and control errors', function () {
test('errors after event firing', function () {
$form = new Form;
+ $form->allowCrossOrigin();
$form->addText('item');
$form->addError('1');
diff --git a/tests/Forms/Forms.maxPostSize.phpt b/tests/Forms/Forms.maxPostSize.phpt
index 2190b3f58..4ce5b07b3 100644
--- a/tests/Forms/Forms.maxPostSize.phpt
+++ b/tests/Forms/Forms.maxPostSize.phpt
@@ -15,9 +15,9 @@ require __DIR__ . '/../bootstrap.php';
$_SERVER['REQUEST_METHOD'] = 'POST';
$_SERVER['CONTENT_LENGTH'] = PHP_INT_MAX;
-$_COOKIE[Nette\Http\Helpers::StrictCookieName] = '1';
$form = new Form;
+$form->allowCrossOrigin();
$form->addHidden('x');
$form->isSuccess();
diff --git a/tests/Forms/Forms.no.events.phpt b/tests/Forms/Forms.no.events.phpt
index 6696f5ae6..3618c4a15 100644
--- a/tests/Forms/Forms.no.events.phpt
+++ b/tests/Forms/Forms.no.events.phpt
@@ -16,7 +16,6 @@ require __DIR__ . '/../bootstrap.php';
setUp(function () {
$_SERVER['REQUEST_METHOD'] = 'POST';
$_POST = ['send' => 'x'];
- $_COOKIE[Nette\Http\Helpers::StrictCookieName] = '1';
ob_start();
Form::initialize(true);
});
@@ -25,6 +24,7 @@ setUp(function () {
test('firing events without handlers', function () {
Assert::error(function () {
$form = new Form;
+ $form->allowCrossOrigin();
$form->addSubmit('send');
$form->fireEvents();
}, E_USER_WARNING);
@@ -33,6 +33,7 @@ test('firing events without handlers', function () {
test('no error with onSuccess handler', function () {
Assert::noError(function () {
$form = new Form;
+ $form->allowCrossOrigin();
$form->addSubmit('send');
$form->onSuccess[] = function () {};
$form->fireEvents();
@@ -42,6 +43,7 @@ test('no error with onSuccess handler', function () {
test('no error with onSubmit handler', function () {
Assert::noError(function () {
$form = new Form;
+ $form->allowCrossOrigin();
$form->addSubmit('send');
$form->onSubmit[] = function () {};
$form->fireEvents();
@@ -51,6 +53,7 @@ test('no error with onSubmit handler', function () {
test('no error with button onClick handler', function () {
Assert::noError(function () {
$form = new Form;
+ $form->allowCrossOrigin();
$form->addSubmit('send')
->onClick[] = function () {};
$form->fireEvents();
diff --git a/tests/Forms/Forms.onClick.phpt b/tests/Forms/Forms.onClick.phpt
index 74ffae46f..04dda4a1a 100644
--- a/tests/Forms/Forms.onClick.phpt
+++ b/tests/Forms/Forms.onClick.phpt
@@ -12,11 +12,11 @@ require __DIR__ . '/../bootstrap.php';
test('valid submission event order', function () {
$_SERVER['REQUEST_METHOD'] = 'POST';
- $_COOKIE[Nette\Http\Helpers::StrictCookieName] = '1';
$_POST = ['btn' => ''];
$called = [];
$form = new Form;
+ $form->allowCrossOrigin();
$form->addText('name');
$button = $form->addSubmit('btn');
@@ -42,11 +42,11 @@ test('valid submission event order', function () {
test('error during onClick propagation', function () {
$_SERVER['REQUEST_METHOD'] = 'POST';
- $_COOKIE[Nette\Http\Helpers::StrictCookieName] = '1';
$_POST = ['btn' => ''];
$called = [];
$form = new Form;
+ $form->allowCrossOrigin();
$form->addText('name');
$button = $form->addSubmit('btn');
@@ -79,11 +79,11 @@ test('error during onClick propagation', function () {
test('invalid submission due to validation', function () {
$_SERVER['REQUEST_METHOD'] = 'POST';
- $_COOKIE[Nette\Http\Helpers::StrictCookieName] = '1';
$_POST = ['btn' => ''];
$called = [];
$form = new Form;
+ $form->allowCrossOrigin();
$form->addText('name')
->setRequired();
$button = $form->addSubmit('btn');
diff --git a/tests/Forms/Forms.onRender.phpt b/tests/Forms/Forms.onRender.phpt
index 9197fbb1e..594d43fae 100644
--- a/tests/Forms/Forms.onRender.phpt
+++ b/tests/Forms/Forms.onRender.phpt
@@ -13,8 +13,6 @@ use Tester\Assert;
require __DIR__ . '/../bootstrap.php';
-ob_start();
-
$called = [];
$form = new Form;
$form->addText('name');
diff --git a/tests/Forms/Forms.onSuccess.phpt b/tests/Forms/Forms.onSuccess.phpt
index 7dda256a8..5a6111f5f 100644
--- a/tests/Forms/Forms.onSuccess.phpt
+++ b/tests/Forms/Forms.onSuccess.phpt
@@ -15,10 +15,10 @@ require __DIR__ . '/../bootstrap.php';
test('basic success event order', function () {
$_SERVER['REQUEST_METHOD'] = 'POST';
- $_COOKIE[Nette\Http\Helpers::StrictCookieName] = '1';
$called = [];
$form = new Form;
+ $form->allowCrossOrigin();
$form->addText('name');
$form->addSubmit('submit');
@@ -38,10 +38,10 @@ test('basic success event order', function () {
test('error during onSuccess chain', function () {
$_SERVER['REQUEST_METHOD'] = 'POST';
- $_COOKIE[Nette\Http\Helpers::StrictCookieName] = '1';
$called = [];
$form = new Form;
+ $form->allowCrossOrigin();
$form->addText('name');
$form->addSubmit('submit');
@@ -68,10 +68,10 @@ test('error during onSuccess chain', function () {
test('validation failure event flow', function () {
$_SERVER['REQUEST_METHOD'] = 'POST';
- $_COOKIE[Nette\Http\Helpers::StrictCookieName] = '1';
$called = [];
$form = new Form;
+ $form->allowCrossOrigin();
$form->addText('name')
->setRequired();
$form->addSubmit('submit');
diff --git a/tests/Forms/Forms.renderer.1.phpt b/tests/Forms/Forms.renderer.1.phpt
index 0505b04f7..f83aae70a 100644
--- a/tests/Forms/Forms.renderer.1.phpt
+++ b/tests/Forms/Forms.renderer.1.phpt
@@ -15,7 +15,6 @@ require __DIR__ . '/../bootstrap.php';
$_SERVER['REQUEST_METHOD'] = 'POST';
-$_COOKIE[Nette\Http\Helpers::StrictCookieName] = '1';
$_POST = ['name' => 'John Doe ', 'age' => '', 'email' => ' @ ', 'send' => 'on', 'street' => '', 'city' => '', 'country' => 'HU', 'password' => 'xxx', 'password2' => '', 'note' => '', 'submit1' => 'Send', 'userid' => '231'];
@@ -37,6 +36,7 @@ $sex = [
$form = new Form;
+$form->allowCrossOrigin();
$form->addGroup('Personal data')
diff --git a/tests/Forms/Forms.renderer.2.phpt b/tests/Forms/Forms.renderer.2.phpt
index 813271bd8..47739be65 100644
--- a/tests/Forms/Forms.renderer.2.phpt
+++ b/tests/Forms/Forms.renderer.2.phpt
@@ -15,7 +15,6 @@ require __DIR__ . '/../bootstrap.php';
$_SERVER['REQUEST_METHOD'] = 'POST';
-$_COOKIE[Nette\Http\Helpers::StrictCookieName] = '1';
$_POST = ['name' => 'John Doe ', 'age' => '9.9', 'email' => '@', 'street' => '', 'city' => 'Troubsko', 'country' => '0', 'password' => 'xx', 'password2' => 'xx', 'note' => '', 'submit1' => 'Send', 'userid' => '231'];
@@ -37,8 +36,9 @@ $sex = [
$form = new Form;
+$form->allowCrossOrigin();
-$renderer = $form->renderer;
+$renderer = $form->getRenderer();
$renderer->wrappers['form']['container'] = Html::el('div')->id('form');
$renderer->wrappers['form']['errors'] = false;
$renderer->wrappers['group']['container'] = null;
diff --git a/tests/Forms/Forms.submittedBy.phpt b/tests/Forms/Forms.submittedBy.phpt
index d22389f81..b023cf8f2 100644
--- a/tests/Forms/Forms.submittedBy.phpt
+++ b/tests/Forms/Forms.submittedBy.phpt
@@ -15,7 +15,6 @@ require __DIR__ . '/../bootstrap.php';
setUp(function () {
$_SERVER['REQUEST_METHOD'] = 'POST';
- $_COOKIE[Nette\Http\Helpers::StrictCookieName] = '1';
$_GET = $_POST = $_FILES = [];
ob_start();
Form::initialize(true);
@@ -23,10 +22,10 @@ setUp(function () {
test('identifying submitted button', function () {
- $name = 'name';
- $_POST = [Form::TrackerId => $name, 'send2' => ''];
+ $_POST = ['send2' => ''];
- $form = new Form($name);
+ $form = new Form;
+ $form->allowCrossOrigin();
$btn1 = $form->addSubmit('send1');
$btn2 = $form->addSubmit('send2');
$btn3 = $form->addSubmit('send3');
@@ -37,10 +36,10 @@ test('identifying submitted button', function () {
test('image button submission detection', function () {
- $name = 'name';
- $_POST = [Form::TrackerId => $name, 'send2' => ['x' => '1', 'y' => '1']];
+ $_POST = ['send2' => ['x' => '1', 'y' => '1']];
- $form = new Form($name);
+ $form = new Form;
+ $form->allowCrossOrigin();
$btn1 = $form->addImageButton('send1');
$btn2 = $form->addImageButton('send2');
$btn3 = $form->addImageButton('send3');
diff --git a/tests/netteForms/SpecRunner.html b/tests/netteForms/SpecRunner.html
deleted file mode 100644
index d9c000c38..000000000
--- a/tests/netteForms/SpecRunner.html
+++ /dev/null
@@ -1,23 +0,0 @@
-
-
-
-
- Jasmine Spec Runner v2.2.0
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/tests/netteForms/karma.conf.ts b/tests/netteForms/karma.conf.ts
deleted file mode 100644
index 3712c64dd..000000000
--- a/tests/netteForms/karma.conf.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-module.exports = function (config) {
- config.set({
- basePath: '',
- frameworks: ['jasmine'],
- browsers: ['ChromeHeadless'],
- files: [
- '../../src/assets/netteForms.js',
- 'spec/*Spec.js',
- ],
- autoWatch: false,
- singleRun: true,
- });
-};
diff --git a/tests/netteForms/setup.js b/tests/netteForms/setup.js
new file mode 100644
index 000000000..f465b5fa0
--- /dev/null
+++ b/tests/netteForms/setup.js
@@ -0,0 +1,12 @@
+import fs from 'fs';
+import { fileURLToPath } from 'url';
+import { dirname, join } from 'path';
+import vm from 'vm';
+
+const __dirname = dirname(fileURLToPath(import.meta.url));
+
+
+// Load and execute netteForms.js in global context
+const netteFormsPath = join(__dirname, '../../src/assets/netteForms.js');
+const netteFormsCode = fs.readFileSync(netteFormsPath, 'utf-8');
+vm.runInThisContext(netteFormsCode);
diff --git a/tests/netteForms/spec/Nette.validateRuleSpec.js b/tests/netteForms/spec/Nette.validateRule.spec.js
similarity index 97%
rename from tests/netteForms/spec/Nette.validateRuleSpec.js
rename to tests/netteForms/spec/Nette.validateRule.spec.js
index 074f7e3a2..7d44b65fa 100644
--- a/tests/netteForms/spec/Nette.validateRuleSpec.js
+++ b/tests/netteForms/spec/Nette.validateRule.spec.js
@@ -1,3 +1,5 @@
+import { describe, it, expect, beforeEach, afterEach } from 'vitest';
+
describe('Nette.getValue & validateRule', () => {
let testContainer;
@@ -5,7 +7,7 @@ describe('Nette.getValue & validateRule', () => {
testContainer.innerHTML = '';
let form = testContainer.querySelector('form'),
- el = form.input;
+ el = form.elements['input'];
expect(Nette.getValue(el)).toBe('');
expect(Nette.validateRule(el, 'filled')).toBe(false);
@@ -85,7 +87,7 @@ describe('Nette.getValue & validateRule', () => {
testContainer.innerHTML = '';
let form = testContainer.querySelector('form'),
- el = form.input;
+ el = form.elements['input'];
expect(Nette.getValue(el)).toBe('');
expect(Nette.validateRule(el, 'filled')).toBe(false);
@@ -144,7 +146,7 @@ describe('Nette.getValue & validateRule', () => {
testContainer.innerHTML = '';
let form = testContainer.querySelector('form'),
- el = form.input;
+ el = form.elements['input'];
expect(Nette.getValue(el)).toBe('');
expect(Nette.validateRule(el, 'filled')).toBe(false);
@@ -164,7 +166,7 @@ describe('Nette.getValue & validateRule', () => {
testContainer.innerHTML = '';
let form = testContainer.querySelector('form'),
- el = form.input;
+ el = form.elements['input'];
expect(Nette.getValue(el) instanceof FileList).toBe(true);
expect(Nette.getValue(el).length).toBe(0);
@@ -175,7 +177,7 @@ describe('Nette.getValue & validateRule', () => {
testContainer.innerHTML = '';
let form = testContainer.querySelector('form'),
- el = form['input[]'];
+ el = form.elements['input[]'];
expect(Nette.getValue(el) instanceof FileList).toBe(true);
expect(Nette.getValue(el).length).toBe(0);
@@ -186,7 +188,7 @@ describe('Nette.getValue & validateRule', () => {
testContainer.innerHTML = '';
let form = testContainer.querySelector('form'),
- el = form.input;
+ el = form.elements['input'];
expect(Nette.getValue(el)).toBe(false);
expect(Nette.validateRule(el, 'filled')).toBe(false);
@@ -209,7 +211,7 @@ describe('Nette.getValue & validateRule', () => {
`;
let form = testContainer.querySelector('form'),
- el = form['input[]'][0];
+ el = form.elements['input[]'][0];
expect(Nette.getValue(el)).toEqual([]);
expect(Nette.validateRule(el, 'filled')).toBe(false);
@@ -243,7 +245,7 @@ describe('Nette.getValue & validateRule', () => {
testContainer.innerHTML = '';
let form = testContainer.querySelector('form'),
- el = form['input[]'];
+ el = form.elements['input[]'];
expect(Nette.getValue(el)).toEqual([]);
expect(Nette.validateRule(el, 'filled')).toBe(false);
@@ -266,7 +268,7 @@ describe('Nette.getValue & validateRule', () => {
testContainer.innerHTML = '`;
let form = testContainer.querySelector('form'),
- el = form.input[0];
+ el = form.elements['input'][0];
expect(Nette.getValue(el)).toBe(null);
expect(Nette.validateRule(el, 'filled')).toBe(false);
@@ -320,7 +322,7 @@ describe('Nette.getValue & validateRule', () => {
`;
let form = testContainer.querySelector('form'),
- el = form.input;
+ el = form.elements['input'];
expect(Nette.getValue(el)).toBe('');
expect(Nette.validateRule(el, 'filled')).toBe(false);
@@ -348,7 +350,7 @@ describe('Nette.getValue & validateRule', () => {
`;
let form = testContainer.querySelector('form'),
- el = form['input[]'];
+ el = form.elements['input[]'];
expect(Nette.getValue(el)).toEqual([]);
expect(Nette.validateRule(el, 'filled')).toBe(false);
diff --git a/tests/netteForms/spec/Nette.validatorsSpec.js b/tests/netteForms/spec/Nette.validators.spec.js
similarity index 99%
rename from tests/netteForms/spec/Nette.validatorsSpec.js
rename to tests/netteForms/spec/Nette.validators.spec.js
index 0db1bd357..8139c72f2 100644
--- a/tests/netteForms/spec/Nette.validatorsSpec.js
+++ b/tests/netteForms/spec/Nette.validators.spec.js
@@ -1,3 +1,5 @@
+import { describe, it, expect } from 'vitest';
+
describe('Nette.validators', () => {
it('equal', () => {
diff --git a/tests/types/TypesTest.phpt b/tests/types/TypesTest.phpt
new file mode 100644
index 000000000..b32158903
--- /dev/null
+++ b/tests/types/TypesTest.phpt
@@ -0,0 +1,9 @@
+', $container->getValues());
+ assertType('array|array', $container->getValues('array'));
+ assertType(FormDto::class, $container->getValues(FormDto::class));
+}
+
+
+function testFormContainerGetUntrustedValues(Container $container): void
+{
+ assertType('Nette\Utils\ArrayHash', $container->getUntrustedValues(null));
+ assertType('array|array', $container->getUntrustedValues('array'));
+ assertType(FormDto::class, $container->getUntrustedValues(FormDto::class));
+}
+
+
+function testFormContainerArrayAccess(Container $container): void
+{
+ assertType('Nette\ComponentModel\IComponent', $container['name']);
+}
+
+
+function testFormEvents(Form $form): void
+{
+ $form->onSuccess[] = function (Form $form, ArrayHash $values): void {
+ assertType(Form::class, $form);
+ assertType(ArrayHash::class, $values);
+ };
+
+ $form->onError[] = function (Form $form): void {
+ assertType(Form::class, $form);
+ };
+}
diff --git a/vitest.config.ts b/vitest.config.ts
new file mode 100644
index 000000000..838b0d53e
--- /dev/null
+++ b/vitest.config.ts
@@ -0,0 +1,10 @@
+import { defineConfig } from 'vitest/config';
+
+export default defineConfig({
+ test: {
+ environment: 'jsdom',
+ include: ['tests/netteForms/spec/**/*.spec.js'],
+ setupFiles: ['./tests/netteForms/setup.js'],
+ globals: true,
+ },
+});