diff --git a/docs/general/contributing.md b/docs/general/contributing.md index a364645ba..ce7935e26 100644 --- a/docs/general/contributing.md +++ b/docs/general/contributing.md @@ -101,6 +101,10 @@ To run integrations with [Playwright](https://playwright.dev/), first run `yarn [Here's a helpful page](https://github.com/Microsoft/vscode/wiki/IME-Test) detailing how to test various input scenarios on Windows, Mac and Linux. +## Android tests + +When making changes that might affect Android compatibility, you can perform the manual Android tests at [/examples/android-tests](https://slatejs.org/examples/android-tests). + ## Publishing Releases **Important**: When creating releases using Lerna with the instructions below, you will be given choices around how to increase version numbers. You should always use a `major`, `minor` or `patch` release and must never use a `prerelease`. If a prerelease is used, the root package will not link to the packages in the `packages` directory creating hard to diagnose issues. diff --git a/package.json b/package.json index 291463871..8580a62c2 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "build:rollup": "rollup --config ./config/rollup/rollup.config.js --bundleConfigAsCjs", "changesetversion": "yarn changeset version && yarn install && git add .", "clean": "rimraf './packages/*/{dist,lib,node_modules}' './site/{.next,out}' --glob", - "fix": "yarn fix:prettier && yarn fix:eslint", + "fix": "yarn tsc:examples && yarn fix:prettier && yarn fix:eslint", "fix:eslint": "yarn lint:eslint --fix", "fix:prettier": "yarn lint:prettier --write", "lint": "yarn lint:typescript && yarn lint:eslint && yarn lint:prettier", diff --git a/site/examples/js/android-tests.jsx b/site/examples/js/android-tests.jsx new file mode 100644 index 000000000..314562f4a --- /dev/null +++ b/site/examples/js/android-tests.jsx @@ -0,0 +1,234 @@ +import React, { useCallback, useEffect, useMemo, useState } from 'react' +import { createEditor } from 'slate' +import { withHistory } from 'slate-history' +import { Editable, Slate, withReact } from 'slate-react' +import { css } from '@emotion/css' + +const TEST_CASES = [ + { + id: 'split-join', + name: 'Split/Join', + instructions: + 'Hit enter twice then backspace twice in the following places:\n- Before "before"\n- Between the two "d"s in "middle"\n- After "after"', + value: [ + { + type: 'paragraph', + children: [ + { text: 'One ' }, + { text: 'before', bold: true }, + { text: ' two ' }, + { text: 'middle', bold: true }, + { text: ' three ' }, + { text: 'after', bold: true }, + { text: ' four' }, + ], + }, + ], + }, + { + id: 'insert', + name: 'Insertion', + instructions: + 'Enter text below each line of instruction, including mis-spelling "wasnt"', + value: [ + { + type: 'paragraph', + children: [ + { text: 'Type by tapping keys: ', bold: true }, + { text: 'It wasnt me. No.' }, + ], + }, + { + type: 'paragraph', + children: [{ text: '' }], + }, + { + type: 'paragraph', + children: [ + { text: 'Type using glide typing: ', bold: true }, + { text: 'Yes Sam, I am.' }, + ], + }, + { + type: 'paragraph', + children: [{ text: '' }], + }, + { + type: 'paragraph', + children: [ + { text: 'Type using voice input: ', bold: true }, + { text: 'The quick brown fox jumps over the lazy dog' }, + ], + }, + { + type: 'paragraph', + children: [{ text: '' }], + }, + { + type: 'paragraph', + children: [{ text: 'Write any two words using an IME', bold: true }], + }, + { + type: 'paragraph', + children: [{ text: '' }], + }, + ], + }, + { + id: 'special', + name: 'Special', + instructions: 'Follow the instructions on each line', + value: [ + { + type: 'paragraph', + children: [ + { + text: 'Type "it is", move cursor to "i|t" and hit enter.', + bold: true, + }, + ], + }, + { + type: 'paragraph', + children: [{ text: '' }], + }, + { + type: 'paragraph', + children: [ + { + text: 'Move cursor to "mid|dle" and press space, backspace, space, backspace.', + bold: true, + }, + ], + }, + { + type: 'paragraph', + children: [{ text: 'The middle word.' }], + }, + { + type: 'paragraph', + children: [ + { + text: 'Place cursor in line below. Wait for caps on keyboard to show up. If not try again. Type "It me. No." and check it doesn\'t mangle on the last period.', + bold: true, + }, + ], + }, + { + type: 'paragraph', + children: [{ text: '' }], + }, + ], + }, + { + id: 'empty', + name: 'Empty', + instructions: + 'Type "hello world", press enter, "hi", press enter, "bye", and then backspace over everything', + value: [ + { + type: 'paragraph', + children: [{ text: '' }], + }, + ], + }, + { + id: 'remove', + name: 'Remove', + instructions: + 'Select from ANCHOR to FOCUS and press backspace. Move cursor to end. Backspace over all remaining content.', + value: [ + { + type: 'paragraph', + children: [ + { text: 'Go and ' }, + { text: 'select', bold: true }, + { text: ' from this ANCHOR and then' }, + ], + }, + { + type: 'paragraph', + children: [{ text: 'go and select' }], + }, + { + type: 'paragraph', + children: [ + { text: 'to this FOCUS then press ' }, + { text: 'backspace.', bold: true }, + ], + }, + { + type: 'paragraph', + children: [ + { text: 'After you have done that move selection to very end.' }, + ], + }, + { + type: 'paragraph', + children: [ + { text: 'Then try ' }, + { text: 'backspacing', bold: true }, + { text: ' over all remaining text.' }, + ], + }, + ], + }, +] +const AndroidTestsExample = () => { + const [testId, setTestId] = useState( + () => window.location.hash.replace('#', '') || TEST_CASES[0].id + ) + useEffect(() => { + window.history.replaceState({}, '', `#${testId}`) + }, [testId]) + const testCase = TEST_CASES.find(({ id }) => id === testId) + if (!testCase) { + throw new Error(`Could not find test case '${testId}'`) + } + return ( + <> + + +

+ {testCase.instructions} +

+ + + + ) +} +const TestCase = ({ value }) => { + const renderLeaf = useCallback(props => , []) + const editor = useMemo(() => withHistory(withReact(createEditor())), []) + return ( + + + + ) +} +const Leaf = ({ attributes, children, leaf }) => { + if (leaf.bold) { + children = {children} + } + return {children} +} +export default AndroidTestsExample diff --git a/site/examples/js/check-lists.jsx b/site/examples/js/check-lists.jsx index 36084fc80..317ade149 100644 --- a/site/examples/js/check-lists.jsx +++ b/site/examples/js/check-lists.jsx @@ -1,22 +1,22 @@ -import React, { useMemo, useCallback } from 'react' -import { - Slate, - Editable, - withReact, - useSlateStatic, - useReadOnly, - ReactEditor, -} from 'slate-react' +import { css } from '@emotion/css' +import React, { useCallback, useMemo } from 'react' import { Editor, - Transforms, - Range, Point, - createEditor, + Range, Element as SlateElement, + Transforms, + createEditor, } from 'slate' -import { css } from '@emotion/css' import { withHistory } from 'slate-history' +import { + Editable, + ReactEditor, + Slate, + useReadOnly, + useSlateStatic, + withReact, +} from 'slate-react' const initialValue = [ { diff --git a/site/examples/ts/android-tests.tsx b/site/examples/ts/android-tests.tsx new file mode 100644 index 000000000..94a009e8c --- /dev/null +++ b/site/examples/ts/android-tests.tsx @@ -0,0 +1,254 @@ +import React, { useCallback, useEffect, useMemo, useState } from 'react' +import { Descendant, createEditor } from 'slate' +import { withHistory } from 'slate-history' +import { Editable, RenderLeafProps, Slate, withReact } from 'slate-react' +import { css } from '@emotion/css' + +interface AndroidTestCase { + id: string + name: string + instructions: string + value: Descendant[] +} + +const TEST_CASES: AndroidTestCase[] = [ + { + id: 'split-join', + name: 'Split/Join', + instructions: + 'Hit enter twice then backspace twice in the following places:\n- Before "before"\n- Between the two "d"s in "middle"\n- After "after"', + value: [ + { + type: 'paragraph', + children: [ + { text: 'One ' }, + { text: 'before', bold: true }, + { text: ' two ' }, + { text: 'middle', bold: true }, + { text: ' three ' }, + { text: 'after', bold: true }, + { text: ' four' }, + ], + }, + ], + }, + { + id: 'insert', + name: 'Insertion', + instructions: + 'Enter text below each line of instruction, including mis-spelling "wasnt"', + value: [ + { + type: 'paragraph', + children: [ + { text: 'Type by tapping keys: ', bold: true }, + { text: 'It wasnt me. No.' }, + ], + }, + { + type: 'paragraph', + children: [{ text: '' }], + }, + { + type: 'paragraph', + children: [ + { text: 'Type using glide typing: ', bold: true }, + { text: 'Yes Sam, I am.' }, + ], + }, + { + type: 'paragraph', + children: [{ text: '' }], + }, + { + type: 'paragraph', + children: [ + { text: 'Type using voice input: ', bold: true }, + { text: 'The quick brown fox jumps over the lazy dog' }, + ], + }, + { + type: 'paragraph', + children: [{ text: '' }], + }, + { + type: 'paragraph', + children: [{ text: 'Write any two words using an IME', bold: true }], + }, + { + type: 'paragraph', + children: [{ text: '' }], + }, + ], + }, + { + id: 'special', + name: 'Special', + instructions: 'Follow the instructions on each line', + value: [ + { + type: 'paragraph', + children: [ + { + text: 'Type "it is", move cursor to "i|t" and hit enter.', + bold: true, + }, + ], + }, + { + type: 'paragraph', + children: [{ text: '' }], + }, + { + type: 'paragraph', + children: [ + { + text: 'Move cursor to "mid|dle" and press space, backspace, space, backspace.', + bold: true, + }, + ], + }, + { + type: 'paragraph', + children: [{ text: 'The middle word.' }], + }, + { + type: 'paragraph', + children: [ + { + text: 'Place cursor in line below. Wait for caps on keyboard to show up. If not try again. Type "It me. No." and check it doesn\'t mangle on the last period.', + bold: true, + }, + ], + }, + { + type: 'paragraph', + children: [{ text: '' }], + }, + ], + }, + { + id: 'empty', + name: 'Empty', + instructions: + 'Type "hello world", press enter, "hi", press enter, "bye", and then backspace over everything', + value: [ + { + type: 'paragraph', + children: [{ text: '' }], + }, + ], + }, + { + id: 'remove', + name: 'Remove', + instructions: + 'Select from ANCHOR to FOCUS and press backspace. Move cursor to end. Backspace over all remaining content.', + value: [ + { + type: 'paragraph', + children: [ + { text: 'Go and ' }, + { text: 'select', bold: true }, + { text: ' from this ANCHOR and then' }, + ], + }, + { + type: 'paragraph', + children: [{ text: 'go and select' }], + }, + { + type: 'paragraph', + children: [ + { text: 'to this FOCUS then press ' }, + { text: 'backspace.', bold: true }, + ], + }, + { + type: 'paragraph', + children: [ + { text: 'After you have done that move selection to very end.' }, + ], + }, + { + type: 'paragraph', + children: [ + { text: 'Then try ' }, + { text: 'backspacing', bold: true }, + { text: ' over all remaining text.' }, + ], + }, + ], + }, +] + +const AndroidTestsExample = () => { + const [testId, setTestId] = useState( + () => window.location.hash.replace('#', '') || TEST_CASES[0].id + ) + + useEffect(() => { + window.history.replaceState({}, '', `#${testId}`) + }, [testId]) + + const testCase = TEST_CASES.find(({ id }) => id === testId) + if (!testCase) { + throw new Error(`Could not find test case '${testId}'`) + } + + return ( + <> + + +

+ {testCase.instructions} +

+ + + + ) +} + +const TestCase = ({ value }: AndroidTestCase) => { + const renderLeaf = useCallback( + (props: RenderLeafProps) => , + [] + ) + + const editor = useMemo(() => withHistory(withReact(createEditor())), []) + + return ( + + + + ) +} + +const Leaf = ({ attributes, children, leaf }: RenderLeafProps) => { + if (leaf.bold) { + children = {children} + } + + return {children} +} + +export default AndroidTestsExample diff --git a/site/pages/examples/[example].tsx b/site/pages/examples/[example].tsx index fbc7b0b44..eb0e44d7c 100644 --- a/site/pages/examples/[example].tsx +++ b/site/pages/examples/[example].tsx @@ -7,6 +7,7 @@ import { ErrorBoundary } from 'react-error-boundary' import { Icon } from '../../examples/ts/components/index' +import AndroidTests from '../../examples/ts/android-tests' import CheckLists from '../../examples/ts/check-lists' import CodeHighlighting from '../../examples/ts/code-highlighting' import EditableVoids from '../../examples/ts/editable-voids' @@ -33,13 +34,15 @@ import CustomPlaceholder from '../../examples/ts/custom-placeholder' // node import { getAllExamples } from '../api' -type ExampleTuple = [string, React.ComponentType, string] +type ExampleTuple = [name: string, component: React.ComponentType, path: string] const EXAMPLES: ExampleTuple[] = [ + ['Android Tests', AndroidTests, 'android-tests'], ['Checklists', CheckLists, 'check-lists'], + ['Code Highlighting', CodeHighlighting, 'code-highlighting'], + ['Custom Placeholder', CustomPlaceholder, 'custom-placeholder'], ['Editable Voids', EditableVoids, 'editable-voids'], ['Embeds', Embeds, 'embeds'], - ['Code Highlighting', CodeHighlighting, 'code-highlighting'], ['Forced Layout', ForcedLayout, 'forced-layout'], ['Hovering Toolbar', HoveringToolbar, 'hovering-toolbar'], ['Huge Document', HugeDocument, 'huge-document'], @@ -51,15 +54,20 @@ const EXAMPLES: ExampleTuple[] = [ ['Paste HTML', PasteHtml, 'paste-html'], ['Plain Text', PlainText, 'plaintext'], ['Read-only', ReadOnly, 'read-only'], + ['Rendering in iframes', IFrames, 'iframe'], ['Rich Text', RichText, 'richtext'], ['Search Highlighting', SearchHighlighting, 'search-highlighting'], ['Shadow DOM', ShadowDOM, 'shadow-dom'], ['Styling', Styling, 'styling'], ['Tables', Tables, 'tables'], - ['Rendering in iframes', IFrames, 'iframe'], - ['Custom placeholder', CustomPlaceholder, 'custom-placeholder'], ] +const HIDDEN_EXAMPLES = ['android-tests'] + +const NON_HIDDEN_EXAMPLES = EXAMPLES.filter( + ([, , path]) => !HIDDEN_EXAMPLES.includes(path) +) + const Header = (props: React.HTMLAttributes) => (
{ - {EXAMPLES.map(([n, , p]) => ( + {NON_HIDDEN_EXAMPLES.map(([n, , p]) => (