React router server side build issue - build

I am trying to deploy my react application and it is soo much harder than I initially thought..
Well, I´ve followed this guide: https://github.com/reactjs/react-router-tutorial/tree/master/lessons/13-server-rendering and it does not work.
I get this error message:
ERROR in ./modules/routes.js
Module parse failed: /Users/Alfred/React/newReactWebpack/reactWebpack/modules/routes.js Unexpected token (17:2)
You may need an appropriate loader to handle this file type.
SyntaxError: Unexpected token (17:2)
I feel like I am totally unable to debug this at this point because of the fact that I have no idea how the webpack.server.config.js file works, nor the webpack.config.js file.
So I am looking for a webpack wizard who can help me with this. I would appreciate it a TON!
Thanks in advance!
webpack.server.config.js
var fs = require('fs')
var path = require('path')
module.exports = {
entry: path.resolve(__dirname, 'server.js'),
output: {
filename: 'server.bundle.js'
},
target: 'node',
// keep node_module paths out of the bundle
externals: fs.readdirSync(path.resolve(__dirname, 'node_modules')).concat([
'react-dom/server', 'react/addons',
]).reduce(function (ext, mod) {
ext[mod] = 'commonjs ' + mod
return ext
}, {}),
node: {
__filename: true,
__dirname: true
},
module: {
loaders: [
{ test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader?presets[]=es2015&presets[]=react' }
]
}
}
webpack.config.js
'use strict';
const path = require('path');
const args = require('minimist')(process.argv.slice(2));
// List of allowed environments
const allowedEnvs = ['dev', 'dist', 'test'];
// Set the correct environment
let env;
if (args._.length > 0 && args._.indexOf('start') !== -1) {
env = 'test';
} else if (args.env) {
env = args.env;
} else {
env = 'dev';
}
process.env.REACT_WEBPACK_ENV = env;
/**
* Build the webpack configuration
* #param {String} wantedEnv The wanted environment
* #return {Object} Webpack config
*/
function buildConfig(wantedEnv) {
let isValid = wantedEnv && wantedEnv.length > 0 && allowedEnvs.indexOf(wantedEnv) !== -1;
let validEnv = isValid ? wantedEnv : 'dev';
let config = require(path.join(__dirname, 'cfg/' + validEnv));
return config;
}
module.exports = buildConfig(env);
server.js
// ...
// import some new stuff
import React from 'react'
// we'll use this to render our app to an html string
import { renderToString } from 'react-dom/server'
// and these to match the url to routes and then render
import { match, RouterContext } from 'react-router'
import routes from './modules/routes'
// ...
// send all requests to index.html so browserHistory works
app.get('*', (req, res) => {
match({ routes: routes, location: req.url }, (err, redirect, props) => {
// in here we can make some decisions all at once
if (err) {
// there was an error somewhere during route matching
res.status(500).send(err.message)
} else if (redirect) {
// we haven't talked about `onEnter` hooks on routes, but before a
// route is entered, it can redirect. Here we handle on the server.
res.redirect(redirect.pathname + redirect.search)
} else if (props) {
// if we got props then we matched a route and can render
const appHtml = renderToString(<RouterContext {...props}/>)
res.send(renderPage(appHtml))
} else {
// no errors, no redirect, we just didn't match anything
res.status(404).send('Not Found')
}
})
})
function renderPage(appHtml) {
return `
<!doctype html public="storage">
<html>
<meta charset=utf-8/>
<title>My First React Router App</title>
<link rel=stylesheet href=/index.css>
<div id=app>${appHtml}</div>
<script src="/bundle.js"></script>
`
}
var PORT = process.env.PORT || 8080
app.listen(PORT, function() {
console.log('Production Express server running at localhost:' + PORT)
})
package.json
"scripts": {
"start": "if-env NODE_ENV=production && npm run start:prod || npm run start:dev",
"start:dev": "webpack-dev-server --inline --content-base public/ --history-api-fallback",
"start:prod": "npm run build && node server.bundle.js",
"build:client": "webpack",
"build:server": "webpack --config webpack.server.config.js",
"build": "npm run build:client && npm run build:server"
},
routes.js
import React from 'react'
import { Route, IndexRoute } from 'react-router'
import LandingPage from '../src/compositions/landingPage/LandingPage'
import Webshop from '../src/compositions/webshopPage/Webshop'
import Gallery from '../src/compositions/galleryPage/Gallery'
import Services from '../src/compositions/servicesPage/Services'
import Checkout from '../src/compositions/checkoutPage/Checkout'
import Faq from '../src/compositions/faq/Faq'
import Products from '../src/components/webshop/products/Products'
import Product from '../src/components/webshop/products/Product'
import WebshopHome from '../src/compositions/webshopPage/WebshopHome'
import Admin from '../src/compositions/adminPage/Admin'
import Login from '../src/compositions/adminPage/Login'
module.exports = (
<Route path="/" component={LandingPage}>
<Route path="webshop" component={Webshop}>
<IndexRoute component={WebshopHome}></IndexRoute>
<Route path="/webshop/checkout" component={Checkout}></Route>
<Route path="/webshop/:category" component={Products}></Route>
<Route path="/webshop/:category/:subcategory" component={Products}></Route>
<Route path="/webshop/:category/:subcategory/:product" component={Product}></Route>
</Route>
<Route path="gallery" component={Gallery}></Route>
<Route path="services" component={Services}></Route>
<Route path="login" component={Login}></Route>
<Route path="admin" component={Admin} onEnter={requireAuth}>
<Route path="/admin/:site" component={Admin}></Route>
<Route path="/admin/:site/:section" component={Admin}></Route>
<Route path="/admin/:site/:section/:category/:subcategory" component={Admin}></Route>
</Route>
<Route path="faq" component={Faq}></Route>
<Route path="*" component={NotFound}></Route>
</Route>
)
index.js
import React from 'react'
import firebase from 'firebase/app'
require('firebase/auth')
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import { Router, browserHistory } from 'react-router'
import routes from '../modules/routes'
import store from './store/store'
//Initialize firebase
var config = {
apiKey: ***********
authDomain: **********
databaseURL: **********
storageBucket: **********
}
firebase.initializeApp(config)
const app = document.getElementById('app')
const NotFound = () => (<h4 style={{textAlign: 'center', paddingTop: 100}}>404.. Oops, nu hamnade du fel!</h4>)
function requireAuth(nextState, replace, callback) {
firebase.auth().onAuthStateChanged((user) => {
if (null === user) {
replace({
pathname: '/login',
state: { nextPathname: nextState.location.pathname }
})
callback()
}
callback()
})
}
render(
(<Provider store={store}>
<Router routes={routes} history={browserHistory}>
</Router>
</Provider>), app)

Related

TypeError: _bundlr_network_client__WEBPACK_IMPORTED_MODULE_0__ is not a constructor while using metaplex.nfts().uploadMetadata()

I'm using this metaplex javascript SDK for working with nfts on solana blockchain.
While uploading the metadata for an nft, I am getting the following error:
TypeError: _bundlr_network_client__WEBPACK_IMPORTED_MODULE_0__ is not a constructor
Code for connecting to metaplex:
const fromWallet = Keypair.generate();
console.log(fromWallet);
const connection = new Connection(clusterApiUrl("devnet"));
const metaplex = Metaplex.make(connection)
.use(keypairIdentity(fromWallet))
.use(
bundlrStorage({
address: "https://devnet.bundlr.network",
providerUrl: "https://api.devnet.solana.com",
timeout: 60000,
})
);
Uploading metadata function:
async function uploadMetadata() {
try {
const { uri, metadata } = await metaplex.nfts().uploadMetadata({
name: formInput.name,
image: image,
description: formInput.description,
});
console.log(metadata.image);
return uri;
} catch (error) {
console.log(`Error uploading metadata - ${error}`);
}
}
I couldn't understand why I'm getting this error. I tried to hit the function by removing .use(keypairIdentity(fromWallet)) from metaplex configuration. But I am getting another error regarding undefined wallet that way. For the similar configuration of metaplex, mx.nfts().findNftByMint(new PublicKey(address)) is working correctly.
Any help is appreciated. Thank you.
I was facing the exact same issue
I opened an issue on metaplex github repo https://github.com/metaplex-foundation/js/issues/138
so it turned out we need to use the wallet adapter
keypairIdentity & walletAdapterIdentity should work on the browser however only walletAdapterIdentity worked for me .
the link to wallet adapter also created by metaplex is https://github.com/solana-labs/wallet-adapter
update, you just need to wrap you app component
import { WalletAdapterNetwork } from '#solana/wallet-adapter-base';
import { ConnectionProvider, useConnection, useWallet, WalletProvider } from '#solana/wallet-adapter-react';
import { WalletModalProvider, WalletMultiButton } from '#solana/wallet-adapter-react-ui';
import {
GlowWalletAdapter,
PhantomWalletAdapter,
SlopeWalletAdapter,
SolflareWalletAdapter,
TorusWalletAdapter,
} from '#solana/wallet-adapter-wallets';
import { clusterApiUrl, PublicKey } from '#solana/web3.js';
import './App.css';
import '#solana/wallet-adapter-react-ui/styles.css';
import { useEffect, useState, useMemo } from "react";
import { Metaplex } from '#metaplex-foundation/js';
export const App = () => {
return (
<BrowserRouter>
<Context>
<Content />
</Context>
</BrowserRouter>
);
};
const Context = ({ children }) => {
const network = WalletAdapterNetwork.Devnet;
const endpoint = useMemo(() => clusterApiUrl(network), [network]);
const wallets = useMemo(
() => [
new PhantomWalletAdapter(),
new GlowWalletAdapter(),
new SlopeWalletAdapter(),
new SolflareWalletAdapter({ network }),
new TorusWalletAdapter(),
],
[network]
);
return (
<ConnectionProvider endpoint={endpoint}>
<WalletProvider wallets={wallets} autoConnect>
<WalletModalProvider>{children}</WalletModalProvider>
</WalletProvider>
</ConnectionProvider>
);
};
const Content = () => {
const { connection } = useConnection();
const wallet = useWallet();
const metaplex = Metaplex.make(connection);
const [walletAddress, setWalletAddress] = useState('');
const [walletWarning, setWalletWarning] = useState(false);
const [disabled, setDisabled] = useState(false);
useEffect(() => {
const onload = async () => {
await checkIfWalletIsConnected();
}
window.addEventListener('load', onload);
return () => window.removeEventListener('load', onload);
}, []);
return (
<div className='main-app-container'>
<div className='sec-app-container'>
<WalletMultiButton/>
<div className="router-container">
<Routes>
<Route exact path="/" element={<Landing walletWarning={walletWarning} />} />
<Route exact path="/mint_interface"
element={
<MintInterface wallet={wallet} connection={connection} />
} />
</Routes>
</div>
</div>
</div >
);
};
export default App;
and in the MintInterface Component
import { useState } from 'react';
import { Form, Button, Spinner } from 'react-bootstrap';
import { WalletModalProvider, WalletMultiButton } from '#solana/wallet-adapter-react-ui';
import { Metaplex, keypairIdentity, walletAdapterIdentity, bundlrStorage } from '#metaplex-foundation/js';
import { Connection, clusterApiUrl, Keypair } from '#solana/web3.js';
import "./minting.css";
const cluster = "devnet";
function MintInterface({ wallet, connection }) {
const [maturityDate, setMaturityDate] = useState("");
const [quantity, setQuantity] = useState(1);
const [loading, setLoading] = useState(false);
const metaplex = Metaplex.make(connection)
.use(walletAdapterIdentity(wallet))
.use(
bundlrStorage({
address: "https://devnet.bundlr.network",
providerUrl: "https://api.devnet.solana.com",
timeout: 60000,
})
);
);
}
export default MintInterface;
make sure you use walletAdapterIdentity in the configuration

