fix(WebWorker): Add zone support to MessageBus

Closes #4053
This commit is contained in:
Jason Teplitz
2015-09-08 10:52:06 -07:00
parent 3b9c08676a
commit f3da37c92f
35 changed files with 628 additions and 365 deletions

View File

@ -186,6 +186,20 @@ function commonTests() {
}, 80);
}));
it('should call standalone onEventDone', inject([AsyncTestCompleter], (async) => {
_zone.overrideOnTurnStart(null);
_zone.overrideOnEventDone(() => { _log.add('onEventDone'); });
_zone.overrideOnTurnDone(null);
macroTask(() => { _zone.run(_log.fn('run')); });
macroTask(() => {
expect(_log.result()).toEqual('run; onEventDone');
async.done();
}, 80);
}));
it('should not allow onEventDone to cause further digests',
inject([AsyncTestCompleter], (async) => {
_zone.overrideOnTurnStart(null);

View File

@ -19,5 +19,5 @@ void expectSinkSendsEncodedJson(SpyObject socket, MessageBusSink sink,
void expectMessageEquality(String message, Map expectedData, String channel) {
expect(JSON.decode(message))
.toEqual({'channel': channel, 'message': expectedData});
.toEqual([{'channel': channel, 'message': expectedData}]);
}

View File

@ -38,6 +38,7 @@ main() {
inject([AsyncTestCompleter], (async) {
const NUM_CLIENTS = 5;
var sink = new MultiClientServerMessageBusSink();
sink.initChannel(CHANNEL, false);
int numMessagesSent = 0;
// initialize all the sockets
var sockets = new List<WebSocketWrapper>(NUM_CLIENTS);
@ -78,7 +79,7 @@ main() {
for (var i = 0; i < numMessages; i++) {
var message = {'value': random.nextInt(MAX)};
messageHistory
.add(JSON.encode({'channel': CHANNEL, 'message': message}));
.add(JSON.encode([{'channel': CHANNEL, 'message': message}]));
}
// copy the message history to ensure the test fails if the wrapper modifies the list
return new List.from(messageHistory);
@ -136,7 +137,7 @@ main() {
});
void sendMessage(StreamController controller, dynamic message) {
controller.add(JSON.encode(message));
controller.add(JSON.encode([message]));
}
void testForwardingMessages(bool primary, bool events, Function done) {
@ -146,10 +147,11 @@ main() {
new WebSocketWrapper(messageHistory, resultMarkers, result.socket);
socket.setPrimary(primary);
var source = new MultiClientServerMessageBusSource(null);
var source = new MultiClientServerMessageBusSource();
source.addConnection(socket);
var channel = events ? EVENT_CHANNEL : CHANNEL;
source.initChannel(channel, false);
source.from(channel).listen((message) {
expect(message).toEqual(MESSAGE);
done();
@ -187,7 +189,9 @@ main() {
socket.setPrimary(true);
var source =
new MultiClientServerMessageBusSource(() => async.done());
new MultiClientServerMessageBusSource();
source.onResult.listen((result) => async.done());
source.initChannel(CHANNEL, false);
source.addConnection(socket);
var message = {

View File

@ -28,6 +28,8 @@ main() {
inject([AsyncTestCompleter], (async) {
var socket = new SpyWebSocket();
var sink = new SingleClientServerMessageBusSink();
sink.initChannel(CHANNEL, false);
sink.setConnection(socket);
expectSinkSendsEncodedJson(socket, sink, "add", async);
}));
@ -36,6 +38,7 @@ main() {
"should buffer messages before connect",
inject([AsyncTestCompleter], (async) {
var sink = new SingleClientServerMessageBusSink();
sink.initChannel(CHANNEL, false);
sink.to(CHANNEL).add(MESSAGE);
var socket = new SpyWebSocket();
@ -51,6 +54,8 @@ main() {
inject([AsyncTestCompleter], (async) {
var SECOND_MESSAGE = const {'test': 12, 'second': 'hi'};
var sink = new SingleClientServerMessageBusSink();
sink.initChannel(CHANNEL, false);
sink.to(CHANNEL).add(MESSAGE);
var socket = new SpyWebSocket();
@ -78,20 +83,19 @@ main() {
it(
"should decode JSON messages and emit them",
inject([AsyncTestCompleter], (async) {
var socket = new SpyWebSocket();
StreamController<String> controller =
new StreamController.broadcast();
socket.spy("asBroadcastStream").andCallFake(() => controller.stream);
var source = new SingleClientServerMessageBusSource();
source.setConnectionFromWebSocket(socket);
source.initChannel(CHANNEL, false);
source.attachTo(controller.stream);
source.from(CHANNEL).listen((message) {
expect(message).toEqual(MESSAGE);
async.done();
});
controller
.add(JSON.encode({'channel': CHANNEL, 'message': MESSAGE}));
.add(JSON.encode([{'channel': CHANNEL, 'message': MESSAGE}]));
}));
});
}

View File

@ -28,6 +28,7 @@ main() {
inject([AsyncTestCompleter], (async) {
var socket = new SpyWebSocket();
var sink = new WebSocketMessageBusSink(socket);
sink.initChannel(CHANNEL, false);
expectSinkSendsEncodedJson(socket, sink, "send", async);
}));
});
@ -41,6 +42,7 @@ main() {
new StreamController.broadcast();
socket.spy("get:onMessage").andCallFake(() => controller.stream);
var source = new WebSocketMessageBusSource(socket);
source.initChannel(CHANNEL, false);
source.from(CHANNEL).listen((message) {
expect(message).toEqual(MESSAGE);
@ -49,7 +51,7 @@ main() {
var event = new SpyMessageEvent();
event.spy("get:data").andCallFake(
() => JSON.encode({'channel': CHANNEL, 'message': MESSAGE}));
() => JSON.encode([{'channel': CHANNEL, 'message': MESSAGE}]));
controller.add(event);
}));
});

View File

@ -10,9 +10,11 @@ import {
SpyObject,
proxy
} from 'angular2/test_lib';
import {ObservableWrapper} from 'angular2/src/core/facade/async';
import {ObservableWrapper, TimerWrapper} from 'angular2/src/core/facade/async';
import {MessageBus} from 'angular2/src/web_workers/shared/message_bus';
import {createConnectedMessageBus} from './message_bus_util';
import {MockNgZone} from 'angular2/src/mock/ng_zone_mock';
import {NgZone} from 'angular2/src/core/zone/ng_zone';
export function main() {
/**
@ -27,6 +29,7 @@ export function main() {
inject([AsyncTestCompleter], (async) => {
const CHANNEL = "CHANNEL 1";
const MESSAGE = "Test message";
bus.initChannel(CHANNEL, false);
var fromEmitter = bus.from(CHANNEL);
ObservableWrapper.subscribe(fromEmitter, (message: any) => {
@ -41,6 +44,7 @@ export function main() {
const CHANNEL = "CHANNEL 1";
const MESSAGE = "TESTING";
const NUM_LISTENERS = 2;
bus.initChannel(CHANNEL, false);
var callCount = 0;
var emitHandler = (message: any) => {
@ -66,6 +70,8 @@ export function main() {
const MESSAGE_ONE = "This is a message on CHANNEL 1";
const MESSAGE_TWO = "This is a message on CHANNEL 2";
var callCount = 0;
bus.initChannel(CHANNEL_ONE, false);
bus.initChannel(CHANNEL_TWO, false);
var firstFromEmitter = bus.from(CHANNEL_ONE);
ObservableWrapper.subscribe(firstFromEmitter, (message) => {
@ -91,4 +97,55 @@ export function main() {
ObservableWrapper.callNext(secondToEmitter, MESSAGE_TWO);
}));
});
describe("PostMessageBusSink", () => {
var bus: MessageBus;
const CHANNEL = "Test Channel";
function setup(runInZone: boolean, zone: NgZone) {
bus.attachToZone(zone);
bus.initChannel(CHANNEL, runInZone);
}
/**
* Flushes pending messages and then runs the given function.
*/
function flushMessages(fn: () => void) { TimerWrapper.setTimeout(fn, 10); }
beforeEach(() => { bus = createConnectedMessageBus(); });
it("should buffer messages and wait for the zone to exit before sending",
inject([AsyncTestCompleter, NgZone], (async, zone: MockNgZone) => {
setup(true, zone);
var wasCalled = false;
ObservableWrapper.subscribe(bus.from(CHANNEL), (message) => { wasCalled = true; });
ObservableWrapper.callNext(bus.to(CHANNEL), "hi");
flushMessages(() => {
expect(wasCalled).toBeFalsy();
zone.simulateZoneExit();
flushMessages(() => {
expect(wasCalled).toBeTruthy();
async.done();
});
});
}));
it("should send messages immediatly when run outside the zone",
inject([AsyncTestCompleter, NgZone], (async, zone: MockNgZone) => {
setup(false, zone);
var wasCalled = false;
ObservableWrapper.subscribe(bus.from(CHANNEL), (message) => { wasCalled = true; });
ObservableWrapper.callNext(bus.to(CHANNEL), "hi");
flushMessages(() => {
expect(wasCalled).toBeTruthy();
async.done();
});
}));
});
}

View File

@ -39,7 +39,11 @@ export function main() {
describe("UIMessageBroker", () => {
var messageBuses;
beforeEach(() => { messageBuses = createPairedMessageBuses(); });
beforeEach(() => {
messageBuses = createPairedMessageBuses();
messageBuses.ui.initChannel(CHANNEL);
messageBuses.worker.initChannel(CHANNEL);
});
it("should call registered method with correct arguments",
inject([Serializer], (serializer) => {
var broker = new ServiceMessageBroker(messageBuses.ui, serializer, CHANNEL);

View File

@ -5,6 +5,8 @@ import {
MessageBus
} from 'angular2/src/web_workers/shared/message_bus';
import {MockEventEmitter} from './mock_event_emitter';
import {BaseException} from 'angular2/src/core/facade/lang';
import {NgZone} from 'angular2/src/core/zone/ng_zone';
/**
* Returns two MessageBus instances that are attached to each other.
@ -30,30 +32,56 @@ export class PairedMessageBuses {
export class MockMessageBusSource implements MessageBusSource {
constructor(private _channels: StringMap<string, MockEventEmitter>) {}
from(channel: string): MockEventEmitter {
initChannel(channel: string, runInZone = true) {
if (!StringMapWrapper.contains(this._channels, channel)) {
this._channels[channel] = new MockEventEmitter();
}
}
from(channel: string): MockEventEmitter {
if (!StringMapWrapper.contains(this._channels, channel)) {
throw new BaseException(`${channel} is not set up. Did you forget to call initChannel?`);
}
return this._channels[channel];
}
attachToZone(zone: NgZone) {}
}
export class MockMessageBusSink implements MessageBusSink {
constructor(private _channels: StringMap<string, MockEventEmitter>) {}
initChannel(channel: string, runInZone = true) {
if (!StringMapWrapper.contains(this._channels, channel)) {
this._channels[channel] = new MockEventEmitter();
}
}
to(channel: string): MockEventEmitter {
if (!StringMapWrapper.contains(this._channels, channel)) {
this._channels[channel] = new MockEventEmitter();
}
return this._channels[channel];
}
attachToZone(zone: NgZone) {}
}
/**
* Mock implementation of the {@link MessageBus} for tests.
* Runs syncronously, and does not support running within the zone.
*/
export class MockMessageBus extends MessageBus {
constructor(public sink: MockMessageBusSink, public source: MockMessageBusSource) { super(); }
initChannel(channel: string, runInZone = true) {
this.sink.initChannel(channel, runInZone);
this.source.initChannel(channel, runInZone);
}
to(channel: string): MockEventEmitter { return this.sink.to(channel); }
from(channel: string): MockEventEmitter { return this.source.from(channel); }
attachToZone(zone: NgZone) {}
}

View File

@ -11,7 +11,6 @@ import {
proxy
} from 'angular2/test_lib';
import {Serializer} from 'angular2/src/web_workers/shared/serializer';
import {NgZone} from 'angular2/src/core/zone/ng_zone';
import {ON_WEB_WORKER} from 'angular2/src/web_workers/shared/api';
import {bind} from 'angular2/core';
import {RenderProtoViewRefStore} from 'angular2/src/web_workers/shared/render_proto_view_ref_store';
@ -34,11 +33,10 @@ export function main() {
RenderViewWithFragmentsStore
]);
it("should dispatch events",
inject([Serializer, NgZone, AsyncTestCompleter], (serializer, zone, async) => {
it("should dispatch events", inject([Serializer, AsyncTestCompleter], (serializer, async) => {
var messageBuses = createPairedMessageBuses();
var webWorkerEventDispatcher =
new WebWorkerEventDispatcher(messageBuses.worker, serializer, zone);
new WebWorkerEventDispatcher(messageBuses.worker, serializer);
var elementIndex = 15;
var eventName = 'click';

View File

@ -45,5 +45,5 @@ export function main() {
class MockMessageBrokerFactory extends ClientMessageBrokerFactory {
constructor(private _messageBroker: ClientMessageBroker) { super(null, null); }
createMessageBroker(channel: string) { return this._messageBroker; }
createMessageBroker(channel: string, runInZone = true) { return this._messageBroker; }
}