|
1 |
| -import { expect } from 'chai'; |
| 1 | +import { assert, expect } from 'chai'; |
2 | 2 | import { describe, it } from 'mocha';
|
3 | 3 |
|
4 | 4 | import { expectJSON } from '../../__testUtils__/expectJSON.js';
|
5 | 5 | import { expectPromise } from '../../__testUtils__/expectPromise.js';
|
6 | 6 | import { resolveOnNextTick } from '../../__testUtils__/resolveOnNextTick.js';
|
7 | 7 |
|
| 8 | +import { promiseWithResolvers } from '../../jsutils/promiseWithResolvers.js'; |
| 9 | + |
8 | 10 | import type { DocumentNode } from '../../language/ast.js';
|
9 | 11 | import { parse } from '../../language/parser.js';
|
10 | 12 |
|
@@ -856,6 +858,253 @@ describe('Execute: defer directive', () => {
|
856 | 858 | ]);
|
857 | 859 | });
|
858 | 860 |
|
| 861 | + it('Initiates deferred grouped field sets only if they have been released as pending', async () => { |
| 862 | + const document = parse(` |
| 863 | + query { |
| 864 | + ... @defer { |
| 865 | + a { |
| 866 | + ... @defer { |
| 867 | + b { |
| 868 | + c { d } |
| 869 | + } |
| 870 | + } |
| 871 | + } |
| 872 | + } |
| 873 | + ... @defer { |
| 874 | + a { |
| 875 | + someField |
| 876 | + ... @defer { |
| 877 | + b { |
| 878 | + e { f } |
| 879 | + } |
| 880 | + } |
| 881 | + } |
| 882 | + } |
| 883 | + } |
| 884 | + `); |
| 885 | + |
| 886 | + const { promise: slowFieldPromise, resolve: resolveSlowField } = |
| 887 | + promiseWithResolvers(); |
| 888 | + let cResolverCalled = false; |
| 889 | + let eResolverCalled = false; |
| 890 | + const executeResult = experimentalExecuteIncrementally({ |
| 891 | + schema, |
| 892 | + document, |
| 893 | + rootValue: { |
| 894 | + a: { |
| 895 | + someField: slowFieldPromise, |
| 896 | + b: { |
| 897 | + c: () => { |
| 898 | + cResolverCalled = true; |
| 899 | + return { d: 'd' }; |
| 900 | + }, |
| 901 | + e: () => { |
| 902 | + eResolverCalled = true; |
| 903 | + return { f: 'f' }; |
| 904 | + }, |
| 905 | + }, |
| 906 | + }, |
| 907 | + }, |
| 908 | + enableEarlyExecution: false, |
| 909 | + }); |
| 910 | + |
| 911 | + assert('initialResult' in executeResult); |
| 912 | + |
| 913 | + const result1 = executeResult.initialResult; |
| 914 | + expectJSON(result1).toDeepEqual({ |
| 915 | + data: {}, |
| 916 | + pending: [ |
| 917 | + { id: '0', path: [] }, |
| 918 | + { id: '1', path: [] }, |
| 919 | + ], |
| 920 | + hasNext: true, |
| 921 | + }); |
| 922 | + |
| 923 | + const iterator = executeResult.subsequentResults[Symbol.asyncIterator](); |
| 924 | + |
| 925 | + expect(cResolverCalled).to.equal(false); |
| 926 | + expect(eResolverCalled).to.equal(false); |
| 927 | + |
| 928 | + const result2 = await iterator.next(); |
| 929 | + expectJSON(result2).toDeepEqual({ |
| 930 | + value: { |
| 931 | + pending: [{ id: '2', path: ['a'] }], |
| 932 | + incremental: [ |
| 933 | + { |
| 934 | + data: { a: {} }, |
| 935 | + id: '0', |
| 936 | + }, |
| 937 | + { |
| 938 | + data: { b: {} }, |
| 939 | + id: '2', |
| 940 | + }, |
| 941 | + { |
| 942 | + data: { c: { d: 'd' } }, |
| 943 | + id: '2', |
| 944 | + subPath: ['b'], |
| 945 | + }, |
| 946 | + ], |
| 947 | + completed: [{ id: '0' }, { id: '2' }], |
| 948 | + hasNext: true, |
| 949 | + }, |
| 950 | + done: false, |
| 951 | + }); |
| 952 | + |
| 953 | + expect(cResolverCalled).to.equal(true); |
| 954 | + expect(eResolverCalled).to.equal(false); |
| 955 | + |
| 956 | + resolveSlowField('someField'); |
| 957 | + |
| 958 | + const result3 = await iterator.next(); |
| 959 | + expectJSON(result3).toDeepEqual({ |
| 960 | + value: { |
| 961 | + pending: [{ id: '3', path: ['a'] }], |
| 962 | + incremental: [ |
| 963 | + { |
| 964 | + data: { someField: 'someField' }, |
| 965 | + id: '1', |
| 966 | + subPath: ['a'], |
| 967 | + }, |
| 968 | + { |
| 969 | + data: { e: { f: 'f' } }, |
| 970 | + id: '3', |
| 971 | + subPath: ['b'], |
| 972 | + }, |
| 973 | + ], |
| 974 | + completed: [{ id: '1' }, { id: '3' }], |
| 975 | + hasNext: false, |
| 976 | + }, |
| 977 | + done: false, |
| 978 | + }); |
| 979 | + |
| 980 | + expect(eResolverCalled).to.equal(true); |
| 981 | + |
| 982 | + const result4 = await iterator.next(); |
| 983 | + expectJSON(result4).toDeepEqual({ |
| 984 | + value: undefined, |
| 985 | + done: true, |
| 986 | + }); |
| 987 | + }); |
| 988 | + |
| 989 | + it('Initiates unique deferred grouped field sets after those that are common to sibling defers', async () => { |
| 990 | + const document = parse(` |
| 991 | + query { |
| 992 | + ... @defer { |
| 993 | + a { |
| 994 | + ... @defer { |
| 995 | + b { |
| 996 | + c { d } |
| 997 | + } |
| 998 | + } |
| 999 | + } |
| 1000 | + } |
| 1001 | + ... @defer { |
| 1002 | + a { |
| 1003 | + ... @defer { |
| 1004 | + b { |
| 1005 | + c { d } |
| 1006 | + e { f } |
| 1007 | + } |
| 1008 | + } |
| 1009 | + } |
| 1010 | + } |
| 1011 | + } |
| 1012 | + `); |
| 1013 | + |
| 1014 | + const { promise: cPromise, resolve: resolveC } = |
| 1015 | + // eslint-disable-next-line @typescript-eslint/no-invalid-void-type |
| 1016 | + promiseWithResolvers<void>(); |
| 1017 | + let cResolverCalled = false; |
| 1018 | + let eResolverCalled = false; |
| 1019 | + const executeResult = experimentalExecuteIncrementally({ |
| 1020 | + schema, |
| 1021 | + document, |
| 1022 | + rootValue: { |
| 1023 | + a: { |
| 1024 | + b: { |
| 1025 | + c: async () => { |
| 1026 | + cResolverCalled = true; |
| 1027 | + await cPromise; |
| 1028 | + return { d: 'd' }; |
| 1029 | + }, |
| 1030 | + e: () => { |
| 1031 | + eResolverCalled = true; |
| 1032 | + return { f: 'f' }; |
| 1033 | + }, |
| 1034 | + }, |
| 1035 | + }, |
| 1036 | + }, |
| 1037 | + enableEarlyExecution: false, |
| 1038 | + }); |
| 1039 | + |
| 1040 | + assert('initialResult' in executeResult); |
| 1041 | + |
| 1042 | + const result1 = executeResult.initialResult; |
| 1043 | + expectJSON(result1).toDeepEqual({ |
| 1044 | + data: {}, |
| 1045 | + pending: [ |
| 1046 | + { id: '0', path: [] }, |
| 1047 | + { id: '1', path: [] }, |
| 1048 | + ], |
| 1049 | + hasNext: true, |
| 1050 | + }); |
| 1051 | + |
| 1052 | + const iterator = executeResult.subsequentResults[Symbol.asyncIterator](); |
| 1053 | + |
| 1054 | + expect(cResolverCalled).to.equal(false); |
| 1055 | + expect(eResolverCalled).to.equal(false); |
| 1056 | + |
| 1057 | + const result2 = await iterator.next(); |
| 1058 | + expectJSON(result2).toDeepEqual({ |
| 1059 | + value: { |
| 1060 | + pending: [ |
| 1061 | + { id: '2', path: ['a'] }, |
| 1062 | + { id: '3', path: ['a'] }, |
| 1063 | + ], |
| 1064 | + incremental: [ |
| 1065 | + { |
| 1066 | + data: { a: {} }, |
| 1067 | + id: '0', |
| 1068 | + }, |
| 1069 | + ], |
| 1070 | + completed: [{ id: '0' }, { id: '1' }], |
| 1071 | + hasNext: true, |
| 1072 | + }, |
| 1073 | + done: false, |
| 1074 | + }); |
| 1075 | + |
| 1076 | + resolveC(); |
| 1077 | + |
| 1078 | + expect(cResolverCalled).to.equal(true); |
| 1079 | + expect(eResolverCalled).to.equal(false); |
| 1080 | + |
| 1081 | + const result3 = await iterator.next(); |
| 1082 | + expectJSON(result3).toDeepEqual({ |
| 1083 | + value: { |
| 1084 | + incremental: [ |
| 1085 | + { |
| 1086 | + data: { b: { c: { d: 'd' } } }, |
| 1087 | + id: '2', |
| 1088 | + }, |
| 1089 | + { |
| 1090 | + data: { e: { f: 'f' } }, |
| 1091 | + id: '3', |
| 1092 | + subPath: ['b'], |
| 1093 | + }, |
| 1094 | + ], |
| 1095 | + completed: [{ id: '2' }, { id: '3' }], |
| 1096 | + hasNext: false, |
| 1097 | + }, |
| 1098 | + done: false, |
| 1099 | + }); |
| 1100 | + |
| 1101 | + const result4 = await iterator.next(); |
| 1102 | + expectJSON(result4).toDeepEqual({ |
| 1103 | + value: undefined, |
| 1104 | + done: true, |
| 1105 | + }); |
| 1106 | + }); |
| 1107 | + |
859 | 1108 | it('Can deduplicate multiple defers on the same object', async () => {
|
860 | 1109 | const document = parse(`
|
861 | 1110 | query {
|
|
0 commit comments