@remit/web-client 0.0.304 → 0.0.306
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/components/calendar/CalendarEventPane.stories.tsx +23 -0
- package/src/components/calendar/CalendarEventPane.tsx +13 -1
- package/src/components/calendar/OpenCalendarEvent.tsx +13 -2
- package/src/components/calendar/WriteCalendarEvent.tsx +2 -1
- package/src/components/calendar/pending-suggestions.render.test.ts +5 -0
- package/src/components/onboarding/OnboardingWizard.flow.test.ts +222 -2
- package/src/components/onboarding/OnboardingWizard.tsx +262 -15
- package/src/components/onboarding/onboarding.stories.tsx +90 -0
- package/src/components/settings/AccountServices.render.test.ts +4 -1
- package/src/components/settings/CalendarSettingsPanel.tsx +22 -16
- package/src/components/settings/CalendarSubscribeCard.stories.tsx +89 -0
- package/src/components/settings/CalendarSubscribeCard.tsx +115 -0
- package/src/components/settings/CalendarSubscriptionStatus.stories.tsx +77 -0
- package/src/components/settings/CalendarSubscriptionStatus.tsx +88 -0
- package/src/components/settings/accounts.stories.tsx +5 -0
- package/src/hooks/calendar/instance.ts +7 -0
- package/src/hooks/calendar/useCalendarSubscriptions.ts +103 -0
- package/src/hooks/calendar/useCalendars.ts +4 -0
- package/src/hooks/intelligence-calendar.render.test.ts +5 -0
- package/src/lib/calendar-subscription.ts +11 -0
- package/src/routes/settings/-accounts.test.ts +70 -2
- package/src/routes/settings/accounts.tsx +87 -7
- package/src/routes/settings/calendars.tsx +49 -10
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@remit/web-client",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.306",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Remit web client, published as composable primitives — the app shell, auth shells, and runtime config. A distributor imports what it composes and bundles it.",
|
|
6
6
|
"exports": {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { CalendarDescriptor, CalendarEventData } from "@remit/ui";
|
|
2
2
|
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
3
|
+
import { expect, within } from "storybook/test";
|
|
3
4
|
import { CalendarEventPane } from "./CalendarEventPane";
|
|
4
5
|
|
|
5
6
|
/**
|
|
@@ -175,3 +176,25 @@ export const ChangedElsewhere: Story = {
|
|
|
175
176
|
export const NotWritableYet: Story = {
|
|
176
177
|
args: { onEdit: undefined, onDelete: undefined },
|
|
177
178
|
};
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* An event from a subscribed feed. The controls are gone because the feed
|
|
182
|
+
* owns the event, and the note says so where they would have been.
|
|
183
|
+
*/
|
|
184
|
+
export const FromSubscription: Story = {
|
|
185
|
+
args: {
|
|
186
|
+
onEdit: undefined,
|
|
187
|
+
onDelete: undefined,
|
|
188
|
+
readOnlyNote:
|
|
189
|
+
"Read-only: from calendar.google.com. Change it where the feed comes from.",
|
|
190
|
+
},
|
|
191
|
+
play: async ({ canvasElement }) => {
|
|
192
|
+
const canvas = within(canvasElement);
|
|
193
|
+
await expect(
|
|
194
|
+
canvas.getByText(
|
|
195
|
+
"Read-only: from calendar.google.com. Change it where the feed comes from.",
|
|
196
|
+
),
|
|
197
|
+
).toBeVisible();
|
|
198
|
+
await expect(canvas.queryByRole("button", { name: "Edit" })).toBeNull();
|
|
199
|
+
},
|
|
200
|
+
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { CalendarDescriptor, CalendarEventData } from "@remit/ui";
|
|
2
2
|
import { EventDetail } from "@remit/ui";
|
|
3
|
-
import { AlertCircle, CalendarDays } from "lucide-react";
|
|
3
|
+
import { AlertCircle, CalendarDays, Lock } from "lucide-react";
|
|
4
4
|
import type { ReactNode } from "react";
|
|
5
5
|
import { formatEventWhen } from "@/lib/calendar-format";
|
|
6
6
|
|
|
@@ -76,6 +76,11 @@ export interface CalendarEventPaneProps {
|
|
|
76
76
|
* when the last one did.
|
|
77
77
|
*/
|
|
78
78
|
problem?: string;
|
|
79
|
+
/**
|
|
80
|
+
* Why this event cannot be changed here, stated where Edit and Delete would
|
|
81
|
+
* be. Empty for an event the reader can change.
|
|
82
|
+
*/
|
|
83
|
+
readOnlyNote?: string;
|
|
79
84
|
onEdit?: () => void;
|
|
80
85
|
onDelete?: () => void;
|
|
81
86
|
onClose: () => void;
|
|
@@ -87,6 +92,7 @@ export function CalendarEventPane({
|
|
|
87
92
|
isOccurrence,
|
|
88
93
|
absence = NOT_IN_VIEW,
|
|
89
94
|
problem = "",
|
|
95
|
+
readOnlyNote = "",
|
|
90
96
|
onEdit,
|
|
91
97
|
onDelete,
|
|
92
98
|
onClose,
|
|
@@ -129,6 +135,12 @@ export function CalendarEventPane({
|
|
|
129
135
|
One occurrence of a repeating event
|
|
130
136
|
</p>
|
|
131
137
|
)}
|
|
138
|
+
{readOnlyNote !== "" && (
|
|
139
|
+
<p className="flex shrink-0 items-start gap-2 border-b border-line bg-surface-sunken px-row-inset py-2 text-sm text-fg-muted">
|
|
140
|
+
<Lock className="mt-0.5 size-4 shrink-0" aria-hidden="true" />
|
|
141
|
+
<span className="min-w-0 flex-1 break-words">{readOnlyNote}</span>
|
|
142
|
+
</p>
|
|
143
|
+
)}
|
|
132
144
|
{problem !== "" && (
|
|
133
145
|
<div
|
|
134
146
|
role="alert"
|
|
@@ -38,6 +38,7 @@ import { useCalendarData } from "@/hooks/useCalendarData";
|
|
|
38
38
|
import { formatEventWhen } from "@/lib/calendar-format";
|
|
39
39
|
import { calendarEventReportHref } from "@/lib/calendar-report";
|
|
40
40
|
import type { CalendarSearch } from "@/lib/calendar-route";
|
|
41
|
+
import { subscriptionReadOnlyNote } from "@/lib/calendar-subscription";
|
|
41
42
|
import { useCalendarAddress, useCalendarNavigation } from "@/routing";
|
|
42
43
|
|
|
43
44
|
/**
|
|
@@ -113,7 +114,7 @@ export function OpenCalendarEvent({
|
|
|
113
114
|
}: OpenCalendarEventProps) {
|
|
114
115
|
const { view, date, calendarIds } = useCalendarAddress();
|
|
115
116
|
const { events, isLoading } = useCalendarData({ view, date, calendarIds });
|
|
116
|
-
const { calendars, timeZoneByCalendarId } = useCalendars();
|
|
117
|
+
const { calendars, collections, timeZoneByCalendarId } = useCalendars();
|
|
117
118
|
const { closeEvent } = useCalendarNavigation();
|
|
118
119
|
const { updateEvent, deleteEvent, isWriting } = useCalendarWrites();
|
|
119
120
|
|
|
@@ -178,7 +179,8 @@ export function OpenCalendarEvent({
|
|
|
178
179
|
recurrenceId !== "";
|
|
179
180
|
// Until the resource is read there is no etag, and a write with no etag is a
|
|
180
181
|
// write that can silently overwrite somebody.
|
|
181
|
-
const writable =
|
|
182
|
+
const writable =
|
|
183
|
+
event !== undefined && resource !== undefined && !calendar?.readOnly;
|
|
182
184
|
|
|
183
185
|
const settle = (outcome: CalendarWriteOutcome, done: () => void) => {
|
|
184
186
|
if (outcome.kind === "written") {
|
|
@@ -340,6 +342,15 @@ export function OpenCalendarEvent({
|
|
|
340
342
|
}
|
|
341
343
|
}
|
|
342
344
|
calendar={calendar}
|
|
345
|
+
readOnlyNote={
|
|
346
|
+
calendar?.readOnly
|
|
347
|
+
? subscriptionReadOnlyNote(
|
|
348
|
+
collections.find(
|
|
349
|
+
(collection) => collection.calendarId === calendar.id,
|
|
350
|
+
)?.subscriptionUrl ?? "",
|
|
351
|
+
)
|
|
352
|
+
: ""
|
|
353
|
+
}
|
|
343
354
|
isOccurrence={recurrenceId !== undefined}
|
|
344
355
|
absence={absenceOf(search, seriesOnly && isLoading)}
|
|
345
356
|
problem={problem}
|
|
@@ -30,7 +30,8 @@ export interface WriteCalendarEventProps {
|
|
|
30
30
|
export function WriteCalendarEvent({ onClose }: WriteCalendarEventProps) {
|
|
31
31
|
const { date } = useCalendarAddress();
|
|
32
32
|
const seed = useCalendarComposeSeed();
|
|
33
|
-
const { calendars, timeZoneByCalendarId } = useCalendars();
|
|
33
|
+
const { calendars: held, timeZoneByCalendarId } = useCalendars();
|
|
34
|
+
const calendars = held.filter((calendar) => !calendar.readOnly);
|
|
34
35
|
const { createEvent, isWriting } = useCalendarWrites();
|
|
35
36
|
const [problem, setProblem] = useState("");
|
|
36
37
|
const [draft, setDraft] = useState<EventDraft>(() => {
|
|
@@ -35,6 +35,11 @@ const calendars = [
|
|
|
35
35
|
source: "Default",
|
|
36
36
|
timezone: "Europe/Amsterdam",
|
|
37
37
|
syncSequence: 1,
|
|
38
|
+
subscriptionUrl: "",
|
|
39
|
+
subscriptionEnabled: false,
|
|
40
|
+
subscriptionCheckedAt: 0,
|
|
41
|
+
subscriptionFetchedAt: 0,
|
|
42
|
+
subscriptionError: "",
|
|
38
43
|
createdAt: 0,
|
|
39
44
|
updatedAt: 0,
|
|
40
45
|
},
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
} from "../../hooks/useRedirectEnded";
|
|
16
16
|
import { createDomHarness, type DomHarness } from "../../test-support/dom";
|
|
17
17
|
import { type HttpMock, mockFetch } from "../../test-support/http";
|
|
18
|
-
import { OnboardingWizard } from "./OnboardingWizard";
|
|
18
|
+
import { type MicrosoftReturn, OnboardingWizard } from "./OnboardingWizard";
|
|
19
19
|
|
|
20
20
|
let harness: DomHarness | undefined;
|
|
21
21
|
let http: HttpMock;
|
|
@@ -41,18 +41,34 @@ interface Backend {
|
|
|
41
41
|
lastError?: string;
|
|
42
42
|
mailboxCountTotal?: number;
|
|
43
43
|
mailboxCountSynced?: number;
|
|
44
|
+
accounts?: Record<string, unknown>[];
|
|
45
|
+
accountsAfterSignIn?: Record<string, unknown>[];
|
|
44
46
|
}
|
|
45
47
|
|
|
46
48
|
const completed: string[] = [];
|
|
47
49
|
const cancelled: string[] = [];
|
|
48
50
|
|
|
49
|
-
const start = (
|
|
51
|
+
const start = (
|
|
52
|
+
backend: Backend = {},
|
|
53
|
+
skipWelcome = false,
|
|
54
|
+
microsoftReturn?: MicrosoftReturn,
|
|
55
|
+
): DomHarness => {
|
|
50
56
|
http = mockFetch((call) => {
|
|
51
57
|
if (call.path === "/accounts/test-connection") return backend.test ?? OK;
|
|
52
58
|
if (call.path.endsWith("/oauth/microsoft/start")) {
|
|
53
59
|
return { authorizationUrl: CONSENT_URL };
|
|
54
60
|
}
|
|
55
61
|
if (call.path === "/accounts") return { accountId: "acc-new" };
|
|
62
|
+
if (call.path === "/config" && backend.accounts) {
|
|
63
|
+
return { accounts: backend.accounts, mailboxes: [] };
|
|
64
|
+
}
|
|
65
|
+
if (call.path === "/config" && backend.accountsAfterSignIn) {
|
|
66
|
+
const signedIn = http.to("/oauth/microsoft/start").length > 0;
|
|
67
|
+
return {
|
|
68
|
+
accounts: signedIn ? backend.accountsAfterSignIn : [],
|
|
69
|
+
mailboxes: [],
|
|
70
|
+
};
|
|
71
|
+
}
|
|
56
72
|
if (call.path === "/config") {
|
|
57
73
|
return {
|
|
58
74
|
accounts: [{ accountId: "acc-new", lastError: backend.lastError }],
|
|
@@ -83,6 +99,7 @@ const start = (backend: Backend = {}, skipWelcome = false): DomHarness => {
|
|
|
83
99
|
skipWelcome,
|
|
84
100
|
onComplete: (accountId: string) => completed.push(accountId),
|
|
85
101
|
onCancel: () => cancelled.push("cancelled"),
|
|
102
|
+
microsoftReturn,
|
|
86
103
|
}),
|
|
87
104
|
);
|
|
88
105
|
return harness;
|
|
@@ -487,3 +504,206 @@ describe("OnboardingWizard — a sync that stalls", () => {
|
|
|
487
504
|
assert.match(dom.text(), /IMAP LOGIN failed/);
|
|
488
505
|
});
|
|
489
506
|
});
|
|
507
|
+
|
|
508
|
+
const OUTLOOK_CALENDAR_ONLY = {
|
|
509
|
+
accountId: "acc-outlook",
|
|
510
|
+
email: "alice@outlook.com",
|
|
511
|
+
authType: "oauthMicrosoft",
|
|
512
|
+
syncedServices: ["Calendar"],
|
|
513
|
+
grantedScopes: ["https://graph.microsoft.com/Calendars.Read"],
|
|
514
|
+
};
|
|
515
|
+
|
|
516
|
+
const serviceBoxes = (dom: DomHarness): Record<string, HTMLInputElement> => {
|
|
517
|
+
const [mail, calendar] = dom.queryAll<HTMLInputElement>(
|
|
518
|
+
'input[type="checkbox"]',
|
|
519
|
+
);
|
|
520
|
+
assert.ok(mail && calendar, "expected a Mail and a Calendar checkbox");
|
|
521
|
+
return { mail, calendar };
|
|
522
|
+
};
|
|
523
|
+
|
|
524
|
+
const startRequests = (): Array<Record<string, unknown> | undefined> =>
|
|
525
|
+
http.to("/oauth/microsoft/start").map((call) => call.body);
|
|
526
|
+
|
|
527
|
+
describe("OnboardingWizard — what a Microsoft account syncs (#1183)", () => {
|
|
528
|
+
afterEach(() => {
|
|
529
|
+
Reflect.deleteProperty(document, "visibilityState");
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
it("asks for mail and calendar before the sign-in, both on", async () => {
|
|
533
|
+
const dom = start();
|
|
534
|
+
await walkToMicrosoftStep(dom);
|
|
535
|
+
|
|
536
|
+
assert.match(dom.text(), /What should Remit sync\?/);
|
|
537
|
+
const { mail, calendar } = serviceBoxes(dom);
|
|
538
|
+
assert.equal(mail.checked, true);
|
|
539
|
+
assert.equal(calendar.checked, true);
|
|
540
|
+
});
|
|
541
|
+
|
|
542
|
+
it("sends both services with the sign-in start when both stay on", async () => {
|
|
543
|
+
const dom = start();
|
|
544
|
+
await walkToRedirect(dom);
|
|
545
|
+
|
|
546
|
+
assert.deepEqual(startRequests(), [{ services: ["Mail", "Calendar"] }]);
|
|
547
|
+
});
|
|
548
|
+
|
|
549
|
+
it("sends calendar alone when mail is turned off", async () => {
|
|
550
|
+
const dom = start();
|
|
551
|
+
await walkToMicrosoftStep(dom);
|
|
552
|
+
dom.click(serviceBoxes(dom).mail);
|
|
553
|
+
clickText(dom, "Sign in with Microsoft");
|
|
554
|
+
await settle(dom);
|
|
555
|
+
|
|
556
|
+
assert.deepEqual(startRequests(), [{ services: ["Calendar"] }]);
|
|
557
|
+
});
|
|
558
|
+
|
|
559
|
+
it("sends mail alone when calendar is turned off", async () => {
|
|
560
|
+
const dom = start();
|
|
561
|
+
await walkToMicrosoftStep(dom);
|
|
562
|
+
dom.click(serviceBoxes(dom).calendar);
|
|
563
|
+
clickText(dom, "Sign in with Microsoft");
|
|
564
|
+
await settle(dom);
|
|
565
|
+
|
|
566
|
+
assert.deepEqual(startRequests(), [{ services: ["Mail"] }]);
|
|
567
|
+
});
|
|
568
|
+
|
|
569
|
+
it("refuses an empty choice with the reason and keeps the button live", async () => {
|
|
570
|
+
const dom = start();
|
|
571
|
+
await walkToMicrosoftStep(dom);
|
|
572
|
+
const { mail, calendar } = serviceBoxes(dom);
|
|
573
|
+
dom.click(mail);
|
|
574
|
+
dom.click(calendar);
|
|
575
|
+
clickText(dom, "Sign in with Microsoft");
|
|
576
|
+
await settle(dom);
|
|
577
|
+
|
|
578
|
+
assert.match(dom.text(), /Pick at least one/);
|
|
579
|
+
assert.deepEqual(startRequests(), []);
|
|
580
|
+
const button = dom.byText("button", "Sign in with Microsoft");
|
|
581
|
+
assert.equal(button.getAttribute("disabled"), null);
|
|
582
|
+
});
|
|
583
|
+
|
|
584
|
+
it("shows what Microsoft granted once the new account appears", async () => {
|
|
585
|
+
const dom = start({ accountsAfterSignIn: [OUTLOOK_CALENDAR_ONLY] });
|
|
586
|
+
await walkToMicrosoftStep(dom);
|
|
587
|
+
dom.click(serviceBoxes(dom).mail);
|
|
588
|
+
clickText(dom, "Sign in with Microsoft");
|
|
589
|
+
await settle(dom);
|
|
590
|
+
setVisibility("hidden");
|
|
591
|
+
dom.dispatch(dom.document, new Event("visibilitychange"));
|
|
592
|
+
dom.dispatch(dom.window, new Event("pagehide"));
|
|
593
|
+
setVisibility("visible");
|
|
594
|
+
dom.dispatch(dom.document, new Event("visibilitychange"));
|
|
595
|
+
await dom.waitFor(
|
|
596
|
+
() => dom.text().includes("Connected alice@outlook.com"),
|
|
597
|
+
"the granted step",
|
|
598
|
+
);
|
|
599
|
+
|
|
600
|
+
assert.match(dom.text(), /Access granted/);
|
|
601
|
+
assert.match(dom.text(), /Calendars\.Read/);
|
|
602
|
+
assert.match(dom.text(), /Not synced/);
|
|
603
|
+
clickText(dom, "Continue");
|
|
604
|
+
assert.deepEqual(completed, ["acc-outlook"]);
|
|
605
|
+
});
|
|
606
|
+
|
|
607
|
+
it("opens on the granted result when the callback returns to it", async () => {
|
|
608
|
+
const dom = start({ accounts: [OUTLOOK_CALENDAR_ONLY] }, true, {
|
|
609
|
+
kind: "connected",
|
|
610
|
+
accountId: "acc-outlook",
|
|
611
|
+
});
|
|
612
|
+
await dom.waitFor(
|
|
613
|
+
() => dom.text().includes("Connected alice@outlook.com"),
|
|
614
|
+
"the granted step",
|
|
615
|
+
);
|
|
616
|
+
|
|
617
|
+
assert.match(dom.text(), /Calendar/);
|
|
618
|
+
assert.match(dom.text(), /Access granted/);
|
|
619
|
+
});
|
|
620
|
+
|
|
621
|
+
it("says the account is missing and offers the sign-in again", async () => {
|
|
622
|
+
const dom = start({ accounts: [] }, true, {
|
|
623
|
+
kind: "connected",
|
|
624
|
+
accountId: "acc-gone",
|
|
625
|
+
});
|
|
626
|
+
await dom.waitFor(
|
|
627
|
+
() => dom.text().includes("The account is not here"),
|
|
628
|
+
"the missing-account state",
|
|
629
|
+
);
|
|
630
|
+
|
|
631
|
+
clickText(dom, "Sign in again");
|
|
632
|
+
assert.match(dom.text(), /What should Remit sync\?/);
|
|
633
|
+
});
|
|
634
|
+
|
|
635
|
+
it("opens on the sign-in with the reason when Microsoft withheld a scope", async () => {
|
|
636
|
+
const message =
|
|
637
|
+
"Microsoft did not grant access to every service you picked. Sign in again and accept each permission Microsoft asks for.";
|
|
638
|
+
const dom = start({}, true, { kind: "refused", message, reconnect: false });
|
|
639
|
+
|
|
640
|
+
assert.ok(dom.text().includes(message));
|
|
641
|
+
assert.match(dom.text(), /What should Remit sync\?/);
|
|
642
|
+
const button = dom.byText("button", "Sign in with Microsoft");
|
|
643
|
+
assert.equal(button.getAttribute("disabled"), null);
|
|
644
|
+
});
|
|
645
|
+
|
|
646
|
+
it("drops the refusal once the person goes back and returns", async () => {
|
|
647
|
+
const message =
|
|
648
|
+
"Microsoft did not grant access to Calendar. Sign in again and accept the Calendar permission when Microsoft asks.";
|
|
649
|
+
const dom = start({}, true, { kind: "refused", message, reconnect: false });
|
|
650
|
+
assert.ok(dom.text().includes(message));
|
|
651
|
+
|
|
652
|
+
clickText(dom, "Back");
|
|
653
|
+
clickText(dom, "Outlook / Microsoft 365");
|
|
654
|
+
clickText(dom, "Continue with Microsoft");
|
|
655
|
+
await settle(dom);
|
|
656
|
+
|
|
657
|
+
assert.match(dom.text(), /What should Remit sync\?/);
|
|
658
|
+
assert.equal(dom.text().includes(message), false);
|
|
659
|
+
});
|
|
660
|
+
|
|
661
|
+
it("reopens a refused reconnect on the account, its address and services filled", async () => {
|
|
662
|
+
const message =
|
|
663
|
+
"Microsoft did not grant access to Calendar. Sign in again and accept the Calendar permission when Microsoft asks.";
|
|
664
|
+
const dom = start({ accounts: [] }, true, {
|
|
665
|
+
kind: "refused",
|
|
666
|
+
message,
|
|
667
|
+
email: "alice@outlook.com",
|
|
668
|
+
services: ["Calendar"],
|
|
669
|
+
reconnect: true,
|
|
670
|
+
});
|
|
671
|
+
|
|
672
|
+
assert.match(dom.text(), /Reconnect alice@outlook\.com/);
|
|
673
|
+
assert.ok(dom.text().includes(message));
|
|
674
|
+
const field = dom.query<HTMLInputElement>("#microsoft-email");
|
|
675
|
+
assert.equal(field?.value, "alice@outlook.com");
|
|
676
|
+
const { mail, calendar } = serviceBoxes(dom);
|
|
677
|
+
assert.equal(mail.checked, false);
|
|
678
|
+
assert.equal(calendar.checked, true);
|
|
679
|
+
|
|
680
|
+
clickText(dom, "Sign in with Microsoft");
|
|
681
|
+
await settle(dom);
|
|
682
|
+
assert.deepEqual(startRequests(), [
|
|
683
|
+
{ email: "alice@outlook.com", services: ["Calendar"] },
|
|
684
|
+
]);
|
|
685
|
+
});
|
|
686
|
+
|
|
687
|
+
it("shows the granted result for a reconnected account it already held", async () => {
|
|
688
|
+
const dom = start({ accounts: [OUTLOOK_CALENDAR_ONLY] }, true, {
|
|
689
|
+
kind: "connected",
|
|
690
|
+
accountId: "acc-outlook",
|
|
691
|
+
});
|
|
692
|
+
await dom.waitFor(
|
|
693
|
+
() => dom.text().includes("Connected alice@outlook.com"),
|
|
694
|
+
"the granted step",
|
|
695
|
+
);
|
|
696
|
+
|
|
697
|
+
clickText(dom, "Continue");
|
|
698
|
+
assert.deepEqual(completed, ["acc-outlook"]);
|
|
699
|
+
});
|
|
700
|
+
|
|
701
|
+
it("never offers the choice to a password account", async () => {
|
|
702
|
+
const dom = start();
|
|
703
|
+
await walkToServers(dom);
|
|
704
|
+
assert.doesNotMatch(dom.text(), /What should Remit sync/);
|
|
705
|
+
clickText(dom, "Continue");
|
|
706
|
+
assert.doesNotMatch(dom.text(), /What should Remit sync/);
|
|
707
|
+
assert.equal(dom.query('input[type="checkbox"]'), null);
|
|
708
|
+
});
|
|
709
|
+
});
|