Skip to content

Commit

Permalink
Adding method to retrieve ordered list of dependencies for computed o…
Browse files Browse the repository at this point in the history
…bservables
  • Loading branch information
WHenderson committed Dec 6, 2015
1 parent 3d46cca commit f4ff3af
Show file tree
Hide file tree
Showing 5 changed files with 50 additions and 0 deletions.
1 change: 1 addition & 0 deletions spec/asyncBehaviors.js
Original file line number Diff line number Diff line change
Expand Up @@ -557,6 +557,7 @@ describe('Rate-limited', function() {
expect(computed()).toEqual(1);
// The computed should not be dependent on the second observable
expect(computed.getDependenciesCount()).toEqual(1);
expect(computed.getDependencies()).toEqual([observableSwitch]);

// Updating the second observable shouldn't re-evaluate computed
observableValue(2);
Expand Down
22 changes: 22 additions & 0 deletions spec/dependentObservableBehaviors.js
Original file line number Diff line number Diff line change
Expand Up @@ -257,12 +257,14 @@ describe('Dependent Observable', function() {
);
expect(timesEvaluated).toEqual(1);
expect(computed.getDependenciesCount()).toEqual(1);
expect(computed.getDependencies()).toEqual([ underlyingObservable ]);
expect(computed.isActive()).toEqual(true);

timeToDispose = true;
underlyingObservable(101);
expect(timesEvaluated).toEqual(1);
expect(computed.getDependenciesCount()).toEqual(0);
expect(computed.getDependencies()).toEqual([]);
expect(computed.isActive()).toEqual(false);
});

Expand Down Expand Up @@ -356,12 +358,14 @@ describe('Dependent Observable', function() {

// initially computed has no dependencies since it has not been evaluated
expect(computed.getDependenciesCount()).toEqual(0);
expect(computed.getDependencies()).toEqual([]);

// Now subscribe to computed
computed.subscribe(result);

// The dependency should now be tracked
expect(computed.getDependenciesCount()).toEqual(1);
expect(computed.getDependencies()).toEqual([ data ]);

// But the subscription should not have sent down the initial value
expect(result()).toEqual(undefined);
Expand Down Expand Up @@ -411,18 +415,21 @@ describe('Dependent Observable', function() {

// initially there is only one dependency
expect(computed.getDependenciesCount()).toEqual(1);
expect(computed.getDependencies()).toEqual([observableDependent]);

// create a change subscription that also accesses an observable
computed.subscribe(function() { observableIndependent() });
// now trigger evaluation of the computed by updating its dependency
observableDependent(1);
// there should still only be one dependency
expect(computed.getDependenciesCount()).toEqual(1);
expect(computed.getDependencies()).toEqual([observableDependent]);

// also test with a beforeChange subscription
computed.subscribe(function() { observableIndependent() }, null, 'beforeChange');
observableDependent(2);
expect(computed.getDependenciesCount()).toEqual(1);
expect(computed.getDependencies()).toEqual([observableDependent]);
});

it('Should not subscribe to observables accessed through change notifications of a modified observable', function() {
Expand All @@ -434,18 +441,21 @@ describe('Dependent Observable', function() {

// initially there is only one dependency
expect(computed.getDependenciesCount()).toEqual(1);
expect(computed.getDependencies()).toEqual([observableDependent]);

// create a change subscription that also accesses an observable
observableModified.subscribe(function() { observableIndependent() });
// now trigger evaluation of the computed by updating its dependency
observableDependent(1);
// there should still only be one dependency
expect(computed.getDependenciesCount()).toEqual(1);
expect(computed.getDependencies()).toEqual([observableDependent]);

// also test with a beforeChange subscription
observableModified.subscribe(function() { observableIndependent() }, null, 'beforeChange');
observableDependent(2);
expect(computed.getDependenciesCount()).toEqual(1);
expect(computed.getDependencies()).toEqual([observableDependent]);
});

it('Should be able to re-evaluate a computed that previously threw an exception', function() {
Expand All @@ -470,6 +480,7 @@ describe('Dependent Observable', function() {
expect(computed()).toEqual(1);
// The computed should not be dependent on the second observable
expect(computed.getDependenciesCount()).toEqual(1);
expect(computed.getDependencies()).toEqual([observableSwitch]);

// Updating the second observable shouldn't re-evaluate computed
observableValue(2);
Expand Down Expand Up @@ -571,6 +582,7 @@ describe('Dependent Observable', function() {
expect(evaluateCount).toEqual(1);
expect(computed()).toEqual(1);
expect(computed.getDependenciesCount()).toEqual(0);
expect(computed.getDependencies()).toEqual([]);
});

it('Should not evaluate (or add dependencies) after it has been disposed if created with "deferEvaluation"', function () {
Expand All @@ -591,6 +603,7 @@ describe('Dependent Observable', function() {
expect(evaluateCount).toEqual(0);
expect(computed()).toEqual(undefined);
expect(computed.getDependenciesCount()).toEqual(0);
expect(computed.getDependencies()).toEqual([]);
});

it('Should not add dependencies if disposed during evaluation', function () {
Expand All @@ -611,13 +624,15 @@ describe('Dependent Observable', function() {
expect(evaluateCount).toEqual(1);
expect(computed()).toEqual(1);
expect(computed.getDependenciesCount()).toEqual(2);
expect(computed.getDependencies()).toEqual([observableToTriggerDisposal, observableGivingValue]);
expect(observableGivingValue.getSubscriptionsCount()).toEqual(1);

// Now cause a disposal during evaluation
observableToTriggerDisposal(true);
expect(evaluateCount).toEqual(2);
expect(computed()).toEqual(2);
expect(computed.getDependenciesCount()).toEqual(0);
expect(computed.getDependencies()).toEqual([]);
expect(observableGivingValue.getSubscriptionsCount()).toEqual(0);
});

Expand Down Expand Up @@ -668,26 +683,33 @@ describe('Dependent Observable', function() {
++evaluationCount;
// no dependencies at first
expect(ko.computedContext.getDependenciesCount()).toEqual(0);
expect(ko.computedContext.getDependencies()).toEqual([]);
// add a single dependency
observable1();
expect(ko.computedContext.getDependenciesCount()).toEqual(1);
expect(ko.computedContext.getDependencies()).toEqual([observable1]);
// add a second one
observable2();
expect(ko.computedContext.getDependenciesCount()).toEqual(2);
expect(ko.computedContext.getDependencies()).toEqual([observable1, observable2]);
// accessing observable again doesn't affect count
observable1();
expect(ko.computedContext.getDependenciesCount()).toEqual(2);
expect(ko.computedContext.getDependencies()).toEqual([observable1, observable2]);
});

expect(evaluationCount).toEqual(1); // single evaluation
expect(computed.getDependenciesCount()).toEqual(2); // matches value from context
expect(computed.getDependencies()).toEqual([observable1, observable2]);

observable1(2);
expect(evaluationCount).toEqual(2); // second evaluation
expect(computed.getDependenciesCount()).toEqual(2); // matches value from context
expect(computed.getDependencies()).toEqual([observable1, observable2]);

// value outside of computed is undefined
expect(ko.computedContext.getDependenciesCount()).toBeUndefined();
expect(ko.computedContext.getDependencies()).toBeUndefined();
});
});
});
13 changes: 13 additions & 0 deletions spec/pureComputedBehaviors.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ describe('Pure Computed', function() {

// getDependenciesCount returns the correct number
expect(computed.getDependenciesCount()).toEqual(1);
expect(computed.getDependencies()).toEqual([data]);
});

it('Should not evaluate after it has been disposed', function () {
Expand Down Expand Up @@ -121,6 +122,7 @@ describe('Pure Computed', function() {
computed.subscribe(function (value) { notifiedValues.push(value); });
expect(data.getSubscriptionsCount()).toEqual(1);
expect(computed.getDependenciesCount()).toEqual(1);
expect(computed.getDependencies()).toEqual([data]);

// The subscription should not have sent down the initial value
expect(notifiedValues).toEqual([]);
Expand All @@ -137,12 +139,14 @@ describe('Pure Computed', function() {

expect(data.getSubscriptionsCount()).toEqual(1);
expect(computed.getDependenciesCount()).toEqual(1);
expect(computed.getDependencies()).toEqual([data]);

// Dispose the subscription to the computed
subscription.dispose();
// It goes to sleep, disposing its subscription to the observable
expect(data.getSubscriptionsCount()).toEqual(0);
expect(computed.getDependenciesCount()).toEqual(1); // dependency count of computed doesn't change
expect(computed.getDependencies()).toEqual([data]);
});

it('Should fire "awake" and "asleep" events when changing state', function() {
Expand Down Expand Up @@ -183,6 +187,7 @@ describe('Pure Computed', function() {
expect(computed()).toEqual('A');
expect(timesEvaluated).toEqual(1);
expect(computed.getDependenciesCount()).toEqual(1);
expect(computed.getDependencies()).toEqual([data]);

// Subscribing to the computed adds a subscription to the dependency without re-evaluating
subscription = computed.subscribe(subscribeFunc);
Expand Down Expand Up @@ -314,12 +319,14 @@ describe('Pure Computed', function() {
expect(computed()).toEqual('A');
expect(timesEvaluated).toEqual(1);
expect(computed.getDependenciesCount()).toEqual(2);
expect(computed.getDependencies()).toEqual([observableToTriggerDisposal, observableGivingValue]);

// Now cause a disposal during evaluation
observableToTriggerDisposal(true);
expect(computed()).toEqual('A');
expect(timesEvaluated).toEqual(2);
expect(computed.getDependenciesCount()).toEqual(0);
expect(computed.getDependencies()).toEqual([]);
});

describe('Should maintain order of subscriptions', function () {
Expand Down Expand Up @@ -400,25 +407,31 @@ describe('Pure Computed', function() {
computed = ko.pureComputed(function() {
// no dependencies at first
expect(ko.computedContext.getDependenciesCount()).toEqual(0);
expect(ko.computedContext.getDependencies()).toEqual([]);
// add a single dependency
observable1();
expect(ko.computedContext.getDependenciesCount()).toEqual(1);
expect(ko.computedContext.getDependencies()).toEqual([observable1]);
// add a second one
observable2();
expect(ko.computedContext.getDependenciesCount()).toEqual(2);
expect(ko.computedContext.getDependencies()).toEqual([observable1, observable2]);
// accessing observable again doesn't affect count
observable1();
expect(ko.computedContext.getDependenciesCount()).toEqual(2);
expect(ko.computedContext.getDependencies()).toEqual([observable1, observable2]);

return ++evaluationCount;
});

expect(computed()).toEqual(1); // single evaluation
expect(computed.getDependenciesCount()).toEqual(2); // matches value from context
expect(computed.getDependencies()).toEqual([observable1, observable2]);

observable1(2);
expect(computed()).toEqual(2); // second evaluation
expect(computed.getDependenciesCount()).toEqual(2); // matches value from context
expect(computed.getDependencies()).toEqual([observable1, observable2]);
});
});
});
5 changes: 5 additions & 0 deletions src/subscribables/dependencyDetection.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,11 @@ ko.computedContext = ko.dependencyDetection = (function () {
return currentFrame.computed.getDependenciesCount();
},

getDependencies: function () {
if (currentFrame)
return currentFrame.computed.getDependencies();
},

isInitial: function() {
if (currentFrame)
return currentFrame.isInitial;
Expand Down
9 changes: 9 additions & 0 deletions src/subscribables/dependentObservable.js
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,15 @@ var computedFn = {
getDependenciesCount: function () {
return this[computedState].dependenciesCount;
},
getDependencies: function () {
var dependencyTracking = this[computedState].dependencyTracking, dependentObservables = [];

ko.utils.objectForEach(dependencyTracking, function (id, dependency) {
dependentObservables[dependency._order] = dependency._target;
});

return dependentObservables;
},
addDependencyTracking: function (id, target, trackingObj) {
if (this[computedState].pure && target === this) {
throw Error("A 'pure' computed must not be called recursively");
Expand Down

0 comments on commit f4ff3af

Please sign in to comment.