Testing livewire component does not transfer variable to route() method - laravel-livewire

I'm testing my livewire component. In my component view I have a named route
route('add_result_bulk', ['round' => $round])
When I run the test, it gives this error:
Missing required parameter for [Route: add_results_bulk] [URI: results/add/{round}/bulk] [Missing parameter: round]. (View: /var/www/resources/views/results/livewire/add-results.blade.php) (View: /var/www/resources/views/results/livewire/add-results.blade.php)
If I put a #dd($round) right in front of the route() call, it dumps the $round variable.
Why is it not picking it up in the route then? My route is defined as:
Route::get('results/add/{round}/bulk', [ResultsController::class, 'bulk'])->name('add_results_bulk');
This is my test:
Livewire::test(AddResultsWrapper::class, ['round' => $round])
->set('results', [
[
"id" => null,
"name" => "",
"reserve" => null,
"clean" => false,
], [
"id" => null,
"name" => "Driver 2",
"reserve" => null,
"clean" => false,
],
])->call('validate')
->assertHasErrors(['results.0.name']);
}
$round is declared as a public property in my component. When I access the page through the browser, the link shows up and works correctly.
This is my view:
<div>
<form wire:submit.prevent="">
#csrf
<input type="hidden" name="form_type" value="single">
<div class="max-w-7xl mx-auto py-10">
<div class="bg-white overflow-hidden shadow-xl xl:rounded-lg overflow-x-auto">
<div class="p-6 pb-0 px-8 bg-white grid grid-cols-12 gap-x-4 gap-y-1 form-container min-w-1200px">
<div class="col-span-12">
<b>Please note:</b> When adding multi-lobby results, results need to be added using the <a
href="{{ route('add_results_bulk', ['round' => $round]) }}"
class="no-underline hover:underline text-blue-700">Bulk Loader</a>. Loading the results
here, will cause positions to be calculated incorrectly.
</div>
</div>
<div
class="p-6 px-8 bg-white border-b border-gray-200 grid grid-cols-12 gap-x-4 gap-y-1 form-container min-w-1200px">
...

Related

Livewire: create and update in two tables

