+
{JSON.stringify(props.theme)}
-
+
{JSON.stringify(props.tutorialSeen)}
- ),
- };
-});
+ )
+ }
+})
-jest.mock("./Widget", () => {
+jest.mock('./Widget', () => {
return {
__esModule: true,
- WidgetsList: () =>
WidgetsList
,
- };
-});
+ WidgetsList: () =>
WidgetsList
+ }
+})
-jest.mock("./Backend", () => {
+jest.mock('./Backend', () => {
return {
__esModule: true,
BackendProvider: ({ children }) => <>{children}>,
- IndexesHandler: ({ children }) => <>{children}>,
- };
-});
+ IndexesHandler: ({ children }) => <>{children}>
+ }
+})
// Mock available languages
-jest.mock("./i18n/available", () => {
+jest.mock('./i18n/available', () => {
return {
__esModule: true,
default: [
- { key: "tested", label: "Tested", value: { locale: "en-en" } },
- { key: "ca-es", label: "Tested ca-es", value: { locale: "fr-FR" } },
- { key: "es-es", label: "Tested es-es", value: { locale: "es-es" } },
- ],
- };
-});
-
-test("fixes location hash", () => {
- window.location.hash = "#/%23/";
- fixLocationHash();
- expect(window.location.hash).toBe("#/#/");
-});
-
-test("uses default language `ca-es` when navigator language is unknown", () => {
- expect(getDefaultLanguage([])).toBe("ca-es");
-});
-
-test("detects navigator language", () => {
- const languageGetter = jest.spyOn(window.navigator, "language", "get");
- languageGetter.mockReturnValue("es-ES");
- expect(getDefaultLanguage([{ key: "es-es" }])).toBe("es-es");
-});
-
-test("renders mocked dashboard", () => {
- const app = render(
);
- const element = app.getByTestId("dashboard-mock");
- expect(element).toBeInTheDocument();
-});
-
-test("detects user changed language", async () => {
- let app;
+ { key: 'tested', label: 'Tested', value: { locale: 'en-en' } },
+ { key: 'ca-es', label: 'Tested ca-es', value: { locale: 'fr-FR' } },
+ { key: 'es-es', label: 'Tested es-es', value: { locale: 'es-es' } }
+ ]
+ }
+})
+
+test('fixes location hash', () => {
+ window.location.hash = '#/%23/'
+ fixLocationHash()
+ expect(window.location.hash).toBe('#/#/')
+})
+
+test('uses default language `ca-es` when navigator language is unknown', () => {
+ expect(getDefaultLanguage([])).toBe('ca-es')
+})
+
+test('detects navigator language', () => {
+ const languageGetter = jest.spyOn(window.navigator, 'language', 'get')
+ languageGetter.mockReturnValue('es-ES')
+ expect(getDefaultLanguage([{ key: 'es-es' }])).toBe('es-es')
+})
+
+test('renders mocked dashboard', () => {
+ const app = render(
)
+ const element = app.getByTestId('dashboard-mock')
+ expect(element).toBeInTheDocument()
+})
+
+test('detects user changed language', async () => {
+ let app
act(() => {
- app = render(
);
- });
+ app = render(
)
+ })
await act(async () => {
// User selects a language
- const button = app.getByTestId("dashboard-mock-fn-language-change");
- expect(button).toBeInTheDocument();
- fireEvent.click(button);
+ const button = app.getByTestId('dashboard-mock-fn-language-change')
+ expect(button).toBeInTheDocument()
+ fireEvent.click(button)
// Execute events block in current event loop
- await delay(0);
+ await delay(0)
- const status = app.getByTestId("dashboard-mock-language");
- expect(status).toHaveTextContent("tested");
- });
-});
+ const status = app.getByTestId('dashboard-mock-language')
+ expect(status).toHaveTextContent('tested')
+ })
+})
-test("detects user changed theme", async () => {
- let app;
+test('detects user changed theme', async () => {
+ let app
act(() => {
- app = render(
);
- });
+ app = render(
)
+ })
await act(async () => {
// User selects a theme
- const button = app.getByTestId("dashboard-mock-fn-theme-change");
- expect(button).toBeInTheDocument();
- fireEvent.click(button);
+ const button = app.getByTestId('dashboard-mock-fn-theme-change')
+ expect(button).toBeInTheDocument()
+ fireEvent.click(button)
// Execute events block in current event loop
- await delay(0);
+ await delay(0)
- const status = app.getByTestId("dashboard-mock-theme");
- expect(status).toHaveTextContent("tested");
- });
-});
+ const status = app.getByTestId('dashboard-mock-theme')
+ expect(status).toHaveTextContent('tested')
+ })
+})
-test("detects user visited tutorial", async () => {
- let app;
+test('detects user visited tutorial', async () => {
+ let app
act(() => {
- app = render(
);
- });
+ app = render(
)
+ })
await act(async () => {
// User selects a theme
- const button = app.getByTestId("dashboard-mock-fn-tutorial-seen");
- expect(button).toBeInTheDocument();
- fireEvent.click(button);
+ const button = app.getByTestId('dashboard-mock-fn-tutorial-seen')
+ expect(button).toBeInTheDocument()
+ fireEvent.click(button)
// Execute events block in current event loop
- await delay(0);
+ await delay(0)
- const status = app.getByTestId("dashboard-mock-tutorial-seen");
- expect(status).toHaveTextContent("tested");
- });
-});
+ const status = app.getByTestId('dashboard-mock-tutorial-seen')
+ expect(status).toHaveTextContent('tested')
+ })
+})
diff --git a/app/src/Backend/Base/Cache.js b/app/src/Backend/Base/Cache.js
index 02c57edb..047239eb 100644
--- a/app/src/Backend/Base/Cache.js
+++ b/app/src/Backend/Base/Cache.js
@@ -1,108 +1,108 @@
-import Common, { log } from "./Common";
+import Common, { log } from './Common'
// Transform a string into a smaller/hash one of it
const hashStr = (str) => {
if (str.length === 0) {
- return 0;
+ return 0
}
return [...str]
.map((char) => char.charCodeAt(0))
.reduce((hash, int) => {
- const hashTmp = (hash << 5) - hash + int;
- return hashTmp & hashTmp; // Convert to 32bit integer
- }, 0);
-};
+ const hashTmp = (hash << 5) - hash + int
+ return hashTmp & hashTmp // Convert to 32bit integer
+ }, 0)
+}
// Handles an element inside the cache
class FetchCacheElement extends Common {
- name = "Cache Element";
- fetching = false;
- result = null;
- lastModified = 0;
- invalidated = false;
- listeners = [];
- url = "";
-
- constructor(url) {
- super();
- this.url = url;
+ name = 'Cache Element'
+ fetching = false
+ result = null
+ lastModified = 0
+ invalidated = false
+ listeners = []
+ url = ''
+
+ constructor (url) {
+ super()
+ this.url = url
}
// Handle a fetch error by calling all the listeners' onError in the queue
onError = (error) => {
- this.listeners.forEach((listener) => listener.onError(error));
- this.cleanFetch();
- };
+ this.listeners.forEach((listener) => listener.onError(error))
+ this.cleanFetch()
+ }
// Add `this.url` to error messages
catchFetchErrorsMessage = (err) =>
- `${this.url}: ${this.name} backend: ${err.message}`;
+ `${this.url}: ${this.name} backend: ${err.message}`
// Removes all unneeded data related to a fetch
- cleanFetch = () => (this.fetching = false);
+ cleanFetch = () => (this.fetching = false)
// Registers a listener
addListener = (onSuccess, onError) => {
this.listeners.push({
onSuccess,
- onError,
- });
+ onError
+ })
// If this is the first listener, launch the fetch
if (!this.fetching && this.result === null) {
- this.fetch();
+ this.fetch()
}
// If we already have the data and it has not been invalidated, create
// a self-resolving promise which executes the listener after resolution
if (!this.invalidated && this.result !== null) {
- new Promise((resolve, reject) => resolve(this.result)).then(onSuccess);
+ new Promise((resolve, reject) => resolve(this.result)).then(onSuccess)
}
- this.log(`Added listener to ${this.url}: ${this.listeners.length - 1}`);
+ this.log(`Added listener to ${this.url}: ${this.listeners.length - 1}`)
- return () => this.removeListener(onSuccess);
- };
+ return () => this.removeListener(onSuccess)
+ }
// Disables/unregisters a listener
removeListener = (onSuccess) => {
// Finds the listener by comparing the onSuccess function pointer
const found = this.listeners
.map((listener, index) => ({ listener, index }))
- .find((l) => l.listener.onSuccess === onSuccess);
+ .find((l) => l.listener.onSuccess === onSuccess)
if (found) {
- this.log(`Remove listener from ${this.url}: ${found.index}`);
+ this.log(`Remove listener from ${this.url}: ${found.index}`)
// Remove element from array
- this.listeners.splice(found.index, 1);
+ this.listeners.splice(found.index, 1)
} else {
- console.warn(`Remove listener from ${this.url}: Listener not found`);
+ console.warn(`Remove listener from ${this.url}: Listener not found`)
}
// If there is an ongoing fetch and there are no more listeners left, abort the fetch
if (this.fetching && this.listeners.length === 0) {
- this.abort();
- this.cleanFetch();
+ this.abort()
+ this.cleanFetch()
}
- };
+ }
// Caches the data and calls all the listeners' onSuccess
processSuccessFetch = ({ result, lastModified }) => {
- this.result = result;
- this.lastModified = lastModified;
- this.listeners.forEach((listener) => listener.onSuccess(this.result));
+ this.result = result
+ this.lastModified = lastModified
+ this.listeners.forEach((listener) => listener.onSuccess(this.result))
- this.cleanFetch();
+ this.cleanFetch()
// Allow re-chaining promises
- return this.result;
- };
+ return this.result
+ }
// Gets a Date object from a fetch response `last-modified` HTTP header
getLastModifiedFromResponse = (response) =>
- new Date(response.headers.get("last-modified"));
+ new Date(response.headers.get('last-modified'))
// Fetches a URL:
// - Transforms from JSON to JS object
@@ -111,31 +111,31 @@ class FetchCacheElement extends Common {
// - Calls the callback
// - If there is an error, processes all error listeners
fetch = (callback = () => {}) => {
- this.fetching = true;
+ this.fetching = true
return fetch(this.url, { signal: this.controller.signal })
.then(this.handleFetchErrors)
.then((response) =>
response.json().then((result) => ({
result,
- lastModified: this.getLastModifiedFromResponse(response),
+ lastModified: this.getLastModifiedFromResponse(response)
}))
)
.then(this.processSuccessFetch)
.then(callback)
.catch(this.catchFetchErrors)
- .finally(this.cleanFetch);
- };
+ .finally(this.cleanFetch)
+ }
// Sends a HEAD request to download URL header's `last-modified` value and
// returns the comparison against saved value: `true` if new one is higher
checkIfNeedUpdate = (onSuccess, onError) => {
- return fetch(this.url, { method: "HEAD", signal: this.controller.signal })
+ return fetch(this.url, { method: 'HEAD', signal: this.controller.signal })
.then(this.handleFetchErrors)
.then(this.getLastModifiedFromResponse)
.then((date) => date > this.lastModified /* || true */) // TEST TODO BUG DEBUG
.then(onSuccess)
- .catch(onError);
- };
+ .catch(onError)
+ }
// Invalidates the cache and recalls it's download if we have any listener
invalidate = () => {
@@ -143,26 +143,26 @@ class FetchCacheElement extends Common {
if (this.invalidated) {
// Resolve without doing nothing
// It has already just been invalidated
- this.log(`${this.url}: It has already been invalidated`);
- resolve();
+ this.log(`${this.url}: It has already been invalidated`)
+ resolve()
} else if (this.result !== null) {
- this.invalidated = true;
+ this.invalidated = true
// Are there listeners?
if (this.listeners.length > 0) {
- this.log(`${this.url}: Fetch it!`);
+ this.log(`${this.url}: Fetch it!`)
this.fetch(() => {
- this.invalidated = false;
- });
- resolve();
+ this.invalidated = false
+ })
+ resolve()
} else {
// Resolve without doing nothing
// Someone downloaded it, but unregistered from it: changed data source
this.log(
`${this.url}: Someone downloaded it, but unregistered from it: changed data source`
- );
- this.invalidated = false;
- resolve();
+ )
+ this.invalidated = false
+ resolve()
}
} else {
// Resolve without doing nothing
@@ -170,13 +170,13 @@ class FetchCacheElement extends Common {
this.log(`${this.url}: It was never downloaded`, {
result: this.result,
invalidated: this.invalidated,
- t: this,
- });
- this.invalidated = false;
- resolve();
+ t: this
+ })
+ this.invalidated = false
+ resolve()
}
- });
- };
+ })
+ }
}
// Handles the whole cache
@@ -190,36 +190,36 @@ class FetchCache {
...
}
*/
- data = {};
+ data = {}
- name = "Cache manager";
- log = log;
+ name = 'Cache manager'
+ log = log
// Handles fetch requests:
// - Creates a new fetch if it's the first time for this URL
// - Adds fetch listener if there is an ongoing fetch for that URL
// - Returns the result if a fetch for that URL has already been cached
fetch = (url, onSuccess, onError = () => {}) => {
- const id = hashStr(url);
+ const id = hashStr(url)
// If we have not an ongoing request for this URL, create one
if (!(id in this.data)) {
- this.data[id] = new FetchCacheElement(url);
+ this.data[id] = new FetchCacheElement(url)
}
- return this.data[id].addListener(onSuccess, onError);
- };
+ return this.data[id].addListener(onSuccess, onError)
+ }
// Sends a HEAD request to download URL header's `last-modified` value and
// returns the comparison against saved value: `true` if new one is higher
checkIfNeedUpdate = (url, onSuccess, onError) => {
- const id = hashStr(url);
- return this.data[id].checkIfNeedUpdate(onSuccess, onError);
- };
+ const id = hashStr(url)
+ return this.data[id].checkIfNeedUpdate(onSuccess, onError)
+ }
// Invalidates a cache entry and recalls it's download if it has any listener
invalidate = (url) => {
- const id = hashStr(url);
+ const id = hashStr(url)
if (!(id in this.data)) {
return new Promise((resolve, reject) => {
// Resolve without doing nothing
@@ -227,18 +227,18 @@ class FetchCache {
this.log(`${url}: It was never downloaded`, {
id,
url,
- data: this.data,
- });
- resolve(true);
- });
+ data: this.data
+ })
+ resolve(true)
+ })
}
- return this.data[id].invalidate();
- };
+ return this.data[id].invalidate()
+ }
}
// This is a singleton:
// All clients re-use the same cache
-const cache = new FetchCache();
-Object.freeze(cache);
+const cache = new FetchCache()
+Object.freeze(cache)
-export default cache;
+export default cache
diff --git a/app/src/Backend/Base/Common.js b/app/src/Backend/Base/Common.js
index 9fcfe41e..680c290e 100644
--- a/app/src/Backend/Base/Common.js
+++ b/app/src/Backend/Base/Common.js
@@ -1,60 +1,60 @@
-const noop = () => {};
+const noop = () => {}
const log = (...args) => {
- if (["development", "test"].includes(process.env.NODE_ENV)) {
- console.log(...args);
+ if (['development', 'test'].includes(process.env.NODE_ENV)) {
+ console.log(...args)
}
-};
+}
class Common {
// Visible backend name
// Change when subclassing
- name = "Common";
+ name = 'Common'
// Abort controller
// Used to stop pending fetches when user changes the date
- controller = new AbortController();
+ controller = new AbortController()
// Overload to do something useful with errors
- onError = noop;
+ onError = noop
// Can overload onError also on instantiation time
- constructor({ onError } = {}) {
- this.onError = onError || this.onError;
+ constructor ({ onError } = {}) {
+ this.onError = onError || this.onError
}
// Abort the abort controller and clean it up creating a new one for next fetches
// Add here all side-effects cancelling (fetches, timers, etc)
// Remember to call super() when subclassing
abort = () => {
- this.controller.abort();
- this.controller = new AbortController();
- };
+ this.controller.abort()
+ this.controller = new AbortController()
+ }
// Raises exception on response error
handleFetchErrors = (response) => {
// Raise succeeded non-ok responses
if (!response.ok) {
- throw new Error(response.statusText);
+ throw new Error(response.statusText)
}
- return response;
- };
+ return response
+ }
// Catches fetch errors, original or 'self-raised', and throws to `onError` prop
// Filters out non-error "Connection aborted"
- catchFetchErrorsMessage = (err) => `${this.name} backend: ${err.message}`;
- catchFetchErrorsAbortMessage = (err) => "Connection aborted";
+ catchFetchErrorsMessage = (err) => `${this.name} backend: ${err.message}`
+ catchFetchErrorsAbortMessage = (err) => 'Connection aborted'
catchFetchErrors = (err) => {
- if (err.name === "AbortError") {
- console.log(this.catchFetchErrorsAbortMessage());
+ if (err.name === 'AbortError') {
+ console.log(this.catchFetchErrorsAbortMessage())
} else {
- err.message = this.catchFetchErrorsMessage(err);
- this.onError(err);
+ err.message = this.catchFetchErrorsMessage(err)
+ this.onError(err)
}
- };
+ }
- log = log;
+ log = log
}
-export default Common;
-export { log };
+export default Common
+export { log }
diff --git a/app/src/Backend/Base/Common.test.js b/app/src/Backend/Base/Common.test.js
index c3c589e5..2fc016d4 100644
--- a/app/src/Backend/Base/Common.test.js
+++ b/app/src/Backend/Base/Common.test.js
@@ -2,147 +2,148 @@ import {
delay,
MockFetch,
AbortError,
- catchConsoleLog,
-} from "../../testHelpers";
+ catchConsoleLog
+} from '../../testHelpers'
-import Common from "./Common";
+import Common from './Common'
// Common is suposed to be extended
class TestCommon extends Common {
- constructor({ onUpdate, ...rest } = {}) {
- super(rest);
- this.onUpdate = onUpdate || (() => {});
+ constructor ({ onUpdate, ...rest } = {}) {
+ super(rest)
+ this.onUpdate = onUpdate || (() => {})
}
+
subscribe = jest.fn((url) => {
return fetch(url, { signal: this.controller.signal })
.then(this.handleFetchErrors)
.then((response) => response.json())
.then(this.onUpdate)
- .catch(this.catchFetchErrors);
- });
+ .catch(this.catchFetchErrors)
+ })
}
-let mockedFetch;
+let mockedFetch
beforeAll(() => {
- mockedFetch = new MockFetch();
- mockedFetch.mock();
-});
+ mockedFetch = new MockFetch()
+ mockedFetch.mock()
+})
beforeEach(() => {
- mockedFetch.unmock();
- mockedFetch.mock();
-});
+ mockedFetch.unmock()
+ mockedFetch.mock()
+})
afterAll(() => {
- mockedFetch.unmock();
-});
+ mockedFetch.unmock()
+})
-test("Common calls onUpdate", async () => {
- const url = "test1";
+test('Common calls onUpdate', async () => {
+ const url = 'test1'
const options = {
onUpdate: jest.fn(),
- onError: jest.fn(),
- };
- const testCommon = new TestCommon(options);
- await testCommon.subscribe(url);
+ onError: jest.fn()
+ }
+ const testCommon = new TestCommon(options)
+ await testCommon.subscribe(url)
- expect(options.onUpdate).toHaveBeenCalledTimes(1);
-});
+ expect(options.onUpdate).toHaveBeenCalledTimes(1)
+})
-test("Common calls onError when error is thrown", async () => {
- const url = "test1";
+test('Common calls onError when error is thrown', async () => {
+ const url = 'test1'
const options = {
onUpdate: jest.fn(),
- onError: jest.fn(),
- };
- const testCommon = new TestCommon(options);
- const fetchThrowErrorOld = mockedFetch.options.throwError;
- mockedFetch.options.throwError = new Error("Testing network/server errors");
- await testCommon.subscribe(url);
-
- expect(options.onError).toHaveBeenCalledTimes(1);
- mockedFetch.options.throwError = fetchThrowErrorOld;
-});
-
-test("Common does not calls onUpdate nor onError when thrown error is an AbortError", async () => {
- const url = "test1";
+ onError: jest.fn()
+ }
+ const testCommon = new TestCommon(options)
+ const fetchThrowErrorOld = mockedFetch.options.throwError
+ mockedFetch.options.throwError = new Error('Testing network/server errors')
+ await testCommon.subscribe(url)
+
+ expect(options.onError).toHaveBeenCalledTimes(1)
+ mockedFetch.options.throwError = fetchThrowErrorOld
+})
+
+test('Common does not calls onUpdate nor onError when thrown error is an AbortError', async () => {
+ const url = 'test1'
const options = {
onUpdate: jest.fn(),
- onError: jest.fn(),
- };
- const testCommon = new TestCommon(options);
- const fetchThrowErrorOld = mockedFetch.options.throwError;
- mockedFetch.options.throwError = new AbortError("Testing abort errors");
+ onError: jest.fn()
+ }
+ const testCommon = new TestCommon(options)
+ const fetchThrowErrorOld = mockedFetch.options.throwError
+ mockedFetch.options.throwError = new AbortError('Testing abort errors')
const { output } = await catchConsoleLog(async () => {
- await testCommon.subscribe(url);
- });
+ await testCommon.subscribe(url)
+ })
- expect(options.onUpdate).toHaveBeenCalledTimes(0);
- expect(options.onError).toHaveBeenCalledTimes(0);
- expect(output[0].includes("Connection aborted")).toBe(true);
- mockedFetch.options.throwError = fetchThrowErrorOld;
-});
+ expect(options.onUpdate).toHaveBeenCalledTimes(0)
+ expect(options.onError).toHaveBeenCalledTimes(0)
+ expect(output[0].includes('Connection aborted')).toBe(true)
+ mockedFetch.options.throwError = fetchThrowErrorOld
+})
-test("Common calls onError when response is errorish", async () => {
- const url = "test1";
+test('Common calls onError when response is errorish', async () => {
+ const url = 'test1'
const options = {
onUpdate: jest.fn(),
- onError: jest.fn(),
- };
- const testCommon = new TestCommon(options);
+ onError: jest.fn()
+ }
+ const testCommon = new TestCommon(options)
// Test server error
- const fetchResponseOptionsOld = mockedFetch.options.responseOptions;
+ const fetchResponseOptionsOld = mockedFetch.options.responseOptions
mockedFetch.options.responseOptions = () => ({
status: 401,
- statusText: "Testing unauthorized request",
- ok: false,
- });
- await testCommon.subscribe(url);
+ statusText: 'Testing unauthorized request',
+ ok: false
+ })
+ await testCommon.subscribe(url)
- expect(options.onError).toHaveBeenCalledTimes(1);
- mockedFetch.options.responseOptions = fetchResponseOptionsOld;
-});
+ expect(options.onError).toHaveBeenCalledTimes(1)
+ mockedFetch.options.responseOptions = fetchResponseOptionsOld
+})
-test("Common uses noop default values for onUpdate and onError", async () => {
- const url = "test1";
- const testCommon = new TestCommon();
+test('Common uses noop default values for onUpdate and onError', async () => {
+ const url = 'test1'
+ const testCommon = new TestCommon()
// Test for onUpdate
- const onUpdateOriginal = testCommon.onUpdate;
- testCommon.onUpdate = jest.fn(onUpdateOriginal);
- await testCommon.subscribe(url);
+ const onUpdateOriginal = testCommon.onUpdate
+ testCommon.onUpdate = jest.fn(onUpdateOriginal)
+ await testCommon.subscribe(url)
- expect(testCommon.onUpdate).toHaveBeenCalledTimes(1);
+ expect(testCommon.onUpdate).toHaveBeenCalledTimes(1)
// Test for onError
- const onErrorOriginal = testCommon.onError;
- testCommon.onError = jest.fn(onErrorOriginal);
- const fetchThrowErrorOld = mockedFetch.options.throwError;
- mockedFetch.options.throwError = new Error("Testing network/server errors");
- await testCommon.subscribe(url);
+ const onErrorOriginal = testCommon.onError
+ testCommon.onError = jest.fn(onErrorOriginal)
+ const fetchThrowErrorOld = mockedFetch.options.throwError
+ mockedFetch.options.throwError = new Error('Testing network/server errors')
+ await testCommon.subscribe(url)
- expect(testCommon.onError).toHaveBeenCalledTimes(1);
- mockedFetch.options.throwError = fetchThrowErrorOld;
-});
+ expect(testCommon.onError).toHaveBeenCalledTimes(1)
+ mockedFetch.options.throwError = fetchThrowErrorOld
+})
-test("Common aborts a connection correctly", async () => {
- const url = "test1";
- const testCommon = new TestCommon();
+test('Common aborts a connection correctly', async () => {
+ const url = 'test1'
+ const testCommon = new TestCommon()
// Mock controller's abort method to count its calls
- const abortOriginal = testCommon.controller.abort;
- const abortMocked = jest.fn(abortOriginal);
- testCommon.controller.abort = abortMocked;
- const promise = testCommon.subscribe(url);
- await delay(1);
+ const abortOriginal = testCommon.controller.abort
+ const abortMocked = jest.fn(abortOriginal)
+ testCommon.controller.abort = abortMocked
+ const promise = testCommon.subscribe(url)
+ await delay(1)
- testCommon.abort();
+ testCommon.abort()
// Ensure promise is not left in background
- await promise;
+ await promise
// Controller's abort has been called once
- expect(abortMocked).toHaveBeenCalledTimes(1);
+ expect(abortMocked).toHaveBeenCalledTimes(1)
// Controller has been substituted, so abort function
// must not be the same as before
- expect(abortMocked).not.toBe(testCommon.controller.abort);
-});
+ expect(abortMocked).not.toBe(testCommon.controller.abort)
+})
diff --git a/app/src/Backend/Base/ContextCreator.jsx b/app/src/Backend/Base/ContextCreator.jsx
index 7ae9bc6a..151de77e 100644
--- a/app/src/Backend/Base/ContextCreator.jsx
+++ b/app/src/Backend/Base/ContextCreator.jsx
@@ -21,14 +21,11 @@ const ContextCreator = (Handler, defaultName) => {
const withHandler =
(WrappedComponent, name = defaultName) =>
- (props) =>
- (
-
- {(context) => (
-
- )}
-
- )
+ (props) => (
+
+ {(context) => }
+
+ )
return {
Provider,
diff --git a/app/src/Backend/Base/GHPages.js b/app/src/Backend/Base/GHPages.js
index 5acc442b..c4772f83 100644
--- a/app/src/Backend/Base/GHPages.js
+++ b/app/src/Backend/Base/GHPages.js
@@ -1,20 +1,20 @@
-import Common from "./Common";
-import cache from "./Cache";
+import Common from './Common'
+import cache from './Cache'
// Handle data backend and cache for backends at GH Pages
// This is not a singleton: unique error handlers
class GHPages extends Common {
// Visible backend name
- name = "GitHub Pages";
+ name = 'GitHub Pages'
// Used to update the data at official schedule
// Official schedule's at 10am. It often is some minutes later.
// GH Pages cache backend Workflow schedule is at 10:30 and lasts few minutes (less than 5).
// We schedule at 10:35
- timerDataUpdate = false;
- officialUpdateTime = "10:35".split(":");
+ timerDataUpdate = false
+ officialUpdateTime = '10:35'.split(':')
- indexUrl = ""; // Needs to be declared in extended classes
+ indexUrl = '' // Needs to be declared in extended classes
/*
Handle data update and cache invalidation
@@ -26,69 +26,69 @@ class GHPages extends Common {
return cache.checkIfNeedUpdate(
this.indexUrl,
async (updateNeeded) => {
- this.log(`${this.name}: update needed: ${updateNeeded}`);
- await callback(updateNeeded);
+ this.log(`${this.name}: update needed: ${updateNeeded}`)
+ await callback(updateNeeded)
},
(error) => {
- console.error(`${this.name}: updating data from backend:`, error);
- onError(error);
+ console.error(`${this.name}: updating data from backend:`, error)
+ onError(error)
}
- );
- };
+ )
+ }
// Invalidate all URLs, except index
invalidateAll = async () => {
console.warn(
`${this.name}: Need to define abstract function 'invalidateAll'`
- );
- //await cache.invalidate( this.indexUrl );
- };
+ )
+ // await cache.invalidate( this.indexUrl );
+ }
// Updates all sources, sequentially
updateAll = (callback) => {
return (async (callback) => {
// Invalidate each active JSON first
- await this.invalidateAll();
+ await this.invalidateAll()
// Finally, invalidate the `index` JSON
- this.log(`${this.name}: Invalidate index`);
+ this.log(`${this.name}: Invalidate index`)
- await cache.invalidate(this.indexUrl);
+ await cache.invalidate(this.indexUrl)
- callback(true);
- })(callback);
- };
+ callback(true)
+ })(callback)
+ }
// Updates all sources if needed
// Always calls callback with a boolean argument indicating if an update was made
updateIfNeeded = (callback) => {
return this.checkUpdate(async (updateNeeded) => {
if (updateNeeded) {
- await this.updateAll(() => callback(true));
+ await this.updateAll(() => callback(true))
} else {
- callback(false);
+ callback(false)
}
- });
- };
+ })
+ }
// Cancels an ongoing timer for update
cancelUpdateSchedule = () => {
if (this.timerDataUpdate) {
- clearTimeout(this.timerDataUpdate);
- this.timerDataUpdate = false;
+ clearTimeout(this.timerDataUpdate)
+ this.timerDataUpdate = false
}
- };
+ }
// Try to update data on next official scheduled data update
scheduleNextUpdate = ({ millis = false, ...options } = {}) => {
// If millis is not defined, call on next calculated default
- const nextMillis = millis === false ? this.millisToNextUpdate() : millis;
+ const nextMillis = millis === false ? this.millisToNextUpdate() : millis
- const nextUpdateDate = new Date(new Date().getTime() + nextMillis);
- this.log(`${this.name}: Next update on ${nextUpdateDate}`);
+ const nextUpdateDate = new Date(new Date().getTime() + nextMillis)
+ this.log(`${this.name}: Next update on ${nextUpdateDate}`)
// Just in case
- this.cancelUpdateSchedule();
+ this.cancelUpdateSchedule()
// If data has been updated and `recursive` is true, re-schedule data update for the next day
// Else (recursive || not recursive but data NOT updated), schedule data update in 5 minutes
@@ -98,35 +98,35 @@ class GHPages extends Common {
recursive = false,
onBeforeUpdate = () => {},
onAfterUpdate = () => {},
- notUpdatedMillis = 300_000,
- } = options;
+ notUpdatedMillis = 300_000
+ } = options
- onBeforeUpdate();
+ onBeforeUpdate()
this.updateIfNeeded((updated) => {
- onAfterUpdate(updated);
+ onAfterUpdate(updated)
if (recursive || !updated) {
this.scheduleNextUpdate({
...options,
- ...(updated ? {} : { millis: notUpdatedMillis }),
- });
+ ...(updated ? {} : { millis: notUpdatedMillis })
+ })
}
- });
- }, nextMillis);
+ })
+ }, nextMillis)
- return nextUpdateDate;
- };
+ return nextUpdateDate
+ }
abort = () => {
- this.cancelUpdateSchedule();
- super.abort();
- };
+ this.cancelUpdateSchedule()
+ super.abort()
+ }
// Calculates haw many milliseconds until next schedulled update (today's or tomorrow)
// TODO: Take care of timezones: Official date is in CEST/GMT+0200 (with daylight saving modifications), Date uses user's timezone and returns UTC
// Now, it only works if user timezone is CEST
// Probably, the best would be to translate both dates into UTC and, only then, compare them
millisToNextUpdate = () => {
- const now = new Date();
+ const now = new Date()
const todayDataSchedule = new Date(
now.getFullYear(),
now.getMonth(),
@@ -134,13 +134,13 @@ class GHPages extends Common {
...this.officialUpdateTime,
0,
0
- );
- const millisTillSchedulle = todayDataSchedule - now;
+ )
+ const millisTillSchedulle = todayDataSchedule - now
return millisTillSchedulle <= 0
? millisTillSchedulle + 86_400_000 // it's on or after today's schedule, try next schedule tomorrow.
- : millisTillSchedulle;
- };
+ : millisTillSchedulle
+ }
}
-export default GHPages;
+export default GHPages
diff --git a/app/src/Backend/Base/GHPages.test.js b/app/src/Backend/Base/GHPages.test.js
index 0f6d557b..f1f907fd 100644
--- a/app/src/Backend/Base/GHPages.test.js
+++ b/app/src/Backend/Base/GHPages.test.js
@@ -2,10 +2,10 @@ import {
delay,
catchConsoleLog,
catchConsoleWarn,
- catchConsoleError,
-} from "../../testHelpers";
+ catchConsoleError
+} from '../../testHelpers'
-import GHPages from "./GHPages";
+import GHPages from './GHPages'
/*
TODO:
@@ -14,186 +14,186 @@ import GHPages from "./GHPages";
- Test `millisToNextUpdate` with an extra day timelapse (`officialUpdateTime` on today, but earlier than now)
- Test `abort`
*/
-const mockDelay = delay;
+const mockDelay = delay
class MockCache {
- success = true;
- successValue = true;
- errorValue = new Error("Test cache checkIfNeedUpdate error");
+ success = true
+ successValue = true
+ errorValue = new Error('Test cache checkIfNeedUpdate error')
run = async (url, onSuccess, onError) => {
- await mockDelay(10);
+ await mockDelay(10)
if (this.success) {
- await onSuccess(this.successValue);
+ await onSuccess(this.successValue)
} else {
- await onError(this.errorValue);
+ await onError(this.errorValue)
}
- };
+ }
}
-const mockCache = new MockCache();
-jest.mock("./Cache", () => {
+const mockCache = new MockCache()
+jest.mock('./Cache', () => {
return {
__esModule: true,
default: {
invalidate: jest.fn(async (url) => {
- await mockDelay(10);
+ await mockDelay(10)
}),
- checkIfNeedUpdate: (...args) => mockCache.run(...args),
- },
- };
-});
+ checkIfNeedUpdate: (...args) => mockCache.run(...args)
+ }
+ }
+})
class TestGHPages extends GHPages {
- indexUrl = "testIndex";
+ indexUrl = 'testIndex'
// Invalidate all URLs, except index
invalidateAll = jest.fn(async () => {
- await delay(10);
- });
+ await delay(10)
+ })
}
-test("GHPages correctly checks for updates", async () => {
- const testGHPages = new TestGHPages();
- const onSuccess = jest.fn(() => console.log("SUCCESS"));
- const onError = jest.fn((err) => console.error("ERROR:", err));
+test('GHPages correctly checks for updates', async () => {
+ const testGHPages = new TestGHPages()
+ const onSuccess = jest.fn(() => console.log('SUCCESS'))
+ const onError = jest.fn((err) => console.error('ERROR:', err))
// Test success with update needed
const { output: outputSuccess } = await catchConsoleLog(
async () => await testGHPages.checkUpdate(onSuccess, onError)
- );
- expect(onSuccess).toHaveBeenCalledTimes(1);
- expect(onError).toHaveBeenCalledTimes(0);
- expect(onSuccess).toHaveBeenCalledWith(mockCache.successValue);
- expect(outputSuccess[0].includes("update needed")).toBe(true);
+ )
+ expect(onSuccess).toHaveBeenCalledTimes(1)
+ expect(onError).toHaveBeenCalledTimes(0)
+ expect(onSuccess).toHaveBeenCalledWith(mockCache.successValue)
+ expect(outputSuccess[0].includes('update needed')).toBe(true)
// Test error
- mockCache.success = false;
+ mockCache.success = false
const { output: outputError } = await catchConsoleError(
async () => await testGHPages.checkUpdate(onSuccess, onError)
- );
- expect(onSuccess).toHaveBeenCalledTimes(1);
- expect(onError).toHaveBeenCalledTimes(1);
- expect(outputError[1].includes("Test cache checkIfNeedUpdate error")).toBe(
+ )
+ expect(onSuccess).toHaveBeenCalledTimes(1)
+ expect(onError).toHaveBeenCalledTimes(1)
+ expect(outputError[1].includes('Test cache checkIfNeedUpdate error')).toBe(
true
- );
- mockCache.success = true;
-});
+ )
+ mockCache.success = true
+})
-test("GHPages correctly warns about the need to overload `invalidateAll`", async () => {
- const testGHPages = new GHPages();
+test('GHPages correctly warns about the need to overload `invalidateAll`', async () => {
+ const testGHPages = new GHPages()
const { output } = await catchConsoleWarn(
async () => await testGHPages.invalidateAll()
- );
- expect(output[0].includes("abstract function")).toBe(true);
-});
+ )
+ expect(output[0].includes('abstract function')).toBe(true)
+})
-test("GHPages correctly updates all own URLs", async () => {
- const testGHPages = new TestGHPages();
- const callback = jest.fn();
+test('GHPages correctly updates all own URLs', async () => {
+ const testGHPages = new TestGHPages()
+ const callback = jest.fn()
const { output } = await catchConsoleLog(
async () => await testGHPages.updateAll(callback)
- );
- expect(output[0].includes("Invalidate index")).toBe(true);
- expect(testGHPages.invalidateAll).toHaveBeenCalledTimes(1);
- expect(callback).toHaveBeenCalledTimes(1);
-});
+ )
+ expect(output[0].includes('Invalidate index')).toBe(true)
+ expect(testGHPages.invalidateAll).toHaveBeenCalledTimes(1)
+ expect(callback).toHaveBeenCalledTimes(1)
+})
-test("GHPages correctly updates all own URLs, if needed", async () => {
- const testGHPages = new TestGHPages();
- const callback = jest.fn();
+test('GHPages correctly updates all own URLs, if needed', async () => {
+ const testGHPages = new TestGHPages()
+ const callback = jest.fn()
// Test no need to update
- mockCache.successValue = false;
+ mockCache.successValue = false
const { output } = await catchConsoleLog(
async () => await testGHPages.updateIfNeeded(callback)
- );
- expect(output[0].includes("update needed: false")).toBe(true);
- expect(testGHPages.invalidateAll).toHaveBeenCalledTimes(0);
- expect(callback).toHaveBeenCalledTimes(1);
- expect(callback).toHaveBeenCalledWith(false);
- mockCache.successValue = true;
+ )
+ expect(output[0].includes('update needed: false')).toBe(true)
+ expect(testGHPages.invalidateAll).toHaveBeenCalledTimes(0)
+ expect(callback).toHaveBeenCalledTimes(1)
+ expect(callback).toHaveBeenCalledWith(false)
+ mockCache.successValue = true
// Test need to update
const { output: output2 } = await catchConsoleLog(async () => {
- await testGHPages.updateIfNeeded(callback);
- });
- expect(output2[0].includes("update needed: true")).toBe(true);
- expect(testGHPages.invalidateAll).toHaveBeenCalledTimes(1);
- expect(callback).toHaveBeenCalledTimes(2);
- expect(callback).toHaveBeenCalledWith(true);
-});
+ await testGHPages.updateIfNeeded(callback)
+ })
+ expect(output2[0].includes('update needed: true')).toBe(true)
+ expect(testGHPages.invalidateAll).toHaveBeenCalledTimes(1)
+ expect(callback).toHaveBeenCalledTimes(2)
+ expect(callback).toHaveBeenCalledWith(true)
+})
-test("GHPages correctly schedules next update loop", async () => {
- const testGHPages = new TestGHPages();
+test('GHPages correctly schedules next update loop', async () => {
+ const testGHPages = new TestGHPages()
// Generate a date in the near future
- const future = new Date();
- future.setMinutes(future.getMinutes() + 2);
+ const future = new Date()
+ future.setMinutes(future.getMinutes() + 2)
testGHPages.officialUpdateTime = [
future.getHours(),
future.getMinutes(),
- future.getSeconds(),
- ];
+ future.getSeconds()
+ ]
// Generate a string with the generated date, prepending '0' to each value if it's smaller than 10
- const pad2 = (num) => `${num < 10 ? "0" : ""}${num}`;
- const expectedTimeString = testGHPages.officialUpdateTime.map(pad2).join(":");
+ const pad2 = (num) => `${num < 10 ? '0' : ''}${num}`
+ const expectedTimeString = testGHPages.officialUpdateTime.map(pad2).join(':')
// First try: update on mocked schedule
const { output } = await catchConsoleLog(async () => {
- testGHPages.scheduleNextUpdate();
- });
- expect(output[0].includes("Next update on")).toBe(true);
- expect(output[0].includes(expectedTimeString)).toBe(true);
- expect(testGHPages.invalidateAll).toHaveBeenCalledTimes(0);
+ testGHPages.scheduleNextUpdate()
+ })
+ expect(output[0].includes('Next update on')).toBe(true)
+ expect(output[0].includes(expectedTimeString)).toBe(true)
+ expect(testGHPages.invalidateAll).toHaveBeenCalledTimes(0)
- testGHPages.cancelUpdateSchedule();
+ testGHPages.cancelUpdateSchedule()
// Second try: update on `nextMillis` milliseconds
- const nextMillis = 20;
+ const nextMillis = 20
const options = {
millis: nextMillis,
notUpdatedMillis: 10,
onBeforeUpdate: jest.fn(),
- onAfterUpdate: jest.fn(),
- };
+ onAfterUpdate: jest.fn()
+ }
const { output: output2 } = await catchConsoleLog(async () => {
- testGHPages.scheduleNextUpdate(options);
- });
- expect(output2[0].includes("Next update on")).toBe(true);
+ testGHPages.scheduleNextUpdate(options)
+ })
+ expect(output2[0].includes('Next update on')).toBe(true)
// Should not have been updated yet (before wait)
- expect(testGHPages.invalidateAll).toHaveBeenCalledTimes(0);
- expect(options.onBeforeUpdate).toHaveBeenCalledTimes(0);
- expect(options.onAfterUpdate).toHaveBeenCalledTimes(0);
+ expect(testGHPages.invalidateAll).toHaveBeenCalledTimes(0)
+ expect(options.onBeforeUpdate).toHaveBeenCalledTimes(0)
+ expect(options.onAfterUpdate).toHaveBeenCalledTimes(0)
// Silence output
- const checkUpdateOld = testGHPages.checkUpdate.bind(testGHPages);
+ const checkUpdateOld = testGHPages.checkUpdate.bind(testGHPages)
testGHPages.checkUpdate = jest.fn(async (...args) => {
await catchConsoleLog(async () => {
- await checkUpdateOld(...args);
- });
- });
+ await checkUpdateOld(...args)
+ })
+ })
// Force to recurse to next try (no update needed yet)
- mockCache.successValue = false;
- await delay(nextMillis);
- expect(options.onBeforeUpdate).toHaveBeenCalledTimes(1);
- expect(options.onAfterUpdate).toHaveBeenCalledTimes(0);
+ mockCache.successValue = false
+ await delay(nextMillis)
+ expect(options.onBeforeUpdate).toHaveBeenCalledTimes(1)
+ expect(options.onAfterUpdate).toHaveBeenCalledTimes(0)
- await delay(10);
- expect(options.onBeforeUpdate).toHaveBeenCalledTimes(1);
- expect(options.onAfterUpdate).toHaveBeenCalledTimes(1);
+ await delay(10)
+ expect(options.onBeforeUpdate).toHaveBeenCalledTimes(1)
+ expect(options.onAfterUpdate).toHaveBeenCalledTimes(1)
// Set to need an update
- mockCache.successValue = true;
- await delay(10);
- expect(options.onBeforeUpdate).toHaveBeenCalledTimes(2);
- expect(options.onAfterUpdate).toHaveBeenCalledTimes(1);
+ mockCache.successValue = true
+ await delay(10)
+ expect(options.onBeforeUpdate).toHaveBeenCalledTimes(2)
+ expect(options.onAfterUpdate).toHaveBeenCalledTimes(1)
- await delay(nextMillis + 15);
- expect(options.onBeforeUpdate).toHaveBeenCalledTimes(2);
- expect(options.onAfterUpdate).toHaveBeenCalledTimes(2);
+ await delay(nextMillis + 15)
+ expect(options.onBeforeUpdate).toHaveBeenCalledTimes(2)
+ expect(options.onAfterUpdate).toHaveBeenCalledTimes(2)
// Should already have been updated (after wait)
- expect(testGHPages.invalidateAll).toHaveBeenCalledTimes(1);
-});
+ expect(testGHPages.invalidateAll).toHaveBeenCalledTimes(1)
+})
diff --git a/app/src/Backend/Base/withHandlerGenerator.jsx b/app/src/Backend/Base/withHandlerGenerator.jsx
index 69de8013..76d1ee3a 100644
--- a/app/src/Backend/Base/withHandlerGenerator.jsx
+++ b/app/src/Backend/Base/withHandlerGenerator.jsx
@@ -34,11 +34,13 @@ const withHandlerGenerator = (
[BackendHandler, params]
)
- return value === false ? (
-
- ) : (
-
- )
+ return value === false
+ ? (
+
+ )
+ : (
+
+ )
}
// Return wrapper component wrapped with a
diff --git a/app/src/Backend/Base/withHandlerGenerator.test.jsx b/app/src/Backend/Base/withHandlerGenerator.test.jsx
index 62c01aef..f8065a6f 100644
--- a/app/src/Backend/Base/withHandlerGenerator.test.jsx
+++ b/app/src/Backend/Base/withHandlerGenerator.test.jsx
@@ -1,130 +1,129 @@
-import React from "react";
+import React from 'react'
import {
render,
createEvent,
fireEvent,
act,
screen,
- within,
-} from "@testing-library/react";
+ within
+} from '@testing-library/react'
-import { delay } from "../../testHelpers";
-import withHandlerGenerator from "./withHandlerGenerator";
+import { delay } from '../../testHelpers'
+import withHandlerGenerator from './withHandlerGenerator'
// Mock the
component
-jest.mock("../../Loading", () => {
+jest.mock('../../Loading', () => {
return {
__esModule: true,
- default: (props) =>
Loading
,
- };
-});
+ default: (props) =>
Loading
+ }
+})
// Mocked backend handler, like in src/Backend/*/index.js
class BackendHandler {
- constructor(testParam) {
- this.testParam = testParam;
+ constructor (testParam) {
+ this.testParam = testParam
}
index = jest.fn((callback) => {
- const { testParam } = this;
- const timer = setTimeout(callback({ role: "tested", testParam }), 20);
- return () => clearTimeout(timer);
- });
+ const { testParam } = this
+ const timer = setTimeout(callback({ role: 'tested', testParam }), 20)
+ return () => clearTimeout(timer)
+ })
}
// Mocked backend handler HOC
-const withBackendHandlerHOC = (Wrapped) => (props) =>
- (
-
-
-
- );
-
-test("withHandlerGenerator correctly generates a HOC to create a Wrapped component with data as a prop", async () => {
+const withBackendHandlerHOC = (Wrapped) => (props) => (
+
+
+
+)
+
+test('withHandlerGenerator correctly generates a HOC to create a Wrapped component with data as a prop', async () => {
// Generate a testing HOC
- const withIndex = (WrappedComponent, name = "index") =>
+ const withIndex = (WrappedComponent, name = 'index') =>
withHandlerGenerator(
withBackendHandlerHOC,
({ testParam }) => ({ testParam }),
({ testParam }, Handler, setIndex) => {
- const handler = new Handler(testParam);
- return handler.index(setIndex);
+ const handler = new Handler(testParam)
+ return handler.index(setIndex)
},
name,
WrappedComponent
- );
+ )
// Use the generated testing HOC to pass `index` as a prop
const TestComponent = withIndex(
({ index: index_, role, testParam, ...props }) => {
- const { testParam: testParamIndex, ...index } = index_;
+ const { testParam: testParamIndex, ...index } = index_
return (
-
-
-
+
+
+
- );
+ )
}
- );
+ )
- let rendered;
+ let rendered
await act(async () => {
- const paramValue = "test-value";
+ const paramValue = 'test-value'
rendered = render(
-
- );
+
+ )
// Initial
state
- const wrapperInitial = rendered.getByRole("wrapper");
- expect(wrapperInitial).toBeInTheDocument();
+ const wrapperInitial = rendered.getByRole('wrapper')
+ expect(wrapperInitial).toBeInTheDocument()
- const loading = within(wrapperInitial).getByRole("loading");
- expect(loading).toBeInTheDocument();
+ const loading = within(wrapperInitial).getByRole('loading')
+ expect(loading).toBeInTheDocument()
- await delay(20);
+ await delay(20)
// Final state, with `index` as a prop in the wrapped component
- const wrapper = rendered.getByRole("wrapper");
- expect(wrapper).toBeInTheDocument();
+ const wrapper = rendered.getByRole('wrapper')
+ expect(wrapper).toBeInTheDocument()
- const wrapped = within(wrapper).getByRole("test-component");
- expect(wrapped).toBeInTheDocument();
+ const wrapped = within(wrapper).getByRole('test-component')
+ expect(wrapped).toBeInTheDocument()
- const index = within(wrapped).getByRole("tested");
- expect(index).toBeInTheDocument();
- expect(index.getAttribute("data-testid")).toBe("index");
+ const index = within(wrapped).getByRole('tested')
+ expect(index).toBeInTheDocument()
+ expect(index.getAttribute('data-testid')).toBe('index')
- const testParam = within(wrapped).getByTestId("testParam");
- expect(testParam).toBeInTheDocument();
+ const testParam = within(wrapped).getByTestId('testParam')
+ expect(testParam).toBeInTheDocument()
- const testParamIndex = within(wrapped).getByTestId("testParamIndex");
- expect(testParamIndex).toBeInTheDocument();
+ const testParamIndex = within(wrapped).getByTestId('testParamIndex')
+ expect(testParamIndex).toBeInTheDocument()
- expect(testParamIndex.getAttribute("value")).toBe(paramValue);
- expect(testParamIndex.getAttribute("value")).toBe(
- testParam.getAttribute("value")
- );
- });
+ expect(testParamIndex.getAttribute('value')).toBe(paramValue)
+ expect(testParamIndex.getAttribute('value')).toBe(
+ testParam.getAttribute('value')
+ )
+ })
await act(async () => {
- const paramValue = "test-value-2";
+ const paramValue = 'test-value-2'
rendered.rerender(
-
- );
+
+ )
- await delay(0);
+ await delay(0)
- const testParam = rendered.getByTestId("testParam");
- expect(testParam).toBeInTheDocument();
+ const testParam = rendered.getByTestId('testParam')
+ expect(testParam).toBeInTheDocument()
- const testParamIndex = rendered.getByTestId("testParamIndex");
- expect(testParamIndex).toBeInTheDocument();
+ const testParamIndex = rendered.getByTestId('testParamIndex')
+ expect(testParamIndex).toBeInTheDocument()
- expect(testParamIndex.getAttribute("value")).toBe(paramValue);
- expect(testParamIndex.getAttribute("value")).toBe(
- testParam.getAttribute("value")
- );
- });
-});
+ expect(testParamIndex.getAttribute('value')).toBe(paramValue)
+ expect(testParamIndex.getAttribute('value')).toBe(
+ testParam.getAttribute('value')
+ )
+ })
+})
diff --git a/app/src/Backend/Bcn/handler.js b/app/src/Backend/Bcn/handler.js
index 9cd5f2e5..a816668e 100644
--- a/app/src/Backend/Bcn/handler.js
+++ b/app/src/Backend/Bcn/handler.js
@@ -1,67 +1,67 @@
-import GHPages from "../Base/GHPages";
-import cache from "../Base/Cache";
+import GHPages from '../Base/GHPages'
+import cache from '../Base/Cache'
const BcnStaticHost =
process.env.REACT_APP_BCN_STATIC_HOST ??
- "https://emibcn.github.io/covid-data/Bcn";
-const BcnDataStaticURL = `${BcnStaticHost}/index.json`;
+ 'https://emibcn.github.io/covid-data/Bcn'
+const BcnDataStaticURL = `${BcnStaticHost}/index.json`
// Handle data backend and cache for BCN
class BcnDataHandler extends GHPages {
// Visible backend name
- name = "Barcelona JSON Files";
- indexUrl = BcnDataStaticURL;
- unsubscribeIndex = () => {};
+ name = 'Barcelona JSON Files'
+ indexUrl = BcnDataStaticURL
+ unsubscribeIndex = () => {}
// If index data is not passed, subscribe to index
// URL and parse its content once downloaded
- constructor(index) {
- super();
+ constructor (index) {
+ super()
if (index) {
- this.parseIndex(index);
+ this.parseIndex(index)
} else {
- this.unsubscribeIndex = this.indexInternal(this.parseIndex);
+ this.unsubscribeIndex = this.indexInternal(this.parseIndex)
}
}
/*
Return dynamic data (with cache)
*/
- indexData = [];
- indexInternal = (callback) => cache.fetch(BcnDataStaticURL, callback);
+ indexData = []
+ indexInternal = (callback) => cache.fetch(BcnDataStaticURL, callback)
// Return unsubscription callback
// Return unsubscription from possible automatic download
index = (callback) => {
- const unsubscribe = this.indexInternal(callback);
+ const unsubscribe = this.indexInternal(callback)
return () => {
- unsubscribe();
- this.unsubscribeIndex();
- };
- };
+ unsubscribe()
+ this.unsubscribeIndex()
+ }
+ }
parseIndex = (index) => {
// Save/cache index data
- this.indexData = index;
+ this.indexData = index
// Update active downloads
- this.invalidateAll();
- };
+ this.invalidateAll()
+ }
// Active URLs: those which will be invalidated on update
- active = [];
+ active = []
// Invalidate all URLs, except index
invalidateAll = async () => {
for (const url of this.active) {
- if (process.env.NODE_ENV === "development") {
- console.log(`${this.name}: Invalidate '?${url}'`);
+ if (process.env.NODE_ENV === 'development') {
+ console.log(`${this.name}: Invalidate '?${url}'`)
}
- await cache.invalidate(`${BcnStaticHost}/${url}`);
+ await cache.invalidate(`${BcnStaticHost}/${url}`)
}
- };
+ }
// Gets the breadcrumb of ancestors and the found node, or empty array (recursive)
findBreadcrumb = (
@@ -70,63 +70,63 @@ class BcnDataHandler extends GHPages {
compare = ({ code }, value) => code === value
) => {
if (node === null) {
- node = { sections: this.indexData };
+ node = { sections: this.indexData }
}
if (compare(node, value)) {
- return [node];
- } else if ("sections" in node) {
+ return [node]
+ } else if ('sections' in node) {
for (const child of node.sections) {
- const found = this.findBreadcrumb(child, value, compare);
+ const found = this.findBreadcrumb(child, value, compare)
if (found.length) {
- return [...found, node];
+ return [...found, node]
}
}
}
- return [];
- };
+ return []
+ }
// Find a node in a tree
findChild = (node, value, compare) => {
- const list = this.findBreadcrumb(node, value, compare);
+ const list = this.findBreadcrumb(node, value, compare)
if (list.length) {
- return list[0];
+ return list[0]
}
- };
+ }
// Find division/population section
findInitialNode = (dataset) => {
- return this.indexData.find(({ code }) => code === dataset);
- };
+ return this.indexData.find(({ code }) => code === dataset)
+ }
// Return filtered index data values
- filter = (fn) => this.indexData.filter(fn);
+ filter = (fn) => this.indexData.filter(fn)
// Active URLs: those which will be invalidated on update
- active = [];
+ active = []
// Fetch JSON data and subscribe to updates
data = (dataset, callback) => {
// Find region name in link and children, recursively
- const found = this.findChild(null, dataset);
+ const found = this.findChild(null, dataset)
// TODO: We should do something else on error
if (!found) {
console.warn(`Could not find data in index: ${dataset}`, {
- index: this.indexData,
- });
- callback(false);
- return () => {};
+ index: this.indexData
+ })
+ callback(false)
+ return () => {}
}
// Add URL to active ones. Will be invalidated on update
if (!this.active.includes(found.values)) {
- this.active.push(found.values);
+ this.active.push(found.values)
}
// Get URL content (download or cached)
// Return unsubscription callback
- return cache.fetch(`${BcnStaticHost}/${found.values}`, callback);
- };
+ return cache.fetch(`${BcnStaticHost}/${found.values}`, callback)
+ }
}
-export default BcnDataHandler;
+export default BcnDataHandler
diff --git a/app/src/Backend/Charts/handler.js b/app/src/Backend/Charts/handler.js
index 789e67d3..7fe0abc3 100644
--- a/app/src/Backend/Charts/handler.js
+++ b/app/src/Backend/Charts/handler.js
@@ -1,129 +1,129 @@
-import GHPages from "../Base/GHPages";
-import cache from "../Base/Cache";
+import GHPages from '../Base/GHPages'
+import cache from '../Base/Cache'
const ChartStaticHost =
process.env.REACT_APP_CHART_STATIC_HOST ??
- "https://emibcn.github.io/covid-data/Charts";
-const ChartDataStaticURL = `${ChartStaticHost}/index.json`;
-const ChartDataBase = `${ChartStaticHost}/chart.json`;
+ 'https://emibcn.github.io/covid-data/Charts'
+const ChartDataStaticURL = `${ChartStaticHost}/index.json`
+const ChartDataBase = `${ChartStaticHost}/chart.json`
// Handle data backend and cache for Charts
// This is not a singleton: unique error handlers
class ChartDataHandler extends GHPages {
// Visible backend name
- name = "Charts JSON Files";
- indexUrl = ChartDataStaticURL;
- unsubscribeIndex = () => {};
+ name = 'Charts JSON Files'
+ indexUrl = ChartDataStaticURL
+ unsubscribeIndex = () => {}
/*
Processed data from index
*/
- divisions = [];
- populations = [];
+ divisions = []
+ populations = []
// If index data is not passed, subscribe to index
// URL and parse its content once downloaded
- constructor(index) {
- super();
+ constructor (index) {
+ super()
if (index) {
- this.parseIndex(index);
+ this.parseIndex(index)
} else {
- this.unsubscribeIndex = this.indexInternal(this.parseIndex);
+ this.unsubscribeIndex = this.indexInternal(this.parseIndex)
}
}
/*
Return dynamic data (with cache)
*/
- indexData = [];
- indexInternal = (callback) => cache.fetch(ChartDataStaticURL, callback);
+ indexData = []
+ indexInternal = (callback) => cache.fetch(ChartDataStaticURL, callback)
// Return unsubscription callback
// If automatic index download was done (in constructor),
// unsubscribe also from it
index = (callback) => {
- const unsubscribe = this.indexInternal(callback);
+ const unsubscribe = this.indexInternal(callback)
return () => {
- unsubscribe();
- this.unsubscribeIndex();
- };
- };
+ unsubscribe()
+ this.unsubscribeIndex()
+ }
+ }
parseIndex = (index) => {
// Get a unique (`[...new Set( )]`) list of options elements
- this.divisions = [...new Set(index.map(({ territori }) => territori))];
- this.populations = [...new Set(index.map(({ poblacio }) => poblacio))];
+ this.divisions = [...new Set(index.map(({ territori }) => territori))]
+ this.populations = [...new Set(index.map(({ poblacio }) => poblacio))]
// Save/cache index data
- this.indexData = index;
+ this.indexData = index
// Update active downloads
- this.invalidateAll();
- };
+ this.invalidateAll()
+ }
// Active URLs: those which will be invalidated on update
- active = [];
+ active = []
- getURL = (url) => `${ChartDataBase}${encodeURIComponent(`?${url}`)}`;
+ getURL = (url) => `${ChartDataBase}${encodeURIComponent(`?${url}`)}`
// Invalidate all URLs, except index
invalidateAll = async () => {
for (const url of this.active) {
- this.log(`${this.name}: Invalidate '?${url}'`);
- await cache.invalidate(this.getURL(url));
+ this.log(`${this.name}: Invalidate '?${url}'`)
+ await cache.invalidate(this.getURL(url))
}
- };
+ }
// Gets the breadcrumb of ancestors and the found node, or empty array (recursive)
findBreadcrumb = (node, value, compare = (node, url) => node.url === url) => {
if (compare(node, value)) {
- return [node];
- } else if ("children" in node) {
+ return [node]
+ } else if ('children' in node) {
for (const child of node.children) {
- const found = this.findBreadcrumb(child, value, compare);
+ const found = this.findBreadcrumb(child, value, compare)
if (found.length) {
- return [...found, node];
+ return [...found, node]
}
}
}
- return [];
- };
+ return []
+ }
// Find a node in a tree
findChild = (node, value, compare) => {
- const list = this.findBreadcrumb(node, value, compare);
+ const list = this.findBreadcrumb(node, value, compare)
if (list.length) {
- return list[0];
+ return list[0]
}
- };
+ }
// Find division/population section
findInitialNode = (division, population) => {
return this.indexData.find(
(link) => link.territori === division && link.poblacio === population
- );
- };
+ )
+ }
find = (division, population, url) => {
- const initialLink = this.findInitialNode(division, population);
- return this.findChild(initialLink, url);
- };
+ const initialLink = this.findInitialNode(division, population)
+ return this.findChild(initialLink, url)
+ }
// Used to fix region when changing main options.
// TODO: Find a better way. ID by breadcrumb?
findRegion = (division, population, region) => {
- const initialNode = this.findInitialNode(division, population);
- const found = this.findChild(initialNode, region);
+ const initialNode = this.findInitialNode(division, population)
+ const found = this.findChild(initialNode, region)
if (!found) {
// Try to find in the other valid initialNodes (same division)
const nodes = this.indexData.filter(
(node) => division === node.territori && population !== node.poblacio
- );
+ )
for (const node of nodes) {
// Look for region in the other initialNode
- const f1 = this.findChild(node, region);
+ const f1 = this.findChild(node, region)
// If found, find in our initialNode for a region with the same name
const f2 =
f1 &&
@@ -131,56 +131,56 @@ class ChartDataHandler extends GHPages {
initialNode,
f1.name,
(node, name) => node.name === name
- );
+ )
// If found, use it
if (f2) {
- return f2;
+ return f2
}
}
// Not found in valid initialNodes: default to actual initialNode's root
- return initialNode;
+ return initialNode
}
// Found!
- return found;
- };
+ return found
+ }
// Fetch JSON data and subscribe to updates
data = (division, population, url, callback) => {
- const initialLink = this.findInitialNode(division, population);
+ const initialLink = this.findInitialNode(division, population)
// If found (why should it not?)
if (initialLink) {
// Find region name in link and children, recursively
- const found = this.findChild(initialLink, url);
+ const found = this.findChild(initialLink, url)
// TODO: We should do something else on error
if (!found) {
console.warn(
`Could not find data in index: ${division}/${population}/${url}`,
{ initialLink, index: this.indexData }
- );
- callback(false);
- return () => {};
+ )
+ callback(false)
+ return () => {}
}
// Add URL to active ones. Will be invalidated on update
if (!this.active.includes(found.url)) {
- this.active.push(found.url);
+ this.active.push(found.url)
}
// Get URL content (download or cached)
- return cache.fetch(this.getURL(found.url), callback);
+ return cache.fetch(this.getURL(found.url), callback)
}
console.warn(
`Could not find initial node in index: ${division}/${population}/${url}`,
{ initialLink, index: this.indexData }
- );
- callback(false);
- return () => {};
- };
+ )
+ callback(false)
+ return () => {}
+ }
}
-export default ChartDataHandler;
+export default ChartDataHandler
diff --git a/app/src/Backend/IndexesHandler.jsx b/app/src/Backend/IndexesHandler.jsx
index 7067525f..a3bd367a 100644
--- a/app/src/Backend/IndexesHandler.jsx
+++ b/app/src/Backend/IndexesHandler.jsx
@@ -1,14 +1,14 @@
-import React from "react";
-import PropTypes from "prop-types";
+import React from 'react'
+import PropTypes from 'prop-types'
-import Loading from "../Loading";
-import withDocumentVisibility from "../withDocumentVisibility";
+import Loading from '../Loading'
+import withDocumentVisibility from '../withDocumentVisibility'
-import { withHandler as withMapsDataHandler } from "../Backend/Maps/context";
-import { withHandler as withChartsDataHandler } from "../Backend/Charts/context";
-import { withHandler as withBcnDataHandler } from "../Backend/Bcn/context";
+import { withHandler as withMapsDataHandler } from '../Backend/Maps/context'
+import { withHandler as withChartsDataHandler } from '../Backend/Charts/context'
+import { withHandler as withBcnDataHandler } from '../Backend/Bcn/context'
-import Provider from "./Provider";
+import Provider from './Provider'
// Initially downloads all backends indexes
// - Shows
while data is downloading
@@ -29,22 +29,22 @@ import Provider from "./Provider";
// - Unsubscribe to index updates. If listeners go down to 0 and there is an
// ongoing download, it is automatically aborted by each backend handler.
class IndexesHandler extends React.Component {
- constructor(props) {
- super(props);
+ constructor (props) {
+ super(props)
// Create initial handled backends array
- this.backends = ["maps", "charts", "bcn"].map((name) => ({
+ this.backends = ['maps', 'charts', 'bcn'].map((name) => ({
name,
initializer: props[`${name}DataHandler`],
state: `${name}DataHandler`,
- unsubscribe: () => {},
- }));
+ unsubscribe: () => {}
+ }))
// Initialize to null an index state for each backend
this.state = this.backends.reduce((acc, { state }) => {
- acc[state] = null;
- return acc;
- }, {});
+ acc[state] = null
+ return acc
+ }, {})
}
// TODO: Launch new notification
@@ -53,8 +53,8 @@ class IndexesHandler extends React.Component {
onBeforeUpdate = (backend) => {
console.log(
`onBeforeUpdate: Looking for an update of backend ${backend.handler.name}`
- );
- };
+ )
+ }
// TODO: Launch new notification
// App can stop onBeforeUpdate
and resolve to ok/ok depending on `updated`
@@ -62,35 +62,35 @@ class IndexesHandler extends React.Component {
onAfterUpdate = (backend, updated) => {
console.log(
`onAfterUpdate: The backend ${backend.handler.name} was${
- updated ? "" : " not"
+ updated ? '' : ' not'
} updated`
- );
- };
+ )
+ }
// Checks if we need to update it and, if so, do it
updateIfNeeded = (backend) => {
return new Promise((resolve) => {
// If it has not yet been downloaded,
// we definitively still don't need an update
- const now = new Date();
+ const now = new Date()
if (
this.state[backend.state] !== null &&
!!backend.nextUpdateDate &&
now > backend.nextUpdateDate
) {
- this.onBeforeUpdate(backend);
+ this.onBeforeUpdate(backend)
backend.handler.updateIfNeeded((updated) => {
- this.onAfterUpdate(backend, updated);
- resolve(backend, updated);
- });
+ this.onAfterUpdate(backend, updated)
+ resolve(backend, updated)
+ })
} else {
- resolve(backend, false);
+ resolve(backend, false)
}
- });
- };
+ })
+ }
// Return a Promise resolved once all backends have updated if needed
- updateAllIfNeeded = () => Promise.all(this.backends.map(this.updateIfNeeded));
+ updateAllIfNeeded = () => Promise.all(this.backends.map(this.updateIfNeeded))
// Schedules a backend update and saves its
// due date into its `this.backends` object
@@ -99,105 +99,107 @@ class IndexesHandler extends React.Component {
// A `force now` callback can be passed to allow forcing immediate update
// A `cancel` callback can be passed to allow cancelling the scheduled update
scheduleNextUpdate = (backend) => {
- const { milllisToNextUpdate: notUpdatedMillis } = this.props;
+ const { milllisToNextUpdate: notUpdatedMillis } = this.props
// Mind the timer on unmount
// Use the date where needed
backend.nextUpdateDate = backend.handler.scheduleNextUpdate({
onBeforeUpdate: this.onBeforeUpdate.bind(this, backend),
onAfterUpdate: this.onAfterUpdate.bind(this, backend),
- notUpdatedMillis,
- });
- };
+ notUpdatedMillis
+ })
+ }
// Fetch data once mounted
- componentDidMount() {
+ componentDidMount () {
// Get `visible` status before the update: if it changes,
// we will subscribe to update schedule in componentDidUpdate
- const { visible } = this.props;
+ const { visible } = this.props
this.backends.forEach((backend) => {
// Instantiate the handler
- backend.handler = new backend.initializer();
+ backend.handler = new backend.initializer()
// Subscribe to index updates
// Save unsubscriber
backend.unsubscribe = backend.handler.index((index) => {
// Only on first download
if (!this.state[backend.state]) {
- this.onAfterUpdate(backend, true);
+ this.onAfterUpdate(backend, true)
}
// Save data into state
- this.setState({ [backend.state]: index });
+ this.setState({ [backend.state]: index })
// Once data has been fetched, schedule next data update
if (visible) {
- this.scheduleNextUpdate(backend);
+ this.scheduleNextUpdate(backend)
}
- });
- });
+ })
+ })
}
// Handle scheduled updates depending if user has the tab active or not
componentDidUpdate = async (prevProps, prevState) => {
- const { visible } = this.props;
+ const { visible } = this.props
if (visible !== prevProps.visible) {
if (visible) {
- console.log("Update backends if needed");
- await this.updateAllIfNeeded();
+ console.log('Update backends if needed')
+ await this.updateAllIfNeeded()
- console.log("Schedule next indexes updates");
- this.backends.forEach(this.scheduleNextUpdate);
+ console.log('Schedule next indexes updates')
+ this.backends.forEach(this.scheduleNextUpdate)
} else {
- console.log("Cancel schedule of next indexes updates");
- this.backends.forEach(({ handler }) => handler.cancelUpdateSchedule());
+ console.log('Cancel schedule of next indexes updates')
+ this.backends.forEach(({ handler }) => handler.cancelUpdateSchedule())
}
}
- };
+ }
// Cleanup side effects
- componentWillUnmount() {
+ componentWillUnmount () {
// Cancel next update timers and update subscriptions
this.backends.forEach((backend) => {
if (backend.handler) {
- backend.handler.cancelUpdateSchedule();
- backend.unsubscribe();
+ backend.handler.cancelUpdateSchedule()
+ backend.unsubscribe()
}
- });
+ })
}
- render() {
+ render () {
// Get an array with all handled indexes values
const indexes = this.backends
.map(({ state }) => state)
- .map((state) => this.state[state]);
+ .map((state) => this.state[state])
const provided = this.backends.reduce(
(acc, { name, handler }) => ({
...acc,
- [name]: handler,
+ [name]: handler
}),
{}
- );
+ )
// Show
while some index has not yet been loaded
- return indexes.some((index) => !index) ? (
-
- ) : (
-
{this.props.children}
- );
+ return indexes.some((index) => !index)
+ ? (
+
+ )
+ : (
+
{this.props.children}
+ )
}
}
IndexesHandler.defaultProps = {
// Retry every 5 minuts after each "not updated yet" response
- milllisToNextUpdate: 300_000,
-};
+ milllisToNextUpdate: 300_000
+}
IndexesHandler.propTypes = {
// Delay before retrying a schedule after a "not updated yet" response
- milllisToNextUpdate: PropTypes.number,
-};
+ milllisToNextUpdate: PropTypes.number
+}
// withMapsDataHandler: Add `mapsDataHandler` prop to use maps backend data
// withChartsDataHandler: Add `chartsDataHandler` prop to use charts backend data
@@ -208,4 +210,4 @@ export default withMapsDataHandler(
withChartsDataHandler(
withBcnDataHandler(withDocumentVisibility(IndexesHandler))
)
-);
+)
diff --git a/app/src/Backend/Maps/handler.js b/app/src/Backend/Maps/handler.js
index b261bf5a..d17ead0d 100644
--- a/app/src/Backend/Maps/handler.js
+++ b/app/src/Backend/Maps/handler.js
@@ -1,48 +1,48 @@
-import GHPages from "../Base/GHPages";
-import cache from "../Base/Cache";
+import GHPages from '../Base/GHPages'
+import cache from '../Base/Cache'
-import MapDataStatic from "./MapDataStatic";
+import MapDataStatic from './MapDataStatic'
// Handle data backend and cache for Maps
// This is not a singleton: unique error handlers
class MapDataHandler extends GHPages {
// Visible backend name
- name = "Maps JSON Files";
+ name = 'Maps JSON Files'
// Overload "abstract" member
- indexUrl = MapDataStatic.days;
+ indexUrl = MapDataStatic.days
// Invalidate all URLs, except index
invalidateAll = async () => {
// Invalidate each map JSON first
for (const kind of MapDataHandler.kinds()) {
for (const value of MapDataHandler.values(kind)) {
- this.log(`Invalidate ${kind} - ${value}`);
- await cache.invalidate(MapDataStatic.kind[kind].values[value]);
+ this.log(`Invalidate ${kind} - ${value}`)
+ await cache.invalidate(MapDataStatic.kind[kind].values[value])
}
}
- };
+ }
/*
Return static data
*/
- static kinds = () => Object.keys(MapDataStatic.kind);
- static values = (kind) => Object.keys(MapDataStatic.kind[kind].values);
- static svg = (kind) => MapDataStatic.kind[kind].svg;
+ static kinds = () => Object.keys(MapDataStatic.kind)
+ static values = (kind) => Object.keys(MapDataStatic.kind[kind].values)
+ static svg = (kind) => MapDataStatic.kind[kind].svg
- static metadata = (values, meta) => MapDataStatic.metadata[values][meta];
- static metaColors = (values) => MapDataHandler.metadata(values, "colors");
- static metaTitle = (values) => MapDataHandler.metadata(values, "title");
- static metaLabel = (values) => MapDataHandler.metadata(values, "label");
- static metaName = (values) => MapDataHandler.metadata(values, "name");
+ static metadata = (values, meta) => MapDataStatic.metadata[values][meta]
+ static metaColors = (values) => MapDataHandler.metadata(values, 'colors')
+ static metaTitle = (values) => MapDataHandler.metadata(values, 'title')
+ static metaLabel = (values) => MapDataHandler.metadata(values, 'label')
+ static metaName = (values) => MapDataHandler.metadata(values, 'name')
/*
Return dynamic data (with cache)
*/
- days = (callback) => cache.fetch(MapDataStatic.days, callback);
- index = (callback) => this.days(callback);
+ days = (callback) => cache.fetch(MapDataStatic.days, callback)
+ index = (callback) => this.days(callback)
data = (kind, values, callback) =>
- cache.fetch(MapDataStatic.kind[kind].values[values], callback);
+ cache.fetch(MapDataStatic.kind[kind].values[values], callback)
}
-export default MapDataHandler;
+export default MapDataHandler
diff --git a/app/src/Backend/Maps/index.test.js b/app/src/Backend/Maps/index.test.js
index 9aa58e0a..c6bc435c 100644
--- a/app/src/Backend/Maps/index.test.js
+++ b/app/src/Backend/Maps/index.test.js
@@ -1,6 +1,6 @@
-import MapDataHandler, { context } from "./index";
+import MapDataHandler, { context } from './index'
-test("Maps", async () => {
- expect(MapDataHandler).toBeTruthy();
- expect(context).toBeTruthy();
-});
+test('Maps', async () => {
+ expect(MapDataHandler).toBeTruthy()
+ expect(context).toBeTruthy()
+})
diff --git a/app/src/Dashboard.jsx b/app/src/Dashboard.jsx
index 38a48ff6..47bad885 100644
--- a/app/src/Dashboard.jsx
+++ b/app/src/Dashboard.jsx
@@ -22,7 +22,10 @@ import NotificationsIcon from '@material-ui/icons/Notifications';
import Menu from './Menu'
import ModalRouterWithRoutes from './ModalRouterWithRoutes'
import AppThemeProvider from './AppThemeProvider'
-import { withServiceWorkerUpdater, LocalStoragePersistenceService } from '@3m1/service-worker-updater'
+import {
+ withServiceWorkerUpdater,
+ LocalStoragePersistenceService
+} from '@3m1/service-worker-updater'
const Copyright = translate('Copyright')((props) => {
const { t } = props
@@ -163,8 +166,8 @@ const Dashboard = (props) => {
}
export default translate('Widget')(
- withServiceWorkerUpdater(
- Dashboard,
- { persistenceService: new LocalStoragePersistenceService('CovidRefactored') }
- ))
+ withServiceWorkerUpdater(Dashboard, {
+ persistenceService: new LocalStoragePersistenceService('CovidRefactored')
+ })
+)
export { Copyright }
diff --git a/app/src/ErrorCatcher.jsx b/app/src/ErrorCatcher.jsx
index 50d8f381..86f6e68e 100644
--- a/app/src/ErrorCatcher.jsx
+++ b/app/src/ErrorCatcher.jsx
@@ -1,37 +1,39 @@
-import React from "react";
-import PropTypes from "prop-types";
+import React from 'react'
+import PropTypes from 'prop-types'
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { faSyncAlt as faRefresh } from "@fortawesome/free-solid-svg-icons";
-import { translate } from "react-translate";
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
+import { faSyncAlt as faRefresh } from '@fortawesome/free-solid-svg-icons'
+import { translate } from 'react-translate'
const ShowErrorDefault = (props) => {
- const { onRetry, reloadOnRetry, errorMessage, errorStack, count, t } = props;
+ const { onRetry, reloadOnRetry, errorMessage, errorStack, count, t } = props
return (
<>
-
{t("Something went wrong :(")}
-
-
+ {t('Something went wrong :(')}
+
+
{reloadOnRetry
- ? t("Try reloading the app to recover from it")
- : t("Try recreating this component to recover from the error")}
+ ? t('Try reloading the app to recover from it')
+ : t('Try recreating this component to recover from the error')}
-
+
{errorMessage}
- {reloadOnRetry ? null : (
-
- {t("Counter")}: {count}
-
- )}
-
{errorStack}
+ {reloadOnRetry
+ ? null
+ : (
+
+ {t('Counter')}: {count}
+
+ )}
+
{errorStack}
>
- );
-};
+ )
+}
ShowErrorDefault.defaultProps = {
- t: (text) => text,
-};
+ t: (text) => text
+}
ShowErrorDefault.propTypes = {
onRetry: PropTypes.func.isRequired,
@@ -39,8 +41,8 @@ ShowErrorDefault.propTypes = {
errorMessage: PropTypes.string.isRequired,
errorStack: PropTypes.string.isRequired,
count: PropTypes.number.isRequired,
- t: PropTypes.func.isRequired,
-};
+ t: PropTypes.func.isRequired
+}
// Catches unhandled errors:
// - Show it to user
@@ -48,71 +50,71 @@ ShowErrorDefault.propTypes = {
class ErrorCatcher extends React.PureComponent {
initialState = {
hasError: false,
- errorMessage: "",
- errorStack: "",
- info: "",
- };
+ errorMessage: '',
+ errorStack: '',
+ info: ''
+ }
- constructor(props) {
- super(props);
+ constructor (props) {
+ super(props)
this.state = {
...this.initialState,
- count: 0,
- };
+ count: 0
+ }
}
- static getDerivedStateFromError(error) {
+ static getDerivedStateFromError (error) {
// Update state so the next render will show the fallback UI.
return {
hasError: true,
errorMessage: error.message,
- errorStack: error.stack,
- };
+ errorStack: error.stack
+ }
}
// Catch unhandled errors and send them to help improving the app
- componentDidCatch(error, info) {
+ componentDidCatch (error, info) {
this.setState(
({ count }) => ({
info: info.componentStack,
- count: count + 1,
+ count: count + 1
}),
- () => this.sendEvent("Unhandled Error")
- );
+ () => this.sendEvent('Unhandled Error')
+ )
}
reloadApp = () => {
- this.sendEvent("Reload page from Error");
+ this.sendEvent('Reload page from Error')
// Reload app
- window.location.reload();
- };
+ window.location.reload()
+ }
retry = () => {
if (this.props.reloadOnRetry) {
- this.reloadApp();
+ this.reloadApp()
} else {
- this.sendEvent("Reload component from Error");
- this.props.onRetry(this.state.count);
+ this.sendEvent('Reload component from Error')
+ this.props.onRetry(this.state.count)
this.setState({
- ...this.initialState,
- });
+ ...this.initialState
+ })
}
- };
+ }
// Send event to GA
sendEvent = (event) => {
const {
props: { sendEvent, origin },
- state: { errorMessage, count },
- } = this;
- sendEvent(event, origin, `${errorMessage} (${count})`);
- };
+ state: { errorMessage, count }
+ } = this
+ sendEvent(event, origin, `${errorMessage} (${count})`)
+ }
- render() {
+ render () {
if (this.state.hasError) {
- const { reloadOnRetry, ShowError } = this.props;
- const { errorMessage, errorStack, count } = this.state;
+ const { reloadOnRetry, ShowError } = this.props
+ const { errorMessage, errorStack, count } = this.state
// Show error to user
return (
@@ -123,33 +125,33 @@ class ErrorCatcher extends React.PureComponent {
errorStack={errorStack}
count={count}
/>
- );
+ )
}
// Execute contained components
- return this.props.children;
+ return this.props.children
}
}
ErrorCatcher.defaultProps = {
- origin: "No name",
+ origin: 'No name',
sendEvent: () => {},
onRetry: () => {},
reloadOnRetry: true,
- ShowError: translate("ErrorCatcher")(ShowErrorDefault),
-};
+ ShowError: translate('ErrorCatcher')(ShowErrorDefault)
+}
ErrorCatcher.propTypes = {
origin: PropTypes.string.isRequired,
sendEvent: PropTypes.func.isRequired,
onRetry: PropTypes.func.isRequired,
reloadOnRetry: PropTypes.bool.isRequired,
- ShowError: PropTypes.elementType.isRequired,
-};
+ ShowError: PropTypes.elementType.isRequired
+}
const withErrorCatcher = (origin, component) => (
{component}
-);
+)
-export default ErrorCatcher;
-export { withErrorCatcher, ShowErrorDefault };
+export default ErrorCatcher
+export { withErrorCatcher, ShowErrorDefault }
diff --git a/app/src/ModalRouter.jsx b/app/src/ModalRouter.jsx
index bcaa5862..867faac3 100644
--- a/app/src/ModalRouter.jsx
+++ b/app/src/ModalRouter.jsx
@@ -1,79 +1,79 @@
-import React from "react";
-import { Route, Switch, Redirect } from "react-router-dom";
+import React from 'react'
+import { Route, Switch, Redirect } from 'react-router-dom'
-import Dialog from "@material-ui/core/Dialog";
-import DialogActions from "@material-ui/core/DialogActions";
-import DialogContent from "@material-ui/core/DialogContent";
-import Button from "@material-ui/core/Button";
-import useMediaQuery from "@material-ui/core/useMediaQuery";
-import { useTheme } from "@material-ui/core/styles";
+import Dialog from '@material-ui/core/Dialog'
+import DialogActions from '@material-ui/core/DialogActions'
+import DialogContent from '@material-ui/core/DialogContent'
+import Button from '@material-ui/core/Button'
+import useMediaQuery from '@material-ui/core/useMediaQuery'
+import { useTheme } from '@material-ui/core/styles'
-import { translate } from "react-translate";
+import { translate } from 'react-translate'
class CloseModal extends React.PureComponent {
- constructor(props) {
- super(props);
+ constructor (props) {
+ super(props)
- if (!("history" in props) || props.history.location.hash !== "") {
- global.setTimeout(() => props.history.push("#"), 10);
+ if (!('history' in props) || props.history.location.hash !== '') {
+ global.setTimeout(() => props.history.push('#'), 10)
}
}
- render() {
- return null;
+ render () {
+ return null
}
}
class ModalRouterInner extends React.PureComponent {
- constructor(props) {
- super();
+ constructor (props) {
+ super()
// Set initial state
- this.history = props.history;
+ this.history = props.history
this.state = {
...this.getPathState(props.location),
autoForce: false,
forced: false,
- mounted: false,
- };
+ mounted: false
+ }
}
- getPathState(location) {
- const path = location.hash.replace(/[^#]*#(.*)$/, "$1");
+ getPathState (location) {
+ const path = location.hash.replace(/[^#]*#(.*)$/, '$1')
return {
- modalIsOpen: !!path.length && path !== "close",
+ modalIsOpen: !!path.length && path !== 'close',
path,
- initialPath: this.state ? this.state.initialPath : path,
- };
+ initialPath: this.state ? this.state.initialPath : path
+ }
}
- componentDidMount() {
+ componentDidMount () {
// Register history change event listener
- this.unlisten = this.history.listen(this.handleHistoryChange);
+ this.unlisten = this.history.listen(this.handleHistoryChange)
this.setState({
...this.getPathState(this.props.location),
- mounted: true,
- });
+ mounted: true
+ })
}
- componentWillUnmount() {
+ componentWillUnmount () {
// Unregister history change event listener
- this.unlisten();
+ this.unlisten()
if (this.timer) {
- global.clearTimeout(this.timer);
+ global.clearTimeout(this.timer)
}
}
// Force user to different URLs
- componentDidUpdate(prevProps, prevState) {
+ componentDidUpdate (prevProps, prevState) {
// Remember old URL when forcing
if (this.props.force !== false && this.state.forced !== this.props.force) {
- this.setState({ forced: this.props.force });
+ this.setState({ forced: this.props.force })
}
// Clear autoForce after it has been forced
if (this.state.autoForce === this.state.path) {
- this.setState({ autoForce: false });
+ this.setState({ autoForce: false })
}
// Force old modal after forcing another (do auto force)
@@ -84,45 +84,45 @@ class ModalRouterInner extends React.PureComponent {
this.state.initialPath.length > 1 &&
this.state.autoForce !== this.state.initialPath
) {
- this.setState({ autoForce: this.state.initialPath });
+ this.setState({ autoForce: this.state.initialPath })
}
}
- openModal = () => this.setState({ modalIsOpen: true });
+ openModal = () => this.setState({ modalIsOpen: true })
closeModal = (propsClose = {}) => {
- if (!("history" in propsClose) || propsClose.history.location.path !== "") {
+ if (!('history' in propsClose) || propsClose.history.location.path !== '') {
if (!this.timer) {
this.timer = global.setTimeout(() => {
- this.history.push("#");
- this.timer = undefined;
- }, 10);
+ this.history.push('#')
+ this.timer = undefined
+ }, 10)
}
}
- };
+ }
handleHistoryChange = (location, action) => {
- const state = this.getPathState(location);
- this.setState(state);
- };
+ const state = this.getPathState(location)
+ this.setState(state)
+ }
- render() {
- const { children, initializing, force, fullScreen, t } = this.props;
- const { mounted, autoForce, path, forced, modalIsOpen } = this.state;
+ render () {
+ const { children, initializing, force, fullScreen, t } = this.props
+ const { mounted, autoForce, path, forced, modalIsOpen } = this.state
if (initializing || !mounted) {
- return null;
+ return null
}
// Redirect to forced URL
if (force !== false && force !== path && force !== forced) {
- return
;
+ return
}
// If previously have been forced when showing a modal,
// force to go back there once the forced has been visited
if (autoForce !== false && autoForce !== path) {
- return
;
+ return
}
return (
@@ -131,8 +131,8 @@ class ModalRouterInner extends React.PureComponent {
onClose={this.closeModal}
/* Be fullsreen on `sm` sreens */
fullScreen={fullScreen}
- aria-labelledby={"modal_heading"}
- aria-describedby={"modal_description"}
+ aria-labelledby='modal_heading'
+ aria-describedby='modal_description'
>
@@ -143,38 +143,38 @@ class ModalRouterInner extends React.PureComponent {
-
- {t("Close")}
+
+ {t('Close')}
- );
+ )
}
}
// Get fullScreen prop using MediaQuery
const withFullScreen = (Component) => {
return (props) => {
- const theme = useTheme();
- const fullScreen = useMediaQuery(theme.breakpoints.down("sm"));
- return
;
- };
-};
+ const theme = useTheme()
+ const fullScreen = useMediaQuery(theme.breakpoints.down('sm'))
+ return
+ }
+}
-const ModalRouterInnerWithClasses = withFullScreen(ModalRouterInner);
+const ModalRouterInnerWithClasses = withFullScreen(ModalRouterInner)
class ModalRouter extends React.PureComponent {
- render() {
- const { children, ...rest } = this.props;
+ render () {
+ const { children, ...rest } = this.props
return (
(
)}
/>
- );
+ )
}
}
-export default translate("ModalRouter")(ModalRouter);
+export default translate('ModalRouter')(ModalRouter)
diff --git a/app/src/ModalRouterWithRoutes.jsx b/app/src/ModalRouterWithRoutes.jsx
index dde3379a..2109026c 100644
--- a/app/src/ModalRouterWithRoutes.jsx
+++ b/app/src/ModalRouterWithRoutes.jsx
@@ -22,7 +22,10 @@ const ModalRouterWithRoutes = (props) => {
render={() =>
withErrorCatcher(
'Language',
-
+
)}
/>
{
if (this.timer !== false) {
- clearTimeout(this.timer);
- this.timer = false;
+ clearTimeout(this.timer)
+ this.timer = false
}
- };
+ }
// Run function, if not throtled or if force == true
- run(force, timeout, func) {
+ run (force, timeout, func) {
if (force) {
- this.clear();
+ this.clear()
}
if (this.timer === false) {
- this.start(timeout);
- func();
+ this.start(timeout)
+ func()
}
}
}
-export default Throtle;
+export default Throtle
diff --git a/app/src/Widget/Actions.jsx b/app/src/Widget/Actions.jsx
index d57e019c..1c4ae257 100644
--- a/app/src/Widget/Actions.jsx
+++ b/app/src/Widget/Actions.jsx
@@ -1,46 +1,46 @@
-import React from "react";
-import PropTypes from "prop-types";
+import React from 'react'
+import PropTypes from 'prop-types'
-import { translate } from "react-translate";
+import { translate } from 'react-translate'
-import Button from "@material-ui/core/Button";
-import Dialog from "@material-ui/core/Dialog";
-import DialogActions from "@material-ui/core/DialogActions";
-import DialogContent from "@material-ui/core/DialogContent";
-import DialogTitle from "@material-ui/core/DialogTitle";
-import useMediaQuery from "@material-ui/core/useMediaQuery";
-import { useTheme } from "@material-ui/core/styles";
+import Button from '@material-ui/core/Button'
+import Dialog from '@material-ui/core/Dialog'
+import DialogActions from '@material-ui/core/DialogActions'
+import DialogContent from '@material-ui/core/DialogContent'
+import DialogTitle from '@material-ui/core/DialogTitle'
+import useMediaQuery from '@material-ui/core/useMediaQuery'
+import { useTheme } from '@material-ui/core/styles'
-import Draggable from "react-draggable";
+import Draggable from 'react-draggable'
-import WidgetMenu from "./Menu";
-import ErrorCatcher from "../ErrorCatcher";
+import WidgetMenu from './Menu'
+import ErrorCatcher from '../ErrorCatcher'
class DraggableResponsiveDialogUntranslated extends React.PureComponent {
/*
Handle dialog open/close status
*/
state = {
- open: false,
- };
+ open: false
+ }
// Set open status to the selected menu code
handleClickOpen = (open) => {
- this.setState({ open });
- };
+ this.setState({ open })
+ }
handleClose = () => {
- this.setState({ open: false });
- };
+ this.setState({ open: false })
+ }
- render() {
- const { open } = this.state;
+ render () {
+ const { open } = this.state
// Get `restProps` for child renders (including `id`)
- const { sections, fullScreen, t, ...restProps } = this.props;
- const { id } = this.props;
+ const { sections, fullScreen, t, ...restProps } = this.props
+ const { id } = this.props
// Get shortcut to content & title render functions
- const Content = open ? sections[open].render : () => {};
- const Title = open ? sections[open].title : () => {};
+ const Content = open ? sections[open].render : () => {}
+ const Title = open ? sections[open].title : () => {}
return (
<>
@@ -69,40 +69,40 @@ class DraggableResponsiveDialogUntranslated extends React.PureComponent {
-
- {t("Close")}
+
+ {t('Close')}
) : null}
>
- );
+ )
}
}
-const DraggableResponsiveDialog = translate("Widget")(
+const DraggableResponsiveDialog = translate('Widget')(
DraggableResponsiveDialogUntranslated
-);
+)
DraggableResponsiveDialog.propTypes = {
id: PropTypes.string.isRequired,
// [sectionID('view','edit','legend',...)]: {icon: string, label: string/fn }
- sections: PropTypes.object.isRequired,
-};
+ sections: PropTypes.object.isRequired
+}
// Get fullScreen prop using MediaQuery
const withFullScreen = (Component) => {
return (props) => {
- const theme = useTheme();
- const fullScreen = useMediaQuery(theme.breakpoints.down("sm"));
- return ;
- };
-};
+ const theme = useTheme()
+ const fullScreen = useMediaQuery(theme.breakpoints.down('sm'))
+ return
+ }
+}
-export default withFullScreen(DraggableResponsiveDialog);
+export default withFullScreen(DraggableResponsiveDialog)
diff --git a/app/src/Widget/List.jsx b/app/src/Widget/List.jsx
index 7f447796..3fa5a092 100644
--- a/app/src/Widget/List.jsx
+++ b/app/src/Widget/List.jsx
@@ -1,35 +1,35 @@
-import React from "react";
-import PropTypes from "prop-types";
+import React from 'react'
+import PropTypes from 'prop-types'
-import ListHeader from "./ListHeader";
-import MenuAddWidget from "./MenuAddWidget";
-import WidgetsTypes from "./Widgets";
-import SortableWidgetContainer from "./SortableWidgetContainer";
+import ListHeader from './ListHeader'
+import MenuAddWidget from './MenuAddWidget'
+import WidgetsTypes from './Widgets'
+import SortableWidgetContainer from './SortableWidgetContainer'
-import Throtle from "../Throtle";
-import DateSlider from "./Slider";
-import Loading from "../Loading";
+import Throtle from '../Throtle'
+import DateSlider from './Slider'
+import Loading from '../Loading'
-import { withIndex as withMapsIndex } from "../Backend/Maps/context";
-import { WidgetStorageContextProvider, withStorageHandler } from "./Storage";
+import { withIndex as withMapsIndex } from '../Backend/Maps/context'
+import { WidgetStorageContextProvider, withStorageHandler } from './Storage'
// GUID generator: used to create unique temporal IDs for widgets
const S4 = () =>
- (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);
+ (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1)
const guidGenerator = () =>
- "a-" +
+ 'a-' +
S4() +
S4() +
- "-" +
+ '-' +
S4() +
- "-" +
+ '-' +
S4() +
- "-" +
+ '-' +
S4() +
- "-" +
+ '-' +
S4() +
S4() +
- S4();
+ S4()
// Renders widgets list
// - Manages days list (using backend) and currently selected Date with a Slider
@@ -46,80 +46,80 @@ class WidgetsList extends React.PureComponent {
// Managed data: days & currently selected day
state = {
currentDate: null,
- widgets: [],
- };
+ widgets: []
+ }
// Widgets list temporal IDs
// Used to maintain widget uniqueness during a session
- widgetsIds = [];
+ widgetsIds = []
// Used in the slider to prevent blocking the UI
// with excessive calls on mouse move
- throtle = new Throtle();
+ throtle = new Throtle()
updateWidgets = () => {
// Ensure a unique and constant ID for each widget
- const length = this.widgetsIds.length;
+ const length = this.widgetsIds.length
if (length < this.props.widgets.length) {
this.widgetsIds = this.props.widgets.map((w, index) =>
index < length ? this.widgetsIds[index] : guidGenerator()
- );
+ )
}
// Generate a cached (in state) madurated list of widgets
const widgets = this.props.widgets.map(({ type, payload }, index) => ({
id: this.widgetsIds[index],
Component: WidgetsTypes.find((w) => w.key === type).Component,
- ...{ payload },
- }));
+ ...{ payload }
+ }))
- this.setState({ widgets });
- };
+ this.setState({ widgets })
+ }
- componentDidMount() {
- const { days } = this.props;
- this.updateWidgets();
- this.setState({ currentDate: days.length - 1 });
+ componentDidMount () {
+ const { days } = this.props
+ this.updateWidgets()
+ this.setState({ currentDate: days.length - 1 })
}
// Cleanup side effects
- componentWillUnmount() {
+ componentWillUnmount () {
// Cancel possible throtle timer
- this.throtle.clear();
+ this.throtle.clear()
}
- componentDidUpdate(prevProps, prevState) {
- const { days: daysOld } = prevProps;
- const { days } = this.props;
+ componentDidUpdate (prevProps, prevState) {
+ const { days: daysOld } = prevProps
+ const { days } = this.props
if (daysOld !== days) {
- const { currentDate } = this.state;
+ const { currentDate } = this.state
const isLast =
- !currentDate || !daysOld || currentDate === daysOld.length - 1;
+ !currentDate || !daysOld || currentDate === daysOld.length - 1
// If we were on last `days` item, go to the new last
if (isLast) {
this.setState({
- currentDate: days.length - 1,
- });
+ currentDate: days.length - 1
+ })
}
}
if (prevProps.widgets !== this.props.widgets) {
- this.updateWidgets();
+ this.updateWidgets()
}
}
// Slider helpers
onSetDate = (event, currentDate) =>
- this.throtle.run(false, 10, () => this.setState({ currentDate }));
+ this.throtle.run(false, 10, () => this.setState({ currentDate }))
// Adds a new default widget to the list
onAdd = (widgetType) => {
- const widgets = [...this.props.widgets];
- widgets.push({ type: widgetType ?? "map" });
- return this.props.onChangeData({ widgets });
- };
+ const widgets = [...this.props.widgets]
+ widgets.push({ type: widgetType ?? 'map' })
+ return this.props.onChangeData({ widgets })
+ }
// Handle onRemove event from widget
// Uses `this.widgetsIDs` for widget identification
@@ -127,47 +127,47 @@ class WidgetsList extends React.PureComponent {
onRemove = (id) => {
const widgets = this.props.widgets.filter(
(w, index) => this.widgetsIds[index] !== id
- );
- this.widgetsIds = this.widgetsIds.filter((idElement) => idElement !== id);
- return this.props.onChangeData({ widgets });
- };
+ )
+ this.widgetsIds = this.widgetsIds.filter((idElement) => idElement !== id)
+ return this.props.onChangeData({ widgets })
+ }
// Handle onChangeData event from widget
// Calls parent' onChangeData to save the data
onChangeData = (id, data) => {
- const { widgets } = this.props;
+ const { widgets } = this.props
const widgetsNew = widgets.map((w, i) => ({
...w,
- ...(id === this.widgetsIds[i] ? { payload: data } : {}),
- }));
- return this.props.onChangeData({ widgets: widgetsNew });
- };
+ ...(id === this.widgetsIds[i] ? { payload: data } : {})
+ }))
+ return this.props.onChangeData({ widgets: widgetsNew })
+ }
// Handle widgets reordering
// Takes care of both `this.widgetsIds` and `this.props.widgets`
onReorder = (oldIndex, newIndex) => {
// Reorder ID
- const id = this.widgetsIds[oldIndex];
- this.widgetsIds.splice(oldIndex, 1);
- this.widgetsIds.splice(newIndex, 0, id);
+ const id = this.widgetsIds[oldIndex]
+ this.widgetsIds.splice(oldIndex, 1)
+ this.widgetsIds.splice(newIndex, 0, id)
// Reorder data (keep props immutable)
// Use `widgets.filter` to clone original array
- const { widgets } = this.props;
- const widget = widgets[oldIndex];
- const widgetsNew = widgets.filter((w, index) => index !== oldIndex);
- widgetsNew.splice(newIndex, 0, widget);
+ const { widgets } = this.props
+ const widget = widgets[oldIndex]
+ const widgetsNew = widgets.filter((w, index) => index !== oldIndex)
+ widgetsNew.splice(newIndex, 0, widget)
// Save data
- return this.props.onChangeData({ widgets: widgetsNew });
- };
+ return this.props.onChangeData({ widgets: widgetsNew })
+ }
- render() {
- const { days } = this.props;
- const { widgets, currentDate } = this.state;
+ render () {
+ const { days } = this.props
+ const { widgets, currentDate } = this.state
if (currentDate === null) {
- return ;
+ return
}
return (
@@ -194,19 +194,19 @@ class WidgetsList extends React.PureComponent {
widgets={widgets}
/>
>
- );
+ )
}
}
WidgetsList.propTypes = {
widgets: PropTypes.array.isRequired,
days: PropTypes.array.isRequired,
- onChangeData: PropTypes.func.isRequired,
-};
+ onChangeData: PropTypes.func.isRequired
+}
// withStorageHandler: Handle params from storage providers (route + localStorage) into props
// withMapsIndex: Add `days` prop to use maps backend index (days)
-const WidgetsListWithHOCs = withStorageHandler(withMapsIndex(WidgetsList));
+const WidgetsListWithHOCs = withStorageHandler(withMapsIndex(WidgetsList))
// Manage some context providers details:
// - pathFilter: How to split `location` ( `path` prop)
@@ -214,22 +214,22 @@ const WidgetsListWithHOCs = withStorageHandler(withMapsIndex(WidgetsList));
// - paramsToString: Parse back a JS object into a `location` path
const WidgetsListWithStorageContextProviders = (props) => (
{
- const { widgets } = params;
- let widgetsParsed;
+ const { widgets } = params
+ let widgetsParsed
try {
widgetsParsed = widgets
- .split("/")
- .map((w) => JSON.parse(decodeURIComponent(w)));
+ .split('/')
+ .map((w) => JSON.parse(decodeURIComponent(w)))
} catch (err) {
- widgetsParsed = [];
+ widgetsParsed = []
}
return Object.assign({}, props, {
- widgets: widgetsParsed,
- });
+ widgets: widgetsParsed
+ })
}}
paramsToString={(params) => {
const widgets = params.widgets
@@ -237,16 +237,16 @@ const WidgetsListWithStorageContextProviders = (props) => (
encodeURIComponent(
JSON.stringify({
type: w.type,
- payload: w.payload,
+ payload: w.payload
})
)
)
- .join("/");
- return `/${widgets}`;
+ .join('/')
+ return `/${widgets}`
}}
>
-);
+)
-export default WidgetsListWithStorageContextProviders;
+export default WidgetsListWithStorageContextProviders
diff --git a/app/src/Widget/MenuAddWidget.jsx b/app/src/Widget/MenuAddWidget.jsx
index 292675db..7b145434 100644
--- a/app/src/Widget/MenuAddWidget.jsx
+++ b/app/src/Widget/MenuAddWidget.jsx
@@ -53,34 +53,36 @@ const MenuAddWidget = React.memo((props) => {
- {anchorEl ? (
-
- ) : null}
+ {anchorEl
+ ? (
+
+ )
+ : null}
>
)
})
diff --git a/app/src/Widget/MenuItem.jsx b/app/src/Widget/MenuItem.jsx
index fbc2802f..204587c4 100644
--- a/app/src/Widget/MenuItem.jsx
+++ b/app/src/Widget/MenuItem.jsx
@@ -1,29 +1,29 @@
-import React from "react";
-import PropTypes from "prop-types";
+import React from 'react'
+import PropTypes from 'prop-types'
-import MenuItem from "@material-ui/core/MenuItem";
-import ListItemIcon from "@material-ui/core/ListItemIcon";
-import ListItemText from "@material-ui/core/ListItemText";
+import MenuItem from '@material-ui/core/MenuItem'
+import ListItemIcon from '@material-ui/core/ListItemIcon'
+import ListItemText from '@material-ui/core/ListItemText'
// Renders an item into the widget's popup actions menu
// Ensures click event uses widget id
const WidgetMenuItem = React.forwardRef((props, ref) => {
- const { onClick, option, icon, label } = props;
- const handleClick = () => onClick(option);
+ const { onClick, option, icon, label } = props
+ const handleClick = () => onClick(option)
return (
{icon}
- );
-});
+ )
+})
-WidgetMenuItem.displayName = WidgetMenuItem;
+WidgetMenuItem.displayName = WidgetMenuItem
WidgetMenuItem.propTypes = {
icon: PropTypes.element.isRequired,
label: PropTypes.string.isRequired,
onClick: PropTypes.func.isRequired,
- option: PropTypes.string.isRequired,
-};
+ option: PropTypes.string.isRequired
+}
-export default WidgetMenuItem;
+export default WidgetMenuItem
diff --git a/app/src/Widget/Storage/StorageContextProviderLocalStorage.jsx b/app/src/Widget/Storage/StorageContextProviderLocalStorage.jsx
index b7b66c78..d9456f22 100644
--- a/app/src/Widget/Storage/StorageContextProviderLocalStorage.jsx
+++ b/app/src/Widget/Storage/StorageContextProviderLocalStorage.jsx
@@ -1,9 +1,9 @@
-import React from "react";
+import React from 'react'
-import Storage from "react-simple-storage";
+import Storage from 'react-simple-storage'
-import Loading from "../../Loading";
-import WidgetStorageContext from "./StorageContext";
+import Loading from '../../Loading'
+import WidgetStorageContext from './StorageContext'
/*
Context provider using localStorage as data source for Widgets params
@@ -11,49 +11,51 @@ import WidgetStorageContext from "./StorageContext";
class LocalStorageProvider extends React.Component {
state = {
initializing: true,
- data: {},
- };
+ data: {}
+ }
onChangeData = (data) => {
- this.setState({ data });
- };
+ this.setState({ data })
+ }
stopInitializing = () => {
- this.setState({ initializing: false });
- };
+ this.setState({ initializing: false })
+ }
- render() {
- const { children, ...props } = this.props;
- const { initializing, data } = this.state;
+ render () {
+ const { children, ...props } = this.props
+ const { initializing, data } = this.state
return (
<>
{/* Persistent state saver into localStorage, only on window close */}
- {initializing ? (
-
- ) : (
-
- {children}
-
- )}
+ {initializing
+ ? (
+
+ )
+ : (
+
+ {children}
+
+ )}
>
- );
+ )
}
}
-LocalStorageProvider.propTypes = {};
+LocalStorageProvider.propTypes = {}
-export default LocalStorageProvider;
+export default LocalStorageProvider
diff --git a/app/src/Widget/Storage/StorageContextProviderRouter.jsx b/app/src/Widget/Storage/StorageContextProviderRouter.jsx
index adf9140c..c0c5f5a8 100644
--- a/app/src/Widget/Storage/StorageContextProviderRouter.jsx
+++ b/app/src/Widget/Storage/StorageContextProviderRouter.jsx
@@ -1,68 +1,67 @@
-import React from "react";
-import { PropTypes } from "prop-types";
-import { Route, Switch } from "react-router-dom";
+import React from 'react'
+import { PropTypes } from 'prop-types'
+import { Route, Switch } from 'react-router-dom'
-import WidgetStorageContext from "./StorageContext";
-import withStorageHandler from "./withStorageHandler";
+import WidgetStorageContext from './StorageContext'
+import withStorageHandler from './withStorageHandler'
/*
Context provider using React Route as data source for Widgets params
*/
class RouterProvider extends React.Component {
// History change listener
- unlisten = () => {};
+ unlisten = () => {}
- constructor(props) {
- super(props);
- this.history = props.history;
+ constructor (props) {
+ super(props)
+ this.history = props.history
}
historyPush = (data, replace = false) => {
- const newPath = this.props.paramsToString(data);
+ const newPath = this.props.paramsToString(data)
// Only PUSH or REPLACE if something have to change
if (this.history.location.pathname !== newPath) {
if (!replace) {
- this.history.push(newPath + this.history.location.hash);
+ this.history.push(newPath + this.history.location.hash)
} else {
- this.history.replace(newPath + this.history.location.hash);
+ this.history.replace(newPath + this.history.location.hash)
}
}
- };
+ }
handleHistoryChange = (location, action) => {
// Do nothing when change is made by us
- if (action !== "POP") {
- return;
+ if (action !== 'POP') {
}
- };
+ }
componentDidMount = () => {
// Register history change event listener
- this.unlisten = this.history.listen(this.handleHistoryChange);
+ this.unlisten = this.history.listen(this.handleHistoryChange)
// Check if data from localStorage is better
if (
!this.props.params.widgets.length &&
this.props.data.widgets &&
this.props.data.widgets.length
) {
- this.historyPush(this.props.data, true);
+ this.historyPush(this.props.data, true)
}
- };
+ }
componentWillUnmount = () => {
// Unregister history change event listener
- this.unlisten();
- };
+ this.unlisten()
+ }
// Save data into location
onChangeData = (data) => {
- this.historyPush({ ...data }, true);
- this.props.onChangeData({ ...data });
- };
+ this.historyPush({ ...data }, true)
+ this.props.onChangeData({ ...data })
+ }
// Render the provider and the children
- render() {
+ render () {
const {
children,
data,
@@ -72,7 +71,7 @@ class RouterProvider extends React.Component {
history,
onChangeData,
...props
- } = this.props;
+ } = this.props
return (
{children}
- );
+ )
}
}
@@ -95,12 +94,12 @@ RouterProvider.propTypes = {
onChangeData: PropTypes.func.isRequired,
// Receive params and history from browser location/Router
params: PropTypes.object.isRequired,
- history: PropTypes.object.isRequired,
-};
+ history: PropTypes.object.isRequired
+}
// Proxy Route data into child props provider
const RouterProviderWithSwitch = (props) => {
- const { pathFilter, paramsFilter, paramsToString, ...restProps } = props;
+ const { pathFilter, paramsFilter, paramsToString, ...restProps } = props
return (
{
render={(propsRoute) => {
const {
match: { params },
- history,
- } = propsRoute;
- const processedParams = paramsFilter(params);
+ history
+ } = propsRoute
+ const processedParams = paramsFilter(params)
return (
- );
+ )
}}
/>
- );
-};
+ )
+}
RouterProviderWithSwitch.propTypes = {
// How to handle hash storage
pathFilter: PropTypes.string.isRequired,
paramsFilter: PropTypes.func.isRequired,
- paramsToString: PropTypes.func.isRequired,
-};
+ paramsToString: PropTypes.func.isRequired
+}
// Consume localStorage storage provider
const RouterProviderWithSwitchWithStorageHandler = withStorageHandler(
RouterProviderWithSwitch,
- { data: { widgets: { type: "map" } } } // TODO: default
-);
+ { data: { widgets: { type: 'map' } } } // TODO: default
+)
-export default RouterProviderWithSwitchWithStorageHandler;
+export default RouterProviderWithSwitchWithStorageHandler
diff --git a/app/src/Widget/Storage/withStorageHandler.jsx b/app/src/Widget/Storage/withStorageHandler.jsx
index a0842a0b..e1a042d5 100644
--- a/app/src/Widget/Storage/withStorageHandler.jsx
+++ b/app/src/Widget/Storage/withStorageHandler.jsx
@@ -1,6 +1,6 @@
-import React from "react";
+import React from 'react'
-import WidgetStorageContext from "./StorageContext";
+import WidgetStorageContext from './StorageContext'
/*
Proxies data from this context into Component props
@@ -8,9 +8,9 @@ import WidgetStorageContext from "./StorageContext";
const withStorageHandler = (Component, defaults) => {
class WithStorageHandler extends React.Component {
storageHandler = (data) => {
- const { forwardedRef, ...props } = this.props;
- const { onChangeData = () => {}, ...restData } = data;
- const allData = Object.assign({}, defaults, restData, props);
+ const { forwardedRef, ...props } = this.props
+ const { onChangeData = () => {}, ...restData } = data
+ const allData = Object.assign({}, defaults, restData, props)
return (
{
onChangeData={onChangeData}
{...allData}
/>
- );
- };
+ )
+ }
- render() {
+ render () {
return (
{this.storageHandler}
- );
+ )
}
}
// Return wrapper respecting ref
const forwarded = React.forwardRef((props, ref) => (
- ));
+ ))
- forwarded.propTypes = Component.propTypes;
- forwarded.defaultProps = Component.defaultProps;
+ forwarded.propTypes = Component.propTypes
+ forwarded.defaultProps = Component.defaultProps
- return forwarded;
-};
+ return forwarded
+}
-export default withStorageHandler;
+export default withStorageHandler
diff --git a/app/src/Widget/Widgets/Bcn/EditDataset.jsx b/app/src/Widget/Widgets/Bcn/EditDataset.jsx
index 3cf50675..a44aabc1 100644
--- a/app/src/Widget/Widgets/Bcn/EditDataset.jsx
+++ b/app/src/Widget/Widgets/Bcn/EditDataset.jsx
@@ -1,63 +1,63 @@
-import React from "react";
+import React from 'react'
-import { withStyles } from "@material-ui/core/styles";
-import TreeView from "@material-ui/lab/TreeView";
-import ExpandMoreIcon from "@material-ui/icons/ExpandMore";
-import ChevronRightIcon from "@material-ui/icons/ChevronRight";
-import TreeItem from "@material-ui/lab/TreeItem";
+import { withStyles } from '@material-ui/core/styles'
+import TreeView from '@material-ui/lab/TreeView'
+import ExpandMoreIcon from '@material-ui/icons/ExpandMore'
+import ChevronRightIcon from '@material-ui/icons/ChevronRight'
+import TreeItem from '@material-ui/lab/TreeItem'
-import { withHandler } from "../../../Backend/Bcn/context";
+import { withHandler } from '../../../Backend/Bcn/context'
// From: https://material-ui.com/components/tree-view/#rich-object
const styles = {
root: {
flexGrow: 1,
- maxWidth: 400,
- },
-};
+ maxWidth: 400
+ }
+}
const Tree = ({ node }) => (
{Array.isArray(node.sections)
? node.sections.map((node, key) => )
: null}
-);
+)
class RecursiveTreeView extends React.Component {
state = {
breadcrumb: [],
- value: "",
- };
+ value: ''
+ }
onNodeSelect = (event, value, ...rest) => {
// Don't save values for disabled nodes
if (!/^DISABLED-/.test(value)) {
- this.props.onChange(value);
+ this.props.onChange(value)
}
- };
+ }
- constructor(props) {
- super(props);
+ constructor (props) {
+ super(props)
- const { value, bcnDataHandler } = props;
+ const { value, bcnDataHandler } = props
this.state = {
value,
breadcrumb: bcnDataHandler
.findBreadcrumb(null, value)
- .map((node) => `${!("values" in node) ? "DISABLED-" : ""}${node.code}`),
- };
+ .map((node) => `${!('values' in node) ? 'DISABLED-' : ''}${node.code}`)
+ }
}
render = () => {
- const { classes, bcnDataHandler, ...restProps } = this.props;
- const { breadcrumb, value } = this.state;
+ const { classes, bcnDataHandler, ...restProps } = this.props
+ const { breadcrumb, value } = this.state
return (
{bcnDataHandler
- .filter((section) => ["graph", "chart"].includes(section.type))
+ .filter((section) => ['graph', 'chart'].includes(section.type))
.map((section) => (
))}
- );
- };
+ )
+ }
}
-export default withHandler(withStyles(styles)(RecursiveTreeView));
+export default withHandler(withStyles(styles)(RecursiveTreeView))
diff --git a/app/src/Widget/Widgets/Bcn/Widget.jsx b/app/src/Widget/Widgets/Bcn/Widget.jsx
index c3051c04..5cad9185 100644
--- a/app/src/Widget/Widgets/Bcn/Widget.jsx
+++ b/app/src/Widget/Widgets/Bcn/Widget.jsx
@@ -1,21 +1,21 @@
-import React from "react";
-import PropTypes from "prop-types";
+import React from 'react'
+import PropTypes from 'prop-types'
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import BcnLogo from "./BcnLogo";
-import { faEdit } from "@fortawesome/free-solid-svg-icons";
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
+import BcnLogo from './BcnLogo'
+import { faEdit } from '@fortawesome/free-solid-svg-icons'
-import { withHandler, withData } from "../../../Backend/Bcn/context";
+import { withHandler, withData } from '../../../Backend/Bcn/context'
-import Chart from "../Common/Chart";
-import Edit from "./Edit";
-import withWidget from "../../Widget";
+import Chart from '../Common/Chart'
+import Edit from './Edit'
+import withWidget from '../../Widget'
const ChartWrapper = withWidget({
// The normal view
view: {
icon: ,
- label: ({ t }) => t("View"),
+ label: ({ t }) => t('View'),
title: (props) => props.title,
render: withData((props) => {
const {
@@ -31,108 +31,108 @@ const ChartWrapper = withWidget({
bcnDataHandler,
// Passed through
...restProps
- } = props;
+ } = props
// Once downloaded, set parent's data, so it can properly
// set title and other widget components
- React.useEffect(() => setBcnData(data), [data, setBcnData]);
+ React.useEffect(() => setBcnData(data), [data, setBcnData])
- return ;
- }),
+ return
+ })
},
// Edit data
edit: {
icon: ,
- label: ({ t }) => t("Edit"),
- title: ({ t }) => t("Edit BCN parameters"),
- render: Edit,
- },
-});
+ label: ({ t }) => t('Edit'),
+ title: ({ t }) => t('Edit BCN parameters'),
+ render: Edit
+ }
+})
/*
Combine BcnData backend with Chart
*/
class DataHandler extends React.Component {
state = {
- bcnData: null,
- };
+ bcnData: null
+ }
- constructor(props) {
- super(props);
+ constructor (props) {
+ super(props)
// Set initial state for meta info
this.state = {
...this.state,
- ...this.getMeta(this.props.dataset, this.state.bcnData),
- };
+ ...this.getMeta(this.props.dataset, this.state.bcnData)
+ }
}
defaultMeta = {
- title: "...",
- name: "...",
+ title: '...',
+ name: '...',
theme: null,
yAxis: null,
- source: null,
- };
+ source: null
+ }
- setBcnData = (bcnData) => this.setState({ bcnData });
+ setBcnData = (bcnData) => this.setState({ bcnData })
// Get metadata from given params
getMeta = (dataset, bcnData) => {
if (!bcnData) {
- return this.defaultMeta;
+ return this.defaultMeta
}
- const found = this.props.bcnDataHandler.findChild(null, dataset);
+ const found = this.props.bcnDataHandler.findChild(null, dataset)
if (!found) {
- return this.defaultMeta;
+ return this.defaultMeta
}
- const { title, description, theme, yAxis, source } = found;
+ const { title, description, theme, yAxis, source } = found
return {
title,
name: title,
description,
theme,
yAxis,
- source,
- };
- };
+ source
+ }
+ }
// Update metadata
updateData = () => {
const {
state: { bcnData },
- props: { dataset },
- } = this;
+ props: { dataset }
+ } = this
this.setState({
- ...this.getMeta(dataset, bcnData),
- });
- };
+ ...this.getMeta(dataset, bcnData)
+ })
+ }
- componentDidUpdate(prevProps, prevState) {
+ componentDidUpdate (prevProps, prevState) {
const {
state: { bcnData },
- props: { dataset },
- } = this;
+ props: { dataset }
+ } = this
if (dataset !== prevProps.dataset || bcnData !== prevState.bcnData) {
- this.updateData();
+ this.updateData()
}
}
// Update params in parents
onChange = (dataset) => {
this.props.onChangeData(this.props.id, {
- dataset,
- });
- };
+ dataset
+ })
+ }
// Datasets are on same data object
onChangeDataset = (dataset) => {
- this.onChange(dataset);
- };
+ this.onChange(dataset)
+ }
- render() {
+ render () {
const {
state: {
// Meta
@@ -141,20 +141,20 @@ class DataHandler extends React.Component {
description,
theme,
yAxis,
- source,
+ source
},
props: { days, indexValues, id, dataset, onRemove },
onChangeDataset,
- setBcnData,
- } = this;
+ setBcnData
+ } = this
return (
- );
+ )
}
}
DataHandler.defaultProps = {
- dataset: "IND_DEF_OBS_CAT",
+ dataset: 'IND_DEF_OBS_CAT',
onChangeData: () => {},
- onRemove: () => {},
-};
+ onRemove: () => {}
+}
DataHandler.propTypes = {
days: PropTypes.arrayOf(PropTypes.string).isRequired,
@@ -195,7 +195,7 @@ DataHandler.propTypes = {
id: PropTypes.string.isRequired,
onChangeData: PropTypes.func.isRequired,
onRemove: PropTypes.func.isRequired,
- dataset: PropTypes.string.isRequired,
-};
+ dataset: PropTypes.string.isRequired
+}
-export default withHandler(DataHandler);
+export default withHandler(DataHandler)
diff --git a/app/src/Widget/Widgets/Chart/Edit.jsx b/app/src/Widget/Widgets/Chart/Edit.jsx
index 5c2e9e12..e8893fc8 100644
--- a/app/src/Widget/Widgets/Chart/Edit.jsx
+++ b/app/src/Widget/Widgets/Chart/Edit.jsx
@@ -1,40 +1,40 @@
-import React from "react";
-import PropTypes from "prop-types";
+import React from 'react'
+import PropTypes from 'prop-types'
-import { translate } from "react-translate";
+import { translate } from 'react-translate'
-import { makeStyles } from "@material-ui/core/styles";
-import InputLabel from "@material-ui/core/InputLabel";
-import FormHelperText from "@material-ui/core/FormHelperText";
-import FormControl from "@material-ui/core/FormControl";
-import Select from "@material-ui/core/Select";
-import Divider from "@material-ui/core/Divider";
+import { makeStyles } from '@material-ui/core/styles'
+import InputLabel from '@material-ui/core/InputLabel'
+import FormHelperText from '@material-ui/core/FormHelperText'
+import FormControl from '@material-ui/core/FormControl'
+import Select from '@material-ui/core/Select'
+import Divider from '@material-ui/core/Divider'
-import ChartRegionSelector from "./EditRegion";
+import ChartRegionSelector from './EditRegion'
// UI-Material Styles
const useStyles = makeStyles((theme) => ({
formControl: {
- minWidth: 120,
+ minWidth: 120
},
selectEmpty: {
- marginTop: theme.spacing(2),
+ marginTop: theme.spacing(2)
},
labelFixed: {
- position: "initial",
- transform: "unset",
- marginBottom: theme.spacing(2),
- },
-}));
+ position: 'initial',
+ transform: 'unset',
+ marginBottom: theme.spacing(2)
+ }
+}))
const FormDecorators = (props) => {
- const classes = useStyles();
- const { label, help, id, children, fixTree } = props;
+ const classes = useStyles()
+ const { label, help, id, children, fixTree } = props
return (
@@ -45,17 +45,19 @@ const FormDecorators = (props) => {
{label}
{children}
- {help ? (
- {help}
- ) : null}
+ {help
+ ? (
+ {help}
+ )
+ : null}
- );
-};
+ )
+}
// Renders a Select, with options from props
const Selector = (props) => {
- const { label, help, id, options, ...restProps } = props;
+ const { label, help, id, options, ...restProps } = props
return (
{
aria-describedby={`helper-text-${id}`}
inputProps={{
name: label,
- id: id,
+ id
}}
>
{options.map(({ value, label }) => (
@@ -74,78 +76,81 @@ const Selector = (props) => {
))}
- );
-};
+ )
+}
// Renders a Select with chart division options
-const ChartDivisionSelector = translate("Widget/Chart/Edit")((props) => {
- const { t, divisions, ...restProps } = props;
+const ChartDivisionSelector = translate('Widget/Chart/Edit')((props) => {
+ const { t, divisions, ...restProps } = props
const ChartDivisionSelectorOptions = React.useMemo(
() => divisions.map((kind) => ({ value: kind, label: kind })),
[divisions]
- );
+ )
return (
- );
-});
+ )
+})
// Renders a Select with chart population options
-const ChartPopulationSelector = translate("Widget/Chart/Edit")((props) => {
- const { t, populations, ...restProps } = props;
+const ChartPopulationSelector = translate('Widget/Chart/Edit')((props) => {
+ const { t, populations, ...restProps } = props
const ChartPopulationSelectorOptions = React.useMemo(
() => populations.map((kind) => ({ value: kind, label: kind })),
[populations]
- );
+ )
return (
- );
-});
+ )
+})
// Renders a Select with chart population options
-const ChartDatasetSelector = translate("Widget/Chart/Edit")((props) => {
- const { t, ...restProps } = props;
+const ChartDatasetSelector = translate('Widget/Chart/Edit')((props) => {
+ const { t, ...restProps } = props
const ChartDatasetSelectorOptions = React.useMemo(
() => [
- { value: "grafic_extensio", label: t("Extensió") },
- { value: "grafic_risc_iepg", label: t("Risc iEPG") },
- { value: "seguiment", label: t("Tracking") },
- { value: "situacio", label: t("Situation") },
+ { value: 'grafic_extensio', label: t('Extensió') },
+ { value: 'grafic_risc_iepg', label: t('Risc iEPG') },
+ { value: 'seguiment', label: t('Tracking') },
+ { value: 'situacio', label: t('Situation') }
],
[t]
- );
+ )
return (
- );
-});
+ )
+})
/*
Renders the edit form for the Map widget
*/
class Edit extends React.PureComponent {
onChangeChartDivision = (event) =>
- this.props.onChangeChartDivision(event.target.value);
+ this.props.onChangeChartDivision(event.target.value)
+
onChangeChartPopulation = (event) =>
- this.props.onChangeChartPopulation(event.target.value);
+ this.props.onChangeChartPopulation(event.target.value)
+
onChangeChartDataset = (event) =>
- this.props.onChangeChartDataset(event.target.value);
- onChangeChartRegion = (value) => this.props.onChangeChartRegion(value);
+ this.props.onChangeChartDataset(event.target.value)
- render() {
+ onChangeChartRegion = (value) => this.props.onChangeChartRegion(value)
+
+ render () {
const {
chartDivision,
chartPopulation,
@@ -154,32 +159,32 @@ class Edit extends React.PureComponent {
divisions,
populations,
chartsIndex,
- t,
- } = this.props;
+ t
+ } = this.props
return (
-
+
- );
+ )
}
}
@@ -201,7 +206,7 @@ Edit.propTypes = {
onChangeChartDivision: PropTypes.func.isRequired,
onChangeChartPopulation: PropTypes.func.isRequired,
onChangeChartDataset: PropTypes.func.isRequired,
- onChangeChartRegion: PropTypes.func.isRequired,
-};
+ onChangeChartRegion: PropTypes.func.isRequired
+}
-export default translate("Widget/Chart/Edit")(Edit);
+export default translate('Widget/Chart/Edit')(Edit)
diff --git a/app/src/Widget/Widgets/Chart/EditRegion/EditRegion.jsx b/app/src/Widget/Widgets/Chart/EditRegion/EditRegion.jsx
index 805cca28..a0db6a7e 100644
--- a/app/src/Widget/Widgets/Chart/EditRegion/EditRegion.jsx
+++ b/app/src/Widget/Widgets/Chart/EditRegion/EditRegion.jsx
@@ -1,11 +1,11 @@
-import React from "react";
-import PropTypes from "prop-types";
+import React from 'react'
+import PropTypes from 'prop-types'
-import { withHandler } from "../../../../Backend/Charts/context";
+import { withHandler } from '../../../../Backend/Charts/context'
-import RecursiveTreeView from "./RecursiveTreeView";
+import RecursiveTreeView from './RecursiveTreeView'
-function EditRegion(props) {
+function EditRegion (props) {
const {
// Remove it from restProps: it comes from
// Charts handler, not parent component
@@ -17,20 +17,20 @@ function EditRegion(props) {
onChange,
chartsDataHandler,
...restProps
- } = props;
+ } = props
- const onNodeSelect = (event, v) => onChange(Number(v));
+ const onNodeSelect = (event, v) => onChange(Number(v))
const initialNode = React.useMemo(
() => chartsDataHandler.findInitialNode(division, population),
[chartsDataHandler, division, population]
- );
+ )
const found = React.useMemo(
() =>
chartsDataHandler
.findBreadcrumb(initialNode, value)
.map(({ url }) => `${url}`),
[initialNode, value, chartsDataHandler]
- );
+ )
return (
- );
+ )
}
const EditRegionPropTypes = {
@@ -50,14 +50,14 @@ const EditRegionPropTypes = {
onChange: PropTypes.func.isRequired,
chartsDataHandler: PropTypes.shape({
findInitialNode: PropTypes.func.isRequired,
- findBreadcrumb: PropTypes.func.isRequired,
+ findBreadcrumb: PropTypes.func.isRequired
}).isRequired,
- chartsIndex: PropTypes.string,
-};
+ chartsIndex: PropTypes.string
+}
-EditRegion.propTypes = EditRegionPropTypes;
+EditRegion.propTypes = EditRegionPropTypes
EditRegion.defaultProps = {
- chartsIndex: "",
-};
+ chartsIndex: ''
+}
-export default withHandler(EditRegion);
+export default withHandler(EditRegion)
diff --git a/app/src/Widget/Widgets/Chart/EditRegion/RecursiveTreeView.jsx b/app/src/Widget/Widgets/Chart/EditRegion/RecursiveTreeView.jsx
index 3fb2ec7f..44ff5663 100644
--- a/app/src/Widget/Widgets/Chart/EditRegion/RecursiveTreeView.jsx
+++ b/app/src/Widget/Widgets/Chart/EditRegion/RecursiveTreeView.jsx
@@ -1,23 +1,23 @@
-import React from "react";
-import PropTypes from "prop-types";
+import React from 'react'
+import PropTypes from 'prop-types'
-import TreeView from "@material-ui/lab/TreeView";
-import { makeStyles } from "@material-ui/core/styles";
-import ExpandMoreIcon from "@material-ui/icons/ExpandMore";
-import ChevronRightIcon from "@material-ui/icons/ChevronRight";
+import TreeView from '@material-ui/lab/TreeView'
+import { makeStyles } from '@material-ui/core/styles'
+import ExpandMoreIcon from '@material-ui/icons/ExpandMore'
+import ChevronRightIcon from '@material-ui/icons/ChevronRight'
-import RenderTree, { RenderTreePropTypesShape } from "./RenderTree";
+import RenderTree, { RenderTreePropTypesShape } from './RenderTree'
const useStyles = makeStyles({
root: {
height: 110,
flexGrow: 1,
- maxWidth: 400,
- },
-});
+ maxWidth: 400
+ }
+})
-function RecursiveTreeView({ value, found, node, onNodeSelect, ...props }) {
- const classes = useStyles();
+function RecursiveTreeView ({ value, found, node, onNodeSelect, ...props }) {
+ const classes = useStyles()
return (
- );
+ )
}
const RecursiveTreeViewPropTypes = {
value: PropTypes.string.isRequired,
found: PropTypes.arrayOf(PropTypes.string).isRequired,
node: RenderTreePropTypesShape.isRequired,
- onNodeSelect: PropTypes.func.isRequired,
-};
+ onNodeSelect: PropTypes.func.isRequired
+}
-RecursiveTreeView.propTypes = RecursiveTreeViewPropTypes;
+RecursiveTreeView.propTypes = RecursiveTreeViewPropTypes
-export default RecursiveTreeView;
-export { RecursiveTreeViewPropTypes };
+export default RecursiveTreeView
+export { RecursiveTreeViewPropTypes }
diff --git a/app/src/Widget/Widgets/Chart/EditRegion/RenderTree.jsx b/app/src/Widget/Widgets/Chart/EditRegion/RenderTree.jsx
index 543278ea..1de30e6b 100644
--- a/app/src/Widget/Widgets/Chart/EditRegion/RenderTree.jsx
+++ b/app/src/Widget/Widgets/Chart/EditRegion/RenderTree.jsx
@@ -1,11 +1,11 @@
-import React from "react";
-import PropTypes from "prop-types";
+import React from 'react'
+import PropTypes from 'prop-types'
-import TreeItem from "@material-ui/lab/TreeItem";
+import TreeItem from '@material-ui/lab/TreeItem'
// From: https://material-ui.com/components/tree-view/#rich-object
-function RenderTree({ url, name, children = [] }) {
+function RenderTree ({ url, name, children = [] }) {
return (
{children.map((child) => (
@@ -14,29 +14,29 @@ function RenderTree({ url, name, children = [] }) {
))}
- );
+ )
}
// Recursive PropTypes
const RenderTreePropTypes = {
name: PropTypes.string.isRequired,
- url: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
-};
+ url: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired
+}
-const RenderTreePropTypesShape = PropTypes.shape(RenderTreePropTypes);
+const RenderTreePropTypesShape = PropTypes.shape(RenderTreePropTypes)
RenderTreePropTypes.children = PropTypes.oneOfType([
PropTypes.arrayOf(RenderTreePropTypesShape),
- PropTypes.any,
-]);
+ PropTypes.any
+])
-RenderTree.propTypes = RenderTreePropTypes;
+RenderTree.propTypes = RenderTreePropTypes
RenderTree.defaultProps = {
- children: [],
-};
+ children: []
+}
// Cache the tree
-const RenderTreeMemoized = React.memo(RenderTree);
+const RenderTreeMemoized = React.memo(RenderTree)
-export default RenderTreeMemoized;
-export { RenderTreePropTypes, RenderTreePropTypesShape };
+export default RenderTreeMemoized
+export { RenderTreePropTypes, RenderTreePropTypesShape }
diff --git a/app/src/Widget/Widgets/Chart/EditRegion/index.js b/app/src/Widget/Widgets/Chart/EditRegion/index.js
index 2c4c65c6..4c5ae330 100644
--- a/app/src/Widget/Widgets/Chart/EditRegion/index.js
+++ b/app/src/Widget/Widgets/Chart/EditRegion/index.js
@@ -1,3 +1,3 @@
-import EditRegion from "./EditRegion";
+import EditRegion from './EditRegion'
-export default EditRegion;
+export default EditRegion
diff --git a/app/src/Widget/Widgets/Chart/Widget.jsx b/app/src/Widget/Widgets/Chart/Widget.jsx
index 8d32b409..78de80a3 100644
--- a/app/src/Widget/Widgets/Chart/Widget.jsx
+++ b/app/src/Widget/Widgets/Chart/Widget.jsx
@@ -1,23 +1,23 @@
-import React from "react";
-import PropTypes from "prop-types";
+import React from 'react'
+import PropTypes from 'prop-types'
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import {
faEdit,
- faChartArea as faChart,
-} from "@fortawesome/free-solid-svg-icons";
+ faChartArea as faChart
+} from '@fortawesome/free-solid-svg-icons'
-import { withHandler, withData } from "../../../Backend/Charts/context";
+import { withHandler, withData } from '../../../Backend/Charts/context'
-import Chart from "./MultiChart";
-import Edit from "./Edit";
-import withWidget from "../../Widget";
+import Chart from './MultiChart'
+import Edit from './Edit'
+import withWidget from '../../Widget'
const ChartWrapper = withWidget({
// The normal view
view: {
icon:
,
- label: ({ t }) => t("View"),
+ label: ({ t }) => t('View'),
title: ({ title }) => title,
// withData: Uses {chartPopulation, chartDivision, chartRegion} props to handle `chartDataset` download
@@ -33,12 +33,12 @@ const ChartWrapper = withWidget({
// Used
setChartData,
indexValues,
- id,
- } = props;
+ id
+ } = props
// Once downloaded, set parent's data, so it can properly
// set title and other widget components
- React.useEffect(() => setChartData(valors), [valors, setChartData]);
+ React.useEffect(() => setChartData(valors), [valors, setChartData])
return (
- );
- }),
+ )
+ })
},
// Edit data
edit: {
icon:
,
- label: ({ t }) => t("Edit"),
- title: ({ t }) => t("Edit chart parameters"),
- render: Edit,
- },
-});
+ label: ({ t }) => t('Edit'),
+ title: ({ t }) => t('Edit chart parameters'),
+ render: Edit
+ }
+})
/*
Combine ChartData backend with Chart
*/
class DataHandler extends React.Component {
state = {
- chartData: false,
- };
+ chartData: false
+ }
- constructor(props) {
- super(props);
+ constructor (props) {
+ super(props)
// Default values: first element of each's group
- const { chartDivision, chartPopulation, chartRegion, chartDataset } = props;
- const { chartData } = this.state;
+ const { chartDivision, chartPopulation, chartRegion, chartDataset } = props
+ const { chartData } = this.state
// Used to find metadata from selected options
// and for the rest of options while editing
@@ -90,8 +90,8 @@ class DataHandler extends React.Component {
chartRegion,
chartDataset,
chartData
- ),
- };
+ )
+ }
}
// Get chartData from child
@@ -100,9 +100,9 @@ class DataHandler extends React.Component {
// - Allows continue editing once a value has changed
setChartData = (chartData) => {
if (chartData !== this.state.chartData) {
- this.setState({ chartData });
+ this.setState({ chartData })
}
- };
+ }
// Get metadata from given params
getMeta = (
@@ -117,30 +117,30 @@ class DataHandler extends React.Component {
chartDivision,
chartPopulation,
chartRegion
- );
+ )
if (!chartData) {
return {
- title: "...",
- name: "...",
- node,
- };
+ title: '...',
+ name: '...',
+ node
+ }
}
const title =
- chartData[chartDataset]?.title || chartData[chartDataset]?.name;
+ chartData[chartDataset]?.title || chartData[chartDataset]?.name
return {
title,
name: title,
- node,
- };
- };
+ node
+ }
+ }
// Set map meta data
updateMetadata = () => {
const { chartDivision, chartPopulation, chartRegion, chartDataset } =
- this.props;
- const { chartData } = this.state;
+ this.props
+ const { chartData } = this.state
this.setState({
...this.getMeta(
@@ -149,14 +149,14 @@ class DataHandler extends React.Component {
chartRegion,
chartDataset,
chartData
- ),
- });
- };
+ )
+ })
+ }
- componentDidUpdate(prevProps, prevState) {
+ componentDidUpdate (prevProps, prevState) {
const { chartDivision, chartPopulation, chartRegion, chartDataset } =
- this.props;
- const { chartData } = this.state;
+ this.props
+ const { chartData } = this.state
// If chartData is unset, gather new data
if (
@@ -167,7 +167,7 @@ class DataHandler extends React.Component {
chartDataset !== prevProps.chartDataset ||
chartData !== prevState.chartData
) {
- this.updateMetadata();
+ this.updateMetadata()
}
}
@@ -179,66 +179,66 @@ class DataHandler extends React.Component {
chartRegion,
chartDataset
) => {
- this.updateMetadata();
- const { chartsDataHandler, onChangeData, id } = this.props;
+ this.updateMetadata()
+ const { chartsDataHandler, onChangeData, id } = this.props
const region = chartsDataHandler.findRegion(
chartDivision,
chartPopulation,
chartRegion
- );
+ )
onChangeData(id, {
chartDivision,
chartPopulation,
chartDataset,
- chartRegion: region.url,
- });
- };
+ chartRegion: region.url
+ })
+ }
// Force data update on division change
onChangeChartDivision = (chartDivision) => {
- const { chartPopulation, chartRegion, chartDataset } = this.props;
+ const { chartPopulation, chartRegion, chartDataset } = this.props
this.onChangeChart(
chartDivision,
chartPopulation,
chartRegion,
chartDataset
- );
- };
+ )
+ }
// Force data update on value change
onChangeChartPopulation = (chartPopulation) => {
- const { chartDivision, chartRegion, chartDataset } = this.props;
+ const { chartDivision, chartRegion, chartDataset } = this.props
this.onChangeChart(
chartDivision,
chartPopulation,
chartRegion,
chartDataset
- );
- };
+ )
+ }
// Force data update on value change
onChangeChartRegion = (chartRegion) => {
- const { chartDivision, chartPopulation, chartDataset } = this.props;
+ const { chartDivision, chartPopulation, chartDataset } = this.props
this.onChangeChart(
chartDivision,
chartPopulation,
chartRegion,
chartDataset
- );
- };
+ )
+ }
// Datasets are on same data object
onChangeChartDataset = (chartDataset) => {
- const { chartDivision, chartPopulation, chartRegion } = this.props;
+ const { chartDivision, chartPopulation, chartRegion } = this.props
this.onChangeChart(
chartDivision,
chartPopulation,
chartRegion,
chartDataset
- );
- };
+ )
+ }
- render() {
+ render () {
const {
id,
days,
@@ -248,17 +248,17 @@ class DataHandler extends React.Component {
chartDataset,
chartRegion,
chartsDataHandler: { divisions, populations },
- onRemove,
- } = this.props;
- const { title, name, node } = this.state;
+ onRemove
+ } = this.props
+ const { title, name, node } = this.state
return (
- );
+ )
}
}
// Default values: first element of each's group
-const chartDivision = "REGIÓ/AGA";
-const chartPopulation = "Població total";
-const chartRegion = 0;
-const chartDataset = "grafic_extensio";
+const chartDivision = 'REGIÓ/AGA'
+const chartPopulation = 'Població total'
+const chartRegion = 0
+const chartDataset = 'grafic_extensio'
DataHandler.defaultProps = {
chartDivision,
@@ -301,8 +301,8 @@ DataHandler.defaultProps = {
chartRegion,
chartDataset,
onChangeData: () => {},
- onRemove: () => {},
-};
+ onRemove: () => {}
+}
DataHandler.propTypes = {
days: PropTypes.arrayOf(PropTypes.string).isRequired,
@@ -314,7 +314,7 @@ DataHandler.propTypes = {
chartPopulation: PropTypes.string.isRequired,
chartDataset: PropTypes.string.isRequired,
chartRegion: PropTypes.oneOfType([PropTypes.string, PropTypes.number])
- .isRequired,
-};
+ .isRequired
+}
-export default withHandler(DataHandler);
+export default withHandler(DataHandler)
diff --git a/app/src/Widget/Widgets/Common/Chart.jsx b/app/src/Widget/Widgets/Common/Chart.jsx
index 0297282b..8e395c92 100644
--- a/app/src/Widget/Widgets/Common/Chart.jsx
+++ b/app/src/Widget/Widgets/Common/Chart.jsx
@@ -286,8 +286,8 @@ function Chart (props) {
const yAxisLabel = yAxis?.label || ''
const scale = yAxis?.scale
? yAxis.scale === 'squarified'
- ? 'pow'
- : yAxis.scale
+ ? 'pow'
+ : yAxis.scale
: 'linear'
const { ResponsiveContainer, ComposedChart, ReferenceLine, XAxis, YAxis } =
diff --git a/app/src/Widget/Widgets/Common/ChartTooltip.jsx b/app/src/Widget/Widgets/Common/ChartTooltip.jsx
index b0f72829..c09ee9fc 100644
--- a/app/src/Widget/Widgets/Common/ChartTooltip.jsx
+++ b/app/src/Widget/Widgets/Common/ChartTooltip.jsx
@@ -133,12 +133,14 @@ ChartLegend.propTypes = {
const ChartTooltip = ({ active, payload, label }) => {
const classes = useStyles()
- return active ? (
-
- ) : null
+ return active
+ ? (
+
+ )
+ : null
}
ChartTooltip.defaultProps = {
diff --git a/app/src/Widget/Widgets/Common/FormDecorators.jsx b/app/src/Widget/Widgets/Common/FormDecorators.jsx
index 9074df0c..1d510e7e 100644
--- a/app/src/Widget/Widgets/Common/FormDecorators.jsx
+++ b/app/src/Widget/Widgets/Common/FormDecorators.jsx
@@ -40,9 +40,11 @@ const FormDecorators = (props) => {
{label}
{children}
- {help ? (
-
{help}
- ) : null}
+ {help
+ ? (
+
{help}
+ )
+ : null}
)
diff --git a/app/src/Widget/Widgets/Common/Selector.jsx b/app/src/Widget/Widgets/Common/Selector.jsx
index a97f0b93..d79f464d 100644
--- a/app/src/Widget/Widgets/Common/Selector.jsx
+++ b/app/src/Widget/Widgets/Common/Selector.jsx
@@ -16,7 +16,7 @@ const Selector = (props) => {
aria-describedby={`helper-text-${id}`}
inputProps={{
name: label,
- id: id
+ id
}}
>
{options.map((option) => (
diff --git a/app/src/Widget/Widgets/Map/Edit.jsx b/app/src/Widget/Widgets/Map/Edit.jsx
index 89613987..fa6bd2ed 100644
--- a/app/src/Widget/Widgets/Map/Edit.jsx
+++ b/app/src/Widget/Widgets/Map/Edit.jsx
@@ -1,36 +1,36 @@
-import React from "react";
-import PropTypes from "prop-types";
+import React from 'react'
+import PropTypes from 'prop-types'
-import { translate } from "react-translate";
+import { translate } from 'react-translate'
-import { makeStyles } from "@material-ui/core/styles";
-import InputLabel from "@material-ui/core/InputLabel";
-import FormHelperText from "@material-ui/core/FormHelperText";
-import FormControl from "@material-ui/core/FormControl";
-import Select from "@material-ui/core/Select";
-import Divider from "@material-ui/core/Divider";
+import { makeStyles } from '@material-ui/core/styles'
+import InputLabel from '@material-ui/core/InputLabel'
+import FormHelperText from '@material-ui/core/FormHelperText'
+import FormControl from '@material-ui/core/FormControl'
+import Select from '@material-ui/core/Select'
+import Divider from '@material-ui/core/Divider'
-import MapData from "../../../Backend/Maps";
+import MapData from '../../../Backend/Maps'
// UI-Material Styles
const useStyles = makeStyles((theme) => ({
formControl: {
- minWidth: 120,
+ minWidth: 120
},
selectEmpty: {
- marginTop: theme.spacing(2),
- },
-}));
+ marginTop: theme.spacing(2)
+ }
+}))
// Renders a Select, with options from props
const Selector = (props) => {
- const classes = useStyles();
- const { label, help, id, options, ...restProps } = props;
+ const classes = useStyles()
+ const { label, help, id, options, ...restProps } = props
return (
@@ -41,7 +41,7 @@ const Selector = (props) => {
aria-describedby={`helper-text-${id}`}
inputProps={{
name: label,
- id: id,
+ id
}}
>
{options.map(({ value, label }) => (
@@ -50,72 +50,74 @@ const Selector = (props) => {
))}
- {help ? (
- {help}
- ) : null}
+ {help
+ ? (
+ {help}
+ )
+ : null}
- );
-};
+ )
+}
// Renders a Select with map kinds options
const MapSelectorOptions = MapData.kinds().map((kind) => ({
value: kind,
- label: kind,
-}));
-const MapSelector = translate("Widget/Map/Edit")((props) => {
- const { t, ...restProps } = props;
+ label: kind
+}))
+const MapSelector = translate('Widget/Map/Edit')((props) => {
+ const { t, ...restProps } = props
return (
- );
-});
+ )
+})
// Renders a Select with map values options
const MapValueSelectorOptions = MapData.kinds().reduce((options, kind) => {
options[kind] = MapData.values(kind).map((values) => ({
value: values,
- label: MapData.metaName(values),
- }));
- return options;
-}, {});
-const MapValueSelector = translate("Widget/Map/Edit")((props) => {
- const { kind, t, ...restProps } = props;
+ label: MapData.metaName(values)
+ }))
+ return options
+}, {})
+const MapValueSelector = translate('Widget/Map/Edit')((props) => {
+ const { kind, t, ...restProps } = props
return (
- );
-});
+ )
+})
/*
Renders the edit form for the Map widget
*/
class Edit extends React.PureComponent {
- onChangeMapKind = (event) => this.props.onChangeMapKind(event.target.value);
- onChangeMapValue = (event) => this.props.onChangeMapValue(event.target.value);
+ onChangeMapKind = (event) => this.props.onChangeMapKind(event.target.value)
+ onChangeMapValue = (event) => this.props.onChangeMapValue(event.target.value)
- render() {
- const { mapKind, mapValue } = this.props;
+ render () {
+ const { mapKind, mapValue } = this.props
return (
-
+
- );
+ )
}
}
@@ -123,7 +125,7 @@ Edit.propTypes = {
mapKind: PropTypes.string.isRequired,
mapValue: PropTypes.string.isRequired,
onChangeMapKind: PropTypes.func.isRequired,
- onChangeMapValue: PropTypes.func.isRequired,
-};
+ onChangeMapValue: PropTypes.func.isRequired
+}
-export default Edit;
+export default Edit
diff --git a/app/src/Widget/Widgets/Map/Legend.jsx b/app/src/Widget/Widgets/Map/Legend.jsx
index bed9b7dd..48c80caf 100644
--- a/app/src/Widget/Widgets/Map/Legend.jsx
+++ b/app/src/Widget/Widgets/Map/Legend.jsx
@@ -38,13 +38,15 @@ Element.propTypes = {
// Renders children conditionally wrapped with a tooltip,
// depending on the existence and length of `title` prop
const ConditionalTooltip = ({ title, children, ...rest }) =>
- title && title.length ? (
-
- {children}
-
- ) : (
- children
- )
+ title && title.length
+ ? (
+
+ {children}
+
+ )
+ : (
+ children
+ )
ConditionalTooltip.defaultProps = {
children: []
diff --git a/app/src/Widget/Widgets/Map/Map.css b/app/src/Widget/Widgets/Map/Map.css
index ebfd13d9..855e0a32 100644
--- a/app/src/Widget/Widgets/Map/Map.css
+++ b/app/src/Widget/Widgets/Map/Map.css
@@ -5,7 +5,10 @@
stroke-linejoin: round;
transform-box: fill-box;
transform-origin: center;
- transition: transform 0.1s, stroke-width 0.2s, stroke 0.1s;
+ transition:
+ transform 0.1s,
+ stroke-width 0.2s,
+ stroke 0.1s;
paint-order: stroke;
}
diff --git a/app/src/Widget/Widgets/Map/MapImage.jsx b/app/src/Widget/Widgets/Map/MapImage.jsx
index 4bcd39ce..f9fdd087 100644
--- a/app/src/Widget/Widgets/Map/MapImage.jsx
+++ b/app/src/Widget/Widgets/Map/MapImage.jsx
@@ -1,51 +1,51 @@
-import React from "react";
-import PropTypes from "prop-types";
+import React from 'react'
+import PropTypes from 'prop-types'
-import { styled } from "@material-ui/core/styles";
+import { styled } from '@material-ui/core/styles'
-import { ReactSVG } from "react-svg";
-import ReactTooltip from "react-tooltip";
+import { ReactSVG } from 'react-svg'
+import ReactTooltip from 'react-tooltip'
-import Loading from "../../../Loading";
-import LegendElement from "../Common/LegendElement";
+import Loading from '../../../Loading'
+import LegendElement from '../Common/LegendElement'
-import "./Map.css";
+import './Map.css'
// Renders ReactTooltip with MaterialUI theme styles
const ReactTooltipStyled = styled(ReactTooltip)(({ theme }) => ({
backgroundColor: `${theme.palette.background.default} !important`,
color: `${theme.palette.text.primary} !important`,
- opacity: "1 !important",
+ opacity: '1 !important',
borderColor: `${theme.palette.text.hint} !important`,
...theme.shape,
fontFamily: theme.typography.fontFamily,
fontSize: theme.typography.fontSize,
- "&:before": {
- borderTopColor: `${theme.palette.text.hint} !important`,
+ '&:before': {
+ borderTopColor: `${theme.palette.text.hint} !important`
},
- "&:after": {
- borderTopColor: `${theme.palette.background.default} !important`,
- },
-}));
+ '&:after': {
+ borderTopColor: `${theme.palette.background.default} !important`
+ }
+}))
// Helper functions for ReactTooltip
-const fallback = () =>
Error! ; /* Should never happen */
-const loading = () =>
;
+const fallback = () =>
Error! /* Should never happen */
+const loading = () =>
const TipContent = React.memo(
({ id, valuesDay, label, element, colorGetter }) => {
- const valueRaw = valuesDay.get(id);
- const color = colorGetter(valueRaw);
- const value = `${label}: ${valueRaw ?? ""}`;
- const title = element.getAttribute("label");
+ const valueRaw = valuesDay.get(id)
+ const color = colorGetter(valueRaw)
+ const value = `${label}: ${valueRaw ?? ''}`
+ const title = element.getAttribute('label')
return (
<>
{title}
-
+
>
- );
+ )
}
-);
+)
/*
Renders an SVG map, allowing to change each region background color and tooltip
@@ -57,116 +57,116 @@ const TipContent = React.memo(
Some of the code here has been inspired in the original from https://dadescovid.org
*/
class MapImage extends React.Component {
- svg = null;
+ svg = null
state = {
// Used to force update based on side effects
- svgStatus: null,
- };
+ svgStatus: null
+ }
// Save SVG DOM node reference
// This way we can access it's regions to modify them
- wrapperNodeSVG = React.createRef();
+ wrapperNodeSVG = React.createRef()
// Get the SVG node itself
isSVGMounted = () => {
return (
- this?.wrapperNodeSVG?.current?.container?.querySelector(`svg`) !== null &&
+ this?.wrapperNodeSVG?.current?.container?.querySelector('svg') !== null &&
this.svg !== null
- );
- };
+ )
+ }
// Gets the color for a given value (in func params) in a colors table (in props)
getColor = (value) => {
- const color = this.props.colors.find((c) => value >= c.value);
- return color ? color.color : "fff";
- };
+ const color = this.props.colors.find((c) => value >= c.value)
+ return color ? color.color : 'fff'
+ }
// Sets the background color in a SVG region for a given value
setColorBackground = (element, value) => {
- element.style.fill = this.getColor(value);
- };
+ element.style.fill = this.getColor(value)
+ }
// Sets the background color for all regions in values prop
fillColors = (props) => {
if (this.timer) {
- return;
+ return
}
this.timer = requestAnimationFrame(() => {
- const values = props.values[props.indexValues] || {};
- const { mapElements } = this;
+ const values = props.values[props.indexValues] || {}
+ const { mapElements } = this
for (const [id, value] of values.entries()) {
- const element = mapElements.get(id);
+ const element = mapElements.get(id)
if (element !== undefined) {
- this.setColorBackground(element, value);
+ this.setColorBackground(element, value)
}
}
- this.timer = false;
- }, 0);
- };
+ this.timer = false
+ }, 0)
+ }
// Sets data into a map region to be used by the tooltip renderer)
setTooltipData = (element) => {
- const dataFor = `mapa-${this.props.id}`;
- element.setAttribute("data-tip", element.id);
- element.setAttribute("data-for", dataFor);
- };
+ const dataFor = `mapa-${this.props.id}`
+ element.setAttribute('data-tip', element.id)
+ element.setAttribute('data-for', dataFor)
+ }
// Save an Map of the map elements, indexed by their `id`
saveElementsIndex = (svg) => {
this.mapElements = new Map(
Array.prototype.map.call(
- this.svg.querySelectorAll(`.map_elem`),
+ this.svg.querySelectorAll('.map_elem'),
(current) => {
- this.setTooltipData(current);
- return [current.id, current];
+ this.setTooltipData(current)
+ return [current.id, current]
}
)
- );
- };
+ )
+ }
// Process data into the SVG DOM node before first injecting it into the DOM tree
beforeInjection = (svg) => {
- this.svg = svg;
- this.saveElementsIndex();
- this.setState({ svgStatus: "beforeInjection" });
- };
+ this.svg = svg
+ this.saveElementsIndex()
+ this.setState({ svgStatus: 'beforeInjection' })
+ }
// Get SVG DOM node and reset colors from data if needed
afterInjection = (error, svg) => {
if (error) {
- throw Error(error);
+ throw Error(error)
}
// Call ReactTooltip to rebuild its database with new objects
- ReactTooltip.rebuild();
- this.svg = svg;
- this.fillColors(this.props);
- this.setState({ svgStatus: "afterInjection" });
- };
+ ReactTooltip.rebuild()
+ this.svg = svg
+ this.fillColors(this.props)
+ this.setState({ svgStatus: 'afterInjection' })
+ }
// Update background colors if SVG is mounted
updateColorsIfPossible = (props) => {
if (this.isSVGMounted()) {
- this.fillColors(props);
+ this.fillColors(props)
}
- };
+ }
- componentDidUpdate(prevProps, prevState) {
+ componentDidUpdate (prevProps, prevState) {
if (this.props.mapSrc !== prevProps.mapSrc) {
- this.setState({ svgStatus: "srcChanged" });
+ this.setState({ svgStatus: 'srcChanged' })
}
}
- componentDidMount() {
- this.updateColorsIfPossible(this.props);
- this.setState({ svgStatus: "mounted" });
+ componentDidMount () {
+ this.updateColorsIfPossible(this.props)
+ this.setState({ svgStatus: 'mounted' })
}
// Help GC about side effects
- componentWillUnmount() {
+ componentWillUnmount () {
if (this.timer) {
- cancelAnimationFrame(this.timer);
+ cancelAnimationFrame(this.timer)
}
}
@@ -174,39 +174,39 @@ class MapImage extends React.Component {
// - Only render on map/svg changes
// - Trigger color update on indexUpdate, but
// avoid render if this is the only change
- shouldComponentUpdate(nextProps, nextState) {
+ shouldComponentUpdate (nextProps, nextState) {
if (this.props.indexValues !== nextProps.indexValues) {
- this.updateColorsIfPossible(nextProps);
+ this.updateColorsIfPossible(nextProps)
if (this.state.tooltipShow) {
// It's slow, but it's the faster solution found to update tooltip content
// without re-drawing full tooltip component (very slow)
- ReactTooltip.show(this.mapElements.get(this.state.tooltipShow));
+ ReactTooltip.show(this.mapElements.get(this.state.tooltipShow))
}
}
if (
this.props.mapSrc !== nextProps.mapSrc ||
this.state.svgStatus !== nextState.svgStatus
) {
- return true;
+ return true
}
- return false;
+ return false
}
// Renders the tip contents for a region
- afterTipShow = (event) => this.setState({ tooltipShow: event.target.id });
- afterTipHide = (event) => this.setState({ tooltipShow: false });
+ afterTipShow = (event) => this.setState({ tooltipShow: event.target.id })
+ afterTipHide = (event) => this.setState({ tooltipShow: false })
getTipContent = (id) => {
if (!id) {
- return "";
+ return ''
}
- const element = this.mapElements?.get(id) ?? false;
+ const element = this.mapElements?.get(id) ?? false
if (!element) {
- return "";
+ return ''
}
- const { values, indexValues, label } = this.props;
- const valuesDay = values[indexValues];
+ const { values, indexValues, label } = this.props
+ const valuesDay = values[indexValues]
return (
- );
- };
+ )
+ }
- render() {
- const { mapSrc, title, id } = this.props;
+ render () {
+ const { mapSrc, title, id } = this.props
return (
<>
>
- );
+ )
}
}
MapImage.defaultProps = {
values: [],
indexValues: 0,
- label: "Index",
- title: "Map",
-};
+ label: 'Index',
+ title: 'Map'
+}
MapImage.propTypes = {
label: PropTypes.string,
@@ -264,10 +264,10 @@ MapImage.propTypes = {
colors: PropTypes.arrayOf(
PropTypes.shape({
value: PropTypes.number.isRequired,
- color: PropTypes.string.isRequired,
+ color: PropTypes.string.isRequired
})
).isRequired,
- indexValues: PropTypes.number.isRequired,
-};
+ indexValues: PropTypes.number.isRequired
+}
-export default MapImage;
+export default MapImage
diff --git a/app/src/Widget/Widgets/Map/Widget.jsx b/app/src/Widget/Widgets/Map/Widget.jsx
index f9e74153..3798d285 100644
--- a/app/src/Widget/Widgets/Map/Widget.jsx
+++ b/app/src/Widget/Widgets/Map/Widget.jsx
@@ -1,31 +1,31 @@
-import React from "react";
-import PropTypes from "prop-types";
+import React from 'react'
+import PropTypes from 'prop-types'
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import {
faInfoCircle as faLegend,
faEdit,
- faGlobeEurope as faMap,
-} from "@fortawesome/free-solid-svg-icons";
+ faGlobeEurope as faMap
+} from '@fortawesome/free-solid-svg-icons'
-import MapData from "../../../Backend/Maps";
-import { withData } from "../../../Backend/Maps/context";
+import MapData from '../../../Backend/Maps'
+import { withData } from '../../../Backend/Maps/context'
-import MapImage from "./MapImage";
-import Edit from "./Edit";
-import Legend from "./Legend";
-import withWidget from "../../Widget";
+import MapImage from './MapImage'
+import Edit from './Edit'
+import Legend from './Legend'
+import withWidget from '../../Widget'
const MapWrapper = withWidget({
// The normal view
view: {
icon:
,
- label: ({ t }) => t("View"),
+ label: ({ t }) => t('View'),
title: (props) => props.name,
render: withData(
({ t, mapKind, label, mapData, indexValues, colors, id }) => (
)
- ),
+ )
},
// Edit data
edit: {
icon:
,
- label: ({ t }) => t("Edit"),
- title: ({ t }) => t("Edit map parameters"),
+ label: ({ t }) => t('Edit'),
+ title: ({ t }) => t('Edit map parameters'),
render: (props) => (
- ),
+ )
},
// Show map legend
legend: {
icon:
,
- label: ({ t }) => t("Legend"),
+ label: ({ t }) => t('Legend'),
title: (props) => props.title,
render: (props) => (
- ),
- },
-});
+ )
+ }
+})
/*
Combine MapData backend with MapWrapper/MapImage
*/
class DataHandler extends React.Component {
- constructor(props) {
- super(props);
+ constructor (props) {
+ super(props)
// Default values: first element of each's group
// TODO: Get data from storage/router/props
- const { mapKind, mapValue } = props;
+ const { mapKind, mapValue } = props
this.state = {
mapKindDefault: mapKind,
mapValueDefault: mapValue,
- ...this.getMeta(mapValue),
- };
+ ...this.getMeta(mapValue)
+ }
}
// Get metadata from given params
@@ -85,42 +85,42 @@ class DataHandler extends React.Component {
colors: MapData.metaColors(mapValue),
title: MapData.metaTitle(mapValue),
label: MapData.metaLabel(mapValue),
- name: MapData.metaLabel(mapValue),
- });
+ name: MapData.metaLabel(mapValue)
+ })
// Update map metadata
onChangeMapKind = (mapKind) => {
- const { mapValue } = this.props;
+ const { mapValue } = this.props
this.props.onChangeData(this.props.id, {
mapKind,
- mapValue,
- });
- };
+ mapValue
+ })
+ }
// Update map metadata
onChangeMapValue = (mapValue) => {
- const { mapKind } = this.props;
+ const { mapKind } = this.props
this.setState({
- ...this.getMeta(mapValue),
- });
+ ...this.getMeta(mapValue)
+ })
this.props.onChangeData(this.props.id, {
mapKind,
- mapValue,
- });
- };
+ mapValue
+ })
+ }
- render() {
- const { days, indexValues, id, mapKind, mapValue } = this.props;
+ render () {
+ const { days, indexValues, id, mapKind, mapValue } = this.props
const { mapKindDefault, mapValueDefault, colors, title, label, name } =
- this.state;
+ this.state
return (
- );
+ )
}
}
// Default values: first element of each's group
-const mapKind = MapData.kinds()[0];
-const mapValue = MapData.values(mapKind)[0];
+const mapKind = MapData.kinds()[0]
+const mapValue = MapData.values(mapKind)[0]
DataHandler.defaultProps = {
mapKind,
mapValue,
onChangeData: () => {},
- onRemove: () => {},
-};
+ onRemove: () => {}
+}
DataHandler.propTypes = {
days: PropTypes.arrayOf(PropTypes.string).isRequired,
@@ -162,7 +162,7 @@ DataHandler.propTypes = {
onChangeData: PropTypes.func.isRequired,
onRemove: PropTypes.func.isRequired,
mapKind: PropTypes.string.isRequired,
- mapValue: PropTypes.string.isRequired,
-};
+ mapValue: PropTypes.string.isRequired
+}
-export default DataHandler;
+export default DataHandler
diff --git a/app/src/asyncComponent.jsx b/app/src/asyncComponent.jsx
index 0ca184e2..ead33d21 100644
--- a/app/src/asyncComponent.jsx
+++ b/app/src/asyncComponent.jsx
@@ -55,11 +55,13 @@ const asyncModuleComponent = (importModule, modules, Wrapped) =>
}, [])
// Once loaded, pass components to wrapped as a prop
- return !components ? (
-
- ) : (
-
- )
+ return !components
+ ? (
+
+ )
+ : (
+
+ )
}
export default asyncComponent
diff --git a/app/src/i18n/available.test.js b/app/src/i18n/available.test.js
index c143916b..d541c63f 100644
--- a/app/src/i18n/available.test.js
+++ b/app/src/i18n/available.test.js
@@ -1,54 +1,54 @@
-import available from "./available";
+import available from './available'
-test("languages are defined correctly (minimally)", () => {
+test('languages are defined correctly (minimally)', () => {
const groups = {
- en: ["en", "en-en", "en-us", "en-au"],
- fr: ["ca", "ca-es"],
- es: ["es", "es-es"],
- pl: ["pl", "pl-pl"],
- no: ["nb-no"],
- };
+ en: ['en', 'en-en', 'en-us', 'en-au'],
+ fr: ['ca', 'ca-es'],
+ es: ['es', 'es-es'],
+ pl: ['pl', 'pl-pl'],
+ no: ['nb-no']
+ }
for (const locale of Object.keys(groups)) {
for (const keyIndex of groups[locale]) {
expect(available.find(({ key }) => key === keyIndex).value.locale).toBe(
locale
- );
+ )
}
}
-});
+})
-test("all languages define all and only all translations defined in English", () => {
- const english = available.find(({ key }) => key === "en").value;
+test('all languages define all and only all translations defined in English', () => {
+ const english = available.find(({ key }) => key === 'en').value
const languages = available
- .filter(({ key }) => ["ca", "es", "pl", "nb-no"].includes(key))
- .map(({ value }) => value);
+ .filter(({ key }) => ['ca', 'es', 'pl', 'nb-no'].includes(key))
+ .map(({ value }) => value)
// All defined keys in English are defined in all languages
const components = Object.keys(english).filter(
- (key) => !["locale", "name"].includes(key)
- );
+ (key) => !['locale', 'name'].includes(key)
+ )
for (const component of components) {
for (const text of Object.keys(english[component])) {
// English key is equal to english translation
try {
- expect(english[component][text]).toBe(text);
+ expect(english[component][text]).toBe(text)
} catch (err) {
throw new Error(
`Component '${component}' defined in English is NOT defined in English`
- );
+ )
}
// All English keys are defined in all languages
for (const language of languages) {
try {
- expect(language[component][text]).toBeDefined();
- expect(language[component][text].length).not.toBe(0);
+ expect(language[component][text]).toBeDefined()
+ expect(language[component][text].length).not.toBe(0)
} catch (err) {
throw new Error(
`Text key '${text}' defined in component '${component}' defined in English is NOT defined in language '${language.locale}'`
- );
+ )
}
}
}
@@ -57,29 +57,29 @@ test("all languages define all and only all translations defined in English", ()
// All defined keys and components in all languages are defined in English
for (const language of languages) {
const components = Object.keys(language).filter(
- (key) => !["locale", "name"].includes(key)
- );
+ (key) => !['locale', 'name'].includes(key)
+ )
for (const component of components) {
// Component defined in language is also defined in English
try {
- expect(english[component]).toBeDefined();
+ expect(english[component]).toBeDefined()
} catch (err) {
throw new Error(
`Component '${component}' defined in language '${language.locale}' is NOT defined in English`
- );
+ )
}
// All keys defined in language's components are also defined in English' components
for (const text of Object.keys(language[component])) {
try {
- expect(english[component][text]).toBeDefined();
+ expect(english[component][text]).toBeDefined()
} catch (err) {
throw new Error(
`Text key '${text}' defined in component '${component}' defined in language '${language.locale}' is NOT defined in English`
- );
+ )
}
}
}
}
-});
+})
diff --git a/app/src/testHelpers.js b/app/src/testHelpers.js
index 3b63639f..8d5af0f5 100644
--- a/app/src/testHelpers.js
+++ b/app/src/testHelpers.js
@@ -1,102 +1,102 @@
-import React from "react";
-import { useLocation } from "react-router-dom";
+import React from 'react'
+import { useLocation } from 'react-router-dom'
-const delay = (millis) => new Promise((resolve) => setTimeout(resolve, millis));
+const delay = (millis) => new Promise((resolve) => setTimeout(resolve, millis))
-const catchConsoleWarn = async (cb, severity = "warn") => {
- const output = [];
- const originalWarn = console[severity];
+const catchConsoleWarn = async (cb, severity = 'warn') => {
+ const output = []
+ const originalWarn = console[severity]
const mockedWarn = jest.fn((msg, ...rest) => {
- output.push(msg);
+ output.push(msg)
if (rest) {
- output.push(`${rest}`);
+ output.push(`${rest}`)
}
- });
- console[severity] = mockedWarn;
- const value = await cb();
- console[severity] = originalWarn;
+ })
+ console[severity] = mockedWarn
+ const value = await cb()
+ console[severity] = originalWarn
return {
value,
output,
- fn: mockedWarn,
- };
-};
+ fn: mockedWarn
+ }
+}
-const catchConsoleError = async (cb) => await catchConsoleWarn(cb, "error");
-const catchConsoleLog = async (cb) => await catchConsoleWarn(cb, "log");
-const catchConsoleDir = async (cb) => await catchConsoleWarn(cb, "dir");
+const catchConsoleError = async (cb) => await catchConsoleWarn(cb, 'error')
+const catchConsoleLog = async (cb) => await catchConsoleWarn(cb, 'log')
+const catchConsoleDir = async (cb) => await catchConsoleWarn(cb, 'dir')
const createClientXY = (x, y) => ({
clientX: x,
clientY: y,
pageX: x,
- pageY: y,
-});
+ pageY: y
+})
const createStartTouchEventObject = ({ x = 0, y = 0 }) => ({
- touches: [createClientXY(x, y)],
-});
+ touches: [createClientXY(x, y)]
+})
const createMoveTouchEventObject = (coords) => ({
touches: coords.map(({ x = 0, y = 0 }) => createClientXY(x, y)),
- changedTouches: coords.map(({ x = 0, y = 0 }) => createClientXY(x, y)),
-});
+ changedTouches: coords.map(({ x = 0, y = 0 }) => createClientXY(x, y))
+})
const createEndTouchEventObject = ({ x = 0, y = 0 }) => ({
touches: [createClientXY(x, y)],
- changedTouches: [createClientXY(x, y)],
-});
+ changedTouches: [createClientXY(x, y)]
+})
// Testing components
-const LocationDisplay = ({ member = "hash" }) => {
- const location = useLocation();
- return
{location[member]}
;
-};
+const LocationDisplay = ({ member = 'hash' }) => {
+ const location = useLocation()
+ return
{location[member]}
+}
// Helper to mock and test fetch
class MockFetch {
- original = null;
+ original = null
options = {
date: new Date(),
throwError: false,
responseOptions: () => ({
headers: {
- "Content-Type": "application/json; charset=utf-8",
- "last-modified": this.options.date.toString(),
- },
- }),
- };
+ 'Content-Type': 'application/json; charset=utf-8',
+ 'last-modified': this.options.date.toString()
+ }
+ })
+ }
mock = () => {
// Mock fetch
if (this.original === null) {
- this.original = global.fetch;
+ this.original = global.fetch
global.fetch = jest.fn(async (url, options) => {
- await delay(500);
+ await delay(500)
if (this.options.throwError !== false) {
- throw this.options.throwError;
+ throw this.options.throwError
}
try {
return new Response(
JSON.stringify({ tested: true, url }),
this.options.responseOptions.bind(this)()
- );
+ )
} catch (err) {
- console.error("MOCKED FETCH ERROR:", err);
- throw err;
+ console.error('MOCKED FETCH ERROR:', err)
+ throw err
}
- });
+ })
}
- };
+ }
unmock = () => {
// Unmock fetch
- global.fetch = this.original;
- this.original = null;
- };
+ global.fetch = this.original
+ this.original = null
+ }
}
class AbortError extends Error {
- constructor(...args) {
- super(...args);
- this.name = "AbortError";
+ constructor (...args) {
+ super(...args)
+ this.name = 'AbortError'
}
}
@@ -111,5 +111,5 @@ export {
catchConsoleLog,
catchConsoleDir,
MockFetch,
- AbortError,
-};
+ AbortError
+}
diff --git a/app/src/tests/App.int.test.js b/app/src/tests/App.int.test.js
index ec4519da..1b357d90 100644
--- a/app/src/tests/App.int.test.js
+++ b/app/src/tests/App.int.test.js
@@ -1,59 +1,59 @@
-import React from "react";
-import { render, fireEvent, act } from "@testing-library/react";
+import React from 'react'
+import { render, fireEvent, act } from '@testing-library/react'
-import "../testSetup";
-import App from "../App";
+import '../testSetup'
+import App from '../App'
-jest.mock("../Backend", () => {
+jest.mock('../Backend', () => {
return {
__esModule: true,
BackendProvider: ({ children }) => <>{children}>,
- IndexesHandler: ({ children }) => <>{children}>,
- };
-});
+ IndexesHandler: ({ children }) => <>{children}>
+ }
+})
-jest.mock("../Widget", () => {
+jest.mock('../Widget', () => {
return {
__esModule: true,
- WidgetsList: () =>
WidgetsList
,
- };
-});
+ WidgetsList: () =>
WidgetsList
+ }
+})
-test("renders copyright link", () => {
- const { getByText } = render(
{}} />);
- const linkElement = getByText(/Source code of/i);
- expect(linkElement).toBeInTheDocument();
-});
+test('renders copyright link', () => {
+ const { getByText } = render( {}} />)
+ const linkElement = getByText(/Source code of/i)
+ expect(linkElement).toBeInTheDocument()
+})
-test("menu shows detected new service worker", async () => {
- let app;
+test('menu shows detected new service worker', async () => {
+ let app
act(() => {
- app = render( {}} />);
- });
+ app = render( {}} />)
+ })
await act(async () => {
- const event = new CustomEvent("onNewServiceWorker", {
- detail: { registration: true },
- });
- fireEvent(document, event);
- const updateElement = await app.findByText("1"); // Detect menu badge
- expect(updateElement).toBeInTheDocument();
- });
-});
+ const event = new CustomEvent('onNewServiceWorker', {
+ detail: { registration: true }
+ })
+ fireEvent(document, event)
+ const updateElement = await app.findByText('1') // Detect menu badge
+ expect(updateElement).toBeInTheDocument()
+ })
+})
-test("translates app into detected language", async () => {
- const languageGetter = jest.spyOn(window.navigator, "language", "get");
+test('translates app into detected language', async () => {
+ const languageGetter = jest.spyOn(window.navigator, 'language', 'get')
await act(async () => {
- languageGetter.mockReturnValue("en-US");
- const app = render( {}} />);
- const [codeElement] = await app.findAllByText("Refactored");
- expect(codeElement).toBeInTheDocument();
+ languageGetter.mockReturnValue('en-US')
+ const app = render( {}} />)
+ const [codeElement] = await app.findAllByText('Refactored')
+ expect(codeElement).toBeInTheDocument()
// Ensure app is fully re-created to re-run the constructor
- app.unmount();
- });
+ app.unmount()
+ })
await act(async () => {
- languageGetter.mockReturnValue("ca-ES");
- const app = render( {}} />);
- const [codeElement] = await app.findAllByText("Refactoritzada");
- expect(codeElement).toBeInTheDocument();
- });
-});
+ languageGetter.mockReturnValue('ca-ES')
+ const app = render( {}} />)
+ const [codeElement] = await app.findAllByText('Refactoritzada')
+ expect(codeElement).toBeInTheDocument()
+ })
+})