Use workbox in Jest test - javascript

My Vue app has a component that listens to a service worker to determine if the app needs to update. I would like to add a simple test that the component displays like it should. However, I need service workers and workbox working properly in a jest. I got service workers working with service-worker-mock, however I can't find any resources on how to incorporate workbox. Here is my test so far:
import UpdateNotification from '#/components/UpdateNotification.vue';
import { workbox } from 'workbox-sw';
const makeServiceWorkerEnv = require('service-worker-mock');
describe('UpdateNotification', () => {
beforeEach(() => {
Object.assign(global, makeServiceWorkerEnv(), workbox);
jest.resetModules();
});
it('displays', async() => {
require('../../../src/service-worker');
render(UpdateNotification);
});
});
And here is my service worker:
/* eslint-disable no-undef*/
workbox.precaching.precacheAndRoute(self.__precacheManifest);
workbox.routing.registerNavigationRoute(workbox.precaching.getCacheKeyForURL('index.html'));
My solution so far is breaking at require('../../../src/service-worker'); in the test due to:
TypeError: Cannot read property 'precacheAndRoute' of undefined
1 | /* eslint-disable no-undef*/
> 2 | workbox.precaching.precacheAndRoute(self.__precacheManifest);
That import of { workbox } is just kind of guesswork, and I think that's where I'm going wrong.

Related

Jest virtual mock: how do I troubleshoot this failure?

So I have this import statement in a module that I'm trying to test using jest 25.1 running on node 11.1.0. The import statement is for a module that is only available when running on the jvm's nashorn runtime, so I'm using jest's virtual mock to control the behavior in the unit tests. Here's what the import statement looks like in the module under test:
import RequestBuilder from 'nashorn-js/request_builder'
...and after the other lines in the import block, this:
const request = RequestBuilder.create('some-string')
.sendTo('some-other-string')
.withAction('yet-another-string')
.getResultWith( consumer => consumer.result( consumer.message().body() ) )
export const functionA = () => {...} // uses 'request' variable
export const functionB = () => {...} // uses 'request' variable
In the corresponding .spec file, I have this virtual mock setup:
const mockGetResultWith = {
getResultWith: jest.fn()
}
const mockWithAction = {
withAction: jest.fn().mockImplementation(() => mockGetResultWith)
}
const mockSendTo = {
sendTo: jest.fn().mockImplementation(() => mockWithAction)
}
const mockBuilder = {
create: jest.fn().mockImplementation(() => mockSendTo)
}
jest.mock(
'nashorn-js/request_builder',
() => mockBuilder,
{ virtual: true }
)
require('nashorn-js/request_builder')
import { functionA, functionB } from './module-under-test'
I have been trying unsuccessfully to get past this failure from jest:
● Test suite failed to run
TypeError: Cannot read property 'create' of undefined
35 | }
36 |
> 37 | const verify = RequestBuilder.create('some-string')
| ^
38 | .sendTo('some-other-string')
39 | .withAction('yet-another-string')
40 | .getResultWith( consumer => consumer.result( consumer.message().body() ) )
I've tried all kinds of different mock structures, using require vs import, etc, but haven't found the magic bullet.
As near as I can tell, it does not appear that the RequestBuilder import statement is even invoking the virtual mock. Or at least, if I add console.log() statements to the virtual mock factory function, I never see those log messages in the output.
Anybody have any idea what I'm missing or what else to try? I have pretty much the same pattern in use in other parts of the code, where this setup works, but for some mystical reason with this module, I can't get the virtual mock working. Any help is greatly appreciated.
So, the problem here turned out to be jest's implementation of import vs require in the .spec file.
By changing this line:
import { functionA, functionB } from './module-under-test'
To this:
const module = require('./module-under-test')
const functionA = module.functionA
const functionB = module.functionB
The module under test now loads successfully, and the tests run as expected.
I have no explanation for this, and haven't been able to find jest documentation describing why I'd get any different behavior between require vs import. In fact, I have the mock configuration setup before any import statements as described here:
https://github.com/kentcdodds/how-jest-mocking-works
If anybody out there understands what's going on with this import behavior, or has a link describing what I'm missing, I'd sure appreciate the info.

