I'm currently working on my first vue application, currently building the login logics.
For State management, pinia is being used. I created a Pinia Store to manage the "isLoggedIn" state globally.
import { defineStore } from "pinia";
export const useLoginStatusStore = defineStore('loginStatus', {
id: 'loginStatus',
state: () => ({
isLoggedIn: false
}),
actions: {
logIn() {
this.isLoggedIn = true
console.log("Login", this.isLoggedIn)
},
logOut() {
this.isLoggedIn = false
console.log("Logout", this.isLoggedIn)
}
}
})
So far so good, its working, i can access the state and actions in the components and router file.
**<roouter.js>**
import { createRouter, createWebHistory } from 'vue-router'
import { createPinia } from 'pinia'
import { createApp, ref } from 'vue'
import { useLoginStatusStore } from '../stores/loginStatus.js'
import App from '../App.vue'
import WelcomeView from '../views/public/WelcomeView.vue'
import SplashView from '../views/public/SplashView.vue'
const pinia = createPinia()
const app = createApp(App)
app.use(pinia)
const loginStatusStore = useLoginStatusStore()
let isLoggedIn = ref(loginStatusStore.isLoggedIn)
console.log("isLoggedIn", loginStatusStore.isLoggedIn)
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/',
name: 'splash',
component: SplashView
},
{
path: '/welcome',
name: 'welcome',
component: WelcomeView
},
{
path: '/login',
name: 'login',
component: () => import('../views/public/LoginView.vue')
},
{
path: '/signup',
name: 'signup',
component: () => import('../views/public/SignUpView.vue')
},
{
path: '/resetpassword',
name: 'resetpassword',
component: () => import('../views/public/ForgotPasswordView.vue')
},
{
path: '/home',
name: 'home',
component: () => import('../views/protected/HomeView.vue'),
meta: { requiresAuth: true }
},
{
path: '/sounds',
name: 'sounds',
component: () => import('../views/protected/SoundsView.vue'),
meta: { requiresAuth: true }
},
{
path: '/player',
name: 'soundPlayer',
component: () => import('../views/protected/SoundPlayerView.vue'),
meta: { requiresAuth: true }
},
{
path: '/profile',
name: 'profile',
component: () => import('../views/protected/ProfileView.vue'),
meta: { requiresAuth: true }
},
{
path: '/meditation',
name: 'meditation',
component: () => import('../views/protected/MeditationView.vue'),
meta: { requiresAuth: true }
},
{
path: '/tools',
name: 'tools',
component: () => import('../views/protected/ToolsView.vue'),
meta: { requiresAuth: true }
}
]
})
router.beforeEach((to, from, next) => {
if (to.meta.requiresAuth) {
console.log("Router", isLoggedIn.value)
if (!isLoggedIn.value) {
next({
name: 'welcome'
})
} else {
next()
}
} else {
next()
}
})
export default router
In the router it's being used for protected routes and in App.vue for conditional class rendering.
The Problem is, that when the state gets updated, it doesn't get updated in the components and the components themselves don't update either. I tried with the $subscribe method in pinia, but didnt manage to get it working. I know, whats needed is something that creates reactivity here. But no clue how to do that. I'm grateful for any help with this :)
thanks for reading
**App.vue**
<script setup>
import { RouterView } from 'vue-router';
import DevNavItem from '#/components/header/DevNavItem.vue'
import HeaderItem from '#/components/header/HeaderItem.vue'
import FooterItem from '#/components/footer/FooterItem.vue'
import { useLoginStatusStore } from './stores/loginStatus.js';
const loginStatusStore = useLoginStatusStore()
const isLoggedIn = loginStatusStore.isLoggedIn
console.log("App.vue", loginStatusStore.isLoggedIn)
</script>
<template>
<DevNavItem />
<HeaderItem v-if="isLoggedIn" />
<RouterView :class="isLoggedIn ? 'mainProtected' : 'mainPublic'" />
<FooterItem v-if="isLoggedIn" />
</template>
<style>
/*FONT-IMPORT*/
#import url("#/assets/font/alegreya_font.scss");
/* GENERAL STYLES */
* {
padding: 0;
margin: 0;
box-sizing: border-box;
}
header {
position: top;
}
.mainProtected {
width: 100vw;
height: 83vh;
overflow: hidden;
}
.mainPublic {
width: 100vw;
height: 100vh;
overflow: hidden;
}
/* GLOBAL CLASSES */
.mainLogo {
height: 350px;
width: 350px;
background: url("./img/icons/main.png") center/cover no-repeat;
}
.leavesBackground {
background-color: #253334;
background-image: url("./src/img/images/background_partial.png");
background-repeat: no-repeat;
background-position: bottom;
background-size: contain;
}
.logoSmall {
background: url("./img/icons/main.png") center/contain no-repeat;
height: 100px;
width: 100px;
}
.buttonPublic {
padding: 20px 0;
text-align: center;
background-color: #7c9a92;
color: #fff;
border-radius: 15px;
width: 90%;
text-decoration: none;
font-size: 24px;
border: none;
}
</style>
I tried subscribing to the state with $subscribe, but it didn't work.
storeToRefs()
You need to use storeToRefs() to extract properties from the store while keeping those properties reactive.
import { storeToRefs } from 'pinia'
const themeStore = useThemeStore();
const { isDark } = storeToRefs(themeStore);
Computed property
Thanks to #Fennec for suggesting the computed way of getting reactive state. Although I don't recommend this method since there is a dedicated storeToRefs() available.
import { computed } from 'vue'
const themeStore = useThemeStore();
const isDark = computed(() => themeStore.isDark);
WRONG ways to get reactive state from the Pinia store:
All the ways listed below of getting the state (properties, getters) from the Pinia store are WRONG:
import { useThemeStore } from "./stores/theme.js";
const themeStore = useThemeStore();
// WRONG ways of extracting state from store
let isDark = themeStore.isDark; // not reactive
let isDark = ref(themeStore.isDark); // reactive, but will not be updated with the store
let { isDark } = themeStore; // not reactive, cannot destructure
Destructuring actions directly from the store.
Its worth to note here that "you can destructure actions directly from the store as they are bound to the store itself." (docs)
If you have an action named "increment" in your store, you can just extract it directly from the store in your component:
...
const { increment } = store // actions can be destructured directly
...
Also, according to Pinia docs, the first argument is the unique ID, so you do not need to specify the id again inside the options object. Or you can just ignore the first argument and just specify the id as an option. Either way is fine.
Related
strong text
I'm new in laravel and vue js . I'm trying to learn Vue js .In a Laravel+Vue project, i tried to use axios to post an API response. axios is not defined in Vue js 2. How to solve this problem.When i add some data. data didn't show and also didn't work my delete function. and why I face this problem ? thanks for advance
app.js
import Vue from 'vue';
import App from './vue/app';
import { library } from '#fortawesome/fontawesome-svg-core'
import { faPlusSquare, faTrash } from '#fortawesome/free-solid-svg-icons'
import { FontAwesomeIcon } from '#fortawesome/vue-fontawesome'
library.add(faPlusSquare, faTrash)
Vue.component('font-awesome-icon', FontAwesomeIcon)
const app = new Vue ({
el: '#app',
components: { App }
});
addItemForm
<template>
<div class="addItem">
<input type="text" v-model="item.name" />
<font-awesome-icon
icon="plus-square"
#click="addItem()"
:class="[item.name ? 'active' : 'inactive', 'plus']"
/>
</div>
</template>
<script>
export default {
data: function () {
return {
item: {
name: "",
},
};
},
methods: {
addItem() {
if (this.item.name == "") {
return;
}
axios
.post("api/item/store", {
item: this.item,
})
.then((response) => {
if (response.status == 201) {
this.item.name = "";
this.$emit("reloadlist");
}
})
.catch((error) => {
console.log(error);
});
},
},
};
</script>
<style scoped>
.addItem {
display: flex;
justify-content: center;
align-content: center;
}
input {
background: rgb(236, 164, 138);
border: 0px;
outline: none;
padding: 5px;
margin-right: 10px;
width: 100%;
}
.plus {
font-size: 20px;
}
.active {
color: rgb(34, 211, 57);
}
.inactive {
color: rgb(63, 66, 63);
}
</style>
app.vue
<template>
<div class="todoListContainer">
<div class="heading">
<h2 id="title">Todo List</h2>
<add-item-form v-on:reloadlist="getList()" />
</div>
<list-view :items="items"
v-on:reloadlist="getList()" />
</div>
</template>
<script>
import addItemForm from "./addItemForm.vue";
import listView from "./listView.vue";
export default {
components: {
addItemForm,
listView,
},
data: function () {
return {
items: [],
};
},
methods: {
getList() {
axios
.post('api/items')
.then((response) => {
this.items = response.data;
})
.catch((error) => {
console.log(error);
});
},
},
created() {
this.getList();
},
};
</script>
<style scoped>
.todoListContainer {
width: 350px;
margin: auto;
}
.heading {
background: wheat;
padding: 10px;
}
#title {
text-align: center;
}
</style>
Firstly, install axios and vue-axios
npm install axios vue-axios
Seconly, import it inside your script
<script>
import addItemForm from "./addItemForm.vue";
import listView from "./listView.vue";
import axios from 'axios'; // <-- add this line
export default {
components: {
addItemForm,
listView,
},
data: function () {
return {
items: [],
};
},
methods: {
getList() {
axios
.post('api/items')
.then((response) => {
this.items = response.data;
})
.catch((error) => {
console.log(error);
});
},
},
created() {
this.getList();
},
};
</script>
In order to use, axios you have to import the axios. Considering you have already installed axios already in your project as it is third party library.
import axios from 'axios';
Add above line in the component, wherever you use the package.
First install both axios and vue-axios packages.
npm install axios vue-axios
Then in app.js file write this code:
import axios from 'axios'
import VueAxios from 'vue-axios'
Vue.use(VueAxios, axios)
// this is the default base url in laravel
axios.defaults.baseURL = 'http://127.0.0.1:8000';
// this line is written to avoid csrf error
window.axios.defaults.headers.common = {
'X-Requested-With': 'XMLHttpRequest',
'X-CSRF-TOKEN' : document.querySelector('meta[name="csrf-token"]').getAttribute('content')
};
Then when you want to use axios just write this.axios.
hi you must install Axios first
npm install axios vue-axios
After authorization, I write the user type to the state, based on this type, I want to show / hide some routes.
src/store/index.js:
import Vue from "vue";
import Vuex from "vuex";
import getters from "./getters";
import user from "./modules/user";
Vue.use(Vuex);
const store = new Vuex.Store({
modules: { user },
getters
});
export default store;
src/store/getters.js:
const getters = {
token: state => state.user.token,
name: state => state.user.name,
type: state => state.user.type
};
export default getters;
src/router/index.js:
import Vue from "vue";
import Router from "vue-router";
import Layout from "#/layout";
Vue.use(Router);
export const constantRoutes = [
{
path: "/login",
component: () => import("#/views/Login"),
hidden: true
},
{
path: "/",
component: Layout,
redirect: "/dashboard",
children: [
{
path: "dashboard",
name: "Dashboard",
component: () => import("#/views/Dashboard"),
meta: { title: "routes.dashboard", icon: "el-icon-odometer" }
}
]
},
{
path: "/providers",
component: Layout,
redirect: "/providers/list",
name: "Providers",
meta: { title: "routes.providers", icon: "el-icon-suitcase-1" },
children: [
{
path: 'list',
name: "List",
component: () => import("#/views/providers/ProvidersList"),
meta: { title: "routes.providersList", icon: "el-icon-document" }
}
]
}
];
const createRouter = () =>
new Router({
scrollBehavior: () => ({ y: 0 }),
routes: constantRoutes
});
const router = createRouter();
export function resetRouter() {
const newRouter = createRouter();
router.matcher = newRouter.matcher;
}
export default router;
Authorization control in a separate file src/permission.js:
import router from "./router";
import store from "./store";
import { Message } from "element-ui";
import NProgress from "nprogress";
import "nprogress/nprogress.css";
import { getToken } from "#/utils/auth";
import getPageTitle from "#/utils/get-page-title";
NProgress.configure({ showSpinner: false });
const whiteList = ["/login"];
router.beforeEach(async (to, from, next) => {
NProgress.start();
document.title = getPageTitle(to.meta.title);
const hasToken = getToken();
if (hasToken) {
if (to.path === "/login") {
next({ path: "/" });
NProgress.done();
} else {
const hasGetUserInfo = store.getters.name;
if (hasGetUserInfo) {
next();
} else {
try {
await store.dispatch("user/getInfo");
next();
} catch (error) {
await store.dispatch("user/resetToken");
Message.error(error || "Has Error");
next(`/login?redirect=${to.path}`);
NProgress.done();
}
}
}
} else {
if (whiteList.indexOf(to.path) !== -1) {
next();
} else {
next(`/login?redirect=${to.path}`);
NProgress.done();
}
}
});
router.afterEach(() => {
NProgress.done();
});
As you can see all the code is a collection of copy-paste solutions found somewhere and now I'm completely stuck. How can I hide and deny access to certain routes for users with different state.user.type?
Converting my comment to answer.
Perhaps it will be easier (for you) to use an existing (and tested) solution - something like Vue-ACL or even more advanced.
I'm using redux in my react project. I set initial state in my reducer
const initialState = {
isWarning: false,
};
and I have default props in my react component
LoginView.defaultProps = {
warning: false,
};
and of course I'm destructuring my props in render method
const { warning } = this.props;
here you have code from my component
import React, { Component } from 'react';
import styled from 'styled-components';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import axios from '../../axios';
import Input from '../Input/Input';
import LoggedIn from '../LoggedIn/LoggedIn';
import * as actions from '../../store/actions';
const StyledWrapper = styled.div`
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
`;
const StyledTitle = styled.p`
color: #727272;
margin: 89px 0 73px 0;
font-size: 24px;
`;
const StyledButton = styled.button`
border: none;
border-radius: 6px;
background: #1de278;
height: 48px;
width: 397px;
color: #fff;
font-size: 18px;
cursor: pointer;
&:active,
&:focus {
outline: none;
}
`;
class LoginView extends Component {
state = {
email: '',
password: '',
};
onType = event => {
this.setState({ [event.target.id]: event.target.value });
};
onSubmit = (email, password) => {
axios
.post('api/v1/session', {
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
email,
password,
})
// eslint-disable-next-line react/destructuring-assignment
.then(() => this.props.history.push('/'))
.catch(() => this.props.onLoginError(true));
};
render() {
const { email, password } = this.state;
const { warning } = this.props;
return (
<StyledWrapper>
{console.log(warning)}
<StyledTitle>Log in</StyledTitle>
<Input id="email" placeholderText="Email address"
setInputValue={this.onType} />
<Input
id="password"
placeholderText="Password"
setInputValue={this.onType}
type="password"
/>
<LoggedIn />
<StyledButton onClick={() => this.onSubmit(email,
password)}>Login</StyledButton>
</StyledWrapper>
);
}
}
const mapStateToProps = state => {
return { isWarning: state.login.isWarning };
};
const mapDispatchToProps = dispatch => {
return {
onLoginError: state => dispatch(actions.setErrorLogin(state)),
};
};
LoginView.propTypes = {
warning: PropTypes.bool,
};
LoginView.defaultProps = {
warning: false,
};
export default connect(
mapStateToProps,
mapDispatchToProps,
)(LoginView);
Redux dev tools shows my updated state from false to true, but my warning have false value. In my opinion it's something with default props. Do you have any ideas what could be a problem?
The problem is here:
const mapStateToProps = state => {
return { isWarning: state.login.isWarning };
};
it's supposed to be
const mapStateToProps = state => {
return { warning: state.login.isWarning };
};
Because the component can't find a warning prop it sets it to the default value, so you have to give it the same name.
I am relatively new to Vue and I've been reading the documentation and forums for quite a while for a solution to this one but haven't found it yet.
I am using the Vue Webpack template: here
I'm trying to create an app where I have an API endpoint to get the currency and country for the user as well as the user language from the browser.
I am using this.$store.dispatch('setlanguage') on initialisation of the Vue Component to set the state of the language, and save it to a cookie. The problem here though is that I am unable to alter the vuei18n as my app says it does not exist. The reason I am using vuei18n instead of vueXi18n is because the latter does not have the numberFormat options and this is needed in order to set correctly the currency symbols.
So to get things started:
Project structure:
main.js
App.Vue
store
getters.js
index.js
mutation-types.js
mutations.js
router
index.js
lang
locales
en.js
fr.js
de.js
lang.js
components
LocaleSwitcher.vue
pages
Footer.vue
Home.vue
main.js
import Vue from 'vue'
import VueResource from 'vue-resource'
import Cookies from 'js-cookie'
import App from './App.vue'
import i18n from './lang/lang'
import store from './store'
import router from './router'
Vue.use(VueResource)
Vue.config.productionTip = false
Vue.config.language = Cookies.get('lang')
Vue.config.country = Cookies.get('country')
Vue.config.currency = Cookies.get('cur')
export const localeStrings = {
en: 'English',
de: 'Deutsch',
fr: 'Français',
it: 'Italiano',
nl: 'Nederlands',
sv: 'Svenska',
es: 'Español',
ja: '日本語'
}
export const app = new Vue({
el: '#app',
router,
store,
i18n: i18n,
render: h => h(App),
created () {
this.$store.dispatch('setLang', Vue.config.language)
this.$store.dispatch('setCountry', Vue.config.country)
}
})
lang.js
import Vue from 'vue'
import VueI18n from 'vue-i18n'
Vue.use(VueI18n)
let i18n = new VueI18n({
locale: 'en',
messages: {},
fallbackLocale: 'en',
numberFormats: {}
})
export default {i18n}
App.vue
template>
<div id="app">
<app-header></app-header>
<div class="main-content">
<router-view></router-view>
</div>
<app-footer></app-footer>
</div>
</template>
<script>
export default {
name: 'myapp',
components: {
'app-footer': Footer
},
data () {
return {
msg: 'Welcome to Your Vue.js App',
language: ''
}
}
}
<style>
body {
margin:0;
padding:0;
}
</style>
Home.vue
<template>
<h1>Hello Home! {{msg}}</h1>
</template>
<script>
export default {
data () {
return {
msg: 'Home msg'
}
}
}
</script>
<style scoped>
h1 {
font-family: 'Trebuchet MS', 'Lucida Sans Unicode', 'Lucida Grande', 'Lucida Sans', Arial, sans-serif;
}
</style>
Footer.vue
<template>
<footer>
<p>{{copyright}}</p>
<local-switcher></local-switcher>
</footer>
</template>
<script>
import LocaleSwitcher from '../components/LocaleSwitcher.vue'
export default {
name: 'Footer',
components: {
'local-switcher': LocaleSwitcher
},
data () {
return {
copyright: 'Copyright 2018 Francesco'
}
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
footer{
background: lightgrey;
padding: 10px;
}
</style>
LocaleSwitcher.vue
<template>
<div class="locale-switcher">
<select v-model="activeLocale" #change="changeLang">
<option v-for="(lang, id) in locales" :key="id" v-bind:value="id">{{lang}}</option>
</select>
</div>
</template>
<script>
import {localeStrings} from '../main'
export default {
name: 'locale-switcher',
data: function () {
return {
activeLocale: this.$store.getters.getLanguage,
locales: localeStrings
}
},
methods: {
changeLang () {
this.setLang(this.activeLocale)
},
setLang (lang) {
this.$store.dispatch('setLang', lang)
}
}
}
</script>
router/index.js
import Vue from 'vue'
import Router from 'vue-router'
import p404 from '../pages/error/404.vue'
import Home from '../pages/Home.vue'
Vue.use(Router)
const routes = [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '*',
name: '404',
component: p404
}
]
let router = new Router({
mode: 'history',
routes
})
// I still have to figure out how to use this to set www.mypage.com/mylanguage/home
// use beforeEach route guard to set the language
router.beforeEach((to, from, next) => {
// use the language from the routing param or default language
let language = to.params.lang
console.log(language)
console.log('router end')
if (!language) {
language = 'en'
}
// set the current language for vuex-i18n. note that translation data
// for the language might need to be loaded first
// Vue.i18n.set(language)
next()
})
export default router
store/getters.js
export const getters = {
getLanguage: state => {
return state.language
},
getCountry: state => {
return state.country
},
getCurrency: state => {
return state.currency
}
}
store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
import { state, mutations, actions } from './mutations'
import { getters } from './getters'
Vue.use(Vuex)
// const mapState = Vuex.mapState
const store = new Vuex.Store({
state,
mutations,
actions,
getters
})
export default store
store/mutation-types.js
export const SET_LANG = 'SET_LANG'
export const SET_COUNTRY = 'SET_COUNTRY'
export const SET_CURRENCY = 'SET_CURRENCY'
store/mutations.js
import Vue from 'vue'
import Cookies from 'js-cookie'
import * as types from './mutation-types'
import {app, supportedLocale, supportedCurrencies} from '../main'
import CountryCurrency from '../data/country_currency.json'
import locale2 from 'locale2'
export const state = {
language: Cookies.get(settings.languageCookie),
country: Cookies.get(settings.countryCookie),
currency: Cookies.get(settings.currencyCookie),
loading: false
}
export const mutations = {
[types.SET_LANG] (state, payload) {
Cookies.set('lang', payload)
state.language = payload
},
[types.SET_COUNTRY] (state, payload) {
Cookies.set('country', payload)
state.country = payload
},
[types.SET_CURRENCY] (state, payload) {
Cookies.set('currency', payload)
state.currency = payload
}
}
export const actions = {
async setLang ({commit}, language) {
console.log(app)
console.log(Vue)
var vueMessages = app.$i18n.messages
var userLocale = 'en'
var browserLocale = locale2.split('-', 1)[0]
// testing browser language
if (locale === undefined) {
// get browser language
userLocale = browserLocale
} else if (browserLocale !== locale) {
console.log('browser language changed')
userLocale = browserLocale
} else {
userLocale = locale
}
// check for supported languages
if (!supportedLocale.includes(userLocale)) {
userLocale = 'en'
}
if (language in vueMessages) {
console.log('already in messages')
commit(types.SET_LANG, language)
} else if (!supportedLocale.includes(language)) {
console.log('not supported so commiting default en')
commit(types.SET_LANG, 'en')
} else {
try {
// you can use fetch or import which ever you want.
// Just make sure your webpack support import syntax
// const res = await axios.get(`./src/lang/${payload}.json`)
const res = await import(`../lang/locales/${language}.json`)
app.$i18n.locale = language
app.$i18n.setLocaleMessage(language, res)
var addNumberFormats = {
currency: {
style: 'currency', currencyDisplay: 'symbol', currency: `${app.$i18n.currency}`
}
}
app.$i18n.mergeNumberFormat(language, addNumberFormats)
Cookies.set('lang', language)
commit(types.SET_LANG, language)
} catch (e) {
console.log(e)
}
}
},
setCountry ({commit}, countryCode) {
var userCountry = 'NA'
if (countryCode === undefined || countryCode === 'NA') {
Vue.http.get('https://www.myapi.com/api/v2/geo/country-code', {
timeout: 100
}).then(response => {
state.country = response.data.code
Cookies.set('country', response.data.code)
}, response => {
// error callback
state.country = userCountry
Cookies.set('country', userCountry)
})
} else {
console.log(countryCode)
state.country = countryCode
Cookies.set('country', countryCode)
}
},
setCurrency ({commit}, countryCode) {
var userCurrency = 'USD'
console.log('user country ' + countryCode)
console.log('user currency ' + CountryCurrency[countryCode])
console.log('currency supported: ' + supportedCurrencies.includes(CountryCurrency[countryCode]))
if (CountryCurrency[countryCode] && supportedCurrencies.includes(CountryCurrency[countryCode])) {
userCurrency = CountryCurrency[countryCode]
}
Cookies.set('currency', userCurrency)
app.$i18n.currency = userCurrency
try {
var addNumberFormats = {
currency: {
style: 'currency', currencyDisplay: 'symbol', currency: `${app.$i18n.currency}`
}
}
if (!app.$i18n.numberFormats[app.$i18n.locale]) {
console.log('merge currency')
app.$i18n.setNumberFormat(app.$i18n.locale, addNumberFormats)
}
} catch (error) {
console.log(error)
}
}
}
The problem is that once loaded, I cannot access the app vue i18n. Am I accessing it incorrectly or is there another way to add language and currency settings to this? I hope I have provided enough info Thanks!
Could this answer be relevant in your case? https://stackoverflow.com/a/45460729/2964531
If you need to access or mutate properties of i18n, you can import it directly from store/mutations.js
I believe what I am trying to achieve has been done many times, but I can't manage it.
I would just like to be able to test if an element has a certain class on a certain element.
Splash
import React from 'react';
import { NavLink } from 'react-router-dom'
import Logo from '../shared/logo/index';
import * as styles from './style.css';
class Splash extends React.Component {
render(){
return (
<div className={styles.indexAppContent}>
<NavLink to="/home" className={styles.index}>
<Logo />
</NavLink>
</div>
);
}
}
export default Splash;
style.css
.index {
div {
color: #FFF;
//font-size: 8rem;
}
position: absolute;
left: 50%;
top: 50%;
display: block;
transform: translate3d(-50%, -50%, 0);
-webkit-transform: translate3d(-50%, -50%,0);
-moz-transform: translate3d(-50%, -50%,0);
-ms-transform: translate3d(-50%, -50%,0);
}
.indexAppContent {
height: 100vh;
width: 100vw;
position: relative;
}
However this is the output:
{ className: undefined,
children:
{ '$$typeof': Symbol(react.element),
type: { [Function: NavLink] propTypes: [Object], defaultProps: [Object] },
key: null,
ref: null,
props:
{ to: '/home',
className: undefined,
children: [Object],
activeClassName: 'active',
ariaCurrent: 'true' },
_owner: null,
_store: {} } }
Splash
/* eslint-disable object-property-newline */
import React from 'react';
import ReactTestUtils from 'react-dom/test-utils'
import { expect } from 'chai';
import { NavLink } from 'react-router-dom'
import { shallow } from 'enzyme';
//Splash
import Splash from '../../../src/components/Splash';
import * as styles from '../../../src/components/Splash/style.css';
//logo
import Logo from '../../../src/components/shared/logo';
describe('<Splash />', () => {
const wrapperSplash = shallow(<Splash/>);
const wrapperNavLink = shallow(<NavLink />);
const wrapperLogo = shallow(<Logo />);
it('must be defined', () => {
expect(wrapperSplash).to.be.defined;
});
it('should have one logo', () => {
expect(wrapperSplash.find(Logo)).to.have.length(1);
})
it('should have className', () => {
expect(wrapperSplash.first().prop('className'))
.to.contain('indexAppContent');
})
it('Logo links to Home', () => {
expect(wrapperSplash.find(NavLink).first().props().to)
.equals('/Home');
})
});
Test
/* eslint-disable object-property-newline */
import React from 'react';
import ReactTestUtils from 'react-dom/test-utils'
import { expect } from 'chai';
import { NavLink } from 'react-router-dom'
import { shallow } from 'enzyme';
it('should have className', () => {
console.info(wrapperSplash.first().props());
expect(wrapperSplash.first().prop('className'))
.to.contain('indexAppContent');
})
Test Helper
import path from 'path';
import csshook from 'css-modules-require-hook/preset' // import hook before routes
import routes from '/shared/views/routes'
import requireHacker from 'require-hacker';
import sass from 'node-sass';
import {jsdom} from 'jsdom';
import injectTapEventPlugin from 'react-tap-event-plugin';
injectTapEventPlugin();
hook({
extensions: ['.css'],
generateScopedName: '[local]',
preprocessCss: (data, filename) =>
sass.renderSync({
data,
file: filename,
importer: (url) => {
if (url.indexOf('~') === 0) {
const node_modules_path = path.resolve(__dirname, '../..', 'node_modules');
return {
file: path.join(node_modules_path, url.replace('~', ''))
};
}
return {file: url};
}
}).css
});
const fakeComponentString = `
module.exports = require('react').createClass({
render() {
return null;
}
});
`;
requireHacker.hook('svg', () => fakeComponentString);
// jsdom
const exposedProperties = ['window', 'navigator', 'document'];
global.document = jsdom('');
global.window = document.defaultView;
Object.keys(document.defaultView).forEach((property) => {
if (typeof global[property] === 'undefined') {
global[property] = document.defaultView[property];
}
});
global.navigator = {
userAgent: 'node.js'
};
You are checking a prop of that component and not the existence of a class in the root node that was rendered by this component.
Obviously you are not passing this prop.
You set the class on the root element of this component, thus you should check for nested element that hold this class value in the dom and not the attribute (prop) className
You can do it with this syntax for example:
it('should have class of indexAppContent', () => {
expect(wrapperSplash.find('.indexAppContent')).to.have.length(1);
})