Compare commits

..

No commits in common. "main" and "0.0.5" have entirely different histories.
main ... 0.0.5

48 changed files with 601 additions and 2748 deletions

View file

@ -9,7 +9,7 @@ jobs:
env: env:
REPO: '${{ github.repository }}' REPO: '${{ github.repository }}'
TOKEN: '${{ secrets.GITHUB_TOKEN }}' TOKEN: '${{ secrets.GITHUB_TOKEN }}'
GIT_SERVER: 'git.hannover.ccc.de' GIT_SERVER: 'hannover.ccc.de/gitlab'
run: | run: |
git clone --branch $GITHUB_HEAD_REF https://${TOKEN}@${GIT_SERVER}/${REPO}.git . git clone --branch $GITHUB_HEAD_REF https://${TOKEN}@${GIT_SERVER}/${REPO}.git .
git fetch git fetch
@ -23,19 +23,7 @@ jobs:
- name: lint - name: lint
run: composer lint run: composer lint
- name: test - name: test
run: composer mutation run: composer test
- name: Add comment to pull request
run: |
echo '```' >> /tmp/pull-request-comment
cat var/log/infection.txt >> /tmp/pull-request-comment
cat var/log/summary.log >> /tmp/pull-request-comment
echo '```' >> /tmp/pull-request-comment
jq -n --arg msg "$(cat /tmp/pull-request-comment)" '{"body": $msg}' > /tmp/git-msg
curl -X POST \
-H "Authorization: token ${GITHUB_TOKEN}" \
-H "Content-Type: application/json" \
-d @/tmp/git-msg \
"${{ env.GITHUB_SERVER_URL }}/api/v1/repos/${{ github.repository }}/issues/${{ github.event.pull_request.number }}/comments"
- name: GIT commit and push all changed files - name: GIT commit and push all changed files
env: env:
CI_COMMIT_MESSAGE: Continuous Integration Fixes CI_COMMIT_MESSAGE: Continuous Integration Fixes

View file

@ -13,7 +13,7 @@ jobs:
REPO: '${{ github.repository }}' REPO: '${{ github.repository }}'
TOKEN: '${{ secrets.GITHUB_TOKEN }}' TOKEN: '${{ secrets.GITHUB_TOKEN }}'
BRANCH: '${{ env.GITHUB_REF_NAME }}' BRANCH: '${{ env.GITHUB_REF_NAME }}'
GIT_SERVER: 'git.hannover.ccc.de' GIT_SERVER: 'hannover.ccc.de/gitlab'
run: | run: |
git clone --branch $GITHUB_REF_NAME https://${TOKEN}@${GIT_SERVER}/${REPO}.git . git clone --branch $GITHUB_REF_NAME https://${TOKEN}@${GIT_SERVER}/${REPO}.git .
git fetch git fetch
@ -27,7 +27,7 @@ jobs:
- name: lint - name: lint
run: composer lint run: composer lint
- name: test - name: test
run: composer mutation run: composer test
- name: GIT commit and push all changed files - name: GIT commit and push all changed files
env: env:
CI_COMMIT_MESSAGE: Continuous Integration Fixes CI_COMMIT_MESSAGE: Continuous Integration Fixes

View file

