Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 0 additions & 7 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,6 @@ Entries from `0.4.0` onward are generated automatically by `bin/release.sh` from
* @michalbiarda made their first contribution in https://github.com/marko-php/marko/pull/58


## [Unreleased]

### New Features
* feat: add marko/page-cache and marko/page-cache-file packages
* feat(database): add `selectRaw`, `whereRaw`, and `orderByRaw` to `QueryBuilderInterface` with positional bindings and denylist validation
* feat(core): add module-declared global middleware support via globalMiddleware key in module.php

## [0.5.0] - 2026-05-01

### New Features
Expand Down
5 changes: 4 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -548,6 +548,9 @@
"Marko\\View\\Twig\\Tests\\": "packages/view-twig/tests/",
"Marko\\Vite\\Tests\\": "packages/vite/tests/",
"Marko\\Webhook\\Tests\\": "packages/webhook/tests/"
}
},
"files": [
"packages/database-mysql/tests/Connection/Helpers.php"
]
}
}
47 changes: 25 additions & 22 deletions packages/admin-panel-latte/tests/ResolverIntegrationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,25 +8,28 @@
use Marko\View\ModuleTemplateResolver;
use Marko\View\ViewConfig;

it('ModuleTemplateResolver resolves admin-panel::dashboard/index to the new admin-panel-latte path', function (): void {
$adminPanelLatteDir = dirname(__DIR__);

$modules = [
new ModuleManifest(
name: 'marko/admin-panel-latte',
version: '1.0.0',
path: $adminPanelLatteDir,
source: 'vendor',
extra: ['marko' => ['templates_for' => 'marko/admin-panel']],
),
];

$resolver = new ModuleTemplateResolver(
new ModuleRepository($modules),
new ViewConfig(new FakeConfigRepository(['view.extension' => '.latte'])),
);

$result = $resolver->resolve('admin-panel::dashboard/index');

expect($result)->toBe($adminPanelLatteDir . '/resources/views/dashboard/index.latte');
});
it(
'ModuleTemplateResolver resolves admin-panel::dashboard/index to the new admin-panel-latte path',
function (): void {
$adminPanelLatteDir = dirname(__DIR__);

$modules = [
new ModuleManifest(
name: 'marko/admin-panel-latte',
version: '1.0.0',
path: $adminPanelLatteDir,
source: 'vendor',
extra: ['marko' => ['templates_for' => 'marko/admin-panel']],
),
];

$resolver = new ModuleTemplateResolver(
new ModuleRepository($modules),
new ViewConfig(new FakeConfigRepository(['view.extension' => '.latte'])),
);

$result = $resolver->resolve('admin-panel::dashboard/index');

expect($result)->toBe($adminPanelLatteDir . '/resources/views/dashboard/index.latte');
}
);
4 changes: 3 additions & 1 deletion packages/admin-panel-latte/tests/TemplateMigrationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@
it('LayoutTemplateTest.php has been removed from packages/admin-panel/tests/Unit/Template/', function (): void {
$oldTestPath = dirname(__DIR__, 2) . '/admin-panel/tests/Unit/Template/LayoutTemplateTest.php';

expect(file_exists($oldTestPath))->toBeFalse('LayoutTemplateTest.php should not exist in admin-panel/tests/Unit/Template/');
expect(file_exists($oldTestPath))->toBeFalse(
'LayoutTemplateTest.php should not exist in admin-panel/tests/Unit/Template/'
);
});

