Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
65 commits
Select commit Hold shift + click to select a range
c3c0a52
feat: add test fro rich editor
katsyuta May 26, 2026
1125545
refactor: use dom api for text selected
katsyuta May 30, 2026
a2779d1
test: add tests for image
katsyuta May 30, 2026
3394b17
test: update setup
katsyuta May 30, 2026
30081a9
refactor: remove editor, it do not necessary
katsyuta May 30, 2026
2781d07
test: remove snapshot test
katsyuta May 30, 2026
ec3230c
chore: rename
katsyuta May 30, 2026
0d13492
chore: rename
katsyuta May 31, 2026
19518e3
chore: update snapshot
katsyuta May 31, 2026
8f01bbd
test: add cases
katsyuta May 31, 2026
88638ec
refactor: use async api only when it necessary
katsyuta Jun 1, 2026
545123a
chore: improve helper func
katsyuta Jun 2, 2026
6b25738
chore: remove unnecessary package
katsyuta Jun 2, 2026
c05f19b
chore: remove
katsyuta Jun 2, 2026
77ef6ea
refactor: improve helper api
katsyuta Jun 4, 2026
47b9982
chore: docs
katsyuta Jun 4, 2026
e874e13
chore: fix typo
katsyuta Jun 7, 2026
b18507a
fix: wait before check image
katsyuta Jun 7, 2026
412d8bf
fix: improve checks
katsyuta Jun 7, 2026
8c74246
chore: style
katsyuta Jun 7, 2026
aa0dfed
fix: use iterator in mock, remove act
katsyuta Jun 7, 2026
5d06adc
chore: improve the searching text node in utils
katsyuta Jun 7, 2026
2c8902b
chore: fix typo, remove only
katsyuta Jun 7, 2026
110acb6
fix: ts error
katsyuta Jun 7, 2026
5d4cc0c
chore: remove comment
katsyuta Jun 8, 2026
0b5d66d
chore: rename
katsyuta Jun 8, 2026
b3d1a58
fix: add chakra provider
katsyuta Jun 8, 2026
825ae4e
chore: rename folder
katsyuta Jun 18, 2026
f1a6de8
refactor: utils must receive the node
katsyuta Jun 18, 2026
c864910
refactor: set position for cursor
katsyuta Jun 18, 2026
0e85f5b
test: add test for few editors
katsyuta Jun 18, 2026
d45539b
chore: rename, fix error
katsyuta Jun 18, 2026
05ddeb6
test: improve test, add lib for trigger event
katsyuta Jun 18, 2026
efbff64
refactor: improve basic md test
katsyuta Jun 18, 2026
6ee1625
refactor: vitest settings, rename
katsyuta Jun 18, 2026
3b8e1c9
chore: typo, remove unncessary file
katsyuta Jun 18, 2026
be2f358
refactor: mock only necessary deps in provider
katsyuta Jun 18, 2026
9a9e43b
chore: rename
katsyuta Jun 18, 2026
5b1c11d
test: update
katsyuta Jun 19, 2026
f4422d1
test: add
katsyuta Jun 19, 2026
a837265
test: add
katsyuta Jun 19, 2026
b2772d0
chore: rename file with md example
katsyuta Jun 23, 2026
f65e50b
chore: rename
katsyuta Jun 23, 2026
69d627b
test: add. rename
katsyuta Jun 23, 2026
b3eb322
Merge branch 'master' into 208-add-tests-for-a-richeditor
katsyuta Jun 23, 2026
1e072ee
fix: import error
katsyuta Jun 23, 2026
6550eb2
Merge branch 'master' into 208-add-tests-for-a-richeditor
katsyuta Jun 24, 2026
8d3f295
chore: rename, update comment
katsyuta Jun 24, 2026
df2f60e
chore: typo
katsyuta Jun 24, 2026
897c478
chore: rename
katsyuta Jun 24, 2026
a3518d2
chore: update expectations
katsyuta Jun 24, 2026
e46dec6
chore: rename
katsyuta Jun 24, 2026
d261fab
chore: fix
katsyuta Jun 24, 2026
0b8a1b3
chore: improve checks
katsyuta Jun 24, 2026
ffaa9f4
chore: define node mock not global
katsyuta Jun 24, 2026
a30d29e
Merge branch 'master' into 208-add-tests-for-a-richeditor
katsyuta Jun 27, 2026
57bbc78
chore: move deps
katsyuta Jun 27, 2026
1118932
test: update checks
katsyuta Jun 27, 2026
f72de2b
test: update checks
katsyuta Jun 28, 2026
bf6c002
chore: remove unnecessary dep
katsyuta Jun 29, 2026
483ef76
refactor: sort before snapshot
katsyuta Jun 29, 2026
641639b
chore: rename
katsyuta Jun 29, 2026
e577ae0
Merge branch 'master' into 208-add-tests-for-a-richeditor
katsyuta Jun 29, 2026
a80f07c
chore: update snapshot
katsyuta Jun 29, 2026
f0095c5
chore: update checks
katsyuta Jun 29, 2026
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
388 changes: 178 additions & 210 deletions package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions packages/app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
"@swc/core": "^1.15.11",
"@testing-library/jest-dom": "^6.9.1",
"@testing-library/react": "^16.3.2",
"@testing-library/user-event": "^14.6.1",
"@types/bytes": "^3.1.5",
"@types/file-saver": "^2.0.7",
"@types/humanize-duration": "^3.27.4",
Expand Down
23 changes: 23 additions & 0 deletions packages/app/scripts/vitest.setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,27 @@ const isDOMLikeEnv = typeof window !== 'undefined' && typeof document !== 'undef
if (isDOMLikeEnv) {
require('@testing-library/jest-dom');
require('blob-polyfill');

// Mock this function because jsdom does not implement the layout API; getClientRects returns an object without the `item` method,
// causing HighlightingPlugin to crash
Element.prototype.getClientRects = () =>
({
item: () => null,
length: 0,
[Symbol.iterator]: function* () {},
}) as unknown as DOMRectList;

// Mock this function because the Rich Editor uses it to calculate cursor and selection positions.
// Tests may fail without it
Range.prototype.getBoundingClientRect = () => ({
width: 0,
height: 0,
top: 0,
left: 0,
right: 0,
bottom: 0,
x: 0,
y: 0,
toJSON: () => {},
});
}
20 changes: 12 additions & 8 deletions packages/app/src/core/storage/interop/export/NotesExporter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,9 +107,11 @@ test('Export all notes and attached files', async () => {
const filesList = exportTarget.list();
await expect(
filesList.then((paths) =>
paths.map((path) =>
path.replaceAll(/[a-z\d]+(-[a-z\d]+){4}/g, '[REDACTED-UUID]'),
),
paths
.map((path) =>
path.replaceAll(/[a-z\d]+(-[a-z\d]+){4}/g, '[REDACTED-UUID]'),
)
.sort(),
),
).resolves.toMatchSnapshot('Files list');

Expand All @@ -129,7 +131,7 @@ test('Export all notes and attached files', async () => {
.replace(/updated: \d+/, `updated: ${200_000 + index}`)
.replaceAll(/[a-z\d]+(-[a-z\d]+){4}/g, '[REDACTED-UUID]');
}),
),
).then((notes) => notes.sort()),
).resolves.toMatchSnapshot('Notes texts');
});