How can I run a global setup script before any mocha test in watch mode

I want to run mocha tests in a TDD manner (--watch mode), which works fine.
But I have a "global setup.js" file, which mocks part of the application, that is used by most tests.
If I run the tests normally or in watch mode for the first time everything is fine because the setup script loads.
If a test or source file is changed, however, only the relevant tests run (sounds awesome in theory) but since my global mocking script is not run the tests fail.
How can I execute a setup script each time (once per overall test run) even in watch mode with mocha?
This is the command I use:
vue-cli-service test:unit --watch
# pure mocha would be (I assume)
mocha 'tests/**/*.spec.js' --watch
I have tried using the --require and --file option, but they are also not rerun on file changes.
I am using a vue app created with the VUE CLI and this is how my code looks
// setup.spec.js
import { config } from "#vue/test-utils";
before(() => {
config.mocks["$t"] = () => {};
});
// some_test.spec.js
import { expect } from "chai";
import { shallowMount } from "#vue/test-utils";
import MyComp from "#/components/MyComp.vue";
describe("MyComp", () => {
it("renders sth", () => {
const wrapper = shallowMount(MyComp);
expect(wrapper.find(".sth").exists()).to.be.true;
});
});
This isn't a very satisfying answer because it feels like there should be a better way but you can import your setup script into the individual test files.
For example:
// some_test.spec.js
import 'setup.spec.js' //<-- this guy right here
import { expect } from "chai";
import { shallowMount } from "#vue/test-utils";
import MyComp from "#/components/MyComp.vue";
describe("MyComp", () => {
it("renders sth", () => {
const wrapper = shallowMount(MyComp);
expect(wrapper.find(".sth").exists()).to.be.true;
});
});
Feels sub optimal, but it is better than replicating logic everywhere.
Have you tried utilizing .mocharc.js file to setup your mocha configurations before you run a test?
'use strict';
module.exports = {
package: './package.json',
watch: true,
timeout: 100000
};

Auth0 authentication with Gridsome server side rendering breaks with window is undefined

