Code Coverage doesn't reach some lines of codes even though I'm testing them. :(
Here is bind.ts decorator and [[NOT COVERED]] code coverage tag I created. Currently the set(value) is not covered by the test even though I'm covering it.
type Descriptor<T> = TypedPropertyDescriptor<T>;
export default function bind<T = Function>(
target: object,
key: string,
descriptor: Descriptor<T>
): Descriptor<T> {
...
set(value: T): void {
[[22-NOT COVERED]] if (process.env.NODE_ENV !== 'test') {
[[23-NOT COVERED]] throw new Error('Unable to set new value to decorated method');
[[24-NOT COVERED]] }
[[25-NOT COVERED]] Object.defineProperty(this, key, { ...descriptor, value });
},
};
}
bind.spec.ts
My strategy is to create new class Component and test its context on call
class MockClass extends React.PureComponent<Props, State> {
#bind
getProp(): string {
const { propName } = this.props;
return propName;
}
#bind
getState(): string {
const { stateName } = this.state;
return stateName;
}
#bind
setProp(value: string): void {
this.state = { stateName: value };
}
}
...
describe('bind', () => {
const mockState = {
stateName: 'stateName',
};
const mockProp = {
propName: 'propName',
};
const mockClass = new MockClass(mockProp, mockState);
...
it('should have called it once', () => {
expect(mockClass.getProp()).toBe(mockProp.propName);
});
it('should have called it in setState', () => {
expect(mockClass.setProp('newState')).toBe(undefined); <<<- This can cover 22-25??
});
The uncovered setter is code that would be exercised if you set a value of the class property. You don't have any test code that does this. You're only getting a property named setProp then calling it. The fact that the property has "set" in its name may be confusing matters.
Your test code would have to do something like this to test the setter of the decorator:
mockClass.props.otherPropName = 'blah';
mockClass.getProp = function() {
const { otherPropName } = this.props;
return otherPropName;
};
expect(mockClass.getProp()).toEqual('blah');
Related
i have been struggling with some logic about using multiple page objects in one method, i couldnt find any idea to use like that logic, for example;
These are my methods in my page object called usersTable;
get rolesAndStatusMenu() {
return cy.get("#menu- > .MuiPaper-root > .MuiMenu-list>li");
}
get usersPartialRow() {
return cy.get(".MuiTableBody-root>tr>td");
}
settings(options: string) {
return cy
.get(
"[style='position: fixed; z-index: 1300; inset: 0px;'] > .MuiPaper-root > .MuiList-root",
)
.contains(options);
}
menuButton(userId: string) {
return cy.get(`.user_${userId}>td>button`);
}
userRow(userId?: string) {
const userrow = ".MuiTableBody-root>tr";
if (userId === undefined) {
return cy.get(userrow);
}
return cy.get(userrow).get(`.user_${userId}`);
}
im using userRow method in this test like that;
usersTable.userRow(userId).should("not.exist");
And for exaple im using my userMenu and settings method in this test;
usersTable.menuButton(userId).click();
usersTable.settings("Impersonate").click();
Let's come to the idea that I want to do but I can't find the way to do it;
usersTable.userRow(userId).settings.menuButton.click()
usersTable.userRow(userId).settings.impersonate.click()
Is there a any way to use like that ? All ideas are accepted
Update
I have one more page object, i define my usersTable component modal inside called usersPage page modal
import { UsersTable } from "../components/UsersTable ";
export class Users {
visit() {
return cy.visit("/users");
}
get headingText() {
return cy.get(".MuiTypography-h5");
}
get inviteUserBtn() {
return cy.get(".MuiGrid-root> .MuiButtonBase-root");
}
get inviteUserModal() {
return cy.get(".MuiDialogContent-root");
}
get usersTable() {
return new UsersTable();
}
}
So my code looks like this
usersPage.usersTable.menuButton(userId).click();
usersPage.usersTable.settings("Impersonate").click();
usersPage.visit();
usersPage.usersTable.menuButton(userId).click();
usersPage.usersTable.settings("Delete").click();
usersPage.usersTable.userRow(userId).should("not.exist");
For example using this way
usersPage.usersTable.userRow(userId).settings.menuButton.click()
So maybe i can create class inside UsersTable
export class UsersTable {
...
}
class userTableRow {
}
**and returning it in `UsersTable` or something like that ?**
Second Update
Now i create a class inside UsersTable file;
class UserRow {
userRow(userId?: string) {
const userrow = ".MuiTableBody-root>tr";
if (userId === undefined) {
return cy.get(userrow);
}
return cy.get(userrow).find(`.user_${userId}`);
}
get menuButton() {
return this.userRow(`>td>button`); //Btw im not sure this one is working, i think something is wrong here;
}
get impersonate() {
return cy
.get(
"[style='position: fixed; z-index: 1300; inset: 0px;'] > .MuiPaper-root > .MuiList-root",
)
.contains("Impersonate");
}
get delete() {
return cy
.get(
"[style='position: fixed; z-index: 1300; inset: 0px;'] > .MuiPaper-root > .MuiList-root",
)
.contains("Delete");
}
}
And for using this class returned in UsersTable class;
userRow(userId?: string) {
const userrow = ".MuiTableBody-root>tr";
if (userId === undefined) {
return cy.get(userrow);
}
return new UserRow(userId); **// but got error, it says Expected 0 arguments, but got 1.**
}
If i use like this comment section;
// get UserRow() {
// return new UserRow();
// }
I can able to reach everything inside user but i can't use my test like this;
usersPage.usersTable.UserRow(userId).settings.menuButton.click()
or maybe
usersPage.usersTable.UserRow.userRow(userId).settings.menuButton.click()
But i can use like this;
usersPage.usersTable.UserRow.menuButton.click()
How can i define userId?: string for UserRow userId is constantly changing every time, I get it from API inside test, So I can't define for sure.
To make class methods fluid, you will need to return this.
class UsersTable {
settings(options: string) {
cy.get(...).contains(options);
return this // call next method on return value
}
menuButton(userId: string) {
return cy.get(`.user_${userId}>td>button`); // cannot call another after this one
}
userRow(userId?: string) {
cy.get(userrow).get(`.user_${userId}`);
return this; // call next method on return value
}
}
Should work,
usersTable.userRow(userId).settings().menuButton().click()
But now, where are the values from first two methods?
You would need to store them in the class
class UsersTable {
userId: string = '';
setting: string = '';
settings(options: string) {
cy.get(...).contains(options)
.invoke('text')
.then(text => this.setting = text)
return this
}
menuButton() { // no parameter, take value from field
return cy.get(`.user_${this.setting}>td>button`);
}
userRow() { // no parameter, take value from field
cy.get(userrow).get(`.user_${this.userId}`)
.invoke('text')
.then(text => this.userId = text)
return this; // call next method on return value
}
}
But now have lost flexibility, methods are tightly coupled, not independent anymore.
The UserTable methods return the Cypress.Chainable type, so you will need to unwrap the result to pass it to the next method.
Also, it's returning the element but next method needs text content, so extract that as well.
usersTable.userRow(userId)
.then((userIdElement: JQuery<HTMLElement>) => { // unwrap Chainable
const text = userIdElement.text() // extract text
usersTable.settings(text)
})
.then((settingsElement: JQuery<HTMLElement>) => { // unwrap Chainable
const text = settingsElement.text() // extract text
usersTable.menuButton(text).click()
})
If any of the elements are HTMLInputElement, use userIdElement.val() instead.
An adjustment to userRow():
class UsersTable {
...
userRow(userId?: string): Cypress.Chainable<JQuery<HTMLElement>> {
const userrow = ".MuiTableBody-root>tr";
if (userId === undefined) {
return cy.get(userrow);
}
return cy.get(userrow)
.find(`.user_${userId}`) // use find instead of get
}
}
How to do it with Custom Commands instead of pageObject
Chaining is a natural code pattern for Cypress commands, so using Custom Commands
commands.js
/// <reference types="cypress" />
declare namespace Cypress {
interface Chainable<Subject = any> {
settings(options?: string): Chainable<JQuery<HTMLElement>>;
menuButton(userId?: string): Chainable<JQuery<HTMLElement>>;
userRow(userId?: string): Chainable<JQuery<HTMLElement>>;
}
}
Cypress.Commands.add('settings', {prevSubject: 'optional'}, (subject: any, options?: string): Cypress.Chainable<JQuery<HTMLElement>> => {
if (options === undefined) {
options = subject as string;
}
return cy.get("[style='position: fixed; z-index: 1300; inset: 0px;'] > .MuiPaper-root > .MuiList-root")
.contains(options)
})
Cypress.Commands.add('menuButton', {prevSubject: 'optional'}, (subject: any, userId?: string): Cypress.Chainable<JQuery<HTMLElement>> => {
if (userId === undefined) {
userId = subject as string;
}
return cy.get(`.user_${userId}>td>button`);
})
Cypress.Commands.add('userRow', (userId?: string): Cypress.Chainable<JQuery<HTMLElement>> => {
const userrow = ".MuiTableBody-root>tr";
if (userId === undefined) {
return cy.get(userrow);
}
return cy.get(userrow)
.find(`.user_${userId}`)
})
test
it('tests with userId from userRow()', () => {
const userId = '1'
cy.userRow(userId)
.settings() // as child command, userId from previous command
.menuButton()
.click()
});
it('tests with userId hard-coded', () => {
cy.settings('abc') // as parent command, userId passed as parameter
.menuButton()
.click()
});
I am trying to use lit-translate to translate my "Elmish" typescript website into different languages. I use webpack and dotnet.
Inside my index.ts I register the translate config:
registerTranslateConfig({
lookup: (key, config) => config.strings != null ? config.strings[key] : key,
empty: key => key,
loader: lang => {
return new Promise((resolve, reject) => {
resolve({"title": "Der Titel"});
})}
});
use("en");
(The loader is hardcoded because getting the localization file also didn't work, but that's not important for now).
Inside html I use get("title")or translate("title") to get the translation.
Instead of the translation, I either read [title] or
(part) => { partCache.set(part, cb); updatePart(part, cb); }
If I assign the result of translate() to a variable, I get the following result inside the Object:
TypeError: 'caller', 'callee', and 'arguments' properties may not be accessed on strict mode functions or the arguments objects for calls to them
at Function.r (<anonymous>:1:83)
at Module../src/index.ts (http://localhost:8080/app.bundle.js:9800:10)
at __webpack_require__ (http://localhost:8080/app.bundle.js:12476:33)
at http://localhost:8080/app.bundle.js:13494:11
at http://localhost:8080/app.bundle.js:13497:12
I already tried disabling webpack strict mode.
The full class looks like the following:
export class App extends Application<Model, Message, {}> {
#property({type: Boolean})
hasLoadedStrings = false;
init(): [Model, Cmd] {
const initModel: Model = {
openPage: [{title: "Home"}, home]
}
return [initModel, Cmd.none]
}
constructor() {
super();
this.hasLoadedStrings = false;
}
shouldUpdate (changedProperties: Map<string | number | symbol, unknown>) {
return this.hasLoadedStrings && super.shouldUpdate(changedProperties);
}
async connectedCallback () {
super.connectedCallback();
await use("en");
this.hasLoadedStrings = true;
}
}
customElements.define('my-element', App);
I solved the problem by writing my own little translation library.
The lit-translate package contains errors if you use it as suggested in the documentation so feel free to use my solution:
translate.ts:
const de_config =
{
"Category": {
"Home": "Start",
},
"Home": {
"Welcome back,": "Willkommen zurück,"
};
export function translate(key: string) {
const keyParts = key.split(".");
const keyCategory = keyParts.shift();
const keyWord = keyParts.join('.');
let translationIndexes = typedKeys(de_config);
for(let translationIndex of translationIndexes)
{
if(translationIndex == keyCategory) {
let translationKeys = typedKeys(de_config[translationIndex]);
for(let translationKey of translationKeys) {
if(translationKey == keyWord) {
return de_config[translationIndex][translationKey];
}
}
}
}
return key;
}
function typedKeys<T>(o: T): (keyof T)[] {
return Object.keys(o) as (keyof T)[];
}
Access translations with:
import { translate } from './translate';
translate("Category.Home");
One could also store translation object in a different file, write a function to change language dynamically, etc...
I am trying to create a class which looks like this:
class Foo {
_bar?: () => void; // this is a property which will invoke a function, if it is defined
set bar(barFunctionDef: () => void) { // this stores a reference to the function which we'll want to invoke
this._bar = barFunctionDef;
}
get bar() { // this should either invoke the function, or return a reference to the function so that it can be invoked
return this._bar;
}
doSomething() { // this is what will be called in an external class
if(condition) {
this.bar; // this is where I would like to invoke the function (bar)
}
}
}
Essentially, I want to have a class which will store references to functions which will be optionally set. There will be many "bar" class properties which will all have the type () => void.
Is there a "correct" way to call this.bar inside doSomething in order to invoke the function that is stored on that property?
Don't know if that's what you're trying to achieve, but I set up an example of generic implementation here
type MethodReturnsVoid = () => void;
class Foo {
methods: Map<string, MethodReturnsVoid>;
constructor() {
this.methods = new Map();
}
public getMethod(methodName: string): MethodReturnsVoid {
if (this.methods.has(methodName)) {
return this.methods.get(methodName);
}
return null;
}
public setMethod(methodName: string, method: () => void): void {
this.methods.set(methodName, method);
}
}
const foo = new Foo();
const func: MethodReturnsVoid = () => console.log('func');
const anotherFunc: MethodReturnsVoid = () => console.log('anotherFunc');
foo.setMethod('func', func);
foo.setMethod('anotherFunc', anotherFunc);
const methodFunc = foo.getMethod('func');
if (methodFunc) methodFunc();
const methodAnotherFunc = foo.getMethod('anotherFunc');
if (methodAnotherFunc) methodAnotherFunc();
I am trying to serialize/deserialize an object. I am thinking that the best way would be to save the path to the file that called as part of the json, but I am unsure of how to get said path.
Can getting this path to the file (A.ts/B.ts) be done when called within the parent (Base.ts)?
Is there maybe a better approach to doing this? I am trying to take a class created in the main node process, and and pass it to a worker process, the only why to do this that I can see is to serialize/deserialize the class somehow.
// src/Base.ts
export abstract class Base {
public serialize() {
return JSON.stringify({path: '', obj: this})
}
public static deserialize(json: string) {
let { path, obj } = JSON.parse(json) as { path: string, obj: { [key: string]: any } }
let newable = require(path)
let o = new newable
return Object.assign(o, obj)
}
}
// src/filter/A.ts
export class A extends Base {
public cat: string = 'meow'
public sayHi() { return this.cat }
}
// src/filter/B.ts
export class B extends Base {
public dog: string = 'woof'
public sayHi() { return this.dog }
}
// test.ts
let serializedA = new A().serialize()
let serializedB = new B().serialize()
// Create child...
let worker = cp.fork(path.join(__dirname, './worker'), [], { silent: true })
worker.send({ serializedA, serializedB })
// worker.ts
process.on('message', msg => {
let classA = Base.deserialize(msg.serializedA)
let classB = Base.deserialize(msg.serializedB)
})
The simplest way that comes to mind would be to have a set of class names associated with callbacks that would require the appropriate classes.
// src/JsonIO.ts
export class JsonIO {
private _classes: { name: string, callback: () => { new(): any } }[] = []
public serialize(obj: any): string {
return JSON.stringify({ class: obj.constructor.name, value: obj })
}
public deserialize(json: string) {
const obj = JSON.parse(json) as { class: string, value: any }
const clazz = this._classes.find(c => c.name == obj.class)
if(!clazz) return obj.value
return Object.assign(new (clazz.callback()), obj.value)
}
public registerClass(name: string, callback: () => { new(): any }) {
this._classes.push({ name, callback })
}
}
// src/Base.ts
export abstract class Base { /* ... */ }
// src/filter/A.ts
export class A {
public cat: string = 'meow'
}
// src/filter/B.ts
export class B {
public dog: string = 'woof'
}
// test.ts
const io = new JsonIO()
io.registerClass('A', () => A /* require('filter/A.ts') */)
io.registerClass('B', () => B /* require('filter/B.ts') */)
const serializedA = io.serialize(new A)
const serializedB = io.serialize(new B)
const a = io.deserialize(serializedA)
const b = io.deserialize(serializedB)
I cannot understand how 'this' context works in typescript. I cannot access class members in methods. Below is my code
class adopterDetailCtrl {
public adopter: IAdopter;
public $router: any;
static $inject = ['app.common.services.AdopterService'];
constructor(private adopterService: app.common.services.IAdopterServices) {
this.adopter = null;
}
$routerOnActivate(next) {
if (next.params.id > 0) {
this.getAdopterById(next.params.id);
}
}
getAdopterById(adopterId: number): void {
var AdopterList = this.adopterService.getAdopterById();
AdopterList.query({ id: adopterId }, (data: adopter.IAdopter[]) => {
this.adopter = data[0];//this.adopter is undefined here. this refers to 'window'
});
}
setAdopter(data: IAdopter) {
this.adopter = data;//can access this.adopter
}
}
The this context is just the same in typescript as it as in javascript, as the code you actually run is the compiled javascript that the typescript compiler outputs.
In javascript you have two ways to deal with this issue:
Use the arrow function
Use the Function.prototype.bind function
You are probably passing getAdopterById as a callback, if that's the case then it will be easy to solve using bind:
let myobj = new adopterDetailCtrl(...);
...
someFunction(myobj.getAdopterById.bind(myobj));
You can also modify the reference for the method of the instance in the ctor:
(1)
class adopterDetailCtrl {
public adopter: IAdopter;
public $router: any;
static $inject = ['app.common.services.AdopterService'];
constructor(private adopterService: app.common.services.IAdopterServices) {
this.adopter = null;
this.getAdopterById = (adopterId: number) => {
var AdopterList = this.adopterService.getAdopterById();
AdopterList.query({ id: adopterId }, (data: adopter.IAdopter[]) => {
this.adopter = data[0];//this.adopter is undefined here. this refers to 'window'
});
}
}
$routerOnActivate(next) {
if (next.params.id > 0) {
this.getAdopterById(next.params.id);
}
}
getAdopterById: (adopterId: number) => void;
setAdopter(data: IAdopter) {
this.adopter = data;//can access this.adopter
}
}
Notice that the method declaration is empty and the implementation is set in the ctor using the arrow function.
(2)
class adopterDetailCtrl {
public adopter: IAdopter;
public $router: any;
static $inject = ['app.common.services.AdopterService'];
constructor(private adopterService: app.common.services.IAdopterServices) {
this.adopter = null;
this.getAdopterById = this.getAdopterById.bind(this);
}
$routerOnActivate(next) {
if (next.params.id > 0) {
this.getAdopterById(next.params.id);
}
}
getAdopterById(adopterId: number): void {
var AdopterList = this.adopterService.getAdopterById();
AdopterList.query({ id: adopterId }, (data: adopter.IAdopter[]) => {
this.adopter = data[0];//this.adopter is undefined here. this refers to 'window'
});
}
setAdopter(data: IAdopter) {
this.adopter = data;//can access this.adopter
}
}
Here in the ctor you reassign the bound this.getAdopterById.bind(this) to this.getAdopterById.
In both of these cases you can freely pass the getAdopterById method as a callback and not worrying about the scope of this.
Another note on the arrow functions is that this is a new feature in ES6, and if you don't choose the ES6 target in your compilation options then the compiler won't actually use this notation but instead will convert this:
class A {
private x: number;
fn(): void {
setTimeout(() => console.log(this.x), 1);
}
}
To:
var A = (function () {
function A() {
}
A.prototype.fn = function () {
var _this = this;
setTimeout(function () { return console.log(_this.x); }, 1);
};
return A;
}());
This way the scope of this is saved in _this and in the callback function _this.x is used instead of this.x.