From 3f4b486459574e38d9e33c0c9b8b1ac0b918a31f Mon Sep 17 00:00:00 2001 From: Sam Tolmay Date: Tue, 20 Oct 2020 14:32:09 +0200 Subject: [PATCH] feat(build): build connections and menus --- packages/build/src/build/buildConnections.js | 33 ++ .../build/src/build/buildConnections.test.js | 60 +++ packages/build/src/build/buildMenu.js | 87 ++++ packages/build/src/build/buildMenu.test.js | 408 ++++++++++++++++++ packages/build/src/test/testContext.js | 33 ++ 5 files changed, 621 insertions(+) create mode 100644 packages/build/src/build/buildConnections.js create mode 100644 packages/build/src/build/buildConnections.test.js create mode 100644 packages/build/src/build/buildMenu.js create mode 100644 packages/build/src/build/buildMenu.test.js create mode 100644 packages/build/src/test/testContext.js diff --git a/packages/build/src/build/buildConnections.js b/packages/build/src/build/buildConnections.js new file mode 100644 index 000000000..d70886806 --- /dev/null +++ b/packages/build/src/build/buildConnections.js @@ -0,0 +1,33 @@ +/* eslint-disable no-param-reassign */ + +/* + Copyright 2020 Lowdefy, Inc + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import { type } from '@lowdefy/helpers'; + +async function buildConnections({ components, context }) { + if (type.isArray(components.connections)) { + components.connections.forEach((connection) => { + connection.connectionId = connection.id; + connection.id = `connection:${connection.id}`; + }); + } + await context.logger.debug('Built connections'); + await context.logger.debug(JSON.stringify(components.connections)); + return components; +} + +export default buildConnections; diff --git a/packages/build/src/build/buildConnections.test.js b/packages/build/src/build/buildConnections.test.js new file mode 100644 index 000000000..2b4ab3517 --- /dev/null +++ b/packages/build/src/build/buildConnections.test.js @@ -0,0 +1,60 @@ +/* + Copyright 2020 Lowdefy, Inc + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import buildConnections from './buildConnections'; +import testContext from '../test/testContext'; + +const context = testContext(); + +test('buildConnections no connections', async () => { + const components = {}; + const res = await buildConnections({ components, context }); + expect(res.connections).toBe(undefined); +}); + +test('buildConnections connections not an array', async () => { + const components = { + connections: 'connections', + }; + const res = await buildConnections({ components, context }); + expect(res).toEqual({ + connections: 'connections', + }); +}); + +test('buildConnections', async () => { + const components = { + connections: [ + { + id: 'connection1', + }, + { + id: 'connection2', + }, + ], + }; + const res = await buildConnections({ components, context }); + expect(res.connections).toEqual([ + { + id: 'connection:connection1', + connectionId: 'connection1', + }, + { + id: 'connection:connection2', + connectionId: 'connection2', + }, + ]); +}); diff --git a/packages/build/src/build/buildMenu.js b/packages/build/src/build/buildMenu.js new file mode 100644 index 000000000..c64b5bed6 --- /dev/null +++ b/packages/build/src/build/buildMenu.js @@ -0,0 +1,87 @@ +/* eslint-disable no-param-reassign */ + +/* + Copyright 2020 Lowdefy, Inc + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import { type } from '@lowdefy/helpers'; + +async function buildDefaultMenu({ components, context }) { + context.logger.warn('No menus found. Building default menu.'); + const pages = type.isArray(components.pages) ? components.pages : []; + const menus = [ + { + id: 'default', + links: pages.map((page, i) => ({ + id: `${i}`, + type: 'MenuLink', + pageId: page.pageId, + })), + }, + ]; + + return menus; +} + +function loopItems(parent, menuId, pages, missingPageWarnings) { + if (type.isArray(parent.links)) { + parent.links.forEach((menuItem) => { + if (menuItem.type === 'MenuLink') { + if (type.isString(menuItem.pageId)) { + const page = pages.find((pg) => pg.pageId === menuItem.pageId); + if (!page) { + missingPageWarnings.push({ + menuItemId: menuItem.id, + pageId: menuItem.pageId, + }); + // remove menuItem from menu + menuItem.remove = true; + return; + } + } + } + menuItem.menuItemId = menuItem.id; + menuItem.id = `menuitem:${menuId}:${menuItem.id}`; + loopItems(menuItem, menuId, pages, missingPageWarnings); + }); + + parent.links = parent.links.filter((item) => item.remove !== true); + } +} + +async function buildMenu({ components, context }) { + const pages = type.isArray(components.pages) ? components.pages : []; + if (type.isUndefined(components.menus) || components.menus.length === 0) { + components.menus = await buildDefaultMenu({ components, context }); + } + const missingPageWarnings = []; + components.menus.forEach((menu) => { + menu.menuId = menu.id; + menu.id = `menu:${menu.id}`; + loopItems(menu, menu.menuId, pages, missingPageWarnings); + }); + await Promise.all( + missingPageWarnings.map(async (warning) => { + await context.logger.warn( + `Page ${warning.pageId} referenced in menu link ${warning.menuItemId} not found.` + ); + }) + ); + await context.logger.debug('Built menus'); + await context.logger.debug(JSON.stringify(components.menus)); + return components; +} + +export default buildMenu; diff --git a/packages/build/src/build/buildMenu.test.js b/packages/build/src/build/buildMenu.test.js new file mode 100644 index 000000000..a66bbcdee --- /dev/null +++ b/packages/build/src/build/buildMenu.test.js @@ -0,0 +1,408 @@ +/* + Copyright 2020 Lowdefy, Inc + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import buildMenu from './buildMenu'; +import testContext from '../test/testContext'; + +const mockLogWarn = jest.fn(); + +const logger = { + warn: mockLogWarn, +}; + +const context = testContext({ logger }); + +beforeEach(() => { + mockLogWarn.mockReset(); +}); + +test('buildMenu menus exist', async () => { + const components = { + menus: [ + { + id: 'my_menu', + links: [ + { + id: 'menu_page_1', + properties: { + title: 'Page 1', + }, + type: 'MenuLink', + pageId: 'page_1', + }, + { + id: 'menu_external', + properties: { + title: 'Page 1', + }, + type: 'MenuLink', + url: 'www.lowdefy.com', + }, + ], + }, + ], + pages: [ + { + id: 'page:page_1', + pageId: 'page_1', + }, + ], + }; + const res = await buildMenu({ components, context }); + expect(res).toEqual({ + menus: [ + { + id: 'menu:my_menu', + menuId: 'my_menu', + links: [ + { + id: 'menuitem:my_menu:menu_page_1', + menuItemId: 'menu_page_1', + properties: { + title: 'Page 1', + }, + type: 'MenuLink', + pageId: 'page_1', + }, + { + id: 'menuitem:my_menu:menu_external', + menuItemId: 'menu_external', + properties: { + title: 'Page 1', + }, + type: 'MenuLink', + url: 'www.lowdefy.com', + }, + ], + }, + ], + pages: [ + { + id: 'page:page_1', + pageId: 'page_1', + }, + ], + }); +}); + +test('buildMenu nested menus', async () => { + const components = { + menus: [ + { + id: 'my_menu', + links: [ + { + id: 'group', + type: 'MenuGroup', + links: [ + { + id: 'menu_page_1', + properties: { + title: 'Page 1', + }, + type: 'MenuLink', + pageId: 'page_1', + }, + ], + }, + ], + }, + ], + pages: [ + { + id: 'page:page_1', + pageId: 'page_1', + }, + ], + }; + const res = await buildMenu({ components, context }); + expect(res).toEqual({ + menus: [ + { + id: 'menu:my_menu', + menuId: 'my_menu', + links: [ + { + id: 'menuitem:my_menu:group', + menuItemId: 'group', + type: 'MenuGroup', + links: [ + { + id: 'menuitem:my_menu:menu_page_1', + menuItemId: 'menu_page_1', + properties: { + title: 'Page 1', + }, + type: 'MenuLink', + pageId: 'page_1', + }, + ], + }, + ], + }, + ], + pages: [ + { + id: 'page:page_1', + pageId: 'page_1', + }, + ], + }); +}); + +test('buildMenu default menu', async () => { + const components = { + pages: [ + { + id: 'page:page_1', + pageId: 'page_1', + }, + { + id: 'page:page_2', + pageId: 'page_2', + }, + { + id: 'page:page_3', + pageId: 'page_3', + }, + ], + }; + const res = await buildMenu({ components, context }); + expect(res).toEqual({ + menus: [ + { + id: 'menu:default', + menuId: 'default', + links: [ + { + id: 'menuitem:default:0', + menuItemId: '0', + type: 'MenuLink', + pageId: 'page_1', + }, + { + id: 'menuitem:default:1', + menuItemId: '1', + type: 'MenuLink', + pageId: 'page_2', + }, + { + id: 'menuitem:default:2', + menuItemId: '2', + type: 'MenuLink', + pageId: 'page_3', + }, + ], + }, + ], + pages: [ + { + id: 'page:page_1', + pageId: 'page_1', + }, + { + id: 'page:page_2', + pageId: 'page_2', + }, + { + id: 'page:page_3', + pageId: 'page_3', + }, + ], + }); +}); + +test('buildMenu no menu or pages exist', async () => { + const components = {}; + const res = await buildMenu({ components, context }); + expect(res).toEqual({ + menus: [ + { + id: 'menu:default', + menuId: 'default', + links: [], + }, + ], + }); +}); + +test('buildMenu page does not exist', async () => { + const components = { + menus: [ + { + id: 'my_menu', + links: [ + { + id: 'menu_page_1', + type: 'MenuLink', + pageId: 'page_1', + }, + ], + }, + ], + pages: [], + }; + const res = await buildMenu({ components, context }); + expect(res).toEqual({ + menus: [ + { + id: 'menu:my_menu', + menuId: 'my_menu', + links: [], + }, + ], + pages: [], + }); + expect(mockLogWarn.mock.calls).toEqual([ + ['Page page_1 referenced in menu link menu_page_1 not found.'], + ]); +}); + +test('buildMenu page does not exist, nested', async () => { + const components = { + menus: [ + { + id: 'my_menu', + links: [ + { + id: 'MenuGroup1', + type: 'MenuGroup', + links: [ + { + id: 'menu_page_1', + type: 'MenuLink', + pageId: 'page_1', + }, + ], + }, + { + id: 'MenuGroup2', + type: 'MenuGroup', + links: [ + { + id: 'MenuGroup3', + type: 'MenuGroup', + links: [ + { + id: 'menu_page_2', + type: 'MenuLink', + pageId: 'page_2', + }, + ], + }, + ], + }, + ], + }, + ], + pages: [], + }; + const res = await buildMenu({ components, context }); + expect(res).toEqual({ + menus: [ + { + id: 'menu:my_menu', + menuId: 'my_menu', + links: [ + { + id: 'menuitem:my_menu:MenuGroup1', + menuItemId: 'MenuGroup1', + type: 'MenuGroup', + links: [], + }, + { + id: 'menuitem:my_menu:MenuGroup2', + menuItemId: 'MenuGroup2', + + type: 'MenuGroup', + links: [ + { + id: 'menuitem:my_menu:MenuGroup3', + menuItemId: 'MenuGroup3', + type: 'MenuGroup', + links: [], + }, + ], + }, + ], + }, + ], + pages: [], + }); + expect(mockLogWarn.mock.calls).toEqual([ + ['Page page_1 referenced in menu link menu_page_1 not found.'], + ['Page page_2 referenced in menu link menu_page_2 not found.'], + ]); +}); + +test('buildMenu pages not array, menu exists', async () => { + const components = { + menus: [ + { + id: 'my_menu', + links: [ + { + id: 'menu_external', + properties: { + title: 'Page 1', + }, + type: 'MenuLink', + url: 'www.lowdefy.com', + }, + ], + }, + ], + pages: 'pages', + }; + const res = await buildMenu({ components, context }); + expect(res).toEqual({ + menus: [ + { + id: 'menu:my_menu', + menuId: 'my_menu', + links: [ + { + id: 'menuitem:my_menu:menu_external', + menuItemId: 'menu_external', + properties: { + title: 'Page 1', + }, + type: 'MenuLink', + url: 'www.lowdefy.com', + }, + ], + }, + ], + pages: 'pages', + }); +}); + +test('buildMenu pages not array, no menu', async () => { + const components = { + pages: 'pages', + }; + const res = await buildMenu({ components, context }); + expect(res).toEqual({ + menus: [ + { + id: 'menu:default', + menuId: 'default', + links: [], + }, + ], + pages: 'pages', + }); +}); diff --git a/packages/build/src/test/testContext.js b/packages/build/src/test/testContext.js new file mode 100644 index 000000000..f98a5beee --- /dev/null +++ b/packages/build/src/test/testContext.js @@ -0,0 +1,33 @@ +function testContext({ logger = {}, configLoader, artifactSetter } = {}) { + const defaultLogger = { + success: () => {}, + warn: () => {}, + error: () => {}, + debug: () => {}, + }; + + const context = { + configLoader: { + load: () => {}, + }, + artifactSetter: { + set: () => [], + }, + }; + + context.logger = { + ...defaultLogger, + ...logger, + }; + + if (configLoader) { + context.configLoader = configLoader; + } + if (artifactSetter) { + context.artifactSetter = artifactSetter; + } + + return context; +} + +export default testContext;