@adatechnology/conversations-ui 0.7.0 → 0.9.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.
- package/README.md +96 -1
- package/dist/{ConversationSimulatorPanel-CrfFjiWh.d.ts → ConversationSimulatorPanel-q-wVgBCN.d.ts} +1 -1
- package/dist/{chunk-HMCOTZAX.js → chunk-NSOAUYNE.js} +33 -28
- package/dist/chunk-ZSOD5AQT.js +19 -0
- package/dist/flows/index.js +4 -2
- package/dist/index.d.ts +4 -4
- package/dist/index.js +5 -3
- package/dist/participant/index.d.ts +48 -3
- package/dist/participant/index.js +782 -347
- package/dist/preview/index.d.ts +3 -3
- package/dist/{quickReply.types-B4gmZsSr.d.ts → quickReply.types-DzWH3NYV.d.ts} +1 -1
- package/dist/styles.css +173 -52
- package/package.json +2 -1
- package/src/Tooltip.tsx +5 -12
- package/src/TooltipDom.test.tsx +78 -0
- package/src/domHarness.test-helper.ts +53 -0
- package/src/participant/ConversationThread.test.tsx +165 -0
- package/src/participant/ConversationThread.tsx +117 -0
- package/src/participant/ParticipantBackButton.tsx +1 -1
- package/src/participant/ParticipantBubbleBody.tsx +54 -0
- package/src/participant/ParticipantBubbleStatus.tsx +22 -28
- package/src/participant/ParticipantChannelBadges.tsx +10 -2
- package/src/participant/ParticipantComposer.tsx +1 -1
- package/src/participant/ParticipantComposerControls.tsx +2 -0
- package/src/participant/ParticipantCopyableToken.tsx +1 -0
- package/src/participant/ParticipantFailedMenu.tsx +40 -0
- package/src/participant/ParticipantFailedMenuList.tsx +41 -0
- package/src/participant/ParticipantMessageBubble.test.tsx +6 -6
- package/src/participant/ParticipantMessageBubble.tsx +26 -54
- package/src/participant/ParticipantMessageText.test.tsx +1 -1
- package/src/participant/ParticipantMessageText.tsx +4 -2
- package/src/participant/ParticipantProtocolBadge.tsx +1 -0
- package/src/participant/ParticipantScrollToLatest.tsx +1 -1
- package/src/participant/ParticipantThread.tsx +30 -10
- package/src/participant/ParticipantThreadHeader.test.tsx +1 -1
- package/src/participant/ParticipantThreadHeader.tsx +10 -0
- package/src/participant/ParticipantThreadScreen.tsx +6 -46
- package/src/participant/ParticipantTimeline.tsx +6 -3
- package/src/participant/conversationThreadApi.test.ts +27 -0
- package/src/participant/conversationThreadApi.ts +24 -0
- package/src/participant/conversationThreadCss.test.ts +116 -0
- package/src/participant/conversationThreadSummary.ts +34 -0
- package/src/participant/failedMenuKeyboard.ts +29 -0
- package/src/participant/index.ts +3 -0
- package/src/participant/participantContrast.test-helper.ts +116 -0
- package/src/participant/participantFailedMenuDom.test.tsx +154 -0
- package/src/participant/participantFailedMenuState.test.ts +61 -0
- package/src/participant/participantFailedMenuState.ts +57 -0
- package/src/participant/participantFailedMessage.test.tsx +83 -0
- package/src/participant/participantGolden.test.tsx +46 -0
- package/src/participant/participantGoldenScenarios.test-helper.tsx +57 -0
- package/src/participant/participantLabels.ts +6 -0
- package/src/participant/participantMessages.ts +4 -3
- package/src/participant/participantNewMessages.test.ts +21 -0
- package/src/participant/participantNewMessages.ts +8 -3
- package/src/participant/participantPerspective.test.ts +67 -0
- package/src/participant/participantPerspective.ts +50 -0
- package/src/participant/participantPerspectiveRender.test.tsx +94 -0
- package/src/participant/participantThemeContrast.test.ts +106 -0
- package/src/participant/participantTooltips.test.tsx +74 -0
- package/src/participant/test-fixtures/conversations-list.golden.html +1 -0
- package/src/participant/test-fixtures/conversations-selected.golden.html +1 -0
- package/src/participant/test-fixtures/thread-avatars-initials.golden.html +1 -0
- package/src/participant/test-fixtures/thread-closed.golden.html +1 -0
- package/src/participant/test-fixtures/thread-default.golden.html +1 -0
- package/src/participant/test-fixtures/thread-error.golden.html +1 -0
- package/src/participant/test-fixtures/thread-loading.golden.html +1 -0
- package/src/participant/test-fixtures/thread-plain.golden.html +1 -0
- package/src/participant/test-fixtures/thread-tail-off.golden.html +1 -0
- package/src/participant/useFailedMenuEffects.ts +59 -0
- package/src/participant/useParticipantConversationView.ts +3 -0
- package/src/participant/useParticipantFailedMenu.ts +60 -0
- package/src/participant/useParticipantThread.ts +13 -2
- package/src/participant/useParticipantThreadController.ts +112 -0
- package/src/participant/useParticipantThreadScroll.ts +3 -1
- package/src/participant/useParticipantUnread.test.tsx +40 -0
- package/src/participant/useParticipantUnread.ts +38 -0
- package/src/participant/useSuppressTouchTooltips.ts +38 -0
- package/src/styles.css +43 -17
- package/src/tooltipLayerRegistry.test.ts +56 -0
- package/src/tooltipLayerRegistry.ts +34 -0
package/dist/preview/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { M as MessagePayload } from '../quickReply.types-
|
|
2
|
-
import { C as ConversationSummary, a as ConversationEventSource, b as ConversationsApi, L as ListConversationsParams, c as ConversationPage, S as SSEProvider, d as ConversationDocument, e as SendPreviewMediaParams, P as PreviewUploadedMedia, f as PreviewWebhookClient, R as ResolveMediaUrl } from '../ConversationSimulatorPanel-
|
|
3
|
-
export { A as AudioRecorderButton, g as AudioRecorderButtonLabels, h as AudioRecorderButtonProps, i as ConversationPreview, j as ConversationPreviewProps, k as ConversationSimulatorClient, l as ConversationSimulatorPanel, m as ConversationSimulatorPanelLabels, n as ConversationSimulatorPanelProps, o as CreatePreviewMediaUploaderParams, p as CreatePreviewWebhookClientParams, D as DEFAULT_AUDIO_RECORDER_BUTTON_LABELS, q as DEFAULT_CONVERSATION_SIMULATOR_PANEL_LABELS, r as DEFAULT_MEDIA_UPLOAD_PATH, s as PreviewInProductionError, t as PreviewMediaUploadRejectedError, u as PreviewMediaUploadRequest, v as PreviewUploadedMedia, w as PreviewWebhookRejectedError, x as SIMULATOR_FILE_MEDIA_KINDS, y as SendSimulatorMediaParams, z as SimulatorMediaKind, T as ToSimulatorClientParams, B as acceptsMediaKind, E as assertPreviewEnvironment, F as createPreviewMediaPoster, G as createPreviewMediaUploader, H as createPreviewWebhookClient, I as isConversationSimulatorClient, J as mediaKindOf, K as mediaTypeOf, M as signPreviewPayload, N as simulatorPanelLabelsOf, O as toConversationSimulatorClient } from '../ConversationSimulatorPanel-
|
|
1
|
+
import { M as MessagePayload } from '../quickReply.types-DzWH3NYV.js';
|
|
2
|
+
import { C as ConversationSummary, a as ConversationEventSource, b as ConversationsApi, L as ListConversationsParams, c as ConversationPage, S as SSEProvider, d as ConversationDocument, e as SendPreviewMediaParams, P as PreviewUploadedMedia, f as PreviewWebhookClient, R as ResolveMediaUrl } from '../ConversationSimulatorPanel-q-wVgBCN.js';
|
|
3
|
+
export { A as AudioRecorderButton, g as AudioRecorderButtonLabels, h as AudioRecorderButtonProps, i as ConversationPreview, j as ConversationPreviewProps, k as ConversationSimulatorClient, l as ConversationSimulatorPanel, m as ConversationSimulatorPanelLabels, n as ConversationSimulatorPanelProps, o as CreatePreviewMediaUploaderParams, p as CreatePreviewWebhookClientParams, D as DEFAULT_AUDIO_RECORDER_BUTTON_LABELS, q as DEFAULT_CONVERSATION_SIMULATOR_PANEL_LABELS, r as DEFAULT_MEDIA_UPLOAD_PATH, s as PreviewInProductionError, t as PreviewMediaUploadRejectedError, u as PreviewMediaUploadRequest, v as PreviewUploadedMedia, w as PreviewWebhookRejectedError, x as SIMULATOR_FILE_MEDIA_KINDS, y as SendSimulatorMediaParams, z as SimulatorMediaKind, T as ToSimulatorClientParams, B as acceptsMediaKind, E as assertPreviewEnvironment, F as createPreviewMediaPoster, G as createPreviewMediaUploader, H as createPreviewWebhookClient, I as isConversationSimulatorClient, J as mediaKindOf, K as mediaTypeOf, M as signPreviewPayload, N as simulatorPanelLabelsOf, O as toConversationSimulatorClient } from '../ConversationSimulatorPanel-q-wVgBCN.js';
|
|
4
4
|
import { InteractiveReplyOption } from '@adatechnology/meta-whatsapp-contracts/testing';
|
|
5
5
|
import * as react from 'react';
|
|
6
6
|
export { PREVIEW_MEDIA_ID_PREFIX } from '@adatechnology/meta-whatsapp-contracts';
|
|
@@ -271,4 +271,4 @@ type ConversationVariable = {
|
|
|
271
271
|
readonly value: string;
|
|
272
272
|
};
|
|
273
273
|
|
|
274
|
-
export { type ConversationChannel as C, DEFAULT_CONVERSATION_CHANNEL as D, type FormatContactHandleParams as F, HANDLE_KIND as H, type InteractiveSelection as I, type MessagePayload as M, type QuickReply as Q, REOPEN_MECHANISM as R, type StoredAttachmentSendResult as S, type TranscriptionMode as T, type
|
|
274
|
+
export { type ConversationChannel as C, DEFAULT_CONVERSATION_CHANNEL as D, type FormatContactHandleParams as F, HANDLE_KIND as H, type InteractiveSelection as I, type MessagePayload as M, type QuickReply as Q, REOPEN_MECHANISM as R, type StoredAttachmentSendResult as S, type TranscriptionMode as T, type MessageTranscription as a, type QuickReplyInput as b, type QuickReplyAttachment as c, type ConversationsTheme as d, type InteractivePayload as e, type ConversationsFeatures as f, type QueuedAttachment as g, type ConversationVariable as h, type ChannelFilter as i, type ChannelFilterOption as j, CHANNEL_CAPABILITIES as k, CHANNEL_FILTER_ALL as l, CONVERSATION_CHANNEL as m, type ChannelCapabilities as n, type ConversationsUIConfig as o, type HandleKind as p, type InteractiveOption as q, type InteractiveSection as r, QUICK_REPLY_ATTACHMENT_LIMIT as s, type ReopenMechanism as t, type TranscriptionStatus as u, capabilitiesOf as v, channelFiltersFor as w, contactFlag as x, formatContactHandle as y };
|
package/dist/styles.css
CHANGED
|
@@ -1314,12 +1314,14 @@
|
|
|
1314
1314
|
--cv-p-i-border: var(--cv-p-border, #d5d9df);
|
|
1315
1315
|
--cv-p-i-accent: var(--cv-p-accent, #a85a1c);
|
|
1316
1316
|
--cv-p-i-accent-contrast: var(--cv-p-accent-contrast, #ffffff);
|
|
1317
|
-
--cv-p-i-highlight: var(--cv-p-highlight, #
|
|
1317
|
+
--cv-p-i-highlight: var(--cv-p-highlight, #fcf3ea);
|
|
1318
1318
|
--cv-p-i-danger: var(--cv-p-danger, #c62828);
|
|
1319
1319
|
--cv-p-i-radius: var(--cv-p-radius, 0);
|
|
1320
1320
|
--cv-p-i-touch: 2.75rem;
|
|
1321
1321
|
--cv-p-i-scrollbar-thumb: var(--cv-p-scrollbar-thumb, var(--cv-p-i-border));
|
|
1322
1322
|
--cv-p-i-scrollbar-thumb-hover: var(--cv-p-scrollbar-thumb-hover, var(--cv-p-i-accent));
|
|
1323
|
+
--cv-p-i-tick: var(--cv-p-tick, var(--cv-p-i-text-muted));
|
|
1324
|
+
--cv-p-i-tick-read: var(--cv-p-tick-read, #0369a1);
|
|
1323
1325
|
--cv-p-i-wallpaper-ink: var(--cv-p-i-border);
|
|
1324
1326
|
--cv-p-i-wallpaper:
|
|
1325
1327
|
radial-gradient(
|
|
@@ -1354,6 +1356,7 @@
|
|
|
1354
1356
|
--cv-p-i-accent-contrast: var(--cv-p-accent-contrast, #10222c);
|
|
1355
1357
|
--cv-p-i-highlight: var(--cv-p-highlight, rgb(213 138 71 / 10%));
|
|
1356
1358
|
--cv-p-i-danger: var(--cv-p-danger, #ff6b63);
|
|
1359
|
+
--cv-p-i-tick-read: var(--cv-p-tick-read, #5cc8ff);
|
|
1357
1360
|
}
|
|
1358
1361
|
.cv-p *,
|
|
1359
1362
|
.cv-p *::before,
|
|
@@ -1379,8 +1382,8 @@
|
|
|
1379
1382
|
.cv-p-chip,
|
|
1380
1383
|
.cv-p-row,
|
|
1381
1384
|
.cv-p-thread__older,
|
|
1382
|
-
.cv-p-
|
|
1383
|
-
.cv-p-
|
|
1385
|
+
.cv-p-failed-menu__trigger,
|
|
1386
|
+
.cv-p-failed-menu__item {
|
|
1384
1387
|
font: inherit;
|
|
1385
1388
|
color: inherit;
|
|
1386
1389
|
cursor: pointer;
|
|
@@ -1463,6 +1466,9 @@
|
|
|
1463
1466
|
.cv-p-error__message {
|
|
1464
1467
|
margin: 0;
|
|
1465
1468
|
}
|
|
1469
|
+
.cv-p-thread__scroll > .cv-p-error {
|
|
1470
|
+
background: var(--cv-p-i-surface);
|
|
1471
|
+
}
|
|
1466
1472
|
.cv-p-loading {
|
|
1467
1473
|
display: flex;
|
|
1468
1474
|
flex-direction: column;
|
|
@@ -1706,6 +1712,29 @@
|
|
|
1706
1712
|
text-overflow: ellipsis;
|
|
1707
1713
|
min-width: 0;
|
|
1708
1714
|
}
|
|
1715
|
+
.cv-p-thread__counterpart {
|
|
1716
|
+
margin: 0;
|
|
1717
|
+
overflow: hidden;
|
|
1718
|
+
text-overflow: ellipsis;
|
|
1719
|
+
white-space: nowrap;
|
|
1720
|
+
font-size: 0.78rem;
|
|
1721
|
+
line-height: 1.3;
|
|
1722
|
+
color: var(--cv-p-i-text-muted);
|
|
1723
|
+
}
|
|
1724
|
+
.cv-p-thread__actions {
|
|
1725
|
+
flex: 0 0 auto;
|
|
1726
|
+
display: flex;
|
|
1727
|
+
align-items: center;
|
|
1728
|
+
gap: 0.5rem;
|
|
1729
|
+
margin-inline-start: auto;
|
|
1730
|
+
max-width: 100%;
|
|
1731
|
+
}
|
|
1732
|
+
.cv-p-thread__header:has(.cv-p-thread__actions) {
|
|
1733
|
+
flex-wrap: wrap;
|
|
1734
|
+
}
|
|
1735
|
+
.cv-p-thread__header:has(.cv-p-thread__actions) .cv-p-thread__heading {
|
|
1736
|
+
min-width: 6rem;
|
|
1737
|
+
}
|
|
1709
1738
|
.cv-p-thread__meta {
|
|
1710
1739
|
display: flex;
|
|
1711
1740
|
flex-wrap: wrap;
|
|
@@ -1770,6 +1799,7 @@
|
|
|
1770
1799
|
padding: 0 1rem;
|
|
1771
1800
|
font-size: 0.75rem;
|
|
1772
1801
|
color: var(--cv-p-i-accent);
|
|
1802
|
+
background: var(--cv-p-i-surface-raised);
|
|
1773
1803
|
}
|
|
1774
1804
|
.cv-p-thread__live:empty {
|
|
1775
1805
|
position: absolute;
|
|
@@ -1911,33 +1941,6 @@
|
|
|
1911
1941
|
.cv-p-bubble--no-tail::after {
|
|
1912
1942
|
content: none;
|
|
1913
1943
|
}
|
|
1914
|
-
@media (forced-colors: active) {
|
|
1915
|
-
.cv-p-bubble::before,
|
|
1916
|
-
.cv-p-bubble::after {
|
|
1917
|
-
content: none;
|
|
1918
|
-
}
|
|
1919
|
-
.cv-p-bubble,
|
|
1920
|
-
.cv-p-bubble--mine {
|
|
1921
|
-
border-radius: var(--cv-p-i-radius);
|
|
1922
|
-
}
|
|
1923
|
-
.cv-p-thread__scroll {
|
|
1924
|
-
background-image: none;
|
|
1925
|
-
}
|
|
1926
|
-
.cv-p-thread__scroll,
|
|
1927
|
-
.cv-p-inbox,
|
|
1928
|
-
.cv-p-composer__input {
|
|
1929
|
-
scrollbar-width: auto;
|
|
1930
|
-
scrollbar-color: auto;
|
|
1931
|
-
}
|
|
1932
|
-
.cv-p-filters,
|
|
1933
|
-
.cv-p-quick {
|
|
1934
|
-
mask-image: none;
|
|
1935
|
-
-webkit-mask-image: none;
|
|
1936
|
-
}
|
|
1937
|
-
.cv-p-thread__latest-badge {
|
|
1938
|
-
border: 1px solid ButtonText;
|
|
1939
|
-
}
|
|
1940
|
-
}
|
|
1941
1944
|
@media print {
|
|
1942
1945
|
.cv-p-bubble::before,
|
|
1943
1946
|
.cv-p-bubble::after {
|
|
@@ -1967,8 +1970,7 @@
|
|
|
1967
1970
|
min-width: 0;
|
|
1968
1971
|
margin-inline-end: auto;
|
|
1969
1972
|
}
|
|
1970
|
-
.cv-p-bubble > .cv-p-attachment
|
|
1971
|
-
.cv-p-bubble > .cv-p-bubble__actions {
|
|
1973
|
+
.cv-p-bubble > .cv-p-attachment {
|
|
1972
1974
|
flex: 1 0 100%;
|
|
1973
1975
|
}
|
|
1974
1976
|
.cv-p-bubble__meta {
|
|
@@ -1990,33 +1992,114 @@
|
|
|
1990
1992
|
align-items: center;
|
|
1991
1993
|
gap: 0.25rem;
|
|
1992
1994
|
}
|
|
1993
|
-
.cv-p-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1995
|
+
.cv-p-bubble .cv-status-ticks--queued,
|
|
1996
|
+
.cv-p-bubble .cv-status-ticks--sent,
|
|
1997
|
+
.cv-p-bubble .cv-status-ticks--delivered {
|
|
1998
|
+
color: var(--cv-p-i-tick);
|
|
1999
|
+
}
|
|
2000
|
+
.cv-p-bubble .cv-status-ticks--read {
|
|
2001
|
+
color: var(--cv-p-i-tick-read);
|
|
2002
|
+
}
|
|
2003
|
+
.cv-p-bubble .cv-status-ticks--failed {
|
|
2004
|
+
color: var(--cv-p-i-danger);
|
|
2005
|
+
}
|
|
2006
|
+
@supports (color: color-mix(in srgb, red, blue)) {
|
|
2007
|
+
.cv-p-bubble--failed {
|
|
2008
|
+
border-color: color-mix(in srgb, var(--cv-p-i-danger) 55%, var(--cv-p-i-border));
|
|
2009
|
+
--cv-p-tail-edge: color-mix(in srgb, var(--cv-p-i-danger) 55%, var(--cv-p-i-border));
|
|
2010
|
+
}
|
|
2011
|
+
.cv-p-bubble--mine.cv-p-bubble--failed {
|
|
2012
|
+
border-color: color-mix(in srgb, var(--cv-p-i-danger) 55%, var(--cv-p-i-accent));
|
|
2013
|
+
--cv-p-tail-edge: color-mix(in srgb, var(--cv-p-i-danger) 55%, var(--cv-p-i-accent));
|
|
2014
|
+
}
|
|
2015
|
+
}
|
|
2016
|
+
.cv-p-failed-retry {
|
|
2017
|
+
position: absolute;
|
|
2018
|
+
top: 50%;
|
|
2019
|
+
right: calc(100% + 0.125rem);
|
|
2020
|
+
transform: translateY(-50%);
|
|
2021
|
+
display: grid;
|
|
2022
|
+
place-items: center;
|
|
2023
|
+
width: var(--cv-p-i-touch);
|
|
2024
|
+
height: var(--cv-p-i-touch);
|
|
2025
|
+
padding: 0;
|
|
1999
2026
|
background: transparent;
|
|
2000
|
-
border:
|
|
2027
|
+
border: 0;
|
|
2001
2028
|
color: var(--cv-p-i-danger);
|
|
2002
|
-
|
|
2003
|
-
|
|
2029
|
+
font: inherit;
|
|
2030
|
+
cursor: pointer;
|
|
2004
2031
|
}
|
|
2005
|
-
.cv-p-
|
|
2006
|
-
display:
|
|
2007
|
-
|
|
2008
|
-
|
|
2032
|
+
.cv-p-failed-retry__icon {
|
|
2033
|
+
display: grid;
|
|
2034
|
+
place-items: center;
|
|
2035
|
+
width: 2rem;
|
|
2036
|
+
height: 2rem;
|
|
2037
|
+
border: 1px solid var(--cv-p-i-danger);
|
|
2038
|
+
border-radius: 50%;
|
|
2009
2039
|
}
|
|
2010
|
-
.cv-p-
|
|
2011
|
-
|
|
2040
|
+
.cv-p-failed-retry:hover .cv-p-failed-retry__icon,
|
|
2041
|
+
.cv-p-failed-retry:focus-visible .cv-p-failed-retry__icon {
|
|
2042
|
+
background: var(--cv-p-i-surface-raised);
|
|
2043
|
+
}
|
|
2044
|
+
.cv-p-failed-menu {
|
|
2012
2045
|
display: inline-flex;
|
|
2013
|
-
|
|
2014
|
-
|
|
2046
|
+
}
|
|
2047
|
+
.cv-p-failed-menu__trigger {
|
|
2048
|
+
position: relative;
|
|
2049
|
+
display: inline-grid;
|
|
2050
|
+
place-items: center;
|
|
2051
|
+
min-width: 1.25rem;
|
|
2052
|
+
height: 0.75rem;
|
|
2053
|
+
padding: 0;
|
|
2015
2054
|
background: transparent;
|
|
2055
|
+
border: 0;
|
|
2056
|
+
color: var(--cv-p-i-text-muted);
|
|
2057
|
+
font: inherit;
|
|
2058
|
+
line-height: 0.75rem;
|
|
2059
|
+
cursor: pointer;
|
|
2060
|
+
}
|
|
2061
|
+
.cv-p-failed-menu__trigger::after {
|
|
2062
|
+
content: "";
|
|
2063
|
+
position: absolute;
|
|
2064
|
+
inset: -0.5rem -0.4rem;
|
|
2065
|
+
}
|
|
2066
|
+
.cv-p-failed-menu__list {
|
|
2067
|
+
position: absolute;
|
|
2068
|
+
right: 0;
|
|
2069
|
+
bottom: calc(100% + 0.25rem);
|
|
2070
|
+
z-index: 2;
|
|
2071
|
+
display: flex;
|
|
2072
|
+
flex-direction: column;
|
|
2073
|
+
min-width: 8rem;
|
|
2074
|
+
background: var(--cv-p-i-surface-raised);
|
|
2016
2075
|
border: 1px solid var(--cv-p-i-border);
|
|
2017
|
-
color: inherit;
|
|
2018
2076
|
border-radius: var(--cv-p-i-radius);
|
|
2019
|
-
|
|
2077
|
+
}
|
|
2078
|
+
.cv-p-failed-menu__item {
|
|
2079
|
+
min-height: var(--cv-p-i-touch);
|
|
2080
|
+
padding: 0 0.75rem;
|
|
2081
|
+
background: transparent;
|
|
2082
|
+
border: 0;
|
|
2083
|
+
color: var(--cv-p-i-text);
|
|
2084
|
+
font:
|
|
2085
|
+
0.7rem "SFMono-Regular",
|
|
2086
|
+
Consolas,
|
|
2087
|
+
monospace;
|
|
2088
|
+
text-align: start;
|
|
2089
|
+
text-transform: uppercase;
|
|
2090
|
+
letter-spacing: 0.04em;
|
|
2091
|
+
cursor: pointer;
|
|
2092
|
+
}
|
|
2093
|
+
.cv-p-failed-menu__list--below {
|
|
2094
|
+
bottom: auto;
|
|
2095
|
+
top: calc(100% + 0.25rem);
|
|
2096
|
+
}
|
|
2097
|
+
.cv-p-failed-menu__item--danger {
|
|
2098
|
+
color: var(--cv-p-i-danger);
|
|
2099
|
+
}
|
|
2100
|
+
.cv-p-failed-menu__item:hover,
|
|
2101
|
+
.cv-p-failed-menu__item:focus-visible {
|
|
2102
|
+
background: var(--cv-p-i-highlight);
|
|
2020
2103
|
}
|
|
2021
2104
|
.cv-p-attachment {
|
|
2022
2105
|
display: flex;
|
|
@@ -2483,7 +2566,45 @@
|
|
|
2483
2566
|
.cv-p-copyable__done {
|
|
2484
2567
|
margin-inline-start: 0.35rem;
|
|
2485
2568
|
font-size: 0.85em;
|
|
2486
|
-
color: var(--cv-p-i-
|
|
2569
|
+
color: var(--cv-p-i-text);
|
|
2570
|
+
}
|
|
2571
|
+
@media (forced-colors: active) {
|
|
2572
|
+
.cv-p-bubble::before,
|
|
2573
|
+
.cv-p-bubble::after {
|
|
2574
|
+
content: none;
|
|
2575
|
+
}
|
|
2576
|
+
.cv-p-bubble,
|
|
2577
|
+
.cv-p-bubble--mine {
|
|
2578
|
+
border-radius: var(--cv-p-i-radius);
|
|
2579
|
+
}
|
|
2580
|
+
.cv-p-thread__scroll {
|
|
2581
|
+
background-image: none;
|
|
2582
|
+
}
|
|
2583
|
+
.cv-p-thread__scroll,
|
|
2584
|
+
.cv-p-inbox,
|
|
2585
|
+
.cv-p-composer__input {
|
|
2586
|
+
scrollbar-width: auto;
|
|
2587
|
+
scrollbar-color: auto;
|
|
2588
|
+
}
|
|
2589
|
+
.cv-p-filters,
|
|
2590
|
+
.cv-p-quick {
|
|
2591
|
+
mask-image: none;
|
|
2592
|
+
-webkit-mask-image: none;
|
|
2593
|
+
}
|
|
2594
|
+
.cv-p-thread__latest-badge {
|
|
2595
|
+
border: 1px solid ButtonText;
|
|
2596
|
+
}
|
|
2597
|
+
.cv-p-bubble .cv-status-ticks,
|
|
2598
|
+
.cv-p-copyable__done {
|
|
2599
|
+
color: CanvasText;
|
|
2600
|
+
}
|
|
2601
|
+
.cv-p-failed-menu__list {
|
|
2602
|
+
border-color: CanvasText;
|
|
2603
|
+
}
|
|
2604
|
+
.cv-p-failed-menu__item:focus-visible,
|
|
2605
|
+
.cv-p-failed-menu__item:hover {
|
|
2606
|
+
outline: 2px solid Highlight;
|
|
2607
|
+
}
|
|
2487
2608
|
}
|
|
2488
2609
|
@media (forced-colors: active) {
|
|
2489
2610
|
.cv-p-text__code,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adatechnology/conversations-ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "WhatsApp conversation UI components — parametrizável por endpoint, tema e feature flags",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -49,6 +49,7 @@
|
|
|
49
49
|
}
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
|
+
"@happy-dom/global-registrator": "^18.0.1",
|
|
52
53
|
"@types/bun": "1.3.14",
|
|
53
54
|
"@types/react": "^18 || ^19",
|
|
54
55
|
"@types/react-dom": "^18 || ^19",
|
package/src/Tooltip.tsx
CHANGED
|
@@ -14,6 +14,8 @@
|
|
|
14
14
|
import { useEffect, useRef, useState } from 'react'
|
|
15
15
|
import { createPortal } from 'react-dom'
|
|
16
16
|
|
|
17
|
+
import { createTooltipLayerRegistry } from './tooltipLayerRegistry'
|
|
18
|
+
|
|
17
19
|
/** Atributo que marca um elemento como portador de dica. */
|
|
18
20
|
export const TOOLTIP_ATTRIBUTE = 'data-cv-tooltip'
|
|
19
21
|
|
|
@@ -64,25 +66,16 @@ function tooltipStateFor(target: Element, text: string): TooltipState {
|
|
|
64
66
|
/**
|
|
65
67
|
* Só uma camada desenha por vez. A camada é montada por cada superfície do pacote (inbox, fluxos,
|
|
66
68
|
* documentos) para o host não precisar montá-la à mão; duas superfícies na mesma tela renderizariam
|
|
67
|
-
* dois balões sobrepostos sem essa trava.
|
|
69
|
+
* dois balões sobrepostos sem essa trava. Quando a dona desmonta, a posse passa à próxima montada.
|
|
68
70
|
*/
|
|
69
|
-
|
|
71
|
+
const layerRegistry = createTooltipLayerRegistry()
|
|
70
72
|
|
|
71
73
|
export function TooltipLayer() {
|
|
72
74
|
const identity = useRef({})
|
|
73
75
|
const [isOwner, setIsOwner] = useState(false)
|
|
74
76
|
const [tooltip, setTooltip] = useState<TooltipState | undefined>(undefined)
|
|
75
77
|
|
|
76
|
-
useEffect(() =>
|
|
77
|
-
const owned = identity.current
|
|
78
|
-
if (layerOwner && layerOwner !== owned) return undefined
|
|
79
|
-
layerOwner = owned
|
|
80
|
-
setIsOwner(true)
|
|
81
|
-
|
|
82
|
-
return () => {
|
|
83
|
-
if (layerOwner === owned) layerOwner = undefined
|
|
84
|
-
}
|
|
85
|
-
}, [])
|
|
78
|
+
useEffect(() => layerRegistry.register(identity.current, setIsOwner), [])
|
|
86
79
|
|
|
87
80
|
useEffect(() => {
|
|
88
81
|
if (!isOwner) return undefined
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { afterAll, beforeAll, describe, expect, it } from 'bun:test'
|
|
2
|
+
import { act, useRef } from 'react'
|
|
3
|
+
|
|
4
|
+
import { setupDom, wait, type DomHarness } from './domHarness.test-helper'
|
|
5
|
+
import { TooltipLayer } from './Tooltip'
|
|
6
|
+
import { useSuppressTouchTooltips } from './participant/useSuppressTouchTooltips'
|
|
7
|
+
|
|
8
|
+
let harness: DomHarness
|
|
9
|
+
|
|
10
|
+
beforeAll(async () => {
|
|
11
|
+
harness = await setupDom()
|
|
12
|
+
})
|
|
13
|
+
afterAll(() => harness.teardown())
|
|
14
|
+
|
|
15
|
+
function hover(target: Element): void {
|
|
16
|
+
act(() => void target.dispatchEvent(new Event('pointerover', { bubbles: true })))
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function balloons(): number {
|
|
20
|
+
return document.body.querySelectorAll('[role="tooltip"]').length
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function Target() {
|
|
24
|
+
return <button type="button" data-cv-tooltip="Hint">x</button>
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
describe('TooltipLayer ownership', () => {
|
|
28
|
+
it('draws one balloon with two layers, and keeps drawing after the owner unmounts', async () => {
|
|
29
|
+
const first = harness.mount(<TooltipLayer />)
|
|
30
|
+
harness.mount(<TooltipLayer />)
|
|
31
|
+
const target = harness.mount(<Target />).container.querySelector('button')!
|
|
32
|
+
|
|
33
|
+
hover(target)
|
|
34
|
+
await wait(200)
|
|
35
|
+
expect(balloons()).toBe(1)
|
|
36
|
+
|
|
37
|
+
first.unmount()
|
|
38
|
+
act(() => void target.dispatchEvent(new Event('pointerdown', { bubbles: true })))
|
|
39
|
+
hover(target)
|
|
40
|
+
await wait(200)
|
|
41
|
+
expect(balloons()).toBe(1)
|
|
42
|
+
})
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
function Thread() {
|
|
46
|
+
const rootRef = useRef<HTMLDivElement>(null)
|
|
47
|
+
useSuppressTouchTooltips(rootRef)
|
|
48
|
+
return (
|
|
49
|
+
<div ref={rootRef}>
|
|
50
|
+
<Target />
|
|
51
|
+
</div>
|
|
52
|
+
)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
describe('touch on the participant thread', () => {
|
|
56
|
+
it('does not light the balloon when a tap focuses the button', async () => {
|
|
57
|
+
harness.mount(<TooltipLayer />)
|
|
58
|
+
const button = harness.mount(<Thread />).container.querySelector('button')!
|
|
59
|
+
|
|
60
|
+
act(() => void button.dispatchEvent(new PointerEvent('pointerdown', { bubbles: true, pointerType: 'touch' })))
|
|
61
|
+
act(() => void button.dispatchEvent(new Event('focusin', { bubbles: true })))
|
|
62
|
+
await wait(200)
|
|
63
|
+
|
|
64
|
+
expect(balloons()).toBe(0)
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
it('still shows it for keyboard focus after a touch', async () => {
|
|
68
|
+
harness.mount(<TooltipLayer />)
|
|
69
|
+
const button = harness.mount(<Thread />).container.querySelector('button')!
|
|
70
|
+
|
|
71
|
+
act(() => void button.dispatchEvent(new PointerEvent('pointerdown', { bubbles: true, pointerType: 'touch' })))
|
|
72
|
+
act(() => void button.dispatchEvent(new KeyboardEvent('keydown', { key: 'Tab', bubbles: true })))
|
|
73
|
+
act(() => void button.dispatchEvent(new Event('focusin', { bubbles: true })))
|
|
74
|
+
await wait(200)
|
|
75
|
+
|
|
76
|
+
expect(balloons()).toBe(1)
|
|
77
|
+
})
|
|
78
|
+
})
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { GlobalRegistrator } from '@happy-dom/global-registrator'
|
|
2
|
+
import { act, type ReactElement } from 'react'
|
|
3
|
+
import type { Root } from 'react-dom/client'
|
|
4
|
+
|
|
5
|
+
export type MountedTree = {
|
|
6
|
+
readonly container: HTMLElement
|
|
7
|
+
readonly unmount: () => void
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export type DomHarness = {
|
|
11
|
+
readonly mount: (element: ReactElement) => MountedTree
|
|
12
|
+
readonly teardown: () => void
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Registers happy-dom for one test file only and mounts React trees into it. react-dom/client is
|
|
17
|
+
* imported after the registration because it reads the DOM globals when it loads.
|
|
18
|
+
*/
|
|
19
|
+
export async function setupDom(): Promise<DomHarness> {
|
|
20
|
+
GlobalRegistrator.register()
|
|
21
|
+
;(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true
|
|
22
|
+
const { createRoot } = await import('react-dom/client')
|
|
23
|
+
const roots: Root[] = []
|
|
24
|
+
|
|
25
|
+
function mount(element: ReactElement): MountedTree {
|
|
26
|
+
const container = document.createElement('div')
|
|
27
|
+
document.body.appendChild(container)
|
|
28
|
+
const root = createRoot(container)
|
|
29
|
+
roots.push(root)
|
|
30
|
+
act(() => root.render(element))
|
|
31
|
+
return { container, unmount: () => act(() => root.unmount()) }
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function teardown(): void {
|
|
35
|
+
for (const root of roots) act(() => root.unmount())
|
|
36
|
+
document.body.innerHTML = ''
|
|
37
|
+
GlobalRegistrator.unregister()
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return { mount, teardown }
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function pressKey(target: Element, key: string): void {
|
|
44
|
+
act(() => {
|
|
45
|
+
target.dispatchEvent(new KeyboardEvent('keydown', { key, bubbles: true, cancelable: true }))
|
|
46
|
+
})
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function wait(milliseconds: number): Promise<void> {
|
|
50
|
+
return act(async () => {
|
|
51
|
+
await new Promise((resolve) => setTimeout(resolve, milliseconds))
|
|
52
|
+
})
|
|
53
|
+
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { afterAll, beforeAll, describe, expect, it, mock } from 'bun:test'
|
|
2
|
+
import { renderToStaticMarkup } from 'react-dom/server'
|
|
3
|
+
|
|
4
|
+
import type { ParticipantMessage } from '@adatechnology/conversation-contracts'
|
|
5
|
+
|
|
6
|
+
import type { ConversationThreadApi } from './conversationThreadApi'
|
|
7
|
+
import type { ConversationThreadProps } from './ConversationThread'
|
|
8
|
+
import { buildMessage, findForeignClassTokens, findUtilityClassTokens } from './participantFixtures.test-helper'
|
|
9
|
+
import * as realThreadModule from './useParticipantThread'
|
|
10
|
+
|
|
11
|
+
const COUNTERPART = buildMessage({
|
|
12
|
+
id: 'a',
|
|
13
|
+
direction: 'inbound',
|
|
14
|
+
authorName: 'Ana Souza',
|
|
15
|
+
createdAt: '2026-10-01T10:00:00.000Z',
|
|
16
|
+
})
|
|
17
|
+
const OWN_READ = buildMessage({ id: 'b', direction: 'outbound', status: 'read', createdAt: '2026-10-01T10:01:00.000Z' })
|
|
18
|
+
const OWN_DELIVERED = buildMessage({
|
|
19
|
+
id: 'c',
|
|
20
|
+
direction: 'outbound',
|
|
21
|
+
status: 'delivered',
|
|
22
|
+
createdAt: '2026-10-01T10:02:00.000Z',
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
const realUseParticipantThread = realThreadModule.useParticipantThread
|
|
26
|
+
let injected: readonly ParticipantMessage[] | undefined
|
|
27
|
+
|
|
28
|
+
const API: ConversationThreadApi = {
|
|
29
|
+
fetchMessages: async () => [],
|
|
30
|
+
sendMessage: async () => ({ outcome: 'queued' }),
|
|
31
|
+
resolveAttachmentUrl: async () => 'https://files.example/a',
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
let ConversationThread: typeof import('./ConversationThread').ConversationThread
|
|
35
|
+
|
|
36
|
+
beforeAll(async () => {
|
|
37
|
+
// Server rendering runs no effects, so the messages are injected over the real hook.
|
|
38
|
+
mock.module('./useParticipantThread', () => ({
|
|
39
|
+
...realThreadModule,
|
|
40
|
+
useParticipantThread: (...args: Parameters<typeof realUseParticipantThread>) => {
|
|
41
|
+
const result = realUseParticipantThread(...args)
|
|
42
|
+
return injected ? { ...result, messages: injected, status: 'ready' as const } : result
|
|
43
|
+
},
|
|
44
|
+
}))
|
|
45
|
+
ConversationThread = (await import('./ConversationThread')).ConversationThread
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
afterAll(() => {
|
|
49
|
+
injected = undefined
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
function render(messages: readonly ParticipantMessage[], overrides: Partial<ConversationThreadProps> = {}): string {
|
|
53
|
+
injected = messages
|
|
54
|
+
return renderToStaticMarkup(
|
|
55
|
+
<ConversationThread api={API} subject={{ subjectType: 'case', subjectId: '7' }} title="Case 7" {...overrides} />,
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function bubblesOf(markup: string): string[] {
|
|
60
|
+
return markup.split('<div class="cv-p-bubble').slice(1)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
describe('ConversationThread, operator perspective', () => {
|
|
64
|
+
const MESSAGES = [COUNTERPART, OWN_READ, OWN_DELIVERED]
|
|
65
|
+
|
|
66
|
+
it('owns the outbound messages and not the inbound ones', () => {
|
|
67
|
+
const bubbles = bubblesOf(render(MESSAGES, { perspective: 'operator' }))
|
|
68
|
+
expect(bubbles.map((bubble) => bubble.startsWith(' cv-p-bubble--mine'))).toEqual([false, true, true])
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
it('draws the ticks on the operator messages only', () => {
|
|
72
|
+
const [received, readOwn, deliveredOwn] = bubblesOf(render(MESSAGES, { perspective: 'operator' }))
|
|
73
|
+
expect(received).not.toContain('cv-status-ticks')
|
|
74
|
+
expect(readOwn).toContain('cv-status-ticks--read')
|
|
75
|
+
expect(deliveredOwn).toContain('cv-status-ticks--delivered')
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
it('draws the initials on the counterpart and never on the operator messages', () => {
|
|
79
|
+
const markup = render(MESSAGES, { perspective: 'operator', avatars: 'initials' })
|
|
80
|
+
expect(markup.match(/class="cv-p-avatar" aria-hidden="true">AS</g)?.length).toBe(1)
|
|
81
|
+
expect(markup.match(/class="cv-p-bubble-row"/g)?.length).toBe(1)
|
|
82
|
+
expect(render([OWN_READ, OWN_DELIVERED], { perspective: 'operator', avatars: 'initials' })).not.toContain(
|
|
83
|
+
'cv-p-avatar',
|
|
84
|
+
)
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
it('labels the received bubble with the author name', () => {
|
|
88
|
+
expect(render(MESSAGES, { perspective: 'operator' })).toContain(
|
|
89
|
+
'<span class="cv-p-bubble__author">Ana Souza</span>',
|
|
90
|
+
)
|
|
91
|
+
})
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
describe('ConversationThread, default perspective', () => {
|
|
95
|
+
const MESSAGES = [COUNTERPART, OWN_READ]
|
|
96
|
+
|
|
97
|
+
it('is the participant: inbound is mine, and naming it explicitly changes nothing', () => {
|
|
98
|
+
const implicit = render(MESSAGES)
|
|
99
|
+
expect(bubblesOf(implicit).map((bubble) => bubble.startsWith(' cv-p-bubble--mine'))).toEqual([true, false])
|
|
100
|
+
expect(render(MESSAGES, { perspective: 'participant' })).toBe(implicit)
|
|
101
|
+
})
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
describe('ConversationThread, header and footer', () => {
|
|
105
|
+
it('shows the host actions and the counterpart line in the header, and nothing without them', () => {
|
|
106
|
+
const plain = render([COUNTERPART])
|
|
107
|
+
expect(plain).not.toContain('cv-p-thread__actions')
|
|
108
|
+
expect(plain).not.toContain('cv-p-thread__counterpart')
|
|
109
|
+
const markup = render([COUNTERPART], {
|
|
110
|
+
headerActions: <button type="button">Close</button>,
|
|
111
|
+
counterpartLabel: 'Participant: Ana',
|
|
112
|
+
})
|
|
113
|
+
expect(markup).toContain('<div class="cv-p-thread__actions"><button type="button">Close</button></div>')
|
|
114
|
+
expect(markup).toContain('<p class="cv-p-thread__counterpart">Participant: Ana</p>')
|
|
115
|
+
expect(markup.indexOf('cv-p-thread__actions')).toBeLessThan(markup.indexOf('</header>'))
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
it('removes the composer and shows the notice when closed, and keeps it when open', () => {
|
|
119
|
+
const closed = render([COUNTERPART], { status: 'closed', perspective: 'operator' })
|
|
120
|
+
expect(closed).toContain('This conversation is closed')
|
|
121
|
+
expect(closed).not.toContain('<form')
|
|
122
|
+
expect(render([COUNTERPART], { status: 'open' })).toContain('<form')
|
|
123
|
+
expect(render([COUNTERPART])).toContain('<form')
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
it('draws the title, protocol and channels only from what the host gave', () => {
|
|
127
|
+
expect(render([COUNTERPART])).not.toContain('cv-p-protocol')
|
|
128
|
+
expect(render([COUNTERPART])).not.toContain('cv-p-channels')
|
|
129
|
+
const markup = render([COUNTERPART], { protocol: '261009-K7M2', channels: ['app', 'whatsapp'] })
|
|
130
|
+
expect(markup).toContain('261009-K7M2')
|
|
131
|
+
expect(markup).toContain('cv-p-channel--whatsapp')
|
|
132
|
+
expect(markup).toContain('Case 7')
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
it('draws no back arrow without onBack', () => {
|
|
136
|
+
expect(render([COUNTERPART])).not.toContain('cv-p-thread__back')
|
|
137
|
+
expect(render([COUNTERPART], { onBack: () => undefined })).toContain('cv-p-thread__back')
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
it('offers quick replies as plain buttons that only fill the field', () => {
|
|
141
|
+
const markup = render([COUNTERPART], {
|
|
142
|
+
quickReplies: [{ id: '1', title: 'Thanks', shortcut: 'ty', body: 'Thank you' }],
|
|
143
|
+
})
|
|
144
|
+
const chips = [...markup.matchAll(/<button[^>]*class="cv-p-chip"[^>]*>/g)].map((match) => match[0])
|
|
145
|
+
expect(chips).toHaveLength(1)
|
|
146
|
+
expect(chips[0]).toContain('type="button"')
|
|
147
|
+
expect(render([COUNTERPART])).not.toContain('cv-p-quick')
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
it('applies the theme and the class on a wrapper and uses only the package classes', () => {
|
|
151
|
+
const markup = render([COUNTERPART, OWN_READ], {
|
|
152
|
+
theme: { primaryColor: '#123456' },
|
|
153
|
+
className: 'host-panel',
|
|
154
|
+
onBack: () => undefined,
|
|
155
|
+
})
|
|
156
|
+
expect(markup).toContain('--cv-p-accent:#123456')
|
|
157
|
+
expect(markup).toContain('cv-p-root host-panel')
|
|
158
|
+
expect(findUtilityClassTokens(markup.replace('host-panel', ''))).toEqual([])
|
|
159
|
+
expect(findForeignClassTokens(markup.replace('host-panel', ''))).toEqual([])
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
it('draws the conversation without any provider or list around it', () => {
|
|
163
|
+
expect(render([])).toContain('cv-p-thread__title')
|
|
164
|
+
})
|
|
165
|
+
})
|