UNPKG

inlineresources

Version:

Inlines style sheets, images, fonts and scripts in HTML documents. Works in the browser.

1,359 lines (1,098 loc) 51.3 kB
"use strict"; var inlineCss = require("../../src/inlineCss"), util = require("../../src/util"), backgroundValueParser = require("../../src/backgroundValueParser"); describe("Inline CSS content", function () { var joinUrlSpy, ajaxSpy, binaryAjaxSpy, getDataURIForImageURLSpy; var parseRules = function (text) { var doc = document.implementation.createHTMLDocument(""), style = doc.createElement("style"); style.textContent = text; doc.documentElement.appendChild(style); return Array.prototype.slice.call(style.sheet.cssRules); }; beforeEach(function () { joinUrlSpy = spyOn(util, "joinUrl").and.callFake(function (base, url) { return url; }); ajaxSpy = spyOn(util, "ajax"); binaryAjaxSpy = spyOn(util, "binaryAjax"); getDataURIForImageURLSpy = spyOn(util, "getDataURIForImageURL"); }); describe("adjustPathsOfCssResources", function () { var extractCssUrlSpy; beforeEach(function () { extractCssUrlSpy = spyOn( backgroundValueParser, "extractCssUrl" ).and.callFake(function (cssUrl) { if (/^url/.test(cssUrl)) { return cssUrl.replace(/^url\("?/, "").replace(/"?\)$/, ""); } else { throw "error"; } }); }); it("should map background paths relative to the stylesheet", function () { var rules = parseRules( 'div { background-image: url("below/green.png"); }' ); joinUrlSpy.and.callFake(function (base, url) { if (url === "below/green.png" && base === "theBase/some.css") { return "theBase/below/green.png"; } }); inlineCss.adjustPathsOfCssResources("theBase/some.css", rules); expect(rules[0].style.getPropertyValue("background-image")).toMatch( /url\(\"?theBase\/below\/green\.png\"?\)/ ); }); it("should map font paths relative to the stylesheet", function () { var rules = parseRules( '@font-face { font-family: "test font"; src: url("fake.woff"); }' ); joinUrlSpy.and.callFake(function (base, url) { if (url === "fake.woff" && base === "below/some.css") { return "below/fake.woff"; } }); inlineCss.adjustPathsOfCssResources("below/some.css", rules); expect(rules[0].style.getPropertyValue("src")).toMatch( /url\(\"?below\/fake\.woff\"?\)/ ); }); it("should map import paths relative to the stylesheet", function () { var rules = parseRules("@import url(my.css);"); joinUrlSpy.and.callFake(function (base, url) { if (url === "my.css" && base === "below/some.css") { return "below/my.css"; } }); inlineCss.adjustPathsOfCssResources("below/some.css", rules); expect(rules[0].href).toEqual("below/my.css"); }); it("should report changes", function () { var rules = parseRules("@import url(my.css);"); joinUrlSpy.and.callFake(function () { return "below/my.css"; }); var hasChanges = inlineCss.adjustPathsOfCssResources( "below/some.css", rules ); expect(hasChanges).toBe(true); }); it("should report no changes", function () { var rules = parseRules( 'div { background-image: url("data:image/png;base64,someDataUri");' ); var hasChanges = inlineCss.adjustPathsOfCssResources( "below/some.css", rules ); expect(hasChanges).toBe(false); }); it("should keep all src references intact when mapping resource paths", function () { var rules = parseRules( '@font-face { font-family: "test font"; src: local("some font"), url("fake.woff"); }' ); joinUrlSpy.and.callFake(function (base, url) { if (base === "some_url/some.css") { return "some_url/" + url; } }); inlineCss.adjustPathsOfCssResources("some_url/some.css", rules); expect(rules[0].style.getPropertyValue("src")).toMatch( /local\("?some font"?\), url\(\"?some_url\/fake\.woff\"?\)/ ); }); it("should keep the font-family when inlining with Webkit", function () { var rules = parseRules( "@font-face { font-family: 'test font'; src: url(\"fake.woff\"); }" ); joinUrlSpy.and.callFake(function (base, url) { if (base === "some_url/some.css") { return "some_url/" + url; } }); inlineCss.adjustPathsOfCssResources("some_url/some.css", rules); expect(rules[0].style.getPropertyValue("font-family")).toMatch( /["']test font["']/ ); }); it("should keep the font-style when inlining with Webkit", function () { var rules = parseRules( "@font-face { font-family: 'test font'; font-style: italic; src: url(\"fake.woff\"); }" ); joinUrlSpy.and.callFake(function (base, url) { if (base === "some_url/some.css") { return "some_url/" + url; } }); inlineCss.adjustPathsOfCssResources("some_url/some.css", rules); expect(rules[0].style.getPropertyValue("font-style")).toEqual( "italic" ); }); it("should keep the font-weight when inlining with Webkit", function () { var rules = parseRules( "@font-face { font-family: 'test font'; font-weight: 700; src: url(\"fake.woff\"); }" ); joinUrlSpy.and.callFake(function (base, url) { if (base === "some_url/some.css") { return "some_url/" + url; } }); inlineCss.adjustPathsOfCssResources("some_url/some.css", rules); expect(rules[0].style.getPropertyValue("font-weight")).toEqual( "700" ); }); it("should keep the !important specifity override", function () { var rules = parseRules( 'div { background-image: url("../green.png") !important; }' ); joinUrlSpy.and.callFake(function () { return "green.png"; }); inlineCss.adjustPathsOfCssResources("below/some.css", rules); expect(rules[0].cssText).toMatch(/\! ?important/); }); }); describe("loadCSSImportsForRules", function () { var adjustPathsOfCssResourcesSpy, ajaxUrlMocks = {}; var setupAjaxMock = function () { ajaxSpy.and.callFake(function (url) { if (ajaxUrlMocks[url] !== undefined) { return Promise.resolve(ajaxUrlMocks[url]); } else { return Promise.reject({ url: "THEURL" + url, }); } }); }; var mockAjaxUrl = function (url, content) { ajaxUrlMocks[url] = content; }; beforeEach(function () { adjustPathsOfCssResourcesSpy = spyOn( inlineCss, "adjustPathsOfCssResources" ); setupAjaxMock(); }); it("should replace an import with the content of the given URL", function (done) { var rules = parseRules('@import url("that.css");'); mockAjaxUrl("that.css", "p { font-size: 10px; }"); inlineCss .loadCSSImportsForRules(rules, [], {}) .then(function (result) { expect(result.hasChanges).toBe(true); expect(rules.length).toEqual(1); expect(rules[0].cssText).toMatch( /p \{\s*font-size: 10px;\s*\}/ ); done(); }); }); it("should inline multiple linked CSS and keep order", function (done) { var rules = parseRules( '@import url("this.css");\n' + '@import url("that.css");' ); mockAjaxUrl("this.css", "div { display: inline-block; }"); mockAjaxUrl("that.css", "p { font-size: 10px; }"); inlineCss.loadCSSImportsForRules(rules, [], {}).then(function () { expect(rules[0].cssText).toMatch( /div \{\s*display: inline-block;\s*\}/ ); expect(rules[1].cssText).toMatch( /p \{\s*font-size: 10px;\s*\}/ ); done(); }); }); it("should support an import without the functional url() form", function (done) { var rules = parseRules('@import "that.css";'); mockAjaxUrl("that.css", ""); inlineCss.loadCSSImportsForRules(rules, [], {}).then(function () { expect(ajaxSpy).toHaveBeenCalledWith( "that.css", jasmine.any(Object) ); done(); }); }); it("should handle empty content", function (done) { var rules = parseRules('@import url("that.css");'); mockAjaxUrl("that.css", ""); inlineCss.loadCSSImportsForRules(rules, [], {}).then(function () { expect(rules.length).toEqual(0); done(); }); }); it("should not add CSS if no content is given", function (done) { var rules = parseRules( '@import url("that.css");\n' + '@import url("this.css");' ); mockAjaxUrl("that.css", ""); mockAjaxUrl("this.css", "span { font-weight: bold; }"); inlineCss.loadCSSImportsForRules(rules, [], {}).then(function () { expect(rules.length).toEqual(1); done(); }); }); it("should ignore invalid values", function (done) { var rules = parseRules("@import invalid url;"); inlineCss .loadCSSImportsForRules(rules, [], {}) .then(function (result) { expect(result.hasChanges).toBe(false); done(); }); }); it("should not touch unrelated CSS", function (done) { var rules = parseRules("span { padding-left: 0; }"); inlineCss .loadCSSImportsForRules(rules, [], {}) .then(function (result) { expect(result.hasChanges).toBe(false); done(); }); }); it("should not include a document more than once", function (done) { var rules = parseRules( '@import url("that.css");\n' + '@import url("that.css");' ); mockAjaxUrl("that.css", "p { font-size: 12px; }"); inlineCss.loadCSSImportsForRules(rules, [], {}).then(function () { expect(ajaxSpy.calls.count()).toEqual(1); expect(rules.length).toEqual(1); done(); }); }); it("should handle import in an import", function (done) { var rules = parseRules('@import url("this.css");'); mockAjaxUrl("this.css", '@import url("that.css");'); mockAjaxUrl("that.css", "p { font-weight: bold; }"); inlineCss.loadCSSImportsForRules(rules, [], {}).then(function () { expect(rules.length).toEqual(1); expect(rules[0].cssText).toMatch( /p \{\s*font-weight: bold;\s*\}/ ); done(); }); }); it("should handle cyclic imports", function (done) { var rules = parseRules('@import url("this.css");'); mockAjaxUrl( "this.css", '@import url("that.css");\n' + "span { font-weight: 300; }" ); mockAjaxUrl( "that.css", '@import url("this.css");\n' + "p { font-weight: bold; }" ); inlineCss.loadCSSImportsForRules(rules, [], {}).then(function () { expect(rules[0].cssText).toMatch( /p \{\s*font-weight: bold;\s*\}/ ); expect(rules[1].cssText).toMatch( /span \{\s*font-weight: 300;\s*\}/ ); done(); }); }); it("should handle recursive imports", function (done) { var rules = parseRules('@import url("this.css");'); mockAjaxUrl("this.css", '@import url("this.css");'); inlineCss.loadCSSImportsForRules(rules, [], {}).then(function () { expect(ajaxSpy.calls.count()).toEqual(1); expect(rules.length).toEqual(0); done(); }); }); it("should handle a baseUrl", function (done) { var rules = parseRules('@import url("that.css");'); inlineCss .loadCSSImportsForRules(rules, [], { baseUrl: "url_base/page.html", }) .then(function () { expect(joinUrlSpy).toHaveBeenCalledWith( "url_base/page.html", "that.css" ); done(); }); }); it("should map resource paths relative to the stylesheet", function (done) { var rules = parseRules('@import url("url_base/that.css");'); joinUrlSpy.and.callFake(function (base) { if (base === "") { return base; } }); mockAjaxUrl( "url_base/that.css", 'div { background-image: url("../green.png"); }\n' + '@font-face { font-family: "test font"; src: url("fake.woff"); }' ); inlineCss.loadCSSImportsForRules(rules, [], {}).then(function () { expect(adjustPathsOfCssResourcesSpy).toHaveBeenCalledWith( "url_base/that.css", jasmine.any(Object), {} ); expect( adjustPathsOfCssResourcesSpy.calls .mostRecent() .args[1][0].style.getPropertyValue("background-image") ).toMatch(/url\(\"?\.\.\/green\.png\"?\)/); done(); }); }); it("should circumvent caching if requested", function (done) { var rules = parseRules('@import url("that.css");'); inlineCss .loadCSSImportsForRules(rules, [], { cache: "none" }) .then(function () { expect(ajaxSpy).toHaveBeenCalledWith("that.css", { cache: "none", }); done(); }); }); it("should not circumvent caching by default", function (done) { var rules = parseRules('@import url("that.css");'); inlineCss.loadCSSImportsForRules(rules, [], {}).then(function () { expect(ajaxSpy).toHaveBeenCalledWith("that.css", {}); done(); }); }); describe("error handling", function () { beforeEach(function () { joinUrlSpy.and.callThrough(); mockAjaxUrl("existing_document.css", ""); mockAjaxUrl( "existing_with_second_level_nonexisting.css", '@import url("nonexisting.css");' ); }); it("should report an error if a stylesheet could not be loaded", function (done) { var rules = parseRules('@import url("does_not_exist.css");'); inlineCss .loadCSSImportsForRules(rules, [], {}) .then(function (result) { expect(result.hasChanges).toEqual(false); expect(result.errors[0]).toEqual( jasmine.objectContaining({ resourceType: "stylesheet", url: "THEURL" + "does_not_exist.css", msg: "Unable to load stylesheet " + "THEURL" + "does_not_exist.css", }) ); done(); }); }); it("should only report a failing stylesheet as error", function (done) { var rules = parseRules( '@import url("existing_document.css");\n' + '@import url("does_not_exist.css");' ); inlineCss .loadCSSImportsForRules(rules, [], {}) .then(function (result) { expect(result.errors.length).toBe(1); done(); }); }); it("should report multiple failing stylesheets as error", function (done) { var rules = parseRules( '@import url("does_not_exist.css");\n' + '@import url("also_does_not_exist.css");' ); inlineCss .loadCSSImportsForRules(rules, [], {}) .then(function (result) { expect(result.errors).toEqual([ jasmine.any(Object), jasmine.any(Object), ]); expect(result.errors[0]).not.toEqual(result.errors[1]); done(); }); }); it("should report errors from second level @imports", function (done) { var rules = parseRules( '@import url("existing_with_second_level_nonexisting.css");' ); inlineCss .loadCSSImportsForRules(rules, [], {}) .then(function (result) { expect(result.errors[0]).toEqual( jasmine.objectContaining({ resourceType: "stylesheet", url: "THEURL" + "nonexisting.css", }) ); done(); }); }); it("should report an empty list for a successful stylesheet", function (done) { var rules = parseRules('@import url("existing_document.css");'); inlineCss .loadCSSImportsForRules(rules, [], {}) .then(function (result) { expect(result.errors).toEqual([]); done(); }); }); }); }); describe("loadAndInlineCSSResourcesForRules", function () { var extractCssUrlSpy, urlMocks = {}; var setupGetDataURIForImageURLMock = function () { getDataURIForImageURLSpy.and.callFake(function (url) { if (urlMocks[url] !== undefined) { return Promise.resolve(urlMocks[url]); } else { return Promise.reject({ url: "THEURL" + url, }); } }); }; var mockGetDataURIForImageURL = function (imageUrl, imageDataUri) { urlMocks[imageUrl] = imageDataUri; }; beforeEach(function () { extractCssUrlSpy = spyOn( backgroundValueParser, "extractCssUrl" ).and.callFake(function (cssUrl) { if (/^url/.test(cssUrl)) { return cssUrl.replace(/^url\("?/, "").replace(/"?\)$/, ""); } else { throw "error"; } }); setupGetDataURIForImageURLMock(); }); it("should work with empty content", function (done) { inlineCss .loadAndInlineCSSResourcesForRules([], {}) .then(function () { done(); }); }); describe("on background-image", function () { it("should not touch an already inlined background-image", function (done) { var rules = parseRules( 'span { background-image: url("data:image/png;base64,soMEfAkebASE64="); }' ); inlineCss .loadAndInlineCSSResourcesForRules(rules, {}) .then(function (result) { expect(result.hasChanges).toBe(false); expect( rules[0].style.getPropertyValue("background-image") ).toMatch( /url\("?data:image\/png;base64,soMEfAkebASE64="?\)/ ); done(); }); }); it("should ignore invalid values", function (done) { var rules = parseRules( 'span { background-image: "invalid url"; }' ); extractCssUrlSpy.and.callFake(function () { throw new Error("Invalid url"); }); inlineCss .loadAndInlineCSSResourcesForRules(rules, {}) .then(function (result) { expect(result.hasChanges).toBe(false); done(); }); }); it("should ignore an invalid value together with a valid url", function (done) { var rules = parseRules( 'span { background-image: "invalid url", url("valid/url.png"); }' ); extractCssUrlSpy.and.callFake(function (value) { if (value === 'url("valid/url.png")') { return "valid/url.png"; } else { throw new Error("Invalid url"); } }); mockGetDataURIForImageURL( "valid/url.png", "data:image/png;base64,someDataUri" ); inlineCss .loadAndInlineCSSResourcesForRules(rules, {}) .then(function (result) { expect(result.hasChanges).toBe(false); done(); }); }); it("should inline a background-image", function (done) { var anImage = "anImage.png", anImagesDataUri = "data:image/png;base64,someDataUri", rules = parseRules( 'span { background-image: url("' + anImage + '"); }' ); mockGetDataURIForImageURL(anImage, anImagesDataUri); inlineCss .loadAndInlineCSSResourcesForRules(rules, {}) .then(function (result) { expect(result.hasChanges).toBe(true); expect( extractCssUrlSpy.calls.mostRecent().args[0] ).toMatch(new RegExp('url\\("?' + anImage + '"?\\)')); expect( rules[0].style.getPropertyValue("background-image") ).toMatch( new RegExp('url\\("?' + anImagesDataUri + '"?\\)') ); done(); }); }); it("should inline a background declaration", function (done) { var anImage = "anImage.png", anImagesDataUri = "data:image/png;base64,someDataUri", rules = parseRules( 'span { background: url("' + anImage + '") top left, url("data:image/png;base64,someMoreDataUri") #FFF; }' ); mockGetDataURIForImageURL(anImage, anImagesDataUri); inlineCss .loadAndInlineCSSResourcesForRules(rules, {}) .then(function () { expect(rules[0].cssText).toMatch( /(background: [^;]*url\("?data:image\/png;base64,someDataUri"?\).*\s*top\s*.*, .*url\("?data:image\/png;base64,someMoreDataUri"?\).*;)|(background-image:\s*url\("?data:image\/png;base64,someDataUri"?\)\s*,\s*url\("?data:image\/png;base64,someMoreDataUri"?\)\s*;)/ ); done(); }); }); it("should inline multiple background-images in one rule", function (done) { var backgroundImageRegex = /url\("?([^\)"]+)"?\)\s*,\s*url\("?([^\)"]+)"?\)/, anImage = "anImage.png", anImagesDataUri = "data:image/png;base64,someDataUri", aSecondImage = "aSecondImage.png", aSecondImagesDataUri = "data:image/png;base64,anotherDataUri", rules = parseRules( 'span { background-image: url("' + anImage + '"), url("' + aSecondImage + '"); }' ), match; mockGetDataURIForImageURL(anImage, anImagesDataUri); mockGetDataURIForImageURL(aSecondImage, aSecondImagesDataUri); inlineCss .loadAndInlineCSSResourcesForRules(rules, {}) .then(function () { expect( extractCssUrlSpy.calls.mostRecent().args[0] ).toMatch( new RegExp('url\\("?' + aSecondImage + '"?\\)') ); expect( rules[0].style.getPropertyValue("background-image") ).toMatch(backgroundImageRegex); match = backgroundImageRegex.exec( rules[0].style.getPropertyValue("background-image") ); expect(match[1]).toEqual(anImagesDataUri); expect(match[2]).toEqual(aSecondImagesDataUri); done(); }); }); it("should not break background-position (#30)", function (done) { var rules = parseRules( 'span { background-image: url("anImage.png"); background-position: 0 center, right center;}' ); mockGetDataURIForImageURL( "anImage.png", "data:image/png;base64,someDataUri" ); inlineCss .loadAndInlineCSSResourcesForRules(rules, {}) .then(function () { expect( rules[0].style.getPropertyValue( "background-position" ) ).toMatch( /0(px)?( (center|50%))?, (right|100%)( (center|50%))?/ ); done(); }); }); it("should handle a baseUrl", function () { var rules = parseRules( 'span { background-image: url("image.png"); }' ); inlineCss.loadAndInlineCSSResourcesForRules(rules, { baseUrl: "url_base/page.html", }); expect( getDataURIForImageURLSpy.calls.mostRecent().args[1].baseUrl ).toEqual("url_base/page.html"); }); it("should circumvent caching if requested", function () { var anImage = "anImage.png", rules = parseRules( 'span { background-image: url("' + anImage + '"); }' ); inlineCss.loadAndInlineCSSResourcesForRules(rules, { cache: "none", }); expect(getDataURIForImageURLSpy).toHaveBeenCalledWith(anImage, { cache: "none", }); }); it("should not circumvent caching by default", function () { var anImage = "anImage.png", rules = parseRules( 'span { background-image: url("' + anImage + '"); }' ); inlineCss.loadAndInlineCSSResourcesForRules(rules, {}); expect(getDataURIForImageURLSpy).toHaveBeenCalledWith( anImage, {} ); }); it("should keep the !important specifity override", function (done) { var anImage = "anImage.png", rules = parseRules( 'span { background-image: url("' + anImage + '") !important; }' ); mockGetDataURIForImageURL( anImage, "data:image/png;base64,someDataUri" ); inlineCss .loadAndInlineCSSResourcesForRules(rules, {}) .then(function (result) { expect(result.hasChanges).toBe(true); expect(rules[0].cssText).toMatch(/\! ?important/); done(); }); }); it("should not return an error twice for a missing image", function (done) { var rules = parseRules( 'span { background: url("someImage.png"); background-image: url("someImage.png"); }' ); inlineCss .loadAndInlineCSSResourcesForRules(rules, {}) .then(function (result) { expect(result.errors.length).toBe(1); done(); }); }); }); describe("on background-image with errors", function () { var aBackgroundImageThatDoesExist = "a_backgroundImage_that_does_exist.png"; beforeEach(function () { mockGetDataURIForImageURL(aBackgroundImageThatDoesExist, ""); joinUrlSpy.and.callThrough(); }); it("should report an error if a backgroundImage could not be loaded", function (done) { var rules = parseRules( 'span { background-image: url("a_backgroundImage_that_doesnt_exist.png"); }' ); inlineCss .loadAndInlineCSSResourcesForRules(rules, {}) .then(function (result) { expect(result.hasChanges).toBe(false); expect(result.errors[0]).toEqual( jasmine.objectContaining({ resourceType: "backgroundImage", url: "THEURL" + "a_backgroundImage_that_doesnt_exist.png", msg: "Unable to load background-image " + "THEURL" + "a_backgroundImage_that_doesnt_exist.png", }) ); done(); }); }); it("should only report a failing backgroundImage as error", function (done) { var rules = parseRules( 'span { background-image: url("a_backgroundImage_that_doesnt_exist.png"); }\n' + 'span { background-image: url("' + aBackgroundImageThatDoesExist + '"); }' ); inlineCss .loadAndInlineCSSResourcesForRules(rules, {}) .then(function (result) { expect(result.errors.length).toBe(1); done(); }); }); it("should report multiple failing backgroundImages as error", function (done) { var rules = parseRules( 'span { background-image: url("a_backgroundImage_that_doesnt_exist.png"); }\n' + 'span { background-image: url("another_backgroundImage_that_doesnt_exist.png"); }' ); inlineCss .loadAndInlineCSSResourcesForRules(rules, {}) .then(function (result) { var errors = result.errors; expect(errors).toEqual([ jasmine.any(Object), jasmine.any(Object), ]); expect(errors[0]).not.toEqual(errors[1]); done(); }); }); it("should only report one failing backgroundImage for multiple in a rule", function (done) { var rules = parseRules( 'span { background-image: url("' + aBackgroundImageThatDoesExist + '"), url("a_backgroundImage_that_doesnt_exist.png"); }' ); inlineCss .loadAndInlineCSSResourcesForRules(rules, {}) .then(function (result) { expect(result.errors.length).toBe(1); expect(result.errors[0]).toEqual( jasmine.objectContaining({ resourceType: "backgroundImage", url: "THEURL" + "a_backgroundImage_that_doesnt_exist.png", }) ); done(); }); }); it("should report multiple failing backgroundImages in one rule as error", function (done) { var rules = parseRules( 'span { background-image: url("a_backgroundImage_that_doesnt_exist.png"), url("another_backgroundImage_that_doesnt_exist.png"); }' ); inlineCss .loadAndInlineCSSResourcesForRules(rules, {}) .then(function (result) { var errors = result.errors; expect(errors).toEqual([ jasmine.any(Object), jasmine.any(Object), ]); expect(errors[0]).not.toEqual(errors[1]); done(); }); }); it("should report an empty list for a successful backgroundImage", function (done) { var rules = parseRules( 'span { background-image: url("' + aBackgroundImageThatDoesExist + '"); }' ); inlineCss .loadAndInlineCSSResourcesForRules(rules, {}) .then(function (result) { expect(result.errors).toEqual([]); done(); }); }); }); describe("on font-face", function () { var fontFaceSrcRegex = /url\("?([^\)"]+)"?\)(\s*format\("?([^\)"]+)"?\))?/, ajaxUrlMocks = {}; var setupAjaxMock = function () { binaryAjaxSpy.and.callFake(function (url) { if (ajaxUrlMocks[url] !== undefined) { return Promise.resolve(ajaxUrlMocks[url]); } else { return Promise.reject(); } }); }; var mockBinaryAjaxUrl = function (url, content) { ajaxUrlMocks[url] = content; }; var expectFontFaceUrlToMatch = function (rule, url, format) { var extractedUrl, match; match = fontFaceSrcRegex.exec( rule.style.getPropertyValue("src") ); extractedUrl = match[1]; expect(extractedUrl).toEqual(url); if (format) { expect(match[3]).toEqual(format); } }; beforeEach(function () { setupAjaxMock(); }); it("should not touch an already inlined font", function (done) { var rules = parseRules( '@font-face { font-family: "test font"; src: url("data:font/woff;base64,soMEfAkebASE64="); }' ); inlineCss .loadAndInlineCSSResourcesForRules(rules, {}) .then(function (result) { expect(result.hasChanges).toBe(false); expectFontFaceUrlToMatch( rules[0], "data:font/woff;base64,soMEfAkebASE64=" ); done(); }); }); it("should ignore an invalid source", function (done) { var rules = parseRules( '@font-face { font-family: "test font"; src: "invalid url"; }' ); inlineCss .loadAndInlineCSSResourcesForRules(rules, {}) .then(function (result) { expect(result.hasChanges).toBe(false); expect(binaryAjaxSpy).not.toHaveBeenCalled(); done(); }); }); it("should skip over an invalid source together with a valid one", function (done) { var rules = parseRules( '@font-face { font-family: "test font"; src: "invalid url", url("fake.woff"); }' ); mockBinaryAjaxUrl("fake.woff", "this is not a font"); inlineCss .loadAndInlineCSSResourcesForRules(rules, {}) .then(function (result) { expect(result.hasChanges).toBe(true); expectFontFaceUrlToMatch( rules[0], "data:font/woff;base64,dGhpcyBpcyBub3QgYSBmb250" ); done(); }); }); it("should ignore a local font", function (done) { var rules = parseRules( '@font-face { font-family: "test font"; src: local("font name"); }' ); inlineCss .loadAndInlineCSSResourcesForRules(rules, {}) .then(function (result) { expect(result.hasChanges).toBe(false); expect(binaryAjaxSpy).not.toHaveBeenCalled(); done(); }); }); it("should inline a font", function (done) { var rules = parseRules( '@font-face { font-family: "test font"; src: url("fake.woff"); }' ); mockBinaryAjaxUrl("fake.woff", "this is not a font"); inlineCss .loadAndInlineCSSResourcesForRules(rules, {}) .then(function (result) { expect(result.hasChanges).toBe(true); expectFontFaceUrlToMatch( rules[0], "data:font/woff;base64,dGhpcyBpcyBub3QgYSBmb250" ); done(); }); }); it("should take a font from url with alternatives", function (done) { var rules = parseRules( '@font-face { font-family: "test font"; src: local("font name"), url("fake.woff"); }' ); mockBinaryAjaxUrl("fake.woff", "this is not a font"); inlineCss .loadAndInlineCSSResourcesForRules(rules, {}) .then(function () { expectFontFaceUrlToMatch( rules[0], "data:font/woff;base64,dGhpcyBpcyBub3QgYSBmb250" ); done(); }); }); it("should detect a woff", function (done) { var rules = parseRules( '@font-face { font-family: "test font"; src: url("fake.woff") format("woff"); }' ); mockBinaryAjaxUrl("fake.woff", "font's content"); inlineCss .loadAndInlineCSSResourcesForRules(rules, {}) .then(function () { expectFontFaceUrlToMatch( rules[0], "data:font/woff;base64,Zm9udCdzIGNvbnRlbnQ=", "woff" ); done(); }); }); it("should detect a truetype font", function (done) { var rules = parseRules( '@font-face { font-family: "test font"; src: url("fake.ttf") format("truetype"); }' ); mockBinaryAjaxUrl("fake.ttf", "font's content"); inlineCss .loadAndInlineCSSResourcesForRules(rules, {}) .then(function () { expectFontFaceUrlToMatch( rules[0], "data:font/truetype;base64,Zm9udCdzIGNvbnRlbnQ=", "truetype" ); done(); }); }); it("should detect a opentype font", function (done) { var rules = parseRules( '@font-face { font-family: "test font"; src: url("fake.otf") format("opentype"); }' ); mockBinaryAjaxUrl("fake.otf", "font's content"); inlineCss .loadAndInlineCSSResourcesForRules(rules, {}) .then(function () { expectFontFaceUrlToMatch( rules[0], "data:font/opentype;base64,Zm9udCdzIGNvbnRlbnQ=", "opentype" ); done(); }); }); it("should keep all src references intact", function (done) { var rules = parseRules( '@font-face { font-family: "test font"; src: local("Fake Font"), url("fake.otf") format("opentype"), url("fake.woff"), local("Another Fake Font"); }' ); mockBinaryAjaxUrl("fake.woff", "font"); mockBinaryAjaxUrl("fake.otf", "font"); inlineCss .loadAndInlineCSSResourcesForRules(rules, {}) .then(function () { expect(rules[0].style.getPropertyValue("src")).toMatch( /local\("?Fake Font"?\), url\("?data:font\/opentype;base64,Zm9udA=="?\) format\("?opentype"?\), url\("?data:font\/woff;base64,Zm9udA=="?\), local\("?Another Fake Font"?\)/ ); done(); }); }); it("should handle a baseUrl", function (done) { var rules = parseRules( '@font-face { font-family: "test font"; src: url("fake.woff"); }' ); inlineCss .loadAndInlineCSSResourcesForRules(rules, { baseUrl: "url_base/page.html", }) .then(function () { expect( binaryAjaxSpy.calls.mostRecent().args[1].baseUrl ).toEqual("url_base/page.html"); done(); }); }); it("should circumvent caching if requested", function (done) { var fontUrl = "fake.woff", rules = parseRules( '@font-face { font-family: "test font"; src: url("' + fontUrl + '"); }' ); inlineCss .loadAndInlineCSSResourcesForRules(rules, { cache: "none" }) .then(function () { expect(binaryAjaxSpy).toHaveBeenCalledWith(fontUrl, { cache: "none", }); done(); }); }); it("should not circumvent caching by default", function (done) { var fontUrl = "fake.woff", rules = parseRules( '@font-face { font-family: "test font"; src: url("' + fontUrl + '"); }' ); inlineCss .loadAndInlineCSSResourcesForRules(rules, {}) .then(function () { expect(binaryAjaxSpy).toHaveBeenCalledWith(fontUrl, {}); done(); }); }); }); describe("on font-face with errors", function () { var aFontReferenceThatDoesExist = "a_font_that_does_exist.woff"; beforeEach(function () { binaryAjaxSpy.and.callFake(function (url) { if (url === aFontReferenceThatDoesExist) { return Promise.resolve(); } else { return Promise.reject({ url: "THEURL" + url, }); } }); joinUrlSpy.and.callThrough(); }); it("should report an error if a font could not be loaded", function (done) { var rules = parseRules( '@font-face { font-family: "test font"; src: url("a_font_that_doesnt_exist.woff"); }' ); inlineCss .loadAndInlineCSSResourcesForRules(rules, {}) .then(function (result) { expect(result.hasChanges).toBe(false); expect(result.errors[0]).toEqual( jasmine.objectContaining({ resourceType: "fontFace", url: "THEURL" + "a_font_that_doesnt_exist.woff", msg: "Unable to load font-face " + "THEURL" + "a_font_that_doesnt_exist.woff", }) ); done(); }); }); it("should only report a failing font as error", function (done) { var rules = parseRules( '@font-face { font-family: "test font1"; src: url("a_font_that_doesnt_exist.woff"); }\n' + '@font-face { font-family: "test font2"; src: url("' + aFontReferenceThatDoesExist + '"); }' ); inlineCss .loadAndInlineCSSResourcesForRules(rules, {}) .then(function (result) { expect(result.errors.length).toBe(1); done(); }); }); it("should report multiple failing fonts as error", function (done) { var rules = parseRules( '@font-face { font-family: "test font1";