diff --git a/README.md b/README.md index 13bc4683..51987040 100644 --- a/README.md +++ b/README.md @@ -228,6 +228,10 @@ function HTML () { } `} + {/* link elements */} + + + {/* noscript elements */} {` diff --git a/src/Helmet.js b/src/Helmet.js index d88ee7e4..9b982788 100644 --- a/src/Helmet.js +++ b/src/Helmet.js @@ -20,7 +20,7 @@ const Helmet = Component => * @param {Boolean} defer: true * @param {Boolean} encodeSpecialCharacters: true * @param {Object} htmlAttributes: {"lang": "en", "amp": undefined} - * @param {Array} link: [{"rel": "canonical", "href": "http://mysite.com/example"}] + * @param {Array} link: [{"rel": "canonical", "href": "http://mysite.com/example", "onLoad": "functionCall()", "onError": "functionCall()"}] * @param {Array} meta: [{"name": "description", "content": "Test description"}] * @param {Array} noscript: [{"innerHTML": " console.log(newState)" diff --git a/src/HelmetConstants.js b/src/HelmetConstants.js index 37818463..56611655 100644 --- a/src/HelmetConstants.js +++ b/src/HelmetConstants.js @@ -31,7 +31,9 @@ export const TAG_PROPERTIES = { NAME: "name", PROPERTY: "property", REL: "rel", - SRC: "src" + SRC: "src", + ONLOAD: "onload", + ONERROR: "onerror" }; export const REACT_TAG_MAP = { diff --git a/src/HelmetUtils.js b/src/HelmetUtils.js index 63ad85a6..73738fb7 100644 --- a/src/HelmetUtils.js +++ b/src/HelmetUtils.js @@ -217,7 +217,12 @@ const reducePropsToState = propsList => ({ htmlAttributes: getAttributesFromPropsList(ATTRIBUTE_NAMES.HTML, propsList), linkTags: getTagsFromPropsList( TAG_NAMES.LINK, - [TAG_PROPERTIES.REL, TAG_PROPERTIES.HREF], + [ + TAG_PROPERTIES.REL, + TAG_PROPERTIES.HREF, + TAG_PROPERTIES.ONLOAD, + TAG_PROPERTIES.ONERROR + ], propsList ), metaTags: getTagsFromPropsList( @@ -273,19 +278,21 @@ const rafPolyfill = (() => { const cafPolyfill = (id: string | number) => clearTimeout(id); -const requestAnimationFrame = typeof window !== "undefined" - ? window.requestAnimationFrame || +const requestAnimationFrame = + typeof window !== "undefined" + ? window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || rafPolyfill - : global.requestAnimationFrame || rafPolyfill; + : global.requestAnimationFrame || rafPolyfill; -const cancelAnimationFrame = typeof window !== "undefined" - ? window.cancelAnimationFrame || +const cancelAnimationFrame = + typeof window !== "undefined" + ? window.cancelAnimationFrame || window.webkitCancelAnimationFrame || window.mozCancelAnimationFrame || cafPolyfill - : global.cancelAnimationFrame || cafPolyfill; + : global.cancelAnimationFrame || cafPolyfill; const warn = msg => { return console && typeof console.warn === "function" && console.warn(msg); @@ -442,9 +449,10 @@ const updateTags = (type, tags) => { ); } } else { - const value = typeof tag[attribute] === "undefined" - ? "" - : tag[attribute]; + const value = + typeof tag[attribute] === "undefined" + ? "" + : tag[attribute]; newElement.setAttribute(attribute, value); } } @@ -477,9 +485,10 @@ const updateTags = (type, tags) => { const generateElementAttributesAsString = attributes => Object.keys(attributes).reduce((str, key) => { - const attr = typeof attributes[key] !== "undefined" - ? `${key}="${attributes[key]}"` - : `${key}`; + const attr = + typeof attributes[key] !== "undefined" + ? `${key}="${attributes[key]}"` + : `${key}`; return str ? `${str} ${attr}` : attr; }, ""); @@ -508,12 +517,13 @@ const generateTagsAsString = (type, tags, encode) => ) ) .reduce((string, attribute) => { - const attr = typeof tag[attribute] === "undefined" - ? attribute - : `${attribute}="${encodeSpecialCharacters( - tag[attribute], - encode - )}"`; + const attr = + typeof tag[attribute] === "undefined" + ? attribute + : `${attribute}="${encodeSpecialCharacters( + tag[attribute], + encode + )}"`; return string ? `${string} ${attr}` : attr; }, ""); diff --git a/test/HelmetDeclarativeTest.js b/test/HelmetDeclarativeTest.js index bdcf5229..3168e45b 100644 --- a/test/HelmetDeclarativeTest.js +++ b/test/HelmetDeclarativeTest.js @@ -50,7 +50,9 @@ describe("Helmet - Declarative API", () => { ReactDOM.render( - Title: {someValue} + + Title: {someValue} + , container ); @@ -247,7 +249,9 @@ describe("Helmet - Declarative API", () => { ReactDOM.render( - {dollarTitle} + + {dollarTitle} + , container ); @@ -264,7 +268,9 @@ describe("Helmet - Declarative API", () => { it("properly handles title with children and titleTemplate", done => { ReactDOM.render( - {"extra"} + {"test"} + + {"extra"} + {"test"} + , container ); @@ -281,7 +287,9 @@ describe("Helmet - Declarative API", () => { ReactDOM.render( - {chineseTitle} + + {chineseTitle} + , container ); @@ -341,7 +349,7 @@ describe("Helmet - Declarative API", () => { ReactDOM.render( - {" "} + , container @@ -2073,6 +2081,83 @@ describe("Helmet - Declarative API", () => { done(); }); }); + + it("renders tag when onload attribute is present", done => { + ReactDOM.render( + + + + , + container + ); + + requestAnimationFrame(() => { + const tagNodes = headElement.querySelectorAll( + `link[${HELMET_ATTRIBUTE}]` + ); + const existingTags = Array.prototype.slice.call(tagNodes); + const firstTag = existingTags[0]; + + expect(existingTags).to.not.equal(undefined); + expect(existingTags.length).to.be.equal(2); + + expect(existingTags).to.have.deep + .property("[0]") + .that.is.an.instanceof(Element); + expect(firstTag).to.have.property("getAttribute"); + expect(firstTag.getAttribute("rel")).to.equal("stylesheet"); + expect(firstTag.getAttribute("media")).to.equal("all"); + expect(firstTag.getAttribute("type")).to.equal("text/css"); + expect(firstTag.getAttribute("onload")).to.equal( + "functionCall()" + ); + expect(firstTag.getAttribute("onerror")).to.equal( + "errorFunctionCall()" + ); + expect(firstTag.getAttribute("href")).to.equal( + "http://localhost/critical-style.css" + ); + expect(firstTag.outerHTML).to.equal( + `` + ); + + const secondTag = existingTags[1]; + + expect(existingTags).to.have.deep + .property("[1]") + .that.is.an.instanceof(Element); + expect(secondTag).to.have.property("getAttribute"); + expect(secondTag.getAttribute("rel")).to.equal( + "stylesheet" + ); + expect(secondTag.getAttribute("media")).to.equal("none"); + expect(secondTag.getAttribute("type")).to.equal("text/css"); + expect(secondTag.getAttribute("onload")).to.equal( + "if(media!='all')media='all'" + ); + expect(secondTag.getAttribute("href")).to.equal( + "http://localhost/non-critical-style.css" + ); + expect(secondTag.outerHTML).to.equal( + `` + ); + + done(); + }); + }); }); describe("script tags", () => { @@ -2308,7 +2393,9 @@ describe("Helmet - Declarative API", () => { const noscriptInnerHTML = ``; ReactDOM.render( - {noscriptInnerHTML} + + {noscriptInnerHTML} + , container ); @@ -2376,7 +2463,9 @@ describe("Helmet - Declarative API", () => { it("does not render tag when primary attribute is null", done => { ReactDOM.render( - {undefined} + + {undefined} + , container ); @@ -2408,8 +2497,12 @@ describe("Helmet - Declarative API", () => { ReactDOM.render( - - + + , container ); @@ -2454,7 +2547,9 @@ describe("Helmet - Declarative API", () => { `; ReactDOM.render( - + , container ); @@ -2498,7 +2593,9 @@ describe("Helmet - Declarative API", () => { it("does not render tag when primary attribute is null", done => { ReactDOM.render( - + , container ); @@ -2529,14 +2626,10 @@ describe("Helmet - Declarative API", () => { ReactDOM.render( - + - + , container @@ -2622,7 +2715,9 @@ describe("Helmet - Declarative API", () => { it("opts out of string encoding", () => { ReactDOM.render( - {"This is text and & and '."} + + {"This is text and & and '."} + , container ); @@ -2926,7 +3021,9 @@ describe("Helmet - Declarative API", () => { it("renders title tag as string", () => { ReactDOM.render( - {"Dangerous