How should I mock out react-native-extended-stylesheet in Mockery.js? - node.js

I have started using ava.js in my react native project. I have an AVA setup file that looks like this (from the Ignite starter project):
import mockery from 'mockery';
import m from 'module';
// inject __DEV__ as it is not available when running through the tests
global.__DEV__ = true;
// We enable mockery and leave it on.
mockery.enable();
// Silence the warnings when *real* modules load... this is a change from
// the norm. We want to opt-in instead of opt-out because not everything
// will be mocked.
mockery.warnOnUnregistered(false);
// Mock any libs that get called in here
// I'm looking at you react-native-router-flux, reactotron etc!
mockery.registerMock('reactotron-react-native', {});
mockery.registerMock('react-native-fetch-blob', {});
mockery.registerMock('reactotron-redux', {});
mockery.registerMock('reactotron-apisauce', {});
mockery.registerMock('react-native-animatable', { View: 'Animatable.View' });
mockery.registerMock('react-native-vector-icons/Ionicons', {});
mockery.registerMock('react-native-vector-icons/FontAwesome', {});
mockery.registerMock('react-native-config', {
CLIENT_API_USERNAME_DEV: 'username',
CLIENT_API_PASSWORD_DEV: 'password'});
mockery.registerMock('react-native-device-info', {});
mockery.registerMock('react-native-uuid-generator', {});
mockery.registerMock('react-native-cached-image', {});
mockery.registerMock('react-native-extended-stylesheet', {
EStyleSheet: {
create: {}
}
});
// mock i18n as it uses react native stufff
mockery.registerMock('react-native-i18n', {
t: key => key,
});
// Mock all images for React Native
const originalLoader = m._load;
m._load = (request, parent, isMain) => {
if (request.match(/.jpeg|.jpg|.png|.gif$/)) {
return { uri: request };
}
return originalLoader(request, parent, isMain);
};
I have created a test on a simple component I am using. However, in our app, we started using react-native-extended-stylesheet. This means we need to mock that module to get our tests to run and it is exactly this that I am having trouble with.
I have a test that looks something like this:
import { test } from 'ava';
import React from 'react';
import TheComponent from '../../App/Components/TheComponent';
import { render } from 'enzyme';
const wrapper = render(<TheComponent/>);
test('component exists', (t) => {
t.is(wrapper.length, 1);
});
TheComponent imports the following style code:
// #flow
import { Platform } from 'react-native';
import EStyleSheet from 'react-native-extended-stylesheet';
import { ApplicationStyles, Metrics, Colors, Fonts } from '../../Themes/';
const height = Math.floor((Metrics.screenWidth / 2) * Metrics.defaultImageRatio);
const width = Metrics.screenWidth / 2;
export default EStyleSheet.create({
...ApplicationStyles.screen,
item: {
width,
height,
justifyContent: 'center',
alignItems: 'center',
margin: 0,
backgroundColor: 'transparent',
}, // more styles here...
});
I have seen a few examples of using mockery's registerMock but to be honest I'm not sure I grok them all. It seems to me that I should be using something like what I used above:
mockery.registerMock('react-native-extended-stylesheet', {
EStyleSheet: {
create: {}
}
});
But running that gives me the following error:
_reactNativeExtendedStylesheet2.default.create(_extends({},
^
TypeError: _reactNativeExtendedStylesheet2.default.create is not a function
So what is the correct syntax in registerMock to get this to run? (I welcome other answers too if they address the underlying issue - how can I get on and test my component?)

Related

Firebase Realtime Database not working on NextJS only after build - Maximum call stack size exceeded

I used Firebase Realtime Database with Next.js, for back-end.
And I wrote the below code. But It works only in local, not in production. The reason I comments some lines as follows is that I want to show that the error was not caused by my code. onValue, get, set, update were all the same. The same error was raised. And also I confirmed that effect function in useEffect() runs only once.
/* lib/firebase.ts */
import { initializeApp } from 'firebase/app';
import { getDatabase } from 'firebase/database';
// Initialize Firebase
const firebaseConfig = {
...
};
const app = initializeApp(firebaseConfig);
// Initialize Realtime Database
export const database = getDatabase(app);
import { onValue, ref, update } from 'firebase/database';
import { database } from 'lib/firebase';
...
useEffect(() => {
onValue(
ref(database, `objects/${user.uid}`),
(snapshots) => {
console.log(snapshots);
// const fetchedObjects: Common.Object[] = [];
// snapshots.forEach((snapshot) => {
// fetchedObjects.push({ key: snapshot.key, ...snapshot.val() } as Common.Object);
// });
// setTimeout(() => {
// setObjects(fetchedObjects);
// setIsObjectLoaded(true);
// }, 500);
}
);
}, []);
As a result of console.log(), a snapshot object with no children was returned. But It should be a snapshot object with serveral child nodes according to database state. It is the case in local.
Only in production, the below error was raised. _app-e9aaf40698fc4780.js was bundled by Next.js, so It was so complicated that I couldn't read it. But when I deduce from comments, maybe it can be firebase/database code.
Uncaught RangeError: Maximum call stack size exceeded
at i (_app-e9aaf40698fc4780.js:1486:770)
at i (_app-e9aaf40698fc4780.js:1486:897)
at i (_app-e9aaf40698fc4780.js:1486:906)
at i (_app-e9aaf40698fc4780.js:1486:897)
at i (_app-e9aaf40698fc4780.js:1486:906)
at i (_app-e9aaf40698fc4780.js:1486:897)
at i (_app-e9aaf40698fc4780.js:1486:906)
at i (_app-e9aaf40698fc4780.js:1486:897)
at i (_app-e9aaf40698fc4780.js:1486:906)
at i (_app-e9aaf40698fc4780.js:1486:897)
1486 line's code is here.
*/ let e3,e4,e6=new class extends eW{compare(e,t){let n=e.node.getPriority(),r=t.node.getPriority(),i=n.compareTo(r);return 0===i?q(e.name,t.name):i}isDefinedOn(e){return!e.getPriority().isEmpty()}indexedValueChanged(e,t){return!e.getPriority().equals(t.getPriority())}minPost(){return eq.MIN}maxPost(){return new eq(j,new e2("[PRIORITY-POST]",e4))}makePost(e,t){let n=e3(e);return new eq(t,new e2("[PRIORITY-POST]",n))}toString(){return".priority"}},e5=Math.log(2);class e8{constructor(e){var t;this.count=parseInt(Math.log(e+1)/e5,10),this.current_=this.count-1;let n=parseInt(Array(this.count+1).join("1"),2);this.bits_=e+1&n}nextBitIsOne(){let e=!(this.bits_&1<<this.current_);return this.current_--,e}}let e9=function(e,t,n,r){e.sort(t);let i=function(t,r){let s=r-t,o,a;if(0===s)return null;if(1===s)return o=e[t],a=n?n(o):o,new eG(a,o.node,eG.BLACK,null,null);{let l=parseInt(s/2,10)+t,u=i(t,l),c=i(l+1,r);return o=e[l],a=n?n(o):o,new eG(a,o.node,eG.BLACK,u,c)}},s=new e8(e.length),o=function(t){let r=null,s=null,o=e.length,a=function(t,r){let s=o-t;o-=t;let a=i(s+1,o),u=e[s],c=n?n(u):u;l(new eG(c,u.node,r,null,a))},l=function(e){r?(r.left=e,r=e):(s=e,r=e)};for(let u=0;u<t.count;++u){let c=t.nextBitIsOne(),h=Math.pow(2,t.count-(u+1));c?a(h,eG.BLACK):(a(h,eG.BLACK),a(h,eG.RED))}return s}(s);return new eY(r||t,o)},e7,te={};class tt{constructor(e,t){this.indexes_=e,this.indexSet_=t}static get Default(){return(0,f.hu)(te&&e6,"ChildrenNode.ts has not been loaded"),e7=e7||new tt({".priority":te},{".priority":e6})}get(e){let t=(0,f.DV)(this.indexes_,e);if(!t)throw Error("No index defined for "+e);return t instanceof eY?t:null}hasIndex(e){return(0,f.r3)(this.indexSet_,e.toString())}addIndex(e,t){(0,f.hu)(e!==e$,"KeyIndex always exists and isn't meant to be added to the IndexMap.");let n=[],r=!1,i=t.getIterator(eq.Wrap),s=i.getNext();for(;s;)r=r||e.isDefinedOn(s.node),n.push(s),s=i.getNext();let o;o=r?e9(n,e.getCompare()):te;let a=e.toString(),l=Object.assign({},this.indexSet_);l[a]=e;let u=Object.assign({},this.indexes_);return u[a]=o,new tt(u,l)}addToIndexes(e,t){let n=(0,f.UI)(this.indexes_,(n,r)=>{let i=(0,f.DV)(this.indexSet_,r);if((0,f.hu)(i,"Missing index implementation for "+r),n===te){if(!i.isDefinedOn(e.node))return te;{let s=[],o=t.getIterator(eq.Wrap),a=o.getNext();for(;a;)a.name!==e.name&&s.push(a),a=o.getNext();return s.push(e),e9(s,i.getCompare())}}{let l=t.get(e.name),u=n;return l&&(u=u.remove(new eq(e.name,l))),u.insert(e,e.node)}});return new tt(n,this.indexSet_)}removeFromIndexes(e,t){let n=(0,f.UI)(this.indexes_,n=>{if(n===te)return n;{let r=t.get(e.name);return r?n.remove(new eq(e.name,r)):n}});return new tt(n,this.indexSet_)}}/**
I can't interpret the code, and I can't figure out the cause, so I'm going crazy. Help me.
+) I also tested on the new Next.js project created by create-next-app.
npx create-next-app#latest --typescript
cd {project folder name}
npm install firebase
And the code I wrote is here.
import "../styles/globals.css";
import type { AppProps } from "next/app";
import { useEffect } from "react";
import { initializeApp } from "firebase/app";
import { getDatabase, onValue, ref } from "firebase/database";
function MyApp({ Component, pageProps }: AppProps) {
useEffect(() => {
const firebaseConfig = {
...
};
const app = initializeApp(firebaseConfig);
const db = getDatabase(app);
// It works only in local
onValue(ref(db, "???"), (snapshots) => console.log(snapshots));
}, []);
return <Component {...pageProps} />;
}
export default MyApp;
As a result, like my project, the same error was raised. But It works well in local.
I'm the author of this post.
I resolved this problem by downgrading Next.js version from 12.3.1 to 12.3.0.

NextJS useRouter() returns null in unit test with Storybook add-on

I'm running into an issue when integrating my stories into my unit tests using Jest & React Testing Library in a NextJS app. In Storybook, my NextJS components render without issue with the Storybook Addon Next Router working as expected. However, in my Jest test file, my test throws an error because useRouter() returns null. I believe I've setup all the addons correctly.
My setup:
NextJS 12
Jest & React Testing Library
Storybook
Storybook Addon Next Router (for using Next router inside Storybook)
#storybook/testing-react (for integrating my stories, args & params with testing library)
The problem:
I've setup all the files according to the docs. The story renders fine inside Storybook and the useRouter() works thanks to the Storybook Next addon. However, the composeStories() function from #storybook/testing-react seems to fail to properly initialize the Next router Provider from the first addon. My unit test fails with the following error:
Test suite failed to run
TypeError: Cannot read property 'locale' of null
And points to the following line inside my component:
// Events.tsx
const { locale = 'en' } = useRouter();
This is my test file:
// Events.test.tsx
import React from 'react';
import { render, screen } from '#testing-library/react';
import { composeStories } from '#storybook/testing-react';
import * as stories from './Events.stories'
const { WithSeasonsAndEvents } = composeStories(stories);
describe('Events Screen', ()=> {
render(<WithSeasonsAndEvents />);
it('renders input data', ()=> {
// set up test
});
});
And my stories file:
// Events.stories.tsx
import React from 'react';
import { ComponentStory, ComponentMeta } from '#storybook/react';
import Events from './Events';
export default {
title: 'Events/Events Screen',
component: Events
} as ComponentMeta<typeof Events>;
const Template: ComponentStory<typeof Events> = (args) => <Events {...args} />;
export const WithSeasonsAndEvents = Template.bind({});
WithSeasonsAndEvents.args = {
// many args here
};
This story renders fine in Storybook. useRouter() works as expected inside my story and I am able to use all of its properties including pathname and locale. However, for some reason the useRouter() function returns null when the composed story is being rendered by React Testing Library.
What I have tried:
Verified installing #storybook/testing-react and set up my global configuration in jest setup file:
// jest-setup.ts
import '#testing-library/jest-dom/extend-expect';
import { setGlobalConfig } from '#storybook/testing-react';
// Storybook's preview file location
import * as globalStorybookConfig from './.storybook/preview';
setGlobalConfig(globalStorybookConfig);
Verified that jest reads my setup file and finds my Storybook preview file
Verified both .storybook/preview.js and .storybook/main.js match the usage guide:
preview.js
// .storybook/preview.js
import '../styles/tailwind.css';
import { RouterContext } from "next/dist/shared/lib/router-context"; // next 12 https://storybook.js.org/docs/react/writing-stories/parameters
export const parameters = {
actions: { argTypesRegex: "^on[A-Z].*" },
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/,
},
},
nextRouter: {
Provider: RouterContext.Provider,
locale: 'en',
},
}
main.js
// .storybook/main.js
module.exports = {
"stories": [
"../stories/**/*.stories.mdx",
"../stories/**/*.stories.#(js|jsx|ts|tsx)",
"../components/**/*.stories.#(js|jsx|ts|tsx|mdx)"
],
"addons": [
"#storybook/addon-links",
"#storybook/addon-essentials",
"storybook-addon-next-router",
],
"framework": "#storybook/react"
}
Stories render fine in Storybook, all useRouter() features work, and when I console.log the useRouter() return value, I get the full suite of NextJS useRouter object properties:
// >> console.log(useRouter()); inside Storybook
{
route: "/",
pathname: "/",
query: {},
asPath: "/",
events: {},
isFallback: false,
locale: 'en'
}
However, on the unit test, when logging the return value of useRouter(); inside my component, it returns null. Understandably, because it is null, the { locale } variable assignment is throwing an error in my unit test.
When logging the value of useRouter variable, both inside my Storybook preview and inside my unit test I get the following function:
// >> console.log(useRouter.toString())
function useRouter() {
return _react.default.useContext(_routerContext.RouterContext);
}
Does anyone have any idea on what can be going wrong? I'm fairly new to Storybook but I've tried looking through GitHuB issues and online and haven't been able to solve. No idea why useRouter() returns null inside Jest if the composeStories() fn should take care of resolving my Storybook params & args. Any insight would be appreciated.
I was running with the same issue. I solved it by exporting WithNextRouter as a decorator on preview.js
// preview.js
import { WithNextRouter } from 'storybook-addon-next-router/dist/decorators';
export const decorators = [WithNextRouter];

