Compare commits

...

23 Commits

Author SHA1 Message Date
Matt Walsh
0e457881c7 5.9.10 2023-06-28 10:21:30 -05:00
Matt Walsh
56d6a68e9a Merge pull request #27 from blackchip-org/time-refresh
Fix time flickering when frame is first displayed
2023-06-28 10:18:29 -05:00
Mike McGann
6b5ac04498 fix time flickering when frame is first displayed 2023-06-27 16:06:45 -04:00
Matt Walsh
465fa5a99b capture dist 2023-05-31 23:12:34 -05:00
Matt Walsh
9c0d42c5c4 5.9.9 2023-05-31 23:11:30 -05:00
Matt Walsh
07ad5141a4 fix radar timestamp sorting 2023-05-31 23:11:12 -05:00
Matt Walsh
2c010a9a32 switch from css zoom to transform-scale 2023-05-31 22:57:36 -05:00
Matt Walsh
3b050073ed Merge pull request #26 from N7KnightOne/patch-1
Fixed Copy Commands
2023-05-31 21:11:19 -05:00
N7KnightOne
2953dc993c Fixed copy commands
In a Dockerfile, when using COPY with more than one source file, the destination must be a directory and end with a /. Or, you can specify each file that needs to be copied.
2023-05-31 15:53:50 -07:00
Matt Walsh
f481c5cfeb Merge pull request #23 from rmitchellscott/docker
Add Docker build
2023-04-22 21:26:41 -05:00
Matt Walsh
e4672d12d7 5.9.8 2023-04-22 21:24:17 -05:00
Matt Walsh
04ed3e0a52 fix npm lint script 2023-04-22 21:23:31 -05:00
Matt Walsh
58a337efbf fix hazards - current conditions race condition close #24 2023-04-22 21:16:30 -05:00
Mitchell Scott
5da4a50a96 Revert "Pin Node to v16."
This reverts commit d850165752.
2023-04-19 16:04:05 -06:00
Mitchell Scott
d5cce14fc2 Add workflow permissions for image build 2023-04-17 09:31:53 -06:00
Mitchell Scott
d850165752 Pin Node to v16. Workaround for netbymatt/ws4kp#24 2023-04-16 14:20:59 -06:00
Mitchell Scott
2d0af7a143 Swap to netbymatt GHCR 2023-04-13 16:35:54 -06:00
Mitchell Scott
146a3fda76 Tweak Dockerfile for better caching 2023-04-13 15:46:27 -06:00
Mitchell Scott
e1083c83ae Add dockerfile and build 2023-04-13 11:43:52 -06:00
Matt Walsh
249cbb93e6 add test via multiple locations 2023-01-17 16:10:06 -06:00
Matt Walsh
888b35ea73 code cleanup 2023-01-17 14:13:51 -06:00
Matt Walsh
2a9e5b370e don't re-parse current conditions 2023-01-17 11:26:57 -06:00
Matt Walsh
87d4155d71 capture dist 2023-01-10 14:13:30 -06:00
30 changed files with 2393 additions and 7940 deletions

3
.dockerignore Normal file
View File

@@ -0,0 +1,3 @@
.git/
Dockerfile
.vscode/

47
.github/workflows/build-docker.yaml vendored Normal file
View File

@@ -0,0 +1,47 @@
name: build-docker
on: push
jobs:
build:
name: Build Image
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout
uses: actions/checkout@v3
- id: short-sha
uses: benjlevesque/short-sha@v1.2
- name: Docker meta
id: meta
uses: docker/metadata-action@v4
with:
images: |
ghcr.io/netbymatt/ws4kp
tags: |
type=raw,priority=1000,value=latest,enable=${{ github.ref == 'refs/heads/main' }}
type=ref,event=branch
${{ steps.short-sha.outputs.sha }}
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Set up Buildx
uses: docker/setup-buildx-action@v2
- name: Login to GitHub Container Registry
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and Push
id: docker_build
uses: docker/build-push-action@v3
with:
context: .
pull: true
push: ${{ github.ref == 'refs/heads/main' }}
platforms: linux/amd64,linux/arm/v7,linux/arm64/v8
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max

