pull/28/head
Corentin RICHARD 10 months ago
commit aab1260624

@ -0,0 +1 @@
{"version":1,"defects":{"ProfilTest::test_add_and_remove_follower":3,"ProfilTest::test_add_and_remove_following":3},"times":{"ProfilTest::test_it_can_be_instantiated":0.003,"ProfilTest::test_name":0,"ProfilTest::test_description":0,"ProfilTest::test_password":0,"ProfilTest::test_roles":0,"ProfilTest::test_user_identifier":0,"ProfilTest::test_add_and_remove_post":0.003,"ProfilTest::test_add_and_remove_commentary":0.001,"ProfilTest::test_add_and_remove_follower":0.001,"ProfilTest::test_add_and_remove_following":0.003}}

@ -98,7 +98,7 @@
} }
}, },
"require-dev": { "require-dev": {
"phpunit/phpunit": "^9.5", "phpunit/phpunit": "^11.2",
"symfony/browser-kit": "7.0.7", "symfony/browser-kit": "7.0.7",
"symfony/css-selector": "7.0.7", "symfony/css-selector": "7.0.7",
"symfony/debug-bundle": "7.0.7", "symfony/debug-bundle": "7.0.7",

662
composer.lock generated

File diff suppressed because it is too large Load Diff

@ -1,38 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- https://phpunit.readthedocs.io/en/latest/configuration.html -->
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
backupGlobals="false"
colors="true"
bootstrap="tests/bootstrap.php"
convertDeprecationsToExceptions="false"
>
<php>
<ini name="display_errors" value="1" />
<ini name="error_reporting" value="-1" />
<server name="APP_ENV" value="test" force="true" />
<server name="SHELL_VERBOSITY" value="-1" />
<server name="SYMFONY_PHPUNIT_REMOVE" value="" />
<server name="SYMFONY_PHPUNIT_VERSION" value="9.5" />
</php>
<testsuites>
<testsuite name="Project Test Suite">
<directory>tests</directory>
</testsuite>
</testsuites>
<coverage processUncoveredFiles="true">
<include>
<directory suffix=".php">src</directory>
</include>
</coverage>
<listeners>
<listener class="Symfony\Bridge\PhpUnit\SymfonyTestsListener" />
</listeners>
<extensions>
</extensions>
</phpunit>

@ -1,5 +1,11 @@
.form-container { .form-container {
&.form-row {
display: flex;
flex-direction: row;
align-items: center;
}
.form-group { .form-group {
display: flex; display: flex;
flex-direction: column; flex-direction: column;

@ -0,0 +1,31 @@
.wrapper {
display: flex;
flex-direction: column;
width: 70%;
background-color: #f2f2f7;
margin: 0 auto;
padding: 20px;
border-radius: 1rem;
margin-top: 5vh;
border: 3px solid black;
gap: 1rem;
.form-container {
#simple_search {
display: flex;
flex-direction: row;
align-items: center;
gap: 2rem;
justify-content: space-between;
:first-child {
width: 100%;
}
#simple_search_search {
display: flex;
flex-grow: 1;
}
}
}
}

@ -9,18 +9,14 @@ use Symfony\Component\Routing\Attribute\Route;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use App\Entity\Post; use App\Entity\Post;
use App\Form\Type\PostType; use App\Form\Type\PostType;
use App\Form\Type\CommentType; use App\Form\CommentType;
use App\Form\Type\SimpleSearchType;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
class PostController extends AbstractController class PostController extends AbstractController
{ {
private EntityManagerInterface $em; public function __construct(private EntityManagerInterface $em){}
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;
}
#[Route('/', name: 'all_posts', methods: ['GET'])] #[Route('/', name: 'all_posts', methods: ['GET'])]
public function getAllPost(): Response public function getAllPost(): Response
@ -28,7 +24,7 @@ class PostController extends AbstractController
$posts = $this->em->getRepository(Post::class)->findAll(); $posts = $this->em->getRepository(Post::class)->findAll();
return $this->render('post/all.html.twig', [ return $this->render('post/all.html.twig', [
"posts" => $posts, "posts" => array_reverse($posts),
"title" => "Derniers Posts" "title" => "Derniers Posts"
]); ]);
} }
@ -36,16 +32,13 @@ class PostController extends AbstractController
#[Route( #[Route(
'/post/{id}', '/post/{id}',
name: 'display_post', name: 'display_post',
methods: ['GET', 'POST'], requirements: ['id' => '\d+'],
requirements: ['id' => '\d+'] methods: ['GET', 'POST']
)] )]
public function getPost(int $id, Request $request): Response public function getPost(int $id, Request $request): Response
{ {
$post = $this->em->getRepository(Post::class)->find($id); $post = $this->em->getRepository(Post::class)->find($id);
if (!$post) {
}
$comment = new Commentary(); $comment = new Commentary();
$commentForm = $this->createForm(CommentType::class, $comment); $commentForm = $this->createForm(CommentType::class, $comment);
@ -135,4 +128,27 @@ class PostController extends AbstractController
'form' => $form, 'form' => $form,
]); ]);
} }
#[Route('/post/search', name: 'search_post', methods: ['GET', 'POST'])]
public function searchPost(Request $request): Response
{
$form = $this->createForm(SimpleSearchType::class);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$searchString = $form->get('search')->getData();
$posts = $this->em->getRepository(Post::class)->searchByTitleOrText($searchString);
return $this->render('post/search.html.twig', [
'posts' => $posts,
'form' => $this->createForm(SimpleSearchType::class)
]);
// return new Response(print_r($posts, true));
}
return $this->render('post/search.html.twig', [
'form' => $form
]);
}
} }

