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

test: [TOL-1361] Rich Text editor component tests #1565

Merged
merged 41 commits into from
Feb 23, 2024
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
90aa6d1
test: rich text commands tests
colomolo Dec 19, 2023
338f7b7
test: add embedded asset blocks
colomolo Dec 20, 2023
6836913
test: add embedded entry blocks tests
colomolo Dec 20, 2023
7a58272
test: add entry inlines test
colomolo Dec 20, 2023
90ac5dc
test: add resource blocks tests
colomolo Dec 20, 2023
263c7b6
test: add resources inline tests
colomolo Dec 21, 2023
98a5ee7
test: add headings tests
colomolo Dec 21, 2023
487c5a2
test: add hr tests
colomolo Dec 21, 2023
6bfd5c4
test: add links tests
colomolo Dec 21, 2023
f31a8d2
test: add marks tests
colomolo Dec 21, 2023
e23a590
chore: fix minor things
colomolo Dec 21, 2023
30079c7
test: add pasting tests
colomolo Dec 22, 2023
a990d99
test: add quotes tests
colomolo Dec 22, 2023
971c8f4
chore: add tab plugin to cypres config
colomolo Dec 22, 2023
1c30ceb
test: add fixtures
colomolo Dec 22, 2023
326518b
chore: clean up
colomolo Feb 13, 2024
645f451
test: add richtext editor suite
colomolo Feb 14, 2024
dbc3a57
test: add Tables suite
colomolo Feb 15, 2024
41bd2d5
test: add escape inlines suite
colomolo Feb 15, 2024
b75beba
chore: clean up rich text page
colomolo Feb 15, 2024
c3b8f88
chore: remove helpers duplication
colomolo Feb 16, 2024
31f3826
test: add list suite
colomolo Feb 16, 2024
d9f1717
test: add tracking suite
colomolo Feb 19, 2024
80876a4
chore: refactor test suites tsx→ts
colomolo Feb 19, 2024
8f2de06
Merge branch 'master' into TOL-1361-rich-text-editor-component-tests
colomolo Feb 19, 2024
3e22b76
chore: clean up e2e stuff
colomolo Feb 19, 2024
c6d99f7
fix: ci pipeline
colomolo Feb 19, 2024
21e3c49
fix: ci pipeline 2
colomolo Feb 19, 2024
853b8c4
Merge branch 'master' into TOL-1361-rich-text-editor-component-tests
colomolo Feb 20, 2024
f9b2461
test: fix mount props passing
colomolo Feb 20, 2024
7ceadf0
test: bring back frame sizes
colomolo Feb 20, 2024
7eb0220
test: optimize select all action
colomolo Feb 20, 2024
a64ed50
test: move delete codes to const
colomolo Feb 20, 2024
0bc033f
test: try remove wait on getValue
colomolo Feb 20, 2024
ca3c915
test: revert getValue wait
colomolo Feb 20, 2024
067303c
chore: address review issues
colomolo Feb 21, 2024
5afb685
test: tighten assertions
colomolo Feb 22, 2024
b40bcd1
test: toggle headers on real text (to reduce flakyness)
colomolo Feb 22, 2024
8daf79d
Merge branch 'master' into TOL-1361-rich-text-editor-component-tests
colomolo Feb 22, 2024
040940c
chore: update yarn.lock
colomolo Feb 22, 2024
f5d8ebe
Revert "chore: update yarn.lock"
colomolo Feb 22, 2024
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
2 changes: 1 addition & 1 deletion cypress.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ export default defineConfig({
module: {
rules: [
{
test: /\.tsx?$/,
test: /\.t|jsx?$/,
exclude: [/node_modules/],
use: [
{
Expand Down
224 changes: 224 additions & 0 deletions cypress/component/rich-text/RichTextEditor.Commands.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
import React from 'react';

import { BLOCKS, INLINES } from '@contentful/rich-text-types';

import { RichTextEditor } from '../../../packages/rich-text/src';
import { block, document as doc, text } from '../../../packages/rich-text/src/helpers/nodeFactory';
import { createRichTextFakeSdk } from '../../fixtures';
import { mount } from '../mount';
import { RichTextPage } from './RichTextPage';

describe('Rich Text Editor - Commands', () => {
let richText: RichTextPage;

beforeEach(() => {
const sdk = createRichTextFakeSdk();
richText = new RichTextPage();

mount(<RichTextEditor sdk={sdk} isInitiallyDisabled={false} />);
});

describe('Palette', () => {
const getPalette = () => cy.findByTestId('rich-text-commands');
const getCommandList = () => cy.findByTestId('rich-text-commands-list');

it('should be visible', () => {
richText.editor.click().type('/');
getPalette().should('be.visible');
});

it('should close on pressing esc', () => {
richText.editor.click().type('/');
getPalette().should('be.visible');
richText.editor.type('{esc}');
getPalette().should('not.exist');
richText.expectValue(doc(block(BLOCKS.PARAGRAPH, {}, text('/'))));
});

it('should be searchable', () => {
richText.editor.click().type('/asset');
getCommandList()
.children()
.each((child) => {
cy.wrap(child).should('include.text', 'Asset');
});
});

it('should delete search text after navigating', () => {
richText.editor.click().type('/asset');
getCommandList().findByText('Embed Asset').click();
richText.expectValue(doc(block(BLOCKS.PARAGRAPH, {}, text('/'))));
});

it('should navigate on category enter', () => {
richText.editor.click().type('/');
getCommandList().findByText('Embed Example Content Type').click();
getCommandList().should('be.visible');
getCommandList().findByText('Embed Example Content Type').should('not.exist');
});

it('should embed entry', () => {
richText.editor.click().type('/');
getCommandList().findByText('Embed Example Content Type').click();
getCommandList().findByText('The best article ever').click();

//this is used instead of snapshot value because we have randomized entry IDs
richText.getValue().should((doc) => {
expect(
doc.content.filter((node) => node.nodeType === BLOCKS.EMBEDDED_ENTRY)
).to.have.length(1);
});
});

it('should embed inline', () => {
richText.editor.click().type('/');
getCommandList().findByText('Embed Example Content Type - Inline').click();
getCommandList().findByText('The best article ever').click();

//this is used instead of snapshot value because we have randomized entry IDs
richText.getValue().should((doc) => {
expect(
doc.content[0].content.filter((node) => node.nodeType === INLINES.EMBEDDED_ENTRY)
).to.have.length(1);
});
});

it('should embed asset', () => {
richText.editor.click().type('/');
getCommandList().findByText('Embed Asset').click();
getCommandList().findByText('test').click();

const expectedValue = doc(
block(BLOCKS.PARAGRAPH, {}, text()),
block(BLOCKS.EMBEDDED_ASSET, {
target: { sys: { id: 'published_asset', type: 'Link', linkType: 'Asset' } },
}),
block(BLOCKS.PARAGRAPH, {}, text())
);

richText.expectValue(expectedValue);
});

it('should delete command after embedding', () => {
richText.editor.click().type('/');
getCommandList().findByText('Embed Example Content Type').click();
getCommandList().findByText('The best article ever').click();

richText.editor.children().contains('/').should('not.exist');
});

it('should navigate then embed on pressing enter', () => {
richText.editor.click().type('/');
getCommandList().findByText('Embed Example Content Type').should('exist');
richText.editor.type('{enter}');
getCommandList().findByText('Embed Example Content Type').should('not.exist');
richText.editor.type('{enter}');

//this is used instead of snapshot value because we have randomized entry IDs
richText.getValue().should((doc) => {
expect(
doc.content.filter((node) => node.nodeType === BLOCKS.EMBEDDED_ENTRY)
).to.have.length(1);
});
});

it('should select next item on down arrow press', () => {
richText.editor.click().type('/{downarrow}{enter}{enter}');

richText.editor.findByTestId('embedded-entry-inline').should('exist');

//this is used instead of snapshot value because we have randomized entry IDs
richText.getValue().should((doc) => {
expect(
doc.content[0].content.filter((node) => node.nodeType === INLINES.EMBEDDED_ENTRY)
).to.have.length(1);
});
});

it('should select previous item on up arrow press', () => {
richText.editor.click().type('/{downarrow}{uparrow}{enter}{enter}');

//this is used instead of snapshot value because we have randomized entry IDs
richText.getValue().should((doc) => {
expect(
doc.content.filter((node) => node.nodeType === BLOCKS.EMBEDDED_ENTRY)
).to.have.length(1);
});
});

it('should not delete adjacent text', () => {
richText.editor.click().type('test/{downarrow}{enter}{enter}');

//this is used instead of snapshot value because we have randomized entry IDs
richText.getValue().should((doc) => {
expect(doc.content[0].content[0].value).to.equal('test');
expect(
doc.content[0].content.filter((node) => node.nodeType === INLINES.EMBEDDED_ENTRY)
).to.have.length(1);
});
});

it('should work inside headings', () => {
richText.editor.click().type('Heading 1');
richText.toolbar.toggleHeading(BLOCKS.HEADING_1);
richText.editor.click().type('/{enter}{enter}');

//this is used instead of snapshot value because we have randomized entry IDs
richText.getValue().should((doc) => {
expect(
doc.content.filter((node) => {
return node.nodeType === BLOCKS.EMBEDDED_ENTRY || node.nodeType === BLOCKS.HEADING_1;
})
).to.have.length(2);
});
});

it('should be disabled without any action item', () => {
const sdk = createRichTextFakeSdk({
validations: [
{
enabledNodeTypes: ['heading-1'],
},
],
});
mount(<RichTextEditor sdk={sdk} isInitiallyDisabled={false} actionsDisabled={true} />);

// try to open command prompt
richText.editor.click().type('/');
getPalette().should('not.exist');

// try to press enter and type content, which would not work with the open palette
richText.editor.click().type('{enter}Hello');
richText.expectValue({
nodeType: 'document',
data: {},
content: [
{
nodeType: 'paragraph',
data: {},
content: [
{
nodeType: 'text',
value: '/',
marks: [],
data: {},
},
],
},
{
nodeType: 'paragraph',
data: {},
content: [
{
nodeType: 'text',
value: 'Hello',
marks: [],
data: {},
},
],
},
],
});
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import React from 'react';

import { BLOCKS } from '@contentful/rich-text-types';

import { RichTextEditor } from '../../../packages/rich-text/src';
import { block, document as doc, text } from '../../../packages/rich-text/src/helpers/nodeFactory';
import { createRichTextFakeSdk } from '../../fixtures';
import { mod } from '../../fixtures/utils';
import { mount } from '../mount';
import { KEYS, emptyParagraph, paragraphWithText } from './helpers';
import { RichTextPage } from './RichTextPage';

// the sticky toolbar gets in the way of some of the tests, therefore
// we increase the viewport height to fit the whole page on the screen

describe('Rich Text Editor - Embedded Entry Assets', { viewportHeight: 2000 }, () => {
let richText: RichTextPage;
const expectDocumentToBeEmpty = () => richText.expectValue(undefined);

const assetBlock = () =>
block(BLOCKS.EMBEDDED_ASSET, {
target: {
sys: {
id: 'published_asset',
type: 'Link',
linkType: 'Asset',
},
},
});

beforeEach(() => {
const sdk = createRichTextFakeSdk();
richText = new RichTextPage();

mount(<RichTextEditor sdk={sdk} isInitiallyDisabled={false} />);
});

const methods: [string, () => void][] = [
[
'using the toolbar button',
() => {
richText.toolbar.embed('asset-block');
},
],
[
'using the keyboard shortcut',
() => {
richText.editor.type(`{${mod}+shift+a}`);
},
],
];

for (const [triggerMethod, triggerEmbeddedAsset] of methods) {
describe(triggerMethod, () => {
it('adds paragraph before the block when pressing enter if the block is first document node', () => {
richText.editor.click();
triggerEmbeddedAsset();

richText.editor.find('[data-entity-id="published_asset"]').click();
richText.editor.trigger('keydown', KEYS.enter);

richText.expectValue(doc(emptyParagraph(), assetBlock(), emptyParagraph()));
});

it('adds paragraph between two blocks when pressing enter', () => {
function addEmbeddedAsset() {
richText.editor.click('bottom').then(triggerEmbeddedAsset);
richText.editor.click('bottom');
}

addEmbeddedAsset();
addEmbeddedAsset();

// Press enter on the first asset block
richText.editor.click().find('[data-entity-id="published_asset"]').first().click();
richText.editor.trigger('keydown', KEYS.enter);

// Press enter on the second asset block
richText.editor.click().find('[data-entity-id="published_asset"]').first().click();
richText.editor.trigger('keydown', KEYS.enter);

richText.expectValue(
doc(emptyParagraph(), assetBlock(), emptyParagraph(), assetBlock(), emptyParagraph())
);
});

it('adds and removes embedded assets', () => {
richText.editor.click().then(triggerEmbeddedAsset);

richText.expectValue(doc(assetBlock(), emptyParagraph()));

cy.findByTestId('cf-ui-card-actions').click();
cy.findByTestId('card-action-remove').click();

richText.expectValue(undefined);
});

it('adds and removes embedded assets by selecting and pressing `backspace`', () => {
richText.editor.click().then(triggerEmbeddedAsset);

richText.expectValue(doc(assetBlock(), emptyParagraph()));

cy.findByTestId('cf-ui-asset-card').click();
// .type('{backspace}') does not work on non-typable elements.(contentEditable=false)
richText.editor.trigger('keydown', KEYS.backspace);

richText.expectValue(undefined);
});

it('adds embedded assets between words', () => {
richText.editor
.click()
.type('foobar{leftarrow}{leftarrow}{leftarrow}')
.then(triggerEmbeddedAsset);

richText.expectValue(
doc(
block(BLOCKS.PARAGRAPH, {}, text('foo')),
assetBlock(),
block(BLOCKS.PARAGRAPH, {}, text('bar'))
)
);
});

it('should be selected on backspace', () => {
richText.editor.click();
triggerEmbeddedAsset();

richText.editor.type('{downarrow}X');

richText.expectValue(doc(assetBlock(), paragraphWithText('X')));

richText.editor.type('{backspace}{backspace}');

richText.expectValue(doc(assetBlock(), emptyParagraph()));

richText.editor.type('{backspace}');

expectDocumentToBeEmpty();
});
});
}
});
Loading
Loading