Expand Down Expand Up @@ -218,9 +220,11 @@ test('Export all notes and attached files with custom file names', async () => {
const filesList = exportTarget.list();
await expect(
filesList.then((paths) =>
paths.map((path) =>
path.replaceAll(/[a-z\d]+(-[a-z\d]+){4}/g, '[REDACTED-UUID]'),
),
paths
.map((path) =>
path.replaceAll(/[a-z\d]+(-[a-z\d]+){4}/g, '[REDACTED-UUID]'),
)
.sort(),
),
).resolves.toMatchSnapshot('Files list');

Expand All @@ -240,7 +244,7 @@ test('Export all notes and attached files with custom file names', async () => {
.replace(/updated: \d+/, `updated: ${200_000 + index}`)
.replaceAll(/[a-z\d]+(-[a-z\d]+){4}/g, '[REDACTED-UUID]');
}),
),
).then((notes) => notes.sort()),
).resolves.toMatchSnapshot('Notes texts');
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ exports[`Export all notes and attached files > Files list 1`] = `
"/[REDACTED-UUID].md",
"/[REDACTED-UUID].md",
"/[REDACTED-UUID].md",
"/_resources/[REDACTED-UUID]-test.txt",
Comment thread
vitonsky marked this conversation as resolved.
"/[REDACTED-UUID].md",
"/[REDACTED-UUID].md",
"/_resources/[REDACTED-UUID]-test.txt",
]
`;

