-
Notifications
You must be signed in to change notification settings - Fork 45
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
15 changed files
with
400 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
global.fetch = require('jest-fetch-mock') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
/* eslint-env jest */ | ||
|
||
import { inlineUrlsInAttributes, urlToDataUri, removeNode } from 'src/freeze-dry/common' | ||
import * as responseToDataUri from 'src/util/response-to-data-uri' | ||
import { dataURLToBlob } from 'blob-util' | ||
|
||
|
||
const imageDataUri = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAAAAAA6fptVAAAACklEQVR4nGNiAAAABgADNjd8qAAAAABJRU5ErkJggg==' | ||
|
||
beforeEach(() => { | ||
fetch.resetMocks() | ||
}) | ||
|
||
describe('removeNode', () => { | ||
test('should remove the node', () => { | ||
const parser = new DOMParser() | ||
const doc = parser.parseFromString( | ||
'<html><head></head><body></body></html>', | ||
'text/html' | ||
) | ||
removeNode(doc.querySelector('head')) | ||
expect(doc.querySelector('head')).toBeNull() | ||
}) | ||
}) | ||
|
||
describe('urlToDataUri', () => { | ||
test('should return a dataUri given a URL', async () => { | ||
const someDataUri = 'data:text/html,<h1>bananas</h1>' | ||
const spy = jest.spyOn(responseToDataUri, 'default').mockImplementation(async () => { | ||
return someDataUri | ||
}) | ||
const dataUri = await urlToDataUri('https://example.com/page') | ||
expect(dataUri).toBe(someDataUri) | ||
spy.mockRestore() | ||
}) | ||
|
||
test('should return a "about:invalid" upon failure', async () => { | ||
const spy = jest.spyOn(responseToDataUri, 'default').mockImplementation(async () => { | ||
throw new Error('mock error') | ||
}) | ||
const dataUri = await urlToDataUri('http://example.com') | ||
expect(dataUri).toBe('about:invalid') | ||
spy.mockRestore() | ||
}) | ||
|
||
test('should return a "about:invalid" when fetching fails', async () => { | ||
fetch.mockRejectOnce() | ||
const dataUri = await urlToDataUri('http://example.com') | ||
expect(dataUri).toBe('about:invalid') | ||
}) | ||
}) | ||
|
||
describe('inlineUrlsInAttributes', () => { | ||
const docUrl = 'https://example.com/page' | ||
const parser = new DOMParser() | ||
let imageBlob | ||
|
||
beforeAll(async () => { | ||
imageBlob = await dataURLToBlob(imageDataUri) | ||
}) | ||
|
||
test('should change the URL in <img> tag to a dataUri', async () => { | ||
fetch.mockResponseOnce(imageBlob) | ||
const doc = parser.parseFromString( | ||
'<html><body><img src="public/image/background.png" alt="background" /></body></html>', | ||
'text/html' | ||
) | ||
const rootElement = doc.documentElement | ||
await inlineUrlsInAttributes({elements: 'img', attributes: 'src', rootElement, docUrl}) | ||
expect(rootElement.querySelector('img').getAttribute('data-original-src')).toBe('public/image/background.png') | ||
expect(rootElement.querySelector('img').getAttribute('src')).toBe(imageDataUri) | ||
}) | ||
|
||
test('should change the URL in the <link> tag to a dataUri', async () => { | ||
fetch.mockResponseOnce(imageBlob) | ||
const doc = parser.parseFromString( | ||
'<html><head><link rel="icon" href="public/image/favicon.ico"></head></html>', | ||
'text/html' | ||
) | ||
const rootElement = doc.documentElement | ||
await inlineUrlsInAttributes({elements: 'link', attributes: 'href', rootElement, docUrl}) | ||
expect(rootElement.querySelector('link').getAttribute('data-original-href')).toBe('public/image/favicon.ico') | ||
expect(rootElement.querySelector('link').getAttribute('href')).toBe(imageDataUri) | ||
}) | ||
|
||
test('should remove the attribute integrity from the tag', async () => { | ||
const doc = parser.parseFromString( | ||
`<html> | ||
<head> | ||
<link | ||
href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" | ||
rel="stylesheet" | ||
integrity="sha256-MfvZlkHCEqatNoGiOXveE8FIwMzZg4W85qfrfIFBfYc=" | ||
> | ||
</head> | ||
</html>`, | ||
'text/html' | ||
) | ||
const rootElement = doc.documentElement | ||
await inlineUrlsInAttributes({elements: 'link', attributes: 'href', fixIntegrity: true, rootElement, docUrl}) | ||
expect(rootElement.querySelector('link').getAttribute('integrity')).toBeNull() | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
/* eslint-env jest */ | ||
|
||
import fixLinks from 'src/freeze-dry/fix-links' | ||
|
||
|
||
describe('fixLinks', () => { | ||
const docUrl = 'https://example.com/page' | ||
|
||
test('should insert the <base> element into <head>', async () => { | ||
const doc = window.document.implementation.createHTMLDocument() | ||
const rootElement = doc.documentElement | ||
doc.querySelector('head').insertAdjacentElement = jest.fn() | ||
const docUrl = 'about:blank' | ||
await fixLinks({rootElement, docUrl}) | ||
const base = document.createElement('base') | ||
base.href = docUrl | ||
// XXX insertAdjacentElement is not yet implemented in jsdom. This test is a placeholder | ||
// until a better solution arises. | ||
expect(rootElement.querySelector('head').insertAdjacentElement) | ||
.toBeCalledWith('afterbegin', base) | ||
}) | ||
|
||
test('should do nothing for absolute URLs', async () => { | ||
const rootElement = window.document.createElement('div') | ||
rootElement.innerHTML = '<a href="https://example.com/#home">Link</a>' | ||
await fixLinks({rootElement, docUrl}) | ||
expect(rootElement.querySelector('*[href]').getAttribute('href')) | ||
.toBe('https://example.com/#home') | ||
}) | ||
|
||
test('should make relative URLs absolute', async () => { | ||
const rootElement = window.document.createElement('div') | ||
rootElement.innerHTML = '<a href="otherpage#home">Link</a>' | ||
await fixLinks({rootElement, docUrl}) | ||
expect(rootElement.querySelector('*[href]').getAttribute('href')) | ||
.toBe('https://example.com/otherpage#home') | ||
}) | ||
|
||
test('should not alter inline javascript in href attribute', async () => { | ||
const rootElement = window.document.createElement('div') | ||
rootElement.innerHTML = `<a href="javascript:alert('Hello');">Link</a>` | ||
await fixLinks({rootElement, docUrl}) | ||
expect(rootElement.querySelector('*[href]').getAttribute('href')) | ||
.toBe(`javascript:alert('Hello');`) | ||
}) | ||
|
||
test('should not alter mailto: URIs in href attribute', async () => { | ||
const rootElement = window.document.createElement('div') | ||
rootElement.innerHTML = `<a href="mailto:someone@example.com">Link</a>` | ||
await fixLinks({rootElement, docUrl}) | ||
expect(rootElement.querySelector('*[href]').getAttribute('href')) | ||
.toBe(`mailto:someone@example.com`) | ||
}) | ||
|
||
test('should not alter data urls in href attribute', async () => { | ||
const datauri = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAAAAAA6fptVAAAACklEQVR4nGNiAAAABgADNjd8qAAAAABJRU5ErkJggg==' | ||
const rootElement = window.document.createElement('div') | ||
rootElement.innerHTML = `<a href="${datauri}">Link</a>` | ||
await fixLinks({rootElement, docUrl}) | ||
expect(rootElement.querySelector('*[href]').getAttribute('href')).toBe(datauri) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
/* eslint-env jest */ | ||
/* eslint import/namespace: "off" */ | ||
|
||
import freezeDry from 'src/freeze-dry/index' | ||
import * as inlineStyles from 'src/freeze-dry/inline-styles' | ||
import * as removeScripts from 'src/freeze-dry/remove-scripts' | ||
import * as inlineImages from 'src/freeze-dry/inline-images' | ||
import * as setContentSecurityPolicy from 'src/freeze-dry/set-content-security-policy' | ||
import * as fixLinks from 'src/freeze-dry/fix-links' | ||
import * as removeNoscripts from './remove-noscripts' | ||
|
||
|
||
beforeAll(() => { | ||
inlineStyles.default = jest.fn() | ||
removeScripts.default = jest.fn() | ||
inlineImages.default = jest.fn() | ||
fixLinks.default = jest.fn() | ||
setContentSecurityPolicy.default = jest.fn() | ||
removeNoscripts.default = jest.fn() | ||
}) | ||
|
||
describe('freezeDry', () => { | ||
test('should run all the jobs', async () => { | ||
await freezeDry() | ||
expect(inlineStyles.default).toHaveBeenCalled() | ||
expect(removeScripts.default).toHaveBeenCalled() | ||
expect(inlineImages.default).toHaveBeenCalled() | ||
expect(fixLinks.default).toHaveBeenCalled() | ||
expect(setContentSecurityPolicy.default).toHaveBeenCalled() | ||
expect(removeNoscripts.default).toHaveBeenCalled() | ||
}) | ||
|
||
test('should return the HTML document as a string', async () => { | ||
// XXX We depend on the default empty document provided by jest. | ||
const html = await freezeDry() | ||
expect(html).toBe('<html><head></head><body></body></html>') | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
/* eslint-env jest */ | ||
/* eslint import/namespace: "off" */ | ||
|
||
import inlineImages, { getUrlsFromSrcset } from 'src/freeze-dry/inline-images' | ||
import * as common from 'src/freeze-dry/common' | ||
|
||
|
||
describe('getUrlsFromSrcset', () => { | ||
test('should return URLs for srcset', () => { | ||
const srcset = 'https://example.com/background1.jpg 0.5x, https://example.com/background2.jpg 1x, background3.jpg 2x' | ||
const urls = getUrlsFromSrcset(srcset) | ||
expect(urls[0]).toBe('https://example.com/background1.jpg') | ||
expect(urls[1]).toBe('https://example.com/background2.jpg') | ||
expect(urls[2]).toBe('background3.jpg') | ||
}) | ||
}) | ||
|
||
describe('inlineImages', () => { | ||
test('should call inlineUrlsInAttributes', async () => { | ||
common.inlineUrlsInAttributes = jest.fn() | ||
const doc = window.document.implementation.createHTMLDocument() | ||
const rootElement = doc.documentElement | ||
await inlineImages({rootElement, docUrl: 'about:blank'}) | ||
expect(common.inlineUrlsInAttributes).toHaveBeenCalledTimes(3) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
/* eslint-env jest */ | ||
|
||
import inlineStyles from 'src/freeze-dry/inline-styles' | ||
import * as common from 'src/freeze-dry/common' | ||
import { dataURLToBlob } from 'blob-util' | ||
|
||
|
||
const imageDataUri = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAAAAAA6fptVAAAACklEQVR4nGNiAAAABgADNjd8qAAAAABJRU5ErkJggg==' | ||
|
||
describe('inlineStyles', () => { | ||
const parser = new DOMParser() | ||
let imageBlob | ||
|
||
beforeAll(async () => { | ||
imageBlob = await dataURLToBlob(imageDataUri) | ||
}) | ||
|
||
test('should return <style> tag with the fetched stylesheet', async () => { | ||
const styleSheet = 'div{background-image: url("public/image/background.jpeg");}' | ||
fetch.mockResponseOnce(new Blob([styleSheet])) | ||
fetch.mockResponseOnce(imageBlob) | ||
const doc = parser.parseFromString( | ||
`<html> | ||
<head> | ||
<link | ||
rel="stylesheet" | ||
type="text/css" | ||
href="https://example.com/theme.css" | ||
> | ||
</head> | ||
</html>`, | ||
'text/html' | ||
) | ||
const docUrl = 'https://example.com' | ||
await inlineStyles({rootElement: doc.documentElement, docUrl}) | ||
expect(doc.querySelector('style').innerHTML) | ||
.toBe(`div{background-image: url(${imageDataUri});}`) | ||
}) | ||
|
||
test('should convert the url in <style> to dataUris', async () => { | ||
const styleSheet = 'div{background-image: url("public/image/background.jpeg");}' | ||
fetch.mockResponseOnce(new Blob([styleSheet])) | ||
const styleSheetAsDataUri = `data:text/plain;charset=utf-8;base64,aHR0cHM6Ly9leGFtcGxlLmNvbS9wdWJsaWMvaW1hZ2UvYmFja2dyb3VuZC5qcGVn` | ||
const spy = jest.spyOn(common, 'urlToDataUri').mockReturnValue(styleSheetAsDataUri) | ||
const doc = parser.parseFromString( | ||
`<html> | ||
<head> | ||
<style type="text/css"> | ||
div{background-image: url("public/image/background.jpeg");} | ||
</style> | ||
</head> | ||
<div> | ||
</div> | ||
</html>`, | ||
'text/html' | ||
) | ||
const docUrl = 'https://example.com' | ||
await inlineStyles({rootElement: doc.documentElement, docUrl}) | ||
expect(spy).toHaveBeenCalled() | ||
expect(doc.querySelector('style').innerHTML.trim()) | ||
.toBe(`div{background-image: url(${styleSheetAsDataUri});}`) | ||
spy.mockRestore() | ||
}) | ||
|
||
test('should convert the urls in a style attribute to data uris', async () => { | ||
const spy = jest.spyOn(common, 'urlToDataUri').mockReturnValue(imageDataUri) | ||
const doc = parser.parseFromString( | ||
'<html><div style="background-image: url(\'public/image/background.jpeg\');"></div></html>', | ||
'text/html' | ||
) | ||
const docUrl = 'https://example.com' | ||
await inlineStyles({rootElement: doc.documentElement, docUrl}) | ||
expect(spy).toHaveBeenCalled() | ||
expect(doc.querySelector('div').getAttribute('style')) | ||
.toBe(`background-image: url(${imageDataUri});`) | ||
spy.mockRestore() | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
/* eslint-env jest */ | ||
|
||
import removeNoscripts from 'src/freeze-dry/remove-noscripts' | ||
import * as common from 'src/freeze-dry/common' | ||
|
||
|
||
describe('removeNoscripts', () => { | ||
test('should call removeNode for every <noscript> tag', () => { | ||
const spy = jest.spyOn(common, 'removeNode') | ||
const parser = new DOMParser() | ||
const doc = parser.parseFromString( | ||
'<html><head><noscript>No script was loaded</noscript><noscript>Your browser does not support JavaScript</noscript></head></html>', | ||
'text/html' | ||
) | ||
const rootElement = doc.documentElement | ||
removeNoscripts({rootElement}) | ||
expect(spy).toHaveBeenCalledTimes(2) | ||
}) | ||
|
||
test('should remove all the <noscript> tags from the node', () => { | ||
const parser = new DOMParser() | ||
const doc = parser.parseFromString( | ||
'<html><head><noscript>No script was loaded</noscript><noscript>Your browser does not support JavaScript</noscript></head></html>', | ||
'text/html' | ||
) | ||
const rootElement = doc.documentElement | ||
removeNoscripts({rootElement}) | ||
expect(rootElement.querySelector('noscript')).toBeNull() | ||
}) | ||
}) |
Oops, something went wrong.