Răsfoiți Sursa

Pull out loadiconSprite function from inline script

- wrote test for initIconSpirite
- changed from xmlhttprequest to fetch
- prepared a domReady util
- Fixes #9811
Lovelyfin00 2 ani în urmă
părinte
comite
9e9a84c953

+ 1 - 0
CHANGELOG.txt

@@ -120,6 +120,7 @@ Changelog
  * Maintenance: Add deprecation warnings for `wagtail.core` and other imports deprecated in Wagtail 3.0 (Matt Westcott)
  * Maintenance: Migrate admin upgrade notification message implementation to a Stimulus controller (Loveth Omokaro)
  * Maintenance: Migrate workflow and workflow tasks enable action to a Stimulus controller (Loveth Omokaro)
+ * Maintenance: Pull out icon sprite setup function from inline script to its own TypeScript file & add unit tests (Loveth Omokaro)
 
 
 4.1.2 (xx.xx.xxxx) - IN DEVELOPMENT

+ 6 - 0
client/src/entrypoints/admin/icons.js

@@ -0,0 +1,6 @@
+import { initIconSprite } from '../../includes/initIconSprite';
+
+const url = document.currentScript.dataset.iconUrl;
+const container = document.querySelector('[data-sprite]');
+
+initIconSprite(container, url);

+ 62 - 0
client/src/includes/initIconSprite.test.js

@@ -0,0 +1,62 @@
+import { initIconSprite } from './initIconSprite';
+
+const flushPromises = () => new Promise(setImmediate);
+
+describe('initIconSprite', () => {
+  const spriteURL = 'https://example.com/sprite.svg';
+  const responseText = '<svg>...</svg>';
+
+  beforeEach(() => {
+    global.fetch = jest.fn();
+    document.body.innerHTML = `<div data-sprite></div>`;
+  });
+
+  afterEach(() => {
+    jest.clearAllMocks();
+  });
+
+  it('should insert the response text into the spriteContainer', async () => {
+    const spriteContainer = document.querySelector('[data-sprite]');
+
+    global.fetch.mockResolvedValue({
+      text: () => Promise.resolve(responseText),
+    });
+    initIconSprite(spriteContainer, spriteURL);
+    await flushPromises();
+
+    expect(global.fetch).toHaveBeenCalled();
+    expect(global.fetch).toHaveBeenCalledWith(spriteURL);
+    expect(spriteContainer.innerHTML).toEqual(responseText);
+  });
+
+  it('should store the response text in localStorage', async () => {
+    const spriteContainer = document.querySelector('[data-sprite]');
+
+    jest.spyOn(Storage.prototype, 'setItem').mockImplementation(() => {});
+
+    global.fetch.mockResolvedValue({
+      text: () => Promise.resolve(responseText),
+    });
+    initIconSprite(spriteContainer, spriteURL);
+    await flushPromises();
+
+    expect(localStorage).not.toBe(null);
+    expect(localStorage['wagtail:spriteData']).toBe(responseText);
+    expect(localStorage['wagtail:spriteRevision']).toBe(spriteURL);
+  });
+
+  it('should throw an error if the fetch fails', async () => {
+    const spriteContainer = document.querySelector('[data-sprite]');
+    global.fetch.mockRejectedValue('Fetch failed');
+    const spy = jest.spyOn(console, 'error').mockImplementation();
+
+    initIconSprite(spriteContainer, spriteURL);
+    await flushPromises();
+
+    expect(global.fetch).toHaveBeenCalled();
+    expect(global.fetch).toHaveBeenCalledWith(spriteURL);
+    expect(spy).toHaveBeenCalledWith(
+      `Error fetching ${spriteURL}. Error: Fetch failed`,
+    );
+  });
+});

+ 53 - 0
client/src/includes/initIconSprite.ts

