@remit/mailbox-service 0.0.109 → 0.0.110

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/mailbox-service",
3
- "version": "0.0.109",
3
+ "version": "0.0.110",
4
4
  "type": "module",
5
5
  "main": "src/index.ts",
6
6
  "types": "src/index.ts",
@@ -65,6 +65,42 @@ describe("clauseMatches", () => {
65
65
  );
66
66
  });
67
67
 
68
+ it("matches HasWords on the sender display name alone, as search does", () => {
69
+ const msg = message({
70
+ from: "booking@mail.example",
71
+ fromName: "Austrian Airlines Booking",
72
+ subject: "Your trip to Vienna",
73
+ text: "Check in opens 24 hours before departure.",
74
+ });
75
+ assert.equal(
76
+ clauseMatches(
77
+ clause(FilterClauseField.HasWords, "Austrian airlines"),
78
+ msg,
79
+ ),
80
+ true,
81
+ );
82
+ });
83
+
84
+ it("matches HasWords regardless of word order and case", () => {
85
+ assert.equal(
86
+ clauseMatches(
87
+ clause(FilterClauseField.HasWords, "ALICE invoice"),
88
+ message(),
89
+ ),
90
+ true,
91
+ );
92
+ });
93
+
94
+ it("does not match HasWords when one word is missing", () => {
95
+ assert.equal(
96
+ clauseMatches(
97
+ clause(FilterClauseField.HasWords, "invoice receipt"),
98
+ message(),
99
+ ),
100
+ false,
101
+ );
102
+ });
103
+
68
104
  it("never matches an empty clause value", () => {
69
105
  assert.equal(
70
106
  clauseMatches(clause(FilterClauseField.HasWords, " "), message()),
@@ -49,6 +49,20 @@ export interface FilterMessage {
49
49
  const includesFold = (haystack: string, needle: string): boolean =>
50
50
  haystack.toLowerCase().includes(needle.toLowerCase());
51
51
 
52
+ /**
53
+ * Whether every whitespace-separated word of `value` appears somewhere in the
54
+ * sender address, sender name, subject or body — the shape free-text search
55
+ * matches with (`buildSearchConditions`), so a filter made from a search keeps
56
+ * the mail that search showed.
57
+ */
58
+ const hasEveryWord = (value: string, msg: FilterMessage): boolean => {
59
+ const fields = [msg.from, msg.fromName, msg.subject, msg.text];
60
+ return value
61
+ .split(/\s+/)
62
+ .filter(Boolean)
63
+ .every((word) => fields.some((field) => includesFold(field, word)));
64
+ };
65
+
52
66
  const hostOf = (address: string): string => {
53
67
  const at = address.lastIndexOf("@");
54
68
  return at >= 0 ? address.slice(at + 1) : address;
@@ -65,8 +79,8 @@ const registrableDomain = (addressOrHost: string): string | null =>
65
79
 
66
80
  /**
67
81
  * Whether one literal clause matches the message. From matches against the
68
- * sender address and display name; Subject against the subject; HasWords against
69
- * subject or body; ListId against the exact normalized `List-Id`; FromDomain
82
+ * sender address and display name; Subject against the subject; HasWords
83
+ * requires every word somewhere in sender, subject or body; ListId against the exact normalized `List-Id`; FromDomain
70
84
  * against the sender's registrable domain. An empty clause value never matches.
71
85
  */
72
86
  export const clauseMatches = (
@@ -81,7 +95,7 @@ export const clauseMatches = (
81
95
  case FilterClauseField.Subject:
82
96
  return includesFold(msg.subject, value);
83
97
  case FilterClauseField.HasWords:
84
- return includesFold(msg.subject, value) || includesFold(msg.text, value);
98
+ return hasEveryWord(value, msg);
85
99
  case FilterClauseField.ListId: {
86
100
  const target = normalizeListId(value);
87
101
  return target !== "" && normalizeListId(msg.listId) === target;