@ -7,14 +7,13 @@
"php": ">=8.3", "php": ">=8.3",
"ext-ctype": "*", "ext-ctype": "*",
"ext-iconv": "*", "ext-iconv": "*",
"doctrine/dbal": "^4.1", "doctrine/dbal": "^4",
"doctrine/doctrine-bundle": "^2.12", "doctrine/doctrine-bundle": "^2.12",
"doctrine/doctrine-migrations-bundle": "^3.3.1", "doctrine/doctrine-migrations-bundle": "^3.3",
"doctrine/orm": "^3.2.1", "doctrine/orm": "^3.2",
"psr/clock": "^1.0",
"symfony/console": "7.1.*", "symfony/console": "7.1.*",
"symfony/dotenv": "7.1.*", "symfony/dotenv": "7.1.*",
"symfony/flex": "^2.4.6", "symfony/flex": "^2",
"symfony/form": "7.1.*", "symfony/form": "7.1.*",
"symfony/framework-bundle": "7.1.*", "symfony/framework-bundle": "7.1.*",
"symfony/runtime": "7.1.*", "symfony/runtime": "7.1.*",
@ -25,22 +24,20 @@
"symfony/yaml": "7.1.*" "symfony/yaml": "7.1.*"
}, },
"require-dev": { "require-dev": {
"infection/infection": "^0.29.6", "lubiana/code-quality": "^1.7",
"lubiana/code-quality": "^1.7.2", "phpunit/phpunit": "^9.5",
"phpunit/phpunit": "^9.6.20",
"symfony/browser-kit": "7.1.*", "symfony/browser-kit": "7.1.*",
"symfony/css-selector": "7.1.*", "symfony/css-selector": "7.1.*",
"symfony/maker-bundle": "^1.60", "symfony/maker-bundle": "^1.60",
"symfony/phpunit-bridge": "^7.1.3", "symfony/phpunit-bridge": "^7.1",
"symplify/config-transformer": "^12.3.4" "symplify/config-transformer": "^12.3"
}, },
"config": { "config": {
"allow-plugins": { "allow-plugins": {
"php-http/discovery": true, "php-http/discovery": true,
"symfony/flex": true, "symfony/flex": true,
"symfony/runtime": true, "symfony/runtime": true,
"dealerdirect/phpcodesniffer-composer-installer": true, "dealerdirect/phpcodesniffer-composer-installer": true
"infection/extension-installer": true
}, },
"sort-packages": true, "sort-packages": true,
"platform": { "platform": {
@ -83,8 +80,7 @@
"rector", "rector",
"ecs --fix || ecs --fix" "ecs --fix || ecs --fix"
], ],
"test": "bin/phpunit", "test": "bin/phpunit"
"mutation": "infection --threads=8 --show-mutations"
}, },
"conflict": { "conflict": {
"symfony/symfony": "*" "symfony/symfony": "*"

1749
composer.lock generated

File diff suppressed because it is too large Load diff

View file

@ -5,9 +5,6 @@ use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigura
return static function (ContainerConfigurator $containerConfigurator): void { return static function (ContainerConfigurator $containerConfigurator): void {
$containerConfigurator->extension('twig', [ $containerConfigurator->extension('twig', [
'file_name_pattern' => '*.twig', 'file_name_pattern' => '*.twig',
'globals' => [
'favicon' => '@App\Service\Favicon',
],
]); ]);
if ($containerConfigurator->env() === 'test') { if ($containerConfigurator->env() === 'test') {
$containerConfigurator->extension('twig', [ $containerConfigurator->extension('twig', [

View file

@ -1,20 +0,0 @@
{
"$schema": "vendor/infection/infection/resources/schema.json",
"source": {
"directories": [
"src"
]
},
"timeout": 30,
"logs": {
"text": "var/log/infection.txt",
"summary": "var/log/summary.log",
},
"mutators": {
"@default": true,
"global-ignore": [
"App\\Service\\Favicon::__toString",
"ORM\\Column.*"
]
}
}

View file

@ -1,36 +0,0 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20240815151510 extends AbstractMigration
{
public function getDescription(): string
{
return '';
}
public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE menu_item ADD COLUMN deleted_at DATETIME DEFAULT NULL');
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('CREATE TEMPORARY TABLE __temp__menu_item AS SELECT id, name, food_vendor_id FROM menu_item');
$this->addSql('DROP TABLE menu_item');
$this->addSql('CREATE TABLE menu_item (id BLOB NOT NULL, name VARCHAR(255) NOT NULL, food_vendor_id BLOB NOT NULL, PRIMARY KEY(id), CONSTRAINT FK_D754D5506EF983E8 FOREIGN KEY (food_vendor_id) REFERENCES food_vendor (id) NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('INSERT INTO menu_item (id, name, food_vendor_id) SELECT id, name, food_vendor_id FROM __temp__menu_item');
$this->addSql('DROP TABLE __temp__menu_item');
$this->addSql('CREATE INDEX IDX_D754D5506EF983E8 ON menu_item (food_vendor_id)');
}
}

View file

@ -1,35 +0,0 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20240816193410 extends AbstractMigration
{
public function getDescription(): string
{
return '';
}
public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE food_vendor ADD COLUMN menu_link VARCHAR(255) DEFAULT NULL');
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('CREATE TEMPORARY TABLE __temp__food_vendor AS SELECT name, id FROM food_vendor');
$this->addSql('DROP TABLE food_vendor');
$this->addSql('CREATE TABLE food_vendor (name VARCHAR(50) NOT NULL, id BLOB NOT NULL, PRIMARY KEY(id))');
$this->addSql('INSERT INTO food_vendor (name, id) SELECT name, id FROM __temp__food_vendor');
$this->addSql('DROP TABLE __temp__food_vendor');
}
}

View file

@ -6,9 +6,7 @@
backupGlobals="false" backupGlobals="false"
colors="true" colors="true"
bootstrap="tests/bootstrap.php" bootstrap="tests/bootstrap.php"
convertDeprecationsToExceptions="true" convertDeprecationsToExceptions="false"
executionOrder="random"
resolveDependencies="true"
> >
<php> <php>
<ini name="display_errors" value="1" /> <ini name="display_errors" value="1" />

View file

@ -1 +0,0 @@
blockquote,header{background:var(--nc-bg-2)}dt,summary,table caption{font-weight:700}img,pre,textarea{max-width:100%}:root{--nc-font-sans:'Inter',-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen,Ubuntu,Cantarell,'Open Sans','Helvetica Neue',sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";--nc-font-mono:Consolas,monaco,'Ubuntu Mono','Liberation Mono','Courier New',Courier,monospace;--nc-tx-1:#000000;--nc-tx-2:#1A1A1A;--nc-bg-1:#FFFFFF;--nc-bg-2:#F6F8FA;--nc-bg-3:#E5E7EB;--nc-lk-1:#0070F3;--nc-lk-2:#0366D6;--nc-lk-tx:#FFFFFF;--nc-ac-1:#79FFE1;--nc-ac-tx:#0C4047;--nc-d-tx-1:#ffffff;--nc-d-tx-2:#eeeeee;--nc-d-bg-1:#000000;--nc-d-bg-2:#111111;--nc-d-bg-3:#222222;--nc-d-lk-1:#3291FF;--nc-d-lk-2:#0070F3;--nc-d-lk-tx:#FFFFFF;--nc-d-ac-1:#7928CA;--nc-d-ac-tx:#FFFFFF}@media (prefers-color-scheme:dark){:root{--nc-tx-1:var(--nc-d-tx-1);--nc-tx-2:var(--nc-d-tx-2);--nc-bg-1:var(--nc-d-bg-1);--nc-bg-2:var(--nc-d-bg-2);--nc-bg-3:var(--nc-d-bg-3);--nc-lk-1:var(--nc-d-lk-1);--nc-lk-2:var(--nc-d-lk-2);--nc-lk-tx:var(--nc--dlk-tx);--nc-ac-1:var(--nc-d-ac-1);--nc-ac-tx:var(--nc--dac-tx)}}*{margin:0;padding:0}address,area,article,aside,audio,blockquote,datalist,details,dl,fieldset,figure,form,iframe,img,input,meter,nav,ol,optgroup,option,output,p,pre,progress,ruby,section,table,textarea,ul,video{margin-bottom:1rem}button,html,input,select{font-family:var(--nc-font-sans)}body{margin:0 auto;max-width:750px;padding:2rem;border-radius:6px;overflow-x:hidden;word-break:break-word;overflow-wrap:break-word;background:var(--nc-bg-1);color:var(--nc-tx-2);font-size:1.03rem;line-height:1.5}::selection{background:var(--nc-ac-1);color:var(--nc-ac-tx)}h1,h2,h3,h4,h5,h6{line-height:1;color:var(--nc-tx-1);padding-top:.875rem}h1,h2,h3{color:var(--nc-tx-1);padding-bottom:2px;margin-bottom:8px;border-bottom:1px solid var(--nc-bg-2)}h4,h5,h6{margin-bottom:.3rem}h1{font-size:2.25rem}h2{font-size:1.85rem}h3{font-size:1.55rem}h4{font-size:1.25rem}h5{font-size:1rem}h6{font-size:.875rem}a{color:var(--nc-lk-1)}a:hover{color:var(--nc-lk-2)}abbr,abbr:hover{cursor:help}blockquote{padding:1.5rem;border-left:5px solid var(--nc-bg-3)}blockquote :last-child{padding-bottom:0;margin-bottom:0}header{border-bottom:1px solid var(--nc-bg-3);padding:2rem 1.5rem;margin:-2rem calc(50% - 50vw) 2rem;padding-left:calc(50vw - 50%);padding-right:calc(50vw - 50%)}header h1,header h2,header h3{padding-bottom:0;border-bottom:0}header>:first-child{margin-top:0;padding-top:0}a img,details[open]>:last-child,header>:last-child,ol ol,ol ul,ul ol,ul ul{margin-bottom:0}a button,button,input[type=button],input[type=reset],input[type=submit]{font-size:1rem;display:inline-block;padding:6px 12px;text-align:center;text-decoration:none;white-space:nowrap;background:var(--nc-lk-1);color:var(--nc-lk-tx);border:0;border-radius:4px;box-sizing:border-box;cursor:pointer;color:var(--nc-lk-tx)}a button[disabled],button[disabled],input[type=button][disabled],input[type=reset][disabled],input[type=submit][disabled]{opacity:.5;cursor:not-allowed}.button:enabled:hover,.button:focus,button:enabled:hover,button:focus,input[type=button]:enabled:hover,input[type=button]:focus,input[type=reset]:enabled:hover,input[type=reset]:focus,input[type=submit]:enabled:hover,input[type=submit]:focus{background:var(--nc-lk-2)}code,details,input,kbd,pre,samp,select,textarea,th,tr:nth-child(2n){background:var(--nc-bg-2)}code,kbd,pre,samp{font-family:var(--nc-font-mono);border:1px solid var(--nc-bg-3);border-radius:4px;padding:3px 6px;font-size:.9em}code pre,pre code{background:inherit;font-size:inherit;color:inherit;border:0;padding:0;margin:0}details,fieldset{border:1px solid var(--nc-bg-3)}kbd{border-bottom:3px solid var(--nc-bg-3)}pre{padding:1rem 1.4rem;overflow:auto}code pre{display:inline}details{padding:.6rem 1rem;border-radius:4px}summary{cursor:pointer}details[open]{padding-bottom:.75rem}details[open] summary{margin-bottom:6px}dd::before{content:'→ '}hr{border:0;border-bottom:1px solid var(--nc-bg-3);margin:1rem auto}fieldset{margin-top:1rem;padding:2rem;border-radius:4px}input,select,td,textarea,th{border:1px solid var(--nc-bg-3)}legend{padding:auto .5rem}table{border-collapse:collapse;width:100%}td,th{text-align:left;padding:.5rem}table caption{margin-bottom:.5rem}ol,ul{padding-left:2rem}li{margin-top:.4rem}mark{padding:3px 6px;background:var(--nc-ac-1);color:var(--nc-ac-tx)}input,select,textarea{padding:6px 12px;margin-bottom:.5rem;color:var(--nc-tx-2);border-radius:4px;box-shadow:none;box-sizing:border-box}

View file

@ -1,13 +0,0 @@
<svg
enable-background="new 0 0 512 512"
viewBox="0 0 512 512"
xmlns="http://www.w3.org/2000/svg"
>
<g
transform="rotate(30, 256, 256)"
>
<path d="m51.6 72.608 199.566-34.825 213.114 34.825c19.867 0 32.553 21.19 23.171 38.701l-206.224 384.92c-9.908 18.492-36.421 18.499-46.337.011l-206.455-384.92c-9.393-17.512 3.293-38.712 23.165-38.712z" fill="#fecb21"/>
<path d="m270.254 307.902c0 28.665-23.238 51.902-51.902 51.902s-51.902-23.237-51.902-51.902 23.237-51.902 51.902-51.902 51.902 23.237 51.902 51.902zm65.862 81.954c-25.323-13.433-56.74-3.794-70.173 21.528-13.433 25.323-3.794 56.74 21.528 70.173m48.645-342.08c-28.665 0-51.902 23.237-51.902 51.902s23.237 51.902 51.902 51.902 51.902-23.237 51.902-51.902-23.237-51.902-51.902-51.902zm-182.102-33.763c-28.665 0-51.902 23.237-51.902 51.902s23.237 51.902 51.902 51.902 51.902-23.237 51.902-51.902-23.237-51.902-51.902-51.902z" fill="#f43b22"/>
<path d="m41.204 130.519c-13.328.001-26.256-7.039-33.184-19.518-10.167-18.308-3.566-41.391 14.743-51.557 34.605-19.215 72.08-34.048 111.383-44.088 39.334-10.047 80.138-15.214 121.279-15.356 40.982-.14 81.845 4.711 121.369 14.423 40.306 9.904 78.8 24.757 114.412 44.147 18.393 10.014 25.184 33.041 15.17 51.433-10.014 18.391-33.043 25.185-51.433 15.169-29.887-16.273-62.269-28.757-96.246-37.105-33.046-8.12-67.199-12.236-101.53-12.236-.496 0-.986.001-1.481.002-34.903.121-69.481 4.494-102.772 12.998-33.009 8.432-64.412 20.851-93.338 36.912-5.829 3.239-12.145 4.776-18.372 4.776z" fill="#c4790c"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

View file

@ -0,0 +1,35 @@
<?php declare(strict_types=1);
namespace App\Command;
use App\Service\FakeData;
use Override;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
#[AsCommand(
name: 'app:fake-data',
description: 'add some fake data to database',
)]
final class FakeDataCommand extends Command
{
public function __construct(
private readonly FakeData $fakeData,
) {
parent::__construct();
}
#[Override]
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$this->fakeData->resetDb();
$this->fakeData->generate();
$io->success('Added some fake data to database');
return Command::SUCCESS;
}
}

View file

@ -0,0 +1,66 @@
<?php declare(strict_types=1);
namespace App\Command;
use App\Entity\MenuItem;
use App\Repository\MenuItemRepository;
use App\Repository\OrderItemRepository;
use Doctrine\ORM\EntityManagerInterface;
use Override;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use function sprintf;
#[AsCommand(
name: 'app:migrate-orderitems-menuitems',
description: 'Migrate orderitems to menu items',
)]
final class MigrateOrderitemsMenuitemsCommand extends Command
{
public function __construct(
private readonly EntityManagerInterface $entityManager,
private readonly OrderItemRepository $orderItemRepository,
private readonly MenuItemRepository $menuItemRepository,
) {
parent::__construct();
}
#[Override]
protected function configure(): void {}
#[Override]
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$orderItems = $this->orderItemRepository->findAll();
foreach ($orderItems as $orderItem) {
$menuItem = $this->menuItemRepository->findOneBy([
'name' => $orderItem->getName(),
'foodVendor' => $orderItem->getFoodOrder()
->getFoodVendor(),
]);
if ($menuItem === null) {
$menuItem = new MenuItem;
$menuItem->setName($orderItem->getName());
$menuItem->setFoodVendor($orderItem->getFoodOrder()->getFoodVendor());
$this->entityManager->persist($menuItem);
$this->entityManager->flush();
$output->writeln(sprintf('Menu item %s added', $menuItem->getName()));
}
$orderItem->setMenuItem($menuItem);
$this->entityManager->persist($orderItem);
}
$this->entityManager->flush();
$io->success('You have a new command! Now make it your own! Pass --help to see your options.');
return Command::SUCCESS;
}
}

View file

@ -14,49 +14,11 @@ use Symfony\Component\Routing\Attribute\Route;
#[Route('/food/order')] #[Route('/food/order')]
final class FoodOrderController extends AbstractController final class FoodOrderController extends AbstractController
{ {
#[Route( #[Route('/', name: 'app_food_order_index', methods: ['GET'])]
path: '/list',
name: 'app_food_order_index',
methods: ['GET']
)]
public function index(FoodOrderRepository $foodOrderRepository): Response public function index(FoodOrderRepository $foodOrderRepository): Response
{ {
return $this->render('food_order/index.html.twig', [ return $this->render('food_order/index.html.twig', [
'food_orders' => $foodOrderRepository->findLatestEntries(days: 3), 'food_orders' => $foodOrderRepository->findLatestEntries(),
'current_page' => 0,
'next_page' => 0,
'prev_page' => 0,
]);
}
#[Route(
path: '/list/archive/{page}',
name: 'app_food_order_archive',
requirements: [
'page' => '\d+',
],
methods: ['GET']
)]
public function archive(FoodOrderRepository $foodOrderRepository, int $page = 1): Response
{
$nextPage = $page + 1;
$prevPage = $page - 1;
$itemsPerPage = 10;
$count = $foodOrderRepository->count();
if($count < $page * $itemsPerPage) {
$nextPage = $page;
}
return $this->render('food_order/index.html.twig', [
'food_orders' => $foodOrderRepository->findLatestEntries(
page: $page,
pagesize: $itemsPerPage,
days: 0
),
'current_page' => $page,
'next_page' => $nextPage,
'prev_page' => $prevPage,
]); ]);
} }
@ -95,8 +57,7 @@ final class FoodOrderController extends AbstractController
#[Route('/{id}/close', name: 'app_food_order_close', methods: ['GET'])] #[Route('/{id}/close', name: 'app_food_order_close', methods: ['GET'])]
public function close(FoodOrder $foodOrder, FoodOrderRepository $repository): Response public function close(FoodOrder $foodOrder, FoodOrderRepository $repository): Response
{ {
$foodOrder->close(); $repository->save($foodOrder->close());
$repository->save();
return $this->redirectToRoute('app_food_order_show', [ return $this->redirectToRoute('app_food_order_show', [
'id' => $foodOrder->getId(), 'id' => $foodOrder->getId(),
], Response::HTTP_SEE_OTHER); ], Response::HTTP_SEE_OTHER);
@ -105,8 +66,7 @@ final class FoodOrderController extends AbstractController
#[Route('/{id}/open', name: 'app_food_order_open', methods: ['GET'])] #[Route('/{id}/open', name: 'app_food_order_open', methods: ['GET'])]
public function open(FoodOrder $foodOrder, FoodOrderRepository $repository): Response public function open(FoodOrder $foodOrder, FoodOrderRepository $repository): Response
{ {
$foodOrder->open(); $repository->save($foodOrder->open());
$repository->save();
return $this->redirectToRoute('app_food_order_show', [ return $this->redirectToRoute('app_food_order_show', [
'id' => $foodOrder->getId(), 'id' => $foodOrder->getId(),
], Response::HTTP_SEE_OTHER); ], Response::HTTP_SEE_OTHER);

View file

@ -1,57 +0,0 @@
<?php declare(strict_types=1);
namespace App\Controller;
use App\Entity\MenuItem;
use App\Form\MenuItemType;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
#[Route('/menu/item')]
final class MenuItemController extends AbstractController
{
#[Route('/{id}', name: 'app_menu_item_show', methods: ['GET'])]
public function show(MenuItem $menuItem): Response
{
return $this->render('menu_item/show.html.twig', [
'menu_item' => $menuItem,
]);
}
#[Route('/{id}/edit', name: 'app_menu_item_edit', methods: ['GET', 'POST'])]
public function edit(Request $request, MenuItem $menuItem, EntityManagerInterface $entityManager): Response
{
$form = $this->createForm(MenuItemType::class, $menuItem);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$entityManager->flush();
return $this->redirectToRoute('app_menu_item_show', [
'id' => $menuItem->getId(),
], Response::HTTP_SEE_OTHER);
}
return $this->render('menu_item/edit.html.twig', [
'menu_item' => $menuItem,
'form' => $form,
]);
}
#[Route('/{id}', name: 'app_menu_item_delete', methods: ['POST'])]
public function delete(Request $request, MenuItem $menuItem, EntityManagerInterface $entityManager): Response
{
if ($this->isCsrfTokenValid('delete' . $menuItem->getId(), $request->getPayload()->getString('_token'))) {
$menuItem->delete();
$entityManager->flush();
}
return $this->redirectToRoute('app_food_vendor_show', [
'id' => $menuItem->getFoodVendor()
->getId(),
], Response::HTTP_SEE_OTHER);
}
}

View file

@ -56,7 +56,6 @@ final class OrderItemController extends AbstractController
} }
$menuItems = $menuItemRepository->findBy([ $menuItems = $menuItemRepository->findBy([
'foodVendor' => $foodOrder->getFoodVendor(), 'foodVendor' => $foodOrder->getFoodVendor(),
'deletedAt' => null,
]); ]);
return $this->render('order_item/new.html.twig', [ return $this->render('order_item/new.html.twig', [
@ -91,39 +90,18 @@ final class OrderItemController extends AbstractController
} }
#[Route('/{id}/edit', name: 'app_order_item_edit', methods: ['GET', 'POST'])] #[Route('/{id}/edit', name: 'app_order_item_edit', methods: ['GET', 'POST'])]
public function edit( public function edit(Request $request, OrderItem $orderItem, EntityManagerInterface $entityManager): Response
Request $request, {
OrderItem $orderItem,
EntityManagerInterface $entityManager,
MenuItemRepository $menuItemRepository,
): Response {
$foodOrder = $orderItem->getFoodOrder(); $foodOrder = $orderItem->getFoodOrder();
if ($foodOrder->isClosed()) { if ($foodOrder->isClosed()) {
return $this->redirectToRoute('app_food_order_show', [ return $this->redirectToRoute('app_food_order_show', [
'id' => $foodOrder->getId(), 'id' => $foodOrder->getId(),
], Response::HTTP_SEE_OTHER); ], Response::HTTP_SEE_OTHER);
} }
$orderItem->setName($orderItem->getMenuItem()->getName());
$form = $this->createForm(OrderItemType::class, $orderItem); $form = $this->createForm(OrderItemType::class, $orderItem);
$form->setData($orderItem);
$form->handleRequest($request); $form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) { if ($form->isSubmitted() && $form->isValid()) {
$menuItem = $menuItemRepository->findOneBy([
'name' => $orderItem->getName(),
'foodVendor' => $foodOrder->getFoodVendor(),
]);
if ($menuItem === null) {
$menuItem = new MenuItem;
$menuItem->setName($orderItem->getName());
$menuItem->setFoodVendor($foodOrder->getFoodVendor());
$entityManager->persist($menuItem);
}
$orderItem->setMenuItem($menuItem);
$orderItem->setFoodOrder($foodOrder);
$entityManager->persist($orderItem);
$entityManager->flush(); $entityManager->flush();
return $this->redirectToRoute('app_food_order_show', [ return $this->redirectToRoute('app_food_order_show', [

View file

@ -8,14 +8,19 @@ use DateTimeImmutable;
use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection; use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\IdGenerator\UlidGenerator;
use Symfony\Bridge\Doctrine\Types\UlidType; use Symfony\Bridge\Doctrine\Types\UlidType;
use Symfony\Component\Uid\Ulid; use Symfony\Component\Uid\Ulid;
use function iterator_to_array;
#[ORM\Entity(repositoryClass: FoodOrderRepository::class)] #[ORM\Entity(repositoryClass: FoodOrderRepository::class)]
class FoodOrder class FoodOrder
{ {
#[ORM\Id]
#[ORM\GeneratedValue(strategy: 'CUSTOM')]
#[ORM\Column(type: UlidType::NAME, unique: true)]
#[ORM\CustomIdGenerator(class: UlidGenerator::class)]
private Ulid|null $id = null;
#[ORM\Column(nullable: true)] #[ORM\Column(nullable: true)]
private DateTimeImmutable|null $closedAt = null; private DateTimeImmutable|null $closedAt = null;
@ -34,11 +39,8 @@ class FoodOrder
])] ])]
private string|null $createdBy = 'nobody'; private string|null $createdBy = 'nobody';
public function __construct( public function __construct()
#[ORM\Id] {
#[ORM\Column(type: UlidType::NAME, unique: true)]
private Ulid|null $id = new Ulid
) {
$this->orderItems = new ArrayCollection; $this->orderItems = new ArrayCollection;
$this->open(); $this->open();
} }
@ -58,7 +60,7 @@ class FoodOrder
return $this->closedAt; return $this->closedAt;
} }
public function setClosedAt(DateTimeImmutable|null $closedAt = null): static public function setClosedAt(DateTimeImmutable|null $closedAt): static
{ {
$this->closedAt = $closedAt; $this->closedAt = $closedAt;
@ -67,10 +69,7 @@ class FoodOrder
public function isClosed(): bool public function isClosed(): bool
{ {
if (! $this->closedAt instanceof DateTimeImmutable) { return $this->closedAt instanceof DateTimeImmutable && $this->closedAt->getTimestamp() <= (new DateTimeImmutable)->getTimestamp();
return false;
}
return $this->closedAt < new DateTimeImmutable;
} }
public function close(): static public function close(): static
@ -104,23 +103,6 @@ class FoodOrder
return $this->orderItems; return $this->orderItems;
} }
/**
* @return Collection<int, OrderItem>
*/
public function getOrderItemsSortedByName(): Collection
{
$iterator = $this->getOrderItems()
->getIterator();
$iterator->uasort(
static fn(OrderItem $a, OrderItem $b): int => $a->getName() <=> $b->getName()
);
return new ArrayCollection(
iterator_to_array(
$iterator
)
);
}
public function addOrderItem(OrderItem $orderItem): static public function addOrderItem(OrderItem $orderItem): static
{ {
if (! $this->orderItems->contains($orderItem)) { if (! $this->orderItems->contains($orderItem)) {
@ -134,7 +116,7 @@ class FoodOrder
public function removeOrderItem(OrderItem $orderItem): static public function removeOrderItem(OrderItem $orderItem): static
{ {
// set the owning side to null (unless already changed) // set the owning side to null (unless already changed)
if ($this->orderItems->removeElement($orderItem)) { if ($this->orderItems->removeElement($orderItem) && $orderItem->getFoodOrder() === $this) {
$orderItem->setFoodOrder(null); $orderItem->setFoodOrder(null);
} }

View file

@ -13,6 +13,12 @@ use Symfony\Component\Uid\Ulid;
#[ORM\Entity(repositoryClass: FoodVendorRepository::class)] #[ORM\Entity(repositoryClass: FoodVendorRepository::class)]
class FoodVendor class FoodVendor
{ {
#[ORM\Id]
#[ORM\GeneratedValue(strategy: 'CUSTOM')]
#[ORM\Column(type: UlidType::NAME, unique: true)]
#[ORM\CustomIdGenerator(class: UlidGenerator::class)]
private Ulid|null $id = null;
#[ORM\Column(length: 50)] #[ORM\Column(length: 50)]
private string|null $name = null; private string|null $name = null;
@ -28,16 +34,8 @@ class FoodVendor
#[ORM\OneToMany(targetEntity: MenuItem::class, mappedBy: 'foodVendor', orphanRemoval: true)] #[ORM\OneToMany(targetEntity: MenuItem::class, mappedBy: 'foodVendor', orphanRemoval: true)]
private Collection $menuItems; private Collection $menuItems;
#[ORM\Column(length: 255, nullable: true)] public function __construct()
private string|null $menuLink = null; {
public function __construct(
#[ORM\Id]
#[ORM\GeneratedValue(strategy: 'CUSTOM')]
#[ORM\Column(type: UlidType::NAME, unique: true)]
#[ORM\CustomIdGenerator(class: UlidGenerator::class)]
private Ulid|null $id = new Ulid
) {
$this->foodOrders = new ArrayCollection; $this->foodOrders = new ArrayCollection;
$this->menuItems = new ArrayCollection; $this->menuItems = new ArrayCollection;
} }
@ -80,7 +78,7 @@ class FoodVendor
public function removeFoodOrder(FoodOrder $foodOrder): static public function removeFoodOrder(FoodOrder $foodOrder): static
{ {
// set the owning side to null (unless already changed) // set the owning side to null (unless already changed)
if ($this->foodOrders->removeElement($foodOrder)) { if ($this->foodOrders->removeElement($foodOrder) && $foodOrder->getFoodVendor() === $this) {
$foodOrder->setFoodVendor(null); $foodOrder->setFoodVendor(null);
} }
@ -90,15 +88,10 @@ class FoodVendor
/** /**
* @return Collection<int, MenuItem> * @return Collection<int, MenuItem>
*/ */
public function getMenuItems(bool $withDeleted = false): Collection public function getMenuItems(): Collection
{ {
if ($withDeleted) {
return $this->menuItems; return $this->menuItems;
} }
return $this->menuItems->filter(
static fn(MenuItem $item): bool => $item->isDeleted() === false
);
}
public function addMenuItem(MenuItem $menuItem): static public function addMenuItem(MenuItem $menuItem): static
{ {
@ -113,22 +106,10 @@ class FoodVendor
public function removeMenuItem(MenuItem $menuItem): static public function removeMenuItem(MenuItem $menuItem): static
{ {
// set the owning side to null (unless already changed) // set the owning side to null (unless already changed)
if ($this->menuItems->removeElement($menuItem)) { if ($this->menuItems->removeElement($menuItem) && $menuItem->getFoodVendor() === $this) {
$menuItem->setFoodVendor(null); $menuItem->setFoodVendor(null);
} }
return $this; return $this;
} }
public function getMenuLink(): string|null
{
return $this->menuLink;
}
public function setMenuLink(string|null $menuLink): static
{
$this->menuLink = $menuLink;
return $this;
}
} }

View file

@ -3,7 +3,6 @@
namespace App\Entity; namespace App\Entity;
use App\Repository\MenuItemRepository; use App\Repository\MenuItemRepository;
use DateTimeImmutable;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\IdGenerator\UlidGenerator; use Symfony\Bridge\Doctrine\IdGenerator\UlidGenerator;
use Symfony\Bridge\Doctrine\Types\UlidType; use Symfony\Bridge\Doctrine\Types\UlidType;
@ -12,6 +11,12 @@ use Symfony\Component\Uid\Ulid;
#[ORM\Entity(repositoryClass: MenuItemRepository::class)] #[ORM\Entity(repositoryClass: MenuItemRepository::class)]
class MenuItem class MenuItem
{ {
#[ORM\Id]
#[ORM\GeneratedValue(strategy: 'CUSTOM')]
#[ORM\Column(type: UlidType::NAME, unique: true)]
#[ORM\CustomIdGenerator(class: UlidGenerator::class)]
private Ulid|null $id = null;
#[ORM\Column(length: 255)] #[ORM\Column(length: 255)]
private string|null $name = null; private string|null $name = null;
@ -19,17 +24,6 @@ class MenuItem
#[ORM\JoinColumn(nullable: false)] #[ORM\JoinColumn(nullable: false)]
private FoodVendor|null $foodVendor = null; private FoodVendor|null $foodVendor = null;
#[ORM\Column(nullable: true)]
private DateTimeImmutable|null $deletedAt = null;
public function __construct(
#[ORM\Id]
#[ORM\GeneratedValue(strategy: 'CUSTOM')]
#[ORM\Column(type: UlidType::NAME, unique: true)]
#[ORM\CustomIdGenerator(class: UlidGenerator::class)]
private Ulid|null $id = new Ulid
) {}
public function getId(): Ulid|null public function getId(): Ulid|null
{ {
return $this->id; return $this->id;
@ -58,27 +52,4 @@ class MenuItem
return $this; return $this;
} }
public function isDeleted(): bool
{
return $this->getDeletedAt() instanceof DateTimeImmutable;
}
public function delete(): static
{
$this->setDeletedAt(new DateTimeImmutable);
return $this;
}
public function getDeletedAt(): DateTimeImmutable|null
{
return $this->deletedAt;
}
public function setDeletedAt(DateTimeImmutable|null $deletedAt = new DateTimeImmutable): static
{
$this->deletedAt = $deletedAt;
return $this;
}
} }

View file

@ -2,11 +2,13 @@
namespace App\Form; namespace App\Form;
use App\Entity\FoodOrder;
use App\Entity\FoodVendor; use App\Entity\FoodVendor;
use Override; use Override;
use Symfony\Bridge\Doctrine\Form\Type\EntityType; use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
final class FoodOrderType extends AbstractType final class FoodOrderType extends AbstractType
{ {
@ -21,7 +23,6 @@ final class FoodOrderType extends AbstractType
]) ])
->add(child: 'closedAt', options: [ ->add(child: 'closedAt', options: [
'label' => 'closes at', 'label' => 'closes at',
'view_timezone' => 'Europe/Berlin',
]) ])
->add(child: 'createdBy') ->add(child: 'createdBy')
; ;
@ -29,4 +30,12 @@ final class FoodOrderType extends AbstractType
$builder->setAction($action); $builder->setAction($action);
} }
} }
#[Override]
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => FoodOrder::class,
]);
}
} }

View file

@ -15,7 +15,6 @@ final class FoodVendorType extends AbstractType
{ {
$builder $builder
->add('name') ->add('name')
->add('menuLink')
; ;
} }

View file

@ -1,28 +0,0 @@
<?php declare(strict_types=1);
namespace App\Form;
use App\Entity\MenuItem;
use Override;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
final class MenuItemType extends AbstractType
{
#[Override]
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('name')
;
}
#[Override]
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => MenuItem::class,
]);
}
}