it('the moved Pest file uses dirname(__DIR__) for $viewsPath (one level up)', function (): void {
Expand Down
114 changes: 63 additions & 51 deletions packages/admin-panel-twig/tests/TemplateExistenceTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,43 +5,52 @@
describe('admin-panel-twig templates', function (): void {
$viewsDir = dirname(__DIR__) . '/resources/views';

test('auth/login.twig exists and renders a form with email and password fields', function () use ($viewsDir): void {
$path = $viewsDir . '/auth/login.twig';

expect(file_exists($path))->toBeTrue();

$contents = file_get_contents($path);

expect($contents)
->toContain('<form')
->and($contents)->toContain('name="email"')
->and($contents)->toContain('name="password"');
});

test('layout/base.twig exists and contains HTML doctype, sidebar include, and content block', function () use ($viewsDir): void {
$path = $viewsDir . '/layout/base.twig';

expect(file_exists($path))->toBeTrue();

$contents = file_get_contents($path);

expect($contents)
->toContain('<!DOCTYPE html>')
->and($contents)->toContain("{% include 'admin-panel::partials/sidebar'")
->and($contents)->toContain('{% block content %}');
});

test('dashboard/index.twig exists and extends layout/base.twig with a content block', function () use ($viewsDir): void {
$path = $viewsDir . '/dashboard/index.twig';

expect(file_exists($path))->toBeTrue();

$contents = file_get_contents($path);

expect($contents)
->toContain("{% extends 'admin-panel::layout/base' %}")
->and($contents)->toContain('{% block content %}');
});
test(
'auth/login.twig exists and renders a form with email and password fields',
function () use ($viewsDir): void {
$path = $viewsDir . '/auth/login.twig';

expect(file_exists($path))->toBeTrue();

$contents = file_get_contents($path);

expect($contents)
->toContain('<form')
->and($contents)->toContain('name="email"')
->and($contents)->toContain('name="password"');
}
);

test(
'layout/base.twig exists and contains HTML doctype, sidebar include, and content block',
function () use ($viewsDir): void {
$path = $viewsDir . '/layout/base.twig';

expect(file_exists($path))->toBeTrue();

$contents = file_get_contents($path);

expect($contents)
->toContain('<!DOCTYPE html>')
->and($contents)->toContain("{% include 'admin-panel::partials/sidebar'")
->and($contents)->toContain('{% block content %}');
}
);

test(
'dashboard/index.twig exists and extends layout/base.twig with a content block',
function () use ($viewsDir): void {
$path = $viewsDir . '/dashboard/index.twig';

expect(file_exists($path))->toBeTrue();

$contents = file_get_contents($path);

expect($contents)
->toContain("{% extends 'admin-panel::layout/base' %}")
->and($contents)->toContain('{% block content %}');
}
);

test('partials/sidebar.twig exists and iterates menu items', function () use ($viewsDir): void {
$path = $viewsDir . '/partials/sidebar.twig';
Expand Down Expand Up @@ -81,18 +90,21 @@
->and($contents)->toContain('{{ csrfToken }}');
});

test('the layout\'s content block can be overridden by child templates (verified via dashboard.twig)', function () use ($viewsDir): void {
$layoutPath = $viewsDir . '/layout/base.twig';
$dashboardPath = $viewsDir . '/dashboard/index.twig';

expect(file_exists($layoutPath))->toBeTrue()
->and(file_exists($dashboardPath))->toBeTrue();

$layoutContents = file_get_contents($layoutPath);
$dashboardContents = file_get_contents($dashboardPath);

expect($layoutContents)->toContain('{% block content %}')
->and($dashboardContents)->toContain('{% block content %}')
->and($dashboardContents)->toContain('{% endblock %}');
});
test(
'the layout\'s content block can be overridden by child templates (verified via dashboard.twig)',
function () use ($viewsDir): void {
$layoutPath = $viewsDir . '/layout/base.twig';
$dashboardPath = $viewsDir . '/dashboard/index.twig';

expect(file_exists($layoutPath))->toBeTrue()
->and(file_exists($dashboardPath))->toBeTrue();

$layoutContents = file_get_contents($layoutPath);
$dashboardContents = file_get_contents($dashboardPath);

expect($layoutContents)->toContain('{% block content %}')
->and($dashboardContents)->toContain('{% block content %}')
->and($dashboardContents)->toContain('{% endblock %}');
}
);
});
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,9 @@ public function getMenuItems(): array
}

it('requires authentication via AdminAuthMiddleware for dashboard', function (): void {
$middlewareAttributes = (new ReflectionMethod(DashboardController::class, 'index'))->getAttributes(Middleware::class);
$middlewareAttributes = (new ReflectionMethod(DashboardController::class, 'index'))->getAttributes(
Middleware::class
);

expect($middlewareAttributes)->toHaveCount(1)
->and($middlewareAttributes[0]->newInstance()->middleware)->toContain(AdminAuthMiddleware::class);
Expand Down
4 changes: 3 additions & 1 deletion packages/admin-panel/tests/Unit/EngineSiblingCleanupTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
it('packages/admin-panel/resources/views/ no longer exists', function (): void {
$viewsDir = dirname(__DIR__, 2) . '/resources/views';

expect(is_dir($viewsDir))->toBeFalse('resources/views/ directory should not exist after engine sibling extraction');
expect(is_dir($viewsDir))->toBeFalse(
'resources/views/ directory should not exist after engine sibling extraction'
);
});

it('packages/admin-panel/composer.json includes a suggest block', function (): void {
Expand Down
25 changes: 14 additions & 11 deletions packages/admin/tests/Unit/Exceptions/NoDriverExceptionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,20 @@
use Marko\Admin\Exceptions\NoDriverException;

describe('NoDriverException', function (): void {
it('has DRIVER_PACKAGES constant listing marko/admin-api, marko/admin-auth, and marko/admin-panel', function (): void {
$reflection = new ReflectionClass(NoDriverException::class);
$constants = $reflection->getConstants();

expect($constants)->toHaveKey('DRIVER_PACKAGES')
->and($constants['DRIVER_PACKAGES'])->toBe([
'marko/admin-api',
'marko/admin-auth',
'marko/admin-panel',
]);
});
it(
'has DRIVER_PACKAGES constant listing marko/admin-api, marko/admin-auth, and marko/admin-panel',
function (): void {
$reflection = new ReflectionClass(NoDriverException::class);
$constants = $reflection->getConstants();

expect($constants)->toHaveKey('DRIVER_PACKAGES')
->and($constants['DRIVER_PACKAGES'])->toBe([
'marko/admin-api',
'marko/admin-auth',
'marko/admin-panel',
]);
}
);

it('provides suggestion with composer require commands for all driver packages', function (): void {
$exception = NoDriverException::noDriverInstalled();
Expand Down
5 changes: 4 additions & 1 deletion packages/amphp/src/Command/PubSubListenCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@ public function __construct(
private EventLoopRunner $runner,
) {}

public function execute(Input $input, Output $output): int
public function execute(
Input $input,
Output $output,
): int
{
$output->writeLine('Starting pub/sub listener...');
$output->writeLine('Press Ctrl+C to stop.');
Expand Down
9 changes: 6 additions & 3 deletions packages/authentication/tests/KnownDriversValidationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,12 @@
$knownDriversPath = __DIR__ . '/../known-drivers.php';
$skeletonComposerPath = __DIR__ . '/../../skeleton/composer.json';

test('skeleton suggest block contains all authentication drivers', function () use ($knownDriversPath, $skeletonComposerPath) {
KnownDriversValidator::assertSkeletonSuggestContainsAll($knownDriversPath, $skeletonComposerPath);
});
test(
'skeleton suggest block contains all authentication drivers',
function () use ($knownDriversPath, $skeletonComposerPath) {
KnownDriversValidator::assertSkeletonSuggestContainsAll($knownDriversPath, $skeletonComposerPath);
}
);

test('every authentication driver follows marko slash prefix pattern', function () use ($knownDriversPath) {
KnownDriversValidator::assertDocsUrlsResolveToValidPattern($knownDriversPath);
Expand Down
30 changes: 24 additions & 6 deletions packages/authentication/tests/Unit/Guard/SessionGuardTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -316,17 +316,26 @@ public function retrieveByCredentials(array $credentials): ?AuthenticatableInter
return null;
}

public function validateCredentials(AuthenticatableInterface $user, array $credentials): bool
public function validateCredentials(
AuthenticatableInterface $user,
array $credentials,
): bool
{
return false;
}

public function retrieveByRememberToken(int|string $identifier, string $token): ?AuthenticatableInterface
public function retrieveByRememberToken(
int|string $identifier,
string $token,
): ?AuthenticatableInterface
{
return $this->userByRememberToken;
}

public function updateRememberToken(AuthenticatableInterface $user, ?string $token): void
public function updateRememberToken(
AuthenticatableInterface $user,
?string $token,
): void
{
$user->setRememberToken($token);
}
Expand Down Expand Up @@ -416,17 +425,26 @@ public function retrieveByCredentials(array $credentials): ?AuthenticatableInter
return null;
}

public function validateCredentials(AuthenticatableInterface $user, array $credentials): bool
public function validateCredentials(
AuthenticatableInterface $user,
array $credentials,
): bool
{
return false;
}

public function retrieveByRememberToken(int|string $identifier, string $token): ?AuthenticatableInterface
public function retrieveByRememberToken(
int|string $identifier,
string $token,
): ?AuthenticatableInterface
{
return $this->userByRememberToken;
}

public function updateRememberToken(AuthenticatableInterface $user, ?string $token): void
public function updateRememberToken(
AuthenticatableInterface $user,
?string $token,
): void
{
$user->setRememberToken($token);
}
Expand Down
Loading