Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sequence #1

Merged
merged 20 commits into from
Nov 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 10 additions & 27 deletions .github/workflows/php.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,53 +44,36 @@ jobs:
--health-timeout 5s
--health-retries 5

strategy:
fail-fast: false
matrix:
craftVersion: ["~3.7.33", "^4.2"]

steps:
- uses: actions/checkout@v3

- name: Validate composer.json and composer.lock
run: composer validate --strict

- name: Create dynamic cache key
id: cache-key
env:
CRAFT_VERSION: ${{ matrix.craftVersion }}
run: |
<?php
$env = "DYNAMIC_CACHE_KEY=" . (getenv("CRAFT_VERSION") != "3.7.33" ? time() : "");
file_put_contents(getenv('GITHUB_OUTPUT'), $env, FILE_APPEND);
shell: php -f {0}

- name: Cache Composer packages
id: composer-cache
uses: actions/cache@v3
with:
path: |
composer.lock
vendor
key: ${{ runner.os }}-craft-vendor-${{ matrix.craftVersion }}-${{ steps.cache-key.outputs.DYNAMIC_CACHE_KEY }}-${{ hashFiles('composer.json') }}
key: ${{ runner.os }}-craft-vendor-${{ hashFiles('composer.json') }}
restore-keys: |
${{ runner.os }}-craft-vendor-~3.7.33-
${{ runner.os }}-craft-vendor-

- name: Install dependencies
run: composer update --with "craftcms/cms:~3.7.33" --prefer-dist --no-progress
run: |
composer install --prefer-dist --no-progress
./bin/post-clone.sh

- name: Copy config files
run: mkdir -p ./storage && cp -r ./stubs/config ./config
run: |
mkdir -p ./storage
cp -r ./stubs/config ./
cat config/app.php

- name: Install Craft
run: ./src/bin/craft install --username=admin [email protected] --password=secret --siteName=Pest --siteUrl=http://localhost:8080 --language=en-US

- name: Update to Craft ${{ matrix.craftVersion }}
run: composer update --with "craftcms/cms:${{ matrix.craftVersion }}" -W && ./src/bin/craft migrate/all --no-backup=1 --interactive=0
if: matrix.craftVersion != '~3.7.33'

- name: Generate compiled classes
run: ./src/bin/craft pest/ide/generate-mixins
run: ./src/bin/craft install --interactive=0 --username=admin [email protected] --password=secret --siteName=Pest --siteUrl=http://localhost:8080 --language=en-US

- name: Run PHPStan
run: ./vendor/bin/phpstan analyse src
Expand Down
12 changes: 11 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
/vendor
.DS_Store
.idea
composer.lock
cpresources
/src/bin/index.php
.phpunit.result.cache
Expand All @@ -15,3 +14,14 @@ src/bin/cpresources
!/storage/.gitkeep
node_modules
/docs/.vitepress/dist
/composer.lock
/composer-local.lock
/storage
/web
/bootstrap.php
/.env
/cpresources
/templates
/node_modules
/.env.example
/cpresources
32 changes: 32 additions & 0 deletions bin/create-default-fs.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

// Load shared bootstrap
require __DIR__.'/../bootstrap.php';

// Load and run Craft
/** @var craft\console\Application $app */
$app = require CRAFT_VENDOR_PATH.'/craftcms/cms/bootstrap/console.php';

$fs = $app->fs->getFilesystemByHandle('local');
if (! $fs) {
$fs = $app->fs->createFilesystem([
'type' => \craft\fs\Local::class,
'name' => 'Local',
'handle' => 'local',
'hasUrls' => true,
'url' => 'http://localhost:8080/volumes/local/',
'settings' => ['path' => CRAFT_BASE_PATH.'/web/volumes/local'],
]);
$result = $app->fs->saveFilesystem($fs);
}

$volume = $app->volumes->getVolumeByHandle('local');
if (! $volume) {
$volume = new \craft\models\Volume();
$volume->name = 'Local';
$volume->handle = 'local';
$volume->fs = $fs;
$app->volumes->saveVolume($volume);
}