Books API has not been used in project ******** before or it is disabled. Error code 403

I'm trying to migrate to Google Identity Services. When I log a user in and try to get info from their Google Books account I get "error code 403. Books API has not been used in project ****** before or it is disabled"
I checked the project number that was being used before I tried to migrate to Google Identity Services and the project number is totally different from the one stated in the error, and I definitely have the Book API enabled.
I use the following scripts in the react index.html page:
<script src="https://apis.google.com/js/api.js" async defer></script>
script src="https://accounts.google.com/gsi/client" async defer></script>
App.js
import { Routes, Route, Link } from "react-router-dom";
import Home from "./pages/Home";
import CreatePost from "./pages/CreatePost";
import Login from "./pages/Login";
import Logout from "./pages/Logout";
import "./App.css";
import { useEffect, useState } from "react";
function App() {
const [isAuth, setIsAuth] = useState(false);
useEffect(()=>{
console.log(isAuth);
}, [isAuth]);
var tokenClient;
function gisInit() {
tokenClient = window.google.accounts.oauth2.initTokenClient({
client_id: *********,
scope: 'https://www.googleapis.com/auth/books',
});
}
function gapiStart() {
window.gapi.client.init({
}).then(function() {
// fetch the Books API
window.gapi.client.load('books', 'v1');
}).then(function(response) {
console.log('books loaded');
gisInit()
}, function(reason) {
console.log('Error: ' + reason.result.error.message);
});
}
useEffect(()=>{
window.gapi.load('client', gapiStart);
})
return (
<>
<nav>
<Link to="/">Home</Link>
<Link to="/createpost">Create</Link>
{ !isAuth ? <Link to="/login">Login</Link> : <Link to="/logout">Logout</Link> }
</nav>
<Routes>
<Route path="/" element={ <Home /> } />
<Route path="/createpost" element={ <CreatePost /> } />
<Route path="/login" element={ <Login setIsAuth={ setIsAuth } /> } />
<Route path="/logout" element={ <Logout setIsAuth={ setIsAuth } /> } />
</Routes>
</>
);
}
export default App;
Login.js
import {auth, provider} from "../firebase-config";
import { signInWithPopup, GoogleAuthProvider } from "firebase/auth";
import { useNavigate } from "react-router-dom";
import { useEffect } from "react";
// var url = 'books/v1/mylibrary/bookshelves/4/volumes?fields=totalItems, items(id, volumeInfo/title, volumeInfo/authors, volumeInfo/publishedDate, volumeInfo/publisher, volumeInfo/industryIdentifiers, volumeInfo/imageLinks)'; //A local page
function getData(access_token){
// console.log("token " + access_token);
window.gapi.client.setToken({"access_token":access_token});
const fetchUserData = new Promise(function(resolve, reject){
const request = window.gapi.client.request({
'method': 'GET',
// 'path': 'books/v1/mylibrary/bookshelves/4/volumes?fields=totalItems, items(id, volumeInfo/title, volumeInfo/authors, volumeInfo/publishedDate, volumeInfo/publisher, volumeInfo/industryIdentifiers, volumeInfo/imageLinks)'
'path': 'books/v1/mylibrary/bookshelves/4/volumes?fields=totalItems, items(id)',
});
// // Execute the API request.
request.execute( function(response) {
// const obj = response.result;
resolve(response);
reject("Error");
});
});
fetchUserData.then((value)=>{
console.log(value);
}).catch((error)=>{
console.log(error)
});
}
function Login({ setIsAuth }){
useEffect(()=>{
signInWithGoogle();
});
let navigate = useNavigate();
provider.addScope("https://www.googleapis.com/auth/books");
const signInWithGoogle = () =>{
signInWithPopup(auth, provider).then((result)=>{
// This gives you a Google Access Token. You can use it to access the Google API.
const credential = GoogleAuthProvider.credentialFromResult(result);
let token = credential.accessToken;
if(result.user){
setIsAuth(true);
getData(token);
console.log(result.user.displayName);
navigate("/");
}
}).catch((error)=>{
if(error.code === 'auth/popup-closed-by-user'){
setIsAuth(false);
navigate("/");
}
});
}
return (
<>
<p>Logging in...</p>
</>
)
}
export default Login;
What do I have to do to be able to use the right project?
If I swap out the firebase auth and just use the Google Identity Services auth then I can get the information back from the signed in users Google Books account. Looking at the browser network tab the "authorization: Bearer" is the same.
import { useNavigate } from "react-router-dom";
import { useEffect } from "react";
// var url = 'books/v1/mylibrary/bookshelves/4/volumes?fields=totalItems, items(id, volumeInfo/title, volumeInfo/authors, volumeInfo/publishedDate, volumeInfo/publisher, volumeInfo/industryIdentifiers, volumeInfo/imageLinks)'; //A local page
function getData(){
const fetchUserData = new Promise(function(resolve, reject){
const request = window.gapi.client.request({
'method': 'GET',
'path': 'books/v1/mylibrary/bookshelves/4/volumes?fields=totalItems, items(id, volumeInfo/title, volumeInfo/authors, volumeInfo/publishedDate, volumeInfo/publisher, volumeInfo/industryIdentifiers, volumeInfo/imageLinks)'
});
// // Execute the API request.
request.execute( function(response) {
// const obj = response.result;
resolve(response);
reject("Error");
});
});
fetchUserData.then((value)=>{
console.log(value);
}).catch((error)=>{
console.log(error)//error shows an empty array when controller abort called
});
}
function Login({ setIsAuth }){
var tokenClient;
var access_token;
let navigate = useNavigate();
function getToken(){
tokenClient.requestAccessToken();
}
function initGis(){
tokenClient = window.google.accounts.oauth2.initTokenClient({
client_id: *******,
scope: 'https://www.googleapis.com/auth/books',
callback: (tokenResponse) => {
access_token = tokenResponse.access_token;
if(access_token !== undefined){
setIsAuth(true);
getData();
navigate("/");
}
},//end of callback:
});
}
useEffect(()=>{
initGis();
getToken();
});
return (
<>
<p>Logging in...</p>
</>
)
}
export default Login;
All I needed to do to solve the problem is when making the firebase project select the Google Cloud Platform project from the dropdown list.

