Compare commits
6 commits
Author | SHA1 | Date | |
---|---|---|---|
a95adfcc3d | |||
7e3324b77f | |||
f09464f9f7 | |||
09d4c662be | |||
c78f3d0435 | |||
4e0615281c |
12 changed files with 374 additions and 33 deletions
composer.jsoncomposer.lock
web/modules/custom
example/tests/src/Functional
my_module
|
@ -109,6 +109,7 @@
|
|||
"require-dev": {
|
||||
"drupal/core-dev": "^10",
|
||||
"fenetikm/autoload-drupal": "dev-autoload-tests",
|
||||
"phpspec/prophecy-phpunit": "^2"
|
||||
"phpspec/prophecy-phpunit": "^2",
|
||||
"phpstan/phpstan-strict-rules": "^1.5"
|
||||
}
|
||||
}
|
||||
|
|
51
composer.lock
generated
51
composer.lock
generated
|
@ -4,7 +4,7 @@
|
|||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "b7efed8e16e9b99d9aba5afbdb499669",
|
||||
"content-hash": "2d3f22a75cc98e2996a5d6b9f3934faf",
|
||||
"packages": [
|
||||
{
|
||||
"name": "asm89/stack-cors",
|
||||
|
@ -8218,6 +8218,55 @@
|
|||
},
|
||||
"time": "2023-05-26T11:05:59+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpstan/phpstan-strict-rules",
|
||||
"version": "1.5.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/phpstan/phpstan-strict-rules.git",
|
||||
"reference": "b21c03d4f6f3a446e4311155f4be9d65048218e6"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/b21c03d4f6f3a446e4311155f4be9d65048218e6",
|
||||
"reference": "b21c03d4f6f3a446e4311155f4be9d65048218e6",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.2 || ^8.0",
|
||||
"phpstan/phpstan": "^1.10"
|
||||
},
|
||||
"require-dev": {
|
||||
"nikic/php-parser": "^4.13.0",
|
||||
"php-parallel-lint/php-parallel-lint": "^1.2",
|
||||
"phpstan/phpstan-deprecation-rules": "^1.1",
|
||||
"phpstan/phpstan-phpunit": "^1.0",
|
||||
"phpunit/phpunit": "^9.5"
|
||||
},
|
||||
"type": "phpstan-extension",
|
||||
"extra": {
|
||||
"phpstan": {
|
||||
"includes": [
|
||||
"rules.neon"
|
||||
]
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"PHPStan\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"description": "Extra strict and opinionated rules for PHPStan",
|
||||
"support": {
|
||||
"issues": "https://github.com/phpstan/phpstan-strict-rules/issues",
|
||||
"source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.5.1"
|
||||
},
|
||||
"time": "2023-03-29T14:47:40+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpunit/php-code-coverage",
|
||||
"version": "9.2.27",
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\example\Functional;
|
||||
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
final class AdminPageTest extends BrowserTestBase {
|
||||
|
||||
public $defaultTheme = 'stark';
|
||||
|
||||
/** @test */
|
||||
public function the_admin_page_is_not_accessible_to_anonymous_users(): void {
|
||||
$this->drupalGet(path: '/admin');
|
||||
|
||||
$assert = $this->assertSession();
|
||||
|
||||
$assert->statusCodeEquals(Response::HTTP_FORBIDDEN);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function the_admin_page_is_accessible_by_admin_users(): void {
|
||||
$adminUser = $this->createUser(
|
||||
permissions: [
|
||||
'access administration pages',
|
||||
],
|
||||
);
|
||||
|
||||
$this->drupalLogin(account: $adminUser);
|
||||
|
||||
$this->drupalGet(path: '/admin');
|
||||
|
||||
$assert = $this->assertSession();
|
||||
|
||||
$assert->statusCodeEquals(Response::HTTP_OK);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\example\Functional;
|
||||
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
final class ExamplePageTest extends BrowserTestBase {
|
||||
|
||||
public $defaultTheme = 'stark';
|
||||
|
||||
protected static $modules = [
|
||||
// Core.
|
||||
'node',
|
||||
|
||||
// Custom.
|
||||
"example"
|
||||
];
|
||||
|
||||
/** @test */
|
||||
public function should_load_the_example_page_for_anonymous_users(): void {
|
||||
// Arrange.
|
||||
|
||||
// Act.
|
||||
$this->drupalGet('/@opdavies/drupal-module-template');
|
||||
|
||||
// Assert.
|
||||
$this->assertSession()->statusCodeEquals(Response::HTTP_OK);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\example\Functional;
|
||||
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
final class FrontPageTest extends BrowserTestBase {
|
||||
|
||||
public $defaultTheme = 'stark';
|
||||
|
||||
protected static $modules = ['node', 'views'];
|
||||
|
||||
/** @test */
|
||||
public function the_front_page_loads_for_anonymous_users(): void {
|
||||
$this->config('system.site')
|
||||
->set('page.front', '/node')
|
||||
->save(TRUE);
|
||||
|
||||
$this->drupalGet('<front>');
|
||||
|
||||
$assert = $this->assertSession();
|
||||
|
||||
$assert->statusCodeEquals(Response::HTTP_OK);
|
||||
$assert->pageTextContains('No front page content has been created yet.');
|
||||
}
|
||||
|
||||
}
|
3
web/modules/custom/my_module/my_module.info.yml
Normal file
3
web/modules/custom/my_module/my_module.info.yml
Normal file
|
@ -0,0 +1,3 @@
|
|||
name: My Module
|
||||
type: module
|
||||
core_version_requirement: ^10
|
7
web/modules/custom/my_module/my_module.routing.yml
Normal file
7
web/modules/custom/my_module/my_module.routing.yml
Normal file
|
@ -0,0 +1,7 @@
|
|||
blog.page:
|
||||
path: /blog
|
||||
defaults:
|
||||
_controller: Drupal\my_module\Controller\BlogPageController
|
||||
_title: Blog
|
||||
requirements:
|
||||
_permission: access content
|
6
web/modules/custom/my_module/my_module.services.yml
Normal file
6
web/modules/custom/my_module/my_module.services.yml
Normal file
|
@ -0,0 +1,6 @@
|
|||
services:
|
||||
Drupal\my_module\Controller\BlogPageController:
|
||||
autowire: true
|
||||
|
||||
Drupal\my_module\Repository\ArticleRepository:
|
||||
autowire: true
|
|
@ -0,0 +1,51 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\my_module\Controller;
|
||||
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Entity\EntityViewBuilderInterface;
|
||||
use Drupal\Core\Render\RendererInterface;
|
||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
|
||||
use Drupal\my_module\Repository\ArticleRepository;
|
||||
|
||||
final class BlogPageController {
|
||||
|
||||
use StringTranslationTrait;
|
||||
|
||||
private EntityViewBuilderInterface $nodeViewBuilder;
|
||||
|
||||
public function __construct(
|
||||
private RendererInterface $renderer,
|
||||
EntityTypeManagerInterface $entityTypeManager,
|
||||
private ArticleRepository $articleRepository,
|
||||
) {
|
||||
$this->nodeViewBuilder = $entityTypeManager->getViewBuilder(entity_type_id: 'node');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function __invoke(): array {
|
||||
$articles = $this->articleRepository->getAll();
|
||||
|
||||
if ($articles === []) {
|
||||
return ['#markup' => $this->t('Welcome to my blog!')];
|
||||
}
|
||||
|
||||
$build = [];
|
||||
|
||||
foreach ($articles as $article) {
|
||||
$build[] = $this->nodeViewBuilder->view(
|
||||
entity: $article,
|
||||
view_mode: 'teaser',
|
||||
);
|
||||
}
|
||||
|
||||
return [
|
||||
'#markup' => $this->renderer->render($build),
|
||||
];
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\my_module\Repository;
|
||||
|
||||
use Drupal\Core\Entity\EntityStorageInterface;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\node\NodeInterface;
|
||||
|
||||
final class ArticleRepository {
|
||||
|
||||
private EntityStorageInterface $nodeStorage;
|
||||
|
||||
public function __construct(EntityTypeManagerInterface $entityTypeManager) {
|
||||
$this->nodeStorage = $entityTypeManager->getStorage(entity_type_id: 'node');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, NodeInterface>
|
||||
*/
|
||||
public function getAll(): array {
|
||||
/** @var array<int, NodeInterface> */
|
||||
$articles = $this->nodeStorage->loadByProperties([
|
||||
'status' => NodeInterface::PUBLISHED,
|
||||
'type' => 'article',
|
||||
]);
|
||||
|
||||
// Sort the articles by their created time.
|
||||
uasort($articles, function (NodeInterface $a, NodeInterface $b): int {
|
||||
return $a->getCreatedTime() < $b->getCreatedTime() ? 1 : -1;
|
||||
});
|
||||
|
||||
return $articles;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\my_module\Functional;
|
||||
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
final class BlogPageTest extends BrowserTestBase {
|
||||
|
||||
public $defaultTheme = 'stark';
|
||||
|
||||
protected static $modules = [
|
||||
// Core.
|
||||
'node',
|
||||
|
||||
// Custom.
|
||||
'my_module',
|
||||
];
|
||||
|
||||
/** @test */
|
||||
public function the_blog_page_loads_for_anonymous_users_and_contains_the_right_text(): void {
|
||||
$this->drupalGet('/blog');
|
||||
|
||||
$assert = $this->assertSession();
|
||||
|
||||
$assert->statusCodeEquals(Response::HTTP_OK);
|
||||
$assert->responseContains('<h1>Blog</h1>');
|
||||
$assert->pageTextContains('Welcome to my blog!');
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_shows_articles(): void {
|
||||
$this->createContentType(['type' => 'article']);
|
||||
|
||||
$this->createNode([
|
||||
'title' => 'This is a test article',
|
||||
'type' => 'article',
|
||||
]);
|
||||
|
||||
$this->drupalGet('/blog');
|
||||
|
||||
$assert = $this->assertSession();
|
||||
|
||||
$assert->statusCodeEquals(Response::HTTP_OK);
|
||||
$assert->pageTextContains('This is a test article');
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,98 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\my_module\Kernel\Repository;
|
||||
|
||||
use Drupal\Core\Datetime\DrupalDateTime;
|
||||
use Drupal\KernelTests\Core\Entity\EntityKernelTestBase;
|
||||
use Drupal\my_module\Repository\ArticleRepository;
|
||||
use Drupal\node\Entity\Node;
|
||||
use Drupal\Tests\node\Traits\NodeCreationTrait;
|
||||
|
||||
final class ArticleRepositoryTest extends EntityKernelTestBase {
|
||||
|
||||
use NodeCreationTrait;
|
||||
|
||||
protected $strictConfigSchema = FALSE;
|
||||
|
||||
public static $modules = [
|
||||
'node',
|
||||
'my_module',
|
||||
];
|
||||
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->installSchema(module: 'node', tables: ['node_access']);
|
||||
|
||||
$this->installConfig([
|
||||
'filter',
|
||||
]);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function nodes_that_are_not_articles_are_not_returned() {
|
||||
$this->createNode(['type' => 'article'])->save();
|
||||
$this->createNode(['type' => 'page'])->save();
|
||||
$this->createNode(['type' => 'article'])->save();
|
||||
$this->createNode(['type' => 'page'])->save();
|
||||
$this->createNode(['type' => 'article'])->save();
|
||||
|
||||
$this->assertCount(5, Node::loadMultiple());
|
||||
|
||||
$repository = $this->container->get(ArticleRepository::class);
|
||||
$articles = $repository->getAll();
|
||||
|
||||
$this->assertCount(3, $articles);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function only_published_articles_are_returned() {
|
||||
$this->createNode(['type' => 'article', 'status' => Node::PUBLISHED])->save();
|
||||
$this->createNode(['type' => 'article', 'status' => Node::NOT_PUBLISHED])->save();
|
||||
$this->createNode(['type' => 'article', 'status' => Node::PUBLISHED])->save();
|
||||
$this->createNode(['type' => 'article', 'status' => Node::NOT_PUBLISHED])->save();
|
||||
$this->createNode(['type' => 'article', 'status' => Node::PUBLISHED])->save();
|
||||
|
||||
$repository = $this->container->get(ArticleRepository::class);
|
||||
$articles = $repository->getAll();
|
||||
|
||||
$this->assertCount(3, $articles);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function nodes_are_ordered_by_date_and_returned_newest_first() {
|
||||
$this->createNode([
|
||||
'type' => 'article',
|
||||
'created' => (new DrupalDateTime('-2 days'))->getTimestamp(),
|
||||
]);
|
||||
|
||||
$this->createNode([
|
||||
'type' => 'article',
|
||||
'created' => (new DrupalDateTime('-1 week'))->getTimestamp(),
|
||||
]);
|
||||
|
||||
$this->createNode([
|
||||
'type' => 'article',
|
||||
'created' => (new DrupalDateTime('-1 hour'))->getTimestamp(),
|
||||
]);
|
||||
|
||||
$this->createNode([
|
||||
'type' => 'article',
|
||||
'created' => (new DrupalDateTime('-1 year'))->getTimestamp(),
|
||||
]);
|
||||
|
||||
$this->createNode([
|
||||
'type' => 'article',
|
||||
'created' => (new DrupalDateTime('-1 month'))->getTimestamp(),
|
||||
]);
|
||||
|
||||
$repository = $this->container->get(ArticleRepository::class);
|
||||
$nodes = $repository->getAll();
|
||||
$nodeIds = array_keys($nodes);
|
||||
|
||||
$this->assertSame([3, 1, 2, 5, 4], $nodeIds);
|
||||
}
|
||||
|
||||
}
|
Reference in a new issue