I am testing a web project using Laracasts\Integrated library (with PHPUNIT).
I have a following HTML form:
<input type="text" name = "company[0][name]" id="comp_0" />
I have the following test case:
/** #test **/
public function add_new()
{
$this->type('New Company' , 'company[0][name]');
}
I get the following error:
Symfony\Component\CssSelector\Exception\SyntaxErrorException: Expected identifier or "*", but <number at 20> found.
I am searching and trying alot ... but i am not able to get the solution for it.
Solution was simple:
/** #test **/
public function add_this()
{
$this->storeInput('company[0][name]' , 'New Company Here On Nepal' ,true);
}
public function storeInput($element, $text, $force = false)
{
if ($force) {
$this->inputs[$element] = $text;
return $this;
}
else {
return parent::storeInput($element, $text);
}
}
Related
I laravel 9 project with squizlabs/php_codesniffer my phpstorm 2021 shows error :
Expected parameter of type '\TValue', 'Vote' provided
in model when I use table name in scope condition :
class QuizQualityResult extends Model
{
protected $table = 'quiz_quality_results';
public function scopeGetByVoteCategories($query, $voteCategoryId= null)
{
// “new Vote” is marked as error
$voteTable = with(new Vote)->getTable();
if (!empty($voteCategoryId)) {
if ( is_array($voteCategoryId) ) {
$query->whereIn( $voteTable . '.vote_category_id', $voteCategoryId);
} else {
$query->where( $voteTable . ' . vote_category_id', $voteCategoryId);
}
}
return $query;
}
If there is a way to fix this error ? Or maybe to use better syntax here ?
Thanks!
There is no need for helper with() here
$voteTable = (new Vote())->getTable()
Ps: there is a feeling that your method does not work the way you intended. Perhaps you meant to do the following (I could be wrong):
public function scopeGetByVoteCategories($query, $voteCategoryId = null)
{
if (empty($voteCategoryId)) {
return $query;
}
return $query->whereHas('vote', static function ($query) use ($voteCategoryId) {
if (is_array($voteCategoryId)) {
return $query->whereIn('vote_category_id', $voteCategoryId);
}
return $query->where('vote_category_id', $voteCategoryId);
});
}
public function vote()
{
// your relationship
}
I have this test in my Laravel project:
// here are some test function configurations
// found nothing if not match
$this->post(action([TagController::class, 'search'], '!! Not foundable name !!'))
->assertOk()
->assertJsonCount(0, 'data');
// found it if match
$this->post(action([TagController::class, 'search'], $matchName))
->assertOk()
->assertJsonCount(1, 'data');
// found it if partly match
$this->post(action([TagController::class, 'search'], $partlyMatchName))
->assertOk()
->assertJsonCount(1, 'data');
Now I see this result if test failed:
Failed to assert that the response count matched the expected 0
Failed asserting that actual size 4 matches expected size 0.
This isn't say too mutch for me, I don't see which assertation failed and exactly why. I want to define custom message for this case.
I want to do someting like this:
->assertJsonCount(
0,
'data',
'It should found noting, because of conditions are not match'
);
Is there any way to send custom message to the tester user in this case?
You should override decodeResponseJson() method of Illuminate\Testing\TestReponse class.
Create two classes of TestResponse and AssertableJsonString in Tests namespace as follows:
TestResponse
namespace Tests;
use Illuminate\Testing\Assert as PHPUnit;
class TestResponse extends \Illuminate\Testing\TestResponse
{
/**
* #inheritDoc
*/
public function decodeResponseJson()
{
$testJson = new AssertableJsonString($this->getContent());
$decodedResponse = $testJson->json();
if (is_null($decodedResponse) || $decodedResponse === false) {
if ($this->exception) {
throw $this->exception;
} else {
PHPUnit::fail('Invalid JSON was returned from the route.');
}
}
return $testJson;
}
}
AssertableJsonString
namespace Tests;
use Illuminate\Testing\Assert as PHPUnit;
class AssertableJsonString extends \Illuminate\Testing\AssertableJsonString implements \ArrayAccess, \Countable
{
/**
* #inheritDoc
*/
public function assertCount(int $count, $key = null, $message="Failed to assert that the response count matched the expected %d")
{
if (! is_null($key)) {
PHPUnit::assertCount(
$count, data_get($this->decoded, $key),
sprintf($message, $count)
);
return $this;
}
PHPUnit::assertCount($count,
$this->decoded,
sprintf($message, $count)
);
return $this;
}
}
Now, you need to bind Laravel TestResponse class to your custom TestResponse in boot method of AppServiceProvider as follows:
public function boot()
{
$this->app->bind(\Illuminate\Testing\TestResponse::class,\Tests\TestResponse::class);
}
Notice: You need to place %d in your message format to be replaced by sprintf() function.
With laravel 7 /livewire app I make crud using Repository and I got listing of data ok,
In mount event I assign protected var $FacilityRepository , which works ok in render method,
but it is null in edit method and I got error:
Call to a member function getById() on null
when user clicks on “edit link”
<?php
namespace App\Http\Livewire\Admin;
use App\library\CheckValueType;
use App\Settings;
use DB;
use Livewire\Component;
use App\Facility;
use Livewire\WithPagination;
use App\Repositories\Interfaces\FacilityRepositoryInterface;
class Facilities extends Component
{
use WithPagination;
public $form= [
'name'=>'',
'descr'=> '',
'created_at'=> '',
'is_reopen' => false,
];
public $current_facility_id;
public $filter_name= '';
public $updateMode = 'browse';
protected $FacilityRepository;
public function render()
{
$this->facility_rows_count = Facility
::getByName($this->filter_name, true)
->count();
$backend_per_page = Settings::getValue('backend_per_page', CheckValueType::cvtInteger, 20);
\Log::info( 'render -10 $this->FacilityRepository::' . print_r( json_encode($this->FacilityRepository), true ) );
// line above logged as : [2020-09-04 16:46:26] local.INFO: render -10 $this->FacilityRepository::{}
return view('livewire.admin.facilities.container', [
'facilityDataRows' => $this->FacilityRepository->filterWithPagination(
[
'name'=>$this->filter_name,
],
$backend_per_page
),
'facility_rows_count'=> $this->facility_rows_count
]); // this listing is rendered OK
}
public function mount( FacilityRepositoryInterface $FacilityRepository ) {
$this->FacilityRepository = $FacilityRepository;
\Log::info( '-101mount $this->FacilityRepository::' . print_r( json_encode($this->FacilityRepository), true ) );
// line above logged as : [2020-09-04 16:46:26] local.INFO: -101mount $this->FacilityRepository::{}
}
public function edit($id)
{
\Log::info( '-1 edit $id::' . print_r( json_encode( $id ), true ) );
\Log::info( '-1 edit $this->FacilityRepository::' . print_r( $this->FacilityRepository, true ) );
// line above logged as : [2020-09-04 16:46:28] local.INFO: -1 edit $this->FacilityRepository::
// AND ERROR NEXT
$this->form = $this->FacilityRepository->getById($id)->toArray();
\Log::info( '-1023 $this->form ::' . print_r( json_encode( $this->form ), true ) );
$this->current_facility_id = $id;
$this->form['created_at'] = getCFFormattedDateTime($this->form['created_at']);
$this->emit('facility_opened',[ 'mode'=>'edit', 'id'=>$id ]);
$this->updateMode = 'edit';
}
In template edit link is defined as :
#foreach($facilityDataRows as $nextFacilityDataRow)
<tr>
<td class="text-right m-0">
<a wire:click="edit({{$nextFacilityDataRow->id}})"
class="p-1 a_edit_item_{{$nextFacilityDataRow->id}} a_link">
{{$nextFacilityDataRow->id}}
</a>
</td>
...
Why error and how to fix it ?
Modified # 2:
If I make
class Facilities extends Component
{
...
public $FacilityRepository;
}
I got error :
Livewire component's [admin.facilities] public property [FacilityRepository] must be of type: [numeric, string, array, null, or boolean]. Only protected or private properties can be set as other types because JavaScript doesn't need to access them.
I tried to declare method edit as :
public function edit( FacilityRepositoryInterface $facilityRepository, int $id)
{ // Did you mean this ?
...
}
I got error :
Call to a member function filterWithPagination() on null
on method filterWithPagination, which is used in render method, when I show listing of data.
Which way is correct ?
Modified # 3:
If to modify :
public function render(FacilityRepositoryInterface $facilityRepository)
{
I got error :
Declaration of App\Http\Livewire\Admin\Facilities::render(App\Repositories\Interfaces\FacilityRepositoryInterface $facilityRepository) should be compatible with Livewire\Component::render()
?
Modified # 4:
Opening page in mode I have 2 inputs with lazy defined, like
<dd class="horiz_divider_right_23" wire:model="form.title.lazy" x-data="{ name: '{{$form['name']}}'}">
<input
x-model="name"
x-on:blur="$dispatch('name', name)"
id="name"
class="form-control editable_field admin_control_input"
placeholder="Enter descriptive name"
autocomplete=off
>
#error('form.name')
<div class="validation_error">{{ clearValidationError($message,['form.'=>'']) }}</div> #enderror
</dd>
and when I edit some of fields on blur ervent I got the same error :
Call to a member function filterWithPagination() on null
with url in error description :
VM5783:1 POST http://local-hostels3.com/livewire/message/admin.facilities 500 (Internal Server Error)
where http://local-hostels3.com is my local hosting
Have I in some way to overrride message method ?
"laravel/framework": "^7.0",
"livewire/livewire": "^1.3",
Thanks!
protected and private properties DO NOT persist between Livewire updates. In general, you should avoid using them for storing state.
https://laravel-livewire.com/docs/properties/#important-notes
That being said, you can use dependency injection again, just pass whatever you need (FacilityRepositoryInterface in this case) as the first argument(s) of the edit method.
Same applies to the render method, so you can skip mount altogether.
Correction
The last bit in my original answer is wrong, you cannot use DI in the render method.
So for use in render, use the mount method and for use in edit, bring it via the first parameter. If render complains about not having it after usage of edit, save to the protected property inside edit as well.
Final code, that should work
class Facilities extends Component
{
protected $FacilityRepository;
public function mount(FacilityRepositoryInterface $FacilityRepository)
{
$this->FacilityRepository = $FacilityRepository;
}
public function render()
{
// use $this->FacilityRepository->...
}
public function edit(FacilityRepositoryInterface $FacilityRepository, $id)
{
$this->FacilityRepository = $FacilityRepository;
// rest of the edit method from your code
}
}
add boot method
public function boot(FacilityRepositoryInterface $FacilityRepository)
{
$this->FacilityRepository = $FacilityRepository;
}
I got a problem rendering nested view, here is what I'm trying to do
I changed your 'request' of HMVC (HMVC-GitHub or/and HMVC-Pattern) function into an Elements module
namespace Modules\Main\Libraries;
/**
* Elements
*
* Helps to build UI elements for the application
*/
class Elements extends \Phalcon\Mvc\User\Component
{
public function loadModule($path = '', $data = array()) {
$di = clone $this->getDI();
$dispatcher = $di->get('dispatcher');
$paths = explode('/', $path);
$data = is_array($data) ? $data : array($data);
// get controller name
if (isset($paths[0])) {
$controller = $paths[0];
}
// get action name
if (isset($paths[1])) {
$action = $paths[1];
}
// get params
if (isset($paths[2])) {
array_splice($paths, 0, 2);
$params = array_merge($paths, $data);
} else {
$params = $data;
}
if (!empty($controller)) {
$dispatcher->setControllerName($controller);
} else {
$dispatcher->setControllerName('index');
}
if (!empty($action)) {
$dispatcher->setActionName($action);
} else {
$dispatcher->setActionName('index');
}
if (!empty($params)) {
if(is_array($params)) {
$dispatcher->setParams($params);
} else {
$dispatcher->setParams((array) $params);
}
} else {
$dispatcher->setParams(array());
}
$dispatcher->dispatch();
$response = $dispatcher->getReturnedValue();
if ($response instanceof ResponseInterface) {
return $response->getContent();
}
return $response;
}
}
and I have 2 controllers:
namespace Modules\Main\Controllers;
class IndexController extends ControllerBase
{
public function indexAction()
{
$secondContent = $this->elements->loadModule('test/hello/json');
$this->view->setVar('secondContent', $secondContent);
}
}
and
namespace Modules\Main\Controllers;
use \Phalcon\Http\Response;
class TestController extends ControllerBase
{
public function indexAction()
{
}
public function helloAction($format='html', $param = 'empty')
{
$this->view->setVar('content', 'Hello this is test value "'.$param.'"');
$content = $this->view->getContent();
return (string)$content;
// return 'Hello this is test value "'.$param.'"';
}
}
my DI
$di['elements'] = function() {
return new \Modules\Main\Libraries\Elements();
};
Views files
IndexController::Index
<h1>Congratulations!</h1>
<p>You're now flying with Phalcon. Great things are about to happen!</p>
<p>Second content: {{ secondContent}}</p>
<p>HMVC: {{ elements.loadModule('test/hello/json', 'test') }}</p>
and HelloController::test
This is :: {{ content }}
expecting to get
Congratulations!
You're now flying with Phalcon. Great things are about to happen!
Second content: This is :: Hello this is test value "empty"
HMVC: This is :: Hello this is test value "test"
but it only rendering the HelloController (First call from IndexController::indexAction):
This is :: Hello this is test value "empty"
if I change IndexController::indexAction to
public function indexAction()
{
$secondContent = '';
$this->view->setVar('secondContent', $secondContent);
}
and TestController::helloAction to
public function helloAction($format='html', $param = 'empty')
{
$this->view->setVar('content', 'Hello this is test value "'.$param.'"');
$content = $this->view->getContent();
//return (string) $content;
return 'Hello this is test value "'.$param.'"';
}
the result that i get is (Second content is empty):
Congratulations!
You're now flying with Phalcon. Great things are about to happen!
Second content:
HMVC: Hello this is test value "test"
Any solution to solve this ?
Thanks,
Helman
Phalcon have built-it modules feature, you dont have to built your own module loader, you just need create module bootstrap that extend ModuleDefinitionInterface.
Just take a look this sample from phalcon multi module
https://github.com/phalcon/mvc/tree/master/multiple
this example below is taken from link above, This contain module bootstrap code.
<?php
namespace Multiple\Frontend;
class Module
{
public function registerAutoloaders()
{
$loader = new \Phalcon\Loader();
$loader->registerNamespaces(array(
'Multiple\Frontend\Controllers' => '../apps/frontend/controllers/',
'Multiple\Frontend\Models' => '../apps/frontend/models/',
));
$loader->register();
}
/**
* Register the services here to make them general or register in the ModuleDefinition to make them module-specific
*/
public function registerServices($di)
{
//Registering a dispatcher
$di->set('dispatcher', function () {
$dispatcher = new \Phalcon\Mvc\Dispatcher();
//Attach a event listener to the dispatcher
$eventManager = new \Phalcon\Events\Manager();
$eventManager->attach('dispatch', new \Acl('frontend'));
$dispatcher->setEventsManager($eventManager);
$dispatcher->setDefaultNamespace("Multiple\Frontend\Controllers\\");
return $dispatcher;
});
//Registering the view component
$di->set('view', function () {
$view = new \Phalcon\Mvc\View();
$view->setViewsDir('../apps/frontend/views/');
return $view;
});
$di->set('db', function () {
return new \Phalcon\Db\Adapter\Pdo\Mysql(array(
"host" => "localhost",
"username" => "root",
"password" => "secret",
"dbname" => "invo"
));
});
}
}
you can load module using this code below
$app = new \Phalcon\Mvc\Application();
$app->registerModules(array(
'frontend' => array(
'className' => 'Multiple\Frontend\Module',
'path' => '../apps/frontend/Module.php'
),
'backend' => array(
'className' => 'Multiple\Backend\Module',
'path' => '../apps/backend/Module.php'
)
));
I have Page object:
Page:
actAs:
Timestampable: ~
I18n:
fields: [name,content,attachment,course_name, course_description ]
actAs:
Sluggable: { fields: [name], uniqueBy: [lang, name], canUpdate: true }
columns:
...
is_course: { type: boolean }
course_name: { type: string(255) }
course_description: { type: string(500) }
And PageForm with embedded i18n forms:
//PageForm.class.php
public function configure()
{
...
$this->embedI18n(array('pl','en'));
$this->widgetSchema->setLabel('en', 'Angielski');
$this->widgetSchema->setLabel('pl', 'Polski');
}
Fields course_name and course_description aren't required while is_course is set to false. But if is_course is enabled validation should throw error that course_name and course_description are required.
I have readed "Symfony Advanced Forms" guide and some other posts, but I don't know should I use sfValidatorCallback in PageForm or PostValidator in PageTranslationForm? I tried to use sfValidatorCallback in this way:
//PageForm.class.php
public function configure()
{
...
$this->validatorSchema->setPostValidator(
new sfValidatorCallback(array('callback' => array($this,'checkCourses')))
);
}
public function checkCourses($validator, $values)
{
if($values['is_course'])
{
if($values['pl']['course_name'] && !$values['pl']['course_description'])
{
$error = new sfValidatorError($validator,'Required filed');
throw new sfValidatorErrorSchema($validator, array( _what_name_ => $error));
}
}
return $values;
}
But i don't know how to throw error in $values['pl']['course_description'] because Symfony API says _what_name_ should be an array of errors..
I'm really confused what is what during process of validating forms in symfony.
//Edit
I have done some changes in PageTranslationForm and now it looks like this...
//PageTranslationform.class.php
public function configure()
{
//......
$this->validatorSchema->setPostValidator(
new sfValidatorCallback(array('callback' => array($this,'checkCourses')))
);
//.....
}
public function checkCourses($validator, $values)
{
if($values['course_name'] && !$values['course_description'])
{
$error = new sfValidatorError($validator,'Required');
throw new sfValidatorErrorSchema($validator, array( 'course_description' => $error));
}
elseif(!$values['course_name'] && $values['course_description'])
{
$error = new sfValidatorError($validator,'Required');
throw new sfValidatorErrorSchema($validator, array( 'course_name' => $error));
}
return $values;
}
And it almost works but... this validator should be enabled only if in PageForm is_course is set to "true". How can I access field "is_course" from PageForm in checkCourses function in PageTranslationForm?
//SOLUTION
Thanks Jeremy, I used your idea and finally got this solution:
//PageForm.class.php
public function configure()
{
$this->embedI18n(array('pl','en'));
$this->widgetSchema->setLabel('en', 'Angielski');
$this->widgetSchema->setLabel('pl', 'Polski');
//.....
if($this->getObject()->getIsCourse())
{
foreach($this->getEmbeddedForms() as $key => $form)
{
$this->validatorSchema[$key]->setPostValidator(
new sfValidatorCallback(array('callback' => array($form,'checkCourses')))
);
}
}
}
//PageTranslationForm.class.php
//configure etc
public function checkCourses($validator, $values)
{
$errorSchema = new sfValidatorErrorSchema($validator);
if($values['course_name'] && !$values['course_description'])
{
$errorSchema->addError(new sfValidatorError($validator,'required'), 'course_description');
}
elseif(!$values['course_name'] && $values['course_description'])
{
$errorSchema->addError(new sfValidatorError($validator,'required'), 'course_name');
}
elseif(!$values['course_name'] && !$values['course_description'])
{
$errorSchema->addError(new sfValidatorError($validator,'required'), 'course_name');
$errorSchema->addError(new sfValidatorError($validator,'required'), 'course_description');
}
if (count($errorSchema))
{
throw new sfValidatorErrorSchema($validator, $errorSchema);
}
return $values;
}
Thanks for you advice, It works perfectly and I hope it would be helpful :)
This should be a post validator because you are using multiple values.
When validating via a post validator, you can throw the error in two different ways:
Globally
When the error is thrown globally, it will show up as part of sfForm::renderGlobalErrors. To throw globally, simply throw the error in the callback:
public function checkCourses($validator, $values)
{
if ($invalid)
{
throw new sfValidatorError($validator, 'Required.'); //global messages should be more specific than this
}
}
Locally
To have the error be rendered locally, throw a schema with an array, as you are doing. The keys of the array will determine the fields the errors are rendered on. This is probably want you want here.
public function checkCourses($validator, $values)
{
if ($invalid)
{
$error = new sfValidatorError($validator,'Required filed');
throw new sfValidatorErrorSchema($validator, array('course_description' => $error));
}
}