@celestia-island/hikari 0.55.131 → 0.55.133
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/package.json +1 -1
- package/src/animation/registerAnimations.ts +6 -0
- package/src/components/HkPersistentToast.scss +143 -0
- package/src/components/HkPersistentToast.test.tsx +263 -0
- package/src/components/HkPersistentToast.tsx +290 -0
- package/src/components/HkPersistentToastGroup.scss +22 -0
- package/src/components/HkPersistentToastGroup.test.tsx +82 -0
- package/src/components/HkPersistentToastGroup.tsx +45 -0
- package/src/components/HkUserPicker.scss +184 -0
- package/src/components/HkUserPicker.test.tsx +208 -0
- package/src/components/HkUserPicker.tsx +267 -0
- package/src/i18n/locales/ar/components.json +12 -1
- package/src/i18n/locales/de/components.json +12 -1
- package/src/i18n/locales/en/components.json +12 -1
- package/src/i18n/locales/es/components.json +12 -1
- package/src/i18n/locales/fr/components.json +12 -1
- package/src/i18n/locales/ja/components.json +12 -1
- package/src/i18n/locales/ko/components.json +12 -1
- package/src/i18n/locales/pt/components.json +12 -1
- package/src/i18n/locales/ru/components.json +12 -1
- package/src/i18n/locales/zh-Hans/components.json +12 -1
- package/src/i18n/locales/zh-Hant/components.json +12 -1
- package/src/index.ts +5 -0
package/package.json
CHANGED
|
@@ -119,6 +119,12 @@ registerCssAnimation("hk-placeholder-marquee-scroll", { infinite: true });
|
|
|
119
119
|
registerCssAnimation("s-admin-header-emergency-pulse", { infinite: true });
|
|
120
120
|
registerCssAnimation("hk-loading-veil-in");
|
|
121
121
|
registerCssAnimation("hk-loading-veil-spin", { infinite: true });
|
|
122
|
+
// components/HkPersistentToast.scss — chrome-bar chip spinner (loops
|
|
123
|
+
// forever while the loading tone shows).
|
|
124
|
+
registerCssAnimation("hk-persistent-toast-spin", { infinite: true });
|
|
125
|
+
// components/HkPersistentToast.scss — hover detail card entrance (plays
|
|
126
|
+
// once; hides instantly on close, so no leave keyframe).
|
|
127
|
+
registerCssAnimation("hk-persistent-toast-card-in");
|
|
122
128
|
// The styles/theme/mixins.scss ambient/glow/entrance keyframes were
|
|
123
129
|
// retired with the legacy Gen-1 sheet vendoring (hikari #476) — their
|
|
124
130
|
// registrar entries went with them.
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
// Chrome-bar persistent status chip — proportions match a title-bar
|
|
2
|
+
// loading pill (HkStatusPill's compactness at toast-family semantics).
|
|
3
|
+
// All colors ride the RGB-channel theme tokens so light/dark and the
|
|
4
|
+
// glass/opacity pipelines apply without component-side branching.
|
|
5
|
+
|
|
6
|
+
.hk-persistent-toast-wrap {
|
|
7
|
+
display: inline-flex;
|
|
8
|
+
align-items: center;
|
|
9
|
+
gap: 2px;
|
|
10
|
+
min-width: 0;
|
|
11
|
+
// The wrap owns the live-region semantics; the chip inside may be a
|
|
12
|
+
// button (chrome no-drag regions only exempt buttons from window
|
|
13
|
+
// dragging — see the wowsp title bar's interactive() gate).
|
|
14
|
+
font-size: var(--text-2xs, 0.6875rem);
|
|
15
|
+
line-height: 1.4;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.hk-persistent-toast {
|
|
19
|
+
display: inline-flex;
|
|
20
|
+
align-items: center;
|
|
21
|
+
gap: var(--space-6, 0.375rem);
|
|
22
|
+
padding: 2px var(--space-8, 0.5rem);
|
|
23
|
+
border-radius: var(--radius-full, 999px);
|
|
24
|
+
border: 1px solid var(--border-faint, rgb(var(--color-border) / 10%));
|
|
25
|
+
background: rgb(var(--color-surface) / 60%);
|
|
26
|
+
color: rgb(var(--color-text) / 72%);
|
|
27
|
+
font: inherit;
|
|
28
|
+
max-width: 220px;
|
|
29
|
+
min-width: 0;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Interactive variant (detail slot provided): a real button — reset the
|
|
33
|
+
// browser chrome, tighten the start padding so the icon optically
|
|
34
|
+
// balances, and give the hover a faint lift like HkTag's interactive
|
|
35
|
+
// cousins.
|
|
36
|
+
button.hk-persistent-toast {
|
|
37
|
+
cursor: pointer;
|
|
38
|
+
appearance: none;
|
|
39
|
+
padding-inline-start: var(--space-6, 0.375rem);
|
|
40
|
+
|
|
41
|
+
&:hover {
|
|
42
|
+
background: rgb(var(--color-surface) / 85%);
|
|
43
|
+
color: rgb(var(--color-text) / 90%);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
&:focus-visible {
|
|
47
|
+
outline: 1px solid rgb(var(--color-primary) / 70%);
|
|
48
|
+
outline-offset: 1px;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.hk-persistent-toast__icon {
|
|
53
|
+
flex-shrink: 0;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.hk-persistent-toast--warning .hk-persistent-toast__icon {
|
|
57
|
+
color: rgb(var(--color-warning));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.hk-persistent-toast--error .hk-persistent-toast__icon {
|
|
61
|
+
color: rgb(var(--color-error));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.hk-persistent-toast--info .hk-persistent-toast__icon {
|
|
65
|
+
color: rgb(var(--color-muted));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.hk-persistent-toast__label {
|
|
69
|
+
overflow: hidden;
|
|
70
|
+
text-overflow: ellipsis;
|
|
71
|
+
white-space: nowrap;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Same grammar as HkToast's spinner, sized for the chip and themed via
|
|
75
|
+
// currentColor so the tone classes stay in one place.
|
|
76
|
+
.hk-persistent-toast__spinner {
|
|
77
|
+
width: 11px;
|
|
78
|
+
height: 11px;
|
|
79
|
+
flex-shrink: 0;
|
|
80
|
+
border: 2px solid rgb(var(--color-text) / 25%);
|
|
81
|
+
border-top-color: rgb(var(--color-text) / 80%);
|
|
82
|
+
border-radius: 50%;
|
|
83
|
+
box-sizing: border-box;
|
|
84
|
+
animation: hk-persistent-toast-spin 0.7s linear infinite;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
@keyframes hk-persistent-toast-spin {
|
|
88
|
+
to {
|
|
89
|
+
transform: rotate(360deg);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.hk-persistent-toast__dismiss {
|
|
94
|
+
appearance: none;
|
|
95
|
+
border: none;
|
|
96
|
+
background: transparent;
|
|
97
|
+
color: rgb(var(--color-text) / 45%);
|
|
98
|
+
cursor: pointer;
|
|
99
|
+
display: inline-flex;
|
|
100
|
+
align-items: center;
|
|
101
|
+
padding: 2px;
|
|
102
|
+
border-radius: var(--radius-full, 999px);
|
|
103
|
+
|
|
104
|
+
&:hover {
|
|
105
|
+
color: rgb(var(--color-text));
|
|
106
|
+
background: rgb(var(--color-text) / 8%);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
&:focus-visible {
|
|
110
|
+
outline: 1px solid rgb(var(--color-primary) / 70%);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// The detail card: a fixed-position rich surface teleported to body,
|
|
115
|
+
// positioned by the component (below the chip, end-aligned, viewport-
|
|
116
|
+
// clamped). Glass surface rides the shared hii-dropdown-content class;
|
|
117
|
+
// only geometry and motion live here.
|
|
118
|
+
.hk-persistent-toast-card {
|
|
119
|
+
position: fixed;
|
|
120
|
+
min-width: 200px;
|
|
121
|
+
max-width: 340px;
|
|
122
|
+
padding: var(--space-8, 0.5rem) var(--space-10, 0.75rem);
|
|
123
|
+
border-radius: var(--radius-lg, 0.5rem);
|
|
124
|
+
font-size: var(--text-2xs, 0.6875rem);
|
|
125
|
+
line-height: 1.5;
|
|
126
|
+
color: rgb(var(--color-text));
|
|
127
|
+
font-family: inherit;
|
|
128
|
+
animation: hk-persistent-toast-card-in 120ms ease-out;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
@keyframes hk-persistent-toast-card-in {
|
|
132
|
+
from {
|
|
133
|
+
opacity: 0;
|
|
134
|
+
transform: translateY(-4px);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
@media (prefers-reduced-motion: reduce) {
|
|
139
|
+
.hk-persistent-toast__spinner,
|
|
140
|
+
.hk-persistent-toast-card {
|
|
141
|
+
animation: none;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { createApp, defineComponent, h, nextTick } from "vue";
|
|
3
|
+
|
|
4
|
+
import HkPersistentToast from "./HkPersistentToast";
|
|
5
|
+
import { POPUP_Z_BANDS, usePopupManager } from "../runtime/usePopupManager";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* HkPersistentToast contract tests:
|
|
9
|
+
* - inert default: a chip without a detail slot renders a plain span,
|
|
10
|
+
* engages no popup-manager entry and exposes no aria-expanded
|
|
11
|
+
* - hover open/close: detail slot + chip mouseenter opens the rich card
|
|
12
|
+
* after the open delay (one tooltip-kind band entry, z stamped
|
|
13
|
+
* inline); chip mouseleave closes it after the grace; moving onto the
|
|
14
|
+
* card cancels the close (hover continuity is the whole point of the
|
|
15
|
+
* rich card — the host list must survive the trip across the gap)
|
|
16
|
+
* - click pinning: a click pins the card against the grace timer until
|
|
17
|
+
* an outside pointerdown / Escape / another click closes it
|
|
18
|
+
* - dismiss: the ✕ sibling button emits `dismiss` (never nested inside
|
|
19
|
+
* the chip button)
|
|
20
|
+
* - the wrap carries the live-region semantics (role=status +
|
|
21
|
+
* aria-live=polite) so label changes announce
|
|
22
|
+
*
|
|
23
|
+
* House style: raw createApp mounts on a shared container list torn
|
|
24
|
+
* down after each case; the popup-manager singleton is REAL state,
|
|
25
|
+
* swept afterEach the way HkTooltip.test.tsx does.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
const mounts: Array<{ app: ReturnType<typeof createApp>; container: HTMLElement }> = [];
|
|
29
|
+
|
|
30
|
+
interface MountOptions {
|
|
31
|
+
props?: Record<string, unknown>;
|
|
32
|
+
detail?: string;
|
|
33
|
+
onDismiss?: () => void;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function mount({ props = {}, detail, onDismiss }: MountOptions = {}) {
|
|
37
|
+
const container = document.createElement("div");
|
|
38
|
+
document.body.appendChild(container);
|
|
39
|
+
const host = defineComponent({
|
|
40
|
+
setup() {
|
|
41
|
+
return () =>
|
|
42
|
+
h(
|
|
43
|
+
HkPersistentToast,
|
|
44
|
+
{ ...props, onDismiss } as never,
|
|
45
|
+
detail ? { detail: () => h("p", { class: "card-probe" }, detail) } : undefined,
|
|
46
|
+
);
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
const app = createApp(host);
|
|
50
|
+
app.mount(container);
|
|
51
|
+
mounts.push({ app, container });
|
|
52
|
+
return { app, container };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const manager = usePopupManager();
|
|
56
|
+
|
|
57
|
+
function tooltipEntries() {
|
|
58
|
+
return [...manager.registry.value.values()].filter((e) => e.kind === "tooltip");
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function chip(c: HTMLElement): HTMLElement {
|
|
62
|
+
return c.querySelector<HTMLElement>(".hk-persistent-toast")!;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function wrap(c: HTMLElement): HTMLElement {
|
|
66
|
+
return c.querySelector<HTMLElement>(".hk-persistent-toast-wrap")!;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function card(): HTMLElement {
|
|
70
|
+
return document.querySelector<HTMLElement>(".hk-persistent-toast-card")!;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** real-timer settle: covers the open/close setTimeout + the rAF position pass */
|
|
74
|
+
async function settle() {
|
|
75
|
+
await new Promise((r) => setTimeout(r, 20));
|
|
76
|
+
await nextTick();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function fire(el: Element, type: string, Ctor: typeof MouseEvent = MouseEvent) {
|
|
80
|
+
el.dispatchEvent(new Ctor(type, { bubbles: true }));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
afterEach(() => {
|
|
84
|
+
vi.useRealTimers();
|
|
85
|
+
for (const { app, container } of mounts.splice(0)) {
|
|
86
|
+
app.unmount();
|
|
87
|
+
container.remove();
|
|
88
|
+
}
|
|
89
|
+
for (const entry of [...manager.registry.value.values()]) {
|
|
90
|
+
manager.unregister(entry.id);
|
|
91
|
+
}
|
|
92
|
+
document.querySelectorAll(".hk-persistent-toast-card").forEach((el) => el.remove());
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
describe("HkPersistentToast inert default", () => {
|
|
96
|
+
it("renders label, tone class and live-region semantics as a plain span", () => {
|
|
97
|
+
const { container } = mount({ props: { tone: "error", label: "Upstream fault" } });
|
|
98
|
+
const c = chip(container);
|
|
99
|
+
expect(c.tagName).toBe("SPAN");
|
|
100
|
+
expect(c.classList.contains("hk-persistent-toast--error")).toBe(true);
|
|
101
|
+
expect(c.textContent).toContain("Upstream fault");
|
|
102
|
+
expect(wrap(container).getAttribute("role")).toBe("status");
|
|
103
|
+
expect(wrap(container).getAttribute("aria-live")).toBe("polite");
|
|
104
|
+
expect(c.getAttribute("aria-expanded")).toBeNull();
|
|
105
|
+
expect(tooltipEntries().length).toBe(0);
|
|
106
|
+
expect(document.querySelector(".hk-persistent-toast-card")).toBeNull();
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it("renders the spinner for the loading tone", () => {
|
|
110
|
+
const { container } = mount({ props: { tone: "loading", label: "Loading" } });
|
|
111
|
+
expect(container.querySelector(".hk-persistent-toast__spinner")).not.toBeNull();
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it("renders the ✕ sibling button only when dismissible", () => {
|
|
115
|
+
const onDismiss = vi.fn();
|
|
116
|
+
const { container } = mount({ props: { label: "x", dismissible: true }, onDismiss });
|
|
117
|
+
const dismiss = container.querySelector<HTMLElement>(".hk-persistent-toast__dismiss")!;
|
|
118
|
+
expect(dismiss).not.toBeNull();
|
|
119
|
+
// The ✕ must never nest inside the chip (a button in a button is
|
|
120
|
+
// invalid markup).
|
|
121
|
+
expect(chip(container).contains(dismiss)).toBe(false);
|
|
122
|
+
dismiss.click();
|
|
123
|
+
expect(onDismiss).toHaveBeenCalledTimes(1);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it("renders no ✕ when not dismissible", () => {
|
|
127
|
+
const { container } = mount({ props: { label: "x" } });
|
|
128
|
+
expect(container.querySelector(".hk-persistent-toast__dismiss")).toBeNull();
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
describe("HkPersistentToast detail card", () => {
|
|
133
|
+
it("opens on hover after the delay and closes after the grace", async () => {
|
|
134
|
+
const { container } = mount({
|
|
135
|
+
props: { label: "fault", openDelay: 0, closeGrace: 0, detailLabel: "Failing hosts" },
|
|
136
|
+
detail: "vortex.wowsgame.cn",
|
|
137
|
+
});
|
|
138
|
+
fire(chip(container), "mouseenter");
|
|
139
|
+
await settle();
|
|
140
|
+
const c = card();
|
|
141
|
+
expect(c).not.toBeNull();
|
|
142
|
+
expect(c.textContent).toContain("vortex.wowsgame.cn");
|
|
143
|
+
expect(chip(container).getAttribute("aria-expanded")).toBe("true");
|
|
144
|
+
const entries = tooltipEntries();
|
|
145
|
+
expect(entries.length).toBe(1);
|
|
146
|
+
expect(entries[0]!.kind).toBe("tooltip");
|
|
147
|
+
expect(c.style.zIndex).toBe(String(POPUP_Z_BANDS.tooltip));
|
|
148
|
+
|
|
149
|
+
fire(chip(container), "mouseleave");
|
|
150
|
+
await settle();
|
|
151
|
+
expect(card()).toBeNull();
|
|
152
|
+
expect(tooltipEntries().length).toBe(0);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it("delays the hover open by openDelay", async () => {
|
|
156
|
+
const { container } = mount({
|
|
157
|
+
props: { label: "fault", openDelay: 80, closeGrace: 0 },
|
|
158
|
+
detail: "hosts",
|
|
159
|
+
});
|
|
160
|
+
fire(chip(container), "mouseenter");
|
|
161
|
+
await nextTick();
|
|
162
|
+
expect(card()).toBeNull();
|
|
163
|
+
await new Promise((r) => setTimeout(r, 120));
|
|
164
|
+
await nextTick();
|
|
165
|
+
expect(card()).not.toBeNull();
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it("keeps the card open while the pointer moves onto it", async () => {
|
|
169
|
+
const { container } = mount({
|
|
170
|
+
props: { label: "fault", openDelay: 0, closeGrace: 0 },
|
|
171
|
+
detail: "hosts",
|
|
172
|
+
});
|
|
173
|
+
fire(chip(container), "mouseenter");
|
|
174
|
+
await settle();
|
|
175
|
+
// Chip → card crossing: the close grace must be cancelled by the
|
|
176
|
+
// card's own mouseenter, or the host list dies mid-read.
|
|
177
|
+
fire(chip(container), "mouseleave");
|
|
178
|
+
fire(card(), "mouseenter");
|
|
179
|
+
await settle();
|
|
180
|
+
expect(card()).not.toBeNull();
|
|
181
|
+
// Leaving the card closes (nothing pinned).
|
|
182
|
+
fire(card(), "mouseleave");
|
|
183
|
+
await settle();
|
|
184
|
+
expect(card()).toBeNull();
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it("pins on click: grace timer and chip leave cannot close it", async () => {
|
|
188
|
+
const { container } = mount({
|
|
189
|
+
props: { label: "fault", openDelay: 0, closeGrace: 0 },
|
|
190
|
+
detail: "hosts",
|
|
191
|
+
});
|
|
192
|
+
fire(chip(container), "mouseenter");
|
|
193
|
+
await settle();
|
|
194
|
+
chip(container).click();
|
|
195
|
+
fire(chip(container), "mouseleave");
|
|
196
|
+
await settle();
|
|
197
|
+
expect(card()).not.toBeNull();
|
|
198
|
+
expect(chip(container).getAttribute("aria-expanded")).toBe("true");
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it("an outside pointerdown closes a pinned card", async () => {
|
|
202
|
+
const { container } = mount({
|
|
203
|
+
props: { label: "fault", openDelay: 0, closeGrace: 0 },
|
|
204
|
+
detail: "hosts",
|
|
205
|
+
});
|
|
206
|
+
chip(container).click();
|
|
207
|
+
await settle();
|
|
208
|
+
expect(card()).not.toBeNull();
|
|
209
|
+
document.body.dispatchEvent(
|
|
210
|
+
new MouseEvent("pointerdown", { bubbles: true }),
|
|
211
|
+
);
|
|
212
|
+
await settle();
|
|
213
|
+
expect(card()).toBeNull();
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
it("a click inside the card does not close a pinned card", async () => {
|
|
217
|
+
const { container } = mount({
|
|
218
|
+
props: { label: "fault", openDelay: 0, closeGrace: 0 },
|
|
219
|
+
detail: "hosts",
|
|
220
|
+
});
|
|
221
|
+
chip(container).click();
|
|
222
|
+
await settle();
|
|
223
|
+
card().dispatchEvent(new MouseEvent("pointerdown", { bubbles: true }));
|
|
224
|
+
await settle();
|
|
225
|
+
expect(card()).not.toBeNull();
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
it("Escape closes a pinned card", async () => {
|
|
229
|
+
const { container } = mount({
|
|
230
|
+
props: { label: "fault", openDelay: 0, closeGrace: 0 },
|
|
231
|
+
detail: "hosts",
|
|
232
|
+
});
|
|
233
|
+
chip(container).click();
|
|
234
|
+
await settle();
|
|
235
|
+
chip(container).dispatchEvent(new KeyboardEvent("keydown", { key: "Escape" }));
|
|
236
|
+
await settle();
|
|
237
|
+
expect(card()).toBeNull();
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it("a second click toggles a pinned card closed", async () => {
|
|
241
|
+
const { container } = mount({
|
|
242
|
+
props: { label: "fault", openDelay: 0, closeGrace: 0 },
|
|
243
|
+
detail: "hosts",
|
|
244
|
+
});
|
|
245
|
+
chip(container).click();
|
|
246
|
+
await settle();
|
|
247
|
+
chip(container).click();
|
|
248
|
+
await settle();
|
|
249
|
+
expect(card()).toBeNull();
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
it("releases the band entry on unmount while open", async () => {
|
|
253
|
+
const { app, container } = mount({
|
|
254
|
+
props: { label: "fault", openDelay: 0, closeGrace: 0 },
|
|
255
|
+
detail: "hosts",
|
|
256
|
+
});
|
|
257
|
+
fire(chip(container), "mouseenter");
|
|
258
|
+
await settle();
|
|
259
|
+
expect(tooltipEntries().length).toBe(1);
|
|
260
|
+
app.unmount();
|
|
261
|
+
expect(tooltipEntries().length).toBe(0);
|
|
262
|
+
});
|
|
263
|
+
});
|