diff --git a/app/code/Magento/PageCache/view/frontend/requirejs-config.js b/app/code/Magento/PageCache/view/frontend/requirejs-config.js index 3bbfd7563f4eb18d657a38123d6057febccddaeb..ee4f512e0b7800ed5f71c7e568d7b1e41f798612 100644 --- a/app/code/Magento/PageCache/view/frontend/requirejs-config.js +++ b/app/code/Magento/PageCache/view/frontend/requirejs-config.js @@ -6,12 +6,7 @@ var config = { map: { '*': { - formKey: 'Magento_PageCache/js/form-key', pageCache: 'Magento_PageCache/js/page-cache' } - }, - deps: [ - 'Magento_PageCache/js/form-key', - 'Magento_PageCache/js/msg-box' - ] -}; \ No newline at end of file + } +}; diff --git a/app/code/Magento/PageCache/view/frontend/templates/javascript.phtml b/app/code/Magento/PageCache/view/frontend/templates/javascript.phtml index 2a3100b1acee5ff75c8e48ddff4086f8d589668e..25502193ff8ecd7271750666c9d1764ce3775fe6 100644 --- a/app/code/Magento/PageCache/view/frontend/templates/javascript.phtml +++ b/app/code/Magento/PageCache/view/frontend/templates/javascript.phtml @@ -7,15 +7,12 @@ <?php /** @var \Magento\PageCache\Block\Javascript $this */ ?> <script> require([ - "jquery", - "mage/mage" -], function(jQuery){ - - //<![CDATA[ - jQuery(function () { - jQuery('body').mage('pageCache', <?php echo $this->getScriptOptions(); ?>); - }); - //]]> + 'jquery', + 'pageCache', + 'domReady!' +], function($){ + 'use strict'; + $('body').pageCache(<?php echo $this->getScriptOptions(); ?>); }); </script> diff --git a/app/code/Magento/PageCache/view/frontend/web/js/page-cache.js b/app/code/Magento/PageCache/view/frontend/web/js/page-cache.js index b5106032d00444ec20e831de901ab19b606b5c5f..16552af15c93d8e25fd955b2c7a3b125787b6642 100644 --- a/app/code/Magento/PageCache/view/frontend/web/js/page-cache.js +++ b/app/code/Magento/PageCache/view/frontend/web/js/page-cache.js @@ -4,15 +4,109 @@ * Copyright © 2015 Magento. All rights reserved. * See COPYING.txt for license details. */ -/*jshint browser:true jquery:true expr:true*/ + define([ - "jquery", - "jquery/ui", - "mage/cookies", - "Magento_PageCache/js/comments" -], function($){ - "use strict"; - + 'jquery', + 'domReady', + 'jquery/ui', + 'mage/cookies' +], function ($, domReady) { + 'use strict'; + + /** + * Nodes tree to flat list converter + * @returns {Array} + */ + $.fn.comments = function () { + var elements = []; + + /** + * @param {jQuery} element - Comment holder + */ + (function lookup(element) { + $(element).contents().each(function (index, el) { + switch (el.nodeType) { + case 1: // ELEMENT_NODE + lookup(el); + break; + + case 8: // COMMENT_NODE + elements.push(el); + break; + + case 9: // DOCUMENT_NODE + var hostName = window.location.hostname, + iFrameHostName = $('<a>') + .prop('href', element.prop('src')) + .prop('hostname'); + + if (hostName === iFrameHostName) { + lookup($(el).find('body')); + } + break; + } + }); + })(this); + + return elements; + }; + + /** + * MsgBox Widget checks if message box is displayed and sets cookie + */ + $.widget('mage.msgBox', { + options: { + msgBoxCookieName: 'message_box_display', + msgBoxSelector: '.main div.messages' + }, + + /** + * Creates widget 'mage.msgBox' + * @private + */ + _create: function () { + if ($.mage.cookies.get(this.options.msgBoxCookieName)) { + $.mage.cookies.clear(this.options.msgBoxCookieName); + } else { + $(this.options.msgBoxSelector).hide(); + } + } + }); + + /** + * FormKey Widget - this widget is generating from key, saves it to cookie and + */ + $.widget('mage.formKey', { + options: { + inputSelector: 'input[name="form_key"]', + allowedCharacters: '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ', + length: 16 + }, + + /** + * Creates widget 'mage.formKey' + * @private + */ + _create: function () { + var date, + formKey = $.mage.cookies.get('form_key'); + + if (!formKey) { + formKey = generateRandomString(this.options.allowedCharacters, this.options.length); + date = new Date(); + date.setTime(date.getTime() + (365 * 24 * 60 * 60 * 1000)); + $.mage.cookies.set('form_key', formKey, { + expires: date, + path: '/' + }); + } + $(this.options.inputSelector).val(formKey); + } + }); + + /** + * PageCache Widget + */ $.widget('mage.pageCache', { options: { url: '/', @@ -21,26 +115,46 @@ define([ versionCookieName: 'private_content_version', handles: [] }, + + /** + * Creates widget 'mage.pageCache' + * @private + */ _create: function () { - var version = $.mage.cookies.get(this.options.versionCookieName); + var placeholders, + version = $.mage.cookies.get(this.options.versionCookieName); + if (!version) { - return ; + return; } - var placeholders = this._searchPlaceholders(this.element.comments()); - if (placeholders.length) { + placeholders = this._searchPlaceholders(this.element.comments()); + + if (placeholders && placeholders.length) { this._ajax(placeholders, version); } }, + + /** + * Parse page for placeholders. + * @param {Array} elements + * @returns {Array} + * @private + */ _searchPlaceholders: function (elements) { var placeholders = [], - tmp = {}; - if (!elements.length) { + tmp = {}, + ii, + len, + el, matches, name; + + if (!(elements && elements.length)) { return placeholders; } - for (var i = 0; i < elements.length; i++) { - var el = elements[i], - matches = this.options.patternPlaceholderOpen.exec(el.nodeValue), - name = null; + + for (ii = 0, len = elements.length; ii < len; ii++) { + el = elements[ii]; + matches = this.options.patternPlaceholderOpen.exec(el.nodeValue); + name = null; if (matches) { name = matches[1]; @@ -50,8 +164,10 @@ define([ }; } else { matches = this.options.patternPlaceholderClose.exec(el.nodeValue); + if (matches) { name = matches[1]; + if (tmp[name]) { tmp[name].closeElement = el; placeholders.push(tmp[name]); @@ -60,44 +176,74 @@ define([ } } } + return placeholders; }, - _replacePlaceholder: function(placeholder, html) { + + /** + * Parse for page and replace placeholders + * @param {Object} placeholder + * @param {Object} html + * @protected + */ + _replacePlaceholder: function (placeholder, html) { + if (!placeholder || !html) { + return; + } + var parent = $(placeholder.openElement).parent(), contents = parent.contents(), startReplacing = false, - prevSibling = null; - for (var y = 0; y < contents.length; y++) { - var element = contents[y]; + prevSibling = null, + yy, + len, + element; + + for (yy = 0, len = contents.length; yy < len; yy++) { + element = contents[yy]; + if (element == placeholder.openElement) { startReplacing = true; } + if (startReplacing) { $(element).remove(); } else if (element.nodeType != 8) { //due to comment tag doesn't have siblings we try to find it manually prevSibling = element; } + if (element == placeholder.closeElement) { break; } } + if (prevSibling) { $(prevSibling).after(html); } else { $(parent).prepend(html); } + // trigger event to use mage-data-init attribute $(parent).trigger('contentUpdated'); }, + + /** + * AJAX helper + * @param {Object} placeholders + * @param {String} version + * @private + */ _ajax: function (placeholders, version) { - var data = { - blocks: [], - handles: this.options.handles, - version: version - }; - for (var i = 0; i < placeholders.length; i++) { - data.blocks.push(placeholders[i].name); + var ii, + data = { + blocks: [], + handles: this.options.handles, + version: version + }; + + for (ii = 0; ii < placeholders.length; ii++) { + data.blocks.push(placeholders[ii].name); } data.blocks = JSON.stringify(data.blocks.sort()); data.handles = JSON.stringify(data.handles); @@ -108,18 +254,54 @@ define([ cache: true, dataType: 'json', context: this, + + /** + * Response handler + * @param {Object} response + */ success: function (response) { - for (var i = 0; i < placeholders.length; i++) { - var placeholder = placeholders[i]; - if (!response.hasOwnProperty(placeholder.name)) { - continue; + var placeholder, i; + + for (i = 0; i < placeholders.length; i++) { + placeholder = placeholders[i]; + + if (response.hasOwnProperty(placeholder.name)) { + this._replacePlaceholder(placeholder, response[placeholder.name]); } - this._replacePlaceholder(placeholder, response[placeholder.name]); } } }); } }); - - return $.mage.pageCache; + + domReady(function () { + $('body') + .pageCache() + .msgBox() + .formKey(); + }); + + return { + 'pageCache': $.mage.pageCache, + 'formKey': $.mage.formKey, + 'msgBox': $.mage.msgBox + }; + + /** + * Helper. Generate random string + * TODO: Merge with mage/utils + * @param {String} chars - list of symbols + * @param {Number} length - length for need string + * @returns {String} + */ + function generateRandomString(chars, length) { + var result = ''; + length = length > 0 && Number.isFinite(length) ? length : 1; + + while (length--) { + result += chars[Math.round(Math.random() * (chars.length - 1))]; + } + + return result; + } }); diff --git a/dev/tests/js/spec/integration/Magento/PageCache/frontend/js/pageCacheSpec.js b/dev/tests/js/spec/integration/Magento/PageCache/frontend/js/pageCacheSpec.js new file mode 100644 index 0000000000000000000000000000000000000000..a1c8a63fab3f1ad8a09b036c5a5c5898afc8dee3 --- /dev/null +++ b/dev/tests/js/spec/integration/Magento/PageCache/frontend/js/pageCacheSpec.js @@ -0,0 +1,340 @@ +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +/*global describe*/ +/*global beforeEach*/ +/*global afterEach*/ +/*global it*/ +/*global expect*/ +/*global jasmine*/ +/*global spyOn*/ +define([ + 'jquery', + 'Magento_PageCache/js/page-cache' +], function ($) { + 'use strict'; + + if (!Function.prototype.bind) { + /** + * @param {Object} bind + * @returns {Function} + */ + Function.prototype.bind = function (bind) { + var self = this; + + /** + * @returns {Function} + */ + return function () { + var args = Array.prototype.slice.call(arguments); + + return self.apply(bind || null, args); + }; + }; + } + + describe('Testing html-comments-parser $.fn.comments behavior', function () { + var element, + iframe, + comment, + host; + + beforeEach(function () { + element = $('<div />'); + iframe = $('<iframe />'); + comment = '<!--COMMENT CONTENT-->'; + host = window.location.hostname; + + $('body') + .append(element) + .append(iframe); + }); + + afterEach(function () { + $(element).remove(); + $(iframe).remove(); + }); + + it('comments fn exists', function () { + expect($.fn.comments).toBeDefined(); + expect($.fn.comments()).toEqual([]); + }); + + it('on empty node comments() returns empty Array', function () { + expect($(element).comments()).toEqual([]); + expect($(iframe).insertAfter('body').comments()).toEqual([]); + }); + + it('on non-empty node comments() returns empty Array with nodes', function () { + element.html(comment); + expect($(element).comments().length).toEqual(1); + expect($(element).comments()[0].nodeType).toEqual(8); + expect($(element).comments()[0].nodeValue).toEqual('COMMENT CONTENT'); + }); + + it('on iframe from same host returns Array with nodes', function () { + iframe.contents().find('body').html(comment); + iframe.attr('src', '//' + host + '/'); + + expect(iframe.comments().length).toEqual(1); + expect(iframe.comments()[0].nodeType).toEqual(8); + expect(iframe.comments()[0].nodeValue).toEqual('COMMENT CONTENT'); + }); + + it('on iframe from other host returns empty Array', function () { + iframe.contents().find('body').html(comment); + iframe.attr('src', '//' + host + '.otherHost/'); + + expect(iframe.comments().length).toEqual(0); + }); + }); + + describe('Testing msgBox Widget', function () { + var wdContainer, + msgCookieName, + msgContainer; + + beforeEach(function () { + wdContainer = $('<div />'); + msgContainer = $('<div />'); + msgCookieName = 'FAKE_COOKIE'; + }); + + afterEach(function () { + $(wdContainer).remove(); + $(msgContainer).remove(); + }); + + it('widget extends jQuery object', function () { + expect($.fn.msgBox).toBeDefined(); + }); + + it('widget gets options', function () { + wdContainer.msgBox({ + 'msgBoxCookieName': msgCookieName + }); + expect(wdContainer.msgBox('option', 'msgBoxCookieName')).toBe('FAKE_COOKIE'); + }); + + it('widget disables cookie if it exist', function () { + spyOn($.mage.cookies, 'get').and.returnValue('FAKE_MAGE_COOKIE'); + spyOn($.mage.cookies, 'clear'); + + wdContainer.msgBox({ + 'msgBoxSelector': msgContainer + }); + + expect($.mage.cookies.get).toHaveBeenCalled(); + expect($.mage.cookies.clear).toHaveBeenCalled(); + }); + + it('widget disables messageBox if cookie not exist', function () { + spyOn($.mage.cookies, 'get'); + + wdContainer.msgBox({ + 'msgBoxSelector': msgContainer + }); + + expect($.mage.cookies.get).toHaveBeenCalled(); + expect(msgContainer.is(':hidden')).toBeTruthy(); + }); + + it('widget exist on load on body', function (done) { + $(function () { + expect($('body').data('mageMsgBox')).toBeDefined(); + done(); + }); + }); + }); + + describe('Testing FormKey Widget', function () { + var wdContainer, + msgCookieName, + inputContainer; + + beforeEach(function () { + wdContainer = $('<div />'); + inputContainer = $('<input />'); + msgCookieName = 'FAKE_COOKIE'; + }); + + afterEach(function () { + $(wdContainer).remove(); + $(inputContainer).remove(); + }); + + it('widget extends jQuery object', function () { + expect($.fn.formKey).toBeDefined(); + }); + + it('widget set value to input[form_key]', function () { + spyOn($.mage.cookies, 'get').and.returnValue('FAKE_COOKIE'); + + wdContainer.formKey({ + 'inputSelector': inputContainer + }); + + expect($.mage.cookies.get).toHaveBeenCalled(); + expect(inputContainer.val()).toBe('FAKE_COOKIE'); + }); + + it('widget set value to input[form_key]', function () { + spyOn($.mage.cookies, 'set'); + spyOn($.mage.cookies, 'get'); + + wdContainer.formKey({ + 'inputSelector': inputContainer + }); + + expect($.mage.cookies.get).toHaveBeenCalled(); + expect($.mage.cookies.set).toHaveBeenCalled(); + expect(inputContainer.val()).toEqual(jasmine.any(String)); + }); + + it('widget exist on load on body', function (done) { + $(function () { + expect($('body').data('mageFormKey')).toBeDefined(); + done(); + }); + }); + }); + + describe('Testing PageCache Widget', function () { + var wdContainer, + versionCookieName, + pageBlockContainer; + + beforeEach(function () { + wdContainer = $('<div />'); + pageBlockContainer = $('<div />'); + versionCookieName = 'FAKE_COOKIE'; + }); + + afterEach(function () { + $(wdContainer).remove(); + $(pageBlockContainer).remove(); + }); + + it('widget extends jQuery object', function () { + expect($.fn.pageCache).toBeDefined(); + }); + + it('widget breaks if no private_content_version cookie', function () { + spyOn($.mage.cookies, 'get'); + spyOn($.fn, 'comments'); + + wdContainer.pageCache(); + + expect($.mage.cookies.get).toHaveBeenCalled(); + expect($.fn.comments).not.toHaveBeenCalled(); + }); + + it('_searchPlaceholders called only when HTML_COMMENTS', function () { + var nodes; + spyOn($.mage.cookies, 'get').and.returnValue('FAKE_VERSION_COOKIE'); + spyOn($.mage.pageCache.prototype, '_searchPlaceholders'); + + wdContainer + .html('<!-- BLOCK FAKE_BLOCK -->FAKE_TEXT<!-- /BLOCK FAKE_BLOCK -->') + .pageCache(); + + nodes = wdContainer.comments(); + expect(nodes.length).toEqual(2); + + expect($.mage.cookies.get).toHaveBeenCalled(); + expect($.mage.pageCache.prototype._searchPlaceholders).toHaveBeenCalled(); + expect($.mage.pageCache.prototype._searchPlaceholders).toHaveBeenCalledWith(nodes); + }); + + it('_searchPlaceholders return Array of blocks', function () { + var nodes, + searches; + spyOn($.mage.cookies, 'get').and.returnValue('FAKE_VERSION_COOKIE'); + + wdContainer + .html('<!-- BLOCK FAKE_BLOCK -->FAKE_TEXT<!-- /BLOCK FAKE_BLOCK -->') + .pageCache(); + + nodes = wdContainer.comments(); + searches = wdContainer.data('pageCache')._searchPlaceholders(nodes); + expect(wdContainer.data('pageCache')._searchPlaceholders()).toEqual([]); + expect(searches[0]).toEqual(jasmine.objectContaining({ + name: 'FAKE_BLOCK' + })); + expect(searches[0].openElement.nodeType).toBeDefined(); + expect(searches[0].closeElement.nodeType).toBeDefined(); + }); + + it('_searchPlaceholders called only when HTML_COMMENTS', function () { + var nodes; + spyOn($.mage.cookies, 'get').and.returnValue('FAKE_VERSION_COOKIE'); + spyOn($.mage.pageCache.prototype, '_searchPlaceholders'); + + wdContainer + .html('<!-- BLOCK FAKE_BLOCK -->FAKE_TEXT<!-- /BLOCK FAKE_BLOCK -->') + .pageCache(); + + nodes = wdContainer.comments(); + expect(nodes.length).toEqual(2); + + expect($.mage.cookies.get).toHaveBeenCalled(); + expect($.mage.pageCache.prototype._searchPlaceholders).toHaveBeenCalled(); + expect($.mage.pageCache.prototype._searchPlaceholders).toHaveBeenCalledWith(nodes); + }); + + it('_replacePlaceholder append HTML after sibling node', function () { + var replacer, + searcher, + placeholders, + context; + + context = { + options: { + patternPlaceholderOpen: /^ BLOCK (.+) $/, + patternPlaceholderClose: /^ \/BLOCK (.+) $/ + } + }; + replacer = $.mage.pageCache.prototype._replacePlaceholder.bind(context); + searcher = $.mage.pageCache.prototype._searchPlaceholders.bind(context); + + wdContainer + .html('<span></span><!-- BLOCK FAKE_BLOCK -->FAKE_TEXT<!-- /BLOCK FAKE_BLOCK -->'); + placeholders = searcher(wdContainer.comments()); + replacer(placeholders[0], '<span>FAKE_HTML</span>'); + + expect(wdContainer.html()).toEqual('<span></span><span>FAKE_HTML</span>'); + }); + + it('_replacePlaceholder prepend HTML if no sibling', function () { + var replacer, + searcher, + placeholders, + context; + + context = { + options: { + patternPlaceholderOpen: /^ BLOCK (.+) $/, + patternPlaceholderClose: /^ \/BLOCK (.+) $/ + } + }; + replacer = $.mage.pageCache.prototype._replacePlaceholder.bind(context); + searcher = $.mage.pageCache.prototype._searchPlaceholders.bind(context); + + wdContainer + .html('<!-- BLOCK FAKE_BLOCK -->FAKE_TEXT<!-- /BLOCK FAKE_BLOCK -->'); + placeholders = searcher(wdContainer.comments()); + replacer(placeholders[0], '<span>FAKE_HTML</span>'); + + expect(wdContainer.html()).toEqual('<span>FAKE_HTML</span>'); + }); + + it('widget exist on load on body', function (done) { + $(function () { + expect($('body').data('magePageCache')).toBeDefined(); + done(); + }); + }); + }); +});