$app->run();
47 changes: 47 additions & 0 deletions bin/post-clone.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#!/bin/bash

if [ ! -d "storage" ]; then
mkdir -p storage
fi

if [ ! -f ".env" ]; then
cp vendor/craftcms/craft/.env.example.dev ./.env.example
fi

if ! grep -q "CRAFT_RUN_QUEUE_AUTOMATICALLY=false" .env.example; then
echo "" >> .env
echo "CRAFT_RUN_QUEUE_AUTOMATICALLY=false" >> .env.example
echo "" >> .env
fi

if [ ! -f "config/app.php" ]; then
mkdir -p config
echo "<?php return [
'components' => [
'queue' => [
'class' => \yii\queue\sync\Queue::class,
'handle' => true, // if tasks should be executed immediately
],
],
'bootstrap' => [
function (\$app) {
(new \\markhuot\\craftpest\\Pest)->bootstrap(\$app);
},
]
];" > config/app.php
fi

if [ ! -d "web" ]; then
cp -r vendor/craftcms/craft/web ./
fi

if [ ! -f "craft" ]; then
cp vendor/craftcms/craft/craft ./
chmod +x ./craft
fi

if [ ! -f "bootstrap.php" ]; then
cp vendor/craftcms/craft/bootstrap.php ./
fi

php craft setup/keys
4 changes: 4 additions & 0 deletions bin/post-install.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/bin/bash

