diff --git a/e2e/next-app/app/editorProbe.tsx b/e2e/next-app/app/editorProbe.tsx index 3936c86..3d9268d 100644 --- a/e2e/next-app/app/editorProbe.tsx +++ b/e2e/next-app/app/editorProbe.tsx @@ -2,60 +2,7 @@ import { useState } from 'react'; -import MonacoEditor, { DiffEditor, loader, useMonaco, type Monaco } from '@willbooster/monaco-react'; - -type LoaderConfig = Parameters[0]; - -const model = { - uri: { path: '/e2e.ts' }, - dispose: () => {}, - getFullModelRange: () => ({}), -}; - -const codeEditor = { - dispose: () => {}, - executeEdits: () => {}, - getModel: () => model, - getOption: () => false, - getValue: () => 'const answer = 42;', - onDidChangeModelContent: () => ({ dispose: () => {} }), - pushUndoStop: () => {}, - restoreViewState: () => {}, - revealLine: () => {}, - saveViewState: () => ({}), - setModel: () => {}, - updateOptions: () => {}, -}; - -const diffEditor = { - dispose: () => {}, - getModel: () => ({ original: model, modified: model }), - getModifiedEditor: () => codeEditor, - getOriginalEditor: () => codeEditor, - setModel: () => {}, - updateOptions: () => {}, -}; - -const monaco = { - editor: { - create: () => codeEditor, - createDiffEditor: () => diffEditor, - createModel: () => model, - EditorOption: { - readOnly: 'readOnly', - }, - getModel: () => {}, - getModelMarkers: () => [], - onDidChangeMarkers: () => ({ dispose: () => {} }), - setModelLanguage: () => {}, - setTheme: () => {}, - }, - Uri: { - parse: (path: string) => ({ path }), - }, -} as unknown as Monaco; - -loader.config({ monaco: monaco as LoaderConfig['monaco'] }); +import MonacoEditor, { DiffEditor, useMonaco } from '@willbooster/monaco-react'; export default function EditorProbe() { const [editorStatus, setEditorStatus] = useState('editor-pending'); @@ -64,7 +11,7 @@ export default function EditorProbe() { return ( <> -

{loadedMonaco === monaco ? 'hook-ok' : 'hook-pending'}

+

{loadedMonaco?.editor ? 'hook-ok' : 'hook-pending'}

{editorStatus}
{ void editor; - setEditorStatus(mountedMonaco === monaco ? 'editor-ok' : 'editor-mismatch'); + setEditorStatus(mountedMonaco.editor ? 'editor-ok' : 'editor-mismatch'); }} />
{diffStatus}
@@ -83,7 +30,7 @@ export default function EditorProbe() { language="typescript" onMount={(editor, mountedMonaco) => { void editor; - setDiffStatus(mountedMonaco === monaco ? 'diff-ok' : 'diff-mismatch'); + setDiffStatus(mountedMonaco.editor ? 'diff-ok' : 'diff-mismatch'); }} /> diff --git a/e2e/next-app/next-env.d.ts b/e2e/next-app/next-env.d.ts index 9edff1c..2d5420e 100644 --- a/e2e/next-app/next-env.d.ts +++ b/e2e/next-app/next-env.d.ts @@ -1,5 +1,6 @@ /// /// +/// import "./.next/types/routes.d.ts"; // NOTE: This file should not be edited diff --git a/e2e/next-app/next.e2e.ts b/e2e/next-app/next.e2e.ts index 9479e3f..819eb6e 100644 --- a/e2e/next-app/next.e2e.ts +++ b/e2e/next-app/next.e2e.ts @@ -43,3 +43,30 @@ test('loads monaco-react through the Next.js app router', async ({ page }) => { await expect(page.getByTestId('diff-status')).toHaveText('diff-ok'); expect(errors).toEqual([]); }); + +test('keeps Monaco stylesheet after Next.js Head changes', async ({ page }) => { + const errors: string[] = []; + page.on('console', (message) => { + if (message.type() === 'error') { + errors.push(message.text()); + } + }); + page.on('pageerror', (error) => errors.push(error.message)); + + await page.goto('/issue272'); + + await expect(page.getByTestId('editor-status')).toHaveText('editor-ok'); + await expect(page.getByTestId('stylesheet-count')).toHaveText('1'); + + const remountButton = page.getByRole('button', { name: 'Remount editor' }); + await remountButton.click(); + await expect(page.getByTestId('head-revision')).toHaveText('1'); + await expect(page.getByTestId('editor-status')).toHaveText('editor-ok'); + await expect(page.getByTestId('stylesheet-count')).toHaveText('1'); + + await remountButton.click(); + await expect(page.getByTestId('head-revision')).toHaveText('2'); + await expect(page.getByTestId('editor-status')).toHaveText('editor-ok'); + await expect(page.getByTestId('stylesheet-count')).toHaveText('1'); + expect(errors).toEqual([]); +}); diff --git a/e2e/next-app/pages/issue272.tsx b/e2e/next-app/pages/issue272.tsx new file mode 100644 index 0000000..7d5626a --- /dev/null +++ b/e2e/next-app/pages/issue272.tsx @@ -0,0 +1,55 @@ +import Head from 'next/head'; +import { useEffect, useState } from 'react'; + +import MonacoEditor from '@willbooster/monaco-react'; + +export default function Issue272Page() { + const [headRevision, setHeadRevision] = useState(0); + const [isEditorVisible, setIsEditorVisible] = useState(true); + const [editorStatus, setEditorStatus] = useState('editor-pending'); + const [stylesheetCount, setStylesheetCount] = useState(0); + const faviconHref = `data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg'%3E%3Ctext%3E${headRevision}%3C/text%3E%3C/svg%3E`; + + useEffect(() => { + setStylesheetCount(countMonacoStylesheets()); + }, [headRevision, isEditorVisible]); + + function remountEditorWithHeadChange() { + setEditorStatus('editor-pending'); + setIsEditorVisible(false); + setHeadRevision((currentRevision) => currentRevision + 1); + requestAnimationFrame(() => setIsEditorVisible(true)); + } + + return ( + <> + + Issue 272 + + +
+ +

{headRevision}

+

{stylesheetCount}

+

{editorStatus}

+ {isEditorVisible && ( + { + setStylesheetCount(countMonacoStylesheets()); + setEditorStatus(monaco.editor ? 'editor-ok' : 'editor-mismatch'); + }} + /> + )} +
+ + ); +} + +function countMonacoStylesheets() { + return document.querySelectorAll('link[rel="stylesheet"][href*="/vs/editor/editor.main.css"]').length; +}