1
Fork 0
mirror of https://github.com/pds-nest/nest.git synced 2024-10-16 12:07:27 +00:00

💥 Refactor some things to improve code quality

This commit is contained in:
Steffo 2021-05-22 03:36:42 +02:00
parent 7a6e72c830
commit bc2310f18e
Signed by: steffo
GPG key ID: 6965406171929D01
11 changed files with 323 additions and 110 deletions

View file

@ -11,6 +11,7 @@ module.exports = {
config.roots = config.roots.map(root => root.replace("src", "nest_frontend"))
config.collectCoverageFrom = config.collectCoverageFrom.map(root => root.replace("src", "nest_frontend"))
config.testMatch = config.testMatch.map(root => root.replace("src", "nest_frontend"))
console.debug(config)
return config;
}
}

View file

@ -1,4 +1,3 @@
// Link.react.test.js
import React from "react"
import "@testing-library/jest-dom/extend-expect"
import { render, screen } from "@testing-library/react"

View file

@ -0,0 +1,92 @@
import { IconDefinition, faQuestionCircle } from "@fortawesome/free-solid-svg-icons"
/**
* Condition class for an undefined/unknown condition.
*
* See [the Condition spec](https://gitlab.steffo.eu/nest/g2-progetto/-/wikis/sprint-2/Specifica-delle-Conditions).
*/
export class Condition {
content
type
id
constructor(type, content, id = null) {
this.content = content
this.type = type
this.id = id
}
/**
* Get the condition as an object readable by the backend.
*
* @returns {{id, type, content}}
*/
serialize() {
return {
type: this.type,
content: this.content,
id: this.id,
}
}
/**
* Display parameters for the badge representing this condition.
*
* @returns {{color: string, icon: IconDefinition, title, content}}
*/
display() {
return {
color: "Grey",
icon: faQuestionCircle,
title: this.id,
content: this.content,
}
}
}
/**
* Require a tweet to contain a specific hashtag to be gathered.
*/
export class ConditionHashtag extends Condition {
constructor(hashtag, id = null) {
super(0, hashtag, id)
}
}
/**
* Require a tweet to be posted by a certain user to be gathered.
*/
export class ConditionUser extends Condition {
constructor(user, id = null) {
super(5, user, id)
}
}
/**
* Require a tweet to be posted before or after a certain time to be gathered.
*/
export class ConditionTime extends Condition {
timeRay
constructor(timeRay, id = null) {
super(2, timeRay.toString(), id)
this.timeRay = timeRay
}
}
/**
* Require a tweet to have coordinates associated and to be posted within the {@link MapArea}.
*/
export class ConditionLocation extends Condition {
mapArea
constructor(mapArea, id = null) {
super(3, mapArea.toString(), id)
this.mapArea = mapArea
}
}

View file

@ -0,0 +1,56 @@
import "@testing-library/jest-dom/extend-expect"
import { Condition, ConditionHashtag, ConditionLocation, ConditionTime, ConditionUser } from "./Condition"
import TimeRay from "./TimeRay"
import MapArea from "./MapArea"
test("Condition can be constructed", () => {
expect(new Condition(0, "hi")).toBeTruthy()
expect(new Condition(0, "hi", 1)).toBeTruthy()
})
test("ConditionHashtag can be constructed", () => {
expect(new ConditionHashtag("PdS2021")).toBeTruthy()
expect(new ConditionHashtag("PdS2021", 1)).toBeTruthy()
})
test("ConditionUser can be constructed", () => {
expect(new ConditionUser("USteffo")).toBeTruthy()
expect(new ConditionUser("USteffo", 1)).toBeTruthy()
})
test("ConditionTime can be constructed", () => {
const now = new Date()
const timeRay = new TimeRay(true, now)
expect(new ConditionTime(timeRay)).toBeTruthy()
expect(new ConditionTime(timeRay, 1)).toBeTruthy()
})
test("ConditionLocation can be constructed", () => {
const mapArea = new MapArea(1000, 0.000, 0.000)
expect(new ConditionLocation(mapArea)).toBeTruthy()
expect(new ConditionLocation(mapArea, 1)).toBeTruthy()
})
test("ConditionHashtag has the correct type", () => {
expect(new ConditionHashtag("PdS2021").type).toBe(0)
})
test("ConditionUser has the correct type", () => {
expect(new ConditionUser("USteffo").type).toBe(5)
})
test("ConditionTime has the correct type", () => {
const now = new Date()
const timeRay = new TimeRay(true, now)
expect(new ConditionTime(timeRay).type).toBe(5)
})
test("ConditionLocation has the correct type", () => {
const mapArea = new MapArea(1000, 0.000, 0.000)
expect(new ConditionLocation(mapArea).type).toBe(3)
})

View file

