diff --git a/js/src/mainviewmodel.js b/js/src/mainviewmodel.js new file mode 100644 index 0000000..645fcf5 --- /dev/null +++ b/js/src/mainviewmodel.js @@ -0,0 +1,79 @@ +/* + * μlogger + * + * Copyright(C) 2019 Bartek Fabiszewski (www.fabiszewski.net) + * + * This is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +import ViewModel from './viewmodel.js'; + +const hiddenClass = 'menu-hidden'; + +export default class MainViewModel extends ViewModel { + + /** + * @param {uState} state + */ + constructor(state) { + super({ + onMenuToggle: null, + onShowUserMenu: null + }); + this.state = state; + this.model.onMenuToggle = () => this.toggleSideMenu(); + this.model.onShowUserMenu = () => this.toggleUserMenu(); + this.hideUserMenuCallback = (e) => this.hideUserMenu(e); + this.menuEl = document.querySelector('#menu'); + this.userMenuEl = document.querySelector('#user-menu'); + } + + init() { + this.bindAll(); + } + + toggleSideMenu() { + if (this.menuEl.classList.contains(hiddenClass)) { + this.menuEl.classList.remove(hiddenClass); + } else { + this.menuEl.classList.add(hiddenClass); + } + } + + /** + * Toggle user menu visibility + */ + toggleUserMenu() { + if (this.userMenuEl.classList.contains(hiddenClass)) { + this.userMenuEl.classList.remove(hiddenClass); + window.addEventListener('click', this.hideUserMenuCallback, true); + } else { + this.userMenuEl.classList.add(hiddenClass); + } + } + + /** + * Click listener callback to hide user menu + * @param {MouseEvent} event + */ + hideUserMenu(event) { + const el = event.target; + this.userMenuEl.classList.add(hiddenClass); + window.removeEventListener('click', this.hideUserMenuCallback, true); + if (!el.parentElement.classList.contains('user-menu')) { + event.stopPropagation(); + } + } + +} diff --git a/js/test/mainviewmodel.test.js b/js/test/mainviewmodel.test.js new file mode 100644 index 0000000..0863666 --- /dev/null +++ b/js/test/mainviewmodel.test.js @@ -0,0 +1,135 @@ +/* + * μlogger + * + * Copyright(C) 2019 Bartek Fabiszewski (www.fabiszewski.net) + * + * This is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +import MainViewModel from '../src/mainviewmodel.js'; +import ViewModel from '../src/viewmodel.js'; +import uState from '../src/state.js'; + +describe('MainViewModel tests', () => { + + const hiddenClass = 'menu-hidden'; + let vm; + let state; + let menuEl; + let userMenuEl; + + beforeEach(() => { + const fixture = `
+
+ user + +
+ +
`; + document.body.insertAdjacentHTML('afterbegin', fixture); + menuEl = document.querySelector('#menu'); + userMenuEl = document.querySelector('#user-menu'); + spyOn(window, 'addEventListener'); + spyOn(window, 'removeEventListener').and.callThrough(); + state = new uState(); + vm = new MainViewModel(state); + }); + + afterEach(() => { + document.body.removeChild(document.querySelector('#fixture')); + }); + + it('should create instance', () => { + expect(vm).toBeInstanceOf(ViewModel); + expect(vm.state).toBe(state); + expect(vm.menuEl).toBe(menuEl); + expect(vm.userMenuEl).toBe(userMenuEl); + }); + + it('should hide side menu', (done) => { + // given + const buttonEl = document.querySelector('#menu-button a'); + vm.init(); + // when + buttonEl.click(); + // then + setTimeout(() => { + expect(menuEl.classList.contains(hiddenClass)).toBe(true); + done(); + }, 100); + }); + + it('should show side menu', (done) => { + // given + const buttonEl = document.querySelector('#menu-button a'); + menuEl.classList.add(hiddenClass); + vm.init(); + // when + buttonEl.click(); + // then + setTimeout(() => { + expect(menuEl.classList.contains(hiddenClass)).toBe(false); + done(); + }, 100); + }); + + it('should hide user menu', (done) => { + // given + const buttonEl = document.querySelector('#user-menu-button'); + userMenuEl.classList.remove(hiddenClass); + vm.init(); + // when + buttonEl.click(); + // then + setTimeout(() => { + expect(userMenuEl.classList.contains(hiddenClass)).toBe(true); + done(); + }, 100); + }); + + it('should show user menu', (done) => { + // given + const buttonEl = document.querySelector('#user-menu-button'); + vm.init(); + // when + buttonEl.click(); + // then + setTimeout(() => { + expect(userMenuEl.classList.contains(hiddenClass)).toBe(false); + expect(window.addEventListener).toHaveBeenCalledTimes(1); + expect(window.addEventListener).toHaveBeenCalledWith('click', vm.hideUserMenuCallback, true); + done(); + }, 100); + }); + + it('should hide user menu on window click', (done) => { + // given + userMenuEl.classList.remove(hiddenClass); + window.addEventListener.and.callThrough(); + window.addEventListener('click', vm.hideUserMenuCallback, true); + vm.init(); + // when + document.body.click(); + // then + setTimeout(() => { + expect(userMenuEl.classList.contains(hiddenClass)).toBe(true); + expect(window.removeEventListener).toHaveBeenCalledTimes(1); + expect(window.removeEventListener).toHaveBeenCalledWith('click', vm.hideUserMenuCallback, true); + done(); + }, 100); + }); + +});