Skip to content

Commit 623279e

Browse files
authored
Merge pull request #13 from ngfelixl/validate-data
feat: data validation, add missing tests
2 parents 7913c77 + 69924cf commit 623279e

File tree

9 files changed

+213
-72
lines changed

9 files changed

+213
-72
lines changed

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "nodeplotlib",
3-
"version": "0.5.2",
3+
"version": "0.6.0",
44
"description": "NodeJS frontend-less plotting lib using plotly.js inspired by matplotlib",
55
"main": "dist/lib/index.js",
66
"types": "dist/lib/index.d.ts",
@@ -19,7 +19,7 @@
1919
"scripts": {
2020
"build": "npm run clean && webpack && npm run build-copy-files",
2121
"clean": "shx rm -rf dist",
22-
"test": "jest --config jest.config.json --coverage",
22+
"test": "jest --config jest.config.json --coverage --maxWorkers=15",
2323
"format": "prettier --write \"src/**/*.ts\" \"src/**/*.js\"",
2424
"lint": "tslint --project ./tsconfig.json",
2525
"coverage": "cat coverage/lcov.info | coveralls",

src/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,6 @@
11
export { plot, clear, stack } from './plot';
22
export { Plot, Layout } from './models/index';
3+
4+
import { plot } from './plot';
5+
6+
plot([{x: [1, 2], y: [1, 2]}]);

src/plot.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ export function clear(): void {
2222
* @param layout
2323
*/
2424
export function stack(data: Plot[], layout?: Layout): void {
25+
validate(data, layout);
26+
2527
const container: IPlot = layout ? { data, layout } : { data };
2628
plots.push(container);
2729
}
@@ -34,8 +36,10 @@ export function stack(data: Plot[], layout?: Layout): void {
3436
*/
3537
export function plot(data?: Plot[] | null, layout?: Layout): void {
3638
if (data) {
39+
validate(data, layout);
3740
stack(data, layout);
3841
}
42+
3943
const id = Object.keys(plotContainer).length;
4044

4145
plotContainer[id] = {
@@ -47,3 +51,15 @@ export function plot(data?: Plot[] | null, layout?: Layout): void {
4751

4852
server.spawn(plotContainer);
4953
}
54+
55+
function validate(data: Plot[], layout?: Layout) {
56+
if (!(data instanceof Array) || data.length === 0) {
57+
throw new TypeError('Plot data must be an array with at least 1 element');
58+
}
59+
60+
if (layout) {
61+
if (!(layout instanceof Object)) {
62+
throw new TypeError('Layout must be an object');
63+
}
64+
}
65+
}

src/server.ts

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { readFile } from 'fs';
22
import { createServer, IncomingMessage, Server as HttpServer, ServerResponse } from 'http';
3+
import { Socket } from 'net';
34
import opn from 'opn';
45
import { join } from 'path';
56
import { IPlotsContainer } from './models';
@@ -8,10 +9,21 @@ export class Server {
89
private instance: HttpServer;
910
private plotsContainer: IPlotsContainer = {};
1011
private port: number;
12+
private sockets: {[id: number]: Socket} = {};
13+
private nextSocketID = 0;
1114

1215
constructor(port: number) {
1316
this.port = port;
1417
this.instance = this.createServer();
18+
19+
this.instance.on('connection', (socket: Socket) => {
20+
const id = this.nextSocketID++;
21+
this.sockets[id] = socket;
22+
23+
socket.on('close', () => {
24+
delete this.sockets[id];
25+
});
26+
});
1527
}
1628

1729
/**
@@ -30,12 +42,18 @@ export class Server {
3042
}
3143

3244
/**
33-
* Closes the webserver and clears the plots container.
45+
* Closes the webserver, destroys all connected sockets
46+
* and clears the plots container.
3447
*/
3548
public clean() {
3649
if (this.instance.address()) {
3750
this.instance.close();
3851
}
52+
53+
for (const socket of Object.values(this.sockets)) {
54+
socket.destroy();
55+
}
56+
3957
this.plotsContainer = {};
4058
}
4159

@@ -86,7 +104,7 @@ export class Server {
86104
}
87105

88106
/**
89-
*
107+
* Serves the website at http://localhost:PORT/plots/:id/index.html
90108
* @param req
91109
* @param res
92110
*/
@@ -141,7 +159,7 @@ export class Server {
141159
.reduce((a, b) => a && b.opened, true);
142160

143161
if (this.instance && !pending && opened) {
144-
this.instance.close();
162+
this.clean();
145163
}
146164
}
147165
}

test/clear.spec.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
1-
import * as lib from '../src/plot';
1+
import { clear, plots } from '../src/plot';
22

33
describe('clear', () => {
44
beforeEach(() => {
5-
lib.plots.push('fdsa' as any);
5+
plots.push('test' as any);
66
});
77

88
it('should clear the plots array', () => {
9-
lib.clear();
9+
clear();
1010

11-
expect(lib.plots).toEqual([]);
11+
expect(plots).toEqual([]);
1212
});
1313

1414
it('should be clearable multiple times', () => {
15-
lib.clear();
16-
lib.clear();
15+
clear();
16+
clear();
1717

18-
expect(lib.plots).toEqual([]);
18+
expect(plots).toEqual([]);
1919
});
2020
});

test/plot.spec.ts

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { plot } from '../src/index';
1+
import { plot, Plot } from '../src/index';
2+
import { plots, stack } from '../src/plot';
23
// import { Server } from '../src/server';
34
jest.mock('../src/server');
45

@@ -7,11 +8,29 @@ describe('plot', () => {
78
jest.resetAllMocks();
89
});
910

10-
it('should call "spawn" once', () => {
11-
plot();
11+
it('should throw an error if array length is 0', () => {
12+
expect(() => { plot([]) })
13+
.toThrow(new RegExp('Plot data must be an array with at least 1 element'))
14+
});
15+
16+
it('should spawn the server if data is valid', () => {
17+
plot([{x: [1], y: [1], type: 'line' as any}]);
1218
});
1319

14-
it('should stack data and call "spawn" once when using with data', () => {
20+
/* it('should stack data and call "spawn" once when using with data', () => {
1521
plot([], {});
22+
}); */
23+
24+
it('should clear the temporary plots array', () => {
25+
stack([{x: [1], y: [2], type: 'line' as any}]);
26+
expect(plots.length).toBe(1);
27+
28+
plot();
29+
expect(plots.length).toBe(0);
30+
});
31+
32+
it('should throw an error if layout is not an object', () => {
33+
expect(() => { plot([{x: [1], y: [1], type: 'line' as any}], 'test' as any) })
34+
.toThrow(new RegExp('Layout must be an object'));
1635
});
1736
});

test/server.spec.ts

Lines changed: 37 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,27 @@
11
import opn from 'opn';
22
import request from 'request';
33
import { Server } from '../src/server';
4+
5+
const port = 8080;
6+
const validData = {
7+
opened: false,
8+
pending: false,
9+
plots: [{data: [{ x: [1], y: [2]}]}]
10+
};
11+
412
jest.mock('opn');
13+
jest.mock('fs', () => ({readFile: (path: any, options: any, callback: (err: any, data: any) => void) => {
14+
callback('Error', null);
15+
}}));
516

617
describe('Server', () => {
718
let server: any;
819

920
beforeEach(() => {
10-
server = new Server(8080);
21+
server = new Server(port);
1122
});
1223

24+
1325
it('should instantiate', () => {
1426
expect(server).toBeTruthy();
1527
});
@@ -25,105 +37,75 @@ describe('Server', () => {
2537
});
2638

2739
it('should serve the data', (done) => {
28-
server.spawn({0: {
29-
opened: false,
30-
pending: false,
31-
plots: [{data: [{ x: [1], y: [2]}]}]
32-
}});
40+
server.spawn({0: validData});
3341

34-
request(`http://localhost:8080/data/0`, (err, response, body) => {
42+
request(`http://localhost:${port}/data/0`, (err, response, body) => {
3543
expect(JSON.parse(body)).toEqual([{data: [{ x: [1], y: [2]}]}]);
3644
done();
3745
});
3846
});
3947

4048
it('should spawn two times but listen just once', (done) => {
41-
const data = {0: {
42-
opened: false,
43-
pending: false,
44-
plots: [{data: [{ x: [1], y: [2]}]}]
45-
}};
49+
const data = {0: validData};
4650

4751
server.spawn(data);
4852
server.spawn(data);
4953

50-
request(`http://localhost:8080/data/0`, (err, response, body) => {
54+
request(`http://localhost:${port}/data/0`, (err, response, body) => {
5155
expect(JSON.parse(body)).toEqual([{data: [{ x: [1], y: [2]}]}]);
5256
done();
5357
});
5458
});
5559

5660
it('should serve the website and return 404 if html file not found', (done) => {
57-
server.spawn({0: {
58-
opened: false,
59-
pending: false,
60-
plots: [{data: [{ x: [1], y: [2]}]}]
61-
}});
61+
server.spawn({0: validData});
6262

63-
request(`http://localhost:8080/plots/0/index.html`, (err, response, body) => {
63+
request(`http://localhost:${port}/plots/0/index.html`, (err, response, body) => {
6464
expect(response.statusCode).toBe(404);
6565
done();
6666
});
6767
});
6868

6969
it('should serve the nodeplotlib script and return 404 if file not found', (done) => {
70-
server.spawn({0: {
71-
opened: false,
72-
pending: false,
73-
plots: [{data: [{ x: [1], y: [2]}]}]
74-
}});
70+
server.spawn({0: validData});
7571

76-
request(`http://localhost:8080/plots/0/nodeplotlib.min.js`, (err, response, body) => {
72+
request(`http://localhost:${port}/plots/0/nodeplotlib.min.js`, (err, response, body) => {
7773
expect(response.statusCode).toBe(404);
7874
done();
7975
});
8076
});
8177

8278
it('should serve the plotly.min.js script and return 404 if file not found', (done) => {
83-
server.spawn({0: {
84-
opened: false,
85-
pending: false,
86-
plots: [{data: [{ x: [1], y: [2]}]}]
87-
}});
79+
server.spawn({0: validData});
8880

89-
request(`http://localhost:8080/plots/0/plotly.min.js`, (err, response, body) => {
81+
request(`http://localhost:${port}/plots/0/plotly.min.js`, (err, response, body) => {
9082
expect(response.statusCode).toBe(404);
9183
done();
9284
});
9385
});
9486

9587
it('should not close the webserver, if one plot hasn\'t got its data', (done) => {
96-
server.spawn({0: {
97-
opened: false,
98-
pending: false,
99-
plots: [{data: [{ x: [1], y: [2]}]}]
100-
},
101-
1: {
102-
opened: false,
103-
pending: false,
104-
plots: [{data: [{ x: [1], y: [3]}]}]
105-
}});
88+
server.spawn({
89+
0: { pending: false, opened: false, plots: [{ data: [{x: [1], y: [2]}] }]},
90+
1: { pending: false, opened: false, plots: [{ data: [{x: [1], y: [3]}] }]}
91+
});
10692

107-
request(`http://localhost:8080/data/0`, (err, response, body) => {
93+
request(`http://localhost:${port}/data/0`, (err, response, body) => {
10894
expect(JSON.parse(body)).toEqual([{data: [{ x: [1], y: [2]}]}]);
10995

110-
request(`http://localhost:8080/data/1`, (err1, response1, body1) => {
96+
request(`http://localhost:${port}/data/1`, (err1, response1, body1) => {
11197
expect(JSON.parse(body1)).toEqual([{data: [{ x: [1], y: [3]}]}]);
11298
done();
11399
});
114100
});
115101
});
116102

117103
it('should return 404 if routes not matching', (done) => {
118-
const data = {0: {
119-
opened: false,
120-
pending: false,
121-
plots: [{data: [{ x: [1], y: [2]}]}]
122-
}};
104+
const data = {0: validData};
123105

124106
server.spawn(data);
125107

126-
request(`http://localhost:8080/fdsaffds`, (err, response, body) => {
108+
request(`http://localhost:${port}/fdsaffds`, (err, response, body) => {
127109
expect(response.statusCode).toBe(404);
128110
expect(response.body).toBe('Server address not found');
129111
done();
@@ -134,4 +116,9 @@ describe('Server', () => {
134116
server.clean();
135117
server = null;
136118
});
137-
});
119+
120+
// afterAll(() => {
121+
// console.log((process as any)._getActiveRequests());
122+
// console.log((process as any)._getActiveHandles()[0]);
123+
// });
124+
});

0 commit comments

Comments
 (0)