@pie-lib/editable-html-tip-tap 1.1.0-next.6059 → 1.1.1-next.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.
Files changed (152) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/lib/__tests__/EditableHtml.test.js +374 -0
  3. package/lib/__tests__/constants.test.js +28 -0
  4. package/lib/__tests__/extensions.test.js +214 -0
  5. package/lib/__tests__/index.test.js +246 -0
  6. package/lib/__tests__/size-utils.test.js +57 -0
  7. package/lib/__tests__/theme.test.js +17 -0
  8. package/lib/components/CharacterPicker.js +18 -0
  9. package/lib/components/CharacterPicker.js.map +1 -1
  10. package/lib/components/EditableHtml.js +22 -5
  11. package/lib/components/EditableHtml.js.map +1 -1
  12. package/lib/components/MenuBar.js +17 -0
  13. package/lib/components/MenuBar.js.map +1 -1
  14. package/lib/components/TiptapContainer.js +16 -0
  15. package/lib/components/TiptapContainer.js.map +1 -1
  16. package/lib/components/__tests__/AltDialog.test.js +201 -0
  17. package/lib/components/__tests__/CharacterPicker.test.js +313 -0
  18. package/lib/components/__tests__/CssIcon.test.js +58 -0
  19. package/lib/components/__tests__/DragInTheBlank.test.js +309 -0
  20. package/lib/components/__tests__/ExplicitConstructedResponse.test.js +263 -0
  21. package/lib/components/__tests__/ImageToolbar.test.js +195 -0
  22. package/lib/components/__tests__/InlineDropdown.test.js +297 -0
  23. package/lib/components/__tests__/InsertImageHandler.test.js +162 -0
  24. package/lib/components/__tests__/MediaDialog.test.js +435 -0
  25. package/lib/components/__tests__/MediaToolbar.test.js +126 -0
  26. package/lib/components/__tests__/MediaWrapper.test.js +96 -0
  27. package/lib/components/__tests__/MenuBar.test.js +459 -0
  28. package/lib/components/__tests__/RespArea.test.js +171 -0
  29. package/lib/components/__tests__/TableIcons.test.js +153 -0
  30. package/lib/components/__tests__/TextAlign.test.js +216 -0
  31. package/lib/components/__tests__/TiptapContainer.test.js +196 -0
  32. package/lib/components/__tests__/characterUtils.test.js +189 -0
  33. package/lib/components/__tests__/choice.test.js +213 -0
  34. package/lib/components/__tests__/custom-popper.test.js +108 -0
  35. package/lib/components/__tests__/done-button.test.js +72 -0
  36. package/lib/components/__tests__/toolbar-buttons.test.js +277 -0
  37. package/lib/components/characters/characterUtils.js +2 -0
  38. package/lib/components/characters/characterUtils.js.map +1 -1
  39. package/lib/components/characters/custom-popper.js +1 -0
  40. package/lib/components/characters/custom-popper.js.map +1 -1
  41. package/lib/components/common/done-button.js +1 -0
  42. package/lib/components/common/done-button.js.map +1 -1
  43. package/lib/components/common/toolbar-buttons.js +12 -0
  44. package/lib/components/common/toolbar-buttons.js.map +1 -1
  45. package/lib/components/icons/CssIcon.js +1 -0
  46. package/lib/components/icons/CssIcon.js.map +1 -1
  47. package/lib/components/icons/RespArea.js +10 -0
  48. package/lib/components/icons/RespArea.js.map +1 -1
  49. package/lib/components/icons/TableIcons.js +1 -0
  50. package/lib/components/icons/TableIcons.js.map +1 -1
  51. package/lib/components/icons/TextAlign.js +7 -0
  52. package/lib/components/icons/TextAlign.js.map +1 -1
  53. package/lib/components/image/AltDialog.js +5 -0
  54. package/lib/components/image/AltDialog.js.map +1 -1
  55. package/lib/components/image/ImageToolbar.js +13 -0
  56. package/lib/components/image/ImageToolbar.js.map +1 -1
  57. package/lib/components/image/InsertImageHandler.js +10 -0
  58. package/lib/components/image/InsertImageHandler.js.map +1 -1
  59. package/lib/components/media/MediaDialog.js +18 -0
  60. package/lib/components/media/MediaDialog.js.map +1 -1
  61. package/lib/components/media/MediaToolbar.js +2 -0
  62. package/lib/components/media/MediaToolbar.js.map +1 -1
  63. package/lib/components/media/MediaWrapper.js +11 -0
  64. package/lib/components/media/MediaWrapper.js.map +1 -1
  65. package/lib/components/respArea/DragInTheBlank/DragInTheBlank.js +10 -0
  66. package/lib/components/respArea/DragInTheBlank/DragInTheBlank.js.map +1 -1
  67. package/lib/components/respArea/DragInTheBlank/choice.js +8 -0
  68. package/lib/components/respArea/DragInTheBlank/choice.js.map +1 -1
  69. package/lib/components/respArea/ExplicitConstructedResponse.js +8 -0
  70. package/lib/components/respArea/ExplicitConstructedResponse.js.map +1 -1
  71. package/lib/components/respArea/InlineDropdown.js +7 -0
  72. package/lib/components/respArea/InlineDropdown.js.map +1 -1
  73. package/lib/components/respArea/ToolbarIcon.js +10 -0
  74. package/lib/components/respArea/ToolbarIcon.js.map +1 -1
  75. package/lib/constants.js +1 -0
  76. package/lib/constants.js.map +1 -1
  77. package/lib/extensions/__tests__/component.test.js +314 -0
  78. package/lib/extensions/__tests__/css.test.js +218 -0
  79. package/lib/extensions/__tests__/custom-toolbar-wrapper.test.js +185 -0
  80. package/lib/extensions/__tests__/extended-table.test.js +114 -0
  81. package/lib/extensions/__tests__/image.test.js +178 -0
  82. package/lib/extensions/__tests__/media.test.js +296 -0
  83. package/lib/extensions/__tests__/responseArea.test.js +332 -0
  84. package/lib/extensions/component.js +22 -2
  85. package/lib/extensions/css.js +11 -0
  86. package/lib/extensions/css.js.map +1 -1
  87. package/lib/extensions/custom-toolbar-wrapper.js +15 -0
  88. package/lib/extensions/custom-toolbar-wrapper.js.map +1 -1
  89. package/lib/extensions/extended-table.js +4 -0
  90. package/lib/extensions/extended-table.js.map +1 -1
  91. package/lib/extensions/image-component.js +314 -0
  92. package/lib/extensions/image-component.js.map +1 -0
  93. package/lib/extensions/image.js +13 -2
  94. package/lib/extensions/image.js.map +1 -1
  95. package/lib/extensions/index.js +12 -2
  96. package/lib/extensions/index.js.map +1 -1
  97. package/lib/extensions/math.js +16 -0
  98. package/lib/extensions/math.js.map +1 -1
  99. package/lib/extensions/media.js +15 -0
  100. package/lib/extensions/media.js.map +1 -1
  101. package/lib/extensions/responseArea.js +22 -0
  102. package/lib/extensions/responseArea.js.map +1 -1
  103. package/lib/index.js +7 -0
  104. package/lib/index.js.map +1 -1
  105. package/lib/styles/editorContainerStyles.js +1 -0
  106. package/lib/styles/editorContainerStyles.js.map +1 -1
  107. package/lib/theme.js +1 -0
  108. package/lib/theme.js.map +1 -1
  109. package/lib/utils/size.js +6 -0
  110. package/lib/utils/size.js.map +1 -1
  111. package/package.json +8 -8
  112. package/src/__tests__/EditableHtml.test.jsx +266 -0
  113. package/src/__tests__/constants.test.js +20 -0
  114. package/src/__tests__/extensions.test.js +208 -0
  115. package/src/__tests__/index.test.jsx +146 -0
  116. package/src/__tests__/size-utils.test.js +64 -0
  117. package/src/__tests__/theme.test.js +17 -0
  118. package/src/components/EditableHtml.jsx +8 -6
  119. package/src/components/__tests__/AltDialog.test.jsx +147 -0
  120. package/src/components/__tests__/CharacterPicker.test.jsx +195 -0
  121. package/src/components/__tests__/CssIcon.test.jsx +46 -0
  122. package/src/components/__tests__/DragInTheBlank.test.jsx +255 -0
  123. package/src/components/__tests__/ExplicitConstructedResponse.test.jsx +161 -0
  124. package/src/components/__tests__/ImageToolbar.test.jsx +128 -0
  125. package/src/components/__tests__/InlineDropdown.test.jsx +187 -0
  126. package/src/components/__tests__/InsertImageHandler.test.js +154 -0
  127. package/src/components/__tests__/MediaDialog.test.jsx +293 -0
  128. package/src/components/__tests__/MediaToolbar.test.jsx +74 -0
  129. package/src/components/__tests__/MediaWrapper.test.jsx +81 -0
  130. package/src/components/__tests__/MenuBar.test.jsx +217 -0
  131. package/src/components/__tests__/RespArea.test.jsx +122 -0
  132. package/src/components/__tests__/TableIcons.test.jsx +149 -0
  133. package/src/components/__tests__/TextAlign.test.jsx +167 -0
  134. package/src/components/__tests__/TiptapContainer.test.jsx +138 -0
  135. package/src/components/__tests__/characterUtils.test.js +166 -0
  136. package/src/components/__tests__/choice.test.jsx +171 -0
  137. package/src/components/__tests__/custom-popper.test.jsx +82 -0
  138. package/src/components/__tests__/done-button.test.jsx +54 -0
  139. package/src/components/__tests__/toolbar-buttons.test.jsx +234 -0
  140. package/src/extensions/__tests__/css.test.js +196 -0
  141. package/src/extensions/__tests__/custom-toolbar-wrapper.test.jsx +180 -0
  142. package/src/extensions/__tests__/extended-table.test.js +107 -0
  143. package/src/extensions/__tests__/image-component.test.jsx +249 -0
  144. package/src/extensions/__tests__/image.test.js +136 -0
  145. package/src/extensions/__tests__/media.test.js +270 -0
  146. package/src/extensions/__tests__/responseArea.test.js +310 -0
  147. package/src/extensions/{component.jsx → image-component.jsx} +11 -1
  148. package/src/extensions/image.js +1 -1
  149. package/src/extensions/index.js +5 -1
  150. package/LICENSE.md +0 -5
  151. package/NEXT.CHANGELOG.json +0 -1
  152. package/lib/extensions/component.js.map +0 -1
