Skip to content

Commit 2211ac6

Browse files
Markioniumfacebook-github-bot
authored andcommitted
Context not properly provided through data injector and subscriptions (#4846)
Summary: This PR fixes two issues we've encountered. 1. ResolverContext isn't passed through the `resolverDataInjector`. This surfaces specifically when using nested resolvers. 2. When updates propagate through the RelayStoreSubscriptions the context value is lost due the RelayStoreSubscriptions instance not receiving the context correctly. I noticed two of the 3 paths in `resolverDataInjector` do not seem to be covered by tests. Pull Request resolved: #4846 Reviewed By: tyao1 Differential Revision: D66390780 Pulled By: captbaritone fbshipit-source-id: 0599b1807170b898f598d90969a1801a26958e37
1 parent 652ab3b commit 2211ac6

14 files changed

+1203
-7
lines changed

packages/react-relay/__tests__/LiveResolvers-test.js

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1895,3 +1895,88 @@ test('ResolverContext can contain observable values', async () => {
18951895
counter_context: 1,
18961896
});
18971897
});
1898+
1899+
test('ResolverContext as passed through nested resolver counters', async () => {
1900+
const source = RelayRecordSource.create({
1901+
'client:root': {
1902+
__id: 'client:root',
1903+
__typename: '__Root',
1904+
me: {__ref: '1'},
1905+
},
1906+
'1': {
1907+
__id: '1',
1908+
__typename: 'User',
1909+
id: '1',
1910+
},
1911+
});
1912+
const FooQuery = graphql`
1913+
query LiveResolversTestCounterContextBaseQuery {
1914+
base_counter_context {
1915+
count_plus_one
1916+
}
1917+
}
1918+
`;
1919+
1920+
let next: (v: number) => void = () => {
1921+
throw new Error('next() not initialized');
1922+
};
1923+
1924+
const operation = createOperationDescriptor(FooQuery, {});
1925+
const store = new RelayModernStore(source, {
1926+
gcReleaseBufferSize: 0,
1927+
resolverContext: {
1928+
counter: Observable.create<number>(observer => {
1929+
next = (value: number) => observer.next(value);
1930+
}),
1931+
},
1932+
});
1933+
1934+
const environment = new RelayModernEnvironment({
1935+
network: RelayNetwork.create(jest.fn()),
1936+
store,
1937+
});
1938+
1939+
let observedCounter = null;
1940+
1941+
const snapshot = environment.lookup(operation.fragment);
1942+
// $FlowFixMe[unclear-type] - lookup() doesn't have the nice types of reading a fragment through the actual APIs:
1943+
observedCounter = (snapshot.data: any).base_counter_context.count_plus_one;
1944+
1945+
const environmentUpdateHandler = jest.fn(() => {
1946+
const s = environment.lookup(operation.fragment);
1947+
// $FlowFixMe[unclear-type] - lookup() doesn't have the nice types of reading a fragment through the actual APIs:
1948+
observedCounter = (s.data: any).base_counter_context.count_plus_one;
1949+
});
1950+
const disposable = environment.subscribe(
1951+
snapshot,
1952+
// $FlowFixMe[invalid-tuple-arity] Error found while enabling LTI on this file
1953+
environmentUpdateHandler,
1954+
);
1955+
1956+
// SETUP COMPLETE
1957+
1958+
// Read the initial value
1959+
expect(observedCounter).toBe(0);
1960+
expect(environmentUpdateHandler).not.toHaveBeenCalled();
1961+
1962+
// Increment and assert we get notified of the new value
1963+
next(43);
1964+
expect(environmentUpdateHandler).toHaveBeenCalledTimes(1);
1965+
expect(observedCounter).toBe(44);
1966+
1967+
// Unsubscribe then increment and assert don't get notified.
1968+
disposable.dispose();
1969+
next(1);
1970+
expect(environmentUpdateHandler).toHaveBeenCalledTimes(1);
1971+
expect(observedCounter).toBe(44);
1972+
1973+
// Explicitly read and assert we see the incremented value
1974+
// missed before due to unsubscribing.
1975+
const nextSnapshot = environment.lookup(operation.fragment);
1976+
1977+
expect(nextSnapshot.data).toEqual({
1978+
base_counter_context: {
1979+
count_plus_one: 2,
1980+
},
1981+
});
1982+
});
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @flow strict-local
8+
* @format
9+
* @oncall relay
10+
*/
11+
12+
'use strict';
13+
14+
import type {RelayResolverModelWithContextTestFragment$key} from './__generated__/RelayResolverModelWithContextTestFragment.graphql';
15+
import type {TestResolverContextType} from 'relay-runtime/mutations/__tests__/TestResolverContextType';
16+
17+
const React = require('react');
18+
const {
19+
RelayEnvironmentProvider,
20+
useClientQuery,
21+
useFragment,
22+
} = require('react-relay');
23+
const TestRenderer = require('react-test-renderer');
24+
const {Observable} = require('relay-runtime');
25+
const RelayNetwork = require('relay-runtime/network/RelayNetwork');
26+
const {graphql} = require('relay-runtime/query/GraphQLTag');
27+
const {
28+
addTodo,
29+
resetStore,
30+
} = require('relay-runtime/store/__tests__/resolvers/ExampleTodoStore');
31+
const RelayModernEnvironment = require('relay-runtime/store/RelayModernEnvironment');
32+
const RelayModernStore = require('relay-runtime/store/RelayModernStore.js');
33+
const RelayRecordSource = require('relay-runtime/store/RelayRecordSource');
34+
const {
35+
disallowConsoleErrors,
36+
disallowWarnings,
37+
injectPromisePolyfill__DEPRECATED,
38+
} = require('relay-test-utils-internal');
39+
40+
injectPromisePolyfill__DEPRECATED();
41+
disallowWarnings();
42+
disallowConsoleErrors();
43+
44+
beforeEach(() => {
45+
resetStore(() => {});
46+
});
47+
48+
function EnvironmentWrapper({
49+
children,
50+
environment,
51+
}: {
52+
children: React.Node,
53+
environment: RelayModernEnvironment,
54+
}) {
55+
return (
56+
<RelayEnvironmentProvider environment={environment}>
57+
<React.Suspense fallback="Loading...">{children}</React.Suspense>
58+
</RelayEnvironmentProvider>
59+
);
60+
}
61+
62+
const RelayResolverModelWithContextTestFragment = graphql`
63+
fragment RelayResolverModelWithContextTestFragment on TodoModel {
64+
id
65+
description
66+
another_value_from_context
67+
}
68+
`;
69+
70+
const RelayResolverModelWithContextTestQuery = graphql`
71+
query RelayResolverModelWithContextTestQuery($id: ID!) {
72+
todo_model(todoID: $id) {
73+
...RelayResolverModelWithContextTestFragment
74+
}
75+
}
76+
`;
77+
78+
describe('RelayResolverModelWithContext', () => {
79+
function TodoComponent(props: {
80+
fragmentKey: ?RelayResolverModelWithContextTestFragment$key,
81+
}) {
82+
const data = useFragment(
83+
RelayResolverModelWithContextTestFragment,
84+
props.fragmentKey,
85+
);
86+
if (data == null) {
87+
return null;
88+
}
89+
90+
return data.another_value_from_context;
91+
}
92+
93+
function TodoRootComponent(props: {todoID: string}) {
94+
const data = useClientQuery(RelayResolverModelWithContextTestQuery, {
95+
id: props.todoID,
96+
});
97+
if (data?.todo_model == null) {
98+
return null;
99+
}
100+
101+
return <TodoComponent fragmentKey={data?.todo_model} />;
102+
}
103+
104+
test('returns a value from context when resolverDataInjector is used', () => {
105+
const resolverContext: TestResolverContextType = {
106+
greeting: {
107+
myHello: 'This is a value from context',
108+
},
109+
counter: Observable.create<number>(observer => {
110+
observer.next(10);
111+
}),
112+
};
113+
114+
const store = new RelayModernStore(RelayRecordSource.create(), {
115+
resolverContext,
116+
});
117+
const environment = new RelayModernEnvironment({
118+
network: RelayNetwork.create(jest.fn()),
119+
store,
120+
});
121+
122+
addTodo('Test todo');
123+
124+
let renderer;
125+
TestRenderer.act(() => {
126+
renderer = TestRenderer.create(
127+
<EnvironmentWrapper environment={environment}>
128+
<TodoRootComponent todoID="todo-1" />
129+
</EnvironmentWrapper>,
130+
);
131+
});
132+
expect(renderer?.toJSON()).toEqual('This is a value from context');
133+
});
134+
});

0 commit comments

Comments
 (0)