Expand All @@ -25,21 +25,11 @@ tags:
---

Hello world!
",
"---
title: Note 5
created: 100001
updated: 200001
tags: []

---

Note with [invalid attachment](res://[REDACTED-UUID]) and mention for [invalid note](note://[REDACTED-UUID])
",
"---
title: Note 2
created: 100002
updated: 200002
created: 100003
updated: 200003
tags: []

---
Expand All @@ -48,8 +38,8 @@ Mention for [Note 1](./[REDACTED-UUID].md)
",
"---
title: Note 3
created: 100003
updated: 200003
created: 100002
updated: 200002
tags: []

---
Expand All @@ -65,6 +55,16 @@ tags: []
---

Note with [attachment](./_resources/[REDACTED-UUID]-test.txt) and mention for [Note 1](./[REDACTED-UUID].md)
",
"---
title: Note 5
created: 100001
updated: 200001
tags: []

---

Note with [invalid attachment](res://[REDACTED-UUID]) and mention for [invalid note](note://[REDACTED-UUID])
",
]
`;
Expand Down Expand Up @@ -106,12 +106,12 @@ exports[`Export all notes and attached files > onProcessed hook calls 1`] = `

exports[`Export all notes and attached files with custom file names > Files list 1`] = `
[
"/notes/bar/[REDACTED-UUID]-Note 1.md",
"/notes/[REDACTED-UUID]-Note 5.md",
"/notes/[REDACTED-UUID]-Note 2.md",
"/files/[REDACTED-UUID]-test.txt",
"/notes/[REDACTED-UUID]-Note 2.md",
"/notes/[REDACTED-UUID]-Note 3.md",
"/notes/[REDACTED-UUID]-Note 4.md",
"/notes/[REDACTED-UUID]-Note 5.md",
"/notes/bar/[REDACTED-UUID]-Note 1.md",
]
`;

Expand All @@ -129,21 +129,11 @@ tags:
---

Hello world!
",
"---
title: Note 5
created: 100001
updated: 200001
tags: []

---

Note with [invalid attachment](res://[REDACTED-UUID]) and mention for [invalid note](note://[REDACTED-UUID])
",
"---
title: Note 2
created: 100002
updated: 200002
created: 100003
updated: 200003
tags: []

---
Expand All @@ -152,8 +142,8 @@ Mention for [Note 1](<./bar/[REDACTED-UUID]-Note 1.md>)
",
"---
title: Note 3
created: 100003
updated: 200003
created: 100002
updated: 200002
tags: []

---
Expand All @@ -169,6 +159,16 @@ tags: []
---

Note with [attachment](../files/[REDACTED-UUID]-test.txt) and mention for [Note 1](<./bar/[REDACTED-UUID]-Note 1.md>)
",
"---
title: Note 5
created: 100001
updated: 200001
tags: []

---

Note with [invalid attachment](res://[REDACTED-UUID]) and mention for [invalid note](note://[REDACTED-UUID])
",
]
`;
Expand Down
Comment thread
vitonsky marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { screen, within } from '@testing-library/react';
import userEvent from '@testing-library/user-event';

import { renderRichEditor } from './utils/renderRichEditor';
import { selectContent } from './utils/utils';

test('Editor updates when value changes', async () => {
const editor = await renderRichEditor({ value: `# Big text` });

expect(screen.getByRole('heading', { level: 1 })).toHaveTextContent('Big text');

// Run component rerender with new value
await editor.rerender({ value: `### Not so big text` });

expect(screen.getByRole('heading', { level: 3 })).toHaveTextContent(
'Not so big text',
);

// The old header was removed
expect(screen.queryByRole('heading', { level: 1 })).not.toBeInTheDocument();
});

