diff --git a/.babelrc b/.babelrc
deleted file mode 100644
index a43c1889..00000000
--- a/.babelrc
+++ /dev/null
@@ -1,11 +0,0 @@
-{
- "presets": [
- ["kyt-react", {"modules": true}],
- ],
- "plugins": [
- "transform-class-properties",
- "transform-es2015-modules-commonjs",
- "transform-object-rest-spread",
- "transform-regenerator",
- ]
-}
diff --git a/.circleci/config.yml b/.circleci/config.yml
new file mode 100644
index 00000000..75678ea8
--- /dev/null
+++ b/.circleci/config.yml
@@ -0,0 +1,31 @@
+version: 2
+jobs:
+ build:
+ docker:
+ - image: cimg/node:18.17.1
+
+ steps:
+ - checkout
+
+ - restore_cache:
+ keys:
+ - deps-{{ checksum "package.json" }}
+
+ - run: yarn
+
+ - save_cache:
+ paths:
+ - node_modules
+ key: deps-{{ checksum "package.json" }}
+
+ - run:
+ name: Tests
+ command: yarn test
+
+ - run:
+ name: Type checks
+ command: yarn tsc
+
+ - run:
+ name: ESLint
+ command: yarn lint
diff --git a/.eslintignore b/.eslintignore
new file mode 100644
index 00000000..a1d158e5
--- /dev/null
+++ b/.eslintignore
@@ -0,0 +1,3 @@
+/lib
+/node_modules
+package.json
diff --git a/.eslintrc.js b/.eslintrc.js
new file mode 100644
index 00000000..97a8b2c2
--- /dev/null
+++ b/.eslintrc.js
@@ -0,0 +1,36 @@
+/**
+ * @type {import('@types/eslint').Linter.BaseConfig}
+ */
+module.exports = {
+ extends: [
+ '@remix-run/eslint-config',
+ '@remix-run/eslint-config/node',
+ '@remix-run/eslint-config/jest-testing-library',
+ 'prettier',
+ ],
+ plugins: ['prettier'],
+ rules: {
+ 'import/order': [
+ 'error',
+ {
+ 'newlines-between': 'always',
+ },
+ ],
+ 'prettier/prettier': [
+ 'error',
+ {
+ singleQuote: true,
+ trailingComma: 'es5',
+ useTabs: false,
+ tabWidth: 2,
+ printWidth: 100,
+ },
+ ],
+ 'testing-library/render-result-naming-convention': 'off',
+ },
+ settings: {
+ jest: {
+ version: 27,
+ },
+ },
+};
diff --git a/.eslintrc.json b/.eslintrc.json
deleted file mode 100644
index 03c1371e..00000000
--- a/.eslintrc.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "extends": [
- "eslint-config-kyt"
- ]
-}
diff --git a/.gitignore b/.gitignore
index f39528b3..3063f07d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,2 @@
-es
lib
node_modules
diff --git a/.husky/commit-msg b/.husky/commit-msg
new file mode 100755
index 00000000..2785bc1d
--- /dev/null
+++ b/.husky/commit-msg
@@ -0,0 +1 @@
+yarn commitlint --edit $1
diff --git a/.npmignore b/.npmignore
index 876e5a28..fe00227a 100644
--- a/.npmignore
+++ b/.npmignore
@@ -1,6 +1,6 @@
-.babelrc
-.eslintrc.json
+.eslintrc.js
jest.setup.js
+*.config.js
node_modules
src
__tests__
diff --git a/.nvmrc b/.nvmrc
new file mode 100644
index 00000000..4a1f488b
--- /dev/null
+++ b/.nvmrc
@@ -0,0 +1 @@
+18.17.1
diff --git a/.prettierignore b/.prettierignore
new file mode 100644
index 00000000..09ddce2b
--- /dev/null
+++ b/.prettierignore
@@ -0,0 +1,6 @@
+/lib
+/es
+/coverage
+/node_modules
+package.json
+package-lock.json
diff --git a/.prettierrc b/.prettierrc
new file mode 100644
index 00000000..54983dda
--- /dev/null
+++ b/.prettierrc
@@ -0,0 +1,8 @@
+{
+ "singleQuote": true,
+ "trailingComma": "es5",
+ "arrowParens": "avoid",
+ "printWidth": 100,
+ "useTabs": false,
+ "tabWidth": 2
+}
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 00000000..17201341
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,11 @@
+{
+ "eslint.validate": [
+ "javascript",
+ "javascriptreact",
+ "typescript",
+ "typescriptreact"
+ ],
+ "editor.codeActionsOnSave": {
+ "source.fixAll.eslint": "explicit"
+ }
+}
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 00000000..aa7a7d51
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright 2018 The New York Times Company
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/README.md b/README.md
index 597559e7..cac92ba9 100644
--- a/README.md
+++ b/README.md
@@ -1,18 +1,24 @@
# react-helmet-async
+[](https://circleci.com/gh/staylor/react-helmet-async)
+
+[Announcement post on Times Open blog](https://open.nytimes.com/the-future-of-meta-tag-management-for-modern-react-development-ec26a7dc9183)
+
This package is a fork of [React Helmet](https://github.com/nfl/react-helmet).
-`` usage is synonymous, but SSR is radically different.
+`` usage is synonymous, but server and client now requires `` to encapsulate state per request.
-`react-helmet` relies on `react-side-effect`, which is not thread-safe. If you are doing anything asynchronous on the server,
-you need Helmet to to encapsulate data on a per-request basis, this package does just that.
+`react-helmet` relies on `react-side-effect`, which is not thread-safe. If you are doing anything asynchronous on the server, you need Helmet to encapsulate data on a per-request basis, this package does just that.
## Usage
-The main way that this package differs from `react-helmet` is that it requires using a Provider to encapsulate Helmet state for your React
-tree. If you use libraries like Redux or Apollo, you are already familiar with this paradigm:
+**New is 1.0.0:** No more default export! `import { Helmet } from 'react-helmet-async'`
+
+The main way that this package differs from `react-helmet` is that it requires using a Provider to encapsulate Helmet state for your React tree. If you use libraries like Redux or Apollo, you are already familiar with this paradigm:
```javascript
-import Helmet, { HelmetProvider } from 'react-helmet-async';
+import React from 'react';
+import ReactDOM from 'react-dom';
+import { Helmet, HelmetProvider } from 'react-helmet-async';
const app = (
@@ -25,6 +31,11 @@ const app = (
);
+
+ReactDOM.hydrate(
+ app,
+ document.getElementById(‘app’)
+);
```
On the server, we will no longer use static methods to extract state. `react-side-effect`
@@ -32,7 +43,9 @@ exposed a `.rewind()` method, which Helmet used when calling `Helmet.renderStati
to pass a `context` prop to `HelmetProvider`, which will hold our state specific to each request.
```javascript
-import { HelmetProvider } from 'react-helmet-async';
+import React from 'react';
+import { renderToString } from 'react-dom/server';
+import { Helmet, HelmetProvider } from 'react-helmet-async';
const helmetContext = {};
@@ -48,9 +61,11 @@ const app = (
);
+const html = renderToString(app);
+
const { helmet } = helmetContext;
-// helmet.title.toString() etc...
+// helmet.title.toString() etc…
```
## Streams
@@ -62,7 +77,7 @@ This is possible if your data hydration method already parses your React tree. E
import through from 'through';
import { renderToNodeStream } from 'react-dom/server';
import { getDataFromTree } from 'react-apollo';
-import Helmet, { HelmetProvider } from 'react-helmet-async';
+import { Helmet, HelmetProvider } from 'react-helmet-async';
import template from 'server/template';
const helmetContext = {};
@@ -102,6 +117,88 @@ renderToNodeStream(app)
.pipe(res);
```
+## Usage in Jest
+While testing in using jest, if there is a need to emulate SSR, the following string is required to have the test behave the way they are expected to.
+
+```javascript
+import { HelmetProvider } from 'react-helmet-async';
+
+HelmetProvider.canUseDOM = false;
+```
+
+## Prioritizing tags for SEO
+
+It is understood that in some cases for SEO, certain tags should appear earlier in the HEAD. Using the `prioritizeSeoTags` flag on any `` component allows the server render of react-helmet-async to expose a method for prioritizing relevant SEO tags.
+
+In the component:
+```javascript
+
+ A fancy webpage
+
+
+
+
+
+```
+
+In your server template:
+
+```javascript
+
+
+ ${helmet.title.toString()}
+ ${helmet.priority.toString()}
+ ${helmet.meta.toString()}
+ ${helmet.link.toString()}
+ ${helmet.script.toString()}
+
+ ...
+
+```
+
+Will result in:
+
+```html
+
+
+ A fancy webpage
+
+
+
+
+
+ ...
+
+```
+
+A list of prioritized tags and attributes can be found in [constants.ts](./src/constants.ts).
+
+## Usage without Context
+You can optionally use `` outside a context by manually creating a stateful `HelmetData` instance, and passing that stateful object to each `` instance:
+
+
+```js
+import React from 'react';
+import { renderToString } from 'react-dom/server';
+import { Helmet, HelmetProvider, HelmetData } from 'react-helmet-async';
+
+const helmetData = new HelmetData({});
+
+const app = (
+
+
+ Hello World
+
+
+ Hello World
+
+);
+
+const html = renderToString(app);
+
+const { helmet } = helmetData.context;
+```
+
## License
-Licensed under the MIT License, Copyright © 2018 Scott Taylor
+Licensed under the Apache 2.0 License, Copyright © 2018 Scott Taylor
diff --git a/__tests__/__snapshots__/fragment.test.js.snap b/__tests__/__snapshots__/fragment.test.js.snap
new file mode 100644
index 00000000..c6f21889
--- /dev/null
+++ b/__tests__/__snapshots__/fragment.test.js.snap
@@ -0,0 +1,5 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`fragments parses Fragments 1`] = `"Hello"`;
+
+exports[`fragments parses nested Fragments 1`] = `"Baz"`;
diff --git a/__tests__/__snapshots__/misc.test.tsx.snap b/__tests__/__snapshots__/misc.test.tsx.snap
new file mode 100644
index 00000000..a88069ad
--- /dev/null
+++ b/__tests__/__snapshots__/misc.test.tsx.snap
@@ -0,0 +1,29 @@
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
+
+exports[`misc > API > encodes special characters 1`] = `" "`;
+
+exports[`misc > API > only adds new tags and preserves tags when rendering additional Helmet instances 1`] = `" "`;
+
+exports[`misc > API > only adds new tags and preserves tags when rendering additional Helmet instances 2`] = `" "`;
+
+exports[`misc > API > only adds new tags and preserves tags when rendering additional Helmet instances 3`] = `" "`;
+
+exports[`misc > API > only adds new tags and preserves tags when rendering additional Helmet instances 4`] = `" "`;
+
+exports[`misc > API > only adds new tags and preserves tags when rendering additional Helmet instances 5`] = `" "`;
+
+exports[`misc > API > recognizes valid tags regardless of attribute ordering 1`] = `" "`;
+
+exports[`misc > Declarative API > encodes special characters 1`] = `" "`;
+
+exports[`misc > Declarative API > only adds new tags and preserves tags when rendering additional Helmet instances 1`] = `" "`;
+
+exports[`misc > Declarative API > only adds new tags and preserves tags when rendering additional Helmet instances 2`] = `" "`;
+
+exports[`misc > Declarative API > only adds new tags and preserves tags when rendering additional Helmet instances 3`] = `" "`;
+
+exports[`misc > Declarative API > only adds new tags and preserves tags when rendering additional Helmet instances 4`] = `" "`;
+
+exports[`misc > Declarative API > only adds new tags and preserves tags when rendering additional Helmet instances 5`] = `" "`;
+
+exports[`misc > Declarative API > recognizes valid tags regardless of attribute ordering 1`] = `" "`;
diff --git a/__tests__/api/__snapshots__/base.test.js.snap b/__tests__/api/__snapshots__/base.test.js.snap
deleted file mode 100644
index 76328f55..00000000
--- a/__tests__/api/__snapshots__/base.test.js.snap
+++ /dev/null
@@ -1,5 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`base tag API sets base tag based on deepest nested component 1`] = `" "`;
-
-exports[`base tag Declarative API sets base tag based on deepest nested component 1`] = `" "`;
diff --git a/__tests__/api/__snapshots__/base.test.tsx.snap b/__tests__/api/__snapshots__/base.test.tsx.snap
new file mode 100644
index 00000000..d9681db6
--- /dev/null
+++ b/__tests__/api/__snapshots__/base.test.tsx.snap
@@ -0,0 +1,5 @@
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
+
+exports[`base tag > API > sets base tag based on deepest nested component 1`] = `" "`;
+
+exports[`base tag > Declarative API > sets base tag based on deepest nested component 1`] = `" "`;
diff --git a/__tests__/api/__snapshots__/client.test.js.snap b/__tests__/api/__snapshots__/client.test.js.snap
deleted file mode 100644
index e5e6c3e8..00000000
--- a/__tests__/api/__snapshots__/client.test.js.snap
+++ /dev/null
@@ -1,17 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`onChangeClientState API when handling client state change, calls the function with new state, addedTags and removedTags 1`] = `" "`;
-
-exports[`onChangeClientState API when handling client state change, calls the function with new state, addedTags and removedTags 2`] = `" "`;
-
-exports[`onChangeClientState API when handling client state change, calls the function with new state, addedTags and removedTags 3`] = `" "`;
-
-exports[`onChangeClientState API when handling client state change, calls the function with new state, addedTags and removedTags 4`] = `""`;
-
-exports[`onChangeClientState Declarative API when handling client state change, calls the function with new state, addedTags and removedTags 1`] = `" "`;
-
-exports[`onChangeClientState Declarative API when handling client state change, calls the function with new state, addedTags and removedTags 2`] = `" "`;
-
-exports[`onChangeClientState Declarative API when handling client state change, calls the function with new state, addedTags and removedTags 3`] = `" "`;
-
-exports[`onChangeClientState Declarative API when handling client state change, calls the function with new state, addedTags and removedTags 4`] = `""`;
diff --git a/__tests__/api/__snapshots__/client.test.tsx.snap b/__tests__/api/__snapshots__/client.test.tsx.snap
new file mode 100644
index 00000000..2642229d
--- /dev/null
+++ b/__tests__/api/__snapshots__/client.test.tsx.snap
@@ -0,0 +1,17 @@
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
+
+exports[`onChangeClientState > API > when handling client state change, calls the function with new state, addedTags and removedTags 1`] = `" "`;
+
+exports[`onChangeClientState > API > when handling client state change, calls the function with new state, addedTags and removedTags 2`] = `" "`;
+
+exports[`onChangeClientState > API > when handling client state change, calls the function with new state, addedTags and removedTags 3`] = `" "`;
+
+exports[`onChangeClientState > API > when handling client state change, calls the function with new state, addedTags and removedTags 4`] = `""`;
+
+exports[`onChangeClientState > Declarative API > when handling client state change, calls the function with new state, addedTags and removedTags 1`] = `" "`;
+
+exports[`onChangeClientState > Declarative API > when handling client state change, calls the function with new state, addedTags and removedTags 2`] = `" "`;
+
+exports[`onChangeClientState > Declarative API > when handling client state change, calls the function with new state, addedTags and removedTags 3`] = `" "`;
+
+exports[`onChangeClientState > Declarative API > when handling client state change, calls the function with new state, addedTags and removedTags 4`] = `""`;
diff --git a/__tests__/api/__snapshots__/link.test.js.snap b/__tests__/api/__snapshots__/link.test.js.snap
deleted file mode 100644
index 84926dff..00000000
--- a/__tests__/api/__snapshots__/link.test.js.snap
+++ /dev/null
@@ -1,49 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`link tags API allows duplicate link tags if specified in the same component 1`] = `" "`;
-
-exports[`link tags API allows duplicate link tags if specified in the same component 2`] = `" "`;
-
-exports[`link tags API does not render tag when primary attribute is null 1`] = `" "`;
-
-exports[`link tags API overrides duplicate link tags with a single link tag in a nested component 1`] = `" "`;
-
-exports[`link tags API overrides single link tag with duplicate link tags in a nested component 1`] = `" "`;
-
-exports[`link tags API overrides single link tag with duplicate link tags in a nested component 2`] = `" "`;
-
-exports[`link tags API sets link tags based on deepest nested component 1`] = `" "`;
-
-exports[`link tags API sets link tags based on deepest nested component 2`] = `" "`;
-
-exports[`link tags API sets link tags based on deepest nested component 3`] = `" "`;
-
-exports[`link tags API tags 'rel' and 'href' properly use 'rel' as the primary identification for this tag, regardless of ordering 1`] = `" "`;
-
-exports[`link tags API tags with rel='stylesheet' uses the href as the primary identification of the tag, regardless of ordering 1`] = `" "`;
-
-exports[`link tags API tags with rel='stylesheet' uses the href as the primary identification of the tag, regardless of ordering 2`] = `" "`;
-
-exports[`link tags Declarative API allows duplicate link tags if specified in the same component 1`] = `" "`;
-
-exports[`link tags Declarative API allows duplicate link tags if specified in the same component 2`] = `" "`;
-
-exports[`link tags Declarative API does not render tag when primary attribute is null 1`] = `" "`;
-
-exports[`link tags Declarative API overrides duplicate link tags with a single link tag in a nested component 1`] = `" "`;
-
-exports[`link tags Declarative API overrides single link tag with duplicate link tags in a nested component 1`] = `" "`;
-
-exports[`link tags Declarative API overrides single link tag with duplicate link tags in a nested component 2`] = `" "`;
-
-exports[`link tags Declarative API sets link tags based on deepest nested component 1`] = `" "`;
-
-exports[`link tags Declarative API sets link tags based on deepest nested component 2`] = `" "`;
-
-exports[`link tags Declarative API sets link tags based on deepest nested component 3`] = `" "`;
-
-exports[`link tags Declarative API tags 'rel' and 'href' properly use 'rel' as the primary identification for this tag, regardless of ordering 1`] = `" "`;
-
-exports[`link tags Declarative API tags with rel='stylesheet' uses the href as the primary identification of the tag, regardless of ordering 1`] = `" "`;
-
-exports[`link tags Declarative API tags with rel='stylesheet' uses the href as the primary identification of the tag, regardless of ordering 2`] = `" "`;
diff --git a/__tests__/api/__snapshots__/link.test.tsx.snap b/__tests__/api/__snapshots__/link.test.tsx.snap
new file mode 100644
index 00000000..370b1aa9
--- /dev/null
+++ b/__tests__/api/__snapshots__/link.test.tsx.snap
@@ -0,0 +1,49 @@
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
+
+exports[`link tags > API > allows duplicate link tags if specified in the same component 1`] = `" "`;
+
+exports[`link tags > API > allows duplicate link tags if specified in the same component 2`] = `" "`;
+
+exports[`link tags > API > does not render tag when primary attribute is null 1`] = `" "`;
+
+exports[`link tags > API > overrides duplicate link tags with a single link tag in a nested component 1`] = `" "`;
+
+exports[`link tags > API > overrides single link tag with duplicate link tags in a nested component 1`] = `" "`;
+
+exports[`link tags > API > overrides single link tag with duplicate link tags in a nested component 2`] = `" "`;
+
+exports[`link tags > API > sets link tags based on deepest nested component 1`] = `" "`;
+
+exports[`link tags > API > sets link tags based on deepest nested component 2`] = `" "`;
+
+exports[`link tags > API > sets link tags based on deepest nested component 3`] = `" "`;
+
+exports[`link tags > API > tags 'rel' and 'href' properly use 'rel' as the primary identification for this tag, regardless of ordering 1`] = `" "`;
+
+exports[`link tags > API > tags with rel='stylesheet' uses the href as the primary identification of the tag, regardless of ordering 1`] = `" "`;
+
+exports[`link tags > API > tags with rel='stylesheet' uses the href as the primary identification of the tag, regardless of ordering 2`] = `" "`;
+
+exports[`link tags > Declarative API > allows duplicate link tags if specified in the same component 1`] = `" "`;
+
+exports[`link tags > Declarative API > allows duplicate link tags if specified in the same component 2`] = `" "`;
+
+exports[`link tags > Declarative API > does not render tag when primary attribute is null 1`] = `" "`;
+
+exports[`link tags > Declarative API > overrides duplicate link tags with a single link tag in a nested component 1`] = `" "`;
+
+exports[`link tags > Declarative API > overrides single link tag with duplicate link tags in a nested component 1`] = `" "`;
+
+exports[`link tags > Declarative API > overrides single link tag with duplicate link tags in a nested component 2`] = `" "`;
+
+exports[`link tags > Declarative API > sets link tags based on deepest nested component 1`] = `" "`;
+
+exports[`link tags > Declarative API > sets link tags based on deepest nested component 2`] = `" "`;
+
+exports[`link tags > Declarative API > sets link tags based on deepest nested component 3`] = `" "`;
+
+exports[`link tags > Declarative API > tags 'rel' and 'href' properly use 'rel' as the primary identification for this tag, regardless of ordering 1`] = `" "`;
+
+exports[`link tags > Declarative API > tags with rel='stylesheet' uses the href as the primary identification of the tag, regardless of ordering 1`] = `" "`;
+
+exports[`link tags > Declarative API > tags with rel='stylesheet' uses the href as the primary identification of the tag, regardless of ordering 2`] = `" "`;
diff --git a/__tests__/api/__snapshots__/meta.test.js.snap b/__tests__/api/__snapshots__/meta.test.js.snap
deleted file mode 100644
index 93dba226..00000000
--- a/__tests__/api/__snapshots__/meta.test.js.snap
+++ /dev/null
@@ -1,35 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`meta tags API allows duplicate meta tags if specified in the same component 1`] = `" "`;
-
-exports[`meta tags API allows duplicate meta tags if specified in the same component 2`] = `" "`;
-
-exports[`meta tags API fails gracefully when meta is wrong shape 1`] = `"Helmet: meta should be of type \\"Array\\". Instead found type \\"object\\""`;
-
-exports[`meta tags API overrides duplicate meta tags with single meta tag in a nested component 1`] = `" "`;
-
-exports[`meta tags API overrides single meta tag with duplicate meta tags in a nested component 1`] = `" "`;
-
-exports[`meta tags API overrides single meta tag with duplicate meta tags in a nested component 2`] = `" "`;
-
-exports[`meta tags API sets meta tags based on deepest nested component 1`] = `" "`;
-
-exports[`meta tags API sets meta tags based on deepest nested component 2`] = `" "`;
-
-exports[`meta tags API sets meta tags based on deepest nested component 3`] = `" "`;
-
-exports[`meta tags Declarative API allows duplicate meta tags if specified in the same component 1`] = `" "`;
-
-exports[`meta tags Declarative API allows duplicate meta tags if specified in the same component 2`] = `" "`;
-
-exports[`meta tags Declarative API overrides duplicate meta tags with single meta tag in a nested component 1`] = `" "`;
-
-exports[`meta tags Declarative API overrides single meta tag with duplicate meta tags in a nested component 1`] = `" "`;
-
-exports[`meta tags Declarative API overrides single meta tag with duplicate meta tags in a nested component 2`] = `" "`;
-
-exports[`meta tags Declarative API sets meta tags based on deepest nested component 1`] = `" "`;
-
-exports[`meta tags Declarative API sets meta tags based on deepest nested component 2`] = `" "`;
-
-exports[`meta tags Declarative API sets meta tags based on deepest nested component 3`] = `" "`;
diff --git a/__tests__/api/__snapshots__/meta.test.tsx.snap b/__tests__/api/__snapshots__/meta.test.tsx.snap
new file mode 100644
index 00000000..83e3f683
--- /dev/null
+++ b/__tests__/api/__snapshots__/meta.test.tsx.snap
@@ -0,0 +1,35 @@
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
+
+exports[`meta tags > API > allows duplicate meta tags if specified in the same component 1`] = `" "`;
+
+exports[`meta tags > API > allows duplicate meta tags if specified in the same component 2`] = `" "`;
+
+exports[`meta tags > API > fails gracefully when meta is wrong shape 1`] = `"Helmet: meta should be of type \\"Array\\". Instead found type \\"object\\""`;
+
+exports[`meta tags > API > overrides duplicate meta tags with single meta tag in a nested component 1`] = `" "`;
+
+exports[`meta tags > API > overrides single meta tag with duplicate meta tags in a nested component 1`] = `" "`;
+
+exports[`meta tags > API > overrides single meta tag with duplicate meta tags in a nested component 2`] = `" "`;
+
+exports[`meta tags > API > sets meta tags based on deepest nested component 1`] = `" "`;
+
+exports[`meta tags > API > sets meta tags based on deepest nested component 2`] = `" "`;
+
+exports[`meta tags > API > sets meta tags based on deepest nested component 3`] = `" "`;
+
+exports[`meta tags > Declarative API > allows duplicate meta tags if specified in the same component 1`] = `" "`;
+
+exports[`meta tags > Declarative API > allows duplicate meta tags if specified in the same component 2`] = `" "`;
+
+exports[`meta tags > Declarative API > overrides duplicate meta tags with single meta tag in a nested component 1`] = `" "`;
+
+exports[`meta tags > Declarative API > overrides single meta tag with duplicate meta tags in a nested component 1`] = `" "`;
+
+exports[`meta tags > Declarative API > overrides single meta tag with duplicate meta tags in a nested component 2`] = `" "`;
+
+exports[`meta tags > Declarative API > sets meta tags based on deepest nested component 1`] = `" "`;
+
+exports[`meta tags > Declarative API > sets meta tags based on deepest nested component 2`] = `" "`;
+
+exports[`meta tags > Declarative API > sets meta tags based on deepest nested component 3`] = `" "`;
diff --git a/__tests__/api/__snapshots__/noscript.test.js.snap b/__tests__/api/__snapshots__/noscript.test.js.snap
deleted file mode 100644
index 9e1c59ae..00000000
--- a/__tests__/api/__snapshots__/noscript.test.js.snap
+++ /dev/null
@@ -1,5 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`noscript tags API updates noscript tags 1`] = `" "`;
-
-exports[`noscript tags Declarative API updates noscript tags 1`] = `" "`;
diff --git a/__tests__/api/__snapshots__/noscript.test.tsx.snap b/__tests__/api/__snapshots__/noscript.test.tsx.snap
new file mode 100644
index 00000000..d99ccac8
--- /dev/null
+++ b/__tests__/api/__snapshots__/noscript.test.tsx.snap
@@ -0,0 +1,5 @@
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
+
+exports[`noscript tags > API > updates noscript tags 1`] = `" "`;
+
+exports[`noscript tags > Declarative API > updates noscript tags 1`] = `" "`;
diff --git a/__tests__/api/__snapshots__/script.test.js.snap b/__tests__/api/__snapshots__/script.test.js.snap
deleted file mode 100644
index 3e2e4a4a..00000000
--- a/__tests__/api/__snapshots__/script.test.js.snap
+++ /dev/null
@@ -1,13 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`script tags API sets script tags based on deepest nested component 1`] = `""`;
-
-exports[`script tags API sets script tags based on deepest nested component 2`] = `""`;
-
-exports[`script tags API sets undefined attribute values to empty strings 1`] = `""`;
-
-exports[`script tags Declarative API sets script tags based on deepest nested component 1`] = `""`;
-
-exports[`script tags Declarative API sets script tags based on deepest nested component 2`] = `""`;
-
-exports[`script tags Declarative API sets undefined attribute values to empty strings 1`] = `""`;
diff --git a/__tests__/api/__snapshots__/script.test.tsx.snap b/__tests__/api/__snapshots__/script.test.tsx.snap
new file mode 100644
index 00000000..72d0db70
--- /dev/null
+++ b/__tests__/api/__snapshots__/script.test.tsx.snap
@@ -0,0 +1,13 @@
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
+
+exports[`script tags > API > sets script tags based on deepest nested component 1`] = `""`;
+
+exports[`script tags > API > sets script tags based on deepest nested component 2`] = `""`;
+
+exports[`script tags > API > sets undefined attribute values to empty strings 1`] = `""`;
+
+exports[`script tags > Declarative API > sets script tags based on deepest nested component 1`] = `""`;
+
+exports[`script tags > Declarative API > sets script tags based on deepest nested component 2`] = `""`;
+
+exports[`script tags > Declarative API > sets undefined attribute values to empty strings 1`] = `""`;
diff --git a/__tests__/api/__snapshots__/style.test.js.snap b/__tests__/api/__snapshots__/style.test.tsx.snap
similarity index 67%
rename from __tests__/api/__snapshots__/style.test.js.snap
rename to __tests__/api/__snapshots__/style.test.tsx.snap
index d3d01962..f7fc5b2f 100644
--- a/__tests__/api/__snapshots__/style.test.js.snap
+++ b/__tests__/api/__snapshots__/style.test.tsx.snap
@@ -1,6 +1,6 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
-exports[`Declarative API updates style tags 1`] = `
+exports[`Declarative API > updates style tags 1`] = `
""
`;
-exports[`Declarative API updates style tags 2`] = `
+exports[`Declarative API > updates style tags 2`] = `
""
`;
-exports[`style tags updates style tags 1`] = `
+exports[`style tags > updates style tags 1`] = `
""
`;
-exports[`style tags updates style tags 2`] = `
+exports[`style tags > updates style tags 2`] = `
""`;
-
-exports[`server API renders style tags as string 1`] = `""`;
-
-exports[`server Declarative API renders style tags as React components 1`] = `""`;
-
-exports[`server Declarative API renders style tags as string 1`] = `""`;
diff --git a/__tests__/server/__snapshots__/style.test.tsx.snap b/__tests__/server/__snapshots__/style.test.tsx.snap
new file mode 100644
index 00000000..4fb5a067
--- /dev/null
+++ b/__tests__/server/__snapshots__/style.test.tsx.snap
@@ -0,0 +1,9 @@
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
+
+exports[`server > API > renders style tags as React components 1`] = `""`;
+
+exports[`server > API > renders style tags as string 1`] = `""`;
+
+exports[`server > Declarative API > renders style tags as React components 1`] = `""`;
+
+exports[`server > Declarative API > renders style tags as string 1`] = `""`;
diff --git a/__tests__/server/__snapshots__/title.test.js.snap b/__tests__/server/__snapshots__/title.test.js.snap
deleted file mode 100644
index a77924dc..00000000
--- a/__tests__/server/__snapshots__/title.test.js.snap
+++ /dev/null
@@ -1,35 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`server API does not encode all characters with HTML character entity equivalents 1`] = `"膣膗 鍆錌雔 "`;
-
-exports[`server API encodes special characters in title 1`] = `"Dangerous <script> include "`;
-
-exports[`server API opts out of string encoding 1`] = `"This is text and & and '. "`;
-
-exports[`server API renders title as React component 1`] = `"Dangerous <script> include "`;
-
-exports[`server API renders title tag as string 1`] = `"Dangerous <script> include "`;
-
-exports[`server API renders title with itemprop name as React component 1`] = `"Title with Itemprop "`;
-
-exports[`server API renders title with itemprop name as string 1`] = `"Title with Itemprop "`;
-
-exports[`server Declarative API does not encode all characters with HTML character entity equivalents 1`] = `"膣膗 鍆錌雔 "`;
-
-exports[`server Declarative API encodes special characters in title 1`] = `"Dangerous <script> include "`;
-
-exports[`server Declarative API opts out of string encoding 1`] = `"This is text and & and '. "`;
-
-exports[`server Declarative API renders title and allows children containing expressions 1`] = `"Title: Some Great Title "`;
-
-exports[`server Declarative API renders title as React component 1`] = `"Dangerous <script> include "`;
-
-exports[`server Declarative API renders title tag as string 1`] = `"Dangerous <script> include "`;
-
-exports[`server Declarative API renders title with itemprop name as React component 1`] = `"Title with Itemprop "`;
-
-exports[`server Declarative API renders title with itemprop name as string 1`] = `"Title with Itemprop "`;
-
-exports[`server renderStatic does html encode title 1`] = `"Dangerous <script> include "`;
-
-exports[`server renderStatic renders title as React component 1`] = `"Dangerous <script> include "`;
diff --git a/__tests__/server/__snapshots__/title.test.tsx.snap b/__tests__/server/__snapshots__/title.test.tsx.snap
new file mode 100644
index 00000000..1a76da97
--- /dev/null
+++ b/__tests__/server/__snapshots__/title.test.tsx.snap
@@ -0,0 +1,35 @@
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
+
+exports[`server > API > does not encode all characters with HTML character entity equivalents 1`] = `"膣膗 鍆錌雔 "`;
+
+exports[`server > API > encodes special characters in title 1`] = `"Dangerous <script> include "`;
+
+exports[`server > API > opts out of string encoding 1`] = `"This is text and & and '. "`;
+
+exports[`server > API > renders title as React component 1`] = `"Dangerous <script> include "`;
+
+exports[`server > API > renders title tag as string 1`] = `"Dangerous <script> include "`;
+
+exports[`server > API > renders title with itemprop name as React component 1`] = `"Title with Itemprop "`;
+
+exports[`server > API > renders title with itemprop name as string 1`] = `"Title with Itemprop "`;
+
+exports[`server > Declarative API > does not encode all characters with HTML character entity equivalents 1`] = `"膣膗 鍆錌雔 "`;
+
+exports[`server > Declarative API > encodes special characters in title 1`] = `"Dangerous <script> include "`;
+
+exports[`server > Declarative API > opts out of string encoding 1`] = `"This is text and & and '. "`;
+
+exports[`server > Declarative API > renders title and allows children containing expressions 1`] = `"Title: Some Great Title "`;
+
+exports[`server > Declarative API > renders title as React component 1`] = `"Dangerous <script> include "`;
+
+exports[`server > Declarative API > renders title tag as string 1`] = `"Dangerous <script> include "`;
+
+exports[`server > Declarative API > renders title with itemprop name as React component 1`] = `"Title with Itemprop "`;
+
+exports[`server > Declarative API > renders title with itemprop name as string 1`] = `"Title with Itemprop "`;
+
+exports[`server > renderStatic > does html encode title 1`] = `"Dangerous <script> include "`;
+
+exports[`server > renderStatic > renders title as React component 1`] = `"Dangerous <script> include "`;
diff --git a/__tests__/server/base.test.js b/__tests__/server/base.test.tsx
similarity index 67%
rename from __tests__/server/base.test.js
rename to __tests__/server/base.test.tsx
index d0a5cbf9..af8b09af 100644
--- a/__tests__/server/base.test.js
+++ b/__tests__/server/base.test.tsx
@@ -1,17 +1,12 @@
import React from 'react';
-import ReactDOM from 'react-dom';
import ReactServer from 'react-dom/server';
-import Helmet from '../../src';
+
+import { Helmet } from '../../src';
import Provider from '../../src/Provider';
+import { renderContext, isArray } from '../utils';
Helmet.defaultProps.defer = false;
-const mount = document.getElementById('mount');
-
-const render = (node, context = {}) => {
- ReactDOM.render({node} , mount);
-};
-
beforeAll(() => {
Provider.canUseDOM = false;
});
@@ -20,16 +15,10 @@ afterAll(() => {
Provider.canUseDOM = true;
});
-const isArray = {
- asymmetricMatch: actual => Array.isArray(actual),
-};
-
describe('server', () => {
describe('API', () => {
it('renders base tag as React component', () => {
- const context = {};
- render( , context);
- const head = context.helmet;
+ const head = renderContext( );
expect(head.base).toBeDefined();
expect(head.base.toComponent).toBeDefined();
@@ -39,7 +28,7 @@ describe('server', () => {
expect(baseComponent).toEqual(isArray);
expect(baseComponent).toHaveLength(1);
- baseComponent.forEach(base => {
+ baseComponent.forEach((base: Element) => {
expect(base).toEqual(expect.objectContaining({ type: 'base' }));
});
@@ -49,11 +38,7 @@ describe('server', () => {
});
it('renders base tags as string', () => {
- const context = {};
- render( , context);
-
- const head = context.helmet;
-
+ const head = renderContext( );
expect(head.base).toBeDefined();
expect(head.base.toString).toBeDefined();
expect(head.base.toString()).toMatchSnapshot();
@@ -62,16 +47,12 @@ describe('server', () => {
describe('Declarative API', () => {
it('renders base tag as React component', () => {
- const context = {};
- render(
+ const head = renderContext(
- ,
- context
+
);
- const head = context.helmet;
-
expect(head.base).toBeDefined();
expect(head.base.toComponent).toBeDefined();
@@ -80,7 +61,7 @@ describe('server', () => {
expect(baseComponent).toEqual(isArray);
expect(baseComponent).toHaveLength(1);
- baseComponent.forEach(base => {
+ baseComponent.forEach((base: Element) => {
expect(base).toEqual(expect.objectContaining({ type: 'base' }));
});
@@ -90,16 +71,12 @@ describe('server', () => {
});
it('renders base tags as string', () => {
- const context = {};
- render(
+ const head = renderContext(
- ,
- context
+
);
- const head = context.helmet;
-
expect(head.base).toBeDefined();
expect(head.base.toString).toBeDefined();
expect(head.base.toString()).toMatchSnapshot();
diff --git a/__tests__/server/bodyAttributes.test.js b/__tests__/server/bodyAttributes.test.tsx
similarity index 64%
rename from __tests__/server/bodyAttributes.test.js
rename to __tests__/server/bodyAttributes.test.tsx
index 394c9a14..a30e69ec 100644
--- a/__tests__/server/bodyAttributes.test.js
+++ b/__tests__/server/bodyAttributes.test.tsx
@@ -1,17 +1,12 @@
import React from 'react';
-import ReactDOM from 'react-dom';
import ReactServer from 'react-dom/server';
-import Helmet from '../../src';
+
+import { Helmet } from '../../src';
import Provider from '../../src/Provider';
+import { renderContext } from '../utils';
Helmet.defaultProps.defer = false;
-const mount = document.getElementById('mount');
-
-const render = (node, context = {}) => {
- ReactDOM.render({node} , mount);
-};
-
beforeAll(() => {
Provider.canUseDOM = false;
});
@@ -23,16 +18,12 @@ afterAll(() => {
describe('server', () => {
describe('Declarative API', () => {
it('renders body attributes as component', () => {
- const context = {};
- render(
+ const head = renderContext(
- ,
- context
+
);
-
- const { bodyAttributes } = context.helmet;
- const attrs = bodyAttributes.toComponent();
+ const attrs = head.bodyAttributes.toComponent();
expect(attrs).toBeDefined();
@@ -42,16 +33,12 @@ describe('server', () => {
});
it('renders body attributes as string', () => {
- const context = {};
- render(
+ const body = renderContext(
- ,
- context
+
);
- const body = context.helmet;
-
expect(body.bodyAttributes).toBeDefined();
expect(body.bodyAttributes.toString).toBeDefined();
expect(body.bodyAttributes.toString()).toMatchSnapshot();
diff --git a/__tests__/server/helmetData.test.tsx b/__tests__/server/helmetData.test.tsx
new file mode 100644
index 00000000..0c2ec67f
--- /dev/null
+++ b/__tests__/server/helmetData.test.tsx
@@ -0,0 +1,161 @@
+import React from 'react';
+
+import { Helmet } from '../../src';
+import Provider from '../../src/Provider';
+import HelmetData from '../../src/HelmetData';
+import { HELMET_ATTRIBUTE } from '../../src/constants';
+import { render } from '../utils';
+
+Helmet.defaultProps.defer = false;
+
+describe('Helmet Data', () => {
+ describe('server', () => {
+ beforeAll(() => {
+ Provider.canUseDOM = false;
+ });
+
+ afterAll(() => {
+ Provider.canUseDOM = true;
+ });
+
+ it('renders without context', () => {
+ const helmetData = new HelmetData({});
+
+ render(
+
+ );
+
+ const head = helmetData.context.helmet;
+
+ expect(head.base).toBeDefined();
+ expect(head.base.toString).toBeDefined();
+ expect(head.base.toString()).toMatchSnapshot();
+ });
+
+ it('renders declarative without context', () => {
+ const helmetData = new HelmetData({});
+
+ render(
+
+
+
+ );
+
+ const head = helmetData.context.helmet;
+
+ expect(head.base).toBeDefined();
+ expect(head.base.toString).toBeDefined();
+ expect(head.base.toString()).toMatchSnapshot();
+ });
+
+ it('sets base tag based on deepest nested component', () => {
+ const helmetData = new HelmetData({});
+
+ render(
+
+
+
+
+
+
+
+
+ );
+
+ const head = helmetData.context.helmet;
+
+ expect(head.base).toBeDefined();
+ expect(head.base.toString).toBeDefined();
+ expect(head.base.toString()).toMatchSnapshot();
+ });
+
+ it('works with the same context object but separate HelmetData instances', () => {
+ const context = {} as any;
+
+ render(
+
+
+
+
+
+
+
+
+ );
+
+ const head = context.helmet;
+
+ expect(head.base).toBeDefined();
+ expect(head.base.toString).toBeDefined();
+ expect(head.base.toString()).toMatchSnapshot();
+ });
+ });
+
+ describe('browser', () => {
+ it('renders without context', () => {
+ const helmetData = new HelmetData({});
+
+ render(
+
+ );
+
+ const existingTags = [...document.head.querySelectorAll(`base[${HELMET_ATTRIBUTE}]`)];
+ const [firstTag] = existingTags;
+
+ expect(existingTags).toBeDefined();
+ expect(existingTags).toHaveLength(1);
+
+ expect(firstTag).toBeInstanceOf(Element);
+ expect(firstTag.getAttribute).toBeDefined();
+ expect(firstTag).toHaveAttribute('href', 'http://localhost/');
+ expect(firstTag.outerHTML).toMatchSnapshot();
+ });
+
+ it('renders declarative without context', () => {
+ const helmetData = new HelmetData({});
+
+ render(
+
+
+
+ );
+
+ const existingTags = [...document.head.querySelectorAll(`base[${HELMET_ATTRIBUTE}]`)];
+ const [firstTag] = existingTags;
+
+ expect(existingTags).toBeDefined();
+ expect(existingTags).toHaveLength(1);
+
+ expect(firstTag).toBeInstanceOf(Element);
+ expect(firstTag.getAttribute).toBeDefined();
+ expect(firstTag).toHaveAttribute('href', 'http://localhost/');
+ expect(firstTag.outerHTML).toMatchSnapshot();
+ });
+
+ it('sets base tag based on deepest nested component', () => {
+ const helmetData = new HelmetData({});
+
+ render(
+
+
+
+
+
+
+
+
+ );
+
+ const existingTags = [...document.head.querySelectorAll(`base[${HELMET_ATTRIBUTE}]`)];
+ const [firstTag] = existingTags;
+
+ expect(existingTags).toBeDefined();
+ expect(existingTags).toHaveLength(1);
+
+ expect(firstTag).toBeInstanceOf(Element);
+ expect(firstTag.getAttribute).toBeDefined();
+ expect(firstTag).toHaveAttribute('href', 'http://mysite.com/public');
+ expect(firstTag.outerHTML).toMatchSnapshot();
+ });
+ });
+});
diff --git a/__tests__/server/htmlAttributes.test.js b/__tests__/server/htmlAttributes.test.tsx
similarity index 68%
rename from __tests__/server/htmlAttributes.test.js
rename to __tests__/server/htmlAttributes.test.tsx
index 8743c676..7cd87a9a 100644
--- a/__tests__/server/htmlAttributes.test.js
+++ b/__tests__/server/htmlAttributes.test.tsx
@@ -1,17 +1,12 @@
import React from 'react';
-import ReactDOM from 'react-dom';
import ReactServer from 'react-dom/server';
-import Helmet from '../../src';
+
+import { Helmet } from '../../src';
import Provider from '../../src/Provider';
+import { renderContext } from '../utils';
Helmet.defaultProps.defer = false;
-const mount = document.getElementById('mount');
-
-const render = (node, context = {}) => {
- ReactDOM.render({node} , mount);
-};
-
beforeAll(() => {
Provider.canUseDOM = false;
});
@@ -23,19 +18,16 @@ afterAll(() => {
describe('server', () => {
describe('API', () => {
it('renders html attributes as component', () => {
- const context = {};
- render(
+ const head = renderContext(
,
- context
+ />
);
- const { htmlAttributes } = context.helmet;
- const attrs = htmlAttributes.toComponent();
+ const attrs = head.htmlAttributes.toComponent();
expect(attrs).toBeDefined();
@@ -45,19 +37,15 @@ describe('server', () => {
});
it('renders html attributes as string', () => {
- const context = {};
- render(
+ const head = renderContext(
,
- context
+ />
);
- const head = context.helmet;
-
expect(head.htmlAttributes).toBeDefined();
expect(head.htmlAttributes.toString).toBeDefined();
expect(head.htmlAttributes.toString()).toMatchSnapshot();
@@ -66,16 +54,13 @@ describe('server', () => {
describe('Declarative API', () => {
it('renders html attributes as component', () => {
- const context = {};
- render(
+ const head = renderContext(
- ,
- context
+
);
- const { htmlAttributes } = context.helmet;
- const attrs = htmlAttributes.toComponent();
+ const attrs = head.htmlAttributes.toComponent();
expect(attrs).toBeDefined();
@@ -85,16 +70,12 @@ describe('server', () => {
});
it('renders html attributes as string', () => {
- const context = {};
- render(
+ const head = renderContext(
- ,
- context
+
);
- const head = context.helmet;
-
expect(head.htmlAttributes).toBeDefined();
expect(head.htmlAttributes.toString).toBeDefined();
expect(head.htmlAttributes.toString()).toMatchSnapshot();
diff --git a/__tests__/server/link.test.js b/__tests__/server/link.test.tsx
similarity index 76%
rename from __tests__/server/link.test.js
rename to __tests__/server/link.test.tsx
index d21a8944..059b0b94 100644
--- a/__tests__/server/link.test.js
+++ b/__tests__/server/link.test.tsx
@@ -1,17 +1,12 @@
import React from 'react';
-import ReactDOM from 'react-dom';
import ReactServer from 'react-dom/server';
-import Helmet from '../../src';
+
+import { Helmet } from '../../src';
import Provider from '../../src/Provider';
+import { renderContext, isArray } from '../utils';
Helmet.defaultProps.defer = false;
-const mount = document.getElementById('mount');
-
-const render = (node, context = {}) => {
- ReactDOM.render({node} , mount);
-};
-
beforeAll(() => {
Provider.canUseDOM = false;
});
@@ -20,15 +15,10 @@ afterAll(() => {
Provider.canUseDOM = true;
});
-const isArray = {
- asymmetricMatch: actual => Array.isArray(actual),
-};
-
describe('server', () => {
describe('API', () => {
it('renders link tags as React components', () => {
- const context = {};
- render(
+ const head = renderContext(
{
type: 'text/css',
},
]}
- />,
- context
+ />
);
- const head = context.helmet;
-
expect(head.link).toBeDefined();
expect(head.link.toComponent).toBeDefined();
@@ -52,7 +39,7 @@ describe('server', () => {
expect(linkComponent).toEqual(isArray);
expect(linkComponent).toHaveLength(2);
- linkComponent.forEach(link => {
+ linkComponent.forEach((link: Element) => {
expect(link).toEqual(expect.objectContaining({ type: 'link' }));
});
@@ -62,8 +49,7 @@ describe('server', () => {
});
it('renders link tags as string', () => {
- const context = {};
- render(
+ const head = renderContext(
{
type: 'text/css',
},
]}
- />,
- context
+ />
);
- const head = context.helmet;
-
expect(head.link).toBeDefined();
expect(head.link.toString).toBeDefined();
expect(head.link.toString()).toMatchSnapshot();
@@ -87,17 +70,13 @@ describe('server', () => {
describe('Declarative API', () => {
it('renders link tags as React components', () => {
- const context = {};
- render(
+ const head = renderContext(
- ,
- context
+
);
- const head = context.helmet;
-
expect(head.link).toBeDefined();
expect(head.link.toComponent).toBeDefined();
@@ -106,7 +85,7 @@ describe('server', () => {
expect(linkComponent).toEqual(isArray);
expect(linkComponent).toHaveLength(2);
- linkComponent.forEach(link => {
+ linkComponent.forEach((link: Element) => {
expect(link).toEqual(expect.objectContaining({ type: 'link' }));
});
@@ -116,17 +95,13 @@ describe('server', () => {
});
it('renders link tags as string', () => {
- const context = {};
- render(
+ const head = renderContext(
- ,
- context
+
);
- const head = context.helmet;
-
expect(head.link).toBeDefined();
expect(head.link.toString).toBeDefined();
expect(head.link.toString()).toMatchSnapshot();
diff --git a/__tests__/server/meta.test.js b/__tests__/server/meta.test.tsx
similarity index 73%
rename from __tests__/server/meta.test.js
rename to __tests__/server/meta.test.tsx
index a10fc14e..979a34d9 100644
--- a/__tests__/server/meta.test.js
+++ b/__tests__/server/meta.test.tsx
@@ -1,17 +1,12 @@
-import React, { Fragment } from 'react';
-import ReactDOM from 'react-dom';
+import React from 'react';
import ReactServer from 'react-dom/server';
-import Helmet from '../../src';
+
+import { Helmet } from '../../src';
import Provider from '../../src/Provider';
+import { renderContext, isArray } from '../utils';
Helmet.defaultProps.defer = false;
-const mount = document.getElementById('mount');
-
-const render = (node, context = {}) => {
- ReactDOM.render({node} , mount);
-};
-
beforeAll(() => {
Provider.canUseDOM = false;
});
@@ -20,15 +15,10 @@ afterAll(() => {
Provider.canUseDOM = true;
});
-const isArray = {
- asymmetricMatch: actual => Array.isArray(actual),
-};
-
describe('server', () => {
describe('API', () => {
it('renders meta tags as React components', () => {
- const context = {};
- render(
+ const head = renderContext(
{
{ property: 'og:type', content: 'article' },
{ itemprop: 'name', content: 'Test name itemprop' },
]}
- />,
- context
+ />
);
- const head = context.helmet;
-
expect(head.meta).toBeDefined();
expect(head.meta.toComponent).toBeDefined();
@@ -54,18 +41,17 @@ describe('server', () => {
expect(metaComponent).toEqual(isArray);
expect(metaComponent).toHaveLength(5);
- metaComponent.forEach(meta => {
+ metaComponent.forEach((meta: Element) => {
expect(meta).toEqual(expect.objectContaining({ type: 'meta' }));
});
- const markup = ReactServer.renderToStaticMarkup({metaComponent} );
+ const markup = ReactServer.renderToStaticMarkup(metaComponent);
expect(markup).toMatchSnapshot();
});
it('renders meta tags as string', () => {
- const context = {};
- render(
+ const head = renderContext(
{
{ property: 'og:type', content: 'article' },
{ itemprop: 'name', content: 'Test name itemprop' },
]}
- />,
- context
+ />
);
- const head = context.helmet;
-
expect(head.meta).toBeDefined();
expect(head.meta.toString).toBeDefined();
expect(head.meta.toString()).toMatchSnapshot();
@@ -91,8 +74,7 @@ describe('server', () => {
describe('Declarative API', () => {
it('renders meta tags as React components', () => {
- const context = {};
- render(
+ const head = renderContext(
{
- ,
- context
+
);
- const head = context.helmet;
-
expect(head.meta).toBeDefined();
expect(head.meta.toComponent).toBeDefined();
@@ -116,33 +95,29 @@ describe('server', () => {
expect(metaComponent).toEqual(isArray);
expect(metaComponent).toHaveLength(5);
- metaComponent.forEach(meta => {
+ metaComponent.forEach((meta: Element) => {
expect(meta).toEqual(expect.objectContaining({ type: 'meta' }));
});
- const markup = ReactServer.renderToStaticMarkup({metaComponent} );
+ const markup = ReactServer.renderToStaticMarkup(metaComponent);
expect(markup).toMatchSnapshot();
});
it('renders meta tags as string', () => {
- const context = {};
- render(
+ const head = renderContext(
- ,
- context
+
);
- const head = context.helmet;
-
expect(head.meta).toBeDefined();
expect(head.meta.toString).toBeDefined();
expect(head.meta.toString()).toMatchSnapshot();
diff --git a/__tests__/server/noscript.test.js b/__tests__/server/noscript.test.tsx
similarity index 76%
rename from __tests__/server/noscript.test.js
rename to __tests__/server/noscript.test.tsx
index 000a51ab..edc514df 100644
--- a/__tests__/server/noscript.test.js
+++ b/__tests__/server/noscript.test.tsx
@@ -1,17 +1,12 @@
import React from 'react';
-import ReactDOM from 'react-dom';
import ReactServer from 'react-dom/server';
-import Helmet from '../../src';
+
+import { Helmet } from '../../src';
import Provider from '../../src/Provider';
+import { renderContext, isArray } from '../utils';
Helmet.defaultProps.defer = false;
-const mount = document.getElementById('mount');
-
-const render = (node, context = {}) => {
- ReactDOM.render({node} , mount);
-};
-
beforeAll(() => {
Provider.canUseDOM = false;
});
@@ -20,15 +15,10 @@ afterAll(() => {
Provider.canUseDOM = true;
});
-const isArray = {
- asymmetricMatch: actual => Array.isArray(actual),
-};
-
describe('server', () => {
describe('API', () => {
it('renders noscript tags as React components', () => {
- const context = {};
- render(
+ const head = renderContext(
{
innerHTML: ' ',
},
]}
- />,
- context
+ />
);
- const head = context.helmet;
-
expect(head.noscript).toBeDefined();
expect(head.noscript.toComponent).toBeDefined();
@@ -54,7 +41,7 @@ describe('server', () => {
expect(noscriptComponent).toEqual(isArray);
expect(noscriptComponent).toHaveLength(2);
- noscriptComponent.forEach(noscript => {
+ noscriptComponent.forEach((noscript: Element) => {
expect(noscript).toEqual(expect.objectContaining({ type: 'noscript' }));
});
@@ -66,17 +53,13 @@ describe('server', () => {
describe('Declarative API', () => {
it('renders noscript tags as React components', () => {
- const context = {};
- render(
+ const head = renderContext(
{` `}
{` `}
- ,
- context
+
);
- const head = context.helmet;
-
expect(head.noscript).toBeDefined();
expect(head.noscript.toComponent).toBeDefined();
@@ -85,7 +68,7 @@ describe('server', () => {
expect(noscriptComponent).toEqual(isArray);
expect(noscriptComponent).toHaveLength(2);
- noscriptComponent.forEach(noscript => {
+ noscriptComponent.forEach((noscript: Element) => {
expect(noscript).toEqual(expect.objectContaining({ type: 'noscript' }));
});
diff --git a/__tests__/server/script.test.js b/__tests__/server/script.test.tsx
similarity index 76%
rename from __tests__/server/script.test.js
rename to __tests__/server/script.test.tsx
index a2d01f72..7f844ab9 100644
--- a/__tests__/server/script.test.js
+++ b/__tests__/server/script.test.tsx
@@ -1,17 +1,12 @@
import React from 'react';
-import ReactDOM from 'react-dom';
import ReactServer from 'react-dom/server';
-import Helmet from '../../src';
+
+import { Helmet } from '../../src';
import Provider from '../../src/Provider';
+import { renderContext, isArray } from '../utils';
Helmet.defaultProps.defer = false;
-const mount = document.getElementById('mount');
-
-const render = (node, context = {}) => {
- ReactDOM.render({node} , mount);
-};
-
beforeAll(() => {
Provider.canUseDOM = false;
});
@@ -20,15 +15,10 @@ afterAll(() => {
Provider.canUseDOM = true;
});
-const isArray = {
- asymmetricMatch: actual => Array.isArray(actual),
-};
-
describe('server', () => {
describe('API', () => {
it('renders script tags as React components', () => {
- const context = {};
- render(
+ const head = renderContext(
{
type: 'text/javascript',
},
]}
- />,
- context
+ />
);
- const head = context.helmet;
-
expect(head.script).toBeDefined();
expect(head.script.toComponent).toBeDefined();
@@ -54,7 +41,7 @@ describe('server', () => {
expect(scriptComponent).toEqual(isArray);
expect(scriptComponent).toHaveLength(2);
- scriptComponent.forEach(script => {
+ scriptComponent.forEach((script: Element) => {
expect(script).toEqual(expect.objectContaining({ type: 'script' }));
});
@@ -64,8 +51,7 @@ describe('server', () => {
});
it('renders script tags as string', () => {
- const context = {};
- render(
+ const head = renderContext(
{
type: 'text/javascript',
},
]}
- />,
- context
+ />
);
- const head = context.helmet;
-
expect(head.script).toBeDefined();
expect(head.script.toString).toBeDefined();
expect(head.script.toString()).toMatchSnapshot();
@@ -91,17 +74,13 @@ describe('server', () => {
describe('Declarative API', () => {
it('renders script tags as React components', () => {
- const context = {};
- render(
+ const head = renderContext(
- ,
- context
+
);
- const head = context.helmet;
-
expect(head.script).toBeDefined();
expect(head.script.toComponent).toBeDefined();
@@ -110,7 +89,7 @@ describe('server', () => {
expect(scriptComponent).toEqual(isArray);
expect(scriptComponent).toHaveLength(2);
- scriptComponent.forEach(script => {
+ scriptComponent.forEach((script: Element) => {
expect(script).toEqual(expect.objectContaining({ type: 'script' }));
});
@@ -120,17 +99,13 @@ describe('server', () => {
});
it('renders script tags as string', () => {
- const context = {};
- render(
+ const head = renderContext(
- ,
- context
+
);
- const head = context.helmet;
-
expect(head.script).toBeDefined();
expect(head.script.toString).toBeDefined();
expect(head.script.toString()).toMatchSnapshot();
diff --git a/__tests__/server/server.test.js b/__tests__/server/server.test.tsx
similarity index 63%
rename from __tests__/server/server.test.js
rename to __tests__/server/server.test.tsx
index 06caea84..9831f3e9 100644
--- a/__tests__/server/server.test.js
+++ b/__tests__/server/server.test.tsx
@@ -1,17 +1,12 @@
import React from 'react';
-import ReactDOM from 'react-dom';
import ReactServer from 'react-dom/server';
-import Helmet from '../../src';
+
+import { Helmet } from '../../src';
import Provider from '../../src/Provider';
+import { renderContext, isArray } from '../utils';
Helmet.defaultProps.defer = false;
-const mount = document.getElementById('mount');
-
-const render = (node, context = {}) => {
- ReactDOM.render({node} , mount);
-};
-
beforeAll(() => {
Provider.canUseDOM = false;
});
@@ -20,21 +15,14 @@ afterAll(() => {
Provider.canUseDOM = true;
});
-const isArray = {
- asymmetricMatch: actual => Array.isArray(actual),
-};
-
describe('server', () => {
describe('API', () => {
it('rewind() provides a fallback object for empty Helmet state', () => {
- const context = {};
- render(
, context);
-
- const head = context.helmet;
+ const head = renderContext(
);
expect(head.htmlAttributes).toBeDefined();
expect(head.htmlAttributes.toString).toBeDefined();
- expect(head.htmlAttributes.toString()).toEqual('');
+ expect(head.htmlAttributes.toString()).toBe('');
expect(head.htmlAttributes.toComponent).toBeDefined();
expect(head.htmlAttributes.toComponent()).toEqual({});
@@ -49,93 +37,103 @@ describe('server', () => {
expect(head.base).toBeDefined();
expect(head.base.toString).toBeDefined();
- expect(head.base.toString()).toEqual('');
+ expect(head.base.toString()).toBe('');
expect(head.base.toComponent).toBeDefined();
+
const baseComponent = head.base.toComponent();
+
expect(baseComponent).toEqual(isArray);
expect(baseComponent).toHaveLength(0);
expect(head.meta).toBeDefined();
expect(head.meta.toString).toBeDefined();
- expect(head.meta.toString()).toEqual('');
+ expect(head.meta.toString()).toBe('');
expect(head.meta.toComponent).toBeDefined();
+
const metaComponent = head.meta.toComponent();
+
expect(metaComponent).toEqual(isArray);
expect(metaComponent).toHaveLength(0);
expect(head.link).toBeDefined();
expect(head.link.toString).toBeDefined();
- expect(head.link.toString()).toEqual('');
+ expect(head.link.toString()).toBe('');
expect(head.link.toComponent).toBeDefined();
+
const linkComponent = head.link.toComponent();
+
expect(linkComponent).toEqual(isArray);
expect(linkComponent).toHaveLength(0);
expect(head.script).toBeDefined();
expect(head.script.toString).toBeDefined();
- expect(head.script.toString()).toEqual('');
+ expect(head.script.toString()).toBe('');
expect(head.script.toComponent).toBeDefined();
+
const scriptComponent = head.script.toComponent();
+
expect(scriptComponent).toEqual(isArray);
expect(scriptComponent).toHaveLength(0);
expect(head.noscript).toBeDefined();
expect(head.noscript.toString).toBeDefined();
- expect(head.noscript.toString()).toEqual('');
+ expect(head.noscript.toString()).toBe('');
expect(head.noscript.toComponent).toBeDefined();
+
const noscriptComponent = head.noscript.toComponent();
+
expect(noscriptComponent).toEqual(isArray);
expect(noscriptComponent).toHaveLength(0);
expect(head.style).toBeDefined();
expect(head.style.toString).toBeDefined();
- expect(head.style.toString()).toEqual('');
+ expect(head.style.toString()).toBe('');
expect(head.style.toComponent).toBeDefined();
+
const styleComponent = head.style.toComponent();
+
expect(styleComponent).toEqual(isArray);
expect(styleComponent).toHaveLength(0);
+
+ expect(head.priority).toBeDefined();
+ expect(head.priority.toString).toBeDefined();
+ expect(head.priority.toString()).toBe('');
+ expect(head.priority.toComponent).toBeDefined();
});
it('does not render undefined attribute values', () => {
- const context = {};
- render(
+ const head = renderContext(
,
- context
+ />
);
- const { script } = context.helmet;
- expect(script.toString()).toMatchSnapshot();
+ expect(head.script.toString()).toMatchSnapshot();
});
});
describe('Declarative API', () => {
it('provides initial values if no state is found', () => {
- const context = {};
- render(
, context);
- const head = context.helmet;
+ const head = renderContext(
);
expect(head.meta).toBeDefined();
expect(head.meta.toString).toBeDefined();
- expect(head.meta.toString()).toEqual('');
+ expect(head.meta.toString()).toBe('');
});
it('rewind() provides a fallback object for empty Helmet state', () => {
- const context = {};
- render(
, context);
-
- const head = context.helmet;
+ const head = renderContext(
);
expect(head.htmlAttributes).toBeDefined();
expect(head.htmlAttributes.toString).toBeDefined();
- expect(head.htmlAttributes.toString()).toEqual('');
+ expect(head.htmlAttributes.toString()).toBe('');
expect(head.htmlAttributes.toComponent).toBeDefined();
expect(head.htmlAttributes.toComponent()).toEqual({});
@@ -150,64 +148,120 @@ describe('server', () => {
expect(head.base).toBeDefined();
expect(head.base.toString).toBeDefined();
- expect(head.base.toString()).toEqual('');
+ expect(head.base.toString()).toBe('');
expect(head.base.toComponent).toBeDefined();
+
const baseComponent = head.base.toComponent();
+
expect(baseComponent).toEqual(isArray);
expect(baseComponent).toHaveLength(0);
expect(head.meta).toBeDefined();
expect(head.meta.toString).toBeDefined();
- expect(head.meta.toString()).toEqual('');
+ expect(head.meta.toString()).toBe('');
expect(head.meta.toComponent).toBeDefined();
+
const metaComponent = head.meta.toComponent();
+
expect(metaComponent).toEqual(isArray);
expect(metaComponent).toHaveLength(0);
expect(head.link).toBeDefined();
expect(head.link.toString).toBeDefined();
- expect(head.link.toString()).toEqual('');
+ expect(head.link.toString()).toBe('');
expect(head.link.toComponent).toBeDefined();
+
const linkComponent = head.link.toComponent();
+
expect(linkComponent).toEqual(isArray);
expect(linkComponent).toHaveLength(0);
expect(head.script).toBeDefined();
expect(head.script.toString).toBeDefined();
- expect(head.script.toString()).toEqual('');
+ expect(head.script.toString()).toBe('');
expect(head.script.toComponent).toBeDefined();
+
const scriptComponent = head.script.toComponent();
+
expect(scriptComponent).toEqual(isArray);
expect(scriptComponent).toHaveLength(0);
expect(head.noscript).toBeDefined();
expect(head.noscript.toString).toBeDefined();
- expect(head.noscript.toString()).toEqual('');
+ expect(head.noscript.toString()).toBe('');
expect(head.noscript.toComponent).toBeDefined();
+
const noscriptComponent = head.noscript.toComponent();
+
expect(noscriptComponent).toEqual(isArray);
expect(noscriptComponent).toHaveLength(0);
expect(head.style).toBeDefined();
expect(head.style.toString).toBeDefined();
- expect(head.style.toString()).toEqual('');
+ expect(head.style.toString()).toBe('');
expect(head.style.toComponent).toBeDefined();
+
const styleComponent = head.style.toComponent();
+
expect(styleComponent).toEqual(isArray);
expect(styleComponent).toHaveLength(0);
+
+ expect(head.priority).toBeDefined();
+ expect(head.priority.toString).toBeDefined();
+ expect(head.priority.toString()).toBe('');
+ expect(head.priority.toComponent).toBeDefined();
});
it('does not render undefined attribute values', () => {
- const context = {};
- render(
+ const head = renderContext(
- ,
- context
+
+ );
+
+ expect(head.script.toString()).toMatchSnapshot();
+ });
+
+ it('prioritizes SEO tags when asked to', () => {
+ const head = renderContext(
+
+
+
+
+
);
- const { script } = context.helmet;
- expect(script.toString()).toMatchSnapshot();
+ expect(head.priority.toString()).toContain('rel="canonical" href="https://www.tacobell.com/"');
+ expect(head.link.toString()).not.toContain('rel="canonical" href="https://www.tacobell.com/"');
+
+ expect(head.priority.toString()).toContain(
+ 'property="og:title" content="A very important title"'
+ );
+ expect(head.meta.toString()).not.toContain(
+ 'property="og:title" content="A very important title"'
+ );
+ });
+
+ it('does not prioritize SEO unless asked to', () => {
+ const head = renderContext(
+
+
+
+
+
+ );
+
+ expect(head.priority.toString()).not.toContain(
+ 'rel="canonical" href="https://www.tacobell.com/"'
+ );
+ expect(head.link.toString()).toContain('rel="canonical" href="https://www.tacobell.com/"');
+
+ expect(head.priority.toString()).not.toContain(
+ 'property="og:title" content="A very important title"'
+ );
+ expect(head.meta.toString()).toContain(
+ 'property="og:title" content="A very important title"'
+ );
});
});
});
diff --git a/__tests__/server/style.test.js b/__tests__/server/style.test.tsx
similarity index 77%
rename from __tests__/server/style.test.js
rename to __tests__/server/style.test.tsx
index 647d6b84..4a418a7c 100644
--- a/__tests__/server/style.test.js
+++ b/__tests__/server/style.test.tsx
@@ -1,17 +1,12 @@
import React from 'react';
-import ReactDOM from 'react-dom';
import ReactServer from 'react-dom/server';
-import Helmet from '../../src';
+
+import { Helmet } from '../../src';
import Provider from '../../src/Provider';
+import { renderContext, isArray } from '../utils';
Helmet.defaultProps.defer = false;
-const mount = document.getElementById('mount');
-
-const render = (node, context = {}) => {
- ReactDOM.render({node} , mount);
-};
-
beforeAll(() => {
Provider.canUseDOM = false;
});
@@ -20,15 +15,10 @@ afterAll(() => {
Provider.canUseDOM = true;
});
-const isArray = {
- asymmetricMatch: actual => Array.isArray(actual),
-};
-
describe('server', () => {
describe('API', () => {
it('renders style tags as React components', () => {
- const context = {};
- render(
+ const head = renderContext(
{
cssText: `p {font-size: 12px;}`,
},
]}
- />,
- context
+ />
);
- const head = context.helmet;
-
expect(head.style).toBeDefined();
expect(head.style.toComponent).toBeDefined();
@@ -55,12 +42,12 @@ describe('server', () => {
expect(styleComponent).toHaveLength(2);
const markup = ReactServer.renderToStaticMarkup(styleComponent);
+
expect(markup).toMatchSnapshot();
});
it('renders style tags as string', () => {
- const context = {};
- render(
+ const head = renderContext(
{
cssText: `p {font-size: 12px;}`,
},
]}
- />,
- context
+ />
);
- const head = context.helmet;
-
expect(head.style).toBeDefined();
expect(head.style.toString).toBeDefined();
expect(head.style.toString()).toMatchSnapshot();
@@ -86,17 +70,13 @@ describe('server', () => {
describe('Declarative API', () => {
it('renders style tags as React components', () => {
- const context = {};
- render(
+ const head = renderContext(
- ,
- context
+
);
- const head = context.helmet;
-
expect(head.style).toBeDefined();
expect(head.style.toComponent).toBeDefined();
@@ -111,17 +91,13 @@ describe('server', () => {
});
it('renders style tags as string', () => {
- const context = {};
- render(
+ const head = renderContext(
- ,
- context
+
);
- const head = context.helmet;
-
expect(head.style).toBeDefined();
expect(head.style.toString).toBeDefined();
expect(head.style.toString()).toMatchSnapshot();
diff --git a/__tests__/server/title.test.js b/__tests__/server/title.test.tsx
similarity index 72%
rename from __tests__/server/title.test.js
rename to __tests__/server/title.test.tsx
index 1e9dc9b3..e8e02595 100644
--- a/__tests__/server/title.test.js
+++ b/__tests__/server/title.test.tsx
@@ -1,17 +1,12 @@
import React from 'react';
-import ReactDOM from 'react-dom';
import ReactServer from 'react-dom/server';
-import Helmet from '../../src';
+
+import { Helmet } from '../../src';
import Provider from '../../src/Provider';
+import { renderContext, isArray } from '../utils';
Helmet.defaultProps.defer = false;
-const mount = document.getElementById('mount');
-
-const render = (node, context = {}) => {
- ReactDOM.render({node} , mount);
-};
-
beforeAll(() => {
Provider.canUseDOM = false;
});
@@ -20,27 +15,20 @@ afterAll(() => {
Provider.canUseDOM = true;
});
-const isArray = {
- asymmetricMatch: actual => Array.isArray(actual),
-};
-
describe('server', () => {
describe('API', () => {
it('provides initial values if no state is found', () => {
- const context = {};
const NullComponent = () => null;
- render( , context);
- const head = context.helmet;
+
+ const head = renderContext( );
expect(head.meta).toBeDefined();
expect(head.meta.toString).toBeDefined();
- expect(head.meta.toString()).toEqual('');
+ expect(head.meta.toString()).toBe('');
});
it('encodes special characters in title', () => {
- const context = {};
- render( , context);
- const head = context.helmet;
+ const head = renderContext( );
expect(head.title).toBeDefined();
expect(head.title.toString).toBeDefined();
@@ -48,12 +36,9 @@ describe('server', () => {
});
it('opts out of string encoding', () => {
- const context = {};
- render(
- ,
- context
+ const head = renderContext(
+
);
- const head = context.helmet;
expect(head.title).toBeDefined();
expect(head.title.toString).toBeDefined();
@@ -61,9 +46,7 @@ describe('server', () => {
});
it('renders title as React component', () => {
- const context = {};
- render( include'} />, context);
- const head = context.helmet;
+ const head = renderContext( );
expect(head.title).toBeDefined();
expect(head.title.toComponent).toBeDefined();
@@ -73,7 +56,7 @@ describe('server', () => {
expect(titleComponent).toEqual(isArray);
expect(titleComponent).toHaveLength(1);
- titleComponent.forEach(title => {
+ titleComponent.forEach((title: Element) => {
expect(title).toEqual(expect.objectContaining({ type: 'title' }));
});
@@ -83,12 +66,9 @@ describe('server', () => {
});
it('renders title with itemprop name as React component', () => {
- const context = {};
- render(
- ,
- context
+ const head = renderContext(
+
);
- const head = context.helmet;
expect(head.title).toBeDefined();
expect(head.title.toComponent).toBeDefined();
@@ -98,7 +78,7 @@ describe('server', () => {
expect(titleComponent).toEqual(isArray);
expect(titleComponent).toHaveLength(1);
- titleComponent.forEach(title => {
+ titleComponent.forEach((title: Element) => {
expect(title).toEqual(expect.objectContaining({ type: 'title' }));
});
@@ -108,10 +88,7 @@ describe('server', () => {
});
it('renders title tag as string', () => {
- const context = {};
- render( include'} />, context);
-
- const head = context.helmet;
+ const head = renderContext( );
expect(head.title).toBeDefined();
expect(head.title.toString).toBeDefined();
@@ -119,34 +96,27 @@ describe('server', () => {
});
it('renders title with itemprop name as string', () => {
- const context = {};
- render(
- ,
- context
+ const head = renderContext(
+
);
- const head = context.helmet;
-
expect(head.title).toBeDefined();
expect(head.title.toString).toBeDefined();
const titleString = head.title.toString();
+
expect(titleString).toMatchSnapshot();
});
it('does not encode all characters with HTML character entity equivalents', () => {
- const context = {};
const chineseTitle = '膣膗 鍆錌雔';
- render(
+ const head = renderContext(
-
,
- context
+
);
- const head = context.helmet;
-
expect(head.title).toBeDefined();
expect(head.title.toString).toBeDefined();
expect(head.title.toString()).toMatchSnapshot();
@@ -155,49 +125,36 @@ describe('server', () => {
describe('Declarative API', () => {
it('encodes special characters in title', () => {
- const context = {};
- render(
+ const head = renderContext(
{`Dangerous