Can I wait on connectedCallback finsh work in tests? - javascript

I am trying to test some code and I know it will work as expected, but I do not know how can I write tests in that case because my attribute is set always after test have finish?
In general, I have created an abstract WebComponent class which needs to be inherited by my custom web-components instead of HTMLElement. This whole abstract class have some common logic for all my web-components for example setting props if user passed any by constructor.
I have created a simple example to show what I want to achieve. In the example below I am creating a HelloWorld component this component has one observedAttributes which is heading-content (value which will be dispalyed inside <h1></h1> of HelloWorld component template). If users sets heading-content via constructor, then I am saving this value inside this.propsContent. Then after connectedCallback is triggered I am setting this props setContentFromProps which triggers attributeChangedCallback and this callback does the rest.
Is it possible to somehow wait until these actions end?
Example
HelloWorld component:
const template = `<h1 id="hello-world-content"></h1>`;
export class HelloWorld extends HTMLElement {
static TAG = 'hello-world';
static observedAttributes = ['heading-content'];
constructor(props) {
super();
this.attachShadow({ mode: 'open' });
this.shadowRoot.innerHTML = template;
this.headingContent = null;
this.propsContent = props;
this.helloHeading = this.shadowRoot.querySelector('#hello-world-content');
}
connectedCallback() {
this.setContentFromProps();
}
attributeChangedCallback(name, oldValue, newValue) {
if (name === 'heading-content') {
this.helloHeading.textContent = newValue;
this.headingContent = newValue;
}
}
setContentFromProps() {
this.setAttribute('heading-content', this.propsContent);
}
}
customElements.define(HelloWorld.TAG, HelloWorld);
HelloWorld unit tests:
import 'jest';
import { HelloWorld } from './HelloWorld';
describe(HelloWorld.name, () => {
test('should set heading content to given value', () => {
const helloWorld = new HelloWorld('dupa');
expect(helloWorld.headingContent).toBe('dupa');
});
});
Test result:
expect(received).toBe(expected) // Object.is equality
Expected: "dupa"
Received: null

Not without adding code; there is no allAttributesChangesDone callback. And its not possible to write unless you know upfront What needs to be changed.
I see, so it is impossible. Anyway thank you for your answer
If you wait till the Event Loop is done; you can be fairly sure all updates are done... unless there is async code doing updates...
"What the heck is the Event Loop:" https://youtube.com/watch?v=8aGhZQkoFbQ&vl=en
That's true but If I wait when EventLoop will become free than my tests can take much more time. Actually, now after I thought more about that my test is 'not working' (in my opinion a few hours ago) I can say I was mistaken and the test works correctly, and I should take care of the case when someone is trying to access the attribute before it is initialized.

Related

Store web element's value in a parameter and use it in various js files in testcafe

