@remit/backend 0.0.142 → 0.0.144
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/dev-server/content-handler.d.ts +38 -0
- package/dist/dev-server/content-handler.js +46 -0
- package/dist/src/api.d.ts +7 -0
- package/dist/src/api.js +144 -0
- package/dist/src/auth.d.ts +30 -0
- package/dist/src/auth.js +73 -0
- package/dist/src/config/msoauth.d.ts +30 -0
- package/dist/src/config/msoauth.js +30 -0
- package/{src/data-backend.ts → dist/src/data-backend.d.ts} +2 -4
- package/dist/src/data-backend.js +12 -0
- package/{src/derive/autoMoved.ts → dist/src/derive/autoMoved.d.ts} +1 -29
- package/dist/src/derive/autoMoved.js +49 -0
- package/dist/src/derive/contentSignature.d.ts +41 -0
- package/{src/derive/contentSignature.ts → dist/src/derive/contentSignature.js} +27 -72
- package/dist/src/derive/contentUrl.d.ts +46 -0
- package/dist/src/derive/contentUrl.js +46 -0
- package/{src/derive/deriveMuted.ts → dist/src/derive/deriveMuted.d.ts} +1 -3
- package/dist/src/derive/deriveMuted.js +7 -0
- package/dist/src/derive/enrichThreadRows.d.ts +118 -0
- package/dist/src/derive/enrichThreadRows.js +151 -0
- package/dist/src/derive/filterThreadCriteria.d.ts +23 -0
- package/dist/src/derive/filterThreadCriteria.js +24 -0
- package/{src/derive/pendingMoveCounts.ts → dist/src/derive/pendingMoveCounts.d.ts} +5 -53
- package/dist/src/derive/pendingMoveCounts.js +53 -0
- package/{src/derive/senderTrust.ts → dist/src/derive/senderTrust.d.ts} +1 -9
- package/dist/src/derive/senderTrust.js +19 -0
- package/dist/src/error.d.ts +21 -0
- package/dist/src/error.js +85 -0
- package/dist/src/handlers/account-guards.d.ts +36 -0
- package/dist/src/handlers/account-guards.js +107 -0
- package/dist/src/handlers/account-oauth.d.ts +14 -0
- package/dist/src/handlers/account-oauth.js +387 -0
- package/dist/src/handlers/account-overrides.d.ts +53 -0
- package/dist/src/handlers/account-overrides.js +233 -0
- package/dist/src/handlers/account-signature.d.ts +28 -0
- package/dist/src/handlers/account-signature.js +85 -0
- package/dist/src/handlers/account.d.ts +6 -0
- package/dist/src/handlers/account.js +350 -0
- package/dist/src/handlers/address.d.ts +35 -0
- package/dist/src/handlers/address.js +141 -0
- package/dist/src/handlers/calendar-event.d.ts +70 -0
- package/dist/src/handlers/calendar-event.js +425 -0
- package/dist/src/handlers/calendar-feed.d.ts +47 -0
- package/dist/src/handlers/calendar-feed.js +192 -0
- package/dist/src/handlers/calendar-suggestion.d.ts +47 -0
- package/dist/src/handlers/calendar-suggestion.js +212 -0
- package/dist/src/handlers/calendar.d.ts +116 -0
- package/dist/src/handlers/calendar.js +330 -0
- package/dist/src/handlers/config.d.ts +43 -0
- package/dist/src/handlers/config.js +176 -0
- package/dist/src/handlers/ensure-account-config.d.ts +9 -0
- package/dist/src/handlers/ensure-account-config.js +27 -0
- package/dist/src/handlers/filter.d.ts +3 -0
- package/dist/src/handlers/filter.js +103 -0
- package/dist/src/handlers/folder-role-appointments.d.ts +68 -0
- package/dist/src/handlers/folder-role-appointments.js +168 -0
- package/dist/src/handlers/folder-role-labels.d.ts +26 -0
- package/dist/src/handlers/folder-role-labels.js +40 -0
- package/dist/src/handlers/folder-role.d.ts +9 -0
- package/dist/src/handlers/folder-role.js +40 -0
- package/dist/src/handlers/index.d.ts +2 -0
- package/dist/src/handlers/index.js +60 -0
- package/dist/src/handlers/label.d.ts +43 -0
- package/dist/src/handlers/label.js +114 -0
- package/dist/src/handlers/mailbox-bindings.d.ts +41 -0
- package/dist/src/handlers/mailbox-bindings.js +61 -0
- package/dist/src/handlers/mailbox.d.ts +23 -0
- package/dist/src/handlers/mailbox.js +219 -0
- package/dist/src/handlers/me.d.ts +2 -0
- package/dist/src/handlers/me.js +138 -0
- package/dist/src/handlers/message.d.ts +84 -0
- package/dist/src/handlers/message.js +607 -0
- package/dist/src/handlers/organize.d.ts +10 -0
- package/dist/src/handlers/organize.js +112 -0
- package/dist/src/handlers/outbox-attachment.d.ts +23 -0
- package/dist/src/handlers/outbox-attachment.js +81 -0
- package/dist/src/handlers/outbox.d.ts +4 -0
- package/dist/src/handlers/outbox.js +144 -0
- package/dist/src/handlers/sync-progress.d.ts +46 -0
- package/dist/src/handlers/sync-progress.js +63 -0
- package/dist/src/handlers/sync.d.ts +28 -0
- package/dist/src/handlers/sync.js +82 -0
- package/dist/src/handlers/system-update.d.ts +2 -0
- package/dist/src/handlers/system-update.js +172 -0
- package/dist/src/handlers/thread.d.ts +148 -0
- package/dist/src/handlers/thread.js +233 -0
- package/dist/src/handlers/unified-threads.d.ts +247 -0
- package/dist/src/handlers/unified-threads.js +409 -0
- package/{src/handlers/vip-suggestions.ts → dist/src/handlers/vip-suggestions.d.ts} +1 -13
- package/dist/src/handlers/vip-suggestions.js +16 -0
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.js +37 -0
- package/{src/json.ts → dist/src/json.d.ts} +1 -4
- package/dist/src/json.js +10 -0
- package/dist/src/jwt-auth.d.ts +18 -0
- package/dist/src/jwt-auth.js +97 -0
- package/dist/src/request-context.d.ts +9 -0
- package/dist/src/request-context.js +22 -0
- package/dist/src/request.d.ts +3 -0
- package/dist/src/request.js +7 -0
- package/dist/src/response.d.ts +27 -0
- package/dist/src/response.js +87 -0
- package/dist/src/service/account-deletion.d.ts +22 -0
- package/dist/src/service/account-deletion.js +39 -0
- package/dist/src/service/compose-sqlite.d.ts +2 -0
- package/dist/src/service/compose-sqlite.js +11 -0
- package/dist/src/service/create-remit-client.d.ts +95 -0
- package/dist/src/service/create-remit-client.js +207 -0
- package/dist/src/service/data-client.d.ts +12 -0
- package/dist/src/service/data-client.js +44 -0
- package/dist/src/service/export-identity.d.ts +3 -0
- package/dist/src/service/export-identity.js +35 -0
- package/dist/src/service/fire-and-forget.d.ts +54 -0
- package/dist/src/service/fire-and-forget.js +38 -0
- package/dist/src/service/organize.d.ts +184 -0
- package/dist/src/service/organize.js +384 -0
- package/dist/src/service/refusal.d.ts +4 -0
- package/dist/src/service/refusal.js +20 -0
- package/dist/src/service/sender-category.d.ts +51 -0
- package/{src/service/sender-category.ts → dist/src/service/sender-category.js} +40 -81
- package/dist/src/service/sqs.d.ts +1 -0
- package/{src/service/sqs.ts → dist/src/service/sqs.js} +0 -1
- package/dist/src/service/trigger-sync.d.ts +51 -0
- package/dist/src/service/trigger-sync.js +39 -0
- package/dist/src/types.d.ts +49 -0
- package/dist/src/types.js +1 -0
- package/package.json +113 -98
- package/.env.test +0 -4
- package/dev-server/content-auth.test.ts +0 -113
- package/dev-server/content-auth.ts +0 -54
- package/dev-server/content-handler.test.ts +0 -105
- package/dev-server/content-handler.ts +0 -79
- package/dev-server/content-path.test.ts +0 -37
- package/dev-server/content-path.ts +0 -27
- package/dev-server/cors.test.ts +0 -48
- package/dev-server/cors.ts +0 -25
- package/dev-server/keep-alive.test.ts +0 -24
- package/dev-server/lambda-helpers.ts +0 -69
- package/dev-server/log-output.test.ts +0 -173
- package/dev-server/oauth-callback-route.test.ts +0 -66
- package/dev-server/relational-health.test.ts +0 -83
- package/dev-server/relational-health.ts +0 -39
- package/dev-server/server.ts +0 -384
- package/dev-server/sync-age.test.ts +0 -91
- package/dev-server/sync-age.ts +0 -55
- package/dev-server/upload-handler.ts +0 -132
- package/dev-server/upload-route.test.ts +0 -196
- package/scripts/backfill-classification.ts +0 -91
- package/scripts/backfill-list-id.ts +0 -83
- package/scripts/config-save.ts +0 -128
- package/src/auth.test.ts +0 -12
- package/src/auth.ts +0 -92
- package/src/config/msoauth.test.ts +0 -48
- package/src/config/msoauth.ts +0 -48
- package/src/data-backend.test.ts +0 -23
- package/src/derive/autoMoved.test.ts +0 -104
- package/src/derive/contentSignature.test.ts +0 -161
- package/src/derive/contentUrl.test.ts +0 -172
- package/src/derive/contentUrl.ts +0 -86
- package/src/derive/deriveMuted.test.ts +0 -34
- package/src/derive/enrichThreadRows.test.ts +0 -360
- package/src/derive/enrichThreadRows.ts +0 -234
- package/src/derive/filterThreadCriteria.test.ts +0 -126
- package/src/derive/filterThreadCriteria.ts +0 -51
- package/src/derive/senderTrust.test.ts +0 -67
- package/src/error.test.ts +0 -201
- package/src/error.ts +0 -123
- package/src/handlers/account-guards.test.ts +0 -67
- package/src/handlers/account-guards.ts +0 -169
- package/src/handlers/account-oauth-callback.test.ts +0 -155
- package/src/handlers/account-oauth-consent.test.ts +0 -336
- package/src/handlers/account-oauth-start.test.ts +0 -191
- package/src/handlers/account-oauth.test.ts +0 -439
- package/src/handlers/account-oauth.ts +0 -635
- package/src/handlers/account-overrides.test.ts +0 -88
- package/src/handlers/account-overrides.ts +0 -323
- package/src/handlers/account-ownership.ts +0 -25
- package/src/handlers/account-signature.ts +0 -117
- package/src/handlers/account-synced-services.test.ts +0 -245
- package/src/handlers/account.ts +0 -552
- package/src/handlers/address.test.ts +0 -381
- package/src/handlers/address.ts +0 -200
- package/src/handlers/calendar-event.test.ts +0 -685
- package/src/handlers/calendar-event.ts +0 -738
- package/src/handlers/calendar-feed-validators.test.ts +0 -228
- package/src/handlers/calendar-feed.test.ts +0 -439
- package/src/handlers/calendar-feed.ts +0 -291
- package/src/handlers/calendar-sqlite-fixture.ts +0 -36
- package/src/handlers/calendar-suggestion.test.ts +0 -1269
- package/src/handlers/calendar-suggestion.ts +0 -425
- package/src/handlers/calendar.test.ts +0 -1080
- package/src/handlers/calendar.ts +0 -517
- package/src/handlers/config.test.ts +0 -245
- package/src/handlers/config.ts +0 -332
- package/src/handlers/ensure-account-config.ts +0 -30
- package/src/handlers/filter.test.ts +0 -644
- package/src/handlers/filter.ts +0 -518
- package/src/handlers/folder-role-appointments.test.ts +0 -438
- package/src/handlers/folder-role-appointments.ts +0 -249
- package/src/handlers/folder-role-labels.ts +0 -63
- package/src/handlers/folder-role.test.ts +0 -209
- package/src/handlers/folder-role.ts +0 -80
- package/src/handlers/index.ts +0 -83
- package/src/handlers/label.test.ts +0 -112
- package/src/handlers/label.ts +0 -210
- package/src/handlers/mailbox-bindings.ts +0 -111
- package/src/handlers/mailbox-rename-refusal.test.ts +0 -247
- package/src/handlers/mailbox-settled-target.test.ts +0 -251
- package/src/handlers/mailbox.test.ts +0 -280
- package/src/handlers/mailbox.ts +0 -581
- package/src/handlers/me.test.ts +0 -81
- package/src/handlers/me.ts +0 -203
- package/src/handlers/message-read-backfill.test.ts +0 -103
- package/src/handlers/message.test.ts +0 -131
- package/src/handlers/message.ts +0 -1131
- package/src/handlers/organize.test.ts +0 -277
- package/src/handlers/organize.ts +0 -197
- package/src/handlers/outbox-attachment.test.ts +0 -765
- package/src/handlers/outbox-attachment.ts +0 -126
- package/src/handlers/outbox-list.test.ts +0 -179
- package/src/handlers/outbox.test.ts +0 -772
- package/src/handlers/outbox.ts +0 -258
- package/src/handlers/search.test.ts +0 -161
- package/src/handlers/search.ts +0 -105
- package/src/handlers/sync-progress.test.ts +0 -205
- package/src/handlers/sync-progress.ts +0 -99
- package/src/handlers/sync.test.ts +0 -168
- package/src/handlers/sync.ts +0 -118
- package/src/handlers/system-update.test.ts +0 -497
- package/src/handlers/system-update.ts +0 -230
- package/src/handlers/thread.test.ts +0 -396
- package/src/handlers/thread.ts +0 -427
- package/src/handlers/unified-threads-mail-sources.sqlite.test.ts +0 -197
- package/src/handlers/unified-threads.test.ts +0 -785
- package/src/handlers/unified-threads.ts +0 -669
- package/src/index.test.ts +0 -83
- package/src/index.ts +0 -228
- package/src/jwt-auth.test.ts +0 -152
- package/src/jwt-auth.ts +0 -132
- package/src/log-redaction.test.ts +0 -144
- package/src/openapi-spec.test.ts +0 -118
- package/src/request-context.test.ts +0 -61
- package/src/request-context.ts +0 -33
- package/src/request-pipeline.test.ts +0 -88
- package/src/request.ts +0 -10
- package/src/response.test.ts +0 -130
- package/src/response.ts +0 -144
- package/src/service/compose-sqlite.ts +0 -98
- package/src/service/config-import.ts +0 -39
- package/src/service/create-remit-client.ts +0 -521
- package/src/service/data-client.test.ts +0 -97
- package/src/service/data-client.ts +0 -58
- package/src/service/export-identity.ts +0 -39
- package/src/service/filter.ts +0 -40
- package/src/service/fire-and-forget.test.ts +0 -126
- package/src/service/fire-and-forget.ts +0 -79
- package/src/service/localhost-env-config.test.ts +0 -36
- package/src/service/organize-queue.ts +0 -98
- package/src/service/organize.test.ts +0 -1451
- package/src/service/organize.ts +0 -1025
- package/src/service/read-path-filter-config.test.ts +0 -243
- package/src/service/semantic-capability.test.ts +0 -78
- package/src/service/semantic-capability.ts +0 -95
- package/src/service/sender-category.test.ts +0 -235
- package/src/service/trigger-sync.test.ts +0 -205
- package/src/service/trigger-sync.ts +0 -100
- package/src/stdout-capture-fixture.ts +0 -40
- package/src/types.ts +0 -271
- package/tsconfig.json +0 -7
|
@@ -1,785 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import { describe, test } from "node:test";
|
|
3
|
-
import type { MessageCategory } from "@remit/api-openapi-types";
|
|
4
|
-
import type {
|
|
5
|
-
AccountItem,
|
|
6
|
-
AccountSettingItem,
|
|
7
|
-
MailboxItem,
|
|
8
|
-
SearchOptions,
|
|
9
|
-
} from "@remit/data-ports";
|
|
10
|
-
import {
|
|
11
|
-
buildInboxMailboxMap,
|
|
12
|
-
buildListAllThreadsOptions,
|
|
13
|
-
buildListStarredThreadsOptions,
|
|
14
|
-
buildSearchAllThreadsOptions,
|
|
15
|
-
dedupeByMessageId,
|
|
16
|
-
executeUnifiedThreadListing,
|
|
17
|
-
type InboxMapClient,
|
|
18
|
-
type UnifiedThreadClient,
|
|
19
|
-
type UnifiedThreadParams,
|
|
20
|
-
} from "./unified-threads.js";
|
|
21
|
-
|
|
22
|
-
// #44: Flagged filtered client-side over the newest 50 unified-inbox rows, so a
|
|
23
|
-
// star outside that window — or in any folder that is not INBOX — never showed.
|
|
24
|
-
// The starred scope is every non-muted mailbox, served by its own access
|
|
25
|
-
// pattern.
|
|
26
|
-
|
|
27
|
-
const CONFIG_ID = "cfg-1";
|
|
28
|
-
|
|
29
|
-
const account = (accountId: string): AccountItem =>
|
|
30
|
-
({ accountId }) as unknown as AccountItem;
|
|
31
|
-
|
|
32
|
-
const mailbox = (
|
|
33
|
-
mailboxId: string,
|
|
34
|
-
accountId: string,
|
|
35
|
-
fullPath: string,
|
|
36
|
-
specialUse?: string[],
|
|
37
|
-
): MailboxItem =>
|
|
38
|
-
({ mailboxId, accountId, fullPath, specialUse }) as unknown as MailboxItem;
|
|
39
|
-
|
|
40
|
-
const mutedSetting = (
|
|
41
|
-
name: "AccountMuted" | "MailboxMuted",
|
|
42
|
-
targetId: string,
|
|
43
|
-
): AccountSettingItem =>
|
|
44
|
-
({
|
|
45
|
-
name: `${name}#${targetId}`,
|
|
46
|
-
value: { kind: "MutedFlag", value: { value: true, setAt: 0 } },
|
|
47
|
-
}) as unknown as AccountSettingItem;
|
|
48
|
-
|
|
49
|
-
const buildClient = (
|
|
50
|
-
accounts: AccountItem[],
|
|
51
|
-
mailboxesByAccount: Record<string, MailboxItem[]>,
|
|
52
|
-
settings: AccountSettingItem[] = [],
|
|
53
|
-
): InboxMapClient =>
|
|
54
|
-
({
|
|
55
|
-
account: { listMailSourcesByAccountConfig: async () => accounts },
|
|
56
|
-
mailbox: {
|
|
57
|
-
listAllByAccount: async (accountId: string) =>
|
|
58
|
-
mailboxesByAccount[accountId] ?? [],
|
|
59
|
-
},
|
|
60
|
-
accountSetting: { listByAccountConfig: async () => settings },
|
|
61
|
-
}) as unknown as InboxMapClient;
|
|
62
|
-
|
|
63
|
-
describe("buildInboxMailboxMap", () => {
|
|
64
|
-
test("separates the INBOX scope from the wider starred scope", async () => {
|
|
65
|
-
const client = buildClient([account("a1")], {
|
|
66
|
-
a1: [
|
|
67
|
-
mailbox("m-inbox", "a1", "INBOX"),
|
|
68
|
-
mailbox("m-archive", "a1", "Archive", ["Archive"]),
|
|
69
|
-
mailbox("m-sub", "a1", "INBOX/Receipts"),
|
|
70
|
-
],
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
const { inboxMailboxIds, starredMailboxIds, mailboxIdToAccountId } =
|
|
74
|
-
await buildInboxMailboxMap(CONFIG_ID, client);
|
|
75
|
-
|
|
76
|
-
assert.deepEqual([...inboxMailboxIds], ["m-inbox"]);
|
|
77
|
-
assert.deepEqual([...starredMailboxIds].sort(), [
|
|
78
|
-
"m-archive",
|
|
79
|
-
"m-inbox",
|
|
80
|
-
"m-sub",
|
|
81
|
-
]);
|
|
82
|
-
// The map must cover every mailbox, or a starred row from Archive could
|
|
83
|
-
// not resolve its accountId.
|
|
84
|
-
assert.equal(mailboxIdToAccountId.get("m-archive"), "a1");
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
// A star on mail in Spam or Trash is not something the starred view should
|
|
88
|
-
// resurface; Gmail's All Mail is a second copy of everything, so including it
|
|
89
|
-
// renders every starred Gmail message twice.
|
|
90
|
-
test("the starred scope excludes All Mail, Junk and Trash", async () => {
|
|
91
|
-
const client = buildClient([account("a1")], {
|
|
92
|
-
a1: [
|
|
93
|
-
mailbox("m-inbox", "a1", "INBOX"),
|
|
94
|
-
mailbox("m-all", "a1", "[Gmail]/All Mail", ["All"]),
|
|
95
|
-
mailbox("m-junk", "a1", "[Gmail]/Spam", ["Junk"]),
|
|
96
|
-
mailbox("m-trash", "a1", "[Gmail]/Trash", ["Trash"]),
|
|
97
|
-
mailbox("m-sent", "a1", "[Gmail]/Sent Mail", ["Sent"]),
|
|
98
|
-
],
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
const { starredMailboxIds, mailboxIdToAccountId } =
|
|
102
|
-
await buildInboxMailboxMap(CONFIG_ID, client);
|
|
103
|
-
|
|
104
|
-
assert.deepEqual([...starredMailboxIds].sort(), ["m-inbox", "m-sent"]);
|
|
105
|
-
// Excluded mailboxes still resolve an accountId — the map is the wider set.
|
|
106
|
-
assert.equal(mailboxIdToAccountId.get("m-trash"), "a1");
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
test("a mailbox carrying several special uses is excluded on any one of them", async () => {
|
|
110
|
-
const client = buildClient([account("a1")], {
|
|
111
|
-
a1: [mailbox("m-archive-all", "a1", "Archive", ["Archive", "All"])],
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
const { starredMailboxIds } = await buildInboxMailboxMap(CONFIG_ID, client);
|
|
115
|
-
assert.equal(starredMailboxIds.size, 0);
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
// #49: the unified list backing the unscoped search was INBOX-only, so a
|
|
119
|
-
// search from the daily brief could not see Archive, Sent, Spam or any
|
|
120
|
-
// custom folder. The scope is defined by what it excludes — anything not on
|
|
121
|
-
// that list is searched, Drafts included.
|
|
122
|
-
test("the search scope reaches every folder but Trash", async () => {
|
|
123
|
-
const client = buildClient([account("a1")], {
|
|
124
|
-
a1: [
|
|
125
|
-
mailbox("m-inbox", "a1", "INBOX"),
|
|
126
|
-
mailbox("m-sub", "a1", "INBOX/Receipts"),
|
|
127
|
-
mailbox("m-archive", "a1", "Archive", ["Archive"]),
|
|
128
|
-
mailbox("m-sent", "a1", "[Gmail]/Sent Mail", ["Sent"]),
|
|
129
|
-
mailbox("m-junk", "a1", "[Gmail]/Spam", ["Junk"]),
|
|
130
|
-
mailbox("m-drafts", "a1", "[Gmail]/Drafts", ["Drafts"]),
|
|
131
|
-
mailbox("m-custom", "a1", "Projects/Remit"),
|
|
132
|
-
mailbox("m-trash", "a1", "[Gmail]/Trash", ["Trash"]),
|
|
133
|
-
],
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
const { searchMailboxIds } = await buildInboxMailboxMap(CONFIG_ID, client);
|
|
137
|
-
|
|
138
|
-
assert.deepEqual(
|
|
139
|
-
[...searchMailboxIds].sort(),
|
|
140
|
-
[
|
|
141
|
-
"m-archive",
|
|
142
|
-
"m-custom",
|
|
143
|
-
"m-drafts",
|
|
144
|
-
"m-inbox",
|
|
145
|
-
"m-junk",
|
|
146
|
-
"m-sent",
|
|
147
|
-
"m-sub",
|
|
148
|
-
],
|
|
149
|
-
"Spam and Drafts are in scope; Trash is not",
|
|
150
|
-
);
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
// Trash is matched by path too, for servers that do not advertise the
|
|
154
|
-
// special-use attribute.
|
|
155
|
-
test("Trash is excluded without a special-use attribute", async () => {
|
|
156
|
-
const client = buildClient([account("a1")], {
|
|
157
|
-
a1: [
|
|
158
|
-
mailbox("m-inbox", "a1", "INBOX"),
|
|
159
|
-
mailbox("m-trash", "a1", "[Gmail]/Trash"),
|
|
160
|
-
// Whole path, never a prefix — a user's own folder is real mail.
|
|
161
|
-
mailbox("m-trash-talk", "a1", "Trash talk"),
|
|
162
|
-
],
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
const { searchMailboxIds } = await buildInboxMailboxMap(CONFIG_ID, client);
|
|
166
|
-
assert.deepEqual([...searchMailboxIds].sort(), ["m-inbox", "m-trash-talk"]);
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
// A row is keyed by (threadId, messageId), both mailbox-independent, so
|
|
170
|
-
// Gmail's copies collapse into ONE row owned by whichever mailbox synced it
|
|
171
|
-
// first. Barring the virtual folders from the scope would therefore delete
|
|
172
|
-
// the only row such a message has — mail that exists and cannot be found.
|
|
173
|
-
test("the virtual copies stay IN the search scope, so mail they own is findable", async () => {
|
|
174
|
-
const client = buildClient([account("a1")], {
|
|
175
|
-
a1: [
|
|
176
|
-
mailbox("m-inbox", "a1", "INBOX"),
|
|
177
|
-
mailbox("m-all", "a1", "[Gmail]/All Mail", ["All"]),
|
|
178
|
-
mailbox("m-starred", "a1", "[Gmail]/Starred", ["Flagged"]),
|
|
179
|
-
mailbox("m-important", "a1", "[Gmail]/Important", ["Important"]),
|
|
180
|
-
],
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
const { searchMailboxIds, virtualCopyMailboxIds } =
|
|
184
|
-
await buildInboxMailboxMap(CONFIG_ID, client);
|
|
185
|
-
|
|
186
|
-
assert.deepEqual(
|
|
187
|
-
[...searchMailboxIds].sort(),
|
|
188
|
-
["m-all", "m-important", "m-inbox", "m-starred"],
|
|
189
|
-
"nothing is barred — duplicates are dropped after the read instead",
|
|
190
|
-
);
|
|
191
|
-
assert.deepEqual(
|
|
192
|
-
[...virtualCopyMailboxIds].sort(),
|
|
193
|
-
["m-all", "m-important", "m-starred"],
|
|
194
|
-
"they are still identified, to decide which duplicate to drop",
|
|
195
|
-
);
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
test("a user folder named like a virtual copy is not treated as one", async () => {
|
|
199
|
-
const client = buildClient([account("a1")], {
|
|
200
|
-
a1: [
|
|
201
|
-
mailbox("m-starred-ideas", "a1", "Starred ideas"),
|
|
202
|
-
mailbox("m-important-clients", "a1", "Important clients"),
|
|
203
|
-
],
|
|
204
|
-
});
|
|
205
|
-
|
|
206
|
-
const { virtualCopyMailboxIds } = await buildInboxMailboxMap(
|
|
207
|
-
CONFIG_ID,
|
|
208
|
-
client,
|
|
209
|
-
);
|
|
210
|
-
assert.equal(virtualCopyMailboxIds.size, 0);
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
// The starred scope drops Junk; the search scope keeps it. Two sets, two
|
|
214
|
-
// judgements — a star in Spam is noise, a search for mail that landed in
|
|
215
|
-
// Spam is the whole point.
|
|
216
|
-
test("the search scope and the starred scope differ on Junk", async () => {
|
|
217
|
-
const client = buildClient([account("a1")], {
|
|
218
|
-
a1: [
|
|
219
|
-
mailbox("m-inbox", "a1", "INBOX"),
|
|
220
|
-
mailbox("m-junk", "a1", "[Gmail]/Spam", ["Junk"]),
|
|
221
|
-
],
|
|
222
|
-
});
|
|
223
|
-
|
|
224
|
-
const { searchMailboxIds, starredMailboxIds } = await buildInboxMailboxMap(
|
|
225
|
-
CONFIG_ID,
|
|
226
|
-
client,
|
|
227
|
-
);
|
|
228
|
-
|
|
229
|
-
assert.equal(searchMailboxIds.has("m-junk"), true);
|
|
230
|
-
assert.equal(starredMailboxIds.has("m-junk"), false);
|
|
231
|
-
});
|
|
232
|
-
|
|
233
|
-
test("the search scope spans every non-muted account", async () => {
|
|
234
|
-
const client = buildClient([account("a1"), account("a2")], {
|
|
235
|
-
a1: [mailbox("m1-archive", "a1", "Archive", ["Archive"])],
|
|
236
|
-
a2: [mailbox("m2-sent", "a2", "Sent", ["Sent"])],
|
|
237
|
-
});
|
|
238
|
-
|
|
239
|
-
const { searchMailboxIds } = await buildInboxMailboxMap(CONFIG_ID, client);
|
|
240
|
-
|
|
241
|
-
assert.deepEqual([...searchMailboxIds].sort(), ["m1-archive", "m2-sent"]);
|
|
242
|
-
});
|
|
243
|
-
|
|
244
|
-
test("muted mailboxes are excluded from the search scope too", async () => {
|
|
245
|
-
const client = buildClient(
|
|
246
|
-
[account("a1")],
|
|
247
|
-
{
|
|
248
|
-
a1: [
|
|
249
|
-
mailbox("m-inbox", "a1", "INBOX"),
|
|
250
|
-
mailbox("m-muted", "a1", "Archive"),
|
|
251
|
-
],
|
|
252
|
-
},
|
|
253
|
-
[mutedSetting("MailboxMuted", "m-muted")],
|
|
254
|
-
);
|
|
255
|
-
|
|
256
|
-
const { searchMailboxIds } = await buildInboxMailboxMap(CONFIG_ID, client);
|
|
257
|
-
assert.deepEqual([...searchMailboxIds], ["m-inbox"]);
|
|
258
|
-
});
|
|
259
|
-
|
|
260
|
-
test("muted mailboxes are excluded from both scopes", async () => {
|
|
261
|
-
const client = buildClient(
|
|
262
|
-
[account("a1")],
|
|
263
|
-
{
|
|
264
|
-
a1: [
|
|
265
|
-
mailbox("m-inbox", "a1", "INBOX"),
|
|
266
|
-
mailbox("m-muted", "a1", "Archive"),
|
|
267
|
-
],
|
|
268
|
-
},
|
|
269
|
-
[mutedSetting("MailboxMuted", "m-muted")],
|
|
270
|
-
);
|
|
271
|
-
|
|
272
|
-
const { inboxMailboxIds, starredMailboxIds } = await buildInboxMailboxMap(
|
|
273
|
-
CONFIG_ID,
|
|
274
|
-
client,
|
|
275
|
-
);
|
|
276
|
-
|
|
277
|
-
assert.deepEqual([...inboxMailboxIds], ["m-inbox"]);
|
|
278
|
-
assert.deepEqual([...starredMailboxIds], ["m-inbox"]);
|
|
279
|
-
});
|
|
280
|
-
|
|
281
|
-
test("a muted account contributes no mailboxes", async () => {
|
|
282
|
-
const client = buildClient(
|
|
283
|
-
[account("a1")],
|
|
284
|
-
{ a1: [mailbox("m-inbox", "a1", "INBOX")] },
|
|
285
|
-
[mutedSetting("AccountMuted", "a1")],
|
|
286
|
-
);
|
|
287
|
-
|
|
288
|
-
const { starredMailboxIds } = await buildInboxMailboxMap(CONFIG_ID, client);
|
|
289
|
-
assert.equal(starredMailboxIds.size, 0);
|
|
290
|
-
});
|
|
291
|
-
});
|
|
292
|
-
|
|
293
|
-
describe("buildListStarredThreadsOptions", () => {
|
|
294
|
-
test("scopes to every supplied mailbox, not just the inbox", () => {
|
|
295
|
-
const options = buildListStarredThreadsOptions(
|
|
296
|
-
{},
|
|
297
|
-
new Set(["m-inbox", "m-archive"]),
|
|
298
|
-
);
|
|
299
|
-
|
|
300
|
-
assert.deepEqual([...options.mailboxIds].sort(), ["m-archive", "m-inbox"]);
|
|
301
|
-
assert.equal(options.order, "desc");
|
|
302
|
-
assert.equal(options.excludeDeleted, true);
|
|
303
|
-
});
|
|
304
|
-
|
|
305
|
-
test("carries the caller's paging through", () => {
|
|
306
|
-
const options = buildListStarredThreadsOptions(
|
|
307
|
-
{ continuationToken: "tok", order: "asc", limit: 10 },
|
|
308
|
-
new Set(["m-inbox"]),
|
|
309
|
-
);
|
|
310
|
-
|
|
311
|
-
assert.equal(options.continuationToken, "tok");
|
|
312
|
-
assert.equal(options.order, "asc");
|
|
313
|
-
assert.equal(options.limit, 10);
|
|
314
|
-
});
|
|
315
|
-
|
|
316
|
-
test("defaults the page size to the unified default", () => {
|
|
317
|
-
const starred = buildListStarredThreadsOptions({}, new Set(["m-inbox"]));
|
|
318
|
-
const unified = buildListAllThreadsOptions({}, new Set(["m-inbox"]));
|
|
319
|
-
assert.equal(starred.limit, unified.limit);
|
|
320
|
-
});
|
|
321
|
-
});
|
|
322
|
-
|
|
323
|
-
// A backend that keys a row by its mailbox returns one row per Gmail copy, so
|
|
324
|
-
// a starred, Important inbox message arrives four times and crowds genuine
|
|
325
|
-
// matches off the page. Dropping the extras here — rather than barring the
|
|
326
|
-
// virtual folders from the scope — keeps the message reachable when the only
|
|
327
|
-
// row it has sits in one of them.
|
|
328
|
-
describe("dedupeByMessageId", () => {
|
|
329
|
-
const row = (messageId: string, mailboxId: string) => ({
|
|
330
|
-
messageId,
|
|
331
|
-
mailboxId,
|
|
332
|
-
});
|
|
333
|
-
const virtual = new Set(["m-all", "m-starred", "m-important"]);
|
|
334
|
-
const isVirtual = (r: { mailboxId: string }) => virtual.has(r.mailboxId);
|
|
335
|
-
|
|
336
|
-
test("a starred, Important Gmail inbox message collapses to one row", () => {
|
|
337
|
-
const deduped = dedupeByMessageId(
|
|
338
|
-
[
|
|
339
|
-
row("msg-1", "m-inbox"),
|
|
340
|
-
row("msg-1", "m-all"),
|
|
341
|
-
row("msg-1", "m-starred"),
|
|
342
|
-
row("msg-1", "m-important"),
|
|
343
|
-
],
|
|
344
|
-
isVirtual,
|
|
345
|
-
);
|
|
346
|
-
|
|
347
|
-
assert.equal(deduped.length, 1);
|
|
348
|
-
assert.equal(deduped[0].mailboxId, "m-inbox");
|
|
349
|
-
});
|
|
350
|
-
|
|
351
|
-
test("the real folder wins even when a virtual copy came first", () => {
|
|
352
|
-
const deduped = dedupeByMessageId(
|
|
353
|
-
[row("msg-1", "m-all"), row("msg-1", "m-archive")],
|
|
354
|
-
isVirtual,
|
|
355
|
-
);
|
|
356
|
-
|
|
357
|
-
assert.deepEqual(
|
|
358
|
-
deduped.map((r) => r.mailboxId),
|
|
359
|
-
["m-archive"],
|
|
360
|
-
);
|
|
361
|
-
});
|
|
362
|
-
|
|
363
|
-
// The case the scope exclusion would have broken: nothing but a virtual
|
|
364
|
-
// copy holds this message, so that row must survive.
|
|
365
|
-
test("a message that exists only in a virtual folder is kept", () => {
|
|
366
|
-
const deduped = dedupeByMessageId([row("msg-1", "m-starred")], isVirtual);
|
|
367
|
-
|
|
368
|
-
assert.deepEqual(
|
|
369
|
-
deduped.map((r) => r.mailboxId),
|
|
370
|
-
["m-starred"],
|
|
371
|
-
);
|
|
372
|
-
});
|
|
373
|
-
|
|
374
|
-
test("the same mail in two real folders keeps the first, newest by order", () => {
|
|
375
|
-
const deduped = dedupeByMessageId(
|
|
376
|
-
[row("msg-1", "m-archive"), row("msg-1", "m-custom")],
|
|
377
|
-
isVirtual,
|
|
378
|
-
);
|
|
379
|
-
|
|
380
|
-
assert.deepEqual(
|
|
381
|
-
deduped.map((r) => r.mailboxId),
|
|
382
|
-
["m-archive"],
|
|
383
|
-
);
|
|
384
|
-
});
|
|
385
|
-
|
|
386
|
-
test("distinct messages are all kept, in order", () => {
|
|
387
|
-
const deduped = dedupeByMessageId(
|
|
388
|
-
[
|
|
389
|
-
row("msg-1", "m-inbox"),
|
|
390
|
-
row("msg-2", "m-archive"),
|
|
391
|
-
row("msg-3", "m-junk"),
|
|
392
|
-
],
|
|
393
|
-
isVirtual,
|
|
394
|
-
);
|
|
395
|
-
|
|
396
|
-
assert.deepEqual(
|
|
397
|
-
deduped.map((r) => r.messageId),
|
|
398
|
-
["msg-1", "msg-2", "msg-3"],
|
|
399
|
-
);
|
|
400
|
-
});
|
|
401
|
-
|
|
402
|
-
test("an empty page stays empty", () => {
|
|
403
|
-
assert.deepEqual(dedupeByMessageId([], isVirtual), []);
|
|
404
|
-
});
|
|
405
|
-
});
|
|
406
|
-
|
|
407
|
-
describe("buildSearchAllThreadsOptions", () => {
|
|
408
|
-
test("carries the whole search scope, not just the inbox", () => {
|
|
409
|
-
const options = buildSearchAllThreadsOptions(
|
|
410
|
-
{},
|
|
411
|
-
new Set(["m-inbox", "m-archive", "m-junk"]),
|
|
412
|
-
);
|
|
413
|
-
|
|
414
|
-
assert.deepEqual([...options.mailboxIds].sort(), [
|
|
415
|
-
"m-archive",
|
|
416
|
-
"m-inbox",
|
|
417
|
-
"m-junk",
|
|
418
|
-
]);
|
|
419
|
-
assert.equal(options.order, "desc");
|
|
420
|
-
assert.equal(options.excludeDeleted, true);
|
|
421
|
-
});
|
|
422
|
-
|
|
423
|
-
test("carries the caller's paging through", () => {
|
|
424
|
-
const options = buildSearchAllThreadsOptions(
|
|
425
|
-
{ continuationToken: "tok", order: "asc", limit: 10 },
|
|
426
|
-
new Set(["m-inbox"]),
|
|
427
|
-
);
|
|
428
|
-
|
|
429
|
-
assert.equal(options.continuationToken, "tok");
|
|
430
|
-
assert.equal(options.order, "asc");
|
|
431
|
-
assert.equal(options.limit, 10);
|
|
432
|
-
});
|
|
433
|
-
|
|
434
|
-
test("defaults the page size to the unified default", () => {
|
|
435
|
-
const search = buildSearchAllThreadsOptions({}, new Set(["m-inbox"]));
|
|
436
|
-
const unified = buildListAllThreadsOptions({}, new Set(["m-inbox"]));
|
|
437
|
-
assert.equal(search.limit, unified.limit);
|
|
438
|
-
});
|
|
439
|
-
});
|
|
440
|
-
|
|
441
|
-
// #308: the cross-account collections filtered and counted in the browser, over
|
|
442
|
-
// the pages a client happened to have loaded. Both are the query's job now.
|
|
443
|
-
describe("executeUnifiedThreadListing", () => {
|
|
444
|
-
interface Call {
|
|
445
|
-
mode: "listByDate" | "listByStarred" | "searchByDate" | "count";
|
|
446
|
-
search?: SearchOptions;
|
|
447
|
-
limit?: number;
|
|
448
|
-
continuationToken?: string;
|
|
449
|
-
inboxMailboxIds?: Set<string>;
|
|
450
|
-
mailboxIds?: Set<string>;
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
const emptyPage = { items: [], continuationToken: undefined };
|
|
454
|
-
|
|
455
|
-
const buildListingClient = (calls: Call[], count = 0) =>
|
|
456
|
-
({
|
|
457
|
-
account: {
|
|
458
|
-
listMailSourcesByAccountConfig: async () => [
|
|
459
|
-
account("a1"),
|
|
460
|
-
account("a2"),
|
|
461
|
-
],
|
|
462
|
-
},
|
|
463
|
-
mailbox: {
|
|
464
|
-
listAllByAccount: async (accountId: string) =>
|
|
465
|
-
accountId === "a1"
|
|
466
|
-
? [
|
|
467
|
-
mailbox("m-inbox", "a1", "INBOX"),
|
|
468
|
-
mailbox("m-archive", "a1", "Archive"),
|
|
469
|
-
]
|
|
470
|
-
: [
|
|
471
|
-
mailbox("m2-inbox", "a2", "INBOX"),
|
|
472
|
-
mailbox("m2-archive", "a2", "Archive"),
|
|
473
|
-
mailbox("m2-trash", "a2", "Trash", ["Trash"]),
|
|
474
|
-
],
|
|
475
|
-
},
|
|
476
|
-
accountSetting: { listByAccountConfig: async () => [] },
|
|
477
|
-
message: { get: async () => [] },
|
|
478
|
-
address: { getAddress: async () => [] },
|
|
479
|
-
messageLabel: { listByMessageIds: async () => [] },
|
|
480
|
-
label: { listByAccountConfig: async () => [] },
|
|
481
|
-
threadMessage: {
|
|
482
|
-
listByDate: async (
|
|
483
|
-
_config: string,
|
|
484
|
-
options?: { search?: SearchOptions; limit?: number },
|
|
485
|
-
) => {
|
|
486
|
-
calls.push({ mode: "listByDate", ...options });
|
|
487
|
-
return emptyPage;
|
|
488
|
-
},
|
|
489
|
-
listByStarred: async (
|
|
490
|
-
_config: string,
|
|
491
|
-
options?: { search?: SearchOptions; limit?: number },
|
|
492
|
-
) => {
|
|
493
|
-
calls.push({ mode: "listByStarred", ...options });
|
|
494
|
-
return emptyPage;
|
|
495
|
-
},
|
|
496
|
-
searchByDate: async (
|
|
497
|
-
_config: string,
|
|
498
|
-
search: SearchOptions,
|
|
499
|
-
options?: { limit?: number },
|
|
500
|
-
) => {
|
|
501
|
-
calls.push({ mode: "searchByDate", search, ...options });
|
|
502
|
-
return emptyPage;
|
|
503
|
-
},
|
|
504
|
-
countThreadsInScope: async (
|
|
505
|
-
_config: string,
|
|
506
|
-
search: SearchOptions,
|
|
507
|
-
options?: { mailboxIds?: Set<string> },
|
|
508
|
-
) => {
|
|
509
|
-
calls.push({ mode: "count", search, ...options });
|
|
510
|
-
return count;
|
|
511
|
-
},
|
|
512
|
-
},
|
|
513
|
-
}) as unknown as UnifiedThreadClient;
|
|
514
|
-
|
|
515
|
-
const params = (
|
|
516
|
-
overrides: Partial<UnifiedThreadParams> = {},
|
|
517
|
-
): UnifiedThreadParams => ({
|
|
518
|
-
starredOnly: false,
|
|
519
|
-
count: false,
|
|
520
|
-
results: true,
|
|
521
|
-
...overrides,
|
|
522
|
-
});
|
|
523
|
-
|
|
524
|
-
test("sends the row filters into the starred query", async () => {
|
|
525
|
-
const calls: Call[] = [];
|
|
526
|
-
await executeUnifiedThreadListing(buildListingClient(calls), CONFIG_ID, {
|
|
527
|
-
...params({ starredOnly: true }),
|
|
528
|
-
category: ["social", "personal"] as MessageCategory[],
|
|
529
|
-
unread: true,
|
|
530
|
-
attachments: true,
|
|
531
|
-
});
|
|
532
|
-
|
|
533
|
-
assert.equal(calls.length, 1);
|
|
534
|
-
assert.equal(calls[0].mode, "listByStarred");
|
|
535
|
-
assert.deepEqual(calls[0].search, {
|
|
536
|
-
starred: true,
|
|
537
|
-
category: ["social", "personal"],
|
|
538
|
-
unread: true,
|
|
539
|
-
attachments: true,
|
|
540
|
-
});
|
|
541
|
-
});
|
|
542
|
-
|
|
543
|
-
test("sends the row filters into the INBOX query", async () => {
|
|
544
|
-
const calls: Call[] = [];
|
|
545
|
-
await executeUnifiedThreadListing(buildListingClient(calls), CONFIG_ID, {
|
|
546
|
-
...params(),
|
|
547
|
-
category: ["uncategorized"] as MessageCategory[],
|
|
548
|
-
});
|
|
549
|
-
|
|
550
|
-
assert.equal(calls[0].mode, "listByDate");
|
|
551
|
-
assert.deepEqual(calls[0].search, { category: ["uncategorized"] });
|
|
552
|
-
});
|
|
553
|
-
|
|
554
|
-
test("sends the row filters into the search query", async () => {
|
|
555
|
-
const calls: Call[] = [];
|
|
556
|
-
await executeUnifiedThreadListing(buildListingClient(calls), CONFIG_ID, {
|
|
557
|
-
...params({ starredOnly: true }),
|
|
558
|
-
searchText: "invoice",
|
|
559
|
-
unread: true,
|
|
560
|
-
});
|
|
561
|
-
|
|
562
|
-
assert.equal(calls[0].mode, "searchByDate");
|
|
563
|
-
assert.deepEqual(calls[0].search, {
|
|
564
|
-
query: "invoice",
|
|
565
|
-
starred: true,
|
|
566
|
-
unread: true,
|
|
567
|
-
});
|
|
568
|
-
});
|
|
569
|
-
|
|
570
|
-
// The defect the count replaces: a page-length figure grew with every press
|
|
571
|
-
// of "load more" while the header presented it as a total.
|
|
572
|
-
test("counts the same however far the caller has paged", async () => {
|
|
573
|
-
const calls: Call[] = [];
|
|
574
|
-
const first = await executeUnifiedThreadListing(
|
|
575
|
-
buildListingClient(calls, 42),
|
|
576
|
-
CONFIG_ID,
|
|
577
|
-
params({ starredOnly: true, count: true, unread: true }),
|
|
578
|
-
);
|
|
579
|
-
const later = await executeUnifiedThreadListing(
|
|
580
|
-
buildListingClient(calls, 42),
|
|
581
|
-
CONFIG_ID,
|
|
582
|
-
params({
|
|
583
|
-
starredOnly: true,
|
|
584
|
-
count: true,
|
|
585
|
-
unread: true,
|
|
586
|
-
continuationToken: "page-3",
|
|
587
|
-
limit: 10,
|
|
588
|
-
}),
|
|
589
|
-
);
|
|
590
|
-
|
|
591
|
-
assert.equal(first.count, 42);
|
|
592
|
-
assert.equal(later.count, 42);
|
|
593
|
-
});
|
|
594
|
-
|
|
595
|
-
test("reads the count alone when results are not wanted", async () => {
|
|
596
|
-
const calls: Call[] = [];
|
|
597
|
-
const response = await executeUnifiedThreadListing(
|
|
598
|
-
buildListingClient(calls, 7),
|
|
599
|
-
CONFIG_ID,
|
|
600
|
-
params({ starredOnly: true, count: true, results: false }),
|
|
601
|
-
);
|
|
602
|
-
|
|
603
|
-
assert.deepEqual(
|
|
604
|
-
calls.map((call) => call.mode),
|
|
605
|
-
["count"],
|
|
606
|
-
);
|
|
607
|
-
assert.equal(response.items, undefined);
|
|
608
|
-
assert.equal(response.count, 7);
|
|
609
|
-
});
|
|
610
|
-
|
|
611
|
-
test("omits the count when it was not asked for", async () => {
|
|
612
|
-
const calls: Call[] = [];
|
|
613
|
-
const response = await executeUnifiedThreadListing(
|
|
614
|
-
buildListingClient(calls, 7),
|
|
615
|
-
CONFIG_ID,
|
|
616
|
-
params({ starredOnly: true }),
|
|
617
|
-
);
|
|
618
|
-
|
|
619
|
-
assert.equal(response.count, undefined);
|
|
620
|
-
assert.deepEqual(response.items, []);
|
|
621
|
-
});
|
|
622
|
-
|
|
623
|
-
// #1128: the one free-text parameter matches subject and From at once, so
|
|
624
|
-
// `from:` and `subject:` could not be asked for separately and the Flagged
|
|
625
|
-
// view applied both over the pages it had already fetched.
|
|
626
|
-
test("sends from and subject as their own terms", async () => {
|
|
627
|
-
const calls: Call[] = [];
|
|
628
|
-
await executeUnifiedThreadListing(buildListingClient(calls), CONFIG_ID, {
|
|
629
|
-
...params({ starredOnly: true }),
|
|
630
|
-
from: "alice",
|
|
631
|
-
subject: "invoice",
|
|
632
|
-
});
|
|
633
|
-
|
|
634
|
-
assert.deepEqual(calls[0].search, {
|
|
635
|
-
from: "alice",
|
|
636
|
-
subject: "invoice",
|
|
637
|
-
starred: true,
|
|
638
|
-
});
|
|
639
|
-
});
|
|
640
|
-
|
|
641
|
-
test("blank field text asks for nothing", async () => {
|
|
642
|
-
const calls: Call[] = [];
|
|
643
|
-
await executeUnifiedThreadListing(buildListingClient(calls), CONFIG_ID, {
|
|
644
|
-
...params(),
|
|
645
|
-
from: "",
|
|
646
|
-
subject: "",
|
|
647
|
-
});
|
|
648
|
-
|
|
649
|
-
assert.deepEqual(calls[0].search, {});
|
|
650
|
-
});
|
|
651
|
-
|
|
652
|
-
// #1137: the count answered a wider set than the list, because the browser
|
|
653
|
-
// dropped muted senders after the rows arrived and nothing told the server.
|
|
654
|
-
test("the muted term reaches the listing and the count alike", async () => {
|
|
655
|
-
const calls: Call[] = [];
|
|
656
|
-
await executeUnifiedThreadListing(buildListingClient(calls), CONFIG_ID, {
|
|
657
|
-
...params({ count: true }),
|
|
658
|
-
muted: false,
|
|
659
|
-
});
|
|
660
|
-
|
|
661
|
-
const listing = calls.find((call) => call.mode === "listByDate");
|
|
662
|
-
const counting = calls.find((call) => call.mode === "count");
|
|
663
|
-
assert.deepEqual(listing?.search, { muted: false });
|
|
664
|
-
assert.deepEqual(counting?.search, { muted: false });
|
|
665
|
-
});
|
|
666
|
-
|
|
667
|
-
test("an unstated muted term filters nothing", async () => {
|
|
668
|
-
const calls: Call[] = [];
|
|
669
|
-
await executeUnifiedThreadListing(
|
|
670
|
-
buildListingClient(calls),
|
|
671
|
-
CONFIG_ID,
|
|
672
|
-
params(),
|
|
673
|
-
);
|
|
674
|
-
|
|
675
|
-
assert.equal(Object.hasOwn(calls[0].search ?? {}, "muted"), false);
|
|
676
|
-
});
|
|
677
|
-
|
|
678
|
-
// #1136: the account pill narrowed the rows after they arrived, so a count
|
|
679
|
-
// over every account was not the size of a list showing one and the brief
|
|
680
|
-
// withheld every number rather than overstate.
|
|
681
|
-
test("an account scope narrows the listing and the count to that account", async () => {
|
|
682
|
-
const calls: Call[] = [];
|
|
683
|
-
await executeUnifiedThreadListing(
|
|
684
|
-
buildListingClient(calls, 3),
|
|
685
|
-
CONFIG_ID,
|
|
686
|
-
params({ accountId: "a2", count: true }),
|
|
687
|
-
);
|
|
688
|
-
|
|
689
|
-
const listing = calls.find((call) => call.mode === "listByDate");
|
|
690
|
-
const counting = calls.find((call) => call.mode === "count");
|
|
691
|
-
assert.deepEqual([...(listing?.inboxMailboxIds ?? [])], ["m2-inbox"]);
|
|
692
|
-
assert.deepEqual([...(counting?.mailboxIds ?? [])].sort(), ["m2-inbox"]);
|
|
693
|
-
});
|
|
694
|
-
|
|
695
|
-
test("an account scope narrows the search scope too", async () => {
|
|
696
|
-
const calls: Call[] = [];
|
|
697
|
-
await executeUnifiedThreadListing(buildListingClient(calls), CONFIG_ID, {
|
|
698
|
-
...params({ accountId: "a2" }),
|
|
699
|
-
searchText: "invoice",
|
|
700
|
-
});
|
|
701
|
-
|
|
702
|
-
assert.equal(calls[0].mode, "searchByDate");
|
|
703
|
-
assert.deepEqual([...(calls[0].mailboxIds ?? [])].sort(), [
|
|
704
|
-
"m2-archive",
|
|
705
|
-
"m2-inbox",
|
|
706
|
-
]);
|
|
707
|
-
});
|
|
708
|
-
|
|
709
|
-
test("no account scope leaves the cross-account aggregate", async () => {
|
|
710
|
-
const calls: Call[] = [];
|
|
711
|
-
await executeUnifiedThreadListing(
|
|
712
|
-
buildListingClient(calls),
|
|
713
|
-
CONFIG_ID,
|
|
714
|
-
params(),
|
|
715
|
-
);
|
|
716
|
-
|
|
717
|
-
assert.deepEqual([...(calls[0].inboxMailboxIds ?? [])].sort(), [
|
|
718
|
-
"m-inbox",
|
|
719
|
-
"m2-inbox",
|
|
720
|
-
]);
|
|
721
|
-
});
|
|
722
|
-
|
|
723
|
-
// An account the config does not read narrows to nothing, which is the
|
|
724
|
-
// honest answer — never the whole aggregate under another account's name.
|
|
725
|
-
test("an unknown account matches nothing", async () => {
|
|
726
|
-
const calls: Call[] = [];
|
|
727
|
-
const response = await executeUnifiedThreadListing(
|
|
728
|
-
buildListingClient(calls, 99),
|
|
729
|
-
CONFIG_ID,
|
|
730
|
-
params({ accountId: "a-other", count: true }),
|
|
731
|
-
);
|
|
732
|
-
|
|
733
|
-
assert.deepEqual(calls, []);
|
|
734
|
-
assert.deepEqual(response.items, []);
|
|
735
|
-
assert.equal(response.count, 0);
|
|
736
|
-
});
|
|
737
|
-
|
|
738
|
-
// #313: the Spam offer stated the junk share of the page the client had
|
|
739
|
-
// loaded, offered as a folder total. A count of one folder under the search's
|
|
740
|
-
// own criteria is what makes that number the folder's.
|
|
741
|
-
test("a folder scope narrows the search and the count to that folder", async () => {
|
|
742
|
-
const calls: Call[] = [];
|
|
743
|
-
const response = await executeUnifiedThreadListing(
|
|
744
|
-
buildListingClient(calls, 12),
|
|
745
|
-
CONFIG_ID,
|
|
746
|
-
{ ...params({ mailboxId: "m2-archive", count: true }), searchText: "x" },
|
|
747
|
-
);
|
|
748
|
-
|
|
749
|
-
const searching = calls.find((call) => call.mode === "searchByDate");
|
|
750
|
-
const counting = calls.find((call) => call.mode === "count");
|
|
751
|
-
assert.deepEqual([...(searching?.mailboxIds ?? [])], ["m2-archive"]);
|
|
752
|
-
assert.deepEqual([...(counting?.mailboxIds ?? [])], ["m2-archive"]);
|
|
753
|
-
assert.equal(response.count, 12);
|
|
754
|
-
});
|
|
755
|
-
|
|
756
|
-
test("a folder scope intersects the account scope rather than replacing it", async () => {
|
|
757
|
-
const calls: Call[] = [];
|
|
758
|
-
const response = await executeUnifiedThreadListing(
|
|
759
|
-
buildListingClient(calls, 99),
|
|
760
|
-
CONFIG_ID,
|
|
761
|
-
{
|
|
762
|
-
...params({ accountId: "a1", mailboxId: "m2-archive", count: true }),
|
|
763
|
-
searchText: "x",
|
|
764
|
-
},
|
|
765
|
-
);
|
|
766
|
-
|
|
767
|
-
assert.deepEqual(calls, []);
|
|
768
|
-
assert.equal(response.count, 0);
|
|
769
|
-
});
|
|
770
|
-
|
|
771
|
-
// Trash is the one folder the unscoped search never reaches, so a caller
|
|
772
|
-
// asking what it holds of a search is asking about mail the search does not
|
|
773
|
-
// see. Answering with the folder's own contents would widen the scope.
|
|
774
|
-
test("a folder the search scope excludes matches nothing", async () => {
|
|
775
|
-
const calls: Call[] = [];
|
|
776
|
-
const response = await executeUnifiedThreadListing(
|
|
777
|
-
buildListingClient(calls, 99),
|
|
778
|
-
CONFIG_ID,
|
|
779
|
-
{ ...params({ mailboxId: "m2-trash", count: true }), searchText: "x" },
|
|
780
|
-
);
|
|
781
|
-
|
|
782
|
-
assert.deepEqual(calls, []);
|
|
783
|
-
assert.equal(response.count, 0);
|
|
784
|
-
});
|
|
785
|
-
});
|