From 5cd78f15633bc7108f27046cfeada10ce5f3d712 Mon Sep 17 00:00:00 2001 From: Pavel Milkevich Date: Thu, 8 Feb 2024 14:51:30 +0100 Subject: [PATCH 1/2] Split exports to client/server/common #474 --- .gitignore | 3 ++- package.json | 2 +- src/common/env.ts | 28 +++++++++++++++------------- src/guillotine/fetchContent.ts | 6 +++--- src/guillotine/fetchFromApi.ts | 13 ++++--------- src/guillotine/fetchGuillotine.ts | 12 ++++-------- src/index.ts | 7 ++----- src/server.ts | 5 +++++ tsconfig.json | 15 +++++++++------ 9 files changed, 45 insertions(+), 46 deletions(-) create mode 100644 src/server.ts diff --git a/.gitignore b/.gitignore index f5ba70c7..a1ee3e29 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ /*.map /baseMappings.js /client.js +/server.js /common/ /guillotine/ /index.js @@ -81,4 +82,4 @@ yarn-debug.log* yarn-error.log* # No permanent docs yet, only temporary used by madge -/docs/ \ No newline at end of file +/docs/ diff --git a/package.json b/package.json index 8d87ead6..c2b39f94 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "access": "public" }, "scripts": { - "clean": "del-cli *.d.ts *.js.map baseMappings.* client.* common coverage index.* enonic-nextjs-adapter-*.tgz guillotine i18n query types utils views dist", + "clean": "del-cli *.d.ts *.js.map baseMappings.* client.* server.* common coverage index.* enonic-nextjs-adapter-*.tgz guillotine i18n query types utils views dist", "build": "tsc", "release": "tsc --outDir ./dist && npx --yes cpy-cli package.json README.md ./dist", "check:types": "tsc --noEmit", diff --git a/src/common/env.ts b/src/common/env.ts index 2ec9c1b0..c86b8d80 100644 --- a/src/common/env.ts +++ b/src/common/env.ts @@ -1,10 +1,14 @@ import {ENV_VARS} from './constants'; +const isServer = typeof window === 'undefined'; + +// IMPORTANT: +// NEXT_PUBLIC_ vars should be explicitly referenced to be made available on the client side !!! /** URL to the guillotine API */ -export const API_URL = (process.env[ENV_VARS.API_URL] || process.env[`NEXT_PUBLIC_${ENV_VARS.API_URL}`]); +export const API_URL = isServer ? process.env[ENV_VARS.API_URL] : process.env['NEXT_PUBLIC_ENONIC_API']; /** Optional utility value - defining in one place the name of the target app (the app that defines the content types, the app name is therefore part of the content type strings used both in typeselector and in query introspections) */ -export const APP_NAME = (process.env[ENV_VARS.APP_NAME] || process.env[`NEXT_PUBLIC_${ENV_VARS.APP_NAME}`]); +export const APP_NAME = isServer ? process.env[ENV_VARS.APP_NAME] : process.env['NEXT_PUBLIC_ENONIC_APP_NAME']; /** Optional utility value - derived from APP_NAME, only with underscores instead of dots */ export const APP_NAME_UNDERSCORED = (APP_NAME || '').replace(/\./g, '_'); @@ -12,12 +16,12 @@ export const APP_NAME_UNDERSCORED = (APP_NAME || '').replace(/\./g, '_'); /** Optional utility value - derived from APP_NAME, only with dashes instead of dots */ export const APP_NAME_DASHED = (APP_NAME || '').replace(/\./g, '-'); -const mode = process.env.MODE || process.env.NEXT_PUBLIC_MODE; +const mode = isServer ? process.env.MODE : process.env.NEXT_PUBLIC_MODE; export const IS_DEV_MODE = (mode === 'development'); /** Locales and Enonic XP projects correspondence list */ -export const PROJECTS = (process.env[ENV_VARS.PROJECTS] || process.env[`NEXT_PUBLIC_${ENV_VARS.PROJECTS}`]); +export const PROJECTS = isServer ? process.env[ENV_VARS.PROJECTS] : process.env['NEXT_PUBLIC_ENONIC_PROJECTS']; const requiredConstants = { [ENV_VARS.APP_NAME]: APP_NAME, @@ -25,13 +29,11 @@ const requiredConstants = { [ENV_VARS.PROJECTS]: PROJECTS, }; -if (typeof window === 'undefined') { - // Verify required values on server-side only - Object.keys(requiredConstants).forEach((key: string) => { - const val = requiredConstants[key]; - if (!val) { - throw new Error(`Environment variable '${key}' is missing (from .env?)`); - } - }); -} +// Verify required values on server-side only +Object.keys(requiredConstants).forEach((key: string) => { + const val = requiredConstants[key]; + if (!val) { + throw new Error(`Environment variable '${key}' is missing (from .env?)`); + } +}); diff --git a/src/guillotine/fetchContent.ts b/src/guillotine/fetchContent.ts index 020d8544..fd7eb04b 100644 --- a/src/guillotine/fetchContent.ts +++ b/src/guillotine/fetchContent.ts @@ -1,4 +1,4 @@ -import type {ComponentDescriptor, ContentFetcher, Context, FetchContentResult} from '../types'; +import type {ComponentDescriptor, Context, FetchContentResult} from '../types'; import {headers} from 'next/headers'; @@ -42,7 +42,7 @@ import {createMetaData} from './createMetaData'; * @param context object from Next, contains .query info * @returns FetchContentResult object: {data?: T, error?: {code, message}} */ -export const fetchContent: ContentFetcher = async (context: Context): Promise => { +export async function fetchContent(context: Context): Promise { const {locale, locales, defaultLocale} = getRequestLocaleInfo(context); // ideally we only want to set headers in draft mode, @@ -247,4 +247,4 @@ export const fetchContent: ContentFetcher = async (context: Context): Promise>( +export async function fetchFromApi>( apiUrl: string, body: ContentApiBaseBody, projectConfig: ProjectLocaleConfig, headers?: {}, method = 'POST', -) => { +) { const options = { method, headers: { @@ -71,4 +66,4 @@ export const fetchFromApi = async >( } return json; -}; \ No newline at end of file +} diff --git a/src/guillotine/fetchGuillotine.ts b/src/guillotine/fetchGuillotine.ts index 1a7b9e0c..f1fdb3dc 100644 --- a/src/guillotine/fetchGuillotine.ts +++ b/src/guillotine/fetchGuillotine.ts @@ -1,20 +1,16 @@ -import type { - ContentApiBaseBody, - GuillotineResult, - ProjectLocaleConfig, -} from '../types'; +import type {ContentApiBaseBody, GuillotineResult, ProjectLocaleConfig} from '../types'; import {fetchFromApi} from './fetchFromApi'; /** Guillotine-specialized fetch, using the generic fetch above */ -export const fetchGuillotine = async >( +export async function fetchGuillotine>( contentApiUrl: string, body: ContentApiBaseBody, projectConfig: ProjectLocaleConfig, headers?: {}, -): Promise => { +): Promise { if (typeof body.query !== 'string' || !body.query.trim()) { return { error: { @@ -62,4 +58,4 @@ export const fetchGuillotine = async >( return {error: {code: 'Client-side error', message: err.message}}; } }); -}; \ No newline at end of file +} diff --git a/src/index.ts b/src/index.ts index 5d0184ba..d6ffb5b8 100644 --- a/src/index.ts +++ b/src/index.ts @@ -22,11 +22,7 @@ export { XP_REQUEST_TYPE, } from './common/constants'; -export {fetchContent} from './guillotine/fetchContent'; -export {fetchContentPathsForAllLocales} from './guillotine/fetchContentPathsForAllLocales'; -export {fetchContentPathsForLocale} from './guillotine/fetchContentPathsForLocale'; -export {fetchFromApi} from './guillotine/fetchFromApi'; -export {fetchGuillotine} from './guillotine/fetchGuillotine'; + export {richTextQuery} from './guillotine/metadata/richTextQuery'; export {validateData} from './guillotine/validateData'; @@ -35,6 +31,7 @@ export {I18n} from './i18n/i18n'; export {getContentApiUrl} from './utils/getContentApiUrl'; export {getProjectLocaleConfig} from './utils/getProjectLocaleConfig'; export {getProjectLocaleConfigById} from './utils/getProjectLocaleConfigById'; +export {getProjectLocaleConfigByLocale} from './utils/getProjectLocaleConfigByLocale'; export {getRequestLocaleInfo} from './utils/getRequestLocaleInfo'; export {sanitizeGraphqlName} from './utils/sanitizeGraphqlName'; diff --git a/src/server.ts b/src/server.ts new file mode 100644 index 00000000..6d21893c --- /dev/null +++ b/src/server.ts @@ -0,0 +1,5 @@ +export {fetchContent} from './guillotine/fetchContent'; +export {fetchContentPathsForAllLocales} from './guillotine/fetchContentPathsForAllLocales'; +export {fetchContentPathsForLocale} from './guillotine/fetchContentPathsForLocale'; +export {fetchFromApi} from './guillotine/fetchFromApi'; +export {fetchGuillotine} from './guillotine/fetchGuillotine'; diff --git a/tsconfig.json b/tsconfig.json index cdc96bdc..3c953876 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,7 +7,7 @@ // dom and dom.iterable for now. "dom", "dom.iterable", - "esnext", + "esnext" ], "allowJs": true, "skipLibCheck": true, @@ -23,7 +23,7 @@ "outDir": ".", "declaration": true, - "sourceMap": true, + "sourceMap": true // We have done Triple-Slash Directives where needed instead: // "types": [ @@ -36,15 +36,18 @@ // Specifies an allowlist of files to include in the program. An error occurs // if any of the files can’t be found. "files": [ - "src/index.ts" + "src/index.ts", + "src/client.ts", + "src/server.ts", + "src/baseMappings.ts" ], // Specifies an array of filenames or patterns to include in the program. // These filenames are resolved relative to the directory containing the // tsconfig.json file. "include": [ - "src/**/*.ts", - "src/**/*.tsx" + "src/views/**/*.ts*", + "src/utils/**/*.ts*" ], // Specifies an array of filenames or patterns that should be skipped when @@ -61,5 +64,5 @@ "moduleTypes": { "jest.config.ts": "cjs" } - }, + } } From 6e0bb4fcd3d854f0432c608c579a9152403bcfc5 Mon Sep 17 00:00:00 2001 From: Pavel Milkevich Date: Fri, 9 Feb 2024 11:23:52 +0100 Subject: [PATCH 2/2] Split exports to client/server/common #474 - fixed client-side env vars - fixed tests --- src/common/env.ts | 4 +- test/guillotine/fetchContent.test.ts | 24 +++--- .../fetchContentPathsForAllLocales.test.ts | 4 +- test/guillotine/fetchFromApi.test.ts | 22 ++---- test/guillotine/fetchGuillotine.test.ts | 41 ++++------ test/index.test.client.ts | 62 +++++++++++++++ test/index.test.ts | 76 ++++++++----------- 7 files changed, 130 insertions(+), 103 deletions(-) create mode 100644 test/index.test.client.ts diff --git a/src/common/env.ts b/src/common/env.ts index c86b8d80..b999476e 100644 --- a/src/common/env.ts +++ b/src/common/env.ts @@ -3,7 +3,7 @@ import {ENV_VARS} from './constants'; const isServer = typeof window === 'undefined'; // IMPORTANT: -// NEXT_PUBLIC_ vars should be explicitly referenced to be made available on the client side !!! +// NEXT_PUBLIC_ vars should be explicitly referenced to be made available on the client side (substituted with constants) !!! /** URL to the guillotine API */ export const API_URL = isServer ? process.env[ENV_VARS.API_URL] : process.env['NEXT_PUBLIC_ENONIC_API']; @@ -33,7 +33,7 @@ const requiredConstants = { Object.keys(requiredConstants).forEach((key: string) => { const val = requiredConstants[key]; if (!val) { - throw new Error(`Environment variable '${key}' is missing (from .env?)`); + throw new Error(`Environment variable '${isServer ? '' : 'NEXT_PUBLIC_'}${key}' is missing (from .env?)`); } }); diff --git a/test/guillotine/fetchContent.test.ts b/test/guillotine/fetchContent.test.ts index 2ede80f8..9258dbf7 100644 --- a/test/guillotine/fetchContent.test.ts +++ b/test/guillotine/fetchContent.test.ts @@ -1,9 +1,4 @@ -import type { - Component, - Context, - GuillotineResponseJson, - RecursivePartial, -} from '../../src/types'; +import type {Component, Context, GuillotineResponseJson, RecursivePartial,} from '../../src/types'; import { @@ -24,7 +19,6 @@ import { XP_BASE_URL_HEADER, XP_REQUEST_TYPE, } from '../../src/common/constants'; -import {ENONIC_APP_NAME} from '../constants'; // import {ws} from '../testUtils'; @@ -379,7 +373,7 @@ describe('guillotine', () => { contentJson: GUILLOTINE_RESULT_CONTENT, metaJson: GUILLOTINE_RESULT_META_MINIMAL, }); - import('../../src').then((moduleName) => { + import('../../src/server').then((moduleName) => { const context: Context = { headers: { get(name: string) { @@ -506,7 +500,7 @@ describe('guillotine', () => { } }, }); - import('../../src').then(async ({fetchContent}) => { + import('../../src/server').then(async ({fetchContent}) => { const context: Context = { contentPath: '_/component/path', }; @@ -540,7 +534,7 @@ describe('guillotine', () => { contentJson: GUILLOTINE_RESULT_WITH_ERROR, // Throws before this is used metaJson: GUILLOTINE_RESULT_WITH_ERROR, }); - import('../../src').then(async ({fetchContent}) => { + import('../../src/server').then(async ({fetchContent}) => { const context: Context = { contentPath: '_/component/path', }; @@ -574,7 +568,7 @@ describe('guillotine', () => { contentJson: GUILLOTINE_RESULT_WITH_ERROR, // Throws before this is used metaJson: GUILLOTINE_RESULT_META_INCOMPLETE, }); - import('../../src').then(async ({fetchContent}) => { + import('../../src/server').then(async ({fetchContent}) => { const context: Context = { contentPath: '_/component/path', }; @@ -619,7 +613,7 @@ describe('guillotine', () => { }, }); - import('../../src').then(async ({fetchContent}) => { + import('../../src/server').then(async ({fetchContent}) => { const context: Context = { contentPath: '_/component/path', headers: { @@ -679,7 +673,7 @@ describe('guillotine', () => { } } }`; - import('../../src').then(async ({ComponentRegistry, fetchContent}) => { + Promise.all([import('../../src'), import('../../src/server')]).then(async ([{ComponentRegistry}, {fetchContent}]) => { ComponentRegistry.setCommonQuery(QUERY_COMMON); ComponentRegistry.addContentType(CATCH_ALL, { configQuery: '{catchAll contentType configQuery}', @@ -760,7 +754,7 @@ describe('guillotine', () => { metaJson: GUILLOTINE_RESULT_META, }); const BASE_URL = '/base/url'; - import('../../src').then((moduleName) => { + import('../../src/server').then((moduleName) => { const context: Context = { headers: { get(name: string) { @@ -806,4 +800,4 @@ describe('guillotine', () => { }); // it }); // describe fetchContent -}); // describe guillotine \ No newline at end of file +}); // describe guillotine diff --git a/test/guillotine/fetchContentPathsForAllLocales.test.ts b/test/guillotine/fetchContentPathsForAllLocales.test.ts index 00c58f37..37bd4243 100644 --- a/test/guillotine/fetchContentPathsForAllLocales.test.ts +++ b/test/guillotine/fetchContentPathsForAllLocales.test.ts @@ -90,7 +90,7 @@ describe('guillotine', () => { describe('fetchContentPathsForAllLocales', () => { it('works with just path', () => { const path = '/HAS_NO_EFFECT_SINCE_RESPONSE_IS_MOCKED'; - import('../../src').then((moduleName) => { + import('../../src/server').then((moduleName) => { expect(moduleName.fetchContentPathsForAllLocales(path)) .resolves.toEqual([{ "contentPath": [""], @@ -142,7 +142,7 @@ describe('guillotine', () => { }`; const path = '/HAS_NO_EFFECT_SINCE_RESPONSE_IS_MOCKED'; const countPerLocale = 1; - import('../../src').then((moduleName) => { + import('../../src/server').then((moduleName) => { expect(moduleName.fetchContentPathsForAllLocales( path, query, countPerLocale )).resolves.toEqual([{ diff --git a/test/guillotine/fetchFromApi.test.ts b/test/guillotine/fetchFromApi.test.ts index d33c3df1..3378a77f 100644 --- a/test/guillotine/fetchFromApi.test.ts +++ b/test/guillotine/fetchFromApi.test.ts @@ -1,17 +1,7 @@ -import type { - ContentApiBaseBody, - Context, - ProjectLocaleConfig -} from '../../src/types'; +import type {ContentApiBaseBody, ProjectLocaleConfig} from '../../src/types'; -import { - beforeEach, - describe, - expect, - jest, - test as it -} from '@jest/globals'; -import { afterEach } from 'node:test'; +import {beforeEach, describe, expect, jest, test as it} from '@jest/globals'; +import {afterEach} from 'node:test'; globalThis.console = { @@ -98,7 +88,7 @@ describe('guillotine', () => { describe('fetchFromApi', () => { it('returns query results', () => { - import('../../src').then((moduleName) => { + import('../../src/server').then((moduleName) => { moduleName.fetchFromApi(...FETCH_FROM_API_PARAMS_VALID).then((result) => { // console.debug(result); expect(result).toEqual({ @@ -118,7 +108,7 @@ describe('guillotine', () => { jest.spyOn(globalThis, 'fetch').mockImplementation(() => { throw new Error('fetch error'); }); - import('../../src').then((moduleName) => { + import('../../src/server').then((moduleName) => { expect(() => moduleName.fetchFromApi(...FETCH_FROM_API_PARAMS_VALID)).rejects.toThrow(Error(JSON.stringify({ code: 'API', message: 'fetch error' @@ -126,4 +116,4 @@ describe('guillotine', () => { }); }); }); // fetchFromApi -}); \ No newline at end of file +}); diff --git a/test/guillotine/fetchGuillotine.test.ts b/test/guillotine/fetchGuillotine.test.ts index 3a1e9488..b2760786 100644 --- a/test/guillotine/fetchGuillotine.test.ts +++ b/test/guillotine/fetchGuillotine.test.ts @@ -1,17 +1,8 @@ -import type { - ContentApiBaseBody, - ProjectLocaleConfig -} from '../../src/types'; +import type {ContentApiBaseBody, ProjectLocaleConfig} from '../../src/types'; -import { - beforeEach, - describe, - expect, - jest, - test as it -} from '@jest/globals'; -import { afterEach } from 'node:test'; +import {beforeEach, describe, expect, jest, test as it} from '@jest/globals'; +import {afterEach} from 'node:test'; globalThis.console = { @@ -103,7 +94,7 @@ describe('guillotine', () => { describe('fetchGuillotine', () => { it('fetches a response from guillotine', () => { - import('../../src').then((moduleName) => { + import('../../src/server').then((moduleName) => { moduleName.fetchGuillotine(...FETCH_GUILLOTINE_PARAMS_VALID).then((result) => { expect(result).toEqual({ guillotine: { @@ -125,7 +116,7 @@ describe('guillotine', () => { FETCH_GUILLOTINE_PARAMS_VALID[2], FETCH_GUILLOTINE_PARAMS_VALID[3], ] - import('../../src').then((moduleName) => { + import('../../src/server').then((moduleName) => { moduleName.fetchGuillotine(contentApiUrl, body, projectConfig, headers).then((result) => { expect(result).toEqual({ guillotine: { @@ -148,7 +139,7 @@ describe('guillotine', () => { status: 200 } as Response); }); - import('../../src').then((moduleName) => { + import('../../src/server').then((moduleName) => { moduleName.fetchGuillotine(...FETCH_GUILLOTINE_PARAMS_VALID).then((result) => { expect(result).toEqual({ error: { @@ -172,7 +163,7 @@ describe('guillotine', () => { FETCH_GUILLOTINE_PARAMS_VALID[2], FETCH_GUILLOTINE_PARAMS_VALID[3], ] - import('../../src').then((moduleName) => { + import('../../src/server').then((moduleName) => { moduleName.fetchGuillotine(contentApiUrl, body, projectConfig, headers).then((result) => { expect(result).toEqual({ guillotine: { @@ -195,7 +186,7 @@ describe('guillotine', () => { FETCH_GUILLOTINE_PARAMS_VALID[2], FETCH_GUILLOTINE_PARAMS_VALID[3] ] - import('../../src').then((moduleName) => { + import('../../src/server').then((moduleName) => { moduleName.fetchGuillotine(contentApiUrl, body, projectConfig, headers).then((result) => { expect(result).toEqual({ error: { @@ -217,7 +208,7 @@ describe('guillotine', () => { FETCH_GUILLOTINE_PARAMS_VALID[2], FETCH_GUILLOTINE_PARAMS_VALID[3], ] - import('../../src').then((moduleName) => { + import('../../src/server').then((moduleName) => { moduleName.fetchGuillotine(contentApiUrl, body, projectConfig, headers).then((result) => { expect(result).toEqual({ error: { @@ -241,7 +232,7 @@ describe('guillotine', () => { status: 200 } as Response); }); - import('../../src').then((moduleName) => { + import('../../src/server').then((moduleName) => { moduleName.fetchGuillotine(...FETCH_GUILLOTINE_PARAMS_VALID).then((result) => { expect(result).toEqual({ error: { @@ -257,7 +248,7 @@ describe('guillotine', () => { jest.spyOn(globalThis, 'fetch').mockImplementation(() => { throw new Error('fetch error'); }); - import('../../src').then((moduleName) => { + import('../../src/server').then((moduleName) => { expect(moduleName.fetchGuillotine(...FETCH_GUILLOTINE_PARAMS_VALID)).resolves.toEqual({ error: { code: 'API', @@ -276,7 +267,7 @@ describe('guillotine', () => { status: 500 } as Response); }); - import('../../src').then((moduleName) => { + import('../../src/server').then((moduleName) => { expect(moduleName.fetchGuillotine(...FETCH_GUILLOTINE_PARAMS_VALID)).resolves.toEqual({ error: { code: 500, @@ -294,7 +285,7 @@ describe('guillotine', () => { status: 200 } as Response); }); - import('../../src').then((moduleName) => { + import('../../src/server').then((moduleName) => { expect(moduleName.fetchGuillotine(...FETCH_GUILLOTINE_PARAMS_VALID)).resolves.toEqual({ error: { code: 500, @@ -313,7 +304,7 @@ describe('guillotine', () => { status: 200 } as Response); }); - import('../../src').then((moduleName) => { + import('../../src/server').then((moduleName) => { expect(moduleName.fetchGuillotine(...FETCH_GUILLOTINE_PARAMS_VALID)).resolves.toEqual({ error: { code: 500, @@ -331,7 +322,7 @@ describe('guillotine', () => { jest.doMock( '../../src/guillotine/fetchFromApi', () => ({fetchFromApi}) ); - import('../../src').then((moduleName) => { + import('../../src/server').then((moduleName) => { expect(moduleName.fetchGuillotine(...FETCH_GUILLOTINE_PARAMS_VALID)).resolves.toEqual({ error: { code: 'Client-side error', @@ -342,4 +333,4 @@ describe('guillotine', () => { }); }); }); // fetchGuillotine -}); \ No newline at end of file +}); diff --git a/test/index.test.client.ts b/test/index.test.client.ts new file mode 100644 index 00000000..8b0f4abb --- /dev/null +++ b/test/index.test.client.ts @@ -0,0 +1,62 @@ +import {afterEach, beforeEach, describe, expect, jest, test as it} from '@jest/globals'; +import {ENONIC_API, ENONIC_APP_NAME, ENONIC_PROJECTS} from './constants'; + + +globalThis.console = { + error: console.error, + // error: jest.fn(), + warn: jest.fn(), + log: jest.fn(), + info: jest.fn(), + debug: jest.fn(), +} as unknown as Console; + + +describe('index (CLIENT)', () => { + + beforeEach(() => { + // nothing here + jest.resetModules(); + }); + + afterEach(() => { + // nothing here + }); + + it('returns process.env.NEXT_PUBLIC_ENONIC_APP_NAME (CLIENT)', () => { + + jest.replaceProperty(process, 'env', { + NEXT_PUBLIC_ENONIC_API: ENONIC_API, + NEXT_PUBLIC_ENONIC_PROJECTS: ENONIC_PROJECTS, + NEXT_PUBLIC_ENONIC_APP_NAME: ENONIC_APP_NAME, + }); + + import('../src').then((moduleName) => { + expect(moduleName.APP_NAME).toEqual(ENONIC_APP_NAME); + }); + }); + + it('throws when process.env.NEXT_PUBLIC_ENONIC_APP_NAME is missing (CLIENT)', () => { + + jest.replaceProperty(process, 'env', { + NEXT_PUBLIC_ENONIC_API: ENONIC_API, + NEXT_PUBLIC_ENONIC_PROJECTS: ENONIC_PROJECTS, + }); + + expect(import('../src')) + .rejects.toThrow(Error("Environment variable 'NEXT_PUBLIC_ENONIC_APP_NAME' is missing (from .env?)")); + } + ); + + it('throws when process.env.NEXT_PUBLIC_ENONIC_API is missing (CLIENT)', () => { + + jest.replaceProperty(process, 'env', { + NEXT_PUBLIC_ENONIC_PROJECTS: ENONIC_PROJECTS, + NEXT_PUBLIC_ENONIC_APP_NAME: ENONIC_APP_NAME, + }); + + expect(import('../src')) + .rejects.toThrow(Error("Environment variable 'NEXT_PUBLIC_ENONIC_API' is missing (from .env?)")); + } + ); +}); // describe index diff --git a/test/index.test.ts b/test/index.test.ts index 8514e01b..c51ba736 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -1,20 +1,5 @@ -import type {Context} from '../src/types'; - -import { - // afterAll, - afterEach, - // beforeAll, - beforeEach, - describe, - expect, - jest, - test as it -} from '@jest/globals'; -import { - ENONIC_API, - ENONIC_APP_NAME, - ENONIC_PROJECTS -} from './constants'; +import {afterEach, beforeEach, describe, expect, jest, test as it} from '@jest/globals'; +import {ENONIC_API, ENONIC_APP_NAME, ENONIC_PROJECTS} from './constants'; globalThis.console = { @@ -27,46 +12,51 @@ globalThis.console = { } as unknown as Console; -// const OLD_ENV = process.env; - - describe('index', () => { beforeEach(() => { + // nothing here + jest.resetModules(); + }); + + afterEach(() => { + // nothing here + }); + + it('returns process.env.ENONIC_APP_NAME', () => { + jest.replaceProperty(process, 'env', { - // ...OLD_ENV, ENONIC_API, ENONIC_APP_NAME, ENONIC_PROJECTS }); - }); - afterEach(() => { - // Might restore the value of replaceProperty, but since process.env is used in a module, must reset modules too. - jest.restoreAllMocks(); - jest.resetModules(); + import('../src').then((moduleName) => { + expect(moduleName.APP_NAME).toEqual('com.enonic.app.enonic'); + }); }); - describe('APP_NAME', () => { - it('returns process.env.ENONIC_APP_NAME', () => { - import('../src').then((moduleName) => { - expect(moduleName.APP_NAME).toEqual('com.enonic.app.enonic'); - }); - }); + it('throws when process.env.ENONIC_APP_NAME is missing', () => { - it('returns process.env.NEXT_PUBLIC_ENONIC_APP_NAME (when ENONIC_APP_NAME is not set)', () => { - // Might restore the value of replaceProperty, but since process.env is used in a module, must reset modules too. - jest.restoreAllMocks(); - jest.resetModules(); // Does reset replaceProperty jest.replaceProperty(process, 'env', { - // ...OLD_ENV, ENONIC_API, - ENONIC_PROJECTS, - NEXT_PUBLIC_ENONIC_APP_NAME: 'com.enonic.app.next-public', + ENONIC_PROJECTS }); - import('../src').then((moduleName) => { - expect(moduleName.APP_NAME).toEqual('com.enonic.app.next-public'); + + expect(import('../src')) + .rejects.toThrow(Error("Environment variable 'ENONIC_APP_NAME' is missing (from .env?)")); + } + ); + + it('throws when process.env.ENONIC_API is missing', () => { + + jest.replaceProperty(process, 'env', { + ENONIC_APP_NAME, + ENONIC_PROJECTS }); - }); - }); + + expect(import('../src')) + .rejects.toThrow(Error("Environment variable 'ENONIC_API' is missing (from .env?)")); + } + ); }); // describe index