Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions packages/react/src/SuperDocEditor.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
expect((wrapper as HTMLElement)?.style.backgroundColor).toBe('red');
});

it('should handle unmount without throwing', async () => {

Check failure on line 39 in packages/react/src/SuperDocEditor.test.tsx

View workflow job for this annotation

GitHub Actions / validate

src/SuperDocEditor.test.tsx > SuperDocEditor > mounting and unmounting > should handle unmount without throwing

Error: Test timed out in 5000ms. If this is a long-running test, pass a timeout value as the last argument or configure it globally with "testTimeout". ❯ src/SuperDocEditor.test.tsx:39:5
const onReady = vi.fn();
const { unmount } = render(<SuperDocEditor onReady={onReady} />);

Expand Down Expand Up @@ -117,6 +117,46 @@
{ timeout: 5000 },
);
});

it('should route onTransaction through the latest callback after rerender', async () => {
const ref = createRef<SuperDocRef>();
const onReady = vi.fn();
const firstOnTransaction = vi.fn();
const secondOnTransaction = vi.fn();

const { rerender } = render(<SuperDocEditor ref={ref} onReady={onReady} onTransaction={firstOnTransaction} />);

await waitFor(() => expect(onReady).toHaveBeenCalled(), { timeout: 5000 });

const instance = ref.current?.getInstance();
expect(instance).toBeTruthy();

const transactionEvent = {
editor: {},
sourceEditor: {},
transaction: { docChanged: true },
surface: 'body',
};

const firstCallCountBeforeManualDispatch = firstOnTransaction.mock.calls.length;
(instance as any).config.onTransaction(transactionEvent);

expect(firstOnTransaction).toHaveBeenLastCalledWith(transactionEvent);
expect(firstOnTransaction).toHaveBeenCalledTimes(firstCallCountBeforeManualDispatch + 1);
expect(secondOnTransaction).not.toHaveBeenCalled();

rerender(<SuperDocEditor ref={ref} onReady={onReady} onTransaction={secondOnTransaction} />);

expect(ref.current?.getInstance()).toBe(instance);

const firstCallCountBeforeRerenderDispatch = firstOnTransaction.mock.calls.length;
const secondCallCountBeforeManualDispatch = secondOnTransaction.mock.calls.length;
(instance as any).config.onTransaction(transactionEvent);

expect(firstOnTransaction).toHaveBeenCalledTimes(firstCallCountBeforeRerenderDispatch);
expect(secondOnTransaction).toHaveBeenLastCalledWith(transactionEvent);
expect(secondOnTransaction).toHaveBeenCalledTimes(secondCallCountBeforeManualDispatch + 1);
});
});

describe('onEditorDestroy', () => {
Expand Down
11 changes: 10 additions & 1 deletion packages/react/src/SuperDocEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import type {
SuperDocReadyEvent,
SuperDocEditorCreateEvent,
SuperDocEditorUpdateEvent,
SuperDocTransactionEvent,
SuperDocContentErrorEvent,
SuperDocExceptionEvent,
} from './types';
Expand Down Expand Up @@ -46,6 +47,7 @@ function SuperDocEditorInner(props: SuperDocEditorProps, ref: ForwardedRef<Super
onEditorCreate,
onEditorDestroy,
onEditorUpdate,
onTransaction,
onContentError,
onException,
// Key props that trigger rebuild when changed
Expand Down Expand Up @@ -85,6 +87,7 @@ function SuperDocEditorInner(props: SuperDocEditorProps, ref: ForwardedRef<Super
onEditorCreate,
onEditorDestroy,
onEditorUpdate,
onTransaction,
onContentError,
onException,
});
Expand All @@ -96,10 +99,11 @@ function SuperDocEditorInner(props: SuperDocEditorProps, ref: ForwardedRef<Super
onEditorCreate,
onEditorDestroy,
onEditorUpdate,
onTransaction,
onContentError,
onException,
};
}, [onReady, onEditorCreate, onEditorDestroy, onEditorUpdate, onContentError, onException]);
}, [onReady, onEditorCreate, onEditorDestroy, onEditorUpdate, onTransaction, onContentError, onException]);

// Queue mode changes that happen during init
const pendingModeRef = useRef<DocumentMode | null>(null);
Expand Down Expand Up @@ -192,6 +196,11 @@ function SuperDocEditorInner(props: SuperDocEditorProps, ref: ForwardedRef<Super
callbacksRef.current.onEditorUpdate?.(event);
}
},
onTransaction: (event: SuperDocTransactionEvent) => {
if (!destroyed) {
callbacksRef.current.onTransaction?.(event);
}
},
onContentError: (event: SuperDocContentErrorEvent) => {
if (!destroyed) {
callbacksRef.current.onContentError?.(event);
Expand Down
1 change: 1 addition & 0 deletions packages/react/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export type {
SuperDocReadyEvent,
SuperDocEditorCreateEvent,
SuperDocEditorUpdateEvent,
SuperDocTransactionEvent,
SuperDocContentErrorEvent,
SuperDocExceptionEvent,
} from './types';
22 changes: 22 additions & 0 deletions packages/react/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,24 @@
sectionType?: string | null;
}

/** Event passed to onTransaction callback. Mirrors superdoc's EditorTransactionEvent. */
export interface SuperDocTransactionEvent {
/** The primary editor associated with the transaction. For header/footer edits, this is the main body editor. */
editor: Editor;
/** The editor instance that emitted the transaction. For body edits, this matches `editor`. */
sourceEditor: Editor;
/** The ProseMirror transaction or transaction-like payload emitted by the source editor. */
transaction: any;

Check warning on line 77 in packages/react/src/types.ts

View workflow job for this annotation

GitHub Actions / validate

Unexpected any. Specify a different type
/** Time spent applying the transaction, in milliseconds. */
duration?: number;
/** The surface where the transaction originated. */
surface: EditorSurface;
/** Relationship ID for header/footer edits. */
headerId?: string | null;
/** Header/footer variant (`default`, `first`, `even`, `odd`) when available. */
sectionType?: string | null;
}

/** Event passed to onContentError callback */
export interface SuperDocContentErrorEvent {
error: Error;
Expand Down Expand Up @@ -107,6 +125,7 @@
| 'onEditorCreate'
| 'onEditorDestroy'
| 'onEditorUpdate'
| 'onTransaction'
| 'onContentError'
| 'onException';

Expand All @@ -127,6 +146,9 @@
/** Callback when document content is updated */
onEditorUpdate?: (event: SuperDocEditorUpdateEvent) => void;

/** Callback when a transaction is emitted */
onTransaction?: (event: SuperDocTransactionEvent) => void;

/** Callback when there is a content parsing error */
onContentError?: (event: SuperDocContentErrorEvent) => void;

Expand Down
Loading