@@ -0,0 +1,82 @@
1
+ import React from 'react';
2
+ import { render } from '@testing-library/react';
3
+ import CustomPopper from '../characters/custom-popper';
4
+
5
+ jest.mock('@mui/material/Popper', () => ({
6
+ __esModule: true,
7
+ default: ({ children, id, open, ...props }) =>
8
+ open ? (
9
+ <div id={id} data-testid="popper" {...props}>
10
+ {children}
11
+ </div>
12
+ ) : null,
13
+ }));
14
+
15
+ describe('CustomPopper', () => {
16
+ const mockAnchorEl = document.createElement('div');
17
+
18
+ it('renders without crashing', () => {
19
+ const { container } = render(<CustomPopper anchorEl={mockAnchorEl}>Content</CustomPopper>);
20
+ expect(container).toBeInTheDocument();
21
+ });
22
+
23
+ it('renders children', () => {
24
+ const { getByText } = render(<CustomPopper anchorEl={mockAnchorEl}>Test Content</CustomPopper>);
25
+ expect(getByText('Test Content')).toBeInTheDocument();
26
+ });
27
+
28
+ it('is always open', () => {
29
+ const { getByTestId } = render(<CustomPopper anchorEl={mockAnchorEl}>Content</CustomPopper>);
30
+ expect(getByTestId('popper')).toBeInTheDocument();
31
+ });
32
+
33
+ it('has correct id', () => {
34
+ const { container } = render(<CustomPopper anchorEl={mockAnchorEl}>Content</CustomPopper>);
35
+ const popper = container.querySelector('#mouse-over-popover');
36
+ expect(popper).toBeInTheDocument();
37
+ });
38
+
39
+ it('applies correct styling to typography', () => {
40
+ const { getByText } = render(<CustomPopper anchorEl={mockAnchorEl}>Content</CustomPopper>);
41
+ const typography = getByText('Content');
42
+ expect(typography).toHaveStyle({ fontSize: '50px', textAlign: 'center' });
43
+ });
44
+
45
+ it('passes additional props to Popper', () => {
46
+ const { container } = render(
47
+ <CustomPopper anchorEl={mockAnchorEl} onClose={jest.fn()}>
48
+ Content
49
+ </CustomPopper>,
50
+ );
51
+ expect(container).toBeInTheDocument();
52
+ });
53
+
54
+ it('renders multiple children', () => {
55
+ const { getByText } = render(
56
+ <CustomPopper anchorEl={mockAnchorEl}>
57
+ <div>First</div>
58
+ <div>Second</div>
59
+ </CustomPopper>,
60
+ );
61
+ expect(getByText('First')).toBeInTheDocument();
62
+ expect(getByText('Second')).toBeInTheDocument();
63
+ });
64
+
65
+ it('has pointer-events disabled', () => {
66
+ const { container } = render(<CustomPopper anchorEl={mockAnchorEl}>Content</CustomPopper>);
67
+ // Styling is applied via styled component, just verify it rendered
68
+ expect(container).toBeInTheDocument();
69
+ });
70
+
71
+ it('has high z-index', () => {
72
+ const { container } = render(<CustomPopper anchorEl={mockAnchorEl}>Content</CustomPopper>);
73
+ // Styling is applied via styled component, just verify it rendered
74
+ expect(container).toBeInTheDocument();
75
+ });
76
+
77
+ it('has correct background color', () => {
78
+ const { container } = render(<CustomPopper anchorEl={mockAnchorEl}>Content</CustomPopper>);
79
+ // Styling is applied via styled component, just verify it rendered
80
+ expect(container).toBeInTheDocument();
81
+ });
82
+ });
@@ -0,0 +1,54 @@
1
+ import React from 'react';
2
+ import { render, fireEvent } from '@testing-library/react';
3
+ import { RawDoneButton, DoneButton } from '../common/done-button';
4
+
5
+ describe('RawDoneButton', () => {
6
+ it('renders without crashing', () => {
7
+ const { container } = render(<RawDoneButton onClick={jest.fn()} />);
8
+ expect(container).toBeInTheDocument();
9
+ });
10
+
11
+ it('renders with Check icon', () => {
12
+ const { container } = render(<RawDoneButton onClick={jest.fn()} />);
13
+ const button = container.querySelector('button');
14
+ expect(button).toBeInTheDocument();
15
+ });
16
+
17
+ it('calls onClick when clicked', () => {
18
+ const onClick = jest.fn();
19
+ const { container } = render(<RawDoneButton onClick={onClick} />);
20
+ const button = container.querySelector('button');
21
+ fireEvent.click(button);
22
+ expect(onClick).toHaveBeenCalled();
23
+ });
24
+
25
+ it('has aria-label "Done"', () => {
26
+ const { container } = render(<RawDoneButton onClick={jest.fn()} />);
27
+ const button = container.querySelector('button');
28
+ expect(button).toHaveAttribute('aria-label', 'Done');
29
+ });
30
+
31
+ it('accepts doneButtonRef prop', () => {
32
+ const doneButtonRef = jest.fn();
33
+ const { container } = render(<RawDoneButton onClick={jest.fn()} doneButtonRef={doneButtonRef} />);
34
+ // doneButtonRef is passed as buttonRef to IconButton, just verify it's accepted
35
+ expect(container).toBeInTheDocument();
36
+ });
37
+
38
+ it('applies correct styling', () => {
39
+ const { container } = render(<RawDoneButton onClick={jest.fn()} />);
40
+ const button = container.querySelector('button');
41
+ expect(button).toHaveClass('MuiIconButton-root');
42
+ });
43
+ });
44
+
45
+ describe('DoneButton', () => {
46
+ it('is the same as RawDoneButton', () => {
47
+ expect(DoneButton).toBe(RawDoneButton);
48
+ });
49
+
50
+ it('renders correctly', () => {
51
+ const { container } = render(<DoneButton onClick={jest.fn()} />);
52
+ expect(container.querySelector('button')).toBeInTheDocument();
53
+ });
54
+ });
@@ -0,0 +1,234 @@
1
+ import React from 'react';
2
+ import { render, fireEvent } from '@testing-library/react';
3
+ import { RawButton, Button, RawMarkButton, MarkButton } from '../common/toolbar-buttons';
4
+
5
+ describe('RawButton', () => {
6
+ it('renders without crashing', () => {
7
+ const { container } = render(<RawButton onClick={jest.fn()}>Click me</RawButton>);
8
+ expect(container).toBeInTheDocument();
9
+ });
10
+
11
+ it('renders children', () => {
12
+ const { getByText } = render(<RawButton onClick={jest.fn()}>Test Button</RawButton>);
13
+ expect(getByText('Test Button')).toBeInTheDocument();
14
+ });
15
+
16
+ it('calls onClick on mouse down', () => {
17
+ const onClick = jest.fn();
18
+ const { getByText } = render(<RawButton onClick={onClick}>Click</RawButton>);
19
+ fireEvent.mouseDown(getByText('Click'));
20
+ expect(onClick).toHaveBeenCalled();
21
+ });
22
+
23
+ it('prevents default on mouse down', () => {
24
+ const onClick = jest.fn();
25
+ const { getByText } = render(<RawButton onClick={onClick}>Click</RawButton>);
26
+ const button = getByText('Click');
27
+ const event = new MouseEvent('mousedown', { bubbles: true, cancelable: true });
28
+ const preventDefaultSpy = jest.spyOn(event, 'preventDefault');
29
+ button.dispatchEvent(event);
30
+ expect(preventDefaultSpy).toHaveBeenCalled();
31
+ });
32
+
33
+ it('calls onClick on Enter key press', () => {
34
+ const onClick = jest.fn();
35
+ const { getByText } = render(<RawButton onClick={onClick}>Click</RawButton>);
36
+ fireEvent.keyDown(getByText('Click'), { key: 'Enter' });
37
+ expect(onClick).toHaveBeenCalled();
38
+ });
39
+
40
+ it('calls onClick on Space key press', () => {
41
+ const onClick = jest.fn();
42
+ const { getByText } = render(<RawButton onClick={onClick}>Click</RawButton>);
43
+ fireEvent.keyDown(getByText('Click'), { key: ' ' });
44
+ expect(onClick).toHaveBeenCalled();
45
+ });
46
+
47
+ it('does not call onClick on other key press', () => {
48
+ const onClick = jest.fn();
49
+ const { getByText } = render(<RawButton onClick={onClick}>Click</RawButton>);
50
+ fireEvent.keyDown(getByText('Click'), { key: 'a' });
51
+ expect(onClick).not.toHaveBeenCalled();
52
+ });
53
+
54
+ it('applies active style when active is true', () => {
55
+ const { getByText } = render(
56
+ <RawButton onClick={jest.fn()} active={true}>
57
+ Active
58
+ </RawButton>,
59
+ );
60
+ const button = getByText('Active');
61
+ expect(button).toHaveStyle({ color: 'black' });
62
+ });
63
+
64
+ it('applies disabled style when disabled is true', () => {
65
+ const { getByText } = render(
66
+ <RawButton onClick={jest.fn()} disabled={true}>
67
+ Disabled
68
+ </RawButton>,
69
+ );
70
+ const button = getByText('Disabled');
71
+ expect(button).toHaveStyle({ cursor: 'not-allowed' });
72
+ });
73
+
74
+ it('applies extraStyles when provided', () => {
75
+ const extraStyles = { backgroundColor: 'red' };
76
+ const { getByText } = render(
77
+ <RawButton onClick={jest.fn()} extraStyles={extraStyles}>
78
+ Styled
79
+ </RawButton>,
80
+ );
81
+ const button = getByText('Styled');
82
+ expect(button).toHaveStyle({ backgroundColor: 'red' });
83
+ });
84
+
85
+ it('has aria-label when provided', () => {
86
+ const { getByLabelText } = render(
87
+ <RawButton onClick={jest.fn()} ariaLabel="Test Button">
88
+ Button
89
+ </RawButton>,
90
+ );
91
+ expect(getByLabelText('Test Button')).toBeInTheDocument();
92
+ });
93
+
94
+ it('has aria-pressed attribute', () => {
95
+ const { getByText } = render(
96
+ <RawButton onClick={jest.fn()} active={true}>
97
+ Button
98
+ </RawButton>,
99
+ );
100
+ expect(getByText('Button')).toHaveAttribute('aria-pressed', 'true');
101
+ });
102
+
103
+ it('is focusable with tabIndex', () => {
104
+ const { getByText } = render(<RawButton onClick={jest.fn()}>Button</RawButton>);
105
+ expect(getByText('Button')).toHaveAttribute('tabIndex', '0');
106
+ });
107
+ });
108
+
109
+ describe('Button', () => {
110
+ it('is the same as RawButton', () => {
111
+ expect(Button).toBe(RawButton);
112
+ });
113
+ });
114
+
115
+ describe('RawMarkButton', () => {
116
+ it('renders without crashing', () => {
117
+ const { container } = render(
118
+ <RawMarkButton onToggle={jest.fn()} mark="bold" label="Bold">
119
+ B
120
+ </RawMarkButton>,
121
+ );
122
+ expect(container).toBeInTheDocument();
123
+ });
124
+
125
+ it('renders children', () => {
126
+ const { getByText } = render(
127
+ <RawMarkButton onToggle={jest.fn()} mark="bold" label="Bold">
128
+ Bold
129
+ </RawMarkButton>,
130
+ );
131
+ expect(getByText('Bold')).toBeInTheDocument();
132
+ });
133
+
134
+ it('calls onToggle with mark on mouse down', () => {
135
+ const onToggle = jest.fn();
136
+ const { getByText } = render(
137
+ <RawMarkButton onToggle={onToggle} mark="italic" label="Italic">
138
+ I
139
+ </RawMarkButton>,
140
+ );
141
+ fireEvent.mouseDown(getByText('I'));
142
+ expect(onToggle).toHaveBeenCalledWith('italic');
143
+ });
144
+
145
+ it('prevents default on mouse down', () => {
146
+ const onToggle = jest.fn();
147
+ const { getByText } = render(
148
+ <RawMarkButton onToggle={onToggle} mark="bold" label="Bold">
149
+ B
150
+ </RawMarkButton>,
151
+ );
152
+ const button = getByText('B');
153
+ const event = new MouseEvent('mousedown', { bubbles: true, cancelable: true });
154
+ const preventDefaultSpy = jest.spyOn(event, 'preventDefault');
155
+ button.dispatchEvent(event);
156
+ expect(preventDefaultSpy).toHaveBeenCalled();
157
+ });
158
+
159
+ it('calls onToggle on Enter key press', () => {
160
+ const onToggle = jest.fn();
161
+ const { getByText } = render(
162
+ <RawMarkButton onToggle={onToggle} mark="underline" label="Underline">
163
+ U
164
+ </RawMarkButton>,
165
+ );
166
+ fireEvent.keyDown(getByText('U'), { key: 'Enter' });
167
+ expect(onToggle).toHaveBeenCalledWith('underline');
168
+ });
169
+
170
+ it('calls onToggle on Space key press', () => {
171
+ const onToggle = jest.fn();
172
+ const { getByText } = render(
173
+ <RawMarkButton onToggle={onToggle} mark="strike" label="Strike">
174
+ S
175
+ </RawMarkButton>,
176
+ );
177
+ fireEvent.keyDown(getByText('S'), { key: ' ' });
178
+ expect(onToggle).toHaveBeenCalledWith('strike');
179
+ });
180
+
181
+ it('does not call onToggle on other key press', () => {
182
+ const onToggle = jest.fn();
183
+ const { getByText } = render(
184
+ <RawMarkButton onToggle={onToggle} mark="bold" label="Bold">
185
+ B
186
+ </RawMarkButton>,
187
+ );
188
+ fireEvent.keyDown(getByText('B'), { key: 'a' });
189
+ expect(onToggle).not.toHaveBeenCalled();
190
+ });
191
+
192
+ it('applies active style when active is true', () => {
193
+ const { getByText } = render(
194
+ <RawMarkButton onToggle={jest.fn()} mark="bold" label="Bold" active={true}>
195
+ B
196
+ </RawMarkButton>,
197
+ );
198
+ const button = getByText('B');
199
+ expect(button).toHaveStyle({ color: 'black' });
200
+ });
201
+
202
+ it('has aria-label from label prop', () => {
203
+ const { getByLabelText } = render(
204
+ <RawMarkButton onToggle={jest.fn()} mark="bold" label="Toggle Bold">
205
+ B
206
+ </RawMarkButton>,
207
+ );
208
+ expect(getByLabelText('Toggle Bold')).toBeInTheDocument();
209
+ });
210
+
211
+ it('has aria-pressed attribute', () => {
212
+ const { getByText } = render(
213
+ <RawMarkButton onToggle={jest.fn()} mark="bold" label="Bold" active={true}>
214
+ B
215
+ </RawMarkButton>,
216
+ );
217
+ expect(getByText('B')).toHaveAttribute('aria-pressed', 'true');
218
+ });
219
+
220
+ it('is focusable with tabIndex', () => {
221
+ const { getByText } = render(
222
+ <RawMarkButton onToggle={jest.fn()} mark="bold" label="Bold">
223
+ B
224
+ </RawMarkButton>,
225
+ );
226
+ expect(getByText('B')).toHaveAttribute('tabIndex', '0');
227
+ });
228
+ });
229
+
230
+ describe('MarkButton', () => {
231
+ it('is the same as RawMarkButton', () => {
232
+ expect(MarkButton).toBe(RawMarkButton);
233
+ });
234
+ });
@@ -0,0 +1,196 @@
1
+ import { CSSMark, removeDialogs } from '../css';
2
+
3
+ jest.mock('@tiptap/core', () => ({
4
+ Mark: { create: jest.fn((config) => config) },
5
+ mergeAttributes: jest.fn((...args) => Object.assign({}, ...args)),
6
+ }));
7
+
8
+ jest.mock('react-dom', () => ({
9
+ render: jest.fn(),
10
+ }));
11
+
12
+ describe('CSSMark', () => {
13
+ describe('configuration', () => {
14
+ it('has correct name', () => {
15
+ expect(CSSMark.name).toBe('cssmark');
16
+ });
17
+ });
18
+
19
+ describe('addOptions', () => {
20
+ it('returns default options with empty classes array', () => {
21
+ const options = CSSMark.addOptions();
22
+
23
+ expect(options).toHaveProperty('classes');
24
+ expect(Array.isArray(options.classes)).toBe(true);
25
+ expect(options.classes).toHaveLength(0);
26
+ });
27
+ });
28
+
29
+ describe('addAttributes', () => {
30
+ it('returns class attribute configuration', () => {
31
+ const attributes = CSSMark.addAttributes();
32
+
33
+ expect(attributes).toHaveProperty('class');
34
+ expect(attributes.class).toHaveProperty('default', null);
35
+ expect(attributes.class).toHaveProperty('parseHTML');
36
+ expect(attributes.class).toHaveProperty('renderHTML');
37
+ expect(typeof attributes.class.parseHTML).toBe('function');
38
+ expect(typeof attributes.class.renderHTML).toBe('function');
39
+ });
40
+
41
+ it('parseHTML extracts class attribute', () => {
42
+ const attributes = CSSMark.addAttributes();
43
+ const mockEl = {
44
+ getAttribute: jest.fn((attr) => (attr === 'class' ? 'my-class' : null)),
45
+ };
46
+
47
+ const result = attributes.class.parseHTML(mockEl);
48
+
49
+ expect(result).toBe('my-class');
50
+ expect(mockEl.getAttribute).toHaveBeenCalledWith('class');
51
+ });
52
+
53
+ it('renderHTML returns empty object when no class', () => {
54
+ const attributes = CSSMark.addAttributes();
55
+ const result = attributes.class.renderHTML({ class: null });
56
+
57
+ expect(result).toEqual({});
58
+ });
59
+
60
+ it('renderHTML returns class when present', () => {
61
+ const attributes = CSSMark.addAttributes();
62
+ const result = attributes.class.renderHTML({ class: 'my-class' });
63
+
64
+ expect(result).toEqual({ class: 'my-class' });
65
+ });
66
+ });
67
+
68
+ describe('parseHTML', () => {
69
+ beforeEach(() => {
70
+ CSSMark.options = { classes: ['allowed-class', 'another-class'] };
71
+ });
72
+
73
+ it('returns array with span[class] selector', () => {
74
+ const rules = CSSMark.parseHTML();
75
+
76
+ expect(Array.isArray(rules)).toBe(true);
77
+ expect(rules).toHaveLength(1);
78
+ expect(rules[0]).toHaveProperty('tag', 'span[class]');
79
+ expect(rules[0]).toHaveProperty('getAttrs');
80
+ });
81
+
82
+ it('matches allowed classes', () => {
83
+ const rules = CSSMark.parseHTML();
84
+ const mockEl = {
85
+ getAttribute: jest.fn(() => 'allowed-class some-other'),
86
+ };
87
+
88
+ const result = rules[0].getAttrs(mockEl);
89
+
90
+ expect(result).toEqual({ class: 'allowed-class' });
91
+ });
92
+
93
+ it('returns false for non-allowed classes', () => {
94
+ const rules = CSSMark.parseHTML();
95
+ const mockEl = {
96
+ getAttribute: jest.fn(() => 'not-allowed'),
97
+ };
98
+
99
+ const result = rules[0].getAttrs(mockEl);
100
+
101
+ expect(result).toBe(false);
102
+ });
103
+
104
+ it('handles empty class attribute', () => {
105
+ const rules = CSSMark.parseHTML();
106
+ const mockEl = {
107
+ getAttribute: jest.fn(() => ''),
108
+ };
109
+
110
+ const result = rules[0].getAttrs(mockEl);
111
+
112
+ expect(result).toBe(false);
113
+ });
114
+ });
115
+
116
+ describe('renderHTML', () => {
117
+ it('returns span with merged attributes', () => {
118
+ const result = CSSMark.renderHTML({
119
+ HTMLAttributes: { class: 'my-class' },
120
+ });
121
+
122
+ expect(result[0]).toBe('span');
123
+ expect(result[1]).toEqual({ class: 'my-class' });
124
+ expect(result[2]).toBe(0);
125
+ });
126
+ });
127
+
128
+ describe('addCommands', () => {
129
+ it('returns setCSSClass command', () => {
130
+ const commands = CSSMark.addCommands();
131
+
132
+ expect(commands).toHaveProperty('setCSSClass');
133
+ expect(typeof commands.setCSSClass).toBe('function');
134
+ });
135
+
136
+ it('returns unsetCSSClass command', () => {
137
+ const commands = CSSMark.addCommands();
138
+
139
+ expect(commands).toHaveProperty('unsetCSSClass');
140
+ expect(typeof commands.unsetCSSClass).toBe('function');
141
+ });
142
+
143
+ it('returns openCSSClassDialog command', () => {
144
+ const commands = CSSMark.addCommands();
145
+
146
+ expect(commands).toHaveProperty('openCSSClassDialog');
147
+ expect(typeof commands.openCSSClassDialog).toBe('function');
148
+ });
149
+
150
+ it('setCSSClass sets mark with class name', () => {
151
+ const context = { name: 'cssmark' };
152
+ const commands = CSSMark.addCommands.call(context);
153
+ const mockCommands = {
154
+ setMark: jest.fn(() => true),
155
+ };
156
+
157
+ const result = commands.setCSSClass('my-class')({ commands: mockCommands });
158
+
159
+ expect(mockCommands.setMark).toHaveBeenCalledWith('cssmark', { class: 'my-class' });
160
+ expect(result).toBe(true);
161
+ });
162
+
163
+ it('unsetCSSClass unsets mark', () => {
164
+ const context = { name: 'cssmark' };
165
+ const commands = CSSMark.addCommands.call(context);
166
+ const mockCommands = {
167
+ unsetMark: jest.fn(() => true),
168
+ };
169
+
170
+ const result = commands.unsetCSSClass()({ commands: mockCommands });
171
+
172
+ expect(mockCommands.unsetMark).toHaveBeenCalledWith('cssmark');
173
+ expect(result).toBe(true);
174
+ });
175
+ });
176
+ });
177
+
178
+ describe('removeDialogs', () => {
179
+ it('removes all elements with insert-css-dialog class', () => {
180
+ const mockElements = [{ remove: jest.fn() }, { remove: jest.fn() }];
181
+
182
+ document.querySelectorAll = jest.fn(() => mockElements);
183
+
184
+ removeDialogs();
185
+
186
+ expect(document.querySelectorAll).toHaveBeenCalledWith('.insert-css-dialog');
187
+ expect(mockElements[0].remove).toHaveBeenCalled();
188
+ expect(mockElements[1].remove).toHaveBeenCalled();
189
+ });
190
+
191
+ it('handles no dialogs gracefully', () => {
192
+ document.querySelectorAll = jest.fn(() => []);
193
+
194
+ expect(() => removeDialogs()).not.toThrow();
195
+ });
196
+ });