From a37c0a1e98fc4d471dccb9e112224fe47036636f Mon Sep 17 00:00:00 2001 From: Sam Kortchmar Date: Tue, 6 Aug 2019 18:46:23 -0400 Subject: [PATCH 1/9] First stab --- extension/app/components/StateTree.js | 2 +- extension/chrome/extension/{page-api.js => api.js} | 9 +++++++++ extension/chrome/extension/background.js | 2 +- extension/chrome/extension/reselect-tools-app.js | 2 +- 4 files changed, 12 insertions(+), 3 deletions(-) rename extension/chrome/extension/{page-api.js => api.js} (72%) 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/chrome/extension/page-api.js b/extension/chrome/extension/api.js similarity index 72% rename from extension/chrome/extension/page-api.js rename to extension/chrome/extension/api.js index 6510dc9..7ded352 100644 --- a/extension/chrome/extension/page-api.js +++ b/extension/chrome/extension/api.js @@ -25,3 +25,12 @@ export function selectorGraph() { const str = 'JSON.stringify(window.__RESELECT_TOOLS__.selectorGraph())'; return evalPromise(str); } + +export function resetRecomputations() { + const str = `(function() { + for (const selector of new Set(Object.values(window.__RESELECT_TOOLS__.selectorGraph().nodes))) { + selector.resetRecomputations && selector.resetRecomputations(); + } + })();` + return evalPromise(str); +} 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/reselect-tools-app.js b/extension/chrome/extension/reselect-tools-app.js index 68f148b..174f11d 100644 --- a/extension/chrome/extension/reselect-tools-app.js +++ b/extension/chrome/extension/reselect-tools-app.js @@ -3,7 +3,7 @@ import ReactDOM from 'react-dom'; import Root from '../../app/containers/Root'; import './reselect-tools-app.css'; -import * as api from './page-api'; +import * as api from './api'; import createStore from '../../app/store/configureStore'; import createApiMiddleware from '../../app/utils/apiMiddleware'; From 6ecc1345921db1d66e2b37c14363ba6f45f7d420 Mon Sep 17 00:00:00 2001 From: Sam Kortchmar Date: Tue, 6 Aug 2019 21:36:36 -0400 Subject: [PATCH 2/9] reworked example a bit to better illustrate recomputations --- examples/demo-app.js | 76 ++++++++++++++++++++++++++++++++++---------- examples/demo.html | 4 ++- 2 files changed, 62 insertions(+), 18 deletions(-) diff --git a/examples/demo-app.js b/examples/demo-app.js index 69f8137..566bc52 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..6aeceae 100644 --- a/examples/demo.html +++ b/examples/demo.html @@ -17,7 +17,9 @@ - + + + From 3010db7e457e46a7fda376e82c0af080f3bfa6a1 Mon Sep 17 00:00:00 2001 From: Sam Kortchmar Date: Tue, 6 Aug 2019 22:57:58 -0400 Subject: [PATCH 3/9] got it all working then i realized i would need to rev the lib --- examples/demo.html | 4 +-- extension/app/actions/graph.js | 4 +-- extension/app/components/Header.js | 20 +++++++++++---- extension/app/containers/App.js | 14 +++++++--- extension/app/utils/apiMiddleware.js | 3 ++- extension/app/utils/rpc.js | 8 ------ extension/chrome/extension/api.js | 38 ++++++++++++++++++++-------- 7 files changed, 60 insertions(+), 31 deletions(-) diff --git a/examples/demo.html b/examples/demo.html index 6aeceae..8092347 100644 --- a/examples/demo.html +++ b/examples/demo.html @@ -18,8 +18,8 @@ - - + + 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..8a68985 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 { @@ -68,7 +73,7 @@ class NumberButton extends Component { } -export default function Header({ onRefresh, onHelp, onPaintWorst }) { +export default function Header({ onRefresh, onResetRecomputations, onHelp, onPaintWorst }) { return (
+ Icon={Clear} + onClick={onResetRecomputations} + >Reset Recomputations Select Most Recomputed - +
); } @@ -97,4 +106,5 @@ Header.propTypes = { onRefresh: PropTypes.func, onHelp: PropTypes.func, onPaintWorst: PropTypes.func, + onResetRecomputations: PropTypes.func, }; diff --git a/extension/app/containers/App.js b/extension/app/containers/App.js index b66561b..9e00cf2 100644 --- a/extension/app/containers/App.js +++ b/extension/app/containers/App.js @@ -48,7 +48,7 @@ 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'); } @@ -110,6 +110,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 +118,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); @@ -194,6 +201,7 @@ export default class App extends Component { onRefresh={this.refreshGraph} onHelp={openGitRepo} onPaintWorst={this.paintNWorst} + onResetRecomputations={this.resetRecomputations} /> { this.renderContent() } 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/chrome/extension/api.js b/extension/chrome/extension/api.js index 7ded352..8314ca3 100644 --- a/extension/chrome/extension/api.js +++ b/extension/chrome/extension/api.js @@ -12,25 +12,43 @@ function evalPromise(str) { }); } +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 function checkSelector(id) { - const str = `(function() { + return execute` 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); +export function selectorGraph(resetRecomputations) { + let resetStr = ''; + if (resetRecomputations) { + const expr = ` + for (const selector of new Set(Object.values(window.__RESELECT_TOOLS__.selectorGraph().nodes))) { + selector.resetRecomputations && selector.resetRecomputations(); + }; + `; + resetStr = expr; + } + return execute` + ${resetStr} + return JSON.stringify(window.__RESELECT_TOOLS__.selectorGraph()) + `; } export function resetRecomputations() { const str = `(function() { - for (const selector of new Set(Object.values(window.__RESELECT_TOOLS__.selectorGraph().nodes))) { - selector.resetRecomputations && selector.resetRecomputations(); - } - })();` + })();`; return evalPromise(str); } From 01a9c0295b869c2f37099541f4e0c378f8c0fb1d Mon Sep 17 00:00:00 2001 From: Sam Kortchmar Date: Sun, 11 Aug 2019 20:40:48 -0400 Subject: [PATCH 4/9] rehabilitated extension tests --- extension/.eslintrc | 1 + extension/test/app/actions/graph.spec.js | 7 + extension/test/app/actions/todos.spec.js | 46 ---- extension/test/app/components/Header.spec.js | 44 ---- extension/test/app/reducers/graph.spec.js | 8 + extension/test/app/reducers/todos.spec.js | 243 ------------------- 6 files changed, 16 insertions(+), 333 deletions(-) create mode 100644 extension/test/app/actions/graph.spec.js delete mode 100644 extension/test/app/actions/todos.spec.js delete mode 100644 extension/test/app/components/Header.spec.js create mode 100644 extension/test/app/reducers/graph.spec.js delete mode 100644 extension/test/app/reducers/todos.spec.js 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/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 - }]); - }); -}); From 898f79124124f96a857b3077c45768d728dd0bca Mon Sep 17 00:00:00 2001 From: Sam Kortchmar Date: Sun, 11 Aug 2019 20:52:06 -0400 Subject: [PATCH 5/9] wrote utils for parsing version --- extension/app/utils/version.js | 28 ++++++++++++++++++++++++ extension/test/app/utils/version.spec.js | 14 ++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 extension/app/utils/version.js create mode 100644 extension/test/app/utils/version.spec.js diff --git a/extension/app/utils/version.js b/extension/app/utils/version.js new file mode 100644 index 0000000..3b6092f --- /dev/null +++ b/extension/app/utils/version.js @@ -0,0 +1,28 @@ + +export function greaterThan(v1, v2) { + if (v1 === undefined) { + 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/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; + }); +}); From 6795be8117afb7e0bec9dea2ac8aff9ef21ced48 Mon Sep 17 00:00:00 2001 From: Sam Kortchmar Date: Sun, 11 Aug 2019 21:04:29 -0400 Subject: [PATCH 6/9] asyncified --- extension/chrome/extension/api.js | 15 ++++++++---- .../chrome/extension/reselect-tools-app.js | 24 ++++++++++++------- src/index.js | 4 +++- 3 files changed, 30 insertions(+), 13 deletions(-) diff --git a/extension/chrome/extension/api.js b/extension/chrome/extension/api.js index 8314ca3..f291ed1 100644 --- a/extension/chrome/extension/api.js +++ b/extension/chrome/extension/api.js @@ -12,7 +12,7 @@ function evalPromise(str) { }); } -function execute(strings, ...keys) { +async function execute(strings, ...keys) { const lastIndex = strings.length - 1; const vanilla = strings .slice(0, lastIndex) @@ -23,7 +23,7 @@ function execute(strings, ...keys) { } -export function checkSelector(id) { +export async function checkSelector(id) { return execute` const __reselect_last_check = window.__RESELECT_TOOLS__.checkSelector('${id}'); console.log(__reselect_last_check); @@ -31,7 +31,7 @@ export function checkSelector(id) { `; } -export function selectorGraph(resetRecomputations) { +export async function selectorGraph(resetRecomputations) { let resetStr = ''; if (resetRecomputations) { const expr = ` @@ -47,7 +47,14 @@ export function selectorGraph(resetRecomputations) { `; } -export function resetRecomputations() { +export async function getLibVersion() { + return execute` + return window.__RESELECT_TOOLS__.version; + `; +} + + +export async function resetRecomputations() { const str = `(function() { })();`; return evalPromise(str); diff --git a/extension/chrome/extension/reselect-tools-app.js b/extension/chrome/extension/reselect-tools-app.js index 174f11d..3a43148 100644 --- a/extension/chrome/extension/reselect-tools-app.js +++ b/extension/chrome/extension/reselect-tools-app.js @@ -8,7 +8,7 @@ import * as api 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,15 +23,16 @@ 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.7', }; @@ -39,9 +40,16 @@ const apiMiddleware = createApiMiddleware(api); // const apiMiddleware = createApiMiddleware(window.location.origin === 'http://localhost:8000' ? mockApi : api); -const initialState = {}; +async function go() { + const version = await api.getLibVersion(); + const initialState = { + version, + }; -ReactDOM.render( - , - document.querySelector('#root') -); + ReactDOM.render( + , + document.querySelector('#root') + ); +} + +go(); diff --git a/src/index.js b/src/index.js index ad267bf..4405b48 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', } } From 993b386e3227aa8265cb5e9fd73c30eb74c64c72 Mon Sep 17 00:00:00 2001 From: Sam Kortchmar Date: Mon, 12 Aug 2019 00:28:26 -0400 Subject: [PATCH 7/9] conditionally show button --- extension/app/components/Header.js | 25 ++++++++----- extension/app/containers/App.js | 35 +++++-------------- extension/app/reducers/index.js | 6 +++- extension/app/selectors/selectors.js | 28 +++++++++++++++ extension/app/utils/version.js | 2 +- extension/chrome/extension/api.js | 2 +- .../chrome/extension/reselect-tools-app.js | 8 ++--- 7 files changed, 64 insertions(+), 42 deletions(-) create mode 100644 extension/app/selectors/selectors.js diff --git a/extension/app/components/Header.js b/extension/app/components/Header.js index 8a68985..b6f5a89 100644 --- a/extension/app/components/Header.js +++ b/extension/app/components/Header.js @@ -30,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); @@ -42,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; @@ -73,7 +71,13 @@ class NumberButton extends Component { } -export default function Header({ onRefresh, onResetRecomputations, onHelp, onPaintWorst }) { +export default function Header({ + onRefresh, + onResetRecomputations, + onHelp, + onPaintWorst, + supportsRefreshRecomputations, +}) { return (
- + { supportsRefreshRecomputations ? + : null + } { - 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) { @@ -179,7 +161,7 @@ export default class App extends Component { this.sg = sg} + ref={(sg) => { this.sg = sg; }} {...graph} /> @@ -202,6 +184,7 @@ export default class App extends Component { 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/version.js b/extension/app/utils/version.js index 3b6092f..43b35e0 100644 --- a/extension/app/utils/version.js +++ b/extension/app/utils/version.js @@ -1,6 +1,6 @@ export function greaterThan(v1, v2) { - if (v1 === undefined) { + if (!v1) { v1 = '0.0.7'; // we only started doing this in 0.0.8 } diff --git a/extension/chrome/extension/api.js b/extension/chrome/extension/api.js index f291ed1..7239443 100644 --- a/extension/chrome/extension/api.js +++ b/extension/chrome/extension/api.js @@ -49,7 +49,7 @@ export async function selectorGraph(resetRecomputations) { export async function getLibVersion() { return execute` - return window.__RESELECT_TOOLS__.version; + return window.__RESELECT_TOOLS__.version || null; `; } diff --git a/extension/chrome/extension/reselect-tools-app.js b/extension/chrome/extension/reselect-tools-app.js index 3a43148..0ddaba4 100644 --- a/extension/chrome/extension/reselect-tools-app.js +++ b/extension/chrome/extension/reselect-tools-app.js @@ -3,7 +3,7 @@ import ReactDOM from 'react-dom'; import Root from '../../app/containers/Root'; import './reselect-tools-app.css'; -import * as api from './api'; +import * as realApi from './api'; import createStore from '../../app/store/configureStore'; import createApiMiddleware from '../../app/utils/apiMiddleware'; @@ -32,13 +32,13 @@ const mockApi = { const f = { id: 'f', recomputations: 6, isNamed: true }; return { nodes: { a, b, c, d, e, f }, edges: [{ from: 'a', to: 'b' }, { from: 'b', to: 'c' }] }; }, - getLibVersion: async () => '0.0.7', + 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(); From 037413b5b6b92c8ee42f29bd4a13e0eaf98a0800 Mon Sep 17 00:00:00 2001 From: Sam Kortchmar Date: Mon, 12 Aug 2019 00:54:39 -0400 Subject: [PATCH 8/9] ok done --- examples/demo-app.js | 2 +- examples/demo.html | 19 +++++++++++++++---- extension/chrome/extension/api.js | 11 ++--------- lib/index.js | 17 ++++++++++------- src/index.js | 2 +- 5 files changed, 29 insertions(+), 22 deletions(-) diff --git a/examples/demo-app.js b/examples/demo-app.js index 566bc52..ff23de6 100644 --- a/examples/demo-app.js +++ b/examples/demo-app.js @@ -63,7 +63,7 @@ update(); function update() { const currentUserDiv = document.getElementById('current-user'); selectorGraph(); - currentUserDiv.innerHTML = `Current User: ${currentUser$(STORE).name}`; + currentUserDiv.innerHTML = `Current User: ${currentUser$(STORE).name}.`; } document.addEventListener('DOMContentLoaded', () => { diff --git a/examples/demo.html b/examples/demo.html index 8092347..934079c 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,10 +24,13 @@ - - - - + +
+ Note that downstream selectors only recompute when you increment the current user's age.
+ + + +
diff --git a/extension/chrome/extension/api.js b/extension/chrome/extension/api.js index 7239443..1a98704 100644 --- a/extension/chrome/extension/api.js +++ b/extension/chrome/extension/api.js @@ -35,7 +35,7 @@ export async function selectorGraph(resetRecomputations) { let resetStr = ''; if (resetRecomputations) { const expr = ` - for (const selector of new Set(Object.values(window.__RESELECT_TOOLS__.selectorGraph().nodes))) { + for (const selector of window.__RESELECT_TOOLS__._allSelectors) { selector.resetRecomputations && selector.resetRecomputations(); }; `; @@ -49,13 +49,6 @@ export async function selectorGraph(resetRecomputations) { export async function getLibVersion() { return execute` - return window.__RESELECT_TOOLS__.version || null; + return JSON.stringify(window.__RESELECT_TOOLS__.version); `; } - - -export async function resetRecomputations() { - const str = `(function() { - })();`; - return evalPromise(str); -} 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 4405b48..1a4454f 100644 --- a/src/index.js +++ b/src/index.js @@ -140,6 +140,6 @@ if (typeof window !== 'undefined') { selectorGraph, checkSelector, _allSelectors, - version: '0.0.8', + version: '0.0.8' } } From 4e19d3dea70b643cec55dbce950a694f5a56a741 Mon Sep 17 00:00:00 2001 From: Sam Kortchmar Date: Mon, 12 Aug 2019 01:00:26 -0400 Subject: [PATCH 9/9] italics --- examples/demo.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/demo.html b/examples/demo.html index 934079c..6a7e88f 100644 --- a/examples/demo.html +++ b/examples/demo.html @@ -26,7 +26,7 @@
- Note that downstream selectors only recompute when you increment the current user's age.
+ Note that downstream selectors only recompute when you increment the current user's age.