@ -7,6 +7,7 @@ use App\Form\RegistrationFormType;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Bundle\SecurityBundle\Security; use Symfony\Bundle\SecurityBundle\Security;
use Symfony\Component\Form\FormError;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface; use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
@ -22,6 +23,11 @@ class RegistrationController extends AbstractController
$form->handleRequest($request); $form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) { if ($form->isSubmitted() && $form->isValid()) {
$plainPassword = $form->get('plainPassword')->getData();
if (strlen($plainPassword) < 6) {
$form->get('plainPassword')->addError(new FormError('Your password should be at least 6 characters'));
}
$user->setName($form->get('name')->getData()); $user->setName($form->get('name')->getData());
$hashedPassword = $userPasswordHasher->hashPassword( $hashedPassword = $userPasswordHasher->hashPassword(

@ -211,4 +211,12 @@ class Post
return $this; return $this;
} }
/**
* @param int|null $id
*/
public function setId(?int $id): void
{
$this->id = $id;
}
} }

@ -208,7 +208,7 @@ class Profil implements UserInterface, PasswordAuthenticatedUserInterface
public function addFollower(self $follower): static public function addFollower(self $follower): static
{ {
if (!$this->followers->contains($follower) && $follower!=$this) { if (!$this->followers->contains($follower) && $follower !== $this) {
$this->followers->add($follower); $this->followers->add($follower);
} }
@ -232,7 +232,7 @@ class Profil implements UserInterface, PasswordAuthenticatedUserInterface
public function addFollowing(self $following): static public function addFollowing(self $following): static
{ {
if (!$this->following->contains($following) && $following!=$this) { if (!$this->following->contains($following) && $following !== $this) {
$this->following->add($following); $this->following->add($following);
$following->addFollower($this); $following->addFollower($this);
} }

@ -1,6 +1,6 @@
<?php <?php
namespace App\Form\Type; namespace App\Form;
use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType; use Symfony\Component\Form\Extension\Core\Type\SubmitType;

@ -20,28 +20,12 @@ class RegistrationFormType extends AbstractType
->add('name') ->add('name')
->add('agreeTerms', CheckboxType::class, [ ->add('agreeTerms', CheckboxType::class, [
'mapped' => false, 'mapped' => false,
'constraints' => [ 'invalid_message' => 'You should agree to our terms.',
new IsTrue([
'message' => 'You should agree to our terms.',
]),
],
]) ])
->add('plainPassword', PasswordType::class, [ ->add('plainPassword', PasswordType::class, [
// instead of being set onto the object directly,
// this is read and encoded in the controller
'mapped' => false, 'mapped' => false,
'attr' => ['autocomplete' => 'new-password'], 'attr' => ['autocomplete' => 'new-password'],
'constraints' => [ 'invalid_message' => 'Please enter a password',
new NotBlank([
'message' => 'Please enter a password',
]),
new Length([
'min' => 6,
'minMessage' => 'Your password should be at least {{ limit }} characters',
// max length allowed by Symfony for security reasons
'max' => 4096,
]),
],
]) ])
; ;
} }

@ -0,0 +1,23 @@
<?php
namespace App\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
class SimpleSearchType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('search', TextType::class, [
'label' => false
])
->add('submit', SubmitType::class, [
'label' => 'Search'
])
;
}
}

