diff --git a/package.json b/package.json index a4b7cde..6a5a38b 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "build:backend": "scripts/build-backend.sh", "check:backend": "tsc -p packages/backend --noEmit", "build:common": "tsc -p packages/common", - "check:common": "tsc -p packages/common --noEmit" + "check:common": "tsc -p packages/common --noEmit", + "test:common": "scripts/test-common.sh" } } diff --git a/packages/common/package.json b/packages/common/package.json index faf3f3e..5e0f97b 100644 --- a/packages/common/package.json +++ b/packages/common/package.json @@ -10,6 +10,10 @@ "node": ">=9.5" }, "scripts": { - "test": "echo \"Tests only available from root wrapper\"" + "test": "node ./test | tap-spec" + }, + "devDependencies": { + "tap-spec": "^5.0.0", + "tape": "^4.9.1" } } diff --git a/packages/common/src/helpers.ts b/packages/common/src/helpers.ts index eb1c1f0..b139b06 100644 --- a/packages/common/src/helpers.ts +++ b/packages/common/src/helpers.ts @@ -112,3 +112,81 @@ export class NumStats { return this.index < this.history ? this.stack.slice(0, this.index) : this.stack; } } + +/** + * Insert an item into a sorted array using binary search. + * + * @type {T} item type + * @param {T} item to be inserted + * @param {Array} array to be modified + * @param {(a, b) => number} compare function + * + * @return {number} insertion index + */ +export function sortedInsert(item: T, into: Array, compare: (a: T, b: T) => number): number { + if (into.length === 0) { + into.push(item); + + return 0; + } + + let min = 0; + let max = into.length - 1; + + while (min !== max) { + const guess = (min + max) / 2 | 0; + + if (compare(item, into[guess]) < 0) { + max = Math.max(min, guess - 1); + } else { + min = Math.min(max, guess + 1); + } + } + + let insert = compare(item, into[min]) <= 0 ? min : min + 1; + + into.splice(insert, 0, item); + + return insert; +} + +/** + * Find an index of an element within a sorted array. This should be substantially + * faster than `indexOf` for large arrays. + * + * @type {T} item type + * @param {T} item to find + * @param {Array} array to look through + * @param {(a, b) => number} compare function + * + * @return {number} index of the element, `-1` if not found + */ +export function sortedIndexOf(item:T, within: Array, compare: (a: T, b: T) => number): number { + if (within.length === 0) { + return -1; + } + + let min = 0; + let max = within.length - 1; + + while (min !== max) { + const guess = (min + max) / 2 | 0; + const other = within[guess]; + + if (item === other) { + return guess; + } + + if (compare(item, other) < 0) { + max = Math.max(min, guess - 1); + } else { + min = Math.min(max, guess + 1); + } + } + + if (item === within[min]) { + return min; + } + + return -1; +} diff --git a/packages/common/test/index.js b/packages/common/test/index.js new file mode 100644 index 0000000..fbdb3f2 --- /dev/null +++ b/packages/common/test/index.js @@ -0,0 +1,94 @@ +const test = require('tape'); +const common = require('../build/'); + +test('sortedInsert', (assert) => { + const { sortedInsert } = common; + const cmp = (a, b) => a - b; + + let mod = sortedInsert(3, [1,2,4,5], cmp); + + function assertInsert(item, into, equals) { + sortedInsert(item, into, cmp); + assert.same(into, equals, `Inserts ${item}`); + } + + assertInsert(1, [2,3,4,5,6,7,8,9], [1,2,3,4,5,6,7,8,9]); + assertInsert(2, [1,3,4,5,6,7,8,9], [1,2,3,4,5,6,7,8,9]); + assertInsert(3, [1,2,4,5,6,7,8,9], [1,2,3,4,5,6,7,8,9]); + assertInsert(4, [1,2,3,5,6,7,8,9], [1,2,3,4,5,6,7,8,9]); + assertInsert(5, [1,2,3,4,6,7,8,9], [1,2,3,4,5,6,7,8,9]); + assertInsert(6, [1,2,3,4,5,7,8,9], [1,2,3,4,5,6,7,8,9]); + assertInsert(7, [1,2,3,4,5,6,8,9], [1,2,3,4,5,6,7,8,9]); + assertInsert(8, [1,2,3,4,5,6,7,9], [1,2,3,4,5,6,7,8,9]); + assertInsert(9, [1,2,3,4,5,6,7,8], [1,2,3,4,5,6,7,8,9]); + + assert.end(); +}); + +test('sortedInsert fuzz', (assert) => { + const { sortedInsert } = common; + const cmp = (a, b) => a - b; + const scramble = () => Math.random() - 0.5; + const sorted = [1,2,3,4,5,6,7,8,9]; + + for (let i = 0; i < 50; i++) { + const scrambled = sorted.sort(scramble); + const resorted = []; + + for (const item of scrambled) { + sortedInsert(item, resorted, cmp); + } + + assert.same(resorted, [1,2,3,4,5,6,7,8,9], `resort ${scrambled}`); + } + + assert.end(); +}); + +test('sortedInsert indexes', (assert) => { + const { sortedInsert } = common; + const cmp = (a, b) => a - b; + const into = []; + + assert.equals(sortedInsert(5, into, cmp), 0, 'Insert 5'); + assert.same(into, [5], 'Elements check out'); + assert.equals(sortedInsert(1, into, cmp), 0, 'Insert 1'); + assert.same(into, [1,5], 'Elements check out'); + assert.equals(sortedInsert(9, into, cmp), 2, 'Insert 9'); + assert.same(into, [1,5,9], 'Elements check out'); + assert.equals(sortedInsert(3, into, cmp), 1, 'Insert 3'); + assert.same(into, [1,3,5,9], 'Elements check out'); + assert.equals(sortedInsert(7, into, cmp), 3, 'Insert 7'); + assert.same(into, [1,3,5,7,9], 'Elements check out'); + assert.equals(sortedInsert(4, into, cmp), 2, 'Insert 4'); + assert.same(into, [1,3,4,5,7,9], 'Elements check out'); + assert.equals(sortedInsert(6, into, cmp), 4, 'Insert 6'); + assert.same(into, [1,3,4,5,6,7,9], 'Elements check out'); + assert.equals(sortedInsert(2, into, cmp), 1, 'Insert 2'); + assert.same(into, [1,2,3,4,5,6,7,9], 'Elements check out'); + assert.equals(sortedInsert(8, into, cmp), 7, 'Insert 8'); + assert.same(into, [1,2,3,4,5,6,7,8,9], 'Elements check out'); + + assert.end(); +}); + +test('sortedIndexOf', (assert) => { + const { sortedIndexOf } = common; + const cmp = (a, b) => a - b; + + assert.equals(sortedIndexOf(1, [1,2,3,4,5,6,7,8,9], cmp), 0, 'Found 1'); + assert.equals(sortedIndexOf(2, [1,2,3,4,5,6,7,8,9], cmp), 1, 'Found 2'); + assert.equals(sortedIndexOf(3, [1,2,3,4,5,6,7,8,9], cmp), 2, 'Found 3'); + assert.equals(sortedIndexOf(4, [1,2,3,4,5,6,7,8,9], cmp), 3, 'Found 4'); + assert.equals(sortedIndexOf(5, [1,2,3,4,5,6,7,8,9], cmp), 4, 'Found 5'); + assert.equals(sortedIndexOf(6, [1,2,3,4,5,6,7,8,9], cmp), 5, 'Found 6'); + assert.equals(sortedIndexOf(7, [1,2,3,4,5,6,7,8,9], cmp), 6, 'Found 7'); + assert.equals(sortedIndexOf(8, [1,2,3,4,5,6,7,8,9], cmp), 7, 'Found 8'); + assert.equals(sortedIndexOf(9, [1,2,3,4,5,6,7,8,9], cmp), 8, 'Found 9'); + + assert.equals(sortedIndexOf(0, [1,2,3,4,5,6,7,8,9], cmp), -1, 'No 0'); + assert.equals(sortedIndexOf(10, [1,2,3,4,5,6,7,8,9], cmp), -1, 'No 10'); + assert.equals(sortedIndexOf(5.5, [1,2,3,4,5,6,7,8,9], cmp), -1, 'No 5.5'); + + assert.end(); +}); diff --git a/packages/frontend/src/App.tsx b/packages/frontend/src/App.tsx index 0c8acdf..72762b8 100644 --- a/packages/frontend/src/App.tsx +++ b/packages/frontend/src/App.tsx @@ -3,7 +3,7 @@ import { Types } from '@dotstats/common'; import { Chains, Chain, Ago, OfflineIndicator } from './components'; import { Connection } from './Connection'; import { PersistentObject, PersistentSet } from './persist'; -import { State } from './state'; +import { State, compareNodes } from './state'; import './App.css'; @@ -36,13 +36,13 @@ export default class App extends React.Component<{}, State> { ); this.pins = new PersistentSet('pinned_names', (pins) => { - const { nodes } = this.state; + const { nodes, sortedNodes } = this.state; for (const node of nodes.values()) { node.pinned = pins.has(node.nodeDetails[0]); } - this.setState({ nodes, pins }); + this.setState({ nodes, pins, sortedNodes: sortedNodes.sort(compareNodes) }); }); this.state = { @@ -54,6 +54,7 @@ export default class App extends React.Component<{}, State> { subscribed: null, chains: new Map(), nodes: new Map(), + sortedNodes: [], settings: this.settings.raw(), pins: this.pins.get(), }; diff --git a/packages/frontend/src/Connection.ts b/packages/frontend/src/Connection.ts index 3fe4bb8..6cfc5be 100644 --- a/packages/frontend/src/Connection.ts +++ b/packages/frontend/src/Connection.ts @@ -1,5 +1,6 @@ import { VERSION, timestamp, FeedMessage, Types, Maybe, sleep } from '@dotstats/common'; -import { State, Update } from './state'; +import { sortedInsert, sortedIndexOf } from '@dotstats/common'; +import { State, Update, compareNodes } from './state'; import { PersistentSet } from './persist'; const { Actions } = FeedMessage; @@ -140,9 +141,8 @@ export class Connection { private handleMessages = (event: MessageEvent) => { const data = event.data as FeedMessage.Data; - const nodes = this.state.nodes; - const chains = this.state.chains; - const changes = { nodes, chains }; + const { nodes, chains } = this.state; + let { sortedNodes } = this.state; messages: for (const message of FeedMessage.deserialize(data)) { switch (message.action) { @@ -176,14 +176,32 @@ export class Connection { const node = { pinned, id, nodeDetails, nodeStats, blockDetails, location }; nodes.set(id, node); + sortedInsert(node, sortedNodes, compareNodes); + + if (nodes.size !== sortedNodes.length) { + console.error('Node count in sorted array is wrong!'); + } break; } case Actions.RemovedNode: { - nodes.delete(message.payload); + const id = message.payload; + const node = nodes.get(id); - break; + if (node) { + nodes.delete(id); + const index = sortedIndexOf(node, sortedNodes, compareNodes); + sortedNodes.splice(index, 1); + + if (nodes.size !== sortedNodes.length) { + console.error('Node count in sorted array is wrong!'); + } + + break; + } + + continue messages; } case Actions.LocatedNode: { @@ -191,7 +209,7 @@ export class Connection { const node = nodes.get(id); if (!node) { - return; + continue messages; } node.location = [latitude, longitude, city]; @@ -204,10 +222,11 @@ export class Connection { const node = nodes.get(id); if (!node) { - return; + continue messages; } node.blockDetails = blockDetails; + sortedNodes = sortedNodes.sort(compareNodes); break; } @@ -245,6 +264,7 @@ export class Connection { if (this.state.subscribed === message.payload) { nodes.clear(); + sortedNodes = []; this.state = this.update({ subscribed: null, nodes, chains }); @@ -263,6 +283,7 @@ export class Connection { case Actions.UnsubscribedFrom: { if (this.state.subscribed === message.payload) { nodes.clear(); + sortedNodes = []; this.state = this.update({ subscribed: null, nodes }); } @@ -281,7 +302,7 @@ export class Connection { } } - this.state = this.update(changes); + this.state = this.update({ nodes, chains, sortedNodes }); this.autoSubscribe(); } diff --git a/packages/frontend/src/components/Chain/Chain.css b/packages/frontend/src/components/Chain/Chain.css index cf5c037..6e91f0e 100644 --- a/packages/frontend/src/components/Chain/Chain.css +++ b/packages/frontend/src/components/Chain/Chain.css @@ -8,34 +8,14 @@ position: relative; } - .Chain-tabs { position: absolute; - right: 9px; - bottom: 0; - height: 36px; + right: 5px; + bottom: 10px; width: 200px; text-align: right; } -.Chain-tab-unit { - display: inline-block; -} - -.Chain-tab-unit .Icon { - margin-right: 1px; - font-size: 24px; - padding: 6px; - background: #ccc; - color: #555; - cursor: pointer; -} - -.Chain-tab-unit-on .Icon { - background: #222; - color: #fff; -} - .Chain-content-container { position: absolute; left: 0; diff --git a/packages/frontend/src/components/Chain/Chain.tsx b/packages/frontend/src/components/Chain/Chain.tsx index 3753d3e..a3b8a6f 100644 --- a/packages/frontend/src/components/Chain/Chain.tsx +++ b/packages/frontend/src/components/Chain/Chain.tsx @@ -43,23 +43,6 @@ export namespace Chain { } } -function sortNodes(a: AppState.Node, b: AppState.Node): number { - if (a.pinned === b.pinned) { - if (a.blockDetails[0] === b.blockDetails[0]) { - const aPropagation = a.blockDetails[4] == null ? Infinity : a.blockDetails[4] as number; - const bPropagation = b.blockDetails[4] == null ? Infinity : b.blockDetails[4] as number; - - // Ascending sort by propagation time - return aPropagation - bPropagation; - } - } else { - return Number(b.pinned) - Number(a.pinned); - } - - // Descending sort by block number - return b.blockDetails[0] - a.blockDetails[0]; -} - export class Chain extends React.Component { constructor(props: Chain.Props) { super(props); @@ -157,9 +140,7 @@ export class Chain extends React.Component { { - nodes - .sort(sortNodes) - .map((node) => ) + nodes.map((node) => ) } @@ -220,7 +201,7 @@ export class Chain extends React.Component { } private nodes(): AppState.Node[] { - return Array.from(this.props.appState.nodes.values()); + return this.props.appState.sortedNodes; } private pixelPosition(lat: Types.Latitude, lon: Types.Longitude): Node.Location.Position { diff --git a/packages/frontend/src/components/Chain/Tab.css b/packages/frontend/src/components/Chain/Tab.css new file mode 100644 index 0000000..4ff5513 --- /dev/null +++ b/packages/frontend/src/components/Chain/Tab.css @@ -0,0 +1,24 @@ + +.Chain-Tab { + display: inline-block; +} + +.Chain-Tab .Icon { + margin-right: 5px; + font-size: 24px; + padding: 6px; + color: #555; + cursor: pointer; + padding: 10px; + border-radius: 40px; + transition: background-color 0.15s linear; +} + +.Chain-Tab:hover .Icon { + background: #ccc; +} + +.Chain-Tab-on .Icon, .Chain-Tab-on:hover .Icon { + background: #d64ca8; + color: #fff; +} diff --git a/packages/frontend/src/components/Chain/Tab.tsx b/packages/frontend/src/components/Chain/Tab.tsx index 0a8fada..6a8d2e2 100644 --- a/packages/frontend/src/components/Chain/Tab.tsx +++ b/packages/frontend/src/components/Chain/Tab.tsx @@ -2,6 +2,8 @@ import * as React from 'react'; import { Chain } from './'; import { Icon } from '../'; +import './Tab.css'; + export namespace Tab { export interface Props { label: string; @@ -17,7 +19,7 @@ export class Tab extends React.Component { public render() { const { label, icon, display, current } = this.props; const highlight = display === current; - const className = highlight ? 'Chain-tab-unit-on Chain-tab-unit' : 'Chain-tab-unit'; + const className = highlight ? 'Chain-Tab-on Chain-Tab' : 'Chain-Tab'; return (
diff --git a/packages/frontend/src/state.ts b/packages/frontend/src/state.ts index fcaade3..ffd3a9f 100644 --- a/packages/frontend/src/state.ts +++ b/packages/frontend/src/state.ts @@ -35,8 +35,26 @@ export interface State { subscribed: Maybe; chains: Map; nodes: Map; + sortedNodes: State.Node[]; settings: Readonly; pins: Readonly>; } export type Update = (changes: Pick | null) => Readonly; + +export function compareNodes(a: State.Node, b: State.Node): number { + if (a.pinned === b.pinned) { + if (a.blockDetails[0] === b.blockDetails[0]) { + const aPropagation = a.blockDetails[4] == null ? Infinity : a.blockDetails[4] as number; + const bPropagation = b.blockDetails[4] == null ? Infinity : b.blockDetails[4] as number; + + // Ascending sort by propagation time + return aPropagation - bPropagation; + } + } else { + return +b.pinned - +a.pinned; + } + + // Descending sort by block number + return b.blockDetails[0] - a.blockDetails[0]; +} diff --git a/scripts/test-common.sh b/scripts/test-common.sh new file mode 100755 index 0000000..2de4396 --- /dev/null +++ b/scripts/test-common.sh @@ -0,0 +1 @@ +tsc -p packages/common && node packages/common/test | tap-spec diff --git a/yarn.lock b/yarn.lock index 1351a0f..f7af65a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1289,6 +1289,10 @@ buffer-indexof@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/buffer-indexof/-/buffer-indexof-1.1.1.tgz#52fabcc6a606d1a00302802648ef68f639da268c" +buffer-shims@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/buffer-shims/-/buffer-shims-1.0.0.tgz#9978ce317388c649ad8793028c3477ef044a8b51" + buffer-xor@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9" @@ -1421,7 +1425,7 @@ center-align@^0.1.1: align-text "^0.1.3" lazy-cache "^1.0.3" -chalk@1.1.3, chalk@^1.1.3: +chalk@1.1.3, chalk@^1.0.0, chalk@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" dependencies: @@ -2007,7 +2011,7 @@ decode-uri-component@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" -deep-equal@^1.0.1: +deep-equal@^1.0.1, deep-equal@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5" @@ -2055,7 +2059,7 @@ define-property@^2.0.2: is-descriptor "^1.0.2" isobject "^3.0.1" -defined@^1.0.0: +defined@^1.0.0, defined@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.0.tgz#c98d9bcef75674188e110969151199e39b1fa693" @@ -2321,7 +2325,7 @@ error-ex@^1.2.0: dependencies: is-arrayish "^0.2.1" -es-abstract@^1.5.1, es-abstract@^1.7.0: +es-abstract@^1.5.0, es-abstract@^1.5.1, es-abstract@^1.7.0: version "1.12.0" resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.12.0.tgz#9dbbdd27c6856f0001421ca18782d786bf8a6165" dependencies: @@ -2691,6 +2695,13 @@ fbjs@^0.8.16, fbjs@^0.8.9: setimmediate "^1.0.5" ua-parser-js "^0.7.18" +figures@^1.4.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/figures/-/figures-1.7.0.tgz#cbe1e3affcf1cd44b80cadfed28dc793a9701d2e" + dependencies: + escape-string-regexp "^1.0.5" + object-assign "^4.1.0" + figures@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962" @@ -2791,6 +2802,12 @@ follow-redirects@^1.0.0: dependencies: debug "^3.1.0" +for-each@~0.3.3: + version "0.3.3" + resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e" + dependencies: + is-callable "^1.1.3" + for-in@^1.0.1, for-in@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" @@ -2903,7 +2920,7 @@ fsevents@^1.0.0, fsevents@^1.1.3, fsevents@^1.2.2, fsevents@^1.2.3: nan "^2.9.2" node-pre-gyp "^0.10.0" -function-bind@^1.1.1: +function-bind@^1.0.2, function-bind@^1.1.1, function-bind@~1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" @@ -2979,6 +2996,17 @@ glob@^7.0.3, glob@^7.0.5, glob@^7.1.1, glob@^7.1.2: once "^1.3.0" path-is-absolute "^1.0.0" +glob@~7.1.2: + version "7.1.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1" + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + global-dirs@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-0.1.1.tgz#b319c0dd4607f353f3be9cca4c72fc148c49f445" @@ -3132,7 +3160,7 @@ has-values@^1.0.0: is-number "^3.0.0" kind-of "^4.0.0" -has@^1.0.1: +has@^1.0.1, has@~1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" dependencies: @@ -3549,7 +3577,7 @@ is-extglob@^2.1.0, is-extglob@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" -is-finite@^1.0.0: +is-finite@^1.0.0, is-finite@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/is-finite/-/is-finite-1.0.2.tgz#cc6677695602be550ef11e8b4aa6305342b6d0aa" dependencies: @@ -4595,7 +4623,7 @@ minimist@0.0.8: version "0.0.8" resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" -minimist@^1.1.0, minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0: +minimist@^1.1.0, minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0, minimist@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" @@ -4891,6 +4919,10 @@ object-copy@^0.1.0: define-property "^0.2.5" kind-of "^3.0.3" +object-inspect@~1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.6.0.tgz#c70b6cbf72f274aab4c34c0c82f5167bf82cf15b" + object-keys@^1.0.8: version "1.0.12" resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.0.12.tgz#09c53855377575310cca62f55bb334abff7b3ed2" @@ -5103,6 +5135,10 @@ parse-json@^2.2.0: dependencies: error-ex "^1.2.0" +parse-ms@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parse-ms/-/parse-ms-1.0.1.tgz#56346d4749d78f23430ca0c713850aef91aa361d" + parse-passwd@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/parse-passwd/-/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6" @@ -5215,6 +5251,10 @@ pkg-dir@^2.0.0: dependencies: find-up "^2.1.0" +plur@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/plur/-/plur-1.0.0.tgz#db85c6814f5e5e5a3b49efc28d604fec62975156" + pn@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/pn/-/pn-1.1.0.tgz#e2f4cef0e219f463c179ab37463e4e1ecdccbafb" @@ -5553,10 +5593,22 @@ pretty-format@^22.4.0, pretty-format@^22.4.3: ansi-regex "^3.0.0" ansi-styles "^3.2.0" +pretty-ms@^2.1.0: + version "2.1.0" + resolved "http://registry.npmjs.org/pretty-ms/-/pretty-ms-2.1.0.tgz#4257c256df3fb0b451d6affaab021884126981dc" + dependencies: + is-finite "^1.0.1" + parse-ms "^1.0.0" + plur "^1.0.0" + private@^0.1.6, private@^0.1.8: version "0.1.8" resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff" +process-nextick-args@~1.0.6: + version "1.0.7" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-1.0.7.tgz#150e20b756590ad3f91093f25a4f2ad8bff30ba3" + process-nextick-args@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa" @@ -5724,6 +5776,10 @@ rc@^1.0.1, rc@^1.1.6, rc@^1.2.7: minimist "^1.2.0" strip-json-comments "~2.0.1" +re-emitter@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/re-emitter/-/re-emitter-1.1.3.tgz#fa9e319ffdeeeb35b27296ef0f3d374dac2f52a7" + react-dev-utils@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/react-dev-utils/-/react-dev-utils-5.0.1.tgz#1f396e161fe44b595db1b186a40067289bf06613" @@ -5889,6 +5945,18 @@ readable-stream@1.0: isarray "0.0.1" string_decoder "~0.10.x" +readable-stream@2.2.9: + version "2.2.9" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.2.9.tgz#cf78ec6f4a6d1eb43d26488cac97f042e74b7fc8" + dependencies: + buffer-shims "~1.0.0" + core-util-is "~1.0.0" + inherits "~2.0.1" + isarray "~1.0.0" + process-nextick-args "~1.0.6" + string_decoder "~1.0.0" + util-deprecate "~1.0.1" + readdirp@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.1.0.tgz#4ed0ad060df3073300c48440373f72d1cc642d78" @@ -6123,6 +6191,12 @@ resolve@^1.1.7, resolve@^1.3.2: dependencies: path-parse "^1.0.5" +resolve@~1.7.1: + version "1.7.1" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.7.1.tgz#aadd656374fd298aee895bc026b8297418677fd3" + dependencies: + path-parse "^1.0.5" + restore-cursor@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf" @@ -6130,6 +6204,12 @@ restore-cursor@^2.0.0: onetime "^2.0.0" signal-exit "^3.0.2" +resumer@~0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/resumer/-/resumer-0.0.0.tgz#f1e8f461e4064ba39e82af3cdc2a8c893d076759" + dependencies: + through "~2.3.4" + ret@~0.1.10: version "0.1.15" resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" @@ -6527,6 +6607,12 @@ split-string@^3.0.1, split-string@^3.0.2: dependencies: extend-shallow "^3.0.0" +split@1.0.0: + version "1.0.0" + resolved "http://registry.npmjs.org/split/-/split-1.0.0.tgz#c4395ce683abcd254bc28fe1dabb6e5c27dcffae" + dependencies: + through "2" + sprintf-js@~1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" @@ -6636,6 +6722,14 @@ string-width@^1.0.1, string-width@^1.0.2: is-fullwidth-code-point "^2.0.0" strip-ansi "^4.0.0" +string.prototype.trim@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.1.2.tgz#d04de2c89e137f4d7d206f086b5ed2fae6be8cea" + dependencies: + define-properties "^1.1.2" + es-abstract "^1.5.0" + function-bind "^1.0.2" + string_decoder@^1.0.0, string_decoder@~1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" @@ -6646,6 +6740,12 @@ string_decoder@~0.10.x: version "0.10.31" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" +string_decoder@~1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.0.3.tgz#0fc67d7c141825de94282dd536bec6b9bce860ab" + dependencies: + safe-buffer "~5.1.0" + strip-ansi@3.0.1, strip-ansi@^3.0.0, strip-ansi@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" @@ -6763,10 +6863,50 @@ symbol-tree@^3.2.2: version "3.2.2" resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.2.tgz#ae27db38f660a7ae2e1c3b7d1bc290819b8519e6" +tap-out@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/tap-out/-/tap-out-2.1.0.tgz#c093079a915036de8b835bfa3297f14458b15358" + dependencies: + re-emitter "1.1.3" + readable-stream "2.2.9" + split "1.0.0" + trim "0.0.1" + +tap-spec@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/tap-spec/-/tap-spec-5.0.0.tgz#7329e4e66e8aa68da2a164215abbb903a7c5d352" + dependencies: + chalk "^1.0.0" + duplexer "^0.1.1" + figures "^1.4.0" + lodash "^4.17.10" + pretty-ms "^2.1.0" + repeat-string "^1.5.2" + tap-out "^2.1.0" + through2 "^2.0.0" + tapable@^0.2.7: version "0.2.8" resolved "https://registry.yarnpkg.com/tapable/-/tapable-0.2.8.tgz#99372a5c999bf2df160afc0d74bed4f47948cd22" +tape@^4.9.1: + version "4.9.1" + resolved "https://registry.yarnpkg.com/tape/-/tape-4.9.1.tgz#1173d7337e040c76fbf42ec86fcabedc9b3805c9" + dependencies: + deep-equal "~1.0.1" + defined "~1.0.0" + for-each "~0.3.3" + function-bind "~1.1.1" + glob "~7.1.2" + has "~1.0.3" + inherits "~2.0.3" + minimist "~1.2.0" + object-inspect "~1.6.0" + resolve "~1.7.1" + resumer "~0.0.0" + string.prototype.trim "~1.1.2" + through "~2.3.8" + tar@^4: version "4.4.4" resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.4.tgz#ec8409fae9f665a4355cc3b4087d0820232bb8cd" @@ -6810,7 +6950,7 @@ through2@^2.0.0: readable-stream "^2.1.5" xtend "~4.0.1" -through@^2.3.6: +through@2, through@^2.3.6, through@~2.3.4, through@~2.3.8: version "2.3.8" resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" @@ -6903,6 +7043,10 @@ trim-right@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003" +trim@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/trim/-/trim-0.0.1.tgz#5858547f6b290757ee95cccc666fb50084c460dd" + ts-jest@22.0.1: version "22.0.1" resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-22.0.1.tgz#48942936a466c2e76e259b02e2f1356f1839afc3"