Skip to content

Commit

Permalink
__wip: wip
Browse files Browse the repository at this point in the history
  • Loading branch information
tjmehta committed Jul 28, 2016
1 parent 5c08fd6 commit 06421d6
Show file tree
Hide file tree
Showing 23 changed files with 1,710 additions and 1 deletion.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@
"setupEnvScriptFile": "node_modules/fbjs-scripts/jest/environment.js",
"persistModuleRegistryBetweenSpecs": true,
"modulePathIgnorePatterns": [
"[.](.*).js",
"<rootDir>/lib/",
"<rootDir>/src/(.*).native.js",
"<rootDir>/node_modules/(?!(fbjs/lib/|react/lib/|fbjs-scripts/jest))"
Expand Down
2 changes: 2 additions & 0 deletions src/RelayPublic.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const RelayContainer = require('RelayContainer');
const RelayEnvironment = require('RelayEnvironment');
const RelayInternals = require('RelayInternals');
const RelayMutation = require('RelayMutation');
const RelaySubscription = require('RelaySubscription');
const RelayPropTypes = require('RelayPropTypes');
const RelayQL = require('RelayQL');
const RelayReadyStateRenderer = require('RelayReadyStateRenderer');
Expand All @@ -39,6 +40,7 @@ if (typeof global.__REACT_DEVTOOLS_GLOBAL_HOOK__ !== 'undefined') {
const RelayPublic = {
Environment: RelayEnvironment,
Mutation: RelayMutation,
Subscription: RelaySubscription,
PropTypes: RelayPropTypes,
QL: RelayQL,
ReadyStateRenderer: RelayReadyStateRenderer,
Expand Down
1 change: 1 addition & 0 deletions src/container/RelayContainer.js
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ function createContainerComponent(
hasPartialData: this.hasPartialData.bind(this),
pendingVariables: null,
route,
subscribe: this.context.relay.subscribe,
setVariables: this.setVariables.bind(this),
variables: {},
},
Expand Down
17 changes: 17 additions & 0 deletions src/container/__tests__/RelayContainer-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ const ReactTestUtils = require('ReactTestUtils');
const Relay = require('Relay');
const RelayEnvironment = require('RelayEnvironment');
const RelayMutation = require('RelayMutation');
const RelaySubscription = require('RelaySubscription');
const RelayQuery = require('RelayQuery');
const RelayRoute = require('RelayRoute');
const RelayTestUtils = require('RelayTestUtils');
Expand Down Expand Up @@ -563,6 +564,22 @@ describe('RelayContainer', function() {
});
});

describe('props.relay.subscribe', () => {
it('forwards to the underlying RelayEnvironment', () => {
const mockSubscription = new RelaySubscription();
environment.subscribe = jest.fn();
render.mockImplementation(function() {
this.props.relay.subscribe(mockSubscription);
});
RelayTestRenderer.render(
() => <MockContainer />,
environment,
mockRoute
);
expect(environment.subscribe.mock.calls[0][0]).toBe(mockSubscription);
});
});

it('creates resolvers for each query prop with a fragment pointer', () => {
RelayTestRenderer.render(
() => <MockContainer foo={mockFooPointer} />,
Expand Down
12 changes: 12 additions & 0 deletions src/network-layer/default/RelayDefaultNetworkLayer.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,12 @@ const RelayMutationRequest = require('RelayMutationRequest');

const fetch = require('fetch');
const fetchWithRetries = require('fetchWithRetries');
const invariant = require('invariant');

import type {InitWithRetries} from 'fetchWithRetries';
import type RelayQueryRequest from 'RelayQueryRequest';
import type RelaySubscriptionRequest from 'RelaySubscriptionRequest';
import type {Subscription} from 'RelayTypes';

type GraphQLError = {
message: string,
Expand All @@ -40,6 +43,7 @@ class RelayDefaultNetworkLayer {
// Facilitate reuse when creating custom network layers.
(this: any).sendMutation = this.sendMutation.bind(this);
(this: any).sendQueries = this.sendQueries.bind(this);
(this: any).sendSubscription = this.sendSubscription.bind(this);
(this: any).supports = this.supports.bind(this);
}

Expand Down Expand Up @@ -80,6 +84,14 @@ class RelayDefaultNetworkLayer {
)));
}

sendSubscription(request: RelaySubscriptionRequest): Subscription {
invariant(
false,
'RelayDefaultNetworkLayer: `sendSubscription` is not implemented in the ' +
'default network layer. A custom network layer must be injected.'
);
}

supports(...options: Array<string>): boolean {
// Does not support the only defined option, "defer".
return false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const RelayConnectionInterface = require('RelayConnectionInterface');
const RelayDefaultNetworkLayer = require('RelayDefaultNetworkLayer');
const RelayMetaRoute = require('RelayMetaRoute');
const RelayMutationRequest = require('RelayMutationRequest');
const RelaySubscriptionRequest = require('RelaySubscriptionRequest');
const RelayQuery = require('RelayQuery');
const RelayQueryRequest = require('RelayQueryRequest');
const RelayTestUtils = require('RelayTestUtils');
Expand Down Expand Up @@ -412,4 +413,49 @@ describe('RelayDefaultNetworkLayer', () => {
);
});
});

