From 99693449cd80d13be1f0b68106cc79702f61dbe8 Mon Sep 17 00:00:00 2001 From: Shiven Sinha Date: Sat, 18 May 2019 21:09:44 +0530 Subject: [PATCH] Prettied everything --- .babelrc | 4 +- .codacy.yml | 10 +- .idea/workspace.xml | 481 ++++++++++------------------------ .travis.yml | 8 +- SpecRunner.html | 17 +- example/blog.html | 37 +-- example/form.html | 102 ++++--- index.js | 137 +++++----- karma.conf.js | 29 +- tests/append.js | 126 +++++---- tests/mock-data/mockData.json | 2 +- tests/read.js | 274 +++++++++++-------- tests/utils/normalizeURL.js | 101 ++++--- 13 files changed, 630 insertions(+), 698 deletions(-) diff --git a/.babelrc b/.babelrc index 20935a2..04f8629 100644 --- a/.babelrc +++ b/.babelrc @@ -7,5 +7,5 @@ } ] ], - "plugins": [ "istanbul" ] -} \ No newline at end of file + "plugins": ["istanbul"] +} diff --git a/.codacy.yml b/.codacy.yml index 6352810..cec4fa0 100644 --- a/.codacy.yml +++ b/.codacy.yml @@ -1,6 +1,6 @@ exclude_paths: -- 'tests/**' -- 'jasmine/**' -- 'dist/**' -- 'karma.conf.js' -- 'CODE_OF_CONDUCT.md' \ No newline at end of file + - "tests/**" + - "jasmine/**" + - "dist/**" + - "karma.conf.js" + - "CODE_OF_CONDUCT.md" diff --git a/.idea/workspace.xml b/.idea/workspace.xml index b33610a..c86c55f 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -1,9 +1,20 @@ - + + + - + + + + + + + + + + @@ -14,229 +25,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -246,10 +34,10 @@ - + - - + + @@ -257,8 +45,8 @@ - - + + @@ -326,56 +114,44 @@ - - true true true - - true - DEFINITION_ORDER - - - - - - - - - - + @@ -423,6 +199,18 @@ + + + + + + + + + + + + + +
- - + +
- - + +
- - + +
- - - - - - + - + form.addEventListener("ResponseReceived", event => { + if (event.detail.status === 200) { + requestCompletedAlert.classList.remove("invisible"); + } + submitButton.disabled = false; + }); + + diff --git a/index.js b/index.js index 7486696..e5d2110 100644 --- a/index.js +++ b/index.js @@ -1,99 +1,110 @@ -document.addEventListener('DOMContentLoaded', updateHTML); +document.addEventListener("DOMContentLoaded", updateHTML); function updateHTML() { - const applicableParents = document.querySelectorAll('[data-stein-url]'); + const applicableParents = document.querySelectorAll("[data-stein-url]"); for (let i = 0, element; (element = applicableParents[i]); i++) { const search = element.dataset.steinSearch, - limit = element.dataset.steinLimit, - offset = element.dataset.steinOffset; + limit = element.dataset.steinLimit, + offset = element.dataset.steinOffset; let URL = element.dataset.steinUrl; - URL = URL.endsWith('/') ? URL : URL + '/'; // Normalize URL to end with slash + URL = URL.endsWith("/") ? URL : URL + "/"; // Normalize URL to end with slash // Just add appropriate event handler if the parent node is a form, no interpolations - if (element.tagName === 'FORM') { + if (element.tagName === "FORM") { return configureForm(element, URL); } - element.style.display = 'none'; + element.style.display = "none"; - fetchData({URL, search, limit, offset}) - .then((data) => { - // Get innerHTML, interpolate it for each row in spreadsheet - const contentUnits = []; - for (let i = 0; i < data.length; i++) { - contentUnits.push(element.innerHTML); - } - const interpolatedUnits = contentUnits.map((contentUnit, index) => { - return interpolateString(contentUnit, data[index]); - }); - - // Update the DOM - element.innerHTML = interpolatedUnits.join(''); - element.style.display = ''; - }) - .catch((error) => { - throw new Error(error); + fetchData({ URL, search, limit, offset }) + .then(data => { + // Get innerHTML, interpolate it for each row in spreadsheet + const contentUnits = []; + for (let i = 0; i < data.length; i++) { + contentUnits.push(element.innerHTML); + } + const interpolatedUnits = contentUnits.map((contentUnit, index) => { + return interpolateString(contentUnit, data[index]); }); + + // Update the DOM + element.innerHTML = interpolatedUnits.join(""); + element.style.display = ""; + }) + .catch(error => { + throw new Error(error); + }); } } -function fetchData({URL, search, limit, offset}) { +function fetchData({ URL, search, limit, offset }) { let URLGetParameters = [ - limit ? `limit=${limit}` : '', - offset ? `offset=${offset}` : '', - search ? `search=${search}` : '' + limit ? `limit=${limit}` : "", + offset ? `offset=${offset}` : "", + search ? `search=${search}` : "" ]; - const queryURL = `${URL}?${URLGetParameters.join('&')}`; + const queryURL = `${URL}?${URLGetParameters.join("&")}`; return new Promise((resolve, reject) => { fetch(queryURL) - .then((response) => response.json()) - .then((response) => resolve(response)) - .catch((error) => reject(error)); + .then(response => response.json()) + .then(response => resolve(response)) + .catch(error => reject(error)); }); } function interpolateString(string, replacements) { return string.replace(/{{([^{}]*)}}/g, (fullCapture, key) => { const replacement = replacements[key]; - return typeof replacement === 'string' ? replacement : fullCapture; + return typeof replacement === "string" ? replacement : fullCapture; }); } function configureForm(form, URL) { - form.addEventListener('submit', (e) => { - e.preventDefault(); - let formData = new FormData(form); + form.addEventListener("submit", e => { + e.preventDefault(); + let formData = new FormData(form); - // Convert FormData into JSON - formData = Array.from(formData.entries()).reduce((memo, pair) => ({ - ...memo, - [pair[0]]: pair[1], - }), {}); + // Convert FormData into JSON + formData = Array.from(formData.entries()).reduce( + (memo, pair) => ({ + ...memo, + [pair[0]]: pair[1] + }), + {} + ); - const requestData = { - method: 'POST', - mode: 'cors', - headers: { - 'Content-Type': 'application/json; charset=utf-8', - }, - body: JSON.stringify([formData]) // The API expects an array of rows, and we have just one row - }; + const requestData = { + method: "POST", + mode: "cors", + headers: { + "Content-Type": "application/json; charset=utf-8" + }, + body: JSON.stringify([formData]) // The API expects an array of rows, and we have just one row + }; - fetch(URL, requestData) - .then((response) => new Promise((resolve) => response.json().then((body) => resolve({ - status: response.status, - body - })))) - .then((response) => { - const submitEvent = new CustomEvent('ResponseReceived', {detail: response}); - e.target.dispatchEvent(submitEvent); - }) - .catch((error) => { - throw new Error(error); - }); - } - ); + fetch(URL, requestData) + .then( + response => + new Promise(resolve => + response.json().then(body => + resolve({ + status: response.status, + body + }) + ) + ) + ) + .then(response => { + const submitEvent = new CustomEvent("ResponseReceived", { + detail: response + }); + e.target.dispatchEvent(submitEvent); + }) + .catch(error => { + throw new Error(error); + }); + }); } diff --git a/karma.conf.js b/karma.conf.js index 398cec5..fcc4bff 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -1,31 +1,34 @@ -module.exports = function (config) { +module.exports = function(config) { config.set({ - basePath: '', - frameworks: ['jasmine'], + basePath: "", + frameworks: ["jasmine"], files: [ - 'tests/**/*.js', - {pattern: 'tests/**/*.json', watched: true, served: true, included: false}, - 'tests/**/*.html', - 'index.js' + "tests/**/*.js", + { + pattern: "tests/**/*.json", + watched: true, + served: true, + included: false + }, + "tests/**/*.html", + "index.js" ], proxies: { "/tests/": "/base/tests/" }, preprocessors: { - 'index.js': ['babel'] + "index.js": ["babel"] }, - reporters: ['progress', 'coverage'], + reporters: ["progress", "coverage"], port: 9876, colors: true, logLevel: config.LOG_INFO, autoWatch: false, - browsers: ['ChromeHeadless'], + browsers: ["ChromeHeadless"], singleRun: true, concurrency: Infinity, coverageReporter: { - reporters: [ - {type: 'lcov', subdir: 'report-lcov'} - ] + reporters: [{ type: "lcov", subdir: "report-lcov" }] } }); }; diff --git a/tests/append.js b/tests/append.js index 955aa55..5d4a4a3 100644 --- a/tests/append.js +++ b/tests/append.js @@ -1,118 +1,132 @@ -describe('Write Sheets', function () { - const fixturePath = 'tests/fixtures/append.html', - steinURL = 'http://localhost:8080/v1/storages/5bbf8e7e78625c1890294656/Sheet1', - mockAppendSuccessResponse = fetch('tests/mock-data/mockAppendSuccessResponse.json'); +describe("Write Sheets", function() { + const fixturePath = "tests/fixtures/append.html", + steinURL = + "http://localhost:8080/v1/storages/5bbf8e7e78625c1890294656/Sheet1", + mockAppendSuccessResponse = fetch( + "tests/mock-data/mockAppendSuccessResponse.json" + ); function mockFetch() { // Need this cute line to return a 'clone' of the mock fetch response. This is because a ReadableStream's .json() can only be called once. After all, it's a stream. return new Promise(resolve => { - mockAppendSuccessResponse.then(response => resolve(response.clone())) + mockAppendSuccessResponse.then(response => resolve(response.clone())); }); } - beforeAll(function (done) { - this.workspaceDiv = document.createElement('div'); - this.workspaceDiv.id = 'workspace'; + beforeAll(function(done) { + this.workspaceDiv = document.createElement("div"); + this.workspaceDiv.id = "workspace"; document.body.appendChild(this.workspaceDiv); fetch(fixturePath) - .then(response => response.text()) - .then(html => { - this.fixture = html; - done(); - }); + .then(response => response.text()) + .then(html => { + this.fixture = html; + done(); + }); }); - afterAll(function () { + afterAll(function() { document.body.removeChild(this.workspaceDiv); }); - beforeEach(function () { + beforeEach(function() { // Added spy in beforeEach because the individual specs may alter the spy. - spyOn(window, 'fetch').and.callThrough(); + spyOn(window, "fetch").and.callThrough(); this.workspaceDiv.innerHTML = this.fixture; - document.getElementById('parentElement').setAttribute('data-stein-url', steinURL); + document + .getElementById("parentElement") + .setAttribute("data-stein-url", steinURL); updateHTML(); }); - afterEach(function () { - this.workspaceDiv.innerHTML = ''; + afterEach(function() { + this.workspaceDiv.innerHTML = ""; }); - it('should prevent default submit behaviour, i.e., page should not refresh', function () { - document.getElementById('submit-button').click(); + it("should prevent default submit behaviour, i.e., page should not refresh", function() { + document.getElementById("submit-button").click(); // If the page does not reload, this will be executed expect(true).toBe(true); }); - describe('should make request', function () { - it('to correct URL', function () { + describe("should make request", function() { + it("to correct URL", function() { // Simulate form submit - document.getElementById('submit-button').click(); + document.getElementById("submit-button").click(); - const requestedURL = normalizeURL(window.fetch.calls.mostRecent().args[0]), - expectedURL = normalizeURL(steinURL); + const requestedURL = normalizeURL( + window.fetch.calls.mostRecent().args[0] + ), + expectedURL = normalizeURL(steinURL); expect(requestedURL).toBe(expectedURL); }); - it('with correct method and headers', function () { - document.getElementById('submit-button').click(); + it("with correct method and headers", function() { + document.getElementById("submit-button").click(); const requestArgument = window.fetch.calls.mostRecent().args[1]; - expect(requestArgument.method.toUpperCase()).toBe('POST'); - expect(requestArgument.mode.toUpperCase()).toBe('CORS'); + expect(requestArgument.method.toUpperCase()).toBe("POST"); + expect(requestArgument.mode.toUpperCase()).toBe("CORS"); expect(requestArgument.headers).toEqual({ - 'Content-Type': 'application/json; charset=utf-8' + "Content-Type": "application/json; charset=utf-8" }); }); - it('with correct body', function () { - document.getElementById('title-input').value = 'An interesting title'; - document.getElementById('author-input').value = 'Shiven Sinha'; - document.getElementById('link-input').value = 'https://example.com/post/1'; - document.getElementById('content-textarea').value = 'A short introduction'; + it("with correct body", function() { + document.getElementById("title-input").value = "An interesting title"; + document.getElementById("author-input").value = "Shiven Sinha"; + document.getElementById("link-input").value = + "https://example.com/post/1"; + document.getElementById("content-textarea").value = + "A short introduction"; - const formData = new FormData(document.getElementById('parentElement')); + const formData = new FormData(document.getElementById("parentElement")); let parsedData = {}; for (let [key, value] of formData.entries()) { parsedData[key] = value; } - document.getElementById('submit-button').click(); + document.getElementById("submit-button").click(); - expect(window.fetch.calls.mostRecent().args[1].body).toBe(JSON.stringify([parsedData])); + expect(window.fetch.calls.mostRecent().args[1].body).toBe( + JSON.stringify([parsedData]) + ); }); }); - describe('dispatch event on receiving response', function () { - it('should dispatch event ResponseReceived on the form on receiving response', function (done) { - document.getElementById('parentElement').addEventListener('ResponseReceived', () => { - expect(true).toBe(true); - done(); - }); + describe("dispatch event on receiving response", function() { + it("should dispatch event ResponseReceived on the form on receiving response", function(done) { + document + .getElementById("parentElement") + .addEventListener("ResponseReceived", () => { + expect(true).toBe(true); + done(); + }); fetch.and.callFake(mockFetch); - document.getElementById('submit-button').click(); + document.getElementById("submit-button").click(); }); - it('should provide details of response', function (done) { - document.getElementById('parentElement').addEventListener('ResponseReceived', (event) => { - expect(event.detail).toEqual({ - status: 200, - body: {updatedRange: 'Sheet1!A8:C9'} + it("should provide details of response", function(done) { + document + .getElementById("parentElement") + .addEventListener("ResponseReceived", event => { + expect(event.detail).toEqual({ + status: 200, + body: { updatedRange: "Sheet1!A8:C9" } + }); + done(); }); - done(); - }); fetch.and.callFake(mockFetch); - document.getElementById('submit-button').click(); + document.getElementById("submit-button").click(); }); }); - -}); \ No newline at end of file +}); diff --git a/tests/mock-data/mockData.json b/tests/mock-data/mockData.json index 9abdff8..fc325c8 100644 --- a/tests/mock-data/mockData.json +++ b/tests/mock-data/mockData.json @@ -17,4 +17,4 @@ "content": "The other night, I was in the middle of an argument with my boyfriend when my mom called. I picked up, because if I don’t, she’ll keep leaving voicemails until my phone explodes.", "link": "https://medium.com/s/powertrip/the-awkward-power-dynamics-of-being-friends-with-your-parents-39ba8b585fc4" } -] \ No newline at end of file +] diff --git a/tests/read.js b/tests/read.js index 9e946c6..1fec22e 100644 --- a/tests/read.js +++ b/tests/read.js @@ -1,183 +1,236 @@ -describe('Read Sheets', function () { - const fixturePath = 'tests/fixtures/read.html', - mockFetchResponse = fetch('tests/mock-data/mockData.json'), - mockIncorrectFetchResponse = fetch('nonexistent.json'), - steinURL = 'http://localhost:8080/v1/storages/5bbf8e7e78625c1890294656/Sheet1'; +describe("Read Sheets", function() { + const fixturePath = "tests/fixtures/read.html", + mockFetchResponse = fetch("tests/mock-data/mockData.json"), + mockIncorrectFetchResponse = fetch("nonexistent.json"), + steinURL = + "http://localhost:8080/v1/storages/5bbf8e7e78625c1890294656/Sheet1"; function mockFetch() { // Need this cute line to return a 'clone' of the mock fetch response. This is because a ReadableStream's .json() can only be called once. After all, it's a stream. return new Promise(resolve => { - mockFetchResponse.then(response => resolve(response.clone())) + mockFetchResponse.then(response => resolve(response.clone())); }); } - beforeAll(function (done) { + beforeAll(function(done) { // Initialise DOM workspace - this.workspaceDiv = document.createElement('div'); - this.workspaceDiv.id = 'workspace'; + this.workspaceDiv = document.createElement("div"); + this.workspaceDiv.id = "workspace"; document.body.appendChild(this.workspaceDiv); fetch(fixturePath) - .then(response => response.text()) - .then(html => { - this.fixture = html; - done(); - }); + .then(response => response.text()) + .then(html => { + this.fixture = html; + done(); + }); }); - afterAll(function () { + afterAll(function() { document.body.removeChild(this.workspaceDiv); }); - beforeEach(function () { + beforeEach(function() { // Added spy in beforeEach because the individual specs may alter the spy. - spyOn(window, 'fetch').and.callFake(mockFetch); + spyOn(window, "fetch").and.callFake(mockFetch); }); - afterEach(function () { - this.workspaceDiv.innerHTML = ''; + afterEach(function() { + this.workspaceDiv.innerHTML = ""; }); - it('should hide the parent element initially', function (done) { + it("should hide the parent element initially", function(done) { this.workspaceDiv.innerHTML = this.fixture; - const parentElement = document.getElementById('parentElement'); - parentElement.setAttribute('data-stein-url', steinURL); + const parentElement = document.getElementById("parentElement"); + parentElement.setAttribute("data-stein-url", steinURL); updateHTML(); - expect(parentElement.style.display).toBe('none'); + expect(parentElement.style.display).toBe("none"); done(); }); - describe('should make request to correct URL', function () { - beforeEach(function () { + describe("should make request to correct URL", function() { + beforeEach(function() { this.workspaceDiv.innerHTML = this.fixture; - this.parentElement = document.getElementById('parentElement'); - this.parentElement.setAttribute('data-stein-url', steinURL); + this.parentElement = document.getElementById("parentElement"); + this.parentElement.setAttribute("data-stein-url", steinURL); }); - it('without any options set', function () { + it("without any options set", function() { updateHTML(); // Normalize URLs to compare them directly const requestedURL = normalizeURL(fetch.calls.mostRecent().args[0]), - expectedURL = normalizeURL(steinURL); + expectedURL = normalizeURL(steinURL); expect(requestedURL).toEqual(expectedURL); }); - it('even when URL with trailing / is provided', function () { - this.parentElement.setAttribute('data-stein-url', `${steinURL}/`); + it("even when URL with trailing / is provided", function() { + this.parentElement.setAttribute("data-stein-url", `${steinURL}/`); updateHTML(); const requestedURL = normalizeURL(fetch.calls.mostRecent().args[0]), - expectedURL = normalizeURL(steinURL); + expectedURL = normalizeURL(steinURL); expect(requestedURL).toBe(expectedURL); }); - it('with limit parameter', function () { + it("with limit parameter", function() { const limitValue = 3; - this.parentElement.setAttribute('data-stein-limit', limitValue.toString()); + this.parentElement.setAttribute( + "data-stein-limit", + limitValue.toString() + ); updateHTML(); const requestedURL = normalizeURL(fetch.calls.mostRecent().args[0]), - expectedURL = normalizeURL(`${steinURL}/?limit=${limitValue}`); + expectedURL = normalizeURL(`${steinURL}/?limit=${limitValue}`); expect(requestedURL).toBe(expectedURL); }); - it('with offset parameter', function () { + it("with offset parameter", function() { const offsetValue = 1; - this.parentElement.setAttribute('data-stein-offset', offsetValue.toString()); + this.parentElement.setAttribute( + "data-stein-offset", + offsetValue.toString() + ); updateHTML(); const requestedURL = normalizeURL(fetch.calls.mostRecent().args[0]), - expectedURL = normalizeURL(`${steinURL}/?offset=${offsetValue}`); + expectedURL = normalizeURL(`${steinURL}/?offset=${offsetValue}`); expect(requestedURL).toBe(expectedURL); }); - it('with search parameter', function () { - const searchConditions = {author: "Zat Rana"}; - this.parentElement.setAttribute('data-stein-search', JSON.stringify(searchConditions)); + it("with search parameter", function() { + const searchConditions = { author: "Zat Rana" }; + this.parentElement.setAttribute( + "data-stein-search", + JSON.stringify(searchConditions) + ); updateHTML(); const requestedURL = normalizeURL(fetch.calls.mostRecent().args[0]), - expectedURL = normalizeURL(`${steinURL}/?search=${JSON.stringify(searchConditions)}`); + expectedURL = normalizeURL( + `${steinURL}/?search=${JSON.stringify(searchConditions)}` + ); expect(requestedURL).toBe(expectedURL); }); - it('with both limit and offset parameters', function () { + it("with both limit and offset parameters", function() { const limitValue = 3, - offsetValue = 1; - - this.parentElement.setAttribute('data-stein-limit', limitValue.toString()); - this.parentElement.setAttribute('data-stein-offset', offsetValue.toString()); + offsetValue = 1; + + this.parentElement.setAttribute( + "data-stein-limit", + limitValue.toString() + ); + this.parentElement.setAttribute( + "data-stein-offset", + offsetValue.toString() + ); updateHTML(); const requestedURL = normalizeURL(fetch.calls.mostRecent().args[0]), - expectedURL = normalizeURL(`${steinURL}/?limit=${limitValue}&offset=${offsetValue}`); + expectedURL = normalizeURL( + `${steinURL}/?limit=${limitValue}&offset=${offsetValue}` + ); expect(requestedURL).toBe(expectedURL); }); - it('with both limit and search parameters', function () { + it("with both limit and search parameters", function() { const limitValue = 3, - searchConditions = {author: "Zat Rana"}; - - this.parentElement.setAttribute('data-stein-limit', limitValue.toString()); - this.parentElement.setAttribute('data-stein-search', JSON.stringify(searchConditions)); + searchConditions = { author: "Zat Rana" }; + + this.parentElement.setAttribute( + "data-stein-limit", + limitValue.toString() + ); + this.parentElement.setAttribute( + "data-stein-search", + JSON.stringify(searchConditions) + ); updateHTML(); const requestedURL = normalizeURL(fetch.calls.mostRecent().args[0]), - expectedURL = normalizeURL(`${steinURL}/?limit=${limitValue}&search=${JSON.stringify(searchConditions)}`); + expectedURL = normalizeURL( + `${steinURL}/?limit=${limitValue}&search=${JSON.stringify( + searchConditions + )}` + ); expect(requestedURL).toBe(expectedURL); }); - it('with both offset and search parameters', function () { + it("with both offset and search parameters", function() { const offsetValue = 1, - searchConditions = {author: "Zat Rana"}; - - this.parentElement.setAttribute('data-stein-offset', offsetValue.toString()); - this.parentElement.setAttribute('data-stein-search', JSON.stringify(searchConditions)); + searchConditions = { author: "Zat Rana" }; + + this.parentElement.setAttribute( + "data-stein-offset", + offsetValue.toString() + ); + this.parentElement.setAttribute( + "data-stein-search", + JSON.stringify(searchConditions) + ); updateHTML(); const requestedURL = normalizeURL(fetch.calls.mostRecent().args[0]), - expectedURL = normalizeURL(`${steinURL}/?offset=${offsetValue}&search=${JSON.stringify(searchConditions)}`); + expectedURL = normalizeURL( + `${steinURL}/?offset=${offsetValue}&search=${JSON.stringify( + searchConditions + )}` + ); expect(requestedURL).toBe(expectedURL); }); - it('with all search, limit, and offset parameters', function () { + it("with all search, limit, and offset parameters", function() { const limitValue = 3, - offsetValue = 1, - searchConditions = {author: "Zat Rana"}; - - this.parentElement.setAttribute('data-stein-limit', limitValue.toString()); - this.parentElement.setAttribute('data-stein-offset', offsetValue.toString()); - this.parentElement.setAttribute('data-stein-search', JSON.stringify(searchConditions)); + offsetValue = 1, + searchConditions = { author: "Zat Rana" }; + + this.parentElement.setAttribute( + "data-stein-limit", + limitValue.toString() + ); + this.parentElement.setAttribute( + "data-stein-offset", + offsetValue.toString() + ); + this.parentElement.setAttribute( + "data-stein-search", + JSON.stringify(searchConditions) + ); updateHTML(); const requestedURL = normalizeURL(fetch.calls.mostRecent().args[0]), - expectedURL = normalizeURL(`${steinURL}/?limit=${limitValue}&offset=${offsetValue}&search=${JSON.stringify(searchConditions)}`); + expectedURL = normalizeURL( + `${steinURL}/?limit=${limitValue}&offset=${offsetValue}&search=${JSON.stringify( + searchConditions + )}` + ); expect(requestedURL).toBe(expectedURL); }); }); - it('should make the parent element visible on receiving data', function (done) { + it("should make the parent element visible on receiving data", function(done) { // Set up an observer for changes in the parent element that would be injected later const mutationObserver = new MutationObserver(() => { - expect(parentElement.style.display).toEqual(''); + expect(parentElement.style.display).toEqual(""); done(); }); this.workspaceDiv.innerHTML = this.fixture; - const parentElement = document.getElementById('parentElement'); - parentElement.setAttribute('data-stein-url', steinURL); + const parentElement = document.getElementById("parentElement"); + parentElement.setAttribute("data-stein-url", steinURL); // Activate the observer on parent element mutationObserver.observe(parentElement, { @@ -188,36 +241,36 @@ describe('Read Sheets', function () { updateHTML(); }); - it('should interpolate correctly on receiving data', function (done) { + it("should interpolate correctly on receiving data", function(done) { // Set up an observer for changes in the parent element that would be injected later const mutationObserver = new MutationObserver(() => { - const titleElements = document.querySelectorAll('.title'), - authorElements = document.querySelectorAll('.author'), - contentElements = document.querySelectorAll('.content'), - linkElements = document.querySelectorAll('.link'); + const titleElements = document.querySelectorAll(".title"), + authorElements = document.querySelectorAll(".author"), + contentElements = document.querySelectorAll(".content"), + linkElements = document.querySelectorAll(".link"); mockFetch() - .then(response => response.json()) - .then(data => { - // Comparing an objected generated from reversing the interpolations to the mock data for each 'row' - data.forEach((currentRecord, index) => { - const interpolationResults = { - title: titleElements[index].innerHTML, - author: authorElements[index].innerHTML, - content: contentElements[index].innerHTML, - link: linkElements[index].href - }; - - expect(currentRecord).toEqual(interpolationResults); - }); - - done(); + .then(response => response.json()) + .then(data => { + // Comparing an objected generated from reversing the interpolations to the mock data for each 'row' + data.forEach((currentRecord, index) => { + const interpolationResults = { + title: titleElements[index].innerHTML, + author: authorElements[index].innerHTML, + content: contentElements[index].innerHTML, + link: linkElements[index].href + }; + + expect(currentRecord).toEqual(interpolationResults); }); + + done(); + }); }); this.workspaceDiv.innerHTML = this.fixture; - const parentElement = document.getElementById('parentElement'); - parentElement.setAttribute('data-stein-url', steinURL); + const parentElement = document.getElementById("parentElement"); + parentElement.setAttribute("data-stein-url", steinURL); // Activate the observer on parent element mutationObserver.observe(parentElement, { @@ -229,25 +282,28 @@ describe('Read Sheets', function () { }); // Any changes to make this more elegant are welcome - it('should throw error on incorrect data received', function (done) { + it("should throw error on incorrect data received", function(done) { this.workspaceDiv.innerHTML = this.fixture; - const parentElement = document.getElementById('parentElement'); - parentElement.setAttribute('data-stein-url', steinURL); - spyOn(window, 'fetchData').and.callThrough(); + const parentElement = document.getElementById("parentElement"); + parentElement.setAttribute("data-stein-url", steinURL); + spyOn(window, "fetchData").and.callThrough(); - fetch.and.returnValue(new Promise(resolve => { - resolve(mockIncorrectFetchResponse); - })); + fetch.and.returnValue( + new Promise(resolve => { + resolve(mockIncorrectFetchResponse); + }) + ); updateHTML(); - fetchData.calls.mostRecent().returnValue - .then(() => { - done.fail('does not throw error'); - }) - .catch((e) => { - expect(e).toBeTruthy(); - done(); - }); + fetchData.calls + .mostRecent() + .returnValue.then(() => { + done.fail("does not throw error"); + }) + .catch(e => { + expect(e).toBeTruthy(); + done(); + }); }); }); diff --git a/tests/utils/normalizeURL.js b/tests/utils/normalizeURL.js index dc437cb..e2c2e79 100644 --- a/tests/utils/normalizeURL.js +++ b/tests/utils/normalizeURL.js @@ -1,75 +1,88 @@ -const URLParser = typeof URL === 'undefined' ? require('url').URL : URL; +const URLParser = typeof URL === "undefined" ? require("url").URL : URL; function testParameter(name, filters) { - return filters.some(filter => filter instanceof RegExp ? filter.test(name) : filter === name); -}; + return filters.some(filter => + filter instanceof RegExp ? filter.test(name) : filter === name + ); +} function normalizeURL(urlString, opts) { - opts = Object.assign({ - defaultProtocol: 'http:', - normalizeProtocol: true, - forceHttp: false, - forceHttps: false, - stripHash: true, - stripWWW: true, - removeQueryParameters: [/^utm_\w+/i], - removeTrailingSlash: true, - removeDirectoryIndex: false, - sortQueryParameters: true - }, opts); + opts = Object.assign( + { + defaultProtocol: "http:", + normalizeProtocol: true, + forceHttp: false, + forceHttps: false, + stripHash: true, + stripWWW: true, + removeQueryParameters: [/^utm_\w+/i], + removeTrailingSlash: true, + removeDirectoryIndex: false, + sortQueryParameters: true + }, + opts + ); // Backwards compatibility - if (Reflect.has(opts, 'normalizeHttps')) { + if (Reflect.has(opts, "normalizeHttps")) { opts.forceHttp = opts.normalizeHttps; } - if (Reflect.has(opts, 'normalizeHttp')) { + if (Reflect.has(opts, "normalizeHttp")) { opts.forceHttps = opts.normalizeHttp; } - if (Reflect.has(opts, 'stripFragment')) { + if (Reflect.has(opts, "stripFragment")) { opts.stripHash = opts.stripFragment; } urlString = urlString.trim(); - const hasRelativeProtocol = urlString.startsWith('//'); + const hasRelativeProtocol = urlString.startsWith("//"); const isRelativeUrl = !hasRelativeProtocol && /^\.*\//.test(urlString); // Prepend protocol if (!isRelativeUrl) { - urlString = urlString.replace(/^(?!(?:\w+:)?\/\/)|^\/\//, opts.defaultProtocol); + urlString = urlString.replace( + /^(?!(?:\w+:)?\/\/)|^\/\//, + opts.defaultProtocol + ); } const urlObj = new URLParser(urlString); if (opts.forceHttp && opts.forceHttps) { - throw new Error('The `forceHttp` and `forceHttps` options cannot be used together'); + throw new Error( + "The `forceHttp` and `forceHttps` options cannot be used together" + ); } - if (opts.forceHttp && urlObj.protocol === 'https:') { - urlObj.protocol = 'http:'; + if (opts.forceHttp && urlObj.protocol === "https:") { + urlObj.protocol = "http:"; } - if (opts.forceHttps && urlObj.protocol === 'http:') { - urlObj.protocol = 'https:'; + if (opts.forceHttps && urlObj.protocol === "http:") { + urlObj.protocol = "https:"; } // Remove hash if (opts.stripHash) { - urlObj.hash = ''; + urlObj.hash = ""; } // Remove duplicate slashes if not preceded by a protocol if (urlObj.pathname) { // TODO: Use the following instead when targeting Node.js 10 // `urlObj.pathname = urlObj.pathname.replace(/(? { - if (/^(?!\/)/g.test(p1)) { - return `${p1}/`; + urlObj.pathname = urlObj.pathname.replace( + /((?![https?:]).)\/{2,}/g, + (_, p1) => { + if (/^(?!\/)/g.test(p1)) { + return `${p1}/`; + } + return "/"; } - return '/'; - }); + ); } // Decode URI octets @@ -82,26 +95,32 @@ function normalizeURL(urlString, opts) { opts.removeDirectoryIndex = [/^index\.[a-z]+$/]; } - if (Array.isArray(opts.removeDirectoryIndex) && opts.removeDirectoryIndex.length > 0) { - let pathComponents = urlObj.pathname.split('/'); + if ( + Array.isArray(opts.removeDirectoryIndex) && + opts.removeDirectoryIndex.length > 0 + ) { + let pathComponents = urlObj.pathname.split("/"); const lastComponent = pathComponents[pathComponents.length - 1]; if (testParameter(lastComponent, opts.removeDirectoryIndex)) { pathComponents = pathComponents.slice(0, pathComponents.length - 1); - urlObj.pathname = pathComponents.slice(1).join('/') + '/'; + urlObj.pathname = pathComponents.slice(1).join("/") + "/"; } } if (urlObj.hostname) { // Remove trailing dot - urlObj.hostname = urlObj.hostname.replace(/\.$/, ''); + urlObj.hostname = urlObj.hostname.replace(/\.$/, ""); // Remove `www.` - if (opts.stripWWW && /^www\.([a-z\-\d]{2,63})\.([a-z.]{2,5})$/.test(urlObj.hostname)) { + if ( + opts.stripWWW && + /^www\.([a-z\-\d]{2,63})\.([a-z.]{2,5})$/.test(urlObj.hostname) + ) { // Each label should be max 63 at length (min: 2). // The extension should be max 5 at length (min: 2). // Source: https://en.wikipedia.org/wiki/Hostname#Restrictions_on_valid_host_names - urlObj.hostname = urlObj.hostname.replace(/^www\./, ''); + urlObj.hostname = urlObj.hostname.replace(/^www\./, ""); } } @@ -123,14 +142,14 @@ function normalizeURL(urlString, opts) { urlString = urlObj.toString(); // Remove ending `/` - if (opts.removeTrailingSlash || urlObj.pathname === '/') { - urlString = urlString.replace(/\/$/, ''); + if (opts.removeTrailingSlash || urlObj.pathname === "/") { + urlString = urlString.replace(/\/$/, ""); } // Restore relative protocol, if applicable if (hasRelativeProtocol && !opts.normalizeProtocol) { - urlString = urlString.replace(/^http:\/\//, '//'); + urlString = urlString.replace(/^http:\/\//, "//"); } return urlString; -} \ No newline at end of file +}