@dfosco/storyboard-react 4.0.0-beta.9 → 4.0.0

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 (75) hide show
  1. package/package.json +6 -3
  2. package/src/AuthModal/AuthModal.jsx +134 -0
  3. package/src/AuthModal/AuthModal.module.css +221 -0
  4. package/src/BranchBar/BranchBar.jsx +56 -0
  5. package/src/BranchBar/BranchBar.module.css +230 -0
  6. package/src/BranchBar/useBranches.js +79 -0
  7. package/src/CommandPalette/CommandPalette.jsx +936 -0
  8. package/src/CommandPalette/CreateDialog.jsx +219 -0
  9. package/src/CommandPalette/command-palette.css +111 -0
  10. package/src/Icon.jsx +180 -0
  11. package/src/Viewfinder.jsx +1104 -57
  12. package/src/Viewfinder.module.css +1107 -149
  13. package/src/canvas/CanvasControls.jsx +51 -2
  14. package/src/canvas/CanvasControls.module.css +31 -0
  15. package/src/canvas/CanvasPage.bridge.test.jsx +142 -19
  16. package/src/canvas/CanvasPage.dragdrop.test.jsx +346 -0
  17. package/src/canvas/CanvasPage.jsx +807 -251
  18. package/src/canvas/CanvasPage.module.css +98 -50
  19. package/src/canvas/CanvasPage.multiselect.test.jsx +13 -11
  20. package/src/canvas/CanvasToolbar.jsx +2 -2
  21. package/src/canvas/MarqueeOverlay.jsx +20 -0
  22. package/src/canvas/PageSelector.jsx +239 -0
  23. package/src/canvas/PageSelector.module.css +165 -0
  24. package/src/canvas/PageSelector.test.jsx +104 -0
  25. package/src/canvas/canvasApi.js +22 -8
  26. package/src/canvas/canvasTheme.js +96 -52
  27. package/src/canvas/componentIsolate.jsx +33 -7
  28. package/src/canvas/useCanvas.js +9 -8
  29. package/src/canvas/useCanvas.test.js +4 -4
  30. package/src/canvas/useMarqueeSelect.js +187 -0
  31. package/src/canvas/useMarqueeSelect.test.js +78 -0
  32. package/src/canvas/widgets/CodePenEmbed.jsx +292 -0
  33. package/src/canvas/widgets/CodePenEmbed.module.css +161 -0
  34. package/src/canvas/widgets/ComponentWidget.jsx +42 -10
  35. package/src/canvas/widgets/ComponentWidget.module.css +6 -5
  36. package/src/canvas/widgets/FigmaEmbed.jsx +110 -24
  37. package/src/canvas/widgets/FigmaEmbed.module.css +21 -7
  38. package/src/canvas/widgets/LinkPreview.jsx +297 -11
  39. package/src/canvas/widgets/LinkPreview.module.css +386 -18
  40. package/src/canvas/widgets/LinkPreview.test.jsx +193 -0
  41. package/src/canvas/widgets/MarkdownBlock.jsx +86 -5
  42. package/src/canvas/widgets/MarkdownBlock.module.css +64 -15
  43. package/src/canvas/widgets/PrototypeEmbed.jsx +96 -145
  44. package/src/canvas/widgets/PrototypeEmbed.module.css +74 -4
  45. package/src/canvas/widgets/StickyNote.module.css +5 -0
  46. package/src/canvas/widgets/StickyNote.test.jsx +9 -9
  47. package/src/canvas/widgets/StoryWidget.jsx +277 -0
  48. package/src/canvas/widgets/StoryWidget.module.css +211 -0
  49. package/src/canvas/widgets/WidgetChrome.jsx +76 -20
  50. package/src/canvas/widgets/WidgetChrome.module.css +2 -6
  51. package/src/canvas/widgets/WidgetWrapper.module.css +2 -0
  52. package/src/canvas/widgets/codepenUrl.js +75 -0
  53. package/src/canvas/widgets/codepenUrl.test.js +76 -0
  54. package/src/canvas/widgets/embedInteraction.test.jsx +235 -0
  55. package/src/canvas/widgets/embedOverlay.module.css +35 -0
  56. package/src/canvas/widgets/embedTheme.js +138 -39
  57. package/src/canvas/widgets/githubUrl.js +82 -0
  58. package/src/canvas/widgets/githubUrl.test.js +74 -0
  59. package/src/canvas/widgets/iframeDevLogs.js +49 -0
  60. package/src/canvas/widgets/iframeDevLogs.test.jsx +81 -0
  61. package/src/canvas/widgets/index.js +4 -0
  62. package/src/canvas/widgets/pasteRules.js +295 -0
  63. package/src/canvas/widgets/pasteRules.test.js +474 -0
  64. package/src/canvas/widgets/snapshotDisplay.test.jsx +259 -0
  65. package/src/canvas/widgets/widgetConfig.js +16 -5
  66. package/src/canvas/widgets/widgetConfig.test.js +34 -12
  67. package/src/context.jsx +145 -16
  68. package/src/hooks/useSceneData.js +4 -2
  69. package/src/hooks/useThemeState.js +61 -0
  70. package/src/hooks/useThemeState.test.js +66 -0
  71. package/src/index.js +10 -0
  72. package/src/story/StoryPage.jsx +117 -0
  73. package/src/story/StoryPage.module.css +18 -0
  74. package/src/vite/data-plugin.js +348 -66
  75. package/src/vite/data-plugin.test.js +405 -5
