From 87845ab7876d2f0644a02d89a9eb70638a20be15 Mon Sep 17 00:00:00 2001
From: HOU Bin
Date: Fri, 17 May 2019 18:33:37 +0900
Subject: [PATCH] Add test for useHelmet() hook
---
.eslintrc | 15 +-
.prettierrc.json | 11 +
src/Helmet.js | 13 +-
test/HelmetHookTest.js | 4864 ++++++++++++++++++++++++++++++++++++++++
test/HelmetTest.js | 1 -
5 files changed, 4882 insertions(+), 22 deletions(-)
create mode 100644 .prettierrc.json
create mode 100644 test/HelmetHookTest.js
diff --git a/.eslintrc b/.eslintrc
index 5a10dda5..a30cc7b5 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -13,19 +13,6 @@
"rules": {
"react-hooks/rules-of-hooks": "error",
"react-hooks/exhaustive-deps": "error",
- "prettier/prettier": [
- "error",
- {
- "printWidth": 80,
- "tabWidth": 4,
- "singleQuote": false,
- "trailingComma": "none",
- "bracketSpacing": false,
- "semi": true,
- "useTabs": false,
- "parser": "babel",
- "jsxBracketSameLine": false
- }
- ]
+ "prettier/prettier": "error"
}
}
diff --git a/.prettierrc.json b/.prettierrc.json
new file mode 100644
index 00000000..89870b37
--- /dev/null
+++ b/.prettierrc.json
@@ -0,0 +1,11 @@
+{
+ "printWidth": 80,
+ "tabWidth": 4,
+ "singleQuote": false,
+ "trailingComma": "none",
+ "bracketSpacing": false,
+ "semi": true,
+ "useTabs": false,
+ "parser": "babel",
+ "jsxBracketSameLine": false
+}
diff --git a/src/Helmet.js b/src/Helmet.js
index c22f79dc..3bf76749 100644
--- a/src/Helmet.js
+++ b/src/Helmet.js
@@ -280,7 +280,7 @@ function generateUniqueString() {
);
}
-function useHelmet(props) {
+function useHelmet(props = {}) {
const instance = useMemo(() => generateUniqueString(), []);
const dispatch = useContext(HelmetContext);
const called = useRef(false);
@@ -291,7 +291,11 @@ function useHelmet(props) {
}
prevProps.current = props;
const {children, ...restProps} = props;
- let newProps = {...restProps};
+ let newProps = {
+ defer: true,
+ encodeSpecialCharacters: true,
+ ...restProps
+ };
if (children) {
newProps = mapChildrenToProps(children, newProps);
}
@@ -321,11 +325,6 @@ const Helmet = props => {
Helmet.displayName = "Helmet";
-Helmet.defaultProps = {
- defer: true,
- encodeSpecialCharacters: true
-};
-
if (process.env.NODE_ENV !== "production") {
/**
* @param {Object} base: {"target": "_blank", "href": "http://mysite.com/"}
diff --git a/test/HelmetHookTest.js b/test/HelmetHookTest.js
new file mode 100644
index 00000000..61531f6f
--- /dev/null
+++ b/test/HelmetHookTest.js
@@ -0,0 +1,4864 @@
+/* eslint max-nested-callbacks: [1, 7] */
+/* eslint-disable import/no-named-as-default */
+
+import React from "react";
+import ReactDOM from "react-dom";
+import ReactServer from "react-dom/server";
+import {Helmet, HelmetProvider, createHelmetStore, useHelmet} from "../src";
+import {requestAnimationFrame} from "../src/HelmetUtils.js";
+
+const HELMET_ATTRIBUTE = "data-react-helmet";
+
+describe("useHelmet()", () => {
+ let headElement;
+
+ const container = document.createElement("div");
+
+ beforeEach(() => {
+ headElement =
+ headElement || document.head || document.querySelector("head");
+
+ // resets DOM after each run
+ headElement.innerHTML = "";
+ });
+
+ afterEach(() => {
+ ReactDOM.unmountComponentAtNode(container);
+ });
+
+ describe("api", () => {
+ describe("title", () => {
+ it("updates page title", done => {
+ const store = createHelmetStore();
+ function HeadInfo() {
+ useHelmet({
+ defaultTitle: "Fallback",
+ title: "Test Title"
+ });
+ return null;
+ }
+ ReactDOM.render(
+
+
+ ,
+ container
+ );
+
+ requestAnimationFrame(() => {
+ expect(document.title).to.equal("Test Title");
+
+ done();
+ });
+ });
+
+ it("updates page title with multiple children", done => {
+ const store = createHelmetStore();
+ function HeadInfo({title}) {
+ useHelmet({
+ title
+ });
+ return null;
+ }
+ ReactDOM.render(
+
+
+
+
+
+
+ ,
+ container
+ );
+
+ requestAnimationFrame(() => {
+ expect(document.title).to.equal("Child Two Title");
+
+ done();
+ });
+ });
+
+ it("updates page title with multiple useHelmet()", done => {
+ const store = createHelmetStore();
+ function HeadInfo() {
+ useHelmet({
+ title: "Test Title"
+ });
+ useHelmet({
+ title: "Child One Title"
+ });
+ useHelmet({
+ title: "Child Two Title"
+ });
+ return null;
+ }
+ ReactDOM.render(
+
+
+
+
+ ,
+ container
+ );
+
+ requestAnimationFrame(() => {
+ expect(document.title).to.equal("Child Two Title");
+
+ done();
+ });
+ });
+
+ it("updates page title with multiple useHelmet()", done => {
+ const store = createHelmetStore();
+ function HeadInfo() {
+ useHelmet({
+ title: "Test Title"
+ });
+ useHelmet({
+ title: "Child One Title"
+ });
+ useHelmet({
+ title: "Child Two Title"
+ });
+ return null;
+ }
+ ReactDOM.render(
+
+
+
+
+ ,
+ container
+ );
+
+ requestAnimationFrame(() => {
+ expect(document.title).to.equal("Child Two Title");
+
+ done();
+ });
+ });
+
+ it("sets title based on deepest nested component", done => {
+ const store = createHelmetStore();
+ function HeadInfo1() {
+ useHelmet({title: "Main Title"});
+ return null;
+ }
+
+ function HeadInfo2() {
+ useHelmet({title: "Nested Title"});
+ return null;
+ }
+ ReactDOM.render(
+
+
+
+
+
+ ,
+ container
+ );
+
+ requestAnimationFrame(() => {
+ expect(document.title).to.equal("Nested Title");
+
+ done();
+ });
+ });
+
+ it("sets title using deepest nested component with a defined title", done => {
+ const store = createHelmetStore();
+ function HeadInfo1() {
+ useHelmet({title: "Main Title"});
+ return null;
+ }
+ function HeadInfo2() {
+ useHelmet({});
+ return null;
+ }
+ ReactDOM.render(
+
+
+
+
+
+ ,
+ container
+ );
+
+ requestAnimationFrame(() => {
+ expect(document.title).to.equal("Main Title");
+
+ done();
+ });
+ });
+
+ it("sets title using multiple useHelmet() with a defined title", done => {
+ const store = createHelmetStore();
+ function HeadInfo1() {
+ useHelmet({title: "Main Title"});
+ useHelmet({});
+ return null;
+ }
+ ReactDOM.render(
+
+
+
+
+ ,
+ container
+ );
+
+ requestAnimationFrame(() => {
+ expect(document.title).to.equal("Main Title");
+
+ done();
+ });
+ });
+
+ it("uses defaultTitle if no title is defined", done => {
+ const store = createHelmetStore();
+ function HeadInfo() {
+ useHelmet({
+ defaultTitle: "Fallback",
+ title: "",
+ titleTemplate:
+ "This is a %s of the titleTemplate feature"
+ });
+ return null;
+ }
+ ReactDOM.render(
+
+
+ ,
+ container
+ );
+
+ requestAnimationFrame(() => {
+ expect(document.title).to.equal("Fallback");
+
+ done();
+ });
+ });
+
+ it("uses a titleTemplate if defined", done => {
+ const store = createHelmetStore();
+ function HeadInfo() {
+ useHelmet({
+ defaultTitle: "Fallback",
+ title: "Test",
+ titleTemplate:
+ "This is a %s of the titleTemplate feature"
+ });
+ return null;
+ }
+ ReactDOM.render(
+
+
+ ,
+ container
+ );
+
+ requestAnimationFrame(() => {
+ expect(document.title).to.equal(
+ "This is a Test of the titleTemplate feature"
+ );
+
+ done();
+ });
+ });
+
+ it("replaces multiple title strings in titleTemplate", done => {
+ const store = createHelmetStore();
+ function HeadInfo() {
+ useHelmet({
+ title: "Test",
+ titleTemplate:
+ "This is a %s of the titleTemplate feature. Another %s."
+ });
+ return null;
+ }
+ ReactDOM.render(
+
+
+ ,
+ container
+ );
+
+ requestAnimationFrame(() => {
+ expect(document.title).to.equal(
+ "This is a Test of the titleTemplate feature. Another Test."
+ );
+
+ done();
+ });
+ });
+
+ it("uses a titleTemplate based on deepest nested component", done => {
+ const store = createHelmetStore();
+ function HeadInfo1() {
+ useHelmet({
+ title: "Test",
+ titleTemplate:
+ "This is a %s of the titleTemplate feature"
+ });
+ return null;
+ }
+ function HeadInfo2() {
+ useHelmet({
+ title: "Second Test",
+ titleTemplate:
+ "A %s using nested titleTemplate attributes"
+ });
+ return null;
+ }
+ ReactDOM.render(
+
+
+
+
+
+ ,
+ container
+ );
+
+ requestAnimationFrame(() => {
+ expect(document.title).to.equal(
+ "A Second Test using nested titleTemplate attributes"
+ );
+
+ done();
+ });
+ });
+
+ it("uses a titleTemplate based on multiple useHelmet()", done => {
+ const store = createHelmetStore();
+ function HeadInfo1() {
+ useHelmet({
+ title: "Test",
+ titleTemplate:
+ "This is a %s of the titleTemplate feature"
+ });
+ useHelmet({
+ title: "Second Test",
+ titleTemplate:
+ "A %s using nested titleTemplate attributes"
+ });
+ return null;
+ }
+ ReactDOM.render(
+
+
+
+
+ ,
+ container
+ );
+
+ requestAnimationFrame(() => {
+ expect(document.title).to.equal(
+ "A Second Test using nested titleTemplate attributes"
+ );
+
+ done();
+ });
+ });
+
+ it("merges deepest component title with nearest upstream titleTemplate", done => {
+ const store = createHelmetStore();
+ function HeadInfo1() {
+ useHelmet({
+ title: "Test",
+ titleTemplate:
+ "This is a %s of the titleTemplate feature"
+ });
+ return null;
+ }
+ function HeadInfo2() {
+ useHelmet({
+ title: "Second Test"
+ });
+ return null;
+ }
+ ReactDOM.render(
+
+
+
+
+
+ ,
+ container
+ );
+
+ requestAnimationFrame(() => {
+ expect(document.title).to.equal(
+ "This is a Second Test of the titleTemplate feature"
+ );
+
+ done();
+ });
+ });
+
+ it("merges deepest component title with nearest upstream titleTemplate and with multiple useHelmet()", done => {
+ const store = createHelmetStore();
+ function HeadInfo1() {
+ useHelmet({
+ title: "Test",
+ titleTemplate:
+ "This is a %s of the titleTemplate feature"
+ });
+ useHelmet({
+ title: "Second Test"
+ });
+ return null;
+ }
+ ReactDOM.render(
+
+
+
+
+ ,
+ container
+ );
+
+ requestAnimationFrame(() => {
+ expect(document.title).to.equal(
+ "This is a Second Test of the titleTemplate feature"
+ );
+
+ done();
+ });
+ });
+
+ it("renders dollar characters in a title correctly when titleTemplate present", done => {
+ const store = createHelmetStore();
+ const dollarTitle = "te$t te$$t te$$$t te$$$$t";
+
+ function HeadInfo() {
+ useHelmet({
+ title: dollarTitle,
+ titleTemplate: "This is a %s"
+ });
+ return null;
+ }
+
+ ReactDOM.render(
+
+
+ ,
+ container
+ );
+
+ requestAnimationFrame(() => {
+ expect(document.title).to.equal(
+ "This is a te$t te$$t te$$$t te$$$$t"
+ );
+
+ done();
+ });
+ });
+
+ it("does not encode all characters with HTML character entity equivalents", done => {
+ const store = createHelmetStore();
+ const chineseTitle = "膣膗 鍆錌雔";
+
+ function HeadInfo() {
+ useHelmet({
+ title: chineseTitle
+ });
+ return null;
+ }
+
+ ReactDOM.render(
+
+
+
+
+ ,
+ container
+ );
+
+ requestAnimationFrame(() => {
+ expect(document.title).to.equal(chineseTitle);
+
+ done();
+ });
+ });
+
+ it("page title with prop itemprop", done => {
+ const store = createHelmetStore();
+ function HeadInfo() {
+ useHelmet({
+ defaultTitle: "Fallback",
+ title: "Test Title with itemProp",
+ titleAttributes: {itemprop: "name"}
+ });
+ return null;
+ }
+ ReactDOM.render(
+
+
+ ,
+ container
+ );
+
+ requestAnimationFrame(() => {
+ const titleTag = document.getElementsByTagName("title")[0];
+ expect(document.title).to.equal("Test Title with itemProp");
+ expect(titleTag.getAttribute("itemprop")).to.equal("name");
+
+ done();
+ });
+ });
+ });
+
+ describe("title attributes", () => {
+ beforeEach(() => {
+ headElement.innerHTML = `Test Title`;
+ });
+
+ it("update title attributes", done => {
+ const store = createHelmetStore();
+ function HeadInfo() {
+ useHelmet({
+ titleAttributes: {itemprop: "name"}
+ });
+ return null;
+ }
+ ReactDOM.render(
+
+
+ ,
+ container
+ );
+
+ requestAnimationFrame(() => {
+ const titleTag = document.getElementsByTagName("title")[0];
+ expect(titleTag.getAttribute("itemprop")).to.equal("name");
+ expect(titleTag.getAttribute(HELMET_ATTRIBUTE)).to.equal(
+ "itemprop"
+ );
+
+ done();
+ });
+ });
+
+ it("sets attributes based on the deepest nested component", done => {
+ const store = createHelmetStore();
+ function HeadInfo1() {
+ useHelmet({
+ titleAttributes: {
+ lang: "en",
+ hidden: undefined
+ }
+ });
+ return null;
+ }
+ function HeadInfo2() {
+ useHelmet({
+ titleAttributes: {
+ lang: "ja"
+ }
+ });
+ return null;
+ }
+ ReactDOM.render(
+
+
+
+
+
+ ,
+ container
+ );
+
+ requestAnimationFrame(() => {
+ const titleTag = document.getElementsByTagName("title")[0];
+ expect(titleTag.getAttribute("lang")).to.equal("ja");
+ expect(titleTag.getAttribute("hidden")).to.equal("");
+ expect(titleTag.getAttribute(HELMET_ATTRIBUTE)).to.equal(
+ "lang,hidden"
+ );
+
+ done();
+ });
+ });
+
+ it("sets attributes based on the multiple useHelmet()", done => {
+ const store = createHelmetStore();
+ function HeadInfo1() {
+ useHelmet({
+ titleAttributes: {
+ lang: "en",
+ hidden: undefined
+ }
+ });
+ useHelmet({
+ titleAttributes: {
+ lang: "ja"
+ }
+ });
+ return null;
+ }
+ ReactDOM.render(
+
+
+
+
+ ,
+ container
+ );
+
+ requestAnimationFrame(() => {
+ const titleTag = document.getElementsByTagName("title")[0];
+ expect(titleTag.getAttribute("lang")).to.equal("ja");
+ expect(titleTag.getAttribute("hidden")).to.equal("");
+ expect(titleTag.getAttribute(HELMET_ATTRIBUTE)).to.equal(
+ "lang,hidden"
+ );
+
+ done();
+ });
+ });
+
+ it("handles valueless attributes", done => {
+ const store = createHelmetStore();
+ function HeadInfo1() {
+ useHelmet({
+ titleAttributes: {
+ hidden: undefined
+ }
+ });
+ return null;
+ }
+ ReactDOM.render(
+
+
+ ,
+ container
+ );
+
+ requestAnimationFrame(() => {
+ const titleTag = document.getElementsByTagName("title")[0];
+ expect(titleTag.getAttribute("hidden")).to.equal("");
+ expect(titleTag.getAttribute(HELMET_ATTRIBUTE)).to.equal(
+ "hidden"
+ );
+
+ done();
+ });
+ });
+
+ it("clears title attributes that are handled within helmet", done => {
+ const store = createHelmetStore();
+ function HeadInfo1() {
+ useHelmet({
+ titleAttributes: {
+ lang: "en",
+ hidden: undefined
+ }
+ });
+ return null;
+ }
+
+ function HeadInfo2() {
+ useHelmet({});
+ return null;
+ }
+ ReactDOM.render(
+
+
+ ,
+ container
+ );
+
+ requestAnimationFrame(() => {
+ ReactDOM.render(
+
+
+ ,
+ container
+ );
+
+ requestAnimationFrame(() => {
+ const titleTag = document.getElementsByTagName(
+ "title"
+ )[0];
+ expect(titleTag.getAttribute("lang")).to.be.null;
+ expect(titleTag.getAttribute("hidden")).to.be.null;
+ expect(
+ titleTag.getAttribute(HELMET_ATTRIBUTE)
+ ).to.equal(null);
+
+ done();
+ });
+ });
+ });
+ });
+
+ describe("html attributes", () => {
+ it("updates html attributes", done => {
+ const store = createHelmetStore();
+ function HeadInfo1() {
+ useHelmet({
+ htmlAttributes: {
+ class: "myClassName",
+ lang: "en"
+ },
+ lang: "en"
+ });
+ return null;
+ }
+ ReactDOM.render(
+
+
+ ,
+ container
+ );
+
+ requestAnimationFrame(() => {
+ const htmlTag = document.getElementsByTagName("html")[0];
+ expect(htmlTag.getAttribute("class")).to.equal(
+ "myClassName"
+ );
+ expect(htmlTag.getAttribute("lang")).to.equal("en");
+ expect(htmlTag.getAttribute(HELMET_ATTRIBUTE)).to.equal(
+ "class,lang"
+ );
+
+ done();
+ });
+ });
+
+ it("sets attributes based on the deepest nested component", done => {
+ const store = createHelmetStore();
+ function HeadInfo1() {
+ useHelmet({
+ htmlAttributes: {
+ lang: "en"
+ }
+ });
+ return null;
+ }
+ function HeadInfo2() {
+ useHelmet({
+ htmlAttributes: {
+ lang: "ja"
+ }
+ });
+ return null;
+ }
+ ReactDOM.render(
+
+
+
+
+
+ ,
+ container
+ );
+
+ requestAnimationFrame(() => {
+ const htmlTag = document.getElementsByTagName("html")[0];
+ expect(htmlTag.getAttribute("lang")).to.equal("ja");
+ expect(htmlTag.getAttribute(HELMET_ATTRIBUTE)).to.equal(
+ "lang"
+ );
+
+ done();
+ });
+ });
+
+ it("sets attributes based on the multiple useHelmet()", done => {
+ const store = createHelmetStore();
+ function HeadInfo1() {
+ useHelmet({
+ htmlAttributes: {
+ lang: "en"
+ }
+ });
+ useHelmet({
+ htmlAttributes: {
+ lang: "ja"
+ }
+ });
+ return null;
+ }
+
+ ReactDOM.render(
+
+
+
+
+ ,
+ container
+ );
+
+ requestAnimationFrame(() => {
+ const htmlTag = document.getElementsByTagName("html")[0];
+ expect(htmlTag.getAttribute("lang")).to.equal("ja");
+ expect(htmlTag.getAttribute(HELMET_ATTRIBUTE)).to.equal(
+ "lang"
+ );
+
+ done();
+ });
+ });
+
+ it("handles valueless attributes", done => {
+ const store = createHelmetStore();
+ function HeadInfo1() {
+ useHelmet({
+ htmlAttributes: {
+ amp: undefined
+ }
+ });
+ return null;
+ }
+ ReactDOM.render(
+
+
+ ,
+ container
+ );
+
+ requestAnimationFrame(() => {
+ const htmlTag = document.getElementsByTagName("html")[0];
+ expect(htmlTag.getAttribute("amp")).to.equal("");
+ expect(htmlTag.getAttribute(HELMET_ATTRIBUTE)).to.equal(
+ "amp"
+ );
+
+ done();
+ });
+ });
+
+ it("clears html attributes that are handled within helmet", done => {
+ const store = createHelmetStore();
+ function HeadInfo1() {
+ useHelmet({
+ htmlAttributes: {
+ lang: "en",
+ amp: undefined
+ }
+ });
+ return null;
+ }
+ function HeadInfo2() {
+ useHelmet({});
+ return null;
+ }
+ ReactDOM.render(
+
+
+ ,
+ container
+ );
+
+ requestAnimationFrame(() => {
+ ReactDOM.render(
+
+
+ ,
+ container
+ );
+
+ requestAnimationFrame(() => {
+ const htmlTag = document.getElementsByTagName(
+ "html"
+ )[0];
+ expect(htmlTag.getAttribute("lang")).to.be.null;
+ expect(htmlTag.getAttribute("amp")).to.be.null;
+ expect(htmlTag.getAttribute(HELMET_ATTRIBUTE)).to.equal(
+ null
+ );
+
+ done();
+ });
+ });
+ });
+
+ it("updates with multiple additions and removals - overwrite and new", done => {
+ function HeadInfo1() {
+ useHelmet({
+ htmlAttributes: {
+ lang: "en",
+ amp: undefined
+ }
+ });
+ return null;
+ }
+ function HeadInfo2() {
+ useHelmet({
+ htmlAttributes: {
+ lang: "ja",
+ id: "html-tag",
+ title: "html tag"
+ }
+ });
+ return null;
+ }
+ const store = createHelmetStore();
+ ReactDOM.render(
+
+
+ ,
+ container
+ );
+
+ requestAnimationFrame(() => {
+ ReactDOM.render(
+
+
+ ,
+ container
+ );
+
+ requestAnimationFrame(() => {
+ const htmlTag = document.getElementsByTagName(
+ "html"
+ )[0];
+ expect(htmlTag.getAttribute("amp")).to.equal(null);
+ expect(htmlTag.getAttribute("lang")).to.equal("ja");
+ expect(htmlTag.getAttribute("id")).to.equal("html-tag");
+ expect(htmlTag.getAttribute("title")).to.equal(
+ "html tag"
+ );
+ expect(htmlTag.getAttribute(HELMET_ATTRIBUTE)).to.equal(
+ "lang,id,title"
+ );
+
+ done();
+ });
+ });
+ });
+
+ it("updates with multiple additions and removals - all new", done => {
+ const store = createHelmetStore();
+ function HeadInfo1() {
+ useHelmet({
+ htmlAttributes: {
+ lang: "en",
+ amp: undefined
+ }
+ });
+ return null;
+ }
+ function HeadInfo2() {
+ useHelmet({
+ htmlAttributes: {
+ id: "html-tag",
+ title: "html tag"
+ }
+ });
+ return null;
+ }
+ ReactDOM.render(
+
+
+ ,
+ container
+ );
+
+ requestAnimationFrame(() => {
+ ReactDOM.render(
+
+
+ ,
+ container
+ );
+
+ requestAnimationFrame(() => {
+ const htmlTag = document.getElementsByTagName(
+ "html"
+ )[0];
+ expect(htmlTag.getAttribute("amp")).to.equal(null);
+ expect(htmlTag.getAttribute("lang")).to.equal(null);
+ expect(htmlTag.getAttribute("id")).to.equal("html-tag");
+ expect(htmlTag.getAttribute("title")).to.equal(
+ "html tag"
+ );
+ expect(htmlTag.getAttribute(HELMET_ATTRIBUTE)).to.equal(
+ "id,title"
+ );
+
+ done();
+ });
+ });
+ });
+
+ context("initialized outside of helmet", () => {
+ before(() => {
+ const htmlTag = document.getElementsByTagName("html")[0];
+ htmlTag.setAttribute("test", "test");
+ });
+
+ it("attributes are not cleared", done => {
+ const store = createHelmetStore();
+ function HeadInfo() {
+ useHelmet({});
+ return null;
+ }
+
+ ReactDOM.render(
+
+
+ ,
+ container
+ );
+
+ requestAnimationFrame(() => {
+ const htmlTag = document.getElementsByTagName(
+ "html"
+ )[0];
+ expect(htmlTag.getAttribute("test")).to.equal("test");
+ expect(htmlTag.getAttribute(HELMET_ATTRIBUTE)).to.equal(
+ null
+ );
+
+ done();
+ });
+ });
+
+ it("attributes are overwritten if specified in helmet", done => {
+ const store = createHelmetStore();
+ function HeadInfo() {
+ useHelmet({
+ htmlAttributes: {
+ test: "helmet-attr"
+ }
+ });
+ return null;
+ }
+ ReactDOM.render(
+
+
+ ,
+ container
+ );
+
+ requestAnimationFrame(() => {
+ const htmlTag = document.getElementsByTagName(
+ "html"
+ )[0];
+ expect(htmlTag.getAttribute("test")).to.equal(
+ "helmet-attr"
+ );
+ expect(htmlTag.getAttribute(HELMET_ATTRIBUTE)).to.equal(
+ "test"
+ );
+
+ done();
+ });
+ });
+
+ it("attributes are cleared once managed in helmet", done => {
+ const store = createHelmetStore();
+ function HeadInfo1() {
+ useHelmet({
+ htmlAttributes: {
+ test: "helmet-attr"
+ }
+ });
+ return null;
+ }
+ function HeadInfo2() {
+ useHelmet({});
+ return null;
+ }
+ ReactDOM.render(
+
+
+ ,
+ container
+ );
+
+ requestAnimationFrame(() => {
+ ReactDOM.render(
+
+
+ ,
+ container
+ );
+
+ requestAnimationFrame(() => {
+ const htmlTag = document.getElementsByTagName(
+ "html"
+ )[0];
+ expect(htmlTag.getAttribute("test")).to.equal(null);
+ expect(
+ htmlTag.getAttribute(HELMET_ATTRIBUTE)
+ ).to.equal(null);
+
+ done();
+ });
+ });
+ });
+ });
+ });
+
+ describe("onChangeClientState", () => {
+ it("when handling client state change, calls the function with new state, addedTags and removedTags ", done => {
+ const store = createHelmetStore();
+ const spy = sinon.spy();
+ function HeadInfo1() {
+ useHelmet({
+ base: {href: "http://mysite.com/"},
+ link: [
+ {
+ href: "http://localhost/helmet",
+ rel: "canonical"
+ }
+ ],
+ meta: [{charset: "utf-8"}],
+ script: [
+ {
+ src: "http://localhost/test.js",
+ type: "text/javascript"
+ }
+ ],
+ title: "Main Title",
+ onChangeClientState: spy
+ });
+ return null;
+ }
+ ReactDOM.render(
+
+
+
+
+ ,
+ container
+ );
+
+ requestAnimationFrame(() => {
+ expect(spy.called).to.equal(true);
+ const newState = spy.getCall(0).args[0];
+ const addedTags = spy.getCall(0).args[1];
+ const removedTags = spy.getCall(0).args[2];
+
+ expect(newState).to.deep.contain({title: "Main Title"});
+ expect(newState.baseTag).to.deep.contain({
+ href: "http://mysite.com/"
+ });
+ expect(newState.metaTags).to.deep.contain({
+ charset: "utf-8"
+ });
+ expect(newState.linkTags).to.deep.contain({
+ href: "http://localhost/helmet",
+ rel: "canonical"
+ });
+ expect(newState.scriptTags).to.deep.contain({
+ src: "http://localhost/test.js",
+ type: "text/javascript"
+ });
+
+ expect(addedTags).to.have.property("baseTag");
+ expect(addedTags.baseTag).to.have.deep.nested.property(
+ "[0]"
+ );
+ expect(addedTags.baseTag[0].outerHTML).to.equal(
+ ``
+ );
+
+ expect(addedTags).to.have.property("metaTags");
+ expect(addedTags.metaTags).to.have.deep.nested.property(
+ "[0]"
+ );
+ expect(addedTags.metaTags[0].outerHTML).to.equal(
+ ``
+ );
+
+ expect(addedTags).to.have.property("linkTags");
+ expect(addedTags.linkTags).to.have.deep.nested.property(
+ "[0]"
+ );
+ expect(addedTags.linkTags[0].outerHTML).to.equal(
+ ``
+ );
+
+ expect(addedTags).to.have.property("scriptTags");
+ expect(addedTags.scriptTags).to.have.deep.nested.property(
+ "[0]"
+ );
+ expect(addedTags.scriptTags[0].outerHTML).to.equal(
+ ``
+ );
+
+ expect(removedTags).to.be.empty;
+
+ done();
+ });
+ });
+
+ it("calls the deepest defined callback with the deepest state", done => {
+ const store = createHelmetStore();
+ const spy = sinon.spy();
+ function HeadInfo1() {
+ useHelmet({
+ title: "Main Title",
+ onChangeClientState: spy
+ });
+ return null;
+ }
+
+ function HeadInfo2() {
+ useHelmet({
+ title: "Deeper Title"
+ });
+ return null;
+ }
+ ReactDOM.render(
+
+
+
+
+
+ ,
+ container
+ );
+
+ requestAnimationFrame(() => {
+ expect(spy.callCount).to.equal(1);
+ expect(spy.getCall(0).args[0]).to.deep.contain({
+ title: "Deeper Title"
+ });
+
+ done();
+ });
+ });
+
+ it("calls the deepest defined callback with the deepest state with multiple useHelmet()", done => {
+ const store = createHelmetStore();
+ const spy = sinon.spy();
+ function HeadInfo1() {
+ useHelmet({
+ title: "Main Title",
+ onChangeClientState: spy
+ });
+ useHelmet({
+ title: "Deeper Title"
+ });
+ return null;
+ }
+
+ ReactDOM.render(
+
+
+
+
+ ,
+ container
+ );
+
+ requestAnimationFrame(() => {
+ expect(spy.callCount).to.equal(1);
+ expect(spy.getCall(0).args[0]).to.deep.contain({
+ title: "Deeper Title"
+ });
+
+ done();
+ });
+ });
+ });
+
+ describe("base tag", () => {
+ it("updates base tag", done => {
+ const store = createHelmetStore();
+ function HeadInfo1() {
+ useHelmet({
+ base: {href: "http://mysite.com/"}
+ });
+ return null;
+ }
+ ReactDOM.render(
+
+
+ ,
+ container
+ );
+
+ requestAnimationFrame(() => {
+ const existingTags = headElement.querySelectorAll(
+ `base[${HELMET_ATTRIBUTE}]`
+ );
+
+ expect(existingTags).to.not.equal(undefined);
+
+ const filteredTags = [].slice
+ .call(existingTags)
+ .filter(tag => {
+ return (
+ tag.getAttribute("href") ===
+ "http://mysite.com/"
+ );
+ });
+
+ expect(filteredTags.length).to.equal(1);
+
+ done();
+ });
+ });
+
+ it("clears the base tag if one is not specified", done => {
+ const store = createHelmetStore();
+ function HeadInfo1() {
+ useHelmet({
+ base: {href: "http://mysite.com/"}
+ });
+ return null;
+ }
+ function HeadInfo2() {
+ useHelmet({});
+ return null;
+ }
+ ReactDOM.render(
+
+
+ ,
+ container
+ );
+
+ requestAnimationFrame(() => {
+ ReactDOM.render(
+
+
+ ,
+ container
+ );
+
+ requestAnimationFrame(() => {
+ const existingTags = headElement.querySelectorAll(
+ `base[${HELMET_ATTRIBUTE}]`
+ );
+
+ expect(existingTags).to.not.equal(undefined);
+ expect(existingTags.length).to.equal(0);
+
+ done();
+ });
+ });
+ });
+
+ it("tags without 'href' are not accepted", done => {
+ const store = createHelmetStore();
+ function HeadInfo1() {
+ useHelmet({
+ base: {property: "won't work"}
+ });
+ return null;
+ }
+ ReactDOM.render(
+
+
+ ,
+ container
+ );
+
+ requestAnimationFrame(() => {
+ const existingTags = headElement.querySelectorAll(
+ `base[${HELMET_ATTRIBUTE}]`
+ );
+
+ expect(existingTags).to.not.equal(undefined);
+ expect(existingTags.length).to.equal(0);
+
+ done();
+ });
+ });
+
+ it("sets base tag based on deepest nested component", done => {
+ const store = createHelmetStore();
+ function HeadInfo1() {
+ useHelmet({
+ base: {href: "http://mysite.com/"}
+ });
+ return null;
+ }
+ function HeadInfo2() {
+ useHelmet({
+ base: {href: "http://mysite.com/public"}
+ });
+ return null;
+ }
+ ReactDOM.render(
+
+
+
+
+
+ ,
+ container
+ );
+
+ requestAnimationFrame(() => {
+ const existingTags = headElement.querySelectorAll(
+ `base[${HELMET_ATTRIBUTE}]`
+ );
+ const firstTag = Array.prototype.slice.call(
+ existingTags
+ )[0];
+
+ expect(existingTags).to.not.equal(undefined);
+
+ expect(existingTags.length).to.be.equal(1);
+
+ expect(existingTags)
+ .to.have.deep.nested.property("[0]")
+ .that.is.an.instanceof(Element);
+ expect(firstTag).to.have.property("getAttribute");
+ expect(firstTag.getAttribute("href")).to.equal(
+ "http://mysite.com/public"
+ );
+ expect(firstTag.outerHTML).to.equal(
+ ``
+ );
+
+ done();
+ });
+ });
+
+ it("does not render tag when primary attribute is null", done => {
+ const store = createHelmetStore();
+ function HeadInfo1() {
+ useHelmet({
+ base: {href: undefined}
+ });
+ return null;
+ }
+ ReactDOM.render(
+
+
+ ,
+ container
+ );
+
+ requestAnimationFrame(() => {
+ const tagNodes = headElement.querySelectorAll(
+ `base[${HELMET_ATTRIBUTE}]`
+ );
+ const existingTags = Array.prototype.slice.call(tagNodes);
+ expect(existingTags).to.be.empty;
+
+ done();
+ });
+ });
+ });
+
+ describe("meta tags", () => {
+ it("updates meta tags", done => {
+ const store = createHelmetStore();
+ function HeadInfo1() {
+ useHelmet({
+ meta: [
+ {charset: "utf-8"},
+ {
+ name: "description",
+ content: "Test description"
+ },
+ {
+ "http-equiv": "content-type",
+ content: "text/html"
+ },
+ {property: "og:type", content: "article"},
+ {
+ itemprop: "name",
+ content: "Test name itemprop"
+ }
+ ]
+ });
+ return null;
+ }
+ ReactDOM.render(
+
+
+ ,
+ container
+ );
+
+ requestAnimationFrame(() => {
+ const tagNodes = headElement.querySelectorAll(
+ `meta[${HELMET_ATTRIBUTE}]`
+ );
+ const existingTags = Array.prototype.slice.call(tagNodes);
+
+ expect(existingTags).to.not.equal(undefined);
+
+ const filteredTags = [].slice
+ .call(existingTags)
+ .filter(tag => {
+ return (
+ tag.getAttribute("charset") === "utf-8" ||
+ (tag.getAttribute("name") === "description" &&
+ tag.getAttribute("content") ===
+ "Test description") ||
+ (tag.getAttribute("http-equiv") ===
+ "content-type" &&
+ tag.getAttribute("content") ===
+ "text/html") ||
+ (tag.getAttribute("itemprop") === "name" &&
+ tag.getAttribute("content") ===
+ "Test name itemprop")
+ );
+ });
+
+ expect(filteredTags.length).to.be.at.least(4);
+
+ done();
+ });
+ });
+
+ it("clears all meta tags if none are specified", done => {
+ const store = createHelmetStore();
+ function HeadInfo1() {
+ useHelmet({
+ meta: [
+ {
+ name: "description",
+ content: "Test description"
+ }
+ ]
+ });
+ return null;
+ }
+ function HeadInfo2() {
+ useHelmet({});
+ return null;
+ }
+ ReactDOM.render(
+
+
+ ,
+ container
+ );
+
+ requestAnimationFrame(() => {
+ ReactDOM.render(
+
+
+ ,
+ container
+ );
+
+ requestAnimationFrame(() => {
+ const existingTags = headElement.querySelectorAll(
+ `meta[${HELMET_ATTRIBUTE}]`
+ );
+
+ expect(existingTags).to.not.equal(undefined);
+ expect(existingTags.length).to.equal(0);
+
+ done();
+ });
+ });
+ });
+
+ it("tags without 'name', 'http-equiv', 'property', 'charset', or 'itemprop' are not accepted", done => {
+ const store = createHelmetStore();
+ function HeadInfo1() {
+ useHelmet({
+ meta: [{href: "won't work"}]
+ });
+ return null;
+ }
+ ReactDOM.render(
+
+
+ ,
+ container
+ );
+
+ requestAnimationFrame(() => {
+ const existingTags = headElement.querySelectorAll(
+ `meta[${HELMET_ATTRIBUTE}]`
+ );
+
+ expect(existingTags).to.not.equal(undefined);
+ expect(existingTags.length).to.equal(0);
+
+ done();
+ });
+ });
+
+ it("sets meta tags based on deepest nested component", done => {
+ const store = createHelmetStore();
+ function HeadInfo1() {
+ useHelmet({
+ meta: [
+ {charset: "utf-8"},
+ {
+ name: "description",
+ content: "Test description"
+ }
+ ]
+ });
+ return null;
+ }
+
+ function HeadInfo2() {
+ useHelmet({
+ meta: [
+ {
+ name: "description",
+ content: "Inner description"
+ },
+ {
+ name: "keywords",
+ content: "test,meta,tags"
+ }
+ ]
+ });
+ return null;
+ }
+ ReactDOM.render(
+
+
+
+
+
+ ,
+ container
+ );
+
+ requestAnimationFrame(() => {
+ const tagNodes = headElement.querySelectorAll(
+ `meta[${HELMET_ATTRIBUTE}]`
+ );
+ const existingTags = Array.prototype.slice.call(tagNodes);
+
+ const firstTag = existingTags[0];
+ const secondTag = existingTags[1];
+ const thirdTag = existingTags[2];
+
+ expect(existingTags).to.not.equal(undefined);
+
+ expect(existingTags.length).to.be.equal(3);
+
+ expect(existingTags)
+ .to.have.deep.nested.property("[0]")
+ .that.is.an.instanceof(Element);
+ expect(firstTag).to.have.property("getAttribute");
+ expect(firstTag.getAttribute("charset")).to.equal("utf-8");
+ expect(firstTag.outerHTML).to.equal(
+ ``
+ );
+
+ expect(existingTags)
+ .to.have.deep.nested.property("[1]")
+ .that.is.an.instanceof(Element);
+ expect(secondTag).to.have.property("getAttribute");
+ expect(secondTag.getAttribute("name")).to.equal(
+ "description"
+ );
+ expect(secondTag.getAttribute("content")).to.equal(
+ "Inner description"
+ );
+ expect(secondTag.outerHTML).to.equal(
+ ``
+ );
+
+ expect(existingTags)
+ .to.have.deep.nested.property("[2]")
+ .that.is.an.instanceof(Element);
+ expect(thirdTag).to.have.property("getAttribute");
+ expect(thirdTag.getAttribute("name")).to.equal("keywords");
+ expect(thirdTag.getAttribute("content")).to.equal(
+ "test,meta,tags"
+ );
+ expect(thirdTag.outerHTML).to.equal(
+ ``
+ );
+
+ done();
+ });
+ });
+
+ it("sets meta tags based on multiple useHelmet()", done => {
+ const store = createHelmetStore();
+ function HeadInfo1() {
+ useHelmet({
+ meta: [
+ {charset: "utf-8"},
+ {
+ name: "description",
+ content: "Test description"
+ }
+ ]
+ });
+ useHelmet({
+ meta: [
+ {
+ name: "description",
+ content: "Inner description"
+ },
+ {
+ name: "keywords",
+ content: "test,meta,tags"
+ }
+ ]
+ });
+ return null;
+ }
+
+ ReactDOM.render(
+
+
+
+
+ ,
+ container
+ );
+
+ requestAnimationFrame(() => {
+ const tagNodes = headElement.querySelectorAll(
+ `meta[${HELMET_ATTRIBUTE}]`
+ );
+ const existingTags = Array.prototype.slice.call(tagNodes);
+
+ const firstTag = existingTags[0];
+ const secondTag = existingTags[1];
+ const thirdTag = existingTags[2];
+
+ expect(existingTags).to.not.equal(undefined);
+
+ expect(existingTags.length).to.be.equal(3);
+
+ expect(existingTags)
+ .to.have.deep.nested.property("[0]")
+ .that.is.an.instanceof(Element);
+ expect(firstTag).to.have.property("getAttribute");
+ expect(firstTag.getAttribute("charset")).to.equal("utf-8");
+ expect(firstTag.outerHTML).to.equal(
+ ``
+ );
+
+ expect(existingTags)
+ .to.have.deep.nested.property("[1]")
+ .that.is.an.instanceof(Element);
+ expect(secondTag).to.have.property("getAttribute");
+ expect(secondTag.getAttribute("name")).to.equal(
+ "description"
+ );
+ expect(secondTag.getAttribute("content")).to.equal(
+ "Inner description"
+ );
+ expect(secondTag.outerHTML).to.equal(
+ ``
+ );
+
+ expect(existingTags)
+ .to.have.deep.nested.property("[2]")
+ .that.is.an.instanceof(Element);
+ expect(thirdTag).to.have.property("getAttribute");
+ expect(thirdTag.getAttribute("name")).to.equal("keywords");
+ expect(thirdTag.getAttribute("content")).to.equal(
+ "test,meta,tags"
+ );
+ expect(thirdTag.outerHTML).to.equal(
+ ``
+ );
+
+ done();
+ });
+ });
+
+ it("allows duplicate meta tags if specified in the same component", done => {
+ const store = createHelmetStore();
+ function HeadInfo1() {
+ useHelmet({
+ meta: [
+ {
+ name: "description",
+ content: "Test description"
+ },
+ {
+ name: "description",
+ content: "Duplicate description"
+ }
+ ]
+ });
+ return null;
+ }
+ ReactDOM.render(
+
+
+ ,
+ container
+ );
+
+ requestAnimationFrame(() => {
+ const tagNodes = headElement.querySelectorAll(
+ `meta[${HELMET_ATTRIBUTE}]`
+ );
+ const existingTags = Array.prototype.slice.call(tagNodes);
+ const firstTag = existingTags[0];
+ const secondTag = existingTags[1];
+
+ expect(existingTags).to.not.equal(undefined);
+
+ expect(existingTags.length).to.equal(2);
+
+ expect(existingTags)
+ .to.have.deep.nested.property("[0]")
+ .that.is.an.instanceof(Element);
+ expect(firstTag).to.have.property("getAttribute");
+ expect(firstTag.getAttribute("name")).to.equal(
+ "description"
+ );
+ expect(firstTag.getAttribute("content")).to.equal(
+ "Test description"
+ );
+ expect(firstTag.outerHTML).to.equal(
+ ``
+ );
+
+ expect(existingTags)
+ .to.have.deep.nested.property("[1]")
+ .that.is.an.instanceof(Element);
+ expect(secondTag).to.have.property("getAttribute");
+ expect(secondTag.getAttribute("name")).to.equal(
+ "description"
+ );
+ expect(secondTag.getAttribute("content")).to.equal(
+ "Duplicate description"
+ );
+ expect(secondTag.outerHTML).to.equal(
+ ``
+ );
+
+ done();
+ });
+ });
+
+ it("overrides duplicate meta tags with single meta tag in a nested component", done => {
+ const store = createHelmetStore();
+ function HeadInfo1() {
+ useHelmet({
+ meta: [
+ {
+ name: "description",
+ content: "Test description"
+ },
+ {
+ name: "description",
+ content: "Duplicate description"
+ }
+ ]
+ });
+ return null;
+ }
+ function HeadInfo2() {
+ useHelmet({
+ meta: [
+ {
+ name: "description",
+ content: "Inner description"
+ }
+ ]
+ });
+ return null;
+ }
+ ReactDOM.render(
+
+
+
+
+
+ ,
+ container
+ );
+
+ requestAnimationFrame(() => {
+ const tagNodes = headElement.querySelectorAll(
+ `meta[${HELMET_ATTRIBUTE}]`
+ );
+ const existingTags = Array.prototype.slice.call(tagNodes);
+ const firstTag = existingTags[0];
+
+ expect(existingTags).to.not.equal(undefined);
+
+ expect(existingTags.length).to.equal(1);
+
+ expect(existingTags)
+ .to.have.deep.nested.property("[0]")
+ .that.is.an.instanceof(Element);
+ expect(firstTag).to.have.property("getAttribute");
+ expect(firstTag.getAttribute("name")).to.equal(
+ "description"
+ );
+ expect(firstTag.getAttribute("content")).to.equal(
+ "Inner description"
+ );
+ expect(firstTag.outerHTML).to.equal(
+ ``
+ );
+
+ done();
+ });
+ });
+
+ it("overrides duplicate meta tags with single meta tag in a nested component with multiple useHelmet()", done => {
+ const store = createHelmetStore();
+ function HeadInfo1() {
+ useHelmet({
+ meta: [
+ {
+ name: "description",
+ content: "Test description"
+ },
+ {
+ name: "description",
+ content: "Duplicate description"
+ }
+ ]
+ });
+ useHelmet({
+ meta: [
+ {
+ name: "description",
+ content: "Inner description"
+ }
+ ]
+ });
+ return null;
+ }
+ ReactDOM.render(
+
+
+
+
+ ,
+ container
+ );
+
+ requestAnimationFrame(() => {
+ const tagNodes = headElement.querySelectorAll(
+ `meta[${HELMET_ATTRIBUTE}]`
+ );
+ const existingTags = Array.prototype.slice.call(tagNodes);
+ const firstTag = existingTags[0];
+
+ expect(existingTags).to.not.equal(undefined);
+
+ expect(existingTags.length).to.equal(1);
+
+ expect(existingTags)
+ .to.have.deep.nested.property("[0]")
+ .that.is.an.instanceof(Element);
+ expect(firstTag).to.have.property("getAttribute");
+ expect(firstTag.getAttribute("name")).to.equal(
+ "description"
+ );
+ expect(firstTag.getAttribute("content")).to.equal(
+ "Inner description"
+ );
+ expect(firstTag.outerHTML).to.equal(
+ ``
+ );
+
+ done();
+ });
+ });
+
+ it("overrides single meta tag with duplicate meta tags in a nested component", done => {
+ const store = createHelmetStore();
+ function HeadInfo1() {
+ useHelmet({
+ meta: [
+ {
+ name: "description",
+ content: "Test description"
+ }
+ ]
+ });
+ return null;
+ }
+
+ function HeadInfo2() {
+ useHelmet({
+ meta: [
+ {
+ name: "description",
+ content: "Inner description"
+ },
+ {
+ name: "description",
+ content: "Inner duplicate description"
+ }
+ ]
+ });
+ return null;
+ }
+ ReactDOM.render(
+
+
+
+
+
+ ,
+ container
+ );
+
+ requestAnimationFrame(() => {
+ const tagNodes = headElement.querySelectorAll(
+ `meta[${HELMET_ATTRIBUTE}]`
+ );
+ const existingTags = Array.prototype.slice.call(tagNodes);
+ const firstTag = existingTags[0];
+ const secondTag = existingTags[1];
+
+ expect(existingTags).to.not.equal(undefined);
+
+ expect(existingTags.length).to.equal(2);
+
+ expect(existingTags)
+ .to.have.deep.nested.property("[0]")
+ .that.is.an.instanceof(Element);
+ expect(firstTag).to.have.property("getAttribute");
+ expect(firstTag.getAttribute("name")).to.equal(
+ "description"
+ );
+ expect(firstTag.getAttribute("content")).to.equal(
+ "Inner description"
+ );
+ expect(firstTag.outerHTML).to.equal(
+ ``
+ );
+
+ expect(existingTags)
+ .to.have.deep.nested.property("[1]")
+ .that.is.an.instanceof(Element);
+ expect(secondTag).to.have.property("getAttribute");
+ expect(secondTag.getAttribute("name")).to.equal(
+ "description"
+ );
+ expect(secondTag.getAttribute("content")).to.equal(
+ "Inner duplicate description"
+ );
+ expect(secondTag.outerHTML).to.equal(
+ ``
+ );
+
+ done();
+ });
+ });
+
+ it("overrides single meta tag with duplicate meta tags in a nested component with multiple useHelmet()", done => {
+ const store = createHelmetStore();
+ function HeadInfo1() {
+ useHelmet({
+ meta: [
+ {
+ name: "description",
+ content: "Test description"
+ }
+ ]
+ });
+ useHelmet({
+ meta: [
+ {
+ name: "description",
+ content: "Inner description"
+ },
+ {
+ name: "description",
+ content: "Inner duplicate description"
+ }
+ ]
+ });
+ return null;
+ }
+
+ ReactDOM.render(
+
+
+
+
+ ,
+ container
+ );
+
+ requestAnimationFrame(() => {
+ const tagNodes = headElement.querySelectorAll(
+ `meta[${HELMET_ATTRIBUTE}]`
+ );
+ const existingTags = Array.prototype.slice.call(tagNodes);
+ const firstTag = existingTags[0];
+ const secondTag = existingTags[1];
+
+ expect(existingTags).to.not.equal(undefined);
+
+ expect(existingTags.length).to.equal(2);
+
+ expect(existingTags)
+ .to.have.deep.nested.property("[0]")
+ .that.is.an.instanceof(Element);
+ expect(firstTag).to.have.property("getAttribute");
+ expect(firstTag.getAttribute("name")).to.equal(
+ "description"
+ );
+ expect(firstTag.getAttribute("content")).to.equal(
+ "Inner description"
+ );
+ expect(firstTag.outerHTML).to.equal(
+ ``
+ );
+
+ expect(existingTags)
+ .to.have.deep.nested.property("[1]")
+ .that.is.an.instanceof(Element);
+ expect(secondTag).to.have.property("getAttribute");
+ expect(secondTag.getAttribute("name")).to.equal(
+ "description"
+ );
+ expect(secondTag.getAttribute("content")).to.equal(
+ "Inner duplicate description"
+ );
+ expect(secondTag.outerHTML).to.equal(
+ ``
+ );
+
+ done();
+ });
+ });
+
+ it("does not render tag when primary attribute is null", done => {
+ const store = createHelmetStore();
+ function HeadInfo1() {
+ useHelmet({
+ meta: [
+ {
+ name: undefined,
+ content: "Inner duplicate description"
+ }
+ ]
+ });
+ return null;
+ }
+ ReactDOM.render(
+
+
+ ,
+ container
+ );
+
+ requestAnimationFrame(() => {
+ const tagNodes = headElement.querySelectorAll(
+ `meta[${HELMET_ATTRIBUTE}]`
+ );
+ const existingTags = Array.prototype.slice.call(tagNodes);
+ expect(existingTags).to.be.empty;
+
+ done();
+ });
+ });
+
+ it("fails gracefully when meta is wrong shape", done => {
+ const store = createHelmetStore();
+ const error = sinon.stub(console, "error");
+ const warn = sinon.stub(console, "warn");
+
+ function HeadInfo1() {
+ useHelmet({
+ meta: {name: "title", content: "some title"}
+ });
+ return null;
+ }
+
+ ReactDOM.render(
+
+
+ ,
+ container
+ );
+
+ requestAnimationFrame(() => {
+ const tagNodes = headElement.querySelectorAll(
+ `meta[${HELMET_ATTRIBUTE}]`
+ );
+ const existingTags = Array.prototype.slice.call(tagNodes);
+ expect(existingTags).to.be.empty;
+
+ expect(warn.called).to.be.true;
+
+ const [warning] = warn.getCall(0).args;
+ expect(warning).to.equal(
+ `Helmet: meta should be of type "Array". Instead found type "object"`
+ );
+
+ error.restore();
+ warn.restore();
+
+ done();
+ });
+ });
+ });
+
+ describe("link tags", () => {
+ it("updates link tags", done => {
+ const store = createHelmetStore();
+ function HeadInfo1() {
+ useHelmet({
+ link: [
+ {
+ href: "http://localhost/helmet",
+ rel: "canonical"
+ },
+ {
+ href: "http://localhost/style.css",
+ rel: "stylesheet",
+ type: "text/css"
+ }
+ ]
+ });
+ return null;
+ }
+ ReactDOM.render(
+
+
+ ,
+ container
+ );
+
+ requestAnimationFrame(() => {
+ const tagNodes = headElement.querySelectorAll(
+ `link[${HELMET_ATTRIBUTE}]`
+ );
+ const existingTags = Array.prototype.slice.call(tagNodes);
+
+ expect(existingTags).to.not.equal(undefined);
+
+ const filteredTags = [].slice
+ .call(existingTags)
+ .filter(tag => {
+ return (
+ (tag.getAttribute("href") ===
+ "http://localhost/style.css" &&
+ tag.getAttribute("rel") === "stylesheet" &&
+ tag.getAttribute("type") === "text/css") ||
+ (tag.getAttribute("href") ===
+ "http://localhost/helmet" &&
+ tag.getAttribute("rel") === "canonical")
+ );
+ });
+
+ expect(filteredTags.length).to.be.at.least(2);
+
+ done();
+ });
+ });
+
+ it("clears all link tags if none are specified", done => {
+ const store = createHelmetStore();
+ function HeadInfo1() {
+ useHelmet({
+ link: [
+ {
+ href: "http://localhost/helmet",
+ rel: "canonical"
+ }
+ ]
+ });
+ return null;
+ }
+ function HeadInfo2() {
+ useHelmet({});
+ return null;
+ }
+ ReactDOM.render(
+
+
+ ,
+ container
+ );
+
+ requestAnimationFrame(() => {
+ ReactDOM.render(
+
+
+ ,
+ container
+ );
+
+ requestAnimationFrame(() => {
+ const tagNodes = headElement.querySelectorAll(
+ `link[${HELMET_ATTRIBUTE}]`
+ );
+ const existingTags = Array.prototype.slice.call(
+ tagNodes
+ );
+
+ expect(existingTags).to.not.equal(undefined);
+ expect(existingTags.length).to.equal(0);
+
+ done();
+ });
+ });
+ });
+
+ it("tags without 'href' or 'rel' are not accepted, even if they are valid for other tags", done => {
+ const store = createHelmetStore();
+ function HeadInfo1() {
+ useHelmet({
+ link: [{"http-equiv": "won't work"}]
+ });
+ return null;
+ }
+ ReactDOM.render(
+
+
+ ,
+ container
+ );
+
+ requestAnimationFrame(() => {
+ const tagNodes = headElement.querySelectorAll(
+ `link[${HELMET_ATTRIBUTE}]`
+ );
+ const existingTags = Array.prototype.slice.call(tagNodes);
+
+ expect(existingTags).to.not.equal(undefined);
+ expect(existingTags.length).to.equal(0);
+
+ done();
+ });
+ });
+
+ it("tags 'rel' and 'href' properly use 'rel' as the primary identification for this tag, regardless of ordering", done => {
+ const store = createHelmetStore();
+ function HeadInfo1() {
+ useHelmet({
+ link: [
+ {
+ href: "http://localhost/helmet",
+ rel: "canonical"
+ }
+ ]
+ });
+ return null;
+ }
+ function HeadInfo2() {
+ useHelmet({
+ link: [
+ {
+ rel: "canonical",
+ href: "http://localhost/helmet/new"
+ }
+ ]
+ });
+ return null;
+ }
+ function HeadInfo3() {
+ useHelmet({
+ link: [
+ {
+ href: "http://localhost/helmet/newest",
+ rel: "canonical"
+ }
+ ]
+ });
+ return null;
+ }
+ 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.equal(1);
+
+ expect(existingTags)
+ .to.have.deep.nested.property("[0]")
+ .that.is.an.instanceof(Element);
+ expect(firstTag).to.have.property("getAttribute");
+ expect(firstTag.getAttribute("rel")).to.equal("canonical");
+ expect(firstTag.getAttribute("href")).to.equal(
+ "http://localhost/helmet/newest"
+ );
+ expect(firstTag.outerHTML).to.equal(
+ ``
+ );
+
+ done();
+ });
+ });
+
+ it("tags 'rel' and 'href' properly use 'rel' as the primary identification for this tag, regardless of ordering, using multiple useHelmet()", done => {
+ const store = createHelmetStore();
+ function HeadInfo1() {
+ useHelmet({
+ link: [
+ {
+ href: "http://localhost/helmet",
+ rel: "canonical"
+ }
+ ]
+ });
+ useHelmet({
+ link: [
+ {
+ rel: "canonical",
+ href: "http://localhost/helmet/new"
+ }
+ ]
+ });
+ useHelmet({
+ link: [
+ {
+ href: "http://localhost/helmet/newest",
+ rel: "canonical"
+ }
+ ]
+ });
+ return null;
+ }
+ 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.equal(1);
+
+ expect(existingTags)
+ .to.have.deep.nested.property("[0]")
+ .that.is.an.instanceof(Element);
+ expect(firstTag).to.have.property("getAttribute");
+ expect(firstTag.getAttribute("rel")).to.equal("canonical");
+ expect(firstTag.getAttribute("href")).to.equal(
+ "http://localhost/helmet/newest"
+ );
+ expect(firstTag.outerHTML).to.equal(
+ ``
+ );
+
+ done();
+ });
+ });
+
+ it("tags with rel='stylesheet' uses the href as the primary identification of the tag, regardless of ordering", done => {
+ const store = createHelmetStore();
+ function HeadInfo1() {
+ useHelmet({
+ link: [
+ {
+ href: "http://localhost/style.css",
+ rel: "stylesheet",
+ type: "text/css",
+ media: "all"
+ }
+ ]
+ });
+ return null;
+ }
+ function HeadInfo2() {
+ useHelmet({
+ link: [
+ {
+ rel: "stylesheet",
+ href: "http://localhost/inner.css",
+ type: "text/css",
+ media: "all"
+ }
+ ]
+ });
+ return null;
+ }
+ ReactDOM.render(
+
+
+
+
+
+ ,
+ container
+ );
+
+ requestAnimationFrame(() => {
+ const tagNodes = headElement.querySelectorAll(
+ `link[${HELMET_ATTRIBUTE}]`
+ );
+ const existingTags = Array.prototype.slice.call(tagNodes);
+ const firstTag = existingTags[0];
+ const secondTag = existingTags[1];
+
+ expect(existingTags).to.not.equal(undefined);
+
+ expect(existingTags.length).to.equal(2);
+
+ expect(existingTags)
+ .to.have.deep.nested.property("[0]")
+ .that.is.an.instanceof(Element);
+ expect(firstTag).to.have.property("getAttribute");
+ expect(firstTag.getAttribute("href")).to.equal(
+ "http://localhost/style.css"
+ );
+ expect(firstTag.getAttribute("rel")).to.equal("stylesheet");
+ expect(firstTag.getAttribute("type")).to.equal("text/css");
+ expect(firstTag.getAttribute("media")).to.equal("all");
+ expect(firstTag.outerHTML).to.equal(
+ ``
+ );
+
+ expect(existingTags)
+ .to.have.deep.nested.property("[1]")
+ .that.is.an.instanceof(Element);
+ expect(secondTag).to.have.property("getAttribute");
+ expect(secondTag.getAttribute("rel")).to.equal(
+ "stylesheet"
+ );
+ expect(secondTag.getAttribute("href")).to.equal(
+ "http://localhost/inner.css"
+ );
+ expect(secondTag.getAttribute("type")).to.equal("text/css");
+ expect(secondTag.getAttribute("media")).to.equal("all");
+ expect(secondTag.outerHTML).to.equal(
+ ``
+ );
+
+ done();
+ });
+ });
+
+ it("tags with rel='stylesheet' uses the href as the primary identification of the tag, regardless of ordering, with multiple useHelmet()", done => {
+ const store = createHelmetStore();
+ function HeadInfo1() {
+ useHelmet({
+ link: [
+ {
+ href: "http://localhost/style.css",
+ rel: "stylesheet",
+ type: "text/css",
+ media: "all"
+ }
+ ]
+ });
+ useHelmet({
+ link: [
+ {
+ rel: "stylesheet",
+ href: "http://localhost/inner.css",
+ type: "text/css",
+ media: "all"
+ }
+ ]
+ });
+ return null;
+ }
+ ReactDOM.render(
+
+
+
+
+ ,
+ container
+ );
+
+ requestAnimationFrame(() => {
+ const tagNodes = headElement.querySelectorAll(
+ `link[${HELMET_ATTRIBUTE}]`
+ );
+ const existingTags = Array.prototype.slice.call(tagNodes);
+ const firstTag = existingTags[0];
+ const secondTag = existingTags[1];
+
+ expect(existingTags).to.not.equal(undefined);
+
+ expect(existingTags.length).to.equal(2);
+
+ expect(existingTags)
+ .to.have.deep.nested.property("[0]")
+ .that.is.an.instanceof(Element);
+ expect(firstTag).to.have.property("getAttribute");
+ expect(firstTag.getAttribute("href")).to.equal(
+ "http://localhost/style.css"
+ );
+ expect(firstTag.getAttribute("rel")).to.equal("stylesheet");
+ expect(firstTag.getAttribute("type")).to.equal("text/css");
+ expect(firstTag.getAttribute("media")).to.equal("all");
+ expect(firstTag.outerHTML).to.equal(
+ ``
+ );
+
+ expect(existingTags)
+ .to.have.deep.nested.property("[1]")
+ .that.is.an.instanceof(Element);
+ expect(secondTag).to.have.property("getAttribute");
+ expect(secondTag.getAttribute("rel")).to.equal(
+ "stylesheet"
+ );
+ expect(secondTag.getAttribute("href")).to.equal(
+ "http://localhost/inner.css"
+ );
+ expect(secondTag.getAttribute("type")).to.equal("text/css");
+ expect(secondTag.getAttribute("media")).to.equal("all");
+ expect(secondTag.outerHTML).to.equal(
+ ``
+ );
+
+ done();
+ });
+ });
+
+ it("sets link tags based on deepest nested component", done => {
+ const store = createHelmetStore();
+ function HeadInfo1() {
+ useHelmet({
+ link: [
+ {
+ rel: "canonical",
+ href: "http://localhost/helmet"
+ },
+ {
+ href: "http://localhost/style.css",
+ rel: "stylesheet",
+ type: "text/css",
+ media: "all"
+ }
+ ]
+ });
+
+ return null;
+ }
+ function HeadInfo2() {
+ useHelmet({
+ link: [
+ {
+ rel: "canonical",
+ href: "http://localhost/helmet/innercomponent"
+ },
+ {
+ href: "http://localhost/inner.css",
+ rel: "stylesheet",
+ type: "text/css",
+ media: "all"
+ }
+ ]
+ });
+ return null;
+ }
+ ReactDOM.render(
+
+
+
+
+
+ ,
+ container
+ );
+
+ requestAnimationFrame(() => {
+ const tagNodes = headElement.querySelectorAll(
+ `link[${HELMET_ATTRIBUTE}]`
+ );
+ const existingTags = Array.prototype.slice.call(tagNodes);
+ const firstTag = existingTags[0];
+ const secondTag = existingTags[1];
+ const thirdTag = existingTags[2];
+
+ expect(existingTags).to.not.equal(undefined);
+
+ expect(existingTags.length).to.be.at.least(2);
+
+ expect(existingTags)
+ .to.have.deep.nested.property("[0]")
+ .that.is.an.instanceof(Element);
+ expect(firstTag).to.have.property("getAttribute");
+ expect(firstTag.getAttribute("href")).to.equal(
+ "http://localhost/style.css"
+ );
+ expect(firstTag.getAttribute("rel")).to.equal("stylesheet");
+ expect(firstTag.getAttribute("type")).to.equal("text/css");
+ expect(firstTag.getAttribute("media")).to.equal("all");
+ expect(firstTag.outerHTML).to.equal(
+ ``
+ );
+
+ expect(existingTags)
+ .to.have.deep.nested.property("[1]")
+ .that.is.an.instanceof(Element);
+ expect(secondTag).to.have.property("getAttribute");
+ expect(secondTag.getAttribute("href")).to.equal(
+ "http://localhost/helmet/innercomponent"
+ );
+ expect(secondTag.getAttribute("rel")).to.equal("canonical");
+ expect(secondTag.outerHTML).to.equal(
+ ``
+ );
+
+ expect(existingTags)
+ .to.have.deep.nested.property("[2]")
+ .that.is.an.instanceof(Element);
+ expect(thirdTag).to.have.property("getAttribute");
+ expect(thirdTag.getAttribute("href")).to.equal(
+ "http://localhost/inner.css"
+ );
+ expect(thirdTag.getAttribute("rel")).to.equal("stylesheet");
+ expect(thirdTag.getAttribute("type")).to.equal("text/css");
+ expect(thirdTag.getAttribute("media")).to.equal("all");
+ expect(thirdTag.outerHTML).to.equal(
+ ``
+ );
+
+ done();
+ });
+ });
+
+ it("allows duplicate link tags if specified in the same component", done => {
+ const store = createHelmetStore();
+ function HeadInfo1() {
+ useHelmet({
+ link: [
+ {
+ rel: "canonical",
+ href: "http://localhost/helmet"
+ },
+ {
+ rel: "canonical",
+ href: "http://localhost/helmet/component"
+ }
+ ]
+ });
+ return null;
+ }
+ ReactDOM.render(
+
+
+ ,
+ container
+ );
+
+ requestAnimationFrame(() => {
+ const tagNodes = headElement.querySelectorAll(
+ `link[${HELMET_ATTRIBUTE}]`
+ );
+ const existingTags = Array.prototype.slice.call(tagNodes);
+ const firstTag = existingTags[0];
+ const secondTag = existingTags[1];
+
+ expect(existingTags).to.not.equal(undefined);
+
+ expect(existingTags.length).to.be.at.least(2);
+
+ expect(existingTags)
+ .to.have.deep.nested.property("[0]")
+ .that.is.an.instanceof(Element);
+ expect(firstTag).to.have.property("getAttribute");
+ expect(firstTag.getAttribute("rel")).to.equal("canonical");
+ expect(firstTag.getAttribute("href")).to.equal(
+ "http://localhost/helmet"
+ );
+ expect(firstTag.outerHTML).to.equal(
+ ``
+ );
+
+ expect(existingTags)
+ .to.have.deep.nested.property("[1]")
+ .that.is.an.instanceof(Element);
+ expect(secondTag).to.have.property("getAttribute");
+ expect(secondTag.getAttribute("rel")).to.equal("canonical");
+ expect(secondTag.getAttribute("href")).to.equal(
+ "http://localhost/helmet/component"
+ );
+ expect(secondTag.outerHTML).to.equal(
+ ``
+ );
+
+ done();
+ });
+ });
+
+ it("overrides duplicate link tags with a single link tag in a nested component", done => {
+ const store = createHelmetStore();
+ function HeadInfo1() {
+ useHelmet({
+ link: [
+ {
+ rel: "canonical",
+ href: "http://localhost/helmet"
+ },
+ {
+ rel: "canonical",
+ href: "http://localhost/helmet/component"
+ }
+ ]
+ });
+ return null;
+ }
+ function HeadInfo2() {
+ useHelmet({
+ link: [
+ {
+ rel: "canonical",
+ href: "http://localhost/helmet/innercomponent"
+ }
+ ]
+ });
+ return null;
+ }
+ 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(1);
+
+ expect(existingTags)
+ .to.have.deep.nested.property("[0]")
+ .that.is.an.instanceof(Element);
+ expect(firstTag).to.have.property("getAttribute");
+ expect(firstTag.getAttribute("rel")).to.equal("canonical");
+ expect(firstTag.getAttribute("href")).to.equal(
+ "http://localhost/helmet/innercomponent"
+ );
+ expect(firstTag.outerHTML).to.equal(
+ ``
+ );
+
+ done();
+ });
+ });
+
+ it("overrides duplicate link tags with a single link tag with multiple useHelmet()", done => {
+ const store = createHelmetStore();
+ function HeadInfo1() {
+ useHelmet({
+ link: [
+ {
+ rel: "canonical",
+ href: "http://localhost/helmet"
+ }
+ ]
+ });
+ useHelmet({
+ link: [
+ {
+ rel: "canonical",
+ href: "http://localhost/helmet/component"
+ }
+ ]
+ });
+ return null;
+ }
+ function HeadInfo2() {
+ useHelmet({
+ link: [
+ {
+ rel: "canonical",
+ href: "http://localhost/helmet/innercomponent"
+ }
+ ]
+ });
+ return null;
+ }
+ 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(1);
+
+ expect(existingTags)
+ .to.have.deep.nested.property("[0]")
+ .that.is.an.instanceof(Element);
+ expect(firstTag).to.have.property("getAttribute");
+ expect(firstTag.getAttribute("rel")).to.equal("canonical");
+ expect(firstTag.getAttribute("href")).to.equal(
+ "http://localhost/helmet/innercomponent"
+ );
+ expect(firstTag.outerHTML).to.equal(
+ ``
+ );
+
+ done();
+ });
+ });
+
+ it("overrides single link tag with duplicate link tags in a nested component", done => {
+ const store = createHelmetStore();
+ function HeadInfo1() {
+ useHelmet({
+ link: [
+ {
+ rel: "canonical",
+ href: "http://localhost/helmet"
+ }
+ ]
+ });
+ return null;
+ }
+ function HeadInfo2() {
+ useHelmet({
+ link: [
+ {
+ rel: "canonical",
+ href: "http://localhost/helmet/component"
+ },
+ {
+ rel: "canonical",
+ href: "http://localhost/helmet/innercomponent"
+ }
+ ]
+ });
+ return null;
+ }
+ ReactDOM.render(
+
+
+
+
+
+ ,
+ container
+ );
+
+ requestAnimationFrame(() => {
+ const tagNodes = headElement.querySelectorAll(
+ `link[${HELMET_ATTRIBUTE}]`
+ );
+ const existingTags = Array.prototype.slice.call(tagNodes);
+ const firstTag = existingTags[0];
+ const secondTag = existingTags[1];
+
+ expect(existingTags).to.not.equal(undefined);
+
+ expect(existingTags.length).to.be.equal(2);
+
+ expect(existingTags)
+ .to.have.deep.nested.property("[0]")
+ .that.is.an.instanceof(Element);
+ expect(firstTag).to.have.property("getAttribute");
+ expect(firstTag.getAttribute("rel")).to.equal("canonical");
+ expect(firstTag.getAttribute("href")).to.equal(
+ "http://localhost/helmet/component"
+ );
+ expect(firstTag.outerHTML).to.equal(
+ ``
+ );
+
+ expect(existingTags)
+ .to.have.deep.nested.property("[1]")
+ .that.is.an.instanceof(Element);
+ expect(secondTag).to.have.property("getAttribute");
+ expect(secondTag.getAttribute("rel")).to.equal("canonical");
+ expect(secondTag.getAttribute("href")).to.equal(
+ "http://localhost/helmet/innercomponent"
+ );
+ expect(secondTag.outerHTML).to.equal(
+ ``
+ );
+
+ done();
+ });
+ });
+
+ it("does not render tag when primary attribute is null", done => {
+ const store = createHelmetStore();
+ function HeadInfo1() {
+ useHelmet({
+ link: [
+ {rel: "icon", sizes: "192x192", href: null},
+ {
+ rel: "canonical",
+ href: "http://localhost/helmet/component"
+ }
+ ]
+ });
+ return null;
+ }
+ 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(1);
+
+ expect(existingTags)
+ .to.have.deep.nested.property("[0]")
+ .that.is.an.instanceof(Element);
+ expect(firstTag).to.have.property("getAttribute");
+ expect(firstTag.getAttribute("rel")).to.equal("canonical");
+ expect(firstTag.getAttribute("href")).to.equal(
+ "http://localhost/helmet/component"
+ );
+ expect(firstTag.outerHTML).to.equal(
+ ``
+ );
+
+ done();
+ });
+ });
+ });
+
+ describe("script tags", () => {
+ it("updates script tags", done => {
+ const store = createHelmetStore();
+ const scriptInnerHTML = `
+ {
+ "@context": "http://schema.org",
+ "@type": "NewsArticle",
+ "url": "http://localhost/helmet"
+ }
+ `;
+ function HeadInfo1() {
+ useHelmet({
+ script: [
+ {
+ src: "http://localhost/test.js",
+ type: "text/javascript"
+ },
+ {
+ src: "http://localhost/test2.js",
+ type: "text/javascript"
+ },
+ {
+ type: "application/ld+json",
+ innerHTML: scriptInnerHTML
+ }
+ ]
+ });
+ return null;
+ }
+ ReactDOM.render(
+
+
+ ,
+ container
+ );
+
+ requestAnimationFrame(() => {
+ const existingTags = headElement.getElementsByTagName(
+ "script"
+ );
+
+ expect(existingTags).to.not.equal(undefined);
+
+ const filteredTags = [].slice
+ .call(existingTags)
+ .filter(tag => {
+ return (
+ (tag.getAttribute("src") ===
+ "http://localhost/test.js" &&
+ tag.getAttribute("type") ===
+ "text/javascript") ||
+ (tag.getAttribute("src") ===
+ "http://localhost/test2.js" &&
+ tag.getAttribute("type") ===
+ "text/javascript") ||
+ (tag.getAttribute("type") ===
+ "application/ld+json" &&
+ tag.innerHTML === scriptInnerHTML)
+ );
+ });
+
+ expect(filteredTags.length).to.be.at.least(3);
+
+ done();
+ });
+ });
+
+ it("clears all scripts tags if none are specified", done => {
+ const store = createHelmetStore();
+ function HeadInfo1() {
+ useHelmet({
+ script: [
+ {
+ src: "http://localhost/test.js",
+ type: "text/javascript"
+ }
+ ]
+ });
+ return null;
+ }
+ function HeadInfo2() {
+ useHelmet({});
+ return null;
+ }
+ ReactDOM.render(
+
+
+ ,
+ container
+ );
+
+ requestAnimationFrame(() => {
+ ReactDOM.render(
+
+
+ ,
+ container
+ );
+
+ requestAnimationFrame(() => {
+ const existingTags = headElement.querySelectorAll(
+ `script[${HELMET_ATTRIBUTE}]`
+ );
+
+ expect(existingTags).to.not.equal(undefined);
+ expect(existingTags.length).to.equal(0);
+
+ done();
+ });
+ });
+ });
+
+ it("tags without 'src' are not accepted", done => {
+ const store = createHelmetStore();
+ function HeadInfo1() {
+ useHelmet({
+ script: [{property: "won't work"}]
+ });
+ return null;
+ }
+ ReactDOM.render(
+
+
+ ,
+ container
+ );
+
+ requestAnimationFrame(() => {
+ const existingTags = headElement.querySelectorAll(
+ `script[${HELMET_ATTRIBUTE}]`
+ );
+
+ expect(existingTags).to.not.equal(undefined);
+ expect(existingTags.length).to.equal(0);
+
+ done();
+ });
+ });
+
+ it("sets script tags based on deepest nested component", done => {
+ const store = createHelmetStore();
+ function HeadInfo1() {
+ useHelmet({
+ script: [
+ {
+ src: "http://localhost/test.js",
+ type: "text/javascript"
+ }
+ ]
+ });
+ return null;
+ }
+ function HeadInfo2() {
+ useHelmet({
+ script: [
+ {
+ src: "http://localhost/test2.js",
+ type: "text/javascript"
+ }
+ ]
+ });
+ return null;
+ }
+ ReactDOM.render(
+
+
+
+
+
+ ,
+ container
+ );
+
+ requestAnimationFrame(() => {
+ const tagNodes = headElement.querySelectorAll(
+ `script[${HELMET_ATTRIBUTE}]`
+ );
+ const existingTags = Array.prototype.slice.call(tagNodes);
+ const firstTag = existingTags[0];
+ const secondTag = existingTags[1];
+
+ expect(existingTags).to.not.equal(undefined);
+
+ expect(existingTags.length).to.be.at.least(2);
+
+ expect(existingTags)
+ .to.have.deep.nested.property("[0]")
+ .that.is.an.instanceof(Element);
+ expect(firstTag).to.have.property("getAttribute");
+ expect(firstTag.getAttribute("src")).to.equal(
+ "http://localhost/test.js"
+ );
+ expect(firstTag.getAttribute("type")).to.equal(
+ "text/javascript"
+ );
+ expect(firstTag.outerHTML).to.equal(
+ ``
+ );
+
+ expect(existingTags)
+ .to.have.deep.nested.property("[1]")
+ .that.is.an.instanceof(Element);
+ expect(secondTag).to.have.property("getAttribute");
+ expect(secondTag.getAttribute("src")).to.equal(
+ "http://localhost/test2.js"
+ );
+ expect(secondTag.getAttribute("type")).to.equal(
+ "text/javascript"
+ );
+ expect(secondTag.outerHTML).to.equal(
+ ``
+ );
+
+ done();
+ });
+ });
+
+ it("sets script tags based on multiple useHelmet()", done => {
+ const store = createHelmetStore();
+ function HeadInfo1() {
+ useHelmet({
+ script: [
+ {
+ src: "http://localhost/test.js",
+ type: "text/javascript"
+ }
+ ]
+ });
+ useHelmet({
+ script: [
+ {
+ src: "http://localhost/test2.js",
+ type: "text/javascript"
+ }
+ ]
+ });
+ return null;
+ }
+ ReactDOM.render(
+
+
+
+
+ ,
+ container
+ );
+
+ requestAnimationFrame(() => {
+ const tagNodes = headElement.querySelectorAll(
+ `script[${HELMET_ATTRIBUTE}]`
+ );
+ const existingTags = Array.prototype.slice.call(tagNodes);
+ const firstTag = existingTags[0];
+ const secondTag = existingTags[1];
+
+ expect(existingTags).to.not.equal(undefined);
+
+ expect(existingTags.length).to.be.at.least(2);
+
+ expect(existingTags)
+ .to.have.deep.nested.property("[0]")
+ .that.is.an.instanceof(Element);
+ expect(firstTag).to.have.property("getAttribute");
+ expect(firstTag.getAttribute("src")).to.equal(
+ "http://localhost/test.js"
+ );
+ expect(firstTag.getAttribute("type")).to.equal(
+ "text/javascript"
+ );
+ expect(firstTag.outerHTML).to.equal(
+ ``
+ );
+
+ expect(existingTags)
+ .to.have.deep.nested.property("[1]")
+ .that.is.an.instanceof(Element);
+ expect(secondTag).to.have.property("getAttribute");
+ expect(secondTag.getAttribute("src")).to.equal(
+ "http://localhost/test2.js"
+ );
+ expect(secondTag.getAttribute("type")).to.equal(
+ "text/javascript"
+ );
+ expect(secondTag.outerHTML).to.equal(
+ ``
+ );
+
+ done();
+ });
+ });
+
+ it("sets undefined attribute values to empty strings", done => {
+ const store = createHelmetStore();
+ function HeadInfo1() {
+ useHelmet({
+ script: [
+ {
+ src: "foo.js",
+ async: undefined
+ }
+ ]
+ });
+ return null;
+ }
+ ReactDOM.render(
+
+
+ ,
+ container
+ );
+
+ requestAnimationFrame(() => {
+ const existingTag = headElement.querySelector(
+ `script[${HELMET_ATTRIBUTE}]`
+ );
+
+ expect(existingTag).to.not.equal(undefined);
+ expect(existingTag.outerHTML)
+ .to.be.a("string")
+ .that.equals(
+ ``
+ );
+
+ done();
+ });
+ });
+
+ it("does not render tag when primary attribute (src) is null", done => {
+ const store = createHelmetStore();
+ function HeadInfo1() {
+ useHelmet({
+ script: [
+ {
+ src: undefined,
+ type: "text/javascript"
+ }
+ ]
+ });
+ return null;
+ }
+ ReactDOM.render(
+
+
+ ,
+ container
+ );
+
+ requestAnimationFrame(() => {
+ const tagNodes = headElement.querySelectorAll(
+ `script[${HELMET_ATTRIBUTE}]`
+ );
+ const existingTags = Array.prototype.slice.call(tagNodes);
+ expect(existingTags).to.be.empty;
+
+ done();
+ });
+ });
+
+ it("does not render tag when primary attribute (innerHTML) is null", done => {
+ const store = createHelmetStore();
+ function HeadInfo1() {
+ useHelmet({
+ script: [
+ {
+ innerHTML: undefined
+ }
+ ]
+ });
+ return null;
+ }
+ ReactDOM.render(
+
+
+ ,
+ container
+ );
+
+ requestAnimationFrame(() => {
+ const tagNodes = headElement.querySelectorAll(
+ `script[${HELMET_ATTRIBUTE}]`
+ );
+ const existingTags = Array.prototype.slice.call(tagNodes);
+ expect(existingTags).to.be.empty;
+
+ done();
+ });
+ });
+ });
+
+ describe("noscript tags", () => {
+ it("updates noscript tags", done => {
+ const store = createHelmetStore();
+ const noscriptInnerHTML = ``;
+ function HeadInfo1() {
+ useHelmet({
+ noscript: [{id: "bar", innerHTML: noscriptInnerHTML}]
+ });
+ return null;
+ }
+ ReactDOM.render(
+
+
+ ,
+ container
+ );
+
+ requestAnimationFrame(() => {
+ const existingTags = headElement.getElementsByTagName(
+ "noscript"
+ );
+
+ expect(existingTags).to.not.equal(undefined);
+ expect(existingTags.length).to.equal(1);
+ expect(
+ existingTags[0].innerHTML === noscriptInnerHTML &&
+ existingTags[0].id === "bar"
+ );
+
+ done();
+ });
+ });
+
+ it("clears all noscripts tags if none are specified", done => {
+ const store = createHelmetStore();
+ function HeadInfo1() {
+ useHelmet({
+ noscript: [{id: "bar"}]
+ });
+ return null;
+ }
+ function HeadInfo2() {
+ useHelmet({});
+ return null;
+ }
+ ReactDOM.render(
+
+
+ ,
+ container
+ );
+
+ requestAnimationFrame(() => {
+ ReactDOM.render(
+
+
+ ,
+ container
+ );
+
+ requestAnimationFrame(() => {
+ const existingTags = headElement.querySelectorAll(
+ `script[${HELMET_ATTRIBUTE}]`
+ );
+
+ expect(existingTags).to.not.equal(undefined);
+ expect(existingTags.length).to.equal(0);
+
+ done();
+ });
+ });
+ });
+
+ it("tags without 'innerHTML' are not accepted", done => {
+ const store = createHelmetStore();
+ ReactDOM.render(
+
+
+ ,
+ container
+ );
+
+ requestAnimationFrame(() => {
+ const existingTags = headElement.querySelectorAll(
+ `noscript[${HELMET_ATTRIBUTE}]`
+ );
+
+ expect(existingTags).to.not.equal(undefined);
+ expect(existingTags.length).to.equal(0);
+
+ done();
+ });
+ });
+
+ it("does not render tag when primary attribute is null", done => {
+ const store = createHelmetStore();
+ ReactDOM.render(
+
+
+ ,
+ container
+ );
+
+ requestAnimationFrame(() => {
+ const tagNodes = headElement.querySelectorAll(
+ `noscript[${HELMET_ATTRIBUTE}]`
+ );
+ const existingTags = Array.prototype.slice.call(tagNodes);
+ expect(existingTags).to.be.empty;
+
+ done();
+ });
+ });
+ });
+
+ describe("style tags", () => {
+ it("updates style tags", done => {
+ const store = createHelmetStore();
+ const cssText1 = `
+ body {
+ background-color: green;
+ }
+ `;
+ const cssText2 = `
+ p {
+ font-size: 12px;
+ }
+ `;
+ ReactDOM.render(
+
+
+ ,
+ container
+ );
+
+ requestAnimationFrame(() => {
+ const tagNodes = headElement.querySelectorAll(
+ `style[${HELMET_ATTRIBUTE}]`
+ );
+ const existingTags = Array.prototype.slice.call(tagNodes);
+
+ const [firstTag, secondTag] = existingTags;
+ expect(existingTags).to.not.equal(undefined);
+ expect(existingTags.length).to.be.equal(2);
+
+ expect(existingTags)
+ .to.have.deep.nested.property("[0]")
+ .that.is.an.instanceof(Element);
+ expect(firstTag).to.have.property("getAttribute");
+ expect(firstTag.getAttribute("type")).to.equal("text/css");
+ expect(firstTag.innerHTML).to.equal(cssText1);
+ expect(firstTag.outerHTML).to.equal(
+ ``
+ );
+
+ expect(existingTags)
+ .to.have.deep.nested.property("[1]")
+ .that.is.an.instanceof(Element);
+ expect(secondTag.innerHTML).to.equal(cssText2);
+ expect(secondTag.outerHTML).to.equal(
+ ``
+ );
+
+ done();
+ });
+ });
+
+ it("clears all style tags if none are specified", done => {
+ const store = createHelmetStore();
+ const cssText = `
+ body {
+ background-color: green;
+ }
+ `;
+ ReactDOM.render(
+
+
+ ,
+ container
+ );
+
+ requestAnimationFrame(() => {
+ ReactDOM.render(
+
+
+ ,
+ container
+ );
+
+ requestAnimationFrame(() => {
+ const existingTags = headElement.querySelectorAll(
+ `style[${HELMET_ATTRIBUTE}]`
+ );
+
+ expect(existingTags).to.not.equal(undefined);
+ expect(existingTags.length).to.equal(0);
+
+ done();
+ });
+ });
+ });
+
+ it("tags without 'cssText' are not accepted", done => {
+ const store = createHelmetStore();
+ ReactDOM.render(
+
+
+ ,
+ container
+ );
+
+ requestAnimationFrame(() => {
+ const existingTags = headElement.querySelectorAll(
+ `style[${HELMET_ATTRIBUTE}]`
+ );
+
+ expect(existingTags).to.not.equal(undefined);
+ expect(existingTags.length).to.equal(0);
+
+ done();
+ });
+ });
+
+ it("does not render tag when primary attribute is null", done => {
+ const store = createHelmetStore();
+ ReactDOM.render(
+
+
+ ,
+ container
+ );
+
+ requestAnimationFrame(() => {
+ const tagNodes = headElement.querySelectorAll(
+ `style[${HELMET_ATTRIBUTE}]`
+ );
+ const existingTags = Array.prototype.slice.call(tagNodes);
+ expect(existingTags).to.be.empty;
+
+ done();
+ });
+ });
+ });
+ });
+
+ describe("deferred tags", () => {
+ beforeEach(() => {
+ window.__spy__ = sinon.spy();
+ });
+
+ afterEach(() => {
+ delete window.__spy__;
+ });
+
+ it("executes synchronously when defer={true} and async otherwise", done => {
+ const store = createHelmetStore();
+ ReactDOM.render(
+
+
+
+
+
+ ,
+ container
+ );
+
+ expect(window.__spy__.callCount).to.equal(1);
+
+ requestAnimationFrame(() => {
+ expect(window.__spy__.callCount).to.equal(2);
+ expect(window.__spy__.args).to.deep.equal([[1], [2]]);
+ done();
+ });
+ });
+ });
+
+ describe("server", () => {
+ const stringifiedHtmlAttributes = `lang="ga" class="myClassName"`;
+ const stringifiedTitle = `Dangerous <script> include`;
+ const unEncodedStringifiedTitle = `This is text and & and '.`;
+ const stringifiedTitleWithItemprop = `Title with Itemprop`;
+ const stringifiedBaseTag = ``;
+
+ const stringifiedMetaTags = [
+ ``,
+ ``,
+ ``,
+ ``,
+ ``
+ ].join("");
+
+ const stringifiedLinkTags = [
+ ``,
+ ``
+ ].join("");
+
+ const stringifiedScriptTags = [
+ ``,
+ ``
+ ].join("");
+
+ const stringifiedNoscriptTags = [
+ ``,
+ ``
+ ].join("");
+
+ const stringifiedStyleTags = [
+ ``,
+ ``
+ ].join("");
+
+ it("provides initial values if no state is found", () => {
+ const store = createHelmetStore();
+ let head = store.renderStatic();
+ head = store.renderStatic();
+
+ expect(head.meta).to.exist;
+ expect(head.meta).to.respondTo("toString");
+
+ expect(head.meta.toString()).to.equal("");
+ });
+
+ it("encodes special characters in title", () => {
+ const store = createHelmetStore();
+ ReactDOM.render(
+
+
+ ,
+ container
+ );
+
+ const head = store.renderStatic();
+
+ expect(head.title).to.exist;
+ expect(head.title).to.respondTo("toString");
+
+ expect(head.title.toString()).to.equal(stringifiedTitle);
+ });
+
+ it("opts out of string encoding", () => {
+ const store = createHelmetStore();
+ ReactDOM.render(
+
+
+ ,
+ container
+ );
+
+ const head = store.renderStatic();
+ expect(head.title).to.exist;
+ expect(head.title).to.respondTo("toString");
+
+ expect(head.title.toString()).to.equal(unEncodedStringifiedTitle);
+ });
+
+ it("renders title as React component", () => {
+ const store = createHelmetStore();
+ ReactDOM.render(
+
+ include"} />
+ ,
+ container
+ );
+
+ const head = store.renderStatic();
+
+ expect(head.title).to.exist;
+ expect(head.title).to.respondTo("toComponent");
+
+ const titleComponent = head.title.toComponent();
+
+ expect(titleComponent)
+ .to.be.an("array")
+ .that.has.lengthOf(1);
+
+ titleComponent.forEach(title => {
+ expect(title)
+ .to.be.an("object")
+ .that.contains.property("type", "title");
+ });
+
+ const markup = ReactServer.renderToStaticMarkup(
+ {titleComponent}
+ );
+
+ expect(markup)
+ .to.be.a("string")
+ .that.equals(`${stringifiedTitle}
`);
+ });
+
+ it("renders title with itemprop name as React component", () => {
+ const store = createHelmetStore();
+ ReactDOM.render(
+
+
+ ,
+ container
+ );
+
+ const head = store.renderStatic();
+
+ expect(head.title).to.exist;
+ expect(head.title).to.respondTo("toComponent");
+
+ const titleComponent = head.title.toComponent();
+
+ expect(titleComponent)
+ .to.be.an("array")
+ .that.has.lengthOf(1);
+
+ titleComponent.forEach(title => {
+ expect(title)
+ .to.be.an("object")
+ .that.contains.property("type", "title");
+ });
+
+ const markup = ReactServer.renderToStaticMarkup(
+ {titleComponent}
+ );
+
+ expect(markup.toLowerCase())
+ .to.be.a("string")
+ .that.equals(
+ `${stringifiedTitleWithItemprop}
`.toLowerCase()
+ );
+ });
+
+ it("renders base tag as React component", () => {
+ const store = createHelmetStore();
+ ReactDOM.render(
+
+
+ ,
+ container
+ );
+
+ const head = store.renderStatic();
+
+ expect(head.base).to.exist;
+ expect(head.base).to.respondTo("toComponent");
+
+ const baseComponent = head.base.toComponent();
+
+ expect(baseComponent)
+ .to.be.an("array")
+ .that.has.lengthOf(1);
+
+ baseComponent.forEach(base => {
+ expect(base)
+ .to.be.an("object")
+ .that.contains.property("type", "base");
+ });
+
+ const markup = ReactServer.renderToStaticMarkup(
+ {baseComponent}
+ );
+
+ expect(markup)
+ .to.be.a("string")
+ .that.equals(`${stringifiedBaseTag}
`);
+ });
+
+ it("renders meta tags as React components", () => {
+ const store = createHelmetStore();
+ ReactDOM.render(
+
+ < `"
+ },
+ {
+ "http-equiv": "content-type",
+ content: "text/html"
+ },
+ {property: "og:type", content: "article"},
+ {itemprop: "name", content: "Test name itemprop"}
+ ]}
+ />
+ ,
+ container
+ );
+
+ const head = store.renderStatic();
+
+ expect(head.meta).to.exist;
+ expect(head.meta).to.respondTo("toComponent");
+
+ const metaComponent = head.meta.toComponent();
+
+ expect(metaComponent)
+ .to.be.an("array")
+ .that.has.lengthOf(5);
+
+ metaComponent.forEach(meta => {
+ expect(meta)
+ .to.be.an("object")
+ .that.contains.property("type", "meta");
+ });
+
+ const markup = ReactServer.renderToStaticMarkup(
+ {metaComponent}
+ );
+
+ expect(markup.toLowerCase())
+ .to.be.a("string")
+ .that.equals(`${stringifiedMetaTags}
`.toLowerCase());
+ });
+
+ it("renders link tags as React components", () => {
+ const store = createHelmetStore();
+ ReactDOM.render(
+
+
+ ,
+ container
+ );
+
+ const head = store.renderStatic();
+
+ expect(head.link).to.exist;
+ expect(head.link).to.respondTo("toComponent");
+
+ const linkComponent = head.link.toComponent();
+
+ expect(linkComponent)
+ .to.be.an("array")
+ .that.has.lengthOf(2);
+
+ linkComponent.forEach(link => {
+ expect(link)
+ .to.be.an("object")
+ .that.contains.property("type", "link");
+ });
+
+ const markup = ReactServer.renderToStaticMarkup(
+ {linkComponent}
+ );
+
+ expect(markup)
+ .to.be.a("string")
+ .that.equals(`${stringifiedLinkTags}
`);
+ });
+
+ it("renders script tags as React components", () => {
+ const store = createHelmetStore();
+ ReactDOM.render(
+
+
+ ,
+ container
+ );
+
+ const head = store.renderStatic();
+
+ expect(head.script).to.exist;
+ expect(head.script).to.respondTo("toComponent");
+
+ const scriptComponent = head.script.toComponent();
+
+ expect(scriptComponent)
+ .to.be.an("array")
+ .that.has.lengthOf(2);
+
+ scriptComponent.forEach(script => {
+ expect(script)
+ .to.be.an("object")
+ .that.contains.property("type", "script");
+ });
+
+ const markup = ReactServer.renderToStaticMarkup(
+ {scriptComponent}
+ );
+
+ expect(markup)
+ .to.be.a("string")
+ .that.equals(`${stringifiedScriptTags}
`);
+ });
+
+ it("renders noscript tags as React components", () => {
+ const store = createHelmetStore();
+ ReactDOM.render(
+
+ '
+ },
+ {
+ id: "bar",
+ innerHTML:
+ ''
+ }
+ ]}
+ />
+ ,
+ container
+ );
+
+ const head = store.renderStatic();
+
+ expect(head.noscript).to.exist;
+ expect(head.noscript).to.respondTo("toComponent");
+
+ const noscriptComponent = head.noscript.toComponent();
+
+ expect(noscriptComponent)
+ .to.be.an("array")
+ .that.has.lengthOf(2);
+
+ noscriptComponent.forEach(noscript => {
+ expect(noscript)
+ .to.be.an("object")
+ .that.contains.property("type", "noscript");
+ });
+
+ const markup = ReactServer.renderToStaticMarkup(
+ {noscriptComponent}
+ );
+
+ expect(markup)
+ .to.be.a("string")
+ .that.equals(`${stringifiedNoscriptTags}
`);
+ });
+
+ it("renders style tags as React components", () => {
+ const store = createHelmetStore();
+ ReactDOM.render(
+
+
+ ,
+ container
+ );
+
+ const head = store.renderStatic();
+
+ expect(head.style).to.exist;
+ expect(head.style).to.respondTo("toComponent");
+
+ const styleComponent = head.style.toComponent();
+
+ expect(styleComponent)
+ .to.be.an("array")
+ .that.has.lengthOf(2);
+
+ const markup = ReactServer.renderToStaticMarkup(
+ {styleComponent}
+ );
+
+ expect(markup)
+ .to.be.a("string")
+ .that.equals(`${stringifiedStyleTags}
`);
+ });
+
+ it("renders title tag as string", () => {
+ const store = createHelmetStore();
+ ReactDOM.render(
+
+ include"} />
+ ,
+ container
+ );
+
+ const head = store.renderStatic();
+
+ expect(head.title).to.exist;
+ expect(head.title).to.respondTo("toString");
+
+ expect(head.title.toString())
+ .to.be.a("string")
+ .that.equals(stringifiedTitle);
+ });
+
+ it("renders title with itemprop name as string", () => {
+ const store = createHelmetStore();
+ ReactDOM.render(
+
+
+ ,
+ container
+ );
+
+ const head = store.renderStatic();
+
+ expect(head.title).to.exist;
+ expect(head.title).to.respondTo("toString");
+
+ const titleString = head.title.toString();
+ expect(titleString)
+ .to.be.a("string")
+ .that.equals(stringifiedTitleWithItemprop);
+ });
+
+ it("renders base tags as string", () => {
+ const store = createHelmetStore();
+ ReactDOM.render(
+
+
+ ,
+ container
+ );
+
+ const head = store.renderStatic();
+
+ expect(head.base).to.exist;
+ expect(head.base).to.respondTo("toString");
+
+ expect(head.base.toString())
+ .to.be.a("string")
+ .that.equals(stringifiedBaseTag);
+ });
+
+ it("renders meta tags as string", () => {
+ const store = createHelmetStore();
+ ReactDOM.render(
+
+ < `"
+ },
+ {
+ "http-equiv": "content-type",
+ content: "text/html"
+ },
+ {property: "og:type", content: "article"},
+ {itemprop: "name", content: "Test name itemprop"}
+ ]}
+ />
+ ,
+ container
+ );
+
+ const head = store.renderStatic();
+
+ expect(head.meta).to.exist;
+ expect(head.meta).to.respondTo("toString");
+
+ expect(head.meta.toString())
+ .to.be.a("string")
+ .that.equals(stringifiedMetaTags);
+ });
+
+ it("renders link tags as string", () => {
+ const store = createHelmetStore();
+ ReactDOM.render(
+
+
+ ,
+ container
+ );
+
+ const head = store.renderStatic();
+
+ expect(head.link).to.exist;
+ expect(head.link).to.respondTo("toString");
+
+ expect(head.link.toString())
+ .to.be.a("string")
+ .that.equals(stringifiedLinkTags);
+ });
+
+ it("renders script tags as string", () => {
+ const store = createHelmetStore();
+ ReactDOM.render(
+
+
+ ,
+ container
+ );
+
+ const head = store.renderStatic();
+
+ expect(head.script).to.exist;
+ expect(head.script).to.respondTo("toString");
+
+ expect(head.script.toString())
+ .to.be.a("string")
+ .that.equals(stringifiedScriptTags);
+ });
+
+ it("renders style tags as string", () => {
+ const store = createHelmetStore();
+ ReactDOM.render(
+
+
+ ,
+ container
+ );
+
+ const head = store.renderStatic();
+
+ expect(head.style).to.exist;
+ expect(head.style).to.respondTo("toString");
+
+ expect(head.style.toString())
+ .to.be.a("string")
+ .that.equals(stringifiedStyleTags);
+ });
+
+ it("renders html attributes as component", () => {
+ const store = createHelmetStore();
+ ReactDOM.render(
+
+
+ ,
+ container
+ );
+
+ const {htmlAttributes} = store.renderStatic();
+ const attrs = htmlAttributes.toComponent();
+
+ expect(attrs).to.exist;
+
+ const markup = ReactServer.renderToStaticMarkup(
+
+ );
+
+ expect(markup)
+ .to.be.a("string")
+ .that.equals(``);
+ });
+
+ it("renders html attributes as string", () => {
+ const store = createHelmetStore();
+ ReactDOM.render(
+
+
+ ,
+ container
+ );
+
+ const head = store.renderStatic();
+
+ expect(head.htmlAttributes).to.exist;
+ expect(head.htmlAttributes).to.respondTo("toString");
+
+ expect(head.htmlAttributes.toString())
+ .to.be.a("string")
+ .that.equals(stringifiedHtmlAttributes);
+ });
+
+ it("does not encode all characters with HTML character entity equivalents", () => {
+ const chineseTitle = "膣膗 鍆錌雔";
+ const stringifiedChineseTitle = `
${chineseTitle}`;
+
+ const store = createHelmetStore();
+ ReactDOM.render(
+
+
+
+
+ ,
+ container
+ );
+
+ const head = store.renderStatic();
+
+ expect(head.title).to.exist;
+ expect(head.title).to.respondTo("toString");
+
+ expect(head.title.toString())
+ .to.be.a("string")
+ .that.equals(stringifiedChineseTitle);
+ });
+
+ it("rewind() provides a fallback object for empty Helmet state", () => {
+ const store = createHelmetStore();
+ ReactDOM.render(
+
+
+ ,
+ container
+ );
+
+ const head = store.renderStatic();
+
+ expect(head.htmlAttributes).to.exist;
+ expect(head.htmlAttributes).to.respondTo("toString");
+ expect(head.htmlAttributes.toString()).to.equal("");
+ expect(head.htmlAttributes).to.respondTo("toComponent");
+ expect(head.htmlAttributes.toComponent()).to.be.an("object").that.is
+ .empty;
+
+ expect(head.title).to.exist;
+ expect(head.title).to.respondTo("toString");
+ expect(head.title.toString()).to.equal(
+ ``
+ );
+ expect(head.title).to.respondTo("toComponent");
+
+ const markup = ReactServer.renderToStaticMarkup(
+ {head.title.toComponent()}
+ );
+
+ expect(markup)
+ .to.be.a("string")
+ .that.equals(
+ ``
+ );
+
+ expect(head.base).to.exist;
+ expect(head.base).to.respondTo("toString");
+ expect(head.base.toString()).to.equal("");
+ expect(head.base).to.respondTo("toComponent");
+ expect(head.base.toComponent()).to.be.an("array").that.is.empty;
+
+ expect(head.meta).to.exist;
+ expect(head.meta).to.respondTo("toString");
+ expect(head.meta.toString()).to.equal("");
+ expect(head.meta).to.respondTo("toComponent");
+ expect(head.meta.toComponent()).to.be.an("array").that.is.empty;
+
+ expect(head.link).to.exist;
+ expect(head.link).to.respondTo("toString");
+ expect(head.link.toString()).to.equal("");
+ expect(head.link).to.respondTo("toComponent");
+ expect(head.link.toComponent()).to.be.an("array").that.is.empty;
+
+ expect(head.script).to.exist;
+ expect(head.script).to.respondTo("toString");
+ expect(head.script.toString()).to.equal("");
+ expect(head.script).to.respondTo("toComponent");
+ expect(head.script.toComponent()).to.be.an("array").that.is.empty;
+
+ expect(head.noscript).to.exist;
+ expect(head.noscript).to.respondTo("toString");
+ expect(head.noscript.toString()).to.equal("");
+ expect(head.noscript).to.respondTo("toComponent");
+ expect(head.noscript.toComponent()).to.be.an("array").that.is.empty;
+
+ expect(head.style).to.exist;
+ expect(head.style).to.respondTo("toString");
+ expect(head.style.toString()).to.equal("");
+ expect(head.style).to.respondTo("toComponent");
+ expect(head.style.toComponent()).to.be.an("array").that.is.empty;
+ });
+
+ it("does not render undefined attribute values", () => {
+ const store = createHelmetStore();
+ ReactDOM.render(
+
+
+ ,
+ container
+ );
+
+ const {script} = store.renderStatic();
+ const stringifiedScriptTag = script.toString();
+
+ expect(stringifiedScriptTag)
+ .to.be.a("string")
+ .that.equals(
+ ``
+ );
+ });
+ });
+
+ describe("misc", () => {
+ it("lets you read current state in peek() whether or not a DOM is present", done => {
+ const store = createHelmetStore();
+ ReactDOM.render(
+
+
+ ,
+ container
+ );
+
+ requestAnimationFrame(() => {
+ expect(store.peek().title).to.be.equal("Fancy title");
+ done();
+ });
+ });
+
+ it("encodes special characters", done => {
+ const store = createHelmetStore();
+ ReactDOM.render(
+
+
+ ,
+ container
+ );
+
+ requestAnimationFrame(() => {
+ const existingTags = headElement.querySelectorAll(
+ `meta[${HELMET_ATTRIBUTE}]`
+ );
+ const existingTag = existingTags[0];
+
+ expect(existingTags).to.not.equal(undefined);
+
+ expect(existingTags.length).to.be.equal(1);
+
+ expect(existingTags)
+ .to.have.deep.nested.property("[0]")
+ .that.is.an.instanceof(Element);
+ expect(existingTag).to.have.property("getAttribute");
+ expect(existingTag.getAttribute("name")).to.equal(
+ "description"
+ );
+ expect(existingTag.getAttribute("content")).to.equal(
+ 'This is "quoted" text and & and \'.'
+ );
+ expect(existingTag.outerHTML).to.equal(
+ ``
+ );
+
+ done();
+ });
+ });
+
+ it("does not change the DOM if it recevies identical props", done => {
+ const store = createHelmetStore();
+ const spy = sinon.spy();
+ ReactDOM.render(
+
+
+ ,
+ container
+ );
+
+ requestAnimationFrame(() => {
+ // Re-rendering will pass new props to an already mounted Helmet
+ ReactDOM.render(
+
+
+ ,
+ container
+ );
+
+ requestAnimationFrame(() => {
+ expect(spy.callCount).to.equal(1);
+
+ done();
+ });
+ });
+ });
+
+ it("does not write the DOM if the client and server are identical", done => {
+ const store = createHelmetStore();
+ headElement.innerHTML = ``;
+
+ const spy = sinon.spy();
+ ReactDOM.render(
+
+
+ ,
+ container
+ );
+
+ requestAnimationFrame(() => {
+ expect(spy.called).to.equal(true);
+
+ const [, addedTags, removedTags] = spy.getCall(0).args;
+
+ expect(addedTags).to.be.empty;
+ expect(removedTags).to.be.empty;
+
+ done();
+ });
+ });
+
+ it("only adds new tags and preserves tags when rendering additional Helmet instances", done => {
+ const store = createHelmetStore();
+ const spy = sinon.spy();
+ let addedTags;
+ let removedTags;
+ ReactDOM.render(
+
+
+ ,
+ container
+ );
+
+ requestAnimationFrame(() => {
+ expect(spy.called).to.equal(true);
+ addedTags = spy.getCall(0).args[1];
+ removedTags = spy.getCall(0).args[2];
+
+ expect(addedTags).to.have.property("metaTags");
+ expect(addedTags.metaTags).to.have.deep.nested.property("[0]");
+ expect(addedTags.metaTags[0].outerHTML).to.equal(
+ ``
+ );
+ expect(addedTags).to.have.property("linkTags");
+ expect(addedTags.linkTags).to.have.deep.nested.property("[0]");
+ expect(addedTags.linkTags[0].outerHTML).to.equal(
+ ``
+ );
+ expect(removedTags).to.be.empty;
+
+ // Re-rendering will pass new props to an already mounted Helmet
+ ReactDOM.render(
+
+
+ ,
+ container
+ );
+
+ requestAnimationFrame(() => {
+ expect(spy.callCount).to.equal(2);
+ addedTags = spy.getCall(1).args[1];
+ removedTags = spy.getCall(1).args[2];
+
+ expect(addedTags).to.have.property("metaTags");
+ expect(addedTags.metaTags).to.have.deep.nested.property(
+ "[0]"
+ );
+ expect(addedTags.metaTags[0].outerHTML).to.equal(
+ ``
+ );
+ expect(addedTags).to.have.property("linkTags");
+ expect(addedTags.linkTags).to.have.deep.nested.property(
+ "[0]"
+ );
+ expect(addedTags.linkTags[0].outerHTML).to.equal(
+ ``
+ );
+ expect(removedTags).to.have.property("metaTags");
+ expect(removedTags.metaTags).to.have.deep.nested.property(
+ "[0]"
+ );
+ expect(removedTags.metaTags[0].outerHTML).to.equal(
+ ``
+ );
+ expect(removedTags).to.not.have.property("linkTags");
+
+ done();
+ });
+ });
+ });
+
+ it("does not accept nested Helmets", done => {
+ const store = createHelmetStore();
+ const warn = sinon.stub(console, "warn");
+
+ ReactDOM.render(
+
+
+
+
+ ,
+ container
+ );
+
+ requestAnimationFrame(() => {
+ expect(document.title).to.equal("Test Title");
+ expect(warn.called).to.be.true;
+
+ const [warning] = warn.getCall(0).args;
+ expect(warning).to.equal(
+ "You may be attempting to nest components within each other, which is not allowed. Refer to our API for more information."
+ );
+
+ warn.restore();
+ done();
+ });
+ });
+
+ it("recognizes valid tags regardless of attribute ordering", done => {
+ const store = createHelmetStore();
+ ReactDOM.render(
+
+
+ ,
+ container
+ );
+
+ requestAnimationFrame(() => {
+ const existingTags = headElement.querySelectorAll(
+ `meta[${HELMET_ATTRIBUTE}]`
+ );
+ const existingTag = existingTags[0];
+
+ expect(existingTags).to.not.equal(undefined);
+
+ expect(existingTags.length).to.be.equal(1);
+
+ expect(existingTags)
+ .to.have.deep.nested.property("[0]")
+ .that.is.an.instanceof(Element);
+ expect(existingTag).to.have.property("getAttribute");
+ expect(existingTag.getAttribute("name")).to.equal(
+ "description"
+ );
+ expect(existingTag.getAttribute("content")).to.equal(
+ "Test Description"
+ );
+ expect(existingTag.outerHTML).to.equal(
+ ``
+ );
+
+ done();
+ });
+ });
+
+ it("requestAnimationFrame works as expected", done => {
+ requestAnimationFrame(cb => {
+ expect(cb).to.exist;
+ expect(cb).to.be.a("number");
+
+ done();
+ });
+ });
+ });
+});
diff --git a/test/HelmetTest.js b/test/HelmetTest.js
index fa737107..79ba5690 100644
--- a/test/HelmetTest.js
+++ b/test/HelmetTest.js
@@ -840,7 +840,6 @@ describe("Helmet", () => {
expect(spy.getCall(0).args[0]).to.deep.contain({
title: "Deeper Title"
});
-
done();
});
});