describe('sendSubscription', () => {
let mockSubscriptionRequest;

beforeEach(() => {
const initialVariables = {feedbackId: 'aFeedbackId'};
function makeMockSubscription() {
class MockSubscriptionClass extends Relay.Subscription {
static initialVariables = initialVariables;
getConfigs() {
return [];
}
getSubscription() {
return Relay.QL`
subscription {
feedbackLikeSubscribe (input: $input) {
feedback {
likeSentence
}
}
}
`;
}
getVariables() {
return initialVariables;
}
}
return MockSubscriptionClass;
}
const MockSubscription = makeMockSubscription();
const mockSubscription = new MockSubscription();
mockSubscriptionRequest = new RelaySubscriptionRequest(mockSubscription, {
onNext: jest.fn(),
onError: jest.fn(),
onCompleted: jest.fn(),
});
});

it('should throw "not implemented" invariant error', () => {
expect(() => networkLayer.sendSubscription(mockSubscriptionRequest)).toFailInvariant(
'RelayDefaultNetworkLayer: `sendSubscription` is not implemented in the ' +
'default network layer. A custom network layer must be injected.'
);
});
});
});
24 changes: 24 additions & 0 deletions src/network/RelayNetworkLayer.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@
'use strict';

import type RelayMutationRequest from 'RelayMutationRequest';
import type RelaySubscriptionRequest from 'RelaySubscriptionRequest';
const RelayProfiler = require('RelayProfiler');
import type RelayQuery from 'RelayQuery';
const RelayQueryRequest = require('RelayQueryRequest');
import type {ChangeSubscription, NetworkLayer} from 'RelayTypes';
import type {Subscription} from 'RelayTypes';

const invariant = require('invariant');
const resolveImmediate = require('resolveImmediate');
Expand Down Expand Up @@ -114,6 +116,28 @@ class RelayNetworkLayer {
}
}

sendSubscription(subscriptionRequest: RelaySubscriptionRequest): Subscription {
const implementation = this._getImplementation();

invariant(
typeof implementation.sendSubscription === 'function',
'RelayNetworkLayer: does not support subscriptions. Expected `sendSubscription` to be ' +
'a function.'
);

const result = implementation.sendSubscription(subscriptionRequest);

invariant(
result && typeof result.dispose === 'function',
'RelayNetworkLayer: `sendSubscription` should return an object with a ' +
'`dispose` property that is a no-argument function. This function is ' +
'called when the client unsubscribes from the subscription ' +
'and any network layer resources can be cleaned up.'
);

return result;
}

