{{ message }}
var app = new Vue({
el: '#app',
data: {
message: 'Hello hostel!'
}
})
Counter: {{ counter }}
(open Chrome DevTools)
{{ hostel.name }}
Book now
var app = new Vue({
el: '#app',
data: {
hostel: {
name: 'Happy Hostel',
availability: true
}
}
})
var app = new Vue({
el: '#app',
data: {
hostel: {
name: 'Happy Hostel',
availability: true
}
}
})
{{ hostel.name }}
Book now
var app = new Vue({
el: '#app',
data: {
hostel: {
name: 'Happy Hostel',
availability: true,
isActive: true
}
}
})
{{ hostel.name }}
var app = new Vue({
el: '#app',
data: {
hostels: [
{ name: 'Happy Hostel' },
{ name: 'Awesome Hostel' },
{ name: 'Another Hostel' }
]
}
})
Message: {{ message }}
Message: {{ message }}
const vueModel = new Vue({
el: '#app',
data: {
message: 'Long live v-model!'
},
computed: {
messageUpperCase () {
return this.message.toUpperCase()
}
}
})
Vue.component('hostel-detail', {
template: `I'm a hostel!`
})
new Vue({
el: '#app'
})
Vue.component('hostel-detail', {
template: `hostel: {{ name }}`,
props: {
name: String
}
})
shortcut: :name="'Cool hostel'"
Vue.component('hostel-price', {
template: `Price: {{ price }}`,
props: {
price: {
type: Number,
required: true,
validator (value) {
return value > 0
},
default: 100
}
}
})
new Vue({
el: '#emitEvents',
data: {
hostel: {
name: 'Cool hostel'
}
},
methods: {
addToCart (hostel) {
this.$emit('selecthostel', hostel)
}
}
})
Cart: {{ cartToString }}
Vue.component('hostel-list-item', {
template: '',
props: ['hostel'],
methods: {
click () {
this.$emit('select', this.hostel)
}
}
})
var hostelList = new Vue({
el: '#hostel-list-demo',
name: 'hostelList',
data: {
properties: ['hostelA', 'hostelB'],
cart: []
},
computed: {
cartToString () {
return this.cart.length ? this.cart.join(' ') : '(empty)'
}
},
methods: {
add (hostel) {
this.cart.push(hostel)
}
}
})
Cart: {{ cartToString }}
Vue.JS is...{{ appreciation }}
var vueWatcher = new Vue({
el: '#vue-watcher',
data: {
appreciation: ''
},
watch: {
appreciation (newappreciation) {
if (newappreciation === 'bad') {
this.appreciation = 'awesome'
}
}
}
})
Vue.JS is...{{ appreciation }}
mounted()
{{ starship.name }}
{{ starship.model }}
const lifecycle = new Vue({
el: '#lifecycle',
data: {
starship: {}
},
mounted () {
fetch('https://swapi.co/api/starships/12/')
.then(response => response.json())
.then(data => {
this.starship = data
})
}
})
hot reload > live reload
npm install -g vue-cli
vue init webpack my-project
cd my-project
npm install
npm run dev
├── src/
│ ├── main.js
│ ├── App.vue
│ └── api/
│ ├── bookings.js
│ ├── hostel.js
│ └── user.js
│ └── components/
│ └── ...
│ └── .../
│ └── ...
// src/api/hostel.js
import axios from 'axios'
export function findAll (limit = 25) {
return axios.get('hostels/?limit=' + limit)
.then(response => response.data)
}
import * as hostelApi from '../api/hostels'
export default {
data () {
return {
hostels: []
}
},
created () {
hostelApi.findAll().then((data) => {
this.hostels = data
})
}
}
Go to my account
Go to booking
const Account = { template: 'account' }
const Bookings = { template: 'bookings' }
const routes = [
{ path: '/account', component: Account },
{ path: '/bookings', component: Bookings }
]
const router = new VueRouter({
routes: routes
})
const app = new Vue({
router
}).$mount('#app')
Components need to share the same state
├── src/
│ ├── main.js
│ ├── App.vue
│ └── api/
│ └── ...
│ └── components/
│ └── ...
│ └── store/
│ └── modules/
│ └── ...
│ ├── index.js
│ └── mutation-types.js
// store/modules/hostel.js
import * as api from '../api'
import * as types from '../mutation-types'
export default {
state: {
hostels: []
},
getters = {
allHostels: state => state.hostels,
highRatedHostels: state => state.hostels.filter(h => h.score >= 7)
},
mutations: {
[types.RECEIVE_HOSTEL] (state, hostels) {
state.hostels = hostels
}
},
actions: {
loadCountryHostels ({ commit }, country) {
commit(types.LOAD_HOSTELS_BY_COUNTRY, { country })
api.getHostels(country).then(data => {
commit(types.RECEIVE_HOSTELS, { data })
})
}
}
}
// MyComponent.vue
import { mapGetters, mapActions } from 'vuex'
export default {
computed: mapGetters({
hostels: 'allHostels'
}),
methods: mapActions([
'loadCountryHostels'
]),
created () {
this.$store.dispatch('loadCountryHostels', 'Berlin')
}
}
// store/actions.js
export const addToSelection = ({ commit }, hostel) => {
commit(types.ADD_TO_SELECTION, { hostel })
}
// store/modules/hostelList.js
[types.ADD_TO_SELECTION] (state, { hostel }) {
state.hostelList.slice(state.hostelList.indexOf(hostel), 1)
}
// store/modules/hostelSelection.js
[types.ADD_TO_SELECTION] (state, { hostel }) {
state.selection.push(hostel)
}
├── test/
│ ├── __mocks__
│ ├── __snapshots__
│ └── api/
│ └── hostelApi.spec.js
│ └── components/
│ └── __snapshots__/
│ └── Selection.spec.js.snap
│ └── MyComponent.spec.js
│ └── store/
│ └── modules/
│ └── myModule.spec.js
expect(foo).toBe() // use ===
expect(foo).toEqual() // object have same values (not necessary same reference)
expect(true).toBeTruthy()
expect(false).toBeFalsy()
expect('banana').toContain('ban')
import { mount, shallow } from 'vue-test-utils'
import MyComponent from '@/components/MyComponent.vue'
import store from '@/store'
describe('Shop.vue', () => {
it('test initial rendering', () => {
const wrapper = mount(MyComponent, { store })
const template = wrapper.html()
expect(template).toMatchSnapshot()
})
})
it('should do something on close', () => {
const wrapper = mount(MyComponent, { store })
const button = wrapper.findAll('div.btn')
button.trigger('click')
expect(/** ... */).toBe('true')
});
it('should do something on close', () => {
// 🚧 TODO 🚧
});
// tests/unit/specs/__mocks__/axios.js
import userBookingResponse from '../api/userBooking.response.json'
class Axios {
get (url) {
if (url === '/users/1/bookings'){
const data = userBookingResponse
}
const response = { status: 200, statusText: 'OK', data: data }
return Promise.resolve(response)
}
}
export default new Axios()
// tests/unit/specs/__mocks__/axios.js
class Axios {
get (fullUrl) {
const url = fullUrl.replace(process.env.API_URL, '')
switch (url) {
case 'users/1': return Promise.resolve({ data: import('../api/user.1.response.json') })
case 'hostels/?page=3': return Promise.resolve({ data: import('../api/hostels.3.response.json') })
// ...
}
}
}
export default new Axios()
// tests/unit/specs/api/userBookingApi.spec.js
import userBookingResponse from './userBooking.response.json'
import * as userBookingApi from '@/api/userBookingApi'
describe('userBooking HTTP', () => {
it('getUserBooking() should return the response data', () => {
expect.assertions(1)
return expect(userBookingApi.getUserBooking()).resolves.toEqual(userBookingResponse)
})
})
it('return all hostels', () => {
const state = {
hostels: [{ name: 'hostelA'}, { name: 'hostelB'}]
}
const result = hostelStore.getters.allHostels(state)
expect(result).toEqual({ name: 'hostelA'}, { name: 'hostelB'})
})
// tests/unit/custom/testAction.js
export const testAction = (action, payload, state, expectedMutations, done) => {
let count = 0
// mock commit
const commit = (type, payload) => {
const mutation = expectedMutations[count]
try {
expect(mutation.type).toEqual(type)
if (payload) {
expect(mutation.payload).toEqual(payload)
}
} catch (error) {
done(error)
}
count++
if (count >= expectedMutations.length) {
done()
}
}
// call the action with mocked store and arguments
action({ commit, state }, payload)
// check if no mutations should have been dispatched
if (expectedMutations.length === 0) {
expect(count).toEqual(0)
done()
}
}
testAction(hostel.actions.loadCountryHostels, 'Spain', {}, [
//expected commits
{ type: 'LOAD_HOSTELS_BY_COUNTRY', payload: { country: 'Spain' } },
{ type: 'RECEIVE_HOSTELS', payload: { data: expectedJSONHostels } }
], done)
Dumb components:
Smart components (aka containers):