Unit testing css with angular - html

Is there any way in which I could test say the height css property of an element?
I have a function that changes the height of a div and it would be nice to check in the tests that the function operates correctly...
ie:
<div #myDiv>Hello world</div>
#ViewChild('myDiv') myDiv: ElementRef;
myFunct() {
this.myDiv.nativeElement.style.height = 500;
}
it('should increase div size', () => {
// Here is where I get stuck...
});
Update
Implementing a test such as:
it('should return correct height of dropdown when initialised', () => {
component.myFunct();
expect(component.dropdown.nativeElement.style.height).toBe(34);
});
results in a test failure with message:
Expected '' to be 500.

Something like this...
Exposed the myDiv publicly
Setup the testbed for the component (usually added by default)
Add
component.myFunct();
expect(component.myDiv.nativeElement.style.height).toBe(500);

Related

Angular unit test failure after implementing material modules

So I've created some unit test cases inside a component which are all working fine and as intended. However I've got those console errors which were saying:
NG0304: 'mat-card' is not a known element
I've tried to fix those console errors by importing all required material modules and the BrowserAnimationsModule. Now there are no console errors anymore, but 4 of my test are failing now with the following error:
TypeError: Cannot read property 'nativeElement' of null
They all have the same structure but various values.
This is my HTML structure:
<div *ngIf="allowed" class="header">
//material components without *ngIf
<div *ngIf="!show" class="hideDiv">Nothing here</div>
<div *ngIf="show" class=showDiv>Even more material components without *ngIf</div>
</div>
Those are the unit tests which are working:
it('should render div only when allowed is true', () => {
component.allowed= true;
fixture.detectChanges();
const debugElement: DebugElement = fixture.debugElement.query(By.css('.header')).nativeElement;
expect(debugElement).toBeDefined();
});
Those are the unit tests which are not working (and getting null as value for some reason):
it('should render div only when show is true', () => {
component.allowed= true;
component.show= true;
fixture.detectChanges();
const debugElement: DebugElement = fixture.debugElement.query(By.css('.showDiv')).nativeElement;
expect(debugElement).toBeDefined();
});

Wrong colors displayed after React renders div to DOM

I was writing some code to map random colors to cells in row.
const COLORS = ['blue', 'green', 'orange', 'red', 'purple', 'yellow'];
const createRandomColors = () => {
const randomColors = [];
for (let i = 0; i < COLORS.length; i++) {
const randomColor = COLORS[Math.floor(Math.random() * COLORS.length)];
randomColors.push(randomColor);
}
return randomColors;
}
const App = () => {
const row = useMemo(createRandomColors, []);
console.log(row);
const cells = useMemo(() => row.map((cell, cellIndex) =>
<div key={cellIndex} style={{ backgroundColor: cell }}>{cellIndex}</div>
), [row]);
cells.forEach(cell => console.log(cell.props.style.backgroundColor));
return (
<div className="app">
<div className="row">
{cells}
</div>
</div>
);
};
export default App;
The problem is that after rendering div elements they have completely different inline style background-color that was specified when mapping divs.
Please see CodeSandBox and take a look at console log and real results rendered.
Why is this happening?
The reason this appears wonky is related to using <StrictMode> in your index.html file. If you remove <StrictMode> you'll see your code works the way you expect. But don't do that, you really do have a bug.
React wants functional components to be idempotent, meaning they do not have side effects. To help you catch side-effects, React will call your code twice back to back when it renders. see strict mode By doing this, it helps uncover subtle issues like the one you're currently experiencing.
One solution is to create the random colors once using useEffect(). Another is to generate the colors outside the functional component.
UPDATE
Please mark the answer as 'accepted' if it solves your issue. You are correct. useMemo will save the computation so it will not be re-computed unless dependencies change. However, react is purposely calling your code twice (in debug mode only) to help you catch unintentional side effects in your classes or hooks. When using strict mode, it's as if you have two of the component instead of one. i.e.
/* StrictMode in debug */
<StrictMode>
<App/>
</StrictMode>
/* ... be like this: */
<>
<App/>
<App/>
</>
If you (temporarily) remove the <StrictMode> tag you'll see your code works as expected. And if you add code that causes your component to render again (e.g. a click counter) your useMemo should prevent the cells from being regenerated each render.
Add a console log to print every time createRandomColors() is called. Since your code is being called twice, you should see the debug log appear twice, but you don't. Why not? React surpasses the console.log the 2nd time it calls your code.
At the top of your code (line 3) add const log = console.log, then replace everywhere you use console.log with just log and you'll have the full picture of what's occurring.
Keep experimenting. We've all been here.

