@flighthq/share 0.4.0-next.1898.266773a → 0.4.0-next.1908.751021c
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/share.d.ts +5 -0
- package/dist/share.d.ts.map +1 -1
- package/dist/share.js +55 -64
- package/dist/share.js.map +1 -1
- package/package.json +3 -3
- package/src/share.test.ts +90 -8
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { attachShareSignals, canShareContent, detachShareSignals, disposeShareSignals, enableShareSignals, hasShareContentFields, isShareAvailable, shareContent, shareContentWithResult, shareFiles, shareText, shareUrl, } from './contract';
|
|
1
|
+
export { explainShareBackend, attachShareSignals, canShareContent, detachShareSignals, disposeShareSignals, enableShareSignals, hasShareContentFields, isShareAvailable, shareContent, shareContentWithResult, shareFiles, shareText, shareUrl, } 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,kBAAkB,EAClB,eAAe,EACf,kBAAkB,EAClB,mBAAmB,EACnB,kBAAkB,EAClB,qBAAqB,EACrB,gBAAgB,EAChB,YAAY,EACZ,sBAAsB,EACtB,UAAU,EACV,SAAS,EACT,QAAQ,GACT,MAAM,YAAY,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,mBAAmB,EACnB,kBAAkB,EAClB,eAAe,EACf,kBAAkB,EAClB,mBAAmB,EACnB,kBAAkB,EAClB,qBAAqB,EACrB,gBAAgB,EAChB,YAAY,EACZ,sBAAsB,EACtB,UAAU,EACV,SAAS,EACT,QAAQ,GACT,MAAM,YAAY,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { attachShareSignals, canShareContent, detachShareSignals, disposeShareSignals, enableShareSignals, hasShareContentFields, isShareAvailable, shareContent, shareContentWithResult, shareFiles, shareText, shareUrl, } from './contract';
|
|
1
|
+
export { explainShareBackend, attachShareSignals, canShareContent, detachShareSignals, disposeShareSignals, enableShareSignals, hasShareContentFields, isShareAvailable, shareContent, shareContentWithResult, shareFiles, shareText, shareUrl, } 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,kBAAkB,EAClB,eAAe,EACf,kBAAkB,EAClB,mBAAmB,EACnB,kBAAkB,EAClB,qBAAqB,EACrB,gBAAgB,EAChB,YAAY,EACZ,sBAAsB,EACtB,UAAU,EACV,SAAS,EACT,QAAQ,GACT,MAAM,YAAY,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,mBAAmB,EACnB,kBAAkB,EAClB,eAAe,EACf,kBAAkB,EAClB,mBAAmB,EACnB,kBAAkB,EAClB,qBAAqB,EACrB,gBAAgB,EAChB,YAAY,EACZ,sBAAsB,EACtB,UAAU,EACV,SAAS,EACT,QAAQ,GACT,MAAM,YAAY,CAAC"}
|
package/dist/share.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { BackendExplanation } from '@flighthq/types/contract';
|
|
1
2
|
import type { ShareBackend, ShareContent, ShareFile, ShareOptions, ShareResult, ShareSignals } from '@flighthq/types/contract';
|
|
2
3
|
export declare function attachShareSignals(signals: ShareSignals): void;
|
|
3
4
|
export declare function canShareContent(content: Readonly<ShareContent>): boolean;
|
|
@@ -5,9 +6,13 @@ export declare function createWebShareBackend(): ShareBackend;
|
|
|
5
6
|
export declare function detachShareSignals(signals: ShareSignals): void;
|
|
6
7
|
export declare function disposeShareSignals(signals: ShareSignals): void;
|
|
7
8
|
export declare function enableShareSignals(): ShareSignals;
|
|
9
|
+
export declare function explainShareBackend(): BackendExplanation;
|
|
8
10
|
export declare function getShareBackend(): ShareBackend;
|
|
9
11
|
export declare function hasShareContentFields(content: Readonly<ShareContent>): boolean;
|
|
12
|
+
export declare function installShareHostBackend(backend: ShareBackend): void;
|
|
10
13
|
export declare function isShareAvailable(): boolean;
|
|
14
|
+
export declare function observeShareHostResult(operation: string, succeeded: boolean): void;
|
|
15
|
+
export declare function resetShareBackendForTest(): void;
|
|
11
16
|
export declare function setShareBackend(backend: ShareBackend | null): void;
|
|
12
17
|
export declare function shareContent(content: Readonly<ShareContent>, options?: Readonly<ShareOptions>): Promise<boolean>;
|
|
13
18
|
export declare function shareContentWithResult(content: Readonly<ShareContent>, options?: Readonly<ShareOptions>): Promise<ShareResult>;
|
package/dist/share.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"share.d.ts","sourceRoot":"","sources":["../src/share.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,YAAY,EACZ,YAAY,EACZ,SAAS,EACT,YAAY,EACZ,WAAW,EACX,YAAY,EACb,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"share.d.ts","sourceRoot":"","sources":["../src/share.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AACnE,OAAO,KAAK,EACV,YAAY,EACZ,YAAY,EACZ,SAAS,EACT,YAAY,EACZ,WAAW,EACX,YAAY,EACb,MAAM,0BAA0B,CAAC;AAElC,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,YAAY,GAAG,IAAI,CAE9D;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,QAAQ,CAAC,YAAY,CAAC,GAAG,OAAO,CAExE;AAED,wBAAgB,qBAAqB,IAAI,YAAY,CA2CpD;AAED,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,YAAY,GAAG,IAAI,CAE9D;AAED,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,YAAY,GAAG,IAAI,CAE/D;AAED,wBAAgB,kBAAkB,IAAI,YAAY,CAIjD;AAED,wBAAgB,mBAAmB,IAAI,kBAAkB,CAaxD;AAED,wBAAgB,eAAe,IAAI,YAAY,CAE9C;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,QAAQ,CAAC,YAAY,CAAC,GAAG,OAAO,CAM9E;AAED,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,YAAY,GAAG,IAAI,CAMnE;AAED,wBAAgB,gBAAgB,IAAI,OAAO,CAE1C;AAED,wBAAgB,sBAAsB,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,GAAG,IAAI,CAKlF;AAED,wBAAgB,wBAAwB,IAAI,IAAI,CAK/C;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,YAAY,GAAG,IAAI,GAAG,IAAI,CAElE;AAED,wBAAgB,YAAY,CAAC,OAAO,EAAE,QAAQ,CAAC,YAAY,CAAC,EAAE,OAAO,CAAC,EAAE,QAAQ,CAAC,YAAY,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,CAGhH;AAED,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,QAAQ,CAAC,YAAY,CAAC,EAC/B,OAAO,CAAC,EAAE,QAAQ,CAAC,YAAY,CAAC,GAC/B,OAAO,CAAC,WAAW,CAAC,CAStB;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,SAAS,SAAS,EAAE,EAAE,OAAO,CAAC,EAAE,QAAQ,CAAC,YAAY,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,CAE1G;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,QAAQ,CAAC,YAAY,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,CAE1F;AAED,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,QAAQ,CAAC,YAAY,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,CAExF"}
|
package/dist/share.js
CHANGED
|
@@ -1,27 +1,10 @@
|
|
|
1
1
|
import { createSignal, emitSignal } from '@flighthq/signals/contract';
|
|
2
|
-
// Attaches `signals` to receive share result events emitted by shareContentWithResult calls.
|
|
3
|
-
// Idempotent by construction — the attached groups are a set, so attaching twice delivers once.
|
|
4
|
-
// Pair with detachShareSignals / disposeShareSignals.
|
|
5
|
-
//
|
|
6
|
-
// Note: shareContent (the boolean convenience wrapper) does NOT emit signals — only
|
|
7
|
-
// shareContentWithResult emits onShareResult. If you need signals on every share, use
|
|
8
|
-
// shareContentWithResult directly.
|
|
9
2
|
export function attachShareSignals(signals) {
|
|
10
3
|
_attachedSignals.add(signals);
|
|
11
4
|
}
|
|
12
|
-
// True when the active backend can share the given content. Returns false when sharing is
|
|
13
|
-
// unavailable or the content is not shareable by this platform (e.g. a MIME type not accepted by
|
|
14
|
-
// the share sheet). Distinct from isShareAvailable: that probe asks whether sharing is possible at
|
|
15
|
-
// all; this asks whether this specific content is shareable.
|
|
16
5
|
export function canShareContent(content) {
|
|
17
6
|
return getShareBackend().canShare(content);
|
|
18
7
|
}
|
|
19
|
-
// Builds the default web backend over navigator.share / navigator.canShare.
|
|
20
|
-
// share and shareWithResult resolve to false/dismissed=true when the Web Share API is absent (jsdom,
|
|
21
|
-
// unsupported browsers) or the user cancels. canShare returns false when the API is absent or the
|
|
22
|
-
// platform cannot share the given content. isAvailable returns true when navigator.share exists.
|
|
23
|
-
// Files are converted from ShareFile (data URL) to DOM File at the boundary; conversion errors
|
|
24
|
-
// fall back to false.
|
|
25
8
|
export function createWebShareBackend() {
|
|
26
9
|
return {
|
|
27
10
|
isAvailable() {
|
|
@@ -61,39 +44,40 @@ export function createWebShareBackend() {
|
|
|
61
44
|
return { completed: true, activityType: null, dismissed: false };
|
|
62
45
|
}
|
|
63
46
|
catch (err) {
|
|
64
|
-
// AbortError means the user explicitly cancelled; other errors are failures.
|
|
65
47
|
const dismissed = err instanceof Error && err.name === 'AbortError';
|
|
66
48
|
return { completed: false, activityType: null, dismissed };
|
|
67
49
|
}
|
|
68
50
|
},
|
|
69
51
|
};
|
|
70
52
|
}
|
|
71
|
-
// Stops delivery to `signals` and forgets its subscription. Safe to call when not attached.
|
|
72
53
|
export function detachShareSignals(signals) {
|
|
73
54
|
_attachedSignals.delete(signals);
|
|
74
55
|
}
|
|
75
|
-
// Releases `signals` for garbage collection by detaching its subscription. The signals remain plain
|
|
76
|
-
// GC-managed memory afterward.
|
|
77
56
|
export function disposeShareSignals(signals) {
|
|
78
57
|
detachShareSignals(signals);
|
|
79
58
|
}
|
|
80
|
-
// Enables a signals group for share result events. Signals stay inert until attachShareSignals is
|
|
81
|
-
// called. This is the opt-in; the cost is assumed when attached.
|
|
82
59
|
export function enableShareSignals() {
|
|
83
60
|
return {
|
|
84
61
|
onShareResult: createSignal(),
|
|
85
62
|
};
|
|
86
63
|
}
|
|
87
|
-
|
|
64
|
+
export function explainShareBackend() {
|
|
65
|
+
if (_custom !== null) {
|
|
66
|
+
return { conflict: _hostConflict, layer: 'custom', operation: null, viability: 'unobserved' };
|
|
67
|
+
}
|
|
68
|
+
if (_host !== null) {
|
|
69
|
+
return {
|
|
70
|
+
conflict: _hostConflict,
|
|
71
|
+
layer: 'host',
|
|
72
|
+
operation: _hostObservation !== null ? _hostObservation.operation : null,
|
|
73
|
+
viability: _hostObservation !== null ? _hostObservation.viability : 'unobserved',
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
return { conflict: false, layer: 'host-not-enabled', operation: null, viability: 'unobserved' };
|
|
77
|
+
}
|
|
88
78
|
export function getShareBackend() {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
return _backend;
|
|
92
|
-
}
|
|
93
|
-
// True when content has at least one populated field (title, text, url, or a non-empty files array).
|
|
94
|
-
// The Web Share API requires at least one field to be present; calling shareContent with an empty
|
|
95
|
-
// payload will throw on some engines, which shareContent swallows to false. Use this to detect
|
|
96
|
-
// an obviously-empty payload before calling shareContent.
|
|
79
|
+
return _custom ?? _host ?? _sentinel;
|
|
80
|
+
}
|
|
97
81
|
export function hasShareContentFields(content) {
|
|
98
82
|
if (content.title !== undefined && content.title !== '')
|
|
99
83
|
return true;
|
|
@@ -105,30 +89,37 @@ export function hasShareContentFields(content) {
|
|
|
105
89
|
return true;
|
|
106
90
|
return false;
|
|
107
91
|
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
92
|
+
export function installShareHostBackend(backend) {
|
|
93
|
+
if (_host !== null) {
|
|
94
|
+
if (_host !== backend)
|
|
95
|
+
_hostConflict = true;
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
_host = backend;
|
|
99
|
+
}
|
|
111
100
|
export function isShareAvailable() {
|
|
112
101
|
return getShareBackend().isAvailable();
|
|
113
102
|
}
|
|
114
|
-
|
|
103
|
+
export function observeShareHostResult(operation, succeeded) {
|
|
104
|
+
_hostObservation = {
|
|
105
|
+
operation,
|
|
106
|
+
viability: succeeded ? 'available' : 'runtime-api-unavailable',
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
export function resetShareBackendForTest() {
|
|
110
|
+
_custom = null;
|
|
111
|
+
_host = null;
|
|
112
|
+
_hostConflict = false;
|
|
113
|
+
_hostObservation = null;
|
|
114
|
+
}
|
|
115
115
|
export function setShareBackend(backend) {
|
|
116
|
-
|
|
116
|
+
_custom = backend;
|
|
117
117
|
}
|
|
118
|
-
// Opens the native share sheet with the given content. Resolves true on success, false when the host
|
|
119
|
-
// denies, the user cancels, or sharing is unavailable. An empty content payload (no title/text/url/
|
|
120
|
-
// files) is caught by hasShareContentFields and returns false immediately rather than forwarding to the
|
|
121
|
-
// backend (which may throw). Pass options to control presentation on native hosts (parentWindow,
|
|
122
|
-
// sourceRect on iPad).
|
|
123
118
|
export function shareContent(content, options) {
|
|
124
119
|
if (!hasShareContentFields(content))
|
|
125
120
|
return Promise.resolve(false);
|
|
126
121
|
return getShareBackend().share(content, options);
|
|
127
122
|
}
|
|
128
|
-
// Opens the native share sheet and returns a full ShareResult describing completion, cancellation,
|
|
129
|
-
// and which activity/app was chosen. Emits onShareResult on all attached ShareSignals groups.
|
|
130
|
-
// An empty content payload returns { completed: false, activityType: null, dismissed: false }
|
|
131
|
-
// immediately. Pass options to control presentation on native hosts.
|
|
132
123
|
export async function shareContentWithResult(content, options) {
|
|
133
124
|
if (!hasShareContentFields(content)) {
|
|
134
125
|
return { completed: false, activityType: null, dismissed: false };
|
|
@@ -139,27 +130,34 @@ export async function shareContentWithResult(content, options) {
|
|
|
139
130
|
}
|
|
140
131
|
return result;
|
|
141
132
|
}
|
|
142
|
-
// Opens the share sheet with a file payload. A convenience wrapper over shareContent, completing the
|
|
143
|
-
// trio with shareText / shareUrl — files are the third thing the Web Share Level 2 payload carries,
|
|
144
|
-
// and leaving it out meant the one payload kind that needs a descriptor was also the one with no
|
|
145
|
-
// shorthand. An empty array resolves false, like any other empty payload.
|
|
146
133
|
export function shareFiles(files, options) {
|
|
147
134
|
return shareContent({ files }, options);
|
|
148
135
|
}
|
|
149
|
-
// Opens the share sheet with a plain text payload. A convenience wrapper over shareContent.
|
|
150
136
|
export function shareText(text, options) {
|
|
151
137
|
return shareContent({ text }, options);
|
|
152
138
|
}
|
|
153
|
-
// Opens the share sheet with a URL payload. A convenience wrapper over shareContent.
|
|
154
139
|
export function shareUrl(url, options) {
|
|
155
140
|
return shareContent({ url }, options);
|
|
156
141
|
}
|
|
157
|
-
let
|
|
158
|
-
|
|
159
|
-
|
|
142
|
+
let _custom = null;
|
|
143
|
+
let _host = null;
|
|
144
|
+
let _hostConflict = false;
|
|
145
|
+
let _hostObservation = null;
|
|
146
|
+
const _sentinel = {
|
|
147
|
+
canShare() {
|
|
148
|
+
return false;
|
|
149
|
+
},
|
|
150
|
+
isAvailable() {
|
|
151
|
+
return false;
|
|
152
|
+
},
|
|
153
|
+
async share() {
|
|
154
|
+
return false;
|
|
155
|
+
},
|
|
156
|
+
async shareWithResult() {
|
|
157
|
+
return { completed: false, activityType: null, dismissed: false };
|
|
158
|
+
},
|
|
159
|
+
};
|
|
160
160
|
const _attachedSignals = new Set();
|
|
161
|
-
// Converts a ShareContent (with portable ShareFile descriptors) to the navigator.share data shape,
|
|
162
|
-
// converting ShareFile data URLs to DOM File objects at the web boundary.
|
|
163
161
|
function shareContentToNavigatorData(content) {
|
|
164
162
|
const data = {};
|
|
165
163
|
if (content.title !== undefined)
|
|
@@ -173,14 +171,7 @@ function shareContentToNavigatorData(content) {
|
|
|
173
171
|
}
|
|
174
172
|
return data;
|
|
175
173
|
}
|
|
176
|
-
// Converts a portable ShareFile descriptor to a DOM File for navigator.share / navigator.canShare.
|
|
177
|
-
// Throws on a data URL with no comma, which the backend's try/catch turns into the ordinary false
|
|
178
|
-
// sentinel. That is deliberate: the previous code let a missing comma through as index -1, which
|
|
179
|
-
// makes `substring(0, -1)` empty and `substring(0)` the whole string, so a malformed descriptor
|
|
180
|
-
// produced a plausible-looking File containing the URL text instead of failing. A share that
|
|
181
|
-
// silently sends the wrong bytes is worse than one that reports it could not share.
|
|
182
174
|
function shareFileToDomFile(file) {
|
|
183
|
-
// Parse the data URL: 'data:<mimeType>;base64,<data>' or 'data:<mimeType>,<data>'
|
|
184
175
|
const comma = file.dataUrl.indexOf(',');
|
|
185
176
|
if (comma === -1)
|
|
186
177
|
throw new Error('share: dataUrl is not a data URL (no comma)');
|
package/dist/share.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"share.js","sourceRoot":"","sources":["../src/share.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;
|
|
1
|
+
{"version":3,"file":"share.js","sourceRoot":"","sources":["../src/share.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flighthq/share",
|
|
3
|
-
"version": "0.4.0-next.
|
|
3
|
+
"version": "0.4.0-next.1908.751021c",
|
|
4
4
|
"author": "Joshua Granick and other contributors",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -38,8 +38,8 @@
|
|
|
38
38
|
"clean:dist": "tsx ../../scripts/clean-package-dist.ts"
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@flighthq/signals": "0.4.0-next.
|
|
42
|
-
"@flighthq/types": "0.4.0-next.
|
|
41
|
+
"@flighthq/signals": "0.4.0-next.1908.751021c",
|
|
42
|
+
"@flighthq/types": "0.4.0-next.1908.751021c"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
45
|
"typescript": "^5.3.0"
|
package/src/share.test.ts
CHANGED
|
@@ -10,13 +10,17 @@ import {
|
|
|
10
10
|
enableShareSignals,
|
|
11
11
|
getShareBackend,
|
|
12
12
|
hasShareContentFields,
|
|
13
|
+
installShareHostBackend,
|
|
13
14
|
isShareAvailable,
|
|
15
|
+
resetShareBackendForTest,
|
|
14
16
|
setShareBackend,
|
|
15
17
|
shareContent,
|
|
16
18
|
shareContentWithResult,
|
|
17
19
|
shareFiles,
|
|
18
20
|
shareText,
|
|
19
21
|
shareUrl,
|
|
22
|
+
explainShareBackend,
|
|
23
|
+
observeShareHostResult,
|
|
20
24
|
} from './share';
|
|
21
25
|
|
|
22
26
|
function fakeBackend(
|
|
@@ -57,7 +61,7 @@ function restoreNavigator(): void {
|
|
|
57
61
|
}
|
|
58
62
|
|
|
59
63
|
afterEach(() => {
|
|
60
|
-
|
|
64
|
+
resetShareBackendForTest();
|
|
61
65
|
restoreNavigator();
|
|
62
66
|
});
|
|
63
67
|
|
|
@@ -104,7 +108,7 @@ describe('canShareContent', () => {
|
|
|
104
108
|
expect(canShareContent({ text: 'x' })).toBe(true);
|
|
105
109
|
});
|
|
106
110
|
|
|
107
|
-
it('returns false from the
|
|
111
|
+
it('returns false from the sentinel when no backend is installed', () => {
|
|
108
112
|
expect(canShareContent({ text: 'x' })).toBe(false);
|
|
109
113
|
});
|
|
110
114
|
|
|
@@ -217,8 +221,36 @@ describe('enableShareSignals', () => {
|
|
|
217
221
|
});
|
|
218
222
|
});
|
|
219
223
|
|
|
224
|
+
describe('explainShareBackend', () => {
|
|
225
|
+
afterEach(() => resetShareBackendForTest());
|
|
226
|
+
|
|
227
|
+
it('reports host-not-enabled when no backend is installed', () => {
|
|
228
|
+
resetShareBackendForTest();
|
|
229
|
+
const explanation = explainShareBackend();
|
|
230
|
+
expect(explanation.layer).toBe('host-not-enabled');
|
|
231
|
+
expect(explanation.conflict).toBe(false);
|
|
232
|
+
expect(explanation.viability).toBe('unobserved');
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
it('reports custom layer when a custom backend is set', () => {
|
|
236
|
+
setShareBackend(fakeBackend());
|
|
237
|
+
expect(explainShareBackend().layer).toBe('custom');
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it('reports host layer when a host backend is installed', () => {
|
|
241
|
+
installShareHostBackend(fakeBackend());
|
|
242
|
+
expect(explainShareBackend().layer).toBe('host');
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
it('reports conflict when two different host backends are installed', () => {
|
|
246
|
+
installShareHostBackend(fakeBackend());
|
|
247
|
+
installShareHostBackend(fakeBackend());
|
|
248
|
+
expect(explainShareBackend().conflict).toBe(true);
|
|
249
|
+
});
|
|
250
|
+
});
|
|
251
|
+
|
|
220
252
|
describe('getShareBackend', () => {
|
|
221
|
-
it('falls back to
|
|
253
|
+
it('falls back to the sentinel when no backend is installed', () => {
|
|
222
254
|
expect(getShareBackend()).not.toBeNull();
|
|
223
255
|
});
|
|
224
256
|
|
|
@@ -260,8 +292,27 @@ describe('hasShareContentFields', () => {
|
|
|
260
292
|
});
|
|
261
293
|
});
|
|
262
294
|
|
|
295
|
+
describe('installShareHostBackend', () => {
|
|
296
|
+
afterEach(() => resetShareBackendForTest());
|
|
297
|
+
|
|
298
|
+
it('installs a host backend that getShareBackend returns', () => {
|
|
299
|
+
const backend = fakeBackend();
|
|
300
|
+
installShareHostBackend(backend);
|
|
301
|
+
expect(getShareBackend()).toBe(backend);
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
it('is first-host-wins: a second different backend sets conflict', () => {
|
|
305
|
+
const first = fakeBackend();
|
|
306
|
+
const second = fakeBackend();
|
|
307
|
+
installShareHostBackend(first);
|
|
308
|
+
installShareHostBackend(second);
|
|
309
|
+
expect(getShareBackend()).toBe(first);
|
|
310
|
+
expect(explainShareBackend().conflict).toBe(true);
|
|
311
|
+
});
|
|
312
|
+
});
|
|
313
|
+
|
|
263
314
|
describe('isShareAvailable', () => {
|
|
264
|
-
it('returns false from the
|
|
315
|
+
it('returns false from the sentinel when no backend is installed', () => {
|
|
265
316
|
expect(isShareAvailable()).toBe(false);
|
|
266
317
|
});
|
|
267
318
|
|
|
@@ -270,14 +321,45 @@ describe('isShareAvailable', () => {
|
|
|
270
321
|
expect(isShareAvailable()).toBe(true);
|
|
271
322
|
});
|
|
272
323
|
|
|
273
|
-
it('returns true when navigator.share exists', () => {
|
|
324
|
+
it('returns true when the web backend is installed and navigator.share exists', () => {
|
|
274
325
|
mockNavigator({ share: async () => {} });
|
|
326
|
+
installShareHostBackend(createWebShareBackend());
|
|
275
327
|
expect(isShareAvailable()).toBe(true);
|
|
276
328
|
});
|
|
277
329
|
});
|
|
278
330
|
|
|
331
|
+
describe('observeShareHostResult', () => {
|
|
332
|
+
afterEach(() => resetShareBackendForTest());
|
|
333
|
+
|
|
334
|
+
it('records a successful observation', () => {
|
|
335
|
+
installShareHostBackend(fakeBackend());
|
|
336
|
+
observeShareHostResult('canShare', true);
|
|
337
|
+
const explanation = explainShareBackend();
|
|
338
|
+
expect(explanation.operation).toBe('canShare');
|
|
339
|
+
expect(explanation.viability).toBe('available');
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
it('records a failed observation', () => {
|
|
343
|
+
installShareHostBackend(fakeBackend());
|
|
344
|
+
observeShareHostResult('canShare', false);
|
|
345
|
+
expect(explainShareBackend().viability).toBe('runtime-api-unavailable');
|
|
346
|
+
});
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
describe('resetShareBackendForTest', () => {
|
|
350
|
+
it('clears all backend slots', () => {
|
|
351
|
+
setShareBackend(fakeBackend());
|
|
352
|
+
installShareHostBackend(fakeBackend());
|
|
353
|
+
observeShareHostResult('canShare', true);
|
|
354
|
+
resetShareBackendForTest();
|
|
355
|
+
expect(explainShareBackend().layer).toBe('host-not-enabled');
|
|
356
|
+
expect(explainShareBackend().conflict).toBe(false);
|
|
357
|
+
expect(explainShareBackend().viability).toBe('unobserved');
|
|
358
|
+
});
|
|
359
|
+
});
|
|
360
|
+
|
|
279
361
|
describe('setShareBackend', () => {
|
|
280
|
-
it('clears back to the
|
|
362
|
+
it('clears back to the sentinel when passed null', () => {
|
|
281
363
|
setShareBackend(fakeBackend());
|
|
282
364
|
setShareBackend(null);
|
|
283
365
|
expect(getShareBackend()).not.toBeNull();
|
|
@@ -292,7 +374,7 @@ describe('shareContent', () => {
|
|
|
292
374
|
expect(backend.shared).toEqual({ title: 't', url: 'u' });
|
|
293
375
|
});
|
|
294
376
|
|
|
295
|
-
it('returns false from the
|
|
377
|
+
it('returns false from the sentinel when no backend is installed', async () => {
|
|
296
378
|
expect(await shareContent({ text: 'x' })).toBe(false);
|
|
297
379
|
});
|
|
298
380
|
|
|
@@ -401,7 +483,7 @@ describe('shareContentWithResult', () => {
|
|
|
401
483
|
expect(result.activityType).toBe('com.example.App');
|
|
402
484
|
});
|
|
403
485
|
|
|
404
|
-
it('returns false from
|
|
486
|
+
it('returns false from the sentinel when no backend is installed', async () => {
|
|
405
487
|
const result = await shareContentWithResult({ text: 'x' });
|
|
406
488
|
expect(result.completed).toBe(false);
|
|
407
489
|
});
|