mirror of
https://github.com/bitsocialnet/5chan.git
synced 2026-08-03 07:41:04 +02:00
fix(account-data-editor): load Ace before esm-resolver
This commit is contained in:
@@ -58,6 +58,16 @@ vi.mock('@bitsocialnet/bitsocial-react-hooks', () => ({
|
|||||||
|
|
||||||
vi.mock('react-ace', async () => {
|
vi.mock('react-ace', async () => {
|
||||||
const ReactModule = await vi.importActual<typeof import('react')>('react');
|
const ReactModule = await vi.importActual<typeof import('react')>('react');
|
||||||
|
await Promise.resolve();
|
||||||
|
(
|
||||||
|
globalThis as typeof globalThis & {
|
||||||
|
ace?: { config?: { setModuleUrl?: ReturnType<typeof vi.fn> } };
|
||||||
|
}
|
||||||
|
).ace = {
|
||||||
|
config: {
|
||||||
|
setModuleUrl: vi.fn(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
default: ({ value, onChange }: { value: string; onChange: (nextValue: string) => void }) =>
|
default: ({ value, onChange }: { value: string; onChange: (nextValue: string) => void }) =>
|
||||||
@@ -71,7 +81,13 @@ vi.mock('react-ace', async () => {
|
|||||||
|
|
||||||
vi.mock('ace-builds/src-noconflict/mode-json', () => ({}));
|
vi.mock('ace-builds/src-noconflict/mode-json', () => ({}));
|
||||||
vi.mock('ace-builds/src-noconflict/theme-monokai', () => ({}));
|
vi.mock('ace-builds/src-noconflict/theme-monokai', () => ({}));
|
||||||
vi.mock('ace-builds/esm-resolver', () => ({}));
|
vi.mock('ace-builds/esm-resolver', () => {
|
||||||
|
if (!(globalThis as typeof globalThis & { ace?: unknown }).ace) {
|
||||||
|
throw new Error('ace is not defined');
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
});
|
||||||
vi.mock('ace-builds/src-noconflict/worker-json?url', () => ({ default: '/worker-json.js' }));
|
vi.mock('ace-builds/src-noconflict/worker-json?url', () => ({ default: '/worker-json.js' }));
|
||||||
|
|
||||||
let root: Root;
|
let root: Root;
|
||||||
@@ -135,6 +151,7 @@ describe('AccountDataEditor', () => {
|
|||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
vi.resetModules();
|
vi.resetModules();
|
||||||
vi.clearAllMocks();
|
vi.clearAllMocks();
|
||||||
|
delete (globalThis as typeof globalThis & { ace?: unknown }).ace;
|
||||||
AccountDataEditor = (await import('../account-data-editor')).default;
|
AccountDataEditor = (await import('../account-data-editor')).default;
|
||||||
testState.account = { id: 'test-id', name: 'Account 1', author: { address: '0x123', shortAddress: '0x1...3' } };
|
testState.account = { id: 'test-id', name: 'Account 1', author: { address: '0x123', shortAddress: '0x1...3' } };
|
||||||
testState.alertMock.mockReset();
|
testState.alertMock.mockReset();
|
||||||
@@ -183,7 +200,8 @@ describe('AccountDataEditor', () => {
|
|||||||
await waitForEditor();
|
await waitForEditor();
|
||||||
|
|
||||||
expect(container.textContent).not.toContain('loading_editor');
|
expect(container.textContent).not.toContain('loading_editor');
|
||||||
expect(queryEditor()).toBeTruthy();
|
expect(container.textContent).not.toContain('editor_fallback_warning');
|
||||||
|
expect(container.querySelector('[data-testid="ace-editor"]')).toBeTruthy();
|
||||||
|
|
||||||
await clickButton('return_to_settings');
|
await clickButton('return_to_settings');
|
||||||
|
|
||||||
|
|||||||
@@ -12,15 +12,25 @@ type AceModuleLoadResult = {
|
|||||||
onBeforeLoad: (ace: { config?: { setModuleUrl?: (name: string, value: string) => void } }) => void;
|
onBeforeLoad: (ace: { config?: { setModuleUrl?: (name: string, value: string) => void } }) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type EditorPhase = 'warning' | 'loading' | 'editor' | 'fallback';
|
||||||
|
|
||||||
|
type EditorState = {
|
||||||
|
phase: EditorPhase;
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
AceEditor: React.ComponentType<any> | null;
|
||||||
|
aceOnBeforeLoad: AceModuleLoadResult['onBeforeLoad'] | undefined;
|
||||||
|
text: string;
|
||||||
|
};
|
||||||
|
|
||||||
const loadAce = async () => {
|
const loadAce = async () => {
|
||||||
const [aceModule, workerJsonModule] = await Promise.all([
|
const aceModule = await import('react-ace');
|
||||||
import('react-ace'),
|
const [workerJsonModule] = await Promise.all([
|
||||||
import('ace-builds/src-noconflict/worker-json?url'),
|
import('ace-builds/src-noconflict/worker-json?url'),
|
||||||
import('ace-builds/esm-resolver'),
|
import('ace-builds/esm-resolver'),
|
||||||
import('ace-builds/src-noconflict/mode-json'),
|
import('ace-builds/src-noconflict/mode-json'),
|
||||||
import('ace-builds/src-noconflict/theme-monokai'),
|
import('ace-builds/src-noconflict/theme-monokai'),
|
||||||
]);
|
]);
|
||||||
// Vite CJS interop can double-wrap the default export
|
// Load react-ace first so esm-resolver sees the global ace instance.
|
||||||
const mod = aceModule.default;
|
const mod = aceModule.default;
|
||||||
const Editor = typeof mod === 'function' ? mod : (mod as unknown as { default: typeof mod }).default;
|
const Editor = typeof mod === 'function' ? mod : (mod as unknown as { default: typeof mod }).default;
|
||||||
|
|
||||||
@@ -39,31 +49,37 @@ const AccountDataEditor = () => {
|
|||||||
const account = useAccount();
|
const account = useAccount();
|
||||||
const returnTo = (location.state as { returnTo?: string } | null)?.returnTo ?? DEFAULT_RETURN_TO;
|
const returnTo = (location.state as { returnTo?: string } | null)?.returnTo ?? DEFAULT_RETURN_TO;
|
||||||
|
|
||||||
const [phase, setPhase] = useState<'warning' | 'loading' | 'editor' | 'fallback'>('warning');
|
const [{ phase, AceEditor, aceOnBeforeLoad, text }, setEditorState] = useState<EditorState>({
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
phase: 'warning',
|
||||||
const [AceEditor, setAceEditor] = useState<React.ComponentType<any> | null>(null);
|
AceEditor: null,
|
||||||
const [aceOnBeforeLoad, setAceOnBeforeLoad] = useState<AceModuleLoadResult['onBeforeLoad'] | undefined>(undefined);
|
aceOnBeforeLoad: undefined,
|
||||||
const [text, setText] = useState('');
|
text: '',
|
||||||
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (phase !== 'loading') return;
|
if (phase !== 'loading') return;
|
||||||
loadAce()
|
loadAce()
|
||||||
.then(({ Editor, onBeforeLoad }) => {
|
.then(({ Editor, onBeforeLoad }) => {
|
||||||
setAceEditor(() => Editor);
|
setEditorState({
|
||||||
setAceOnBeforeLoad(() => onBeforeLoad);
|
phase: 'editor',
|
||||||
setText(buildEditableAccountJson(account));
|
AceEditor: Editor,
|
||||||
setPhase('editor');
|
aceOnBeforeLoad: onBeforeLoad,
|
||||||
|
text: buildEditableAccountJson(account),
|
||||||
|
});
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
setAceOnBeforeLoad(undefined);
|
setEditorState({
|
||||||
setText(buildEditableAccountJson(account));
|
phase: 'fallback',
|
||||||
setPhase('fallback');
|
AceEditor: null,
|
||||||
|
aceOnBeforeLoad: undefined,
|
||||||
|
text: buildEditableAccountJson(account),
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}, [phase, account]);
|
}, [phase, account]);
|
||||||
|
|
||||||
const handleGoBack = () => navigate(returnTo);
|
const handleGoBack = () => navigate(returnTo);
|
||||||
const handleContinue = () => setPhase('loading');
|
const handleContinue = () => setEditorState((current) => ({ ...current, phase: 'loading' }));
|
||||||
const handleReset = () => setText(buildEditableAccountJson(account));
|
const handleReset = () => setEditorState((current) => ({ ...current, text: buildEditableAccountJson(account) }));
|
||||||
const handleReturn = () => navigate(returnTo);
|
const handleReturn = () => navigate(returnTo);
|
||||||
|
|
||||||
const handleSave = async () => {
|
const handleSave = async () => {
|
||||||
@@ -122,13 +138,13 @@ const AccountDataEditor = () => {
|
|||||||
fontSize={13}
|
fontSize={13}
|
||||||
showPrintMargin={false}
|
showPrintMargin={false}
|
||||||
value={text}
|
value={text}
|
||||||
onChange={setText}
|
onChange={(nextText: string) => setEditorState((current) => ({ ...current, text: nextText }))}
|
||||||
onBeforeLoad={aceOnBeforeLoad}
|
onBeforeLoad={aceOnBeforeLoad}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<textarea
|
<textarea
|
||||||
value={text}
|
value={text}
|
||||||
onChange={(e) => setText(e.target.value)}
|
onChange={(e) => setEditorState((current) => ({ ...current, text: e.target.value }))}
|
||||||
style={{ width: '100%', height: '500px', fontFamily: 'monospace', fontSize: 13 }}
|
style={{ width: '100%', height: '500px', fontFamily: 'monospace', fontSize: 13 }}
|
||||||
spellCheck={false}
|
spellCheck={false}
|
||||||
/>
|
/>
|
||||||
|
|||||||
Reference in New Issue
Block a user