Skip to content

Commit 27e7eb6

Browse files
authored
graphiql 5: allow multiple independant instances on the same page (#3990)
* upd * upd * upd * default query * persist headers * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * reduce rerenders * add logs * try * cleanup * cspell * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * fixes * fixes * fixes * fixes * fixes * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * prettier * upd * move plugins check to store * upd * upd * refactor * refactor * upd * upd * upd * upd * upd * upd * lint fix * all in one * yarn lokc * fix * yarn.lock * update babel.config.js * fix tests * update examples * yarn.lock * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * pick * provider * autocomplete leafs * editors * query editor * upd * upd * upd * useSynchronizeValue * synchronizeActiveTabValues * synchronizeActiveTabValues and setEditorValues * storeTabs * actions * setOperationFacts * cleanup * move editor logic to provider * add InnerGraphiQLProvider * move editor logic * move more * move schema * refactor plugins and graphiql * upd * upd * swap isIntrospecting and isFetching * cleanupDisposables * lint * add test-d typecheck test * gitignore * run yarn * fix graphiql build * use cn * fix history tests * fix tests * types check pass * run ayrn * run ayrn * tests pass * upd * Delete .changeset/five-cars-roll.md * Update packages/graphiql-plugin-doc-explorer/src/components/__tests__/doc-explorer.spec.tsx * fix run at cursor action * add run action for response editor * fix copy query action * copy query * fix prettify editors action * fix merge query action * polish * move `getAutoCompleteLeafs` * formatShortcutForOS * formatShortcutForOS * formatShortcutForOS * remove on click reference from variable editor * store on click reference in ref * polish * add changeset and allow override all default GraphiQL plugins * upd * better autocomplete for editor theme * try fix tests * merge conflicts and fix ClipboardItem is not defined * fix vitest and e2e * Update .changeset/beige-months-care.md * upd setup-files * upd monaco-editor mocks * fix lint * should works now! * polish * polish * polish * build pass on next 15.4 * remove unneeded actions * merge conflicts * Update .changeset/beige-months-care.md * Update .changeset/beige-months-care.md * rm next.js example because netlify fails
1 parent 2403c68 commit 27e7eb6

File tree

79 files changed

+1742
-1834
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

79 files changed

+1742
-1834
lines changed

.changeset/beige-months-care.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
---
2+
'@graphiql/plugin-doc-explorer': minor
3+
'@graphiql/plugin-explorer': major
4+
'@graphiql/plugin-history': minor
5+
'@graphiql/react': minor
6+
'graphiql': major
7+
---
8+
9+
- allow multiple independent instances of GraphiQL on the same page
10+
- store `onClickReference` in query editor in React `ref`
11+
- remove `onClickReference` from variable editor
12+
- fix shortcut text per OS for run query in execute query button's tooltip and in default query
13+
- allow override all default GraphiQL plugins
14+
- adjust operation argument color to be purple from GraphiQL v2 on dark/light theme

.eslintrc.js

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,11 @@ module.exports = {
3232
'functions/*',
3333
'packages/vscode-graphql-syntax/tests/__fixtures__/*',
3434
// symlinks
35+
'packages/graphiql-react/__mocks__/monaco-editor.ts',
3536
'packages/graphiql-plugin-doc-explorer/__mocks__/zustand.ts',
37+
'packages/graphiql-plugin-doc-explorer/__mocks__/monaco-editor.ts',
3638
'packages/graphiql-plugin-history/__mocks__/zustand.ts',
39+
'packages/graphiql-plugin-history/__mocks__/monaco-editor.ts',
3740
],
3841
overrides: [
3942
{
@@ -147,6 +150,10 @@ module.exports = {
147150
property: 'getComputedStyle',
148151
message: 'Use `getComputedStyle` instead',
149152
},
153+
{
154+
object: 'self',
155+
message: 'Use `globalThis` instead',
156+
},
150157
],
151158
'no-return-assign': 'error',
152159
'no-return-await': 'error',
@@ -367,6 +374,7 @@ module.exports = {
367374
'unicorn/no-length-as-slice-end': 'error',
368375
'unicorn/prefer-string-replace-all': 'error',
369376
'unicorn/prefer-array-some': 'error',
377+
// '@typescript-eslint/prefer-for-of': 'error', TODO
370378
'unicorn/no-hex-escape': 'off', // TODO: enable
371379
// doesn't catch a lot of cases; we use ESLint builtin `no-restricted-syntax` to forbid `.keyCode`
372380
'unicorn/prefer-keyboard-event-key': 'off',
@@ -379,6 +387,13 @@ module.exports = {
379387
'import-x/no-named-as-default-member': 'off',
380388
},
381389
},
390+
{
391+
files: ['packages/{monaco-graphql,graphiql*}/**/*.{ts,tsx,mts,cts}'],
392+
excludedFiles: ['packages/graphiql-toolkit/**/*.{ts,tsx}'],
393+
rules: {
394+
'@typescript-eslint/no-unnecessary-condition': 'error',
395+
},
396+
},
382397
{
383398
// Rules that requires type information
384399
files: ['**/*.{ts,tsx,mts,cts}'],
@@ -515,9 +530,7 @@ module.exports = {
515530
rules: {
516531
'@typescript-eslint/no-restricted-imports': [
517532
'error',
518-
...RESTRICTED_IMPORTS
519-
// TODO: enable when monaco-editor will be migrated over codemirror
520-
.filter(({ name }) => name !== 'monaco-editor'),
533+
...RESTRICTED_IMPORTS,
521534
{
522535
name: 'react',
523536
importNames: ['memo', 'useCallback', 'useMemo'],
@@ -572,6 +585,7 @@ module.exports = {
572585
'react-hooks/rules-of-hooks': 'off',
573586
'sonarjs/no-dead-store': 'off',
574587
'@typescript-eslint/no-restricted-imports': 'off',
588+
'@typescript-eslint/no-unnecessary-condition': 'off',
575589
},
576590
},
577591
],

examples/graphiql-nextjs/package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,14 @@
44
"private": true,
55
"scripts": {
66
"types:check": "tsc --noEmit",
7-
"dev": "next --turbopack",
8-
"build": "next build --turbopack",
7+
"dev": "next",
8+
"build": "next build",
99
"start": "next start",
1010
"lint": "next lint"
1111
},
1212
"dependencies": {
1313
"graphiql": "^5.0.0-rc.0",
14-
"next": "15.3.3",
14+
"next": "15.4.0",
1515
"react": "^19.1.0",
1616
"react-dom": "^19.1.0"
1717
},

examples/monaco-graphql-nextjs/package.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,17 @@
55
"type": "module",
66
"scripts": {
77
"types:check": "tsc --noEmit",
8-
"dev": "next --turbopack",
9-
"build": "next build --turbopack",
8+
"dev": "next",
9+
"build": "next build",
1010
"start": "next start"
1111
},
1212
"dependencies": {
1313
"@graphiql/toolkit": "^0.11.3",
1414
"graphql": "^16.9.0",
1515
"jsonc-parser": "^3.2.0",
16-
"monaco-editor": "^0.39.0",
16+
"monaco-editor": "^0.52.2",
1717
"monaco-graphql": "^1.7.0",
18-
"next": "15.3.3",
18+
"next": "15.4.0",
1919
"react": "^19.1.0",
2020
"react-dom": "^19.1.0"
2121
},

examples/monaco-graphql-webpack/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
"graphql-language-service": "^5.4.0",
1515
"json-schema": "^0.4.0",
1616
"jsonc-parser": "^3.2.0",
17-
"monaco-editor": "^0.47.0",
17+
"monaco-editor": "^0.52.2",
1818
"monaco-graphql": "^1.7.0",
1919
"prettier": "3.3.2"
2020
},

package.json

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
"examples/monaco-graphql-nextjs",
1313
"examples/monaco-graphql-react-vite",
1414
"examples/graphiql-vite",
15-
"examples/graphiql-nextjs",
1615
"examples/graphiql-webpack"
1716
]
1817
},
@@ -128,7 +127,6 @@
128127
"wsrun": "^5.2.4"
129128
},
130129
"resolutions": {
131-
"monaco-editor": "0.47.0",
132130
"@babel/traverse": "^7.23.2",
133131
"vscode-languageserver-types": "3.17.3",
134132
"markdown-it": "14.1.0",
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../graphiql/__mocks__/monaco-editor.ts

packages/graphiql-plugin-doc-explorer/setup-files.ts

Lines changed: 3 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -2,36 +2,6 @@
22

33
import '@testing-library/jest-dom';
44

5-
vi.mock('zustand'); // to make it works like Jest (auto-mocking)
6-
7-
/**
8-
* Fixes TypeError: document.queryCommandSupported is not a function
9-
*/
10-
if (!navigator.clipboard) {
11-
Object.defineProperty(navigator, 'clipboard', {
12-
writable: false,
13-
value: {
14-
write: async () => null,
15-
},
16-
});
17-
}
18-
19-
/**
20-
* Fixes TypeError: mainWindow.matchMedia is not a function
21-
* @see https://jestjs.io/docs/manual-mocks#mocking-methods-which-are-not-implemented-in-jsdom
22-
*/
23-
if (!window.matchMedia) {
24-
Object.defineProperty(window, 'matchMedia', {
25-
writable: false,
26-
value: vi.fn().mockImplementation(query => ({
27-
matches: false,
28-
media: query,
29-
onchange: null,
30-
addListener: vi.fn(), // deprecated
31-
removeListener: vi.fn(), // deprecated
32-
addEventListener: vi.fn(),
33-
removeEventListener: vi.fn(),
34-
dispatchEvent: vi.fn(),
35-
})),
36-
});
37-
}
5+
// to make it works like Jest (auto-mocking)
6+
vi.mock('zustand');
7+
vi.mock('monaco-editor');

packages/graphiql-plugin-doc-explorer/src/components/__tests__/doc-explorer.spec.tsx

Lines changed: 48 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
import { act, render } from '@testing-library/react';
1+
import { Mock } from 'vitest';
2+
import { useGraphiQL as $useGraphiQL } from '@graphiql/react';
3+
import { render } from '@testing-library/react';
24
import { GraphQLInt, GraphQLObjectType, GraphQLSchema } from 'graphql';
35
import { FC, useEffect } from 'react';
46
import {
@@ -7,7 +9,17 @@ import {
79
useDocExplorerActions,
810
} from '../../context';
911
import { DocExplorer } from '../doc-explorer';
10-
import { schemaStore } from '../../../../graphiql-react/dist/stores/schema';
12+
13+
const useGraphiQL = $useGraphiQL as Mock;
14+
15+
vi.mock('@graphiql/react', async () => {
16+
const originalModule =
17+
await vi.importActual<typeof import('@graphiql/react')>('@graphiql/react');
18+
return {
19+
...originalModule,
20+
useGraphiQL: vi.fn(),
21+
};
22+
});
1123

1224
function makeSchema(fieldName = 'field') {
1325
return new GraphQLSchema({
@@ -29,15 +41,16 @@ function makeSchema(fieldName = 'field') {
2941
}
3042

3143
const defaultSchemaContext = {
32-
...schemaStore.getInitialState(),
33-
async introspect() {},
44+
fetchError: null,
45+
introspect() {},
46+
isFetching: false,
3447
schema: makeSchema(),
48+
validationErrors: [],
3549
};
3650

3751
const withErrorSchemaContext = {
38-
...schemaStore.getInitialState(),
52+
...defaultSchemaContext,
3953
fetchError: 'Error fetching schema',
40-
async introspect() {},
4154
schema: new GraphQLSchema({ description: 'GraphQL Schema for testing' }),
4255
};
4356

@@ -50,21 +63,29 @@ const DocExplorerWithContext: FC = () => {
5063
};
5164

5265
describe('DocExplorer', () => {
66+
beforeEach(() => {
67+
vi.resetModules();
68+
});
69+
5370
it('renders spinner when the schema is loading', () => {
54-
schemaStore.setState({ isFetching: true });
71+
useGraphiQL.mockImplementation(cb =>
72+
cb({ ...defaultSchemaContext, isFetching: true }),
73+
);
5574
const { container } = render(<DocExplorerWithContext />);
5675
const spinner = container.querySelectorAll('.graphiql-spinner');
5776
expect(spinner).toHaveLength(1);
5877
});
5978
it('renders with null schema', () => {
60-
schemaStore.setState({ ...defaultSchemaContext, schema: null });
79+
useGraphiQL.mockImplementation(cb =>
80+
cb({ ...defaultSchemaContext, schema: null }),
81+
);
6182
const { container } = render(<DocExplorerWithContext />);
6283
const error = container.querySelectorAll('.graphiql-doc-explorer-error');
6384
expect(error).toHaveLength(1);
6485
expect(error[0]).toHaveTextContent('No GraphQL schema available');
6586
});
6687
it('renders with schema', () => {
67-
schemaStore.setState(defaultSchemaContext);
88+
useGraphiQL.mockImplementation(cb => cb(defaultSchemaContext));
6889
const { container } = render(<DocExplorerWithContext />);
6990
const error = container.querySelectorAll('.graphiql-doc-explorer-error');
7091
expect(error).toHaveLength(0);
@@ -73,14 +94,11 @@ describe('DocExplorer', () => {
7394
).toHaveTextContent('GraphQL Schema for testing');
7495
});
7596
it('renders correctly with schema error', () => {
76-
schemaStore.setState(withErrorSchemaContext);
97+
useGraphiQL.mockImplementation(cb => cb(withErrorSchemaContext));
7798
const { rerender, container } = render(<DocExplorerWithContext />);
7899
const error = container.querySelector('.graphiql-doc-explorer-error');
79100
expect(error).toHaveTextContent('Error fetching schema');
80-
81-
act(() => {
82-
schemaStore.setState(defaultSchemaContext);
83-
});
101+
useGraphiQL.mockImplementation(cb => cb(defaultSchemaContext));
84102
rerender(<DocExplorerWithContext />);
85103
const errors = container.querySelectorAll('.graphiql-doc-explorer-error');
86104
expect(errors).toHaveLength(0);
@@ -104,45 +122,38 @@ describe('DocExplorer', () => {
104122
};
105123

106124
// Initial render, set initial state
107-
schemaStore.setState({
108-
...defaultSchemaContext,
109-
schema: initialSchema,
110-
});
125+
useGraphiQL.mockImplementation(cb =>
126+
cb({ ...defaultSchemaContext, schema: initialSchema }),
127+
);
111128
const { container, rerender } = render(
112129
<DocExplorerStore>
113130
<SetInitialStack />
114131
</DocExplorerStore>,
115132
);
116133

117134
// First proper render of doc explorer
118-
act(() => {
119-
schemaStore.setState({
120-
...defaultSchemaContext,
121-
schema: initialSchema,
122-
});
123-
});
124135
rerender(
125136
<DocExplorerStore>
126137
<DocExplorer />
127138
</DocExplorerStore>,
128139
);
129140

130-
const [title] = container.querySelectorAll('.graphiql-doc-explorer-title');
141+
const title = container.querySelector('.graphiql-doc-explorer-title')!;
131142
expect(title.textContent).toEqual('field');
132143

133144
// Second render of doc explorer, this time with a new schema, with _same_ field name
134-
act(() => {
135-
schemaStore.setState({
145+
useGraphiQL.mockImplementation(cb =>
146+
cb({
136147
...defaultSchemaContext,
137148
schema: makeSchema(), // <<< New, but equivalent, schema
138-
});
139-
});
149+
}),
150+
);
140151
rerender(
141152
<DocExplorerStore>
142153
<DocExplorer />
143154
</DocExplorerStore>,
144155
);
145-
const [title2] = container.querySelectorAll('.graphiql-doc-explorer-title');
156+
const title2 = container.querySelector('.graphiql-doc-explorer-title')!;
146157
// Because `Query.field` still exists in the new schema, we can still render it
147158
expect(title2.textContent).toEqual('field');
148159
});
@@ -166,23 +177,16 @@ describe('DocExplorer', () => {
166177
};
167178

168179
// Initial render, set initial state
169-
schemaStore.setState({
170-
...defaultSchemaContext,
171-
schema: initialSchema,
172-
});
180+
useGraphiQL.mockImplementation(cb =>
181+
cb({ ...defaultSchemaContext, schema: initialSchema }),
182+
);
173183
const { container, rerender } = render(
174184
<DocExplorerStore>
175185
<SetInitialStack />
176186
</DocExplorerStore>,
177187
);
178188

179189
// First proper render of doc explorer
180-
act(() => {
181-
schemaStore.setState({
182-
...defaultSchemaContext,
183-
schema: initialSchema,
184-
});
185-
});
186190
rerender(
187191
<DocExplorerStore>
188192
<DocExplorer />
@@ -193,12 +197,12 @@ describe('DocExplorer', () => {
193197
expect(title.textContent).toEqual('field');
194198

195199
// Second render of doc explorer, this time with a new schema, with a different field name
196-
act(() => {
197-
schemaStore.setState({
200+
useGraphiQL.mockImplementation(cb =>
201+
cb({
198202
...defaultSchemaContext,
199203
schema: makeSchema('field2'), // <<< New schema with a new field name
200-
});
201-
});
204+
}),
205+
);
202206
rerender(
203207
<DocExplorerStore>
204208
<DocExplorer />

0 commit comments

Comments
 (0)