Skip to content

Commit

Permalink
refactor: Make createMenuItems be based on a signal
Browse files Browse the repository at this point in the history
Fixes #2752
  • Loading branch information
Artur- committed Sep 23, 2024
1 parent ec4ba7f commit d611caa
Show file tree
Hide file tree
Showing 5 changed files with 55 additions and 44 deletions.
1 change: 1 addition & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions packages/ts/file-router/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@
"dependencies": {
"@vaadin/hilla-generator-utils": "24.5.0-beta1",
"@vaadin/hilla-react-auth": "24.5.0-beta1",
"@vaadin/hilla-react-signals": "24.5.0-beta1",
"react": "^18.2.0",
"rollup": "^4.12.0",
"typescript": "5.6.2"
Expand Down
46 changes: 26 additions & 20 deletions packages/ts/file-router/src/runtime/createMenuItems.ts
Original file line number Diff line number Diff line change
@@ -1,35 +1,41 @@
import { signal } from '@vaadin/hilla-react-signals';
import type { VaadinWindow } from '../shared/internal.js';
import type { MenuItem, ViewConfig } from '../types.js';

export const viewsSignal = signal((window as VaadinWindow).Vaadin?.views);

/**
* Creates menu items from the views provided by the server. The views are sorted according to the
* {@link ViewConfig.menu.order}, filtered out if they are explicitly excluded via {@link ViewConfig.menu.exclude}.
* Note that views with no order are put below views with an order. Ties are resolved based on the path string
* comparison.
*
* @param vaadinObject - The Vaadin object containing the server views.
* @returns A list of menu items.
*/
export function createMenuItems(vaadinObject = (window as VaadinWindow).Vaadin): readonly MenuItem[] {
export function createMenuItems(): readonly MenuItem[] {
// @ts-expect-error: esbuild injection
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
__REGISTER__('createMenuItems', vaadinObject);
__REGISTER__('createMenuItems', (window as VaadinWindow).Vaadin?.views);
const collator = new Intl.Collator('en-US');
return vaadinObject?.views
? Object.entries(vaadinObject.views)
// Filter out the views that are explicitly excluded from the menu.
.filter(([_, value]) => !value.menu?.exclude)
// Map the views to menu items.
.map(([path, config]) => ({
to: path,
icon: config.menu?.icon,
title: config.menu?.title ?? config.title,
order: config.menu?.order,
}))
// Sort views according to the order specified in the view configuration.
.sort((menuA, menuB) => {
const ordersDiff = (menuA.order ?? Number.MAX_VALUE) - (menuB.order ?? Number.MAX_VALUE);
return ordersDiff !== 0 ? ordersDiff : collator.compare(menuA.to, menuB.to);
})
: [];
if (!viewsSignal.value) {
return [];
}

return (
Object.entries(viewsSignal.value)
// Filter out the views that are explicitly excluded from the menu.
.filter(([_, value]) => !value.menu?.exclude)
// Map the views to menu items.
.map(([path, config]) => ({
to: path,
icon: config.menu?.icon,
title: config.menu?.title ?? config.title,
order: config.menu?.order,
}))
// Sort views according to the order specified in the view configuration.
.sort((menuA, menuB) => {
const ordersDiff = (menuA.order ?? Number.MAX_VALUE) - (menuB.order ?? Number.MAX_VALUE);
return ordersDiff !== 0 ? ordersDiff : collator.compare(menuA.to, menuB.to);
})
);
}
47 changes: 23 additions & 24 deletions packages/ts/file-router/test/runtime/createMenuItems.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import './vaadinGlobals.js'; // eslint-disable-line import/no-unassigned-import
import { expect, use } from '@esm-bundle/chai';
import chaiLike from 'chai-like';
import type { Writable } from 'type-fest';
import { createMenuItems } from '../../src/runtime/createMenuItems.js';
import { createMenuItems, viewsSignal } from '../../src/runtime/createMenuItems.js';
import type { MenuItem } from '../../src/types.js';

use(chaiLike);
Expand All @@ -25,17 +26,16 @@ function cleanup(items: Array<Writable<MenuItem>>) {
describe('@vaadin/hilla-file-router', () => {
describe('createMenuItems', () => {
it('should generate a set of menu items', () => {
const items = createMenuItems({
views: {
'/about': { route: 'about', title: 'About' },
'/profile/': { title: 'Profile' },
'/profile/account/security/password': { title: 'Password' },
'/profile/account/security/two-factor-auth': { title: 'Two Factor Auth' },
'/profile/friends/list': { title: 'List' },
'/test/empty': {},
'/test/no-default-export': { title: 'No Default Export' },
},
});
viewsSignal.value = {
'/about': { route: 'about', title: 'About' },
'/profile/': { title: 'Profile' },
'/profile/account/security/password': { title: 'Password' },
'/profile/account/security/two-factor-auth': { title: 'Two Factor Auth' },
'/profile/friends/list': { title: 'List' },
'/test/empty': {},
'/test/no-default-export': { title: 'No Default Export' },
};
const items = createMenuItems();
cleanup(items as Array<Writable<MenuItem>>);

const expected = [
Expand Down Expand Up @@ -73,18 +73,17 @@ describe('@vaadin/hilla-file-router', () => {
});

it('should sort menu items by order then by natural string comparison based on path', () => {
const items = createMenuItems({
views: {
'/a/b': { route: 'about', title: 'About' },
'': { title: 'Profile' },
'/profile/account/security/password': { title: 'Password', menu: { order: 20 } },
'/profile/account/security/two-factor-auth': { title: 'Two Factor Auth', menu: { order: 20 } },
'/b': { title: 'List' },
'/': { title: 'Root' },
'/test/empty': { title: 'empty', menu: { order: 5 } },
'/test/no-default-export': { title: 'No Default Export', menu: { order: 10 } },
},
});
viewsSignal.value = {
'/a/b': { route: 'about', title: 'About' },
'': { title: 'Profile' },
'/profile/account/security/password': { title: 'Password', menu: { order: 20 } },
'/profile/account/security/two-factor-auth': { title: 'Two Factor Auth', menu: { order: 20 } },
'/b': { title: 'List' },
'/': { title: 'Root' },
'/test/empty': { title: 'empty', menu: { order: 5 } },
'/test/no-default-export': { title: 'No Default Export', menu: { order: 10 } },
};
const items = createMenuItems();
const cleanedUp = (items as Array<Writable<MenuItem>>).map((item) => ({
to: item.to,
title: item.title,
Expand Down
4 changes: 4 additions & 0 deletions packages/ts/file-router/test/runtime/vaadinGlobals.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
(global as any).window = { Vaadin: {} };

export {};

0 comments on commit d611caa

Please sign in to comment.