12
.vscode/launch.json vendored
View File

@@ -63,7 +63,17 @@
"env": {
"DIST": "1"
}
}
},
{
"name": "Test",
"program": "${workspaceFolder}/tests/index.js",
"request": "launch",
"skipFiles": [
"<node_internals>/**"
],
"type": "node",
"outputCapture": "std"
},
],
"compounds": [
{

10
Dockerfile Normal file
View File

@@ -0,0 +1,10 @@
FROM node:18-alpine
WORKDIR /app
COPY package.json .
COPY package-lock.json .
RUN npm ci
COPY . .
CMD ["node", "index.js"]

View File

@@ -19,12 +19,18 @@ This project is based on the work of [Mike Battaglia](https://github.com/vbguyny
## Run Your WeatherStar
There are a lot of CORS considerations and issues with api.weather.gov that are easiest to deal with by running a local server to see this in action (or use the live link above). You'll need Node.js >12.0 to run the local server.
To run via Node locally:
```
git clone https://github.com/netbymatt/ws4kp.git
cd ws4kp
npm i
node index.js
```
To run via Docker:
```
docker run -p 8080:8080 ghcr.io/netbymatt/ws4kp
```
Open your web browser: http://localhost:8080/
## Updates in 5.0

2
dist/index.html vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -31,17 +31,17 @@ const index = (req, res) => {
};
// debugging
if (process.env?.DIST !== '1') {
// debugging
app.get('/index.html', index);
app.get('/', index);
app.get('*', express.static(path.join(__dirname, './server')));
} else {
if (process.env?.DIST === '1') {
// distribution
app.use('/images', express.static(path.join(__dirname, './server/images')));
app.use('/fonts', express.static(path.join(__dirname, './server/fonts')));
app.use('/scripts', express.static(path.join(__dirname, './server/scripts')));
app.use('/', express.static(path.join(__dirname, './dist')));
} else {
// debugging
app.get('/index.html', index);
app.get('/', index);
app.get('*', express.static(path.join(__dirname, './server')));
}
const server = app.listen(port, () => {

8447
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,13 +1,13 @@
{
"name": "ws4kp",
"version": "5.9.7",
"version": "5.9.10",
"description": "Welcome to the WeatherStar 4000+ project page!",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build:css": "sass ./server/styles/scss/style.scss ./server/styles/compiled.css",
"lint": "eslint ./server/scripts/**",
"lint:fix": "eslint --fix ./server/scripts/**"
"lint": "eslint ./server/scripts/**/*.mjs",
"lint:fix": "eslint --fix ./server/scripts/**/*.mjs"
},
"repository": {
"type": "git",
@@ -25,8 +25,8 @@
"eslint": "^8.21.0",
"eslint-config-airbnb-base": "^15.0.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-sonarjs": "^0.17.0",
"eslint-plugin-unicorn": "^45.0.2",
"eslint-plugin-sonarjs": "^0.19.0",
"eslint-plugin-unicorn": "^46.0.0",
"express": "^4.17.1",
"gulp": "^4.0.2",
"gulp-concat": "^2.6.1",

View File

@@ -30,8 +30,9 @@ class CurrentWeather extends WeatherDisplay {
const filteredStations = weatherParameters.stations.filter((station) => station?.properties?.stationIdentifier?.length === 4 && !skipStations.includes(station.properties.stationIdentifier.slice(0, 1)));
// Load the observations
let observations; let
station;
let observations;
let station;
// station number counter
let stationNum = 0;
while (!observations && stationNum < filteredStations.length) {
@@ -73,7 +74,7 @@ class CurrentWeather extends WeatherDisplay {
}
// we only get here if there was no error above
this.data = { ...observations, station };
this.data = parseData({ ...observations, station });
this.getDataCallback();
// stop here if we're disabled
@@ -84,89 +85,37 @@ class CurrentWeather extends WeatherDisplay {
this.setStatus(STATUS.loaded);
}
// format the data for use outside this function
parseData() {
if (!this.data) return false;
const data = {};
const observations = this.data.features[0].properties;
// values from api are provided in metric
data.observations = observations;
data.Temperature = Math.round(observations.temperature.value);
data.TemperatureUnit = 'C';
data.DewPoint = Math.round(observations.dewpoint.value);
data.Ceiling = Math.round(observations.cloudLayers[0]?.base?.value ?? 0);
data.CeilingUnit = 'm.';
data.Visibility = Math.round(observations.visibility.value / 1000);
data.VisibilityUnit = ' km.';
data.WindSpeed = Math.round(observations.windSpeed.value);
data.WindDirection = directionToNSEW(observations.windDirection.value);
data.Pressure = Math.round(observations.barometricPressure.value);
data.HeatIndex = Math.round(observations.heatIndex.value);
data.WindChill = Math.round(observations.windChill.value);
data.WindGust = Math.round(observations.windGust.value);
data.WindUnit = 'KPH';
data.Humidity = Math.round(observations.relativeHumidity.value);
data.Icon = getWeatherIconFromIconLink(observations.icon);
data.PressureDirection = '';
data.TextConditions = observations.textDescription;
data.station = this.data.station;
// difference since last measurement (pascals, looking for difference of more than 150)
const pressureDiff = (observations.barometricPressure.value - this.data.features[1].properties.barometricPressure.value);
if (pressureDiff > 150) data.PressureDirection = 'R';
if (pressureDiff < -150) data.PressureDirection = 'F';
data.Temperature = celsiusToFahrenheit(data.Temperature);
data.TemperatureUnit = 'F';
data.DewPoint = celsiusToFahrenheit(data.DewPoint);
data.Ceiling = Math.round(metersToFeet(data.Ceiling) / 100) * 100;
data.CeilingUnit = 'ft.';
data.Visibility = kilometersToMiles(observations.visibility.value / 1000);
data.VisibilityUnit = ' mi.';
data.WindSpeed = kphToMph(data.WindSpeed);
data.WindUnit = 'MPH';
data.Pressure = pascalToInHg(data.Pressure).toFixed(2);
data.HeatIndex = celsiusToFahrenheit(data.HeatIndex);
data.WindChill = celsiusToFahrenheit(data.WindChill);
data.WindGust = kphToMph(data.WindGust);
return data;
}
async drawCanvas() {
super.drawCanvas();
const fill = {};
// parse each time to deal with a change in units if necessary
const data = this.parseData();
fill.temp = data.Temperature + String.fromCharCode(176);
let Conditions = data.observations.textDescription;
if (Conditions.length > 15) {
Conditions = shortConditions(Conditions);
let condition = this.data.observations.textDescription;
if (condition.length > 15) {
condition = shortConditions(condition);
}
fill.condition = Conditions;
fill.wind = data.WindDirection.padEnd(3, '') + data.WindSpeed.toString().padStart(3, ' ');
if (data.WindGust) fill['wind-gusts'] = `Gusts to ${data.WindGust}`;
const fill = {
temp: this.data.Temperature + String.fromCharCode(176),
condition,
wind: this.data.WindDirection.padEnd(3, '') + this.data.WindSpeed.toString().padStart(3, ' '),
location: locationCleanup(this.data.station.properties.name).substr(0, 20),
humidity: `${this.data.Humidity}%`,
dewpoint: this.data.DewPoint + String.fromCharCode(176),
ceiling: (this.data.Ceiling === 0 ? 'Unlimited' : this.data.Ceiling + this.data.CeilingUnit),
visibility: this.data.Visibility + this.data.VisibilityUnit,
pressure: `${this.data.Pressure} ${this.data.PressureDirection}`,
icon: { type: 'img', src: this.data.Icon },
};
fill.location = locationCleanup(this.data.station.properties.name).substr(0, 20);
if (this.data.WindGust) fill['wind-gusts'] = `Gusts to ${this.data.WindGust}`;
fill.humidity = `${data.Humidity}%`;
fill.dewpoint = data.DewPoint + String.fromCharCode(176);
fill.ceiling = (data.Ceiling === 0 ? 'Unlimited' : data.Ceiling + data.CeilingUnit);
fill.visibility = data.Visibility + data.VisibilityUnit;
fill.pressure = `${data.Pressure} ${data.PressureDirection}`;
if (data.observations.heatIndex.value && data.HeatIndex !== data.Temperature) {
if (this.data.observations.heatIndex.value && this.data.HeatIndex !== this.data.Temperature) {
fill['heat-index-label'] = 'Heat Index:';
fill['heat-index'] = data.HeatIndex + String.fromCharCode(176);
} else if (data.observations.windChill.value && data.WindChill !== '' && data.WindChill < data.Temperature) {
fill['heat-index'] = this.data.HeatIndex + String.fromCharCode(176);
} else if (this.data.observations.windChill.value && this.data.WindChill !== '' && this.data.WindChill < this.data.Temperature) {
fill['heat-index-label'] = 'Wind Chill:';
fill['heat-index'] = data.WindChill + String.fromCharCode(176);
fill['heat-index'] = this.data.WindChill + String.fromCharCode(176);
}
fill.icon = { type: 'img', src: data.Icon };
const area = this.elem.querySelector('.main');
area.innerHTML = '';
@@ -180,9 +129,9 @@ class CurrentWeather extends WeatherDisplay {
async getCurrentWeather(stillWaiting) {
if (stillWaiting) this.stillWaitingCallbacks.push(stillWaiting);
return new Promise((resolve) => {
if (this.data) resolve(this.parseData());
if (this.data) resolve(this.data);
// data not available, put it into the data callback queue
this.getDataCallbacks.push(() => resolve(this.parseData()));
this.getDataCallbacks.push(() => resolve(this.data));
});
}
}
@@ -206,6 +155,52 @@ const shortConditions = (_condition) => {
return condition;
};
// format the received data
const parseData = (data) => {
const observations = data.features[0].properties;
// values from api are provided in metric
data.observations = observations;
data.Temperature = Math.round(observations.temperature.value);
data.TemperatureUnit = 'C';
data.DewPoint = Math.round(observations.dewpoint.value);
data.Ceiling = Math.round(observations.cloudLayers[0]?.base?.value ?? 0);
data.CeilingUnit = 'm.';
data.Visibility = Math.round(observations.visibility.value / 1000);
data.VisibilityUnit = ' km.';
data.WindSpeed = Math.round(observations.windSpeed.value);
data.WindDirection = directionToNSEW(observations.windDirection.value);
data.Pressure = Math.round(observations.barometricPressure.value);
data.HeatIndex = Math.round(observations.heatIndex.value);
data.WindChill = Math.round(observations.windChill.value);
data.WindGust = Math.round(observations.windGust.value);
data.WindUnit = 'KPH';
data.Humidity = Math.round(observations.relativeHumidity.value);
data.Icon = getWeatherIconFromIconLink(observations.icon);
data.PressureDirection = '';
data.TextConditions = observations.textDescription;
// difference since last measurement (pascals, looking for difference of more than 150)
const pressureDiff = (observations.barometricPressure.value - data.features[1].properties.barometricPressure.value);
if (pressureDiff > 150) data.PressureDirection = 'R';
if (pressureDiff < -150) data.PressureDirection = 'F';
// convert to us units
data.Temperature = celsiusToFahrenheit(data.Temperature);
data.TemperatureUnit = 'F';
data.DewPoint = celsiusToFahrenheit(data.DewPoint);
data.Ceiling = Math.round(metersToFeet(data.Ceiling) / 100) * 100;
data.CeilingUnit = 'ft.';
data.Visibility = kilometersToMiles(observations.visibility.value / 1000);
data.VisibilityUnit = ' mi.';
data.WindSpeed = kphToMph(data.WindSpeed);
data.WindUnit = 'MPH';
data.Pressure = pascalToInHg(data.Pressure).toFixed(2);
data.HeatIndex = celsiusToFahrenheit(data.HeatIndex);
data.WindChill = celsiusToFahrenheit(data.WindChill);
data.WindGust = kphToMph(data.WindGust);
return data;
};
const display = new CurrentWeather(1, 'current-weather');
registerDisplay(display);

View File

@@ -55,8 +55,8 @@ class ExtendedForecast extends WeatherDisplay {
const fill = {
icon: { type: 'img', src: Day.icon },
condition: Day.text,
date: Day.dayName,
};
fill.date = Day.dayName;
const { low } = Day;
if (low !== undefined) {

View File

@@ -8,7 +8,6 @@ import { DateTime } from '../vendor/auto/luxon.mjs';
class HourlyGraph extends WeatherDisplay {
constructor(navId, elemId, defaultActive) {
// special height and width for scrolling
super(navId, elemId, 'Hourly Graph', defaultActive);
// move the top right data into the correct location on load

View File

@@ -123,10 +123,15 @@ const getWeatherRegionalIconFromIconLink = (link, _isNightTime) => {
case 'tropical_storm':
return addPath('Thunderstorm.gif');
case 'wind':
case 'wind_few':
case 'wind_sct':
case 'wind_bkn':
case 'wind_ovc':
case 'wind-n':
case 'wind_few-n':
case 'wind_bkn-n':
case 'wind_ovc-n':
return addPath('Wind.gif');
case 'wind_skc':
@@ -207,6 +212,9 @@ const getWeatherIconFromIconLink = (link, _isNightTime) => {
return addPath('CC_Fog.gif');
case 'rain_sleet':
case 'rain_sleet-n':
case 'sleet':
case 'sleet-n':
return addPath('Sleet.gif');
case 'rain_showers':
@@ -242,6 +250,8 @@ const getWeatherIconFromIconLink = (link, _isNightTime) => {
case 'snow_fzra-n':
case 'fzra':
case 'fzra-n':
case 'rain_fzra':
case 'rain_fzra-n':
return addPath('CC_FreezingRain.gif');
case 'snow_sleet':
@@ -265,9 +275,10 @@ const getWeatherIconFromIconLink = (link, _isNightTime) => {
case 'wind_sct':
case 'wind_bkn':
case 'wind_ovc':
return addPath('CC_Windy.gif');
case 'wind_skc':
case 'wind_few-n':
case 'wind_bkn-n':
case 'wind_ovc-n':
case 'wind_skc-n':
case 'wind_sct-n':
return addPath('CC_Windy.gif');

View File

@@ -112,6 +112,12 @@ const updateStatus = (value) => {
value.status = displays[firstDisplayIndex].status;
}
// if hazards data arrives after the firstDisplayIndex loads, then we need to hot wire this to the first display
if (value.id === 0 && value.status === STATUS.loaded && displays[0].timing.totalScreens === 0) {
value.id = firstDisplayIndex;
value.status = displays[firstDisplayIndex].status;
}
// if this is the first display and we're playing, load it up so it starts playing
if (isPlaying() && value.id === firstDisplayIndex && value.status === STATUS.loaded) {
navTo(msg.command.firstFrame);
@@ -268,9 +274,9 @@ const resize = () => {
const scale = Math.min(widthZoomPercent, heightZoomPercent);
if (scale < 1.0 || document.fullscreenElement) {
document.querySelector('#container').style.zoom = scale;
document.querySelector('#container').style.transform = `scale(${scale})`;
} else {
document.querySelector('#container').style.zoom = 1;
document.querySelector('#container').style.transform = 'unset';
}
};

View File

@@ -91,7 +91,7 @@ class Radar extends WeatherDisplay {
const anchors = xmlDoc.querySelectorAll('a');
const urls = [];
Array.from(anchors).forEach((elem) => {
if (elem.innerHTML?.includes('.png') && elem.innerHTML?.includes('n0r_')) {
if (elem.innerHTML?.match(/n0r_\d{12}\.png/)) {
urls.push(elem.href);
}
});
@@ -99,7 +99,8 @@ class Radar extends WeatherDisplay {
});
// get the last few images
const sortedPngs = pngs.sort((a, b) => (Date(a) < Date(b) ? -1 : 1));
const timestampRegex = /_(\d{12})\.png/;
const sortedPngs = pngs.sort((a, b) => (a.match(timestampRegex)[1] < b.match(timestampRegex)[1] ? -1 : 1));
const urls = sortedPngs.slice(-(this.dopplerRadarImageMax));
// calculate offsets and sizes

View File

@@ -167,9 +167,7 @@ class RegionalForecast extends WeatherDisplay {
// draw the map
const scale = 640 / (offsetXY.x * 2);
const map = this.elem.querySelector('.map');
map.style.zoom = scale;
map.style.top = `-${sourceXY.y}px`;
map.style.left = `-${sourceXY.x}px`;
map.style.transform = `scale(${scale}) translate(-${sourceXY.x}px, -${sourceXY.y}px)`;
const cities = data.map((city) => {
const fill = {};

View File

@@ -151,6 +151,7 @@ class WeatherDisplay {
drawCanvas() {
// clean up the first-run flag in screen index
if (this.screenIndex < 0) this.screenIndex = 0;
if (this.okToDrawCurrentDateTime) this.drawCurrentDateTime();
}
finishDraw() {
@@ -159,14 +160,13 @@ class WeatherDisplay {
this.drawCurrentDateTime();
// auto clock refresh
if (!this.dateTimeInterval) {
setInterval(() => this.drawCurrentDateTime(), 100);
// only draw if canvas is active to conserve battery
setInterval(() => this.active && this.drawCurrentDateTime(), 100);
}
}
}
drawCurrentDateTime() {
// only draw if canvas is active to conserve battery
if (!this.active) return;
// Get the current date and time.
const now = DateTime.local().setZone(timeZone());

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -95,8 +95,9 @@ body {
}
}
}
.autocomplete-suggestions {
background-color: #ffffff;
border: 1px solid #000000;
@@ -284,6 +285,7 @@ body {
height: 480px;
overflow: hidden;
background-image: url(../images/BackGround1_1.png);
transform-origin: 0 0;
}
@@ -291,6 +293,7 @@ body {
background-image: none;
width: unset;
height: unset;
transform-origin: unset;
}
#loading {
@@ -373,7 +376,7 @@ body {
}
#divTwcBottom img {
zoom: 75%;
transform: scale(0.75);
}
#divTwc:fullscreen {

View File

@@ -12,6 +12,7 @@
.map {
position: absolute;
transform-origin: 0 0;
}
.location {

1
tests/README.md Normal file
View File

@@ -0,0 +1 @@
Currently, tests take a different approach from typical unit testing. The test methodology loads several forecasts for different locations and logs them all to one logger so errors can be found such as missing icons, locations that do not have all of the necessary data or other changes that may occur between geographical locations.

42
tests/index.js Normal file
View File

@@ -0,0 +1,42 @@
const puppeteer = require('puppeteer');
const { setTimeout } = require('node:timers/promises');
const { readFile } = require('fs/promises');
const messageFormatter = require('./messageformatter');
(async () => {
const browser = await puppeteer.launch({
// headless: false,
slowMo: 10,
timeout: 10_000,
dumpio: true,
});
// get the list of locations
const LOCATIONS = JSON.parse(await readFile('./tests/locations.json'));
// get the page
const page = (await browser.pages())[0];
await page.goto('http://localhost:8080');
page.on('console', messageFormatter);
// run all the locations
for (let i = 0; i < LOCATIONS.length; i += 1) {
const location = LOCATIONS[i];
console.log(location);
// eslint-disable-next-line no-await-in-loop
await tester(location, page);
}
browser.close();
})();
const tester = async (location, page) => {
// Set the address
await page.type('#txtAddress', location);
await setTimeout(500);
// get the page
await page.click('#btnGetLatLng');
// wait for errors
await setTimeout(5000);
};

52
tests/locations.json Normal file
View File

@@ -0,0 +1,52 @@
[
"New York, New York",
"Los Angeles, California",
"Chicago, Illinois",
"Houston, Texas",
"Phoenix, Arizona",
"Philadelphia, Pennsylvania",
"San Antonio, Texas",
"San Diego, California",
"Dallas, Texas",
"San Jose, California",
"Austin, Texas",
"Jacksonville, Florida",
"Fort Worth, Texas",
"Columbus, Ohio",
"Charlotte, North Carolina",
"Indianapolis, Indiana",
"San Francisco, California",
"Seattle, Washington",
"Denver, Colorado",
"Nashville, Tennessee",
"Washington, District of Columbia",
"Oklahoma City, Oklahoma",
"Boston, Massachusetts",
"El Paso, Texas",
"Portland, Oregon",
"Las Vegas, Nevada",
"Memphis, Tennessee",
"Detroit, Michigan",
"Baltimore, Maryland",
"Milwaukee, Wisconsin",
"Albuquerque, New Mexico",
"Fresno, California",
"Tucson, Arizona",
"Sacramento, California",
"Mesa, Arizona",
"Kansas City, Missouri",
"Atlanta, Georgia",
"Omaha, Nebraska",
"Colorado Springs, Colorado",
"Raleigh, North Carolina",
"Long Beach, California",
"Virginia Beach, Virginia",
"Oakland, California",
"Miami, Florida",
"Minneapolis, Minnesota",
"Bakersfield, California",
"Tulsa, Oklahoma",
"Aurora, Colorado",
"Arlington, Texas",
"Wichita, Kansas"
]

28
tests/messageformatter.js Normal file
View File

@@ -0,0 +1,28 @@
const chalk = require('chalk');
const describe = (jsHandle) => jsHandle.executionContext().evaluate(
// serialize |obj| however you want
(obj) => `OBJ: ${typeof obj}, ${obj}`,
jsHandle,
);
const colors = {
LOG: chalk.grey,
ERR: chalk.red,
WAR: chalk.yellow,
INF: chalk.cyan,
};
const formatter = async (message) => {
const args = await Promise.all(message.args().map((arg) => describe(arg)));
// make ability to paint different console[types]
const type = message.type().substr(0, 3).toUpperCase();
const color = colors[type] || chalk.blue;
let text = '';
for (let i = 0; i < args.length; i += 1) {
text += `[${i}] ${args[i]} `;
}
console.log(color(`CONSOLE.${type}: ${message.text()}\n${text} `));
};
module.exports = formatter;

1436
tests/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

15
tests/package.json Normal file
View File

@@ -0,0 +1,15 @@
{
"name": "ws4kp-tests",
"version": "1.0.0",
"description": "Currently, tests take a different approach from typical unit testing. The test methodology loads several forecasts for different locations and logs them all to one logger so errors can be found such as missing icons, locations that do not have all of the necessary data or other changes that may occur between geographical locations.",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "MIT",
"dependencies": {
"chalk": "^4.0.0",
"puppeteer": "^19.5.2"
}
}