In our insurance domain, the below scenario we want to achieve using testcafe:-
1st file:- Login into the application
2nd file:- create a claim, store the claim number into the global variable
3rd file:- use that globally declared claim number in all the Testscripts.
we are using the Page object model to achieve our scenario.
Please let us know how can we achieve this in testcafe.
As we suspect, the web element value that we get in 2nd file gets vanished as soon as the test case gets executed. so how can we pass that web element value in our 3rd file?
If possible, please let us know the steps in detail.
we have tried the below keywords to define our selector but it didn't work.
global
globalthis
We want to pass the data(fetched web element value) from one testscript to another testscript. Our question is whether it's possible or not
//page.js
import { Selector, t } from 'testcafe';
class PageModel {
constructor() {
global.ClaimNumber = Selector('#Txt_claimnumber');
//selector for Claim Number
this.DateOfEvent = Selector('#dateofevent');
//selector for Date of Event
this.DateOfClaim = Selector('#dateofclaim')
//selector for Date of Claim
this.TimeOfEvent = Selector('#timeofevent')
//selector for Time of Event
this.TimeOfClaim = Selector('#timeofclaim')
//selector for Time of Claim
this.ClaimStatus = Selector('#claimstatus')
//selector for Claim Status
this.Save = Selector('#Save');
//selector for Save Button
}};
export default new PageModel();
//test.js
import { Selector } from 'testcafe';
import PageModel from './page';
fixtureGetting Started
.pagehttps://devexpress.github.io/testcafe/example;
var claimid;//claimid will be generate after saving a claim
test('My first test', async t => {
await t
.typeText(this.DateOfEvent, '20/09/2022')
.typeText(this.DateOfClaim, '20/09/2022')
.typeText(this.TimeOfEvent, '12:00')
.typeText(this.TimeOfClaim, '12:00')
.typeText(this.ClaimStatus, 'xyz')
.click(this.Save)
claimid=global.ClaimNumber.value
//After saving the claim we want to fetch claimid and want to use that claim id in another testscript
});
//test1.js
import { Selector } from 'testcafe';
import PageModel from './page';
import Test from './test'
fixtureGetting Started
.pagehttps://devexpress.github.io/testcafe/example;
test('My first test', async t => {
var claimid1='23445';
await t.expect(claimid1).eql('claimid');
//want to verify claimid getting from test.js is equal to claimid from test1.js or not
//this is just an example but our requirement is to use claimid (getting from test.js) for different different operation into test1.js testscript.
});
Could you please tell us how to achieve this scenario.
It isn't correct to use information from one test in another one. If you want to prepare something before any test starts, you can use hooks. Also, if you need to reuse auth information, use Roles. It will be great practice.
Please see the following example with a global variable:
//test.js
import { Selector } from 'testcafe';
import PageModel from './page';
fixture`Getting Started`
.page`https://devexpress.github.io/testcafe/example`;
test('My first test', async t => {
await t
.typeText(global.developerNameSelector, 'John Smith')
.click('#submit-button')
// Use the assertion to check if the actual header text is equal to the expected one
.expect(Selector('#article-header').innerText).eql('Thank you, John Smith!');
});
//page.js
import { Selector, t } from 'testcafe';
class PageModel {
constructor() {
global.developerNameSelector = Selector('#developer-name');
}
};
export default new PageModel();
As my colleague mentioned above, it is very bad practice to use data from one test in another one. However, if it is required, you can use the "global" object in a common JavaScript way to accomplish this task:
test('My first test', async () => {
global.someVar = 'developer-name';
});
test('My second test', async t => {
await t.typeText(`#${global.someVar}`, 'some text');
});
Note that if, for some reason, the order in which the tests are executed changes (for example, in concurrency mode), then you will encounter unexpected behavior.
Also, I just checked your code and found out that you are trying to save the result of the "expect" method call (Assertion object) to your variable. Would you please clarify why? What behavior are you are trying to achieve?

How is this inheritance working in JavaScript?

Watching a React course, he wanted to extract a Component into a more reusable component, My question/confusion is about this line below:
this.doSubmit(); that how does it even not error? because doSubmit() is in the CHILD class. It is not defined in the parent class. So shouldn't it error?
class Form extends Component {
handleSubmit = (e) => {
e.preventDefault();
// bunch of code then next line is my question:
this.doSubmit();
};
}
class LoginForm extends Form {
doSubmit = () => {
// call server
console.log("submitted");
};
}
Since you mentioned this is a course, I suspect there might be something more. But based on what you provided, this will not work: exactly as you said, the method is in child class.
I created a sample and you will see the error when call handleSubmit(): https://codesandbox.io/s/thirsty-buck-zpgll?file=/src/App.js

How to add custom request header to testcafe test suite?

I have a bunch of tests that I am running through testcafe. Now I need to add a custom request header for each test that uniquely identifies the call is originating from the testcafe suite and not a real user.
Is there a way to add the custom header to all the test cases at once?
I was looking at this but it seems like I would need to update each fixture to get this working. So, I wanted to know if there's a way I can set it on a top level file before calling the test suite?
EDIT:
So, this is what I am currently doing. I have created a new file that contains the class:
import { RequestHook } from 'testcafe';
class CustomHeader extends RequestHook {
constructor () {
// No URL filtering applied to this hook
// so it will be used for all requests.
super();
}
onRequest (e) {
e.requestOptions.headers['my_custom_variable'] = 'my_value';
}
onResponse (e) {
// This method must also be overridden,
// but you can leave it blank.
}
}
const customHeader = new CustomHeader();
export default customHeader;
And then in my each test file, update the fixtures to be like this:
import { customHeader } from 'customer_header'
fixture(`Test app avail`)
.page(appURL)
.requestHooks(customHeader)
.beforeEach(async() => {
await myTestfunction();
});
Does this make sense?
Currently there is no way to specify hooks on the test run level. But, if updating each fixture is not reasonable in your case, you can use the workaround posted in the discussion about this feature. In order to apply your request hook to each fixture in the test suite you'll need to change "setup.js" (from the workaround above) as follows:
export const fixture = (...args) => global.fixture(...args)
.requestHooks(customHeader)
.beforeEach(async t => {
console.log('each');
});

Instance variable vs module-scoped variable

