@agent-native/toolkit 0.216.0 → 0.217.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/agent-native.eject.json +5 -0
- package/dist/app/auth/AuthPage.d.ts.map +1 -1
- package/dist/app/auth/AuthPage.js +33 -44
- package/dist/app/auth/AuthPage.js.map +1 -1
- package/dist/app/onboarding/GettingStartedCard.d.ts +35 -0
- package/dist/app/onboarding/GettingStartedCard.d.ts.map +1 -0
- package/dist/app/onboarding/GettingStartedCard.js +38 -0
- package/dist/app/onboarding/GettingStartedCard.js.map +1 -0
- package/dist/app/onboarding/index.d.ts +1 -0
- package/dist/app/onboarding/index.d.ts.map +1 -1
- package/dist/app/onboarding/index.js +1 -0
- package/dist/app/onboarding/index.js.map +1 -1
- package/dist/app/providers/AppProviders.d.ts.map +1 -1
- package/dist/app/providers/AppProviders.js +3 -1
- package/dist/app/providers/AppProviders.js.map +1 -1
- package/dist/app/settings/AccountSettingsCard.d.ts.map +1 -1
- package/dist/app/settings/AccountSettingsCard.js +2 -1
- package/dist/app/settings/AccountSettingsCard.js.map +1 -1
- package/dist/app/settings/index.d.ts +1 -0
- package/dist/app/settings/index.d.ts.map +1 -1
- package/dist/app/settings/index.js +1 -0
- package/dist/app/settings/index.js.map +1 -1
- package/dist/app/settings/shell/SettingsShell.d.ts +40 -1
- package/dist/app/settings/shell/SettingsShell.d.ts.map +1 -1
- package/dist/app/settings/shell/SettingsShell.js +87 -20
- package/dist/app/settings/shell/SettingsShell.js.map +1 -1
- package/dist/app/settings/shell/bridge.d.ts +5 -3
- package/dist/app/settings/shell/bridge.d.ts.map +1 -1
- package/dist/app/settings/shell/bridge.js +8 -5
- package/dist/app/settings/shell/bridge.js.map +1 -1
- package/dist/app/settings/shell/index.d.ts +2 -2
- package/dist/app/settings/shell/index.d.ts.map +1 -1
- package/dist/app/settings/shell/index.js.map +1 -1
- package/dist/app/settings/shell/registry.d.ts +9 -1
- package/dist/app/settings/shell/registry.d.ts.map +1 -1
- package/dist/app/settings/shell/registry.js +16 -0
- package/dist/app/settings/shell/registry.js.map +1 -1
- package/dist/app/settings/shell/routing.d.ts +5 -4
- package/dist/app/settings/shell/routing.d.ts.map +1 -1
- package/dist/app/settings/shell/routing.js +38 -10
- package/dist/app/settings/shell/routing.js.map +1 -1
- package/dist/app/shared/CommandMenu.d.ts +86 -6
- package/dist/app/shared/CommandMenu.d.ts.map +1 -1
- package/dist/app/shared/CommandMenu.js +303 -41
- package/dist/app/shared/CommandMenu.js.map +1 -1
- package/dist/app/shared/ConsentBanner.d.ts +9 -0
- package/dist/app/shared/ConsentBanner.d.ts.map +1 -0
- package/dist/app/shared/ConsentBanner.js +35 -0
- package/dist/app/shared/ConsentBanner.js.map +1 -0
- package/dist/ui/sonner.d.ts +2 -2
- package/dist/ui/sonner.d.ts.map +1 -1
- package/dist/ui/sonner.js +2 -2
- package/dist/ui/sonner.js.map +1 -1
- package/package.json +3 -3
- package/src/app/auth/AuthPage.interaction.spec.tsx +74 -0
- package/src/app/auth/AuthPage.tsx +32 -39
- package/src/app/onboarding/GettingStartedCard.tsx +203 -0
- package/src/app/onboarding/index.ts +1 -0
- package/src/app/providers/AppProviders.tsx +7 -1
- package/src/app/settings/AccountSettingsCard.spec.tsx +31 -0
- package/src/app/settings/AccountSettingsCard.tsx +8 -0
- package/src/app/settings/index.ts +1 -0
- package/src/app/settings/shell/SettingsShell.spec.tsx +472 -1
- package/src/app/settings/shell/SettingsShell.tsx +245 -31
- package/src/app/settings/shell/bridge.spec.ts +12 -0
- package/src/app/settings/shell/bridge.ts +17 -4
- package/src/app/settings/shell/index.ts +6 -1
- package/src/app/settings/shell/registry.ts +33 -1
- package/src/app/settings/shell/routing.spec.ts +92 -1
- package/src/app/settings/shell/routing.ts +52 -9
- package/src/app/shared/CommandMenu.spec.tsx +1662 -1
- package/src/app/shared/CommandMenu.tsx +763 -164
- package/src/app/shared/ConsentBanner.spec.tsx +141 -0
- package/src/app/shared/ConsentBanner.tsx +141 -0
- package/src/ui/sonner.spec.ts +10 -0
- package/src/ui/sonner.tsx +2 -2
|
@@ -29,7 +29,11 @@ vi.mock("@agent-native/core/client/feature-flags/use-feature-flag", () => ({
|
|
|
29
29
|
useFeatureFlags: () => ({}),
|
|
30
30
|
}));
|
|
31
31
|
vi.mock("../../chat/AgentSidebar.js", () => ({
|
|
32
|
-
AgentToggleButton: () =>
|
|
32
|
+
AgentToggleButton: () => (
|
|
33
|
+
<button type="button" data-testid="shared-agent-toggle">
|
|
34
|
+
agent
|
|
35
|
+
</button>
|
|
36
|
+
),
|
|
33
37
|
}));
|
|
34
38
|
|
|
35
39
|
const appState = vi.hoisted(() => ({
|
|
@@ -52,7 +56,10 @@ import { useSettingsPageHeader, useSettingsShell } from "./context.js";
|
|
|
52
56
|
import { CORE_SETTINGS_PAGES } from "./core-pages.js";
|
|
53
57
|
import {
|
|
54
58
|
_resetSettingsPagesForTests,
|
|
59
|
+
getSettingsPages,
|
|
60
|
+
mergeAppSettingsPages,
|
|
55
61
|
registerSettingsPages,
|
|
62
|
+
type AppSettingsPageDefinition,
|
|
56
63
|
type SettingsPageProps,
|
|
57
64
|
} from "./registry.js";
|
|
58
65
|
import {
|
|
@@ -83,6 +90,42 @@ function StubPage({ pageId, sub }: SettingsPageProps) {
|
|
|
83
90
|
);
|
|
84
91
|
}
|
|
85
92
|
|
|
93
|
+
function AppSettingsPage({ pageId, sub }: SettingsPageProps) {
|
|
94
|
+
const { navigate } = useSettingsShell();
|
|
95
|
+
return (
|
|
96
|
+
<div data-testid="app-settings-page">
|
|
97
|
+
page:{pageId}
|
|
98
|
+
{sub ? ` sub:${sub}` : ""}
|
|
99
|
+
<label>
|
|
100
|
+
App control
|
|
101
|
+
<input aria-label="App control" type="checkbox" />
|
|
102
|
+
</label>
|
|
103
|
+
<button
|
|
104
|
+
type="button"
|
|
105
|
+
onClick={() => navigate(pageId, "delivery")}
|
|
106
|
+
data-testid="open-app-subpage"
|
|
107
|
+
>
|
|
108
|
+
Delivery
|
|
109
|
+
</button>
|
|
110
|
+
</div>
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function appPage(
|
|
115
|
+
id: string,
|
|
116
|
+
label: string,
|
|
117
|
+
order = 25,
|
|
118
|
+
component: AppSettingsPageDefinition["component"] = StubPage,
|
|
119
|
+
): AppSettingsPageDefinition {
|
|
120
|
+
return {
|
|
121
|
+
id,
|
|
122
|
+
label,
|
|
123
|
+
order,
|
|
124
|
+
icon: CORE_SETTINGS_PAGES[0]!.icon,
|
|
125
|
+
component,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
86
129
|
async function flush() {
|
|
87
130
|
await act(async () => {
|
|
88
131
|
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
@@ -209,6 +252,395 @@ describe("SettingsShell", () => {
|
|
|
209
252
|
expect(container.textContent).toContain("Back to Clips");
|
|
210
253
|
});
|
|
211
254
|
|
|
255
|
+
it("adds ordered app-owned pages and renders their custom controls", async () => {
|
|
256
|
+
await render({
|
|
257
|
+
notifications: <p>Email notifications</p>,
|
|
258
|
+
appSettingsPages: [
|
|
259
|
+
appPage(
|
|
260
|
+
"custom-preferences",
|
|
261
|
+
"Custom preferences",
|
|
262
|
+
25,
|
|
263
|
+
AppSettingsPage,
|
|
264
|
+
),
|
|
265
|
+
appPage("custom-options", "Custom options", 27),
|
|
266
|
+
],
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
expect(groupLabels().find((group) => group.id === "app")?.pages).toEqual([
|
|
270
|
+
"app",
|
|
271
|
+
"notifications",
|
|
272
|
+
"custom-preferences",
|
|
273
|
+
"custom-options",
|
|
274
|
+
"automations",
|
|
275
|
+
"channels",
|
|
276
|
+
"mcp",
|
|
277
|
+
"labs",
|
|
278
|
+
]);
|
|
279
|
+
clickPage("custom-preferences");
|
|
280
|
+
await waitFor(() =>
|
|
281
|
+
Boolean(container.querySelector('[data-testid="app-settings-page"]')),
|
|
282
|
+
);
|
|
283
|
+
expect(
|
|
284
|
+
container.querySelector('[data-testid="app-settings-page"]')?.textContent,
|
|
285
|
+
).toContain("page:custom-preferences");
|
|
286
|
+
expect(
|
|
287
|
+
container.querySelector('[aria-label="App control"]'),
|
|
288
|
+
).not.toBeNull();
|
|
289
|
+
expect(
|
|
290
|
+
getSettingsPages().some((page) => page.id === "mail-preferences"),
|
|
291
|
+
).toBe(false);
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
it("renders ordered app sections with page, link, and action entries", async () => {
|
|
295
|
+
const onAddWorkspace = vi.fn();
|
|
296
|
+
const customPage = {
|
|
297
|
+
...appPage("workspace-alpha", "Workspace Alpha", 25, AppSettingsPage),
|
|
298
|
+
subpages: [{ id: "settings", label: "Workspace settings" }],
|
|
299
|
+
};
|
|
300
|
+
|
|
301
|
+
await render({
|
|
302
|
+
appSettingsPages: [customPage],
|
|
303
|
+
appNavigationSections: [
|
|
304
|
+
{
|
|
305
|
+
id: "workspaces",
|
|
306
|
+
label: "Workspaces",
|
|
307
|
+
items: [
|
|
308
|
+
{
|
|
309
|
+
id: "workspace-overview",
|
|
310
|
+
type: "page",
|
|
311
|
+
pageId: "workspace-alpha",
|
|
312
|
+
label: "Workspace Alpha",
|
|
313
|
+
},
|
|
314
|
+
{
|
|
315
|
+
id: "workspace-settings",
|
|
316
|
+
type: "page",
|
|
317
|
+
pageId: "workspace-alpha",
|
|
318
|
+
sub: "settings",
|
|
319
|
+
label: "Workspace settings",
|
|
320
|
+
},
|
|
321
|
+
{
|
|
322
|
+
id: "workspace-help",
|
|
323
|
+
type: "link",
|
|
324
|
+
label: "Workspace help",
|
|
325
|
+
href: "/help",
|
|
326
|
+
visibleWhen: () => false,
|
|
327
|
+
},
|
|
328
|
+
{
|
|
329
|
+
id: "add-workspace",
|
|
330
|
+
type: "action",
|
|
331
|
+
label: "Add workspace",
|
|
332
|
+
onSelect: onAddWorkspace,
|
|
333
|
+
},
|
|
334
|
+
],
|
|
335
|
+
},
|
|
336
|
+
{
|
|
337
|
+
id: "administration",
|
|
338
|
+
label: "Administration",
|
|
339
|
+
visibleWhen: (context) => context.isAdmin,
|
|
340
|
+
items: [
|
|
341
|
+
{
|
|
342
|
+
id: "invite-member",
|
|
343
|
+
type: "action",
|
|
344
|
+
label: "Invite member",
|
|
345
|
+
onSelect: () => undefined,
|
|
346
|
+
},
|
|
347
|
+
],
|
|
348
|
+
},
|
|
349
|
+
],
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
expect(
|
|
353
|
+
[...rail().querySelectorAll<HTMLElement>("[data-settings-group]")].map(
|
|
354
|
+
(group) => group.dataset.settingsGroup,
|
|
355
|
+
),
|
|
356
|
+
).toEqual(["workspaces"]);
|
|
357
|
+
expect(
|
|
358
|
+
rail().querySelector('[data-settings-link="workspace-help"]'),
|
|
359
|
+
).toBeNull();
|
|
360
|
+
expect(
|
|
361
|
+
rail()
|
|
362
|
+
.querySelector('[data-settings-nav-item="workspace-settings"]')
|
|
363
|
+
?.getAttribute("href"),
|
|
364
|
+
).toBe("/settings/workspace-alpha/settings");
|
|
365
|
+
|
|
366
|
+
act(() => {
|
|
367
|
+
rail()
|
|
368
|
+
.querySelector<HTMLButtonElement>(
|
|
369
|
+
'[data-settings-action="add-workspace"]',
|
|
370
|
+
)
|
|
371
|
+
?.click();
|
|
372
|
+
});
|
|
373
|
+
expect(onAddWorkspace).toHaveBeenCalled();
|
|
374
|
+
|
|
375
|
+
const subpageLink = rail().querySelector<HTMLElement>(
|
|
376
|
+
'[data-settings-nav-item="workspace-settings"]',
|
|
377
|
+
);
|
|
378
|
+
expect(subpageLink?.getAttribute("href")).toBe(
|
|
379
|
+
"/settings/workspace-alpha/settings",
|
|
380
|
+
);
|
|
381
|
+
act(() => {
|
|
382
|
+
subpageLink?.dispatchEvent(
|
|
383
|
+
new MouseEvent("click", { bubbles: true, cancelable: true }),
|
|
384
|
+
);
|
|
385
|
+
});
|
|
386
|
+
expect(
|
|
387
|
+
rail().querySelector(
|
|
388
|
+
'[data-settings-nav-item="workspace-overview"][aria-current="page"]',
|
|
389
|
+
),
|
|
390
|
+
).toBeNull();
|
|
391
|
+
expect(
|
|
392
|
+
rail().querySelector(
|
|
393
|
+
'[data-settings-nav-item="workspace-settings"][aria-current="page"]',
|
|
394
|
+
),
|
|
395
|
+
).not.toBeNull();
|
|
396
|
+
await waitFor(() =>
|
|
397
|
+
Boolean(container.querySelector('[data-testid="app-settings-page"]')),
|
|
398
|
+
);
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
it("keeps controlled template tabs ahead of a same-id app page", async () => {
|
|
402
|
+
const extraTab = {
|
|
403
|
+
id: "workspace-alpha",
|
|
404
|
+
label: "Template workspace",
|
|
405
|
+
content: "Template workspace content",
|
|
406
|
+
};
|
|
407
|
+
const customPage = appPage(
|
|
408
|
+
"workspace-alpha",
|
|
409
|
+
"Workspace alpha",
|
|
410
|
+
5,
|
|
411
|
+
AppSettingsPage,
|
|
412
|
+
);
|
|
413
|
+
|
|
414
|
+
await render({
|
|
415
|
+
value: "profile",
|
|
416
|
+
initialValue: "profile",
|
|
417
|
+
extraTabs: [extraTab],
|
|
418
|
+
appSettingsPages: [customPage],
|
|
419
|
+
});
|
|
420
|
+
await act(async () => {
|
|
421
|
+
root.render(
|
|
422
|
+
renderWithToolkitI18n(
|
|
423
|
+
<SettingsShell
|
|
424
|
+
appName="Clips"
|
|
425
|
+
value="workspace-alpha"
|
|
426
|
+
initialValue="profile"
|
|
427
|
+
extraTabs={[extraTab]}
|
|
428
|
+
appSettingsPages={[customPage]}
|
|
429
|
+
/>,
|
|
430
|
+
),
|
|
431
|
+
);
|
|
432
|
+
});
|
|
433
|
+
await waitFor(() =>
|
|
434
|
+
window.location.pathname.endsWith("/app-workspace-alpha"),
|
|
435
|
+
);
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
it("mounts an app chat panel and replaces the shared toggle by default", async () => {
|
|
439
|
+
await render({
|
|
440
|
+
agentPanel: <div data-testid="app-agent-panel">Local chat</div>,
|
|
441
|
+
agentToggle: (
|
|
442
|
+
<button type="button" data-testid="app-agent-toggle">
|
|
443
|
+
Open chat
|
|
444
|
+
</button>
|
|
445
|
+
),
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
const shell = container.querySelector<HTMLElement>("[data-settings-shell]");
|
|
449
|
+
const panel = shell?.querySelector('[data-testid="app-agent-panel"]');
|
|
450
|
+
expect(panel).not.toBeNull();
|
|
451
|
+
expect(panel?.parentElement).toBe(shell);
|
|
452
|
+
expect(
|
|
453
|
+
shell?.querySelector('[data-testid="app-agent-toggle"]'),
|
|
454
|
+
).not.toBeNull();
|
|
455
|
+
expect(
|
|
456
|
+
shell?.querySelector('[data-testid="shared-agent-toggle"]'),
|
|
457
|
+
).toBeNull();
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
it("renders an opted-in app page widget slot after the page content", async () => {
|
|
461
|
+
window.history.replaceState(null, "", "/settings/app-help");
|
|
462
|
+
await render({
|
|
463
|
+
appSettingsPages: [
|
|
464
|
+
{
|
|
465
|
+
...appPage("app-help", "App help"),
|
|
466
|
+
widgetSlot: <div data-testid="settings-help-widget">Help</div>,
|
|
467
|
+
},
|
|
468
|
+
],
|
|
469
|
+
initialValue: "app-help",
|
|
470
|
+
value: "app-help",
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
const widget = container.querySelector('[data-settings-widget-slot=""]');
|
|
474
|
+
expect(widget?.textContent).toBe("Help");
|
|
475
|
+
expect(widget?.previousElementSibling?.getAttribute("data-testid")).toBe(
|
|
476
|
+
"page",
|
|
477
|
+
);
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
it("does not render an app page widget slot when it is omitted", async () => {
|
|
481
|
+
window.history.replaceState(null, "", "/settings/app-help");
|
|
482
|
+
await render({
|
|
483
|
+
appSettingsPages: [appPage("app-help", "App help")],
|
|
484
|
+
initialValue: "app-help",
|
|
485
|
+
value: "app-help",
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
expect(
|
|
489
|
+
container.querySelector('[data-settings-widget-slot=""]'),
|
|
490
|
+
).toBeNull();
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
it("keeps template tabs separate when an app page uses the same id", async () => {
|
|
494
|
+
await render({
|
|
495
|
+
appSettingsPages: [
|
|
496
|
+
appPage("custom-controls", "Custom controls", 25, AppSettingsPage),
|
|
497
|
+
],
|
|
498
|
+
extraTabs: [
|
|
499
|
+
{ id: "custom-controls", label: "Template controls", content: "tabs" },
|
|
500
|
+
],
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
expect(groupLabels().find((group) => group.id === "app")?.pages).toEqual(
|
|
504
|
+
expect.arrayContaining(["custom-controls", "app-custom-controls"]),
|
|
505
|
+
);
|
|
506
|
+
clickPage("custom-controls");
|
|
507
|
+
await waitFor(() =>
|
|
508
|
+
Boolean(container.querySelector('[data-testid="app-settings-page"]')),
|
|
509
|
+
);
|
|
510
|
+
expect(
|
|
511
|
+
container.querySelector('[data-testid="app-settings-page"]')?.textContent,
|
|
512
|
+
).toContain("page:custom-controls");
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
it("opens an app page when its id matches an app area", async () => {
|
|
516
|
+
await render({
|
|
517
|
+
appAreas: [{ id: "recordings", label: "Recordings", content: null }],
|
|
518
|
+
appSettingsPages: [
|
|
519
|
+
appPage("recordings", "Recordings settings", 25, AppSettingsPage),
|
|
520
|
+
],
|
|
521
|
+
});
|
|
522
|
+
|
|
523
|
+
clickPage("recordings");
|
|
524
|
+
await waitFor(() =>
|
|
525
|
+
Boolean(container.querySelector('[data-testid="app-settings-page"]')),
|
|
526
|
+
);
|
|
527
|
+
|
|
528
|
+
expect(
|
|
529
|
+
container.querySelector('[data-testid="app-settings-page"]')?.textContent,
|
|
530
|
+
).toContain("page:recordings");
|
|
531
|
+
expect(window.location.pathname).toBe("/settings/recordings");
|
|
532
|
+
});
|
|
533
|
+
|
|
534
|
+
it("rejects app page ids that would replace an existing page", () => {
|
|
535
|
+
expect(() =>
|
|
536
|
+
mergeAppSettingsPages(CORE_SETTINGS_PAGES, [
|
|
537
|
+
appPage("profile", "Replacement profile"),
|
|
538
|
+
]),
|
|
539
|
+
).toThrow(
|
|
540
|
+
'App settings page id "profile" conflicts with an existing page id.',
|
|
541
|
+
);
|
|
542
|
+
expect(() =>
|
|
543
|
+
mergeAppSettingsPages(
|
|
544
|
+
[],
|
|
545
|
+
[
|
|
546
|
+
appPage("custom-preferences", "First"),
|
|
547
|
+
appPage("custom-preferences", "Second"),
|
|
548
|
+
],
|
|
549
|
+
),
|
|
550
|
+
).toThrow('Duplicate app settings page id "custom-preferences".');
|
|
551
|
+
});
|
|
552
|
+
|
|
553
|
+
it("keeps app pages and search entries isolated between shell instances", async () => {
|
|
554
|
+
const firstRouter = createMemoryRouter(
|
|
555
|
+
[
|
|
556
|
+
{
|
|
557
|
+
path: "/settings/*",
|
|
558
|
+
element: (
|
|
559
|
+
<SettingsShell
|
|
560
|
+
appName="First app"
|
|
561
|
+
appSettingsPages={[
|
|
562
|
+
{
|
|
563
|
+
...appPage("first-settings", "First settings", 25),
|
|
564
|
+
keywords: "first-only",
|
|
565
|
+
},
|
|
566
|
+
]}
|
|
567
|
+
/>
|
|
568
|
+
),
|
|
569
|
+
},
|
|
570
|
+
],
|
|
571
|
+
{ initialEntries: ["/settings/first-settings"] },
|
|
572
|
+
);
|
|
573
|
+
const secondRouter = createMemoryRouter(
|
|
574
|
+
[
|
|
575
|
+
{
|
|
576
|
+
path: "/settings/*",
|
|
577
|
+
element: (
|
|
578
|
+
<SettingsShell
|
|
579
|
+
appName="Second app"
|
|
580
|
+
appSettingsPages={[
|
|
581
|
+
{
|
|
582
|
+
...appPage("second-settings", "Second settings", 25),
|
|
583
|
+
keywords: "second-only",
|
|
584
|
+
},
|
|
585
|
+
]}
|
|
586
|
+
/>
|
|
587
|
+
),
|
|
588
|
+
},
|
|
589
|
+
],
|
|
590
|
+
{ initialEntries: ["/settings/second-settings"] },
|
|
591
|
+
);
|
|
592
|
+
|
|
593
|
+
await act(async () => {
|
|
594
|
+
root.render(
|
|
595
|
+
renderWithToolkitI18n(
|
|
596
|
+
<>
|
|
597
|
+
<RouterProvider router={firstRouter} />
|
|
598
|
+
<RouterProvider router={secondRouter} />
|
|
599
|
+
</>,
|
|
600
|
+
),
|
|
601
|
+
);
|
|
602
|
+
});
|
|
603
|
+
await flush();
|
|
604
|
+
|
|
605
|
+
const shells = [
|
|
606
|
+
...container.querySelectorAll<HTMLElement>("[data-settings-shell]"),
|
|
607
|
+
];
|
|
608
|
+
expect(shells).toHaveLength(2);
|
|
609
|
+
expect(shells[0]?.querySelector('[data-testid="page"]')?.textContent).toBe(
|
|
610
|
+
"page:first-settings",
|
|
611
|
+
);
|
|
612
|
+
expect(shells[1]?.querySelector('[data-testid="page"]')?.textContent).toBe(
|
|
613
|
+
"page:second-settings",
|
|
614
|
+
);
|
|
615
|
+
expect(
|
|
616
|
+
shells[0]?.querySelector('[data-settings-page="second-settings"]'),
|
|
617
|
+
).toBeNull();
|
|
618
|
+
expect(
|
|
619
|
+
shells[1]?.querySelector('[data-settings-page="first-settings"]'),
|
|
620
|
+
).toBeNull();
|
|
621
|
+
|
|
622
|
+
const inputs = shells.map(
|
|
623
|
+
(shell) => shell.querySelector<HTMLInputElement>('input[type="search"]')!,
|
|
624
|
+
);
|
|
625
|
+
const setValue = Object.getOwnPropertyDescriptor(
|
|
626
|
+
HTMLInputElement.prototype,
|
|
627
|
+
"value",
|
|
628
|
+
)!.set!;
|
|
629
|
+
act(() => {
|
|
630
|
+
setValue.call(inputs[0], "first-only");
|
|
631
|
+
inputs[0]!.dispatchEvent(new Event("input", { bubbles: true }));
|
|
632
|
+
setValue.call(inputs[1], "first-only");
|
|
633
|
+
inputs[1]!.dispatchEvent(new Event("input", { bubbles: true }));
|
|
634
|
+
});
|
|
635
|
+
expect(shells[0]?.querySelector('[role="option"]')?.textContent).toContain(
|
|
636
|
+
"First settings",
|
|
637
|
+
);
|
|
638
|
+
expect(shells[1]?.querySelector('[role="option"]')).toBeNull();
|
|
639
|
+
expect(getSettingsPages().map((page) => page.id)).not.toEqual(
|
|
640
|
+
expect.arrayContaining(["first-settings", "second-settings"]),
|
|
641
|
+
);
|
|
642
|
+
});
|
|
643
|
+
|
|
212
644
|
it("shows Notifications and What's new when the app passes them", async () => {
|
|
213
645
|
await render({
|
|
214
646
|
notifications: <div>email</div>,
|
|
@@ -530,6 +962,45 @@ describe("SettingsShell", () => {
|
|
|
530
962
|
);
|
|
531
963
|
});
|
|
532
964
|
|
|
965
|
+
it("routes an app page search entry to its nested page", async () => {
|
|
966
|
+
const customPage: AppSettingsPageDefinition = {
|
|
967
|
+
...appPage("custom-controls", "Custom controls", 25, AppSettingsPage),
|
|
968
|
+
subpages: [{ id: "delivery", label: "Delivery settings" }],
|
|
969
|
+
searchEntries: [
|
|
970
|
+
{
|
|
971
|
+
id: "delivery-rules",
|
|
972
|
+
label: "Delivery rules",
|
|
973
|
+
keywords: "exceptions",
|
|
974
|
+
sub: "delivery",
|
|
975
|
+
},
|
|
976
|
+
],
|
|
977
|
+
};
|
|
978
|
+
await render({ appSettingsPages: [customPage] });
|
|
979
|
+
|
|
980
|
+
clickPage("custom-controls");
|
|
981
|
+
await waitFor(() =>
|
|
982
|
+
Boolean(container.querySelector('[data-testid="app-settings-page"]')),
|
|
983
|
+
);
|
|
984
|
+
expect(
|
|
985
|
+
container.querySelector('[aria-label="App control"]'),
|
|
986
|
+
).not.toBeNull();
|
|
987
|
+
|
|
988
|
+
expect(search("delivery exceptions")[0]).toContain("Delivery rules");
|
|
989
|
+
press("Enter");
|
|
990
|
+
await flush();
|
|
991
|
+
expect(window.location.pathname).toBe(
|
|
992
|
+
"/settings/custom-controls/delivery",
|
|
993
|
+
);
|
|
994
|
+
expect(
|
|
995
|
+
container.querySelector('header nav [aria-current="page"]')
|
|
996
|
+
?.textContent,
|
|
997
|
+
).toBe("Delivery settings");
|
|
998
|
+
expect(
|
|
999
|
+
container.querySelector('[data-testid="app-settings-page"]')
|
|
1000
|
+
?.textContent,
|
|
1001
|
+
).toContain("sub:delivery");
|
|
1002
|
+
});
|
|
1003
|
+
|
|
533
1004
|
it("focuses on / and clears on Escape", async () => {
|
|
534
1005
|
await render();
|
|
535
1006
|
act(() => {
|