@@ -1,37 +1,77 @@
1
+ /* ── Plain link-preview card ──────────────────────────────────────── */
2
+
3
+ .container {
4
+ position: relative;
5
+ }
6
+
1
7
  .card {
2
8
  display: flex;
3
- align-items: center;
4
- gap: 12px;
5
- padding: 14px 16px;
6
- text-decoration: none;
7
- color: inherit;
8
- min-width: 240px;
9
- transition: background 150ms;
9
+ flex-direction: column;
10
+ width: 320px;
11
+ border-radius: 6px;
12
+ border: 2px solid var(--borderColor-default, #d1d9e0);
13
+ background: var(--bgColor-default, #ffffff);
14
+ box-sizing: border-box;
15
+ overflow: hidden;
16
+ font-family: var(--tc-font-stack, system-ui, -apple-system, sans-serif);
10
17
  }
11
18
 
12
- .card:hover {
13
- background: var(--bgColor-muted, #f6f8fa);
19
+ :global([data-sb-canvas-theme^='dark']) .card {
20
+ border-color: var(--borderColor-default, #3d444d);
14
21
  }
15
22
 
16
- .icon {
17
- font-size: 20px;
18
- flex-shrink: 0;
23
+ /* Hide own border when parent widget slot is selected (avoid double focus ring) */
24
+ :global([data-widget-selected]) .card {
25
+ border-color: transparent;
19
26
  }
20
27
 
21
- .text {
22
- overflow: hidden;
23
- min-width: 0;
28
+ .ogImage {
29
+ width: 100%;
30
+ max-height: 200px;
31
+ object-fit: cover;
32
+ display: block;
33
+ }
34
+
35
+ .body {
36
+ display: flex;
37
+ flex-direction: column;
38
+ gap: 4px;
39
+ padding: 12px 14px;
24
40
  }
25
41
 
26
42
  .title {
27
43
  margin: 0;
28
44
  font-size: 14px;
29
45
  font-weight: 600;
30
- line-height: 1.4;
46
+ line-height: 1.35;
31
47
  color: var(--fgColor-default, #1f2328);
32
- white-space: nowrap;
48
+ cursor: text;
49
+ word-break: break-word;
50
+ }
51
+
52
+ .titleInput {
53
+ margin: 0;
54
+ padding: 0;
55
+ border: none;
56
+ outline: none;
57
+ background: transparent;
58
+ font-family: inherit;
59
+ font-size: 14px;
60
+ font-weight: 600;
61
+ line-height: 1.35;
62
+ color: var(--fgColor-default, #1f2328);
63
+ width: 100%;
64
+ }
65
+
66
+ .description {
67
+ margin: 0;
68
+ font-size: 12px;
69
+ line-height: 1.4;
70
+ color: var(--fgColor-muted, #656d76);
71
+ display: -webkit-box;
72
+ -webkit-line-clamp: 2;
73
+ -webkit-box-orient: vertical;
33
74
  overflow: hidden;
34
- text-overflow: ellipsis;
35
75
  }
36
76
 
37
77
  .url {
@@ -49,3 +89,331 @@
49
89
  text-decoration: underline;
50
90
  color: var(--fgColor-accent, #0969da);
51
91
  }
92
+
93
+ /* ── GitHub issue / discussion card ──────────────────────────────── */
94
+
95
+ .issueCard {
96
+ display: flex;
97
+ flex-direction: column;
98
+ width: 580px;
99
+ min-height: 300px;
100
+ border-radius: 10px;
101
+ border: 1px solid var(--borderColor-default, #d1d9e0);
102
+ background: var(--bgColor-default, #ffffff);
103
+ box-sizing: border-box;
104
+ overflow: hidden;
105
+ font-family: var(--tc-font-stack, system-ui, -apple-system, sans-serif);
106
+ }
107
+
108
+ .issueCardCollapsed {
109
+ max-height: 800px;
110
+ }
111
+
112
+ .typeBar {
113
+ display: flex;
114
+ align-items: center;
115
+ gap: 6px;
116
+ padding: 10px 10px;
117
+ font-size: 12px;
118
+ font-weight: 500;
119
+ color: var(--fgColor-muted, #656d76);
120
+ background: var(--bgColor-muted, #f6f8fa);
121
+ border-bottom: 1px solid var(--borderColor-muted, #d8dee4);
122
+ white-space: nowrap;
123
+ user-select: none;
124
+ }
125
+
126
+ .issueBodyScrollable {
127
+ flex: 1;
128
+ min-height: 0;
129
+ overflow-y: auto;
130
+ overflow-x: hidden;
131
+ pointer-events: auto;
132
+ }
133
+
134
+ .issueHeader {
135
+ padding: 24px 24px 0;
136
+ }
137
+
138
+ .issueTitle {
139
+ margin: 0 0 4px;
140
+ font-size: 32px;
141
+ font-weight: 400;
142
+ line-height: 1.25;
143
+ color: var(--fgColor-default, #1f2328);
144
+ }
145
+
146
+ .issueTitleLink {
147
+ color: var(--fgColor-default, #1f2328) !important;
148
+ text-decoration: none !important;
149
+ }
150
+
151
+ .issueTitleLink:hover {
152
+ text-decoration: underline !important;
153
+ color: var(--fgColor-default, #1f2328) !important;
154
+ }
155
+
156
+ .issueNumber {
157
+ font-size: 32px;
158
+ font-weight: 400;
159
+ color: var(--fgColor-muted, #656d76);
160
+ }
161
+
162
+ .issueContext {
163
+ margin: 0;
164
+ font-size: 12px;
165
+ color: var(--fgColor-muted, #656d76);
166
+ }
167
+
168
+ .issueByline {
169
+ display: flex;
170
+ align-items: center;
171
+ justify-content: space-between;
172
+ gap: 8px;
173
+ padding: 12px 24px;
174
+ border-bottom: 1px solid var(--borderColor-muted, #d8dee4);
175
+ }
176
+
177
+ .issueBylineLeft {
178
+ display: flex;
179
+ align-items: center;
180
+ gap: 8px;
181
+ min-width: 0;
182
+ }
183
+
184
+ .avatar {
185
+ width: 20px;
186
+ height: 20px;
187
+ border-radius: 50%;
188
+ flex-shrink: 0;
189
+ }
190
+
191
+ .authorLink {
192
+ display: flex;
193
+ align-items: center;
194
+ gap: 8px;
195
+ text-decoration: none !important;
196
+ color: var(--fgColor-default, #1f2328) !important;
197
+ font-size: 13px;
198
+ font-weight: 600;
199
+ white-space: nowrap;
200
+ }
201
+
202
+ .authorLink:hover {
203
+ text-decoration: underline !important;
204
+ color: var(--fgColor-default, #1f2328) !important;
205
+ }
206
+
207
+ .bylineText {
208
+ font-size: 13px;
209
+ color: var(--fgColor-muted, #656d76);
210
+ white-space: nowrap;
211
+ overflow: hidden;
212
+ text-overflow: ellipsis;
213
+ }
214
+
215
+ .bylineText strong {
216
+ font-weight: 600;
217
+ color: var(--fgColor-default, #1f2328);
218
+ }
219
+
220
+ /* ── Issue body (rendered markdown) ──────────────────────────────── */
221
+
222
+ .issueBody {
223
+ padding: 16px 24px 24px;
224
+ font-size: 14px;
225
+ line-height: 1.6;
226
+ color: var(--fgColor-default, #1f2328);
227
+ }
228
+
229
+ .issueBody :global(*) {
230
+ pointer-events: none;
231
+ }
232
+
233
+ .issueBody a {
234
+ color: var(--fgColor-accent, #0969da);
235
+ text-decoration: none;
236
+ pointer-events: auto;
237
+ cursor: pointer;
238
+ }
239
+
240
+ .issueBody a:hover {
241
+ text-decoration: underline;
242
+ }
243
+
244
+ .issueBody img {
245
+ max-width: 100%;
246
+ height: auto;
247
+ border-radius: 6px;
248
+ border: 1px solid var(--borderColor-muted, #d8dee4);
249
+ margin: 8px 0;
250
+ display: block;
251
+ pointer-events: auto;
252
+ }
253
+
254
+ .issueBody video {
255
+ max-width: 100%;
256
+ height: auto;
257
+ border-radius: 6px;
258
+ border: 1px solid var(--borderColor-muted, #d8dee4);
259
+ margin: 8px 0;
260
+ display: block;
261
+ pointer-events: auto;
262
+ background: var(--bgColor-muted, #f6f8fa);
263
+ }
264
+ .issueBody h1 {
265
+ font-size: 20px;
266
+ font-weight: 700;
267
+ margin: 16px 0 8px;
268
+ padding-bottom: 4px;
269
+ border-bottom: 1px solid var(--borderColor-muted, #d8dee4);
270
+ line-height: 1.3;
271
+ }
272
+
273
+ .issueBody h1:first-child {
274
+ margin-top: 0;
275
+ }
276
+
277
+ .issueBody h2 {
278
+ font-size: 17px;
279
+ font-weight: 600;
280
+ margin: 14px 0 6px;
281
+ padding-bottom: 3px;
282
+ border-bottom: 1px solid var(--borderColor-muted, #d8dee4);
283
+ line-height: 1.3;
284
+ }
285
+
286
+ .issueBody h3 {
287
+ font-size: 15px;
288
+ font-weight: 600;
289
+ margin: 12px 0 4px;
290
+ line-height: 1.3;
291
+ }
292
+
293
+ .issueBody p {
294
+ margin: 0 0 12px;
295
+ }
296
+
297
+ .issueBody code {
298
+ background: var(--bgColor-neutral-muted, #afb8c133);
299
+ padding: 2px 5px;
300
+ border-radius: 4px;
301
+ font-size: 12px;
302
+ font-weight: 400;
303
+ font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
304
+ }
305
+
306
+ .issueBody ul {
307
+ margin: 0 0 12px;
308
+ padding-left: 24px;
309
+ list-style-type: disc;
310
+ }
311
+
312
+ .issueBody ol {
313
+ margin: 0 0 12px;
314
+ padding-left: 24px;
315
+ list-style-type: decimal;
316
+ }
317
+
318
+ .issueBody li {
319
+ margin: 0 0 4px;
320
+ display: list-item;
321
+ }
322
+
323
+ .issueBody li > ul,
324
+ .issueBody li > ol {
325
+ margin-top: 4px;
326
+ margin-bottom: 4px;
327
+ }
328
+
329
+ /* Accent-color checkboxes */
330
+ .issueBody input[type="checkbox"] {
331
+ margin-right: 6px;
332
+ pointer-events: none;
333
+ accent-color: var(--fgColor-accent, #0969da);
334
+ }
335
+
336
+ .issueBody li:has(input[type="checkbox"]) {
337
+ list-style: none;
338
+ margin-left: -24px;
339
+ }
340
+
341
+ /* GFM: Strikethrough */
342
+ .issueBody del {
343
+ text-decoration: line-through;
344
+ color: var(--fgColor-muted, #656d76);
345
+ }
346
+
347
+ /* GFM: Tables */
348
+ .issueBody table {
349
+ border-collapse: collapse;
350
+ margin: 12px 0;
351
+ width: 100%;
352
+ font-size: 13px;
353
+ }
354
+
355
+ .issueBody th,
356
+ .issueBody td {
357
+ border: 1px solid var(--borderColor-default, #d0d7de);
358
+ padding: 6px 12px;
359
+ text-align: left;
360
+ }
361
+
362
+ .issueBody th {
363
+ background: var(--bgColor-muted, #f6f8fa);
364
+ font-weight: 600;
365
+ }
366
+
367
+ /* Code blocks */
368
+ .issueBody pre {
369
+ padding: 12px 16px;
370
+ border-radius: 6px;
371
+ border: 1px solid var(--borderColor-muted, #d8dee4);
372
+ overflow-x: auto;
373
+ margin: 12px 0;
374
+ background: var(--bgColor-neutral-muted, #afb8c133);
375
+ line-height: 1.4;
376
+ }
377
+
378
+ .issueBody pre code {
379
+ background: none;
380
+ padding: 0;
381
+ font-size: 12px;
382
+ white-space: pre;
383
+ word-break: normal;
384
+ overflow-wrap: normal;
385
+ display: block;
386
+ }
387
+
388
+ /* Blockquotes */
389
+ .issueBody blockquote {
390
+ border-left: 4px solid var(--borderColor-default, #d0d7de);
391
+ margin: 12px 0;
392
+ padding: 4px 16px;
393
+ color: var(--fgColor-muted, #656d76);
394
+ }
395
+
396
+ /* Horizontal rules */
397
+ .issueBody hr {
398
+ border: none;
399
+ border-top: 1px solid var(--borderColor-default, #d0d7de);
400
+ margin: 16px 0;
401
+ }
402
+
403
+ /* Details/summary — show content expanded, no collapse UI */
404
+ .issueBody details {
405
+ border: none;
406
+ margin: 0;
407
+ padding: 0;
408
+ }
409
+
410
+ .issueBody summary {
411
+ display: none;
412
+ }
413
+
414
+ .error {
415
+ margin: 0;
416
+ padding: 0 24px 12px;
417
+ font-size: 12px;
418
+ color: var(--fgColor-danger, #cf222e);
419
+ }
@@ -0,0 +1,193 @@
1
+ import { render, screen, fireEvent } from '@testing-library/react'
2
+ import { describe, expect, it, vi } from 'vitest'
3
+ import LinkPreview from './LinkPreview.jsx'
4
+
5
+ describe('LinkPreview', () => {
6
+ it('renders GitHub issue card with markdown body and author byline', () => {
7
+ const { container } = render(
8
+ <LinkPreview
9
+ id="link-1"
10
+ props={{
11
+ url: 'https://github.com/dfosco/storyboard/issues/42',
12
+ title: '#42 Ship GitHub embeds',
13
+ github: {
14
+ context: 'GitHub · dfosco/storyboard · Issue #42',
15
+ body: '## Summary\n\nThis is a **bold** point.\n\n- Item one\n- Item two',
16
+ authors: ['dfosco'],
17
+ createdAt: '2026-01-01T00:00:00Z',
18
+ },
19
+ }}
20
+ />,
21
+ )
22
+
23
+ // Title split: text + muted number
24
+ expect(screen.getByText('Ship GitHub embeds')).toBeInTheDocument()
25
+ expect(screen.getByText('#42')).toBeInTheDocument()
26
+
27
+ // Markdown body renders headings, bold, lists
28
+ const headings = container.querySelectorAll('h2')
29
+ expect(headings.length).toBeGreaterThanOrEqual(1)
30
+ // Find the body heading (not the title)
31
+ const summaryHeading = Array.from(headings).find(h => h.textContent === 'Summary')
32
+ expect(summaryHeading).toBeTruthy()
33
+ expect(container.querySelectorAll('li')).toHaveLength(2)
34
+
35
+ // Author byline
36
+ expect(screen.getByText('dfosco')).toBeInTheDocument()
37
+ })
38
+
39
+ it('does not render GitHub layout for non-GitHub links', () => {
40
+ render(
41
+ <LinkPreview
42
+ id="link-2"
43
+ props={{
44
+ url: 'https://example.com/docs',
45
+ title: 'Example docs',
46
+ }}
47
+ />,
48
+ )
49
+
50
+ expect(screen.getByText('Example docs')).toBeInTheDocument()
51
+ expect(screen.getByText('example.com')).toBeInTheDocument()
52
+ })
53
+
54
+ it('renders plain link-preview without github data', () => {
55
+ const { container } = render(
56
+ <LinkPreview
57
+ id="link-3"
58
+ props={{
59
+ url: 'https://figma.com/design/abc',
60
+ title: 'My design',
61
+ width: 320,
62
+ height: 120,
63
+ }}
64
+ />,
65
+ )
66
+
67
+ expect(screen.getByText('My design')).toBeInTheDocument()
68
+ // No issue card rendered
69
+ expect(container.querySelector('header')).toBeNull()
70
+ })
71
+
72
+ it('renders OG image when present', () => {
73
+ const { container } = render(
74
+ <LinkPreview
75
+ id="link-4"
76
+ props={{
77
+ url: 'https://example.com',
78
+ title: 'With image',
79
+ ogImage: 'https://example.com/og.png',
80
+ }}
81
+ />,
82
+ )
83
+
84
+ const img = container.querySelector('img')
85
+ expect(img).toBeTruthy()
86
+ expect(img.src).toBe('https://example.com/og.png')
87
+ })
88
+
89
+ it('does not render image element when ogImage is absent', () => {
90
+ const { container } = render(
91
+ <LinkPreview
92
+ id="link-5"
93
+ props={{
94
+ url: 'https://example.com',
95
+ title: 'No image',
96
+ }}
97
+ />,
98
+ )
99
+
100
+ expect(container.querySelector('img')).toBeNull()
101
+ })
102
+
103
+ it('hides broken OG image on error', () => {
104
+ const { container } = render(
105
+ <LinkPreview
106
+ id="link-6"
107
+ props={{
108
+ url: 'https://example.com',
109
+ title: 'Broken image',
110
+ ogImage: 'https://example.com/broken.png',
111
+ }}
112
+ />,
113
+ )
114
+
115
+ const img = container.querySelector('img')
116
+ expect(img).toBeTruthy()
117
+ fireEvent.error(img)
118
+ expect(img.style.display).toBe('none')
119
+ })
120
+
121
+ it('renders description when present', () => {
122
+ render(
123
+ <LinkPreview
124
+ id="link-7"
125
+ props={{
126
+ url: 'https://example.com',
127
+ title: 'With desc',
128
+ description: 'A short description of the page',
129
+ }}
130
+ />,
131
+ )
132
+
133
+ expect(screen.getByText('A short description of the page')).toBeInTheDocument()
134
+ })
135
+
136
+ it('enters edit mode on double-click and saves on change', () => {
137
+ const onUpdate = vi.fn()
138
+ render(
139
+ <LinkPreview
140
+ id="link-8"
141
+ props={{
142
+ url: 'https://example.com',
143
+ title: 'Editable title',
144
+ }}
145
+ onUpdate={onUpdate}
146
+ />,
147
+ )
148
+
149
+ const titleEl = screen.getByText('Editable title')
150
+ fireEvent.doubleClick(titleEl)
151
+
152
+ const input = document.querySelector('input[type="text"]')
153
+ expect(input).toBeTruthy()
154
+ expect(input.value).toBe('Editable title')
155
+
156
+ fireEvent.change(input, { target: { value: 'New title' } })
157
+ expect(onUpdate).toHaveBeenCalledWith({ title: 'New title' })
158
+ })
159
+
160
+ it('does not enter edit mode when onUpdate is missing', () => {
161
+ render(
162
+ <LinkPreview
163
+ id="link-9"
164
+ props={{
165
+ url: 'https://example.com',
166
+ title: 'Read-only',
167
+ }}
168
+ />,
169
+ )
170
+
171
+ const titleEl = screen.getByText('Read-only')
172
+ fireEvent.doubleClick(titleEl)
173
+
174
+ // Should not show an input
175
+ expect(document.querySelector('input[type="text"]')).toBeNull()
176
+ })
177
+
178
+ it('shows fallback text when title is empty', () => {
179
+ render(
180
+ <LinkPreview
181
+ id="link-10"
182
+ props={{
183
+ url: 'https://example.com/page',
184
+ }}
185
+ onUpdate={() => {}}
186
+ />,
187
+ )
188
+
189
+ // Falls back to hostname — appears in both title and URL
190
+ const matches = screen.getAllByText('example.com')
191
+ expect(matches.length).toBeGreaterThanOrEqual(1)
192
+ })
193
+ })