@flighthq/tray 0.3.0-next.1032.e2b99fc → 0.3.0-next.1162.e90e518
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/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/tray.d.ts +2 -0
- package/dist/tray.d.ts.map +1 -1
- package/dist/tray.js +49 -7
- package/dist/tray.js.map +1 -1
- package/package.json +2 -2
- package/src/tray.test.ts +128 -0
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { createTrayIcon, destroyTrayIcon, displayTrayBalloon, getTrayCapabilities, getTrayIconBounds, getTrayIconTitle, getTrayIconTooltip, getTrayIcons, isTrayDestroyed, onTrayEvent, popupTrayContextMenu, removeTrayBalloon, setTrayIcon, setTrayIconContextMenu, setTrayIconTemplate, setTrayIconTitle, setTrayIconTooltip, setTrayIgnoreDoubleClickEvents, setTrayPressedIcon, startTrayIconAnimation, } from './contract';
|
|
1
|
+
export { createTrayIcon, destroyTrayIcon, displayTrayBalloon, getTrayCapabilities, getTrayIconBounds, getTrayIconTitle, getTrayIconTooltip, getTrayIcons, isTrayDestroyed, isTrayIconAnimating, onTrayEvent, popupTrayContextMenu, removeTrayBalloon, setTrayIcon, setTrayIconContextMenu, setTrayIconTemplate, setTrayIconTitle, setTrayIconTooltip, setTrayIgnoreDoubleClickEvents, setTrayPressedIcon, startTrayIconAnimation, stopTrayIconAnimation, } from './contract';
|
|
2
2
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,cAAc,EACd,eAAe,EACf,kBAAkB,EAClB,mBAAmB,EACnB,iBAAiB,EACjB,gBAAgB,EAChB,kBAAkB,EAClB,YAAY,EACZ,eAAe,EACf,WAAW,EACX,oBAAoB,EACpB,iBAAiB,EACjB,WAAW,EACX,sBAAsB,EACtB,mBAAmB,EACnB,gBAAgB,EAChB,kBAAkB,EAClB,8BAA8B,EAC9B,kBAAkB,EAClB,sBAAsB,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,cAAc,EACd,eAAe,EACf,kBAAkB,EAClB,mBAAmB,EACnB,iBAAiB,EACjB,gBAAgB,EAChB,kBAAkB,EAClB,YAAY,EACZ,eAAe,EACf,mBAAmB,EACnB,WAAW,EACX,oBAAoB,EACpB,iBAAiB,EACjB,WAAW,EACX,sBAAsB,EACtB,mBAAmB,EACnB,gBAAgB,EAChB,kBAAkB,EAClB,8BAA8B,EAC9B,kBAAkB,EAClB,sBAAsB,EACtB,qBAAqB,GACtB,MAAM,YAAY,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { createTrayIcon, destroyTrayIcon, displayTrayBalloon, getTrayCapabilities, getTrayIconBounds, getTrayIconTitle, getTrayIconTooltip, getTrayIcons, isTrayDestroyed, onTrayEvent, popupTrayContextMenu, removeTrayBalloon, setTrayIcon, setTrayIconContextMenu, setTrayIconTemplate, setTrayIconTitle, setTrayIconTooltip, setTrayIgnoreDoubleClickEvents, setTrayPressedIcon, startTrayIconAnimation, } from './contract';
|
|
1
|
+
export { createTrayIcon, destroyTrayIcon, displayTrayBalloon, getTrayCapabilities, getTrayIconBounds, getTrayIconTitle, getTrayIconTooltip, getTrayIcons, isTrayDestroyed, isTrayIconAnimating, onTrayEvent, popupTrayContextMenu, removeTrayBalloon, setTrayIcon, setTrayIconContextMenu, setTrayIconTemplate, setTrayIconTitle, setTrayIconTooltip, setTrayIgnoreDoubleClickEvents, setTrayPressedIcon, startTrayIconAnimation, stopTrayIconAnimation, } from './contract';
|
|
2
2
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,cAAc,EACd,eAAe,EACf,kBAAkB,EAClB,mBAAmB,EACnB,iBAAiB,EACjB,gBAAgB,EAChB,kBAAkB,EAClB,YAAY,EACZ,eAAe,EACf,WAAW,EACX,oBAAoB,EACpB,iBAAiB,EACjB,WAAW,EACX,sBAAsB,EACtB,mBAAmB,EACnB,gBAAgB,EAChB,kBAAkB,EAClB,8BAA8B,EAC9B,kBAAkB,EAClB,sBAAsB,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,cAAc,EACd,eAAe,EACf,kBAAkB,EAClB,mBAAmB,EACnB,iBAAiB,EACjB,gBAAgB,EAChB,kBAAkB,EAClB,YAAY,EACZ,eAAe,EACf,mBAAmB,EACnB,WAAW,EACX,oBAAoB,EACpB,iBAAiB,EACjB,WAAW,EACX,sBAAsB,EACtB,mBAAmB,EACnB,gBAAgB,EAChB,kBAAkB,EAClB,8BAA8B,EAC9B,kBAAkB,EAClB,sBAAsB,EACtB,qBAAqB,GACtB,MAAM,YAAY,CAAC"}
|
package/dist/tray.d.ts
CHANGED
|
@@ -11,6 +11,7 @@ export declare function getTrayIcons(): readonly TrayIcon[];
|
|
|
11
11
|
export declare function getTrayIconTitle(tray: TrayIcon): string;
|
|
12
12
|
export declare function getTrayIconTooltip(tray: TrayIcon): string;
|
|
13
13
|
export declare function isTrayDestroyed(tray: TrayIcon): boolean;
|
|
14
|
+
export declare function isTrayIconAnimating(tray: TrayIcon): boolean;
|
|
14
15
|
export declare function onTrayEvent(listener: (event: Readonly<TrayEventData>) => void): () => void;
|
|
15
16
|
export declare function popupTrayContextMenu(tray: TrayIcon, position?: Readonly<Vector2Like>): void;
|
|
16
17
|
export declare function removeTrayBalloon(tray: TrayIcon): void;
|
|
@@ -23,4 +24,5 @@ export declare function setTrayIconTooltip(tray: TrayIcon, tooltip: string): voi
|
|
|
23
24
|
export declare function setTrayIgnoreDoubleClickEvents(tray: TrayIcon, ignore: boolean): void;
|
|
24
25
|
export declare function setTrayPressedIcon(tray: TrayIcon, icon: string): void;
|
|
25
26
|
export declare function startTrayIconAnimation(tray: TrayIcon, frames: readonly string[], intervalMs: number): () => void;
|
|
27
|
+
export declare function stopTrayIconAnimation(tray: TrayIcon): void;
|
|
26
28
|
//# sourceMappingURL=tray.d.ts.map
|
package/dist/tray.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tray.d.ts","sourceRoot":"","sources":["../src/tray.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,gBAAgB,EAChB,aAAa,EACb,WAAW,EACX,kBAAkB,EAClB,gBAAgB,EAChB,aAAa,EACb,QAAQ,EACR,eAAe,EAChB,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"tray.d.ts","sourceRoot":"","sources":["../src/tray.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,gBAAgB,EAChB,aAAa,EACb,WAAW,EACX,kBAAkB,EAClB,gBAAgB,EAChB,aAAa,EACb,QAAQ,EACR,eAAe,EAChB,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAgB5D,wBAAgB,cAAc,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAC,eAAe,CAAC,GAAG,QAAQ,GAAG,IAAI,CAGnF;AAKD,wBAAgB,oBAAoB,IAAI,WAAW,CAmElD;AAOD,wBAAgB,eAAe,CAAC,IAAI,EAAE,QAAQ,GAAG,IAAI,CAGpD;AAID,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,CAAC,kBAAkB,CAAC,GAAG,IAAI,CAE9F;AAGD,wBAAgB,cAAc,IAAI,WAAW,CAG5C;AAKD,wBAAgB,mBAAmB,IAAI,QAAQ,CAAC,gBAAgB,CAAC,CAEhE;AAID,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,QAAQ,GAAG,QAAQ,CAAC,aAAa,CAAC,GAAG,IAAI,CAEhF;AAGD,wBAAgB,YAAY,IAAI,SAAS,QAAQ,EAAE,CAIlD;AAGD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,QAAQ,GAAG,MAAM,CAEvD;AAGD,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,QAAQ,GAAG,MAAM,CAEzD;AAID,wBAAgB,eAAe,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAEvD;AAID,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAE3D;AAKD,wBAAgB,WAAW,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,QAAQ,CAAC,aAAa,CAAC,KAAK,IAAI,GAAG,MAAM,IAAI,CAE1F;AAKD,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,QAAQ,EAAE,QAAQ,CAAC,EAAE,QAAQ,CAAC,WAAW,CAAC,GAAG,IAAI,CAE3F;AAGD,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,QAAQ,GAAG,IAAI,CAEtD;AAGD,wBAAgB,cAAc,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI,GAAG,IAAI,CAEhE;AAID,wBAAgB,WAAW,CAAC,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAE9D;AAID,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,SAAS,gBAAgB,EAAE,GAAG,IAAI,CAE/F;AAKD,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,OAAO,GAAG,IAAI,CAE7E;AAGD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAEpE;AAGD,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAExE;AAID,wBAAgB,8BAA8B,CAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,GAAG,IAAI,CAEpF;AAID,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAErE;AAcD,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,MAAM,EAAE,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,IAAI,CAgBhH;AAMD,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,QAAQ,GAAG,IAAI,CAK1D"}
|
package/dist/tray.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
// Web tray capability constants. Web has no system tray — all capabilities are false.
|
|
1
|
+
// Web tray capability constants. Web has no system tray — all capabilities are false. Readonly and
|
|
2
|
+
// shared: getCapabilities hands this same object to every caller, so a mutable one would let any of
|
|
3
|
+
// them rewrite what the backend reports to all the others.
|
|
2
4
|
const WEB_CAPABILITIES = {
|
|
3
5
|
balloon: false,
|
|
4
6
|
bounds: false,
|
|
@@ -85,7 +87,12 @@ export function createWebTrayBackend() {
|
|
|
85
87
|
};
|
|
86
88
|
}
|
|
87
89
|
// Destroys a tray icon and frees its host resource. No-op when the host has no tray.
|
|
90
|
+
// Any icon animation running on this tray is stopped first: the interval writes through setTrayIcon,
|
|
91
|
+
// and left running it would keep calling into a resource the host has freed — forever, since the
|
|
92
|
+
// timer holds its own closure alive. Destroying the thing being animated is the clearest possible
|
|
93
|
+
// signal that the animation is over, so the caller does not have to have kept the stop function.
|
|
88
94
|
export function destroyTrayIcon(tray) {
|
|
95
|
+
stopTrayIconAnimation(tray);
|
|
89
96
|
getTrayBackend().destroy(tray.id);
|
|
90
97
|
}
|
|
91
98
|
// Displays a Windows balloon notification from the tray icon. No-op on macOS/Linux and on web.
|
|
@@ -129,6 +136,11 @@ export function getTrayIconTooltip(tray) {
|
|
|
129
136
|
export function isTrayDestroyed(tray) {
|
|
130
137
|
return getTrayBackend().isDestroyed(tray.id);
|
|
131
138
|
}
|
|
139
|
+
// True while an icon animation started by startTrayIconAnimation is running on this tray. False once
|
|
140
|
+
// it is stopped, replaced by a later start, or the tray is destroyed.
|
|
141
|
+
export function isTrayIconAnimating(tray) {
|
|
142
|
+
return _animations.has(tray.id);
|
|
143
|
+
}
|
|
132
144
|
// Subscribes to tray icon events, delivering a rich TrayEventData payload (id, type, bounds,
|
|
133
145
|
// position, modifier keys, and drop payloads). Returns an unsubscribe function. On web this
|
|
134
146
|
// never fires (no tray); a native host is required.
|
|
@@ -183,21 +195,51 @@ export function setTrayIgnoreDoubleClickEvents(tray, ignore) {
|
|
|
183
195
|
export function setTrayPressedIcon(tray, icon) {
|
|
184
196
|
getTrayBackend().setPressedIcon(tray.id, icon);
|
|
185
197
|
}
|
|
186
|
-
// Starts an animated icon sequence by cycling through the given frames at the specified interval
|
|
187
|
-
//
|
|
188
|
-
//
|
|
189
|
-
//
|
|
198
|
+
// Starts an animated icon sequence by cycling through the given frames at the specified interval,
|
|
199
|
+
// returning a stop function. An empty frame list is a no-op. The tray icon is not destroyed when the
|
|
200
|
+
// animation stops, and the last frame shown stays.
|
|
201
|
+
//
|
|
202
|
+
// A tray icon has one image, so it has one animation: starting a second on the same tray replaces the
|
|
203
|
+
// first rather than running both. Without that, a perfectly ordinary sequence — swap a "syncing"
|
|
204
|
+
// animation for an "error" one — left two intervals writing to the same icon on the same tick, and
|
|
205
|
+
// the only handle to the orphaned one was a stop function the caller had no reason to still be
|
|
206
|
+
// holding. The returned function stops only the animation it started, so calling a stale one cannot
|
|
207
|
+
// cancel a newer animation.
|
|
208
|
+
//
|
|
190
209
|
// Note: interval timing is best-effort; the actual frame rate depends on the host event loop.
|
|
191
210
|
export function startTrayIconAnimation(tray, frames, intervalMs) {
|
|
192
211
|
if (frames.length === 0)
|
|
193
|
-
return
|
|
212
|
+
return _noopStop;
|
|
213
|
+
stopTrayIconAnimation(tray);
|
|
194
214
|
let index = 0;
|
|
195
215
|
setTrayIcon(tray, frames[index]);
|
|
196
216
|
const handle = setInterval(() => {
|
|
197
217
|
index = (index + 1) % frames.length;
|
|
198
218
|
setTrayIcon(tray, frames[index]);
|
|
199
219
|
}, intervalMs);
|
|
200
|
-
|
|
220
|
+
_animations.set(tray.id, handle);
|
|
221
|
+
return () => {
|
|
222
|
+
// Only clear if this animation is still the current one; a later start already replaced it.
|
|
223
|
+
if (_animations.get(tray.id) !== handle)
|
|
224
|
+
return;
|
|
225
|
+
clearInterval(handle);
|
|
226
|
+
_animations.delete(tray.id);
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
// Stops the icon animation running on this tray, if any. Idempotent, and safe on a tray that never
|
|
230
|
+
// animated. The named counterpart to startTrayIconAnimation for callers that hold the TrayIcon but
|
|
231
|
+
// not the stop function it returned — which is most of them, since the handle is what gets passed
|
|
232
|
+
// around and the closure is what gets dropped.
|
|
233
|
+
export function stopTrayIconAnimation(tray) {
|
|
234
|
+
const handle = _animations.get(tray.id);
|
|
235
|
+
if (handle === undefined)
|
|
236
|
+
return;
|
|
237
|
+
clearInterval(handle);
|
|
238
|
+
_animations.delete(tray.id);
|
|
201
239
|
}
|
|
202
240
|
let _backend = null;
|
|
241
|
+
// The interval per animating tray id. Module-scoped like the backend, and correct at that scope: an
|
|
242
|
+
// id is the host's, so one id is one icon is one animation.
|
|
243
|
+
const _animations = new Map();
|
|
244
|
+
function _noopStop() { }
|
|
203
245
|
//# sourceMappingURL=tray.js.map
|
package/dist/tray.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tray.js","sourceRoot":"","sources":["../src/tray.ts"],"names":[],"mappings":"AAYA,
|
|
1
|
+
{"version":3,"file":"tray.js","sourceRoot":"","sources":["../src/tray.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flighthq/tray",
|
|
3
|
-
"version": "0.3.0-next.
|
|
3
|
+
"version": "0.3.0-next.1162.e90e518",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "https://github.com/flighthq/flight.git",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"clean:dist": "tsx ../../scripts/clean-package-dist.ts"
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@flighthq/types": "0.3.0-next.
|
|
39
|
+
"@flighthq/types": "0.3.0-next.1162.e90e518"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"typescript": "^5.3.0"
|
package/src/tray.test.ts
CHANGED
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
getTrayIconTitle,
|
|
21
21
|
getTrayIconTooltip,
|
|
22
22
|
isTrayDestroyed,
|
|
23
|
+
isTrayIconAnimating,
|
|
23
24
|
onTrayEvent,
|
|
24
25
|
popupTrayContextMenu,
|
|
25
26
|
removeTrayBalloon,
|
|
@@ -32,6 +33,7 @@ import {
|
|
|
32
33
|
setTrayIgnoreDoubleClickEvents,
|
|
33
34
|
setTrayPressedIcon,
|
|
34
35
|
startTrayIconAnimation,
|
|
36
|
+
stopTrayIconAnimation,
|
|
35
37
|
} from './tray';
|
|
36
38
|
|
|
37
39
|
// Fake backend state per icon.
|
|
@@ -54,6 +56,9 @@ function fakeBackend(caps: Partial<TrayCapabilities> = {}): TrayBackend & {
|
|
|
54
56
|
capabilities: TrayCapabilities;
|
|
55
57
|
fireEvent(event: TrayEventData): void;
|
|
56
58
|
lastPopupPosition: { x: number; y: number } | null;
|
|
59
|
+
// Every icon written, in order — the animation tests assert on the sequence of writes rather than
|
|
60
|
+
// the final image, because two competing animations can leave a correct-looking final frame.
|
|
61
|
+
setIconCalls: string[];
|
|
57
62
|
trays: Map<number, FakeTray>;
|
|
58
63
|
} {
|
|
59
64
|
const capabilities: TrayCapabilities = {
|
|
@@ -68,9 +73,12 @@ function fakeBackend(caps: Partial<TrayCapabilities> = {}): TrayBackend & {
|
|
|
68
73
|
let eventListener: ((event: Readonly<TrayEventData>) => void) | null = null;
|
|
69
74
|
const trays = new Map<number, FakeTray>();
|
|
70
75
|
|
|
76
|
+
const setIconCalls: string[] = [];
|
|
77
|
+
|
|
71
78
|
return {
|
|
72
79
|
capabilities,
|
|
73
80
|
trays,
|
|
81
|
+
setIconCalls,
|
|
74
82
|
lastPopupPosition: null,
|
|
75
83
|
create(options) {
|
|
76
84
|
const id = nextId++;
|
|
@@ -129,6 +137,7 @@ function fakeBackend(caps: Partial<TrayCapabilities> = {}): TrayBackend & {
|
|
|
129
137
|
if (tray) tray.contextMenu = items;
|
|
130
138
|
},
|
|
131
139
|
setIcon(id, icon) {
|
|
140
|
+
setIconCalls.push(icon);
|
|
132
141
|
const tray = trays.get(id);
|
|
133
142
|
if (tray) tray.icon = icon;
|
|
134
143
|
},
|
|
@@ -396,6 +405,43 @@ describe('isTrayDestroyed', () => {
|
|
|
396
405
|
});
|
|
397
406
|
});
|
|
398
407
|
|
|
408
|
+
describe('isTrayIconAnimating', () => {
|
|
409
|
+
beforeEach(() => {
|
|
410
|
+
vi.useFakeTimers();
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
afterEach(() => {
|
|
414
|
+
vi.useRealTimers();
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
it('is false before, true during, and false after an animation', () => {
|
|
418
|
+
setTrayBackend(fakeBackend());
|
|
419
|
+
const tray = createTrayIcon({ icon: 'a.png' })!;
|
|
420
|
+
expect(isTrayIconAnimating(tray)).toBe(false);
|
|
421
|
+
const stop = startTrayIconAnimation(tray, ['a.png', 'b.png'], 100);
|
|
422
|
+
expect(isTrayIconAnimating(tray)).toBe(true);
|
|
423
|
+
stop();
|
|
424
|
+
expect(isTrayIconAnimating(tray)).toBe(false);
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
it('is false for an empty frame list, which starts nothing', () => {
|
|
428
|
+
setTrayBackend(fakeBackend());
|
|
429
|
+
const tray = createTrayIcon({ icon: 'a.png' })!;
|
|
430
|
+
startTrayIconAnimation(tray, [], 100);
|
|
431
|
+
expect(isTrayIconAnimating(tray)).toBe(false);
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
it('tracks each tray independently', () => {
|
|
435
|
+
setTrayBackend(fakeBackend());
|
|
436
|
+
const first = createTrayIcon({ icon: 'a.png' })!;
|
|
437
|
+
const second = createTrayIcon({ icon: 'b.png' })!;
|
|
438
|
+
startTrayIconAnimation(first, ['a.png', 'b.png'], 100);
|
|
439
|
+
expect(isTrayIconAnimating(first)).toBe(true);
|
|
440
|
+
expect(isTrayIconAnimating(second)).toBe(false);
|
|
441
|
+
stopTrayIconAnimation(first);
|
|
442
|
+
});
|
|
443
|
+
});
|
|
444
|
+
|
|
399
445
|
describe('onTrayEvent', () => {
|
|
400
446
|
it('delivers rich TrayEventData via the active backend', () => {
|
|
401
447
|
const backend = fakeBackend();
|
|
@@ -645,3 +691,85 @@ describe('startTrayIconAnimation', () => {
|
|
|
645
691
|
expect(() => stop()).not.toThrow();
|
|
646
692
|
});
|
|
647
693
|
});
|
|
694
|
+
|
|
695
|
+
describe('stopTrayIconAnimation', () => {
|
|
696
|
+
beforeEach(() => {
|
|
697
|
+
vi.useFakeTimers();
|
|
698
|
+
});
|
|
699
|
+
|
|
700
|
+
afterEach(() => {
|
|
701
|
+
vi.useRealTimers();
|
|
702
|
+
});
|
|
703
|
+
|
|
704
|
+
it('stops the interval so no further frames are written', () => {
|
|
705
|
+
const backend = fakeBackend();
|
|
706
|
+
setTrayBackend(backend);
|
|
707
|
+
const tray = createTrayIcon({ icon: 'frame0.png' })!;
|
|
708
|
+
startTrayIconAnimation(tray, ['frame0.png', 'frame1.png'], 100);
|
|
709
|
+
stopTrayIconAnimation(tray);
|
|
710
|
+
const iconAtStop = backend.trays.get(tray.id)!.icon;
|
|
711
|
+
vi.advanceTimersByTime(500);
|
|
712
|
+
expect(backend.trays.get(tray.id)!.icon).toBe(iconAtStop);
|
|
713
|
+
});
|
|
714
|
+
|
|
715
|
+
it('is idempotent and safe on a tray that never animated', () => {
|
|
716
|
+
setTrayBackend(fakeBackend());
|
|
717
|
+
const tray = createTrayIcon({ icon: 'a.png' })!;
|
|
718
|
+
expect(() => {
|
|
719
|
+
stopTrayIconAnimation(tray);
|
|
720
|
+
stopTrayIconAnimation(tray);
|
|
721
|
+
}).not.toThrow();
|
|
722
|
+
});
|
|
723
|
+
});
|
|
724
|
+
|
|
725
|
+
describe('tray icon animation lifecycle', () => {
|
|
726
|
+
beforeEach(() => {
|
|
727
|
+
vi.useFakeTimers();
|
|
728
|
+
});
|
|
729
|
+
|
|
730
|
+
afterEach(() => {
|
|
731
|
+
vi.useRealTimers();
|
|
732
|
+
});
|
|
733
|
+
|
|
734
|
+
it('replaces a running animation instead of running both', () => {
|
|
735
|
+
// The regression this pins: two starts on one tray left two intervals writing to the same icon on
|
|
736
|
+
// the same tick — the icon flickered between the two sequences, and the only handle to the first
|
|
737
|
+
// was a stop function the caller had no reason to still hold.
|
|
738
|
+
const backend = fakeBackend();
|
|
739
|
+
setTrayBackend(backend);
|
|
740
|
+
const tray = createTrayIcon({ icon: 'a0.png' })!;
|
|
741
|
+
startTrayIconAnimation(tray, ['a0.png', 'a1.png'], 100);
|
|
742
|
+
startTrayIconAnimation(tray, ['b0.png', 'b1.png'], 100);
|
|
743
|
+
backend.setIconCalls.length = 0;
|
|
744
|
+
vi.advanceTimersByTime(100);
|
|
745
|
+
expect(backend.setIconCalls).toEqual(['b1.png']);
|
|
746
|
+
});
|
|
747
|
+
|
|
748
|
+
it('stops animating a tray that has been destroyed', () => {
|
|
749
|
+
// The regression this pins: the interval outlived the icon, calling setIcon on a destroyed tray
|
|
750
|
+
// forever — and the timer kept its own closure alive, so nothing could collect it.
|
|
751
|
+
const backend = fakeBackend();
|
|
752
|
+
setTrayBackend(backend);
|
|
753
|
+
const tray = createTrayIcon({ icon: 'a0.png' })!;
|
|
754
|
+
startTrayIconAnimation(tray, ['a0.png', 'a1.png'], 100);
|
|
755
|
+
destroyTrayIcon(tray);
|
|
756
|
+
backend.setIconCalls.length = 0;
|
|
757
|
+
vi.advanceTimersByTime(500);
|
|
758
|
+
expect(backend.setIconCalls).toEqual([]);
|
|
759
|
+
expect(isTrayIconAnimating(tray)).toBe(false);
|
|
760
|
+
});
|
|
761
|
+
|
|
762
|
+
it('a stale stop function cannot cancel the animation that replaced it', () => {
|
|
763
|
+
const backend = fakeBackend();
|
|
764
|
+
setTrayBackend(backend);
|
|
765
|
+
const tray = createTrayIcon({ icon: 'a0.png' })!;
|
|
766
|
+
const staleStop = startTrayIconAnimation(tray, ['a0.png', 'a1.png'], 100);
|
|
767
|
+
startTrayIconAnimation(tray, ['b0.png', 'b1.png'], 100);
|
|
768
|
+
staleStop();
|
|
769
|
+
expect(isTrayIconAnimating(tray)).toBe(true);
|
|
770
|
+
backend.setIconCalls.length = 0;
|
|
771
|
+
vi.advanceTimersByTime(100);
|
|
772
|
+
expect(backend.setIconCalls).toEqual(['b1.png']);
|
|
773
|
+
stopTrayIconAnimation(tray);
|
|
774
|
+
});
|
|
775
|
+
});
|