{"id":15413784,"url":"https://github.com/mwood23/slate-test-utils","last_synced_at":"2025-04-14T00:01:26.686Z","repository":{"id":41240679,"uuid":"427868018","full_name":"mwood23/slate-test-utils","owner":"mwood23","description":"A toolkit to test Slate rich text editors with Jest, React Testing Library, and hyperscript! Write user driven integration tests with ease.","archived":false,"fork":false,"pushed_at":"2022-05-15T07:02:29.000Z","size":1416,"stargazers_count":57,"open_issues_count":7,"forks_count":6,"subscribers_count":4,"default_branch":"master","last_synced_at":"2024-04-24T16:19:35.059Z","etag":null,"topics":["rich-text-editor","slate","slate-react","slatejs"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mwood23.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2021-11-14T07:46:25.000Z","updated_at":"2024-04-04T18:35:25.000Z","dependencies_parsed_at":"2022-09-07T18:22:17.885Z","dependency_job_id":null,"html_url":"https://github.com/mwood23/slate-test-utils","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mwood23%2Fslate-test-utils","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mwood23%2Fslate-test-utils/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mwood23%2Fslate-test-utils/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mwood23%2Fslate-test-utils/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mwood23","download_url":"https://codeload.github.com/mwood23/slate-test-utils/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248799931,"owners_count":21163403,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["rich-text-editor","slate","slate-react","slatejs"],"created_at":"2024-10-01T16:58:48.824Z","updated_at":"2025-04-14T00:01:26.543Z","avatar_url":"https://github.com/mwood23.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Slate Test Utils\n\nA toolkit to test Slate rich text editors with Jest, React Testing Library, and hyperscript! Write user centric integration tests with ease. Read the [announcement](https://www.marcuswood.io/blog/effective-slate-testing-using-react-testing-library).\n\n- 🚀 Works with Jest, React Testing Library, and JSDOM (Create React App and Vite friendly)\n- 🙏 Out of the box support for testing: typing, selection, keyboard events, beforeInput events, normalization, history, operations,\n- 🐣 Stage editor state using Hyperscript instead of manual mocking or creating a Storybook story per state\n- 🏕 Stage tests with a mocked collapsed, expanded, or reverse expanded selection\n- ✅ Supports any Slate React editor\n- 🎩 Beautiful diffs on failing tests\n- ⚙️ Supports any number of nodes and custom data structures\n- 🌊 Supports emulating Windows and Mac for OS specific testing\n- 💃 Conversational API that makes testing complex workflows easy\n- 🦆 Test variants of your editor with the same test\n- 📸 Snapshot testing friendly (if you're into that kinda thing)\n- 👔 Fully typed with TypeScript\n\nWant to learn more about Slate? [Join the newsletter.](https://marcuswood.ck.page/slate)\n\n\n\nhttps://user-images.githubusercontent.com/13633613/168461296-8e72eae3-9438-4f81-9153-20265e97a3f4.mp4\n_Created with [Wave Snippets](https://wavesnippets.com/)_\n\n![](/static/test-results.png)\n\n### Example\n\nTo see full examples go to `example/`.\n\n```tsx\n/** @jsx jsx */\n\nimport { assertOutput, buildTestHarness } from '../../dist/esm'\nimport { RichTextExample } from './Editor'\nimport { jsx } from './test-utils'\nimport { fireEvent } from '@testing-library/dom'\n\nit('user inserts an bulleted list with a few items', async () =\u003e {\n  const input = (\n    \u003ceditor\u003e\n      \u003chp\u003e\n        \u003chtext\u003e\n          \u003ccursor /\u003e\n        \u003c/htext\u003e\n      \u003c/hp\u003e\n    \u003c/editor\u003e\n  )\n\n  const [\n    editor,\n    { type, pressEnter, deleteBackward, triggerKeyboardEvent },\n    { getByTestId },\n  ] = await buildTestHarness(RichTextExample)({\n    editor: input,\n  })\n\n  // Click the unordered list button in the nav\n  const unorderedList = getByTestId('bulleted-list')\n  fireEvent.mouseDown(unorderedList)\n\n  await type('🥕')\n  await deleteBackward()\n  await type('Carrots')\n\n  assertOutput(\n    editor,\n    \u003ceditor\u003e\n      \u003chbulletedlist\u003e\n        \u003chlistitem\u003e\n          \u003chtext\u003e\n            Carrots\n            \u003ccursor /\u003e\n          \u003c/htext\u003e\n        \u003c/hlistitem\u003e\n      \u003c/hbulletedlist\u003e\n    \u003c/editor\u003e,\n  )\n})\n```\n\n## Motivation\n\n**Rich text editors are hard.** What makes them harder is being able to test them in a way the gives you confidence that your code works as expected. There's so many user input mechanisms, edge cases, selection, state, normalization, and more to keep in mind when developing.\n\nYou could do an end to end testing framework, but even those aren't [without struggles](https://github.com/ianstormtaylor/slate/issues/3476#issuecomment-617594068), not to mention they're slow and another piece of infrastructure to worry about. Additionally, mocking up every what if scenario becomes difficult because generating the test states takes time. Even if you manage to get it all set up, it's hard to see the diff on your breaking tests unlike Jest that has a fantastic reporter for diffing JSON (what Slate state serializes to by default).\n\nAfter trying, E2E tests, no tests (don't recommend 😅), and unit tests like [Slate core](https://github.com/ianstormtaylor/slate/tree/main/packages/slate/test) nothing seemed to give me enough confidence and convenience that my was code working as intended.\n\nThis is where the Slate Test Utils come in! It's an abstraction that uses hyperscript to generate editor states that can be tested in a JSDOM environment with a bit of black magic.\n\nMy hope is that by providing a better way to test, everyone can deliver better editor experiences. I also hope that this helps get Slate-React to a stable 1.0 by providing a way to test it internally.\n\n### Testing ContentEditable in JSDOM?\n\nIt's well documented that [JSDOM does not support](https://github.com/jsdom/jsdom/issues/1670) `contenteditable`, the API that Slate is built on top of. JSDOM is a mocked DOM that you run your tests again when using Jest. However, since Slate has done\nan amazing job saving us from the darkness of working with `contenteditable` directly there's an opportunity to test a large part of the internal Slate-React API and in turn, our code.\n\nThat opportunity is what this library takes advantage of. There's some **big limitations** with this testing approach, but I would estimate that it has covered over 90% of my testing needs and has completely changed how I write Slate code.\n\n## Installation\n\nThe installation to make this work in your environment is going to be a 🐻 bear, I apologize in advance. Test environments are always difficult to setup.\n\n### Prerequisites\n\nMake sure you have Jest, React Testing Library, and React Testing Library DOM configured.\n\n1. [Setup Jest](https://jestjs.io/docs/getting-started)\n1. [Setup React Testing Library](https://testing-library.com/docs/react-testing-library/setup)\n1. [Add Patch Package](https://github.com/ds300/patch-package)\n\n### Install Slate Test Utils\n\n```sh\nyarn add -D slate-test-utils\n\n# Or\n\nnpm install -D slate-test-utils\n```\n\nNow this is where the black magic comes into play. We need to patch your `node_modules` with some things that will make JSDOM play friendly with our test harness. Go to this repo and find the `/patches` folder and copy them into a `/patches` folder at the root of your repo. Once you have done that...\n\n```sh\nyarn install\n\n# Or\n\nnpm install\n```\n\nThat should apply your patches to your `node_modules`. You may get a warning if the versions mismatch, but long as you don't get an error you are good to go. If you get an error you will need to manually create your own patches based off the ones in this repo.\n\nLastly, you need to add this line to your `setupTests.js` file for Jest so we can mock things.\n\n```js\nimport 'slate-test-utils/dist/cjs/mocks'\n\n// or if you are in commonjs\n\nrequire('slate-test-utils/dist/cjs/mocks')\n```\n\n### Configuring Your Hyperscript\n\nThe schemaless core of Slate is truly amazing and is fully supported with slate-test-utils. Since we cannot know what your editor's structure is like you need to configure your own hyperscript. Create a file called `testUtils` or `slateTestUtils` and fill out what your document looks like.\n\n```tsx\n// @ts-ignore - Imports will be there from the upstream patch\nimport { createHyperscript, createText } from 'slate-hyperscript'\n/**\n * This is the mapping for the JSX that creates editor state. Add to it as needed.\n * The h prefix isn't needed. It's added to be consistent and to let us know it's\n * hyperscript.\n */\nexport const jsx = createHyperscript({\n  elements: {\n    // Add any nodes here with any attributes that's required or optional\n    hp: { type: 'paragraph' },\n    hbulletedlist: { type: 'bulleted-list' },\n    hlistitem: { type: 'list-item' },\n    inline: { inline: true },\n    block: {},\n    wrapper: {},\n  },\n  creators: {\n    htext: createText,\n  },\n})\n```\n\n#### Typescript\n\nIf you are using TypeScript you need to let the compiler know about your custom JSX types. Within your `/src` directory add a `hyperscript.d.ts` file.\n\n```ts\ndeclare namespace JSX {\n  interface Element {}\n  interface IntrinsicElements {\n    hp: any\n    editor: any\n    htext: {\n      // These optional params will show up in the autocomplete!\n      bold?: boolean\n      underline?: boolean\n      italic?: boolean\n      children?: any\n    }\n    hbulletedlist: any\n    hlistitem: any\n    cursor: any\n    focus: any\n    anchor: any\n  }\n}\n```\n\n### Making your Editor Test Friendly\n\nFor this to work, your RichTextEditor component has to accept two props:\n\n- `editor`: This is an editor singleton that the test harness creates and passed into your editor. It's what the hyperscript creates for you.\n- `initialValue`: This is the `editor.children` from the editor singleton.\n\nYour editor call-site will look something like this to make it test friendly:\n\n```tsx\nconst emptyEditor: Descendant[] = [\n  {\n    type: 'paragraph',\n    children: [{ text: '' }],\n  },\n]\n\nexport const RichTextExample: FC\u003c{\n  editor?: Editor\n  initialValue?: Descendant[]\n}\u003e = ({\n  editor: mockEditor,\n  initialValue = emptyEditor,\n}) =\u003e {\n  // Starts with a default value same as usual except now we can stage\n  // in one for our testing.\n  const [value, setValue] = useState\u003cDescendant[]\u003e(initialValue)\n  const editor = useMemo(() =\u003e createEditor(), [])\n```\n\nLast step, you need to add a `data-testid` to your `Editable` component.\n\n```tsx\n  \u003cEditable\n    data-testid=\"slate-content-editable\"\n```\n\n## Testing\n\nWith your editor configured you should be good to go! Check out `/example` for a bunch of tests and patterns.\n\nFor all tests make sure you add this to the top:\n\n```ts\n/** @jsx jsx */\n\nimport { jsx } from '../test-utils'\n```\n\nThe first line sets the pragma that will parse your hyperscript. The second line will import the pragma.\n\n## API\n\nThe test utils export a few methods that help you create user centric tests for your editor.\n\n### BuildTestHarness\n\nA test harness for the RichTextEditor that adds custom queries to assert on, lots of simulated actions, and a custom rerender in case you want to assert on the DOM. In most cases, you'll want to assert directly on the editor state to check that the editor selection and other pieces of the editor are working as intended.\n\nYour first invocation of the test harness needs to be a React component.\n\n```ts\nconst richTextHarness = buildTestHarness(RichTextExample)\n```\n\n\u003e Tip! You can partially apply the `buildTestHarness` function to create a bunch of test harnesses per\n\u003e variant of your editor.\n\nNext, you need to pass in the config to render that component. You must pass an `editor` anything else is optional. You are returned a tuple of props. The first is going to be the editor you passed into the harness. The second is going to be commands for testing. The third is custom queries for asserting and the bag of props from `render` in React Testing Library.\n\n#### Config\n\nUse these properties to customize the testHarness\n\n```ts\n/**\n * A Slate editor singleton.\n */\neditor: any\n/**\n * Pretty logs out all operations on the editor so you can see what's going on in tests.\n */\ndebug?: boolean\n/**\n * Ensures Slate content is valid before rendering. This is not turned on by default\n * because you may want to test invalid states for normalization or testing purposes.\n *\n * @default false\n */\nstrict?: boolean\n/**\n * Props you would like to pass down to the element you have passed in to test. This could be disabled states\n * variants, specific styles, or anything else!\n */\ncomponentProps?: any\n\n/**\n * The test ID for the Editable component that is used\n * to run the test harness.\n *\n * @default 'slate-content-editable'\n */\ntestID?: string\n```\n\n```ts\nconst [editor, { triggerKeyboardEvent, type }] = await buildTestHarness(\n  RichTextExample,\n)({\n  editor: input,\n})\n```\n\nMost of your call-sites will look like this:\n\n```ts\nconst [editor, { triggerKeyboardEvent, type }] = await buildTestHarness(\n  RichTextExample,\n)({\n  editor: input,\n})\n\n// Or this, same thing except with this you can reuse the first part of the function!\nconst richTextHarness = buildTestHarness(RichTextExample)\n\nconst [editor, { triggerKeyboardEvent, type }] = await richTextHarness({\n  editor: input,\n})\n```\n\n#### Commands\n\nThese commands are what you can use to interact with your rendered editor\n\n```ts\ntype: (s: string) =\u003e Promise\u003cvoid\u003e\ndeleteForward: () =\u003e Promise\u003cvoid\u003e\ndeleteBackward: () =\u003e Promise\u003cvoid\u003e\ndeleteEntireSoftline: () =\u003e Promise\u003cvoid\u003e\ndeleteHardLineBackward: () =\u003e Promise\u003cvoid\u003e\ndeleteSoftLineBackward: () =\u003e Promise\u003cvoid\u003e\ndeleteHardLineForward: () =\u003e Promise\u003cvoid\u003e\ndeleteSoftLineForward: () =\u003e Promise\u003cvoid\u003e\ndeleteWordBackward: () =\u003e Promise\u003cvoid\u003e\ndeleteWordForward: () =\u003e Promise\u003cvoid\u003e\npaste: (payload: string, ?{ types: 'text/html' | 'text/plain' | 'image/png'[] }) =\u003e Promise\u003cvoid\u003e\npressEnter: () =\u003e Promise\u003cvoid\u003e\n/**\n * Use a hotkey combination from is-hotkey. See testHarness internals\n * for usage.\n */\ntriggerKeyboardEvent: (hotkey: string) =\u003e Promise\u003cvoid\u003e\ntypeSpace: () =\u003e Promise\u003cvoid\u003e\nundo: () =\u003e Promise\u003cvoid\u003e\nredo: () =\u003e Promise\u003cvoid\u003e\nselectAll: () =\u003e Promise\u003cvoid\u003e\nisApple: () =\u003e boolean\nrerender: () =\u003e void\n```\n\n#### Queries\n\nThe third param is the bag of props returned from `render`. It includes some helper queries for Slate and all of the default methods returned from React Testing Library.\n\n## Test Runner\n\nThe test runner will run your tests simulated in iOS and Windows environments by mocking the user agent. This is useful for testing keyboard events and other OS specific functionality. Refer to `example/src/tests/mac-windows.test.tsx` for usage.\n\n## Running Example Folder\n\nRun the example project to see it in action and get an idea of some fun patterns you can include in your testing.\n\n```sh\ngit clone https://github.com/mwood23/slate-test-utils\n\ncd slate-test-utils\n\nyarn install \u0026\u0026 yarn build\n\ncd example\n\nyarn install\n\nyarn test\n```\n\n## Limitations\n\nThere are some big limitations to this approach when testing your editor. You will not be able to test 100% of the behavior of your editor with this framework, so manual testing or E2E tests will be needed depending on your use case.\n\n- Any contenteditable event that is not handled by your or Slate React will not work. For example, if you fire the key down `arrowLeft`, nothing will happen unless you handle that event specifically because `contenteditable` is not fully supported.\n- We are using our own jsx pragma to parse the tests so you will not be able to use React components in the same file. That's the reason in the test harness we have a `componentProps` field that lets you put in any amount of custom props you need to test.\n- React 17.x will work, but if you use TypeScript, you may run into problems parsing your tests because of [how it works](https://www.typescriptlang.org/docs/handbook/jsx.html). If you do, you will need to create a tsconfig specific for your tests with `\"jsx\": \"react\"`.\n- Jest 26/27 are supported depending on your version. It is important to note that since we patch JSDOM, you need to make sure that the patch files will work.\n- Slate 0.70.0 is the only officially supported version although I have tested this all the way to version 0.59.0. Since Slate is beta, your mileage may vary. Please open an issue if you see anything weird.\n\n## Unknown Support\n\nThere is a lot to support with Slate. I'm not sure if these will work or not because I haven't needed to use them often enough to know. Open to PRs to add this functionality or example usages!\n\n1. Copy-paste\n2. Void elements\n\n## Errors\n\n- If you get an error about `DataTransfer` not being defined then you haven't imported the `slate-test-utils` mock correctly\n- If you get an error about your patch file make sure your versions are consistent with the `/example` file or create a patch specific to that version\n- If your editor does not appear to be updating from your tests make sure you have made your editor test friendly\n- If you get an error in your test about your hyperscript not being correct or un-parsable make sure you are importing the pragma and your built hyperscript\n\n## FAQ\n\n- Could I use this with ProseMirror? I suppose you could depending on how they handle their events under the hood.\n\n## TODO\n\n- PR the patches to the respective repos, especially the Slate ones.\n- Write tests in Slate-React using the test utils?\n\n## Contributing\n\nAny and all PRs, issues, and ideas for improvement welcomes!\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmwood23%2Fslate-test-utils","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmwood23%2Fslate-test-utils","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmwood23%2Fslate-test-utils/lists"}