As part of learning Vue and Vue Test Utils I develop some simple games. I made a simple version of craps. Because in many other games a die (or dice) is used, I decided to create a reusable component GameDie
.
GameDie.vue
<template>
<span class="dice">{{ dice.icon }}</span>
</template>
<script>
export default {
name: 'GameDie',
data() {
return {
dieFaces: [
{
value: undefined,
icon: '🎲',
},
{
value: 1,
icon: '⚀',
},
{
value: 2,
icon: '⚁',
},
{
value: 3,
icon: '⚂',
},
{
value: 4,
icon: '⚃',
},
{
value: 5,
icon: '⚄',
},
{
value: 6,
icon: '⚅',
},
],
value: undefined,
};
},
computed: {
dice() {
return this.dieFaces.filter((el) => el.value === this.value).shift();
},
},
methods: {
/**
* Generate a random number between 1 and 6.
* Assign it to the property value.
*/
throwDice() {
this.value = 1 + Math.floor(Math.random() * 6);
this.$eventBus.$emit('transmit-dice', this.value);
},
/**
* Reset the dice.
*/
resetDice() {
if (this.value) {
this.value = undefined;
}
},
},
created() {
this.$eventBus.$on('throw-dice', this.throwDice);
this.$eventBus.$on('reset-dice', this.resetDice);
},
};
</script>
<style scoped>
.dice {
font-size: 4em;
}
</style>
I use emojis for the dice. When the die is thrown the value is calculated with Math.random
. The component relies on a global event bus.
My main.js
looks like this:
import Vue from 'vue';
import { BootstrapVue } from 'bootstrap-vue';
import App from './App.vue';
import 'bootstrap/dist/css/bootstrap.css';
import 'bootstrap-vue/dist/bootstrap-vue.css';
Vue.use(BootstrapVue);
Vue.config.productionTip = false;
Vue.prototype.$eventBus = new Vue();
new Vue({
render: (h) => h(App),
}).$mount('#app');
The GameDie.vue
component can now be used for different games, for craps or backgammon you need two dices:
<template>
<div>
<game-die></game-die>
<game-die></game-die>
</div>
</template>
For yamb you would need for example 5 dice, for Mensch ärgere Dich nicht just one.
Since I didn't have any other idea I used a global event bus to trigger the throwing and resetting the die. If we include GameDie.vue
as a child component then the parent should actually communicate with it by passing props. But I was not sure how could I implement this.
The unit tests are here:
import { expect } from 'chai';
import sinon from 'sinon';
import mergeWith from 'lodash.mergewith';
import { shallowMount, createLocalVue } from '@vue/test-utils';
import Vue from 'vue';
import GameDie from '@/components/GameDie.vue';
/* eslint-disable no-param-reassign */
const GlobalPlugins = {
install(v) {
v.prototype.$eventBus = new Vue();
},
};
/* eslint-enable no-param-reassign */
const localVue = createLocalVue();
localVue.use(GlobalPlugins);
describe('GameDie.vue', () => {
function createWrapper(overrides) {
const options = {
mocks: {
$eventBus: {
$on: sinon.spy(),
$emit: sinon.spy(),
},
},
};
return shallowMount(GameDie, mergeWith(options, overrides));
}
it('should render the dice', () => {
const wrapper = createWrapper();
expect(wrapper.text()).to.equal('🎲');
});
it('should render a die face', () => {
const data = () => ({
value: 2,
});
const wrapper = createWrapper({ data });
expect(wrapper.vm.value).to.equal(2);
expect(wrapper.text()).to.equal('⚁');
});
it('should listen to custom events', () => {
const wrapper = createWrapper();
expect(wrapper.vm.$eventBus.$on.calledTwice).to.equal(true);
expect(wrapper.vm.$eventBus.$on.calledWith('throw-dice')).to.equal(true);
expect(wrapper.vm.$eventBus.$on.calledWith('reset-dice')).to.equal(true);
});
it('should listen to custom event throw-dice', () => {
const throwDiceSpy = sinon.spy();
const wrapper = shallowMount(GameDie, {
localVue,
methods: {
throwDice: throwDiceSpy,
},
});
wrapper.vm.$eventBus.$emit('throw-dice');
expect(throwDiceSpy.called).to.equal(true);
});
it('should listen to custom event reset-dice', () => {
const resetDiceSpy = sinon.spy();
const wrapper = shallowMount(GameDie, {
localVue,
methods: {
resetDice: resetDiceSpy,
},
});
wrapper.vm.$eventBus.$emit('reset-dice');
expect(resetDiceSpy.called).to.equal(true);
});
it('should emit a custom event transmit-dice', () => {
const wrapper = createWrapper();
wrapper.vm.throwDice();
expect(wrapper.vm.$eventBus.$emit.called).to.equal(true);
});
});
What can be improved here? Is there a better way using props? Or maybe Vue.js is not an adequate tool for programming games?
For the unit tests I used the input from a question I posted on StackOverflow. If the approach with the global event bus is good, how would you enhance the tests?