UNPKG

inlineresources

Version:

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

1,018 lines (743 loc) 42 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(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 ignore 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(false); 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"; src: url("a_font_that_doesnt_exist.woff"); }\n' + '@font-face { font-family: "test font2"; src: url("another_font_that_doesnt_exist.woff"); }'); 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('@font-face { font-family: "test font2"; src: url("' + aFontReferenceThatDoesExist + '"); }'); inlineCss.loadAndInlineCSSResourcesForRules(rules, {}).then(function (result) { expect(result.errors).toEqual([]); done(); }); }); }); }); });