Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions coverage.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
ℹ start of coverage report
ℹ ----------------------------------------------------------
ℹ file | line % | branch % | funcs % | uncovered lines
ℹ ----------------------------------------------------------
ℹ ----------------------------------------------------------
ℹ all files | 100.00 | 100.00 | 100.00 |
ℹ ----------------------------------------------------------
ℹ end of coverage report
8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@
"scripts": {
"build": "npm run lint && rm -rf dist && npm run rollup",
"changelog": "auto-changelog -p",
"fix": "npx oxlint --fix *.js src tests && npx oxfmt *.js src tests/unit --write",
"lint": "npx oxlint *.js src tests && npx oxfmt *.js src tests/unit --check",
"fix": "npx oxlint --fix *.js src test && npx oxfmt *.js src test --write",
"lint": "npx oxlint *.js src test && npx oxfmt *.js src test --check",
"rollup": "rollup --config",
"test": "npm run lint && node --test --experimental-test-coverage tests/**/*.js",
"coverage": "node --test --experimental-test-coverage --test-coverage-exclude=dist/** --test-coverage-exclude=tests/** --test-reporter=spec tests/**/*.test.js 2>&1 | grep -A 1000 \"start of coverage report\" > coverage.txt",
"test": "npm run lint && node --test --experimental-test-coverage test/**/*.js",
"coverage": "node --test --experimental-test-coverage --test-coverage-exclude=dist/** --test-coverage-exclude=test/** --test-reporter=spec test/**/*.js 2>&1 | grep -A 1000 \"start of coverage report\" > coverage.txt",
"prepare": "husky install"
},
"repository": {
Expand Down
253 changes: 121 additions & 132 deletions test/eventsource.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import assert from "node:assert";
import {eventsource} from "../dist/tiny-eventsource.cjs";
import { test } from "node:test";
import { setTimeout as delay } from "node:timers/promises";
import { eventsource } from "../dist/tiny-eventsource.cjs";

const mockRequest = {
handlers: {},
Expand All @@ -9,8 +11,8 @@ const mockRequest = {
socket: {
setTimeout: () => void 0,
setNoDelay: () => void 0,
setKeepAlive: () => void 0
}
setKeepAlive: () => void 0,
},
};

const mockResponse = {
Expand All @@ -21,158 +23,145 @@ const mockResponse = {
},
write: () => {
mockRequest.handlers.close();
}
},
};

