diff --git a/lib/mixins/property-effects.js b/lib/mixins/property-effects.js index e5af97ce51..a07cbfc197 100644 --- a/lib/mixins/property-effects.js +++ b/lib/mixins/property-effects.js @@ -539,7 +539,7 @@ function getComputedOrder(inst) { if (!ordered) { ordered = new Map(); const effects = inst[TYPES.COMPUTE]; - const {counts, ready} = dependencyCounts(inst); + let {counts, ready, total} = dependencyCounts(inst); let curr; while ((curr = ready.shift())) { ordered.set(curr, ordered.size); @@ -548,12 +548,16 @@ function getComputedOrder(inst) { computedByCurr.forEach(fx => { // Note `methodInfo` is where the computed property name is stored const computedProp = fx.info.methodInfo; + --total; if (--counts[computedProp] === 0) { ready.push(computedProp); } }); } } + if (total !== 0) { + console.warn(`Computed graph for ${inst.localName} incomplete; circular?`); + } inst.constructor.__orderedComputedDeps = ordered; } return ordered; @@ -563,7 +567,8 @@ function getComputedOrder(inst) { * Generates a map of property-to-dependency count (`counts`, where "dependency * count" is the number of dependencies a given property has assuming it is a * computed property, otherwise 0). It also returns a pre-populated list of - * `ready` properties that have no dependencies. + * `ready` properties that have no dependencies and a `total` count, which is + * used for error-checking the graph. * * Used by `orderedComputed: true` computed property algorithm. * @@ -574,20 +579,35 @@ function getComputedOrder(inst) { * dependencies. */ function dependencyCounts(inst) { - const props = inst.constructor._properties; const infoForComputed = inst[COMPUTE_INFO]; const counts = {}; const ready = []; - for (let p in props) { + let total = 0; + for (let p in infoForComputed) { const info = infoForComputed[p]; - if (info) { - // Be sure to add the method name itself in case of "dynamic functions" - counts[p] = info.args.length + (info.dynamicFn ? 1 : 0); - } else { - ready.push(p); + // Be sure to add the method name itself in case of "dynamic functions" + total += counts[p] = + info.args.filter(a => !a.literal).length + (info.dynamicFn ? 1 : 0); + // Add any dependencies that are not themselves computed to the ready queue + info.args.forEach(arg => { + const dep = arg.rootProperty; + // Putting a count of 0 in for ready deps is used for de-duping + if (dep && !infoForComputed[dep] && !(dep in counts)) { + counts[dep] = 0; + ready.push(dep); + } + }); + // Add the method name to the ready queue if "dynamic function" + if (info.dynamicFn) { + const dep = info.methodName; + // Putting a count of 0 in for ready deps is used for de-duping + if (dep && !infoForComputed[dep] && !(dep in counts)) { + counts[dep] = 0; + ready.push(dep); + } } } - return {counts, ready}; + return {counts, ready, total}; } /** diff --git a/test/unit/property-effects-elements.js b/test/unit/property-effects-elements.js index ff216fa66c..43b75410be 100644 --- a/test/unit/property-effects-elements.js +++ b/test/unit/property-effects-elements.js @@ -1015,7 +1015,7 @@ customElements.define('x-computed-ordering', class extends PolymerElement { static get properties() { return { a: {type: Number, value: 1000}, - b: {type: Number, value: 100}, + // b: {type: Number, value: 100}, // Intentionally undeclared; init in ctor c: {type: Number, value: 10}, d: {type: Number, value: 1}, abbcd: {computed: 'computeABBCD(a, b, bcd)', observer: 'abbcdChanged'}, @@ -1034,6 +1034,7 @@ customElements.define('x-computed-ordering', class extends PolymerElement { } constructor() { super(); + this.b = 100; sinon.spy(this, 'computeABBCD'); sinon.spy(this, 'computeBCD'); sinon.spy(this, 'computeBC'); diff --git a/test/unit/property-effects.html b/test/unit/property-effects.html index 590bbd58a0..f27be46485 100644 --- a/test/unit/property-effects.html +++ b/test/unit/property-effects.html @@ -2091,7 +2091,7 @@ if (orderedComputed) { - suite.only('computed property ordering', function() { + suite('computed property ordering', function() { var el; setup(function() {