View file

@ -15,12 +15,10 @@ final class OrderItemType extends AbstractType
{ {
$builder $builder
->add(child: 'name', options: [ ->add(child: 'name', options: [
'label' => 'order item', 'data' => $options['name'] ?? '',
])
->add(child: 'extras')
->add(child: 'createdBy', options: [
'label' => 'your name',
]) ])
->add('extras')
->add('createdBy')
; ;
} }

View file

@ -5,6 +5,7 @@ namespace App\Form;
use Override; use Override;
use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
final class UserNameFormType extends AbstractType final class UserNameFormType extends AbstractType
{ {
@ -12,7 +13,17 @@ final class UserNameFormType extends AbstractType
public function buildForm(FormBuilderInterface $builder, array $options): void public function buildForm(FormBuilderInterface $builder, array $options): void
{ {
$builder $builder
->add(child: 'username') ->add(child: 'username', options: [
'required' => false,
])
; ;
} }
#[Override]
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
// Configure your form options here
]);
}
} }

View file

@ -3,10 +3,7 @@
namespace App\Repository; namespace App\Repository;
use App\Entity\FoodOrder; use App\Entity\FoodOrder;
use DateInterval;
use DateTimeImmutable;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Persistence\ManagerRegistry; use Doctrine\Persistence\ManagerRegistry;
/** /**
@ -19,8 +16,10 @@ final class FoodOrderRepository extends ServiceEntityRepository
parent::__construct($registry, FoodOrder::class); parent::__construct($registry, FoodOrder::class);
} }
public function save(): void public function save(FoodOrder $order): void
{ {
$this->getEntityManager()
->persist($order);
$this->getEntityManager() $this->getEntityManager()
->flush(); ->flush();
} }
@ -28,23 +27,15 @@ final class FoodOrderRepository extends ServiceEntityRepository
/** /**
* @return FoodOrder[] * @return FoodOrder[]
*/ */
public function findLatestEntries(int $page = 1, int $pagesize = 10, int $days = 4): array public function findLatestEntries(int $limit = 5): array
{ {
$qb = $this->createQueryBuilder('alias');
$result = $this->createQueryBuilder('alias') $qb->orderBy('alias.id', 'DESC');
->orderBy('alias.id', 'DESC') $qb->setMaxResults($limit);
->setFirstResult(($page - 1) * $pagesize)
->setMaxResults($pagesize)
->getQuery()
->getResult();
if ($days < 1) { $query = $qb->getQuery();
return $result;
}
$date = (new DateTimeImmutable)->sub(new DateInterval('P' . $days . 'D')); return $query->getResult();
return (new ArrayCollection($result))
->filter(static fn(FoodOrder $order): bool => $order->getCreatedAt() >= $date)
->getValues();
} }
} }

