diff --git a/lib/asset-js.js b/lib/asset-js.js index 0b9766aa..d53f4d82 100644 --- a/lib/asset-js.js +++ b/lib/asset-js.js @@ -1,7 +1,8 @@ - - import { uriIsRelative, pathnameBuilder } from './utils.js'; -import { buildScriptElement, buildReactScriptAttributes } from './html-utils.js'; +import { + buildScriptElement, + buildReactScriptAttributes, +} from './html-utils.js'; const inspect = Symbol.for('nodejs.util.inspect.custom'); @@ -29,6 +30,23 @@ export default class PodiumAssetJs { #data; #strategy; #scope; + + /** + * @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 {array} [options.data] + * @param {string} [options.strategy] + * @param {string} [options.scope] + */ constructor({ referrerpolicy = '', crossorigin = undefined, @@ -44,11 +62,12 @@ export default class PodiumAssetJs { strategy = undefined, scope = undefined, } = {}) { - if (!toUndefined(value)) + if (!toUndefined(value)) { throw new Error( `Value for argument variable "value", "${value}", is not valid`, ); - + } + this.#pathname = pathname; this.#prefix = prefix; this.#value = value; @@ -57,8 +76,15 @@ export default class PodiumAssetJs { this.#crossorigin = crossorigin; this.#integrity = integrity; this.#nomodule = nomodule; - this.#async = async; - this.#defer = defer; + + if (async && defer) { + this.#async = false; + this.#defer = true; + } else { + this.#async = async; + this.#defer = defer; + } + this.#type = type; this.#data = data; this.#strategy = strategy; @@ -148,7 +174,7 @@ export default class PodiumAssetJs { } get strategy() { - return this.#strategy; + return this.#strategy; } set strategy(value) { @@ -156,7 +182,7 @@ export default class PodiumAssetJs { } get scope() { - return this.#scope; + return this.#scope; } set scope(value) { @@ -204,4 +230,4 @@ export default class PodiumAssetJs { get [Symbol.toStringTag]() { return 'PodiumAssetJs'; } -}; +} diff --git a/tests/asset-js.js b/tests/asset-js.js index 9cddb63f..754f16da 100644 --- a/tests/asset-js.js +++ b/tests/asset-js.js @@ -4,229 +4,301 @@ import AssetJs from '../lib/asset-js.js'; tap.test('Js() - object tag - should be PodiumAssetJs', (t) => { const obj = new AssetJs({ value: '/foo' }); - t.equal(Object.prototype.toString.call(obj), - '[object PodiumAssetJs]', - ); + t.equal(Object.prototype.toString.call(obj), '[object PodiumAssetJs]'); t.end(); -}); +}); tap.test('Js() - no value given to "value" argument', (t) => { t.plan(1); - t.throws(() => { - const obj = new AssetJs(); // eslint-disable-line no-unused-vars - }, /Value for argument variable "value", "undefined", is not valid/, 'Should throw'); - t.end(); -}); - -tap.test('Js() - no arguments given - should construct object with default values', (t) => { - const obj = new AssetJs({ value: '/foo' }); - t.equal(obj.referrerpolicy, ''); - t.equal(obj.crossorigin, undefined); - t.equal(obj.integrity, ''); - t.notOk(obj.nomodule); - t.notOk(obj.async); - t.notOk(obj.defer); - t.equal(obj.value, '/foo'); - t.equal(obj.type, 'default'); - t.equal(obj.src, '/foo'); - t.same(obj.data, []); - t.end(); -}); - -tap.test('Js() - no arguments given - should construct JSON with default values', (t) => { - const obj = new AssetJs({ value: '/foo' }); - const json = JSON.parse(JSON.stringify(obj)); - t.same(json, { - value: '/foo', - type: 'default', - }); - t.end(); -}); - -tap.test('Js() - pathname is given - prefix is unset - should NOT append pathname to "value"', (t) => { - const obj = new AssetJs({ value: '/foo', pathname: '/bar' }); - t.equal(obj.value, '/foo'); - t.equal(obj.src, '/foo'); - t.end(); -}); - -tap.test('Js() - pathname is given - prefix is false - should NOT append pathname to "value"', (t) => { - const obj = new AssetJs({ value: '/foo', pathname: '/bar', prefix: false }); - t.equal(obj.value, '/foo'); - t.equal(obj.src, '/foo'); - t.end(); -}); - -tap.test('Js() - pathname is given - prefix is true - should append pathname to "value"', (t) => { - const obj = new AssetJs({ value: '/foo', pathname: '/bar', prefix: true }); - t.equal(obj.value, '/bar/foo'); - t.equal(obj.src, '/bar/foo'); - t.end(); -}); - -tap.test('Js() - pathname is given - prefix is unset - should NOT append pathname to "value" for toJSON()', (t) => { - const obj = new AssetJs({ value: '/foo', pathname: '/bar' }); - const json = JSON.parse(JSON.stringify(obj)); - t.same(json, { - value: '/foo', - type: 'default', - }); - t.end(); -}); - -tap.test('Js() - pathname is given - prefix is false - should NOT append pathname to "value" for toJSON()', (t) => { - const obj = new AssetJs({ value: '/foo', pathname: '/bar', prefix: false }); - const json = JSON.parse(JSON.stringify(obj)); - t.same(json, { - value: '/foo', - type: 'default', - }); - t.end(); -}); - -tap.test('Js() - pathname is given - prefix is true - should NOT append pathname to "value" for toJSON()', (t) => { - const obj = new AssetJs({ value: '/foo', pathname: '/bar', prefix: true }); - const json = JSON.parse(JSON.stringify(obj)); - t.same(json, { - value: '/foo', - type: 'default', - }); - t.end(); -}); - -tap.test('Js() - pathname is given - prefix is unset - should NOT append pathname to "src" for toHTML()', (t) => { - const obj = new AssetJs({ value: '/foo', pathname: '/bar' }); - t.equal(obj.toHTML(), ''); - t.end(); -}); - -tap.test('Js() - pathname is given - prefix is false - should NOT append pathname to "src" for toHTML()', (t) => { - const obj = new AssetJs({ value: '/foo', pathname: '/bar', prefix: false }); - t.equal(obj.value, '/foo'); - t.equal(obj.toHTML(), ''); - t.end(); -}); - -tap.test('Js() - pathname is given - prefix is true - should append pathname to "src" for toHTML()', (t) => { - const obj = new AssetJs({ value: '/foo', pathname: '/bar', prefix: true }); - t.equal(obj.toHTML(), ''); - t.end(); -}); - -tap.test('Js() - value if absoulte - pathname is given - prefix is true - should NOT append pathname to "value"', (t) => { - const obj = new AssetJs({ - value: 'http://somewhere.else.com/foo', - pathname: '/bar', - prefix: true, - }); - t.equal(obj.value, 'http://somewhere.else.com/foo'); - t.equal(obj.src, 'http://somewhere.else.com/foo'); - - const json = JSON.parse(JSON.stringify(obj)); - t.same(json, { - value: 'http://somewhere.else.com/foo', - type: 'default', - }); - - t.equal(obj.toHTML(), - '', - ); - t.end(); -}); - -tap.test('Js() - set "referrerpolicy" - should construct object as t.equaled', (t) => { - const obj = new AssetJs({ - value: '/foo', - }); - - obj.referrerpolicy = 'bar'; - - t.equal(obj.referrerpolicy, 'bar'); - t.equal(obj.toHTML(), - '', - ); - - const json = JSON.parse(JSON.stringify(obj)); - t.same(json, { - referrerpolicy: 'bar', - value: '/foo', - type: 'default', - }); - - const repl = new AssetJs(json); - t.equal(repl.referrerpolicy, 'bar'); - t.end(); -}); - -tap.test('Js() - set "crossorigin" - should construct object as t.equaled', (t) => { - const obj = new AssetJs({ - value: '/foo', - }); - - obj.crossorigin = 'bar'; - - t.equal(obj.crossorigin, 'bar'); - t.equal(obj.toHTML(), - '', + t.throws( + () => { + const obj = new AssetJs(); // eslint-disable-line no-unused-vars + }, + /Value for argument variable "value", "undefined", is not valid/, + 'Should throw', ); - - const json = JSON.parse(JSON.stringify(obj)); - t.same(json, { - crossorigin: 'bar', - value: '/foo', - type: 'default', - }); - - const repl = new AssetJs(json); - t.equal(repl.crossorigin, 'bar'); t.end(); -}); - -tap.test('Js() - set "integrity" - should construct object as t.equaled', (t) => { - const obj = new AssetJs({ - value: '/foo', - }); - - obj.integrity = 'bar'; - - t.equal(obj.integrity, 'bar'); - t.equal(obj.toHTML(), - '', - ); - - const json = JSON.parse(JSON.stringify(obj)); - t.same(json, { - integrity: 'bar', - value: '/foo', - type: 'default', - }); - - const repl = new AssetJs(json); - t.equal(repl.integrity, 'bar'); - t.end(); -}); - -tap.test('Js() - set "nomodule" - should construct object as t.equaled', (t) => { - const obj = new AssetJs({ - value: '/foo', - }); - - obj.nomodule = true; - - t.ok(obj.nomodule); - t.equal(obj.toHTML(), ''); - - const json = JSON.parse(JSON.stringify(obj)); - t.same(json, { - nomodule: true, - value: '/foo', - type: 'default', - }); - - const repl = new AssetJs(json); - t.ok(repl.nomodule); - t.end(); -}); +}); + +tap.test( + 'Js() - no arguments given - should construct object with default values', + (t) => { + const obj = new AssetJs({ value: '/foo' }); + t.equal(obj.referrerpolicy, ''); + t.equal(obj.crossorigin, undefined); + t.equal(obj.integrity, ''); + t.notOk(obj.nomodule); + t.notOk(obj.async); + t.notOk(obj.defer); + t.equal(obj.value, '/foo'); + t.equal(obj.type, 'default'); + t.equal(obj.src, '/foo'); + t.same(obj.data, []); + t.end(); + }, +); + +tap.test( + 'Js() - no arguments given - should construct JSON with default values', + (t) => { + const obj = new AssetJs({ value: '/foo' }); + const json = JSON.parse(JSON.stringify(obj)); + t.same(json, { + value: '/foo', + type: 'default', + }); + t.end(); + }, +); + +tap.test( + 'Js() - pathname is given - prefix is unset - should NOT append pathname to "value"', + (t) => { + const obj = new AssetJs({ value: '/foo', pathname: '/bar' }); + t.equal(obj.value, '/foo'); + t.equal(obj.src, '/foo'); + t.end(); + }, +); + +tap.test( + 'Js() - pathname is given - prefix is false - should NOT append pathname to "value"', + (t) => { + const obj = new AssetJs({ + value: '/foo', + pathname: '/bar', + prefix: false, + }); + t.equal(obj.value, '/foo'); + t.equal(obj.src, '/foo'); + t.end(); + }, +); + +tap.test( + 'Js() - pathname is given - prefix is true - should append pathname to "value"', + (t) => { + const obj = new AssetJs({ + value: '/foo', + pathname: '/bar', + prefix: true, + }); + t.equal(obj.value, '/bar/foo'); + t.equal(obj.src, '/bar/foo'); + t.end(); + }, +); + +tap.test( + 'Js() - pathname is given - prefix is unset - should NOT append pathname to "value" for toJSON()', + (t) => { + const obj = new AssetJs({ value: '/foo', pathname: '/bar' }); + const json = JSON.parse(JSON.stringify(obj)); + t.same(json, { + value: '/foo', + type: 'default', + }); + t.end(); + }, +); + +tap.test( + 'Js() - pathname is given - prefix is false - should NOT append pathname to "value" for toJSON()', + (t) => { + const obj = new AssetJs({ + value: '/foo', + pathname: '/bar', + prefix: false, + }); + const json = JSON.parse(JSON.stringify(obj)); + t.same(json, { + value: '/foo', + type: 'default', + }); + t.end(); + }, +); + +tap.test( + 'Js() - pathname is given - prefix is true - should NOT append pathname to "value" for toJSON()', + (t) => { + const obj = new AssetJs({ + value: '/foo', + pathname: '/bar', + prefix: true, + }); + const json = JSON.parse(JSON.stringify(obj)); + t.same(json, { + value: '/foo', + type: 'default', + }); + t.end(); + }, +); + +tap.test( + 'Js() - pathname is given - prefix is unset - should NOT append pathname to "src" for toHTML()', + (t) => { + const obj = new AssetJs({ value: '/foo', pathname: '/bar' }); + t.equal(obj.toHTML(), ''); + t.end(); + }, +); + +tap.test( + 'Js() - pathname is given - prefix is false - should NOT append pathname to "src" for toHTML()', + (t) => { + const obj = new AssetJs({ + value: '/foo', + pathname: '/bar', + prefix: false, + }); + t.equal(obj.value, '/foo'); + t.equal(obj.toHTML(), ''); + t.end(); + }, +); + +tap.test( + 'Js() - pathname is given - prefix is true - should append pathname to "src" for toHTML()', + (t) => { + const obj = new AssetJs({ + value: '/foo', + pathname: '/bar', + prefix: true, + }); + t.equal(obj.toHTML(), ''); + t.end(); + }, +); + +tap.test( + 'Js() - value if absoulte - pathname is given - prefix is true - should NOT append pathname to "value"', + (t) => { + const obj = new AssetJs({ + value: 'http://somewhere.else.com/foo', + pathname: '/bar', + prefix: true, + }); + t.equal(obj.value, 'http://somewhere.else.com/foo'); + t.equal(obj.src, 'http://somewhere.else.com/foo'); + + const json = JSON.parse(JSON.stringify(obj)); + t.same(json, { + value: 'http://somewhere.else.com/foo', + type: 'default', + }); + + t.equal( + obj.toHTML(), + '', + ); + t.end(); + }, +); + +tap.test( + 'Js() - set "referrerpolicy" - should construct object as t.equaled', + (t) => { + const obj = new AssetJs({ + value: '/foo', + }); + + obj.referrerpolicy = 'bar'; + + t.equal(obj.referrerpolicy, 'bar'); + t.equal( + obj.toHTML(), + '', + ); + + const json = JSON.parse(JSON.stringify(obj)); + t.same(json, { + referrerpolicy: 'bar', + value: '/foo', + type: 'default', + }); + + const repl = new AssetJs(json); + t.equal(repl.referrerpolicy, 'bar'); + t.end(); + }, +); + +tap.test( + 'Js() - set "crossorigin" - should construct object as t.equaled', + (t) => { + const obj = new AssetJs({ + 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: 'default', + }); + + const repl = new AssetJs(json); + t.equal(repl.crossorigin, 'bar'); + t.end(); + }, +); + +tap.test( + 'Js() - set "integrity" - should construct object as t.equaled', + (t) => { + const obj = new AssetJs({ + value: '/foo', + }); + + obj.integrity = 'bar'; + + t.equal(obj.integrity, 'bar'); + t.equal(obj.toHTML(), ''); + + const json = JSON.parse(JSON.stringify(obj)); + t.same(json, { + integrity: 'bar', + value: '/foo', + type: 'default', + }); + + const repl = new AssetJs(json); + t.equal(repl.integrity, 'bar'); + t.end(); + }, +); + +tap.test( + 'Js() - set "nomodule" - should construct object as t.equaled', + (t) => { + const obj = new AssetJs({ + value: '/foo', + }); + + obj.nomodule = true; + + t.ok(obj.nomodule); + t.equal(obj.toHTML(), ''); + + const json = JSON.parse(JSON.stringify(obj)); + t.same(json, { + nomodule: true, + value: '/foo', + type: 'default', + }); + + const repl = new AssetJs(json); + t.ok(repl.nomodule); + t.end(); + }, +); tap.test('Js() - set "async" - should construct object as t.equaled', (t) => { const obj = new AssetJs({ @@ -248,7 +320,7 @@ tap.test('Js() - set "async" - should construct object as t.equaled', (t) => { const repl = new AssetJs(json); t.ok(repl.async); t.end(); -}); +}); tap.test('Js() - set "defer" - should construct object as t.equaled', (t) => { const obj = new AssetJs({ @@ -270,7 +342,7 @@ tap.test('Js() - set "defer" - should construct object as t.equaled', (t) => { const repl = new AssetJs(json); t.ok(repl.defer); t.end(); -}); +}); tap.test('Js() - set "type" - should construct object as t.equaled', (t) => { const obj = new AssetJs({ @@ -291,66 +363,93 @@ tap.test('Js() - set "type" - should construct object as t.equaled', (t) => { const repl = new AssetJs(json); t.equal(repl.type, 'esm'); t.end(); -}); +}); tap.test('Js() - set "data" - should construct object as t.equaled', (t) => { const obj = new AssetJs({ value: '/foo', }); - obj.data = [{ - key: 'foo', - value: 'bar' - }]; + obj.data = [ + { + key: 'foo', + value: 'bar', + }, + ]; - t.same(obj.data, [{ - key: 'foo', - value: 'bar' - }]); + t.same(obj.data, [ + { + key: 'foo', + value: 'bar', + }, + ]); t.equal(obj.toHTML(), ''); const json = JSON.parse(JSON.stringify(obj)); t.same(json, { value: '/foo', - data: [{ - key: 'foo', - value: 'bar' - }], + data: [ + { + key: 'foo', + value: 'bar', + }, + ], type: 'default', }); const repl = new AssetJs(json); - t.same(repl.data, [{ - key: 'foo', - value: 'bar' - }]); + t.same(repl.data, [ + { + key: 'foo', + value: 'bar', + }, + ]); t.end(); -}); +}); tap.test('Js() - set "value"', (t) => { t.plan(1); const obj = new AssetJs({ value: '/foo', }); - t.throws(() => { - obj.value = '/bar'; - }, /Cannot set read-only property./, 'Should throw'); + t.throws( + () => { + obj.value = '/bar'; + }, + /Cannot set read-only property./, + 'Should throw', + ); t.end(); -}); +}); tap.test('Js() - set "src"', (t) => { t.plan(1); const obj = new AssetJs({ value: '/foo', }); - t.throws(() => { - obj.src = '/bar'; - }, /Cannot set read-only property./, 'Should throw'); + t.throws( + () => { + obj.src = '/bar'; + }, + /Cannot set read-only property./, + 'Should throw', + ); t.end(); -}); +}); tap.test('Js() - validate object against schema - should validate', (t) => { const obj = new AssetJs({ value: '/foo' }); t.notOk(schema.js([obj]).error); t.end(); -}); +}); + +tap.test('Js() - ignores async if both id and defer are set', (t) => { + const asset = new AssetJs({ + value: '/foo', + async: true, + defer: true, + }); + t.ok(asset.defer); + t.notOk(asset.async, 'Async should be ignored if defer is set'); + t.end(); +}); diff --git a/tests/html-utils.js b/tests/html-utils.js index 9dd0f774..cbefd627 100644 --- a/tests/html-utils.js +++ b/tests/html-utils.js @@ -12,7 +12,7 @@ tap.test('.buildLinkElement() - "value" property has a value - should appended " value: '/foo', }); const result = utils.buildLinkElement(obj); - t.equal(result, + t.equal(result, '', ); t.end(); @@ -24,7 +24,7 @@ tap.test('.buildLinkElement() - "crossorigin" property has a value - should appe crossorigin: 'bar', }); const result = utils.buildLinkElement(obj); - t.equal(result, + t.equal(result, '', ); t.end(); @@ -36,7 +36,7 @@ tap.test('.buildLinkElement() - "disabled" property is "true" - should appended disabled: true, }); const result = utils.buildLinkElement(obj); - t.equal(result, + t.equal(result, '', ); t.end(); @@ -48,7 +48,7 @@ tap.test('.buildLinkElement() - "hreflang" property has a value - should appende hreflang: 'bar', }); const result = utils.buildLinkElement(obj); - t.equal(result, + t.equal(result, '', ); t.end(); @@ -60,7 +60,7 @@ tap.test('.buildLinkElement() - "title" property has a value - should appended " title: 'bar', }); const result = utils.buildLinkElement(obj); - t.equal(result, + t.equal(result, '', ); t.end(); @@ -72,7 +72,7 @@ tap.test('.buildLinkElement() - "media" property has a value - should appended " media: 'bar', }); const result = utils.buildLinkElement(obj); - t.equal(result, + t.equal(result, '', ); t.end(); @@ -84,7 +84,7 @@ tap.test('.buildLinkElement() - "as" property has a value - should appended "as" as: 'bar', }); const result = utils.buildLinkElement(obj); - t.equal(result, + t.equal(result, '', ); t.end(); @@ -123,7 +123,7 @@ tap.test('.buildLinkElement() - properties are "undefined" - should NOT appended as: undefined, }); const result = utils.buildLinkElement(obj); - t.equal(result, + t.equal(result, '', ); t.end(); @@ -186,7 +186,7 @@ tap.test('.buildLinkElement() - crossorigin boolean true', (t) => { value: '/bar', }); const result = utils.buildLinkElement(obj); - t.equal(result, + t.equal(result, ``, ); t.end(); @@ -198,7 +198,7 @@ tap.test('.buildLinkElement() - crossorigin boolean false', (t) => { value: '/bar', }); const result = utils.buildLinkElement(obj); - t.equal(result, + t.equal(result, ``, ); t.end(); @@ -441,17 +441,15 @@ tap.test('.buildScriptAttributes() - advanced', (t) => { const obj = new AssetJs({ value: '/bar', crossorigin: true, - async: true, integrity: 'fake', defer: true, type: 'module', }); - t.same(utils.buildScriptAttributes(obj), [ + t.same(utils.buildScriptAttributes(obj), [ { key: 'src', value: '/bar' }, { key: 'type', value: 'module' }, { key: 'crossorigin' }, { key: 'integrity', value: 'fake' }, - { key: 'async' }, { key: 'defer' }, ]); t.end(); @@ -459,7 +457,7 @@ tap.test('.buildScriptAttributes() - advanced', (t) => { tap.test('.buildLinkAttributes() - basic', (t) => { const obj = new AssetCss({ value: '/bar' }); - t.same(utils.buildLinkAttributes(obj), [ + t.same(utils.buildLinkAttributes(obj), [ { key: 'href', value: '/bar' }, { key: 'type', value: 'text/css' }, { key: 'rel', value: 'stylesheet' }, @@ -497,15 +495,13 @@ tap.test('.buildReactScriptAttributes()', (t) => { value: '/bar', crossorigin: true, async: true, - defer: true, nomodule: true, }); - t.same(utils.buildReactScriptAttributes(obj), { + t.same(utils.buildReactScriptAttributes(obj), { src: '/bar', crossOrigin: '', noModule: true, async: true, - defer: true, }); t.end(); }); @@ -516,7 +512,7 @@ tap.test('.buildReactLinkAttributes()', (t) => { crossorigin: true, disabled: true, }); - t.same(utils.buildReactLinkAttributes(obj), { + t.same(utils.buildReactLinkAttributes(obj), { href: '/bar', crossOrigin: '', rel: 'stylesheet',