From fc081816e08ade6838d05a96f84088de9f2734ce Mon Sep 17 00:00:00 2001 From: Per-Kristian Nordnes Date: Fri, 10 Nov 2023 17:19:10 +0100 Subject: [PATCH] Fix issue with ReactEditor.focus + tests (#5527) * Fix issue with slate-react static ReactEditor.focus method This will make sure we don't try to focus the editor while it's in the midst of applying operations. If this is the case, retry setting focus in the next tick. * Replace react-test-renderer with @testing-library/react We need to be able to test against window features, like the DOM selection. @testing-library/react has a very similar API, but have also these features, which react-test-renderer is missing. * Rewrite tests for @testing-library/react This will rewrite the existing tests for Editable and move them into a own file. * Add tests for ReactEditor.focus * Add changeset --- .changeset/thick-fishes-dance.md | 7 + packages/slate-react/package.json | 3 +- .../slate-react/src/plugin/react-editor.ts | 33 ++- packages/slate-react/test/editable.spec.tsx | 204 ++++++++++++++ packages/slate-react/test/index.spec.tsx | 201 -------------- .../slate-react/test/react-editor.spec.tsx | 90 +++++++ yarn.lock | 255 ++++++++++++++---- 7 files changed, 541 insertions(+), 252 deletions(-) create mode 100644 .changeset/thick-fishes-dance.md create mode 100644 packages/slate-react/test/editable.spec.tsx delete mode 100644 packages/slate-react/test/index.spec.tsx create mode 100644 packages/slate-react/test/react-editor.spec.tsx diff --git a/.changeset/thick-fishes-dance.md b/.changeset/thick-fishes-dance.md new file mode 100644 index 000000000..a9aa4d92b --- /dev/null +++ b/.changeset/thick-fishes-dance.md @@ -0,0 +1,7 @@ +--- +'slate-react': minor +--- + +Fixes a bug with `ReactEditor.focus` where it would throw an error if the editor was in the middle of applying pending operations. +With this change, setting focus will be retried until the editor no longer has any pending operations. +Calling `ReactEditor.focus` on a editor without a current selection, will now make a selection in the top of the document. diff --git a/packages/slate-react/package.json b/packages/slate-react/package.json index b83d0b3c8..f091eb21b 100644 --- a/packages/slate-react/package.json +++ b/packages/slate-react/package.json @@ -26,15 +26,14 @@ }, "devDependencies": { "@babel/runtime": "^7.23.2", + "@testing-library/react": "^14.0.0", "@types/jest": "29.5.6", "@types/jsdom": "^21.1.4", "@types/react": "^18.2.28", "@types/react-dom": "^18.2.13", - "@types/react-test-renderer": "^18.0.3", "@types/resize-observer-browser": "^0.1.8", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-test-renderer": "^18.2.0", "slate": "^0.100.0", "slate-hyperscript": "^0.100.0", "source-map-loader": "^4.0.1" diff --git a/packages/slate-react/src/plugin/react-editor.ts b/packages/slate-react/src/plugin/react-editor.ts index 6c4d5e817..422b1e816 100644 --- a/packages/slate-react/src/plugin/react-editor.ts +++ b/packages/slate-react/src/plugin/react-editor.ts @@ -117,7 +117,7 @@ export interface ReactEditorInterface { /** * Focus the editor. */ - focus: (editor: ReactEditor) => void + focus: (editor: ReactEditor, options?: { retries: number }) => void /** * Return the host window of the current editor. @@ -411,19 +411,44 @@ export const ReactEditor: ReactEditorInterface = { ) }, - focus: editor => { + focus: (editor, options = { retries: 5 }) => { + // Return if already focused + if (IS_FOCUSED.get(editor)) { + return + } + + // Retry setting focus if the editor has pending operations. + // The DOM (selection) is unstable while changes are applied. + // Retry until retries are exhausted or editor is focused. + if (options.retries <= 0) { + throw new Error( + 'Could not set focus, editor seems stuck with pending operations' + ) + } + if (editor.operations.length > 0) { + setTimeout(() => { + ReactEditor.focus(editor, { retries: options.retries - 1 }) + }, 10) + return + } + const el = ReactEditor.toDOMNode(editor, editor) const root = ReactEditor.findDocumentOrShadowRoot(editor) - IS_FOCUSED.set(editor, true) - if (root.activeElement !== el) { + // Ensure that the DOM selection state is set to the editor's selection if (editor.selection && root instanceof Document) { const domSelection = root.getSelection() const domRange = ReactEditor.toDOMRange(editor, editor.selection) domSelection?.removeAllRanges() domSelection?.addRange(domRange) } + // Create a new selection in the top of the document if missing + if (!editor.selection) { + Transforms.select(editor, Editor.start(editor, [])) + editor.onChange() + } el.focus({ preventScroll: true }) + IS_FOCUSED.set(editor, true) } }, diff --git a/packages/slate-react/test/editable.spec.tsx b/packages/slate-react/test/editable.spec.tsx new file mode 100644 index 000000000..d907cc1e5 --- /dev/null +++ b/packages/slate-react/test/editable.spec.tsx @@ -0,0 +1,204 @@ +import React, { useEffect } from 'react' +import { createEditor, Text, Transforms } from 'slate' +import { act, render } from '@testing-library/react' +import { Slate, withReact, Editable } from '../src' + +describe('slate-react', () => { + describe('Editable', () => { + describe('NODE_TO_KEY logic', () => { + test('should not unmount the node that gets split on a split_node operation', async () => { + const editor = withReact(createEditor()) + const initialValue = [{ type: 'block', children: [{ text: 'test' }] }] + const mounts = jest.fn() + + act(() => { + render( + {}} + > + { + useEffect(() => mounts(), []) + + return children + }} + /> + + ) + }) + + // slate updates at next tick, so we need this to be async + await act(async () => + Transforms.splitNodes(editor, { at: { path: [0, 0], offset: 2 } }) + ) + + // 2 renders, one for the main element and one for the split element + expect(mounts).toHaveBeenCalledTimes(2) + }) + + test('should not unmount the node that gets merged into on a merge_node operation', async () => { + const editor = withReact(createEditor()) + const initialValue = [ + { type: 'block', children: [{ text: 'te' }] }, + { type: 'block', children: [{ text: 'st' }] }, + ] + const mounts = jest.fn() + + act(() => { + render( + {}} + > + { + useEffect(() => mounts(), []) + + return children + }} + /> + + ) + }) + + // slate updates at next tick, so we need this to be async + await act(async () => + Transforms.mergeNodes(editor, { at: { path: [0, 0], offset: 0 } }) + ) + + // only 2 renders for the initial render + expect(mounts).toHaveBeenCalledTimes(2) + }) + }) + test('calls onSelectionChange when editor select change', async () => { + const editor = withReact(createEditor()) + const initialValue = [ + { type: 'block', children: [{ text: 'te' }] }, + { type: 'block', children: [{ text: 'st' }] }, + ] + const onChange = jest.fn() + const onValueChange = jest.fn() + const onSelectionChange = jest.fn() + + act(() => { + render( + + + + ) + }) + + await act(async () => + Transforms.select(editor, { path: [0, 0], offset: 2 }) + ) + + expect(onSelectionChange).toHaveBeenCalled() + expect(onChange).toHaveBeenCalled() + expect(onValueChange).not.toHaveBeenCalled() + }) + + test('calls onValueChange when editor children change', async () => { + const editor = withReact(createEditor()) + const initialValue = [{ type: 'block', children: [{ text: 'test' }] }] + const onChange = jest.fn() + const onValueChange = jest.fn() + const onSelectionChange = jest.fn() + + act(() => { + render( + + + + ) + }) + + await act(async () => Transforms.insertText(editor, 'Hello word!')) + + expect(onValueChange).toHaveBeenCalled() + expect(onChange).toHaveBeenCalled() + expect(onSelectionChange).not.toHaveBeenCalled() + }) + + test('calls onValueChange when editor setNodes', async () => { + const editor = withReact(createEditor()) + const initialValue = [{ type: 'block', children: [{ text: 'test' }] }] + const onChange = jest.fn() + const onValueChange = jest.fn() + const onSelectionChange = jest.fn() + + act(() => { + render( + + + + ) + }) + + await act(async () => + Transforms.setNodes( + editor, + // @ts-ignore + { bold: true }, + { + at: { path: [0, 0], offset: 2 }, + match: Text.isText, + split: true, + } + ) + ) + + expect(onChange).toHaveBeenCalled() + expect(onValueChange).toHaveBeenCalled() + expect(onSelectionChange).not.toHaveBeenCalled() + }) + + test('calls onValueChange when editor children change', async () => { + const editor = withReact(createEditor()) + const initialValue = [{ type: 'block', children: [{ text: 'test' }] }] + const onChange = jest.fn() + const onValueChange = jest.fn() + const onSelectionChange = jest.fn() + + act(() => { + render( + + + + ) + }) + + await act(async () => Transforms.insertText(editor, 'Hello word!')) + + expect(onValueChange).toHaveBeenCalled() + expect(onChange).toHaveBeenCalled() + expect(onSelectionChange).not.toHaveBeenCalled() + }) + }) +}) diff --git a/packages/slate-react/test/index.spec.tsx b/packages/slate-react/test/index.spec.tsx deleted file mode 100644 index 47de4135b..000000000 --- a/packages/slate-react/test/index.spec.tsx +++ /dev/null @@ -1,201 +0,0 @@ -import React, { useEffect } from 'react' -import { createEditor, Text, Transforms } from 'slate' -import { create, act, ReactTestRenderer } from 'react-test-renderer' -import { Slate, withReact, Editable } from '../src' - -const createNodeMock = () => ({ - ownerDocument: global.document, - getRootNode: () => global.document, -}) - -class MockResizeObserver { - observe() {} - - unobserve() {} - - disconnect() {} -} - -describe('slate-react', () => { - window.ResizeObserver = MockResizeObserver as any - - describe('Editable', () => { - describe('NODE_TO_KEY logic', () => { - test('should not unmount the node that gets split on a split_node operation', async () => { - const editor = withReact(createEditor()) - const initialValue = [{ type: 'block', children: [{ text: 'test' }] }] - const mounts = jest.fn() - - let el: ReactTestRenderer - - act(() => { - el = create( - {}} - > - { - useEffect(() => mounts(), []) - - return children - }} - /> - , - { createNodeMock } - ) - }) - - // slate updates at next tick, so we need this to be async - await act(async () => - Transforms.splitNodes(editor, { at: { path: [0, 0], offset: 2 } }) - ) - - // 2 renders, one for the main element and one for the split element - expect(mounts).toHaveBeenCalledTimes(2) - }) - - test('should not unmount the node that gets merged into on a merge_node operation', async () => { - const editor = withReact(createEditor()) - const initialValue = [ - { type: 'block', children: [{ text: 'te' }] }, - { type: 'block', children: [{ text: 'st' }] }, - ] - const mounts = jest.fn() - - let el: ReactTestRenderer - - act(() => { - el = create( - {}} - > - { - useEffect(() => mounts(), []) - - return children - }} - /> - , - { createNodeMock } - ) - }) - - // slate updates at next tick, so we need this to be async - await act(async () => - Transforms.mergeNodes(editor, { at: { path: [0, 0], offset: 0 } }) - ) - - // only 2 renders for the initial render - expect(mounts).toHaveBeenCalledTimes(2) - }) - }) - }) - - test('calls onSelectionChange when editor select change', async () => { - const editor = withReact(createEditor()) - const initialValue = [ - { type: 'block', children: [{ text: 'te' }] }, - { type: 'block', children: [{ text: 'st' }] }, - ] - const onChange = jest.fn() - const onValueChange = jest.fn() - const onSelectionChange = jest.fn() - - act(() => { - create( - - - , - { createNodeMock } - ) - }) - - await act(async () => - Transforms.select(editor, { path: [0, 0], offset: 2 }) - ) - - expect(onSelectionChange).toHaveBeenCalled() - expect(onChange).toHaveBeenCalled() - expect(onValueChange).not.toHaveBeenCalled() - }) - - test('calls onValueChange when editor children change', async () => { - const editor = withReact(createEditor()) - const initialValue = [{ type: 'block', children: [{ text: 'test' }] }] - const onChange = jest.fn() - const onValueChange = jest.fn() - const onSelectionChange = jest.fn() - - act(() => { - create( - - - , - { createNodeMock } - ) - }) - - await act(async () => Transforms.insertText(editor, 'Hello word!')) - - expect(onValueChange).toHaveBeenCalled() - expect(onChange).toHaveBeenCalled() - expect(onSelectionChange).not.toHaveBeenCalled() - }) - - test('calls onValueChange when editor setNodes', async () => { - const editor = withReact(createEditor()) - const initialValue = [{ type: 'block', children: [{ text: 'test' }] }] - const onChange = jest.fn() - const onValueChange = jest.fn() - const onSelectionChange = jest.fn() - - act(() => { - create( - - - , - { createNodeMock } - ) - }) - - await act(async () => - Transforms.setNodes( - editor, - // @ts-ignore - { bold: true }, - { - at: { path: [0, 0], offset: 2 }, - match: Text.isText, - split: true, - } - ) - ) - - expect(onChange).toHaveBeenCalled() - expect(onValueChange).toHaveBeenCalled() - expect(onSelectionChange).not.toHaveBeenCalled() - }) -}) diff --git a/packages/slate-react/test/react-editor.spec.tsx b/packages/slate-react/test/react-editor.spec.tsx new file mode 100644 index 000000000..6ebd5c310 --- /dev/null +++ b/packages/slate-react/test/react-editor.spec.tsx @@ -0,0 +1,90 @@ +import React, { useEffect } from 'react' +import { createEditor, Text, Transforms } from 'slate' +import { act, render } from '@testing-library/react' +import { Slate, withReact, Editable, ReactEditor } from '../src' + +describe('slate-react', () => { + describe('ReactEditor', () => { + describe('.focus', () => { + test('should set focus in top of document with no editor selection', async () => { + const editor = withReact(createEditor()) + const initialValue = [{ type: 'block', children: [{ text: 'test' }] }] + const testSelection = { + anchor: { path: [0, 0], offset: 0 }, + focus: { path: [0, 0], offset: 0 }, + } + + act(() => { + render( + + + + ) + }) + + expect(editor.selection).toBe(null) + + await act(async () => { + ReactEditor.focus(editor) + }) + + expect(editor.selection).toEqual(testSelection) + + await act(async () => { + const windowSelection = ReactEditor.getWindow(editor).getSelection() + expect(windowSelection?.focusNode?.textContent).toBe('test') + expect(windowSelection?.anchorNode?.textContent).toBe('test') + expect(windowSelection?.anchorOffset).toBe( + testSelection.anchor.offset + ) + expect(windowSelection?.focusOffset).toBe(testSelection.focus.offset) + }) + }) + + test('should be able to call .focus without getting toDOMNode errors', async () => { + const editor = withReact(createEditor()) + const initialValue = [{ type: 'block', children: [{ text: 'test' }] }] + const propagatedValue = [ + { type: 'block', children: [{ text: 'foo' }] }, + { type: 'block', children: [{ text: 'bar' }] }, + ] + + const testSelection = { + anchor: { path: [1, 0], offset: 0 }, + focus: { path: [1, 0], offset: 3 }, + } + + act(() => { + render( + + + + ) + }) + + await act(async () => { + Transforms.removeNodes(editor, { at: [0] }) + Transforms.insertNodes(editor, propagatedValue) + ReactEditor.focus(editor) // Note: calling focus in the middle of these transformations. + Transforms.select(editor, testSelection) + }) + + expect(editor.selection).toEqual(testSelection) + + await act(async () => { + ReactEditor.focus(editor) + }) + + await act(async () => { + const windowSelection = ReactEditor.getWindow(editor).getSelection() + expect(windowSelection?.focusNode?.textContent).toBe('bar') + expect(windowSelection?.anchorNode?.textContent).toBe('bar') + expect(windowSelection?.anchorOffset).toBe( + testSelection.anchor.offset + ) + expect(windowSelection?.focusOffset).toBe(testSelection.focus.offset) + }) + }) + }) + }) +}) diff --git a/yarn.lock b/yarn.lock index 0ff5dfcc5..9b0d4c048 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3223,6 +3223,36 @@ __metadata: languageName: node linkType: hard +"@testing-library/dom@npm:^9.0.0": + version: 9.3.3 + resolution: "@testing-library/dom@npm:9.3.3" + dependencies: + "@babel/code-frame": ^7.10.4 + "@babel/runtime": ^7.12.5 + "@types/aria-query": ^5.0.1 + aria-query: 5.1.3 + chalk: ^4.1.0 + dom-accessibility-api: ^0.5.9 + lz-string: ^1.5.0 + pretty-format: ^27.0.2 + checksum: 34e0a564da7beb92aa9cc44a9080221e2412b1a132eb37be3d513fe6c58027674868deb9f86195756d98d15ba969a30fe00632a4e26e25df2a5a4f6ac0686e37 + languageName: node + linkType: hard + +"@testing-library/react@npm:^14.0.0": + version: 14.0.0 + resolution: "@testing-library/react@npm:14.0.0" + dependencies: + "@babel/runtime": ^7.12.5 + "@testing-library/dom": ^9.0.0 + "@types/react-dom": ^18.0.0 + peerDependencies: + react: ^18.0.0 + react-dom: ^18.0.0 + checksum: 4a54c8f56cc4a39b50803205f84f06280bb76521d6d5d4b3b36651d760c7c7752ef142d857d52aaf4fad4848ed7a8be49afc793a5dda105955d2f8bef24901ac + languageName: node + linkType: hard + "@tootallnate/once@npm:1": version: 1.1.2 resolution: "@tootallnate/once@npm:1.1.2" @@ -3254,6 +3284,13 @@ __metadata: languageName: node linkType: hard +"@types/aria-query@npm:^5.0.1": + version: 5.0.4 + resolution: "@types/aria-query@npm:5.0.4" + checksum: ad8b87e4ad64255db5f0a73bc2b4da9b146c38a3a8ab4d9306154334e0fc67ae64e76bfa298eebd1e71830591fb15987e5de7111bdb36a2221bdc379e3415fb0 + languageName: node + linkType: hard + "@types/babel__core@npm:^7.1.14": version: 7.1.15 resolution: "@types/babel__core@npm:7.1.15" @@ -3470,6 +3507,15 @@ __metadata: languageName: node linkType: hard +"@types/react-dom@npm:^18.0.0": + version: 18.2.15 + resolution: "@types/react-dom@npm:18.2.15" + dependencies: + "@types/react": "*" + checksum: 8e9631600c21ff561328e38a951d1991b3b3b20f538af4c0efbd1327c883a5573a63f50e1b945c34fa51b114b30e1ca5e62317bd54f21e063d6697b4be843a03 + languageName: node + linkType: hard + "@types/react-dom@npm:^18.2.13": version: 18.2.13 resolution: "@types/react-dom@npm:18.2.13" @@ -3479,15 +3525,6 @@ __metadata: languageName: node linkType: hard -"@types/react-test-renderer@npm:^18.0.3": - version: 18.0.3 - resolution: "@types/react-test-renderer@npm:18.0.3" - dependencies: - "@types/react": "*" - checksum: 6834c7c998d9e699b7b9aca31ce0f550a29a63526a9ab15f62764eeade28fcce634030e5cfd223a8417e8c53234e01bfb961906b35fd9a72dd513aba549181f0 - languageName: node - linkType: hard - "@types/react@npm:*, @types/react@npm:^18.2.28": version: 18.2.28 resolution: "@types/react@npm:18.2.28" @@ -4014,6 +4051,15 @@ __metadata: languageName: node linkType: hard +"aria-query@npm:5.1.3": + version: 5.1.3 + resolution: "aria-query@npm:5.1.3" + dependencies: + deep-equal: ^2.0.5 + checksum: 929ff95f02857b650fb4cbcd2f41072eee2f46159a6605ea03bf63aa572e35ffdff43d69e815ddc462e16e07de8faba3978afc2813650b4448ee18c9895d982b + languageName: node + linkType: hard + "array-buffer-byte-length@npm:^1.0.0": version: 1.0.0 resolution: "array-buffer-byte-length@npm:1.0.0" @@ -5671,6 +5717,32 @@ __metadata: languageName: node linkType: hard +"deep-equal@npm:^2.0.5": + version: 2.2.2 + resolution: "deep-equal@npm:2.2.2" + dependencies: + array-buffer-byte-length: ^1.0.0 + call-bind: ^1.0.2 + es-get-iterator: ^1.1.3 + get-intrinsic: ^1.2.1 + is-arguments: ^1.1.1 + is-array-buffer: ^3.0.2 + is-date-object: ^1.0.5 + is-regex: ^1.1.4 + is-shared-array-buffer: ^1.0.2 + isarray: ^2.0.5 + object-is: ^1.1.5 + object-keys: ^1.1.1 + object.assign: ^4.1.4 + regexp.prototype.flags: ^1.5.0 + side-channel: ^1.0.4 + which-boxed-primitive: ^1.0.2 + which-collection: ^1.0.1 + which-typed-array: ^1.1.9 + checksum: eb61c35157b6ecb96a5359b507b083fbff8ddb4c86a78a781ee38485f77a667465e45d63ee2ebd8a00e86d94c80e499906900cd82c2debb400237e1662cd5397 + languageName: node + linkType: hard + "deep-is@npm:^0.1.3": version: 0.1.4 resolution: "deep-is@npm:0.1.4" @@ -5897,6 +5969,13 @@ __metadata: languageName: node linkType: hard +"dom-accessibility-api@npm:^0.5.9": + version: 0.5.16 + resolution: "dom-accessibility-api@npm:0.5.16" + checksum: 005eb283caef57fc1adec4d5df4dd49189b628f2f575af45decb210e04d634459e3f1ee64f18b41e2dcf200c844bc1d9279d80807e686a30d69a4756151ad248 + languageName: node + linkType: hard + "domexception@npm:^4.0.0": version: 4.0.0 resolution: "domexception@npm:4.0.0" @@ -6153,6 +6232,23 @@ __metadata: languageName: node linkType: hard +"es-get-iterator@npm:^1.1.3": + version: 1.1.3 + resolution: "es-get-iterator@npm:1.1.3" + dependencies: + call-bind: ^1.0.2 + get-intrinsic: ^1.1.3 + has-symbols: ^1.0.3 + is-arguments: ^1.1.1 + is-map: ^2.0.2 + is-set: ^2.0.2 + is-string: ^1.0.7 + isarray: ^2.0.5 + stop-iteration-iterator: ^1.0.0 + checksum: 8fa118da42667a01a7c7529f8a8cca514feeff243feec1ce0bb73baaa3514560bd09d2b3438873cf8a5aaec5d52da248131de153b28e2638a061b6e4df13267d + languageName: node + linkType: hard + "es-iterator-helpers@npm:^1.0.12": version: 1.0.15 resolution: "es-iterator-helpers@npm:1.0.15" @@ -7065,6 +7161,13 @@ __metadata: languageName: node linkType: hard +"function-bind@npm:^1.1.2": + version: 1.1.2 + resolution: "function-bind@npm:1.1.2" + checksum: 2b0ff4ce708d99715ad14a6d1f894e2a83242e4a52ccfcefaee5e40050562e5f6dafc1adbb4ce2d4ab47279a45dc736ab91ea5042d843c3c092820dfe032efb1 + languageName: node + linkType: hard + "function.prototype.name@npm:^1.1.5, function.prototype.name@npm:^1.1.6": version: 1.1.6 resolution: "function.prototype.name@npm:1.1.6" @@ -7162,6 +7265,18 @@ __metadata: languageName: node linkType: hard +"get-intrinsic@npm:^1.2.2": + version: 1.2.2 + resolution: "get-intrinsic@npm:1.2.2" + dependencies: + function-bind: ^1.1.2 + has-proto: ^1.0.1 + has-symbols: ^1.0.3 + hasown: ^2.0.0 + checksum: 447ff0724df26829908dc033b62732359596fcf66027bc131ab37984afb33842d9cd458fd6cecadfe7eac22fd8a54b349799ed334cf2726025c921c7250e7417 + languageName: node + linkType: hard + "get-package-type@npm:^0.1.0": version: 0.1.0 resolution: "get-package-type@npm:0.1.0" @@ -7631,6 +7746,15 @@ __metadata: languageName: node linkType: hard +"hasown@npm:^2.0.0": + version: 2.0.0 + resolution: "hasown@npm:2.0.0" + dependencies: + function-bind: ^1.1.2 + checksum: 6151c75ca12554565098641c98a40f4cc86b85b0fd5b6fe92360967e4605a4f9610f7757260b4e8098dd1c2ce7f4b095f2006fe72a570e3b6d2d28de0298c176 + languageName: node + linkType: hard + "he@npm:1.2.0": version: 1.2.0 resolution: "he@npm:1.2.0" @@ -8016,6 +8140,17 @@ __metadata: languageName: node linkType: hard +"internal-slot@npm:^1.0.4": + version: 1.0.6 + resolution: "internal-slot@npm:1.0.6" + dependencies: + get-intrinsic: ^1.2.2 + hasown: ^2.0.0 + side-channel: ^1.0.4 + checksum: 7872454888047553ce97a3fa1da7cc054a28ec5400a9c2e9f4dbe4fe7c1d041cb8e8301467614b80d4246d50377aad2fb58860b294ed74d6700cc346b6f89549 + languageName: node + linkType: hard + "internal-slot@npm:^1.0.5": version: 1.0.5 resolution: "internal-slot@npm:1.0.5" @@ -8041,6 +8176,16 @@ __metadata: languageName: node linkType: hard +"is-arguments@npm:^1.1.1": + version: 1.1.1 + resolution: "is-arguments@npm:1.1.1" + dependencies: + call-bind: ^1.0.2 + has-tostringtag: ^1.0.0 + checksum: 7f02700ec2171b691ef3e4d0e3e6c0ba408e8434368504bb593d0d7c891c0dbfda6d19d30808b904a6cb1929bca648c061ba438c39f296c2a8ca083229c49f27 + languageName: node + linkType: hard + "is-array-buffer@npm:^3.0.1, is-array-buffer@npm:^3.0.2": version: 3.0.2 resolution: "is-array-buffer@npm:3.0.2" @@ -8278,7 +8423,7 @@ __metadata: languageName: node linkType: hard -"is-map@npm:^2.0.1": +"is-map@npm:^2.0.1, is-map@npm:^2.0.2": version: 2.0.2 resolution: "is-map@npm:2.0.2" checksum: ace3d0ecd667bbdefdb1852de601268f67f2db725624b1958f279316e13fecb8fa7df91fd60f690d7417b4ec180712f5a7ee967008e27c65cfd475cc84337728 @@ -8399,7 +8544,7 @@ __metadata: languageName: node linkType: hard -"is-set@npm:^2.0.1": +"is-set@npm:^2.0.1, is-set@npm:^2.0.2": version: 2.0.2 resolution: "is-set@npm:2.0.2" checksum: b64343faf45e9387b97a6fd32be632ee7b269bd8183701f3b3f5b71a7cf00d04450ed8669d0bd08753e08b968beda96fca73a10fd0ff56a32603f64deba55a57 @@ -9892,6 +10037,15 @@ __metadata: languageName: node linkType: hard +"lz-string@npm:^1.5.0": + version: 1.5.0 + resolution: "lz-string@npm:1.5.0" + bin: + lz-string: bin/bin.js + checksum: 1ee98b4580246fd90dd54da6e346fb1caefcf05f677c686d9af237a157fdea3fd7c83a4bc58f858cd5b10a34d27afe0fdcbd0505a47e0590726a873dc8b8f65d + languageName: node + linkType: hard + "magic-string@npm:^0.22.5": version: 0.22.5 resolution: "magic-string@npm:0.22.5" @@ -11036,6 +11190,16 @@ __metadata: languageName: node linkType: hard +"object-is@npm:^1.1.5": + version: 1.1.5 + resolution: "object-is@npm:1.1.5" + dependencies: + call-bind: ^1.0.2 + define-properties: ^1.1.3 + checksum: 989b18c4cba258a6b74dc1d74a41805c1a1425bce29f6cabb50dcb1a6a651ea9104a1b07046739a49a5bb1bc49727bcb00efd5c55f932f6ea04ec8927a7901fe + languageName: node + linkType: hard + "object-keys@npm:^1.0.12, object-keys@npm:^1.1.1": version: 1.1.1 resolution: "object-keys@npm:1.1.1" @@ -11790,6 +11954,17 @@ __metadata: languageName: node linkType: hard +"pretty-format@npm:^27.0.2": + version: 27.5.1 + resolution: "pretty-format@npm:27.5.1" + dependencies: + ansi-regex: ^5.0.1 + ansi-styles: ^5.0.0 + react-is: ^17.0.1 + checksum: cf610cffcb793885d16f184a62162f2dd0df31642d9a18edf4ca298e909a8fe80bdbf556d5c9573992c102ce8bf948691da91bf9739bee0ffb6e79c8a8a6e088 + languageName: node + linkType: hard + "pretty-format@npm:^29.0.0, pretty-format@npm:^29.7.0": version: 29.7.0 resolution: "pretty-format@npm:29.7.0" @@ -12020,13 +12195,6 @@ __metadata: languageName: node linkType: hard -"react-is@npm:^16.12.0 || ^17.0.0 || ^18.0.0, react-is@npm:^18.0.0, react-is@npm:^18.2.0": - version: 18.2.0 - resolution: "react-is@npm:18.2.0" - checksum: e72d0ba81b5922759e4aff17e0252bd29988f9642ed817f56b25a3e217e13eea8a7f2322af99a06edb779da12d5d636e9fda473d620df9a3da0df2a74141d53e - languageName: node - linkType: hard - "react-is@npm:^16.13.1, react-is@npm:^16.8.1": version: 16.13.1 resolution: "react-is@npm:16.13.1" @@ -12034,6 +12202,20 @@ __metadata: languageName: node linkType: hard +"react-is@npm:^17.0.1": + version: 17.0.2 + resolution: "react-is@npm:17.0.2" + checksum: 9d6d111d8990dc98bc5402c1266a808b0459b5d54830bbea24c12d908b536df7883f268a7868cfaedde3dd9d4e0d574db456f84d2e6df9c4526f99bb4b5344d8 + languageName: node + linkType: hard + +"react-is@npm:^18.0.0": + version: 18.2.0 + resolution: "react-is@npm:18.2.0" + checksum: e72d0ba81b5922759e4aff17e0252bd29988f9642ed817f56b25a3e217e13eea8a7f2322af99a06edb779da12d5d636e9fda473d620df9a3da0df2a74141d53e + languageName: node + linkType: hard + "react-router-dom@npm:^6.17.0": version: 6.17.0 resolution: "react-router-dom@npm:6.17.0" @@ -12058,31 +12240,6 @@ __metadata: languageName: node linkType: hard -"react-shallow-renderer@npm:^16.15.0": - version: 16.15.0 - resolution: "react-shallow-renderer@npm:16.15.0" - dependencies: - object-assign: ^4.1.1 - react-is: ^16.12.0 || ^17.0.0 || ^18.0.0 - peerDependencies: - react: ^16.0.0 || ^17.0.0 || ^18.0.0 - checksum: 6052c7e3e9627485120ebd8257f128aad8f56386fe8d42374b7743eac1be457c33506d153c7886b4e32923c0c352d402ab805ef9ca02dbcd8393b2bdeb6e5af8 - languageName: node - linkType: hard - -"react-test-renderer@npm:^18.2.0": - version: 18.2.0 - resolution: "react-test-renderer@npm:18.2.0" - dependencies: - react-is: ^18.2.0 - react-shallow-renderer: ^16.15.0 - scheduler: ^0.23.0 - peerDependencies: - react: ^18.2.0 - checksum: 6b6980ced93fa2b72662d5e4ab3b4896833586940047ce52ca9aca801e5432adf05fcbe28289b0af3ce6a2a7c590974e25dcc8aa43d0de658bfe8bbcd686f958 - languageName: node - linkType: hard - "react-values@npm:^0.3.3": version: 0.3.3 resolution: "react-values@npm:0.3.3" @@ -13227,13 +13384,13 @@ __metadata: dependencies: "@babel/runtime": ^7.23.2 "@juggle/resize-observer": ^3.4.0 + "@testing-library/react": ^14.0.0 "@types/is-hotkey": ^0.1.8 "@types/jest": 29.5.6 "@types/jsdom": ^21.1.4 "@types/lodash": ^4.14.200 "@types/react": ^18.2.28 "@types/react-dom": ^18.2.13 - "@types/react-test-renderer": ^18.0.3 "@types/resize-observer-browser": ^0.1.8 direction: ^1.0.4 is-hotkey: ^0.2.0 @@ -13241,7 +13398,6 @@ __metadata: lodash: ^4.17.21 react: ^18.2.0 react-dom: ^18.2.0 - react-test-renderer: ^18.2.0 scroll-into-view-if-needed: ^3.1.0 slate: ^0.100.0 slate-hyperscript: ^0.100.0 @@ -13528,6 +13684,15 @@ __metadata: languageName: node linkType: hard +"stop-iteration-iterator@npm:^1.0.0": + version: 1.0.0 + resolution: "stop-iteration-iterator@npm:1.0.0" + dependencies: + internal-slot: ^1.0.4 + checksum: d04173690b2efa40e24ab70e5e51a3ff31d56d699550cfad084104ab3381390daccb36652b25755e420245f3b0737de66c1879eaa2a8d4fc0a78f9bf892fcb42 + languageName: node + linkType: hard + "stream-transform@npm:^2.1.3": version: 2.1.3 resolution: "stream-transform@npm:2.1.3"