100
src/Service/FakeData.php Normal file
View file

@ -0,0 +1,100 @@
<?php declare(strict_types=1);
namespace App\Service;
use App\Entity\FoodOrder;
use App\Entity\FoodVendor;
use App\Entity\OrderItem;
use App\Repository\FoodOrderRepository;
use App\Repository\FoodVendorRepository;
use App\Repository\OrderItemRepository;
use Doctrine\ORM\EntityManagerInterface;
use function range;
final readonly class FakeData
{
public function __construct(
private EntityManagerInterface $entityManager,
private FoodVendorRepository $foodVendorRepository,
private FoodOrderRepository $foodOrderRepository,
private OrderItemRepository $orderItemRepository,
) {}
public function resetDb(): void
{
foreach ($this->orderItemRepository->findAll() as $item) {
$this->entityManager->remove($item);
}
foreach ($this->foodOrderRepository->findAll() as $item) {
$this->entityManager->remove($item);
}
foreach ($this->foodVendorRepository->findAll() as $item) {
$this->entityManager->remove($item);
}
}
public function generate(int $vendorAmount = 3, int $orderAmount = 4, int $itemAmount = 10): void
{
$vendors = $this->generateVendors($vendorAmount);
foreach ($vendors as $vendor) {
$orders = $this->generateOrdersForVendor($vendor, $orderAmount);
foreach ($orders as $order) {
$this->generateItemsForOrder($order, $itemAmount);
}
}
$this->entityManager->flush();
}
/**
* @return FoodVendor[]
*/
public function generateVendors(int $amount = 10): array
{
$vendors = [];
foreach (range(1, $amount) as $i) {
$vendor = new FoodVendor;
$vendor->setName('Food Vendor ' . $i);
$this->entityManager->persist($vendor);
$vendors[] = $vendor;
}
return $vendors;
}
/**
* @return FoodOrder[]
*/
public function generateOrdersForVendor(FoodVendor $vendor, int $amount = 10): array
{
$orders = [];
foreach (range(1, $amount) as $i) {
$order = new FoodOrder;
$order->setFoodVendor($vendor);
if ($i % 2 === 0) {
$order->close();
}
$this->entityManager->persist($order);
$orders[] = $order;
}
return $orders;
}
/**
* @return OrderItem[]
*/
public function generateItemsForOrder(FoodOrder $order, int $amount = 10): array
{
$items = [];
foreach (range(1, $amount) as $i) {
$item = new OrderItem;
$item->setName('Item ' . $i);
$item->setFoodOrder($order);
if ($i % 2 === 0) {
$item->setExtras('Extra ' . $i);
}
$this->entityManager->persist($item);
$items[] = $item;
}
return $items;
}
}

