Skip to content

Commit

Permalink
Merge branch 'test-freezedry'
Browse files Browse the repository at this point in the history
  • Loading branch information
Treora committed Jul 13, 2017
2 parents c7d578f + 314f069 commit 1873c0b
Show file tree
Hide file tree
Showing 15 changed files with 400 additions and 5 deletions.
3 changes: 3 additions & 0 deletions .jest-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@
"js",
"jsx"
],
"setupFiles": [
"./setupJest.js"
],
"unmockedModulePathPatterns": [
"<rootDir>/node_modules/react",
"<rootDir>/node_modules/react-dom",
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@
"identity-obj-proxy": "^3.0.0",
"jest": "^20.0.4",
"jest-cli": "^20.0.4",
"jest-fetch-mock": "^1.1.1",
"jest-fetch-mock": "^1.2.0",
"loose-envify": "^1.3.1",
"pify": "^3.0.0",
"postcss-cssnext": "^2.11.0",
Expand Down
1 change: 1 addition & 0 deletions setupJest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
global.fetch = require('jest-fetch-mock')
103 changes: 103 additions & 0 deletions src/freeze-dry/common.test.js
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()
})
})
62 changes: 62 additions & 0 deletions src/freeze-dry/fix-links.test.js
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)
})
})
1 change: 1 addition & 0 deletions src/freeze-dry/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import inlineImages from './inline-images'
import setContentSecurityPolicy from './set-content-security-policy'
import fixLinks from './fix-links'


export default async function freezeDry (
document = window.document,
docUrl = document.URL,
Expand Down
38 changes: 38 additions & 0 deletions src/freeze-dry/index.test.js
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>')
})
})
3 changes: 2 additions & 1 deletion src/freeze-dry/inline-images.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import whenAllSettled from 'src/util/when-all-settled'
import { inlineUrlsInAttributes } from './common'

function getUrlsFromSrcset(srcsetValue) {

export function getUrlsFromSrcset(srcsetValue) {
// srcset example: srcset="http://image 2x, http://other-image 1.5x"
const URLs = srcsetValue.split(',').map(srcsetItem =>
srcsetItem.trim().split(/\s+/)[0]
Expand Down
26 changes: 26 additions & 0 deletions src/freeze-dry/inline-images.test.js
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)
})
})
1 change: 1 addition & 0 deletions src/freeze-dry/inline-styles.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import whenAllSettled from 'src/util/when-all-settled'
import { urlToDataUri } from './common'


// Finds all url(...) occurrances in a string of CSS, then fetches and inlines
// them as data URIs.
// Returns the processed (and possibly much larger) string of CSS.
Expand Down
78 changes: 78 additions & 0 deletions src/freeze-dry/inline-styles.test.js
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()
})
})
30 changes: 30 additions & 0 deletions src/freeze-dry/remove-noscripts.test.js
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()
})
})
Loading

0 comments on commit 1873c0b

Please sign in to comment.