@ -0,0 +1,104 @@
/**
* Error thrown when a function is not implemented in the current class/instance.
*/
class NotImplementedError {
name
constructor(name) {
this.name = name
}
}
/**
* An error in the N.E.S.T. frontend-backend communication.
*/
class BackendCommunicationError {
}
/**
* Error thrown when trying to access a backend view which doesn't exist or isn't allowed in the used hook.
*/
class ViewNotAllowedError extends BackendCommunicationError {
view
constructor(view) {
super()
this.view = view
}
}
/**
* Error thrown when trying to access a backend view when outside a {@link ContextServer}.
*/
class ServerNotConfiguredError extends BackendCommunicationError {
}
/**
* Error thrown when trying to access a backend view while another access is ongoing.
*
* This is not allowed due to potential race conditions.
*/
class FetchAlreadyRunningError extends BackendCommunicationError {
}
/**
* Abstract class for {@link DecodeError} and {@link ResultError}.
*/
class FetchError extends BackendCommunicationError {
status
statusText
constructor(status, statusText) {
super()
this.status = status
this.statusText = statusText
}
}
/**
* Error thrown when the frontend can't parse the data received from the backend.
*/
class DecodeError extends FetchError {
error
constructor(status, statusText, error) {
super(status, statusText)
this.error = error
}
}
/**
* Error thrown when the backend returns a falsy `"result"` value.
*/
class ResultError extends FetchError {
status
statusText
data
constructor(status, statusText, data) {
super(status, statusText)
this.data = data
}
getMsg() {
return this.data.msg
}
getCode() {
return this.data.code
}
}

View file

@ -0,0 +1,34 @@
/**
* An area on a map, defined by a latitude `lat`, a longitude `lng` and a radius `rad` in meters.
*/
export default class MapArea {
/**
* @param rad - Radius of the area in meters.
* @param lat - Latitude of the center of the radius.
* @param lng - Longitude of the center of the radius.
*/
constructor(rad, lat, lng) {
this.rad = rad
this.lat = lat
this.lng = lng
}
/**
* @returns {string}
*/
toString() {
return `${this.rad} ${this.lat.toFixed(7)} ${this.lng.toFixed(7)}`
}
/**
* Render the MapArea as an human-readable string.
*
* @returns {string}
*/
toHumanString() {
if(this.rad >= 2000) {
const kmRadius = Math.round(this.rad / 1000)
return `${kmRadius}km ${this.lat.toFixed(3)} ${this.lng.toFixed(3)}`
}
}
}

View file

@ -0,0 +1,24 @@
/**
* An half-line of time, defined by a `date` and a boolean `isBefore` indicating if the time before or after the
* specified date should be selected.
*/
export default class TimeRay {
isBefore
date
/**
* @param isBefore - `true` to select times earlier than the date, `false` to select times after the date.
* @param date - The date to start measurements from.
*/
constructor(isBefore, date) {
this.isBefore = isBefore
this.date = date
}
/**
* @returns {string}
*/
toString() {
return `${this.isBefore ? "<" : ">"} ${this.date.toISOString()}`
}
}

View file

@ -1,40 +0,0 @@
import isString from "is-string"
const typeEnums = {
"HASHTAG": 0,
"TIME": 2,
"COORDINATES": 3,
"PLACE": 4,
"USER": 5,
}
/**
* A search/filtering Condition.
*
* See https://gitlab.steffo.eu/nest/g2-progetto/-/wikis/Specifica-delle-Conditions .
*/
export default class Condition {
/**
* Create a new Condition.
*
* @param type - The type of Condition to create.
* It can be a number or one of the following strings:
* `"hashtag"`, `"time"`, `"coordinates"`, `"place"`.
* @param content - The content of the Condition.
* @param id - The id of the Condition on the backend, or null if the Condition hasn't been committed yet.
*/
constructor(type, content, id = null) {
if(isString(type)) {
this.type = typeEnums[type.toUpperCase()]
}
else {
this.type = type
}
this.content = content
this.id = id
}
}

View file

@ -1,69 +0,0 @@
class NestError {
}
class ViewNotAllowedError extends NestError {
view
constructor(view) {
super()
this.view = view
}
}
class ServerNotConfiguredError extends NestError {
}
class FetchAlreadyRunningError extends NestError {
}
class FetchError extends NestError {
status
statusText
constructor(status, statusText) {
super()
this.status = status
this.statusText = statusText
}
}
class DecodeError extends FetchError {
error
constructor(status, statusText, error) {
super(status, statusText)
this.error = error
}
}
class ResultError extends FetchError {
status
statusText
data
constructor(status, statusText, data) {
super(status, statusText)
this.data = data
}
getMsg() {
return this.data.msg
}
getCode() {
return this.data.code
}
}

11
package-lock.json generated
View file

@ -20,6 +20,7 @@
"@testing-library/user-event": "^12.8.3",
"chart.js": "^3.2.1",
"classnames": "^2.3.1",
"geolib": "^3.3.1",
"is-string": "^1.0.5",
"leaflet": "^1.7.1",
"react": "^17.0.2",
@ -9416,6 +9417,11 @@
"node": ">=6.9.0"
}
},
"node_modules/geolib": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/geolib/-/geolib-3.3.1.tgz",
"integrity": "sha512-sfahBXFcgELdpumDZV5b3KWiINkZxC5myAkLk067UUcTmTXaiE9SWmxMEHztn/Eus4JX6kesHxaIuZlniYgUtg=="
},
"node_modules/get-caller-file": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
@ -30086,6 +30092,11 @@
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
"integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="
},
"geolib": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/geolib/-/geolib-3.3.1.tgz",
"integrity": "sha512-sfahBXFcgELdpumDZV5b3KWiINkZxC5myAkLk067UUcTmTXaiE9SWmxMEHztn/Eus4JX6kesHxaIuZlniYgUtg=="
},
"get-caller-file": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",

View file

@ -18,6 +18,7 @@
"@testing-library/user-event": "^12.8.3",
"chart.js": "^3.2.1",
"classnames": "^2.3.1",
"geolib": "^3.3.1",
"is-string": "^1.0.5",
"leaflet": "^1.7.1",
"react": "^17.0.2",