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
9 changes: 9 additions & 0 deletions src/Illuminate/View/Compilers/ComponentTagCompiler.php
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,10 @@ public function componentClass(string $component)
return $class;
}

if (class_exists($class = $class.'\\'.Str::afterLast($class, '\\'))) {
return $class;
}

if (! is_null($guess = $this->guessAnonymousComponentUsingNamespaces($viewFactory, $component)) ||
! is_null($guess = $this->guessAnonymousComponentUsingPaths($viewFactory, $component))) {
return $guess;
Expand Down Expand Up @@ -338,6 +342,7 @@ protected function guessAnonymousComponentUsingPaths(Factory $viewFactory, strin
if (! is_null($guess = match (true) {
$viewFactory->exists($guess = $path['prefixHash'].$delimiter.$formattedComponent) => $guess,
$viewFactory->exists($guess = $path['prefixHash'].$delimiter.$formattedComponent.'.index') => $guess,
$viewFactory->exists($guess = $path['prefixHash'].$delimiter.$formattedComponent.'.'.Str::afterLast($formattedComponent, '.')) => $guess,
default => null,
})) {
return $guess;
Expand Down Expand Up @@ -376,6 +381,10 @@ protected function guessAnonymousComponentUsingNamespaces(Factory $viewFactory,
if ($viewFactory->exists($view = $this->guessViewName($componentName, $directory).'.index')) {
return $view;
}

if ($viewFactory->exists($view = $this->guessViewName($componentName, $directory).'.'.Str::afterLast($componentName, '.'))) {
return $view;
}
});
}

Expand Down
122 changes: 119 additions & 3 deletions tests/View/Blade/BladeComponentTagCompilerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,24 @@ public function testBasicComponentParsing()
'@endComponentClass##END-COMPONENT-CLASS##</div>', trim($result));
}

public function testNestedDefaultComponentParsing()
{
$container = new Container;
$container->instance(Application::class, $app = m::mock(Application::class));
$container->instance(Factory::class, $factory = m::mock(Factory::class));
$app->shouldReceive('getNamespace')->once()->andReturn('App\\');
Container::setInstance($container);

$result = $this->compiler()->compileTags('<div><x-card /></div>');

$this->assertSame("<div>##BEGIN-COMPONENT-CLASS##@component('App\View\Components\Card\Card', 'card', [])
<?php if (isset(\$attributes) && \$attributes instanceof Illuminate\View\ComponentAttributeBag): ?>
<?php \$attributes = \$attributes->except(\App\View\Components\Card\Card::ignoredParameterNames()); ?>
<?php endif; ?>
<?php \$component->withAttributes([]); ?>\n".
'@endComponentClass##END-COMPONENT-CLASS##</div>', trim($result));
}

public function testBasicComponentWithEmptyAttributesParsing()
{
$this->mockViewFactory();
Expand Down Expand Up @@ -501,6 +519,25 @@ public function testClasslessComponentsWithIndexView()
'@endComponentClass##END-COMPONENT-CLASS##', trim($result));
}

public function testClasslessComponentsWithComponentView()
{
$container = new Container;
$container->instance(Application::class, $app = m::mock(Application::class));
$container->instance(Factory::class, $factory = m::mock(Factory::class));
$app->shouldReceive('getNamespace')->andReturn('App\\');
$factory->shouldReceive('exists')->andReturn(false, false, true);
Container::setInstance($container);

$result = $this->compiler()->compileTags('<x-anonymous-component :name="\'Taylor\'" :age="31" wire:model="foo" />');

$this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\View\AnonymousComponent', 'anonymous-component', ['view' => 'components.anonymous-component.anonymous-component','data' => ['name' => 'Taylor','age' => 31,'wire:model' => 'foo']])
<?php if (isset(\$attributes) && \$attributes instanceof Illuminate\View\ComponentAttributeBag): ?>
<?php \$attributes = \$attributes->except(\Illuminate\View\AnonymousComponent::ignoredParameterNames()); ?>
<?php endif; ?>
<?php \$component->withAttributes(['name' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute('Taylor'),'age' => 31,'wire:model' => 'foo']); ?>\n".
'@endComponentClass##END-COMPONENT-CLASS##', trim($result));
}

