From 468f06dd03e68319b2b1b1a4878caf3564e0a3f4 Mon Sep 17 00:00:00 2001 From: Christian Westgaard Date: Thu, 15 Feb 2024 14:45:58 +0100 Subject: [PATCH] Fix #122 Remove disableAppConfig --- jest.config.ts | 1 + .../seo/queryForFirstSiteWithAppAndUrl.ts} | 2 +- src/main/resources/admin/widgets/seo/seo.ts | 42 ++++--- .../contentMetaFieldsResolver.ts | 39 +++---- .../metaFieldsImagesResolver.ts | 8 +- .../lib/common/findImageIdInContent.ts | 8 +- ...{getTheConfig.ts => getAppOrSiteConfig.ts} | 38 +++++-- src/main/resources/lib/common/getAppendix.ts | 22 ++-- src/main/resources/lib/common/getFullTitle.ts | 25 ++--- src/main/resources/lib/common/getImageId.ts | 14 +-- src/main/resources/lib/common/getImageUrl.ts | 17 +-- .../lib/common/getMetaDescription.ts | 19 +--- src/main/resources/lib/common/getPageTitle.ts | 23 +--- .../resources/lib/metadata/getMetaData.ts | 46 +++----- .../resources/lib/metadata/getReusableData.ts | 36 ------ .../resources/lib/metadata/getTitleHtml.ts | 19 ++-- .../lib/types/MetafieldsSiteConfig.d.ts | 1 - .../resources/site/processors/add-metadata.ts | 27 +++-- test/guillotine/guillotine.test.ts | 24 ++-- test/lib/common/getImageUrl.test.ts | 65 +++++------ test/lib/metadata/getMetaData.test.ts | 106 +++++++++++------- test/mocks/mockLibXpNode.ts | 10 +- 22 files changed, 263 insertions(+), 329 deletions(-) rename src/main/resources/{lib/common/getSite.ts => admin/widgets/seo/queryForFirstSiteWithAppAndUrl.ts} (93%) rename src/main/resources/lib/common/{getTheConfig.ts => getAppOrSiteConfig.ts} (57%) delete mode 100644 src/main/resources/lib/metadata/getReusableData.ts diff --git a/jest.config.ts b/jest.config.ts index da2fb18..a1b869d 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -12,6 +12,7 @@ export default { }, moduleNameMapper: { '/guillotine/(.*)': '/src/main/resources/guillotine/$1', + '/lib/app-metafields/(.*)': '/src/main/resources/lib/app-metafields/$1', '/lib/common/(.*)': '/src/main/resources/lib/common/$1', '/lib/metadata/(.*)': '/src/main/resources/lib/metadata/$1', '/lib/types(.*)': '/src/main/resources/lib/types$1', diff --git a/src/main/resources/lib/common/getSite.ts b/src/main/resources/admin/widgets/seo/queryForFirstSiteWithAppAndUrl.ts similarity index 93% rename from src/main/resources/lib/common/getSite.ts rename to src/main/resources/admin/widgets/seo/queryForFirstSiteWithAppAndUrl.ts index 03914a9..cd42f13 100644 --- a/src/main/resources/lib/common/getSite.ts +++ b/src/main/resources/admin/widgets/seo/queryForFirstSiteWithAppAndUrl.ts @@ -5,7 +5,7 @@ import type { MetafieldsSiteConfig } from '/lib/types/MetafieldsSiteConfig'; import {query} from '/lib/xp/content'; -export const getSite = ({ +export const queryForFirstSiteWithAppAndUrl = ({ applicationKey, // Avoid app.name so it can be used in Guillotine Extension Context siteUrl } : { diff --git a/src/main/resources/admin/widgets/seo/seo.ts b/src/main/resources/admin/widgets/seo/seo.ts index 43354dc..7f98c38 100644 --- a/src/main/resources/admin/widgets/seo/seo.ts +++ b/src/main/resources/admin/widgets/seo/seo.ts @@ -11,6 +11,8 @@ import { // @ts-expect-error // No types yet import {render} from '/lib/thymeleaf'; +import {queryForFirstSiteWithAppAndUrl} from '/admin/widgets/seo/queryForFirstSiteWithAppAndUrl'; + import {prependBaseUrl} from '/lib/app-metafields/url/prependBaseUrl'; import {getAppendix} from '/lib/common/getAppendix'; import {getBlockRobots} from '/lib/common/getBlockRobots'; @@ -19,8 +21,7 @@ import {getImageUrl} from '/lib/common/getImageUrl'; import {getLang} from '/lib/common/getLang'; import {getMetaDescription} from '/lib/common/getMetaDescription'; import {getPageTitle} from '/lib/common/getPageTitle'; -import {getSite} from '/lib/common/getSite'; -import {getTheConfig} from '/lib/common/getTheConfig'; +import {getAppOrSiteConfig} from '/lib/common/getAppOrSiteConfig'; /* @@ -60,33 +61,29 @@ export const get = (req: Request) => { if (content) { // The first part of the content '_path' is the site's URL, make sure to fetch current site! const parts = content._path.split('/'); - const site = getSite({ + const site = queryForFirstSiteWithAppAndUrl({ applicationKey: app.name, // NOTE: Using app.name is fine, since it's outside Guillotine Execution Context siteUrl: parts[1] }); // Send the first /x/-part of the content's path. if (site) { - const siteConfig = getTheConfig({ + const appOrSiteConfig = getAppOrSiteConfig({ applicationConfig: app.config, // NOTE: Using app.config is fine, since it's outside Guillotine Execution Context applicationKey: app.name, // NOTE: Using app.name is fine, since it's outside Guillotine Execution Context site }); - if (siteConfig) { + if (appOrSiteConfig) { const isFrontpage = site._path === content._path; const pageTitle = getPageTitle({ - applicationConfig: app.config, // NOTE: Using app.config is fine, since it's outside Guillotine Execution Context - applicationKey: app.name, // NOTE: Using app.name is fine, since it's outside Guillotine Execution Context + appOrSiteConfig, content, - site }); const titleAppendix = getAppendix({ - applicationConfig: app.config, // NOTE: Using app.config is fine, since it's outside Guillotine Execution Context - applicationKey: app.name, // NOTE: Using app.name is fine, since it's outside Guillotine Execution Context + appOrSiteConfig, isFrontpage, site, }); let description = getMetaDescription({ - applicationConfig: app.config, // NOTE: Using app.config is fine, since it's outside Guillotine Execution Context - applicationKey: app.name, // NOTE: Using app.name is fine, since it's outside Guillotine Execution Context + appOrSiteConfig, content, site }); @@ -96,9 +93,9 @@ export const get = (req: Request) => { const absoluteUrl = pageUrl({ path: content._path, type: "absolute" }); let ogUrl: string; - if (siteConfig.baseUrl) { + if (appOrSiteConfig.baseUrl) { ogUrl = prependBaseUrl({ - baseUrl: siteConfig.baseUrl, + baseUrl: appOrSiteConfig.baseUrl, contentPath: content._path, sitePath: site._path }); @@ -110,9 +107,9 @@ export const get = (req: Request) => { let canonical = null; const contentForCanonicalUrl = getContentForCanonicalUrl(content); if (contentForCanonicalUrl) { - if (siteConfig.baseUrl) { + if (appOrSiteConfig.baseUrl) { canonical = prependBaseUrl({ - baseUrl: siteConfig.baseUrl, + baseUrl: appOrSiteConfig.baseUrl, contentPath: contentForCanonicalUrl ? contentForCanonicalUrl._path : content._path, @@ -128,12 +125,11 @@ export const get = (req: Request) => { } const imageUrl = getImageUrl({ - applicationConfig: app.config, // NOTE: Using app.config is fine, since it's outside Guillotine Execution Context - applicationKey: app.name, // NOTE: Using app.name is fine, since it's outside Guillotine Execution Context + appOrSiteConfig, content, site, - defaultImg: siteConfig.seoImage, - defaultImgPrescaled: siteConfig.seoImageIsPrescaled + defaultImg: appOrSiteConfig.seoImage, + defaultImgPrescaled: appOrSiteConfig.seoImageIsPrescaled }); params = { @@ -143,7 +139,7 @@ export const get = (req: Request) => { description: description, image: imageUrl, canonical, - blockRobots: (siteConfig.blockRobots || getBlockRobots(content)) + blockRobots: (appOrSiteConfig.blockRobots || getBlockRobots(content)) }, og: { type: (isFrontpage ? 'website' : 'article'), @@ -159,11 +155,11 @@ export const get = (req: Request) => { } }, twitter: { - active: (siteConfig.twitterUsername ? true : false), + active: (appOrSiteConfig.twitterUsername ? true : false), title: pageTitle, description: description, image: imageUrl, - site: siteConfig.twitterUsername || null + site: appOrSiteConfig.twitterUsername || null } }; } diff --git a/src/main/resources/guillotine/typeFieldResolvers/contentMetaFieldsResolver.ts b/src/main/resources/guillotine/typeFieldResolvers/contentMetaFieldsResolver.ts index 35ba3b5..be2e509 100644 --- a/src/main/resources/guillotine/typeFieldResolvers/contentMetaFieldsResolver.ts +++ b/src/main/resources/guillotine/typeFieldResolvers/contentMetaFieldsResolver.ts @@ -4,25 +4,22 @@ import type {Resolver} from '/lib/types/guillotine'; import {startsWith} from '@enonic/js-utils/string/startsWith'; import {includes as arrayIncludes} from '@enonic/js-utils/array/includes'; -import { - get as getContentByKey, - getSite as libsContentGetSite, -} from '/lib/xp/content'; +import {getSite as libsContentGetSite} from '/lib/xp/content'; import { get as getContext, run as runInContext } from '/lib/xp/context'; +import {siteRelativePath} from '/lib/app-metafields/path/siteRelativePath'; import {prependBaseUrl} from '/lib/app-metafields/url/prependBaseUrl'; + +import {getAppOrSiteConfig} from '/lib/common/getAppOrSiteConfig'; import {getBlockRobots} from '/lib/common/getBlockRobots'; import {getContentForCanonicalUrl} from '/lib/common/getContentForCanonicalUrl'; import {getLang} from '/lib/common/getLang'; import {getMetaDescription} from '/lib/common/getMetaDescription'; import {getFullTitle} from '/lib/common/getFullTitle'; -import {getSiteConfigFromSite} from '/lib/common/getSiteConfigFromSite'; -import {getTheConfig} from '/lib/common/getTheConfig'; -import {APP_CONFIG, APP_NAME, APP_NAME_PATH, MIXIN_PATH} from '/lib/common/constants'; -import { siteRelativePath } from '/lib/app-metafields/path/siteRelativePath'; +import {APP_CONFIG, APP_NAME} from '/lib/common/constants'; export const contentMetaFieldsResolver: Resolver< @@ -75,37 +72,31 @@ export const contentMetaFieldsResolver: Resolver< principals }, () => { const site = libsContentGetSite({ key: _path }); - const description = getMetaDescription({ + const appOrSiteConfig = getAppOrSiteConfig({ applicationConfig: APP_CONFIG, applicationKey: APP_NAME, - content, site }); - const appOrSiteConfig = getTheConfig({ - applicationConfig: APP_CONFIG, - applicationKey: APP_NAME, + const description = getMetaDescription({ + appOrSiteConfig, + content, site }); const title = getFullTitle({ - applicationConfig: APP_CONFIG, - applicationKey: APP_NAME, + appOrSiteConfig, content, site }); const isFrontpage = site._path === _path; - const siteConfig = getSiteConfigFromSite({ - applicationKey: APP_NAME, - site - }); - const blockRobots = siteConfig.blockRobots || getBlockRobots(content) + const blockRobots = appOrSiteConfig.blockRobots || getBlockRobots(content) let canonical: string|null = null; const contentForCanonicalUrl = getContentForCanonicalUrl(content); if (contentForCanonicalUrl) { if (appOrSiteConfig.baseUrl) { canonical = prependBaseUrl({ - baseUrl: siteConfig.baseUrl, + baseUrl: appOrSiteConfig.baseUrl, contentPath: contentForCanonicalUrl._path, sitePath: site._path }); @@ -119,7 +110,7 @@ export const contentMetaFieldsResolver: Resolver< const url: string = appOrSiteConfig.baseUrl ? prependBaseUrl({ - baseUrl: siteConfig.baseUrl, + baseUrl: appOrSiteConfig.baseUrl, contentPath: content._path, sitePath: site._path }) @@ -130,9 +121,9 @@ export const contentMetaFieldsResolver: Resolver< // return >>{ return { + _appOrSiteConfig: appOrSiteConfig, _content: content, _site: site, - _siteConfig: siteConfig, canonical, description, locale: getLang(content, site), @@ -152,7 +143,7 @@ export const contentMetaFieldsResolver: Resolver< site: appOrSiteConfig.twitterUsername, }, verification: { - google: siteConfig.siteVerification || null + google: appOrSiteConfig.siteVerification || null }, url, }; diff --git a/src/main/resources/guillotine/typeFieldResolvers/metaFieldsImagesResolver.ts b/src/main/resources/guillotine/typeFieldResolvers/metaFieldsImagesResolver.ts index 1cf98bf..a225e38 100644 --- a/src/main/resources/guillotine/typeFieldResolvers/metaFieldsImagesResolver.ts +++ b/src/main/resources/guillotine/typeFieldResolvers/metaFieldsImagesResolver.ts @@ -12,17 +12,15 @@ import { get as getContext, run as runInContext } from '/lib/xp/context'; -import {commaStringToArray} from '/lib/common/commaStringToArray'; -import {findStringValueInObject} from '/lib/common/findStringValueInObject'; import {getImageId} from '/lib/common/getImageId'; export const metaFieldsImagesResolver: Resolver< {}, // args { // source + _appOrSiteConfig: MetafieldsSiteConfig _content: Content _site: Site - _siteConfig: MetafieldsSiteConfig } > = (env) => { // log.info(`resolvers content metafields ${JSON.stringify(env, null, 4)}`); @@ -37,9 +35,9 @@ export const metaFieldsImagesResolver: Resolver< // siteKey // NOTE: Can be undefined when x-guillotine-sitekey is missing } = localContext; const { + _appOrSiteConfig, _content, _site, - _siteConfig } = source; const context = getContext(); // log.info('metaFieldsImagesResolver context: %s', JSON.stringify(context, null, 4)); @@ -64,9 +62,9 @@ export const metaFieldsImagesResolver: Resolver< }, () => { const imageId = getImageId({ + appOrSiteConfig: _appOrSiteConfig, content: _content, site: _site, - siteConfig: _siteConfig }); if (imageId) { const imageContent = getContentByKey({ key: imageId }); diff --git a/src/main/resources/lib/common/findImageIdInContent.ts b/src/main/resources/lib/common/findImageIdInContent.ts index 868da1e..50edd10 100644 --- a/src/main/resources/lib/common/findImageIdInContent.ts +++ b/src/main/resources/lib/common/findImageIdInContent.ts @@ -19,19 +19,19 @@ import { export function findImageIdInContent({ + appOrSiteConfig, content, - siteConfig }: { + appOrSiteConfig: MetafieldsSiteConfig content: Content - siteConfig: MetafieldsSiteConfig }): ImageId|undefined { if(content.x?.[APP_NAME_PATH]?.[MIXIN_PATH]?.seoImage) { return ImageIdBuilder.from(content.x[APP_NAME_PATH][MIXIN_PATH].seoImage as string); } - const userDefinedPaths = CommaSeparatedStringBuilder.from(siteConfig.pathsImages || ''); + const userDefinedPaths = CommaSeparatedStringBuilder.from(appOrSiteConfig.pathsImages || ''); const userDefinedArray = userDefinedPaths ? commaStringToArray(userDefinedPaths) : []; - const userDefinedValue = userDefinedPaths ? findValueInObject(content, userDefinedArray, siteConfig.fullPath) : null; + const userDefinedValue = userDefinedPaths ? findValueInObject(content, userDefinedArray, appOrSiteConfig.fullPath) : null; const firstItem = forceArray(userDefinedValue)[0]; diff --git a/src/main/resources/lib/common/getTheConfig.ts b/src/main/resources/lib/common/getAppOrSiteConfig.ts similarity index 57% rename from src/main/resources/lib/common/getTheConfig.ts rename to src/main/resources/lib/common/getAppOrSiteConfig.ts index 07fdb5b..5c7b387 100644 --- a/src/main/resources/lib/common/getTheConfig.ts +++ b/src/main/resources/lib/common/getAppOrSiteConfig.ts @@ -2,6 +2,8 @@ import type {Site} from '/lib/xp/portal'; import type {MetafieldsSiteConfig} from '/lib/types/MetafieldsSiteConfig'; +import {isSet} from '@enonic/js-utils/value/isSet'; +// import {toStr} from '@enonic/js-utils/value/toStr'; import {getSiteConfig as libPortalGetSiteConfig} from '/lib/xp/portal'; import {getSiteConfigFromSite} from '/lib/common/getSiteConfigFromSite'; @@ -14,28 +16,42 @@ interface GetTheConfigParams { // The configuration needs to be fetched first from site config (using current content if site context is not available - like for widgets), and lastly we'll check for any config files and use these to overwrite. -export const getTheConfig = ({ +export const getAppOrSiteConfig = ({ applicationConfig, applicationKey, site, }: GetTheConfigParams): MetafieldsSiteConfig => { - let config = libPortalGetSiteConfig(); - if (!config) { - config = getSiteConfigFromSite({ - applicationKey, - site, - }); - } - if (applicationConfig && !config.disableAppConfig) { + let appOrSiteConfig: MetafieldsSiteConfig = {} + + if (applicationConfig) { + // log.info('applicationConfig:%s', toStr(applicationConfig)); for (let prop in applicationConfig) { let value: string|boolean = applicationConfig[prop]; if (prop !== 'config.filename' && prop !== 'service.pid') { // Default props for .cfg-files, not to use further. if (value === 'true' || value === 'false') { value = value === 'true'; } - (config as Record)[prop] = value; + (appOrSiteConfig as Record)[prop] = value; } } } - return config; + + let siteConfig = libPortalGetSiteConfig(); + if (!siteConfig) { + siteConfig = getSiteConfigFromSite({ + applicationKey, + site, + }); + } + + // log.info('siteConfig:%s', toStr(siteConfig)); + for (let key in siteConfig) { + const value = siteConfig[key as keyof typeof siteConfig]; + if (isSet(value)) { + (appOrSiteConfig as Record)[key] = value; + } + } + + // log.info('config:%s', toStr(config)); + return appOrSiteConfig; }; diff --git a/src/main/resources/lib/common/getAppendix.ts b/src/main/resources/lib/common/getAppendix.ts index f620f40..e7fbd70 100644 --- a/src/main/resources/lib/common/getAppendix.ts +++ b/src/main/resources/lib/common/getAppendix.ts @@ -2,12 +2,8 @@ import type {Site} from '@enonic-types/lib-portal'; import type {MetafieldsSiteConfig} from '/lib/types/MetafieldsSiteConfig'; -import {getTheConfig} from '/lib/common/getTheConfig'; - - interface GetAppendixParams { - applicationConfig: Record - applicationKey: string + appOrSiteConfig: MetafieldsSiteConfig isFrontpage?: boolean site: Site } @@ -15,20 +11,16 @@ interface GetAppendixParams { // Concat site title? Trigger if set to true in settings, or if not set at all (default = true) export const getAppendix = ({ - applicationConfig, // Avoid app.config so it can be used in Guillotine Extension Context - applicationKey, // Avoid app.name so it can be used in Guillotine Extension Context + appOrSiteConfig, isFrontpage, site, }: GetAppendixParams): string => { - const siteConfig = getTheConfig({ - applicationConfig, - applicationKey, - site - }); let titleAppendix = ''; - if (siteConfig.titleBehaviour || !siteConfig.hasOwnProperty("titleBehaviour")) { - const separator = siteConfig.titleSeparator || '-'; - const titleRemoveOnFrontpage = siteConfig.hasOwnProperty("titleFrontpageBehaviour") ? siteConfig.titleFrontpageBehaviour : true; // Default true needs to be respected + if (appOrSiteConfig.titleBehaviour || !appOrSiteConfig.hasOwnProperty("titleBehaviour")) { + const separator = appOrSiteConfig.titleSeparator || '-'; + const titleRemoveOnFrontpage = appOrSiteConfig.hasOwnProperty("titleFrontpageBehaviour") + ? appOrSiteConfig.titleFrontpageBehaviour + : true; // Default true needs to be respected if (!isFrontpage || !titleRemoveOnFrontpage) { titleAppendix = ' ' + separator + ' ' + site.displayName; } diff --git a/src/main/resources/lib/common/getFullTitle.ts b/src/main/resources/lib/common/getFullTitle.ts index 0ed7b2d..ccbff0c 100644 --- a/src/main/resources/lib/common/getFullTitle.ts +++ b/src/main/resources/lib/common/getFullTitle.ts @@ -7,29 +7,26 @@ import {getAppendix} from '/lib/common/getAppendix'; import {getPageTitle} from '/lib/common/getPageTitle'; +interface GetFullTitleParams { + appOrSiteConfig: MetafieldsSiteConfig + content: Content + site: Site +} + export function getFullTitle({ - applicationConfig, // Avoid app.config so it can be used in Guillotine Extension Context - applicationKey, // Avoid app.name so it can be used in Guillotine Extension Context, + appOrSiteConfig, content, site, -}: { - applicationConfig: Record - applicationKey: string - content: Content - site: Site -}) { +}: GetFullTitleParams) { const isFrontpage = site._path === content._path; const titleAppendix = getAppendix({ - applicationConfig, - applicationKey, + appOrSiteConfig, isFrontpage, site, }); const pageTitle = getPageTitle({ - applicationConfig, - applicationKey, - content, - site + appOrSiteConfig, + content }); return `${pageTitle}${titleAppendix}`; } diff --git a/src/main/resources/lib/common/getImageId.ts b/src/main/resources/lib/common/getImageId.ts index 24e9d52..5d5e2f4 100644 --- a/src/main/resources/lib/common/getImageId.ts +++ b/src/main/resources/lib/common/getImageId.ts @@ -8,30 +8,30 @@ import {ImageIdBuilder} from '/lib/types'; interface GetImageUrlParams { + appOrSiteConfig: MetafieldsSiteConfig content: Content site: Site - siteConfig: MetafieldsSiteConfig } export function getImageId({ + appOrSiteConfig, content, site, - siteConfig }: GetImageUrlParams): ImageId|undefined { // 1. Try to find an image within the content isself const imageId = findImageIdInContent({ + appOrSiteConfig, content, - siteConfig }); if (imageId) { return imageId; } // log.info(`getImageId: Didn't find any image on content ${content._path}`); - // 2. Fallback to siteConfig image - if (siteConfig.seoImage) { - return ImageIdBuilder.from(siteConfig.seoImage); + // 2. Fallback to appOrSiteConfig image + if (appOrSiteConfig.seoImage) { + return ImageIdBuilder.from(appOrSiteConfig.seoImage); } // log.info(`getImageId: Not even an override image on content ${content._path}`); @@ -42,7 +42,7 @@ export function getImageId({ // log.info(`getImageId ${content._path} !== ${site._path}`); return findImageIdInContent({ + appOrSiteConfig, content: site, - siteConfig }); } diff --git a/src/main/resources/lib/common/getImageUrl.ts b/src/main/resources/lib/common/getImageUrl.ts index b0fb0de..0d08a89 100644 --- a/src/main/resources/lib/common/getImageUrl.ts +++ b/src/main/resources/lib/common/getImageUrl.ts @@ -11,13 +11,11 @@ import { attachmentUrl, imageUrl } from '/lib/xp/portal'; -import {getTheConfig} from '/lib/common/getTheConfig'; import {findImageIdInContent} from '/lib/common/findImageIdInContent'; interface GetImageUrlParams { - applicationConfig: Record - applicationKey: string + appOrSiteConfig: MetafieldsSiteConfig content: Content defaultImg?: ImageId defaultImgPrescaled?: boolean @@ -61,23 +59,16 @@ function _imageUrlFromId(imageId: ImageId): string|null { export const getImageUrl = ({ - applicationConfig, - applicationKey, + appOrSiteConfig, content, defaultImg, defaultImgPrescaled, site, }: GetImageUrlParams): string|null|undefined => { - const siteConfig = getTheConfig({ - applicationConfig, - applicationKey, - site - }); - // Try to find an image in the content's image or images properties const imageId = findImageIdInContent({ + appOrSiteConfig, content, - siteConfig }); if (imageId || (defaultImg && !defaultImgPrescaled)) { @@ -93,8 +84,8 @@ export const getImageUrl = ({ } const siteImageId = findImageIdInContent({ + appOrSiteConfig, content: site, - siteConfig }); if (siteImageId) { return _imageUrlFromId(siteImageId); diff --git a/src/main/resources/lib/common/getMetaDescription.ts b/src/main/resources/lib/common/getMetaDescription.ts index 6708be6..ba08984 100644 --- a/src/main/resources/lib/common/getMetaDescription.ts +++ b/src/main/resources/lib/common/getMetaDescription.ts @@ -6,33 +6,24 @@ import type {MetafieldsSiteConfig} from '/lib/types/MetafieldsSiteConfig'; import {commaStringToArray} from '/lib/common/commaStringToArray'; import {APP_NAME_PATH, MIXIN_PATH} from '/lib/common/constants'; import {findStringValueInObject} from '/lib/common/findStringValueInObject'; -import {getTheConfig} from '/lib/common/getTheConfig'; import {CommaSeparatedStringBuilder} from '/lib/types'; interface GetMetaDescriptionParams { - applicationConfig: Record - applicationKey: string + appOrSiteConfig: MetafieldsSiteConfig content: Content site: Site } export const getMetaDescription = ({ - applicationConfig, - applicationKey, + appOrSiteConfig, content, site, }: GetMetaDescriptionParams): string => { - const siteConfig = getTheConfig({ - applicationConfig, - applicationKey, - site - }); - - const userDefinedPaths = CommaSeparatedStringBuilder.from(siteConfig.pathsDescriptions || ''); + const userDefinedPaths = CommaSeparatedStringBuilder.from(appOrSiteConfig.pathsDescriptions || ''); const userDefinedArray = userDefinedPaths ? commaStringToArray(userDefinedPaths) : []; - const userDefinedValue = userDefinedPaths ? findStringValueInObject(content, userDefinedArray, siteConfig.fullPath) : null; + const userDefinedValue = userDefinedPaths ? findStringValueInObject(content, userDefinedArray, appOrSiteConfig.fullPath) : null; const setWithMixin = content.x[APP_NAME_PATH] && content.x[APP_NAME_PATH][MIXIN_PATH] @@ -41,7 +32,7 @@ export const getMetaDescription = ({ let metaDescription = ( setWithMixin ? content.x[APP_NAME_PATH][MIXIN_PATH].seoDescription // Get from mixin : userDefinedValue - || siteConfig.seoDescription // Use default for site + || appOrSiteConfig.seoDescription // Use default for site || site.data.description // Use bottom default || '' // Don't crash plugin on clean installs ) as string; diff --git a/src/main/resources/lib/common/getPageTitle.ts b/src/main/resources/lib/common/getPageTitle.ts index ab404dd..6edbbd2 100644 --- a/src/main/resources/lib/common/getPageTitle.ts +++ b/src/main/resources/lib/common/getPageTitle.ts @@ -1,40 +1,29 @@ import type {Content} from '/lib/xp/content'; -import type {Site} from '@enonic-types/lib-portal'; import type {MetafieldsSiteConfig} from '/lib/types/MetafieldsSiteConfig'; import {commaStringToArray} from '/lib/common/commaStringToArray'; import {APP_NAME_PATH, MIXIN_PATH} from '/lib/common/constants'; import {findStringValueInObject} from '/lib/common/findStringValueInObject'; -import {getTheConfig} from '/lib/common/getTheConfig'; import {stringOrNull} from '/lib/common/stringOrNull'; import {CommaSeparatedStringBuilder} from '/lib/types'; interface GetPageTitleParams { - applicationConfig: Record - applicationKey: string + appOrSiteConfig: MetafieldsSiteConfig content: Content - site: Site } export const getPageTitle = ({ - applicationConfig, - applicationKey, + appOrSiteConfig, content, - site }: GetPageTitleParams): string => { - const siteConfig = getTheConfig({ - applicationConfig, - applicationKey, - site - }); - // log.info('siteConfig: %s', JSON.stringify(siteConfig, null, 4)); - - const userDefinedPaths = CommaSeparatedStringBuilder.from(siteConfig.pathsTitles || ''); + // log.info('appOrSiteConfig: %s', JSON.stringify(appOrSiteConfig, null, 4)); + + const userDefinedPaths = CommaSeparatedStringBuilder.from(appOrSiteConfig.pathsTitles || ''); const userDefinedArray = userDefinedPaths ? commaStringToArray(userDefinedPaths) : []; - const userDefinedValue = userDefinedPaths ? findStringValueInObject(content, userDefinedArray, siteConfig.fullPath) : null; + const userDefinedValue = userDefinedPaths ? findStringValueInObject(content, userDefinedArray, appOrSiteConfig.fullPath) : null; return content.x?.[APP_NAME_PATH]?.[MIXIN_PATH]?.seoTitle as string || stringOrNull(userDefinedValue) // json property defined by user as important diff --git a/src/main/resources/lib/metadata/getMetaData.ts b/src/main/resources/lib/metadata/getMetaData.ts index 3a0f17b..4520dc3 100644 --- a/src/main/resources/lib/metadata/getMetaData.ts +++ b/src/main/resources/lib/metadata/getMetaData.ts @@ -14,7 +14,6 @@ import {getImageUrl} from '/lib/common/getImageUrl'; import {getLang} from '/lib/common/getLang'; import {getMetaDescription} from '/lib/common/getMetaDescription'; import {getPageTitle} from '/lib/common/getPageTitle'; -import {getTheConfig} from '/lib/common/getTheConfig'; interface MetaDataModel { @@ -42,10 +41,8 @@ interface MetaDataModel { } interface GetMetaDataParams { - applicationConfig: Record - applicationKey: string + appOrSiteConfig: MetafieldsSiteConfig site: Site - siteConfig: MetafieldsSiteConfig content?: Content returnType?: 'json'|'html' selfClosingTags?: boolean @@ -60,10 +57,8 @@ function _resolveMetadata(params: MetaDataModel, selfClosingTags=false) { export function getMetaData({ - applicationConfig, - applicationKey, + appOrSiteConfig, site, - siteConfig, content=undefined, returnType="json", selfClosingTags=false @@ -72,24 +67,16 @@ export function getMetaData({ return undefined; } - const appOrSiteConfig = getTheConfig({ - applicationConfig, - applicationKey, - site - }); - const isFrontpage = site._path === content._path; const pageTitle = getPageTitle({ - applicationConfig, - applicationKey, + appOrSiteConfig, content, - site }); - const siteVerification = siteConfig.siteVerification || null; + const siteVerification = appOrSiteConfig.siteVerification || null; - const absoluteUrl = siteConfig.baseUrl + const absoluteUrl = appOrSiteConfig.baseUrl ? prependBaseUrl({ - baseUrl: siteConfig.baseUrl, + baseUrl: appOrSiteConfig.baseUrl, contentPath: content._path, sitePath: site._path }) @@ -97,9 +84,9 @@ export function getMetaData({ const canonicalContent = getContentForCanonicalUrl(content); const canonicalUrl = canonicalContent - ? siteConfig.baseUrl + ? appOrSiteConfig.baseUrl ? prependBaseUrl({ - baseUrl: siteConfig.baseUrl, + baseUrl: appOrSiteConfig.baseUrl, contentPath: canonicalContent._path, sitePath: site._path }) @@ -108,31 +95,28 @@ export function getMetaData({ const imageUrl = !appOrSiteConfig.removeOpenGraphImage ? getImageUrl({ - applicationConfig, - applicationKey, + appOrSiteConfig, content, site, - defaultImg: siteConfig.seoImage, - defaultImgPrescaled: siteConfig.seoImageIsPrescaled + defaultImg: appOrSiteConfig.seoImage, + defaultImgPrescaled: appOrSiteConfig.seoImageIsPrescaled }) : null; const twitterImageUrl = !appOrSiteConfig.removeTwitterImage ? getImageUrl({ - applicationConfig, - applicationKey, + appOrSiteConfig, content, site, - defaultImg: siteConfig.seoImage + defaultImg: appOrSiteConfig.seoImage }) : null; const params: MetaDataModel = { - blockRobots: siteConfig.blockRobots || getBlockRobots(content), + blockRobots: appOrSiteConfig.blockRobots || getBlockRobots(content), canonicalUrl, description: getMetaDescription({ - applicationConfig, - applicationKey, + appOrSiteConfig, content, site }), diff --git a/src/main/resources/lib/metadata/getReusableData.ts b/src/main/resources/lib/metadata/getReusableData.ts deleted file mode 100644 index 79a5668..0000000 --- a/src/main/resources/lib/metadata/getReusableData.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { - get as getContentByKey, - getSite as libsContentGetSite, - getSiteConfig as libsContentGetSiteConfig, -} from '/lib/xp/content'; -import { - getContent as getCurrentContent, - getSite as libPortalGetSite, - getSiteConfig as libPortalGetSiteConfig, -} from '/lib/xp/portal'; - - -export function getReusableData({ - applicationKey, // Avoid app.name so it can be used in Guillotine Extension Context - contentPath = undefined -}: { - applicationKey: string - contentPath?: string -}) { - let site, content, siteConfig; - - if (!contentPath) { - site = libPortalGetSite(); - content = getCurrentContent(); - siteConfig = libPortalGetSiteConfig(); - } else { - content = getContentByKey({ key: contentPath }); - site = libsContentGetSite({ key: contentPath }); - siteConfig = libsContentGetSiteConfig({ - applicationKey, - key: contentPath - }); - } - - return { site, content, siteConfig }; -} diff --git a/src/main/resources/lib/metadata/getTitleHtml.ts b/src/main/resources/lib/metadata/getTitleHtml.ts index 16ffc71..4a0ce28 100644 --- a/src/main/resources/lib/metadata/getTitleHtml.ts +++ b/src/main/resources/lib/metadata/getTitleHtml.ts @@ -6,24 +6,23 @@ import type {MetafieldsSiteConfig} from '/lib/types/MetafieldsSiteConfig'; import {getFullTitle} from '/lib/common/getFullTitle'; +interface GetTitleHtmlParams { + appOrSiteConfig: MetafieldsSiteConfig + content?: Content + site: Site +} + export function getTitleHtml({ - applicationConfig, // Avoid app.config so it can be used in Guillotine Extension Context - applicationKey, // Avoid app.name so it can be used in Guillotine Extension Context, + appOrSiteConfig, content=undefined, site, -}: { - applicationConfig: Record - applicationKey: string - content?: Content - site: Site -}) { +}: GetTitleHtmlParams) { if (!content) { return undefined; } return `${getFullTitle({ - applicationConfig, - applicationKey, + appOrSiteConfig, content, site })}`; diff --git a/src/main/resources/lib/types/MetafieldsSiteConfig.d.ts b/src/main/resources/lib/types/MetafieldsSiteConfig.d.ts index ecf287f..823fa06 100644 --- a/src/main/resources/lib/types/MetafieldsSiteConfig.d.ts +++ b/src/main/resources/lib/types/MetafieldsSiteConfig.d.ts @@ -10,7 +10,6 @@ export declare interface MetafieldsSiteConfig { baseUrl?: string blockRobots?: boolean canonical?: boolean - disableAppConfig?: boolean fullPath?: boolean pathsDescriptions?: CommaSeparatedString pathsImages?: CommaSeparatedString diff --git a/src/main/resources/site/processors/add-metadata.ts b/src/main/resources/site/processors/add-metadata.ts index af9cadc..1e14975 100644 --- a/src/main/resources/site/processors/add-metadata.ts +++ b/src/main/resources/site/processors/add-metadata.ts @@ -2,9 +2,13 @@ import type {Request, Response} from '/lib/types'; import {forceArray} from '@enonic/js-utils/array/forceArray'; +import { + getContent as getCurrentContent, + getSite as libPortalGetSite, +} from '/lib/xp/portal'; +import {getAppOrSiteConfig} from '/lib/common/getAppOrSiteConfig'; import {getFixedHtmlAttrsAsString} from '/lib/metadata/getFixedHtmlAttrsAsString'; import {getMetaData} from '/lib/metadata/getMetaData' -import {getReusableData} from '/lib/metadata/getReusableData'; import {getTitleHtml} from '/lib/metadata/getTitleHtml'; @@ -13,14 +17,13 @@ const XML_MEDIA_TYPES = ['application/xhtml+xml', 'application/xml', 'text/xml'] export const responseProcessor = (req: Request, res: Response) => { - const reusableData = getReusableData({ + const site = libPortalGetSite(); + const content = getCurrentContent(); + const appOrSiteConfig = getAppOrSiteConfig({ + applicationConfig: app.config, // NOTE: Using app.config is fine, since it's outside Guillotine Execution Context applicationKey: app.name, // NOTE: Using app.name is fine, since it's outside Guillotine Execution Context + site }); - // log.info(`Reusable data: ${JSON.stringify(reusableData, null, 4)}`); - - const site = reusableData.site; - const content = reusableData.content; - const siteConfig = reusableData.siteConfig; let titleAdded = false; const isResponseContentTypeHtml = res.contentType.indexOf(HTML_MEDIA_TYPE) > -1; @@ -35,8 +38,7 @@ export const responseProcessor = (req: Request, res: Response) => { // Svg are text/html can have a if (titleHasIndex && htmlIndex > -1) { const titleHtml = getTitleHtml({ - applicationConfig: app.config, // NOTE: Using app.config is fine, since it's outside Guillotine Execution Context - applicationKey: app.name, // NOTE: Using app.name is fine, since it's outside Guillotine Execution Context + appOrSiteConfig, content, site, }) || ""; @@ -61,10 +63,8 @@ export const responseProcessor = (req: Request, res: Response) => { if ( isResponseContentTypeHtml || isResponseContentTypeXml ) { const selfClosingTags = isResponseContentTypeXml; const metadata: string = getMetaData({ - applicationConfig: app.config, // NOTE: Using app.config is fine, since it's outside Guillotine Execution Context - applicationKey: app.name, // NOTE: Using app.name is fine, since it's outside Guillotine Execution Context + appOrSiteConfig, site, - siteConfig, content, returnType: 'html', selfClosingTags @@ -74,8 +74,7 @@ export const responseProcessor = (req: Request, res: Response) => { if ( !titleAdded ) { const titleHtml = getTitleHtml({ - applicationConfig: app.config, // NOTE: Using app.config is fine, since it's outside Guillotine Execution Context - applicationKey: app.name, // NOTE: Using app.name is fine, since it's outside Guillotine Execution Context + appOrSiteConfig, content, site, }) || ""; diff --git a/test/guillotine/guillotine.test.ts b/test/guillotine/guillotine.test.ts index 9e1f438..a42b742 100644 --- a/test/guillotine/guillotine.test.ts +++ b/test/guillotine/guillotine.test.ts @@ -40,6 +40,7 @@ import { import {mockImage} from '../mocks/mockImage'; import {mockLibUtil} from '../mocks/mockLibUtil'; import {mockLibXpContext} from '../mocks/mockLibXpContext'; +import {mockLibXpNode} from '../mocks/mockLibXpNode'; // @ts-ignore TS2339: Property 'log' does not exist on type 'typeof globalThis'. globalThis.log = { @@ -52,7 +53,6 @@ globalThis.log = { const metaFieldsSiteConfig: MetafieldsSiteConfig = { blockRobots: true, canonical: true, - disableAppConfig: true, fullPath: true, pathsDescriptions: 'pathsDescriptions', // with comma pathsImages: 'pathsImages', // with comma @@ -60,7 +60,6 @@ const metaFieldsSiteConfig: MetafieldsSiteConfig = { seoDescription: 'seoDescription', seoImage: 'seoImage', seoImageIsPrescaled: true, - seoTitle: 'seoTitle', siteVerification: 'siteVerification', removeOpenGraphImage: true, removeOpenGraphUrl: true, @@ -74,7 +73,7 @@ const metaFieldsSiteConfig: MetafieldsSiteConfig = { const siteContent: Site<MetafieldsSiteConfig> = { _id: 'siteContentId', _name: 'siteContentName', - _path: 'siteContentPath', + _path: '/siteContentPath', attachments: {}, creator: 'user:system:creator', createdTime: '2021-01-01T00:00:00Z', @@ -96,7 +95,7 @@ const siteContent: Site<MetafieldsSiteConfig> = { const folderContent: BaseFolder = { _id: 'folderContentId', _name: 'folderContentName', - _path: 'folderContentPath', + _path: '/folderContentPath', attachments: {}, creator: 'user:system:creator', createdTime: '2021-01-01T00:00:00Z', @@ -115,6 +114,11 @@ const imageContent = mockImage({ }); mockLibXpContext(); +mockLibXpNode({ + nodes: { + folderContentId: {} + } +}); jest.mock( '/lib/xp/portal', @@ -127,9 +131,7 @@ jest.mock( mockLibUtil(); const folderMetaFields: MetaFields = { - alternates: { - canonical: null - }, + canonical: null, description: 'seoDescription', // image: imageContent, locale: 'en_US', @@ -151,6 +153,7 @@ const folderMetaFields: MetaFields = { verification: { google: 'siteVerification' }, + url: '/folderContentPath' }; const graphQLContent = GraphQLContentBuilder.from(siteContent); @@ -167,7 +170,7 @@ const graphQL: GraphQL = { LocalTime: GraphQLLocalTimeBuilder.from('00:00:00'), LocalDateTime: GraphQLLocalDateTimeBuilder.from('2021-01-01T00:00:00'), nonNull: (type) => type, - list: (type) => type, + list: (type) => [type], reference: (typeName) => { // console.debug('reference typeName', typeName); if (typeName === 'media_Image') { @@ -216,7 +219,7 @@ describe('guillotine extensions', () => { MetaFields: { description: "Meta fields for a content", fields: { - alternates: {type: '{"json": "value"}'}, + canonical: {type: 'string'}, description: {type: "string"}, image: { type: imageContent @@ -228,6 +231,7 @@ describe('guillotine extensions', () => { title: {type: "string"}, twitter: {type: '{"json": "value"}'}, verification: {type: '{"json": "value"}'}, + url: {type: 'string'}, } } // MetaFields } // types @@ -257,9 +261,9 @@ describe('guillotine extensions', () => { }, source: folderContent })).toEqual({ + _appOrSiteConfig: metaFieldsSiteConfig, _content: folderContent, _site: siteContent, - _siteConfig: metaFieldsSiteConfig, ...folderMetaFields }); // expect(metaFieldsImagesResolver({})); diff --git a/test/lib/common/getImageUrl.test.ts b/test/lib/common/getImageUrl.test.ts index c853644..51cc657 100644 --- a/test/lib/common/getImageUrl.test.ts +++ b/test/lib/common/getImageUrl.test.ts @@ -33,15 +33,13 @@ globalThis.log = { const metaFieldsSiteConfig: MetafieldsSiteConfig = { blockRobots: true, canonical: true, - disableAppConfig: true, // fullPath: true, - pathsDescriptions: 'pathsDescriptions', // with comma - pathsImages: 'pathsImages', // with comma - pathsTitles: 'pathsTitles', // with comma + pathsDescriptions: 'pathsDescriptions0,pathsDescriptions1', // with comma + pathsImages: 'pathsImages0,pathsImages1', // with comma + pathsTitles: 'pathsTitles0,pathsTitles1', // with comma seoDescription: 'seoDescription', seoImage: 'seoImage', seoImageIsPrescaled: true, - seoTitle: 'seoTitle', siteVerification: 'siteVerification', removeOpenGraphImage: true, removeOpenGraphUrl: true, @@ -137,8 +135,7 @@ describe('getImageUrl', () => { it('should return undefined when no image found', () => { import('/lib/common/getImageUrl').then(({getImageUrl}) => { expect(getImageUrl({ - applicationConfig: {}, - applicationKey: 'com.enonic.app.metafields', + appOrSiteConfig: metaFieldsSiteConfig, content: mockContent({ prefix: 'articleWithoutImage', type: 'base:folder', @@ -151,8 +148,7 @@ describe('getImageUrl', () => { it('should return attachmentUrl when defaultImg and defaultImgPrescaled provided', () => { import('/lib/common/getImageUrl').then(({getImageUrl}) => { expect(getImageUrl({ - applicationConfig: {}, - applicationKey: 'com.enonic.app.metafields', + appOrSiteConfig: metaFieldsSiteConfig, content: mockContent({ prefix: 'articleWithoutImage', type: 'base:folder', @@ -165,32 +161,33 @@ describe('getImageUrl', () => { }); // it it('should handle svgs', () => { + const content = mockContent({ + prefix: 'articleWithImage', + data: { + pathsImages1: 'fourImageContentId', + }, + type: 'base:folder', + }); + // console.info('content', content); + const getImageUrlParams = { + appOrSiteConfig: metaFieldsSiteConfig, + content, + site: siteContent + }; import('/lib/common/getImageUrl').then(({getImageUrl}) => { - expect(getImageUrl({ - applicationConfig: {}, - applicationKey: 'com.enonic.app.metafields', - content: mockContent({ - prefix: 'articleWithImage', - data: { - image: 'fourImageContentId', - }, - type: 'base:folder', - }), - site: siteContent - })).toBe('fourImageContentIdblock(1200,630)absoluteImageUrl'); + expect(getImageUrl(getImageUrlParams)).toBe('fourImageContentIdblock(1200,630)absoluteImageUrl'); }); // import }); // it - it('should return an url when content has data.image', () => { + it('should return an url when content has data.pathsImages0', () => { import('/lib/common/getImageUrl').then(({getImageUrl}) => { expect(getImageUrl({ - applicationConfig: {}, - applicationKey: 'com.enonic.app.metafields', + appOrSiteConfig: metaFieldsSiteConfig, content: mockContent({ prefix: 'articleWithImage', data: { - image: 'oneImageContentId', - images: 'twoImageContentId' + pathsImages0: 'oneImageContentId', + pathsImages1: 'twoImageContentId' }, type: 'base:folder', }), @@ -199,15 +196,14 @@ describe('getImageUrl', () => { }); // import }); // it - it('should return an url when content has data.images', () => { + it('should return an url when content has data.pathsImages1', () => { import('/lib/common/getImageUrl').then(({getImageUrl}) => { expect(getImageUrl({ - applicationConfig: {}, - applicationKey: 'com.enonic.app.metafields', + appOrSiteConfig: metaFieldsSiteConfig, content: mockContent({ prefix: 'articleWithImages', data: { - images: 'twoImageContentId' + pathsImages1: 'twoImageContentId' }, type: 'base:folder', }), @@ -216,17 +212,14 @@ describe('getImageUrl', () => { }); // import }); // it - it('should return an url when content has data.pathsImages[0].images', () => { + it('should return an url when content has data.pathsImages0[0]', () => { import('/lib/common/getImageUrl').then(({getImageUrl}) => { expect(getImageUrl({ - applicationConfig: {}, - applicationKey: 'com.enonic.app.metafields', + appOrSiteConfig: metaFieldsSiteConfig, content: mockContent({ prefix: 'articleWithImages', data: { - pathsImages: [{ - image: 'twoImageContentId' - }] + pathsImages0: ['twoImageContentId'] }, type: 'base:folder', }), diff --git a/test/lib/metadata/getMetaData.test.ts b/test/lib/metadata/getMetaData.test.ts index 8d98cfc..8f9acac 100644 --- a/test/lib/metadata/getMetaData.test.ts +++ b/test/lib/metadata/getMetaData.test.ts @@ -20,6 +20,7 @@ import {mockContent} from '../../mocks/mockContent'; import {mockLibThymeleaf} from '../../mocks/mockLibThymeleaf'; import {mockLibUtil} from '../../mocks/mockLibUtil'; import {mockLibXpContext} from '../../mocks/mockLibXpContext'; +import {mockLibXpNode} from '../../mocks/mockLibXpNode'; import {mocklibXpPortal} from '../../mocks/mockLibXpPortal'; import {mockSite} from '../../mocks/mockSite'; @@ -42,7 +43,9 @@ globalThis.resolve = (path: string) => { return data; } -const metaFieldsSiteConfig: MetafieldsSiteConfig = {}; +const metaFieldsSiteConfig: MetafieldsSiteConfig = { + pathsImages: 'pathsImages0,pathsImages1' +}; describe('getMetaData', () => { @@ -71,31 +74,35 @@ describe('getMetaData', () => { }); it('should return undefined when content is undefined', () => { + mockLibXpNode(); mocklibXpPortal({ siteConfig: metaFieldsSiteConfig }); import('/lib/metadata/getMetaData').then(({getMetaData}) => { expect(getMetaData({ - applicationConfig: {}, - applicationKey: 'com.enonic.app.metafields', + appOrSiteConfig: metaFieldsSiteConfig, site: mockSite({ description: 'Site description', prefix: 'site', siteConfig: metaFieldsSiteConfig }), - siteConfig: metaFieldsSiteConfig, })).toBeUndefined(); }); // import }); // it it('should return an object when content is defined', () => { + mockLibXpNode({ + nodes: { + oneContentId: {}, + siteContentId: {} + } + }); mocklibXpPortal({ siteConfig: metaFieldsSiteConfig }); import('/lib/metadata/getMetaData').then(({getMetaData}) => { expect(getMetaData({ - applicationConfig: {}, - applicationKey: 'com.enonic.app.metafields', + appOrSiteConfig: metaFieldsSiteConfig, content: mockContent({ prefix: 'one', type: 'base:folder', @@ -105,11 +112,9 @@ describe('getMetaData', () => { prefix: 'site', siteConfig: metaFieldsSiteConfig }), - siteConfig: metaFieldsSiteConfig, })).toEqual({ blockRobots: false, - canonical: undefined, - canonicalUrl: 'oneContentPathabsolutePageUrl', + canonicalUrl: null, description: 'Site description', imageUrl: undefined, imageWidth: 1200, @@ -134,6 +139,11 @@ describe('getMetaData', () => { }); // it it('should handle frontpage', () => { + mockLibXpNode({ + nodes: { + siteContentId: {} + } + }); mocklibXpPortal({ siteConfig: metaFieldsSiteConfig }); @@ -144,15 +154,12 @@ describe('getMetaData', () => { siteConfig: metaFieldsSiteConfig }); expect(getMetaData({ - applicationConfig: {}, - applicationKey: 'com.enonic.app.metafields', + appOrSiteConfig: metaFieldsSiteConfig, content: site, site, - siteConfig: metaFieldsSiteConfig, })).toEqual({ blockRobots: false, - canonical: undefined, - canonicalUrl: 'siteContentPathabsolutePageUrl', + canonicalUrl: null, description: 'Site description', imageUrl: undefined, imageWidth: 1200, @@ -173,6 +180,12 @@ describe('getMetaData', () => { }); // it it('should make imageUrl', () => { + mockLibXpNode({ + nodes: { + oneContentId: {}, + siteContentId: {} + } + }); mocklibXpPortal({ siteConfig: metaFieldsSiteConfig }); @@ -180,7 +193,7 @@ describe('getMetaData', () => { const contentWithImage = mockContent({ prefix: 'one', data: { - image: 'oneImageContentId', + pathsImages0: 'oneImageContentId', }, type: 'base:folder', }); @@ -191,15 +204,12 @@ describe('getMetaData', () => { siteConfig }); expect(getMetaData({ - applicationConfig: {}, - applicationKey: 'com.enonic.app.metafields', + appOrSiteConfig: siteConfig, content: contentWithImage, site, - siteConfig, })).toEqual({ blockRobots: false, - canonical: undefined, - canonicalUrl: 'oneContentPathabsolutePageUrl', + canonicalUrl: null, description: 'Site description', imageUrl: 'oneImageContentIdblock(1200,630)absoluteImageUrl', imageWidth: 1200, @@ -228,6 +238,12 @@ describe('getMetaData', () => { ...metaFieldsSiteConfig, removeOpenGraphImage: true, }; + mockLibXpNode({ + nodes: { + oneContentId: {}, + siteContentId: {} + } + }); mocklibXpPortal({ siteConfig }); @@ -235,7 +251,7 @@ describe('getMetaData', () => { const contentWithImage = mockContent({ prefix: 'one', data: { - image: 'oneImageContentId' + pathsImages0: 'oneImageContentId' }, type: 'base:folder', }); @@ -245,17 +261,14 @@ describe('getMetaData', () => { siteConfig }); expect(getMetaData({ - applicationConfig: {}, - applicationKey: 'com.enonic.app.metafields', + appOrSiteConfig: siteConfig, content: contentWithImage, site, - siteConfig, })).toEqual({ blockRobots: false, - canonical: undefined, - canonicalUrl: 'oneContentPathabsolutePageUrl', + canonicalUrl: null, description: 'Site description', - imageUrl: null, + imageUrl: null, // The test is that this is null imageWidth: 1200, imageHeight: 630, locale: 'en_US', @@ -282,6 +295,12 @@ describe('getMetaData', () => { ...metaFieldsSiteConfig, removeTwitterImage: true, }; + mockLibXpNode({ + nodes: { + oneContentId: {}, + siteContentId: {} + } + }); mocklibXpPortal({ siteConfig }); @@ -289,7 +308,7 @@ describe('getMetaData', () => { const contentWithImage = mockContent({ prefix: 'one', data: { - image: 'oneImageContentId' + pathsImages0: 'oneImageContentId' }, type: 'base:folder', }); @@ -299,15 +318,12 @@ describe('getMetaData', () => { siteConfig }); expect(getMetaData({ - applicationConfig: {}, - applicationKey: 'com.enonic.app.metafields', + appOrSiteConfig: siteConfig, content: contentWithImage, site, - siteConfig, })).toEqual({ blockRobots: false, - canonical: undefined, - canonicalUrl: 'oneContentPathabsolutePageUrl', + canonicalUrl: null, description: 'Site description', imageUrl: 'oneImageContentIdblock(1200,630)absoluteImageUrl', imageWidth: 1200, @@ -332,13 +348,18 @@ describe('getMetaData', () => { }); // it it('should html when returnType is html', () => { + mockLibXpNode({ + nodes: { + oneContentId: {}, + siteContentId: {} + } + }); mocklibXpPortal({ siteConfig: metaFieldsSiteConfig }); import('/lib/metadata/getMetaData').then(({getMetaData}) => { expect(getMetaData({ - applicationConfig: {}, - applicationKey: 'com.enonic.app.metafields', + appOrSiteConfig: metaFieldsSiteConfig, content: mockContent({ prefix: 'one', type: 'base:folder', @@ -348,13 +369,12 @@ describe('getMetaData', () => { prefix: 'site', siteConfig: metaFieldsSiteConfig }), - siteConfig: metaFieldsSiteConfig, returnType: 'html', // selfClosingTags: true // Doesn't affect the output in this test })).resolves.toEqual( '<html><head></head><body><div data-th-remove="tag">\n' + '<meta data-th-if="${blockRobots}" name="robots" content="noindex,nofollow">\n' + - '<link data-th-if="${canonical}" data-th-attr="rel=canonical" data-th-href="${canonicalUrl}">\n' + + '<link th:if="${canonicalUrl}" th:attr="rel=canonical" th:href="${canonicalUrl}">\n' + '<meta data-th-if="${siteVerification}" name="google-site-verification" content="" data-th-attr="content=${siteVerification}">\n' + '<meta name="description" data-th-attr="content=${description}">\n' + '<!--/* Open graph */-->\n' + @@ -394,13 +414,18 @@ describe('getMetaData', () => { }); // it it('should handle selfClosingTags', () => { + mockLibXpNode({ + nodes: { + oneContentId: {}, + siteContentId: {} + } + }); mocklibXpPortal({ siteConfig: metaFieldsSiteConfig }); import('/lib/metadata/getMetaData').then(({getMetaData}) => { expect(getMetaData({ - applicationConfig: {}, - applicationKey: 'com.enonic.app.metafields', + appOrSiteConfig: metaFieldsSiteConfig, content: mockContent({ prefix: 'one', type: 'base:folder', @@ -410,13 +435,12 @@ describe('getMetaData', () => { prefix: 'site', siteConfig: metaFieldsSiteConfig }), - siteConfig: metaFieldsSiteConfig, returnType: 'html', selfClosingTags: true // Doesn't affect the output in this test })).resolves.toEqual( '<html><head></head><body><div data-th-remove="tag">\n' + '<meta data-th-if="${blockRobots}" name="robots" content="noindex,nofollow">\n' + - '<link data-th-if="${canonical}" data-th-attr="rel=canonical" data-th-href="${canonicalUrl}">\n' + + '<link th:if="${canonicalUrl}" th:attr="rel=canonical" th:href="${canonicalUrl}">\n' + '<meta data-th-if="${siteVerification}" name="google-site-verification" content="" data-th-attr="content=${siteVerification}">\n' + '<meta name="description" data-th-attr="content=${description}">\n' + '<!--/* Open graph */-->\n' + diff --git a/test/mocks/mockLibXpNode.ts b/test/mocks/mockLibXpNode.ts index b685d71..6bb0087 100644 --- a/test/mocks/mockLibXpNode.ts +++ b/test/mocks/mockLibXpNode.ts @@ -11,14 +11,20 @@ import {jest} from '@jest/globals'; export function mockLibXpNode({ nodes = {} }: { - nodes?: Record<string, string> + nodes?: Record<string, Record<string, unknown>> } = {}) { jest.mock( '/lib/xp/node', () => ({ connect: jest.fn<typeof connect>().mockReturnValue({ // get: jest.fn<RepoConnection['get']>().mockImplementation((...keys: (string | GetNodeParams | (string | GetNodeParams)[])[]) => nodes[keys[0]]) - get: jest.fn().mockImplementation((key: string) => nodes[key]), + get: jest.fn().mockImplementation((key: string) => { + const node = nodes[key] + if (!node) { + console.info(`No node found for key: ${key}`); + } + return node; + }), query: jest.fn().mockReturnValue({ count: 0, hits: [],