View file

@ -1,18 +0,0 @@
<?php declare(strict_types=1);
namespace App\Service;
use Override;
use Stringable;
use function random_int;
final class Favicon implements Stringable
{
#[Override]
public function __toString(): string
{
$rotate = random_int(0, 380);
return "data:image/svg+xml, %3Csvg enable-background='new 0 0 512 512' viewBox='0 0 512 512' xmlns='http://www.w3.org/2000/svg' %3E%3Cg transform='rotate({$rotate}, 256, 256)' %3E%3Cpath d='m51.6 72.608 199.566-34.825 213.114 34.825c19.867 0 32.553 21.19 23.171 38.701l-206.224 384.92c-9.908 18.492-36.421 18.499-46.337.011l-206.455-384.92c-9.393-17.512 3.293-38.712 23.165-38.712z' fill='%23fecb21'/%3E%3Cpath d='m270.254 307.902c0 28.665-23.238 51.902-51.902 51.902s-51.902-23.237-51.902-51.902 23.237-51.902 51.902-51.902 51.902 23.237 51.902 51.902zm65.862 81.954c-25.323-13.433-56.74-3.794-70.173 21.528-13.433 25.323-3.794 56.74 21.528 70.173m48.645-342.08c-28.665 0-51.902 23.237-51.902 51.902s23.237 51.902 51.902 51.902 51.902-23.237 51.902-51.902-23.237-51.902-51.902-51.902zm-182.102-33.763c-28.665 0-51.902 23.237-51.902 51.902s23.237 51.902 51.902 51.902 51.902-23.237 51.902-51.902-23.237-51.902-51.902-51.902z' fill='%23f43b22'/%3E%3Cpath d='m41.204 130.519c-13.328.001-26.256-7.039-33.184-19.518-10.167-18.308-3.566-41.391 14.743-51.557 34.605-19.215 72.08-34.048 111.383-44.088 39.334-10.047 80.138-15.214 121.279-15.356 40.982-.14 81.845 4.711 121.369 14.423 40.306 9.904 78.8 24.757 114.412 44.147 18.393 10.014 25.184 33.041 15.17 51.433-10.014 18.391-33.043 25.185-51.433 15.169-29.887-16.273-62.269-28.757-96.246-37.105-33.046-8.12-67.199-12.236-101.53-12.236-.496 0-.986.001-1.481.002-34.903.121-69.481 4.494-102.772 12.998-33.009 8.432-64.412 20.851-93.338 36.912-5.829 3.239-12.145 4.776-18.372 4.776z' fill='%23c4790c'/%3E%3C/g%3E%3C/svg%3E%0A";
}
}

View file

