Showing preview only (339K chars total). Download the full file or copy to clipboard to get everything.
Repository: nfl/react-helmet
Branch: master
Commit: 1b57ddb1524b
Files: 21
Total size: 328.4 KB
Directory structure:
gitextract_v89gdlt3/
├── .babelrc
├── .editorconfig
├── .eslintrc
├── .gitignore
├── .npmignore
├── .nvmrc
├── .travis.yml
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── ISSUE_TEMPLATE.md
├── LICENSE
├── README.md
├── karma.config.js
├── package.json
├── rollup.config.js
├── src/
│ ├── Helmet.js
│ ├── HelmetConstants.js
│ └── HelmetUtils.js
└── test/
├── HelmetDeclarativeTest.js
└── HelmetTest.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .babelrc
================================================
{
"presets": [
["env", {
"targets": {
"browsers": ["last 1 versions", "ie >= 10"]
},
"modules": false,
"loose": true
}],
"react"
],
"plugins": [
"transform-class-properties",
"transform-object-rest-spread",
"external-helpers"
]
}
================================================
FILE: .editorconfig
================================================
# editorconfig.org
root = true
[*]
indent_style = space
indent_size = 4
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[package.json]
indent_style = space
indent_size = 2
[.travis.yml]
indent_style = space
indent_size = 2
================================================
FILE: .eslintrc
================================================
{
"extends": ["nfl", "prettier"],
"env": {
"browser": true,
"mocha": true,
"es6": true
},
"globals": {
"expect": false,
"sinon": false
},
"plugins": [
"prettier"
],
"rules": {
"prettier/prettier": ["error", {
"printWidth": 80,
"tabWidth": 4,
"singleQuote": false,
"trailingComma": "none",
"bracketSpacing": false,
"semi": true,
"useTabs": false,
"parser": "babylon",
"jsxBracketSameLine": false
}
]
}
}
================================================
FILE: .gitignore
================================================
.DS_Store
.eslintcache
.idea
coverage
es
lib
node_modules
npm-debug.log
================================================
FILE: .npmignore
================================================
.arcconfig
.babelrc
.editorconfig
.eslintrc
.nvmrc
*.yml
CHANGELOG.md
CONTRIBUTING.md
coverage
karma.config.js
test/
node_modules
src
travis.yml
rollup.config.js
================================================
FILE: .nvmrc
================================================
v6.6.0
================================================
FILE: .travis.yml
================================================
language: node_js
services:
- xvfb
addons:
apt:
packages:
- google-chrome-stable
node_js:
- "node"
- 12
- 11
- 10
- 8
before_install:
- export CHROME_BIN=/usr/bin/google-chrome
cache:
yarn: true
directories:
- node_modules
after_success: codecov
================================================
FILE: CHANGELOG.md
================================================
<a name="6.1.0"></a>
# [6.1.0](https://github.com/nfl/react-helmet/compare/6.0.0...6.1.0) (2020-06-08)
### Features
- Re-add Helmet as a default export [(#547)](https://github.com/nfl/react-helmet/pull/547)
- Note: You may now choose `import Helmet` or `import {Helmet}`
- Updates react-fast-compare to get support for Preact [(#559)](https://github.com/nfl/react-helmet/pull/559)
<a name="6.0.0"></a>
# [6.0.0](https://github.com/nfl/react-helmet/compare/5.2.0...6.0.0) (2020-04-09)
### Features
- Bundle with Rollup instead of Webpack - As a result, the default export was removed and Helmet must now be imported as a named component - `import {Helmet} from "react-helmet"` [(#395)](https://github.com/nfl/react-helmet/pull/395)
- Replace deepEqual with isEqual [(#402)](https://github.com/nfl/react-helmet/pull/402)
<a name="6.0.0-beta.2"></a>
# [6.0.0-beta.2](https://github.com/nfl/react-helmet/compare/6.0.0-beta...6.0.0-beta.2) (2020-01-27)
<a name="6.0.0-beta"></a>
# [6.0.0-beta](https://github.com/nfl/react-helmet/compare/5.2.0...6.0.0-beta) (2018-12-07)
<a name="5.2.0"></a>
# [5.2.0](https://github.com/nfl/react-helmet/compare/5.1.3...5.2.0) (2017-08-29)
### Features
- Adds support for synchronously updated tags
([#297](https://github.com/nfl/react-helmet/pull/297))
([6a3d3bf](https://github.com/nfl/react-helmet/commit/6a3d3bf)), closes [#291](https://github.com/nfl/react-helmet/issues/291)
### Bug Fixes
- Remove unexpected comma in server-rendered title ([#289](https://github.com/nfl/react-helmet/pull/289)) ([66b8212](https://github.com/nfl/react-helmet/commit/66b8212)), closes [#286](https://github.com/nfl/react-helmet/issues/286)
- Replace requestIdleCallback with requestAnimationFrame for a more consistent DOM write cycle. ([#307](https://github.com/nfl/react-helmet/issues/307)) ([a2323ad](https://github.com/nfl/react-helmet/commit/a2323ad))
<a name="5.1.3"></a>
## [5.1.3](https://github.com/nfl/react-helmet/compare/5.0.3...5.1.3) (2017-05-18)
### Bug Fixes
- Add support for renderable Arrays of strings ([#275](https://github.com/nfl/react-helmet/issues/275)) ([aad5457](https://github.com/nfl/react-helmet/commit/aad5457)), closes [#272](https://github.com/nfl/react-helmet/issues/272)
<a name="5.0.3"></a>
## [5.0.3](https://github.com/nfl/react-helmet/compare/5.0.2...5.0.3) (2017-04-10)
### Bug Fixes
- React.propTypes -> PropTypes ([#265](https://github.com/nfl/react-helmet/issues/265)) ([68ece0c](https://github.com/nfl/react-helmet/commit/68ece0c))
<a name="5.0.2"></a>
## [5.0.2](https://github.com/nfl/react-helmet/compare/5.0.1...5.0.2) (2017-03-28)
### Bug Fixes
- prevent clearing existing title ([#259](https://github.com/nfl/react-helmet/issues/259)) ([549b603](https://github.com/nfl/react-helmet/commit/549b603))
<a name="5.0.1"></a>
## [5.0.1](https://github.com/nfl/react-helmet/compare/5.0.0...5.0.1) (2017-03-24)
### Bug Fixes
- handle falsy children values ([#256](https://github.com/nfl/react-helmet/issues/256)) ([4a60765](https://github.com/nfl/react-helmet/commit/4a60765))
<a name="5.0.0"></a>
# [5.0.0](https://github.com/nfl/react-helmet/compare/4.0.0...5.0.0) (2017-03-21)
### Features
- New Simplified API (fully backward-compatible) - Helmet now takes plain HTML tags for the majority of the API with just a few remaining props for Helmet - retaining `titleTemplate`, `defaultTitle`, `onChangeClientState`, and one new - `encodeSpecialCharacters` - refer to README for details. Directly passing Helmet props will be deprecated in the future. [(#246)](https://github.com/nfl/react-helmet/pull/246)
- `requestIdleCallback` utilized to consolidate DOM changes and makes these non-blocking for things like animations. Fixes first client-side render not matching server-side render. Maintains one DOM change between route changes on the client-side as well. [(#248)](https://github.com/nfl/react-helmet/pull/248)
- On server-side, `Helmet.renderStatic()` aliased to `Helmet.rewind()` for more clarity. `rewind` will be deprecated in the future.
- Yarn support
# 4.0.0
### Features
- Replacing PlainComponent with stateless functional component `NullComponent`, with a hard requirement to use React 15 or higher.
# 3.3.2
### Bugfixes
- Removed stateless functional component `NullComponent` because of it's incompatibility with React 14 and reverted back to PlainComponent.
# 3.3.1 [BROKEN]
### Bugfixes
- README updates - npm badge and helmet image
- Bump react-side-effect to 1.1.0
- Removing PlainComponent, replaced with NullComponent defined within Helmet.js
- Refactored code - cut lines of code and used `reduce` to simplify functions
- Replaced PlainComponent with NullComponent (now within Helmet.js)
# 3.3.0
### Features
- `itemprop` available in meta tags
- New API - `titleAttributes` to add attributes to the title tag
- `class` support for html tag
# 3.2.3
### Bugfixes
- applied previous fix (undefined value for primary attribute key) to base tag
- fix htmlAttributes fallback value when calling rewind() on blank Helmet
- Removed unneeded dependencies - shallowequal, warning
- babel configuration moved into .babelrc
- eslint configuration moved into .eslintrc
# 3.2.2
### Bugfixes
- Removed breaking changes `jsnext:main` and `module` from package.json. `es` version required special babel configuration for end users and needs to be re-thought for major release.
- Reverted `canUseDOM` setter in `Helmet.js`, as this was a breaking change for some users.
- [fix] runtime error when providing undefined value for primary attribute key (applies to meta, link, script, noscript, style)
# 3.2.1 [BROKEN]
### Bugfixes
- Removing "engines" field in package.json.
# 3.2.0 [BROKEN]
### Features
- `<noscript>` support
### Bugfixes
- Prevent stripping dollar signs from title when titleTemplate is present
- Offering jsnext:main build
- Removed Gulp Dependency
- Bump Dependencies
- IE8 Support
# 3.1.0
### Features
- Add support for `<style>` elements.
# 3.0.2
### Bugfixes
- Avoids rendering "undefined" if it's passed in as a value of an attribute, but instead renders just the attribute name.
- When htmlAttributes gets cleared, or is blank, the helmet attribute on the html tag, used for tracking, is cleaned up.
- Upgrading devDependency of React to 15.
# 3.0.1
### Bugfixes
- The htmlAttributes feature will no longer remove existing attributes on the HTML tag
# 3.0.0
### Features
- innerHTML for scripts. Originally added to support the use of JSON-LD (https://developers.google.com/schemas/formats/json-ld?hl=en), but this can be used for any inline scripts you would like in your document head.
- New htmlAttributes prop which allows users to add attributes to their html tag.
- New defaultTitle prop which allows users to have a fallback title in the scenario where a Helmet wants to define a titleTemplate for it's nested routes, but not for itself (for example, at the root component level). See README for use cases.
### Bugfixes
- Removed all polyfills from Helmet. Due to reported conflicts, to remove bloat, and to encourage users to polyfill at the application level. Please double-check that you weren't relying solely on Helmet for polyfilling certain features.
# 2.3.1
### Bugfixes
- Fallback values for rewind on the server threw a `tags.map` error in Node. Changing the tag default values to `[]` fixes it.
# 2.3.0
### Bugfixes
- FOUC fix - existing tags that persist between route changes, will not be removed and re-added to the DOM. They will remain unchanged. This will avoid, in particular, stylesheets being removed and re-added causing an unstyled flash when the new Helmet is rendered.
- onChangeClientState enhanced to also return the html tags that were added and removed.
- provide fallback object for rewind() result - If no Helmets are rendered, rewind() will still return head.base, head.title, etc.
- Tag attributes ordering does not matter. It no longer looks at the first valid attribute to identify the tag. All attributes of the tag will be searched for names that can be found in HelmetConstants.js. When rel="canonical" is included, it will take priority over href.
- Bump dependencies
# 2.2.0
### Features
- New prop `onChangeClientState` to set a callback function that is called in the event the DOM is changed by Helmet. When set on a Helmet, it will apply to all subsequent Helmet instances downstream (similar to titleTemplate).
### Bugfixes
- Fix for double encoding when returning state to the server as React components.
- dist -> lib
- Added CLA url to CONTRIBUTING.
- Added .babelrc to .npmignore (fix for now, as the settings were not compatible with Babel 6)
- Bump dependencies (except Babel 6 as a dev dependency - coming soon)
# 2.1.1
### Bugfixes
- Remove npm he dependency
- HTML entitiy encode only special characters instead of all characters that have HTML entity equivalents
# 2.1.0
### Features
- All head attributes (title / base / meta / link / script) returned with `.toComponent()` and `.toString()` methods to use in any scenario when calling rewind on the server.
- Helmet using React 14 for unit testing.
### Bugfixes
- Bump dependencies
# 2.0.0
### Features
- Base tag support.
- Script tag support.
- All head attributes (title / base / meta / link / script) returned as React components on the server with toString() support
- Removed ability to nest children in Helmet.
- Decorated component from react-side-effect, now wrapped by Helmet in order to enforce deep equal check on shouldComponentUpdate. This will limit unnecessary DOM changes and rendering.
### Bugfixes
- Bump dependencies
# 1.1.5
### Bugfixes
- Adding webpack under devDependencies, as it's no longer automatically installed as a peer dependency
- Bump dependencies
# 1.1.4
### Bugfixes
- Bumping to react-side-effect 1.0.2
- Updating peer dependences for react 0.14.0-rc1
- Bump dependencies
# 1.1.3
### Bugfixes
- Externalize react-side-effect
- shouldComponentUpdate compares props with deep equal
- handleClientStateChange compares DOM changes with deep equal to prevent unnecessary DOM changes
- Warning users to not nest children in their Helmet components. We found that when Helmet contains children, those children are part of the props that are compared in shouldComponentUpdate. And this causes unnecessary renders as the props are always different, even with the same Helmet title/meta/link props.
- Adding react-helmet-example to README
- Bumping to react-side-effect 1.0.1
- Bump dependencies.
# 1.1.2
### Bugfixes
- Use named exports in HelmetConstants
- Allow all React 0.14 betas in peer dependencies
- Bump dependencies.
- Fixed invariant check in CreateSideEffect
# 1.1.1
### Bugfixes
- Externalizing of React helpers - exenv, invariant, shallowequal
- Using ES6 collections to manage tags
- Bumping peer dependency for use in React 0.14.0-beta.
- Title encoded when being rendered on the server
- Import a smaller subset of core-js modules instead of the whole shim.
# 1.1.0
### Features
- titleTemplate attribute to help format `document.title`
### Bugfixes
- Bump dependencies.
- Title will never be overwritten with blank title. Lifts constraint where every component with Helmet needed to define a title.
- Re-organization of unit tests.
# 1.0.1
### Bugfixes
- Bump dependencies
- rewind() saves title/meta/link values before disposing
- Typo in README - use Helmet.rewind()
- "he" package added to dependencies
- Added Travis CI integration
- npm requirement removed - removed reference in README (System Requirements) and in package.json (engines)
# 1.0.0
### Features
- Initial release
================================================
FILE: CODE_OF_CONDUCT.md
================================================
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, sex characteristics, gender identity and expression,
level of experience, education, socio-economic status, nationality, personal
appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at engineers@nfl.com. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see
https://www.contributor-covenant.org/faq
================================================
FILE: CONTRIBUTING.md
================================================
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*
- [Contributing to this project](#contributing-to-this-project)
- [Pull requests](#pull-requests)
- [Development Process](#development-process)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
# Contributing to this project
Please take a moment to review this document in order to make the contribution
process easy and effective for everyone involved.
_**Please Note:** These guidelines are adapted from [@necolas](https://github.com/necolas)'s
[issue-guidelines](https://github.com/necolas/issue-guidelines) and serve as
an excellent starting point for contributing to any open source project._
<a name="pull-requests"></a>
## Pull requests
Good pull requests - patches, improvements, new features - are a fantastic
help. They should remain focused in scope and avoid containing unrelated
commits.
**Please ask first** before embarking on any significant pull request (e.g.
implementing features, refactoring code, porting to a different language),
otherwise you risk spending a lot of time working on something that the
project's developers might not want to merge into the project.
Make sure to sign the CLA - https://cla-assistant.io/nfl/react-helmet along with your PR submission.
<a name="development"></a>
## Development Process
Here are some guidelines to making changes and preparing your PR:
1. Make your proposed changes to the repository, along with updating/adding test cases.
2. (Optional) If you prefer to also test your changes in a real application, you can do the following:
1. Run `npm link` in `react-helmet` repository.
2. `cd` to your favorite React application, run `npm link react-helmet` to point to your local repository.
3. Run your application to verify your changes.
3. Run `npm test` to verify all test cases pass.
4. Run `npm run lint` to verify there are no linting errors.
================================================
FILE: ISSUE_TEMPLATE.md
================================================
**Do you want to request a *feature* or report a *bug*?**
**What is the current behavior?**
**If the current behavior is a bug,
please provide the steps to reproduce and if
possible a minimal demo of the problem.
Your bug will get fixed much faster if we can run your
code and it doesn't have dependencies other than React and react-helmet.
Paste the link to your JSFiddle (https://jsfiddle.net/Luktwrdm/) or
CodeSandbox (https://codesandbox.io/s/new) example below:**
**What is the expected behavior?**
**Which versions of React and react-helmet, and which browser / OS are affected by this issue?
Did this work in previous versions of React and/or react-helmet?**
================================================
FILE: LICENSE
================================================
Copyright (c) 2015 NFL
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
<img align="right" width="200" src="http://static.nfl.com/static/content/public/static/img/logos/react-helmet.jpg" />
# React Helmet
[](https://www.npmjs.org/package/react-helmet)
[](https://codecov.io/github/nfl/react-helmet?branch=master)
[](https://travis-ci.org/nfl/react-helmet)
[](https://david-dm.org/nfl/react-helmet)
[](CONTRIBUTING.md#pull-requests)
This reusable React component will manage all of your changes to the document head.
Helmet _takes_ plain HTML tags and _outputs_ plain HTML tags. It's dead simple, and React beginner friendly.
## [6.1.0 Major Changes](https://github.com/nfl/react-helmet/releases/tag/6.1.0)
## Example
```javascript
import React from "react";
import {Helmet} from "react-helmet";
class Application extends React.Component {
render () {
return (
<div className="application">
<Helmet>
<meta charSet="utf-8" />
<title>My Title</title>
<link rel="canonical" href="http://mysite.com/example" />
</Helmet>
...
</div>
);
}
};
```
Nested or latter components will override duplicate changes:
```javascript
<Parent>
<Helmet>
<title>My Title</title>
<meta name="description" content="Helmet application" />
</Helmet>
<Child>
<Helmet>
<title>Nested Title</title>
<meta name="description" content="Nested component" />
</Helmet>
</Child>
</Parent>
```
outputs:
```html
<head>
<title>Nested Title</title>
<meta name="description" content="Nested component">
</head>
```
See below for a full reference guide.
## Features
- Supports all valid head tags: `title`, `base`, `meta`, `link`, `script`, `noscript`, and `style` tags.
- Supports attributes for `body`, `html` and `title` tags.
- Supports server-side rendering.
- Nested components override duplicate head changes.
- Duplicate head changes are preserved when specified in the same component (support for tags like "apple-touch-icon").
- Callback for tracking DOM changes.
## Compatibility
Helmet 5 is fully backward-compatible with previous Helmet releases, so you can upgrade at any time without fear of breaking changes. We encourage you to update your code to our more semantic API, but please feel free to do so at your own pace.
## Installation
Yarn:
```bash
yarn add react-helmet
```
npm:
```bash
npm install --save react-helmet
```
## Server Usage
To use on the server, call `Helmet.renderStatic()` after `ReactDOMServer.renderToString` or `ReactDOMServer.renderToStaticMarkup` to get the head data for use in your prerender.
Because this component keeps track of mounted instances, **you have to make sure to call `renderStatic` on server**, or you'll get a memory leak.
```javascript
ReactDOMServer.renderToString(<Handler />);
const helmet = Helmet.renderStatic();
```
This `helmet` instance contains the following properties:
- `base`
- `bodyAttributes`
- `htmlAttributes`
- `link`
- `meta`
- `noscript`
- `script`
- `style`
- `title`
Each property contains `toComponent()` and `toString()` methods. Use whichever is appropriate for your environment. For attributes, use the JSX spread operator on the object returned by `toComponent()`. E.g:
### As string output
```javascript
const html = `
<!doctype html>
<html ${helmet.htmlAttributes.toString()}>
<head>
${helmet.title.toString()}
${helmet.meta.toString()}
${helmet.link.toString()}
</head>
<body ${helmet.bodyAttributes.toString()}>
<div id="content">
// React stuff here
</div>
</body>
</html>
`;
```
### As React components
```javascript
function HTML () {
const htmlAttrs = helmet.htmlAttributes.toComponent();
const bodyAttrs = helmet.bodyAttributes.toComponent();
return (
<html {...htmlAttrs}>
<head>
{helmet.title.toComponent()}
{helmet.meta.toComponent()}
{helmet.link.toComponent()}
</head>
<body {...bodyAttrs}>
<div id="content">
// React stuff here
</div>
</body>
</html>
);
}
```
### Note: Use the same instance
If you are using a prebuilt compilation of your app with webpack in the server be sure to include this in the `webpack file` so that the same instance of `react-helmet` is used.
```
externals: ["react-helmet"],
```
Or to import the *react-helmet* instance from the app on the server.
### Reference Guide
```javascript
<Helmet
{/* (optional) set to false to disable string encoding (server-only) */}
encodeSpecialCharacters={true}
{/*
(optional) Useful when you want titles to inherit from a template:
<Helmet
titleTemplate="%s | MyAwesomeWebsite.com"
>
<title>Nested Title</title>
</Helmet>
outputs:
<head>
<title>Nested Title | MyAwesomeWebsite.com</title>
</head>
*/}
titleTemplate="MySite.com - %s"
{/*
(optional) used as a fallback when a template exists but a title is not defined
<Helmet
defaultTitle="My Site"
titleTemplate="My Site - %s"
/>
outputs:
<head>
<title>My Site</title>
</head>
*/}
defaultTitle="My Default Title"
{/* (optional) set to false to not use requestAnimationFrame and instead update the DOM as soon as possible.
Useful if you want to update the title when the tab is out of focus
*/}
defer={false}
{/* (optional) callback that tracks DOM changes */}
onChangeClientState={(newState, addedTags, removedTags) => console.log(newState, addedTags, removedTags)}
>
{/* html attributes */}
<html lang="en" amp />
{/* body attributes */}
<body className="root" />
{/* title attributes and value */}
<title itemProp="name" lang="en">My Plain Title or {`dynamic`} title</title>
{/* base element */}
<base target="_blank" href="http://mysite.com/" />
{/* multiple meta elements */}
<meta name="description" content="Helmet application" />
<meta property="og:type" content="article" />
{/* multiple link elements */}
<link rel="canonical" href="http://mysite.com/example" />
<link rel="apple-touch-icon" href="http://mysite.com/img/apple-touch-icon-57x57.png" />
<link rel="apple-touch-icon" sizes="72x72" href="http://mysite.com/img/apple-touch-icon-72x72.png" />
{locales.map((locale) => {
<link rel="alternate" href="http://example.com/{locale}" hrefLang={locale} key={locale}/>
})}
{/* multiple script elements */}
<script src="http://include.com/pathtojs.js" type="text/javascript" />
{/* inline script elements */}
<script type="application/ld+json">{`
{
"@context": "http://schema.org"
}
`}</script>
{/* noscript elements */}
<noscript>{`
<link rel="stylesheet" type="text/css" href="foo.css" />
`}</noscript>
{/* inline style elements */}
<style type="text/css">{`
body {
background-color: blue;
}
p {
font-size: 12px;
}
`}</style>
</Helmet>
```
## Contributing to this project
Please take a moment to review the [guidelines for contributing](CONTRIBUTING.md).
* [Pull requests](CONTRIBUTING.md#pull-requests)
* [Development Process](CONTRIBUTING.md#development)
## License
MIT
<img align="left" height="200" src="http://static.nfl.com/static/content/public/static/img/logos/ENG_SigilLockup_4C_POS_RGB.png" />
================================================
FILE: karma.config.js
================================================
// Karma configuration
module.exports = function(config) {
function normalizationBrowserName(browser) {
return browser.toLowerCase().split(/[ /-]/)[0];
}
config.set({
// ... normal karma configuration
basePath: "",
// How long will Karma wait for a message from a browser before disconnecting from it (in ms).
browserNoActivityTimeout: 60000,
client: {
mocha: {
bail: true,
reporter: "html"
}
},
// frameworks to use
frameworks: ["chai-sinon", "mocha"],
files: ["./test/*.js"],
preprocessors: {
"./test/*.js": ["rollup", "sourcemap"]
},
coverageReporter: {
dir: "coverage/json",
includeAllSources: true,
reporters: [
{
type: "json",
subdir: normalizationBrowserName
}
]
},
rollupPreprocessor: {
output: {
format: "iife",
name: "helmet",
sourcemap: "inline"
},
plugins: [
require("rollup-plugin-replace")({
"process.env.NODE_ENV": "'development'"
}),
require("rollup-plugin-babel")({
exclude: "node_modules/**",
plugins: [
[
"istanbul",
{
exclude: ["**/node_modules/**", "**/test/**"]
}
]
]
}),
require("rollup-plugin-node-resolve")({
browser: true
}),
require("rollup-plugin-commonjs")()
]
},
// test results reporter to use
// possible values: "dots", "progress", "junit", "growl", "coverage"
reporters: ["coverage", "spec"],
// web server port
port: 9876,
// enable / disable colors in the output (reporters and logs)
colors: true,
// level of logging
// possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
logLevel: config.LOG_INFO,
// enable / disable watching file and executing tests whenever any file changes
autoWatch: true,
// Start these browsers, currently available:
// - Chrome
// - ChromeCanary
// - Firefox
// - Opera (has to be installed with `npm install karma-opera-launcher`)
// - Safari (only Mac; has to be installed with `npm install karma-safari-launcher`)
// - PhantomJS
// - IE (only Windows; has to be installed with `npm install karma-ie-launcher`)
browsers: process.env.TRAVIS ? ["ChromeTravis"] : ["Chrome", "Firefox"],
customLaunchers: {
ChromeTravis: {
base: "Chrome",
flags: ["--no-sandbox"]
}
},
// If browser does not capture in given timeout [ms], kill it
captureTimeout: 60000,
// Continuous Integration mode
// if true, it capture browsers, run tests and exit
singleRun: true
});
};
================================================
FILE: package.json
================================================
{
"name": "react-helmet",
"description": "A document head manager for React",
"version": "6.1.0",
"main": "./lib/Helmet.js",
"module": "./es/Helmet.js",
"author": "NFL <engineers@nfl.com>",
"contributors": [
"Chris Welch <chris.welch@nfl.com>",
"Robert dela Victoria <robert.delavictoria@nfl.com>"
],
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/nfl/react-helmet"
},
"keywords": [
"react-helmet",
"nfl",
"react",
"document",
"head",
"title",
"meta",
"link",
"script",
"base",
"noscript",
"style"
],
"peerDependencies": {
"react": ">=16.3.0"
},
"dependencies": {
"object-assign": "^4.1.1",
"prop-types": "^15.7.2",
"react-fast-compare": "^3.1.1",
"react-side-effect": "^2.1.0"
},
"devDependencies": {
"babel-core": "^6.24.0",
"babel-eslint": "^9.0.0",
"babel-plugin-external-helpers": "^6.22.0",
"babel-plugin-istanbul": "^4.0.0",
"babel-plugin-transform-class-properties": "^6.23.0",
"babel-plugin-transform-object-rest-spread": "^6.23.0",
"babel-preset-env": "^1.2.2",
"babel-preset-react": "^6.23.0",
"chai": "^3.5.0",
"codecov": "^3.6.5",
"conventional-changelog-cli": "^1.3.1",
"cz-conventional-changelog": "^2.0.0",
"eslint": "^3.18.0",
"eslint-config-nfl": "^11.1.0",
"eslint-config-prettier": "^6.11.0",
"eslint-plugin-import": "^2.2.0",
"eslint-plugin-jsx-a11y": "^6.2.3",
"eslint-plugin-mocha": "^4.9.0",
"eslint-plugin-prettier": "^2.1.2",
"eslint-plugin-react": "^6.10.2",
"istanbul": "^0.4.5",
"karma": "^1.5.0",
"karma-chai": "^0.1.0",
"karma-chai-sinon": "^0.1.5",
"karma-chrome-launcher": "^3.1.0",
"karma-coverage": "^1.1.1",
"karma-firefox-launcher": "^1.0.1",
"karma-html-reporter": "^0.2.7",
"karma-mocha": "^2.0.1",
"karma-rollup-preprocessor": "^6.1.0",
"karma-sourcemap-loader": "^0.3.7",
"karma-spec-reporter": "^0.0.32",
"karma-tap-reporter": "^0.0.6",
"mocha": "^7.2.0",
"prettier": "^1.4.4",
"react": "16.13.1",
"react-dom": "16.13.1",
"rimraf": "^3.0.2",
"rollup": "^0.67.0",
"rollup-plugin-babel": "^3.0.7",
"rollup-plugin-commonjs": "^9.2.0",
"rollup-plugin-node-resolve": "^3.4.0",
"rollup-plugin-replace": "^2.1.0",
"sinon": "^2.1.0",
"sinon-chai": "^2.8.0"
},
"scripts": {
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s",
"clean": "rimraf lib coverage es",
"lint": "eslint --ignore-path .gitignore --fix -- .",
"test": "karma start karma.config.js",
"posttest": "istanbul report lcov text",
"pretest": "npm run clean && npm run lint",
"commit": "git-cz",
"build": "rollup -c",
"prepublish": "npm run build"
},
"config": {
"commitizen": {
"path": "./node_modules/cz-conventional-changelog"
}
}
}
================================================
FILE: rollup.config.js
================================================
import babel from "rollup-plugin-babel";
import pkg from "./package.json";
const baseConfig = {
input: "src/Helmet.js",
plugins: [
babel({
exclude: "node_modules/**"
})
],
external: [
...Object.keys(pkg.dependencies),
...Object.keys(pkg.peerDependencies)
]
};
export default [
Object.assign(
{
output: {
file: pkg.main,
format: "cjs"
}
},
baseConfig
),
Object.assign(
{
output: {
file: "es/Helmet.js",
format: "esm"
}
},
baseConfig
)
];
================================================
FILE: src/Helmet.js
================================================
import React from "react";
import PropTypes from "prop-types";
import withSideEffect from "react-side-effect";
import isEqual from "react-fast-compare";
import {
convertReactPropstoHtmlAttributes,
handleClientStateChange,
mapStateOnServer,
reducePropsToState,
warn
} from "./HelmetUtils.js";
import {TAG_NAMES, VALID_TAG_NAMES} from "./HelmetConstants.js";
const Helmet = Component =>
class HelmetWrapper extends React.Component {
/**
* @param {Object} base: {"target": "_blank", "href": "http://mysite.com/"}
* @param {Object} bodyAttributes: {"className": "root"}
* @param {String} defaultTitle: "Default Title"
* @param {Boolean} defer: true
* @param {Boolean} encodeSpecialCharacters: true
* @param {Object} htmlAttributes: {"lang": "en", "amp": undefined}
* @param {Array} link: [{"rel": "canonical", "href": "http://mysite.com/example"}]
* @param {Array} meta: [{"name": "description", "content": "Test description"}]
* @param {Array} noscript: [{"innerHTML": "<img src='http://mysite.com/js/test.js'"}]
* @param {Function} onChangeClientState: "(newState) => console.log(newState)"
* @param {Array} script: [{"type": "text/javascript", "src": "http://mysite.com/js/test.js"}]
* @param {Array} style: [{"type": "text/css", "cssText": "div { display: block; color: blue; }"}]
* @param {String} title: "Title"
* @param {Object} titleAttributes: {"itemprop": "name"}
* @param {String} titleTemplate: "MySite.com - %s"
*/
static propTypes = {
base: PropTypes.object,
bodyAttributes: PropTypes.object,
children: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.node),
PropTypes.node
]),
defaultTitle: PropTypes.string,
defer: PropTypes.bool,
encodeSpecialCharacters: PropTypes.bool,
htmlAttributes: PropTypes.object,
link: PropTypes.arrayOf(PropTypes.object),
meta: PropTypes.arrayOf(PropTypes.object),
noscript: PropTypes.arrayOf(PropTypes.object),
onChangeClientState: PropTypes.func,
script: PropTypes.arrayOf(PropTypes.object),
style: PropTypes.arrayOf(PropTypes.object),
title: PropTypes.string,
titleAttributes: PropTypes.object,
titleTemplate: PropTypes.string
};
static defaultProps = {
defer: true,
encodeSpecialCharacters: true
};
// Component.peek comes from react-side-effect:
// For testing, you may use a static peek() method available on the returned component.
// It lets you get the current state without resetting the mounted instance stack.
// Don’t use it for anything other than testing.
static peek = Component.peek;
static rewind = () => {
let mappedState = Component.rewind();
if (!mappedState) {
// provide fallback if mappedState is undefined
mappedState = mapStateOnServer({
baseTag: [],
bodyAttributes: {},
encodeSpecialCharacters: true,
htmlAttributes: {},
linkTags: [],
metaTags: [],
noscriptTags: [],
scriptTags: [],
styleTags: [],
title: "",
titleAttributes: {}
});
}
return mappedState;
};
static set canUseDOM(canUseDOM) {
Component.canUseDOM = canUseDOM;
}
shouldComponentUpdate(nextProps) {
return !isEqual(this.props, nextProps);
}
mapNestedChildrenToProps(child, nestedChildren) {
if (!nestedChildren) {
return null;
}
switch (child.type) {
case TAG_NAMES.SCRIPT:
case TAG_NAMES.NOSCRIPT:
return {
innerHTML: nestedChildren
};
case TAG_NAMES.STYLE:
return {
cssText: nestedChildren
};
}
throw new Error(
`<${
child.type
} /> elements are self-closing and can not contain children. Refer to our API for more information.`
);
}
flattenArrayTypeChildren({
child,
arrayTypeChildren,
newChildProps,
nestedChildren
}) {
return {
...arrayTypeChildren,
[child.type]: [
...(arrayTypeChildren[child.type] || []),
{
...newChildProps,
...this.mapNestedChildrenToProps(child, nestedChildren)
}
]
};
}
mapObjectTypeChildren({
child,
newProps,
newChildProps,
nestedChildren
}) {
switch (child.type) {
case TAG_NAMES.TITLE:
return {
...newProps,
[child.type]: nestedChildren,
titleAttributes: {...newChildProps}
};
case TAG_NAMES.BODY:
return {
...newProps,
bodyAttributes: {...newChildProps}
};
case TAG_NAMES.HTML:
return {
...newProps,
htmlAttributes: {...newChildProps}
};
}
return {
...newProps,
[child.type]: {...newChildProps}
};
}
mapArrayTypeChildrenToProps(arrayTypeChildren, newProps) {
let newFlattenedProps = {...newProps};
Object.keys(arrayTypeChildren).forEach(arrayChildName => {
newFlattenedProps = {
...newFlattenedProps,
[arrayChildName]: arrayTypeChildren[arrayChildName]
};
});
return newFlattenedProps;
}
warnOnInvalidChildren(child, nestedChildren) {
if (process.env.NODE_ENV !== "production") {
if (!VALID_TAG_NAMES.some(name => child.type === name)) {
if (typeof child.type === "function") {
return warn(
`You may be attempting to nest <Helmet> components within each other, which is not allowed. Refer to our API for more information.`
);
}
return warn(
`Only elements types ${VALID_TAG_NAMES.join(
", "
)} are allowed. Helmet does not support rendering <${
child.type
}> elements. Refer to our API for more information.`
);
}
if (
nestedChildren &&
typeof nestedChildren !== "string" &&
(!Array.isArray(nestedChildren) ||
nestedChildren.some(
nestedChild => typeof nestedChild !== "string"
))
) {
throw new Error(
`Helmet expects a string as a child of <${
child.type
}>. Did you forget to wrap your children in braces? ( <${
child.type
}>{\`\`}</${
child.type
}> ) Refer to our API for more information.`
);
}
}
return true;
}
mapChildrenToProps(children, newProps) {
let arrayTypeChildren = {};
React.Children.forEach(children, child => {
if (!child || !child.props) {
return;
}
const {children: nestedChildren, ...childProps} = child.props;
const newChildProps = convertReactPropstoHtmlAttributes(
childProps
);
this.warnOnInvalidChildren(child, nestedChildren);
switch (child.type) {
case TAG_NAMES.LINK:
case TAG_NAMES.META:
case TAG_NAMES.NOSCRIPT:
case TAG_NAMES.SCRIPT:
case TAG_NAMES.STYLE:
arrayTypeChildren = this.flattenArrayTypeChildren({
child,
arrayTypeChildren,
newChildProps,
nestedChildren
});
break;
default:
newProps = this.mapObjectTypeChildren({
child,
newProps,
newChildProps,
nestedChildren
});
break;
}
});
newProps = this.mapArrayTypeChildrenToProps(
arrayTypeChildren,
newProps
);
return newProps;
}
render() {
const {children, ...props} = this.props;
let newProps = {...props};
if (children) {
newProps = this.mapChildrenToProps(children, newProps);
}
return <Component {...newProps} />;
}
};
const NullComponent = () => null;
const HelmetSideEffects = withSideEffect(
reducePropsToState,
handleClientStateChange,
mapStateOnServer
)(NullComponent);
const HelmetExport = Helmet(HelmetSideEffects);
HelmetExport.renderStatic = HelmetExport.rewind;
export {HelmetExport as Helmet};
export default HelmetExport;
================================================
FILE: src/HelmetConstants.js
================================================
export const ATTRIBUTE_NAMES = {
BODY: "bodyAttributes",
HTML: "htmlAttributes",
TITLE: "titleAttributes"
};
export const TAG_NAMES = {
BASE: "base",
BODY: "body",
HEAD: "head",
HTML: "html",
LINK: "link",
META: "meta",
NOSCRIPT: "noscript",
SCRIPT: "script",
STYLE: "style",
TITLE: "title"
};
export const VALID_TAG_NAMES = Object.values(TAG_NAMES);
export const TAG_PROPERTIES = {
CHARSET: "charset",
CSS_TEXT: "cssText",
HREF: "href",
HTTPEQUIV: "http-equiv",
INNER_HTML: "innerHTML",
ITEM_PROP: "itemprop",
NAME: "name",
PROPERTY: "property",
REL: "rel",
SRC: "src",
TARGET: "target"
};
export const REACT_TAG_MAP = {
accesskey: "accessKey",
charset: "charSet",
class: "className",
contenteditable: "contentEditable",
contextmenu: "contextMenu",
"http-equiv": "httpEquiv",
itemprop: "itemProp",
tabindex: "tabIndex"
};
export const HELMET_PROPS = {
DEFAULT_TITLE: "defaultTitle",
DEFER: "defer",
ENCODE_SPECIAL_CHARACTERS: "encodeSpecialCharacters",
ON_CHANGE_CLIENT_STATE: "onChangeClientState",
TITLE_TEMPLATE: "titleTemplate"
};
export const HTML_TAG_MAP = Object.keys(REACT_TAG_MAP).reduce((obj, key) => {
obj[REACT_TAG_MAP[key]] = key;
return obj;
}, {});
export const SELF_CLOSING_TAGS = [
TAG_NAMES.NOSCRIPT,
TAG_NAMES.SCRIPT,
TAG_NAMES.STYLE
];
export const HELMET_ATTRIBUTE = "data-react-helmet";
================================================
FILE: src/HelmetUtils.js
================================================
import React from "react";
import objectAssign from "object-assign";
import {
ATTRIBUTE_NAMES,
HELMET_ATTRIBUTE,
HELMET_PROPS,
HTML_TAG_MAP,
REACT_TAG_MAP,
SELF_CLOSING_TAGS,
TAG_NAMES,
TAG_PROPERTIES
} from "./HelmetConstants.js";
const encodeSpecialCharacters = (str, encode = true) => {
if (encode === false) {
return String(str);
}
return String(str)
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
};
const getTitleFromPropsList = propsList => {
const innermostTitle = getInnermostProperty(propsList, TAG_NAMES.TITLE);
const innermostTemplate = getInnermostProperty(
propsList,
HELMET_PROPS.TITLE_TEMPLATE
);
if (innermostTemplate && innermostTitle) {
// use function arg to avoid need to escape $ characters
return innermostTemplate.replace(
/%s/g,
() =>
Array.isArray(innermostTitle)
? innermostTitle.join("")
: innermostTitle
);
}
const innermostDefaultTitle = getInnermostProperty(
propsList,
HELMET_PROPS.DEFAULT_TITLE
);
return innermostTitle || innermostDefaultTitle || undefined;
};
const getOnChangeClientState = propsList => {
return (
getInnermostProperty(propsList, HELMET_PROPS.ON_CHANGE_CLIENT_STATE) ||
(() => {})
);
};
const getAttributesFromPropsList = (tagType, propsList) => {
return propsList
.filter(props => typeof props[tagType] !== "undefined")
.map(props => props[tagType])
.reduce((tagAttrs, current) => {
return {...tagAttrs, ...current};
}, {});
};
const getBaseTagFromPropsList = (primaryAttributes, propsList) => {
return propsList
.filter(props => typeof props[TAG_NAMES.BASE] !== "undefined")
.map(props => props[TAG_NAMES.BASE])
.reverse()
.reduce((innermostBaseTag, tag) => {
if (!innermostBaseTag.length) {
const keys = Object.keys(tag);
for (let i = 0; i < keys.length; i++) {
const attributeKey = keys[i];
const lowerCaseAttributeKey = attributeKey.toLowerCase();
if (
primaryAttributes.indexOf(lowerCaseAttributeKey) !==
-1 &&
tag[lowerCaseAttributeKey]
) {
return innermostBaseTag.concat(tag);
}
}
}
return innermostBaseTag;
}, []);
};
const getTagsFromPropsList = (tagName, primaryAttributes, propsList) => {
// Calculate list of tags, giving priority innermost component (end of the propslist)
const approvedSeenTags = {};
return propsList
.filter(props => {
if (Array.isArray(props[tagName])) {
return true;
}
if (typeof props[tagName] !== "undefined") {
warn(
`Helmet: ${tagName} should be of type "Array". Instead found type "${typeof props[
tagName
]}"`
);
}
return false;
})
.map(props => props[tagName])
.reverse()
.reduce((approvedTags, instanceTags) => {
const instanceSeenTags = {};
instanceTags
.filter(tag => {
let primaryAttributeKey;
const keys = Object.keys(tag);
for (let i = 0; i < keys.length; i++) {
const attributeKey = keys[i];
const lowerCaseAttributeKey = attributeKey.toLowerCase();
// Special rule with link tags, since rel and href are both primary tags, rel takes priority
if (
primaryAttributes.indexOf(lowerCaseAttributeKey) !==
-1 &&
!(
primaryAttributeKey === TAG_PROPERTIES.REL &&
tag[primaryAttributeKey].toLowerCase() ===
"canonical"
) &&
!(
lowerCaseAttributeKey === TAG_PROPERTIES.REL &&
tag[lowerCaseAttributeKey].toLowerCase() ===
"stylesheet"
)
) {
primaryAttributeKey = lowerCaseAttributeKey;
}
// Special case for innerHTML which doesn't work lowercased
if (
primaryAttributes.indexOf(attributeKey) !== -1 &&
(attributeKey === TAG_PROPERTIES.INNER_HTML ||
attributeKey === TAG_PROPERTIES.CSS_TEXT ||
attributeKey === TAG_PROPERTIES.ITEM_PROP)
) {
primaryAttributeKey = attributeKey;
}
}
if (!primaryAttributeKey || !tag[primaryAttributeKey]) {
return false;
}
const value = tag[primaryAttributeKey].toLowerCase();
if (!approvedSeenTags[primaryAttributeKey]) {
approvedSeenTags[primaryAttributeKey] = {};
}
if (!instanceSeenTags[primaryAttributeKey]) {
instanceSeenTags[primaryAttributeKey] = {};
}
if (!approvedSeenTags[primaryAttributeKey][value]) {
instanceSeenTags[primaryAttributeKey][value] = true;
return true;
}
return false;
})
.reverse()
.forEach(tag => approvedTags.push(tag));
// Update seen tags with tags from this instance
const keys = Object.keys(instanceSeenTags);
for (let i = 0; i < keys.length; i++) {
const attributeKey = keys[i];
const tagUnion = objectAssign(
{},
approvedSeenTags[attributeKey],
instanceSeenTags[attributeKey]
);
approvedSeenTags[attributeKey] = tagUnion;
}
return approvedTags;
}, [])
.reverse();
};
const getInnermostProperty = (propsList, property) => {
for (let i = propsList.length - 1; i >= 0; i--) {
const props = propsList[i];
if (props.hasOwnProperty(property)) {
return props[property];
}
}
return null;
};
const reducePropsToState = propsList => ({
baseTag: getBaseTagFromPropsList(
[TAG_PROPERTIES.HREF, TAG_PROPERTIES.TARGET],
propsList
),
bodyAttributes: getAttributesFromPropsList(ATTRIBUTE_NAMES.BODY, propsList),
defer: getInnermostProperty(propsList, HELMET_PROPS.DEFER),
encode: getInnermostProperty(
propsList,
HELMET_PROPS.ENCODE_SPECIAL_CHARACTERS
),
htmlAttributes: getAttributesFromPropsList(ATTRIBUTE_NAMES.HTML, propsList),
linkTags: getTagsFromPropsList(
TAG_NAMES.LINK,
[TAG_PROPERTIES.REL, TAG_PROPERTIES.HREF],
propsList
),
metaTags: getTagsFromPropsList(
TAG_NAMES.META,
[
TAG_PROPERTIES.NAME,
TAG_PROPERTIES.CHARSET,
TAG_PROPERTIES.HTTPEQUIV,
TAG_PROPERTIES.PROPERTY,
TAG_PROPERTIES.ITEM_PROP
],
propsList
),
noscriptTags: getTagsFromPropsList(
TAG_NAMES.NOSCRIPT,
[TAG_PROPERTIES.INNER_HTML],
propsList
),
onChangeClientState: getOnChangeClientState(propsList),
scriptTags: getTagsFromPropsList(
TAG_NAMES.SCRIPT,
[TAG_PROPERTIES.SRC, TAG_PROPERTIES.INNER_HTML],
propsList
),
styleTags: getTagsFromPropsList(
TAG_NAMES.STYLE,
[TAG_PROPERTIES.CSS_TEXT],
propsList
),
title: getTitleFromPropsList(propsList),
titleAttributes: getAttributesFromPropsList(
ATTRIBUTE_NAMES.TITLE,
propsList
)
});
const rafPolyfill = (() => {
let clock = Date.now();
return (callback: Function) => {
const currentTime = Date.now();
if (currentTime - clock > 16) {
clock = currentTime;
callback(currentTime);
} else {
setTimeout(() => {
rafPolyfill(callback);
}, 0);
}
};
})();
const cafPolyfill = (id: string | number) => clearTimeout(id);
const requestAnimationFrame =
typeof window !== "undefined"
? (window.requestAnimationFrame &&
window.requestAnimationFrame.bind(window)) ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
rafPolyfill
: global.requestAnimationFrame || rafPolyfill;
const cancelAnimationFrame =
typeof window !== "undefined"
? window.cancelAnimationFrame ||
window.webkitCancelAnimationFrame ||
window.mozCancelAnimationFrame ||
cafPolyfill
: global.cancelAnimationFrame || cafPolyfill;
const warn = msg => {
return console && typeof console.warn === "function" && console.warn(msg);
};
let _helmetCallback = null;
const handleClientStateChange = newState => {
if (_helmetCallback) {
cancelAnimationFrame(_helmetCallback);
}
if (newState.defer) {
_helmetCallback = requestAnimationFrame(() => {
commitTagChanges(newState, () => {
_helmetCallback = null;
});
});
} else {
commitTagChanges(newState);
_helmetCallback = null;
}
};
const commitTagChanges = (newState, cb) => {
const {
baseTag,
bodyAttributes,
htmlAttributes,
linkTags,
metaTags,
noscriptTags,
onChangeClientState,
scriptTags,
styleTags,
title,
titleAttributes
} = newState;
updateAttributes(TAG_NAMES.BODY, bodyAttributes);
updateAttributes(TAG_NAMES.HTML, htmlAttributes);
updateTitle(title, titleAttributes);
const tagUpdates = {
baseTag: updateTags(TAG_NAMES.BASE, baseTag),
linkTags: updateTags(TAG_NAMES.LINK, linkTags),
metaTags: updateTags(TAG_NAMES.META, metaTags),
noscriptTags: updateTags(TAG_NAMES.NOSCRIPT, noscriptTags),
scriptTags: updateTags(TAG_NAMES.SCRIPT, scriptTags),
styleTags: updateTags(TAG_NAMES.STYLE, styleTags)
};
const addedTags = {};
const removedTags = {};
Object.keys(tagUpdates).forEach(tagType => {
const {newTags, oldTags} = tagUpdates[tagType];
if (newTags.length) {
addedTags[tagType] = newTags;
}
if (oldTags.length) {
removedTags[tagType] = tagUpdates[tagType].oldTags;
}
});
cb && cb();
onChangeClientState(newState, addedTags, removedTags);
};
const flattenArray = possibleArray => {
return Array.isArray(possibleArray)
? possibleArray.join("")
: possibleArray;
};
const updateTitle = (title, attributes) => {
if (typeof title !== "undefined" && document.title !== title) {
document.title = flattenArray(title);
}
updateAttributes(TAG_NAMES.TITLE, attributes);
};
const updateAttributes = (tagName, attributes) => {
const elementTag = document.getElementsByTagName(tagName)[0];
if (!elementTag) {
return;
}
const helmetAttributeString = elementTag.getAttribute(HELMET_ATTRIBUTE);
const helmetAttributes = helmetAttributeString
? helmetAttributeString.split(",")
: [];
const attributesToRemove = [].concat(helmetAttributes);
const attributeKeys = Object.keys(attributes);
for (let i = 0; i < attributeKeys.length; i++) {
const attribute = attributeKeys[i];
const value = attributes[attribute] || "";
if (elementTag.getAttribute(attribute) !== value) {
elementTag.setAttribute(attribute, value);
}
if (helmetAttributes.indexOf(attribute) === -1) {
helmetAttributes.push(attribute);
}
const indexToSave = attributesToRemove.indexOf(attribute);
if (indexToSave !== -1) {
attributesToRemove.splice(indexToSave, 1);
}
}
for (let i = attributesToRemove.length - 1; i >= 0; i--) {
elementTag.removeAttribute(attributesToRemove[i]);
}
if (helmetAttributes.length === attributesToRemove.length) {
elementTag.removeAttribute(HELMET_ATTRIBUTE);
} else if (
elementTag.getAttribute(HELMET_ATTRIBUTE) !== attributeKeys.join(",")
) {
elementTag.setAttribute(HELMET_ATTRIBUTE, attributeKeys.join(","));
}
};
const updateTags = (type, tags) => {
const headElement = document.head || document.querySelector(TAG_NAMES.HEAD);
const tagNodes = headElement.querySelectorAll(
`${type}[${HELMET_ATTRIBUTE}]`
);
const oldTags = Array.prototype.slice.call(tagNodes);
const newTags = [];
let indexToDelete;
if (tags && tags.length) {
tags.forEach(tag => {
const newElement = document.createElement(type);
for (const attribute in tag) {
if (tag.hasOwnProperty(attribute)) {
if (attribute === TAG_PROPERTIES.INNER_HTML) {
newElement.innerHTML = tag.innerHTML;
} else if (attribute === TAG_PROPERTIES.CSS_TEXT) {
if (newElement.styleSheet) {
newElement.styleSheet.cssText = tag.cssText;
} else {
newElement.appendChild(
document.createTextNode(tag.cssText)
);
}
} else {
const value =
typeof tag[attribute] === "undefined"
? ""
: tag[attribute];
newElement.setAttribute(attribute, value);
}
}
}
newElement.setAttribute(HELMET_ATTRIBUTE, "true");
// Remove a duplicate tag from domTagstoRemove, so it isn't cleared.
if (
oldTags.some((existingTag, index) => {
indexToDelete = index;
return newElement.isEqualNode(existingTag);
})
) {
oldTags.splice(indexToDelete, 1);
} else {
newTags.push(newElement);
}
});
}
oldTags.forEach(tag => tag.parentNode.removeChild(tag));
newTags.forEach(tag => headElement.appendChild(tag));
return {
oldTags,
newTags
};
};
const generateElementAttributesAsString = attributes =>
Object.keys(attributes).reduce((str, key) => {
const attr =
typeof attributes[key] !== "undefined"
? `${key}="${attributes[key]}"`
: `${key}`;
return str ? `${str} ${attr}` : attr;
}, "");
const generateTitleAsString = (type, title, attributes, encode) => {
const attributeString = generateElementAttributesAsString(attributes);
const flattenedTitle = flattenArray(title);
return attributeString
? `<${type} ${HELMET_ATTRIBUTE}="true" ${attributeString}>${encodeSpecialCharacters(
flattenedTitle,
encode
)}</${type}>`
: `<${type} ${HELMET_ATTRIBUTE}="true">${encodeSpecialCharacters(
flattenedTitle,
encode
)}</${type}>`;
};
const generateTagsAsString = (type, tags, encode) =>
tags.reduce((str, tag) => {
const attributeHtml = Object.keys(tag)
.filter(
attribute =>
!(
attribute === TAG_PROPERTIES.INNER_HTML ||
attribute === TAG_PROPERTIES.CSS_TEXT
)
)
.reduce((string, attribute) => {
const attr =
typeof tag[attribute] === "undefined"
? attribute
: `${attribute}="${encodeSpecialCharacters(
tag[attribute],
encode
)}"`;
return string ? `${string} ${attr}` : attr;
}, "");
const tagContent = tag.innerHTML || tag.cssText || "";
const isSelfClosing = SELF_CLOSING_TAGS.indexOf(type) === -1;
return `${str}<${type} ${HELMET_ATTRIBUTE}="true" ${attributeHtml}${
isSelfClosing ? `/>` : `>${tagContent}</${type}>`
}`;
}, "");
const convertElementAttributestoReactProps = (attributes, initProps = {}) => {
return Object.keys(attributes).reduce((obj, key) => {
obj[REACT_TAG_MAP[key] || key] = attributes[key];
return obj;
}, initProps);
};
const convertReactPropstoHtmlAttributes = (props, initAttributes = {}) => {
return Object.keys(props).reduce((obj, key) => {
obj[HTML_TAG_MAP[key] || key] = props[key];
return obj;
}, initAttributes);
};
const generateTitleAsReactComponent = (type, title, attributes) => {
// assigning into an array to define toString function on it
const initProps = {
key: title,
[HELMET_ATTRIBUTE]: true
};
const props = convertElementAttributestoReactProps(attributes, initProps);
return [React.createElement(TAG_NAMES.TITLE, props, title)];
};
const generateTagsAsReactComponent = (type, tags) =>
tags.map((tag, i) => {
const mappedTag = {
key: i,
[HELMET_ATTRIBUTE]: true
};
Object.keys(tag).forEach(attribute => {
const mappedAttribute = REACT_TAG_MAP[attribute] || attribute;
if (
mappedAttribute === TAG_PROPERTIES.INNER_HTML ||
mappedAttribute === TAG_PROPERTIES.CSS_TEXT
) {
const content = tag.innerHTML || tag.cssText;
mappedTag.dangerouslySetInnerHTML = {__html: content};
} else {
mappedTag[mappedAttribute] = tag[attribute];
}
});
return React.createElement(type, mappedTag);
});
const getMethodsForTag = (type, tags, encode) => {
switch (type) {
case TAG_NAMES.TITLE:
return {
toComponent: () =>
generateTitleAsReactComponent(
type,
tags.title,
tags.titleAttributes,
encode
),
toString: () =>
generateTitleAsString(
type,
tags.title,
tags.titleAttributes,
encode
)
};
case ATTRIBUTE_NAMES.BODY:
case ATTRIBUTE_NAMES.HTML:
return {
toComponent: () => convertElementAttributestoReactProps(tags),
toString: () => generateElementAttributesAsString(tags)
};
default:
return {
toComponent: () => generateTagsAsReactComponent(type, tags),
toString: () => generateTagsAsString(type, tags, encode)
};
}
};
const mapStateOnServer = ({
baseTag,
bodyAttributes,
encode,
htmlAttributes,
linkTags,
metaTags,
noscriptTags,
scriptTags,
styleTags,
title = "",
titleAttributes
}) => ({
base: getMethodsForTag(TAG_NAMES.BASE, baseTag, encode),
bodyAttributes: getMethodsForTag(
ATTRIBUTE_NAMES.BODY,
bodyAttributes,
encode
),
htmlAttributes: getMethodsForTag(
ATTRIBUTE_NAMES.HTML,
htmlAttributes,
encode
),
link: getMethodsForTag(TAG_NAMES.LINK, linkTags, encode),
meta: getMethodsForTag(TAG_NAMES.META, metaTags, encode),
noscript: getMethodsForTag(TAG_NAMES.NOSCRIPT, noscriptTags, encode),
script: getMethodsForTag(TAG_NAMES.SCRIPT, scriptTags, encode),
style: getMethodsForTag(TAG_NAMES.STYLE, styleTags, encode),
title: getMethodsForTag(TAG_NAMES.TITLE, {title, titleAttributes}, encode)
});
export {convertReactPropstoHtmlAttributes};
export {handleClientStateChange};
export {mapStateOnServer};
export {reducePropsToState};
export {requestAnimationFrame};
export {warn};
================================================
FILE: test/HelmetDeclarativeTest.js
================================================
/* eslint max-nested-callbacks: [1, 7] */
/* eslint-disable react/jsx-sort-props */
/* eslint-disable jsx-a11y/html-has-lang */
import React from "react";
import ReactDOM from "react-dom";
import ReactServer from "react-dom/server";
import {Helmet} from "../src/Helmet";
import {HTML_TAG_MAP} from "../src/HelmetConstants";
import {requestAnimationFrame} from "../src/HelmetUtils.js";
const HELMET_ATTRIBUTE = "data-react-helmet";
describe("Helmet - Declarative API", () => {
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 => {
ReactDOM.render(
<Helmet defaultTitle={"Fallback"}>
<title>Test Title</title>
</Helmet>,
container
);
requestAnimationFrame(() => {
expect(document.title).to.equal("Test Title");
done();
});
});
it("updates page title and allows children containing expressions", done => {
const someValue = "Some Great Title";
ReactDOM.render(
<Helmet>
<title>Title: {someValue}</title>
</Helmet>,
container
);
requestAnimationFrame(() => {
expect(document.title).to.equal("Title: Some Great Title");
done();
});
});
it("updates page title with multiple children", done => {
ReactDOM.render(
<div>
<Helmet>
<title>Test Title</title>
</Helmet>
<Helmet>
<title>Child One Title</title>
</Helmet>
<Helmet>
<title>Child Two Title</title>
</Helmet>
</div>,
container
);
requestAnimationFrame(() => {
expect(document.title).to.equal("Child Two Title");
done();
});
});
it("sets title based on deepest nested component", done => {
ReactDOM.render(
<div>
<Helmet>
<title>Main Title</title>
</Helmet>
<Helmet>
<title>Nested Title</title>
</Helmet>
</div>,
container
);
requestAnimationFrame(() => {
expect(document.title).to.equal("Nested Title");
done();
});
});
it("sets title using deepest nested component with a defined title", done => {
ReactDOM.render(
<div>
<Helmet>
<title>Main Title</title>
</Helmet>
<Helmet />
</div>,
container
);
requestAnimationFrame(() => {
expect(document.title).to.equal("Main Title");
done();
});
});
it("uses defaultTitle if no title is defined", done => {
ReactDOM.render(
<Helmet
defaultTitle={"Fallback"}
titleTemplate={
"This is a %s of the titleTemplate feature"
}
>
<title />
</Helmet>,
container
);
requestAnimationFrame(() => {
expect(document.title).to.equal("Fallback");
done();
});
});
it("uses a titleTemplate if defined", done => {
ReactDOM.render(
<Helmet
defaultTitle={"Fallback"}
titleTemplate={
"This is a %s of the titleTemplate feature"
}
>
<title>Test</title>
</Helmet>,
container
);
requestAnimationFrame(() => {
expect(document.title).to.equal(
"This is a Test of the titleTemplate feature"
);
done();
});
});
it("replaces multiple title strings in titleTemplate", done => {
ReactDOM.render(
<Helmet
titleTemplate={
"This is a %s of the titleTemplate feature. Another %s."
}
>
<title>Test</title>
</Helmet>,
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 => {
ReactDOM.render(
<div>
<Helmet
titleTemplate={
"This is a %s of the titleTemplate feature"
}
>
<title>Test</title>
</Helmet>
<Helmet
titleTemplate={
"A %s using nested titleTemplate attributes"
}
>
<title>Second Test</title>
</Helmet>
</div>,
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 => {
ReactDOM.render(
<div>
<Helmet
titleTemplate={
"This is a %s of the titleTemplate feature"
}
>
<title>Test</title>
</Helmet>
<Helmet>
<title>Second Test</title>
</Helmet>
</div>,
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 dollarTitle = "te$t te$$t te$$$t te$$$$t";
ReactDOM.render(
<Helmet titleTemplate={"This is a %s"}>
<title>{dollarTitle}</title>
</Helmet>,
container
);
requestAnimationFrame(() => {
expect(document.title).to.equal(
"This is a te$t te$$t te$$$t te$$$$t"
);
done();
});
});
it("properly handles title with children and titleTemplate", done => {
ReactDOM.render(
<Helmet titleTemplate={"This is an %s"}>
<title>
{"extra"} + {"test"}
</title>
</Helmet>,
container
);
requestAnimationFrame(() => {
expect(document.title).to.equal("This is an extra + test");
done();
});
});
it("does not encode all characters with HTML character entity equivalents", done => {
const chineseTitle = "膣膗 鍆錌雔";
ReactDOM.render(
<Helmet>
<title>{chineseTitle}</title>
</Helmet>,
container
);
requestAnimationFrame(() => {
expect(document.title).to.equal(chineseTitle);
done();
});
});
it("page title with prop itemProp", done => {
ReactDOM.render(
<Helmet defaultTitle={"Fallback"}>
<title itemProp="name">Test Title with itemProp</title>
</Helmet>,
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();
});
});
it("retains existing title tag when no title tag is defined", done => {
headElement.innerHTML = `<title>Existing Title</title>`;
ReactDOM.render(
<Helmet>
<meta name="keywords" content="stuff" />
</Helmet>,
container
);
requestAnimationFrame(() => {
expect(document.title).to.equal("Existing Title");
done();
});
});
it.skip("clears title tag if empty title is defined", done => {
ReactDOM.render(
<Helmet>
<title>Existing Title</title>
<meta name="keywords" content="stuff" />
</Helmet>,
container
);
requestAnimationFrame(() => {
expect(document.title).to.equal("Existing Title");
ReactDOM.render(
<Helmet>
<title />
<meta name="keywords" content="stuff" />
</Helmet>,
container
);
requestAnimationFrame(() => {
expect(document.title).to.equal("");
done();
});
});
});
});
describe("title attributes", () => {
beforeEach(() => {
headElement.innerHTML = `<title>Test Title</title>`;
});
it("updates title attributes", done => {
ReactDOM.render(
<Helmet>
<title itemProp="name" />
</Helmet>,
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 => {
ReactDOM.render(
<div>
<Helmet>
<title lang="en" hidden />
</Helmet>
<Helmet>
<title lang="ja" />
</Helmet>
</div>,
container
);
requestAnimationFrame(() => {
const titleTag = document.getElementsByTagName("title")[0];
expect(titleTag.getAttribute("lang")).to.equal("ja");
expect(titleTag.getAttribute("hidden")).to.equal("true");
expect(titleTag.getAttribute(HELMET_ATTRIBUTE)).to.equal(
"lang,hidden"
);
done();
});
});
it("handles valueless attributes", done => {
ReactDOM.render(
<Helmet>
<title hidden />
</Helmet>,
container
);
requestAnimationFrame(() => {
const titleTag = document.getElementsByTagName("title")[0];
expect(titleTag.getAttribute("hidden")).to.equal("true");
expect(titleTag.getAttribute(HELMET_ATTRIBUTE)).to.equal(
"hidden"
);
done();
});
});
it("clears title attributes that are handled within helmet", done => {
ReactDOM.render(
<Helmet>
<title lang="en" hidden />
</Helmet>,
container
);
requestAnimationFrame(() => {
ReactDOM.render(<Helmet />, 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 => {
ReactDOM.render(
<Helmet>
<html className="myClassName" lang="en" />
</Helmet>,
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 => {
ReactDOM.render(
<div>
<Helmet>
<html lang="en" />
</Helmet>
<Helmet>
<html lang="ja" />
</Helmet>
</div>,
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 => {
ReactDOM.render(
<Helmet>
<html amp />
</Helmet>,
container
);
requestAnimationFrame(() => {
const htmlTag = document.getElementsByTagName("html")[0];
expect(htmlTag.getAttribute("amp")).to.equal("true");
expect(htmlTag.getAttribute(HELMET_ATTRIBUTE)).to.equal(
"amp"
);
done();
});
});
it("clears html attributes that are handled within helmet", done => {
ReactDOM.render(
<Helmet>
<html lang="en" amp />
</Helmet>,
container
);
requestAnimationFrame(() => {
ReactDOM.render(<Helmet />, 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 => {
ReactDOM.render(
<Helmet>
<html lang="en" amp />
</Helmet>,
container
);
requestAnimationFrame(() => {
ReactDOM.render(
<Helmet>
<html lang="ja" id="html-tag" title="html tag" />
</Helmet>,
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 => {
ReactDOM.render(
<Helmet>
<html lang="en" amp />
</Helmet>,
container
);
requestAnimationFrame(() => {
ReactDOM.render(
<Helmet>
<html id="html-tag" title="html tag" />
</Helmet>,
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("are not cleared", done => {
ReactDOM.render(<Helmet />, 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("overwritten if specified in helmet", done => {
ReactDOM.render(
<Helmet>
<html test="helmet-attr" />
</Helmet>,
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("cleared once it is managed in helmet", done => {
ReactDOM.render(
<Helmet>
<html test="helmet-attr" />
</Helmet>,
container
);
requestAnimationFrame(() => {
ReactDOM.render(<Helmet />, 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("body attributes", () => {
context("valid attributes", () => {
const attributeList = {
accessKey: "c",
className: "test",
contentEditable: "true",
contextMenu: "mymenu",
"data-animal-type": "lion",
dir: "rtl",
draggable: "true",
dropzone: "copy",
hidden: "true",
id: "test",
lang: "fr",
spellcheck: "true",
style: "color:green",
tabIndex: "-1",
title: "test",
translate: "no"
};
Object.keys(attributeList).forEach(attribute => {
it(attribute, done => {
const attrValue = attributeList[attribute];
const attr = {
[attribute]: attrValue
};
ReactDOM.render(
<Helmet>
<body {...attr} />
</Helmet>,
container
);
requestAnimationFrame(() => {
const bodyTag = document.body;
const reactCompatAttr =
HTML_TAG_MAP[attribute] || attribute;
expect(
bodyTag.getAttribute(reactCompatAttr)
).to.equal(attrValue);
expect(
bodyTag.getAttribute(HELMET_ATTRIBUTE)
).to.equal(reactCompatAttr);
done();
});
});
});
});
it("updates multiple body attributes", done => {
ReactDOM.render(
<Helmet>
<body className="myClassName" tabIndex={-1} />
</Helmet>,
container
);
requestAnimationFrame(() => {
const bodyTag = document.body;
expect(bodyTag.getAttribute("class")).to.equal(
"myClassName"
);
expect(bodyTag.getAttribute("tabindex")).to.equal("-1");
expect(bodyTag.getAttribute(HELMET_ATTRIBUTE)).to.equal(
"class,tabindex"
);
done();
});
});
it("sets attributes based on the deepest nested component", done => {
ReactDOM.render(
<div>
<Helmet>
<body lang="en" />
</Helmet>
<Helmet>
<body lang="ja" />
</Helmet>
</div>,
container
);
requestAnimationFrame(() => {
const bodyTag = document.body;
expect(bodyTag.getAttribute("lang")).to.equal("ja");
expect(bodyTag.getAttribute(HELMET_ATTRIBUTE)).to.equal(
"lang"
);
done();
});
});
it("handles valueless attributes", done => {
ReactDOM.render(
<Helmet>
<body hidden />
</Helmet>,
container
);
requestAnimationFrame(() => {
const bodyTag = document.body;
expect(bodyTag.getAttribute("hidden")).to.equal("true");
expect(bodyTag.getAttribute(HELMET_ATTRIBUTE)).to.equal(
"hidden"
);
done();
});
});
it("clears body attributes that are handled within helmet", done => {
ReactDOM.render(
<Helmet>
<body lang="en" hidden />
</Helmet>,
container
);
requestAnimationFrame(() => {
ReactDOM.render(<Helmet />, container);
requestAnimationFrame(() => {
const bodyTag = document.body;
expect(bodyTag.getAttribute("lang")).to.be.null;
expect(bodyTag.getAttribute("hidden")).to.be.null;
expect(bodyTag.getAttribute(HELMET_ATTRIBUTE)).to.equal(
null
);
done();
});
});
});
it("updates with multiple additions and removals - overwrite and new", done => {
ReactDOM.render(
<Helmet>
<body lang="en" hidden />
</Helmet>,
container
);
requestAnimationFrame(() => {
ReactDOM.render(
<Helmet>
<body lang="ja" id="body-tag" title="body tag" />
</Helmet>,
container
);
requestAnimationFrame(() => {
const bodyTag = document.body;
expect(bodyTag.getAttribute("hidden")).to.equal(null);
expect(bodyTag.getAttribute("lang")).to.equal("ja");
expect(bodyTag.getAttribute("id")).to.equal("body-tag");
expect(bodyTag.getAttribute("title")).to.equal(
"body tag"
);
expect(bodyTag.getAttribute(HELMET_ATTRIBUTE)).to.equal(
"lang,id,title"
);
done();
});
});
});
it("updates with multiple additions and removals - all new", done => {
ReactDOM.render(
<Helmet>
<body lang="en" hidden />
</Helmet>,
container
);
requestAnimationFrame(() => {
ReactDOM.render(
<Helmet>
<body id="body-tag" title="body tag" />
</Helmet>,
container
);
requestAnimationFrame(() => {
const bodyTag = document.body;
expect(bodyTag.getAttribute("hidden")).to.equal(null);
expect(bodyTag.getAttribute("lang")).to.equal(null);
expect(bodyTag.getAttribute("id")).to.equal("body-tag");
expect(bodyTag.getAttribute("title")).to.equal(
"body tag"
);
expect(bodyTag.getAttribute(HELMET_ATTRIBUTE)).to.equal(
"id,title"
);
done();
});
});
});
context("initialized outside of helmet", () => {
before(() => {
const bodyTag = document.body;
bodyTag.setAttribute("test", "test");
});
it("attributes are not cleared", done => {
ReactDOM.render(<Helmet />, container);
requestAnimationFrame(() => {
const bodyTag = document.body;
expect(bodyTag.getAttribute("test")).to.equal("test");
expect(bodyTag.getAttribute(HELMET_ATTRIBUTE)).to.equal(
null
);
done();
});
});
it("attributes are overwritten if specified in helmet", done => {
ReactDOM.render(
<Helmet>
<body test="helmet-attr" />
</Helmet>,
container
);
requestAnimationFrame(() => {
const bodyTag = document.body;
expect(bodyTag.getAttribute("test")).to.equal(
"helmet-attr"
);
expect(bodyTag.getAttribute(HELMET_ATTRIBUTE)).to.equal(
"test"
);
done();
});
});
it("attributes are cleared once managed in helmet", done => {
ReactDOM.render(
<Helmet>
<body test="helmet-attr" />
</Helmet>,
container
);
requestAnimationFrame(() => {
ReactDOM.render(<Helmet />, container);
requestAnimationFrame(() => {
const bodyTag = document.body;
expect(bodyTag.getAttribute("test")).to.equal(null);
expect(
bodyTag.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 spy = sinon.spy();
ReactDOM.render(
<div>
<Helmet onChangeClientState={spy}>
<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</title>
</Helmet>
</div>,
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.contain({title: "Main Title"});
expect(newState.baseTag).to.contain({
href: "http://mysite.com/"
});
expect(newState.metaTags).to.contain({charset: "utf-8"});
expect(newState.linkTags).to.contain({
href: "http://localhost/helmet",
rel: "canonical"
});
expect(newState.scriptTags).to.contain({
src: "http://localhost/test.js",
type: "text/javascript"
});
expect(addedTags).to.have.property("baseTag");
expect(addedTags.baseTag).to.have.deep.property("[0]");
expect(addedTags.baseTag[0].outerHTML).to.equal(
`<base href="http://mysite.com/" data-react-helmet="true">`
);
expect(addedTags).to.have.property("metaTags");
expect(addedTags.metaTags).to.have.deep.property("[0]");
expect(addedTags.metaTags[0].outerHTML).to.equal(
`<meta charset="utf-8" data-react-helmet="true">`
);
expect(addedTags).to.have.property("linkTags");
expect(addedTags.linkTags).to.have.deep.property("[0]");
expect(addedTags.linkTags[0].outerHTML).to.equal(
`<link href="http://localhost/helmet" rel="canonical" data-react-helmet="true">`
);
expect(addedTags).to.have.property("scriptTags");
expect(addedTags.scriptTags).to.have.deep.property("[0]");
expect(addedTags.scriptTags[0].outerHTML).to.equal(
`<script src="http://localhost/test.js" type="text/javascript" data-react-helmet="true"></script>`
);
expect(removedTags).to.be.empty;
done();
});
});
it("calls the deepest defined callback with the deepest state", done => {
const spy = sinon.spy();
ReactDOM.render(
<div>
<Helmet onChangeClientState={spy}>
<title>Main Title</title>
</Helmet>
<Helmet>
<title>Deeper Title</title>
</Helmet>
</div>,
container
);
requestAnimationFrame(() => {
expect(spy.callCount).to.equal(1);
expect(spy.getCall(0).args[0]).to.contain({
title: "Deeper Title"
});
done();
});
});
});
describe("base tag", () => {
it("updates base tag", done => {
ReactDOM.render(
<Helmet>
<base href="http://mysite.com/" />
</Helmet>,
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 => {
ReactDOM.render(
<Helmet base={{href: "http://mysite.com/"}} />,
container
);
requestAnimationFrame(() => {
ReactDOM.render(<Helmet />, 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 => {
ReactDOM.render(
<Helmet>
<base property="won't work" />
</Helmet>,
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 => {
ReactDOM.render(
<div>
<Helmet>
<base href="http://mysite.com" />
</Helmet>
<Helmet>
<base href="http://mysite.com/public" />
</Helmet>
</div>,
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.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(
`<base href="http://mysite.com/public" ${HELMET_ATTRIBUTE}="true">`
);
done();
});
});
it("does not render tag when primary attribute is null", done => {
ReactDOM.render(
<Helmet>
<base href={undefined} />
</Helmet>,
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 => {
ReactDOM.render(
<Helmet>
<meta charSet="utf-8" />
<meta name="description" content="Test description" />
<meta httpEquiv="content-type" content="text/html" />
<meta property="og:type" content="article" />
<meta itemProp="name" content="Test name itemprop" />
</Helmet>,
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 => {
ReactDOM.render(
<Helmet>
<meta name="description" content="Test description" />
</Helmet>,
container
);
requestAnimationFrame(() => {
ReactDOM.render(<Helmet />, 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 => {
ReactDOM.render(
<Helmet>
<meta href="won't work" />
</Helmet>,
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 => {
ReactDOM.render(
<div>
<Helmet>
<meta charSet="utf-8" />
<meta
name="description"
content="Test description"
/>
</Helmet>
<Helmet>
<meta
name="description"
content="Inner description"
/>
<meta name="keywords" content="test,meta,tags" />
</Helmet>
</div>,
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.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(
`<meta charset="utf-8" ${HELMET_ATTRIBUTE}="true">`
);
expect(existingTags)
.to.have.deep.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(
`<meta name="description" content="Inner description" ${HELMET_ATTRIBUTE}="true">`
);
expect(existingTags)
.to.have.deep.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(
`<meta name="keywords" content="test,meta,tags" ${HELMET_ATTRIBUTE}="true">`
);
done();
});
});
it("allows duplicate meta tags if specified in the same component", done => {
ReactDOM.render(
<Helmet>
<meta name="description" content="Test description" />
<meta
name="description"
content="Duplicate description"
/>
</Helmet>,
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.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(
`<meta name="description" content="Test description" ${HELMET_ATTRIBUTE}="true">`
);
expect(existingTags)
.to.have.deep.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(
`<meta name="description" content="Duplicate description" ${HELMET_ATTRIBUTE}="true">`
);
done();
});
});
it("overrides duplicate meta tags with single meta tag in a nested component", done => {
ReactDOM.render(
<div>
<Helmet>
<meta
name="description"
content="Test description"
/>
<meta
name="description"
content="Duplicate description"
/>
</Helmet>
<Helmet>
<meta
name="description"
content="Inner description"
/>
</Helmet>
</div>,
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.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(
`<meta name="description" content="Inner description" ${HELMET_ATTRIBUTE}="true">`
);
done();
});
});
it("overrides single meta tag with duplicate meta tags in a nested component", done => {
ReactDOM.render(
<div>
<Helmet>
<meta
name="description"
content="Test description"
/>
</Helmet>
<Helmet>
<meta
name="description"
content="Inner description"
/>
<meta
name="description"
content="Inner duplicate description"
/>
</Helmet>
</div>,
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.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(
`<meta name="description" content="Inner description" ${HELMET_ATTRIBUTE}="true">`
);
expect(existingTags)
.to.have.deep.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(
`<meta name="description" content="Inner duplicate description" ${HELMET_ATTRIBUTE}="true">`
);
done();
});
});
it("does not render tag when primary attribute is null", done => {
ReactDOM.render(
<Helmet>
<meta
name={undefined}
content="Inner duplicate description"
/>
</Helmet>,
container
);
requestAnimationFrame(() => {
const tagNodes = headElement.querySelectorAll(
`meta[${HELMET_ATTRIBUTE}]`
);
const existingTags = Array.prototype.slice.call(tagNodes);
expect(existingTags).to.be.empty;
done();
});
});
});
describe("link tags", () => {
it("updates link tags", done => {
ReactDOM.render(
<Helmet>
<link href="http://localhost/helmet" rel="canonical" />
<link
href="http://localhost/style.css"
rel="stylesheet"
type="text/css"
/>
</Helmet>,
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 => {
ReactDOM.render(
<Helmet>
<link href="http://localhost/helmet" rel="canonical" />
</Helmet>,
container
);
requestAnimationFrame(() => {
ReactDOM.render(<Helmet />, 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 => {
ReactDOM.render(
<Helmet>
<link httpEquiv="won't work" />
</Helmet>,
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 => {
ReactDOM.render(
<div>
<Helmet>
<link
href="http://localhost/helmet"
rel="canonical"
/>
</Helmet>
<Helmet>
<link
rel="canonical"
href="http://localhost/helmet/new"
/>
</Helmet>
<Helmet>
<link
href="http://localhost/helmet/newest"
rel="canonical"
/>
</Helmet>
</div>,
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.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(
`<link href="http://localhost/helmet/newest" rel="canonical" ${HELMET_ATTRIBUTE}="true">`
);
done();
});
});
it("tags with rel='stylesheet' uses the href as the primary identification of the tag, regardless of ordering", done => {
ReactDOM.render(
<div>
<Helmet>
<link
href="http://localhost/style.css"
rel="stylesheet"
type="text/css"
media="all"
/>
</Helmet>
<Helmet>
<link
rel="stylesheet"
href="http://localhost/inner.css"
type="text/css"
media="all"
/>
</Helmet>
</div>,
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.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(
`<link href="http://localhost/style.css" rel="stylesheet" type="text/css" media="all" ${HELMET_ATTRIBUTE}="true">`
);
expect(existingTags)
.to.have.deep.property("[1]")
.that.is.an.instanceof(Element);
expect(secondTag).to.have.property("getAttribute");
expect(secondTag.getAttribute("rel")).to.equal(
"stylesheet"
);
expect(secondTag.getAttribute("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(
`<link rel="stylesheet" href="http://localhost/inner.css" type="text/css" media="all" ${HELMET_ATTRIBUTE}="true">`
);
done();
});
});
it("sets link tags based on deepest nested component", done => {
ReactDOM.render(
<div>
<Helmet>
<link
rel="canonical"
href="http://localhost/helmet"
/>
<link
href="http://localhost/style.css"
rel="stylesheet"
type="text/css"
media="all"
/>
</Helmet>
<Helmet>
<link
rel="canonical"
href="http://localhost/helmet/innercomponent"
/>
<link
href="http://localhost/inner.css"
rel="stylesheet"
type="text/css"
media="all"
/>
</Helmet>
</div>,
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.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(
`<link href="http://localhost/style.css" rel="stylesheet" type="text/css" media="all" ${HELMET_ATTRIBUTE}="true">`
);
expect(existingTags)
.to.have.deep.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(
`<link rel="canonical" href="http://localhost/helmet/innercomponent" ${HELMET_ATTRIBUTE}="true">`
);
expect(existingTags)
.to.have.deep.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(
`<link href="http://localhost/inner.css" rel="stylesheet" type="text/css" media="all" ${HELMET_ATTRIBUTE}="true">`
);
done();
});
});
it("allows duplicate link tags if specified in the same component", done => {
ReactDOM.render(
<Helmet>
<link rel="canonical" href="http://localhost/helmet" />
<link
rel="canonical"
href="http://localhost/helmet/component"
/>
</Helmet>,
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.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(
`<link rel="canonical" href="http://localhost/helmet" ${HELMET_ATTRIBUTE}="true">`
);
expect(existingTags)
.to.have.deep.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(
`<link rel="canonical" href="http://localhost/helmet/component" ${HELMET_ATTRIBUTE}="true">`
);
done();
});
});
it("overrides duplicate link tags with a single link tag in a nested component", done => {
ReactDOM.render(
<div>
<Helmet>
<link
rel="canonical"
href="http://localhost/helmet"
/>
<link
rel="canonical"
href="http://localhost/helmet/component"
/>
</Helmet>
<Helmet>
<link
rel="canonical"
href="http://localhost/helmet/innercomponent"
/>
</Helmet>
</div>,
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.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(
`<link rel="canonical" href="http://localhost/helmet/innercomponent" ${HELMET_ATTRIBUTE}="true">`
);
done();
});
});
it("overrides single link tag with duplicate link tags in a nested component", done => {
ReactDOM.render(
<div>
<Helmet>
<link
rel="canonical"
href="http://localhost/helmet"
/>
</Helmet>
<Helmet>
<link
rel="canonical"
href="http://localhost/helmet/component"
/>
<link
rel="canonical"
href="http://localhost/helmet/innercomponent"
/>
</Helmet>
</div>,
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.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(
`<link rel="canonical" href="http://localhost/helmet/component" ${HELMET_ATTRIBUTE}="true">`
);
expect(existingTags)
.to.have.deep.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(
`<link rel="canonical" href="http://localhost/helmet/innercomponent" ${HELMET_ATTRIBUTE}="true">`
);
done();
});
});
it("does not render tag when primary attribute is null", done => {
ReactDOM.render(
<Helmet>
<link rel="icon" sizes="192x192" href={null} />
<link
rel="canonical"
href="http://localhost/helmet/component"
/>
</Helmet>,
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.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(
`<link rel="canonical" href="http://localhost/helmet/component" ${HELMET_ATTRIBUTE}="true">`
);
done();
});
});
});
describe("script tags", () => {
it("updates script tags", done => {
const scriptInnerHTML = `
{
"@context": "http://schema.org",
"@type": "NewsArticle",
"url": "http://localhost/helmet"
}
`;
ReactDOM.render(
<Helmet>
<script
src="http://localhost/test.js"
type="text/javascript"
/>
<script
src="http://localhost/test2.js"
type="text/javascript"
/>
<script type="application/ld+json">
{scriptInnerHTML}
</script>
</Helmet>,
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 => {
ReactDOM.render(
<Helmet>
<script
src="http://localhost/test.js"
type="text/javascript"
/>
</Helmet>,
container
);
requestAnimationFrame(() => {
ReactDOM.render(<Helmet />, 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 => {
ReactDOM.render(
<Helmet>
<script property="won't work" />
</Helmet>,
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 => {
ReactDOM.render(
<div>
<Helmet>
<script
src="http://localhost/test.js"
type="text/javascript"
/>
<script
src="http://localhost/test2.js"
type="text/javascript"
/>
</Helmet>
</div>,
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.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(
`<script src="http://localhost/test.js" type="text/javascript" ${HELMET_ATTRIBUTE}="true"></script>`
);
expect(existingTags)
.to.have.deep.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(
`<script src="http://localhost/test2.js" type="text/javascript" ${HELMET_ATTRIBUTE}="true"></script>`
);
done();
});
});
it("sets undefined attribute values to empty strings", done => {
ReactDOM.render(
<Helmet>
<script src="foo.js" async={undefined} />
</Helmet>,
container
);
requestAnimationFrame(() => {
const existingTag = headElement.querySelector(
`script[${HELMET_ATTRIBUTE}]`
);
expect(existingTag).to.not.equal(undefined);
expect(existingTag.outerHTML)
.to.be.a("string")
.that.equals(
`<script src="foo.js" async="" ${HELMET_ATTRIBUTE}="true"></script>`
);
done();
});
});
it("does not render tag when primary attribute (src) is null", done => {
ReactDOM.render(
<Helmet>
<script src={undefined} type="text/javascript" />
</Helmet>,
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 => {
ReactDOM.render(
<Helmet>
<script innerHTML={undefined} />
</Helmet>,
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 noscriptInnerHTML = `<link rel="stylesheet" type="text/css" href="foo.css" />`;
ReactDOM.render(
<Helmet>
<noscript id="bar">{noscriptInnerHTML}</noscript>
</Helmet>,
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 => {
ReactDOM.render(
<Helmet>
<noscript id="bar" />
</Helmet>,
container
);
requestAnimationFrame(() => {
ReactDOM.render(<Helmet />, 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 => {
ReactDOM.render(
<Helmet>
<noscript property="won't work" />
</Helmet>,
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 => {
ReactDOM.render(
<Helmet>
<noscript>{undefined}</noscript>
</Helmet>,
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 cssText1 = `
body {
background-color: green;
}
`;
const cssText2 = `
p {
font-size: 12px;
}
`;
ReactDOM.render(
<Helmet>
<style type="text/css">{cssText1}</style>
<style>{cssText2}</style>
</Helmet>,
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.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(
`<style type="text/css" ${HELMET_ATTRIBUTE}="true">${cssText1}</style>`
);
expect(existingTags)
.to.have.deep.property("[1]")
.that.is.an.instanceof(Element);
expect(secondTag.innerHTML).to.equal(cssText2);
expect(secondTag.outerHTML).to.equal(
`<style ${HELMET_ATTRIBUTE}="true">${cssText2}</style>`
);
done();
});
});
it("clears all style tags if none are specified", done => {
const cssText = `
body {
background-color: green;
}
`;
ReactDOM.render(
<Helmet>
<style type="text/css">{cssText}</style>
</Helmet>,
container
);
requestAnimationFrame(() => {
ReactDOM.render(<Helmet />, 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 => {
ReactDOM.render(
<Helmet>
<style property="won't work" />
</Helmet>,
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 => {
ReactDOM.render(
<Helmet>
<style>{undefined}</style>
</Helmet>,
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 => {
ReactDOM.render(
<div>
<Helmet defer={false}>
<script>window.__spy__(1)</script>
</Helmet>
<Helmet>
<script>window.__spy__(2)</script>
</Helmet>
</div>,
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 stringifiedBodyAttributes = `lang="ga" class="myClassName"`;
const stringifiedTitle = `<title ${HELMET_ATTRIBUTE}="true">Dangerous <script> include</title>`;
const unEncodedStringifiedTitle = `<title ${HELMET_ATTRIBUTE}="true">This is text and & and '.</title>`;
const stringifiedTitleWithItemprop = `<title ${HELMET_ATTRIBUTE}="true" itemprop="name">Title with Itemprop</title>`;
// Separate itemprop string for the server - Per https://github.com/facebook/react/issues/12403 the server renders HTML Microdata as camel case
const stringifiedTitleWithItempropFromServer = `<title ${HELMET_ATTRIBUTE}="true" itemProp="name">Title with Itemprop</title>`;
const stringifiedTitleWithTitleExpression = `<title ${HELMET_ATTRIBUTE}="true">Title: Some Great Title</title>`;
const stringifiedBaseTag = `<base ${HELMET_ATTRIBUTE}="true" target="_blank" href="http://localhost/"/>`;
const stringifiedMetaTags = [
`<meta ${HELMET_ATTRIBUTE}="true" charset="utf-8"/>`,
`<meta ${HELMET_ATTRIBUTE}="true" name="description" content="Test description & encoding of special characters like ' " > < \`"/>`,
`<meta ${HELMET_ATTRIBUTE}="true" http-equiv="content-type" content="text/html"/>`,
`<meta ${HELMET_ATTRIBUTE}="true" property="og:type" content="article"/>`,
`<meta ${HELMET_ATTRIBUTE}="true" itemprop="name" content="Test name itemprop"/>`
].join("");
// Separate itemprop string for the server - Per https://github.com/facebook/react/issues/12403 the server renders HTML Microdata as camel case
const stringifiedMetaTagsFromServer = [
`<meta ${HELMET_ATTRIBUTE}="true" charSet="utf-8"/>`,
`<meta ${HELMET_ATTRIBUTE}="true" name="description" content="Test description & encoding of special characters like ' " > < \`"/>`,
`<meta ${HELMET_ATTRIBUTE}="true" http-equiv="content-type" content="text/html"/>`,
`<meta ${HELMET_ATTRIBUTE}="true" property="og:type" content="article"/>`,
`<meta ${HELMET_ATTRIBUTE}="true" itemProp="name" content="Test name itemprop"/>`
].join("");
const stringifiedLinkTags = [
`<link ${HELMET_ATTRIBUTE}="true" href="http://localhost/helmet" rel="canonical"/>`,
`<link ${HELMET_ATTRIBUTE}="true" href="http://localhost/style.css" rel="stylesheet" type="text/css"/>`
].join("");
const stringifiedScriptTags = [
`<script ${HELMET_ATTRIBUTE}="true" src="http://localhost/test.js" type="text/javascript"></script>`,
`<script ${HELMET_ATTRIBUTE}="true" src="http://localhost/test2.js" type="text/javascript"></script>`
].join("");
const stringifiedNoscriptTags = [
`<noscript ${HELMET_ATTRIBUTE}="true" id="foo"><link rel="stylesheet" type="text/css" href="/style.css" /></noscript>`,
`<noscript ${HELMET_ATTRIBUTE}="true" id="bar"><link rel="stylesheet" type="text/css" href="/style2.css" /></noscript>`
].join("");
const stringifiedStyleTags = [
`<style ${HELMET_ATTRIBUTE}="true" type="text/css">body {background-color: green;}</style>`,
`<style ${HELMET_ATTRIBUTE}="true" type="text/css">p {font-size: 12px;}</style>`
].join("");
before(() => {
Helmet.canUseDOM = false;
});
it("provides initial values if no state is found", () => {
let head = Helmet.rewind();
head = Helmet.rewind();
expect(head.meta).to.exist;
expect(head.meta).to.respondTo("toString");
expect(head.meta.toString()).to.equal("");
});
it("encodes special characters in title", () => {
ReactDOM.render(
<Helmet>
<title>{`Dangerous <script> include`}</title>
</Helmet>,
container
);
const head = Helmet.rewind();
expect(head.title).to.exist;
expect(head.title).to.respondTo("toString");
expect(head.title.toString()).to.equal(stringifiedTitle);
});
it("opts out of string encoding", () => {
ReactDOM.render(
<Helmet encodeSpecialCharacters={false}>
<title>{"This is text and & and '."}</title>
</Helmet>,
container
);
const head = Helmet.rewind();
expect(head.title).to.exist;
expect(head.title).to.respondTo("toString");
expect(head.title.toString()).to.equal(unEncodedStringifiedTitle);
});
it("renders title as React component", () => {
ReactDOM.render(
<Helmet>
<title>{`Dangerous <script> include`}</title>
</Helmet>,
container
);
const head = Helmet.rewind();
expect(head.title).to.exist;
expect(head.title).to.respondTo("toComponent");
const titleComponent = head.title.toComponent();
expect(titleComponent)
.to.be.an("array")
.that.has.length.of(1);
titleComponent.forEach(title => {
expect(title)
.to.be.an("object")
.that.contains.property("type", "title");
});
const markup = ReactServer.renderToStaticMarkup(
<div>{titleComponent}</div>
);
expect(markup)
.to.be.a("string")
.that.equals(`<div>${stringifiedTitle}</div>`);
});
it("renders title with itemprop name as React component", () => {
ReactDOM.render(
<Helmet>
<title itemProp="name">Title with Itemprop</title>
</Helmet>,
container
);
const head = Helmet.rewind();
expect(head.title).to.exist;
expect(head.title).to.respondTo("toComponent");
const titleComponent = head.title.toComponent();
expect(titleComponent)
.to.be.an("array")
.that.has.length.of(1);
titleComponent.forEach(title => {
expect(title)
.to.be.an("object")
.that.contains.property("type", "title");
});
const markup = ReactServer.renderToStaticMarkup(
<div>{titleComponent}</div>
);
expect(markup)
.to.be.a("string")
.that.equals(
`<div>${stringifiedTitleWithItempropFromServer}</div>`
);
});
it("renders base tag as React component", () => {
ReactDOM.render(
<Helmet>
<base target="_blank" href="http://localhost/" />
</Helmet>,
container
);
const head = Helmet.rewind();
expect(head.base).to.exist;
expect(head.base).to.respondTo("toComponent");
const baseComponent = head.base.toComponent();
expect(baseComponent)
.to.be.an("array")
.that.has.length.of(1);
baseComponent.forEach(base => {
expect(base)
.to.be.an("object")
.that.contains.property("type", "base");
});
const markup = ReactServer.renderToStaticMarkup(
<div>{baseComponent}</div>
);
expect(markup)
.to.be.a("string")
.that.equals(`<div>${stringifiedBaseTag}</div>`);
});
it("renders meta tags as React components", () => {
ReactDOM.render(
<Helmet>
<meta charSet="utf-8" />
<meta
name="description"
content={
"Test description & encoding of special characters like ' \" > < `"
}
/>
<meta httpEquiv="content-type" content="text/html" />
<meta property="og:type" content="article" />
<meta itemProp="name" content="Test name itemprop" />
</Helmet>,
container
);
const head = Helmet.rewind();
expect(head.meta).to.exist;
expect(head.meta).to.respondTo("toComponent");
const metaComponent = head.meta.toComponent();
expect(metaComponent)
.to.be.an("array")
.that.has.length.of(5);
metaComponent.forEach(meta => {
expect(meta)
.to.be.an("object")
.that.contains.property("type", "meta");
});
const markup = ReactServer.renderToStaticMarkup(
<div>{metaComponent}</div>
);
expect(markup)
.to.be.a("string")
.that.equals(`<div>${stringifiedMetaTagsFromServer}</div>`);
});
it("renders link tags as React components", () => {
ReactDOM.render(
<Helmet>
<link href="http://localhost/helmet" rel="canonical" />
<link
href="http://localhost/style.css"
rel="stylesheet"
type="text/css"
/>
</Helmet>,
container
);
const head = Helmet.rewind();
expect(head.link).to.exist;
expect(head.link).to.respondTo("toComponent");
const linkComponent = head.link.toComponent();
expect(linkComponent)
.to.be.an("array")
.that.has.length.of(2);
linkComponent.forEach(link => {
expect(link)
.to.be.an("object")
.that.contains.property("type", "link");
});
const markup = ReactServer.renderToStaticMarkup(
<div>{linkComponent}</div>
);
expect(markup)
.to.be.a("string")
.that.equals(`<div>${stringifiedLinkTags}</div>`);
});
it("renders script tags as React components", () => {
ReactDOM.render(
<Helmet>
<script
src="http://localhost/test.js"
type="text/javascript"
/>
<script
src="http://localhost/test2.js"
type="text/javascript"
/>
</Helmet>,
container
);
const head = Helmet.rewind();
expect(head.script).to.exist;
expect(head.script).to.respondTo("toComponent");
const scriptComponent = head.script.toComponent();
expect(scriptComponent)
.to.be.an("array")
.that.has.length.of(2);
scriptComponent.forEach(script => {
expect(script)
.to.be.an("object")
.that.contains.property("type", "script");
});
const markup = ReactServer.renderToStaticMarkup(
<div>{scriptComponent}</div>
);
expect(markup)
.to.be.a("string")
.that.equals(`<div>${stringifiedScriptTags}</div>`);
});
it("renders noscript tags as React components", () => {
ReactDOM.render(
<Helmet>
<noscript id="foo">{`<link rel="stylesheet" type="text/css" href="/style.css" />`}</noscript>
<noscript id="bar">{`<link rel="stylesheet" type="text/css" href="/style2.css" />`}</noscript>
</Helmet>,
container
);
const head = Helmet.rewind();
expect(head.noscript).to.exist;
expect(head.noscript).to.respondTo("toComponent");
const noscriptComponent = head.noscript.toComponent();
expect(noscriptComponent)
.to.be.an("array")
.that.has.length.of(2);
noscriptComponent.forEach(noscript => {
expect(noscript)
.to.be.an("object")
.that.contains.property("type", "noscript");
});
const markup = ReactServer.renderToStaticMarkup(
<div>{noscriptComponent}</div>
);
expect(markup)
.to.be.a("string")
.that.equals(`<div>${stringifiedNoscriptTags}</div>`);
});
it("renders style tags as React components", () => {
ReactDOM.render(
<Helmet>
<style type="text/css">{`body {background-color: green;}`}</style>
<style type="text/css">{`p {font-size: 12px;}`}</style>
</Helmet>,
container
);
const head = Helmet.rewind();
expect(head.style).to.exist;
expect(head.style).to.respondTo("toComponent");
const styleComponent = head.style.toComponent();
expect(styleComponent)
.to.be.an("array")
.that.has.length.of(2);
const markup = ReactServer.renderToStaticMarkup(
<div>{styleComponent}</div>
);
expect(markup)
.to.be.a("string")
.that.equals(`<div>${stringifiedStyleTags}</div>`);
});
it("renders title tag as string", () => {
ReactDOM.render(
<Helmet>
<title>{"Dangerous <script> include"}</title>
</Helmet>,
container
);
const head = Helmet.rewind();
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 and allows children containing expressions", done => {
const someValue = "Some Great Title";
ReactDOM.render(
<Helmet>
<title>Title: {someValue}</title>
</Helmet>,
container
);
const head = Helmet.rewind();
expect(head.title).to.exist;
expect(head.title).to.respondTo("toString");
requestAnimationFrame(() => {
expect(head.title.toString())
.to.be.a("string")
.that.equals(stringifiedTitleWithTitleExpression);
done();
});
});
it("renders title with itemprop name as string", () => {
ReactDOM.render(
<Helmet>
<title itemProp="name">Title with Itemprop</title>
</Helmet>,
container
);
const head = Helmet.rewind();
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", () => {
ReactDOM.render(
<Helmet>
<base target="_blank" href="http://localhost/" />
</Helmet>,
container
);
const head = Helmet.rewind();
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", () => {
ReactDOM.render(
<Helmet>
<meta charSet="utf-8" />
<meta
name="description"
content="Test description & encoding of special characters like ' " > < `"
/>
<meta httpEquiv="content-type" content="text/html" />
<meta property="og:type" content="article" />
<meta itemProp="name" content="Test name itemprop" />
</Helmet>,
container
);
const head = Helmet.rewind();
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", () => {
ReactDOM.render(
<Helmet>
<link href="http://localhost/helmet" rel="canonical" />
<link
href="http://localhost/style.css"
rel="stylesheet"
type="text/css"
/>
</Helmet>,
container
);
const head = Helmet.rewind();
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", () => {
ReactDOM.render(
<Helmet>
<script
src="http://localhost/test.js"
type="text/javascript"
/>
<script
src="http://localhost/test2.js"
type="text/javascript"
/>
</Helmet>,
container
);
const head = Helmet.rewind();
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", () => {
ReactDOM.render(
<Helmet>
<style type="text/css">{`body {background-color: green;}`}</style>
<style type="text/css">{`p {font-size: 12px;}`}</style>
</Helmet>,
container
);
const head = Helmet.rewind();
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", () => {
ReactDOM.render(
<Helmet>
<html lang="ga" className="myClassName" />
</Helmet>,
container
);
const {htmlAttributes} = Helmet.rewind();
const attrs = htmlAttributes.toComponent();
expect(attrs).to.exist;
const markup = ReactServer.renderToStaticMarkup(
<html lang="en" {...attrs} />
);
expect(markup)
.to.be.a("string")
.that.equals(`<html ${stringifiedHtmlAttributes}></html>`);
});
it("renders html attributes as string", () => {
ReactDOM.render(
<Helmet>
<html lang="ga" className="myClassName" />
</Helmet>,
container
);
const head = Helmet.rewind();
expect(head.htmlAttributes).to.exist;
expect(head.htmlAttributes).to.respondTo("toString");
expect(head.htmlAttributes.toString())
.to.be.a("string")
.that.equals(stringifiedHtmlAttributes);
});
it("renders body attributes as component", () => {
ReactDOM.render(
<Helmet>
<body lang="ga" className="myClassName" />
</Helmet>,
container
);
const {bodyAttributes} = Helmet.rewind();
const attrs = bodyAttributes.toComponent();
expect(attrs).to.exist;
const markup = ReactServer.renderToStaticMarkup(
<body lang="en" {...attrs} />
);
expect(markup)
.to.be.a("string")
.that.equals(`<body ${stringifiedBodyAttributes}></body>`);
});
it("renders body attributes as string", () => {
ReactDOM.render(
<Helmet>
<body lang="ga" className="myClassName" />
</Helmet>,
container
);
const body = Helmet.rewind();
expect(body.bodyAttributes).to.exist;
expect(body.bodyAttributes).to.respondTo("toString");
expect(body.bodyAttributes.toString())
.to.be.a("string")
.that.equals(stringifiedBodyAttributes);
});
it("does not encode all characters with HTML character entity equivalents", () => {
const chineseTitle = "膣膗 鍆錌雔";
const stringifiedChineseTitle = `<title ${HELMET_ATTRIBUTE}="true">${chineseTitle}</title>`;
ReactDOM.render(
<div>
<Helmet>
<title>{chineseTitle}</title>
</Helmet>
</div>,
container
);
const head = Helmet.rewind();
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", () => {
ReactDOM.render(<div />, container);
const head = Helmet.rewind();
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(
`<title ${HELMET_ATTRIBUTE}="true"></title>`
);
expect(head.title).to.respondTo("toComponent");
const markup = ReactServer.renderToStaticMarkup(
<div>{head.title.toComponent()}</div>
);
expect(markup)
.to.be.a("string")
.that.equals(
`<div><title ${HELMET_ATTRIBUTE}="true"></title></div>`
);
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", () => {
ReactDOM.render(
<Helmet>
<script src="foo.js" async={undefined} />
</Helmet>,
container
);
const {script} = Helmet.rewind();
const stringifiedScriptTag = script.toString();
expect(stringifiedScriptTag)
.to.be.a("string")
.that.equals(
`<script ${HELMET_ATTRIBUTE}="true" src="foo.js" async></script>`
);
});
context("renderStatic", () => {
it("does html encode title", () => {
ReactDOM.render(
<Helmet>
<title>{`Dangerous <script> include`}</title>
</Helmet>,
container
);
const head = Helmet.renderStatic();
expect(head.title).to.exist;
expect(head.title).to.respondTo("toString");
expect(head.title.toString()).to.equal(stringifiedTitle);
});
it("renders title as React component", () => {
ReactDOM.render(
<Helmet>
<title>{`Dangerous <script> include`}</title>
</Helmet>,
container
);
const head = Helmet.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.length.of(1);
titleComponent.forEach(title => {
expect(title)
.to.be.an("object")
.that.contains.property("type", "title");
});
const markup = ReactServer.renderToStaticMarkup(
<div>{titleComponent}</div>
);
expect(markup)
.to.be.a("string")
.that.equals(`<div>${stringifiedTitle}</div>`);
});
});
after(() => {
Helmet.canUseDOM = true;
});
});
describe("misc", () => {
it("throws in rewind() when a DOM is present", () => {
ReactDOM.render(
<Helmet>
<title>Fancy title</title>
</Helmet>,
container
);
expect(Helmet.rewind).to.throw(
"You may only call rewind() on the server. Call peek() to read the current state."
);
});
it("lets you read current state in peek() whether or not a DOM is present", done => {
ReactDOM.render(
<Helmet>
<title>Fancy title</title>
</Helmet>,
container
);
requestAnimationFrame(() => {
expect(Helmet.peek().title).to.be.equal("Fancy title");
Helmet.canUseDOM = false;
expect(Helmet.peek().title).to.be.equal("Fancy title");
Helmet.canUseDOM = true;
done();
});
});
it("encodes special characters", done => {
ReactDOM.render(
<Helmet>
<meta
name="description"
content={'This is "quoted" text and & and \'.'}
/>
</Helmet>,
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.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(
`<meta name="description" content="This is "quoted" text and & and '." ${HELMET_ATTRIBUTE}="true">`
);
done();
});
});
it("does not change the DOM if it recevies identical props", done => {
const spy = sinon.spy();
ReactDOM.render(
<Helmet onChangeClientState={spy}>
<meta name="description" content="Test description" />
<title>Test Title</title>
</Helmet>,
container
);
requestAnimationFrame(() => {
// Re-rendering will pass new props to an already mounted Helmet
ReactDOM.render(
<Helmet onChangeClientState={spy}>
<meta name="description" content="Test description" />
<title>Test Title</title>
</Helmet>,
container
);
requestAnimationFrame(() => {
expect(spy.callCount).to.equal(1);
done();
});
});
});
it("does not write the DOM if the client and server are identical", done => {
headElement.innerHTML = `<script ${HELMET_ATTRIBUTE}="true" src="http://localhost/test.js" type="text/javascript" />`;
const spy = sinon.spy();
ReactDOM.render(
<Helmet onChangeClientState={spy}>
gitextract_v89gdlt3/
├── .babelrc
├── .editorconfig
├── .eslintrc
├── .gitignore
├── .npmignore
├── .nvmrc
├── .travis.yml
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── ISSUE_TEMPLATE.md
├── LICENSE
├── README.md
├── karma.config.js
├── package.json
├── rollup.config.js
├── src/
│ ├── Helmet.js
│ ├── HelmetConstants.js
│ └── HelmetUtils.js
└── test/
├── HelmetDeclarativeTest.js
└── HelmetTest.js
SYMBOL INDEX (21 symbols across 5 files)
FILE: karma.config.js
function normalizationBrowserName (line 4) | function normalizationBrowserName(browser) {
FILE: src/Helmet.js
method canUseDOM (line 88) | static set canUseDOM(canUseDOM) {
method shouldComponentUpdate (line 92) | shouldComponentUpdate(nextProps) {
method mapNestedChildrenToProps (line 96) | mapNestedChildrenToProps(child, nestedChildren) {
method flattenArrayTypeChildren (line 121) | flattenArrayTypeChildren({
method mapObjectTypeChildren (line 139) | mapObjectTypeChildren({
method mapArrayTypeChildrenToProps (line 172) | mapArrayTypeChildrenToProps(arrayTypeChildren, newProps) {
method warnOnInvalidChildren (line 185) | warnOnInvalidChildren(child, nestedChildren) {
method mapChildrenToProps (line 226) | mapChildrenToProps(children, newProps) {
method render (line 273) | render() {
FILE: src/HelmetConstants.js
constant ATTRIBUTE_NAMES (line 1) | const ATTRIBUTE_NAMES = {
constant TAG_NAMES (line 7) | const TAG_NAMES = {
constant VALID_TAG_NAMES (line 20) | const VALID_TAG_NAMES = Object.values(TAG_NAMES);
constant TAG_PROPERTIES (line 22) | const TAG_PROPERTIES = {
constant REACT_TAG_MAP (line 36) | const REACT_TAG_MAP = {
constant HELMET_PROPS (line 47) | const HELMET_PROPS = {
constant HTML_TAG_MAP (line 55) | const HTML_TAG_MAP = Object.keys(REACT_TAG_MAP).reduce((obj, key) => {
constant SELF_CLOSING_TAGS (line 60) | const SELF_CLOSING_TAGS = [
constant HELMET_ATTRIBUTE (line 66) | const HELMET_ATTRIBUTE = "data-react-helmet";
FILE: test/HelmetDeclarativeTest.js
constant HELMET_ATTRIBUTE (line 12) | const HELMET_ATTRIBUTE = "data-react-helmet";
FILE: test/HelmetTest.js
constant HELMET_ATTRIBUTE (line 10) | const HELMET_ATTRIBUTE = "data-react-helmet";
Condensed preview — 21 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (352K chars).
[
{
"path": ".babelrc",
"chars": 358,
"preview": "{\n \"presets\": [\n [\"env\", {\n \"targets\": {\n \"browsers\": [\"last 1 versions\", \"ie >= 10\""
},
{
"path": ".editorconfig",
"chars": 271,
"preview": "# editorconfig.org\nroot = true\n\n[*]\nindent_style = space\nindent_size = 4\nend_of_line = lf\ncharset = utf-8\ntrim_trailing_"
},
{
"path": ".eslintrc",
"chars": 659,
"preview": "{\n \"extends\": [\"nfl\", \"prettier\"],\n \"env\": {\n \"browser\": true,\n \"mocha\": true,\n \"es6\": true\n "
},
{
"path": ".gitignore",
"chars": 72,
"preview": ".DS_Store\n.eslintcache\n.idea\ncoverage\nes\nlib\nnode_modules\nnpm-debug.log\n"
},
{
"path": ".npmignore",
"chars": 162,
"preview": ".arcconfig\n.babelrc\n.editorconfig\n.eslintrc\n.nvmrc\n*.yml\nCHANGELOG.md\nCONTRIBUTING.md\ncoverage\nkarma.config.js\ntest/\nnod"
},
{
"path": ".nvmrc",
"chars": 7,
"preview": "v6.6.0\n"
},
{
"path": ".travis.yml",
"chars": 286,
"preview": "language: node_js\n\nservices:\n - xvfb\n\naddons:\n apt:\n packages:\n - google-chrome-stable\n\nnode_js:\n - \"node\"\n "
},
{
"path": "CHANGELOG.md",
"chars": 11881,
"preview": "<a name=\"6.1.0\"></a>\n\n# [6.1.0](https://github.com/nfl/react-helmet/compare/6.0.0...6.1.0) (2020-06-08)\n\n### Features\n\n-"
},
{
"path": "CODE_OF_CONDUCT.md",
"chars": 3349,
"preview": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nIn the interest of fostering an open and welcoming environment, w"
},
{
"path": "CONTRIBUTING.md",
"chars": 2092,
"preview": "<!-- START doctoc generated TOC please keep comment here to allow auto update -->\n<!-- DON'T EDIT THIS SECTION, INSTEAD "
},
{
"path": "ISSUE_TEMPLATE.md",
"chars": 677,
"preview": "**Do you want to request a *feature* or report a *bug*?**\n\n**What is the current behavior?**\n\n**If the current behavior "
},
{
"path": "LICENSE",
"chars": 1047,
"preview": "Copyright (c) 2015 NFL\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software an"
},
{
"path": "README.md",
"chars": 8171,
"preview": "<img align=\"right\" width=\"200\" src=\"http://static.nfl.com/static/content/public/static/img/logos/react-helmet.jpg\" />\n\n#"
},
{
"path": "karma.config.js",
"chars": 3384,
"preview": "// Karma configuration\n\nmodule.exports = function(config) {\n function normalizationBrowserName(browser) {\n ret"
},
{
"path": "package.json",
"chars": 2938,
"preview": "{\n \"name\": \"react-helmet\",\n \"description\": \"A document head manager for React\",\n \"version\": \"6.1.0\",\n \"main\": \"./lib"
},
{
"path": "rollup.config.js",
"chars": 680,
"preview": "import babel from \"rollup-plugin-babel\";\n\nimport pkg from \"./package.json\";\n\nconst baseConfig = {\n input: \"src/Helmet"
},
{
"path": "src/Helmet.js",
"chars": 10384,
"preview": "import React from \"react\";\nimport PropTypes from \"prop-types\";\nimport withSideEffect from \"react-side-effect\";\nimport is"
},
{
"path": "src/HelmetConstants.js",
"chars": 1486,
"preview": "export const ATTRIBUTE_NAMES = {\n BODY: \"bodyAttributes\",\n HTML: \"htmlAttributes\",\n TITLE: \"titleAttributes\"\n};"
},
{
"path": "src/HelmetUtils.js",
"chars": 21134,
"preview": "import React from \"react\";\nimport objectAssign from \"object-assign\";\nimport {\n ATTRIBUTE_NAMES,\n HELMET_ATTRIBUTE,"
},
{
"path": "test/HelmetDeclarativeTest.js",
"chars": 139113,
"preview": "/* eslint max-nested-callbacks: [1, 7] */\n/* eslint-disable react/jsx-sort-props */\n/* eslint-disable jsx-a11y/html-has-"
},
{
"path": "test/HelmetTest.js",
"chars": 128139,
"preview": "/* eslint max-nested-callbacks: [1, 7] */\n/* eslint-disable import/no-named-as-default */\n\nimport React from \"react\";\nim"
}
]
About this extraction
This page contains the full source code of the nfl/react-helmet GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 21 files (328.4 KB), approximately 59.6k tokens, and a symbol index with 21 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.