{{ 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):