From d4792240677357d1c96690a6dd43d932d7d9b9fe Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 27 Nov 2017 07:21:06 -0500 Subject: [PATCH] prevent cyclical store computations, and computation duplication --- store.js | 13 ++++++++++++- test/store/index.js | 26 +++++++++++++++++++++++++- 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/store.js b/store.js index a1d6dc4d2e..fefc13cd9a 100644 --- a/store.js +++ b/store.js @@ -48,19 +48,30 @@ assign(Store.prototype, { _sortComputedProperties: function() { var computed = this._computed; var sorted = this._sortedComputedProperties = []; + var cycles; var visited = blankObject(); function visit(key) { + if (cycles[key]) { + throw new Error(`Cyclical dependency detected — a computed property cannot indirectly depend on itself`); + } + if (visited[key]) return; + visited[key] = true; + var c = computed[key]; if (c) { + cycles[key] = true; c.deps.forEach(visit); sorted.push(c); } } - for (var key in this._computed) visit(key); + for (var key in this._computed) { + cycles = blankObject(); + visit(key); + } }, compute: function(key, deps, fn) { diff --git a/test/store/index.js b/test/store/index.js index 25099a9066..fabaa64c4b 100644 --- a/test/store/index.js +++ b/test/store/index.js @@ -1,5 +1,5 @@ import assert from 'assert'; -import { Store, combineStores } from '../../store.js'; +import { Store } from '../../store.js'; describe('store', () => { describe('get', () => { @@ -142,5 +142,29 @@ describe('store', () => { store.set({ bar: 'whatever' }); }, /'bar' is a read-only property/); }); + + it('allows multiple dependents to depend on the same computed property', () => { + const store = new Store({ + a: 1 + }); + + store.compute('b', ['a'], a => a * 2); + store.compute('c', ['b'], b => b * 3); + store.compute('d', ['b'], b => b * 4); + + assert.deepEqual(store.get(), { a: 1, b: 2, c: 6, d: 8 }); + + // bit cheeky, testing a private property, but whatever + assert.equal(store._sortedComputedProperties.length, 3); + }); + + it('prevents cyclical dependencies', () => { + const store = new Store(); + + assert.throws(() => { + store.compute('a', ['b'], b => b + 1); + store.compute('b', ['a'], a => a + 1); + }, /Cyclical dependency detected — a computed property cannot indirectly depend on itself/); + }); }); });