@ -3,16 +3,8 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>{% block title %}Welcome!{% endblock %}</title> <title>{% block title %}Welcome!{% endblock %}</title>
<link rel="icon" type="image/svg+xml" <link rel="icon" type="image/png" href="/static/img/slice-of-pizza.png" />
href="{{ favicon }}" /> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@exampledev/new.css@1.1.2/new.min.css">
{% set currentDate = "now"|date("d") %}
{% if currentDate % 3 == 0 %}
<link rel="stylesheet" href="/static/css/new.min.css">
{% elseif currentDate % 3 == 1 %}
<link rel="stylesheet" href="/static/css/simple.min.css">
{% else %}
<link rel="stylesheet" href="/static/css/water.min.css">
{% endif %}
<style> <style>
label{ label{
display: block; display: block;
@ -27,7 +19,7 @@
<a href="{{ path('app_food_order_index') }}">Orders</a> / <a href="{{ path('app_food_order_index') }}">Orders</a> /
<a href="{{ path('app_food_vendor_index') }}">Vendors</a> / <a href="{{ path('app_food_vendor_index') }}">Vendors</a> /
<a <a
href="https://git.hannover.ccc.de/lubiana/futtern/issues/new" href="https://hannover.ccc.de/gitlab/lubiana/futtern/issues/new"
target="_blank" target="_blank"
>Create Issue</a> >Create Issue</a>
</nav> </nav>

View file

@ -4,14 +4,7 @@
{% block body %} {% block body %}
<h1>FoodOrder index</h1> <h1>FoodOrder index</h1>
<div>
<button
hx-get="{{ path('app_food_order_new') }}"
hx-trigger="click"
hx-target="closest div"
>Create new</button>
</div>
<hr>
<table class="table"> <table class="table">
<thead> <thead>
<tr> <tr>
@ -25,21 +18,18 @@
<tbody> <tbody>
{% for food_order in food_orders %} {% for food_order in food_orders %}
{{ include('food_order/table_row.html.twig') }} {{ include('food_order/table_row.html.twig') }}
{% endfor %} {% else %}
{% if food_orders|length < 10 %}
<tr> <tr>
<td colspan="5"> <td colspan="4">no records found</td>
check the <a href="{{ path('app_food_order_archive') }}">archive</a>
for older orders
</td>
</tr> </tr>
{% endif %} {% endfor %}
</tbody> </tbody>
</table> </table>
{% if prev_page > 0 %} <div>
<a href="{{ path('app_food_order_archive', {'page': prev_page}) }}">previous page</a> | <button
{% endif %} hx-get="{{ path('app_food_order_new') }}"
{% if next_page > current_page %} hx-trigger="click"
<a href="{{ path('app_food_order_archive', {'page': next_page}) }}">next page</a> hx-target="closest div"
{% endif %} >Create new</button>
</div>
{% endblock %} {% endblock %}

View file

@ -17,11 +17,11 @@
</tr> </tr>
<tr> <tr>
<th>CreatedAt</th> <th>CreatedAt</th>
<td>{{ food_order.createdAt ? food_order.createdAt|date('Y-m-d H:i:s', 'Europe/Berlin') : '' }}</td> <td>{{ food_order.createdAt ? food_order.createdAt|date('Y-m-d H:i:s') : '' }}</td>
</tr> </tr>
<tr> <tr>
<th>ClosedAt</th> <th>ClosedAt</th>
<td>{{ food_order.closedAt ? food_order.closedAt|date('Y-m-d H:i:s', 'Europe/Berlin') : '' }}</td> <td>{{ food_order.closedAt ? food_order.closedAt|date('Y-m-d H:i:s') : '' }}</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
@ -43,7 +43,7 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for item in food_order.orderItemsSortedByName %} {% for item in food_order.orderItems %}
<tr> <tr>
<td>{{ item.createdBy }}</td> <td>{{ item.createdBy }}</td>
<td>{{ item.name }}</td> <td>{{ item.name }}</td>

View file

@ -1,8 +1,8 @@
<tr> <tr>
<td>{{ food_order.createdBy }}</td> <td>{{ food_order.createdBy }}</td>
<td>{{ food_order.foodVendor.name }}</td> <td>{{ food_order.foodVendor.name }}</td>
<td>{{ food_order.createdAt ? food_order.createdAt|date('Y-m-d H:i:s', 'Europe/Berlin') : '' }}</td> <td>{{ food_order.createdAt ? food_order.createdAt|date('Y-m-d H:i:s') : '' }}</td>
<td>{{ food_order.closedAt ? food_order.closedAt|date('Y-m-d H:i:s', 'Europe/Berlin') : '' }}</td> <td>{{ food_order.closedAt ? food_order.closedAt|date('Y-m-d H:i:s') : '' }}</td>
<td> <td>
<a href="{{ path('app_food_order_show', {'id': food_order.id}) }}">show</a> <a href="{{ path('app_food_order_show', {'id': food_order.id}) }}">show</a>
</td> </td>

View file

@ -11,26 +11,9 @@
<th>Name</th> <th>Name</th>
<td>{{ food_vendor.name }}</td> <td>{{ food_vendor.name }}</td>
</tr> </tr>
<tr>
<th>Menu</th>
<td><a href="{{ food_vendor.menuLink }}" target="_blank">{{ food_vendor.menuLink }}</a></td>
</tr>
</tbody> </tbody>
</table> </table>
<section>
<h2>known menuitems</h2>
<ul>
{% for item in food_vendor.menuItems %}
<li>
<a href="{{ path('app_menu_item_show', {'id': item.id}) }}">{{ item.name }}</a>
</li>
{% endfor %}
</ul>
</section>
<a href="{{ path('app_food_vendor_index') }}">back to list</a> <a href="{{ path('app_food_vendor_index') }}">back to list</a>
<a href="{{ path('app_food_vendor_edit', {'id': food_vendor.id}) }}">edit</a> <a href="{{ path('app_food_vendor_edit', {'id': food_vendor.id}) }}">edit</a>

View file

@ -1,4 +0,0 @@
<form method="post" action="{{ path('app_menu_item_delete', {'id': menu_item.id}) }}" onsubmit="return confirm('Are you sure you want to delete this item?');">
<input type="hidden" name="_token" value="{{ csrf_token('delete' ~ menu_item.id) }}">
<button class="btn">Delete</button>
</form>

View file

@ -1,4 +0,0 @@
{{ form_start(form) }}
{{ form_widget(form) }}
<button class="btn">{{ button_label|default('Save') }}</button>
{{ form_end(form) }}

View file

@ -1,11 +0,0 @@
{% extends 'base.html.twig' %}
{% block title %}Edit MenuItem{% endblock %}
{% block body %}
<h1>Edit MenuItem</h1>
{{ include('menu_item/_form.html.twig', {'button_label': 'Update'}) }}
{{ include('menu_item/_delete_form.html.twig') }}
{% endblock %}

View file

@ -1,35 +0,0 @@
{% extends 'base.html.twig' %}
{% block title %}MenuItem index{% endblock %}
{% block body %}
<h1>MenuItem index</h1>
<table class="table">
<thead>
<tr>
<th>Id</th>
<th>Name</th>
<th>actions</th>
</tr>
</thead>
<tbody>
{% for menu_item in menu_items %}
<tr>
<td>{{ menu_item.id }}</td>
<td>{{ menu_item.name }}</td>
<td>
<a href="{{ path('app_menu_item_show', {'id': menu_item.id}) }}">show</a>
<a href="{{ path('app_menu_item_edit', {'id': menu_item.id}) }}">edit</a>
</td>
</tr>
{% else %}
<tr>
<td colspan="3">no records found</td>
</tr>
{% endfor %}
</tbody>
</table>
<a href="{{ path('app_menu_item_new') }}">Create new</a>
{% endblock %}

View file

@ -1,11 +0,0 @@
{% extends 'base.html.twig' %}
{% block title %}New MenuItem{% endblock %}
{% block body %}
<h1>Create new MenuItem</h1>
{{ include('menu_item/_form.html.twig') }}
<a href="{{ path('app_menu_item_index') }}">back to list</a>
{% endblock %}

View file

@ -1,26 +0,0 @@
{% extends 'base.html.twig' %}
{% block title %}MenuItem{% endblock %}
{% block body %}
<h1>MenuItem</h1>
<table class="table">
<tbody>
<tr>
<th>Id</th>
<td>{{ menu_item.id }}</td>
</tr>
<tr>
<th>Name</th>
<td>{{ menu_item.name }}</td>
</tr>
</tbody>
</table>
<a href="{{ path('app_food_vendor_show', { 'id': menu_item.foodVendor.id}) }}">back to list</a>
<a href="{{ path('app_menu_item_edit', {'id': menu_item.id}) }}">edit</a>
{{ include('menu_item/_delete_form.html.twig') }}
{% endblock %}

View file

@ -7,14 +7,6 @@
{{ include('order_item/_form.html.twig') }} {{ include('order_item/_form.html.twig') }}
<hr />
{% if food_order.foodVendor.menuLink != '' %}
<a href="{{ food_order.foodVendor.menuLink }}" target="_blank">
External link to Menu
</a>
{% endif %}
<div> <div>
<b>click a button to select a given menuitem</b> <b>click a button to select a given menuitem</b>
</div> </div>

View file

@ -4,13 +4,10 @@ namespace App\Tests\Controller;
use App\Entity\FoodOrder; use App\Entity\FoodOrder;
use App\Entity\FoodVendor; use App\Entity\FoodVendor;
use App\Entity\MenuItem;
use App\Entity\OrderItem;
use App\Tests\DbWebTest; use App\Tests\DbWebTest;
use Override; use Override;
use Symfony\Component\DomCrawler\Crawler; use Symfony\Component\DomCrawler\Crawler;
use function range;
use function sprintf; use function sprintf;
final class FoodOrderControllerTest extends DbWebTest final class FoodOrderControllerTest extends DbWebTest
@ -44,7 +41,7 @@ final class FoodOrderControllerTest extends DbWebTest
$this->manager->persist($this->vendor); $this->manager->persist($this->vendor);
$this->manager->flush(); $this->manager->flush();
$crawler = $this->client->request('GET', "{$this->path}list"); $crawler = $this->client->request('GET', $this->path);
self::assertResponseStatusCodeSame(200); self::assertResponseStatusCodeSame(200);
self::assertPageTitleContains('FoodOrder index'); self::assertPageTitleContains('FoodOrder index');
$this->assertCount( $this->assertCount(
@ -54,120 +51,6 @@ final class FoodOrderControllerTest extends DbWebTest
); );
} }
public function testOrderedItems(): void
{
$order = new FoodOrder;
$order->setFoodVendor($this->vendor);
$this->manager->persist($order);
$this->manager->persist($this->vendor);
$menuItemA = new MenuItem;
$menuItemA->setName('A');
$menuItemA->setFoodVendor($this->vendor);
$this->manager->persist($menuItemA);
$itemA = new OrderItem;
$itemA->setMenuItem($menuItemA);
$itemA->setName($menuItemA->getName());
$order->addOrderItem($itemA);
$this->manager->persist($itemA);
$menuItemC = new MenuItem;
$menuItemC->setName('C');
$menuItemC->setFoodVendor($this->vendor);
$this->manager->persist($menuItemC);
$itemC = new OrderItem;
$itemC->setMenuItem($menuItemC);
$itemC->setName($menuItemC->getName());
$order->addOrderItem($itemC);
$this->manager->persist($itemC);
$menuItemB = new MenuItem;
$menuItemB->setName('B');
$menuItemB->setFoodVendor($this->vendor);
$this->manager->persist($menuItemB);
$itemB = new OrderItem;
$itemB->setMenuItem($menuItemB);
$itemB->setName($menuItemB->getName());
$order->addOrderItem($itemB);
$this->manager->persist($itemB);
$this->manager->flush();
$crawler = $this->client->request('GET', "{$this->path}{$order->getId()}");
self::assertResponseIsSuccessful();
$tdContent = $crawler->filter(
'table.table:nth-child(6) > tbody:nth-child(2) > tr:nth-child(1) > td:nth-child(2)'
)->text();
$this->assertEquals('A', $tdContent);
$tdContent = $crawler->filter(
'table.table:nth-child(6) > tbody:nth-child(2) > tr:nth-child(2) > td:nth-child(2)'
)->text();
$this->assertEquals('B', $tdContent);
$tdContent = $crawler->filter(
'table.table:nth-child(6) > tbody:nth-child(2) > tr:nth-child(3) > td:nth-child(2)'
)->text();
$this->assertEquals('C', $tdContent);
}
public function testPaginatedIndex(): void
{
$this->generatePaginatedOrders();
$crawler = $this->client->request('GET', "{$this->path}list");
self::assertResponseStatusCodeSame(200);
self::assertPageTitleContains('FoodOrder index');
$this->assertElementContainsCount(
$crawler,
'td',
1,
'older orders'
);
$this->assertElementContainsCount(
$crawler,
'td',
0,
'next page'
);
}
/**
* @testWith [1, 0, 1]
* [2, 1, 1]
* [3, 1, 1]
* [4, 1, 0, 5]
*/
public function testPaginatedFirstPage(int $page, int $prevPage, int $nextPage, int $items = 10): void
{
$this->generatePaginatedOrders();
$crawler = $this->client->request('GET', "{$this->path}list/archive/{$page}");
self::assertResponseStatusCodeSame(200);
self::assertPageTitleContains('FoodOrder index');
$this->assertElementContainsCount(
$crawler,
'td',
$items,
'nobody'
);
$this->assertElementContainsCount(
$crawler,
'a',
$nextPage,
'next page'
);
$this->assertElementContainsCount(
$crawler,
'a',
$prevPage,
'previous page'
);
}
public function testNew(): void public function testNew(): void
{ {
$this->client->request('GET', sprintf('%snew', $this->path)); $this->client->request('GET', sprintf('%snew', $this->path));
@ -178,51 +61,7 @@ final class FoodOrderControllerTest extends DbWebTest
'food_order[foodVendor]' => $this->vendor->getId(), 'food_order[foodVendor]' => $this->vendor->getId(),
]); ]);
self::assertResponseRedirects("{$this->path}list"); self::assertResponseRedirects($this->path);
self::assertSame(1, $this->repository->count([])); self::assertSame(1, $this->repository->count([]));
} }
public function testOpen(): void
{
$order = new FoodOrder;
$order->setFoodVendor($this->vendor);
$order->close();
$this->assertTrue($order->isClosed());
$this->manager->persist($order);
$this->manager->flush();
$this->client->request('GET', sprintf('%s%s/open', $this->path, $order->getId()));
self::assertResponseRedirects("{$this->path}{$order->getId()}");
$openOrder = $this->repository->find($order->getId());
$this->assertFalse($openOrder->isClosed());
}
public function testClose(): void
{
$order = new FoodOrder;
$order->setClosedAt();
$order->setFoodVendor($this->vendor);
$this->assertFalse($order->isClosed());
$this->manager->persist($order);
$this->manager->flush();
$this->client->request('GET', sprintf('%s%s/close', $this->path, $order->getId()));
self::assertResponseRedirects("{$this->path}{$order->getId()}");
$openOrder = $this->repository->find($order->getId());
$this->assertTrue($openOrder->isClosed());
}
private function generatePaginatedOrders(): void
{
foreach (range(1, 35) as $i) {
$order = new FoodOrder($this->generateOldUlid());
$order->setFoodVendor($this->vendor);
$order->close();
$this->manager->persist($order);
}
$this->manager->flush();
}
} }

View file