test('Editor is not editable in readonly mode', async () => {
const user = userEvent.setup();
await renderRichEditor({
value: '# Hello',
isReadOnly: true,
});

expect(screen.getByRole('textbox')).toHaveAttribute('contenteditable', 'false');

// Cannot enter text
await user.click(screen.getByRole('heading'));
await user.keyboard('Some text');

expect(screen.getByRole('textbox')).toHaveTextContent('Hello');
expect(screen.getByRole('textbox')).not.toHaveTextContent('Some text');
Comment thread
katsyuta marked this conversation as resolved.
});

test('Formatting text in one editor does not affect the other editor', async () => {
const editorA = await renderRichEditor({ value: 'Big text' });
const editorB = await renderRichEditor({ value: 'Small text' });

const textBoxes = screen.getAllByRole('textbox');
expect(textBoxes).toHaveLength(2);

// initial state
expect(within(textBoxes[0]).queryByRole('paragraph')).toHaveTextContent('Big text');
expect(within(textBoxes[1]).queryByRole('paragraph')).toHaveTextContent('Small text');

// apply italic in editorA
selectContent(textBoxes[0], 'Big text');
await editorA.format('italic');

expect(within(textBoxes[0]).getByRole('emphasis')).toHaveTextContent('Big text');
expect(within(textBoxes[1]).queryByRole('emphasis')).not.toBeInTheDocument();

// apply strikethrough in editorB
selectContent(textBoxes[1], 'Small text');
await editorB.format('strikethrough');

expect(within(textBoxes[1]).getByRole('deletion')).toHaveTextContent('Small text');
expect(within(textBoxes[1]).queryByRole('emphasis')).not.toBeInTheDocument();

expect(within(textBoxes[0]).getByRole('emphasis')).toHaveTextContent('Big text');
expect(within(textBoxes[0]).queryByRole('deletion')).not.toBeInTheDocument();
});

test('ReadOnly editor is not editable while the other editor remains editable', async () => {
const user = userEvent.setup();
await renderRichEditor({ value: 'Editable text' });
await renderRichEditor({ value: 'ReadOnly text', isReadOnly: true });

const textBoxes = screen.getAllByRole('textbox');
expect(textBoxes).toHaveLength(2);

expect(textBoxes[0]).toHaveAttribute('contenteditable', 'true');
expect(textBoxes[1]).toHaveAttribute('contenteditable', 'false');

// editorA is editable — editing works
await user.click(within(textBoxes[0]).getByText('Editable text'));
await user.keyboard('New text ');

expect(textBoxes[0]).toHaveTextContent('New text Editable text');

// editorB is readOnly — editing must be ignored
await user.click(within(textBoxes[1]).getByText('ReadOnly text'));
await user.keyboard('Some text');

expect(textBoxes[1]).toHaveTextContent('ReadOnly text');
expect(textBoxes[1]).not.toHaveTextContent('Some text');
expect(textBoxes[1]).toHaveAttribute('contenteditable', 'false');
});
Loading