Skip to content
This repository has been archived by the owner on Oct 1, 2019. It is now read-only.

Commit

Permalink
Add documentation and tests for menu utils
Browse files Browse the repository at this point in the history
  • Loading branch information
voidxnull committed Oct 7, 2016
1 parent 68d1d20 commit 59773c1
Show file tree
Hide file tree
Showing 2 changed files with 101 additions and 29 deletions.
78 changes: 49 additions & 29 deletions src/utils/menu.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,57 +15,77 @@
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* Recursive depth-first search.
*/
function find(currentPath, items) {
for (const item of items) {
const same = (item.regexp && currentPath.match(item.regexp) !== null) ||
item.path === currentPath;
if (same) {
return item;
}

if (item.children) {
const result = find(currentPath, item.children);

if (result) {
return result;
}
}
}

return null;
}

export class MenuTree {
/**
* Menu item format:
* {
* name: 'Name',
* path: '/path', // string or regexp
* children: [] // optional array of menu items
* }
* `
* {
* name: 'Name', // Any property
* path: '/path', // String representing a path.
* regexp: /\/path/, // Optional regexp
* children: [] // Optional array of menu items
* }
* `
* When `regexp` is specified it will be used for matching instead of `path`.
* @param {Object[]} items An array of menu items.
*/
constructor(items) {
this.items = Object.freeze(items);
}

/**
* Finds the root menu item for the current path.
* @param {String} currentPath A path without a query.
*/
getCurrentRoot(currentPath) {
for (const item of this.items) {
const current = this.find(currentPath, item.children);
if (item.children) {
const current = find(currentPath, item.children);

if (current) {
return item;
if (current) {
return item;
}
}
}

return null;
}

/**
* Finds the matching menu item for the current path.
* @param {String} currentPath A path without a query.
*/
getCurrent(currentPath) {
return this.find(currentPath, this.items);
}

find(currentPath, items) {
for (const item of items) {
const same = (item.regexp && currentPath.match(item.regexp) !== null) ||
item.path === currentPath;
if (same) {
return item;
}

if (item.children) {
const result = this.find(currentPath, item.children);

if (result) {
return result;
}
}
}

return null;
return find(currentPath, this.items);
}
}

/**
* Menu for tool pages (/tools/*).
*/
export const toolsMenu = new MenuTree([
{
name: 'Account',
Expand Down
52 changes: 52 additions & 0 deletions test/unit/utils/menu.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/* eslint-env node, mocha */
import { expect } from '../../../test-helpers/expect-unit';
import { MenuTree } from '../../../src/utils/menu';

const menu = new MenuTree([
{
name: 'Root1',
path: '/root1',
children: [
{
name: 'Root1 > 1',
path: '/root1/1'
},
{
name: 'Root1 > n',
regexp: /\/root1\/\d+/
}
]
},
{
name: 'Root2',
path: '/root2'
}
]);

describe('MenuItem', () => {
describe('getCurrentRoot', () => {
it('returns root item when path is found', () => {
expect(menu.getCurrentRoot('/root1/1'), 'to satisfy', { name: 'Root1' });
expect(menu.getCurrentRoot('/root1/234'), 'to satisfy', { name: 'Root1' });
});

it('returns null when path is not found', () => {
expect(menu.getCurrentRoot('/root1/out-of-regexp'), 'to be null');
});
});

describe('getCurrent', () => {
it('returns menu item when path is found', () => {
expect(menu.getCurrent('/root1/1'), 'to satisfy', { name: 'Root1 > 1' });
expect(menu.getCurrent('/root1/234'), 'to satisfy', { name: 'Root1 > n' });
});

it('returns null when path is not found', () => {
expect(menu.getCurrent('/root1/out-of-regexp'), 'to be null');
});

it('works on root items', () => {
expect(menu.getCurrent('/root2'), 'to satisfy', { name: 'Root2' });
});
});
});

0 comments on commit 59773c1

Please sign in to comment.