Compare commits
25 Commits
mocking-wi
...
master
Author | SHA1 | Date | |
---|---|---|---|
|
086236f719 | ||
|
fcf7c24b86 | ||
|
9c034a6100 | ||
|
d89541fe3a | ||
|
19a6869c55 | ||
|
1f4029dad2 | ||
|
e6395483e2 | ||
|
855880617d | ||
|
ac12992c60 | ||
|
4c2c0b7220 | ||
|
c7d066790e | ||
|
b2f34a6b01 | ||
37744733b4 | |||
b39e47f4ea | |||
c02df46440 | |||
271f445aef | |||
df3f2a3049 | |||
ce1d8f4ab6 | |||
f919726e4d | |||
67907d7cfb | |||
30f5e576d0 | |||
01392a2c20 | |||
|
dd4bf59e41 | ||
f6d98e472e | |||
920c160632 |
19
LICENSE
Normal file
19
LICENSE
Normal file
|
@ -0,0 +1,19 @@
|
|||
MIT License Copyright (c) <year> <copyright holders>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is furnished
|
||||
to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice (including the next
|
||||
paragraph) shall be included in all copies or substantial portions of the
|
||||
Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
|
||||
OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
|
||||
OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
126
README.md
126
README.md
|
@ -1,6 +1,6 @@
|
|||
# easy-rss-parser
|
||||
# listener rss
|
||||
|
||||
A lightweight library to give some additions for the [rss-parser package](https://github.com/rbren/rss-parser).
|
||||
A lightweight library to make simple actions with a RSS feed.
|
||||
|
||||
# USAGE
|
||||
|
||||
|
@ -10,15 +10,15 @@ You can parse RSS from a URL with some custom data.
|
|||
An example :
|
||||
|
||||
```js
|
||||
const easyParser = require("easy-rss-parser");
|
||||
const ListenerRss = easyParser.ListenerRss;
|
||||
const ListenerModule = require("ListenerRSS");
|
||||
const ListenerRss = ListenerModule.ListenerRss;
|
||||
|
||||
let listener = new ListenerRss("my-test-service", "fake.rss.service");
|
||||
const listener = new ListenerRss({
|
||||
address: "fake.rss.service"
|
||||
});
|
||||
|
||||
// make a request to the adr 'fake.rss.service'
|
||||
myListener.fetchRSS().then((obj, err) => {
|
||||
// some act
|
||||
});
|
||||
const feed = await myListener.fetchRSS();
|
||||
```
|
||||
|
||||
## Recurrent usage
|
||||
|
@ -27,16 +27,19 @@ You can parse RSS from a URL each n times.
|
|||
An example :
|
||||
|
||||
```js
|
||||
const easyParser = require("easy-rss-parser");
|
||||
const ListenerRss = easyParser.ListenerRss;
|
||||
const ListenerModule = require("ListenerRSS");
|
||||
const ListenerRss = ListenerModule.ListenerRss;
|
||||
|
||||
let listener = new ListenerRss("my-test-service", "fake.rss.service", 5 * 60);
|
||||
const listener = new ListenerRss({
|
||||
address: "fake.rss.service"
|
||||
});
|
||||
|
||||
let callback_fun = (obj, err) => {
|
||||
// some act
|
||||
};
|
||||
// call callback_fun each 5 minutes
|
||||
listener.start(callback_fun);
|
||||
listener.on("update", feed => { /* ... */ });
|
||||
listener.on("error", err => { /* ... */ });
|
||||
listener.on("newEntries", feedEntries => { /* ... */ });
|
||||
|
||||
|
||||
listener.start();
|
||||
|
||||
/*...*/
|
||||
|
||||
|
@ -45,34 +48,25 @@ listener.stop();
|
|||
|
||||
# Documentation
|
||||
|
||||
## ListenerRSSInfo
|
||||
## ListenerRss.Config
|
||||
|
||||
A class to structure listener's data.
|
||||
An interface to structure listener's data.
|
||||
|
||||
### Constructor
|
||||
|
||||
`constructor(name, address, timeloop, customfields)`
|
||||
|
||||
- name : the service name
|
||||
- address : the service address
|
||||
- [optional] timeloop : time to wait between 2 request in seconds (default 5 minutes)
|
||||
- [optional] customfields : to notice field who's custom to the service (default blank)
|
||||
[cf annexe CustomFields](#customfields)
|
||||
- [optional] lastEntriesLinks : to specify an predefined history.
|
||||
|
||||
## ListenerRSS
|
||||
|
||||
### Constructor
|
||||
|
||||
`constructor(listenerRSSInfo)`
|
||||
`constructor(ListenerRss.Config)`
|
||||
|
||||
- listenerRSSInfo : object from the ListenerRSSInfo's class.
|
||||
|
||||
`constructor(name, address, timeloop, customfields)`
|
||||
|
||||
- name : the service name
|
||||
- address : the service address
|
||||
- [optional] timeloop : time to wait between 2 request in seconds (default 5 minutes)
|
||||
- [optional] customfields : to notice field who's custom to the service (default blank)
|
||||
- ListenerRss.Config : object from the ListenerRss.Config's class.
|
||||
|
||||
### fetchRSS()
|
||||
|
||||
|
@ -85,26 +79,45 @@ object who's contain the data. [cf Annexe Output](#output)
|
|||
|
||||
#### Issues
|
||||
|
||||
Return an error if the server can't be resolved.
|
||||
Reject the promise if the server can't be resolved.
|
||||
|
||||
### start(callbackFun)
|
||||
### start()
|
||||
|
||||
This function will execute the callbackFun each time loop.
|
||||
This function will call the `update` event to each success update, the
|
||||
`error` event to each fail update, and the `newEntries` event for
|
||||
each update who contains a new item.
|
||||
|
||||
#### Parameter
|
||||
#### Events
|
||||
|
||||
The `callbackFun` is the function who's going to be called each time loop. She need to be under the shape :
|
||||
Each event take one arg into the callback function.
|
||||
|
||||
```js
|
||||
(obj, err) => {
|
||||
/*...*/
|
||||
};
|
||||
listener.on("update", feed => { /* ... */ });
|
||||
listener.on("error", err => { /* ... */ });
|
||||
listener.on("newEntries", feedEntries => { /* ... */ });
|
||||
|
||||
```
|
||||
|
||||
#### update
|
||||
|
||||
It used a callback who receive the received object entirely inside an object.
|
||||
|
||||
#### error
|
||||
|
||||
It used a callback who receive an error object.
|
||||
|
||||
#### newEntries
|
||||
|
||||
It used a callback who receive only new entries inside an array.
|
||||
|
||||
### stop()
|
||||
|
||||
This function will stop the execution of the callbackFun each time loop.
|
||||
|
||||
### getProperty()
|
||||
|
||||
This function will return a ListenerRss.Config (a.k.a. a JSON object) item corresponding to the internal configuration of the class.
|
||||
|
||||
# Annexe
|
||||
|
||||
## CustomFields
|
||||
|
@ -146,21 +159,24 @@ In this case it's useless to specify the parent field, so you can just omit the
|
|||
Here an example of what type of json object is output during a fetch :
|
||||
|
||||
```json
|
||||
feedUrl: 'fake.rrs.service'
|
||||
title: 'myFakeApiTitle'
|
||||
description: 'My Fake api desc'
|
||||
link: 'fake.rrs.service'
|
||||
items:
|
||||
- title: 'My last item'
|
||||
link: 'fake.rrs.service/item1'
|
||||
pubDate: 'Thu, 12 Nov 2015 21:16:39 +0000'
|
||||
creator: 'someone'
|
||||
content: '<a href="http://example.com">this is a link</a> & <b>this is bold text</b>'
|
||||
contentSnippet: 'this is a link & this is bold text'
|
||||
guid: 'fake.rrs.service/item1'
|
||||
categories:
|
||||
- test
|
||||
- npm
|
||||
- fakeInfos
|
||||
isoDate: '2015-11-12T21:16:39.000Z'
|
||||
{
|
||||
"feedUrl": "fake.rrs.service",
|
||||
"title": "myFakeApiTitle",
|
||||
"description": "My Fake api desc",
|
||||
"link": "fake.rrs.service",
|
||||
"items": [
|
||||
{
|
||||
"title": "My last item",
|
||||
"link": "fake.rrs.service/item1",
|
||||
"pubDate": "Thu, 12 Nov 2015 21:16:39 +0000",
|
||||
"creator": "someone",
|
||||
"content": "<a href=\"http://example.com\">this is a link</a> & <b>this is bold text</b>",
|
||||
"contentSnippet": "this is a link & this is bold text",
|
||||
"guid": "fake.rrs.service/item1",
|
||||
"categories": ["test", "npm", "fakeInfos"],
|
||||
"isoDate": "2015-11-12T21:16:39.000Z"
|
||||
}
|
||||
/*Some Others items*/
|
||||
]
|
||||
}
|
||||
```
|
||||
|
|
3804
package-lock.json
generated
3804
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
24
package.json
24
package.json
|
@ -1,11 +1,14 @@
|
|||
{
|
||||
"name": "listener-rss",
|
||||
"version": "1.0.0",
|
||||
"description": "A lightweight library to give some additions for the [rss-parser package](https://github.com/rbren/rss-parser).",
|
||||
"main": "index.js",
|
||||
"version": "0.0.2",
|
||||
"description": "A lightweight library to create a listener from a rss feed.",
|
||||
"main": "build/index.js",
|
||||
"types": "build/index.d.ts",
|
||||
"scripts": {
|
||||
"test": "cross-env TS_NODE_PROJECT='./tests/tsconfig.json' mocha --require ts-node/register ./tests/**/*-spec.ts",
|
||||
"build": "tsc -p ./src"
|
||||
"test": "env TS_NODE_PROJECT='./tests/tsconfig.json' mocha --require ts-node/register ./tests/**/*-spec.ts",
|
||||
"build": "tsc",
|
||||
"lint": "eslint 'tests/**/*.ts' 'src/**/*.ts'",
|
||||
"prepublish": "npm run-script build && npm run-script lint && npm test"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@ -15,10 +18,13 @@
|
|||
"rss",
|
||||
"rss-parser"
|
||||
],
|
||||
"author": "Amaury Joly <amaury.joly@hotmail.com>",
|
||||
"author": "Amaury Joly <joly.amaury@hotmail.com>",
|
||||
"contributors": [
|
||||
"Florent Fayolle <florent.git@zeteo.me>"
|
||||
],
|
||||
"files": [
|
||||
"build/"
|
||||
],
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@types/chai": "^4.2.14",
|
||||
|
@ -26,7 +32,6 @@
|
|||
"@types/node": "^14.14.25",
|
||||
"@typescript-eslint/eslint-plugin": "^4.14.2",
|
||||
"@typescript-eslint/parser": "^4.14.2",
|
||||
"cross-env": "7.0.3",
|
||||
"chai": "4.3.0",
|
||||
"eslint": "^7.19.0",
|
||||
"eslint-config-airbnb-base": "^14.2.1",
|
||||
|
@ -38,12 +43,13 @@
|
|||
"prettier": "2.2.1",
|
||||
"proxyquire": "2.1.3",
|
||||
"sinon-chai": "3.5.0",
|
||||
"ts-sinon": "2.0.1",
|
||||
"ts-mock-imports": "1.3.3",
|
||||
"ts-node": "9.1.1",
|
||||
"ts-sinon": "2.0.1",
|
||||
"tsc-watch": "^4.2.9",
|
||||
"typescript": "^4.1.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"rss-parser": "3.11.0"
|
||||
"rss-parser": "^3.11.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
export interface ListenerRSSInfos {
|
||||
readonly name: string; // name of the listener
|
||||
readonly address: string; // feed's address
|
||||
readonly timeloop?: number; // update time RSS feed
|
||||
readonly customfields?: { [key: string]: string | string[] }; // rss fields custom
|
||||
}
|
|
@ -1,2 +1 @@
|
|||
export { ListenerRss } from "./listener-rss";
|
||||
export { ListenerRSSInfos } from "./Models/ListenerRSSInfos";
|
||||
|
|
|
@ -1,89 +1,104 @@
|
|||
import Parser from "rss-parser/index";
|
||||
import { ListenerRSSInfos as ListenerInfo } from "./Models/ListenerRSSInfos";
|
||||
import Parser from "rss-parser";
|
||||
import EventEmitter from "events";
|
||||
|
||||
const DEFAULT_TIMELOOP: number = 5 * 60; // default timeloop is 5 min
|
||||
|
||||
namespace ListenerRss {
|
||||
export type Config = {
|
||||
address: string; // feed's address
|
||||
timeloop?: number; // update time RSS feed
|
||||
customfields?: { [key: string]: string | string[] }; // rss fields custom
|
||||
lastEntriesLinks?: string[]; // links from lastentries
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit 'update' when he's making a fetch during the start fun
|
||||
* Emit 'update_err' when the fetch has an issue
|
||||
* Emit 'error' when the fetch has an issue
|
||||
* Emit 'newEntries' when the fetch has new entris
|
||||
*/
|
||||
export class ListenerRss extends EventEmitter {
|
||||
name: string = "";
|
||||
class ListenerRss extends EventEmitter {
|
||||
address: string = "";
|
||||
timeloop: number = DEFAULT_TIMELOOP; // time in seconds
|
||||
customfields?: { [key: string]: string[] | string };
|
||||
|
||||
// private fields
|
||||
parser: Parser | undefined = undefined;
|
||||
parser: Parser;
|
||||
loopRunning: boolean = false;
|
||||
lastEntriesLinks: string[] = [];
|
||||
private firstUpdate = true;
|
||||
|
||||
/**
|
||||
* @brief constructor
|
||||
* @param config ListenerRSSInfos interface who's contain the ListenerInfos
|
||||
* @param config ListenerRSSInfos interface who contains the ListenerInfos
|
||||
*/
|
||||
constructor(config: ListenerInfo) {
|
||||
constructor(config: ListenerRss.Config) {
|
||||
super();
|
||||
|
||||
this.setData(config);
|
||||
this.setParser();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Private function. Is useed to initilize the parser object with the customfields var
|
||||
*/
|
||||
setParser() {
|
||||
// set parser
|
||||
this.parser = new Parser(
|
||||
this.customfields !== undefined
|
||||
? {
|
||||
customFields: {
|
||||
feed: [],
|
||||
item: Object.entries(this.customfields).map(([, value]) => {
|
||||
return Array.isArray(value) ? value[0] : value;
|
||||
}),
|
||||
},
|
||||
}
|
||||
: {}
|
||||
); // if customfield is set -> let's set the parser with, else let the option empty
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Private function. Initialized the listener with an ListenerRSSInfos interface
|
||||
* @param infos ListenerRSSInfos interface who's contain the ListenerInfos
|
||||
*/
|
||||
setData(infos: ListenerInfo) {
|
||||
// Set data
|
||||
this.name = infos.name;
|
||||
this.address = infos.address;
|
||||
this.address = config.address;
|
||||
this.timeloop =
|
||||
infos.timeloop === undefined ? DEFAULT_TIMELOOP : infos.timeloop;
|
||||
this.customfields = infos.customfields;
|
||||
config.timeloop === undefined ? DEFAULT_TIMELOOP : config.timeloop;
|
||||
this.customfields = config.customfields;
|
||||
this.lastEntriesLinks =
|
||||
config.lastEntriesLinks === undefined ? [] : config.lastEntriesLinks;
|
||||
|
||||
this.parser = this.generateParser();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief use the parseURL function from rss-parser with the objects datas
|
||||
* @brief Private function. Is used to initilize the parser object with the customfields var
|
||||
*/
|
||||
generateParser() {
|
||||
const parserConfig = this.customfields && {
|
||||
customFields: {
|
||||
feed: [],
|
||||
item: Object.entries(this.customfields).map(([, value]) => {
|
||||
return Array.isArray(value) ? value[0] : value;
|
||||
}),
|
||||
},
|
||||
};
|
||||
return new Parser(parserConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief use the parseURL function from rss-parser with the objects data
|
||||
* @return return a promise with the received data
|
||||
*/
|
||||
fetchRSS(): Promise<Parser.Output<any>> {
|
||||
if (this.parser !== undefined && this.address !== undefined) {
|
||||
return this.parser.parseURL(this.address).catch((err) => {
|
||||
throw new Error("bad address or no access : " + err);
|
||||
});
|
||||
} else throw new Error("listener must be first initialized");
|
||||
return this.parser.parseURL(this.address);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief call the callback function each looptime
|
||||
* @param callback function who's going to be called with the latest get
|
||||
*/
|
||||
start(): void {
|
||||
if (this.loopRunning) return;
|
||||
|
||||
this.loopRunning = true;
|
||||
|
||||
const fun: () => void = () => {
|
||||
this.fetchRSS()
|
||||
.then((obj: { [key: string]: any }) => this.emit("update", obj))
|
||||
.catch((err) => this.emit("update_err", err));
|
||||
const fun: () => void = async () => {
|
||||
await Promise.resolve(
|
||||
await this.fetchRSS()
|
||||
.then((obj: { [key: string]: any }) => {
|
||||
this.emit("update", obj);
|
||||
const updatedEntriesLinks = obj.items.map(
|
||||
(item: { link: string }) => item.link
|
||||
);
|
||||
|
||||
const newEntries = obj.items.filter(
|
||||
(item: { link: string }) =>
|
||||
!this.lastEntriesLinks.includes(item.link)
|
||||
);
|
||||
|
||||
this.lastEntriesLinks = updatedEntriesLinks;
|
||||
|
||||
if (!this.firstUpdate && newEntries.length !== 0)
|
||||
this.emit("newEntries", newEntries);
|
||||
|
||||
this.firstUpdate = false;
|
||||
})
|
||||
.catch((err) => this.emit("error", err))
|
||||
);
|
||||
};
|
||||
|
||||
(async () => {
|
||||
|
@ -100,4 +115,19 @@ export class ListenerRss extends EventEmitter {
|
|||
stop(): void {
|
||||
this.loopRunning = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief parse the datas inti a ListenerRSSInfos object
|
||||
* @return return a ListenerRSSInfos object
|
||||
*/
|
||||
getProperty(): ListenerRss.Config {
|
||||
return {
|
||||
address: this.address,
|
||||
customfields: this.customfields,
|
||||
lastEntriesLinks: this.lastEntriesLinks,
|
||||
timeloop: this.timeloop,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export { ListenerRss };
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
{
|
||||
"extends": "../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"moduleResolution": "node"
|
||||
},
|
||||
"exclude": [
|
||||
"build/",
|
||||
"node_modules"
|
||||
]
|
||||
}
|
|
@ -1,53 +1,43 @@
|
|||
// external lib
|
||||
import Parser from "rss-parser";
|
||||
import * as Parser from "rss-parser";
|
||||
|
||||
// tested class
|
||||
import {
|
||||
ListenerRSSInfos as ListenerRRSInfo,
|
||||
ListenerRss as Listeners,
|
||||
} from "./../src/index";
|
||||
import { ListenerRss } from "../";
|
||||
|
||||
// Unit test
|
||||
import assert from "assert";
|
||||
import * as chai from "chai";
|
||||
import * as sinon from "ts-sinon";
|
||||
|
||||
const sinonChai = require("sinon-chai");
|
||||
import sinon from "ts-sinon";
|
||||
import sinonChai from "sinon-chai";
|
||||
import { ImportMock, InPlaceMockManager } from "ts-mock-imports";
|
||||
|
||||
chai.use(sinonChai);
|
||||
|
||||
const expect = chai.expect;
|
||||
|
||||
describe("test class RSS: jsonfile", function () {
|
||||
// let myListener: Listeners | undefined = undefined;
|
||||
|
||||
const infosListener: ListenerRRSInfo = {
|
||||
name: "my-test-service",
|
||||
const infosListener: ListenerRss.Config = {
|
||||
address: "fake.rss.service",
|
||||
timeloop: 15,
|
||||
customfields: {
|
||||
description: ["media:group", "media:description"],
|
||||
icon: ["media:group", "media:thumbnail"],
|
||||
},
|
||||
lastEntriesLinks: ["my_url_02.com"],
|
||||
};
|
||||
|
||||
// parseURL tests
|
||||
let stubListener: sinon.StubbedInstance<Listeners>;
|
||||
let stubParser: sinon.StubbedInstance<Parser>;
|
||||
|
||||
const mockedRSSOutput: Parser.Output<{
|
||||
"media:group": { [key: string]: string | [any] };
|
||||
}> = {
|
||||
const mockedRSSOutput: Parser.Output<any> = {
|
||||
items: [
|
||||
{
|
||||
title: "my title 00",
|
||||
title: "my title 02",
|
||||
"media:group": {
|
||||
"media:description": "my description 00",
|
||||
"media:description": "my description 02",
|
||||
"media:thumbnail": [
|
||||
{ $: { height: 360, width: 420, url: "my_image00.jpg" } },
|
||||
{ $: { height: 360, width: 420, url: "my_image02.jpg" } },
|
||||
],
|
||||
},
|
||||
link: "my_url_00.com",
|
||||
pubDate: "myDate00",
|
||||
link: "my_url_02.com",
|
||||
pubDate: "myDate02",
|
||||
},
|
||||
{
|
||||
title: "my title 01",
|
||||
|
@ -61,86 +51,34 @@ describe("test class RSS: jsonfile", function () {
|
|||
pubDate: "myDate01",
|
||||
},
|
||||
{
|
||||
title: "my title 02",
|
||||
title: "my title 00",
|
||||
"media:group": {
|
||||
"media:description": "my description 02",
|
||||
"media:description": "my description 00",
|
||||
"media:thumbnail": [
|
||||
{ $: { height: 360, width: 420, url: "my_image02.jpg" } },
|
||||
{ $: { height: 360, width: 420, url: "my_image00.jpg" } },
|
||||
],
|
||||
},
|
||||
link: "my_url_02.com",
|
||||
pubDate: "myDate02",
|
||||
link: "my_url_00.com",
|
||||
pubDate: "myDate00",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
/**
|
||||
* The function create my Stubs for my Listener and my Parser
|
||||
*/
|
||||
function fun_initStub(
|
||||
myListener: Listeners
|
||||
): sinon.StubbedInstance<Listeners> {
|
||||
stubListener = sinon.stubObject<Listeners>(myListener, ["setParser"]);
|
||||
stubListener.setParser.callsFake(() => {
|
||||
if (stubListener.parser !== undefined) {
|
||||
stubParser = sinon.stubObject<Parser>(stubListener.parser, [
|
||||
"parseURL",
|
||||
]);
|
||||
stubParser.parseURL
|
||||
.withArgs(infosListener.address)
|
||||
.resolves(mockedRSSOutput);
|
||||
stubParser.parseURL
|
||||
.withArgs("bad.rss.service")
|
||||
.rejects(new Error("connect ECONNREFUSED 127.0.0.1:80"));
|
||||
}
|
||||
stubListener.parser = stubParser;
|
||||
});
|
||||
stubListener.setParser();
|
||||
// stubListener.fetchRSS.returns(stubParser.parseURL(stubListener.address));
|
||||
return stubListener;
|
||||
}
|
||||
|
||||
// afterEach(function () {
|
||||
// // restore stubs
|
||||
// myListener = undefined;
|
||||
// });
|
||||
|
||||
describe("Building Ytb listener", function () {
|
||||
it("The build without issues (infosListener parameters)", function () {
|
||||
let myListener = new Listeners(infosListener);
|
||||
|
||||
// assertions
|
||||
// myListener data
|
||||
expect(myListener.timeloop).to.eql(15);
|
||||
expect(myListener.name).to.eql("my-test-service");
|
||||
expect(myListener.address).to.eql("fake.rss.service");
|
||||
expect(myListener.customfields).to.eql({
|
||||
description: ["media:group", "media:description"],
|
||||
icon: ["media:group", "media:thumbnail"],
|
||||
});
|
||||
expect(myListener.parser)
|
||||
.to.have.property("options")
|
||||
.to.have.property("customFields")
|
||||
.to.be.eql({
|
||||
feed: [],
|
||||
item: ["media:group", "media:group"],
|
||||
});
|
||||
});
|
||||
it("The build without issues (raw infos : 4 params)", function () {
|
||||
let myListener = new Listeners({
|
||||
name: "my-test-service",
|
||||
it("builds with 4 params", function () {
|
||||
const myListener = new ListenerRss({
|
||||
address: "fake.rss.service",
|
||||
timeloop: 15,
|
||||
customfields: {
|
||||
description: ["media:group", "media:description"],
|
||||
icon: ["media:group", "media:thumbnail"],
|
||||
},
|
||||
lastEntriesLinks: ["my_url_02.com"],
|
||||
});
|
||||
|
||||
// assertions
|
||||
// myListener data
|
||||
expect(myListener.timeloop).to.eql(15);
|
||||
expect(myListener.name).to.eql("my-test-service");
|
||||
expect(myListener.address).to.eql("fake.rss.service");
|
||||
expect(myListener.customfields).to.eql({
|
||||
description: ["media:group", "media:description"],
|
||||
|
@ -153,17 +91,19 @@ describe("test class RSS: jsonfile", function () {
|
|||
feed: [],
|
||||
item: ["media:group", "media:group"],
|
||||
});
|
||||
expect(myListener.lastEntriesLinks).to.be.eql(["my_url_02.com"]);
|
||||
});
|
||||
it("The build without issues (raw infos : just 2 params)", function () {
|
||||
let myListener = new Listeners({
|
||||
name: "my-test-service",
|
||||
|
||||
it("builds with 3 params (no custom fields)", function () {
|
||||
const myListener = new ListenerRss({
|
||||
address: "fake.rss.service",
|
||||
timeloop: 15,
|
||||
lastEntriesLinks: ["my_url_02.com"],
|
||||
});
|
||||
|
||||
// assertions
|
||||
// myListener data
|
||||
expect(myListener.timeloop).to.eql(5 * 60);
|
||||
expect(myListener.name).to.eql("my-test-service");
|
||||
expect(myListener.timeloop).to.eql(15);
|
||||
expect(myListener.address).to.eql("fake.rss.service");
|
||||
expect(myListener.customfields).to.eql(undefined);
|
||||
expect(myListener.parser)
|
||||
|
@ -173,194 +113,400 @@ describe("test class RSS: jsonfile", function () {
|
|||
feed: [],
|
||||
item: [],
|
||||
});
|
||||
});
|
||||
});
|
||||
it("The build without issues (raw infos : just 3 params (no custom fields))", function () {
|
||||
let myListener = new Listeners({
|
||||
name: "my-test-service",
|
||||
address: "fake.rss.service",
|
||||
timeloop: 15,
|
||||
expect(myListener.lastEntriesLinks).to.be.eql(["my_url_02.com"]);
|
||||
});
|
||||
|
||||
// assertions
|
||||
// myListener data
|
||||
expect(myListener.timeloop).to.eql(15);
|
||||
expect(myListener.name).to.eql("my-test-service");
|
||||
expect(myListener.address).to.eql("fake.rss.service");
|
||||
expect(myListener.customfields).to.eql(undefined);
|
||||
expect(myListener.parser)
|
||||
.to.have.property("options")
|
||||
.to.have.property("customFields")
|
||||
.to.be.eql({
|
||||
feed: [],
|
||||
item: [],
|
||||
it("build with 3 params (no timeloop)", function () {
|
||||
const myListener = new ListenerRss({
|
||||
address: "fake.rss.service",
|
||||
customfields: {
|
||||
description: ["media:group", "media:description"],
|
||||
icon: ["media:group", "media:thumbnail"],
|
||||
},
|
||||
lastEntriesLinks: ["my_url_02.com"],
|
||||
});
|
||||
});
|
||||
it("The build without issues (raw infos : just 3 params (no timeloop))", function () {
|
||||
let myListener = new Listeners({
|
||||
name: "my-test-service",
|
||||
address: "fake.rss.service",
|
||||
customfields: {
|
||||
|
||||
// assertions
|
||||
// myListener data
|
||||
expect(myListener.timeloop).to.eql(5 * 60);
|
||||
expect(myListener.address).to.eql("fake.rss.service");
|
||||
expect(myListener.customfields).to.eql({
|
||||
description: ["media:group", "media:description"],
|
||||
icon: ["media:group", "media:thumbnail"],
|
||||
},
|
||||
});
|
||||
expect(myListener.parser)
|
||||
.to.have.property("options")
|
||||
.to.have.property("customFields")
|
||||
.to.be.eql({
|
||||
feed: [],
|
||||
item: ["media:group", "media:group"],
|
||||
});
|
||||
expect(myListener.lastEntriesLinks).to.be.eql(["my_url_02.com"]);
|
||||
});
|
||||
|
||||
// assertions
|
||||
// myListener data
|
||||
expect(myListener.timeloop).to.eql(5 * 60);
|
||||
expect(myListener.name).to.eql("my-test-service");
|
||||
expect(myListener.address).to.eql("fake.rss.service");
|
||||
expect(myListener.customfields).to.eql({
|
||||
description: ["media:group", "media:description"],
|
||||
icon: ["media:group", "media:thumbnail"],
|
||||
});
|
||||
expect(myListener.parser)
|
||||
.to.have.property("options")
|
||||
.to.have.property("customFields")
|
||||
.to.be.eql({
|
||||
feed: [],
|
||||
item: ["media:group", "media:group"],
|
||||
it("build with 3 params (no lastEntries)", function () {
|
||||
const myListener = new ListenerRss({
|
||||
address: "fake.rss.service",
|
||||
customfields: {
|
||||
description: ["media:group", "media:description"],
|
||||
icon: ["media:group", "media:thumbnail"],
|
||||
},
|
||||
});
|
||||
|
||||
// assertions
|
||||
// myListener data
|
||||
expect(myListener.timeloop).to.eql(5 * 60);
|
||||
expect(myListener.address).to.eql("fake.rss.service");
|
||||
expect(myListener.customfields).to.eql({
|
||||
description: ["media:group", "media:description"],
|
||||
icon: ["media:group", "media:thumbnail"],
|
||||
});
|
||||
expect(myListener.parser)
|
||||
.to.have.property("options")
|
||||
.to.have.property("customFields")
|
||||
.to.be.eql({
|
||||
feed: [],
|
||||
item: ["media:group", "media:group"],
|
||||
});
|
||||
expect(myListener.lastEntriesLinks).to.be.eql([]);
|
||||
});
|
||||
|
||||
it("builds with 1 params (only address)", function () {
|
||||
const myListener = new ListenerRss({
|
||||
address: "fake.rss.service",
|
||||
});
|
||||
|
||||
// assertions
|
||||
// myListener data
|
||||
expect(myListener.timeloop).to.eql(5 * 60);
|
||||
expect(myListener.address).to.eql("fake.rss.service");
|
||||
expect(myListener.customfields).to.eql(undefined);
|
||||
expect(myListener.parser)
|
||||
.to.have.property("options")
|
||||
.to.have.property("customFields")
|
||||
.to.be.eql({
|
||||
feed: [],
|
||||
item: [],
|
||||
});
|
||||
expect(myListener.lastEntriesLinks).to.be.eql([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("fetch some data", function () {
|
||||
it("fetch without issues", function () {
|
||||
let myListener = new Listeners(infosListener);
|
||||
fun_initStub();
|
||||
describe("export property", function () {
|
||||
it("should export properties into a ListenerRSSInfos", function () {
|
||||
// given
|
||||
const myListener = new ListenerRss(infosListener);
|
||||
|
||||
expect(myListener).to.not.be.undefined;
|
||||
if (myListener !== undefined) {
|
||||
// fetch
|
||||
let res = stubListener.fetchRSS();
|
||||
// assertions
|
||||
expect(myListener.getProperty()).to.be.eql(infosListener);
|
||||
});
|
||||
});
|
||||
|
||||
//assertion
|
||||
// calls
|
||||
expect(stubParser.parseURL).to.have.been.calledOnce;
|
||||
expect(stubParser.parseURL).to.have.been.calledWith(
|
||||
infosListener.address
|
||||
);
|
||||
describe("data fetching", function () {
|
||||
it("fetches without issues", async function () {
|
||||
// given
|
||||
const mockManager: InPlaceMockManager<Parser> = ImportMock.mockClassInPlace<Parser>(
|
||||
Parser
|
||||
);
|
||||
const stubParser = mockManager.mock("parseURL");
|
||||
stubParser.resolves(mockedRSSOutput);
|
||||
|
||||
res
|
||||
.then((obj: any) => {
|
||||
expect(obj).to.be.eql(mockedRSSOutput);
|
||||
})
|
||||
.catch((err) => {
|
||||
expect(err).to.be.undefined;
|
||||
});
|
||||
} else throw new Error("Error into the before instruction");
|
||||
const myListener = new ListenerRss(infosListener);
|
||||
|
||||
// when
|
||||
const res = await myListener.fetchRSS();
|
||||
|
||||
// then
|
||||
expect(stubParser).to.have.been.calledOnce;
|
||||
expect(stubParser).to.have.been.calledWith(infosListener.address);
|
||||
expect(res).to.be.eql(mockedRSSOutput);
|
||||
});
|
||||
|
||||
it("fetch with bad address", function () {
|
||||
let myListener = new Listeners({
|
||||
name: "my-test-service",
|
||||
it("rejects when fetching fails", async function () {
|
||||
// given
|
||||
const mockManager: InPlaceMockManager<Parser> = ImportMock.mockClassInPlace<Parser>(
|
||||
Parser
|
||||
);
|
||||
const stubParser = mockManager.mock("parseURL");
|
||||
const err = new Error("connect ECONNREFUSED 127.0.0.1:80");
|
||||
stubParser.rejects(err);
|
||||
|
||||
const myListener = new ListenerRss({
|
||||
address: "bad.rss.service",
|
||||
customfields: {
|
||||
description: ["media:group", "media:description"],
|
||||
icon: ["media:group", "media:thumbnail"],
|
||||
},
|
||||
});
|
||||
let stubListener = fun_initStub(myListener);
|
||||
// fetch
|
||||
let res = stubListener.fetchRSS();
|
||||
|
||||
//assertion
|
||||
// calls
|
||||
expect(stubParser.parseURL).to.have.been.calledOnce;
|
||||
expect(stubParser.parseURL).to.have.been.calledWith("bad.rss.service");
|
||||
// Promise
|
||||
res
|
||||
.then((obj: any) => {
|
||||
expect(obj).to.be.undefined;
|
||||
})
|
||||
.catch((err) => {
|
||||
expect(err).to.be.eql(new Error("connect ECONNREFUSED 127.0.0.1:80"));
|
||||
});
|
||||
// when
|
||||
await assert.rejects(() => myListener.fetchRSS(), err);
|
||||
});
|
||||
});
|
||||
|
||||
describe.skip("start", function () {
|
||||
it("Let's start the timer", async function () {
|
||||
let clock: sinon.default.SinonFakeTimers = sinon.default.useFakeTimers();
|
||||
describe("start", function () {
|
||||
it("fetches immediately the RSS information", async function () {
|
||||
// given
|
||||
const clock = sinon.useFakeTimers();
|
||||
|
||||
const mockManager: InPlaceMockManager<Parser> = ImportMock.mockClassInPlace<Parser>(
|
||||
Parser
|
||||
);
|
||||
const stubParser = mockManager.mock("parseURL");
|
||||
stubParser.resolves(mockedRSSOutput);
|
||||
|
||||
// classic build
|
||||
let myListener = new Listeners({
|
||||
name: "my-test-service",
|
||||
address: "fake.rss.service",
|
||||
timeloop: 60,
|
||||
customfields: {
|
||||
description: ["media:group", "media:description"],
|
||||
icon: ["media:group", "media:thumbnail"],
|
||||
},
|
||||
});
|
||||
let stubListener = fun_initStub(myListener);
|
||||
stubListener.fetchRSS.reset();
|
||||
stubListener.fetchRSS.resolves(mockedRSSOutput);
|
||||
const myListener = new ListenerRss(infosListener);
|
||||
|
||||
//spy
|
||||
let fun_spy: sinon.default.SinonSpy = sinon.default.spy((obj, err) => {
|
||||
expect(obj).to.be.eql(mockedRSSOutput);
|
||||
expect(err).to.be.eql(undefined);
|
||||
});
|
||||
const updateListenerSpy = sinon.spy();
|
||||
|
||||
// start timer
|
||||
stubListener.on("update", (obj) => fun_spy(obj));
|
||||
// stubListener.start(fun_spy);
|
||||
myListener.on("update", updateListenerSpy);
|
||||
|
||||
// wait and assertion
|
||||
// After 1ms
|
||||
myListener.start();
|
||||
|
||||
// when
|
||||
await clock.tickAsync(1);
|
||||
expect(stubListener.fetchRSS).to.have.been.calledOnce;
|
||||
expect(fun_spy).to.have.been.calledOnce;
|
||||
|
||||
// After 60s
|
||||
await clock.tickAsync(59999);
|
||||
expect(stubListener.fetchRSS).to.have.been.calledTwice;
|
||||
expect(fun_spy).to.have.been.calledTwice;
|
||||
|
||||
stubListener.stop();
|
||||
// then
|
||||
expect(updateListenerSpy).to.have.been.calledOnce;
|
||||
expect(updateListenerSpy).to.have.been.calledWith(mockedRSSOutput);
|
||||
expect(stubParser).to.have.calledWith(myListener.address);
|
||||
myListener.stop();
|
||||
});
|
||||
|
||||
it("Let's start the timer (with a bad address)", async function () {
|
||||
let clock: sinon.default.SinonFakeTimers = sinon.default.useFakeTimers();
|
||||
it("has fetched multiple times after a while", async function () {
|
||||
// given
|
||||
const clock = sinon.useFakeTimers();
|
||||
|
||||
const mockManager: InPlaceMockManager<Parser> = ImportMock.mockClassInPlace<Parser>(
|
||||
Parser
|
||||
);
|
||||
const stubParser = mockManager.mock("parseURL");
|
||||
stubParser.resolves(mockedRSSOutput);
|
||||
|
||||
// classic build
|
||||
let myListener = new Listeners({
|
||||
name: "my-test-service",
|
||||
address: "fake.rss.service",
|
||||
timeloop: 60,
|
||||
customfields: {
|
||||
description: ["media:group", "media:description"],
|
||||
icon: ["media:group", "media:thumbnail"],
|
||||
},
|
||||
const myListener = new ListenerRss({
|
||||
...infosListener,
|
||||
timeloop: 15,
|
||||
});
|
||||
let stubListener = fun_initStub(myListener);
|
||||
stubListener.fetchRSS.reset();
|
||||
stubListener.fetchRSS.rejects(
|
||||
new Error("connect ECONNREFUSED 127.0.0.1:80")
|
||||
);
|
||||
|
||||
//spy
|
||||
let fun_spy: sinon.default.SinonSpy = sinon.default.spy((obj, err) => {
|
||||
expect(obj).to.be.eql(undefined);
|
||||
expect(err).to.not.be.eql(undefined);
|
||||
});
|
||||
const updateListenerSpy: sinon.SinonSpy = sinon.spy();
|
||||
const newRSSOutput = {
|
||||
...mockedRSSOutput,
|
||||
items: mockedRSSOutput.items.concat({
|
||||
title: "my title 03",
|
||||
"media:group": {
|
||||
"media:description": "my description 03",
|
||||
"media:thumbnail": [
|
||||
{ $: { height: 360, width: 420, url: "my_image03.jpg" } },
|
||||
],
|
||||
},
|
||||
link: "my_url_03.com",
|
||||
pubDate: "myDate03",
|
||||
}),
|
||||
};
|
||||
|
||||
// start timer
|
||||
// stubListener.start(fun_spy);
|
||||
myListener.on("update", updateListenerSpy);
|
||||
|
||||
myListener.start();
|
||||
|
||||
// when
|
||||
await clock.tickAsync(1);
|
||||
stubParser.resolves(newRSSOutput);
|
||||
await clock.tickAsync(60000);
|
||||
|
||||
// then
|
||||
expect(updateListenerSpy).to.have.been.callCount(5);
|
||||
expect(updateListenerSpy.firstCall).to.have.been.calledWith(
|
||||
mockedRSSOutput
|
||||
);
|
||||
expect(updateListenerSpy.secondCall).to.have.been.calledWith(
|
||||
newRSSOutput
|
||||
);
|
||||
|
||||
myListener.stop();
|
||||
});
|
||||
|
||||
it("notifies with a 'error' when fetching fails", async function () {
|
||||
const clock = sinon.useFakeTimers();
|
||||
|
||||
const mockManager: InPlaceMockManager<Parser> = ImportMock.mockClassInPlace<Parser>(
|
||||
Parser
|
||||
);
|
||||
const stubParser = mockManager.mock("parseURL");
|
||||
const expectedErr = new Error("connect ECONNREFUSED 127.0.0.1:80");
|
||||
stubParser.rejects(expectedErr);
|
||||
|
||||
// classic build
|
||||
const myListener = new ListenerRss({
|
||||
...infosListener,
|
||||
timeloop: 60,
|
||||
});
|
||||
|
||||
//spy
|
||||
const updateListenerSpy = sinon.spy();
|
||||
const updateErrorListenerSpy = sinon.spy();
|
||||
|
||||
myListener.on("error", updateErrorListenerSpy);
|
||||
myListener.on("update", updateListenerSpy);
|
||||
|
||||
// start timer
|
||||
myListener.start();
|
||||
|
||||
// wait and assertion
|
||||
// After 1ms
|
||||
await clock.tickAsync(1);
|
||||
expect(stubListener.fetchRSS).to.have.been.calledOnce;
|
||||
expect(fun_spy).to.have.been.calledOnce;
|
||||
expect(updateErrorListenerSpy).to.have.been.calledOnce;
|
||||
expect(updateListenerSpy).to.not.have.been.called;
|
||||
expect(updateErrorListenerSpy).to.have.been.calledWith(expectedErr);
|
||||
|
||||
// After 60s
|
||||
await clock.tickAsync(59999);
|
||||
expect(stubListener.fetchRSS).to.have.been.calledTwice;
|
||||
expect(fun_spy).to.have.been.calledTwice;
|
||||
await clock.tickAsync(60000);
|
||||
expect(updateErrorListenerSpy).to.have.been.calledTwice;
|
||||
expect(updateListenerSpy).to.not.have.been.called;
|
||||
|
||||
stubListener.stop();
|
||||
// When the service is back
|
||||
stubParser.resolves(mockedRSSOutput);
|
||||
await clock.tickAsync(60000);
|
||||
|
||||
expect(updateListenerSpy).to.have.been.calledOnce;
|
||||
expect(updateListenerSpy).to.have.been.calledWith(mockedRSSOutput);
|
||||
|
||||
myListener.stop();
|
||||
});
|
||||
|
||||
it("notifies with 'newEntries' when a new entry is detected", async function () {
|
||||
// given
|
||||
const clock = sinon.useFakeTimers();
|
||||
|
||||
const mockManager = ImportMock.mockClassInPlace<Parser>(Parser);
|
||||
const stubParser = mockManager.mock("parseURL");
|
||||
stubParser.resolves(mockedRSSOutput);
|
||||
const newEntry = {
|
||||
title: "my title 03",
|
||||
"media:group": {
|
||||
"media:description": "my description 03",
|
||||
"media:thumbnail": [
|
||||
{ $: { height: 360, width: 420, url: "my_image03.jpg" } },
|
||||
],
|
||||
},
|
||||
link: "my_url_03.com",
|
||||
pubDate: "myDate03",
|
||||
};
|
||||
const newRSSOutput = {
|
||||
...mockedRSSOutput,
|
||||
items: [newEntry, ...mockedRSSOutput.items],
|
||||
};
|
||||
|
||||
// classic build
|
||||
const myListener = new ListenerRss({
|
||||
...infosListener,
|
||||
timeloop: 60,
|
||||
});
|
||||
|
||||
//spy
|
||||
const updateListenerSpy = sinon.spy();
|
||||
const newEntriesListenerSpy = sinon.spy();
|
||||
|
||||
myListener.on("update", updateListenerSpy);
|
||||
myListener.on("newEntries", newEntriesListenerSpy);
|
||||
|
||||
// when
|
||||
myListener.start();
|
||||
|
||||
// then
|
||||
await clock.tickAsync(1);
|
||||
expect(updateListenerSpy).to.have.been.calledOnce;
|
||||
expect(newEntriesListenerSpy).to.not.have.been.called;
|
||||
|
||||
// given
|
||||
stubParser.resolves(newRSSOutput);
|
||||
|
||||
// then
|
||||
await clock.tickAsync(60000);
|
||||
expect(updateListenerSpy).to.have.been.calledTwice;
|
||||
expect(newEntriesListenerSpy).to.have.been.calledOnce;
|
||||
expect(newEntriesListenerSpy).to.have.been.calledWith([newEntry]);
|
||||
|
||||
// given
|
||||
newEntriesListenerSpy.resetHistory();
|
||||
|
||||
// then
|
||||
await clock.tickAsync(60000);
|
||||
expect(updateListenerSpy).to.have.been.calledThrice;
|
||||
expect(updateListenerSpy).to.have.been.calledWith(mockedRSSOutput);
|
||||
expect(newEntriesListenerSpy).to.not.have.been.called;
|
||||
|
||||
myListener.stop();
|
||||
});
|
||||
|
||||
it("not notifies with 'newEntries' when a new entry is detected but she's already in the history", async function () {
|
||||
// given
|
||||
const clock = sinon.useFakeTimers();
|
||||
|
||||
const mockManager = ImportMock.mockClassInPlace<Parser>(Parser);
|
||||
const stubParser = mockManager.mock("parseURL");
|
||||
stubParser.resolves(mockedRSSOutput);
|
||||
const newEntry = {
|
||||
title: "my title 03",
|
||||
"media:group": {
|
||||
"media:description": "my description 03",
|
||||
"media:thumbnail": [
|
||||
{ $: { height: 360, width: 420, url: "my_image03.jpg" } },
|
||||
],
|
||||
},
|
||||
link: "my_url_03.com",
|
||||
pubDate: "myDate03",
|
||||
};
|
||||
const newRSSOutput = {
|
||||
...mockedRSSOutput,
|
||||
items: [newEntry, ...mockedRSSOutput.items],
|
||||
};
|
||||
|
||||
// classic build
|
||||
const myListener = new ListenerRss({
|
||||
...infosListener,
|
||||
timeloop: 60,
|
||||
lastEntriesLinks: ["my_url_02.com", "my_url_01.com", "my_url_00.com"],
|
||||
});
|
||||
|
||||
//spy
|
||||
const updateListenerSpy = sinon.spy();
|
||||
const newEntriesListenerSpy = sinon.spy();
|
||||
|
||||
myListener.on("update", updateListenerSpy);
|
||||
myListener.on("newEntries", newEntriesListenerSpy);
|
||||
|
||||
// when
|
||||
myListener.start();
|
||||
|
||||
// then
|
||||
await clock.tickAsync(1);
|
||||
expect(updateListenerSpy).to.have.been.calledOnce;
|
||||
expect(newEntriesListenerSpy).to.not.have.been.calledOnce;
|
||||
|
||||
// given
|
||||
stubParser.resolves(newRSSOutput);
|
||||
|
||||
// then
|
||||
await clock.tickAsync(60000);
|
||||
expect(updateListenerSpy).to.have.been.calledTwice;
|
||||
expect(newEntriesListenerSpy).to.have.been.calledOnce;
|
||||
expect(newEntriesListenerSpy).to.have.been.calledWith([newEntry]);
|
||||
|
||||
// given
|
||||
newEntriesListenerSpy.resetHistory();
|
||||
|
||||
// then
|
||||
await clock.tickAsync(60000);
|
||||
expect(updateListenerSpy).to.have.been.calledThrice;
|
||||
expect(updateListenerSpy).to.have.been.calledWith(mockedRSSOutput);
|
||||
expect(newEntriesListenerSpy).to.not.have.been.called;
|
||||
|
||||
myListener.stop();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
{
|
||||
"extends": "../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"esModuleInterop": true
|
||||
}
|
||||
},
|
||||
"files": ["./index-spec.ts"]
|
||||
}
|
||||
|
|
|
@ -1,21 +1,72 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "es2018",
|
||||
"module": "es2015",
|
||||
"lib": ["es6"],
|
||||
"allowJs": true,
|
||||
"outDir": "build",
|
||||
"rootDir": "src",
|
||||
"strict": true,
|
||||
"moduleResolution": "node",
|
||||
"noImplicitAny": true,
|
||||
"esModuleInterop": true,
|
||||
"resolveJsonModule": false,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true
|
||||
},
|
||||
"exclude": [
|
||||
"build/",
|
||||
"node_modules"
|
||||
]
|
||||
/* Visit https://aka.ms/tsconfig.json to read more about this file */
|
||||
|
||||
/* Basic Options */
|
||||
// "incremental": true, /* Enable incremental compilation */
|
||||
"target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ES2021', or 'ESNEXT'. */
|
||||
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
|
||||
// "lib": [], /* Specify library files to be included in the compilation. */
|
||||
// "allowJs": true, /* Allow javascript files to be compiled. */
|
||||
// "checkJs": true, /* Report errors in .js files. */
|
||||
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */
|
||||
// "declaration": true, /* Generates corresponding '.d.ts' file. */
|
||||
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
|
||||
// "sourceMap": true, /* Generates corresponding '.map' file. */
|
||||
// "outFile": "./", /* Concatenate and emit output to single file. */
|
||||
// "outDir": "./build/", /* Redirect output structure to the directory. */
|
||||
// "rootDir": "./src/", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
|
||||
// "composite": true, /* Enable project compilation */
|
||||
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
|
||||
// "removeComments": true, /* Do not emit comments to output. */
|
||||
// "noEmit": true, /* Do not emit outputs. */
|
||||
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
|
||||
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
|
||||
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
|
||||
|
||||
/* Strict Type-Checking Options */
|
||||
"strict": true, /* Enable all strict type-checking options. */
|
||||
// "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
|
||||
// "strictNullChecks": true, /* Enable strict null checks. */
|
||||
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
|
||||
// "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
|
||||
// "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
|
||||
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
|
||||
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
|
||||
|
||||
/* Additional Checks */
|
||||
// "noUnusedLocals": true, /* Report errors on unused locals. */
|
||||
// "noUnusedParameters": true, /* Report errors on unused parameters. */
|
||||
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
|
||||
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
|
||||
// "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
|
||||
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an 'override' modifier. */
|
||||
// "noPropertyAccessFromIndexSignature": true, /* Require undeclared properties from index signatures to use element accesses. */
|
||||
|
||||
/* Module Resolution Options */
|
||||
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
|
||||
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
|
||||
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
|
||||
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
|
||||
// "typeRoots": [], /* List of folders to include type definitions from. */
|
||||
// "types": [], /* Type declaration files to be included in compilation. */
|
||||
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
|
||||
"esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
|
||||
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
|
||||
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||
|
||||
/* Source Map Options */
|
||||
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
|
||||
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
|
||||
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
|
||||
|
||||
/* Experimental Options */
|
||||
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
|
||||
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
|
||||
|
||||
/* Advanced Options */
|
||||
"skipLibCheck": true, /* Skip type checking of declaration files. */
|
||||
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
|
||||
}
|
||||
}
|
||||
|
|
11
tsconfig.json
Normal file
11
tsconfig.json
Normal file
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"extends": "./tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"declaration": true, /* Generates corresponding '.d.ts' file. */
|
||||
"declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
|
||||
"outDir": "./build/", /* Redirect output structure to the directory. */
|
||||
"rootDir": "./src/", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
|
||||
},
|
||||
"files": ["./src/index.ts", "./src/listener-rss.ts"],
|
||||
"exclude": ["./tests/**/*"]
|
||||
}
|
Loading…
Reference in New Issue
Block a user