Merge pull request #11 from microsoft/relay-compiler-language-graphitation
Add graphitation specific wrapper around relay-compiler typescript plugin
This commit is contained in:
Коммит
1bcea46d02
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"type": "none",
|
||||||
|
"comment": "Use generated typings in tests",
|
||||||
|
"packageName": "@graphitation/apollo-react-relay-duct-tape",
|
||||||
|
"email": "eloy.de.enige@gmail.com",
|
||||||
|
"dependentChangeType": "none"
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"type": "none",
|
||||||
|
"comment": "chmod",
|
||||||
|
"packageName": "monorepo-scripts",
|
||||||
|
"email": "eloy.de.enige@gmail.com",
|
||||||
|
"dependentChangeType": "none"
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"type": "none",
|
||||||
|
"comment": "Initial import",
|
||||||
|
"packageName": "relay-compiler-language-graphitation",
|
||||||
|
"email": "eloy.de.enige@gmail.com",
|
||||||
|
"dependentChangeType": "none"
|
||||||
|
}
|
|
@ -3,3 +3,5 @@
|
||||||
Compatibility wrapper that provides the react-relay API on top of Apollo Client.
|
Compatibility wrapper that provides the react-relay API on top of Apollo Client.
|
||||||
|
|
||||||
_The name is a reference to the Apollo 13 mission._
|
_The name is a reference to the Apollo 13 mission._
|
||||||
|
|
||||||
|
Use this together with [relay-compiler-language-graphitation](../relay-compiler-language-graphitation) to have typings generated.
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
"test": "monorepo-scripts test",
|
"test": "monorepo-scripts test",
|
||||||
"types": "monorepo-scripts types",
|
"types": "monorepo-scripts types",
|
||||||
"just": "monorepo-scripts",
|
"just": "monorepo-scripts",
|
||||||
"relay": "relay-compiler --language typescript --schema ./src/__tests__/schema.graphql --src ./src"
|
"generate-interfaces": "relay-compiler --language graphitation --schema ./src/__tests__/schema.graphql --src ./src"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@apollo/client": "^3.3.15",
|
"@apollo/client": "^3.3.15",
|
||||||
|
@ -20,7 +20,8 @@
|
||||||
"monorepo-scripts": "*",
|
"monorepo-scripts": "*",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
"relay-compiler": "^11.0.2",
|
"relay-compiler": "^11.0.2",
|
||||||
"relay-compiler-language-typescript": "^14.0.0"
|
"relay-compiler-language-graphitation": "^0.1.0",
|
||||||
|
"ts-expect": "^1.3.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"graphql": "^15.5.0",
|
"graphql": "^15.5.0",
|
||||||
|
|
16
packages/apollo-react-relay-duct-tape/src/__tests__/__generated__/hooksTestFragment.graphql.ts
сгенерированный
Normal file
16
packages/apollo-react-relay-duct-tape/src/__tests__/__generated__/hooksTestFragment.graphql.ts
сгенерированный
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
// @ts-nocheck
|
||||||
|
;
|
||||||
|
import { FragmentRefs } from "@graphitation/apollo-react-relay-duct-tape";
|
||||||
|
export type hooksTestFragment = {
|
||||||
|
readonly id: string;
|
||||||
|
readonly name: string;
|
||||||
|
readonly __typename: "User";
|
||||||
|
readonly " $refType": "hooksTestFragment";
|
||||||
|
};
|
||||||
|
export type hooksTestFragment$data = hooksTestFragment;
|
||||||
|
export type hooksTestFragment$key = {
|
||||||
|
readonly " $data"?: hooksTestFragment$data;
|
||||||
|
readonly " $fragmentRefs": FragmentRefs<"hooksTestFragment">;
|
||||||
|
};
|
|
@ -1,111 +1,17 @@
|
||||||
/* tslint:disable */
|
/* tslint:disable */
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
|
;
|
||||||
import { ConcreteRequest } from "relay-runtime";
|
import { FragmentRefs } from "@graphitation/apollo-react-relay-duct-tape";
|
||||||
export type hooksTestQueryVariables = {
|
export type hooksTestQueryVariables = {
|
||||||
id: string;
|
id: string;
|
||||||
};
|
};
|
||||||
export type hooksTestQueryResponse = {
|
export type hooksTestQueryResponse = {
|
||||||
readonly user: {
|
readonly user: {
|
||||||
readonly __typename: string;
|
readonly " $fragmentRefs": FragmentRefs<"hooksTestFragment">;
|
||||||
readonly id: string;
|
|
||||||
readonly name: string;
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
export type hooksTestQuery = {
|
export type hooksTestQuery = {
|
||||||
readonly response: hooksTestQueryResponse;
|
readonly response: hooksTestQueryResponse;
|
||||||
readonly variables: hooksTestQueryVariables;
|
readonly variables: hooksTestQueryVariables;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
query hooksTestQuery(
|
|
||||||
$id: ID!
|
|
||||||
) {
|
|
||||||
user(id: $id) {
|
|
||||||
__typename
|
|
||||||
id
|
|
||||||
name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
const node: ConcreteRequest = (function(){
|
|
||||||
var v0 = [
|
|
||||||
{
|
|
||||||
"defaultValue": null,
|
|
||||||
"kind": "LocalArgument",
|
|
||||||
"name": "id"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
v1 = [
|
|
||||||
{
|
|
||||||
"alias": null,
|
|
||||||
"args": [
|
|
||||||
{
|
|
||||||
"kind": "Variable",
|
|
||||||
"name": "id",
|
|
||||||
"variableName": "id"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"concreteType": "User",
|
|
||||||
"kind": "LinkedField",
|
|
||||||
"name": "user",
|
|
||||||
"plural": false,
|
|
||||||
"selections": [
|
|
||||||
{
|
|
||||||
"alias": null,
|
|
||||||
"args": null,
|
|
||||||
"kind": "ScalarField",
|
|
||||||
"name": "__typename",
|
|
||||||
"storageKey": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"alias": null,
|
|
||||||
"args": null,
|
|
||||||
"kind": "ScalarField",
|
|
||||||
"name": "id",
|
|
||||||
"storageKey": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"alias": null,
|
|
||||||
"args": null,
|
|
||||||
"kind": "ScalarField",
|
|
||||||
"name": "name",
|
|
||||||
"storageKey": null
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"storageKey": null
|
|
||||||
}
|
|
||||||
];
|
|
||||||
return {
|
|
||||||
"fragment": {
|
|
||||||
"argumentDefinitions": (v0/*: any*/),
|
|
||||||
"kind": "Fragment",
|
|
||||||
"metadata": null,
|
|
||||||
"name": "hooksTestQuery",
|
|
||||||
"selections": (v1/*: any*/),
|
|
||||||
"type": "Query",
|
|
||||||
"abstractKey": null
|
|
||||||
},
|
|
||||||
"kind": "Request",
|
|
||||||
"operation": {
|
|
||||||
"argumentDefinitions": (v0/*: any*/),
|
|
||||||
"kind": "Operation",
|
|
||||||
"name": "hooksTestQuery",
|
|
||||||
"selections": (v1/*: any*/)
|
|
||||||
},
|
|
||||||
"params": {
|
|
||||||
"cacheID": "6c639dbf8d0688927412bd37fc17a021",
|
|
||||||
"id": null,
|
|
||||||
"metadata": {},
|
|
||||||
"name": "hooksTestQuery",
|
|
||||||
"operationKind": "query",
|
|
||||||
"text": "query hooksTestQuery(\n $id: ID!\n) {\n user(id: $id) {\n __typename\n id\n name\n }\n}\n"
|
|
||||||
}
|
|
||||||
};
|
|
||||||
})();
|
|
||||||
(node as any).hash = '7a1aec44ec5fa0e3d845c54f5d71d236';
|
|
||||||
export default node;
|
|
||||||
|
|
|
@ -1,111 +1,17 @@
|
||||||
/* tslint:disable */
|
/* tslint:disable */
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
|
;
|
||||||
import { ConcreteRequest } from "relay-runtime";
|
import { FragmentRefs } from "@graphitation/apollo-react-relay-duct-tape";
|
||||||
export type hooksTestSubscriptionVariables = {
|
export type hooksTestSubscriptionVariables = {
|
||||||
id: string;
|
id: string;
|
||||||
};
|
};
|
||||||
export type hooksTestSubscriptionResponse = {
|
export type hooksTestSubscriptionResponse = {
|
||||||
readonly userNameChanged: {
|
readonly userNameChanged: {
|
||||||
readonly __typename: string;
|
readonly " $fragmentRefs": FragmentRefs<"hooksTestFragment">;
|
||||||
readonly id: string;
|
|
||||||
readonly name: string;
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
export type hooksTestSubscription = {
|
export type hooksTestSubscription = {
|
||||||
readonly response: hooksTestSubscriptionResponse;
|
readonly response: hooksTestSubscriptionResponse;
|
||||||
readonly variables: hooksTestSubscriptionVariables;
|
readonly variables: hooksTestSubscriptionVariables;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
subscription hooksTestSubscription(
|
|
||||||
$id: ID!
|
|
||||||
) {
|
|
||||||
userNameChanged(id: $id) {
|
|
||||||
__typename
|
|
||||||
id
|
|
||||||
name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
const node: ConcreteRequest = (function(){
|
|
||||||
var v0 = [
|
|
||||||
{
|
|
||||||
"defaultValue": null,
|
|
||||||
"kind": "LocalArgument",
|
|
||||||
"name": "id"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
v1 = [
|
|
||||||
{
|
|
||||||
"alias": null,
|
|
||||||
"args": [
|
|
||||||
{
|
|
||||||
"kind": "Variable",
|
|
||||||
"name": "id",
|
|
||||||
"variableName": "id"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"concreteType": "User",
|
|
||||||
"kind": "LinkedField",
|
|
||||||
"name": "userNameChanged",
|
|
||||||
"plural": false,
|
|
||||||
"selections": [
|
|
||||||
{
|
|
||||||
"alias": null,
|
|
||||||
"args": null,
|
|
||||||
"kind": "ScalarField",
|
|
||||||
"name": "__typename",
|
|
||||||
"storageKey": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"alias": null,
|
|
||||||
"args": null,
|
|
||||||
"kind": "ScalarField",
|
|
||||||
"name": "id",
|
|
||||||
"storageKey": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"alias": null,
|
|
||||||
"args": null,
|
|
||||||
"kind": "ScalarField",
|
|
||||||
"name": "name",
|
|
||||||
"storageKey": null
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"storageKey": null
|
|
||||||
}
|
|
||||||
];
|
|
||||||
return {
|
|
||||||
"fragment": {
|
|
||||||
"argumentDefinitions": (v0/*: any*/),
|
|
||||||
"kind": "Fragment",
|
|
||||||
"metadata": null,
|
|
||||||
"name": "hooksTestSubscription",
|
|
||||||
"selections": (v1/*: any*/),
|
|
||||||
"type": "Subscription",
|
|
||||||
"abstractKey": null
|
|
||||||
},
|
|
||||||
"kind": "Request",
|
|
||||||
"operation": {
|
|
||||||
"argumentDefinitions": (v0/*: any*/),
|
|
||||||
"kind": "Operation",
|
|
||||||
"name": "hooksTestSubscription",
|
|
||||||
"selections": (v1/*: any*/)
|
|
||||||
},
|
|
||||||
"params": {
|
|
||||||
"cacheID": "ad32680a90ff3dcb6a6060afc511e537",
|
|
||||||
"id": null,
|
|
||||||
"metadata": {},
|
|
||||||
"name": "hooksTestSubscription",
|
|
||||||
"operationKind": "subscription",
|
|
||||||
"text": "subscription hooksTestSubscription(\n $id: ID!\n) {\n userNameChanged(id: $id) {\n __typename\n id\n name\n }\n}\n"
|
|
||||||
}
|
|
||||||
};
|
|
||||||
})();
|
|
||||||
(node as any).hash = '5204570ce28f0c0003d6129fea7a3f0d';
|
|
||||||
export default node;
|
|
||||||
|
|
|
@ -8,6 +8,7 @@ import {
|
||||||
ReactTestRenderer,
|
ReactTestRenderer,
|
||||||
} from "react-test-renderer";
|
} from "react-test-renderer";
|
||||||
import { ApolloProvider } from "@apollo/client";
|
import { ApolloProvider } from "@apollo/client";
|
||||||
|
import { expectType } from "ts-expect";
|
||||||
|
|
||||||
import { graphql } from "@graphitation/graphql-js-tag";
|
import { graphql } from "@graphitation/graphql-js-tag";
|
||||||
import * as MockPayloadGenerator from "@graphitation/graphql-js-operation-payload-generator";
|
import * as MockPayloadGenerator from "@graphitation/graphql-js-operation-payload-generator";
|
||||||
|
@ -16,32 +17,98 @@ import {
|
||||||
createMockClient,
|
createMockClient,
|
||||||
} from "@graphitation/apollo-mock-client/src/index"; // FIXME
|
} from "@graphitation/apollo-mock-client/src/index"; // FIXME
|
||||||
|
|
||||||
import {
|
import { useFragment, useLazyLoadQuery, useSubscription } from "../hooks";
|
||||||
GraphQLTaggedNode,
|
|
||||||
useFragment,
|
|
||||||
useLazyLoadQuery,
|
|
||||||
useSubscription,
|
|
||||||
} from "../hooks";
|
|
||||||
// import { GraphQLTaggedNode } from "./taggedNode";
|
|
||||||
import { FragmentRefs } from "../types";
|
|
||||||
|
|
||||||
import { hooksTestQuery } from "./__generated__/hooksTestQuery.graphql";
|
import { hooksTestQuery } from "./__generated__/hooksTestQuery.graphql";
|
||||||
import { hooksTestSubscription } from "./__generated__/hooksTestSubscription.graphql";
|
import { hooksTestSubscription } from "./__generated__/hooksTestSubscription.graphql";
|
||||||
|
import { hooksTestFragment$key } from "./__generated__/hooksTestFragment.graphql";
|
||||||
|
|
||||||
const schema = buildSchema(
|
const schema = buildSchema(
|
||||||
readFileSync(join(__dirname, "schema.graphql"), "utf8")
|
readFileSync(join(__dirname, "schema.graphql"), "utf8")
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fragment test subject
|
||||||
|
*/
|
||||||
|
|
||||||
|
const fragment = graphql`
|
||||||
|
fragment hooksTestFragment on User {
|
||||||
|
__typename
|
||||||
|
id
|
||||||
|
name
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const FragmentComponent: React.FC<{ user: hooksTestFragment$key }> = (
|
||||||
|
props
|
||||||
|
) => {
|
||||||
|
const user = useFragment(fragment, props.user);
|
||||||
|
return <div id={user.__typename}>{user.name}</div>;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Query test subject
|
||||||
|
*/
|
||||||
|
|
||||||
const query = graphql`
|
const query = graphql`
|
||||||
query hooksTestQuery($id: ID!) {
|
query hooksTestQuery($id: ID!) {
|
||||||
user(id: $id) {
|
user(id: $id) {
|
||||||
__typename
|
...hooksTestFragment
|
||||||
id
|
|
||||||
name
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
${fragment}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
const QueryComponent: React.FC = () => {
|
||||||
|
const { data, error } = useLazyLoadQuery<hooksTestQuery>(query, {
|
||||||
|
id: "some-user-id",
|
||||||
|
});
|
||||||
|
if (error) {
|
||||||
|
return <div id="error">{error.message}</div>;
|
||||||
|
} else if (data) {
|
||||||
|
return <FragmentComponent user={data.user} />;
|
||||||
|
} else {
|
||||||
|
return <div id="loading">Loading...</div>;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subscription test subject
|
||||||
|
*/
|
||||||
|
|
||||||
|
const subscription = graphql`
|
||||||
|
subscription hooksTestSubscription($id: ID!) {
|
||||||
|
userNameChanged(id: $id) {
|
||||||
|
...hooksTestFragment
|
||||||
|
}
|
||||||
|
}
|
||||||
|
${fragment}
|
||||||
|
`;
|
||||||
|
|
||||||
|
type SubscriptionHookParams = Parameters<typeof useSubscription>[0];
|
||||||
|
interface SubjectProps {
|
||||||
|
onNext?: SubscriptionHookParams["onNext"];
|
||||||
|
onError?: SubscriptionHookParams["onError"] | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SubscriptionComponent: React.FC<SubjectProps> = ({
|
||||||
|
onNext = jest.fn(),
|
||||||
|
onError = jest.fn(),
|
||||||
|
children,
|
||||||
|
}) => {
|
||||||
|
useSubscription<hooksTestSubscription>({
|
||||||
|
subscription,
|
||||||
|
variables: { id: "some-user-id" },
|
||||||
|
onNext,
|
||||||
|
onError: onError || undefined,
|
||||||
|
});
|
||||||
|
return <>{children}</>;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests
|
||||||
|
*/
|
||||||
|
|
||||||
let client: ApolloMockClient;
|
let client: ApolloMockClient;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
@ -50,24 +117,11 @@ beforeEach(() => {
|
||||||
|
|
||||||
describe(useLazyLoadQuery, () => {
|
describe(useLazyLoadQuery, () => {
|
||||||
it("uses Apollo's useQuery hook", async () => {
|
it("uses Apollo's useQuery hook", async () => {
|
||||||
const Subject: React.FC = () => {
|
|
||||||
const { data, error } = useLazyLoadQuery<hooksTestQuery>(query, {
|
|
||||||
id: "some-user-id",
|
|
||||||
});
|
|
||||||
if (error) {
|
|
||||||
return <div id="error">{error.message}</div>;
|
|
||||||
} else if (data) {
|
|
||||||
return <div id={data.user.__typename}>{data.user.name}</div>;
|
|
||||||
} else {
|
|
||||||
return <div id="loading">Loading...</div>;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let tree: ReactTestRenderer;
|
let tree: ReactTestRenderer;
|
||||||
act(() => {
|
act(() => {
|
||||||
tree = createTestRenderer(
|
tree = createTestRenderer(
|
||||||
<ApolloProvider client={client}>
|
<ApolloProvider client={client}>
|
||||||
<Subject />
|
<QueryComponent />
|
||||||
</ApolloProvider>
|
</ApolloProvider>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -89,74 +143,27 @@ describe(useLazyLoadQuery, () => {
|
||||||
|
|
||||||
describe(useFragment, () => {
|
describe(useFragment, () => {
|
||||||
it("currently simply passes through the data it receives", () => {
|
it("currently simply passes through the data it receives", () => {
|
||||||
const fragment = ({} as unknown) as GraphQLTaggedNode;
|
const fragmentRef: hooksTestFragment$key = {} as any;
|
||||||
const fragmentRef = { someKey: "some-data" } as any;
|
|
||||||
expect(useFragment(fragment, fragmentRef)).toEqual(fragmentRef);
|
expect(useFragment(fragment, fragmentRef)).toEqual(fragmentRef);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("unmasks the opaque data's typing that gets emitted by the compiler", () => {
|
it("unmasks the opaque data's typing that gets emitted by the compiler", () => {
|
||||||
type SomeFragment$data = { someKey: string };
|
const fragmentRef: hooksTestFragment$key = {} as any;
|
||||||
type SomeFragment$key = {
|
const user = useFragment(fragment, fragmentRef);
|
||||||
readonly " $data"?: SomeFragment$data;
|
expectType<string>(user.id);
|
||||||
readonly " $fragmentRefs": FragmentRefs<"SomeFragment">;
|
expectType<string>(user.name);
|
||||||
};
|
|
||||||
|
|
||||||
const fragment = ({} as unknown) as GraphQLTaggedNode;
|
|
||||||
const opaqueFragmentRef = ({} as unknown) as SomeFragment$key;
|
|
||||||
|
|
||||||
// This test just checks that there are no TS errors. Alas the test suite currently won't fail if that were the
|
|
||||||
// case, but at least there's a test that covers the intent.
|
|
||||||
const data: SomeFragment$data = useFragment(fragment, opaqueFragmentRef);
|
|
||||||
void data;
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe(useSubscription, () => {
|
describe(useSubscription, () => {
|
||||||
const subscription = graphql`
|
it("uses Apollo's useSubscription hook and updates the store", async () => {
|
||||||
subscription hooksTestSubscription($id: ID!) {
|
|
||||||
userNameChanged(id: $id) {
|
|
||||||
__typename
|
|
||||||
id
|
|
||||||
name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
type SubscriptionHookParams = Parameters<typeof useSubscription>[0];
|
|
||||||
interface SubjectProps {
|
|
||||||
onNext?: SubscriptionHookParams["onNext"];
|
|
||||||
onError?: SubscriptionHookParams["onError"] | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Subject: React.FC<SubjectProps> = ({
|
|
||||||
onNext = jest.fn(),
|
|
||||||
onError = jest.fn(),
|
|
||||||
children,
|
|
||||||
}) => {
|
|
||||||
useSubscription<hooksTestSubscription>({
|
|
||||||
subscription,
|
|
||||||
variables: { id: "some-user-id" },
|
|
||||||
onNext,
|
|
||||||
onError: onError || undefined,
|
|
||||||
});
|
|
||||||
return <>{children}</>;
|
|
||||||
};
|
|
||||||
|
|
||||||
it("uses Apollo's useSubscription hook", async () => {
|
|
||||||
const QueryComponent = () => {
|
|
||||||
const { data } = useLazyLoadQuery<hooksTestQuery>(query, {
|
|
||||||
id: "some-user-id",
|
|
||||||
});
|
|
||||||
return data ? <div>{data.user.name}</div> : null;
|
|
||||||
};
|
|
||||||
|
|
||||||
let tree: ReactTestRenderer;
|
let tree: ReactTestRenderer;
|
||||||
act(() => {
|
act(() => {
|
||||||
tree = createTestRenderer(
|
tree = createTestRenderer(
|
||||||
<ApolloProvider client={client}>
|
<ApolloProvider client={client}>
|
||||||
<Subject>
|
<SubscriptionComponent>
|
||||||
<QueryComponent />
|
<QueryComponent />
|
||||||
</Subject>
|
</SubscriptionComponent>
|
||||||
</ApolloProvider>
|
</ApolloProvider>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -217,7 +224,7 @@ describe(useSubscription, () => {
|
||||||
act(() => {
|
act(() => {
|
||||||
createTestRenderer(
|
createTestRenderer(
|
||||||
<ApolloProvider client={client}>
|
<ApolloProvider client={client}>
|
||||||
<Subject onNext={onNext} />
|
<SubscriptionComponent onNext={onNext} />
|
||||||
</ApolloProvider>
|
</ApolloProvider>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -246,7 +253,7 @@ describe(useSubscription, () => {
|
||||||
act(() => {
|
act(() => {
|
||||||
createTestRenderer(
|
createTestRenderer(
|
||||||
<ApolloProvider client={client}>
|
<ApolloProvider client={client}>
|
||||||
<Subject onError={onError} />
|
<SubscriptionComponent onError={onError} />
|
||||||
</ApolloProvider>
|
</ApolloProvider>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -265,7 +272,7 @@ describe(useSubscription, () => {
|
||||||
act(() => {
|
act(() => {
|
||||||
createTestRenderer(
|
createTestRenderer(
|
||||||
<ApolloProvider client={client}>
|
<ApolloProvider client={client}>
|
||||||
<Subject onError={null} />
|
<SubscriptionComponent onError={null} />
|
||||||
</ApolloProvider>
|
</ApolloProvider>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
# relay-compiler-language-graphitation
|
||||||
|
|
||||||
|
A relay-compiler plugin that wraps [the TypeScript plugin](https://github.com/relay-tools/relay-compiler-language-typescript) and augments it slightly for our needs:
|
||||||
|
|
||||||
|
- Currently only emit typings, no relay-runtime metadata
|
||||||
|
- Allow interpolation of GraphQL fragment documents
|
||||||
|
- Rewrite `@graphitation_test_operation` to relay-compiler's `@raw_response_type` -- _this will likely be removed short-term_
|
||||||
|
- Rewrite `relay-runtime` import in generated artefacts to `@graphitation/apollo-react-relay-duct-tape`
|
||||||
|
- Strip `__isFoo` fields from raw response typings, which is something that relay-runtime needs
|
|
@ -0,0 +1,24 @@
|
||||||
|
{
|
||||||
|
"name": "relay-compiler-language-graphitation",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"main": "./lib/index.js",
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsc",
|
||||||
|
"lint": "monorepo-scripts lint",
|
||||||
|
"just": "monorepo-scripts"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"graphql": "^14.6.0",
|
||||||
|
"relay-compiler-language-typescript": "^14.0.0",
|
||||||
|
"typescript": "^4.2.3"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/jest": "^26.0.22",
|
||||||
|
"@types/relay-compiler": "^8.0.0",
|
||||||
|
"monorepo-scripts": "*"
|
||||||
|
},
|
||||||
|
"publishConfig": {
|
||||||
|
"main": "./lib/index.js",
|
||||||
|
"access": "public"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,84 @@
|
||||||
|
/**
|
||||||
|
* Taken from https://github.com/relay-tools/relay-compiler-language-typescript/blob/b186ac3d9d9682949638211c3126c015f8706b0f/src/FindGraphQLTags.ts
|
||||||
|
* License: MIT
|
||||||
|
* Copyright 2018 Kaare Hoff Skovgaard kaare@kaareskovgaard.net, Eloy Durán eloy.de.enige@gmail.com
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* NOTE: This is currently in-flight and mostly re-uses code from the above mentioned package, where it's tested.
|
||||||
|
*/
|
||||||
|
/* istanbul ignore file */
|
||||||
|
|
||||||
|
import * as ts from "typescript";
|
||||||
|
import {
|
||||||
|
GraphQLTag,
|
||||||
|
GraphQLTagFinder,
|
||||||
|
} from "relay-compiler/lib/language/RelayLanguagePluginInterface";
|
||||||
|
import { rewriteGraphitationDirectives } from "./rewriteGraphitationDirectives";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @note Difference from the TS language plugin is that we only support hooks, so no need for HOCs.
|
||||||
|
*/
|
||||||
|
function visit(node: ts.Node, addGraphQLTag: (tag: GraphQLTag) => void): void {
|
||||||
|
function visitNode(node: ts.Node) {
|
||||||
|
switch (node.kind) {
|
||||||
|
case ts.SyntaxKind.TaggedTemplateExpression: {
|
||||||
|
const taggedTemplate = node as ts.TaggedTemplateExpression;
|
||||||
|
if (isGraphQLTag(taggedTemplate.tag)) {
|
||||||
|
// TODO: This code previously had no validation and thus no
|
||||||
|
// keyName/sourceLocationOffset. Are these right?
|
||||||
|
addGraphQLTag({
|
||||||
|
keyName: null,
|
||||||
|
template: getGraphQLText(taggedTemplate),
|
||||||
|
sourceLocationOffset: getSourceLocationOffset(taggedTemplate),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ts.forEachChild(node, visitNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
visitNode(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isGraphQLTag(tag: ts.Node): boolean {
|
||||||
|
return (
|
||||||
|
tag.kind === ts.SyntaxKind.Identifier &&
|
||||||
|
(tag as ts.Identifier).text === "graphql"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @note The difference here is that we do allow substitutions, but only in the trailing part, so we always return the
|
||||||
|
* head. This might lead to a bad DX when the user does allow substitution in the document part and receiving
|
||||||
|
* hard to understand errors, but seeing as this is meant as temporary solution it may be a worthwhile trade-off.
|
||||||
|
*/
|
||||||
|
function getTemplateNode(quasi: ts.TaggedTemplateExpression) {
|
||||||
|
if (quasi.template.kind === ts.SyntaxKind.TemplateExpression) {
|
||||||
|
return quasi.template.head;
|
||||||
|
}
|
||||||
|
return quasi.template;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @note The difference here is that we rewrite graphitation specific directives to relay ones.
|
||||||
|
*/
|
||||||
|
function getGraphQLText(quasi: ts.TaggedTemplateExpression) {
|
||||||
|
return rewriteGraphitationDirectives(getTemplateNode(quasi).text);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSourceLocationOffset(quasi: ts.TaggedTemplateExpression) {
|
||||||
|
const pos = getTemplateNode(quasi).pos;
|
||||||
|
const loc = quasi.getSourceFile().getLineAndCharacterOfPosition(pos);
|
||||||
|
return {
|
||||||
|
line: loc.line + 1,
|
||||||
|
column: loc.character + 1,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const find: GraphQLTagFinder = (text, filePath) => {
|
||||||
|
const result: GraphQLTag[] = [];
|
||||||
|
const ast = ts.createSourceFile(filePath, text, ts.ScriptTarget.Latest, true);
|
||||||
|
visit(ast, (tag) => result.push(tag));
|
||||||
|
return result;
|
||||||
|
};
|
|
@ -0,0 +1,13 @@
|
||||||
|
/**
|
||||||
|
* NOTE: This is currently in-flight and mostly re-uses code from the above mentioned package, where it's tested.
|
||||||
|
*/
|
||||||
|
/* istanbul ignore file */
|
||||||
|
|
||||||
|
import { FormatModule } from "relay-compiler/lib/language/RelayLanguagePluginInterface";
|
||||||
|
|
||||||
|
export const formatModule: FormatModule = ({ hash, typeText }) =>
|
||||||
|
`/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
// @ts-nocheck
|
||||||
|
${hash ? `/* ${hash} */\n` : ""};
|
||||||
|
${typeText || ""}`;
|
|
@ -0,0 +1,21 @@
|
||||||
|
import type { PluginInitializer } from "relay-compiler/lib/language/RelayLanguagePluginInterface";
|
||||||
|
import typescriptPluginInitializer from "relay-compiler-language-typescript";
|
||||||
|
|
||||||
|
import { find as findGraphQLTags } from "./findGraphQLTags";
|
||||||
|
import { formatModule } from "./formatModule";
|
||||||
|
import { generateFactory } from "./typeGenerator";
|
||||||
|
|
||||||
|
const pluginInitializer: PluginInitializer = () => {
|
||||||
|
const typescriptPlugin = typescriptPluginInitializer();
|
||||||
|
return {
|
||||||
|
...typescriptPlugin,
|
||||||
|
findGraphQLTags,
|
||||||
|
formatModule,
|
||||||
|
typeGenerator: {
|
||||||
|
...typescriptPlugin.typeGenerator,
|
||||||
|
generate: generateFactory(typescriptPlugin.typeGenerator.generate),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export = pluginInitializer;
|
|
@ -0,0 +1,32 @@
|
||||||
|
/**
|
||||||
|
* NOTE: This is currently in-flight and mostly re-uses code from the above mentioned package, where it's tested.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { parse, print, visit } from "graphql";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This rewrites graphitation specific directives to relay ones. Currently it does the following:
|
||||||
|
*
|
||||||
|
* - `@graphitation_test_operation` is rewritten to `@raw_response_type`.
|
||||||
|
* In the future this should probably also add `@relay_test_operation`.
|
||||||
|
*
|
||||||
|
* @param document A single GraphQL document
|
||||||
|
*/
|
||||||
|
export function rewriteGraphitationDirectives(document: string) {
|
||||||
|
const documentNode = parse(document);
|
||||||
|
const rewrittenDocumentNode = visit(documentNode, {
|
||||||
|
Directive(directiveNode) {
|
||||||
|
if (directiveNode.name.value === "graphitation_test_operation") {
|
||||||
|
return {
|
||||||
|
...directiveNode,
|
||||||
|
name: {
|
||||||
|
kind: "Name",
|
||||||
|
value: "raw_response_type",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return print(rewrittenDocumentNode);
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
/**
|
||||||
|
* NOTE: This is currently in-flight and mostly re-uses code from the above mentioned package, where it's tested.
|
||||||
|
*/
|
||||||
|
/* istanbul ignore file */
|
||||||
|
|
||||||
|
import { TypeGenerator } from "relay-compiler/lib/language/RelayLanguagePluginInterface";
|
||||||
|
|
||||||
|
export function generateFactory(wrappedGenerate: TypeGenerator["generate"]) {
|
||||||
|
const generate: TypeGenerator["generate"] = (schema, node, options) => {
|
||||||
|
const generated = wrappedGenerate(schema, node, options);
|
||||||
|
return (
|
||||||
|
generated
|
||||||
|
.replace("relay-runtime", "@graphitation/apollo-react-relay-duct-tape")
|
||||||
|
// These fields in the `@raw_response_type` output are really just for relay-runtime, so for now we can just
|
||||||
|
// strip them out entirely.
|
||||||
|
.replace(/^\s+readonly __is[A-Z].+;\n/gm, "")
|
||||||
|
);
|
||||||
|
};
|
||||||
|
return generate;
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"composite": false,
|
||||||
|
"incremental": true,
|
||||||
|
"tsBuildInfoFile": ".tsbuildinfo",
|
||||||
|
"target": "es5",
|
||||||
|
"module": "commonjs",
|
||||||
|
"moduleResolution": "Node",
|
||||||
|
"rootDir": "src",
|
||||||
|
"outDir": "lib",
|
||||||
|
"declaration": false,
|
||||||
|
"declarationMap": false,
|
||||||
|
"strict": true,
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"esModuleInterop": true
|
||||||
|
},
|
||||||
|
"include": ["src"],
|
||||||
|
"references": []
|
||||||
|
}
|
|
@ -14,7 +14,7 @@ export const test = () => {
|
||||||
return jestTask({
|
return jestTask({
|
||||||
config: path.join(__dirname, "config", "jest.config.js"),
|
config: path.join(__dirname, "config", "jest.config.js"),
|
||||||
watch: argv().watch,
|
watch: argv().watch,
|
||||||
_: argv()._
|
_: argv()._,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
36
yarn.lock
36
yarn.lock
|
@ -1192,6 +1192,20 @@
|
||||||
"@types/scheduler" "*"
|
"@types/scheduler" "*"
|
||||||
csstype "^3.0.2"
|
csstype "^3.0.2"
|
||||||
|
|
||||||
|
"@types/relay-compiler@^8.0.0":
|
||||||
|
version "8.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/relay-compiler/-/relay-compiler-8.0.0.tgz#98961b923555145067d3895f8774f3024f14e6fa"
|
||||||
|
integrity sha512-rSR62gfhm2ZZp888RK93/jODOCA1j2MpWr5ibZEyYprmVG1G9KE5TfNUERCcc9z58F4d8tMIX00ZpBOU1sWcDA==
|
||||||
|
dependencies:
|
||||||
|
"@types/relay-runtime" "*"
|
||||||
|
graphql "^14.5.3"
|
||||||
|
typescript "^3.0.0"
|
||||||
|
|
||||||
|
"@types/relay-runtime@*":
|
||||||
|
version "11.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/relay-runtime/-/relay-runtime-11.0.0.tgz#7ddbe41152d46b614d1f3f7f0672cfb721eb93f1"
|
||||||
|
integrity sha512-D5JNxDhkcPSIMZc2j3N/fh61399O309ph9RVmzbF+Fsyvepsqy7FirFKcJ2CMfYxE/kb8ZD/Xdzs3CYZEiQc4w==
|
||||||
|
|
||||||
"@types/scheduler@*":
|
"@types/scheduler@*":
|
||||||
version "0.16.1"
|
version "0.16.1"
|
||||||
resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.1.tgz#18845205e86ff0038517aab7a18a62a6b9f71275"
|
resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.1.tgz#18845205e86ff0038517aab7a18a62a6b9f71275"
|
||||||
|
@ -3324,6 +3338,13 @@ graphql-tag@^2.12.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
tslib "^2.1.0"
|
tslib "^2.1.0"
|
||||||
|
|
||||||
|
graphql@^14.5.3, graphql@^14.6.0:
|
||||||
|
version "14.7.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/graphql/-/graphql-14.7.0.tgz#7fa79a80a69be4a31c27dda824dc04dac2035a72"
|
||||||
|
integrity sha512-l0xWZpoPKpppFzMfvVyFmp9vLN7w/ZZJPefUicMCepfJeQ8sMcztloGYY9DfjVPo6tIUDzU5Hw3MUbIjj9AVVA==
|
||||||
|
dependencies:
|
||||||
|
iterall "^1.2.2"
|
||||||
|
|
||||||
graphql@^15.0.0, graphql@^15.5.0:
|
graphql@^15.0.0, graphql@^15.5.0:
|
||||||
version "15.5.0"
|
version "15.5.0"
|
||||||
resolved "https://registry.yarnpkg.com/graphql/-/graphql-15.5.0.tgz#39d19494dbe69d1ea719915b578bf920344a69d5"
|
resolved "https://registry.yarnpkg.com/graphql/-/graphql-15.5.0.tgz#39d19494dbe69d1ea719915b578bf920344a69d5"
|
||||||
|
@ -3838,6 +3859,11 @@ istanbul-reports@^3.0.2:
|
||||||
html-escaper "^2.0.0"
|
html-escaper "^2.0.0"
|
||||||
istanbul-lib-report "^3.0.0"
|
istanbul-lib-report "^3.0.0"
|
||||||
|
|
||||||
|
iterall@^1.2.2:
|
||||||
|
version "1.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/iterall/-/iterall-1.3.0.tgz#afcb08492e2915cbd8a0884eb93a8c94d0d72fea"
|
||||||
|
integrity sha512-QZ9qOMdF+QLHxy1QIpUHUU1D5pS2CG2P69LF6L6CPjPYA/XMOmKV3PZpawHoAjHNyB0swdVTRxdYT4tbBbxqwg==
|
||||||
|
|
||||||
jest-changed-files@^26.6.2:
|
jest-changed-files@^26.6.2:
|
||||||
version "26.6.2"
|
version "26.6.2"
|
||||||
resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-26.6.2.tgz#f6198479e1cc66f22f9ae1e22acaa0b429c042d0"
|
resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-26.6.2.tgz#f6198479e1cc66f22f9ae1e22acaa0b429c042d0"
|
||||||
|
@ -6303,6 +6329,11 @@ trim-repeated@^1.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
escape-string-regexp "^1.0.2"
|
escape-string-regexp "^1.0.2"
|
||||||
|
|
||||||
|
ts-expect@^1.3.0:
|
||||||
|
version "1.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/ts-expect/-/ts-expect-1.3.0.tgz#3f8d3966e0e22b5e2bb88337eb99db6816a4c1cf"
|
||||||
|
integrity sha512-e4g0EJtAjk64xgnFPD6kTBUtpnMVzDrMb12N1YZV0VvSlhnVT3SGxiYTLdGy8Q5cYHOIC/FAHmZ10eGrAguicQ==
|
||||||
|
|
||||||
ts-invariant@^0.7.0:
|
ts-invariant@^0.7.0:
|
||||||
version "0.7.3"
|
version "0.7.3"
|
||||||
resolved "https://registry.yarnpkg.com/ts-invariant/-/ts-invariant-0.7.3.tgz#13aae22a4a165393aaf5cecdee45ef4128d358b8"
|
resolved "https://registry.yarnpkg.com/ts-invariant/-/ts-invariant-0.7.3.tgz#13aae22a4a165393aaf5cecdee45ef4128d358b8"
|
||||||
|
@ -6433,6 +6464,11 @@ typedarray-to-buffer@^3.1.5:
|
||||||
dependencies:
|
dependencies:
|
||||||
is-typedarray "^1.0.0"
|
is-typedarray "^1.0.0"
|
||||||
|
|
||||||
|
typescript@^3.0.0:
|
||||||
|
version "3.9.9"
|
||||||
|
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.9.tgz#e69905c54bc0681d0518bd4d587cc6f2d0b1a674"
|
||||||
|
integrity sha512-kdMjTiekY+z/ubJCATUPlRDl39vXYiMV9iyeMuEuXZh2we6zz80uovNN2WlAxmmdE/Z/YQe+EbOEXB5RHEED3w==
|
||||||
|
|
||||||
typescript@^4.2.3:
|
typescript@^4.2.3:
|
||||||
version "4.2.3"
|
version "4.2.3"
|
||||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.2.3.tgz#39062d8019912d43726298f09493d598048c1ce3"
|
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.2.3.tgz#39062d8019912d43726298f09493d598048c1ce3"
|
||||||
|
|
Загрузка…
Ссылка в новой задаче