From 3aea19b5490a1f34cda051ff3ace1cc4839cfb16 Mon Sep 17 00:00:00 2001 From: William Killerud Date: Tue, 23 Apr 2024 12:36:55 +0200 Subject: [PATCH] fix: generate types from JSDoc --- .github/workflows/publish.yml | 96 +- .github/workflows/test.yml | 22 +- .gitignore | 1 + .prettierrc | 16 +- README.md | 8 +- index.d.ts | 120 --- lib/asset-css.js | 52 +- lib/asset-js.js | 49 +- lib/html-document.js | 55 +- lib/html-utils.js | 52 +- lib/http-incoming.js | 66 +- lib/main.js | 44 +- lib/utils.js | 163 ++-- package.json | 119 +-- ...est.cjs => html-document.test.js.test.cjs} | 10 +- tests/asset-css.js | 362 ------- tests/asset-css.test.js | 457 +++++++++ tests/{asset-js.js => asset-js.test.js} | 0 ...html-document.js => html-document.test.js} | 85 +- tests/html-utils.js | 523 ----------- tests/html-utils.test.js | 629 +++++++++++++ tests/http-incoming.js | 241 ----- tests/http-incoming.test.js | 313 +++++++ tests/utils.js | 671 ------------- tests/utils.test.js | 880 ++++++++++++++++++ tsconfig.json | 16 + tsconfig.test.json | 9 + 27 files changed, 2854 insertions(+), 2205 deletions(-) delete mode 100644 index.d.ts rename tap-snapshots/tests/{html-document.js.test.cjs => html-document.test.js.test.cjs} (82%) delete mode 100644 tests/asset-css.js create mode 100644 tests/asset-css.test.js rename tests/{asset-js.js => asset-js.test.js} (100%) rename tests/{html-document.js => html-document.test.js} (60%) delete mode 100644 tests/html-utils.js create mode 100644 tests/html-utils.test.js delete mode 100644 tests/http-incoming.js create mode 100644 tests/http-incoming.test.js delete mode 100644 tests/utils.js create mode 100644 tests/utils.test.js create mode 100644 tsconfig.json create mode 100644 tsconfig.test.json diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 489e56c8..d7cfc837 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -1,50 +1,56 @@ name: Release and Publish on: - push: - branches: - - master - - alpha - - beta - - next + push: + branches: + - master + - alpha + - beta + - next jobs: - test: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: 20.x - - name: npm install - run: | - npm install - - name: npm lint - run: | - npm run lint - - name: npm test - run: | - npm test - - release: - name: Release - runs-on: ubuntu-latest - needs: [test] - steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: 20.x - - name: npm install - run: | - npm install - - name: npx semantic-release - run: | - npx semantic-release - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + test: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 20.x + + - name: npm install + run: npm install + + - name: npm lint + run: npm run lint + + - name: npm types + run: npm run types + + - name: npm test + run: npm test + + release: + name: Release + runs-on: ubuntu-latest + needs: [test] + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 20.x + + - name: npm install + run: npm install + + - name: npm types + run: npm run types + + - name: npx semantic-release + run: npx semantic-release + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c8d8979f..e57582c3 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -11,22 +11,20 @@ jobs: node-version: [18.x, 20.x] steps: - uses: actions/checkout@v4 + - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} + - name: npm install - run: | - npm install - env: - CI: true + run: npm install + - name: npm lint - run: | - npm run lint - env: - CI: true + run: npm run lint + + - name: npm types + run: npm run types + - name: npm test - run: | - npm test - env: - CI: true + run: npm test diff --git a/.gitignore b/.gitignore index 2efc68f7..63fda18f 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ node_modules/ *.log .vscode dist +types diff --git a/.prettierrc b/.prettierrc index 3fb27daf..ac09cddc 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,5 +1,19 @@ { "singleQuote": true, "trailingComma": "all", - "tabWidth": 4 + "tabWidth": 4, + "overrides": [ + { + "files": [ + ".prettierrc", + "*.json", + "*.yml", + ".travis.yml", + ".eslintrc" + ], + "options": { + "tabWidth": 2 + } + } + ] } diff --git a/README.md b/README.md index 256a2d10..5be4221c 100644 --- a/README.md +++ b/README.md @@ -2,14 +2,12 @@ Common generic utility methods shared by @podium modules. -[![Dependencies](https://img.shields.io/david/podium-lib/utils.svg)](https://david-dm.org/podium-lib/utils) ![GitHub Actions status](https://github.com/podium-lib/utils/workflows/Run%20Lint%20and%20Tests/badge.svg) -[![Known Vulnerabilities](https://snyk.io/test/github/podium-lib/utils/badge.svg?targetFile=package.json&style=flat-square)](https://snyk.io/test/github/podium-lib/utils?targetFile=package.json) ## Installation ```bash -$ npm install @podium/utils +npm install @podium/utils ``` ## API @@ -251,7 +249,7 @@ The method takes the following arguments: import { AssetCss, buildLinkElement } from '@podium/utils'; const css = new AssetCss({ - value: 'https://cdn.foo.com/style.css' + value: 'https://cdn.foo.com/style.css', }); const element = buildLinkElement(css); @@ -272,7 +270,7 @@ The method takes the following arguments: import { AssetJs, buildScriptElement } from '@podium/utils'; const js = new utils.AssetJs({ - value: 'https://cdn.foo.com/script.js' + value: 'https://cdn.foo.com/script.js', }); const element = utils.buildScriptElement(js); diff --git a/index.d.ts b/index.d.ts deleted file mode 100644 index 58249f6e..00000000 --- a/index.d.ts +++ /dev/null @@ -1,120 +0,0 @@ -import { IncomingMessage, ServerResponse } from 'http'; - -declare interface PodiumAsset { - readonly value: string; - prefix?: boolean; - toHTML(): string; -} - -export class AssetCss - implements - Pick< - HTMLLinkElement, - 'as' | 'disabled' | 'hreflang' | 'title' | 'media' | 'rel' | 'type' - >, - PodiumAsset -{ - constructor(options?: { - as?: string | false | null; - crossorigin?: string | null | boolean; - disabled?: boolean | '' | null; - hreflang?: string | false | null; - title?: string | false | null; - media?: string | false | null; - rel?: string | false | null; - type?: string | false | null; - value: string | false | null; - data?: Array<{ key: string; value: string }>; - strategy?: "beforeInteractive" | "afterInteractive" | "lazy"; - scope?: "content" | "fallback" | "all"; - }); - - as: string; - crossorigin: string | null; - disabled: boolean; - hreflang: string; - title: string; - media: string; - rel: string; - type: string; - readonly value: string; - data?: Array<{ key: string; value: string }>; - strategy?: "beforeInteractive" | "afterInteractive" | "lazy"; - scope?: "content" | "fallback" | "all"; - toHTML(): string; - toJSON(): Record; - toJsxAttributes(): Record; -} - -export class AssetJs - implements - Pick, - PodiumAsset -{ - constructor(options?: { - crossorigin?: string | null | boolean; - type?: string | null | false; - integrity?: string | null | false; - referrerpolicy?: string | null | false; - nomodule?: boolean | null | ''; - async?: boolean | null | ''; - defer?: boolean | null | ''; - value: string | null; - data?: Array<{ key: string; value: string }>; - strategy?: "beforeInteractive" | "afterInteractive" | "lazy"; - scope?: "content" | "fallback" | "all"; - }); - - crossorigin: string | null; - type: string; - integrity: string; - referrerpolicy: string; - nomodule: boolean; - async: boolean; - defer: boolean; - prefix?: boolean; - readonly value: string; - data?: Array<{ key: string; value: string }>; - strategy?: "beforeInteractive" | "afterInteractive" | "lazy"; - scope?: "content" | "fallback" | "all"; - toHTML(): string; - toJSON(): Record; - toJsxAttributes(): Record; -} - -export class HttpIncoming { - constructor(request: IncomingMessage, response: ServerResponse, params?: T); - - development: boolean; - - readonly response: ServerResponse; - - readonly request: IncomingMessage; - - context: any; - - readonly params: T; - - proxy: boolean; - - name: string; - - view: any; - - readonly url: URL; - - css: Array; - - js: Array; - - toJSON(): { - development: boolean; - context: any; - params: T; - proxy: boolean; - name: string; - url: URL; - css: Array; - js: Array; - }; -} diff --git a/lib/asset-css.js b/lib/asset-css.js index 26a02903..52808088 100644 --- a/lib/asset-css.js +++ b/lib/asset-css.js @@ -6,12 +6,35 @@ const inspect = Symbol.for('nodejs.util.inspect.custom'); // Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link // NOTE: Only includes attributes used for loading CSS -const toUndefined = value => { +/** + * Returns the value, or undefined if the value is false or an empty string. + * @template T + * @param {T} value + * @returns {T | undefined} + */ +const toUndefined = (value) => { if (value === false) return undefined; if (value === '') return undefined; return value; }; +/** + * @typedef {object} CssAsset + * @property {string} value + * @property {string} [type] + * @property {string} [rel] + * @property {string | boolean} [crossorigin] + * @property {string} [hreflang=""] + * @property {boolean} [prefix=false] + * @property {boolean} [disabled=false] + * @property {string} [pathname=""] + * @property {string} [title=""] + * @property {string} [media=""] + * @property {string} [as=""] + * @property {string} [strategy] + * @property {string} [scope] + */ + export default class PodiumAssetCss { #crossorigin; #pathname; @@ -26,6 +49,24 @@ export default class PodiumAssetCss { #as; #strategy; #scope; + + /** + * @constructor + * @param {object} options + * @param {boolean | string | null} [options.crossorigin] + * @param {string | null} [options.hreflang=""] + * @param {boolean | null} [options.prefix=false] + * @param {boolean | null} [options.disabled=false] + * @param {string | null} [options.pathname=""] + * @param {string | null} [options.title=""] + * @param {string | null} [options.media=""] + * @param {string | null} [options.type="text/css"] + * @param {string | null} [options.rel="stylesheet"] + * @param {string | null} [options.as=""] + * @param {string | null} [options.value] + * @param {string | null} [options.strategy] + * @param {string | null} [options.scope] + */ constructor({ crossorigin = undefined, pathname = '', @@ -160,6 +201,9 @@ export default class PodiumAssetCss { this.#scope = value; } + /** + * @returns {CssAsset} + */ toJSON() { return { crossorigin: toUndefined(this.crossorigin), @@ -172,7 +216,7 @@ export default class PodiumAssetCss { rel: this.rel, as: toUndefined(this.as), strategy: toUndefined(this.strategy), - scope: toUndefined(this.scope) + scope: toUndefined(this.scope), }; } @@ -193,7 +237,7 @@ export default class PodiumAssetCss { as: this.as, }; } - + toJsxAttributes() { return buildReactLinkAttributes(this); } @@ -201,4 +245,4 @@ export default class PodiumAssetCss { get [Symbol.toStringTag]() { return 'PodiumAssetCss'; } -}; +} diff --git a/lib/asset-js.js b/lib/asset-js.js index d53f4d82..cb0dfbfd 100644 --- a/lib/asset-js.js +++ b/lib/asset-js.js @@ -9,6 +9,12 @@ const inspect = Symbol.for('nodejs.util.inspect.custom'); // Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script // NOTE: "nonce" is deliberately left out since we do not support inline scripts +/** + * Returns the value, or undefined if the value is false, an empty array or an empty string. + * @template T + * @param {T} value + * @returns {T | undefined} + */ const toUndefined = (value) => { if (Array.isArray(value) && value.length === 0) return undefined; if (value === false) return undefined; @@ -16,6 +22,21 @@ const toUndefined = (value) => { return value; }; +/** + * @typedef {object} JavaScriptAsset + * @property {string} [referrerpolicy] + * @property {string | boolean} [crossorigin] + * @property {string} [integrity] + * @property {boolean} [nomodule] + * @property {string} value + * @property {boolean} [async] + * @property {boolean} [defer] + * @property {string} [type] + * @property {Array<{ key: string; value?: unknown }>} [data] + * @property {string} [strategy] + * @property {string} [scope] + */ + export default class PodiumAssetJs { #referrerpolicy; #crossorigin; @@ -32,20 +53,21 @@ export default class PodiumAssetJs { #scope; /** + * @constructor * @param {object} options - * @param {string} [options.referrerpolicy] - * @param {string} [options.crossorigin] - * @param {string} [options.integrity] - * @param {string} [options.pathname] - * @param {boolean} [options.nomodule=false] - * @param {boolean} [options.prefix=false] - * @param {string} [options.value] - * @param {boolean} [options.async=false] - * @param {boolean} [options.defer=false] - * @param {string} [options.type="default"] + * @param {string | null} [options.referrerpolicy] + * @param {boolean | string | null} [options.crossorigin] + * @param {string | null} [options.integrity] + * @param {string | null} [options.pathname] + * @param {boolean | null} [options.nomodule=false] + * @param {boolean | null} [options.prefix=false] + * @param {string | null} [options.value] + * @param {boolean | null} [options.async=false] + * @param {boolean | null} [options.defer=false] + * @param {string | null} [options.type="default"] * @param {array} [options.data] - * @param {string} [options.strategy] - * @param {string} [options.scope] + * @param {string | null} [options.strategy] + * @param {string | null} [options.scope] */ constructor({ referrerpolicy = '', @@ -189,6 +211,9 @@ export default class PodiumAssetJs { this.#scope = value; } + /** + * @returns {JavaScriptAsset} + */ toJSON() { return { referrerpolicy: toUndefined(this.referrerpolicy), diff --git a/lib/html-document.js b/lib/html-document.js index aa0c2537..1e40ce7d 100644 --- a/lib/html-document.js +++ b/lib/html-document.js @@ -1,29 +1,68 @@ import * as utils from './html-utils.js'; -export const document = (incoming = {}, body = '', head = '') => { +/** + * @param {import('./http-incoming.js').PodiumHttpIncoming} incoming Typically res.local.podium + * @param {string} [body] HTML content for + * @param {string} [head] HTML content for + * @returns {string} HTML document as a string + */ +export const document = (incoming, body = '', head = '') => { let scripts = incoming.js; let styles = incoming.css; - const lang = incoming.view.locale || incoming.context['podium-locale'] || incoming.context.locale || incoming.params.locale || 'en-US'; + const lang = + incoming.view.locale || + incoming.context['podium-locale'] || + incoming.context.locale || + incoming.params.locale || + 'en-US'; // backwards compatibility for scripts and styles - if (typeof incoming.js === 'string') scripts = [{ type: 'default', value: incoming.js }]; - if (typeof incoming.css === 'string') styles = [{ type: 'text/css', value: incoming.css, rel: 'stylesheet' }]; + if (typeof incoming.js === 'string') + scripts = [{ type: 'default', value: incoming.js }]; + if (typeof incoming.css === 'string') + styles = [{ type: 'text/css', value: incoming.css, rel: 'stylesheet' }]; - return ` + return /* html */ ` ${styles.map(utils.buildLinkElement).join('\n ')} - ${scripts.filter((script) => script.strategy === "beforeInteractive").map(utils.buildScriptElement).join('\n ')} + ${scripts + .filter( + (script) => + typeof script !== 'string' && + script.strategy === 'beforeInteractive', + ) + .map(utils.buildScriptElement) + .join('\n ')} ${incoming.view.title ? incoming.view.title : ''} ${head} ${body} - ${scripts.filter((script) => script.strategy === "afterInteractive" || !script.strategy).map(utils.buildScriptElement).join('\n ')} - ${scripts.filter((script) => script.strategy === "lazy").map((script) => ``).join('\n ')} + ${scripts + .filter( + (script) => + typeof script === 'string' || + script.strategy === 'afterInteractive' || + !script.strategy, + ) + .map(utils.buildScriptElement) + .join('\n ')} + ${scripts + .filter( + (script) => + typeof script !== 'string' && script.strategy === 'lazy', + ) + .map( + (script) => + ``, + ) + .join('\n ')} `; }; diff --git a/lib/html-utils.js b/lib/html-utils.js index 2eb23de2..70bfb1ba 100644 --- a/lib/html-utils.js +++ b/lib/html-utils.js @@ -1,13 +1,21 @@ /* eslint-disable no-restricted-syntax */ +/** + * @param {unknown} value + * @returns {boolean} + */ export const notEmpty = (value) => { - if (value === false) return value; + if (value === false) return false; if (value === undefined) return false; if (value === null) return false; if (value !== '') return true; return false; }; +/** + * @param {import("./asset-js.js").JavaScriptAsset} obj + * @returns {Array<{ key: string; value?: unknown; }>} + */ export const buildScriptAttributes = (obj) => { const args = []; // lazy uses dynamic import in the script body, everything else uses the src attribute @@ -16,7 +24,11 @@ export const buildScriptAttributes = (obj) => { } // ESM and module are valid "module" types. Lazy requires type="module" so we set it here. - if (obj.type === 'esm' || obj.type === 'module' || obj.strategy === 'lazy') { + if ( + obj.type === 'esm' || + obj.type === 'module' || + obj.strategy === 'lazy' + ) { args.push({ key: 'type', value: 'module' }); } @@ -54,6 +66,12 @@ export const buildScriptAttributes = (obj) => { return args; }; +/** + * Transforms HTML attribute names to JSX compatible camelCased props. + * + * @param {import("./asset-js.js").JavaScriptAsset} obj + * @returns {object} + */ export const buildReactScriptAttributes = (obj) => { const attrs = {}; for (const { key, value } of buildScriptAttributes(obj)) { @@ -64,16 +82,24 @@ export const buildReactScriptAttributes = (obj) => { else attrs[key] = value; } return attrs; -} +}; +/** + * @param {import("./asset-js.js").JavaScriptAsset} obj + * @returns {string} + */ export const buildScriptElement = (obj) => { - const attrs = buildScriptAttributes(obj).map(({key, value}) => { + const attrs = buildScriptAttributes(obj).map(({ key, value }) => { if (!value && value !== '') return key; return `${key}="${value}"`; - }) + }); return ``; }; +/** + * @param {import("./asset-css.js").CssAsset} obj + * @returns {Array<{ key: string; value?: unknown; }>} + */ export const buildLinkAttributes = (obj) => { const args = []; args.push({ key: 'href', value: obj.value }); @@ -114,6 +140,12 @@ export const buildLinkAttributes = (obj) => { return args; }; +/** + * Transforms HTML attribute names to JSX compatible camelCased props. + * + * @param {import("./asset-css.js").CssAsset} obj + * @returns {object} + */ export const buildReactLinkAttributes = (obj) => { const attrs = {}; for (const { key, value } of buildLinkAttributes(obj)) { @@ -123,12 +155,16 @@ export const buildReactLinkAttributes = (obj) => { else attrs[key] = value; } return attrs; -} +}; +/** + * @param {import("./asset-css.js").CssAsset} obj + * @returns {string} + */ export const buildLinkElement = (obj) => { - const attrs = buildLinkAttributes(obj).map(({key, value}) => { + const attrs = buildLinkAttributes(obj).map(({ key, value }) => { if (!value && value !== '') return key; return `${key}="${value}"`; - }) + }); return ``; }; diff --git a/lib/http-incoming.js b/lib/http-incoming.js index 06ac7666..93f43dc8 100644 --- a/lib/http-incoming.js +++ b/lib/http-incoming.js @@ -2,13 +2,26 @@ import { URL } from 'node:url'; const inspect = Symbol.for('nodejs.util.inspect.custom'); -const urlFromRequest = request => { +const urlFromRequest = (request) => { const protocol = request?.protocol || 'http'; const host = request?.headers?.host || 'localhost'; const url = request?.url || ''; return new URL(url, `${protocol.replace(/:/, '')}://${host}`); }; +/** + * @typedef {object} PodiumHttpIncoming + * @property {string} name + * @property {object} context + * @property {object} view + * @property {URL} url + * @property {Array} css + * @property {Array} js + * @property {object} [params] + * @property {boolean} [development] + * @property {boolean} [proxy] + */ + export default class HttpIncoming { #development; #response; @@ -18,9 +31,32 @@ export default class HttpIncoming { #proxy; #name; #view; + + /** + * @type {URL | undefined} + */ #url; + + /** + * @type {Array} + */ #css; + + /** + * @type {Array} + */ #js; + + /** + * @param {object} [request={}] The incoming HTTP request + * @param {object} [response={}] The HTTP response + * @param {object} [params={}] Parameters such as locale. Typically res.locals. + * + * @example + * ```js + * const incoming = new HttpIncoming(req, res, res.locals); + * ``` + */ constructor(request = {}, response = {}, params = {}) { this.#development = false; this.#response = response; @@ -33,7 +69,6 @@ export default class HttpIncoming { this.#url = undefined; this.#css = []; this.#js = []; - } set development(value) { @@ -44,6 +79,9 @@ export default class HttpIncoming { return this.#development; } + /** + * @throws {Error} Read only-property + */ set response(value) { throw new Error('Cannot set read-only property.'); } @@ -68,24 +106,33 @@ export default class HttpIncoming { return this.#context; } + // No way to type writeonly properly at time of writing: https://github.com/microsoft/TypeScript/issues/21759 + // This is a workaround for a type error where TS complains we're trying to set a value to something that is type void. + /** + * @type {unknown} + */ set podlets(value) { const podlets = Array.isArray(value) ? value : [value]; - podlets.forEach(podlet => { + podlets.forEach((podlet) => { if (podlet.css) { - podlet.css.forEach(item => { + podlet.css.forEach((item) => { this.#css.push(item); }); } if (podlet.js) { - podlet.js.forEach(item => { + podlet.js.forEach((item) => { this.#js.push(item); }); } }); } + /** + * @type {unknown} + * @throws This prop is write only. Read from {@link css} and {@link js}. + */ get podlets() { throw new Error( `The getter for .podlets is reserved for later implementation`, @@ -130,7 +177,7 @@ export default class HttpIncoming { get url() { if (this.#url) return this.#url; - return urlFromRequest(this.#request);; + return urlFromRequest(this.#request); } set css(value) { @@ -153,6 +200,9 @@ export default class HttpIncoming { return this.#js; } + /** + * @returns {PodiumHttpIncoming} + */ toJSON() { return { development: this.development, @@ -182,8 +232,8 @@ export default class HttpIncoming { js: this.js, }; } - + get [Symbol.toStringTag]() { return 'PodiumHttpIncoming'; } -}; +} diff --git a/lib/main.js b/lib/main.js index 652db9ee..624f58e9 100644 --- a/lib/main.js +++ b/lib/main.js @@ -1,15 +1,41 @@ -export { default as HttpIncoming } from './http-incoming.js'; -export { default as AssetCss } from './asset-css.js'; -export { default as AssetJs } from './asset-js.js'; +import HttpIncoming from './http-incoming.js'; +import AssetCss from './asset-css.js'; +import AssetJs from './asset-js.js'; +import { document as template } from './html-document.js'; +import { buildScriptElement, buildLinkElement } from './html-utils.js'; +import { + duplicateOnLocalsPodium, + uriRelativeToAbsolute, + getFromLocalsPodium, + deserializeContext, + setAtLocalsPodium, + serializeContext, + pathnameBuilder, + uriIsRelative, + uriBuilder, + isFunction, + isString, +} from './utils.js'; + +/** + * @typedef {import('./asset-css.js').CssAsset} PodiumCssAsset + */ -export { document as template } from './html-document.js'; +/** + * @typedef {import('./asset-js.js').JavaScriptAsset} PodiumJavaScriptAsset + */ -export { - buildScriptElement, - buildLinkElement, -} from './html-utils.js'; +/** + * @typedef {import('./http-incoming.js').PodiumHttpIncoming} PodiumHttpIncoming + */ export { + HttpIncoming, + AssetCss, + AssetJs, + template, + buildLinkElement, + buildScriptElement, duplicateOnLocalsPodium, uriRelativeToAbsolute, getFromLocalsPodium, @@ -21,4 +47,4 @@ export { uriBuilder, isFunction, isString, -} from './utils.js'; +}; diff --git a/lib/utils.js b/lib/utils.js index 088892da..4aecb81f 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -1,38 +1,33 @@ import camelcase from 'camelcase'; -import { URL } from 'url'; +import { URL } from 'node:url'; /** - * Checks if a value is a string + * Checks if a value is a string. * - * @param {*} str A value to check - * - * @returns {Boolean} + * @param {unknown} [str] A value to check + * @returns {boolean} */ -export const isString = str => typeof str === 'string'; +export const isString = (str) => typeof str === 'string'; /** - * Checks if a value is a function - * - * @param {*} fn A value to check + * Checks if a value is a function. * - * @returns {Boolean} + * @param {unknown} [fn] A value to check + * @returns {boolean} */ -export const isFunction = fn => { +export const isFunction = (fn) => { const type = {}.toString.call(fn); return type === '[object Function]' || type === '[object AsyncFunction]'; }; /** - * Constructs an pathname out of pathname fragments. - * Will wash out duplicate "/" characters and return a pathname - * without a "/" at end of the pathname. If the first input starts - * with a "/" it will be perserved. - * - * @param {String|Array} args Pathnames to combine. + * Constructs a pathname out of pathname fragments. Will remove duplicate "/" + * characters and return a pathname without a trailing "/". If the first + * argument starts with a "/" it will be perserved. * - * @returns {String} an pathname + * @param {string[] | Array} args Pathnames to combine. + * @returns {string} The combined pathname. */ - export const pathnameBuilder = (...args) => { const separator = '/'; let prefixCheck = true; @@ -50,14 +45,14 @@ export const pathnameBuilder = (...args) => { } return false; }) - .map(arg => { + .map((arg) => { if (prefixCheck) { prefixCheck = false; if (arg.charAt(0) === separator) { prefix = separator; } } - return arg.split(separator).filter(item => item); + return arg.split(separator).filter((item) => item); }) .flat(); @@ -68,43 +63,37 @@ export const pathnameBuilder = (...args) => { * Constructs an absolute URI out of a absolute manifest URI * and a relative URI. * - * @param {String} input Relative URI - * @param {String} base Absolute manifest URI to append the input too - * @param {String} extra Relative path to be appended at the end of the URI - * - * @returns {String} an absolute URI + * @param {string} input Relative URI. + * @param {string} base Absolute manifest URI to append the input too. + * @param {string} [extra] Relative path to be appended at the end of the URI. + * @returns {string} An absolute URI. */ - export const uriBuilder = (input = '', base = '', extra = '') => { const uriObj = new URL(base); const basePath = uriObj.pathname .split('/') - .filter(item => item && !item.includes('.json')); + .filter((item) => item && !item.includes('.json')); uriObj.pathname = pathnameBuilder(basePath, input, extra); return uriObj.toString(); }; /** - * Checks if a URI is relative + * Checks if a URI is relative. * - * @param {String} uri A URI to check - * - * @returns {Boolean} + * @param {string} uri A URI to check + * @returns {boolean} */ - -export const uriIsRelative = uri => uri.substr(0, 4) !== 'http'; +export const uriIsRelative = (uri) => uri.substr(0, 4) !== 'http'; /** * Check if a URI is absolute or relative and if relative compose an * absolute URI out of a absolute mainfest URI. * - * @param {String} input Relative or absolute URI - * @param {String} base Absolute manifest URI to append the possible relative input too - * @param {String} extra Relative path to be appended at the end of the URI - * - * @returns {String} an absolute URI + * @param {string} input Relative or absolute URI. + * @param {string} base Absolute manifest URI to append the possible relative input to. + * @param {string} extra Relative path to be appended at the end of the URI. + * @returns {string} An absolute URI. */ - export const uriRelativeToAbsolute = (input = '', base = '', extra = '') => { if (uriIsRelative(input)) { return uriBuilder(input, base, extra); @@ -113,19 +102,17 @@ export const uriRelativeToAbsolute = (input = '', base = '', extra = '') => { }; /** - * Set a value on a property on .locals.podium on a http response object. + * Set a value on a property on .locals.podium on an HTTP response object. * Ensures that .locals.podium exists on the http response object. * * If property is not provided, .locals.podium will be created, if not * already existing, on the response object. * - * @param {Object} response A http response object - * @param {String} property Property for the value - * @param {*} value Value to store on the property - * - * @returns {Object} The http response object + * @param {object} [response] An HTTP response object. + * @param {string} [property] Property for the value. + * @param {unknown} [value] Value to store on the property. + * @returns {object} The http response object */ - export const setAtLocalsPodium = (response = {}, property, value) => { if (!response.locals) { response.locals = {}; @@ -143,15 +130,13 @@ export const setAtLocalsPodium = (response = {}, property, value) => { }; /** - * Get the value from a property on .locals.podium on a http response object. + * Get the value from a property on .locals.podium on an HTTP response object. * Ensures that .locals.podium exists on the http response object. * - * @param {Object} response A http response object - * @param {String} property Property for the value - * - * @returns {Object} The property, or `null` if it does not exist + * @param {object} [response] An HTTP response object. + * @param {string} [property] Property for the value. + * @returns {object | null} The property, or `null` if it does not exist. */ - export const getFromLocalsPodium = (response = {}, property) => { if (!response.locals) { return null; @@ -169,17 +154,18 @@ export const getFromLocalsPodium = (response = {}, property) => { }; /** - * Get the value from a property on .locals.podium on a http response object - * and sets its value on another key. + * Copies the value from one property to another on .locals.podium on an HTTP response object. * - * @param {Object} response A http response object - * @param {String} fromProperty Property for the existent value - * @param {String} toProperty Property for the duplicated value - * - * @returns {Object} The http response object + * @param {object} response An HTTP response object. + * @param {string} [fromProperty] Property for the existent value. + * @param {string} [toProperty] Property for the duplicated value. + * @returns {object} The updated HTTP response object. */ - -export const duplicateOnLocalsPodium = (response = {}, fromProperty, toProperty) => +export const duplicateOnLocalsPodium = ( + response = {}, + fromProperty, + toProperty, +) => setAtLocalsPodium( response, toProperty, @@ -187,18 +173,28 @@ export const duplicateOnLocalsPodium = (response = {}, fromProperty, toProperty) ); /** - * Serialize a context object into a http header object - * - * @param {Object} headers A http headers object the context will be copied into. - * @param {Object} context A contect object to copy from - * @param {*} arg An argument value passed on to the function if a context value is a function - * - * @returns {Object} A http header object + * Serialize a context object into an HTTP header object, calling the function if a context value is callable. + * + * @param {object} [headers={}] An HTTP headers object the context will be copied to. + * @param {object} [context={}] A context object to copy from. + * @param {unknown} [arg=""] An argument value passed on to the function if a context value is a function. + * @returns {object} An object with deserialized context properties and values. + * + * @see {@link deserializeContext} + * + * @example + * ```js + * let headers = {}; + * const context = { + * 'podium-public-pathname': 'podium-resource', + * }; + * headers = serializeContext(headers, context); + * // headers: { 'podium-public-pathname': 'podium-resource' } + * ``` */ - export const serializeContext = (headers = {}, context = {}, arg = '') => { const localHeaders = headers; - Object.keys(context).forEach(key => { + Object.keys(context).forEach((key) => { if (isString(context[key])) { localHeaders[key] = context[key]; } @@ -211,17 +207,26 @@ export const serializeContext = (headers = {}, context = {}, arg = '') => { }; /** - * Deserialize a context object from a http header object - * - * @param {Object} headers A http headers object the context will be extracted from. - * @param {String} prefix The prefix used to mark what properties are context properties - * - * @returns {Object} A object containing context properties and values + * Deserialize a context object from an HTTP header object. + * + * @param {object} [headers={}] An HTTP headers object the context will be extracted from. + * @param {string} [prefix="podium"] The prefix used to mark what properties are context properties. + * @returns {object} An object with deserialized context properties and values. + * + * @see {@link serializeContext} + * + * @example + * ```js + * const headers = { + * 'podium-public-pathname': 'podium-resource', + * }; + * const context = deserializeContext(headers); + * // { publicPathname: 'podium-resource' } + * ``` */ - export const deserializeContext = (headers = {}, prefix = 'podium') => { const context = {}; - Object.keys(headers).forEach(key => { + Object.keys(headers).forEach((key) => { if (key.startsWith(prefix)) { const k = camelcase(key.replace(`${prefix}-`, '')); context[k] = headers[key]; diff --git a/package.json b/package.json index 379be20a..ffef0206 100644 --- a/package.json +++ b/package.json @@ -1,60 +1,63 @@ { - "name": "@podium/utils", - "version": "5.0.3", - "description": "Common generic utility methods shared by @podium modules.", - "type": "module", - "license": "MIT", - "keywords": [ - "micro services", - "micro frontend", - "components", - "podium" - ], - "repository": { - "type": "git", - "url": "git@github.com:podium-lib/utils.git" - }, - "bugs": { - "url": "https://github.com/podium-lib/issues" - }, - "homepage": "https://podium-lib.io/", - "files": [ - "package.json", - "CHANGELOG.md", - "index.d.ts", - "README.md", - "LICENSE", - "dist", - "lib" - ], - "main": "./lib/main.js", - "types": "index.d.ts", - "scripts": { - "lint": "eslint .", - "lint:fix": "eslint --fix .", - "test": "tap --no-check-coverage", - "test:snapshots:update": "tap --snapshot", - "bench": "node benchmark/benchmark.js" - }, - "devDependencies": { - "@semantic-release/git": "10.0.1", - "@semantic-release/changelog": "6.0.3", - "@semantic-release/commit-analyzer": "11.1.0", - "@semantic-release/github": "9.2.6", - "@semantic-release/npm": "11.0.3", - "@semantic-release/release-notes-generator": "12.1.0", - "benchmark": "2.1.4", - "eslint": "8.57.0", - "eslint-config-airbnb-base": "15.0.0", - "eslint-config-prettier": "9.1.0", - "eslint-plugin-import": "2.29.1", - "eslint-plugin-prettier": "5.1.3", - "@babel/eslint-parser": "7.24.1", - "tap": "16.3.10", - "prettier": "3.2.5", - "@podium/schemas": "5.0.0" - }, - "dependencies": { - "camelcase": "8.0.0" - } + "name": "@podium/utils", + "version": "5.0.3", + "description": "Common generic utility methods shared by @podium modules.", + "type": "module", + "license": "MIT", + "keywords": [ + "micro services", + "micro frontend", + "components", + "podium" + ], + "repository": { + "type": "git", + "url": "git@github.com:podium-lib/utils.git" + }, + "bugs": { + "url": "https://github.com/podium-lib/issues" + }, + "homepage": "https://podium-lib.io/", + "files": [ + "package.json", + "CHANGELOG.md", + "README.md", + "LICENSE", + "dist", + "lib", + "types.d.ts" + ], + "main": "./lib/main.js", + "types": "./types/main.d.ts", + "scripts": { + "bench": "node benchmark/benchmark.js", + "lint": "eslint .", + "lint:fix": "eslint --fix .", + "test": "tap --no-check-coverage && tsc --project tsconfig.test.json", + "test:snapshots:update": "tap --snapshot", + "types": "tsc --declaration --emitDeclarationOnly" + }, + "devDependencies": { + "@babel/eslint-parser": "7.24.1", + "@podium/schemas": "5.0.0", + "@semantic-release/changelog": "6.0.3", + "@semantic-release/commit-analyzer": "11.1.0", + "@semantic-release/git": "10.0.1", + "@semantic-release/github": "9.2.6", + "@semantic-release/npm": "11.0.3", + "@semantic-release/release-notes-generator": "12.1.0", + "@types/node": "20.12.7", + "benchmark": "2.1.4", + "eslint": "8.57.0", + "eslint-config-airbnb-base": "15.0.0", + "eslint-config-prettier": "9.1.0", + "eslint-plugin-import": "2.29.1", + "eslint-plugin-prettier": "5.1.3", + "prettier": "3.2.5", + "tap": "16.3.10", + "typescript": "5.4.5" + }, + "dependencies": { + "camelcase": "8.0.0" + } } diff --git a/tap-snapshots/tests/html-document.js.test.cjs b/tap-snapshots/tests/html-document.test.js.test.cjs similarity index 82% rename from tap-snapshots/tests/html-document.js.test.cjs rename to tap-snapshots/tests/html-document.test.js.test.cjs index bbdd3f0a..bd62bff9 100644 --- a/tap-snapshots/tests/html-document.js.test.cjs +++ b/tap-snapshots/tests/html-document.test.js.test.cjs @@ -5,7 +5,7 @@ * Make sure to inspect the output below. Do not ignore changes! */ 'use strict' -exports[`tests/html-document.js TAP .document() - "type" is "module", "strategy" is set - should place assets based on strategy > must match snapshot 1`] = ` +exports[`tests/html-document.test.js TAP .document() - "type" is "module", "strategy" is set - should place assets based on strategy > must match snapshot 1`] = ` @@ -25,7 +25,7 @@ exports[`tests/html-document.js TAP .document() - "type" is "module", "strategy" ` -exports[`tests/html-document.js TAP .document() - arguments given - handles v4 js and css syntax > should render template using values given 1`] = ` +exports[`tests/html-document.test.js TAP .document() - arguments given - handles v4 js and css syntax > should render template using values given 1`] = ` @@ -49,7 +49,7 @@ exports[`tests/html-document.js TAP .document() - arguments given - handles v4 j ` -exports[`tests/html-document.js TAP .document() - arguments given > should render template using values given 1`] = ` +exports[`tests/html-document.test.js TAP .document() - arguments given > should render template using values given 1`] = ` @@ -69,7 +69,7 @@ exports[`tests/html-document.js TAP .document() - arguments given > should rende ` -exports[`tests/html-document.js TAP .document() - js "type" is "esm" > should set type to module on script tags 1`] = ` +exports[`tests/html-document.test.js TAP .document() - js "type" is "esm" > should set type to module on script tags 1`] = ` @@ -93,7 +93,7 @@ exports[`tests/html-document.js TAP .document() - js "type" is "esm" > should se ` -exports[`tests/html-document.js TAP .document() - no arguments given > should render template 1`] = ` +exports[`tests/html-document.test.js TAP .document() - no arguments given > should render template 1`] = ` diff --git a/tests/asset-css.js b/tests/asset-css.js deleted file mode 100644 index 6550a12c..00000000 --- a/tests/asset-css.js +++ /dev/null @@ -1,362 +0,0 @@ -import tap from 'tap'; -import * as schema from '@podium/schemas'; -import AssetCss from '../lib/asset-css.js'; - -tap.test('Css() - object tag - should be PodiumAssetCss', (t) => { - const obj = new AssetCss({ value: '/foo' }); - t.equal(Object.prototype.toString.call(obj), '[object PodiumAssetCss]'); - t.end(); -}); - -tap.test('Css() - no value given to "value" argument', (t) => { - t.plan(1); - t.throws(() => { - const obj = new AssetCss(); // eslint-disable-line no-unused-vars - }, /Value for argument variable "value", "undefined", is not valid/, 'Should throw'); - t.end(); -}); - -tap.test('Css() - no arguments given - should construct object with default values', (t) => { - const obj = new AssetCss({ value: '/foo' }); - t.equal(obj.crossorigin, undefined); - t.notOk(obj.disabled); - t.equal(obj.hreflang, ''); - t.equal(obj.value, '/foo'); - t.equal(obj.title, ''); - t.equal(obj.media, ''); - t.equal(obj.type, 'text/css'); - t.equal(obj.href, '/foo'); - t.equal(obj.rel, 'stylesheet'); - t.equal(obj.as, ''); - t.end(); -}); - -tap.test('Css() - no arguments given - should construct JSON with default values', (t) => { - const obj = new AssetCss({ value: '/foo' }); - const json = JSON.parse(JSON.stringify(obj)); - t.same(json, { - value: '/foo', - type: 'text/css', - rel: 'stylesheet', - }); - t.end(); -}); - -tap.test('Css() - pathname is given - prefix is unset - should NOT append pathname to "value"', (t) => { - const obj = new AssetCss({ value: '/foo', pathname: '/bar' }); - t.equal(obj.value, '/foo'); - t.equal(obj.href, '/foo'); - t.end(); -}); - -tap.test('Css() - pathname is given - prefix is false - should NOT append pathname to "value"', (t) => { - const obj = new AssetCss({ value: '/foo', pathname: '/bar', prefix: false }); - t.equal(obj.value, '/foo'); - t.equal(obj.href, '/foo'); - t.end(); -}); - -tap.test('Css() - pathname is given - prefix is true - should append pathname to "value"', (t) => { - const obj = new AssetCss({ value: '/foo', pathname: '/bar', prefix: true }); - t.equal(obj.value, '/bar/foo'); - t.equal(obj.href, '/bar/foo'); - t.end(); -}); - -tap.test('Css() - pathname is given - prefix is unset - should NOT append pathname to "value" for toJSON()', (t) => { - const obj = new AssetCss({ value: '/foo', pathname: '/bar' }); - const json = JSON.parse(JSON.stringify(obj)); - t.same(json, { - value: '/foo', - type: 'text/css', - rel: 'stylesheet', - }); - t.end(); -}); - -tap.test('Css() - pathname is given - prefix is false - should NOT append pathname to "value" for toJSON()', (t) => { - const obj = new AssetCss({ value: '/foo', pathname: '/bar', prefix: false }); - const json = JSON.parse(JSON.stringify(obj)); - t.same(json, { - value: '/foo', - type: 'text/css', - rel: 'stylesheet', - }); - t.end(); -}); - -tap.test('Css() - pathname is given - prefix is true - should NOT append pathname to "value" for toJSON()', (t) => { - const obj = new AssetCss({ value: '/foo', pathname: '/bar', prefix: true }); - const json = JSON.parse(JSON.stringify(obj)); - t.same(json, { - value: '/foo', - type: 'text/css', - rel: 'stylesheet', - }); - t.end(); -}); - -tap.test('Css() - pathname is given - prefix is unset - should NOT append pathname to "href" for toHTML()', (t) => { - const obj = new AssetCss({ value: '/foo', pathname: '/bar' }); - t.equal(obj.toHTML(), ''); - t.end(); -}); - -tap.test('Css() - pathname is given - prefix is false - should NOT append pathname to "href" for toHTML()', (t) => { - const obj = new AssetCss({ value: '/foo', pathname: '/bar', prefix: false }); - t.equal(obj.value, '/foo'); - t.equal(obj.toHTML(), ''); - t.end(); -}); - -tap.test('Css() - pathname is given - prefix is true - should append pathname to "href" for toHTML()', (t) => { - const obj = new AssetCss({ value: '/foo', pathname: '/bar', prefix: true }); - t.equal(obj.toHTML(), ''); - t.end(); -}); - -tap.test('Css() - value if absoulte - pathname is given - prefix is true - should NOT append pathname to "value"', (t) => { - const obj = new AssetCss({ - value: 'http://somewhere.else.com/foo', - pathname: '/bar', - prefix: true, - }); - t.equal(obj.value, 'http://somewhere.else.com/foo'); - t.equal(obj.href, 'http://somewhere.else.com/foo'); - - const json = JSON.parse(JSON.stringify(obj)); - t.same(json, { - value: 'http://somewhere.else.com/foo', - type: 'text/css', - rel: 'stylesheet', - }); - - t.equal(obj.toHTML(), ''); - t.end(); -}); - -tap.test('Css() - set "crossorigin" - should construct object as expected', (t) => { - const obj = new AssetCss({ - value: '/foo', - }); - - obj.crossorigin = 'bar'; - - t.equal(obj.crossorigin, 'bar'); - t.equal(obj.toHTML(), - '', - ); - - const json = JSON.parse(JSON.stringify(obj)); - t.same(json, { - crossorigin: 'bar', - value: '/foo', - type: 'text/css', - rel: 'stylesheet', - }); - - const repl = new AssetCss(json); - t.equal(repl.crossorigin, 'bar'); - t.end(); -}); - -tap.test('Css() - set "disabled" - should construct object as expected', (t) => { - const obj = new AssetCss({ - value: '/foo', - }); - - obj.disabled = true; - - t.ok(obj.disabled); - t.equal(obj.toHTML(), - '', - ); - - const json = JSON.parse(JSON.stringify(obj)); - t.same(json, { - disabled: true, - value: '/foo', - type: 'text/css', - rel: 'stylesheet', - }); - - const repl = new AssetCss(json); - t.ok(repl.disabled); - t.end(); -}); - -tap.test('Css() - set "hreflang" - should construct object as expected', (t) => { - const obj = new AssetCss({ - value: '/foo', - }); - - obj.hreflang = 'bar'; - - t.equal(obj.hreflang, 'bar'); - t.equal(obj.toHTML(), - '', - ); - - const json = JSON.parse(JSON.stringify(obj)); - t.same(json, { - hreflang: 'bar', - value: '/foo', - type: 'text/css', - rel: 'stylesheet', - }); - - const repl = new AssetCss(json); - t.equal(repl.hreflang, 'bar'); - t.end(); -}); - -tap.test('Css() - set "title" - should construct object as expected', (t) => { - const obj = new AssetCss({ - value: '/foo', - }); - - obj.title = 'bar'; - - t.equal(obj.title, 'bar'); - t.equal(obj.toHTML(), - '', - ); - - const json = JSON.parse(JSON.stringify(obj)); - t.same(json, { - title: 'bar', - value: '/foo', - type: 'text/css', - rel: 'stylesheet', - }); - - const repl = new AssetCss(json); - t.equal(repl.title, 'bar'); - t.end(); -}); - -tap.test('Css() - set "media" - should construct object as expected', (t) => { - const obj = new AssetCss({ - value: '/foo', - }); - - obj.media = 'bar'; - - t.equal(obj.media, 'bar'); - t.equal(obj.toHTML(), - '', - ); - - const json = JSON.parse(JSON.stringify(obj)); - t.same(json, { - media: 'bar', - value: '/foo', - type: 'text/css', - rel: 'stylesheet', - }); - - const repl = new AssetCss(json); - t.equal(repl.media, 'bar'); - t.end(); -}); - -tap.test('Css() - set "type" - should construct object as expected', (t) => { - const obj = new AssetCss({ - value: '/foo', - }); - - obj.type = 'bar'; - - t.equal(obj.type, 'bar'); - t.equal(obj.toHTML(), - '', - ); - - const json = JSON.parse(JSON.stringify(obj)); - t.same(json, { - value: '/foo', - type: 'bar', - rel: 'stylesheet', - }); - - const repl = new AssetCss(json); - t.equal(repl.type, 'bar'); - t.end(); -}); - -tap.test('Css() - set "rel" - should construct object as expected', (t) => { - const obj = new AssetCss({ - value: '/foo', - }); - - obj.rel = 'bar'; - - t.equal(obj.rel, 'bar'); - t.equal(obj.toHTML(), - '', - ); - - const json = JSON.parse(JSON.stringify(obj)); - t.same(json, { - value: '/foo', - type: 'text/css', - rel: 'bar', - }); - - const repl = new AssetCss(json); - t.equal(repl.rel, 'bar'); - t.end(); -}); - -tap.test('Css() - set "as" - should construct object as expected', (t) => { - const obj = new AssetCss({ - value: '/foo', - }); - - obj.as = 'bar'; - - t.equal(obj.as, 'bar'); - t.equal(obj.toHTML(), - '', - ); - - const json = JSON.parse(JSON.stringify(obj)); - t.same(json, { - as: 'bar', - value: '/foo', - type: 'text/css', - rel: 'stylesheet', - }); - - const repl = new AssetCss(json); - t.equal(repl.as, 'bar'); - t.end(); -}); - -tap.test('Css() - set "value"', (t) => { - t.plan(1); - const obj = new AssetCss({ - value: '/foo', - }); - t.throws(() => { - obj.value = '/bar'; - }, /Cannot set read-only property./, 'Should throw'); - t.end(); -}); - -tap.test('Css() - set "href" - should throw', (t) => { - t.plan(1); - const obj = new AssetCss({ - value: '/foo', - }); - t.throws(() => { - obj.href = '/bar'; - }, /Cannot set read-only property./, 'Should throw'); - t.end(); -}); - -tap.test('Css() - validate object against schema - should validate', (t) => { - const obj = new AssetCss({ value: '/foo' }); - t.notOk(schema.css([obj]).error); - t.end(); -}); diff --git a/tests/asset-css.test.js b/tests/asset-css.test.js new file mode 100644 index 00000000..c3bbe7b5 --- /dev/null +++ b/tests/asset-css.test.js @@ -0,0 +1,457 @@ +import tap from 'tap'; +import * as schema from '@podium/schemas'; +import AssetCss from '../lib/asset-css.js'; + +tap.test('Css() - object tag - should be PodiumAssetCss', (t) => { + const obj = new AssetCss({ value: '/foo' }); + t.equal(Object.prototype.toString.call(obj), '[object PodiumAssetCss]'); + t.end(); +}); + +tap.test('Css() - no value given to "value" argument', (t) => { + t.plan(1); + t.throws( + () => { + const obj = new AssetCss(); // eslint-disable-line no-unused-vars + }, + /Value for argument variable "value", "undefined", is not valid/, + 'Should throw', + ); + t.end(); +}); + +tap.test( + 'Css() - no arguments given - should construct object with default values', + (t) => { + const obj = new AssetCss({ value: '/foo' }); + t.equal(obj.crossorigin, undefined); + t.notOk(obj.disabled); + t.equal(obj.hreflang, ''); + t.equal(obj.value, '/foo'); + t.equal(obj.title, ''); + t.equal(obj.media, ''); + t.equal(obj.type, 'text/css'); + t.equal(obj.href, '/foo'); + t.equal(obj.rel, 'stylesheet'); + t.equal(obj.as, ''); + t.end(); + }, +); + +tap.test( + 'Css() - no arguments given - should construct JSON with default values', + (t) => { + const obj = new AssetCss({ value: '/foo' }); + const json = JSON.parse(JSON.stringify(obj)); + t.same(json, { + value: '/foo', + type: 'text/css', + rel: 'stylesheet', + }); + t.end(); + }, +); + +tap.test( + 'Css() - pathname is given - prefix is unset - should NOT append pathname to "value"', + (t) => { + const obj = new AssetCss({ value: '/foo', pathname: '/bar' }); + t.equal(obj.value, '/foo'); + t.equal(obj.href, '/foo'); + t.end(); + }, +); + +tap.test( + 'Css() - pathname is given - prefix is false - should NOT append pathname to "value"', + (t) => { + const obj = new AssetCss({ + value: '/foo', + pathname: '/bar', + prefix: false, + }); + t.equal(obj.value, '/foo'); + t.equal(obj.href, '/foo'); + t.end(); + }, +); + +tap.test( + 'Css() - pathname is given - prefix is true - should append pathname to "value"', + (t) => { + const obj = new AssetCss({ + value: '/foo', + pathname: '/bar', + prefix: true, + }); + t.equal(obj.value, '/bar/foo'); + t.equal(obj.href, '/bar/foo'); + t.end(); + }, +); + +tap.test( + 'Css() - pathname is given - prefix is unset - should NOT append pathname to "value" for toJSON()', + (t) => { + const obj = new AssetCss({ value: '/foo', pathname: '/bar' }); + const json = JSON.parse(JSON.stringify(obj)); + t.same(json, { + value: '/foo', + type: 'text/css', + rel: 'stylesheet', + }); + t.end(); + }, +); + +tap.test( + 'Css() - pathname is given - prefix is false - should NOT append pathname to "value" for toJSON()', + (t) => { + const obj = new AssetCss({ + value: '/foo', + pathname: '/bar', + prefix: false, + }); + const json = JSON.parse(JSON.stringify(obj)); + t.same(json, { + value: '/foo', + type: 'text/css', + rel: 'stylesheet', + }); + t.end(); + }, +); + +tap.test( + 'Css() - pathname is given - prefix is true - should NOT append pathname to "value" for toJSON()', + (t) => { + const obj = new AssetCss({ + value: '/foo', + pathname: '/bar', + prefix: true, + }); + const json = JSON.parse(JSON.stringify(obj)); + t.same(json, { + value: '/foo', + type: 'text/css', + rel: 'stylesheet', + }); + t.end(); + }, +); + +tap.test( + 'Css() - pathname is given - prefix is unset - should NOT append pathname to "href" for toHTML()', + (t) => { + const obj = new AssetCss({ value: '/foo', pathname: '/bar' }); + t.equal( + obj.toHTML(), + '', + ); + t.end(); + }, +); + +tap.test( + 'Css() - pathname is given - prefix is false - should NOT append pathname to "href" for toHTML()', + (t) => { + const obj = new AssetCss({ + value: '/foo', + pathname: '/bar', + prefix: false, + }); + t.equal(obj.value, '/foo'); + t.equal( + obj.toHTML(), + '', + ); + t.end(); + }, +); + +tap.test( + 'Css() - pathname is given - prefix is true - should append pathname to "href" for toHTML()', + (t) => { + const obj = new AssetCss({ + value: '/foo', + pathname: '/bar', + prefix: true, + }); + t.equal( + obj.toHTML(), + '', + ); + t.end(); + }, +); + +tap.test( + 'Css() - value if absoulte - pathname is given - prefix is true - should NOT append pathname to "value"', + (t) => { + const obj = new AssetCss({ + value: 'http://somewhere.else.com/foo', + pathname: '/bar', + prefix: true, + }); + t.equal(obj.value, 'http://somewhere.else.com/foo'); + t.equal(obj.href, 'http://somewhere.else.com/foo'); + + const json = JSON.parse(JSON.stringify(obj)); + t.same(json, { + value: 'http://somewhere.else.com/foo', + type: 'text/css', + rel: 'stylesheet', + }); + + t.equal( + obj.toHTML(), + '', + ); + t.end(); + }, +); + +tap.test( + 'Css() - set "crossorigin" - should construct object as expected', + (t) => { + const obj = new AssetCss({ + value: '/foo', + }); + + obj.crossorigin = 'bar'; + + t.equal(obj.crossorigin, 'bar'); + t.equal( + obj.toHTML(), + '', + ); + + const json = JSON.parse(JSON.stringify(obj)); + t.same(json, { + crossorigin: 'bar', + value: '/foo', + type: 'text/css', + rel: 'stylesheet', + }); + + const repl = new AssetCss(json); + t.equal(repl.crossorigin, 'bar'); + t.end(); + }, +); + +tap.test( + 'Css() - set "disabled" - should construct object as expected', + (t) => { + const obj = new AssetCss({ + value: '/foo', + }); + + obj.disabled = true; + + t.ok(obj.disabled); + t.equal( + obj.toHTML(), + '', + ); + + const json = JSON.parse(JSON.stringify(obj)); + t.same(json, { + disabled: true, + value: '/foo', + type: 'text/css', + rel: 'stylesheet', + }); + + const repl = new AssetCss(json); + t.ok(repl.disabled); + t.end(); + }, +); + +tap.test( + 'Css() - set "hreflang" - should construct object as expected', + (t) => { + const obj = new AssetCss({ + value: '/foo', + }); + + obj.hreflang = 'bar'; + + t.equal(obj.hreflang, 'bar'); + t.equal( + obj.toHTML(), + '', + ); + + const json = JSON.parse(JSON.stringify(obj)); + t.same(json, { + hreflang: 'bar', + value: '/foo', + type: 'text/css', + rel: 'stylesheet', + }); + + const repl = new AssetCss(json); + t.equal(repl.hreflang, 'bar'); + t.end(); + }, +); + +tap.test('Css() - set "title" - should construct object as expected', (t) => { + const obj = new AssetCss({ + value: '/foo', + }); + + obj.title = 'bar'; + + t.equal(obj.title, 'bar'); + t.equal( + obj.toHTML(), + '', + ); + + const json = JSON.parse(JSON.stringify(obj)); + t.same(json, { + title: 'bar', + value: '/foo', + type: 'text/css', + rel: 'stylesheet', + }); + + const repl = new AssetCss(json); + t.equal(repl.title, 'bar'); + t.end(); +}); + +tap.test('Css() - set "media" - should construct object as expected', (t) => { + const obj = new AssetCss({ + value: '/foo', + }); + + obj.media = 'bar'; + + t.equal(obj.media, 'bar'); + t.equal( + obj.toHTML(), + '', + ); + + const json = JSON.parse(JSON.stringify(obj)); + t.same(json, { + media: 'bar', + value: '/foo', + type: 'text/css', + rel: 'stylesheet', + }); + + const repl = new AssetCss(json); + t.equal(repl.media, 'bar'); + t.end(); +}); + +tap.test('Css() - set "type" - should construct object as expected', (t) => { + const obj = new AssetCss({ + value: '/foo', + }); + + obj.type = 'bar'; + + t.equal(obj.type, 'bar'); + t.equal(obj.toHTML(), ''); + + const json = JSON.parse(JSON.stringify(obj)); + t.same(json, { + value: '/foo', + type: 'bar', + rel: 'stylesheet', + }); + + const repl = new AssetCss(json); + t.equal(repl.type, 'bar'); + t.end(); +}); + +tap.test('Css() - set "rel" - should construct object as expected', (t) => { + const obj = new AssetCss({ + value: '/foo', + }); + + obj.rel = 'bar'; + + t.equal(obj.rel, 'bar'); + t.equal(obj.toHTML(), ''); + + const json = JSON.parse(JSON.stringify(obj)); + t.same(json, { + value: '/foo', + type: 'text/css', + rel: 'bar', + }); + + const repl = new AssetCss(json); + t.equal(repl.rel, 'bar'); + t.end(); +}); + +tap.test('Css() - set "as" - should construct object as expected', (t) => { + const obj = new AssetCss({ + value: '/foo', + }); + + obj.as = 'bar'; + + t.equal(obj.as, 'bar'); + t.equal( + obj.toHTML(), + '', + ); + + const json = JSON.parse(JSON.stringify(obj)); + t.same(json, { + as: 'bar', + value: '/foo', + type: 'text/css', + rel: 'stylesheet', + }); + + const repl = new AssetCss(json); + t.equal(repl.as, 'bar'); + t.end(); +}); + +tap.test('Css() - set "value"', (t) => { + t.plan(1); + const obj = new AssetCss({ + value: '/foo', + }); + t.throws( + () => { + obj.value = '/bar'; + }, + /Cannot set read-only property./, + 'Should throw', + ); + t.end(); +}); + +tap.test('Css() - set "href" - should throw', (t) => { + t.plan(1); + const obj = new AssetCss({ + value: '/foo', + }); + t.throws( + () => { + obj.href = '/bar'; + }, + /Cannot set read-only property./, + 'Should throw', + ); + t.end(); +}); + +tap.test('Css() - validate object against schema - should validate', (t) => { + const obj = new AssetCss({ value: '/foo' }); + t.notOk(schema.css([obj]).error); + t.end(); +}); diff --git a/tests/asset-js.js b/tests/asset-js.test.js similarity index 100% rename from tests/asset-js.js rename to tests/asset-js.test.js diff --git a/tests/html-document.js b/tests/html-document.test.js similarity index 60% rename from tests/html-document.js rename to tests/html-document.test.js index b87df48f..6386cd4e 100644 --- a/tests/html-document.js +++ b/tests/html-document.test.js @@ -2,7 +2,6 @@ import tap from 'tap'; import HttpIncoming from '../lib/http-incoming.js'; import { document } from '../lib/html-document.js'; - const SIMPLE_REQ = { headers: {}, }; @@ -33,28 +32,31 @@ tap.test('.document() - arguments given', (t) => { const head = 'this goes in the head section'; const body = 'this goes in the body section'; - const result = document(incoming, body, head); + const result = document(incoming, body, head); t.matchSnapshot(result, 'should render template using values given'); t.end(); }); -tap.test('.document() - arguments given - handles v4 js and css syntax', (t) => { - const incoming = new HttpIncoming(SIMPLE_REQ, SIMPLE_RES); - incoming.css = [ - { value: 'http://somecssurl1.com', type: 'text/css' }, - { value: 'http://somecssurl2.com', type: 'text/css' }, - { value: 'http://somecssurl3.com', type: 'text/css' }, - ]; - incoming.js = [ - { value: 'http://somejsurl1.com', type: 'default' }, - { value: 'http://somejsurl2.com', type: 'default' }, - { value: 'http://somejsurl3.com', type: 'default' }, - ]; - - const result = document(incoming); - t.matchSnapshot(result, 'should render template using values given'); - t.end(); -}); +tap.test( + '.document() - arguments given - handles v4 js and css syntax', + (t) => { + const incoming = new HttpIncoming(SIMPLE_REQ, SIMPLE_RES); + incoming.css = [ + { value: 'http://somecssurl1.com', type: 'text/css' }, + { value: 'http://somecssurl2.com', type: 'text/css' }, + { value: 'http://somecssurl3.com', type: 'text/css' }, + ]; + incoming.js = [ + { value: 'http://somejsurl1.com', type: 'default' }, + { value: 'http://somejsurl2.com', type: 'default' }, + { value: 'http://somejsurl3.com', type: 'default' }, + ]; + + const result = document(incoming); + t.matchSnapshot(result, 'should render template using values given'); + t.end(); + }, +); tap.test('.document() - js "type" is "esm"', (t) => { const incoming = new HttpIncoming(SIMPLE_REQ, SIMPLE_RES); @@ -74,19 +76,34 @@ tap.test('.document() - js "type" is "esm"', (t) => { t.end(); }); -tap.test('.document() - "type" is "module", "strategy" is set - should place assets based on strategy', (t) => { - const incoming = new HttpIncoming(SIMPLE_REQ, SIMPLE_RES); - incoming.css = [{ value: 'http://somecssurl1.com', type: 'text/css' }]; - incoming.js = [ - { value: 'http://somejsurl1.com/lazy', type: 'module', strategy: 'lazy' }, - { value: 'http://somejsurl2.com/before', type: 'module', strategy: 'beforeInteractive' }, - { value: 'http://somejsurl3.com/after', type: 'module', strategy: 'afterInteractive' }, - ]; - - const result = document(incoming); - t.matchSnapshot(result); - t.end(); -}); +tap.test( + '.document() - "type" is "module", "strategy" is set - should place assets based on strategy', + (t) => { + const incoming = new HttpIncoming(SIMPLE_REQ, SIMPLE_RES); + incoming.css = [{ value: 'http://somecssurl1.com', type: 'text/css' }]; + incoming.js = [ + { + value: 'http://somejsurl1.com/lazy', + type: 'module', + strategy: 'lazy', + }, + { + value: 'http://somejsurl2.com/before', + type: 'module', + strategy: 'beforeInteractive', + }, + { + value: 'http://somejsurl3.com/after', + type: 'module', + strategy: 'afterInteractive', + }, + ]; + + const result = document(incoming); + t.matchSnapshot(result); + t.end(); + }, +); tap.test('.document() - lang tag priority - params has priority', (t) => { const incoming = new HttpIncoming(SIMPLE_REQ, SIMPLE_RES, { locale: 'sv' }); @@ -106,8 +123,8 @@ tap.test('.document() - lang tag priority - context has priority', (t) => { tap.test('.document() - lang tag priority - view has priority', (t) => { const incoming = new HttpIncoming(SIMPLE_REQ, SIMPLE_RES, { locale: 'sv' }); incoming.context = { locale: 'en-NZ' }; - incoming.view = { locale: 'nb' } + incoming.view = { locale: 'nb' }; const result = document(incoming, ''); t.match(result, 'lang="nb"', 'should render lang tag nb'); t.end(); -}); \ No newline at end of file +}); diff --git a/tests/html-utils.js b/tests/html-utils.js deleted file mode 100644 index cbefd627..00000000 --- a/tests/html-utils.js +++ /dev/null @@ -1,523 +0,0 @@ -import tap from 'tap'; -import AssetCss from '../lib/asset-css.js'; -import AssetJs from '../lib/asset-js.js'; -import * as utils from '../lib/html-utils.js'; - -/** - * .buildLinkElement() - */ - -tap.test('.buildLinkElement() - "value" property has a value - should appended "href" attribute to element', (t) => { - const obj = new AssetCss({ - value: '/foo', - }); - const result = utils.buildLinkElement(obj); - t.equal(result, - '', - ); - t.end(); -}); - -tap.test('.buildLinkElement() - "crossorigin" property has a value - should appended "crossorigin" attribute to element', (t) => { - const obj = new AssetCss({ - value: '/foo', - crossorigin: 'bar', - }); - const result = utils.buildLinkElement(obj); - t.equal(result, - '', - ); - t.end(); -}); - -tap.test('.buildLinkElement() - "disabled" property is "true" - should appended "disabled" attribute to element', (t) => { - const obj = new AssetCss({ - value: '/foo', - disabled: true, - }); - const result = utils.buildLinkElement(obj); - t.equal(result, - '', - ); - t.end(); -}); - -tap.test('.buildLinkElement() - "hreflang" property has a value - should appended "hreflang" attribute to element', (t) => { - const obj = new AssetCss({ - value: '/foo', - hreflang: 'bar', - }); - const result = utils.buildLinkElement(obj); - t.equal(result, - '', - ); - t.end(); -}); - -tap.test('.buildLinkElement() - "title" property has a value - should appended "title" attribute to element', (t) => { - const obj = new AssetCss({ - value: '/foo', - title: 'bar', - }); - const result = utils.buildLinkElement(obj); - t.equal(result, - '', - ); - t.end(); -}); - -tap.test('.buildLinkElement() - "media" property has a value - should appended "media" attribute to element', (t) => { - const obj = new AssetCss({ - value: '/foo', - media: 'bar', - }); - const result = utils.buildLinkElement(obj); - t.equal(result, - '', - ); - t.end(); -}); - -tap.test('.buildLinkElement() - "as" property has a value - should appended "as" attribute to element', (t) => { - const obj = new AssetCss({ - value: '/foo', - as: 'bar', - }); - const result = utils.buildLinkElement(obj); - t.equal(result, - '', - ); - t.end(); -}); - -tap.test('.buildLinkElement() - "type" property has a value - should appended "type" attribute to element', (t) => { - const obj = new AssetCss({ - value: '/foo', - type: 'bar', - }); - const result = utils.buildLinkElement(obj); - t.equal(result, ''); - t.end(); -}); - -tap.test('.buildLinkElement() - "rel" property has a value - should appended "rel" attribute to element', (t) => { - const obj = new AssetCss({ - value: '/foo', - rel: 'bar', - }); - const result = utils.buildLinkElement(obj); - t.equal(result, ''); - t.end(); -}); - -tap.test('.buildLinkElement() - properties are "undefined" - should NOT appended attributes to element', (t) => { - const obj = new AssetCss({ - crossorigin: undefined, - disabled: undefined, - hreflang: undefined, - title: undefined, - media: undefined, - value: '/foo', - type: undefined, - rel: undefined, - as: undefined, - }); - const result = utils.buildLinkElement(obj); - t.equal(result, - '', - ); - t.end(); -}); - -tap.test('.buildLinkElement() - properties are "null" - should NOT appended attributes to element', (t) => { - const obj = new AssetCss({ - crossorigin: null, - disabled: null, - hreflang: null, - title: null, - media: null, - value: '/foo', - type: null, - rel: null, - as: null, - }); - const result = utils.buildLinkElement(obj); - t.equal(result, ''); - t.end(); -}); - -tap.test('.buildLinkElement() - properties are "false" - should NOT appended attributes to element', (t) => { - const obj = new AssetCss({ - crossorigin: false, - disabled: false, - hreflang: false, - title: false, - media: false, - value: '/foo', - type: false, - rel: false, - as: false, - }); - const result = utils.buildLinkElement(obj); - t.equal(result, ''); - t.end(); -}); - -tap.test('.buildLinkElement() - properties are empty string - should NOT appended attributes to element', (t) => { - const obj = new AssetCss({ - crossorigin: '', - disabled: '', - hreflang: '', - title: '', - media: '', - value: '/foo', - type: '', - rel: '', - as: '', - }); - const result = utils.buildLinkElement(obj); - t.equal(result, ''); - t.end(); -}); - -tap.test('.buildLinkElement() - crossorigin boolean true', (t) => { - const obj = new AssetCss({ - crossorigin: true, - value: '/bar', - }); - const result = utils.buildLinkElement(obj); - t.equal(result, - ``, - ); - t.end(); -}); - -tap.test('.buildLinkElement() - crossorigin boolean false', (t) => { - const obj = new AssetCss({ - crossorigin: false, - value: '/bar', - }); - const result = utils.buildLinkElement(obj); - t.equal(result, - ``, - ); - t.end(); -}); - -/** - * .buildScriptElement() - */ - -tap.test('.buildScriptElement() - "value" property has a value - should appended "src" attribute to element', (t) => { - const obj = new AssetJs({ - value: '/foo', - }); - const result = utils.buildScriptElement(obj); - t.equal(result, ''); - t.end(); -}); - -tap.test('.buildScriptElement() - "type" property has "module" as value - should appended "type" attribute with "module" as value to element', (t) => { - const obj = new AssetJs({ - value: '/foo', - type: 'module', - }); - const result = utils.buildScriptElement(obj); - t.equal(result, ''); - t.end(); -}); - -tap.test('.buildScriptElement() - "type" property has "esm" as value - should appended "type" attribute with "module" as value to element', (t) => { - const obj = new AssetJs({ - value: '/foo', - type: 'esm', - }); - const result = utils.buildScriptElement(obj); - t.equal(result, ''); - t.end(); -}); - -tap.test('.buildScriptElement() - "type" property has "cjs" as value - should NOT appended a attribute to element', (t) => { - const obj = new AssetJs({ - value: '/foo', - type: 'cjs', - }); - const result = utils.buildScriptElement(obj); - t.equal(result, ''); - t.end(); -}); - -tap.test('.buildScriptElement() - "referrerpolicy" property has a value - should appended "referrerpolicy" attribute to element', (t) => { - const obj = new AssetJs({ - value: '/foo', - referrerpolicy: 'bar', - }); - const result = utils.buildScriptElement(obj); - t.equal(result, ''); - t.end(); -}); - -tap.test('.buildScriptElement() - "crossorigin" property has a value - should appended "crossorigin" attribute to element', (t) => { - const obj = new AssetJs({ - value: '/foo', - crossorigin: 'bar', - }); - const result = utils.buildScriptElement(obj); - t.equal(result, ''); - t.end(); -}); - -tap.test('.buildScriptElement() - "integrity" property has a value - should appended "integrity" attribute to element', (t) => { - const obj = new AssetJs({ - value: '/foo', - integrity: 'bar', - }); - const result = utils.buildScriptElement(obj); - t.equal(result, ''); - t.end(); -}); - -tap.test('.buildScriptElement() - "nomodule" property is "true" - should appended "nomodule" attribute to element', (t) => { - const obj = new AssetJs({ - value: '/foo', - nomodule: true, - }); - const result = utils.buildScriptElement(obj); - t.equal(result, ''); - t.end(); -}); - -tap.test('.buildScriptElement() - "async" property is "true" - should appended "async" attribute to element', (t) => { - const obj = new AssetJs({ - value: '/foo', - async: true, - }); - const result = utils.buildScriptElement(obj); - t.equal(result, ''); - t.end(); -}); - -tap.test('.buildScriptElement() - "data" property has a value - should appended "data" attribute to element', (t) => { - const obj = new AssetJs({ - value: '/foo', - data: [ - { - key: 'foo', - value: 'bar', - }, - ], - }); - const result = utils.buildScriptElement(obj); - t.equal(result, ''); - t.end(); -}); - -tap.test('.buildScriptElement() - "defer" property is "true" - should appended "defer" attribute to element', (t) => { - const obj = new AssetJs({ - value: '/foo', - defer: true, - }); - const result = utils.buildScriptElement(obj); - t.equal(result, ''); - t.end(); -}); - -tap.test('.buildScriptElement() - properties are "undefined" - should NOT appended attributes to element', (t) => { - const obj = new AssetJs({ - referrerpolicy: undefined, - crossorigin: undefined, - integrity: undefined, - nomodule: undefined, - async: undefined, - defer: undefined, - value: '/foo', - type: undefined, - }); - const result = utils.buildScriptElement(obj); - t.equal(result, ''); - t.end(); -}); - -tap.test('.buildScriptElement() - properties are "null" - should NOT appended attributes to element', (t) => { - const obj = new AssetJs({ - referrerpolicy: null, - crossorigin: null, - integrity: null, - nomodule: null, - async: null, - defer: null, - value: '/foo', - type: null, - }); - const result = utils.buildScriptElement(obj); - t.equal(result, ''); - t.end(); -}); - -tap.test('.buildScriptElement() - properties are "false" - should NOT appended attributes to element', (t) => { - const obj = new AssetJs({ - referrerpolicy: false, - crossorigin: false, - integrity: false, - nomodule: false, - async: false, - defer: false, - value: '/foo', - type: false, - }); - const result = utils.buildScriptElement(obj); - t.equal(result, ''); - t.end(); -}); - -tap.test('.buildScriptElement() - properties are empty string - should NOT appended attributes to element', (t) => { - const obj = new AssetJs({ - referrerpolicy: '', - integrity: '', - nomodule: '', - async: '', - defer: '', - value: '/foo', - type: '', - }); - const result = utils.buildScriptElement(obj); - t.equal(result, ''); - t.end(); -}); - -tap.test('.buildScriptElement() - crossorigin empty string', (t) => { - const obj = new AssetJs({ - crossorigin: '', - value: '/foo', - }); - const result = utils.buildScriptElement(obj); - t.equal(result, ``); - t.end(); -}); - -tap.test('.buildScriptElement() - crossorigin boolean true', (t) => { - const obj = new AssetJs({ - crossorigin: true, - value: '/bar', - }); - const result = utils.buildScriptElement(obj); - t.equal(result, ``); - t.end(); -}); - -tap.test('.buildScriptElement() - crossorigin boolean false', (t) => { - const obj = new AssetJs({ - crossorigin: false, - value: '/bar', - }); - const result = utils.buildScriptElement(obj); - t.equal(result, ``); - t.end(); -}); - -tap.test('.buildScriptAttributes() - basic', (t) => { - const obj = new AssetJs({ value: '/bar' }); - t.same(utils.buildScriptAttributes(obj), [ - { key: 'src', value: '/bar' } - ]); - t.end(); -}); - -tap.test('.buildScriptElement() - strategy lazy - builds HTML with dynamic import', (t) => { - const obj = new AssetJs({ value: '/foo', type: 'module', strategy: 'lazy' }); - const result = utils.buildScriptElement(obj); - t.equal(result, ''); - t.end(); -}); - -tap.test('.buildScriptElement() - strategy lazy - automatically includes type="module" if not provided', (t) => { - const obj = new AssetJs({ value: '/foo', strategy: 'lazy' }); - const result = utils.buildScriptElement(obj); - t.equal(result, ''); - t.end(); -}); - -tap.test('.buildScriptAttributes() - advanced', (t) => { - const obj = new AssetJs({ - value: '/bar', - crossorigin: true, - integrity: 'fake', - defer: true, - type: 'module', - }); - t.same(utils.buildScriptAttributes(obj), [ - { key: 'src', value: '/bar' }, - { key: 'type', value: 'module' }, - { key: 'crossorigin' }, - { key: 'integrity', value: 'fake' }, - { key: 'defer' }, - ]); - t.end(); -}); - -tap.test('.buildLinkAttributes() - basic', (t) => { - const obj = new AssetCss({ value: '/bar' }); - t.same(utils.buildLinkAttributes(obj), [ - { key: 'href', value: '/bar' }, - { key: 'type', value: 'text/css' }, - { key: 'rel', value: 'stylesheet' }, - ]); - t.end(); -}); - - -tap.test('.buildLinkAttributes() - advanced', (t) => { - const obj = new AssetCss({ - value: '/bar', - disabled: true, - hreflang: 'test1', - title: 'test2', - media: 'test3', - as: 'test4', - type: 'test5', - rel: 'test6', - }); - t.same(utils.buildLinkAttributes(obj), [ - { key: 'href', value: '/bar' }, - { key: 'disabled' }, - { key: 'hreflang', value: 'test1' }, - { key: 'title', value: 'test2' }, - { key: 'media', value: 'test3' }, - { key: 'as', value: 'test4' }, - { key: 'type', value: 'test5' }, - { key: 'rel', value: 'test6' }, - ]); - t.end(); -}); - -tap.test('.buildReactScriptAttributes()', (t) => { - const obj = new AssetJs({ - value: '/bar', - crossorigin: true, - async: true, - nomodule: true, - }); - t.same(utils.buildReactScriptAttributes(obj), { - src: '/bar', - crossOrigin: '', - noModule: true, - async: true, - }); - t.end(); -}); - -tap.test('.buildReactLinkAttributes()', (t) => { - const obj = new AssetCss({ - value: '/bar', - crossorigin: true, - disabled: true, - }); - t.same(utils.buildReactLinkAttributes(obj), { - href: '/bar', - crossOrigin: '', - rel: 'stylesheet', - disabled: true, - type: 'text/css', - }); - t.end(); -}); diff --git a/tests/html-utils.test.js b/tests/html-utils.test.js new file mode 100644 index 00000000..929d584f --- /dev/null +++ b/tests/html-utils.test.js @@ -0,0 +1,629 @@ +import tap from 'tap'; +import AssetCss from '../lib/asset-css.js'; +import AssetJs from '../lib/asset-js.js'; +import * as utils from '../lib/html-utils.js'; + +/** + * .buildLinkElement() + */ + +tap.test( + '.buildLinkElement() - "value" property has a value - should appended "href" attribute to element', + (t) => { + const obj = new AssetCss({ + value: '/foo', + }); + const result = utils.buildLinkElement(obj); + t.equal(result, ''); + t.end(); + }, +); + +tap.test( + '.buildLinkElement() - "crossorigin" property has a value - should appended "crossorigin" attribute to element', + (t) => { + const obj = new AssetCss({ + value: '/foo', + crossorigin: 'bar', + }); + const result = utils.buildLinkElement(obj); + t.equal( + result, + '', + ); + t.end(); + }, +); + +tap.test( + '.buildLinkElement() - "disabled" property is "true" - should appended "disabled" attribute to element', + (t) => { + const obj = new AssetCss({ + value: '/foo', + disabled: true, + }); + const result = utils.buildLinkElement(obj); + t.equal( + result, + '', + ); + t.end(); + }, +); + +tap.test( + '.buildLinkElement() - "hreflang" property has a value - should appended "hreflang" attribute to element', + (t) => { + const obj = new AssetCss({ + value: '/foo', + hreflang: 'bar', + }); + const result = utils.buildLinkElement(obj); + t.equal( + result, + '', + ); + t.end(); + }, +); + +tap.test( + '.buildLinkElement() - "title" property has a value - should appended "title" attribute to element', + (t) => { + const obj = new AssetCss({ + value: '/foo', + title: 'bar', + }); + const result = utils.buildLinkElement(obj); + t.equal( + result, + '', + ); + t.end(); + }, +); + +tap.test( + '.buildLinkElement() - "media" property has a value - should appended "media" attribute to element', + (t) => { + const obj = new AssetCss({ + value: '/foo', + media: 'bar', + }); + const result = utils.buildLinkElement(obj); + t.equal( + result, + '', + ); + t.end(); + }, +); + +tap.test( + '.buildLinkElement() - "as" property has a value - should appended "as" attribute to element', + (t) => { + const obj = new AssetCss({ + value: '/foo', + as: 'bar', + }); + const result = utils.buildLinkElement(obj); + t.equal( + result, + '', + ); + t.end(); + }, +); + +tap.test( + '.buildLinkElement() - "type" property has a value - should appended "type" attribute to element', + (t) => { + const obj = new AssetCss({ + value: '/foo', + type: 'bar', + }); + const result = utils.buildLinkElement(obj); + t.equal(result, ''); + t.end(); + }, +); + +tap.test( + '.buildLinkElement() - "rel" property has a value - should appended "rel" attribute to element', + (t) => { + const obj = new AssetCss({ + value: '/foo', + rel: 'bar', + }); + const result = utils.buildLinkElement(obj); + t.equal(result, ''); + t.end(); + }, +); + +tap.test( + '.buildLinkElement() - properties are "undefined" - should NOT appended attributes to element', + (t) => { + const obj = new AssetCss({ + crossorigin: undefined, + disabled: undefined, + hreflang: undefined, + title: undefined, + media: undefined, + value: '/foo', + type: undefined, + rel: undefined, + as: undefined, + }); + const result = utils.buildLinkElement(obj); + t.equal(result, ''); + t.end(); + }, +); + +tap.test( + '.buildLinkElement() - properties are "null" - should NOT appended attributes to element', + (t) => { + const obj = new AssetCss({ + crossorigin: null, + disabled: null, + hreflang: null, + title: null, + media: null, + value: '/foo', + type: null, + rel: null, + as: null, + }); + const result = utils.buildLinkElement(obj); + t.equal(result, ''); + t.end(); + }, +); + +tap.test( + '.buildLinkElement() - properties are "false" - should NOT appended attributes to element', + (t) => { + const obj = new AssetCss({ + crossorigin: false, + disabled: false, + // @ts-ignore Testing bad input + hreflang: false, + // @ts-ignore + title: false, + // @ts-ignore + media: false, + value: '/foo', + // @ts-ignore + type: false, + // @ts-ignore + rel: false, + // @ts-ignore + as: false, + }); + const result = utils.buildLinkElement(obj); + t.equal(result, ''); + t.end(); + }, +); + +tap.test( + '.buildLinkElement() - properties are empty string - should NOT appended attributes to element', + (t) => { + const obj = new AssetCss({ + crossorigin: '', + // @ts-ignore Testing bad input + disabled: '', + hreflang: '', + title: '', + media: '', + value: '/foo', + type: '', + rel: '', + as: '', + }); + const result = utils.buildLinkElement(obj); + t.equal(result, ''); + t.end(); + }, +); + +tap.test('.buildLinkElement() - crossorigin boolean true', (t) => { + const obj = new AssetCss({ + crossorigin: true, + value: '/bar', + }); + const result = utils.buildLinkElement(obj); + t.equal( + result, + ``, + ); + t.end(); +}); + +tap.test('.buildLinkElement() - crossorigin boolean false', (t) => { + const obj = new AssetCss({ + crossorigin: false, + value: '/bar', + }); + const result = utils.buildLinkElement(obj); + t.equal(result, ``); + t.end(); +}); + +/** + * .buildScriptElement() + */ + +tap.test( + '.buildScriptElement() - "value" property has a value - should appended "src" attribute to element', + (t) => { + const obj = new AssetJs({ + value: '/foo', + }); + const result = utils.buildScriptElement(obj); + t.equal(result, ''); + t.end(); + }, +); + +tap.test( + '.buildScriptElement() - "type" property has "module" as value - should appended "type" attribute with "module" as value to element', + (t) => { + const obj = new AssetJs({ + value: '/foo', + type: 'module', + }); + const result = utils.buildScriptElement(obj); + t.equal(result, ''); + t.end(); + }, +); + +tap.test( + '.buildScriptElement() - "type" property has "esm" as value - should appended "type" attribute with "module" as value to element', + (t) => { + const obj = new AssetJs({ + value: '/foo', + type: 'esm', + }); + const result = utils.buildScriptElement(obj); + t.equal(result, ''); + t.end(); + }, +); + +tap.test( + '.buildScriptElement() - "type" property has "cjs" as value - should NOT appended a attribute to element', + (t) => { + const obj = new AssetJs({ + value: '/foo', + type: 'cjs', + }); + const result = utils.buildScriptElement(obj); + t.equal(result, ''); + t.end(); + }, +); + +tap.test( + '.buildScriptElement() - "referrerpolicy" property has a value - should appended "referrerpolicy" attribute to element', + (t) => { + const obj = new AssetJs({ + value: '/foo', + referrerpolicy: 'bar', + }); + const result = utils.buildScriptElement(obj); + t.equal(result, ''); + t.end(); + }, +); + +tap.test( + '.buildScriptElement() - "crossorigin" property has a value - should appended "crossorigin" attribute to element', + (t) => { + const obj = new AssetJs({ + value: '/foo', + crossorigin: 'bar', + }); + const result = utils.buildScriptElement(obj); + t.equal(result, ''); + t.end(); + }, +); + +tap.test( + '.buildScriptElement() - "integrity" property has a value - should appended "integrity" attribute to element', + (t) => { + const obj = new AssetJs({ + value: '/foo', + integrity: 'bar', + }); + const result = utils.buildScriptElement(obj); + t.equal(result, ''); + t.end(); + }, +); + +tap.test( + '.buildScriptElement() - "nomodule" property is "true" - should appended "nomodule" attribute to element', + (t) => { + const obj = new AssetJs({ + value: '/foo', + nomodule: true, + }); + const result = utils.buildScriptElement(obj); + t.equal(result, ''); + t.end(); + }, +); + +tap.test( + '.buildScriptElement() - "async" property is "true" - should appended "async" attribute to element', + (t) => { + const obj = new AssetJs({ + value: '/foo', + async: true, + }); + const result = utils.buildScriptElement(obj); + t.equal(result, ''); + t.end(); + }, +); + +tap.test( + '.buildScriptElement() - "data" property has a value - should appended "data" attribute to element', + (t) => { + const obj = new AssetJs({ + value: '/foo', + data: [ + { + key: 'foo', + value: 'bar', + }, + ], + }); + const result = utils.buildScriptElement(obj); + t.equal(result, ''); + t.end(); + }, +); + +tap.test( + '.buildScriptElement() - "defer" property is "true" - should appended "defer" attribute to element', + (t) => { + const obj = new AssetJs({ + value: '/foo', + defer: true, + }); + const result = utils.buildScriptElement(obj); + t.equal(result, ''); + t.end(); + }, +); + +tap.test( + '.buildScriptElement() - properties are "undefined" - should NOT appended attributes to element', + (t) => { + const obj = new AssetJs({ + referrerpolicy: undefined, + crossorigin: undefined, + integrity: undefined, + nomodule: undefined, + async: undefined, + defer: undefined, + value: '/foo', + type: undefined, + }); + const result = utils.buildScriptElement(obj); + t.equal(result, ''); + t.end(); + }, +); + +tap.test( + '.buildScriptElement() - properties are "null" - should NOT appended attributes to element', + (t) => { + const obj = new AssetJs({ + referrerpolicy: null, + crossorigin: null, + integrity: null, + nomodule: null, + async: null, + defer: null, + value: '/foo', + type: null, + }); + const result = utils.buildScriptElement(obj); + t.equal(result, ''); + t.end(); + }, +); + +tap.test( + '.buildScriptElement() - properties are "false" - should NOT appended attributes to element', + (t) => { + const obj = new AssetJs({ + // @ts-ignore Testing bad input + referrerpolicy: false, + // @ts-ignore Testing bad input + crossorigin: false, + // @ts-ignore Testing bad input + integrity: false, + nomodule: false, + async: false, + defer: false, + value: '/foo', + // @ts-ignore Testing bad input + type: false, + }); + const result = utils.buildScriptElement(obj); + t.equal(result, ''); + t.end(); + }, +); + +tap.test( + '.buildScriptElement() - properties are empty string - should NOT appended attributes to element', + (t) => { + const obj = new AssetJs({ + referrerpolicy: '', + integrity: '', + // @ts-ignore Testing bad input + nomodule: '', + // @ts-ignore Testing bad input + async: '', + // @ts-ignore Testing bad input + defer: '', + value: '/foo', + type: '', + }); + const result = utils.buildScriptElement(obj); + t.equal(result, ''); + t.end(); + }, +); + +tap.test('.buildScriptElement() - crossorigin empty string', (t) => { + const obj = new AssetJs({ + crossorigin: '', + value: '/foo', + }); + const result = utils.buildScriptElement(obj); + t.equal(result, ``); + t.end(); +}); + +tap.test('.buildScriptElement() - crossorigin boolean true', (t) => { + const obj = new AssetJs({ + crossorigin: true, + value: '/bar', + }); + const result = utils.buildScriptElement(obj); + t.equal(result, ``); + t.end(); +}); + +tap.test('.buildScriptElement() - crossorigin boolean false', (t) => { + const obj = new AssetJs({ + crossorigin: false, + value: '/bar', + }); + const result = utils.buildScriptElement(obj); + t.equal(result, ``); + t.end(); +}); + +tap.test('.buildScriptAttributes() - basic', (t) => { + const obj = new AssetJs({ value: '/bar' }); + t.same(utils.buildScriptAttributes(obj), [{ key: 'src', value: '/bar' }]); + t.end(); +}); + +tap.test( + '.buildScriptElement() - strategy lazy - builds HTML with dynamic import', + (t) => { + const obj = new AssetJs({ + value: '/foo', + type: 'module', + strategy: 'lazy', + }); + const result = utils.buildScriptElement(obj); + t.equal(result, ''); + t.end(); + }, +); + +tap.test( + '.buildScriptElement() - strategy lazy - automatically includes type="module" if not provided', + (t) => { + const obj = new AssetJs({ value: '/foo', strategy: 'lazy' }); + const result = utils.buildScriptElement(obj); + t.equal(result, ''); + t.end(); + }, +); + +tap.test('.buildScriptAttributes() - advanced', (t) => { + const obj = new AssetJs({ + value: '/bar', + crossorigin: true, + integrity: 'fake', + defer: true, + type: 'module', + }); + t.same(utils.buildScriptAttributes(obj), [ + { key: 'src', value: '/bar' }, + { key: 'type', value: 'module' }, + { key: 'crossorigin' }, + { key: 'integrity', value: 'fake' }, + { key: 'defer' }, + ]); + t.end(); +}); + +tap.test('.buildLinkAttributes() - basic', (t) => { + const obj = new AssetCss({ value: '/bar' }); + t.same(utils.buildLinkAttributes(obj), [ + { key: 'href', value: '/bar' }, + { key: 'type', value: 'text/css' }, + { key: 'rel', value: 'stylesheet' }, + ]); + t.end(); +}); + +tap.test('.buildLinkAttributes() - advanced', (t) => { + const obj = new AssetCss({ + value: '/bar', + disabled: true, + hreflang: 'test1', + title: 'test2', + media: 'test3', + as: 'test4', + type: 'test5', + rel: 'test6', + }); + t.same(utils.buildLinkAttributes(obj), [ + { key: 'href', value: '/bar' }, + { key: 'disabled' }, + { key: 'hreflang', value: 'test1' }, + { key: 'title', value: 'test2' }, + { key: 'media', value: 'test3' }, + { key: 'as', value: 'test4' }, + { key: 'type', value: 'test5' }, + { key: 'rel', value: 'test6' }, + ]); + t.end(); +}); + +tap.test('.buildReactScriptAttributes()', (t) => { + const obj = new AssetJs({ + value: '/bar', + crossorigin: true, + async: true, + nomodule: true, + }); + t.same(utils.buildReactScriptAttributes(obj), { + src: '/bar', + crossOrigin: '', + noModule: true, + async: true, + }); + t.end(); +}); + +tap.test('.buildReactLinkAttributes()', (t) => { + const obj = new AssetCss({ + value: '/bar', + crossorigin: true, + disabled: true, + }); + t.same(utils.buildReactLinkAttributes(obj), { + href: '/bar', + crossOrigin: '', + rel: 'stylesheet', + disabled: true, + type: 'text/css', + }); + t.end(); +}); diff --git a/tests/http-incoming.js b/tests/http-incoming.js deleted file mode 100644 index 95559e5d..00000000 --- a/tests/http-incoming.js +++ /dev/null @@ -1,241 +0,0 @@ -import tap from 'tap'; -import HttpIncoming from '../lib/http-incoming.js'; - -const SIMPLE_REQ = {}; - -const ADVANCED_REQ = { - headers: { - host: 'localhost:3030', - }, - hostname: 'localhost', - url: '/some/path?foo=bar', -}; - -const SIMPLE_RES = { - locals: {}, -}; - -const SIMPLE_PARAMS = { - foo: 'bar', -}; - -tap.test('PodiumHttpIncoming() - object tag - should be PodiumHttpIncoming', (t) => { - const incoming = new HttpIncoming(SIMPLE_REQ); - t.equal(Object.prototype.toString.call(incoming), '[object PodiumHttpIncoming]'); - t.end(); -}); - -tap.test('PodiumHttpIncoming() - no arguments given - should construct object with default values', (t) => { - const incoming = new HttpIncoming(SIMPLE_REQ); - t.same(incoming.request, SIMPLE_REQ); - t.same(incoming.response, {}); - t.same(incoming.url, {}); - t.same(incoming.params, {}); - t.notOk(incoming.proxy); - t.same(incoming.context, {}); - t.notOk(incoming.development); - t.equal(incoming.name, ''); - t.same(incoming.css, []); - t.same(incoming.js, []); - t.end(); -}); - -tap.test('PodiumHttpIncoming() - "request" argument given - should store request on ".request"', (t) => { - const incoming = new HttpIncoming(ADVANCED_REQ); - t.same(incoming.request, ADVANCED_REQ); - t.end(); -}); - -tap.test('PodiumHttpIncoming() - "request" argument given - should set parsed URL on ".url"', (t) => { - const incoming = new HttpIncoming(ADVANCED_REQ); - t.equal(incoming.url.hostname, 'localhost'); - t.equal(incoming.url.host, 'localhost:3030'); - t.equal(incoming.url.port, '3030'); - t.equal(incoming.url.protocol, 'http:'); - t.equal(incoming.url.pathname, '/some/path'); - t.equal(incoming.url.search, '?foo=bar'); - t.end(); -}); - -tap.test('PodiumHttpIncoming() - "response" argument given - should store request on ".response"', (t) => { - const incoming = new HttpIncoming(ADVANCED_REQ, SIMPLE_RES); - t.same(incoming.response, SIMPLE_RES); - t.end(); -}); - -tap.test('PodiumHttpIncoming() - "params" argument given - should store request on ".params"', (t) => { - const incoming = new HttpIncoming(ADVANCED_REQ, SIMPLE_RES, SIMPLE_PARAMS); - t.same(incoming.params, SIMPLE_PARAMS); - t.end(); -}); - -tap.test('PodiumHttpIncoming.request - set value', (t) => { - t.plan(1); - const incoming = new HttpIncoming(ADVANCED_REQ, SIMPLE_RES); - t.throws(() => { - incoming.request = 'foo'; - }, /Cannot set read-only property./, 'Should throw'); - t.end(); -}); - -tap.test('PodiumHttpIncoming.response - set value', (t) => { - t.plan(1); - const incoming = new HttpIncoming(ADVANCED_REQ, SIMPLE_RES); - t.throws(() => { - incoming.response = 'foo'; - }, /Cannot set read-only property./, 'Should throw'); - t.end(); -}); - -tap.test('PodiumHttpIncoming.params - set value', (t) => { - t.plan(1); - const incoming = new HttpIncoming(ADVANCED_REQ, SIMPLE_RES); - t.throws(() => { - incoming.params = 'foo'; - }, /Cannot set read-only property./, 'Should throw'); - t.end(); -}); - -tap.test('PodiumHttpIncoming.url - set value - should set value', (t) => { - const incoming = new HttpIncoming(ADVANCED_REQ, SIMPLE_RES); - incoming.url = 'http://localhost:8080/foo'; - t.equal(incoming.url.href, 'http://localhost:8080/foo'); - t.end(); -}); - -tap.test('PodiumHttpIncoming.development - set value - should set value', (t) => { - const incoming = new HttpIncoming(ADVANCED_REQ, SIMPLE_RES); - incoming.development = true; - t.ok(incoming.development); - t.end(); -}); - -tap.test('PodiumHttpIncoming.name - set value - should set value', (t) => { - const incoming = new HttpIncoming(ADVANCED_REQ, SIMPLE_RES); - incoming.name = 'a_name'; - t.equal(incoming.name, 'a_name'); - t.end(); -}); - -tap.test('PodiumHttpIncoming.css - set legal value - should set value', (t) => { - const incoming = new HttpIncoming(ADVANCED_REQ, SIMPLE_RES); - incoming.css = ['a_css']; - t.same(incoming.css, ['a_css']); - t.end(); -}); - -tap.test('PodiumHttpIncoming.css - set illegal value', (t) => { - t.plan(1); - const incoming = new HttpIncoming(ADVANCED_REQ, SIMPLE_RES); - t.throws(() => { - incoming.css = 'a_css'; - }, /Value for property ".css" must be an Array/, 'Should throw'); - t.end(); -}); - -tap.test('PodiumHttpIncoming.js - set legal value - should set value', (t) => { - const incoming = new HttpIncoming(ADVANCED_REQ, SIMPLE_RES); - incoming.js = ['a_js']; - t.same(incoming.js, ['a_js']); - t.end(); -}); - -tap.test('PodiumHttpIncoming.js - set illegal value - should throw', (t) => { - t.plan(1); - const incoming = new HttpIncoming(ADVANCED_REQ, SIMPLE_RES); - t.throws(() => { - incoming.js = 'a_js'; - }, /Value for property ".js" must be an Array/, 'Should throw'); - t.end(); -}); - -tap.test('PodiumHttpIncoming.proxy - set value - should set value', (t) => { - const incoming = new HttpIncoming(ADVANCED_REQ, SIMPLE_RES); - incoming.proxy = true; - t.ok(incoming.proxy); - t.end(); -}); - -tap.test('PodiumHttpIncoming.context - set value - should set value', (t) => { - const incoming = new HttpIncoming(ADVANCED_REQ, SIMPLE_RES); - incoming.context = { foo: 'bar' }; - t.same(incoming.context, { foo: 'bar' }); - t.end(); -}); - -tap.test('PodiumHttpIncoming.view - no value - should return empty object', (t) => { - const incoming = new HttpIncoming(ADVANCED_REQ, SIMPLE_RES); - t.same(incoming.view, {}); - t.end(); -}); - -tap.test('PodiumHttpIncoming.view - set value - should set value', (t) => { - const incoming = new HttpIncoming(ADVANCED_REQ, SIMPLE_RES); - incoming.view = { - title: 'foo', - }; - t.same(incoming.view, { - title: 'foo', - }); - t.end(); -}); - -tap.test('PodiumHttpIncoming.podlets - set legal single value - should append values to .css and .js', (t) => { - const incoming = new HttpIncoming(ADVANCED_REQ, SIMPLE_RES); - incoming.podlets = { - css: [{ value: 'foo.css' }], - js: [{ value: 'foo.js' }], - }; - t.same(incoming.css, [{ value: 'foo.css' }]); - t.same(incoming.js, [{ value: 'foo.js' }]); - t.end(); -}); - -tap.test('PodiumHttpIncoming.podlets - set legal array value - should append values to .css and .js', (t) => { - const incoming = new HttpIncoming(ADVANCED_REQ, SIMPLE_RES); - incoming.podlets = [ - { css: [{ value: 'foo.css' }], js: [{ value: 'foo.js' }] }, - { css: [{ value: 'bar.css' }], js: [{ value: 'bar.js' }] }, - ]; - t.same(incoming.css, [{ value: 'foo.css' }, { value: 'bar.css' }]); - t.same(incoming.js, [{ value: 'foo.js' }, { value: 'bar.js' }]); - t.end(); -}); - -tap.test('PodiumHttpIncoming.podlets - set legal value - should append those values with .css and .js to .css and .js', (t) => { - const incoming = new HttpIncoming(ADVANCED_REQ, SIMPLE_RES); - incoming.podlets = [ - { css: [{ value: 'foo.css' }], js: [{ value: 'foo.js' }] }, - { css: [{ value: 'bar.css' }], js: [{ value: 'bar.js' }] }, - { ssc: [{ value: 'xyz.css' }], sj: [{ value: 'xyz.js' }] }, - ]; - t.same(incoming.css, [{ value: 'foo.css' }, { value: 'bar.css' }]); - t.same(incoming.js, [{ value: 'foo.js' }, { value: 'bar.js' }]); - t.end(); -}); - -tap.test('PodiumHttpIncoming.podlets - get value - should throw', (t) => { - t.plan(1); - const incoming = new HttpIncoming(ADVANCED_REQ, SIMPLE_RES); - t.throws(() => { - const foo = incoming.podlets; // eslint-disable-line no-unused-vars - }, /The getter for .podlets is reserved for later implementation/, 'Should throw'); - t.end(); -}); - -tap.test('PodiumHttpIncoming.toJSON() - call method - should return object without ".request" and ".response"', (t) => { - const incoming = new HttpIncoming(SIMPLE_REQ, SIMPLE_RES); - const result = incoming.toJSON(); - t.equal(result.request, undefined); - t.equal(result.response, undefined); - t.same(result.url, {}); - t.same(result.params, {}); - t.same(result.context, {}); - t.same(result.view, {}); - t.notOk(result.proxy); - t.notOk(result.development); - t.equal(result.name, ''); - t.same(result.css, []); - t.same(result.js, []); - t.end(); -}); diff --git a/tests/http-incoming.test.js b/tests/http-incoming.test.js new file mode 100644 index 00000000..5b44e0b5 --- /dev/null +++ b/tests/http-incoming.test.js @@ -0,0 +1,313 @@ +import tap from 'tap'; +import HttpIncoming from '../lib/http-incoming.js'; + +const SIMPLE_REQ = {}; + +const ADVANCED_REQ = { + headers: { + host: 'localhost:3030', + }, + hostname: 'localhost', + url: '/some/path?foo=bar', +}; + +const SIMPLE_RES = { + locals: {}, +}; + +const SIMPLE_PARAMS = { + foo: 'bar', +}; + +tap.test( + 'PodiumHttpIncoming() - object tag - should be PodiumHttpIncoming', + (t) => { + const incoming = new HttpIncoming(SIMPLE_REQ); + t.equal( + Object.prototype.toString.call(incoming), + '[object PodiumHttpIncoming]', + ); + t.end(); + }, +); + +tap.test( + 'PodiumHttpIncoming() - no arguments given - should construct object with default values', + (t) => { + const incoming = new HttpIncoming(SIMPLE_REQ); + t.same(incoming.request, SIMPLE_REQ); + t.same(incoming.response, {}); + t.same(incoming.url, {}); + t.same(incoming.params, {}); + t.notOk(incoming.proxy); + t.same(incoming.context, {}); + t.notOk(incoming.development); + t.equal(incoming.name, ''); + t.same(incoming.css, []); + t.same(incoming.js, []); + t.end(); + }, +); + +tap.test( + 'PodiumHttpIncoming() - "request" argument given - should store request on ".request"', + (t) => { + const incoming = new HttpIncoming(ADVANCED_REQ); + t.same(incoming.request, ADVANCED_REQ); + t.end(); + }, +); + +tap.test( + 'PodiumHttpIncoming() - "request" argument given - should set parsed URL on ".url"', + (t) => { + const incoming = new HttpIncoming(ADVANCED_REQ); + t.equal(incoming.url.hostname, 'localhost'); + t.equal(incoming.url.host, 'localhost:3030'); + t.equal(incoming.url.port, '3030'); + t.equal(incoming.url.protocol, 'http:'); + t.equal(incoming.url.pathname, '/some/path'); + t.equal(incoming.url.search, '?foo=bar'); + t.end(); + }, +); + +tap.test( + 'PodiumHttpIncoming() - "response" argument given - should store request on ".response"', + (t) => { + const incoming = new HttpIncoming(ADVANCED_REQ, SIMPLE_RES); + t.same(incoming.response, SIMPLE_RES); + t.end(); + }, +); + +tap.test( + 'PodiumHttpIncoming() - "params" argument given - should store request on ".params"', + (t) => { + const incoming = new HttpIncoming( + ADVANCED_REQ, + SIMPLE_RES, + SIMPLE_PARAMS, + ); + t.same(incoming.params, SIMPLE_PARAMS); + t.end(); + }, +); + +tap.test('PodiumHttpIncoming.request - set value', (t) => { + t.plan(1); + const incoming = new HttpIncoming(ADVANCED_REQ, SIMPLE_RES); + t.throws( + () => { + incoming.request = 'foo'; + }, + /Cannot set read-only property./, + 'Should throw', + ); + t.end(); +}); + +tap.test('PodiumHttpIncoming.response - set value', (t) => { + t.plan(1); + const incoming = new HttpIncoming(ADVANCED_REQ, SIMPLE_RES); + t.throws( + () => { + incoming.response = 'foo'; + }, + /Cannot set read-only property./, + 'Should throw', + ); + t.end(); +}); + +tap.test('PodiumHttpIncoming.params - set value', (t) => { + t.plan(1); + const incoming = new HttpIncoming(ADVANCED_REQ, SIMPLE_RES); + t.throws( + () => { + incoming.params = 'foo'; + }, + /Cannot set read-only property./, + 'Should throw', + ); + t.end(); +}); + +tap.test('PodiumHttpIncoming.url - set value - should set value', (t) => { + const incoming = new HttpIncoming(ADVANCED_REQ, SIMPLE_RES); + // @ts-ignore The setter converts from a string to a URL 🙅 + incoming.url = 'http://localhost:8080/foo'; + t.equal(incoming.url.href, 'http://localhost:8080/foo'); + t.end(); +}); + +tap.test( + 'PodiumHttpIncoming.development - set value - should set value', + (t) => { + const incoming = new HttpIncoming(ADVANCED_REQ, SIMPLE_RES); + incoming.development = true; + t.ok(incoming.development); + t.end(); + }, +); + +tap.test('PodiumHttpIncoming.name - set value - should set value', (t) => { + const incoming = new HttpIncoming(ADVANCED_REQ, SIMPLE_RES); + incoming.name = 'a_name'; + t.equal(incoming.name, 'a_name'); + t.end(); +}); + +tap.test('PodiumHttpIncoming.css - set legal value - should set value', (t) => { + const incoming = new HttpIncoming(ADVANCED_REQ, SIMPLE_RES); + incoming.css = ['a_css']; + t.same(incoming.css, ['a_css']); + t.end(); +}); + +tap.test('PodiumHttpIncoming.css - set illegal value', (t) => { + t.plan(1); + const incoming = new HttpIncoming(ADVANCED_REQ, SIMPLE_RES); + t.throws( + () => { + // @ts-ignore Testing bad value + incoming.css = 'a_css'; + }, + /Value for property ".css" must be an Array/, + 'Should throw', + ); + t.end(); +}); + +tap.test('PodiumHttpIncoming.js - set legal value - should set value', (t) => { + const incoming = new HttpIncoming(ADVANCED_REQ, SIMPLE_RES); + incoming.js = ['a_js']; + t.same(incoming.js, ['a_js']); + t.end(); +}); + +tap.test('PodiumHttpIncoming.js - set illegal value - should throw', (t) => { + t.plan(1); + const incoming = new HttpIncoming(ADVANCED_REQ, SIMPLE_RES); + t.throws( + () => { + // @ts-ignore Testing bad value + incoming.js = 'a_js'; + }, + /Value for property ".js" must be an Array/, + 'Should throw', + ); + t.end(); +}); + +tap.test('PodiumHttpIncoming.proxy - set value - should set value', (t) => { + const incoming = new HttpIncoming(ADVANCED_REQ, SIMPLE_RES); + incoming.proxy = true; + t.ok(incoming.proxy); + t.end(); +}); + +tap.test('PodiumHttpIncoming.context - set value - should set value', (t) => { + const incoming = new HttpIncoming(ADVANCED_REQ, SIMPLE_RES); + incoming.context = { foo: 'bar' }; + t.same(incoming.context, { foo: 'bar' }); + t.end(); +}); + +tap.test( + 'PodiumHttpIncoming.view - no value - should return empty object', + (t) => { + const incoming = new HttpIncoming(ADVANCED_REQ, SIMPLE_RES); + t.same(incoming.view, {}); + t.end(); + }, +); + +tap.test('PodiumHttpIncoming.view - set value - should set value', (t) => { + const incoming = new HttpIncoming(ADVANCED_REQ, SIMPLE_RES); + incoming.view = { + title: 'foo', + }; + t.same(incoming.view, { + title: 'foo', + }); + t.end(); +}); + +tap.test( + 'PodiumHttpIncoming.podlets - set legal single value - should append values to .css and .js', + (t) => { + const incoming = new HttpIncoming(ADVANCED_REQ, SIMPLE_RES); + incoming.podlets = { + css: [{ value: 'foo.css' }], + js: [{ value: 'foo.js' }], + }; + t.same(incoming.css, [{ value: 'foo.css' }]); + t.same(incoming.js, [{ value: 'foo.js' }]); + t.end(); + }, +); + +tap.test( + 'PodiumHttpIncoming.podlets - set legal array value - should append values to .css and .js', + (t) => { + const incoming = new HttpIncoming(ADVANCED_REQ, SIMPLE_RES); + incoming.podlets = [ + { css: [{ value: 'foo.css' }], js: [{ value: 'foo.js' }] }, + { css: [{ value: 'bar.css' }], js: [{ value: 'bar.js' }] }, + ]; + t.same(incoming.css, [{ value: 'foo.css' }, { value: 'bar.css' }]); + t.same(incoming.js, [{ value: 'foo.js' }, { value: 'bar.js' }]); + t.end(); + }, +); + +tap.test( + 'PodiumHttpIncoming.podlets - set legal value - should append those values with .css and .js to .css and .js', + (t) => { + const incoming = new HttpIncoming(ADVANCED_REQ, SIMPLE_RES); + incoming.podlets = [ + { css: [{ value: 'foo.css' }], js: [{ value: 'foo.js' }] }, + { css: [{ value: 'bar.css' }], js: [{ value: 'bar.js' }] }, + { ssc: [{ value: 'xyz.css' }], sj: [{ value: 'xyz.js' }] }, + ]; + t.same(incoming.css, [{ value: 'foo.css' }, { value: 'bar.css' }]); + t.same(incoming.js, [{ value: 'foo.js' }, { value: 'bar.js' }]); + t.end(); + }, +); + +tap.test('PodiumHttpIncoming.podlets - get value - should throw', (t) => { + t.plan(1); + const incoming = new HttpIncoming(ADVANCED_REQ, SIMPLE_RES); + t.throws( + () => { + const foo = incoming.podlets; // eslint-disable-line no-unused-vars + }, + /The getter for .podlets is reserved for later implementation/, + 'Should throw', + ); + t.end(); +}); + +tap.test( + 'PodiumHttpIncoming.toJSON() - call method - should return object without ".request" and ".response"', + (t) => { + const incoming = new HttpIncoming(SIMPLE_REQ, SIMPLE_RES); + const result = incoming.toJSON(); + // @ts-ignore Testing for absence + t.equal(result.request, undefined); + // @ts-ignore Testing for absence + t.equal(result.response, undefined); + t.same(result.url, {}); + t.same(result.params, {}); + t.same(result.context, {}); + t.same(result.view, {}); + t.notOk(result.proxy); + t.notOk(result.development); + t.equal(result.name, ''); + t.same(result.css, []); + t.same(result.js, []); + t.end(); + }, +); diff --git a/tests/utils.js b/tests/utils.js deleted file mode 100644 index 7a74d222..00000000 --- a/tests/utils.js +++ /dev/null @@ -1,671 +0,0 @@ -import tap from 'tap'; -import * as utils from '../lib/utils.js' - -/** - * .isString() - */ - -tap.test('.isString() - no arguments given - should return false', (t) => { - const result = utils.isString(); - t.notOk(result); - t.end(); -}); - -tap.test('.isString() - arguments is an object - should return false', (t) => { - const result = utils.isString({}); - t.notOk(result); - t.end(); -}); - -tap.test('.isString() - arguments is an string - should return true', (t) => { - const result = utils.isString('function'); - t.ok(result); - t.end(); -}); - -tap.test('.isString() - arguments is an array - should return false', (t) => { - const result = utils.isString([]); - t.notOk(result); - t.end(); -}); - -tap.test('.isString() - arguments is an boolean - should return false', (t) => { - const result = utils.isString(true); - t.notOk(result); - t.end(); -}); - -tap.test('.isString() - arguments is an number - should return false', (t) => { - const result = utils.isString(42); - t.notOk(result); - t.end(); -}); - -tap.test('.isString() - arguments is an function - should return false', (t) => { - const result = utils.isString(() => {}); - t.notOk(result); - t.end(); -}); - -tap.test('.isString() - arguments is an arrow function - should return false', (t) => { - const result = utils.isString(() => {}); - t.notOk(result); - t.end(); -}); - -tap.test('.isString() - arguments is an async function - should return false', (t) => { - const result = utils.isString(async () => {}); - t.notOk(result); - t.end(); -}); - -/** - * .isFunction() - */ - -tap.test('.isFunction() - no arguments given - should return false', (t) => { - const result = utils.isFunction(); - t.notOk(result); - t.end(); -}); - -tap.test('.isFunction() - arguments is an object - should return false', (t) => { - const result = utils.isFunction({}); - t.notOk(result); - t.end(); -}); - -tap.test('.isFunction() - arguments is an string - should return false', (t) => { - const result = utils.isFunction('function'); - t.notOk(result); - t.end(); -}); - -tap.test('.isFunction() - arguments is an array - should return false', (t) => { - const result = utils.isFunction([]); - t.notOk(result); - t.end(); -}); - -tap.test('.isFunction() - arguments is an boolean - should return false', (t) => { - const result = utils.isFunction(true); - t.notOk(result); - t.end(); -}); - -tap.test('.isFunction() - arguments is an number - should return false', (t) => { - const result = utils.isFunction(42); - t.notOk(result); - t.end(); -}); - -tap.test('.isFunction() - arguments is an function - should return true', (t) => { - const result = utils.isFunction(() => {}); - t.ok(result); - t.end(); -}); - -tap.test('.isFunction() - arguments is an arrow function - should return true', (t) => { - const result = utils.isFunction(() => {}); - t.ok(result); - t.end(); -}); - -tap.test('.isFunction() - arguments is an async function - should return true', (t) => { - const result = utils.isFunction(async () => {}); - t.ok(result); - t.end(); -}); - -/** - * .pathnameBuilder() - */ - -tap.test('.pathnameBuilder() - no arguments - should return empty String', (t) => { - const result = utils.pathnameBuilder(); - t.equal(result, ''); - t.end(); -}); - -tap.test('.pathnameBuilder() - single argument with "/" - should return single "/"', (t) => { - const result = utils.pathnameBuilder('/'); - t.equal(result, '/'); - t.end(); -}); - -tap.test('.pathnameBuilder() - single argument with double "/" - should return single "/"', (t) => { - const result = utils.pathnameBuilder('//'); - t.equal(result, '/'); - t.end(); -}); - -tap.test('.pathnameBuilder() - multiple arguments with double "/" - should return single "/"', (t) => { - const result = utils.pathnameBuilder('//', '//', '//'); - t.equal(result, '/'); - t.end(); -}); - -tap.test('.pathnameBuilder() - single argument with no "/" - should start pathname without "/", separate each entry with single "/" and end with no "/"', (t) => { - const result = utils.pathnameBuilder('a'); - t.equal(result, 'a'); - t.end(); -}); - -tap.test('.pathnameBuilder() - single argument staring with "/" - should start pathname with "/", separate each entry with single "/" and end with no "/"', (t) => { - const result = utils.pathnameBuilder('/a'); - t.equal(result, '/a'); - t.end(); -}); - -tap.test('.pathnameBuilder() - single argument ending with "/" - should start pathname without "/", separate each entry with single "/" and end with no "/"', (t) => { - const result = utils.pathnameBuilder('a/'); - t.equal(result, 'a'); - t.end(); -}); - -tap.test('.pathnameBuilder() - single argument starting and ending with "/" - should start pathname with "/", separate each entry with single "/" and end with no "/"', (t) => { - const result = utils.pathnameBuilder('/a/'); - t.equal(result, '/a'); - t.end(); -}); - -tap.test('.pathnameBuilder() - multiple arguments all starting and ending with "/" - should start pathname with "/", separate each entry with single "/" and end with no "/"', (t) => { - const result = utils.pathnameBuilder('/a/b/', '/c/d/', '/e/f/'); - t.equal(result, '/a/b/c/d/e/f'); - t.end(); -}); - -tap.test('.pathnameBuilder() - multiple arguments one without "/" at end and one without "/" at the beginning - should start pathname with "/", separate each entry with single "/" and end with no "/"', (t) => { - const result = utils.pathnameBuilder('/a/b/', '/c/d', 'e/f/'); - t.equal(result, '/a/b/c/d/e/f'); - t.end(); -}); - -tap.test('.pathnameBuilder() - multiple arguments where the last ends with a "file extension" - should start pathname with "/", separate each entry with single "/" and end with no "/"', (t) => { - const result = utils.pathnameBuilder('/a/b/', '/c/d', 'e/f.json'); - t.equal(result, '/a/b/c/d/e/f.json'); - t.end(); -}); - -tap.test('.pathnameBuilder() - one argument is an Array of Strings - should start pathname with "/", separate each entry with single "/" and end with no "/"', (t) => { - const result = utils.pathnameBuilder('/a/b/', ['c', 'd'], 'e/f/'); - t.equal(result, '/a/b/c/d/e/f'); - t.end(); -}); - -tap.test('.pathnameBuilder() - one argument is "undefined" - should start pathname with "/", separate each entry with single "/" and end with no "/"', (t) => { - const result = utils.pathnameBuilder('/a/b/', undefined, '/c/d/'); - t.equal(result, '/a/b/c/d'); - t.end(); -}); - -tap.test('.pathnameBuilder() - one argument is not a String or Array - should start pathname with "/", separate each entry with single "/" and end with no "/"', (t) => { - const result = utils.pathnameBuilder('/a/b/', {}, '/c/d/'); - t.equal(result, '/a/b/c/d'); - t.end(); -}); - -tap.test('.pathnameBuilder() - emtpy arguments at the beginning, first String starts with "/" - should ignore empty arguments start pathname with "/", separate each entry with single "/" and end with no "/"', (t) => { - const result = utils.pathnameBuilder('', '', '/a/b/', ''); - t.equal(result, '/a/b'); - t.end(); -}); - -tap.test('.pathnameBuilder() - emtpy arguments at the beginning, first String starts without "/" - should ignore empty arguments start pathname without "/", separate each entry with single "/" and end with no "/"', (t) => { - const result = utils.pathnameBuilder('', '', 'a/b/', ''); - t.equal(result, 'a/b'); - t.end(); -}); - -/** - * .uriBuilder() - */ - -tap.test('.uriBuilder() - no arguments', (t) => { - t.plan(1); - t.throws(() => { - utils.uriBuilder(); - }, /Invalid URL/, 'Should throw'); - t.end(); -}); - -tap.test('.uriBuilder() - "base is empty"', (t) => { - t.plan(1); - t.throws(() => { - utils.uriBuilder('/podlet.html'); - }, /Invalid URL/, 'Should throw'); - t.end(); -}); - -tap.test('.uriBuilder() - "base" has long path with .json - should replace .json file with "input"', (t) => { - const result = utils.uriBuilder( - '/podlet.html', - 'http://localhost:7000/podlet/a/manifest.json', - ); - t.equal(result, 'http://localhost:7000/podlet/a/podlet.html'); - t.end(); -}); - -tap.test('.uriBuilder() - "base" has short path with .json - should replace .json file with "input"', (t) => { - const result = utils.uriBuilder( - '/podlet.html', - 'http://localhost:7000/manifest.json', - ); - t.equal(result, 'http://localhost:7000/podlet.html'); - t.end(); -}); - -tap.test('.uriBuilder() - "base" has long path without .json - should append "input" to "base"', (t) => { - const result = utils.uriBuilder( - '/podlet.html', - 'http://localhost:7000/podlet/a/', - ); - t.equal(result, 'http://localhost:7000/podlet/a/podlet.html'); - t.end(); -}); - -tap.test('.uriBuilder() - "base" has short path without .json - should append "input" to "base"', (t) => { - const result = utils.uriBuilder('/podlet.html', 'http://localhost:7000/'); - t.equal(result, 'http://localhost:7000/podlet.html'); - t.end(); -}); - -tap.test('.uriBuilder() - "input" does not begin with "/" - should replace .json file with "input"', (t) => { - const result = utils.uriBuilder( - 'podlet.html', - 'http://localhost:7000/podlet/a/manifest.json', - ); - t.equal(result, 'http://localhost:7000/podlet/a/podlet.html'); - t.end(); -}); - -tap.test('.uriBuilder() - "base" is without .json and does not end with "/" - should append "input" to "base"', (t) => { - const result = utils.uriBuilder('/podlet.html', 'http://localhost:7000'); - t.equal(result, 'http://localhost:7000/podlet.html'); - t.end(); -}); - -tap.test('.uriBuilder() - "extra" is provided - should append "extra"', (t) => { - const result = utils.uriBuilder( - '/podlet', - 'http://localhost:7000/foo/', - '/a/b', - ); - t.equal(result, 'http://localhost:7000/foo/podlet/a/b'); - t.end(); -}); - -/** - * .uriIsRelative() - */ - -tap.test('.uriIsRelative() - "uri" is relative - should return "true"', (t) => { - t.ok(utils.uriIsRelative('/manifest.json')); - t.end(); -}); - -tap.test('.uriIsRelative() - "uri" is absolute - should return "false"', (t) => { - t.notOk(utils.uriIsRelative('http://localhost:7000/manifest.json')); - t.end(); -}); - -/** - * .uriRelativeToAbsolute() - */ - -tap.test('.uriRelativeToAbsolute() - "input" is relative - should build absolute URI', (t) => { - const result = utils.uriRelativeToAbsolute( - '/podlet', - 'http://localhost:7000/foo/', - '/a/b', - ); - t.equal(result, 'http://localhost:7000/foo/podlet/a/b'); - t.end(); -}); - -tap.test('.uriRelativeToAbsolute() - "input" is absolute - should return absolute URI', (t) => { - const result = utils.uriRelativeToAbsolute( - 'http://localhost:7000/foo/podlet/a/b', - 'http://localhost:7000/bar/', - '/b/a', - ); - t.equal(result, 'http://localhost:7000/foo/podlet/a/b'); - t.end(); -}); - -tap.test('.uriRelativeToAbsolute() - no arguments', (t) => { - t.plan(1); - t.throws(() => { - utils.uriRelativeToAbsolute(); - }, /Invalid URL/, 'Should throw'); - t.end(); -}); - -/** - * .setAtLocalsPodium() - */ - -tap.test('.setAtLocalsPodium() - no arguments - should return res.locals.podium', (t) => { - t.same(utils.setAtLocalsPodium(), { - locals: { - podium: {}, - }, - }); - t.end(); -}); - -tap.test('.setAtLocalsPodium() - response argument is an empty object - should return res.locals.podium', (t) => { - t.same(utils.setAtLocalsPodium({}), { - locals: { - podium: {}, - }, - }); - t.end(); -}); - -tap.test('.setAtLocalsPodium() - response argument has .locals - should return res.locals.podium', (t) => { - t.same( - utils.setAtLocalsPodium({ - locals: {}, - }), - { - locals: { - podium: {}, - }, - }); - t.end(); -}); - -tap.test('.setAtLocalsPodium() - response argument has .locals.podium - should return res.locals.podium', (t) => { - t.same( - utils.setAtLocalsPodium({ - locals: { - podium: {}, - }, - }), - { - locals: { - podium: {}, - }, - }); - t.end(); -}); - -tap.test('.setAtLocalsPodium() - property argument has value - should set property', (t) => { - t.same(utils.setAtLocalsPodium({}, 'foo'), { - locals: { - podium: { - foo: undefined, - }, - }, - }); - t.end(); -}); - -tap.test('.setAtLocalsPodium() - property argument is empty string - should not set property', (t) => { - t.same(utils.setAtLocalsPodium({}, ''), { - locals: { - podium: {}, - }, - }); - t.end(); -}); - -tap.test('.setAtLocalsPodium() - property argument is not string - should not set property', (t) => { - t.same(utils.setAtLocalsPodium({}, []), { - locals: { - podium: {}, - }, - }); - t.end(); -}); - -tap.test('.setAtLocalsPodium() - value argument has value - should set value on property', (t) => { - t.same(utils.setAtLocalsPodium({}, 'foo', 'bar'), { - locals: { - podium: { - foo: 'bar', - }, - }, - }); - t.end(); -}); - -tap.test('.setAtLocalsPodium() - .locals.podium already have properties - should append new property and value', (t) => { - t.same( - utils.setAtLocalsPodium( - { - locals: { - podium: { - xyz: 'zyx', - }, - }, - }, - 'foo', - 'bar', - ), - { - locals: { - podium: { - xyz: 'zyx', - foo: 'bar', - }, - }, - }); - t.end(); -}); - -/** - * .getFromLocalsPodium() - */ - -tap.test('.getFromLocalsPodium() - no arguments - should return null', (t) => { - t.equal(utils.getFromLocalsPodium(), null); - t.end(); -}); - -tap.test('.getFromLocalsPodium() - response argument is an empty object - should return null', (t) => { - t.equal(utils.getFromLocalsPodium({}), null); - t.end(); -}); - -tap.test('.getFromLocalsPodium() - response argument has .locals - should return null', (t) => { - t.equal( - utils.getFromLocalsPodium({ - locals: {}, - }), - null); - t.end(); -}); - -tap.test('.getFromLocalsPodium() - response argument has .locals.podium - should return null', (t) => { - t.equal( - utils.getFromLocalsPodium({ - locals: { - podium: {}, - }, - }), - null); - t.end(); -}); - -tap.test('.getFromLocalsPodium() - property argument has value - should get property', (t) => { - t.equal( - utils.getFromLocalsPodium( - { - locals: { - podium: { - foo: undefined, - }, - }, - }, - 'foo', - ), - undefined); - t.end(); -}); - -tap.test('.getFromLocalsPodium() - property argument is empty string - should not get property', (t) => { - t.equal(utils.getFromLocalsPodium({}, ''), null); - t.end(); -}); - -tap.test('.getFromLocalsPodium() - property argument is not string - should not get property', (t) => { - t.equal(utils.getFromLocalsPodium({}, []), null); - t.end(); -}); - -tap.test('.getFromLocalsPodium() - value argument has value - should set value on property', (t) => { - t.equal( - utils.getFromLocalsPodium( - { - locals: { - podium: { - foo: 'bar', - }, - }, - }, - 'foo', - ), - 'bar'); - t.end(); -}); - -/** - * .duplicateOnLocalsPodium() - */ - -tap.test('.duplicateOnLocalsPodium() - property arguments has value - should set property on response object', (t) => { - t.same( - utils.duplicateOnLocalsPodium( - { - locals: { - podium: { - foo: 'foobar', - }, - }, - }, - 'foo', - 'bar', - ), - { - locals: { - podium: { - foo: 'foobar', - bar: 'foobar', - }, - }, - }); - t.end(); -}); - -tap.test('.duplicateOnLocalsPodium() - property arguments has no value - should leave response object untouched', (t) => { - t.same( - utils.duplicateOnLocalsPodium({ - locals: { - podium: { - foo: 'foobar', - }, - }, - }), - { - locals: { - podium: { - foo: 'foobar', - }, - }, - }); - t.end(); -}); - -tap.test('.duplicateOnLocalsPodium() - no arguments is given - should return an object with .locals.podium property', (t) => { - t.same(utils.duplicateOnLocalsPodium(), { - locals: { - podium: {}, - }, - }); - t.end(); -}); - -/** - * .serializeContext() - */ - -tap.test('.serializeContext() - no arguments given - should return empty object', (t) => { - const result = utils.serializeContext(); - t.same(result, {}); - t.end(); -}); - -tap.test('.serializeContext() - headers and context is given - should copy context into headers', (t) => { - const context = { - 'podium-foo': 'bar', - 'podium-bar': 'foo', - }; - - const headers = { - test: 'xyz', - }; - - const result = utils.serializeContext(headers, context); - t.same(result, { - 'podium-foo': 'bar', - 'podium-bar': 'foo', - test: 'xyz', - }); - t.end(); -}); - -tap.test('.serializeContext() - one key on the context is a function - should call the function and set value on headers', (t) => { - const context = { - 'podium-foo': 'bar', - 'podium-bar': name => `${name}-test`, - }; - - const headers = { - test: 'xyz', - }; - - const result = utils.serializeContext(headers, context, 'foo'); - t.same(result, { - 'podium-foo': 'bar', - 'podium-bar': 'foo-test', - test: 'xyz', - }); - t.end(); -}); - -/** - * .deserializeContext() - */ - -tap.test('.deserializeContext() - no arguments given - should return empty object', (t) => { - const result = utils.deserializeContext(); - t.same(result, {}); - t.end(); -}); - -tap.test('.deserializeContext() - headers argument with context is given - should return object with podium prefixed properties', (t) => { - const headers = { - bar: 'foo', - 'podium-foo': 'bar podium', - }; - - const result = utils.deserializeContext(headers); - t.same(result, { foo: 'bar podium' }); - t.end(); -}); - -tap.test('.deserializeContext() - prefix argument with alternate value is given - should return object with only given prefixed properties', (t) => { - const headers = { - bar: 'foo', - 'podium-foo': 'bar podium', - 'helium-foo': 'foo helium', - }; - - const result = utils.deserializeContext(headers, 'helium'); - t.same(result, { foo: 'foo helium' }); - t.end(); -}); - - diff --git a/tests/utils.test.js b/tests/utils.test.js new file mode 100644 index 00000000..e8369254 --- /dev/null +++ b/tests/utils.test.js @@ -0,0 +1,880 @@ +import tap from 'tap'; +import * as utils from '../lib/utils.js'; + +/** + * .isString() + */ + +tap.test('.isString() - no arguments given - should return false', (t) => { + const result = utils.isString(); + t.notOk(result); + t.end(); +}); + +tap.test('.isString() - arguments is an object - should return false', (t) => { + const result = utils.isString({}); + t.notOk(result); + t.end(); +}); + +tap.test('.isString() - arguments is an string - should return true', (t) => { + const result = utils.isString('function'); + t.ok(result); + t.end(); +}); + +tap.test('.isString() - arguments is an array - should return false', (t) => { + const result = utils.isString([]); + t.notOk(result); + t.end(); +}); + +tap.test('.isString() - arguments is an boolean - should return false', (t) => { + const result = utils.isString(true); + t.notOk(result); + t.end(); +}); + +tap.test('.isString() - arguments is an number - should return false', (t) => { + const result = utils.isString(42); + t.notOk(result); + t.end(); +}); + +tap.test( + '.isString() - arguments is an function - should return false', + (t) => { + const result = utils.isString(() => {}); + t.notOk(result); + t.end(); + }, +); + +tap.test( + '.isString() - arguments is an arrow function - should return false', + (t) => { + const result = utils.isString(() => {}); + t.notOk(result); + t.end(); + }, +); + +tap.test( + '.isString() - arguments is an async function - should return false', + (t) => { + const result = utils.isString(async () => {}); + t.notOk(result); + t.end(); + }, +); + +/** + * .isFunction() + */ + +tap.test('.isFunction() - no arguments given - should return false', (t) => { + const result = utils.isFunction(); + t.notOk(result); + t.end(); +}); + +tap.test( + '.isFunction() - arguments is an object - should return false', + (t) => { + const result = utils.isFunction({}); + t.notOk(result); + t.end(); + }, +); + +tap.test( + '.isFunction() - arguments is an string - should return false', + (t) => { + const result = utils.isFunction('function'); + t.notOk(result); + t.end(); + }, +); + +tap.test('.isFunction() - arguments is an array - should return false', (t) => { + const result = utils.isFunction([]); + t.notOk(result); + t.end(); +}); + +tap.test( + '.isFunction() - arguments is an boolean - should return false', + (t) => { + const result = utils.isFunction(true); + t.notOk(result); + t.end(); + }, +); + +tap.test( + '.isFunction() - arguments is an number - should return false', + (t) => { + const result = utils.isFunction(42); + t.notOk(result); + t.end(); + }, +); + +tap.test( + '.isFunction() - arguments is an function - should return true', + (t) => { + const result = utils.isFunction(() => {}); + t.ok(result); + t.end(); + }, +); + +tap.test( + '.isFunction() - arguments is an arrow function - should return true', + (t) => { + const result = utils.isFunction(() => {}); + t.ok(result); + t.end(); + }, +); + +tap.test( + '.isFunction() - arguments is an async function - should return true', + (t) => { + const result = utils.isFunction(async () => {}); + t.ok(result); + t.end(); + }, +); + +/** + * .pathnameBuilder() + */ + +tap.test( + '.pathnameBuilder() - no arguments - should return empty String', + (t) => { + const result = utils.pathnameBuilder(); + t.equal(result, ''); + t.end(); + }, +); + +tap.test( + '.pathnameBuilder() - single argument with "/" - should return single "/"', + (t) => { + const result = utils.pathnameBuilder('/'); + t.equal(result, '/'); + t.end(); + }, +); + +tap.test( + '.pathnameBuilder() - single argument with double "/" - should return single "/"', + (t) => { + const result = utils.pathnameBuilder('//'); + t.equal(result, '/'); + t.end(); + }, +); + +tap.test( + '.pathnameBuilder() - multiple arguments with double "/" - should return single "/"', + (t) => { + const result = utils.pathnameBuilder('//', '//', '//'); + t.equal(result, '/'); + t.end(); + }, +); + +tap.test( + '.pathnameBuilder() - single argument with no "/" - should start pathname without "/", separate each entry with single "/" and end with no "/"', + (t) => { + const result = utils.pathnameBuilder('a'); + t.equal(result, 'a'); + t.end(); + }, +); + +tap.test( + '.pathnameBuilder() - single argument staring with "/" - should start pathname with "/", separate each entry with single "/" and end with no "/"', + (t) => { + const result = utils.pathnameBuilder('/a'); + t.equal(result, '/a'); + t.end(); + }, +); + +tap.test( + '.pathnameBuilder() - single argument ending with "/" - should start pathname without "/", separate each entry with single "/" and end with no "/"', + (t) => { + const result = utils.pathnameBuilder('a/'); + t.equal(result, 'a'); + t.end(); + }, +); + +tap.test( + '.pathnameBuilder() - single argument starting and ending with "/" - should start pathname with "/", separate each entry with single "/" and end with no "/"', + (t) => { + const result = utils.pathnameBuilder('/a/'); + t.equal(result, '/a'); + t.end(); + }, +); + +tap.test( + '.pathnameBuilder() - multiple arguments all starting and ending with "/" - should start pathname with "/", separate each entry with single "/" and end with no "/"', + (t) => { + const result = utils.pathnameBuilder('/a/b/', '/c/d/', '/e/f/'); + t.equal(result, '/a/b/c/d/e/f'); + t.end(); + }, +); + +tap.test( + '.pathnameBuilder() - multiple arguments one without "/" at end and one without "/" at the beginning - should start pathname with "/", separate each entry with single "/" and end with no "/"', + (t) => { + const result = utils.pathnameBuilder('/a/b/', '/c/d', 'e/f/'); + t.equal(result, '/a/b/c/d/e/f'); + t.end(); + }, +); + +tap.test( + '.pathnameBuilder() - multiple arguments where the last ends with a "file extension" - should start pathname with "/", separate each entry with single "/" and end with no "/"', + (t) => { + const result = utils.pathnameBuilder('/a/b/', '/c/d', 'e/f.json'); + t.equal(result, '/a/b/c/d/e/f.json'); + t.end(); + }, +); + +tap.test( + '.pathnameBuilder() - one argument is an Array of Strings - should start pathname with "/", separate each entry with single "/" and end with no "/"', + (t) => { + const result = utils.pathnameBuilder('/a/b/', ['c', 'd'], 'e/f/'); + t.equal(result, '/a/b/c/d/e/f'); + t.end(); + }, +); + +tap.test( + '.pathnameBuilder() - one argument is "undefined" - should start pathname with "/", separate each entry with single "/" and end with no "/"', + (t) => { + // @ts-ignore Testing bad value + const result = utils.pathnameBuilder('/a/b/', undefined, '/c/d/'); + t.equal(result, '/a/b/c/d'); + t.end(); + }, +); + +tap.test( + '.pathnameBuilder() - one argument is not a String or Array - should start pathname with "/", separate each entry with single "/" and end with no "/"', + (t) => { + // @ts-ignore Testing bad value + const result = utils.pathnameBuilder('/a/b/', {}, '/c/d/'); + t.equal(result, '/a/b/c/d'); + t.end(); + }, +); + +tap.test( + '.pathnameBuilder() - emtpy arguments at the beginning, first String starts with "/" - should ignore empty arguments start pathname with "/", separate each entry with single "/" and end with no "/"', + (t) => { + const result = utils.pathnameBuilder('', '', '/a/b/', ''); + t.equal(result, '/a/b'); + t.end(); + }, +); + +tap.test( + '.pathnameBuilder() - emtpy arguments at the beginning, first String starts without "/" - should ignore empty arguments start pathname without "/", separate each entry with single "/" and end with no "/"', + (t) => { + const result = utils.pathnameBuilder('', '', 'a/b/', ''); + t.equal(result, 'a/b'); + t.end(); + }, +); + +/** + * .uriBuilder() + */ + +tap.test('.uriBuilder() - no arguments', (t) => { + t.plan(1); + t.throws( + () => { + utils.uriBuilder(); + }, + /Invalid URL/, + 'Should throw', + ); + t.end(); +}); + +tap.test('.uriBuilder() - "base is empty"', (t) => { + t.plan(1); + t.throws( + () => { + utils.uriBuilder('/podlet.html'); + }, + /Invalid URL/, + 'Should throw', + ); + t.end(); +}); + +tap.test( + '.uriBuilder() - "base" has long path with .json - should replace .json file with "input"', + (t) => { + const result = utils.uriBuilder( + '/podlet.html', + 'http://localhost:7000/podlet/a/manifest.json', + ); + t.equal(result, 'http://localhost:7000/podlet/a/podlet.html'); + t.end(); + }, +); + +tap.test( + '.uriBuilder() - "base" has short path with .json - should replace .json file with "input"', + (t) => { + const result = utils.uriBuilder( + '/podlet.html', + 'http://localhost:7000/manifest.json', + ); + t.equal(result, 'http://localhost:7000/podlet.html'); + t.end(); + }, +); + +tap.test( + '.uriBuilder() - "base" has long path without .json - should append "input" to "base"', + (t) => { + const result = utils.uriBuilder( + '/podlet.html', + 'http://localhost:7000/podlet/a/', + ); + t.equal(result, 'http://localhost:7000/podlet/a/podlet.html'); + t.end(); + }, +); + +tap.test( + '.uriBuilder() - "base" has short path without .json - should append "input" to "base"', + (t) => { + const result = utils.uriBuilder( + '/podlet.html', + 'http://localhost:7000/', + ); + t.equal(result, 'http://localhost:7000/podlet.html'); + t.end(); + }, +); + +tap.test( + '.uriBuilder() - "input" does not begin with "/" - should replace .json file with "input"', + (t) => { + const result = utils.uriBuilder( + 'podlet.html', + 'http://localhost:7000/podlet/a/manifest.json', + ); + t.equal(result, 'http://localhost:7000/podlet/a/podlet.html'); + t.end(); + }, +); + +tap.test( + '.uriBuilder() - "base" is without .json and does not end with "/" - should append "input" to "base"', + (t) => { + const result = utils.uriBuilder( + '/podlet.html', + 'http://localhost:7000', + ); + t.equal(result, 'http://localhost:7000/podlet.html'); + t.end(); + }, +); + +tap.test('.uriBuilder() - "extra" is provided - should append "extra"', (t) => { + const result = utils.uriBuilder( + '/podlet', + 'http://localhost:7000/foo/', + '/a/b', + ); + t.equal(result, 'http://localhost:7000/foo/podlet/a/b'); + t.end(); +}); + +/** + * .uriIsRelative() + */ + +tap.test('.uriIsRelative() - "uri" is relative - should return "true"', (t) => { + t.ok(utils.uriIsRelative('/manifest.json')); + t.end(); +}); + +tap.test( + '.uriIsRelative() - "uri" is absolute - should return "false"', + (t) => { + t.notOk(utils.uriIsRelative('http://localhost:7000/manifest.json')); + t.end(); + }, +); + +/** + * .uriRelativeToAbsolute() + */ + +tap.test( + '.uriRelativeToAbsolute() - "input" is relative - should build absolute URI', + (t) => { + const result = utils.uriRelativeToAbsolute( + '/podlet', + 'http://localhost:7000/foo/', + '/a/b', + ); + t.equal(result, 'http://localhost:7000/foo/podlet/a/b'); + t.end(); + }, +); + +tap.test( + '.uriRelativeToAbsolute() - "input" is absolute - should return absolute URI', + (t) => { + const result = utils.uriRelativeToAbsolute( + 'http://localhost:7000/foo/podlet/a/b', + 'http://localhost:7000/bar/', + '/b/a', + ); + t.equal(result, 'http://localhost:7000/foo/podlet/a/b'); + t.end(); + }, +); + +tap.test('.uriRelativeToAbsolute() - no arguments', (t) => { + t.plan(1); + t.throws( + () => { + utils.uriRelativeToAbsolute(); + }, + /Invalid URL/, + 'Should throw', + ); + t.end(); +}); + +/** + * .setAtLocalsPodium() + */ + +tap.test( + '.setAtLocalsPodium() - no arguments - should return res.locals.podium', + (t) => { + t.same(utils.setAtLocalsPodium(), { + locals: { + podium: {}, + }, + }); + t.end(); + }, +); + +tap.test( + '.setAtLocalsPodium() - response argument is an empty object - should return res.locals.podium', + (t) => { + t.same(utils.setAtLocalsPodium({}), { + locals: { + podium: {}, + }, + }); + t.end(); + }, +); + +tap.test( + '.setAtLocalsPodium() - response argument has .locals - should return res.locals.podium', + (t) => { + t.same( + utils.setAtLocalsPodium({ + locals: {}, + }), + { + locals: { + podium: {}, + }, + }, + ); + t.end(); + }, +); + +tap.test( + '.setAtLocalsPodium() - response argument has .locals.podium - should return res.locals.podium', + (t) => { + t.same( + utils.setAtLocalsPodium({ + locals: { + podium: {}, + }, + }), + { + locals: { + podium: {}, + }, + }, + ); + t.end(); + }, +); + +tap.test( + '.setAtLocalsPodium() - property argument has value - should set property', + (t) => { + t.same(utils.setAtLocalsPodium({}, 'foo'), { + locals: { + podium: { + foo: undefined, + }, + }, + }); + t.end(); + }, +); + +tap.test( + '.setAtLocalsPodium() - property argument is empty string - should not set property', + (t) => { + t.same(utils.setAtLocalsPodium({}, ''), { + locals: { + podium: {}, + }, + }); + t.end(); + }, +); + +tap.test( + '.setAtLocalsPodium() - property argument is not string - should not set property', + (t) => { + // @ts-ignore Testing bad value + t.same(utils.setAtLocalsPodium({}, []), { + locals: { + podium: {}, + }, + }); + t.end(); + }, +); + +tap.test( + '.setAtLocalsPodium() - value argument has value - should set value on property', + (t) => { + t.same(utils.setAtLocalsPodium({}, 'foo', 'bar'), { + locals: { + podium: { + foo: 'bar', + }, + }, + }); + t.end(); + }, +); + +tap.test( + '.setAtLocalsPodium() - .locals.podium already have properties - should append new property and value', + (t) => { + t.same( + utils.setAtLocalsPodium( + { + locals: { + podium: { + xyz: 'zyx', + }, + }, + }, + 'foo', + 'bar', + ), + { + locals: { + podium: { + xyz: 'zyx', + foo: 'bar', + }, + }, + }, + ); + t.end(); + }, +); + +/** + * .getFromLocalsPodium() + */ + +tap.test('.getFromLocalsPodium() - no arguments - should return null', (t) => { + t.equal(utils.getFromLocalsPodium(), null); + t.end(); +}); + +tap.test( + '.getFromLocalsPodium() - response argument is an empty object - should return null', + (t) => { + t.equal(utils.getFromLocalsPodium({}), null); + t.end(); + }, +); + +tap.test( + '.getFromLocalsPodium() - response argument has .locals - should return null', + (t) => { + t.equal( + utils.getFromLocalsPodium({ + locals: {}, + }), + null, + ); + t.end(); + }, +); + +tap.test( + '.getFromLocalsPodium() - response argument has .locals.podium - should return null', + (t) => { + t.equal( + utils.getFromLocalsPodium({ + locals: { + podium: {}, + }, + }), + null, + ); + t.end(); + }, +); + +tap.test( + '.getFromLocalsPodium() - property argument has value - should get property', + (t) => { + t.equal( + utils.getFromLocalsPodium( + { + locals: { + podium: { + foo: undefined, + }, + }, + }, + 'foo', + ), + undefined, + ); + t.end(); + }, +); + +tap.test( + '.getFromLocalsPodium() - property argument is empty string - should not get property', + (t) => { + t.equal(utils.getFromLocalsPodium({}, ''), null); + t.end(); + }, +); + +tap.test( + '.getFromLocalsPodium() - property argument is not string - should not get property', + (t) => { + // @ts-ignore Testing bad value + t.equal(utils.getFromLocalsPodium({}, []), null); + t.end(); + }, +); + +tap.test( + '.getFromLocalsPodium() - value argument has value - should set value on property', + (t) => { + t.equal( + utils.getFromLocalsPodium( + { + locals: { + podium: { + foo: 'bar', + }, + }, + }, + 'foo', + ), + 'bar', + ); + t.end(); + }, +); + +/** + * .duplicateOnLocalsPodium() + */ + +tap.test( + '.duplicateOnLocalsPodium() - property arguments has value - should set property on response object', + (t) => { + t.same( + utils.duplicateOnLocalsPodium( + { + locals: { + podium: { + foo: 'foobar', + }, + }, + }, + 'foo', + 'bar', + ), + { + locals: { + podium: { + foo: 'foobar', + bar: 'foobar', + }, + }, + }, + ); + t.end(); + }, +); + +tap.test( + '.duplicateOnLocalsPodium() - property arguments has no value - should leave response object untouched', + (t) => { + t.same( + utils.duplicateOnLocalsPodium({ + locals: { + podium: { + foo: 'foobar', + }, + }, + }), + { + locals: { + podium: { + foo: 'foobar', + }, + }, + }, + ); + t.end(); + }, +); + +tap.test( + '.duplicateOnLocalsPodium() - no arguments is given - should return an object with .locals.podium property', + (t) => { + t.same(utils.duplicateOnLocalsPodium(), { + locals: { + podium: {}, + }, + }); + t.end(); + }, +); + +/** + * .serializeContext() + */ + +tap.test( + '.serializeContext() - no arguments given - should return empty object', + (t) => { + const result = utils.serializeContext(); + t.same(result, {}); + t.end(); + }, +); + +tap.test( + '.serializeContext() - headers and context is given - should copy context into headers', + (t) => { + const context = { + 'podium-foo': 'bar', + 'podium-bar': 'foo', + }; + + const headers = { + test: 'xyz', + }; + + const result = utils.serializeContext(headers, context); + t.same(result, { + 'podium-foo': 'bar', + 'podium-bar': 'foo', + test: 'xyz', + }); + t.end(); + }, +); + +tap.test( + '.serializeContext() - one key on the context is a function - should call the function and set value on headers', + (t) => { + const context = { + 'podium-foo': 'bar', + 'podium-bar': (name) => `${name}-test`, + }; + + const headers = { + test: 'xyz', + }; + + const result = utils.serializeContext(headers, context, 'foo'); + t.same(result, { + 'podium-foo': 'bar', + 'podium-bar': 'foo-test', + test: 'xyz', + }); + t.end(); + }, +); + +/** + * .deserializeContext() + */ + +tap.test( + '.deserializeContext() - no arguments given - should return empty object', + (t) => { + const result = utils.deserializeContext(); + t.same(result, {}); + t.end(); + }, +); + +tap.test( + '.deserializeContext() - headers argument with context is given - should return object with podium prefixed properties', + (t) => { + const headers = { + bar: 'foo', + 'podium-foo': 'bar podium', + }; + + const result = utils.deserializeContext(headers); + t.same(result, { foo: 'bar podium' }); + t.end(); + }, +); + +tap.test( + '.deserializeContext() - prefix argument with alternate value is given - should return object with only given prefixed properties', + (t) => { + const headers = { + bar: 'foo', + 'podium-foo': 'bar podium', + 'helium-foo': 'foo helium', + }; + + const result = utils.deserializeContext(headers, 'helium'); + t.same(result, { foo: 'foo helium' }); + t.end(); + }, +); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 00000000..564be4fc --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "lib": ["es2020"], + "module": "nodenext", + "target": "es2020", + "resolveJsonModule": true, + "checkJs": true, + "allowJs": true, + "moduleResolution": "nodenext", + "declaration": true, + "skipLibCheck": true, + "allowSyntheticDefaultImports": true, + "outDir": "types" + }, + "include": ["./lib/**/*.js"] +} diff --git a/tsconfig.test.json b/tsconfig.test.json new file mode 100644 index 00000000..801ba2e6 --- /dev/null +++ b/tsconfig.test.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.json", + "include": ["./tests/**/*.js"], + "compilerOptions": { + "module": "nodenext", + "moduleResolution": "nodenext", + "noEmit": true + } +}