supports(...options: Array<string>): boolean {
const implementation = this._getImplementation();
return implementation.supports(...options);
Expand Down
116 changes: 116 additions & 0 deletions src/network/RelaySubscriptionRequest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
/**
* Copyright (c) 2013-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule RelaySubscriptionRequest
* @flow
*/

'use strict';

import type {PrintedQuery} from 'RelayInternalTypes';
import type RelayQuery from 'RelayQuery';
import type {
RelaySubscriptionObservableCallbacks,
SubscriptionResult,
Variables,
} from 'RelayTypes';

const printRelayQuery = require('printRelayQuery');

/**
* @internal
*
* Instances of these are made available via `RelayNetworkLayer.sendSubscription`.
*/
class RelaySubscriptionRequest {
_subscription: RelayQuery.Subscription;
_callbacks: RelaySubscriptionObservableCallbacks;
_printedQuery: ?PrintedQuery;

constructor(
subscription: RelayQuery.Subscription,
callbacks: RelaySubscriptionObservableCallbacks,
) {
this._subscription = subscription;
this._callbacks = callbacks;
}

/**
* @public
*
* Gets a string name used to refer to this request for printing debug output.
*/
getDebugName(): string {
return this._subscription.getName();
}

/**
* @public
*
* Gets the variables used by the subscription. These variables should be
* serialized and sent in the GraphQL request.
*/
getVariables(): Variables {
return this._getPrintedQuery().variables;
}

/**
* @public
*
* Gets a string representation of the GraphQL subscription.
*/
getQueryString(): string {
return this._getPrintedQuery().text;
}

/**
* @public
* @unstable
*/
getSubscription(): RelayQuery.Subscription {
return this._subscription;
}

/**
* @public
* @unstable
*/
onCompleted(): void {
return this._callbacks && this._callbacks.onCompleted();
}

/**
* @public
* @unstable
*/
onNext(payload: SubscriptionResult): void {
return this._callbacks && this._callbacks.onNext(payload);
}

/**
* @public
* @unstable
*/
onError(error: Error): void {
return this._callbacks && this._callbacks.onError(error);
}

/**
* @private
*
* Returns the memoized printed query.
*/
_getPrintedQuery(): PrintedQuery {
if (!this._printedQuery) {
this._printedQuery = printRelayQuery(this._subscription);
}
return this._printedQuery;
}
}

module.exports = RelaySubscriptionRequest;
55 changes: 55 additions & 0 deletions src/network/__tests__/RelayNetworkLayer-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ describe('RelayNetworkLayer', () => {
injectedNetworkLayer = {
sendMutation: jest.fn(),
sendQueries: jest.fn(),
sendSubscription: jest.fn(),
supports: jest.fn(() => true),
};
networkLayer = new RelayNetworkLayer();
Expand Down Expand Up @@ -204,6 +205,60 @@ describe('RelayNetworkLayer', () => {
});
});

describe('sendSubscription', () => {
let subscriptionRequest;
beforeEach(() => {
subscriptionRequest = {};
});

it('should call network layer does sendSubscription', () => {
injectedNetworkLayer.sendSubscription.mockReturnValue({ dispose: jest.fn() });
networkLayer.sendSubscription(subscriptionRequest);

expect(injectedNetworkLayer.sendSubscription).toBeCalled();
expect(injectedNetworkLayer.sendSubscription.mock.calls.length).toBe(1);
expect(injectedNetworkLayer.sendSubscription).toBeCalledWith(subscriptionRequest);
});

it('throws error when network layer sendSubscription does not returns nothing', () => {
injectedNetworkLayer.sendSubscription.mockReturnValue(undefined);

expect(() => networkLayer.sendSubscription(subscriptionRequest)).toFailInvariant(
'RelayNetworkLayer: `sendSubscription` should return an object with a ' +
'`dispose` property that is a no-argument function. This function is ' +
'called when the client unsubscribes from the subscription ' +
'and any network layer resources can be cleaned up.'
);
});

it('throws error when network layer sendSubscription does not return a disposable', () => {
injectedNetworkLayer.sendSubscription.mockReturnValue({});

expect(() => networkLayer.sendSubscription(subscriptionRequest)).toFailInvariant(
'RelayNetworkLayer: `sendSubscription` should return an object with a ' +
'`dispose` property that is a no-argument function. This function is ' +
'called when the client unsubscribes from the subscription ' +
'and any network layer resources can be cleaned up.'
);
});

describe('injected network layer does not have sendSubscription', () => {
beforeEach(() => {
networkLayer.injectImplementation({
...injectedNetworkLayer,
sendSubscription: undefined,
});
});

it('throws when network layer does not have sendSubscription', () => {
expect(() => networkLayer.sendSubscription(subscriptionRequest)).toFailInvariant(
'RelayNetworkLayer: does not support subscriptions. Expected `sendSubscription` to be ' +
'a function.'
);
});
});
});

describe('addNetworkSubscriber', () => {
let mutationCallback;
let queryCallback;
Expand Down
Loading

0 comments on commit 06421d6

Please sign in to comment.