describe("Testing functionality", function () {
it("It should do nothing with stock configuration", function () {
this.eventsource = eventsource();
assert.equal(this.eventsource.heartbeat.ms, 0, "Should be '0'");
assert.equal(this.eventsource.heartbeat.msg, "ping", "Should be 'ping'");
assert.equal(this.eventsource.initial.length, 0, "Should be '0'");
});

it("It should have an accurate listener count", function () {
const fn = () => void 0;

this.ms = 0;
this.eventsource = eventsource({ms: this.ms}, "Hello World!");
assert.equal(this.eventsource.heartbeat.ms, 0, "Should be '0'");
assert.equal(this.eventsource.initial.length, 1, "Should be '1'");
assert.equal(this.eventsource.initial[0], "Hello World!", "Should be 'Hello World!'");
assert.equal(this.eventsource.listenerCount("data"), 0, "Should be '0'");
this.eventsource.on("data", fn);
assert.equal(this.eventsource.listenerCount("data"), 1, "Should be '1'");
this.eventsource.off("data", fn);
});

it("It should have a heartbeat", function (done) {
this.ms = 250;
this.eventsource = eventsource({ms: this.ms}, "Hello World!");

test("stock configuration", function () {
const es = eventsource();
assert.equal(es.heartbeat.ms, 0, "Should be '0'");
assert.equal(es.heartbeat.msg, "ping", "Should be 'ping'");
assert.equal(es.initial.length, 0, "Should be '0'");
});

test("accurate listener count", function () {
const fn = () => void 0;
const es = eventsource({ ms: 0 }, "Hello World!");
assert.equal(es.heartbeat.ms, 0, "Should be '0'");
assert.equal(es.initial.length, 1, "Should be '1'");
assert.equal(es.initial[0], "Hello World!", "Should be 'Hello World!'");
assert.equal(es.listenerCount("data"), 0, "Should be '0'");
es.on("data", fn);
assert.equal(es.listenerCount("data"), 1, "Should be '1'");
es.off("data", fn);
});

test("heartbeat interval fires", async function () {
const ms = 250;
const es = eventsource({ ms }, "Hello World!");

await new Promise((resolve) => {
let finish = false;
const fn = arg => {
const fn = (arg) => {
if (finish === false) {
finish = true;
assert.equal(arg.data, "ping", "Should be 'ping'");
this.eventsource.off("data", fn);
setTimeout(() => {
this.eventsource.heartbeat.ms = 0;
done();
}, this.ms);
es.off("data", fn);
es.heartbeat.ms = 0;
resolve();
}
};

assert.equal(this.eventsource.heartbeat.ms, this.ms, `Should be '${this.ms}'`);
assert.equal(this.eventsource.initial.length, 1, "Should be '1'");
assert.equal(this.eventsource.initial[0], "Hello World!", "Should be 'Hello World!'");
this.eventsource.init(mockRequest, mockResponse);
this.eventsource.on("data", fn);
assert.equal(es.heartbeat.ms, ms, `Should be '${ms}'`);
assert.equal(es.initial.length, 1, "Should be '1'");
assert.equal(es.initial[0], "Hello World!", "Should be 'Hello World!'");
es.init(mockRequest, mockResponse);
es.on("data", fn);
});
});

it("It should send custom events", function (done) {
this.eventsource = eventsource();
test("send custom events", async function () {
const es = eventsource();

await new Promise((resolve) => {
let finish = false;
const fn = arg => {
const fn = (arg) => {
if (finish === false) {
finish = true;
assert.equal(arg.data, "Hello World!", "Should be 'Hello World!'");
assert.equal(arg.event, "customEvent", "Should be 'customEvent'");
this.eventsource.off("data", fn);
done();
es.off("data", fn);
resolve();
}
};

this.eventsource.init(mockRequest, mockResponse);
this.eventsource.on("data", fn);
this.eventsource.send("Hello World!", "customEvent");
es.init(mockRequest, mockResponse);
es.on("data", fn);
es.send("Hello World!", "customEvent");
});
});

it("stop() cancels active heartbeat — no ping after stop()", function (done) {
this.ms = 300;
this.eventsource = eventsource({ms: this.ms}, "Hello World!");
this.eventsource.init(mockRequest, mockResponse);
let pingCount = 0;

this.eventsource.on("data", () => {
pingCount++;
});

setTimeout(() => {
assert.equal(pingCount, 1, "Should have received one ping before stop()");
this.eventsource.stop();
setTimeout(() => {
assert.equal(pingCount, 1, "Should still have only one ping after stop()");
this.eventsource.removeAllListeners("data");
done();
}, this.ms * 2 + 100);
}, this.ms + 10);
});
test("stop() cancels active heartbeat", async function () {
const ms = 300;
const es = eventsource({ ms }, "Hello World!");
es.init(mockRequest, mockResponse);
let pingCount = 0;

it("stop() on non-heartbeat instance is a no-op", function () {
this.eventsource = eventsource({ms: 0});
assert.doesNotThrow(() => {
this.eventsource.stop();
});
es.on("data", () => {
pingCount++;
});

it("stop() returns the EventSource instance for chaining", function () {
this.eventsource = eventsource({ms: 200});
const ret = this.eventsource.stop();
assert.equal(ret, this.eventsource, "stop() should return this");
await delay(ms + 10);
assert.equal(pingCount, 1, "Should have received one ping before stop()");
es.stop();
await delay(ms * 2 + 100);
assert.equal(pingCount, 1, "Should still have only one ping after stop()");
es.removeAllListeners("data");
});

test("stop() on non-heartbeat instance is a no-op", function () {
const es = eventsource({ ms: 0 });
assert.doesNotThrow(() => {
es.stop();
});

it("repeated stop() calls are safe", function () {
this.eventsource = eventsource({ms: 0});
assert.doesNotThrow(() => {
this.eventsource.stop();
this.eventsource.stop();
this.eventsource.stop();
});
});

it("CLOSE event stops heartbeat — no ping after disconnect", function (done) {
this.ms = 250;
const req = {
handlers: {},
on: (arg, fn) => {
req.handlers[arg] = fn;
},
socket: {
setTimeout: () => void 0,
setNoDelay: () => void 0,
setKeepAlive: () => void 0
}
};
const res = {
statusCode: 0,
headers: {},
setHeader: (key, value) => {
res.headers[key] = value;
},
write: () => void 0
};
this.eventsource = eventsource({ms: this.ms}, "Hello World!");
this.eventsource.init(req, res);
let pingCount = 0;

this.eventsource.on("data", () => {
pingCount++;
});

// Wait for first ping to confirm heartbeat is running
setTimeout(() => {
assert.ok(pingCount >= 1, "Should have received at least one ping");
// Simulate client disconnect by triggering CLOSE handler
req.handlers.close();
// Wait another heartbeat interval — no more pings should arrive
setTimeout(() => {
const finalCount = pingCount;
assert.equal(pingCount, finalCount, "Should not receive more pings after disconnect");
this.eventsource.removeAllListeners("data");
done();
}, this.ms + 100);
}, this.ms + 10);
});
});

test("stop() returns the EventSource instance for chaining", function () {
const es = eventsource({ ms: 200 });
const ret = es.stop();
assert.equal(ret, es, "stop() should return this");
});

test("repeated stop() calls are safe", function () {
const es = eventsource({ ms: 0 });
assert.doesNotThrow(() => {
es.stop();
es.stop();
es.stop();
});
});

test("CLOSE event stops heartbeat — no ping after disconnect", async function () {
const ms = 250;
const req = {
handlers: {},
on: (arg, fn) => {
req.handlers[arg] = fn;
},
socket: {
setTimeout: () => void 0,
setNoDelay: () => void 0,
setKeepAlive: () => void 0,
},
};
const res = {
statusCode: 0,
headers: {},
setHeader: (key, value) => {
res.headers[key] = value;
},
write: () => void 0,
};
const es = eventsource({ ms }, "Hello World!");
es.init(req, res);
let pingCount = 0;

es.on("data", () => {
pingCount++;
});

await delay(ms + 10);
assert.ok(pingCount >= 1, "Should have received at least one ping");
req.handlers.close();
await delay(ms + 100);
assert.equal(pingCount, pingCount, "Should not receive more pings after disconnect");
es.removeAllListeners("data");
});
Loading