How to get all tests working for Components, Redux Actions and Reducers for a Create React Native App (CRNA) using Expo (default) while not ejected?
Also uses Axios, Redux-Thunk async actions and React-Native Maps through Expo.
Well after reading and re-reading the relevant documentation for Jest, Enzyme and Redux,
as well as googling issues with specific NPM package versions I sorted this out.
There's a lot of "moving parts" in that all NPM packages have to play nice together. I.E testing, mocking, redux and and your flavour of React.
Here's what works at this time (2018-01-16).
Setup
Environment
OS X High Sierra
Visual Studio Code
Project platform
Create React Native App (CRNA)
Expo 23.0.4
React 16.0.0-alpha.12
React-Native 0.50.3
Testing framework
Jest ^22.0.6
Jest-CLI ^22.0.6
Jest-Enzyme ^4.0.2
Jest-Expo ^22.0.0
React-addons-test-utils ^15.6.2
React-DOM 16.0.0-beta.5
package.json
Working tests for Redux actions, reducers and components.
{
"name": "MyApp",
"version": "0.0.1",
"private": true,
"author": "Thomas Hagström <thomas#crossplatform.se>",
"devDependencies": {
"axios-mock-adapter": "^1.10.0",
"babel": "^6.3.26",
"babel-eslint": "^8.2.1",
"babel-jest": "^22.0.6",
"babel-polyfill": "^6.16.0",
"babel-preset-airbnb": "^1.0.1",
"babel-preset-es2015": "^6.18.0",
"babel-preset-react": "^6.16.0",
"babel-preset-react-native": "1.9.0",
"eslint": "^4.15.0",
"eslint-config-airbnb": "^16.1.0",
"eslint-plugin-import": "^2.8.0",
"eslint-plugin-jsx-a11y": "^6.0.3",
"eslint-plugin-react": "^7.5.1",
"jest": "^22.0.6",
"jest-cli": "^22.0.6",
"jest-enzyme": "^4.0.2",
"jest-expo": "^22.0.0",
"react-addons-test-utils": "^15.6.2",
"react-dom": "^16.0.0-beta.5",
"react-native-mock": "^0.3.1",
"react-native-scripts": "1.8.1",
"react-test-renderer": "^16.0.0-alpha.12",
"remotedev-rn-debugger": "^0.8.3"
},
"babel": {
"presets": [
"es2015",
"react"
]
},
"main": "./node_modules/react-native-scripts/build/bin/crna-entry.js",
"scripts": {
"start": "react-native-scripts start",
"eject": "react-native-scripts eject",
"android": "react-native-scripts android",
"ios": "react-native-scripts ios",
"test": "node node_modules/jest/bin/jest.js --watch",
"postinstall": "remotedev-debugger --hostname localhost --port 5678 --injectserver",
"eslint": "./node_modules/.bin/eslint"
},
"remotedev": {
"hostname": "localhost",
"port": 5678
},
"jest": {
"preset": "jest-expo",
"transformIgnorePatterns": [
"node_modules/(?!(react-native|jest-resolve|expo|lodash|enzyme|prop-types|react|jest-enzyme|enzyme|jest-expo|jest-serializer-enzyme|react-native-elements|react-native-google-places-autocomplete)/)"
],
"setupFiles": [
"./config/jest/globalFetch.js",
"./config/enzyme/index.js"
]
},
"dependencies": {
"#expo/vector-icons": "^6.2.2",
"axios": "^0.17.1",
"expo": "^23.0.4",
"enzyme": "^3.3.0",
"enzyme-adapter-react-16": "^1.1.1",
"lodash": "^4.17.4",
"prop-types": "^15.6.0",
"react": "16.0.0-alpha.12",
"react-native": "0.50.3",
"react-native-elements": "^0.18.5",
"react-native-google-places-autocomplete": "^1.3.6",
"react-native-maps": "^0.18.0",
"react-navigation": "^1.0.0-beta.23",
"react-navigation-redux": "^0.1.0",
"react-redux": "^5.0.6",
"redux": "^3.7.2",
"redux-logger": "^3.0.6",
"redux-promise": "^0.5.3",
"redux-thunk": "^2.2.0",
"redux-mock-store": "^1.4.0",
"remote-redux-devtools": "^0.5.12",
"socketcluster-server": "^9.1.2"
}
}
Enzyme global config
The config script for Enzyme, see package.json below, looks like this.
// config/enzyme/index.js
import Enzyme from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
// Setup enzyme's react adapter
Enzyme.configure({ adapter: new Adapter() });
Examples
Global Mocks
Expo Kit
In the root of my project I've placed mocks in a __mocks__ directory so they will automatically be picked up by Jest.
This will solve cases where native mobile API calls are used - specifically ExpoKit SDK - and not just HTTP REST.
// __mocks__/expo.js
jest.mock('expo', () => {
const expo = require.requireActual('expo');
const positionMock = {
latitude: 1,
longitude: 1,
};
// Mock the expo library
return {
Location: {
setApiKey: jest.fn(),
getCurrentPositionAsync:
options =>
new Promise(
resolve => resolve(options ? {
coords: positionMock,
} : null)
, null,
)
,
},
Constants: {
manifest: {
extra: { google: { maps: 'Your-API-KEY-HERE' } },
},
},
Permissions: {
LOCATION: 'location',
askAsync: type => new Promise(resolve =>
resolve(type === 'location' ?
{ status: 'granted' }
: null)),
},
...expo,
};
});
Redux - Mock - Store
To configure Redux with Thunk, so you don't have to do this before every (action) test. Meaning in your tests importing redux-mock-store will use the below implementation:
// __mocks__/redux-mock-store.js
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
const middlewares = [thunk];
const mockStore = configureMockStore(middlewares);
export default mockStore;
Constants
Used as redux action types.
// src/Constants.js
const MapConstants = {
MAP_LOCATION_CHANGED: 'MAP REGION CHANGED',
MAP_LOCATION_BUSY: 'MAP: GETTING LOCATION',
MAP_LOCATION_SUCCESS: 'MAP: GET LOCATION SUCCESS',
MAP_LOCATION_FAILED: 'MAP: GET LOCATION FAILED',
};
Redux Action Creators
Here we used the above configuration in an action test.
// src/Actions/__tests__/MapActions.test.js
import configureMockStore from 'redux-mock-store';
import { MapConstants } from '../../Constants';
import {
GetLocation
} from '../MapActions';
const store = configureMockStore();
describe('map actions', () => {
beforeEach(() => {
store.clearActions();
});
test('GetLocation returns SUCCESS when done', async () => {
const expectedPayload = { latitude: 1, longitude: 1 };
const expectedActions = [
{ type: MapConstants.MAP_LOCATION_BUSY },
{ type: MapConstants.MAP_LOCATION_SUCCESS, payload: expectedPayload },
];
// Dispatch action
await store.dispatch(GetLocation());
expect(store.getActions()).toMatchSnapshot();
expect(store.getActions()).toEqual(expectedActions);
});
});
Components
I use a pure component and do my redux connect on a separate container.
import React from 'react';
import { shallow } from 'enzyme';
import Map from '../Map';
import { Colors } from '../../styles';
// src/Components/__tests__/map.test.js
function setup () {
const props = {
GetLocation: jest.fn(),
LocationChanged: jest.fn(),
map: {
isBusy: false,
hasError: false,
errorMessage: null,
location: null,
region: {
latitude: 45.52220671242907,
longitude: -122.6653281029795,
latitudeDelta: 0.04864195044303443,
longitudeDelta: 0.040142817690068,
},
},
};
const enzymeWrapper = shallow(<Map {...props} />);
return {
props,
enzymeWrapper,
};
}
describe('components', () => {
describe('Map', () => {
it('should render self and subcomponents', () => {
const { enzymeWrapper } = setup();
expect(enzymeWrapper).toMatchSnapshot();
const busyProps = enzymeWrapper.find('BusyIndicator').props();
expect(busyProps.isBusy).toBe(false);
expect(busyProps.loadingIndicatorColor).toEqual("#FFFFFF");
});
// TODO: Mock map functions
});
});
Redux Reducer
Ensure the reducer returns correct state and doesn't mutate it.
import MapReducer from '../MapReducer';
import { MapConstants } from '../../Constants';
describe('MapReducer', () => {
test('should return the initial state', () => {
expect(MapReducer(undefined, {}))
.toEqual({
isBusy: false,
hasError: false,
errorMessage: null,
location: null,
region: {
latitude: 45.52220671242907,
longitude: -122.6653281029795,
latitudeDelta: 0.04864195044303443,
longitudeDelta: 0.040142817690068,
},
});
});
test(`should handle ${MapConstants.MAP_LOCATION_BUSY}`, () => {
expect(MapReducer({}, {
type: MapConstants.MAP_LOCATION_BUSY,
}))
.toEqual({
hasError: false,
errorMessage: null,
isBusy: true,
type: MapConstants.MAP_LOCATION_BUSY,
});
});
test(`should handle ${MapConstants.MAP_LOCATION_SUCCESS}`, () => {
const resultArray = ['test'];
expect(MapReducer({}, {
type: MapConstants.MAP_LOCATION_SUCCESS,
payload: resultArray,
}))
.toEqual({
isBusy: false,
hasError: false,
errorMessage: null,
location: resultArray,
type: MapConstants.MAP_LOCATION_SUCCESS,
});
});
test(`should handle ${MapConstants.MAP_LOCATION_FAILED}`, () => {
const errorString = 'test error';
expect(MapReducer({}, {
type: MapConstants.MAP_LOCATION_FAILED,
payload: errorString,
}))
.toEqual({
isBusy: false,
hasError: true,
errorMessage: errorString,
location: null,
type: MapConstants.MAP_LOCATION_FAILED,
});
});
test(`should handle ${MapConstants.MAP_LOCATION_CHANGED}`, () => {
const resultArray = ['test'];
expect(MapReducer({}, {
type: MapConstants.MAP_LOCATION_CHANGED,
payload: resultArray,
}))
.toEqual({
isBusy: false,
hasError: false,
errorMessage: null,
region: resultArray,
type: MapConstants.MAP_LOCATION_CHANGED,
});
});
});
Related
I can't get the token data using getToken({req, secret}), always return null.
Next JS 13.1.1 and next-auth 4.3.4
package.json:
{
"name": "frontend",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint",
"deploy": "npx serverless"
},
"dependencies": {
"#emotion/react": "^11.10.0",
"#emotion/styled": "^11.10.0",
"#mui/icons-material": "^5.8.4",
"#mui/lab": "^5.0.0-alpha.95",
"#mui/material": "^5.10.0",
"#reduxjs/toolkit": "^1.8.4",
"animate.css": "^4.1.1",
"chart.js": "^3.9.1",
"cookie": "^0.5.0",
"html-to-image": "^1.11.4",
"jsonwebtoken": "^9.0.0",
"jspdf": "^2.1.0",
"moment": "^2.29.4",
"next": "^13.1.1",
"next-auth": "^4.3.4",
"react": "^18.2.0",
"react-chartjs-2": "^4.3.1",
"react-cookie": "^4.1.1",
"react-dom": "^18.2.0",
"react-dropzone": "^14.2.2",
"react-hook-form": "^7.34.1",
"react-redux": "^8.0.2",
"sass": "^1.54.4",
"swr": "^1.3.0",
"uuid": "^8.3.2"
},
"devDependencies": {
"#types/cookie": "^0.5.1",
"#types/jsonwebtoken": "^9.0.0",
"#types/node": "18.7.5",
"#types/react": "18.0.17",
"#types/react-dom": "18.0.6",
"#types/uuid": "^8.3.4",
"eslint": "8.22.0",
"eslint-config-next": "^13.1.1",
"typescript": "4.7.4"
}
}
[...nextauth].js:
import NextAuth, { NextAuthOptions } from "next-auth";
// Providers
import Credentials from "next-auth/providers/credentials";
// Services
import { authService, refreshAccessToken } from "../../../services";
export const authOptions: NextAuthOptions = {
providers: [
Credentials({
name: "Credentials",
credentials: {
username: {
label: "Username",
type: "text",
placeholder: "C.C.",
autoComplete: true,
},
password: {
label: "Password",
type: "password",
placeholder: "Type your password...",
autoComplete: true,
},
},
async authorize(credentials) {
return await authService({
username: credentials!.username,
password: credentials!.password,
});
},
}),
],
pages: {
signIn: "/auth/login",
},
session: {
strategy: "jwt",
maxAge: 60 * 60 * 7, // 7 hours
updateAge: 60 * 60 * 2,
},
callbacks: {
async jwt({ token, account, user }) {
if (user) {
token.accessToken = user.accessToken;
token.refreshToken = user.refreshToken;
if (account) {
switch (account.type) {
case "credentials":
token.user = user.user;
token.accessTokenExpires = user.accessTokenExpires;
break;
}
}
}
if (Date.now() / 1000 < (token.accessTokenExpires as number)) {
return token;
}
return await refreshAccessToken(token);
},
async session({ session, token }) {
session.accessToken = token.accessToken;
session.user = token.user as any;
return session;
},
},
// secret: process.env.NEXTAUTH_SECRET!,
secret: process.env.NEXTAUTH_SECRET,
};
export default NextAuth(authOptions);
and middleware.ts:
import { getToken } from "next-auth/jwt";
import { withAuth } from "next-auth/middleware";
import { NextResponse } from "next/server";
// Interfaces
import { IUser } from "./interfaces";
const secret = process.env.NEXTAUTH_SECRET;
export default withAuth(async function middleware(req) {
const token = await getToken({ req, secret }); // ALWAYS returns null
const { user } = (token as { user: IUser; accessToken: string }) || {};
const isAuth = !!token;
const isAuthPage = req.nextUrl.pathname.startsWith("/auth");
if (isAuthPage) {
if (isAuth) {
// return NextResponse.redirect(new URL(`/${user.hierarchy}`, req.url));
return NextResponse.redirect(new URL(`/role`, req.url));
}
return null;
}
if (!isAuth) {
let from = req.nextUrl.pathname;
if (req.nextUrl.search) {
from += req.nextUrl.search;
}
return NextResponse.redirect(new URL(`/auth/login`, req.url));
}
});
process.env.NEXTAUTH_SECRET = secret.
I tried to debug with nextAuth, but the middleware works fine when the isAuth is false (if i try to load protected route, middleware redirect to /auth), just fail when the getToken function try to get the cookie
to verify auth, what is wrong? :(
I have a small simple setup. With mobx and preact.
class AppStore {
loadingobjects = true;
constructor() {
makeObservable(this, {
loadingobjects: observable,
});
this.fetchCommonObjects();
}
fetchCommonObjects = () => {
window
.fetch(url)
.then((res) => res.json())
.then((json) => {
/* data processing */
this.loadingobjects = false;
});
};
}
export const AppStoreContext = createContext();
function AppStoreProvider({ children }) {
return (
<AppStoreContext.Provider value={new AppStore()}>
{children}
</AppStoreContext.Provider>
);
}
export default AppStoreProvider;
export default function useAppStore() {
return useContext(AppStoreContext);
}
const List = observer(() => {
const store = useAppStore();
if (store.loadingobjects) {
return <div class="ui active centered inline loader"></div>;
} else {
return (page content);
}
});
problem is that store.loadingobjects Is always false. Seems like im doing something wrong but i cant put my finger on it...
What am i missing or doing wrong?
Edit addding my configs:
package.json
{
"name": "project",
"version": "0.0.2",
"license": "MIT",
"scripts": {
"start": "set NODE_ENV=dev && webpack serve --mode=development",
"build": "set NODE_ENV=production && webpack -p",
},
"devDependencies": {
"#babel/core": "^7.20.12",
"#babel/plugin-transform-runtime": "^7.19.6",
"#babel/preset-env": "^7.20.2",
"#babel/preset-react": "^7.18.6",
"babel-loader": "^9.1.2",
"babel-plugin-import": "^1.13.6",
"html-webpack-plugin": "^5.5.0",
"surge": "^0.19.0",
"webpack": "^5.75.0",
"webpack-cli": "^5.0.1",
"webpack-dev-server": "^4.11.1"
},
"dependencies": {
"#babel/polyfill": "^7.12.1",
"mobx": "^6.7.0",
"mobx-react": "^7.6.0",
"preact": "^10.11.3"
}
}
webpack.config.js
const path = require('path');
const isProd = (process.env.NODE_ENV === 'production');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
//input
entry: ["#babel/polyfill",'./src'],
resolve: {
alias:{
"react": "preact/compat",
"react-dom": "preact/compat",
"react/jsx-runtime": "preact/jsx-runtime"
}
},
//output
output: {
path : path.join(__dirname, 'build'),
filename : 'bundle.js'
},
//transformations
module: {
rules : [
{
test: /\.m?js$/,
exclude: /node_modules/,
use: {
loader: "babel-loader",
},
}
]
},
//sourcemaps
devtool: 'source-map',
plugins: [new HtmlWebpackPlugin({
template: './src/index.html',
favicon: "./src/favicon.ico"
})],
//server
devServer: {
compress: true,
historyApiFallback: true
}
}
.babelrc
{
"presets": ["#babel/preset-react", ["#babel/preset-env", {"useBuiltIns": "usage",}]],
"plugins": [
["#babel/plugin-transform-runtime"],
[
"#babel/plugin-transform-react-jsx",
{
"pragma": "h",
"pragmaFrag": "Fragment"
}
]
]
}
I found the issue. I started cutting the components into smaller pieces and then adding and removing them into the component hierarchy until i found the component causing the issue. It turned out that i had done onClick={method()} and this was changeing state causing the endless rerenders.
I am trying to set up a graphql express server with apollo, express, typeorm, and graphql, however I am getting this error now that I have implemented the UserResolver.ts file. I have already tried installing graphql and even have downgraded it to 14.2.1 but it still doesn't work. Here is the source code:
index.ts
import "reflect-metadata";
import express from 'express';
import {ApolloServer} from 'apollo-server-express';
import { buildSchema } from "type-graphql";
import { UserResolver } from "./UserResolver";
(async () => {
const app = express();
app.get('/', (_req, res) => res.send('hello'))
const apolloServer = new ApolloServer({
schema: await buildSchema({
resolvers: [UserResolver]
})
});
// my line idk if it works
await apolloServer.start();
apolloServer.applyMiddleware({ app });
app.listen(4000, () => {
console.log("express server started");
});
})()
// createConnection().then(async connection => {
// console.log("Inserting a new user into the database...");
// const user = new User();
// user.firstName = "Timber";
// user.lastName = "Saw";
// user.age = 25;
// await connection.manager.save(user);
// console.log("Saved a new user with id: " + user.id);
// console.log("Loading users from the database...");
// const users = await connection.manager.find(User);
// console.log("Loaded users: ", users);
// console.log("Here you can setup and run express/koa/any other framework.");
// }).catch(error => console.log(error));
UserResolver.ts
import { Query, Resolver } from 'type-graphql';
#Resolver()
export class UserResolver{
#Query(() => String)
hello(){
return "hi";
}
}
ormconfig.json
{
"type": "postgres",
"host": "localhost",
"port": 5432,
"username": "postgres",
"password": "7434006a",
"database": "graphqldatabase",
"synchronize": true,
"logging": false,
"entities": [
"src/entity/**/*.ts"
],
"migrations": [
"src/migration/**/*.ts"
],
"subscribers": [
"src/subscriber/**/*.ts"
],
"cli": {
"entitiesDir": "src/entity",
"migrationsDir": "src/migration",
"subscribersDir": "src/subscriber"
}
}
package.json
{
"name": "backend",
"version": "0.0.1",
"description": "Awesome project developed with TypeORM.",
"devDependencies": {
"#types/express": "^4.17.13",
"#types/graphql": "^14.5.0",
"#types/node": "^8.0.29",
"ts-node": "3.3.0",
"typescript": "3.3.3333"
},
"dependencies": {
"#nestjs/common": "^8.2.3",
"#nestjs/core": "^8.2.3",
"#nestjs/typeorm": "^8.0.2",
"apollo-server-express": "^3.5.0",
"express": "^4.17.1",
"graphql": "^14.2.1",
"pg": "^8.4.0",
"reflect-metadata": "^0.1.10",
"rxjs": "^7.4.0",
"typeorm": "0.2.41"
},
"scripts": {
"start": "ts-node src/index.ts",
"typeorm": "node --require ts-node/register ./node_modules/typeorm/cli.js --config src/config/postgres.config.ts"
}
}
I can't seem to get the following example to work with vue3 and testing library.
https://github.com/testing-library/vue-testing-library/blob/main/src/tests/translations-vue-i18n.js
I've even tried to modify the example like so to get $t to be recognized by injecting messages into a mock but no luck.
Does anyone have an example that works with vue 3?
Here are the details ...
Translations.spec.js
import '#testing-library/jest-dom'
import {render, fireEvent} from '#testing-library/vue'
import Vuei18n from 'vue-i18n'
import Translations from '#/components/Translations'
const messages = {
en: {
Hello: 'Hello!',
message: {
hello: 'Hello!'
}
},
ja: {
Hello: 'こんにちは',
message: {
hello: 'こんにちは'
}
},
}
test('renders translations', async () => {
const {queryByText, getByText} = render(Translations, {
global: {
mocks: {
$t: (messages) => messages
}
}
}, vue => {
// Let's register and configure Vuei18n normally
vue.use(Vuei18n)
const i18n = new Vuei18n({
locale: 'ja',
fallbackLocale: 'ja',
messages,
})
// Notice how we return an object from the callback function. It will be
// merged as an additional option on the created Vue instance.
return {
i18n,
}
})
//expect(getByText('Hello!')).toBeInTheDocument()
//await fireEvent.click(getByText('Japanese'))
expect(getByText('こんにちは')).toBeInTheDocument()
//expect(queryByText('Hello!')).not.toBeInTheDocument()
})
Translations.vue
<template>
<div>
<h2>{{ $t("Hello") }}</h2>
<h2>{{ $t("message.hello") }}</h2>
<button #click="switchLocale('en')">English</button>
<button #click="switchLocale('ja')">Japanese</button>
</div>
</template>
<script>
export default {
name: 'Translations',
methods: {
switchLocale(locale) {
this.$i18n.locale = locale
},
},
}
</script>
package.json
{
"name": "mc",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint",
"test:unit": "vue-cli-service test:unit"
},
"dependencies": {
"#fortawesome/fontawesome-svg-core": "^1.2.35",
"#fortawesome/free-solid-svg-icons": "^5.15.3",
"#fortawesome/vue-fontawesome": "^3.0.0-4",
"#popperjs/core": "^2.9.2",
"bootstrap": "^5.0.2",
"core-js": "^3.6.5",
"es6-promise": "^4.2.8",
"vue": "^3.1.4",
"vue-hotjar": "^1.4.0",
"vue-i18n": "^9.1.6",
"vue-loader": "^16.2.0",
"vue-router": "^4.0.10",
"vuex": "^4.0.2"
},
"devDependencies": {
"#babel/core": "^7.14.8",
"#babel/preset-env": "^7.14.8",
"#testing-library/jest-dom": "^5.14.1",
"#testing-library/vue": "^6.4.2",
"#vue/cli-plugin-babel": "^4.5.13",
"#vue/cli-plugin-eslint": "^4.5.13",
"#vue/cli-plugin-router": "^4.5.13",
"#vue/cli-plugin-unit-jest": "^4.5.13",
"#vue/cli-plugin-vuex": "^4.5.13",
"#vue/cli-service": "^4.5.13",
"#vue/compiler-sfc": "^3.1.4",
"#vue/eslint-config-prettier": "^6.0.0",
"#vue/test-utils": "^2.0.0-rc.9",
"babel-eslint": "^10.1.0",
"eslint": "^6.7.2",
"eslint-plugin-prettier": "^3.4.0",
"eslint-plugin-vue": "^7.0.0",
"flush-promises": "^1.0.2",
"prettier": "^2.3.2",
"typescript": "^4.3.5",
"vue-jest": "^5.0.0-alpha.10"
},
"eslintConfig": {
"root": true,
"env": {
"node": true
},
"extends": [
"plugin:vue/vue3-essential",
"eslint:recommended"
],
"parserOptions": {
"parser": "babel-eslint"
},
"rules": {}
},
"browserslist": [
"> 1%",
"last 2 versions",
"not dead"
]
}
Error
FAIL tests/unit/Translations.spec.js
● renders translations
TestingLibraryElementError: Unable to find an element with the text: こんにちは. This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your matcher more flexible.
<body>
<div>
<div>
<h2>
Hello
</h2>
<h2>
message.hello
</h2>
<button>
English
</button>
<button>
Japanese
</button>
</div>
</div>
</body>
47 | //await fireEvent.click(getByText('Japanese'))
48 |
> 49 | expect(getByText('こんにちは')).toBeInTheDocument()
| ^
50 |
51 | //expect(queryByText('Hello!')).not.toBeInTheDocument()
52 | })
I had the same problem and solved it like this:
I am using the next version of #vue/test-utils and vue-jest ("#vue/test-utils": "^2.0.0-rc.16" + "vue-jest": "^5.0.0-alpha.10").
I created a file called jest.init.js (u can call it anything u like)
import { config } from '#vue/test-utils';
import translations from '#/locales/en';
config.global.mocks = {
$t: (msg) => translations[msg],
};
and then initiate it as setup file in jest.config.js
module.exports = {
...
setupFiles: [
'./tests/unit/jest.init.js',
],
...
};
This answer is for everyone stumbling across that question when using Composition API where there's no global $t to mock.
I've solved it by exporting a function createConfiguredI18n in src/plugins/i18n.ts:
import { createI18n, I18nOptions } from 'vue-i18n'
import deDE from '#/locales/de-DE.json'
import enUS from '#/locales/en-US.json'
// Type-define 'de-DE' as the master schema for the resource
type MessageSchema = typeof deDE
export function createConfiguredI18n(locale: string, fallbackLocale: string) {
return createI18n<I18nOptions, [MessageSchema], 'de-DE' | 'en-US'>({
locale: locale || 'en-US',
fallbackLocale: fallbackLocale || 'en-US',
messages: {
'de-DE': deDE,
'en-US': enUS,
},
})
}
export const i18n = createConfiguredI18n('de-DE', 'en-US')
Then in the unit test you can do the following to initialize vue-i18n with your translations:
import {flushPromises, mount, VueWrapper} from '#vue/test-utils'
import {nextTick} from 'vue'
import {createConfiguredI18n} from '#/plugins/i18n'
...
describe('SubjectUnderTest', () => {
it('should display translation "FooBar"', async () => {
const locale = 'de-DE'
const fallbackLocale = 'en-US'
const wrapper = await createWrapper({locale, fallbackLocale})
...
}
async function createWrapper(options: {
locale: string
fallbackLocale: string
}): Promise<VueWrapper> {
const i18n = createConfiguredI18n(options.locale, options.fallbackLocale)
const wrapper = mount(sut, {
global: {
plugins: [i18n],
},
})
await nextTick()
await flushPromises()
return wrapper
}
}
If you don't want the translations but instead mock them and check for the keys only, you can do the following in your unit test instead:
import {flushPromises, mount, VueWrapper} from '#vue/test-utils'
import {nextTick} from 'vue'
import {i18n} from '#/plugins/i18n'
...
i18n.global.t = (key) => key
describe('SubjectUnderTest', () => {
it('should display translation for key "foo.bar"', async () => {
const wrapper = await createWrapper()
...
}
async function createWrapper(): Promise<VueWrapper> {
const wrapper = mount(sut, {
global: {
plugins: [i18n],
},
})
await nextTick()
await flushPromises()
return wrapper
}
}
I am new to Webpack and bundling typescript files into a single file. I got the following setup where I would like to achieve a single JS files with all my typescript bundled.
tsconfig.json:
{
"compilerOptions": {
"allowSyntheticDefaultImports": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"module": "commonjs",
"noEmitOnError": true,
"noImplicitAny": false,
"removeComments": true,
"sourceMap": true,
"target": "es6",
"moduleResolution": "node"
},
"exclude": [
"node_modules",
"libs"
]
}
Webpack.config.js
var path = require("path");
const { CheckerPlugin } = require('awesome-typescript-loader');
const config = {
entry: {
Bootstrap: "./Bootstrapper.ts"
},
output: {
//output file naming conventions https://webpack.js.org/configuration/output/#output-filename when having more different configs with outputs
filename: "[name].bundle.js",
path: path.resolve(__dirname, "wwwroot/dist")
},
context: path.resolve(__dirname, "App"),
devtool: "inline-source-map",
module: {
rules: [
{
test: /\.tsx?$/,
loader: "awesome-typescript-loader"
}
]
},
plugins: [
new CheckerPlugin()
]
}
module.exports = config;
Bootstrap typescript file where I incude some node_module that actually work. JQuery works for instance. But If I try to use the class and subclasses that I use from a single namespace I walk against a wall.
Bootstrapper.ts
import * as $ from "jquery";
import * as Konva from "konva";
import * as SignalR from "#aspnet/signalr";
import * as ko from "knockout";
//How do I use these classes that are in different files
import App = Project.Web.Core.App;
$("document").ready(() => {
let app = new App();
alert("This works if I leave the App reference out!");
});
This is the App.ts that I try to use (import App = Project.Web.Core.App;)
namespace Project.Web.Core {
export class App {
pageId: number = 0;
pageRequestId: string = "";
//drawingManager = new Managers.KonvaDrawManager();
signalRmanager = new Managers.SignalRmanager("");
constructor() {
$("document").ready(() => {
this.pageId = $("#container").data("pageid");
this.pageRequestId = $("#container").data("pagerequestid");
this.signalRmanager.pageRequestId = this.pageRequestId;
//this.signalRmanager.setupSignalRConnection(this.drawingManager);
this.loadCanvasData();
});
window.onresize = () => {
//this.drawingManager.rescale();
};
window.onunload = () => {
this.signalRmanager.notifyPageUnloaded();
}
}
loadCanvasData() {
this.pageId = $("#container").data("pageid");
$.get({
dataType: "json",
url: `api/page/${this.pageId}/stage`,
success: (data: any) => {
//this.drawingManager.initializeStage(data);
},
complete: (data: any) => {
if (data.status === 200) {
this.signalRmanager.notifyPageLoaded();
}
}
});
}
}
}
Packages I use
{
"name": "Project.Web_core",
"private": true,
"version": "0.0.0",
"scripts": {
"build": "./node_modules/.bin/webpack -d",
"build:prod": "./node_modules/.bin/webpack -p",
"watch": "./node_modules/.bin/webpack --watch",
"dev": "./node_modules/.bin/webpack-dev-server"
},
"devDependencies": {
"#types/jquery": "^3.3.1",
"#types/knockout": "^3.4.53",
"#types/knockout.mapping": "^2.0.33",
"#types/webpack-env": "1.13.5",
"aspnet-prerendering": "^3.0.1",
"aspnet-webpack": "^2.0.3",
"awesome-typescript-loader": "^5.0.0",
"babel-core": "^6.26.0",
"babel-loader": "^7.1.4",
"event-source-polyfill": "0.0.12",
"json-loader": "0.5.7",
"node-sass": "^4.8.3",
"sass-loader": "^6.0.7",
"style-loader": "0.20.1",
"typescript": "2.7.1",
"uglifyjs-webpack-plugin": "^1.2.4",
"webpack": "^4.5.0",
"webpack-cli": "^2.0.14",
"webpack-dev-middleware": "^3.1.2",
"webpack-dev-server": "^3.1.3",
"webpack-merge": "4.1.1"
},
"dependencies": {
"#aspnet/signalr": "^1.0.0-preview1-update1",
"es5-shim": "^4.5.10",
"es6-shim": "^0.35.3",
"jquery": "3.3.1",
"knockout": "^3.4.2",
"knockout-mapping": "^2.6.0",
"konva": "^2.0.2",
"watchpack": "^1.4.0"
}
}
I hope someone can help me out clearify my wrong way of thinking.
There are several things:
Typescript config, you can copy. With your types
change import export and remove namespace
export class App { ... }
import { App } from '/path/to/your/file';
class needs a destroyer
and finally in webpack.config.js you can set properties
entry: {
Bootstrap: "./Bootstrapper.ts",
namespace: "./path-to-your-namespace.ts",
anotherNamespace: "./path-to-your-anotherNamespace.ts"
},
resolve: {
extensions: ['.js', '.ts', '.json']
}