NestJS createTestingModule and getHttpServer() error 503

I'm using NestJS 7.6.0 and I'm experiencing a problem with E2E testing.
I have a very simple module that exposes a /health route in its controller. I'd like to test this route using the E2E testing feature of nestJs documented here:
https://docs.nestjs.com/fundamentals/testing#end-to-end-testing
I'm currently using the quite same code:
describe('AppController (e2e)', () => {
let app: INestApplication;
beforeAll(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule]
}).compile();
app = moduleFixture.createNestApplication();
await app.init();
});
it('/health (GET)', async() => {
return await request(app.getHttpServer())
.get('/health')
.expect(200)
});
});
If I run this test with npm run test I have this error:
npm run test
> dv-ewok-api#0.0.1 test ....
> jest
[Nest] 54638 - 28/06/2021, 14:53:30 [ExceptionHandler] Health Check has failed! {"ready":{"status":"down","message":"connect ECONNREFUSED 127.0.0.1:3000"}}
FAIL src/health/health.controller.spec.ts
● AppController (e2e) › /health (GET)
expected 200 "OK", got 503 "Service Unavailable"
32 | return await request(app.getHttpServer())
33 | .get('/health')
> 34 | .expect(200)
| ^
35 |
36 | });
37 | });
It seems that the httpServer is not running.
To make it work I have to modify the code like this:
...
beforeAll(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule]
}).compile();
app = moduleFixture.createNestApplication();
await app.init();
await app.listen(3000); // <===== ADD THIS LINE
});
...
I just added a app.listen(3000) and the test passed. I don't know if this is an oversight in the documentation or if I did it wrong.
Thank you for your help.
Edit for adding some more details
For the HealthCheck part, I'am using the NestJS Terminus integration described here.
So, my app.module looks like this :
import { Module } from '#nestjs/common';
import { TerminusModule } from '#nestjs/terminus';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { HealthController } from './health/health.controller';
#Module({
imports: [TerminusModule],
controllers: [AppController, HealthController],
providers: [AppService],
})
export class AppModule {}
As you can see, I import the TerminusModule and I declare my 2 controllers: AppController and HealthController.
In AppController I have just a /ping route that says "hello world"
import { Controller, Get } from '#nestjs/common';
import { AppService } from './app.service';
#Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
#Get('/ping')
getHello(): string {
return this.appService.getHello();
}
}
And in HealthController I have just the /health route that calls the /ping on localhost.
import { Controller, Get } from "#nestjs/common";
import { HealthCheck, HealthCheckService, HttpHealthIndicator } from "#nestjs/terminus";
#Controller('health')
export class HealthController {
constructor(
private health: HealthCheckService,
private http: HttpHealthIndicator,
) {}
#Get()
#HealthCheck()
check() {
return this.health.check([
() => this.http.pingCheck('ping', 'http://localhost:3000/ping'),
]);
}
}
And if you run tests with npm run test, it shows the 503 error linked above. To make it work, I need to add await app.listen(3000); in the beforeAll() method right after the init() (cf. above).
I think my problem comes from "terminus" because if I start a new NestJS project which does not use Terminus, e2e testing works fine, without the need to add the .listen().
Do I need to do something special in the createTestingModule() with TerminusModule?
Thank you

