From 2d28182afe5d03ea318e3f1c6b29492e477dea10 Mon Sep 17 00:00:00 2001 From: francoism90 Date: Sat, 6 Apr 2024 21:04:46 +0200 Subject: [PATCH] Initial commit --- .editorconfig | 2 +- .eslintrc.cjs | 38 ++ .github/FUNDING.yml | 2 +- .github/ISSUE_TEMPLATE/bug.yml | 120 +++--- .github/ISSUE_TEMPLATE/config.yml | 6 +- .github/dependabot.yml | 17 +- .github/workflows/dependabot-auto-merge.yml | 5 +- .github/workflows/phpstan.yml | 28 -- .github/workflows/run-tests.yml | 12 +- .github/workflows/update-changelog.yml | 2 +- .gitignore | 1 + CHANGELOG.md | 2 +- LICENSE.md | 2 +- README.md | 60 +-- composer.json | 63 ++- config/skeleton.php | 6 - config/wireuse.php | 14 + configure.php | 366 ------------------ database/factories/ModelFactory.php | 19 - .../migrations/create_skeleton_table.php.stub | 19 - package.json | 24 ++ phpunit.xml.dist | 7 +- postcss.config.js | 8 + prettier.config.js | 7 + resources/views/.gitkeep | 0 src/Auth/Concerns/WithAuthentication.php | 28 ++ src/Auth/Concerns/WithAuthorization.php | 50 +++ src/Commands/SkeletonCommand.php | 19 - src/Exceptions/RateLimitedException.php | 19 + src/Facades/Skeleton.php | 16 - src/Facades/WireUse.php | 22 ++ src/Forms/Concerns/WithForm.php | 97 +++++ src/Forms/Concerns/WithForms.php | 17 + src/Forms/Concerns/WithSession.php | 66 ++++ src/Forms/Concerns/WithThrottle.php | 29 ++ src/Forms/Concerns/WithValidation.php | 22 ++ src/Forms/Support/Form.php | 50 +++ src/Models/Concerns/WithQueryBuilder.php | 50 +++ src/Models/Forms/CreateForm.php | 31 ++ src/Models/Forms/UpdateForm.php | 41 ++ src/Skeleton.php | 7 - src/SkeletonServiceProvider.php | 25 -- src/States/Concerns/WithStates.php | 15 + src/Support/Blade/Bladeable.php | 23 ++ src/Support/Concerns/WithHooks.php | 15 + src/Support/Concerns/WithRateLimiting.php | 48 +++ src/Support/Discover/ComponentScout.php | 49 +++ src/Support/Discover/LivewireScout.php | 17 + .../LegacyModels/EloquentCollectionSynth.php | 81 ++++ .../LegacyModels/EloquentModelSynth.php | 90 +++++ .../ModelStateObjectSynth.php | 26 ++ .../SupportModelStateObjects.php | 15 + .../Livewire/Models/CollectionSynth.php | 46 +++ src/Support/Livewire/Models/ModelSynth.php | 67 ++++ src/Support/Livewire/StateObjects/State.php | 106 +++++ .../StateObjects/StateObjectSynth.php | 69 ++++ .../StateObjects/SupportStateObjects.php | 61 +++ src/Views/Components/Component.php | 13 + src/Views/Components/Page.php | 19 + src/Views/Concerns/WithHash.php | 16 + src/Views/Concerns/WithLivewire.php | 24 ++ src/Views/Concerns/WithSeo.php | 21 + src/WireUse.php | 82 ++++ src/WireUseServiceProvider.php | 135 +++++++ tailwind.config.js | 10 + tests/ArchTest.php | 5 - tests/ExampleTest.php | 5 - tests/Pest.php | 14 +- tests/TestCase.php | 36 -- tests/database/factories/PostFactory.php | 23 ++ tests/database/factories/UserFactory.php | 25 ++ .../Synthesizers/PropertySynthesizerTest.php | 36 ++ tests/src/Models/Post.php | 32 ++ tests/src/Models/User.php | 38 ++ tests/src/Support/BladeDirectiveTest.php | 12 + tests/src/TestCase.php | 97 +++++ tests/src/TestClasses/BladeComponent.php | 23 ++ .../LivewireCollectionComponent.php | 20 + .../TestClasses/LivewireModelComponent.php | 20 + 79 files changed, 2127 insertions(+), 726 deletions(-) create mode 100644 .eslintrc.cjs delete mode 100644 .github/workflows/phpstan.yml delete mode 100644 config/skeleton.php create mode 100644 config/wireuse.php delete mode 100644 configure.php delete mode 100644 database/factories/ModelFactory.php delete mode 100644 database/migrations/create_skeleton_table.php.stub create mode 100644 package.json create mode 100644 postcss.config.js create mode 100644 prettier.config.js delete mode 100644 resources/views/.gitkeep create mode 100644 src/Auth/Concerns/WithAuthentication.php create mode 100644 src/Auth/Concerns/WithAuthorization.php delete mode 100644 src/Commands/SkeletonCommand.php create mode 100644 src/Exceptions/RateLimitedException.php delete mode 100644 src/Facades/Skeleton.php create mode 100644 src/Facades/WireUse.php create mode 100644 src/Forms/Concerns/WithForm.php create mode 100644 src/Forms/Concerns/WithForms.php create mode 100644 src/Forms/Concerns/WithSession.php create mode 100644 src/Forms/Concerns/WithThrottle.php create mode 100644 src/Forms/Concerns/WithValidation.php create mode 100644 src/Forms/Support/Form.php create mode 100644 src/Models/Concerns/WithQueryBuilder.php create mode 100644 src/Models/Forms/CreateForm.php create mode 100644 src/Models/Forms/UpdateForm.php delete mode 100755 src/Skeleton.php delete mode 100644 src/SkeletonServiceProvider.php create mode 100644 src/States/Concerns/WithStates.php create mode 100644 src/Support/Blade/Bladeable.php create mode 100644 src/Support/Concerns/WithHooks.php create mode 100644 src/Support/Concerns/WithRateLimiting.php create mode 100644 src/Support/Discover/ComponentScout.php create mode 100644 src/Support/Discover/LivewireScout.php create mode 100644 src/Support/Livewire/LegacyModels/EloquentCollectionSynth.php create mode 100644 src/Support/Livewire/LegacyModels/EloquentModelSynth.php create mode 100644 src/Support/Livewire/ModelStateObjects/ModelStateObjectSynth.php create mode 100644 src/Support/Livewire/ModelStateObjects/SupportModelStateObjects.php create mode 100644 src/Support/Livewire/Models/CollectionSynth.php create mode 100644 src/Support/Livewire/Models/ModelSynth.php create mode 100644 src/Support/Livewire/StateObjects/State.php create mode 100644 src/Support/Livewire/StateObjects/StateObjectSynth.php create mode 100644 src/Support/Livewire/StateObjects/SupportStateObjects.php create mode 100644 src/Views/Components/Component.php create mode 100644 src/Views/Components/Page.php create mode 100644 src/Views/Concerns/WithHash.php create mode 100644 src/Views/Concerns/WithLivewire.php create mode 100644 src/Views/Concerns/WithSeo.php create mode 100755 src/WireUse.php create mode 100644 src/WireUseServiceProvider.php create mode 100644 tailwind.config.js delete mode 100644 tests/ArchTest.php delete mode 100644 tests/ExampleTest.php delete mode 100644 tests/TestCase.php create mode 100644 tests/database/factories/PostFactory.php create mode 100644 tests/database/factories/UserFactory.php create mode 100644 tests/src/Livewire/Synthesizers/PropertySynthesizerTest.php create mode 100644 tests/src/Models/Post.php create mode 100644 tests/src/Models/User.php create mode 100644 tests/src/Support/BladeDirectiveTest.php create mode 100644 tests/src/TestCase.php create mode 100644 tests/src/TestClasses/BladeComponent.php create mode 100644 tests/src/TestClasses/LivewireCollectionComponent.php create mode 100644 tests/src/TestClasses/LivewireModelComponent.php diff --git a/.editorconfig b/.editorconfig index dd9a2b51..94b654a4 100644 --- a/.editorconfig +++ b/.editorconfig @@ -11,5 +11,5 @@ trim_trailing_whitespace = true [*.md] trim_trailing_whitespace = false -[*.{yml,yaml}] +[*.{js,cjs,ts,yml,yaml}] indent_size = 2 diff --git a/.eslintrc.cjs b/.eslintrc.cjs new file mode 100644 index 00000000..b34602a5 --- /dev/null +++ b/.eslintrc.cjs @@ -0,0 +1,38 @@ +module.exports = { + env: { + browser: true, + es2021: true, + node: true, + }, + extends: [], + overrides: [ + { + files: ['.eslintrc.{js,cjs}'], + parserOptions: { + sourceType: 'script', + }, + }, + { + files: ['*.ts', '*.tsx', '*.js'], + parser: '@typescript-eslint/parser', + extends: ['airbnb-base', 'plugin:tailwindcss/recommended'], + rules: { + 'import/no-extraneous-dependencies': 'off', + 'import/no-unresolved': 'off', + 'tailwindcss/no-custom-classname': 'off', + }, + }, + { + files: ['*.html', '*.blade.php'], + parser: '@angular-eslint/template-parser', + extends: ['plugin:tailwindcss/recommended'], + rules: { + 'tailwindcss/no-custom-classname': 'off', + }, + }, + ], + parserOptions: { + ecmaVersion: 'latest', + sourceType: 'module', + }, +}; diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index c68765b0..79a3d16e 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1 +1 @@ -github: :vendor_name +github: foxws diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml index fe4cfe6d..8511f775 100644 --- a/.github/ISSUE_TEMPLATE/bug.yml +++ b/.github/ISSUE_TEMPLATE/bug.yml @@ -1,66 +1,66 @@ name: Bug Report description: Report an Issue or Bug with the Package -title: "[Bug]: " -labels: ["bug"] +title: '[Bug]: ' +labels: ['bug'] body: - - type: markdown - attributes: - value: | - We're sorry to hear you have a problem. Can you help us solve it by providing the following details. - - type: textarea - id: what-happened - attributes: - label: What happened? - description: What did you expect to happen? - placeholder: I cannot currently do X thing because when I do, it breaks X thing. - validations: - required: true - - type: textarea - id: how-to-reproduce - attributes: - label: How to reproduce the bug - description: How did this occur, please add any config values used and provide a set of reliable steps if possible. - placeholder: When I do X I see Y. - validations: - required: true - - type: input - id: package-version - attributes: - label: Package Version - description: What version of our Package are you running? Please be as specific as possible - placeholder: 2.0.0 - validations: - required: true - - type: input - id: php-version - attributes: - label: PHP Version - description: What version of PHP are you running? Please be as specific as possible - placeholder: 8.2.0 - validations: - required: true - - type: input - id: laravel-version - attributes: - label: Laravel Version - description: What version of Laravel are you running? Please be as specific as possible - placeholder: 9.0.0 - validations: - required: true - - type: dropdown - id: operating-systems - attributes: - label: Which operating systems does with happen with? - description: You may select more than one. - multiple: true - options: + - type: markdown + attributes: + value: | + We're sorry to hear you have a problem. Can you help us solve it by providing the following details. + - type: textarea + id: what-happened + attributes: + label: What happened? + description: What did you expect to happen? + placeholder: I cannot currently do X thing because when I do, it breaks X thing. + validations: + required: true + - type: textarea + id: how-to-reproduce + attributes: + label: How to reproduce the bug + description: How did this occur, please add any config values used and provide a set of reliable steps if possible. + placeholder: When I do X I see Y. + validations: + required: true + - type: input + id: package-version + attributes: + label: Package Version + description: What version of our Package are you running? Please be as specific as possible + placeholder: 2.0.0 + validations: + required: true + - type: input + id: php-version + attributes: + label: PHP Version + description: What version of PHP are you running? Please be as specific as possible + placeholder: 8.2.0 + validations: + required: true + - type: input + id: laravel-version + attributes: + label: Laravel Version + description: What version of Laravel are you running? Please be as specific as possible + placeholder: 9.0.0 + validations: + required: true + - type: dropdown + id: operating-systems + attributes: + label: Which operating systems does with happen with? + description: You may select more than one. + multiple: true + options: - macOS - Windows - Linux - - type: textarea - id: notes - attributes: - label: Notes - description: Use this field to provide any other notes that you feel might be relevant to the issue. - validations: - required: false + - type: textarea + id: notes + attributes: + label: Notes + description: Use this field to provide any other notes that you feel might be relevant to the issue. + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 96701be9..59c92069 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,11 +1,11 @@ blank_issues_enabled: false contact_links: - name: Ask a question - url: https://github.com/:vendor_name/:package_name/discussions/new?category=q-a + url: https://github.com/foxws/wireuse/discussions/new?category=q-a about: Ask the community for help - name: Request a feature - url: https://github.com/:vendor_name/:package_name/discussions/new?category=ideas + url: https://github.com/foxws/wireuse/discussions/new?category=ideas about: Share ideas for new features - name: Report a security issue - url: https://github.com/:vendor_name/:package_name/security/policy + url: https://github.com/foxws/wireuse/security/policy about: Learn how to notify us for sensitive bugs diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 39b15807..c0b95018 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -3,17 +3,16 @@ version: 2 updates: - - - package-ecosystem: "github-actions" - directory: "/" + - package-ecosystem: 'github-actions' + directory: '/' schedule: - interval: "weekly" + interval: 'weekly' labels: - - "dependencies" + - 'dependencies' - - package-ecosystem: "composer" - directory: "/" + - package-ecosystem: 'composer' + directory: '/' schedule: - interval: "weekly" + interval: 'weekly' labels: - - "dependencies" + - 'dependencies' diff --git a/.github/workflows/dependabot-auto-merge.yml b/.github/workflows/dependabot-auto-merge.yml index 70d8e7bd..770a709c 100644 --- a/.github/workflows/dependabot-auto-merge.yml +++ b/.github/workflows/dependabot-auto-merge.yml @@ -11,12 +11,11 @@ jobs: timeout-minutes: 5 if: ${{ github.actor == 'dependabot[bot]' }} steps: - - name: Dependabot metadata id: metadata - uses: dependabot/fetch-metadata@v1.6.0 + uses: dependabot/fetch-metadata@v2.0.0 with: - github-token: "${{ secrets.GITHUB_TOKEN }}" + github-token: '${{ secrets.GITHUB_TOKEN }}' - name: Auto-merge Dependabot PRs for semver-minor updates if: ${{steps.metadata.outputs.update-type == 'version-update:semver-minor'}} diff --git a/.github/workflows/phpstan.yml b/.github/workflows/phpstan.yml deleted file mode 100644 index f495e76d..00000000 --- a/.github/workflows/phpstan.yml +++ /dev/null @@ -1,28 +0,0 @@ -name: PHPStan - -on: - push: - paths: - - '**.php' - - 'phpstan.neon.dist' - - '.github/workflows/phpstan.yml' - -jobs: - phpstan: - name: phpstan - runs-on: ubuntu-latest - timeout-minutes: 5 - steps: - - uses: actions/checkout@v4 - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: '8.1' - coverage: none - - - name: Install composer dependencies - uses: ramsey/composer-install@v3 - - - name: Run PHPStan - run: ./vendor/bin/phpstan --error-format=github diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index f7492c54..5f16eefc 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -16,14 +16,12 @@ jobs: strategy: fail-fast: true matrix: - os: [ubuntu-latest, windows-latest] - php: [8.3, 8.2, 8.1] - laravel: [10.*] + os: [ubuntu-latest] + php: [8.3, 8.2] stability: [prefer-lowest, prefer-stable] include: - - laravel: 10.* - testbench: 8.* - carbon: ^2.63 + - laravel: 11.* + carbon: ^3.1.1 name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.stability }} - ${{ matrix.os }} @@ -45,7 +43,7 @@ jobs: - name: Install dependencies run: | - composer require "laravel/framework:${{ matrix.laravel }}" "orchestra/testbench:${{ matrix.testbench }}" "nesbot/carbon:${{ matrix.carbon }}" --no-interaction --no-update + composer require "laravel/framework:${{ matrix.laravel }}" "nesbot/carbon:${{ matrix.carbon }}" --no-interaction --no-update composer update --${{ matrix.stability }} --prefer-dist --no-interaction - name: List Installed Dependencies diff --git a/.github/workflows/update-changelog.yml b/.github/workflows/update-changelog.yml index 39de30d6..22e0edcb 100644 --- a/.github/workflows/update-changelog.yml +++ b/.github/workflows/update-changelog.yml @@ -1,4 +1,4 @@ -name: "Update Changelog" +name: 'Update Changelog' on: release: diff --git a/.gitignore b/.gitignore index a7f372d8..731068ce 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ .phpunit.cache build composer.lock +package-lock.json coverage docs phpunit.xml diff --git a/CHANGELOG.md b/CHANGELOG.md index 87b3242a..0377e614 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,3 @@ # Changelog -All notable changes to `:package_name` will be documented in this file. +All notable changes to `wireuse` will be documented in this file. diff --git a/LICENSE.md b/LICENSE.md index 58c9ad42..6f0d85db 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) :vendor_name +Copyright (c) Foxws Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 375da962..c6fa14a1 100644 --- a/README.md +++ b/README.md @@ -1,69 +1,29 @@ -# :package_description +# WireUse -[![Latest Version on Packagist](https://img.shields.io/packagist/v/:vendor_slug/:package_slug.svg?style=flat-square)](https://packagist.org/packages/:vendor_slug/:package_slug) -[![GitHub Tests Action Status](https://img.shields.io/github/actions/workflow/status/:vendor_slug/:package_slug/run-tests.yml?branch=main&label=tests&style=flat-square)](https://github.com/:vendor_slug/:package_slug/actions?query=workflow%3Arun-tests+branch%3Amain) -[![GitHub Code Style Action Status](https://img.shields.io/github/actions/workflow/status/:vendor_slug/:package_slug/fix-php-code-style-issues.yml?branch=main&label=code%20style&style=flat-square)](https://github.com/:vendor_slug/:package_slug/actions?query=workflow%3A"Fix+PHP+code+style+issues"+branch%3Amain) -[![Total Downloads](https://img.shields.io/packagist/dt/:vendor_slug/:package_slug.svg?style=flat-square)](https://packagist.org/packages/:vendor_slug/:package_slug) - ---- -This repo can be used to scaffold a Laravel package. Follow these steps to get started: +[![Latest Version on Packagist](https://img.shields.io/packagist/v/foxws/wireuse.svg?style=flat-square)](https://packagist.org/packages/foxws/wireuse) +[![GitHub Tests Action Status](https://img.shields.io/github/actions/workflow/status/foxws/wireuse/run-tests.yml?branch=3.x&label=tests&style=flat-square)](https://github.com/foxws/wireuse/actions?query=workflow%3Arun-tests+branch%3A3.x) +[![GitHub Code Style Action Status](https://img.shields.io/github/actions/workflow/status/foxws/wireuse/fix-php-code-style-issues.yml?branch=3.x&label=code%20style&style=flat-square)](https://github.com/foxws/wireuse/actions?query=workflow%3A"Fix+PHP+code+style+issues"+branch%3A3.x) +[![Total Downloads](https://img.shields.io/packagist/dt/foxws/wireuse.svg?style=flat-square)](https://packagist.org/packages/foxws/wireuse) -1. Press the "Use this template" button at the top of this repo to create a new repo with the contents of this skeleton. -2. Run "php ./configure.php" to run a script that will replace all placeholders throughout all the files. -3. Have fun creating your package. -4. If you need help creating a package, consider picking up our Laravel Package Training video course. ---- - -This is where your description should go. Limit it to a paragraph or two. Consider adding a small example. - -## Support us - -[](https://spatie.be/github-ad-click/:package_name) - -We invest a lot of resources into creating [best in class open source packages](https://spatie.be/open-source). You can support us by [buying one of our paid products](https://spatie.be/open-source/support-us). - -We highly appreciate you sending us a postcard from your hometown, mentioning which of our package(s) you are using. You'll find our address on [our contact page](https://spatie.be/about-us). We publish all received postcards on [our virtual postcard wall](https://spatie.be/open-source/postcards). +Collection of essential Livewire Utilities. ## Installation You can install the package via composer: ```bash -composer require :vendor_slug/:package_slug -``` - -You can publish and run the migrations with: - -```bash -php artisan vendor:publish --tag=":package_slug-migrations" -php artisan migrate +composer require foxws/wireuse ``` You can publish the config file with: ```bash -php artisan vendor:publish --tag=":package_slug-config" -``` - -This is the contents of the published config file: - -```php -return [ -]; -``` - -Optionally, you can publish the views using - -```bash -php artisan vendor:publish --tag=":package_slug-views" +php artisan vendor:publish --tag="wireuse-config" ``` ## Usage -```php -$variable = new VendorName\Skeleton(); -echo $variable->echoPhrase('Hello, VendorName!'); -``` +To learn more about WireUse, please read our [documentation](https://foxws.nl/projects/wireuse)! ## Testing @@ -85,7 +45,7 @@ Please review [our security policy](../../security/policy) on how to report secu ## Credits -- [:author_name](https://github.com/:author_username) +- [Foxws](https://github.com/foxws) - [All Contributors](../../contributors) ## License diff --git a/composer.json b/composer.json index 65e9908b..47a49177 100644 --- a/composer.json +++ b/composer.json @@ -1,63 +1,56 @@ { - "name": ":vendor_slug/:package_slug", - "description": ":package_description", + "name": "foxws/wireuse", + "description": "Collection of essential Livewire utilities", "keywords": [ - ":vendor_name", + "foxws", "laravel", - ":package_slug" + "livewire", + "wireuse" ], - "homepage": "https://github.com/:vendor_slug/:package_slug", + "homepage": "https://github.com/foxws/wireuse", "license": "MIT", "authors": [ { - "name": ":author_name", - "email": "author@domain.com", + "name": "Foxws", + "email": "foxws@users.noreply.github.com", "role": "Developer" } ], "require": { "php": "^8.2", - "spatie/laravel-package-tools": "^1.16", - "illuminate/contracts": "^10.0||^11.0" + "artesaos/seotools": "^1.3", + "illuminate/contracts": "^10.0|^11.0", + "laravel/scout": "^10.0|^11.0", + "livewire/livewire": "^3.4", + "spatie/laravel-model-states": "^2.7", + "spatie/laravel-package-tools": "^1.16.3", + "spatie/php-structure-discoverer": "^2.1" }, "require-dev": { - "laravel/pint": "^1.14", - "nunomaduro/collision": "^8.1.1||^7.10.0", - "larastan/larastan": "^2.9", - "orchestra/testbench": "^9.0.0||^8.22.0", - "pestphp/pest": "^2.34", + "laravel/pint": "^1.14.0", + "nunomaduro/collision": "^8.0", + "orchestra/testbench": "^9.0", + "pestphp/pest": "^2.34.4", "pestphp/pest-plugin-arch": "^2.7", "pestphp/pest-plugin-laravel": "^2.3", - "phpstan/extension-installer": "^1.3", - "phpstan/phpstan-deprecation-rules": "^1.1", - "phpstan/phpstan-phpunit": "^1.3", - "spatie/laravel-ray": "^1.35" + "phpstan/extension-installer": "^1.3.1", + "phpstan/phpstan-deprecation-rules": "^1.1.4", + "phpstan/phpstan-phpunit": "^1.3.16", + "spatie/laravel-ray": "^1.35.1" }, "autoload": { "psr-4": { - "VendorName\\Skeleton\\": "src/", - "VendorName\\Skeleton\\Database\\Factories\\": "database/factories/" + "Foxws\\WireUse\\": "src/" } }, "autoload-dev": { "psr-4": { - "VendorName\\Skeleton\\Tests\\": "tests/", + "Foxws\\WireUse\\Tests\\": "tests/src/", + "Foxws\\WireUse\\Tests\\Database\\Factories\\": "tests/database/factories/", "Workbench\\App\\": "workbench/app/" } }, "scripts": { - "post-autoload-dump": "@composer run prepare", - "clear": "@php vendor/bin/testbench package:purge-skeleton --ansi", - "prepare": "@php vendor/bin/testbench package:discover --ansi", - "build": [ - "@composer run prepare", - "@php vendor/bin/testbench workbench:build --ansi" - ], - "start": [ - "Composer\\Config::disableProcessTimeout", - "@composer run build", - "@php vendor/bin/testbench serve" - ], "analyse": "vendor/bin/phpstan analyse", "test": "vendor/bin/pest", "test-coverage": "vendor/bin/pest --coverage", @@ -73,10 +66,10 @@ "extra": { "laravel": { "providers": [ - "VendorName\\Skeleton\\SkeletonServiceProvider" + "Foxws\\WireUse\\WireUseServiceProvider" ], "aliases": { - "Skeleton": "VendorName\\Skeleton\\Facades\\Skeleton" + "WireUse": "Foxws\\WireUse\\Facades\\WireUse" } } }, diff --git a/config/skeleton.php b/config/skeleton.php deleted file mode 100644 index 7e741865..00000000 --- a/config/skeleton.php +++ /dev/null @@ -1,6 +0,0 @@ - true, +]; diff --git a/configure.php b/configure.php deleted file mode 100644 index 085b8780..00000000 --- a/configure.php +++ /dev/null @@ -1,366 +0,0 @@ -#!/usr/bin/env php - $version) { - if (in_array($name, $names, true)) { - unset($data['require-dev'][$name]); - } - } - - file_put_contents(__DIR__.'/composer.json', json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)); -} - -function remove_composer_script($scriptName) -{ - $data = json_decode(file_get_contents(__DIR__.'/composer.json'), true); - - foreach ($data['scripts'] as $name => $script) { - if ($scriptName === $name) { - unset($data['scripts'][$name]); - break; - } - } - - file_put_contents(__DIR__.'/composer.json', json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)); -} - -function remove_readme_paragraphs(string $file): void -{ - $contents = file_get_contents($file); - - file_put_contents( - $file, - preg_replace('/.*/s', '', $contents) ?: $contents - ); -} - -function safeUnlink(string $filename) -{ - if (file_exists($filename) && is_file($filename)) { - unlink($filename); - } -} - -function determineSeparator(string $path): string -{ - return str_replace('/', DIRECTORY_SEPARATOR, $path); -} - -function replaceForWindows(): array -{ - return preg_split('/\\r\\n|\\r|\\n/', run('dir /S /B * | findstr /v /i .git\ | findstr /v /i vendor | findstr /v /i '.basename(__FILE__).' | findstr /r /i /M /F:/ ":author :vendor :package VendorName skeleton migration_table_name vendor_name vendor_slug author@domain.com"')); -} - -function replaceForAllOtherOSes(): array -{ - return explode(PHP_EOL, run('grep -E -r -l -i ":author|:vendor|:package|VendorName|skeleton|migration_table_name|vendor_name|vendor_slug|author@domain.com" --exclude-dir=vendor ./* ./.github/* | grep -v '.basename(__FILE__))); -} - -function getGitHubApiEndpoint(string $endpoint): ?stdClass -{ - try { - $curl = curl_init("https://api.github.com/{$endpoint}"); - curl_setopt_array($curl, [ - CURLOPT_RETURNTRANSFER => true, - CURLOPT_FOLLOWLOCATION => true, - CURLOPT_HTTPGET => true, - CURLOPT_HTTPHEADER => [ - 'User-Agent: spatie-configure-script/1.0', - ], - ]); - - $response = curl_exec($curl); - $statusCode = curl_getinfo($curl, CURLINFO_HTTP_CODE); - - curl_close($curl); - - if ($statusCode === 200) { - return json_decode($response); - } - } catch (Exception $e) { - // ignore - } - - return null; -} - -function searchCommitsForGitHubUsername(): string -{ - $authorName = strtolower(trim(shell_exec('git config user.name'))); - - $committersRaw = shell_exec("git log --author='@users.noreply.github.com' --pretty='%an:%ae' --reverse"); - $committersLines = explode("\n", $committersRaw ?? ''); - $committers = array_filter(array_map(function ($line) use ($authorName) { - $line = trim($line); - [$name, $email] = explode(':', $line) + [null, null]; - - return [ - 'name' => $name, - 'email' => $email, - 'isMatch' => strtolower($name) === $authorName && ! str_contains($name, '[bot]'), - ]; - }, $committersLines), fn ($item) => $item['isMatch']); - - if (empty($committers)) { - return ''; - } - - $firstCommitter = reset($committers); - - return explode('@', $firstCommitter['email'])[0] ?? ''; -} - -function guessGitHubUsernameUsingCli() -{ - try { - if (preg_match('/ogged in to github\.com as ([a-zA-Z-_]+).+/', shell_exec('gh auth status -h github.com 2>&1'), $matches)) { - return $matches[1]; - } - } catch (Exception $e) { - // ignore - } - - return ''; -} - -function guessGitHubUsername(): string -{ - $username = searchCommitsForGitHubUsername(); - if (! empty($username)) { - return $username; - } - - $username = guessGitHubUsernameUsingCli(); - if (! empty($username)) { - return $username; - } - - // fall back to using the username from the git remote - $remoteUrl = shell_exec('git config remote.origin.url'); - $remoteUrlParts = explode('/', str_replace(':', '/', trim($remoteUrl))); - - return $remoteUrlParts[1] ?? ''; -} - -function guessGitHubVendorInfo($authorName, $username): array -{ - $remoteUrl = shell_exec('git config remote.origin.url'); - $remoteUrlParts = explode('/', str_replace(':', '/', trim($remoteUrl))); - - $response = getGitHubApiEndpoint("orgs/{$remoteUrlParts[1]}"); - - if ($response === null) { - return [$authorName, $username]; - } - - return [$response->name ?? $authorName, $response->login ?? $username]; -} - -$gitName = run('git config user.name'); -$authorName = ask('Author name', $gitName); - -$gitEmail = run('git config user.email'); -$authorEmail = ask('Author email', $gitEmail); -$authorUsername = ask('Author username', guessGitHubUsername()); - -$guessGitHubVendorInfo = guessGitHubVendorInfo($authorName, $authorUsername); - -$vendorName = ask('Vendor name', $guessGitHubVendorInfo[0]); -$vendorUsername = ask('Vendor username', $guessGitHubVendorInfo[1] ?? slugify($vendorName)); -$vendorSlug = slugify($vendorUsername); - -$vendorNamespace = str_replace('-', '', ucwords($vendorName)); -$vendorNamespace = ask('Vendor namespace', $vendorNamespace); - -$currentDirectory = getcwd(); -$folderName = basename($currentDirectory); - -$packageName = ask('Package name', $folderName); -$packageSlug = slugify($packageName); -$packageSlugWithoutPrefix = remove_prefix('laravel-', $packageSlug); - -$className = title_case($packageName); -$className = ask('Class name', $className); -$variableName = lcfirst($className); -$description = ask('Package description', "This is my package {$packageSlug}"); - -$usePhpStan = confirm('Enable PhpStan?', true); -$useLaravelPint = confirm('Enable Laravel Pint?', true); -$useDependabot = confirm('Enable Dependabot?', true); -$useLaravelRay = confirm('Use Ray for debugging?', true); -$useUpdateChangelogWorkflow = confirm('Use automatic changelog updater workflow?', true); - -writeln('------'); -writeln("Author : {$authorName} ({$authorUsername}, {$authorEmail})"); -writeln("Vendor : {$vendorName} ({$vendorSlug})"); -writeln("Package : {$packageSlug} <{$description}>"); -writeln("Namespace : {$vendorNamespace}\\{$className}"); -writeln("Class name : {$className}"); -writeln('---'); -writeln('Packages & Utilities'); -writeln('Use Laravel/Pint : '.($useLaravelPint ? 'yes' : 'no')); -writeln('Use Larastan/PhpStan : '.($usePhpStan ? 'yes' : 'no')); -writeln('Use Dependabot : '.($useDependabot ? 'yes' : 'no')); -writeln('Use Ray App : '.($useLaravelRay ? 'yes' : 'no')); -writeln('Use Auto-Changelog : '.($useUpdateChangelogWorkflow ? 'yes' : 'no')); -writeln('------'); - -writeln('This script will replace the above values in all relevant files in the project directory.'); - -if (! confirm('Modify files?', true)) { - exit(1); -} - -$files = (str_starts_with(strtoupper(PHP_OS), 'WIN') ? replaceForWindows() : replaceForAllOtherOSes()); - -foreach ($files as $file) { - replace_in_file($file, [ - ':author_name' => $authorName, - ':author_username' => $authorUsername, - 'author@domain.com' => $authorEmail, - ':vendor_name' => $vendorName, - ':vendor_slug' => $vendorSlug, - 'VendorName' => $vendorNamespace, - ':package_name' => $packageName, - ':package_slug' => $packageSlug, - ':package_slug_without_prefix' => $packageSlugWithoutPrefix, - 'Skeleton' => $className, - 'skeleton' => $packageSlug, - 'migration_table_name' => title_snake($packageSlug), - 'variable' => $variableName, - ':package_description' => $description, - ]); - - match (true) { - str_contains($file, determineSeparator('src/Skeleton.php')) => rename($file, determineSeparator('./src/'.$className.'.php')), - str_contains($file, determineSeparator('src/SkeletonServiceProvider.php')) => rename($file, determineSeparator('./src/'.$className.'ServiceProvider.php')), - str_contains($file, determineSeparator('src/Facades/Skeleton.php')) => rename($file, determineSeparator('./src/Facades/'.$className.'.php')), - str_contains($file, determineSeparator('src/Commands/SkeletonCommand.php')) => rename($file, determineSeparator('./src/Commands/'.$className.'Command.php')), - str_contains($file, determineSeparator('database/migrations/create_skeleton_table.php.stub')) => rename($file, determineSeparator('./database/migrations/create_'.title_snake($packageSlugWithoutPrefix).'_table.php.stub')), - str_contains($file, determineSeparator('config/skeleton.php')) => rename($file, determineSeparator('./config/'.$packageSlugWithoutPrefix.'.php')), - str_contains($file, 'README.md') => remove_readme_paragraphs($file), - default => [], - }; -} - -if (! $useLaravelPint) { - safeUnlink(__DIR__.'/.github/workflows/fix-php-code-style-issues.yml'); - safeUnlink(__DIR__.'/pint.json'); -} - -if (! $usePhpStan) { - safeUnlink(__DIR__.'/phpstan.neon.dist'); - safeUnlink(__DIR__.'/phpstan-baseline.neon'); - safeUnlink(__DIR__.'/.github/workflows/phpstan.yml'); - - remove_composer_deps([ - 'phpstan/extension-installer', - 'phpstan/phpstan-deprecation-rules', - 'phpstan/phpstan-phpunit', - 'larastan/larastan', - ]); - - remove_composer_script('phpstan'); -} - -if (! $useDependabot) { - safeUnlink(__DIR__.'/.github/dependabot.yml'); - safeUnlink(__DIR__.'/.github/workflows/dependabot-auto-merge.yml'); -} - -if (! $useLaravelRay) { - remove_composer_deps(['spatie/laravel-ray']); -} - -if (! $useUpdateChangelogWorkflow) { - safeUnlink(__DIR__.'/.github/workflows/update-changelog.yml'); -} - -confirm('Execute `composer install` and run tests?') && run('composer install && composer test'); - -confirm('Let this script delete itself?', true) && unlink(__FILE__); diff --git a/database/factories/ModelFactory.php b/database/factories/ModelFactory.php deleted file mode 100644 index c51604f4..00000000 --- a/database/factories/ModelFactory.php +++ /dev/null @@ -1,19 +0,0 @@ -id(); - - // add fields - - $table->timestamps(); - }); - } -}; diff --git a/package.json b/package.json new file mode 100644 index 00000000..ebdbc741 --- /dev/null +++ b/package.json @@ -0,0 +1,24 @@ +{ + "private": true, + "type": "module", + "scripts": { + "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.ctx,.mts --fix --ignore-path .gitignore", + "format": "npx prettier . --write" + }, + "devDependencies": { + "@angular-eslint/template-parser": "^17.3.0", + "@tailwindcss/forms": "^0.5.7", + "@tailwindcss/typography": "^0.5.10", + "@typescript-eslint/parser": "^7.2.0", + "autoprefixer": "^10.4.18", + "eslint": "^8.57.0", + "eslint-config-airbnb-base": "^15.0.0", + "eslint-plugin-import": "^2.29.1", + "eslint-plugin-tailwindcss": "^3.15.1", + "postcss": "^8.4.35", + "postcss-import": "^16.0.1", + "prettier": "^3.2.5", + "prettier-plugin-tailwindcss": "^0.5.12", + "tailwindcss": "^3.4.1" + } +} diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 4c610422..532a4fd9 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -16,7 +16,7 @@ backupStaticProperties="false" > - + tests @@ -35,4 +35,9 @@ ./src + + + + + diff --git a/postcss.config.js b/postcss.config.js new file mode 100644 index 00000000..feab3515 --- /dev/null +++ b/postcss.config.js @@ -0,0 +1,8 @@ +export default { + plugins: { + 'postcss-import': {}, + 'tailwindcss/nesting': {}, + tailwindcss: {}, + autoprefixer: {}, + }, +}; diff --git a/prettier.config.js b/prettier.config.js new file mode 100644 index 00000000..4f26fae5 --- /dev/null +++ b/prettier.config.js @@ -0,0 +1,7 @@ +export default { + trailingComma: 'es5', + semi: true, + singleQuote: true, + printWidth: 180, + plugins: ['prettier-plugin-tailwindcss'], +}; diff --git a/resources/views/.gitkeep b/resources/views/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/src/Auth/Concerns/WithAuthentication.php b/src/Auth/Concerns/WithAuthentication.php new file mode 100644 index 00000000..f10c585a --- /dev/null +++ b/src/Auth/Concerns/WithAuthentication.php @@ -0,0 +1,28 @@ +check(); + } + + protected static function getAuthUser(): ?User + { + return auth()->user(); + } + + protected static function getAuthId(): int|string|null + { + return auth()->id(); + } + + protected static function getAuthKey(): int|string|null + { + return static::getAuthUser()?->getRouteKey(); + } +} diff --git a/src/Auth/Concerns/WithAuthorization.php b/src/Auth/Concerns/WithAuthorization.php new file mode 100644 index 00000000..a570cfba --- /dev/null +++ b/src/Auth/Concerns/WithAuthorization.php @@ -0,0 +1,50 @@ +authorizeAccess(); + } + + protected function authorizeAccess(): void + { + // $this->canViewAny(Todo::class); + } + + protected function canViewAny(mixed $arguments): void + { + if ($arguments instanceof Model) { + $arguments = $arguments->getMorphClass(); + } + + $this->authorize('viewAny', $arguments); + } + + protected function canView(mixed $arguments): void + { + $this->authorize('view', $arguments); + } + + protected function canCreate(mixed $arguments): void + { + $this->authorize('create', $arguments); + } + + protected function canUpdate(mixed $arguments): void + { + $this->authorize('update', $arguments); + } + + protected function canDelete(mixed $arguments): void + { + $this->authorize('delete', $arguments); + } +} diff --git a/src/Commands/SkeletonCommand.php b/src/Commands/SkeletonCommand.php deleted file mode 100644 index 3e5f6280..00000000 --- a/src/Commands/SkeletonCommand.php +++ /dev/null @@ -1,19 +0,0 @@ -comment('All done'); - - return self::SUCCESS; - } -} diff --git a/src/Exceptions/RateLimitedException.php b/src/Exceptions/RateLimitedException.php new file mode 100644 index 00000000..5ddad950 --- /dev/null +++ b/src/Exceptions/RateLimitedException.php @@ -0,0 +1,19 @@ +ip, + $this->seconds, + )); + } +} diff --git a/src/Facades/Skeleton.php b/src/Facades/Skeleton.php deleted file mode 100644 index 571a498a..00000000 --- a/src/Facades/Skeleton.php +++ /dev/null @@ -1,16 +0,0 @@ -getType()->getName(); + } + + protected function collect(...$properties): Collection + { + return $properties + ? collect($this->only(...$properties)) + : collect($this->all()); + } + + protected function keys(): array + { + return array_keys($this->all()); + } + + public function get(string $property, mixed $default = null): mixed + { + return $this->getPropertyValue($property) ?: $default; + } + + public function has(...$properties): bool + { + return $this->collect() + ->has($properties); + } + + public function contains(string $property, mixed $args): bool + { + $propertyValue = $this->get($property); + + if (is_array($propertyValue)) { + return in_array($args, $propertyValue); + } + + return $propertyValue === $args; + } + + public function is(string $property, mixed $args = null): bool + { + return $this->get($property) == $args; + } + + public function isStrict(string $property, mixed $args = null): bool + { + return $this->get($property) === $args; + } + + public function filled(...$properties): bool + { + return $this->collect($properties) + ->filter() + ->isNotEmpty(); + } + + public function blank(...$properties): bool + { + return $this->collect($properties) + ->filter() + ->isEmpty(); + } + + public function clear(bool $submit = true): void + { + $properties = $this->keys(); + + $this->reset($properties); + + if ($submit && method_exists($this, 'submit')) { + $this->submit(); + } + } + + public function fails($rules = null, $messages = [], $attributes = []): bool + { + try { + $this->parentValidate($rules, $messages, $attributes); + } catch (ValidationException $e) { + return invade($e->validator)->fails(); + } + + return false; + } +} diff --git a/src/Forms/Concerns/WithForms.php b/src/Forms/Concerns/WithForms.php new file mode 100644 index 00000000..1284f7a4 --- /dev/null +++ b/src/Forms/Concerns/WithForms.php @@ -0,0 +1,17 @@ +getFormObjects()); + + return blank($name) + ? $forms->first() + : $forms->firstWhere('propertyName', $name); + } +} diff --git a/src/Forms/Concerns/WithSession.php b/src/Forms/Concerns/WithSession.php new file mode 100644 index 00000000..89a4d82e --- /dev/null +++ b/src/Forms/Concerns/WithSession.php @@ -0,0 +1,66 @@ +hasStore()) { + return; + } + + rescue( + fn () => $this->fill($this->getStore()) && $this->validate(), + fn () => $this->resetStore(), + ); + } + + public function store(): void + { + if (! static::$store || ! $this->storeWhen()) { + return; + } + + // Make sure to not store any invalid data + $this->validate(); + + session()->put($this->storeId(), serialize($this->storeWith())); + } + + public function forget(): void + { + session()->forget($this->storeId()); + } + + protected function getStore(): array + { + return unserialize(session()->get($this->storeId(), [])); + } + + protected function hasStore(): bool + { + return session()->has($this->storeId()); + } + + protected function storeWhen(): bool + { + return true; + } + + protected function storeWith(): array + { + return $this->all(); + } + + protected function storeId(): string + { + return $this->classHash(); + } +} diff --git a/src/Forms/Concerns/WithThrottle.php b/src/Forms/Concerns/WithThrottle.php new file mode 100644 index 00000000..d040c672 --- /dev/null +++ b/src/Forms/Concerns/WithThrottle.php @@ -0,0 +1,29 @@ +getThrottleModel(); + + $this->resetErrorBag($field); + + $this->addError($field, __('Please retry in :seconds seconds', [ + 'seconds' => $e->seconds ?? 0, + ])); + } + + protected function getThrottleModel(): string + { + $fields = array_keys($this->all()); + + return $fields[0] ?? 'throttled'; + } +} diff --git a/src/Forms/Concerns/WithValidation.php b/src/Forms/Concerns/WithValidation.php new file mode 100644 index 00000000..072a6546 --- /dev/null +++ b/src/Forms/Concerns/WithValidation.php @@ -0,0 +1,22 @@ +validate(); + + return; + } + + rescue( + fn () => $this->validate(), + fn () => $this->reset(), + ); + } +} diff --git a/src/Forms/Support/Form.php b/src/Forms/Support/Form.php new file mode 100644 index 00000000..04c9ac6c --- /dev/null +++ b/src/Forms/Support/Form.php @@ -0,0 +1,50 @@ +rateLimit(); + + $this->callHook('beforeValidate'); + + $this->check(); + + $this->callHook('afterValidate'); + + $this->store(); + + $this->callHook('beforeHandle'); + + $this->handle(); + + $this->callHook('afterHandle'); + } catch (RateLimitedException $e) { + $this->handleThrottle($e); + } + } + + protected function handle(): void + { + // + } +} diff --git a/src/Models/Concerns/WithQueryBuilder.php b/src/Models/Concerns/WithQueryBuilder.php new file mode 100644 index 00000000..47251919 --- /dev/null +++ b/src/Models/Concerns/WithQueryBuilder.php @@ -0,0 +1,50 @@ +getModelClass(), Model::class)); + + $this->authorize('viewAny', $this->getModelClass()); + } + + protected static function getModelClass(): ?string + { + return static::$model; + } + + protected static function getModel(): Model + { + return app(static::getModelClass()); + } + + protected static function getQuery(): Builder + { + return static::getModel() + ->newQuery(); + } + + protected static function getScout(string $query = '*', ?Closure $callback = null): ScoutBuilder + { + return static::getModel() + ->search($query, $callback); + } + + protected static function getLimit(): ?int + { + return static::$limit; + } +} diff --git a/src/Models/Forms/CreateForm.php b/src/Models/Forms/CreateForm.php new file mode 100644 index 00000000..ada5a7d8 --- /dev/null +++ b/src/Models/Forms/CreateForm.php @@ -0,0 +1,31 @@ +canCreate($this->model); + + parent::submit(); + } + + protected function set(string $class): void + { + $this->canCreate($class); + + $this->model = $class; + } + + protected function handle(): void + { + app(static::$model)::create( + $this->all() + ); + } +} diff --git a/src/Models/Forms/UpdateForm.php b/src/Models/Forms/UpdateForm.php new file mode 100644 index 00000000..0351ca59 --- /dev/null +++ b/src/Models/Forms/UpdateForm.php @@ -0,0 +1,41 @@ +canUpdate($this->model); + + parent::submit(); + } + + public function delete(): void + { + $this->canDelete($this->model); + + $this->model->delete(); + } + + protected function set(Model $model): void + { + $this->canUpdate($model); + + $this->model = $model; + } + + protected function handle(): void + { + $this->model->update( + $this->all() + ); + } +} diff --git a/src/Skeleton.php b/src/Skeleton.php deleted file mode 100755 index 66fab603..00000000 --- a/src/Skeleton.php +++ /dev/null @@ -1,7 +0,0 @@ -name('skeleton') - ->hasConfigFile() - ->hasViews() - ->hasMigration('create_skeleton_table') - ->hasCommand(SkeletonCommand::class); - } -} diff --git a/src/States/Concerns/WithStates.php b/src/States/Concerns/WithStates.php new file mode 100644 index 00000000..61abcae8 --- /dev/null +++ b/src/States/Concerns/WithStates.php @@ -0,0 +1,15 @@ +all()) + ->filter(fn (mixed $value) => $value instanceof State); + } +} diff --git a/src/Support/Blade/Bladeable.php b/src/Support/Blade/Bladeable.php new file mode 100644 index 00000000..50ea1ffd --- /dev/null +++ b/src/Support/Blade/Bladeable.php @@ -0,0 +1,23 @@ +squish() + ->split('/[\s,]+/') + ->sort(fn (string $value) => str($value)->startsWith('!')) + ->implode(' '); + } + + public static function cssClassKey(...$keys): Collection + { + return collect($keys) + ->map(fn (string $value) => str($value)->startsWith('class:') ? $value : "class:{$value}"); + } +} diff --git a/src/Support/Concerns/WithHooks.php b/src/Support/Concerns/WithHooks.php new file mode 100644 index 00000000..5883b013 --- /dev/null +++ b/src/Support/Concerns/WithHooks.php @@ -0,0 +1,15 @@ +{$hook}(...$args); + } + + return value($hook, $args); + } +} diff --git a/src/Support/Concerns/WithRateLimiting.php b/src/Support/Concerns/WithRateLimiting.php new file mode 100644 index 00000000..21f8d759 --- /dev/null +++ b/src/Support/Concerns/WithRateLimiting.php @@ -0,0 +1,48 @@ +ip(), + RateLimiter::availableIn($key) + ); + } + + static::incrementRateLimiter(); + } + + protected static function incrementRateLimiter(): void + { + RateLimiter::increment(static::getRateLimitKey(), static::$decaySeconds); + } + + protected static function clearRateLimiter(): void + { + RateLimiter::clear(static::getRateLimitKey()); + } + + protected static function getRateLimitKey(): string + { + return hash('crc32c', serialize([ + get_called_class(), request()->ip(), + ])); + } +} diff --git a/src/Support/Discover/ComponentScout.php b/src/Support/Discover/ComponentScout.php new file mode 100644 index 00000000..6eb61751 --- /dev/null +++ b/src/Support/Discover/ComponentScout.php @@ -0,0 +1,49 @@ +path) + ->parallel() + ->extending(Component::class) + ->full(); + } + + public function identifier(): string + { + return $this->prefix ?? static::class; + } + + public function cacheDriver(): LaravelDiscoverCacheDriver + { + return new LaravelDiscoverCacheDriver( + prefix: $this->identifier(), + ); + } + + public function prefix(string $prefix): static + { + $this->prefix = $prefix; + + return $this; + } + + public function path(string $path): static + { + $this->path = $path; + + return $this; + } +} diff --git a/src/Support/Discover/LivewireScout.php b/src/Support/Discover/LivewireScout.php new file mode 100644 index 00000000..3dd7de32 --- /dev/null +++ b/src/Support/Discover/LivewireScout.php @@ -0,0 +1,17 @@ +path) + ->parallel() + ->extending(Component::class) + ->full(); + } +} diff --git a/src/Support/Livewire/LegacyModels/EloquentCollectionSynth.php b/src/Support/Livewire/LegacyModels/EloquentCollectionSynth.php new file mode 100644 index 00000000..869a57f2 --- /dev/null +++ b/src/Support/Livewire/LegacyModels/EloquentCollectionSynth.php @@ -0,0 +1,81 @@ +getQueueableClass(); + $modelKeys = array_map(fn ($model) => $model->getRouteKey(), $target->all()); + + $meta = []; + + $meta['keys'] = $modelKeys; + $meta['class'] = $class; + $meta['modelClass'] = $modelClass; + + if ($modelClass && ($connection = $this->getConnection($target)) !== $modelClass::make()->getConnectionName()) { + $meta['connection'] = $connection; + } + + $relations = $target->getQueueableRelations(); + + if (count($relations)) { + $meta['relations'] = $relations; + } + + $rules = $this->getRules($this->context); + + if (empty($rules)) { + return [[], $meta]; + } + + $data = $this->getDataFromCollection($target, $rules); + + foreach ($data as $key => $child) { + + $data[$key] = $dehydrateChild($key, $child); + } + + return [$data, $meta]; + } + + protected function loadCollection($meta) + { + if (isset($meta['keys']) && count($meta['keys']) >= 0 && ! empty($meta['modelClass'])) { + $model = new $meta['modelClass']; + + if (isset($meta['connection'])) { + $model->setConnection($meta['connection']); + } + + $query = $model + ->newQueryWithoutScopes() + ->whereIn((new $meta['modelClass'])->getRouteKeyName(), $meta['keys']); + + if (isset($meta['relations'])) { + $query->with($meta['relations']); + } + + $query->useWritePdo(); + + $collection = $query->get(); + + $collection = $collection->keyBy->getRouteKey(); + + return new $meta['class']( + collect($meta['keys'])->map(function ($id) use ($collection) { + return $collection[$id] ?? null; + })->filter() + ); + } + + return new $meta['class'](); + } +} diff --git a/src/Support/Livewire/LegacyModels/EloquentModelSynth.php b/src/Support/Livewire/LegacyModels/EloquentModelSynth.php new file mode 100644 index 00000000..e76a8833 --- /dev/null +++ b/src/Support/Livewire/LegacyModels/EloquentModelSynth.php @@ -0,0 +1,90 @@ +getMorphClass(); + } catch (ClassMorphViolationException $e) { + // If the model is not using morph classes, this exception is thrown + $alias = $class; + } + + $meta = []; + + if ($target->exists) { + $meta['key'] = $target->getRouteKey(); + } + + $meta['class'] = $alias; + + if ($target->getConnectionName() !== $class::make()->getConnectionName()) { + $meta['connection'] = $target->getConnectionName(); + } + + $relations = $target->getQueueableRelations(); + + if (count($relations)) { + $meta['relations'] = $relations; + } + + $rules = $this->getRules($this->context); + + if (empty($rules)) { + return [[], $meta]; + } + + $data = $this->getDataFromModel($target, $rules); + + foreach ($data as $key => $child) { + $data[$key] = $dehydrateChild($key, $child); + } + + return [$data, $meta]; + } + + protected function loadModel($meta): ?Model + { + $class = $meta['class']; + + // If no alias found, this returns `null` + $aliasClass = Relation::getMorphedModel($class); + + if (! is_null($aliasClass)) { + $class = $aliasClass; + } + + if (isset($meta['key'])) { + $model = new $class; + + if (isset($meta['connection'])) { + $model->setConnection($meta['connection']); + } + + $query = $model + ->newQueryWithoutScopes() + ->where((new $class)->getRouteKeyName(), $meta['key']); + + if (isset($meta['relations'])) { + $query->with($meta['relations']); + } + + $model = $query->first(); + } else { + $model = new $class(); + } + + return $model; + } +} diff --git a/src/Support/Livewire/ModelStateObjects/ModelStateObjectSynth.php b/src/Support/Livewire/ModelStateObjects/ModelStateObjectSynth.php new file mode 100644 index 00000000..e32cea5a --- /dev/null +++ b/src/Support/Livewire/ModelStateObjects/ModelStateObjectSynth.php @@ -0,0 +1,26 @@ +getMorphClass(), []]; + } + + public function hydrate($value) + { + return $value; + } +} diff --git a/src/Support/Livewire/ModelStateObjects/SupportModelStateObjects.php b/src/Support/Livewire/ModelStateObjects/SupportModelStateObjects.php new file mode 100644 index 00000000..d5fd5e7d --- /dev/null +++ b/src/Support/Livewire/ModelStateObjects/SupportModelStateObjects.php @@ -0,0 +1,15 @@ +propertySynthesizer( + ModelStateObjectSynth::class + ); + } +} diff --git a/src/Support/Livewire/Models/CollectionSynth.php b/src/Support/Livewire/Models/CollectionSynth.php new file mode 100644 index 00000000..b9c4f37a --- /dev/null +++ b/src/Support/Livewire/Models/CollectionSynth.php @@ -0,0 +1,46 @@ +newQueryWithoutScopes() + ->whereIn((new $modelClass)->getRouteKeyName(), $keys) + ->useWritePdo() + ->get(); + + $collection = $collection->keyBy->getRouteKey(); + + return new $meta['class']( + collect($meta['keys'])->map(function ($id) use ($collection) { + return $collection[$id] ?? null; + })->filter() + ); + } +} diff --git a/src/Support/Livewire/Models/ModelSynth.php b/src/Support/Livewire/Models/ModelSynth.php new file mode 100644 index 00000000..4e9d5e1e --- /dev/null +++ b/src/Support/Livewire/Models/ModelSynth.php @@ -0,0 +1,67 @@ +getMorphClass(); + } catch (ClassMorphViolationException $e) { + // If the model is not using morph classes, this exception is thrown + $alias = $class; + } + + $serializedModel = $target->exists + ? (array) $this->getSerializedPropertyValue($target) + : null; + + $meta = ['class' => $alias]; + + // If the model doesn't exist as it's an empty model or has been + // recently deleted, then we don't want to include any key. + if ($serializedModel) { + $meta['key'] = $target->getRouteKey(); + } + + return [ + null, + $meta, + ]; + } + + public function hydrate($data, $meta) + { + $class = $meta['class']; + + // If no alias found, this returns `null` + $aliasClass = Relation::getMorphedModel($class); + + if (! is_null($aliasClass)) { + $class = $aliasClass; + } + + // If no key is provided then an empty model is returned + if (! array_key_exists('key', $meta)) { + return new $class; + } + + $key = $meta['key']; + + $model = (new $class) + ->newQueryWithoutScopes() + ->where((new $class)->getRouteKeyName(), $key) + ->useWritePdo() + ->firstOrFail(); + + return $model; + } +} diff --git a/src/Support/Livewire/StateObjects/State.php b/src/Support/Livewire/StateObjects/State.php new file mode 100644 index 00000000..83a22eb1 --- /dev/null +++ b/src/Support/Livewire/StateObjects/State.php @@ -0,0 +1,106 @@ +component; + } + + public function getPropertyName() + { + return $this->propertyName; + } + + public function all() + { + return $this->toArray(); + } + + public function only($properties) + { + $results = []; + + foreach (is_array($properties) ? $properties : func_get_args() as $property) { + $results[$property] = $this->hasProperty($property) ? $this->getPropertyValue($property) : null; + } + + return $results; + } + + public function except($properties) + { + $properties = is_array($properties) ? $properties : func_get_args(); + + return array_diff_key($this->all(), array_flip($properties)); + } + + public function hasProperty($prop) + { + return property_exists($this, Utils::beforeFirstDot($prop)); + } + + public function getPropertyValue($name) + { + $value = $this->{Utils::beforeFirstDot($name)}; + + if (Utils::containsDots($name)) { + return data_get($value, Utils::afterFirstDot($name)); + } + + return $value; + } + + public function fill($values) + { + $publicProperties = array_keys($this->all()); + + if ($values instanceof Model) { + $values = $values->toArray(); + } + + foreach ($values as $key => $value) { + if (in_array(Utils::beforeFirstDot($key), $publicProperties)) { + data_set($this, $key, $value); + } + } + } + + public function reset(...$properties): void + { + $properties = count($properties) && is_array($properties[0]) + ? $properties[0] + : $properties; + + if (empty($properties)) { + $properties = array_keys($this->all()); + } + + $freshInstance = new static($this->getComponent(), $this->getPropertyName()); + + foreach ($properties as $property) { + data_set($this, $property, data_get($freshInstance, $property)); + } + } + + public function toArray(): array + { + return Utils::getPublicProperties($this); + } +} diff --git a/src/Support/Livewire/StateObjects/StateObjectSynth.php b/src/Support/Livewire/StateObjects/StateObjectSynth.php new file mode 100644 index 00000000..b1a1a784 --- /dev/null +++ b/src/Support/Livewire/StateObjects/StateObjectSynth.php @@ -0,0 +1,69 @@ +toArray(); + + foreach ($data as $key => $child) { + $data[$key] = $dehydrateChild($key, $child); + } + + return [$data, ['class' => get_class($target)]]; + } + + public function hydrate($data, $meta, $hydrateChild) + { + $state = new $meta['class']($this->context->component, $this->path); + + $callBootMethod = static::bootStateObject($this->context->component, $state, $this->path); + + foreach ($data as $key => $child) { + if ($child === null && Utils::propertyIsTypedAndUninitialized($state, $key)) { + continue; + } + + $state->$key = $hydrateChild($key, $child); + } + + $callBootMethod(); + + return $state; + } + + public function set(&$target, $key, $value) + { + if ($value === null && Utils::propertyIsTyped($target, $key)) { + unset($target->$key); + } else { + $target->$key = $value; + } + } + + public static function bootStateObject($component, $state, $path) + { + $component->mergeOutsideAttributes( + AttributeCollection::fromComponent($component, $state, $path.'.') + ); + + return function () use ($state) { + wrap($state)->boot(); + }; + } +} diff --git a/src/Support/Livewire/StateObjects/SupportStateObjects.php b/src/Support/Livewire/StateObjects/SupportStateObjects.php new file mode 100644 index 00000000..70a4a75d --- /dev/null +++ b/src/Support/Livewire/StateObjects/SupportStateObjects.php @@ -0,0 +1,61 @@ +propertySynthesizer( + StateObjectSynth::class + ); + } + + public function boot() + { + $this->initializeStateObjects(); + } + + protected function initializeStateObjects() + { + foreach ((new ReflectionClass($this->component))->getProperties() as $property) { + // Public properties only... + if ($property->isPublic() !== true) { + continue; + } + + // Uninitialized properties only... + if ($property->isInitialized($this->component)) { + continue; + } + + $type = $property->getType(); + + if (! $type instanceof ReflectionNamedType) { + continue; + } + + $typeName = $type->getName(); + + // "State" object property types only... + if (! is_subclass_of($typeName, State::class)) { + continue; + } + + $state = new $typeName( + $this->component, + $name = $property->getName() + ); + + $callBootMethod = StateObjectSynth::bootStateObject($this->component, $state, $name); + + $property->setValue($this->component, $state); + + $callBootMethod(); + } + } +} diff --git a/src/Views/Components/Component.php b/src/Views/Components/Component.php new file mode 100644 index 00000000..c71c51e6 --- /dev/null +++ b/src/Views/Components/Component.php @@ -0,0 +1,13 @@ +attributes->get('id', $this->wireModel() ?? (string) $this->uuid()); + } + + public function wireModel(): ?string + { + return $this->attributes->whereStartsWith('wire:model')->first(); + } + + public function uuid(): UuidInterface + { + return once(fn (): UuidInterface => Str::uuid()); + } +} diff --git a/src/Views/Concerns/WithSeo.php b/src/Views/Concerns/WithSeo.php new file mode 100644 index 00000000..b4d11532 --- /dev/null +++ b/src/Views/Concerns/WithSeo.php @@ -0,0 +1,21 @@ +seo()->setTitle(static::getTitle()); + } + + if (method_exists(static::class, 'getDescription')) { + $this->seo()->setDescription(static::getDescription()); + } + } +} diff --git a/src/WireUse.php b/src/WireUse.php new file mode 100755 index 00000000..7489ff92 --- /dev/null +++ b/src/WireUse.php @@ -0,0 +1,82 @@ +path($path) + ->prefix("laravel-components-{$prefix}") + ->get(); + + collect($scout) + ->each(function (DiscoveredClass $class) use ($namespace, $prefix, $callback) { + $name = $callback instanceof Closure + ? $callback($class, $namespace) + : static::componentName($class, $namespace, $prefix); + + Blade::component($class->getFcqn(), $name->value()); + }); + } + + public static function registerLivewireComponents( + string $path, + string $namespace = 'App\\', + string $prefix = '', + ?Closure $callback = null, + ): void { + $scout = LivewireScout::create() + ->path($path) + ->prefix("livewire-components-{$prefix}") + ->get(); + + collect($scout) + ->each(function (DiscoveredClass $class) use ($namespace, $prefix, $callback) { + $name = $callback instanceof Closure + ? $callback($class, $namespace) + : static::componentName($class, $namespace, $prefix); + + Livewire::component($name->value(), $class->getFcqn()); + }); + } + + public static function componentName(DiscoveredClass $class, string $namespace, string $prefix): Stringable + { + return str($class->name) + ->kebab() + ->prepend( + static::componentPrefix($prefix), + static::componentNamespace($class, $namespace) + ); + } + + public static function componentPrefix(string $prefix): string + { + return str($prefix) + ->kebab() + ->finish('::'); + } + + public static function componentNamespace(DiscoveredClass $class, string $namespace): Stringable + { + return str($class->namespace) + ->after($namespace) + ->match('/(.*)\\\\/') + ->kebab() + ->finish('-'); + } +} diff --git a/src/WireUseServiceProvider.php b/src/WireUseServiceProvider.php new file mode 100644 index 00000000..f407ae71 --- /dev/null +++ b/src/WireUseServiceProvider.php @@ -0,0 +1,135 @@ +name('wireuse') + ->hasConfigFile() + ->hasInstallCommand(function (InstallCommand $command) { + $command + ->publishConfigFile(); + }); + } + + public function packageRegistered() + { + $this->app->singleton(Bladeable::class); + } + + public function bootingPackage(): void + { + $this + ->registerFeatures() + ->registerBladeMacros(); + } + + protected function registerFeatures(): static + { + foreach ([ + \Foxws\WireUse\Support\Livewire\ModelStateObjects\SupportModelStateObjects::class, + \Foxws\WireUse\Support\Livewire\StateObjects\SupportStateObjects::class, + ] as $feature) { + app('livewire')->componentHook($feature); + } + + return $this; + } + + protected function registerBladeMacros(): static + { + if (config('wireuse.register_macros') === false) { + return $this; + } + + ComponentAttributeBag::macro('cssClass', function (array $values = []): ComponentAttributeBag { + /** @var ComponentAttributeBag $this */ + foreach ($values as $key => $value) { + $key = app(Bladeable::class)->cssClassKey($key)->first(); + + if (! $this->has($key)) { + $this->offsetSet($key, $value); + } + } + + return $this; + }); + + ComponentAttributeBag::macro('classMerge', function (?array $values = null): ComponentAttributeBag { + /** @var ComponentAttributeBag $this */ + $values ??= str($this->whereStartsWith('class:'))->matchAll('/class:(.*?)\=/s'); + + $classList = collect($values) + ->map(function (mixed $value, int|string $key) { + if (is_bool($value) && $value === false) { + return; + } + + $key = app(Bladeable::class)->cssClassKey( + is_numeric($key) ? $value : $key + ); + + return $this->get($key->first(), ''); + }) + ->merge($this->get('class')) + ->join(' '); + + $this->offsetSet('class', $classList); + + return $this + ->classSort() + ->classWithout(); + }); + + ComponentAttributeBag::macro('classFor', function (string $key, ?string $default = null): ComponentAttributeBag { + /** @var ComponentAttributeBag $this */ + $value = $this->get(app(Bladeable::class)->cssClassKey($key)->first(), $default ?? ''); + + $this->offsetSet('class', $value); + + return $this + ->classSort() + ->classWithout(); + }); + + ComponentAttributeBag::macro('classAny', function (...$keys): ComponentAttributeBag { + /** @var ComponentAttributeBag $this */ + $value = $this->only(app(Bladeable::class)->cssClassKey($keys)); + + $this->offsetSet('class', $value->join(' ')); + + return $this + ->classSort() + ->classWithout(); + }); + + ComponentAttributeBag::macro('classSort', function (): ComponentAttributeBag { + /** @var ComponentAttributeBag $this */ + $classList = app(Bladeable::class)->classSort( + $this->get('class', '') + ); + + $this->offsetSet('class', $classList); + + return $this; + }); + + ComponentAttributeBag::macro('classWithout', function (): ComponentAttributeBag { + /** @var ComponentAttributeBag $this */ + + return $this + ->whereDoesntStartWith('class:'); + }); + + return $this; + } +} diff --git a/tailwind.config.js b/tailwind.config.js new file mode 100644 index 00000000..73fd597f --- /dev/null +++ b/tailwind.config.js @@ -0,0 +1,10 @@ +import forms from '@tailwindcss/forms'; +import typography from '@tailwindcss/typography'; +import theme from './resources/css/presets/tailwind.config.preset'; + +/** @type {import('tailwindcss').Config} */ +export default { + presets: [theme], + content: ['./resources/**/*.blade.php'], + plugins: [forms, typography], +}; diff --git a/tests/ArchTest.php b/tests/ArchTest.php deleted file mode 100644 index 87fb64cd..00000000 --- a/tests/ArchTest.php +++ /dev/null @@ -1,5 +0,0 @@ -expect(['dd', 'dump', 'ray']) - ->each->not->toBeUsed(); diff --git a/tests/ExampleTest.php b/tests/ExampleTest.php deleted file mode 100644 index 5d363218..00000000 --- a/tests/ExampleTest.php +++ /dev/null @@ -1,5 +0,0 @@ -toBeTrue(); -}); diff --git a/tests/Pest.php b/tests/Pest.php index 7fe1500a..527f14f8 100644 --- a/tests/Pest.php +++ b/tests/Pest.php @@ -1,5 +1,15 @@ in(__DIR__); +expect() + ->extend('toBeSameModel', fn (Model $model) => $this->is($model)->toBeTrue()); + +beforeEach(function () { + // Fake instances + \Illuminate\Support\Facades\Bus::fake(); + \Illuminate\Support\Facades\Mail::fake(); + \Illuminate\Support\Facades\Notification::fake(); + \Illuminate\Support\Facades\Queue::fake(); + \Illuminate\Support\Facades\Storage::fake(); +}); diff --git a/tests/TestCase.php b/tests/TestCase.php deleted file mode 100644 index d04fb0cc..00000000 --- a/tests/TestCase.php +++ /dev/null @@ -1,36 +0,0 @@ - 'VendorName\\Skeleton\\Database\\Factories\\'.class_basename($modelName).'Factory' - ); - } - - protected function getPackageProviders($app) - { - return [ - SkeletonServiceProvider::class, - ]; - } - - public function getEnvironmentSetUp($app) - { - config()->set('database.default', 'testing'); - - /* - $migration = include __DIR__.'/../database/migrations/create_skeleton_table.php.stub'; - $migration->up(); - */ - } -} diff --git a/tests/database/factories/PostFactory.php b/tests/database/factories/PostFactory.php new file mode 100644 index 00000000..16963929 --- /dev/null +++ b/tests/database/factories/PostFactory.php @@ -0,0 +1,23 @@ + $this->faker->uuid(), + 'user_id' => User::factory(), + 'title' => $this->faker->sentence(), + 'content' => $this->faker->paragraph(), + 'published_at' => now(), + ]; + } +} diff --git a/tests/database/factories/UserFactory.php b/tests/database/factories/UserFactory.php new file mode 100644 index 00000000..09608ac2 --- /dev/null +++ b/tests/database/factories/UserFactory.php @@ -0,0 +1,25 @@ + $this->faker->uuid(), + 'name' => $this->faker->name(), + 'email' => $this->faker->unique()->safeEmail(), + 'email_verified_at' => now(), + 'password' => Hash::make('password'), + 'remember_token' => Str::random(10), + ]; + } +} diff --git a/tests/src/Livewire/Synthesizers/PropertySynthesizerTest.php b/tests/src/Livewire/Synthesizers/PropertySynthesizerTest.php new file mode 100644 index 00000000..2006966b --- /dev/null +++ b/tests/src/Livewire/Synthesizers/PropertySynthesizerTest.php @@ -0,0 +1,36 @@ +create(); + + Livewire::test(LivewireModelComponent::class, compact('post')) + ->assertStatus(200); +}); + +it('can load models with synthesizers', function () { + $posts = Post::factory()->count(3)->create(); + + Livewire::test(LivewireCollectionComponent::class, compact('posts')) + ->assertStatus(200); +}); diff --git a/tests/src/Models/Post.php b/tests/src/Models/Post.php new file mode 100644 index 00000000..e7e10e7c --- /dev/null +++ b/tests/src/Models/Post.php @@ -0,0 +1,32 @@ +belongsTo(User::class, 'user_id'); + } + + public function getRouteKeyName(): string + { + return 'uuid'; + } +} diff --git a/tests/src/Models/User.php b/tests/src/Models/User.php new file mode 100644 index 00000000..5dcd8fb8 --- /dev/null +++ b/tests/src/Models/User.php @@ -0,0 +1,38 @@ +hasMany(Post::class, 'user_id'); + } + + public function getRouteKeyName(): string + { + return 'uuid'; + } +} diff --git a/tests/src/Support/BladeDirectiveTest.php b/tests/src/Support/BladeDirectiveTest.php new file mode 100644 index 00000000..66a74f5e --- /dev/null +++ b/tests/src/Support/BladeDirectiveTest.php @@ -0,0 +1,12 @@ +')) + ->toContain('class="flex flex-nowrap bg-gray-300 opacity-50"') + ->toMatchSnapshot(); +}); diff --git a/tests/src/TestCase.php b/tests/src/TestCase.php new file mode 100644 index 00000000..d65a727c --- /dev/null +++ b/tests/src/TestCase.php @@ -0,0 +1,97 @@ +setUpDatabase($this->app); + $this->setUpComponents($this->app); + $this->setUpLivewire($this->app); + } + + protected function getEnvironmentSetUp($app) + { + $app['config']->set('cache.default', 'file'); + + $app['config']->set('database.default', 'sqlite'); + $app['config']->set('database.connections.sqlite', [ + 'driver' => 'sqlite', + 'database' => ':memory:', + 'prefix' => '', + ]); + + $app['config']->set('view.paths', [__DIR__.'/../resources/views']); + } + + protected function getPackageProviders($app) + { + return [ + LivewireServiceProvider::class, + WireUseServiceProvider::class, + ]; + } + + /** + * @param \Illuminate\Foundation\Application $app + */ + protected function setUpDatabase($app) + { + Schema::dropAllTables(); + + Schema::create('users', function (Blueprint $table) { + $table->id(); + $table->uuid(); + $table->string('email')->unique(); + $table->string('name')->nullable(); + $table->string('password'); + $table->rememberToken(); + $table->timestamp('email_verified_at')->nullable(); + $table->timestamps(); + }); + + Schema::create('posts', function (Blueprint $table) { + $table->id(); + $table->uuid(); + $table->foreignId('user_id')->nullable()->index(); + $table->string('title'); + $table->text('content')->nullable(); + $table->timestamp('published_at')->nullable(); + $table->timestamps(); + }); + } + + /** + * @param \Illuminate\Foundation\Application $app + */ + protected function setUpComponents($app) + { + Blade::component('test-component', BladeComponent::class); + } + + /** + * @param \Illuminate\Foundation\Application $app + */ + protected function setUpLivewire($app) + { + Livewire::component('test-collection', LivewireCollectionComponent::class); + Livewire::component('test-model', LivewireModelComponent::class); + } +} diff --git a/tests/src/TestClasses/BladeComponent.php b/tests/src/TestClasses/BladeComponent.php new file mode 100644 index 00000000..9d85f866 --- /dev/null +++ b/tests/src/TestClasses/BladeComponent.php @@ -0,0 +1,23 @@ +cssClass([ + 'layer' => 'flex flex-nowrap', + 'color' => 'bg-gray-300 opacity-50', + ]) + ->classMerge() + }}> + {{-- wow, such great content here --}} + + blade; + } +} diff --git a/tests/src/TestClasses/LivewireCollectionComponent.php b/tests/src/TestClasses/LivewireCollectionComponent.php new file mode 100644 index 00000000..6f4bcefe --- /dev/null +++ b/tests/src/TestClasses/LivewireCollectionComponent.php @@ -0,0 +1,20 @@ + + {{-- wow, such great articles here --}} + + HTML; + } +} diff --git a/tests/src/TestClasses/LivewireModelComponent.php b/tests/src/TestClasses/LivewireModelComponent.php new file mode 100644 index 00000000..367451d6 --- /dev/null +++ b/tests/src/TestClasses/LivewireModelComponent.php @@ -0,0 +1,20 @@ + + {{-- wow, such great article content here --}} + + HTML; + } +}