Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/comments mvi 1 testing #5007

Merged
merged 19 commits into from
Oct 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
94351b0
fix(form/inputs): Track focusPath for a span's .text prop + harden te…
skogsmaskin Aug 25, 2023
2ed28bf
fix(core/inputs): support setting initial PTE selection
skogsmaskin Oct 16, 2023
e85b8bb
test(playwright-ct): support document and focusPath props
skogsmaskin Oct 16, 2023
861d1e8
test(playwright-ct): add test for focus tracking in PT-input
skogsmaskin Oct 16, 2023
5504748
test(playwrigth-ct): fix issue with Webkit tests
skogsmaskin Oct 16, 2023
906e8a0
refactor(portable-text-editor): rename var to be more explicit
skogsmaskin Oct 17, 2023
5257dd2
refactor(playwright-ct): make TestWrapper form agnostic
skogsmaskin Oct 17, 2023
45f5d7e
doc(form): add doc and example for components extention of block type
skogsmaskin Oct 18, 2023
068c900
fix(portable-text-editor): set focus after operations are applied
skogsmaskin Oct 18, 2023
68c89c8
fix(portable-text-editor): ensure that the range is valid before dele…
skogsmaskin Oct 18, 2023
8f3b7cb
fix(portable-text-editor): fix issue where all the content is deleted
skogsmaskin Oct 18, 2023
b1dc4f9
fix(form/inputs): fix bug with onRemove cb for TextBlock
skogsmaskin Oct 18, 2023
9585963
test(comments): add test-ids
skogsmaskin Oct 18, 2023
bef453f
refactor(comments): refactor focus handling in the CommentInput
skogsmaskin Oct 18, 2023
550cebe
refactor(core/form/inputs): simplify focus handling
skogsmaskin Oct 19, 2023
f4da4c4
test(playwright-ct): add tests for CommentInput
skogsmaskin Oct 19, 2023
2b8c57c
fix(core/inputs): fix issue with toolbar popover visibility toggling
skogsmaskin Oct 20, 2023
77f3f6c
fix(comments): use current editor value over snapshot to update comment
skogsmaskin Oct 20, 2023
5195533
test(playwright-ct): update props after refactor
skogsmaskin Oct 20, 2023
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
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,24 @@ export function createWithEditableAPI(
types: PortableTextMemberSchemaTypes,
keyGenerator: () => string,
) {
let focusTimerHandle: NodeJS.Timeout
return function withEditableAPI(editor: PortableTextSlateEditor): PortableTextSlateEditor {
portableTextEditor.setEditable({
focus: (): void => {
// If the editor has pending operations, focus should be requested after
// those changes are applied. Retry in the next tick if this is the case.
// Relevant: https://github.com/ianstormtaylor/slate/pull/5516#issuecomment-1768359239
if (editor.operations.length > 0) {
if (focusTimerHandle) {
clearTimeout(focusTimerHandle)
}
focusTimerHandle = setTimeout(() => {
// Make sure everything is applied
editor.onChange()
PortableTextEditor.focus(portableTextEditor)
})
return
}
// Make a selection if missing
if (!editor.selection) {
const point = {path: [0, 0], offset: 0}
Expand Down Expand Up @@ -349,6 +364,10 @@ export function createWithEditableAPI(
delete: (selection: EditorSelection, options?: EditableAPIDeleteOptions): void => {
if (selection) {
const range = toSlateRange(selection, editor)
const hasRange = range && range.anchor.path.length > 0 && range.focus.path.length > 0
if (!hasRange) {
throw new Error('Invalid range')
}
if (range) {
if (!options?.mode || options?.mode === 'selected') {
debug(`Deleting content in selection`)
Expand Down Expand Up @@ -388,6 +407,14 @@ export function createWithEditableAPI(
hanging: true,
})
})
// If the editor was emptied, insert a placeholder block
// directly into the editor's children. We don't want to do this
// through a Transform (because that would trigger a change event
// that would insert the placeholder into the actual value
// which should remain empty)
if (editor.children.length === 0) {
editor.children = [editor.createPlaceholderBlock()]
}
editor.onChange()
}
}
Expand Down
31 changes: 31 additions & 0 deletions packages/sanity/playwright-ct/tests/comments/CommentInput.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import {expect, test} from '@playwright/experimental-ct-react'
import React from 'react'
import {testHelpers} from '../utils/testHelpers'
import {CommentsInputStory} from './CommentInputStory'

test.describe('Comments', () => {
test.describe('CommentInput', () => {
test('Should render', async ({mount, page}) => {
await mount(<CommentsInputStory />)
const $editable = page.getByTestId('comment-input-editable')
await expect($editable).toBeVisible()
})

test('Should be able to type into', async ({mount, page}) => {
const {insertPortableText} = testHelpers({page})
await mount(<CommentsInputStory />)
const $editable = page.getByTestId('comment-input-editable')
await expect($editable).toBeEditable()
await insertPortableText('My first comment!', $editable)
await expect($editable).toHaveText('My first comment!')
})

test('Should bring up mentions menu when typing @', async ({mount, page}) => {
await mount(<CommentsInputStory />)
const $editable = page.getByTestId('comment-input-editable')
await expect($editable).toBeEditable()
await page.keyboard.type(`@`)
await expect(page.getByTestId('comments-mentions-menu')).toBeVisible()
})
})
})
38 changes: 38 additions & 0 deletions packages/sanity/playwright-ct/tests/comments/CommentInputStory.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import React, {useState} from 'react'
import {CurrentUser, PortableTextBlock} from '@sanity/types'
import {noop} from 'lodash'
import {CommentInput} from '../../../src/desk/comments/src/components/pte/comment-input/CommentInput'
import {TestWrapper} from '../formBuilder/utils/TestWrapper'

const currentUser: CurrentUser = {
email: '',
id: '',
name: '',
role: '',
roles: [],
profileImage: '',
provider: '',
}

const SCHEMA_TYPES: [] = []

export function CommentsInputStory() {
const [value, setValue] = useState<PortableTextBlock[] | null>(null)

return (
<TestWrapper schemaTypes={SCHEMA_TYPES}>
<CommentInput
focusOnMount
placeholder="Your comment..."
focusLock
currentUser={currentUser}
onChange={setValue}
value={value}
mentionOptions={{data: [], error: null, loading: false}}
onDiscardConfirm={noop}
onDiscardCancel={noop}
onSubmit={noop}
/>
</TestWrapper>
)
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {defineField, defineType} from '@sanity/types'
import React from 'react'
import {TestWrapper} from './utils/TestWrapper'
import {TestForm} from './utils/TestForm'

const SCHEMA_TYPES = [
defineType({
Expand All @@ -20,7 +21,11 @@ const SCHEMA_TYPES = [
]

export function ArrayInputStory() {
return <TestWrapper schemaTypes={SCHEMA_TYPES} />
return (
<TestWrapper schemaTypes={SCHEMA_TYPES}>
<TestForm />
</TestWrapper>
)
}

export default ArrayInputStory
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {defineArrayMember, defineField, defineType} from '@sanity/types'
import React from 'react'
import {TestWrapper} from '../../utils/TestWrapper'
import {TestForm} from '../../utils/TestForm'

const SCHEMA_TYPES = [
defineType({
Expand All @@ -22,7 +23,11 @@ const SCHEMA_TYPES = [
]

export function AnnotationsStory() {
return <TestWrapper schemaTypes={SCHEMA_TYPES} />
return (
<TestWrapper schemaTypes={SCHEMA_TYPES}>
<TestForm />
</TestWrapper>
)
}

export default AnnotationsStory
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {defineArrayMember, defineField, defineType} from '@sanity/types'
import React from 'react'
import {BulbOutlineIcon} from '@sanity/icons'
import {TestWrapper} from '../../utils/TestWrapper'
import {TestForm} from '../../utils/TestForm'

const SCHEMA_TYPES = [
defineType({
Expand Down Expand Up @@ -35,7 +36,11 @@ const SCHEMA_TYPES = [
]

export function DecoratorsStory() {
return <TestWrapper schemaTypes={SCHEMA_TYPES} />
return (
<TestWrapper schemaTypes={SCHEMA_TYPES}>
<TestForm />
</TestWrapper>
)
}

export default DecoratorsStory
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
/* eslint-disable max-nested-callbacks */
import {expect, test} from '@playwright/experimental-ct-react'
import React from 'react'
import {Path, SanityDocument} from '@sanity/types'
import {Page} from '@playwright/test'
import FocusTrackingStory from './FocusTrackingStory'

export type UpdateFn = () => {focusPath: Path; document: SanityDocument}

const document: SanityDocument = {
_id: '123',
_type: 'test',
_createdAt: new Date().toISOString(),
_updatedAt: new Date().toISOString(),
_rev: '123',
body: [
{
_type: 'block',
_key: 'a',
children: [{_type: 'span', _key: 'b', text: 'Foo'}],
markDefs: [],
},
{
_type: 'block',
_key: 'c',
children: [{_type: 'span', _key: 'd', text: 'Bar'}],
markDefs: [],
},
{
_type: 'block',
_key: 'e',
children: [{_type: 'span', _key: 'f', text: 'Baz'}],
markDefs: [],
},
{
_type: 'block',
_key: 'g',
children: [
{_type: 'span', _key: 'h', text: 'Hello '},
{_type: 'inlineObjectWithTextProperty', _key: 'i', text: 'there'},
{_type: 'span', _key: 'j', text: ' playwright'},
],
markDefs: [],
},
{
_type: 'testObjectBlock',
_key: 'k',
text: 'Hello world',
},
],
}

test.describe('Portable Text Input', () => {
test.beforeEach(async ({page}) => {
await page.evaluate(() => {
window.localStorage.debug = 'sanity-pte:*'
})
})
test.describe('Should track focusPath', () => {
test(`for span .text`, async ({mount, page}) => {
const initialPath = ['body', {_key: 'c'}, 'children', {_key: 'd'}, 'text']
const component = await mount(
<FocusTrackingStory document={document} focusPath={initialPath} />,
)
await waitForFocusPath(page, initialPath)
const $portableTextInput = component.getByTestId('field-body')
const $pteTextbox = $portableTextInput.getByRole('textbox')
await expect($pteTextbox).toBeFocused()
expect(await getFocusedNodeText(page)).toEqual('Bar')
await setFocusPathFromOutside(page, ['body', {_key: 'e'}, 'children', {_key: 'f'}, 'text'])
expect(await getFocusedNodeText(page)).toEqual('Baz')
})
test(`for span child root`, async ({mount, page}) => {
const initialPath = ['body', {_key: 'c'}, 'children', {_key: 'd'}]
const component = await mount(
<FocusTrackingStory document={document} focusPath={initialPath} />,
)
await waitForFocusPath(page, initialPath)
const $portableTextInput = component.getByTestId('field-body')
const $pteTextbox = $portableTextInput.getByRole('textbox')
await expect($pteTextbox).toBeFocused()
expect(await getFocusedNodeText(page)).toEqual('Bar')
await setFocusPathFromOutside(page, ['body', {_key: 'e'}, 'children', {_key: 'f'}])
expect(await getFocusedNodeText(page)).toEqual('Baz')
})
test(`for inline objects with .text prop`, async ({mount, page}) => {
const initialPath = ['body', {_key: 'g'}, 'children', {_key: 'i'}, 'text']
const component = await mount(
<FocusTrackingStory document={document} focusPath={initialPath} />,
)
await waitForFocusPath(page, initialPath)
const $portableTextInput = component.getByTestId('field-body')
const $pteTextbox = $portableTextInput.getByRole('textbox')
await expect($pteTextbox).not.toBeFocused()
const inlineObjectTextInput = page.getByTestId('inlineTextInputField').getByRole('textbox')
await expect(inlineObjectTextInput).toBeFocused()
await setFocusPathFromOutside(page, ['body', {_key: 'e'}, 'children', {_key: 'f'}])
await expect($pteTextbox).toBeFocused()
})
test(`for object blocks with .text prop`, async ({mount, page}) => {
const initialPath = ['body', {_key: 'k'}, 'text']
const component = await mount(
<FocusTrackingStory document={document} focusPath={initialPath} />,
)
await waitForFocusPath(page, initialPath)
const $portableTextInput = component.getByTestId('field-body')
const $pteTextbox = $portableTextInput.getByRole('textbox')
await expect($pteTextbox).not.toBeFocused()
const blockObjectInput = page.getByTestId('objectBlockInputField').getByRole('textbox')
await expect(blockObjectInput).toBeFocused()
})
test(`for block paths`, async ({mount, page}) => {
const initialPath = ['body', {_key: 'k'}]
const component = await mount(
<FocusTrackingStory document={document} focusPath={initialPath} />,
)
await waitForFocusPath(page, initialPath)
const $portableTextInput = component.getByTestId('field-body')
const $pteTextbox = $portableTextInput.getByRole('textbox')
await expect($pteTextbox).not.toBeFocused()
const blockObjectInput = page.getByTestId('objectBlockInputField').getByRole('textbox')
await expect(blockObjectInput).toBeVisible()
await setFocusPathFromOutside(page, ['body', {_key: 'g'}])
await expect($pteTextbox).toBeFocused()
await expect(blockObjectInput).not.toBeVisible()
})
})
})

async function setFocusPathFromOutside(page: Page, path: Path) {
await page.evaluate(
({_path}) => {
;(window as any).__setFocusPath(_path)
},
{_path: path},
)
await waitForFocusPath(page, path)
}

// Make sure the focusPath is propagated before we start testing on it
async function waitForFocusPath(page: Page, path: Path) {
await page.waitForSelector(`[data-focus-path='${JSON.stringify(path)}']`)
}

function getFocusedNodeText(page: Page) {
return page.evaluate(() => window.getSelection()?.focusNode?.textContent)
}
Loading