diff --git a/package-lock.json b/package-lock.json
index 0881b5b7..929bcedd 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -11,6 +11,7 @@
"dependencies": {
"@75lb/deep-merge": "^1.1.2",
"@rollup/plugin-replace": "5.0.5",
+ "@testing-library/user-event": "^14.5.2",
"@web/dev-server-rollup": "0.6.1",
"@web/test-runner": "0.18.1",
"@web/test-runner-commands": "0.9.0",
@@ -1927,7 +1928,6 @@
"version": "7.23.1",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.1.tgz",
"integrity": "sha512-hC2v6p8ZSI/W0HUzh3V8C5g+NwSKzKPtJwSpTjwl0o297GP9+ZLQSkdvHz46CM3LqyoXxq+5G9komY+eSqSO0g==",
- "dev": true,
"dependencies": {
"regenerator-runtime": "^0.14.0"
},
@@ -3783,6 +3783,109 @@
"node": ">=14"
}
},
+ "node_modules/@testing-library/dom": {
+ "version": "10.4.0",
+ "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.0.tgz",
+ "integrity": "sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==",
+ "peer": true,
+ "dependencies": {
+ "@babel/code-frame": "^7.10.4",
+ "@babel/runtime": "^7.12.5",
+ "@types/aria-query": "^5.0.1",
+ "aria-query": "5.3.0",
+ "chalk": "^4.1.0",
+ "dom-accessibility-api": "^0.5.9",
+ "lz-string": "^1.5.0",
+ "pretty-format": "^27.0.2"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@testing-library/dom/node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "peer": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/@testing-library/dom/node_modules/chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "peer": true,
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/@testing-library/dom/node_modules/chalk/node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "peer": true,
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/@testing-library/dom/node_modules/pretty-format": {
+ "version": "27.5.1",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz",
+ "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==",
+ "peer": true,
+ "dependencies": {
+ "ansi-regex": "^5.0.1",
+ "ansi-styles": "^5.0.0",
+ "react-is": "^17.0.1"
+ },
+ "engines": {
+ "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0"
+ }
+ },
+ "node_modules/@testing-library/dom/node_modules/react-is": {
+ "version": "17.0.2",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
+ "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
+ "peer": true
+ },
+ "node_modules/@testing-library/dom/node_modules/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "peer": true,
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/@testing-library/user-event": {
+ "version": "14.5.2",
+ "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.5.2.tgz",
+ "integrity": "sha512-YAh82Wh4TIrxYLmfGcixwD18oIjyC1pFQC2Y01F2lzV2HTMiYrI0nze0FD0ocB//CKS/7jIUgae+adPqxK5yCQ==",
+ "engines": {
+ "node": ">=12",
+ "npm": ">=6"
+ },
+ "peerDependencies": {
+ "@testing-library/dom": ">=7.21.4"
+ }
+ },
"node_modules/@tootallnate/once": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz",
@@ -3814,6 +3917,12 @@
"@types/node": "*"
}
},
+ "node_modules/@types/aria-query": {
+ "version": "5.0.4",
+ "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz",
+ "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==",
+ "peer": true
+ },
"node_modules/@types/babel__code-frame": {
"version": "7.0.4",
"resolved": "https://registry.npmjs.org/@types/babel__code-frame/-/babel__code-frame-7.0.4.tgz",
@@ -4631,7 +4740,6 @@
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
"integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
- "dev": true,
"engines": {
"node": ">=10"
},
@@ -4663,6 +4771,15 @@
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
"dev": true
},
+ "node_modules/aria-query": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz",
+ "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==",
+ "peer": true,
+ "dependencies": {
+ "dequal": "^2.0.3"
+ }
+ },
"node_modules/array-back": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/array-back/-/array-back-3.1.0.tgz",
@@ -6578,6 +6695,15 @@
"node": ">= 0.6.0"
}
},
+ "node_modules/dequal": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
+ "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
+ "peer": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/destroy": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
@@ -6641,6 +6767,12 @@
"node": ">=6.0.0"
}
},
+ "node_modules/dom-accessibility-api": {
+ "version": "0.5.16",
+ "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz",
+ "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==",
+ "peer": true
+ },
"node_modules/dom-serializer": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
@@ -11269,6 +11401,15 @@
"node": ">=12"
}
},
+ "node_modules/lz-string": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz",
+ "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==",
+ "peer": true,
+ "bin": {
+ "lz-string": "bin/bin.js"
+ }
+ },
"node_modules/magic-string": {
"version": "0.25.9",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz",
@@ -13736,8 +13877,7 @@
"node_modules/regenerator-runtime": {
"version": "0.14.0",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz",
- "integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==",
- "dev": true
+ "integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA=="
},
"node_modules/regenerator-transform": {
"version": "0.15.2",
diff --git a/package.json b/package.json
index c0e4fab4..3b3bd3c9 100644
--- a/package.json
+++ b/package.json
@@ -6,13 +6,17 @@
"scripts": {
"test": "npm run wtr && npm run jest",
"wtr": "wtr \"./test/**/*.test.(js|html)\" --node-resolve --port=2000 --coverage --concurrent-browsers 4",
+ "wtr:file": "wtr --node-resolve --port=2000 --coverage --concurrent-browsers 4",
"wtr:watch": "npm run wtr -- --watch",
+ "wtr:file:watch": "npm run wtr:file -- --watch",
"int": "wtr \"./test/integration/**/*.int.(js|html)\" --node-resolve --port=2000 --concurrent-browsers 3 --config wtr-integration.config.mjs",
"int:watch": "npm run int -- --watch",
"int3": "wtr \"./test/integration/**/*.int.(js|html)\" --node-resolve --port=2000 --concurrent-browsers 3 --config wtr-int-browsers.config.mjs",
"int3:watch": "npm run int3 -- --watch",
"jest": "jest --testPathPattern=test --coverage --coverageDirectory=coverage/jest",
+ "jest:file": "jest --coverage --coverageDirectory=coverage/jest",
"jest:watch": "npm run jest -- --watchAll",
+ "jest:file:watch": "npm run jest:file -- --watchAll",
"lcov": "lcov -a coverage/jest/lcov.info -a coverage/wtr/lcov.info -o coverage/lcov.info",
"lint": "npm run lint:js && npm run lint:css",
"lint:js": "eslint .",
@@ -63,6 +67,7 @@
"dependencies": {
"@75lb/deep-merge": "^1.1.2",
"@rollup/plugin-replace": "5.0.5",
+ "@testing-library/user-event": "^14.5.2",
"@web/dev-server-rollup": "0.6.1",
"@web/test-runner": "0.18.1",
"@web/test-runner-commands": "0.9.0",
diff --git a/test/blocks/acom-widget/acom-widget-redirect.jest.js b/test/blocks/acom-widget/acom-widget-redirect.jest.js
new file mode 100644
index 00000000..a55fd88a
--- /dev/null
+++ b/test/blocks/acom-widget/acom-widget-redirect.jest.js
@@ -0,0 +1,72 @@
+/**
+ * @jest-environment jsdom
+ */
+/* eslint-disable compat/compat */
+/* eslint-disable no-undef */
+import path from 'path';
+import fs from 'fs';
+import { userEvent } from '@testing-library/user-event';
+
+const mockFetch = jest.fn(() => Promise.resolve({
+ json: () => Promise.resolve({
+ access_token: '123',
+ discovery: {
+ resources: {
+ jobs: { status: { uri: 'https://pdfnow-dev.adobe.io/status' } },
+ assets: {
+ upload: { uri: 'https://pdfnow-dev.adobe.io/upload' },
+ download_uri: { uri: 'https://pdfnow-dev.adobe.io/download' },
+ createpdf: { uri: 'https://pdfnow-dev.adobe.io/createpdf' },
+ },
+ },
+ },
+ }),
+ ok: true,
+}));
+
+const xhrMock = {
+ abort: jest.fn(),
+ open: jest.fn(),
+ setRequestHeader: jest.fn(),
+ onreadystatechange: jest.fn(),
+ progress: jest.fn(),
+ upload: new EventTarget(),
+ send: jest.fn(),
+ readyState: 4,
+ responseText: JSON.stringify({
+ uri: 'https://www.example.com',
+ job_uri: 'https://www.example.com/job_uri',
+ }),
+ status: 201,
+};
+
+describe('acom-widget block', () => {
+ beforeEach(() => {
+ document.head.innerHTML = fs.readFileSync(path.resolve(__dirname, './mocks/head.html'), 'utf8');
+ document.body.innerHTML = fs.readFileSync(path.resolve(__dirname, './mocks/body.html'), 'utf8');
+ window.fetch = mockFetch;
+ window.XMLHttpRequest = jest.fn(() => xhrMock);
+ });
+
+ afterEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('upload PDF', async () => {
+ const log = jest.spyOn(console, 'log');
+
+ delete window.location;
+ window.location = new URL('https://localhost/acrobat/online/ai-chat-pdf.html?redirect=off');
+
+ const blockModule = await import('../../../acrobat/blocks/acom-widget/acom-widget.js');
+
+ const block = document.querySelector('.acom-widget');
+ await blockModule.default(block);
+
+ const input = document.querySelector('input');
+ const file = new File(['hello'], 'hello.png', { type: 'image/png' });
+ await userEvent.upload(input, file);
+ await xhrMock.onreadystatechange();
+ expect(log.mock.calls[0][0]).toContain('Blob Viewer URL:');
+ });
+});
diff --git a/test/blocks/acom-widget/acom-widget.jest.js b/test/blocks/acom-widget/acom-widget.jest.js
new file mode 100644
index 00000000..46fb14a6
--- /dev/null
+++ b/test/blocks/acom-widget/acom-widget.jest.js
@@ -0,0 +1,81 @@
+/**
+ * @jest-environment jsdom
+ */
+/* eslint-disable compat/compat */
+/* eslint-disable no-undef */
+import path from 'path';
+import fs from 'fs';
+import { userEvent } from '@testing-library/user-event';
+import { delay } from '../../helpers/waitfor.js';
+import init from '../../../acrobat/blocks/acom-widget/acom-widget.js';
+
+const mockfetch = jest.fn(() => Promise.resolve({
+ json: () => Promise.resolve({
+ access_token: '123',
+ discovery: {
+ resources: {
+ jobs: { status: { uri: 'https://pdfnow-dev.adobe.io/status' } },
+ assets: {
+ upload: { uri: 'https://pdfnow-dev.adobe.io/upload' },
+ download_uri: { uri: 'https://pdfnow-dev.adobe.io/download' },
+ createpdf: { uri: 'https://pdfnow-dev.adobe.io/createpdf' },
+ },
+ },
+ },
+ }),
+ ok: true,
+}));
+
+const mockXhr = {
+ abort: jest.fn(),
+ open: jest.fn(),
+ setRequestHeader: jest.fn(),
+ onreadystatechange: jest.fn(),
+ progress: jest.fn(),
+ upload: new EventTarget(),
+ send: jest.fn(),
+ readyState: 4,
+ responseText: JSON.stringify({
+ uri: 'https://www.example.com/asseturi/',
+ job_uri: 'https://www.example.com/job_uri',
+ }),
+ status: 201,
+};
+
+describe('acom-widget block', () => {
+ beforeEach(() => {
+ document.head.innerHTML = fs.readFileSync(path.resolve(__dirname, './mocks/head.html'), 'utf8');
+ document.body.innerHTML = fs.readFileSync(path.resolve(__dirname, './mocks/body.html'), 'utf8');
+ window.fetch = mockfetch;
+ window.XMLHttpRequest = jest.fn(() => mockXhr);
+ });
+
+ afterEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('upload PDF', async () => {
+ window.alert = jest.fn();
+
+ delete window.localStorage.limit;
+
+ delete window.location;
+ window.location = new URL('https://localhost/acrobat/online/ai-chat-pdf.html');
+
+ const block = document.querySelector('.acom-widget');
+ await init(block);
+
+ const input = document.querySelector('input');
+ const file = new File(['hello'], 'hello.png', { type: 'image/png' });
+
+ window.location = { assign: jest.fn() };
+
+ await userEvent.upload(input, file);
+
+ await mockXhr.onreadystatechange();
+
+ await delay(100);
+
+ expect(window.location.href).toMatch(/pdfNowAssetUri=https:\/\/www.example.com\/asseturi\//);
+ });
+});
diff --git a/test/blocks/acom-widget/acom-widget.test.js b/test/blocks/acom-widget/acom-widget.test.js
new file mode 100644
index 00000000..4a6be132
--- /dev/null
+++ b/test/blocks/acom-widget/acom-widget.test.js
@@ -0,0 +1,153 @@
+/* eslint-disable compat/compat */
+import { readFile } from '@web/test-runner-commands';
+import { expect } from '@esm-bundle/chai';
+import sinon from 'sinon';
+import { delay, waitForElement } from '../../helpers/waitfor.js';
+
+const { default: init } = await import(
+ '../../../acrobat/blocks/acom-widget/acom-widget.js'
+);
+
+const uploadFile = (input, file) => {
+ const changeEvent = new Event('change');
+ Object.defineProperty(changeEvent, 'target', { writable: false, value: { files: [file] } });
+ input.dispatchEvent(changeEvent);
+};
+
+describe('acom-widget block', () => {
+ let xhr;
+
+ beforeEach(async () => {
+ sinon.stub(window, 'fetch');
+ window.fetch.callsFake((x) => {
+ if (x === 'https://pdfnow-dev.adobe.io/status') {
+ return Promise.resolve({ ok: false });
+ }
+ return Promise.resolve({
+ json: () => Promise.resolve({
+ access_token: '123',
+ discovery: {
+ resources: {
+ jobs: { status: { uri: 'https://pdfnow-dev.adobe.io/status' } },
+ assets: {
+ upload: { uri: 'https://pdfnow-dev.adobe.io/upload' },
+ download_uri: { uri: 'https://pdfnow-dev.adobe.io/download' },
+ createpdf: { uri: 'https://pdfnow-dev.adobe.io/createpdf' },
+ },
+ },
+ },
+ }),
+ ok: true,
+ });
+ });
+ xhr = sinon.useFakeXMLHttpRequest();
+ document.head.innerHTML = await readFile({ path: './mocks/head.html' });
+ document.body.innerHTML = await readFile({ path: './mocks/body.html' });
+ delete window.localStorage.limit;
+ });
+
+ afterEach(() => {
+ xhr.restore();
+ sinon.restore();
+ });
+
+ it('reach limit', async () => {
+ window.localStorage.limit = 2;
+
+ const block = document.body.querySelector('.acom-widget');
+ await init(block);
+
+ expect(document.querySelector('.upsell')).to.exist;
+ });
+
+ it('upload invalid file', async () => {
+ const alert = sinon.stub(window, 'alert').callsFake(() => {});
+
+ const block = document.querySelector('.acom-widget');
+ await init(block);
+
+ const input = document.querySelector('input');
+ const file = new File(['hello'], 'hello.txt', { type: 'text/plain' });
+
+ uploadFile(input, file);
+
+ expect(alert.getCall(0).args[0]).to.eq('This file is invalid');
+ });
+
+ it('cancel upload', async () => {
+ sinon.stub(window, 'alert').callsFake(() => {});
+
+ const block = document.querySelector('.acom-widget');
+ await init(block);
+
+ const input = document.querySelector('input');
+ const file = new File(['hello'], 'hello.png', { type: 'image/png' });
+
+ uploadFile(input, file);
+
+ await delay(1000);
+
+ document.querySelector('.widget-cancel').click();
+
+ const upload = await waitForElement('#file-upload');
+ expect(upload).to.be.exist;
+ });
+
+ it('SSRF check', async () => {
+ window.fetch.restore();
+ sinon.stub(window, 'fetch');
+ window.fetch.returns(Promise.resolve({
+ json: () => Promise.resolve({
+ access_token: '123',
+ discovery: { resources: { assets: { upload: { uri: 'https://example.com/upload' } } } },
+ }),
+ ok: true,
+ }));
+ sinon.stub(window, 'alert').callsFake(() => {});
+
+ const block = document.querySelector('.acom-widget');
+ await init(block);
+
+ const input = document.querySelector('input');
+ const file = new File(['hello'], 'hello.png', { type: 'image/png' });
+
+ uploadFile(input, file);
+
+ await delay(500);
+
+ expect(alert.getCall(0).args[0]).to.eq('An error occurred during the upload process. Please try again.');
+ });
+
+ it('upload PNG and fail at job status', async () => {
+ sinon.stub(window, 'alert').callsFake(() => {});
+
+ const requests = [];
+
+ xhr.onCreate = (x) => {
+ requests.push(x);
+ };
+
+ const block = document.body.querySelector('.acom-widget');
+ await init(block);
+
+ const input = document.querySelector('input');
+ const file = new File(['hello'], 'hello.png', { type: 'image/png' });
+
+ uploadFile(input, file);
+
+ await delay(500);
+
+ requests[0].respond(
+ 201,
+ { 'Content-Type': 'application/json' },
+ JSON.stringify({
+ uri: 'https://www.example.com/product',
+ job_uri: 'https://www.example.com/job_uri',
+ }),
+ );
+
+ await delay(500);
+
+ expect(alert.getCall(0).args[0]).to.eq('Failed to create PDF');
+ });
+});
diff --git a/test/blocks/acom-widget/mocks/body.html b/test/blocks/acom-widget/mocks/body.html
new file mode 100644
index 00000000..6fa3246d
--- /dev/null
+++ b/test/blocks/acom-widget/mocks/body.html
@@ -0,0 +1,9 @@
+
+
+
diff --git a/test/blocks/acom-widget/mocks/head.html b/test/blocks/acom-widget/mocks/head.html
new file mode 100644
index 00000000..be4425f0
--- /dev/null
+++ b/test/blocks/acom-widget/mocks/head.html
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file