How to use react-intl with jest

I'm working with React Testing Library an I need to get the translation for a text, we mocked in our setupTest file the react-intl library:
jest.mock('react-intl', () => {
const reactIntl = require.requireActual('react-intl');
const intl = reactIntl.createIntl({
locale: 'en'
});
return {
...reactIntl,
useIntl: () => intl
};
});
but I don't know how to use it in the test, could someone provide me an complete examble of a test using the library to get a translation, please?
I tried to do it in this way:
let intl = useIntl();
let i18n = {
header: intl.formatMessage({
id: 'header.myHeader',
defaultMessage: 'header.myHeader'
})
};
but there are any messages in intl from the locales.
Regards.
Don't mock it, try and directly wrap your component with IntlProvider, or even better, create a helper render where you wrap with IntlProvider and render it with that:
import {IntlProvider} from 'react-intl`;
import {render} from '#testing-library/react';
const renderWithReactIntl = (component, locale, messages) => {
return render(<IntlProvider locale={locale} messages={messages}>
{component}
</IntlProvider>
);
};
Within your test() or it() just wrap your unconnected component in renderWithReactIntl:
const { /testing library selector goes here/ }
= renderWithReactIntl(<YourComponent />, messages, locale)

Mocking node dependency for Typescript with Mocha / Sinon

