@runtypelabs/persona 4.19.0 → 4.20.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/dist/approval-ui.d.cts +77 -5
- package/dist/approval-ui.d.ts +77 -5
- package/dist/artifacts-ui.d.cts +77 -5
- package/dist/artifacts-ui.d.ts +77 -5
- package/dist/codegen.cjs +1 -1
- package/dist/codegen.js +1 -1
- package/dist/context-mentions.d.cts +77 -5
- package/dist/context-mentions.d.ts +77 -5
- package/dist/event-stream-view.d.cts +77 -5
- package/dist/event-stream-view.d.ts +77 -5
- package/dist/forms-ui.d.cts +77 -5
- package/dist/forms-ui.d.ts +77 -5
- package/dist/index.cjs +50 -50
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +78 -6
- package/dist/index.d.ts +78 -6
- package/dist/index.global.js +31 -31
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +50 -50
- package/dist/index.js.map +1 -1
- package/dist/launcher.global.js.map +1 -1
- package/dist/session-reconnect.d.cts +77 -5
- package/dist/session-reconnect.d.ts +77 -5
- package/dist/smart-dom-reader.d.cts +77 -5
- package/dist/smart-dom-reader.d.ts +77 -5
- package/dist/theme-editor-preview.cjs +49 -49
- package/dist/theme-editor-preview.d.cts +77 -5
- package/dist/theme-editor-preview.d.ts +77 -5
- package/dist/theme-editor-preview.js +28 -28
- package/dist/theme-editor.d.cts +77 -5
- package/dist/theme-editor.d.ts +77 -5
- package/dist/theme-reference.cjs +1 -1
- package/dist/theme-reference.d.cts +46 -5
- package/dist/theme-reference.d.ts +46 -5
- package/dist/theme-reference.js +1 -1
- package/dist/widget.css +1 -1
- package/package.json +1 -1
- package/src/components/composer-actions.ts +27 -1
- package/src/components/composer-model-picker.ts +4 -14
- package/src/components/composer-overflow-menu.ts +14 -0
- package/src/components/composer-parts.ts +20 -0
- package/src/index-core.ts +2 -0
- package/src/styles/widget-styles.test.ts +83 -1
- package/src/styles/widget.css +50 -14
- package/src/theme-reference.ts +1 -1
- package/src/types/theme.ts +47 -5
- package/src/types.ts +33 -0
- package/src/ui.composer-action-visibility.test.ts +202 -0
- package/src/ui.composer-models.test.ts +37 -0
- package/src/ui.composer-modes.test.ts +82 -0
- package/src/ui.composer-overflow.test.ts +78 -0
- package/src/utils/__snapshots__/tokens-parity.test.ts.snap +1633 -686
- package/src/utils/composer-modes.test.ts +33 -0
- package/src/utils/composer-modes.ts +19 -2
- package/src/utils/tokens-parity.test.ts +20 -0
- package/src/utils/tokens.test.ts +76 -0
- package/src/utils/tokens.ts +4 -4
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
|
|
3
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
4
|
+
|
|
5
|
+
import { createAgentExperience } from "./ui";
|
|
6
|
+
import type { ComposerAction } from "./types";
|
|
7
|
+
|
|
8
|
+
const mounts: HTMLElement[] = [];
|
|
9
|
+
const controllers: ReturnType<typeof createAgentExperience>[] = [];
|
|
10
|
+
|
|
11
|
+
const makeController = (config: Record<string, unknown> = {}) => {
|
|
12
|
+
const mount = document.createElement("div");
|
|
13
|
+
document.body.appendChild(mount);
|
|
14
|
+
mounts.push(mount);
|
|
15
|
+
const controller = createAgentExperience(mount, {
|
|
16
|
+
apiUrl: "https://api.example.com/chat",
|
|
17
|
+
launcher: { enabled: false },
|
|
18
|
+
persistState: false,
|
|
19
|
+
suggestionChips: [],
|
|
20
|
+
...config,
|
|
21
|
+
} as unknown as Parameters<typeof createAgentExperience>[1]);
|
|
22
|
+
controllers.push(controller);
|
|
23
|
+
return { mount, controller };
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/** The store coalesces on a microtask, so state-driven syncs need a turn. */
|
|
27
|
+
const flush = async (times = 4) => {
|
|
28
|
+
for (let i = 0; i < times; i += 1) {
|
|
29
|
+
// eslint-disable-next-line no-await-in-loop
|
|
30
|
+
await Promise.resolve();
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const actionEl = (mount: HTMLElement, id: string) =>
|
|
35
|
+
mount.querySelector<HTMLElement>(`[data-persona-composer-action="${id}"]`);
|
|
36
|
+
|
|
37
|
+
const typeInto = (mount: HTMLElement, value: string): void => {
|
|
38
|
+
const textarea = mount.querySelector<HTMLTextAreaElement>(
|
|
39
|
+
"[data-persona-composer-input]"
|
|
40
|
+
)!;
|
|
41
|
+
textarea.value = value;
|
|
42
|
+
textarea.dispatchEvent(new Event("input", { bubbles: true }));
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const voiceAction = (patch: Partial<ComposerAction> = {}): ComposerAction =>
|
|
46
|
+
({
|
|
47
|
+
id: "voice-mode",
|
|
48
|
+
placement: "end",
|
|
49
|
+
label: "Voice mode",
|
|
50
|
+
iconName: "mic",
|
|
51
|
+
onSelect: () => {},
|
|
52
|
+
...patch,
|
|
53
|
+
}) as ComposerAction;
|
|
54
|
+
|
|
55
|
+
describe("ComposerAction.visibility", () => {
|
|
56
|
+
beforeEach(() => {
|
|
57
|
+
window.scrollTo = vi.fn();
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
afterEach(() => {
|
|
61
|
+
controllers.splice(0).forEach((controller) => {
|
|
62
|
+
try {
|
|
63
|
+
controller.destroy();
|
|
64
|
+
} catch {
|
|
65
|
+
/* already destroyed in the test */
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
mounts.splice(0).forEach((mount) => mount.remove());
|
|
69
|
+
document.body.innerHTML = "";
|
|
70
|
+
window.localStorage.clear();
|
|
71
|
+
vi.restoreAllMocks();
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("mounts the control on every draft state under the default", async () => {
|
|
75
|
+
const { mount } = makeController({
|
|
76
|
+
composer: { actions: [voiceAction()] },
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
expect(actionEl(mount, "voice-mode")).toBeTruthy();
|
|
80
|
+
typeInto(mount, "Hello");
|
|
81
|
+
await flush();
|
|
82
|
+
expect(actionEl(mount, "voice-mode")).toBeTruthy();
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('"when-empty" drops the control out of the DOM once the draft has text', async () => {
|
|
86
|
+
const { mount } = makeController({
|
|
87
|
+
composer: { actions: [voiceAction({ visibility: "when-empty" })] },
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
await flush();
|
|
91
|
+
expect(actionEl(mount, "voice-mode")).toBeTruthy();
|
|
92
|
+
|
|
93
|
+
typeInto(mount, "Hello");
|
|
94
|
+
await flush();
|
|
95
|
+
// Removed, not faded: gone from layout and from tab order.
|
|
96
|
+
expect(actionEl(mount, "voice-mode")).toBeNull();
|
|
97
|
+
|
|
98
|
+
// Whitespace is not a draft.
|
|
99
|
+
typeInto(mount, " ");
|
|
100
|
+
await flush();
|
|
101
|
+
expect(actionEl(mount, "voice-mode")).toBeTruthy();
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('"when-text" is the inverse of "when-empty" on the same state', async () => {
|
|
105
|
+
const { mount } = makeController({
|
|
106
|
+
composer: {
|
|
107
|
+
actions: [
|
|
108
|
+
voiceAction({ visibility: "when-empty" }),
|
|
109
|
+
voiceAction({ id: "improve", visibility: "when-text", label: "Improve" }),
|
|
110
|
+
],
|
|
111
|
+
},
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
await flush();
|
|
115
|
+
expect(actionEl(mount, "voice-mode")).toBeTruthy();
|
|
116
|
+
expect(actionEl(mount, "improve")).toBeNull();
|
|
117
|
+
|
|
118
|
+
typeInto(mount, "Hello");
|
|
119
|
+
await flush();
|
|
120
|
+
expect(actionEl(mount, "voice-mode")).toBeNull();
|
|
121
|
+
expect(actionEl(mount, "improve")).toBeTruthy();
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it('hides a "when-empty" control while streaming, and holds it across chunks', async () => {
|
|
125
|
+
const chunks = [
|
|
126
|
+
'data: {"type":"text_delta","delta":"one"}\n\n',
|
|
127
|
+
'data: {"type":"text_delta","delta":" two"}\n\n',
|
|
128
|
+
];
|
|
129
|
+
let release: () => void = () => {};
|
|
130
|
+
const settled = new Promise<void>((resolve) => {
|
|
131
|
+
release = resolve;
|
|
132
|
+
});
|
|
133
|
+
const encoder = new TextEncoder();
|
|
134
|
+
global.fetch = vi.fn().mockImplementation(
|
|
135
|
+
async () =>
|
|
136
|
+
({
|
|
137
|
+
ok: true,
|
|
138
|
+
headers: new Headers({ "content-type": "text/event-stream" }),
|
|
139
|
+
body: new ReadableStream({
|
|
140
|
+
async start(controller) {
|
|
141
|
+
for (const chunk of chunks) {
|
|
142
|
+
controller.enqueue(encoder.encode(chunk));
|
|
143
|
+
// Each chunk is its own turn, so the action row re-syncs between
|
|
144
|
+
// them: a post-render patch would be visible here.
|
|
145
|
+
await Promise.resolve();
|
|
146
|
+
}
|
|
147
|
+
await settled;
|
|
148
|
+
controller.close();
|
|
149
|
+
},
|
|
150
|
+
}),
|
|
151
|
+
}) as unknown as Response
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
const { mount } = makeController({
|
|
155
|
+
composer: { actions: [voiceAction({ visibility: "when-empty" })] },
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
typeInto(mount, "Hello");
|
|
159
|
+
await flush();
|
|
160
|
+
mount
|
|
161
|
+
.querySelector<HTMLButtonElement>("[data-persona-composer-submit]")!
|
|
162
|
+
.click();
|
|
163
|
+
|
|
164
|
+
// The draft cleared on submit; the stream is what keeps the control hidden.
|
|
165
|
+
for (let i = 0; i < 12; i += 1) {
|
|
166
|
+
// eslint-disable-next-line no-await-in-loop
|
|
167
|
+
await flush();
|
|
168
|
+
expect(actionEl(mount, "voice-mode")).toBeNull();
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
release();
|
|
172
|
+
await flush(20);
|
|
173
|
+
expect(actionEl(mount, "voice-mode")).toBeTruthy();
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it("stays hidden when the visible predicate says so, whatever the draft", async () => {
|
|
177
|
+
const { mount } = makeController({
|
|
178
|
+
composer: {
|
|
179
|
+
actions: [voiceAction({ visibility: "when-empty", visible: false })],
|
|
180
|
+
},
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
await flush();
|
|
184
|
+
expect(actionEl(mount, "voice-mode")).toBeNull();
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it("follows a live update() of the visibility mode", async () => {
|
|
188
|
+
const { mount, controller } = makeController({
|
|
189
|
+
composer: { actions: [voiceAction()] },
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
typeInto(mount, "Hello");
|
|
193
|
+
await flush();
|
|
194
|
+
expect(actionEl(mount, "voice-mode")).toBeTruthy();
|
|
195
|
+
|
|
196
|
+
controller.update({
|
|
197
|
+
composer: { actions: [voiceAction({ visibility: "when-empty" })] },
|
|
198
|
+
});
|
|
199
|
+
await flush();
|
|
200
|
+
expect(actionEl(mount, "voice-mode")).toBeNull();
|
|
201
|
+
});
|
|
202
|
+
});
|
|
@@ -335,6 +335,43 @@ describe("composer model picker", () => {
|
|
|
335
335
|
);
|
|
336
336
|
});
|
|
337
337
|
|
|
338
|
+
it("returns the menu to the stylesheet fallback when an update unsets the token", () => {
|
|
339
|
+
const token = "--persona-components-composer-modelPicker-menuBackground";
|
|
340
|
+
const { mount, controller } = makeController({
|
|
341
|
+
...popoverConfig(),
|
|
342
|
+
theme: {
|
|
343
|
+
components: { composer: { modelPicker: { menuBackground: "#1e1f20" } } },
|
|
344
|
+
},
|
|
345
|
+
});
|
|
346
|
+
expect(mount.style.getPropertyValue(token)).toBe("#1e1f20");
|
|
347
|
+
// The panel is portaled to document.body, so it never inherits the
|
|
348
|
+
// mount's theme vars. Stamped on the trigger here because jsdom's
|
|
349
|
+
// computed style does not inherit custom properties.
|
|
350
|
+
const button = trigger(mount)!;
|
|
351
|
+
button.style.setProperty(token, "#1e1f20");
|
|
352
|
+
|
|
353
|
+
button.click();
|
|
354
|
+
const panel = menu()!;
|
|
355
|
+
expect(panel.style.getPropertyValue(token)).toBe("#1e1f20");
|
|
356
|
+
button.click();
|
|
357
|
+
|
|
358
|
+
// Explicit undefined is the documented reset, so the mount stops
|
|
359
|
+
// carrying the var; the trigger's stand-in follows it.
|
|
360
|
+
controller.update({
|
|
361
|
+
theme: {
|
|
362
|
+
components: { composer: { modelPicker: { menuBackground: undefined } } },
|
|
363
|
+
},
|
|
364
|
+
});
|
|
365
|
+
expect(mount.style.getPropertyValue(token)).toBe("");
|
|
366
|
+
button.style.removeProperty(token);
|
|
367
|
+
|
|
368
|
+
// The menu outlives the update, so the next open has to clear the value
|
|
369
|
+
// the first one stamped; an empty inline value is the fallback state.
|
|
370
|
+
trigger(mount)!.click();
|
|
371
|
+
expect(menu()).toBe(panel);
|
|
372
|
+
expect(panel.style.getPropertyValue(token)).toBe("");
|
|
373
|
+
});
|
|
374
|
+
|
|
338
375
|
it("shows the selected label on the closed control", () => {
|
|
339
376
|
const { mount } = makeController(
|
|
340
377
|
popoverConfig({ selectedModelId: "smart" })
|
|
@@ -326,3 +326,85 @@ describe("composer modes", () => {
|
|
|
326
326
|
expect(panel.querySelector('[role="menuitem"]')?.textContent).toContain("Hidden");
|
|
327
327
|
});
|
|
328
328
|
});
|
|
329
|
+
|
|
330
|
+
describe("modeGroups chipVisibility", () => {
|
|
331
|
+
beforeEach(() => {
|
|
332
|
+
window.scrollTo = vi.fn();
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
afterEach(() => {
|
|
336
|
+
controllers.splice(0).forEach((controller) => {
|
|
337
|
+
try {
|
|
338
|
+
controller.destroy();
|
|
339
|
+
} catch {
|
|
340
|
+
/* already destroyed */
|
|
341
|
+
}
|
|
342
|
+
});
|
|
343
|
+
mounts.splice(0).forEach((mount) => mount.remove());
|
|
344
|
+
document.body.innerHTML = "";
|
|
345
|
+
window.localStorage.clear();
|
|
346
|
+
vi.restoreAllMocks();
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
const hiddenGroups: ComposerModeGroup[] = [
|
|
350
|
+
{ id: "tool", selection: "single", chipVisibility: "hidden" },
|
|
351
|
+
{ id: "style", selection: "multiple" },
|
|
352
|
+
];
|
|
353
|
+
|
|
354
|
+
it("keeps the bar button but renders no chip for the opted-out group", async () => {
|
|
355
|
+
const { mount, controller } = makeController({
|
|
356
|
+
composer: { modes, modeGroups: hiddenGroups },
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
modeButton(mount, "search").click();
|
|
360
|
+
await flush();
|
|
361
|
+
expect(controller.getComposerState().activeModeIds).toEqual(["search"]);
|
|
362
|
+
expect(modeButton(mount, "search").getAttribute("aria-pressed")).toBe("true");
|
|
363
|
+
expect(chips(mount)).toHaveLength(0);
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
it("still chips a group that did not opt out", async () => {
|
|
367
|
+
const { mount } = makeController({
|
|
368
|
+
composer: { modes, modeGroups: hiddenGroups },
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
modeButton(mount, "search").click();
|
|
372
|
+
modeButton(mount, "concise").click();
|
|
373
|
+
await flush();
|
|
374
|
+
expect(
|
|
375
|
+
chips(mount).map((chip) => chip.getAttribute("data-persona-composer-mode"))
|
|
376
|
+
).toEqual(["concise"]);
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
it("leaves no chip row gap when every active mode is suppressed", async () => {
|
|
380
|
+
const { mount } = makeController({
|
|
381
|
+
composer: {
|
|
382
|
+
modes,
|
|
383
|
+
modeGroups: hiddenGroups,
|
|
384
|
+
defaultActiveModeIds: ["search"],
|
|
385
|
+
},
|
|
386
|
+
});
|
|
387
|
+
await flush();
|
|
388
|
+
const row = chipRow(mount);
|
|
389
|
+
expect(row === null || row.style.display === "none").toBe(true);
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
it("follows a live update() that opts the group back in", async () => {
|
|
393
|
+
const { mount, controller } = makeController({
|
|
394
|
+
composer: {
|
|
395
|
+
modes,
|
|
396
|
+
modeGroups: hiddenGroups,
|
|
397
|
+
defaultActiveModeIds: ["search"],
|
|
398
|
+
},
|
|
399
|
+
});
|
|
400
|
+
await flush();
|
|
401
|
+
expect(chips(mount)).toHaveLength(0);
|
|
402
|
+
|
|
403
|
+
controller.update({ composer: { modes, modeGroups } });
|
|
404
|
+
await flush();
|
|
405
|
+
expect(
|
|
406
|
+
chips(mount).map((chip) => chip.getAttribute("data-persona-composer-mode"))
|
|
407
|
+
).toEqual(["search"]);
|
|
408
|
+
expect(chipRow(mount)!.style.display).toBe("flex");
|
|
409
|
+
});
|
|
410
|
+
});
|
|
@@ -103,6 +103,84 @@ describe("composer overflow menu in the live widget", () => {
|
|
|
103
103
|
vi.restoreAllMocks();
|
|
104
104
|
});
|
|
105
105
|
|
|
106
|
+
it("forwards the panel surface tokens onto the portaled menu on open", () => {
|
|
107
|
+
const { mount } = makeController({
|
|
108
|
+
composer: {
|
|
109
|
+
actions: [action("folded", { presentation: "overflow" })],
|
|
110
|
+
actionOverflow: { enabled: true },
|
|
111
|
+
},
|
|
112
|
+
});
|
|
113
|
+
// The panel is portaled to document.body, so it never inherits the mount's
|
|
114
|
+
// theme vars. Stamped on the trigger here because jsdom's computed style
|
|
115
|
+
// does not inherit custom properties.
|
|
116
|
+
const trigger = triggerOf(mount)!;
|
|
117
|
+
trigger.style.setProperty(
|
|
118
|
+
"--persona-components-composer-overflowMenu-background",
|
|
119
|
+
"#353535"
|
|
120
|
+
);
|
|
121
|
+
trigger.style.setProperty(
|
|
122
|
+
"--persona-components-composer-overflowMenu-borderColor",
|
|
123
|
+
"rgba(255, 255, 255, 0.08)"
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
const panel = openPanel(mount)!;
|
|
127
|
+
|
|
128
|
+
expect(
|
|
129
|
+
panel.style.getPropertyValue(
|
|
130
|
+
"--persona-components-composer-overflowMenu-background"
|
|
131
|
+
)
|
|
132
|
+
).toBe("#353535");
|
|
133
|
+
expect(
|
|
134
|
+
panel.style.getPropertyValue(
|
|
135
|
+
"--persona-components-composer-overflowMenu-borderColor"
|
|
136
|
+
)
|
|
137
|
+
).toBe("rgba(255, 255, 255, 0.08)");
|
|
138
|
+
// Unset keys stamp nothing, so the stylesheet's fallback still stands.
|
|
139
|
+
expect(
|
|
140
|
+
panel.style.getPropertyValue(
|
|
141
|
+
"--persona-components-composer-overflowMenu-shadow"
|
|
142
|
+
)
|
|
143
|
+
).toBe("");
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it("returns the panel to the stylesheet fallback when an update unsets the token", () => {
|
|
147
|
+
const token = "--persona-components-composer-overflowMenu-background";
|
|
148
|
+
const { mount, controller } = makeController({
|
|
149
|
+
composer: {
|
|
150
|
+
actions: [action("folded", { presentation: "overflow" })],
|
|
151
|
+
actionOverflow: { enabled: true },
|
|
152
|
+
},
|
|
153
|
+
theme: {
|
|
154
|
+
components: { composer: { overflowMenu: { background: "#353535" } } },
|
|
155
|
+
},
|
|
156
|
+
});
|
|
157
|
+
expect(mount.style.getPropertyValue(token)).toBe("#353535");
|
|
158
|
+
// Stamped on the trigger for the same reason as the test above: jsdom's
|
|
159
|
+
// computed style does not inherit custom properties.
|
|
160
|
+
const trigger = triggerOf(mount)!;
|
|
161
|
+
trigger.style.setProperty(token, "#353535");
|
|
162
|
+
|
|
163
|
+
const panel = openPanel(mount)!;
|
|
164
|
+
expect(panel.style.getPropertyValue(token)).toBe("#353535");
|
|
165
|
+
triggerOf(mount)!.click();
|
|
166
|
+
|
|
167
|
+
// Explicit undefined is the documented reset, so the mount stops carrying
|
|
168
|
+
// the var; the trigger's stand-in follows it.
|
|
169
|
+
controller.update({
|
|
170
|
+
theme: {
|
|
171
|
+
components: { composer: { overflowMenu: { background: undefined } } },
|
|
172
|
+
},
|
|
173
|
+
});
|
|
174
|
+
expect(mount.style.getPropertyValue(token)).toBe("");
|
|
175
|
+
trigger.style.removeProperty(token);
|
|
176
|
+
|
|
177
|
+
// The panel outlives the update, so the next open has to clear the value
|
|
178
|
+
// the first one stamped; an empty inline value is the fallback state.
|
|
179
|
+
const reopened = openPanel(mount)!;
|
|
180
|
+
expect(reopened).toBe(panel);
|
|
181
|
+
expect(reopened.style.getPropertyValue(token)).toBe("");
|
|
182
|
+
});
|
|
183
|
+
|
|
106
184
|
it("sorts the trigger at the 900 anchor by default", () => {
|
|
107
185
|
const { mount } = makeController({
|
|
108
186
|
composer: {
|