First to say that I am a newbie in Laravel. I have started to develop the frontend of an APP in Laravel Nova with Livewire. The problem is that before, I was able to do operations in my controller but I don't know how to do it in the Livewire resource.
I have a simple form:
<form wire:submit.prevent="submit" class="rounded px-8 pt-6 pb-8 mb-4">
<input type="text" placeholder="Introduzca código" wire:model="code" class="md:inline-block ktext-gray-700 text-sm font-bold mb-2">
<br>
#error('code')
{{$message}}
#enderror
<br>
<input type="text" placeholder="Introduzca tipo" wire:model="access" class="md:inline-block ktext-gray-700 text-sm font-bold mb-2">
<br>
#error('access')
{{$message}}
#enderror
<br><br>
<x-jet-button type="submit">Crear Asistencia</x-jet-button>
</form>
Which inserts three data into a table:
public function submit()
{
//validate
$this->validate();
Attendance::create([
'code' => $this->code,
'ip' => $_SERVER['REMOTE_ADDR'],
'access' => $this->access,
]);
But I want that in a second table, when the "code" field matches (it is in both tables and is a boolean) the code is update in this second table.
How would you do it? Thanks in advance.
Im not a pro laravel but a think any like the code below can help with yout question:
// If you have more than one register with same code:
$infos = SecondTableModel::where('code', $this->code)->get();
foreach ($infos as $info) {
SecondTableModel::find($info->id)->update([
'ip' => $_SERVER['REMOTE_ADDR'],
'access' => $this->access,
// ... OTHERS FIELSDS
]);
}
// If you have only one register on the second table with the same code
SecondTableModel::where('code', $this->code)->first()->update([
'ip' => $_SERVER['REMOTE_ADDR'],
'access' => $this->access,
// ... OTHERS FIELSDS
]);

How to hook event of validation error in livewire 2?

Reading how validations work at https://laravel-livewire.com/docs/2.x/input-validation
I did not find if there is a way in livewire 2 when I have
validation error to hook event as I need to send dispatchBrowserEvent event
to show message with toastr ?
My form is rather big and val;idation field can be out of screen
and I want to pay attention of user that there are validation errors...
Updated Block # 1:
You propose to get rid of livewire validate method and use laravel validate methods, which are written here
https://laravel.com/docs/8.x/validation, like :
$validator = Validator::make($request->all(), [
'title' => 'required|unique:posts|max:255',
'body' => 'required',
]);
if ($validator->fails()) {
...
In my conmponent I define all variables under 1 variable $form :
public $form
= [
'name' => '',
'state_id' => '',
...
public function firstStepSubmit()
{
$rules = [
'name' => 'required',
'state_id' => 'required',
];
$validation = Validator::make( $this->form, $rules, Hostel::getValidationMessagesArray());
$failed = $validation->fails();
if ($failed) {
$errorMsg = $validation->getMessageBag();
$focus_field = array_key_first($errorMsg->getMessages());
$this->dispatchBrowserEvent('PersonalPageMessageWarning', [
'title' => 'Personal Hostel',
'message' => 'Your hostel has invalid data !',
'focus_field' => str_replace('form.', '', $focus_field ),
]);
$validation->validate(); // What for this line ? Looks like we really need it ?
return;
}
$this->currentStep = 2;
} // public function firstStepSubmit()
// What for is
$validation->validate();
line inside of failed block ? Do we really need it ?
2) moving to laravel original methods sems for me as step back... Are there some livewire hooks/methods for it ?
Thanks in advance!
I got a solution to that. Let's say you have next:
// in blade
<div class="col-md-6 flex-row">
<label for="name">Name</label>
<input id="name" type="text" class="form-control flex-row" wire:model="name">
#error('name') <span class="error" style="color: #ff0000">{{ $message }}</span> #enderror
</div>
<div class="col-md-6 flex-row">
<label for="last_name">Name</label>
<input id="last_name" type="text" class="form-control flex-row" wire:model="last_name">
#error('last_name') <span class="error" style="color: #ff0000">{{ $message }}</span> #enderror
</div>
.... // all the rest of elements
//in component
public $name, $last_name;
// the rest of properies
public function store()
{
$validation = Validator::make([
'name' => $this->name,
'last_name' => $this->last_name,
.....
], $this->rules());
if ($validation->fails()) {
$errorMsg = $validation->getMessageBag();
$this->dispatchBrowserEvent('focusErrorInput',['field' => array_key_first($errorMsg->getMessages())]);
$validation->validate();
}
//... other code
now, if validation fails, the above validation checks dispatch and event with all the non-validated fields and in the blade's script tag handle the element focus in order of the error bag field. So, if the element is out of windows this will be focused
<script>
window.addEventListener('focusErrorInput', event => {
var $field = '#' + event.detail.field;
$($field).focus()
})
</script>

Unit Testing Login Vue Jest ValidationProvider

I am new to Jest and I am trying to mock the store, an action and to assert that the method was indeed called. Basically I want to check the login function.
I cannot query the button because I am retrieving only a part of the component and I don't know what I'm missing.
Where my Vue component looks like:
<ValidationObserver id="observer" v-slot="{ invalid }" :class="['w-full h-full']">
<form class="flex flex-col justify-around h-full" #submit.prevent="onSubmit">
<ValidationProvider v-slot="{ errors }" name="accessCode" :class="['w-full py-6']">
<sd-field :class="['sd-field_secondary', { ['sd-invalid ']: errors && errors.length > 0 }]">
<label for="email">{{ $t("form.access-code") }}</label>
<sd-input v-model="accessCode" type="accessCode" />
<span class="sd-error">{{ errors[0] }}</span>
</sd-field>
</ValidationProvider>
<div class="btn-group-y">
<button class="btn btn-fixed btn-blueDark" type="submit">
<span>{{ $t("account.login") }}</span>
</button>
<!-- <button class="btn btn-link" type=""> //TODO: temporary hide
<span>{{ $t("account.forgot_id") }}</span>
</button> -->
</div>
</form>
</ValidationObserver>
***MY TEST FILE***
import login from "../../pages/index/login";
import Vuex from "vuex";
import { mount, createLocalVue } from "#vue/test-utils";
const localVue = createLocalVue();
localVue.use(Vuex);
describe("Login form", () => {
it("calls the login action correctly", () => {
const loginMock = jest.fn(() => Promise.resolve());
const store = new Vuex.Store({
actions: {
onSubmit: loginMock,
},
});
const wrapper = mount(login, { localVue, store, stubs: { ValidationObserver: true } });
console.log(wrapper.html());
// wrapper.find("button").trigger("click");
// expect(loginMock).toHaveBeenCalled();
});
});
console.log(wrapper.html()) returns only a part of component
<div class="h-full w-full"><validationobserver id="observer"
class="w-full h-full"> </validationobserver>
</div>
With a warning also:
> console.error node_modules/vue/dist/vue.runtime.common.dev.js:621
> [Vue warn]: Unknown custom element: <ValidationObserver> - did you register the
> component correctly? For recursive components, make sure to provide the "name" option.
> found in
> ---> <Anonymous>
> <Root>
I would like to understand how this works, I have tried to stub and other ways found but with no success.
I can get rid of the warning by adding the stubs: { ValidationObserver: true } to mount but I actually need to access the elements inside.
Thank you!

How to autofocus an input field in semantic-ui-react?

I'm having a difficult time autofocusing an input field with semantic-ui-react. The documentation doesn't seem to include an autoFocus prop and the focus prop doesn't place the cursor inside the input field as would be expected.
<Form onSubmit={this.handleFormSubmit}>
<Form.Field>
<Form.Input
onChange={e => this.setState({ username: e.target.value })}
placeholder='Enter your username'
fluid />
</Form.Field>
</Form>
EDIT: This code works:
<Form onSubmit={this.handleFormSubmit}>
<Form.Input
onChange={e => this.setState({ username: e.target.value })}
placeholder="Enter your username"
autoFocus
fluid />
</Form>
The focus prop is purely to add a focus effect on the input's appareance, it does not actually set the focus.
Any props unused by Semantic are passed down to the DOM element, so if you set an autoFocus prop, it should go down to the input.
However, as explained in the Form documentation:
Form.Input
Sugar for <Form.Field control={Input} />.
So your code should rather be:
const yourForm = (
<Form onSubmit={this.handleFormSubmit}>
<Form.Input
onChange={e => this.setState({ username: e.target.value })}
onSelect={() => this.setState({ usernameErr: false })}
placeholder="Enter your username"
error={usernameErr}
iconPosition="left"
name="username"
size="large"
icon="user"
fluid
autoFocus
/>
</Form>
)
Note that this only works if you want the focus to happen right when the wrapper component is mounted. If you want to focus the input after it has been mounted, you have to use a ref and call the focus() method on it, just as showed in the documentation, like so:
class InputExampleRefFocus extends Component {
handleRef = (c) => {
this.inputRef = c
}
focus = () => {
this.inputRef.focus()
}
render() {
return (
<div>
<Button content='focus' onClick={this.focus} />
<Input ref={this.handleRef} placeholder='Search...' />
</div>
)
}
}
Hope that helps!
I would have assumed that semantic UI would pass all unknown props to the root element, the input. So if it does, you should be able to add the autoFocus attribute to it, if not, you will have to control which input is being focused in your state.
<Input placeholder='Search...' focus={this.state.focusedElement === "search"}/>
In order to tell the input field to focus, you need to create a reference (ref) to the input field as follows:
import React, { useState, useRef } from 'react';
import { Input, Button } from 'semantic-ui-react';
const SearchInputExample = () => {
const [searchValue, setSearchValue] = useState('');
// Create reference to the input field
const searchRef = useRef(null);
const handleSearchValueChange = event => setSearchValue(event.target.value);
return (
<div>
<Input
placeholder="Search..."
// Assign the ref created to a ref attribute
ref={searchRef}
value={searchValue}
onChange={handleSearchValueChange}
/>
<Button
onClick={() => {
setSearchValue('');
// Use the ref assigned to put the focus inside the input
searchRef.current.focus();
}}
>
Clear search (and focus)
</Button>
</div>
);
};
export default SearchInputExample;
You can read more about the useRef() hook here

Sync the states of multiple instances of same Ember Component

Here's the situation:
I wrote a component facility-search which searches through some fixture data. I put multiple instances of {{facility-search}} on same template (Tab Pages). This component has some input boxes where we can write search keywords. I want to observe change in value of input box and update the same to another instance of component so that both of them will be in sync.
This is what I'm doing in components/facility-search.js
import Ember from 'ember';
import Em from 'ember';
var FacilitySearchComponent = Ember.Component.extend({
// Tag name appears in HTML
tagName: 'div',
// RandomNumber
searchBoxId: null,
// All Facilities
allFacilities: null,
// Search Values
textFacility: "",
textCountry: "",
textSpecies: "",
// Text Input Ids needed for <label for="id"/>
// Elements in multuple components should not have same id
textFacilityId: 'text-facility',
textCountryId: 'text-country',
textSpeciesId: 'text-species',
// Initialize Ids
randomNumber: function(){
this.set('searchBoxId',(Math.floor(Math.random() * 100) + 1));
this.set('textFacilityId', this.get('textFacilityId') + "-" + this.get('searchBoxId'));
this.set('textCountryId', this.get('textCountryId') + "-" + this.get('searchBoxId'));
this.set('textSpeciesId', this.get('textSpeciesId') + "-" + this.get('searchBoxId'));
}.on('init'),
// When component is inserted
didInsertElement: function() {
this.set('filteredFacilities', this.get('allFacilities'));
},
// Observe Search Values
watchForFilterChanges: function() {
this.filterResults();
}.observes('textFacility', 'textCountry', 'textSpecies'),
// Filter Data
filterResults: function() {
var facilities = // Some mechanism to filter data
self.sendAction('updateFacilities', facilities);
}.on('allFacilities'),
actions: {
clearSearch: function() {
this.set('textFacility', null);
this.set('textCountry', null);
this.set('textSpecies', null);
this.filterResults();
},
}
});
export default FacilitySearchComponent;
This is my templates/components/facility-search.hbs
<div class="card">
<div class="card-content directory-search">
<div class="card-title grey-text text-darken-3">
<h4>Search Facilities</h4>
<h4><small class="teal-text">{{filteredFacilities.length}} total</small></h4>
</div>
<form {{action "textSearch" this on="submit" data="lol"}}>
<div class="row">
<div class="input-field col s12">
<label for="{{textFacilityId}}">Search by facility</label>
{{input value=textFacility type="text" id=textFacilityId label="Facility Name"}}
</div>
<div class="input-field col s12">
<label for="{{textCountryId}}">Search by country</label>
{{input value=textCountry type="text" id=textCountryId label="Facility Country"}}
</div>
<div class="input-field col s12">
<label for="{{textSpeciesId}}">Search by species</label>
{{input value=textSpecies type="text" id=textSpeciesId label="Facility Species"}}
</div>
</div>
</form>
<a {{action 'clearSearch'}} class="waves-effect waves-light btn"><i class="material-icons right">clear_all</i>clear search</a>
</div>
</div>
This is my controllers/map.js
import Ember from 'ember';
import pagedArray from 'ember-cli-pagination/computed/paged-array';
export default Ember.Controller.extend({
// Facility to be shown in modal
selectedFacility: null,
// Facilities to be shown in search
filteredFacilities: [],
// Initialize filteredFacilities to model
initializeFacilities: function() {
this.set('filteredFacilities', this.get("model"));
}.observes('model'),
actions: {
showFacilityInModal: function(facility){
this.set('selectedFacility', facility);
},
updateFacilities: function(facilities){
this.set('filteredFacilities', facilities);
},
}
});
This is routes/map.js
import Ember from 'ember';
export default Ember.Route.extend({
model: function() {
return this.store.find('facility');
},
});
And this is how I'm using component in templates/map.hbs
{{facility-search allFacilities=model updateFacilities='updateFacilities'}}
I learned that if we put component multiple times; it will have complete new instances. So updating variables textFacility and others cannot be observed in another instance of same component. But I want to update those values in another instance as well. Any idea how we can sync the states of multiple instances of same component?
If I understand you question you want to share values between all component so if you change it in one, it changes in the other.
You can do this:
text : {
facility: "",
country: "",
species: "",
}
instead of
textFacility: "",
textCountry: "",
textSpecies: "",
Declaring it as an object means it will be shares across all component instances like a static variable.
I found a workaround !! Not sure if its a right way of doing it.
I put variables textCountry in controllers/map.js and passed it to component as follows:
{{facility-search textCountryComponentSpec=textCountry allFacilities=model updateFacilities='updateFacilities'}}
where textCountryComponentSpec holds that value in component. Then I observed changes in textCountryComponentSpec in component and update textCountry in controller. Since it is passed to components it reflects the changes.
If you know a better way please post.