imapflow 1.3.7 → 1.4.1
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/.github/workflows/codeql.yml +1 -1
- package/.github/workflows/release.yaml +2 -2
- package/.github/workflows/stale.yml +1 -1
- package/.github/workflows/test.yml +10 -3
- package/.release-please-manifest.json +1 -1
- package/CHANGELOG.md +14 -0
- package/CLAUDE.md +93 -1
- package/lib/imap-flow.d.ts +44 -26
- package/lib/search-compiler.js +48 -0
- package/package.json +5 -5
- package/test/search-compiler-test.js +80 -0
|
@@ -57,7 +57,7 @@ jobs:
|
|
|
57
57
|
# your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages
|
|
58
58
|
steps:
|
|
59
59
|
- name: Checkout repository
|
|
60
|
-
uses: actions/checkout@
|
|
60
|
+
uses: actions/checkout@v6
|
|
61
61
|
|
|
62
62
|
# Add any setup steps before running the `github/codeql-action/init` action.
|
|
63
63
|
# This includes steps like installing compilers or runtimes (`actions/setup-node`
|
|
@@ -12,7 +12,7 @@ jobs:
|
|
|
12
12
|
stale:
|
|
13
13
|
runs-on: ubuntu-latest
|
|
14
14
|
steps:
|
|
15
|
-
- uses: actions/stale@
|
|
15
|
+
- uses: actions/stale@v10
|
|
16
16
|
with:
|
|
17
17
|
stale-issue-message: 'This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 5 days.'
|
|
18
18
|
stale-pr-message: 'This PR is stale because it has been open 45 days with no activity. Remove stale label or comment or this will be closed in 10 days.'
|
|
@@ -7,18 +7,25 @@ on:
|
|
|
7
7
|
permissions:
|
|
8
8
|
contents: read
|
|
9
9
|
|
|
10
|
+
concurrency:
|
|
11
|
+
group: ${{ github.workflow }}-${{ github.ref }}
|
|
12
|
+
cancel-in-progress: true
|
|
13
|
+
|
|
10
14
|
jobs:
|
|
11
15
|
test:
|
|
16
|
+
name: Test (Node ${{ matrix.node }})
|
|
17
|
+
timeout-minutes: 10
|
|
12
18
|
strategy:
|
|
13
19
|
matrix:
|
|
14
|
-
node: [
|
|
20
|
+
node: [22.x, 24.x]
|
|
15
21
|
os: [ubuntu-latest]
|
|
16
22
|
runs-on: ${{ matrix.os }}
|
|
17
23
|
steps:
|
|
18
|
-
- uses: actions/checkout@
|
|
24
|
+
- uses: actions/checkout@v6
|
|
19
25
|
- name: Use Node.js ${{ matrix.node }}
|
|
20
|
-
uses: actions/setup-node@
|
|
26
|
+
uses: actions/setup-node@v6
|
|
21
27
|
with:
|
|
22
28
|
node-version: ${{ matrix.node }}
|
|
29
|
+
cache: npm
|
|
23
30
|
- run: npm install
|
|
24
31
|
- run: npm test
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.4.1](https://github.com/postalsys/imapflow/compare/v1.4.0...v1.4.1) (2026-06-15)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* ship refreshed dependencies (nodemailer 9, updated toolchain) ([e1d36e6](https://github.com/postalsys/imapflow/commit/e1d36e68c7a5dd4dd3bc9a7816dc96a01f063af3))
|
|
9
|
+
|
|
10
|
+
## [1.4.0](https://github.com/postalsys/imapflow/compare/v1.3.7...v1.4.0) (2026-06-09)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Features
|
|
14
|
+
|
|
15
|
+
* add Gmail label search term to the search compiler ([4b2d173](https://github.com/postalsys/imapflow/commit/4b2d1736660441e95cd55a30ad6632a676fb605c))
|
|
16
|
+
|
|
3
17
|
## [1.3.7](https://github.com/postalsys/imapflow/compare/v1.3.6...v1.3.7) (2026-06-08)
|
|
4
18
|
|
|
5
19
|
|
package/CLAUDE.md
CHANGED
|
@@ -1,4 +1,96 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Claude Development Guidelines
|
|
2
|
+
|
|
3
|
+
## Project Overview
|
|
4
|
+
|
|
5
|
+
ImapFlow is a modern, promise-based IMAP client library for Node.js. It opens
|
|
6
|
+
TLS/cleartext connections to IMAP servers, authenticates, and parses untrusted
|
|
7
|
+
protocol responses from those servers into a friendly API. It is published to
|
|
8
|
+
npm as `imapflow` and ships TypeScript type definitions.
|
|
9
|
+
|
|
10
|
+
## Project Structure
|
|
11
|
+
|
|
12
|
+
- `lib/imap-flow.js` - Main `ImapFlow` client class (connection lifecycle, command dispatch, public API)
|
|
13
|
+
- `lib/imap-flow.d.ts` - TypeScript type definitions (published as `types`)
|
|
14
|
+
- `lib/imap-commands.js` - Registry wiring individual command implementations
|
|
15
|
+
- `lib/commands/` - Per-command implementations (login, fetch, search, append, etc.)
|
|
16
|
+
- `lib/handler/` - IMAP response stream parser and command compiler (tokenizer, literals, line handling)
|
|
17
|
+
- `lib/search-compiler.js` - Translates the search query object into IMAP SEARCH terms
|
|
18
|
+
- `lib/charsets.js`, `lib/jp-decoder.js` - Charset/encoding helpers
|
|
19
|
+
- `lib/special-use.js` - SPECIAL-USE mailbox detection
|
|
20
|
+
- `lib/proxy-connection.js` - SOCKS/HTTP proxy connection support
|
|
21
|
+
- `lib/limited-passthrough.js`, `lib/tools.js`, `lib/logger.js` - Internal utilities
|
|
22
|
+
- `test/` - Unit tests (`*-test.js`), run with nodeunit via Grunt
|
|
23
|
+
- `examples/` - Standalone usage examples (not production code)
|
|
24
|
+
|
|
25
|
+
## Technology Stack
|
|
26
|
+
|
|
27
|
+
- **Runtime**: Node.js (CI tests on 22.x and 24.x)
|
|
28
|
+
- **Module system**: CommonJS (see Packaging Constraints below)
|
|
29
|
+
- **Testing**: Grunt + grunt-contrib-nodeunit, ESLint via grunt-eslint
|
|
30
|
+
- **Lint/format**: ESLint (`eslint.config.js`, flat config) + Prettier
|
|
31
|
+
- **Key dependencies**: `@zone-eu/mailsplit`, `libmime`, `libqp`, `libbase64`, `iconv-lite`, `encoding-japanese`, `nodemailer`, `pino`, `socks`
|
|
32
|
+
|
|
33
|
+
## Development Commands
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
npm test # Run full suite via Grunt (ESLint + nodeunit tests)
|
|
37
|
+
npm run coverage # Run tests under c8 coverage (text + html reports)
|
|
38
|
+
npm run lint # Lint with ESLint
|
|
39
|
+
npm run format # Format with Prettier (js, json, md, yml, yaml)
|
|
40
|
+
npm run update # Refresh deps: remove node_modules + lockfile, ncu -u, npm install
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Testing
|
|
44
|
+
|
|
45
|
+
- Tests live in `test/` and are named `*-test.js`; the Grunt nodeunit glob only matches that pattern, so helpers/fixtures are never run as tests.
|
|
46
|
+
- `npm test` runs `grunt`, which runs ESLint first, then the nodeunit suite. Keep the suite green and lint-clean before committing.
|
|
47
|
+
- New tests go in `test/` as `*-test.js`. The parser, command compiler, and search compiler are the most security-sensitive areas - add hostile/malformed-input cases there.
|
|
48
|
+
|
|
49
|
+
## Packaging Constraints (IMPORTANT)
|
|
50
|
+
|
|
51
|
+
EmailEngine (see "Relationship to EmailEngine" below) bundles ImapFlow and all
|
|
52
|
+
of its transitive dependencies into a single self-contained executable using
|
|
53
|
+
[`@yao-pkg/pkg`](https://github.com/yao-pkg/pkg). `pkg` works by snapshotting a
|
|
54
|
+
CommonJS `require()` graph, so it **cannot bundle pure-ESM packages**.
|
|
55
|
+
|
|
56
|
+
Therefore ImapFlow itself and every dependency it pulls in must stay
|
|
57
|
+
CommonJS-compatible:
|
|
58
|
+
|
|
59
|
+
- ImapFlow source stays CommonJS (`require`/`module.exports`). Do not convert the library to ESM.
|
|
60
|
+
- Do not add a dependency that is pure ESM (`"type": "module"` with only an `import`/ESM entry and no CommonJS export). It must be `require()`-able.
|
|
61
|
+
- When `npm run update` or a new dependency would pull in a pure-ESM package (a common outcome of major-version bumps), pin to the last CommonJS-compatible version instead, or find a CommonJS alternative. Verify with a quick `require()` of the package after updating.
|
|
62
|
+
- Keep dynamic `require()` paths static enough for `pkg` to detect; avoid building module paths at runtime in ways the bundler can't trace.
|
|
63
|
+
|
|
64
|
+
## Code Style Rules
|
|
65
|
+
|
|
66
|
+
- Never use emojis in code or documentation, only printable ASCII characters.
|
|
67
|
+
- Use a single hyphen-minus (`-`) as a dash in user-facing strings and docs. Never use double hyphens (`--`), em dashes, or en dashes.
|
|
68
|
+
- When composing git commit messages, do not include Claude as a co-contributor.
|
|
69
|
+
- Use Conventional Commit prefixes (`feat:`, `fix:`, `chore:`, `docs:`, `test:`, `ci:`, ...). Versioning and the changelog are driven by these prefixes via release-please.
|
|
70
|
+
- For commits that do not change published runtime behavior (docs, comments, CI/workflow tweaks, formatting), append `[skip ci]` to the commit message to avoid triggering the GitHub Actions workflows. Exception: do not add `[skip ci]` to commits using a `fix:` or `feat:` prefix - those must run so the release action is triggered.
|
|
71
|
+
- After making code changes:
|
|
72
|
+
1. Run `npm run format` and `npm run lint`
|
|
73
|
+
2. Run `npm test` and keep it green
|
|
74
|
+
3. For non-trivial changes, run `/simplify` to review changed code and `/security-review` to check for security issues before committing
|
|
75
|
+
- After pushing, check the GitHub Actions runs for the push (e.g. `gh run list --branch master`) and report their status, including the CodeQL "CodeQL Advanced" code-scanning run. If a run fails for a strange or unrelated reason (for example a checkout step reporting "account suspended", HTTP 403, or other auth/infrastructure errors that have nothing to do with the change), check <https://www.githubstatus.com/> for an active GitHub incident before assuming the failure is caused by the change.
|
|
76
|
+
|
|
77
|
+
## Relationship to EmailEngine
|
|
78
|
+
|
|
79
|
+
ImapFlow is developed and maintained primarily as the IMAP client used by
|
|
80
|
+
[EmailEngine](https://github.com/postalsys/emailengine). The local development
|
|
81
|
+
copy of EmailEngine lives at `../emailengine` relative to this project root. When
|
|
82
|
+
EmailEngine hits a bug or unhandled promise rejection that originates in
|
|
83
|
+
ImapFlow, the fix belongs here in the ImapFlow source rather than as a
|
|
84
|
+
workaround in EmailEngine. Because EmailEngine packages this library with
|
|
85
|
+
`@yao-pkg/pkg`, never make a change here that breaks CommonJS packaging (see
|
|
86
|
+
Packaging Constraints).
|
|
87
|
+
|
|
88
|
+
## Security
|
|
89
|
+
|
|
90
|
+
Security policy and private reporting channels are documented in
|
|
91
|
+
[`SECURITY.md`](SECURITY.md) / [`SECURITY.txt`](SECURITY.txt). Code scanning runs
|
|
92
|
+
through the "CodeQL Advanced" GitHub Actions workflow
|
|
93
|
+
(`.github/workflows/codeql.yml`, config in `.github/codeql/codeql-config.yml`).
|
|
2
94
|
|
|
3
95
|
## Release Process
|
|
4
96
|
|
package/lib/imap-flow.d.ts
CHANGED
|
@@ -357,6 +357,8 @@ export interface SearchObject {
|
|
|
357
357
|
gmraw?: string;
|
|
358
358
|
/** Gmail raw search query (alias for gmraw) */
|
|
359
359
|
gmailraw?: string;
|
|
360
|
+
/** Gmail label filter (only for Gmail). Compiles to an X-GM-RAW "label:"/"-label:" query. "has" matches messages carrying all listed labels, "not" excludes messages carrying any listed label */
|
|
361
|
+
labels?: { has?: string[]; not?: string[] };
|
|
360
362
|
}
|
|
361
363
|
|
|
362
364
|
export interface FetchQueryObject {
|
|
@@ -373,12 +375,14 @@ export interface FetchQueryObject {
|
|
|
373
375
|
/** If true then include message size in the response */
|
|
374
376
|
size?: boolean;
|
|
375
377
|
/** If true then include full message in the response */
|
|
376
|
-
source?:
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
378
|
+
source?:
|
|
379
|
+
| boolean
|
|
380
|
+
| {
|
|
381
|
+
/** Include full message in the response starting from start byte */
|
|
382
|
+
start?: number;
|
|
383
|
+
/** Include full message in the response, up to maxLength bytes */
|
|
384
|
+
maxLength?: number;
|
|
385
|
+
};
|
|
382
386
|
/** If true then include thread ID in the response (only if server supports either OBJECTID or X-GM-EXT-1 extensions) */
|
|
383
387
|
threadId?: boolean;
|
|
384
388
|
/** If true then include GMail labels in the response (only if server supports X-GM-EXT-1 extension) */
|
|
@@ -740,14 +744,17 @@ export class ImapFlow extends EventEmitter {
|
|
|
740
744
|
mailboxClose(): Promise<boolean>;
|
|
741
745
|
|
|
742
746
|
/** Requests the status of the indicated mailbox */
|
|
743
|
-
status(
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
747
|
+
status(
|
|
748
|
+
path: string,
|
|
749
|
+
query: {
|
|
750
|
+
messages?: boolean;
|
|
751
|
+
recent?: boolean;
|
|
752
|
+
uidNext?: boolean;
|
|
753
|
+
uidValidity?: boolean;
|
|
754
|
+
unseen?: boolean;
|
|
755
|
+
highestModseq?: boolean;
|
|
756
|
+
}
|
|
757
|
+
): Promise<StatusObject>;
|
|
751
758
|
|
|
752
759
|
/** Starts listening for new or deleted messages from the currently opened mailbox */
|
|
753
760
|
idle(): Promise<boolean>;
|
|
@@ -779,10 +786,13 @@ export class ImapFlow extends EventEmitter {
|
|
|
779
786
|
/** Search messages from the currently opened mailbox — returns number[] (backward-compatible) */
|
|
780
787
|
search(query: SearchObject, options?: { uid?: boolean }): Promise<number[] | false>;
|
|
781
788
|
/** Search messages with ESEARCH RETURN options — returns ESearchResult */
|
|
782
|
-
search(
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
789
|
+
search(
|
|
790
|
+
query: SearchObject,
|
|
791
|
+
options: {
|
|
792
|
+
uid?: boolean;
|
|
793
|
+
returnOptions: Array<'MIN' | 'MAX' | 'COUNT' | 'ALL' | { partial: string }>;
|
|
794
|
+
}
|
|
795
|
+
): Promise<ESearchResult | number[] | false>;
|
|
786
796
|
|
|
787
797
|
/** Fetch messages from the currently opened mailbox */
|
|
788
798
|
fetch(range: SequenceString | number[] | SearchObject, query: FetchQueryObject, options?: FetchOptions): AsyncIterableIterator<FetchMessageObject>;
|
|
@@ -794,14 +804,22 @@ export class ImapFlow extends EventEmitter {
|
|
|
794
804
|
fetchOne(seq: SequenceString, query: FetchQueryObject, options?: FetchOptions): Promise<FetchMessageObject | false>;
|
|
795
805
|
|
|
796
806
|
/** Download either full rfc822 formatted message or a specific bodystructure part as a Stream */
|
|
797
|
-
download(
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
807
|
+
download(
|
|
808
|
+
range: SequenceString,
|
|
809
|
+
part?: string,
|
|
810
|
+
options?: {
|
|
811
|
+
uid?: boolean;
|
|
812
|
+
maxBytes?: number;
|
|
813
|
+
chunkSize?: number;
|
|
814
|
+
}
|
|
815
|
+
): Promise<DownloadObject>;
|
|
802
816
|
|
|
803
817
|
/** Fetch multiple attachments as Buffer values */
|
|
804
|
-
downloadMany(
|
|
818
|
+
downloadMany(
|
|
819
|
+
range: SequenceString,
|
|
820
|
+
parts: string[],
|
|
821
|
+
options?: { uid?: boolean }
|
|
822
|
+
): Promise<{
|
|
805
823
|
[part: string]: {
|
|
806
824
|
meta: {
|
|
807
825
|
contentType?: string;
|
|
@@ -811,7 +829,7 @@ export class ImapFlow extends EventEmitter {
|
|
|
811
829
|
encoding?: string;
|
|
812
830
|
};
|
|
813
831
|
content: Buffer | null;
|
|
814
|
-
}
|
|
832
|
+
};
|
|
815
833
|
}>;
|
|
816
834
|
|
|
817
835
|
/** Opens a mailbox if not already open and returns a lock */
|
|
@@ -849,4 +867,4 @@ export class ImapFlow extends EventEmitter {
|
|
|
849
867
|
|
|
850
868
|
/** Response event */
|
|
851
869
|
on(event: 'response', listener: (response: ResponseEvent) => void): this;
|
|
852
|
-
}
|
|
870
|
+
}
|
package/lib/search-compiler.js
CHANGED
|
@@ -261,6 +261,54 @@ module.exports.searchCompiler = (connection, query) => {
|
|
|
261
261
|
}
|
|
262
262
|
break;
|
|
263
263
|
|
|
264
|
+
// Gmail label search. Compiles { has, not } into an X-GM-RAW "label:"/"-label:" query
|
|
265
|
+
// since Gmail labels are not a native IMAP SEARCH key. Gmail-only (X-GM-EXT-1).
|
|
266
|
+
case 'LABELS': {
|
|
267
|
+
let labelQuery = params[term];
|
|
268
|
+
if (!labelQuery || typeof labelQuery !== 'object') {
|
|
269
|
+
break;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Collapse whitespace/quotes and quote multi-word names so they survive as a single token
|
|
273
|
+
let formatLabel = name => {
|
|
274
|
+
name = (name || '')
|
|
275
|
+
.toString()
|
|
276
|
+
.replace(/[\s"]+/g, ' ')
|
|
277
|
+
.trim();
|
|
278
|
+
return name.indexOf(' ') >= 0 ? `"${name}"` : name;
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
let rawParts = [];
|
|
282
|
+
for (let name of [].concat(labelQuery.has || [])) {
|
|
283
|
+
if (name) {
|
|
284
|
+
rawParts.push(`label:${formatLabel(name)}`);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
for (let name of [].concat(labelQuery.not || [])) {
|
|
288
|
+
if (name) {
|
|
289
|
+
rawParts.push(`-label:${formatLabel(name)}`);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Empty filter is a no-op on any server (do not require the extension)
|
|
294
|
+
if (!rawParts.length) {
|
|
295
|
+
break;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
if (!connection.capabilities.has('X-GM-EXT-1')) {
|
|
299
|
+
let error = new Error('Server does not support X-GM-EXT-1 extension required for label search');
|
|
300
|
+
error.code = 'MissingServerExtension';
|
|
301
|
+
throw error;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
let rawQuery = rawParts.join(' ');
|
|
305
|
+
if (isUnicodeString(rawQuery)) {
|
|
306
|
+
hasUnicode = true;
|
|
307
|
+
}
|
|
308
|
+
setOpt(attributes, 'X-GM-RAW', rawQuery);
|
|
309
|
+
break;
|
|
310
|
+
}
|
|
311
|
+
|
|
264
312
|
// Date searches with WITHIN extension support
|
|
265
313
|
case 'BEFORE':
|
|
266
314
|
case 'SINCE':
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "imapflow",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.1",
|
|
4
4
|
"description": "IMAP Client for Node",
|
|
5
5
|
"main": "lib/imap-flow.js",
|
|
6
6
|
"types": "lib/imap-flow.d.ts",
|
|
@@ -28,16 +28,16 @@
|
|
|
28
28
|
"homepage": "https://imapflow.com/",
|
|
29
29
|
"devDependencies": {
|
|
30
30
|
"@eslint/js": "10.0.1",
|
|
31
|
-
"@types/node": "25.9.
|
|
31
|
+
"@types/node": "25.9.3",
|
|
32
32
|
"c8": "11.0.0",
|
|
33
|
-
"eslint": "10.
|
|
33
|
+
"eslint": "10.5.0",
|
|
34
34
|
"eslint-config-nodemailer": "1.2.0",
|
|
35
35
|
"eslint-config-prettier": "10.1.8",
|
|
36
36
|
"grunt": "1.6.2",
|
|
37
37
|
"grunt-cli": "1.5.0",
|
|
38
38
|
"grunt-contrib-nodeunit": "5.0.0",
|
|
39
39
|
"grunt-eslint": "26.0.0",
|
|
40
|
-
"prettier": "3.8.
|
|
40
|
+
"prettier": "3.8.4",
|
|
41
41
|
"proxyquire": "^2.1.3",
|
|
42
42
|
"typescript": "6.0.3"
|
|
43
43
|
},
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"libbase64": "1.3.0",
|
|
49
49
|
"libmime": "5.3.8",
|
|
50
50
|
"libqp": "2.1.1",
|
|
51
|
-
"nodemailer": "
|
|
51
|
+
"nodemailer": "9.0.0",
|
|
52
52
|
"pino": "10.3.1",
|
|
53
53
|
"socks": "2.8.9"
|
|
54
54
|
}
|
|
@@ -443,6 +443,86 @@ module.exports['Search Compiler: GMRAW throws without capability'] = test => {
|
|
|
443
443
|
test.done();
|
|
444
444
|
};
|
|
445
445
|
|
|
446
|
+
// ============================================
|
|
447
|
+
// Gmail label search tests
|
|
448
|
+
// ============================================
|
|
449
|
+
|
|
450
|
+
module.exports['Search Compiler: LABELS has compiles to label: via X-GM-RAW'] = test => {
|
|
451
|
+
let connection = createMockConnection({
|
|
452
|
+
capabilities: [['X-GM-EXT-1', true]]
|
|
453
|
+
});
|
|
454
|
+
let compiled = searchCompiler(connection, { labels: { has: ['Horizon'] } });
|
|
455
|
+
|
|
456
|
+
test.ok(hasAttr(compiled, 'X-GM-RAW'));
|
|
457
|
+
test.ok(hasAttr(compiled, 'label:Horizon'));
|
|
458
|
+
test.done();
|
|
459
|
+
};
|
|
460
|
+
|
|
461
|
+
module.exports['Search Compiler: LABELS not compiles to -label: via X-GM-RAW'] = test => {
|
|
462
|
+
let connection = createMockConnection({
|
|
463
|
+
capabilities: [['X-GM-EXT-1', true]]
|
|
464
|
+
});
|
|
465
|
+
let compiled = searchCompiler(connection, { labels: { not: ['Horizon'] } });
|
|
466
|
+
|
|
467
|
+
test.ok(hasAttr(compiled, 'X-GM-RAW'));
|
|
468
|
+
test.ok(hasAttr(compiled, '-label:Horizon'));
|
|
469
|
+
test.done();
|
|
470
|
+
};
|
|
471
|
+
|
|
472
|
+
module.exports['Search Compiler: LABELS has and not combined'] = test => {
|
|
473
|
+
let connection = createMockConnection({
|
|
474
|
+
capabilities: [['X-GM-EXT-1', true]]
|
|
475
|
+
});
|
|
476
|
+
let compiled = searchCompiler(connection, { labels: { has: ['Imported'], not: ['Horizon'] } });
|
|
477
|
+
|
|
478
|
+
test.ok(hasAttr(compiled, 'label:Imported -label:Horizon'));
|
|
479
|
+
test.done();
|
|
480
|
+
};
|
|
481
|
+
|
|
482
|
+
module.exports['Search Compiler: LABELS quotes multi-word names'] = test => {
|
|
483
|
+
let connection = createMockConnection({
|
|
484
|
+
capabilities: [['X-GM-EXT-1', true]]
|
|
485
|
+
});
|
|
486
|
+
let compiled = searchCompiler(connection, { labels: { has: ['Some Label'] } });
|
|
487
|
+
|
|
488
|
+
test.ok(hasAttr(compiled, 'label:"Some Label"'));
|
|
489
|
+
test.done();
|
|
490
|
+
};
|
|
491
|
+
|
|
492
|
+
module.exports['Search Compiler: LABELS coexists with gmraw'] = test => {
|
|
493
|
+
let connection = createMockConnection({
|
|
494
|
+
capabilities: [['X-GM-EXT-1', true]]
|
|
495
|
+
});
|
|
496
|
+
let compiled = searchCompiler(connection, { gmraw: 'has:attachment', labels: { not: ['Horizon'] } });
|
|
497
|
+
|
|
498
|
+
test.ok(hasAttr(compiled, 'has:attachment'));
|
|
499
|
+
test.ok(hasAttr(compiled, '-label:Horizon'));
|
|
500
|
+
test.done();
|
|
501
|
+
};
|
|
502
|
+
|
|
503
|
+
module.exports['Search Compiler: LABELS throws without capability'] = test => {
|
|
504
|
+
let connection = createMockConnection();
|
|
505
|
+
|
|
506
|
+
try {
|
|
507
|
+
searchCompiler(connection, { labels: { not: ['Horizon'] } });
|
|
508
|
+
test.ok(false, 'Should have thrown');
|
|
509
|
+
} catch (err) {
|
|
510
|
+
test.equal(err.code, 'MissingServerExtension');
|
|
511
|
+
test.ok(err.message.includes('X-GM-EXT-1'));
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
test.done();
|
|
515
|
+
};
|
|
516
|
+
|
|
517
|
+
module.exports['Search Compiler: empty LABELS is a no-op without capability'] = test => {
|
|
518
|
+
let connection = createMockConnection();
|
|
519
|
+
let compiled = searchCompiler(connection, { labels: {} });
|
|
520
|
+
|
|
521
|
+
test.ok(!hasAttr(compiled, 'X-GM-RAW'));
|
|
522
|
+
test.equal(compiled.length, 0);
|
|
523
|
+
test.done();
|
|
524
|
+
};
|
|
525
|
+
|
|
446
526
|
// ============================================
|
|
447
527
|
// Date search tests
|
|
448
528
|
// ============================================
|