diff --git a/Pilot.js b/Pilot.js index c18957d..5baee3c 100644 --- a/Pilot.js +++ b/Pilot.js @@ -17,7 +17,8 @@ }); })(typeof define === 'function' && define.amd ? define : function (deps, callback) { window.Pilot = callback(window.Emitter); - }, function (define) {define('src/querystring',[], function () { + }, function (define) { +define('src/querystring',[], function () { 'use strict'; var encodeURIComponent = window.encodeURIComponent; @@ -476,133 +477,7 @@ define('src/match',[], function () { }; }); -define('src/action-queue',['Emitter'], function(Emitter) { - - function ActionQueue() { - Emitter.apply(this); - - this._queue = []; - this._activeIds = {}; - this._id = 0; - this._lastQueueItem = void 0; - this._endedCount = -1; - } - - ActionQueue.PRIORITY_HIGH = 1; - ActionQueue.PRIORITY_LOW = 0; - - ActionQueue.prototype = { - constructor: ActionQueue, - - push: function(request, action) { - // TODO: arg types check - - // Проставляем по умолчанию наивысший приоритет - if (action.priority == null) { - action.priority = ActionQueue.PRIORITY_HIGH; - } - - var queueItem = { - request: request, - action: action, - timestamp: Date.now(), - id: this._id++ - }; - - // Добавляем в очередь - this._queue.push(queueItem); - // Возвращаем уникальный id - return queueItem.id; - }, - - remove: function(id) { - // Если query был в _activeIds - if (this._activeIds[id]) { - // Сбросим _lastQueueItem - if (this._lastQueueItem === this._activeIds[id]) { - this._lastQueueItem = void 0; - } - - // Сообщим, что прекратили выполнять этот экшн - this.notifyEnd(id, void 0); - return; - } - - var nextQueue = []; - - // Формируем новую очередь без экшна с указанным id - for (var i = 0; i < this._queue.length; i++) { - if (this._queue[i].id !== id) { - nextQueue.push(this._queue[i]); - } - } - - // Сохраним новую очередь - this._queue = nextQueue; - // Сообщим, что прекратили выполнять этот экшн - this.notifyEnd(id, void 0); - }, - - canPoll: function() { - var nextItem = this._queue[0]; - var lastActiveItem = this._lastQueueItem; - - // Не можем поллить, так как очередь пуста - if (!nextItem) { - return false; - } - - // Можем поллить, так как ничего не запущено - if (!lastActiveItem) { - return true; - } - - // Можем поллить, если приоритет последнего запущенного экшна равен приоритету следующего экшна в очереди - return lastActiveItem.action.priority === nextItem.action.priority; - }, - - poll: function() { - var queueItem = this._queue.shift(); - - this._activeIds[queueItem.id] = queueItem; - this._lastQueueItem = queueItem; - - return queueItem; - }, - - notifyEnd: function(id, result) { - // Сбрасываем lastQueueItem, если закончили именно его - if (this._lastQueueItem === this._activeIds[id]) { - this._lastQueueItem = void 0; - } - - // Удаляем из активных в любом случае - delete this._activeIds[id]; - // Сообщаем Loader - this.emit(id + ':end', result); - - // Увеличиваем счётчик завершённых экшнов - this._endedCount++; - }, - - awaitEnd: function(id) { - // Если экшн уже давно выполнился - if (id <= this._endedCount) { - return Promise.resolve(); - } - - // Ожидаем выполнения экшна - return new Promise(function(resolve) { - this.one(id + ':end', resolve); - }.bind(this)); - }, - }; - - return ActionQueue; - -}); - -define('src/loader',['./match', './action-queue'], function (match, ActionQueue) { +define('src/loader',['./match'], function (match, Emitter) { 'use strict'; var _cast = function (name, model) { @@ -652,10 +527,16 @@ define('src/loader',['./match', './action-queue'], function (match, ActionQueue) // Инкрементивный ID запросов нужен для performance this._lastReqId = 0; + // Счётчик выполняемых запросов с высоким приоритетом + // Запросы с низким приоритетом будут выполняться только после того, как этот счётчик станет 0 + this._highPriorityQueries = 0; + // Если есть запросы с высоким приоритетом, этот промис разрезолвится после завершения последнего запроса + this._highPriorityPromise = null; + this._highPriorityPromiseResolve = null; + // Приоритет последнего экшна + this._lastPriority = Loader.PRIORITY_LOW; // Дебаг-режим, выводит в performance все экшны this._debug = false; - // Очередь экшнов - this._actionQueue = new ActionQueue(); this.names.forEach(function (name) { this._index[name] = _cast(name, models[name]); @@ -840,34 +721,68 @@ define('src/loader',['./match', './action-queue'], function (match, ActionQueue) return _fetchPromises[_persistKey]; } - // Добавляем экшн в очередь - var actionId = _this._actionQueue.push(_req, action); - // Пробуем выполнить следующий экшн из очереди - this._tryProcessQueue(); - // Возвращаем промис, который выполнится, когда выполнится этот экшн - return _this._actionQueue.awaitEnd(actionId); - }, + // Приоритет действия + var priority = action.priority == null ? Loader.PRIORITY_HIGH : action.priority; + + if ( + _this._highPriorityQueries && + (priority !== _this._lastPriority || priority === Loader.PRIORITY_LOW) + ) { + return _this._highPriorityPromise + .then(function() { + // Попробуем сделать действие ещё раз после выполнения всех действий с более высоким приоритетом + return _this._executeActionAsync(req, action); + }); + } - _tryProcessQueue: function() { - while (this._actionQueue.canPoll()) { - var queueItem = this._actionQueue.poll(); + // Выставляем активный приоритет + _this._highPriorityQueries++; + _this._lastPriority = priority; - // Отправляем экшн выполняться - var actionPromise = this._loadSources(queueItem.request, queueItem.action); + if (!_this._highPriorityPromise) { + _this._highPriorityPromise = Promise.resolve(); + } - actionPromise - // Ошибку на этом этапе уже обработали - .catch(function () { - }) - .then(function (queueItem, result) { - // Сообщаем, что экшн прекратили выполнять - this._actionQueue.notifyEnd(queueItem.id, result); - // Пробуем выполнить следующий экшн - this._tryProcessQueue(); - }.bind(this, queueItem)); + _this._highPriorityPromise = _this._highPriorityPromise.then( + new Promise(function (resolve) { + _this._highPriorityPromiseResolve = resolve; + }) + ); + + // Отправляем экшн выполняться + var actionPromise = this._loadSources(_req, action); + + actionPromise + // Ошибку на этом этапе уже обработали + .catch(function () {}) + .then(function () { + _this._handleActionEnd(); + }); + + return actionPromise; + }, + + _handleActionEnd: function() { + var _this = this; + _this._highPriorityQueries--; + + // Резолвим high priority promise, если закончили выполнять экшн с высоким приоритетом + if (!_this._highPriorityQueries) { + _this._highPriorityPromiseResolve(); + _this._highPriorityPromise = null; } }, + _executeActionAsync: function(req, action) { + var _this = this; + + return new Promise(function (resolve) { + setImmediate(function () { + resolve(_this._executeAction(req, action)); + }); + }); + }, + _measurePerformance: function (measureName) { if (this._debug && window.performance) { @@ -925,8 +840,8 @@ define('src/loader',['./match', './action-queue'], function (match, ActionQueue) Loader.ACTION_NAVIGATE = 'NAVIGATE'; Loader.ACTION_NONE = 'NONE'; - Loader.PRIORITY_LOW = ActionQueue.PRIORITY_LOW; - Loader.PRIORITY_HIGH = ActionQueue.PRIORITY_HIGH; + Loader.PRIORITY_LOW = 0; + Loader.PRIORITY_HIGH = 1; // Export return Loader; @@ -964,6 +879,9 @@ define('src/request',['./url', './querystring'], function (/** URL */URL, /** qu this.router = router; this.referrer = referrer; this.redirectHref = null; + + // Алиас, который сматчился + this.alias = void 0; }; @@ -1047,6 +965,27 @@ define('src/route',[ return url.replace(/\/+$/, '/'); }; + /** + * Кладёт в target правила из source, если их там не было + */ + var _mergeRules = function (source, target) { + if (!source) { + source = {}; + } + + if (!target) { + target = {}; + } + + Object.keys(source).forEach(function (sourceRule) { + if (!target[sourceRule]) { + target[sourceRule] = source[sourceRule]; + } + }); + + return target; + }; + /** * Преобразование образца маршрута в функцию генерации URL @@ -1165,9 +1104,40 @@ define('src/route',[ */ this.model = options.model.defaults(); + // Основной URL маршрута this.url.regexp = Url.toMatcher(this.url.pattern + (options.__group__ ? '/:any([a-z0-9\\/-]*)' : '')); this._urlBuilder = _toUrlBuilder(this.url.pattern); + // Алиасы + options.aliases = options.aliases || {}; + + this.aliases = Object.keys(options.aliases).map(function (name) { + var url = options.aliases[name]; + + if (typeof url === 'string') { + url = {pattern: url}; + } + + // Какой-то неправильный URL передали + if (!url || typeof url !== 'object') { + return function () { + return false + }; + } + + // Собираем всё, что нужно для построения URL по алиасу и наоборот + url.regexp = Url.toMatcher(url.pattern); + + url.params = _mergeRules(this.url.params, url.params); + url.query = _mergeRules(this.url.query, url.query); + + return { + // Делаем функцию, которая будет смотреть совпадения этого урла с любым другим + matcher: this._matchWithAnyUrl.bind(this, url), + name: name + }; + }.bind(this)); + // Родительский маршрут (группа) this.parentRoute = this.router[this.parentId]; @@ -1326,10 +1296,22 @@ define('src/route',[ * @returns {boolean} */ match: function (url, req) { - var params = Url.match(this.url.regexp, url.pathname), - query = url.query, - _paramsRules = this.url.params, - _queryRules = this.url.query; + return this._matchWithAnyUrl(this.url, url, req); + }, + + /** + * Проверка маршрута с любым URL + * @param {URL} matcherUrl - url, который проверяем + * @param {URL} matchingUrl - url, с которым проверяем + * @param {Pilot.Request} req + * @returns {boolean} + * @private + */ + _matchWithAnyUrl: function (matcherUrl, matchingUrl, req) { + var params = Url.match(matcherUrl.regexp, matchingUrl.pathname), + query = matchingUrl.query, + _paramsRules = matcherUrl.params, + _queryRules = matcherUrl.query; return ( params && @@ -1745,6 +1727,19 @@ define('src/pilot.js',[ // Находим нужный нам маршрут currentRoute = routes.find(function (/** Pilot.Route */item) { + // Пытаемся сматчить этот маршрут по алиасу + var matchedAlias = item.aliases.find(function (alias) { + var matcher = alias.matcher; + return matcher && matcher(url, req); + }); + + // Получилось? + if (matchedAlias) { + req.alias = matchedAlias.name; + return true; + } + + // Матчим по основной регулярке return !item.__group__ && item.match(url, req); }); @@ -1963,4 +1958,4 @@ define('src/pilot.js',[ return Pilot; }); -}); \ No newline at end of file +}); diff --git a/src/action-queue.js b/src/action-queue.js deleted file mode 100644 index 2079854..0000000 --- a/src/action-queue.js +++ /dev/null @@ -1,125 +0,0 @@ -define(['Emitter'], function(Emitter) { - - function ActionQueue() { - Emitter.apply(this); - - this._queue = []; - this._activeIds = {}; - this._id = 0; - this._lastQueueItem = void 0; - this._endedCount = -1; - } - - ActionQueue.PRIORITY_HIGH = 1; - ActionQueue.PRIORITY_LOW = 0; - - ActionQueue.prototype = { - constructor: ActionQueue, - - push: function(request, action) { - // TODO: arg types check - - // Проставляем по умолчанию наивысший приоритет - if (action.priority == null) { - action.priority = ActionQueue.PRIORITY_HIGH; - } - - var queueItem = { - request: request, - action: action, - timestamp: Date.now(), - id: this._id++ - }; - - // Добавляем в очередь - this._queue.push(queueItem); - // Возвращаем уникальный id - return queueItem.id; - }, - - remove: function(id) { - // Если query был в _activeIds - if (this._activeIds[id]) { - // Сбросим _lastQueueItem - if (this._lastQueueItem === this._activeIds[id]) { - this._lastQueueItem = void 0; - } - - // Сообщим, что прекратили выполнять этот экшн - this.notifyEnd(id, void 0); - return; - } - - var nextQueue = []; - - // Формируем новую очередь без экшна с указанным id - for (var i = 0; i < this._queue.length; i++) { - if (this._queue[i].id !== id) { - nextQueue.push(this._queue[i]); - } - } - - // Сохраним новую очередь - this._queue = nextQueue; - // Сообщим, что прекратили выполнять этот экшн - this.notifyEnd(id, void 0); - }, - - canPoll: function() { - var nextItem = this._queue[0]; - var lastActiveItem = this._lastQueueItem; - - // Не можем поллить, так как очередь пуста - if (!nextItem) { - return false; - } - - // Можем поллить, так как ничего не запущено - if (!lastActiveItem) { - return true; - } - - // Можем поллить, если приоритет последнего запущенного экшна равен приоритету следующего экшна в очереди - return lastActiveItem.action.priority === nextItem.action.priority; - }, - - poll: function() { - var queueItem = this._queue.shift(); - - this._activeIds[queueItem.id] = queueItem; - this._lastQueueItem = queueItem; - - return queueItem; - }, - - notifyEnd: function(id, result) { - // Сбрасываем lastQueueItem, если закончили именно его - if (this._lastQueueItem === this._activeIds[id]) { - this._lastQueueItem = void 0; - } - - // Удаляем из активных в любом случае - delete this._activeIds[id]; - // Сообщаем Loader - this.emit(id + ':end', result); - - // Увеличиваем счётчик завершённых экшнов - this._endedCount++; - }, - - awaitEnd: function(id) { - // Если экшн уже давно выполнился - if (id <= this._endedCount) { - return Promise.resolve(); - } - - // Ожидаем выполнения экшна - return new Promise(function(resolve) { - this.one(id + ':end', resolve); - }.bind(this)); - }, - }; - - return ActionQueue; - -}); diff --git a/src/loader.js b/src/loader.js index f3bd58f..3f020f0 100644 --- a/src/loader.js +++ b/src/loader.js @@ -1,4 +1,4 @@ -define(['./match', './action-queue'], function (match, ActionQueue) { +define(['./match'], function (match, Emitter) { 'use strict'; var _cast = function (name, model) { @@ -48,10 +48,16 @@ define(['./match', './action-queue'], function (match, ActionQueue) { // Инкрементивный ID запросов нужен для performance this._lastReqId = 0; + // Счётчик выполняемых запросов с высоким приоритетом + // Запросы с низким приоритетом будут выполняться только после того, как этот счётчик станет 0 + this._highPriorityQueries = 0; + // Если есть запросы с высоким приоритетом, этот промис разрезолвится после завершения последнего запроса + this._highPriorityPromise = null; + this._highPriorityPromiseResolve = null; + // Приоритет последнего экшна + this._lastPriority = Loader.PRIORITY_LOW; // Дебаг-режим, выводит в performance все экшны this._debug = false; - // Очередь экшнов - this._actionQueue = new ActionQueue(); this.names.forEach(function (name) { this._index[name] = _cast(name, models[name]); @@ -236,34 +242,68 @@ define(['./match', './action-queue'], function (match, ActionQueue) { return _fetchPromises[_persistKey]; } - // Добавляем экшн в очередь - var actionId = _this._actionQueue.push(_req, action); - // Пробуем выполнить следующий экшн из очереди - this._tryProcessQueue(); - // Возвращаем промис, который выполнится, когда выполнится этот экшн - return _this._actionQueue.awaitEnd(actionId); - }, + // Приоритет действия + var priority = action.priority == null ? Loader.PRIORITY_HIGH : action.priority; + + if ( + _this._highPriorityQueries && + (priority !== _this._lastPriority || priority === Loader.PRIORITY_LOW) + ) { + return _this._highPriorityPromise + .then(function() { + // Попробуем сделать действие ещё раз после выполнения всех действий с более высоким приоритетом + return _this._executeActionAsync(req, action); + }); + } - _tryProcessQueue: function() { - while (this._actionQueue.canPoll()) { - var queueItem = this._actionQueue.poll(); + // Выставляем активный приоритет + _this._highPriorityQueries++; + _this._lastPriority = priority; - // Отправляем экшн выполняться - var actionPromise = this._loadSources(queueItem.request, queueItem.action); + if (!_this._highPriorityPromise) { + _this._highPriorityPromise = Promise.resolve(); + } - actionPromise - // Ошибку на этом этапе уже обработали - .catch(function () { - }) - .then(function (queueItem, result) { - // Сообщаем, что экшн прекратили выполнять - this._actionQueue.notifyEnd(queueItem.id, result); - // Пробуем выполнить следующий экшн - this._tryProcessQueue(); - }.bind(this, queueItem)); + _this._highPriorityPromise = _this._highPriorityPromise.then( + new Promise(function (resolve) { + _this._highPriorityPromiseResolve = resolve; + }) + ); + + // Отправляем экшн выполняться + var actionPromise = this._loadSources(_req, action); + + actionPromise + // Ошибку на этом этапе уже обработали + .catch(function () {}) + .then(function () { + _this._handleActionEnd(); + }); + + return actionPromise; + }, + + _handleActionEnd: function() { + var _this = this; + _this._highPriorityQueries--; + + // Резолвим high priority promise, если закончили выполнять экшн с высоким приоритетом + if (!_this._highPriorityQueries) { + _this._highPriorityPromiseResolve(); + _this._highPriorityPromise = null; } }, + _executeActionAsync: function(req, action) { + var _this = this; + + return new Promise(function (resolve) { + setImmediate(function () { + resolve(_this._executeAction(req, action)); + }); + }); + }, + _measurePerformance: function (measureName) { if (this._debug && window.performance) { @@ -321,8 +361,8 @@ define(['./match', './action-queue'], function (match, ActionQueue) { Loader.ACTION_NAVIGATE = 'NAVIGATE'; Loader.ACTION_NONE = 'NONE'; - Loader.PRIORITY_LOW = ActionQueue.PRIORITY_LOW; - Loader.PRIORITY_HIGH = ActionQueue.PRIORITY_HIGH; + Loader.PRIORITY_LOW = 0; + Loader.PRIORITY_HIGH = 1; // Export return Loader; diff --git a/src/pilot.js b/src/pilot.js index 65dbd97..a87e304 100644 --- a/src/pilot.js +++ b/src/pilot.js @@ -171,6 +171,19 @@ define([ // Находим нужный нам маршрут currentRoute = routes.find(function (/** Pilot.Route */item) { + // Пытаемся сматчить этот маршрут по алиасу + var matchedAlias = item.aliases.find(function (alias) { + var matcher = alias.matcher; + return matcher && matcher(url, req); + }); + + // Получилось? + if (matchedAlias) { + req.alias = matchedAlias.name; + return true; + } + + // Матчим по основной регулярке return !item.__group__ && item.match(url, req); }); diff --git a/src/request.js b/src/request.js index 0fc5213..0a85aab 100644 --- a/src/request.js +++ b/src/request.js @@ -30,6 +30,9 @@ define(['./url', './querystring'], function (/** URL */URL, /** queryString */qu this.router = router; this.referrer = referrer; this.redirectHref = null; + + // Алиас, который сматчился + this.alias = void 0; }; diff --git a/src/route.js b/src/route.js index 511ba5a..fc0bc02 100644 --- a/src/route.js +++ b/src/route.js @@ -44,6 +44,27 @@ define([ return url.replace(/\/+$/, '/'); }; + /** + * Кладёт в target правила из source, если их там не было + */ + var _mergeRules = function (source, target) { + if (!source) { + source = {}; + } + + if (!target) { + target = {}; + } + + Object.keys(source).forEach(function (sourceRule) { + if (!target[sourceRule]) { + target[sourceRule] = source[sourceRule]; + } + }); + + return target; + }; + /** * Преобразование образца маршрута в функцию генерации URL @@ -162,9 +183,40 @@ define([ */ this.model = options.model.defaults(); + // Основной URL маршрута this.url.regexp = Url.toMatcher(this.url.pattern + (options.__group__ ? '/:any([a-z0-9\\/-]*)' : '')); this._urlBuilder = _toUrlBuilder(this.url.pattern); + // Алиасы + options.aliases = options.aliases || {}; + + this.aliases = Object.keys(options.aliases).map(function (name) { + var url = options.aliases[name]; + + if (typeof url === 'string') { + url = {pattern: url}; + } + + // Какой-то неправильный URL передали + if (!url || typeof url !== 'object') { + return function () { + return false + }; + } + + // Собираем всё, что нужно для построения URL по алиасу и наоборот + url.regexp = Url.toMatcher(url.pattern); + + url.params = _mergeRules(this.url.params, url.params); + url.query = _mergeRules(this.url.query, url.query); + + return { + // Делаем функцию, которая будет смотреть совпадения этого урла с любым другим + matcher: this._matchWithAnyUrl.bind(this, url), + name: name + }; + }.bind(this)); + // Родительский маршрут (группа) this.parentRoute = this.router[this.parentId]; @@ -323,10 +375,22 @@ define([ * @returns {boolean} */ match: function (url, req) { - var params = Url.match(this.url.regexp, url.pathname), - query = url.query, - _paramsRules = this.url.params, - _queryRules = this.url.query; + return this._matchWithAnyUrl(this.url, url, req); + }, + + /** + * Проверка маршрута с любым URL + * @param {URL} matcherUrl - url, который проверяем + * @param {URL} matchingUrl - url, с которым проверяем + * @param {Pilot.Request} req + * @returns {boolean} + * @private + */ + _matchWithAnyUrl: function (matcherUrl, matchingUrl, req) { + var params = Url.match(matcherUrl.regexp, matchingUrl.pathname), + query = matchingUrl.query, + _paramsRules = matcherUrl.params, + _queryRules = matcherUrl.query; return ( params && diff --git a/tests/alias.tests.js b/tests/alias.tests.js new file mode 100644 index 0000000..a477221 --- /dev/null +++ b/tests/alias.tests.js @@ -0,0 +1,164 @@ +/* global describe, beforeEach, test, expect */ + +const Pilot = require('../src/pilot'); + +describe('Pilot:alias', () => { + + test('alias without parameter decoders', async () => { + const app = Pilot.create({ + '#index': { + url: '/:folder?', + aliases: { + 'compose': '/compose/:folder?/:id?' + } + }, + }); + + await app.nav('/'); + + // Обычный переход, алиасов нет + expect(app.route.is('#index')).toBeTruthy(); + expect(app.route.params).toEqual({}); + expect(app.request.alias).toEqual(void 0); + + await app.nav('/inbox'); + + // Обычный переход, алиасов нет + expect(app.route.is('#index')).toBeTruthy(); + expect(app.route.params).toEqual({folder: 'inbox'}); + expect(app.request.alias).toEqual(void 0); + + await app.nav('/compose'); + + // Переход с алиасом без параметров + expect(app.route.is('#index')).toBeTruthy(); + expect(app.route.params).toEqual({}); + expect(app.request.alias).toEqual('compose'); + + await app.nav('/compose/inbox'); + + // Переход с алиасом и параметром из основного роута + expect(app.route.is('#index')).toBeTruthy(); + expect(app.route.params).toEqual({folder: 'inbox'}); + expect(app.request.alias).toEqual('compose'); + + await app.nav('/compose/inbox/id'); + + // Переход с алиасом и всеми параметрами + expect(app.route.is('#index')).toBeTruthy(); + expect(app.route.params).toEqual({folder: 'inbox', id: 'id'}); + expect(app.request.alias).toEqual('compose'); + }); + + test('alias with parameter decoders', async () => { + const app = Pilot.create({ + '#index': { + url: { + pattern: '/:type?/:id?', + params: { + type: { + decode: function (value, req) { + return value; + } + }, + id: { + validate: function (value) { + return value >= 0; + }, + decode: function (value, req) { + return parseInt(value, 10); + } + } + } + }, + aliases: { + 'compose': { + pattern: '/compose/:message?/:id?', + params: { + message: { + decode: function (value, req) { + return 'msg_' + parseInt(value, 10); + } + } + } + } + } + }, + }); + + await app.nav('/'); + + // Обычный переход, алиасов нет + expect(app.route.is('#index')).toBeTruthy(); + expect(app.route.params).toEqual({}); + expect(app.request.alias).toEqual(void 0); + + await app.nav('/inbox'); + + // Обычный переход, алиасов нет + expect(app.route.is('#index')).toBeTruthy(); + expect(app.route.params).toEqual({type: 'inbox'}); + expect(app.request.alias).toEqual(void 0); + + await app.nav('/inbox/3'); + + // Обычный переход, алиасов нет + expect(app.route.is('#index')).toBeTruthy(); + expect(app.route.params).toEqual({type: 'inbox', id: 3}); + expect(app.request.alias).toEqual(void 0); + + await app.nav('/compose'); + + // Переход с алиасом без параметров + expect(app.route.is('#index')).toBeTruthy(); + expect(app.route.params).toEqual({}); + expect(app.request.alias).toEqual('compose'); + + await app.nav('/compose/3002'); + + // Переход с алиасом и параметром + expect(app.route.is('#index')).toBeTruthy(); + expect(app.route.params).toEqual({message: 'msg_3002'}); + expect(app.request.alias).toEqual('compose'); + + await app.nav('/compose/3002/3'); + + // Переход с алиасом и всеми параметрами + expect(app.route.is('#index')).toBeTruthy(); + expect(app.route.params).toEqual({message: 'msg_3002', id: 3}); + expect(app.request.alias).toEqual('compose'); + }); + + test('alias cleanup', async () => { + const app = Pilot.create({ + '#index': { + url: '/', + aliases: { + 'compose': '/compose/' + } + }, + }); + + await app.nav('/'); + + // Обычный переход, алиасов нет + expect(app.route.is('#index')).toBeTruthy(); + expect(app.route.params).toEqual({}); + expect(app.request.alias).toEqual(void 0); + + await app.nav('/compose'); + + // Переход с алиасом без параметров + expect(app.route.is('#index')).toBeTruthy(); + expect(app.route.params).toEqual({}); + expect(app.request.alias).toEqual('compose'); + + await app.nav('/'); + + // Обычный переход, алиасов нет + expect(app.route.is('#index')).toBeTruthy(); + expect(app.route.params).toEqual({}); + expect(app.request.alias).toEqual(void 0); + }); + +}); diff --git a/tests/loader.tests.js b/tests/loader.tests.js index 76696c3..9e08e9e 100644 --- a/tests/loader.tests.js +++ b/tests/loader.tests.js @@ -13,12 +13,7 @@ function sleep(milliseconds) { async function createSleepyLoggingLoader(log, {persist} = {persist: false}) { const loader = new Loader({ // Этот "переход по маршруту" будет просто ждать нужное кол-во милилсекунд - // Ещё он может зависнуть, т.е. вернуть промис, который не разрезолвится никогда data(request, waitFor, action) { - if (action.hang) { - return new Promise(); - } - return sleep(action.timeout) .then(() => `timeout ${action.timeout}`); } @@ -104,7 +99,7 @@ describe('Loader', () => { await sleep(100); - expect(log).toEqual(['timeout 10', 'timeout 20']); + expect(log).toEqual(['timeout 20', 'timeout 10']); }); @@ -233,16 +228,4 @@ describe('Loader', () => { expect(loader.fetch(reqX)).rejects; }); - - - test('infinite loop', async () => { - const log = []; - const loader = await createSleepyLoggingLoader(log); - - loader.dispatch({priority: Loader.PRIORITY_HIGH, hang: true}); - await sleep(50); - await loader.dispatch({timeout: 40, priority: Loader.PRIORITY_HIGH}); - - expect(log).toEqual(['timeout 40']); - }) });