Permalink
Browse files

Merge pull request #2171 from knockout/1735-spectate-event

observables notify a "spectate" event whenever their value changes.
2 parents b98dd95 + 4e55d4b commit 87e8a59e84af304d71823acd64aa335a66d3f1be @mbest mbest committed on GitHub Dec 14, 2016
@@ -245,6 +245,31 @@ describe('Rate-limited', function() {
expect(beforeChangeSpy.calls.length).toBe(1); // Only one beforeChange notification
});
+ it('Should notify "spectator" subscribers whenever the value changes', function () {
+ var observable = new ko.observable('A').extend({rateLimit:500}),
+ spectateSpy = jasmine.createSpy('notifySpy'),
+ notifySpy = jasmine.createSpy('notifySpy');
+
+ observable.subscribe(spectateSpy, null, "spectate");
+ observable.subscribe(notifySpy);
+
+ expect(spectateSpy).not.toHaveBeenCalled();
+ expect(notifySpy).not.toHaveBeenCalled();
+
+ observable('B');
+ expect(spectateSpy).toHaveBeenCalledWith('B');
+ observable('C');
+ expect(spectateSpy).toHaveBeenCalledWith('C');
+
+ expect(notifySpy).not.toHaveBeenCalled();
+ jasmine.Clock.tick(500);
+
+ // "spectate" was called for each new value
+ expect(spectateSpy.argsForCall).toEqual([ ['B'], ['C'] ]);
+ // whereas "change" was only called for the final value
+ expect(notifySpy.argsForCall).toEqual([ ['C'] ]);
+ });
+
it('Should suppress change notification when value is changed/reverted', function() {
var observable = ko.observable('original').extend({rateLimit:500});
var notifySpy = jasmine.createSpy('notifySpy');
@@ -211,6 +211,19 @@ describe('Dependent Observable', function() {
expect(notifiedValue).toEqual(3);
});
+ it('Should notify "spectator" subscribers about changes', function () {
+ var observable = new ko.observable();
+ var computed = ko.computed(function () { return observable(); });
+ var notifiedValues = [];
+ computed.subscribe(function (value) {
+ notifiedValues.push(value);
+ }, null, "spectate");
+
+ observable('A');
+ observable('B');
+ expect(notifiedValues).toEqual([ 'A', 'B' ]);
+ });
+
it('Should notify "beforeChange" subscribers before changes', function () {
var notifiedValue;
var observable = new ko.observable(1);
@@ -100,10 +100,19 @@ describe('Observable', function() {
instance('A');
instance('B');
+ expect(notifiedValues).toEqual([ 'A', 'B' ]);
+ });
- expect(notifiedValues.length).toEqual(2);
- expect(notifiedValues[0]).toEqual('A');
- expect(notifiedValues[1]).toEqual('B');
+ it('Should notify "spectator" subscribers about each new value', function () {
+ var instance = new ko.observable();
+ var notifiedValues = [];
+ instance.subscribe(function (value) {
+ notifiedValues.push(value);
+ }, null, "spectate");
+
+ instance('A');
+ instance('B');
+ expect(notifiedValues).toEqual([ 'A', 'B' ]);
});
it('Should be able to tell it that its value has mutated, at which point it notifies subscribers', function () {
@@ -288,11 +297,13 @@ describe('Observable', function() {
};
instance(456);
- expect(interceptedNotifications.length).toEqual(2);
- expect(interceptedNotifications[0].eventName).toEqual("beforeChange");
- expect(interceptedNotifications[1].eventName).toEqual("None");
- expect(interceptedNotifications[0].value).toEqual(123);
- expect(interceptedNotifications[1].value).toEqual(456);
+ // This represents the current set of events that are generated for an observable. This set might
+ // expand in the future.
+ expect(interceptedNotifications).toEqual([
+ { eventName: 'beforeChange', value: 123 },
+ { eventName: 'spectate', value: 456 },
+ { eventName: 'None', value: 456 }
+ ]);
});
it('Should inherit any properties defined on ko.subscribable.fn or ko.observable.fn', function() {
@@ -81,6 +81,36 @@ describe('Pure Computed', function() {
expect(timesEvaluated).toEqual(3);
});
+ it('Should notify "spectator" subscribers whenever the value changes', function () {
+ var observable = new ko.observable('A');
+ var computed = ko.pureComputed(observable);
+ var computed2 = ko.pureComputed(computed);
+ var notifiedValues = [];
+ computed.subscribe(function (value) {
+ notifiedValues.push(value);
+ expect(computed()).toBe(value);
+ expect(computed2()).toBe(value);
+ }, null, "spectate");
+
+ expect(notifiedValues).toEqual([]);
+
+ // Reading the computed for the first time causes a notification
+ expect(computed()).toEqual('A');
+ expect(computed2()).toEqual('A');
+ expect(notifiedValues).toEqual(['A']);
+
+ // Reading it a second time doesn't
+ expect(computed()).toEqual('A');
+ expect(computed2()).toEqual('A');
+ expect(notifiedValues).toEqual(['A']);
+
+ // Changing the dependency doesn't, but reading the computed again does
+ observable('B');
+ expect(notifiedValues).toEqual(['A']);
+ expect(computed()).toEqual('B');
+ expect(notifiedValues).toEqual(['A', 'B']);
+ });
+
it('Should not subscribe to dependencies while sleeping', function() {
var data = ko.observable('A'),
computed = ko.pureComputed(data);
@@ -283,14 +283,16 @@ var computedFn = {
if (computedObservable.isDifferent(state.latestValue, newValue)) {
if (!state.isSleeping) {
computedObservable["notifySubscribers"](state.latestValue, "beforeChange");
+ } else {
+ computedObservable.updateVersion();
}
state.latestValue = newValue;
if (DEBUG) computedObservable._latestValue = newValue;
- if (state.isSleeping) {
- computedObservable.updateVersion();
- } else if (notifyChange) {
+ computedObservable["notifySubscribers"](state.latestValue, "spectate");
+
+ if (!state.isSleeping && notifyChange) {
computedObservable["notifySubscribers"](state.latestValue);
}
@@ -43,7 +43,10 @@ ko.observable = function (initialValue) {
var observableFn = {
'equalityComparer': valuesArePrimitiveAndEqual,
peek: function() { return this[observableLatestValue]; },
- valueHasMutated: function () { this['notifySubscribers'](this[observableLatestValue]); },
+ valueHasMutated: function () {
+ this['notifySubscribers'](this[observableLatestValue], 'spectate');
+ this['notifySubscribers'](this[observableLatestValue]);
+ },
valueWillMutate: function () { this['notifySubscribers'](this[observableLatestValue], 'beforeChange'); }
};

0 comments on commit 87e8a59

Please sign in to comment.