public function testPackagesClasslessComponents()
{
$container = new Container;
Expand Down Expand Up @@ -528,7 +565,7 @@ public function testClasslessComponentsWithAnonymousComponentNamespace()
$container->instance(Factory::class, $factory = m::mock(Factory::class));

$app->shouldReceive('getNamespace')->once()->andReturn('App\\');
$factory->shouldReceive('exists')->times(3)->andReturnUsing(function ($arg) {
$factory->shouldReceive('exists')->times(4)->andReturnUsing(function ($arg) {
// In our test, we'll do as if the 'public.frontend.anonymous-component'
// view exists and not the others.
return $arg === 'public.frontend.anonymous-component';
Expand Down Expand Up @@ -562,7 +599,7 @@ public function testClasslessComponentsWithAnonymousComponentNamespaceWithIndexV
$container->instance(Factory::class, $factory = m::mock(Factory::class));

$app->shouldReceive('getNamespace')->once()->andReturn('App\\');
$factory->shouldReceive('exists')->times(4)->andReturnUsing(function (string $viewNameBeingCheckedForExistence) {
$factory->shouldReceive('exists')->times(5)->andReturnUsing(function (string $viewNameBeingCheckedForExistence) {
// In our test, we'll do as if the 'public.frontend.anonymous-component'
// view exists and not the others.
return $viewNameBeingCheckedForExistence === 'admin.auth.components.anonymous-component.index';
Expand All @@ -588,6 +625,40 @@ public function testClasslessComponentsWithAnonymousComponentNamespaceWithIndexV
'@endComponentClass##END-COMPONENT-CLASS##', trim($result));
}

public function testClasslessComponentsWithAnonymousComponentNamespaceWithComponentView()
{
$container = new Container;

$container->instance(Application::class, $app = m::mock(Application::class));
$container->instance(Factory::class, $factory = m::mock(Factory::class));

$app->shouldReceive('getNamespace')->once()->andReturn('App\\');
$factory->shouldReceive('exists')->times(6)->andReturnUsing(function (string $viewNameBeingCheckedForExistence) {
// In our test, we'll do as if the 'public.frontend.anonymous-component'
// view exists and not the others.
return $viewNameBeingCheckedForExistence === 'admin.auth.components.anonymous-component.anonymous-component';
});

Container::setInstance($container);

$blade = m::mock(BladeCompiler::class)->makePartial();

$blade->shouldReceive('getAnonymousComponentNamespaces')->once()->andReturn([
'admin.auth' => 'admin.auth.components',
]);

$compiler = $this->compiler([], [], $blade);

$result = $compiler->compileTags('<x-admin.auth::anonymous-component :name="\'Taylor\'" :age="31" wire:model="foo" />');

$this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\View\AnonymousComponent', 'admin.auth::anonymous-component', ['view' => 'admin.auth.components.anonymous-component.anonymous-component','data' => ['name' => 'Taylor','age' => 31,'wire:model' => 'foo']])
<?php if (isset(\$attributes) && \$attributes instanceof Illuminate\View\ComponentAttributeBag): ?>
<?php \$attributes = \$attributes->except(\Illuminate\View\AnonymousComponent::ignoredParameterNames()); ?>
<?php endif; ?>
<?php \$component->withAttributes(['name' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute('Taylor'),'age' => 31,'wire:model' => 'foo']); ?>\n".
'@endComponentClass##END-COMPONENT-CLASS##', trim($result));
}

public function testClasslessComponentsWithAnonymousComponentPath()
{
$container = new Container;
Expand Down Expand Up @@ -621,6 +692,39 @@ public function testClasslessComponentsWithAnonymousComponentPath()
'@endComponentClass##END-COMPONENT-CLASS##', trim($result));
}

public function testClasslessComponentsWithAnonymousComponentPathComponentName()
{
$container = new Container;

$container->instance(Application::class, $app = m::mock(Application::class));
$container->instance(Factory::class, $factory = m::mock(Factory::class));

$app->shouldReceive('getNamespace')->once()->andReturn('App\\');

$factory->shouldReceive('exists')->andReturnUsing(function ($arg) {
return $arg === md5('test-directory').'::panel.panel';
});

Container::setInstance($container);

$blade = m::mock(BladeCompiler::class)->makePartial();

$blade->shouldReceive('getAnonymousComponentPaths')->once()->andReturn([
['path' => 'test-directory', 'prefix' => null, 'prefixHash' => md5('test-directory')],
]);

$compiler = $this->compiler([], [], $blade);

$result = $compiler->compileTags('<x-panel />');

$this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\View\AnonymousComponent', 'panel', ['view' => '".md5('test-directory')."::panel.panel','data' => []])
<?php if (isset(\$attributes) && \$attributes instanceof Illuminate\View\ComponentAttributeBag): ?>
<?php \$attributes = \$attributes->except(\Illuminate\View\AnonymousComponent::ignoredParameterNames()); ?>
<?php endif; ?>
<?php \$component->withAttributes([]); ?>\n".
'@endComponentClass##END-COMPONENT-CLASS##', trim($result));
}

public function testClasslessIndexComponentsWithAnonymousComponentPath()
{
$container = new Container;
Expand Down Expand Up @@ -689,7 +793,7 @@ public function testItThrowsAnExceptionForNonExistingClass()
$container->instance(Application::class, $app = m::mock(Application::class));
$container->instance(Factory::class, $factory = m::mock(Factory::class));
$app->shouldReceive('getNamespace')->once()->andReturn('App\\');
$factory->shouldReceive('exists')->twice()->andReturn(false);
$factory->shouldReceive('exists')->times(3)->andReturn(false);
Container::setInstance($container);

$this->expectException(InvalidArgumentException::class);
Expand Down Expand Up @@ -855,3 +959,15 @@ public function render()
return 'container';
}
}

namespace App\View\Components\Card;

use Illuminate\View\Component;

class Card extends Component
{
public function render()
{
return 'card';
}
}