diff --git a/.gitignore b/.gitignore
index a24a7ee..76d83e2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,33 +1,11 @@
-# CRAFT ENVIRONMENT
-.env.php
-.env.sh
-.env
-
-# COMPOSER
-/vendor
+.idea
+.php_cs
+.php_cs.cache
+.php-cs-fixer.cache
+.phpunit.result.cache
+/build
composer.lock
+vendor
+*.log
+/tests/_craft/storage/
-# BUILD FILES
-/bower_components/*
-/node_modules/*
-/build/*
-/yarn-error.log
-
-# MISC FILES
-.cache
-.DS_Store
-.idea
-.project
-.settings
-*.esproj
-*.sublime-workspace
-*.sublime-project
-*.tmproj
-*.tmproject
-.vscode/*
-!.vscode/settings.json
-!.vscode/tasks.json
-!.vscode/launch.json
-!.vscode/extensions.json
-config.codekit3
-prepros-6.config
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 407c653..efb08a1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,9 @@
# Changelog
+## [2.0.0] - Unreleased
+
+
+
## [1.9.2] - 2022-03-26
- Fix issues with Supertable / exclude SuperTableBlockElement
diff --git a/composer.json b/composer.json
index 913f2a1..f2314ad 100644
--- a/composer.json
+++ b/composer.json
@@ -24,20 +24,26 @@
}
],
"require": {
- "craftcms/cms": "^3.2.0",
+ "craftcms/cms": "^4.1.0",
"guzzlehttp/guzzle": "^6.5.5|^7.2.0"
},
"require-dev": {
- "vimeo/psalm": "^4.4"
+ "craftcms/phpstan": "*",
+ "friendsofphp/php-cs-fixer": "^3.0",
+ "pestphp/pest": "^1.2",
+ "pestphp/pest-plugin-parallel": "^1.0"
},
"autoload": {
"psr-4": {
- "ostark\\upper\\": "src/"
- }
+ "ostark\\Upper\\": "src/"
+ },
+ "files": [
+ "src/helpers.php"
+ ]
},
"scripts": {
- "ps": "phpstan analyse src --level=5 -c phpstan.neon",
- "stan": "@ps"
+ "phpstan": "vendor/bin/phpstan analyse",
+ "test": "vendor/bin/pest"
},
"extra": {
"name": "Upper",
@@ -45,5 +51,16 @@
"hasCpSettings": false,
"hasCpSection": false,
"changelogUrl": "https://raw.githubusercontent.com/ostark/upper/master/CHANGELOG.md"
- }
+ },
+ "config": {
+ "allow-plugins": {
+ "yiisoft/yii2-composer": true,
+ "composer/package-versions-deprecated": true,
+ "craftcms/plugin-installer": true,
+ "pestphp/pest-plugin": true
+ }
+ },
+
+ "prefer-stable": true,
+ "minimum-stability": "dev"
}
diff --git a/phpstan.neon b/phpstan.neon
new file mode 100644
index 0000000..be0a203
--- /dev/null
+++ b/phpstan.neon
@@ -0,0 +1,11 @@
+includes:
+ - vendor/craftcms/phpstan/phpstan.neon
+parameters:
+ level: 6
+ paths:
+ - src
+ - tests
+ tmpDir: build/phpstan
+ checkMissingIterableValueType: false
+ ignoreErrors:
+ - "#Unable to resolve the template type T in call to method static method#"
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
new file mode 100644
index 0000000..9203aad
--- /dev/null
+++ b/phpunit.xml.dist
@@ -0,0 +1,38 @@
+
+
+
+
+ tests
+
+
+
+
+ ./src
+
+
+
+
+
+
+
+
+
+
+
diff --git a/psalm.xml b/psalm.xml
deleted file mode 100644
index 5509c48..0000000
--- a/psalm.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/CacheResponse.php b/src/CacheResponse.php
index 71d3309..f6df701 100644
--- a/src/CacheResponse.php
+++ b/src/CacheResponse.php
@@ -1,11 +1,11 @@
-getRequest();
-
- // Don't cache CP, LivePreview, Action, Non-GET requests
- if ($request->getIsCpRequest() ||
- $request->getIsLivePreview() ||
- $request->getIsActionRequest() ||
- !$request->getIsGet()
- ) {
- $response = \Craft::$app->getResponse();
- $response->addCacheControlDirective('private');
- $response->addCacheControlDirective('no-cache');
-
- return false;
- }
-
- // Collect tags
- Event::on(ElementQuery::class, ElementQuery::EVENT_AFTER_POPULATE_ELEMENT, function (PopulateElementEvent $event) {
-
- // Don't collect MatrixBlock and User elements for now
- if (!Plugin::getInstance()->getSettings()->isCachableElement(get_class($event->element))) {
- return;
- }
-
- // Tag with GlobalSet handle
- if ($event->element instanceof \craft\elements\GlobalSet) {
- Plugin::getInstance()->getTagCollection()->add($event->element->handle);
- }
-
- // Add to collection
- Plugin::getInstance()->getTagCollection()->addTagsFromElement($event->row);
-
- });
-
- // Add the tags to the response header
- Event::on(View::class, View::EVENT_AFTER_RENDER_PAGE_TEMPLATE, function (TemplateEvent $event) {
-
- /** @var \yii\web\Response $response */
- $response = \Craft::$app->getResponse();
- $plugin = Plugin::getInstance();
- $tagCollection = $plugin->getTagCollection();
- $tags = $plugin->getTagCollection()->getAll();
- $settings = $plugin->getSettings();
- $headers = $response->getHeaders();
-
- // Make existing cache-control headers accessible
- $response->setCacheControlDirectiveFromString($headers->get('cache-control'));
-
- // Don't cache if private | no-cache set already
- if ($response->hasCacheControlDirective('private') || $response->hasCacheControlDirective('no-cache')) {
- $headers->set(Plugin::INFO_HEADER_NAME, 'BYPASS');
-
- return;
- }
-
- // MaxAge or defaultMaxAge?
- $maxAge = $response->getMaxAge() ?? $settings->defaultMaxAge;
-
- // Set Headers
- $maxBytes = $settings->maxBytesForCacheTagHeader;
- $maxedTags = $tagCollection->getUntilMaxBytes($maxBytes);
- $response->setTagHeader($settings->getTagHeaderName(), $maxedTags, $settings->getHeaderTagDelimiter());
-
- // Flag truncation
- if (count($tags) > count($maxedTags)) {
- $headers->set(Plugin::TRUNCATED_HEADER_NAME, count($tags) - count($maxedTags));
- }
-
- $response->setSharedMaxAge($maxAge);
- $headers->set(Plugin::INFO_HEADER_NAME, "CACHED: " . date(\DateTime::ISO8601));
-
- $plugin->trigger($plugin::EVENT_AFTER_SET_TAG_HEADER, new CacheResponseEvent([
- 'tags' => $tags,
- 'maxAge' => $maxAge,
- 'requestUrl' => \Craft::$app->getRequest()->getUrl(),
- 'headers' => $response->getHeaders()->toArray()
- ]
- ));
- });
-
- }
-
-
- public static function registerCpEvents()
- {
- // Register cache purge checkbox
- Event::on(
- ClearCaches::class,
- ClearCaches::EVENT_REGISTER_CACHE_OPTIONS,
- function (RegisterCacheOptionsEvent $event) {
- $driver = ucfirst(Plugin::getInstance()->getSettings()->driver);
- $event->options[] = [
- 'key' => 'upper-purge-all',
- 'label' => \Craft::t('upper', 'Upper ({driver})', ['driver' => $driver]),
- 'action' => function () {
- Plugin::getInstance()->getPurger()->purgeAll();
- },
- ];
- }
- );
- }
-
-
- public static function registerFallback()
- {
-
- Event::on(Plugin::class, Plugin::EVENT_AFTER_SET_TAG_HEADER, function (CacheResponseEvent $event) {
-
- // not tagged?
- if (0 == count($event->tags)) {
- return;
- }
-
- // fulltext or array
- $tags = \Craft::$app->getDb()->getIsMysql()
- ? implode(" ", $event->tags)
- : str_replace(['[', ']'], ['{', '}'], json_encode($event->tags) ?: '[]');
-
- // in order to have a unique (collitions are possible) identifier by url with a fixed length
- $urlHash = md5($event->requestUrl);
-
- try {
- // Insert item
- \Craft::$app->getDb()->createCommand()
- ->upsert(
- // Table
- Plugin::CACHE_TABLE,
-
- // Identifier
- ['urlHash' => $urlHash],
-
- // Data
- [
- 'urlHash' => $urlHash,
- 'url' => $event->requestUrl,
- 'tags' => $tags,
- 'headers' => json_encode($event->headers),
- 'siteId' => \Craft::$app->getSites()->currentSite->id
- ]
- )
- ->execute();
- } catch (\Exception $e) {
- \Craft::warning("Failed to register fallback.", "upper");
- }
-
- });
-
- }
-
-
- /**
- * @param \yii\base\Event $event
- */
- protected static function handleUpdateEvent(Event $event)
- {
- $tags = [];
-
-
- if ($event instanceof ElementEvent) {
-
- if (!Plugin::getInstance()->getSettings()->isCachableElement(get_class($event->element))) {
- return;
- }
-
- // Prevent purge on updates of drafts or revisions
- if (ElementHelper::isDraftOrRevision($event->element)) {
- return;
- }
-
- // Prevent purge on resaving
- if (property_exists($event->element, 'resaving') && $event->element->resaving === true) {
- return;
- }
-
- if ($event->element instanceof \craft\elements\GlobalSet && is_string($event->element->handle)) {
- $tags[] = $event->element->handle;
- } elseif ($event->element instanceof \craft\elements\Asset && $event->isNew) {
- $tags[] = (string)$event->element->volumeId;
- } else {
- if (isset($event->element->sectionId)) {
- $tags[] = Plugin::TAG_PREFIX_SECTION . $event->element->sectionId;
- }
- if (!$event->isNew) {
- $tags[] = Plugin::TAG_PREFIX_ELEMENT . $event->element->getId();
- }
- }
- }
-
- if ($event instanceof SectionEvent) {
- $tags[] = Plugin::TAG_PREFIX_SECTION . $event->section->id;
- }
-
- if ($event instanceof MoveElementEvent or $event instanceof ElementStructureEvent) {
- $tags[] = Plugin::TAG_PREFIX_STRUCTURE . $event->structureId;
- }
-
- if (count($tags) === 0) {
- return;
- }
-
- foreach ($tags as $tag) {
- $tag = Plugin::getInstance()->getTagCollection()->prepareTag($tag);
-
- $purgeEvent = new PurgeEvent([
- 'tag' => $tag,
- ]);
-
- Plugin::getInstance()->trigger(Plugin::EVENT_BEFORE_PURGE, $purgeEvent);
-
- // Push to queue
- \Craft::$app->getQueue()->push(new PurgeCacheJob([
- 'tag' => $purgeEvent->tag
- ]
- ));
-
- Plugin::getInstance()->trigger(Plugin::EVENT_AFTER_PURGE, $purgeEvent);
- }
-
- }
-
-}
diff --git a/src/events/CacheResponseEvent.php b/src/Events/CacheResponseEvent.php
similarity index 86%
rename from src/events/CacheResponseEvent.php
rename to src/Events/CacheResponseEvent.php
index 4c56501..d2a2b7c 100644
--- a/src/events/CacheResponseEvent.php
+++ b/src/Events/CacheResponseEvent.php
@@ -1,11 +1,11 @@
-settings = $settings;
+ $this->tags = $tags;
+ }
+
+ public function __invoke(TemplateEvent $event): void
+ {
+ /** @var \yii\web\Response $response */
+ $response = \Craft::$app->getResponse();
+ $plugin = Plugin::getInstance();
+ $tagCollection = $this->tags;
+ $tags = $this->tags->getAll();
+ $headers = $response->getHeaders();
+
+ // Make existing cache-control headers accessible
+ $response->setCacheControlDirectiveFromString($headers->get('cache-control'));
+
+ // Don't cache if private | no-cache set already
+ if ($response->hasCacheControlDirective('private') || $response->hasCacheControlDirective('no-cache')) {
+ $headers->set(Plugin::INFO_HEADER_NAME, 'BYPASS');
+
+ return;
+ }
+
+ // MaxAge or defaultMaxAge?
+ $maxAge = $response->getMaxAge() ?? $this->settings->defaultMaxAge;
+
+ // Set Headers
+ $maxBytes = $this->settings->maxBytesForCacheTagHeader;
+ $maxedTags = $tagCollection->getUntilMaxBytes($maxBytes);
+ $response->setTagHeader($this->settings->getTagHeaderName(), $maxedTags, $this->settings->getHeaderTagDelimiter());
+
+ // Flag truncation
+ if (count($tags) > count($maxedTags)) {
+ $headers->set(Plugin::TRUNCATED_HEADER_NAME, count($tags) - count($maxedTags));
+ }
+
+ $response->setSharedMaxAge($maxAge);
+ $headers->set(Plugin::INFO_HEADER_NAME, "CACHED: " . date(\DateTime::ISO8601));
+
+ $plugin->trigger($plugin::EVENT_AFTER_SET_TAG_HEADER, new CacheResponseEvent([
+ 'tags' => $tags,
+ 'maxAge' => $maxAge,
+ 'requestUrl' => \Craft::$app->getRequest()->getUrl(),
+ 'headers' => $response->getHeaders()->toArray()
+ ]
+ ));
+
+ }
+}
diff --git a/src/Handlers/CollectTagsFromElementQuery.php b/src/Handlers/CollectTagsFromElementQuery.php
new file mode 100644
index 0000000..4d5574d
--- /dev/null
+++ b/src/Handlers/CollectTagsFromElementQuery.php
@@ -0,0 +1,34 @@
+settings = $settings;
+ $this->tags = $tags;
+ }
+
+ public function __invoke(PopulateElementEvent $event): void
+ {
+ // Don't collect MatrixBlock and User elements for now
+ if (!$this->settings->isCachableElement(get_class($event->element))) {
+ return;
+ }
+
+ // Tag with GlobalSet handle
+ if ($event->element instanceof \craft\elements\GlobalSet) {
+ $this->tags->add($event->element->handle);
+ }
+
+ // Add to collection
+ $this->tags->addTagsFromElement($event->row);
+ }
+}
diff --git a/src/Handlers/CollectTagsFromTemplateCache.php b/src/Handlers/CollectTagsFromTemplateCache.php
new file mode 100644
index 0000000..d0232dc
--- /dev/null
+++ b/src/Handlers/CollectTagsFromTemplateCache.php
@@ -0,0 +1,17 @@
+settings = $settings;
+ }
+
+ public function __invoke(): void
+ {
+ }
+}
diff --git a/src/Handlers/InvalidateCache.php b/src/Handlers/InvalidateCache.php
new file mode 100644
index 0000000..44ffc34
--- /dev/null
+++ b/src/Handlers/InvalidateCache.php
@@ -0,0 +1,89 @@
+settings = $settings;
+ }
+
+ public function __invoke(Event $event): void
+ {
+
+ $tags = [];
+
+ if ($event instanceof ElementEvent) {
+
+ if (!$this->settings->isCachableElement(get_class($event->element))) {
+ return;
+ }
+
+ // Prevent purge on updates of drafts or revisions
+ if (ElementHelper::isDraftOrRevision($event->element)) {
+ return;
+ }
+
+ // Prevent purge on resaving
+ if (property_exists($event->element, 'resaving') && $event->element->resaving === true) {
+ return;
+ }
+
+ if ($event->element instanceof \craft\elements\GlobalSet && is_string($event->element->handle)) {
+ $tags[] = $event->element->handle;
+ } elseif ($event->element instanceof \craft\elements\Asset && $event->isNew) {
+ $tags[] = (string)$event->element->volumeId;
+ } else {
+ if (isset($event->element->sectionId)) {
+ $tags[] = Plugin::TAG_PREFIX_SECTION . $event->element->sectionId;
+ }
+ if (!$event->isNew) {
+ $tags[] = Plugin::TAG_PREFIX_ELEMENT . $event->element->getId();
+ }
+ }
+ }
+
+ if ($event instanceof SectionEvent) {
+ $tags[] = Plugin::TAG_PREFIX_SECTION . $event->section->id;
+ }
+
+ if ($event instanceof MoveElementEvent or $event instanceof ElementStructureEvent) {
+ $tags[] = Plugin::TAG_PREFIX_STRUCTURE . $event->structureId;
+ }
+
+ if (count($tags) === 0) {
+ return;
+ }
+
+ foreach ($tags as $tag) {
+ $tag = Plugin::getInstance()->getTagCollection()->prepareTag($tag);
+
+ $purgeEvent = new PurgeEvent([
+ 'tag' => $tag,
+ ]);
+
+ Plugin::getInstance()->trigger(Plugin::EVENT_BEFORE_PURGE, $purgeEvent);
+
+ // Push to queue
+ \Craft::$app->getQueue()->push(new PurgeCacheJob([
+ 'tag' => $purgeEvent->tag
+ ]
+ ));
+
+ Plugin::getInstance()->trigger(Plugin::EVENT_AFTER_PURGE, $purgeEvent);
+ }
+
+ }
+}
diff --git a/src/Handlers/RegisterCacheCheckbox.php b/src/Handlers/RegisterCacheCheckbox.php
new file mode 100644
index 0000000..a236f16
--- /dev/null
+++ b/src/Handlers/RegisterCacheCheckbox.php
@@ -0,0 +1,32 @@
+settings = $settings;
+ $this->purger = $purger;
+ }
+
+ public function __invoke(RegisterCacheOptionsEvent $event, CachePurgeInterface $purger): void
+ {
+ $driver = ucfirst($this->settings->driver);
+ $event->options[] = [
+ 'key' => 'upper-purge-all',
+ 'label' => \Craft::t('upper', 'Upper ({driver})', ['driver' => $driver]),
+ 'action' => function () {
+ $this->purger->purgeAll();
+ },
+ ];
+
+
+ }
+}
diff --git a/src/Handlers/StoreTagUrlRelation.php b/src/Handlers/StoreTagUrlRelation.php
new file mode 100644
index 0000000..0714007
--- /dev/null
+++ b/src/Handlers/StoreTagUrlRelation.php
@@ -0,0 +1,55 @@
+tags)) {
+ return;
+ }
+
+ // fulltext or array
+ $tags = \Craft::$app->getDb()->getIsMysql()
+ ? implode(" ", $event->tags)
+ : str_replace(['[', ']'], ['{', '}'], json_encode($event->tags) ?: '[]');
+
+ // in order to have a unique (collitions are possible) identifier by url with a fixed length
+ $urlHash = md5($event->requestUrl);
+
+ try {
+ // Insert item
+ \Craft::$app->getDb()->createCommand()
+ ->upsert(
+ // Table
+ Plugin::CACHE_TABLE,
+
+ // Identifier
+ ['urlHash' => $urlHash],
+
+ // Data
+ [
+ 'urlHash' => $urlHash,
+ 'url' => $event->requestUrl,
+ 'tags' => $tags,
+ 'headers' => json_encode($event->headers),
+ 'siteId' => \Craft::$app->getSites()->currentSite->id
+ ]
+ )
+ ->execute();
+ } catch (\Exception $e) {
+ \Craft::warning("Failed to register fallback.", "upper");
+ }
+
+ }
+}
diff --git a/src/jobs/PurgeCacheJob.php b/src/Jobs/PurgeCacheJob.php
similarity index 87%
rename from src/jobs/PurgeCacheJob.php
rename to src/Jobs/PurgeCacheJob.php
index 7210177..87a4aa3 100644
--- a/src/jobs/PurgeCacheJob.php
+++ b/src/Jobs/PurgeCacheJob.php
@@ -1,13 +1,13 @@
-setComponents([
- 'purger' => PurgerFactory::create($this->getSettings()->toArray()),
- 'tagCollection' => TagCollection::class
- ]);
+ // Register TagCollection in container
+ Craft::$container->setSingleton(TagCollection::class, function () {
+ $collection = new TagCollection();
+ $collection->setKeyPrefix($this->getSettings()->getKeyPrefix());
+ return $collection;
+ });
+
+ // Register Purger in container
+ Craft::$container->set(CachePurgeInterface::class , function () {
+ return PurgerFactory::create($this->getSettings()->toArray());
+ });
// Attach Behaviors
- \Craft::$app->getResponse()->attachBehavior('cache-control', CacheControlBehavior::class);
- \Craft::$app->getResponse()->attachBehavior('tag-header', TagHeaderBehavior::class);
+ // TODO -> different implementation
+ // \Craft::$app->getResponse()->attachBehavior('cache-control', CacheControlBehavior::class);
+ // \Craft::$app->getResponse()->attachBehavior('tag-header', TagHeaderBehavior::class);
// Register event handlers
- EventRegistrar::registerFrontendEvents();
- EventRegistrar::registerCpEvents();
- EventRegistrar::registerUpdateEvents();
-
- if ($this->getSettings()->useLocalTags) {
- EventRegistrar::registerFallback();
- }
+ $this->registerFrontendEventHandlers();
+ $this->registerCpEventHandlers();
+ $this->registerUpdateEventHandlers();
// Register Twig extension
\Craft::$app->getView()->registerTwigExtension(new TwigExtension);
}
- // ServiceLocators
- // =========================================================================
- /**
- * @return \ostark\upper\drivers\CachePurgeInterface
- */
- public function getPurger(): CachePurgeInterface
+ private function registerFrontendEventHandlers(): void
{
- return $this->get('purger');
+ if ($this->isNotCacheable()) {
+ // TODO
+ // $response = \Craft::$app->getResponse();
+ // $response->addCacheControlDirective('private');
+ //$response->addCacheControlDirective('no-cache');
+ return;
+ }
+
+ Event::on(
+ ElementQuery::class,
+ ElementQuery::EVENT_AFTER_POPULATE_ELEMENT,
+ new CollectTagsFromElementQuery($this->getSettings(), tags())
+ );
+
+ Event::on(
+ ElementQuery::class,
+ ElementQuery::EVENT_DEFINE_CACHE_TAGS,
+ new CollectTagsFromTemplateCache($this->getSettings())
+ );
+
+ Event::on(
+ View::class,
+ View::EVENT_AFTER_RENDER_PAGE_TEMPLATE,
+ new AddCacheResponse($this->getSettings(), tags())
+ );
+
+ Event::on(
+ Plugin::class,
+ Plugin::EVENT_AFTER_SET_TAG_HEADER,
+ new StoreTagUrlRelation()
+ );
+
}
+ private function registerCpEventHandlers(): void
+ {
+ Event::on(
+ ClearCaches::class,
+ ClearCaches::EVENT_REGISTER_CACHE_OPTIONS,
+ new RegisterCacheCheckbox($this->getSettings(), purger())
+ );
+ }
- /**
- * @return \ostark\upper\TagCollection
- */
- public function getTagCollection(): TagCollection
+ private function registerUpdateEventHandlers(): void
{
- /* @var \ostark\upper\TagCollection $collection */
- $collection = $this->get('tagCollection');
- $collection->setKeyPrefix($this->getSettings()->getKeyPrefix());
+ $handler = new InvalidateCache($this->getSettings());
- return $collection;
+ Event::on(Elements::class, Elements::EVENT_AFTER_SAVE_ELEMENT, $handler);
+ Event::on(Element::class, Element::EVENT_AFTER_MOVE_IN_STRUCTURE, $handler);
+ Event::on(Elements::class, Elements::EVENT_AFTER_DELETE_ELEMENT, $handler);
+ Event::on(Structures::class, Structures::EVENT_AFTER_MOVE_ELEMENT, $handler);
+ Event::on(Sections::class, Sections::EVENT_AFTER_SAVE_SECTION, $handler);
}
- // Protected Methods
- // =========================================================================
+ private function isNotCacheable(): bool
+ {
+ if (\Craft::$app instanceof \craft\console\Application) {
+ return true;
+ }
+
+ $request = \Craft::$app->getRequest();
+
+ if ($request->getIsCpRequest() ||
+ $request->getIsLivePreview() ||
+ $request->getIsActionRequest() ||
+ !$request->getIsGet()
+ ) {
+ return true;
+ }
+ return false;
+ }
+
/**
* Creates and returns the model used to store the plugin’s settings.
- *
- * @return \craft\base\Model|null
*/
- protected function createSettingsModel()
+ protected function createSettingsModel(): PluginSettings
{
- return new Settings();
+ return new PluginSettings();
}
@@ -122,7 +186,7 @@ protected function createSettingsModel()
* Is called after the plugin is installed.
* Copies example config to project's config folder
*/
- protected function afterInstall()
+ protected function afterInstall():void
{
$configSourceFile = __DIR__ . DIRECTORY_SEPARATOR . 'config.example.php';
$configTargetFile = \Craft::$app->getConfig()->configDir . DIRECTORY_SEPARATOR . $this->handle . '.php';
@@ -131,5 +195,4 @@ protected function afterInstall()
copy($configSourceFile, $configTargetFile);
}
}
-
}
diff --git a/src/PurgerFactory.php b/src/PurgerFactory.php
index 6f9dfb2..0456838 100644
--- a/src/PurgerFactory.php
+++ b/src/PurgerFactory.php
@@ -1,16 +1,16 @@
-set('foo', new \my\FooDummy());
+});
+
+it('is always true', function () {
+
+ $someResult = true;
+
+ // Assert
+ expect($someResult)->toBeTrue();
+});
diff --git a/tests/Pest.php b/tests/Pest.php
new file mode 100644
index 0000000..6d4256c
--- /dev/null
+++ b/tests/Pest.php
@@ -0,0 +1,39 @@
+in('Feature');
+
+/*
+|--------------------------------------------------------------------------
+| Expectations
+|--------------------------------------------------------------------------
+|
+| When you're writing tests, you often need to check that values meet certain conditions. The
+| "expect()" function gives you access to a set of "expectations" methods that you can use
+| to assert different things. Of course, you may extend the Expectation API at any time.
+|
+*/
+
+
+/*
+|--------------------------------------------------------------------------
+| Functions
+|--------------------------------------------------------------------------
+|
+| While Pest is very powerful out-of-the-box, you may have some testing code specific to your
+| project that you don't want to repeat in every file. Here you can also expose helpers as
+| global functions to help you to reduce the number of lines of code in your test files.
+|
+*/
diff --git a/tests/bootstrap.php b/tests/bootstrap.php
index bfe6e85..9ed0f43 100644
--- a/tests/bootstrap.php
+++ b/tests/bootstrap.php
@@ -1,13 +1,56 @@
env = $environment;
+$configService->configDir = CRAFT_CONFIG_PATH;
+$configService->appDefaultsDir = dirname(__DIR__) . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'config' . DIRECTORY_SEPARATOR . 'defaults';
+$generalConfig = $configService->getConfigFromFile('general');
+
+$config = \craft\helpers\ArrayHelper::merge(
+ [
+ 'vendorPath' => CRAFT_VENDOR_PATH,
+ 'env' => $environment,
+ 'components' => [
+ 'config' => $configService,
+ ],
+ 'id' => 'test',
+ 'basePath' => __DIR__,
+ 'class' => craft\console\Application::class,
+ ],
+ require 'vendor/craftcms/cms/src/config/app.php',
+ require 'vendor/craftcms/cms/src/config/app.console.php',
+ $configService->getConfigFromFile('app'),
+);
+
+// Initialize the application
+/** @var \craft\web\Application|craft\console\Application $app */
+$app = Craft::createObject($config);
+
+
+// Load and run Craft
+/** @var craft\console\Application $app */
+\Craft::$app = $app;