diff --git a/.babelrc b/.babelrc deleted file mode 100644 index 3fc3d6a..0000000 --- a/.babelrc +++ /dev/null @@ -1,9 +0,0 @@ -{ - "presets": [ - "es2015", - "stage-2" - ], - "plugins": [ - "transform-runtime" - ] -} diff --git a/.jshintrc b/.jshintrc deleted file mode 100644 index d8dee1b..0000000 --- a/.jshintrc +++ /dev/null @@ -1,38 +0,0 @@ -{ - "node": true, - "browser": false, - "esnext": true, - "bitwise": false, - "camelcase": false, - "curly": true, - "eqeqeq": true, - "immed": true, - "latedef": true, - "newcap": true, - "noarg": true, - "quotmark": "single", - "regexp": true, - "undef": true, - "unused": "vars", - "strict": true, - "trailing": true, - "smarttabs": false, - "predef": [ - "define", - "require", - "exports", - "module", - "describe", - "before", - "beforeEach", - "after", - "afterEach", - "it", - "inject", - "expect", - "spyOn" - ], - "indent": 5, - "devel": true, - "noempty": true -} diff --git a/package.json b/package.json index 9b197a2..5a3a123 100644 --- a/package.json +++ b/package.json @@ -10,12 +10,14 @@ "bin": "bin/essence", "scripts": { "start": "npm run test-watch", - "build": "babel src --out-dir lib", - "test": "mocha --recursive --compilers js:babel-register", - "test-watch": "mocha --watch --recursive --compilers js:babel-register", + "try": "node ./bin/essence", + "build": "tsc", + "test": "cross-env TS_NODE_COMPILER_OPTIONS={\\\"module\\\":\\\"commonjs\\\"} mocha --recursive --compilers ts:ts-node/register", + "test-watch": "npm run test -- --watch", "prepublish": "npm run test && npm run build" }, "dependencies": { + "@types/chai-as-promised": "^7.1.0", "axios": "^0.9.1", "cheerio": "^0.20.0", "commander": "^2.9.0", @@ -25,22 +27,15 @@ "xml2js": "^0.4.16" }, "devDependencies": { - "babel": "^6.5.2", - "babel-cli": "^6.7.5", - "babel-core": "^6.7.6", - "babel-eslint": "^6.0.2", - "babel-plugin-transform-runtime": "^6.7.5", - "babel-preset-es2015": "^6.6.0", - "babel-preset-stage-2": "^6.5.0", - "babel-register": "^6.7.2", - "babel-runtime": "^6.6.1", + "@types/chai": "^4.0.4", + "@types/mocha": "^2.2.43", "chai": "^3.5.0", "chai-as-promised": "^5.3.0", - "eslint": "^2.8.0", - "eslint-config-vtech": "^0.1.1", - "eslint-loader": "^1.3.0", - "eslint-plugin-babel": "^3.2.0", + "cross-env": "^5.0.5", "mocha": "^2.4.5", - "sinon": "^2.2.0" + "sinon": "^2.2.0", + "ts-node": "^3.3.0", + "tslint-immutable": "^4.4.0", + "typescript": "^2.5.3" } } diff --git a/src/Container.ts b/src/Container.ts new file mode 100644 index 0000000..2939b27 --- /dev/null +++ b/src/Container.ts @@ -0,0 +1,40 @@ +import {Map} from 'immutable'; +import {memoize} from 'lodash'; + + + +interface Factory { + (get): any; +} + +export default class Container { + private factories: Map; + + constructor(factories = Map()) { + this.factories = factories; + this.get = this.get.bind(this); + } + + // Executes the factory registered for the given key and + // returns its result. + get(key): T { + const factory = this.factories.get(key); + + if (!factory) { + throw new Error(`No factory found for key '${key}'`); + } + + return factory(this.get); + } + + // Returns a new Container with the given factory. + with(key, factory) { + return new Container(this.factories.set(key, factory)); + } + + // Returns a new Container with the given factory. + // The factory will be memoized. + withUnique(key, factory) { + return this.with(key, memoize(factory)); + } +} diff --git a/src/Payload.ts b/src/Payload.ts new file mode 100644 index 0000000..be21cec --- /dev/null +++ b/src/Payload.ts @@ -0,0 +1,36 @@ +import Request from './Request'; +import Response from './Response'; + + + +interface UpdateRequest{ + (r: Request): Request; +} + +interface UpdateResponse{ + (r: Response): Response; +} + +export default class Payload { + + constructor(readonly req: Request, readonly res: Response) {} + + static from(url: string) { + return new Payload( + new Request(url), + new Response() + ); + } + + withRequest(req: Request | UpdateRequest) { + return (req instanceof Request) + ? new Payload(req, this.res) + : new Payload(req(this.req), this.res); + } + + withResponse(res: Response | UpdateResponse) { + return (res instanceof Response) + ? new Payload(this.req, res) + : new Payload(this.req, res(this.res)); + } +} diff --git a/src/Request.ts b/src/Request.ts new file mode 100644 index 0000000..b24af48 --- /dev/null +++ b/src/Request.ts @@ -0,0 +1,10 @@ + + + +export default class Request { + constructor(readonly url: string) {} + + withUrl(url) { + return new Request(url); + } +} diff --git a/src/Response.ts b/src/Response.ts new file mode 100644 index 0000000..702f123 --- /dev/null +++ b/src/Response.ts @@ -0,0 +1,93 @@ +import {Map} from 'immutable'; +import {set, reduce} from 'lodash'; + + + +export default class Response { + private props: Map>; + + constructor(props = Map>()) { + this.props = props; + } + + isEmpty() { + return !this.props.size; + } + + has(key) { + return this.props.has(key); + } + + get(key, missing?) { + return this.first(key, missing); + } + + // Returns the first data available for the given key. + first(key, missing?) { + const all = this.all(key); + + return all.length + ? all[0] + : missing; + } + + // Returns all data available for the given key. + all(key) { + return this.props.get(key, []); + } + + // Returns all keys that have any associated data. + keys() { + return this.props.keySeq().toArray(); + } + + // Returns number of data associated to the given key. + count(key) { + return this.all(key).length; + } + + groups(keys) { + const groups = []; + + keys.forEach((key) => { + this.all(key).forEach((value, i) => { + set(groups, [i, key], value); + }); + }); + + return groups; + } + + allGroups() { + return this.groups(this.keys()); + } + + // Returns a new response with the given prop. + withProp(key, value) { + const all = this.all(key); + const values = all.concat(value); + + return new Response( + this.props.set(key, values) + ); + } + + // Returns a new response with the given props. + withProps(newProps) { + return reduce( + newProps, + (response, value, key) => + response.withProp(key, value), + new Response(this.props) + ); + } + + // Returns a JSON representation of the response. + toJson(spaces) { + return JSON.stringify( + this.allGroups(), + null, + spaces + ); + } +} diff --git a/src/condition.js b/src/condition.js deleted file mode 100644 index 7bb2ae6..0000000 --- a/src/condition.js +++ /dev/null @@ -1,22 +0,0 @@ -/** - * Creates a condition that will execute the given middleware - * if the predicate returns true. - * - * @param function predicate Predicate. - * @param function middleware Middleware. - * @return function Condition. - */ -export default function createCondition(predicate, middleware) { - - /** - * Returns the given payload optionnaly updated by - * the middleware. - * - * @param object payload Payload. - * @return object Updated payload. - */ - return async (payload) => - predicate(payload) - ? await middleware(payload) - : payload; -} diff --git a/src/condition.ts b/src/condition.ts new file mode 100644 index 0000000..2874565 --- /dev/null +++ b/src/condition.ts @@ -0,0 +1,16 @@ +import Payload from './payload'; +import Middleware from './middleware'; + + + +// Creates a condition that will execute the given middleware +// if the filter returns true. +export default function condition( + filter: (T) => boolean, + middleware: Middleware +): Middleware { + return async (payload) => + filter(payload) + ? await middleware(payload) + : payload; +}; diff --git a/src/conditions/isResponseEmpty.js b/src/conditions/isResponseEmpty.js deleted file mode 100644 index 4c3e1e1..0000000 --- a/src/conditions/isResponseEmpty.js +++ /dev/null @@ -1,9 +0,0 @@ -/** - * Tells if the given response is empty. - * - * @param object payload Payload. - * @return bool - */ -export default function isResponseEmpty({res}) { - return res.isEmpty(); -} diff --git a/src/conditions/isResponseEmpty.ts b/src/conditions/isResponseEmpty.ts new file mode 100644 index 0000000..1ed8f7c --- /dev/null +++ b/src/conditions/isResponseEmpty.ts @@ -0,0 +1,7 @@ +import Payload from '../Payload'; + + + +export default function isResponseEmpty(payload: Payload): boolean { + return payload.res.isEmpty(); +} diff --git a/src/conditions/requestUrlMatchesRegex.js b/src/conditions/requestUrlMatchesRegex.js deleted file mode 100644 index 149fb69..0000000 --- a/src/conditions/requestUrlMatchesRegex.js +++ /dev/null @@ -1,10 +0,0 @@ -/** - * Tells if the given request url matches a regex. - * - * @param Regexp regex Regex. - * @param object payload Payload. - * @return bool - */ -export default function requestUrlMatchesRegex(regex, {req}) { - return regex.test(req.url()); -} diff --git a/src/conditions/requestUrlMatchesRegex.ts b/src/conditions/requestUrlMatchesRegex.ts new file mode 100644 index 0000000..6917257 --- /dev/null +++ b/src/conditions/requestUrlMatchesRegex.ts @@ -0,0 +1,10 @@ +import Payload from '../Payload'; + + + +export default function requestUrlMatchesRegex( + regex: RegExp, + payload: Payload +): boolean { + return regex.test(payload.req.url); +} diff --git a/src/createContainer.js b/src/createContainer.js deleted file mode 100644 index ba9bb25..0000000 --- a/src/createContainer.js +++ /dev/null @@ -1,56 +0,0 @@ -import {Map} from 'immutable'; -import {memoize} from 'lodash'; - - - -/** - * - */ -export default function createContainer(factories = Map()) { - - /** - * Executes the factory regiqtered for the given key and - * returns its result. - * - * @param string key Key. - * @return mixed Result. - */ - const get = (key) => { - const factory = factories.get(key); - - if (!factory) { - throw new Error( - `No factory found for key '${key}'` - ); - } - - return factory(get); - }; - - /** - * Returns a new Container with the given factory. - * - * @param string key Key. - * @param function factory Factory. - * @return Container Container. - */ - const withFactory = (key, factory) => - createContainer(factories.set(key, factory)); - - /** - * Returns a new Container with the given factory. - * The factory will be memoized. - * - * @param string key Key. - * @param function factory Factory. - * @return Container Container. - */ - const withUniqueFactory = (key, factory) => - withFactory(key, memoize(factory)); - - return { - get, - with: withFactory, - withUnique: withUniqueFactory - }; -} diff --git a/src/createRequest.js b/src/createRequest.js deleted file mode 100644 index 4e9eb71..0000000 --- a/src/createRequest.js +++ /dev/null @@ -1,29 +0,0 @@ -import {constant} from 'lodash'; - - - -/** - * - */ -export default function createRequest(url) { - - /** - * Returns the request URL. - * - * @return string URL. - */ - const getUrl = constant(url); - - /** - * Returns a new Request with the given URL. - * - * @param string url URL. - */ - const withUrl = (url) => - createRequest(url); - - return { - url: getUrl, - withUrl - } -} diff --git a/src/createResponse.js b/src/createResponse.js deleted file mode 100644 index 4062668..0000000 --- a/src/createResponse.js +++ /dev/null @@ -1,144 +0,0 @@ -import {Map} from 'immutable'; -import {set, reduce} from 'lodash'; - - - -/** - * - */ -export default function createResponse(props = Map()) { - - /** - * Tells if the response is empty. - * - * @return bool - */ - const isEmpty = () => - !props.size; - - /** - * Tells if the response has any data for the given key. - * - * @param string key Key. - * @return bool - */ - const hasProp = (key) => - props.has(key); - - /** - * Returns the first data available for the given key. - * - * @param string key Key. - * @param string missing Value to be returned if the key - * does not exists. - * @return mixed Data. - */ - const firstProp = (key, missing) => { - const all = allProps(key); - - return all.length - ? all[0] - : missing; - }; - - /** - * Returns all data available for the given key. - * - * @param string key Key. - * @return array Data. - */ - const allProps = (key) => - props.get(key, []); - - /** - * Returns all keys that has any associated data. - * - * @return array Data. - */ - const allKeys = () => - props.keySeq().toArray(); - - /** - * Returns number of data associated to the given key. - * - * @param string key Key. - * @return int Count. - */ - const propCount = (key) => - allProps(key).length; - - /** - * - */ - const propGroups = (keys) => { - const groups = []; - - keys.forEach((key) => { - allProps(key).forEach((value, i) => { - set(groups, [i, key], value); - }); - }); - - return groups; - }; - - const allPropGroups = () => - propGroups(allKeys()); - - /** - * Returns a new response with the given prop. - * - * @param string key Key. - * @param mixed value Value. - * @return Response Response. - */ - const withProp = (key, value) => { - const all = allProps(key); - const values = all.concat(value); - - return createResponse( - props.set(key, values) - ); - }; - - /** - * Returns a new response with the given props. - * - * @param object newProps New Props. - * @return Response Response. - */ - const withProps = (newProps) => - reduce( - newProps, - (res, value, key) => - res.withProp(key, value), - createResponse(props) - ); - - /** - * Returns a JSON representation of the response. - * - * @param int spaces Number of spaces used for indentation. - */ - const toJson = (spaces) => - JSON.stringify( - allPropGroups(), - null, - spaces - ); - - return { - isEmpty, - withProp, - withProps, - toJson, - has: hasProp, - first: firstProp, - get: firstProp, - all: allProps, - keys: allKeys, - count: propCount, - groups: propGroups, - allGroups: allPropGroups - }; -}; diff --git a/src/container.js b/src/defaultContainer.ts similarity index 86% rename from src/container.js rename to src/defaultContainer.ts index b4316f7..3b093df 100644 --- a/src/container.js +++ b/src/defaultContainer.ts @@ -1,6 +1,8 @@ -import axios from 'axios'; +import * as axios from 'axios'; import {memoize, partial, property, stubTrue} from 'lodash'; -import createContainer from './createContainer'; +import Middleware from './Middleware'; +import Payload from './Payload'; +import Container from './Container'; import extract from './extract'; import pipe from './pipe'; import condition from './condition'; @@ -10,7 +12,7 @@ import refactorRequestUrl from './preparators/refactorRequestUrl'; import extractMetaTags from './extractors/extractMetaTags'; import extractOEmbed from './extractors/extractOEmbed'; import extractOEmbedFromService from './extractors/extractOEmbedFromService'; -import OEmbedFormats from './extractors/oEmbed/OEmbedFormats'; +import OEmbedFormat from './extractors/oEmbed/OEmbedFormat'; import findServiceFromList from './extractors/oEmbed/findServiceFromList'; import findServiceFromUrl from './extractors/oEmbed/findServiceFromUrl'; import mapResponseProps from './presenters/mapResponseProps'; @@ -18,10 +20,21 @@ import fillResponseUrl from './presenters/fillResponseUrl'; -/** - * - */ -export default createContainer() +const getHeaders = () => { + const head = memoize(axios.head); + return async (url) => + head(url).then(property('headers')); +}; + +const getBody = () => { + const get = memoize(axios.get); + return async (url) => + get(url).then(property('data')); +}; + + + +export default new Container() .withUnique('handleError', () => stubTrue) .withUnique('getHeaders', () => { const head = memoize(axios.head); @@ -46,13 +59,14 @@ export default createContainer() 'https://www.youtube.com/watch?v=$3' ) ) - .withUnique('oEmbedServices', () => ({ - 'youtube': { + .withUnique('oEmbedServices', () => ([ + { + name: 'YouTube', pattern: /youtube\.com|youtu\.be/i, endpoint: 'http://www.youtube.com/oembed?format=json&url=:url', - format: OEmbedFormats.json + format: OEmbedFormat.Json } - })) + ])) .withUnique('findServiceFromList', (get) => partial(findServiceFromList, get('oEmbedServices')) ) @@ -117,7 +131,7 @@ export default createContainer() }) ) .withUnique('middlewares', (get) => - pipe([ + pipe(>>[ condition( get('isYoutubeRequest'), get('prepareYoutubeRequest') diff --git a/src/extract.js b/src/extract.js deleted file mode 100644 index 72acdd8..0000000 --- a/src/extract.js +++ /dev/null @@ -1,20 +0,0 @@ -import createRequest from './createRequest'; -import createResponse from './createResponse'; - - - -/** - * Extracts info from the given URL using a reducer. - * - * @param function reduce Reducer function. - * @param string url URL. - * @return object Payload. - */ -export default async function extract(reduce, url) { - const {res} = await reduce({ - req: createRequest(url), - res: createResponse() - }); - - return res; -} diff --git a/src/extract.ts b/src/extract.ts new file mode 100644 index 0000000..30f67a5 --- /dev/null +++ b/src/extract.ts @@ -0,0 +1,16 @@ +import Payload from './Payload'; +import Middleware from './middleware'; +import Request from './Request'; +import Response from './Response'; + + + +// Extracts info from the given URL using a middleware. +export default async function extract( + middleware: Middleware, + url: string +): Promise { + const payload = Payload.from(url); + const {res} = await middleware(payload); + return res; +}; diff --git a/src/extractors/extractMetaTags.js b/src/extractors/extractMetaTags.js deleted file mode 100644 index 431ad95..0000000 --- a/src/extractors/extractMetaTags.js +++ /dev/null @@ -1,49 +0,0 @@ -import cheerio from 'cheerio'; - - - -/** - * @TODO: stream the page to stop early. - */ -const extractProperties = (pattern, html) => { - const $ = cheerio.load(html); - const metas = $('meta', 'head'); - const props = {}; - - metas.each((i, meta) => { - const name = $(meta).attr('property'); - - if (!name || !name.match(pattern)) { - return; - } - - if (!props[name]) { - props[name] = []; - } - - props[name].push( - $(meta).attr('content') - ); - }); - - return props; -}; - - - -/** - * - */ -export default async function extractMetaTags( - getBody, - pattern, - {req, res} -) { - const html = await getBody(req.url()); - const props = extractProperties(pattern, html); - - return { - req, - res: res.withProps(props) - }; -} diff --git a/src/extractors/extractMetaTags.ts b/src/extractors/extractMetaTags.ts new file mode 100644 index 0000000..f5c58a4 --- /dev/null +++ b/src/extractors/extractMetaTags.ts @@ -0,0 +1,46 @@ +import {load} from 'cheerio'; +import {isEmpty} from 'lodash'; +import Payload from '../Payload'; + + + +// @TODO: stream the page to stop early. +const extractProperties = (pattern: RegExp, html: string): object => { + const $ = load(html); + const metas = $('meta', 'head'); + const props = {}; + + metas.each((i, meta) => { + const name = $(meta).attr('property'); + + if (!name || !name.match(pattern)) { + return; + } + + if (!props[name]) { + props[name] = []; + } + + props[name].push( + $(meta).attr('content') + ); + }); + + return props; +}; + +export default async function extractMetaTags( + getBody, + pattern: RegExp, + payload: Payload +): Promise { + const html = await getBody(payload.req.url); + const props = extractProperties(pattern, html); + + if (isEmpty(props)) { + return payload; + } + + const response = payload.res.withProps(props); + return payload.withResponse(response); +} diff --git a/src/extractors/extractOEmbed.js b/src/extractors/extractOEmbed.ts similarity index 50% rename from src/extractors/extractOEmbed.js rename to src/extractors/extractOEmbed.ts index 7eb9900..7dcaef3 100644 --- a/src/extractors/extractOEmbed.js +++ b/src/extractors/extractOEmbed.ts @@ -1,12 +1,13 @@ -/** - * - */ +import Payload from '../Payload'; + + + export default async function extractOEmbed( findService, extract, - payload -) { - const service = await findService(payload.req.url()); + payload: Payload +): Promise { + const service = await findService(payload.req.url); return service ? extract(service, payload) : payload; diff --git a/src/extractors/extractOEmbedFromService.js b/src/extractors/extractOEmbedFromService.js deleted file mode 100644 index 214c042..0000000 --- a/src/extractors/extractOEmbedFromService.js +++ /dev/null @@ -1,62 +0,0 @@ -import xml2js from 'xml2js'; -import {isString} from 'lodash'; -import Formats from './oEmbed/OEmbedFormats'; - - - -/** - * - */ -async function parseXml(xml) { - const options = { - explicitArray: false - }; - - return new Promise((resolve, reject) => { - xml2js.parseString(xml, options, (error, data) => { - if (error) { - reject(error); - } else { - resolve(data.oembed); - } - }); - }); -} - -/** - * - */ -async function parse(body, format) { - switch (format) { - case Formats.json: - return isString(body) - ? JSON.parse(body) - : body - - case Formats.xml: - return await parseXml(body); - - default: - return {}; - } -} - - - -/** - * - */ -export default async function extractOEmbedFromService( - getBody, - {endpoint, format}, - {req, res} -) { - const url = endpoint.replace(/:url/i, req.url()); - const body = await getBody(url); - const props = await parse(body, format); - - return { - req, - res: res.withProps(props) - }; -} diff --git a/src/extractors/extractOEmbedFromService.ts b/src/extractors/extractOEmbedFromService.ts new file mode 100644 index 0000000..6aa351c --- /dev/null +++ b/src/extractors/extractOEmbedFromService.ts @@ -0,0 +1,55 @@ +import {parseString} from 'xml2js'; +import {isEmpty, isString} from 'lodash'; +import Payload from '../Payload'; +import OEmbedFormat from './oEmbed/OEmbedFormat'; +import OEmbedService from './oEmbed/OEmbedService'; + + + +async function parseXml(xml: string): Promise { + const options = { + explicitArray: false + }; + + return new Promise((resolve, reject) => { + parseString(xml, options, (error, data) => { + if (error) { + reject(error); + } else { + resolve(data.oembed); + } + }); + }); +} + +async function parse(body: string, format: OEmbedFormat): Promise { + switch (format) { + case OEmbedFormat.Json: + return isString(body) + ? JSON.parse(body) + : body + + case OEmbedFormat.Xml: + return await parseXml(body); + + default: + return {}; + } +} + +export default async function extractOEmbedFromService( + getBody, + {endpoint, format}: OEmbedService, + payload: Payload +): Promise { + const url = endpoint.replace(/:url/i, payload.req.url); + const body = await getBody(url); + const props = await parse(body, format); + + if (isEmpty(props)) { + return payload; + } + + const response = payload.res.withProps(props); + return payload.withResponse(response); +} diff --git a/src/extractors/oEmbed/OEmbedFormat.ts b/src/extractors/oEmbed/OEmbedFormat.ts new file mode 100644 index 0000000..a161bbd --- /dev/null +++ b/src/extractors/oEmbed/OEmbedFormat.ts @@ -0,0 +1,6 @@ +enum OEmbedFormat { + Json = 'json', + Xml = 'xml' +}; + +export default OEmbedFormat; diff --git a/src/extractors/oEmbed/OEmbedFormats.js b/src/extractors/oEmbed/OEmbedFormats.js deleted file mode 100644 index 7d62443..0000000 --- a/src/extractors/oEmbed/OEmbedFormats.js +++ /dev/null @@ -1,7 +0,0 @@ -/** - * - */ -export default { - json: 'json', - xml: 'xml' -}; \ No newline at end of file diff --git a/src/extractors/oEmbed/OEmbedService.ts b/src/extractors/oEmbed/OEmbedService.ts new file mode 100644 index 0000000..a6eadbd --- /dev/null +++ b/src/extractors/oEmbed/OEmbedService.ts @@ -0,0 +1,9 @@ +import OEmbedFormat from './OEmbedFormat'; + + + +export default interface OEmbedService { + readonly pattern?: RegExp, + readonly endpoint: string, + readonly format: OEmbedFormat +} diff --git a/src/extractors/oEmbed/completeEndpoint.js b/src/extractors/oEmbed/completeEndpoint.ts similarity index 100% rename from src/extractors/oEmbed/completeEndpoint.js rename to src/extractors/oEmbed/completeEndpoint.ts diff --git a/src/extractors/oEmbed/createService.js b/src/extractors/oEmbed/createService.js deleted file mode 100644 index 3856455..0000000 --- a/src/extractors/oEmbed/createService.js +++ /dev/null @@ -1,13 +0,0 @@ -import Formats from './OEmbedFormats'; - - - -/** - * - */ -export default function createService(endpoint, format = Formats.json) { - return { - endpoint, - format - }; -}; \ No newline at end of file diff --git a/src/extractors/oEmbed/findServiceFromHtml.js b/src/extractors/oEmbed/findServiceFromHtml.ts similarity index 57% rename from src/extractors/oEmbed/findServiceFromHtml.js rename to src/extractors/oEmbed/findServiceFromHtml.ts index c095de5..c74e956 100644 --- a/src/extractors/oEmbed/findServiceFromHtml.js +++ b/src/extractors/oEmbed/findServiceFromHtml.ts @@ -1,13 +1,11 @@ -import cheerio from 'cheerio'; -import createService from './createService'; +import {load} from 'cheerio'; +import OEmbedService from './OEmbedService'; -/** - * @TODO: stream the page to stop early. - */ -export default function findServiceFromHtml(html) { - const $ = cheerio.load(html); +// @TODO: stream the page to stop early. +export default function findServiceFromHtml(html: string): OEmbedService { + const $ = load(html); const links = $('link', 'head'); const pattern = /application\/(json|xml)\+oembed/i; let service; @@ -24,10 +22,14 @@ export default function findServiceFromHtml(html) { const matches = pattern.exec(type); if (matches && matches[1]) { - service = createService(href, matches[1]); + service = { + endpoint: href, + format: matches[1] + }; + return false; } }); return service; -}; \ No newline at end of file +}; diff --git a/src/extractors/oEmbed/findServiceFromList.js b/src/extractors/oEmbed/findServiceFromList.js deleted file mode 100644 index 53054ff..0000000 --- a/src/extractors/oEmbed/findServiceFromList.js +++ /dev/null @@ -1,12 +0,0 @@ -import {find} from 'lodash'; - - - -/** - * - */ -export default function findServiceFromList(services, url) { - return find(services, ({pattern}) => - pattern.test(url) - ); -} \ No newline at end of file diff --git a/src/extractors/oEmbed/findServiceFromList.ts b/src/extractors/oEmbed/findServiceFromList.ts new file mode 100644 index 0000000..4dcf3de --- /dev/null +++ b/src/extractors/oEmbed/findServiceFromList.ts @@ -0,0 +1,12 @@ +import OEmbedService from './OEmbedService'; + + + +export default function findServiceFromList( + services: ReadonlyArray, + url: string +): OEmbedService { + return services.find(({pattern}) => + pattern.test(url) + ); +} diff --git a/src/extractors/oEmbed/findServiceFromUrl.js b/src/extractors/oEmbed/findServiceFromUrl.js deleted file mode 100644 index 0749e27..0000000 --- a/src/extractors/oEmbed/findServiceFromUrl.js +++ /dev/null @@ -1,11 +0,0 @@ -import findServiceFromHtml from './findServiceFromHtml'; - - - -/** - * - */ -export default async function findServiceFromUrl(getBody, url) { - const html = await getBody(url); - return findServiceFromHtml(html); -}; \ No newline at end of file diff --git a/src/extractors/oEmbed/findServiceFromUrl.ts b/src/extractors/oEmbed/findServiceFromUrl.ts new file mode 100644 index 0000000..6de8411 --- /dev/null +++ b/src/extractors/oEmbed/findServiceFromUrl.ts @@ -0,0 +1,12 @@ +import OEmbedService from './OEmbedService'; +import findServiceFromHtml from './findServiceFromHtml'; + + + +export default async function findServiceFromUrl( + getBody, + url: string +): Promise { + const html = await getBody(url); + return findServiceFromHtml(html); +}; diff --git a/src/index.js b/src/index.ts similarity index 64% rename from src/index.js rename to src/index.ts index b97bd63..6848cd7 100644 --- a/src/index.js +++ b/src/index.ts @@ -1,14 +1,15 @@ -import container from './container'; +import defaultContainer from './defaultContainer'; +// a default extractor +export {defaultContainer}; +export default defaultContainer.get('extract'); - -/** - * Reexports. - */ -export {default as createContainer} from './createContainer'; -export {default as createRequest} from './createRequest'; -export {default as createResponse} from './createResponse'; -export {default as container} from './container'; +// reexports +export {default as Request} from './Request'; +export {default as Response} from './Response'; +export {default as Payload} from './Payload'; +export {default as Middleware} from './Middleware'; +export {default as Container} from './Container'; export {default as condition} from './condition'; export {default as pipe} from './pipe'; export {default as extract} from './extract'; @@ -17,16 +18,11 @@ export {default as requestUrlMatchesRegex} from './conditions/requestUrlMatchesR export {default as refactorRequestUrl} from './preparators/refactorRequestUrl'; export {default as extractOEmbed} from './extractors/extractOEmbed'; export {default as extractOEmbedFromService} from './extractors/extractOEmbedFromService'; -export {default as OEmbedFormats} from './extractors/oEmbed/OEmbedFormats'; -export {default as createService} from './extractors/oEmbed/createService'; +export {default as OEmbedFormat} from './extractors/oEmbed/OEmbedFormat'; +export {default as OEmbedService} from './extractors/oEmbed/OEmbedService'; export {default as findServiceFromHtml} from './extractors/oEmbed/findServiceFromHtml'; export {default as findServiceFromList} from './extractors/oEmbed/findServiceFromList'; export {default as findServiceFromUrl} from './extractors/oEmbed/findServiceFromUrl'; export {default as extractMetaTags} from './extractors/extractMetaTags'; export {default as fillResponseUrl} from './presenters/fillResponseUrl'; export {default as mapResponseProps} from './presenters/mapResponseProps'; - -/** - * A default extractor. - */ -export default container.get('extract'); diff --git a/src/middleware.ts b/src/middleware.ts new file mode 100644 index 0000000..0d0639c --- /dev/null +++ b/src/middleware.ts @@ -0,0 +1,7 @@ +import Payload from './Payload'; + + + +export default interface Middleware { + (value: T): Promise; +} diff --git a/src/pipe.js b/src/pipe.js deleted file mode 100644 index 0ecccfe..0000000 --- a/src/pipe.js +++ /dev/null @@ -1,28 +0,0 @@ -import {noop, clone} from 'lodash'; - - - -/** - * Reduces a payload through as set of middlewares. - * - * @param array middlewares Middlewares. - * @param function handleError . - * @return function The actual pipeline. - */ -export default function pipe(middlewares, handleError = noop) { - return async function reduce(payload) { - let p = clone(payload); - - for (const middleware of middlewares) { - try { - p = await middleware(p); - } catch (e) { - if (!handleError(e)) { - break; - } - } - } - - return p; - }; -} diff --git a/src/pipe.ts b/src/pipe.ts new file mode 100644 index 0000000..0c5ad5d --- /dev/null +++ b/src/pipe.ts @@ -0,0 +1,31 @@ +import {noop, clone} from 'lodash'; +import Payload from './Payload'; +import Middleware from './middleware'; + + + +interface ErrorHandler { + (e: Error): boolean; +} + +// Reduces a payload through as set of middlewares. +export default function pipe( + middlewares: ReadonlyArray>, + handleError: ErrorHandler = noop +): Middleware { + return async (payload) => { + let p = clone(payload); + + for (const middleware of middlewares) { + try { + p = await middleware(p); + } catch (e) { + if (!handleError(e)) { + break; + } + } + } + + return p; + }; +}; diff --git a/src/preparators/refactorRequestUrl.js b/src/preparators/refactorRequestUrl.js deleted file mode 100644 index 22d5603..0000000 --- a/src/preparators/refactorRequestUrl.js +++ /dev/null @@ -1,18 +0,0 @@ -/** - * - */ -export default function refactorRequestUrl( - regex, - replacement, - {req, ...payload} -) { - return { - ...payload, - req: req.withUrl( - req.url().replace( - regex, - replacement - ) - ) - }; -} diff --git a/src/preparators/refactorRequestUrl.ts b/src/preparators/refactorRequestUrl.ts new file mode 100644 index 0000000..4ec9f4c --- /dev/null +++ b/src/preparators/refactorRequestUrl.ts @@ -0,0 +1,13 @@ +import Payload from '../Payload'; + + + +export default function refactorRequestUrl( + regex: RegExp, + replacement: string, + payload: Payload +): Payload { + const url = payload.req.url.replace(regex, replacement); + const request = payload.req.withUrl(url); + return payload.withRequest(request); +} diff --git a/src/presenters/fillResponseUrl.js b/src/presenters/fillResponseUrl.js deleted file mode 100644 index b109e62..0000000 --- a/src/presenters/fillResponseUrl.js +++ /dev/null @@ -1,14 +0,0 @@ -/** - * - */ -export default function fillResponseUrl({req, res}) { - const filledRes = - (!res.isEmpty() && !res.has('url')) - ? res.withProp('url', req.url()) - : res; - - return { - req, - res: filledRes - }; -} diff --git a/src/presenters/fillResponseUrl.ts b/src/presenters/fillResponseUrl.ts new file mode 100644 index 0000000..5c1e232 --- /dev/null +++ b/src/presenters/fillResponseUrl.ts @@ -0,0 +1,12 @@ +import Payload from '../Payload'; + + + +export default function fillResponseUrl(payload: Payload): Payload { + if (payload.res.isEmpty() || payload.res.has('url')) { + return payload; + } + + const response = payload.res.withProp('url', payload.req.url); + return payload.withResponse(response); +} diff --git a/src/presenters/mapResponseProps.js b/src/presenters/mapResponseProps.js deleted file mode 100644 index 2293b3a..0000000 --- a/src/presenters/mapResponseProps.js +++ /dev/null @@ -1,23 +0,0 @@ -import {reduce} from 'lodash'; - - - -/** - * - */ -const mapProp = (res, to, from) => - res.has(from) - ? res.withProp(to, res.get(from)) - : res; - - - -/** - * - */ -export default function mapResponseProps(mapping, {req, res}) { - return { - req, - res: reduce(mapping, mapProp, res) - }; -} diff --git a/src/presenters/mapResponseProps.ts b/src/presenters/mapResponseProps.ts new file mode 100644 index 0000000..c9793c4 --- /dev/null +++ b/src/presenters/mapResponseProps.ts @@ -0,0 +1,16 @@ +import {reduce} from 'lodash'; +import Payload from '../Payload'; +import Response from '../Response'; + + + +const mapProp = (res: Response, to: string, from: string): Response => + res.has(from) + ? res.withProp(to, res.get(from)) + : res; + +export default function mapResponseProps(mapping: object, payload: Payload): Payload { + return payload.withResponse( + reduce(mapping, mapProp, payload.res) + ); +} diff --git a/test/createContainer.js b/test/Container.ts similarity index 61% rename from test/createContainer.js rename to test/Container.ts index 654dec1..80685fa 100644 --- a/test/createContainer.js +++ b/test/Container.ts @@ -1,13 +1,23 @@ import {expect} from 'chai'; +import {spy} from 'sinon'; import {constant} from 'lodash'; -import {createContainer} from '../src'; +import {Container} from '../src'; -describe('createContainer', function() { +describe('Container', function() { describe('get', function() { + it('should pass itself to factories', function() { + const container = new Container() + .with('factory', function(get) { + expect(get).to.equal(container.get); + }); + + container.get('factory') + }); + it('should return the result of a factory', function() { - const container = createContainer() + const container = new Container() .with('factory', constant('result')); expect(container.get('factory')) @@ -15,7 +25,7 @@ describe('createContainer', function() { }); it('should throw if a factory doesn\'t exist', function() { - const container = createContainer(); + const container = new Container(); expect(() => container.get('factory')) .to.throw(Error); @@ -24,7 +34,7 @@ describe('createContainer', function() { describe('with', function() { it('should return a new Container without affecting the original one', function() { - const container = createContainer(); + const container = new Container(); const otherContainer = container .with('factory', constant('result')); @@ -36,19 +46,20 @@ describe('createContainer', function() { }); it('should call the factory for each get()', function() { - let count = 0; - const factory = () => ++count; - const container = createContainer() + const factory = spy(() => null); + const container = new Container() .with('factory', factory); - expect(container.get('factory')).to.equal(1); - expect(container.get('factory')).to.equal(2); + container.get('factory'); + container.get('factory'); + + expect(factory.callCount).to.equal(2); }); }); describe('withUnique', function() { it('should return a new Container without affecting the original one', function() { - const container = createContainer(); + const container = new Container(); const otherContainer = container .withUnique('factory', () => 'result'); @@ -60,13 +71,14 @@ describe('createContainer', function() { }); it('should call the factory only once', function() { - let count = 0; - const factory = () => ++count; - const container = createContainer() + const factory = spy(() => null); + const container = new Container() .withUnique('factory', factory); - expect(container.get('factory')).to.equal(1); - expect(container.get('factory')).to.equal(1); + container.get('factory'); + container.get('factory'); + + expect(factory.callCount).to.equal(1); }); }); }); diff --git a/test/createRequest.js b/test/Request.ts similarity index 57% rename from test/createRequest.js rename to test/Request.ts index 1e42380..aa386ff 100644 --- a/test/createRequest.js +++ b/test/Request.ts @@ -1,15 +1,15 @@ import {expect} from 'chai'; -import {createRequest} from '../src'; +import {Request} from '../src'; -describe('createRequest', function() { +describe('Request', function() { describe('url', function() { it('should return the URL', function() { const url = 'url'; - const request = createRequest(url); + const request = new Request(url); - expect(request.url()).to.equal(url); + expect(request.url).to.equal(url); }); }); @@ -17,11 +17,11 @@ describe('createRequest', function() { it('should return a new Request without affecting the original one', function() { const url = 'url'; const otherUrl = 'other-url'; - const request = createRequest(url); + const request = new Request(url); const otherRequest = request.withUrl(otherUrl); - expect(request.url()).to.equal(url); - expect(otherRequest.url()).to.equal(otherUrl); + expect(request.url).to.equal(url); + expect(otherRequest.url).to.equal(otherUrl); }); }); }); diff --git a/test/createResponse.js b/test/Response.ts similarity index 77% rename from test/createResponse.js rename to test/Response.ts index 032dcc7..0e2ce6e 100644 --- a/test/createResponse.js +++ b/test/Response.ts @@ -1,12 +1,12 @@ import {expect} from 'chai'; -import {createResponse} from '../src'; +import {Response} from '../src'; -describe('createResponse', function() { +describe('Response', function() { describe('isEmpty', function() { it('should tell if the response is empty', function() { - const response = createResponse(); + const response = new Response(); const otherResponse = response.withProp('title', 'Title'); expect(response.isEmpty()).to.be.true; @@ -16,7 +16,8 @@ describe('createResponse', function() { describe('has', function() { it('should tell if a prop exists', function() { - const response = createResponse().withProp('foo', 'bar'); + const response = new Response() + .withProp('foo', 'bar'); expect(response.has('foo')).to.be.true; expect(response.has('bar')).to.be.false; @@ -25,7 +26,7 @@ describe('createResponse', function() { describe('get/first', function() { it('should return the first prop for a key', function() { - const response = createResponse() + const response = new Response() .withProp('foo', 'bar') .withProp('foo', 'baz'); @@ -33,7 +34,7 @@ describe('createResponse', function() { }); it('should return a default value for a missing key', function() { - const response = createResponse(); + const response = new Response(); const missing = 'missing'; expect(response.get('foo', missing)) @@ -43,7 +44,7 @@ describe('createResponse', function() { describe('all', function() { it('should return all the prop for a key', function() { - const response = createResponse() + const response = new Response() .withProp('foo', 'bar') .withProp('foo', 'baz'); @@ -54,10 +55,11 @@ describe('createResponse', function() { describe('keys', function() { it('should return all the keys', function() { - const response = createResponse().withProps({ - foo: 1, - bar: 2 - }); + const response = new Response() + .withProps({ + foo: 1, + bar: 2 + }); expect(response.keys()) .to.deep.equal(['foo', 'bar']); @@ -66,7 +68,7 @@ describe('createResponse', function() { describe('count', function() { it('should return prop count for a key', function() { - const response = createResponse() + const response = new Response() .withProp('foo', 'bar') .withProp('foo', 'baz'); @@ -76,10 +78,12 @@ describe('createResponse', function() { describe('groups', function() { it('should return prop groups for keys', function() { - const response = createResponse().withProps({ - foo: ['foo1', 'foo2'], - bar: ['bar1', 'bar2'] - }); + const response = new Response() + .withProps({ + foo: ['foo1', 'foo2'], + bar: ['bar1', 'bar2'], + baz: ['baz1', 'baz2'] + }); expect(response.groups(['foo', 'bar'])) .to.deep.equal([ @@ -91,10 +95,11 @@ describe('createResponse', function() { describe('allGroups', function() { it('should return prop groups for all keys', function() { - const response = createResponse().withProps({ - foo: ['foo1', 'foo2'], - bar: ['bar1', 'bar2'] - }); + const response = new Response() + .withProps({ + foo: ['foo1', 'foo2'], + bar: ['bar1', 'bar2'] + }); expect(response.allGroups()) .to.deep.equal([ @@ -106,7 +111,7 @@ describe('createResponse', function() { describe('withProp', function() { it('should return a new Response without affecting the original one', function() { - const response = createResponse(); + const response = new Response(); const otherResponse = response.withProp('foo', 'bar'); expect(response.get('foo')).to.be.undefined; @@ -116,7 +121,7 @@ describe('createResponse', function() { describe('withProps', function() { it('should return a new Response without affecting the original one', function() { - const response = createResponse(); + const response = new Response(); const otherResponse = response.withProps({ foo: 'bar', bar: 'baz' diff --git a/test/condition.js b/test/condition.ts similarity index 77% rename from test/condition.js rename to test/condition.ts index 7532687..a8833b2 100644 --- a/test/condition.js +++ b/test/condition.ts @@ -1,8 +1,8 @@ -import chai, {expect} from 'chai'; -import chaiAsPromised from 'chai-as-promised'; +import {use, expect} from 'chai'; +import * as chaiAsPromised from 'chai-as-promised'; import {condition as createCondition} from '../src'; -chai.use(chaiAsPromised); +use(chaiAsPromised); @@ -10,8 +10,11 @@ describe('condition', function() { it('should pass a payload to the condition', function() { const payload = 'payload'; const condition = createCondition( - (p) => expect(p).to.equal(payload), - () => {} + (p) => { + expect(p).to.equal(payload) + return true; + }, + async (p) => p ); return condition(payload); @@ -23,7 +26,7 @@ describe('condition', function() { const condition = createCondition( () => true, - () => updated + async (p) => updated ); return expect(condition(original)) @@ -36,7 +39,7 @@ describe('condition', function() { const condition = createCondition( () => false, - () => updated + async () => updated ); return expect(condition(original)) diff --git a/test/conditions/isResponseEmpty.js b/test/conditions/isResponseEmpty.js deleted file mode 100644 index e38a403..0000000 --- a/test/conditions/isResponseEmpty.js +++ /dev/null @@ -1,17 +0,0 @@ -import {expect} from 'chai'; -import {isResponseEmpty, createResponse} from '../../src'; - - - -describe('isResponseEmpty', function() { - it('should tell if the response is empty', function() { - expect(isResponseEmpty({ - res: createResponse() - })).to.be.true; - - expect(isResponseEmpty({ - res: createResponse() - .withProp('title', 'Title') - })).to.be.false; - }); -}); diff --git a/test/conditions/isResponseEmpty.ts b/test/conditions/isResponseEmpty.ts new file mode 100644 index 0000000..acab1e8 --- /dev/null +++ b/test/conditions/isResponseEmpty.ts @@ -0,0 +1,16 @@ +import {expect} from 'chai'; +import {isResponseEmpty, Payload} from '../../src'; + + + +describe('isResponseEmpty', function() { + it('should tell if the response is empty', function() { + const empty = Payload.from(''); + const notEmpty = empty.withResponse((res) => + res.withProp('foo', 'bar') + ); + + expect(isResponseEmpty(empty)).to.be.true; + expect(isResponseEmpty(notEmpty)).to.be.false; + }); +}); diff --git a/test/conditions/requestUrlMatchesRegex.js b/test/conditions/requestUrlMatchesRegex.js deleted file mode 100644 index 403bc00..0000000 --- a/test/conditions/requestUrlMatchesRegex.js +++ /dev/null @@ -1,32 +0,0 @@ -import {expect} from 'chai'; -import {requestUrlMatchesRegex, createRequest} from '../../src'; - - - -describe('requestUrlMatchesRegex', function() { - it('should tell if the requested URL matches a regex', function() { - const isYoutubeRequest = - requestUrlMatchesRegex.bind( - null, - /youtube\.com|youtu\.be/i - ); - - expect(isYoutubeRequest({ - req: createRequest( - 'https://www.youtube.com/watch?v=eNcbmrKdf3U' - ) - })).to.be.true; - - expect(isYoutubeRequest({ - req: createRequest( - 'https://youtu.be/G5R-F1RmL5Y' - ) - })).to.be.true; - - expect(isYoutubeRequest({ - req: createRequest( - 'https://www.google.com' - ) - })).to.be.false; - }); -}); diff --git a/test/conditions/requestUrlMatchesRegex.ts b/test/conditions/requestUrlMatchesRegex.ts new file mode 100644 index 0000000..be21966 --- /dev/null +++ b/test/conditions/requestUrlMatchesRegex.ts @@ -0,0 +1,26 @@ +import {expect} from 'chai'; +import {partial} from 'lodash'; +import {requestUrlMatchesRegex, Payload} from '../../src'; + + + +describe('requestUrlMatchesRegex', function() { + it('should tell if the requested URL matches a regex', function() { + const isYoutubeRequest = partial( + requestUrlMatchesRegex, + /youtube\.com|youtu\.be/i + ); + + expect(isYoutubeRequest( + Payload.from('https://www.youtube.com/watch?v=eNcbmrKdf3U') + )).to.be.true; + + expect(isYoutubeRequest( + Payload.from('https://youtu.be/G5R-F1RmL5Y') + )).to.be.true; + + expect(isYoutubeRequest( + Payload.from('https://www.google.com') + )).to.be.false; + }); +}); diff --git a/test/container.js b/test/defaultContainer.ts similarity index 77% rename from test/container.js rename to test/defaultContainer.ts index 7a7d67c..1fb01cf 100644 --- a/test/container.js +++ b/test/defaultContainer.ts @@ -1,19 +1,20 @@ -import chai, {expect} from 'chai'; -import chaiAsPromised from 'chai-as-promised'; -import {container} from '../src'; +import {use, expect} from 'chai'; +import * as chaiAsPromised from 'chai-as-promised'; +import {defaultContainer, Response} from '../src'; -chai.use(chaiAsPromised); +use(chaiAsPromised); describe('container', function() { describe('extract', function() { + interface Extractor { + (url: string): Promise; + } + const propMatches = (key, value) => (res) => (res.get(key) === value); - const printResponse = (res) => - console.log(res.toJson(2)); - const tests = [ { name: 'a YouTube video', @@ -39,7 +40,7 @@ describe('container', function() { tests.forEach(({name, url, test}) => { it(`should extract info from ${name}`, function() { - const extract = container.get('extract'); + const extract = defaultContainer.get('extract'); return expect(extract(url)) .to.eventually.satisfy(test); }); diff --git a/test/extract.js b/test/extract.js index 99b1f55..eed04a2 100644 --- a/test/extract.js +++ b/test/extract.js @@ -1,24 +1,20 @@ -import chai, {expect} from 'chai'; -import chaiAsPromised from 'chai-as-promised'; -import {extract, pipe} from '../src'; +import {use, expect} from 'chai'; +import * as chaiAsPromised from 'chai-as-promised'; +import {extract} from '../src'; -chai.use(chaiAsPromised); +use(chaiAsPromised); describe('extract', function() { it('should extract an URL', function() { const url = 'http://example.com'; - const fillResponseUrl = async ({req, res}) => ({ - req, - res: res.withProp('url', req.url()) - }); - - const reduce = pipe([ - fillResponseUrl - ], false); + const fillResponseUrl = async (payload) => + payload.withResponse((res) => + res.withProp('url', payload.req.url) + ); - return expect(extract(reduce, url)) + return expect(extract(fillResponseUrl, url)) .to.eventually.satisfy((res) => (res.get('url') === url) ); diff --git a/test/extractors/extractMetaTags.js b/test/extractors/extractMetaTags.ts similarity index 65% rename from test/extractors/extractMetaTags.js rename to test/extractors/extractMetaTags.ts index 88380de..103be9a 100644 --- a/test/extractors/extractMetaTags.js +++ b/test/extractors/extractMetaTags.ts @@ -1,15 +1,15 @@ -import chai, {expect} from 'chai'; -import chaiAsPromised from 'chai-as-promised'; -import sinon from 'sinon'; -import {extractMetaTags, createRequest, createResponse} from '../../src'; +import {use, expect} from 'chai'; +import * as chaiAsPromised from 'chai-as-promised'; +import {spy} from 'sinon'; +import {extractMetaTags, Payload} from '../../src'; -chai.use(chaiAsPromised); +use(chaiAsPromised); describe('extractMetaTags', function() { it('should extract meta tags', function() { - const getBody = sinon.spy(async () => ` + const getBody = spy(async () => ` @@ -23,11 +23,7 @@ describe('extractMetaTags', function() { `); - const payload = { - req: createRequest('http://example.com/title'), - res: createResponse() - }; - + const payload = Payload.from('http://example.com/title'); const promise = extractMetaTags(getBody, /^ns:/i, payload); return expect(promise) diff --git a/test/extractors/extractOEmbed.js b/test/extractors/extractOEmbed.js deleted file mode 100644 index e900a80..0000000 --- a/test/extractors/extractOEmbed.js +++ /dev/null @@ -1,12 +0,0 @@ -import chai, {expect} from 'chai'; -import chaiAsPromised from 'chai-as-promised'; -import sinon from 'sinon'; -import {extractOEmbedAuto, OEmbedFormats, createRequest, createResponse} from '../../src'; - -chai.use(chaiAsPromised); - - - -describe('extractOEmbedAuto', function() { - -}); diff --git a/test/extractors/extractOEmbed.ts b/test/extractors/extractOEmbed.ts new file mode 100644 index 0000000..84ed865 --- /dev/null +++ b/test/extractors/extractOEmbed.ts @@ -0,0 +1,7 @@ +import {expect} from 'chai'; + + + +describe('extractOEmbedAuto', function() { + +}); diff --git a/test/extractors/extractOEmbedFromService.js b/test/extractors/extractOEmbedFromService.ts similarity index 61% rename from test/extractors/extractOEmbedFromService.js rename to test/extractors/extractOEmbedFromService.ts index cec53e1..6dcf0a4 100644 --- a/test/extractors/extractOEmbedFromService.js +++ b/test/extractors/extractOEmbedFromService.ts @@ -1,21 +1,15 @@ -import chai, {expect} from 'chai'; -import chaiAsPromised from 'chai-as-promised'; -import sinon from 'sinon'; -import { - extractOEmbedFromService, - OEmbedFormats, - createService, - createRequest, - createResponse -} from '../../src'; +import {use, expect} from 'chai'; +import * as chaiAsPromised from 'chai-as-promised'; +import {spy} from 'sinon'; +import {extractOEmbedFromService, OEmbedFormat, Payload} from '../../src'; -chai.use(chaiAsPromised); +use(chaiAsPromised); describe('extractOEmbedFromService', function() { it('should extract data from JSON', function() { - const getBody = sinon.spy(async () => ` + const getBody = spy(async () => ` { "version": "1.0", "type": "video", @@ -24,16 +18,12 @@ describe('extractOEmbedFromService', function() { } `); - const payload = { - req: createRequest('http://example.com/video'), - res: createResponse() + const payload = Payload.from('http://example.com/video'); + const service = { + endpoint: 'http://example.com/oembed?url=:url', + format: OEmbedFormat.Json }; - const service = createService( - 'http://example.com/oembed?url=:url', - OEmbedFormats.json - ); - const promise = extractOEmbedFromService( getBody, service, @@ -49,7 +39,7 @@ describe('extractOEmbedFromService', function() { }); it('should extract data from XML', function() { - const getBody = sinon.spy(async () => ` + const getBody = spy(async () => ` @@ -60,16 +50,12 @@ describe('extractOEmbedFromService', function() { `); - const payload = { - req: createRequest('http://example.com/video'), - res: createResponse() + const payload = Payload.from('http://example.com/video'); + const service = { + endpoint: 'http://example.com/oembed?url=:url', + format: OEmbedFormat.Xml }; - const service = createService( - 'http://example.com/oembed?url=:url', - OEmbedFormats.xml - ); - const promise = extractOEmbedFromService( getBody, service, diff --git a/test/extractors/oEmbed/findServiceFromHtml.js b/test/extractors/oEmbed/findServiceFromHtml.ts similarity index 81% rename from test/extractors/oEmbed/findServiceFromHtml.js rename to test/extractors/oEmbed/findServiceFromHtml.ts index f96f6c6..a28a77f 100644 --- a/test/extractors/oEmbed/findServiceFromHtml.js +++ b/test/extractors/oEmbed/findServiceFromHtml.ts @@ -1,6 +1,6 @@ import {expect} from 'chai'; import {forEach} from 'lodash'; -import {findServiceFromHtml, OEmbedFormats, createService} from '../../../src'; +import {findServiceFromHtml, OEmbedFormat} from '../../../src'; @@ -19,10 +19,14 @@ describe('findServiceFromHtml', function() { .to.be.undefined; }); - forEach(OEmbedFormats, (format, name) => { + forEach(OEmbedFormat, (format, name) => { it(`should find a service in ${name} format`, function() { const endpoint = 'http://example.com/oembed?url=http://example.com/video'; - const service = createService(endpoint, format); + const service = { + endpoint, + format + }; + const html = ` diff --git a/test/extractors/oEmbed/findServiceFromList.js b/test/extractors/oEmbed/findServiceFromList.ts similarity index 73% rename from test/extractors/oEmbed/findServiceFromList.js rename to test/extractors/oEmbed/findServiceFromList.ts index 1abe505..45660d2 100644 --- a/test/extractors/oEmbed/findServiceFromList.js +++ b/test/extractors/oEmbed/findServiceFromList.ts @@ -1,16 +1,17 @@ import {expect} from 'chai'; -import {findServiceFromList, OEmbedFormats} from '../../../src'; +import {findServiceFromList, OEmbedFormat} from '../../../src'; describe('findServiceFromList', function() { - const services = { - youtube: { + const services = [ + { + name: 'YouTube', pattern: /youtube\.com|youtu\.be/i, endpoint: 'http://www.youtube.com/oembed?format=json&url=:url', - format: OEmbedFormats.json + format: OEmbedFormat.Json } - }; + ]; it('should find no service', function() { const service = findServiceFromList( @@ -27,6 +28,6 @@ describe('findServiceFromList', function() { 'https://www.youtube.com/watch?v=ZuZz9NSOh10' ); - expect(service).to.deep.equal(services.youtube); + expect(service).to.deep.equal(services[0]); }); }); diff --git a/test/pipe.js b/test/pipe.ts similarity index 83% rename from test/pipe.js rename to test/pipe.ts index 68994ea..b35205a 100644 --- a/test/pipe.js +++ b/test/pipe.ts @@ -1,9 +1,9 @@ -import chai, {expect} from 'chai'; -import chaiAsPromised from 'chai-as-promised'; +import {use, expect} from 'chai'; +import * as chaiAsPromised from 'chai-as-promised'; import {stubTrue} from 'lodash'; -import {pipe, createErrors} from '../src'; +import {pipe} from '../src'; -chai.use(chaiAsPromised); +use(chaiAsPromised); diff --git a/test/preparators/refactorRequestUrl.js b/test/preparators/refactorRequestUrl.ts similarity index 55% rename from test/preparators/refactorRequestUrl.js rename to test/preparators/refactorRequestUrl.ts index 84253f6..9edb6fc 100644 --- a/test/preparators/refactorRequestUrl.js +++ b/test/preparators/refactorRequestUrl.ts @@ -1,22 +1,19 @@ import {expect} from 'chai'; -import {refactorRequestUrl, createRequest} from '../../src'; +import {refactorRequestUrl, Payload} from '../../src'; describe('refactorRequestUrl', function() { it('should refactor URLs', function() { - const payload = { - req: createRequest('https://youtu.be/id') - }; - + const payload = Payload.from('https://youtu.be/id'); const refactored = refactorRequestUrl( /^(.*)(v=|v\/|embed\/|youtu\.be\/)([a-z0-9_-]+)(.*)$/i, 'https://www.youtube.com/watch?v=$3', payload ); - expect(refactored).to.satisfy(({req}) => - (req.url() === 'https://www.youtube.com/watch?v=id') + expect(refactored).to.satisfy((payload) => + (payload.req.url === 'https://www.youtube.com/watch?v=id') ); }); }); diff --git a/test/presenters/mapResponseProps.js b/test/presenters/mapResponseProps.ts similarity index 64% rename from test/presenters/mapResponseProps.js rename to test/presenters/mapResponseProps.ts index 28706f4..a4a2a69 100644 --- a/test/presenters/mapResponseProps.js +++ b/test/presenters/mapResponseProps.ts @@ -1,5 +1,5 @@ import {expect} from 'chai'; -import {mapResponseProps, createResponse} from '../../src'; +import {mapResponseProps, Payload} from '../../src'; @@ -9,9 +9,11 @@ describe('mapResponseProps', function() { 'foo': 'baz' }; - const payload = { - res: createResponse().withProp('foo', 'bar') - }; + const payload = Payload + .from('') + .withResponse((res) => + res.withProp('foo', 'bar') + ); const mapped = mapResponseProps(mapping, payload); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..e9cfe88 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "allowJs": true, + "outDir": "./lib", + "target": "es5", + "module": "commonjs", + "lib": [ + "es2015" + ] + }, + "include": [ + "./src/**/*" + ] +}