@ -3,7 +3,6 @@
namespace App\Tests\Controller; namespace App\Tests\Controller;
use App\Entity\FoodVendor; use App\Entity\FoodVendor;
use App\Entity\MenuItem;
use App\Tests\DbWebTest; use App\Tests\DbWebTest;
use Override; use Override;
@ -38,7 +37,6 @@ final class FoodVendorControllerTest extends DbWebTest
{ {
$fixture = new FoodVendor; $fixture = new FoodVendor;
$fixture->setName('My Title'); $fixture->setName('My Title');
$fixture->setMenuLink('https://example.com/');
$this->manager->persist($fixture); $this->manager->persist($fixture);
$this->manager->flush(); $this->manager->flush();
@ -46,68 +44,9 @@ final class FoodVendorControllerTest extends DbWebTest
$crawler = $this->client->request('GET', sprintf('%s%s', $this->path, $fixture->getId())); $crawler = $this->client->request('GET', sprintf('%s%s', $this->path, $fixture->getId()));
$this->assertResponseIsSuccessful(); $this->assertResponseIsSuccessful();
$nameNode = $crawler->filter('td') $nameNode = $crawler->filter('td')
->last(); ->last();
$nameNode = $crawler->filter( $this->assertSame('My Title', $nameNode->text());
'.table > tbody:nth-child(1) > tr:nth-child(1) > td:nth-child(2)'
)->text();
$menuLinkNode = $crawler->filter(
'.table > tbody:nth-child(1) > tr:nth-child(2) > td:nth-child(2) > a:nth-child(1)'
)->text();
$this->assertSame('My Title', $nameNode);
$this->assertSame('https://example.com/', $menuLinkNode);
}
public function testShowMenuItems(): void
{
$fixture = new FoodVendor;
$fixture->setName('My Title');
$this->manager->persist($fixture);
$this->manager->flush();
$itemOne = new MenuItem;
$itemOne->setName('Item One');
$fixture->addMenuItem($itemOne);
$this->manager->persist($itemOne);
$itemTwo = new MenuItem;
$itemTwo->setName('Item Two');
$itemTwo->setFoodVendor($fixture);
$fixture->addMenuItem($itemTwo);
$this->manager->persist($itemTwo);
$itemThree = new MenuItem;
$itemThree->setName('Item Three');
$itemThree->setFoodVendor($fixture);
$fixture->addMenuItem($itemThree);
$this->manager->persist($itemThree);
$itemFour = new MenuItem;
$itemFour->setName('Item Four');
$itemFour->setFoodVendor($fixture);
$fixture->addMenuItem($itemFour);
$this->manager->persist($itemFour);
$this->manager->flush();
$crawler = $this->client->request('GET', sprintf('%s%s', $this->path, $fixture->getId()));
$this->assertResponseIsSuccessful();
$nameNode = $crawler->filter(
'.table > tbody:nth-child(1) > tr:nth-child(1) > td:nth-child(2)'
)->text();
$this->assertSame('My Title', $nameNode);
$itemNodes = $crawler->filter('li');
$this->assertCount(4, $itemNodes);
} }
public function testEdit(): void public function testEdit(): void
@ -118,17 +57,10 @@ final class FoodVendorControllerTest extends DbWebTest
$this->manager->persist($fixture); $this->manager->persist($fixture);
$this->manager->flush(); $this->manager->flush();
$crawler = $this->client->request('GET', sprintf('%s%s/edit', $this->path, $fixture->getId())); $this->client->request('GET', sprintf('%s%s/edit', $this->path, $fixture->getId()));
$this->assertSame(
$crawler->filter('#food_vendor_name')
->last()
->attr('value', ''),
'Value'
);
$this->client->submitForm('Update', [ $this->client->submitForm('Update', [
'food_vendor[name]' => 'Something New', 'food_vendor[name]' => 'Something New',
'food_vendor[menuLink]' => 'https://example.com/',
]); ]);
self::assertResponseRedirects('/food/vendor/'); self::assertResponseRedirects('/food/vendor/');
@ -136,7 +68,6 @@ final class FoodVendorControllerTest extends DbWebTest
$fixture = $this->repository->findAll(); $fixture = $this->repository->findAll();
self::assertSame('Something New', $fixture[0]->getName()); self::assertSame('Something New', $fixture[0]->getName());
self::assertSame('https://example.com/', $fixture[0]->getMenuLink());
} }
#[Override] #[Override]

View file

@ -1,76 +0,0 @@
<?php declare(strict_types=1);
namespace App\Tests\Controller;
use App\Tests\DbWebTest;
use Override;
final class HomeControllerTest extends DbWebTest
{
public function testIndex(): void
{
$this->client->request(
'GET',
'/'
);
self::assertResponseStatusCodeSame(302);
self::assertResponseHeaderSame('Location', '/food/order/list');
}
public function testSetUsername(): void
{
$this->client->request(
'GET',
'/username',
);
self::assertResponseStatusCodeSame(200);
$this->client->submitForm('Save', [
'user_name_form[username]' => 'Testing-1',
]);
self::assertResponseStatusCodeSame(302);
self::assertResponseHeaderSame('Location', '/food/order/list');
self::assertResponseCookieValueSame('username', 'Testing-1');
$crawler = $this->client->request(
'GET',
'/username',
);
self::assertResponseStatusCodeSame(200);
$this->assertSame(
$crawler->filter('#user_name_form_username')
->last()
->attr('value', ''),
'Testing-1'
);
}
public function testRemoveUsername(): void
{
$this->client->request(
'GET',
'/username',
);
self::assertResponseStatusCodeSame(200);
$this->client->submitForm('Save', [
'user_name_form[username]' => '',
]);
self::assertResponseStatusCodeSame(302);
self::assertResponseHeaderSame('Location', '/food/order/list');
self::assertResponseCookieValueSame('username', '');
}
#[Override]
public function getEntityClass(): string
{
return '';
}
}

View file

@ -1,113 +0,0 @@
<?php declare(strict_types=1);
namespace App\Tests\Controller;
use App\Entity\FoodOrder;
use App\Entity\FoodVendor;
use App\Entity\MenuItem;
use App\Tests\DbWebTest;
use Override;
use function sprintf;
final class MenuItemControllerTest extends DbWebTest
{
private string $path = '/menu/item/';
private FoodVendor $vendor;
#[Override]
public function setUp(): void
{
parent::setUp();
$this->vendor = new FoodVendor;
$this->vendor->setName('Food Vendor');
$this->manager->persist($this->vendor);
$this->manager->flush();
}
#[Override]
public function getEntityClass(): string
{
return MenuItem::class;
}
public function testShow(): void
{
$menuItem = new MenuItem;
$menuItem->setName('Testing 1 2');
$this->vendor->addMenuItem($menuItem);
$this->manager->persist($this->vendor);
$this->manager->persist($menuItem);
$this->manager->flush();
$crawler = $this->client->request('GET', "{$this->path}{$menuItem->getId()}");
$idValue = $crawler->filter(
'.table > tbody:nth-child(1) > tr:nth-child(1) > td:nth-child(2)'
)->text();
$nameValue = $crawler->filter(
'.table > tbody:nth-child(1) > tr:nth-child(2) > td:nth-child(2)'
)->text();
self::assertResponseStatusCodeSame(200);
$this->assertEquals($idValue, $menuItem->getId());
$this->assertEquals($nameValue, $menuItem->getName());
}
public function testEdit(): void
{
$menuItem = new MenuItem;
$menuItem->setName('Testing 1 2');
$this->vendor->addMenuItem($menuItem);
$this->manager->persist($this->vendor);
$this->manager->persist($menuItem);
$this->manager->flush();
$crawler = $this->client->request('GET', sprintf('%s%s/edit', $this->path, $menuItem->getId()));
$nameElem = $crawler->filter('#menu_item_name');
$this->assertEquals(
'Testing 1 2',
$nameElem->attr('value')
);
$this->client->submitForm('Update', [
'menu_item[name]' => 'Testing-1',
]);
self::assertResponseRedirects(sprintf('/menu/item/%s', $menuItem->getId()));
}
public function testDelete(): void
{
$menuItem = new MenuItem;
$menuItem->setName('Testing 1 2');
$this->vendor->addMenuItem($menuItem);
$this->manager->persist($this->vendor);
$this->manager->persist($menuItem);
$order = new FoodOrder;
$order->setFoodVendor($this->vendor);
$this->manager->persist($order);
$this->manager->flush();
$this->assertFalse($menuItem->isDeleted());
$this->client->request('GET', "{$this->path}{$menuItem->getId()}");
$this->client->submitForm('Delete', []);
$menuItem = $this->repository->find($menuItem->getId());
$this->assertTrue($menuItem->isDeleted());
$crawler = $this->client->request('GET', '/order/item/new/' . $order->getId());
$count = $crawler->filter('body > main:nth-child(2) > div:nth-child(5)')
->children()
->count();
$this->assertSame(0, $count);
$this->assertResponseIsSuccessful();
}
}

View file

