diff --git a/examples/demo-app.js b/examples/demo-app.js index 69f8137..ff23de6 100644 --- a/examples/demo-app.js +++ b/examples/demo-app.js @@ -1,58 +1,100 @@ var { selectorGraph } = ReselectTools; var { createSelector } = Reselect; -ReselectTools.getStateWith(() => STORE); var STORE = { data: { users: { - '1': { - id: '1', + 'bob': { + id: 'bob', name: 'bob', - pets: ['a', 'b'], + age: 24, + pets: ['fluffy', 'paws'], }, - '2': { - id: '2', + 'alice': { + id: 'alice', name: 'alice', - pets: ['a'], + age: 22, + pets: ['fluffy'], } }, pets: { - 'a': { + 'fluffy': { name: 'fluffy', }, - 'b': { + 'paws': { name: 'paws', } } }, ui: { - currentUser: '1', + currentUser: 'alice', } }; -const data$ = (state) => state.data; const ui$ = (state) => state.ui; -var users$ = createSelector(data$, (data) => data.users); -var pets$ = createSelector(data$, ({ pets }) => pets); -var currentUser$ = createSelector(ui$, users$, (ui, users) => users[ui.currentUser]); +const users$ = (state) => state.data.users; +const pets$ = (state) => state.data.pets; +const currentUser$ = createSelector(ui$, users$, (ui, users) => users[ui.currentUser]); -var currentUserPets$ = createSelector(currentUser$, pets$, (currentUser, pets) => currentUser.pets.map((petId) => pets[petId])); +const currentUserPets$ = createSelector(currentUser$, pets$, (currentUser, pets) => currentUser.pets.map((petId) => pets[petId])); +const currentUserAge$ = createSelector(currentUser$, (currentUser) => currentUser.age); const random$ = (state) => 1; const thingy$ = createSelector(random$, (number) => number + 1); const selectors = { - data$, ui$, users$, pets$, currentUser$, currentUserPets$, + currentUserAge$, random$, thingy$, }; ReselectTools.registerSelectors(selectors); +ReselectTools.getStateWith(() => STORE); + + +drawCytoscapeGraph(selectorGraph()); +update(); + +function update() { + const currentUserDiv = document.getElementById('current-user'); + selectorGraph(); + currentUserDiv.innerHTML = `Current User: ${currentUser$(STORE).name}.`; +} + +document.addEventListener('DOMContentLoaded', () => { + const updateBobAge = () => { + STORE = Object.assign({}, STORE, { + data: Object.assign({}, STORE.data, { + users: Object.assign({}, STORE.data.users, { + bob: Object.assign({}, STORE.data.users.bob, { + age: STORE.data.users.bob.age + 1 + }) + }) + }) + }); + + update(); + }; + + const updateAliceAge = () => { + STORE = Object.assign({}, STORE, { + data: Object.assign({}, STORE.data, { + users: Object.assign({}, STORE.data.users, { + alice: Object.assign({}, STORE.data.users.alice, { + age: STORE.data.users.alice.age + 1 + }) + }) + }) + }); + update(); + }; -drawCytoscapeGraph(selectorGraph()); \ No newline at end of file + document.getElementById('incr-bob-age').addEventListener('click', updateBobAge); + document.getElementById('incr-alice-age').addEventListener('click', updateAliceAge); +}); diff --git a/examples/demo.html b/examples/demo.html index 75df76e..6a7e88f 100644 --- a/examples/demo.html +++ b/examples/demo.html @@ -6,6 +6,14 @@ height: 90vh; } + #info { + position: absolute; + bottom: 0; + left: 0; + background: white; + display: inline-block; + } + @@ -16,8 +24,13 @@ - - + +
+ Note that downstream selectors only recompute when you increment the current user's age.
+ + + +
diff --git a/extension/.eslintrc b/extension/.eslintrc index 51051f7..fb341d4 100644 --- a/extension/.eslintrc +++ b/extension/.eslintrc @@ -14,6 +14,7 @@ "react/jsx-filename-extension": [1, { "extensions": [".js", ".jsx"] }], "jsx-a11y/no-static-element-interactions": 0, "jsx-a11y/label-has-for": 0, + "no-param-reassign": 0, "consistent-return": 0, "comma-dangle": 0, "spaced-comment": 0, diff --git a/extension/app/actions/graph.js b/extension/app/actions/graph.js index 0127998..de30cf1 100644 --- a/extension/app/actions/graph.js +++ b/extension/app/actions/graph.js @@ -25,6 +25,6 @@ export function getSelectorGraphSuccess(graph) { return { type: types.GET_SELECTOR_GRAPH_SUCCESS, payload: { graph } }; } -export function getSelectorGraph() { - return { type: types.GET_SELECTOR_GRAPH }; +export function getSelectorGraph(resetRecomputations = false) { + return { type: types.GET_SELECTOR_GRAPH, payload: { resetRecomputations }}; } diff --git a/extension/app/components/Header.js b/extension/app/components/Header.js index 993b408..b6f5a89 100644 --- a/extension/app/components/Header.js +++ b/extension/app/components/Header.js @@ -2,6 +2,7 @@ import React, { Component, PropTypes } from 'react'; import Button from 'remotedev-app/lib/components/Button'; import MdHelp from 'react-icons/lib/md/help'; import FindReplace from 'react-icons/lib/md/find-replace'; +import Clear from 'react-icons/lib/md/clear'; import RefreshIcon from 'react-icons/lib/md/refresh'; import styles from 'remotedev-app/lib/styles'; @@ -18,6 +19,10 @@ const headerStyles = { color: 'white', outline: 'none', }, + helpButton: { + flexGrow: 0, + maxWidth: '40px', + }, }; class NumberButton extends Component { @@ -25,6 +30,7 @@ class NumberButton extends Component { defaultValue: PropTypes.number, onClick: PropTypes.func, numbers: PropTypes.array.isRequired, + children: PropTypes.node.isRequired, } constructor(props) { super(props); @@ -37,12 +43,9 @@ class NumberButton extends Component { this.setState({ value: e.target.value.toString() }); e.stopPropagation(); } - onClickWithNumber(e) { + onClickWithNumber() { this.props.onClick(parseInt(this.state.value, 10)); } - stopPropagation(e) { - e.stopPropagation(); - } render() { const { numbers, children, ...other } = this.props; const { value } = this.state; @@ -68,7 +71,13 @@ class NumberButton extends Component { } -export default function Header({ onRefresh, onHelp, onPaintWorst }) { +export default function Header({ + onRefresh, + onResetRecomputations, + onHelp, + onPaintWorst, + supportsRefreshRecomputations, +}) { return (
- + { supportsRefreshRecomputations ? + : null + } Select Most Recomputed - +
); } @@ -97,4 +112,6 @@ Header.propTypes = { onRefresh: PropTypes.func, onHelp: PropTypes.func, onPaintWorst: PropTypes.func, + onResetRecomputations: PropTypes.func, + supportsRefreshRecomputations: PropTypes.bool }; diff --git a/extension/app/components/StateTree.js b/extension/app/components/StateTree.js index 87d2c56..0864088 100644 --- a/extension/app/components/StateTree.js +++ b/extension/app/components/StateTree.js @@ -1,7 +1,7 @@ import JSONTree from 'react-json-tree'; import React, { PropTypes } from 'react'; -const shouldExpandNode = (keyName, data, level) => false; +const shouldExpandNode = (keyName, data, level) => level === 0; const isObject = o => typeof o === 'object'; diff --git a/extension/app/containers/App.js b/extension/app/containers/App.js index b66561b..ec39452 100644 --- a/extension/app/containers/App.js +++ b/extension/app/containers/App.js @@ -13,6 +13,10 @@ import Header from '../components/Header'; import * as SelectorActions from '../actions/graph'; +import { + supportsRefreshRecomputations$, + checkedSelector$ +} from '../selectors/selectors'; const contentStyles = { content: { @@ -48,35 +52,10 @@ function renderMessage(message) { function openGitRepo() { - const url = 'https://github.com/skortchmark9/reselect-devtools-extension'; + const url = 'https://github.com/skortchmark9/reselect-tools'; window.open(url, '_blank'); } -const checkedSelector$ = (state) => { - const { checkedSelectorId, nodes, edges } = state.graph; - const selector = nodes[checkedSelectorId]; - if (!selector) return; - - // this is a bit ugly because it relies on edges being in order. - const dependencies = edges.filter(edge => edge.from === checkedSelectorId); - const dependencyIds = dependencies.map(edge => edge.to); - - if (!selector.inputs) { - return selector; - } - - const { inputs } = selector; - if (dependencyIds.length !== inputs.length) { - console.error(`Uh oh, inputs and edges out of sync on ${checkedSelectorId}`); - } - - const zipped = []; - for (let i = 0; i < dependencyIds.length; i++) { - zipped.push([dependencyIds[i], inputs[i]]); - } - return { ...selector, zipped }; -}; - const RecomputationsTotal = ({ nodes }) => { const nodeArr = Object.keys(nodes).map(k => nodes[k]); @@ -84,10 +63,12 @@ const RecomputationsTotal = ({ nodes }) => { return

{total} Recomputations

; }; + @connect( state => ({ graph: state.graph, checkedSelector: checkedSelector$(state), + supportsRefreshRecomputations: supportsRefreshRecomputations$(state), }), dispatch => ({ actions: bindActionCreators(SelectorActions, dispatch) @@ -99,6 +80,7 @@ export default class App extends Component { actions: PropTypes.object.isRequired, graph: PropTypes.object, checkedSelector: PropTypes.object, + supportsRefreshRecomputations: PropTypes.bool, }; constructor(props) { @@ -110,6 +92,7 @@ export default class App extends Component { this.refreshGraph = this.refreshGraph.bind(this); this.toggleDock = this.toggleDock.bind(this); this.paintNWorst = this.paintNWorst.bind(this); + this.resetRecomputations = this.resetRecomputations.bind(this); } componentDidMount() { @@ -117,11 +100,17 @@ export default class App extends Component { } refreshGraph() { + this.baseRefreshGraph(false); + } + baseRefreshGraph(resetRecomputations) { this.sg && this.sg.reset(); this.resetSelectorData(); - this.props.actions.getSelectorGraph(); - } + this.props.actions.getSelectorGraph(resetRecomputations); + } + resetRecomputations() { + this.baseRefreshGraph(true); + } paintNWorst(n) { this.resetSelectorData(); this.sg.highlightNMostRecomputed(n); @@ -172,7 +161,7 @@ export default class App extends Component { this.sg = sg} + ref={(sg) => { this.sg = sg; }} {...graph} /> @@ -194,6 +183,8 @@ export default class App extends Component { onRefresh={this.refreshGraph} onHelp={openGitRepo} onPaintWorst={this.paintNWorst} + onResetRecomputations={this.resetRecomputations} + supportsRefreshRecomputations={this.props.supportsRefreshRecomputations} /> { this.renderContent() } diff --git a/extension/app/reducers/index.js b/extension/app/reducers/index.js index 235a7f5..eff8989 100644 --- a/extension/app/reducers/index.js +++ b/extension/app/reducers/index.js @@ -1,6 +1,10 @@ import { combineReducers } from 'redux'; import graph from './graph'; +const identity = (x = null) => x; + export default combineReducers({ - graph + graph, + // we provide this so combineReducers doesnt complain about initial state + version: identity, }); diff --git a/extension/app/selectors/selectors.js b/extension/app/selectors/selectors.js new file mode 100644 index 0000000..9b4fddb --- /dev/null +++ b/extension/app/selectors/selectors.js @@ -0,0 +1,28 @@ +import { greaterThan007 } from '../utils/version'; + +export const supportsRefreshRecomputations$ = state => greaterThan007(state.version); + +export const checkedSelector$ = (state) => { + const { checkedSelectorId, nodes, edges } = state.graph; + const selector = nodes[checkedSelectorId]; + if (!selector) return; + + // this is a bit ugly because it relies on edges being in order. + const dependencies = edges.filter(edge => edge.from === checkedSelectorId); + const dependencyIds = dependencies.map(edge => edge.to); + + if (!selector.inputs) { + return selector; + } + + const { inputs } = selector; + if (dependencyIds.length !== inputs.length) { + console.error(`Uh oh, inputs and edges out of sync on ${checkedSelectorId}`); + } + + const zipped = []; + for (let i = 0; i < dependencyIds.length; i++) { + zipped.push([dependencyIds[i], inputs[i]]); + } + return { ...selector, zipped }; +}; diff --git a/extension/app/utils/apiMiddleware.js b/extension/app/utils/apiMiddleware.js index 1dc8071..3800bb2 100644 --- a/extension/app/utils/apiMiddleware.js +++ b/extension/app/utils/apiMiddleware.js @@ -22,7 +22,8 @@ export default api => store => next => async (action) => { if (action.type === types.GET_SELECTOR_GRAPH) { try { - const graph = await api.selectorGraph(); + const { resetRecomputations } = action.payload; + const graph = await api.selectorGraph(resetRecomputations); store.dispatch(getSelectorGraphSuccess(graph)); } catch (e) { store.dispatch(getSelectorGraphFailed()); diff --git a/extension/app/utils/rpc.js b/extension/app/utils/rpc.js index 0f855d2..63d1dcb 100644 --- a/extension/app/utils/rpc.js +++ b/extension/app/utils/rpc.js @@ -1,11 +1,3 @@ -chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { - const where = sender.tab ? 'a content script' : 'the extension'; - const message = `extension received a message from ${where}`; - console.log(message); - sendResponse({ k: true }); -}); - - function sendMessage(data) { console.log(chrome.windows.getCurrent((x) => console.log(x))); chrome.tabs.query({active: true, currentWindow: true}, function(tabs) { diff --git a/extension/app/utils/version.js b/extension/app/utils/version.js new file mode 100644 index 0000000..43b35e0 --- /dev/null +++ b/extension/app/utils/version.js @@ -0,0 +1,28 @@ + +export function greaterThan(v1, v2) { + if (!v1) { + v1 = '0.0.7'; // we only started doing this in 0.0.8 + } + + const v1Parts = v1.split('.').map(x => parseInt(x, 10)); + const v2Parts = v2.split('.').map(x => parseInt(x, 10)); + + for (let i = 0; i < 3; i += 1) { + const v1Part = v1Parts[i]; + const v2Part = v2Parts[i]; + + if (v1Part > v2Part) { + return true; + } + if (v1Part < v2Part) { + return false; + } + } + // they were the same the whole way. + return false; +} + +/* we're looking for recomputations, reset recomputations */ +export function greaterThan007(v1) { + return greaterThan(v1, '0.0.7'); +} diff --git a/extension/chrome/extension/api.js b/extension/chrome/extension/api.js new file mode 100644 index 0000000..1a98704 --- /dev/null +++ b/extension/chrome/extension/api.js @@ -0,0 +1,54 @@ +function evalPromise(str) { + return new Promise((resolve, reject) => { + chrome.devtools.inspectedWindow.eval(str, (resultStr, err) => { + const result = JSON.parse(resultStr); + if (err && err.isException) { + console.error(err.value); + reject(err.value); + } else { + resolve(result); + } + }); + }); +} + +async function execute(strings, ...keys) { + const lastIndex = strings.length - 1; + const vanilla = strings + .slice(0, lastIndex) + .reduce((p, s, i) => p + s + keys[i], '') + + strings[lastIndex]; + + return evalPromise(`(function() {${vanilla}})()`); +} + + +export async function checkSelector(id) { + return execute` + const __reselect_last_check = window.__RESELECT_TOOLS__.checkSelector('${id}'); + console.log(__reselect_last_check); + return JSON.stringify(__reselect_last_check); + `; +} + +export async function selectorGraph(resetRecomputations) { + let resetStr = ''; + if (resetRecomputations) { + const expr = ` + for (const selector of window.__RESELECT_TOOLS__._allSelectors) { + selector.resetRecomputations && selector.resetRecomputations(); + }; + `; + resetStr = expr; + } + return execute` + ${resetStr} + return JSON.stringify(window.__RESELECT_TOOLS__.selectorGraph()) + `; +} + +export async function getLibVersion() { + return execute` + return JSON.stringify(window.__RESELECT_TOOLS__.version); + `; +} diff --git a/extension/chrome/extension/background.js b/extension/chrome/extension/background.js index 0afca4b..9d62731 100644 --- a/extension/chrome/extension/background.js +++ b/extension/chrome/extension/background.js @@ -1,3 +1,3 @@ chrome.browserAction.onClicked.addListener(() => { - window.open('https://github.com/skortchmark9/reselect-devtools-extension'); + window.open('https://github.com/skortchmark9/reselect-tools'); }); diff --git a/extension/chrome/extension/page-api.js b/extension/chrome/extension/page-api.js deleted file mode 100644 index 6510dc9..0000000 --- a/extension/chrome/extension/page-api.js +++ /dev/null @@ -1,27 +0,0 @@ -function evalPromise(str) { - return new Promise((resolve, reject) => { - chrome.devtools.inspectedWindow.eval(str, (resultStr, err) => { - const result = JSON.parse(resultStr); - if (err && err.isException) { - console.error(err.value); - reject(err.value); - } else { - resolve(result); - } - }); - }); -} - -export function checkSelector(id) { - const str = `(function() { - const __reselect_last_check = window.__RESELECT_TOOLS__.checkSelector('${id}'); - console.log(__reselect_last_check); - return JSON.stringify(__reselect_last_check); - })()`; - return evalPromise(str); -} - -export function selectorGraph() { - const str = 'JSON.stringify(window.__RESELECT_TOOLS__.selectorGraph())'; - return evalPromise(str); -} diff --git a/extension/chrome/extension/reselect-tools-app.js b/extension/chrome/extension/reselect-tools-app.js index 68f148b..0ddaba4 100644 --- a/extension/chrome/extension/reselect-tools-app.js +++ b/extension/chrome/extension/reselect-tools-app.js @@ -3,12 +3,12 @@ import ReactDOM from 'react-dom'; import Root from '../../app/containers/Root'; import './reselect-tools-app.css'; -import * as api from './page-api'; +import * as realApi from './api'; import createStore from '../../app/store/configureStore'; import createApiMiddleware from '../../app/utils/apiMiddleware'; -const checkSelector = (id) => { +const checkSelector = async (id) => { if (id === 'c') { return Promise.resolve({ inputs: [1], output: {hey: 'hey'}, id, name: id }); } @@ -23,25 +23,33 @@ const checkSelector = (id) => { const mockApi = { checkSelector, - selectorGraph: () => { + selectorGraph: async () => { const a = { id: 'a', recomputations: 10, isNamed: true }; const b = { id: 'b', recomputations: 10, isNamed: true }; const c = { id: 'c', recomputations: 10, isNamed: true }; const d = { id: 'd', recomputations: 2, isNamed: true }; const e = { id: 'e', recomputations: 4, isNamed: true }; const f = { id: 'f', recomputations: 6, isNamed: true }; - return Promise.resolve({ nodes: { a, b, c, d, e, f }, edges: [{ from: 'a', to: 'b' }, { from: 'b', to: 'c' }] }); + return { nodes: { a, b, c, d, e, f }, edges: [{ from: 'a', to: 'b' }, { from: 'b', to: 'c' }] }; }, + getLibVersion: async () => '0.0.8', }; +const useMock = false; +const api = useMock ? mockApi : realApi; const apiMiddleware = createApiMiddleware(api); -// const apiMiddleware = createApiMiddleware(window.location.origin === 'http://localhost:8000' ? mockApi : api); +async function go() { + const version = await api.getLibVersion(); + const initialState = { + version, + }; -const initialState = {}; + ReactDOM.render( + , + document.querySelector('#root') + ); +} -ReactDOM.render( - , - document.querySelector('#root') -); +go(); diff --git a/extension/test/app/actions/graph.spec.js b/extension/test/app/actions/graph.spec.js new file mode 100644 index 0000000..e7f9e8b --- /dev/null +++ b/extension/test/app/actions/graph.spec.js @@ -0,0 +1,7 @@ +/* eslint-disable no-unused-vars */ +import { expect } from 'chai'; +import * as types from '../../../app/constants/ActionTypes'; +import * as actions from '../../../app/actions/graph'; + +describe('graph actions', () => { +}); diff --git a/extension/test/app/actions/todos.spec.js b/extension/test/app/actions/todos.spec.js deleted file mode 100644 index b1cfb71..0000000 --- a/extension/test/app/actions/todos.spec.js +++ /dev/null @@ -1,46 +0,0 @@ -import { expect } from 'chai'; -import * as types from '../../../app/constants/ActionTypes'; -import * as actions from '../../../app/actions/todos'; - -describe('todoapp todo actions', () => { - it('addTodo should create ADD_TODO action', () => { - expect(actions.addTodo('Use Redux')).to.eql({ - type: types.ADD_TODO, - text: 'Use Redux' - }); - }); - - it('deleteTodo should create DELETE_TODO action', () => { - expect(actions.deleteTodo(1)).to.eql({ - type: types.DELETE_TODO, - id: 1 - }); - }); - - it('editTodo should create EDIT_TODO action', () => { - expect(actions.editTodo(1, 'Use Redux everywhere')).to.eql({ - type: types.EDIT_TODO, - id: 1, - text: 'Use Redux everywhere' - }); - }); - - it('completeTodo should create COMPLETE_TODO action', () => { - expect(actions.completeTodo(1)).to.eql({ - type: types.COMPLETE_TODO, - id: 1 - }); - }); - - it('completeAll should create COMPLETE_ALL action', () => { - expect(actions.completeAll()).to.eql({ - type: types.COMPLETE_ALL - }); - }); - - it('clearCompleted should create CLEAR_COMPLETED action', () => { - expect(actions.clearCompleted('Use Redux')).to.eql({ - type: types.CLEAR_COMPLETED - }); - }); -}); diff --git a/extension/test/app/components/Header.spec.js b/extension/test/app/components/Header.spec.js deleted file mode 100644 index f61b0b9..0000000 --- a/extension/test/app/components/Header.spec.js +++ /dev/null @@ -1,44 +0,0 @@ -import { expect } from 'chai'; -import sinon from 'sinon'; -import React from 'react'; -import TestUtils from 'react-addons-test-utils'; -import Header from '../../../app/components/Header'; -import TodoTextInput from '../../../app/components/TodoTextInput'; - -function setup() { - const props = { - addTodo: sinon.spy() - }; - - const renderer = TestUtils.createRenderer(); - renderer.render(
); - const output = renderer.getRenderOutput(); - - return { props, output, renderer }; -} - -describe('todoapp Header component', () => { - it('should render correctly', () => { - const { output } = setup(); - - expect(output.type).to.equal('header'); - - const [h1, input] = output.props.children; - - expect(h1.type).to.equal('h1'); - expect(h1.props.children).to.equal('todos'); - - expect(input.type).to.equal(TodoTextInput); - expect(input.props.newTodo).to.equal(true); - expect(input.props.placeholder).to.equal('What needs to be done?'); - }); - - it('should call addTodo if length of text is greater than 0', () => { - const { output, props } = setup(); - const input = output.props.children[1]; - input.props.onSave(''); - expect(props.addTodo.callCount).to.equal(0); - input.props.onSave('Use Redux'); - expect(props.addTodo.callCount).to.equal(1); - }); -}); diff --git a/extension/test/app/reducers/graph.spec.js b/extension/test/app/reducers/graph.spec.js new file mode 100644 index 0000000..58ed237 --- /dev/null +++ b/extension/test/app/reducers/graph.spec.js @@ -0,0 +1,8 @@ +/* eslint-disable no-unused-vars */ +import { expect } from 'chai'; +import * as types from '../../../app/constants/ActionTypes'; +import graph from '../../../app/reducers/graph'; + +describe('graph reducer', () => { + +}); diff --git a/extension/test/app/reducers/todos.spec.js b/extension/test/app/reducers/todos.spec.js deleted file mode 100644 index d03f149..0000000 --- a/extension/test/app/reducers/todos.spec.js +++ /dev/null @@ -1,243 +0,0 @@ -import { expect } from 'chai'; -import * as types from '../../../app/constants/ActionTypes'; -import todos from '../../../app/reducers/todos'; - -describe('todoapp todos reducer', () => { - it('should handle initial state', () => { - expect( - todos(undefined, {}) - ).to.eql([{ - text: 'Use Redux', - completed: false, - id: 0 - }]); - }); - - it('should handle ADD_TODO', () => { - expect( - todos([], { - type: types.ADD_TODO, - text: 'Run the tests' - }) - ).to.eql([{ - text: 'Run the tests', - completed: false, - id: 0 - }]); - - expect( - todos([{ - text: 'Use Redux', - completed: false, - id: 0 - }], { - type: types.ADD_TODO, - text: 'Run the tests' - }) - ).to.eql([{ - text: 'Run the tests', - completed: false, - id: 1 - }, { - text: 'Use Redux', - completed: false, - id: 0 - }]); - - expect( - todos([{ - text: 'Run the tests', - completed: false, - id: 1 - }, { - text: 'Use Redux', - completed: false, - id: 0 - }], { - type: types.ADD_TODO, - text: 'Fix the tests' - }) - ).to.eql([{ - text: 'Fix the tests', - completed: false, - id: 2 - }, { - text: 'Run the tests', - completed: false, - id: 1 - }, { - text: 'Use Redux', - completed: false, - id: 0 - }]); - }); - - it('should handle DELETE_TODO', () => { - expect( - todos([{ - text: 'Run the tests', - completed: false, - id: 1 - }, { - text: 'Use Redux', - completed: false, - id: 0 - }], { - type: types.DELETE_TODO, - id: 1 - }) - ).to.eql([{ - text: 'Use Redux', - completed: false, - id: 0 - }]); - }); - - it('should handle EDIT_TODO', () => { - expect( - todos([{ - text: 'Run the tests', - completed: false, - id: 1 - }, { - text: 'Use Redux', - completed: false, - id: 0 - }], { - type: types.EDIT_TODO, - text: 'Fix the tests', - id: 1 - }) - ).to.eql([{ - text: 'Fix the tests', - completed: false, - id: 1 - }, { - text: 'Use Redux', - completed: false, - id: 0 - }]); - }); - - it('should handle COMPLETE_TODO', () => { - expect( - todos([{ - text: 'Run the tests', - completed: false, - id: 1 - }, { - text: 'Use Redux', - completed: false, - id: 0 - }], { - type: types.COMPLETE_TODO, - id: 1 - }) - ).to.eql([{ - text: 'Run the tests', - completed: true, - id: 1 - }, { - text: 'Use Redux', - completed: false, - id: 0 - }]); - }); - - it('should handle COMPLETE_ALL', () => { - expect( - todos([{ - text: 'Run the tests', - completed: true, - id: 1 - }, { - text: 'Use Redux', - completed: false, - id: 0 - }], { - type: types.COMPLETE_ALL - }) - ).to.eql([{ - text: 'Run the tests', - completed: true, - id: 1 - }, { - text: 'Use Redux', - completed: true, - id: 0 - }]); - - // Unmark if all todos are currently completed - expect( - todos([{ - text: 'Run the tests', - completed: true, - id: 1 - }, { - text: 'Use Redux', - completed: true, - id: 0 - }], { - type: types.COMPLETE_ALL - }) - ).to.eql([{ - text: 'Run the tests', - completed: false, - id: 1 - }, { - text: 'Use Redux', - completed: false, - id: 0 - }]); - }); - - it('should handle CLEAR_COMPLETED', () => { - expect( - todos([{ - text: 'Run the tests', - completed: true, - id: 1 - }, { - text: 'Use Redux', - completed: false, - id: 0 - }], { - type: types.CLEAR_COMPLETED - }) - ).to.eql([{ - text: 'Use Redux', - completed: false, - id: 0 - }]); - }); - - it('should not generate duplicate ids after CLEAR_COMPLETED', () => { - expect( - [{ - type: types.COMPLETE_TODO, - id: 0 - }, { - type: types.CLEAR_COMPLETED - }, { - type: types.ADD_TODO, - text: 'Write more tests' - }].reduce(todos, [{ - id: 0, - completed: false, - text: 'Use Redux' - }, { - id: 1, - completed: false, - text: 'Write tests' - }]) - ).to.eql([{ - text: 'Write more tests', - completed: false, - id: 2 - }, { - text: 'Write tests', - completed: false, - id: 1 - }]); - }); -}); diff --git a/extension/test/app/utils/version.spec.js b/extension/test/app/utils/version.spec.js new file mode 100644 index 0000000..1f0ca14 --- /dev/null +++ b/extension/test/app/utils/version.spec.js @@ -0,0 +1,14 @@ +import { expect } from 'chai'; +import { greaterThan007 } from '../../../app/utils/version'; + + +describe('version utils', () => { + it('should do greater than 0.0.7 correctly', () => { + expect(greaterThan007('0.0.8')).to.be.true; + expect(greaterThan007('0.1.0')).to.be.true; + expect(greaterThan007('1.0.0')).to.be.true; + expect(greaterThan007(undefined)).to.be.false; + expect(greaterThan007('0.0.7')).to.be.false; + expect(greaterThan007('0.0.6')).to.be.false; + }); +}); diff --git a/lib/index.js b/lib/index.js index 584c5cb..d047b07 100644 --- a/lib/index.js +++ b/lib/index.js @@ -93,6 +93,7 @@ function checkSelector(selector) { _selector$selectorNam = _selector.selectorName, selectorName = _selector$selectorNam === undefined ? null : _selector$selectorNam; + var isNamed = typeof selectorName === 'string'; var recomputations = selector.recomputations ? selector.recomputations() : null; @@ -105,14 +106,14 @@ function checkSelector(selector) { extra.inputs = dependencies.map(function (parentSelector) { return parentSelector(state); }); - } catch (e) { - extra.error = 'checkSelector: error getting inputs of selector ' + selectorName + '. The error was:\n' + e; - } - try { - extra.output = selector(state); + try { + extra.output = selector(state); + } catch (e) { + extra.error = 'checkSelector: error getting output of selector ' + selectorName + '. The error was:\n' + e; + } } catch (e) { - extra.error = 'checkSelector: error getting output of selector ' + selectorName + '. The error was:\n' + e; + extra.error = 'checkSelector: error getting inputs of selector ' + selectorName + '. The error was:\n' + e; } Object.assign(ret, extra); @@ -204,6 +205,8 @@ function selectorGraph() { if (typeof window !== 'undefined') { window.__RESELECT_TOOLS__ = { selectorGraph: selectorGraph, - checkSelector: checkSelector + checkSelector: checkSelector, + _allSelectors: _allSelectors, + version: '0.0.8' }; } \ No newline at end of file diff --git a/src/index.js b/src/index.js index ad267bf..1a4454f 100644 --- a/src/index.js +++ b/src/index.js @@ -138,6 +138,8 @@ export function selectorGraph(selectorKey = defaultSelectorKey) { if (typeof window !== 'undefined') { window.__RESELECT_TOOLS__ = { selectorGraph, - checkSelector + checkSelector, + _allSelectors, + version: '0.0.8' } }