Use external fixture files

This commit is contained in:
Bartek Fabiszewski 2019-12-24 13:33:54 +01:00
parent 8f2d673950
commit 54b25da4b7
11 changed files with 300 additions and 98 deletions

View File

@ -30,6 +30,7 @@ export default class ViewModel {
*/
constructor(model) {
this._model = model;
this.root = document;
}
/**
@ -41,8 +42,10 @@ export default class ViewModel {
/**
* Apply bindings for model properties
* @param {HTMLElement=} root Root element
*/
bindAll() {
bindAll(root = document) {
this.root = root;
for (const key in this._model) {
if (this._model.hasOwnProperty(key)) {
this.bind(key);
@ -58,7 +61,7 @@ export default class ViewModel {
*/
bind(key) {
const dataProp = 'bind';
const observers = document.querySelectorAll(`[data-${dataProp}]`);
const observers = this.root.querySelectorAll(`[data-${dataProp}]`);
observers.forEach(/** @param {HTMLElement} element */ (element) => {
const name = element.dataset[dataProp];
if (name === key) {

View File

@ -19,6 +19,7 @@
import ChartViewModel from '../src/chartviewmodel.js';
import Chartist from 'chartist'
import Fixture from './helpers/fixture.js';
import TrackFactory from './helpers/trackfactory.js';
import ViewModel from '../src/viewmodel.js';
import { lang } from '../src/initializer.js';
@ -43,6 +44,12 @@ describe('ChartViewModel tests', () => {
let chartData;
let chartPointNodes;
beforeEach((done) => {
Fixture.load('main.html')
.then(() => done())
.catch((e) => done.fail(e));
});
beforeEach(() => {
// language=XML
chartFixture = `<svg xmlns:ct="http://gionkunz.github.com/chartist-js/ct" width="100%" height="100%" class="ct-chart-line">
@ -59,15 +66,6 @@ describe('ChartViewModel tests', () => {
</g>
<g class="ct-labels"/>
</svg>`;
const fixture = `<div id="fixture">
<div id="other" class="section">
<a id="altitudes" data-bind="onChartToggle">chart</a>
</div>
<div id="bottom">
<div id="chart"></div>
<a id="chart-close" data-bind="onChartToggle">close</a>
</div>
</div>`;
chartData = [
{ x: 0, y: 130 },
{ x: 48, y: 104 },
@ -76,7 +74,6 @@ describe('ChartViewModel tests', () => {
{ x: 236, y: 118 },
{ x: 387, y: 118 }
];
document.body.insertAdjacentHTML('afterbegin', fixture);
chartEl = document.querySelector('#chart');
chartContainerEl = document.querySelector('#bottom');
buttonEl = document.querySelector('#altitudes');
@ -95,7 +92,7 @@ describe('ChartViewModel tests', () => {
});
afterEach(() => {
document.body.removeChild(document.querySelector('#fixture'));
Fixture.clear();
uObserve.unobserveAll(lang);
});

View File

@ -19,6 +19,7 @@
import { config, lang } from '../src/initializer.js';
import ConfigViewModel from '../src/configviewmodel.js';
import Fixture from './helpers/fixture.js';
import ViewModel from '../src/viewmodel.js';
import uObserve from '../src/observe.js';
import uState from '../src/state.js';
@ -43,6 +44,12 @@ describe('ConfigViewModel tests', () => {
const newLang = 'pl';
const newUnits = 'imperial';
beforeEach((done) => {
Fixture.load('main.html')
.then(() => done())
.catch((e) => done.fail(e));
});
beforeEach(() => {
config.reinitialize();
config.interval = 10;
@ -50,34 +57,6 @@ describe('ConfigViewModel tests', () => {
config.units = 'metric';
config.mapApi = 'gmaps';
const fixture = `<div id="fixture">
<div class="section">
<a id="set-interval" data-bind="onSetInterval"><span id="interval" data-bind="interval">${config.interval}</span></a>
</div>
<div>
<label for="api">api</label>
<select id="api" name="api" data-bind="mapApi">
<option value="gmaps" selected>Google Maps</option>
<option value="openlayers">OpenLayers</option>
</select>
</div>
<div>
<label for="lang">lang</label>
<select id="lang" name="lang" data-bind="lang">
<option value="en" selected>English</option>
<option value="pl">Polish</option>
</select>
</div>
<div class="section">
<label for="units">units</label>
<select id="units" name="units" data-bind="units">
<option value="metric" selected>Metric</option>
<option value="imperial">Imperial</option>
<option value="nautical">Nautical</option>
</select>
</div>
</div>`;
document.body.insertAdjacentHTML('afterbegin', fixture);
intervalEl = document.querySelector('#interval');
apiEl = document.querySelector('#api');
langEl = document.querySelector('#lang');
@ -92,7 +71,7 @@ describe('ConfigViewModel tests', () => {
});
afterEach(() => {
document.body.removeChild(document.querySelector('#fixture'));
Fixture.clear();
uObserve.unobserveAll(lang);
});

101
js/test/fixtures/main-authorized.html vendored Normal file
View File

@ -0,0 +1,101 @@
<div id="container">
<div id="menu">
<div id="menu-content">
<div>
<a data-bind="onShowUserMenu"><img class="icon" alt="User" src="images/user.svg"> testUser</a>
<div id="user-menu" class="menu-hidden">
<a id="user-pass"><img class="icon" alt="Change password" src="images/lock.svg"> Change password</a>
<a href="utils/logout.php"><img class="icon" alt="Log out" src="images/poweroff.svg"> Log out</a>
</div>
</div>
<div class="section">
<label for="user">User</label>
<select id="user" data-bind="currentUserId" name="user"></select>
</div>
<div class="section">
<label for="track">Track</label>
<select id="track" data-bind="currentTrackId" name="track"></select>
<input id="latest" type="checkbox" data-bind="showLatest"> <label for="latest">latest position</label><br>
<input id="auto-reload" type="checkbox" data-bind="autoReload"> <label for="auto-reload">autoreload</label> (<a id="set-interval" data-bind="onSetInterval"><span id="interval" data-bind="interval">10</span></a> s)<br>
<a id="force-reload" data-bind="onReload"> Reload now</a><br>
</div>
<div id="summary" class="section" data-bind="summary"></div>
<div id="other" class="section">
<a id="altitudes" data-bind="onChartToggle">Altitudes chart</a>
</div>
<div>
<label for="api">Map API</label>
<select id="api" name="api" data-bind="mapApi">
<option value="gmaps">Google Maps</option>
<option value="openlayers" selected>OpenLayers</option>
</select>
</div>
<div>
<label for="lang">Language</label>
<select id="lang" name="lang" data-bind="lang">
<option value="cs">Čeština</option>
<option value="de">Deutsch</option>
<option value="en" selected>English</option>
<option value="es">Español</option>
<option value="fr">Français</option>
<option value="it">Italiano</option>
<option value="hu">Magyar</option>
<option value="nl">Nederlands</option>
<option value="pl">Polski</option>
<option value="ru">Русский</option>
<option value="zh">中文</option>
</select>
</div>
<div class="section">
<label for="units">Units</label>
<select id="units" name="units" data-bind="units">
<option value="metric" selected>Metric</option>
<option value="imperial">Imperial/US</option>
<option value="nautical">Nautical</option>
</select>
</div>
<div class="section">
<div class="menu-title">Export track</div>
<a id="export-kml" class="menu-link" data-bind="onExportKml">kml</a>
<a id="export-gpx" class="menu-link" data-bind="onExportGpx">gpx</a>
</div>
<div class="section">
<div id="import" class="menu-title">Import track</div>
<form id="import-form" enctype="multipart/form-data" method="post">
<input type="hidden" name="MAX_FILE_SIZE" value="8388608" />
<input type="file" id="input-file" name="gpx" data-bind="inputFile"/>
</form>
<a id="import-gpx" class="menu-link" data-bind="onImportGpx">gpx</a>
</div>
<div id="admin-menu">
<div class="menu-title">Administration</div>
<a id="adduser" class="menu-link">Add user</a>
<a id="edituser" class="menu-link">Edit user</a>
<a id="edittrack" class="menu-link">Edit track</a>
</div>
</div>
<div id="menu-button"><a data-bind="onMenuToggle"></a></div>
<div id="footer"><a target="_blank" href="https://github.com/bfabiszewski/ulogger-server"><span class="mi">μ</span>logger</a> 1.0-beta</div>
</div>
<div id="main">
<div id="map-canvas"></div>
<div id="bottom">
<div id="chart"></div>
<a id="chart-close" data-bind="onChartToggle">close</a>
</div>
</div>
</div>

80
js/test/fixtures/main.html vendored Normal file
View File

@ -0,0 +1,80 @@
<div id="container">
<div id="menu">
<div id="menu-content">
<a href="login.php"><img class="icon" alt="Log in" src="images/key.svg"> Log in</a>
<div class="section">
<label for="user">User</label>
<select id="user" data-bind="currentUserId" name="user"></select>
</div>
<div class="section">
<label for="track">Track</label>
<select id="track" data-bind="currentTrackId" name="track"></select>
<input id="latest" type="checkbox" data-bind="showLatest"> <label for="latest">latest position</label><br>
<input id="auto-reload" type="checkbox" data-bind="autoReload"> <label for="auto-reload">autoreload</label> (<a id="set-interval" data-bind="onSetInterval"><span id="interval" data-bind="interval">10</span></a> s)<br>
<a id="force-reload" data-bind="onReload"> Reload now</a><br>
</div>
<div id="summary" class="section" data-bind="summary"></div>
<div id="other" class="section">
<a id="altitudes" data-bind="onChartToggle">Altitudes chart</a>
</div>
<div>
<label for="api">Map API</label>
<select id="api" name="api" data-bind="mapApi">
<option value="gmaps">Google Maps</option>
<option value="openlayers" selected>OpenLayers</option>
</select>
</div>
<div>
<label for="lang">Language</label>
<select id="lang" name="lang" data-bind="lang">
<option value="cs">Čeština</option>
<option value="de">Deutsch</option>
<option value="en" selected>English</option>
<option value="es">Español</option>
<option value="fr">Français</option>
<option value="it">Italiano</option>
<option value="hu">Magyar</option>
<option value="nl">Nederlands</option>
<option value="pl">Polski</option>
<option value="ru">Русский</option>
<option value="zh">中文</option>
</select>
</div>
<div class="section">
<label for="units">Units</label>
<select id="units" name="units" data-bind="units">
<option value="metric" selected>Metric</option>
<option value="imperial">Imperial/US</option>
<option value="nautical">Nautical</option>
</select>
</div>
<div class="section">
<div class="menu-title">Export track</div>
<a id="export-kml" class="menu-link" data-bind="onExportKml">kml</a>
<a id="export-gpx" class="menu-link" data-bind="onExportGpx">gpx</a>
</div>
</div>
<div id="menu-button"><a data-bind="onMenuToggle"></a></div>
<div id="footer"><a target="_blank" href="https://github.com/bfabiszewski/ulogger-server"><span class="mi">μ</span>logger</a> 1.0-beta</div>
</div>
<div id="main">
<div id="map-canvas"></div>
<div id="bottom">
<div id="chart"></div>
<a id="chart-close" data-bind="onChartToggle">close</a>
</div>
</div>
</div>

View File

@ -0,0 +1,55 @@
/*
* μ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 <http://www.gnu.org/licenses/>.
*/
const baseUrl = '/base/test/fixtures/';
export default class Fixture {
static load(url) {
return this.get(url).then((fixture) => {
document.body.insertAdjacentHTML('afterbegin', fixture);
});
}
static clear() {
document.body.innerHTML = '';
}
/**
* @param {string} url
* @return {Promise<string, Error>}
*/
static get(url) {
url = baseUrl + url;
const xhr = new XMLHttpRequest();
return new Promise((resolve, reject) => {
xhr.onreadystatechange = () => {
if (xhr.readyState === XMLHttpRequest.DONE) {
if (xhr.status === 200) {
resolve(xhr.responseText);
} else {
reject(new Error(`HTTP error ${xhr.status}`));
}
}
};
xhr.open('GET', url, true);
xhr.send();
});
}
}

View File

@ -17,6 +17,7 @@
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
import Fixture from './helpers/fixture.js';
import MainViewModel from '../src/mainviewmodel.js';
import ViewModel from '../src/viewmodel.js';
import uState from '../src/state.js';
@ -28,20 +29,20 @@ describe('MainViewModel tests', () => {
let state;
let menuEl;
let userMenuEl;
let userButtonEl;
let menuButtonEl;
beforeEach((done) => {
Fixture.load('main-authorized.html')
.then(() => done())
.catch((e) => done.fail(e));
});
beforeEach(() => {
const fixture = `<div id="fixture">
<div>
<a id="user-menu-button" data-bind="onShowUserMenu">user</a>
<div id="user-menu" class="menu-hidden"></div>
</div>
<div id="menu">
<div id="menu-button"><a data-bind="onMenuToggle"></a></div>
</div>
</div>`;
document.body.insertAdjacentHTML('afterbegin', fixture);
menuEl = document.querySelector('#menu');
userMenuEl = document.querySelector('#user-menu');
userButtonEl = document.querySelector('a[data-bind="onShowUserMenu"]');
menuButtonEl = document.querySelector('#menu-button a');
spyOn(window, 'addEventListener');
spyOn(window, 'removeEventListener').and.callThrough();
state = new uState();
@ -49,7 +50,7 @@ describe('MainViewModel tests', () => {
});
afterEach(() => {
document.body.removeChild(document.querySelector('#fixture'));
Fixture.clear();
});
it('should create instance', () => {
@ -61,10 +62,9 @@ describe('MainViewModel tests', () => {
it('should hide side menu', (done) => {
// given
const buttonEl = document.querySelector('#menu-button a');
vm.init();
// when
buttonEl.click();
menuButtonEl.click();
// then
setTimeout(() => {
expect(menuEl.classList.contains(hiddenClass)).toBe(true);
@ -74,11 +74,10 @@ describe('MainViewModel tests', () => {
it('should show side menu', (done) => {
// given
const buttonEl = document.querySelector('#menu-button a');
menuEl.classList.add(hiddenClass);
vm.init();
// when
buttonEl.click();
menuButtonEl.click();
// then
setTimeout(() => {
expect(menuEl.classList.contains(hiddenClass)).toBe(false);
@ -88,11 +87,10 @@ describe('MainViewModel tests', () => {
it('should hide user menu', (done) => {
// given
const buttonEl = document.querySelector('#user-menu-button');
userMenuEl.classList.remove(hiddenClass);
vm.init();
// when
buttonEl.click();
userButtonEl.click();
// then
setTimeout(() => {
expect(userMenuEl.classList.contains(hiddenClass)).toBe(true);
@ -102,10 +100,9 @@ describe('MainViewModel tests', () => {
it('should show user menu', (done) => {
// given
const buttonEl = document.querySelector('#user-menu-button');
vm.init();
// when
buttonEl.click();
userButtonEl.click();
// then
setTimeout(() => {
expect(userMenuEl.classList.contains(hiddenClass)).toBe(false);

View File

@ -18,6 +18,7 @@
*/
import { config, lang } from '../src/initializer.js';
import Fixture from './helpers/fixture.js';
import MapViewModel from '../src/mapviewmodel.js';
import TrackFactory from './helpers/trackfactory.js';
import ViewModel from '../src/viewmodel.js';
@ -36,12 +37,13 @@ describe('MapViewModel tests', () => {
let track;
const defaultApi = 'mockApi';
beforeEach((done) => {
Fixture.load('main.html')
.then(() => done())
.catch((e) => done.fail(e));
});
beforeEach(() => {
const fixture = `<div id="fixture">
<div id="map-canvas"></div>
<div id="menu-button"><a data-bind="onMenuToggle"></a></div>
</div>`;
document.body.insertAdjacentHTML('afterbegin', fixture);
mapEl = document.querySelector('#map-canvas');
menuButtonEl = document.querySelector('#menu-button a');
config.reinitialize();
@ -69,7 +71,7 @@ describe('MapViewModel tests', () => {
});
afterEach(() => {
document.body.removeChild(document.querySelector('#fixture'));
Fixture.clear();
uObserve.unobserveAll(lang);
});

View File

@ -18,6 +18,7 @@
*/
import { auth, config, lang } from '../src/initializer.js';
import Fixture from './helpers/fixture.js';
import TrackFactory from './helpers/trackfactory.js';
import TrackViewModel from '../src/trackviewmodel.js';
import ViewModel from '../src/viewmodel.js';
@ -57,29 +58,13 @@ describe('TrackViewModel tests', () => {
let user;
const MAX_FILE_SIZE = 10;
beforeEach(() => {
const fixture = `<div id="fixture">
<div class="section">
<select id="track" data-bind="currentTrackId" name="track"></select>
<input id="latest" type="checkbox" data-bind="showLatest">
<input id="auto-reload" type="checkbox" data-bind="autoReload">
<a id="force-reload" data-bind="onReload">reload</a>
</div>
<div id="summary" class="section" data-bind="summary"></div>
<div class="section">
<a id="export-kml" class="menu-link" data-bind="onExportKml">kml</a>
<a id="export-gpx" class="menu-link" data-bind="onExportGpx">gpx</a>
</div>
<div class="section">
<form id="import-form" enctype="multipart/form-data" method="post">
<input type="hidden" name="MAX_FILE_SIZE" value="${MAX_FILE_SIZE}" />
<input type="file" id="input-file" name="gpx" data-bind="inputFile"/>
</form>
<a id="import-gpx" class="menu-link" data-bind="onImportGpx">gpx</a>
</div>
</div>`;
beforeEach((done) => {
Fixture.load('main-authorized.html')
.then(() => done())
.catch((e) => done.fail(e));
});
document.body.insertAdjacentHTML('afterbegin', fixture);
beforeEach(() => {
config.reinitialize();
config.interval = 10;
lang.init(config);
@ -93,6 +78,8 @@ describe('TrackViewModel tests', () => {
forceReloadEl = document.querySelector('#force-reload');
inputFileEl = document.querySelector('#input-file');
autoReloadEl = document.querySelector('#auto-reload');
const maxEl = document.querySelector('input[name="MAX_FILE_SIZE"]');
maxEl.value = MAX_FILE_SIZE;
state = new uState();
vm = new TrackViewModel(state);
track1 = TrackFactory.getTrack(0, { id: 1, name: 'track1' });
@ -106,7 +93,7 @@ describe('TrackViewModel tests', () => {
});
afterEach(() => {
document.body.removeChild(document.querySelector('#fixture'));
Fixture.clear();
uObserve.unobserveAll(lang);
auth.user = null;
});

View File

@ -18,6 +18,7 @@
*/
import { auth, config, lang } from '../src/initializer.js';
import Fixture from './helpers/fixture.js';
import UserViewModel from '../src/userviewmodel.js';
import ViewModel from '../src/viewmodel.js';
import uSelect from '../src/select.js';
@ -34,14 +35,13 @@ describe('UserViewModel tests', () => {
let userEl;
let vm;
beforeEach((done) => {
Fixture.load('main.html')
.then(() => done())
.catch((e) => done.fail(e));
});
beforeEach(() => {
const fixture = `<div id="fixture">
<div class="section">
<label for="user">user</label>
<select id="user" data-bind="currentUserId" name="user"></select>
</div>
</div>`;
document.body.insertAdjacentHTML('afterbegin', fixture);
userEl = document.querySelector('#user');
config.reinitialize();
lang.init(config);
@ -55,7 +55,7 @@ describe('UserViewModel tests', () => {
});
afterEach(() => {
document.body.removeChild(document.querySelector('#fixture'));
Fixture.clear();
auth.user = null;
});

View File

@ -18,6 +18,7 @@ module.exports = function(config) {
{ pattern: 'test/*.test.js', type: 'module' },
{ pattern: 'test/*.stub.js', type: 'module', included: false },
{ pattern: 'test/helpers/*.js', type: 'module', included: false },
{ pattern: 'test/fixtures/*.html', included: false },
{ pattern: 'src/**/*.js', type: 'module', included: false }
],
exclude: [],