Refresh previous screen on goBack()

I am new to React Native. How can we refresh/reload previous screen when returning to it by calling goBack()?
Lets say we have 3 screens A, B, C:
A -> B -> C
When we run goBack() from screen C it goes back to screen B but with old state/data. How can we refresh it? The constructor doesn't get called 2nd time.
Adding an Api Call in a focus callBack in the screen you're returning to solves the issue.
componentDidMount() {
this.props.fetchData();
this.willFocusSubscription = this.props.navigation.addListener(
'willFocus',
() => {
this.props.fetchData();
}
);
}
componentWillUnmount() {
this.willFocusSubscription.remove();
}
UPDATE 2023: willFocus event was renamed to focus
componentDidMount() {
this.props.fetchData();
this.focusSubscription = this.props.navigation.addListener(
'focus',
() => {
this.props.fetchData();
}
);
}
componentWillUnmount() {
this.focusSubscription();
}
How about using useIsFocused hook?
https://reactnavigation.org/docs/function-after-focusing-screen/#re-rendering-screen-with-the-useisfocused-hook
const componentB = (props) => {
// check if screen is focused
const isFocused = useIsFocused();
// listen for isFocused, if useFocused changes
// call the function that you use to mount the component.
useEffect(() => {
isFocused && updateSomeFunction()
},[isFocused]);
}
For react-navigation 5.x use
5.x
use
componentDidMount() {
this.loadData();
this.focusListener = this.props.navigation.addListener('focus', () => {
this.loadData();
//Put your Data loading function here instead of my this.loadData()
});
}
For functional component
function Home({ navigation }) {
React.useEffect(() => {
const unsubscribe = navigation.addListener('focus', () => {
loadData();
//Put your Data loading function here instead of my loadData()
});
return unsubscribe;
}, [navigation]);
return <HomeContent />;
}
On your screen B constructor will work like magic :)
this.props.navigation.addListener(
'didFocus',
payload => {
this.setState({is_updated:true});
}
);
Yes, constructor is called only for the first time and you can't call it twice.
First: But you can separate the data getter/setter from the constructor and put it in a function, this way you can pass the function down to the next Scene and whenever you're going back you may simply recall the function.
Better: You can make a go back function in your first scene which also updates the scene while going back and pass the go back function down. This way the second scene would not be aware of your update function which is reasonable.
Best: You can use redux and dispatch a go-back action in your second scene. Then in your reducer you take care of going back & refreshing your scene.
The built in listener function which comes with React-Navigation would be the easiest solution. Whenever a component is 'focused' on a again by navigating back, the listener will fire off. By writing a loadData function that can be called both when loading the Component AND when the listener is notified, you can easily reload data when navigating back.
componentWillMount(){
this._subscribe = this.props.navigation.addListener('didFocus', () => {
this.LoadData();
//Put your Data loading function here instead of my this.LoadData()
});}
Easy! insert the function inside useFocusEffect(func)
import { useFocusEffect } from '#react-navigation/native'
I have a similar situation and the way i refreshed was to reset the route when the back button is pressed. So, what happens is when the back button is pressed the screen is re-pushed into the stack and the useEffect on my screen loads the data
navigation.reset({
index: 0,
routes: [{ name: "SCREEN WHERE THE GOBACK BUTTON SHOULD GO" }],
});
Update for react-navigation v5 and use the React Hooks. Actually, the use is the same with react base class. For more detail, please checkout the documentation here
Here is the sample code:
function Profile({ navigation }) {
React.useEffect(() => {
const unsubscribe = navigation.addListener('focus', () => {
// do something
});
return unsubscribe;
}, [navigation]);
return <ProfileContent />;
}
As above code, We add the event listener while the variable navigation change then We do something like call function refresh() and finally, we return the function for removing the event listener. Simple!
I think we have a very easy way (which works in 2021) to do so. Instead of using goBack or navigate, you should use push
this.props.navigation.push('your_route_B').
You can also pass params in the same way as we pass in navigate.
The only difference b/w navigate and push is that navigate checks if the route which we are passing exists in the stack. Thus taking us to the older one but, push just sends us there without checking whether that is in the stack or not (i.e, whether the route was visited earlier or not.)
This can be achived by useFocusEffect from '#react-navigation/native'
useFocusEffect will effect every time when screen is focus
Ref: https://reactnavigation.org/docs/use-focus-effect/
import { useFocusEffect } from '#react-navigation/native';
function Profile({ }) {
useFocusEffect(
React.useCallback(() => {
//Below alert will fire every time when profile screen is focused
alert('Hi from profile')
}, [])
);
return // ...code ;
}
You can use this event: navigation.addListener('focus'
And you can implement like this:
const Cards = ({ navigation }) => {
...
useEffect(() => {
const load =async ()=>{
const a = await selectGlobalCards()
}
navigation.addListener('focus',() =>{
load();
});
}, [])
or you can use useIsFocused, and you can use that as a dependecy for useEffect
import { useIsFocused } from '#react-navigation/native'
const Cards = ({ navigation }) => {
const isFocused = useIsFocused()
useEffect(() => {
const load =async ()=>{
const a = await selectGlobalCards()
}
load()
}, [isFocused])
For react navigation (5.x), you just need to add a focus subscription and put your component initializing logic in a separate function like so:
componentDidMount() {
this.init();
this.didFocusSubscription = this.props.navigation.addListener(
'focus',
() => {
this.init();
}
);
}
init = async () => {
//fetch some data and set state here
}
If you're trying to get new data into a previous view, and it isn't working, you may want to revisit the way you're piping data into that view to begin with. Calling goBack shouldn't effect the mounting of a previous component, and likely won't call its constructor again as you've noted.
As a first step, I would ask if you're using a Component, PureComponent, or Functional Component. Based on your constructor comment it sounds like you're extending a Component class.
If you're using a component, the render method is subject to shouldComponentUpdate and the value of your state is in your control.
I would recommend using componentWillReceiveProps to validate the component is receiving the new data, and ensuring its state has been updated to reflect the new data.
If you're using the constructor to call an API or async function of some kind, consider moving that function into a parent component of both the route you're calling goBack from and the component you're wanting to update with the most recent data. Then you can ask your parent component to re-query the API, or update its state from a child component.
If Route C updates the "state/data" of the application, that update should be propagated to a shared parent of routes A, B and C, and then passsed down as a prop.
Alternatively, you can use a state management solution like Redux to maintain that state independent of parent/child components - you would wrap your components in a connect higher-order component to get the latest updates any time the application state changes.
TL;DR Ultimately it sounds like the answer to your question is rooted in where your application state is being stored. It should be stored high enough in your component hierarchy that each route always receives the latest data as a prop, passed from its parent.
Thanks to #Bat.
I have spent a lot of hours on finding the answer and finally, I got a basic solution which is working according to my needs. I was quite worried though.
Simply make a function like this in your previous activity make sure to bind it.
changeData(){
var mydata= salesmanActions.retrieveAllSalesman();
this.setState({dataListFill: mydata});
alert('' + mydata.length);
}
Simple, then in constructor bind this,
this.changeData= this.changeData.bind(this);
After that, as I am using react native navigation, so I will simply pass this function to the second screen just like the code below:
onPress={() => this.props.navigation.navigate('Add Salesman', {doChange:
this.changeData} )}
So when the new screen registered as "Add Salesman" will be called, a parameter named "doChange" which is assigned a function will also be transfered to other screen.
Now, in other screen call this method anywhere, by :
this.props.route.params.doChange();
It works for me. I hope works for you too, THANKS for the idea #Bat.
let we have 2 screen A and B , screen A showing all data . and screen B is responsible for adding that data. we add some data on using screen B and want to show instant changes on Screen A . we use below code in A
componentDidMount(){
this.focusListener = this.props.navigation.addListener('focus', () => {
thi`enter code here`s.startData();
//Put your Data loading function here
});
}
This is what you can do with react navigation v6.
Create a separate stack in stack navigator like this:
const PropertyListStack = () => {
return (
<Stack.Navigator screenOptions={{headerShown: false}}>
<Stack.Screen name={ROUTE_PROPERTY_LIST} component={PropertyList}/>
</Stack.Navigator>
)};
Now, whenever you you want to reload your initial screen navigate using this stack. like this:
navigation.navigate(
ROUTE_DASHBOARD_TABS,
{screen: ROUTE_PROPERTY_LIST_STACK}
);
This will reload your base screen. In my case base screen is PropertyList.
If you know the name of the Screen you want to go , then you can use this code.
navigation.navigate("Screen"); navigation.replace("Screen");
This code works fine if you don't have nested routes.
This answer assumes that the react-native-navigation library is being used, which is unlikely because it doesn't actually have a goBack() method...
The constructor doesn't call a second time because screen A and B are still rendered (but hidden behind screen C). If you need to know when screen B is going to be visible again you can listen to navigation events.
class ScreenB extends Component {
constructor(props) {
super(props);
// Listen to all events for screen B
this.props.navigator.setOnNavigatorEvent(this.onNavigatorEvent);
}
onNavigatorEvent = event => {
switch (event.id) {
case 'willAppear':
// refresh your state...
break;
};
}
Other events: willDisappear, didAppear, didDisappear
An alternate solution to your problem is to use a state management solution like Redux to provide the state to all screens whenever it is updated (rather than just on screen transitions. See old react-native-nav/redux example.

Sammy.js : Partial calling/rendering my html twice

I am new to knockout and sammy. I am implementing SPA using Sammy(router) and KnockOut(binding).
I have below code.
this.get('#/Page/:operation', function (context) {
this.partial('templates/Page1.html', { cache: false }).then(function (content) {
// the first argument to then is the content of the
// prev operation
$('#sammyDiv').html(content);
});
});
When I checked the console it's saying "You cannot apply bindings multiple times to the same element.(…)".
Am I doing anything wrong here?
What's the difference between Partial and Render?
Im guessing you are injecting new html into the parent which has already been bound. You should either be more specific about which divs you are binding too. I.E on apply bindings to the parent div of the html you injected, or clean and reapply bindings. I use both methods throughout my application.
if (isBound("my-div"))
ko.cleanNode(document.getElementById('my-div'));
ko.applyBindings(myModel, document.getElementById('my-div'));
Helper Function:
// Checks if Element has Already been Bound (Stops Errors Occuring if already bound as element can't be bound multiple times)
var isBound = function (id) {
if (document.getElementById(id) != null)
return !!ko.dataFor(document.getElementById(id));
else
return false;
};
I use this check before all my bindings as a safety net.
Thanks for the response and comments. Issue was with Sammy router.
this.get('#/Page/:operation', function (context) {
this.partial('templates/Page1.html', { cache: false }).then(function (content) {
// the first argument to then is the content of the
// prev operation
$('#sammyDiv').html(content);
});
});
changed to
this.get('#/Page/:operation', function (context) {
this.partial('templates/Page1.html', { cache: false });
});
It worked perfectly fine. Thanks again.

Is there a way to test HTML element in angularjs? [Here is my app in plunkr][1] [1]: https://jsfiddle.net/4jttdczt/

I'm new to angularjs trying to test HTML elements but my test gets failed.Actually,i want to test the HTML element's value or text which it contain.
can anyone suggest me how can i achieve it?
here is my controllerspec:
describe('myAppCtrl', function() {
var scope, controller, httpBackend;
beforeEach(module('myApp'));
beforeEach(inject(function($rootScope, $controller, $httpBackend) {
scope = $rootScope;
controller = $controller;
httpBackend = $httpBackend;
}));
it('should show the text of element',function() {
expect($('#testtext')).toBe('First Angular JS App');
});
});
What you're doing above is with Karma for unit testing, and is only loading up the modules you've specified. In this case it loads the module myApp, but myApp doesn't have any reference to HTML and doesn't load the DOM itself.
If you want to test HTML elements directly, take a look at end to end testing using Protractor:
https://docs.angularjs.org/guide/e2e-testing