I've implemented the auth0 Vuejs according to their tutorial with Gridsome, and it worked fine in develop.
However, when I run gridsome build the build fails because window is undefined in a server context.
I've found a few issues in Auth0-js lib that claim that Auth0 should only be used in client side, however, due to the way Gridsome works, I can't seem to find a way to only load the Auth0-js in client side.
Gridsome has main.js where I would add plugins, and in there, I define the routing for authentication.
Main.js
import AuthServicePlugin from '~/plugins/auth0.plugin'
import auth from '~/auth/auth.service'
export default function (Vue, { router, head, isClient }) {
...
Vue.use(AuthServicePlugin)
//Handle Authentication
router.beforeEach((to, from, next) => {
if (to.path === "/auth/logout" || to.path === "/auth/callback" || auth.isAuthenticated()) {
return next();
}
// Specify the current path as the customState parameter, meaning it
// will be returned to the application after auth
auth.login({ target: to.path });
})
Based on a Gatsbyb.js auth0 implementation tutorial, I've tried to exlude auth0-js from webpack loading with null-loader
gridsome.config.js
configureWebpack: {
/*
* During the build step, `auth0-js` will break because it relies on
* browser-specific APIs. Fortunately, we don’t need it during the build.
* Using Webpack’s null loader, we’re able to effectively ignore `auth0-js`
* during the build. (See `src/utils/auth.js` to see how we prevent this
* from breaking the app.)
*/
module: {
rules: [
{
test: /auth0-js/,
use: 'null-loader',
},
],
},
I would love to get some ideas about how to include and load Auth0 only in client side context with Gridsome
I had the same problem with using Firebase Authentication with Gridsome.
It seems that code in the created() lifecycle hook gets executed in the Gridsome build process (which is a server environment), but code in the mounted() lifecycle hook only executes in the browser!
The solution was to put all the code that should only run in the client in the mounted lifecycle hook.
mounted() {
// load the `auth0-js` here
...
}
In my instance (with Firebase Auth) this was the solution:
In the Default Layout component:
const app = import("firebase/app");
const auth = import("firebase/auth");
const database = import("firebase/firestore");
const storage = import("firebase/storage");
Promise.all([app, auth, database, storage]).then(values => {
// now we can access the loaded libraries 😍!
});
}

Using Shared Worker in React

I have a backend app that constantly serves events to my React app via Web Sockets. When a specific event is received a new browser tab should be opened.
The application will be run by a user in multiple tabs, so I need to open a new tab only once and prevent it from being opened by all running instances.
I've tried using Redux persistent storage, but it doesn't seem to correspond my needs. The best solution that I've found is Shared Workers.
I've tried using Shared Worker in my React app, but I can't set up it properly. It's either being imported incorrectly or Webpack is unable to load it
Uncaught SyntaxError: Unexpected token <
When I googled I haven't found any examples of using Shared Worker in React app (with or without CRA) and at this point, I'm not even sure it's possible. I did found some Web Workers examples, but they have totally different configs.
Can anyone please share some specifics of running Shared Worker in React? Or any other ideas that can provide me with similar functionality will be also greatly appreciated.
Edit: Adding lastest code of what I've tried. Disregard the counter logic, consider just the setup:
worker.js
import React from 'react';
export const startCounter = () => {
window.self.addEventListener("message", event => {
console.log(event.data, self);
let initial = event.data;
setInterval(() => this.postMessage(initial++), 1000);});
}
App.js
import React, { Component } from 'react';
import {startCounter} from './worker';
class App extends Component {
componentDidMount() {
const worker = new SharedWorker(startCounter);
worker.port.start()
// worker.postMessage(this.state.counter);
// worker.addEventListener('message', event => this.setState({counter: event.data}));
}
render() {
return (
<div className="App">
</div>
);
}
}
export default App;
make a file called WebWorker.js which looks like this:
export default class WebWorker {
constructor(worker) {
const code = worker.toString();
const blob = new Blob(['('+code+')()']);
return new SharedWorker(URL.createObjectURL(blob));
}
}
and import it to your main file and do this:
const workers = new WebWorker(worker);
workers.postMessage(some message);
Clarifying #Birat's answer: The SharedWorker constructor is looking for a URL, but here you're passing it a function:
const worker = new SharedWorker(startCounter);
Give this a try instead:
const worker = new SharedWorker(new URL('./worker', import.meta.url));

Trying to mock just certain named exports (e.g. PushNotificationsIOS) in react-native with jest

Given I have an implementations files that looks something :
import ReactNative, { PushNotificationIOS, AsyncStorage } from 'react-native';
export function tryNotify() {
PushNotificationIOS.addEventListener('register', token => {
callback(token);
});
PushNotificationIOS.requestPermissions();
}
export function trySave(token) {
AsyncStorage.setItem('blah', token);
}
So if I want to write a test that spies on:
PushNotificationIOS.addEventListener.
However, I can't work out how to mock it, because as soon as I mock react-native...
describe('notify()', () => {
let generator;
beforeAll(() => {
jest.mock('react-native', () => ({
PushNotificationIOS: {
addEventListener: jest.fn(),
requestPermission: jest.fn(),
},
}));
});
afterAll(() => {
jest.unmock('react-native');
});
// No tests yet!
});
...I start getting the following error in my test:
Invariant Violation: Navigator is deprecated and has been removed from this package. It can now be installed and imported from `react-native-deprecated-custom-components` instead of `react-native`. Learn about alternative navigation solutions at http://facebook.github.io/react-native/docs/navigation.html
My best guess is I'm interfering with the inbuilt react-native mocks that jest provides:
The Jest preset built into react-native comes with a few defaults mocks that are applied on a react-native repository.
-jest docs
But I don't know where to look for to confirm this.
Does anyone have experience with this?
Thanks!
Edit
So I have two solutions:
AsyncStorage: the answer below works, as does this SO answer
PushNotificationsIOS: the answer below does not work for me, but this SO answer did
You can't jest.mock('react-native',... because react-native does some slightly nasty things with its exports, such that they can't be imported en-masse by jest or anything else.
You'll need to bypass this by targeting the module more directly:
jest.mock('react-native/Libraries/PushNotificationIOS', () => {})

Categories