The following components will yield the same result:
const currYear = Date.now().getFullYear();
class App extends React.Component {
render() {
return <MyComponent year={currYear} />;
}
};
class App extends React.Component {
constructor() {
super();
this.currYear = Date.now().getFullYear();
}
render() {
return <MyComponent year={this.currYear} />;
}
};
Assume the variable never changes.
Can their application be considered equivalent?
If not, are there situations where one should prefer the one method over the other?
Been curious for a long time, but never found a solid answer.
In this particular case they are equivalent, primarily because App is supposed to be instantiated once.
This wouldn't be true for a component that is instantiated multiple times. If a user changes system time or a new year comes in, this.currYear changes in future component instances, too.
Using constants as class fields provides more flexibility. There may be a need to change them during tests, this may improve testability. And can be changed in child classes when needed. Even if you didn't design the class to be extendable, another developer may benefit from extensibility:
class BackToTheFutureApp extends App {
currYear = 1955;
}
The first component defines a global javascript variable which could clash with something else (third party components or scripts that may be present). If another component or script also defines this variable you will get a run-time error. If this is the only instance in the whole application (including any other components you use) then it will be no issue. Still, it is somehow cleaner not to define global variables.
Inside of your constructor means that for each instance of your component you'll also have an instance of that variable. I'd imagine you'd be better of using it as per your first example because that will create just one variable no matter how many instances of your component their are. It's also worth noting though that if you take this approach and your program is running for more than a year that it may at some point be incorrect.
first let us answer following question. By answering which we make correct choice.
Will currYear variable be used by other components other than App if no then it should be implemented inside App Component.
Why you may ask:
1.For code readability by other developers.
2.To make it obvious that currYear is used only by App component and no any other
component.
3.To prevent accidental change.
In the first case:
const currYear = Date.now().getFullYear(); // Date is created here
class App extends React.Component {
render() {
return <MyComponent year={currYear} />;
}
};
currYear is the date when the file was required from other file or included on page.
While on second example you declare currYear when the instance of the class is created, so dates will be different:
class App extends React.Component {
constructor() {
super();
this.currYear = Date.now().getFullYear();
}
render() {
return <MyComponent year={this.currYear} />;
}
};
<App/> // Date is created here
Also the first pattern is very bad in almost all cases, for example if you do currYear.setDate(...) variable value will change in every class without re-rendering view.

Subscriptions with react-meteor-data

From the docs, I wrote my container like so
export default InventoryItemsList = createContainer(() => {
const itemsCollection = Meteor.subscribe('allInventoryItems');
const loading = !itemsCollection.ready();
return {
loading,
items: !loading ? InventoryItems.find().fetch() : []
};
}, class InventoryItemsListComponent extends Component {
render() {
let items = this.props.items;
/* some render logic */
return /*... react component template ...*/ ;
}
});
The problem I see is that
The container function is executed many times, thus calling Meteor.subscribe more than once; is that good? Will Meteor just ignore subsequent subscriptions?
According to this tutorial, subscriptions need to be stopped, but the docs do not mention it at all. This does not take care on it's own, does it?
What is the recommended way to stop (i.e. unsubscribe) or resolve the 2 issues that I see from that point?
Is TrackerRact actually better? (yes, I know this is opinionated, but surely there is some form of a convention with meteor react, here!)
1) The container component is a reactive component, so whenever documents are changed or added to the given collection, it's going to call via your container and update the DOM.
2) As far as I know, the container will only subscribe to the collection via the actual component you bind it to. Once you leave that component, the subscriptions should stop.
If you want to unsubscribe directly, you can just call this.props.items.stop() in your componentWillUnmount() method.
Finally, I will have to say that using React specific implementations are always better than using Meteor specific functions (i.e. it's always better to use state variables over Sessions with React, as it's always better to use containers than Tracker.autorun() with React, etc, etc).
About your problem 2), this is how I solve it
1- When you subscribe to something, store those subscriptions references and pass them to the component.
Here an example with 2 subscriptions, but subscribing to only 1, is even easier.
createContainer((props) =>{
const subscription = Meteor.subscribe('Publication1', props.param1);
const subscription2 = Meteor.subscribe('Publication2', props.param1, props.param2);
const loading = !(subscription.ready() && subscription2.ready());
const results1 = loading ? undefined : Collection1.find().fetch();
const results2 = loading ? undefined : Collection2.findOne({a:1});
return {subscriptions: [subscription, subscription2], loading, results1, results2};
}, MyComp);
Then in my component:
class MyComp extends Component {
......
componentWillUnmount() {
this.props.subscriptions.forEach((s) =>{
s.stop();
});
}
....
}
This way, the component will get in props.subscriptions all the subscriptions it needs to stop before unmounting.
Also you can use this.props.loading to know if the subscriptions are ready (of course, you can have 2 different ready1 and ready2 if it helps).
Last thing, after subscribing, if you .find(), dont forget to .fetch(), else the results will not be reactive.

Categories

Resources