@ -8,7 +8,6 @@ use App\Entity\MenuItem;
use App\Entity\OrderItem; use App\Entity\OrderItem;
use App\Repository\MenuItemRepository; use App\Repository\MenuItemRepository;
use App\Tests\DbWebTest; use App\Tests\DbWebTest;
use DateTimeImmutable;
use Override; use Override;
use function sprintf; use function sprintf;
@ -38,16 +37,6 @@ final class OrderItemControllerTest extends DbWebTest
$this->menuItem->setName('Testing'); $this->menuItem->setName('Testing');
$this->menuItem->setFoodVendor($this->vendor); $this->menuItem->setFoodVendor($this->vendor);
$vendor2 = new FoodVendor;
$vendor2->setName('Vendor 2');
$menuItem2 = new MenuItem;
$menuItem2->setName('Testing2');
$menuItem2->setFoodVendor($vendor2);
$this->manager->persist($vendor2);
$this->manager->persist($menuItem2);
$this->manager->persist($this->menuItem); $this->manager->persist($this->menuItem);
$this->manager->flush(); $this->manager->flush();
@ -56,16 +45,11 @@ final class OrderItemControllerTest extends DbWebTest
public function testNew(): void public function testNew(): void
{ {
$crawler = $this->client->request( $this->client->request(
'GET', 'GET',
sprintf('%snew/%s', $this->path, $this->order->getId()) sprintf('%snew/%s', $this->path, $this->order->getId())
); );
$children = $crawler->filter('body > main:nth-child(2) > div:nth-child(5)')
->children();
$this->assertCount(1, $children);
self::assertResponseStatusCodeSame(200); self::assertResponseStatusCodeSame(200);
$this->client->submitForm('Save', [ $this->client->submitForm('Save', [
@ -76,26 +60,7 @@ final class OrderItemControllerTest extends DbWebTest
self::assertResponseRedirects(sprintf('/food/order/%s', $this->order->getId())); self::assertResponseRedirects(sprintf('/food/order/%s', $this->order->getId()));
self::assertSame(1, $this->repository->count([])); self::assertSame(1, $this->repository->count([]));
self::assertSame(1, $this->menuItemRepository->count([ self::assertSame(1, $this->menuItemRepository->count([]));
'foodVendor' => $this->vendor->getId(),
]));
}
public function testNewOrderClosed(): void
{
$this->order->setClosedAt(new DateTimeImmutable('-1 Hour'));
$this->manager->persist($this->order);
$this->manager->flush();
$this->client->request(
'GET',
sprintf('%snew/%s', $this->path, $this->order->getId())
);
self::assertResponseRedirects(sprintf('/food/order/%s', $this->order->getId()));
self::assertSame(0, $this->repository->count([]));
} }
public function testNewCreateMenuItem(): void public function testNewCreateMenuItem(): void
@ -115,7 +80,7 @@ final class OrderItemControllerTest extends DbWebTest
self::assertResponseRedirects(sprintf('/food/order/%s', $this->order->getId())); self::assertResponseRedirects(sprintf('/food/order/%s', $this->order->getId()));
self::assertSame(1, $this->repository->count([])); self::assertSame(1, $this->repository->count([]));
self::assertSame(3, $this->menuItemRepository->count([])); self::assertSame(2, $this->menuItemRepository->count([]));
} }
public function testRemove(): void public function testRemove(): void
@ -136,80 +101,6 @@ final class OrderItemControllerTest extends DbWebTest
self::assertSame(0, $this->repository->count([])); self::assertSame(0, $this->repository->count([]));
} }
public function testOrderClosed(): void
{
$fixture = new OrderItem;
$fixture->setName('Testing');
$fixture->setExtras('Value');
$fixture->setMenuItem($this->menuItem);
$fixture->setFoodOrder($this->order);
$this->order->close();
$this->manager->persist($this->order);
$this->manager->persist($fixture);
$this->manager->flush();
$this->client->request('GET', sprintf('%sdelete/%s', $this->path, $fixture->getId()));
self::assertResponseRedirects(sprintf('/food/order/%s', $this->order->getId()));
self::assertSame(1, $this->repository->count([]));
}
public function testEdit(): void
{
$orderItem = new OrderItem;
$orderItem->setName('Testing');
$orderItem->setExtras('My Extra');
$orderItem->setFoodOrder($this->order);
$orderItem->setMenuItem($this->menuItem);
$this->manager->persist($orderItem);
$this->manager->flush();
$crawler = $this->client->request('GET', sprintf('%s%s/edit', $this->path, $orderItem->getId()));
$nameElem = $crawler->filter('#order_item_name');
$extrasElem = $crawler->filter('#order_item_extras');
$this->assertEquals(
'Testing',
$nameElem->attr('value')
);
$this->assertEquals(
'My Extra',
$extrasElem->attr('value')
);
$this->client->submitForm('Update', [
'order_item[name]' => 'Testing-1',
'order_item[extras]' => 'Testing-1',
]);
self::assertResponseRedirects(sprintf('/food/order/%s', $this->order->getId()));
self::assertSame(1, $this->repository->count([]));
self::assertSame(3, $this->menuItemRepository->count([]));
}
public function testEditOrderClosed(): void
{
$orderItem = new OrderItem;
$orderItem->setName('Testing');
$orderItem->setExtras('My Extra');
$orderItem->setFoodOrder($this->order);
$orderItem->setMenuItem($this->menuItem);
$this->order->close();
$this->manager->persist($orderItem);
$this->manager->persist($this->order);
$this->manager->flush();
$this->client->request('GET', sprintf('%s%s/edit', $this->path, $orderItem->getId()));
self::assertResponseRedirects(sprintf('/food/order/%s', $this->order->getId()));
}
public function testCopy(): void public function testCopy(): void
{ {
$orderItem = new OrderItem; $orderItem = new OrderItem;
@ -232,28 +123,7 @@ final class OrderItemControllerTest extends DbWebTest
$this->assertSame($orderItem->getName(), $item->getName()); $this->assertSame($orderItem->getName(), $item->getName());
$this->assertSame($orderItem->getExtras(), $item->getExtras()); $this->assertSame($orderItem->getExtras(), $item->getExtras());
} }
}
public function testCopyOrderClosed(): void
{
$orderItem = new OrderItem;
$orderItem->setName('My Title');
$orderItem->setExtras('My Title');
$orderItem->setFoodOrder($this->order);
$orderItem->setMenuItem($this->menuItem);
$this->order->close();
$this->manager->persist($this->order);
$this->manager->persist($orderItem);
$this->manager->flush();
$this->client->request('GET', sprintf('%s%s/copy', $this->path, $orderItem->getId()));
self::assertResponseRedirects(sprintf('/food/order/%s', $this->order->getId()));
$result = $this->repository->findBy([
'foodOrder' => $this->order->getId(),
]);
$this->assertCount(1, $result);
} }
#[Override] #[Override]

View file

@ -2,18 +2,12 @@
namespace App\Tests; namespace App\Tests;
use DateInterval;
use DateTimeImmutable;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository; use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\Tools\SchemaTool; use Doctrine\ORM\Tools\SchemaTool;
use Override; use Override;
use Symfony\Bundle\FrameworkBundle\KernelBrowser; use Symfony\Bundle\FrameworkBundle\KernelBrowser;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
use Symfony\Component\DomCrawler\Crawler;
use Symfony\Component\Uid\Ulid;
use function str_contains;
abstract class DbWebTest extends WebTestCase abstract class DbWebTest extends WebTestCase
{ {
@ -34,26 +28,6 @@ abstract class DbWebTest extends WebTestCase
$schemaTool->dropDatabase(); $schemaTool->dropDatabase();
$schemaTool->updateSchema($metadata); $schemaTool->updateSchema($metadata);
if ($this->getEntityClass() !== '') {
$this->repository = $this->manager->getRepository($this->getEntityClass()); $this->repository = $this->manager->getRepository($this->getEntityClass());
} }
}
protected function generateOldUlid(int $daysToSubtract = 10): Ulid
{
$date = (new DateTimeImmutable)->sub(new DateInterval('P' . $daysToSubtract . 'D'));
$ulidString = Ulid::generate($date);
return Ulid::fromString($ulidString);
}
protected function assertElementContainsCount(Crawler $crawler, string $element, int $count, string $text): void
{
$this->assertCount(
$count,
$crawler->filter($element)
->reduce(
static fn(Crawler $node, $i): bool => str_contains($node->text(), $text),
)
);
}
} }

View file

@ -1,24 +0,0 @@
<?php declare(strict_types=1);
namespace App\Tests\Entity;
use App\Entity\FoodOrder;
use App\Entity\OrderItem;
use PHPUnit\Framework\TestCase;
final class FoodOrderTest extends TestCase
{
public function testFoodOrder(): void
{
$order = new FoodOrder;
$orderItem = new OrderItem;
$this->assertCount(0, $order->getOrderItems());
$order->addOrderItem($orderItem);
$order->addOrderItem($orderItem);
$this->assertCount(1, $order->getOrderItems());
$this->assertSame($order, $orderItem->getFoodOrder());
$order->removeOrderItem($orderItem);
$this->assertCount(0, $order->getOrderItems());
$this->assertNull($orderItem->getFoodOrder());
}
}

View file

@ -1,63 +0,0 @@
<?php declare(strict_types=1);
namespace App\Tests\Entity;
use App\Entity\FoodOrder;
use App\Entity\FoodVendor;
use App\Entity\MenuItem;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Uid\Ulid;
final class FoodVendorTest extends TestCase
{
public function testFoodVendor(): void
{
$vendor = new FoodVendor;
$vendor->setName('Test');
$this->assertEquals('Test', $vendor->getName());
$this->assertInstanceOf(Ulid::class, $vendor->getId());
$this->assertCount(0, $vendor->getFoodOrders());
$order1 = new FoodOrder;
$vendor->addFoodOrder($order1);
$vendor->addFoodOrder($order1);
$this->assertCount(1, $vendor->getFoodOrders());
$this->assertSame($vendor, $order1->getFoodVendor());
$vendor->removeFoodOrder($order1);
$this->assertCount(0, $vendor->getFoodOrders());
$this->assertNull($order1->getFoodVendor());
}
public function testMenuItem(): void
{
$vendor = new FoodVendor;
$menuItem1 = new MenuItem;
$menuItem2 = new MenuItem;
$this->assertCount(0, $vendor->getMenuItems());
$vendor->addMenuItem($menuItem1);
$vendor->addMenuItem($menuItem1);
$this->assertCount(1, $vendor->getMenuItems());
$vendor->removeMenuItem($menuItem1);
$this->assertCount(0, $vendor->getMenuItems());
$this->assertNull($menuItem1->getFoodVendor());
$vendor->addMenuItem($menuItem1);
$menuItem2->delete();
$vendor->addMenuItem($menuItem2);
$this->assertCount(1, $vendor->getMenuItems());
$this->assertCount(2, $vendor->getMenuItems(true));
}
public function testRemoveForeignMenuItem(): void
{
$vendor1 = new FoodVendor;
$vendor2 = new FoodVendor;
$item1 = new MenuItem;
$vendor1->addMenuItem($item1);
$this->assertCount(1, $vendor1->getMenuItems());
$vendor2->removeMenuItem($item1);
$this->assertCount(1, $vendor1->getMenuItems());
$this->assertSame($vendor1, $item1->getFoodVendor());
}
}

View file

@ -1,30 +0,0 @@
<?php declare(strict_types=1);
namespace App\Tests\Entity;
use App\Entity\FoodVendor;
use App\Entity\MenuItem;
use DateTimeImmutable;
use PHPUnit\Framework\TestCase;
final class MenuItemTest extends TestCase
{
public function testMenuItem(): void
{
$item = new MenuItem;
$item->setName('Test');
$this->assertEquals('Test', $item->getName());
$vendor = new FoodVendor;
$vendor->setName('Test');
$item->setFoodVendor($vendor);
$this->assertEquals($vendor, $item->getFoodVendor());
$this->assertFalse($item->isDeleted());
$this->assertNull($item->getDeletedAt());
$item->delete();
$this->assertTrue($item->isDeleted());
$this->assertInstanceOf(DateTimeImmutable::class, $item->getDeletedAt());
}
}