extended Selector Function with fs - css-selectors

I try to write an own Selector that will read an XML and get an XPath from it. But my idea does not work any suggestion?
I use
//xpath.js
import { Selector } from 'testcafe';
import fs from "fs";
import downloadsFolder from "downloads-folder";
import {DOMParser} from 'xmldom'
const elementByXPath = Selector(xpath => {
const items = [];
var xml = fs.readFileSync(downloadsFolder()+'export.xml', 'utf8').toString();
var doc = new DOMParser().parseFromString(xml);
//for debug reson
console.log(xml);
/*
Logik for XPath here
*/
items.push('0');
return items;
});
export default function (xpath) {
return Selector(elementByXPath(xpath));
}
and the fixture is
//testfixture.js
import { Selector } from 'testcafe';
import XPathSelector from './xpath';
fixture `SM Run 1`
test('Per Anhalter durch die Galaxies', async t => {
await t
.navigateTo("http://www.ifoerster.com")
await t
.expect(elementByXPath('test')).eql(1)
console.log("Fertig")
});
In my understanding, this has to work.

Related

Jest Mock returns undefined instead of value

I am using Jest to test a react component. I am trying to mock a function from other dependency. The function from dependency should return an array, but it is showing undefined on the console.
Below file is the tsx file, when I click the button, it should call the dependency function to get the list of the Frames.
ExitAppButton.tsx:
import React, { useContext, useState } from 'react';
import { TestContext } from '../ContextProvider';
import { useDispatch } from 'react-redux';
const ExitAppButton = (props: any): JSX.Element => {
const { sdkInstance } = useContext(TestContext);
const exitAppClicked = () => {
const appList = sdkInstance.getFrames().filter((app: any) => {app.appType === "Test App"}).length}
test file, SignOutOverlay.test.tsx:
import * as React from 'react';
import { fireEvent, render, screen } from '#testing-library/react';
import SignOutOverlay from '.';
import ExitAppButton from './ExitAppButton';
import { TestContext } from '../ContextProvider';
import { Provider } from 'react-redux';
import configureStore from 'redux-mock-store';
const api = require('#praestosf/container-sdk/src/api');
const mockStore = configureStore([]);
jest.mock('#praestosf/container-sdk/src/api');
api.getFrames.mockReturnValue([{appType:"Test App"},{appType:"Test App"},{appType:"Not Test App"}]);
describe('Test Exit app Button', () => {
const renderExitAppButton = () => {
const store = mockStore([{}]);
render(
<Provider store={store}>
<TestContext.Provider value={{ sdkInstance: api }}>
<SignOutOverlay>
<ExitAppButton/>
</SignOutOverlay>
</TestContext.Provider>
</Provider>
);
};
it('should to be clicked and logged out', () => {
renderExitAppButton();
fireEvent.click(screen.getByTestId('exit-app-button-id'));
});
This is the dependency file, api.js
const getFrames = () => {
let frames = window.sessionStorage.getItem('TestList');
frames = frames ? JSON.parse(frames) : [];
return frames
};
const API = function () { };
API.prototype = {
constructor: API,
getFrames
};
module.exports = new API();
I mocked the getFrame function to return an array of 3 objects, but when running the test case, it is returning undefined. Below error was showing:
TypeError: Cannot read property 'filter' of undefined
Am I mocking this correct?
I think it's because api.getFrames is undefined and not a mock.
Try changing your mock statement to this:
jest.mock('#praestosf/container-sdk/src/api', () => ({
getFrames: jest.fn(),
// add more functions if needed
}));
Turns out, I have the other file with the same test name which is causing the problem. I am beginner for Jest, a tip for developer like me, we should always run test case file alone using
jest file.test.tsx
Not all files at a time:
jest

Cannot spy on mocked functions of a class using a manual mock

Note: I have created a full paired down Github repo that you can download and checkout for yourself here
I am trying to mock out a non-default exported class in a module using a manual mock in the folder __mocks__ directly adjacent to the file. This works fine and I can get the mock to load correctly in my tests. However, if I export the mocked function as described here in the docs it doesn't seem to actually give me a reference to the function that the mocked class calls in the test. This means when I call
expect(getAppDetailMock).toHaveBeenCalledTimes(1);
It fails as it has not been called at all.
expect(jest.fn()).toHaveBeenCalledTimes(expected)
Expected number of calls: 1
Received number of calls: 0
My suspicion is that it has something to do with the class itself ending up with a new version of the function, and not the very same reference I am importing... I say this because I did the exact same pattern to mock out a package in my node_modules which was not a class and it worked perfectly fine.
Although you can check out the full code example here on github I have copied my files below for full clarity. Any ideas on why this is not working as I would expect?
// api/__mocks__/clients.ts
export const getAppDetailMock = jest.fn();
export const AppsClient = jest.fn().mockImplementation(() => {
return {
getAppDetail: getAppDetailMock,
};
});
// App.test.js
import React from "react";
import {
render,
screen,
waitForElementToBeRemoved,
} from "#testing-library/react";
import App from "./App";
import { getAppDetailMock } from "./api/__mocks__/clients";
jest.mock("./api/clients");
describe("<App /> ", () => {
beforeEach(() => {
getAppDetailMock.mockReset();
});
test("Should show not found message if app does not exist", async () => {
const appId = "8500f5dd-8b41-4cb8-95fa-246b1f25855b";
getAppDetailMock.mockResolvedValue(null);
render(<App appId={appId} />);
await waitForElementToBeRemoved(() => screen.getByText("Loading"));
expect(getAppDetailMock).toHaveBeenCalledTimes(1);
expect(getAppDetailMock).toHaveBeenCalledWith(appId);
expect(screen.getByText(`App not found found`)).toBeInTheDocument();
});
test("Should show not found message if app does not exist", async () => {
const appId = "8500f5dd-8b41-4cb8-95fa-246b1f25855b";
getAppDetailMock.mockResolvedValue({ appId });
render(<App appId={appId} />);
await waitForElementToBeRemoved(() => screen.getByText("Loading"));
expect(getAppDetailMock).toHaveBeenCalledTimes(1);
expect(getAppDetailMock).toHaveBeenCalledWith(appId);
expect(
screen.getByText(`App found with id '${appId}'`)
).toBeInTheDocument();
});
});
// App.tsx
import React, { useState, useEffect } from "react";
import "./App.css";
import { AppsClient, AppDetailDto } from "./api/clients";
type AppProps = {
appId: string;
};
const App: React.FC<AppProps> = ({ appId }) => {
const [hasLoaded, setHasLoaded] = useState(false);
const [appDetail, setAppDetail] = useState<AppDetailDto | null>(null);
useEffect(() => {
const getAppDetail = async (appId: string) => {
try {
setHasLoaded(false);
const result = await new AppsClient().getAppDetail(appId);
setAppDetail(result);
setHasLoaded(true);
} catch (error) {}
};
if (!hasLoaded) {
getAppDetail(appId);
}
}, [appId, hasLoaded]);
if (!hasLoaded) {
return <>Loading</>;
} else if (!appDetail) {
return <>App not found found</>;
}
return <>App found with id '{appDetail.appId}'</>;
};
export default App;
// api/clients.ts
import { BaseClient } from "./baseClient";
export interface AppDetailDto {
appId: string;
}
export class AppsClient extends BaseClient {
getAppDetail(appId: string): Promise<AppDetailDto> {
return Promise.resolve({ appId });
}
}
Try checking to see if your getAppDetail has been called instead.
so something more like:
expect(getAppDetail).toHaveBeenCalledTimes(1);
Or, if you can, pull in the entire mocked component and check that it's specific getAppDetail has been called, like:
expect(AppsClient.getAppDetail).toHaveBeenCalledTimes(1);
Your suspicion is probably correct... the fact that you're importing a separate instance of the getAppDetailMock, that instance of it won't have been called, as it's a separate import not related to the AppsClient.getAppDetail mock.
Try import direct from ./api/clients with #ts-ignore
// #ts-ignore
import { getAppDetailMock } from "./api/clients";
or
import * as Clients from './api/clients';
import * as ClientsMock from './api/__mocks__/clients';
jest.mock("./api/clients");
const {
getAppDetailMock
} = (Clients as unknown) as typeof ClientsMock;

In React, how do I instantiate a class without a constructor in my unit test?

I'm using React 16.13.0. I have the below class (Missions.ts) ...
class Missions {
repo() : MissionRepository {
return getRepository(Mission) as MissionRepository;
}
async getAll() : Promise<Mission[]> {
return this.repo().find();
}
...
async removeVolunteerFromMission(missionId: string) {
const missions = this.repo();
const mission = await missions.findById(missionId);
mission.volunteerId = '';
mission.status = MissionStatus.unassigned;
return missions.update(mission);
}
...
}
export default new Missions();
I'm trying to write some unit tests for the methods but I'm having trouble figuring out how to instantiate my class. I have tried the below ...
import React from "react";
import { CustomRepository, getRepository } from 'fireorm';
import { BaseRepository } from './BaseRepository'
import { Mission } from './schema';
import { Missions } from './Missions';
describe('Missions', () => {
describe('#removeVolunteerFromMission', () => {
const missionId = "1234";
const mission = new Mission();
beforeEach(() => {
const mockFindById = jest.fn();
BaseRepository.prototype.findById = mockFindById;
mockFindById.mockReturnValue(Promise.resolve(mission));
const mockUpdate = jest.fn();
BaseRepository.prototype.update = mockUpdate;
mockUpdate.mockReturnValue(Promise.resolve(mission));
});
it('unassigns volunteer', () => {
const obj = new Missions();
obj.removeVolunteerFromMission(missionId)
expect(mockFindById).toHaveBeenCalledTimes(1);
expect(mockUpdate).toHaveBeenCalledTimes(1);
expect(mission.volunteer).toBe(null);
});
});
});
However upon running my test using "hpm test", I'm getting this error
TypeError: _Missions.Missions is not a constructor
on the line
const obj = new Missions()
How do I instantiate my class within the test?
The problem is you're exporting an instance of Missions class and not the class itself, and you cannot instantiate an instance again. So export only the class and then instantiate it where you want to use it.
export default Missions;
Instantiate it where you want to use it:
const missions = new Missions();
Also default export are imported without the curly brackets, so do this:
import Missions from './Missions';

ReactJS/Reflux Converting mixins to ES6

I'm trying to convert the following Compenent to ES6, I already modified all the code for Store/Action, I can't figure out a solution to replace mixins and use normal ES6.
This is for a front end project, I'm just trying to parse JSON Data into components using Reflux.
import React from 'react';
import Reflux from 'reflux';
import {Jumbotron} from 'react-bootstrap';
import {Button} from 'react-bootstrap';
import {Link} from 'react-router';
import PopularActions from '../Actions/DragAction';
import PopularStore from '../Stores/DragStore';
import createReactClass from 'create-react-class';
const JSONViewerReflux = createReactClass({
//this is the part that I need in ES6
mixins: [Reflux.connect(PopularStore, 'DragStore')],
DragStore: null,
render() {
const store = this.DragStore ? this.DragStore : this.state.DragStore;
if(store) {
return (
<Jumbotron>
<h2>JSON Elements</h2>
{
store.map(({Name, Id}) => <div>
<h3>{Name} </h3>
<h3>{Id}</h3>
</div>)
}
</Jumbotron>
);
} else {
setTimeout(PopularActions.fetchPopular, 2000);
return <span />
}
}
});
export default JSONViewerReflux;
//Here is the store/action
import Reflux from 'reflux';
import $ from 'jquery';
import DragActions from '../Actions/DragAction';
const data = [];
function parseData(fetchedData) {
console.log(fetchedData);
const dragitemData = fetchedData.users;
const dragitem = dragitemData.map(({name, ID}) => {
return {
Name: name,
Id: ID
};
});
this.dragitem = dragitem;
this.trigger(this.dragitem);
}
const DragStore = Reflux.createStore({
listenables: [DragActions],
Url: 'https://api.myjson.com/bins/dvx65',
dragitem: [],
init() {
this.fetchPopular();
},
fetchPopular() {
const that = this;
$.ajax({
url: this.Url,
method: 'GET'
}).done(parseData.bind(this))
}
});
export default DragStore;
import Reflux from 'reflux';
const DragAction = Reflux.createActions([
'fetchPopular'
]);
export default DragAction;
It works but I just want it in ES6 like the rest of the project so I can use easily the {NAME} and {ID} with other components.
mixins are no longer supported in React.Component (ES6 and beyond).
Alternative is discussed in this post

React-intl for non components

Currently I have the following code to expose react-intl to non-components, but it throws an error for intl as undefined.
I have created a separate component as 'CurrentLocale' and inject-intl to it. The exporting function t will use intl formatMessage from CurrentLocale context.
import React from 'react';
import {injectIntl} from 'react-intl';
import PropTypes from 'prop-types';
import { flow } from 'lodash';
class CurrentLocale extends React.Component {
constructor(props,context){
super();
console.log(context,props);
console.log(this.formatMessage);
const { intl } = this.context.intl;//this.props;
this.formatMessage = intl.formatMessage;
}
render() {
return false;
}
}
CurrentLocale.contextTypes={
intl:PropTypes.object,
};
injectIntl(CurrentLocale);
function intl() {
return new CurrentLocale();
}
function formatMessage(...args) {
return intl().formatMessage(...args);
}
const t = opts => {
const id = opts.id;
const type = opts.type;
const values = opts.values;
let t;
switch (type){
case 'message':
default:
t = formatMessage(id, values);
}
return t;
}
export default t;
t is called as in another plain javascript file as,
import t from './locale/t';
t( { type: 'message', id:'button.Next'});
Following is the error message.
Thanks in advance.
There's also another approach very simple I used for solving a similar problem: Provide access to the intl object for a non-component:
import { IntlProvider, addLocaleData } from 'react-intl';
import localeDataDE from 'react-intl/locale-data/de';
import localeDataEN from 'react-intl/locale-data/en';
import { formMessages } from '../../../store/i18n'; // I defined some messages here
import { Locale } from '../../../../utils'; //I set the locale fom here
addLocaleData([...localeDataEN, ...localeDataDE]);
const locale = Locale.setLocale(); //my own methods to retrieve locale
const messages = Locale.setMessages(); //getting messages from the json file.
const intlProvider = new IntlProvider({ locale, messages });
const { intl } = intlProvider.getChildContext();
export const SCHEMA = {
salutation: {
label: intl.formatMessage(formMessages.salutationLabel),
errormessages: {
required: intl.formatMessage(formMessages.salutationError),
},
},
academic_title_code: {
label: intl.formatMessage(formMessages.academicTitleLabel),
},
};
It's working like a charm!
UPDATE for v3.x
After migration to react-intl 3.x
import { createIntl, createIntlCache } from 'react-intl'
import { formMessages } from '../../../store/i18n'; // I defined some messages here
import { Locale } from '../../../../utils'; //I set the locale fom here
const locale = Locale.setLocale(); //my own methods to retrieve locale
const messages = Locale.setMessages(); //getting messages from the json file.
// This is optional but highly recommended
// since it prevents memory leak
const cache = createIntlCache();
const intl = createIntl({ locale, messages }, cache)
export const SCHEMA = {
salutation: {
label: intl.formatMessage(formMessages.salutationLabel),
errormessages: {
required: intl.formatMessage(formMessages.salutationError),
},
},
academic_title_code: {
label: intl.formatMessage(formMessages.academicTitleLabel),
},
};
There's a new way to do it pretty easily with createIntl, it returns an object that you can use outside React components. Here's an example from the documentation.
import {createIntl, createIntlCache, RawIntlProvider} from 'react-intl'
// This is optional but highly recommended
// since it prevents memory leak
const cache = createIntlCache()
const intl = createIntl({
locale: 'fr-FR',
messages: {}
}, cache)
// Call imperatively
intl.formatNumber(20)
// Pass it to IntlProvider
<RawIntlProvider value={intl}>{foo}</RawIntlProvider>
I personally store the intl object in Redux store so I can access it everywhere in my app.
This line: const { intl } = this.context.intl; should be const { intl } = this.context;
Here is a reference post of someone doing almost the exact same thing as you are: https://github.com/yahoo/react-intl/issues/983#issuecomment-342314143
In the above the author is creating essentially a singleton that is exported instead of creating a new instance each time like you have above. This might be something you want to consider as well.
There's also another way solving a similar problem to used react-intl formatMessage for non-components.
Create a LocaleStore.js store file.
import _formatMessage from "format-message";
export default class LocaleStore {
formatMessage = (id, values) => {
if (!(id in this.messages)) {
console.warn("Id not found in intl list: " + id);
return id;
}
return _formatMessage(this.messages[id], values);
};
}
import LocaleStore your CombinedStores.js
import LocaleStore from "./stores/LocaleStore";
import en from "./translations/en";
import de from "./translations/de";
import Global from "./stores/global"
const locale = new LocaleStore("en", {
en,
de
});
export default {
global:new Global(locale)
}
now you can use this in your GlobalStore.js
class GlobalStore {
constructor(locale) {
this.locale = locale;
}
formatMessage=(message_is,formatLanguage="en")=> {
return this.locale.formatMessage(message_id, formatLanguage);
}
}
react-intl decorates your React.Component with wrapped component which is injected internationalized message dynamically so that the locale data is able to be loaded dynamically.
import { injectIntl } from 'react-intl';
class MyComponent extends Component {
render() {
const intl = this.props;
const title = intl.formatMessage({ id: 'title' });
return (<div>{title}</div>);
}
};
export default injectIntl(MyComponent);
It can be applied only in view layer such as React.Component.
react-intl can't be used in Vanilla JS. For example,
export default const rules = {
noSpace(value) {
if (value.includes(' ')) {
return 'Space is not allowed.';
}
}
};
One of alternative is react-intl-universal. It can be used not only in Component but also in Vanilla JS.
For example:
import intl from 'react-intl-universal';
export default const rules = {
noSpace(value) {
if (value.includes(' ')) {
return intl.get('no_space');
}
}
};
See react-intl-universal online example
If you can accept to use a function component I prefer to use the useIntl hook
https://reactjs.org/docs/components-and-props.html#function-and-class-components
I can then get values like this:
import { useIntl } from "react-intl";
const intl = useIntl()
intl.formatMessage({ id: 'myId' }),
https://formatjs.io/docs/react-intl/api/#useintl-hook

Resources