@ -18,17 +18,17 @@ class PostType extends AbstractType
->add('text', TextareaType::class, [ ->add('text', TextareaType::class, [
'attr' => ['rows' => '10'], 'attr' => ['rows' => '10'],
'label' => 'Your dream' 'label' => 'Your dream'
]) ])
->add('dream', CheckboxType::class, [ ->add('dream', CheckboxType::class, [
'required' => false, 'required' => false,
'label' => 'Was it a nightmare ?' 'label' => 'Was it a nightmare ?'
]) ])
// ->add('tags', ChoiceType::class, [ // ->add('tags', ChoiceType::class, [
// "multiple" => true // "multiple" => true
// ]) // ])
->add('submit', SubmitType::class, [ ->add('submit', SubmitType::class, [
'label' => 'Publish' 'label' => 'Publish'
]) ])
; ;
} }
} }

@ -17,29 +17,29 @@ class PostRepository extends ServiceEntityRepository
parent::__construct($registry, Post::class); parent::__construct($registry, Post::class);
} }
/** /**
* @return Post[] Returns an array of Post objects * @return Post[] Returns an array of Post objects
*/ */
public function getPostFromFollowed(Profil $profil): array public function getPostFromFollowed(Profil $profil): array
{ {
return $this->createQueryBuilder('p') return $this->createQueryBuilder('p')
->innerJoin('p.profil', 'a') ->innerJoin('p.profil', 'a')
->innerJoin('a.followers', 'f') ->innerJoin('a.followers', 'f')
->where('f.id = :userId') ->where('f.id = :userId')
->setParameter('userId', $profil->getId()) ->setParameter('userId', $profil->getId())
->orderBy('p.createdAt', 'DESC') ->orderBy('p.createdAt', 'DESC')
->getQuery() ->getQuery()
->getResult(); ->getResult();
; ;
} }
// public function findOneBySomeField($value): ?Post public function searchByTitleOrText(string $searchString): array
// { {
// return $this->createQueryBuilder('p') return $this->createQueryBuilder('p')
// ->andWhere('p.exampleField = :val') ->where('p.title LIKE :searchTerm')
// ->setParameter('val', $value) ->orWhere('p.text LIKE :searchTerm')
// ->getQuery() ->setParameter('searchTerm', '%'.$searchString.'%')
// ->getOneOrNullResult() ->getQuery()
// ; ->getResult();
// } }
} }

@ -33,6 +33,7 @@
<a class="nav-link {{ nightmare|default('') }}" href="/"><h1>Fukafukashita</h1></a> <a class="nav-link {{ nightmare|default('') }}" href="/"><h1>Fukafukashita</h1></a>
</div> </div>
<div class="nav-links"> <div class="nav-links">
<a href="/post/search" class="nav-link {{ nightmare|default('') }}">Search</a>
{% if is_granted('ROLE_USER') %} {% if is_granted('ROLE_USER') %}
<a href="/profil/post/follow" class="nav-link {{ nightmare|default('') }}">Feed</a> <a href="/profil/post/follow" class="nav-link {{ nightmare|default('') }}">Feed</a>
<a href="/profil" class="nav-link {{ nightmare|default('') }}">Profile</a> <a href="/profil" class="nav-link {{ nightmare|default('') }}">Profile</a>

@ -0,0 +1,24 @@
{% extends 'base.html.twig' %}
{% block stylesheets %}
<link rel="stylesheet" href="{{ asset('css/components/form.css') }}">
<link rel="stylesheet" href="{{ asset('css/components/post_mini.css') }}">
<link rel="stylesheet" href="{{ asset('css/components/search.css') }}">
{% endblock %}
{% block body %}
<div class="wrapper">
<div class="form-container">
{{ form(form) }}
</div>
<hr>
{% if posts is defined %}
{% for post in posts %}
{% include 'post/post_mini.html.twig' with { 'post': post} %}
{% endfor %}
{% endif %}
</div>
{% endblock %}

@ -0,0 +1,44 @@
<?php
namespace App\Tests\Entity;
use App\Entity\Commentary;
use PHPUnit\Framework\TestCase;
use App\Entity\Post;
use App\Entity\Profil;
class CommentaryTest extends TestCase
{
public function testGetId()
{
$commentary = new Commentary();
$this->assertNull($commentary->getId());
}
public function testGetSetText()
{
$commentary = new Commentary();
$text = 'This is a test commentary.';
$commentary->setText($text);
$this->assertSame($text, $commentary->getText());
}
public function testGetSetPost()
{
$commentary = new Commentary();
$post = new Post();
$commentary->setPost($post);
$this->assertSame($post, $commentary->getPost());
}
public function testGetSetProfil()
{
$commentary = new Commentary();
$profil = new Profil();
$commentary->setProfil($profil);
$this->assertSame($profil, $commentary->getProfil());
}
}