Vue Jest - Create Mock Api server

I want to create a Mock API Server for my Jest tests so that I can define all my backend endpoints and create responses and authentication checks.
I have managed to set up the server and routes by following some of the source code from Chris Fritz "Vue-Enterprice-boilerplate":
https://github.com/chrisvfritz/vue-enterprise-boilerplate/tree/master/tests/unit
// jest.config.js
const _ = require("lodash");
process.env.MOCK_API_PORT = process.env.MOCK_API_PORT || _.random(9000, 9999);
module.exports = {
preset: "#vue/cli-plugin-unit-jest",
setupFiles: ["./tests/unit/setup"],
globalSetup: "<rootDir>/tests/unit/global-setup",
globalTeardown: "<rootDir>/tests/unit/global-teardown",
testMatch: ["**/(*.)spec.js"],
moduleFileExtensions: ["js", "jsx", "json", "vue"],
transform: {
"^.+\\.vue$": "vue-jest",
"^.+\\.js$": "babel-jest",
".+\\.(css|scss|jpe?g|png|gif|webp|svg|mp4|webm|ogg|mp3|wav|flac|aac|woff2?|eot|ttf|otf)$":
"jest-transform-stub"
},
transformIgnorePatterns: ["/node_modules/(?!vue-spinner)"],
testURL: process.env.API_BASE_URL || `http://localhost:${process.env.MOCK_API_PORT}`
};
The server runs when the tests starts and I can console log the route files.
I just don't know how the axios call from my Vuex would go with the mock API instead of the real one.
Might need to import axios somewhere in the test to prevent the development URL to be used?
/tests/mock-api/routes/auth.js
const Users = require("../resources/users");
module.exports = app => {
console.log('I can see this during tests!');
app.post("/api/v1/login", async (req, res) => {
console.log("I don't see this..");
await Users.authenticate(req.body)
.then(user => {
res.json(user);
})
.catch(error => {
res.status(401).json({ message: error.message });
});
});
});
// /views/Login.spec.js
import Vue from "vue";
import Vuelidate from "vuelidate";
import Login from "#/views/Login";
import BaseButton from "#/components/Globals/_base-button.vue";
import BaseInput from "#/components/Globals/_base-input.vue";
import BaseLabel from "#/components/Globals/_base-label.vue";
import flushPromises from "flush-promises";
import store from "#/store";
import { shallowMount } from "#vue/test-utils";
Vue.use(Vuelidate);
describe("#/views/Login", () => {
// other tests..
it("redirects to posts on successful login", async () => {
const wrapper = shallowMount(Login, { store, stubs: { BaseInput, BaseButton, BaseLabel } });
wrapper.vm.$v.$touch();
const spyDispatch = jest.spyOn(wrapper.vm.$store, "dispatch");
const username = wrapper.find("#username");
const password = wrapper.find("#password");
username.element.value = "johndoe#email.com";
password.element.value = "passwordz";
username.trigger("input");
password.trigger("input");
await wrapper.find("#submitBtn").trigger("click.prevent");
await wrapper.vm.$nextTick();
await flushPromises();
await expect(spyDispatch).toHaveBeenCalledWith("auth/login", {
username: username.element.value,
password: password.element.value
});
// #TODO add expect for redirect as well
});
// /store/auth.js (vuex)
export const actions = {
async login({ commit }, { username, password }) {
console.log("I see this");
const response = await axios.post("/login",
{ username, password }, { withCredentials: true });
console.log("I don't see this");
// #TODO error handling
if (!response) return;
commit("setUser", { ...response.data.user });
router.push({ name: "Posts" });
},
The login action gets called but I don't get passed the axios.post.
Do I need to import axios somewhere to make sure I get a fresh instance? (Vuex uses one I set the baseURL and headers)
All the other tests and logic works except this.

Run unit test with Jest, Vue, Vuetify, and Pug

I am currently working on a project that is using Vue, Class based components, typescript, pug, vuetify and Jest for unit testing. I have been trying to run unit tests using jest and have not been able to get them to work. At this point I am pretty lost as to what could be wrong. It seems that there are issues with unit tests when using vueifty which I think I have sorted out but am not certain. When I run the test the test fails because the wrapper is always empty.
Component
<template lang="pug">
v-row(align="center" justify="center")
v-col(cols="6")
v-card
v-form(ref="loginForm" v-model="valid" v-on:keyup.enter.native="login")
v-card-title#title Login
v-card-text
v-text-field(class="mt-4" label="Username" required outlined v-model="username" :rules="[() => !!username || 'Username Required.']")
v-text-field(label="Password" required outlined password :type="show ? 'text' : 'password'" :append-icon="show ? 'visibility' : 'visibility_off'" #click:append="show = !show" v-model="password" :rules="[() => !!password || 'Password Required.']")
v-alert(v-if="error" v-model="error" type="error" dense dismissible class="mx-4")
| Error while logging in: {{ errorMsg }}
v-card-actions()
div(class="flex-grow-1")
v-btn(class="mr-4" color="teal" :disabled="!valid" large depressed #click="login") Login
div Forgot password?
a(href="/forgot-password" class="mx-2") Click here
div(class="my-2") Don't have an account?
a(href="/signup" class="mx-2") Signup
| for one
</template>
<script lang="ts">
import { AxiosError, AxiosResponse } from 'axios';
import JwtDecode from 'jwt-decode';
import { Component, Vue } from 'vue-property-decorator';
import { TokenDto, VForm } from '#/interfaces/GlobalTypes';
#Component({
name: 'LoginForm',
})
export default class Login extends Vue {
private password: string = '';
private username: string = '';
private show: boolean = false;
private error: boolean = false;
private errorMsg: string = '';
private valid: boolean = false;
... removed rest for brevity
Test
import LoginForm from '#/components/auth/LoginForm.vue';
import login from '#/views/auth/LoginView.vue';
import { createLocalVue, mount } from '#vue/test-utils';
import Vue from 'vue';
import Vuetify from 'vuetify';
// jest.mock('axios')
Vue.use(Vuetify)
const localVue = createLocalVue();
console.log(localVue)
describe('LoginForm.vue', () => {
let vuetify: any
beforeEach(() => {
vuetify = new Vuetify()
});
it('should log in successfully', () => {
const wrapper = mount(LoginForm, {
localVue,
vuetify
})
console.log(wrapper.find('.v-btn'))
});
});
The LoginForm is loaded properly but it does not seeem that that mount creates the wrapper for some reason. When I log the wrapper I get:
VueWrapper {
isFunctionalComponent: undefined,
_emitted: [Object: null prototype] {},
_emittedByOrder: []
}
Any ideas are greatly appericated
you can try:
wrapper.findComponent({name: 'v-btn'})
I guess I am late but I made it work.
I noticed you tried to find VBtn component by 'v-btn' class but VBtn doesn't have it by default. That's why I decided to stub it with my own VBtn that has 'v-btn' class.
import { shallowMount, Wrapper } from '#vue/test-utils'
import Login from '#/components/Login/Login.vue'
import Vue from 'vue'
import Vuetify from 'vuetify'
Vue.use(Vuetify)
let wrapper: Wrapper<Login & { [ key: string]: any }>
const VButtonStub = {
template: '<button class="v-btn"/>'
}
describe('LoginForm.vue', () => {
beforeEach(() => {
wrapper = shallowMount(Login, {
stubs: {
VBtn: VButtonStub
}
})
})
it('should log in successfully', () => {
console.log(wrapper.html())
})
})
After test passed you will see in console log that stubbed component has 'v-btn' class. You can add yours and work with it like you want.