I have a class which controls an audio receiver using an external library marantz-avr:
let AVReceiver = require('marantz-avr');
class MarantzPlugin {
hardwareInstance;
constructor(pluginConfiguration) {
const receiver = new AVReceiver(pluginConfiguration.settings.ip);
this.hardwareInstance = receiver;
}
turnPowerOn() {
this.hardwareInstance.setPowerState('on').then(res => res, error => console.error(error));
}
}
export default MarantzPlugin;
I want to unit test this class. In order to do so I have to mock the marantz-avr library since this library only works when an actual receiver is found on the provided ip address.
In the test below I mock the marantz-avr, however the MarantzPlugin still uses the original AVReceiver instead of the mocked one.
import { suite, test, slow, timeout } from 'mocha-typescript';
import * as mocha from 'mocha';
import * as assert from 'assert';
import * as sinon from 'sinon';
import * as should from 'should';
import MarantzPlugin from './';
let AVReceiver = require('marantz-avr');
#suite
class MarantzPluginTest {
public create() {
before(() => {
sinon.stub(AVReceiver.prototype, 'AVReceiver').callsFake(() => {
return {
setPowerState: () => {
return true;
}
}
});
});
let marantz = new MarantzPlugin({
id: 'MARANTZ',
settings: {
ip: '192.168.178.2',
}
});
marantz.setPowerState('on');
}
}
I've looked into http://sinonjs.org/how-to/link-seams-commonjs/ but when implemented it gave me Error: Cannot find module 'marantz-avr'
Does anyone see what I am missing here, or perhaps have a better way to unit test these kind of classes?

