I've been struggling with going back to the roots of doing non-node reliant javascript, in this particular case for a test. Like many developers, i've fallen into the trap of learning frameworks, and in turn have forgotten / never understood some of the basic paradigms with javascript programming.
The problem
I'm trying to test a simple es6 script i've created for a collapsible element. It can be tested with a framework, however the script is simply inserted into the bottom of a html file, rather than be executed in any more complex way, and needs to remain functional without a complex procedure behind it. I understand that this is not the conventional way to do this, you could easily make the problem trivial with modern tech, but it's the way i'm required to put this together.
Collapsible.js
const initialiseCollapsible = (collapsible) => {
let collapsibleButton = collapsible.querySelector(".collapsible__button");
let collapsibleContent = collapsible.querySelector(".collapsible__content");
collapsibleButton.setAttribute("aria-expanded", "false");
collapsibleContent.setAttribute("aria-hidden", "true");
collapsibleButton.addEventListener("click", () => {
// cast the value of aria-expanded as a boolean
let expanded =
collapsibleButton.getAttribute("aria-expanded") === "false"
? false
: true;
// Toggle the expanded attribute
collapsibleButton.setAttribute("aria-expanded", !expanded);
collapsibleContent.setAttribute("aria-hidden", expanded);
});
};
(() => {
[...document.getElementsByClassName("collapsible")].forEach((collapsible) => {
initialiseCollapsible(collapsible);
});
})();
The file is meant to loop through all instances of .collapsible, adding event listeners so they can toggle correctly, and closing them initially (The content has to be accessible with javascript disabled).
1) Module functionality
With es6 moduling, i'm aware that it's transpiled normally using something such as babel. And with requireJS, I still don't understand whether module.exports is going to be understood by the browser, currently it throws errors because it doesn't understand the syntax. Is there a way of going around this? So I can write a functional script to be called in a html file, while still being able to import this into a test file to run tests on? I'm assuming I could just end up copy and pasting it for the sake of having it available is necessary.
2) Testing the thing
I'm struggling to contemplate how to test this piece of code accurately. I see a few potential tests:
Given a collapsible html element,
The expanded / hidden are correctly set to start (testing the query selector works and that the set attributes are working correctly)
Problem I don't quite understand how to emulate this without some sort of virtual dom that I can tamper with, are there good libraries for this? Ones that will allow me to create a document htmlelement that I can run the script code on to see the changes.
The collapsible button now has a clickEvent on it that matches the code of the example. Problem This feels almost like a functional test to me, i'm testing that something happens when I click the button, almost like cucumber / selenium would be a better route for this. There's a large amount of overhead there for something so simple however, is there an easier way to do this?
I realise this is probably around 10 questions in 1, but if you have any feedback / solutions for the problems posed, feel free to contribute.
Related
A little bit of backstory:
We're trying to address a bug on a local library we have.
I was trying to convince the senior developers of changing naming convention when using word like these:
enable disable hide unhide
because they were using
enable disable hide undelete
We discussed the fact of this being a breaking change and will stop other products from working.
So it was disscused to add the unhide without taking the undelete out so that it does not break anything and in the process, if the user uses either unhide or undelete they should still do the same in theory.
Here comes the programming problem now:
This is my function:
toggleAction(data._id, 'undelete', (err, data) => { // cool stuff }, false)
How can I continue to pass a string (without converting to an array or object) but also start passing 2 values so that the changes have to be made in this file and not in the projects side validating, etc.
Another thing, this function will default if the switch case does not found the string.
Also thought about sending two request at the same time but that's just bad programming.
Also though of fallback to another request with the other naming, but since the switch case will still default if it's not in the options, it will not throw an error to actually do the callback for the next function.
What do you guys think is the right approach for something simple and yet capable of breaking a lot of things.
Thanks in advance.
Kinda opinion based, but it's the wrong approach imho.
I would change the word undelete to unhide only on the rendered websites and keep the internal API the same way until the senior devs can fix the breaking changes.
So the user would see the word 'unhide' on their screen, but the API will still stay 'undelete'.
In more complex applications, you'd use an enumeration list so you can change the labels as many times as you want:
const enum_options = [
'enable',
'disable',
'hide',
'show'
];
// 3 is the index of the word 'show'
toggleAction(data._id, 3, (err, data) => { // cool stuff }, false)
That way the API never has to change when you change the labels to any word you desire. Which will also help alot if you ever have to support multiple languages.
ps:
1) if this is a breaking change, there's an issue with the architecture used.
Fix the issues instead of adding even more fragile code. Switching to like using a string 'undelete-unhide' will still need alot of api changes if the entire app has the word 'undelete' written everywhere at the moment.
2) The opposite of hide is usually 'show', not unhide.
Sorry for the total N00b question, but here goes,
I'm working on writing some test automation, and the website I'm automating sometimes has certain behaviors with certain elements on the page, and other times it has different behaviors with different elements on the page. It usually falls into one of two modes. Call it mode A & mode B.
The thing is there is some shared elements between mode A & mode B, so it's not exclusively different elements.
However, in either case there is one element that is always on the screen when mode B is active, and it is not on the screen when mode A is active.
Also mode A has a slightly longer workflow with a few extra controls to interact with. Lets call this one element "ProfileIdentifier". I've got a locator for it, and I'd like to write some logic code that checks if this element does not exist then interact with the extra controls and do the longer workflow, followed by the common controls and workflow that is common between mode A & mode B.
In short, something like this:
import mypage from '../../page-objects/mypage'; //JS file with all the locators for that page.
async function FillOutForm(t) {
var profileIdentifier = t.getElement(mypage.profileIdentifierLocator);
if (not(profileIdentifier.exists)) {
// Do the extra workflow
}
// Do the common workflow
}
Or perhaps something like this:
import mypage from '../../page-objects/mypage'; //JS file with all the locators for that page.
async function FillOutForm(t) {
var profileIdentifier = t.getElement(mypage.profileIdentifierLocator);
if (profileIdentifier.exists.notOk()) {
// Do the extra workflow
}
// Do the common workflow
}
NOTE: I've looked at other similar questions like:
related question #1
I did see a glimmer of hope in one of the answers to this question:
Possible answer
$('.myModal').isPresent().then(function(inDom) {
return inDom; // returns bool
};
I'm using the Atom JavaScript editor, in combination with TestCafe and ES6 and I'd prefer to say away from code like getElementById, as this isn't the best (performant & stable) way to leverage CSS locators in JavaScript, especially if we have to re-use similar locators across other sections of our site for styling and other testing automation.
You can use the exists asynchronous property to check whether the element exists.
import mypage from '../../page-objects/mypage'; //JS file with all the locators for that page.
async function FillOutForm(t) {
if (!await mypage.profileIdentifierLocator.exists) {
// Do the extra workflow
}
// Do the common workflow
}
When developing a web app with jQuery or normal JavaScript, it is commonplace to check for feature availability first. So for example, if I want to use the document.oncopy event, I should first have something like this to ensure my code doesn't break for lesser browsers:
if ("oncopy" in document) {
// Feature is available
}
I'm a bit puzzled about how this would work in Angular2. I could still use the same if I expect to only run in the browser, but I'm specifically told to leave the DOM alone if I want to use Angular Universal and depend on templates or the DomRenderer instead. This allows the page to be pre-rendered on the server and provides a truly impressive performance gain.
But suppose I want a specific div to be invisible if the document.oncopy is unavailable. My understanding is that this is not recommended:
<div *ngIf="hasFeature()">...</div>
and
hasFeature() {
return 'oncopy' in document;
}
because then I'm still manipulating the DOM. Note that my example is about the document.oncopy but I could choose any feature whatsoever that doesn't have universal support.
I tested this using Chris Nwamba's tutorial on Scotch and added the following to the end of his Home template:
<div *ngIf="hasFeature()">Feature is supported</div>
<div *ngIf="!hasFeature()">Feature is NOT supported</div>
Update: Interestingly, it gave different results on different browsers. On Chrome 55, it executed as it would normally and showed the "Feature is supported" message. On IE11, I received the "not supported" message. In both instances the server log shows a EXCEPTION: document is not defined message, but the page still seems perfectly okay.
So what is the correct way to check for browser features if I want to use Angular Universal?
Update:
I also toyed around with using a field in the template and assigning that field from one of the life cycle hooks. ngAfterContentInit seemed like a fine candidate, but also causes an error on the server. It still runs fine in the browser with no weird effects (that I have noticed so far).
There are two ways to approach this:
Do the check only once the server is done rendering and the client is completely initialised (including the replay of user events done by preboot.js).
Return a reasonable default when the page is running on the server and perform the actual check only in the browser.
I started looking at the first option, but none of the Angular2 life cycle events will help with this. In fact, you can clearly see them all executing on the server and only then on the client.
I then started looking for something usable in preboot.js but quickly realised it was more complex than it needed to be.
So onto option 2 I went. It turns out checking for the browser is as easy as importing and checking isBrowser.
import { isBrowser } from "angular2-universal";
#Component({
// All the usual stuff
})
export class MyComponent {
// ...
hasFeature(): boolean {
return isBrowser && 'oncopy' in document;
}
// ...
}
And then use the template as I showed in the question.
To check if you're running on the server, import and use isNode in exactly the same way. There doesn't seem to be an obvious way to distinguish between Node and ASP.NET Core, but perhaps it's best not to write too much code that specific to platform.
My team has had the occasional problem of developers pushing Karma/Protractor tests containing the .only() function call, which of course makes our Jenkins etc only run that particular test, potentially allowing bugs to slip by. As such I thought I'd try and figure out a way to stop this from happening without being discovered.
First, I thought I'd look into simply using JSHint to point out the function call, but I can't seem to find a way to do that. I also looked at ESLint for its custom plugins, but I can't figure out how to write a plugin for this particular case.
Could you guys give me some ideas on how to solve this issue? Alternative solutions are also appreciated, of course!
Here's a (probably not working example) of how to create a plugin that flags an error if the parser ever sees a only() call. Again, mileage may vary, but it should be enough to get you started. This does not work if it sees a.only(), we'll leave that up to you.
module.exports.rules = {
"no-only-call": context => ({
CallExpression: (node) => {
if(node.callee.name == "only"){
context.report(node, 'Calls to only() are disallowed');
}
}
})
};
https://www.kenneth-truyers.net/2016/05/27/writing-custom-eslint-rules/ - Simple example of creating a custom rule
http://esprima.org/demo/parse.html - Use this online parser to help you understand the parse tree.
https://www.npmjs.com/package/generator-eslint - Use this generator to start your plugin project
I would like to know if organizing JS functions in object notation is a good or bad practice. The intention is to keep js organized/easy to maintain.
Example:
var helper = {
myAlert: function(){
return alert('Alert from helper');
},
toLowerCase: function(){
var str = document.getElementById("txt").innerHTML;
return alert(str.toLowerCase());
}
}
Html:
<body>
<h1 onclick="helper.myAlert()">Hello Plunker!</h1>
<p id="txt" onclick="helper.toLowerCase()">Testing LowerCaseFunction</p>
</body>
Plunker
Thanks in advance!!!
code organization is an strategic topic when it comes to make software survive more than a couple of years yet this is also a very opinionated terrain.
You can make a nice job by keeping semantically related functions near each other under the same namespace, or even make it related to the user story being solved, it can be done in many ways.
If you want to know this approach is good enough, simulate an iteration on it, add a new feature and see what happens to the code.
The code avoids duplication? It gets reusable? Is it easy to locate and relate with the user needs? If so, then it will help you.
Finally, if applicable consider to use some module builder, webpack or browserify for example, so you will be able to not only separate your modules logically but "physically" too.
For me it is a good practice. When working with little projects, you do do see it important, but in large projects you are almost obliged to do it.
simply imagine you are making a chat website, you will need to create function that will delete messages, add messages. you can simply do it as you did it up.
var messages = {
remove:function{//remove code},
add:function{//add code}
}
in this way you can define contacts management object as
var contacts = {
remove:function{//remove code},
add:function{ //add code},
block:function{//block code}
}
This isn't really an answer per se but too long for a comment:
Since you are asking about best practices I'd like to point out a few things in your sample:
onclick. Just...don't. You don't want to grep through your JS trying to diagnose a behavioral problem only to realize 8 hrs in that its being called from HTML. That's a maintenance nightmare waiting to happen.
innerHTML again, just...don't. Unless you are working with HTML. 9 times out of 10, textContent will do and you can assign to it without security risk. Google 'innerHTML security risk' for more info.
alert alert blocks. Blocks your whole browser until you click annoying box. And which of your 50 open tabs was it in? Blocking is bad. Use console.log. You're a dev, and you do have devtools open right?
Your toLowerCase function. You mix data access (getting the DOM element's text) with data transformation. Bad idea.
Note that none of these are particular to JavaScript, they apply to pretty much all UI coding: don't mix business logic with the presentation layer, don't block user interactions, use the principle of least privilege, don't mix data access with data processing, etc. And all of the above issues are bigger problems than whether or not you namespace some functions.