php craft plugin/install keystone
php ./bin/create-default-fs.php > /dev/null 2>&1
6 changes: 4 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
"symfony/process": "^5.3|^6.0",
"illuminate/collections": "^8.23|^9.1|^10.0",
"pestphp/pest": "^2.8",
"vlucas/phpdotenv": "^2.4|^3.4|^5.4"
"vlucas/phpdotenv": "^2.4|^3.4|^5.4",
"craftcms/cms": "^4.5"
},
"autoload": {
"psr-4": {
Expand Down Expand Up @@ -50,6 +51,7 @@
"prefer-stable": true,
"require-dev": {
"craftcms/phpstan": "dev-main",
"symfony/var-dumper": "^5.0|^6.0"
"symfony/var-dumper": "^5.0|^6.0",
"craftcms/craft": "^2.0"
}
}
23 changes: 20 additions & 3 deletions docs/factories.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,10 @@ passed an instance of faker
```php
Entry::factory()
->set('title, 'SOME GREAT TITLE')
->set('title', fn ($faker) => str_to_upper($faker->sentence))
->set('title', fn ($faker) => str_to_upper($faker->sentence()))
->set([
'title' => 'SOME GREAT TITLE',
'title' => fn ($faker) => str_to_upper($faker->sentence)
'title' => fn ($faker) => str_to_upper($faker->sentence())
])
```

Expand Down Expand Up @@ -98,7 +98,7 @@ class Post extends \markhuot\craftpest\factories\Entry
{
return [
// The entry's title field
'title' => $this->faker->sentence,
'title' => $this->faker->sentence(),
// A Category field takes an array of category ids or category factories
'category' => Category::factory()->count(3),
// Generate three body paragraphs of text
Expand All @@ -118,6 +118,23 @@ set by definition or through a `->set()` call.

When creating custom factories, this will most likely meed to be overridden.

## sequence($sequence)
Set a sequence that will be iterated on as multiple models are created. You can
set this to a callback (which gets passed the index) or an array of definitions
where each definition will be used in order.
```php
->sequence(fn ($index) => ['someField' => "the index is {$index}"])
->sequence(['someField' => 'the index is 1'], ['someField' => 'the index is 2'])
```
With the array approach the sequence will be iterated over and looped so if you
pass two items in to a sequence the third created element will re-use the first
item in the sequence. E.g., this will iterate around true/false admins creating
5 admins and 5 non-admins.
```php
->count(10)
->sequence(['isAdmin' => true], ['isAdmin' => false])
```

## make($definition = array ())
Instantiate an Model without persisting it to the database.

Expand Down
10 changes: 9 additions & 1 deletion docs/logging-in.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,17 @@ Acting as accepts a number of "user-like" identifiers to log in a user for the t
2. A user, `->actingAs(User::find()->id(1)->one())`
3. A string that may be a username or email address, `->actingAs('my_great_username')`
4. A callable that returns a User element, `->actingAs(fn () => $someUser)`
5. `null` to log the user out for the given request

## actingAsAdmin()
For many tests the actual user doesn't matter, only that the user is an admin. This method
will return a generic user with admin permissions. This is helpful for testing that something
works, not whether the permissions for that thing are accurate. For more fine-tuned permission
testing you should use `->actingAs()` with a curated user element.
testing you should use `->actingAs()` with a curated user element.

## withToken(string $token)
For GQL requests (and other bearer token requests) you can set a token on the request by calling
`->withToken()` and passing a valid bearer token.
```php
$this->withToken($token)->get('/')->assertOk();
```
12 changes: 0 additions & 12 deletions docs/making-requests.md
Original file line number Diff line number Diff line change
@@ -1,35 +1,26 @@
# Requests

You can simulate requests to the Craft application via several helper methods
on the `TestCase` as well as via the standalone `Http` helpers. The most
common helper is `$this->get('uri')` or `get('uri')`. This will make a request
to Craft at the given `'uri'` and return a [`TestableResponse`](assertions/response.md).

You can kick off a request in a classic test,

```php
it ('gets something', function () {
$this->get('/')->assertOk();
});
```

Using Pest's higher order proxies you can do the same thing without a closure,

```php
it('gets something')
->get('/')
->assertOk();
```

And, lastly, you can skip the description all together and use a descriptionless
test.

```php
use function markhuot\craftpest\helpers\Http\get;

get('/')->assertOk();
```

All of these are functionally identical. You are free to select the syntax that reads
the most naturally for your test and provides the right context for the test. For
more information on the test context see, [Getting Started](getting-started.md).
Expand All @@ -39,19 +30,16 @@ Makes a `GET` request to Craft.

## post(string $uri, array $body = array ())
Makes a `POST` request to Craft.

```php
$this->post('/comments', [
'author' => '...',
'body' => '...',
])->assertOk();
```

Because _many_ `POST` requests need to send the CSRF token along with the
request it is handled automatically within the `->post()` method. If
you would prefer to handle this yourself you may use the raw `->http()` method
insetad. The above `/comments` example is functionally similar to,

```php
$this->http('post', '/comments')
->withCsrfToken()
Expand Down
16 changes: 16 additions & 0 deletions phpunit.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.2/phpunit.xsd" bootstrap="vendor/autoload.php" colors="true" cacheDirectory=".phpunit.cache">
<testsuites>
<testsuite name="Test Suite">
<directory suffix="Test.php">./tests</directory>
</testsuite>
</testsuites>
<coverage/>
<source>
<include>
<directory suffix=".php">./src</directory>
<!-- <directory suffix=".php">./src</directory> -->
<directory suffix=".php">./storage/runtime/compiled_templates</directory>
</include>
</source>
</phpunit>
8 changes: 0 additions & 8 deletions src/Pest.php
Original file line number Diff line number Diff line change
Expand Up @@ -57,13 +57,5 @@ function (DefineBehaviorsEvent $event) {
$event->behaviors[] = FieldTypeHintBehavior::class;
}
);

Event::on(
Fields::class,
Fields::EVENT_AFTER_SAVE_FIELD,
function () {
(new RenderCompiledClasses)->handle();
}
);
}
}
17 changes: 2 additions & 15 deletions src/behaviors/ExpectableBehavior.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,13 @@

namespace markhuot\craftpest\behaviors;

use Pest\Expectation;
use yii\base\Behavior;

class ExpectableBehavior extends Behavior
{
function expect()
{
return test()->expect($this->owner);
return new Expectation($this->owner);
}

// function __isset($name)
// {
// return $name === 'expect';
// }
//
// function __get($name)
// {
// if ($name === 'expect') {
// return test()->expect($this->owner);
// }
//
// throw new \InvalidArgumentException("Could not find {$name} on ".get_class($this->owner));
// }
}
Loading