Skip to content

Commit

Permalink
test(portable-text-editor): update tests
Browse files Browse the repository at this point in the history
  • Loading branch information
skogsmaskin committed Dec 20, 2022
1 parent a9dc1ea commit 05abec0
Show file tree
Hide file tree
Showing 11 changed files with 174 additions and 122 deletions.
2 changes: 1 addition & 1 deletion packages/@sanity/portable-text-editor/src/constants.ts
Expand Up @@ -2,4 +2,4 @@
* Debounce time for flushing local patches (ms since user haven't produced a patch)
* (lower time for tests to speed them up)
*/
export const FLUSH_PATCHES_DEBOUNCE_MS = process.env.NODE_ENV === 'test' ? 50 : 1000
export const FLUSH_PATCHES_DEBOUNCE_MS = process.env.NODE_ENV === 'test' ? 200 : 1000
Expand Up @@ -396,46 +396,63 @@ describe('selection adjustment', () => {
})
})

it('will keep A on same word if B merges marks within that line', async () => {
it('will keep A on same selection if B toggles marks on another block', async () => {
await setDocumentValue([
{
_key: 'someKey',
_key: 'someKey1',
_type: 'block',
markDefs: [],
style: 'normal',
children: [
{_key: 'anotherKey1', _type: 'span', text: '1 ', marks: []},
{_key: 'anotherKey2', _type: 'span', text: '22', marks: ['strong']},
{_key: 'anotherKey3', _type: 'span', text: ' 333', marks: []},
],
children: [{_key: 'anotherKey1', _type: 'span', text: '1', marks: ['strong']}],
},
{
_key: 'someKey2',
_type: 'block',
markDefs: [],
style: 'normal',
children: [{_key: 'anotherKey2', _type: 'span', text: '2', marks: []}],
},
])
const expectedSelectionA = {
anchor: {path: [{_key: 'someKey'}, 'children', {_key: 'anotherKey3'}], offset: 1},
focus: {path: [{_key: 'someKey'}, 'children', {_key: 'anotherKey3'}], offset: 1},
anchor: {path: [{_key: 'someKey1'}, 'children', {_key: 'anotherKey1'}], offset: 0},
focus: {path: [{_key: 'someKey1'}, 'children', {_key: 'anotherKey1'}], offset: 1},
}
const [editorA, editorB] = await getEditors()
await editorA.setSelection(expectedSelectionA)
expect(await editorA.getSelection()).toEqual(expectedSelectionA)
const expectedSelectionB = {
anchor: {path: [{_key: 'someKey'}, 'children', {_key: 'anotherKey2'}], offset: 0},
focus: {path: [{_key: 'someKey'}, 'children', {_key: 'anotherKey2'}], offset: 2},
anchor: {path: [{_key: 'someKey2'}, 'children', {_key: 'anotherKey2'}], offset: 0},
focus: {path: [{_key: 'someKey2'}, 'children', {_key: 'anotherKey2'}], offset: 1},
}
const [editorA, editorB] = await getEditors()
await editorA.setSelection(expectedSelectionA)
await editorB.setSelection(expectedSelectionB)
expect(await editorA.getSelection()).toEqual(expectedSelectionA)
expect(await editorB.getSelection()).toEqual(expectedSelectionB)
await editorB.toggleMark()
await editorA.toggleMark()
const valueB = await editorB.getValue()
expect(valueB).toMatchInlineSnapshot(`
Array [
Object {
"_key": "someKey",
"_key": "someKey1",
"_type": "block",
"children": Array [
Object {
"_key": "anotherKey1",
"_type": "span",
"marks": Array [],
"text": "1 22 333",
"text": "1",
},
],
"markDefs": Array [],
"style": "normal",
},
Object {
"_key": "someKey2",
"_type": "block",
"children": Array [
Object {
"_key": "anotherKey2",
"_type": "span",
"marks": Array [],
"text": "2",
},
],
"markDefs": Array [],
Expand All @@ -446,8 +463,12 @@ describe('selection adjustment', () => {
const valueA = await editorA.getValue()
expect(valueA).toEqual(valueB)
expect(await editorA.getSelection()).toEqual({
anchor: {path: [{_key: 'someKey'}, 'children', {_key: 'anotherKey1'}], offset: 5},
focus: {path: [{_key: 'someKey'}, 'children', {_key: 'anotherKey1'}], offset: 5},
anchor: {path: [{_key: 'someKey1'}, 'children', {_key: 'anotherKey1'}], offset: 0},
focus: {path: [{_key: 'someKey1'}, 'children', {_key: 'anotherKey1'}], offset: 1},
})
expect(await editorB.getSelection()).toEqual({
anchor: {path: [{_key: 'someKey2'}, 'children', {_key: 'anotherKey2'}], offset: 0},
focus: {path: [{_key: 'someKey2'}, 'children', {_key: 'anotherKey2'}], offset: 1},
})
})
})
@@ -1,9 +1,9 @@
/** @jest-environment ./test/setup/collaborative.jest.env.ts */

import '../setup/globals.jest'
import type {PortableTextBlock} from '../../src'
import type {PortableTextBlock} from '@sanity/types'

const initialValue: PortableTextBlock[] | undefined = [
const initialValue: PortableTextBlock[] = [
{
_key: 'randomKey0',
_type: 'block',
Expand Down
12 changes: 6 additions & 6 deletions packages/@sanity/portable-text-editor/test/schema.ts
@@ -1,17 +1,17 @@
import {RawType} from '../src/types/schema'
import {defineType} from '@sanity/types'

export const imageType: RawType = {
export const imageType = {
type: 'image',
name: 'blockImage',
}

export const someObject: RawType = {
export const someObject = {
type: 'object',
name: 'someObject',
fields: [{type: 'string', name: 'color'}],
}

export const blockType: RawType = {
export const blockType = {
type: 'block',
name: 'block',
styles: [
Expand All @@ -27,8 +27,8 @@ export const blockType: RawType = {
of: [someObject],
}

export const portableTextType: RawType = {
export const portableTextType = defineType({
type: 'array',
name: 'body',
of: [blockType, someObject],
}
})
Expand Up @@ -2,10 +2,11 @@ import childProcess from 'child_process'
import NodeEnvironment from 'jest-environment-node'
import {isEqual} from 'lodash'
import ipc from 'node-ipc'
import puppeteer, {ElementHandle, KeyInput} from 'puppeteer'
import {ElementHandle, KeyInput, Browser, Page, launch} from 'puppeteer'
import {PortableTextBlock} from '@sanity/types'
import {FLUSH_PATCHES_DEBOUNCE_MS} from '../../src/constants'
import {normalizeSelection} from '../../src/utils/selection'
import type {EditorSelection, PortableTextBlock} from '../../src'
import type {EditorSelection} from '../../src'

ipc.config.id = 'collaborative-jest-environment-ipc-client'
ipc.config.retry = 1500
Expand All @@ -19,10 +20,11 @@ const WEB_SERVER_ROOT_URL = 'http://localhost:3000'
const DEBUG = process.env.DEBUG || false

// Wait this long for selections and a new doc revision to appear in the clients.
const SELECTION_TIMEOUT_MS = 1500
const SELECTION_TIMEOUT_MS = 300

// How long to wait for a new revision to come back to the client(s) when patched through the server.
const REVISION_TIMEOUT_MS = FLUSH_PATCHES_DEBOUNCE_MS + 1000
// Wait for patch debounce time and some slack for selection adjustment time for everything to be ready
const REVISION_TIMEOUT_MS = FLUSH_PATCHES_DEBOUNCE_MS + SELECTION_TIMEOUT_MS

// eslint-disable-next-line no-process-env
const launchConfig = process.env.CI
Expand All @@ -33,26 +35,22 @@ const launchConfig = process.env.CI
}
: {}

function generateRandomInteger(min: number, max: number) {
return Math.floor(min + Math.random() * (max - min + 1))
}

export const delay = (time: number): Promise<void> => {
return new Promise((resolve) => {
setTimeout(resolve, time)
})
}

export default class CollaborationEnvironment extends NodeEnvironment {
private _browserA?: puppeteer.Browser
private _browserB?: puppeteer.Browser
private _pageA?: puppeteer.Page
private _pageB?: puppeteer.Page
private _browserA?: Browser
private _browserB?: Browser
private _pageA?: Page
private _pageB?: Page

public async setup(): Promise<void> {
await super.setup()
this._browserA = await puppeteer.launch(launchConfig)
this._browserB = await puppeteer.launch(launchConfig)
this._browserA = await launch(launchConfig)
this._browserB = await launch(launchConfig)
this._pageA = await this._browserA.newPage()
this._pageB = await this._browserB.newPage()

Expand All @@ -75,9 +73,11 @@ export default class CollaborationEnvironment extends NodeEnvironment {
}
this._pageA.on('pageerror', (err) => {
console.error('Editor A crashed', err)
throw err
})
this._pageB.on('pageerror', (err) => {
console.error('Editor B crashed', err)
throw err
})
await new Promise<void>((resolve) => {
ipc.connectToNet('socketServer', () => {
Expand Down Expand Up @@ -107,6 +107,7 @@ export default class CollaborationEnvironment extends NodeEnvironment {
value: PortableTextBlock[] | undefined
): Promise<void> => {
ipc.of.socketServer.emit('payload', JSON.stringify({type: 'value', value, testId}))
await delay(REVISION_TIMEOUT_MS) // Wait a little here for the payload to reach the clients
const [valueHandleA, valueHandleB] = await Promise.all([
this._pageA?.waitForSelector('#pte-value'),
this._pageB?.waitForSelector('#pte-value'),
Expand Down Expand Up @@ -138,15 +139,12 @@ export default class CollaborationEnvironment extends NodeEnvironment {
const isMac = /Mac|iPod|iPhone|iPad/.test(userAgent)
const metaKey = isMac ? 'Meta' : 'Control'
const editorId = `${['A', 'B'][index]}${testId}`
const [
editableHandle,
selectionHandle,
valueHandle,
]: (ElementHandle<HTMLDivElement> | null)[] = await Promise.all([
page.waitForSelector('div[contentEditable="true"]'),
page.waitForSelector('#pte-selection'),
page.waitForSelector('#pte-value'),
])
const [editableHandle, selectionHandle, valueHandle]: (ElementHandle<Element> | null)[] =
await Promise.all([
page.waitForSelector('div[contentEditable="true"]'),
page.waitForSelector('#pte-selection'),
page.waitForSelector('#pte-value'),
])

if (!editableHandle || !selectionHandle || !valueHandle) {
throw new Error('Failed to find required editor elements')
Expand All @@ -164,7 +162,7 @@ export default class CollaborationEnvironment extends NodeEnvironment {
}
const getSelection = async (): Promise<EditorSelection | null> => {
const selection = await selectionHandle.evaluate((node) =>
node.innerText ? JSON.parse(node.innerText) : null
node instanceof HTMLElement && node.innerText ? JSON.parse(node.innerText) : null
)
return selection
}
Expand All @@ -179,7 +177,7 @@ export default class CollaborationEnvironment extends NodeEnvironment {

const waitForSelection = async (selection: EditorSelection) => {
const value = await valueHandle.evaluate((node): PortableTextBlock[] | undefined =>
node.innerText ? JSON.parse(node.innerText) : undefined
node instanceof HTMLElement && node.innerText ? JSON.parse(node.innerText) : undefined
)
const normalized = normalizeSelection(selection, value)
const dataVal = JSON.stringify(normalized)
Expand Down Expand Up @@ -280,11 +278,14 @@ export default class CollaborationEnvironment extends NodeEnvironment {
editorId,
})
)
await delay(REVISION_TIMEOUT_MS) // Wait a little here for the payload to reach the client
await waitForSelection(selection)
},
async getValue(): Promise<PortableTextBlock[] | undefined> {
const value = await valueHandle.evaluate((node): PortableTextBlock[] | undefined =>
node.innerText ? JSON.parse(node.innerText) : undefined
node instanceof HTMLElement && node.innerText
? JSON.parse(node.innerText)
: undefined
)
return value
},
Expand Down
@@ -1,5 +1,6 @@
import {PortableTextBlock} from '@sanity/types'
import {KeyInput} from 'puppeteer'
import {EditorSelection, PortableTextBlock} from '../../src'
import {EditorSelection} from '../../src'

export {}

Expand Down
13 changes: 8 additions & 5 deletions packages/@sanity/portable-text-editor/test/web-server/app.tsx
@@ -1,7 +1,8 @@
import {PortableTextBlock} from '@sanity/types'
import {Box, Card, Stack, studioTheme, ThemeProvider} from '@sanity/ui'
import React, {useCallback, useMemo, useState} from 'react'
import {Subject} from 'rxjs'
import {EditorSelection, Patch, PortableTextBlock} from '../../src'
import {EditorSelection, Patch} from '../../src'
import {Editor} from './components/Editor'
import {Value} from './components/Value'

Expand Down Expand Up @@ -73,7 +74,11 @@ export function App() {
return (
<ThemeProvider theme={studioTheme}>
<Stack>
<Card padding={[3, 4, 5, 6]} sizing="border">
<Card padding={[0, 3]} sizing="border">
<Box>editorId: {editorId}</Box>
<Box>testId: {testId}</Box>
</Card>
<Card padding={[0, 3]} sizing="border">
<Box marginBottom={5}>
<Editor
editorId={editorId}
Expand All @@ -84,9 +89,7 @@ export function App() {
/>
</Box>
</Card>
</Stack>
<Stack>
<Card padding={[3, 4, 5, 6]} sizing="border">
<Card padding={[0, 3]} sizing="border">
<Value value={value || undefined} revId={revId || ''} />
</Card>
</Stack>
Expand Down

0 comments on commit 05abec0

Please sign in to comment.