diff --git a/client/src/entrypoints/admin/core.js b/client/src/entrypoints/admin/core.js
index a39e504a3d..cb633b598d 100644
--- a/client/src/entrypoints/admin/core.js
+++ b/client/src/entrypoints/admin/core.js
@@ -1,6 +1,7 @@
import $ from 'jquery';
import { escapeHtml } from '../../utils/text';
import { initButtonSelects } from '../../includes/initButtonSelects';
+import { initTagField } from '../../includes/initTagField';
import { initTooltips } from '../../includes/initTooltips';
/* generic function for adding a message to message area through JS alone */
@@ -19,24 +20,6 @@ window.addMessage = addMessage;
window.escapeHtml = escapeHtml;
-function initTagField(id, autocompleteUrl, options) {
- const finalOptions = {
- autocomplete: { source: autocompleteUrl },
- preprocessTag(val) {
- // Double quote a tag if it contains a space
- // and if it isn't already quoted.
- if (val && val[0] !== '"' && val.indexOf(' ') > -1) {
- return '"' + val + '"';
- }
-
- return val;
- },
- ...options,
- };
-
- $('#' + id).tagit(finalOptions);
-}
-
window.initTagField = initTagField;
/*
diff --git a/client/src/includes/initTagField.test.js b/client/src/includes/initTagField.test.js
new file mode 100644
index 0000000000..72077e8d97
--- /dev/null
+++ b/client/src/includes/initTagField.test.js
@@ -0,0 +1,62 @@
+import $ from 'jquery';
+
+window.$ = $;
+
+import { initTagField } from './initTagField';
+
+describe('initTagField', () => {
+ let element;
+
+ const tagitMock = jest.fn(function tagitMockInner() {
+ element = this;
+ });
+
+ window.$.fn.tagit = tagitMock;
+
+ beforeEach(() => {
+ element = null;
+ jest.clearAllMocks();
+ });
+
+ it('should not call jQuery tagit if the element is not found', () => {
+ expect(tagitMock).not.toHaveBeenCalled();
+
+ initTagField('not-present');
+
+ expect(tagitMock).not.toHaveBeenCalled();
+ });
+
+ it('should call jQuery tagit if the element is found', () => {
+ expect(tagitMock).not.toHaveBeenCalled();
+
+ document.body.innerHTML = `
+
+
+
+ `;
+
+ initTagField('tag-input', '/path/to/autocomplete/', {
+ someOther: 'option',
+ });
+
+ // check the jQuery instance is the correct element
+ expect(element).toContain(document.getElementById('tag-input'));
+
+ // check the tagit util was called correctly with supplied params
+ expect(tagitMock).toHaveBeenCalledWith(
+ expect.objectContaining({
+ autocomplete: { source: '/path/to/autocomplete/' },
+ someOther: 'option',
+ }),
+ );
+
+ // check the supplied preprocessTag function
+ const [{ preprocessTag }] = tagitMock.mock.calls[0];
+
+ expect(preprocessTag).toBeInstanceOf(Function);
+
+ expect(preprocessTag()).toEqual();
+ expect(preprocessTag('"flat white"')).toEqual(`"flat white"`);
+ expect(preprocessTag("'long black'")).toEqual(`"'long black'"`);
+ });
+});
diff --git a/client/src/includes/initTagField.ts b/client/src/includes/initTagField.ts
new file mode 100644
index 0000000000..c714b42432
--- /dev/null
+++ b/client/src/includes/initTagField.ts
@@ -0,0 +1,42 @@
+import $ from 'jquery';
+
+declare global {
+ interface JQuery {
+ tagit(...args): void;
+ }
+}
+
+/**
+ * Initialises the tag fields using the jQuery tagit widget
+ *
+ * @param id - element id to initialise against
+ * @param source - auto complete URL source
+ * @param options - Other options passed to jQuery tagit
+ */
+const initTagField = (
+ id: string,
+ source: string,
+ options: Record,
+): void => {
+ const tagFieldElement = document.getElementById(id);
+
+ if (!tagFieldElement) return;
+
+ const finalOptions = {
+ autocomplete: { source },
+ preprocessTag(val: any) {
+ // Double quote a tag if it contains a space
+ // and if it isn't already quoted.
+ if (val && val[0] !== '"' && val.indexOf(' ') > -1) {
+ return '"' + val + '"';
+ }
+
+ return val;
+ },
+ ...options,
+ };
+
+ $('#' + id).tagit(finalOptions);
+};
+
+export { initTagField };