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 +
+ + {inputError username} + +
+``` + +**`{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} + +{/formContainer} +``` + +### Rendering Patterns + +**Automatic rendering** - Generic template for any form: +```latte +
+ + +
+ {label $input /} + {input $input} + {inputError $input} +
+
+``` + +**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; + el = form.elements['input']; expect(Nette.getValue(el)).toBe(null); expect(Nette.validateRule(el, 'filled')).toBe(false); @@ -290,7 +292,7 @@ describe('Nette.getValue & validateRule', () => {
`; 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, + }, +});