@ -0,0 +1,103 @@
<?php
namespace App\Tests\Entity;
use App\Entity\Post;
use DateTimeImmutable;
use PHPUnit\Framework\TestCase;
use App\Entity\Profil;
use App\Entity\Commentary;
use App\Entity\Tags;
class PostTest extends TestCase
{
public function testGetId()
{
$post = new Post();
$this->assertNull($post->getId());
}
public function testGetSetTitle()
{
$post = new Post();
$title = 'Test Title';
$post->setTitle($title);
$this->assertSame($title, $post->getTitle());
}
public function testGetSetText()
{
$post = new Post();
$text = 'This is a test post.';
$post->setText($text);
$this->assertSame($text, $post->getText());
}
public function testGetSetIsDream()
{
$post = new Post();
$post->setDream(true);
$this->assertTrue($post->isDream());
}
public function testGetSetUpVote()
{
$post = new Post();
$post->setUpVote(10);
$this->assertSame(10, $post->getUpVote());
}
public function testGetSetDownVote()
{
$post = new Post();
$post->setDownVote(5);
$this->assertSame(5, $post->getDownVote());
}
public function testGetSetProfil()
{
$post = new Post();
$profil = new Profil();
$post->setProfil($profil);
$this->assertSame($profil, $post->getProfil());
}
public function testAddRemoveCommentary()
{
$post = new Post();
$commentary = new Commentary();
$post->addCommentary($commentary);
$this->assertTrue($post->getCommentaries()->contains($commentary));
$post->removeCommentary($commentary);
$this->assertFalse($post->getCommentaries()->contains($commentary));
}
public function testAddRemoveTag()
{
$post = new Post();
$tag = new Tags();
$post->addTag($tag);
$this->assertTrue($post->getTags()->contains($tag));
$post->removeTag($tag);
$this->assertFalse($post->getTags()->contains($tag));
}
public function testGetSetCreatedAt()
{
$post = new Post();
$createdAt = new DateTimeImmutable('now');
$post->setCreatedAt($createdAt);
$this->assertSame($createdAt, $post->getCreatedAt());
}
}

@ -0,0 +1,101 @@
<?php
namespace App\Tests\Entity;
use App\Entity\Commentary;
use App\Entity\Post;
use App\Entity\Profil;
use PHPUnit\Framework\TestCase;
class ProfilTest extends TestCase
{
public function test_it_can_be_instantiated(): void
{
$profil = new Profil();
$this->assertInstanceOf(Profil::class, $profil);
}
public function test_name()
{
$profil = new Profil();
$profil->setName('John Doe');
$this->assertEquals('John Doe', $profil->getName());
}
public function test_description()
{
$profil = new Profil();
$profil->setDescription('Lorem ipsum');
$this->assertEquals('Lorem ipsum', $profil->getDescription());
}
public function test_password()
{
$profil = new Profil();
$profil->setPassword('password123');
$this->assertEquals('password123', $profil->getPassword());
}
public function test_roles()
{
$profil = new Profil();
$roles = ['ROLE_USER', 'ROLE_ADMIN'];
$profil->setRoles($roles);
$this->assertEquals($roles, $profil->getRoles());
}
public function test_user_identifier()
{
$profil = new Profil();
$profil->setName('johndoe');
$this->assertEquals('johndoe', $profil->getUserIdentifier());
}
public function test_add_and_remove_post()
{
$profil = new Profil();
$post = new Post();
$profil->addPost($post);
$this->assertTrue($profil->getPosts()->contains($post));
$profil->removePost($post);
$this->assertFalse($profil->getPosts()->contains($post));
}
public function test_add_and_remove_commentary()
{
$profil = new Profil();
$commentary = new Commentary();
$profil->addCommentary($commentary);
$this->assertTrue($profil->getCommentaries()->contains($commentary));
$profil->removeCommentary($commentary);
$this->assertFalse($profil->getCommentaries()->contains($commentary));
}
public function test_add_and_remove_follower()
{
$profil1 = new Profil();
$profil2 = new Profil();
$profil1->addFollower($profil2);
$this->assertTrue($profil1->getFollowers()->contains($profil2));
$profil1->removeFollower($profil2);
$this->assertFalse($profil1->getFollowers()->contains($profil2));
}
public function test_add_and_remove_following()
{
$profil1 = new Profil();
$profil2 = new Profil();
$profil1->addFollowing($profil2);
$this->assertTrue($profil1->getFollowing()->contains($profil2));
$this->assertTrue($profil2->getFollowers()->contains($profil1));
$profil1->removeFollowing($profil2);
$this->assertFalse($profil1->getFollowing()->contains($profil2));
$this->assertFalse($profil2->getFollowers()->contains($profil1));
}
}