@@ -0,0 +1,53 @@
+const domReady = () =>
+  new Promise<void>((resolve) => {
+    if (document.readyState === 'loading') {
+      document.addEventListener('DOMContentLoaded', () => resolve(), {
+        once: true,
+        passive: true,
+      });
+    } else {
+      resolve();
+    }
+  });
+
+/**
+ * Loads sprite data from either Local Storage or via async fetch.
+ * Ensures the sprite data is backed up when pulled in from API.
+ */
+export const initIconSprite = (
+  spriteContainer: HTMLElement,
+  spriteURL: string,
+  revisionKey = 'wagtail:spriteRevision',
+  dataKey = 'wagtail:spriteData',
+): void => {
+  const hasLocalStorage: boolean =
+    'localStorage' in window && typeof window.localStorage !== 'undefined';
+
+  const insert = (data: string | null) => {
+    if (!spriteContainer || !data) return;
+
+    domReady().then(() => {
+      // eslint-disable-next-line no-param-reassign
+      spriteContainer.innerHTML = data;
+    });
+  };
+
+  if (hasLocalStorage && localStorage.getItem(revisionKey) === spriteURL) {
+    const data = localStorage.getItem(dataKey);
+    insert(data);
+  }
+
+  fetch(spriteURL)
+    .then((response) => response.text())
+    .then((data) => {
+      insert(data);
+      if (hasLocalStorage) {
+        localStorage.setItem(dataKey, data);
+        localStorage.setItem(revisionKey, spriteURL);
+      }
+    })
+    .catch((error) => {
+      // eslint-disable-next-line no-console
+      console.error(`Error fetching ${spriteURL}. Error: ${error}`);
+    });
+};

+ 1 - 0
client/webpack.config.js

@@ -40,6 +40,7 @@ module.exports = function exports(env, argv) {
       'draftail',
       'expanding-formset',
       'filtered-select',
+      'icons',
       'lock-unlock-action',
       'modal-workflow',
       'page-chooser-modal',

+ 1 - 0
docs/releases/4.2.md

@@ -159,6 +159,7 @@ Thank you to all who provided feedback, participants to our usability testing se
  * Add deprecation warnings for `wagtail.core` and other imports deprecated in Wagtail 3.0 (Matt Westcott)
  * Migrate admin upgrade notification message implementation to a Stimulus controller (Loveth Omokaro)
  * Migrate workflow and workflow tasks enable action to a Stimulus controller (Loveth Omokaro)
+ * Pull out icon sprite setup function from inline script to its own TypeScript file & add unit tests (Loveth Omokaro)
 
 ## Upgrade considerations
 

+ 1 - 47
wagtail/admin/templates/wagtailadmin/skeleton.html

@@ -17,54 +17,8 @@
     {% sidebar_collapsed as sidebar_collapsed %}
     <body id="wagtail" class="{% block bodyclass %}{% endblock %} {% if sidebar_collapsed %}sidebar-collapsed{% endif %} {% if messages %}has-messages{% endif %}">
         <div data-sprite></div>
-        <script>
-            function loadIconSprite() {
-                var spriteURL = '{% url "wagtailadmin_sprite" %}';
-                var revisionKey = 'wagtail:spriteRevision';
-                var dataKey = 'wagtail:spriteData';
-                var isLocalStorage = 'localStorage' in window && typeof window.localStorage !== 'undefined';
 
-                var insertIt = function (data) {
-                    var spriteContainer = document.body.querySelector('[data-sprite]');
-                    spriteContainer.innerHTML = data;
-                }
-
-                var insert = function (data) {
-                    if (document.body) {
-                        insertIt(data)
-                    } else {
-                        document.addEventListener('DOMContentLoaded', insertIt.bind(null, data));
-                    }
-                }
-
-                if (isLocalStorage && localStorage.getItem(revisionKey) === spriteURL) {
-                    var data = localStorage.getItem(dataKey);
-                    if (data) {
-                        insert(data);
-                        return true;
-                    }
-                }
-
-                try {
-                    var request = new XMLHttpRequest();
-                    request.open('GET', spriteURL, true);
-                    request.onload = function () {
-                        if (request.status >= 200 && request.status < 400) {
-                            data = request.responseText;
-                            insert(data);
-                            if (isLocalStorage) {
-                                localStorage.setItem(dataKey, data);
-                                localStorage.setItem(revisionKey, spriteURL);
-                            }
-                        }
-                    }
-                    request.send();
-                } catch (e) {
-                    console.error(e);
-                }
-            }
-            loadIconSprite();
-        </script>
+        <script src="{% versioned_static 'wagtailadmin/js/icons.js' %}" data-icon-url="{% url 'wagtailadmin_sprite' %}"></script>
 
         <noscript class="capabilitymessage">
             {% blocktrans trimmed %}