How can test watcher in vue with vitest and vue-test-utils? - javascript

I am trying to create a unit test that covers this portion of my Vue component code.
const length = ref(sizes.value[0] ? sizes.value[0].value : 0)
watch(
() => length.value,
(newLength: number) => {
current.value = 1
emits('update:length', newLength)
}
)
The length is modified by a simple select
<select name="length" v-model="length">
<option v-for="opt in sizes" :key="opt.value" :value="opt.value">
{{ opt.label }}
</option>
</select>
The test I wrote is this
it('should pagination emits events', async () => {
await wrapper.vm.setCurrent(5)
wrapper.vm.$emit('update:current', 5)
expect(wrapper.vm.currentBlock).toBe(4)
wrapper.vm.length = 20
wrapper.vm.$emit('update:current', 20)
})
The test is successful and the hatching report tells me that all the watch contents are covered except the watch row itself.
I use vitest, vue/test-utils and all typesafe(eslint for typescript enabled)
How can I get the watch to show as tested as well?

Related

ReactJs doesn't render second map function but when i update the file it does

So, I'm fetching data from an API and using states. At the beginning, I don't get data from API and render the page with using the states with the data I wrote in the file. (I'm trying to say that I'm using static data at the first load). It works well. No problem at all. Then, when I get an input from user I connect with the API, do all the fetching and stuff and update the states. Everything seems normal. Data is received from API, states are updated, no error on console, first map function is rendered etc. but the second map function isn't. But surprisingly, when I change anything in the file and save it (you know that live server updates the page but doesn't reload), it applies the changes that I've done, and it also renders the second map function with using data I received earlier.
My first map function is this:
<div id="hourlyForecast">
{ weatherData.hourly.map((item, index) => {
/* ----- I'm getting a long string from API and these 2 lines are to get the part that i need from that string ------ */
let startFrom = item.dt_txt.length - 8;
let iNeed = item.dt_txt.toString().substr(startFrom, 5);
return (
<div className="hourly" key={index}>
<div className="hour">{iNeed}</div>
<div className="image">
<img
src={`/icons/${item.weather[0].icon}.png`}
alt="weather-icon"
/>
</div>
<div className="degrees">
{Math.round(item.main.temp)}°C
</div>
<div className="wind">
<img
src="wind.png"
alt="wind-icon"
style={{ transform: `rotate(${item.wind.deg}deg)` }}
/>{
{item.wind.speed} m/s
</div>
</div>
)
})
}
</div>
It is working, I guess. After that, I have this second map function:
<div id="daily">
{weatherData.daily.map((item, index) => {
return (
<div className="daily" key={index}>
<div className="day">
/* ------ Api gives me timestamp and this function returns me the day in human words :) ----- */
{giveMeDay(item.dt * 1000)}
</div>
<div className="dailyDegrees">
{Math.round(item.temp)}°C
</div>
<div className="dailyDesc">
{item.desc}
</div>
<div className="img">
<img src={`./icons/${item.icon}.png`} alt="weather-icon" />
</div>
</div>
);
})
}
</div>
It is also working, it should. I mean, they are not that complex.
So, all with that, here are what happens:
At first load I use static data, and it renders the second map function
(IMAGE) Rendered component with static data
When I enter an input it triggers the API Call, and it should re-render, but it does not (IMAGE) Empty Component even though API Call works
But when I change anything in the file after the API Call and save it, live server updates and the second map function is rendered. Let's say I change "°C" in the second map function with "°F" and save it. Then, everything works. (IMAGE) File updated without page being reloaded
I guess that's all I can say. I just don't understand the problem. Surely would appreciate any help.
Here is the part that I do the API stuff: (It can be a mess 'cause gotta do 3 API calls and didn't want to use async function due to my lack of experience with it)
var datam = {};
const work = (e) => {
e.preventDefault();
try {
fetch(
`${ApiUrl}weather?q=${e.target.cityName.value}&appid=${ApiKey}&units=metric&lang=en`
)
.then((data) => {
return data.json();
})
.then((gelen) => {
console.log(gelen);
if (gelen.cod === 200) {
datam = {
degrees: Math.round(gelen.main.temp),
description: gelen.weather[0].description,
feels_like: gelen.main.feels_like,
city: `${e.target.cityName.value}, ${gelen.sys.country}`,
min: gelen.main.temp_min,
max: gelen.main.temp_max,
icon: gelen.weather[0].icon,
lat: gelen.coord.lat,
lon: gelen.coord.lon,
};
} else {
alert("Couldn't get the data");
}
})
.then(() => {
console.log(datam);
fetch(
`${ApiUrl}forecast?lat=${datam.lat}&lon=${datam.lon}&units=metric&appid=${ApiKey}&lang=en`
)
.then((fivedays) => fivedays.json())
.then((veri) => {
console.log(veri);
datam.hourly = [];
if (veri.cod === "200") {
for (let i = 0; i < 8; i++) {
datam.hourly[i] = veri.list[i];
}
console.log(datam);
}
})
.then(() => {
datam.daily = [];
fetch(
`${ApiUrl}onecall?lat=${datam.lat}&lon=${datam.lon}&exclude=current,hourly,minutely,alerts&units=metric&appid=${ApiKey}&lang=en`
)
.then((donus) => donus.json())
.then((yanit) => {
console.log(yanit);
for (let i = 0; i < 6; i++) {
datam.daily[i] = {};
datam.daily[i]["temp"] = yanit.daily[i + 1].temp.day;
console.log("1");
datam.daily[i].desc =
yanit.daily[i + 1].weather[0].description;
datam.daily[i].icon = yanit.daily[i + 1].weather[0].icon;
datam.daily[i].dt = yanit.daily[i + 1].dt;
}
});
})
.then(() => {
console.log(datam);
// ------------ weatherData is the main state I'm using -----------
setWeatherData(datam);
// ------------ searched state is the important for only the first load. If they do the search i change what is being rendered -----------
setSearched(true);
});
//
});
} catch (error) {
alert(error);
}
};
The problem is this here: (VIDEO) Everything Works but doesn't appear until VS Code recompile

Svelte: Data flow between components - how to pass state of children to parent(s)

I haven't been using svelte for very long but I can see a couple ways to go about doing what I need, but they are all feel "un-svelte", so I'm hoping there's a more obvious solution I'm missing. Here is the layout code before I go on about what I need it to do.
<ControlGroup>
<ControlLabel label='Language'>
<Select options={lang_options} on:select={(e) => $settings.lang = e.detail} />
</ControlLabel>
<ControlLabel label='Visibility'>
<Select options={...} on:select />
<Select options={...} on:select />
</ControlLabel>
<ControlLabel label='Some other setting'>
<Select options={...} on:select />
<Select options={...} on:select />
</ControlLabel>
</ControlGroup>
First, the deepest nested elements (selects), need to update one or many stores. So far so good, the on:select directive makes quick work of that.
Second, the selects all have a defaultoption, which if none is provided will be options[0]. The <ControlGroup> needs to be aware if the selects within itself are default. Because when they aren't, a button to reset the selects should be visible, and clicking that should run the reset() function in the selects.
More broadly, the <ControlGroup> and <ControlLabel> components are there to structure the layout, they don't have any complicated logic. They use svelte slots to pass down components. Worth noting, I wrote the multiselects so it's not an opaque library and I can add stuff to them if that's part of the solution.
So without having a reactive let for every single control and then passing lists of those to the controlgroups, what can I do to pass the currently selected value of each select to their parents?
Thank you !
Edit, Current solution:
Top level
<ControlGroup name='General Settings'>
<ControlLabel label='Language'>
<Select id='lang' options={lang_options} selected={lang_current} on:select={(e) => $settings.lang = e.detail} />
</ControlLabel>
<ControlLabel label='Visibility'>
<Select id='visi' options={yesno} on:select={(e) => $settings.visibility = e.detail} />
</ControlLabel>
<ControlLabel label='Show Item Level'>
<Select id='ilvl' options={yesno} on:select={(e) => $settings.showItemLevel = e.detail} />
</ControlLabel>
</ControlGroup>
ControlGroup.svelte
let isDefault = true;
const checkDefault = () => {
for (var id in controls) {
if (controls[id].value === controls[id].defaultoption) continue;
return false;
}
return true;
};
const reset = () => {
for (var id in controls) controls[id].reset();
};
if (group) setContext(group, {
onmount: (id, reset, defaultoption) => controls[id] = {reset, defaultoption},
ondestroy: (id) => delete controls[id],
onselect: (id, value) => {
controls[id].value = value;
isDefault = checkDefault();
}
})
Select.svelte
const groupContext = getContext(group);
if (groupContext) groupContext.onmount(id, reset, defaultoption?.value);
$: if (groupContext) groupContext.onselect(id, selected?.value);
onDestroy(() => {
if (!groupContext) return;
groupContext.ondestroy(id);
});
I do not fully understand your question. But below I show how you can use get- and setContext to pass a value to the parent.
Parent.svelte:
import { setContext } from "svelte";
setContext("aParentId", (childId, value) => {
// function to show child notification in the parent
console.log(childId, value);
}
Child.svelte:
import { getContext } from "svelte";
const notifyParent = getContext("aParentId");
// notify parent if value changes
$: if (value) notifyParent(aChildId, value)

Dynamic Angular Forms on User Interaction

I have a dropdown that the user uses to switch between different Angular forms.
HTML
<form [formGroup]="evalForm">
<mat-form-field appearance="fill">
<mat-label>Select Analysis Group</mat-label>
<mat-select (selectionChange)="groupSelected($event)">
<mat-option *ngFor="let item of groupDropdown | async" [value]="item.value">{{item.text}}</mat-option>
</mat-select>
</mat-form-field>
<ng-container *ngFor="let groups of groupSections;">
<input type="text" [formControlName]="groups?.name">
</ng-container>
</form>
What should the Angular look like to get the reactive forms working when someone selects an item in the dropdown?
What groupSelected() currently does (basic non-working pseudo-code):
groupSelected(selectedItem) {
// 1) Grabs the FormControlNames from the DB
const formControlNamesList = getItemsFromDB(selectedItem);
// 2) Sets FormControlNames for DOM
// [{groups:{name: 'keyOne'}}, {groups:{name: 'keyTwo'}},{groups:{name: 'keyThree'}}]
this.groupSections = setGroupSections(formControlNamesList);
// 3) Creates a formControlNamesList Object in the form
// {"keyOne": FormControl, "keyTwo": FormControl, "keyThree": FormControl}
const formObj = formatForAngularForms(formControlNamesList);
// 4) Set the Reactive Form
this.evalForm = this.fb.group(new FormGroup(formObj));
}
formatForAngularForms(formControlNamesList) {
const formObj = {};
for(let i=0;i<formControlNamesList.length;i++) {
formObj[formControlNamesList[i].name] = new FormControl(null,[Validators.required]);
}
return formObj;
}
At the moment, the console explodes with errors about how it can't find the FormControlName and I suspect it has to do with the timing of when everything gets rendered, but I'm not entirely sure if that's it or not. I need the forms to render completely new forms each time a new value selected from the dropdown. The UI will always be dynamic and each time the dropdown changes the input FormControlNames could be different based on what the DB sends.
I've read that maybe FormArray might be something use but I can't find an example anywhere that matches what I'm looking to do.
Shouldn't you wait for the answers from backend before proceeding to build the form? If getItemsFromDB() returns an observable you could do async/await:
async groupSelected(selectedItem) {
const formControlNamesList = await getItemsFromDB(selectedItem);

onChange is appending data multiple times when checking for a string character

I'm trying to append a drop down value to a textarea field, it works as a mention. So if there is an "#" call the dropdown and user will select a user, once selected the dropdown show hide it self, and the user should be able to append their comment data.
the issue im having is that setCommentBody is appending the selectedUser multiple times/ on every comment change in the
textarea
My objective is
check for # symbol (which its already doing)
render drop down once # symbol is called( which is already doing)
once a value is selected hide drop down and add their comment (dropdown hides only if # symbol is removed, which it should hide after a value is selected)
The mention should pretty much work exactly how stackoverflow comment section has it.
this is what i have so far
const [comment_body, setCommentBody] = useState("");
const [mentionedUser, setMentionedUser] = useState(false);
const commentChange = (comment) => {
console.log("this is the selected User", selectedUser); // selected user is a reducer initalState
// call this condition if # is mentioned once
if (comment.includes("#")) {
setMentionedUser(true); // render dropwdown
setCommentBody(comment.concat(selectedUser)); // append the selected user like #barnowl with the respective comment data
} else {
console.log("can you see me");
setMentionedUser(false); // hide dropdown
setCommentBody(comment);
}
setGifUrl(""); // irrelvant to the problem ignore
};
PostItemContainer
<CommentForm
commentChange={(e: any) => commentChange(e.target.value)}
comment_body={comment_body}
onSubmit={(e) => commentSubmit(e, post.id)}
gifUrl={selectGif}
isGif={gifUrl}
mentionedUser={mentionedUser}
/>;
CommentForm (snippet)
....
<OurTextField
type="gif-commentfield"
selectedUser={selectedUser}
comment_body={props.comment_body}
commentChange={props.commentChange}
setGifSelected={() => setGifSelected(true)}
/>;
{
props.mentionedUser && (
<select
value={selectedUser}
onChange={(e) => setSelectedOptionValue(e.target.value)}
name="mentionedUsers"
>
{mentionUsers.map((item, key) => (
<option key={key} value={item.display}>
{item.display}
</option>
))}
</select>
);
}
A minimal working example of my issue
https://codesandbox.io/s/practical-ives-lfckq?file=/src/App.js
If I understand the use case correctly, the name insertion logic should be triggered when the selection field is changed:
const commentChange = (comment) => {
setCommentBody(comment);
if (comment.includes("#")) {
showMentionList(true);
}
};
const selectedUserChange = (user) => {
setSelectedUser(user);
setCommentBody(commentBody.concat(user).replace("#", ""));
showMentionList(false);
};
...
<select
value={selectedUser}
onChange={(e) => selectedUserChange(e.target.value)}
name="mentionedUsers"
>
{users.map((item, key) => (
<option key={key} value={item}>
{item}
</option>
))}
</select>
Does this work for you? See: https://codesandbox.io/s/broken-haze-qwb41?file=/src/App.js

Passing html element value to composeWithTracker in React

This Meteor code uses React. When user fills in an input box with id myVal, click a button. The input box value gets sent to the server via a method, the server updates the collection vehicles.
It then needs to take that input from user and use that as query to collection.findOne in myfile.jsx. It failed to pass the user input myVal from html input element.
How can it be done? Thanks
// -------------------- myfile.jsx -------------------
const renderWhenData = ( cars ) => {
if ( cars ) {
return <span>{ cars.description }</span>;
}
};
const Info = ( { cars } ) => (
<p>{ renderWhenData( cars ) }</p>
);
const composer = (props, onData) => {
const subscription = Meteor.subscribe('vehicles');
if (subscription.ready()) {
let myVal = document.getElementById('myVal').value;
console.log(myVal); // <------------------ nothing is printed out
const cars = Vehicles.findOne({name: myVal});
onData(null, {cars});
}
};
const Container = composeWithTracker(composer)(Info);
ReactDOM.render(<Container />, document.getElementById('react-info'));
// --------------------- events.js -----------------------
document.getElementById('startButton').addEventListener('click', () => {
const myVal = document.getElementById('myVal').value;
Meteor.call('startInfo', myVal); // <---------- updates server collection
});
<!--main.html-->
<head>
</head>
<body>
<form action="submit">
<input type="text" id="myVal">
<div id="react-info"></div>
</form>
<footer>
<button id="startButton">START</button>
</footer>
</body>
The question is a bit academic. There are better ways to handle this in React. But, since you are using Meteor, this can be done with some Tracker operations.
First define a tracker dependency:
export const textboxDep = new Tracker.Dependency;
Whenever the textbox value changes, trigger the changed event.
textboxDep.changed();
Within the composer, use
textboxDep.depend();
If the composer is written well, when the tracker dependency is invalidated, the whole functional container component runs again. And you should see the value of the textbox there.
Right now, when the value of the textbox is changed, since it is not reactive, the container does not re-render itself.

Categories

Resources