@ -0,0 +1,46 @@
<?php
namespace App\Tests\Entity;
use App\Entity\Tags;
use PHPUnit\Framework\TestCase;
use App\Entity\Post;
class TagsTest extends TestCase
{
public function testGetId()
{
$tags = new Tags();
$this->assertNull($tags->getId());
}
public function testGetSetName()
{
$tags = new Tags();
$name = 'Test Tag';
$tags->setName($name);
$this->assertSame($name, $tags->getName());
}
public function testGetSetColor()
{
$tags = new Tags();
$color = '#FF0000';
$tags->setColor($color);
$this->assertSame($color, $tags->getColor());
}
public function testAddRemovePost()
{
$tags = new Tags();
$post = new Post();
$tags->addPost($post);
$this->assertTrue($tags->getPosts()->contains($post));
$tags->removePost($post);
$this->assertFalse($tags->getPosts()->contains($post));
}
}

@ -0,0 +1,40 @@
<?php
namespace App\Tests\Form;
use App\Form\ProfilType;
use App\Entity\Profil;
use Symfony\Component\Form\Test\TypeTestCase;
class ProfilTypeTest extends TypeTestCase
{
public function testSubmitValidData()
{
$formData = [
'name' => 'Test Name',
'description' => 'Lorem ipsum dolor sit amet.',
];
$objectToCompare = new Profil();
$form = $this->factory->create(ProfilType::class, $objectToCompare);
$object = new Profil();
$object->setName('Test Name');
$object->setDescription('Lorem ipsum dolor sit amet.');
$form->submit($formData);
$this->assertTrue($form->isSynchronized());
$this->assertEquals($object, $objectToCompare);
}
public function testFormFields()
{
$form = $this->factory->create(ProfilType::class);
$this->assertTrue($form->has('name'));
$this->assertTrue($form->has('description'));
}
}

@ -0,0 +1,46 @@
<?php
namespace App\Tests\Form;
use App\Form\RegistrationFormType;
use App\Entity\Profil;
use Symfony\Component\Form\Test\TypeTestCase;
use Symfony\Component\Validator\Validation;
class RegistrationFormTypeTest extends TypeTestCase
{
public function testSubmitValidData()
{
$formData = [
'name' => 'Test Name',
'agreeTerms' => true,
'plainPassword' => 'testpassword',
];
$form = $this->factory->create(RegistrationFormType::class);
$objectToCompare = new Profil();
$form->submit($formData);
$this->assertTrue($form->isSynchronized());
$this->assertEquals($formData['name'], $form->get('name')->getData());
$this->assertEquals($formData['agreeTerms'], $form->get('agreeTerms')->getData());
$this->assertEquals($formData['plainPassword'], $form->get('plainPassword')->getData());
$validator = Validation::createValidator();
$violations = $validator->validate($objectToCompare);
$this->assertCount(0, $violations);
}
public function testFormFields()
{
$form = $this->factory->create(RegistrationFormType::class);
$this->assertTrue($form->has('name'));
$this->assertTrue($form->has('agreeTerms'));
$this->assertTrue($form->has('plainPassword'));
}
}

@ -0,0 +1,37 @@
<?php
namespace App\Tests\Form\Type;
use App\Form\Type\PostType;
use Symfony\Component\Form\Test\TypeTestCase;
class PostTypeTest extends TypeTestCase
{
public function testSubmitValidData()
{
$formData = [
'title' => 'Test Title',
'text' => 'Lorem ipsum dolor sit amet.',
'dream' => true,
];
$form = $this->factory->create(PostType::class);
$form->submit($formData);
$this->assertTrue($form->isSynchronized());
$this->assertEquals($formData['title'], $form->get('title')->getData());
$this->assertEquals($formData['text'], $form->get('text')->getData());
$this->assertEquals($formData['dream'], $form->get('dream')->getData());
}
public function testFormFields()
{
$form = $this->factory->create(PostType::class);
$this->assertTrue($form->has('title'));
$this->assertTrue($form->has('text'));
$this->assertTrue($form->has('dream'));
$this->assertTrue($form->has('submit'));
}
}

Binary file not shown.
Loading…
Cancel
Save