@remit/web-client 0.0.102 → 0.0.103
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
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@remit/web-client",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.103",
|
|
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": {
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import { dirname, resolve } from "node:path";
|
|
4
|
+
import { describe, it } from "node:test";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* A filter made from a search belongs to the account the search ran over (#524).
|
|
9
|
+
* The surface hands its account to the host on the props; a search entry that
|
|
10
|
+
* reads past it writes the rule against whichever account happens to be first in
|
|
11
|
+
* the list, and the mail the user was looking at is never filtered.
|
|
12
|
+
*
|
|
13
|
+
* Which account wins is decided — and tested — in
|
|
14
|
+
* `../../lib/organize/search-to-rule.test.ts`. What is read off the source here
|
|
15
|
+
* is that the host asks that question with the surface's account in hand, which
|
|
16
|
+
* is the wiring the bug lived in and which no unit of the resolver can prove.
|
|
17
|
+
* The host wires routing, history and several data hooks together, so it is read
|
|
18
|
+
* rather than rendered, as this package's other component-level rules are (see
|
|
19
|
+
* `SelectionWizardHost.run-exit.test.ts`).
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
23
|
+
const source = readFileSync(resolve(here, "SelectionWizardHost.tsx"), "utf8");
|
|
24
|
+
|
|
25
|
+
const host = source.match(
|
|
26
|
+
/export function SelectionWizardHost\(([\s\S]*)$/,
|
|
27
|
+
)?.[1];
|
|
28
|
+
|
|
29
|
+
describe("the search entry's account", () => {
|
|
30
|
+
it("is resolved with the account the surface handed over", () => {
|
|
31
|
+
assert.ok(host, "the host is gone");
|
|
32
|
+
const call = host.match(/searchRuleAccountId\(([\s\S]*?)\)/)?.[1];
|
|
33
|
+
assert.ok(call, "the search entry resolves no account of its own");
|
|
34
|
+
assert.match(call, /\bprops\.accountId\b/);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("never reaches past it for the first configured account", () => {
|
|
38
|
+
assert.ok(host, "the host is gone");
|
|
39
|
+
assert.doesNotMatch(host, /accounts\[0\]/);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
@@ -62,6 +62,7 @@ import {
|
|
|
62
62
|
normalizeClauseValue,
|
|
63
63
|
SUPPORTED_CLAUSE_FIELDS,
|
|
64
64
|
} from "@/lib/organize/rule-model";
|
|
65
|
+
import { searchRuleAccountId } from "@/lib/organize/search-to-rule";
|
|
65
66
|
import type { OrganizeMatchPredicate } from "@/lib/organize/sender-fallback";
|
|
66
67
|
import { useWizardEntryValue, useWizardStep } from "@/lib/wizard-history";
|
|
67
68
|
|
|
@@ -1003,9 +1004,9 @@ function SelectionWizardSession({
|
|
|
1003
1004
|
*
|
|
1004
1005
|
* The URL says which affordance opened the wizard, and that decides two things
|
|
1005
1006
|
* and no more: which step it opens on, and whether the query seeds the clauses.
|
|
1006
|
-
* A search entry ticks nothing
|
|
1007
|
-
*
|
|
1008
|
-
*
|
|
1007
|
+
* A search entry ticks nothing, so the selection the bar would have handed over
|
|
1008
|
+
* is not the one it walks; its rule belongs to the account the surface handed
|
|
1009
|
+
* over, and to the one the query names when it names one (#524).
|
|
1009
1010
|
*/
|
|
1010
1011
|
export function SelectionWizardHost(props: SelectionWizardHostProps) {
|
|
1011
1012
|
const fromSearch = useWizardEntryValue() === "search";
|
|
@@ -1024,7 +1025,11 @@ export function SelectionWizardHost(props: SelectionWizardHostProps) {
|
|
|
1024
1025
|
{...(conversion
|
|
1025
1026
|
? {
|
|
1026
1027
|
verb: "organize" as const,
|
|
1027
|
-
accountId:
|
|
1028
|
+
accountId: searchRuleAccountId(
|
|
1029
|
+
conversion,
|
|
1030
|
+
props.accountId,
|
|
1031
|
+
accounts,
|
|
1032
|
+
),
|
|
1028
1033
|
selection: EMPTY_SELECTION,
|
|
1029
1034
|
crossAccount: false,
|
|
1030
1035
|
escalated: undefined,
|
|
@@ -2,7 +2,7 @@ import assert from "node:assert/strict";
|
|
|
2
2
|
import { describe, it } from "node:test";
|
|
3
3
|
import { isConvertible } from "@remit/ui";
|
|
4
4
|
import { parseSearchTokens, type SearchTokenContext } from "../search-tokens";
|
|
5
|
-
import { convertSearchToRule } from "./search-to-rule";
|
|
5
|
+
import { convertSearchToRule, searchRuleAccountId } from "./search-to-rule";
|
|
6
6
|
|
|
7
7
|
const CONTEXT: SearchTokenContext = {
|
|
8
8
|
mailboxesByName: new Map([["archive", "mbx-archive"]]),
|
|
@@ -124,3 +124,39 @@ describe("convertSearchToRule — nothing left to make a filter from", () => {
|
|
|
124
124
|
assert.equal(isConvertible(convert("in:archive receipts")), true);
|
|
125
125
|
});
|
|
126
126
|
});
|
|
127
|
+
|
|
128
|
+
describe("searchRuleAccountId — whose account the filter belongs to", () => {
|
|
129
|
+
const ACCOUNTS = [{ accountId: "acc-first" }, { accountId: "acc-work" }];
|
|
130
|
+
|
|
131
|
+
it("uses the account the surface is showing for an ordinary search", () => {
|
|
132
|
+
assert.equal(
|
|
133
|
+
searchRuleAccountId(convert("invoice"), "acc-work", ACCOUNTS),
|
|
134
|
+
"acc-work",
|
|
135
|
+
);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it("lets an account: facet name a different account than the surface", () => {
|
|
139
|
+
assert.equal(
|
|
140
|
+
searchRuleAccountId(
|
|
141
|
+
convert("account:work invoice"),
|
|
142
|
+
"acc-other",
|
|
143
|
+
ACCOUNTS,
|
|
144
|
+
),
|
|
145
|
+
"acc-work",
|
|
146
|
+
);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it("falls back to the first configured account only when nothing names one", () => {
|
|
150
|
+
assert.equal(
|
|
151
|
+
searchRuleAccountId(convert("invoice"), undefined, ACCOUNTS),
|
|
152
|
+
"acc-first",
|
|
153
|
+
);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it("has no account to give when none is configured", () => {
|
|
157
|
+
assert.equal(
|
|
158
|
+
searchRuleAccountId(convert("invoice"), undefined, []),
|
|
159
|
+
undefined,
|
|
160
|
+
);
|
|
161
|
+
});
|
|
162
|
+
});
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { RemitImapAccountResponse } from "@remit/api-http-client/types.gen.ts";
|
|
1
2
|
import type {
|
|
2
3
|
DroppedFacet,
|
|
3
4
|
DroppedFacetType,
|
|
@@ -103,3 +104,17 @@ export const convertSearchToRule = (
|
|
|
103
104
|
droppedSemantic: keptTerms && searchHadSemanticReach,
|
|
104
105
|
};
|
|
105
106
|
};
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Which account a rule converted from a search belongs to (#524). A query names
|
|
110
|
+
* one only through an explicit `account:` facet, which an ordinary search does
|
|
111
|
+
* not carry; without it the rule belongs to the account whose mail the search
|
|
112
|
+
* ran over, which is the account the surface handed over. Only a surface that
|
|
113
|
+
* has no single account of its own leaves the first configured one to answer.
|
|
114
|
+
*/
|
|
115
|
+
export const searchRuleAccountId = (
|
|
116
|
+
conversion: SearchConversion,
|
|
117
|
+
surfaceAccountId: string | undefined,
|
|
118
|
+
accounts: readonly Pick<RemitImapAccountResponse, "accountId">[],
|
|
119
|
+
): string | undefined =>
|
|
120
|
+
conversion.targetAccountId ?? surfaceAccountId ?? accounts[0]?.accountId;
|