@tuturuuu/ui 0.28.1 → 0.29.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +23 -0
- package/biome.json +1 -1
- package/package.json +53 -53
- package/src/components/ui/text-editor/__tests__/image-extension-clipboard.test.ts +66 -0
- package/src/components/ui/text-editor/__tests__/inline-task-conversion.test.tsx +116 -5
- package/src/components/ui/text-editor/__tests__/markdown-paste-extension.test.ts +392 -3
- package/src/components/ui/text-editor/clipboard-image-files.ts +41 -0
- package/src/components/ui/text-editor/clipboard-serialization.ts +249 -0
- package/src/components/ui/text-editor/color-controls.tsx +1 -1
- package/src/components/ui/text-editor/copy-menu.tsx +129 -0
- package/src/components/ui/text-editor/editor.tsx +7 -0
- package/src/components/ui/text-editor/image-extension.ts +1 -8
- package/src/components/ui/text-editor/markdown-paste-extension.ts +39 -14
- package/src/components/ui/text-editor/tool-bar.tsx +17 -69
- package/src/components/ui/text-editor/toolbar-controls.tsx +59 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.29.1](https://github.com/tutur3u/platform/compare/ui-v0.29.0...ui-v0.29.1) (2026-08-21)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* **ci:** complete dependency and Rust validation ([61e4c12](https://github.com/tutur3u/platform/commit/61e4c12ec3c90707dde63ed7469854519b3688c8))
|
|
9
|
+
|
|
10
|
+
## [0.29.0](https://github.com/tutur3u/platform/compare/ui-v0.28.1...ui-v0.29.0) (2026-08-20)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Features
|
|
14
|
+
|
|
15
|
+
* **editor:** add copy format menu ([389a6f0](https://github.com/tutur3u/platform/commit/389a6f02d822feef271409a9ab31e8fcbb6ef18e))
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
### Bug Fixes
|
|
19
|
+
|
|
20
|
+
* **editor:** contain toolbar overflow ([6bda614](https://github.com/tutur3u/platform/commit/6bda61416cbd2e4d4d4af7c7337d7b4e905274ef))
|
|
21
|
+
* **editor:** keep copy menu in toolbar ([c9dd241](https://github.com/tutur3u/platform/commit/c9dd2416f7e1b397c3293445d0bd0564a48c6bda))
|
|
22
|
+
* **editor:** preserve semantic rich paste ([bea67ca](https://github.com/tutur3u/platform/commit/bea67cac57b52b82a9ed4d7fd1a8a892cf4ceea4))
|
|
23
|
+
* **editor:** preserve task clipboard structure ([2d05d86](https://github.com/tutur3u/platform/commit/2d05d8602f39975805944edfccb2e10818863413))
|
|
24
|
+
* **tasks:** restore pasted image uploads ([3edef4e](https://github.com/tutur3u/platform/commit/3edef4e04a1fc6963904b7861d422f8db9d5b749))
|
|
25
|
+
|
|
3
26
|
## [0.28.1](https://github.com/tutur3u/platform/compare/ui-v0.28.0...ui-v0.28.1) (2026-08-16)
|
|
4
27
|
|
|
5
28
|
|
package/biome.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tuturuuu/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.29.1",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -21,9 +21,9 @@
|
|
|
21
21
|
"@dnd-kit/sortable": "^10.0.0",
|
|
22
22
|
"@dnd-kit/utilities": "^3.2.2",
|
|
23
23
|
"@dqcai/vn-lunar": "^1.0.1",
|
|
24
|
-
"@hookform/resolvers": "^5.
|
|
25
|
-
"@json-render/core": "^0.
|
|
26
|
-
"@json-render/react": "^0.
|
|
24
|
+
"@hookform/resolvers": "^5.9.1",
|
|
25
|
+
"@json-render/core": "^0.20.0",
|
|
26
|
+
"@json-render/react": "^0.20.0",
|
|
27
27
|
"@radix-ui/react-accordion": "^1.2.20",
|
|
28
28
|
"@radix-ui/react-alert-dialog": "^1.1.23",
|
|
29
29
|
"@radix-ui/react-aspect-ratio": "^1.1.15",
|
|
@@ -59,41 +59,41 @@
|
|
|
59
59
|
"@tanstack/react-pacer": "^0.23.0",
|
|
60
60
|
"@tanstack/react-query": "^5.101.2",
|
|
61
61
|
"@tanstack/react-table": "^9.0.0",
|
|
62
|
-
"@tanstack/react-virtual": "^3.14.
|
|
63
|
-
"@tiptap/core": "3.30.
|
|
64
|
-
"@tiptap/extension-collaboration": "3.30.
|
|
65
|
-
"@tiptap/extension-collaboration-caret": "3.30.
|
|
66
|
-
"@tiptap/extension-color": "3.30.
|
|
67
|
-
"@tiptap/extension-details": "3.30.
|
|
68
|
-
"@tiptap/extension-drag-handle": "3.30.
|
|
69
|
-
"@tiptap/extension-drag-handle-react": "3.30.
|
|
70
|
-
"@tiptap/extension-highlight": "3.30.
|
|
71
|
-
"@tiptap/extension-horizontal-rule": "3.30.
|
|
72
|
-
"@tiptap/extension-image": "3.30.
|
|
73
|
-
"@tiptap/extension-link": "3.30.
|
|
74
|
-
"@tiptap/extension-list": "3.30.
|
|
75
|
-
"@tiptap/extension-node-range": "3.30.
|
|
76
|
-
"@tiptap/extension-placeholder": "3.30.
|
|
77
|
-
"@tiptap/extension-strike": "3.30.
|
|
78
|
-
"@tiptap/extension-subscript": "3.30.
|
|
79
|
-
"@tiptap/extension-superscript": "3.30.
|
|
80
|
-
"@tiptap/extension-table": "3.30.
|
|
81
|
-
"@tiptap/extension-table-cell": "3.30.
|
|
82
|
-
"@tiptap/extension-table-header": "3.30.
|
|
83
|
-
"@tiptap/extension-table-row": "3.30.
|
|
84
|
-
"@tiptap/extension-text-align": "3.30.
|
|
85
|
-
"@tiptap/extension-text-style": "3.30.
|
|
86
|
-
"@tiptap/extension-youtube": "3.30.
|
|
87
|
-
"@tiptap/pm": "3.30.
|
|
88
|
-
"@tiptap/react": "3.30.
|
|
89
|
-
"@tiptap/starter-kit": "3.30.
|
|
90
|
-
"@tuturuuu/ai": "0.8.
|
|
91
|
-
"@tuturuuu/apis": "0.11.
|
|
62
|
+
"@tanstack/react-virtual": "^3.14.10",
|
|
63
|
+
"@tiptap/core": "3.30.2",
|
|
64
|
+
"@tiptap/extension-collaboration": "3.30.2",
|
|
65
|
+
"@tiptap/extension-collaboration-caret": "3.30.2",
|
|
66
|
+
"@tiptap/extension-color": "3.30.2",
|
|
67
|
+
"@tiptap/extension-details": "3.30.2",
|
|
68
|
+
"@tiptap/extension-drag-handle": "3.30.2",
|
|
69
|
+
"@tiptap/extension-drag-handle-react": "3.30.2",
|
|
70
|
+
"@tiptap/extension-highlight": "3.30.2",
|
|
71
|
+
"@tiptap/extension-horizontal-rule": "3.30.2",
|
|
72
|
+
"@tiptap/extension-image": "3.30.2",
|
|
73
|
+
"@tiptap/extension-link": "3.30.2",
|
|
74
|
+
"@tiptap/extension-list": "3.30.2",
|
|
75
|
+
"@tiptap/extension-node-range": "3.30.2",
|
|
76
|
+
"@tiptap/extension-placeholder": "3.30.2",
|
|
77
|
+
"@tiptap/extension-strike": "3.30.2",
|
|
78
|
+
"@tiptap/extension-subscript": "3.30.2",
|
|
79
|
+
"@tiptap/extension-superscript": "3.30.2",
|
|
80
|
+
"@tiptap/extension-table": "3.30.2",
|
|
81
|
+
"@tiptap/extension-table-cell": "3.30.2",
|
|
82
|
+
"@tiptap/extension-table-header": "3.30.2",
|
|
83
|
+
"@tiptap/extension-table-row": "3.30.2",
|
|
84
|
+
"@tiptap/extension-text-align": "3.30.2",
|
|
85
|
+
"@tiptap/extension-text-style": "3.30.2",
|
|
86
|
+
"@tiptap/extension-youtube": "3.30.2",
|
|
87
|
+
"@tiptap/pm": "3.30.2",
|
|
88
|
+
"@tiptap/react": "3.30.2",
|
|
89
|
+
"@tiptap/starter-kit": "3.30.2",
|
|
90
|
+
"@tuturuuu/ai": "0.8.3",
|
|
91
|
+
"@tuturuuu/apis": "0.11.3",
|
|
92
92
|
"@tuturuuu/hooks": "0.1.1",
|
|
93
93
|
"@tuturuuu/icons": "0.1.0",
|
|
94
|
-
"@tuturuuu/internal-api": "0.31.
|
|
95
|
-
"@tuturuuu/supabase": "0.5.
|
|
96
|
-
"@tuturuuu/utils": "0.25.
|
|
94
|
+
"@tuturuuu/internal-api": "0.31.1",
|
|
95
|
+
"@tuturuuu/supabase": "0.5.1",
|
|
96
|
+
"@tuturuuu/utils": "0.25.4",
|
|
97
97
|
"@types/debug": "^4.1.13",
|
|
98
98
|
"browser-image-compression": "^2.0.2",
|
|
99
99
|
"class-variance-authority": "^0.7.1",
|
|
@@ -101,23 +101,23 @@
|
|
|
101
101
|
"cookies-next": "^6.1.1",
|
|
102
102
|
"date-fns": "^4.4.0",
|
|
103
103
|
"date-fns-tz": "^3.2.0",
|
|
104
|
-
"dayjs": "^1.11.
|
|
104
|
+
"dayjs": "^1.11.23",
|
|
105
105
|
"debug": "^4.4.3",
|
|
106
106
|
"embla-carousel-react": "^8.6.0",
|
|
107
107
|
"eventemitter3": "^5.0.4",
|
|
108
|
-
"framer-motion": "^13.1.
|
|
108
|
+
"framer-motion": "^13.1.1",
|
|
109
109
|
"gsap": "^3.15.0",
|
|
110
110
|
"html-entities": "^2.6.0",
|
|
111
|
-
"html2canvas-pro": "^2.3.
|
|
112
|
-
"input-otp": "^1.
|
|
111
|
+
"html2canvas-pro": "^2.3.9",
|
|
112
|
+
"input-otp": "^1.5.0",
|
|
113
113
|
"katex": "^0.18.4",
|
|
114
114
|
"lodash": "4.18.1",
|
|
115
115
|
"moment": "^2.30.1",
|
|
116
|
-
"motion": "^13.1.
|
|
117
|
-
"next": "^16.3.
|
|
118
|
-
"next-intl": "^4.13.
|
|
116
|
+
"motion": "^13.1.1",
|
|
117
|
+
"next": "^16.3.2",
|
|
118
|
+
"next-intl": "^4.13.7",
|
|
119
119
|
"next-themes": "^0.4.6",
|
|
120
|
-
"nuqs": "^2.
|
|
120
|
+
"nuqs": "^2.10.0",
|
|
121
121
|
"prosemirror-state": "^1.4.4",
|
|
122
122
|
"prosemirror-view": "1.42.2",
|
|
123
123
|
"qrcode.react": "^4.2.0",
|
|
@@ -125,13 +125,13 @@
|
|
|
125
125
|
"react-colorful": "^5.8.0",
|
|
126
126
|
"react-day-picker": "^10.0.1",
|
|
127
127
|
"react-dom": "^19.2.8",
|
|
128
|
-
"react-dropzone": "^20.1.
|
|
128
|
+
"react-dropzone": "^20.1.1",
|
|
129
129
|
"react-hook-form": "^7.85.0",
|
|
130
130
|
"react-markdown": "^10.1.0",
|
|
131
131
|
"react-papaparse": "^4.4.0",
|
|
132
|
-
"react-pdf": "^10.
|
|
132
|
+
"react-pdf": "^10.5.0",
|
|
133
133
|
"react-resizable": "^4.0.2",
|
|
134
|
-
"react-resizable-panels": "^4.12.
|
|
134
|
+
"react-resizable-panels": "^4.12.3",
|
|
135
135
|
"react-syntax-highlighter": "^16.1.1",
|
|
136
136
|
"recharts": "3.10.1",
|
|
137
137
|
"slot-text": "^0.3.4",
|
|
@@ -155,7 +155,7 @@
|
|
|
155
155
|
"@testing-library/jest-dom": "^7.0.1",
|
|
156
156
|
"@testing-library/react": "^16.3.2",
|
|
157
157
|
"@tuturuuu/types": "0.27.0",
|
|
158
|
-
"@tuturuuu/typescript-config": "0.1.
|
|
158
|
+
"@tuturuuu/typescript-config": "0.1.2",
|
|
159
159
|
"@types/html2canvas": "^1.0.0",
|
|
160
160
|
"@types/lodash": "^4.17.25",
|
|
161
161
|
"@types/node": "^26.2.0",
|
|
@@ -164,15 +164,15 @@
|
|
|
164
164
|
"@types/react-resizable": "^4.0.0",
|
|
165
165
|
"@types/react-syntax-highlighter": "^15.5.13",
|
|
166
166
|
"@types/uuid": "^11.0.0",
|
|
167
|
-
"@vitejs/plugin-react": "^6.0
|
|
167
|
+
"@vitejs/plugin-react": "^6.1.0",
|
|
168
168
|
"autoprefixer": "^10.5.4",
|
|
169
169
|
"jsdom": "^30.0.1",
|
|
170
170
|
"postcss": "^8.5.26",
|
|
171
171
|
"postcss-load-config": "^6.0.1",
|
|
172
172
|
"tailwindcss": "^4.3.3",
|
|
173
173
|
"typescript": "7.0.2",
|
|
174
|
-
"uuid": "^14.0.
|
|
175
|
-
"vitest": "^4.1.
|
|
174
|
+
"uuid": "^14.0.2",
|
|
175
|
+
"vitest": "^4.1.11"
|
|
176
176
|
},
|
|
177
177
|
"peerDependencies": {
|
|
178
178
|
"@tanstack/react-query": "^5.101.2",
|
|
@@ -444,5 +444,5 @@
|
|
|
444
444
|
"import": "./src/components/ui/text-editor/text-replacements.ts"
|
|
445
445
|
}
|
|
446
446
|
},
|
|
447
|
-
"packageManager": "bun@1.
|
|
447
|
+
"packageManager": "bun@1.4.0"
|
|
448
448
|
}
|
|
@@ -18,4 +18,70 @@ describe('ImageExtension clipboard files', () => {
|
|
|
18
18
|
|
|
19
19
|
expect(images).toEqual([imageFile]);
|
|
20
20
|
});
|
|
21
|
+
|
|
22
|
+
it('names an unnamed screenshot from its clipboard MIME type', () => {
|
|
23
|
+
const unnamedImage = new File(['image'], '', { type: 'image/png' });
|
|
24
|
+
|
|
25
|
+
const [image] = __imageExtensionPrivate.getClipboardImageFiles([
|
|
26
|
+
{
|
|
27
|
+
type: 'image/png',
|
|
28
|
+
getAsFile: () => unnamedImage,
|
|
29
|
+
} as DataTransferItem,
|
|
30
|
+
]);
|
|
31
|
+
|
|
32
|
+
expect(image).toBeInstanceOf(File);
|
|
33
|
+
expect(image?.name).toBe('pasted-image.png');
|
|
34
|
+
expect(image?.type).toBe('image/png');
|
|
35
|
+
expect(image?.size).toBe(unnamedImage.size);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('adds an extension to an extensionless clipboard filename', () => {
|
|
39
|
+
const extensionlessImage = new File(['image'], 'Screenshot', {
|
|
40
|
+
type: 'image/jpeg',
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const [image] = __imageExtensionPrivate.getClipboardImageFiles([
|
|
44
|
+
{
|
|
45
|
+
type: 'image/jpeg',
|
|
46
|
+
getAsFile: () => extensionlessImage,
|
|
47
|
+
} as DataTransferItem,
|
|
48
|
+
]);
|
|
49
|
+
|
|
50
|
+
expect(image?.name).toBe('Screenshot.jpg');
|
|
51
|
+
expect(image?.type).toBe('image/jpeg');
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('restores a missing file MIME type from the clipboard item', () => {
|
|
55
|
+
const imageWithoutType = new File(['image'], '', { type: '' });
|
|
56
|
+
|
|
57
|
+
const [image] = __imageExtensionPrivate.getClipboardImageFiles([
|
|
58
|
+
{
|
|
59
|
+
type: 'image/webp',
|
|
60
|
+
getAsFile: () => imageWithoutType,
|
|
61
|
+
} as DataTransferItem,
|
|
62
|
+
]);
|
|
63
|
+
|
|
64
|
+
expect(image?.name).toBe('pasted-image.webp');
|
|
65
|
+
expect(image?.type).toBe('image/webp');
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('ignores non-image items and null image files without losing valid images', () => {
|
|
69
|
+
const imageFile = new File(['image'], 'capture.png', {
|
|
70
|
+
type: 'image/png',
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
const images = __imageExtensionPrivate.getClipboardImageFiles([
|
|
74
|
+
{
|
|
75
|
+
type: 'text/plain',
|
|
76
|
+
getAsFile: () => new File(['text'], 'note.txt'),
|
|
77
|
+
} as DataTransferItem,
|
|
78
|
+
{ type: 'image/png', getAsFile: () => null } as DataTransferItem,
|
|
79
|
+
{
|
|
80
|
+
type: 'image/png',
|
|
81
|
+
getAsFile: () => imageFile,
|
|
82
|
+
} as DataTransferItem,
|
|
83
|
+
]);
|
|
84
|
+
|
|
85
|
+
expect(images).toEqual([imageFile]);
|
|
86
|
+
});
|
|
21
87
|
});
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import { fireEvent, render, screen } from '@testing-library/react';
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
1
|
+
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
|
|
2
|
+
import { Editor } from '@tiptap/core';
|
|
3
|
+
import type { Editor as TiptapEditor } from '@tiptap/react';
|
|
4
|
+
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
5
|
+
import { getEditorExtensions } from '../extensions';
|
|
4
6
|
import { FixedToolbar } from '../tool-bar';
|
|
5
7
|
|
|
6
|
-
function createEditorStub():
|
|
8
|
+
function createEditorStub(): TiptapEditor {
|
|
7
9
|
const chain = {
|
|
8
10
|
focus: vi.fn(() => chain),
|
|
9
11
|
insertTable: vi.fn(() => chain),
|
|
@@ -26,9 +28,16 @@ function createEditorStub(): Editor {
|
|
|
26
28
|
return {
|
|
27
29
|
chain: vi.fn(() => chain),
|
|
28
30
|
isActive: vi.fn(() => false),
|
|
29
|
-
} as unknown as
|
|
31
|
+
} as unknown as TiptapEditor;
|
|
30
32
|
}
|
|
31
33
|
|
|
34
|
+
const editors: Editor[] = [];
|
|
35
|
+
|
|
36
|
+
afterEach(() => {
|
|
37
|
+
for (const editor of editors.splice(0)) editor.destroy();
|
|
38
|
+
vi.restoreAllMocks();
|
|
39
|
+
});
|
|
40
|
+
|
|
32
41
|
describe('inline task conversion toolbar', () => {
|
|
33
42
|
it('delegates conversion to the provided callback', async () => {
|
|
34
43
|
const onConvertToTask = vi.fn();
|
|
@@ -73,4 +82,106 @@ describe('inline task conversion toolbar', () => {
|
|
|
73
82
|
screen.getByRole('button', { name: 'Liste ou titre repliable' })
|
|
74
83
|
).toBeInTheDocument();
|
|
75
84
|
});
|
|
85
|
+
|
|
86
|
+
it('keeps every control in a scrollbar-free horizontal lane without shrinking', () => {
|
|
87
|
+
const { container } = render(
|
|
88
|
+
<FixedToolbar
|
|
89
|
+
editor={createEditorStub()}
|
|
90
|
+
leadingContent={<button type="button">99% left</button>}
|
|
91
|
+
/>
|
|
92
|
+
);
|
|
93
|
+
const toolbar = container.firstElementChild;
|
|
94
|
+
const capacityButton = screen.getByRole('button', { name: '99% left' });
|
|
95
|
+
const boldButton = screen.getByRole('button', { name: 'Bold' });
|
|
96
|
+
const copyButton = screen.getByRole('button', { name: 'Copy content' });
|
|
97
|
+
|
|
98
|
+
expect(toolbar).toHaveClass(
|
|
99
|
+
'@container',
|
|
100
|
+
'flex-nowrap',
|
|
101
|
+
'max-w-full',
|
|
102
|
+
'overflow-x-auto',
|
|
103
|
+
'overflow-y-hidden',
|
|
104
|
+
'overscroll-x-contain',
|
|
105
|
+
'scrollbar-hide'
|
|
106
|
+
);
|
|
107
|
+
expect(toolbar).not.toHaveClass('flex-wrap');
|
|
108
|
+
expect(copyButton.closest('.ml-auto')).toBeNull();
|
|
109
|
+
expect(capacityButton.compareDocumentPosition(boldButton)).toBe(
|
|
110
|
+
Node.DOCUMENT_POSITION_FOLLOWING
|
|
111
|
+
);
|
|
112
|
+
expect(copyButton).toHaveClass('h-8', 'w-8', 'shrink-0', 'rounded-md');
|
|
113
|
+
expect(boldButton).toHaveClass('h-8', 'w-8', 'shrink-0', 'rounded-md');
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('copies the full document in either Markdown or plain-text form', async () => {
|
|
117
|
+
const editor = new Editor({
|
|
118
|
+
extensions: getEditorExtensions(),
|
|
119
|
+
content: {
|
|
120
|
+
type: 'doc',
|
|
121
|
+
content: [
|
|
122
|
+
{
|
|
123
|
+
type: 'heading',
|
|
124
|
+
attrs: { level: 1 },
|
|
125
|
+
content: [{ type: 'text', text: 'Plan' }],
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
type: 'bulletList',
|
|
129
|
+
content: [
|
|
130
|
+
{
|
|
131
|
+
type: 'listItem',
|
|
132
|
+
content: [
|
|
133
|
+
{
|
|
134
|
+
type: 'paragraph',
|
|
135
|
+
content: [
|
|
136
|
+
{
|
|
137
|
+
type: 'text',
|
|
138
|
+
text: 'Ship it',
|
|
139
|
+
marks: [{ type: 'bold' }],
|
|
140
|
+
},
|
|
141
|
+
],
|
|
142
|
+
},
|
|
143
|
+
],
|
|
144
|
+
},
|
|
145
|
+
],
|
|
146
|
+
},
|
|
147
|
+
],
|
|
148
|
+
},
|
|
149
|
+
});
|
|
150
|
+
editors.push(editor);
|
|
151
|
+
const writeText = vi.fn().mockResolvedValue(undefined);
|
|
152
|
+
Object.defineProperty(navigator, 'clipboard', {
|
|
153
|
+
configurable: true,
|
|
154
|
+
value: { writeText },
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
render(<FixedToolbar editor={editor} />);
|
|
158
|
+
|
|
159
|
+
fireEvent.pointerDown(
|
|
160
|
+
await screen.findByRole('button', { name: 'Copy content' })
|
|
161
|
+
);
|
|
162
|
+
fireEvent.click(
|
|
163
|
+
await screen.findByRole('menuitem', { name: /Copy as Markdown/ })
|
|
164
|
+
);
|
|
165
|
+
await waitFor(() =>
|
|
166
|
+
expect(
|
|
167
|
+
screen.queryByRole('menuitem', { name: /Copy as Markdown/ })
|
|
168
|
+
).not.toBeInTheDocument()
|
|
169
|
+
);
|
|
170
|
+
await waitFor(() =>
|
|
171
|
+
expect(writeText).toHaveBeenLastCalledWith('# Plan\n\n- **Ship it**')
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
fireEvent.pointerDown(screen.getByRole('button', { name: 'Copy content' }));
|
|
175
|
+
fireEvent.click(
|
|
176
|
+
await screen.findByRole('menuitem', { name: /Copy as plain text/ })
|
|
177
|
+
);
|
|
178
|
+
await waitFor(() =>
|
|
179
|
+
expect(
|
|
180
|
+
screen.queryByRole('menuitem', { name: /Copy as plain text/ })
|
|
181
|
+
).not.toBeInTheDocument()
|
|
182
|
+
);
|
|
183
|
+
await waitFor(() =>
|
|
184
|
+
expect(writeText).toHaveBeenLastCalledWith('Plan\n\n• Ship it')
|
|
185
|
+
);
|
|
186
|
+
});
|
|
76
187
|
});
|