diff --git a/__tests__/api/base.test.tsx b/__tests__/api/base.test.tsx index 66c2ffb4..bc43f4cb 100644 --- a/__tests__/api/base.test.tsx +++ b/__tests__/api/base.test.tsx @@ -32,7 +32,20 @@ describe('base tag', () => { expect(existingTags).toHaveLength(0); }); - it("tags without 'href' are not accepted", () => { + it("tags with only 'target' are accepted", () => { + render(); + const existingTags = [...document.head.querySelectorAll(`base[${HELMET_ATTRIBUTE}]`)]; + const [firstTag] = existingTags; + + expect(existingTags).toBeDefined(); + expect(existingTags).toHaveLength(1); + expect(firstTag).toBeInstanceOf(Element); + expect(firstTag.getAttribute).toBeDefined(); + expect(firstTag).toHaveAttribute('target', '_blank'); + expect(firstTag).not.toHaveAttribute('href'); + }); + + it("tags without 'href' or 'target' are not accepted", () => { render(); const existingTags = document.head.querySelectorAll(`base[${HELMET_ATTRIBUTE}]`); @@ -99,7 +112,30 @@ describe('base tag', () => { expect(existingTags).toHaveLength(0); }); - it("tags without 'href' are not accepted", () => { + it("tags with only 'target' are accepted", () => { + render( + + + + ); + + const existingTags = [...document.head.querySelectorAll(`base[${HELMET_ATTRIBUTE}]`)]; + const [firstTag] = existingTags; + + expect(existingTags).toBeDefined(); + expect(existingTags).toHaveLength(1); + expect(firstTag).toBeInstanceOf(Element); + expect(firstTag.getAttribute).toBeDefined(); + expect(firstTag).toHaveAttribute('target', '_blank'); + expect(firstTag).not.toHaveAttribute('href'); + }); + + /** + * From the spec: + * https://html.spec.whatwg.org/multipage/semantics.html#the-base-element + * > The `base` element must have either an `href` attribute, a `target` attribute, or both. + */ + it("tags without 'href' or 'target' are not accepted", () => { render( diff --git a/__tests__/server/base.test.tsx b/__tests__/server/base.test.tsx index af8b09af..3cf77a70 100644 --- a/__tests__/server/base.test.tsx +++ b/__tests__/server/base.test.tsx @@ -43,6 +43,31 @@ describe('server', () => { expect(head.base.toString).toBeDefined(); expect(head.base.toString()).toMatchSnapshot(); }); + + it("renders base tag with only 'target' as React component", () => { + const head = renderContext(); + + expect(head.base).toBeDefined(); + expect(head.base.toComponent).toBeDefined(); + + const baseComponent = head.base.toComponent(); + + expect(baseComponent).toEqual(isArray); + expect(baseComponent).toHaveLength(1); + + const markup = ReactServer.renderToStaticMarkup(baseComponent); + + expect(markup).toContain('target="_blank"'); + expect(markup).not.toContain('href='); + }); + + it("renders base tag with only 'target' as string", () => { + const head = renderContext(); + expect(head.base).toBeDefined(); + expect(head.base.toString).toBeDefined(); + expect(head.base.toString()).toContain('target="_blank"'); + expect(head.base.toString()).not.toContain('href='); + }); }); describe('Declarative API', () => { @@ -81,5 +106,39 @@ describe('server', () => { expect(head.base.toString).toBeDefined(); expect(head.base.toString()).toMatchSnapshot(); }); + + it("renders base tag with only 'target' as React component", () => { + const head = renderContext( + + + + ); + + expect(head.base).toBeDefined(); + expect(head.base.toComponent).toBeDefined(); + + const baseComponent = head.base.toComponent(); + + expect(baseComponent).toEqual(isArray); + expect(baseComponent).toHaveLength(1); + + const markup = ReactServer.renderToStaticMarkup(baseComponent); + + expect(markup).toContain('target="_blank"'); + expect(markup).not.toContain('href='); + }); + + it("renders base tag with only 'target' as string", () => { + const head = renderContext( + + + + ); + + expect(head.base).toBeDefined(); + expect(head.base.toString).toBeDefined(); + expect(head.base.toString()).toContain('target="_blank"'); + expect(head.base.toString()).not.toContain('href='); + }); }); }); diff --git a/src/constants.ts b/src/constants.ts index 401f0ea7..b0b97d22 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -9,6 +9,7 @@ export enum TAG_PROPERTIES { PROPERTY = 'property', REL = 'rel', SRC = 'src', + TARGET = 'target', } export enum ATTRIBUTE_NAMES { diff --git a/src/utils.ts b/src/utils.ts index 31501eee..ca6770db 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -204,7 +204,7 @@ const getAnyTrueFromPropsList = (propsList: PropsList, checkedTag: string) => { }; const reducePropsToState = (propsList: PropsList) => ({ - baseTag: getBaseTagFromPropsList([TAG_PROPERTIES.HREF], 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),