How to load google maps javascript api in Aurelia javascript application?

I found npm module google-maps-api and installed it (npm install google-maps-api) but I can't figure out how to import it with systemjs/jspm (jspm cannot find this module). Here's the configuration from my config.js:
"paths": {
"*": "app/dist/*.js",
"github:*": "app/jspm_packages/github/*.js",
"npm:*": "app/jspm_packages/npm/*.js" }
So, when I try do something like this:
import {mapsapi} from 'google-maps-api';
I get the following error in browser console:
GET https://localhost:44308/app/dist/google-maps-api.js 404 (Not Found)
Looking at the filesystem I see that npm installed the module under app/node_modules/google-maps-api so how do I reference it in the import clause from Aurelia module?
I found a solution and answering my own question here:
I finally figured how to install it with jspm, so you just need to give a hint to jspm to install it from npm like so:
jspm install npm:google-maps-api
After jspm completes installation, import (no {} syntax) works fine:
import mapsapi from 'google-maps-api';
then I inject it in constructor and instantiate geocoder api:
#inject(mapsapi('InsertYourGMAPIKeyHere'))
export class MyClass {
constructor(mapsapi) {
let that = this;
let maps = mapsapi.then( function(maps) {
that.maps = maps;
that.geocoder = new google.maps.Geocoder();
});
...
}
In order to create map on a div I use EventAggregator to subscribe for router:navigation:complete event and use setTimeout to schedule map creation:
this.eventAggregator.subscribe('router:navigation:complete', function (e) {
if (e.instruction.fragment === "/yourRouteHere") {
setTimeout(function() {
that.map = new google.maps.Map(document.getElementById('map-div'),
{
center: new google.maps.LatLng(38.8977, -77.0366),
zoom: 15
});
}, 200);
}
});
Here's a complete view-model example that uses attached() to link to your view.
import {inject} from 'aurelia-framework';
import mapsapi from 'google-maps-api';
#inject(mapsapi('your map key here'))
export class MapClass {
constructor(mapsAPI) {
this.mapLoadingPromise = mapsAPI.then(maps => {
this.maps = maps;
});
}
attached() {
this.mapLoadingPromise.then(() => {
var startCoords = {
lat: 0,
long: 0
};
new this.maps.Map(document.getElementById('map-div'), {
center: new this.maps.LatLng(startCoords.lat, startCoords.long),
zoom: 15
});
});
}
}
For everyone using Typescript and getting "Cannot find module 'google-maps-api'" error,
you need to add typings to the solution. Something like this works
declare module 'google-maps-api' {
function mapsapi(apikey: string, libraries?, onComplete?);
namespace mapsapi {
}
export = mapsapi;
}
and then import it like this
import * as mapsapi from 'google-maps-api';

Resources