I'm trying to get QUnit to work with my production environment, and noticed that QUnit exports a log function with this definition:
function ( callback ) {
config[key].push( callback );
}
Why does it do this? There seem to be a hole bunch of functions that are exported globally, like begin, deepEqual, done, etc etc. Isn't it better if all these functions are within a namespace instead?
Shouldn't this behaviour be noted in the documentation? I had another log function defined in my codebase (shorthand for console.log), and this rendered strange bugs from QUnits side because log("a log message") is not correct usage of QUnit's log function.
Is there a way to contain QUnit within its namespace? Giving the code an object instead of window does not work.
Related
It is known that in Cypress we can define custom commands in the commands.js file, with that syntax:
Cypress.Commands.add('login', (email, pw) => {})
Cypress.Commands.overwrite('visit', (orig, url, options) => {})
These commands will then become available across all our tests and can be used from the cy object.
cy.login('my#email.com', '123456')
cy.visit('www.stackoverflow.com', 'www.google.com', { redirect: true })
But I don't get the gist of it. What is the point of doing that when you could just write a regular function?
function login(email, pw) { /* ... */ }
login('my#email.com', '123456')
The only advantage I see is making the function available everywhere without having to export/import it, but you can do that with globals as well. Is that it, or am I missing something?
1. Don't make everything a custom command.
Don't be forget cypress provide API for overwriting existing commands not for only creating new commands.
JS function is great for grouping cy commands and preferable due to it simplicity and readability.
There’s no reason to add this level of complexity when you’re only wrapping a couple of commands only for avoiding Export/Import.
2. Don't overcomplicate things.
A good test automation framework will be is easily understandable, readable and maintainable.Try not to overcomplicate things and create too many abstractions. When in doubt, Always use a JS function.
check out here: https://docs.cypress.io/api/cypress-api/custom-commands#Best-Practices
Yes you are right! The goal is to write cleaner code and availability. With custom commands you can basically write the function in the form of a cypress command and also you don't have to write an import statement in the tests suites. Hence your test suite files remain uniform and cleaner.
The Cypress documentation suggests that commands are the right way to reuse fragments of code, e.g.
Cypress.Commands.add("logout", () => {
cy.get("[data-cy=profile-picture]").click();
cy.contains("Logout").click();
});
cy.logout();
For simple cases like this, why would I use a command over a plain JS function (and all the nice IDE assistance that comes with it). What are the drawbacks of rewriting the above snippet as
export function logout(){
cy.get("[data-cy=profile-picture]").click();
cy.contains("Logout").click();
}
// and now somewhere in a test
logout();
Based on my experience with Cypress (one year project and several hundred test cases), I can say that a plan JS function is great for grouping cy commands.
From my point of view, a custom cy command may be really useful only if it is incorporated into the chain processing (utilizes the subject parameter or returns a Chainable to be used further in the chain). Otherwise, a plain JS function is preferable due to it simplicity and full IDE support (unless you're using an additional plugin).
If you for any reason need to do something inside the cypress loop, you can always wrap you code by cy.then() in a plain JS function:
function myFunction() {
cy.then(() => {
console.log(("I'm inside the Cypress event loop"))
})
}
Commands are for behavior that is needed across all tests. For example, cy.setup or cy.login. Otherwise, use functions.
See official docs: https://docs.cypress.io/api/cypress-api/custom-commands#1-Don-t-make-everything-a-custom-command
I'm trying to delay the construction of every 'page' (i.e. a Wt::WWidget inside my global Wt::WStackedWidget), until it is needed. Therefore I'm using a method similar to the DeferredWidget of the Widget Gallery example of Wt.
However, when I load a library using require, the execution of javascript code is not delayed until the library is loaded, when the content is not loaded with the first request (f.ex. inside WWidget::load()), i.e. running the following code
wApp->require("myLibrary.js"); // defines function MyFunction ();
doJavaScript ("MyFunction ();");
runs without error when it is requested on the first loaded page, but when the content is loaded after a user event, the following javascript error occurs:
MyFunction is not defined
Question: How should I overcome this error or how should I correctly delay the loading of my (large) javascript library until needed?
Further research
Inspecting the source code of WebRenderer::collectJS:
Javascript updates seems to be performed before loading new libraries:
// Executing javascript updates, including doJavaScript calls.
for (unsigned i = 0; i < changes.size(); ++i) {
changes[i]->asJavaScript(sout, DomElement::Priority::Update);
delete changes[i];
}
...
// Loading new libraries.
int librariesLoaded = loadScriptLibraries(*js, app);
Shouldn't the javascript update being delayed until the new libraries are loaded?
Further research - Part 2
Executing javascript code (which may depend on required libraries) is delayed at two different places, i.e. inside
WebRenderer::collectJavaScript: delays execution of all javascript code (including invisible changes) until all old required libraries (excluding newly required libraries f.ex. inside WWidget::load) are loaded.
WebRenderer::collectJS: delays execution of some javascript code until all required libraries (including newly required libraries f.ex. inside WWidget::load) are loaded.
I am not sure with the javascript scriploader behavior. But in my wt experience i make it append in this way.
1) My javascript library is load in my main page at start with require.
2) If i need later some new function, i write it in my script string like this :
string javacode = "function MyTest ( ) { "
"alert('test') ; }"
"MyTest();"
doJavaScript ( javacode ) ;
If you want load some javascript file and run some function after it is load you schould make the require in the contructor of your container class.
Then you derived the function bool Wt::WCompositeWidget::loaded()
and put in this function your dojavaScript...
I am trying to write a unit test where I want to verify that a ajax call has been made.
The code is simple :
it('test spycall',()=>{
spyOn($,"ajax");
//my method call which in turns use ajax
MyFunc();
expect($.ajax.calls.mostRecent().args[0]["url"].toEqual("myurl");
});
The error that I get :
Property 'calls' doesn't exist on type '{settings:jqueryAjaxSettings):jQueryXHR;(url:string, settings?:JQueryAjaxSettings}
$.ajax.calls, among others, is part of the Jasmine testing framework, not JQuery itself (As you know, Jasmine (or rather, Jasmine-Jquery, the plugin you're using) is adding certain debugging functions to JQuery's prototype in order to, well, be able to test ajax calls).
The bad part is that your .d.ts typescript definition file, the file that acts as an interface between typescript and pure JS libraries isn't aware of Jasmine's functions.
There are several ways you could approach fixing this, like
looking if someone has adjusted the JQuery .d.ts file for Jasmine's functions or
creating the new .d.ts file yourself by modifying the original one or, (what I would be doing)
overwriting the typescript definition by declaring $.ajax as any, or not including the typescript definition at all in your testing codebase and declaring $ as any.
There are 2 ways to get rid of the error:
// 1
expect(($.ajax as any).calls.mostRecent().args[0].url).toEqual("myurl");
// 2
let ajaxSpy = spyOn($,"ajax");
expect(ajaxSpy.calls.mostRecent().args[0].url).toEqual("myurl");
You can also use partial matching:
expect(($.ajax as any).calls.mostRecent().args).toEqual([
jasmine.objectContaining({url: "myurl"})
]);
Using JQuery.load(), I change the content of my website's mainWindow to allow the user to switch between tabs. For each tab, there is one or multiple scipts that contain functions that are executed once the tab content is loaded.
Obviously when switching to the tab for the first time, the script has to be fetched from the server and interpreted, but this shouldn't happen if the user switches back to the tab later on. So, to put it short:
Load() html
make sure javascript functions exist, otherwise load script and interpret it.
call a a function on the javascript after the DOM is rebuilt.
Step one and two have to be complete before step 3 is performed.
At the moment, I am using nested callbacks to realize this:
function openFirstTab(){
$("#mainWindow").load("firstTab.php", function(){
if(typeof(onloadfFirstTab) != "function"){
jQuery.getScript("assets/js/FirstTab.js", function(){
onloadFirstTab();
});
}
else{
onloadFirstTab();
}
} );
}
but I feel that there should be a better way.
You can't write the code entirely synchronously since you can't load script synchronously after page load ( unless you do a synchronous XHR request and eval the results - not recommended ).
You've got a couple of choices. There are pre-existing dependency management libs like RequireJS which may fit the bill here, or if you just need to load a single file you can do something like this to clean up your code a bit rather than using if/else:
function loadDependencies() {
// For the sake of example, the script adds "superplugin" to the jQuery prototype
return $.getScript( "http://mysite.com/jquery.superplugin.js" );
}
function action() {
// If superplugin hasn't been loaded yet, then load it
$.when( jQuery.fn.superplugin || loadDependencies() ).done(function() {
// Your dependencies are loaded now
});
}
This makes use of jQuery Deferreds/Promises to make the code much nicer.
If you don't want to load the JS more than once and you are going to dynamically load it, then the only way to know whether it's already loaded is to test for some condition that indicates it has already been loaded. The choices I'm aware of are:
The simplest I know of is what you are already doing (check for the existence of a function that is defined in the javascript).
You could also use a property on each tab (using jQuery's .data() that you set to true after you load the script.
You could write the dynamically loaded code so that it knows how to avoid re-initializing itself if it has already been loaded. In that case, you just load it each time, but the successive times don't do anything. Hint, you can't have any statically defined globals and you have to test if it's already been loaded before it runs its own initialization code.
(Haven't tested it yet, so I am not sure if it works, especially since I didn't yet really understand scope in javascript:)
function require(scripts, callback){
var loadCount = 0;
function done(){
loadCount -=1;
if (loadCount==0){
callback();
}
}
for ( var script in scripts){
if (!script.exitsts()){
loadCount +=1;
jQuery.getScript(script.url, done);
}
}
}
This function takes an array of scripts that are required and makes sure all of them are interpreted before it calls the callback().
The "script" class:
function script(url, testFunc){
this.url =url;
this.testFunction = testFunc;
this.exists = function(){
if(typeof(testFunction)=="function"){
return true;
}
else{
return false;
}
}
}
Where the test-function is a function that is defined (only) in the concerned script.
PS:
To enable caching in JQuery and thus prevent the browser from doing a GET request every time getScript() is called, you can use one of the methods that are presented here.
Even though unnecessary GET - requests are avoided, the script is still getting interpreted every time getScript() is called. This might sometimes be the desired behavior. But in many cases, there is no need to re-interpret library functions. In these cases it makes sense to avoid calling getScript() if the required library functions are already available. (As it is done in this example with script.exists().